From 3fc624f75d38436b8e2b21d31abfda53d44b9197 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Fri, 19 Jul 2019 14:31:50 -0700 Subject: [PATCH 01/21] Initial Commit --- ApiAsAService/ApiAsAService.sln | 28 + .../ApiAsAService/ApiAsAService.csproj | 120 + ApiAsAService/ApiAsAService/App.config | 34 + .../ApiAsAService/App_Start/WebApiConfig.cs | 18 + ApiAsAService/ApiAsAService/Constants.cs | 16 + .../Controllers/HandleAllController.cs | 152 + .../CustomODataPathRouteConstraint.cs | 205 + .../ApiAsAService/CustomODataRoute.cs | 82 + .../DataSource/AnotherDataSource.cs | 86 + .../DataSource/DataSourceProvider.cs | 57 + .../ApiAsAService/DataSource/IDataSource.cs | 19 + .../ApiAsAService/DataSource/MyDataSource.cs | 81 + .../ApiAsAService/DynamicModelHelper.cs | 52 + .../HttpRequestMessageProvider.cs | 13 + .../IHttpRequestMessageProvider.cs | 13 + .../MatchAllRoutingConvention.cs | 44 + ApiAsAService/ApiAsAService/Program.cs | 70 + .../ApiAsAService/Properties/AssemblyInfo.cs | 36 + ApiAsAService/ApiAsAService/packages.config | 31 + ApiAsAService/Diagrams.pptx | Bin 0 -> 36473 bytes ApiAsAService/RESTier/.editorconfig | 56 + ApiAsAService/RESTier/Directory.Build.props | 97 + ApiAsAService/RESTier/License.txt | 25 + ApiAsAService/RESTier/NuGet.Config | 11 + ApiAsAService/RESTier/README.md | 113 + ApiAsAService/RESTier/RESTier.sln | 112 + ApiAsAService/RESTier/databases/Chinook.bak | Bin 0 -> 5697041 bytes .../RESTier/docs/.openpublishing.build.ps1 | 17 + .../docs/.openpublishing.publish.config.json | 120 + ApiAsAService/RESTier/docs/CODEOWNERS | 8 + ApiAsAService/RESTier/docs/README.md | 13 + ApiAsAService/RESTier/docs/mkdocs.yml | 31 + .../docs/msdocs/clients/dot-net-standard.md | 1 + .../RESTier/docs/msdocs/clients/dot-net.md | 1 + .../RESTier/docs/msdocs/clients/typescript.md | 1 + .../docs/msdocs/contribution-guidelines.md | 73 + ApiAsAService/RESTier/docs/msdocs/docfx.json | 70 + .../additional-operations.md | 69 + .../extending-restier/in-memory-provider.md | 83 + .../extending-restier/temporal-types.md | 77 + .../RESTier/docs/msdocs/getting-started.md | 1 + ApiAsAService/RESTier/docs/msdocs/index.md | 113 + ApiAsAService/RESTier/docs/msdocs/license.md | 1 + .../docs/msdocs/release-notes/0-3-0-beta1.md | 20 + .../docs/msdocs/release-notes/0-3-0-beta2.md | 19 + .../docs/msdocs/release-notes/0-4-0-rc.md | 26 + .../docs/msdocs/release-notes/0-4-0-rc2.md | 8 + .../docs/msdocs/release-notes/0-5-0-beta.md | 32 + .../RESTier/docs/msdocs/server/filters.md | 64 + .../docs/msdocs/server/interceptors.md | 301 + .../msdocs/server/method-authorization.md | 352 + .../docs/msdocs/server/model-building.md | 277 + .../RESTier/docs/msdocs/vs-highlight.css | 81 + .../RESTier/samples/api/Directory.Build.props | 27 + .../api/Microsoft.Restier.Samples.Apis.sln | 22 + .../samples/api/chinook/Directory.Build.props | 27 + .../Microsoft.Restier.Samples.Chinook.Api.sln | 22 + .../App_Start/WebApiConfig.cs | 20 + .../Global.asax | 1 + .../Global.asax.cs | 12 + ...crosoft.Restier.Samples.Chinook.Api.csproj | 199 + .../Models/Album.cs | 29 + .../Models/Artist.cs | 24 + .../Models/ChinookContext.cs | 80 + .../Models/Customer.cs | 61 + .../Models/Employee.cs | 69 + .../Models/Genre.cs | 24 + .../Models/Invoice.cs | 46 + .../Models/InvoiceLine.cs | 23 + .../Models/MediaType.cs | 24 + .../Models/Playlist.cs | 24 + .../Models/Track.cs | 51 + .../Properties/AssemblyInfo.cs | 34 + .../Web.Debug.config | 30 + .../Web.Release.config | 31 + .../Web.config | 79 + .../packages.config | 23 + ...t.Restier.Samples.Chinook.Tests.Api.csproj | 64 + .../Properties/AssemblyInfo.cs | 19 + .../UnitTest1.cs | 13 + .../packages.config | 5 + .../RESTier/samples/apps/angular/.gitignore | 4 + .../RESTier/samples/apps/blazor/.gitignore | 4 + .../RESTier/samples/apps/react/.gitignore | 4 + .../RESTier/samples/apps/uwp/.gitignore | 4 + .../RESTier/samples/apps/xamarin/.gitignore | 4 + .../RESTier/src/CodeAnalysisDictionary.xml | 71 + ApiAsAService/RESTier/src/Common.Stylecop | 114 + .../RESTier/src/CommonAssemblyInfo.cs | 16 + .../RESTier/src/Directory.Build.props | 27 + .../RESTier/src/GlobalSuppressions.cs | 194 + .../Batch/RestierBatchChangeSetRequestItem.cs | 151 + .../Batch/RestierBatchHandler.cs | 86 + .../Batch/RestierChangeSetProperty.cs | 81 + .../Extensions/Extensions.cs | 194 + .../Extensions/HttpConfigurationExtensions.cs | 96 + .../HttpRequestMessageExtensions.cs | 47 + .../Extensions/ServiceCollectionExtensions.cs | 71 + .../RestierExceptionFilterAttribute.cs | 145 + .../Filters/ValidationResultDto.cs | 64 + .../DefaultRestierDeserializerProvider.cs | 34 + .../Deserialization/DeserializationHelpers.cs | 96 + .../RestierEnumDeserializer.cs | 45 + .../DefaultRestierSerializerProvider.cs | 122 + .../RestierCollectionSerializer.cs | 47 + .../Serialization/RestierEnumSerializer.cs | 46 + .../RestierPrimitiveSerializer.cs | 101 + .../Serialization/RestierRawSerializer.cs | 49 + .../RestierResourceSerializer.cs | 48 + .../RestierResourceSetSerializer.cs | 83 + .../Microsoft.Restier.AspNet.csproj | 53 + .../Model/EdmHelpers.cs | 244 + .../Model/ModelMapper.cs | 110 + .../Model/OperationAttribute.cs | 50 + .../Model/PropertyAttributes.cs | 36 + .../Model/ResourceAttribute.cs | 17 + .../Model/RestierModelBuilder.cs | 145 + .../Model/RestierModelExtender.cs | 489 ++ .../Model/RestierOperationModelBuilder.cs | 244 + .../Operation/OperationExecutor.cs | 234 + .../Properties/AssemblyInfo.cs | 28 + .../Properties/Resources.Designer.cs | 270 + .../Properties/Resources.resx | 189 + .../Query/RestierQueryBuilder.cs | 276 + .../Query/RestierQueryExecutor.cs | 60 + .../Query/RestierQueryExecutorOptions.cs | 23 + .../RestierController.cs | 707 ++ .../RestierPayloadValueConverter.cs | 68 + .../Results/BaseCollectionResult.cs | 40 + .../Results/BaseResult.cs | 32 + .../Results/BaseSingleResult.cs | 40 + .../Results/ComplexResult.cs | 24 + .../Results/EnumResult.cs | 24 + .../Results/NonResourceCollectionResult.cs | 24 + .../Results/PrimitiveResult.cs | 24 + .../Results/RawResult.cs | 24 + .../Results/ResourceSetResult.cs | 24 + .../Routing/RestierRoutingConvention.cs | 211 + .../src/Microsoft.Restier.AspNet/app.config | 3 + .../Microsoft.Restier.AspNetCore.csproj | 12 + .../src/Microsoft.Restier.Core/ApiBase.cs | 191 + .../ApiConfiguration.cs | 58 + .../ConventionBasedChangeSetItemAuthorizer.cs | 68 + .../ConventionBasedChangeSetItemFilter.cs | 137 + .../ConventionBasedChangeSetItemValidator.cs | 64 + .../ConventionBasedMethodNameFactory.cs | 282 + .../ConventionBasedOperationAuthorizer.cs | 64 + .../ConventionBasedOperationFilter.cs | 89 + ...ConventionBasedQueryExpressionProcessor.cs | 196 + ...atedMetadataTypeTypeDescriptionProvider.cs | 50 + .../AssociatedMetadataTypeTypeDescriptor.cs | 188 + .../MetadataPropertyDescriptorWrapper.cs | 55 + .../DataAnnotations/MetadataTypeAttribute.cs | 45 + .../Microsoft.Restier.Core/DataSourceStub.cs | 82 + .../Enums/RestierEntitySetOperation.cs | 32 + .../Enums/RestierOperationMethod.cs | 19 + .../Enums/RestierPipelineState.cs | 37 + .../ChangeSetValidationException.cs | 65 + .../Exceptions/StatusCodeException.cs | 78 + .../Extensions/ApiBaseExtensions.cs | 508 ++ .../Extensions/ChainedService.cs | 33 + .../Extensions/EnumerableExtensions.cs | 22 + .../Extensions/InvocationContextExtensions.cs | 58 + .../Extensions/ServiceCollectionExtensions.cs | 286 + .../Extensions/TypeExtensions.cs | 144 + .../Globalization/Error.cs | 20 + .../Microsoft.Restier.Core/Helpers/Ensure.cs | 48 + .../Helpers/ExpressionHelperMethods.cs | 56 + .../Helpers/ExpressionHelpers.cs | 243 + .../InvocationContext.cs | 36 + .../Microsoft.Restier.Core.csproj | 71 + .../Model/EdmHelpers.cs | 45 + .../Model/IModelBuilder.cs | 30 + .../Model/IModelMapper.cs | 86 + .../Model/ModelContext.cs | 40 + .../Operation/IOperationAuthorizer.cs | 31 + .../Operation/IOperationExecutor.cs | 31 + .../Operation/IOperationFilter.cs | 46 + .../Operation/OperationContext.cs | 95 + .../Properties/Resources.Designer.cs | 414 + .../Properties/Resources.resx | 237 + .../src/Microsoft.Restier.Core/PropertyBag.cs | 93 + .../Query/DefaultQueryExecutor.cs | 39 + .../Query/DefaultQueryHandler.cs | 471 ++ .../Query/IQueryExecutor.cs | 73 + .../Query/IQueryExpressionAuthorizer.cs | 35 + .../Query/IQueryExpressionExpander.cs | 39 + .../Query/IQueryExpressionProcessor.cs | 41 + .../Query/IQueryExpressionSourcer.cs | 60 + .../Query/ParameterModelReference.cs | 19 + .../Query/PropertyModelReference.cs | 120 + .../Query/QueryContext.cs | 43 + .../Query/QueryExpressionContext.cs | 409 + .../Query/QueryModelReference.cs | 165 + .../Query/QueryRequest.cs | 45 + .../Query/QueryResult.cs | 103 + .../Microsoft.Restier.Core/QueryableSource.cs | 74 + .../RestierContainerBuilder.cs | 135 + .../Submit/ChangeSet.cs | 53 + .../Submit/ChangeSetItem.cs | 393 + .../Submit/ChangeSetItemValidationResult.cs | 53 + .../Submit/DefaultSubmitHandler.cs | 257 + .../Submit/IChangeSetInitializer.cs | 34 + .../Submit/IChangeSetItemAuthorizer.cs | 34 + .../Submit/IChangeSetItemFilter.cs | 54 + .../Submit/IChangeSetItemValidator.cs | 39 + .../Submit/ISubmitExecutor.cs | 31 + .../Submit/SubmitContext.cs | 64 + .../Submit/SubmitResult.cs | 77 + .../EntityFrameworkApi.cs | 86 + .../Extensions/ServiceCollectionExtensions.cs | 54 + .../Microsoft.Restier.EntityFramework.csproj | 35 + .../Model/ModelMapper.cs | 107 + .../Model/ModelProducer.cs | 128 + .../Properties/Resources.Designer.cs | 135 + .../Properties/Resources.resx | 145 + .../Query/QueryExecutor.cs | 111 + .../Query/QueryExpressionProcessor.cs | 80 + .../Query/QueryExpressionSourcer.cs | 71 + .../Spatial/GeographyConverter.cs | 172 + .../Submit/ChangeSetInitializer.cs | 256 + .../Submit/SubmitExecutor.cs | 35 + .../App.config | 42 + ...tier.Providers.EntityFramework7.csproj_old | 192 + .../Properties/AssemblyInfo.cs | 26 + .../Properties/Resources.Designer.cs | 90 + .../Properties/Resources.resx | 130 + .../Submit/ChangeSetInitializer.cs | 265 + .../App_Start/WebApiConfig.cs | 36 + .../Controllers/NorthwindApi.cs | 43 + .../Data/Category.cs | 31 + .../Data/Customer.cs | 41 + .../Data/CustomerDemographic.cs | 29 + .../Data/Employee.cs | 52 + .../Data/Northwind.Context.cs | 40 + .../Data/Northwind.Context.tt | 636 ++ .../Data/Northwind.Designer.cs | 10 + .../Data/Northwind.cs | 9 + .../Data/Northwind.edmx | 922 ++ .../Data/Northwind.edmx.diagram | 33 + .../Data/Northwind.tt | 733 ++ .../Data/Order.cs | 44 + .../Data/Order_Detail.cs | 26 + .../Data/Product.cs | 39 + .../Data/Region.cs | 29 + .../Data/Shipper.cs | 30 + .../Data/Supplier.cs | 39 + .../Data/Territory.cs | 31 + .../Global.asax | 1 + .../Global.asax.cs | 20 + ...ft.Restier.Samples.Northwind.AspNet.csproj | 213 + .../Properties/AssemblyInfo.cs | 35 + .../Web.Debug.config | 30 + .../Web.Release.config | 31 + .../Web.config | 84 + .../Baselines/LibraryApi-ApiMetadata.txt | 93 + .../Baselines/LibraryApi-ApiSurface.txt | 56 + .../Baselines/StoreApi-ApiMetadata.txt | 44 + .../Baselines/StoreApi-ApiSurface.txt | 40 + .../ExceptionHandlerTests.cs | 56 + .../FeatureTests/ActionTests.cs | 70 + .../FeatureTests/AuthorizationTests.cs | 30 + .../FeatureTests/ExpandTests.cs | 31 + .../FeatureTests/FunctionTests.cs | 97 + .../FeatureTests/MetadataTests.cs | 115 + .../FeatureTests/QueryTests.cs | 57 + .../FeatureTests/UpdateTests.cs | 61 + .../Microsoft.Restier.Tests.AspNet.csproj | 21 + .../Model/RestierModelBuilderTests.cs | 66 + .../Model/RestierModelExtenderTests.cs | 329 + .../ODataControllerFallbackTests.cs | 182 + .../Issue541_CountPlusParametersFails.cs | 74 + .../RestierControllerTests.cs | 111 + .../RestierQueryBuilderTests.cs | 34 + .../DisallowEverythingAuthorizer.cs | 14 + .../TestResources/UnauthorizedLibraryApi.cs | 26 + .../Microsoft.Restier.Tests.AspNet/app.config | 16 + .../Microsoft.Restier.Tests.AspNetCore.csproj | 19 + .../ApiBaseTests.cs | 354 + .../ApiConfigurationTests.cs | 181 + .../ConventionBasedMethodNameFactoryTests.cs | 147 + .../DataSourceStubsTests.cs | 62 + .../InvocationContextTests.cs | 79 + .../Microsoft.Restier.Tests.Core.csproj | 21 + .../Model/DefaultModelHandlerTests.cs | 245 + .../PropertyBagTests.cs | 107 + .../ServiceConfigurationTests.cs | 485 ++ .../App.config | 16 + .../ChangeSetPreparerTests.cs | 51 + ...osoft.Restier.Tests.EntityFramework.csproj | 28 + .../Microsoft.Restier.Tests.Shared.csproj | 32 + .../RestierTestBase.cs | 19 + .../Scenarios/Library/Address.cs | 28 + .../Scenarios/Library/Book.cs | 32 + .../Scenarios/Library/Employee.cs | 37 + .../Scenarios/Library/LibraryApi.cs | 80 + .../Scenarios/Library/LibraryCard.cs | 22 + .../Scenarios/Library/LibraryContext.cs | 127 + .../Scenarios/Library/Publisher.cs | 23 + .../Scenarios/Library/Universe.cs | 47 + .../Scenarios/Store/StoreApi.cs | 205 + ApiAsAService/RESTier/src/Strict.ruleset | 7 + ApiAsAService/RESTierAsAService.sln | 104 + ApiAsAService/ReadMe.md | 1 + ApiAsAService/WebAPI/License.txt | 26 + ApiAsAService/WebAPI/README.md | 110 + ApiAsAService/WebAPI/Settings.StyleCop | 392 + .../WebAPI/WebApiOData.Performance.msbuild | 85 + ApiAsAService/WebAPI/build.cmd | 2 + ApiAsAService/WebAPI/build.ps1 | 476 ++ .../AspNetCoreODataSample.Web.csproj | 19 + .../Controllers/MoviesController.cs | 136 + .../Controllers/PeopleController.cs | 36 + .../Models/EdmModelBuilder.cs | 38 + .../AspNetCoreODataSample.Web/Models/Genre.cs | 14 + .../AspNetCoreODataSample.Web/Models/Level.cs | 20 + .../AspNetCoreODataSample.Web/Models/Movie.cs | 23 + .../Models/MovieContext.cs | 28 + .../Models/MovieStar.cs | 13 + .../Models/Person.cs | 20 + .../AspNetCoreODataSample.Web/Program.cs | 21 + .../Properties/launchSettings.json | 29 + .../AspNetCoreODataSample.Web/Startup.cs | 53 + .../appsettings.Development.json | 10 + .../appsettings.json | 15 + .../App_Start/WebApiConfig.cs | 18 + .../ApplicationInsights.config | 79 + .../AspNetODataSample.Web.csproj | 204 + .../Controllers/TodoItemsController.cs | 48 + .../samples/AspNetODataSample.Web/Global.asax | 1 + .../AspNetODataSample.Web/Global.asax.cs | 15 + .../Models/DataSource.cs | 45 + .../Models/EdmModelBuilder.cs | 25 + .../AspNetODataSample.Web/Models/TodoItem.cs | 12 + .../Models/TodoItemContext.cs | 12 + .../Properties/AssemblyInfo.cs | 35 + .../AspNetODataSample.Web/Web.Debug.config | 30 + .../AspNetODataSample.Web/Web.Release.config | 31 + .../samples/AspNetODataSample.Web/Web.config | 72 + .../AspNetODataSample.Web/packages.config | 21 + ApiAsAService/WebAPI/sln/.nuget/NuGet.Config | 13 + ApiAsAService/WebAPI/sln/.nuget/NuGet.exe | Bin 0 -> 5432400 bytes .../WebAPI/sln/.nuget/packages.config | 10 + .../WebAPI/sln/WebApiOData.AspNet.sln | 98 + .../WebAPI/sln/WebApiOData.AspNetCore.sln | 65 + .../WebAPI/sln/WebApiOData.E2E.AspNet.sln | 127 + .../WebAPI/sln/WebApiOData.E2E.AspNetCore.sln | 100 + .../sln/WebApiOData.Performance.Official.sln | 38 + .../WebAPI/sln/WebApiOData.Performance.sln | 52 + .../WebAPI/src/CodeAnalysisDictionary.xml | 76 + .../WebAPI/src/CommonAssemblyInfo.cs | 21 + .../WebAPI/src/GlobalSuppressions.cs | 6 + .../Batch/ODataBatchContent.cs | 63 + .../Batch/ODataBatchHandler.cs | 57 + .../Builder/ActionConfiguration.cs | 292 + .../Builder/ActionOnDeleteAttribute.cs | 30 + .../Builder/AutoExpandAttribute.cs | 20 + .../Builder/BindableOperationFinder.cs | 118 + .../Builder/BindingParameterConfiguration.cs | 50 + ...ndingPathConfigurationOfTStructuralType.cs | 632 ++ .../Builder/BindingPathHelper.cs | 29 + .../Builder/CapabilitiesNavigationType.cs | 26 + .../CapabilitiesVocabularyConstants.cs | 77 + .../CapabilitiesVocabularyExtensionMethods.cs | 274 + .../CollectionPropertyConfiguration.cs | 67 + .../Builder/CollectionTypeConfiguration.cs | 102 + .../Builder/ComplexPropertyConfiguration.cs | 56 + .../Builder/ComplexTypeConfiguration.cs | 86 + .../ComplexTypeConfigurationOfTComplexType.cs | 84 + .../Builder/ContainedAttribute.cs | 15 + .../Builder/ContainmentPathBuilder.cs | 177 + .../AbstractEntityTypeDiscoveryConvention.cs | 20 + .../AbstractTypeDiscoveryConvention.cs | 21 + .../ActionLinkGenerationConvention.cs | 50 + .../AssociationSetDiscoveryConvention.cs | 99 + .../ActionOnDeleteAttributeConvention.cs | 32 + .../Attributes/AttributeConvention.cs | 74 + .../AttributeEdmPropertyConvention.cs | 89 + .../Attributes/AttributeEdmTypeConvention.cs | 67 + ...utoExpandAttributeEdmPropertyConvention.cs | 33 + .../AutoExpandAttributeEdmTypeConvention.cs | 49 + .../ColumnAttributeEdmPropertyConvention.cs | 68 + .../ComplexTypeAttributeConvention.cs | 42 + ...encyCheckAttributeEdmPropertyConvention.cs | 45 + .../CountAttributeEdmPropertyConvention.cs | 41 + .../CountAttributeEdmTypeConvention.cs | 50 + .../DataContractAttributeEdmTypeConvention.cs | 74 + ...DataContractAttributeEnumTypeConvention.cs | 85 + ...ataMemberAttributeEdmPropertyConvention.cs | 75 + .../ExpandAttributeEdmPropertyConvention.cs | 52 + .../ExpandAttributeEdmTypeConvention.cs | 63 + .../FilterAttributeEdmPropertyConvention.cs | 51 + .../FilterAttributeEdmTypeConvention.cs | 62 + .../ForeignKeyAttributeConvention.cs | 146 + ...ataMemberAttributeEdmPropertyConvention.cs | 60 + .../KeyAttributeEdmPropertyConvention.cs | 48 + ...MaxLengthAttributeEdmPropertyConvention.cs | 45 + .../MediaTypeAttributeConvention.cs | 27 + ...ilterableAttributeEdmPropertyConvention.cs | 33 + ...CountableAttributeEdmPropertyConvention.cs | 33 + ...xpandableAttributeEdmPropertyConvention.cs | 33 + ...ilterableAttributeEdmPropertyConvention.cs | 33 + ...NotMappedAttributeEdmPropertyConvention.cs | 49 + ...NavigableAttributeEdmPropertyConvention.cs | 33 + ...tSortableAttributeEdmPropertyConvention.cs | 33 + .../OrderByAttributeEdmPropertyConvention.cs | 52 + .../OrderByAttributeEdmTypeConvention.cs | 63 + .../PageAttributeEdmPropertyConvention.cs | 47 + .../PageAttributeEdmTypeConvention.cs | 58 + .../RequiredAttributeEdmPropertyConvention.cs | 54 + .../SelectAttributeEdmPropertyConvention.cs | 52 + .../SelectAttributeEdmTypeConvention.cs | 63 + ...TimestampAttributeEdmPropertyConvention.cs | 45 + ...nsortableAttributeEdmPropertyConvention.cs | 33 + .../Builder/Conventions/ConventionsHelpers.cs | 237 + .../Conventions/EntityKeyConvention.cs | 64 + .../Conventions/EntityTypeConvention.cs | 32 + .../ForeignKeyDiscoveryConvention.cs | 122 + .../FunctionLinkGenerationConvention.cs | 46 + .../Builder/Conventions/IConvention.cs | 12 + .../Conventions/IEdmPropertyConvention.cs | 20 + ...pertyConventionOfTPropertyConfiguration.cs | 21 + .../Builder/Conventions/IEdmTypeConvention.cs | 10 + .../INavigationSourceConvention.cs | 10 + .../Conventions/IOperationConvention.cs | 13 + .../NavigationLinksGenerationConvention.cs | 50 + .../SelfLinksGenerationConvention.cs | 65 + .../Builder/DecimalPropertyConfiguration.cs | 29 + .../DynamicPropertyDictionaryAnnotation.cs | 48 + .../Builder/EdmModelHelperMethods.cs | 969 +++ .../Builder/EdmTypeBuilder.cs | 566 ++ .../Builder/EdmTypeConfigurationExtensions.cs | 256 + .../Builder/EdmTypeMap.cs | 46 + ...ityCollectionConfigurationOfTEntityType.cs | 47 + .../Builder/EntitySetConfiguration.cs | 70 + .../EntitySetConfigurationOfTEntityType.cs | 60 + .../Builder/EntityTypeConfiguration.cs | 221 + .../EntityTypeConfigurationOfTEntityType.cs | 162 + .../Builder/EnumMemberConfiguration.cs | 78 + .../Builder/EnumPropertyConfiguration.cs | 71 + .../Builder/EnumTypeConfiguration.cs | 257 + .../EnumTypeConfigurationOfTEnumType.cs | 96 + .../Builder/FunctionConfiguration.cs | 261 + .../Builder/IEdmTypeConfiguration.cs | 47 + .../Builder/LengthPropertyConfiguration.cs | 29 + .../Builder/LinkGenerationHelpers.cs | 543 ++ .../Builder/LowerCamelCaser.cs | 114 + .../Builder/MediaTypeAttribute.cs | 15 + .../Builder/NameResolverOptions.cs | 31 + .../Builder/NavigationLinkBuilder.cs | 41 + .../NavigationPropertyBindingConfiguration.cs | 80 + .../NavigationPropertyBindingOption.cs | 21 + .../NavigationPropertyConfiguration.cs | 298 + .../Builder/NavigationPropertyExtensions.cs | 125 + .../Builder/NavigationSourceAndAnnotations.cs | 20 + .../Builder/NavigationSourceConfiguration.cs | 578 ++ ...igationSourceConfigurationOfTEntityType.cs | 778 ++ .../NavigationSourceLinkBuilderAnnotation.cs | 274 + .../Builder/NavigationSourceUrlAnnotation.cs | 11 + .../NonBindingParameterConfiguration.cs | 28 + .../Builder/NullableEnumTypeConfiguration.cs | 60 + .../Builder/ODataConventionModelBuilder.cs | 1093 +++ .../ODataConventionModelBuilderExtensions.cs | 55 + .../Builder/ODataModelBuilder.cs | 671 ++ .../Builder/OperationConfiguration.cs | 369 + .../Builder/OperationKind.cs | 27 + .../Builder/OperationLinkBuilder.cs | 105 + .../Builder/OperationTitleAnnotation.cs | 15 + .../Builder/ParameterConfiguration.cs | 94 + .../Builder/PrecisionPropertyConfiguration.cs | 30 + .../Builder/PrimitivePropertyConfiguration.cs | 77 + ...rimitivePropertyConfigurationExtensions.cs | 60 + .../Builder/PrimitiveTypeConfiguration.cs | 98 + .../Builder/PropertyConfiguration.cs | 489 ++ .../Builder/PropertyKind.cs | 41 + .../Builder/PropertyPairSelectorVisitor.cs | 139 + .../Builder/PropertySelectorVisitor.cs | 111 + .../Builder/QueryConfiguration.cs | 149 + .../Builder/ReturnedEntitySetAnnotation.cs | 33 + .../Builder/SelfLinkBuilder.cs | 41 + .../Builder/SingletonAttribute.cs | 16 + .../Builder/SingletonConfiguration.cs | 45 + .../SingletonConfigurationOfTEntityType.cs | 23 + .../StructuralPropertyConfiguration.cs | 35 + .../Builder/StructuralTypeConfiguration.cs | 639 ++ ...turalTypeConfigurationOfTStructuralType.cs | 954 +++ .../ClrEnumMemberAnnotation.cs | 62 + .../ClrPropertyInfoAnnotation.cs | 34 + .../ClrTypeAnnotation.cs | 28 + .../Common/CollectionExtensions.cs | 274 + .../Common/CommonWebApiResources.Designer.cs | 138 + .../Common/CommonWebApiResources.resx | 141 + .../Common/Error.cs | 265 + .../Common/ListWrapperCollection.cs | 32 + .../Common/PropertyHelper.cs | 188 + .../Common/SRResources.Designer.cs | 2452 ++++++ .../Common/SRResources.resx | 907 ++ .../Common/TaskHelpers.cs | 89 + .../Common/TaskHelpersExtensions.cs | 39 + .../CompiledPropertyAccessor.cs | 85 + .../ConcurrencyPropertiesAnnotation.cs | 18 + .../ContentIdHelpers.cs | 86 + .../CustomAggregateMethodAnnotation.cs | 63 + .../DefaultContainerBuilder.cs | 109 + .../Microsoft.AspNet.OData.Shared/Delta.cs | 98 + .../DeltaOfTStructuralType.cs | 661 ++ .../ETagMessageHandler.cs | 175 + .../EdmChangedObjectCollection.cs | 61 + .../EdmComplexCollectionObject.cs | 60 + .../EdmComplexObject.cs | 43 + .../EdmDeltaCollectionType.cs | 43 + .../EdmDeltaComplexObject.cs | 44 + .../EdmDeltaDeletedEntityObject.cs | 103 + .../EdmDeltaDeletedLink.cs | 100 + .../EdmDeltaEntityKind.cs | 36 + .../EdmDeltaEntityObject.cs | 74 + .../EdmDeltaLink.cs | 100 + .../EdmDeltaType.cs | 55 + .../EdmEntityCollectionObject.cs | 60 + .../EdmEntityObject.cs | 43 + .../EdmEnumObject.cs | 71 + .../EdmEnumObjectCollection.cs | 61 + .../EdmModelExtensions.cs | 187 + .../EdmStructuredObject.cs | 279 + .../EdmTypeExtensions.cs | 42 + .../EnableQueryAttribute.cs | 818 ++ .../ExpressionHelperMethods.cs | 426 + .../ExpressionHelpers.cs | 280 + .../Extensions/ContainerBuilderExtensions.cs | 93 + .../FastPropertyAccessor.cs | 63 + .../Formatter/ClrTypeCache.cs | 27 + .../Formatter/DefaultODataETagHandler.cs | 94 + .../CollectionDeserializationHelpers.cs | 171 + .../DefaultODataDeserializerProvider.cs | 107 + .../Deserialization/DeserializationHelpers.cs | 449 + .../EnumDeserializationHelpers.cs | 47 + .../ODataActionPayloadDeserializer.cs | 207 + .../ODataCollectionDeserializer.cs | 162 + .../Deserialization/ODataDeserializer.cs | 46 + .../ODataDeserializerContext.cs | 87 + .../ODataDeserializerProvider.cs | 20 + .../ODataEdmTypeDeserializer.cs | 57 + .../ODataEntityReferenceLinkBase.cs | 33 + .../ODataEntityReferenceLinkDeserializer.cs | 69 + .../Deserialization/ODataEnumDeserializer.cs | 95 + .../Deserialization/ODataItemBase.cs | 35 + .../ODataNestedResourceInfoWrapper.cs | 44 + .../ODataPrimitiveDeserializer.cs | 84 + .../Deserialization/ODataReaderExtensions.cs | 144 + .../ODataResourceDeserializer.cs | 622 ++ .../ODataResourceSetDeserializer.cs | 133 + .../ODataResourceSetWrapper.cs | 40 + .../Deserialization/ODataResourceWrapper.cs | 40 + .../Formatter/ETag.cs | 154 + .../Formatter/ETagOfTEntity.cs | 64 + .../Formatter/EdmLibHelpers.cs | 1063 +++ .../Formatter/EdmObjectHelper.cs | 59 + .../Formatter/EdmPrimitiveHelpers.cs | 142 + .../EdmTypeReferenceEqualityComparer.cs | 38 + .../Formatter/IETagHandler.cs | 31 + .../ODataBinaryValueMediaTypeMapping.cs | 29 + .../Formatter/ODataCountMediaTypeMapping.cs | 28 + .../ODataEnumValueMediaTypeMapping.cs | 29 + .../Formatter/ODataInputFormatterHelper.cs | 116 + .../Formatter/ODataMediaTypes.cs | 64 + .../Formatter/ODataMessageWrapper.cs | 154 + .../Formatter/ODataMetadataLevel.cs | 26 + .../Formatter/ODataModelBinderConverter.cs | 416 + .../Formatter/ODataOutputFormatterHelper.cs | 354 + .../ODataPrimitiveValueMediaTypeMapping.cs | 31 + .../ODataRawValueMediaTypeMapping.cs | 45 + .../Formatter/ODataValueExtensions.cs | 26 + .../Formatter/QueryStringMediaTypeMapping.cs | 61 + .../DefaultODataSerializerProvider.cs | 139 + .../Serialization/EntitySelfLinks.cs | 28 + .../ODataCollectionSerializer.cs | 289 + .../Serialization/ODataDeltaFeedSerializer.cs | 378 + .../Serialization/ODataEdmTypeSerializer.cs | 83 + .../ODataEntityReferenceLinkSerializer.cs | 56 + .../ODataEntityReferenceLinksSerializer.cs | 65 + .../Serialization/ODataEnumSerializer.cs | 169 + .../Serialization/ODataErrorSerializer.cs | 54 + .../Serialization/ODataMetadataSerializer.cs | 38 + .../Serialization/ODataPayloadKindHelper.cs | 40 + .../Serialization/ODataPrimitiveSerializer.cs | 279 + .../Serialization/ODataRawValueSerializer.cs | 46 + .../Serialization/ODataResourceSerializer.cs | 1337 +++ .../ODataResourceSetSerializer.cs | 440 + .../Serialization/ODataSerializer.cs | 48 + .../Serialization/ODataSerializerContext.cs | 293 + .../Serialization/ODataSerializerProvider.cs | 20 + .../ODataSerializerProviderExtensions.cs | 25 + .../ODataServiceDocumentSerializer.cs | 45 + .../Serialization/SelectExpandNode.cs | 452 + .../Serialization/TypedEdmComplexObject.cs | 24 + .../Serialization/TypedEdmEntityObject.cs | 24 + .../Serialization/TypedEdmStructuredObject.cs | 118 + .../FunctionImportComparer.cs | 37 + .../GetNextPageHelper.cs | 105 + .../Microsoft.AspNet.OData.Shared/IDelta.cs | 60 + .../IEdmChangedObject.cs | 18 + .../IEdmComplexObject.cs | 14 + .../IEdmDeltaDeletedEntityObject.cs | 24 + .../IEdmDeltaDeletedLink.cs | 13 + .../IEdmDeltaLink.cs | 13 + .../IEdmDeltaLinkBase.cs | 29 + .../IEdmEntityObject.cs | 14 + .../IEdmEnumObject.cs | 15 + .../IEdmObject.cs | 22 + .../IEdmStructuredObject.cs | 25 + .../IPerRouteContainer.cs | 41 + .../Interfaces/IWebApiActionDescriptor.cs | 43 + .../Interfaces/IWebApiActionMap.cs | 23 + .../Interfaces/IWebApiAssembliesResolver.cs | 25 + .../Interfaces/IWebApiContext.cs | 81 + .../Interfaces/IWebApiControllerContext.cs | 34 + .../Interfaces/IWebApiHeaders.cs | 26 + .../Interfaces/IWebApiOptions.cs | 29 + .../Interfaces/IWebApiRequestMessage.cs | 122 + .../Interfaces/IWebApiUrlHelper.cs | 43 + .../MetadataController.cs | 117 + .../Microsoft.AspNet.OData.Shared.projitems | 444 + .../Microsoft.AspNet.OData.Shared.shproj | 12 + ...avigationPropertyQueryableConfiguration.cs | 27 + ...ropertyQueryableConfigurationAnnotation.cs | 30 + .../NonValidatingParameterBindingAttribute.cs | 12 + .../NullEdmComplexObject.cs | 42 + .../ODataActionParameters.cs | 19 + .../ODataController.cs | 13 + .../ODataNullValueMessageHandler.cs | 118 + .../ODataQueryContext.cs | 201 + .../ODataQueryContextExtensions.cs | 51 + .../ODataQueryParameterBindingAttribute.cs | 29 + .../ODataSwaggerConverter.cs | 290 + .../ODataSwaggerUtilities.cs | 648 ++ .../ODataUntypedActionParameters.cs | 39 + .../ODataUriFunctions.cs | 63 + .../PageResult.cs | 66 + .../PageResultOfT.cs | 68 + .../PerRouteContainerBase.cs | 153 + .../PropertyAccessor.cs | 52 + .../Query/AllowedArithmeticOperators.cs | 49 + .../Query/AllowedFunctions.cs | 169 + .../Query/AllowedLogicalOperators.cs | 74 + .../Query/AllowedQueryOptions.cs | 84 + .../Query/ApplyQueryOption.cs | 160 + .../Query/CountAttribute.cs | 20 + .../Query/CountQueryOption.cs | 170 + .../Query/DefaultQuerySettings.cs | 68 + .../Query/DefaultSkipTokenHandler.cs | 424 + .../Query/ExpandAttribute.cs | 136 + .../Query/ExpandConfiguration.cs | 21 + .../Query/Expressions/AggregationBinder.cs | 670 ++ .../AggregationPropertyContainer.cs | 160 + .../Expressions/ClrCanonicalFunctions.cs | 148 + .../Query/Expressions/ClrSafeFunctions.cs | 53 + .../Query/Expressions/DynamicTypeWrapper.cs | 122 + .../DynamicTypeWrapperConverter.cs | 41 + .../Query/Expressions/ExpressionBinderBase.cs | 1083 +++ .../Query/Expressions/FilterBinder.cs | 1693 ++++ .../Expressions/IdentityPropertyMapper.cs | 13 + .../Linq2ObjectsComparisonMethods.cs | 60 + .../Expressions/LinqParameterContainer.cs | 67 + .../Query/Expressions/ModelContainer.cs | 32 + .../Expressions/NamedPropertyExpression.cs | 43 + .../Query/Expressions/PropertyContainer.cs | 239 + .../PropertyContainer.generated.cs | 1570 ++++ .../PropertyContainer.generated.tt | 64 + .../Query/Expressions/SelectExpandBinder.cs | 897 ++ .../Query/Expressions/SelectExpandWrapper.cs | 146 + .../SelectExpandWrapperConverter.cs | 97 + .../Expressions/SelectExpandWrapperOfT.cs | 30 + .../Query/Expressions/UriFunctionsBinder.cs | 161 + .../Query/FilterAttribute.cs | 92 + .../Query/FilterQueryOption.cs | 166 + .../Query/HandleNullPropagationOption.cs | 28 + .../HandleNullPropagationOptionHelper.cs | 62 + .../Query/ICountOptionCollection.cs | 18 + .../Query/IPropertyMapper.cs | 39 + .../Query/ISelectExpandWrapper.cs | 38 + .../Query/ITruncatedCollection.cs | 23 + .../Query/ModelBoundQuerySettings.cs | 298 + .../Query/NonFilterableAttribute.cs | 15 + .../Query/NotCountableAttribute.cs | 16 + .../Query/NotExpandableAttribute.cs | 15 + .../Query/NotFilterableAttribute.cs | 16 + .../Query/NotNavigableAttribute.cs | 15 + .../Query/NotSortableAttribute.cs | 16 + .../Query/ODataQueryOptions.cs | 1028 +++ .../Query/ODataQueryOptionsOfTEntity.cs | 85 + .../Query/ODataQuerySettings.cs | 139 + .../Query/ODataRawQueryOptions.cs | 66 + .../Query/ODataValidationSettings.cs | 274 + .../Query/OrderByAttribute.cs | 95 + .../Query/OrderByCountNode.cs | 34 + .../Query/OrderByItNode.cs | 22 + .../Query/OrderByNode.cs | 135 + .../Query/OrderByOpenPropertyNode.cs | 48 + .../Query/OrderByPropertyNode.cs | 68 + .../Query/OrderByQueryOption.cs | 339 + .../Query/PageAttribute.cs | 37 + .../Query/ParameterAliasNodeTranslator.cs | 404 + .../Query/QueryOptionSetting.cs | 21 + .../Query/SelectAttribute.cs | 95 + .../Query/SelectExpandQueryOption.cs | 693 ++ .../Query/SelectExpandType.cs | 26 + .../Query/SkipQueryOption.cs | 169 + .../Query/SkipTokenHandler.cs | 41 + .../Query/SkipTokenQueryOption.cs | 124 + .../Query/TopQueryOption.cs | 169 + .../Query/TruncatedCollectionOfT.cs | 116 + .../Query/UnsortableAttribute.cs | 15 + .../Query/Validators/CountQueryValidator.cs | 87 + .../Query/Validators/FilterQueryValidator.cs | 1039 +++ .../Query/Validators/ODataQueryValidator.cs | 130 + .../OrderByModelLimitationsValidator.cs | 147 + .../Query/Validators/OrderByQueryValidator.cs | 118 + .../Validators/SelectExpandQueryValidator.cs | 377 + .../Query/Validators/SkipQueryValidator.cs | 48 + .../Validators/SkipTokenQueryValidator.cs | 42 + .../Query/Validators/TopQueryValidator.cs | 66 + .../QueryableRestrictions.cs | 95 + .../QueryableRestrictionsAnnotation.cs | 33 + .../RequestMethod.cs | 46 + .../RequestPreferenceHelpers.cs | 100 + .../ResourceContext.cs | 281 + .../ResourceContextOfTStructuredType.cs | 39 + .../ResourceSetContext.cs | 35 + .../Results/ResultHelpers.cs | 125 + .../Conventions/ActionMapExtensions.cs | 26 + .../Conventions/ActionRoutingConvention.cs | 57 + .../Conventions/AttributeRoutingConvention.cs | 165 + .../DynamicPropertyRoutingConvention.cs | 99 + .../Conventions/EntityRoutingConvention.cs | 62 + .../Conventions/EntitySetRoutingConvention.cs | 96 + .../Conventions/FunctionRoutingConvention.cs | 90 + .../Conventions/MetadataRoutingConvention.cs | 82 + .../NavigationRoutingConvention.cs | 102 + .../NavigationSourceRoutingConvention.cs | 41 + .../Conventions/PropertyRoutingConvention.cs | 185 + .../Conventions/RefRoutingConvention.cs | 115 + .../Conventions/RoutingConventionHelpers.cs | 355 + .../Conventions/SelectControllerResult.cs | 35 + .../Conventions/SingletonRoutingConvention.cs | 72 + .../UnmappedRequestRoutingConvention.cs | 26 + .../Routing/DefaultODataPathHandler.cs | 286 + .../Routing/DefaultODataPathValidator.cs | 182 + .../Routing/IODataPathHandler.cs | 38 + .../Routing/IODataPathTemplateHandler.cs | 24 + .../Routing/KeyValueParser.cs | 157 + .../Routing/ODataOptionalParameter.cs | 26 + .../Routing/ODataParameterHelper.cs | 206 + .../Routing/ODataParameterValue.cs | 29 + .../Routing/ODataPath.cs | 114 + .../ODataPathParameterBindingAttribute.cs | 12 + .../Routing/ODataPathRouteConstraint.cs | 179 + .../Routing/ODataPathSegmentExtensions.cs | 352 + .../Routing/ODataPathSegmentHandler.cs | 457 + .../Routing/ODataPathSegmentTranslator.cs | 390 + .../Routing/ODataRoute.cs | 74 + .../Routing/ODataRouteAttribute.cs | 42 + .../Routing/ODataRouteConstants.cs | 71 + .../Routing/ODataRoutePrefixAttribute.cs | 35 + .../Routing/ODataSegmentKinds.cs | 106 + .../Routing/ODataVersionConstraint.cs | 127 + .../Template/DynamicSegmentTemplate.cs | 91 + .../Template/EntitySetSegmentTemplate.cs | 41 + .../Routing/Template/KeySegmentTemplate.cs | 98 + .../NavigationPropertyLinkSegmentTemplate.cs | 41 + .../NavigationPropertySegmentTemplate.cs | 41 + .../Template/ODataPathSegmentTemplate.cs | 32 + .../Template/ODataPathSegmentTemplateOfT.cs | 25 + .../ODataPathSegmentTemplateTranslator.cs | 174 + .../Routing/Template/ODataPathTemplate.cs | 84 + .../OperationImportSegmentTemplate.cs | 100 + .../Template/OperationSegmentTemplate.cs | 97 + .../Template/PathTemplateSegmentTemplate.cs | 103 + .../Template/PropertySegmentTemplate.cs | 41 + .../Template/SingletonSegmentTemplate.cs | 41 + .../Routing/Template/TypeSegmentTemplate.cs | 42 + .../Routing/UnresolvedPathSegment.cs | 92 + .../TimeZoneInfoHelper.cs | 26 + .../TypeHelper.cs | 482 ++ .../TypedDelta.cs | 34 + ...nqualifiedCallAndEnumPrefixFreeResolver.cs | 77 + .../WebApiAssembliesResolver.cs | 19 + .../Adapters/WebApiActionDescriptor.cs | 102 + .../Adapters/WebApiActionMap.cs | 45 + .../Adapters/WebApiAssembliesResolver.cs | 56 + .../Adapters/WebApiContext.cs | 138 + .../Adapters/WebApiControllerContext.cs | 68 + .../Adapters/WebApiOptions.cs | 43 + .../Adapters/WebApiRequestHeaders.cs | 46 + .../Adapters/WebApiRequestMessage.cs | 228 + .../Adapters/WebApiUrlHelper.cs | 70 + .../Batch/ChangeSetRequestItem.cs | 123 + .../Batch/ChangeSetResponseItem.cs | 83 + .../Batch/DefaultODataBatchHandler.cs | 161 + .../Batch/LazyStreamContent.cs | 46 + .../Batch/ODataBatchContent.cs | 93 + .../Batch/ODataBatchHandler.cs | 82 + .../ODataBatchHttpRequestMessageExtensions.cs | 283 + .../Batch/ODataBatchReaderExtensions.cs | 191 + .../Batch/ODataBatchRequestItem.cs | 102 + .../Batch/ODataBatchResponseItem.cs | 120 + .../Batch/ODataHttpContentExtensions.cs | 59 + .../Batch/OperationRequestItem.cs | 72 + .../Batch/OperationResponseItem.cs | 68 + .../Batch/UnbufferedODataBatchHandler.cs | 199 + .../Builder/ODataConventionModelBuilder.cs | 61 + .../ETagMessageHandler.cs | 64 + .../EnableQueryAttribute.cs | 197 + .../HttpActionDescriptorExtensions.cs | 51 + .../Extensions/HttpConfigurationExtensions.cs | 871 ++ .../Extensions/HttpErrorExtensions.cs | 101 + .../HttpRequestMessageExtensions.cs | 432 + .../HttpRequestMessageProperties.cs | 331 + .../Extensions/UrlHelperExtensions.cs | 86 + .../DefaultODataDeserializerProvider.cs | 23 + .../ODataDeserializerContext.cs | 37 + .../ODataDeserializerProvider.cs | 23 + .../Formatter/ODataCountMediaTypeMapping.cs | 27 + .../Formatter/ODataMediaTypeFormatter.cs | 375 + .../Formatter/ODataMediaTypeFormatters.cs | 120 + .../Formatter/ODataModelBinderProvider.cs | 152 + .../ODataRawValueMediaTypeMapping.cs | 29 + .../Formatter/QueryStringMediaTypeMapping.cs | 58 + .../DefaultODataSerializerProvider.cs | 24 + .../Serialization/ODataErrorSerializer.cs | 38 + .../Serialization/ODataSerializerContext.cs | 66 + .../Serialization/ODataSerializerProvider.cs | 23 + .../FromODataUriAttribute.cs | 42 + .../GetNextPageHelper.cs | 23 + .../GlobalSuppressions.cs | 37 + .../HttpRequestScope.cs | 20 + ...rosoft.AspNet.OData.Nightly.Release.nuspec | 35 + .../Microsoft.AspNet.OData.Release.nuspec | 35 + .../Microsoft.AspNet.OData.csproj | 188 + .../NonValidatingParameterBindingAttribute.cs | 39 + .../Microsoft.AspNet.OData/ODataController.cs | 75 + .../ODataFormattingAttribute.cs | 106 + .../ODataMessageWrapperHelper.cs | 41 + .../ODataNullValueMessageHandler.cs | 58 + .../ODataQueryParameterBindingAttribute.cs | 127 + .../ODataRoutingAttribute.cs | 51 + .../PerRequestActionValueBinder.cs | 68 + .../PerRequestContentNegotiator.cs | 45 + .../PerRequestParameterBinding.cs | 67 + .../PerRouteContainer.cs | 85 + .../Properties/AssemblyInfo.cs | 10 + .../Query/ODataQueryOptions.cs | 55 + .../Query/ODataQueryOptionsOfTEntity.cs | 37 + .../Query/QueryFilterProvider.cs | 61 + .../Microsoft.AspNet.OData/ResourceContext.cs | 54 + .../ResourceSetContext.cs | 88 + .../Results/CreatedODataResult.cs | 168 + .../Results/ResultHelpers.cs | 89 + .../Results/UpdatedODataResult.cs | 131 + .../Conventions/ActionRoutingConvention.cs | 27 + .../Conventions/AttributeRoutingConvention.cs | 274 + .../DynamicPropertyRoutingConvention.cs | 29 + .../Conventions/EntityRoutingConvention.cs | 26 + .../Conventions/EntitySetRoutingConvention.cs | 26 + .../Conventions/FunctionRoutingConvention.cs | 29 + .../Conventions/IODataRoutingConvention.cs | 35 + .../Conventions/MetadataRoutingConvention.cs | 71 + .../NavigationRoutingConvention.cs | 26 + .../NavigationSourceRoutingConvention.cs | 112 + .../Conventions/ODataRoutingConventions.cs | 63 + .../Conventions/PropertyRoutingConvention.cs | 27 + .../Conventions/RefRoutingConvention.cs | 26 + .../Conventions/SingletonRoutingConvention.cs | 26 + .../UnmappedRequestRoutingConvention.cs | 23 + .../Routing/ODataActionSelector.cs | 110 + .../ODataPathParameterBindingAttribute.cs | 61 + .../Routing/ODataPathRouteConstraint.cs | 108 + .../Routing/ODataRoute.cs | 161 + .../Routing/ODataValueProviderFactory.cs | 34 + .../Routing/ODataVersionConstraint.cs | 43 + .../Microsoft.AspNet.OData/Settings.StyleCop | 29 + .../src/Microsoft.AspNet.OData/app.config | 11 + .../Microsoft.AspNet.OData/packages.config | 11 + .../Adapters/WebApiActionDescriptor.cs | 123 + .../Adapters/WebApiActionMap.cs | 47 + .../Adapters/WebApiAssembliesResolver.cs | 70 + .../Adapters/WebApiContext.cs | 165 + .../Adapters/WebApiControllerContext.cs | 68 + .../Adapters/WebApiOptions.cs | 41 + .../Adapters/WebApiRequestHeaders.cs | 52 + .../Adapters/WebApiRequestMessage.cs | 236 + .../Adapters/WebApiUrlHelper.cs | 94 + .../Batch/ChangeSetRequestItem.cs | 71 + .../Batch/ChangeSetResponseItem.cs | 67 + .../Batch/DefaultODataBatchHandler.cs | 137 + .../Batch/HttpRequestExtensions.cs | 96 + .../Batch/ODataBatchContent.cs | 79 + .../Batch/ODataBatchHandler.cs | 82 + .../ODataBatchHttpRequestMessageExtensions.cs | 310 + .../Batch/ODataBatchMiddleware.cs | 61 + .../Batch/ODataBatchPathMapping.cs | 67 + .../Batch/ODataBatchReaderExtensions.cs | 240 + .../Batch/ODataBatchRequestItem.cs | 101 + .../Batch/ODataBatchResponseItem.cs | 72 + .../Batch/OperationRequestItem.cs | 50 + .../Batch/OperationResponseItem.cs | 58 + .../Batch/UnbufferedODataBatchHandler.cs | 169 + .../Builder/ODataConventionModelBuilder.cs | 100 + .../ETagMessageHandler.cs | 52 + .../EnableQueryAttribute.cs | 251 + .../Extensions/ActionDescriptorExtensions.cs | 69 + .../Extensions/HttpContextExtensions.cs | 98 + .../Extensions/HttpRequestExtensions.cs | 466 + .../Extensions/HttpResponseExtensions.cs | 23 + .../ODataApplicationBuilderExtensions.cs | 88 + .../Extensions/ODataRouteBuilderExtensions.cs | 731 ++ .../ODataServiceCollectionExtensions.cs | 112 + .../Extensions/SerializableErrorExtensions.cs | 230 + .../Extensions/UrlHelperExtensions.cs | 87 + .../DefaultODataDeserializerProvider.cs | 23 + .../ODataDeserializerContext.cs | 33 + .../ODataDeserializerProvider.cs | 23 + .../Formatter/IMediaTypeMappingCollection.cs | 22 + .../Formatter/MediaTypeMapping.cs | 48 + .../Formatter/ODataCountMediaTypeMapping.cs | 27 + .../Formatter/ODataInputFormatter.cs | 226 + .../Formatter/ODataInputFormatterFactory.cs | 92 + .../Formatter/ODataModelBinder.cs | 150 + .../Formatter/ODataOutputFormatter.cs | 294 + .../Formatter/ODataOutputFormatterFactory.cs | 127 + .../ODataRawValueMediaTypeMapping.cs | 29 + .../Formatter/QueryStringMediaTypeMapping.cs | 79 + .../DefaultODataSerializerProvider.cs | 24 + .../Serialization/ODataErrorSerializer.cs | 38 + .../Serialization/ODataSerializerContext.cs | 63 + .../Serialization/ODataSerializerProvider.cs | 23 + .../FromODataUriAttribute.cs | 24 + .../GetNextPageHelper.cs | 30 + .../HttpRequestScope.cs | 20 + .../Interfaces/IODataBatchFeature.cs | 34 + .../Interfaces/IODataCoreBuilder.cs | 18 + .../Interfaces/IODataFeature.cs | 88 + ...ft.AspNetCore.OData.Nightly.Release.nuspec | 32 + .../Microsoft.AspNetCore.OData.Release.nuspec | 32 + .../Microsoft.AspNetCore.OData.csproj | 67 + .../NonValidatingParameterBindingAttribute.cs | 35 + .../ODataBatchFeature.cs | 35 + .../ODataController.cs | 55 + .../ODataFeature.cs | 155 + .../ODataFormattingAttribute.cs | 18 + .../ODataMessageWrapperHelper.cs | 41 + .../ODataNullValueMessageHandler.cs | 58 + .../ODataOptions.cs | 30 + .../ODataQueryParameterBindingAttribute.cs | 166 + .../ODataRoutingAttribute.cs | 16 + .../ODataServiceBuilder.cs | 34 + .../ODataValueProviderFactory.cs | 73 + .../PerRouteContainer.cs | 71 + .../Properties/AssemblyInfo.cs | 10 + .../Query/ODataQueryOptions.cs | 61 + .../Query/ODataQueryOptionsOfTEntity.cs | 37 + .../Query/QueryFilterProvider.cs | 134 + .../ResourceContext.cs | 36 + .../ResourceSetContext.cs | 85 + .../Results/CreatedODataResult.cs | 85 + .../Results/ResultHelpers.cs | 83 + .../Results/UpdatedODataResult.cs | 64 + .../Conventions/ActionRoutingConvention.cs | 27 + .../Conventions/AttributeRoutingConvention.cs | 254 + .../DynamicPropertyRoutingConvention.cs | 27 + .../Conventions/EntityRoutingConvention.cs | 27 + .../Conventions/EntitySetRoutingConvention.cs | 27 + .../Conventions/FunctionRoutingConvention.cs | 27 + .../Conventions/IODataRoutingConvention.cs | 26 + .../Conventions/MetadataRoutingConvention.cs | 59 + .../NavigationRoutingConvention.cs | 27 + .../NavigationSourceRoutingConvention.cs | 79 + .../Conventions/ODataRoutingConventions.cs | 63 + .../Conventions/PropertyRoutingConvention.cs | 27 + .../Conventions/RefRoutingConvention.cs | 27 + .../Conventions/SingletonRoutingConvention.cs | 27 + .../UnmappedRequestRoutingConvention.cs | 23 + .../Routing/ODataActionSelector.cs | 170 + .../ODataPathParameterBindingAttribute.cs | 47 + .../Routing/ODataPathRouteConstraint.cs | 81 + .../Routing/ODataRoute.cs | 92 + .../Routing/ODataVersionConstraint.cs | 44 + .../SingleResult.cs | 47 + .../SingleResultOfT.cs | 35 + ApiAsAService/WebAPI/src/Settings.StyleCop | 5 + ApiAsAService/WebAPI/src/Strict.ruleset | 8 + .../WebAPI/tools/35MSSharedLib1024.snk | Bin 0 -> 160 bytes .../WebAPI/tools/GetNugetPackageMetadata.proj | 17 + .../WebAPI/tools/MeasureCodeSharing.ps1 | 29 + .../WebAPI/tools/PoliCheck/RunPoliCheck.ps1 | 35 + .../WebAPI/tools/SkipStrongNames.xml | 70 + .../WebAPI/tools/WebStack.StyleCop.targets | 80 + .../WebAPI/tools/WebStack.settings.targets | 59 + ApiAsAService/WebAPI/tools/WebStack.targets | 6 + .../WebAPI/tools/WebStack.tasks.targets | 221 + .../tools/WebStack.versions.settings.targets | 128 + .../WebAPI/tools/WebStack.xunit.targets | 21 + .../WebAPI/tools/perf/PerfRegression.ps1 | 59 + ApiAsAService/WebAPIAsAService.sln | 178 + ApiAsAService/odata.net/.markdownlint.json | 7 + ApiAsAService/odata.net/Build.props | 111 + ApiAsAService/odata.net/LICENSE.txt | 26 + ApiAsAService/odata.net/OData.sln.lnk | Bin 0 -> 1531 bytes ApiAsAService/odata.net/PerformanceBuild.ps1 | 82 + ApiAsAService/odata.net/README.md | 160 + ApiAsAService/odata.net/build.cmd | 2 + ApiAsAService/odata.net/build.ps1 | 650 ++ ApiAsAService/odata.net/build.root | 1 + .../odata.net-master/exTermEdge_v58.xlsx | Bin 0 -> 60029 bytes .../odata.net/images/ndependlogo.png | Bin 0 -> 4353 bytes .../odata.net/sln/.nuget/NuGet.Config | 14 + ApiAsAService/odata.net/sln/.nuget/NuGet.exe | Bin 0 -> 4266712 bytes .../odata.net/sln/.nuget/NuGet.targets | 144 + .../odata.net/sln/.nuget/packages.config | 26 + ApiAsAService/odata.net/sln/OData.CodeGen.sln | 132 + ApiAsAService/odata.net/sln/OData.Net35.sln | 89 + ApiAsAService/odata.net/sln/OData.Net45.sln | 199 + .../odata.net/sln/OData.NetStandard.sln | 112 + .../sln/OData.Tests.E2E.NetCore.VS2017.sln | 317 + .../odata.net/sln/OData.Tests.E2E.sln | 1651 ++++ .../sln/OData.Tests.NetStandard.VS2017.sln | 245 + .../odata.net/sln/OData.Tests.Performance.sln | 211 + .../odata.net/sln/OData.Tests.WindowsApps.sln | 283 + .../src/AssemblyInfo/AssemblyInfoCommon.cs | 87 + .../src/AssemblyInfo/AssemblyInfoCommon.vb | 101 + .../src/AssemblyInfo/AssemblyKeys.cs | 43 + .../AssemblyInfo/AssemblyMetadataAttribute.cs | 21 + .../src/AssemblyInfo/AssemblyRefs.cs | 201 + .../src/AssemblyInfo/AssemblyRefs.vb | 103 + ApiAsAService/odata.net/src/Build.props | 43 + .../src/CodeGen/GlobalSuppressions.cs | 10 + .../src/CodeGen/InternalsVisibleTo.cs | 11 + .../Microsoft.Data.Web.Design.T4.csproj | 60 + .../src/CodeGen/ODataT4CodeGenerator.cs | 7459 +++++++++++++++++ .../src/CodeGen/ODataT4CodeGenerator.tt | 79 + .../CodeGen/ODataT4CodeGenerator.ttinclude | 5188 ++++++++++++ .../CodeGen/ODataT4CodeGenerator.vstemplate | 27 + .../ItemTemplates/ODataT4Template.CSharp.zip | Bin 0 -> 22 bytes .../ODataT4Template.VisualBasic.zip | Bin 0 -> 22 bytes .../ODataT4ItemTemplate.csproj | 95 + .../src/CodeGen/ODataT4ItemTemplate/ap-pl.txt | 8 + .../ODataT4ItemTemplate/odata_logo.png | Bin 0 -> 7538 bytes .../source.extension.vsixmanifest | 28 + .../odata.net/src/CodeGen/__TemplateIcon.ico | Bin 0 -> 10134 bytes ApiAsAService/odata.net/src/Common.Stylecop | 321 + .../odata.net/src/CustomDictionary.xml | 49 + .../ALinq/ALinqExpressionVisitor.cs | 465 + .../ALinq/AddNewEndingTokenVisitor.cs | 59 + .../ALinq/DataServiceExpressionVisitor.cs | 96 + .../ALinq/DataServiceQueryProvider.cs | 156 + .../Microsoft.OData.Client/ALinq/Evaluator.cs | 208 + .../ALinq/ExpandOnlyPathToStringVisitor.cs | 70 + .../ALinq/ExpressionNormalizer.cs | 803 ++ .../ALinq/ExpressionWriter.cs | 954 +++ .../ALinq/FilterQueryOptionExpression.cs | 89 + .../ALinq/InputBinder.cs | 237 + .../ALinq/InputReferenceExpression.cs | 78 + .../NavigationPropertySingletonExpression.cs | 132 + .../ALinq/NewTreeBuilder.cs | 45 + .../ALinq/OrderByQueryOptionExpression.cs | 78 + .../ALinq/ParameterReplacerVisitor.cs | 58 + .../ALinq/ProjectionAnalyzer.cs | 830 ++ .../ALinq/ProjectionQueryOptionExpression.cs | 84 + .../ALinq/ProjectionRewriter.cs | 99 + .../ALinq/QueryComponents.cs | 243 + .../ALinq/QueryOptionExpression.cs | 53 + .../ALinq/QueryableResourceExpression.cs | 508 ++ .../ALinq/ReflectionUtil.cs | 733 ++ .../ALinq/RemoveWildcardVisitor.cs | 55 + .../ALinq/ResourceBinder.cs | 3027 +++++++ .../ALinq/ResourceExpression.cs | 270 + .../ALinq/ResourceExpressionType.cs | 45 + .../ALinq/ResourceSetExpression.cs | 114 + .../ALinq/SelectExpandPathBuilder.cs | 284 + .../ALinq/SelectExpandPathToStringVisitor.cs | 112 + .../ALinq/SingletonResourceExpression.cs | 108 + .../ALinq/SkipQueryOptionExpression.cs | 72 + .../ALinq/TakeQueryOptionExpression.cs | 72 + .../ALinq/TypeSystem.cs | 377 + .../Microsoft.OData.Client/ALinq/UriHelper.cs | 245 + .../Microsoft.OData.Client/ALinq/UriWriter.cs | 617 ++ .../ActionDescriptor.cs | 13 + .../Annotation/AnnotationHelper.cs | 771 ++ .../InstanceAnnotationDictWeakKeyComparer.cs | 136 + .../src/Microsoft.OData.Client/ArraySet.cs | 235 + .../AtomMaterializerLog.cs | 466 + .../src/Microsoft.OData.Client/AtomParser.cs | 1522 ++++ .../Attribute/EntitySetAttribute.cs | 42 + .../Attribute/EntityTypeAttribute.cs | 20 + .../Attribute/HasStreamAttribute.cs | 16 + .../IgnoreClientPropertyAttribute.cs | 16 + .../Attribute/KeyAttribute.cs | 53 + .../Attribute/NamedStreamAttribute.cs | 30 + .../Attribute/OriginalNameAttribute.cs | 32 + .../Microsoft.OData.Client/BaseAsyncResult.cs | 1157 +++ .../Microsoft.OData.Client/BaseEntityType.cs | 17 + .../Microsoft.OData.Client/BaseSaveResult.cs | 1481 ++++ .../Microsoft.OData.Client/BatchSaveResult.cs | 972 +++ .../Binding/BindingEntityInfo.cs | 486 ++ .../Binding/BindingGraph.cs | 1144 +++ .../Binding/BindingObserver.cs | 1131 +++ .../Binding/BindingUtils.cs | 79 + .../Binding/DataServiceCollectionOfT.cs | 832 ++ .../DataServiceSaveChangesEventArgs.cs | 31 + .../Binding/EntityChangedParams.cs | 110 + .../Binding/EntityCollectionChangedParams.cs | 145 + .../Binding/LoadCompletedEventArgs.cs | 61 + .../BodyOperationParameter.cs | 22 + .../Microsoft.OData.Client.NetStandard.csproj | 951 +++ .../Build.NetStandard/project.json | 15 + ...rosoft.OData.Client.Nightly.Release.nuspec | 82 + .../Microsoft.OData.Client.Release.nuspec | 87 + .../Microsoft.OData.Client.Portable.cs | 344 + .../Microsoft.OData.Client.Portable.csproj | 939 +++ .../Microsoft.OData.Client.Portable.tt | 23 + .../Microsoft.OData.Client.Portable.txt | 3 + ...terized.Microsoft.OData.Client.Portable.cs | 2019 +++++ ...terized.Microsoft.OData.Client.Portable.tt | 20 + .../src/Microsoft.OData.Client/Build.props | 15 + .../BuildingRequestEventArgs.cs | 102 + .../ChangesetResponse.cs | 37 + .../Microsoft.OData.Client/ClientConvert.cs | 177 + .../ClientEdmCollectionValue.cs | 72 + .../Microsoft.OData.Client/ClientEdmModel.cs | 745 ++ .../ClientEdmStructuredValue.cs | 168 + .../src/Microsoft.OData.Client/Common.cs | 339 + .../src/Microsoft.OData.Client/CommonUtil.cs | 77 + .../Microsoft.OData.Client/ContentStream.cs | 58 + .../Microsoft.OData.Client/ContentTypeUtil.cs | 1274 +++ .../ConventionalODataEntityMetadataBuilder.cs | 183 + .../DataServiceActionQuery.cs | 83 + .../DataServiceActionQueryOfT.cs | 102 + .../DataServiceActionQuerySingleOfT.cs | 91 + .../DataServiceClientConfigurations.cs | 38 + .../DataServiceClientException.cs | 118 + .../DataServiceClientFormat.cs | 278 + .../DataServiceClientRequestMessageArgs.cs | 85 + ...rviceClientRequestPipelineConfiguration.cs | 285 + ...viceClientResponsePipelineConfiguration.cs | 364 + .../DataServiceContext.cs | 3508 ++++++++ .../DataServiceQuery.cs | 107 + .../DataServiceQueryContinuation.cs | 169 + .../DataServiceQueryException.cs | 95 + .../DataServiceQueryOfT.cs | 517 ++ .../DataServiceQuerySingleOfT.cs | 249 + .../DataServiceRequest.cs | 348 + .../DataServiceRequestArgs.cs | 118 + .../DataServiceRequestException.cs | 91 + .../DataServiceRequestOfT.cs | 117 + .../DataServiceResponse.cs | 82 + .../DataServiceResponsePreference.cs | 21 + .../DataServiceSaveStream.cs | 98 + .../DataServiceStreamLink.cs | 141 + .../DataServiceStreamResponse.cs | 130 + .../DataServiceTransportInfo.cs | 31 + .../DataServiceUrlKeyDelimiter.cs | 130 + .../DataServiceWebException.cs | 85 + .../src/Microsoft.OData.Client/Descriptor.cs | 139 + .../Microsoft.OData.Client/Diagrams/ALinq.cd | 145 + .../Microsoft.OData.Client/Diagrams/ALinq.cs | 145 + .../Diagrams/Materialization.cd | 126 + .../DictionaryExtensions.cs | 122 + .../DynamicProxyMethodGenerator.cs | 194 + .../EntityDescriptor.cs | 968 +++ .../EntityParameterSendOption.cs | 24 + .../Microsoft.OData.Client/EntityStates.cs | 44 + .../Microsoft.OData.Client/EntityTracker.cs | 580 ++ .../EntityTrackerBase.cs | 76 + .../src/Microsoft.OData.Client/Error.cs | 180 + .../FunctionDescriptor.cs | 13 + .../GetReadStreamResult.cs | 203 + .../GlobalSuppressions.cs | 70 + .../HeaderCollection.cs | 265 + .../src/Microsoft.OData.Client/HttpStack.cs | 19 + .../InternalODataRequestMessage.cs | 252 + .../Microsoft.OData.Client/InvokeResponse.cs | 25 + .../Microsoft.OData.Client/LinkDescriptor.cs | 175 + .../src/Microsoft.OData.Client/LinkInfo.cs | 76 + .../LoadPropertyResult.cs | 338 + .../CollectionValueMaterializationPolicy.cs | 244 + .../Materialization/EntityTrackingAdapter.cs | 232 + .../EntryValueMaterializationPolicy.cs | 679 ++ .../EnumValueMaterializationPolicy.cs | 121 + .../FeedAndEntryMaterializerAdapter.cs | 468 ++ .../Materialization/HttpWebResponseMessage.cs | 193 + .../IODataMaterializerContext.cs | 56 + ...InstanceAnnotationMaterializationPolicy.cs | 323 + .../Materialization/MaterializationPolicy.cs | 30 + .../Materialization/MaterializerEntry.cs | 396 + .../Materialization/MaterializerFeed.cs | 97 + .../MaterializerNavigationLink.cs | 99 + .../ODataCollectionMaterializer.cs | 169 + .../ODataEntityMaterializer.cs | 1056 +++ .../ODataEntityMaterializerInvoker.cs | 164 + .../ODataEntriesEntityMaterializer.cs | 148 + .../Materialization/ODataItemExtensions.cs | 101 + .../Materialization/ODataLinksMaterializer.cs | 124 + ...ODataLoadNavigationPropertyMaterializer.cs | 135 + .../Materialization/ODataMaterializer.cs | 405 + .../ODataMaterializerContext.cs | 88 + .../ODataMessageReaderMaterializer.cs | 185 + .../ODataPropertyMaterializer.cs | 108 + .../ODataReaderEntityMaterializer.cs | 186 + .../Materialization/ODataReaderWrapper.cs | 127 + .../Materialization/ODataValueMaterializer.cs | 53 + .../PrimitivePropertyConverter.cs | 206 + .../PrimitiveValueMaterializationPolicy.cs | 129 + .../StructuralValueMaterializationPolicy.cs | 279 + .../MaterializeFromAtom.cs | 595 ++ .../MaterializedEntityArgs.cs | 42 + .../MediaEntryAttribute.cs | 42 + .../MemberAssignmentAnalysis.cs | 412 + .../src/Microsoft.OData.Client/MergeOption.cs | 56 + .../MessageReaderSettingsArgs.cs | 32 + .../MessageWriterSettingsArgs.cs | 32 + .../Metadata/ClientPropertyAnnotation.cs | 481 ++ .../Metadata/ClientTypeAnnotation.cs | 347 + .../Metadata/ClientTypeCache.cs | 175 + .../Metadata/ClientTypeUtil.cs | 763 ++ ...EdmComplexTypeWithDelayLoadedProperties.cs | 81 + .../Metadata/EdmEntitySetFacade.cs | 121 + .../EdmEntityTypeWithDelayLoadedProperties.cs | 95 + .../EdmEnumTypeWithDelayLoadedMembers.cs | 79 + .../Metadata/EdmFunctionImportFacade.cs | 177 + .../Metadata/EdmFunctionParameterFacade.cs | 81 + .../Metadata/EdmNavigationPropertyFacade.cs | 262 + .../Microsoft.OData.Client.Common.txt | 290 + .../Microsoft.OData.Client.Desktop.txt | 14 + ...oft.OData.Client.NetStandard.VS2017.csproj | 240 + .../Microsoft.OData.Client.cs | 344 + .../Microsoft.OData.Client.csproj | 503 ++ .../Microsoft.OData.Client.tt | 23 + .../MimeTypePropertyAttribute.cs | 48 + .../ODataAnnotatableExtensions.cs | 53 + .../ODataMessageReadingHelper.cs | 79 + .../ODataMessageWritingHelper.cs | 91 + .../ODataProtocolVersion.cs | 18 + .../ODataRequestMessageWrapper.cs | 683 ++ .../OperationDescriptor.cs | 86 + .../OperationParameter.cs | 51 + .../OperationResponse.cs | 73 + .../Parameterized.Microsoft.OData.Client.cs | 2019 +++++ .../Parameterized.Microsoft.OData.Client.tt | 20 + .../Microsoft.OData.Client/ProjectionPath.cs | 113 + .../ProjectionPathBuilder.cs | 321 + .../ProjectionPathSegment.cs | 111 + .../Microsoft.OData.Client/ProjectionPlan.cs | 81 + .../ProjectionPlanCompiler.cs | 1231 +++ .../QueryOperationResponse.cs | 183 + .../QueryOperationResponseOfT.cs | 83 + .../src/Microsoft.OData.Client/QueryResult.cs | 741 ++ .../ReadingEntryArgs.cs | 33 + .../Microsoft.OData.Client/ReadingFeedArgs.cs | 34 + .../ReadingNestedResourceInfoArgs.cs | 34 + .../ReadingWritingEntityEventArgs.cs | 70 + .../ReceivingResponseEventArgs.cs | 59 + .../ReferenceEqualityComparer.cs | 171 + .../src/Microsoft.OData.Client/RequestInfo.cs | 497 ++ .../Microsoft.OData.Client/ResponseInfo.cs | 188 + .../SaveChangesOptions.cs | 38 + .../src/Microsoft.OData.Client/SaveResult.cs | 997 +++ .../SendingRequest2EventArgs.cs | 49 + .../SendingRequestEventArgs.cs | 67 + .../DataServiceClientRequestMessage.cs | 157 + .../Serialization/DataStringEscapeBuilder.cs | 140 + .../Serialization/HttpWebRequestMessage.cs | 580 ++ .../Serialization/ODataPropertyConverter.cs | 692 ++ .../Serialization/ODataWriterWrapper.cs | 184 + .../Serialization/PrimitiveParserToken.cs | 108 + .../Serialization/PrimitiveType.cs | 548 ++ .../Serialization/PrimitiveXmlConverter.cs | 888 ++ .../Serialization/Serializer.cs | 964 +++ .../ShippingAssemblyAttributes.cs | 28 + .../StreamDescriptor.cs | 286 + .../Microsoft.OData.Client/TypeResolver.cs | 447 + .../UriEntityOperationParameter.cs | 47 + .../UriOperationParameter.cs | 24 + .../src/Microsoft.OData.Client/UriResolver.cs | 295 + .../src/Microsoft.OData.Client/UriUtil.cs | 179 + .../src/Microsoft.OData.Client/Util.cs | 610 ++ .../src/Microsoft.OData.Client/Utility.cs | 46 + .../Microsoft.OData.Client/WeakDictionary.cs | 410 + .../src/Microsoft.OData.Client/WebUtil.cs | 486 ++ .../Wrappers/ODataItemWrapper.cs | 27 + .../ODataNestedResourceInfoWrapper.cs | 34 + .../Wrappers/ODataResourceSetWrapper.cs | 35 + .../Wrappers/ODataResourceWrapper.cs | 37 + .../Wrappers/ODataWriterHelper.cs | 82 + .../WritingEntityReferenceLinkArgs.cs | 50 + .../WritingEntryArgs.cs | 42 + .../WritingNestedResourceInfoArgs.cs | 50 + .../Microsoft.OData.Client/XmlConstants.cs | 1081 +++ .../Microsoft.OData.Core/AnnotationFilter.cs | 164 + .../AnnotationFilterPattern.cs | 350 + .../AsyncBufferedStream.cs | 389 + .../Microsoft.OData.Core/BindingPathHelper.cs | 79 + .../BufferedReadStream.cs | 415 + .../BufferingReadStream.cs | 298 + .../Buffers/BufferUtils.cs | 80 + .../Buffers/ICharArrayPool.cs | 29 + .../Microsoft.OData.Core.NetFX35.csproj | 1875 +++++ .../Build.Net35/ShippingAssemblyAttributes.cs | 9 + .../Microsoft.OData.Core.NetStandard.csproj | 1925 +++++ .../Build.NetStandard/project.json | 10 + .../Microsoft.OData.Core.Net35.Release.nuspec | 32 + ...icrosoft.OData.Core.Nightly.Release.nuspec | 35 + .../Microsoft.OData.Core.Release.nuspec | 36 + .../src/Microsoft.OData.Core/Build.props | 15 + .../CollectionWithoutExpectedTypeValidator.cs | 240 + .../ContainerBuilderExtensions.cs | 153 + .../DerivedTypeValidator.cs | 76 + .../DuplicatePropertyNameChecker.cs | 152 + .../EdmExtensionMethods.cs | 119 + .../src/Microsoft.OData.Core/ErrorUtils.cs | 146 + .../Evaluation/EdmValueUtils.cs | 395 + .../IODataResourceMetadataContext.cs | 60 + .../Evaluation/KeySerializer.cs | 199 + .../Evaluation/LiteralFormatter.cs | 569 ++ .../Evaluation/NoOpResourceMetadataBuilder.cs | 172 + .../ODataConventionalEntityMetadataBuilder.cs | 696 ++ ...DataConventionalResourceMetadataBuilder.cs | 568 ++ .../Evaluation/ODataConventionalUriBuilder.cs | 285 + .../Evaluation/ODataMetadataContext.cs | 336 + .../ODataMissingOperationGenerator.cs | 157 + .../ODataResourceMetadataBuilder.cs | 407 + .../ODataResourceMetadataContext.cs | 549 ++ .../Evaluation/ODataUriBuilder.cs | 189 + .../Microsoft.OData.Core/ExceptionUtils.cs | 201 + .../GeographyTypeConverter.cs | 52 + .../GeometryTypeConverter.cs | 56 + .../GlobalSuppressions.cs | 56 + .../Microsoft.OData.Core/HttpHeaderValue.cs | 35 + .../HttpHeaderValueElement.cs | 126 + .../HttpHeaderValueLexer.cs | 615 ++ .../src/Microsoft.OData.Core/HttpUtils.cs | 885 ++ .../Microsoft.OData.Core/IContainerBuilder.cs | 47 + .../IContainerProvider.cs | 26 + .../IDuplicatePropertyNameChecker.cs | 41 + .../IODataOutputInStreamErrorListener.cs | 23 + .../IODataPayloadUriConverter.cs | 36 + .../IODataReaderWriterListener.cs | 24 + .../IODataRequestMessage.cs | 62 + .../IODataRequestMessageAsync.cs | 27 + .../IODataResourceTypeContext.cs | 56 + .../IODataResponseMessage.cs | 53 + .../IODataResponseMessageAsync.cs | 27 + .../IODataStreamListener.cs | 42 + .../IODataStreamReferenceInfo.cs | 21 + .../IPrimitiveTypeConverter.cs | 42 + .../Microsoft.OData.Core/IReaderValidator.cs | 103 + .../Microsoft.OData.Core/IWriterValidator.cs | 139 + .../InstanceAnnotationWriteTracker.cs | 51 + .../InternalErrorCodes.cs | 215 + .../InternalErrorCodesCommon.cs | 38 + .../Json/BufferingJsonReader.cs | 1084 +++ .../Json/DefaultJsonReaderFactory.cs | 27 + .../Json/DefaultJsonWriterFactory.cs | 48 + .../Json/GeoJsonWriterAdapter.cs | 90 + .../Microsoft.OData.Core/Json/IJsonReader.cs | 45 + .../Json/IJsonReaderFactory.cs | 24 + .../Json/IJsonStreamReader.cs | 35 + .../Json/IJsonStreamWriter.cs | 45 + .../Microsoft.OData.Core/Json/IJsonWriter.cs | 167 + .../Json/IJsonWriterFactory.cs | 26 + ...IODataJsonOperationsDeserializerContext.cs | 42 + .../Json/JsonConstants.cs | 168 + .../Json/JsonLightInstanceAnnotationWriter.cs | 247 + .../Microsoft.OData.Core/Json/JsonNodeType.cs | 54 + .../Microsoft.OData.Core/Json/JsonReader.cs | 1362 +++ .../Json/JsonReaderExtensions.cs | 412 + .../Json/JsonSharedUtils.cs | 64 + .../Json/JsonValueUtils.cs | 726 ++ .../Microsoft.OData.Core/Json/JsonWriter.cs | 634 ++ .../Json/JsonWriterExtensions.cs | 210 + .../Json/NonIndentedTextWriter.cs | 51 + .../Json/ODataJsonFormat.cs | 170 + .../Json/ODataJsonReaderCoreUtils.cs | 224 + .../Json/ODataJsonTextWriter.cs | 384 + .../Json/ODataJsonWriterUtils.cs | 252 + .../Json/ODataStringEscapeOption.cs | 25 + .../Json/TextWriterWrapper.cs | 93 + .../IODataJsonLightReaderResourceState.cs | 81 + .../IODataJsonLightWriterResourceState.cs | 80 + .../JsonLight/JsonFullMetadataLevel.cs | 166 + .../JsonFullMetadataTypeNameOracle.cs | 123 + .../JsonLight/JsonLightConstants.cs | 120 + .../JsonLight/JsonLightMetadataLevel.cs | 114 + .../JsonLightODataAnnotationWriter.cs | 128 + .../JsonLight/JsonLightTypeNameOracle.cs | 64 + .../JsonLight/JsonMinimalMetadataLevel.cs | 72 + .../JsonMinimalMetadataTypeNameOracle.cs | 191 + .../JsonLight/JsonNoMetadataLevel.cs | 57 + .../JsonLight/JsonNoMetadataTypeNameOracle.cs | 81 + .../JsonLight/ODataAnnotationNames.cs | 176 + .../ODataJsonLightBatchAtomicGroupCache.cs | 176 + ...taJsonLightBatchBodyContentReaderStream.cs | 368 + ...sonLightBatchPayloadItemPropertiesCache.cs | 279 + .../JsonLight/ODataJsonLightBatchReader.cs | 508 ++ .../ODataJsonLightBatchReaderStream.cs | 114 + .../JsonLight/ODataJsonLightBatchWriter.cs | 651 ++ .../ODataJsonLightCollectionDeserializer.cs | 292 + .../ODataJsonLightCollectionReader.cs | 328 + .../ODataJsonLightCollectionSerializer.cs | 91 + .../ODataJsonLightCollectionWriter.cs | 172 + .../ODataJsonLightContextUriParseResult.cs | 85 + .../ODataJsonLightContextUriParser.cs | 500 ++ .../JsonLight/ODataJsonLightDeltaReader.cs | 220 + .../JsonLight/ODataJsonLightDeltaWriter.cs | 312 + .../JsonLight/ODataJsonLightDeserializer.cs | 917 ++ ...sonLightEntityReferenceLinkDeserializer.cs | 475 ++ ...aJsonLightEntityReferenceLinkSerializer.cs | 164 + .../ODataJsonLightErrorDeserializer.cs | 464 + .../JsonLight/ODataJsonLightInputContext.cs | 849 ++ .../JsonLight/ODataJsonLightOutputContext.cs | 1008 +++ .../ODataJsonLightParameterDeserializer.cs | 220 + .../ODataJsonLightParameterReader.cs | 304 + .../ODataJsonLightParameterWriter.cs | 162 + ...onLightPayloadKindDetectionDeserializer.cs | 183 + ...taJsonLightPropertyAndValueDeserializer.cs | 2112 +++++ .../ODataJsonLightPropertySerializer.cs | 634 ++ .../JsonLight/ODataJsonLightReader.cs | 2985 +++++++ .../ODataJsonLightReaderNestedInfo.cs | 29 + .../ODataJsonLightReaderNestedPropertyInfo.cs | 22 + .../ODataJsonLightReaderNestedResourceInfo.cs | 307 + .../ODataJsonLightReaderNestedStreamInfo.cs | 64 + .../JsonLight/ODataJsonLightReaderUtils.cs | 233 + .../ODataJsonLightResourceDeserializer.cs | 2241 +++++ .../ODataJsonLightResourceSerializer.cs | 509 ++ .../JsonLight/ODataJsonLightSerializer.cs | 255 + ...ataJsonLightServiceDocumentDeserializer.cs | 363 + ...ODataJsonLightServiceDocumentSerializer.cs | 136 + .../JsonLight/ODataJsonLightUtils.cs | 156 + .../ODataJsonLightValidationUtils.cs | 108 + .../ODataJsonLightValueSerializer.cs | 413 + .../JsonLight/ODataJsonLightWriter.cs | 2224 +++++ .../JsonLight/ODataJsonLightWriterUtils.cs | 58 + .../JsonLight/ReorderingJsonReader.cs | 681 ++ .../Microsoft.OData.Core/MediaTypeUtils.cs | 1087 +++ .../MessageStreamWrapper.cs | 324 + .../Metadata/BufferingXmlReader.cs | 1397 +++ .../CachedPrimitiveKeepInContentAnnotation.cs | 51 + .../Metadata/EdmConstants.cs | 136 + .../Metadata/EdmLibraryExtensions.cs | 1965 +++++ .../Metadata/EdmTypeReaderResolver.cs | 149 + .../Metadata/EdmTypeResolver.cs | 48 + .../Metadata/EdmTypeWriterResolver.cs | 71 + .../Metadata/MetadataUtils.cs | 228 + .../Metadata/MetadataUtilsCommon.cs | 579 ++ .../Metadata/ODataAtomErrorDeserializer.cs | 295 + .../Metadata/ODataMetadataConstants.cs | 61 + .../Metadata/ODataMetadataReaderUtils.cs | 73 + .../Metadata/ODataMetadataWriterUtils.cs | 73 + .../Metadata/XmlReaderExtensions.cs | 188 + ...osoft.OData.Core.NetStandard.VS2017.csproj | 109 + .../Microsoft.OData.Core.cs | 944 +++ .../Microsoft.OData.Core.csproj | 712 ++ .../Microsoft.OData.Core.tt | 22 + .../Microsoft.OData.Core.txt | 968 +++ .../src/Microsoft.OData.Core/MimeConstants.cs | 134 + .../MultipartMixed/DependsOnIdsTracker.cs | 90 + .../ODataMultipartMixedBatchFormat.cs | 206 + .../ODataMultipartMixedBatchInputContext.cs | 85 + .../ODataMultipartMixedBatchOutputContext.cs | 82 + .../ODataMultipartMixedBatchReader.cs | 398 + .../ODataMultipartMixedBatchReaderStream.cs | 812 ++ .../ODataMultipartMixedBatchWriter.cs | 448 + .../ODataMultipartMixedBatchWriterUtils.cs | 264 + .../NonDisposingStream.cs | 200 + .../src/Microsoft.OData.Core/ODataAction.cs | 18 + .../Microsoft.OData.Core/ODataAnnotatable.cs | 55 + .../ODataAsynchronousReader.cs | 313 + .../ODataAsynchronousResponseMessage.cs | 217 + .../ODataAsynchronousWriter.cs | 214 + .../ODataBatchOperationHeaders.cs | 169 + .../ODataBatchOperationMessage.cs | 213 + .../ODataBatchOperationRequestMessage.cs | 200 + .../ODataBatchOperationResponseMessage.cs | 156 + .../ODataBatchPayloadUriConverter.cs | 154 + .../ODataBatchPayloadUriOptions.cs | 36 + .../Microsoft.OData.Core/ODataBatchReader.cs | 673 ++ .../ODataBatchReaderState.cs | 36 + .../ODataBatchReaderStream.cs | 63 + .../ODataBatchReaderStreamBuffer.cs | 617 ++ .../ODataBatchReaderStreamScanResult.cs | 28 + .../Microsoft.OData.Core/ODataBatchUtils.cs | 220 + .../Microsoft.OData.Core/ODataBatchWriter.cs | 1045 +++ .../ODataBinaryStreamReader.cs | 175 + .../ODataBinaryStreamWriter.cs | 227 + .../ODataCollectionReader.cs | 50 + .../ODataCollectionReaderCore.cs | 475 ++ .../ODataCollectionReaderCoreAsync.cs | 91 + .../ODataCollectionReaderState.cs | 56 + .../ODataCollectionStart.cs | 61 + .../ODataCollectionStartSerializationInfo.cs | 52 + .../ODataCollectionWriter.cs | 60 + .../ODataCollectionWriterCore.cs | 668 ++ .../Microsoft.OData.Core/ODataConstants.cs | 210 + .../ODataContentTypeException.cs | 61 + .../ODataContextUriBuilder.cs | 297 + .../ODataContextUrlInfo.cs | 440 + .../ODataContextUrlLevel.cs | 33 + .../ODataDeltaDeletedEntry.cs | 144 + .../ODataDeltaDeletedLink.cs | 27 + .../Microsoft.OData.Core/ODataDeltaKind.cs | 32 + .../Microsoft.OData.Core/ODataDeltaLink.cs | 27 + .../ODataDeltaLinkBase.cs | 65 + .../Microsoft.OData.Core/ODataDeltaReader.cs | 47 + .../ODataDeltaReaderState.cs | 89 + .../ODataDeltaResourceSet.cs | 15 + .../ODataDeltaResourceSetSerializationInfo.cs | 96 + .../ODataDeltaSerializationInfo.cs | 51 + .../Microsoft.OData.Core/ODataDeltaWriter.cs | 151 + .../Microsoft.OData.Core/ODataDeserializer.cs | 80 + .../ODataEdmPropertyAnnotation.cs | 23 + .../ODataEntityReferenceLink.cs | 40 + .../ODataEntityReferenceLinks.cs | 55 + .../ODataEntitySetInfo.cs | 15 + .../src/Microsoft.OData.Core/ODataError.cs | 103 + .../Microsoft.OData.Core/ODataErrorDetail.cs | 42 + .../ODataErrorException.cs | 182 + .../Microsoft.OData.Core/ODataException.cs | 63 + .../src/Microsoft.OData.Core/ODataFormat.cs | 152 + .../src/Microsoft.OData.Core/ODataFunction.cs | 18 + .../ODataFunctionImportInfo.cs | 15 + .../Microsoft.OData.Core/ODataInnerError.cs | 97 + .../Microsoft.OData.Core/ODataInputContext.cs | 667 ++ .../ODataInstanceAnnotation.cs | 94 + .../src/Microsoft.OData.Core/ODataItem.cs | 15 + .../ODataJsonDateTimeFormat.cs | 27 + .../ODataLibraryCompatibility.cs | 32 + .../Microsoft.OData.Core/ODataMediaType.cs | 233 + .../ODataMediaTypeFormat.cs | 47 + .../ODataMediaTypeResolver.cs | 177 + .../src/Microsoft.OData.Core/ODataMessage.cs | 342 + .../ODataMessageExtensions.cs | 76 + .../Microsoft.OData.Core/ODataMessageInfo.cs | 59 + .../ODataMessageQuotas.cs | 113 + .../ODataMessageReader.cs | 1517 ++++ .../ODataMessageReaderSettings.cs | 292 + .../ODataMessageWriter.cs | 1355 +++ .../ODataMessageWriterSettings.cs | 421 + .../ODataMetadataFormat.cs | 168 + .../ODataMetadataInputContext.cs | 131 + .../ODataMetadataOutputContext.cs | 153 + .../ODataNestedResourceInfo.cs | 130 + ...DataNestedResourceInfoSerializationInfo.cs | 24 + .../ODataNotificationReader.cs | 134 + .../ODataNotificationStream.cs | 217 + .../ODataNotificationWriter.cs | 341 + .../ODataNullValueBehaviorKind.cs | 32 + .../ODataObjectModelExtensions.cs | 149 + .../Microsoft.OData.Core/ODataOperation.cs | 119 + .../ODataOutputContext.cs | 708 ++ .../ODataParameterReader.cs | 82 + .../ODataParameterReaderCore.cs | 637 ++ .../ODataParameterReaderCoreAsync.cs | 131 + .../ODataParameterReaderState.cs | 47 + .../ODataParameterWriter.cs | 96 + .../ODataParameterWriterCore.cs | 828 ++ .../Microsoft.OData.Core/ODataPayloadKind.cs | 65 + .../ODataPayloadKindDetectionInfo.cs | 91 + .../ODataPayloadKindDetectionResult.cs | 49 + .../ODataPayloadValueConverter.cs | 262 + .../ODataPreferenceHeader.cs | 501 ++ .../src/Microsoft.OData.Core/ODataProperty.cs | 46 + .../Microsoft.OData.Core/ODataPropertyInfo.cs | 49 + .../Microsoft.OData.Core/ODataPropertyKind.cs | 34 + .../ODataPropertySerializationInfo.cs | 23 + .../ODataRawInputContext.cs | 261 + .../ODataRawOutputContext.cs | 399 + .../ODataRawValueConverter.cs | 176 + .../ODataRawValueFormat.cs | 147 + .../ODataRawValueUtils.cs | 251 + .../Microsoft.OData.Core/ODataReadStream.cs | 227 + .../src/Microsoft.OData.Core/ODataReader.cs | 69 + .../Microsoft.OData.Core/ODataReaderCore.cs | 1179 +++ .../ODataReaderCoreAsync.cs | 272 + .../Microsoft.OData.Core/ODataReaderState.cs | 143 + .../ODataRequestMessage.cs | 148 + .../src/Microsoft.OData.Core/ODataResource.cs | 426 + .../ODataResourceSerializationInfo.cs | 111 + .../Microsoft.OData.Core/ODataResourceSet.cs | 63 + .../ODataResourceSetBase.cs | 145 + .../ODataResourceTypeContext.cs | 457 + .../ODataResponseMessage.cs | 136 + .../Microsoft.OData.Core/ODataSerializer.cs | 92 + .../ODataServiceDocument.cs | 42 + .../ODataServiceDocumentElement.cs | 50 + .../ODataSimplifiedOptions.cs | 209 + .../ODataSingletonInfo.cs | 15 + .../src/Microsoft.OData.Core/ODataStream.cs | 78 + .../Microsoft.OData.Core/ODataStreamItem.cs | 80 + .../ODataStreamPropertyInfo.cs | 156 + .../ODataTextStreamReader.cs | 38 + .../ODataTypeAnnotation.cs | 68 + .../src/Microsoft.OData.Core/ODataUtils.cs | 327 + .../ODataUtilsInternal.cs | 135 + .../src/Microsoft.OData.Core/ODataVersion.cs | 20 + .../Microsoft.OData.Core/ODataVersionCache.cs | 63 + .../Microsoft.OData.Core/ODataWriteStream.cs | 187 + .../src/Microsoft.OData.Core/ODataWriter.cs | 419 + .../Microsoft.OData.Core/ODataWriterCore.cs | 3462 ++++++++ .../Parameterized.Microsoft.OData.Core.cs | 6182 ++++++++++++++ .../Parameterized.Microsoft.OData.Core.tt | 19 + .../PrimitiveConverter.cs | 129 + .../PropertyAndAnnotationCollector.cs | 576 ++ .../src/Microsoft.OData.Core/PropertyCache.cs | 31 + .../PropertyCacheHandler.cs | 95 + .../PropertyMetadataTypeInfo.cs | 63 + .../PropertySerializationInfo.cs | 61 + .../PropertyValueTypeInfo.cs | 41 + .../Microsoft.OData.Core/RawValueWriter.cs | 181 + .../ReadOnlyEnumerable.cs | 47 + .../ReadOnlyEnumerableExtensions.cs | 87 + .../ReadOnlyEnumerableOfT.cs | 83 + .../src/Microsoft.OData.Core/ReaderUtils.cs | 189 + .../ReaderValidationUtils.cs | 1172 +++ .../Microsoft.OData.Core/ReaderValidator.cs | 143 + .../ReferenceEqualityComparer.cs | 86 + ...ResourceSetWithoutExpectedTypeValidator.cs | 68 + .../SelectedPropertiesNode.cs | 870 ++ .../Microsoft.OData.Core/ServiceLifetime.cs | 29 + .../Microsoft.OData.Core/ServicePrototype.cs | 22 + .../ServiceProviderExtensions.cs | 107 + .../ShippingAssemblyAttributes.cs | 24 + .../src/Microsoft.OData.Core/SimpleLazy.cs | 109 + .../Microsoft.OData.Core/StreamExtensions.cs | 39 + .../src/Microsoft.OData.Core/TaskUtils.cs | 853 ++ .../Microsoft.OData.Core/TypeNameOracle.cs | 328 + .../src/Microsoft.OData.Core/TypeUtils.cs | 110 + .../Microsoft.OData.Core/UnknownEntitySet.cs | 69 + .../Uri/ApplyClauseToStringBuilder.cs | 296 + .../Uri/ExpressionConstants.cs | 243 + .../Uri/NodeToStringBuilder.cs | 712 ++ .../src/Microsoft.OData.Core/Uri/ODataUri.cs | 247 + .../Uri/ODataUriConversionUtils.cs | 637 ++ .../Uri/ODataUriExtensions.cs | 145 + .../Microsoft.OData.Core/Uri/ODataUriUtils.cs | 180 + .../Uri/ODataUrlKeyDelimiter.cs | 73 + .../Uri/SelectExpandClauseToStringBuilder.cs | 229 + .../Aggregation/AggregateExpression.cs | 101 + .../Aggregation/AggregateExpressionBase.cs | 44 + .../Aggregation/AggregateExpressionToken.cs | 111 + .../UriParser/Aggregation/AggregateToken.cs | 77 + .../Aggregation/AggregateTokenBase.cs | 19 + .../AggregateTransformationNode.cs | 66 + .../Aggregation/AggregationMethod.cs | 89 + .../UriParser/Aggregation/ApplyBinder.cs | 369 + .../UriParser/Aggregation/ApplyClause.cs | 177 + .../Aggregation/ApplyTransformationToken.cs | 19 + .../Aggregation/ComputeTransformationNode.cs | 51 + .../EntitySetAggregateExpression.cs | 49 + .../Aggregation/EntitySetAggregateToken.cs | 123 + .../Aggregation/ExpandTransformationNode.cs | 51 + .../Aggregation/FilterTransformationNode.cs | 51 + .../Aggregation/GroupByPropertyNode.cs | 91 + .../UriParser/Aggregation/GroupByToken.cs | 73 + .../Aggregation/GroupByTransformationNode.cs | 85 + .../Aggregation/TransformationNode.cs | 22 + .../Aggregation/TransformationNodeKind.cs | 39 + .../UriParser/Binders/BinaryOperatorBinder.cs | 96 + .../UriParser/Binders/BinderBase.cs | 45 + .../UriParser/Binders/BindingState.cs | 186 + .../UriParser/Binders/ComputeBinder.cs | 44 + .../Binders/DottedIdentifierBinder.cs | 117 + .../UriParser/Binders/EndPathBinder.cs | 236 + .../UriParser/Binders/EnumBinder.cs | 134 + .../UriParser/Binders/ExpandTreeNormalizer.cs | 228 + .../UriParser/Binders/FilterBinder.cs | 74 + .../UriParser/Binders/FunctionCallBinder.cs | 833 ++ .../UriParser/Binders/InBinder.cs | 210 + .../UriParser/Binders/InnerPathTokenBinder.cs | 245 + .../UriParser/Binders/KeyBinder.cs | 226 + .../UriParser/Binders/LambdaBinder.cs | 118 + .../UriParser/Binders/LiteralBinder.cs | 71 + .../UriParser/Binders/MetadataBinder.cs | 374 + .../UriParser/Binders/MetadataBindingUtils.cs | 180 + .../UriParser/Binders/OrderByBinder.cs | 90 + .../UriParser/Binders/ParameterAliasBinder.cs | 131 + .../UriParser/Binders/RangeVariableBinder.cs | 37 + .../UriParser/Binders/SearchBinder.cs | 48 + .../UriParser/Binders/SelectBinder.cs | 69 + .../UriParser/Binders/SelectExpandBinder.cs | 452 + .../Binders/SelectExpandClauseFinisher.cs | 45 + .../Binders/SelectExpandPathBinder.cs | 68 + .../Binders/SelectExpandSemanticBinder.cs | 47 + .../Binders/SelectExpandSyntacticUnifier.cs | 29 + .../Binders/SelectPathSegmentTokenBinder.cs | 262 + .../UriParser/Binders/SelectTreeNormalizer.cs | 34 + .../UriParser/Binders/UnaryOperatorBinder.cs | 86 + .../UriParser/BuiltInUriFunctions.cs | 539 ++ .../UriParser/CustomUriFunctions.cs | 260 + .../UriParser/CustomUriLiteralPrefixes.cs | 111 + .../UriParser/ExceptionUtil.cs | 66 + .../UriParser/ExpressionLexer.cs | 1340 +++ .../ExpressionLexerLiteralExtensions.cs | 164 + .../UriParser/ExpressionLexerUtils.cs | 126 + .../UriParser/ExpressionToken.cs | 125 + .../UriParser/FunctionSignature.cs | 76 + .../FunctionSignatureWithReturnType.cs | 57 + .../UriParser/InternalErrorCodes.cs | 31 + .../UriParser/KeyPropertyValue.cs | 37 + .../UriParser/LiteralUtils.cs | 76 + .../UriParser/NamedValue.cs | 57 + .../UriParser/ODataPathInfo.cs | 80 + .../UriParser/ODataQueryOptionParser.cs | 704 ++ .../ODataUnrecognizedPathException.cs | 86 + .../UriParser/ODataUriParser.cs | 610 ++ .../UriParser/ODataUriParserConfiguration.cs | 161 + .../UriParser/ODataUriParserSettings.cs | 281 + .../UriParser/OrderByDirection.cs | 27 + .../UriParser/ParseDynamicPathSegment.cs | 20 + .../Parsers/CustomUriLiteralParsers.cs | 246 + .../Parsers/DefaultUriLiteralParser.cs | 82 + .../Parsers/ExpandDepthAndCountValidator.cs | 87 + .../UriParser/Parsers/FunctionCallParser.cs | 217 + .../Parsers/FunctionOverloadResolver.cs | 261 + .../Parsers/FunctionParameterParser.cs | 111 + .../UriParser/Parsers/IFunctionCallParser.cs | 27 + .../UriParser/Parsers/IUriLiteralParser.cs | 34 + .../UriParser/Parsers/IdentifierTokenizer.cs | 125 + .../UriParser/Parsers/KeyFinder.cs | 196 + .../UriParser/Parsers/LiteralParser.cs | 661 ++ .../UriParser/Parsers/NodeFactory.cs | 143 + .../UriParser/Parsers/ODataPathFactory.cs | 28 + .../UriParser/Parsers/ODataPathParser.cs | 1653 ++++ .../UriParser/Parsers/PathParserModelUtils.cs | 150 + .../UriParser/Parsers/PathReverser.cs | 86 + .../UriParser/Parsers/SearchParser.cs | 236 + .../Parsers/SegmentArgumentParser.cs | 364 + .../UriParser/Parsers/SegmentKeyHandler.cs | 181 + .../Parsers/SelectExpandOptionParser.cs | 703 ++ .../UriParser/Parsers/SelectExpandParser.cs | 382 + .../Parsers/SelectExpandSyntacticParser.cs | 55 + .../Parsers/SelectExpandTermParser.cs | 164 + .../Parsers/UriLiteralParsingException.cs | 49 + .../UriParser/Parsers/UriParserHelper.cs | 292 + .../UriParser/Parsers/UriPathParser.cs | 167 + .../Parsers/UriPrimitiveTypeParser.cs | 422 + .../Parsers/UriQueryExpressionParser.cs | 1302 +++ .../UriParser/Parsers/UriTemplateParser.cs | 48 + .../UriParser/QueryNodeUtils.cs | 182 + .../UriParser/QueryOptionUtils.cs | 200 + .../ReadOnlyEnumerableForUriParser.cs | 63 + .../Resolver/AlternateKeysODataUriResolver.cs | 142 + .../UriParser/Resolver/ODataUriResolver.cs | 459 + .../Resolver/StringAsEnumResolver.cs | 184 + .../Resolver/UnqualifiedODataUriResolver.cs | 73 + .../UriParser/SearchLexer.cs | 188 + .../AggregatedCollectionPropertyNode.cs | 138 + .../UriParser/SemanticAst/AllNode.cs | 73 + .../SemanticAst/AnnotationSegment.cs | 95 + .../UriParser/SemanticAst/AnyNode.cs | 73 + .../SemanticAst/BatchReferenceSegment.cs | 132 + .../UriParser/SemanticAst/BatchSegment.cs | 80 + .../SemanticAst/BinaryOperatorNode.cs | 159 + .../SemanticAst/CollectionComplexNode.cs | 150 + .../SemanticAst/CollectionConstantNode.cs | 120 + .../SemanticAst/CollectionFunctionCallNode.cs | 152 + .../SemanticAst/CollectionNavigationNode.cs | 230 + .../UriParser/SemanticAst/CollectionNode.cs | 44 + .../CollectionOpenPropertyAccessNode.cs | 105 + .../CollectionPropertyAccessNode.cs | 131 + .../SemanticAst/CollectionResourceCastNode.cs | 118 + .../CollectionResourceFunctionCallNode.cs | 178 + .../SemanticAst/CollectionResourceNode.cs | 30 + .../UriParser/SemanticAst/ComputeClause.cs | 45 + .../SemanticAst/ComputeExpression.cs | 72 + .../UriParser/SemanticAst/ConstantNode.cs | 121 + .../UriParser/SemanticAst/ConvertNode.cs | 84 + .../UriParser/SemanticAst/CountNode.cs | 79 + .../UriParser/SemanticAst/CountSegment.cs | 81 + .../SemanticAst/CountVirtualPropertyNode.cs | 40 + .../DetermineNavigationSourceTranslator.cs | 249 + .../SemanticAst/DynamicPathSegment.cs | 100 + .../UriParser/SemanticAst/EachSegment.cs | 91 + .../UriParser/SemanticAst/EntityIdSegment.cs | 30 + .../UriParser/SemanticAst/EntitySetSegment.cs | 101 + .../ExpandedNavigationSelectItem.cs | 172 + .../ExpandedReferenceSelectItem.cs | 223 + .../UriParser/SemanticAst/FilterClause.cs | 70 + .../UriParser/SemanticAst/FilterSegment.cs | 150 + .../UriParser/SemanticAst/InNode.cs | 118 + .../UriParser/SemanticAst/KeyLookupNode.cs | 116 + .../UriParser/SemanticAst/KeySegment.cs | 144 + .../UriParser/SemanticAst/LambdaNode.cs | 85 + .../UriParser/SemanticAst/LevelsClause.cs | 55 + .../UriParser/SemanticAst/MetadataSegment.cs | 80 + .../SemanticAst/NamedFunctionParameterNode.cs | 83 + .../NamespaceQualifiedWildcardSelectItem.cs | 54 + .../NavigationPropertyLinkSegment.cs | 105 + .../SemanticAst/NavigationPropertySegment.cs | 106 + .../SemanticAst/NonResourceRangeVariable.cs | 94 + .../NonResourceRangeVariableReferenceNode.cs | 97 + .../UriParser/SemanticAst/ODataExpandPath.cs | 93 + .../UriParser/SemanticAst/ODataPath.cs | 156 + .../SemanticAst/ODataPathExtensions.cs | 307 + .../UriParser/SemanticAst/ODataPathSegment.cs | 118 + .../UriParser/SemanticAst/ODataSelectPath.cs | 86 + .../ODataUnresolvedFunctionParameterAlias.cs | 38 + .../SemanticAst/OperationImportSegment.cs | 277 + .../UriParser/SemanticAst/OperationSegment.cs | 273 + .../SemanticAst/OperationSegmentParameter.cs | 36 + .../UriParser/SemanticAst/OrderByClause.cs | 103 + .../SemanticAst/ParameterAliasNode.cs | 66 + .../ParameterAliasValueAccessor.cs | 55 + .../UriParser/SemanticAst/PathSelectItem.cs | 60 + .../SemanticAst/PathTemplateSegment.cs | 70 + .../UriParser/SemanticAst/PropertySegment.cs | 94 + .../UriParser/SemanticAst/QueryNode.cs | 43 + .../UriParser/SemanticAst/RangeVariable.cs | 35 + .../SemanticAst/RangeVariableKind.cs | 24 + .../UriParser/SemanticAst/ReferenceSegment.cs | 79 + .../SemanticAst/ResourceRangeVariable.cs | 122 + .../ResourceRangeVariableReferenceNode.cs | 120 + .../UriParser/SemanticAst/SearchClause.cs | 39 + .../UriParser/SemanticAst/SearchTermNode.cs | 88 + .../SemanticAst/SelectExpandClause.cs | 138 + .../SelectExpandClauseExtensions.cs | 244 + .../UriParser/SemanticAst/SelectItem.cs | 28 + .../SemanticAst/SingleComplexNode.cs | 134 + .../UriParser/SemanticAst/SingleEntityNode.cs | 21 + .../SemanticAst/SingleNavigationNode.cs | 210 + .../SemanticAst/SingleResourceCastNode.cs | 100 + .../SingleResourceFunctionCallNode.cs | 169 + .../SemanticAst/SingleResourceNode.cs | 26 + .../SemanticAst/SingleValueCastNode.cs | 76 + .../SingleValueFunctionCallNode.cs | 159 + .../UriParser/SemanticAst/SingleValueNode.cs | 35 + .../SingleValueOpenPropertyAccessNode.cs | 92 + .../SingleValuePropertyAccessNode.cs | 108 + .../UriParser/SemanticAst/SingletonSegment.cs | 96 + .../UriParser/SemanticAst/TypeSegment.cs | 122 + .../SemanticAst/UnaryOperatorNode.cs | 108 + .../SemanticAst/UriTemplateExpression.cs | 38 + .../UriParser/SemanticAst/ValueSegment.cs | 95 + .../SemanticAst/WildcardSelectItem.cs | 36 + .../UriParser/SyntacticAst/AllToken.cs | 50 + .../UriParser/SyntacticAst/AnyToken.cs | 50 + .../SyntacticAst/BinaryOperatorToken.cs | 94 + .../SyntacticAst/ComputeExpressionToken.cs | 89 + .../UriParser/SyntacticAst/ComputeToken.cs | 72 + .../SyntacticAst/CustomQueryOptionToken.cs | 74 + .../SyntacticAst/DottedIdentifierToken.cs | 77 + .../UriParser/SyntacticAst/EndPathToken.cs | 81 + .../UriParser/SyntacticAst/ExpandTermToken.cs | 165 + .../UriParser/SyntacticAst/ExpandToken.cs | 65 + .../SyntacticAst/FunctionCallToken.cs | 114 + .../FunctionParameterAliasToken.cs | 60 + .../SyntacticAst/FunctionParameterToken.cs | 79 + .../UriParser/SyntacticAst/InToken.cs | 77 + .../UriParser/SyntacticAst/InnerPathToken.cs | 103 + .../UriParser/SyntacticAst/LambdaToken.cs | 81 + .../UriParser/SyntacticAst/LiteralToken.cs | 122 + .../UriParser/SyntacticAst/NonSystemToken.cs | 97 + .../UriParser/SyntacticAst/OrderByToken.cs | 80 + .../SyntacticAst/PathSegmentToken.cs | 71 + .../UriParser/SyntacticAst/PathToken.cs | 69 + .../UriParser/SyntacticAst/QueryToken.cs | 44 + .../UriParser/SyntacticAst/QueryTokenKind.cs | 159 + .../SyntacticAst/RangeVariableToken.cs | 82 + .../SyntacticAst/SelectExpandTermToken.cs | 112 + .../UriParser/SyntacticAst/SelectTermToken.cs | 79 + .../UriParser/SyntacticAst/SelectToken.cs | 88 + .../UriParser/SyntacticAst/StarToken.cs | 75 + .../SyntacticAst/StringLiteralToken.cs | 68 + .../UriParser/SyntacticAst/SystemToken.cs | 79 + .../SyntacticAst/UnaryOperatorToken.cs | 78 + .../TreeNodeKinds/BinaryOperatorKind.cs | 87 + .../TreeNodeKinds/ExpressionTokenKind.cs | 126 + .../UriParser/TreeNodeKinds/QueryNodeKind.cs | 363 + .../TreeNodeKinds/RequestTargetKind.cs | 57 + .../TreeNodeKinds/UnaryOperatorKind.cs | 27 + .../UriParser/TypeFacetsPromotionRules.cs | 50 + .../UriParser/TypePromotionUtils.cs | 1186 +++ .../UriParser/UriEdmHelpers.cs | 186 + .../UriParser/UriFunctionsHelper.cs | 62 + .../UriParser/UriQueryConstants.cs | 98 + .../Visitors/IPathSegmentTokenVisitor.cs | 51 + .../Visitors/ISyntacticTreeVisitor.cs | 189 + .../Visitors/IsCollectionTranslator.cs | 209 + .../UriParser/Visitors/PathSegmentHandler.cs | 205 + .../PathSegmentToContextUrlPathTranslator.cs | 275 + .../PathSegmentToResourcePathTranslator.cs | 289 + .../Visitors/PathSegmentToStringTranslator.cs | 152 + .../PathSegmentTokenEqualityComparer.cs | 69 + .../Visitors/PathSegmentTokenVisitor.cs | 61 + .../Visitors/PathSegmentTranslator.cs | 217 + .../UriParser/Visitors/QueryNodeVisitor.cs | 317 + .../UriParser/Visitors/SelectItemHandler.cs | 61 + .../Visitors/SelectItemTranslator.cs | 67 + .../Visitors/SelectPropertyVisitor.cs | 253 + .../SplitEndingSegmentOfTypeHandler.cs | 262 + .../Visitors/SyntacticTreeVisitor.cs | 282 + .../src/Microsoft.OData.Core/UriUtils.cs | 276 + .../src/Microsoft.OData.Core/Utils.cs | 123 + .../Microsoft.OData.Core/ValidationKinds.cs | 44 + .../Microsoft.OData.Core/ValidationUtils.cs | 467 ++ .../Value/ODataBinaryStreamValue.cs | 34 + .../Value/ODataCollectionValue.cs | 36 + .../Value/ODataEnumValue.cs | 37 + .../Value/ODataNullValue.cs | 26 + .../Value/ODataPrimitiveValue.cs | 43 + .../Value/ODataResourceValue.cs | 39 + .../Value/ODataStreamReferenceValue.cs | 132 + .../Value/ODataUntypedValue.cs | 29 + .../Microsoft.OData.Core/Value/ODataValue.cs | 26 + .../Value/ODataValueUtils.cs | 74 + .../src/Microsoft.OData.Core/WriterUtils.cs | 76 + .../WriterValidationUtils.cs | 572 ++ .../Microsoft.OData.Core/WriterValidator.cs | 270 + .../Microsoft.OData.Edm.NetFX35.csproj | 1555 ++++ .../Microsoft.OData.Edm.NetStandard.csproj | 1569 ++++ .../Build.NetStandard/project.json | 10 + .../Microsoft.OData.Edm.Net35.Release.nuspec | 26 + ...Microsoft.OData.Edm.Nightly.Release.nuspec | 29 + .../Microsoft.OData.Edm.Release.nuspec | 30 + .../src/Microsoft.OData.Edm/Build.props | 11 + .../src/Microsoft.OData.Edm/Cache.cs | 137 + .../src/Microsoft.OData.Edm/CacheHelper.cs | 26 + .../Microsoft.OData.Edm/Csdl/CsdlConstants.cs | 271 + .../Microsoft.OData.Edm/Csdl/CsdlLocation.cs | 58 + .../Microsoft.OData.Edm/Csdl/CsdlReader.cs | 726 ++ .../Csdl/CsdlReaderSettings.cs | 35 + .../Microsoft.OData.Edm/Csdl/CsdlTarget.cs | 24 + .../Microsoft.OData.Edm/Csdl/CsdlWriter.cs | 177 + .../Csdl/EdmEnumValueParser.cs | 109 + .../Csdl/EdmParseException.cs | 60 + .../Csdl/EdmValueParser.cs | 386 + .../Csdl/EdmValueWriter.cs | 239 + .../Ast/CsdlAbstractNavigationSource.cs | 29 + .../Csdl/Parsing/Ast/CsdlAction.cs | 36 + .../Csdl/Parsing/Ast/CsdlActionImport.cs | 23 + .../Csdl/Parsing/Ast/CsdlAnnotation.cs | 41 + .../Ast/CsdlAnnotationPathExpression.cs | 23 + .../Csdl/Parsing/Ast/CsdlAnnotations.cs | 42 + .../Csdl/Parsing/Ast/CsdlApplyExpression.cs | 38 + .../Parsing/Ast/CsdlBinaryTypeReference.cs | 21 + .../Csdl/Parsing/Ast/CsdlCastExpression.cs | 36 + .../Parsing/Ast/CsdlCollectionExpression.cs | 41 + .../Csdl/Parsing/Ast/CsdlCollectionType.cs | 27 + .../Csdl/Parsing/Ast/CsdlComplexType.cs | 21 + .../Parsing/Ast/CsdlConstantExpression.cs | 75 + .../Parsing/Ast/CsdlDecimalTypeReference.cs | 21 + .../Parsing/Ast/CsdlDirectValueAnnotation.cs | 48 + .../Csdl/Parsing/Ast/CsdlElement.cs | 99 + .../Csdl/Parsing/Ast/CsdlEntityContainer.cs | 50 + .../Parsing/Ast/CsdlEntityReferenceType.cs | 27 + .../Csdl/Parsing/Ast/CsdlEntitySet.cs | 37 + .../Csdl/Parsing/Ast/CsdlEntityType.cs | 36 + .../Csdl/Parsing/Ast/CsdlEnumMember.cs | 31 + .../Parsing/Ast/CsdlEnumMemberExpression.cs | 29 + .../Csdl/Parsing/Ast/CsdlEnumType.cs | 43 + .../Csdl/Parsing/Ast/CsdlExpressionBase.cs | 21 + .../Ast/CsdlExpressionTypeReference.cs | 27 + .../Csdl/Parsing/Ast/CsdlFunction.cs | 41 + .../Csdl/Parsing/Ast/CsdlFunctionBase.cs | 36 + .../Csdl/Parsing/Ast/CsdlFunctionImport.cs | 27 + .../Csdl/Parsing/Ast/CsdlIfExpression.cs | 43 + .../Csdl/Parsing/Ast/CsdlIsTypeExpression.cs | 36 + .../Csdl/Parsing/Ast/CsdlKey.cs | 29 + .../Csdl/Parsing/Ast/CsdlLabeledExpression.cs | 36 + ...sdlLabeledExpressionReferenceExpression.cs | 29 + .../Csdl/Parsing/Ast/CsdlModel.cs | 64 + .../Csdl/Parsing/Ast/CsdlNamedElement.cs | 27 + .../Parsing/Ast/CsdlNamedStructuredType.cs | 50 + .../Parsing/Ast/CsdlNamedTypeReference.cs | 54 + .../Parsing/Ast/CsdlNavigationProperty.cs | 64 + .../Ast/CsdlNavigationPropertyBinding.cs | 34 + .../CsdlNavigationPropertyPathExpression.cs | 24 + .../Csdl/Parsing/Ast/CsdlOnDelete.cs | 27 + .../Csdl/Parsing/Ast/CsdlOperation.cs | 48 + .../Csdl/Parsing/Ast/CsdlOperationImport.cs | 41 + .../Parsing/Ast/CsdlOperationParameter.cs | 46 + .../Csdl/Parsing/Ast/CsdlOperationReturn.cs | 27 + .../Csdl/Parsing/Ast/CsdlPathExpression.cs | 32 + .../Parsing/Ast/CsdlPrimitiveTypeReference.cs | 27 + .../Csdl/Parsing/Ast/CsdlProperty.cs | 34 + .../Parsing/Ast/CsdlPropertyPathExpression.cs | 24 + .../Csdl/Parsing/Ast/CsdlPropertyReference.cs | 27 + .../Csdl/Parsing/Ast/CsdlPropertyValue.cs | 34 + .../Csdl/Parsing/Ast/CsdlRecordExpression.cs | 41 + .../Parsing/Ast/CsdlReferentialConstraint.cs | 34 + .../Ast/CsdlReferentialConstraintRole.cs | 41 + .../Csdl/Parsing/Ast/CsdlSchema.cs | 105 + .../Csdl/Parsing/Ast/CsdlSingleton.cs | 29 + .../Parsing/Ast/CsdlSpatialTypeReference.cs | 24 + .../Parsing/Ast/CsdlStringTypeReference.cs | 22 + .../Csdl/Parsing/Ast/CsdlStructuredType.cs | 36 + .../Parsing/Ast/CsdlTemporalTypeReference.cs | 20 + .../Csdl/Parsing/Ast/CsdlTerm.cs | 41 + .../Csdl/Parsing/Ast/CsdlTypeDefinition.cs | 27 + .../Csdl/Parsing/Ast/CsdlTypeReference.cs | 27 + .../Parsing/Ast/CsdlUntypedTypeReference.cs | 19 + .../Csdl/Parsing/Ast/ICsdlTypeExpression.cs | 18 + .../Parsing/Common/EdmXmlDocumentParser.cs | 517 ++ .../Csdl/Parsing/Common/XmlDocumentParser.cs | 606 ++ .../Csdl/Parsing/Common/XmlElementInfo.cs | 198 + .../Csdl/Parsing/Common/XmlElementParser.cs | 318 + .../Csdl/Parsing/CsdlDocumentParser.cs | 1317 +++ .../Csdl/Parsing/CsdlParser.cs | 106 + .../Microsoft.OData.Edm/Csdl/SchemaReader.cs | 84 + .../Microsoft.OData.Edm/Csdl/SchemaWriter.cs | 90 + .../BadElements/IUnresolvedElement.cs | 12 + .../Semantics/BadElements/UnresolvedAction.cs | 24 + .../BadElements/UnresolvedComplexType.cs | 18 + .../BadElements/UnresolvedEntityContainer.cs | 18 + .../BadElements/UnresolvedEntitySet.cs | 18 + .../BadElements/UnresolvedEntityType.cs | 18 + .../BadElements/UnresolvedEnumMember.cs | 48 + .../BadElements/UnresolvedEnumType.cs | 18 + .../BadElements/UnresolvedFunction.cs | 29 + .../BadElements/UnresolvedLabeledElement.cs | 18 + .../UnresolvedNavigationPropertyPath.cs | 21 + .../BadElements/UnresolvedOperation.cs | 79 + .../BadElements/UnresolvedParameter.cs | 39 + .../BadElements/UnresolvedPrimitiveType.cs | 18 + .../BadElements/UnresolvedProperty.cs | 18 + .../Semantics/BadElements/UnresolvedReturn.cs | 36 + .../Semantics/BadElements/UnresolvedType.cs | 50 + .../BadElements/UnresolvedVocabularyTerm.cs | 87 + .../Csdl/Semantics/CsdlSemanticsAction.cs | 31 + .../Semantics/CsdlSemanticsActionImport.cs | 28 + .../CsdlSemanticsAnnotationPathExpression.cs | 25 + .../Semantics/CsdlSemanticsAnnotations.cs | 35 + .../Semantics/CsdlSemanticsApplyExpression.cs | 161 + .../CsdlSemanticsBinaryConstantExpression.cs | 84 + .../CsdlSemanticsBinaryTypeReference.cs | 31 + .../CsdlSemanticsBooleanConstantExpression.cs | 82 + .../Semantics/CsdlSemanticsCastExpression.cs | 61 + .../CsdlSemanticsCollectionExpression.cs | 72 + .../CsdlSemanticsCollectionTypeDefinition.cs | 55 + .../CsdlSemanticsCollectionTypeExpression.cs | 18 + .../CsdlSemanticsComplexTypeDefinition.cs | 90 + .../CsdlSemanticsDateConstantExpression.cs | 84 + ...manticsDateTimeOffsetConstantExpression.cs | 84 + .../CsdlSemanticsDecimalConstantExpression.cs | 84 + .../CsdlSemanticsDecimalTypeReference.cs | 31 + .../CsdlSemanticsDirectValueAnnotation.cs | 63 + ...lSemanticsDirectValueAnnotationsManager.cs | 29 + ...CsdlSemanticsDurationConstantExpression.cs | 84 + .../Csdl/Semantics/CsdlSemanticsElement.cs | 146 + .../Semantics/CsdlSemanticsEntityContainer.cs | 287 + ...lSemanticsEntityReferenceTypeDefinition.cs | 57 + ...lSemanticsEntityReferenceTypeExpression.cs | 18 + .../Csdl/Semantics/CsdlSemanticsEntitySet.cs | 43 + .../CsdlSemanticsEntityTypeDefinition.cs | 143 + .../Csdl/Semantics/CsdlSemanticsEnumMember.cs | 79 + .../CsdlSemanticsEnumMemberExpression.cs | 71 + .../CsdlSemanticsEnumTypeDefinition.cs | 149 + .../Csdl/Semantics/CsdlSemanticsExpression.cs | 36 + ...CsdlSemanticsFloatingConstantExpression.cs | 84 + .../Csdl/Semantics/CsdlSemanticsFunction.cs | 34 + .../Semantics/CsdlSemanticsFunctionImport.cs | 38 + .../CsdlSemanticsGuidConstantExpression.cs | 84 + .../Semantics/CsdlSemanticsIfExpression.cs | 79 + .../CsdlSemanticsIntConstantExpression.cs | 84 + .../CsdlSemanticsIsTypeExpression.cs | 61 + .../CsdlSemanticsLabeledExpression.cs | 67 + ...icsLabeledExpressionReferenceExpression.cs | 70 + .../Csdl/Semantics/CsdlSemanticsModel.cs | 614 ++ .../CsdlSemanticsNamedTypeReference.cs | 62 + .../CsdlSemanticsNavigationProperty.cs | 274 + ...manticsNavigationPropertyPathExpression.cs | 26 + .../CsdlSemanticsNavigationSource.cs | 223 + .../Semantics/CsdlSemanticsNullExpression.cs | 45 + .../Csdl/Semantics/CsdlSemanticsOperation.cs | 285 + .../Semantics/CsdlSemanticsOperationImport.cs | 91 + .../CsdlSemanticsOperationParameter.cs | 67 + .../Semantics/CsdlSemanticsOperationReturn.cs | 62 + .../CsdlSemanticsOptionalParameter.cs | 26 + .../Semantics/CsdlSemanticsPathExpression.cs | 58 + .../CsdlSemanticsPrimitiveTypeReference.cs | 57 + .../Csdl/Semantics/CsdlSemanticsProperty.cs | 77 + .../CsdlSemanticsPropertyConstructor.cs | 56 + .../CsdlSemanticsPropertyPathExpression.cs | 26 + .../CsdlSemanticsRecordExpression.cs | 77 + .../Csdl/Semantics/CsdlSemanticsSchema.cs | 471 ++ .../Csdl/Semantics/CsdlSemanticsSingleton.cs | 39 + .../CsdlSemanticsSpatialTypeReference.cs | 23 + .../CsdlSemanticsStringConstantExpression.cs | 60 + .../CsdlSemanticsStringTypeReference.cs | 36 + .../CsdlSemanticsStructuredTypeDefinition.cs | 129 + .../CsdlSemanticsTemporalTypeReference.cs | 26 + .../Csdl/Semantics/CsdlSemanticsTerm.cs | 93 + ...sdlSemanticsTimeOfDayConstantExpression.cs | 84 + .../Semantics/CsdlSemanticsTypeDefinition.cs | 33 + .../CsdlSemanticsTypeDefinitionDefinition.cs | 95 + .../CsdlSemanticsTypeDefinitionReference.cs | 132 + .../Semantics/CsdlSemanticsTypeExpression.cs | 51 + .../CsdlSemanticsUntypedTypeReference.cs | 57 + .../CsdlSemanticsVocabularyAnnotation.cs | 428 + .../Serialization/EdmModelCsdlSchemaWriter.cs | 849 ++ .../EdmModelCsdlSerializationVisitor.cs | 715 ++ .../EdmModelReferenceElementsVisitor.cs | 57 + ...delSchemaSeparationSerializationVisitor.cs | 192 + .../Csdl/Serialization/EdmSchema.cs | 82 + .../Serialization/SerializationValidator.cs | 181 + .../Csdl/SerializationExtensionMethods.cs | 308 + .../src/Microsoft.OData.Edm/EdmLocation.cs | 20 + .../Microsoft.OData.Edm/EdmModelVisitor.cs | 872 ++ .../src/Microsoft.OData.Edm/EdmUtil.cs | 727 ++ .../ExtensionMethods/EdmElementComparer.cs | 207 + .../ExtensionMethods/EdmTypeSemantics.cs | 1420 ++++ .../ExtensionMethods/EnumHelper.cs | 331 + .../ExtensionMethods/ExtensionMethods.cs | 3423 ++++++++ .../ToTraceStringExtensionMethods.cs | 194 + .../Microsoft.OData.Edm/GlobalSuppressions.cs | 281 + .../Microsoft.OData.Edm/HashSetInternal.cs | 52 + .../Microsoft.OData.Edm/IDependencyTrigger.cs | 16 + .../src/Microsoft.OData.Edm/IDependent.cs | 16 + .../src/Microsoft.OData.Edm/IFlushCaches.cs | 16 + .../src/Microsoft.OData.Edm/Memoizer.cs | 167 + ...rosoft.OData.Edm.NetStandard.VS2017.csproj | 145 + .../Microsoft.OData.Edm.StyleCop | 312 + .../Microsoft.OData.Edm.cs | 433 + .../Microsoft.OData.Edm.csproj | 607 ++ .../Microsoft.OData.Edm.tt | 22 + .../Microsoft.OData.Edm.txt | 326 + .../Parameterized.Microsoft.OData.Edm.cs | 2207 +++++ .../Parameterized.Microsoft.OData.Edm.tt | 19 + .../DefaultPrimitiveValueConverter.cs | 66 + .../IPrimitiveValueConverter.cs | 32 + .../PassThroughPrimitiveValueConverter.cs | 30 + .../PrimitiveValueConverterConstants.cs | 44 + .../Microsoft.OData.Edm/RegistrationHelper.cs | 178 + .../Schema/AmbiguousBinding.cs | 47 + .../Schema/AmbiguousEntityContainerBinding.cs | 64 + .../Schema/AmbiguousEntitySetBinding.cs | 69 + .../AmbiguousLabeledExpressionBinding.cs | 40 + .../Schema/AmbiguousOperationBinding.cs | 67 + .../Schema/AmbiguousOperationImportBinding.cs | 42 + .../Schema/AmbiguousPropertyBinding.cs | 51 + .../Schema/AmbiguousSingletonBinding.cs | 65 + .../Schema/AmbiguousTermBinding.cs | 73 + .../Schema/AmbiguousTypeBinding.cs | 50 + .../Schema/BadBinaryTypeReference.cs | 40 + .../Schema/BadCollectionType.cs | 35 + .../Schema/BadComplexType.cs | 28 + .../Schema/BadComplexTypeReference.cs | 37 + .../Schema/BadDecimalTypeReference.cs | 40 + .../Schema/BadEdmEnumMemberValue.cs | 24 + .../Microsoft.OData.Edm/Schema/BadElement.cs | 30 + .../Schema/BadEntityContainer.cs | 75 + .../Schema/BadEntityReferenceType.cs | 36 + .../Schema/BadEntitySet.cs | 79 + .../Schema/BadEntityType.cs | 38 + .../Schema/BadEntityTypeReference.cs | 37 + .../Microsoft.OData.Edm/Schema/BadEnumType.cs | 72 + .../Schema/BadLabeledExpression.cs | 50 + .../Schema/BadNamedStructuredType.cs | 52 + .../Schema/BadNavigationProperty.cs | 86 + .../Microsoft.OData.Edm/Schema/BadPathType.cs | 69 + .../Schema/BadPathTypeReference.cs | 37 + .../Schema/BadPrimitiveType.cs | 63 + .../Schema/BadPrimitiveTypeReference.cs | 40 + .../Microsoft.OData.Edm/Schema/BadProperty.cs | 71 + .../Schema/BadSpatialTypeReference.cs | 40 + .../Schema/BadStringTypeReference.cs | 40 + .../Schema/BadStructuredType.cs | 48 + .../Schema/BadTemporalTypeReference.cs | 40 + .../src/Microsoft.OData.Edm/Schema/BadType.cs | 34 + .../Schema/BadTypeDefinition.cs | 62 + .../Schema/BadTypeReference.cs | 37 + .../Schema/CoreModel/EdmCoreModel.cs | 793 ++ .../CoreModel/EdmCoreModelComplexType.cs | 123 + .../CoreModel/EdmCoreModelEntityType.cs | 135 + .../Schema/CoreModel/EdmCoreModelPathType.cs | 66 + .../CoreModel/EdmCoreModelPrimitiveType.cs | 64 + .../CoreModel/EdmCoreModelUntypedType.cs | 68 + .../Schema/CoreModel/IEdmCoreModelElement.cs | 15 + .../Schema/CyclicComplexType.cs | 21 + .../Schema/CyclicEntityContainer.cs | 21 + .../Schema/CyclicEntityType.cs | 21 + .../src/Microsoft.OData.Edm/Schema/Date.cs | 372 + .../Microsoft.OData.Edm/Schema/EdmAction.cs | 46 + .../Schema/EdmActionImport.cs | 64 + .../Schema/EdmBinaryTypeReference.cs | 64 + .../Schema/EdmCollectionType.cs | 42 + .../Schema/EdmCollectionTypeReference.cs | 39 + .../Schema/EdmComplexType.cs | 111 + .../Schema/EdmComplexTypeReference.cs | 24 + .../Schema/EdmConstants.cs | 92 + .../Schema/EdmContainedEntitySet.cs | 186 + .../Schema/EdmDecimalTypeReference.cs | 59 + .../Microsoft.OData.Edm/Schema/EdmElement.cs | 15 + .../Schema/EdmEntityContainer.cs | 295 + .../Schema/EdmEntityReferenceType.cs | 42 + .../Schema/EdmEntityReferenceTypeReference.cs | 32 + .../Schema/EdmEntitySet.cs | 88 + .../Schema/EdmEntitySetBase.cs | 37 + .../Schema/EdmEntityType.cs | 269 + .../Schema/EdmEntityTypeReference.cs | 24 + .../Schema/EdmEnumMember.cs | 49 + .../Schema/EdmEnumMemberValue.cs | 36 + .../Microsoft.OData.Edm/Schema/EdmEnumType.cs | 162 + .../Schema/EdmEnumTypeReference.cs | 24 + .../Microsoft.OData.Edm/Schema/EdmFunction.cs | 55 + .../Schema/EdmFunctionImport.cs | 71 + .../Microsoft.OData.Edm/Schema/EdmInclude.cs | 50 + .../Schema/EdmIncludeAnnotations.cs | 64 + .../Microsoft.OData.Edm/Schema/EdmModel.cs | 190 + .../Schema/EdmModelBase.cs | 234 + .../Schema/EdmNamedElement.cs | 38 + .../Schema/EdmNavigationProperty.cs | 271 + .../Schema/EdmNavigationPropertyBinding.cs | 67 + .../Schema/EdmNavigationPropertyInfo.cs | 69 + .../Schema/EdmNavigationSource.cs | 264 + .../Schema/EdmOperation.cs | 173 + .../Schema/EdmOperationImport.cs | 65 + .../Schema/EdmOperationParameter.cs | 41 + .../Schema/EdmOperationReturn.cs | 39 + .../Schema/EdmOptionalParameter.cs | 43 + .../Schema/EdmPathExpression.cs | 80 + .../Schema/EdmPathTypeReference.cs | 24 + .../Schema/EdmPrimitiveTypeReference.cs | 24 + .../Microsoft.OData.Edm/Schema/EdmProperty.cs | 57 + .../Schema/EdmReference.cs | 81 + .../Schema/EdmReferentialConstraint.cs | 60 + .../Schema/EdmSingleton.cs | 67 + .../Schema/EdmSpatialTypeReference.cs | 67 + .../Schema/EdmStringTypeReference.cs | 75 + .../Schema/EdmStructuralProperty.cs | 58 + .../Schema/EdmStructuredType.cs | 192 + .../Schema/EdmTemporalTypeReference.cs | 46 + .../src/Microsoft.OData.Edm/Schema/EdmType.cs | 31 + .../Schema/EdmTypeDefinition.cs | 96 + .../Schema/EdmTypeDefinitionReference.cs | 149 + .../Schema/EdmTypeReference.cs | 55 + .../Schema/EdmUnknownEntitySet.cs | 69 + .../Schema/EdmUntypedStructuredType.cs | 90 + .../EdmUntypedStructuredTypeReference.cs | 25 + .../Schema/EdmUntypedTypeReference.cs | 25 + .../Schema/IEdmNavigationTargetMapping.cs | 24 + .../Microsoft.OData.Edm/Schema/IEdmRowType.cs | 15 + .../Schema/Interfaces/IEdmAction.cs | 15 + .../Schema/Interfaces/IEdmActionImport.cs | 19 + .../Interfaces/IEdmBinaryTypeReference.cs | 24 + .../Schema/Interfaces/IEdmCheckable.cs | 22 + .../Schema/Interfaces/IEdmCollectionType.cs | 19 + .../Interfaces/IEdmCollectionTypeReference.cs | 15 + .../Schema/Interfaces/IEdmComplexType.cs | 17 + .../Interfaces/IEdmComplexTypeReference.cs | 15 + .../Interfaces/IEdmContainedEntitySet.cs | 20 + .../Interfaces/IEdmDecimalTypeReference.cs | 24 + .../Schema/Interfaces/IEdmElement.cs | 15 + .../Schema/Interfaces/IEdmEntityContainer.cs | 43 + .../Interfaces/IEdmEntityContainerElement.cs | 57 + .../Interfaces/IEdmEntityReferenceType.cs | 19 + .../IEdmEntityReferenceTypeReference.cs | 15 + .../Schema/Interfaces/IEdmEntitySet.cs | 19 + .../Schema/Interfaces/IEdmEntitySetBase.cs | 15 + .../Schema/Interfaces/IEdmEntityType.cs | 27 + .../Interfaces/IEdmEntityTypeReference.cs | 15 + .../Schema/Interfaces/IEdmEnumMember.cs | 26 + .../Schema/Interfaces/IEdmEnumMemberValue.cs | 19 + .../Schema/Interfaces/IEdmEnumType.cs | 31 + .../Interfaces/IEdmEnumTypeReference.cs | 15 + .../Schema/Interfaces/IEdmExpression.cs | 157 + .../Schema/Interfaces/IEdmFullNamedElement.cs | 19 + .../Schema/Interfaces/IEdmFunction.cs | 19 + .../Schema/Interfaces/IEdmFunctionImport.cs | 24 + .../Schema/Interfaces/IEdmInclude.cs | 24 + .../Interfaces/IEdmIncludeAnnotations.cs | 29 + .../Schema/Interfaces/IEdmLocatable.cs | 19 + .../Schema/Interfaces/IEdmModel.cs | 103 + .../Schema/Interfaces/IEdmNamedElement.cs | 19 + .../Interfaces/IEdmNavigationProperty.cs | 76 + .../IEdmNavigationPropertyBinding.cs | 29 + .../Schema/Interfaces/IEdmNavigationSource.cs | 84 + .../Schema/Interfaces/IEdmOperation.cs | 49 + .../Schema/Interfaces/IEdmOperationImport.cs | 24 + .../Interfaces/IEdmOperationParameter.cs | 26 + .../Schema/Interfaces/IEdmOperationReturn.cs | 26 + .../Interfaces/IEdmOptionalParameter.cs | 19 + .../Schema/Interfaces/IEdmPathExpression.cs | 26 + .../Schema/Interfaces/IEdmPathType.cs | 45 + .../Interfaces/IEdmPathTypeReference.cs | 15 + .../Schema/Interfaces/IEdmPrimitiveType.cs | 200 + .../Interfaces/IEdmPrimitiveTypeReference.cs | 15 + .../Schema/Interfaces/IEdmProperty.cs | 52 + .../Schema/Interfaces/IEdmReference.cs | 32 + .../Interfaces/IEdmReferentialConstraint.cs | 52 + .../Schema/Interfaces/IEdmSchemaElement.cs | 62 + .../Schema/Interfaces/IEdmSchemaType.cs | 15 + .../Schema/Interfaces/IEdmSingleton.cs | 15 + .../Interfaces/IEdmSpatialTypeReference.cs | 19 + .../Interfaces/IEdmStringTypeReference.cs | 29 + .../Interfaces/IEdmStructuralProperty.cs | 19 + .../Schema/Interfaces/IEdmStructuredType.cs | 43 + .../Interfaces/IEdmStructuredTypeReference.cs | 15 + .../Interfaces/IEdmTemporalTypeReference.cs | 19 + .../Schema/Interfaces/IEdmType.cs | 75 + .../Schema/Interfaces/IEdmTypeDefinition.cs | 19 + .../Interfaces/IEdmTypeDefinitionReference.cs | 56 + .../Schema/Interfaces/IEdmTypeReference.cs | 24 + .../Schema/Interfaces/IEdmUnknownEntitySet.cs | 19 + .../Schema/Interfaces/IEdmUntypedType.cs | 15 + .../Interfaces/IEdmUntypedTypeReference.cs | 15 + .../Microsoft.OData.Edm/Schema/TimeOfDay.cs | 410 + .../ShippingAssemblyAttributes.cs | 19 + .../src/Microsoft.OData.Edm/TupleInternal.cs | 35 + .../Validation/DuplicateOperationValidator.cs | 204 + .../Validation/EdmError.cs | 59 + .../Validation/EdmErrorCode.cs | 1391 +++ .../Validation/EdmValidator.cs | 58 + .../Validation/ExpressionTypeChecker.cs | 633 ++ .../Validation/InterfaceValidator.cs | 1889 +++++ .../Validation/ObjectLocation.cs | 33 + .../Validation/ValidationContext.cs | 70 + .../Validation/ValidationExtensionMethods.cs | 51 + .../Validation/ValidationHelper.cs | 225 + .../Validation/ValidationRule.cs | 52 + .../Validation/ValidationRuleSet.cs | 222 + .../Validation/ValidationRules.cs | 2871 +++++++ .../VersioningDictionary.cs | 333 + .../src/Microsoft.OData.Edm/VersioningList.cs | 366 + .../src/Microsoft.OData.Edm/VersioningTree.cs | 241 + .../AlternateKeysVocabularies.xml | 18 + .../AlternateKeysVocabularyConstants.cs | 35 + .../AlternateKeysVocabularyModel.cs | 39 + .../Annotations/EdmDirectValueAnnotation.cs | 57 + .../EdmDirectValueAnnotationBinding.cs | 87 + .../EdmDirectValueAnnotationsManager.cs | 456 + .../Annotations/EdmPropertyValueBinding.cs | 47 + .../Vocabularies/Annotations/EdmTerm.cs | 137 + .../EdmTypedDirectValueAnnotationBinding.cs | 54 + .../Annotations/EdmVocabularyAnnotation.cs | 81 + .../Annotations/IEdmDirectValueAnnotation.cs | 24 + .../IEdmDirectValueAnnotationBinding.cs | 34 + .../IEdmDirectValueAnnotationsManager.cs | 54 + .../Annotations/IEdmPropertyValueBinding.cs | 24 + .../Vocabularies/Annotations/IEdmTerm.cs | 29 + .../Annotations/IEdmVocabularyAnnotatable.cs | 15 + .../Annotations/IEdmVocabularyAnnotation.cs | 34 + .../AuthorizationVocabularies.xml | 189 + .../AuthorizationVocabularyModel.cs | 19 + .../Vocabularies/CapabilitiesVocabularies.xml | 926 ++ .../CapabilitiesVocabularyConstants.cs | 29 + .../CapabilitiesVocabularyModel.cs | 24 + .../Vocabularies/CommunityVocabularies.xml | 13 + .../CommunityVocabularyConstants.cs | 17 + .../Vocabularies/CommunityVocabularyModel.cs | 29 + .../Vocabularies/CoreVocabularies.xml | 388 + .../Vocabularies/CoreVocabularyConstants.cs | 65 + .../Vocabularies/CoreVocabularyModel.cs | 118 + .../Vocabularies/EdmExpressionEvaluator.cs | 922 ++ .../Vocabularies/EdmToClrConverter.cs | 982 +++ .../Vocabularies/EdmToClrEvaluator.cs | 132 + .../Expressions/EdmApplyExpression.cs | 67 + .../Expressions/EdmCastExpression.cs | 55 + .../Expressions/EdmCollectionExpression.cs | 84 + .../Expressions/EdmEnumMemberExpression.cs | 47 + .../Expressions/EdmIfExpression.cs | 67 + .../Expressions/EdmIsTypeExpression.cs | 55 + .../Expressions/EdmLabeledExpression.cs | 55 + ...EdmLabeledExpressionReferenceExpression.cs | 68 + .../EdmNavigationPropertyPathExpression.cs | 51 + .../Expressions/EdmNullExpression.cs | 43 + .../Expressions/EdmPropertyConstructor.cs | 47 + .../Expressions/EdmPropertyPathExpression.cs | 51 + .../Expressions/EdmRecordExpression.cs | 84 + .../Expressions/IEdmApplyExpression.cs | 26 + .../IEdmBinaryConstantExpression.cs | 15 + .../IEdmBooleanConstantExpression.cs | 15 + .../Expressions/IEdmCastExpression.cs | 24 + .../Expressions/IEdmCollectionExpression.cs | 26 + .../Expressions/IEdmDateConstantExpression.cs | 15 + .../IEdmDateTimeOffsetConstantExpression.cs | 15 + .../IEdmDecimalConstantExpression.cs | 15 + .../IEdmDurationConstantExpression.cs | 15 + .../Expressions/IEdmEnumMemberExpression.cs | 21 + .../IEdmFloatingConstantExpression.cs | 15 + .../Expressions/IEdmGuidConstantExpression.cs | 15 + .../Expressions/IEdmIfExpression.cs | 29 + .../IEdmIntegerConstantExpression.cs | 15 + .../Expressions/IEdmIsTypeExpression.cs | 24 + .../Expressions/IEdmLabeledExpression.cs | 19 + ...EdmLabeledExpressionReferenceExpression.cs | 19 + .../Expressions/IEdmNullExpression.cs | 15 + .../Expressions/IEdmPropertyConstructor.cs | 24 + .../Expressions/IEdmRecordExpression.cs | 26 + .../IEdmStringConstantExpression.cs | 15 + .../IEdmTimeOfDayConstantExpression.cs | 15 + .../Vocabularies/TryCreateObjectInstance.cs | 54 + .../Vocabularies/ValidationVocabularies.xml | 122 + .../Vocabularies/ValidationVocabularyModel.cs | 33 + .../Vocabularies/Values/EdmBinaryConstant.cs | 61 + .../Vocabularies/Values/EdmBooleanConstant.cs | 60 + .../Vocabularies/Values/EdmCollectionValue.cs | 45 + .../Vocabularies/Values/EdmDateConstant.cs | 60 + .../Values/EdmDateTimeOffsetConstant.cs | 63 + .../Vocabularies/Values/EdmDecimalConstant.cs | 60 + .../Values/EdmDurationConstant.cs | 62 + .../Vocabularies/Values/EdmEnumValue.cs | 53 + .../Values/EdmFloatingConstant.cs | 60 + .../Vocabularies/Values/EdmGuidConstant.cs | 63 + .../Vocabularies/Values/EdmIntegerConstant.cs | 63 + .../Vocabularies/Values/EdmPropertyValue.cs | 77 + .../Vocabularies/Values/EdmStringConstant.cs | 61 + .../Vocabularies/Values/EdmStructuredValue.cs | 106 + .../Values/EdmTimeOfDayConstant.cs | 61 + .../Vocabularies/Values/EdmValue.cs | 46 + .../Vocabularies/Values/IEdmBinaryValue.cs | 19 + .../Vocabularies/Values/IEdmBooleanValue.cs | 19 + .../Values/IEdmCollectionValue.cs | 21 + .../Values/IEdmDateTimeOffsetValue.cs | 21 + .../Vocabularies/Values/IEdmDateValue.cs | 19 + .../Vocabularies/Values/IEdmDecimalValue.cs | 19 + .../Vocabularies/Values/IEdmDelayedValue.cs | 19 + .../Vocabularies/Values/IEdmDurationValue.cs | 21 + .../Vocabularies/Values/IEdmEnumValue.cs | 19 + .../Vocabularies/Values/IEdmFloatingValue.cs | 19 + .../Vocabularies/Values/IEdmGuidValue.cs | 21 + .../Vocabularies/Values/IEdmIntegerValue.cs | 21 + .../Vocabularies/Values/IEdmNullValue.cs | 15 + .../Vocabularies/Values/IEdmPrimitiveValue.cs | 15 + .../Vocabularies/Values/IEdmPropertyValue.cs | 19 + .../Vocabularies/Values/IEdmStringValue.cs | 19 + .../Values/IEdmStructuredValue.cs | 28 + .../Vocabularies/Values/IEdmTimeOfDayValue.cs | 19 + .../Vocabularies/Values/IEdmValue.cs | 110 + .../Vocabularies/VocabularyModelProvider.cs | 120 + .../src/Microsoft.Spatial/ActionOnDispose.cs | 41 + .../Microsoft.Spatial.NetFX35.csproj | 401 + .../Microsoft.Spatial.NetStandard.csproj | 185 + .../Build.NetStandard/project.json | 9 + .../Microsoft.Spatial.Net35.Release.nuspec | 26 + .../Microsoft.Spatial.Nightly.Release.nuspec | 29 + .../Microsoft.Spatial.Release.nuspec | 30 + .../src/Microsoft.Spatial/Build.props | 11 + .../src/Microsoft.Spatial/CompositeKey.cs | 114 + .../src/Microsoft.Spatial/CoordinateSystem.cs | 220 + .../DataServicesSpatialImplementation.cs | 86 + .../src/Microsoft.Spatial/DebugUtils.cs | 25 + .../src/Microsoft.Spatial/DrawBoth.cs | 307 + .../src/Microsoft.Spatial/ExtensionMethods.cs | 58 + .../Microsoft.Spatial/FormatterExtensions.cs | 53 + .../Microsoft.Spatial/ForwardingSegment.cs | 564 ++ .../src/Microsoft.Spatial/GeoJsonConstants.cs | 90 + .../src/Microsoft.Spatial/GeoJsonMember.cs | 44 + .../GeoJsonObjectFormatter.cs | 38 + .../GeoJsonObjectFormatterImplementation.cs | 96 + .../Microsoft.Spatial/GeoJsonObjectReader.cs | 560 ++ .../Microsoft.Spatial/GeoJsonObjectWriter.cs | 189 + .../Microsoft.Spatial/GeoJsonWriterBase.cs | 472 ++ .../src/Microsoft.Spatial/Geography.cs | 118 + .../GeographyBuilderImplementation.cs | 195 + .../Microsoft.Spatial/GeographyCollection.cs | 51 + .../GeographyCollectionImplementation.cs | 79 + .../src/Microsoft.Spatial/GeographyCurve.cs | 20 + .../src/Microsoft.Spatial/GeographyFactory.cs | 275 + .../Microsoft.Spatial/GeographyFactoryOfT.cs | 310 + .../Microsoft.Spatial/GeographyFullGlobe.cs | 45 + .../GeographyFullGlobeImplementation.cs | 55 + .../GeographyHelperMethods.cs | 42 + .../Microsoft.Spatial/GeographyLineString.cs | 51 + .../GeographyLineStringImplementation.cs | 68 + .../Microsoft.Spatial/GeographyMultiCurve.cs | 20 + .../GeographyMultiLineString.cs | 51 + .../GeographyMultiLineStringImplementation.cs | 88 + .../Microsoft.Spatial/GeographyMultiPoint.cs | 51 + .../GeographyMultiPointImplementation.cs | 88 + .../GeographyMultiPolygon.cs | 51 + .../GeographyMultiPolygonImplementation.cs | 87 + .../GeographyMultiSurface.cs | 20 + .../GeographyOperationsExtensions.cs | 57 + .../Microsoft.Spatial/GeographyPipeline.cs | 37 + .../src/Microsoft.Spatial/GeographyPoint.cs | 116 + .../GeographyPointImplementation.cs | 160 + .../src/Microsoft.Spatial/GeographyPolygon.cs | 51 + .../GeographyPolygonImplementation.cs | 80 + .../Microsoft.Spatial/GeographyPosition.cs | 159 + .../src/Microsoft.Spatial/GeographySurface.cs | 20 + .../src/Microsoft.Spatial/Geometry.cs | 100 + .../GeometryBuilderImplementation.cs | 193 + .../Microsoft.Spatial/GeometryCollection.cs | 51 + .../GeometryCollectionImplementation.cs | 79 + .../src/Microsoft.Spatial/GeometryCurve.cs | 20 + .../src/Microsoft.Spatial/GeometryFactory.cs | 279 + .../Microsoft.Spatial/GeometryFactoryOfT.cs | 310 + .../GeometryHelperMethods.cs | 42 + .../Microsoft.Spatial/GeometryLineString.cs | 51 + .../GeometryLineStringImplementation.cs | 78 + .../Microsoft.Spatial/GeometryMultiCurve.cs | 20 + .../GeometryMultiLineString.cs | 51 + .../GeometryMultiLineStringImplementation.cs | 88 + .../Microsoft.Spatial/GeometryMultiPoint.cs | 51 + .../GeometryMultiPointImplementation.cs | 88 + .../Microsoft.Spatial/GeometryMultiPolygon.cs | 51 + .../GeometryMultiPolygonImplementation.cs | 87 + .../Microsoft.Spatial/GeometryMultiSurface.cs | 22 + .../GeometryOperationsExtensions.cs | 57 + .../src/Microsoft.Spatial/GeometryPipeline.cs | 37 + .../src/Microsoft.Spatial/GeometryPoint.cs | 119 + .../GeometryPointImplementation.cs | 158 + .../src/Microsoft.Spatial/GeometryPolygon.cs | 50 + .../GeometryPolygonImplementation.cs | 80 + .../src/Microsoft.Spatial/GeometryPosition.cs | 158 + .../src/Microsoft.Spatial/GeometrySurface.cs | 22 + .../Microsoft.Spatial/GlobalSuppressions.cs | 57 + .../src/Microsoft.Spatial/GmlConstants.cs | 194 + .../src/Microsoft.Spatial/GmlFormatter.cs | 30 + .../GmlFormatterImplementation.cs | 56 + .../src/Microsoft.Spatial/GmlReader.cs | 834 ++ .../src/Microsoft.Spatial/GmlWriter.cs | 388 + .../src/Microsoft.Spatial/IGeoJsonWriter.cs | 52 + .../Microsoft.Spatial/IGeographyProvider.cs | 23 + .../Microsoft.Spatial/IGeometryProvider.cs | 23 + .../src/Microsoft.Spatial/IShapeProvider.cs | 13 + .../src/Microsoft.Spatial/ISpatial.cs | 20 + .../Microsoft.Spatial/InternalsVisibleTo.cs | 17 + .../src/Microsoft.Spatial/LexerToken.cs | 47 + ...icrosoft.Spatial.NetStandard.VS2017.csproj | 85 + .../Microsoft.Spatial/Microsoft.Spatial.cs | 184 + .../Microsoft.Spatial.csproj | 181 + .../Microsoft.Spatial/Microsoft.Spatial.tt | 22 + .../Microsoft.Spatial/Microsoft.Spatial.txt | 58 + .../src/Microsoft.Spatial/OrcasExtensions.cs | 27 + .../Parameterized.Microsoft.Spatial.cs | 378 + .../Parameterized.Microsoft.Spatial.tt | 19 + .../Microsoft.Spatial/ParseErrorException.cs | 50 + .../src/Microsoft.Spatial/Spatial.StyleCop | 231 + .../src/Microsoft.Spatial/SpatialBuilder.cs | 74 + .../src/Microsoft.Spatial/SpatialFactory.cs | 244 + .../src/Microsoft.Spatial/SpatialFormatter.cs | 101 + .../SpatialImplementation.cs | 83 + .../Microsoft.Spatial/SpatialOperations.cs | 68 + .../src/Microsoft.Spatial/SpatialPipeline.cs | 114 + .../src/Microsoft.Spatial/SpatialReader.cs | 108 + .../Microsoft.Spatial/SpatialTreeBuilder.cs | 273 + .../src/Microsoft.Spatial/SpatialType.cs | 75 + .../SpatialTypeExtensions.cs | 32 + .../src/Microsoft.Spatial/SpatialValidator.cs | 21 + .../SpatialValidatorImplementation.cs | 1391 +++ .../src/Microsoft.Spatial/TextLexerBase.cs | 174 + .../Microsoft.Spatial/TypeWashedPipeline.cs | 67 + .../TypeWashedToGeographyLatLongPipeline.cs | 106 + .../TypeWashedToGeographyLongLatPipeline.cs | 106 + .../TypeWashedToGeometryPipeline.cs | 106 + .../odata.net/src/Microsoft.Spatial/Util.cs | 79 + .../WellKnownTextConstants.cs | 106 + .../Microsoft.Spatial/WellKnownTextLexer.cs | 100 + .../WellKnownTextSqlFormatter.cs | 37 + ...WellKnownTextSqlFormatterImplementation.cs | 69 + .../WellKnownTextSqlReader.cs | 468 ++ .../WellKnownTextSqlWriter.cs | 417 + .../WellKnownTextTokenType.cs | 59 + .../Microsoft.Spatial/WrappedGeoJsonWriter.cs | 97 + .../src/Microsoft.Spatial/XmlConstants.cs | 19 + ApiAsAService/odata.net/src/PlatformHelper.cs | 1134 +++ ApiAsAService/odata.net/tools/Build.props | 28 + .../tools/CustomMSBuild/After.Common.targets | 188 + .../tools/CustomMSBuild/Before.Common.targets | 21 + .../odata.net/tools/CustomMSBuild/Build.props | 141 + .../tools/CustomMSBuild/Codesign.props | 91 + .../GetNugetPackageMetadata.proj | 12 + .../tools/CustomMSBuild/ODataRuleSets.ruleset | 599 ++ .../tools/CustomMSBuild/Portable.targets | 18 + .../CustomMSBuild/TargetFrameworkPath.props | 33 + .../tools/CustomMSBuild/Versioning.props | 52 + ApiAsAService/odata.net/tools/ExecutePerf.ps1 | 219 + .../FxCopRules/AtomMaterializerInvokerRule.cs | 181 + .../tools/FxCopRules/BaseDataWebRule.cs | 57 + .../tools/FxCopRules/CodeTypeReferenceRule.cs | 84 + .../tools/FxCopRules/DataWebRules.csproj | 55 + .../tools/FxCopRules/DataWebRules.ruleset | 24 + .../DoNotHandleProhibitedExceptionsRule.cs | 57 + .../EntityDescriptorPublicProperties.cs | 81 + .../tools/FxCopRules/HashSetCtorRule.cs | 73 + .../tools/FxCopRules/HttpWebRequestRule.cs | 116 + .../FxCopRules/IDSPEnumerateTypesRule.cs | 105 + .../tools/FxCopRules/MethodCallFinder.cs | 67 + .../tools/FxCopRules/MethodCallNotAllowed.cs | 230 + .../tools/FxCopRules/ProcessRequestUriRule.cs | 65 + .../odata.net/tools/FxCopRules/Rules.xml | 258 + .../tools/FxCopRules/SelfLinkRule.cs | 74 + ...tDireclyAccessPayloadMetadataProperties.cs | 111 + .../SystemSpatialOperationsPropertyRule.cs | 80 + .../SystemUriEscapeDataStringRule.cs | 65 + .../tools/FxCopRules/SystemUriToStringRule.cs | 69 + .../tools/FxCopRules/ThreadGetSetDataRule.cs | 72 + .../TypeOfDataServiceCollectionOfTRule.cs | 87 + .../WebDataServiceExceptionCtorRule.cs | 66 + .../odata.net/tools/KoKoMo/KoKoMo.csproj | 40 + ApiAsAService/odata.net/tools/KoKoMo/Model.cs | 566 ++ .../odata.net/tools/KoKoMo/ModelAction.cs | 489 ++ .../odata.net/tools/KoKoMo/ModelEngine.cs | 769 ++ .../tools/KoKoMo/ModelEngineOptions.cs | 229 + .../odata.net/tools/KoKoMo/ModelException.cs | 53 + .../odata.net/tools/KoKoMo/ModelExpression.cs | 36 + .../odata.net/tools/KoKoMo/ModelItem.cs | 538 ++ .../odata.net/tools/KoKoMo/ModelItems.cs | 672 ++ .../odata.net/tools/KoKoMo/ModelParameter.cs | 179 + .../odata.net/tools/KoKoMo/ModelRange.cs | 456 + .../tools/KoKoMo/ModelRequirement.cs | 171 + .../odata.net/tools/KoKoMo/ModelTrace.cs | 194 + .../odata.net/tools/KoKoMo/ModelValue.cs | 251 + .../odata.net/tools/KoKoMo/ModelVariable.cs | 183 + .../tools/KoKoMo/Properties/assemblyinfo.cs | 9 + .../odata.net/tools/KoKoMo/kokomo.snk | Bin 0 -> 596 bytes .../odata.net/tools/ModuleCore/Build.props | 15 + .../odata.net/tools/ModuleCore/src/Interop.cs | 281 + .../tools/ModuleCore/src/ModuleCore.csproj | 43 + .../ModuleCore/src/Properties/assemblyinfo.cs | 8 + .../tools/ModuleCore/src/TestAttribute.cs | 726 ++ .../tools/ModuleCore/src/TestCase.cs | 95 + .../tools/ModuleCore/src/TestException.cs | 96 + .../tools/ModuleCore/src/TestItem.cs | 680 ++ .../tools/ModuleCore/src/TestLoader.cs | 147 + .../odata.net/tools/ModuleCore/src/TestLog.cs | 531 ++ .../tools/ModuleCore/src/TestModule.cs | 244 + .../tools/ModuleCore/src/TestParser.cs | 213 + .../tools/ModuleCore/src/TestProperties.cs | 481 ++ .../tools/ModuleCore/src/TestSpec.cs | 289 + .../tools/ModuleCore/src/TestThread.cs | 262 + .../tools/ModuleCore/src/TestVariation.cs | 76 + .../odata.net/tools/ModuleCore/src/Util.cs | 108 + .../tools/ModuleCore/src/framework.snk | Bin 0 -> 160 bytes .../tools/ModuleCore/src/modulecore.snk | Bin 0 -> 596 bytes .../odata.net/tools/PerfAnalysis.ps1 | 186 + .../tools/PoliCheck/RunPoliCheck.ps1 | 34 + .../odata.net/tools/Scripts/TestCleanup.cmd | 3 + .../odata.net/tools/Scripts/TestStartup.cmd | 2 + .../odata.net/tools/Scripts/artcommon.js | 155 + .../odata.net/tools/Scripts/artdbclean.js | 101 + .../ClassGeneratorCommon.ttinclude | 69 + .../ResourceClassGenerator.ttinclude | 219 + .../StringsClassGenerator.ttinclude | 228 + .../35MSSharedLib1024.snk | Bin 0 -> 160 bytes .../tools/StrongNamePublicKeys/testkey.snk | Bin 0 -> 596 bytes .../odata.net/tools/perf/PerfRegression.ps1 | 78 + 2636 files changed, 434815 insertions(+) create mode 100644 ApiAsAService/ApiAsAService.sln create mode 100644 ApiAsAService/ApiAsAService/ApiAsAService.csproj create mode 100644 ApiAsAService/ApiAsAService/App.config create mode 100644 ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs create mode 100644 ApiAsAService/ApiAsAService/Constants.cs create mode 100644 ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs create mode 100644 ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs create mode 100644 ApiAsAService/ApiAsAService/CustomODataRoute.cs create mode 100644 ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs create mode 100644 ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs create mode 100644 ApiAsAService/ApiAsAService/DataSource/IDataSource.cs create mode 100644 ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs create mode 100644 ApiAsAService/ApiAsAService/DynamicModelHelper.cs create mode 100644 ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs create mode 100644 ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs create mode 100644 ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs create mode 100644 ApiAsAService/ApiAsAService/Program.cs create mode 100644 ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/ApiAsAService/packages.config create mode 100644 ApiAsAService/Diagrams.pptx create mode 100644 ApiAsAService/RESTier/.editorconfig create mode 100644 ApiAsAService/RESTier/Directory.Build.props create mode 100644 ApiAsAService/RESTier/License.txt create mode 100644 ApiAsAService/RESTier/NuGet.Config create mode 100644 ApiAsAService/RESTier/README.md create mode 100644 ApiAsAService/RESTier/RESTier.sln create mode 100644 ApiAsAService/RESTier/databases/Chinook.bak create mode 100644 ApiAsAService/RESTier/docs/.openpublishing.build.ps1 create mode 100644 ApiAsAService/RESTier/docs/.openpublishing.publish.config.json create mode 100644 ApiAsAService/RESTier/docs/CODEOWNERS create mode 100644 ApiAsAService/RESTier/docs/README.md create mode 100644 ApiAsAService/RESTier/docs/mkdocs.yml create mode 100644 ApiAsAService/RESTier/docs/msdocs/clients/dot-net-standard.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/clients/dot-net.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/clients/typescript.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/contribution-guidelines.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/docfx.json create mode 100644 ApiAsAService/RESTier/docs/msdocs/extending-restier/additional-operations.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/extending-restier/in-memory-provider.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/extending-restier/temporal-types.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/getting-started.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/index.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/license.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta1.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta2.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc2.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/release-notes/0-5-0-beta.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/server/filters.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/server/interceptors.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/server/method-authorization.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/server/model-building.md create mode 100644 ApiAsAService/RESTier/docs/msdocs/vs-highlight.css create mode 100644 ApiAsAService/RESTier/samples/api/Directory.Build.props create mode 100644 ApiAsAService/RESTier/samples/api/Microsoft.Restier.Samples.Apis.sln create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Directory.Build.props create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api.sln create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/App_Start/WebApiConfig.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Microsoft.Restier.Samples.Chinook.Api.csproj create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Album.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Artist.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/ChinookContext.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Customer.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Employee.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Genre.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Invoice.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/InvoiceLine.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/MediaType.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Playlist.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Track.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Debug.config create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Release.config create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.config create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/packages.config create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Microsoft.Restier.Samples.Chinook.Tests.Api.csproj create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/UnitTest1.cs create mode 100644 ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/packages.config create mode 100644 ApiAsAService/RESTier/samples/apps/angular/.gitignore create mode 100644 ApiAsAService/RESTier/samples/apps/blazor/.gitignore create mode 100644 ApiAsAService/RESTier/samples/apps/react/.gitignore create mode 100644 ApiAsAService/RESTier/samples/apps/uwp/.gitignore create mode 100644 ApiAsAService/RESTier/samples/apps/xamarin/.gitignore create mode 100644 ApiAsAService/RESTier/src/CodeAnalysisDictionary.xml create mode 100644 ApiAsAService/RESTier/src/Common.Stylecop create mode 100644 ApiAsAService/RESTier/src/CommonAssemblyInfo.cs create mode 100644 ApiAsAService/RESTier/src/Directory.Build.props create mode 100644 ApiAsAService/RESTier/src/GlobalSuppressions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchChangeSetRequestItem.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchHandler.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierChangeSetProperty.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/Extensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpRequestMessageExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/ServiceCollectionExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/RestierExceptionFilterAttribute.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/ValidationResultDto.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DefaultRestierDeserializerProvider.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DeserializationHelpers.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/RestierEnumDeserializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/DefaultRestierSerializerProvider.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierCollectionSerializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierEnumSerializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierPrimitiveSerializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierRawSerializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSerializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSetSerializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/EdmHelpers.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/OperationAttribute.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/PropertyAttributes.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ResourceAttribute.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelBuilder.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierOperationModelBuilder.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Operation/OperationExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.Designer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.resx create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryBuilder.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutorOptions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierPayloadValueConverter.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseCollectionResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseSingleResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ComplexResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/EnumResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/NonResourceCollectionResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/PrimitiveResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/RawResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ResourceSetResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/app.config create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNetCore/Microsoft.Restier.AspNetCore.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiBase.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiConfiguration.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemFilter.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemValidator.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedMethodNameFactory.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedQueryExpressionProcessor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptionProvider.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataPropertyDescriptorWrapper.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataTypeAttribute.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataSourceStub.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierEntitySetOperation.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierOperationMethod.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierPipelineState.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/StatusCodeException.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ChainedService.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/EnumerableExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/InvocationContextExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/TypeExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Globalization/Error.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/Ensure.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelperMethods.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelpers.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/InvocationContext.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/EdmHelpers.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelBuilder.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelMapper.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/ModelContext.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationAuthorizer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationFilter.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/OperationContext.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.resx create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/PropertyBag.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionAuthorizer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionExpander.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionProcessor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionSourcer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/ParameterModelReference.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/PropertyModelReference.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryContext.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryExpressionContext.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryModelReference.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryRequest.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/QueryableSource.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSet.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItemValidationResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetInitializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemAuthorizer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemFilter.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemValidator.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ISubmitExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitContext.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitResult.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/EntityFrameworkApi.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Extensions/ServiceCollectionExtensions.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.Designer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.resx create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Spatial/GeographyConverter.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/ChangeSetInitializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/App.config create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Microsoft.Restier.Providers.EntityFramework7.csproj_old create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.Designer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.resx create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Submit/ChangeSetInitializer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/App_Start/WebApiConfig.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Controllers/NorthwindApi.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Category.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Customer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/CustomerDemographic.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Employee.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.tt create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Designer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx.diagram create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.tt create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order_Detail.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Product.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Region.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Shipper.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Supplier.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Territory.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Microsoft.Restier.Samples.Northwind.AspNet.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Debug.config create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Release.config create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiMetadata.txt create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiSurface.txt create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiMetadata.txt create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiSurface.txt create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ExceptionHandlerTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ActionTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/AuthorizationTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ExpandTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/FunctionTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/MetadataTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/QueryTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/UpdateTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Microsoft.Restier.Tests.AspNet.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelBuilderTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelExtenderTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ODataControllerFallbackTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RegressionTests/Issue541_CountPlusParametersFails.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierControllerTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierQueryBuilderTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/DisallowEverythingAuthorizer.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/UnauthorizedLibraryApi.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/app.config create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNetCore/Microsoft.Restier.Tests.AspNetCore.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiBaseTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiConfigurationTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ConventionBasedMethodNameFactoryTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/DataSourceStubsTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/InvocationContextTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Microsoft.Restier.Tests.Core.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Model/DefaultModelHandlerTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/PropertyBagTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ServiceConfigurationTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/App.config create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/ChangeSetPreparerTests.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/Microsoft.Restier.Tests.EntityFramework.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Microsoft.Restier.Tests.Shared.csproj create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/RestierTestBase.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Address.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Book.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Employee.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryApi.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryCard.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryContext.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Publisher.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Universe.cs create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Store/StoreApi.cs create mode 100644 ApiAsAService/RESTier/src/Strict.ruleset create mode 100644 ApiAsAService/RESTierAsAService.sln create mode 100644 ApiAsAService/ReadMe.md create mode 100644 ApiAsAService/WebAPI/License.txt create mode 100644 ApiAsAService/WebAPI/README.md create mode 100644 ApiAsAService/WebAPI/Settings.StyleCop create mode 100644 ApiAsAService/WebAPI/WebApiOData.Performance.msbuild create mode 100644 ApiAsAService/WebAPI/build.cmd create mode 100644 ApiAsAService/WebAPI/build.ps1 create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/AspNetCoreODataSample.Web.csproj create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/MoviesController.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/PeopleController.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/EdmModelBuilder.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Genre.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Level.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Movie.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieContext.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieStar.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Person.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Program.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Properties/launchSettings.json create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Startup.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.Development.json create mode 100644 ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.json create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/App_Start/WebApiConfig.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/ApplicationInsights.config create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/AspNetODataSample.Web.csproj create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Controllers/TodoItemsController.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/DataSource.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/EdmModelBuilder.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItem.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItemContext.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Debug.config create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Release.config create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.config create mode 100644 ApiAsAService/WebAPI/samples/AspNetODataSample.Web/packages.config create mode 100644 ApiAsAService/WebAPI/sln/.nuget/NuGet.Config create mode 100644 ApiAsAService/WebAPI/sln/.nuget/NuGet.exe create mode 100644 ApiAsAService/WebAPI/sln/.nuget/packages.config create mode 100644 ApiAsAService/WebAPI/sln/WebApiOData.AspNet.sln create mode 100644 ApiAsAService/WebAPI/sln/WebApiOData.AspNetCore.sln create mode 100644 ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNet.sln create mode 100644 ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNetCore.sln create mode 100644 ApiAsAService/WebAPI/sln/WebApiOData.Performance.Official.sln create mode 100644 ApiAsAService/WebAPI/sln/WebApiOData.Performance.sln create mode 100644 ApiAsAService/WebAPI/src/CodeAnalysisDictionary.xml create mode 100644 ApiAsAService/WebAPI/src/CommonAssemblyInfo.cs create mode 100644 ApiAsAService/WebAPI/src/GlobalSuppressions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchContent.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/AutoExpandAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindableOperationFinder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingParameterConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathConfigurationOfTStructuralType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesNavigationType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyConstants.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyExtensionMethods.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfigurationOfTComplexType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainedAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainmentPathBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractEntityTypeDiscoveryConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractTypeDiscoveryConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AssociationSetDiscoveryConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ActionOnDeleteAttributeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ColumnAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ConcurrencyCheckAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEnumTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataMemberAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MaxLengthAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MediaTypeAttributeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NonFilterableAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotCountableAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotExpandableAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotFilterableAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotNavigableAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotSortableAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/RequiredAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/UnsortableAttributeEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ConventionsHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ForeignKeyDiscoveryConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/FunctionLinkGenerationConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConventionOfTPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmTypeConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/INavigationSourceConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IOperationConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/NavigationLinksGenerationConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/SelfLinksGenerationConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DecimalPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DynamicPropertyDictionaryAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeConfigurationExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityCollectionConfigurationOfTEntityType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfigurationOfTEntityType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumMemberConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfigurationOfTEnumType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/FunctionConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/IEdmTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LengthPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LowerCamelCaser.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/MediaTypeAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NameResolverOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationLinkBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceAndAnnotations.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfigurationOfTEntityType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceLinkBuilderAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceUrlAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NonBindingParameterConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NullableEnumTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataModelBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationKind.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationLinkBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationTitleAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ParameterConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrecisionPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfigurationExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitiveTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyPairSelectorVisitor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/QueryConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ReturnedEntitySetAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SelfLinkBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfigurationOfTEntityType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrEnumMemberAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrTypeAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CollectionExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.Designer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.resx create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/Error.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/ListWrapperCollection.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/PropertyHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpersExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CompiledPropertyAccessor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ConcurrencyPropertiesAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ContentIdHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CustomAggregateMethodAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DefaultContainerBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Delta.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DeltaOfTStructuralType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ETagMessageHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmChangedObjectCollection.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexCollectionObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaCollectionType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaComplexObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedEntityObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedLink.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityKind.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaLink.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityCollectionObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObjectCollection.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmModelExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmStructuredObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmTypeExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EnableQueryAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelperMethods.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FastPropertyAccessor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ClrTypeCache.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/DefaultODataETagHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/CollectionDeserializationHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DefaultODataDeserializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/EnumDeserializationHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataActionPayloadDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataCollectionDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEdmTypeDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkBase.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEnumDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataItemBase.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataNestedResourceInfoWrapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataPrimitiveDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataReaderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetDeserializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetWrapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceWrapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETag.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETagOfTEntity.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmObjectHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmPrimitiveHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmTypeReferenceEqualityComparer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/IETagHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataBinaryValueMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataCountMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataEnumValueMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMediaTypes.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMessageWrapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMetadataLevel.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataModelBinderConverter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataOutputFormatterHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataPrimitiveValueMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataRawValueMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataValueExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/QueryStringMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/DefaultODataSerializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/EntitySelfLinks.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataDeltaFeedSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEdmTypeSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinkSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinksSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEnumSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataErrorSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataMetadataSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPayloadKindHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPrimitiveSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataRawValueSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSetSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProviderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataServiceDocumentSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/SelectExpandNode.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmComplexObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmEntityObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmStructuredObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FunctionImportComparer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/GetNextPageHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IDelta.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmChangedObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmComplexObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedEntityObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedLink.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLink.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLinkBase.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEntityObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEnumObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmStructuredObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IPerRouteContainer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionDescriptor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionMap.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiAssembliesResolver.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiControllerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiHeaders.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiRequestMessage.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiUrlHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/MetadataController.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.shproj create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfigurationAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NonValidatingParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NullEdmComplexObject.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataActionParameters.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataController.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataNullValueMessageHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContextExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerConverter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerUtilities.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUntypedActionParameters.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUriFunctions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResult.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResultOfT.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PerRouteContainerBase.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PropertyAccessor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedArithmeticOperators.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedFunctions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedLogicalOperators.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedQueryOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultQuerySettings.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultSkipTokenHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandConfiguration.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationPropertyContainer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrCanonicalFunctions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrSafeFunctions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapperConverter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ExpressionBinderBase.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/IdentityPropertyMapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/Linq2ObjectsComparisonMethods.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/LinqParameterContainer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ModelContainer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/NamedPropertyExpression.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.tt create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperOfT.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/UriFunctionsBinder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOptionHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ICountOptionCollection.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/IPropertyMapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ISelectExpandWrapper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ITruncatedCollection.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ModelBoundQuerySettings.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NonFilterableAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotCountableAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotExpandableAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotFilterableAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotNavigableAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotSortableAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptionsOfTEntity.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQuerySettings.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataRawQueryOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataValidationSettings.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByCountNode.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByItNode.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByNode.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByOpenPropertyNode.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByPropertyNode.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/PageAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ParameterAliasNodeTranslator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/QueryOptionSetting.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TopQueryOption.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TruncatedCollectionOfT.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/UnsortableAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/CountQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/FilterQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/ODataQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByModelLimitationsValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SelectExpandQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipTokenQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/TopQueryValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictionsAnnotation.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestMethod.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestPreferenceHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContextOfTStructuredType.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceSetContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Results/ResultHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionMapExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/AttributeRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/DynamicPropertyRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntityRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntitySetRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/FunctionRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/MetadataRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationSourceRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/PropertyRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RefRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RoutingConventionHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SelectControllerResult.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SingletonRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/UnmappedRequestRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathValidator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathTemplateHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/KeyValueParser.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataOptionalParameter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterValue.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPath.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathRouteConstraint.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentTranslator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteConstants.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoutePrefixAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataSegmentKinds.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataVersionConstraint.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/DynamicSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/EntitySetSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/KeySegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertyLinkSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertySegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateOfT.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateTranslator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationImportSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PathTemplateSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PropertySegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/SingletonSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/TypeSegmentTemplate.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/UnresolvedPathSegment.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TimeZoneInfoHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypedDelta.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/UnqualifiedCallAndEnumPrefixFreeResolver.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/WebApiAssembliesResolver.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionDescriptor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionMap.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiAssembliesResolver.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiControllerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestHeaders.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestMessage.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiUrlHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetRequestItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetResponseItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/DefaultODataBatchHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/LazyStreamContent.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchContent.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchReaderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchRequestItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchResponseItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataHttpContentExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationRequestItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationResponseItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/UnbufferedODataBatchHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Builder/ODataConventionModelBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ETagMessageHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/EnableQueryAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpActionDescriptorExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpErrorExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageProperties.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/UrlHelperExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataCountMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatters.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataModelBinderProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataRawValueMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/QueryStringMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataErrorSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/FromODataUriAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GetNextPageHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/HttpRequestScope.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Nightly.Release.nuspec create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Release.nuspec create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/NonValidatingParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataController.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataFormattingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataMessageWrapperHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataNullValueMessageHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataQueryParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataRoutingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestActionValueBinder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestContentNegotiator.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestParameterBinding.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRouteContainer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptionsOfTEntity.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/QueryFilterProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceSetContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/CreatedODataResult.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/ResultHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/UpdatedODataResult.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ActionRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/AttributeRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntityRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntitySetRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/FunctionRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/IODataRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/MetadataRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ODataRoutingConventions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/PropertyRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/RefRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/SingletonRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataActionSelector.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathRouteConstraint.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataRoute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataValueProviderFactory.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataVersionConstraint.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Settings.StyleCop create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionDescriptor.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionMap.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiAssembliesResolver.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiControllerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestHeaders.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestMessage.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiUrlHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetRequestItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetResponseItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/DefaultODataBatchHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/HttpRequestExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchContent.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchMiddleware.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchPathMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchReaderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchRequestItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchResponseItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationRequestItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationResponseItem.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/UnbufferedODataBatchHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Builder/ODataConventionModelBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ETagMessageHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/EnableQueryAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ActionDescriptorExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpContextExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpResponseExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataApplicationBuilderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataRouteBuilderExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataServiceCollectionExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/SerializableErrorExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/UrlHelperExtensions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/IMediaTypeMappingCollection.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/MediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataCountMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatterFactory.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterFactory.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataRawValueMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/QueryStringMediaTypeMapping.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataErrorSerializer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/FromODataUriAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/GetNextPageHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/HttpRequestScope.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataBatchFeature.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataCoreBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataFeature.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Nightly.Release.nuspec create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Release.nuspec create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/NonValidatingParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataBatchFeature.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataController.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFeature.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFormattingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataMessageWrapperHelper.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataNullValueMessageHandler.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataQueryParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataRoutingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataServiceBuilder.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataValueProviderFactory.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/PerRouteContainer.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptionsOfTEntity.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/QueryFilterProvider.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceSetContext.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/CreatedODataResult.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/UpdatedODataResult.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ActionRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/AttributeRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntityRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/FunctionRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/IODataRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/MetadataRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ODataRoutingConventions.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/RefRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/SingletonRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataActionSelector.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathParameterBindingAttribute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathRouteConstraint.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataRoute.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataVersionConstraint.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResult.cs create mode 100644 ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResultOfT.cs create mode 100644 ApiAsAService/WebAPI/src/Settings.StyleCop create mode 100644 ApiAsAService/WebAPI/src/Strict.ruleset create mode 100644 ApiAsAService/WebAPI/tools/35MSSharedLib1024.snk create mode 100644 ApiAsAService/WebAPI/tools/GetNugetPackageMetadata.proj create mode 100644 ApiAsAService/WebAPI/tools/MeasureCodeSharing.ps1 create mode 100644 ApiAsAService/WebAPI/tools/PoliCheck/RunPoliCheck.ps1 create mode 100644 ApiAsAService/WebAPI/tools/SkipStrongNames.xml create mode 100644 ApiAsAService/WebAPI/tools/WebStack.StyleCop.targets create mode 100644 ApiAsAService/WebAPI/tools/WebStack.settings.targets create mode 100644 ApiAsAService/WebAPI/tools/WebStack.targets create mode 100644 ApiAsAService/WebAPI/tools/WebStack.tasks.targets create mode 100644 ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets create mode 100644 ApiAsAService/WebAPI/tools/WebStack.xunit.targets create mode 100644 ApiAsAService/WebAPI/tools/perf/PerfRegression.ps1 create mode 100644 ApiAsAService/WebAPIAsAService.sln create mode 100644 ApiAsAService/odata.net/.markdownlint.json create mode 100644 ApiAsAService/odata.net/Build.props create mode 100644 ApiAsAService/odata.net/LICENSE.txt create mode 100644 ApiAsAService/odata.net/OData.sln.lnk create mode 100644 ApiAsAService/odata.net/PerformanceBuild.ps1 create mode 100644 ApiAsAService/odata.net/README.md create mode 100644 ApiAsAService/odata.net/build.cmd create mode 100644 ApiAsAService/odata.net/build.ps1 create mode 100644 ApiAsAService/odata.net/build.root create mode 100644 ApiAsAService/odata.net/images/PoliCheck/odata.net-master/exTermEdge_v58.xlsx create mode 100644 ApiAsAService/odata.net/images/ndependlogo.png create mode 100644 ApiAsAService/odata.net/sln/.nuget/NuGet.Config create mode 100644 ApiAsAService/odata.net/sln/.nuget/NuGet.exe create mode 100644 ApiAsAService/odata.net/sln/.nuget/NuGet.targets create mode 100644 ApiAsAService/odata.net/sln/.nuget/packages.config create mode 100644 ApiAsAService/odata.net/sln/OData.CodeGen.sln create mode 100644 ApiAsAService/odata.net/sln/OData.Net35.sln create mode 100644 ApiAsAService/odata.net/sln/OData.Net45.sln create mode 100644 ApiAsAService/odata.net/sln/OData.NetStandard.sln create mode 100644 ApiAsAService/odata.net/sln/OData.Tests.E2E.NetCore.VS2017.sln create mode 100644 ApiAsAService/odata.net/sln/OData.Tests.E2E.sln create mode 100644 ApiAsAService/odata.net/sln/OData.Tests.NetStandard.VS2017.sln create mode 100644 ApiAsAService/odata.net/sln/OData.Tests.Performance.sln create mode 100644 ApiAsAService/odata.net/sln/OData.Tests.WindowsApps.sln create mode 100644 ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.cs create mode 100644 ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.vb create mode 100644 ApiAsAService/odata.net/src/AssemblyInfo/AssemblyKeys.cs create mode 100644 ApiAsAService/odata.net/src/AssemblyInfo/AssemblyMetadataAttribute.cs create mode 100644 ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.cs create mode 100644 ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.vb create mode 100644 ApiAsAService/odata.net/src/Build.props create mode 100644 ApiAsAService/odata.net/src/CodeGen/GlobalSuppressions.cs create mode 100644 ApiAsAService/odata.net/src/CodeGen/InternalsVisibleTo.cs create mode 100644 ApiAsAService/odata.net/src/CodeGen/Microsoft.Data.Web.Design.T4.csproj create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.cs create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.tt create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.ttinclude create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.vstemplate create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ItemTemplates/ODataT4Template.CSharp.zip create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ItemTemplates/ODataT4Template.VisualBasic.zip create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ODataT4ItemTemplate.csproj create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ap-pl.txt create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/odata_logo.png create mode 100644 ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/source.extension.vsixmanifest create mode 100644 ApiAsAService/odata.net/src/CodeGen/__TemplateIcon.ico create mode 100644 ApiAsAService/odata.net/src/Common.Stylecop create mode 100644 ApiAsAService/odata.net/src/CustomDictionary.xml create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ALinqExpressionVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/AddNewEndingTokenVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceExpressionVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceQueryProvider.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/Evaluator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpandOnlyPathToStringVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionNormalizer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/FilterQueryOptionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputReferenceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NavigationPropertySingletonExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NewTreeBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/OrderByQueryOptionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ParameterReplacerVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionAnalyzer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionQueryOptionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionRewriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryComponents.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryOptionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryableResourceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ReflectionUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/RemoveWildcardVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpressionType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceSetExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathToStringVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SingletonResourceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SkipQueryOptionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TakeQueryOptionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TypeSystem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ActionDescriptor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/AnnotationHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/InstanceAnnotationDictWeakKeyComparer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ArraySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomMaterializerLog.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntitySetAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntityTypeAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/HasStreamAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/IgnoreClientPropertyAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/KeyAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/NamedStreamAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/OriginalNameAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseAsyncResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseSaveResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/BatchSaveResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingEntityInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingGraph.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingObserver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceCollectionOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceSaveChangesEventArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityChangedParams.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityCollectionChangedParams.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/LoadCompletedEventArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/BodyOperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/Microsoft.OData.Client.NetStandard.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/project.json create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Nightly.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.txt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.props create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/BuildingRequestEventArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ChangesetResponse.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientConvert.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmCollectionValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmStructuredValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Common.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/CommonUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentTypeUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ConventionalODataEntityMetadataBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuery.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQueryOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuerySingleOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientConfigurations.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestMessageArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestPipelineConfiguration.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientResponsePipelineConfiguration.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuery.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryContinuation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuerySingleOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequest.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponse.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponsePreference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceSaveStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamLink.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamResponse.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceTransportInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceUrlKeyDelimiter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceWebException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Descriptor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cd create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/Materialization.cd create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DictionaryExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/DynamicProxyMethodGenerator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityDescriptor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityParameterSendOption.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityStates.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTracker.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTrackerBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Error.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/FunctionDescriptor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/GetReadStreamResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/GlobalSuppressions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/HeaderCollection.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/HttpStack.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/InternalODataRequestMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/InvokeResponse.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkDescriptor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/LoadPropertyResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/CollectionValueMaterializationPolicy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntityTrackingAdapter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntryValueMaterializationPolicy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EnumValueMaterializationPolicy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/FeedAndEntryMaterializerAdapter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/HttpWebResponseMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/IODataMaterializerContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/InstanceAnnotationMaterializationPolicy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializationPolicy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerEntry.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerFeed.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerNavigationLink.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataCollectionMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializerInvoker.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntriesEntityMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataItemExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLinksMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLoadNavigationPropertyMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializerContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMessageReaderMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataPropertyMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderEntityMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataValueMaterializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitivePropertyConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitiveValueMaterializationPolicy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/StructuralValueMaterializationPolicy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializeFromAtom.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializedEntityArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MediaEntryAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MemberAssignmentAnalysis.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MergeOption.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageReaderSettingsArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageWriterSettingsArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientPropertyAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeCache.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmComplexTypeWithDelayLoadedProperties.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntitySetFacade.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntityTypeWithDelayLoadedProperties.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEnumTypeWithDelayLoadedMembers.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionImportFacade.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionParameterFacade.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmNavigationPropertyFacade.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Common.txt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Desktop.txt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.NetStandard.VS2017.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/MimeTypePropertyAttribute.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataAnnotatableExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageReadingHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageWritingHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataProtocolVersion.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataRequestMessageWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationDescriptor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationResponse.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPath.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlan.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlanCompiler.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponse.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponseOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingEntryArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingFeedArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingNestedResourceInfoArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingWritingEntityEventArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ReceivingResponseEventArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ReferenceEqualityComparer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/RequestInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ResponseInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveChangesOptions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequest2EventArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequestEventArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataServiceClientRequestMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataStringEscapeBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/HttpWebRequestMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataPropertyConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataWriterWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveParserToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveXmlConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/Serializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/ShippingAssemblyAttributes.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/StreamDescriptor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/TypeResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/UriEntityOperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/UriOperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/UriResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/UriUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Util.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Utility.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/WeakDictionary.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/WebUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataItemWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataNestedResourceInfoWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceSetWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataWriterHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntityReferenceLinkArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntryArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingNestedResourceInfoArgs.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Client/XmlConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilterPattern.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/AsyncBufferedStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/BindingPathHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferedReadStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferingReadStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/BufferUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/ICharArrayPool.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/ShippingAssemblyAttributes.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/project.json create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Net35.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Nightly.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.props create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/CollectionWithoutExpectedTypeValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ContainerBuilderExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/DerivedTypeValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/DuplicatePropertyNameChecker.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/EdmExtensionMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ErrorUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/EdmValueUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/IODataResourceMetadataContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/KeySerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/LiteralFormatter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/NoOpResourceMetadataBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalResourceMetadataBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalUriBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMetadataContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMissingOperationGenerator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataUriBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ExceptionUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/GeographyTypeConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/GeometryTypeConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/GlobalSuppressions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueLexer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerProvider.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IDuplicatePropertyNameChecker.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataOutputInStreamErrorListener.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataPayloadUriConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataReaderWriterListener.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessageAsync.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResourceTypeContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessageAsync.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamListener.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamReferenceInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IPrimitiveTypeConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IReaderValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/IWriterValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/InstanceAnnotationWriteTracker.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodes.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodesCommon.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonReaderFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonWriterFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/GeoJsonWriterAdapter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReaderFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriterFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IODataJsonOperationsDeserializerContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonLightInstanceAnnotationWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonNodeType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonSharedUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonValueUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriterExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonReaderCoreUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonTextWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonWriterUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataStringEscapeOption.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/TextWriterWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightReaderResourceState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightWriterResourceState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataLevel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataTypeNameOracle.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightMetadataLevel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightODataAnnotationWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightTypeNameOracle.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataLevel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataTypeNameOracle.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataLevel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataTypeNameOracle.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchAtomicGroupCache.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchBodyContentReaderStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReaderStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionSerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParseResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkSerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightErrorDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightInputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightOutputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPayloadKindDetectionDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedPropertyInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedResourceInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedStreamInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceSerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightSerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentSerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValidationUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValueSerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriterUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ReorderingJsonReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MediaTypeUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MessageStreamWrapper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/BufferingXmlReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/CachedPrimitiveKeepInContentAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeReaderResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeWriterResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtilsCommon.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataAtomErrorDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataReaderUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataWriterUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/XmlReaderExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.NetStandard.VS2017.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.txt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MimeConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/DependsOnIdsTracker.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchInputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchOutputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReaderStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriterUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/NonDisposingStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAnnotatable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousResponseMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationHeaders.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationRequestMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationResponseMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriOptions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamBuffer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamScanResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCore.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCoreAsync.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStart.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStartSerializationInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriterCore.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContentTypeException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUriBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlLevel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedEntry.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedLink.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLink.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLinkBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReaderState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSetSerializationInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaSerializationInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeserializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEdmPropertyAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLink.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLinks.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntitySetInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataError.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorDetail.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunctionImportInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInnerError.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInstanceAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataJsonDateTimeFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataLibraryCompatibility.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageQuotas.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataInputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataOutputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfoSerializationInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNullValueBehaviorKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataObjectModelExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOperation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOutputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCore.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriterCore.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionResult.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadValueConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPreferenceHeader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertySerializationInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawInputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawOutputContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueFormat.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReadStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCore.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCoreAsync.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRequestMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResource.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSerializationInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSetBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceTypeContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResponseMessage.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSerializer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocument.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocumentElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSimplifiedOptions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSingletonInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamPropertyInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTextStreamReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTypeAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtilsInternal.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersion.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersionCache.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriteStream.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriterCore.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/PrimitiveConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyAndAnnotationCollector.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCache.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCacheHandler.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyMetadataTypeInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertySerializationInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyValueTypeInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/RawValueWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidationUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ReferenceEqualityComparer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ResourceSetWithoutExpectedTypeValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/SelectedPropertiesNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceLifetime.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ServicePrototype.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceProviderExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ShippingAssemblyAttributes.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/SimpleLazy.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/StreamExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/TaskUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeNameOracle.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UnknownEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ApplyClauseToStringBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/NodeToStringBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUri.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriConversionUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUrlKeyDelimiter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/SelectExpandClauseToStringBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTokenBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTransformationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregationMethod.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyClause.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyTransformationToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ComputeTransformationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ExpandTransformationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/FilterTransformationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByPropertyNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByTransformationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNodeKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinaryOperatorBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinderBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ComputeBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/DottedIdentifierBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EnumBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ExpandTreeNormalizer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FilterBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InnerPathTokenBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/KeyBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LambdaBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LiteralBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/OrderByBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ParameterAliasBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/RangeVariableBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SearchBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandClauseFinisher.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandPathBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSemanticBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSyntacticUnifier.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectPathSegmentTokenBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectTreeNormalizer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/UnaryOperatorBinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/BuiltInUriFunctions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriFunctions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriLiteralPrefixes.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExceptionUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerLiteralExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignature.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignatureWithReturnType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/InternalErrorCodes.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/KeyPropertyValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/LiteralUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/NamedValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUnrecognizedPathException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserConfiguration.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserSettings.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/OrderByDirection.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ParseDynamicPathSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/CustomUriLiteralParsers.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/DefaultUriLiteralParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ExpandDepthAndCountValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionOverloadResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionParameterParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IFunctionCallParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IUriLiteralParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IdentifierTokenizer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/KeyFinder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/LiteralParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/NodeFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathParserModelUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathReverser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SearchParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentArgumentParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentKeyHandler.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandOptionParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandSyntacticParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandTermParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriLiteralParsingException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriParserHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPathParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPrimitiveTypeParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriTemplateParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryNodeUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryOptionUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ReadOnlyEnumerableForUriParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/ODataUriResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/StringAsEnumResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/UnqualifiedODataUriResolver.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SearchLexer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AggregatedCollectionPropertyNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AllNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnnotationSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnyNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchReferenceSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BinaryOperatorNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionComplexNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionConstantNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionFunctionCallNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNavigationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionOpenPropertyAccessNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionPropertyAccessNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceCastNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceFunctionCallNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeClause.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConstantNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConvertNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountVirtualPropertyNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DetermineNavigationSourceTranslator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DynamicPathSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EachSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntityIdSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntitySetSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedNavigationSelectItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedReferenceSelectItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterClause.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/InNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeyLookupNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeySegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LambdaNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LevelsClause.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/MetadataSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamedFunctionParameterNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamespaceQualifiedWildcardSelectItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertyLinkSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertySegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariableReferenceNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPath.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataSelectPath.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataUnresolvedFunctionParameterAlias.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationImportSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegmentParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OrderByClause.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasValueAccessor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathSelectItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathTemplateSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PropertySegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/QueryNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariableKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ReferenceSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariableReferenceNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchClause.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchTermNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClause.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClauseExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleComplexNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleEntityNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleNavigationNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceCastNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceFunctionCallNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueCastNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueFunctionCallNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueOpenPropertyAccessNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValuePropertyAccessNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingletonSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/TypeSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UnaryOperatorNode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UriTemplateExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ValueSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/WildcardSelectItem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AllToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AnyToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/BinaryOperatorToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeExpressionToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/CustomQueryOptionToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/DottedIdentifierToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/EndPathToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandTermToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionCallToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterAliasToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InnerPathToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LambdaToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LiteralToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/NonSystemToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/OrderByToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathSegmentToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/RangeVariableToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectExpandTermToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectTermToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StarToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StringLiteralToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SystemToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/UnaryOperatorToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/BinaryOperatorKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/ExpressionTokenKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/RequestTargetKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/UnaryOperatorKind.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypeFacetsPromotionRules.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypePromotionUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriFunctionsHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriQueryConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IPathSegmentTokenVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IsCollectionTranslator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentHandler.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToContextUrlPathTranslator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToResourcePathTranslator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToStringTranslator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenEqualityComparer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTranslator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemHandler.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemTranslator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectPropertyVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SplitEndingSegmentOfTypeHandler.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/UriUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Utils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationKinds.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataBinaryStreamValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataCollectionValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataEnumValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataNullValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataPrimitiveValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataResourceValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataStreamReferenceValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataUntypedValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValueUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidationUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.Net35/Microsoft.OData.Edm.NetFX35.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/Microsoft.OData.Edm.NetStandard.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/project.json create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Net35.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Nightly.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.props create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Cache.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/CacheHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlLocation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReaderSettings.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlTarget.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmEnumValueParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmParseException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAbstractNavigationSource.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlActionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotationPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotations.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlApplyExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlBinaryTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCastExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlComplexType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDecimalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDirectValueAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityContainer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityReferenceType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMember.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMemberExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIfExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIsTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlKey.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpressionReferenceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedStructuredType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOnDelete.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationReturn.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPrimitiveTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlRecordExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraint.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraintRole.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSchema.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSingleton.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSpatialTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStringTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStructuredType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTemporalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTerm.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlUntypedTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/ICsdlTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/EdmXmlDocumentParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlDocumentParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlParser.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/IUnresolvedElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedAction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedComplexType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityContainer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumMember.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedFunction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedLabeledElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedNavigationPropertyPath.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedOperation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPrimitiveType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedReturn.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedVocabularyTerm.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsActionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotationPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotations.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsApplyExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBooleanConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCastExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsComplexTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateTimeOffsetConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotationsManager.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDurationConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityContainer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMember.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMemberExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFloatingConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunctionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsGuidConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIfExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIntConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIsTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpressionReferenceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNamedTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationPropertyPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationSource.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNullExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationReturn.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOptionalParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPrimitiveTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyConstructor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsRecordExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSchema.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSingleton.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSpatialTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStructuredTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTemporalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTerm.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTimeOfDayConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsUntypedTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsVocabularyAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelReferenceElementsVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelSchemaSeparationSerializationVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmSchema.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/SerializationValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SerializationExtensionMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmLocation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmModelVisitor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmUtil.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmElementComparer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmTypeSemantics.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EnumHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ToTraceStringExtensionMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/GlobalSuppressions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/HashSetInternal.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependencyTrigger.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependent.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/IFlushCaches.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Memoizer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.NetStandard.VS2017.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.StyleCop create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/DefaultPrimitiveValueConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/IPrimitiveValueConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PassThroughPrimitiveValueConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PrimitiveValueConverterConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/RegistrationHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntityContainerBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntitySetBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousLabeledExpressionBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationImportBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousPropertyBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousSingletonBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTermBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTypeBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadBinaryTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadCollectionType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadDecimalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEdmEnumMemberValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityContainer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityReferenceType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEnumType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadLabeledExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNamedStructuredType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNavigationProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadSpatialTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStringTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStructuredType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTemporalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelComplexType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPathType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPrimitiveType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelUntypedType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/IEdmCoreModelElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicComplexType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityContainer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Date.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmAction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmActionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmBinaryTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmContainedEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmDecimalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityContainer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySetBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMember.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMemberValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunctionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmInclude.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmIncludeAnnotations.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModelBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNamedElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyInfo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationSource.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationReturn.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOptionalParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPrimitiveTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReferentialConstraint.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSingleton.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSpatialTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStringTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuralProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuredType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTemporalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinitionReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUnknownEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmNavigationTargetMapping.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmRowType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmAction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmActionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmBinaryTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCheckable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmContainedEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmDecimalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainerElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySetBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMember.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMemberValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFullNamedElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunction.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunctionImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmInclude.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmIncludeAnnotations.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmLocatable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNamedElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationPropertyBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationSource.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationImport.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationReturn.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOptionalParameter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReferentialConstraint.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaElement.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSingleton.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSpatialTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStringTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuralProperty.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTemporalTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinitionReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUnknownEntitySet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedTypeReference.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/TimeOfDay.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/ShippingAssemblyAttributes.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/TupleInternal.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/DuplicateOperationValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmError.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ExpressionTypeChecker.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/InterfaceValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ObjectLocation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationContext.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationExtensionMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationHelper.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRule.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRuleSet.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRules.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningDictionary.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningList.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningTree.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularies.xml create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationsManager.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmPropertyValueBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTerm.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTypedDirectValueAnnotationBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmVocabularyAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationsManager.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmPropertyValueBinding.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmTerm.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotatable.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularies.xml create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularyModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularies.xml create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularies.xml create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularies.xml create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmExpressionEvaluator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrConverter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrEvaluator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmApplyExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCastExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCollectionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmEnumMemberExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIfExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIsTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpressionReferenceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNavigationPropertyPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNullExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyConstructor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyPathExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmRecordExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmApplyExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBinaryConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBooleanConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCastExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCollectionExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateTimeOffsetConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDecimalConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDurationConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmEnumMemberExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmFloatingConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmGuidConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIfExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIntegerConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIsTypeExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpressionReferenceExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmNullExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmPropertyConstructor.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmRecordExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmStringConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmTimeOfDayConstantExpression.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/TryCreateObjectInstance.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularies.xml create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularyModel.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBinaryConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBooleanConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmCollectionValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateTimeOffsetConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDecimalConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDurationConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmEnumValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmFloatingConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmGuidConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmIntegerConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmPropertyValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStringConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStructuredValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmTimeOfDayConstant.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBinaryValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBooleanValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmCollectionValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateTimeOffsetValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDecimalValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDelayedValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDurationValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmEnumValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmFloatingValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmGuidValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmIntegerValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmNullValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPrimitiveValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPropertyValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStringValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStructuredValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmTimeOfDayValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmValue.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/VocabularyModelProvider.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/ActionOnDispose.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Build.Net35/Microsoft.Spatial.NetFX35.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/Microsoft.Spatial.NetStandard.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/project.json create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Net35.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Nightly.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Release.nuspec create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Build.props create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/CompositeKey.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/CoordinateSystem.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/DataServicesSpatialImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/DebugUtils.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/DrawBoth.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/ExtensionMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/FormatterExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/ForwardingSegment.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonMember.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatterImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonWriterBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Geography.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyBuilderImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollection.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollectionImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCurve.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactoryOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobe.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobeImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyHelperMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineString.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineStringImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiCurve.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineString.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineStringImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPoint.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPointImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygon.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygonImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiSurface.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyOperationsExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPipeline.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPoint.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPointImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygon.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygonImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPosition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeographySurface.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Geometry.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryBuilderImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollection.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollectionImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCurve.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactoryOfT.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryHelperMethods.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineString.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineStringImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiCurve.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineString.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineStringImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPoint.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPointImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygon.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygonImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiSurface.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryOperationsExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPipeline.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPoint.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPointImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygon.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygonImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPosition.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GeometrySurface.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GlobalSuppressions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GmlConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatterImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GmlReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/GmlWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/IGeoJsonWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/IGeographyProvider.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/IGeometryProvider.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/IShapeProvider.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/ISpatial.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/InternalsVisibleTo.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/LexerToken.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.NetStandard.VS2017.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.csproj create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.txt create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/OrcasExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.tt create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/ParseErrorException.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Spatial.StyleCop create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFactory.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFormatter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialOperations.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialPipeline.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTreeBuilder.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTypeExtensions.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidator.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidatorImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/TextLexerBase.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedPipeline.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLatLongPipeline.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLongLatPipeline.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeometryPipeline.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/Util.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextConstants.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextLexer.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatterImplementation.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlReader.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextTokenType.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/WrappedGeoJsonWriter.cs create mode 100644 ApiAsAService/odata.net/src/Microsoft.Spatial/XmlConstants.cs create mode 100644 ApiAsAService/odata.net/src/PlatformHelper.cs create mode 100644 ApiAsAService/odata.net/tools/Build.props create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/After.Common.targets create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/Before.Common.targets create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/Build.props create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/Codesign.props create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/GetNugetPackageMetadata.proj create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/ODataRuleSets.ruleset create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/Portable.targets create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/TargetFrameworkPath.props create mode 100644 ApiAsAService/odata.net/tools/CustomMSBuild/Versioning.props create mode 100644 ApiAsAService/odata.net/tools/ExecutePerf.ps1 create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/AtomMaterializerInvokerRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/BaseDataWebRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/CodeTypeReferenceRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.csproj create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.ruleset create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/DoNotHandleProhibitedExceptionsRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/EntityDescriptorPublicProperties.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/HashSetCtorRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/HttpWebRequestRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/IDSPEnumerateTypesRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/MethodCallFinder.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/MethodCallNotAllowed.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/ProcessRequestUriRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/Rules.xml create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/SelfLinkRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/ShouldNotDireclyAccessPayloadMetadataProperties.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/SystemSpatialOperationsPropertyRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/SystemUriEscapeDataStringRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/SystemUriToStringRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/ThreadGetSetDataRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/TypeOfDataServiceCollectionOfTRule.cs create mode 100644 ApiAsAService/odata.net/tools/FxCopRules/WebDataServiceExceptionCtorRule.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/KoKoMo.csproj create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/Model.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelAction.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelEngine.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelEngineOptions.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelException.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelExpression.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelItem.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelItems.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelParameter.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelRange.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelRequirement.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelTrace.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelValue.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/ModelVariable.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/Properties/assemblyinfo.cs create mode 100644 ApiAsAService/odata.net/tools/KoKoMo/kokomo.snk create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/Build.props create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/Interop.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/ModuleCore.csproj create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/Properties/assemblyinfo.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestAttribute.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestCase.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestException.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestItem.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestLoader.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestLog.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestModule.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestParser.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestProperties.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestSpec.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestThread.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/TestVariation.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/Util.cs create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/framework.snk create mode 100644 ApiAsAService/odata.net/tools/ModuleCore/src/modulecore.snk create mode 100644 ApiAsAService/odata.net/tools/PerfAnalysis.ps1 create mode 100644 ApiAsAService/odata.net/tools/PoliCheck/RunPoliCheck.ps1 create mode 100644 ApiAsAService/odata.net/tools/Scripts/TestCleanup.cmd create mode 100644 ApiAsAService/odata.net/tools/Scripts/TestStartup.cmd create mode 100644 ApiAsAService/odata.net/tools/Scripts/artcommon.js create mode 100644 ApiAsAService/odata.net/tools/Scripts/artdbclean.js create mode 100644 ApiAsAService/odata.net/tools/StringResourceGenerator/ClassGeneratorCommon.ttinclude create mode 100644 ApiAsAService/odata.net/tools/StringResourceGenerator/ResourceClassGenerator.ttinclude create mode 100644 ApiAsAService/odata.net/tools/StringResourceGenerator/StringsClassGenerator.ttinclude create mode 100644 ApiAsAService/odata.net/tools/StrongNamePublicKeys/35MSSharedLib1024.snk create mode 100644 ApiAsAService/odata.net/tools/StrongNamePublicKeys/testkey.snk create mode 100644 ApiAsAService/odata.net/tools/perf/PerfRegression.ps1 diff --git a/ApiAsAService/ApiAsAService.sln b/ApiAsAService/ApiAsAService.sln new file mode 100644 index 0000000..043c8e4 --- /dev/null +++ b/ApiAsAService/ApiAsAService.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2026 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAService\ApiAsAService.csproj", "{A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {04797995-11AD-4320-89A0-C353AD9C9B4A} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/ApiAsAService/ApiAsAService.csproj b/ApiAsAService/ApiAsAService/ApiAsAService.csproj new file mode 100644 index 0000000..9da73c0 --- /dev/null +++ b/ApiAsAService/ApiAsAService/ApiAsAService.csproj @@ -0,0 +1,120 @@ + + + + + Debug + AnyCPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925} + Exe + ApiAsAService + ApiAsAService + v4.5 + 512 + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.AspNet.OData.7.1.0\lib\net45\Microsoft.AspNet.OData.dll + + + ..\packages\Microsoft.Azure.DocumentDB.2.5.1\lib\net45\Microsoft.Azure.Documents.Client.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + + + ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll + + + ..\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll + + + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/App.config b/ApiAsAService/ApiAsAService/App.config new file mode 100644 index 0000000..fc74af6 --- /dev/null +++ b/ApiAsAService/ApiAsAService/App.config @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs b/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs new file mode 100644 index 0000000..02c49e2 --- /dev/null +++ b/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; +using Microsoft.AspNet.OData.Extensions; + +namespace DynamicEdmModelCreation +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + config.CustomMapODataServiceRoute("odata", "odata"); + + config.AddODataQueryFilter(); + } + } +} diff --git a/ApiAsAService/ApiAsAService/Constants.cs b/ApiAsAService/ApiAsAService/Constants.cs new file mode 100644 index 0000000..775f8fa --- /dev/null +++ b/ApiAsAService/ApiAsAService/Constants.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DynamicEdmModelCreation +{ + public static class Constants + { + public const string CustomODataPath = "CustomODataPath"; + + public const string ODataDataSource = "ODataDataSource"; + + public const string MyDataSource = "mydatasource"; + + public const string AnotherDataSource = "anotherdatasource"; + } +} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs b/ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs new file mode 100644 index 0000000..6e2d68d --- /dev/null +++ b/ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http; +using DynamicEdmModelCreation.DataSource; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace DynamicEdmModelCreation.Controllers +{ + public class HandleAllController : ODataController + { + private class Customer + { + public int Id; + public string FirstName; + public string LastName; + public int Age; + } + + private Customer[] population = new Customer[] + { + new Customer + { + Id=0, + FirstName="Don", + LastName="Juan", + Age=10, + }, + new Customer + { + Id=1, + FirstName="Sally", + LastName="Spade", + Age=12, + }, + new Customer + { + Id=3, + FirstName="Pat", + LastName="Patrick", + Age=9, + }, + }; + + // Get entityset + [EnableQuery(AllowedQueryOptions =Microsoft.AspNet.OData.Query.AllowedQueryOptions.All)] + public EdmEntityObjectCollection Get() + { + // Get entity set's EDM type: A collection type. + ODataPath path = Request.ODataProperties().Path; + IEdmCollectionType collectionType = (IEdmCollectionType)path.EdmType; + IEdmEntityTypeReference entityType = collectionType.ElementType.AsEntity(); + + // Create an untyped collection with the EDM collection type. + EdmEntityObjectCollection collection = + new EdmEntityObjectCollection(new EdmCollectionTypeReference(collectionType)); + + // Add untyped objects to collection. + DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], entityType, collection); + + return collection; + } + + // Get entityset(key) + public object Get(string key) + { + //return population.Where(c => c.Id.ToString() == key); + + // Get entity type from path. + ODataPath path = Request.ODataProperties().Path; + IEdmEntityType entityType = (IEdmEntityType)path.EdmType; + + // Create an untyped entity object with the entity type. + EdmEntityObject entity = new EdmEntityObject(entityType); + + DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], key, entity); + + return entity; + } + + public IHttpActionResult GetName(string key) + { + // Get entity type from path. + ODataPath path = Request.ODataProperties().Path; + + if (path.PathTemplate != "~/entityset/key/property") + { + return BadRequest("Not the correct property access request!"); + } + + PropertySegment property = path.Segments.Last() as PropertySegment; + IEdmEntityType entityType = property.Property.DeclaringType as IEdmEntityType; + + // Create an untyped entity object with the entity type. + EdmEntityObject entity = new EdmEntityObject(entityType); + + DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], key, entity); + + object value = DataSourceProvider.GetProperty((string)Request.Properties[Constants.ODataDataSource], "Name", entity); + + if (value == null) + { + return NotFound(); + } + + string strValue = value as string; + return Ok(strValue); + } + + public IHttpActionResult GetNavigation(string key, string navigation) + { + ODataPath path = Request.ODataProperties().Path; + + if (path.PathTemplate != "~/entityset/key/navigation") + { + return BadRequest("Not the correct navigation property access request!"); + } + + NavigationPropertySegment property = path.Segments.Last() as NavigationPropertySegment; + if (property == null) + { + return BadRequest("Not the correct navigation property access request!"); + } + + IEdmEntityType entityType = property.NavigationProperty.DeclaringType as IEdmEntityType; + + EdmEntityObject entity = new EdmEntityObject(entityType); + + DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], key, entity); + + object value = DataSourceProvider.GetProperty((string)Request.Properties[Constants.ODataDataSource], navigation, entity); + + if (value == null) + { + return NotFound(); + } + + IEdmEntityObject nav = value as IEdmEntityObject; + if (nav == null) + { + return NotFound(); + } + + return Ok(nav); + } + } +} diff --git a/ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs b/ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs new file mode 100644 index 0000000..0798d40 --- /dev/null +++ b/ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace DynamicEdmModelCreation +{ + public class CustomODataPathRouteConstraint : ODataPathRouteConstraint + { + // "%2F" + private static readonly string escapedSlash = Uri.HexEscape('/'); + + public CustomODataPathRouteConstraint(string routeName) + : base(routeName) + { + } + + public override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (routeDirection == HttpRouteDirection.UriResolution) + { + if (values.TryGetValue(ODataRouteConstants.ODataPath, out object oDataPathValue)) + { + string oDataPathString = oDataPathValue as string; + ODataPath path; + + try + { + IServiceProvider requestContainer = request.CreateRequestContainer(this.RouteName); + IHttpRequestMessageProvider httpRequestMessageProvider = requestContainer.GetRequiredService(); + httpRequestMessageProvider.Request = request; + + string[] segments = oDataPathString.Split('/'); + string dataSource = segments[0]; + request.Properties[Constants.ODataDataSource] = dataSource; + request.Properties[Constants.CustomODataPath] = string.Join("/", segments, 1, segments.Length - 1); + oDataPathString = (string)request.Properties[Constants.CustomODataPath]; + + // Service root is the current RequestUri, less the query string and the ODataPath (always the + // last portion of the absolute path). ODL expects an escaped service root and other service + // root calculations are calculated using AbsoluteUri (also escaped). But routing exclusively + // uses unescaped strings, determined using + // address.GetComponents(UriComponents.Path, UriFormat.Unescaped) + // + // For example if the AbsoluteUri is + // , the + // oDataPathString will contain "FunctionCall(p0='Chinese西雅图Chars')". + // + // Due to this decoding and the possibility of unecessarily-escaped characters, there's no + // reliable way to determine the original string from which oDataPathString was derived. + // Therefore a straightforward string comparison won't always work. See RemoveODataPath() for + // details of chosen approach. + string requestLeftPart = request.RequestUri.GetLeftPart(UriPartial.Path); + string serviceRoot = requestLeftPart; + if (!string.IsNullOrEmpty(oDataPathString)) + { + serviceRoot = RemoveODataPath(serviceRoot, oDataPathString); + } + + // As mentioned above, we also need escaped ODataPath. + // The requestLeftPart and request.RequestUri.Query are both escaped. + // The ODataPath for service documents is empty. + string oDataPathAndQuery = requestLeftPart.Substring(serviceRoot.Length); + if (!string.IsNullOrEmpty(request.RequestUri.Query)) + { + // Ensure path handler receives the query string as well as the path. + oDataPathAndQuery += request.RequestUri.Query; + } + + // Leave an escaped '/' out of the service route because DefaultODataPathHandler will add a + // literal '/' to the end of this string if not already present. That would double the slash + // in response links and potentially lead to later 404s. + if (serviceRoot.EndsWith(escapedSlash, StringComparison.OrdinalIgnoreCase)) + { + serviceRoot = serviceRoot.Substring(0, serviceRoot.Length - 3); + } + + IODataPathHandler pathHandler = requestContainer.GetRequiredService(); + path = pathHandler.Parse(serviceRoot, oDataPathAndQuery, requestContainer); + } + catch (ODataException) + { + path = null; + } + + if (path != null) + { + // Set all the properties we need for routing, querying, formatting + request.ODataProperties().Path = path; + request.ODataProperties().RouteName = this.RouteName; + + if (!values.ContainsKey(ODataRouteConstants.Controller)) + { + // Select controller name using the routing conventions + string controllerName = this.SelectControllerName(path, request); + if (controllerName != null) + { + values[ODataRouteConstants.Controller] = controllerName; + } + } + + return true; + } + } + + // The request doesn't match this route so dipose the request container. + request.DeleteRequestContainer(true); + return false; + } + else + { + // This constraint only applies to URI resolution + return true; + } + } + + // Find the substring of the given URI string before the given ODataPath. Tests rely on the following: + // 1. ODataPath comes at the end of the processed Path + // 2. Virtual path root, if any, comes at the beginning of the Path and a '/' separates it from the rest + // 3. OData prefix, if any, comes between the virtual path root and the ODataPath and '/' characters separate + // it from the rest + // 4. Even in the case of Unicode character corrections, the only differences between the escaped Path and the + // unescaped string used for routing are %-escape sequences which may be present in the Path + // + // Therefore, look for the '/' character at which to lop off the ODataPath. Can't just unescape the given + // uriString because subsequent comparisons would only help to check wehther a match is _possible_, not where + // to do the lopping. + private static string RemoveODataPath(string uriString, string oDataPathString) + { + // Potential index of oDataPathString within uriString. + int endIndex = uriString.Length - oDataPathString.Length - 1; + if (endIndex <= 0) + { + // Bizarre: oDataPathString is longer than uriString. Likely the values collection passed to Match() + // is corrupt. + throw new InvalidOperationException($"Request Uri Is Too Short For ODataPath. the Uri is {uriString}, and the OData path is {oDataPathString}."); + } + + string startString = uriString.Substring(0, endIndex + 1); // Potential return value. + string endString = uriString.Substring(endIndex + 1); // Potential oDataPathString match. + if (string.Equals(endString, oDataPathString, StringComparison.Ordinal)) + { + // Simple case, no escaping in the ODataPathString portion of the Path. In this case, don't do extra + // work to look for trailing '/' in startString. + return startString; + } + + while (true) + { + // Escaped '/' is a derivative case but certainly possible. + int slashIndex = startString.LastIndexOf('/', endIndex - 1); + int escapedSlashIndex = + startString.LastIndexOf(escapedSlash, endIndex - 1, StringComparison.OrdinalIgnoreCase); + if (slashIndex > escapedSlashIndex) + { + endIndex = slashIndex; + } + else if (escapedSlashIndex >= 0) + { + // Include the escaped '/' (three characters) in the potential return value. + endIndex = escapedSlashIndex + 2; + } + else + { + // Failure, unable to find the expected '/' or escaped '/' separator. + throw new InvalidOperationException($"The OData path is not found. The Uri is {uriString}, and the OData path is {oDataPathString}."); + } + + startString = uriString.Substring(0, endIndex + 1); + endString = uriString.Substring(endIndex + 1); + + // Compare unescaped strings to avoid both arbitrary escaping and use of lowercase 'a' through 'f' in + // %-escape sequences. + endString = Uri.UnescapeDataString(endString); + if (string.Equals(endString, oDataPathString, StringComparison.Ordinal)) + { + return startString; + } + + if (endIndex == 0) + { + // Failure, could not match oDataPathString after an initial '/' or escaped '/'. + throw new InvalidOperationException($"The OData path is not found. The Uri is {uriString}, and the OData path is {oDataPathString}."); + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/CustomODataRoute.cs b/ApiAsAService/ApiAsAService/CustomODataRoute.cs new file mode 100644 index 0000000..d7665ce --- /dev/null +++ b/ApiAsAService/ApiAsAService/CustomODataRoute.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Routing; + +namespace DynamicEdmModelCreation +{ + public class CustomODataRoute : ODataRoute + { + private static readonly string _escapedHashMark = Uri.HexEscape('#'); + private static readonly string _escapedQuestionMark = Uri.HexEscape('?'); + + private bool _canGenerateDirectLink; + + public CustomODataRoute(string routePrefix, ODataPathRouteConstraint pathConstraint) + : base(routePrefix, pathConstraint) + { + _canGenerateDirectLink = routePrefix != null && RoutePrefix.IndexOf('{') == -1; + } + + public override IHttpVirtualPathData GetVirtualPath( + HttpRequestMessage request, + IDictionary values) + { + if (values == null || !values.Keys.Contains(HttpRoute.HttpRouteKey, StringComparer.OrdinalIgnoreCase)) + { + return null; + } + + object odataPathValue; + if (!values.TryGetValue(ODataRouteConstants.ODataPath, out odataPathValue)) + { + return null; + } + + string odataPath = odataPathValue as string; + if (odataPath != null) + { + return GenerateLinkDirectly(request, odataPath) ?? base.GetVirtualPath(request, values); + } + + return null; + } + + internal HttpVirtualPathData GenerateLinkDirectly(HttpRequestMessage request, string odataPath) + { + HttpConfiguration configuration = request.GetConfiguration(); + if (configuration == null || !_canGenerateDirectLink) + { + return null; + } + + string dataSource = request.Properties[Constants.ODataDataSource] as string; + string link = CombinePathSegments(RoutePrefix, dataSource); + link = CombinePathSegments(link, odataPath); + link = UriEncode(link); + + return new HttpVirtualPathData(this, link); + } + + private static string CombinePathSegments(string routePrefix, string odataPath) + { + return string.IsNullOrEmpty(routePrefix) + ? odataPath + : (string.IsNullOrEmpty(odataPath) ? routePrefix : routePrefix + '/' + odataPath); + } + + private static string UriEncode(string str) + { + string escape = Uri.EscapeUriString(str); + escape = escape.Replace("#", _escapedHashMark); + escape = escape.Replace("?", _escapedQuestionMark); + return escape; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs b/ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs new file mode 100644 index 0000000..92cd5ed --- /dev/null +++ b/ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.OData.Edm; + +namespace DynamicEdmModelCreation.DataSource +{ + internal class AnotherDataSource : IDataSource + { + private IEdmEntityType _school; + + public void GetModel(EdmModel model, EdmEntityContainer container) + { + EdmEntityType student = new EdmEntityType("ns", "Student"); + student.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); + EdmStructuralProperty key = student.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32); + student.AddKeys(key); + model.AddElement(student); + EdmEntitySet students = container.AddEntitySet("Students", student); + + EdmEntityType school = new EdmEntityType("ns", "School"); + school.AddKeys(school.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32)); + school.AddStructuralProperty("CreatedDay", EdmPrimitiveTypeKind.DateTimeOffset); + model.AddElement(school); + EdmEntitySet schools = container.AddEntitySet("Schools", student); + + EdmNavigationProperty schoolNavProp = student.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo + { + Name = "School", + TargetMultiplicity = EdmMultiplicity.One, + Target = school + }); + students.AddNavigationTarget(schoolNavProp, schools); + + _school = school; + } + + public void Get(IEdmEntityTypeReference entityType, EdmEntityObjectCollection collection) + { + EdmEntityObject entity = new EdmEntityObject(entityType); + entity.TrySetPropertyValue("Name", "Foo"); + entity.TrySetPropertyValue("ID", 100); + entity.TrySetPropertyValue("School", Createchool(99, new DateTimeOffset(2016, 1, 19, 1, 2, 3, TimeSpan.Zero), entity.ActualEdmType)); + collection.Add(entity); + + entity = new EdmEntityObject(entityType); + entity.TrySetPropertyValue("Name", "Bar"); + entity.TrySetPropertyValue("ID", 101); + entity.TrySetPropertyValue("School", Createchool(99, new DateTimeOffset(1978, 11, 15, 1, 2, 3, TimeSpan.Zero), entity.ActualEdmType)); + + collection.Add(entity); + } + + public void Get(string key, EdmEntityObject entity) + { + entity.TrySetPropertyValue("Name", "Foo"); + entity.TrySetPropertyValue("ID", int.Parse(key)); + entity.TrySetPropertyValue("School", Createchool(99, new DateTimeOffset(2016, 1, 19, 1, 2, 3, TimeSpan.Zero), entity.ActualEdmType)); + } + + public object GetProperty(string property, EdmEntityObject entity) + { + object value; + entity.TryGetPropertyValue(property, out value); + return value; + } + + private IEdmEntityObject Createchool(int id, DateTimeOffset dto, IEdmStructuredType edmType) + { + IEdmNavigationProperty navigationProperty = edmType.DeclaredProperties.OfType().FirstOrDefault(e => e.Name == "School"); + if (navigationProperty == null) + { + return null; + } + + EdmEntityObject entity = new EdmEntityObject(navigationProperty.ToEntityType()); + entity.TrySetPropertyValue("ID", id); + entity.TrySetPropertyValue("CreatedDay", dto); + return entity; + } + } +} diff --git a/ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs b/ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs new file mode 100644 index 0000000..d6a5cf7 --- /dev/null +++ b/ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData; +using Microsoft.OData.Edm; + +namespace DynamicEdmModelCreation.DataSource +{ + internal class DataSourceProvider + { + public static IEdmModel GetEdmModel(string dataSourceName) + { + EdmModel model = new EdmModel(); + EdmEntityContainer container = new EdmEntityContainer("ns", "container"); + model.AddElement(container); + + GetDataSource(dataSourceName).GetModel(model, container); + + return model; + } + + public static void Get( + string dataSourceName, + IEdmEntityTypeReference entityType, + EdmEntityObjectCollection collection) + { + GetDataSource(dataSourceName).Get(entityType, collection); + } + + public static void Get(string dataSourceName, string key, EdmEntityObject entity) + { + GetDataSource(dataSourceName).Get(key, entity); + } + + public static object GetProperty(string dataSourceName, string property, EdmEntityObject entity) + { + return GetDataSource(dataSourceName).GetProperty(property, entity); + } + + private static IDataSource GetDataSource(string dataSourceName) + { + dataSourceName = dataSourceName == null ? string.Empty : dataSourceName.ToLowerInvariant(); + + switch (dataSourceName) + { + case Constants.MyDataSource: + return new MyDataSource(); + case Constants.AnotherDataSource: + return new AnotherDataSource(); + default: + throw new InvalidOperationException( + string.Format("Data source: {0} is not registered.", dataSourceName)); + } + } + } +} diff --git a/ApiAsAService/ApiAsAService/DataSource/IDataSource.cs b/ApiAsAService/ApiAsAService/DataSource/IDataSource.cs new file mode 100644 index 0000000..585dad0 --- /dev/null +++ b/ApiAsAService/ApiAsAService/DataSource/IDataSource.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData; +using Microsoft.OData.Edm; + +namespace DynamicEdmModelCreation.DataSource +{ + internal interface IDataSource + { + void GetModel(EdmModel model, EdmEntityContainer container); + + void Get(IEdmEntityTypeReference entityType, EdmEntityObjectCollection collection); + + void Get(string key, EdmEntityObject entity); + + object GetProperty(string property, EdmEntityObject entity); + } +} diff --git a/ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs b/ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs new file mode 100644 index 0000000..a22e67c --- /dev/null +++ b/ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.OData.Edm; + +namespace DynamicEdmModelCreation.DataSource +{ + internal class MyDataSource : IDataSource + { + public void GetModel(EdmModel model, EdmEntityContainer container) + { + EdmEntityType product = new EdmEntityType("ns", "Product"); + product.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); + EdmStructuralProperty key = product.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32); + product.AddKeys(key); + model.AddElement(product); + EdmEntitySet products = container.AddEntitySet("Products", product); + + EdmEntityType detailInfo = new EdmEntityType("ns", "DetailInfo"); + detailInfo.AddKeys(detailInfo.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32)); + detailInfo.AddStructuralProperty("Title", EdmPrimitiveTypeKind.String); + model.AddElement(detailInfo); + EdmEntitySet detailInfos = container.AddEntitySet("DetailInfos", product); + + EdmNavigationProperty detailInfoNavProp = product.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo + { + Name = "DetailInfo", + TargetMultiplicity = EdmMultiplicity.One, + Target = detailInfo + }); + products.AddNavigationTarget(detailInfoNavProp, detailInfos); + } + + public void Get(IEdmEntityTypeReference entityType, EdmEntityObjectCollection collection) + { + EdmEntityObject entity = new EdmEntityObject(entityType); + entity.TrySetPropertyValue("Name", "abc"); + entity.TrySetPropertyValue("ID", 1); + entity.TrySetPropertyValue("DetailInfo", CreateDetailInfo(88, "abc_detailinfo", entity.ActualEdmType)); + + collection.Add(entity); + entity = new EdmEntityObject(entityType); + entity.TrySetPropertyValue("Name", "def"); + entity.TrySetPropertyValue("ID", 2); + entity.TrySetPropertyValue("DetailInfo", CreateDetailInfo(99, "def_detailinfo", entity.ActualEdmType)); + + collection.Add(entity); + } + + public void Get(string key, EdmEntityObject entity) + { + entity.TrySetPropertyValue("Name", "abc"); + entity.TrySetPropertyValue("ID", int.Parse(key)); + entity.TrySetPropertyValue("DetailInfo", CreateDetailInfo(88, "abc_detailinfo", entity.ActualEdmType)); + } + + public object GetProperty(string property, EdmEntityObject entity) + { + object value; + entity.TryGetPropertyValue(property, out value); + return value; + } + + private IEdmEntityObject CreateDetailInfo(int id, string title, IEdmStructuredType edmType) + { + IEdmNavigationProperty navigationProperty = edmType.DeclaredProperties.OfType().FirstOrDefault(e => e.Name == "DetailInfo"); + if (navigationProperty == null) + { + return null; + } + + EdmEntityObject entity = new EdmEntityObject(navigationProperty.ToEntityType()); + entity.TrySetPropertyValue("ID", id); + entity.TrySetPropertyValue("Title", title); + return entity; + } + } +} diff --git a/ApiAsAService/ApiAsAService/DynamicModelHelper.cs b/ApiAsAService/ApiAsAService/DynamicModelHelper.cs new file mode 100644 index 0000000..50c8a29 --- /dev/null +++ b/ApiAsAService/ApiAsAService/DynamicModelHelper.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using DynamicEdmModelCreation.DataSource; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ServiceLifetime = Microsoft.OData.ServiceLifetime; + +namespace DynamicEdmModelCreation +{ + public static class DynamicModelHelper + { + public static ODataRoute CustomMapODataServiceRoute(this HttpConfiguration configuration, string routeName, string routePrefix) + { + ODataRoute route = configuration.MapODataServiceRoute(routeName, routePrefix, builder => + { + // Get the model from the datasource of the current request: model-per-pequest. + builder.AddService(ServiceLifetime.Scoped, sp => + { + IHttpRequestMessageProvider requestMessageProvider = sp.GetRequiredService(); + string dataSource = requestMessageProvider.Request.Properties[Constants.ODataDataSource] as string; + IEdmModel model = DataSourceProvider.GetEdmModel(dataSource); + return model; + }); + + // Create a request provider for every request. This is a workaround for the missing HttpContext of a self-hosted webapi. + builder.AddService(ServiceLifetime.Scoped, sp => new HttpRequestMessageProvider()); + + // The routing conventions are registered as singleton. + builder.AddService(ServiceLifetime.Singleton, sp => + { + IList routingConventions = ODataRoutingConventions.CreateDefault(); + routingConventions.Insert(0, new MatchAllRoutingConvention()); + return routingConventions.ToList().AsEnumerable(); + }); + }); + + CustomODataRoute odataRoute = new CustomODataRoute(route.RoutePrefix, new CustomODataPathRouteConstraint(routeName)); + configuration.Routes.Remove(routeName); + configuration.Routes.Add(routeName, odataRoute); + + return odataRoute; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs b/ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs new file mode 100644 index 0000000..9fe6151 --- /dev/null +++ b/ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; + +namespace DynamicEdmModelCreation +{ + public class HttpRequestMessageProvider : IHttpRequestMessageProvider + { + /// + public HttpRequestMessage Request { get; set; } + } +} diff --git a/ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs b/ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs new file mode 100644 index 0000000..409e940 --- /dev/null +++ b/ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; + +namespace DynamicEdmModelCreation +{ + // based on github https://github.com/OData/ODataSamples/pull/67 + public interface IHttpRequestMessageProvider + { + HttpRequestMessage Request { get; set; } + } +} diff --git a/ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs b/ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs new file mode 100644 index 0000000..ed83735 --- /dev/null +++ b/ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Net.Http; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace DynamicEdmModelCreation +{ + public class MatchAllRoutingConvention : IODataRoutingConvention + { + public string SelectAction( + ODataPath odataPath, + HttpControllerContext controllerContext, + ILookup actionMap) + { + if (odataPath.PathTemplate == "~/entityset/key/navigation") + { + if (controllerContext.Request.Method == HttpMethod.Get) + { + NavigationPropertySegment navigationPathSegment = (NavigationPropertySegment)odataPath.Segments.Last(); + + controllerContext.RouteData.Values["navigation"] = navigationPathSegment.NavigationProperty.Name; + + KeySegment keyValueSegment = (KeySegment)odataPath.Segments[1]; + controllerContext.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Keys.First().Value; + + return "GetNavigation"; + } + } + + return null; + } + + public string SelectController(ODataPath odataPath, HttpRequestMessage request) + { + return (odataPath.Segments.FirstOrDefault() is EntitySetSegment) ? "HandleAll" : null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/Program.cs b/ApiAsAService/ApiAsAService/Program.cs new file mode 100644 index 0000000..35633fa --- /dev/null +++ b/ApiAsAService/ApiAsAService/Program.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using System.Web.Http; +using Microsoft.Owin.Hosting; +using Owin; + +namespace DynamicEdmModelCreation +{ + class Program + { + private static readonly string serviceUrl = "http://localhost:54321"; + + public static void Main(string[] args) + { + using (WebApp.Start(serviceUrl, Configuration)) + { + Console.WriteLine("Server listening at {0}", serviceUrl); + + QueryTheService().Wait(); + + Console.WriteLine("Press Any Key to Exit ..."); + Console.ReadKey(); + } + } + + private static async Task QueryTheService() + { + await SendQuery("/odata/mydatasource/", "Query service document."); + await SendQuery("/odata/mydatasource/$metadata", "Query $metadata."); + await SendQuery("/odata/mydatasource/Products", "Query the Products entity set."); + await SendQuery("/odata/mydatasource/Products(1)", "Query a Product entry."); + + await SendQuery("/odata/anotherdatasource/", "Query service document."); + await SendQuery("/odata/anotherdatasource/$metadata", "Query $metadata."); + await SendQuery("/odata/anotherdatasource/Students", "Query the Students entity set."); + await SendQuery("/odata/anotherdatasource/Students(100)", "Query a Student entry."); + + await SendQuery("/odata/mydatasource/Products(1)/Name", "Query the name of Products(1)."); + await SendQuery("/odata/anotherdatasource/Students(100)/Name", "Query the name of Students(100)."); + + await SendQuery("/odata/mydatasource/Products(1)/DetailInfo", "Query the navigation property 'DetailInfo' of Products(1)."); + await SendQuery("/odata/anotherdatasource/Students(100)/School", "Query the navigation proeprty 'School' of Students(100)."); + } + + private static async Task SendQuery(string query, string queryDescription) + { + Console.WriteLine("Sending request to: {0}. Executing {1}...", query, queryDescription); + + HttpClient client = new HttpClient(); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, serviceUrl + query); + HttpResponseMessage response = await client.SendAsync(request); + + Console.WriteLine("\r\nResult:"); + Console.WriteLine(response.StatusCode); + Console.WriteLine(await response.Content.ReadAsStringAsync()); + Console.WriteLine(); + } + + private static void Configuration(IAppBuilder builder) + { + HttpConfiguration configuration = new HttpConfiguration(); + WebApiConfig.Register(configuration); + builder.UseWebApi(configuration); + } + } +} diff --git a/ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs b/ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b123ad8 --- /dev/null +++ b/ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DynamicEdmModelCreation")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DynamicEdmModelCreation")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a3d7d54b-67d2-44cc-bfcd-c7ce09b58925")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/ApiAsAService/packages.config b/ApiAsAService/ApiAsAService/packages.config new file mode 100644 index 0000000..cf83a5d --- /dev/null +++ b/ApiAsAService/ApiAsAService/packages.config @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/Diagrams.pptx b/ApiAsAService/Diagrams.pptx new file mode 100644 index 0000000000000000000000000000000000000000..f48522303ffabfdc837a706fa25717d8f8b173cd GIT binary patch literal 36473 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 + + + + + true + true + true + true + + false + true + + + + + Microsoft Restier + + Microsoft + Microsoft Corporation. + .NET Foundation + © Microsoft Corporation. All rights reserved. + en-US + https://raw.githubusercontent.com/OData/RESTier/master/License.txt + + MIT + + $(NoWarn);NU5125 + + $(NoWarn);NU5105 + + + odata@nimbleapps.cloud + + https://go.microsoft.com/fwlink/?LinkID=288859 + https://restier.readthedocs.io/en/latest/ + true + odata;wcf data services; + true + + $(MSBuildThisFileDirectory) + https://github.com/OData/RESTier + git + + + + + true + + + SHA256 + + + false + + + $(NoWarn);CA1812 + + + + $(NoWarn);CA1001;CA1707;CA2007;CA1801 + + + + $(NoWarn);CA1001;CA1707;CA1716;CA1801;CA1822 + + + + true + true + + + + netcoreapp2.2;net461 + + + + + + all + + + + + + all + + + all + + + + + diff --git a/ApiAsAService/RESTier/License.txt b/ApiAsAService/RESTier/License.txt new file mode 100644 index 0000000..ad59e83 --- /dev/null +++ b/ApiAsAService/RESTier/License.txt @@ -0,0 +1,25 @@ +RESTier + +Copyright (c) 2018 Microsoft. All rights reserved. + +Material in this repository is made available under the following terms: + 1. Code is licensed under the MIT license, reproduced below. + 2. Documentation is licensed under the Creative Commons Attribution 3.0 United States (Unported) License. + The text of the license can be found here: http://creativecommons.org/licenses/by/3.0/legalcode + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ApiAsAService/RESTier/NuGet.Config b/ApiAsAService/RESTier/NuGet.Config new file mode 100644 index 0000000..13beefc --- /dev/null +++ b/ApiAsAService/RESTier/NuGet.Config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/README.md b/ApiAsAService/RESTier/README.md new file mode 100644 index 0000000..eb674fd --- /dev/null +++ b/ApiAsAService/RESTier/README.md @@ -0,0 +1,113 @@ +
+

Microsoft Restier - OData Made Simple

+ +[Releases](https://github.com/OData/RESTier/releases)   |   Documentation   |   [OData v4.01 Documentation](https://www.odata.org/documentation/) + +[![Build Status][devops-build-img]][devops-build] [![Release Status][devops-release-img]][devops-release] [![Nightly Feed][nightly-feed-img]][nightly-feed]
+[![Code of Conduct][code-of-conduct-img]][code-of-conduct] [![Twitter][twitter-img]][twitter-intent] + +
+ +## What is Restier? + +Restier is an API development framework for building standardized, OData V4 based RESTful services on .NET. + +Restier is the spiritual successor to [WCF Data Services](https://en.wikipedia.org/wiki/WCF_Data_Services). Instead of +generating endless boilerplate code with the current Web API + OData toolchain, RESTier helps you boostrap a standardized, +queryable HTTP-based REST interface in literally minutes. And that's just the beginning. + +Like WCF Data Services before it, Restier provides simple and straightforward ways to shape queries and intercept submissions +_before_ and _after_ they hit the database. And like Web API + OData, you still have the flexibility to add your own +custom queries and actions with techniques you're already familiar with. + +## What is OData? + +OData stands for the Open Data Protocol. OData enables the creation and consumption of RESTful APIs, which allow +resources, defined in a data model and identified by using URLs, to be published and edited by Web clients using +simple HTTP requests. + +OData was originally designed by Microsoft to be a framework for exposing Entity Framework objects over REST services. +The first concepts shipped as "Project Astoria" in 2007. By 2009, the concept had evolved enough for Microsoft to +announce OData, along with a [larger effort](https://blogs.msdn.microsoft.com/odatateam/2009/11/17/breaking-down-data-silos-the-open-data-protocol-odata/) +to push the format as an industry standard. + +Work on the current version of the protocol (V4) began in April 2012, and was ratified by OASIS as an industry standard in Feb 2014. + +## Getting Started +Now that the project has restarted, we have a new location for our [Continuous Integration builds][nightly-feed]. We've simplified the NuGet +packages as well, so now you can just reference `Microsoft.Restier.AspNet` or `Microsoft.Restier.AspNetCore` (coming soon) packages, and we'll take care of +the rest. + +## Use Cases +Coming Soon! + +## Supported Platforms +Restier 1.0 currently ships with support for Classic ASP.NET 5.2.3 and later. Support for ASP.NET Core 2.2 is coming in the first half of 2019. (More specifics will be provided in a few weeks.) + +## Restier Components +The Classic ASP.NET flavor of Restier is made up of the following components: +- **Microsoft.Restier.AspNet:** Plugs into the OData/WebApi processing pipeline and provides query interception capabilities. +- **Microsoft.Restier.Core:** The base library that contains the core convention-based interception framework. +- **Microsoft.Restier.EntityFramework:** Translates intercepted queries down to the database level to be executed. + +While the ASP.NET Core flavor of Restier (when is ships) will consist of the following: +- **Microsoft.Restier.AspNetCore:** Plugs into the OData/WebApi processing pipeline and provides query interception capabilities. +- **Microsoft.Restier.Core:** The base library that contains the core convention-based interception framework. +- **Microsoft.Restier.EntityFrameworkCore:** Translates intercepted queries down to the database level to be executed. + +## Ecosystem +Restier is used in solutions from: +- [BurnRate.io](https://burnrate.io) +- [CloudNimble, Inc.](https://nimbleapps.cloud) +- [Florida Agency for Health Care Administration](https://ahca.myflorida.com) + +There is also a growing set of tools to support Restier-based development +- [Breakdance.Restier](https://github.com/cloudnimble/breakdance): Convention-based name troubleshooting and integration test support. +## Community +After a couple years in statis, Restier is in active development once again. The project is lead by Robert McLaws and Chris Woodruff. + +### Weekly Standups +The core development team meets once a week on Google Hangouts to discuss pressing items and work through the issues list. A history of +those meetings can be found in the Wiki. + +### Contributing +If you'd like to help out with the project, our Contributor's Handbook is also located in the Wiki. + +## Contributors + +Special thanks to everyone involved in making RESTier the best API development platform for .NET. The following people +have made various contributions to the codebase: + +| Microsoft | External | +|---------------|----------------| +| Lewis Cheng | Cengiz Ilerler | +| Challenh | Kemal M | +| Eric Erhardt | Robert McLaws | +| Vincent He | | +| Dong Liu | | +| Layla Liu | | +| Fan Ouyang | | +| Congyong S | | +| Mark Stafford | | +| Ray Yao | | + +## + + + +[devops-build]:https://dev.azure.com/dotnet/OData/_build?definitionId=89 +[devops-release]:https://dev.azure.com/cloudnimble/Restier/_release?view=all&definitionId=1 +[nightly-feed]:https://www.myget.org/F/restier-nightly/api/v3/index.json +[twitter-intent]:https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2FOData%2FRESTier&via=robertmclaws&text=Check%20out%20Restier%21%20It%27s%20the%20simple%2C%20queryable%20framework%20for%20building%20data-driven%20APIs%20in%20.NET%21&hashtags=odata +[code-of-conduct]:https://opensource.microsoft.com/codeofconduct/ + +[devops-build-img]:https://img.shields.io/azure-devops/build/dotnet/odata/89.svg?style=for-the-badge&logo=azuredevops +[devops-release-img]:https://img.shields.io/azure-devops/release/cloudnimble/d3aaa016-9aea-4903-b6a6-abda1d4c84f0/1/1.svg?style=for-the-badge&logo=azuredevops +[nightly-feed-img]:https://img.shields.io/badge/continuous%20integration-feed-0495dc.svg?style=for-the-badge&logo=nuget&logoColor=fff +[github-version-img]:https://img.shields.io/github/release/ryanoasis/nerd-fonts.svg?style=for-the-badge +[gitter-img]:https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=for-the-badge +[code-climate-img]:https://img.shields.io/codeclimate/issues/github/ryanoasis/nerd-fonts.svg?style=for-the-badge +[code-of-conduct-img]: https://img.shields.io/badge/code%20of-conduct-00a1f1.svg?style=for-the-badge&logo=windows +[twitter-img]:https://img.shields.io/badge/share-on%20twitter-55acee.svg?style=for-the-badge&logo=twitter diff --git a/ApiAsAService/RESTier/RESTier.sln b/ApiAsAService/RESTier/RESTier.sln new file mode 100644 index 0000000..2c6037d --- /dev/null +++ b/ApiAsAService/RESTier/RESTier.sln @@ -0,0 +1,112 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29006.145 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{724F25F3-B47A-4A80-8F7A-08B2E8121D10}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{D8A3183C-1E9C-4D6C-AC72-4EF938EC9895}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataProviders", "DataProviders", "{37B52FD3-E72B-406F-8C5A-F146256D7743}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{9D3D8728-C31B-4D5E-B471-79A9DBBA0E58}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Core", "src\Microsoft.Restier.Core\Microsoft.Restier.Core.csproj", "{300B769A-3513-49D0-A035-7DB965C8D2A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.AspNetCore", "src\Microsoft.Restier.AspNetCore\Microsoft.Restier.AspNetCore.csproj", "{97E94F97-E73B-4074-8587-AE1B91B4D61E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.AspNet", "src\Microsoft.Restier.AspNet\Microsoft.Restier.AspNet.csproj", "{8ECF4E97-1816-44AD-AD63-6ACF287ED520}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.EntityFramework", "src\Microsoft.Restier.EntityFramework\Microsoft.Restier.EntityFramework.csproj", "{0E373B2A-2ED2-4566-A275-6BE81CFFE00B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Tests.Core", "src\Microsoft.Restier.Tests.Core\Microsoft.Restier.Tests.Core.csproj", "{16DBAD48-C935-4BF1-BC4A-925031AEA0FA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Tests.EntityFramework", "src\Microsoft.Restier.Tests.EntityFramework\Microsoft.Restier.Tests.EntityFramework.csproj", "{EB7010EC-4AD2-4CEB-8757-46447FEC80C7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Tests.AspNet", "src\Microsoft.Restier.Tests.AspNet\Microsoft.Restier.Tests.AspNet.csproj", "{FD305A0A-5680-4C38-9917-84233F35DE3F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Tests.AspNetCore", "src\Microsoft.Restier.Tests.AspNetCore\Microsoft.Restier.Tests.AspNetCore.csproj", "{39D4AC9D-091B-437E-A616-3E9D77804BDB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{901E6A2A-23EC-4BC8-B4C6-A3EF70D72702}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + Directory.Build.props = Directory.Build.props + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Samples.Northwind.AspNet", "src\Microsoft.Restier.Samples.Northwind.AspNet\Microsoft.Restier.Samples.Northwind.AspNet.csproj", "{3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Tests.Shared", "src\Microsoft.Restier.Tests.Shared\Microsoft.Restier.Tests.Shared.csproj", "{B75D79EE-D5C0-4E1B-82CB-9505880A2730}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Product", "Product", "{76B4E51F-233E-4DD3-AABF-A6F47788040D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Release|Any CPU.Build.0 = Release|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Release|Any CPU.Build.0 = Release|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Release|Any CPU.Build.0 = Release|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Release|Any CPU.Build.0 = Release|Any CPU + {16DBAD48-C935-4BF1-BC4A-925031AEA0FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16DBAD48-C935-4BF1-BC4A-925031AEA0FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16DBAD48-C935-4BF1-BC4A-925031AEA0FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16DBAD48-C935-4BF1-BC4A-925031AEA0FA}.Release|Any CPU.Build.0 = Release|Any CPU + {EB7010EC-4AD2-4CEB-8757-46447FEC80C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB7010EC-4AD2-4CEB-8757-46447FEC80C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB7010EC-4AD2-4CEB-8757-46447FEC80C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB7010EC-4AD2-4CEB-8757-46447FEC80C7}.Release|Any CPU.Build.0 = Release|Any CPU + {FD305A0A-5680-4C38-9917-84233F35DE3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD305A0A-5680-4C38-9917-84233F35DE3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD305A0A-5680-4C38-9917-84233F35DE3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD305A0A-5680-4C38-9917-84233F35DE3F}.Release|Any CPU.Build.0 = Release|Any CPU + {39D4AC9D-091B-437E-A616-3E9D77804BDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39D4AC9D-091B-437E-A616-3E9D77804BDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39D4AC9D-091B-437E-A616-3E9D77804BDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39D4AC9D-091B-437E-A616-3E9D77804BDB}.Release|Any CPU.Build.0 = Release|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Release|Any CPU.Build.0 = Release|Any CPU + {B75D79EE-D5C0-4E1B-82CB-9505880A2730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B75D79EE-D5C0-4E1B-82CB-9505880A2730}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B75D79EE-D5C0-4E1B-82CB-9505880A2730}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B75D79EE-D5C0-4E1B-82CB-9505880A2730}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D8A3183C-1E9C-4D6C-AC72-4EF938EC9895} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} + {37B52FD3-E72B-406F-8C5A-F146256D7743} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} + {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} + {300B769A-3513-49D0-A035-7DB965C8D2A4} = {D8A3183C-1E9C-4D6C-AC72-4EF938EC9895} + {97E94F97-E73B-4074-8587-AE1B91B4D61E} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} + {8ECF4E97-1816-44AD-AD63-6ACF287ED520} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B} = {37B52FD3-E72B-406F-8C5A-F146256D7743} + {16DBAD48-C935-4BF1-BC4A-925031AEA0FA} = {724F25F3-B47A-4A80-8F7A-08B2E8121D10} + {EB7010EC-4AD2-4CEB-8757-46447FEC80C7} = {724F25F3-B47A-4A80-8F7A-08B2E8121D10} + {FD305A0A-5680-4C38-9917-84233F35DE3F} = {724F25F3-B47A-4A80-8F7A-08B2E8121D10} + {39D4AC9D-091B-437E-A616-3E9D77804BDB} = {724F25F3-B47A-4A80-8F7A-08B2E8121D10} + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2} = {DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F} + {B75D79EE-D5C0-4E1B-82CB-9505880A2730} = {724F25F3-B47A-4A80-8F7A-08B2E8121D10} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5A37189C-A5E1-4871-AF65-8EBF2DA60FE3} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/RESTier/databases/Chinook.bak b/ApiAsAService/RESTier/databases/Chinook.bak new file mode 100644 index 0000000000000000000000000000000000000000..1ee0ec0d177d6d182038359d8c5b8a3d1cb0b5b3 GIT binary patch literal 5697041 zcmeFa3xFL(l{a2}@0~k$9+{a;Chvq9Ac2ICkQXF8LK5;Kgpfdh014zB5(r7mgNLA8 z9wI6)U+AhJpAvLk5ns6Kx`>F%hq}wU>h8M!U3}tem5=>IK2Z7pes#LKtNZqy=}Cej zoSNyb>Z)^2o%+?OM|W5CJ$vr*1;!N3Ev79nIb6ZKvEi59>;9twv}CA&*LO#oi;O9m z&hDFUeBp0{rDm(yV0M`uW;c$_W{>GHE6kbZbn9$Q9YC7W!L%06w!dU~&{l+{UAaCX_I;>be*YX|FZlmmJ`p7t}Aw@tDPy`eKML-cy z1QY>9KoL*`6ahux)sDc5g-cHX236CM@BZJIj(7fEZoSQ^uXgaX{EC1gpa>`eihv@Z z2q*%IfFhs>C<2PWUkL&$RxCK1sy>n9{r_OI(G<)-luNE`oq?HYUhA&RA?B0O%X|C& zzt#L24CbC&Px{SSKfZY3$Nzi&%AHF-cakv7Gh47aU^~_Y>@b&_iDseMYqsI9$ILhD zuu@>1S&w5k)(7;MQxMt!s=a0>bl8Md0~5iu0ikt}u-Z`8E=b#L+**JU-m-+s&`3oM zC<2OrhrqeZPUrJKw5#~}U-)=0qVlf`W^*f=G?w@*Hf?69=`^d1ZR_*Q31+2j&>PV- zuRv3rZx)zy(NLG6c`h(>%^4Kv+UIM%_PIsdXN0u|6oEe{0!vrSTg?1sdhMU>+jstT z!JJ@M)-63#_(rtGlwDmAcE-oqU4Vt2>p5=iFs)6vz7$6Vk&oEW7K8#XZUa)E!ZFr# z-}vosw%Tq5m&VMW9+-_4(^fPOpJOB1%pF0ex!a0w!gM6SD#TwLdE@o3fArx~?)&YS z>tFZd!;ix>$ zGxs0zg{2?*{GZ0J`u&!h zggY?QY8>U@=#T472S3e~J4?0&Au9oK$vIYShrD9pqQ$=fT?Y=zX){k+AHjr+W_*Z^ z>rlcDUq9Yw_OVHnDU{{-`%^!@oOIp!c`r9-VVqiu!RIVqRo^__nJ<>@Q%~>pM3Gf7rPlj6w_{uL_{RAzZg(0vg)gyA! zy4ozaE7UK>d7Zh;^x#f(EAB%#VG6q%eMi0(?4(=~u5F)UrkY9K^$JXX&%#ug=P4LR zeA?BRCeJlzOoIblHeC^X#?0iasbBXnXANYHP-h%u%;j~>jNoe)*r$<~jri2%wwdQYM*e z@pqlM!MxGD#oTV*ZtgN4GM~WTgXVMQAI+D{*YOzi|Ckp{Lohh#4vqI8^(> zfNAdGt!2k4j!Qqzbri;=AM92Dc$`o2@qJoKZx6oX9O{Si=vSXxQxt)h0s-wX@D?%M z)MKD)It(OfhY{nWp;sFMFM|%Fr}$Si!_W@n)z(6^yeR};wYMbkTAXW+|M^`ny!F$oQ3 zxcVb_`j(^;b{LY6&M;y$HKYhA0*XLR1RkjbEqLEu=ly@d{Gpp~!+CJ6{?Bo|35Rpz zpZ{G@Se3!O`i zeM^T^9J6-gdSvS2bNVKvgO5z%!t2N*#5=8I2AG=zo#8Zo8#hLb{Rr56ZSF3A=g*n5 zexu|wH<1qE;WbiwCd=R2FiJf|KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*` z6ahs*5l{pa0YyL&Pz3%;5m;;N?v8>}|AH`3FkF^ZHqUl5iSPfp|DW&w8Fub-Pv4aN z`9Mct{fiP;2m4~C{qm|v<>w03DFTXsBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<2Or zBA^H;0*Zhlpa>`eihv@Z2q*%t9t7ll`9lWp|M^pYy}bV~BAM^}FTrs$j%7HUTX_56 zm(~0KVNNmi6#+#+5l{pa0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5l{pa0YyL&Py`eK zML-cy1QdZ+5dwVwuTBwA1QY>9KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*` z6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9 zKoL*`6ahs*5l{pa0YyL&Pz3(+2m~F_p_lLfmyyix|6huug1~C{&aJ)ul$%_*zJF2u zq=S$$vwN92)XIqpaS)`c2t_~9KoL*`6ahs*5l{pa0YyL&Py`eK zML-cy1QY>9KoL*`6ahuxuLl8s|34V$>w7_XQ7{`2Dw}7!8{zZ)zjJ=r#r5vL)bIa$ z&s|d$0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*` z6oFSd0`~oXGfG)-d*|c#{|xc{e+53@`M2YkjKCoH&iz~PSgQ-y_w&F1cWVpgK$r{H zmn$hVyZfThbVWcBPy`eKML-cy1QY>9KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9 zKoL*`6ahuxm5aa~Z69v5@Bas&jPCV+RNTPs|G#DUu5uIN_%5E`|Nk%!pY+Us`~Dh4 z_2D|pZN+!`Z00>fzip;|$Y%>o%lxxW-WA5rGh577v)$~#-=(JKRp;6OO~q_9Pw?Xl zaQqg-b2wNJ3_pcqELLZ@-(+|sQ7iFpH9P{kH4AWpt2|+*OUSIFzr8%}Gxs0zg{2?* z{GZ0J`u&!hOxwILIrjW~w>3R8<;6`~=ic;&U!VQWE5A9{PaC`GqN}IfyK>P($4uV- z-COV5eEY@EJi7O%ljgo_efd|XY`<#;e_|pqNgFsHaqmTet}~@L!-L|L)yydtl}n_x<>s3D9KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*`6oFR_ z0&7hW;Qi|(?ggfMOB$G?fx))!w;6sOfT`!C<2Or zBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<2OrBA^JovJiOK;Qhb-{=a+wAAkR!@BjJz z{}%X6Jq9KoL*`6ahs*5l{pa z0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5l{pa0YyL&cvT``zyFU{?(xU@`eihv@Z z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eioh!w0lWXd!0-RN6#(3SPbc61e{=U9Gr(TO z`~SnAlebiB+du!L4fJw4g5UkOKL8Mzu0Fo--`_X?lC8e~?*)EMQUnwMML-cy1QY>9 zKoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5qK3KVBh}-ctKxqZ{80N zOWk4SAhdeqjiomDOg#t3H3*D>@7y`pKmR)yuIEoShnsE+;IVKK$xeM?XmI*nv5!v; z1ZJjLZq71`%~CTL{`rP{Q>_+cv2Jn5=X}d|g4t-+n>A*Kxx#EWo6IiZ4CaJP^*I-Y zoa@X6{B6S8gxzKx;x02=A<5=n=v&HZp_JW_u)*v_{#(r+b5&YWv2RIpLP;CpZ%gwx z^v!>I$iE)E8_cE9WhYSCX)##`opv~E%6-e4AL&BQb>Q4&HHz?U?7xgFLTn-#mA+-T zGHpcu+kxsvXpyN;)Bek<(aSA{G1fV2bH25@>E>eKOPkpf+M5uEXGiON%QM+xx zDkgT0&iNtzZj^Yp&=+H3gdCmoE&Um2J!hDOW*OQKThZCLUWS-8W`&t&PBjZ`EbY3% zwMP;9p|D*!-%87BkIgY3_4$6tcl|GZWCniFE)fdMYUHvN_O}c6wj0N0AT$XYyaqPC z!rJ2H_B;vtU1hCvccN%|p$I4fihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv@Z z2q*%IfFhs>C<65nc%*Q7VQS$w!3To%!LZ<4=G|sj@dL%S;wi1mC$s+bdii(1+{K_y6732Ko@`CF|~g4-5?|0*Zhl zpa>`eihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>yy_5GYj!p~ zWY(I%?*DhiUu>>NFu)$(M)?0<<9CO4*lUCRpxe*H)0&{~6yDipk zZG*c8qltyFoaC_uwqaaIp@-Q7>JUU(IsIMMNnu@ArG+pFkTAg3c%J_sVgr0mfafEb05?`VGd}b|;uVvz)-)tQn!vWu z06Xp(-e%9P`EU&nox4s7I~plgm;^`|7_JI(=N23Gi$xm$IxglUON$I7Ktkr0*CjQZ z0ye9e1SljhFM!6K|6s$uCZzGNW6h>oZZJrIgn?P&olm!6pP4lNbu4U}rG+pFkPsKS z9cg1>$KlErQv#F{m}~6$?e_dSdv<+F8CP`fIw@=hQmilukT5W}*z;TM`Br;&LR_cl zu9Lz}u(XJi0IneHOz(V_4f`08#=nk>$(3VPGYOC|FjafL&7QBb=i_mvqyBX6I;q(l zq*!4Rz!fezY`F7zHtcIc8vi=hY_6q61`;44bIa>U8w;C`IwgMz@V8Tvw!XzNV z*4aooueV_zBhvWSF=o=+0452LFu;do;`0_8_L)iJU&q4Eu(S{+0TSXOwNcMz>blDIierJ zF0`}|CIJz4q>Yrb*k3-)xUSV=CIJz~b3O+xyb4%@D=SO_6hVT#E-7p+*sL%KP{{rZ zTjzvHK!n|FBjx-i8_tIrr)32yBLNX6l_6)rGM})+oG=N9u*=~EcJK)BMKFd(FaeH} zRk}mPvyj9s;Aq5J%_KmYApG$&S(Gdbq4R0B-g2Xu5)fhga2*&q-)F=5gh>w#1__8T zp7S{{dUi$`wwMy22omIVNns~~&Dxm+C}jVI$y5UT5;!nn{92vWOacccjFW7OnFK`G zi_jEza;QQC?74{vaE;;_ca6*6#&*V3D@+2U4NS=jkaL3#<7Az~(_J$%7S@F;TTBTs zF)&S5fSjBAAxv(;k)Z@c7|;1#tl3I1S>kfogWp$4#qQd^_6D7U12y9`r%jYQ{l$KQP0j zF0Rd&fS-7{!(`-81-7`UbN+g-mHa7p{J0V7-gspYzs=`Bw+ zzo&U=vuUmjIkThG@kHA{48Ec5R~_dJ`Bc;9rr}NBs9al_TKQ??yBn7@{;B+l^2YLU zZ9~iDLEjqqc*E-&CO7=JbZ4o%b6e-)(&sw=Q2g&fD+WDKeACe14q0CuT%0-ZvBK4b zNrfK<+Xo*y`0v}_5xlYMeFHu)9 zKoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5qM=Ha0j+t*6#m5-c;=S z_EmPn`;(RbJIXETx)d;Q|NoafI6iR8z6S3yGuUBQAr@^~K#wo;d6exEwhxqS3Qlcy zu??#G1{=T?01 zfH^L%<*rzwKs=Eki;1lQHnC(5x8>`UQgRiX%bP{Ps#WoXTgq)2?Ww`;g&K;b4@2#> z$IS?Ei|2E*9O5cw3NG5sQKrR4c--9Y1a9$s?pX6~(+d3C&2zQf!-ZQs;TFs#U!L3X zgYG=LHBVmN+}?$}#q+tjMOR$2x!&eIx;F38pa8dcKKFYaGnS_?wL99}<>h^taEs@2 zH%3dKTFrcm+T_~2#|yW3KKEl`?eZ%WH-Nj#aLWOmm-o@Qgy!P;+dOaU=~=-nFF22Jq?%O7SHFlg!a^Gjsy1~`~3q(c-+U~65QhX+~Zh8>sri-2zN%` zNAS33fB@X$`P{#ZxGUxm5Dh^~8)T(<+$RXPc)~51v(<@fGRHttJM!jwvxMd+;Sza^ z=X1}9@-Cab-2vJ*7gEuAdC$QmxWyB0X=7XfC9YyoYd437*Bs;5$6R1_NaVdhxW)6i10;xR zG~F=U%)A$Y0-B5Gb30yR&?DnwC~}rs^vZpzaEq7C-C$^mL-B2g8t%oyEnYVFNb@K% zA7(xsZaDSwK25mA6K<*RA0RLmMAp0>C#(3OIaEWq@=W}yt7AM>zv$$oKcpzT3 z<_%^H#B^YTtk|EIH`fIsZ}EKY4Pnm2d?h~UGYGsjZETfri|2E1jJR9PR&ci?@0xLJ zwQ!5)bMK6}8*JXRmYUIDwiO5B`P`xTy2gSek-`!?5vEDp0#ku0JnHPcRT`PH#4{g+0~~hZVsN{7SHDfL*(jmbMOSW zcwxCCE*EHms1|50Q$tVlslqLupLaY80x@uR22G(bkDJ3E@)l3H*+crSRKMKpbigg1 z&mE6~#rk=300Xype!1gujI~AWn`AZ5!!zJ<-&(GTu zhNrria~J@(c*4EV@*um=6-^t=wyW4s+e+A(f?GUa^SDnnuyB5H02*88Y0l0R+~WD% zai7`?_j+)neTx@rUSJj&x99?uz{c zL%W$?oA)`wEnYTv$-DzZZ^!o+;{H6$dj=9vZt+5HL>Uv$_T)kDcgT$y3Ax1+ z?uAuy$Futq+BR%0Xt7^^NR*r11-Qiv^S0cNQ-pOzYiq+`YrOADaChMnn2Q&3Ur!p= zpSY5lfQxn@UQ_OKEkz)na2G7MT}T8VJCgS)0rB{$l)cp-Ncah@yKSx<8A z5NbZtk_F;rYu;s+*$LE9BGK2JY(P!CY|Vu``K-sw8=8}wy@@S%6#>unqWNS?7KoRv zdD*O^;ILOq*c#cOxp>08Fon5rvrnw)*N&0A#mmmS!QciuJ8!ZfZ}GD87Vb_w2dk-X zd4LnR=QzwH-M%q~+QB@W_xd$PHsmdyf`zj0VTsh(J8yuFd;Hc9Pj@ zNfVfp4S9lHJeHHR;5QI`kIpsnu}LYb8>SOY_a@ak4!dbE?&Pi zhak8>JmF4jUaoKFWP|47g?ZPsF}4!kNw(XpK`E>5B-6ZNEAr237vH^4PBsDz}3Agm;5w|oN4ng?* zdE?Q499gu2*h$iDZnmC1(U#g#U+w9D~6#Va(lf80Ctq zo41{?2zN5?T-S>*Y;u*qV4@xvwW}sQ7cO&$WKO^_td`TTSaDEe{X+NZXAq3tK8Jk2l}aJZ9)6 zLw??NMss;kNAnMcesaJO?Z*xHR?|D0&TSgj^u5aaDi>9HD*rTmb;pzK+uF_=KB?{W z&eKNxsPW#$&5h$4e_8%$d3$+s`I&}KH(cIu!l;Kv&TM#k!1JY#559iT7fP=$%_$X1 zj}}kr`fS%t#p#1jE&goC(L>^ z1HL-q-#bPG-#2%gwI(RQgdTC|H?Tx-fmwj_o!wjPbbwIN7;nb$797qU8? z-vD3zXA4uO2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<2OrBA^H; z0*Zhlpa>`eF9`y8|IfuP1^51+8~o@B_WeJ<|L+z&xcC2d&4Un`Gj9K`tXvDPv%QP5 zqcdWIXthB`Vx1`c{(l`{G+Gf*1QY>9KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9 zKoL*`6ahs*5l{pa0Y%_dgMj`1zuo`uw&y4J|L^U7ll?@CzyJTe9y(vS;n!Qe%gi7? z5rfYr;CnH1&0e#|>@f4pF0;w3LwJ+fh%eafFdJ~?zMq#VLn9RdML-cy1QY>9KoL*` z6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5l{pa0Y#uU1l<0A_x`_j|G)e3 z9Jl}fZJxNLxBr*_a{yk7AMDTmfA=#%K7a3F-T&V^KANfsC<2OrBA^H;0*Zhlpa>`e zihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv^U%16NN{|`$1+`pNE;s)jfGu8UL z4Oav_SvXB$eqjN@sN9UJ;^;C1!}P4YiHMJy%Nx;_n`xSNp5!@0EPvymX_S={_d4Ia*=x#uB4(zCePr2J+z#)*Mn#jyy7q-Sxr zB9(`8>H1!Y1WC{09)MIH&ZW86AVJczxMBBfwUJA6UmT@paknFthjVG}9Y~P$EN&V` zXdG5R8uKfn^epayNaf*N+FGtgf~040cOsRCb7}4ykRa(<+=GzH!@0EPZ$W~jXL0lQ zS$H_N&qJY#$QNI9=~>+DJ9s#kwzb=lAn94$TpPy2xwPi*M}nkhadTwh;apntKS6Uz z&*B~iHXhEUHUA(IBt46JI8u2y_Z0fyO4!3!5&1{tE$La@BS68!xu?MCxEc2ca7%iU z`!uA*V@ZjLaBdWIqd^JdkR)YslZuDqc9@g4ii5mNPme^BH^&JcX>P_ zgm`;|n!CJ7U&X1o1KEZ^ji}Ud)Bt zVeYtHx%53%A2O&CU2Kj)CZ}voIHKhk2SC zjF!6|=4T*1#+=l2c`+Amhq>^YXztJ#=EChTPjmN$xo{It!BqIiI5F_6IO^HCa68N$ zld~3HS8ua*geyA7?aEzmrYYPGwKO;5t2ipinc=jx2)9Em&FxTYMgPEX zHg^f}JRJ8^bGoBmv5I((L`+X}ud*>LACHYV=<=3ZxLvu^+>}(s!I@rGxrN)6yB;^^ zOIhU>?h;aYIBsX>h%!|i9XObtUG4_N^Kjg*+@!DKsMjk>-mZl@?gs#&8;QtWuZ0RX zXU{wwcRFv5-AvEc+|95ZcRFv5fLSwa$=l7a>v4|+ch(GBxSNs6<6azeY0XC=j_KK& zw;-N}<4$Yd6Q*Z#yBW6Q_U#;u*3PqL*sGBix9^=Kg>#zs)xH;lHZVkmhjVFL`xFu+ zo$}hEJLt1;73b}^^X4twu8pO+8DGUwuWu1<*T&M^ysqM?*T#gq1PUI`-7kh^FRM7$ zYh%Lg+L+^Z<>qx2$1ohEN!PbfGoxcYt(j^Q+F!dyM%Zi&ZX^~ z)*|UiJ3klk{V6x;<5nWv>2f=3apjKfJc~PBZhL3(u<2$=syH|bO1ZPR)8!sRw$N4K z$K}rA?#s?;sbc3@+-W<13J6!8Fw?{A4e!Uq&Xe3LK~Rgiv-8@1jq+m5g}WZ+q^;s$ zxy9BHsNqgyzK=xKVG05lW6rBM@8!6v)qEcf*rvD|d+|kyz}xq-`ypVP|ot>wAm{vF9qzT{xt^licSczCWmuK1NNr)2OA(9ix`T zokne+*B4ZgTim|0xYMZl++emXlr+Xwc)>6hN+PR>X#hq>?X>MXJtt5*(-AdBj z?9Zi@WO1ikiH|w;XZ=en$>MhNb5hdTIkML?jYFKw&$GDQ{M@qQv~Q>Xq)(o&$^1Nv z+s)4%ce;&@M*?ZGYm7C*!?`r~6eLJ`GVf)G?~k36KDKk=PTTqYQb=pBRnW(Chb->2 zou_N86B49%$l^}hdD>crfLrW5i#u)SY19q@x7fMkMua*=;1!F&#`YuHf8O?ywry>b z+MXWp$pL!?95>*(*3Y+I(|U5NX?>*S#+I*+xOv2BBU(rN)9^cnpEbN|_;-iBd)NiT zMh*LH*K4~@>iW~5hX-9hX#Sw`pvOAj+?@@`jlW&zHVX zdVOh5sZe^fcvJDzVsr5uh1&|t3!Q~;2X_YN1tWs*o4d_g6BN+tUGFNfJq#4>xN=$d zt@hrSGo$lxj7GrU&Ht~LZs3E%US0XHhpTst|4#K}PHh4Xc9Re&*evbkBC-bq&~q{y zcJ0F%VY|#8gz$)@6h6z5(TaU6_3ZG7*(>(o@Q9_0XF{5oonQ|R8Px1MWhy7VW13j{ zJf_a7n4z}!l^20bNE6FYo*-snWQvkn;I2xw!LIRni#`wNE|^o&k{2sP0Z^an;J=8VlSBx6Z3JU4F5eg6zR+-?qZy2 zVp<}J3vms~uSTSEmKs9r)Q}qjd;iE8vD}BGB|9}d z`zpo6Vn|Gg(GX%*N)L{Pn3(VU(v>2_%#p-p^8jUWvCUw+aDfSF$#JFd*1!@= zUzhB@4nvv`T~vcv_AJB>x<2e#h#hoZ&!1(_LhPW!%ShLK$uN~!HSGN!ouO1u)GrbVrV(>O8-F*qMt8TBQG~2mKvilNZ z)~dLulaY=wS=~Y$S9gRn+ex~m2ytB95zYiG-F*phT-{Ltv#MK&Gpbv9q-1pqaT9Xv zVYab7^DZx4-9j8!ccilzk{jqPr&xe{TYpV=kBxOJIKvD34%-YXnupEz&9AF7gPFLq zq!@8x030RQGZ-^Hn2y>dz~Wq3s<}2@W}mb}LTAiwa}|!Ac(c6ATxPZ+yc_2oW;=Yh z9&HXn0XnP6<}CYcpmR=$OsE5&7&v`En&GbLK2+3ArLk{hS)Vk|o965KD(mE6GIFFf ziYtcYZVvrgENDv{oy32N;6V@f6u4xcZt)qHn6ATO7s@Y)>;V>=ED~a`b096igfg&L zuzwpg$Mo=70tFuzrgEJSi_2Q!@c<6G|FGw=b~P=7GjPPPI4n%i6^ywqUDk3)*%|t2 ztO|Bg&$6=3qhGDD8^NFyVv)wm*;rd>Yw7f2_*@6KXI8#owGrqLsqJ{rO?#1sItQTu z&pc@_q(+VP9B<4rrU1c;?^cPd#~bN`D1 ztq@7qZ4la=o0?JcDIxmVCE^oFW9HxpuvW9SJgy&nY06U!F=`F{Jms0%Yk6c`mhud< zSt_`H+{9v)lb#jXFgL+w@oM0jZSIKJ02pFeyPgPuZa;}Z?g0_S?BaP_;`hVNKcqi=&?n_R+CKQmE7>v z##=o{7w`mkXV?es4o9fXxMsUze#GS_9K=QZ!5F1`&yUYi=-!AUrgXSGWFEuqNpe=4 zZdd7c=VppB7s3-a400hs}^M@YRqcO=SUoGuLu6=_Lats?Tfup$<`nBU{8Ocl~)LT zK6dJ@+^s=dn9OJUF{RVvZmg4hiY9<0cUR(id@xv7Pftme9)F6bV~<&bB;F^9&$VE2 z&cfxN*I}d+w-#PZX?1*G!8~LhHTw$t%sC+yJ$L8iqI%ac>u$tx(wCX&R9gml&SiT1 zbYqs`GS)1a#;X;zQ8vbwA<-^3qei#F!g#;JcFFu`U*cRI%aEH3yENZl5#H(eaSXe9 z?T>vq8EFg)VLPmsUbORZms*Rlo0kYhF{7V|d9&^|l1;YuZp4k-PJUW{*T1-Qneh^H zqEw6d2;9~5hWTh&=;teK!w&O9f^k7#ePLS1bYGZGFNSx^d>BNRPIq_BuCGj|H-vR0 zcPDrfP<#Mw2W?$$uOMA;keOJzuXS@Vn{|Xu{<)a&xwFHm|6{tFizW5QKNoWvh`;}O zxVczTkNk77Y&{PC+``Spl6vN!i)HKC|9W$Cv7{dP=VIA<9DKa8@8@2Mx!4byKGAez z)5@l!n*LaMqVmDY<(0*iQI+2|KGOKk#x0E}HnuhXu>9%r8_O4zCzeh5UmNagxT@jw zhVF)EN?$3xyL4&kPl|tEd~@-_;^bnn_}#+a7Op8QD;!q%UGP}&-e7w$7f`eihv@Z2q*%IfFhs>C<2OrBA^H; z0*Zhlpa>`eihv@Z2q*%IfFhs>yb2Jw+FWe{Zo??JxAPZ-r7qa_|7G)RH`hHdm8*+R zMd0I(6xXNSegl^d_u>362J}uUtJl2@dMCH3`u0BuNJ^pzC<2OrBA^H;0*Zhlpa>`e zihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv^UibmiexBveU_s;#Ou)O@5pD}B? z-*10Skg0C}Kff{L+~ChV`ArwD=TA0AMf?9LXW^oHlofu&b~b)w>nwb#ZK;_H|NMHi ze!Q*#@X^q)BA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv@Z z2q*%Iz^e~|hxz`$&i;SC|L6XH{vIsf+q?b$Uvx_a5ci?>1qXTmKUWuc8Q`ifr*HrJ zLP*mU0YyL&Py`eKML-cy1QY>9KoL*`6ahs*5l{pa0YyL&Py`eKML-cy1QY>9KoL*` zUdagj+B|Hc{r}gycka)HMJ||O2=$D5x@$Cire2EU=Q#Mn|Cj9?dm72QqzM#VCJ9%M39R^^19yZ z$CL81sh?|Y{oLo(&o4Y1C3 z!TK*VJ!T==(YCM^EJb*OP2UZg&1fY(W`#M^oNoP1NMT!K8(NBZNs;z=EZW7SFlH*k zlhJ<8GE)s) zy`u3O?|=N^bJ*Mc)%Az(X1#R=pAP1kEp{~8fmU*VSkf!jrR=%BRBgumABTCXH&_ zClnnaVal87=u`3L0hdoYA$k4ujm?QUBj$;Jwe8N;nZr`%j()q#!X&HR{dLyaE4SU< z^Ji{%95Bb>7;COM{^xhS@YY+GzWtHM7LR+!)1z0M_5t%hcNtT+-6fk>?kl#tZadz6 zGSzp!RDI@D%qZh|<_fa_Gs<)A*`I&EP1)soYSw5u?~+E{+wgo2Eh9ij`euOk)P+R`~UNx zq%?|vBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv@Z2q*%t zWCY^<|E`oLn7>0Xz-}xoMmP5iI%OnQ|JwKe-2Y#}yZ>qMojduuU%k_X>-innrE7QP zZVIPh@XGZA*Kia;w4*#wcr&GPU_bCaWr zqXR1(m@XQo^X9%X9&b!(PWmd2E*wnH=59bdk84BjwC4Pc1*T_nml4mS9(PZep3Tio zZ#*3LbcCI&A}|JrY>dl7jSBOq$9+VYo}D+>jPSU=PPxa0>Dk=ex5>lhoks2GFg=^Q z1*tshaZe7@v$<(YJREl#HC{74n|lD_c{uKUsl9~UnaK2PZrC1e-+W%o5j)S5?zk{L z%^h11YjP`3aCzI9aJx44(~z4eIafu1WzK42!tL6au|G$k|vwmwK2z?F86rwktUnlwK2z?ww4Lt z%W7j%Zr8>fcRKH5z@62`gxj^TdfZdMO`2@Y+Y!&B9yiBGre|}rGvMKB%_3dL$o@2xGGh&$c&3=Q2)7&pNx!F4~J-ghr86FRWnx}1zy@S{l3E~zi zdAq*l2O;JIzg7%j#Q%+x0E=@}33mtiDCKUEgvasObFq=Jf&`S$#{41&b`QlH66q z(^3Qyp5yu!ZfEC6vMy~cq{=h_w4L8Cg|w2ZI1_Ja-&x#g zJ5SdbM`y9~Ebg?Or)_NrxW&%1xYKr?MvZIz#mh;zr&04Y zuZH;wYFXTA)Y9DX=%2-%M$N~ZdB>xF7Izx8_2BRwdnQ3Gi#y#r?2B0EbTi1FNqUDQ z_u-Z&UGjGA1^fBhbT|gWU=hEe7-jbfhT?88s=hED4-;$oioyOePoD%;4Zn4xX z?lk7ka;Ix7&CPL4YAj3hzG`e3xTVIjxcjOxj)GESS=@cq zSP!_R#e@=F*sa$Eo$uDq=!QQPU=y@Rd`3^aOt(&=`l2oIj4PY zc&X)1kD+O9UdtGoRqpf{nlATA;Fd8oi<>W5P|@Je*5&--ZN9&*C1AR36Taae>f(H+)IY;vRui z9?qrj4L*znNzdX=f0a9}`NzR6U+vD~X6xtST$=myNRad_?$Jo);ar;gQ6xxu7WW}Y z<>6c!wSPi_q-Sw=BbA49>AZh{1WC{0?m;RK=dN`D_*EP~$I*ksm@Mu?5zoW9wB}DE zLDG}l93A`9w~#)zRN+qdEop0s`<5*3-ujj*a$^gXz9oyhgj62RJ%v8d^$t};#(hf` zH~S49&iSZS5!r=9(v#eLj?f>}NKe#^`Pk^*{(d$j{yS?p|4Jj{KVu$gxv^zoOQq%U z=3AQ2XzpnKR?|D0&TSgj^u5aaDi>9HDnDwxw{dghxW-?WKU&^io?L#W;nNM5H_U8! zzVwCC>q~P=h0>$Nn~J9vn~UEl+*Vj#=q!9YxHC8}7!iEm+-=sHAV6;@!vHky6xcB? zum-mczoXm(A0%c24hG_J)|mgg=l@P*bZ@Q$GATa5w`l#Z3ip^-+uzIiD{1cAlCQQp z`Ad{n`YyJMQ_wvWQtiThukJ6Q*C<2OrBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<2N=Jp`^S^8G*FyA=3_pYBlc z@XPS45UO;%u7fd5<$8ui2;2hSxy$AbUg^U1{0_+EYAedH2Ui$yX@xoC44%1=tU@9# zRE>!EF31vHa29DIBChI;h`3C{5-m>>arsC@#JsbJxJNP~;-ZEuB5uZrh`1smi)fHf z#HD~)L|r}+SNvrWaUVjI9r4H_;$ohNi0gB+h`0tVBH}XKEFvx?i-@pRC6kCN#v&rZ zpCyS4!y+QWpGCy=Ul9@E&m!V7uZW27XAyBVS42ekvxvB8DqMkZLm-$45KTFbfpXjhUL_2(kIz)}=2-p{JfC{*UKTD^hLn6DRZF(J|i9XS>b%-YU zM8`Rz)02bZF+R}@N0jO?Ci_G)>kv)xiH@&BG}R|Mp$^eBpXkIoMALnulj;y1>l1Ma zX%^zg`9vqzA)4V6&8|Z<(U`ZXKQ+=Y->kuvWiIzH|C1}T{>A9l z)gjvB6Rii)TpVdI_xePfQm2U~<94zy8;35(n={P0W}Vq(m*CPTV_zl>#tCMMCGE?w zK{^qCLfn^ugLsCSZ#J3DP-(B(X7<=T`Z9T|&SXp4m*IP)eVM;U+E*h0DT_GMs?b-} zfOwiM;5M@xMOCK=C<2OrBA^H;0*Zhlpa>`eihv@Z2)xV@_~?K)4mhpj&28%k%o;Gd z-Z{=50H<_|W%zWMy->CNrU zzij$S(>+aBV;}#Nrq-sXDvwm|s$5xFQJGX}s{Ew!i;eGT+}pUkaYAFc@xGR?w_Mlq z!}1r(?<(&qFD;KN7t23r_*}y~8g?|C)-blgH2i1jGo`ndwv`r@4lDh!`0vF}6>lqU zEzU1?7oRVDr||K@TMC;Ca|@#izYo3@+#lQ=YzR&ch6n%G{ImIpd4pMNf@T0+z!Meg z_>haPVBi0j&9mKy;fkph9K#UclR)Rbcl!lzaN+ts9|+}h0Up0dZu#g*HIwfU)hPms zfFhs>C<2OrBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`euTlhV zF*Dm9;`{$X39tL@Mk64>jmvbh|Nr}gS2rDwE585dMw<8FxEY6Y7u|U`_y6_kxNFG@ z2)Mb)ZhqpH{5TrfOEfpU$I%TgntRUUX!2Si++`j|M;p+%uRMd>cFB{GM;j!#!#|(Fb$I;Pl8$uaJw>crh645N2mCb$LF*OTkqemp7qfPVN z%N;b{yU1qPasmj%ML3HP=(b(JY*e?ny+ma5kC?HezZP&PH=r zP8`j`*=X+gh@)9J8@)LZ&BEE}ixbh&*7Plj=x8(g)kX#Uz&*San#!q z(JY)5a#=sk(( z@h*CABKjy7eR(2!f{VT)5q-3azA_O#(M4aCh@Rx4A4^0Zo0{Y3xh|U9 znB(ZE!{f%~IQkSv&8^CD^gI{M&B<|eWU<_i97oT0)ZBm^M=x;E+;SX8FLcp!646or z&27ao^&&@oN+SAH7tO81G4)~>&CSAb^l2`7VIq2oi{^&lnEG@V%`L!j^imhiO}}w8 z_h#Cf=Qdv(Z4G%DqUYi;+}ImOFL%+5ild`#y4<{LqpjIT8*{mpF^=W}yt$Ok4Z3kO zwXo6LzZgfK75#gmLr*E}A0=x8%6x5!4(pbfXbatmxS+HQE|rq?Js-r~xQuW@uVM02Za935?A<>uBnn(Gv8 zU2;2X9KFs(a|3G}y&lnXaTsn{jiWj6*=X*BRHq0i0*Zhlpa>`eihv@Z2q*%IfFhs> zC<2OrBA^H;0*Zhl@YjdHRgLQ#mo#2qTF^A9X=sya`bFjP%9_fll_`}Gm15=Bjo)c} zsPRLMcQj6K9F2Yazb*fJ`Ag-Gl;2+7R6e6Tquf+}rs2_s`y1|TxVB+S!?#WxhU z7grYN6vr1ki!T&@Qut=!GllmTZZ5p0a6w^S;poEP!k>btf+vE{1s@3B66^^s3>F5* z1jB+L_@((*^98fd+-9yYYs3BjeBnUHXZLjR{(ru{9KoL*`6ahs*5l{pa0YyL& zPy`eKML-cy1QY>9KoNLlBJfdjqp|z{gOd0Ce{MXd+iAEj?_&+ShI7{^-^g=cJGUF( zio>}dzU>>_kKC)9Y&r~gxl_hU>|JbN@JM8^3%~!z-vHcYF2|W<%v{Iu42$Z!P`%!I!IUm2ru@1kp`C4o)orqOB7n-Z_o0~`DS2tgWRyi@1IvFv?Cuqi- zqihr7ClwNc4@E=Yiep1auq91!JY57uxF$-nB^PmlG0_M_Z{AY_^w$iSrkR zEqgek*_QbQ1#&IM7Gqmbm%wi*BoamtCq`~zwbH~iZP$*$btFNXHFze zj7G?FBw9U2jc40go{8Qm7Lsi8JZPVkS0+RT8$_oR7U zJ@5+>iF%j~u9+y}bSTCmPY3@DXhJ#11L$LsejJWttz3R-BEdcZ3{yck9R~$4ETsJU zM1u5C5K!K!C=!Jq4jq{ASeS6St1*|rFH(@BCV}6i$V}i@DVVST zo9eCMjm39zlE2s6VMs)nBcklFj-Rqf$U5Adf>w61S&TNpU;gbdBuPjTlI`{vi?51E z_`!iFv(Z*c%#X1>PVDx6=UBv}6mjyA`1Rp+$a@oZ!EZH}g*_IvDEnk%?T-|1uq5ly zxj9w&35Q6JL(M`Y?XdD30ap-u1dEZyIuuyOerCh3R73=$Q%E%XiHDk{5WXF}F%Ddb z7;ztppLU*SwpgfK{`?w1g5YfMU+IC(DNaOiWX6v}3utXVS+_Y0QhXdZ*@>h~!S5xp zn4H7ZLzmN@h~Y?&A!!Pn_C#b8JhECSIQ5B0$A-Ugy%lQsrp1X*MAVHsPlCY-Pn0}9 zGKt;5YaNQTGpct^c_PBGmT)_&C8<0oJQ2}oa}F9#Y^0p#M2RzyxE;pz8vF+KCP-lm zquN{Xv(Z}-=8tqsJ#g9+v5gN)$wriJA)NR`gbt3hb#U4f5sk*@t}Y90n_ri~f1D@{ z!2gx*4nv|uTkVGVh=o#JCMVkI1eEMzD7wSW5#|9>PK6z5qSa1Ikc!k5z<(Xa?3g9d zl9Mb;Y{job&0Gbxm?hDwCnj=P2^3jFTOySbt$Tv)WgN^aY$>EVS3@vr^kc844nL|3lpO0&&9zwOmjG8n2hPi6g%EuXvUjs!q_-&6)0r#<5IKIuExmm zx{yCReVR=l4_T6POeJ!sK#$4LWGW^^oqOp7w)rdE+Vm(c528yEzD~i zCSoG**py@`*Er0)CV4qZEIBw#GsoEocNoH)m=P?%U@U)}YfVL=rolfOp=rqSSft5$ zlBJTF1BKAE7rXIw0a!TWng(_vB9SQR@6F+66;jxsVl-WlZ+AiWH~`$VHl=qy+;DN|=A zBy>*B{wKleIsX?8Nu1E|7-;CbKhJNWH2SG}a8)`V!BV#c;OZiqS3YUmFp z)bI4Pe&_2Tf<9t@XALp_1U7;HtV|oJ9pYIlOVhRTC9RbMt`*KnSD6WrbD?d6ZjPFq z`^p?Oj|H%U<1;Kbnc(#W;iQ5Un5cbD9&3Au+Sw*fa(yer-a5_O7zBA^H;0xy3Ao*g!N*ygJ1@k8kd1{y$CMX!>7G zuWee~bW~HZ=|`0>RqmEd zd_j3+xlsOT!&e*jHC*4Yvf-$P|1N#3^!CzgOY2JWOD&~e7T;RDq`07XNb&cDCklUC zxW2HeFugFK@U!5H!MlUk1g8au2Y)c%F&{H;G;72C{~QPD;{E@8|6exGb{_$sseG!( z{r@vK7YN`eihv@Z2q*%IfFhs> zC<2OrBA^H;0*Zhlpa>`eihv@Z2q*%IfFhs>C<3oW1ZwyHb15F(AS^62X5#4k3rE7| z8+k0!H}ApW@Be>x%`+z>s#n)xMj8GRH)U{l5FI@pjb;v$`TNGaPcMF_}@^_(+#Z<}WALNfvrND*}puBA^H;0*Zhlpa>`e zihv@Z2q*%IfFhs>C<2OrBA^H;0*Zhlpa>`eihv^UDnsCZg4+r^3J(T%23G{<1;+;? zf)~v9%|DvE&9&y+!CKQrd{Ahj_0Pm-ZH!seG<3)~doAz(Z$g;g zmUZrRBf6h-;d(wlNyLwb$~)a_uSKAODUasK>{-^`gfl?=Z`Ecr7s zc9Q>Gd|`;6$lZe??Dhok1Fh4i<5b{R5aFo2_bmlnxdLp)r(u)1myvq^{Wvi|#}c`) zjR=|NVHw25crCvb1)v^r0aypQM9j4!b^(bU2?4CBTmlxSbl``FFY`3vXP$G_1~u7W zVce9c3syis1Z=dmvCh+gpNh^UfJND0KhYciQiWKB*e&aDKdnLp&;W8-#avsd+hCc_ zGDH9k;kUoViS|@^!c1p`wJrMAIkV#TdgZI$6YbYLp2%(8)^;1h=+U(@`1?sJ6=vSFFk!gu2*3DDal=@{_!dz1) zR4z-8RrZT){1KR~_Jh`IaOQ`z_t>i506(o)O-9Istzb^xA}W#LjV3hFHIWh-miHxrVL04^%%xGI6cM?58G^UV!(P~F zi@Ct7?WFjf|9;qERuM!*?m9?TpfK0;EA=2umtWX(*8p2Uvi?OtZv1NjwEsBt`-nXP%Kj*!e>&_KrmE$mDj)g^>m)qtT*p5--1QvkgdW z_iBObt8E5zHlU8FZmeyx*@cjz4jeOWYz`M2`CumoDb8ykeH1 zVEo0L&2aRy=#ygR9?}E{nrZPFZ;N=QW3_l zMd!lm%o@#T8XIa*yaQ3Zy~pLanB&fM27|C2W~BUJ7H|+=U)=Q-(NxvpQEb z@CKOoYL_Nv)PflIS>)1bUL+)4tR9HS^+<#hBhlk>Kpl@|hvqHF$_SSjWGB+MEDnxh zNV!JZ1-OG`SIFvTdz5EyB7_sBT!lLx#wgEsgpszKi*Yvc+l#iAWbemhNtE9XA@Zn9 z^5&dJigeK@`NB2I7wVvZ-uu`jTVP=1X~G%+E7 zVqm%Nxw2;cB0?vvz`p+vj|DuPw8kBcPFmTLb<(Pn)|$BqJBwWN0G+e~DV?;kBXLgV z=Q?S%Qz8cne8DBpq}7;ZO`T0&uiR8wP$^fw(pYVr*LZT*9|wJY(B45u4Em4Gn>$bJ z{LR1*4qQEOz`!qbZ0#7;@h|Pw_7mHmYx_jouC{4y&kT5Az^(xk2K=mbU+d=9v8_LA z`P-JwEn{1L(!8&EOY_*~EtSJ7KWeN}nt3Da|PTq-kH%`lhi>KdF4Ea$e&f z%O5T;Eq9c^((tXpZy$Wt;6a1G*Y(b>XNnIN_Y|iUs|~-zZvkv6bQgXQyf?Tg7#)1i z+-24p_x^t#%F6fu6Rf}9zW@JN!)?RI+iPQ1!ha0&z&-Gtd*JV%aa-G6RE^Jp`!pNN zNo206{Or^uUf6R_AZKF=zX>QjfG43#4y-7YeZ)7p!kD`z z(l@!n&N@gu!C*PiYkE%l&{>Hm7s8wy{o2ls8uZs2O) z35kH*0P<~syj0qOX9bs}^dOLS_U0a(QLYQy!iIy<@#uu*@rLAoz#!6eb3Nu@-^hpP ze83XFj}R?*kD5ye`$j$%z6>=YKS(|iKcY^@!sZe&7clZ|L@eMOD^%EL7B9lzW>+TXOG5+jo#9 z)o0s(-~pV7pTnHt*u|E96&mesG#dBY^ZW&3I=20sz3_G`f@^%_x9FV+w*6ds%(81D zIJu(DN&bA?nTuKGhOC9lJ@)3j>ese4$-gCU{v(;%~~ey*KR zZVn&yvh@6sE~&X(=+8wjvjh0Yb7K*Zy9J!+^>M;V>$k{LhBF_AW9LSHE@g4mCd{<_ zoU0$@T|vC4Rs_)c6CFl9=(v<*Ga8Pph~>A<`T^S_J512#S%7%;p@_*19v2tyf*us& z8nKAT-Gy+47+-H}fEmU8kO(1|IjtZKrU=O0l`Mb>u@F0;2iNEBvQ0^Z$MsXZluC*mGBDJd~V^9Q$EX=b$2GDFI*N%j!!v6N%S5iqPEJ z$dXR!Se;PU*^4L|ngy0`f+`Q2ZbmR1J87-UGW2uf2Rj}SN0ZAn^%e_pVCG?xMV)B$uGxzST1~D#FWxiZuWC5o=ECtQSFz#& zMi_JV8M6RtJW~J>Kk6VcpxdreCfRsv^x3P(;wT-ayj9pP5wEaoN5 zge=ihk|FECM(u6A2w+3YB_O#@QkdDuaxtHXR_d1Z2`hC>R1>ZhefqS7`Aff_Eml+9RKj$tpm$ zM(OB91n*CBiQtruy_6f*MaVL%=zdtdFtW&rcbYU>0uwuFvNT#(eZpGPd5|SB@`v!H zt54WT*>A%UR$6>6$kM3WzL-U}z0}hPxG4+6v6CXtGH0O2bmQs+UmcEoYk;OMHHC&H zEiz{tHaO11XgP~fbA!N50X(cBi_HC0=WIWh zojFK%y*R_M6MSZ)h(`=Um7ul^wEmmFi77(xmi6YgryCwKyJ3C}PFxUZoHiZzg7!+cmjO9kwHx6me z`WTL#!~1m9J39=&?-Au_g|_4BOUNR#5G9jU*tKG4bXLqs2)O=_;n;~T>GmyJP z^79R!Yp6CH-|$@N!P2J&?HV*?(9@j{c5d%Hrt_JBONMt0|CeER4!dv|R|z~<{9>_M zoLzjj@TtP~!ZC%Xg9n1m!QsIV%=^vS=>7i$fXny)ldQkizW=`?IHdb1du`Bn8S`@- zV{u%x04KQ0iC_M)gzBGT6RWvhvLX_kt_7T8t%Ely^I>dkO0k%lo2GhV7MN45m`xc% zDqylEb_)W|#2Aj9=)(STBmUS!xkbfdW^B7w#Bv0j2!>-PB4(k7Vsn-o5n<*8CKvN; zvjVJX3^=RH#XisWj`HZhO$}?>kMd^$EMD!VaU>N7z?pV19#pRq_km7Qdr;Y65$v(SzHA>pA zu5=zIB{IL}bZeI#txiO?iLO-SMqTOhP%Ug-Eig2s7JzE?ZAG6=9EBdS7{G zH5CyNwe%~0Kib`5JZ4@DYrGu7WFAC8kE6*YTAfIxNhMSy zO_qv_(TCNxWp)c9h{)u+cUzKD&L65oRMh(VYC2~cj(9TvELf!Hw%|o6*{*qs7}{^HhA=O6U&}Xsx+4SzykD6>#9;!w5H1`qdY{iDe)JDj|eYYuYuQXYht+Ioi-Vj9%_l z8j!`F0dtu)2>@&7do_ss~bB_+_NE=eb5v((IiD8G#U z%PSF9gMLyFuSD1)2#dc=7Qb_O@RGlodC+tcVF#?!Z*IHw_=BdC2s=nR)!1h}xEBEK z+V?-A3+*mpN%e#*fslH(bmXV5JK>;VB*G3DBN0Q4a@qGeC>{s2E9`r_dUn3{-}at( z&?f^)gpNr$!oUF$kL>F1OAxH}UeHOf;+R(HP?eIg%k@~zJr^x1s;`x=Dh zDV_+cspfjZmL-I(H_ISUM9P{m5l1x=Ls30(N!@lsOHL;_UdCu~P|EcxnM)w}5RBub z|1x?0mW8wX0s1n#ew0ry_^~$%yb|Xaei29b?fo`4jk}3a)^fx%6XJH`ic*7-uP6zqjHWAE~#(XC4Ugn5!R>+nuI7tfW@q8dS(;oS6;ZeDXa8b?2f1d5yU@l@+TiS5~X=n2V3d*FY?v8uC+i zynl7#eTZIn%L#M(QH2Wj&7bI1=z1m-XhIjQ?|AcSVc*YouaN`&&hIM*(W z#fhr$*TIV*-V^6CmzAMq5-norKI*f^%Ydy(#IiTdwY2dZkKLKQOtpD~Rz^TE2R^~imEE!$BVak>C6OWy zHkwZyI|KK4r`EnULNISjohiid@Dp9R^j*_Sjno&2is#uQxa6?S=lejc#yW;q~;cFM3FR(Z0R#Ge5NB-q7ZPtz3|t?^#M7 z!FYh4Pw=XQjl`ns!!bOs=xm+q3+9&=a)H57K5mzs-HScQUc=t-#?#q+S#Z>HeP!Pn zKx(G@$3x=33l0emxo`B+(a(%}*QgUl{nyCrM;<=%krBH`lt#RD_+i7pHf-at7lz(9 zblK4I&`%7xWJvpvhX-FhxM%Q#UFUT*c71lxYX*%O^c{Q~U|wfu=hp|me&Do$zv=i; z#|0e?9S^m?ru~TaAGW=xZE0J%?Q{4xz}NwMS|+soruok1Gn<>6zt&W3I;`o(m0Kz& zR({j?!N#*18yg=gZz+!~f4|{>HLPzK-tgVhyGj?7hL*lvyuEm8@#(@R3YQmV6n+!@ zU9c@UCiu1akXeiO|0rM~u zQ76F8Q+8EE_W?a2DI;81qGXb>7`46yy}@Oewz;Q{auX(kIDNExju@zbhk_W6oe1J0 z*m$ZV%+aLLeG`k!S+_T`Xj^Uzs9JW-bBo;$LCpY$Ne( zhN|1jp>LflxBskBm`Q8rEI;1lM4)r2&rcb0rpS3A?*rVLa}mROh&PUf zmBNy zLBc(dof`xQAtvEEXc~>kgs2QM4gxaq5<~^zIH(NzJ5d~E)KSJkMF;sAbqqQlMGkr# z2jPF;TD7XW>#NS!*?8pq&y(6u?dq?(-c_qsty*>MZsC9faKk}8t>kNsug>r=VAvak zN(pd~DkWg5RH>=RLbL>q*Es3OmR-n{R;aldGCH9Y60T8mBtpk>>9vWl3+ed>mIutO z*U+Ga0F|bc4u+?FsYF78N>qwOT#bc@JEu@@JTS1PptbQUUkMBN9rt4wo4R#STDX(_0hnJk(d1^W`A*p3@zC@ z5TufM@v??Wk&+q`j`%Q^CqXMxibS3pt{2H(l_y1OQc~gMVf0lfkhB(&3@u4X0|*9{SQZ=CDL%QJDluW zC9cG96&(wfbK9ox+k?cf!^06g{eK7?ncfJ;kgoTNo2$>jPLsE#uRMU&gWo5=hvU5m z;TX0J-%&je>3)z&$N2vV$M8`&hUw;;tG6NDXZQoIf9dw>3CSJF9dPUlz68hkCy<8W zr{S6Jl9L`x)^yyH+ynPh-1{du-hUMMwllX^|3@a>c^yAVegen)J8_@ka|ov)-NU$6 zdW>f|X8*spR$V!e<~;a^coUBK&h1!{tbkko?5$NS%$OEHS|$NP_Q#U0C+UIv<&SQy zk|yszch$|+`;q2;Tx-6kwcnN81;_i}hGUo_&6DsS!?mVk`~c!czIl7~F*yDvt>44n z#~*OacX9hPOw^`Ln~ij9;ke^TTD-ptUimwq{rraW;TWGJFZA($@2Z=t*PXnhdM||E zkKY4ty+2veFe8}($NQ=WJ@d9#`!Bt@dN1P0AN7FlEpS|Vo&?9=ry$+N{_WNG<9apR z3t!q+-3`z3_rsHZXQSxZ0~c393;eUDl_UeuBUGVpux4k+A{(%)z4yM(7 zPe=|(4uB(X51+fe`utb7Ro{}KzoMZ(>4$sj``fBJ;OD`={Jm|}N8s1PPlA8uyW6UF zz%PLZG~u=q^>4>_q~2bH8wHnsd0X}AOSV@p$Gv`dk&-@H_=u|hi&0T_%8T+p1HN! z4}Kn4eRK8maJ-*ha&vVz+Q=tyJsF<*yrun%$rs^xpUjZkUqYY10)G$u%SitWJn!EI zdjAR6wqSd;=li!-uSOi{^nBfzrQ~6_Y2Ufa(3rMc>Y6y!UtW_w?Ztn&E0pI7V%}``T)H<;~SQP;o5a!xul8EbW<`OomGjz8hKxnGA?* zn7+up6|Nhu|CH_3nM<};Z_3cyIucJi!jT^JBTJ96jXBy!#$We=9o400Y_HyhgyiqZ z%?~D%#(pySBpm6Py36oaMDG3M2f^{;Ww3@DHP82Q2EgK(3+aF;9ZORyo6 zkoK4VxUKrnwrvnk`m`eqpMdLyJ$Mdwd=q|?9^1obdd4PW;lBAPtH>_#27!T#NhxFzr}-SnN_0}q9 zy|lyAE7KuoqmGEdJH3M6Z3%a+q#VUZ|+LI z0K3clX!}pBoCv*^FU=q-34ga5l;rnRkK}hK9MdwdJK^7sYoFiWB!2_P{J14eH{IsP z{wAa!{%$cnsN}Z=`AvgxzYMp%>4b!NJqMqHrq55>IrHO=JKY2SBIRQrN4slDntS2z z=H!pC-;&=*TRz%@Y3Ns(*JSi(Z^M0`pV$ND$3q2lQ*3_hS9dk+GW$Dn2V;WZcT@6; z%kPn)B2U^4=EuA?!n1Mv{63d_4vzVCS)z|TDdl5dzK0zRWW-T0Z->7dZ9hf5Uxxgy zYdEh$`?C9{9VV|wk>3ru{KUR6Klamfvu%DHKZN=i{%)|}Qf7WHbxZjUha(;4bsOTZ z!?iEp;$$%#^V8|h7tjY%KS?8`AO5bGo?g=4Ve>l>j`{Th&o+K7u9w2I{hd{LG2wXQ zn?Lw~{jy7v%tL<6n|*!QPqXbCoxy!|uWmJ0Fo!#;(~;-%KmYm9KL*G4|8(Pdm33(U zlrK|FN8LQU?!lxo0gJhC=b`=8b4){KO=l!$z^QzvJo%SSZc0zXqVm$F zXWanmG5fm+mrWD9F2aTRwY~k?>T}z#t*(WDsIM|U{ilTUuV-$$wt5Hr68I+gFF*!c z;HSVpb?dd&9q{wuUj}{>=sbdOJ+I-P`AcBom%ul{f8myEtDM_Ccj}JnqZjU|{yh}S z=XZF*_WB6!t%sik|IDHt)jQyqz&F8v;glWKE$~y|p9216q}hRR9za;kda3dAjW;$P-}r}y&*1t0!|?w94^`Gx4y*hu`FJwm_W$Rh?zsQY@qPt+ zAT)k|Us*X}ius-3;i|-JrEnam?x4|KYTh-fCgd90R1-@}kXsXGL|&cH*{7An?|Tc7 zO)s}H&M_jG%s2u1C!Bn)Olx*lw-}Th7SL;fGX@+}xEl~n4KJy@i*tHl!UuhQ@g`(G4LMLLY!*5Xwn059V zwy1>&CkcjwdRx>dVs(Kclp`AgCex+rzIF>t2(YWmZII%3c8G*uBFHnbg1Zvc$W1Vj z0}ov42NkIoZzR;HPbrSLAt;|Gmy?SEuhB_H)YnM?Q-gAO6B}y@{A}u_P%7O=6F5`m z;6)9oR+vJ7TA33j39x>>6JgFVJG@m)TBiI0r}%RApq%Ul z{2GKO1YWc7TvP71CXDRsH6@{{JQpf_Ep{)nXUmT^_X#Yun;Y(@3%o}Dq!*LFx5NUh zQQRV6xQi}uW|6Bn(yQNS1WdUP>RX6%VvU^UbgqbXUuFT878c9Qyn3oDtmON(!$Vp^fT~(5fA%_j*hRhwDq!|HrNAOMp7N3x!w8)As8oe5 zMy@=LPP?y1w)1uH8hRh$rKb0x^27{g?1Yby;Jmby&5fV%5h5IIm6A#Redhw^%)beUEEm@WM^BMv#-GpN2cZ9^CEswzSmIo$>EY-I>0%kKRS^9h{6gV4E zDY^Gy@R)7H0%tQCWFr;=)YDSz=Nqwr*>p;Q!$vG{Hk?vr-&-RVQfxq_NY##dgh1)W z6FWs^_PUJ1l;Xj6A_6Sk0{ZMxBQ; z{u780e^dSQ)H9c}bM$dp#>S~ACE$dp#`S~ABPGBfv0JG_w-Aa!C8!+QZ! z8%lLF^KHB2lR`4^W5W_6rN_&83nAdrqvbxdNImxG^QJD8YL)Y05BMA=TLd3ykcSC` zhti!rp984nff`V1tk8#iYvHBM+g!sokMpw-Vp^mC`(WDi9dXhU1YWw6XZdMPw%~yz zUkB{s&EZh?)O<3ixS!;k{drK`gE>AHFm#chGBRN5rE{Q|R?OSk)(DwlWyxgu$PyS10wp^c`4SKt^9KBRfsO!)QZ``@Dw2nq_Nc5J>bBxnmuk%-UN`EJ_6X+HlWFRl<{DkMs)&>5!X@l9#+vKMwLtMPE3PJo1*Qz)`l z{*g}sQ=yDDnpGR9J;jr23U=>+(%_g~`vrODI8&B1BG&-SPkG7~g!BzUq;ysG!3t)- z4J+`{70U0C^6{k@k?6kX??ckZ(CNi9NhAY=1(@S zYd);`XH6e(y1ePgre8FEyz%nJqZ)tJ@WF=V4F@&+u=2smvdWms_mU4J18)C+GU|r= z{~JwsnA!jT;K)&(N15M=`RH9UOyQD@;e7MXcRg9JN;w!CV-gWqs$b_Lh^sKRWzXWj zVI@RL+tE2F$vO}T36!>^Cq}@uB7R(BdlItbofx6~{8Ct_{m8xUjUXh?I05rJ5*05g83@sxGFa}p)~Vdsg^%$KgSvdUt|3Xk$tFt z&ak8vp;0PC?L78a1J z5v62wnj>{Bu4IWPB*vAAQpC=3#JDm+zVx$~AxW-Olp=YiX^`^eBYr4B&(?$lxpGm8 z;2GeHLtQ?BT}*CNS#t7f2t6(pay7av8Ga~~p8>q2P-5)TN~!E=HKejVqo^+bmzu2C z63f1DOo)vuTgWqOIO9eOXMg$~LLpc!OHh=Ttw0(Smp4OV2b3jthEZM;lh^!R3f^kT zjR(0>)j-R^1=lMgmSaL$p5{6tVX=7jz0~SC2S>ukH4wG$R}U&%kaL|}ypWZoY8GhK zx?V(eV6d|Q`CkirK=a4J0lH$Ga5a5tWhQ#S4tTb=BG-!>q%$utn?ggM6=+VVhefUz zH%Ox&($EpE;2&&!&^*o1f@dI$o#Ge3W^kmaA7k@nQ97;38;Z#!N@t#-!_JB7rt(l4 zZOLIJXe?s%GMb-2+zDHNlpv=*2a!10~Da7XmVtdzL6z`nc4_2LG3QaZXz zTuZ09RGV6WcF7ai@?xSbXdfzTEmupac2(OpStf0ptqCn&k?X|`(LrsSeCMk2p>^Jt z&|($2Ufd8Gn~eTQy^{t<*h=HI=#81k*g7hwH>cY7 zhDz&nJLjQ3ORJRwmU?zxaV_>qH~way(ZR3IYH!5%%xz9S{D9FN4)J$2?lQTz+UN0z z5|@o}bU5X>#2$j*%yg_Lj#RZQwfbW>Jq=Mu=Mqyza=3C$a_Xt4Djj`puOU+k1BE4e z^;Lbkk96>Y$v%jJ27PTUAC30;JHhgQV`e64`QY2%{4Arv@zAg*;q*wzQ^Nxd z1Lu%N7SsyW*^;s#2lCkhswB_E`tGHnRXg>v7E({*u9rlJ&757xG|52V97PO(U# z&p;|NA7kloFto+i@Z*m^PF}dafww)F{(!^v{G423Otk7-UaqD*SsJqR#MY!vV&s?Q zgU1Qv@Uf9A@ze3gXM&~WW$_Y|!!NX5wSP6Dqw}$bk0pHgR7OKtI*bYGcOeWv2W8xk zx&*qmMd~@r4+rCUnMjO@R+(_VFUOgkJZ9pE3CDIas>ZE~KD!~}G>nMSQt%TYsz zD=&FE5|g8|*yt0_19I{q(uY-N9a1vd=DFySrzJ5aT4m+iD@Klj*LEhOwXCce6VzL6 zJ9A}H8FP^(&xgdAXwQeXvj*iu%fWsskC&Jnp6k@w&VJy>A*zF1M9I@&Oi-4X4&HtD z-Q|0wuqLIPf9v_M85W*ALeV6W&JZN zC&mOar~ZkYOuci~qH-=JAMOzGosQfmVn6&{srxLQlJXHcdKgTGphiBVwNLV4Kf2b` z8|`8ZjksKpiE-9WFS4>_>*o1J9t61Uw%b@=S$?=~ov$y6G0|FI^9`+)kdDy7P12!- zQdyUh5w{TJK}wZ&4V_Z@vk?5`>3lTgr<9C1<`FtaC%^g4Z$cWSWaDWZLz#s%DC@j@ zCB{Und_9e086VOp)yH@m(_=JB^)H^raWNXD`W8=PMuK-2g=Hz#r+6C2Cqs`g?K-P# z@qUFI)46ez*6qw>G~UM+=xeDy#q;r|1n-#%xjoDxs@=I_Gu1-0{VZ zw|30x_-*@r?dP|*wST^CL)*b^&$Pa~b$08|TkdH&vn6TyeDih9M>oIN^mk30n%>y- zV&f+pFKIla@n;RY8U`8;Zul{t{~xFvjOYJ%C4T?^2r$L{|2LZOLi;^{MF&kazmw#A zgntQtGQ7Hf_~tS1(Cea*E41=;7LIXCE%Da-3PkaTCOGshl?8|9vTMTcfQ6r#D9)ht zGIhQT9Fwx=l&x5PV2m#i<}xigRYVZD15K_bE`K`@_Suq_e*6RfaHKj^$rkREdm!bKP(=fs} zM+mu(wXs#nFc25UNh28ihvy_C4ueW%q=$F+<->E51xHD_EXaPWEZGlL)%)!&7S#K2lDMgDB)xM+=hdv66)P!@gJz&Jgl zG78bsy+o{|5fU|hM3GTvzpyvHbMsBj@X9?{fixhZ}uHIRV!*X z7r}by-fsP`913yvBBhA$iyjMUGZ5bj`rOH-E0XuwIr~v76tN*fl>JF5T(tz_=qmC7PJ!QPh)xT#kv15QrG zSndmX?)8->zqg8xi-%i*rKbqR)btk;X@HTRZyly4{9}KUot4Yn&K;aU&xnU zN%0FE`$E3=wN>`MWc7x!YMUS8a4xe6<~n=ERXV_lWeMi{(;{*QmnE0&Pm736+81Kl zpB5221jI_U$oLSaSb~R^<&t}XeoDsOn2niU^RTjH=4U4EA(6>tiSSVj9bxAAIDB77 zh|ohWjsS^LqCO*P{6ca^mgVS*oPaq%^zX`_mUK#KUDN3u<8-*4d)0jQ7 z;@9hh6lGSDls*-uYwEO8@|GbX#r3aJ3u=q7Vn`_0&HYLkEx!=rT30Eaq!Q#{IR(#i zCp@yuZC*}j*P4Bv^`>VR(p>8*MS2bd?hm$l-ngz+sutw|1DaDV^~vr7AAaCdX53>e zu3fHlmCE8$NO3j(c<9C-f#u46nQXWMR!S7-ns&0uoOoJcvZSo3lWc^36;ve5wXjm$ zpKErbxQ`+SYuJMPS6scG%04v*+ohB%UvbI*IPii4|JQ`uCY&+hrSYE}KQMmO_^%&u z)d5ou*i*f?dVKZSaep;#-nie6{pi@E$9{dx>M?&9{oc|2qyISSu~Ao#nmX#cBX1db z!pL8axO>F35&trL)9_)#KQ(Obu%Gt4vu9Gzmxi7<^uN3B?e6P-cE~%2%pCHc_#(jK zt`|FZ;jIAeosV{`?r7`yWcvl}|EulcwhP-{ZoRv;A7294(sFRi7n)Z#_cVXLXE z&w}fYrK&D}=viJN*0p`l0x`?E2jaLU$|XMU@C)0q0vo-yIMJ2I3%Zz2WYGnzceNAq!+Y1h)!C{7_oGh3>G zaYXO?Twb&0LdnqBmMYu|6pd3RR&d_Dau0(W>g)|=-N$oFgIkJQJ_O25Uv{GG81mLz zicaHO`iWBQVX{woXRCx}wiK~5GTl|}gD6KO^FQ@(3ty9p3W3t&18FAtx)c&r)KXf$ z(5Qxx&<8+RXF|p)>r&98vXU;Zy%S|68NSzOGZaGfOWrjnWT>e7I2_~gTB#!;LFFu^ zJ$+D0&H_2`@&%#tmEwTi+8X?E=pgM)4>jnqd{Ikw-kyBvvA{)W&R2rGZke&GyBcxq zx;YJ#&3NAT#HpU8tme74D-KD@i+a8F5Q(zaFD23Q3&_^m5h2WezZAb`TEcpQB0r?? z0w{fY;;b5iMLe=6%C!$;h1r)lCyLEMggzN6pPi9Ha>uI_0ks+I3%E8xi&opvxZ_od z-1$%*+OsSR zbXy3O?!fujMy*B*k}kmb z1x=eIoTmzY?stzb+6R*{2knc8HGG>A0yGr4p(z`FtQl~NNeIw67+h3lL(W^ru02!aY{WDL33MvLYwy%FpN1Ob zYPN3?i$K?tauV+cR!Q;NJ0W!@SmVk%J6ayfILEXQ?#^?~iv)i&aN1cJ-nOA0lpwFY z6M}C_loJ6uPU@{wpA9iFPg&4o56$cA@gaBiKpdrcZkVr;6ol*SDzCj0Zca>2H{i7y zFzdb)_)LIlCukkG2EA6_<^0UbW;6W`n^@rWXmJB~m7WIl#PW^;eC!>1m*N1m$Xy4*xM8jNa73$0kTpEY$ zAaJZ8tzGyjy{vVn(I38BO-AeUAuYdx=Q-q{Bez4vN?^C`om9r62*E689CVawYiC8< zT13CQ{c=ry4f zo%59Fi}(Hb!a+$?fUjAiLgr5(Q6aPs5>@M=65SU`6iJji^1%`RaPdBLp7q87Le9y# zo~FQl({mVGXHyXb{{lW7D`kH+mA%uR;2!q<62}2dUK#zl1<( zIptdv4bpzMUjqNPNZ{J>ul+AcE*Z{0?6&tz=y`hRLp?w1esA~m?ma_3JmjJwBZvG; z*S4->yI$ygsPn|32X#K#aZ|^Xj_U3LtK(B>>QY?10bcIM&>TtAtDC782vhjO;pKh|%|SE0x(73) zQbZmFFZW|;j*PRT|127}%%fX!KZfQ|m|h+0Q(cM+N>T2|&>a2Js|(S5cuelc&>Y^< zs|%&phZ}M~hURFNUR_vYb8$oN$Iu+M(yI&eUW^-ZKab`k9OeaPX3(YiE1GFj_$Hf1 z+6GctO%*h|GR zvhEe+&HHgQTOYl0zRO5~sm1*f=vWJta%&4!bhg~YzYKJ&g$i0*sG?QoM5gd;A+Z)J zXi9}%(OK(|BA$9C%`R5o+Co)oA)4E0u@*W3@m^crhF^5rO6qBgpjBHGt>tEX3f~M} ziD`?VRa-(dR>GQ$Y0E^!`x=`om1;j9Dy3xJMeSSAtPOfaXZ4)uqV_Fl ztuaMweFJF9UDUn>4IzO$pSS9oqG^qJKddo9YmGe^l$+^Oc(#&QjR{(7OgVq3L23*j zgJ%C7Yu|#_8dG#wZf>d<*O<0%MTh0)=5efjOWxYP?=L9#SfJ%r-Z}_cdtlA`M5I=i z;)2iO#d=^tYY%*`?ZiX$B%otG@ZpH}n%XSYQd(05-2fW&iZ)%4jY|=>8S8=zW9MNz zZ9gVx)l@}mjj3(vKz+q*i=e56^evY2Y%5`oY$d!O&s#NB(P4el&c`%W@>We%v~tee zQh4_KF-;Y;YO11DY5*B@k){e-HC53e=j=;jnkwja+@&u<^W+8Z$EBv4s_3xXY$Y*G zmAq9`rw6*m+){YLlWFW~$Tdq0lW zv0|v_0GV==!`N6+IPbw-`q4J;UH$lxaU5~+ewgL2LU~bhd>O%@!JK--2#J3VKCr-iR`Jx7+*ia#L9JMd+fQNb=V9U4$;` zi3F|fThXCKW($q=L^9IUHiksTWu?Z5|Ay48sZVyl$=lS4xK}YPpbJ;pmxNLyhrCbs z%BvpDKF0S3<~IH*nk7l$y|yrYh*oXEE#tDBr- zy+I7kHbk#(l3pN8;rUzc$IzjshEk)27EO(zwKqT_;6#kW0 zUwKtldt&HDq@Y))X#P&&sV#Cph7NU2IVYNqEmU+ZhOVb;Y@woSF?6VFA?JSpT68Uj z4s|U={}<4rYkBk`C{=BZDOzjHkC~X>*O;J-YK#&~;i;uelO!>8BjV}RY2NyqZA|XR z&|!`3!KqDMM_cj7MnLW|ytYjr6u4ua?Xkn`hB3_&V>I^tOdMMW=b!t1&@qjVW5|TWhRn_AY3xu@Fr@Qh4fkti}YbHKu6Oe;e%+w6`sJ zsZ9mdifIAT3h)yyJ5J?q=w`H)9FKV4zONx^IF%Z&Q}`;pwC^Oj_aLw5f6DpyI^;p8 zGUs&)KLwsvnJzp3e!lw?-RrxjcE2#>(?hll`Pu0EM_)dA%IIH>dU({PQPW5LZsemQ zZx}gyAceI~c?W%rj+->9DJZ|K;?~T24>;+@Tk9~H`&M~XTOd7Lh=sE3U+ke=0Pus<9 zy=^~k{b=hYt&>}S(egmchL&k9FE&5Yd`7Fx&+?uFd!8Hm z;h~2P{X^3in%>sb-_+3bmByPJPigGH_XOV4uoS!hPgUMuIj>T!Jd@m;4B+{HXaW@O z6DTkD|EUiZ92TPH4746`;L+xHfi`IMut?gXVVctb+x`L)Q(knWv@64O=qOs)1YpkHPM=C#d-xntG1kN(YqW@9)02HC)O+UBb}Ey`z<=PQ1xWqkF@d)MP{q$ zvN9L6%AER4mz`Oq_|0-N4RgxsTEB@=nJZfJ#!Vx0>KW6-(JFK0Jlx?cYN7pxp2}R& zVctc%E`rt;s_3wd6}3=7YYXKX2VIC}-w|t}a}e+An@GB85*P`mxGrjAf@WP?H8VIZ zH{(+HqJCV^THgkWe~PZx#ssbP9ikbZ!WXqML2G?0daGU_OyQ|Tu{I`Xt#2eUPC4g~ z6rN+JSR0#*_*$7Ox`MkYe38rr9m+h`N~BIm&s$_cQ?~T#G;jV+;i)NdKZb6?U3ztU zQ2T90S0skEMefJYq0Ga)Ir@mnd>-O!r8dbjQBP`u4yCrvqE%BzqQ+xV6LcuGu-rwK zNzhHmi(XxJ#L2iKsR=riT9|jy$s|FCni}R^bTUcMYMDIe`TFMC2h)T$RnTgg6s>KH z(oErb&Lw7<1g&ESMTh0)%1LbOAZVSau9Y9R)A9`^HV`$FO>DBGh3;VD!SLx(_9z7rFyj|lxI?bEEQ~09ZK+tN}6kWDA5VYDgkIuJm zTBcZUAm}E;owR*i%6Ki)8~8PR(udaBGd6;5MjU-n-n2}-AKDf{t6eKX(=x?+U_q;0 zQ*_wIXq94iZK2_YC`IDZ>_swUTnb-QV}jNiLqg-UzWF1Cr>4YuB0+17A(3$*dMsjN zH701SF$;FO#@{LYczC7>Wqyi@8Em-)9hQ5N$w&UB2#U&m0`B=%BI(PIQw1I7tx{ur z3STr%6?Boz8BcW4Tu{)IC%rn&n{g?8(KuDmYyr{=Z~>=i#-;E@Crt&dtpo{;D|^yZ z(5=AGs|$B@i&hBd0Z=htw$wQZDQs0THheQez2DpBtpiugYwKIlwQY>)Lv+3R=9(_k z;LX4hbiMlSL7hn(i=kPs^y;+6_&bHCp3D6hx~RSZGU%}gMIU15u)f2**}lap#?WDX zhv;6QMIT~l+Dv+NT4Vg3!cT&i`!RGl+G84ZDK4hK%V;l#t~c774z!H+V(5CKJ<35w zdogr4+6&9gRT&u##?Tze$!HE2a3Ojg;^ck|4ZY8e_CoX$cRz*>M|&Z98PGD?i=o5O z-h-K;Ukc=Mc!|oR`w+h`?VIU+`xbQh_8mi)Z{IO=6ZoN57q$}LzGLX}?K_4J+xJ#w z0$~d8+jk6IuYFGf1!>w5CS6|os z$%V6!-s_s6>*<<5xe!Cw)3rao`o~urO>YuI*V8qBav_GUr)&P?LJS@1TBzs#>UlK#t>r4I@(9)O0(DnL~7lCG)1aIW1Z6)>klHUL=eMt;muP=E4Xz5Gx=ss|= zFYTK>ns48NF5kXm=<@A5hA!W}W9ahjJBAM1_g2V8U5X3ezGLX}?K_4p-@aq$uziP| z`}Q3}hwVG;OMLrQw4Bhn5HhKlw#)W=2Rxmb6Cs{1h3|!z_RTo2Yl7DHt!Q(u$)b_m z>~Kl@&Z9Y&@_N33N#WEoF>MN8wC*6YIi{jl#~zoi96D?zYp~OvKN+Aq{>z3H?fIA4 z)vw*X|K)^FP1rc$7<^mcv*WKDKWqHU2Ym5>^njBOXgc7l)my6Za|GEP|NQxY+Wmj- zO>_T$x(Sz?zIbQbonw!&vN;dozrr66ukP%d&Yh~)MWO8fOUW$o*KkwZ@RhxLtaIHT)FUTnUfWnB=WBrXX$K{GGyc)D^3-6^QqC z9Y2gybUm31I+S_Y9RNWW$()R&@I^ZSf~Gu0rnrDp&Uu}}v!9pyd6^43lzDjOf-N*A zb3unPKM|?brMRG7E0(#o5=HEKQrd+6=H5lDl}O&<_$B1L=$x*g!|_X~Yn;`|{3I{6 z0WddsZ4vZfwMEdOwy1RgW3IkqEmY9d1knbjuho{K7CL~mweQf`MI)MVMJ;pyX(~n& zL>mMWYHCpn6?CXAp{{Y?GNvtp4z(pja~+*&$Y-_(UTI<&M=x?MZ;=J9^{r?vcRh7}*4uV$eP=scU#jL{th;K0R zuHZ_Y+Em7+@a&1?9%KBNNzf{DNl~lko^l>N5rEpfwT)@sVgX1yhAzrm>s#Nb5=T>7 zDlz7u6Ra1!)wBqwp6LgrjYAWN2E~+s>hcef^0W@f_Y5bMX z`&h)(a&GcQO69kxl?XcIT=Q1Wi&}}GL(XH`5^E)bZUX=G>NIcuPT{F7az9^gww~H@ zZ$&H})21d=3B=H0xs`J*ce@>Lg)*1C!*Xlh+Dba?dT<=gGD*2`0T-5gxJ2WBp7T{E zhH+XlCJZ@mf@P9(*D-WhZq1u%Q+UB@9^R%(-eI{lZ)zyf();sQEbp+~+DcSv)YK-^ z+r-eM>RW>EiByu@b!j$ojiISa-uienqK)I|Uhc=xtaW;I%DMjL_+0MC(BWuLWzPIk zc)mNR&Ey_Ka|}$cPSO0G!lRDt{XBXA@fC9?i{67+dGb-t{-lb}OQRXry%g{Q=r zCe(95hnlM8X8IJK5))0%m-}$U?_Fv{d#N$KZ=r$?rKXyyQe(MAYB6*uwc=4v3>``> zEH_(-NG*?Md~Lb6B9@LaN=+!bA}!6AThL*-wZ<4vH0`;_faybYSZ>W*EfeiIufx2@ zBc_&fmAP_Gne#gKJ{PGAtsSNbrADOOXKtSJBTPK+3T<4#73ajH9 zW_q8upv&i-N3(aVmAOidJf-kzS>%kb&s)$%c{849u`ReTCxk?-%!|=wWFAAWh;kllCB^6>&WT9jef!HxZ2$4LZA@GY%@#nfF4R=t2J&dGw=^1lls8h) z_j75SSY-Z+q05vzHu9Jl&AV)U3%X3+EK$h0pa&~+K@V2uQ^8F{=3B^(@R=ek(ltSc z<=)jpEZdLh8b|jgBB(Jzhvg2nMe9A3+A$zm%X!%v6LiSA=B=FTs3o3vJW#RHPXh2+e!ee2-x8qj0O0!r_#Oa#=HI_PK;A0A z_Xp&k?&li=^z;DVFtE4x0@R)$$esqM-T&vPm;3)8H{m?fm+$O;x`(S4l;b(@b8sOm z^!Nccea0z|$yM=x+5eZC3$;d{Wn(-YTT9Hw2)c~cFiog6^AR(6eHV1dx#}EqOW`RY zae19%Ph6Y#u3n!u#buF|5cFX49suRqytPLnpDBD%YZ<`3Mq`Pzb`asl>5NO^SzECh z8^Ar8X-bXYC!Fq8X&lQPn~x44ZDZ1e|Mcoq=8Q|>TkzLpQsiYm5%;)1pREjO4>0dO#zi!5)~41d)2PfDm%<;3KXG}^Su(tUh5zKX z`L9xATnb;*mkN5YG8Z)en=6ELWo0htCLrk5O|ln^OW})TF6hC^T+oA+xvAwr%UsZd zmARnFzbOMky0S7CG-XS#Zj!xVTnb+#b3yaJxk5;%XvU@RMS3pi!OGmUjT)J2D^Wc! zI#VU+Q09teJUK5qJ1FQ-=89%K(M4}J6Lb?&(5q84f2Z(0@N%DJ@petnMKVWHgXRGa zsgoF*Gz=XkhnpmU`_JEUKZfS0HM{n)r!D_vGiSnbKZXw1@KsZpc?!=lW9-C+l)D{w z>D7hk*|;J1^LZP7>S{~TxJA&Rwy4xpTZ-1v1s!UOqP5kI!V{aZaf_gvP%e6Psx6F5 z;Sa%IY}_K~P+Jtuc%l!-pNu|YQVX?3(TpehDE!IjBab$)z{Ogqpo?VA+){X+ zF)%G67&{oHhmbBSHU6Fl@9&dy z**_4Oci}F*x-jo$xFPpr_03k`TY@h)sipAe!OQ&^8oW@F&Sg1I5xX2-?#IyK^JKdm z9ofld|1Hmx<jZ=sU+ zU|Xo5na5ySsGtYiLIurU-1OvDS}HaEOyOt1%l(+t*rS+U1tDFisS6M%_hV>Ub#L)& z(WkomF*MtXZ$q`{)7||Tnx!`qKuGs%D1g2bEyeFQ!^`~`dax~2WIosyD(JzsP(cs2 zg&KP^=oTvI!M0FA54MF0dax~2(1UHEf*x!O74%?RsGtYiLIpk87Aok$wopM+UyaTp zq$}G(1rmCrxux3EQK%XLj}z?Mz82S zFTg5h-oelQoA<*}t{D-yytOYYo424fZ$+2Q+l(h%-XmC0xUzW*TJu(P*}O4q%Zytt z+{e5Ht$8bYPk*_u?+TihiC)oKZZej_v;FgaSYtc|=SN%*nd&buH9;%qir#ZuInD*G zoGUuZIhor#=j==9W9Ss|Y)iZ!FE@3JK87ZSvK2kd_x{4U+H*zkdDNGiXzqeDO+0V4 z=ZemjI|YU-2eA>Ckcv^%a@y{oV>OO{qou( zXw?=)XTR@}f_#x}5wvPc5xVG!M?tH$I5ZaXvUaWLori)}ZBcZ##wZxZ$FxPzsx6An zuGSDuO^s=b%z;@C)~qmbja~aw&!90kbzcL>?&cSt7YN@Qa959hEkO7FU(3F~ejy-y z|Nk*YF0&HZ??2zzxPkUxjhb%aljK~4Pe5=C)jfOIVPDkiqEPn#IqTW`sW9TXvnlIc z`PL$6)pyNFwTAI2Jdv@zXhEyKhiLvz;i+#iyUsWG*K$4$v2^4!HKFQMo^wIh<6P&_ zLJz&SeM6|AL(a`f9pub-=3TTOEa)brpjW3-uBB?R|6yA(9_#@DBK~p;P>Qrj{ox+dig>m`3FF;&v zxfNZn+=33vt!QSK!h0=_Ei7t9}QP53DL9b3ZXIu(jr00ST^<3X}RnIyITF0wl-i&A7`=4IOYs(7Aq_&lm z9eD`4NY_Bx$lPmjKJS6ol((PWe z>a>mVcM4xL(mVk7YIipGKfNGQE0Xy{BelWGd}>_g`=4Ggnp)IKI7gPTGh2$fP|u5c z;JEbasTxSPT=Z^@rA-EJ3;kA`6ekIW9Xv18K1%z*%rw=tZ&Vm@kAGmxCGsV6!hv8&A1ePh&xk} z=lmSpD_3sfVrNZcH6<%^^PI`<g$p?HN5{C-gj#1z@ADRPnD;JQO2!i{ zy2f8IbXa4GmRb^vEH*WUZUR+$b;>z^r|{HNxvywM?05SmuwMfEC9q!t`z5en0{bPf zUjlzZ3FLS6A02ta$WIU1GKAj**gfLyBj$~Gtn0?EWyA9C;t%)xi+B4U9<^x{-y*`_2U<3?Olx_u`H|*pnrAlGf99X>7|^#0Jd@m;3?%Zb5RK@UH4&;|oITF4cej_f%B0P6K{=vQR(PLN1Rq_8BzkX`s$#SFvL(xhyHQAC} z1-~*`pR7pMBv&V!aJ?nD46z%M8RvcM8`sE@c9VK0zWRO{;#VTo^+_Q zxLSd`SAoX$isN9{;ASVQ4ZUlSYLiK?IjjJ36YgD|Yy|!?plzOZuw23TzZkm?Id1@F zJ?L5-CIph_wB!;~mP-wn8&D?lcNxMff!e6)c{0rKI3<#PGt%GSY4>Q_Sv4hJ3qIb0 zQmi$8g^?hoW4YpiCzH#eHONevxiDf<_1C1@3|c~e1LQ$jZ$M60qC}La&4ovB41QV- zehX@4qrqkJvxmqkcqVEC?p~L=Qt-;O zYqFenXxf?32A1F&P?T~>ZEr!`O2n|%G8}V5%*rvuWm2($m{bRXI(2&sWVKoO?9{u5 zB3J4?kr|!;`mg`)y20qY;E=E6y-zwX*nQV|!Ijr}NncLqCGB8zUXb;4UQ!KS=LKJ0 z=OukPotLzG(|JizPUj`gl|sD$;qmTTka_&%Eb`!RWl;2B-6a-FKZATzQ?B z^yPG3(hf%F1zAt$CDq_{Uhw60UecG-c}crBotG5lbY9Z#yUq))p3Y0EBAu7J^>qH~ z<+uHXY=fGz?XlFsRPFUAR%nGuKWndH8!`PuG1`<_kc-BzME zUSazhju6;)GY+Ez!8(~wK~K36v^OIKM=NK5)+MHg+@fh+nUBb&l~iv5?kY{`>M5Uc zGw5@~mnoZTgOkDURmrtB#~K;9+N>gl9T`apTzv&SuH1F{da{uS$7~!kvUitp<_eQz zYSmKF0@F;*TS_6`(;`*;KB?hnl+dsn$c5E>)(qd0OPg)QaTkBt0ph zwgq|G99>JEh%v+})D=f9mx2z)u|mbwZ7;@G90P3z*XI77k6!my8O$5~>rEEo?gpbV zi-?!|4qr`9SFMpTJP$<~3bat^4GFxHBZfhYCnQvT)^6>xrN)a_8G+d1wYB8ITHZ$UvR8AKs znp!3IPQN4txE9WPoqnI{e+$rznu-z|n7$Jb7y1n2mhj5|!n@%KiP znsYkTprmsgPKKVaP154&OwMSiq;q^uMjddRObd32sRhoY9KWtz9hysb86-{F+FV_W zky2d=&SdP(xf#deoKdX6)mm80wFoWF+1J3r2#?Of^dmQ!DKTHsn$;*5d7$1D$imsL zdEjO>{?;0atwft9C$uwlX*laR-B^)LkSCFOJE!w7TPoK_O^3|ZLaHl~&w9v!`nVcx zX)S22#IHUmf(<54v)!_WHjAq(&PLHNo{-k|T zhgi-WH%^;oBS-1gE(!E!1yIx^wk)xYHXo-^M;O_&o<+Aax+k1F?P5x80py;2J29?| zPLHN!Qqp3rv9(~*&i~ZEEi9I^+;wu6TFR7M?mKBuGE$QJu0|w{lQ`2{jr!PZY%@8& zGMOQ1oV2|ak~I~3-${5pw1{?s_Qu|G5}b|rHKvTJePT7)w%H@HS7okjg%;~%ItzMm zC9t$@)K+T#)d<-Jxwgc%NBLp2Bpf>#Pe(k*Nn!eIi?bNlHfI!I0_~EVTI zQu$VG^Kk8QT21OGb=MAQ(WI~8e@o5HZ&Z`mo>rQanen6W$%i~q*A=|YK=d=4mTjAK zYfx^MGZlAPKho<`qeVm5XSn*}@fnjxJ~dZ(q-JF;#d7b)CX|KPDE781(0+rpBPju2 z`faEmgY;K};zskI{fv&a1Ks9hgcd=qq=ab+B^RL~qdTWJJZ@nS(~TbI5j88H!q;fU z923E))GmYcoSfXaYDxs5*27APi?@cOSXwt)5KhKv+KHyD(%$V1AG8r;s zKhN=ut`?g9L()0R64-aS>+DL6-I4p?S9qR^9NuEu0$X=>6*!ZZvo1%t)YL{<2bR8m zhR}6(rq@^tPtV?Ww&qY*%ThwSwkK4aRhj5gIhjieXFYrhCuvFHY{d~78o|1_q;&RS zCbX7)t*o8PID=AP-^2b}bQrya;GCtP1&iZoIXQlC7-ub}BNu6{qIa}K*%oUspZne8 zi!vHGve1(~4T{oV9iA+A}k}OioUNJh73;MmjBaV7P$EPKb5Y%1notXbA4$6DMMSmWkovsQonxU)0-9GIL9UdgfH zOYScYibC+)Vyf9un*Fou14= z`L$n$~B2c{Yb}e|~?#C(S z_c|@3vmKM60qc;4J20oC4s=h)^5Ja7tb%lfQgjw$3iO>LE7~xzxf%VJx^ttAW0GZt zW9kN59eIyR-HkaWI!$-7SU9T%C*0iniPc4kb-FkSZIeAW=ikfFCTRy}NgAhnhi1}b zbX`(8eLE0uGr9`2)=FD@@xCA9NuMxxiQRkdlBQnnk}8wC>Vj$M9~|)y`1-QTeXlKD z(mI}~3AA|L7A!`Yu1{u3I>*<{nsoCZ#m$f)<$1lcNizgj!0C)r(z{n?IL%wEBzZ%P z>*)GL=hr*$@7&h8ymLlpPv^Hf9`1O5$A*r19o3FswST((_V&x$=d~Y-gUC;`-P3kM z+v#lwwmsYWXzSZr-`qM8yW^i~xu#`l%P}ooEl)My-+XiP1F-`=>q@ubGFjn6ea(r|mjB@Htgeph+2^8U)@m06XB%2$*3B$pq!EMn5t7uF)5a89wI5F*C;uAN%Z>`^LODdim(-qZbSxK6>Kt zhT%UQar?+MBVQP~d*sp)<3~I);^q-Q8+FU5iKCW{dTi8BhixA|b!5ZH%ZLAX_m+{@$yb2AWopNT@qGtC#63GysqM_n69(4<^ZHZO9umKb7QEXct%Y*#E9J zZEZ8?O!FsNGn6>=KiX3GObw+aj*?$z7FyQm=eVK~zWL`rPKJFXN&eD?hzht`4ZRop zDU@u7kfX$MOtai0U~HvOw4FzeBAeBgp?4-{zt4xYyy^U9b8@ti znC(__lvwhQSe|y(@V3nI#?b^?$}Z*@<0_5(fhxv30SlMnja+B^V#+dO9iD315y9>)>scm$Zf_ z)ivQsMmi!fI~B~)B3BeaA zsGwC|Vn+8oog<~=bA_z0tkvOgX`ZFDqefn;3A7X(9nt!bC%g5Kqr~CV9hSnKHIz=V zC8Q>B79f(E08GdcZO2s0k%ow}Wm7uRZmzNX&^Cv3Xm4g&I`6IFM>SK{c%@ZK*$$4- zB2S;RG#;#>rS^e4VH`bNi_$UVi7KIaNCC~ySX!T}p}Ew`OJ;+dE6G}}&^tUrkBdns z+Vb95Lr=>qeTS?H3auj|w5Sz-V`gdt@Wt4D{{1sF5qi{EuV%+7TB5V zpy$+g)%If|lsFYS(o)jUQ_>d1*2tcfYu36?BT_pyLX$1L&(hS;(>w?A=7>q@&c5IHNs-dT<8p5>#>aFfrQbUf9P^0FaY^fEGpE!5Oj_PJcD6#kb>q5=d zHo*0jHK+;Iwl_s6QQNe2f1pNdwU4|OTD!`u3T!d6W9>!w7Nt;T98;ZbOL?9R5oPs6 zbxuyMX61iEgcfaWuN^N!e?KEF73$sEZtAl)W`)Hy4=O`r#9G5{Ft#n zLYew^f|Y=Vhze?k5MYj~Vz z>&Dyng%KKTFG^!Y4GraqIT%1z~^{STr7yUf*0)&ng9DJ_an;+RlN zRXa-3o?g~PSA#dwIWJMq@7)Hiw-l}3vzlKAu->7Y=rK&ZqHb$SxVB5i}Yz;MzV{|@Wt3PUCs6V1LeD77* z1M_#WM(gN+rxWEYfKb9JZ4D)IbG~g8YMT*dYgHx2vaL709Cb$4eptIKdFO$ zRI?IW4-I6$&i;ecE{ag2P1bg+wjNPY9%W6I^i-osmCJOY45=$>P1n>|1ueN)m@7c< zdT_}ddq3LZyOu(#92+UMbv0DA75&$0Bh))5t08|igx$Iq)G6j|P0+M2 zG(X>c%j)DESR2)lNvNw8==9QLF?Lp0WX^B$*EGDlb!KuL!s9SDpAWwP=QF0^Y932pI7Ge!U~@n9=b( z&On^h{!-ihZOhw6wtcnr#@6YrziiowQx7jUKis^oxwrXyO*@)SYWjWSeT}OcM>l@6 z;q4968lI`#Ug@v=F8M?k`o56LNA=n6q_056=I5BLPRc z+;iYvoxk(>5IN5%t5)2z#)os{)*zayHM? zSX^1(h(9?EaD}Bc+S1~@iL~Z`#u|+4d0HU5F1j9PqU8L*GSHd}pP#2n`Fkz3lPopv z*vYv_%G8&U9Pv4cjrh;?D?v}TXstJvEBj0W&xP_d14FJ4vW#5$^3RL#nMAHM@OeL> zdAOx{vE_&~x&Fj_xn9Y$Nvs#{k$8Tn5%RPPPi=Fjka_vjol5-(OP$XlP%-3&9-ZylcJ=W!_4uY_a)FwfS&`jv~(3 zIhGRFo=AyWm@SX21Za%B0YiCLZcer|vaN$PC)Q<);atuXcuA0=K5>=hUTMyCB^2G* z2)SJg{%D&hK|bdoHtbqRhwVjmh1O&`zqiR&SMo~IRAWKV_4b4qNJZ1Y?#)@SI1(n3pVCTgX&q7l^0?C<6P8d0={MUlDpkACL%&U&4ezglJpwM`Bl*wQ_OuX7sc;$ zgI1PE;`h1L>R!MYq0er0Z}++FD4(s0)9*L;i}_nw=RUXhE0KX3+@m=p7n z3xn$gPR^pUE#bb%gabO8Gj}AeGxaEKdsc3=mgt**wbw1wS<#{7} z&v7`(-jh}(Ey<>FvQi%P6x57WrVO-nrVNJ~ZQ|HzBXXA>3bs;GA%&!xlu6}mv7~YO zCC@wxrR+G)(sHd*Mi!EeV|#nw*_J#V_K`B&n&(*;D7)ra11O!7cPmk3I=DoA)n8ZaiCHXh<5zGs{FY$K|GNa$7q? zqY7R*?nfQUIK%Z|Q?1N7(vrPDK4Z+2$?Vnb8%VgqU}~%84JF*ZlH~|$T56h7QSX zTBrOG2G7x&5~N!Knw+l~nv&L)E|#y+fc^jWOJKhQ_Df*D1olf{zXbM6V7~yV{bq{pU>aJo>{d+?m8FKrOjYCc!GNoh4FS?%Sy1#3C*Z5%5l{T(-V40QB&OzikW`_t`@w%Pb=cHj(m12Bx3Qz~rG{r4o@h9*cp1FD{vVgd{rzRPzD2K#!fBv6n@iCoLWhNs zhGbImx7}MSKi~u%j&tH0zcjwG>|;stk}OKQ@bBL~_M}4=4WCW_o^3bD(wIU0dH=Ee z^kk1KVoB0FCrREq&s-*HKSFpDO4OhEKOdXUy3cVz98 zqc;4!4ZmZ;W;+E(uUh?c()BdZNq=E;d3_p z-!|-~^1>Zr!`U`G%Z8WR@FpAHX~TzX_*EPJ$c8W3u#?N4a1(9#CL1ob;iWcwn+^Zk zh7Z{AaU1^7hQG04Tf5Lb$c8g)c$y8@*zhlGc)JZhZo}O+{Jsr;Wy5AJ@xhI^;juP6 z)rKo=c%2R3Ys33&`2X1ODI5N$4I8*n4mZw*(`>lVh8NrLY8&pb;YV!vm<|8MhR@sZ zRU3}B?c^vM&a>f#Hr!;xzqH{_8}=A0FaR4sm-exFK!qjxHWy9pbJ0C6n#$v%`&~5a z-bJe|N&8rJE_!!Ae!-=Elzb%GzMMli@u}?syhQKpCkYa-sqz3iywqGjVoQ+$4wcRdYX$KpN)R5AHU$zz6tp6 zQhUKp%trgv2gjoi$wsFx%fqwLWKjH(+33Wjo|28;*^ggvY2Q)!?-(Qjy0nkWS!O6P2KOJx$o_Wc)@bZ*+u)}@VtVH-sy;Nc;KV^ z@e3~PvyWj5Cp|7TdvBN8R|EGgT(mDGhafIGbwuog%Auav=Zq!V^WC2%;%SqbqrH=f z;L^Ui`0t|ccUjKMMti>JXQR`8{DMpS7UI7n;%T3fjegqEUX+dA-Jc}l7iXhAk<;VR z_A$+{@Jq9)y-c{96Jc;!HahLcFSxYNKC&pZ$sAqUcMkqLBK`f0Wc0cC@1tEQ?Q?)a z#M8bYp8CRU^v-_#f=l~&_QcVCuAh;N=Ekp!-rdhgM%zc;BulS@_L((_?sW_f023jy zG8^rQtjb0w{rCl!_Hld45%JLveRW*3i|+3?iIMt!=l-J2MSI%Zb8^ue9fRCpa?wj& zH1~{Lv?sz%AQ!#UWyyUU7i}2it^RMHcJIM1+NWOeSI-^eqD^WfPWx_LuEpX8(Xw#*Ae3QP5_SL|b z!n$WPI8$S4ZytUi0s6uAKPWKAmRT z6#u6K*~|C2{Lzj+o9$?FtesuG(S{3bc##da*zi^xe%OY8Z^I{TxW|UC*l-j?M>oZW zR9(6YZ1@%%-eSYQwc%%N_|G={sSW?zhQsZw>TnzO+wfc)Zm{7yZFr9j|IUVAx8c9r z@b@+xYG+l4+VDghF0?YQy*2 z@If1X#fJZ8!xwGXZf8}!Hay;jr`vF?4R5sJ`)v3L8-B@#PuuX{f-uklgU39-o z&AM~ZY8v*>vb;>DIZ^GOWvR05?1$FCrG5KnSt^sn=}A8%oUm&xYsN@JbtQv*Fz~{Im^a@N~+_f=>HL)){F=i_Z-)>FSL9dOiM=AN8_ToA!$&$PKSwUl%S0uo~zNlfXRI!Ansym1oK&tpr}h z?W)VJA)vZ;R`A&VR!xB9JDYbm-rsm#<3QtM2X!9!NPAn`r^js^{rJeQ4g1gb-L0<- z8F2gm8yiS@Sn?JV%KrbU2fg_~uH=*8O8AH1IU`W_@O=}=(_Wq7aw3wbk%LK~QY}lj zR=Vu06hy`{kAFlPSO;kwYIsPJC$ZFjn7#fkmbkC8*L%(I|Ip3ppi9pJv%UY-U0o+9 z$ufBC(H{3Nb40OB+v;cBpWAUCFYE5Kjd--(_dn2vP!Z#PZNqtxDc$>Q_>2t?vGujV zh99@#4{g|LYn}_lbQjt1JvRJ|4WF}Nr(NNhWy81FaHkEwX2Tb3IL@xzoNPl~`FX$n z{kRQZu;DnnB6G40ueITQHvF~?b$|U3yVA16hS%Hh!#4b`4Rs~vXsfFi*zm13eAtHn zX2aojMP$AWx7hIGHhkKKLu~!bvf))WoMyBu*XS$wD)W5vR1lxF;Wwf9bSd9Jk$vaE zd_1)y?+e(RoN1op<+Hp|a%USbAJ39U|3+;}_pYTN<`GUI$U-byFfYS_^ z1teJ(c94Xo8|jb`iIBSpp%d^lx;*D4Pwkv+>WxoBZK_vuN26}|0KU9vgO7t{KN}Ls z>nH~Z-?tb^e56a9f7$e2l7{cNjwDr{<1bZ=01mTk@QEgU&u}D}avQ$Pd9O+N)h2mw z0N)WAN%}^YIokps5ap-t_}Y(X^5b*K<8PC#lS}vtf{R{&8?<+^IpzO<;BZ>1l#NNFe0jc(6KP2+19gmy0?s7&q*?KAy z-MioiI|}q-iVdYW0tyE%I?Tup1Vqub_w;>H-jo-;n?7r`vdul)gyL^WE~-5)()7b8 zH*r#W(>VO^Z#)iHtvHo-Q!7?KZfah@tHt=&g+os_wct$9O)YEq?{fU>+z1=r^%npG zWt^L>+F1+c)MmE&Kd}U|(5B%;Vz)htpg0X>RGebonx6FhZFkMKkUx9CNt$|D0^uT- z%vQl$zvUC*>*K;D5P5e8UNC6c;i?zi8zsOC@5ITHh+mClEUGG8DLLpJ-@iH+EnRx! z$osEHv{sg@Y$_66OR}3w6W>B*uag+F-nC@MJI#6*PrT7-D8CZu+3V{#W2dJD@#|d= zoGe_ivR_{pRyJCPalTUJ_hB|(YO_cAuU(*KI=39AwP9-WVBAqd+LH9Ch8@^)05ti;`8=h#xH{0-18{TNczp~+dHvGH|dyEwQ zUeFvl*h$e&n^IXbBo~GZ>TC_}44m>zd;Y)o+ z{f_2RIho4b`yES57b`s3?lL+n20u%VlM;TLgD>+e`OUPmJzR|m&WSsCe~aVu9G|jj zt}=ffBi(@at%&BLXgJ{IiuRlZ3wlpqv|wrPS-r~^oZWj)?|Hp!mi_R_O%3?ByMq5W zRT^Q7X%ybkfaT-c8epTBY@<=WiH{H6)PS|`+ZwQWH=(tG=8!d7WI8~JZs6>*zbmHW zg?ILwnEs0;%$SaQI<9#1q&qwM)%-itp*@}*P#hX`AA2_)N!(-^CjLDCC|G*R$qgvH z`Rzya>*~VpLkAS^v0T01hBSY4he;sA#N9)4-Joe4EadKfNtO2X;sVhu{Xb?tp_HTp z{h9-%Bks9)ZN0p+UsLQkd+zgJd*L&EHCkeKWOR`69JJ*BWA94L#SN9H&wUveqA_kUY6LXF#FyZb#6J%c zlf1l#OJWvZB$}v6O!({Tt~%T8J6v?&V(gkZx6Y}qu2WrIy_`PXeU@;Cf*e>~d}U*u z3?+${+w60U#u>-w#Y;YB3&{rNlc6|)uYVP75^G=yl2t7tE8T-E56NL=j<5CvLT(x*-1r!JK&}deG>?*fKj2QQV&aBE zPkcSzfjYWiR#EPksq*Twon=*JJIX4`RN1R#J6mimd%SE%i&tATl)YNAwWKQjcu7T? z-gl;VmTV~5QSxe0rg&@7vf{^!>WdqSCKqog@{2P?%ZjRt>x(8A`9)R5)kPJ>RYes= zl($7YQ#*@xq+U&JO+B94QIttFq&B3Mr8amOZ<$x`)u$$>CexPweyZB5@~Tr6w9EZY zRh6np?VzgsRkhRGsvcJj>W^(-Y&)ULKPr}YZPTG;yH>61gZKZ}rr;56)Q9929<|39 zx_G>0rY)B2!Bghbff6K7=5(t1{b|sPz>R@!6VcZr`5Jgc<+PG@o;-J>wtJpORD>p2 zQDNva?)s59SVBP%Dr+1_hG|zYZ%MjDD9;(6f(i!7_x~@my`Sd}&5MkYkAT|^QqpZM zK_(L%$#@pyI~c#l_!Gv5v;4`7uV(xx;~YcXW_Nq+_=vZA=9Mn?Hpe?7Ym{O1J_j4( zN4JT@ClK)>4BIB+#f3HEV2&vJ_G9QDJG~-eUJ=eCMzhUjjtB#TJIvO@_IafP3M`>_ zxOuB321rKSt$O`fy>Q^wn(U*P&Fp81FIphc=WAOvk}utKw51VK;z@wth7DKaXb!-! zG;d)F?U-3hU-}qF=O<7$PNi>C97p5Wpjy>Akn%SCq=S{E8^2Qto*?k_V0(f<7pXCa zv*kh5thsY4v?*Rr!jZ9|X{fguK1V&_(}7_{c~mK*a<~;W-_{~}Ah{?_Pg`D^DpkK( zcU<=2ORwZ$I`in~qLOL^(4eJPG@Qgp#TZnqT(A{atRGP~QYz`)Rjj|b^3H-+tk4k$ zOay~n-~0Jmu2}u+d*4In8K1}n19_Gc1!TNnDOZF;Ibbs5WCi$3KwU8fu$bNg--i_Iu*zl{d%Aq)U_k~cKcE(+ERG->+C8&4gXJUDEhAhY{Bp=~?#%8dA^b5rv`$7}@J;dG;D*3pA z2^TQ7=Vw*mk5hvDgf9XzH5kH-HNr4Pe?Gth0By=tG*Uht>T(#WNLPlEmD&|hq!X@C zPOfk+;t-%nA9K%JYTKRUP?{2AzT8h;p`y;{xBt4rN;cqyjP0(l!DL^Jc$rDl*$d|9x*Z%`eYt3v*2QwIv5dXg$CAQt2VFB_sDFZ{9H5J3Bs z^zV^~Wc30j@fAzwSmfK?2rdwEWRSsEPzFmTqSA303=4mHkjg|Pl=6w#Sm^s6qyE4F z?=kg&Yq@ptlB|Gn_PFJ)ved<{Z5Na7HJq!Px8Jqx@P%+@MMDm62|0A7T$AVt)-=)+ z{Ki8M75vp0IvzMswcy<%b(VeV<<#lZ0*_}vhorZhWfW5nFn)|N4M%8@Rymxpg;Yt8 zmbT5CR8^&)4?vcVMW$xVv`9xC}(Y@%0ep?r# zJ&^~3;6WmMvzp7nLJsXY|vGf9X zs$bw5NE2miR)$J%!QPESof2~B48?@qXomW(#$eNbI+V;%sWIlTxW;RW(jbGu9@G?J z1;Y7m++l}rgl6>41~zw~k#h~X-67<#mhZ;g!If{eZX+n^4*8aEh_q#>JSEsObUzpm zXv7wukD%EVYg$}V{&?wswfbhO>sozW(W&*C7Mn`ygZKXplnlK8AEB{X|9^SA*V^Ni z6G|r@lV7Dn#^~r&{qKt7q-TIjE9f>EO)7){_%&6h@^#+xhN4^C@ALcrc}x?G(m1zG z52#g8m`HcWfn;!HSF6;IN3l|LURX$$$jdQkLLmFB&rg}_OBmn9coXC8jNfD20zEPU z*$2Sq>oXY3cLTo4*AFxPA>%h0|C2FxqXit!7;|C)3m9L@SazV?%-63m-pN?@5$J2) zd#PH+XEHvI@%4-!VZ4p;Ta5q3SoRqhf)_CalNc{#d>P{g###y@BLKI4`=qp&~YI>u)+ zmK`K-^9*t0Nho$aoRs3SBzFmR&|+Df_}81miGijRnDW2r}wh zgW&2OnvqbT#KMD3j-XE2L81oclO|$G5W1F$Afjjud*u!qkUQv*+(C!t4jP;tBpM!` z9YhqZ;gPw6Fus~2U1r8t%)D|h(>TO}acJBTPczu+4=1fo8~98pVs2;ayNgz76t5F8*!5F8*!5GT8!h7KKnbkJ(VlS1LOQ6a?$OO+1za>285NNVj_SAl-=CgLKE+ z9@HvZLs4EoSjA&W5PI}ElB|c6?6ErLj-|WB_E?n?YaUJrg2w`FT_V_5YnrBB$H0Sw zWwaO^p^RJ{+zHJg!}J=0yWlk-1_VVc^6Pv2cp@~xCX$#`kx%HukoV!QNB6=(9xNly zXm))%2r_>B(8PWEgKUaI55DNfn0u>Kg=ydO4LMW8X%cg5w=q3A!$D3V*#%ZWG`oZ* z*l-}b3JcIjj&S@|^g?zO7WOQ=0=*@>#N0Z&P%Q|7vkNQiB}=2(B{acogo0-m^ud8- zgxRHhx+KW>8Wr#-nO*V%>{!-GhS#LDC6_EAUApZ<;=YGxO?{kTdjzeJmeYMQ3NJD%F>cA++5W{>af=>37s++5t-7 z2cJgXP?k3q@}OF2D6ht%-wIj>V>b4~Y8Yr*9qNPl%|B!O7GeBNdEI{jS!W5^Xc7I{ z?YV?B#*UBuX=BI%q%l_67>i>)Nq>ruMGcCXl-Jf;0m)4+;Y_~t_ zZZEBY-P`ZKx?{U$Tg!T^_)LfWS@+$P)sFkK?)I`uHT<{rU|6Pj2%YBxPj>&f+lX$N z177G_+I2$ZhA!JXckX;<;ZaywHAS`yuWB(ynv6ZEe@I9h&vGt<95V z2b4YA;>;Ek+aBEZtcpil?JECu`Ihoq%g-yXH}C&1_SA4v1@Hft)6Y5VbxO@ z#(!cg3rBn7sf|E?WrDDLArkv20yZ%I2IC(veuMERj62ce65ue#6B*BEd{ zo$-5&ThQPFpbul8@fnO)GyW>$hZ)NkAm8Nce=_b2CnGqV@d=CP{g##S1`Vh@pl;ig7IG%x90MC5aaAQ4yNEz zaSMaYAjqekPk|B=dBu%!8UTdCxgtTjisK+uIEf%p!@N(iYS$q6PNJ9W)?2NRoVT?x2D3ps0BdjR%P^;Og8#cn6!XJW;grcnO~)$TAs3 z?l&|$mIUD?d=7!8#}MRKG+n0C5ClP(gYAll9x_o|BrMej8_9;EL@B;YZYAm0Ry z%MOwxkIxPwiWYc6c96uHoE_x*Ce|tOps4Z~piL&3W1W#5B=^GP>qsmukYWK(w*(Oh zG3(<&A`EzDb`Vi?EHrzK%sa>tgm=q1g7A7EN6-P;L82v!ULuwRq39)oh@zE0BzF*s zUXEBOUpazMzH$UvCb@`)C|@~Zp?u{C@^c5FIOK>0pU)9=eC{Cle2!S~`5ZxT>>NR- zWe15h;Mj>+zHjntMs^TUbXLMe6R{)+Em4lyC2+PJL2$MlLA@+NzEK0dks}r|B}Wi4 zB}Wi!m?H>fIY&@^?jR_iWBv<#K1Yy8L5ayAk}105YoFGE?m;9LV#raJ9Rv!TK=C6Ka40Gox=42}0%r5Cmt71Vu9ieVa&7G}hpFP&5|m-bgG2 z0f6;TN|;|!fwXT{GL{HEDmw@i1f#}|+)MemHbKL165dOM0T0g(0tG=Ma|h{{^`XG1 z@_u$Kx!O}Qer)@2jfuA5oH_Oco0&}9=_s6}(LU!%FHxPoBS z3V@Je9LUknLvHFug>?1l0O%eKVaZ)n{=^FjQ?4dyD5qID%|lv^S@weg%5 zjkMAQ`H?aUtb253&Yf?R$uKroh4@XD+a9b$KgK-POS!4F_YK)TyA@+yy7NpptumG= z&uCV~2keC#cY&<{_9eCGGVh)vyvq)Y9%d|0-z3Lf(8X^2E_i|nIjSsoK}99@wQuAV z=h%vF1}2zF{ReYBcQx;&Pv-zhn7P`&s=N(rP}LSW{C|O%ER!P2QN=s2XToy+rNWC6Ww| zQdr&?JzsDU%~xAY^VC*QB+|yn8R88gkD-m*gEm#9rDa(p5dt~$NUs@s1|Md*v7G6| zA#(KCorusFZ(IdfPF?VkW_;3=<{BKT2lqrqinNJ?9vxv)OqZpUre!quakl#BGl86; z-cYh?3$nIUP9m)+BAMmXkUbWtwPa~+6Qo7r7iZZ7QUwA%FtZV#Wo0SZa!P{cP~w&Z zdwK-6t)uya=Ifj*u_3+KM081W;~)}H`RTC%O$h9Y*@!atQ`-Zr<2r90S;EXhp-XaB z8&nYyvnWT??eW=7d@yx3(W<+?TlsM1qRRf2Z+E$)%d{@-yS&=@#?BQjUnyT-KDxYj zhaK(jXg{re`}VK4yP|yG{2iHI-=%%}JC(og^g)L{?T;-rRs-7L!4(zs0y?gs6Frha=YZ2L>ZP9=&6WLE zcSkW65Z3Z^+KcQh`~im)0eh3O!B_20oWaTT;1RfkC z8*a4dF*02f_e?u^mYqy>a$Ja!y(mehYm&XzPF|NJ8zC1Z$#hLZF1C|5+Q|>t$&c8{ zkJ-u3*~#!|iv{1almBcdqg;~^co9I?jf3fgy%|k2YOP{gP@@^>2esCQZdZD@)4+@L zk^~!`OwF)s-x?{0#M_q`%Hp7;acBPeQS@+IYjn^V>{D*yAo{)sJak1t243K~fRzRX zeI@f)9_o+Ba-{G7j#=Z~Liu2zmoNE%FatS0NU7RxHQ|E8ybOFmI>qb*-%t3!t-J97 zR-j-AHju*%eITy`8x|zrqsG9y!YH7Z3-~XC)PbCd2Q!v_v-vPXkPb|s^XOw@_;$)H z-CLEfT2H60BKn;(sN*<;`oeRlLyr&GETz>oE9e|+WtPz&o*Gt$yJ^FRa7(lmI0UNb znzg z&P$*_^;C2fz1Q6!9FEj(aBsp5Zp&GdnU;)GQ7}Xmuzp5{ojwn4u$e7Iqb3^KGDgP{ zFxDV-Hse){*E7D4ak8)5ck#P}skTd!r+;Q9^x>0% zjHMd1w&2}phzp5}8C|}1HyaHn!5YTv8Jm8g}@9KSndW2gPW( zTc+k<3#~dvGjWH;Xv#<25iy$KJ0?am_n2v+&9czu$7n|0q8QD{TWg`Mv(PqLXb)Iu zk637rS!mA%G^F3?@=id5zJ~T*K=XZ}VYk6#9{rhUy$pkUE@M0FZItZLxaYE_x46{c z!0x%sJ(s!XvL-KM$!e?z-@!eXHL0oHa~aH;sd3L`WCr__8zvFUV}X6{3^?Sav7G~N z5gV5g=TLaI!;~vL(*c05ndf;|(u1}1CuyCysG$gVKUcmJ@S;LvwmGyFkXFaL&mgCLq~C0^(B5SpNl`6EeqlAx-7Q{~Ve zn<_7*VD!FT_}kai&r=oY3WDS&FB>Yicj?^a%+3v+-tO4Hpq8}I+GNCB7V_y6B+ zJ*)@b3&I!h#t-_5zO9_ z+l$Yrq0dy0=vjKPK87yaF&qc8?K9M$?t}xmXvza7O1G^u3S@lLyL15aVM6xRZ!}+U z&)3Px0XrD`$V7ru882ac8RI(`e~a-;jNf4VG2_-LBd0Ipk&I7bd=BG_8Q;qIamGJn z{6CEU%DBA9$UTtp(Tq=IJfHD8#y2s3gz*cEf5rHH#dxZBZhK+gCef{dwDAz8}>8FTJJvQJk8 znVRnO8WAulhwf} zJa7WTg&d1jpuR6cUbJ(+YA65Hki`?UCygf>l>iOgV}U23*aFaFd6Ii93pGV4!MOa! zLkArr^KE{&p>hNcX4_{-JSpB|**?lBknv$ZI)M2wA^Yk#*<)!MPkL6;SA*=cxJ)Wa z-s+pwCOj#0FcJ_7@dIwy)FPW(Xz_%=lWJ`8gM`3EsTH4 z`1g!u-f}1IlgeD!F?@X{;}wjrVtfx{*}Ll}eEnO-@@=j5@KOT29R!SGd>Z3rj4x+= z7vrZGzry$}#(!tr1{qG!pK%T2Qy4F1jAvKC?Tnvb{37EWj6Y%=Y04>9qU`xTM1c$~ z=!kIpMDe+hCCPZ;w{W+xlQ9v=!rdxL*3~1b$QnHO2HYXJt(}a=Lkl++Ls`gJy<{O* z+R1nUX5r52VjCr^?A*QV3KhdXceI8uAP~d8VDLd@Ur_I-fwST>GPSw#?H3M~V)B zons1xG%f=cYnwN!0OhrF58JmVQY~nS7w)e^wI zO$InpIvgAgmaZ;1cxWL92m1Jp-(_-)qsnrZlVTh;J@Cj7-o+*Z9AR2)9C*?RWi(jy zK&M=?)NV1fSRqH^E)=#uX4f~nU)Oy=_h#LH)9w4+{@T61-Qw=Yv>V_3zq%jL?(c0o z_4t0*_LXxwpIO$n`O_UQZnrI6AH4s+PTQ-Ex<}(y`u+dat?q1twgGmmr=ug89HTD+ zqgOrlp`7Q!x-G$@cq``MryGo>&*lumX!^ImW}5zTrU^zYjHYw!f+3_E2h;g%E-&aN7FA1$m9{IvrYf+kbsO@Bp^RiD?*4Al9vT!3=0O_ z6#*IMCbD=!;7NGM1h6NaM4mJk*+KwM`abvhTQN;A!uN+ae9#dG)HAt;j_u{*NxCye zK?Q@PC%wR$yux@W;|YxG8Lwn~1>-vyKhD@RYdeGNHCc|kJsJ3or`-UwH32OZ&?5d7 z&>jrr6$iA4X9cu~TMQvDafWalWGk~|YB~ma><6?? zM3X!U@;Jppx!%~*JeGVeR9;ISQ_^%E51fs+=J7lpINKvyhV8)V-AAb4d5j_aNIAB2 z`9{P!q@-WO7D7pXhD$mIq5*<&4iC7ep^rqBfI z+t7UsF5EAiSdOr-8I*LtaGIGrCrf${R;VB2_^4o@x3()~mzX=qu5g&C!BS|^Jck?^ zWGKETEmDx7EplWiJvh*$uImfhG{Yt^2o7ZG=OIHsCOtJkVFF+O(&S;rG6;AXUrSf- zTYUWz<2M+a#{=Itmx+f7kj;U*O&4)(BXH{@_IRL46q2do#{(ljy!CRj_|&#E3%%wfWB zP^SV=_^L5k^&DUn!)T|5u<8M4L~qJXv9+#u@D#&&kRvz2k zQQ5wj2pnyLyHv&R!gL^M_i?|l3i)u9VafOg7w%V5DH=$dJckL=!ym-g$1*m>cc-L7 z+6Y5dzTryL7~+P(7wF}cpUzoy%fgZHhPXv79J-wThFX*cb2I&Le#?WVi*O;+1mkl3 zp_>cdqM(O^Bkm3#iy3_iDkvn~;bbl+XEC0}+AU@L6~ zzs&ee#(!g60gol<$M_h=Co^8e_!7pqG5#jwZH)hy@rR6C!sQ8iGak-(3gZQgGmLL$ z{3zr9Wc=TZKVXa}I6zOv*rx_CiSb;^&RSJOd~&5E9<1Ga2f)*EG@1@5%wz>f!W2~n9O*V5qSa?6i;ZqqJ>CZ4AMDK-Yo&B!# zd+I%cCA5v|V%kP^Gr`CI+FaQ{2lfZYvp)Drnq8x=Ay`7I*XI-Oa`ifaX^Q+^n=7{w z|CPkwpZSNC|6cu`-~|eU=1U1KXx*9os=Z2qkbq|i)(|i5eHF>UbKK+KyjT6Q`0MKH z1aoN%*hQqD=&95v1V+e%6c0LFP4Un*H1W2p?F6B|N?k+%qgDA^Dsiv%5q(FL|5*K) zz(_Q|MQuB)MZ`<9Qrp4~Bk25hr5Ch0t<4Wg?<{|%#hdAW6(7~6Lz@pO#aw!LCrr0g=&;{L$E9@0%+-4@*?JvaHVQ2eSs*R<>DW;6l{EJW zRNGy5-M-{cqKOE>Fvo`$?Pr@#{1?0)VBrxBF|_^hRmU#0Sr;Np~;-YV5)z z-b;7niyf<9?~+C@0kH72)|F{_MqW`gu~!;B0>JOz|NF$Wc1OCn;V(13oJOYr@SD}I zH%}j6&ec~}zCJ9CegWWx-c%WOH|Oem_uhC*T1w=q`Pcs;UBw~yz5c6Bhymi=d3-uJdJV% zxax=*_odMb0qp$f^oI07=3K4%Ywe&kI`DvvPu%|NG5(QtEq$WvAJa0WW$D`2?@V()OTD$?nLE=y zv;FV$1otafVDS$ z=bH3s=3LEOy=FujwHaXNj=TSqM)wC${j2xyNu$#P`1()gjZ34e1Gw{^j|ZgD!2x{m zNtXlD)6Kc^XAQY4UC$TG51n^odInz{`}=>qo}S4U^NB!IIpu7nATM6!(04?4A@Tjj>fc81PA_HMwrlhZyjUra9D{V~2IC zlzzmtTDSO(-mC}7n3LOBgK0BBW6MSPWkJ74ghs4%aD;6)_8Lg6=TeSM=j1FPZF5?` z4>6xlhx9kBrs>hRc?VFiaWeCnq{Xx$y;V*h&A5ba*hor`qO4j%4CfM_Ll_VYtwz5c!auIgjs^ZD?U}QnBKMf&5pzUjb=FkaNX-~&(w||vIw1!*e?3$`2 zAo~F~G9e&md@X@~@VrlCB9P~9bIF=Ai z>}nfvh>eKPpk_rsI!^R`MK?PpPBb@~X(P_E5$D^83vI+jHsV?vah;90(MEj0MtsCZ ze9T6C&PII4MtsjkR9;YSO{t2O+vBLLk%6h)nqfP09!~m$p$#o8w_&q`9*P+il;J?| zm+CIBxfixGg;U`^0G|%I7p{|F>K7wTWab+;DNC-6OfyE=v@(s&L?-TsWNyFWyvk4h zV`1_AMb}imOxHupuBpUPf6cS!J%0ZB%9E~NSln{nscIYL%O|a_(bXB~M$a;c;aXIm z>Ywe$tv524uGcbeS-8Fu2V|CBb#)+f8MU7i&Rwr%f^KA*fmhKJI#vAT%1RuF5B%2| z&z?u>sN;5AS$Y3e3yTk%d@6N-4Ii%Grt5p?dj9dZl5WU%Gkz;FiJ(1b`=YBVUqRY` zW%OKj_WDX3I_byXv&Z9dek0kA8Y+GbTDq^Us;bfA7yz_5dUx@>9frGqL*HpgCd1um&dO7r^ zrjcnz+M8BSboxR)ajwe|<&iE&b(}|(S3~KLJc^a06Owsk#{ET4$OLSEkY$qpz?QdM zMdgSNoktH{881g)C4R}_C4Eb}l$4k3Dt^ECH^r|Pzg+yC;%^q;Uwm`% zRmEQ_URFG-_>|&t#UqLb756IcP+VI4kD@;ny-~Ei=!Zp{iykRzD7vBOD@ErQEh?H` zG^J=v(a@rUi@F!JDJm}dDD{WbuT!t4zMpzJ_4U*psq0b~r&gurr_M;7kg81$N%c!r zrdp;{>I3h$-Y>jYyzhEXcpJT2ysN!0d*{;H`cu8*y^&tEcc9nNYwmrb-cxU?*VHz( zMLkO0s2kPgYAwCp@X&uuNwa)9TOg(1|EJZ5J!*-9P;_DH>BMwp4BN5S&AQs0HTEm- z(Hi41VWPz-x|H+X#TwdUcN{LWH>0^d>f!T$dyrDjs0qE$ke3GiasrS0Th zysZDfgy^#Vzw!lTX5uwsCbQ6q*1^+?e@s-P$&?=LBRQW|8LgzHg-5GF^mm^AJCuG8 zr$r7+X}#Y(q8>&onyP6A=pfpBV4|9!rjlCoX$cdSWQtfU>^@AyPR~oM5jKbf52L@K zBwp4_t{{bG&`o6JE6sSIb-Xi0{9sx_HG|ei%~40HA+!_&D_NK7^;fe4eX#Txrqap8 zdM+&9oT`(wQjH^KQ5=iv4;02DNY)%mGgjp;pe2oSh}A5E#WGx3B&ie@9M?;v=H6mr zo<+e+={`$oMdeB|p1C8GT&@KzCtA3m>kw7N)D;vLD|JWeHD-sBUUGnE7OmG@MDbRU zlsUu$^;XgKa#C>-?b2OEmRd#TSfn*LheFS%(4`7zkU3Oac8EHHQap=}p(G#2EczQp zcQg{9Ijp@x>krw>>3TlN#0p@nRFnHJCJyYSA=bm52HnLFJ;_?5)fCE+V*>{43ou9+ z`ccpcG+}t0KE_afh#YJ^foYyPyZVpuT(yWOxH_TRK@8={n2=lwkGgR`j`fY;6oG6%Z=U`GJWK8#pV;OdA83zuQwIcgG}pmYv>KQ4wSPCB2aM+^)t%p{`zG~m>`Ejr?4bIB#sdNuC2eMg#(mPTdFA;iDF2I!G zb^}UbD;CT-*S9^8EId)OtRzX29oQNK3kx*MItqxkMOdjTt;`Cl6m69(>ixykaAa4u z(rOI}Lc_{eZjU`!Fl&u94C|)T0u8Z22sR7REV)<1)BZHH6$!Q_(Tv%fD^Qa``;K4_ zl7Jz;xq{ge1C2f3YMCxF*hNBG2vHlG*I+`sW;~ER@nouZ#|3I*zmYM*V&5Qu3Lfn( z(@U77Fia+k?%qwYvkrP3tShoG^bV8O}3IKKv!Y)FZwFjBR z?4u=;utCn@5!fppo)6Zs3p;jl!LBYWO*%;ujIDsMEl?1Qu0mL`VrL!fvcthB ze5g;t*te8pnGXlgrod=y?81aqD;zwJIs!AO=9bvler8e_yods$jR!U}>KzS^w=LKt z2OH+F@c3RKW`7^-^~1r*f`uKXu;?iWHrr~5YS{h>YdLgqPjzBeE?`TjPQm~moCrFl&HIJRxV7EW4favKr3HMVm)T9|U} zDPwj#!tO^}7Iy1FMG|heAkwhe)j`5GFsrQ_M-~mugME*%#EnypELcOGA9Szh5nI@p zVOOT{!r0i(GG=QhZ1bdL?tXI{r8jxPhEF;|zI(YSy%!nwCDYLkBwww|a&GL(gdLhR zOHXnQ@mVnzb`$FxWk_}euyNNAVL)Qg@f%HMmmE)4Q`i^|3o!L9?VBYjg)OQk3lsE` zN>B8{K2g{+N=uO*fZVc{RKN~BVJ{O~tW^nnA2LI#shVu zVjg?KCJo!T^%Ryqx|pJ3ia$qUPq{9_)RWvvp4>zuY`ljB_MC&*6C_#Wv1Qj3VUZ`@ zsQpD2w%sce#&9pIlelh(u$_HeHk(u-!iH_oYK846(kr=H(FwY3Lbo*B9g6n;r~SdO zSC~%FNcI-#vWL~+DP*eU6dLcoV_Uc*Byw#ea;|5Lr0ZcTxg+;Pzy#6&Mwe7$yF)B; z*6L^P;bpcN#0o}sOKF2KP+{vIVOvA2r-3zZ{4i*o2|atYD24~ttPbU(CY1$n_6yYIjiM}mK|E|EdPG_ZRO{dA5-3}d`H=) zvg^yvEt^tycv<_hzqa^Ui*L8MrN#LzPH!>1MR|+=YyQ3F8=GIz{G8^Kn^!jfYw3?m zHJI>EEIz)>fR-Yn<2a5c%qD*+>AsCid`fk;E32ha%6Fki-_7sTc@l0?L` zfp@eLbpO&<)sP}Yfl>jOhu^`R--~?bI*V3aH<`%4=7v$Jg zS_D8tBLH_70hYUup|Pjs^ak7vl$fV?5rVOlytEie37AcP7yy^Z*u^Zb$aSRt0*aa~ z0%Hdw@j|Iob<)cAtNi$1B2kk;=Oy*39)K`5#-!ZjF2z`O+=VH zubdx8*2g39YDxws222k;!3cutc?rTG4W76$g8+kZk_2RCUINCD7ISFK8gKgW3K~N( z0~N-n_P`Fc`dJ@a^kOtV6aahVWyo5Jit$8DH^5N7XaGA@S8G*FZXzdQF3C46Lzk9sQK@JRXR!c}1Ph7bFDvb8bWg9Q%<+=*MieXEx1}b0QNMpTL+1 z#zAJ&Ft!={fF2l)-Xnu1?O_DKpr6SeMhtA3XTIPiRWxIbfV>Tg%4Qb1d1y`}0Orig zKkHb)T*B~=(3miOULvx~h|vLc#bb}r53E{Bxd*f0JOq=aHKe{NWX8EobofuKs>$V4 z_NvARX--1&O2imlLQy_p2Mno&mOv|$mk=ZbmA(`*qXWjQ^YVtVtix)`2TW9@`fU#V_P~FVS5`$O6d5IZM)gD?AWG>!^FQ+n#N;ymtMvn6m zHBJv4=N3eMlwS@_v*lR0w8{f(!}bT9u^D06vMIF<*=(eX409tc%dekv5}V-kGF`47YMwq z&MQ&l$b&H2ohxa0tDRTU#*iao&Xz4^OELUOIMU0RQkWo zZfSOEvx;W_FTFlJHeH(jVacT>btS2iEyZh#k0^e>=o>`~iv|?Eow_e|da7sYzr9<% zGrdmU&(y7|K3M<1jO>E<|5y_1sb2d1|0^ALmX6lfik9rt_Z;a}4&KZNy!7-PU~d#K z5C>QgDBxByN}2 z@OAk5+C;>7@TZu6U?S)J#u<|ru<*dVMlg}{ejS+oF3$+a`(+KJdF|0;?7=HRtmnl$Uc7=5Ap^B@;5FhNW}k_Od>?|9LMi$V8~~C) z48i!_SUvk=o^~CqvXZn9xLowLV1&Fp*(6UGA|;5ddj&t_eIvV0gs;P1L*#Pqw*? z0GLa^%SO`WT{g}`2+bwCK8U$cc=Gc6TJ8C==n=VX?w)tisU^>Y-(!k~d6#YkAYWs4 z*KQlrU#d(jI*iVKG6GOr=Otid;Ps*UEhw^iO~-f4b$7hPJdf1Jq=*%IY2rYdK?Xp? zJuI{);-~aD6FKh++q`{+-B;LFt!<6&sRX)lh7UCPv`&4}&6CK%}*PYQ1m`%0O4Ek8S zOcpaDPyq50f!Q?JAVGY=M2{CJDoMFKF+PdYL`Ff%Gxcl8;$cN@qUYTUs?q+IjE+K- zXQPdIe&GUb$vik&Uh0q3{V$Y(Fy~Q-@`?<*hpVXN-dRN^m&1;y$Yuu{)B|P#Y&;9g zNRzxgAiFg-`B0v(09$Vq>t{bzgJL4)T>@$-?U>moX*bccnw0$9*F=Wze@ep{0eLqh zxebRhke3^c(S2|1WQ55o(kdD~P!jSIg5GCXj7;RbOTifJf7ppD`ZQvMWHl+d+!^z; zOoJ9(!#|L=ZUf|9Dr$6lIFp2#M@bVMB_Xc~aG2-0l=?=P!em;4qxAiubzUA}tB;L{ zyd7FZz^$c9HzGcDm2RSc2Ddf>K9gG;5qZ1y*es_StSea~CU3{i7IT;ugIz*CLo02f z=bfeD2!)B5cSgns8BFA?mKoA)UEO%gY$E5Kh1qR_5r7N~XB*RPad%z0LJJMHCo~fo z*_L-P!X)8nglvNcI!;^ zyz78EdZ@q@ZREln`a=iDh{)T8CQ=-8 z>LIi7p{Wy1WQ-J;$mEN`qC@S&65B+^3NbUxVD>hZ%)@FAEueyY0prBD zJI+He0>B zl*C|VM`+4M#6KtO*aDI{pJNEmqM7sx0FcRc=jOT{S5Xu(Q(61VL%fZ*8fzQ zXQF=w`x^nD!T#pmRo)N1VPkG0!fbgpC10>NcpHmm7xgH5J#{y|*YA|t?%m*>;&t(M zsC(3XrBg~fm2Pi#L$fK(S~hzreNFnLber_{k{e2DnG+2qWWW(SfcVR!a%={h$Urcs2^vBy$8tO{APrDNL!}YJPg1 z_Xx*CPh;~ZoxBNmDs=|a1mAp9$C?&Dzi&is*$~seeqKhF4vjQ@x6Um2Hk=jK4hM>9T=@qEVX7~jPB5ymet{uSf*88_$jS1~?{@d=FQ zFusuS4U8XR{5<0do$DEF@=uVdfp`i^l2J(pWaZNpL8b;C9*{#J=pzSY-=`~rObzlf zAcx%Of>_9%l4Qil)bReEM!`^a*CaPx6L+^5*#{BiUi2H>GEB|@I~fy30`81YR|J_F z-mx@ks=*W(aEA)wRarm|Z;3Yu7BXrU3mG+)g^cly`P#YzO zde=qCA@}GgIpjvQ8r)KI`_N=ul&m!&vhv4gk%^G0IU!08Z#l(I#*~ZTmSHMSx06x- z2Hc_SGo$2C2$}&4H*X0Y`XCJ8`}I-oFqJd2$VAB0Onczv)q!kqZ>2-|k7h1KnU!`T zXlGIs2;g?+p5k~rvn5A6gD8E*T|W}-Od;DEJo({3{Zc{4qrbh191iVFrsk|4b)fT% zZw?A90H6|z!aTNjb0|#EhVguJscvTc1mpk5_?S)zN=rWyt@tp>2ih~hD3^N#Zzt-G6Q={3t5~F-u=GAym4;_RD5)*T) zAB)*j2je+K7!sNcq@Dh!(w5XyX%Fm0^jNn-Z_2)gr6rgL1kBhndmKI53!`ki6ex@* zOry~iafQ3-hfj=SzV#VPu|sElm*e5$olmw42CdtO`m>43r#tDw*> zL0bSTAd>!MP7(c@J1;)Q({66F6$a8`!#z!$ef$<#1 z7c#zq@k5NCXZ$+j_ZX+S;_SxQ>~f~mc)p&=_h4D`r|DG{Vrd6HTo`*6X!}v_brtw$47EW~y)FKhd-XORGaznr! z>o{)>$nhR>Kt_2BgoO1+K0V}2Af#(lNO;Rw54ob2m-!4Fp?IeGQ8#CQF0~_66+!F2*|M>awt32Lk?x5wh3+-%8qrMLo#k+;fBpD z{)oJJclB z`wnGKi3;(3x+2Kb#CqQuUsH)3>wWJC$Y?YIO+wkR-giU59qWDjL^j9tsF1BhHph%8 z8Fe=xQ!|r(gIiVy+z{|-ggW|p(6S6 z#tOCQPDLFE1A=k&jfW1x_xTD6q&8I7Jp|LV!i3vK8KuOW=Bg0CsCA>TnJ>P_xSBBv zBLI(ofK>*m^^EUhoNNQ&3ZeG|LoKN&S@|Y%q9J)`l#t;Afe=67M*l1zhuz{-l_t+N zv8n6Iqm_7j2S}@}7_EGOJn1<@(Vp}id(t^f6O7AY*Sdnecp7BwP=x_j8>Fsbd_UtYjDO1b_l%{Jo%ApS2VO8*%%SU`fV{KT2+7o> zqTFF67wf?&U${v?D0@dhj``!(fD8uim2P6J zN5AM?ve9JUN^v{MK12REAS2n7t3dZ%P~}}hIi+F8T)!FTBCKOzN3Pu3YQihF{`YD3 z{Bp2&ZH?r1;3eX9X;qKa)-nh|-D~NAgd9Gq_H9(`<-6VqUR?nWPh~!c9nTW~e|aGM9z=lb2{(WEd6Ix8y^R zk2DadA+4(7IZf1k=66Pg1!dEEZ0L4e*ITr&2t6vRxMj9tvT0z?Z57z9bc))=_FKxcgyMSZBpP%CA!t0qP z_@g#w*7Sa+^$6-!4Vv0=0Bk97S}PdJ!GMw&D-I>8@l57Y364{J$_N!8?pg zx!UT%_(;YR8P8^X0psf#f1UAG#y@BLC&nc(2*Cl24`+Nl;~9+CFusoQ*BC#?cst`i zGDcYfbYWc0cr4?y8Lwizp7DK*H#7c!jDN>?7vqi?a3mPWcr@cP7%yjhCF2Ih-)8({ z#=l|w3FCHnfG0SJaV_I%jF&S03gbH&KgsxI#&0tI8{-Pj$9{~DVSF;i9$ zGTz3x(I=v6csM~O{zTN^)4+^AkU3;PBg7{XK)*i<$;v01KA?KQjq2YV=uuh7n64X; zwE!YyYGTh%p^&TxB@=||DG(BJqjXrv?d;?Zc5){>89fq?GNwbgrE|-%eg=Coi&-m)Ob6?BwNk@=7}yPxlrJu1S*Vni7A$ot#OMjZH32lIfa+ zY)g_2_e)8#;YQalrl27oVkZyGBKx@I;3V1H@^Cx(2s`;GJ9(I$JlswmX(yx5$5Lq& ztWA6X#%*3&t|JCUHwUY9B5TdcMJ0zY~8JlPiCNF-$Jsx z<)&L2`#0@$OXVA>rW6F_u=6M(Q&|qnO30mxlEWet>%D~BB-bV+)_b{yybqA*kA$z! z^6hvSA#Di-$!YLGgzR{K#Me|J=hYtxxVuxdKz5k;tR4oWQ>$d>KFm&@ZYS5<$+M$m z-=`~rOwAnnwJ10*N)EZRdN0VhOiiqt5^^t&3JDV*>%D~BvEB>hnj_YG(Ig7V)HF%& zWgNMzCGj~LE8INBjU>^Fi6oiyBXUs|nFuubI7v1_V$DyO${v^e9M4pyi*M{f!u_K5 z51votSUWSG0#E`0R9eu^;FT+k4Zst}libdrvk4H4bG&lJ%Q76l+fX?I2k83}P{?t_ z+nMd7j1Cze_M-!s4->Mlev|D?)4XzhR?-)L&I`*+iqaQ2!B$E+Ao0pI zZrC&s5^{6BDxRQwEbt`U6TqHy7tM={dXfwfNj$+PpFF3g?2u~hJ>o@$@FdijI6g0) z)C&1P(2=nWEtlgfjPvPsuj4x$;JL4x9zsPt8RXeUpylTWmhr`pLU+sUWe$*0-LXV}SSCCPM6 zIe)gDJi|_&X(yweAX7=}s$1t1h?Q%9w9fI@SOG_hxBzyJDHPJU3|Opj4osGeaG<}5 z*$_wxAlly)nqb30eUd@_M&K~l`~DZ|<3OWi#x?K=`NAlpNyevq4Dj*@G46 z#~2L=z_cKN-YDvD8!>k<3{{2zg0yO|xXtkt5^`kZ0U|n3bvMtnyIzY4A3)&nea5xL z>&k)xdQUizsU|~_fzg2~3Me2XGqh&TQ|RbP2bw#9uYLFF8{|9}*13a<8y*3m!h&*+$2+Ml8n3v+O1qF1 z_hFfh$6&!lv^ zJ(V+(WV$AspvfhVm6lhx&Ung1^#kD6d06Q>ZKN+`A8UBMAk79fp<7JF5 zXM7jqrx?G&_$|hNXDqul^e-~<_3B(YJ%z6qGrpAZ?Tnvb{37EWj6Y)BihK;vhw%u; zQyDL0d=cYY7(d4N2aJEs_%DphcyP2A<6(>^GoHuz%Z&en@xzQ|5%@3p`p=9@IUjm3 zK9cc7#nS~Pcz zO_tjYyV6c>lF4e@?6O}H6f>!>%H<8^E-* z{J_(@b%-wc#ThD3#QCv1E@uH}0cQbc0cQbc0cQbc0cQbc0cQbc0cQbc0cQbc0cQbc z0cQbc0cQbc0cQbc0cQbc0cQbc0cU}KI}3bA(ffaFw4Rce`dIr5Xs6%*U()?l4?F7M zjr=q^ZlmKmIs~5i;|DY3JQq%5H)Jd_fsizIOkN-tSpI}{4>|-}F`gqA=h6k1KIjlc zUMv>~!RiMcf{Uecf!(69078f0;#|4FeXs^Xhu~s`TwtvOHe}QxSm6MIzQ7_Htgq1* zb14qi;OGm?Hz<({?0JQC6gmVKtN4OW@f8JZZ?dCV1uY08D`(L#^&-iK&9#!WdJ7G! z!IFIE@7lF1N&Aw8w!%U?&q6!bLc7^QTWg`+WTCCH(AHUKYb>;t7TT9BwAB{c1s2+J z3+>H6&i!rDeitVAl-jimrDYXCZ^94OZK?ct)RszoK6W%+uNnTJ>RNuax|(42m@Sn= z(vT@0BBxmxequHb%nDNfLon1Es&>$Qd^&C>=C;K4_&4uWzbsy_))TCt?TEplqmC*4Piq}Bb)9$%fwJZKWeL#Tlyugog!N;=UUAj*;H9yL; z6c>dj311_4mBNtrTSVE9Z;5sicMUlRcTj_!jr-a+@lf~Bpk z9;9zj4O7DiCMlc}LnBz4k z)js`B>K#=|D`QJvRpPXz@Iys1CLh105<1*Pa!{#F``g#Z>-wkr6A;A;H&PsH023GO zCcLq@2z&N3#^0TM7w#;uFD>x)=hUCSM6iZ*M_qF_)eopoP(S=gRirBjV8b(4JzY75 zpn>>Mmo&uq+N9eMfbag*Pgjm6*hV~%c~6XQZF(&M_&z!B>B>O_J0XjXdt-c?(whjt zchdP!SH4TofBcq8$h?pE!us<`^&~<0Z#U_BYQ?M>i=_U1gWzN0Uq_B_k+-w2)|Ueq;Ti`8Rc|LTzc+f<2qb_emHF2Q!)VLkhL`gMX&h`#7gn<}p% zC?#7!=7TZ5C(=(4z}^qPx2f_Sf@*FH{CJanmsZh!e^T^}zaKGY?T$M{Tr469st#b#X} zKS(@~`Ay;r>${Z-_1cX$Zq~L$|5)lf)PorB*-3G5k0&TzSl?|@=-+O?-o(Rwa`iva z_eFTc>C_gGoNtkwrmF7_A(@c9H{dUOQTE=@_uob4E6(l(%gXx`wm&Ix-IR)57?F+o zZYW)&u6Z(6kA?N!jklZnuAcZ%mpm2Y3+ub>cbNL_3gUsxr(=9!|MP&mHtYW9z20Ako5BJzY@xuDp^gp-1yZibW{m){O(=eUv zLvo%aIZag`lOzY90}H%Od2ds*`1yw`M{IrA8fN3)9qka3w>zFMYWVtyou7=HH?=*U z&5_r$8@VUy<82fVbrN4Fm zW`|!k$48X${=b|Ke7JEPa{%4@di_r$m)6s2HCX+bLNM3q&(WpBgZ|OUn{cO6f+aS# zAn;U;T0;{T7SVSG2kToR`npoumq0+G2^PAEK8C(ih9>ig0Yc`Rwf%-&A`6ZSsnMDk9UkN8k3k1nTr1H8$D`M(S#(!u zfcuYR0V7LQr(ouVL=+qsFiy>;=qqVm_)4TY98;=}5bhvD?PBU^o%$lRROS;Wv{ zWp-2myF?Wn7f}=O41ByAnL2{p3s{3h6D)MmU@;|O3EkiL#d{QtsPE;Uv2_$Znzgf7 z#4Ok$Ki_}|Wb7Jt@kK0R(Vk@L617Tu#d5O9EbSZbu$Xu9Ps}*dWVQCvXp)Arpy9=; zGo2a`g9;Y9F>Eb4`kcVMj;27E_2^=~^gkZ~Q)&AHWz3&?6x*P+6OPVe9Z zzJv-~5+|5Q8cZU`k~nN4f!aYrR=a@BC|<)2M(fAra>io)Cck}e`77B50jK3hCb~c){5J>(G^VL8l?0|TY;K+0EIxn zfD%>F%&;jX5d{lf1VkH|gIK^prK)S50>)<*+QW4AAIzc-ZlQYRDeB}bQA;Vu=8})2 zDMSJ@EOh6Ep-ZV>v4sQ^j|P*#Nz@oGAOXwCHJ9u98{Rcri@S*UV6UCgUd}r#POuRc zRxN15mg*9>k_4L8jlO^S!s#wax09UDy2B z<{y>bt%j8Ru;_hnfOl!pMJ2;hU-$l%I<@$g;_s&ZSbSgl-qKS`JC?rI><{Tq=?haH zUJeBB|HJkFhY>lgKI}1pFkahWWP2+ezsCi`tk$PoE$6xMo~-|u*#^0#B}3f$|D;9S zwX=Y;fU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfU|(J zfU|(Jz!%K|di{Tj-t4DjQ~%~{0<->qbJtYyM55r8zpVeyFbB{v|I^)ku0BFbp7zDW z0c-%^(c*oxreBk=kASDT#MbHSfJY($vMf>uVDUYKV7=r@rAb&&@2L*KLTXLKB6&}B z%Ub@Y0~hMRt`rzu%dUg4nB7yIBJ0yN84J`wHtXK8%ubWB=p5RZwWXSbh2-E2*Y;{M z7KeizuJ_htEciBLS;MW#Sj6qAuEZT(m#u@|(LvoPD8ANO2V$``Od;#1H4zJ_J=Kra zJmy*xt=X_B8Ws!LBB{bcXHWHv1SZml#mk;LkQi+1k@XEIoc_BKOLUE~4#Fa1=p8cx z77lx=hhAli)yUymU>ygGfgvuwzE=nJ)^dW6T*{hpU4h;M-WNjm5Rl zTGq>Iek_;L8bfA;$S6DZwy|1azR z|B4HS8Ldw%lk?noPuBm-Y=hj=k|A#Wf6^lE+F8I^z*)dqz*)dqz*)dqz*)dqz*)dq zz*)dqz*)dqz*)dqz*)dqz*)dqz*)dqz*)dqz*)dq;EQH~aQ(l005^O6|0FUBUir)V ze=ITe$L_R!T2MJ{uk?#cRpnQf3ml!ffp|67dWNX`Pz0?q=?0?q=?0?q=?0?q=?0?q=? z0?q=?0?q=?0?q=?0?q=?0?q=?0?q=?0?q=?0?q=?0$&&l^d!*h|2^8-|6;u12lO+D zR;%OF@wc`fR&gR>gkooaE9j`FLty6*E__hVbKz=|*+ImEX<8M2Z~jLoZ^E4+hImii zOrJMjNq@`e_Jineg_=c_Ir{7ME7YNc7Z7E+T1p}KLi$Rg&7!l##5+};s3vGUhd5Si z&N*t7=01mHET^zB^o!Wbl~zl>dH{vSRN>dI*4=wuttIpU_|c^D47G^Vn`hLMNf}VA zF{?e+1nQkYx5d)*x!P_s^!ZY?gzP+s?7B+Z?|hYuOIs6Ph!7~Gx??Ew=aA|v8&|hP zbS2za=HJ%@)}5;DXZOiywx2}SU#xkxMbi-#8?)#y_Ogo6LR3eNvP8SdJaUWWh$^1g4iz;CbRjly}6*)Sa^Hsa4Y#M{qH}EozR}68krSqZM1V?-ywm)qexUqlD(2{KEvI}~rgH<8x=u!`Jd-h!`Y>bEv8q;` zOkJ^)bmWfFzMqj}FDTz`0vkvZWb4IDW-rUHH`6iNH{2i8&aYR~G1_NrOzrPBfwkjR zPqdRTDAw=Jugd^W>lp1rHm3gG?v%xoCbg(#Q%WT{lNO`C15-ds!-Z#qczT{ez&o0Ne zv*iUFQ^lfX?bP&OJ(iK&TP@6#kA(Pm+okGc(Y^DV@sA5-(2*U)R@#w-beH{-@}WH zlnnHQ>;LioKdnCOF@-Qf@osAaozzB#RbD8cbvcoD1ADUmUuGNZMr0(|t^bc0!<{+{ zI14xnI14xnI14xnI14xnI14xnI14xnI14xnI14xnI14xnI14xnI14xnI14xnI14xn zI17B?Ebvg#=$3l@e~Q-JTr4m3WmThD|G%N_sFqX7GI;-w6=RjGX7f zeLElo5!zPUS=aCqw4Cb;`&TN-1u!OE(=ug&h)x)^P+ zh{{tJH-T+(FHjj2+fgB0T5mMKx+qUQvzIl9E||%08C>p_^G0tho%ht!`=Q?PrPQ+C z-)N25L!SECeyH`QTla2KWtL!V-w#t2&x`z)wVO1d1@P4Ry_}|asg?DW*r3I%S~t32 z*}A80+o!tN7E(sHKrxzORl29{*bg*|t~oclVbQs#zP2CeW?xcnw8WZnPaTk0G2XW$ zk%^AurY1;1V=ZU?3&M?7rZ=&#w92C;)wVLdjJuk^R`D_!ZMi0^)a$=Du49-P3`yfU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfU|(JfV03p zw7|3FmzGtPPiyhZ7Mq)2TiUz%{IVA5>Si~kUujmaex{bC-cQvRKU8#N$w}Uu-ttnj z{$JkzXRrT1iO1t*{r`!=O7s`JKSX!(#vrBz_GJCP%r>xy$w6}K|8p3|k(~vc1)K$( z1)K$(1)K$(1)K$(1)K$(1)K$(1)K$(1)K$(1)K$(1)K$(1)K$(1)K$(1)K$(1-=j# zc&OD6%=&+i)@afG|F=@AJvETiK2!~$vx{2pY5Jv~R{1>rd3AgnJ`{)bl6i_t3Yo4yaK4sq z5{kpZ#&zrHr&P70Us%PN#DSoO(78d(tF=pW0b3|CK)C>3;`O4 zU_{rFnCRDyMF3REOTZXP622|GRR6AwX#r1&*$k`pFykZyqs1h3E`42j4jtyZ^3%1r z*+f|$NmPE~$oEcE3mXwAH; zw}oFVA?+8Fcg3>-bA$5Ieq7cq;%0y+&2MPliGULwbl-y>P8DQiq( z+x(O;wBdEzLOe3zzDoi7;Nh?eva<5dFtIphTfvGU_Y9J?4CukY@w&@+9w!K0Nc4!K+Mv3Mb-w7@+A|%myN-)=56<*PlcG>9 z{eSkp1>UNn`hV~9KIh(h?t{zwA`b~sQLl)I2=chRUm}vABF_r~UIBSaq=;r@W~QX3 zW@Y%u6wUmZnLj1-fr@5irb%YzgNzIxDJtoIeP``;X1~sMqy78!so8uud*4|zYi7Q) zX3d(}vu3~c8vW!5eoWlZ3V7C+7`ak0%oUar*t5tWo8e67V7J3mL>OD5Txlq)Se7W- z4z|)Fsy16pVnLU)NMY`BCQx6oG6~LeHm^H>EsJq11|t=oXHX?Em778Tmni{eHb5;F znO{i{o!O-sw8;Kzn^u1@MCG7>sI9(OV4JLw1o0vzs-vCmvo9>D zCk(eH`Ixk)T+k%=uh3?WXnEC(cGE-dDXR|x@T@N`)Ie*3tB>&72~oTVBT?7{`Ba6c zd(Myg$)p(_5c5vyfNU-Qe`3xf%l{|l+^bOAHxW1N>Oz6Ydl;44y*9%(0?w2OqKoh&seYy z%3WQEfHI7r`eR)Ui#!;|h08#Dpi&E2XEiLGkLEJR88?cGoX`1qv)9QI8rfJ!r!W)>|pVc`q)adSz=YTu#M zzAH%Ev6f>Fuc6UM!cyT91`=Ie=zmbc+(?8=2h!r|LjSpcU=@{@Qv7wuSTIP-U0sO4 z999H4^s)O;>C#r#swyWil-yTW2|)^(3W`7R_1pF?bAC@;R4!yFY#2QU}$R z;UQ%}OU$9xRa*NY za+#|Og_rBOeTgO|iPu-O{&S~I_y#F&QKSSzmF_@)0H^$Z_sz$K=xke&kg;-jBDD|1eqy?LalW;i4K8GZ% z6}?;XybURpf*+(T!&FUV)&w3$Q3NSPJUI{3=x#3YBwn}xVHp?PT#~V*);}(|IizCA zY|wIW^GL^%*f6;V7cKYgxEVr!yHF^4;Fy5C9j;p0%_(cawk~&dAwoE(-ip%@qDz_L zwY7iz2M3Xscvj(1C9w-jn$682-q_FRVkWpcluIhqx}$=ss|)=yCg{T=`TV*D@{jUJ zA4*%bDoyxL7YYWaQJy{|8rxm3OsrP&O5VTs8H!)7!#~TKT3cWHWX%ONeQI8=zNC6^ z_3q+z#i_+i@s6r#Rh3ouRW7XTR{3njB^6^Ttcv>zYYY7fZ{_dEFUt4I@66qlYtFUH zZO>kq9hJ>wAIPlCG-h_DZ%8+%hwk@5r`tO%?9{Wb$DKRc}{rs+>}JV%^?~A5@%NQBmzme{n{%h*0)bXh|l9wg>Bp*xkP5j-yruHg(q`k-bfi=@& z|9@$7?f;K?e8zgW^NDn~=|8NaCG>w7{g-Rm=HJ{b&*ks?;{OjVZd+5-Z=?D>T>bV| zzp!m=HFdJp*n@Jx|Gx{}&1DcU2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VBLBJqj5HJWB z1PlTO0fT@+z#w1{FbEg~3<3s$|3(D5(sdu+|6_aTdO@I-^H5aI`D^xDX!E;x%Q}fD zf2IFNN>sWxF8}^;*Po$}8D}3-^VoxIwMomSZ3SwFh^_Wj^ILuj>QBizh+p|r&@>f< zee(SNpMsj`kEGhx%$ory2z$~{3Cr0TKtZGE4|Zs8tDY!G?J%|B%5swgVfVPDf_@-D zz0?CX)?;IXDk`O=2eAL41gXv2HiB-Epq}&qEG_F+2^yq=knd&{gg?z?5HJWB1PlTO z0fT@+z#w1{FbEg~3<3rLgMdN6AYc$M2p9wm0tNwtfI+|@U=T0}7z7Lg27ynHz!dv% zdzk%-wTIqU-%ls~Z?`V6D(q#}e)deq{~zzNacTem!SomW|EJO&RGfj9qX)b}muu$c zDF?}O`TM^3|KlVnDNA{dZVLSW&1DcU2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VBLBJqj z5HJWB1PlTO0fT@+z#w1{FbEg~3<3s$|5gOL(#8J&^kKXN9M^Hd*&SDnHJE;LTG;eZEE9Xl;bk`8*OUS*FZ+omj_4Cr&1@WR+>zIA4efGtP|+ig;DgSL$uvN=`!tE z;P3$y3tvjd_ePe{|9Mn`Ly5zgg!u^y%a!W)P;0XKwh+qLqYTe~@2`S%HJ3raAYc$M z2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VBLBJqj5HJWB1PlTO0fT@+z#w1{_?IH^mOaxt zKQStiNIYr3WZ!MAwyv{R+Amp?>_hE@eW~OB@4V0t@&Ct(0C=-2{{QlY01C3kY@YT{ z_WzgN2H+m=GXDSZa5AwB0tNwtfI+|@U=T0}7z7Lg1_6VBLBJqj5HJWB1PlTO0fT@+ zz#w1{FbEg~3<3rLgMdNcKNA6LLQwwyHvIqbh96fazF9@9BR&1LLu2j~x`QhI{{<%o zZXf?>2gs3Eqr-C&Vyzl-h4uN%EbE#N+uGy4ZCQD%(MniHkem4ua#x>CF5++y%9HT(XhY6o+N`(+b>d!47Pb(QF5j%RQIW zf3$&{8JK_>4m(iQma@s6%Q%|OW?*Jf=ODNlgCQ8>WCX?dOHSqG&Wwjx$CpYOJT$@0 z5RAYO$0+O*L-$DtZiZk4hB$LUF}%97zZ?bD#qnX42EC~u|0wQ zmwJx$pgbMlkg|@n`qIC_uEQQ&k4TT6tI{uWYAkn7f4J3~{`FJsve>fG?u%8v=z1#M zXs4&jJx{T1>tN^T2xNi7BO;=Xj&-U87@=P(5T!eNwZu`B3k#;JTe~y`6+>Tx8NNUl z_p)rFvn`lqnJ!+2ecA5rWj~U18MBY*?q#w_I5h}#eQy>6OT9JF^~*3Ws|LR9(aEFf zkPa?;q#7ubNm-TP&18sms*_3BxhL*nK({QNQR6_9rMpKxd=hg{XDd^3FoiG%RE3wh zh9^>0l*y#5+K8J;oSKLVfeQFM{kNp+H=Z}gI!_J0P$7rY;4$9mTBHKF#A%H7-~86c zFKsv_`@-~wN!$NSy&U4On8IN>n9Cqw5HJWB1PlTO0fT@+z#w1{FbEg~3<3rLgMdN6 zAYc$M2p9wm0tSKqYXnwjTV?OcoRoPdeRaBD`nl8vsk+p_)K$sfXJ? z66*bb#(KB&H2Movyco#QLub0nHRjOeH_LPR`}Fc8AH}w0w}Fok@u%_sj{wJz3<3rL zgMdN6AYc$M2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VBLBJqj5HJWB1PlTO0fT@+;6EM# z>`74m{|WN-O~B6>7rXyij{pCIow_7WrBHeQ-;N%BqT=z-3m2oY zu07y%YFqo4I0Ze}dt^hSxLq}S!0}UO809+J1MZvUqk6ca@ocM=%k{elTFkbpRdE+v zJ*bJ*pUbSX$aA{~IdjsVhs5&CHvXPn&1DcU2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VB zLBJqj5HJWB1PlTO0fT@+z#w1{FbEg~{%sI=*`8^=Z9icD#{MxKvwwx%V(+j<+db?L zt&1K1e|i5O^#6zVzP$g3|Np`Cw_KwRU3`!{m%s0e|G(@uD9_DLG5-I4M9kj?0fT@+ zz#w1{FbEg~3<3rLgMdN6AYc$M2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VBLBJsJ--G}* zCuskFc!cBf`v2e7X9OY5Zo7T{vk93_l19d2o1q~;m{vKLvUXTXIhnI^#|H2VwGjL0aOXP zqk-sN4$XHLT)6%Hd6zX^_x<*3CVzb57>%YEb>gD0!IX0e+4AwcD3_0y0KXFA<>PtL zFCWj_66NE0TcvzFZwr-==WVU>@l?x|8mkm9^xN6y6b0xTa~T8-0tNwtfI+|@U=T0} z7z7Lg1_6VBLBJqj5HJWB1PlTO0fT@+z#w1{FbEg~3<94Ofz9@2dVl;z;^D-#iIs^7 zi5`iM>}Tv_ljqyl+BevCfnEqF@Ol}Syv|Rk_xu^_-Oe*96fZfkGXUrRXX!52=tCFX zEYIcd`{MsEyA8^7^HYrfzaJ6vw?V)lU=T0}7z7Lg1_6VBLBJqj5HJWB1PlTO0fT@+ zz#w1{FbEg~3<3rLgMdN6AYc$M2>dr8fL#U3|KBDT^90`T<7&@0>ji5JJzbNY*)WUl zpq@_uchdg@>HqZM^h4K@F%M4Wr}Dow#1rQRs7tcJ9~sMH+F(UenebDI*4uJur8`BC zbZ|?@6~eQ3i8fe8Q9qI{;e>^y6PnyDx+Gn4F#Zb9PiS&SJV^&Pc3ffk2~F;ZC+XnU zjw>vk(BzJIk`8X}xWdv2P40*%>EQN`D=eMRy0xHaLFCd zLK9wcchg8*)g^Z~O}4Sf-9?)SUicr9-=qpeq>J(|T~1jhRXZUP2Y(8!CW2NQL92_P zwThtCN6>IU2d=Pkwqa5zt!)IYT?9>3MOc0vBGTai5nN&E8kiJH>l8uTFM`%Ng4QL1 z)-{6G?9fCu@TbVcT!$uc+_ZTPP2#v|r$x}_J2Z*oPPf3JNgOwAVFc~;2wICn3oGXt z4o&0~e@fjKIW&pmrY&}8630zj5|QpqhbD2{>6SV)iQ}d%b7&IBO7E+Zpoqa0e8%#U_xlz-`xvdnjAGWH;X zLpy%0LzDDw+7}&~#BtF?1jFpCBgLk1k|UYqccj=tVKlL!!f0v5CDg7vQtBL*E*p_9 z7eUKM&+e!Lnyzw!9}rJ+oCiVTL(juM*W3yhp} zJqPr=W({~I>u-{wP-v|Mvk=-*LJQ$ZLmP*N&?b#>0>$oim1WXchbH#2n>Nm&h3TI0 zm;<0 zbRBmk=JW(lr1SFx4PmQx-c#x4DYTGu9Y1%#lmo%@@gqmuex9HSPiaFbr_e&ub(}tS z>_Oo9+NRZ_)}@UaKog$QzlEpJLeh1-V)3AZ!Sj@}zFOliCuj%*&Ckwiz z{rxlCI6#5!WAua!MLJJMGZse-?hLUpd+v z7k};Oa&~gw6ls}{nIWR0o8x}!>yHdGy z;+_dNarr&L@R?c^igRi<4S?brt&R%Tcjt=s3XP(wo)E%WMW$|Mh?y}q)a8luB!PiFIK z87Yi*$Cb~iX<8WV@&j+~hRTQ0*4(qEM+gn=cWU9IdygzfYwGr+zMx5e#%^xvnw<{cv4lM^7${144o?pDE|KK;n)AhXT_Ctrf6;8{6R*vW2p6)jE z?eKIxd%oYM>78&|4zzMSXLYI=_V@5~Ju5GKZN!c;wBehdA6bT$0}aVSa8a~!WjTBM zFGrQ(+4Cpw{$g|)S`M_3{FF@GJ>aBCWoWrKXO2H1q%11kL0=j&xm>zAKREL9<m;<0bh)=~c({7xJun?XNpdk%-`^zGyXot*!80tsd$yjZRD1}Y z4WJ>-sC8G*65ZsKvy;$5(sg`$*Q#dZ7gCl6(1fRyM|cX&m(HAg-1Xo7O4WSUe>oYs-rKHS`xo0?)b7D{OWI9t*U+wCJFDH+wlBB6rS0;zm$aSQ_L};2 z_09F8>U-7K)W6f}*;eu}?+2ioJyw3lA1lP#IRy6w=4T{qO#blYE#9u>Pr$&wccH|x_YL?{{PaMwg3MZ zQUUP)hwcFtQ%cN{+)JoaHjlrVf!Y|^dI}W#sB}64ku5fKO?UU5+C7?;tCne;Tek_ zdaGv%DW$SYAreQ2PzuF4A#w1a_>@9Qe3B9e4=PS6q!cGDaqytxltM~zG7<+5Do!b+ z6elZj@Sx(9LP~LR5(f_|PAQ}mCoggEpyHH5&|oS=T27&=LXaF+h8mXR-~m@ANk1wM z9&lxlT(u!m;eUqqb6JziJw;>aEn9j87JN8D_5 z9C6CgA>zWLL)r!Sv=4-cbGl9?F6la?A&^Sk&vhJe2Gt?r?x#b#1o(875P+$0Zk7-{ zsF1l5f(I2cFA#E?gy2ENnJ*!DP$3HfAqxW`r%OmFFL4^qpqUh(Gb9ckRERinXYimx z76;-ikq|tnIA=--9#qIu3BiL3StcQ+yu>j&g9jC7g~Y*w3Rx*3cu*m$Bm@sCM4VJL zAMrQK02UQztE2)fDg>@+xH5o6g*>MIrjWzwq11QKkutsAA?Rb7QY(x5vF@hheQn#mzSZ<)HEs=im%T$5W^!?!Fm3s1WfOQ(FB< z#Br)+=@5_RS8+;O9g$T?N%NON(qKlH<^zR~ONV4Rz!j1UgyaJug+K@f4qTdZB?q`d zssbTJcSv9S;i#9xV~l(qEdNkp;CE54oB`%qD)(QR1zgFt(i6wB95$Dp2dAt zhltavvJ?7fzSLgW;Evv+>pqkIPy?r@EqWl%I{eV3T3Lmh%K_MiZ&|9Q;&`e<#NAY< zS}&Xa>G@DwQ~csyHfDDFnWp zDwQ~eaweBu<$+RVf5_*kIPd8=;h`8eERN{cA4H3W%&Yiwl|NTnYhaCC}nYrt2;aWCeJMDNQS`VY=?6 zkW#(GMT}E{y*PjA+$F^Le|0sNLBJqj5HJWB1PlTO0fT@+;NJ>?g%zLm--wE(`SZyy zenLKzZgLpSnF~^vWR|DSCXf2rsbf=DlBayHRL^vqbSnLB^5x|AV;?#XY<@6NxFFJ|{yqJ6yrq`#Jq^GA>C+Csp{m^7%a=&C{@`J?A#50Kp6L%!Oo47c!HnEUS85oi1ljxQ> zF#_S)>F_9A7$^+}> z(j8R1nTK~{lJ4g=(GOie?6>tPek%W4!%ia{0gUj!>9{UkMv|9d0}<_){xa`XNe|)) z*G3V)5Jg-QMLa8txH^is zDvG!=int<*xIBuuEQ+`^ig;!eaY+<$aTIY;6!DBGVoMb9^eE!ODB^-B;`}J$X;H*^ zQN+1X#O5gCoG9Y#DB`Rr;>;-Gj40ytDB`J6#A#8)Q=*6`M-fkoB2JAWPKhF(Sca$u z*Uy(Bs=;+~6!C;8;-o0z@lnKyQN-h-h!diSr-l{F{T45;Z`lc;&6dTaF$t1qrT zvwCv%QPp2gY)bq*@dEkkk57D}J$DxL0@9+aMzJ}Z5B`p4;~(tk}C zG6!UaWoBj0&Rm_jJM){&Tbb%?uk7gTyzF_|?`Hou`(Cy_cX)0>?sW3@zdrXs?hm=W zx%T@?Xo}oPQ+$a^5cNS2(6HwXmXaY2miQw!)tanTq`@23MR~v8LjRiaRTQ zRq;keMP<**;gz#1&#Ano@~4&0RK8tVQ*}tyn5xsN&ae7j)qPdZSM8~4UF=;vuGmui z3Y|Rgi{gvLe-t}Z|E+d=?f=wXS^I_B>9s>@yVquGU$1$hrm}9P#s2?Tpuna5|MC7m zW4+sX9^F9||NjfHqZM^h4KqEB7D7Pvw7WtoBxW*En6->O?~&*A^K_frnX#u8om}Um!DQI1iJfa>bu7d z-A_~BV=Y6&*N^4$r)wt!y8CG&;)vp>$?+Fqw2laLE`PcPA<*4V!$ump{4{)fT`oV( z{hktH69V1+w8jY95fL=?m2QXb=XqpAy1o%K^_B55Jo`nYJ34~aKZ15l1PvcQ zmdjtifkL3WpLT2n%{@Z-(+!SDHza~KG=k=wjiX}t^BWeC&KXlf(~XEohXGkGe}1Ec zKzBcFbOdco1Pz9PTz;P8gg|#cZF~f6LImx&2-?I5+VK&zNfERYB50E%XrGUuoftuz z5<#09K|3jeCfhg>#orF6M5LP*q}BWFGyLuFtcU+4F0s$tG_mI>uDZf#VuOXz#0Cqa zi4EqaNg2cjL;g}uu@S`vbEgxU*kGuWKb_cMZkp6fY%o`Tt)%Y48vr;wOEf93i&nCQ zPeq_}Iq6C?_jX!8ZI&dWyPr1OOKTw8+Y%pT`7}JrMCk%F)VmxFb#>FkdpV4!o95@~ zrbY2YUgh!w-fo)sLrYnxZl(IUX->O_()?v{r$b&&T7{XLhO)S6;$a^R?8;*N?f6`LwHR9JKD{TkBef;9DYYThlA4(6lWLdRn|v|(VDct9 zr(j8Par}z zE3LKGOsk<#&i^0F{P6#uPj@WYV7aFqJzPO|xh5Z4xLKad-`xwA>z_lCfu-5b&+5Yjae(k&3Oe;}lLAmnp_ zkRE}M0|FsE10e?nLJkUq92^LNKe@N2@FMqyz<1mm0*`TT2>isoA@C0OhV%)9z!TgX z2masQ5O{riL*VP}4LLdx0>5r=9C&kkL*T>h4T0yjHw6CL-Vk_cdqV~XLWTrFh6X~K z0wKc!A;SY9BLX2K10kaVA)^B!V*(-AM&+#+e5Ac0@Qn6`z#rNh0xxK92z;NtA@F$i zhQQC+8v^fUZwUVKhQO2A8v_4jZwS1Wy&>>b_J*7k2!UU+Hx9g!y&>>H_J&}*sHgvG z9Kv0V7b--?tqev76@tjPG8i4aAs8LJAs8LJAs8K02!@)v3H8NaYPdRlFgnt~Ugn#X(kc_J)A7Hw2u$A>ix{0cUZO)ttQ{;Oq@SDsgDl zsk|Xb7MB}=~Viy z)QhPnQ}?EBPF_fBP4!B3Naa%RCSOi&Pi{$WPF|C|Fu6K8k9_%uCL5Fc zB`cF3Bz7jANj#XiBk|qD#fi0vh2+&gBGD(&Em4#B$lhiD&faR@WpA=Cwb$8;$iIJ# z-OoNT+ru7~t+#t;6Ly#EZmXDm!P=YIX1$)d$9gt%ll5@sa%+C}eCy84n(R{Rx(xgO zV>KF=92}ca=l{e1e<6ip^%|?(@c;il-Q}8eXf57`e!{gc{{IVz7jlb#8UO$ISecjx z0fT@+z#w1{FbEg~3<3rLgMdN6AYc$M2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VBLBJsJ zpN9apL@57%n>^aq%RBc&EVw}1)`9fTvTm)vweocJw`IZM^D2r4mr}VtxaO91@?7rT zXdR+H>B#T(&pC(U<=E>{z37KeroPBuew}R%x0YIqtyR`CYp%82T3|I>nh7>4^nu*Js;{&ulgjkM-d^4V0DFgD0M7Mmedq2<;r`oDsnbd|8P zA{H*AsY-LKGpH=hRF!%3f361`xSICqLq7?%TZ6HYogEZRmtb=Sm18wASmdD8LaY|4 zGNCk|xviK9vTkft+<-N66-iS8PI*SE=3Yc)SW4)eWi6p6;KOWUw^Ng9U{q}(Fn~7)XhisS@bKvHNb5O6=`yoc}(^!hGtnVJsjEv8qtMyKz}! zOT^J61q+lMaKhMfa12#I)E_6^2MK`qHq)^!bE!k=Hh=`g5}~mq!sT=Xmj%AapHiHe z0Tg&l28&6cA<7sKbYv)jC=n;lYlfwvA znnfkjxS)Mvm1T%a;N6u$>%?M!GGN>YmH`I?#bSXnfCc`$%K&+bC1lXs%Sbk~h%t~j zc1ST0dka!3&-6%_NNEcR_#Dz3O9@IAbY_8biac$BL}6_)oRUHDVicc2Rblgu&%r?Z zd6<-I(4&0`V~-t!DEAerue$R^UyYUTkkYvAEjTI~i$Rm>Qat!Are=gBa@_%AEEcH3 zTvrF@k^(>?v<0FnYuq`ob-6E(fG0RyD%46*KviV+UoeE|%4B6@<>lf*GsJ zl~+N`D0ZubsUT*7D#t2IPz5oA*rR@!3St(q$NiuRVg_ijShCZkgwLx%)G0S=z86b! z29gHX;~OWAtpFeur_q1!AjwQ(tATLYWhSvz0d&dykgCKMso*>SN4oJmDi)C8)nF#E z0S#7z8N>$D+nk&jAl`Nx{iuP6`ZKuUSzp}A2hu>OXM~&>0Cw+AKNLk>7)9inLpU+d zgWSU*GYIHXBtnt&B@9}WFllLI2I!HoTA*ALqCdW-dIXg{_mtHK^;du6t}ZUYK+@y0 z$SmL)F%Izi%(g9yT<)E8th;^h0#jr{8-%m7lbPo^p+4_cI% z2_zvFlabVsVei5y;#C}Xi^P_Pfs}lKwZ_w&u}4%HVuCF8VLU}!U|PSNbQ6pd#sPMJ zEO{J67z8B}7XNFn(NB)VO2iq@`eJb&L~;;J4md1mIPS9+V`jyX0zBv7owysvIs9^>G;J`jnxd$99Wz? zJw$Wh7~NQ9YAP}DmjmbS#xm4l9C0~T64Y9Y;+Wo86`4}PMT0CX-IAr`NZB^A%4L?0 zsR5kj7_)TDqNU%Nu`t0lh&93vArb#?S~@Pbo)$h6gb8g~X6YC&I)cl#|94Br)Fu`A z$8ulE4aj|%y(@cGwqN$W%;wA)nU=}}DqpJjPQ}cMP8B-}-!3#4Iu~~2ug;&GZ<~K6 zcUf*sE}gqC`={h}$ys!Q!1IYK6UQei6K7@nX8)eKBXee^G4o1#Q+h$VG5uO3<3rLgMdN6AYc$M2p9wm0tNwtfI+|@U=T0}7z7Lg z1_6VBLBJqj5HJWB1PlTO0fT@+z##D7i9lDnl>a|A=_V4^FuL2c|Mn1dUu$)v;QJC| z8qT0Q-m}l7|Ja851=%V<&rd%2>znBodG%2H>L2;~30rJBJ75{T)t^BdkFoC)Pxu?# zHW$!u3;m6)b&J(5!sWEN>@4L!wum+d&ZP|+GwA;r6pJ}FF0+4bTf-cV3+O(BsI!#A zE4Z*XIrGMz(-|scuHx0~@VH}lpI_KkcVDTNsaS5eSuQ;a#D`AMp;u;p3A9L*wQ#gm6qdf z-gC@DHtqP}7L#1gByJ1Twm)ploJFm-jLL|Ok!T~FumFcrv>Y~YE~9@7h>9J*>{Q6S zk3M7E!?rbmsE`4qTBPdRtWqs4dC<)uPWm7SPP?<~PD*=_lU5|ji#f-9`?zmE5{!xM z*1QEC+j2Q(^^c}+wXJ?Wp4iLEn?1#qADf`LKAdjPnVTqGUtc=x*F~ExQOKiqzzDoJ z)%_j*{%EjV%ZR>G)fXF)IOevel8@PRB8Ruv09nC^<84{ejpF&`7#p@%Q#m+Jr{U*P zoG}4jkPi53VvF~ZQvGJoGjEVbUZ^!%lQ~_s?GEBJ)W@lr+MGA`L-PI7F+&~6AaZ!g z*B&7bJ$xKwr=R3=U181Rw$;ZM3tput6ZW}78rdI1(rnHYx9PqC2|tmflu!QP-a1K1 z)Cj6k3)M@v%G&!zP|9&mZg5qB4nfV~G{Of7;8=%6&aPSHk8Rzkt=2M(&?9f$O}rX? zB?pg16vjQBH~w?{m)3TE!ltr%+Zp9Xzgea9Axk3K1uYhy?*T`BOnm%^u?7gXG3Wip zQ8zvrlpVCamc#iQy8lw^+Mq5NMOd)U!944D)h1|{al`}L-#MR)20TkVMmTlUeXCsQ zW>8w*k_YE->8Xz<&w0wG77(PC6PnN=UX2gGV;mpTOdMvYmV}QwT+EkKD0Zo%<)E8! z$_2(7{(H4k)*sXy_A6ah(J{KrUTwqeI*&L)7LWtic-0k$6Bp5P!bRi;C4vktcC;NI z>mogW)z_zP*P2?=!ardV)pZ5kQDe^M!Rc0<4Xq!bqvfBG9O%Q~lKs{I-C?<^%1 z;`G6}ys{?wjBYo$(nHg8zn?+zS#!>ydMzfo;C4eD(HCJEFqdvO4BB;0ABPsBL<+noVgt?BE zj~>;yTvso8n{tuy5c$Km1LG8=05v&{IL}cw0H}E4ek?t>>s39U*ERFjovm7otmyF~ zqnAAODy0zJqEongo>;{Gv=<-Qup?-HmTPGqYoRGc0tYn@r}Sbi>AYFi4<}g*gsjuP$Kkn;3>B6=09$8@emmZ9}{8wdTms0ufmDX5fACB!b9}3cRa^=E1c@_ z4(I&dwhkqp(Kai`zT)BMDYm~~h35e`M6X@B?(A1y(5XcOq4nJoi}po7l7rHEJ0(4E z>c@X@x6eu?gRGArc|e7aTVZLDb{(~+>mT(v!u<$NPpc^Y48j4H5QlOd`>wc*;-gH0 z$Y~_m%PBrQh51>x@~6{JofdDUJj7~{w$fZNe!|&bJabp6QqHGRLF>z?_19ekUkd6V zP7C=J1irsz2*ndSjOno5VHv}H89kZfgEy=*f2|mq-}HNi`1tK{8GkrVL$>puf;O=3 zO&qIbac_#{*Pdugv?^wc9Pi8DKAz%T#G5Tu`yXarT5>qI{FXJZ>Tw2rU|+3L^h9li zvbNQBD(CW@r{CYHHIURvWZyrhJ>qvCQ0($OCmxR9xMljEZL6Ww$K4kF_n*FpXtE9= zfGxj_ivc|X3);%aup_kE(O~`r&CF>wg!|Tn}M~(PDx7XUt835 z|D;*h!nb}*>BVmO5v071!i1= zFXwmt=YBz{{bQw0&AG`qBR%D*g$KXq9$(^&dG56r&ilmOy@-pU;*J&^FWm+8sW$tuDk5Ho4%bt*`eV zUr%)DMOvqXuL65V7q>=Vzwmp+$6p&6t%BuRc5KZ*bYJizE%M0Yid&=1>s#we2b^b+ z9Xg4AM=Hdc6n?1ZFnZD!|L8lKDBVm4hto;!VKdHf%@oiJU<;l>p*ZUoa~y=B2QO1x zAvx&fJX^qXWW z(2w4^(+oUMP(S1n3(tE$ZR zDL&OmPRQY>!ZQsK5`d+DCt)8vxUDG#tW%cq_Tib6cOi16Go!#-~#M$YY zmYty2`w2c(!?JJ?7p{&L-v4iDSI91+yS)GZD?QJk|8iY({6#;I=W_QS?+$T|;J z!uaT39D>7fT03!Mq(EJn55FVFxGO8yoT9ChqLJ!>u?h1)UIRuC;t`qOdUE_mC;p*M zAHx_K`c_|If4CD{-gBZnJfDkR>BgEm@Nnk;idS!kmsk^c*Nie^r4Uvy+HyAi0!#PG zI_AWffznP6#peiz&upj0II{^{R?uiYkJ6$YB?U$hQ4HxZ*O(;UXiD`&2z{G0Zg}ZFtu# z5f7+woFjmjS!@iD8%N*2(;ejZpIY(ZenLapqp5{)Uqzg1E1CKQ*k4i{-Jv^1m=>5DaSn%mBzv$4Wn2aqMhWtQw`!<0 zdOW8aUn;+DXT*aw=gxH*rIOWg_ewc)nc#4lK%5~hvL*sc6p{^D(UJyC%FI#}8pk=@ zCR1wY67(IQkKV;=HnM8S{gvk*GhCVu?FFk^z13Dcr#d{{eMz(>>?c`?a=-E7x}D^t z6n(+A1mreqJ=fjKn9s=$AIx&F7P6ZDV^x-HSyjSQ9Qv`;g3KHJ5n2Np3B3+&B5f$z zcUids%!C^R3<3rLgMdN6AYc$M2p9wm0tNwtfI+|@U=T0}7z7Lg1_6VBLBJqj5coes z;Fe6E#LY>|-jIGFaZqL|?L63N4^6jNCnxW*d!+ibz z|4;t^;?$3?L*c5naQ^>!`^~WVEPjjLDO=VzDcbiycCOAdwrrQ@a(8@S&AIq+{|vqg z#Cr<(g#XL{6@YJ1@L~TMO#NBc5* z)!xhgEuD~J+vGG!@BjIQ_95gGZ_&H($?E-o-+Z?={EptTPNMMj^ya%8-Q{{Te~la% zAVGn9BXQPm{QLEz2r2r^04}^or_5a!y^mjKlVd*Ipmjp>=d@xa9He}G;UDZyju-l| zk`EylQg(L5p=dgPl<~IP=m!PPQ5nGz&-&tm4^bY%;!&y@U~9v`f%KEJ+9IvGa`K?= z%%Djz(5~s41-2=~VlhaufOo4K1MG%4=N$UUS*=ulsw(P9e%w`%19^1*C~YhdhdcSp zIWKM?^Tg!VTj@vY4lv}I9_ivzFcWqm<_`+!l0bGmOduZRnZR=y-u3YR)l9Haq_L5H zY}J_DoMBBmmXZTZ95#T%;&ALH8d$SJ4~WHT3VB^mrSs?~Qy+sDGNknLiv3XXutqkM zU2Lm}Z7je?DrfHH#BjbU6PC8t9^5YvBt4JP}dLTo0_td-`lO{@rgWVwJ zkN{l;bop4Q2h|<}h(6^}IpuT)Yb_k61xI@~7U=M?SPUnRe4O*JfGUG;O)!&Al$@c9 z)xn_pa6~>J1rD4X$i>1zOCKkM0L4l;i1NeX8zS|b93m7ed6PP;2B&-gK9)je0a4m# z77!&}dkB*11t^Y;U3k`)bRqY+#rZ>oVs#-Pz#}IYznkI?d5KkgjXWnrlX;{+UccwW zv6~G_18SXRjS~ZzShCin5_{#1S;TI%V83Gqr6x;IVWi3Mtgo;piU)*>@2mzc!!lyR^0cH?ee4*`dh>|oSCx-xgx{k{G6NgMrj1tF^fI-Tf zI*S^R`#UFx;KpkIK~!_Mw&COu-I&nfNFp5DyWAP^m_cj-9;hS_C*f$^^YQDBkrqcd zx$6$WjaB!tPP1X4lGD+>O$Y&w#VV*k7zbFSv4BJmm_<#A-pD4u#v``<30c5r*`Q2y za@d`*Dlw4sFB>pAacpbz7y@%X9X*YtY_y;qV#)tl$`@bx@PAVWtXPo%##mKoDvf@a zH}+Pg!Itn(rA9a*dab7#@kT&SjJW%3ymC^gk63jM${sU_J^GagEw&oLERk)_)06?t zvobCRlu4{nfamOTY86_a8N{}J!-o-O0;STuJN=N(R~HnqJaY(-2AF29r0UG3et0_l z$2oSQ6_{0Q1p`|QBZKH>P9A&0JCYLPO9ac6g2UvyMG5r&rSiv`PBu}$#9_ubbW1A2 zEFjvkL~JxwWG*40&+76rf|$o*)I`wY_%}H)5RJ@bfw+6BgG!?1^~LQolC%X*Ta!w8 zOA=e?M>=X2$I9VTLokALX<`e0u=8myau%2zFQ12dc0 z4SYO-ivBx`fb|{_DeyAGKtrRnXgYmyD;=+9j~JM7(laJP7~k9?$PAJ8wb$rpaA#gu zK#}pRFJ?HLgkO$nF`g}Ik}RB(i#&zph#iMHQqHQP7E1{S$nKMYJ-`R$L{cc zgPD_~rDNH7LzFIe8_diAA{MLoUW1tve`4BDxwp_AhnMom z$xP4_VlnZq^>X6ayPEPV|-bh%MMbHarg9_PA_K_P3cUGrzCBw)TYDV(k+(8)}Bvq-q|mzNq?y>W7LaWNI?M zOkb29nXXL#GIeR{q*O!dH_3~WMAzfC7Cv&2JeRuzdKLims@b#YXQIo}c0bG8-$9NL z;+T7#96d^{YJB`L=LLBTH`@8Uahz+Eekp z?5nwiIj`ygd1esXvIv4UOl4uyZ>$x?K_oaWw0b6u#>r9C`Sa-qA3MQ?Uw%@5(hpZ) zw6eDO!$3V#3-6=6nzF%}YoR8oxtCr_1k5-6mWX2r{$ygtCk;u_#|2sJd9?ft7<5U;Y*bzC5NY1!Whje~whu!7AQd+R&%F zIadsGl2~PFa@e?6D40d;`N(j!nu+Pm>BJ15&d}BQ4D)j?1?K0mlz$W9u!xYrJPpgn zx(s-oJsr5d`8nkH9o1t zp9`BKE5MhE z{3WR;MzM@T5p`jtE7O`-csd>Z9V#JG8EE)<3JomXvKsX;fI350))(uvuy-TO0_r&y z9(YTmzZIxg;F(SAF|Pb87G{)+Tb{62EX*SIhsKKz_3_}4>IZ{Q)U<2>JSUa z|Eh)AKuyO2Iyeq9gV-v%i3YD(NetcC3i>HlWuoPlnL$f^ zO1Y(!@Cq478c=VFn1E2V#qLw8@skw;^n_Rpjv=d3R1GJM-2(=a4TZ+y&Doqdw%i}1 zv_7u^a8k&)vg*~#Ynr@)SJ*n3_l4yt`&M5)=jMd5Ym4n*{0&YnG$)T;-yzh-_+X&k z5YG%y>sY4dKx(KJgg5V6;l#1~G&W_jAAz(rGeE!A8=)P{Av^kF(+;z`vy`psez(I+ z_Mvw#QY_FZWfu>#K<|!Kj)63MV5D@n0s3>S#8^7O%mv;q7&JKV*mg$HWMT%eBx2=1 zP^l(avf{+B5j;{4fee#+Oub;2>y_VW6qBvObj4!pDVs3(E?<3xCO9m!FgGmj8Y3s@zGr`rNOw7iNcN_hkMj(~>zP^K$z7 zbaQ(D^sA{Kq?V)(OTC`_esX5AYx1SUw-YBP+9rNyf6Ja`?{B|somy8@_fYNn+NRn} z?JsJU*BnywdiC|yQ>&}1A1t0#Y*YM6)sm`1s&-XgUwL9>tIEeJ&Z`()@f~ZXWkYTg zNsGTzUaqJMF4?NTB`R%X(F0>B!DKb))6j0xAsbGbWzlezylF4|&S^38I5 zl^e^`FmJH{gXIp)L>Ew}gJ%%0M+Lw9y!YWtiA!&aEniZ{f-($6ES11uSVDK~(O&L+ z3;ND^4L`Sut4xJ*841H( z%6RG4{!iIfztB|SwR2EewT{xc^@Yw2C4+_UZ!Ij5BNlosb2+%S`d79!I5hWiTv!8e z`rK(3QhGPS&8jb)L34cfx9+15?y&Hu#A9km8N8A{gGvs)1%FT=!@ISiAw_CLV{qB- zd*ImZHmxuRTP~;zgPQcd@slZaqbs$>*V~IYw`^+buWf5$XnbfnIC03DJm!E>))`8A zu_6O)qg&=Htqy<2wkCz}^P>_Mj}34Bk~oIxVQ2%qFNAkJe0aE5m&3LD`R@=P_ZTBR z0_oW^N;C&!^P{Vuc+M-Ul41tM|Kt&Xx%58dD?4o3DHm){9qXm|;Hi(c7S5u+fkishB95%Tw*82g%8eL79mF_1e?(tO6F!=`bt}iNKH+SN zJ*?HEv?g)?;m<(!c-m>FSZ}7A~|L|h205OmyjKH z$glrgPCILCQ6DbVqt8G6x_2xP6tINWQpUcK)VH7c&0XP|C#-j&mP<=H&;QPAe=XN) zVfb@-|Mc8lZ+LrG__)fkub%J^itV;M1as&v*rl*jW>LA^WB-ja=l{)XS4k?=3ie*m z7U%pffBS4o>7FwPn(_2G#u)TYw_S-@3EG--SohqQc6)oC0xub}@$X)|kvk*;d4mhDuLBh<4YK@W#)QQ}F2(CNK2AKsYw$_3 zb$|B@_k`<`sF}J*<}1&?_hC7`swFchW#c;Z-CIsdy(Q-JQn==@g0pTrgPiqBdLv+(_Ub- zCfXAkYa%_N9WhIURoddz>9bgbNA5m^>^Qf5kN$-@EYEMCGqJKE60^7Kkrdy(`T!?8 z#6qiU`x#rZplcy@T*uY*XHrVH^&vSy=Rrr{EljY*|9x!MpPjRbY*u$~;T2O@XBe-! zmH5~{t~+YTwg2bXyPrF-581nJ9j!6+u4-W|B4~;IiW)XI^;N%UYiuZz zHv8w=*FWrd@*jh_<48J;c(C_pk-TFEDPwlZsrOU-6Dh<$dX=-FbRQJQOK`>06-2^k4p?zu&jV;r?uk5ul^*eFXEC=9o=qhQTMHFCgnfB&bkD!{%w^dZa)vBz#Ud?VHVkLc+ncgW` z%=Fy7m&X{agTf{goV4$yw%(=f{}r>v@q-?s+2S-;?-Vak>} zn5+_F{Sq^E?mPc^Gtr-)^DUa|4t1d=(!?82W;i1^EI7X{#F(5pWhZfPuMD#!@YV%A z&nq{!Ym1cm9kQ`2&5hkQv*;)9C;_V;5+4D5g_Rz$g$t8)C(@Qcn`BAw=Rrggq zS$%h*U*!!}W%c^PGpS2b-BLFvcBi{!DywcN_P4jxTwL*1&E~2#$*HLwxnuLWT%l&Y zePQ(riD#`g=?nAsWfxTzvU`$!3isxFC)a1Msl3f&6_3VQ#K_xTB%`V!vrH(FPz`#7r`-A7p6I%9^5DA;^_J^kOv3k zNzw4QfXxB^scVnd{3mt+8&%4s9j7S%%bYt1%zt%2L4Q0}l~fSg`O!n!)%J@R{1%++|Fg%G^U;}uqpkFq?M*r3be z!n?;i+@8IhyRP?ir`|Fi@Ok{ut+PJVC=Y_uL7^|es$$2c|oTcD;J9PYfY#Qn|` zmaL2r;)ms5mqxw#aDTKdTd*j{X+Ql&XgS{gh-;brnD4rlB2`%H#i~232=)vDk~jej zwg+-Sx$tfu(#^X=aR46ni8WgHr?1fYx_V2-=_Q3gZ)tXV3*IZ}IR@4WF>3Khj8^5@ z81nd9$4xp9R}22Ij-}`~t^Cr|?7|p*5j{~P3MWQ_j=qUiEI4R4T0d#HDKw9H?U?s1 z`}^~d-r=5m=+!&#I-uS`gS@^{b7yE?8{2ap-*oP}ttPwb!!{4^iD7#WJkQJgER^R5 zZJ6hW&Rz37*~OFJRpD(Jms`6sHd=+LzYgWR-9hHp&Rui9(8c+)*|R)HR&z&dq zh3F;TGqK3&OK3$%C2V-?BExqq@Qw{_`Mqs_49&B-p6j>CxogBPaO&xnMR}Xcd0?I( zH4F2tCwLm^z2EF4(In{z#V;@bq6?Z{l?!lM^`^S z(`mmqox84=^fBm4-G1Byq}9-mgYAYLYNb)!l~+fnz4mtX%ZgiPh#g)2EduKENyo(IVqvWPnWaJH;dsd1Z2mVj{&>Z`A;MzRh-CrwrFUPvaa*{Tl1EXEA zZ-?c4#=~7SXO|uy?zHlVKx=!g9%w$;I_#mxr(wx>jFNZGcGvt}?eSikOT5Rq>$aD7 zn%-S~GRNh*;IA_r4Ziu11446{^&XFWXFGSD!&Ij=uxkx(3D67C1K1H z0V6}X?{sXt-2sV|m2H{v0&VuK#F`Py#{5&lxpsDJn{{3MWtO%>nWeWf#&bwVKtp1! zU5{Q1=8g(2_t8!cR|j%f>tKu=&|?8MM56rP(c;Xx~0x3D0}0qbXhr zXn3)l*lP=;6X(k|2Jbmw*=HRe?AORQ(PvC(oflLxn%D2=N7el<*}J2U-+sZHPxfzO zT?4s%@$3nqxg6{u`}F{_vKl4&R^A?SJ93+`T%3B9i}%zn=m2Vj=oazx9DL_W5mS=Y*?tuB3=`{ZTLUH3_>VWYhkkaXZZ zJ?{LLYz9dvY6V2u!o?n9%p#B!HOl)De zH5>97d+wJ*^Ev%smV_@lcU>!YKa;uHc;Ha8+6TBGJ5{~!q9xa@5pv_V6N$|3EQBsTlg)oe=QGKUG>h_Lvxt2hb3UK zbJtjjrh}xR{aN1d<`}ac*3Ms@aOp3SNCG)_$(5Dm7Ob+n+mQ`~!ipURF%;1q$>Hyj#$A-Pq^8ulc$9$E{9l|0$r!+}azO z0A)li(dXQADDV4Z{_@!ADrfX6)Md`%+jQQp7Q6RdmV^Iw?z+Y1QA(`VpxvRXd7cXl z@lHq|<05t?a=EnfM>-c*4R<-?>7Ky2dN%Qb2H=*!_@nEE@cnbtrd*1-(uG{kDh#3doE#a$8;5=fhi?gf! zE^yYqiUEDJp2{Q|Tt-5-4)nGik728nG~qBSoMs0JWv^P40DdoF^O22?-B%MBL)>#8 znX!5;nJ^z2x>Sx(o|JeS;54}3~i5j9M&ym)P3TP7d2lOifuF8 zKJA>lZXX%%^*qMizhJq#Px!%mN~}P5ZG6;dQstj@&Mu9$&ap7Q6tFZVQ0t@pWVMHF zah$WzXg%BM4b9C}j}6WoedY%7Cm~ zY`Isvp*i6Gi(lvrAcMo7uokXI9`5~U4a_-VFJQF7i~w;E#v0(p-&g69w&R}Zwo`xo)9O&JeH_dFxqyZ5ZWEN9?H-I6XcOoy?8yawz3*=o%KOU| z+$I$P&EmFn*rs;(iB$>}tksD1dC9Q0ItNz^bxd&%4bEL-GSO+FC3MF-cC-dd6iU4K zu>(W--Q~3OPXaBz(BX$2a*(9pPEMVNd(Db=8fXq9!J-SA7oUGTOC*-)i$2oUh-g%By$SW5-C&K(mc5nFir9yJPxc^$6i>ussI^}*O zFlUo~9GtU-SLN-n5|%q6?zzQ>ki{J1 z)x{kyuG-z^;WiCzTiOh3b9tNH#bWiV$qU=guOHW{v94ji4Hc=1skQYTM${kFs#y0{ z`-eN+x8HTu+md%AuS_oQbk}|-?zg+swVeic+R<=HLqo%39jA7Dqr=4=8amwEesKHU z?JjQDyWNhq6WV^z=Grzx+q^^H2k6%NuKGUpziYL&)mwGT>xy-k)()+`u4Y2BmljjS3#$&T+FCiW^684D6%7^l6_ym*74FKPnBPlZ4Cs-&Cp$m;Qf5)+gY=Sg zoAd*z6H{-|mjK!%FHSTjHrf5`E%5)h$yTBF|9thL{)4IdSm*tJ_s+a8fZzYa|9^}` zrRUYhC2!?)<$oawH*B@Hlq@2bB;4cJ9zP4%AktFM=&%bhj-Z9kB5i?HC(PG_`W<;a zw0|))uP->`)p3FG%AFT%1xSQ@&dhd$`xq3lBR0NVza?yK<3nc@-WwQ=r;-=?5ZZ4w z&KgdAXCmz)9Zfr-N2|X_QrHPpgGTF-(>v-qxb#WF(SLOT3rKp7jJK@KW%ZJ0%y>`1 z8s*iV2W$SW_Q;kU0mUZN{Bg>{D$?%!<`<))Kxz8#p+%NmFt(J+Tc?*za3 zgOAqyT$=muN!C*DI(Nt(@9Mzka4_-&tu&d3!PeoO@W`R#Uq6W5?`lV@Y&-gK#@O$FcVPcyqUP!BalEr_ zw>^fFuQ6kEkD`x!{e;jwo18Wn7HA7uj}j{e79fwJGSfpza3TdP4t~caD~Rx1;E&3p zl`fcca%kSWo&6Ud1omOb+alzL6)0Zi;?=D`w4bKAxuj~QGg{0Fj2ag?QU%%fex?wg z9p!l%<^!d1lvt^7hm>=<)FY4ni1?u$qjb9DT z@danU%!7e<-|m)jzghCng#7+&$PK6EgpHTCIk{aM$PMpVL@s1yg4YfpC7&c2?_Ko` zou5ljH9BiR#{|}bWEDg#OpG^r|3B)m{f@&I58UhH`$-L19k-jo>)%eXr=3Y2M=_t8vKl&h4*- z<~hWft1Sx5+2qs_NR!Os!D-@=Z-#OjUv?!zIB}viRliexUPk^{X45x%~ZQ=kzEgTim?+o2>$UjuUEd&>ue1++9-h z)z;jje(Kz{42tDBhh!1wp75wI{_(u)lYQmEzj2xU^Dr&=Q)kWm{lFM3;}za-@`@u? zwZdjutcx*H<3DoSuca88+h#{X-`n4>CstA}GG58d8+`!zMawMvFC*7IM|23yb)z%S zxIVB-B-+WlGO1THxYc-`1U-c{0$BCCSI6qta3)k-#d3Ldl$J{D^Bag zJ3kL)xpmEm(6XH6%$(N*X3!!zvPTQ@B4;D8MTeC{$S}U6*+}=`n)a*T8>@45y>s31 zA;01Ny>r(HtfX8pv&7sKwgK-2KrUPkUL$}l2|I}APQ2R%I+5Rz;iSMY+($TgcMbH( zFsl`PQ+kTumejL2Zt0B!Cx+&DiBrGt1?nj?a(OSKYYHqc%S_VzTs{bwm-RuI=20@`t^>SqX%OShV^^w1%z$IEB3rIhS#Yf z^}OeY*Mi;q09gXx|H0D#$KIR3Nl~2r<2|#pvpci*eXziA$R*-(Eug5&u?UKQupA-+ z3oHV{f?NWkB3^i3hy;-!8Z~GX5tSfO;)#lQ6Jm@KBub3&j-s!}|M@;oRbMl+%);jP z{y%TJcc=T?Pd)Y2Q%_YrRoz|HZE0XD)f3~60<0Q{`?07e`dKJ)^w-r3V#?d1uJ|5{ zy7JSd2cIwbz>`P!NJ!UneTQuCYnR1LXPj!QU8CBnee9)2qPmx^{|XFOG|1a~6K;u_ z<}5WQ?wY78ChZI5ib>{3;5qO#st*^C4#Ptw%Vufp8gbd7geVrz7aKA zuLh4bUja5dX5;}Y4O>30RryGcdTycb5+fhTg^UZ_-Wfd_o_@={R(ui;y{=qQ;jR{-W`Xc?Y%o>%JRX*ceD?MBNXrgA+HI15Cw^Y>+ z8AHlUI<#OsadbW>A+f$sArm3GlT4RcIKVmJ!VP=)SS7mqWWSzXVE_UN313( zV)h@>v^+BE404ANG3u$T#d+=Ei7``tLfuo|8uc96?q5V6#*_0oztOXUWPFHLZogv3 z6LHz(DdUme)4ji0r)JMJ9oO^+dj20N+tj36ljlkYmL4csR+3+`skm?PyG2urf<+r> zZo!WQR~FMfQ^H&$4dFYLoSNW@YA^8FMr8Gd87H zr-##5rS(qxEWCoA{%;Sp3++m+PBl_*N@U*#X* z|H5|@J^$Z@=l}SwlllB#J~2iFe*PaczHPpMg3;_J(yx}lS+?i@n|tSMB=TXl@rMbw zf_+pnNbP${!)N@!`*c5W1=D42g1n(1m+AENw&ptiiq;x=N6!MD1w0FQ7Vs?KS-`V^ zX93Rwo&`J$coy(1;90=4fM)^E0-gmt3wRdrEZ|wdvw&v-&jOwWJPUXh@GS7(Vu2R4 zJ%#80ewx|ir-$jhg^h{)T)mQ?|A%v5&Rs}!Jn}z}eh2AyI{n61(T=u%mp|V`Q2c!x zj@|sSKP|v_fzPBlmzaUEnBMG%l|9pGwuy5tUKNGP%%pjW8!<%6&96Lt0i;y%JT1&Q zJ`yQO`S&lzw*oOQ6myG_el4xZN2-|7IyWlakMCJ@k}hnTV|Swm>D+@fub{IkiqW0kCnsM^urH;8_x5z9zs{U$S4vGJJ^VsRvd<``_YcVI zRLu60kg;9AO@Uo?8g>rI@4`8txxyqL`6(yK^qwaA4WNBzCFR!9pQOS{eP}MvBr4}w zihlBQFQnP6WQ9X^FOt=lzPQwv?9!Fx_92bCkQAiTUCFrM;I?$gt|r|vJLfz_Z#nYz zEW2zg9I|_mlpZ8|0F^@(%c~*^Y=ctJC2;oQYqNbuGooO|J?2&|ChKea2R}MD&u)J= zmrkcY$wW$>`L7G5+m*_rGySyvPYI99M>_cWBWy2g-{~t-+(JHC*t5u`!^>%%~e#sSDXpmqV*NIu`qkGJ2~-9o6pHs(wo5Ie<%qg<&o0yCz8|JmBwI73$v;ut<9_d+{~UMXIg_PEwmA&hgN}7 z??ox~r7UzQZ4S!~qtdCN(!pFYtM;yHzpC0sfd!bIC?sg^` z((O)vx`bXkV_hqs(Jm?-t5%6VOSUa(bGQ_xtSTs_p7g7tUw1+kY};;>x=4ET{kpa& zj{(YqINOH39S$PzfSjOaRggT`3AqwEpZX`aLkre&ER7Mzl4?nTE@%hPt)G%|Oxw}z zk+0ZN+@1$BJE0G11-uS^y1kA+{qzp;^|Ab@r4Q^SexN&PB)PhM_9@5O^@-K?hEsjR zI~HVqGW3B};AsbtW<8Xo*Kh6G(Jm=g>l#7r5^srvEXi3vl7w0hA4*?a=XM&}+V(iR zBxkKgJg01t1dj$k*OTOEzti`iJD&0t?=yaixef;4iS(=JY z@Kx}!@aBD$q%Exj<#tK#wp1NMC)6VNzAmZ+MW=raJd}VWOFndhB>3uX^g|67No{6* z+S&TPD9T9QE|2vt=r83AYyLWL2~D_pUl;f$y!}Sr0y1lTd#a@ z=R|lPoAIlj*0<1N^I~p1#!&hjHkiRHu|K=v`d-#o*70t5St+K+8QN~b-S*K-Pq4Vb ztMzv3CfpPMwBST57d7OOWwm20-e3LC^xi(QmaJ~mtGi=yzS(^INwB4Sx5&w1pEFM1 z6EyJNCA@zLIv?(xO2UmdQKMgUzFk^Lx~#?X=wU`s2@j;Yhqq5ds)Uvw+mk{&(%YJb zQaM*suQ!BvP(y~&o0~=wH{Me%ysyqV=M|(9E9~#*efApiXV({eh+R$6<&D?XrmXB+ zd%S`ColdEp_2>P{?I(YV zQTzysb0(DxV)moGw&sXHbNZ@bInhwN`mwL5ujD^?YxTDnt6MqJlD__G?|1l8cdXdx zEv&bTxh*j1xyR|t?$E(*<5=$?6F+)MaqXG-vNwDoEOG{U8Sxo!ArZ|wT+yc*9z{~9k8|>GyN^U2 zvGa^1Ik?KJCaLn~PAzxstL;aj7DmgBALpGfX|b70U(K!MW)|j-iLI}j^VAc9G{N4f zug-lx9{u9&&RXuW@jnoEEPdn0`Tg?G#`@@Melo_h+a*>Uj2+>(-Q_UBEEA02q*T7T zcmLUL-F5z=bwBo%J>z2Q9zV{M&tE<#w(hawICa+?Se>u2rTd+EPmhnM`(Uak_&NYq zqexEo^muea{kliT$r^Xx#QJr&$7zV}1DD-VgWkzrQaXpaOtt2h-gDz5c=h--+#O@v z&dVnIj4?6lOLQ7etu0BhYRlK3UNGgzIe{rH@d|N)^-oW({lA9NuwYt;ek)30Q3|@xS@QlN5gd zaUA};DR{rX_*RU>;IFilC;qmbq?{a63+w$&SjC93pnkt8Vq?tIkee!S`P+;fSw zuuf6lSb{4hd|?f%rKD|09 zYH8?8p!GENbfWeDZht5_yK?uP-}{X5M_77Tla04K$eKZnud#9!dyzi0>mHJRnzEa$ z5tZ*c$=gI=BfJ+8_QQ3J+%?6TL)-~Lozs#^9(?K-tA6Mb!+kAz(t(AX&jze^(uuh`5tsHX+LNGkC7#7y$=n@cl%e+cTGKwv2}{Fk#wsP$ zIee)WcU0}QuP?{!R!_|6mNJOr^d@=G6?w(gwpAl^O@`fdf21`X%yC>sWn=l zxFOMcrx1DpyN}o0i#EBUR_P=2(^ziOQc&-i6abJI6w^iMx6?Lfw|^obek z{cBVG;TfS-p>g52L*?O5>DhfSd0k+ue|w-)@)}=t;9ydPuQX|S($Bte{w2Qhz^|6f4XgheOP zua*Ga_BFRdZSHR5_5U*4pa-SkBM*Z8O5SJltb{z?7xF&Bd;9wTNRFwP2RsXS7Vs?K zS-`V^X93Rwo&`J$coy(1;90=4fM)^E0-gmt3wRdrEZ|wdvw&v-&jOwWJPUXh@GRh2 zz_Y-Ai3M8FX089n%wTL`V<=19QfXuVgmYc~Ik{Q`DpA_?g^%?@2ZN*w`>-9EC#SBX; zXS%N2b7ZxpHtZVWtZHPL56!rqRy;aoVzxQbuP=uqFZ#!;Rx8opXq&*Zw=pwK&zgmu zC#dhsPq2NVT5>e{%=Wt*QdTH4+e~lHLGw{-NJhD`ik>kq>l0kFikW|1kN0R@X3xWv z%^bEqzB8t+|Ipa{H|@H*XCcarTs^N**Ai#l{P*q`NJh7M($;pv_pQW6X}(<)r*F(s zx8|H$Gn=j1k6OaYGhTnoDr4u$7u5bb`jhWDnKOFXQdhpwlGdaSaiko}6YB4ciciGX zi1b&LFejhhk-^fgHBNlT?GbhDwfmQ7{SSPY_b06HiuPuh1*zv<>)bxKtT(NFb^33e zjy2O>&qbECkI`xVbpJw<;i(z-P4?VXOdEq(TY_VC( zR{q&-F{^FI?kC08E0Z=*Ol<>aiQ}77b7>6-)^DIaA$`oj#LQ1~{y(Sx;^VvCb=yE& z5-~8#6ZKvEKITW`y)kMgCwrbFTvg1e=dQizZ}yci^5tyHH=%8!&%tbW$82X(C;ULzl4vgul~3GOS}DezlYu5+WoA2NfWC%bRERp*66y_=DkHE$61%O9b>m( zYeu$|=Z^=Mk_4xhmie|S6efepmW4!*_oyT}1uk!y{PmgzWYh=`fK(UI5vxm3Qu0-9?NiVFpnz7$>+z&Wv9|1+9RH}-e6o}yUh~PVqycL_Ikg6?`DFgAix1Em zQN1En%2s=)bE)0JM=qwHtl)9iuW#;s`OA2*r7y6?1G2sq5_G+4^!28kql1CZX%(x} zM2&Z{z&tqICr&G5%v~TG@(?vZj?Kb)In| zt?+bS3+oytE2=Pdh&7fw;?=O6?h&qr<+w+&8kXZ9!D?8J*XXNZS!yG!hGjWNxEhw@ z9?@!8j@h8qupFb2SHrT*1Xsf{?_pQNGFQT@VVOIj)v(NYM5|#rW&>Bla-1Vv4a;#4 zvl^Cp8n_yk<0P~imO1NP4a*!2UJc8!j$kz`$7|qfSdNp>YFOq>cr`3@H*hs9$7#T7 zSmsZ7H7s){yc(9dQ>?%=ABh zDdtY>4NiCtr^oiV%WB;zmo?nD^2c?#F0rG#t~%+%ysxY)Alxayn;Y@m#(mcs&mpXP z-PZMwrAhyAXOe~YGvKL%eyff1{vYl>;~fuL`+Y-(d~Mw?u$~Uc9T>c~0&iYG+E$7> z?oTsrrnuu&dJWMX*QnN$FiWzPm+LcH)0LsVA7<$#SEh0=)4C#&JEeFw7XJ?0h0`A+ ziNln(u_Yp(^T7r#V z+#AJxR?gpl_oamwBf-*)T>rk{#rb0jrQz3l4RcJ4s*F>R@+8wJ<|Iy_NO{?ssP?LWS zQfO1|V1;~73{&XE8%HQ~5^oC^YoG2@1WiZJ|Q<^u1i6kt?rN=!BcE zQ|P-jH!Jk!=35l{@db^3cy_I#72kHhLcJeNoZR$RSw!HbV zLc_X$sn9u>9a8A3^*<=o;e($QDxc^3qu8(6%uIz2buCqB=CURVeU(?H(3Yth{cgzs zMZ=pEL`vRELls(i(lCW?4Gvdm?pGre`o7;Ng}!YyR-vA^Pu8KoovYAaS1(X#?`xMS z^xlM96skC7l|rreuU6>$rgtc`t9mnuG`;H=)QG- zQs~J~KUCdZO%p zg|5!tqR?f*Clv~hc}t-aAOEYSZT~=_yLSCep+`D@rqIrt|ET~x@g-*ZpDuwp>Rw&f&-`6O#=9?Q8!uOsfZfTEO75eV{^$MN-=erfc zn=XX!mh?vyy79ry3SBz-4+?$s$Wsap*s0OrmM_LWk}? zU7-`UYt(JPP(?F-qtOc$HH!A@>T?xZK6{2jUpzloq08IPSLorFmMC;ltECE+esz^X z-vsVd=x=-OQRtNoTNP?|P@|k{f3IjK-1>|{lczqf&`Wb(ROsQib}DpN@D+u!ul$Qb zWB>KOLc8leQfTR+9~D}Bd*FGo>5zZqC^Y$!T!rSxGrJq8hYWpjMcb`by1rLr?XvyTW6e{?uMwiu1Q?%bSovzT6?dlY2 zHBF;uPhFsBADwlzLT|jfTA|9&T7`TSk1Dii@)m`D>akU!Th7_0(4>o=Rp{z}?NaE+ z{ckJu(Y%8SbzhbAg5={8BcM=^--H$Fczc#YgL=2td~=RbDCujBGR`hnwC!(nRS55C z5y_F)sub!qYp_DQe;J}__m5QQum7r5DCwu+1E&<7u1sL(eDE>h^H$CfKJplXFeA6{^WLXS7Q zQ=zYiYBXckdPS=~^L~XMd2*9N*Dctr&=nb57242kn?fs7o>ZvnX^ras^1Pz`?$lih zox4V(OEXpbb3!}CezS8U3cY!CJB8k8*-4>hd%G&s`kRv!TC=>L zLZR_P6dJRCtU_6jj92KK(WSc_knm?`3<9(k|=&oPCuh4aE_A7+<4~aE4zN^vL zAN-0oWLr=nyk|)Gnr$dhXx}}h3f*^RCx!NARx31ejz-UQ7_DgY&!4Q&=Qq}Bz6;M) zXkq$vg+BY+`3n8>z9kAh`u;@`$8&TGixnh1TY6 zSE%6QT?!T6yjvl>_egU2*hBjjs(bw-g??S}xk68u{#&8XcKo2wuGf=Zmbmx*EvV2{ z^V1bturO1hySL{nH0}?@3hkKFQlSB}dMdPe&uE3-d3lCHvwohX(D8TIDYS6?QiYmU zU8>N;I*mpjbCsgiRIE{`$+!m;x^>rPg|7YglM3B*{fi3SJ#v>qDLekG(9=KuMWNAG z9Z;z9+AkIA{_yt-&A9s)g?wlEUlF@(nOv;U@c(J1P~Gqr3auY@qCzh%JyRjPPfFrm z(qpnhb&IDe^zI&wHiVZb+VIz|R0!{zlDH2vS*6fsV~s-XURbNpuiD(L(2b=JDD?gs zjW$(0rD%oIUQ=l4yKgJx&;GYU)fL|=^iVK!m!$jA1uYbMYhxFM-dx*Pp`}j^Q0V(_ z2Pw3(ZiGU&7EM*?y$dudnLka@9{%t=g=%}QQ|R6xWQ|RqKey>pF?q3vo>b#^^CFjLY7b$f5 zwx$YY|5c;?OPed&6A!de=z+<{D)fhNSA{o|>Z2lVhhV)Unenh5qo(`3hY$ zW}ZTiez;PhCpO-p(2cj;qtL$WdlhPa{-X+I|KVwcazA=rq1m}_Db#1`UWNK?`#_<2 zP5-74-ccqwU;OiD3LXE&cM7%b@ry!B9yE3f-@z|~3Y|41U7=G?%2DX9lk*k2@gFS} z!W+yaZqAb96>2xSt3vy_R4VjC-av(BZmCr$<>T239e8PpLbcytsnFVCS1WY)wJR0E zyUrxFial!;>N5Ktg+A+XpF%&beng>HUVcKM6JOk=(4sTnR;c&!?)43a`^`e z{o{x46$%b1eT^4r_`2WF>NU~eZ)-K0wxYG7-QV$8g?9X|t3r|YDimt*XfK6+exRR1 zFF#+Ukn!P}3O!smMWLQ8&R6Ki1@jczw`q|=PyOy{g>E?XD~0B+TBp$3_cY2e?o+gj zPku(BuiL+@&~?4{Ds)%D`wIQ3|KAll{(X%`opw;s&K~ejh5mltXA1RyOrt?nUntsL zU-(}aTRrvJVBv7KUOJp z>NBS)H2$p96`DA3v_f0%s!`~@tIt1Y{o!I(8h4NefPNB2at3p4n{y?Dv$9$&HD(6}sT|&I)DU+C!mM={*(N_1!57O-=5n(5mAGDU{h|utL}VcBDeT z`*N&8e|%z`LJzd5Q|Ov2E>P&SCRZzzdhRs}y%o4op_M7C6uP3dMw|Y2yQ2NLV6{SL z9a^JMhw?iV`tI606*^_&0}7SS_`O2&mTprhbHeiq?YRFLc`k=1?B#;jY0>1I6w6|MHeX zZ@u)9LjQB%XH9#!PN}&zck5FjuFB)C?ZjGXK zcw@Oj+g9G7(DzyE6{`GUgF++Q|3;xfCq1sv&_|zEXvL1_6uRW#^9rQ|b}IDG+h0}) zZ;X_DymQ@th4QOESLlP@KP$9gS>SEq+gq8V(AsdWLSN1)R_L4IO%=NStL6&f?UE9= z^`Cnww7s;qLcyO0D75tH;R@}4uv($^!HEimE}gE>Ges9DbZWt3g=Y4=NTCncE?4Nv z>MImlbLdWmO55J8(DjElD>Q27c7niVXWsq1;Q1J^ZxEx9hu&rRz&qm0nl6ymV1%UFnq4v8BUG z2bA_M?NZvVv{`9>X}HuV{kG)OlKmxnOLmt$SF){SQ^~z0YfEk^Sy6Iv$^4QTB@;_V zl?*QFThgd=Lvf_IusFTgUwo+Ov!Vk{!^cu(&X@Fi`kI!50M|6}(sQX2H&arwXZbIvz8pXBVz*^{#?=b4>io!uq7U3RnV{OoYHk^ODfr&;^6_GazQ zdM;~Q)~2j`v({$alC>i1;;i{uGqNUTjmjFF)il)shQ(4hi4AVJUO#lW{1p3W?^P}ra$vg#%CD^ zGTzO2J>!Lp?HQXh?$214u`=VDjAa=MGiGK?${3R|B%^=E2^r-XZ8FL-ax+pheop@; z{gd>4>3h<5r9YFtHT|LVd(zjW-;{o3`qK2d>C@83r&p(+mR^}&k=`-AWqNUXW_lp~ zhqN!!K1zEp?aj2EX-}nXN!ysVK5bRnb!p4f7NymtO-UP@HY{yGTJN+jY3=Ac^7(1u zG$ZX>T6?}fyf?f%{9JfjcvJY^@Y?V#;T7SF!}G&4!V|-z!h^$o!#%^D!mY!l;p}iQ z{A1{=(819Ap|?XXhjxS>4?PLz9&A#{0YNoY=JYG_<&cxYhgblgGsn?_~OI?^cGj&qxnA9Pu{Zmg!El+KeT9%rd znwt7^$~P&Wr0h%Cld>!2nUt+552f6bvL@xGlq*w~rp!&5mNGu2I_0#K%9M(fjwvlu zic>OE0x3TPzX*O5d@uNBaA)wT;FjRV;QHXI;B~>}!9~Hk;FRE4`quq`VDDg;V7p+m zV16(hG=kqIf112Md2jOW;Gh-+lC~#pPP#v7UDC>=Ym$~FElir3G%0CJ(vYP7Nhc(g zC$&i`OUg}3P5RmYjsFw>KK~y7F8?$Bt^SAn_xRWNZ}MO1U+SOhpXML$ulAqjuk=^= zJNjGti~X7Yfd2>I7ru{t@A=;J?esn6+v3~kTkl)tyUw@Vx5!uLo8lYm8|E9}>+S2} zYv*g`%lCzShVNVBQ)9oe*Vt`5XKXVz8TT4%ja!Tr#>MoGg&D>~wf< z(@#JDzloi%>}rqqZM2=nf#c)Qqscx)W*fBNIS4Iz--`FGd4CM=Wro7VjP2|H4MXQ5 zTCzv+EZ|wdvw&v-&jOwWJPUXh@GRh2z_Wm70nY-S1w0FQ7Vs?KS-`V^X93Rwo&`J$ zcoy(1;90=4fM)^E0{=HH(1JEz|L>!x=F9a<{o5K#__=zLQ9;?htJ&1%OX-Nv8v5-a zP(nw&mGo`MS{0%JwYridyX z5~&e0%T7pXG+h8`AVYDLlR#0f2P=nfPa;~A`l3l-yl7-$%M>`#8>0D@DZ2A_o;#aq z;R_9>{oIj~9Fe+kq^1-hZoVWiw$WSG7f}LZ_0x&oB+wqIE1RP|F~l{81jfo$$NHj4V7zG1DyA_IO&4=lE@o7y&h@1vfs&HD zk#wmqq6EfL5g~C^lt77QFE7yJmX`$DBT0F6txrJ-jHMt#;wmVC63wz9oL1aImcUpk zAtY`}66j8;M|~+tV62on)fY_ybxQaY5$Xk~ag~!miDEA&(Bqbq1ll7>If0IL6|>Sx zpgoebt2~Mj*W?oD&QVc)Q6$hErMSK*5-3reP8ueNs~iH6S+F>x}xfD{eXxXpbcQ2Mh$NKN6!iBsy&psfIrDGur4w z`w{fPW_+N$-0@f8qI9MY$|EaCAf0|koj~_`63A$b1Ukn_09|jRn5b)10)5#;=wccr zfxfN;y3nWzWq<@4r3`x1lR%@CfxD3HImm2`LhceL2XN($QU={z33R35GRmN3V45AyF)ef>Kk)!rS(I%1vFwAL`97NY5 zDP*+d?8eA}+eEsdp`U1!9JpI@SL#SnO;L*)#Z|a{k*-BZpix{!bPl8?1A79EQU>l?WKW<`%D~+YbOP|EjgkYmiL|rp+*pO|&OukYnnOE4 z0*zA0?lQ0^&?qK~cGr>vltH5uvO5R53_AX&%AkwUkIMiFG)ft`O{5cmyT)++s5_kg zE?1%5HJn|e^k{Bhq`fI}klPqJ7!W51NC4M@M{T02I0@i>R-?FUcZbv2=+CtX2{cMA za*rzP2{cMeb~`(J0*%s=-OkRQK%-Psx2v!x&?w^~cT3g@pqj?LxRyXvGM{1Hal%dL zxKSj~9VMSdpuVcBar@l`36v<#n-W-*9rs>_1jdS1R$nv;j2A6#z7S2{z_j0W0zK{> z4GEOA>~}QE>r+qyB~n+u|Bi0;`1+zqpgYQO^+l1uSZ3%@Uo;7Hrvz$T%SoU_v0D!G zxR#SZdnCC|IJQ3JBv2w*mIF1eauO&}?8>2Qh+Fd{(47wVCKS!A<49C*5*VFM0Wri) zM*`jH6xJ6-0ws#GErsffCV{b{wTg;n%a#O6H1{p~W1=DwL2nW$5l`egKApY=zeo)s zTN0a$E;N7CQKBC1-d(?_u2X6e?tC>$0=R8xzg^hbkUL*UfQvj5rMV^~mV@Y?O%lMR zRHNj;-8btC5hTzkIdFG8Isx22ZIm2Dcb#&B7M&Nm7Jr0xvA4v^z}@-lF)F&cM#(|6 ziR6|7yknyz;I2jG)SIJIK?47wLdL_YPfDIBSzdAmy<=p5)0%uE|Kz+IGhR)Xg zd}vB2wdjDqrb)XdYnwF5y*+zl+Mu+D{Y(5+wCex9!U2VUFSxv*Rlzg)@Aw85=M}$K zG`{SoCP|_7zVm$Dd^6L3GA?bpA+=BHzLX8Y*}-k4SC!Tk^$rd%ZC<)CrC&;R%9o{Y z8vUA`SoAC7H${=APnT6>zeDf7$jeG@eowPM2Yq2u&aa0c*7|>3bOeoWn_omngifK~ zUIOq(dYjxS;{lE19~-j%e<_La8MpF&74Prhz069ukld}$Sj~GP^VYMuM&8l0fM)^E z0-gmt3wRdrEZ|wdvw&v-&jOwWJPUXh@GRh2z_Wm70nY-S1w0FQ7Vs?KS-`V^X93Rw zo&`J${P$U)1#P_k-%m?E{q#tkw~%oOKV3hWmvF4i8rJ+`qT~7hdGtF-zcc7JzKV9V zU3O1TJgTpEE7VI4lMG*lUTaTtEqt_S5KCOKp1xuTi9jMu&u4nY*)eG2V$jZsK^qu@ zHYf%SQxn|jc8Wop5QB#K%13@5^qXf?U^0x!r);Jdda@m{P_UtkIV$P z31gUzl#qnz8BcwU3QTuOSVF7?l`H}Cy%W;GJ!u@1qF6%0c5qwLlzz@xTkhubwEYShDeCDYLO)*Y)SVFJggF_sBDN1ZVBCJau-WTSO>QyS;A=z(ZO9O zyU~*B3P?y;2Y0(*2`J5kCAf3JrKuNRCRC%`x#+BGH75BdB*9%4TqnCU#FEi43n8zLc(@%Thf$p#C9+xBy0zF+hhso^%B;>oeS=>PH2cFqf3+e z3rI-VlF_9}5-{RTxHR3f_q)N{LqftjxN~7jNZ69@T(AU;DB zfE^O7Q5XX|*Fl(eNYKl5rgjuV!s!jML$m}M6+=S8c8Ip5l8|s|Mvq}gLM7>tu!QJb z&{(c>L+s#g-B>rx5{}pori6rZ;jU3E0Ujp78ifT4&icR}rsL7r0c)2lAR%EpxO2f0 zFcQOcXhP$tSj(F$9)6)DnPMj+VZX-V$=kqfN+NGKPv zmN@f>6!!xXk`QYRbHyp)aryF2JG!Vp+?6}(LuaEK`3Wp>??d&2>`+7LAF-dTK)Zm1 zBd!lD0b{v@N9NHZQX12?x}#%p1MSe0t`YlD$@ZmVSL1{UH}C6`aE*eG>Pqpd$d0|p z2Hj!FTStGAny?O1Cwr2FJ`}qLNufHWm!l_CAKZ4pYGjs>a9OyovrGvI`zW`kH6l#p=SbbC2dLc%r5?W0%%mc5zx$!z!8^m=M8BO85~onPHoD~~}tJ_fBr4BD|V zXz(_sPAUz(`jz!jv_df63&JDYLX=kX^0MPAJvVcEZ;m?(wEsClmJ7?)qR#z!)Z>Ttv4`bzeVW3GN!j{Y53EpKz^q zds=?Z10RK7Lb-5DfR8ea=b9EKRp%{l`fZa9f#F5VQiIJp1t$hpDgtJ#21Map|gbAp>Pw`PqkF3rzu{$}`w z+$(c-<`friFZ(2;O>$ZK`t%i{QE5M>e3iL5xG-y6{w@AbllK(e6wb}NI6Is>K7D`s zfxye5)oF`THYR^o+AZsctTjpN^4pcJEzT~Alq@OxxvXR6wB%v_eFYN@S^w`Ry|G2F z|G$K&7^+UD-yvedMTy>4JTx97-otDSS^qDy4ep`@eZ~gf%Y20RNqFo3fOpormxMcPdn>)H1F1LM2y^EPakF-ghTy?SM{l`iVPb*v|m;Kk;AJZQzp&sG;hv? z1#`}wTQgq8Xh39W-_xr4RZkeyzul0w9Aoi3&D*wp zWYCDnkfGI)AtMJ5Cic+W+W8CS)6 zMRi2ToVnBMCQqMJGjqc9DUs=Q^UZ`hhK@TfazSlmR?Xtb0A%o5wSp^xKKSwq=G0rl= zA*=m~;V>PJ?-1!TWlCiJwCVFA)2AYHb+xs0K07k4W+B;UHW_K^?76cdv!_OC7tOD& zn?j*;=FXl|J9qx{+Ia|z=u(MvIG!cauHND!#dUmDkWz@c?CUE&TU<&;<Mra=+6I13#>26iRNww!ruY;iJb@RwPDAd_f*B!M_ zDSb8y?Hp0o*lb{J+jk@>sNvIR&eZjraxi}y)dOk? zR0HPKa7yK1#74|ZYUj@8S}=R&6s{>X^N})X+v#j)4is^w=mSAh=gywR20Ghr*K=5L zPH1{vUG3b+jM>xcVzZq~T2aus=T7NFCge)B_?*Z|k+%KkpF3jmwAxuUr;Z%dzip(Z zZi?cXJ!uA6jEqcNVjq(dN+?Onn54-$jGZWqtP^E6GmYp}IZ^B`m{vQtR_PR5?2tZZ zRB5ruu`1cf%<1RVMuxPVP}QevRYjMIp2zj?)vM=mojdodI<8l@?%j?nFYnWbeq9H2 zt*DyNmMe>LmtDwRhNw-`UQs#|h1&qT^`!%T)5GP~U-uu*txv0+IY+yMFjw|Ub?R`r zC$aBFt*o4V0Tr|Ml|m1rf{z|lJup%=rmEja>OT6A!$&{R*4a_?uj)6r5A`XP3#iTM z22VmwIz<^gn;K?h()9U6w~6c5XYk;v;V?mHL{+tta8hLCh^pZeVC#0|BXyW5<)jEX z;Zaq?tJ{&hj)O)_IHT%}zE#89k!lk-A8p%nwBc1?f`AcKgQ*{bq_g|i%&(bLGp`o4 zuYX^a2sg~Lhrk;U88#Bip#EG*rF$gn2z4lIg#b%UyYrA{el~N70DCAYF<|)6GoTB3 z39FDoqX$+Em#(AImebCWp~K^)LmIHj+eiAa%M7)1lrp+!^6C)4rB2+oN??m*mc1>8 zU1g-BdEoPg4)0$zJks|pGCm^B5yxGMCG=CoMwo8NP_itUhVuy-R#M;7tr`(b{)E#DYcVl){qORB!@d_3dthD zcJiQk{U^*l9?;$CoMwxisQ)g;yrL&R6BXW{Mty(yvXF5nKNtWqUuZkq;o`d zK4$O3LN%-8@+2DH>6V37ubUQxA|tv+L~OY*Q!@jp>aZ;lYi=z%#}mo>+4K_IQ592l z6Rj*#sZB?8M@=-lD_h%%h}(AX*=j8JJTV%8&WBP!*L@mHwX4(?DCfwQHoZ^QhHM|N z(Aq*C4W=VgX4BZLZuWc{vrV5z6>M%~@$3aeWg3s{XaF~B`aBFiJH;;Cu4E^44VJl7 zcTU~A*+?RAC+;zhvWaC=v|*J~6oBq)%IyvJvm37OQJe2CXI``BwH3k?r}I--`z9C=$Kp$9O<}PxT}3rtD>P1MvZn>t_^NifWB^s z?fQixpjGH>s78Tew|iZCWBFfi@~8)iw|T4e!j5#I66FDiZ01SJjp+zvxKlfGUadU} zM2g=^$}0%QA)NVDlhIN2$!?G#YZ((S=x~3l00~ zYN_j(xtNFF^K0kPz?+BelWHS#YiG?~NLOk#Q|HsDh$}N)*`dZPsGENN0%V<^6vhBMhMQ|o>6oQrvryY=x~5=$s;V_JsTEII9HB}*C5|uZLUPy$ z&^!LcRXd36=ycj0BQe|-^a_Qja~oMT!qF>|_vZ}J(TrAU`GWQlIBxpaHB7ZxDz8d? zU2k)6l@e@sb8R2J^aPiiD@-|@o10sUjMd}jVh>F0evWjmtgF3Xf)WCGIud$BGRP6Q zG`B+|o5u29Py}D-ZxlvITOyC?*!N>@W5+aA_C|c9| z!7a9P8^a83Pf=r5*+F&Wo#`@|FJEJKnfCEe%}#T$sL)3}fSzviM&ZIq3{51sXPT`cDm<$S`_PbDSPG!CK_3xf&|FqF<&l84tU8 za2H(-$&*ZxF&rv%`}Pn)ai&eLJJ)_+hGpv{)lE-S16x9BY;TsW)p{JI7C9}?K1(aBqkRJCKb`6KJ5&_jqevC3n?oGCPBjT$p! zxG1*-FUsOnN#}l zqX+6gB`sCelo{bR%+8F0>g~(ySN6oK3%2nt?D^KT?p^fQInJfM_UvYJSC=tpou)f6 z19cm)UE4c4D3__IWl(iB#YiJ@IKuxo7yig3{lix)9qQ}Bq?N;2>Cw`Est1D^&EmIr zVbD|xj?^eeS(r5nL0T_e)V_gX{3h^~ov+-TkIjKI}eV|w^Z+gW^d_xpMWVnAG4zC~>eIlU zdFXIBk8UQfb46KIzE3HkJ)ynZJB$)K3Z<{%;SAt%{)R!1m$MDLT432J`WLEXGTtpE zoBx8edqZCY#``-Yr6zskf6gCB9+Px-=Klnjmh28MO1;kCI{bOws?7bq<;ILa&)~-? zYkZ4*`^&yD#+3BUZJE(GG&eX=tz_4ojJ1*-PqOhP`%CGFP^@IfE2Z$t1-%W;++M12 z{9{8_vM(bsKH~=7-^lx$c)yzWr}BOP@2hxk7(MA(JM=Op8kKrIK2E*Ovw&v-&jOwW zJPUXh@GRh2z_Wm70nY-S1w0FQ7Vs?KS-`V^X93Rwo&`J$coy(1;90=4fM)^E0-gmt z3;Z`&papH#N_Kg+?=uD&8T1g{XG}8i5t&K8%;wAKi0A+3(Jz(24m#>D+5hJ62blI4^FxjR#DLI5H}K*W`XUV9ZD3&nAjD^!VcQR3tY-u0gzV>*FnrpCg$bau zqtFarwP0ZaM40{U{PJA@J%=I*47BNUXEyOEQx+xwj6P$8ZI71=_=rWe`mk1=!~K!zB#jri5x6U0L1wCvmAPlSulxeh7T*VFaaWLG7FIX zlm-aH*Irnd01-Bw1;~Cz1B9VYvM>Q64Ew}3r_E4UEKC4I_>5okzLNK;yicrZoK3te zOaPscuvWHxYsQHQJBN5#m;gE>VekNYkAg|GG$#-*3ll(R6gBb9X|stGk%b9>m?IZf zqlF0&Vb`!o*0!w8@a-VGb7I5wwds1YnofM&ok zkzH)6xl?t z!J0Kjb3*51_^8G7Sz|OOe2k7YYYbudh9}#sF`84U)~qpv;mhGXrWL@Vjmx2} zkx9+pXI#OUTP`8?Ep*i9&U(fJF0=rA|5~b;&sfV)_IENSxx7hDMEcgb<$}sUgfQ_% zd}eF{P3<3HY>x~c`?)2IhlCI&z6gvyV+-%M^8PX22WXE?3(@D$)Dp&bEFesL5hD1E zZM@&X`zLv?ZB)O_niDT)Q+yGh8PBqSS9$*&@4F*_Hr!*t)=Lg-WJ|LMsL)J&5uX|P zbna980>(Ofbi$kc+!EG;qZ5z#B0e*^umHTv$7giqy|$4SqR%PNX0w)BnD`B_3lDWa_a zj-@jeChmjJj6&k^seKV+yC&eVpIe%BH!I^O;3z*3J;k-}GpB>LKC_)RHuz4&%_VXFrZ3IU9xn;9*Ev%(Y*aa+L z8SfYIp4$g;a0{j*S8+O0psh4J(-~(|+y|c-Pl2ZPPcyb_0v`Lhr5UdCS(vyFJ~Lhf z&3K#lJ9*E82SPlGqho9r#7-TRR@?{ik(ifwzmNAX^IkheU0LuMJElk{p2*WM; z2H!su03k;%tdgUX%|@GI8Ypa;rCA>qM)Ade^BH!DZ$3*HI&#jY0LW;dX0|>64V?=> zz|jeVvtZ2x9G$SUv}OX1PFP=#PNj){N37LAn|*E-KtC2n@udSj0?j5`!uoS`YUqt2 ztcrzEHXB3O0G&Mq+w9$@*EdZs zd%DRzC8JBiC4VTMQ{1X}Ptnyyy^B6C+)y~OFtzZ>g1Ukh1#jnHm48D1zw*}SjmQh; z{XTbQZu8tXbFRqgne)%=b=hZT2eM~mHO+cG^Rmo}%ztF8%@~^D&v-0-T6&Z8-D#Jm zbxZp=ye51`*a$xwni?tz?Ml5QwM**XQ|2~VopO50&%sB6Uzbe@o>f*9d^!1|DfS*QAbFn(uDRO|mGbM`OUW4ws< z|CiAbwQnT-Hqh_i^wZnKPPc8;IR0^b{2*6|&qyK5`}q3TXQVQAu|%DEU$T!fiAV4( z;90=4fM)^E0-gmt3wRdrEZ|wdvw&v-&jOwWJPUXh@GRh2z_Wm70nY-S1w0FQ7Vs?K zS-`V^XMz7B3$&n(*Z*U#gC9F=ImS|c>i(gD8G0{fu5Nlc9r65sI{j7<;K{Uvu@&V$ z$aA>mEcQM~vqe|{gy6>n5RoPmFx5bZG6Cy(90W{^iz4XwkQD@nP6+`_z)U}!m;|Jx zlV%P~@X%aAOv7`e0)mduih+RmIwg*R`1mmg=87PSmJ`HuJO=@j`*bKr!Nlp91Vlqh zL2zgpI5I&e$s8yZhYS$Fs-p!#aHtM~Lv;{fAYB|B1qQ;8HEpSYPALe2PJ#8%1e_p% zIrK>s!HlBwVse0hP?JMf2?VfEOrHfi6EKTZE5(|EfKdEQf)>bxj_xR$04~-81cwX| z5Q_LF2h!JEK}=ahsQxhlp=K0tXc;&%hn8UybP;goFg;2O#4!khPSPYGt`^K3h>IU{ zU{;hv1_%ztGyz1`0|YJ5LC{GDySjyH0@%UD9Gac=0EZ@+9AMWeaTKjKb5u~MLv>Bi za)Kb}6HWyLEs$lvnK@*D;D`c(Lk~;?b40NdSg4*Th7(Q}1jx{WgCHO;ni#|sUWZT+ zG&{=x!J!8Tj-mv?(M&;bG*is&b)*7xfXgR?u z8#|(z1aNS&-~cd_1wqF*IkX;3I90KmdJJ?E2E`&Eu1+aOKe&;(Nt5FC1Ff~f}x4m~tM6FHSA z#X!K1lO3Xfsi`Jt<$@sS6EliKF>vT4!5JLx77Btx2IfoZBv~j3Q5=p`#=4_`;D`bO zqCg<)p$VE8JX?k8q?rI#GYUAst_3p(GN*}5fcZe=R1gIo21Jv99vMU?j3c0v#4!kh zPJs#MxH&XIa|J=rCuS57OvS*VbBcf$h3Ae0pY0YIk|v=dAYEfBJp1IglqIVPzS z5ST*~%(4XmtoWH6IMM0{!Qn{7P5>u!L?sJ?CNhUk(u@KEerB@p$>!)2sb~U6vG?jG z!6DS%tD77EW?nEn*D0g{gP?Ut*92`XEW6^tH?j{RN5Nndz(LRhR+a${EtF;G@hPHY z(vQb5+Bht4@ zh;gAqQ*h`gSW_K@)pc%JF%WbVRtyBKDcb{jXp1pHmmCyh13`5y)Z}m|1{s=zqhL^@ z39N@6<(RnzL95O}LC}h^48#x74^dbK2wEuT1t$&}An4q(43wOX!l|5Ki^4)d(8;pU zK|~1C&rB8sor=k!qp(m6CNu{N1wnI|QBb{2n?fiq;6dQBJ)MBolx2XRg|Z9~z>Xg$ zTS-7iVZtlj2Vwb~$rXI~g_2|k;nvZg;7x)S#{~zsr8@{`FTOV0K{zEmF5f|Dbw|fy z2O*{7PfZ+ze{O%Zxr6Z98S7d_5nv!~q@Z1O5FM&B!LB+J?5Z=tt~wL!sx!f^Iuq=w zGr_Jp6YQ!p0ea|i!7&Iuq6oSc;>aAjpl}RAQ%ztQ&=f@oBQXJ*A~}-)O&zI#pgA}d zXsQV;!>&4W;ObQ?#&rw?EtKn+zP?4WT%}stIJiZ1bjtuiC(AP0tGJMnLq9G`&B2Pn zU4-Zd4zB!&0sulT{emDMIS_E?CkO*+O*t<$MAZb&3kW(D)&qV_J2rNXu&Iv1GA7)- zuZt!ir634e298WX2u>UX5D_(q>Y$^r(56Jy$+BV~Xm81ifuKvDjSPY=7dA2oS}`^d z2s#zc3k(Duv~p}Q$S^ep2TmXd#~_r_LQR5ZHwijpOu#!QbSgLoLC{f{Fv`sVLR=0O z3J$H5NzkbvAP5ef7n1;s;e-jJ3Fs)Shc>V#20?J>0fLTiMnNjt@*D-J=(1q~qG{p5 zwEn}fM%ql836502;YbApM=BsVQW<6w$cmN4A|Mrnvce>9G*|F=HwOp~j^o@MAUHUBxj8_H;&523gF6ZcS}7cZARr1d$GJULotU`= z!4U;sMTg=T1i_()or6;WLDxc)plcx$(18PJ!9ft9kR}ETZ6};82vHnJMdv42O($9) zM>#7NL8riafI|y430f!uB z&U##Q-3gTr4iK~sCWj+0k8kSK#}NetN0iUsZrN88z!?NV%PNQQ%`a3vU*>cKJK!7l>ya(L2w8? zMuloQ{CELCWARDh@dYtnE(T4(a+Qa1cwZ0iZKH?SSW7mJCfD+ z`Av>4wkVtm2-?Ujqa_h^`)9)GL~z&?nqn~H5UNKxJV1j`9fb`9f>sQEkw+*x6_cQ| z55LGa0CXxW6uH%<&q6_P$N(WqhE}Xd3)LkDZmwgFR5}w?YYGDy#vP$+LT42ZEbdX< zruc+{_65ZS-{!xapHlGm{AcqQht@Qi78)HYC>{{%9tsv-SUjV+bto_74}F=sFZEYt zucbbfnx6f;)U~PCrY=csnms*rOzNqrow6%Zk4e2RGe0#c^{bS>Wc)4V^^~VmKFxS2 z<&&)c$+|7;-mE)Qp2)f`WoOo(vo1`Tk#bhbz?2>-ZBhzS0x4ey_Xpny?g%~{To=4P zxHNcP@ZHSh;6F3JN&YeO!{m&tHHiMQ&)k-NAh5CQP_9vSMdnMH zS7y8wcqZ^u#v_6Cf&OJT1}+ZF3XBVkEIU1LLSSmy1!Yf`?JE0gS$SsrKye@?@NLrH zldfv|cG8^OXOk|?-JEnw?p;YY6kb%gsqoi@Hzi$?RF`y4(%_^w3;$SnV$zn}4oM|R z&*!EledixrG^nU&QQM-zqEKFG-k#jP8K0H?QZ^!^dFGUi!TAp~`N-cV^PK#5{0lNp z%zw`BYw}vjQzf}g7B*c`_Nf2%j1B(#GbUx->|f@e?H}*IJNFEKZ~w9Wck)X8A5lSk zmv_jQn*Xt{B!7>uL;mx=(+YbPwks?uY}KS&lP$i^8F%|`@m=c6Da*`kSvJSlwJf`7 zi>6(g_G?<*^yl=fjM}DGH<{pzWDM~IioY(}U-U-Nj-t)^cjcet>*#CZ3;P}}T358R z==!4bioQ1v8hx%30FUjBD^TQ zs+t_1b!Jv&)~F`SjaQqTZ@k}RqFVot=8X;ald<{v`9IeGUqNSRaFgkW3jkai=&jYm zS8mie{t>re6We@_cNb=nQa&{wA%1*s{eOJhcp;tzJPUXh@GRh2z_Wm70nY-S1w0FQ z7Vs?KS-`V^X93Rwo&`J$coy(1;90=4fM)^E0-gmt3wRdrEbxEd0xf9c_5VJam+}mr z@MFWYUTi+2iuR+M-)CIOXEO1o9brsb(c9RG%VesGCd4?FvrL~c@S^KdcRu}mD8-l= zFizsXQ;j~x0%N{0+vsP^HEN9-!nMW}V=z%B)7eZyLn*8e9ce4TJOn)tNsct#DVtX? zIOxA%))eT(j|ox8oE)8W$N=4M_`O$eJD zEjp%CVUwdp$3!e_a(J_GxbU8ZdkOR6sU7Qma)9rS{ggD5Nqa_FDkRwNnj=65&m!p#oe$egdVx0l_ zjqeVYW(dFo*Lk{S6{ZCi?ajuss&sj$h>Nr#@G`)u_J=a4T)i@%0w*yQM>Lk{S6IXH(LIa>Tfh=xs$ zPCDd(ZkK~|$dRMPKbC0N00W)oKwD@rjemOenNEdW_J?Tg1*yL#O;~aA2=%hmq=x#a0#dLc+J(XzK zvkIpoXHqNAI{CPyb7{Gi+IfOF_0M<+j>G2O0Df1+WNqvAKuIfxRlU<)%}Y+Ruy z+v&jAC}E7FOny3tF4(2G=q!rpsdhRMH9cgf6V~*wolfzgJLIRi=%TkwQ8)D=A)3zP zG+l2guBKS7oVlZzcM)z-VwSf zSR8ykc}4Q-z_h@$l)b@7;85WHq(w>PNyVXq{;mEo{zJZY$*JL+LMu`i1qTL=;MU|h z$;$%60+p%RsgaanDLYek1bYTj1ACM91$HFelr%AkUMiM!F!go+dVi(AoqwNir*D(* zVA|HS6=|z|%Y4&(!+a56s;@Y`GHqXYS@{0Y`qX_XUnEUb>;G{}5Su(+!tGK#puqb7 ztLTVO+#oHdUpD>pcJ4#}*r;**qrTN2pF@XeE%clNO|S3ht^cpjI$oG(0nY-S1w0FQ z7Vs?KS-`V^X93Rwo&`J$coy(1;90=4fM)^E0-gmt3wRdrEZ|wdvw&v-&jOwWJPZ6^ zw*V$+nCt(S>nHE{BnGx5UdTGJ*&5#pIzuQHJmIT-Sm36&<}ZEOO5=FPa$1L<1_8@# z9JI>IN6Y)qFs9Q&|GBi>e;)m&((?aK#t7ql3K>B!0+?$or_f3oWXZFo6Awv8Ffv?BB~jGh7hbcyY~0#VFZxfTJh#Vjv%K!95^ zl!?&K#w=9^0d8ij<%`hI#*Eh$$Ra;CGZy$o5NLGst2{U5Qv|q~vFb0vp_inpC3MaC z6af}9FB&v_M2pbRXPC?ZCL+Mi40D2+CcD&UXOV2#*_3R)zadI;3_FYIoOTw;mYq#& zi0Pbm7G}%NzXQb9g!edeXAn^$9BMMR;8O&+nJqh;%x#%DmYuQWGQy#zHICy`1h^$* z9cF|>P3G==iU2n=<{m{j)MP%1PZ3}-Ln(1q5k_X^hlM4;&1@C7X=jn*Y*s?Eiu-fJ zN8a#tqZ=q878%7X(D;cbF4dqhs1t(pkQ zwl*j;xR4pQ8kWwcHSocs*g9KP)NCpEev&vWw`8k|nv!KDtFwxV&Q=vQB}?UZR#9O_ z(L|7})nH5c6aj8EtSV~O4QVOPDk_q#Dr)L1PR3b9h1sg2CUXl`!&yaz8J;`Bp=JhK z@hJk_IwMMiLrvz^e2M@!GnOAmIMh_*7(PXSn;GX34mFvjbtAxH2C|yfVXCMw>ndui zb+LLYVYZr;nZa09RG4E_(OA8eFk4mBEN)qE?ewC;oM06dW~+*tri)cYh1sg2CbQH! zXB8D@tBRVwPWnM-6_s`3J}Q_97mk_1SY4Yi>q4>lja2Swoqj`@b)h7=os83O2(vDf zfW@5614aZOTQWEi<`{({PR3a%vY=gO&{mNk)9uWKS!d8^sgY_xf-{5WQh7F=O|n!` zX9k5?XV5SnhOAVopHfAg85CwKgRO;0K9c8ljKYjsC!1MwQgbN_g`?RS^4p*D*0}l zn;GX31X>Mt0G}ej&8*v!O^wr-83Ar)=@Sra>Ript2yip!atL-Nv&>mXfSVakOg58w z8lNM;%`Ej1!6x%uW=4RUS?Z&Mc_}j^z|Ac6(ZPHrGb6yw42K|_StvL0IRf0wD8~qg zo|I}7!5bRu`4je__40WR0aqlE}5WE*V9bGbf#m5+-#p3C*|`+bDjbc|BPqg}3# zdoUMsJeTX^D1grDPUtWiX!0_3>AOUCi-ZuFrf|lFfAS zT&~Y(fXnsCt3z22GlTJ5uFsJA2yT-(p3C*A_cVdoER=XI*T*koih$QRtjqNo4RE8J$=X*JbG%w7k7Zms$E$Vvv5Z06VQZcA=Pos3)jBu^ zyEV)U-nM3dRX2p$YJ(=Tn9kV-h1qI@W(l@u$<8(?%vKvTE7Gyd>}-R=jHVXhP*d{p zeClk2!fdrclesf9J8yLgGcp|EP?NbUpCZ6*4YdCVhq?*{0d8hoP%37)LTqx~{r|D| zC2&?0$N%$=y_em$_p%GJ;Dw3^h=7RhTW%DQD5B!A+@c~Ppn@^-G=v!A9b$}EhH4OXO$Rh>*Yuah zhZ?`pczol&jhV*ZH{9Lum4>4l_Gm~pJX?QL{paha)%UM|ukI&xm)D(G2WiWzCRX*X zdb9F}m6ud5s2o*URrylIe^jigIILpVijT^lD!;b;)bc6ieahc1d$8>5WsA!8DXS^_ zUFlt=UoM?hx;w4&|7FPyC96uNmh>xmH~nz>g1(ofPe|{Vu1mj?`hM!%)X}s%y`QB{r zTkXr*C$zV=|EcYPwu{^5w~cJ8Y&p1SZYyiWVqZplE@j1&S6Z zTA*lwq6LZ;C|aOsfuaS97ARVvXn~>yiWVqZplE@j1&S6ZTA*lwq6LZ;C|cluxdr-D zFzf%5H0y9Irkx>RG5`Xv|37!TrHOOt%6-3gJaa`cy=kNR%y z75^;#EF=B!Pa!?L#-HV$ZRF1S)N(_s`qkK9CGiMms(i+*z~N~^n7)StL7CE+Pwz5e zvM3IOSSHA0nLRLJnjQ{>^BIujP;!`)#PgD@q-2;0GoW-9lfJV~ydG&v`CEXlQ#GC8?pCf80s5uIqxjus0u;h)buFP6!* zQ!EoE%i=(ImO;LqP+_&ws-!#EyafEEDE#;y_TQJjJ0IYu5u4 z=4#?VP)T_@VXh<3cZ_6WlIy-Y8PahTW=fJI90)w08EvTVbj;+siZVI5VGC6j_^gdoFq4iGf%8rwB6<4N=bvkBpy+E1r6nDKKmdW)3 zWpY|yOzI6btOh}UPIAoT+9{UFwNosUYo}Ny*G|gh)H~M6wNosUYbRyO%bjbdSSGi& z#4@=Jt1{(T+-+Eu$?5MncW%R~OqJGEn8|HeW0~BBRhb;8V@YVGxTR8-2rocmstu~L z<>`cp<91d#v*j^iCb^wW9>avT#hqgewmeB#xWbE9K<1FggvF*f+>pnFMSVEjkjI27 zio*?g#)L`Ptd`AbSeb;ydv*aIGv+0SwXD3J)k?}^!ZK*PMmk0k8hCBwRaTzjSoUn! zM8`dEKx!J<614OtI5kHZalOm5SojCskqO^-6=Ns1cv1?)CGF*-$$eM7~<9GDhv<#c(;MLAs_QzQ6Oc%@Kj_G2V+%cUp z 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

*zj5#KGTNJw&8U) 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�ZFy5ThVQL);8824XD4IEe8O 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

dI>|^+pVL!tGhJy@;7!EUh#_&1A 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

TP9mtTS{A~&1-Ym%r>3vgY|doJL?aqD60 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�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

r$IgOl7&LC%!v&h-x9C9w%pBz9A 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

>Q#8u!8IyqUl*d z_El(KR*-!a+Lskn>(h5SkVCXDE6BbIjmQe}eELoYt{P96A*eDOl=A629k>cD#k#7- zM*s&+B(#F;w9p)~y`ZJoL9lr%$oJ_x9Y_l{Zv~}%1aMFrLMzBtxO+GV3iS!=wXT8~ z2hxIVSV6Wz*oGC9^65JrxC&Ki1=&}P4+q&-RfdCnpT5%pg>49P=$}laZG~u+R#1&k z-|4_r34Fj7L!N#kNS_VDL3V$r77XIAQZsWCCXs)ueq3-@nklka$*er!rltUa)h`!efs<498?Nz)&b;o3a zs(ol-h3aO>1nDvXG1a%yLh|~=d_F@EnnQ*lOxiO9way&0b2!M(A?EXzLOW>J%t7rl z2Vo@2pb#Tbh9J!6t)OZj-hrF;_0iTS)0WGlp|X9d~TyI(k{#LD6R z;UHU~TK6~B`=oW1AHK?`@A~W$4zjb+HymWAg&9H=i$+Eq}3 zaGm}02sJ19qq7kjyymNP`U$7fC3Gxb;49RBmYl`kH!Sd98@lL-J==eg)XU?+^1A9Z z=z?6YHoQYlD{<%;a11yG90QI4$ADvi7Ki-bEe*bfjoq9d6 zn#0T3*7LT3|6flB>o1FX{-<;Br1;Y}2-aW*O2?~$p9wa|VtEU}u7ZOECkQSP#P37k zxK8jv!Ht5S2xiITc1OW7!2<=y2`&;mTksme`vqSR+$^|Na64Kqz_F)bU%|r#=L?=8 zSS@&u;B$ib2~ZsPtveje)QW@W?Jig@I9gEei#S!jUm;CjLL1b+}Lk$Pw^*jsRf z;B3K@1TPc3U63~`P4C0GOeNdW2g)*w*9Kzvf&%fDavd9YfPwhy1GE?h9f$`6N^r{+ zi2XnbZnuosr*Cwmk`w7~ATA4HPq)P_f+UyOViS9nE%pK>n{2U(eQUx>vLuLoYnCPU zeY%#8RPyt9@vW5UAm1rpMW)!d5aEH?B)JE5Qh^w^b~sYWRn&zA;u>E`p~My&f6Ohk zX(NbjlB8c@Fk;Uri5IV;^_)P7AH?2di>m|is!}U9lH_v}8A_nU6lW9lvw_%BLe~Xi z!^x%!OI%};B)?4%+Y7{-mc)xwme@D3&$h%?2CHqciM_=Vn@RqP;6lN(1Xl{)C#ZkeWRraVRdCxZ*4t6= z0KqYW_~j}brwd*sc(>rQf*%O}B-k{YuiH(~7aS!xSMU_U6@qsNJ}vmJ;P-;X!bdwn z9=f|9z^SFZg>#QUj9k$b96L7ua8y&-GTVtK#WO9Aif|FV{Q?MD+4j+46yj zmOyNJQyNB%xRhe^iEbf?t)?7MRE};j5ZC(jjgC|jo{~UZ<7*EUyd*JVct`?qHO1x= z-jP6@A~B!vjKqjr+hU($^SQGvMwiUzF7e`BPs zw?>Q@ev6QpzEhF9u~>Ka5W2%L;23ZWI0hU8jseGjW56-s7;p?Y1{?#90mp!2;14qJ zM!xv}vl1SBet7=BpTZ*L{~ws!zWJgoUU{BG^3C)at9|-7{>x1_>d%=_t-@p5DqX7% zT64rIJl+HdE#9^$}49}XA^zi~Mf#P@@EA@2zQ}8wZ>is0x3N}c`eu85J zmkO>Fd|B{E!JV@By1s(51hsPnu5%pE32qf^Cw6eC;PHZbo5rK^T{}Apr5t++P8B>) z@J_+l7k2I9O{91CO;hjhTNwY&K8`Y%p5vMCEsc-TQfbL>eQSCV;dFI%Yz|t}+X5%h z`w6F5s(E>x@}2N@3xXF?Cg)JJE>=YFCvQdU8i|Pa9!{mVAxM;Wqdsxxt5G8Xd6B)?ZlcF9;#EzHZ%%I$6 z)VnK1UP>p75bVX~u{GstinkeY zRKi7+ry0u5P`VjpOfyI_KVJ84LI&Dooegf+-(v(ViHtc!aq#jzJZ*)ugPa8F7(##T zZa_CkO_<$~qPEVh2lBarQHD*_7#mY&SEWwFu+G=o6u|fBBc~_8d-_NY{Y?~-v-5-k zniibUfP8s1i8k~pXBP^{qv5wEL5~5QkVjszL@qs3b3y^lVG`;$87Z1Nf?Mfz zmf@HzsP|-^E#FHN>);$mD%pkxEhFP`tZhif@j8TL9B=oKjN|PQBP$EZ_}ZQ!ndc)f zy+SgM*E=NRG$zK#CdJ66hh(g8c1Xthj*gKni;=C0kzF4nyCp`3orH#GPWQn;hVrv) zQy@crS@wA#^L;Jb639%vFG$vgBT#qU#OsB7Dn04Xa8GrP{2Z{pPNPmo)YTin?DdIz zDzX#b>M!VX{T`ZLor~XlPlfn#)nzuKQBTpvTc(>mS12|@XbA?D_!wD|nanYCt|PB2 zRAz%zOg>6uQh{Na7=GN%`nk@qq#FJ`&{d?b{!a}{-%Z9>IV#k|ph2ds*MeSdZO<&$ zT$JSvy596Kc2Du_GlSKGwmP%*fw&vSiT1(dp=bQ_&Mav2vO4}Eh)FIEy?jL*r`0i8 zkhVsvgGZ4K<@}Ax7&8)N2Qtss@p1y0AIS0onTdzJhq`WqQG~nROgm^{Jtl_g4r+h5 z^`w%tgSOu1rPy4E^S{M+NsCWdMO1=+v1|NL|OR1)^l;zWhZ*zTMDQ!_?^^>m~U zhC(QSA6WU!F?Jd%+cD zfl!|0fY7uBd5w+|1~KK43LO(#Q^^LNGD`?+f|&B$L^x)liJ1yw$}e>7=S za%ZEH3yQNUgZKY`MI#{m|3_s3`ThT0b9ZXJSbfKmyyv|~arBKOn0RIM4a+eXPLA|No-{yJm3wEFdBOFP=k%DssPZqpf zaE;(og0+I*2{xAHgIxuC2@VsSDR_e5C4#pKJ}&sS;Mal;^Esbw1osiF5S%8c-`jtY ze7{-n?}Bd#ej%7I8&_Hj_7FT+aI)Yrf)@z>O|VArRl&~$8;A|J5bP>ANN|GS62bEX zuM>PwaHF7puYZ>Kf_4-v6Fg9GoZuqCvjwjaykGDI!OenO1-HWuF&%pf_7yx_aK7Lf zg4Kfe2tFtHPr;uBo8dNtj@<>z1@(LX=gId|1+NgiQ*gcDdxAd*mcZ{uM|;8Ef+GZH z3!Wr+nc(e$PYS*x_^n_gJTyp0TfzMVhYHRRObPx~aJ8WRU4Xab`&WX6*mXk3&VoG! z4-uRyc%0y01aA_2RPc4dErNN{&|3+17aS}&N$_aF^964Zd|2=m!G8(ns2d8}G$W{g zH(;QAuM|8=@La)Ff@=j|68u;&Ap^z^f_n+}7d%36q2O79D+TWp+#t9~@K?cYWo@dX z-~obT1dkLvT~Pn7z}@owS-}qke-dmecl^5v`hueb=L()8xI*v_!KVe^75rYXcssU> zc7pp04i}syc%tB?g0~4iA@~o$Zv=~^pWj7rU%^8Krwc9&*kYfQl$Br5_NgS6 z6+)$~@h`jd(&V$Y7)q)rgVo9RY%!G7(wRzrX^RoNto(^1S0o$IWGY5?lXNjFX>N<5 zq?D4Rr(|uhPh!r=LFr<@oZh39#1r>HlAIF|yC7X$>!XTOQ85)MiO2K?)TVzWV1`v2W#L#Y>55xd*-~E+&f)d zOF^|syhSS%n{DurkXW@Hn9o>S?304Z^7$>ENY1jw9*L{zOeOI~F8a>{3}Tm+Kl@CP zf4nVLF;$Z~%@#umVxOJ7$`(Tj4@awQF=DepJd!R(<6fS8(H3*fqeuSO7DIOp&g7T2 z7`nLuW|2RVLby07600kc1-2L>&R}y}jM$u$-E6T3C92o#V~e4rn$A>mh%M$$3Dqz& zBv!hoPW>>sAS70e1hG>gF~w$!J2~-xQ2pik~3{FbaTgXiY?|!L(g`#E%uNLHG#U-79+`8I#bDq zY%yZjDDe~NVovg%bTJp_8(R$Bmk{5VBzK@Y?|?z*Mm1C?@xyx|F&pwgTg=5l;m4$l zImwCXV(yWTvBjJuO!J&{aZaE1i&rM^wZ+IcQ-u|TAcHK%wB;pR4BaJa4uN%sAU4F@ zioZ=4b7z`Ms|G=AHi!`;w)L?%5r~oGCFRA%=OsI(i@EFF+ZLxNHWNE0Bqn2Hi=AnU zkwI>N%WN@{WZh@m;%XA}@Lz3hSC6jCidbJ4LZm<>anUZnVWdiP=tmvBglr zJ@PiR5E5|0x-qwJX^X)i^WEJRvl2=+d8jSs*yvBD*kaZVJDF#TIX1?I)1qQb;<1tw zlfkRg#as<{*kaZVB@f4oH`ro7;N$~aY>M+kytsgtequ7Xqb+777^U!okwI)E$yMCX z7HeW}OOA?)K^0b410}5crW-#_&X0;w_+xA_SHlNA&rL43#fGYCTU<>UumJe((mlB+OZHrBD8qiu-Oeu>)V#-NH`MK}kn`|EvD~kmud)s0p z$>R>z;R4+dbJsi77W*VFEeBOc+v1c`f?o67keHJEo(A1j$%oR#+-qVfFG!M0i3!vj z>0<6?zKavrB#UUBE>Obx_Pj00U2HKj$m3;iTa4mBcTIALEk;gQJk=I!-POsXZ84Pa zfPRiG)=H|A*M!6rn+M(1A+bslcXjKdV)PJiN5$v|e~c5?B%9J2V;UzIqW4S}^Yr!r zTg>^!4Z`8J7=@R~uq{Sx9x~3d#Y`;neU&XnZ0_xEj)*CQ-ZIK!?J5aeEmRhDM1BiG z#rS0Hv#k?BjOv&jpoozb#Hfvl@%+J8?ozi7#Q1F;bcdsk_?$vV-DMener*?%kJ6Z& zB1YZ$Kn#eTVk0JC(lE`zK9|I8G76P zS96H*+d7N|#ns&LFFrq@?o<%Nq*5DWh4*~>_knfC7=8Wv#U_=wd?1F=5m(x$Kfhy1 z-8vAXZXLaPt~k1G9f(o4jzwdaA5*su#4tMIO1nCB^09U6K#Yt!q;L8&!4IuoZFN_x z3tJu8>X24@wrbewqn1y!T-EZVmJ?bgTefWZQ;XMI+|%Ns77JP&+G4L3jaq!N(^ETL zztbr@P1>n^r`9|D()`Wl_ci}(^F_^vHSgTKxcR?!T)(67|FfUJiFX#!q5c1_G`Ora z?+5VcfjU|_q4OMzj;60Ze6960_M;neVw$oWa&)ugM_=5K6XxT;XUM_M9y3L&!Oos1 z({}cBxc11znrosvd)8&m?S1t2#rxIw&YmZ!-@=J;K%BpS>YY73;d`aS*GS8HI7SK1 z6~w|LjunD;2tFP4FPWiGoW7u?&LadclVTUl#mSFq>}3aWog~EI2@Lyx?NNa|Evyd_eF;!H)#_ z$4Ti2JL%>YM<>C4f@1|22%af;wcx#i&kKGi_>15+beD-^55YcyhY3~*o+fyu;9Y{x z2)-})qhJ%+fzv@SDL7Jaj^N3HmkX{Dd`hrZ@H@fAvIA#V!Cr#H1ZN7KAb5%3t%8pW zzAgB*U_;q~(?)O~!3x1?g2xM9BzUvn-v!?g{6a8acHp!Y>>+rt;AFvL1TPT$n_!LL ztAd{iHjo`SEd;v?4icOoxJ2+g!RrJc6x=BIiC~uOyxCE(Oz=R#ae|8k&lbE!@P5G; z1UCzA72Hlb;XMWW3LY*vU+@gUYQcL1pA-D2;Ln21WCzafg5`pv1?LH#DtLw9or3EH z-xK^nutav?v={6xI6`o?;7Nj)3EnRFq~JS(-wHO89XV|U_Y)i{I72Wc_*cQzf{zKl zCHR$Kp^O_l3mOk5T6x;<5Y+m#-ltC7;L=EMVwaTD9f6O*BM>8YN}cGV12GgJsM1H5 z6^Nk#M=FV)CJ-YwpJ>p57_rOLiFOl+t0^{g*ZMHeKx}lwssgb`3UEcW+RhacL-%cJ zObx_{nNnwSTM1&axP>i-ZXBs(OZpoqK~8FXoUPNv^qpdt+G50H-EC}fEr}82`@5!# z5mf89PZuM0jlY{MhDc?yd=Fb(O=6s-ekWUud}HN)Tfd7fMob*3WLNqdloGm2Y4Li5 zzqc($atpkF5E~*Ws`mG@#UPfIVC6H2T^TS)AB-4DB9p$~&lZ;|5yS_ki&Jz( z${%QpkrQMxsi`wz!&N^Eu2GLjj9N*y36e^Et{ELoc6)rHko1 z@jWJ8?E5I?SX*2}qG~!z{qeRK$+38%Ek?m{q>_{9Z@_oT*M-MTe;}?QajiP1*<$3m zn#864Ok1pUQ&7sEV~Y{nqu8E5FI^1%HU5$5V(6~+7o>~JNYQQn;&d^6r{WxCi)($* zgmbAaMg}>_V{9=pn4;JzAI};Fg;)6|K`ME?ErtS)ok|zuisk-Ewz!sJ`*hZ%ZCa$5 zI8w>e=x>l@C2~RyA(cGC7MBJYJj)gQ_Pe)|7wfNC}u5*YyC@YF)~;~;u`;QTWpF$4}X}#LpR61$`&KGM?s!{ zwJr7`rX!VHNq++dp(LfwYi+Sf@;X~wOEGbzlGoGUKnWP+YWSNiuJLsSZ?eT+kdvEj zF?6$%)wZ~r#C+ani=jKE&Na3e#QG%m{X1`t?ZQw-a3$K@Cc))hl?BEnTpFfChe7G zsZ+d@wB=sz7Q>Kf&T)#XNF?{UZm~f@hKYI37^skJ=Uhx(T4Iw#*rGXjorEPu=A;;t zl$N;CRCr^~&=Q*@mN{AC6x+Z?tBJ{YGzV|4u#Cu+93(c+c8Za~DA|vi%bj97C+9iE z25HNEg;R{;>?9*Q%?q7kh>*$j+?7r-v?WxloMPCmq8O&R#wmtK+P=&whDd5(;S`I^ z8C0B{E8Sw*J;wY-#I}9CQ`^i%2GK*Tb&6$CP@J2cVi>8UJ=46+DTZjL*za(PO%lP7 zY2M=&1D8tkey12PMJYzqe9$SbA`wa&H6M10VL%#r%q=E;!eqTuY#VvPE#67?cbZQ+ z#jtCVxGLvqrx+QONj~Qk!+32;RHrv|tgl2a&?cQuf)9FnIHLcsMRa1Y{hQXf$Zv-9)tO}F{h6Roa zgae=XpY`A9kNPM1`}!OC_xg7D*7;WZ%6%hz9eferZsU35CgV(_(C8DyuXl_y_i<4b zk-z`HAgv+;OFaNp5$*TVjwfMiyZ@s#O%xxGhK+g|^vikiOp}r-5bqtvYm=wbK8xOK zEZ^^o7g{f(vl4nqFy32@H%6CJcoBu4O7AVk8+oVDOPpsBKbzj|E4A?A>LQYtkX^j< z7w=r2PrH1(aVfpuxQz5?(Vu!7bAfo7G2XPjkoF47#a#L;r}&6fOkA1Jm`h=$BCW|3 zg4`674)Rqd(krGgNGnKIY%HL#Vv2`c&LKLPLh&wPymVT=OL#Kn0lAdOt`c)YV&y^pwntsqyj1SroO@!sWG#%SYsW1KP8$TvpN=Nk%)0dzi$B*W=^ z48@R|IHHsaAEX83P=W%1vqS}!fdopd%3DskK?x88H6=?1K^bBQ!7=ntqnW5~Cb0}ttErIcU1#T;5F z9m=NO7>*Ly?{1g%RVK;?>jlCS=ZM;eJ;VkrAg+wc4E^bpn{xV-II29tU+|`T_!p2z z>54>|kPGCelqg;Rj#r$cofHWS;X7z$bHtfUMIwRP1I`uV?cd0W^jf^3eKw_oQlpk< z(GhJ2sUoKmXXF6!r8lA0Pbc2p#-xo!6i0f&6oJG7I#VT=&H_)TPoXB@x5KGrB3F1P`wWqW+s}awQbl`(2cY)grLyfQJH><=dKKtM zPlF$!M&W&njNX(7yO%@{5F2hIi8_NOd=2q&h89;rxrC>|E9^E}AndD;HNZ!Zo_x2w zY?l>OvdJPgJPGZ|RQbntGwscuayT`FxB#@a#`I$>C9PhN3JbD!MzIfGl z0Y~Is)(+kZk9XczP!3S4SnO=iLizF5eY-z_1&lqhG2)dHsM%;CH94M1FRBpr0xw%A z>KN@wrY&n*)pCWXQ~0@T!Cw6Y;$r-X^$*lUyu4wogx3|5RrDnoRb+dT-XL2B{2Ak? z-R_)U4nP}Ktq>Ix$q@{Z9D<3GMIfOzq(gteNZgUtHi>UTwZ-~*noUSi&jlqQA zT&h#_kjP^``wMCVzK%AIFOjHzS9-8}oRc5sK-pnK)}$Kq5Pzz$f>;=>;pr%?99MuG za*w_pD%Zl5RTDWeryA6L$-2x%?fN| zYg^UiLg9b7)|EL#uZnWWJ_&7P8s*#BTi{zHc3)IL+OUNhP**>)herP;^Ca;@U8`0J z-0iDWyXJ8PF0ZuZn69phP|sdtmb&6Zd1K4hmC(nPm(%lcwG1zp{Q=78zOI6e*_4L` zRN^waE|cF~L7gGDvMph>10v`PFkZ{+JEVkbWO#~Q9@)3fCi#4dBfr#vT9iFF(10h( zPqj!NMwoq-sL-gOE9Rk$9#X_Ofw*Y#>QgWB6DDZC$epZr)Q-$2{Av#6S6)%8T*FII z7qYG5dKW!Uv7qt{WkT5uAYRgg|q(h{N4u{c1OI3iTCzd7eqR;K>$6T zuL{w#?qx?b@EN8P=zU0sI(z6e&G3^f%}+6!(;?7;PE%X^4PP4lD#zEtq|-W+5lSz`s0z%X z(6MjOp-{>?cAz=8@(*mT@G+-=i+>8z_`vj=Grx2G`NQd-NQi1s@AH6kVS*xXV zdc<1dj@Urag4+@IZ8}YThXU$;4(?YvB!_~;Wd!MiFHMQ7*P0GN`U*yspFVq0<)iOh zENh-aT%eG+I#Wn5joyafYY;`~VCRSAQKHLYwH2wv`Ua@6n;&=cMQVjkw0Y!`|T`a-T*a!&k2r9nyQ!3;cY8NtQN* z4)y3ga=x?~P+34+qh$!7cgR)wZznF{PLegf52st{H0f1vZxdJNT?)wjk`9MO5{)Wf zGaND#3B=}!hA*WfaT)Xj^kqj3rPK6c3Qd?xrwtd;Y1$=FzMf7am2_I~4mt#%!0A(o z)C{&D^kq6t+DZY*ZxR=1LWe|Z5ajnT@EsP?IWd!?)W?hc3xA%!+};N*>H1-0Hc~J}yfNQaVUlpK0fiJRiVx z4pVwMBetiRe$F%nl|x%MrW2W-!}I~B-!iSwzr*dpbTre`n675}IMdxsGx#w`U#9AJ zx|j3$>rCtO?`B6aozL`2rs{XLpYwTIvdq8wUF}3ZKZ_~es)g-8OzR7ur1CPI#q?sP z_c48w>Ca4$NR@VuW4eIpElkxL?7!yodfa4sF+GXte5RK%-N016p*~zkrqhY(iA)zU ztz`NP)4fdF!D(q5!L)*@dc*u9e7=L}PfQ!pIvs54jq)e+`7)-rFnx{b_e|9r<452J zc(he8y@u&_rj6Kf`!OwKdJ)qPm^P~~?G!V;fvI{!dvpHXrj2=tRUf7^nXY1bFVk&Ie_-08u}o(I(@U8?!}NQmhaD;Pj%7NH=}M-LG5v&T zBCqjk#dHAExlFHTTE+A$rje#Hoi0o#FqpDI}1^jN#|k@VpQxzJSOEZK`AHJ)byCGE`RGJ)Z39d<~!AzAHO)O^)eGj)upY)%awH0+Su`1l1snOX3EGyjNC`8E za=tBAEo!T^BRiNXpGnp*`HUq3GEpSx$eC2cIz_BYku&tRq9b=l8~rI!EOU@kd;^GL z8d565qQh!Gp3}tKBGz1Btq;mA=ZCR!05hj?BDbbsOe7-rHt?=|H>d1i!t`CM6SNm<0qII^bfV2QdT6Rm{`rpBQX zr;9)h$oX?9)zB2Pgrc0Lm&Nj|D48#;hQRuX*s5XoZcpWt%a6-M)5AiSIJwK>5@H<) zEY9)vj$e_ELk`%y1Lv#8IV7*+^I!B1|RpqG=*SlMO7PL1$sN6{t9% zZzk02VCnLffAywd=Q2(A@&`Mj0lTzwZd-E1`ILN|Hr-sPpq*GnfDPK%28w{yL~@mh zttN_KT52f5R7zy7qY|TbFmfUXEZc&slwE`hmSoAEMRm7!1XVhAjH5+_sg$g$MyEfz zlu>arNYz$GI_S#ndUoWuHL0WF#tt{I>FAD{)8Z>El|momX(fxyW9^B(7DuYVhG~P- zTBw~SN*abFw4XY^gCpTCpr4LAjJKwv14dyU15dOAV`;Bn(JK?n0#psmKh;RPM|dfW5FoKciVOL#tuM@9YIlwi_O9Uv4Adq zdZ4;>B;Y7!7R84O)=)ZoJf-HaC6^+*5rHjMYEQP5lDp@FJ%`6PXH~mgP>{QDmv%w{ z7dPkum*EM~1aid@;KT+fxmcH}>$1POjvgN1PU zgr#RK_9g|j+R&Me0$!16UtYL+usn_kUqzRRbevZ~SdfRM!svk!W~c4|vZXKrv;lxr zF&Rp(mX`d}6Q>uctW?Q<PkV*iNCh6zw{vHCfT;CdF&+rNi$ zM^*rG!kNBY{R!ysdjqT=bt524Z)JX{BYn9LSlxDE zbW>Mq>PHt?x*69kdpbN^??~Mk;YT6DY*q?YrPZnQ@hX?nC}20l!j{4Znt#kR_tw zhfrwv3+X65%VoV8sbSQL(}CoQ4j8p00k&Hcz*cotK=;Cvz3m@u8Bo8rsv*yIY6s}2 ztvNMhwv_(tB0H*CDWUGL4Bu9g*s#lBN zmwj8T3Rfiu9axe-6~g7d^bms*!|q57Di|V_6NUT7s5GOjy035@fOnx_^>o600cty? zBgxhj0r`S^!_%aaNqNG~`9h-37LE808!agqk8tFVaP5-80vEnHna6K|Z1-~6=tK(e z6Zk!T1cPo#{%p}R+r3#GR|5902IX%E)y-kth$yEVVlXa9_mw|Ez-6K8wp0fBP7)bZ zcY64xC`PN(D9bTVBnTFUELI&qA;!;Q?c$@(Vjx6TimBiw*5etMN;|TEVF?KXp&Av{ zlU&tCJ+a850^vfWf*$@gQPK1*dV#`2-Rr>j=CE|A2!?J>kdzCNvhLH z)p^J1g1iZ-*ocWh8{lRXSOW-MC>WPn@G0--Yp%rF({IHPVG$kO&zengWNciw&B=Cb zD^#{v0k-hFBm9D8mU#4r=OYr_7+cNvYb!MHswKnkGZquUZ81Kn;4}Ie*>t`y<=ndX zOaoM=)4rE>>anfu+Y{SFt&=!RXR?_G@Dwmk)P_I=L8Ui&1D@HUd@3SB{FxjP zs2ZDm{jd-}9x9;d&cEK(Bt5JIFs*yg3G}ET0n7s*bOPOKNB|SZ2c1Co z8WO-vuRoIx-rN4wL3RxZV2b#l6X+Tzfe;yw>wYp&mY}C_KjnN_<`6fUTmvDaxxE%) z)M{*Z$BEixpp4j!+B(MTEU_-8RzR+fK~I{)%Tf)G3gpp}q$ru%Hu8ey7jeri1HIrv z_TxsAfvVJK9)4>lXvkWX?h?|ZKbf<&04RNE$Kp7?0H?hPaZZsL_P6?|V;zp5yUMXR*L)>UG z(47+2J;aS71HDp02;>Ldles&ZE=Dg=JxJhh>Fk{e)JkXH#nqEJ6Are(dBgs0%x}~@olz}pmd%#B+LJqp;>RGLB28zl@0=3d}Rqq<@ z1Zt({s$Tha0)NY8Y8QIB1$qcTgawcXT?f^>AUgr9lsxDJ+?Qyo1TDyTT=$cK(*0uF z2SVbu4;komb=f{3kJ~8R(4EP1FnIkRCK_yYgP>Ph9xaN&*9`mB7BT!u72`lMddS z^o+k6JNnm<0Ir4)I)Un=pIri6Hy?BY?k5fE$_jOWtL%eLz}*g<3E+DCpcAM*2-y&! z6bGGv`$+>zU{G7epWB7IavNz!8nx2ByGKB00=3e;yT=b_09cjh3Cn>iUMqc}6yp{8_Xpo|oIoq>?JX~{qt&3>u{ zDP#vd_E)dZ0b=Y&0x7kTK=p1%CU7(vk6R})&~C=VY|+Ic*}tWW z?M73K7+{e=tvJ5>y49ILT5Uk&ezxIEpjNI;-S-YEfu5B6xb7qaz1#^QaotG<%4p7} z3wfLg3Ad^HTt;z5AmhgL-1MDwZciMN z_<3k~==ji)p-&SYNcdO6kc4^(JA-!y7Y6f!X~B;JX9kWBGzfg?U*d1#-{70z`^~u0 zD75DP$^!{E=6Sd})_f6Uv~NgPH!qN$Z8?)EA5 zh^7T4UMo%9JvuuRs1>IjQ0>-7j!iA81hGvVEtfYLC|ia5$_GFGjE$&b5tV^5;xXcu z28&)6@-+?aspCeKfii0B6#+uxb}KSaMzhC5NFh7uQOf;%NZr^Wfm#`*+=GZSfxpRx zu8d-f>!keGsEg8N(!qO^X#LfNcJ(G96F|3GDK`eaQ`V%MmY$won9?q# zVZBG{o=~@2#{lm!|}OK7Mz zhX(qY$V}AwXzA-7*Rd2$V21fkTxYA&AKbG*przV-UpT0|Jy*K_G0v z=^(VC;ME-v3c*BTRFWd65FiA*5S-%HPy(eDfEQ6Nyc6&k9f2nbDb8-*ao1_X$r zqJ;|FQ6NARBq@wSpwvM_g>*ou+AV=XO$cIaAWAhGP=^iS$EuMzv%iP(q=hlC+{gfZa+Kqq^!KMHD3vQMUBR7q57-%Hk9VjeDOz zB0+@;qfUXP0S2KcLTJtSFT*iWoB|OA0u=@25m6A=vaw+I`ji+OPJyriLA59f9AOk1 z3JcKCghdz7SIiwX=M)H|5U41kEIE;h^<$zq1tJOrDjy2aY~g*{}pEz9m2u6-`7z4zXJrs9ROP zp@Gu}<-{0;K*fjg&>0u@F=I3!aOPBKAd1RM=;8Z1=Eg*UASTMUZ?x#IG(;*8C=CRJ zAW%jv0m`8=7i#-!g8|Bh(11XN3Zq3$k_X0Uq^$USP&I){Rz%6W=iR|E8cuIX$H~Bbah~k8#xm$RTzS1sceQ%1dPX zo+jgCq73d6Iw?l*{VlsD#0ajf_vEB%f|yi>M6WK2(SRUE16fyvLqG({#t4vXi~z~T z2$WHzBy2d7MFB6+3!B|T+d@;AxvC%=^Zc=8>|S0?MEhBX!zQ}h;A10lX zwl(RAq`Q)?PFj()C~11q_@qHeJ(5}_U7l7i$)B_*yfgetcw_kP@HOEJ!l#F4gij0) z4rhm3hwFy};qMbaN_;i($;5jSuT8u#@n4CjCZ0sUuJ4)HCh@SuVB!yY%Pgt37M#8Lw2?@s~^iF7-aCkx} z;iupy!CTW_4?Z1yH0}OiW$@zQlHly%#Ng0ipJ2OS!(d|Y=fJ0dHv-QD9thkJSQS_r zC=Hwv7#27ta8%%kKsfM=|1J|6bqcz8${j zeCvE`eXD)TeC56Z-w0nnUk6`f-#cj$->)=-|0aFg{vqQgV~uepePp7L=l_Go>Exb1 znlr_tKA$nl*MiR9sB>xR8RFcaACJ=)H|Q|{?(yFmNC(;m|NG*PBp3gPCo+bw)7sL_ zlAB+-n7Jl-%a|)HRowX2=d!FQ`zq!dPrQ@4H7`BD+yy^9&fKbtUSMwRxR;nqta_Wd zCx3sJx$)^*g#Ux9yt# z%q`wHh`C>X&S!2@=@{nL96pt~_ZBGbh`G~QcJIeCnJeja9dkp=A7JkI?;c|Ag>N2Z zuJ@fAn5(*RD{})HzQ$b1d2cXx{O|8D7rFEc<{n7j!`%8^zcBa4Q@=82Zr#V+#+iYa zWyu>pc{p=J*EeA<{e8vlTHK6fkKEaYxjUz{XYPqeH|D8i~jtExlZ5x!dzC*eaxMHm$6kMw0lp8xsyhuFgLhg26HzY zTc5d$zi7eS_)+bc%Q(Fgb8W|WV{T_xleypO4PkE9x)SCRKQCkMlNV2CuH@I{%w03` zeCF0(a4B=mPPmG>>>bxI*R|{>=Dz89D|5eJaX)igHb27LF)zHt+`>_BFxR)!cINgp z*}>fXXMf1t7r*_=Txdk2S0bW^>#?%sD-x@Zu2J0d^IEa&whrx?tGc@zbD8gDGuQmV z-pu`Z=K$t5KbOax@$o3;?kz25u2=KZnEQQR1#>&^TgcqTyU%BC<=!iqn{)Yf%w6-I z;xdd|S+?ZZrb8im(l(|kHDDH&gcC&24ps$$wbmlkA4SYy( z`FY>5?1u0BuS%?*`7oWiTb4IvZtq)}%nj?&mbr|NyE51Ely1x=Wn?oqzOol{t2gyw zZpZI=%ng3}IOZmuG?cl*A>)}_cS8|#@11`db5lDkVD81hQsxFNh%$Frr!$$mZQl9J z^`3kYbMFjV&0O&#iu>cvH7wh+?n>suMb|J_u~Tua&cBXjvoBZbjzvM7CHDLgAmmi(aTw2#*%w2H% zIOgu&b24*JJu;EGJKK~px8j@y%pKSGeCEQ_RxtN^@M7jJO}w1Bb6P3xzK{ORvft-j z!Q4rEuVU_~&Q~+{%LUgmms5BrbB#`YjJb-%>zPX{c#gTM+qNKHbDJKV!rZ0fXE3*> zM;UX!emjr3ov&ZX++&?@VQ#?YJDEFW=#Z>n~l&+^=;inKOU8k-2g0Ze}jO-^0uu|KO9%omcfNb4zzW$6R7?6LVkv zdoy#F4u6%o?HBH1uKw6>nftKspUlmR2H%k7eap;X?wUwl=Ju2~WbVh&O_;mr`)157 zf2{>`t={g<+=fPdnG5|nh`Gg2j%IGxU1OPR7b;{fY1s_so<4j5bAu0C#N4c2XE68i zHD@xneC#>QUA6aG<{D+KW$vQA4={JarVY%!eBP7HjsHY(3mQJlvQ`_mGM9GQcIN62 z+riv9_r9w_|NM-(R)McnXoJvpiQkt~(wX}zwJ~!&l8$8Vp`AxDcYj$Y=34%uxWwLF zSayMN40Bud4rXrMaf_Mjar{}#-Eh@K%pIF`9dk2Ztz>S)`!_Oo#)kWtTb}k9bH^@y zg1Na*ZDj7qw>B}?ZQ>T@E_n7O<}yeA!d%^@#tvEDi9ObCkAF9%Lo@;!0gZr0KqH_L z&eM9(x@NMDi!k30ugrnj4;aTCyG`~I~JTTlR+&SDPd}O$8 zI2`^n@yEn36L%)=NPH>r>BL8Aw*4mh9s8=p<%x?E=Oj)~oRl~=@wh}YF*~tCVvEFv ziD@+V{#)p~&}X6dLfhzhldwAByo4nQa}!QYC`>pZVOT=1gpLWV5*j6> z(;WTp!S92+gC7Ln2yPBm1s@LH6}&NcMQ~;C?BMCa^59gOtsfm65N_4o$oW>d%kVHO}>pZ%YTQj(s#MvF~KxNZ%k|Utd>WTVGROeP6_9 z_IMp%#FX#OuMk_JjKU&QHZ=(7CC64)j z{LG`w(a4^TJnA^}WaUGxy4RfXk>mpREoUx!-Wuk1U2!{e`%b@yxeI=~kGZOM?q_bo zDNi!@+^;V%*Q9Kdl2v}oT+^w0m`l0zd*=35{l?sN#fiIQURu4|mbuTjbz|<`ZrRNJ z=j(pVt*y#sZud{)nXBJm5_7$xvzQw@b2f7&-J3n7L;MoWWe3 zF-w?x_o8LYefHj&%r*b(clk=Js}Zn7NHVS24G< z_$B5_e%Qv`_Fg-foBp3SnQPzeN9Gny`-!>otkB02zZqkanEPtS5zJLQdL(m4CN^X4 z=Swn~n^o48xy@U9GWXHeKFt00NFH-luMK7H+-pZMchiw$m>V``9CO>>If=Qa3ksS0 zpxtEV9^W&Cxk0neVJ>gs`OIDX)g{c`efCYvJ=NrX=GL62xUtu7V%dk+z06#zleaPV z`rGd__ruY?QeahU((yy4?yl5YDQ%3nek(fSoN|3qKw?oXW-Q9q> z3DX-gH{#u<%w4{+BXc)h*Oj?@n)P7r+QeMu8l(+k?v!%}GuPq!;mplxHHNu8y~Z+E z_k$CdJL`)A=6p?yn48c;arIZ1v+T;=XEJy8dc~bMZ3WA&j$X`M&dF<-+jYcc%jdV8O+A^pPO~R4cl(7CnR{p3BsEX%RPP# zbMM@68FQCSx`Da3nyzK;u&SGxOZ(v#=Ek>qkhy)UA7<|GCL5S5yX!UPZe9L5b5EYN zLxo=L|4gF$>~_T+*Net$j10bKXC^YYZf!Di?K`A1*R>#nx#s_=r}U<@V(y&rSH?$(BVn9KdQ;(|;2vFyf?1DU&g$`Iy;-ILGUn)fF%cegp2 zxof_e&sC8R0WifMg6PGhLVf@9+b^dQ9bKgAi0CSTceU!QU_ugb~(#Ac^U4OOl zxx{MAJ&K!uT99RXx<{BhZRL^7)#=cbxx`0WG56VdZJGOgUwh^@Ue=GfthPg$Tm1Go z=9ZjL$lOEgind|c0JzDRaSFzh=&y{R4A1fAu?aKb@Ac zTOt|yvJP{HWu-H>VSYX49(unKbC33I%v|9kO_`hi`_atZytF%WFHAO>OSry2b8W&C znR}|$ROb3ETFBhv51r0jV95E*4avKVxv$Q>mbt@QUdP;(?XPEU>&Tm#d)~O4xeqT< z+?eYhVA-R)-({|3+y~6v^3@*Zu7BfK=DNM}8*{HuNc}=0RN1&8a~-d5#hkBsH|8$d z(v!K=alM#JnB1Sa><>pVmvrSM<}SHQaY+@0EW0Q;jk$OF&0wy6%5vsj+I2T`Yu{MU z+`nc&&fJ{iUtn(F(XTVtfAMza&VJzw=63w?6LUpJ1;3PKOPid`+;QimD(;R3%<%Z1EM_}41tN-{2G z?z^3rGuN!wZOnbqVLfy2fAb`B^EN-n+_%?lVXk+*?aXaBds%Qqa$vU^S&!rY$W`OFoq8_HaZ@CnRym{P>t`x{G`8?bl= zb9dcP&Rpi{iA797ZmOq|hZpU5EGWXOK zJwErmk# z|Nr;X|9)ByfS>&*88^}tzt1?q$fon>Q(w(k%G3X+(s?fm+edU<9vx^~T={)l$;Cfr zH=!`RE0tar=riz>YIuOghMy_Bx5$JB<;ZSkZokE4@Vwg9(o$t$58rs+2#RS=x=2ArPz24 z^8s2IU}T#V`+z;FJ9%98aR+B1*T}|;?GLyV-Kia9;|-;FDd~RU;Ql~iXQPLajjtE% zcOKjpx){9-(KhiG?E}h#)iw{TKb2e$=$ z#8vcB`<(~(CrCvfg;MPIc;U8S^$+_!@>aJ%{R42=@7C?MAaI!cKS9!MAseHAYrm}B=@^E-4+Cr@Q3~ON%yCbg+Jg*Y`^p1 zwjf$J#?Ae<;Qs!z7&n3BewV^+!Rn(9*n%hp^042%T%OCLL>}-G_x;YpAeV(M%A@uh zNw)=&2YB^dWt!^SCV?M&lHQ6DpmVq>Av z#mFW-JN_#=JH;sTsKqH_L&anRurTSB=QkJCjrI$QBnmj+bd-C`6vWC(~hsc*n!!rEox6z9i z2Bx=4+7+%0PYN$jKQjDI;*vU7ByLJSA+dhqw$Q53h)^UWCA67dZ7?VynDBHkN-rt+ zJ+MBoFpwSi(f@$I+~3jvHN7-oy049IWyUAQjYgs2qq|+>Ow8&#H=luN+Jza*)1rR) z0Hlb{KcOAB`f6*icE+=ck4M8s8+tl-7-n>EV@(fJni=KB9HWGu$#ga_MN(-RhDm!g zhaO}2j4Tm6gC4)l5f9%Mv05~TbZxbxg<2UMOGt4pttKcS^}q$3PwmUSSkXFR%%Qm`+%)jWKyO8*c>Ueq~IyWd@5uKrDbQrk@urX zrG#=@Otwnt8RsI}D~R({?k$v|ipOgx=3HZ{$iz&_ju+yFDG=&nMU>H|QJQmOQgY?I zpNMcOsZ9}>R0u%JDM|?ydp@1b618Jz(^c#e!oPwPmEXDScN6yK(h;>=L}>v3*;HA0 z_-k85G)BK2DVLGo$fw2g2&1_U&v8Hs)S(E}m5R)rWh*O2#jbE9x6}6I_N0b{3K;LX zbmq(*66M_Gv+7*(joAVuSw$!g+6r=uwj-f)w;pO9%20EPFh$^7;wYZXKQ~z@$hPRT z<3bpAO2{i>3x^!DPqY>arQ~@|_l%N)Be8xW5ZRtViK6b2Gzx1BabMl%%SR#Dr1c|r}aR#Q2hVJ=1bqb#>8>Tu7fvnXTm8|j5`u`)8WfDB`L zri9{n8tX(T&!eK3lR7%4(WF{#HL#vQ8nm?AjhlUM%ZMD4`nNLowO|Nc9WweIyNZxh6uWeV=Hik- zW_%xJrm*#)9jhF$8q>KVb0sO5E~}G;Z_gABLk{flQ9y+TZTUR|NV4jR5)Hsq#)N?byDN2%viEIFDCBU(ywxDDzP7gb2BfMg44 zpF(9(O~}rh8k1FCQ6h}=vZI?#b{3KaTqWUhwV32ZGl(@qSVJ8F71TWHAD5uGW|BETo`Ao?!oE;Ytq(Z4lPgIE>m0#x`SFm=0AwIxY*-RIo2Zb8uBYnhabPq#K?p-;u;6 z&`RcI$t@@};b=M}c11vMl4VfyTNW6K15KIxl1C92rpfLqUom7QIGs+1#Bx|(KwR() zgwlNHnLe7bG=0mUawZNdU|=;uuf^dylKHPEmB0?@<&JAZ#NpKvlMhYJuojr0n|+5;DA^Gk8SopejO!AY`bt@pNc5rK5BCkb)-XLCt$izD9R6mM~wQ!Jbs zj-8Y4Ew1Y=Mia3zXd6Ly;URA5EpFs3KGIv<)LWeCEpFj0Zsjd*<1KFMEk4Ry+`(Ji z(OcZvEv9p-hHh@L1hS1M}@dZmI<+t$eR zNCg^pKD;z=X=0aFjIGB}>n|fQHr)B1Quw9u!&<{QTS#n*(>w1t>X5g7ayW6<4=C)h zHHZs3+M=c)hwz}_Rasci%+pM9A&m{!l-O_`g>9ZB#)V9&3dAPI;Hv)eOtyB>&{6oE zrV;}VOI&4%{Z6rAiQ$Wuc6lzH(H6}ikuA230CEaC8%nfUy@V=i{C)(tW7w)`7)Eow zX#_L^8Uc-fMnEH=5zq)|1T+E~0gb@_a|raR)1c0eY1`87OIw*XEA66jiF?;vJB5j*7FL1AB2)&DB2}uz_AIP zZtYu?oZUC!?d6!nIwafClqz=I>dWUOCf(;(43L?LnsmRLU_WGrHd&V1l_s6h7R^D6u*6j+gb?EiktL3r5JFs!j<%Sm>!LY_ zyTwSBo@>X5O**43n$wt$mJvJ2CQfmBE_9*Yf{q^A?VRFDlg?<1=ETg6+v3={aZ@Oh zb{AqS+qQOBr?|?}?&cInEipPJ%g9bkjGo96+qU~S#kTF(DRavRx<$)~og~6N#K6o$ zJisY75tp`T&Oka^+I9x>oMPL3@YK`Nw#AqNvBZW+XS79gVkX!Pt1geDKubH*635KD z+d08}hNW%W#`J@Fqfb&Ng!(qzyv~ZIwGey_DOn6xhd1Q}XHNHLJ%QvAt+Rjv5+02469> zE=XVM`WGDVTa+~jqKY)DmQoJ|aNWDjeFXv;N>m1rQ?h(ENOdM>YW z@p+o$zGXU{hn%yRUdQw?rf)F)mT8210$XdQCesN_)sS=vpWnsw1*W^0{=u{%4>{eK zj$k^2sam{tJ)f^x{>MIOn+orhnve$ zO!Jr)GW{3R%bDKK^d+Y0qI1~VU*<{;cHDcT-%uh@+0>|k$XF1mU3yha5KJOiI*4Zw zeA;Jt2EiTT1flD21*vS{qAgC)Vex}-O&2E?uIpSuhAGR1i#T@>iI9~R@q=2%4{9Ah zs9p6SRpj>ZgN}|L)G2-tt`uG6Qfc9`&=o`yQDj^ix`I@!Uh#u)i5Mpqt_NK~3VG8b zh(t&a<64|pdGUkts|Tsf53L?V5|P%3>Om@&Tsacs*rq9C$)y@GLCWCx>IPMjaS`V# zmr4s)Y;l4L;s+H~4Q&1d>n(v#Send z$B6}>j}rvPjuRvo{lvJ9LIaNNiltf#T+|gr63a#71i=pj-YyDfixUKAixbqsBgmB5 zfN#W!g-VGNgi47M1PtQ@p)JP=DvTe5%m=;EfX~MX@==iCi>WVFDPo|H4N_H!A=?#8 z1trH15*ISw2J6I+g<6i27Onx}1mPMWP7tmE;slAOfZjQTN5zSS%Y!&U&Ef|&j~|39 zhd2gtrEq|5zq)|1T+E~f&ZlloN2tD zsHXof@EdgBonib$SIa&lBBuX)HC^XhA>@WpLWBG&+AqXl&s%ZP)DIOOkBS)xOqGbM zaMG%_Fx}Fil*H!!bB)d4KDe-E8_=lP2IgjJwgHWrZD1m&W*gA3ZDe}t3zg&Y}(r$6G=)+no}}&-kj3Rqr1>aNojE;iD=%5 zc>~6c&CARklQ|%F*s#3O(5xt#RWfC6ra5#(+IXZvDfc%lU z!wT{TwiQN|YJSDQ$z`OPKc--K-thi;quY+iDxO?WwxG0RPFA~PGRf+UsclVhUO?HK zQIT0XZ`Q1KB)5{Y0{d~&$jj)w6Y@rn6$TtR8#tqAn#fu)F>{J$lgSR5=D7TUgUaTW zBFp7-W|U5uQC>6)iA|kTHaoL;a#_cUMHL*OB6Gp?k~t-@dX%n}haxglS~Rrn9^e10zXjkG^fqCW%`JG)VEI#Z2RgP%j@`GLkf4 zbY3ob(x7o828_)=euMygYH0x{Q8xKh@~Mip?Z_2I=ZzgVdc>H_8KrY0N#pa!4#{-5 zA^fH+E0UBuCX&=YZ*cwy%1m*|lvzboTjq@7f_deHR54qvAh&{%7!5})6EM}orup{ENbzbGL_M4 z<;X9sm|HY!R!Q;k5?GNw#kU%yE1c@LbHol$stzS-$Vwt_5UrQmOSuc~tacs9hKTGy z*(;{q(j0Ld$X0koDlc$qct^CWR zdHAXBijLtZ5m%orFc#4kGIsimrbaK{(uO6Y#9(z*?!JLC453wiBhzS2;TWviw(Z=m zolttc@;p*0_n8`L8y-+{~VUdq8@l84;+1L+*Aan1E&vCzwV^6 zcwq_+J0Gr`3L9r!*vG6n6=>9K$9Yqc={|KD)qCKu8S5T=^&>#32%sOcU$HHN)5;&)T6 zjEhp2`X1n!A-qBB$uD*p8EXyWWlliR9zz1hzI}d^z85^SypP&zNbYY7nnISbBg=R( z|JH{-$~DZ2fyB}lHMc%=NiH!ac%uLKCSL#LNc9i#f-%EL;Mc4+WL!Hj9mMn$rgNEI z!1P9@>g~d>^7-dX|73a?*I@^y1DKx7^fadDF}J`|NSsVt9`ww%^E( z%D2R(rH#6?#Hv4t<^Xz2TxHT3ZP6Sc=OIRm_7LOMp_aJPq%+#wA7?Oyl*H(&JhbuJ zKT90!ue2LD#fDWJya~|K-fM~RGGt3!Zi(@7KueryiSc4UOWfHK<293(c&ABcv_*69 zu0Ts{1KYwWwzXS&i(5OzQ7g%IPBAhl_x4V)VQJ$Xsa8%ZO**43nuC}ASz;K#7R`~b zkgWEh4ZH`?(ylV03vGN=))L#c@oGRzJkqj_cWYW=(-PxtftJ_?7VlfN#CCD;mOx8f zX&J%rZ;MSjqb-_)7YAD6bW1ztI~H~|;9Y{2wp~hGN?78kWd!dKw8WW~m|g~Gi|u@y zPO)tp(JUi2f&-l5+br7yonkxRc}{WEq%+#0IfLowVS9*E{FY@q-zi20Q>tXwNr`Rq$UY=}e+v3qqv2A;-Qyewvj5gOdGLSiJ(Hy*B)v|4x5JEhjj<(n; ze8>Xc_!Kqzo(iv^hoQZU_Z0|H7g!mLXTH-1bLWuFHWlL=PV1ZMdX(d_c z6x&9MoMPL^WT)6L>5R5$&Xyke^5vG|6HKO%lK9@|k4Ug`Vp`%gmXbTQWlOPuxKvObpB)-HT=-Z7TgPV&nC&gg52 z@!n5t(Hy)f$P!nY^q~^5V*#rrwnr(fNwvhQECjL4)Dn;E56iHPm7|t8Y6>Zdu^7}6 z+tq*-pqALK1}yuu#G9-nu};$x+eWak(h}QAVl|~Lwv5Dl113shxyO71#`f=+Z@^Sp z#o=$j$jcVP5`TWv!ZRpIbu(q%jPb<}EgYtoShO{>7g}P}vn4i7r42kutg6~_6~2EY zSBb(^_5|pwKYkhMuUv&bs%O0bOvd=oBM@CK4CsUIfp8Da#seHba z>7`6>XS$K;>r8hu{ex*;zf3iYXW`V zKGKffLEfK<25v<+y@ac5p&k8ETX=<4c1ORv)3WL4cMVHu*!`|;)6p55md&$mO-J;g z2cGF6OK974_T|>yk|FOlQFdGN*(58vBO!UG8LL@-xTRTTmB=wZn3hnC59mJB{Q$A_ ziOMCWx&Xw56>XWzi*8ewvQnasnNd@+M^*jYdgAyYn?KG`v1d z_xhIl)TJW2`9D`>`p%+9PU;u@ib~_E&DWUJ-x>%E)NXb2Rbkc7nSX2I`(fN!FHmpB zak7|(nTkHt&bR0csSj0fTh%4L8AfsVv5&jD_UYfnx;kk#=lf3EV7K^w=M`;Q(%Dh$Wd__mqZo-a17h}o}TIqwA`HkalwI3*}2g^u@ ztR3=;F%%g)$}1tJp2J?H6Fc;i&Og1mtG%2vu#jmc)9y@%FvW}+wse%LYRW))MrEVI z3YqvQJ*4#5*OCqFuk_?y2vWBnGvP6jsz0m#u>t@a*Z+mC`d?Q={r6t^@gaZOi=`CU z|F6`)dU_i~hG}!xxAKp(Ww3Dt)4P~fF@1ySZl=N~q8D42+qR$BxB5QIPcHQElij(} zR5V9UdBS%dvFzbtjP$!?OIFX4*?uQ)HDmp*g{5cTCifs&j4vKV$uTD6iX^ zUSg~ErXJzX(7+7^Hg63q`KUP+Xw;kv)`!?ONV%197hT&Z5qixhem~qdjIM2zOi>D= z4c9h@_WB0-!H$BWuH^iy%9F^SDa429AoFA=ek8N4nA+Di(6sZ7DRPCi@CekEU7Mn= zemS7J5||v4(L~i16^&XTw(?xN%vC1w*x^$||M^W_H>#9dGgH8H8dFtgokc0^Iulja z#HcV9(Y{aBo+@=)ij7@uXx>s4)oMcQ6HQ)4w5r{H$`nNt#r3SxK@tcwiXekhru zuxP_8&kn^A`BA%d`(eAfc_{MaOw-*&n{i+kQ&rvWM>ud}uU1Oes$z58eyA!zb$z9) zivc(BhJ&UyjC2S7U#Oalo{>j5`v2fOE+y6C3vS){6u@BWmphQm|-O~-~s$mc-DF@23&QS_`QV{O|ps8(%o0ZK~#~Yg0mVFvo+f z&5=}L9<`ZC{jOJSV%WwOGBQ0D_29<}eBp}vj-%n2%a5a>3Kc&UdpWXKKJc;{M?Nqn zoJSWIX~;Qk@VlpYQIC=-ihiqg{^~=%6fR78#ExRvV?tprozfOH1v!KVRYfWbdlkdD zn|zKJkzgy8#5jlPHB295x|!)mOx=S8#4&y<*YKdb@eq?JR*dKr!*q$+h+8bR1J%SN zh~^}^#nMQU6e~ZnE^_S_V~u7CJKg^o2fZmQm7)LXwFw5S7zcu(XilaS!@cyT5zq)| z1T+E~0gZr0KqH_L&PPsGX{FE!}Tp8IC{v)(5SZK}vpGJ48 znE&rZIa2TcUmm#p$cu&Cpy^lor4c=vanAogz4^W6e^mhS7^4MgsmUboM{+GiveKBh z27XAykA~trJd906JS=&&@lsz4%pTB&hb14anCF{$8~r*SKN6))(VJEb&$;FPu2}qV zNVh6V9Xkrj&wX-@?=te}6kDz&hwkWpSYkGkc^SeK4{fnEW}3}(6w_Hu&t-Ze(X+A$r-w1Da9OfO@4AJZ*NKV#~{lTg}@WUAhxKbp^HGd+*#TBaMBzRmPU zrghkVj$)d}w2Q}veoC;9vxrav)F=SSo1 znGRxF#B?#!e=~i6>B~%aGY#-GdQ+yonT};z#&iYKo0(QIeV6IaOfz^kwFA?^OeZrv zgXtAaA7r|f=@(3c{B*P#(>_ecF)e3$0n=NUKE?DsroS+)%kzRqGabTo3ezP_uVlK8 z=_^dXWSYP;n3+tEVR{17)0ke!^j4-%Gku@wUZ(Z9p><@M&$O87Ql?iieTeC+Ouu3p z;%ScNO#3n&&vXvcih3-r1%+%|4wm| zZXF*<@u?J_LGe0@zfAGn6#ty!Mv6bAxGmkh>`U=@iqEEa1I1sX_(6()OYuJ_E~S$} zcZ%yMo<;Hb6mO>Z9*Q5Q_+^SeqPQJ>6|5h{6DVFv@#PfXPVtW@ewO05DNfP2e-y=4 z?pX`(XJE+It*g9AO}m~J$gylx)xc>iTQ}lE{ZhQUcJ8{Bq3?dz%??uO*PJWpuA672 zGkkT>b#)hBv(SBo*HUS#pKkG~6qGf|bz}93>z$k6#b3_~Rkf~a_`jw*&)?xwHD*pY z;rn%CgH#!?gfIL1Rp?=(tD50gp+#-|FV4HIZjoQ5Kve~9?5sN{NVPL_%gr~}ofo8P z%uJhhL*0763f-u!-_rfR>#}|omW3WR)_pNZm2uhF8l=)}+#aMtY&+}j2vTVe?+I2R zz26H`X~+)-sdO#>GDwAPWb1w%q^hbPvt?V|AA(f1^}l-Rn!3OERk|vX-nad#oi2yd zrnSlRdN-iiG=RFH?nm$WRX}YcTGsu^ugbznm8BaSTAlEbPo=;LA!qAO4pKFM zs-Z3uteQ6McXh-4stj1{#I0_OU!~x%f~tI~ZeozCw*H>G->jP+q|yz0c95#3e&q{0 z>J|m5vM%$>gH$@h)j=wq;e|me-R5@$s{mW(Cw`T#WgHxS?N?2LTE>vA+ZCwFfG+cl zU)6|*A!O@*>sKMY4X|bF+WaS=mi5SXv|oiDYN7`FRhv8yPxh;@9JH*dKP5<|>D}m8 z?F6ev-0H6U*ykY*u9#^*4y)ugbA8 zSblx2W>V8)eplU?Ry8rR36dZoA{>pc5se30# zrAO?4`&BujoM=ErLP-ocBuhOFpo<_&SLfyC^NTrFoH%O(4 zdLl@r-FPQRwHnT_SJhR-eQu!hSXM(_?;usit(5+Lm8KU@h;@DyW|(!YM)_4atJD1| zw9-xVOutH7VWq4OQfbH^`Bj)=XjS5%2=T~7*X@I%S z@9JXZz6?=?ZZu{Rew6}M7Cp?A`c-IwAzPR7t9EAeIYnhE{HmN)rC*hWvb2}geid3( zfqHXhcSV5nSY9UcFTW}S78tU1@A_4UE0e+PJ--Ue!SWh2d;BVFgWAL0hxyzsfu|S2bk7 z1$X;xD<5QZUITc}uX3Gtt)BO*&~Xgexb6eiioR)sShn?g#CB8Rk@H zwGUPOzi`z*{VJ6~=W%ZHAHOO$q+EM=Kw0{na!Zg(JOAH7szxX;Tlf2L)q6oIo#9tI zgu3xyunNoiMX(Cz8@v1}WTVPu{x!b}*{F3@*&_nn(5iQXR1dmVD>{Z+-5#zwwo|Cp z{BYGb!c|X)tB&a$TGrL!s{6xLjp3?Sx`dXsp(a%IK)7l`*HEh;2B~zVyc?t{8;Z?l za<|Y7FAi6|5w7ZRWN3!p3Rmq4SA7z$y19EmSsL(yFa~RfF48@M8bX zqkUExQ0cM$$H)3qIAT}9wmI`ikP5A8>Ua88jgTRh*O0mRIA4Z4;V`NjGne>Py2IXc z_wvlg1AJDwQFPqEP}Rls*R#D+W#X;9Vy=*=@dM zu}SW@@#-BNovGh8R5>@wzZsxX5VCdcaw@(bhYAV8fG}|27}%yZsK=EPffC@8(cK|- zR7xG?##5bcaCm#=UFG;^pd64F!ZsR&#~|i#(yw2g#Bx~d-pF;n@pQ@iTKdv8Ns?yW zUq#Zc<;RgUeRnUCx{vQq(%6M3k@WrJG9<0uHjE_ouZbj`J8K$AbKaao(wNqlk+gKx z*GO9N%Wskt|LM0$8vMiWko1caenisW+do3mu8N&W9LBrQJXIgLilBICMNo6JMJ^(l_6mLQ=mg zXOMJt>I{;e>pPpI*v6G4mHhZhlK!jv)g*n?lcnRoxrL6c{o2hW-L>TFB#j^T9g>!= zd4Qx<_x*sR1&{3@>D^NvBI)c_J4yOYpGQgh`fpj9e*V*Rtn=h&NIK)Smq}{>@*5;Q z-}PORo*Ko{6Zh|-W4&HW{9Cj8-|v)@^rcIyNE*26c$QupK+>MKP9^E(JtIk)_1Gwq zj#@vFq%XfSiKH*Qc{*#qIE|!dddwi{zMe}+I^&1S`B-u_Nf-Y6Qj#tzzmlXc_qmp& zBeq^g(ww$iNV@J{ES)#*Mmm;E-bT_DPk)W1TZZ3H(#OC50ZD&d{V+*A{`FImV%Pti zq`SVji=>q|u{8BtzoTOlPk4@`FP-)Wl9qkre@Ob^?AJ*8>MvRP{-nRrvD)8$Lefua zTYaGG33WEk<|CW!6fy6b|y(<+Ak#OCmq+2 zG_v#gBpv>%Ye;H)&b1`iRH0DeH zB-cP>Uo}`~R9Y{LmxNan!IkYcHgYM`@(!j5rK+?ab4j}24 z_gT8_)=6~ioa?5MH1_EEB&8QDAnEGM&nD@XQ7cLMfAt$k`u8tcy6?qJbgb>%n@HNQ z{2L_ws{eM9UikNKNZLI1Ig-v=^JkKN^2X~V{pE%CN&4c`ACUC;*2G7ejaL^YNlMkV zBk9uXJCUTm)tjWBe%zO&@f`+`v~7JoN$)QlNz&HUr<1h)FH=cMY?({ax+52oR6c4c zNmCwJOH%KubtK*O&_yI=Zn~MI(S05wDYEsaB+cyq3zFKu`Xou)YJbDW{`@jYm!JG9 zNzYvL4oN>vyiZc{XU=~#8@)e>kaT`Sl%#c+!DDxLs&v+#T1C>50f&+FhqY}<8r!QA zNxx5YA?b)yx{*}9>Nt{q+o=yp=iM@pq~qT`iKJ;ehLAMod&5ZT{^3}X`uu(zNw3Wx zPtvL%Oeg8ZP8X1L(?FJvs{0%r+j9S9B;Eb)m{Dc_eLVdoD?v{(3%118=*Oq^o~^ z8A%g=cQZ-VSKLO@)KT9e>E(&{lN8&*(&6_$LdUN9+b)upfAR;C4x9WxB)!o8PbBTW zzLBKqC+;EX^;I8{)UW-2NILrCk4gH{`Ja&VhySqjM^Adelm6;S|MsL0Jn2JE`pA?1 z<4GTT(kGsj_?SyedQzz;r93I^NoAf??nxD%ROv}oo^+TeReMr9PipT;hkH^7PddVr zI(kwkPwMPRT|BAAle&6RH%~g!le&A-ah}x6lX`nnA5S{olWIMwuP62Mq!T=;zbBpO zNdr7-peGITq?0^puqU1DNkcs86i+(Ulj=Mv<4N_NG}M!3rC*@=)o-C4DAiShFdz&F z1HynXAPfit!hkR!3eURRrel7iC`kC|-=||EJr0-5Qq;E`LlirYCpI(_>l%AEIlpdKLlJ1wjy}W0- zbGmgpl~(EZQg5YRNj;x>I`vrUq15)&ovGVWTT+`+m!#IEmZ#>Wrl-cIhNcFk`lPz2 zI;5&n@zh79@07k#`cmn$rB9abEZtFhZ|NPSH5gTEKSTwOiheUWD)}s zy%OCL?GqJ=XySwT?)Yo*7vs;wpNKyae;|H$ydi#L{F?ZN`1<(D_@el%_@wyA_>g$N zc+Ys}c4h*ibnv5%tfMBj+M6n!@OWOQeANA%w49nqVj*F`r*FN&^?E{V>LPL7U_o*L~R zJvLesZ5u6%ZY_^Q_mupjQbS$YZNtS#Zc{lQAq%rbb2!8lJ1Ivw@2R)cE9!amG~Bs=NNrbls@v2SwMkt9A8cQ)=7rt1m*c{KFdz&F z1HynXAPfit!hkR!3b1L69=tv^;v;lH(zXQJ1Cj)+2%5>5trdf_;foHYKZv(y+hQjJq{AzrC2P^;83 zwNNc?vOgY{O>(oI3-JmyUtI{l%hX!64zi!C zhN=@FyS{*XpsEL{AIL*tuU4H1|Au=-q3eM-x*A1|0{?Yk{-V(1Ur)IG$WV{l8QJ9b zxL)-KPX~afgJ2s0+le3zP$$9GAH3(HLOt$g6m@#2$5YiBAZsBs0L~EM2U5_>$S_xx zxeS^zpVM*Xq3xu%k@-Teo%s39SHuwDe(qNq`-O6kB`BqvVvhT@&{Ec_MbK8}tFwU& zY%Rlp?V)blz&6qs+D?BsHVE3uNwD=*ToiIY&dn8maK{e1sMFOnw_&b^%&}cBgx}Rr z*a|3UCB)}KOXm!c_ehPMi?OFOoDYUGpl!@mQ(WKYf~V%czMwh5Z8NptZ(nFPBi+hP z51)K0l7qRp0e3jmqCj0~6c~(zjx!DXTm&Aj0}sc5wBF5T_|lPeq1p5`vN;>fu_vws ze^x^lYk+6&k>{!N-6I!*4zpkH_EV(BECwm{yfyjlw@=0BLUb4U!Zm|4p=eGpiaD!5 zG$sbj9NiA=PW@Yux#{_i?S1M)JwMUN{LDO_=W59G8?z4e8&4hFQVPr3^!n{*yA2KX zdZ3Z@xp}-^1FdHzw3uaX=b7t{^4Lt*LTfT{U}2$cXwZHr%tY4n-))D5B0I?_?9vt@ z!@&dl${ILz>fyu0ip7W4+28|Gyor8(r}Xep^d}p|ug!ygAxsH0Mm-*^1+?qjdR_@* zv%hxBu6})FXpyHFMNR|L^W356Y{(D`n5&jUQ41)`70{e;qJSqU%run7Q;kf|%$q3= z^EeLk0FPOZxbKBgq5fu!tk24mH8z0N0e)lVp-sKs$o!0gGS~jI*EoI!j>AJ+lyN_o z=}VsZ_2^JKh8r0#4E1=fT1sb>RnRN&T(S_VIairjL}+EYvs^mk;SdtsLYfw#si}er z{o#8*8{_(kKmITh4?y-&7c3?X^<)$!h4cU*%oA*(T=y%W_393htEidZ{^bRse5Q;d z&H$)*_QlO)3`ds5aQ>YOoUViz&&t>WFk`oA;p7bi?r^{o`D|ckjGb3^Xq-dn>ajC^ z^6l@h5B1n>Z&W>Y#<@BU^B6M@_1HZb2WH&7$3q6)c2TIuWua6w?=fZ^>TxVIP$CilMxGbRs2HfFLiUQT^FfhPu*1OFZPaJrz!Yp3jw*Cv~ z$#HCkI~>YUpez=9*OQ~5A+L1%23{M~!rXcg%&GCpaUfim4Ft$|9XSH73{HgkHWw1g zS9_z7dhfa{R|sAUT-tu|>7ia~Z?S(k;16H*&IJ!fstEuTuVQdEw9Gx5u2rK!!ofq2 zk(m8I=S`SWQ1;^}`+CS=wK@%mSf?gKMk_#C>t3}i{LYEf3d(9)KvsBS#y&h1vOLFS zS!epYe|DNtP^Lltai%(}QG?@W6qHqve+(6S#C%}!Tmt>hov)nP;!N2;f2Ln+^LnEKpc6n2&||U7ZFrZbK#;`w^=R%t8Jf)FsrR;5m<=@6)``6DE2p3 ziA`z30&r{(9N*0HgwXcUK9*fG{8o2m``^Fdz&F1HwSpHI;?_ zE3bUA;_`~275^#URz9x$owA$DGG(u)H>S@>e~|iW>eSRlE7&ofu6%--+4FaJ8wyAXg|G2+&{vjOB8wTU$kAg)`cXL!{Z}N>(|G{`+p$mW%e(eBU zSX8Q#Z3n@EJYKo-Vj*5Ju7!ViUlvy?7r?dcB3R`mJFm2ZnbqFmlpoOZCAr->ZbpUgE|zVk(1;R>8vN{=R%24-CBb5muSq{1s&oj)!o zBiAc|vvYxQWRyz5Wjfcswu${Zs1>}!YjKRrp@bbD=FoR^{H#&l*h<~B-5?*lO^$0- z$fj4@c-2@Sd7U<2 zo7X7{Yx6qo-nV&e*Ib*|slwX4);8DX@s^oVUS0Hb=NWi!`dm0`;k@L0edf{Wyf$6~ z^MqyYjDyZhTJP2T5KoWi9=u9!pZ8hodayuEguIrx zXS#KY(t4PWwbJTT=YicSPU+Q?opLSYzY08a&zHJbycxezQ7R8{ z54SFJsaC)|$xCIRwe#}kY`RR3>F&PV@LngC=(V11Fo#$Gb%nDQyt@PQSe=Vkw>7?7 zodfVbr!n)cQMnt8W6)mfJ^K!Db5Gx+p*g#!>1^Fpk@fAa5`+O^Ko}4PgaKhd7!U@8 z0bxKG5C((+VL%uV2L8`8(7sIn+mn7iy(7IPy*xcU-8ubY>XFp8)WxaEsh+8iN_Unv zl&&wGT-v|1qV&1s-N{YK*~wnXw-S#hu1U;G)F$@CAC7N~&xvQ^9pbOVcEq;Emc|Cf z_C%kF-V|LC9TJU3pD5W}vb`SaMAk)yN1~CJoNdl}XS7r0JgaU~{QmzI zxN+#HBj6XqN_c8jsm8O4s370wh16M*xU21mg@SIT_o-=CAobiTLjw{qMUPhJU8MTZT zwYV$|f$$!AYp?dCbc~3x;;!)+*m{! zA*3{N#xldbLaphQoil3;&wM$XUNtS(^vXtrIpZt@bH;}QK&G7Wfh6=d*k{(VXB7Kv z4S$XE0^6ECgT2qz^f`wJZLzFyuTV?gxUroxYYfkfa#3q~)wEpGs~nT;RWOtIm`ga5 ztYyzACa+j)_-io9TGMB+_t~00=MbSSmKDq-YfZ20oLOUdW|WIs)2pWCnqK9YANDvDF(|OwVkf?;!iMoHP1v=gb<+oqYG%G8dgd)(*Koc2VtJ4)$P%*KTHL*kv|3hwYf7P~XHDYiT|KGr8z6?-T8Y;*_Q z>5oKTEqT1;u9B-uR+OApQd?48@^0k0$b*qvA{R&IL^6?XaL4~O=LzR-=Nf0FGs)@a zw07Q8&#Q;v-v1?Pp1%M8sC$VISBUQQDZ~!EMWj@Hm!GIxVF3ZJgBQcL8MZ&e#^H*m z)%`4o#kG(pWD$YSMxF(qJ`DedA63_{tKnBn@f9TfC0_lK_Y`;uYAGDSPk;4QeQ3)S zTCHZoPBna95x*ATF7ITItb$#AcL*>TH;GgfS4Gs*_~V8$_$(sYjA?4^E_f(MN16)6 z<1RY6wn*BM?iF>lzOKflB`#YpJUBYi)^XRGOd--M7rY>0d9D{3937d~?!ttdg}wM- zS>w8aqZ}QX);S04g$B!7-+glECY0L*G;2A}E1^ztnPMUxGH~N!43w=SUADHy_hxlS zsSWNTM<@E|gkjwYQh>FNOzT_O7;gEYwu|aOM-1z3+FD1Zbqd!|+)$?QQ`D8v8ga?S z!hHVmaCU^d{AGELOJ`uMBW0{xQlyk`lQQRr8?5xv#jg2wT?_{N(xb2)xd8% zx3Az)gSBC>3!dx9^c>Ua5L1Uko3DiS-lUfdhZJC~qhRYc>VD`%HENdYgH`qjZLK5I z+M$wh(Cz|5ZeZD?w6%^*YtI-*WY}p3CF@!=aN|NXc&?*h>ll2l7p+fdYCT3<>nPaz z2=y2g-d;UGE4)V8W3{!8w6(79mqDRJ92V}em*BXI!LS|&yWqKwOzVwqO8n8*M+8}) zrmb}p?0HOeheTDXt6noU%bumJb!1vE1ceS!H3Qc4TY+sW_kiK~nQ#C+*O6(xh-`6m zSO(UWPCN;%?s*_dIQ_qhRZl`dM8FYxA|Wj!bLl(iEVz zI9T6pn$|a-xFh=f9RU%NL7&1<;r{9$e73T}aqrf^0i(WghzDyOnbx^8uu_MCbt?zo z>BnGL)cteB)n`qfq}J-Ox(HinErTI6>#39rDn9X zj!fLSGm%o|fV-GRubJUI^tJPXpk&j3S(R& z>yse~l&z!Svh@tRBh0YzjHG)BSx?s1ItsQycSFrgF3UDD-V;Yw(eOJxexf z42KJ_))AHMT0f0hxS_;tE$yML>D#u3b!$ifoa>0JH=q&LpAJ!V9PCsA<4stb?xt{b zq^%>aHQVCZ{}{J(*62gGW7&>wYiI^#>&PtIZ(XTsnwCAoP37n)xa<=542)+JHzbX+ z(G1GgQE=JX+CG0%jp^aW8+EXq`*D}T*%t$zF)$cqqcxPRqu{c&br-OP(>PgE*{8Y( z935$Ef7w+^&q7wXNv*Qc49eC~aM{|r9efg}oojEj7C5h?y^ey*PO0&3BAm-YNE^7( z49eC~aM{|r9sKc!CS^}@^K*0*Ty|1j=brd+Y`{7(%0@FNTSviVYwLN?&U0sA;#?0l zj*f!MPOF=+NOy4uA``cE$F;^JxW#?FL|a#ab(1j_t)YMGh^(`4%m@*MvmSfyK%NhB zlQ}vH_Pn*4=uV)H)j67;qZzQKqhQaqH9u%#jmT&Qp6h6@J+IZC>nPasxN@IDqEXG_ z9L>OU9ck;4KF+l@j)_giwc~Z!Itng3rrba98B{i!LD@PAE?Zky!#Y@#`qm3Lj*i00 zZsVTJ-Dnt2I zLY_W$oSV$ik+$}a$SKvwy;P~uIhvlM8F;RvFwfB%&w^>!-l#D&1J8A|*PcU05F8z8 zYrp65@OF-7;JJ>d>?VB-dkJ1iR=Pz&TApd58F;QEvd+4Ryq_qnXX4fSVaC-v)fi5H z!AM8i+V8pj^qtjG(F{D-5tZ%pTw4cv-d2~bqu{c;sy1$YLn#={`bIM-TSviVYin2w zaM$RqF?Fh&pQEGTvSaEvx76-#Qmbql0it8_jK zmScU>Y!A(#Y#jxct*rykf)jPwItng3=6)VpsUB`p3pbiU**Xd?TU)mQYu%=dmg26NZ17<9cgR3>8l}M_Ox-$ZXvPtV{i6>nUadGfh2L^m!gI)AW~JrEUNRJE#==^H~=>)6{dt z0BgW)&NTgHr{L3sSazam*++o_$kLH!&E_0e=f2Vf6V+fs*bL6m49eD#*80yx+FFn2 zp0(~YcnX5spXRHc5j>=pq}<5;!M0Qh^;PLF8&3mdjl&P-y%*N*rHbR=E1M;jb-Eku zQV1>Eov>(YyX=CjacK-ZH(%yh;X~sY7ydcFWto zQE^%--l{6~QoHY!_oy6D{!HoC(&?q`OaGX>EjcGylYIK{*;S2|D=H=*-lt-G_1F%t z!1Dl05Gi)6i~R`Qsg)uj=5u zsBTyDl)C~8RiY8&0zZa`d;dSK-s^H3;6vqd*fzm-8f+Y1@AJQIH{ku?cuw%)fTQF@ zfF}IVAa`&G*Na9N5C((+VL%uV2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801& zKo}4PgaKhd7&w#}=nCNxy#J3k-5=%q|NK&%1AhTPsfC?xjeL#n|1XC9=V8+ii-Q2+ zqUh{_S_=QIr3VW8IeX|CT6nG`6D@^5V)|qCCspDc?fe@4wTqk_IT4-{>JRVtFM)SQ zmcSz;L8B`O@Nai9sVKpLGJG&h)-}+ zE{2p#K-(XFF&~yNpA)H+lOT^1;2(aEaDbcJK#&%~pQ2p_{}#b_3C;)WB`O0?>Mw&o zrMnouK%oD4>o7F~-hZE{hQkN;rokIHBO%Rr_>RDIH3HHM1I+~ZZ9YxCI$hP%zY%Uc z3`#JM4TGaoA?ImoEO=879L)#M&W0}{i~ye|!XL9e-uc*8sMO;7cu_?hAPhgp@NO)dlcx5Y)$H&<%$pDZk57D zDnS?!2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801&Ko}4PgaKhd7!U@80b!s3 z2Ihsn|Bv_o@%{hXjrafYDFOUk1&7!BRLlGSOoqh5fG{8o2m``^Fdz&F1HynXAPfit z!hkR!3f3`3wAxx(p8Y z?DCxYI^F-r_y6C3P5;Rj5Fq?aZFyQt;lBgGJ_4`8Ur^9%@cZi%0P^a10S|vj5@A3X z5C((+VL%uV2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801&Ko}4Pgn>hbfq9|t z|Kt6CeE+|}_x^v4ajXCJKIh2${{|l-3IoD`Fdz&F1HynXAPfit!hkR!3f z5C((+VL%uV2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801&Ko}4PgaKjTP-g(2 zS#aO~$D8gEM_mJvqmEEp+&HTSLflX}ukxF8|9=_mZ-TTPA?C31cMacUIV`S%JdXt> zWCQstRg#mFO0gD?vX*4h;sXY#VJ*pqHazEEd2LyX_UskNlrw70oNcdI%eGOS+GmS4 zWQVb#O@oF}p)CYMTL!zV#S$Qpwl|2Z;hbULr)94&hK;m+^4g*n(_`+KGs%W6mxnRQ zJ}ugy#%2R^#v`0_Ov_|@JZV^qF>F39=ZrCIq^-?s%i3VuzIkm~o8Ok1pqRX3Tg1$4 zfNW^PYaeaR+DCf`hHW|Q5=U+w<+S|_4cggEq88EMQPh%5IkT4WQVK)MIbuw*Ps_F# z!)9p1@nX4$!N!OX9R$O+8g^NW%s?=-tzeh630G-qTN~PV6Kxwqi)|7D<$MB&oGaso zeV;ZMFWWLt6dSf^hqJ zA&@PeHMvZ#BiQ$8nGK9{Y_Kib8Jl5?=`r`KoL6ki9#K6Twr2feDhP(w ztY0i8r#%_Oa73&{8_bnFq?}pHUQz7Ra+|~$HlG&TBHCdLn@@`^2DKQ&W@ydcZMQ4* z%Iw{CYaasIaJ;My#>?7ZysQnzi(0dentEl{5#|Ab@S?5R2eEtzhSuzZcKx0T3B&QS zHW)8!gYnko&6%~qIkPr6=S<$5SsR=)YEgrjId_nWE7r0trUKc}nssEiE3`H12=jno z*qU`@*KYc1~piI&gbD|$F>;5Mz*YFTa269Vy?J>5yoUY0`@33&%0dCsKwkc1>5Je zec`BCN4XkQs;dNHKo}4PgaKhd7!U@80bxKG5C((+VL%u-$P9F^j90!<@nprl71vd) zu9#fWzoKo$p7KAIKU{u$dA58(`S9`{<;n6l%bqH`uWWPKnzE^71IpT$eUN@J{YbhY zy&=6QJu=-hol3uzdOEc|wI#JKH9a*b)gkp!=}Vvd4+tej$9;{nP5C((+VL%uV z2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801&Ko}4PgaKhd7!U@80bxKG5C((+ zVL%uV2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801&Ko}4PgaKhd7!U@80bxKG z5C((+VL%uV2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801&Ko}4PgaKhd7!U@8 z0bxKG5C((+VL%uV2801&Ko}4PgaKhd7!U@80bxKG5C((+VL%uV2801&Ko}4PgaKhd z7!U@8feEJ%uU9H=5m8yC)Ral}!|~uVB{|j3b~#xjVL%x8>@zT7%GB|QVrrzqVKigf zu%zRtwOdiDcOiLI7cK76aRm1EIJ3Rf=eQDEcXIb4DywFJvOZuA3L0TR7!U@80bxKG5C)2vfeEMUd45EB(|pwd=46}Iqw3vTR0CY* zSK)j;32IEy?&Es-6JzOsY~m`a@G3#6H2jOgzck37 z?5=~~s>V;=?rhoI>UuH&llwx#D$n%0o9hK8-{rHx-RcM|ZMeB6AYKmJZtzok1UqhM z>NOs&&`Nb8#7WpN9}K-=!~Mf>2f`_^;r{*)!FZ~nAgnLEl43lF0FUVqjOPxzEX#7T zPdzTYUa@|TY^GlE3KTITTI69h?3@R2u5N>QV4g0fN^1=^2d4fYJHuNsfz;^Fp< zeXKb=tS=N#HqfViIhYSm#KMDpko^rBEzk?DwLW4l!w`li z>uL|=Prm+>u<(?D8_nV2u{jLSfkdM7&W9&y;VCZw4=<5zI7s~`8!}JQ{ioEzgL9zf z>IeH6hT^QBl!d1f{A>;n_Av~_i6?F0sRBQn!-IVc1LMhp%zX-vANY{3|CCvHa8BJE z9_(Wn7*Dax->@$5dI-l~p1<*22L07Db}gB|jF+!7c)q~b8N9#0t~0V`;jSE9Kl(bu zaj!EN5BkFM=P*3qr~Gj|IXEF0j~?GDtoDW&ng1|6jCUCc#nRq1o=OW3-o|SVPnIg> zFGY!`%EHsO06g5siWARa79M<@qPhCv{(hkFaQ#y=2#E6apK1$F`vUOr^}>O|69B+> zC?B3y7M{Zkz{A%IYp5cNYdmOe;pq^7C+lN}uNR85e%e@gjtIcxgX7I@{#umdTU!fH z#{%&1^};>-2~XBfShM^2`cFFx58m=`ZhYhGg~t7ar&;%#B*=%Sy@jW90eJX&p*Zmz zZsEZ>L)PPiM`rJUEAaI~apLJ<;i&`;y-=KZjrFgXUzWnCH)5*efBoxyeo+@%>e~s6Bk1QXa&Tjwb>$&a);NkmR z#W_FdV%5)41>nJJJ`CS4vhxqN8wXmKxqi^f^^|{jrpEg^=Ib9`Kj2ZF*AIA~*AKF` z`F_|7Zf|=1AY!c_;9Qw#2PqiOljKoxt{>D`?X3s+ni~&-@i5+PMbh4~F28)gq^pJJ zm;&%H-t9$#$A#nF|LJDoIko^i+<%G_&yg0M;|jpT>jz=&EvpGTfWN`xfnKlbZsF-w z03IH{?k~#rc9ew&$6W3oVdERGAG~ys@Z`V$)5F5krvN-W>K!aRVbA^O_I9*|=lBBf z@V$}Z#M9HlQ(FKYUO#w?+FKTE7$b3*I!>e|Y_%IP2$F3s1iS@bLNp z*Uy1oPs}!{@xq_W!m+p(W^lSG1=s5aNaLu(y?N{#us_)wu6-wY2)XvmH#_vXLI!%Enw8-f(fLxMW&ftvqjq2&A_xL&Kk z2=U-}&)1JfTN&ZqfE+qj@e!_9pv4Vbue{IKCv3O>;$S@b`XpCB_#A9cD3twGDi{y@ zd!Xv4dEAA!Q%rdxAkvsL4_T{#>c~B???4a59=RB{d60N2#EdhH2j+?AGY5Ly0+)Ot zxL!4$Db_fAGFUc;hxNsYXR3u~NC9|wgyr>IuAi)r#RIpSuYRUkc(Cs?|6%QouWNWc zit!vc|NS}V!!zB&gHOyihlj6gK5aZ%4^}=rGb}uH1>oU+^J(Mp;DkKarpJSs7M@H2 zc=);opQ*r5TBD{oN3`1RsbHp zuJMo81aH4Z+0dNwjR&(WJi{T6=ISR+ z;C@q_c;;GoM&-dnaJ=`miWAQ~3(x2R@bG<6?nnEpy=9rOX5)P0!F&tPm;&(dIKcCS z{e`Dl7+!*WcotZA#uk8w=Q+iRXQ72>oPj4Ba$Sqp!x)Ma&ms%Yc<{5i^AF!UEza>^ zv4v+s0eG;#VA$9C$-23;{^OFcUw-oSpH&u~83o|MXO%HDhbQZ%IuJkk@T|7*%!J~bs~^6< zQf%$53Ve4|wRInCkVR)VH-&Wwc8;P0d>@SOo$-CJV@Q9X?}KHj9353h^nI{u?>;4- zPfr3RL==MY9O(O$TzYt1__5FMJo5{{!{^_Y)=vv7#W2fPKfT@le-5O^ zaehGoc=$e8apLJ?)z88L@bL9tapF1N!n3FVJk)K}T?O`^EabU=L%#M_YvEZ81vfVy z@O-a0>!+`UXGsBgc>R6`0Xx|9gREQm)e${E=x5|s*N=-$M25k z!Z9E=ogeUh7M_>$x<1d#dH+Dq%X5CB#Pjkt*1Y^o3qdfRC+NW+zJ9XvHRpeR&HmcY zHy+em?QJO(nHvv+@o!^8T6g(vL!e?1-yv+%4a01rQB zcChe-J^!!q47c!{TL2!OU&o8Gy^XN&;Pb@Xo|FpiKRmx?JXx^eIS`1SLdZ8BjI{8q z0&kkb!}Dv#Q(WW0C=1W(0`Tzs`qRRbZBpa;>Swft=ez>&aNGK{@HBxF@*JQZ55`z{ z))auJlnN+LJYy|9YYV`WEowaDEIjK9z{B(F{zW-nkGJrgUjQDSUmvLU#`RClARx*& z9!#+CTu=a>aw_3K;RyiXJCqO4X%?Q(6@aILaxX2)_BPSNvpxV%mi*_4uYZbj{x-?N zb724;#=&tF32lq4e$X#Rb+WEcm$B1s+}BNE9Wsva^(oJf`1+Lh3%x$g`U_}wKe+$s z`$nCt>r=dM!q;m`1>@oQLD=a_G-{ZLifUv(|^~2Yv#feA%{R>`?zq9~6e0|zdJXu}&`|z8u z|JdK-!LpjGAHF_qDV}`*LZ-oZszB?g8f(1%yj2+9e`c?93cfz&@tVgw9f&9z9;yxc#5o+r<`wU_3nuVsVbwCt34@%K%TVy#?dp8heI>;u^09TX;4WfQRuG ziYE(s?%xpH-t_tBWDCz1pD+eonFQTnU=y>W9bcXN$7`oND3OQ~(|xuZy$2)meD1DgY0!Cx+q4LLLV;0> z^4E`jKkHiXv$^)h>xst{8J;Te-BDew`RE2D-U|ak;@ORNQ1SVP=c8Ft@qCoWzk@R$ z?P{$bTy7x<#`7jYEYA6;eZTQKz>{ll!FagFzVIovH?Dtb1`y>N-|YL1Unu|&WAl}#fith-*`g-cz8TM zPMe!%+&XMWJbnjdTizxaHql*Z#aYy1o> z()qy#*Z1yL{UGOjehI_F^J}h;!w73j>my6{g-5~dP2WAxf4^-xWVRc4M_>8;ABKnP z??Ms|_Iilsp*o`Rlv~%Qw-tbg@fIf@`|q874e(?&VE7I0KinRR6OaA(&Ta=3%zqdj zzWymrJoeu^`+5O*c)l}}fbVVp$wC?X7V?b;_TM{efP$K3%RCegCJg+y8lfaEsxuQo(p$q5_KT zdb^$LyS{&l=O4UZ|1*f0-!MFU{)raZ`A6fi@1Jff01x9W6i*iN+`l2Xy=go(*7@{X zP%!%&RzH0HDHPBC#e%uz!(;!wj5`a!!{?u3!IMRZaQ{cwkNx*Dz73k@>W9xi#e&C$ z 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 +Visual Studio 2015 is recommended for code contribution. + +### Steps to create a pull request +These are the recommended steps to create a pull request:
+ +1. Create a forked repository of [https://github.com/OData/RESTier.git](https://github.com/OData/RESTier.git) +2. Clone the forked repository into your local environment +3. Add a git remote to upstream for local repository with command _git remote add upstream +[https://github.com/OData/RESTier.git](https://github.com/OData/RESTier.git)_ +4. Make code changes and add test cases, refer Test specification section for more details about test +5. Test the changed codes with one-click build and test script +6. Commit changed code to local repository with clear message +7. Rebase the code to upstream via command _git pull --rebase upstream master_ and resolve conflicts +if there is any then continue rebase via command _git pull --rebase continue_ +8. Push local commit to the forked repository +9. Create pull request from forked repository Web console via comparing with upstream. +10. Complete a Contributor License Agreement (CLA), refer below section for more details. +11. Pull request will be reviewed by Microsoft OData team +12. Address comments and revise code if necessary +13. Commit the changes to local repository or amend existing commit via command _git commit --amend_ +14. Rebase the code with upstream again via command _git pull --rebase upstream master_ and resolve +conflicts if there is any then continue rebase via command _git pull --rebase continue_ +15. Test the changed codes with one-click build and test script again +16. Push changes to the forked repository and use _--force_ option if existing commit is amended +17. Microsoft OData team will merge the pull request into upstream + +### Test specification +All tests need to be written with xUnit. Here are some rules to follow when you are organizing the +test code: + +- **Project name correspondence** (`X -> X.Tests`). For instance, all the test code of the `Microsoft.Restier.Core` project should be placed in the `Microsoft.Restier.Core.Tests` project. Path and file name correspondence. (`X/Y/Z/A.cs -> X.Tests/Y/Z/ATests.cs`). For example, the test code of the `ConventionBasedApiModelBuilder` class (in the `Microsoft.Restier.Core/Convention/ConventionBasedApiModelBuilder.cs` file) should be placed in the `Microsoft.Restier.Core.Tests/Convention/ConventionBasedApiModelBuilderTests.cs` file. +- **Namespace correspondence** (`X.Tests/Y/Z -> X.Tests.Y.Z`). The namespace of the file should strictly follow the path. For example, the namespace of the `ConventionBasedApiModelBuilderTests.cs` file should be `Microsoft.Restier.Core.Tests.Convention`. +- **Utility classes**. The file for a utility class can be placed at the same level of its user or a shared level that is visible to all its users. But the file name must **NOT** be ended with `Tests` to avoid any confusion. +- **Integration and scenario tests**. Those tests usually involve multiple modules and have some specific scenarios. They should be placed separately in `X.Tests/IntegrationTests` and `X.Tests/ScenarioTests`. There is no hard requirement of the folder structure for those tests. But they should be organized logically and systematically as possible. + +### Complete a Contribution License Agreement (CLA) +You will need to complete a Contributor License Agreement (CLA). Briefly, this agreement testifies +that you are granting us permission to use the submitted change according to the terms of the +project's license, and that the work being submitted is under appropriate copyright. + +Please submit a Contributor License Agreement (CLA) before submitting a pull request. +[Download the agreement](https://github.com/odata/odatacpp/wiki/files/Microsoft Contribution License Agreement.pdf)), +sign, scan, and email it back to [cla@microsoft.com](mailto:cla@microsoft.com). Be sure to include your Github +user name along with the agreement. Only after we have received the signed CLA, we'll review the pull request that +you send. You only need to do this once for contributing to any Microsoft open source projects. \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/docfx.json b/ApiAsAService/RESTier/docs/msdocs/docfx.json new file mode 100644 index 0000000..b142a67 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/docfx.json @@ -0,0 +1,70 @@ +{ + "build": { + "content": [ + { + "files": [ + "**/*.md", + "**/*.yml" + ], + "exclude": [ + "**/obj/**", + "**/includes/**", + "README.md", + "LICENSE", + "LICENSE-CODE", + "ThirdPartyNotices" + ] + } + ], + "resource": [ + { + "files": [ + "**/*.png", + "**/*.jpg", + "**/*.gif", + "**/*.svg", + "**/includes/media/**" + ], + "exclude": [ + "**/obj/**", + "**/includes/*.md" + ] + } + ], + "overwrite": [], + "externalReference": [], + "globalMetadata": { + "uhfHeaderId": "MSDocsHeader-MSPowerApps", + "contributors_to_exclude": [ + "openpublishingbuild", + "sudeepku", + "v-thepet", + "PRMerger10" + ], + "breadcrumb_path": "breadcrumb/toc.yml", + "extendBreadcrumb": true, + "searchScope": [ + "PowerApps" + ], + "titleSuffix": "PowerApps", + "feedback_system": "GitHub", + "feedback_github_repo": "MicrosoftDocs/powerapps-docs", + "feedback_product_url": "https://ideas.powerapps.com", + "search.appverid": "met150" + }, + "fileMetadata": { + "bilingual_type": { + "**/*": "hover over", + "maker/common-data-service/**/*": "", + "maker/model-driven-apps/**/*": "", + "maker/TOC.yml": "", + "maker/dev-community-plan.md": "", + "maker/index.md": "", + "maker/signup-for-powerapps.md": "" + } + }, + "template": [], + "dest": "powerapps-docs", + "markdownEngineName": "markdig" + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/extending-restier/additional-operations.md b/ApiAsAService/RESTier/docs/msdocs/extending-restier/additional-operations.md new file mode 100644 index 0000000..413f74d --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/extending-restier/additional-operations.md @@ -0,0 +1,69 @@ +## Additional WebAPI Operations + +RESTier is built on top of ASP.NET Web API, so like our regular OData support, augmenting your service +with additional actions is very simple. + +First, you must add the action to the EDM Model Builder. + +Currently RESTier can not route an operation request to a method defined in API class for operation model +building, user need to define its own controller with ODataRoute attribute for operation route. + +Operation includes function (bounded), function import (unbounded), action (bounded), and action(unbounded). + +For function and action, the ODataRoute attribute must include namespace information. There is a way to simplify +the URL to omit the namespace, user can enable this via call "config.EnableUnqualifiedNameCall(true);" during registering. + +For function import and action import, the ODataRoute attribute must NOT include namespace information. + +RESTier also supports operation request in batch request, as long as user defines its own controller for operation route. + +This is an example on how to define customized controller with ODataRoute attribute for operation. + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Web.Http; +using System.Web.OData; +using System.Web.OData.Extensions; +using System.Web.OData.Routing; +using Microsoft.OData.Edm.Library; +using Microsoft.OData.Service.Sample.Trippin.Api; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Controllers +{ + public class TrippinController : ODataController + { + private TrippinApi Api + { + get + { + if (api == null) + { + api = new TrippinApi(); + } + + return api; + } + } + ... + // Unbounded action does not need namespace in route attribute + [ODataRoute("ResetDataSource")] + public IHttpActionResult ResetDataSource() + { + // reset the data source; + return StatusCode(HttpStatusCode.NoContent); + } + + [ODataRoute("Trips({key})/Microsoft.OData.Service.Sample.Trippin.Models.EndTrip")] + public IHttpActionResult EndTrip(int key) + { + var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == key); + return Ok(Api.EndTrip(trip)); + } + ... + } +} +``` \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/extending-restier/in-memory-provider.md b/ApiAsAService/RESTier/docs/msdocs/extending-restier/in-memory-provider.md new file mode 100644 index 0000000..aab6488 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/extending-restier/in-memory-provider.md @@ -0,0 +1,83 @@ +## In-Memory Data Provider + +RESTier supports building an OData service with **all-in-memory** resources. However currently RESTier +has not provided a dedicated in-memory provider module so users have to write some service code to bootstrap +the initial model with EDM types themselves. There is a sample service with in-memory provider [here](https://github.com/OData/RESTier/tree/apidev/test/ODataEndToEndTests/Microsoft.OData.Service.Sample.TrippinInMemory). +This subsection mainly talks about how such a service is created. + +First please create an **Empty ASP.NET Web API** project following the instructions in [Section 1.2](http://odata.github.io/RESTier/#01-02-Bootstrap). Stop **BEFORE** the **Generate the model classes** part. + +### Create the Api class +Create a simple data type `Person` with some properties and "fabricate" some fake data. Then add the first entity set `People` to the `Api` class: + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System.Web.OData.Builder; + using Microsoft.OData.Edm; + using Microsoft.Restier.Core; + using Microsoft.Restier.Core.Model; + + namespace Microsoft.OData.Service.Sample.TrippinInMemory + { + public class TrippinApi : ApiBase + { + private static readonly List people = new List + { + ... + }; + + public IQueryable People + { + get { return people.AsQueryable(); } + } + } + } + +### Create an initial model +Since the RESTier convention will not produce any EDM type, an initial model with at least the `Person` type needs to be created by service. Here the `ODataConventionModelBuilder` from OData Web API is used for quick model building. +Any model building methods supported by Web API OData can be used here, refer to **[Web API OData Model builder ](http://odata.github.io/WebApi/#02-01-model-builder-abstract)**document for more information. + + namespace Microsoft.OData.Service.Sample.TrippinInMemory + { + public class TrippinApi : ApiBase + { + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + services.AddService(new ModelBuilder()); + return base.ConfigureApi(services); + } + + private class ModelBuilder : IModelBuilder + { + public Task GetModelAsync(InvocationContext context, CancellationToken cancellationToken) + { + var builder = new ODataConventionModelBuilder(); + builder.EntityType(); + return Task.FromResult(builder.GetEdmModel()); + } + } + } + } + +### Configure the OData endpoint +Replace the `WebApiConfig` class with the following code. No need to create a custom controller if users don't have attribute routing. + + using System.Web.Http; + using Microsoft.Restier.Publisher.OData.Batch; + + namespace Microsoft.OData.Service.Sample.TrippinInMemory + { + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + config.MapRestierRoute( + "TrippinApi", + "api/Trippin", + new RestierBatchHandler(GlobalConfiguration.DefaultServer)).Wait(); + } + } + } diff --git a/ApiAsAService/RESTier/docs/msdocs/extending-restier/temporal-types.md b/ApiAsAService/RESTier/docs/msdocs/extending-restier/temporal-types.md new file mode 100644 index 0000000..f268a39 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/extending-restier/temporal-types.md @@ -0,0 +1,77 @@ +# Temporal Types + +When using the Microsoft.Restier.Providers.EntityFramework provider, temporal types are now supported. The table below +shows how Temporal Types map to SQL Types: + +| EF Type | SQL Type | Edm Type | Need ColumnAttribute? | +|:---------------------:|:------------------:|:------------------:|:---------------------:| +| System.DateTime | DateTime/DateTime2 | Edm.DateTimeOffset | Y | +| System.DateTimeOffset | DateTimeOffset | Edm.DateTimeOffset | N | +| System.DateTime | Date | Edm.Date | Y | +| System.TimeSpan | Time | Edm.TimeOfDay | Y | +| System.TimeSpan | Time | Edm.Duration | N | + +The next sections illustrate how to use use temporal types in various scenarios. + +## Edm.DateTimeOffset +Suppose you have an entity class `Person`, all the following code define `Edm.DateTimeOffset` properties in the +EDM model though the underlying SQL types are different (see the value of the `TypeName` property). You can see +Column attribute is optional here. + + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + public DateTime BirthDateTime1 { get; set; } + + [Column(TypeName = "DateTime")] + public DateTime BirthDateTime2 { get; set; } + + [Column(TypeName = "DateTime2")] + public DateTime BirthDateTime3 { get; set; } + + public DateTimeOffset BirthDateTime4 { get; set; } + } + + +## Edm.Date +The following code define an `Edm.Date` property in the EDM model. + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + [Column(TypeName = "Date")] + public DateTime BirthDate { get; set; } + } + +## Edm.Duration +The following code define an `Edm.Duration` property in the EDM model. + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + public TimeSpan WorkingHours { get; set; } + } + +## Edm.TimeOfDay +The following code define an `Edm.TimeOfDay` property in the EDM model. Please note that you MUST NOT omit the +`ColumnTypeAttribute` on a `TimeSpan` property otherwise it will be recognized as an `Edm.Duration` as described above. + + using System; + using System.ComponentModel.DataAnnotations.Schema; + + public class Person + { + [Column(TypeName = "Time")] + public TimeSpan BirthTime { get; set; } + } + +As before, if you have the need to override `ODataPayloadValueConverter`, please now change to override +`RestierPayloadValueConverter` instead in order not to break the payload value conversion specialized for these +temporal types. \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/getting-started.md b/ApiAsAService/RESTier/docs/msdocs/getting-started.md new file mode 100644 index 0000000..c629fb2 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/getting-started.md @@ -0,0 +1 @@ +[THIS IS A PLACEHOLDER FOR FUTURE CONTENT] \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/index.md b/ApiAsAService/RESTier/docs/msdocs/index.md new file mode 100644 index 0000000..2121e24 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/index.md @@ -0,0 +1,113 @@ +

+ +## What is Restier? + +Restier is an API development framework for building standardized, OData V4 based RESTful services on .NET. + +Restier is the spiritual successor to [WCF Data Services](https://en.wikipedia.org/wiki/WCF_Data_Services). Instead of +generating endless boilerplate code with the current Web API + OData toolchain, RESTier helps you boostrap a standardized, +queryable HTTP-based REST interface in literally minutes. And that's just the beginning. + +Like WCF Data Services before it, Restier provides simple and straightforward ways to shape queries and intercept submissions +_before_ and _after_ they hit the database. And like Web API + OData, you still have the flexibility to add your own +custom queries and actions with techniques you're already familiar with. + +## What is OData? + +OData stands for the Open Data Protocol. OData enables the creation and consumption of RESTful APIs, which allow +resources, defined in a data model and identified by using URLs, to be published and edited by Web clients using +simple HTTP requests. + +OData was originally designed by Microsoft to be a framework for exposing Entity Framework objects over REST services. +The first concepts shipped as "Project Astoria" in 2007. By 2009, the concept had evolved enough for Microsoft to +announce OData, along with a [larger effort](https://blogs.msdn.microsoft.com/odatateam/2009/11/17/breaking-down-data-silos-the-open-data-protocol-odata/) +to push the format as an industry standard. + +Work on the current version of the protocol (V4) began in April 2012, and was ratified by OASIS as an industry standard in Feb 2014. + +## Getting Started +Now that the project has restarted, we have a new location for our [Continuous Integration builds][nightly-feed]. We've simplified the NuGet +packages as well, so now you can just reference `Microsoft.Restier.AspNet` or `Microsoft.Restier.AspNetCore` (coming soon) packages, and we'll take care of +the rest. + +## Use Cases +Coming Soon! + +## Supported Platforms +Restier 1.0 currently ships with support for Classic ASP.NET 5.2.3 and later. Support for ASP.NET Core 2.2 is coming in the first half of 2019. (More specifics will be provided in a few weeks.) + +## Restier Components +The Classic ASP.NET flavor of Restier is made up of the following components: +- **Microsoft.Restier.AspNet:** Plugs into the OData/WebApi processing pipeline and provides query interception capabilities. +- **Microsoft.Restier.Core:** The base library that contains the core convention-based interception framework. +- **Microsoft.Restier.EntityFramework:** Translates intercepted queries down to the database level to be executed. + +While the ASP.NET Core flavor of Restier (when is ships) will consist of the following: +- **Microsoft.Restier.AspNetCore:** Plugs into the OData/WebApi processing pipeline and provides query interception capabilities. +- **Microsoft.Restier.Core:** The base library that contains the core convention-based interception framework. +- **Microsoft.Restier.EntityFrameworkCore:** Translates intercepted queries down to the database level to be executed. + +## Ecosystem +Restier is used in solutions from: +- [BurnRate.io](https://burnrate.io) +- [CloudNimble, Inc.](https://nimbleapps.cloud) +- [Florida Agency for Health Care Administration](https://ahca.myflorida.com) + +There is also a growing set of tools to support Restier-based development +- [Breakdance.Restier](https://github.com/cloudnimble/breakdance): Convention-based name troubleshooting and integration test support. +## Community +After a couple years in statis, Restier is in active development once again. The project is lead by Robert McLaws and Chris Woodruff. + +### Weekly Standups +The core development team meets once a week on Google Hangouts to discuss pressing items and work through the issues list. A history of +those meetings can be found in the Wiki. + +### Contributing +If you'd like to help out with the project, our Contributor's Handbook is also located in the Wiki. + +## Contributors + +Special thanks to everyone involved in making RESTier the best API development platform for .NET. The following people +have made various contributions to the codebase: + +| Microsoft | External | +|---------------|----------------| +| Lewis Cheng | Cengiz Ilerler | +| Challenh | Kemal M | +| Eric Erhardt | Robert McLaws | +| Vincent He | | +| Dong Liu | | +| Layla Liu | | +| Fan Ouyang | | +| Congyong S | | +| Mark Stafford | | +| Ray Yao | | + +## + + + +[devops-build]:https://dev.azure.com/cloudnimble/Restier/_build?definitionId=8 +[devops-release]:https://dev.azure.com/cloudnimble/Restier/_release?view=all&definitionId=1 +[nightly-feed]:https://www.myget.org/F/restier-nightly/api/v3/index.json +[twitter-intent]:https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2FOData%2FRESTier&via=robertmclaws&text=Check%20out%20Restier%21%20It%27s%20the%20simple%2C%20queryable%20framework%20for%20building%20data-driven%20APIs%20in%20.NET%21&hashtags=odata +[code-of-conduct]:https://opensource.microsoft.com/codeofconduct/ + +[devops-build-img]:https://img.shields.io/azure-devops/build/cloudnimble/restier/8.svg?style=for-the-badge&logo=azuredevops +[devops-release-img]:https://img.shields.io/azure-devops/release/cloudnimble/d3aaa016-9aea-4903-b6a6-abda1d4c84f0/1/1.svg?style=for-the-badge&logo=azuredevops +[nightly-feed-img]:https://img.shields.io/badge/continuous%20integration-feed-0495dc.svg?style=for-the-badge&logo=nuget&logoColor=fff +[github-version-img]:https://img.shields.io/github/release/ryanoasis/nerd-fonts.svg?style=for-the-badge +[gitter-img]:https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=for-the-badge +[code-climate-img]:https://img.shields.io/codeclimate/issues/github/ryanoasis/nerd-fonts.svg?style=for-the-badge +[code-of-conduct-img]: https://img.shields.io/badge/code%20of-conduct-00a1f1.svg?style=for-the-badge&logo=windows +[twitter-img]:https://img.shields.io/badge/share-on%20twitter-55acee.svg?style=for-the-badge&logo=twitter \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/license.md b/ApiAsAService/RESTier/docs/msdocs/license.md new file mode 100644 index 0000000..c629fb2 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/license.md @@ -0,0 +1 @@ +[THIS IS A PLACEHOLDER FOR FUTURE CONTENT] \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta1.md b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta1.md new file mode 100644 index 0000000..14512b6 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta1.md @@ -0,0 +1,20 @@ +## Downloads + + - NuGet: `Install-Package Microsoft.Restier -Version 0.3.0-beta1 -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.3.0-beta1)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.3.0-beta1.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.3.0-beta1.tar.gz)] + +## New Features + + - Complex type support [#96](https://github.com/OData/RESTier/issues/96) + +## Enhancements + + - Northwind service uses script to generate database instead of .mdf/.ldf files. [#77](https://github.com/OData/RESTier/issues/77) + - Add StyleCop and FxCop to build process to ensure code quality. + - TripPin service supports singleton. + - Visual Studio 2015 and MSSQLLocalDB. + - Use xUnit 2.0 as the test framework for RESTier. [#104](https://github.com/OData/RESTier/issues/104) + +## Bug Fixes + + - None in this release. \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta2.md b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta2.md new file mode 100644 index 0000000..96fc313 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-3-0-beta2.md @@ -0,0 +1,19 @@ +## Downloads + + - NuGet: `Install-Package Microsoft.Restier -Version 0.3.0-beta2 -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.3.0-beta2)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.3.0-beta2.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.3.0-beta2.tar.gz)] + +## New Features + + - [[Issue](https://github.com/OData/RESTier/issues/126)] [[PR](https://github.com/OData/RESTier/pull/159)] Support concrete classes that implement IDbSet>T< by [mkemal](https://github.com/mkemal) + - [[Issue](https://github.com/OData/RESTier/issues/138)] [[PR](https://github.com/OData/RESTier/pull/194)] Support Edm.Date [Tutorial](http://odata.github.io/RESTier/#03-04-Date) + +## Enhancements + + - Automatically start TripPin service when running E2E cases [#146](https://github.com/OData/RESTier/issues/146) + - No need to change machine configuration for running tests under Release mode + +## Bug Fixes + + - Fix incorrect status code [#115](https://github.com/OData/RESTier/issues/115) + - Computed annotation should not be added for Identity property [#116](https://github.com/OData/RESTier/issues/116) \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc.md b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc.md new file mode 100644 index 0000000..1f7afaa --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc.md @@ -0,0 +1,26 @@ +## Downloads + + - NuGet: `Install-Package Microsoft.Restier -Version 0.4.0-rc -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.4.0-rc)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.4.0-rc.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.4.0-rc.tar.gz)] + +## New Features + + - Unified hook handler mechanism for users to inject hooks, [Tutorial](http://odata.github.io/RESTier/#04-04-Hook-Handler) + - Built-in `RestierController` now handles most CRUD scenarios for users including entity set access, singleton access, entity access, property access with $count/$value, $count query option support. [#136](https://github.com/OData/RESTier/issues/136), [#193](https://github.com/OData/RESTier/issues/193), [#234](https://github.com/OData/RESTier/issues/234), [Tutorial](http://odata.github.io/RESTier/#03-05-Controllers) + - Support building entity set, singleton and operation from `Api` (previously `Domain`). Support navigation property binding. Now users can save much time writing code to build model. [#207](https://github.com/OData/RESTier/issues/207), [Tutorial](http://odata.github.io/RESTier/#02-06-Model-building) + - Support in-memory data source provider [#189](https://github.com/OData/RESTier/issues/189) + +## Enhancements + + - Thorough API cleanup, code refactor and concept reduction [#164](https://github.com/OData/RESTier/issues/164) + - The Conventions project was merged into the Core project. Conventions are now enabled by default. The `OnModelExtending` convention was removed due to inconsistency. [#191](https://github.com/OData/RESTier/issues/191) + - Add a sample service with an in-memory provider [#189](https://github.com/OData/RESTier/issues/189) + - Unified exception-handling process [#24](https://github.com/OData/RESTier/issues/24), [#26](https://github.com/OData/RESTier/issues/26) + - Simplified `MapRestierRoute` now takes an `Api` class instead of a controller class. No custom controller required in simple cases. + - Update project URL in RESTier NuGet packages. + +## Bug Fixes + + - Fix IISExpress instance startup issue in E2E tests [#145](https://github.com/OData/RESTier/issues/145), [#241](https://github.com/OData/RESTier/issues/241) + - Should return 400 if there is any invalid query option [#176](https://github.com/OData/RESTier/issues/176) + - EF7 project bug fixes [#253](https://github.com/OData/RESTier/issues/253), [#254](https://github.com/OData/RESTier/issues/254) \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc2.md b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc2.md new file mode 100644 index 0000000..212a8ac --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-4-0-rc2.md @@ -0,0 +1,8 @@ +## Downloads + + - NuGet: `Install-Package Microsoft.Restier -Version 0.4.0-rc2 -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.4.0-rc2)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.4.0-rc2.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.4.0-rc2.tar.gz)] + +## Bug Fixes + + - Support string as return type or argument of functions [#258](https://github.com/OData/RESTier/issues/258) \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/release-notes/0-5-0-beta.md b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-5-0-beta.md new file mode 100644 index 0000000..c3257ad --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/release-notes/0-5-0-beta.md @@ -0,0 +1,32 @@ +## Downloads + + - NuGet: `Install-Package Microsoft.Restier -Pre` [[Website](http://www.nuget.org/packages/Microsoft.Restier/0.5.0-beta)] + - Source: [[Zip](https://github.com/OData/RESTier/archive/0.5.0-beta.zip)] [[Tarball](https://github.com/OData/RESTier/archive/0.5.0-beta.tar.gz)] + +## New Features + + - [[Issue](https://github.com/OData/RESTier/issues/150)] [[PR](https://github.com/OData/RESTier/pull/286)] Integrate Microsoft Dependency Injection Framework into RESTier. [Tutorial](http://odata.github.io/RESTier/#04-04-Api-Service). + - [[Issue](https://github.com/OData/RESTier/issues/273)] [[PR](https://github.com/OData/RESTier/pull/278)] Support temporal types in Restier.EF. [Tutorial](http://odata.github.io/RESTier/#03-07-Temporal). + - [[Issue](https://github.com/OData/RESTier/issues/383)] [[PR](https://github.com/OData/RESTier/pull/402)] Adopt Web OData Conversion Model builder as default EF provider model builder. [Tutorial](http://odata.github.io/WebApi/#02-04-convention-model-builder). + - [[Issue](https://github.com/OData/RESTier/issues/360)] [[PR](https://github.com/OData/RESTier/pull/399)] Support $apply in RESTier. [Tutorial](http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/odata-data-aggregation-ext-v4.0.html). + +## Enhancements + + - The concept of **hook handler** now becomes **API service** after DI integration. + - The interface `IHookHandler` and `IDelegateHookHandler` are removed. The implementation of any custom API service (previously known as hook handler) should also change accordingly. But this should not be big change. Please see [Tutorial](http://odata.github.io/RESTier/#04-04-Api-Service) for details. + - `AddHookHandler` is now replaced with `AddService` from DI. Please see [Tutorial](http://odata.github.io/RESTier/#04-04-Api-Service) for details. + - `GetHookHandler` is now replaced with `GetApiService` and `GetService` from DI. Please see [Tutorial](http://odata.github.io/RESTier/#04-04-Api-Service) for details. + - All the serializers and `DefaultRestierSerializerProvider` are now public. But we still need to address [#301](https://github.com/OData/RESTier/issues/301) to allow users to override the serializers. + - The interface `IApi` is now removed. Use `ApiBase` instead. We never expect users to directly implement their API classes from `IApi` anyway. The `Context` property in `IApi` now becomes a public property in `ApiBase`. + - Previously the `ApiData` class is very confusing. Now we have given it a more meaningful name `DataSourceStubs` which accurately describes the usage. Along with this change, we also rename `ApiDataReference` to `DataSourceStubReference` accordingly. + - `ApiBase.ApiConfiguration` is renamed to `ApiBase.Configuration` to keep consistent with `ApiBase.Context`. + - The static `Api` class is now separated into two classes `ApiBaseExtensions` and `ApiContextExtensions` to eliminate the ambiguity regarding the previous `Api` class. +## Bug Fixes + + - [[Issue](https://github.com/OData/RESTier/issues/123)] [[PR](https://github.com/OData/RESTier/pull/294)] Fix a bug that prevents using `Edm.Int64` as entity key. + - [[Issue](https://github.com/OData/RESTier/issues/269)] [[PR](https://github.com/OData/RESTier/pull/271)] Fix a bug that `NullReferenceException` is thrown when POST/PATCH/PUT with null property values. + - [[Issue](https://github.com/OData/RESTier/issues/287)] [[PR](https://github.com/OData/RESTier/pull/314)] Fix a bug that $count does not work correctly when there is $expand. + - [[Issue](https://github.com/OData/RESTier/issues/304)] [[PR](https://github.com/OData/RESTier/pull/306)] Fix a bug that `GetModelAsync` is not thread-safe. + - [[Issue](https://github.com/OData/RESTier/issues/304)] [[PR](https://github.com/OData/RESTier/pull/322)] Fix a bug that if `GetModelAsync` takes too long to complete, any subsequent request will fail. + - [[Issue](https://github.com/OData/RESTier/issues/308)] [[PR](https://github.com/OData/RESTier/pull/313)] Fix a bug that `NullReferenceException` is thrown when `ColumnTypeAttribute` does not have a `TypeName` property specified. + - [[Issue](https://github.com/OData/RESTier/issues/309)][[Issue](https://github.com/OData/RESTier/issues/310)][[Issue](https://github.com/OData/RESTier/issues/311)][[Issue](https://github.com/OData/RESTier/issues/312)] [[PR](https://github.com/OData/RESTier/pull/313)] Fix various bugs in the RESTier query pipeline. \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/server/filters.md b/ApiAsAService/RESTier/docs/msdocs/server/filters.md new file mode 100644 index 0000000..3e2a287 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/server/filters.md @@ -0,0 +1,64 @@ +# EntitySet Filters + +Have you ever wanted to limit the results of a particular query based on the current user, or maybe you only want +to return results that are marked "active"? + +EntitySet Filters allow you to consistently control the shape of the results returned from particular EntitySets, +even across navigation properties. + +## Convention-Based Filtering + +Like the rest of RESTier, this is accomplished through a simple convention that +meets the following criteria: + + 1. The filter method name must be `OnFilter{EntitySetName}`, where `{EntitySetName}` is the name the target EntitySet. + 2. It must be a `protected internal` method on the implementing `EntityFrameworkApi` class. + 3. It should accept an IQueryable parameter and return an IQueryable result where T is the Entity type. + +### Example + +```cs +using Microsoft.Restier.Core; +using Microsoft.Restier.Provider.EntityFramework; +using System.Data.Entity; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Filters queries to the Trips EntitySet to only return Users that have Trips. + /// + protected internal IQueryable OnFilterPeople(IQueryable entitySet) + { + return entitySet.Where(c => c.Trips.Any()).AsQueryable(); + } + + /// + /// Filters queries to the Trips EntitySet to only return the current user's Trips. + /// + protected internal IQueryable OnFilterTrips(IQueryable entitySet) + { + return entitySet.Where(c => c.PersonId == ClaimsPrincipal.Current.FindFirst("currentUserId")).AsQueryable(); + } + + } + +} +``` + +## Centralized Filtering + +TODO: Pull content from Section 2.8. \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/server/interceptors.md b/ApiAsAService/RESTier/docs/msdocs/server/interceptors.md new file mode 100644 index 0000000..b738ba9 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/server/interceptors.md @@ -0,0 +1,301 @@ +# Interceptors + +Interceptors allow you to process validation and business logic before *and after* Entities hit the database. For +example, you may need to validate some external business rules before the object is saved, but then after it's saved, +you may need to dump the object to an Azure Storage Queue to get picked up by a WebJob for further processing out-of-band. + +The way RESTier accomplishes this is virtually identical to the [Method Authorization](/server/method-authorization/) +feature. This means there are once again two different approaches to tackle the task. + +As before, no matter what approach you chose, the concept is simple. Either technique uses a function that returns boolean. +Return `true`, and processing continues normally. Return `false`, and RESTier returns a 403 Unauthorized to the client. + +## Convention-Based Interception +Users can control if one of the four submit operations is allowed on some entity set or action by putting some +`protected internal` methods into the `Api` class. The method name must conform to the convention +`On{{BeforeOperation}/{AfterOperation}}{TargetName}`. + + + + + + + + + + + + +
The possible values for {BeforeOperation} are:The possible values for {AfterOperation} are:The possible values for {TargetName} are:
+
    +
  • Inserting
  • +
  • Updating
  • +
  • Deleting
  • +
  • Executing
  • +
+
+
    +
  • Inserted
  • +
  • Updated
  • +
  • Deleted
  • +
  • Executed
  • +
+
+
    +
  • EntitySetName
  • +
  • ActionName
  • +
+
+ +### Example + +The example below demonstrates how both types of `{TargetName}` can be used. + +- The first method shows a simple way to prevent *any* user from deleting a particular EntitySet. +- The second method shows how you can integrate role-based security using multiple techniques. +- The third method shows how to prevent execution a custom Action. + +```cs +using Microsoft.Restier.Providers.EntityFramework; +using System; +using System.Security.Claims; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Specifies whether or not a Trip can be deleted from an EntitySet. + /// + protected void OnInsertingTrip(Trip trip) + { + Trace.WriteLine($"{DateTime.Now.ToString()}: {trip.TripId} is being Inserted."); + + if (string.IsNullOrWhiteSpace(trip.Description)) + { + throw new ODataException("The Trip Description cannot be blank."); + } + } + + /// + /// Specifies whether or not a Trip can be deleted from an EntitySet. + /// + protected void OnInsertedTrip(Trip trip) + { + Trace.WriteLine($"{DateTime.Now.ToString()}: {trip.tripId} has been Inserted."); + + // Pseudocode that represents a real business process. + // EmailManager.SendTripWelcome(trip); + } + + } + +} +``` + +## Centralized Interception + +In addition to the more granular convention-based approach, you can also centralize processing into one location. This is +useful if + +User can use interface `IChangeSetItemAuthorizer` to define any customize authorize logic to see whether user is +authorized for the specified submit, if this method return false, then the related query will get error code 403 (Forbidden). + +There are two steps to plug in the centralized authorization logic. + +- Create a class that implements `IChangeSetItemAuthorizer`. +- Register that class with RESTier through Dependency Injection (DI). + +### Example + +```cs +using Microsoft.OData.Core; +using Microsoft.Restier.Providers.EntityFramework; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// + /// + public class CustomAuthorizer : IChangeSetItemAuthorizer + { + + // The inner handler will call CanUpdate/Insert/Delete method + private IChangeSetItemProcessor Inner { get; set; } + + /// + /// + /// + public Task AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + // TODO: RWM: Provide legitimate samples here, along with parameter documentation. + } + + } + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Allows us to leverage DI to inject additional capabilities into RESTier. + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + return base.ConfigureApi(services) + .AddService(); + } + + } + +} +``` + +NEEDS CLARIFICATION: +In CustomizedAuthorizer, user can decide whether to call the RESTier logic, if user decide to call the RESTier logic, +user can defined a property like "private IChangeSetItemAuthorizer Inner {get; set;}" in class CustomizedAuthorizer, +then call Inner.Inspect() to call RESTier logic which call Authorize part logic defined in section 2.3. + +## Unit Testing Considerations + +Because both of these methods are de-coupled from the code that interacts with the database, the Authorization +logic is easily testable, without having to fire up the entire Web API + RESTier pipeline. + +### Setting up your Unit Test + +If you don't have a unit test project for your API project already, start by creating one. Repeat the process +outlined in "Getting Started" to install the RESTier packages into your Unit Test project. The add the FluentAssertions +package. + +Next, go back to your API project. Expand the "Properties" node, double-click AssemblyInfo.cs, and add the following line +to the very end of the file: `[assembly: InternalsVisibleTo("{TestProjectAssembly}")]`, making sure you replace +{TestProjectAssembly} with the actual assembly name. This is important, because otherwise the tests won't be able to see +the `protected internal` methods the authorization conventions use. + +### Example + +Given the [Convention-Based Authorization](#convention-based-authorization) example, the tests below should have 100% code +coverage, and should pass without any required changes. + +```cs +using FluentAssertions; +using Microsoft.OData.Core; +using Microsoft.OData.Service.Sample.Trippin.Api; +using Microsoft.Restier.Providers.EntityFramework; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Security.Claims; + +namespace Trippin.Tests.Api +{ + + /// + /// Test cases for the RESTier Method Authorizers. + /// + [TestClass] + public class TrippinApiTests + { + + #region Trips EntitySet + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanDelete_IsConfigured() + { + var api = new TrippinApi(); + api.CanDeleteTrips.Should().BeFalse(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to accept Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsAdmin() + { + var api = new TrippinApi(); + + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsAdmin(); + api.CanUpdateTrips.Should().BeTrue(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to reject non-Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsNotAdmin() + { + var api = new TrippinApi(); + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsNonAdmin(); + api.CanUpdateTrips.Should().BeFalse(); + } + + #endregion + + #region Actions + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_CanExecuteResetDataSource_IsConfigured() + { + var api = new TrippinApi(); + api.CanExecuteResetDataSource.Should().BeFalse(); + } + + #endregion + + #region Test Helpers + + /// + /// Sets the Thread.CurrentPrincipal to a test user with an "admin" Role Claim. + /// + internal static void AuthenticateAsAdmin() + { + var claimsCollection = new List + { + new Claim(ClaimTypes.Role, "admin") + }; + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + /// + /// Sets the Thread.CurrentPrincipal to a test user without an "admin" Role Claim. + /// + internal static void AuthenticateAsNonAdmin() + { + var claimsCollection = new List(); + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + #endregion + + } + +} + +``` \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/server/method-authorization.md b/ApiAsAService/RESTier/docs/msdocs/server/method-authorization.md new file mode 100644 index 0000000..7013fc5 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/server/method-authorization.md @@ -0,0 +1,352 @@ +# Method Authorization + +Method Authorization allows you to have fine-grain control over how different types of API requests can be executed. +Since most of RESTier uses built-in convention over repetitive boiler-plate Controllers, you can't just add security attributes +to the controller methods, like you can with Web API. + +However, there are two different methods for defining per-request security. One, like the rest of RESTier, is +convention-based, and the other executes before every request, allowing you to centralize your authorization logic. +This allows you to pick the approach that works best for your architecture. + +No matter what approach you chose, the concept is simple. Either technique uses a function that returns boolean. +Return `true`, and processing continues normally. Return `false`, and RESTier returns a 403 Unauthorized to the client. + +## Convention-Based Authorization +Users can control if one of the four submit operations is allowed on some EntitySet or Action by putting some +`protected internal` methods into the `Api` class. The method name must conform to the convention +`Can{Operation}{TargetName}`. + + + + + + + + + + +
The possible values for {Operation} are:The possible values for {TargetName} are:
+
    +
  • Insert
  • +
  • Update
  • +
  • Delete
  • +
  • Execute
  • +
+
+
    +
  • EntitySetName
  • +
  • ActionName
  • +
+
+ +### Example + +The example below demonstrates how both types of `{TargetName}` can be used. + +- The first method shows a simple way to prevent *any* user from deleting a particular EntitySet. +- The second method shows how you can integrate role-based security using multiple techniques. +- The third method shows how to prevent execution a custom Action. + +```cs +using Microsoft.Restier.Providers.EntityFramework; +using System; +using System.Security.Claims; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Specifies whether or not a Trip can be deleted from an EntitySet. + /// + protected internal bool CanDeleteTrips() + { + return false; + } + + /// + /// User role-based security to specifies whether or not a updated Trip can be sent to an EntitySet. + /// + protected internal bool CanUpdateTrips() + { + // Use claims-based security + return ClaimsPrincipal.Current.IsInRole("admin"); + + // You can also use legacy role-based security, though it's harder to test. + //return HttpContext.Current.User.IsInRole("admin"); + } + + /// + /// Specifies whether or not an Action called ResetDataSource can be executed through the API. + /// + protected internal bool CanExecuteResetDataSource() + { + return false; + } + + } + +} +``` + +## Centralized Authorization + +In addition to the more granular convention-based approach, you can also centralize processing into one location. This is +useful if + +User can use interface `IChangeSetItemAuthorizer` to define any customize authorize logic to see whether user is +authorized for the specified submit, if this method return false, then the related query will get error code 403 (Forbidden). + +There are two steps to plug in the centralized authorization logic. + +- Create a class that implements `IChangeSetItemAuthorizer`. +- Register that class with RESTier through Dependency Injection (DI). + +### Example + +```cs +using Microsoft.OData.Core; +using Microsoft.Restier.Providers.EntityFramework; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// Provides global ChangeSet Authorization for a RESTier API. + /// + public class CustomAuthorizer : IChangeSetItemAuthorizer + { + + /// + /// + /// + public Task AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + // TODO: RWM: Provide legitimate samples here, along with parameter documentation. + } + + } + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Allows us to leverage DI to inject additional capabilities into RESTier. + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + return base.ConfigureApi(services) + .AddService(); + } + + } + +} +``` + +## Leveraging Both Techniques + +There may be certain situations where you want to have a global interceptor, and then pass requests off to the individual +convention-based interceptors. For example, if you need to authenticate a Bearer token. The example below shows you +exactly how this type of scenario would work. + +### Example + +```cs +using Microsoft.OData.Core; +using Microsoft.Restier.Providers.EntityFramework; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + + /// + /// Provides global ChangeSet Authorization for a RESTier API. + /// + public class CustomAuthorizer : IChangeSetItemAuthorizer + { + + /// + /// The built-in ChangeSetItemAuthorizer instance that will be set by RESTier. + /// + private IChangeSetItemAuthorizer InnerAuthorizer {get; set;} + + /// + /// + /// + public Task AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + // TODO: RWM: Provide legitimate samples here, along with parameter documentation. + + // Hand off processing to the appropriate convention-based function. + await InnerAuthorizer.AuthorizeAsync(context, item, cancellationToken); + } + + } + + /// + /// Customizations to the EntityFrameworkApi for the TripPin service. + /// + /// + /// Add the following line in WebApiConfig.cs to register this code: + /// await config.MapRestierRoute("Trippin", "api", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + /// + public class TrippinApi : EntityFrameworkApi + { + + /// + /// Allows us to leverage DI to inject additional capabilities into RESTier. + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + return base.ConfigureApi(services) + .AddService(); + } + + } + +} +``` + +## Unit Testing Considerations + +Because both of these methods are de-coupled from the code that interacts with the database, the Authorization +logic is easily testable, without having to fire up the entire Web API + RESTier pipeline. + +### Setting up your Unit Test + +If you don't have a unit test project for your API project already, start by creating one. Repeat the process +outlined in "Getting Started" to install the RESTier packages into your Unit Test project. The add the FluentAssertions +package. + +Next, go back to your API project. Expand the "Properties" node, double-click AssemblyInfo.cs, and add the following line +to the very end of the file: `[assembly: InternalsVisibleTo("{TestProjectAssembly}")]`, making sure you replace +{TestProjectAssembly} with the actual assembly name. This is important, because otherwise the tests won't be able to see +the `protected internal` methods the authorization conventions use. + +### Example + +Given the [Convention-Based Authorization](#convention-based-authorization) example, the tests below should have 100% code +coverage, and should pass without any required changes. + +```cs +using FluentAssertions; +using Microsoft.OData.Core; +using Microsoft.OData.Service.Sample.Trippin.Api; +using Microsoft.Restier.Providers.EntityFramework; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Security.Claims; + +namespace Trippin.Tests.Api +{ + + /// + /// Test cases for the RESTier Method Authorizers. + /// + [TestClass] + public class TrippinApiTests + { + + #region Trips EntitySet + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanDelete_IsConfigured() + { + var api = new TrippinApi(); + api.CanDeleteTrips.Should().BeFalse(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to accept Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsAdmin() + { + var api = new TrippinApi(); + + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsAdmin(); + api.CanUpdateTrips.Should().BeTrue(); + } + + /// + /// Tests if the Trips EntitySet is properly configured to reject non-Admin update requests. + /// + [TestMethod] + public void TrippinApi_Trips_CanUpdate_IsNotAdmin() + { + var api = new TrippinApi(); + // We won't be testing HttpContext-related security here, because that requires mocking, + // which is outside the scope of this document. + AuthenticateAsNonAdmin(); + api.CanUpdateTrips.Should().BeFalse(); + } + + #endregion + + #region Actions + + /// + /// Tests if the Trips EntitySet is properly configured to reject delete requests. + /// + [TestMethod] + public void TrippinApi_CanExecuteResetDataSource_IsConfigured() + { + var api = new TrippinApi(); + api.CanExecuteResetDataSource.Should().BeFalse(); + } + + #endregion + + #region Test Helpers + + /// + /// Sets the Thread.CurrentPrincipal to a test user with an "admin" Role Claim. + /// + internal static void AuthenticateAsAdmin() + { + var claimsCollection = new List + { + new Claim(ClaimTypes.Role, "admin") + }; + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + /// + /// Sets the Thread.CurrentPrincipal to a test user without an "admin" Role Claim. + /// + internal static void AuthenticateAsNonAdmin() + { + var claimsCollection = new List(); + var claimsIdentity = new ClaimsIdentity(claimsCollection, "Test User"); + Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity); + } + + #endregion + + } + +} + +``` \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/server/model-building.md b/ApiAsAService/RESTier/docs/msdocs/server/model-building.md new file mode 100644 index 0000000..2029611 --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/server/model-building.md @@ -0,0 +1,277 @@ +# Customizing the Entity Model + +OData and the Entity Framework are based on the same underlying concept for mapping the idea of an Entity with +its representation in the database. That "mapping" layer is called the Entity Data Model, or EDM for short. + +Part of the beautiy of RESTier is that, for the majority of API builders, it can construct your EDM for you +*automagically*. But there are times where you have to take charge of the process. And as with many things in RESTier, +the intrepid developers at Microsoft provide you with two ways to do so. + +The first method allows you to completely relpace the automagic model construction with your own, in a manner +very similar to Web API OData. + +The second method lets RESTier do the initial work for you, and then you manipulate the resulting EDM metadata. + +Let's take a look at how each of these methods work. + +## ModelBuilder Takeover + +There are several situations where you are likely going to want to use this approach to create your Model. +For example, if you're migrating from an existing Web API OData v3 or v4 implementation, and needed to +customize that model, you will be able to copy/paste your existing code over, with just a few small changes. +If you're building a new model, but you're using Entity Framework Model First + SQL Views, then you'll +likely need to define a primary key, or omit the View from your service. + +With the Entity Framework provider, the model is built with the +[**ODataConventionModelBuilder**](http://odata.github.io/WebApi/#02-04-convention-model-builder). To +understand how this ModelBuilder works, please take a few minutes and review that documentation. + +# Example + +```cs +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Web.OData.Builder; + +namespace Microsoft.OData.Service.Sample.TrippinInMemory +{ + + internal class CustomizedModelBuilder : IModelBuilder + { + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + var builder = new ODataConventionModelBuilder(); + builder.EntityType(); + return Task.FromResult(builder.GetEdmModel()); + } + } + + /// + /// + /// + public class TrippinApi : ApiBase + { + + /// + /// + /// + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + return base.ConfigureApi(services) + .AddService(); + } + + } + +} +``` + +If RESTier entity framework provider is used and user has no additional types other than those in the database schema, no +custom model builder or even the `Api` class is required because the provider will take over to build the model instead. +But what the provider does behind the scene is similar. + + + +## Extend a model from Api class +The `RestierModelExtender` will further extend the EDM model passed in using the public properties and methods defined in the +`Api` class. Please note that all properties and methods declared in the parent classes are **NOT** considered. + +**Entity set** +If a property declared in the `Api` class satisfies the following conditions, an entity set whose name is the property name +will be added into the model. + + - Public + - Has getter + - Either static or instance + - There is no existing entity set with the same name + - Return type must be `IQueryable` where `T` is class type + +Example: + +```cs +using System.Collections.Generic; +using System.Linq; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinApi : EntityFrameworkApi + { + public IQueryable PeopleWithFriends + { + get { return Context.People.Include("Friends"); } + } + ... + } +} +``` + +**Singleton** +If a property declared in the `Api` class satisfies the following conditions, a singleton whose name is the property name +will be added into the model. + + - Public + - Has getter + - Either static or instance + - There is no existing singleton with the same name + - Return type must be non-generic class type + +Example: + +```cs +using System.Collections.Generic; +using System.Linq; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinApi : EntityFrameworkApi + { + ... + public Person Me { get { return DbContext.People.Find(1); } } + ... + } +} +``` + +Due to some limitations from Entity Framework and OData spec, CUD (insertion, update and deletion) on the singleton entity are +**NOT** supported directly by RESTier. Users need to define their own route to achieve these operations. + +**Navigation property binding** +Starting from version 0.5.0, the `RestierModelExtender` follows the rules below to add navigation property bindings after entity + sets and singletons have been built. + + - Bindings will **ONLY** be added for those entity sets and singletons that have been built inside `RestierModelExtender`. + **Example:** Entity sets built by the RESTier's EF provider are assumed to have their navigation property bindings added already. + - The `RestierModelExtender` only searches navigation sources who have the same entity type as the source navigation property. + **Example:** If the type of a navigation property is `Person` or `Collection(Person)`, only those entity sets and singletons of type `Person` are searched. + - Singleton navigation properties can be bound to either entity sets or singletons. + **Example:** If `Person.BestFriend` is a singleton navigation property, bindings from `BestFriend` to an entity set `People` or to a singleton `Boss` are all allowed. + - Collection navigation properties can **ONLY** be bound to entity sets. + **Example:** If `Person.Friends` is a collection navigation property. **ONLY** binding from `Friends` to an entity set `People` is allowed. Binding from `Friends` to a singleton `Boss` is **NOT** allowed. + - If there is any ambiguity among entity sets or singletons, no binding will be added. + **Example:** For the singleton navigation property `Person.BestFriend`, no binding will be added if 1) there are at least two entity sets (or singletons) both of type `Person`; 2) there is at least one entity set and one singleton both of type `Person`. However for the collection navigation property `Person.Friends`, no binding will be added only if there are at least two entity sets both of type `Person`. One entity set and one singleton both of type `Person` will **NOT** lead to any ambiguity and one binding to the entity set will be added. + +If any expected navigation property binding is not added by RESTier, users can always manually add it through custom model extension (mentioned below). +
+ +**Operation** +If a method declared in the `Api` class satisfies the following conditions, an operation whose name is the method name will be added into the model. + + - Public + - Either static or instance + - There is no existing operation with the same name + +Example (namespace should be specified if the namespace of the method does not match the model): + +```cs +using System.Collections.Generic; +using System.Linq; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinApi : EntityFrameworkApi + { + ... + // Action import + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", HasSideEffects = true)] + public void CleanUpExpiredTrips() {} + + // Bound action + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", HasSideEffects = true)] + public Trip EndTrip(Trip bindingParameter) { ... } + + // Function import + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", EntitySet = "People")] + public IEnumerable GetPeopleWithFriendsAtLeast(int n) { ... } + + // Bound function + [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", EntitySet = "People")] + public Person GetPersonWithMostFriends(IEnumerable bindingParameter) { ... } + ... + } +} +``` + +Note: + +1. Operation attribute's EntitySet property is needed if there are more than one entity set of the entity type that is type of result defined. Take an example if two EntitySet People and AllPersons are defined whose entity type is Person, and the function returns Person or List of Person, then the Operation attribute for function must have EntitySet defined, or EntitySet property is optional. + +2. Function and Action uses the same attribute, and if the method is an action, must specify property HasSideEffects with value of true whose default value is false. + +3. In order to access an operation user must define an action with `ODataRouteAttribute` in his custom controller. +Refer to [section 3.3](http://odata.github.io/RESTier/#03-03-Operation) for more information. + +## Custom model extension +If users have the need to extend the model even after RESTier's conventions have been applied, user can use IServiceCollection AddService to add a ModelBuilder after calling base.ConfigureApi(services). + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Provider.EntityFramework; +using Microsoft.OData.Service.Sample.Trippin.Models; + +namespace Microsoft.OData.Service.Sample.Trippin.Api +{ + public class TrippinAttribute : ApiConfiguratorAttribute + { + protected override IServiceCollection ConfigureApi(IServiceCollection services) + { + services = base.ConfigureApi(services); + // Add your custom model extender here. + services.AddService(); + return services; + } + + private class CustomizedModelBuilder : IModelBuilder + { + public IModelBuilder InnerModelBuilder { get; set; } + + public async Task GetModelAsync(InvocationContext context, CancellationToken cancellationToken) + { + IEdmModel model = null; + + // Call inner model builder to get a model to extend. + if (this.InnerModelBuilder != null) + { + model = await this.InnerModelBuilder.GetModelAsync(context, cancellationToken); + } + + // Do sth to extend the model such as add custom navigation property binding. + + return model; + } + } + } +} +``` + +After the above steps, the final process of building the model will be: + + - User's model builder registered before base.ConfigureApi(services) is called first. + - RESTier's model builder includes EF model builder and RestierModelExtender will be called. + - User's model builder registered after base.ConfigureApi(services) is called. +
+ +If InnerModelBuilder method is not called first, then the calling sequence will be different. +Actually this order not only applies to the `IModelBuilder` but also all other services. + +Refer to [section 4.3](http://odata.github.io/RESTier/#04-03-Api-Service) for more details of RESTier API Service. \ No newline at end of file diff --git a/ApiAsAService/RESTier/docs/msdocs/vs-highlight.css b/ApiAsAService/RESTier/docs/msdocs/vs-highlight.css new file mode 100644 index 0000000..e94200f --- /dev/null +++ b/ApiAsAService/RESTier/docs/msdocs/vs-highlight.css @@ -0,0 +1,81 @@ +/* +Visual Studio-like style based on original C# coloring by Jason Diamond +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-comment, +.hljs-quote, +.hljs-variable { + color: #008000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-built_in, +.hljs-name, +.hljs-tag { + color: #00f; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-literal, +.hljs-template-tag, +.hljs-template-variable, +.hljs-type, +.hljs-addition { + color: #a31515; +} + +.hljs-deletion, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-meta { + color: #2b91af; +} + +.hljs-doctag { + color: #808080; +} + +.hljs-attr { + color: #f00; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #00b0e8; +} + + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +code { + font-size: 80%; +} + +td code { + font-size: 100%; +} + +.wy-menu-vertical .subnav li.current > a { + padding-left: 2.42em; +} +.wy-menu-vertical .subnav li.current > ul li a { + padding-left: 3.23em; +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/api/Directory.Build.props b/ApiAsAService/RESTier/samples/api/Directory.Build.props new file mode 100644 index 0000000..6f2c715 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/Directory.Build.props @@ -0,0 +1,27 @@ + + + + + + + 1.0.0-Beta2.Preview1 + + + + + + $(MSBuildThisFileDirectory) + + true + 7.3 + $(MSBuildThisFileDirectory)..\Shared\ + + + diff --git a/ApiAsAService/RESTier/samples/api/Microsoft.Restier.Samples.Apis.sln b/ApiAsAService/RESTier/samples/api/Microsoft.Restier.Samples.Apis.sln new file mode 100644 index 0000000..b9c9c31 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/Microsoft.Restier.Samples.Apis.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Samples.Chinook.Api", "chinook\Microsoft.Restier.Samples.Chinook.Api\Microsoft.Restier.Samples.Chinook.Api.csproj", "{A0920D03-AB1D-4F56-ABF7-2C694E100F09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Samples.Chinook.Tests.Api", "chinook\Microsoft.Restier.Samples.Chinook.Tests.Api\Microsoft.Restier.Samples.Chinook.Tests.Api.csproj", "{41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Release|Any CPU.Build.0 = Release|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/RESTier/samples/api/chinook/Directory.Build.props b/ApiAsAService/RESTier/samples/api/chinook/Directory.Build.props new file mode 100644 index 0000000..cc02d5e --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Directory.Build.props @@ -0,0 +1,27 @@ + + + + + + + 1.0.0-Beta2.Preview1 + + + + + + $(MSBuildThisFileDirectory) + + true + 7.3 + $(MSBuildThisFileDirectory)..\Shared\ + + + diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api.sln b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api.sln new file mode 100644 index 0000000..474fb33 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Samples.Chinook.Api", "Microsoft.Restier.Samples.Chinook.Api\Microsoft.Restier.Samples.Chinook.Api.csproj", "{A0920D03-AB1D-4F56-ABF7-2C694E100F09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Samples.Chinook.Tests.Api", "Microsoft.Restier.Samples.Chinook.Tests.Api\Microsoft.Restier.Samples.Chinook.Tests.Api.csproj", "{41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0920D03-AB1D-4F56-ABF7-2C694E100F09}.Release|Any CPU.Build.0 = Release|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/App_Start/WebApiConfig.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/App_Start/WebApiConfig.cs new file mode 100644 index 0000000..94f3992 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/App_Start/WebApiConfig.cs @@ -0,0 +1,20 @@ +using System.Web.Http; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Restier.AspNet.Batch; +using Microsoft.Restier.EntityFramework; +using Microsoft.Restier.Samples.Chinook.Api.Models; + +namespace Microsoft.Restier.Samples.Chinook.Api +{ + public static class WebApiConfig + { + public static async void Register(HttpConfiguration config) + { + config.Filter().Expand().Select().OrderBy().MaxTop(null).Count(); + await config.MapRestierRoute>( + "Chinook", + "api/", + new RestierBatchHandler(GlobalConfiguration.DefaultServer)).ConfigureAwait(false); + } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax new file mode 100644 index 0000000..67e5666 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.Restier.Samples.Chinook.Api.WebApiApplication" Language="C#" %> diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax.cs new file mode 100644 index 0000000..50030c0 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Global.asax.cs @@ -0,0 +1,12 @@ +using System.Web.Http; + +namespace Microsoft.Restier.Samples.Chinook.Api +{ + public class WebApiApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + GlobalConfiguration.Configure(WebApiConfig.Register); + } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Microsoft.Restier.Samples.Chinook.Api.csproj b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Microsoft.Restier.Samples.Chinook.Api.csproj new file mode 100644 index 0000000..fac187c --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Microsoft.Restier.Samples.Chinook.Api.csproj @@ -0,0 +1,199 @@ + + + + Debug + AnyCPU + + + 2.0 + {A0920D03-AB1D-4F56-ABF7-2C694E100F09} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Microsoft.Restier.Samples.Chinook.Api + Microsoft.Restier.Samples.Chinook.Api + v4.7.2 + true + + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + true + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\packages\Ben.Demystifier.0.1.3\lib\net45\Ben.Demystifier.dll + + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\Microsoft.AspNet.OData.7.1.0\lib\net45\Microsoft.AspNet.OData.dll + + + ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll + + + + ..\packages\Microsoft.Extensions.DependencyInjection.2.2.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.OData.Core.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\packages\Microsoft.OData.Edm.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + ..\packages\Microsoft.Restier.AspNet.1.0.0-CI-20190303-040812\lib\net472\Microsoft.Restier.AspNet.dll + + + ..\packages\Microsoft.Restier.Core.1.0.0-CI-20190303-040812\lib\net462\Microsoft.Restier.Core.dll + + + ..\packages\Microsoft.Restier.EntityFramework.1.0.0-CI-20190303-040812\lib\net472\Microsoft.Restier.EntityFramework.dll + + + ..\packages\Microsoft.Spatial.7.5.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll + + + ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + + + ..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + ..\packages\System.ComponentModel.Annotations.4.5.0\lib\net461\System.ComponentModel.Annotations.dll + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + ..\packages\System.Reflection.Metadata.1.5.0\lib\netstandard2.0\System.Reflection.Metadata.dll + + + + + + + + + + + + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + + + + + + + + + + + + + + + Global.asax + + + + + + + + + + + + + + + + + + Web.config + + + Web.config + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 58019 + / + http://localhost:58019/ + False + False + + + False + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Album.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Album.cs new file mode 100644 index 0000000..bfcf49c --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Album.cs @@ -0,0 +1,29 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Album")] + public sealed class Album + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Album() + { + Tracks = new HashSet(); + } + + public int AlbumId { get; set; } + + [Required] + [StringLength(160)] + public string Title { get; set; } + + public int ArtistId { get; set; } + + public Artist Artist { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Tracks { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Artist.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Artist.cs new file mode 100644 index 0000000..9488a03 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Artist.cs @@ -0,0 +1,24 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Artist")] + public sealed class Artist + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Artist() + { + Albums = new HashSet(); + } + + public int ArtistId { get; set; } + + [StringLength(120)] + public string Name { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Albums { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/ChinookContext.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/ChinookContext.cs new file mode 100644 index 0000000..38800c0 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/ChinookContext.cs @@ -0,0 +1,80 @@ +using Microsoft.Restier.Samples.Chinook.Api.Models; + +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Data.Entity; + + public class ChinookContext : DbContext + { + public ChinookContext() + : base("name=ChinookContext") + { + } + + public virtual DbSet Albums { get; set; } + public virtual DbSet Artists { get; set; } + public virtual DbSet Customers { get; set; } + public virtual DbSet Employees { get; set; } + public virtual DbSet Genres { get; set; } + public virtual DbSet Invoices { get; set; } + public virtual DbSet InvoiceLines { get; set; } + public virtual DbSet MediaTypes { get; set; } + public virtual DbSet Playlists { get; set; } + public virtual DbSet Tracks { get; set; } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(e => e.Albums) + .WithRequired(e => e.Artist) + .WillCascadeOnDelete(false); + + modelBuilder.Entity() + .HasMany(e => e.Invoices) + .WithRequired(e => e.Customer) + .WillCascadeOnDelete(false); + + modelBuilder.Entity() + .HasMany(e => e.Customers) + .WithOptional(e => e.Employee) + .HasForeignKey(e => e.SupportRepId); + + modelBuilder.Entity() + .HasMany(e => e.Employee1) + .WithOptional(e => e.Employee2) + .HasForeignKey(e => e.ReportsTo); + + modelBuilder.Entity() + .Property(e => e.Total) + .HasPrecision(10, 2); + + modelBuilder.Entity() + .HasMany(e => e.InvoiceLines) + .WithRequired(e => e.Invoice) + .WillCascadeOnDelete(false); + + modelBuilder.Entity() + .Property(e => e.UnitPrice) + .HasPrecision(10, 2); + + modelBuilder.Entity() + .HasMany(e => e.Tracks) + .WithRequired(e => e.MediaType) + .WillCascadeOnDelete(false); + + modelBuilder.Entity() + .HasMany(e => e.Tracks) + .WithMany(e => e.Playlists) + .Map(m => m.ToTable("PlaylistTrack").MapLeftKey("PlaylistId").MapRightKey("TrackId")); + + modelBuilder.Entity() + .Property(e => e.UnitPrice) + .HasPrecision(10, 2); + + modelBuilder.Entity() + .HasMany(e => e.InvoiceLines) + .WithRequired(e => e.Track) + .WillCascadeOnDelete(false); + } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Customer.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Customer.cs new file mode 100644 index 0000000..9843813 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Customer.cs @@ -0,0 +1,61 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Customer")] + public sealed class Customer + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Customer() + { + Invoices = new HashSet(); + } + + public int CustomerId { get; set; } + + [Required] + [StringLength(40)] + public string FirstName { get; set; } + + [Required] + [StringLength(20)] + public string LastName { get; set; } + + [StringLength(80)] + public string Company { get; set; } + + [StringLength(70)] + public string Address { get; set; } + + [StringLength(40)] + public string City { get; set; } + + [StringLength(40)] + public string State { get; set; } + + [StringLength(40)] + public string Country { get; set; } + + [StringLength(10)] + public string PostalCode { get; set; } + + [StringLength(24)] + public string Phone { get; set; } + + [StringLength(24)] + public string Fax { get; set; } + + [Required] + [StringLength(60)] + public string Email { get; set; } + + public int? SupportRepId { get; set; } + + public Employee Employee { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Invoices { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Employee.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Employee.cs new file mode 100644 index 0000000..5f009f9 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Employee.cs @@ -0,0 +1,69 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Employee")] + public sealed class Employee + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Employee() + { + Customers = new HashSet(); + Employee1 = new HashSet(); + } + + public int EmployeeId { get; set; } + + [Required] + [StringLength(20)] + public string LastName { get; set; } + + [Required] + [StringLength(20)] + public string FirstName { get; set; } + + [StringLength(30)] + public string Title { get; set; } + + public int? ReportsTo { get; set; } + + public DateTime? BirthDate { get; set; } + + public DateTime? HireDate { get; set; } + + [StringLength(70)] + public string Address { get; set; } + + [StringLength(40)] + public string City { get; set; } + + [StringLength(40)] + public string State { get; set; } + + [StringLength(40)] + public string Country { get; set; } + + [StringLength(10)] + public string PostalCode { get; set; } + + [StringLength(24)] + public string Phone { get; set; } + + [StringLength(24)] + public string Fax { get; set; } + + [StringLength(60)] + public string Email { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Customers { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Employee1 { get; set; } + + public Employee Employee2 { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Genre.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Genre.cs new file mode 100644 index 0000000..6f148b4 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Genre.cs @@ -0,0 +1,24 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Genre")] + public sealed class Genre + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Genre() + { + Tracks = new HashSet(); + } + + public int GenreId { get; set; } + + [StringLength(120)] + public string Name { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Tracks { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Invoice.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Invoice.cs new file mode 100644 index 0000000..450f20e --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Invoice.cs @@ -0,0 +1,46 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Invoice")] + public sealed class Invoice + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Invoice() + { + InvoiceLines = new HashSet(); + } + + public int InvoiceId { get; set; } + + public int CustomerId { get; set; } + + public DateTime InvoiceDate { get; set; } + + [StringLength(70)] + public string BillingAddress { get; set; } + + [StringLength(40)] + public string BillingCity { get; set; } + + [StringLength(40)] + public string BillingState { get; set; } + + [StringLength(40)] + public string BillingCountry { get; set; } + + [StringLength(10)] + public string BillingPostalCode { get; set; } + + [Column(TypeName = "numeric")] + public decimal Total { get; set; } + + public Customer Customer { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection InvoiceLines { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/InvoiceLine.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/InvoiceLine.cs new file mode 100644 index 0000000..51a185c --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/InvoiceLine.cs @@ -0,0 +1,23 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.ComponentModel.DataAnnotations.Schema; + + [Table("InvoiceLine")] + public class InvoiceLine + { + public int InvoiceLineId { get; set; } + + public int InvoiceId { get; set; } + + public int TrackId { get; set; } + + [Column(TypeName = "numeric")] + public decimal UnitPrice { get; set; } + + public int Quantity { get; set; } + + public virtual Invoice Invoice { get; set; } + + public virtual Track Track { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/MediaType.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/MediaType.cs new file mode 100644 index 0000000..0e90794 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/MediaType.cs @@ -0,0 +1,24 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("MediaType")] + public sealed class MediaType + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public MediaType() + { + Tracks = new HashSet(); + } + + public int MediaTypeId { get; set; } + + [StringLength(120)] + public string Name { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Tracks { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Playlist.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Playlist.cs new file mode 100644 index 0000000..cc1aed4 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Playlist.cs @@ -0,0 +1,24 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Playlist")] + public sealed class Playlist + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Playlist() + { + Tracks = new HashSet(); + } + + public int PlaylistId { get; set; } + + [StringLength(120)] + public string Name { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Tracks { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Track.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Track.cs new file mode 100644 index 0000000..0583acb --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Models/Track.cs @@ -0,0 +1,51 @@ +namespace Microsoft.Restier.Samples.Chinook.Api.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; + + [Table("Track")] + public class Track + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Track() + { + InvoiceLines = new HashSet(); + Playlists = new HashSet(); + } + + public int TrackId { get; set; } + + [Required] + [StringLength(200)] + public string Name { get; set; } + + public int? AlbumId { get; set; } + + public int MediaTypeId { get; set; } + + public int? GenreId { get; set; } + + [StringLength(220)] + public string Composer { get; set; } + + public int Milliseconds { get; set; } + + public int? Bytes { get; set; } + + [Column(TypeName = "numeric")] + public decimal UnitPrice { get; set; } + + public Album Album { get; set; } + + public Genre Genre { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection InvoiceLines { get; set; } + + public MediaType MediaType { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public ICollection Playlists { get; set; } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Properties/AssemblyInfo.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d904694 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Restier.Samples.Chinook.Api")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.Restier.Samples.Chinook.Api")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a0920d03-ab1d-4f56-abf7-2c694e100f09")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Debug.config b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Debug.config new file mode 100644 index 0000000..fae9cfe --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Release.config b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Release.config new file mode 100644 index 0000000..da6e960 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.config b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.config new file mode 100644 index 0000000..cd53586 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/Web.config @@ -0,0 +1,79 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/packages.config b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/packages.config new file mode 100644 index 0000000..975416d --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Api/packages.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Microsoft.Restier.Samples.Chinook.Tests.Api.csproj b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Microsoft.Restier.Samples.Chinook.Tests.Api.csproj new file mode 100644 index 0000000..3056e49 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Microsoft.Restier.Samples.Chinook.Tests.Api.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {41BAC0CC-C2B0-47B8-91C6-8E0E9FCBCF44} + Library + Properties + Microsoft.Restier.Samples.Chinook.Tests.Api + Microsoft.Restier.Samples.Chinook.Tests.Api + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Properties/AssemblyInfo.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..85681d7 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Microsoft.Restier.Samples.Chinook.Api.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.Restier.Samples.Chinook.Api.Tests")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("41bac0cc-c2b0-47b8-91c6-8e0e9fcbcf44")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/UnitTest1.cs b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/UnitTest1.cs new file mode 100644 index 0000000..d327e1d --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/UnitTest1.cs @@ -0,0 +1,13 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Samples.Chinook.Tests.Api +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/packages.config b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/packages.config new file mode 100644 index 0000000..2f7c5a1 --- /dev/null +++ b/ApiAsAService/RESTier/samples/api/chinook/Microsoft.Restier.Samples.Chinook.Tests.Api/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/apps/angular/.gitignore b/ApiAsAService/RESTier/samples/apps/angular/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/ApiAsAService/RESTier/samples/apps/angular/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/apps/blazor/.gitignore b/ApiAsAService/RESTier/samples/apps/blazor/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/ApiAsAService/RESTier/samples/apps/blazor/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/apps/react/.gitignore b/ApiAsAService/RESTier/samples/apps/react/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/ApiAsAService/RESTier/samples/apps/react/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/apps/uwp/.gitignore b/ApiAsAService/RESTier/samples/apps/uwp/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/ApiAsAService/RESTier/samples/apps/uwp/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/ApiAsAService/RESTier/samples/apps/xamarin/.gitignore b/ApiAsAService/RESTier/samples/apps/xamarin/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/ApiAsAService/RESTier/samples/apps/xamarin/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/CodeAnalysisDictionary.xml b/ApiAsAService/RESTier/src/CodeAnalysisDictionary.xml new file mode 100644 index 0000000..f764342 --- /dev/null +++ b/ApiAsAService/RESTier/src/CodeAnalysisDictionary.xml @@ -0,0 +1,71 @@ + + + + + Multi + Bitly + Digg + Facebook + Reddit + Captcha + Composable + Facebook + Gravatar + JSON + Lookahead + MVC + Param + Params + Pluralizer + Pragma + Pragmas + Restier + Templating + Unvalidated + Validator + Validators + Validatable + WebPage + cshtml + vbhtml + asax + Eval + Src + Charset + Coords + Rel + Dto + Tokenizer + ReDim + OAuth + OpenID + Yadis + fwlink + Edm + Deserializer + Api + ws + enc + dir + Auth + bg + Cors + Sourcer + + + WebPage + WebPages + TimeLine + oAuth + userName + HasId + + + + + ID + Db + Dto + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Common.Stylecop b/ApiAsAService/RESTier/src/Common.Stylecop new file mode 100644 index 0000000..bfb542b --- /dev/null +++ b/ApiAsAService/RESTier/src/Common.Stylecop @@ -0,0 +1,114 @@ + + + NoMerge + + Api + changeset + clr + composable + Db + deserializer + Edm + enum + eventing + nullable + queryable + RESTier + sourcer + metadata + odata + + + + + + False + + TemporaryGeneratedFile_.*\.cs$ + + + + + + + + + + False + + + + + True + True + + + + + + + False + + + + + + + + + False + + + + + False + + + + + + + + + False + + + + + + + + + as + of + is + ef + to + db + + + + + + + Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See License.txt in the project root for license information. + + + + + + 120 + + + + + + + False + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/CommonAssemblyInfo.cs b/ApiAsAService/RESTier/src/CommonAssemblyInfo.cs new file mode 100644 index 0000000..afc77cb --- /dev/null +++ b/ApiAsAService/RESTier/src/CommonAssemblyInfo.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; + +#if !NOT_CLS_COMPLIANT +[assembly: CLSCompliant(true)] +#endif +[assembly: NeutralResourcesLanguage("en-US")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersion("1.0.0")] +[assembly: AssemblyMetadata("Serviceable", "True")] \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Directory.Build.props b/ApiAsAService/RESTier/src/Directory.Build.props new file mode 100644 index 0000000..cc02d5e --- /dev/null +++ b/ApiAsAService/RESTier/src/Directory.Build.props @@ -0,0 +1,27 @@ + + + + + + + 1.0.0-Beta2.Preview1 + + + + + + $(MSBuildThisFileDirectory) + + true + 7.3 + $(MSBuildThisFileDirectory)..\Shared\ + + + diff --git a/ApiAsAService/RESTier/src/GlobalSuppressions.cs b/ApiAsAService/RESTier/src/GlobalSuppressions.cs new file mode 100644 index 0000000..fb23735 --- /dev/null +++ b/ApiAsAService/RESTier/src/GlobalSuppressions.cs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +#region Permanent Exclusions +[assembly: SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.EntityFrameworkApi`1.#ConfigureApi(System.Type,Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryRequest.#Create`2(System.Linq.IQueryable`1,System.Linq.Expressions.Expression`1,!!1>>,System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Restier.Core.Submit.ChangeSetValidationException", Justification = "We do not intend to support serialization of this exception yet")] +[assembly: SuppressMessage("Microsoft.Design", "CA1061:DoNotHideBaseClassMethods", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Batch.RestierBatchChangeSetRequestItem.#DisposeResponses(System.Collections.Generic.IEnumerable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.ApiBase.#Dispose()", Justification = "Need to do some clean up before the virtual Dispose(disposing) method gets called.")] +[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "These assemblies are delay-signed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.Restier.Core.Submit.ChangeSetValidationResults")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.Restier.Security.ApiPermission")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Scope = "member", Target = "Microsoft.Restier.Core.Submit.ChangeSetEntry.#Type")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryModelReference.#Type")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ModelProducer.#.cctor()")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController`1.#GetQuery(System.Web.OData.Extensions.HttpRequestMessageProperties)", Justification = "Instance is disposed elsewhere")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Scope = "type", Target = "Microsoft.Restier.Core.Submit.ChangeSetValidationException", Justification = "We do not intend to support serialization of this exception yet")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", Justification = "AssemblyInformationalVersion could be string.")] + +#region CA1004 Generic method with type parameter +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Core.Query.IQueryExecutor.#ExecuteExpressionAsync`1(Microsoft.Restier.Core.Query.QueryContext,System.Linq.IQueryProvider,System.Linq.Expressions.Expression,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Core.ServiceCollectionExtensions.#HasService`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Core.ServiceCollectionExtensions.#AddService`2(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Core.ServiceCollectionExtensions.#MakeSingleton`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Core.ServiceCollectionExtensions.#MakeScoped`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Core.ServiceCollectionExtensions.#MakeTransient`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ServiceCollectionExtensions.#AddEfProviderServices`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.ServiceCollectionExtensions.#AddODataServices`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.HttpConfigurationExtensions.#MapRestierRoute`1(System.Web.Http.HttpConfiguration,System.String,System.String,System.Func`1,Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.HttpConfigurationExtensions.#MapRestierRoute`1(System.Web.Http.HttpConfiguration,System.String,System.String,Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler)")] +#endregion + +#region CA1006 Nested Generic Type +[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`1(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`2(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1,System.Linq.Expressions.Expression`1,!!1>>,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#ResourceTypeKeyPropertiesMap")] +[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryRequest.#Create`2(System.Linq.IQueryable`1,System.Linq.Expressions.Expression`1,!!1>>,System.Nullable`1)")] +#endregion + +#region CA1020 Few types in namespace +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Core")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Core.Model")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Core.Operation")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Providers.EntityFramework")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Publishers.OData")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Publishers.OData.Batch")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Publishers.OData.Filters")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Publishers.OData.Model")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Restier.Publishers.OData.Results")] +#endregion + +#region CA1026 Default Parameter +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler.#.ctor(System.Web.Http.HttpServer,System.Func`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#GetModelAsync(Microsoft.Restier.Core.ApiBase,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`1(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`2(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1,System.Linq.Expressions.Expression`1,!!1>>,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync(Microsoft.Restier.Core.ApiBase,Microsoft.Restier.Core.Query.QueryRequest,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#SubmitAsync(Microsoft.Restier.Core.ApiBase,Microsoft.Restier.Core.Submit.ChangeSet,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryRequest.#.ctor(System.Linq.IQueryable,System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryRequest.#Create`2(System.Linq.IQueryable`1,System.Linq.Expressions.Expression`1,!!1>>,System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryRequest.#Create(System.Linq.IQueryable,System.Linq.Expressions.LambdaExpression,System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryResult.#.ctor(System.Collections.IEnumerable,System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Security.ApiPermission.#CreateGrant(System.String,System.String,System.String,System.String,System.String)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Security.ApiPermission.#CreateDeny(System.String,System.String,System.String,System.String,System.String)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.HttpConfigurationExtensions.#MapRestierRoute`1(System.Web.Http.HttpConfiguration,System.String,System.String,System.Func`1,Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.HttpConfigurationExtensions.#MapRestierRoute`1(System.Web.Http.HttpConfiguration,System.String,System.String,Microsoft.Restier.Publishers.OData.Batch.RestierBatchHandler)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Restier.Core.PreconditionFailedException")] +[assembly: SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Restier.Core.ResourceNotFoundException")] +[assembly: SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Restier.Core.PreconditionRequiredException")] +#endregion + +#region CA1704 Identifiers spelling +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sourcer", Scope = "type", Target = "Microsoft.Restier.Core.Query.IQueryExpressionSourcer")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sourcer", Scope = "type", Target = "Microsoft.Restier.Providers.EntityFramework.QueryExpressionSourcer")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ef", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ServiceCollectionExtensions.#AddEfProviderServices`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ef", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ChangeSetInitializer.#ConvertToEfValue(System.Type,System.Object)")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Etag", Scope = "member", Target = "Microsoft.Restier.Core.Submit.DataModificationItem.#ValidateEtag(System.Linq.IQueryable)")] +#endregion + +#region CA1709 Identifiers case +[assembly: SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ef", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ServiceCollectionExtensions.#AddEfProviderServices`1(Microsoft.Extensions.DependencyInjection.IServiceCollection)")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Ef", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ChangeSetInitializer.#ConvertToEfValue(System.Type,System.Object)")] +#endregion + +#endregion + +#region Temporary Exclusions + +#region CA1811 Review uncalled private code +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Core.Submit.DataModificationItem.#ServerValues")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ModelProducer.#InnerModelBuilder")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.QueryExecutor.#Inner")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Query.RestierQueryExecutor.#Inner")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.ValidationResultDto.#Severity")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.ValidationResultDto.#PropertyName")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.ValidationResultDto.#Message")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.ValidationResultDto.#Id")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.ModelMapper.#InnerMapper")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelBuilder.#InnerModelBuilder")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+ModelBuilder.#.ctor(Microsoft.Restier.Publishers.OData.Model.RestierModelExtender)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+ModelBuilder.#InnerModelBuilder")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+ModelBuilder.#ModelCache")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+ModelMapper.#.ctor(Microsoft.Restier.Publishers.OData.Model.RestierModelExtender)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+ModelMapper.#ModelCache")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+ModelMapper.#InnerModelMapper")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.BaseResult.#EdmType")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoWritePermission")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoReadPermission")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoReadPermission")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoWritePermission")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Ensure.#NotNull`1(System.Nullable`1,System.String)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#EnumerableCastGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#EnumerableToListGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#EnumerableToArrayGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#QueryableAsQueryable")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#QueryableAsQueryableGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#QueryableCountGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#QueryableOfTypeGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#QueryableSelectGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#QueryableSelectManyGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#QueryableWhereGeneric")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#Count(System.Linq.Expressions.Expression,System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#CreateEmptyQueryable(System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#GetCountableQuery`1(System.Linq.IQueryable`1)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#GetEnumerableItemType(System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#GetSelectExpandElementType(System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#OfType(System.Linq.IQueryable,System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#RemoveUnneededStatement(System.Linq.Expressions.MethodCallExpression)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#RemoveSelectExpandStatement(System.Linq.Expressions.MethodCallExpression)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#RemoveAppendWhereStatement(System.Linq.Expressions.Expression)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#Select(System.Linq.IQueryable,System.Linq.Expressions.LambdaExpression)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#SelectMany(System.Linq.IQueryable,System.Linq.Expressions.LambdaExpression,System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#StripQueryMethod(System.Linq.Expressions.Expression,System.String)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#StripPagingOperators`1(System.Linq.IQueryable`1)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelpers.#Where(System.Linq.IQueryable,System.Linq.Expressions.LambdaExpression,System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.TypeExtensions.#GetQualifiedMethod(System.Type,System.String)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.TypeExtensions.#TryGetElementType(System.Type,System.Type&)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.TypeHelper.#GetUnderlyingTypeOrSelf(System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.TypeHelper.#IsDateTime(System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.TypeHelper.#IsDateTimeOffset(System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.TypeHelper.#IsEnum(System.Type)")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.TypeHelper.#IsTimeSpan(System.Type)")] +#endregion + +#region CA2208 Add string message for exception +[assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.QueryableSource.#System.Linq.IQueryProvider.CreateQuery`1(System.Linq.Expressions.Expression)")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.QueryableSource.#System.Linq.IQueryProvider.CreateQuery(System.Linq.Expressions.Expression)")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#ResourceSetTypeMap")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#ResourceTypeKeyPropertiesMap")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Operation.OperationContext.#ParameterValues")] +#endregion + +#region CA1801 Unused Parameters +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#GetQueryableSource`1(System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "arguments", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#GetQueryableSource`1(System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "namespaceName", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#GetQueryableSource`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#GetQueryableSource`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "arguments", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#GetQueryableSource`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Results`1(System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "arguments", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Results`1(System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Result`1(System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "arguments", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Result`1(System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "namespaceName", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Results`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Results`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "arguments", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Results`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "namespaceName", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Result`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "name", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Result`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "arguments", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#Result`1(System.String,System.String,System.Object[])")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "source", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#GetPropertyValue`1(System.Object,System.String)")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "propertyName", Scope = "member", Target = "Microsoft.Restier.Core.DataSourceStub.#GetPropertyValue`1(System.Object,System.String)")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "odataProperties", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController`1.#GetQuery(System.Web.OData.Extensions.HttpRequestMessageProperties)")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes", Scope = "member", Target = "Microsoft.Restier.Providers.EntityFramework.ModelProducer.#GetModelAsync(Microsoft.Restier.Core.Model.ModelContext,System.Threading.CancellationToken)")] +#endregion + +#region CA2000 Dispose objects +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierController.#CreateQueryResponse(System.Linq.IQueryable,Microsoft.OData.Edm.IEdmType,System.Web.OData.Formatter.ETag)")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.RestierExceptionFilterAttribute.#HandleCommonException(System.Web.Http.Filters.HttpActionExecutedContext,System.Boolean,System.Threading.CancellationToken)")] +#endregion + +#region CA1812 Uninstantiated internal classes +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Core.ApiConfiguration")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Core.ConventionBasedChangeSetItemValidator")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Core.PropertyBag")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Providers.EntityFramework.ModelProducer")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Providers.EntityFramework.QueryExpressionProcessor")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+QueryExpressionExpander")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+QueryExpressionSourcer")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.Restier.Publishers.OData.Query.RestierQueryExecutorOptions")] +#endregion + +#endregion + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchChangeSetRequestItem.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchChangeSetRequestItem.cs new file mode 100644 index 0000000..414327c --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchChangeSetRequestItem.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.AspNet.Batch +{ + /// + /// Represents an API request. + /// + public class RestierBatchChangeSetRequestItem : ChangeSetRequestItem + { + /// + /// Initializes a new instance of the class. + /// + /// The request messages. + public RestierBatchChangeSetRequestItem(IEnumerable requests) + : base(requests) + { + } + + /// + /// Asynchronously sends the request. + /// + /// The invoker. + /// The cancellation token. + /// The task object that contains the batch response. + public override async Task SendRequestAsync( + HttpMessageInvoker invoker, + CancellationToken cancellationToken) + { + Ensure.NotNull(invoker, nameof(invoker)); + + var changeSetProperty = new RestierChangeSetProperty(this) + { + ChangeSet = new ChangeSet() + }; + SetChangeSetProperty(changeSetProperty); + + var contentIdToLocationMapping = new Dictionary(); + var responseTasks = new List>>(); + + foreach (var request in Requests) + { + // Since exceptions may occure before the request is sent to RestierController, + // we must catch the exceptions here and call OnChangeSetCompleted, + // so as to avoid deadlock mentioned in Github Issue #82. + var tcs = new TaskCompletionSource(); + var task = + SendMessageAsync(invoker, request, cancellationToken, contentIdToLocationMapping) + .ContinueWith( + t => + { + if (t.Exception != null) + { + var taskEx = (t.Exception.InnerExceptions != null && + t.Exception.InnerExceptions.Count == 1) + ? t.Exception.InnerExceptions.First() + : t.Exception; + changeSetProperty.Exceptions.Add(taskEx); + changeSetProperty.OnChangeSetCompleted(request); + tcs.SetException(taskEx); + } + else + { + tcs.SetResult(t.Result); + } + + return tcs.Task; + }, + cancellationToken); + + responseTasks.Add(task); + } + + // the responseTasks will be complete after: + // - the ChangeSet is submitted + // - the responses are created and + // - the controller actions have returned + await Task.WhenAll(responseTasks).ConfigureAwait(false); + + var responses = new List(); + try + { + foreach (var responseTask in responseTasks) + { + var response = responseTask.Result.Result; + if (response.IsSuccessStatusCode) + { + responses.Add(response); + } + else + { + DisposeResponses(responses); + responses.Clear(); + responses.Add(response); + return new ChangeSetResponseItem(responses); + } + } + } + catch + { + DisposeResponses(responses); + throw; + } + + return new ChangeSetResponseItem(responses); + } + +#pragma warning disable CA1822 // Do not declare static members on generic types + internal async Task SubmitChangeSet(HttpRequestMessage request, ChangeSet changeSet) +#pragma warning restore CA1822 // Do not declare static members on generic types + + { + var requestContainer = request.GetRequestContainer(); + using (var api = requestContainer.GetService()) + { + var submitResults = await api.SubmitAsync(changeSet).ConfigureAwait(false); + } + } + + private static void DisposeResponses(IEnumerable responses) + { + foreach (var response in responses) + { + if (response != null) + { + response.Dispose(); + } + } + } + + private void SetChangeSetProperty(RestierChangeSetProperty changeSetProperty) + { + foreach (var request in Requests) + { + request.SetChangeSet(changeSetProperty); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchHandler.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchHandler.cs new file mode 100644 index 0000000..3f5b0d1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierBatchHandler.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Batch; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.Restier.AspNet.Batch +{ + /// + /// Default implementation of in RESTier. + /// + public class RestierBatchHandler : DefaultODataBatchHandler + { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP server instance. + public RestierBatchHandler(HttpServer httpServer) + : base(httpServer) + { + } + + /// + /// Asynchronously parses the batch requests. + /// + /// The HTTP request that contains the batch requests. + /// The cancellation token. + /// The task object that represents this asynchronous operation. + public override async Task> ParseBatchRequestsAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + Ensure.NotNull(request, nameof(request)); + + var requestContainer = request.CreateRequestContainer(ODataRouteName); + requestContainer.GetRequiredService().BaseUri = GetBaseUri(request); + +#pragma warning disable CA1062 // Validate public arguments + var reader = await request.Content.GetODataMessageReaderAsync(requestContainer, cancellationToken).ConfigureAwait(false); +#pragma warning restore CA1062 // Validate public arguments + request.RegisterForDispose(reader); + + var requests = new List(); + var batchReader = reader.CreateODataBatchReader(); + var batchId = Guid.NewGuid(); + while (batchReader.Read()) + { + if (batchReader.State == ODataBatchReaderState.ChangesetStart) + { + var changeSetRequests = await batchReader.ReadChangeSetRequestAsync(batchId, cancellationToken).ConfigureAwait(false); + foreach (var changeSetRequest in changeSetRequests) + { + changeSetRequest.CopyBatchRequestProperties(request); + changeSetRequest.DeleteRequestContainer(false); + } + + requests.Add(CreateRestierBatchChangeSetRequestItem(changeSetRequests)); + } + else if (batchReader.State == ODataBatchReaderState.Operation) + { + var operationRequest = await batchReader.ReadOperationRequestAsync(batchId, true, cancellationToken).ConfigureAwait(false); + operationRequest.CopyBatchRequestProperties(request); + operationRequest.DeleteRequestContainer(false); + requests.Add(new OperationRequestItem(operationRequest)); + } + } + + return requests; + } + + /// + /// Creates the instance. + /// + /// The list of changeset requests. + /// The created instance. + protected virtual RestierBatchChangeSetRequestItem CreateRestierBatchChangeSetRequestItem(IList changeSetRequests) => + new RestierBatchChangeSetRequestItem(changeSetRequests); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierChangeSetProperty.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierChangeSetProperty.cs new file mode 100644 index 0000000..11fcbba --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Batch/RestierChangeSetProperty.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.AspNet.Batch +{ + /// + /// Represents an API property. + /// TODO need to redesign this class + /// + internal class RestierChangeSetProperty + { + private readonly RestierBatchChangeSetRequestItem changeSetRequestItem; + private readonly TaskCompletionSource changeSetCompletedTaskSource; + private int subRequestCount; + + /// + /// Initializes a new instance of the class. + /// + /// The changeset request item. + public RestierChangeSetProperty(RestierBatchChangeSetRequestItem changeSetRequestItem) + { + this.changeSetRequestItem = changeSetRequestItem; + this.changeSetCompletedTaskSource = new TaskCompletionSource(); + this.subRequestCount = this.changeSetRequestItem.Requests.Count(); + this.Exceptions = new List(); + } + + /// + /// Gets or sets the changeset. + /// + public ChangeSet ChangeSet { get; set; } + + public IList Exceptions { get; set; } + + /// + /// The callback to execute when the changeset is completed. + /// + /// The http request message. + /// The task object that represents this callback execution. + public Task OnChangeSetCompleted(HttpRequestMessage request) + { + if (Interlocked.Decrement(ref this.subRequestCount) == 0) + { + if (Exceptions.Count == 0) + { + this.changeSetRequestItem.SubmitChangeSet(request, this.ChangeSet) + .ContinueWith(t => + { + if (t.Exception != null) + { + var taskEx = + (t.Exception.InnerExceptions != null + && t.Exception.InnerExceptions.Count == 1) + ? t.Exception.InnerExceptions.First() + : t.Exception; + this.changeSetCompletedTaskSource.SetException(taskEx); + } + else + { + this.changeSetCompletedTaskSource.SetResult(true); + } + }); + } + else + { + this.changeSetCompletedTaskSource.SetException(Exceptions); + } + } + + return this.changeSetCompletedTaskSource.Task; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/Extensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/Extensions.cs new file mode 100644 index 0000000..f1712db --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/Extensions.cs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; +using Microsoft.Restier.Core; +using Microsoft.Restier.AspNet.Model; +using System.Net; + +namespace Microsoft.Restier.AspNet +{ + internal static class Extensions + { + private const string PropertyNameOfConcurrencyProperties = "ConcurrencyProperties"; + + private static PropertyInfo etagConcurrencyPropertiesProperty = typeof(ETag).GetProperty( + PropertyNameOfConcurrencyProperties, BindingFlags.NonPublic | BindingFlags.Instance); + + // TODO GithubIssue#485 considering move to API class DI instance + private static readonly ConcurrentDictionary concurrencyCheckFlags + = new ConcurrentDictionary(); + + // TODO GithubIssue#485 considering move to API class DI instance + private static readonly ConcurrentDictionary> + typePropertiesAttributes + = new ConcurrentDictionary>(); + + public static void ApplyTo(this ETag etag, IDictionary propertyValues) + { + if (etag != null) + { + var concurrencyProperties = (IDictionary)etagConcurrencyPropertiesProperty.GetValue(etag); + foreach (var item in concurrencyProperties) + { + propertyValues.Add(item.Key, item.Value); + } + } + } + + public static bool IsConcurrencyCheckEnabled(this IEdmModel model, IEdmEntitySet entitySet) + { + if (concurrencyCheckFlags.TryGetValue(entitySet, out var needCurrencyCheck)) + { + return needCurrencyCheck; + } + + needCurrencyCheck = false; + var annotations = model.FindVocabularyAnnotations( + entitySet, CoreVocabularyModel.ConcurrencyTerm); + var annotation = annotations.FirstOrDefault(); + if (annotation != null) + { + needCurrencyCheck = true; + } + + concurrencyCheckFlags[entitySet] = needCurrencyCheck; + return needCurrencyCheck; + } + + public static IReadOnlyDictionary CreatePropertyDictionary( + this Delta entity, IEdmStructuredType edmType, ApiBase api, bool isCreation) + { + var propertiesAttributes = RetrievePropertiesAttributes(edmType, api); + + var propertyValues = new Dictionary(); + foreach (var propertyName in entity.GetChangedPropertyNames()) + { + if (propertiesAttributes != null && propertiesAttributes.TryGetValue(propertyName, out var attributes)) + { + if ((isCreation && (attributes & PropertyAttributes.IgnoreForCreation) != PropertyAttributes.None) + || (!isCreation && (attributes & PropertyAttributes.IgnoreForUpdate) != PropertyAttributes.None)) + { + // Will not get the properties for update or creation + continue; + } + } + + if (entity.TryGetPropertyValue(propertyName, out var value)) + { + if (value is EdmComplexObject complexObj) + { + value = CreatePropertyDictionary(complexObj, complexObj.ActualEdmType, api, isCreation); + } + + //RWM: Other entities are not allowed in the payload until we support Delta payloads. + if (value is EdmEntityObject entityObj) + { + // TODO: RWM: Turn this message into a language resource. + throw new StatusCodeException(HttpStatusCode.BadRequest, "Navigation Properties were also present in the payload. Please remove related entities from your request and try again."); + } + + propertyValues.Add(propertyName, value); + } + } + + return propertyValues; + } + + public static IDictionary RetrievePropertiesAttributes( + IEdmStructuredType edmType, ApiBase api) + { + if (typePropertiesAttributes.TryGetValue(edmType, out var propertiesAttributes)) + { + return propertiesAttributes; + } + + var model = api.GetModelAsync().Result; + foreach (var property in edmType.DeclaredProperties) + { + var annotations = model.FindVocabularyAnnotations(property); + var attributes = PropertyAttributes.None; + foreach (var annotation in annotations) + { + if (!(annotation is EdmVocabularyAnnotation valueAnnotation)) + { + continue; + } + + if (valueAnnotation.Term.IsSameTerm(CoreVocabularyModel.ImmutableTerm)) + { + if (valueAnnotation.Value is EdmBooleanConstant value && value.Value) + { + attributes |= PropertyAttributes.IgnoreForUpdate; + } + } + + if (valueAnnotation.Term.IsSameTerm(CoreVocabularyModel.ComputedTerm)) + { + if (valueAnnotation.Value is EdmBooleanConstant value && value.Value) + { + attributes |= PropertyAttributes.IgnoreForUpdate; + attributes |= PropertyAttributes.IgnoreForCreation; + } + } + + // TODO add permission annotation check + // CoreVocabularyModel has no permission yet, will add with #480 + } + + // Add property attributes to the dictionary + if (attributes != PropertyAttributes.None) + { + if (propertiesAttributes == null) + { + propertiesAttributes = new Dictionary(); + typePropertiesAttributes[edmType] = propertiesAttributes; + } + + propertiesAttributes.Add(property.Name, attributes); + } + } + + return propertiesAttributes; + } + + public static IEdmTypeReference GetReturnTypeReference(this Type type, IEdmModel model) + { + // In case it is a nullable type, get the underlying type + type = TypeHelper.GetUnderlyingTypeOrSelf(type); + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)) + { + // if the action returns a Task, map that to just be returning a T + type = type.GetGenericArguments()[0]; + } + else if (type == typeof(Task)) + { + // if the action returns a concrete Task, map that to being a void return type. + type = typeof(void); + } + + return EdmHelpers.GetTypeReference(type, model); + } + + public static bool IsSameTerm(this IEdmTerm sourceTerm, IEdmTerm targetTerm) + { + if (sourceTerm.Namespace == targetTerm.Namespace && sourceTerm.Name == targetTerm.Name) + { + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs new file mode 100644 index 0000000..54c06f6 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData; +using Microsoft.Restier.AspNet; +using Microsoft.Restier.AspNet.Batch; +using Microsoft.Restier.Core; +using ServiceLifetime = Microsoft.OData.ServiceLifetime; + + +namespace System.Web.Http +{ + /// + /// Offers a collection of extension methods to . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class HttpConfigurationExtensions + { +#pragma warning disable CA1823 // Do not declare static members on generic types + private const string RootContainerKey = "Microsoft.AspNet.OData.RootContainerMappingsKey"; +#pragma warning restore CA1823 // Do not declare static members on generic types + + /// TODO GitHubIssue#51 : Support model lazy loading + /// + /// Maps the API routes to the RestierController. + /// + /// The user API. + /// The instance. + /// The name of the route. + /// The prefix of the route. + /// The handler for batch requests. + /// The task object containing the resulted instance. + public static Task MapRestierRoute( + this HttpConfiguration config, + string routeName, + string routePrefix, + RestierBatchHandler batchHandler = null) + where TApi : ApiBase + { + // This will be added a service to callback stored in ApiConfiguration + // Callback is called by ApiBase.AddApiServices method to add real services. + ApiBase.AddPublisherServices(typeof(TApi), services => + { + services.AddODataServices(); + }); + + IContainerBuilder func() => new RestierContainerBuilder(typeof(TApi)); + config.UseCustomContainerBuilder(func); + + var conventions = CreateRestierRoutingConventions(config, routeName); + if (batchHandler != null) + { + batchHandler.ODataRouteName = routeName; + } + + void configureAction(IContainerBuilder builder) => builder + .AddService>(ServiceLifetime.Singleton, sp => conventions) + .AddService(ServiceLifetime.Singleton, sp => batchHandler); + + var route = config.MapODataServiceRoute(routeName, routePrefix, configureAction); + + return Task.FromResult(route); + } + + /// + /// Creates the default routing conventions. + /// + /// The instance. + /// The name of the route. + /// The routing conventions created. + private static IList CreateRestierRoutingConventions( + this HttpConfiguration config, string routeName) + { + var conventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, config); + var index = 0; + for (; index < conventions.Count; index++) + { + if (conventions[index] is AttributeRoutingConvention attributeRouting) + { + break; + } + } + + conventions.Insert(index + 1, new RestierRoutingConvention()); + return conventions; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpRequestMessageExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpRequestMessageExtensions.cs new file mode 100644 index 0000000..4a29e19 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpRequestMessageExtensions.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Net.Http; +using Microsoft.Restier.AspNet.Batch; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Offers a collection of extension methods to . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class HttpRequestMessageExtensions + { + private const string ChangeSetKey = "Microsoft.Restier.Submit.ChangeSet"; + + /// + /// Sets the to the . + /// + /// The HTTP request. + /// The change set to be set. + public static void SetChangeSet(this HttpRequestMessage request, RestierChangeSetProperty changeSetProperty) + { + Ensure.NotNull(request, nameof(request)); + request.Properties.Add(ChangeSetKey, changeSetProperty); + } + + /// + /// Gets the from the . + /// + /// The HTTP request. + /// The . + public static RestierChangeSetProperty GetChangeSet(this HttpRequestMessage request) + { + Ensure.NotNull(request, nameof(request)); + + if (request.Properties.TryGetValue(ChangeSetKey, out var value)) + { + return value as RestierChangeSetProperty; + } + + return null; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/ServiceCollectionExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f186b8e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Query; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.OData; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Operation; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.AspNet.Formatter; +using Microsoft.Restier.AspNet.Model; +using Microsoft.Restier.AspNet.Operation; +using Microsoft.Restier.AspNet.Query; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Contains extension methods of . + /// This method is used to add odata publisher service into container. + /// + public static class ServiceCollectionExtensions + { + /// + /// This method is used to add odata publisher service into container. + /// + /// The Api type. + /// The . + /// Current + //[CLSCompliant(false)] + public static IServiceCollection AddODataServices(this IServiceCollection services) + { + if (services.HasService()) + { + // Avoid applying multiple times to a same service collection. + return services; + } + + services.AddService(); + RestierModelExtender.ApplyTo(services, typeof(T)); + RestierOperationModelBuilder.ApplyTo(services, typeof(T)); + + // Add OData Query Settings and validation settings + Func querySettingFactory = (sp) => new ODataQuerySettings + { + HandleNullPropagation = HandleNullPropagationOption.False, + PageSize = null, // no support for server enforced PageSize, yet + }; + + services.AddSingleton(typeof(ODataQuerySettings), querySettingFactory); + services.AddSingleton(); + + // Make serializer and deserializer provider as DI services + // WebApi OData service provider will be added first, need to overwrite. + services.AddSingleton(); + services.AddSingleton(); + + services.TryAddSingleton(); + services.AddSingleton(); + + services.AddService(); + + return services.AddScoped() + .AddService(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/RestierExceptionFilterAttribute.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/RestierExceptionFilterAttribute.cs new file mode 100644 index 0000000..1e4d309 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/RestierExceptionFilterAttribute.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Filters; +using System.Web.Http.Results; +using Microsoft.OData; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.AspNet +{ + /// + /// An ExceptionFilter that is capable of serializing well-known exceptions to the client. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] + internal sealed class RestierExceptionFilterAttribute : ExceptionFilterAttribute + { + private static readonly List Handlers = new List + { + HandleChangeSetValidationException, + HandleCommonException + }; + + private delegate Task ExceptionHandlerDelegate( + HttpActionExecutedContext context, + bool useVerboseErros, + CancellationToken cancellationToken); + + /// + /// The callback to execute when exception occurs. + /// + /// The context where the action is executed. + /// The cancellation token. + /// The task object that represents the callback execution. + public override async Task OnExceptionAsync( + HttpActionExecutedContext actionExecutedContext, + CancellationToken cancellationToken) + { + var config = actionExecutedContext.Request.GetConfiguration(); + var useVerboseErros = config.IncludeErrorDetailPolicy == IncludeErrorDetailPolicy.Always || + (actionExecutedContext.Request.RequestUri.Host.ToLower().Contains("localhost") && config.IncludeErrorDetailPolicy == IncludeErrorDetailPolicy.LocalOnly); + + foreach (var handler in Handlers) + { + var result = await handler.Invoke(actionExecutedContext, useVerboseErros, cancellationToken).ConfigureAwait(false); + + if (result != null) + { + actionExecutedContext.Response = result; + return; + } + } + } + + private static async Task HandleChangeSetValidationException( + HttpActionExecutedContext context, + bool useVerboseErros, + CancellationToken cancellationToken) + { + if (context.Exception is ChangeSetValidationException validationException) + { + var exceptionResult = new NegotiatedContentResult>( + HttpStatusCode.BadRequest, + validationException.ValidationResults.Select(r => new ValidationResultDto(r)), + context.ActionContext.RequestContext.Configuration.Services.GetContentNegotiator(), + context.Request, + new MediaTypeFormatterCollection()); + return await exceptionResult.ExecuteAsync(cancellationToken).ConfigureAwait(false); + } + + return null; + } + + private static Task HandleCommonException( + HttpActionExecutedContext context, + bool useVerboseErros, + CancellationToken cancellationToken) + { + var exception = context.Exception.Demystify(); + if (exception is AggregateException) + { + // In async call, the exception will be wrapped as AggregateException + exception = exception.InnerException.Demystify(); + } + + if (exception == null) + { + return Task.FromResult(null); + } + + HttpStatusCode code; + switch (true) + { + case true when exception is StatusCodeException: + code = (exception as StatusCodeException).StatusCode; + break; + case true when exception is ODataException: + code = HttpStatusCode.BadRequest; + break; + case true when exception is SecurityException: + code = HttpStatusCode.Forbidden; + break; + case true when exception is NotImplementedException: + code = HttpStatusCode.NotImplemented; + break; + default: + code = HttpStatusCode.InternalServerError; + break; + } + + // When exception occured in a ChangeSet request, + // exception must be handled in OnChangeSetCompleted + // to avoid deadlock in Github Issue #82. + var changeSetProperty = context.Request.GetChangeSet(); + if (changeSetProperty != null) + { + changeSetProperty.Exceptions.Add(exception); + changeSetProperty.OnChangeSetCompleted(context.Request); + } + + if (code != HttpStatusCode.Unused) + { + if (useVerboseErros) + { + return Task.FromResult(context.Request.CreateErrorResponse(code, exception)); + } + + return Task.FromResult(context.Request.CreateErrorResponse(code, exception.Message)); + } + + return Task.FromResult(null); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/ValidationResultDto.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/ValidationResultDto.cs new file mode 100644 index 0000000..0a7f6cc --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Filters/ValidationResultDto.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Tracing; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.AspNet +{ + /// + /// A data transfer object that is used to serialize ValidationResult instances to the client. + /// + internal class ValidationResultDto + { + private ChangeSetItemValidationResult result; + + /// + /// Initializes a new instance of the class. + /// + /// The validation result. + public ValidationResultDto(ChangeSetItemValidationResult result) + { + this.result = result; + } + + /// + /// Gets the id of the instance. + /// + public string Id + { + get { return this.result.Id; } + } + + /// + /// Gets the message of the instance. + /// + public string Message + { + get { return this.result.Message; } + } + + /// + /// Gets the property name of the instance. + /// + public string PropertyName + { + get { return this.result.PropertyName; } + } + + // TODO GitHubIssue#40 : Implement Target for ValidationResultDTO + ////public string Target + ////{ + //// get { return this.result.Target.ToString(); } + ////} + + /// + /// Gets the string that represents the severity of the instance. + /// + public string Severity + { + get { return Enum.GetName(typeof(EventLevel), this.result.Severity); } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DefaultRestierDeserializerProvider.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DefaultRestierDeserializerProvider.cs new file mode 100644 index 0000000..1123610 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DefaultRestierDeserializerProvider.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.OData.Edm; +using System; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The default deserializer provider. + /// + public class DefaultRestierDeserializerProvider : DefaultODataDeserializerProvider + { + private readonly RestierEnumDeserializer enumDeserializer; + + /// + /// Initializes a new instance of the class. + /// + /// The container to get the service + public DefaultRestierDeserializerProvider(IServiceProvider rootContainer) : base(rootContainer) => enumDeserializer = new RestierEnumDeserializer(); + + /// + public override ODataEdmTypeDeserializer GetEdmTypeDeserializer(IEdmTypeReference edmType) + { + if (edmType.IsEnum()) + { + return enumDeserializer; + } + + return base.GetEdmTypeDeserializer(edmType); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DeserializationHelpers.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DeserializationHelpers.cs new file mode 100644 index 0000000..aa0cf2e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/DeserializationHelpers.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.OData.Edm; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Net.Http; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// Get clr type from payload. + /// + internal static class DeserializationHelpers + { + internal static object ConvertValue( + object odataValue, + string parameterName, + Type expectedReturnType, + IEdmTypeReference propertyType, + IEdmModel model, + HttpRequestMessage request, + IServiceProvider serviceProvider) + { + var readContext = new ODataDeserializerContext + { + Model = model, + Request = request + }; + + var returnValue = ODataModelBinderConverter.Convert(odataValue, propertyType, expectedReturnType, parameterName, readContext, serviceProvider); + + if (!propertyType.IsCollection()) + { + return returnValue; + } + + return ConvertCollectionType(returnValue, expectedReturnType); + } + + internal static object ConvertCollectionType(object collectionResult, Type expectedReturnType) + { + if (collectionResult == null) + { + return null; + } + + var genericType = expectedReturnType.FindGenericType(typeof(ICollection<>)); + if (genericType != null || expectedReturnType.IsArray) + { + var elementClrType = expectedReturnType.GetElementType() ?? + expectedReturnType.GenericTypeArguments[0]; + var castMethodInfo = ExpressionHelperMethods.EnumerableCastGeneric.MakeGenericMethod(elementClrType); + var castedResult = castMethodInfo.Invoke(null, new object[] { collectionResult }); + + if (expectedReturnType.IsArray) + { + var toArrayMethodInfo = ExpressionHelperMethods.EnumerableToArrayGeneric + .MakeGenericMethod(elementClrType); + var arrayResult = toArrayMethodInfo.Invoke(null, new object[] { castedResult }); + return arrayResult; + } + else if (genericType != null) + { + var toListMethodInfo = ExpressionHelperMethods.EnumerableToListGeneric + .MakeGenericMethod(elementClrType); + var listResult = toListMethodInfo.Invoke(null, new object[] { castedResult }); + return listResult; + } + } + + // There is case where expected type is IEnumerable but actual type is IEnumerable, + // need some convert + genericType = collectionResult.GetType().FindGenericType(typeof(IEnumerable<>)); + var returnGenericType = expectedReturnType.FindGenericType(typeof(IEnumerable<>)); + if (genericType != null && returnGenericType != null) + { + var actualElementType = genericType.GenericTypeArguments[0]; + var expectElementType = returnGenericType.GenericTypeArguments[0]; + if (actualElementType != expectedReturnType) + { + var castMethodInfo = ExpressionHelperMethods + .EnumerableCastGeneric.MakeGenericMethod(expectElementType); + var castedResult = castMethodInfo.Invoke(null, new object[] { collectionResult }); + return castedResult; + } + } + + // It means return type is IEnumerable<> or raw type is passed in value is single value + return collectionResult; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/RestierEnumDeserializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/RestierEnumDeserializer.cs new file mode 100644 index 0000000..a6aa837 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Deserialization/RestierEnumDeserializer.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The serializer for enum result. + /// + internal class RestierEnumDeserializer : ODataEnumDeserializer + { + private ODataEnumDeserializer enumDeserializer = new ODataEnumDeserializer(); + + /// + public override object Read( + ODataMessageReader messageReader, + Type type, + ODataDeserializerContext readContext) + { + return enumDeserializer.Read(messageReader, type, readContext); + } + + /// + public override object ReadInline( + object item, + IEdmTypeReference edmType, + ODataDeserializerContext readContext) + { + var result = enumDeserializer.ReadInline(item, edmType, readContext); + + var edmEnumObject = result as EdmEnumObject; + if (edmEnumObject != null) + { + return edmEnumObject.Value; + } + + return result; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/DefaultRestierSerializerProvider.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/DefaultRestierSerializerProvider.cs new file mode 100644 index 0000000..35f8938 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/DefaultRestierSerializerProvider.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The default serializer provider. + /// + public class DefaultRestierSerializerProvider : DefaultODataSerializerProvider + { + private RestierResourceSetSerializer resourceSetSerializer; + private RestierPrimitiveSerializer primitiveSerializer; + private RestierRawSerializer rawSerializer; + private RestierResourceSerializer resourceSerializer; + private RestierCollectionSerializer collectionSerializer; + private RestierEnumSerializer enumSerializer; + + /// + /// Initializes a new instance of the class. + /// + /// The container to get the service + public DefaultRestierSerializerProvider(IServiceProvider rootContainer) : base(rootContainer) + { + this.resourceSetSerializer = new RestierResourceSetSerializer(this); + this.primitiveSerializer = new RestierPrimitiveSerializer(); + this.rawSerializer = new RestierRawSerializer(); + this.resourceSerializer = new RestierResourceSerializer(this); + this.collectionSerializer = new RestierCollectionSerializer(this); + this.enumSerializer = new RestierEnumSerializer(this); + } + + /// + /// Gets the serializer for the given result type. + /// + /// The type of result to serialize. + /// The HTTP request. + /// The serializer instance. + public override ODataSerializer GetODataPayloadSerializer( + Type type, + HttpRequestMessage request) + { + ODataSerializer serializer = null; + if (type == typeof(ResourceSetResult)) + { + serializer = this.resourceSetSerializer; + } + else if (type == typeof(PrimitiveResult)) + { + serializer = this.primitiveSerializer; + } + else if (type == typeof(RawResult)) + { + serializer = this.rawSerializer; + } + else if (type == typeof(ComplexResult)) + { + serializer = this.resourceSerializer; + } + else if (type == typeof(NonResourceCollectionResult)) + { + serializer = this.collectionSerializer; + } + else if (type == typeof(EnumResult)) + { + serializer = this.enumSerializer; + } + else + { + serializer = base.GetODataPayloadSerializer(type, request); + } + + return serializer; + } + + /// + /// Gets the serializer for the given EDM type reference. + /// + /// The EDM type reference involved in the serializer. + /// The serializer instance. + public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType) + { + if (edmType.IsComplex()) + { + return this.resourceSerializer; + } + + if (edmType.IsPrimitive()) + { + return this.primitiveSerializer; + } + + if (edmType.IsEnum()) + { + return this.enumSerializer; + } + + if (edmType.IsCollection()) + { + var collectionType = edmType.AsCollection(); + if (collectionType.Definition.IsDeltaFeed()) + { + return base.GetEdmTypeSerializer(edmType); + } + else if (collectionType.ElementType().IsEntity() || collectionType.ElementType().IsComplex()) + { + return this.resourceSetSerializer; + } + + return this.collectionSerializer; + } + + return base.GetEdmTypeSerializer(edmType); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierCollectionSerializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierCollectionSerializer.cs new file mode 100644 index 0000000..8195d96 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierCollectionSerializer.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The serializer for collection result. + /// + public class RestierCollectionSerializer : ODataCollectionSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// The serializer provider. + public RestierCollectionSerializer(ODataSerializerProvider provider) + : base(provider) + { + } + + /// + /// Writes the complex result to the response message. + /// + /// The collection result to write. + /// The type of the collection. + /// The message writer. + /// The writing context. + public override void WriteObject( + object graph, + Type type, + ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + NonResourceCollectionResult collectionResult = graph as NonResourceCollectionResult; + if (collectionResult != null) + { + graph = collectionResult.Query; + type = collectionResult.Type; + } + + base.WriteObject(graph, type, messageWriter, writeContext); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierEnumSerializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierEnumSerializer.cs new file mode 100644 index 0000000..ed00e2f --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierEnumSerializer.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The serializer for enum result. + /// + public class RestierEnumSerializer : ODataEnumSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// The serializer provider. + public RestierEnumSerializer(ODataSerializerProvider provider) : base(provider) + { + } + + /// + /// Writes the enum result to the response message. + /// + /// The enum result to write. + /// The type of the enum. + /// The message writer. + /// The writing context. + public override void WriteObject( + object graph, + Type type, + ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + EnumResult enumResult = graph as EnumResult; + if (enumResult != null) + { + graph = enumResult.Result; + type = enumResult.Type; + } + + base.WriteObject(graph, type, messageWriter, writeContext); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierPrimitiveSerializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierPrimitiveSerializer.cs new file mode 100644 index 0000000..938f544 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierPrimitiveSerializer.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The serializer for primitive result. + /// + public class RestierPrimitiveSerializer : ODataPrimitiveSerializer + { + /// + /// Writes the entity result to the response message. + /// + /// The entity result to write. + /// The type of the entity. + /// The message writer. + /// The writing context. + public override void WriteObject( + object graph, + Type type, + ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + if (graph is PrimitiveResult primitiveResult) + { + graph = primitiveResult.Result; + type = primitiveResult.Type; + } + + if (writeContext != null) + { + graph = ConvertToPayloadValue(graph, writeContext); + } + + base.WriteObject(graph, type, messageWriter, writeContext); + } + + /// + /// Creates an for the object represented by . + /// + /// The primitive value. + /// The EDM primitive type of the value. + /// The serializer write context. + /// The created . + public override ODataPrimitiveValue CreateODataPrimitiveValue( + object graph, + IEdmPrimitiveTypeReference primitiveType, + ODataSerializerContext writeContext) + { + // The EDM type of the "graph" would override the EDM type of the property when + // OData Web API infers the primitiveType. Thus for "graph" of System.DateTime, + // the primitiveType is always Edm.DateTimeOffset. + // + // In EF, System.DateTime is used for SqlDate, SqlDateTime and SqlDateTime2. + // All of them have no time zone information thus it is safe to clear the time + // zone when converting the "graph" to a DateTimeOffset. + if (primitiveType != null && primitiveType.IsDateTimeOffset() && graph is DateTime) + { + // If DateTime.Kind equals Local, offset should equal the offset of the system's local time zone + if (((DateTime)graph).Kind == DateTimeKind.Local) + { + graph = new DateTimeOffset((DateTime)graph, TimeZoneInfo.Local.GetUtcOffset((DateTime)graph)); + } + else + { + graph = new DateTimeOffset((DateTime)graph, TimeSpan.Zero); + } + } + + return base.CreateODataPrimitiveValue(graph, primitiveType, writeContext); + } + + internal static object ConvertToPayloadValue(object value, ODataSerializerContext writeContext) + { + Ensure.NotNull(writeContext, nameof(writeContext)); + + IEdmTypeReference edmTypeReference = null; + if (writeContext.Path != null) + { + // Try to get the EDM type of the value from the path. + var edmType = writeContext.Path.EdmType as IEdmPrimitiveType; + if (edmType != null) + { + // Just created to call the payload value converter. + edmTypeReference = new EdmPrimitiveTypeReference(edmType, true /*isNullable*/); + } + } + + var payloadValueConverter + = writeContext.Request.GetRequestContainer().GetService(); + return payloadValueConverter.ConvertToPayloadValue(value, edmTypeReference); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierRawSerializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierRawSerializer.cs new file mode 100644 index 0000000..5f09c6e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierRawSerializer.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The serializer for raw result. + /// + public class RestierRawSerializer : ODataRawValueSerializer + { + /// + /// Writes the entity result to the response message. + /// + /// The entity result to write. + /// The type of the entity. + /// The message writer. + /// The writing context. + public override void WriteObject( + object graph, + Type type, + ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + RawResult rawResult = graph as RawResult; + if (rawResult != null) + { + graph = rawResult.Result; + type = rawResult.Type; + } + + if (writeContext != null) + { + graph = RestierPrimitiveSerializer.ConvertToPayloadValue(graph, writeContext); + } + + if (graph == null) + { + // This is to make ODataRawValueSerializer happily serialize null value. + graph = string.Empty; + } + + base.WriteObject(graph, type, messageWriter, writeContext); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSerializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSerializer.cs new file mode 100644 index 0000000..fbe1147 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSerializer.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The serializer for resource result, and now for complex only, + /// for entity type, WebApi OData resource serializer will be used. + /// + public class RestierResourceSerializer : ODataResourceSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// The serializer provider. + public RestierResourceSerializer(ODataSerializerProvider provider) + : base(provider) + { + } + + /// + /// Writes the complex result to the response message. + /// + /// The complex result to write. + /// The type of the complex. + /// The message writer. + /// The writing context. + public override void WriteObject( + object graph, + Type type, + ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + ComplexResult complexResult = graph as ComplexResult; + if (complexResult != null) + { + graph = complexResult.Result; + type = complexResult.Type; + } + + base.WriteObject(graph, type, messageWriter, writeContext); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSetSerializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSetSerializer.cs new file mode 100644 index 0000000..1efba87 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Formatter/Serialization/RestierResourceSetSerializer.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet.Formatter +{ + /// + /// The serializer for resource set result. + /// + public class RestierResourceSetSerializer : ODataResourceSetSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// The serializer provider. + public RestierResourceSetSerializer(ODataSerializerProvider provider) + : base(provider) + { + } + + /// + /// Writes the entity collection results to the response message. + /// + /// The entity collection results. + /// The type of the entities. + /// The message writer. + /// The writing context. + public override void WriteObject( + object graph, + Type type, + ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + Ensure.NotNull(messageWriter, nameof(messageWriter)); + Ensure.NotNull(writeContext, nameof(writeContext)); + + if (graph is ResourceSetResult collectionResult) + { + graph = collectionResult.Query; + type = collectionResult.Type; + +#pragma warning disable CA1062 // Validate public arguments + if (TryWriteAggregationResult(graph, type, messageWriter, writeContext, collectionResult.EdmType)) +#pragma warning restore CA1062 // Validate public arguments + + { + return; + } + } + + base.WriteObject(graph, type, messageWriter, writeContext); + } + + private bool TryWriteAggregationResult( + object graph, + Type type, + ODataMessageWriter messageWriter, + ODataSerializerContext writeContext, + IEdmTypeReference resourceSetType) + { + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + var elementType = resourceSetType.AsCollection().ElementType(); + if (elementType.IsEntity()) + { + var entitySet = writeContext.NavigationSource as IEdmEntitySetBase; + var entityType = elementType.AsEntity(); + var writer = messageWriter.CreateODataResourceSetWriter(entitySet, entityType.EntityDefinition()); + WriteObjectInline(graph, resourceSetType, writer, writeContext); + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj new file mode 100644 index 0000000..d8890b1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj @@ -0,0 +1,53 @@ + + + + Restier is a framework for building convention-based, secure, queryable APIs with ASP.NET. This package contains runtime components for integrating with ASP.NET Web API 2.2 to automatically handle incoming requests. + + $(Summary) + + Commonly used types: + Microsoft.Restier.AspNet.RestierBatchHandler + + $(PackageTags);webapi;batch + + net462;net472 + + + + + <_Parameter1>Microsoft.Restier.Tests.AspNet + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Microsoft.Restier.AspNet + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/EdmHelpers.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/EdmHelpers.cs new file mode 100644 index 0000000..1c00444 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/EdmHelpers.cs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; + + +namespace Microsoft.Restier.AspNet.Model +{ + /// + /// This class contains some common extension methods for Edm + /// + public static class EdmHelpers + { + private const string DefaultEntityContainerName = "DefaultContainer"; + + /// + /// The type to get the primitive type reference + /// + /// The clr type to get edm type reference + /// The edm type reference for the clr type + public static EdmTypeReference GetPrimitiveTypeReference(this Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + // Only handle primitive type right now + var primitiveTypeKind = EdmHelpers.GetPrimitiveTypeKind(type, out var isNullable); + + if (!primitiveTypeKind.HasValue) + { + return null; + } + + return new EdmPrimitiveTypeReference( + EdmCoreModel.Instance.GetPrimitiveType(primitiveTypeKind.Value), + isNullable); + } + + /// + /// Get the clr type for a specified edm type + /// + /// The edm type to get clr type + /// The provider to get service from DI container + /// The clr type + public static Type GetClrType(this IEdmType edmType, IServiceProvider serviceProvider) + { + var edmModel = serviceProvider.GetService(); + + var annotation = edmModel.GetAnnotationValue(edmType); + if (annotation != null) + { + return annotation.ClrType; + } + + throw new NotSupportedException(string.Format( + CultureInfo.InvariantCulture, + Resources.ElementTypeNotFound, + edmType.FullTypeName())); + } + + /// + /// Get the edm type reference for a clr type + /// + /// The clr type + /// The Edm model + /// The Edm type reference + public static IEdmTypeReference GetTypeReference(this Type type, IEdmModel model) + { + if (type == null || model == null) + { + return null; + } + + if (type.TryGetElementType(out var elementType)) + { + return EdmCoreModel.GetCollection(GetTypeReference(elementType, model)); + } + + var edmType = model.FindDeclaredType(type.FullName); + + if (edmType is IEdmEnumType enumType) + { + return new EdmEnumTypeReference(enumType, true); + } + + if (edmType is IEdmComplexType complexType) + { + return new EdmComplexTypeReference(complexType, true); + } + + if (edmType is IEdmEntityType entityType) + { + return new EdmEntityTypeReference(entityType, true); + } + + return type.GetPrimitiveTypeReference(); + } + + internal static EdmEntityContainer EnsureEntityContainer(this EdmModel model, Type apiType) + { + var container = (EdmEntityContainer)model.EntityContainer; + if (container == null) + { + container = new EdmEntityContainer(apiType.Namespace, DefaultEntityContainerName); + model.AddElement(container); + } + + return container; + } + + internal static IEdmEntitySet FindDeclaredEntitySetByTypeReference( + this IEdmModel model, IEdmTypeReference typeReference) + { + if (!typeReference.TryGetElementTypeReference(out var elementTypeReference)) + { + elementTypeReference = typeReference; + } + + if (!elementTypeReference.IsEntity()) + { + return null; + } + + return model.EntityContainer.EntitySets() + .SingleOrDefault(e => e.EntityType().FullTypeName() == elementTypeReference.FullName()); + } + + private static bool TryGetElementTypeReference( + this IEdmTypeReference typeReference, out IEdmTypeReference elementTypeReference) + { + if (!typeReference.IsCollection()) + { + elementTypeReference = null; + return false; + } + + elementTypeReference = typeReference.AsCollection().ElementType(); + return true; + } + + private static EdmPrimitiveTypeKind? GetPrimitiveTypeKind(Type type, out bool isNullable) + { + isNullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + if (isNullable) + { + type = type.GetGenericArguments()[0]; + } + + if (type == typeof(string)) + { + return EdmPrimitiveTypeKind.String; + } + + if (type == typeof(byte[])) + { + return EdmPrimitiveTypeKind.Binary; + } + + if (type == typeof(bool)) + { + return EdmPrimitiveTypeKind.Boolean; + } + + if (type == typeof(byte)) + { + return EdmPrimitiveTypeKind.Byte; + } + + if (type == typeof(DateTime)) + { + // TODO GitHubIssue#49 : how to map DateTime's in OData v4? there is no Edm.DateTime type anymore + return null; + } + + if (type == typeof(DateTimeOffset)) + { + return EdmPrimitiveTypeKind.DateTimeOffset; + } + + if (type == typeof(decimal)) + { + return EdmPrimitiveTypeKind.Decimal; + } + + if (type == typeof(double)) + { + return EdmPrimitiveTypeKind.Double; + } + + if (type == typeof(Guid)) + { + return EdmPrimitiveTypeKind.Guid; + } + + if (type == typeof(short)) + { + return EdmPrimitiveTypeKind.Int16; + } + + if (type == typeof(int)) + { + return EdmPrimitiveTypeKind.Int32; + } + + if (type == typeof(long)) + { + return EdmPrimitiveTypeKind.Int64; + } + + if (type == typeof(sbyte)) + { + return EdmPrimitiveTypeKind.SByte; + } + + if (type == typeof(float)) + { + return EdmPrimitiveTypeKind.Single; + } + + if (type == typeof(TimeSpan)) + { + // TODO GitHubIssue#49 : this should really be TimeOfDay, + // but EdmPrimitiveTypeKind doesn't support that type. + ////return EdmPrimitiveTypeKind.TimeOfDay; + return EdmPrimitiveTypeKind.Duration; + } + + if (type == typeof(void)) + { + return null; + } + + throw new NotSupportedException(string.Format( + CultureInfo.InvariantCulture, Resources.NotSupportedType, type.FullName)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs new file mode 100644 index 0000000..8753de9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; + +namespace Microsoft.Restier.AspNet.Model +{ + /// + /// Represents a model mapper based on a DbContext. + /// + internal class ModelMapper : IModelMapper + { + internal IModelMapper InnerMapper { get; set; } + + /// + /// Tries to get the relevant type of an entity + /// set, singleton, or composable function import. + /// + /// + /// The context for model mapper. + /// + /// + /// The name of an entity set, singleton or composable function import. + /// + /// + /// When this method returns, provides the + /// relevant type of the queryable source. + /// + /// + /// true if the relevant type was + /// provided; otherwise, false. + /// + public bool TryGetRelevantType( + ModelContext context, + string name, + out Type relevantType) + { + // Cannot await as cannot make method async + var model = context.GetApiService(); + var element = model.EntityContainer.Elements.Where(e => e.Name == name).FirstOrDefault(); + + if (element != null) + { + IEdmType entityType = null; + var entitySet = element as EdmEntitySet; + if (entitySet != null) + { + var entitySetType = entitySet.Type as EdmCollectionType; + entityType = entitySetType.ElementType.Definition; + } + else + { + var singleton = element as EdmSingleton; + if (singleton != null) + { + entityType = singleton.Type; + } + } + + if (entityType != null) + { + ClrTypeAnnotation annotation = model.GetAnnotationValue(entityType); + if (annotation != null) + { + relevantType = annotation.ClrType; + return true; + } + } + } + + return InnerMapper.TryGetRelevantType(context, name, out relevantType); + } + + /// + /// Tries to get the relevant type of a composable function. + /// + /// + /// The context for model mapper. + /// + /// + /// The name of a namespace containing a composable function. + /// + /// + /// The name of composable function. + /// + /// + /// When this method returns, provides the + /// relevant type of the composable function. + /// + /// + /// true if the relevant type was + /// provided; otherwise, false. + /// + public bool TryGetRelevantType( + ModelContext context, + string namespaceName, + string name, + out Type relevantType) + { + // TODO GitHubIssue#39 : support composable function imports + relevantType = null; + return false; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/OperationAttribute.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/OperationAttribute.cs new file mode 100644 index 0000000..8b7abf0 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/OperationAttribute.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.AspNet.Model +{ + /// + /// Attribute that indicates a method is an OData Operation (Function or Action). + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class OperationAttribute : Attribute + { + /// + /// Gets or sets the namespace of the operation. + /// The default value will be same as the namespace of entity type. + /// + public string Namespace { get; set; } + + /// + /// Gets or sets the entity set associated with the operation result. + /// Only need to be set for unbound operations + /// when there are multiple entity sets with same entity type as result entity type. + /// + public string EntitySet { get; set; } + + /// + /// Gets or sets a value indicating whether the function is composable. + /// The default value is false. + /// + public bool IsComposable { get; set; } + + /// + /// Gets or sets a value indicating whether the operation is bound or not. + /// If it is set to true, then no matter what's the first parameter, it will be considered as bound. + /// If it is set to false, then no matter what's the first parameter, it will be considered as unbound. + /// The default value is false. + /// + public bool IsBound { get; set; } + + /// + /// Gets or sets a value indicating whether the operation has side effects. + /// If an operation does not have side effect, it means it is a function, + /// and need to use HTTP Get to call function. + /// If an operation has side effect, it means it is an action, and need to use HTTP post to call action. + /// The default value is false. + /// + public bool HasSideEffects { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/PropertyAttributes.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/PropertyAttributes.cs new file mode 100644 index 0000000..e8a2b37 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/PropertyAttributes.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.AspNet +{ + [Flags] + internal enum PropertyAttributes + { + /// + /// No flag is set for the property + /// + None = 0x0, + + /// + /// Gets or sets a value indicating whether the property should be ignored during update + /// + IgnoreForUpdate = 0x1, + + /// + /// Gets or sets a value indicating whether the property should be ignored during creation + /// + IgnoreForCreation = 0x2, + + /// + /// Gets or sets a value indicating whether there is permission to read the property + /// + NoReadPermission = 0x4, + + /// + /// Gets or sets a value indicating whether there is permission to write the property + /// + NoWritePermission = 0x8 + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ResourceAttribute.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ResourceAttribute.cs new file mode 100644 index 0000000..5619ccb --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ResourceAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.AspNet.Model +{ + /// + /// Attribute that indicates a property is an entity set or singleton. + /// If the property type is IQueryable, it will be built as entity set or it will be built as singleton. + /// The name will be same as property name. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class ResourceAttribute : Attribute + { + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelBuilder.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelBuilder.cs new file mode 100644 index 0000000..c2f4412 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelBuilder.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Builder; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core.Model; + +namespace Microsoft.Restier.AspNet.Model +{ + /// + /// This is a RESTier model build which retrieve information from providers like entity framework provider, + /// then build entity set and entity type based on retrieved information. + /// + internal class RestierModelBuilder : IModelBuilder + { + public IModelBuilder InnerModelBuilder { get; set; } + + /// + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + // This means user build a model with customized model builder registered as inner most + // Its element will be added to built model. + IEdmModel innerModel = null; + if (InnerModelBuilder != null) + { + innerModel = await InnerModelBuilder.GetModelAsync(context, cancellationToken).ConfigureAwait(false); + } + + var entitySetTypeMap = context.ResourceSetTypeMap; + if (entitySetTypeMap == null || entitySetTypeMap.Count == 0) + { + return innerModel; + } + + // Collection of entity type and set name is set by EF now, + // and EF model producer will not build model any more + // Web Api OData conversion model built is been used here, + // refer to Web Api OData document for the detail conversions been used for model built. + var builder = new ODataConventionModelBuilder + { + + // This namespace is used by container + Namespace = entitySetTypeMap.First().Value.Namespace + }; + + var method = typeof(ODataConventionModelBuilder).GetMethod("EntitySet", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + + foreach (var pair in entitySetTypeMap) + { + // Build a method with the specific type argument + var specifiedMethod = method.MakeGenericMethod(pair.Value); + var parameters = new object[] + { + pair.Key + }; + + specifiedMethod.Invoke(builder, parameters); + } + + entitySetTypeMap.Clear(); + + if (context.ResourceTypeKeyPropertiesMap != null) + { + foreach (var pair in context.ResourceTypeKeyPropertiesMap) + { + if (!(builder.GetTypeConfigurationOrNull(pair.Key) is EntityTypeConfiguration edmTypeConfiguration)) + { + continue; + } + + foreach (var property in pair.Value) + { + edmTypeConfiguration.HasKey(property); + } + } + + context.ResourceTypeKeyPropertiesMap.Clear(); + } + + var model = (EdmModel)builder.GetEdmModel(); + + // Add all Inner model content into existing model + // When WebApi OData make conversion model builder accept an existing model, this can be removed. + if (innerModel != null) + { + foreach (var element in innerModel.SchemaElements) + { + if (!(element is EdmEntityContainer)) + { + model.AddElement(element); + } + } + + foreach (var annotation in innerModel.VocabularyAnnotations) + { + model.AddVocabularyAnnotation(annotation); + } + + var entityContainer = (EdmEntityContainer)model.EntityContainer; + var innerEntityContainer = (EdmEntityContainer)innerModel.EntityContainer; + if (innerEntityContainer != null) + { + foreach (var entityset in innerEntityContainer.EntitySets()) + { + if (entityContainer.FindEntitySet(entityset.Name) == null) + { + entityContainer.AddEntitySet(entityset.Name, entityset.EntityType()); + } + } + + foreach (var singleton in innerEntityContainer.Singletons()) + { + if (entityContainer.FindEntitySet(singleton.Name) == null) + { + entityContainer.AddSingleton(singleton.Name, singleton.EntityType()); + } + } + + foreach (var operation in innerEntityContainer.OperationImports()) + { + if (entityContainer.FindOperationImports(operation.Name) == null) + { + if (operation.IsFunctionImport()) + { + entityContainer.AddFunctionImport( + operation.Name, (EdmFunction)operation.Operation, operation.EntitySet); + } + else + { + entityContainer.AddActionImport( + operation.Name, (EdmAction)operation.Operation, operation.EntitySet); + } + } + } + } + } + + return model; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs new file mode 100644 index 0000000..34e2911 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs @@ -0,0 +1,489 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Query; + +namespace Microsoft.Restier.AspNet.Model +{ + /// + /// A convention-based API model builder that extends a model, maps between + /// the model space and the object space, and expands a query expression. + /// + internal class RestierModelExtender + { + private readonly Type targetType; + private readonly ICollection publicProperties = new List(); + private readonly ICollection entitySetProperties = new List(); + private readonly ICollection singletonProperties = new List(); + private readonly ICollection addedNavigationSources = new List(); + + private readonly IDictionary entitySetCache = + new Dictionary(); + + private readonly IDictionary singletonCache = + new Dictionary(); + + private RestierModelExtender(Type targetType) => this.targetType = targetType; + + public static void ApplyTo( + IServiceCollection services, + Type targetType) + { + Ensure.NotNull(services, nameof(services)); + Ensure.NotNull(targetType, nameof(targetType)); + + // The model builder must maintain a singleton life time, for holding states and being injected into + // some other services. + services.AddSingleton(new RestierModelExtender(targetType)); + + services.AddService(); + services.AddService(); + services.AddService(); + services.AddService(); + } + + private static bool IsEntitySetProperty(PropertyInfo property) + { + return property.PropertyType.IsGenericType && + property.PropertyType.GetGenericTypeDefinition() == typeof(IQueryable<>) && + property.PropertyType.GetGenericArguments()[0].IsClass; + } + + private static bool IsSingletonProperty(PropertyInfo property) => !property.PropertyType.IsGenericType && property.PropertyType.IsClass; + + private IQueryable GetEntitySetQuery(QueryExpressionContext context) + { + Ensure.NotNull(context, nameof(context)); + if (context.ModelReference == null) + { + return null; + } + + var dataSourceStubReference = context.ModelReference as DataSourceStubModelReference; + if (dataSourceStubReference == null) + { + return null; + } + + if (!(dataSourceStubReference.Element is IEdmEntitySet entitySet)) + { + return null; + } + + var entitySetProperty = entitySetProperties + .SingleOrDefault(p => p.Name == entitySet.Name); + if (entitySetProperty != null) + { + object target = null; + if (!entitySetProperty.GetMethod.IsStatic) + { + target = context.QueryContext.GetApiService(); + if (target == null || + !targetType.IsInstanceOfType(target)) + { + return null; + } + } + + return entitySetProperty.GetValue(target) as IQueryable; + } + + return null; + } + + private IQueryable GetSingletonQuery(QueryExpressionContext context) + { + Ensure.NotNull(context, nameof(context)); + if (context.ModelReference == null) + { + return null; + } + + if (!(context.ModelReference is DataSourceStubModelReference dataSourceStubReference)) + { + return null; + } + + if (!(dataSourceStubReference.Element is IEdmSingleton singleton)) + { + return null; + } + + var singletonProperty = singletonProperties + .SingleOrDefault(p => p.Name == singleton.Name); + if (singletonProperty != null) + { + object target = null; + if (!singletonProperty.GetMethod.IsStatic) + { + target = context.QueryContext.GetApiService(); + if (target == null || + !targetType.IsInstanceOfType(target)) + { + return null; + } + } + + var value = Array.CreateInstance(singletonProperty.PropertyType, 1); + value.SetValue(singletonProperty.GetValue(target), 0); + return value.AsQueryable(); + } + + return null; + } + + private void ScanForDeclaredPublicProperties() + { + var currentType = targetType; + while (currentType != null && currentType != typeof(ApiBase)) + { + var publicPropertiesDeclaredOnCurrentType = currentType.GetProperties( + BindingFlags.Public | + BindingFlags.Static | + BindingFlags.Instance | + BindingFlags.DeclaredOnly); + + foreach (var property in publicPropertiesDeclaredOnCurrentType) + { + if (property.CanRead && + publicProperties.All(p => p.Name != property.Name)) + { + publicProperties.Add(property); + } + } + + currentType = currentType.BaseType; + } + } + + private void BuildEntitySetsAndSingletons(EdmModel model) + { + foreach (var property in publicProperties) + { + var resourceAttribute = property.GetCustomAttributes(true).FirstOrDefault(); + if (resourceAttribute == null) + { + continue; + } + + var isEntitySet = IsEntitySetProperty(property); + var isSingleton = IsSingletonProperty(property); + if (!isSingleton && !isEntitySet) + { + // This means property type is not IQueryable when indicating an entityset + // or not non-generic type when indicating a singleton + continue; + } + + var propertyType = property.PropertyType; + if (isEntitySet) + { + propertyType = propertyType.GetGenericArguments()[0]; + } + + var entityType = model.FindDeclaredType(propertyType.FullName) as IEdmEntityType; + if (entityType == null) + { + // Skip property whose entity type has not been declared yet. + continue; + } + + var container = model.EnsureEntityContainer(targetType); + if (isEntitySet) + { + if (container.FindEntitySet(property.Name) == null) + { + container.AddEntitySet(property.Name, entityType); + } + + // If ODataConventionModelBuilder is used to build the model, and a entity set is added, + // i.e. the entity set is already in the container, + // we should add it into entitySetProperties and addedNavigationSources + if (!entitySetProperties.Contains(property)) + { + entitySetProperties.Add(property); + addedNavigationSources.Add(container.FindEntitySet(property.Name) as EdmEntitySet); + } + } + else + { + if (container.FindSingleton(property.Name) == null) + { + container.AddSingleton(property.Name, entityType); + } + + if (!singletonProperties.Contains(property)) + { + singletonProperties.Add(property); + addedNavigationSources.Add(container.FindSingleton(property.Name) as EdmSingleton); + } + } + } + } + + private IEdmEntitySet[] GetMatchingEntitySets(IEdmEntityType entityType, IEdmModel model) + { + if (!entitySetCache.TryGetValue(entityType, out var matchingEntitySets)) + { + matchingEntitySets = model.EntityContainer.EntitySets().Where(s => s.EntityType() == entityType).ToArray(); + entitySetCache.Add(entityType, matchingEntitySets); + } + + return matchingEntitySets; + } + + private IEdmSingleton[] GetMatchingSingletons(IEdmEntityType entityType, IEdmModel model) + { + if (!singletonCache.TryGetValue(entityType, out var matchingSingletons)) + { + matchingSingletons = model.EntityContainer.Singletons().Where(s => s.EntityType() == entityType).ToArray(); + singletonCache.Add(entityType, matchingSingletons); + } + + return matchingSingletons; + } + + private void AddNavigationPropertyBindings(IEdmModel model) + { + // Only add navigation property bindings for the navigation sources added by this builder. + foreach (var navigationSource in addedNavigationSources) + { + var sourceEntityType = navigationSource.EntityType(); + foreach (var navigationProperty in sourceEntityType.NavigationProperties()) + { + var targetEntityType = navigationProperty.ToEntityType(); + var matchingEntitySets = GetMatchingEntitySets(targetEntityType, model); + IEdmNavigationSource targetNavigationSource = null; + if (navigationProperty.Type.IsCollection()) + { + // Collection navigation property can only bind to entity set. + if (matchingEntitySets.Length == 1) + { + targetNavigationSource = matchingEntitySets[0]; + } + } + else + { + // Singleton navigation property can bind to either entity set or singleton. + var matchingSingletons = GetMatchingSingletons(targetEntityType, model); + if (matchingEntitySets.Length == 1 && matchingSingletons.Length == 0) + { + targetNavigationSource = matchingEntitySets[0]; + } + else if (matchingEntitySets.Length == 0 && matchingSingletons.Length == 1) + { + targetNavigationSource = matchingSingletons[0]; + } + } + + if (targetNavigationSource != null) + { + navigationSource.AddNavigationTarget(navigationProperty, targetNavigationSource); + } + } + } + } + + internal class ModelBuilder : IModelBuilder + { + public ModelBuilder(RestierModelExtender modelCache) => ModelCache = modelCache; + + public IModelBuilder InnerModelBuilder { get; private set; } + + private RestierModelExtender ModelCache { get; set; } + + /// + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + Ensure.NotNull(context, nameof(context)); + + var modelReturned = await GetModelReturnedByInnerHandlerAsync(context, cancellationToken).ConfigureAwait(false); + if (modelReturned == null) + { + // There is no model returned so return an empty model. + var emptyModel = new EdmModel(); + emptyModel.EnsureEntityContainer(ModelCache.targetType); + return emptyModel; + } + + var edmModel = modelReturned as EdmModel; + if (edmModel == null) + { + // The model returned is not an EDM model. + return modelReturned; + } + + ModelCache.ScanForDeclaredPublicProperties(); + ModelCache.BuildEntitySetsAndSingletons(edmModel); + ModelCache.AddNavigationPropertyBindings(edmModel); + return edmModel; + } + + private async Task GetModelReturnedByInnerHandlerAsync( + ModelContext context, CancellationToken cancellationToken) + { + var innerHandler = InnerModelBuilder; + if (innerHandler != null) + { + return await innerHandler.GetModelAsync(context, cancellationToken).ConfigureAwait(false); + } + + return null; + } + } + + internal class ModelMapper : IModelMapper + { + public ModelMapper(RestierModelExtender modelCache) => ModelCache = modelCache; + + public RestierModelExtender ModelCache { get; set; } + + private IModelMapper InnerModelMapper { get; set; } + + /// + public bool TryGetRelevantType( + ModelContext context, + string name, + out Type relevantType) + { + if (InnerModelMapper != null && + InnerModelMapper.TryGetRelevantType(context, name, out relevantType)) + { + return true; + } + + relevantType = null; + var entitySetProperty = ModelCache.entitySetProperties.SingleOrDefault(p => p.Name == name); + if (entitySetProperty != null) + { + relevantType = entitySetProperty.PropertyType.GetGenericArguments()[0]; + } + + if (relevantType == null) + { + var singletonProperty = ModelCache.singletonProperties.SingleOrDefault(p => p.Name == name); + if (singletonProperty != null) + { + relevantType = singletonProperty.PropertyType; + } + } + + return relevantType != null; + } + + /// + public bool TryGetRelevantType( + ModelContext context, + string namespaceName, + string name, + out Type relevantType) + { + if (InnerModelMapper != null && + InnerModelMapper.TryGetRelevantType(context, namespaceName, name, out relevantType)) + { + return true; + } + + relevantType = null; + return false; + } + } + + internal class QueryExpressionExpander : IQueryExpressionExpander + { + public QueryExpressionExpander(RestierModelExtender modelCache) => ModelCache = modelCache; + + /// + public IQueryExpressionExpander InnerHandler { get; set; } + + private RestierModelExtender ModelCache { get; set; } + + /// + public Expression Expand(QueryExpressionContext context) + { + Ensure.NotNull(context, nameof(context)); + + var result = CallInner(context); + if (result != null) + { + return result; + } + + // Ensure this query constructs from DataSourceStub. + if (context.ModelReference is DataSourceStubModelReference) + { + // Only expand entity set query which returns IQueryable. + var query = ModelCache.GetEntitySetQuery(context); + if (query != null) + { + return query.Expression; + } + } + + // No expansion happened just return the node itself. + return context.VisitedNode; + } + + private Expression CallInner(QueryExpressionContext context) + { + if (InnerHandler != null) + { + return InnerHandler.Expand(context); + } + + return null; + } + } + + internal class QueryExpressionSourcer : IQueryExpressionSourcer + { + public QueryExpressionSourcer(RestierModelExtender modelCache) => ModelCache = modelCache; + + public IQueryExpressionSourcer InnerHandler { get; set; } + + private RestierModelExtender ModelCache { get; set; } + + /// + public Expression ReplaceQueryableSource(QueryExpressionContext context, bool embedded) + { + var result = CallInner(context, embedded); + if (result != null) + { + // Call the provider's sourcer to find the source of the query. + return result; + } + + // This sourcer ONLY deals with queries that cannot be addressed by the provider + // such as a singleton query that cannot be sourced by the EF provider, etc. + var query = ModelCache.GetEntitySetQuery(context) ?? ModelCache.GetSingletonQuery(context); + if (query != null) + { + return Expression.Constant(query); + } + + return null; + } + + private Expression CallInner(QueryExpressionContext context, bool embedded) + { + if (InnerHandler != null) + { + return InnerHandler.ReplaceQueryableSource(context, embedded); + } + + return null; + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierOperationModelBuilder.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierOperationModelBuilder.cs new file mode 100644 index 0000000..7d24f8e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierOperationModelBuilder.cs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using EdmPathExpression = Microsoft.OData.Edm.EdmPathExpression; + +namespace Microsoft.Restier.AspNet.Model +{ + internal class RestierOperationModelBuilder : IModelBuilder + { + private readonly Type targetType; + private readonly ICollection operationInfos = new List(); + + private RestierOperationModelBuilder(Type targetType) => this.targetType = targetType; + + private IModelBuilder InnerHandler { get; set; } + + public static void ApplyTo(IServiceCollection services, Type targetType) + { + services.AddService((sp, next) => new RestierOperationModelBuilder(targetType) + { + InnerHandler = next, + }); + } + + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + EdmModel model = null; + if (InnerHandler != null) + { + model = await InnerHandler.GetModelAsync(context, cancellationToken).ConfigureAwait(false) as EdmModel; + } + + if (model == null) + { + // We don't plan to extend an empty model with operations. + return null; + } + + ScanForOperations(); + + string existingNamespace = null; + if (model.DeclaredNamespaces != null) + { + existingNamespace = model.DeclaredNamespaces.FirstOrDefault(); + } + + BuildOperations(model, existingNamespace); + return model; + } + + private static void BuildOperationParameters(EdmOperation operation, MethodInfo method, IEdmModel model) + { + foreach (var parameter in method.GetParameters()) + { + var parameterTypeReference = parameter.ParameterType.GetTypeReference(model); + var operationParam = new EdmOperationParameter( + operation, + parameter.Name, + parameterTypeReference); + + operation.AddParameter(operationParam); + } + } + + private static EdmPathExpression BuildBoundOperationReturnTypePathExpression( + IEdmTypeReference returnTypeReference, ParameterInfo bindingParameter) + { + // Bound actions or functions that return an entity or a collection of entities + // MAY specify a value for the EntitySetPath attribute + // if determination of the entity set for the return type is contingent on the binding parameter. + // The value for the EntitySetPath attribute consists of a series of segments + // joined together with forward slashes. + // The first segment of the entity set path MUST be the name of the binding parameter. + // The remaining segments of the entity set path MUST represent navigation segments or type casts. + if (returnTypeReference != null && + returnTypeReference.IsEntity() && + bindingParameter != null) + { + return new EdmPathExpression(bindingParameter.Name); + } + + return null; + } + + private static IEdmExpression BuildEntitySetExpression( + IEdmModel model, string entitySetName, IEdmTypeReference returnTypeReference) + { + if (entitySetName == null && returnTypeReference != null) + { + var entitySet = model.FindDeclaredEntitySetByTypeReference(returnTypeReference); + if (entitySet != null) + { + entitySetName = entitySet.Name; + } + } + + if (entitySetName != null) + { + return new EdmPathExpression(entitySetName); + } + + return null; + } + + private static string GetNamespaceName(OperationMethodInfo methodInfo, string modelNamespace) + { + // customized the namespace logic, customized namespace is P0 + var namespaceName = methodInfo.OperationAttribute.Namespace; + + if (namespaceName != null) + { + return namespaceName; + } + + if (modelNamespace != null) + { + return modelNamespace; + } + + // This returns defined class namespace + return methodInfo.Namespace; + } + + private void ScanForOperations() + { + var methods = targetType.GetMethods( + BindingFlags.NonPublic | + BindingFlags.Public | + BindingFlags.Static | + BindingFlags.Instance); + + foreach (var method in methods) + { + var operationAttribute = method.GetCustomAttributes(true).FirstOrDefault(); + if (operationAttribute != null) + { + operationInfos.Add(new OperationMethodInfo + { + Method = method, + OperationAttribute = operationAttribute + }); + } + } + } + + private void BuildOperations(EdmModel model, string modelNamespace) + { + foreach (var operationMethodInfo in operationInfos) + { + // With this method, if return type is nullable type,it will get underlying type + var returnType = TypeHelper.GetUnderlyingTypeOrSelf(operationMethodInfo.Method.ReturnType); + var returnTypeReference = returnType.GetReturnTypeReference(model); + var isBound = operationMethodInfo.IsBound; + var bindingParameter = operationMethodInfo.Method.GetParameters().FirstOrDefault(); + + if (bindingParameter == null && isBound) + { + // Ignore the method which is marked as bounded but no parameters + continue; + } + + var namespaceName = GetNamespaceName(operationMethodInfo, modelNamespace); + + EdmOperation operation = null; + EdmPathExpression path = null; + if (isBound) + { + // Unbound actions or functions should not have EntitySetPath attribute + path = BuildBoundOperationReturnTypePathExpression(returnTypeReference, bindingParameter); + } + + if (operationMethodInfo.HasSideEffects) + { + operation = new EdmAction( + namespaceName, + operationMethodInfo.Name, + returnTypeReference, + isBound, + path); + } + else + { + operation = new EdmFunction( + namespaceName, + operationMethodInfo.Name, + returnTypeReference, + isBound, + path, + operationMethodInfo.IsComposable); + } + + BuildOperationParameters(operation, operationMethodInfo.Method, model); + model.AddElement(operation); + + if (!isBound) + { + // entitySetReferenceExpression refer to an entity set containing entities returned + // by this function/action import. + var entitySetExpression = BuildEntitySetExpression( + model, operationMethodInfo.EntitySet, returnTypeReference); + var entityContainer = model.EnsureEntityContainer(targetType); + if (operationMethodInfo.HasSideEffects) + { + entityContainer.AddActionImport(operation.Name, (EdmAction)operation, entitySetExpression); + } + else + { + entityContainer.AddFunctionImport( + operation.Name, (EdmFunction)operation, entitySetExpression); + } + } + } + } + + private class OperationMethodInfo + { + public MethodInfo Method { get; set; } + + public OperationAttribute OperationAttribute { get; set; } + + public string Name => Method.Name; + + public string Namespace => OperationAttribute.Namespace ?? Method.DeclaringType.Namespace; + + public string EntitySet => OperationAttribute.EntitySet; + + public bool IsComposable => OperationAttribute.IsComposable; + + public bool IsBound => OperationAttribute.IsBound; + + public bool HasSideEffects => OperationAttribute.HasSideEffects; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Operation/OperationExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Operation/OperationExecutor.cs new file mode 100644 index 0000000..30b8595 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Operation/OperationExecutor.cs @@ -0,0 +1,234 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Reflection; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OData.Edm; +using Microsoft.Restier.AspNet.Formatter; +using Microsoft.Restier.AspNet.Model; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Operation; + + +namespace Microsoft.Restier.AspNet.Operation +{ + + /// + /// + /// + internal class OperationExecutor : IOperationExecutor + { + + /// + /// + /// + /// + /// + /// + public async Task ExecuteOperationAsync(OperationContext context, CancellationToken cancellationToken) + { + // Authorization check + await InvokeAuthorizers(context, cancellationToken).ConfigureAwait(false); + + // model build does not support operation with same name + // So method with same name but different signature is not considered. + var method = context.ImplementInstance.GetType().GetMethod(context.OperationName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + + if (method == null) + { + throw new NotImplementedException(Resources.OperationNotImplemented); + } + + var parameterArray = method.GetParameters(); + + var model = context.GetApiService(); + + // Parameters of method and model is exactly mapped or there is parsing error + var parameters = new object[parameterArray.Length]; + + var paraIndex = 0; + if (context.BindingParameterValue != null) + { + // Add binding parameter which is first parameter of method + parameters[0] = PrepareBindingParameter(parameterArray[0].ParameterType, context.BindingParameterValue); + paraIndex = 1; + } + + for (; paraIndex < parameterArray.Length; paraIndex++) + { + var parameter = parameterArray[paraIndex]; + var currentParameterValue = context.GetParameterValueFunc(parameter.Name); + + object convertedValue = null; + if (context.IsFunction) + { + var parameterTypeRef = parameter.ParameterType.GetTypeReference(model); + + // Change to right CLR class for collection/Enum/Complex/Entity + convertedValue = DeserializationHelpers.ConvertValue( + currentParameterValue, + parameter.Name, + parameter.ParameterType, + parameterTypeRef, + model, + context.Request, + context.ServiceProvider); + } + else + { + convertedValue = DeserializationHelpers.ConvertCollectionType( + currentParameterValue, parameter.ParameterType); + } + + parameters[paraIndex] = convertedValue; + } + + context.ParameterValues = parameters; + + // Invoke preprocessing on the operation execution + PerformPreEvent(context, cancellationToken); + + var result = await InvokeOperation(context.ImplementInstance, method, parameters, model).ConfigureAwait(false); + + // Invoke preprocessing on the operation execution + PerformPostEvent(context, cancellationToken); + return result; + } + + private static object PrepareBindingParameter(Type bindingType, IEnumerable bindingParameterValue) + { + var enumerableType = bindingType.FindGenericType(typeof(IEnumerable<>)); + + // This means binding to a single entity + if (enumerableType == null) + { + var entity = bindingParameterValue.SingleOrDefault(); + if (entity == null) + { + throw new StatusCodeException(HttpStatusCode.NotFound, Resources.ResourceNotFound); + } + + return entity; + } + + // This means function is bound to an entity set. + // IQueryable should always have generic type argument + var elementClrType = enumerableType.GenericTypeArguments[0]; + + // For entity set, user can write as ICollection<> or IEnumerable<> or array as method parameters + if (bindingType.IsArray) + { + var toArrayMethodInfo = ExpressionHelperMethods.EnumerableToArrayGeneric + .MakeGenericMethod(elementClrType); + var arrayResult = toArrayMethodInfo.Invoke(null, new object[] { bindingParameterValue }); + return arrayResult; + } + + if (bindingType.FindGenericType(typeof(ICollection<>)) != null) + { + var toListMethodInfo = ExpressionHelperMethods.EnumerableToListGeneric + .MakeGenericMethod(elementClrType); + var listResult = toListMethodInfo.Invoke(null, new object[] { bindingParameterValue }); + return listResult; + } + + return bindingParameterValue; + } + + private static async Task InvokeOperation( + object instanceImplementMethod, MethodInfo method, object[] parameters, IEdmModel model) + { + var result = method.Invoke(instanceImplementMethod, parameters); + var returnType = method.ReturnType; + if (returnType == typeof(void)) + { + return null; + } + + if (result is Task task) + { + await task.ConfigureAwait(false); + if (returnType.GenericTypeArguments.Any()) + { + returnType = returnType.GenericTypeArguments.First(); + var resultProperty = typeof(Task<>).MakeGenericType(returnType).GetProperty("Result"); + result = resultProperty.GetValue(task); + } + else + { + return null; + } + } + + var edmReturnType = returnType.GetReturnTypeReference(model); + if (edmReturnType.IsCollection()) + { + var elementClrType = returnType.GetElementType() ?? returnType.GenericTypeArguments[0]; + if (result == null) + { + return ExpressionHelpers.CreateEmptyQueryable(elementClrType); + } + + var enumerableType = result.GetType().FindGenericType(typeof(IEnumerable<>)); + if (enumerableType != null) + { + return ((IEnumerable)result).AsQueryable(); + } + + // Should never hint this path, add here to make sure collection result will not hint single result part + return ExpressionHelpers.CreateEmptyQueryable(elementClrType); + } + + // This means this is single result + // cannot return new[] { result }.AsQueryable(); as need to return in its own type but not object type + var objectQueryable = new[] { result }.AsQueryable(); + var castMethodInfo = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(returnType); + var castedResult = castMethodInfo.Invoke(null, new object[] { objectQueryable }); + var typedQueryable = ExpressionHelperMethods.QueryableAsQueryable.Invoke(null, new object[] { castedResult }) as IQueryable; + + return typedQueryable; + } + + private static async Task InvokeAuthorizers(OperationContext context, CancellationToken cancellationToken) + { + var authorizor = context.GetApiService(); + if (authorizor == null) + { + return; + } + + if (!await authorizor.AuthorizeAsync(context, cancellationToken).ConfigureAwait(false)) + { + throw new SecurityException(string.Format(CultureInfo.InvariantCulture, Resources.OperationUnAuthorizationExecution, context.OperationName)); + } + } + + private static void PerformPreEvent(OperationContext context, CancellationToken cancellationToken) + { + var processor = context.GetApiService(); + if (processor != null) + { + processor.OnOperationExecutingAsync(context, cancellationToken); + } + } + + private static void PerformPostEvent(OperationContext context, CancellationToken cancellationToken) + { + var processor = context.GetApiService(); + if (processor != null) + { + processor.OnOperationExecutedAsync(context, cancellationToken); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/AssemblyInfo.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2022f78 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/AssemblyInfo.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +//[assembly: AssemblyTitle("RESTier OData Publisher")] +//[assembly: AssemblyDescription("This package contains everything you need to create OData v4.0 services " + +// "using ASP.NET Web API OData wired to an API provided by the RESTier.")] +//[assembly: AssemblyConfiguration("")] +//[assembly: AssemblyCompany("Microsoft")] +//[assembly: AssemblyProduct("Microsoft.Restier.AspNet")] +//[assembly: AssemblyCopyright("Copyright © Microsoft Corporation 2016")] +//[assembly: AssemblyTrademark("")] +//[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9274133d-c4e5-4fea-adf3-e9b181e5f14e")] +[assembly:InternalsVisibleTo("Microsoft.Restier.Tests.AspNet")] \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.Designer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.Designer.cs new file mode 100644 index 0000000..fbf1658 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.Designer.cs @@ -0,0 +1,270 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.AspNet { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Restier.AspNet.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The argument with name {0} cannot be null.. + /// + internal static string ArgumentCannotBeNull { + get { + return ResourceManager.GetString("ArgumentCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} cannot write an object of type '{1}'.. + /// + internal static string CannotWriteObjectType { + get { + return ResourceManager.GetString("CannotWriteObjectType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Controller cannot have null path.. + /// + internal static string ControllerRequiresPath { + get { + return ResourceManager.GetString("ControllerRequiresPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Currently only EntitySets can be deleted from.. + /// + internal static string DeleteOnlySupportedOnEntitySet { + get { + return ResourceManager.GetString("DeleteOnlySupportedOnEntitySet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not a supported EDM type.. + /// + internal static string EdmTypeNotSupported { + get { + return ResourceManager.GetString("EdmTypeNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Element type cannot be found for '{0}'.. + /// + internal static string ElementTypeNotFound { + get { + return ResourceManager.GetString("ElementTypeNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to EntitySet is missing during serialization.. + /// + internal static string EntitySetMissingForSerialization { + get { + return ResourceManager.GetString("EntitySetMissingForSerialization", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Keys were not specified in the format of 'KeyName=KeyValue'.. + /// + internal static string IncorrectKeyFormat { + get { + return ResourceManager.GetString("IncorrectKeyFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Currently only EntitySets can be inserted into.. + /// + internal static string InsertOnlySupportedOnEntitySet { + get { + return ResourceManager.GetString("InsertOnlySupportedOnEntitySet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid request - ODataPath is null.. + /// + internal static string InvalidEmptyPathInRequest { + get { + return ResourceManager.GetString("InvalidEmptyPathInRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid request - No ODataProperties.. + /// + internal static string InvalidODataInfoInRequest { + get { + return ResourceManager.GetString("InvalidODataInfoInRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid request - Expecting {0} path template.. + /// + internal static string InvalidPathTemplateInRequest { + get { + return ResourceManager.GetString("InvalidPathTemplateInRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specified key '{0}' is not a valid property of entity '{1}'.. + /// + internal static string KeyNotValidForEntityType { + get { + return ResourceManager.GetString("KeyNotValidForEntityType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Model state is not valid with message {0}, please check your request.. + /// + internal static string ModelStateIsNotValid { + get { + return ResourceManager.GetString("ModelStateIsNotValid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one key was specified, when multiple were expected.. + /// + internal static string MultiKeyValuesExpected { + get { + return ResourceManager.GetString("MultiKeyValuesExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not supported type: {0}.. + /// + internal static string NotSupportedType { + get { + return ResourceManager.GetString("NotSupportedType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The requested operation is not implemented in Api class.. + /// + internal static string OperationNotImplemented { + get { + return ResourceManager.GetString("OperationNotImplemented", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The user is not authorized to execute operation {0}.. + /// + internal static string OperationUnAuthorizationExecution { + get { + return ResourceManager.GetString("OperationUnAuthorizationExecution", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path segment not supported: {0}. + /// + internal static string PathSegmentNotSupported { + get { + return ResourceManager.GetString("PathSegmentNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Post to unbound action is not supported by `RestierController`.. + /// + internal static string PostToUnboundActionNotSupported { + get { + return ResourceManager.GetString("PostToUnboundActionNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request need to have If-Match or If-None-Match header.. + /// + internal static string PreconditionRequired { + get { + return ResourceManager.GetString("PreconditionRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The resource you requested is not found.. + /// + internal static string ResourceNotFound { + get { + return ResourceManager.GetString("ResourceNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Currently only EntitySets can be updated.. + /// + internal static string UpdateOnlySupportedOnEntitySet { + get { + return ResourceManager.GetString("UpdateOnlySupportedOnEntitySet", resourceCulture); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.resx b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.resx new file mode 100644 index 0000000..fdf3d49 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Properties/Resources.resx @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The argument with name {0} cannot be null. + + + {0} cannot write an object of type '{1}'. + + + Controller cannot have null path. + + + Currently only EntitySets can be deleted from. + + + {0} is not a supported EDM type. + + + Element type cannot be found for '{0}'. + + + EntitySet is missing during serialization. + + + Keys were not specified in the format of 'KeyName=KeyValue'. + + + Currently only EntitySets can be inserted into. + + + Invalid request - ODataPath is null. + + + Invalid request - No ODataProperties. + + + Invalid request - Expecting {0} path template. + + + Specified key '{0}' is not a valid property of entity '{1}'. + + + Model state is not valid with message {0}, please check your request. + + + Only one key was specified, when multiple were expected. + + + Not supported type: {0}. + + + The requested operation is not implemented in Api class. + + + The user is not authorized to execute operation {0}. + + + Path segment not supported: {0} + + + Post to unbound action is not supported by `RestierController`. + + + The request need to have If-Match or If-None-Match header. + + + The resource you requested is not found. + + + Currently only EntitySets can be updated. + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryBuilder.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryBuilder.cs new file mode 100644 index 0000000..a35091c --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryBuilder.cs @@ -0,0 +1,276 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using Microsoft.Restier.AspNet.Model; +using Microsoft.Restier.Core; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.Restier.AspNet.Query +{ + internal class RestierQueryBuilder + { + private const string DefaultNameOfParameterExpression = "currentValue"; + + private readonly ApiBase api; + private readonly ODataPath path; + private readonly IDictionary> handlers = new Dictionary>(); + + private IQueryable queryable; + private Type currentType; + + public RestierQueryBuilder(ApiBase api, ODataPath path) + { + Ensure.NotNull(api, nameof(api)); + Ensure.NotNull(path, nameof(path)); + this.api = api; + this.path = path; + + handlers[typeof(EntitySetSegment)] = HandleEntitySetPathSegment; + handlers[typeof(SingletonSegment)] = HandleSingletonPathSegment; + handlers[typeof(OperationSegment)] = EmptyHandler; + handlers[typeof(OperationImportSegment)] = EmptyHandler; + handlers[typeof(CountSegment)] = HandleCountPathSegment; + handlers[typeof(ValueSegment)] = HandleValuePathSegment; + handlers[typeof(KeySegment)] = HandleKeyValuePathSegment; + handlers[typeof(NavigationPropertySegment)] = HandleNavigationPathSegment; + handlers[typeof(PropertySegment)] = HandlePropertyAccessPathSegment; + handlers[typeof(TypeSegment)] = HandleEntityTypeSegment; + + // Complex cast is not supported by EF, and is not supported here + // this.handlers[ODataSegmentKinds.ComplexCast] = null; + } + + public bool IsCountPathSegmentPresent { get; private set; } + + public bool IsValuePathSegmentPresent { get; private set; } + + public IQueryable BuildQuery() + { + queryable = null; + + foreach (var segment in path.Segments) + { + if (!handlers.TryGetValue(segment.GetType(), out var handler)) + { + throw new NotImplementedException( + string.Format(CultureInfo.InvariantCulture, Resources.PathSegmentNotSupported, segment)); + } + + handler(segment); + } + + return queryable; + } + + #region Helper Methods + internal static IReadOnlyDictionary GetPathKeyValues(ODataPath path) + { + if (path.PathTemplate == "~/entityset/key" || + path.PathTemplate == "~/entityset/key/cast") + { + var keySegment = (KeySegment)path.Segments[1]; + return GetPathKeyValues(keySegment); + } + else if (path.PathTemplate == "~/entityset/cast/key") + { + var keySegment = (KeySegment)path.Segments[2]; + return GetPathKeyValues(keySegment); + } + else + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + Resources.InvalidPathTemplateInRequest, + "~/entityset/key")); + } + } + + private static IReadOnlyDictionary GetPathKeyValues( + KeySegment keySegment) + { + var result = new Dictionary(); + + // TODO GitHubIssue#42 : Improve key parsing logic + // this parsing implementation does not allow key values to contain commas + // Depending on the WebAPI to make KeyValuePathSegment.Values collection public + // (or have the parsing logic public). + var keyValuePairs = keySegment.Keys; + + foreach (var keyValuePair in keyValuePairs) + { + result.Add(keyValuePair.Key, keyValuePair.Value); + } + + return result; + } + + private static BinaryExpression CreateEqualsExpression( + ParameterExpression parameterExpression, + string propertyName, + object propertyValue) + { + var property = Expression.Property(parameterExpression, propertyName); + var constant = Expression.Constant( + TypeConverter.ChangeType(propertyValue, property.Type, CultureInfo.InvariantCulture)); + return Expression.Equal(property, constant); + } + + private static LambdaExpression CreateNotEqualsNullExpression( + Expression propertyExpression, ParameterExpression parameterExpression) + { + var nullConstant = Expression.Constant(null); + var nullFilterExpression = Expression.NotEqual(propertyExpression, nullConstant); + var whereExpression = Expression.Lambda(nullFilterExpression, parameterExpression); + + return whereExpression; + } + #endregion + + #region Handler Methods + private void HandleEntitySetPathSegment(ODataPathSegment segment) + { + var entitySetPathSegment = (EntitySetSegment)segment; + var entitySet = entitySetPathSegment.EntitySet; + queryable = api.GetQueryableSource(entitySet.Name, (object[])null); + currentType = queryable.ElementType; + } + + private void HandleSingletonPathSegment(ODataPathSegment segment) + { + var singletonPathSegment = (SingletonSegment)segment; + var singleton = singletonPathSegment.Singleton; + queryable = api.GetQueryableSource(singleton.Name, (object[])null); + currentType = queryable.ElementType; + } + + private void EmptyHandler(ODataPathSegment segment) + { + // Nothing will be done + } + + private void HandleCountPathSegment(ODataPathSegment segment) => IsCountPathSegmentPresent = true; + + private void HandleValuePathSegment(ODataPathSegment segment) => IsValuePathSegmentPresent = true; + + private void HandleKeyValuePathSegment(ODataPathSegment segment) + { + var keySegment = (KeySegment)segment; + + var parameterExpression = Expression.Parameter(currentType, DefaultNameOfParameterExpression); + var keyValues = GetPathKeyValues(keySegment); + + BinaryExpression keyFilter = null; + foreach (var keyValuePair in keyValues) + { + var equalsExpression = + CreateEqualsExpression(parameterExpression, keyValuePair.Key, keyValuePair.Value); + keyFilter = keyFilter == null ? equalsExpression : Expression.And(keyFilter, equalsExpression); + } + + var whereExpression = Expression.Lambda(keyFilter, parameterExpression); + queryable = ExpressionHelpers.Where(queryable, whereExpression, currentType); + } + + private void HandleNavigationPathSegment(ODataPathSegment segment) + { + var navigationSegment = (NavigationPropertySegment)segment; + var entityParameterExpression = Expression.Parameter(currentType); + var navigationPropertyExpression = + Expression.Property(entityParameterExpression, navigationSegment.NavigationProperty.Name); + + // Check whether property is null or not before further selection + var whereExpression = + CreateNotEqualsNullExpression(navigationPropertyExpression, entityParameterExpression); + queryable = ExpressionHelpers.Where(queryable, whereExpression, currentType); + + if (navigationSegment.NavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + // get the element type of the target + // (the type should be an EntityCollection for navigation queries). + currentType = navigationPropertyExpression.Type.GetEnumerableItemType(); + + // need to explicitly define the delegate type as IEnumerable + var delegateType = typeof(Func<,>).MakeGenericType( + queryable.ElementType, + typeof(IEnumerable<>).MakeGenericType(currentType)); + var selectBody = + Expression.Lambda(delegateType, navigationPropertyExpression, entityParameterExpression); + + queryable = ExpressionHelpers.SelectMany(queryable, selectBody, currentType); + } + else + { + currentType = navigationPropertyExpression.Type; + var selectBody = + Expression.Lambda(navigationPropertyExpression, entityParameterExpression); + queryable = ExpressionHelpers.Select(queryable, selectBody); + } + } + + private void HandlePropertyAccessPathSegment(ODataPathSegment segment) + { + var propertySegment = (PropertySegment)segment; + var entityParameterExpression = Expression.Parameter(currentType); + var structuralPropertyExpression = + Expression.Property(entityParameterExpression, propertySegment.Property.Name); + + // Check whether property is null or not before further selection + if (propertySegment.Property.Type.IsNullable && !propertySegment.Property.Type.IsPrimitive()) + { + var whereExpression = + CreateNotEqualsNullExpression(structuralPropertyExpression, entityParameterExpression); + queryable = ExpressionHelpers.Where(queryable, whereExpression, currentType); + } + + if (propertySegment.Property.Type.IsCollection()) + { + // Produces new query like 'queryable.SelectMany(param => param.PropertyName)'. + // Suppose 'param.PropertyName' is of type 'IEnumerable', the type of the + // resulting query would be 'IEnumerable' too. + currentType = structuralPropertyExpression.Type.GetEnumerableItemType(); + var delegateType = typeof(Func<,>).MakeGenericType( + queryable.ElementType, + typeof(IEnumerable<>).MakeGenericType(currentType)); + var selectBody = + Expression.Lambda(delegateType, structuralPropertyExpression, entityParameterExpression); + queryable = ExpressionHelpers.SelectMany(queryable, selectBody, currentType); + } + else + { + // Produces new query like 'queryable.Select(param => param.PropertyName)'. + currentType = structuralPropertyExpression.Type; + var selectBody = + Expression.Lambda(structuralPropertyExpression, entityParameterExpression); + queryable = ExpressionHelpers.Select(queryable, selectBody); + } + } + + // This only covers entity type cast + // complex type cast uses ComplexCastPathSegment and is not supported by EF now + // CLR type is got from model annotation, which means model must include that annotation. + private void HandleEntityTypeSegment(ODataPathSegment segment) + { + var typeSegment = (TypeSegment)segment; + var edmType = typeSegment.EdmType; + + if (typeSegment.EdmType.TypeKind == EdmTypeKind.Collection) + { + edmType = ((IEdmCollectionType)typeSegment.EdmType).ElementType.Definition; + } + + if (edmType.TypeKind == EdmTypeKind.Entity) + { + currentType = edmType.GetClrType(api.ServiceProvider); + queryable = ExpressionHelpers.OfType(queryable, currentType); + } + } + #endregion + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutor.cs new file mode 100644 index 0000000..7ecd4ea --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutor.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Query; + +namespace Microsoft.Restier.AspNet.Query +{ + /// + /// + /// + internal class RestierQueryExecutor : IQueryExecutor + { + + /// + /// + /// + public IQueryExecutor Inner { get; set; } + + /// + /// + /// + /// + /// + /// + /// + /// + public async Task ExecuteQueryAsync(QueryContext context, IQueryable query, CancellationToken cancellationToken) + { + var countOption = context.GetApiService(); + if (countOption.IncludeTotalCount) + { + var countQuery = ExpressionHelpers.GetCountableQuery(query); + var expression = ExpressionHelpers.Count(countQuery.Expression, countQuery.ElementType); + var result = await ExecuteExpressionAsync(context, countQuery.Provider, expression, cancellationToken).ConfigureAwait(false); + var totalCount = result.Results.Cast().Single(); + + countOption.SetTotalCount(totalCount); + } + + return await Inner.ExecuteQueryAsync(context, query, cancellationToken).ConfigureAwait(false); + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Task ExecuteExpressionAsync( QueryContext context, IQueryProvider queryProvider, Expression expression, CancellationToken cancellationToken) + => Inner.ExecuteExpressionAsync(context, queryProvider, expression, cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutorOptions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutorOptions.cs new file mode 100644 index 0000000..a33ef61 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Query/RestierQueryExecutorOptions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.AspNet.Query +{ + internal class RestierQueryExecutorOptions + { + /// + /// Gets or sets a value indicating whether the total + /// number of items should be retrieved when the + /// result has been filtered using paging operators. + /// + /// + /// Setting this to true may have a performance impact as + /// the data provider may need to execute two independent queries. + /// + public bool IncludeTotalCount { get; set; } + + public Action SetTotalCount { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs new file mode 100644 index 0000000..40a7161 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs @@ -0,0 +1,707 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Results; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using Microsoft.Restier.AspNet.Model; +using Microsoft.Restier.AspNet.Query; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Operation; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; +// This is a must for creating response with correct extension method +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + + +namespace Microsoft.Restier.AspNet +{ + /// + /// The all-in-one controller class to handle API requests. + /// + [ODataFormatting] + [RestierExceptionFilter] + public class RestierController : ODataController + { + private const string IfMatchKey = "@IfMatchKey"; + private const string IfNoneMatchKey = "@IfNoneMatchKey"; + + private ApiBase api; + private bool shouldReturnCount; + private bool shouldWriteRawValue; + + /// + /// Gets the API associated with this controller. + /// + private ApiBase Api + { + get + { + if (api == null) + { + var provider = Request.GetRequestContainer(); + api = provider.GetService(); + } + + return api; + } + } + + /// + /// Handles a GET request to query entities. + /// + /// The cancellation token. + /// The task object that contains the response message. + public async Task Get(CancellationToken cancellationToken) + { + var path = GetPath(); + var lastSegment = path.Segments.LastOrDefault(); + if (lastSegment == null) + { + throw new InvalidOperationException(Resources.ControllerRequiresPath); + } + + IQueryable result = null; + + // Get queryable path builder to builder + var queryable = GetQuery(path); + ETag etag; + + // TODO #365 Do not support additional path segment after function call now + if (lastSegment is OperationImportSegment unboundSegment) + { + var operation = unboundSegment.OperationImports.FirstOrDefault(); + Func getParaValueFunc = p => unboundSegment.Parameters.FirstOrDefault(c => c.Name == p).Value; + result = await ExecuteOperationAsync(getParaValueFunc, operation.Name, true, null, cancellationToken).ConfigureAwait(false); + + var applied = await ApplyQueryOptionsAsync(result, path, true).ConfigureAwait(false); + result = applied.Queryable; + etag = applied.Etag; + } + else + { + if (queryable == null) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, Resources.ResourceNotFound)); + } + + if (lastSegment is OperationSegment) + { + result = await ExecuteQuery(queryable, cancellationToken).ConfigureAwait(false); + + var boundSeg = (OperationSegment)lastSegment; + var operation = boundSeg.Operations.FirstOrDefault(); + Func getParaValueFunc = p => boundSeg.Parameters.FirstOrDefault(c => c.Name == p).Value; + result = await ExecuteOperationAsync(getParaValueFunc, operation.Name, true, result, cancellationToken).ConfigureAwait(false); + + var applied = await ApplyQueryOptionsAsync(result, path, true).ConfigureAwait(false); + result = applied.Queryable; + etag = applied.Etag; + + } + else + { + var applied = await ApplyQueryOptionsAsync(queryable, path, false).ConfigureAwait(false); + result = await ExecuteQuery(applied.Queryable, cancellationToken).ConfigureAwait(false); + etag = applied.Etag; + } + } + + return CreateQueryResponse(result, path.EdmType, etag); + } + + /// + /// Handles a POST request to create an entity. + /// + /// The entity object to create. + /// The cancellation token. + /// The task object that contains the creation result. + public async Task Post(EdmEntityObject edmEntityObject, CancellationToken cancellationToken) + { + if (edmEntityObject == null) + { + throw new ArgumentNullException(nameof(edmEntityObject)); + } + + CheckModelState(); + var path = GetPath(); + if (!(path.NavigationSource is IEdmEntitySet entitySet)) + { + throw new NotImplementedException(Resources.InsertOnlySupportedOnEntitySet); + } + + // In case of type inheritance, the actual type will be different from entity type + var expectedEntityType = path.EdmType; + var actualEntityType = path.EdmType as IEdmStructuredType; + if (edmEntityObject.ActualEdmType != null) + { + expectedEntityType = edmEntityObject.ExpectedEdmType; + actualEntityType = edmEntityObject.ActualEdmType; + } + + var postItem = new DataModificationItem( + entitySet.Name, + expectedEntityType.GetClrType(Api.ServiceProvider), + actualEntityType.GetClrType(Api.ServiceProvider), + RestierEntitySetOperation.Insert, + null, + null, + edmEntityObject.CreatePropertyDictionary(actualEntityType, api, true)); + + var changeSetProperty = Request.GetChangeSet(); + if (changeSetProperty == null) + { + var changeSet = new ChangeSet(); + changeSet.Entries.Add(postItem); + + var result = await Api.SubmitAsync(changeSet, cancellationToken).ConfigureAwait(false); + } + else + { + changeSetProperty.ChangeSet.Entries.Add(postItem); + + await changeSetProperty.OnChangeSetCompleted(Request).ConfigureAwait(false); + } + + return CreateCreatedODataResult(postItem.Resource); + } + + /// + /// Handles a PUT request to fully update an entity. + /// + /// The entity object to update. + /// The cancellation token. + /// The task object that contains the updated result. +#pragma warning disable CA1062 // Validate public arguments + public async Task Put(EdmEntityObject edmEntityObject, CancellationToken cancellationToken) => await Update(edmEntityObject, true, cancellationToken).ConfigureAwait(false); + + /// + /// Handles a PATCH request to partially update an entity. + /// + /// The entity object to update. + /// The cancellation token. + /// The task object that contains the updated result. + public async Task Patch(EdmEntityObject edmEntityObject, CancellationToken cancellationToken) => await Update(edmEntityObject, false, cancellationToken).ConfigureAwait(false); +#pragma warning restore CA1062 // Validate public arguments + + /// + /// Handles a DELETE request to delete an entity. + /// + /// The cancellation token. + /// The task object that contains the deletion result. + public async Task Delete(CancellationToken cancellationToken) + { + var path = GetPath(); + if (!(path.NavigationSource is IEdmEntitySet entitySet)) + { + throw new NotImplementedException(Resources.DeleteOnlySupportedOnEntitySet); + } + + var propertiesInEtag = await GetOriginalValues(entitySet).ConfigureAwait(false); + if (propertiesInEtag == null) + { + throw new StatusCodeException((HttpStatusCode)428, Resources.PreconditionRequired); + } + + var deleteItem = new DataModificationItem( + entitySet.Name, + path.EdmType.GetClrType(Api.ServiceProvider), + null, + RestierEntitySetOperation.Delete, + RestierQueryBuilder.GetPathKeyValues(path), + propertiesInEtag, + null); + + var changeSetProperty = Request.GetChangeSet(); + if (changeSetProperty == null) + { + var changeSet = new ChangeSet(); + changeSet.Entries.Add(deleteItem); + + var result = await Api.SubmitAsync(changeSet, cancellationToken).ConfigureAwait(false); + } + else + { + changeSetProperty.ChangeSet.Entries.Add(deleteItem); + + await changeSetProperty.OnChangeSetCompleted(Request).ConfigureAwait(false); + } + + return StatusCode(HttpStatusCode.NoContent); + } + + /// + /// Handles a POST request to an action. + /// + /// Parameters from action request content. + /// The cancellation token. + /// The task object that contains the action result. + public async Task PostAction( + ODataActionParameters parameters, CancellationToken cancellationToken) + { + CheckModelState(); + var path = GetPath(); + + var lastSegment = path.Segments.LastOrDefault(); + if (lastSegment == null) + { + throw new InvalidOperationException(Resources.ControllerRequiresPath); + } + + IQueryable result = null; + Func getParaValueFunc = p => + { + if (parameters == null) + { + return null; + } + + return parameters[p]; + }; + + var segment = lastSegment as OperationImportSegment; + if (segment != null) + { + var unboundSegment = segment; + var operation = unboundSegment.OperationImports.FirstOrDefault(); + result = await ExecuteOperationAsync(getParaValueFunc, operation.Name, false, null, cancellationToken).ConfigureAwait(false); + } + else + { + // Get queryable path builder to builder + var queryable = GetQuery(path); + if (queryable == null) + { + throw new HttpResponseException( + Request.CreateErrorResponse( + HttpStatusCode.NotFound, + Resources.ResourceNotFound)); + } + + if (lastSegment is OperationSegment) + { + var operationSegment = lastSegment as OperationSegment; + var operation = operationSegment.Operations.FirstOrDefault(); + var queryResult = await ExecuteQuery(queryable, cancellationToken).ConfigureAwait(false); + result = await ExecuteOperationAsync(getParaValueFunc, operation.Name, false, queryResult, cancellationToken).ConfigureAwait(false); + } + } + + if (path.EdmType == null) + { + // This is a void action, return 204 directly + return Request.CreateResponse(HttpStatusCode.NoContent); + } + + return CreateQueryResponse(result, path.EdmType, null); + } + + private static IEdmTypeReference GetTypeReference(IEdmType edmType) + { + Ensure.NotNull(edmType, nameof(edmType)); + + var isNullable = false; + switch (edmType.TypeKind) + { + case EdmTypeKind.Collection: + return new EdmCollectionTypeReference(edmType as IEdmCollectionType); + case EdmTypeKind.Complex: + return new EdmComplexTypeReference(edmType as IEdmComplexType, isNullable); + case EdmTypeKind.Entity: + return new EdmEntityTypeReference(edmType as IEdmEntityType, isNullable); + case EdmTypeKind.EntityReference: + return new EdmEntityReferenceTypeReference(edmType as IEdmEntityReferenceType, isNullable); + case EdmTypeKind.Enum: + return new EdmEnumTypeReference(edmType as IEdmEnumType, isNullable); + case EdmTypeKind.Primitive: + return new EdmPrimitiveTypeReference(edmType as IEdmPrimitiveType, isNullable); + default: + throw Error.NotSupported(Resources.EdmTypeNotSupported, edmType.ToTraceString()); + } + } + + private async Task Update( + EdmEntityObject edmEntityObject, + bool isFullReplaceUpdate, + CancellationToken cancellationToken) + { + CheckModelState(); + var path = GetPath(); + var entitySet = path.NavigationSource as IEdmEntitySet; + if (entitySet == null) + { + throw new NotImplementedException(Resources.UpdateOnlySupportedOnEntitySet); + } + + var propertiesInEtag = await GetOriginalValues(entitySet).ConfigureAwait(false); + if (propertiesInEtag == null) + { + throw new StatusCodeException((HttpStatusCode)428, Resources.PreconditionRequired); + } + + // In case of type inheritance, the actual type will be different from entity type + // This is only needed for put case, and does not need for patch case + // For put request, it will create a new, blank instance of the entity. + // copy over the key values and set any updated values from the client on the new instance. + // Then apply all the properties of the new instance to the instance to be updated. + // This will set any unspecified properties to their default value. + var expectedEntityType = path.EdmType; + var actualEntityType = path.EdmType as IEdmStructuredType; + if (edmEntityObject.ActualEdmType != null) + { + expectedEntityType = edmEntityObject.ExpectedEdmType; + actualEntityType = edmEntityObject.ActualEdmType; + } + + var updateItem = new DataModificationItem( + entitySet.Name, + expectedEntityType.GetClrType(Api.ServiceProvider), + actualEntityType.GetClrType(Api.ServiceProvider), + RestierEntitySetOperation.Update, + RestierQueryBuilder.GetPathKeyValues(path), + propertiesInEtag, + edmEntityObject.CreatePropertyDictionary(actualEntityType, api, false)) + { + IsFullReplaceUpdateRequest = isFullReplaceUpdate + }; + + var changeSetProperty = Request.GetChangeSet(); + if (changeSetProperty == null) + { + var changeSet = new ChangeSet(); + changeSet.Entries.Add(updateItem); + + var result = await Api.SubmitAsync(changeSet, cancellationToken).ConfigureAwait(false); + } + else + { + changeSetProperty.ChangeSet.Entries.Add(updateItem); + + await changeSetProperty.OnChangeSetCompleted(Request).ConfigureAwait(false); + } + + return CreateUpdatedODataResult(updateItem.Resource); + } + + private HttpResponseMessage CreateQueryResponse(IQueryable query, IEdmType edmType, ETag etag) + { + var typeReference = GetTypeReference(edmType); + BaseSingleResult singleResult = null; + HttpResponseMessage response = null; + + if (typeReference.IsPrimitive()) + { + if (shouldReturnCount || shouldWriteRawValue) + { + var rawResult = new RawResult(query, typeReference); + singleResult = rawResult; + response = Request.CreateResponse(HttpStatusCode.OK, rawResult); + } + else + { + var primitiveResult = new PrimitiveResult(query, typeReference); + singleResult = primitiveResult; + response = Request.CreateResponse(HttpStatusCode.OK, primitiveResult); + } + } + + if (typeReference.IsComplex()) + { + var complexResult = new ComplexResult(query, typeReference); + singleResult = complexResult; + response = Request.CreateResponse(HttpStatusCode.OK, complexResult); + } + + if (typeReference.IsEnum()) + { + if (shouldWriteRawValue) + { + var rawResult = new RawResult(query, typeReference); + singleResult = rawResult; + response = Request.CreateResponse(HttpStatusCode.OK, rawResult); + } + else + { + var enumResult = new EnumResult(query, typeReference); + singleResult = enumResult; + response = Request.CreateResponse(HttpStatusCode.OK, enumResult); + } + } + + if (singleResult != null) + { + if (singleResult.Result == null) + { + // Per specification, If the property is single-valued and has the null value, + // the service responds with 204 No Content. + return Request.CreateResponse(HttpStatusCode.NoContent); + } + + return response; + } + + if (typeReference.IsCollection()) + { + var elementType = typeReference.AsCollection().ElementType(); + if (elementType.IsPrimitive() || elementType.IsEnum()) + { + return Request.CreateResponse(HttpStatusCode.OK, new NonResourceCollectionResult(query, typeReference)); + } + + return Request.CreateResponse(HttpStatusCode.OK, new ResourceSetResult(query, typeReference)); + } + + var entityResult = query.SingleOrDefault(); + if (entityResult == null) + { + return Request.CreateResponse(HttpStatusCode.NoContent); + } + + // Check the ETag here + if (etag != null) + { + // request with If-Match header, if match, then should return whole content + // request with If-Match header, if not match, then should return 412 + // request with If-None-Match header, if match, then should return 304 + // request with If-None-Match header, if not match, then should return whole content + etag.EntityType = query.ElementType; + query = etag.ApplyTo(query); + entityResult = query.SingleOrDefault(); + if (entityResult == null && !etag.IsIfNoneMatch) + { + return Request.CreateResponse(HttpStatusCode.PreconditionFailed); + } + else if (entityResult == null) + { + return Request.CreateResponse(HttpStatusCode.NotModified); + } + } + + // Using reflection to create response for single entity so passed in parameter is not object type, + // but will be type of real entity type, then EtagMessageHandler can be used to set ETAG header + // when response is single entity. + // There are three HttpRequestMessageExtensions class defined in different assembles + + // Fix by @xuzhg in PR #609. + var assembly = System.Reflection.Assembly.GetAssembly(typeof(AcceptVerbsAttribute)); + var type = assembly.GetType("System.Net.Http.HttpRequestMessageExtensions"); + var genericMethod = type.GetMethods() + .Where(m => m.Name == "CreateResponse" && m.GetParameters().Length == 3); + var method = genericMethod.FirstOrDefault().MakeGenericMethod(query.ElementType); + response = method.Invoke(null, new object[] { Request, HttpStatusCode.OK, entityResult }) as HttpResponseMessage; + return response; + } + + private IQueryable GetQuery(ODataPath path) + { + var builder = new RestierQueryBuilder(Api, path); + var queryable = builder.BuildQuery(); + shouldReturnCount = builder.IsCountPathSegmentPresent; + shouldWriteRawValue = builder.IsValuePathSegmentPresent; + + return queryable; + } + + /// + /// + /// + /// + /// + /// + /// + private async Task<(IQueryable Queryable, ETag Etag)> ApplyQueryOptionsAsync(IQueryable queryable, ODataPath path, bool applyCount) + { + ETag etag = null; + + if (shouldWriteRawValue) + { + // Query options don't apply to $value. + return (queryable, null); + } + + var properties = Request.ODataProperties(); + var model = await Api.GetModelAsync().ConfigureAwait(false); + var queryContext = new ODataQueryContext(model, queryable.ElementType, path); + var queryOptions = new ODataQueryOptions(queryContext, Request); + + // Get etag for query request + if (queryOptions.IfMatch != null) + { + etag = queryOptions.IfMatch; + } + else if (queryOptions.IfNoneMatch != null) + { + etag = queryOptions.IfNoneMatch; + } + + // TODO GitHubIssue#41 : Ensure stable ordering for query + var settings = Api.GetApiService(); + + if (shouldReturnCount) + { + // Query options other than $filter and $search don't apply to $count. + queryable = queryOptions.ApplyTo(queryable, settings, AllowedQueryOptions.All ^ AllowedQueryOptions.Filter); + return (queryable, etag); + } + + if (queryOptions.Count != null && !applyCount) + { + var queryExecutorOptions = Api.GetApiService(); + queryExecutorOptions.IncludeTotalCount = queryOptions.Count.Value; + queryExecutorOptions.SetTotalCount = value => properties.TotalCount = value; + } + + // Validate query before apply, and query setting like MaxExpansionDepth can be customized here + var validationSettings = Api.GetApiService(); + queryOptions.Validate(validationSettings); + + // Entity count can NOT be evaluated at this point of time because the source + // expression is just a placeholder to be replaced by the expression sourcer. + if (!applyCount) + { + queryable = queryOptions.ApplyTo(queryable, settings, AllowedQueryOptions.Count); + } + else + { + queryable = queryOptions.ApplyTo(queryable, settings); + } + + return (queryable, etag); + } + + private async Task ExecuteQuery(IQueryable queryable, CancellationToken cancellationToken) + { + var queryRequest = new QueryRequest(queryable) + { + ShouldReturnCount = shouldReturnCount + }; + + var queryResult = await Api.QueryAsync(queryRequest, cancellationToken).ConfigureAwait(false); + var result = queryResult.Results.AsQueryable(); + return result; + } + + private ODataPath GetPath() + { + var properties = Request.ODataProperties(); + if (properties == null) + { + throw new InvalidOperationException(Resources.InvalidODataInfoInRequest); + } + + var path = properties.Path; + if (path == null) + { + throw new InvalidOperationException(Resources.InvalidEmptyPathInRequest); + } + + return path; + } + + private Task ExecuteOperationAsync( + Func getParaValueFunc, + string operationName, + bool isFunction, + IQueryable bindingParameterValue, + CancellationToken cancellationToken) + { + var executor = Api.GetApiService(); + + var context = new OperationContext( + getParaValueFunc, + operationName, + Api, + isFunction, + bindingParameterValue, + Request.GetRequestContainer()) + { + Request = Request + }; + var result = executor.ExecuteOperationAsync(context, cancellationToken); + return result; + } + + private async Task> GetOriginalValues(IEdmEntitySet entitySet) + { + var originalValues = new Dictionary(); + + var etagHeaderValue = Request.Headers.IfMatch.SingleOrDefault(); + if (etagHeaderValue != null) + { + var etag = Request.GetETag(etagHeaderValue); + etag.ApplyTo(originalValues); + + originalValues.Add(IfMatchKey, etagHeaderValue.Tag); + return originalValues; + } + + etagHeaderValue = Request.Headers.IfNoneMatch.SingleOrDefault(); + if (etagHeaderValue != null) + { + var etag = Request.GetETag(etagHeaderValue); + etag.ApplyTo(originalValues); + + originalValues.Add(IfNoneMatchKey, etagHeaderValue.Tag); + return originalValues; + } + + // return 428(Precondition Required) if entity requires concurrency check. + var model = await Api.GetModelAsync().ConfigureAwait(false); + var needEtag = model.IsConcurrencyCheckEnabled(entitySet); + if (needEtag) + { + return null; + } + + return originalValues; + } + + private IHttpActionResult CreateCreatedODataResult(object entity) => CreateResult(typeof(CreatedODataResult<>), entity); + + private IHttpActionResult CreateUpdatedODataResult(object entity) => CreateResult(typeof(UpdatedODataResult<>), entity); + + private IHttpActionResult CreateResult(Type resultType, object result) + { + var genericResultType = resultType.MakeGenericType(result.GetType()); + + return (IHttpActionResult)Activator.CreateInstance(genericResultType, result, this); + } + + private void CheckModelState() + { + if (!ModelState.IsValid) + { + var errorList = ( + from item in ModelState + where item.Value.Errors.Any() + select + string.Format( + CultureInfo.InvariantCulture, + "{{ Error: {0}, Exception {1} }}", + item.Value.Errors[0].ErrorMessage, + item.Value.Errors[0].Exception.Message)).ToList(); + + throw new ODataException( + string.Format( + CultureInfo.InvariantCulture, + Resources.ModelStateIsNotValid, + string.Join(";", errorList))); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierPayloadValueConverter.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierPayloadValueConverter.cs new file mode 100644 index 0000000..8e49f06 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierPayloadValueConverter.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// The default payload value converter in RESTier. + /// + public class RestierPayloadValueConverter : ODataPayloadValueConverter + { + /// + /// Converts the given primitive value defined in a type definition from the payload object. + /// + /// The given CLR value. + /// The expected type reference from model. + /// The converted payload value of the underlying type. + public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference) + { + if (edmTypeReference != null) + { + // System.DateTime is shared by *Edm.Date and Edm.DateTimeOffset. + if (value is DateTime) + { + var dateTimeValue = (DateTime)value; + + // System.DateTime[SqlType = Date] => Edm.Library.Date + if (edmTypeReference.IsDate()) + { + return new Date(dateTimeValue.Year, dateTimeValue.Month, dateTimeValue.Day); + } + + // System.DateTime[SqlType = DateTime or DateTime2] => Edm.DateTimeOffset + // If DateTime.Kind equals Local, offset should equal the offset of the system's local time zone + if (dateTimeValue.Kind == DateTimeKind.Local) + { + return new DateTimeOffset(dateTimeValue, TimeZoneInfo.Local.GetUtcOffset(dateTimeValue)); + } + + return new DateTimeOffset(dateTimeValue, TimeSpan.Zero); + } + + // System.TimeSpan is shared by *Edm.TimeOfDay and Edm.Duration: + // System.TimeSpan[SqlType = Time] => Edm.Library.TimeOfDay + // System.TimeSpan[SqlType = Time] => System.TimeSpan[EdmType = Duration] + if (edmTypeReference.IsTimeOfDay() && value is TimeSpan) + { + var timeSpanValue = (TimeSpan)value; + return (TimeOfDay)timeSpanValue; + } + + // System.DateTime is converted to System.DateTimeOffset in OData Web API. + // In order not to break ODL serialization when the EDM type is Edm.Date, + // need to convert System.DateTimeOffset back to Edm.Date. + if (edmTypeReference.IsDate() && value is DateTimeOffset) + { + var dateTimeOffsetValue = (DateTimeOffset)value; + return new Date(dateTimeOffsetValue.Year, dateTimeOffsetValue.Month, dateTimeOffsetValue.Day); + } + } + + return base.ConvertToPayloadValue(value, edmTypeReference); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseCollectionResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseCollectionResult.cs new file mode 100644 index 0000000..742de30 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseCollectionResult.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a collection of objects being returned from an action. + /// + internal abstract class BaseCollectionResult : BaseResult + { + + /// + /// Initializes a new instance of the class. + /// + /// The query that returns a collection of objects. + /// The EDM type reference of the objects. + protected BaseCollectionResult(IQueryable query, IEdmTypeReference edmType) + : base(edmType) + { + Ensure.NotNull(query, nameof(query)); + + Query = query; + Type = query.GetType(); + } + + /// + /// Gets the query that returns a collection of objects. + /// + public IQueryable Query { get; private set; } + + /// + /// Gets the type of the query. + /// + public Type Type { get; private set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseResult.cs new file mode 100644 index 0000000..86d9051 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseResult.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents the result of an OData query. + /// + internal abstract class BaseResult + { + private readonly IEdmTypeReference edmType; + + /// + /// Initializes a new instance of the class. + /// + /// The EDM type reference of the OData result. + protected BaseResult(IEdmTypeReference edmType) + { + Ensure.NotNull(edmType, nameof(edmType)); + + this.edmType = edmType; + } + + /// + /// Gets the EDM type reference of the OData result. + /// + public IEdmTypeReference EdmType => edmType; + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseSingleResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseSingleResult.cs new file mode 100644 index 0000000..253f824 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/BaseSingleResult.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a single object being returned from an action. + /// + internal abstract class BaseSingleResult : BaseResult + { + /// + /// Initializes a new instance of the class. + /// + /// The query that returns an object. + /// The EDM type reference of the object. + protected BaseSingleResult(IQueryable query, IEdmTypeReference edmType) + : base(edmType) + { + Ensure.NotNull(query, nameof(query)); + + Result = query.SingleOrDefault(); + Type = query.ElementType; + } + + /// + /// Gets the result object. + /// + public object Result { get; private set; } + + /// + /// Gets the type of the result object. + /// + public Type Type { get; private set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ComplexResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ComplexResult.cs new file mode 100644 index 0000000..336c9e8 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ComplexResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a single complex value being returned from an action. + /// + internal class ComplexResult : BaseSingleResult + { + /// + /// Initializes a new instance of the class. + /// + /// The query that returns a complex value. + /// The EDM type reference of the complex value. + public ComplexResult(IQueryable query, IEdmTypeReference edmType) + : base(query, edmType) + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/EnumResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/EnumResult.cs new file mode 100644 index 0000000..7da8ada --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/EnumResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a single enum value being returned from an action. + /// + internal class EnumResult : BaseSingleResult + { + /// + /// Initializes a new instance of the class. + /// + /// The query that returns a enum value. + /// The EDM type reference of the enum value. + public EnumResult(IQueryable query, IEdmTypeReference edmType) + : base(query, edmType) + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/NonResourceCollectionResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/NonResourceCollectionResult.cs new file mode 100644 index 0000000..e404b6b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/NonResourceCollectionResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a collection of non-entity or complex values being returned from an action. + /// + internal class NonResourceCollectionResult : BaseCollectionResult + { + /// + /// Initializes a new instance of the class. + /// + /// The query that returns a collection of non-entity or complex values. + /// The EDM type reference of the values. + public NonResourceCollectionResult(IQueryable query, IEdmTypeReference edmType) + : base(query, edmType) + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/PrimitiveResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/PrimitiveResult.cs new file mode 100644 index 0000000..62bc1d9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/PrimitiveResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a single primitive value being returned from an action. + /// + internal class PrimitiveResult : BaseSingleResult + { + /// + /// Initializes a new instance of the class. + /// + /// The query that returns a primitive value. + /// The EDM type reference of the primitive value. + public PrimitiveResult(IQueryable query, IEdmTypeReference edmType) + : base(query, edmType) + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/RawResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/RawResult.cs new file mode 100644 index 0000000..0d1e90a --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/RawResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a raw value being returned from an action. + /// + internal class RawResult : BaseSingleResult + { + /// + /// Initializes a new instance of the class. + /// + /// The query that returns a primitive value. + /// The EDM type reference of the primitive value. + public RawResult(IQueryable query, IEdmTypeReference edmType) + : base(query, edmType) + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ResourceSetResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ResourceSetResult.cs new file mode 100644 index 0000000..f41580d --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Results/ResourceSetResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.AspNet +{ + /// + /// Represents a collection of entity instances being returned from an action. + /// + internal class ResourceSetResult : BaseCollectionResult + { + /// + /// Initializes a new instance of the class. + /// + /// The query that returns a collection of resources. + /// The EDM type reference of the entities or complex. + public ResourceSetResult(IQueryable query, IEdmTypeReference edmType) + : base(query, edmType) + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs new file mode 100644 index 0000000..e272f99 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.Restier.AspNet +{ + /// + /// The default routing convention implementation. + /// + internal class RestierRoutingConvention : IODataRoutingConvention + { + private const string RestierControllerName = "Restier"; + private const string MethodNameOfGet = "Get"; + private const string MethodNameOfPost = "Post"; + private const string MethodNameOfPut = "Put"; + private const string MethodNameOfPatch = "Patch"; + private const string MethodNameOfDelete = "Delete"; + private const string MethodNameOfPostAction = "PostAction"; + + /// + /// Selects OData controller based on parsed OData URI + /// + /// Parsed OData URI + /// Incoming HttpRequest + /// Prefix for controller name + public string SelectController(ODataPath odataPath, HttpRequestMessage request) + { + Ensure.NotNull(odataPath, nameof(odataPath)); + Ensure.NotNull(request, nameof(request)); + + if (IsMetadataPath(odataPath)) + { + return null; + } + + // If user has defined something like PeopleController for the entity set People, + // Then whether there is an action in that controller is checked + // If controller has action for request, will be routed to that controller. + // Cannot mark EntitySetRoutingConversion has higher priority as there will no way + // to route to RESTier controller if there is EntitySet controller but no related action. + if (HasControllerForEntitySetOrSingleton(odataPath, request)) + { + // Fall back to routing conventions defined by OData Web API. + return null; + } + + return RestierControllerName; + } + + /// + /// Selects the appropriate action based on the parsed OData URI. + /// + /// Parsed OData URI + /// Context for HttpController + /// Mapping from action names to HttpActions + /// String corresponding to controller action name + public string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + // TODO GitHubIssue#44 : implement action selection for $ref, navigation scenarios, etc. + Ensure.NotNull(odataPath, nameof(odataPath)); + Ensure.NotNull(controllerContext, nameof(controllerContext)); + Ensure.NotNull(actionMap, nameof(actionMap)); + + if (!(controllerContext.Controller is RestierController)) + { + // RESTier cannot select action on controller which is not RestierController. + return null; + } + + var method = controllerContext.Request.Method; + var lastSegment = odataPath.Segments.LastOrDefault(); + var isAction = IsAction(lastSegment); + + if (method == HttpMethod.Get && !IsMetadataPath(odataPath) && !isAction) + { + return MethodNameOfGet; + } + + if (method == HttpMethod.Post && isAction) + { + return MethodNameOfPostAction; + } + + if (method == HttpMethod.Post) + { + return MethodNameOfPost; + } + + if (method == HttpMethod.Delete) + { + return MethodNameOfDelete; + } + + if (method == HttpMethod.Put) + { + return MethodNameOfPut; + } + + if (method == new HttpMethod("PATCH")) + { + return MethodNameOfPatch; + } + + return null; + } + + private static bool IsMetadataPath(ODataPath odataPath) + { + return odataPath.PathTemplate == "~" || odataPath.PathTemplate == "~/$metadata"; + } + + private static bool HasControllerForEntitySetOrSingleton(ODataPath odataPath, HttpRequestMessage request) + { + string controllerName = null; + + var firstSegment = odataPath.Segments.FirstOrDefault(); + if (firstSegment != null) + { + if (firstSegment is EntitySetSegment entitySetSegment) + { + controllerName = entitySetSegment.EntitySet.Name; + } + else + { + if (firstSegment is SingletonSegment singletonSegment) + { + controllerName = singletonSegment.Singleton.Name; + } + } + } + + if (controllerName != null) + { + var services = request.GetConfiguration().Services; + + var controllers = services.GetHttpControllerSelector().GetControllerMapping(); + if (controllers.TryGetValue(controllerName, out var descriptor) && descriptor != null) + { + // If there is a controller, check whether there is an action + if (HasSelectableAction(request, descriptor)) + { + return true; + } + } + } + + return false; + } + + private static bool HasSelectableAction(HttpRequestMessage request, HttpControllerDescriptor descriptor) + { + var configuration = request.GetConfiguration(); + var actionSelector = configuration.Services.GetActionSelector(); + + // Empty route as this is must and route data is not used by OData routing conversion + var route = new HttpRoute(); + var routeData = new HttpRouteData(route); + + var context = new HttpControllerContext(configuration, routeData, request) + { + ControllerDescriptor = descriptor + }; + + try + { + var action = actionSelector.SelectAction(context); + if (action != null) + { + return true; + } + } + catch (HttpResponseException) + { + // ignored + } + + return false; + } + + private static bool IsAction(ODataPathSegment lastSegment) + { + if (lastSegment is OperationSegment operationSeg) + { + if (operationSeg.Operations.FirstOrDefault() is IEdmAction action) + { + return true; + } + } + + if (lastSegment is OperationImportSegment operationImportSeg) + { + if (operationImportSeg.OperationImports.FirstOrDefault() is IEdmActionImport actionImport) + { + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/app.config b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/app.config new file mode 100644 index 0000000..99ddf3e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/app.config @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNetCore/Microsoft.Restier.AspNetCore.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNetCore/Microsoft.Restier.AspNetCore.csproj new file mode 100644 index 0000000..3dfd04d --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNetCore/Microsoft.Restier.AspNetCore.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.2 + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiBase.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiBase.cs new file mode 100644 index 0000000..c7c2330 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiBase.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.Core +{ + /// + /// Represents a base class for an API. + /// + /// + /// + /// An API configuration is intended to be long-lived, and can be statically cached according to an API type specified when the + /// configuration is created. Additionally, the API model produced as a result of a particular configuration is cached under the same + /// API type to avoid re-computing it on each invocation. + /// + /// + public abstract class ApiBase : IDisposable + { + + #region Private Members + + private static ConcurrentDictionary> publisherServicesCallback = + new ConcurrentDictionary>(); + + private static readonly Action emptyConfig = _ => { }; + + private ApiConfiguration apiConfiguration; + + private readonly DefaultSubmitHandler submitHandler; + + #endregion + + #region Public Properties + + /// + /// Gets the which contains all services of this . + /// + public IServiceProvider ServiceProvider { get; private set; } + + #endregion + + #region Internal Properties + + /// + /// Gets the API configuration for this API. + /// + internal ApiConfiguration Configuration + { + get + { + if (apiConfiguration == null) + { + apiConfiguration = ServiceProvider.GetService(); + } + + return apiConfiguration; + } + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// An containing all services of this . + /// + protected ApiBase(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + submitHandler = new DefaultSubmitHandler(serviceProvider); + } + + #endregion + + #region Static Methods + + /// + /// Configure services for this API. + /// + /// + /// The Api type. + /// + /// + /// The with which is used to store all services. + /// + /// The . + //[CLSCompliant(false)] + public static IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + // Add core and convention's services + services = services.AddCoreServices(apiType) + .AddConventionBasedServices(apiType); + + // This is used to add the publisher's services + GetPublisherServiceCallback(apiType)(services); + + return services; + } + + /// + /// Adds a configuration procedure for apiType. + /// This is expected to be called by publisher like WebApi to add services. + /// + /// + /// The Api Type. + /// + /// + /// An action that will be called during the configuration of apiType. + /// + //[CLSCompliant(false)] + public static void AddPublisherServices(Type apiType, Action configurationCallback) + { + publisherServicesCallback.AddOrUpdate(apiType, configurationCallback, (type, existing) => existing + configurationCallback); + } + + /// + /// Get publisher registering service callback for specified Api. + /// + /// The Api type of which to get the publisher registering service callback. + /// The service registering callback. + //[CLSCompliant(false)] + public static Action GetPublisherServiceCallback(Type apiType) + { + if (publisherServicesCallback.TryGetValue(apiType, out var val)) + { + return val; + } + + return emptyConfig; + } + + #endregion + + #region Public Methods + + /// + /// Asynchronously submits changes made using an API context. + /// + /// A change set, or null to submit existing pending changes. + /// An optional cancellation token. + /// A task that represents the asynchronous operation whose result is a submit result. + public async Task SubmitAsync(ChangeSet changeSet = null, CancellationToken cancellationToken = default) + { + var submitContext = new SubmitContext(ServiceProvider, changeSet); + return await submitHandler.SubmitAsync(submitContext, cancellationToken).ConfigureAwait(false); + } + + #endregion + + #region IDisposable Pattern + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + + /// + /// + /// + /// + /// RWM: See https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1063-implement-idisposable-correctly?view=vs-2017 for more information. + protected virtual void Dispose(bool disposing) + { + // RWM: This Dispose method isn't implemented properly, and may actually be doing more harm than good. + // I'm leaving it for now so we can open an issue and ask the question if this class needs to do more on Dispose, + // But I have a feeling we need to kill this with fire. + if (disposing) + { + // free managed resources + } + } + + #endregion + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiConfiguration.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiConfiguration.cs new file mode 100644 index 0000000..f7d5b98 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/ApiConfiguration.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core +{ + /// + /// Represents a configuration that defines an API. + /// + /// + /// + /// An API configuration defines the model and behavior of an API + /// through a set of registered Api services in DI container. + /// + /// + /// Api services may be singletons, meaning there is at most one instance, + /// or scoped, in which case there will be one instances of the services for each scope. + /// + /// + internal class ApiConfiguration + { + private Task modelTask; + + internal IEdmModel Model { get; private set; } + + internal TaskCompletionSource CompleteModelGeneration(out Task running) + { + var source = new TaskCompletionSource(TaskCreationOptions.AttachedToParent); + var runningTask = Interlocked.CompareExchange(ref modelTask, source.Task, null); + if (runningTask != null) + { + running = runningTask; + source.SetCanceled(); + return null; + } + + source.Task.ContinueWith( + task => + { + if (task.Status == TaskStatus.RanToCompletion) + { + Model = task.Result; + } + else + { + // Set modelTask null to allow retrying GetModelAsync. + Interlocked.Exchange(ref modelTask, null); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + running = null; + return source; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs new file mode 100644 index 0000000..f0d15de --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.Core +{ + /// + /// A convention-based change set item authorizer. + /// + internal class ConventionBasedChangeSetItemAuthorizer : IChangeSetItemAuthorizer + { + private Type targetType; + + private ConventionBasedChangeSetItemAuthorizer(Type targetType) + { + Ensure.NotNull(targetType, nameof(targetType)); + this.targetType = targetType; + } + + /// + public static void ApplyTo(IServiceCollection services, Type targetType) + { + Ensure.NotNull(services, nameof(services)); + Ensure.NotNull(targetType, nameof(targetType)); + services.AddService((sp, next) => new ConventionBasedChangeSetItemAuthorizer(targetType)); + } + + /// + public Task AuthorizeAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + Ensure.NotNull(context, nameof(context)); + var result = true; + + var returnType = typeof(bool); + var dataModification = (DataModificationItem)item; + var methodName = ConventionBasedMethodNameFactory.GetEntitySetMethodName(dataModification, RestierPipelineState.Authorization); + var method = targetType.GetQualifiedMethod(methodName); + + if (method != null && method.IsFamily && method.ReturnType == returnType) + { + object target = null; + if (!method.IsStatic) + { + target = context.GetApiService(); + if (target == null || !targetType.IsInstanceOfType(target)) + { + return Task.FromResult(result); + } + } + + var parameters = method.GetParameters(); + if (parameters.Length == 0) + { + result = (bool)method.Invoke(target, null); + } + } + + return Task.FromResult(result); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemFilter.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemFilter.cs new file mode 100644 index 0000000..7e1c2c4 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemFilter.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.Core +{ + /// + /// A convention-based change set item processor which calls logic like OnInserting and OnInserted. + /// + internal class ConventionBasedChangeSetItemFilter : IChangeSetItemFilter + { + private Type targetType; + + internal ConventionBasedChangeSetItemFilter(Type targetType) + { + Ensure.NotNull(targetType, nameof(targetType)); + this.targetType = targetType; + } + + /// + public static void ApplyTo(IServiceCollection services, Type targetType) + { + Ensure.NotNull(services, nameof(services)); + Ensure.NotNull(targetType, nameof(targetType)); + services.AddService((sp, next) => new ConventionBasedChangeSetItemFilter(targetType)); + } + + /// + public Task OnChangeSetItemProcessingAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + return InvokeProcessorMethodAsync(context, item, RestierPipelineState.PreSubmit); + } + + /// + public Task OnChangeSetItemProcessedAsync(SubmitContext context, ChangeSetItem item, CancellationToken cancellationToken) + { + return InvokeProcessorMethodAsync(context, item, RestierPipelineState.PostSubmit); + } + + /// + /// + /// + /// + /// + private static object[] GetParameters(ChangeSetItem item) + { + switch (item.Type) + { + case ChangeSetItemType.DataModification: + var dataModification = (DataModificationItem)item; + return new object[] { dataModification.Resource }; + + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, item.Type)); + } + } + + /// + /// + /// + /// + /// + /// + private static bool ParametersMatch(ParameterInfo[] methodParameters, object[] parameters) + { + return methodParameters.Length == parameters.Length && !methodParameters.Where((mp, i) => !mp.ParameterType.IsInstanceOfType(parameters[i])).Any(); + } + + /// + /// + /// + /// + /// + /// + /// + private Task InvokeProcessorMethodAsync(SubmitContext context, ChangeSetItem item, RestierPipelineState pipelineState) + { + var dataModification = (DataModificationItem)item; + var expectedMethodName = ConventionBasedMethodNameFactory.GetEntitySetMethodName(dataModification, pipelineState); + var expectedMethod = targetType.GetQualifiedMethod(expectedMethodName); + if (!IsUsable(expectedMethod)) + { + if (expectedMethod != null) + { + Debug.WriteLine($"Restier Filter found '{expectedMethodName}' but it is unaccessible due to its protection level. Change it to be 'protected internal'."); + } + else + { + var actualMethodName = expectedMethodName.Replace(dataModification.ExpectedResourceType.Name, dataModification.ResourceSetName); + var actualMethod = targetType.GetQualifiedMethod(actualMethodName); + if (actualMethod != null) + { + Debug.WriteLine($"BREAKING: Restier Filter expected'{expectedMethodName}' but found '{actualMethodName}'. Please correct the method name."); + } + } + } + else + { + object target = null; + if (!expectedMethod.IsStatic) + { + target = context.GetApiService(); + if (target == null || !targetType.IsInstanceOfType(target)) + { + return Task.WhenAll(); + } + } + + var parameters = GetParameters(item); + var methodParameters = expectedMethod.GetParameters(); + if (ParametersMatch(methodParameters, parameters)) + { + var result = expectedMethod.Invoke(target, parameters); + if (result is Task resultTask) + { + return resultTask; + } + } + } + + return Task.WhenAll(); + } + + private static bool IsUsable(MethodInfo info) => (info != null && (info.ReturnType == typeof(void) || typeof(Task).IsAssignableFrom(info.ReturnType))); + + + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemValidator.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemValidator.cs new file mode 100644 index 0000000..4b3326a --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemValidator.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.Core +{ + /// + /// A convention-based change set item validator. + /// + internal class ConventionBasedChangeSetItemValidator : + IChangeSetItemValidator + { + /// + public Task ValidateChangeSetItemAsync( SubmitContext context, ChangeSetItem item, Collection validationResults, + CancellationToken cancellationToken) + { + Ensure.NotNull(validationResults, nameof(validationResults)); + if (item is DataModificationItem dataModificationItem) + { + var resource = dataModificationItem.Resource; + + // TODO GitHubIssue#50 : should this PropertyDescriptorCollection be cached? + var properties = new AssociatedMetadataTypeTypeDescriptionProvider(resource.GetType()) + .GetTypeDescriptor(resource).GetProperties(); + + var validationContext = new ValidationContext(resource); + + foreach (PropertyDescriptor property in properties) + { + validationContext.MemberName = property.Name; + + var validationAttributes = property.Attributes.OfType(); + foreach (var validationAttribute in validationAttributes) + { + var value = property.GetValue(resource); + var validationResult = validationAttribute.GetValidationResult(value, validationContext); + if (validationResult != ValidationResult.Success) + { + validationResults.Add(new ChangeSetItemValidationResult() + { + Id = validationAttribute.GetType().FullName, + Message = validationResult.ErrorMessage, + Severity = EventLevel.Error, + Target = resource, + PropertyName = property.Name + }); + } + } + } + } + + return Task.WhenAll(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedMethodNameFactory.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedMethodNameFactory.cs new file mode 100644 index 0000000..945342a --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedMethodNameFactory.cs @@ -0,0 +1,282 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; +using Microsoft.Restier.Core.Operation; +using Microsoft.Restier.Core.Submit; +using System.Collections.Generic; + +namespace Microsoft.Restier.Core +{ + + /// + /// A set of string factory methods than generate Restier names for various possible operations. + /// + public static class ConventionBasedMethodNameFactory + { + + #region Constants + + private const string Can = "Can"; + + private const string On = "On"; + + private const string Ing = "ing"; + + private const string Ed = "ed"; + + #endregion + + #region Private Members + + /// + /// The to exclude from Filter name processing. + /// + private static List ExcludedFilterStates = new List + { + RestierPipelineState.Authorization, + RestierPipelineState.PreSubmit, + RestierPipelineState.PostSubmit + }; + + /// + /// The to exclude from EntitySet Submit name processing. + /// + private static List ExcludedEntitySetSubmitOperations = new List + { + RestierEntitySetOperation.Insert, + RestierEntitySetOperation.Update, + RestierEntitySetOperation.Delete + }; + + /// + /// The to exclude from Method Submit name processing. + /// + private static List ExcludedMethodSubmitOperations = new List + { + RestierOperationMethod.Execute + }; + + #endregion + + #region Public Methods + + /// + /// Generates the complete MethodName for a given , , and . + /// + /// The that contains the details for the EntitySet and the Entities it holds. + /// The part of the Restier pipeline currently executing. + /// The currently being executed. + /// A string representing the fully-realized MethodName. + /// + public static string GetEntitySetMethodName(IEdmEntitySet entitySet, RestierPipelineState restierPipelineState, RestierEntitySetOperation operation) + { + if ( entitySet == null + || (operation == RestierEntitySetOperation.Filter && ExcludedFilterStates.Contains(restierPipelineState)) + || restierPipelineState == RestierPipelineState.Submit && ExcludedEntitySetSubmitOperations.Contains(operation)) + { + return string.Empty; + } + + var prefix = GetPipelinePrefixInternal(restierPipelineState); + + //RWM: If, for some reason, we don't have a prefix, then we don't have a method for this operation. So don't do anything. + if (string.IsNullOrWhiteSpace(prefix)) return string.Empty; + + var operationName = GetRestierOperationNameInternal(operation, restierPipelineState); + var suffix = operation != RestierEntitySetOperation.Filter ? GetPipelineSuffixInternal(restierPipelineState) : string.Empty; + var entityReferenceName = GetEntityReferenceNameInternal(operation, entitySet); + return $"{prefix}{operationName}{suffix}{entityReferenceName}"; + } + + /// + /// Generates the complete MethodName for a given , , and . + /// + /// The that contains the details for the EntitySet and the Entities it holds. + /// The part of the Restier pipeline currently executing. + /// A string representing the fully-realized MethodName. + /// + public static string GetEntitySetMethodName(DataModificationItem item, RestierPipelineState restierPipelineState) + { + if (item == null + || (item.EntitySetOperation == RestierEntitySetOperation.Filter && ExcludedFilterStates.Contains(restierPipelineState)) + || restierPipelineState == RestierPipelineState.Submit && ExcludedEntitySetSubmitOperations.Contains(item.EntitySetOperation)) + { + return string.Empty; + } + + var prefix = GetPipelinePrefixInternal(restierPipelineState); + + //RWM: If, for some reason, we don't have a prefix, then we don't have a method for this operation. So don't do anything. + if (string.IsNullOrWhiteSpace(prefix)) return string.Empty; + + var operationName = GetRestierOperationNameInternal(item.EntitySetOperation, restierPipelineState); + var suffix = item.EntitySetOperation != RestierEntitySetOperation.Filter ? GetPipelineSuffixInternal(restierPipelineState) : string.Empty; + var entityReferenceName = GetEntityReferenceNameInternal(item.EntitySetOperation, item.ResourceSetName, item.ExpectedResourceType.Name); + return $"{prefix}{operationName}{suffix}{entityReferenceName}"; + } + + /// + /// Generates the complete MethodName for a given , , and . + /// + /// The to generate a name for. + /// The part of the Restier pipeline currently executing. + /// The currently being executed. + /// A string representing the fully-realized MethodName. + public static string GetFunctionMethodName(IEdmOperationImport operationImport, RestierPipelineState restierPipelineState, RestierOperationMethod restierOperation) + { + if (operationImport == null) return string.Empty; + return GetFunctionMethodNameInternal(operationImport.Operation.Name, restierPipelineState, restierOperation); + } + + /// + /// Generates the complete MethodName for a given , , and . + /// + /// The to generate a name for. + /// The part of the Restier pipeline currently executing. + /// The currently being executed. + /// A string representing the fully-realized MethodName. + public static string GetFunctionMethodName(OperationContext operationImport, RestierPipelineState restierPipelineState, RestierOperationMethod restierOperation) + { + if (operationImport == null) return string.Empty; + return GetFunctionMethodNameInternal(operationImport.OperationName, restierPipelineState, restierOperation); + } + + #endregion + + #region Private Methods + + /// + /// Generates the right EntityName reference for a given Operation. + /// + /// The to determine the Entity name for. + /// The that contains the details for the EntitySet and the Entities it holds. + /// A string representing the right EntityName reference for a given Operation. + internal static string GetEntityReferenceNameInternal(RestierEntitySetOperation operation, IEdmEntitySet entitySet) + { + //RWM: You filter a set, but you Insert/Update/Delete individual items. + return GetEntityReferenceNameInternal(operation, entitySet.Name, entitySet.EntityType().Name); + } + + /// + /// Generates the right EntityName reference for a given Operation. + /// + /// The to determine the Entity name for. + /// The that contains the name of the EntitySet. + /// The that contains the name of the Entity type. + /// A string representing the right EntityName reference for a given Operation. + internal static string GetEntityReferenceNameInternal(RestierEntitySetOperation operation, string entitySetName, string entityTypeName) + { + //RWM: You filter a set, but you Insert/Update/Delete individual items. + return operation == RestierEntitySetOperation.Filter ? entitySetName : entityTypeName; + } + + /// + /// Generates the complete MethodName for a given , , and . + /// + /// The containing the name of the operation. + /// The part of the Restier pipeline currently executing. + /// The currently being executed. + /// A string representing the fully-realized MethodName. + private static string GetFunctionMethodNameInternal(string operationName, RestierPipelineState restierPipelineState, RestierOperationMethod restierOperation) + { + if (restierPipelineState == RestierPipelineState.Submit && ExcludedMethodSubmitOperations.Contains(restierOperation)) + { + return string.Empty; + } + + var prefix = GetPipelinePrefixInternal(restierPipelineState); + + //RWM: If, for some reason, we don't have a prefix, then we don't have a method for this operation. So don't do anything. + if (string.IsNullOrWhiteSpace(prefix)) return string.Empty; + + var restierOperationName = GetRestierOperationNameInternal(restierOperation, restierPipelineState); + var suffix = GetPipelineSuffixInternal(restierPipelineState); + return $"{prefix}{restierOperationName}{suffix}{operationName}"; + } + + /// + /// Generates the right OperationName string for a given and . + /// + /// The to determine the method name for. + /// The to determine the method name for. + /// A string containing the corrected OperationName, accounting for what the suffix will end up being. + internal static string GetRestierOperationNameInternal(RestierEntitySetOperation operation, RestierPipelineState restierPipelineState) + { + return GetRestierOperationNameInternal(operation.ToString(), restierPipelineState); + } + + /// + /// Generates the right OperationName string for a given and . + /// + /// The to determine the method name for. + /// The to determine the method name for. + /// A string containing the corrected OperationName, accounting for what the suffix will end up being. + internal static string GetRestierOperationNameInternal(RestierOperationMethod operation, RestierPipelineState restierPipelineState) + { + return GetRestierOperationNameInternal(operation.ToString(), restierPipelineState); + } + + /// + /// Generates the right OperationName string for a given and . + /// + /// The string representing the Operation to determine the method name for. + /// The to determine the method name for. + /// A string containing the corrected OperationName, accounting for what the suffix will end up being. + /// This method is for base processing. The other overloads should be used to ensure the right name gets generated. + private static string GetRestierOperationNameInternal(string operation, RestierPipelineState restierPipelineState) + { + switch (restierPipelineState) + { + case RestierPipelineState.PreSubmit: + case RestierPipelineState.PostSubmit: + //RWM: If the last letter of the string is an e, cut off it's head. + return operation.LastIndexOf("e") == operation.Length - 1 ? operation.Substring(0, operation.Length - 1) : operation; + default: + return operation; + } + } + + /// + /// Returns a method prefix string for a given . + /// + /// The to determine the prefix for. + /// + internal static string GetPipelinePrefixInternal(RestierPipelineState restierPipelineState) + { + switch (restierPipelineState) + { + case RestierPipelineState.Authorization: + return Can; + case RestierPipelineState.PreSubmit: + case RestierPipelineState.Submit: + case RestierPipelineState.PostSubmit: + return On; + default: + return string.Empty; + } + } + + /// + /// Returns a method suffix string for a given . + /// + /// The to determine the suffix for. + /// + internal static string GetPipelineSuffixInternal(RestierPipelineState restierPipelineState) + { + switch (restierPipelineState) + { + case RestierPipelineState.PreSubmit: + return Ing; + case RestierPipelineState.PostSubmit: + return Ed; + default: + return string.Empty; + } + } + + #endregion + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs new file mode 100644 index 0000000..656392e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationAuthorizer.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Operation; + +namespace Microsoft.Restier.Core +{ + /// + /// A convention-based operation authorizer. + /// + internal class ConventionBasedOperationAuthorizer : IOperationAuthorizer + { + private Type targetType; + + private ConventionBasedOperationAuthorizer(Type targetType) + { + Ensure.NotNull(targetType, nameof(targetType)); + this.targetType = targetType; + } + + public static void ApplyTo(IServiceCollection services, Type targetType) + { + Ensure.NotNull(services, nameof(services)); + Ensure.NotNull(targetType, nameof(targetType)); + services.AddService((sp, next) => new ConventionBasedOperationAuthorizer(targetType)); + } + + /// + public Task AuthorizeAsync(OperationContext context, CancellationToken cancellationToken) + { + Ensure.NotNull(context, nameof(context)); + var result = true; + + var returnType = typeof(bool); + var methodName = ConventionBasedMethodNameFactory.GetFunctionMethodName(context, RestierPipelineState.Authorization, RestierOperationMethod.Execute); + var method = targetType.GetQualifiedMethod(methodName); + + if (method != null && method.IsFamily && method.ReturnType == returnType) + { + object target = null; + if (!method.IsStatic) + { + target = context.ImplementInstance; + if (target == null || !targetType.IsInstanceOfType(target)) + { + return Task.FromResult(result); + } + } + + var parameters = method.GetParameters(); + if (parameters.Length == 0) + { + result = (bool)method.Invoke(target, null); + } + } + + return Task.FromResult(result); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs new file mode 100644 index 0000000..b68255e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationFilter.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Operation; + +namespace Microsoft.Restier.Core +{ + /// + /// A convention-based change set item filter. + /// + internal class ConventionBasedOperationFilter : IOperationFilter + { + private Type targetType; + + private ConventionBasedOperationFilter(Type targetType) + { + Ensure.NotNull(targetType, nameof(targetType)); + this.targetType = targetType; + } + + /// + public static void ApplyTo(IServiceCollection services, Type targetType) + { + Ensure.NotNull(services, nameof(services)); + Ensure.NotNull(targetType, nameof(targetType)); + services.AddService((sp, next) => new ConventionBasedOperationFilter(targetType)); + } + + /// + public Task OnOperationExecutingAsync(OperationContext context, CancellationToken cancellationToken) + { + return InvokeProcessorMethodAsync(context, RestierPipelineState.PreSubmit); + } + + /// + public Task OnOperationExecutedAsync(OperationContext context, CancellationToken cancellationToken) + { + return InvokeProcessorMethodAsync(context, RestierPipelineState.PostSubmit); + } + + private static bool ParametersMatch(ParameterInfo[] methodParameters, object[] parameters) + { + return methodParameters.Length == parameters.Length && !methodParameters.Where((mp, i) => !mp.ParameterType.IsInstanceOfType(parameters[i])).Any(); + } + + private Task InvokeProcessorMethodAsync(OperationContext context, RestierPipelineState pipelineState) + { + var methodName = ConventionBasedMethodNameFactory.GetFunctionMethodName(context, pipelineState, RestierOperationMethod.Execute); + object[] parameters = null; + if (context.ParameterValues != null) + { + context.ParameterValues.ToArray(); + } + + var method = targetType.GetQualifiedMethod(methodName); + + if (method != null && (method.ReturnType == typeof(void) || typeof(Task).IsAssignableFrom(method.ReturnType))) + { + object target = null; + if (!method.IsStatic) + { + target = context.ImplementInstance; + if (target == null || !targetType.IsInstanceOfType(target)) + { + return Task.WhenAll(); + } + } + + var methodParameters = method.GetParameters(); + if (ParametersMatch(methodParameters, parameters)) + { + var result = method.Invoke(target, parameters); + if (result is Task resultTask) + { + return resultTask; + } + } + } + + return Task.WhenAll(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedQueryExpressionProcessor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedQueryExpressionProcessor.cs new file mode 100644 index 0000000..6498da2 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Conventions/ConventionBasedQueryExpressionProcessor.cs @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core.Query; + +namespace Microsoft.Restier.Core +{ + /// + /// A convention-based query expression processor which will apply OnFilter logic into query expression. + /// + internal class ConventionBasedQueryExpressionProcessor : IQueryExpressionProcessor + { + private Type targetType; + + private ConventionBasedQueryExpressionProcessor(Type targetType) => this.targetType = targetType; + + // Inner should be null unless user add one as inner most + public IQueryExpressionProcessor Inner { get; set; } + + /// + public static void ApplyTo(IServiceCollection services, Type targetType) + { + Ensure.NotNull(services, nameof(services)); + Ensure.NotNull(targetType, nameof(targetType)); + services.AddService((sp, next) => new ConventionBasedQueryExpressionProcessor(targetType) + { + Inner = next, + }); + } + + /// + public Expression Process(QueryExpressionContext context) + { + Ensure.NotNull(context, nameof(context)); + + if (Inner != null) + { + var innerFilteredExpression = Inner.Process(context); + if (innerFilteredExpression != null && innerFilteredExpression != context.VisitedNode) + { + return innerFilteredExpression; + } + } + + if (context.ModelReference is DataSourceStubModelReference dataSourceStubReference) + { + if (!(dataSourceStubReference.Element is IEdmEntitySet entitySet)) + { + return null; + } + + if (!(entitySet.Type is IEdmCollectionType collectionType)) + { + return null; + } + + if (!(collectionType.ElementType.Definition is IEdmEntityType entityType)) + { + return null; + } + + return AppendOnFilterExpression(context, entitySet, entityType); + } + + if (context.ModelReference is PropertyModelReference propertyModelReference && propertyModelReference.Property != null) + { + // Could be a single navigation property or a collection navigation property + var propType = propertyModelReference.Property.Type; + if (propType is IEdmCollectionTypeReference collectionTypeReference) + { + var collectionType = collectionTypeReference.Definition as IEdmCollectionType; + propType = collectionType.ElementType; + } + + if (!(propType.Definition is IEdmEntityType entityType)) + { + return null; + } + + // In case of type inheritance, get the base type + while (entityType.BaseType != null) + { + entityType = (IEdmEntityType)entityType.BaseType; + } + + //Get the model, query it for the entity set of a given type. + var entitySet = context.QueryContext.Model.EntityContainer.EntitySets().FirstOrDefault(c => c.EntityType() == entityType); + if (entitySet == null) + { + return null; + } + + return AppendOnFilterExpression(context, entitySet, entityType); + } + + return null; + } + + private Expression AppendOnFilterExpression(QueryExpressionContext context, IEdmEntitySet entitySet, IEdmEntityType entityType) + { + var expectedMethodName = ConventionBasedMethodNameFactory.GetEntitySetMethodName(entitySet, RestierPipelineState.Submit, RestierEntitySetOperation.Filter); + var expectedMethod = targetType.GetQualifiedMethod(expectedMethodName); + if (expectedMethod == null || (!expectedMethod.IsFamily && !expectedMethod.IsFamilyOrAssembly)) + { + if (expectedMethod != null) + { + Debug.WriteLine($"Restier Filter found '{expectedMethodName}' but it is unaccessible due to its protection level. Change it to be 'protected internal'."); + } + else + { + var actualMethodName = expectedMethodName.Replace(entitySet.Name, entityType.Name); + var actualMethod = targetType.GetQualifiedMethod(actualMethodName); + if (actualMethod != null) + { + Debug.WriteLine($"BREAKING: Restier Filter expected'{expectedMethodName}' but found '{actualMethodName}'. Please correct the method name."); + } + } + return null; + } + + var parameter = expectedMethod.GetParameters().SingleOrDefault(); + if (parameter == null || parameter.ParameterType != expectedMethod.ReturnType) + { + return null; + } + + object apiBase = null; + if (!expectedMethod.IsStatic) + { + apiBase = context.QueryContext.GetApiService(); + if (apiBase == null || !targetType.IsInstanceOfType(apiBase)) + { + return null; + } + } + + // The LINQ expression built below has three cases + // For navigation property, just add a where condition from OnFilter method + // For collection property, will be like "Param_0.Prop.AsQueryable().Where(...)" + // For collection property of derived type, will be like "Param_0.Prop.AsQueryable().Where(...).OfType()" + var returnType = context.VisitedNode.Type.FindGenericType(typeof(IQueryable<>)); + var enumerableQueryParameter = (object)context.VisitedNode; + Type elementType = null; + + if (returnType == null) + { + // This means append for properties model reference + var collectionType = context.VisitedNode.Type.FindGenericType(typeof(ICollection<>)); + if (collectionType == null) + { + return null; + } + + elementType = collectionType.GetGenericArguments()[0]; + returnType = typeof(IQueryable<>).MakeGenericType(elementType); + + enumerableQueryParameter = Expression.Call(ExpressionHelperMethods.QueryableAsQueryableGeneric.MakeGenericMethod(elementType), context.VisitedNode); + } + else + { + elementType = returnType.GetGenericArguments()[0]; + } + + var queryType = typeof(EnumerableQuery<>).MakeGenericType(elementType); + var query = Activator.CreateInstance(queryType, enumerableQueryParameter); + if (!(expectedMethod.Invoke(apiBase, new object[] { query }) is IQueryable result)) + { + return null; + } + + if (expectedMethod.ReturnType == returnType) + { + if (result != query) + { + return result.Expression; + } + } + else + { + // This means calling onFilter against derived type and based type is returned + // Need to convert back to derived type with OfType + result = ExpressionHelpers.OfType(result, elementType); + return result.Expression; + } + + return null; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptionProvider.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptionProvider.cs new file mode 100644 index 0000000..26338a9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptionProvider.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.DataAnnotations +{ + /// + /// Extends the metadata information for a class by adding attributes and property + /// information that is defined in an associated class. + /// + public class AssociatedMetadataTypeTypeDescriptionProvider : TypeDescriptionProvider + { + private Type _associatedMetadataType; + + /// + /// Initializes a new instance of the System.ComponentModel.DataAnnotations.AssociatedMetadataTypeTypeDescriptionProvider + /// class by using the specified type. + /// + /// The type for which the metadata provider is created. + public AssociatedMetadataTypeTypeDescriptionProvider(Type type) + : base(TypeDescriptor.GetProvider(type)) + { + } + + /// + /// Initializes a new instance of the System.ComponentModel.DataAnnotations.AssociatedMetadataTypeTypeDescriptionProvider + /// class by using the specified metadata provider type and associated type. + /// + /// The type for which the metadata provider is created. + /// The associated type that contains the metadata. + /// The value of associatedMetadataType is null. + public AssociatedMetadataTypeTypeDescriptionProvider(Type type, Type associatedMetadataType) + : this(type) + { + _associatedMetadataType = associatedMetadataType ?? throw new ArgumentNullException(nameof(associatedMetadataType)); + } + + /// + /// Gets a type descriptor for the specified type and object. + /// + /// The type of object to retrieve the type descriptor for. + /// An instance of the type. + /// The descriptor that provides metadata for the type. + public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) + { + ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(objectType, instance); + return new AssociatedMetadataTypeTypeDescriptor(baseDescriptor, objectType, _associatedMetadataType); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptor.cs new file mode 100644 index 0000000..e5f8414 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/AssociatedMetadataTypeTypeDescriptor.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using SR = Microsoft.Restier.Core.Resources; + +namespace System.ComponentModel.DataAnnotations +{ + internal class AssociatedMetadataTypeTypeDescriptor : CustomTypeDescriptor + { + private Type AssociatedMetadataType + { + get; + set; + } + + private bool IsSelfAssociated + { + get; + set; + } + + public AssociatedMetadataTypeTypeDescriptor(ICustomTypeDescriptor parent, Type type, Type associatedMetadataType) + : base(parent) + { + AssociatedMetadataType = associatedMetadataType ?? TypeDescriptorCache.GetAssociatedMetadataType(type); + IsSelfAssociated = (type == AssociatedMetadataType); + if (AssociatedMetadataType != null) + { + TypeDescriptorCache.ValidateMetadataType(type, AssociatedMetadataType); + } + } + + public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) + { + return GetPropertiesWithMetadata(base.GetProperties(attributes)); + } + + public override PropertyDescriptorCollection GetProperties() + { + return GetPropertiesWithMetadata(base.GetProperties()); + } + + private PropertyDescriptorCollection GetPropertiesWithMetadata(PropertyDescriptorCollection originalCollection) + { + if (AssociatedMetadataType == null) + { + return originalCollection; + } + + bool customDescriptorsCreated = false; + List tempPropertyDescriptors = new List(); + foreach (PropertyDescriptor propDescriptor in originalCollection) + { + Attribute[] newMetadata = TypeDescriptorCache.GetAssociatedMetadata(AssociatedMetadataType, propDescriptor.Name); + PropertyDescriptor descriptor = propDescriptor; + if (newMetadata.Length > 0) + { + // Create a metadata descriptor that wraps the property descriptor + descriptor = new MetadataPropertyDescriptorWrapper(propDescriptor, newMetadata); + customDescriptorsCreated = true; + } + + tempPropertyDescriptors.Add(descriptor); + } + + if (customDescriptorsCreated) + { + return new PropertyDescriptorCollection(tempPropertyDescriptors.ToArray(), true); + } + return originalCollection; + } + + public override AttributeCollection GetAttributes() + { + // Since normal TD behavior is to return cached attribute instances on subsequent + // calls to GetAttributes, we must be sure below to use the TD APIs to get both + // the base and associated attributes + AttributeCollection attributes = base.GetAttributes(); + if (AssociatedMetadataType != null && !IsSelfAssociated) + { + // Note that the use of TypeDescriptor.GetAttributes here opens up the possibility of + // infinite recursion, in the corner case of two Types referencing each other as + // metadata types (or a longer cycle), though the second condition above saves an immediate such + // case where a Type refers to itself. + Attribute[] newAttributes = TypeDescriptor.GetAttributes(AssociatedMetadataType).OfType().ToArray(); + attributes = AttributeCollection.FromExisting(attributes, newAttributes); + } + return attributes; + } + + private static class TypeDescriptorCache + { + private static readonly Attribute[] emptyAttributes = new Attribute[0]; + // Stores the associated metadata type for a type + private static readonly ConcurrentDictionary _metadataTypeCache = new ConcurrentDictionary(); + + // Stores the attributes for a member info + private static readonly ConcurrentDictionary, Attribute[]> _typeMemberCache = new ConcurrentDictionary, Attribute[]>(); + + // Stores whether or not a type and associated metadata type has been checked for validity + private static readonly ConcurrentDictionary, bool> _validatedMetadataTypeCache = new ConcurrentDictionary, bool>(); + + public static void ValidateMetadataType(Type type, Type associatedType) + { + Tuple typeTuple = new Tuple(type, associatedType); + if (!_validatedMetadataTypeCache.ContainsKey(typeTuple)) + { + CheckAssociatedMetadataType(type, associatedType); + _validatedMetadataTypeCache.TryAdd(typeTuple, true); + } + } + + public static Type GetAssociatedMetadataType(Type type) + { + Type associatedMetadataType = null; + if (_metadataTypeCache.TryGetValue(type, out associatedMetadataType)) + { + return associatedMetadataType; + } + + // Try association attribute + MetadataTypeAttribute attribute = (MetadataTypeAttribute)Attribute.GetCustomAttribute(type, typeof(MetadataTypeAttribute)); + if (attribute != null) + { + associatedMetadataType = attribute.MetadataClassType; + } + _metadataTypeCache.TryAdd(type, associatedMetadataType); + return associatedMetadataType; + } + + private static void CheckAssociatedMetadataType(Type mainType, Type associatedMetadataType) + { + // Only properties from main type + HashSet mainTypeMemberNames = new HashSet(mainType.GetProperties().Select(p => p.Name)); + + // Properties and fields from buddy type + var buddyFields = associatedMetadataType.GetFields().Select(f => f.Name); + var buddyProperties = associatedMetadataType.GetProperties().Select(p => p.Name); + HashSet buddyTypeMembers = new HashSet(buddyFields.Concat(buddyProperties), StringComparer.Ordinal); + + // Buddy members should be a subset of the main type's members + if (!buddyTypeMembers.IsSubsetOf(mainTypeMemberNames)) + { + // Reduce the buddy members to the set not contained in the main members + buddyTypeMembers.ExceptWith(mainTypeMemberNames); + + throw new InvalidOperationException(string.Format(SR.AssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties, + mainType.FullName, + String.Join(", ", buddyTypeMembers.ToArray()))); + } + } + + public static Attribute[] GetAssociatedMetadata(Type type, string memberName) + { + var memberTuple = new Tuple(type, memberName); + Attribute[] attributes; + if (_typeMemberCache.TryGetValue(memberTuple, out attributes)) + { + return attributes; + } + + // Allow fields and properties + MemberTypes allowedMemberTypes = MemberTypes.Property | MemberTypes.Field; + // Only public static/instance members + BindingFlags searchFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; + // Try to find a matching member on type + MemberInfo matchingMember = type.GetMember(memberName, allowedMemberTypes, searchFlags).FirstOrDefault(); + if (matchingMember != null) + { + attributes = Attribute.GetCustomAttributes(matchingMember, true /* inherit */); + } + else + { + attributes = emptyAttributes; + } + + _typeMemberCache.TryAdd(memberTuple, attributes); + return attributes; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataPropertyDescriptorWrapper.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataPropertyDescriptorWrapper.cs new file mode 100644 index 0000000..3260bf7 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataPropertyDescriptorWrapper.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Linq; + +namespace System.ComponentModel.DataAnnotations +{ + internal class MetadataPropertyDescriptorWrapper : PropertyDescriptor + { + private PropertyDescriptor _descriptor; + private bool _isReadOnly; + + public MetadataPropertyDescriptorWrapper(PropertyDescriptor descriptor, Attribute[] newAttributes) + : base(descriptor, newAttributes) + { + _descriptor = descriptor; + var readOnlyAttribute = newAttributes.OfType().FirstOrDefault(); + _isReadOnly = (readOnlyAttribute != null ? readOnlyAttribute.IsReadOnly : false); + } + + public override void AddValueChanged(object component, EventHandler handler) { _descriptor.AddValueChanged(component, handler); } + + public override bool CanResetValue(object component) { return _descriptor.CanResetValue(component); } + + public override Type ComponentType { get { return _descriptor.ComponentType; } } + + public override object GetValue(object component) { return _descriptor.GetValue(component); } + + public override bool IsReadOnly + { + get + { + // Dev10 Bug 594083 + // It's not enough to call the wrapped _descriptor because it does not know anything about + // new attributes passed into the constructor of this class. + return _isReadOnly || _descriptor.IsReadOnly; + } + } + + public override Type PropertyType { get { return _descriptor.PropertyType; } } + + public override void RemoveValueChanged(object component, EventHandler handler) { _descriptor.RemoveValueChanged(component, handler); } + + public override void ResetValue(object component) { _descriptor.ResetValue(component); } + + public override void SetValue(object component, object value) { _descriptor.SetValue(component, value); } + + public override bool ShouldSerializeValue(object component) { return _descriptor.ShouldSerializeValue(component); } + + public override bool SupportsChangeEvents { get { return _descriptor.SupportsChangeEvents; } } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataTypeAttribute.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataTypeAttribute.cs new file mode 100644 index 0000000..7fdf62c --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataAnnotations/MetadataTypeAttribute.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using SR = Microsoft.Restier.Core.Resources; + +namespace System.ComponentModel.DataAnnotations +{ + /// + /// Specifies the metadata class to associate with a data model class. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class MetadataTypeAttribute : Attribute + { + private Type _metadataClassType; + + /// + /// Initializes a new instance of the System.ComponentModel.DataAnnotations.MetadataTypeAttribute + /// class. + /// + /// The metadata class to reference. + /// metadataClassType is null. + public MetadataTypeAttribute(Type metadataClassType) + { + _metadataClassType = metadataClassType; + } + + /// + /// Gets the metadata class that is associated with a data-model partial class. + /// + public Type MetadataClassType + { + get + { + if (_metadataClassType == null) + { + throw new InvalidOperationException(SR.MetadataTypeAttribute_TypeCannotBeNull); + } + + return _metadataClassType; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataSourceStub.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataSourceStub.cs new file mode 100644 index 0000000..aeb4818 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/DataSourceStub.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; + +#pragma warning disable CA1801 // Unused method parameters +namespace Microsoft.Restier.Core +{ + /// + /// Represents method stubs that identify API data source. + /// + /// + /// The methods in this class are stubs that identify API data source + /// inside a query expression. This is a generic way to reference a + /// data source in API. Later in the query pipeline the sourcer from + /// the data provider will replace the stub with the actual data source. + /// + public static class DataSourceStub + { + /// + /// Identifies an entity set, singleton or queryable data + /// resulting from a call to a composable function import. + /// + /// + /// The type of the elements in the queryable data. + /// + /// + /// The name of an entity set, singleton or composable function import. + /// + /// + /// If is a composable function import, + /// the arguments to be passed to the composable function import. + /// + /// + /// A representation of the entity set, singleton or queryable + /// data resulting from a call to the composable function import. + /// + public static IQueryable GetQueryableSource(string name, params object[] arguments) => throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); + + /// + /// Identifies queryable data resulting + /// from a call to a composable function. + /// + /// + /// The type of the elements in the queryable data. + /// + /// + /// The name of a namespace containing the composable function. + /// + /// + /// The name of a composable function. + /// + /// + /// The arguments to be passed to the composable function. + /// + /// + /// A representation of the queryable data resulting + /// from a call to the composable function. + /// + public static IQueryable GetQueryableSource(string namespaceName, string name, params object[] arguments) => throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); + + /// + /// Identifies the value of an extended property of an object. + /// + /// + /// The type of the result. + /// + /// + /// A source object. + /// + /// + /// The name of a property. + /// + /// + /// A representation of the value of the + /// extended property of the object. + /// + public static TResult GetPropertyValue(object source, string propertyName) => throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); + } +} +#pragma warning restore CS1633 // Unrecognized #pragma directive \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierEntitySetOperation.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierEntitySetOperation.cs new file mode 100644 index 0000000..784ea7e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierEntitySetOperation.cs @@ -0,0 +1,32 @@ +namespace Microsoft.Restier.Core +{ + + /// + /// Represents the Restier operations available to an EntitySet. + /// + public enum RestierEntitySetOperation + { + + /// + /// Represents a Filter operation. + /// + Filter = 1, + + /// + /// Represents an Insert operation. + /// + Insert = 2, + + /// + /// Represents an Update operation. + /// + Update = 3, + + /// + /// Represents a Delete operation. + /// + Delete = 4 + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierOperationMethod.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierOperationMethod.cs new file mode 100644 index 0000000..3845257 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierOperationMethod.cs @@ -0,0 +1,19 @@ +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core +{ + + /// + /// Represents the Restier operations available to an . + /// + public enum RestierOperationMethod + { + + /// + /// Represents the OperationImport being executed. + /// + Execute = 1 + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierPipelineState.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierPipelineState.cs new file mode 100644 index 0000000..f7775ad --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Enums/RestierPipelineState.cs @@ -0,0 +1,37 @@ +namespace Microsoft.Restier.Core +{ + + /// + /// Represents the different parts of the Restier request execution pipeline. + /// + public enum RestierPipelineState + { + + /// + /// Represents the first step of the pipeline, when Restier checks to see if the call is allowed. + /// + Authorization = 1, + + /// + /// Represents the second step of the pipeline, where the payload is validated. + /// + Validation = 2, + + /// + /// Represents the third step of the pipeline, where the developer can change the payload before it is submitted. + /// + PreSubmit = 3, + + /// + /// Represents the fourth step of the pipeline, where the action is executed against the Entity Framework DbContext. + /// + Submit = 4, + + /// + /// Represents the fifth step of the pipeline, where you can spin off other work after the action has completed successfully. + /// + PostSubmit = 5 + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs new file mode 100644 index 0000000..d6f126b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents an exception that indicates validation errors occurred on entities. + /// + public class ChangeSetValidationException : Exception + { + private IEnumerable errorValidationResults; + + /// + /// + /// + public ChangeSetValidationException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Message of the exception. + public ChangeSetValidationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Message of the exception. + /// Inner exception. + public ChangeSetValidationException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Gets or sets the failed validation results. + /// + public IEnumerable ValidationResults + { + get + { + if (errorValidationResults == null) + { + return Enumerable.Empty(); + } + else + { + return errorValidationResults; + } + } + + set => errorValidationResults = value; + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/StatusCodeException.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/StatusCodeException.cs new file mode 100644 index 0000000..c7a8888 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Exceptions/StatusCodeException.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; + +namespace Microsoft.Restier.Core +{ + /// + /// This exception is used for 404 Not found response. + /// + [Serializable] + public class StatusCodeException : Exception + { + + #region Properties + + /// + /// + /// + public HttpStatusCode StatusCode { get; private set; } = HttpStatusCode.BadRequest; + + #endregion + + #region Default Constructors + + /// + /// Initializes a new instance of the StatusCodeException class. + /// + public StatusCodeException() + : this(null, null) + { + } + + /// + /// Initializes a new instance of the StatusCodeException class. + /// + /// Plain text error message for this exception. + public StatusCodeException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the StatusCodeException class. + /// + /// Plain text error message for this exception. + /// Exception that caused this exception to be thrown. + public StatusCodeException(string message, Exception innerException) : base(message, innerException) + { + } + + #endregion + + /// + /// Initializes a new instance of the StatusCodeException class. + /// + /// + /// Plain text error message for this exception. + public StatusCodeException(HttpStatusCode statusCode, string message) + : this(statusCode, message, null) + { + } + + /// + /// Initializes a new instance of the StatusCodeException class. + /// + /// + /// Plain text error message for this exception. + /// Exception that caused this exception to be thrown. + public StatusCodeException(HttpStatusCode statusCode, string message, Exception innerException) + : base(message, innerException) + { + StatusCode = statusCode; + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs new file mode 100644 index 0000000..a783be1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs @@ -0,0 +1,508 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Query; + +namespace Microsoft.Restier.Core +{ + /// + /// Represents the API engine and provides a set of static + /// (Shared in Visual Basic) methods for interacting with objects + /// that implement . + /// + public static class ApiBaseExtensions + { + private static readonly MethodInfo SourceCoreMethod = typeof(ApiBaseExtensions) + .GetMember("SourceCore", BindingFlags.NonPublic | BindingFlags.Static) + .Cast() + .Single(m => m.IsGenericMethod); + + private static readonly MethodInfo Source2Method = typeof(DataSourceStub) + .GetMember("GetQueryableSource") + .Cast() + .Single(m => m.GetParameters().Length == 2); + + private static readonly MethodInfo Source3Method = typeof(DataSourceStub) + .GetMember("GetQueryableSource") + .Cast() + .Single(m => m.GetParameters().Length == 3); + + #region GetApiService + + /// + /// Gets a service instance. + /// + /// + /// An API. + /// + /// The service type. + /// The service instance. + public static T GetApiService(this ApiBase api) where T : class + { + Ensure.NotNull(api, nameof(api)); + return api.ServiceProvider.GetService(); + } + + /// + /// Gets all registered service instances. + /// + /// + /// An API. + /// + /// The service type. + /// The ordered collection of service instances. + public static IEnumerable GetApiServices(this ApiBase api) where T : class + { + Ensure.NotNull(api, nameof(api)); + return api.ServiceProvider.GetServices(); + } + + #endregion + + #region PropertyBag + + /// + /// Indicates if this object has a property. + /// + /// + /// An API. + /// + /// + /// The name of a property. + /// + /// + /// true if this object has the + /// property; otherwise, false. + /// + public static bool HasProperty(this ApiBase api, string name) => api.GetPropertyBag().HasProperty(name); + + /// + /// Gets a property. + /// + /// + /// The type of the property. + /// + /// + /// An API. + /// + /// + /// The name of a property. + /// + /// + /// The value of the property. + /// + public static T GetProperty(this ApiBase api, string name) => api.GetPropertyBag().GetProperty(name); + + /// + /// Gets a property. + /// + /// + /// An API. + /// + /// + /// The name of a property. + /// + /// + /// The value of the property. + /// + public static object GetProperty(this ApiBase api, string name) => api.GetPropertyBag().GetProperty(name); + + /// + /// Sets a property. + /// + /// + /// An API. + /// + /// + /// The name of a property. + /// + /// + /// A value for the property. + /// + public static void SetProperty(this ApiBase api, string name, object value) => api.GetPropertyBag().SetProperty(name, value); + + /// + /// Removes a property. + /// + /// + /// An API. + /// + /// + /// The name of a property. + /// + public static void RemoveProperty(this ApiBase api, string name) => api.GetPropertyBag().RemoveProperty(name); + + #endregion + + #region Model + + /// + /// Asynchronously gets an API model using an API context. + /// + /// + /// An API. + /// + /// + /// An optional cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is the API model. + /// + public static async Task GetModelAsync(this ApiBase api, CancellationToken cancellationToken = default) + { + Ensure.NotNull(api, nameof(api)); + + var config = api.Configuration; + if (config.Model != null) + { + return config.Model; + } + + var builder = api.GetApiService(); + if (builder == null) + { + throw new InvalidOperationException(Resources.ModelBuilderNotRegistered); + } + + var source = config.CompleteModelGeneration(out var running); + if (source == null) + { + return await running.ConfigureAwait(false); + } + + try + { + var buildContext = new ModelContext(api.ServiceProvider); + var model = await builder.GetModelAsync(buildContext, cancellationToken).ConfigureAwait(false); + source.SetResult(model); + return model; + } + catch (AggregateException e) + { + source.SetException(e.InnerExceptions); + throw; + } + catch (Exception e) + { + source.SetException(e); + throw; + } + } + + #endregion + + #region GetQueryableSource + + /// + /// Gets a queryable source of data using an API context. + /// + /// + /// An API. + /// + /// + /// The name of an entity set, singleton or composable function import. + /// + /// + /// If is a composable function import, + /// the arguments to be passed to the composable function import. + /// + /// + /// A queryable source. + /// + /// + /// + /// If the name identifies a singleton or a composable function import + /// whose result is a singleton, the resulting queryable source will + /// be configured such that it represents exactly zero or one result. + /// + /// + /// Note that the resulting queryable source cannot be synchronously + /// enumerated as the API engine only operates asynchronously. + /// + /// + public static IQueryable GetQueryableSource(this ApiBase api, string name, params object[] arguments) + { + Ensure.NotNull(api, nameof(api)); + Ensure.NotNull(name, nameof(name)); + + return api.SourceCore(null, name, arguments); + } + + /// + /// Gets a queryable source of data using an API context. + /// + /// + /// An API. + /// + /// + /// The name of a namespace containing a composable function. + /// + /// + /// The name of a composable function. + /// + /// + /// The arguments to be passed to the composable function. + /// + /// + /// A queryable source. + /// + /// + /// + /// If the name identifies a composable function whose result is a + /// singleton, the resulting queryable source will be configured such + /// that it represents exactly zero or one result. + /// + /// + /// Note that the resulting queryable source cannot be synchronously + /// enumerated, as the API engine only operates asynchronously. + /// + /// + public static IQueryable GetQueryableSource(this ApiBase api, string namespaceName, string name, params object[] arguments) + { + Ensure.NotNull(api, nameof(api)); + Ensure.NotNull(namespaceName, nameof(namespaceName)); + Ensure.NotNull(name, nameof(name)); + + return SourceCore(api, namespaceName, name, arguments); + } + + /// + /// Gets a queryable source of data using an API context. + /// + /// + /// The type of the elements in the queryable source. + /// + /// + /// An API. + /// + /// + /// The name of an entity set, singleton or composable function import. + /// + /// + /// If is a composable function import, + /// the arguments to be passed to the composable function import. + /// + /// + /// A queryable source. + /// + /// + /// + /// If the name identifies a singleton or a composable function import + /// whose result is a singleton, the resulting queryable source will + /// be configured such that it represents exactly zero or one result. + /// + /// + /// Note that the resulting queryable source cannot be synchronously + /// enumerated, as the API engine only operates asynchronously. + /// + /// + public static IQueryable GetQueryableSource(this ApiBase api, string name, params object[] arguments) + { + Ensure.NotNull(api, nameof(api)); + Ensure.NotNull(name, nameof(name)); + + var elementType = api.EnsureElementType(null, name); + if (typeof(TElement) != elementType) + { + throw new ArgumentException(Resources.ElementTypeNotMatch); + } + + return SourceCore(null, name, arguments); + } + + /// + /// Gets a queryable source of data using an API context. + /// + /// + /// The type of the elements in the queryable source. + /// + /// + /// An API. + /// + /// + /// The name of a namespace containing a composable function. + /// + /// + /// The name of a composable function. + /// + /// + /// The arguments to be passed to the composable function. + /// + /// + /// A queryable source. + /// + /// + /// + /// If the name identifies a composable function whose result is a + /// singleton, the resulting queryable source will be configured such + /// that it represents exactly zero or one result. + /// + /// + /// Note that the resulting queryable source cannot be synchronously + /// enumerated, as the API engine only operates asynchronously. + /// + /// + public static IQueryable GetQueryableSource(this ApiBase api, string namespaceName, string name, params object[] arguments) + { + Ensure.NotNull(api, nameof(api)); + Ensure.NotNull(namespaceName, nameof(namespaceName)); + Ensure.NotNull(name, nameof(name)); + + var elementType = api.EnsureElementType(namespaceName, name); + if (typeof(TElement) != elementType) + { + throw new ArgumentException(Resources.ElementTypeNotMatch); + } + + return SourceCore(namespaceName, name, arguments); + } + + #endregion + + #region Query + + /// + /// Asynchronously queries for data using an API context. + /// + /// + /// An API. + /// + /// + /// A query request. + /// + /// + /// An optional cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a query result. + /// + public static async Task QueryAsync(this ApiBase api, QueryRequest request, CancellationToken cancellationToken = default(CancellationToken)) + { + Ensure.NotNull(api, nameof(api)); + Ensure.NotNull(request, nameof(request)); + + var queryContext = new QueryContext(api.ServiceProvider, request); + var model = await api.GetModelAsync(cancellationToken).ConfigureAwait(false); + queryContext.Model = model; + return await DefaultQueryHandler.QueryAsync(queryContext, cancellationToken).ConfigureAwait(false); + } + + #endregion + + #region GetQueryableSource Private + + /// + /// + /// + /// + /// + /// + /// + /// + private static IQueryable SourceCore(this ApiBase api, string namespaceName, string name, object[] arguments) + { + var elementType = api.EnsureElementType(namespaceName, name); + var method = SourceCoreMethod.MakeGenericMethod(elementType); + var args = new object[] { namespaceName, name, arguments }; + return method.Invoke(null, args) as IQueryable; + } + + /// + /// + /// + /// + /// + /// + /// + /// + private static IQueryable SourceCore(string namespaceName, string name, object[] arguments) + { + MethodInfo sourceMethod; + Expression[] expressions; + if (namespaceName == null) + { + sourceMethod = Source2Method; + expressions = new Expression[] + { + Expression.Constant(name), + Expression.Constant(arguments, typeof(object[])) + }; + } + else + { + sourceMethod = Source3Method; + expressions = new Expression[] + { + Expression.Constant(namespaceName), + Expression.Constant(name), + Expression.Constant(arguments, typeof(object[])) + }; + } + + return new QueryableSource(Expression.Call(null, sourceMethod.MakeGenericMethod(typeof(TElement)), expressions)); + } + + /// + /// + /// + /// + /// + /// + /// + private static Type EnsureElementType(this ApiBase api, string namespaceName, string name) + { + Type elementType = null; + + var mapper = api.GetApiService(); + if (mapper != null) + { + var modelContext = new ModelContext(api.ServiceProvider); + if (namespaceName == null) + { + mapper.TryGetRelevantType(modelContext, name, out elementType); + } + else + { + mapper.TryGetRelevantType(modelContext, namespaceName, name, out elementType); + } + } + + if (elementType == null) + { + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resources.ElementTypeNotFound, name)); + } + + return elementType; + } + + #endregion + + #region PropertyBag Private + + /// + /// + /// + /// + /// + private static PropertyBag GetPropertyBag(this ApiBase api) + { + Ensure.NotNull(api, nameof(api)); + return api.GetApiService(); + } + + #endregion + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ChainedService.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ChainedService.cs new file mode 100644 index 0000000..90a9a40 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ChainedService.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; + +namespace Microsoft.Restier.Core +{ + internal static class ChainedService where TService : class + { + public static readonly Func DefaultFactory = sp => + { + var instances = sp.GetServices>().Reverse(); + + using (var e = instances.GetEnumerator()) + { + Func next = null; + next = () => + { + if (e.MoveNext()) + { + return e.Current(sp, next); + } + + return null; + }; + + return next(); + } + }; + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/EnumerableExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..8975b2d --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/EnumerableExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace System.Collections +{ + internal static class EnumerableExtensions + { + public static object SingleOrDefault(this IEnumerable enumerable) + { + IEnumerator enumerator = enumerable.GetEnumerator(); + object result = enumerator.MoveNext() ? enumerator.Current : null; + + if (enumerator.MoveNext()) + { + throw new InvalidOperationException( + Microsoft.Restier.Core.Resources.QueryShouldGetSingleRecord); + } + + return result; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/InvocationContextExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/InvocationContextExtensions.cs new file mode 100644 index 0000000..573bbcb --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/InvocationContextExtensions.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Restier.Core +{ + /// + /// Represents the API engine and provides a set of static (Shared in Visual Basic) methods for interacting with objects + /// that implement . + /// + public static class InvocationContextExtensions + { + #region GetApiService + + /// + /// Gets an API service. + /// + /// + /// An invocation context. + /// + /// The API service type. + /// The API service instance. + public static T GetApiService(this InvocationContext context) where T : class + { + Ensure.NotNull(context, nameof(context)); + if (context.ServiceProvider != null) + { + return context.ServiceProvider.GetService(); + } + + return null; + } + + /// + /// Gets an ordered collection of service instances. + /// + /// + /// An invocation context. + /// + /// The API service type. + /// The ordered collection of service instances. + public static IEnumerable GetApiServices(this InvocationContext context) where T : class + { + Ensure.NotNull(context, nameof(context)); + if (context.ServiceProvider != null) + { + return context.ServiceProvider.GetServices(); + } + + return null; + } + + #endregion + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..32b39fb --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.Core +{ + /// + /// A delegate which participate in service creation. + /// All registered contributors form a chain, and the last registered will be called first. + /// + /// The service type. + /// + /// The to which this contributor call is registered. + /// + /// + /// Return the result of the previous contributor on the chain. + /// + /// A service instance of . + internal delegate T ApiServiceContributor(IServiceProvider serviceProvider, Func next) where T : class; + + /// + /// Contains extension methods of . + /// + //[CLSCompliant(false)] + public static class ServiceCollectionExtensions + { + /// + /// Return true if the has any service + /// registered. + /// + /// The API service type. + /// The . + /// + /// True if the service is registered. + /// + public static bool HasService(this IServiceCollection services) where TService : class + { + Ensure.NotNull(services, nameof(services)); + + return services.Any(sd => sd.ServiceType == typeof(TService)); + } + + /// + /// Adds a service contributor, which has a chance to chain previously registered service instances. + /// If want to cutoff previous registration, not define a property with type of TService or do not use it. + /// The first TService in function is the service of inner, and the second TService is the service returned. + /// + /// The service type. + /// The . + /// + /// A factory method to create a new instance of service TService, wrapping previous instance."/>. + /// + /// Current + public static IServiceCollection AddService( + this IServiceCollection services, + Func factory) + where TService : class + { + Ensure.NotNull(services, nameof(services)); + Ensure.NotNull(factory, nameof(factory)); + return services.AddContributorNoCheck((sp, next) => factory(sp, next())); + } + + /// + /// Adds a service contributor, which has a chance to chain previously registered service instances. + /// If want to cutoff previous registration, not define a property with type of TService or do not use it. + /// The contributor added will get an instance of from the container, i.e. + /// , every time it's get called. + /// This method will try to register as a service with + /// life time, if it's not yet registered. To override, you can + /// register before or after calling this method. + /// + /// + /// Note: When registering , you must NOT give it a + /// that makes it outlives , that could possibly + /// make an instance of be used in multiple instantiations of + /// , which leads to unpredictable behaviors. + /// + /// The service type. + /// The implementation type. + /// The . + /// Current + public static IServiceCollection AddService(this IServiceCollection services) + where TService : class + where TImplement : class, TService + { + Ensure.NotNull(services, nameof(services)); + + Func, TService> factory = null; + + services.TryAddTransient(); + return services.AddContributorNoCheck((sp, next) => + { + if (factory != null) + { + return factory(sp, next); + } + + var instance = sp.GetService(); + if (instance == null) + { + return instance; + } + + var innerMember = FindInnerMemberAndInject(instance, next); + if (innerMember == null) + { + factory = (serviceProvider, _) => serviceProvider.GetRequiredService(); + return instance; + } + + factory = (serviceProvider, getNext) => + { + // To build a lambda expression like: + // (sp, next) => + // { + // var service = sp.GetRequiredService(); + // service.next = next(); + // return service; + // } + var serviceProviderParam = Expression.Parameter(typeof(IServiceProvider)); + var nextParam = Expression.Parameter(typeof(Func)); + + var value = Expression.Variable(typeof(TImplement)); + var getService = Expression.Call( + typeof(ServiceProviderServiceExtensions), + "GetRequiredService", + new[] { typeof(TImplement) }, + serviceProviderParam); + var inject = Expression.Assign( + Expression.MakeMemberAccess(value, innerMember), + Expression.Invoke(nextParam)); + + var block = Expression.Block( + typeof(TService), + new[] { value }, + Expression.Assign(value, getService), + inject, + value); + + factory = LambdaExpression.Lambda, TService>>( + block, + serviceProviderParam, + nextParam).Compile(); + innerMember = null; + return factory(serviceProvider, getNext); + }; + + return instance; + }); + } + + /// + /// Call this to make singleton lifetime of a service. + /// + /// The service type. + /// The . + /// Current + public static IServiceCollection MakeSingleton(this IServiceCollection services) + where TService : class + { + Ensure.NotNull(services, nameof(services)); + services.AddSingleton(ChainedService.DefaultFactory); + return services; + } + + /// + /// Call this to make scoped lifetime of a service. + /// + /// The service type. + /// The . + /// Current + public static IServiceCollection MakeScoped(this IServiceCollection services) where TService : class + { + Ensure.NotNull(services, nameof(services)); + services.AddScoped(ChainedService.DefaultFactory); + return services; + } + + /// + /// Call this to make transient lifetime of a service. + /// + /// The service type. + /// The . + /// Current + public static IServiceCollection MakeTransient(this IServiceCollection services) + where TService : class + { + Ensure.NotNull(services, nameof(services)); + services.AddTransient(ChainedService.DefaultFactory); + return services; + } + + /// + /// Add core services. + /// + /// + /// The containing API service registrations. + /// + /// + /// The type of a class on which code-based conventions are used. + /// + /// Current + public static IServiceCollection AddCoreServices(this IServiceCollection services, Type apiType) + { + Ensure.NotNull(apiType, nameof(apiType)); + + services.AddScoped(apiType, apiType) + .AddScoped(typeof(ApiBase), apiType); + + services.TryAddSingleton(); + + return services.AddService() + .AddScoped(); + } + + /// + /// Enables code-based conventions for an API. + /// + /// + /// The containing API service registrations. + /// + /// + /// The type of a class on which code-based conventions are used. + /// + /// Current + public static IServiceCollection AddConventionBasedServices(this IServiceCollection services, Type apiType) + { + Ensure.NotNull(apiType, nameof(apiType)); + + ConventionBasedChangeSetItemAuthorizer.ApplyTo(services, apiType); + ConventionBasedChangeSetItemFilter.ApplyTo(services, apiType); + services.AddService(); + ConventionBasedQueryExpressionProcessor.ApplyTo(services, apiType); + ConventionBasedOperationAuthorizer.ApplyTo(services, apiType); + ConventionBasedOperationFilter.ApplyTo(services, apiType); + return services; + } + + private static IServiceCollection AddContributorNoCheck( + this IServiceCollection services, + ApiServiceContributor contributor) + where TService : class + { + // Services have singleton lifetime by default, call Make... to change. + services.TryAddSingleton(typeof(TService), ChainedService.DefaultFactory); + services.AddSingleton(contributor); + + return services; + } + + private static MemberInfo FindInnerMemberAndInject( + TImplement instance, + Func next) + { + var typeInfo = typeof(TImplement).GetTypeInfo(); + var nextProperty = typeInfo + .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .FirstOrDefault(e => e.SetMethod != null && e.PropertyType == typeof(TService)); + if (nextProperty != null) + { + nextProperty.SetValue(instance, next()); + return nextProperty; + } + + var nextField = typeInfo + .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .FirstOrDefault(e => e.FieldType == typeof(TService)); + if (nextField != null) + { + nextField.SetValue(instance, next()); + return nextField; + } + + return null; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/TypeExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/TypeExtensions.cs new file mode 100644 index 0000000..8c5c093 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/TypeExtensions.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; + +namespace System +{ + internal static partial class TypeExtensions + { + private const BindingFlags QualifiedMethodBindingFlags = BindingFlags.NonPublic | + BindingFlags.Static | + BindingFlags.Instance | + BindingFlags.IgnoreCase | + BindingFlags.DeclaredOnly; + + /// + /// Find a base type or implemented interface which has a generic definition + /// represented by the parameter, definition. + /// + /// + /// The subject type. + /// + /// + /// The generic definition to check with. + /// + /// + /// The base type or the interface found; otherwise, null. + /// + public static Type FindGenericType(this Type type, Type definition) + { + if (type == null) + { + return null; + } + + // If the type conforms the given generic definition, no further check required. + if (type.IsGenericDefinition(definition)) + { + return type; + } + + // If the definition is interface, we only need to check the interfaces implemented by the current type + if (definition.IsInterface) + { + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.IsGenericDefinition(definition)) + { + return interfaceType; + } + } + } + else if (!type.IsInterface) + { + // If the definition is not an interface, then the current type cannot be an interface too. + // Otherwise, we should only check the parent class types of the current type. + + // no null check for the type required, as we are sure it is not an interface type + while (type != typeof(object)) + { + if (type.IsGenericDefinition(definition)) + { + return type; + } + + type = type.BaseType; + } + } + + return null; + } + + public static MethodInfo GetQualifiedMethod(this Type type, string methodName) => type.GetMethod(methodName, QualifiedMethodBindingFlags); + + public static bool TryGetElementType(this Type type, out Type elementType) + { + // Special case: string implements IEnumerable however it should + // NOT be treated as a collection type. + if (type == typeof(string)) + { + elementType = null; + return false; + } + + var interfaceType = type.FindGenericType(typeof(IEnumerable<>)); + if (interfaceType != null) + { + elementType = interfaceType.GetGenericArguments()[0]; + return true; + } + + elementType = null; + return false; + } + + private static bool IsGenericDefinition(this Type type, Type definition) + { + return type.IsGenericType && + type.GetGenericTypeDefinition() == definition; + } + } + + internal static class TypeHelper + { + public static Type GetUnderlyingTypeOrSelf(Type type) => Nullable.GetUnderlyingType(type) ?? type; + + public static bool IsEnum(Type type) + { + var underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(type); + return underlyingTypeOrSelf.IsEnum; + } + + public static bool IsDateTime(Type type) + { + var underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(type); + return underlyingTypeOrSelf == typeof(DateTime); + } + + public static bool IsTimeSpan(Type type) + { + var underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(type); + return underlyingTypeOrSelf == typeof(TimeSpan); + } + + public static bool IsDateTimeOffset(Type type) + { + var underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(type); + return underlyingTypeOrSelf == typeof(DateTimeOffset); + } + } + + internal static class TypeConverter + { + public static object ChangeType(object value, Type conversionType, IFormatProvider provider) + { + if (conversionType == typeof(DateTime) && value is DateTimeOffset) + { + return ((DateTimeOffset)value).DateTime; + } + return Convert.ChangeType(value, conversionType, provider); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Globalization/Error.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Globalization/Error.cs new file mode 100644 index 0000000..8680d4f --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Globalization/Error.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Globalization; + +namespace System +{ + internal static class Error + { + public static NotSupportedException NotSupported(string messageFormat, params object[] messageArgs) + { + return new NotSupportedException(Error.Format(messageFormat, messageArgs)); + } + + public static string Format(string format, params object[] args) + { + return string.Format(CultureInfo.CurrentCulture, format, args); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/Ensure.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/Ensure.cs new file mode 100644 index 0000000..af6a745 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/Ensure.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace System +{ + + /// + /// + /// + internal static partial class Ensure + { + + /// + /// + /// + /// + /// + /// + public static void NotNull([ValidatedNotNull]T? value, string paramName) + where T : struct + { + if (value == null) + { + throw new ArgumentNullException(paramName); + } + } + + /// + /// + /// + /// + /// + /// + public static void NotNull([ValidatedNotNull]T value, string paramName) + where T : class + { + if (value == null) + { + throw new ArgumentNullException(paramName); + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + private sealed class ValidatedNotNullAttribute : Attribute + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelperMethods.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelperMethods.cs new file mode 100644 index 0000000..fd7e6ff --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelperMethods.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Reflection; + +namespace System.Linq.Expressions +{ + internal static class ExpressionHelperMethods + { + private const string MethodNameOfCreateQuery = "CreateQuery"; + private const string MethodNameOfAsQueryable = "AsQueryable"; + + public static MethodInfo QueryableSelectGeneric { get; } = GenericMethodOf(_ => Queryable.Select(default(IQueryable), i => i)); + + public static MethodInfo QueryableSelectManyGeneric { get; } = GenericMethodOf(_ => Queryable.SelectMany(default(IQueryable), i => default(IQueryable))); + + public static MethodInfo QueryableWhereGeneric { get; } = GenericMethodOf(_ => Queryable.Where(default, default(Expression>))); + + public static MethodInfo QueryableOfTypeGeneric { get; } = GenericMethodOf(_ => Queryable.OfType(default(IQueryable))); + + public static MethodInfo QueryableCountGeneric { get; } = GenericMethodOf(_ => Queryable.LongCount(default(IQueryable))); + + public static MethodInfo QueryableAsQueryable { get; } = typeof(Queryable).GetMethod( + MethodNameOfAsQueryable, + BindingFlags.Static | BindingFlags.Public, + null, + new[] { typeof(IEnumerable<>) }, + null); + + public static MethodInfo QueryableAsQueryableGeneric { get; } = GenericMethodOf(_ => Queryable.AsQueryable(default(IEnumerable))); + + public static MethodInfo EnumerableCastGeneric { get; } = typeof(Enumerable).GetMethod("Cast"); + + public static MethodInfo EnumerableToListGeneric { get; } = typeof(Enumerable).GetMethod("ToList"); + + public static MethodInfo EnumerableToArrayGeneric { get; } = typeof(Enumerable).GetMethod("ToArray"); + + private static MethodInfo GenericMethodOf(Expression> expression) => GenericMethodOf(expression as Expression); + + public static MethodInfo IQueryProviderCreateQueryGeneric { get; } = typeof(IQueryProvider).GetMethods() + .Single(_ => _.Name == MethodNameOfCreateQuery && _.IsGenericMethodDefinition); + + private static MethodInfo GenericMethodOf(Expression expression) + { + var lambdaExpression = expression as LambdaExpression; + + Contract.Assert(expression.NodeType == ExpressionType.Lambda); + Contract.Assert(lambdaExpression != null); + Contract.Assert(lambdaExpression.Body.NodeType == ExpressionType.Call); + + return (lambdaExpression.Body as MethodCallExpression).Method.GetGenericMethodDefinition(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelpers.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelpers.cs new file mode 100644 index 0000000..13446d4 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Helpers/ExpressionHelpers.cs @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Restier.Core; + +namespace System.Linq.Expressions +{ + internal static class ExpressionHelpers + { + private const string MethodNameOfQueryTake = "Take"; + private const string MethodNameOfQuerySelect = "Select"; + private const string MethodNameOfQuerySkip = "Skip"; + private const string MethodNameOfQueryWhere = "Where"; + private const string MethodNameOfQueryOrderBy = "OrderBy"; + private const string InterfaceNameISelectExpandWrapper = "ISelectExpandWrapper"; + private const string ExpandClauseReflectedTypeName = "SelectExpandBinder"; + + public static IQueryable Select(IQueryable query, LambdaExpression select) + { + var selectMethod = + ExpressionHelperMethods.QueryableSelectGeneric.MakeGenericMethod( + query.ElementType, + select.Body.Type); + return selectMethod.Invoke(null, new object[] { query, select }) as IQueryable; + } + + public static IQueryable SelectMany(IQueryable query, LambdaExpression selectMany, Type selectedElementType) + { + var selectManyMethod = + ExpressionHelperMethods.QueryableSelectManyGeneric.MakeGenericMethod( + query.ElementType, + selectedElementType); + return selectManyMethod.Invoke(null, new object[] { query, selectMany }) as IQueryable; + } + + public static IQueryable Where(IQueryable query, LambdaExpression where, Type type) + { + var whereMethod = ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(type); + return whereMethod.Invoke(null, new object[] { query, where }) as IQueryable; + } + + public static IQueryable OfType(IQueryable query, Type type) + { + var ofTypeMethod = ExpressionHelperMethods.QueryableOfTypeGeneric.MakeGenericMethod(type); + return ofTypeMethod.Invoke(null, new object[] { query }) as IQueryable; + } + + public static Expression Count(Expression queryExpression, Type elementType) + { + var countMethod = ExpressionHelperMethods.QueryableCountGeneric.MakeGenericMethod(elementType); + return Expression.Call(countMethod, queryExpression); + } + + /// + /// Get count IQueryable of the elements with $skip/$top ignored + /// + /// The type parameter for IQueryable + /// The input query. + /// The count IQueryable + public static IQueryable GetCountableQuery(IQueryable query) + { + Ensure.NotNull(query, nameof(query)); + object countQuery = query; + var expression = query.Expression; + + // This is stripping select, expand and top from input query + expression = StripQueryMethod(expression, MethodNameOfQueryTake); + expression = StripQueryMethod(expression, MethodNameOfQuerySkip); + expression = StripQueryMethod(expression, MethodNameOfQuerySelect); + + if (expression != query.Expression) + { + // Don't need orderby for count query + expression = StripQueryMethod(expression, MethodNameOfQueryOrderBy); + + // If Type is Type to then GenericType will be returned. + // e.g. if type is SelectAllAndExpand, then Namespace.Product will be returned. + var elementType = GetSelectExpandElementType(typeof(TElement)); + + var method = ExpressionHelperMethods.IQueryProviderCreateQueryGeneric; + var generic = method.MakeGenericMethod(elementType); + countQuery = generic.Invoke(query.Provider, new object[] { expression }); + } + + // This means there is no $expand/$skip/$top, return count directly + return (IQueryable)countQuery; + } + + /// + /// Create am empty Queryable of specified type + /// + /// The element type of IQueryable + /// The empty IQueryable + public static IQueryable CreateEmptyQueryable(Type elementType) + { + var constructor = typeof(List<>).MakeGenericType(elementType).GetConstructor(Type.EmptyTypes); + var instance = constructor.Invoke(new object[] { }); + var emptyQuerable = ExpressionHelperMethods.QueryableAsQueryable + .Invoke(null, new object[] { instance }) as IQueryable; + return emptyQuerable; + } + + internal static Type GetEnumerableItemType(this Type enumerableType) + { + var type = enumerableType.FindGenericType(typeof(IEnumerable<>)); + if (type != null) + { + return type.GetGenericArguments()[0]; + } + + return enumerableType; + } + + internal static MethodCallExpression RemoveUnneededStatement(this MethodCallExpression methodCallExpression) + { + if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2) + { + return methodCallExpression; + } + + if (methodCallExpression.Method.Name == MethodNameOfQuerySelect) + { + // Check where it is expand case or select, if yes, need to get rid of last select + methodCallExpression = RemoveSelectExpandStatement(methodCallExpression); + if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2) + { + return methodCallExpression; + } + } + + if (methodCallExpression.Method.Name == MethodNameOfQueryTake) + { + // Check where it is top query option, and if yes, remove it. + methodCallExpression = methodCallExpression.Arguments[0] as MethodCallExpression; + if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2) + { + return methodCallExpression; + } + } + + if (methodCallExpression.Method.Name == MethodNameOfQuerySkip) + { + // Check where it is skip query option, and if yes, remove it. + methodCallExpression = methodCallExpression.Arguments[0] as MethodCallExpression; + if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2) + { + return methodCallExpression; + } + } + + if (methodCallExpression.Method.Name == MethodNameOfQueryOrderBy) + { + // Check where it is orderby query option, and if yes, remove it. + methodCallExpression = methodCallExpression.Arguments[0] as MethodCallExpression; + if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2) + { + return methodCallExpression; + } + } + + return methodCallExpression; + } + + internal static MethodCallExpression RemoveSelectExpandStatement(this MethodCallExpression methodCallExpression) + { + // This means a select for expand is appended, will remove it for resource existing check + var expandSelect = methodCallExpression.Arguments[1] as UnaryExpression; + if (!(expandSelect.Operand is LambdaExpression lambdaExpression)) + { + return methodCallExpression; + } + + if (!(lambdaExpression.Body is MemberInitExpression memberInitExpression)) + { + return methodCallExpression; + } + + var returnType = lambdaExpression.ReturnType; + var wrapperInterface = returnType.GetInterface(InterfaceNameISelectExpandWrapper); + if (wrapperInterface != null) + { + methodCallExpression = methodCallExpression.Arguments[0] as MethodCallExpression; + } + + return methodCallExpression; + } + + internal static Expression RemoveAppendWhereStatement(this Expression expression) + { + if (!(expression is MethodCallExpression methodCallExpression) || methodCallExpression.Method.Name != MethodNameOfQueryWhere) + { + return expression; + } + + // This means there may be an appended statement Where(Param_0 => (Param_0.Prop != null)) + var appendedWhere = methodCallExpression.Arguments[1] as UnaryExpression; + if (!(appendedWhere.Operand is LambdaExpression lambdaExpression)) + { + return expression; + } + + if (lambdaExpression.Body is BinaryExpression binaryExpression && binaryExpression.NodeType == ExpressionType.NotEqual) + { + if (binaryExpression.Right is ConstantExpression rightExpression && rightExpression.Value == null) + { + // remove statement like Where(Param_0 => (Param_0.Prop != null)) + expression = methodCallExpression.Arguments[0]; + } + } + + return expression; + } + + private static Expression StripQueryMethod(Expression expression, string methodName) + { + if (expression is MethodCallExpression methodCall && + methodCall.Method.DeclaringType == typeof(Queryable) && + methodCall.Method.Name.Equals(methodName, StringComparison.Ordinal)) + { + expression = methodCall.Arguments[0]; + } + + return expression; + } + + private static Type GetSelectExpandElementType(Type elementType) + { + // Get the generic type of a type. e.g. if type is SelectAllAndExpand, + // then type Namespace.Product will be returned. + // Only generic type of expand clause will be retrieved to make the logic specified for $expand + var typeInfo = elementType.GetTypeInfo(); + if (typeInfo.IsGenericType && typeInfo.ReflectedType != null + && typeInfo.ReflectedType.Name == ExpandClauseReflectedTypeName) + { + elementType = typeInfo.GenericTypeArguments[0]; + } + + return elementType; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/InvocationContext.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/InvocationContext.cs new file mode 100644 index 0000000..e2364a7 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/InvocationContext.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.Core +{ + /// + /// Represents context under which an request is processed. + /// The request could be a query, a submit, an operation execution or a model retrieve. + /// It has subclass for each kinds of request. + /// + /// + /// An invocation context is created each time an request is parsed to a specified request. + /// + public class InvocationContext + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An API context. + /// + public InvocationContext(IServiceProvider provider) + { + Ensure.NotNull(provider, nameof(provider)); + + ServiceProvider = provider; + } + + /// + /// Gets the which contains all services of this scope. + /// + public IServiceProvider ServiceProvider { get; private set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj new file mode 100644 index 0000000..5c7e332 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj @@ -0,0 +1,71 @@ + + + + Restier is a framework for building convention-based, secure, queryable APIs with ASP.NET. This package contains runtime components for implementing Restier convention semantics and query interception. + + $(Summary) + + Commonly used types: + Microsoft.Restier.Core.RestierController + + $(PackageTags) + + netstandard2.0;net462 + + + + + + + + + + + <_Parameter1>Microsoft.Restier.AspNet + + + <_Parameter1>Microsoft.Restier.EntityFramework + + + <_Parameter1>Microsoft.Restier.Tests.Core + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Microsoft.Restier.Core + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/EdmHelpers.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/EdmHelpers.cs new file mode 100644 index 0000000..baa42b5 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/EdmHelpers.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Model +{ + /// + /// An utility class to operate with Edm model. + /// + internal static class EdmHelpers + { + /// + /// Get the type reference based on Edm type + /// + /// The edm type to retrieve Edm type reference + /// The edm type reference + public static IEdmTypeReference GetTypeReference(this IEdmType edmType) + { + Ensure.NotNull(edmType, nameof(edmType)); + + var isNullable = false; + switch (edmType.TypeKind) + { + case EdmTypeKind.Collection: + return new EdmCollectionTypeReference(edmType as IEdmCollectionType); + case EdmTypeKind.Complex: + return new EdmComplexTypeReference(edmType as IEdmComplexType, isNullable); + case EdmTypeKind.Entity: + return new EdmEntityTypeReference(edmType as IEdmEntityType, isNullable); + case EdmTypeKind.EntityReference: + return new EdmEntityReferenceTypeReference(edmType as IEdmEntityReferenceType, isNullable); + case EdmTypeKind.Enum: + return new EdmEnumTypeReference(edmType as IEdmEnumType, isNullable); + case EdmTypeKind.Primitive: + return new EdmPrimitiveTypeReference(edmType as IEdmPrimitiveType, isNullable); + default: + var message = string.Format(CultureInfo.CurrentCulture, Resources.EdmTypeNotSupported, edmType.ToTraceString()); + throw new NotSupportedException(message); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelBuilder.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelBuilder.cs new file mode 100644 index 0000000..6c8c907 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelBuilder.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Model +{ + /// + /// The service for model generation. + /// + public interface IModelBuilder + { + /// + /// Asynchronously gets an API model for an API. + /// + /// + /// The context for processing + /// + /// + /// An optional cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is the API model. + /// + Task GetModelAsync(ModelContext context, CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelMapper.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelMapper.cs new file mode 100644 index 0000000..61bd00b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/IModelMapper.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.Core.Model +{ + /// + /// Represents a service that maps between + /// the model space and the object space. + /// + public interface IModelMapper + { + /// + /// Tries to get the relevant type of an entity + /// set, singleton, or composable function import. + /// + /// + /// The context for model mapper. + /// + /// + /// The name of an entity set, singleton or composable function import. + /// + /// + /// When this method returns, provides the + /// relevant type of the queryable source. + /// + /// + /// true if the relevant type was + /// provided; otherwise, false. + /// + /// + /// + /// For entity sets, the relevant type is its element entity type. + /// + /// + /// For singletons, the relevant type is the singleton entity type. + /// + /// + /// For composable function imports, the relevant type is the return + /// type if it is a primitive, complex or entity type, or the element + /// type of the return type if it is a collection type. + /// + /// + /// This method can return true and assign null as the relevant + /// type when it is overriding a previously registered service and + /// specifically opting to not support the specified queryable source. + /// + /// + bool TryGetRelevantType(ModelContext context, string name, out Type relevantType); + + /// + /// Tries to get the relevant type of a composable function. + /// + /// + /// The context for model mapper. + /// + /// + /// The name of a namespace containing a composable function. + /// + /// + /// The name of composable function. + /// + /// + /// When this method returns, provides the + /// relevant type of the composable function. + /// + /// + /// true if the relevant type was + /// provided; otherwise, false. + /// + /// + /// + /// For composable functions, the relevant type is the return + /// type if it is a primitive, complex or entity type, or the + /// element type of the return type if it is a collection type. + /// + /// + /// This method can return true and assign null as the relevant + /// type when it is overriding a previously registered service and + /// specifically opting to not support the specified composable function. + /// + /// + bool TryGetRelevantType(ModelContext context, string namespaceName, string name, out Type relevantType); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/ModelContext.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/ModelContext.cs new file mode 100644 index 0000000..a031fa0 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Model/ModelContext.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.Restier.Core.Model +{ + /// + /// Represents context under which a model is requested. + /// + public class ModelContext : InvocationContext + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The service provider to get services. + /// + public ModelContext(IServiceProvider provider) : base(provider) + { + ResourceSetTypeMap = new Dictionary(); + ResourceTypeKeyPropertiesMap = new Dictionary>(); + } + + /// + /// Gets or sets resource set and resource type map dictionary, it will be used by publisher for model build. + /// + public IDictionary ResourceSetTypeMap { get; } + + /// + /// Gets or sets resource type and its key properties map dictionary, and used by publisher for model build. + /// This is useful when key properties does not have key attribute + /// or follow Web Api OData key property naming convention. + /// Otherwise, this collection is not needed. + /// + public IDictionary> ResourceTypeKeyPropertiesMap { get; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationAuthorizer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationAuthorizer.cs new file mode 100644 index 0000000..2d51ae3 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationAuthorizer.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.Core.Operation +{ + /// + /// Represents a operation authorizer. + /// + public interface IOperationAuthorizer + { + /// + /// Asynchronously authorizes the Operation. + /// + /// + /// The operation context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task AuthorizeAsync( + OperationContext context, + CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationExecutor.cs new file mode 100644 index 0000000..f22a335 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationExecutor.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Operation +{ + /// + /// Represents a service that executes an operation. + /// + public interface IOperationExecutor + { + /// + /// Asynchronously executes an operation. + /// + /// + /// The operation context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a operation result. + /// + Task ExecuteOperationAsync(OperationContext context, CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationFilter.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationFilter.cs new file mode 100644 index 0000000..7d81205 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/IOperationFilter.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Operation +{ + /// + /// Represents a operation processor. + /// + public interface IOperationFilter + { + /// + /// Asynchronously applies logic before a operation is executed. + /// + /// + /// The operation context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task OnOperationExecutingAsync( + OperationContext context, + CancellationToken cancellationToken); + + /// + /// Asynchronously applies logic after an operation is executed. + /// + /// + /// The submit context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task OnOperationExecutedAsync( + OperationContext context, + CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/OperationContext.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/OperationContext.cs new file mode 100644 index 0000000..73e2b1e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Operation/OperationContext.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net.Http; + +namespace Microsoft.Restier.Core.Operation +{ + /// + /// Represents context under which a operation is executed. + /// One instance created for one execution of one operation. + /// + public class OperationContext : InvocationContext + { + + /// + /// Initializes a new instance of the class. + /// + /// + /// The function that used to retrieve the parameter value name. + /// + /// + /// The operation name. + /// + /// + /// The instance which has the implementation of the operation and used for reflection call + /// + /// + /// A flag indicates this is a function call or action call. + /// + /// + /// A queryable for binding parameter value and if it is function/action import, the value will be null. + /// + /// + /// The service provider used to get service from container. + /// + public OperationContext( + Func getParameterValueFunc, + string operationName, + object implementInstance, + bool isFunction, + IEnumerable bindingParameterValue, + IServiceProvider provider) + : base(provider) + { + GetParameterValueFunc = getParameterValueFunc; + OperationName = operationName; + ImplementInstance = implementInstance; + IsFunction = isFunction; + BindingParameterValue = bindingParameterValue; + } + + /// + /// Gets the operation name. + /// + public string OperationName { get; } + + /// + /// Gets the instance have implemented the operation and used for reflection call. + /// + public object ImplementInstance { get; } + + /// + /// Gets the function that used to retrieve the parameter value name. + /// + public Func GetParameterValueFunc { get; } + + /// + /// Gets a value indicating whether it is a function call or action call. + /// + public bool IsFunction { get; } + + /// + /// Gets the queryable for binding parameter value, + /// and if it is function/action import, the value will be null. + /// + public IEnumerable BindingParameterValue { get; } + + + /// + /// Gets or sets the parameters value array used by method, + /// It is only set after parameters are prepared. + /// +#pragma warning disable CA2227 // Collection properties should be read only + public ICollection ParameterValues { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + + /// + /// Gets or sets the http request for this operation call + /// + public HttpRequestMessage Request { get; set; } // TODO: RWM: Move to ApiBase. + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs new file mode 100644 index 0000000..0a729b7 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.Designer.cs @@ -0,0 +1,414 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Core { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Restier.Core.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The argument with name {0} can not be null.. + /// + internal static string ArgumentCanNotBeNull { + get { + return ResourceManager.GetString("ArgumentCanNotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The associated metadata type for type '{0}' contains the following unknown properties or fields: {1}. Please make sure that the names of these members match the names of the properties on the main type.. + /// + internal static string AssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties { + get { + return ResourceManager.GetString("AssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Calling the methods in 'QueryableSource' or 'QueryableSource<T>' is not supported.. + /// + internal static string CallQueryableSourceMethodNotSupported { + get { + return ResourceManager.GetString("CallQueryableSourceMethodNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The change set cannot be set if there is already a result.. + /// + internal static string CannotSetChangeSetIfThereIsResult { + get { + return ResourceManager.GetString("CannotSetChangeSetIfThereIsResult", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The results source cannot be set if there is any error.. + /// + internal static string CannotSetResultsSourceIfThereIsAnyError { + get { + return ResourceManager.GetString("CannotSetResultsSourceIfThereIsAnyError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The total count cannot be set if there is no result yet.. + /// + internal static string CannotSetTotalCountIfThereIsNoResult { + get { + return ResourceManager.GetString("CannotSetTotalCountIfThereIsNoResult", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Change set preparer is missing.. + /// + internal static string ChangeSetPreparerMissing { + get { + return ResourceManager.GetString("ChangeSetPreparerMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A DataModification must be either: IsNew, IsUpdate, or IsDelete.. + /// + internal static string DataModificationMustBeCUD { + get { + return ResourceManager.GetString("DataModificationMustBeCUD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to "DataModificationItem.ApplyTo cannot be called on an new resource.. + /// + internal static string DataModificationNotSupportCreateResource { + get { + return ResourceManager.GetString("DataModificationNotSupportCreateResource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There should have been at least one predicate applied to the query from the resource key. Ensure there is at least one resource key.. + /// + internal static string DataModificationRequiresResourceKey { + get { + return ResourceManager.GetString("DataModificationRequiresResourceKey", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Do not call the methods in 'DataSourceStub' directly.. + /// + internal static string DoNotCallDataSourceStubMethodDirectly { + get { + return ResourceManager.GetString("DoNotCallDataSourceStubMethodDirectly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not a supported EDM type.. + /// + internal static string EdmTypeNotSupported { + get { + return ResourceManager.GetString("EdmTypeNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Element type cannot be found for '{0}'.. + /// + internal static string ElementTypeNotFound { + get { + return ResourceManager.GetString("ElementTypeNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Element type not match.. + /// + internal static string ElementTypeNotMatch { + get { + return ResourceManager.GetString("ElementTypeNotMatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expander cannot change the expression type.. + /// + internal static string ExpanderCannotChangeExpressionType { + get { + return ResourceManager.GetString("ExpanderCannotChangeExpressionType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expression must be queryable.. + /// + internal static string ExpressionMustBeQueryable { + get { + return ResourceManager.GetString("ExpressionMustBeQueryable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inspection failed.. + /// + internal static string InspectionFailed { + get { + return ResourceManager.GetString("InspectionFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid ChangeSetItem Type: {0}.. + /// + internal static string InvalidChangeSetEntryType { + get { + return ResourceManager.GetString("InvalidChangeSetEntryType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to MetadataClassType cannot be null.. + /// + internal static string MetadataTypeAttribute_TypeCannotBeNull { + get { + return ResourceManager.GetString("MetadataTypeAttribute_TypeCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to IEdmModel cannot be generated since API service IModelBuilder is not registered.. + /// + internal static string ModelBuilderNotRegistered { + get { + return ResourceManager.GetString("ModelBuilderNotRegistered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to InvalidChangeSetEntryType. + /// + internal static string NoPermissionToDeleteEntity { + get { + return ResourceManager.GetString("NoPermissionToDeleteEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current user does not have permission to insert entities into the EntitySet '{0}'.. + /// + internal static string NoPermissionToInsertEntity { + get { + return ResourceManager.GetString("NoPermissionToInsertEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current user does not have permission to invoke the requested action '{0}'.. + /// + internal static string NoPermissionToInvokeAction { + get { + return ResourceManager.GetString("NoPermissionToInvokeAction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current user does not have permission to update entities in the EntitySet '{0}'.. + /// + internal static string NoPermissionToUpdateEntity { + get { + return ResourceManager.GetString("NoPermissionToUpdateEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not supported type: {0}.. + /// + internal static string NotSupportedType { + get { + return ResourceManager.GetString("NotSupportedType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Original query expression should be constant expression.. + /// + internal static string OriginalExpressionShouldBeConstant { + get { + return ResourceManager.GetString("OriginalExpressionShouldBeConstant", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Original query expression should contain a value which is queryable.. + /// + internal static string OriginalExpressionShouldBeQueryable { + get { + return ResourceManager.GetString("OriginalExpressionShouldBeQueryable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The precondition check for request {0} on resource {1} is failed.. + /// + internal static string PreconditionCheckFailed { + get { + return ResourceManager.GetString("PreconditionCheckFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Processor cannot change the expression type.. + /// + internal static string ProcessorCannotChangeExpressionType { + get { + return ResourceManager.GetString("ProcessorCannotChangeExpressionType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'QueryableSource' cannot be used as query.. + /// + internal static string QueryableSourceCannotBeUsedAsQuery { + get { + return ResourceManager.GetString("QueryableSourceCannotBeUsedAsQuery", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Query executor is missing.. + /// + internal static string QueryExecutorMissing { + get { + return ResourceManager.GetString("QueryExecutorMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The LINQ expression for part {0} was not accessible. This may indicate your query has static member components, possibly from an extension method. This is not currently supported./r/nPlease check the EntitySet filter, and see https://github.com/OData/RESTier/issues/564 for more information.. + /// + internal static string QueryMemberNotAccessible { + get { + return ResourceManager.GetString("QueryMemberNotAccessible", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A query for a single entity resulted in more than one record.. + /// + internal static string QueryShouldGetSingleRecord { + get { + return ResourceManager.GetString("QueryShouldGetSingleRecord", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Query expression sourcer is missing.. + /// + internal static string QuerySourcerMissing { + get { + return ResourceManager.GetString("QuerySourcerMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request resource is not found.. + /// + internal static string ResourceNotFound { + get { + return ResourceManager.GetString("ResourceNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Should specify an interface type T for the handler.. + /// + internal static string ShouldBeInterfaceType { + get { + return ResourceManager.GetString("ShouldBeInterfaceType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expression returned by sourcer is missing.. + /// + internal static string SourceExpressionMissing { + get { + return ResourceManager.GetString("SourceExpressionMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Submit executor is missing.. + /// + internal static string SubmitExecutorMissing { + get { + return ResourceManager.GetString("SubmitExecutorMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The operation cannot be performed because one or more objects are invalid. Please inspect the ChangeSetValidationException.ValidationResults property for more information.. + /// + internal static string ValidationFailsTheOperation { + get { + return ResourceManager.GetString("ValidationFailsTheOperation", resourceCulture); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.resx b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.resx new file mode 100644 index 0000000..8245ada --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Properties/Resources.resx @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Calling the methods in 'QueryableSource' or 'QueryableSource<T>' is not supported. + + + The change set cannot be set if there is already a result. + + + The results source cannot be set if there is any error. + + + The total count cannot be set if there is no result yet. + + + Change set preparer is missing. + + + A DataModification must be either: IsNew, IsUpdate, or IsDelete. + + + "DataModificationItem.ApplyTo cannot be called on an new resource. + + + There should have been at least one predicate applied to the query from the resource key. Ensure there is at least one resource key. + + + Do not call the methods in 'DataSourceStub' directly. + + + Element type cannot be found for '{0}'. + + + Element type not match. + + + Expander cannot change the expression type. + + + Expression must be queryable. + + + Processor cannot change the expression type. + + + Inspection failed. + + + Invalid ChangeSetItem Type: {0}. + + + InvalidChangeSetEntryType + + + The current user does not have permission to insert entities into the EntitySet '{0}'. + + + The current user does not have permission to invoke the requested action '{0}'. + + + The current user does not have permission to update entities in the EntitySet '{0}'. + + + Not supported type: {0}. + + + Original query expression should be constant expression. + + + Original query expression should contain a value which is queryable. + + + 'QueryableSource' cannot be used as query. + + + Query executor is missing. + + + Query expression sourcer is missing. + + + Should specify an interface type T for the handler. + + + Expression returned by sourcer is missing. + + + Submit executor is missing. + + + The operation cannot be performed because one or more objects are invalid. Please inspect the ChangeSetValidationException.ValidationResults property for more information. + + + IEdmModel cannot be generated since API service IModelBuilder is not registered. + + + {0} is not a supported EDM type. + + + The request resource is not found. + + + The precondition check for request {0} on resource {1} is failed. + + + The argument with name {0} can not be null. + + + A query for a single entity resulted in more than one record. + + + MetadataClassType cannot be null. + + + The associated metadata type for type '{0}' contains the following unknown properties or fields: {1}. Please make sure that the names of these members match the names of the properties on the main type. + + + The LINQ expression for part {0} was not accessible. This may indicate your query has static member components, possibly from an extension method. This is not currently supported./r/nPlease check the EntitySet filter, and see https://github.com/OData/RESTier/issues/564 for more information. + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/PropertyBag.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/PropertyBag.cs new file mode 100644 index 0000000..27b2922 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/PropertyBag.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Restier.Core +{ + /// + /// Represents a bag of properties. + /// + internal class PropertyBag + { + private readonly IDictionary properties = new Dictionary(); + + /// + /// Indicates if this object has a property. + /// + /// The name of a property. + /// + /// true if this object has the + /// property; otherwise, false. + /// + public bool HasProperty(string name) + { + Ensure.NotNull(name, nameof(name)); + return properties.ContainsKey(name); + } + + /// + /// Gets a property. + /// + /// + /// The type of the property. + /// + /// + /// The name of a property. + /// + /// + /// The value of the property. + /// + public T GetProperty(string name) + { + Ensure.NotNull(name, nameof(name)); + var value = GetProperty(name); + if (!(value is T)) + { + value = default(T); + } + + return (T)value; + } + + /// + /// Gets a property. + /// + /// The name of a property. + /// The value of the property. + public object GetProperty(string name) + { + Ensure.NotNull(name, nameof(name)); + properties.TryGetValue(name, out var value); + return value; + } + + /// + /// Sets a property. + /// + /// + /// The name of a property. + /// + /// + /// A value for the property. + /// + public void SetProperty(string name, object value) + { + Ensure.NotNull(name, nameof(name)); + properties[name] = value; + } + + /// + /// Removes a property. + /// + /// + /// The name of a property. + /// + public void RemoveProperty(string name) + { + Ensure.NotNull(name, nameof(name)); + properties.Remove(name); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryExecutor.cs new file mode 100644 index 0000000..93cca56 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryExecutor.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Default implementation for + /// + internal class DefaultQueryExecutor : IQueryExecutor + { + /// + public Task ExecuteQueryAsync( + QueryContext context, + IQueryable query, + CancellationToken cancellationToken) + { + Ensure.NotNull(context, nameof(context)); + var result = new QueryResult(query.ToList()); + return Task.FromResult(result); + } + + /// + public Task ExecuteExpressionAsync( + QueryContext context, + IQueryProvider queryProvider, + Expression expression, + CancellationToken cancellationToken) + { + Ensure.NotNull(queryProvider, nameof(queryProvider)); + return Task.FromResult(new QueryResult(new[] { queryProvider.Execute(expression) })); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs new file mode 100644 index 0000000..f0e1196 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/DefaultQueryHandler.cs @@ -0,0 +1,471 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents the default query handler. + /// + internal static class DefaultQueryHandler + { + private const string ExpressionMethodNameOfWhere = "Where"; + private const string ExpressionMethodNameOfSelect = "Select"; + private const string ExpressionMethodNameOfSelectMany = "SelectMany"; + + /// + /// Asynchronously executes the query flow. + /// + /// + /// The query context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a query result. + /// + public static async Task QueryAsync( + QueryContext context, + CancellationToken cancellationToken) + { + Ensure.NotNull(context, nameof(context)); + + // process query expression + var expression = context.Request.Expression; + var visitor = new QueryExpressionVisitor(context); + expression = visitor.Visit(expression); + + // get element type + Type elementType = null; + var queryType = expression.Type.FindGenericType(typeof(IQueryable<>)); + if (queryType != null) + { + elementType = queryType.GetGenericArguments()[0]; + } + + // append count expression if requested + if (elementType != null && context.Request.ShouldReturnCount) + { + expression = ExpressionHelpers.Count(expression, elementType); + elementType = null; // now return type is single int + } + + // execute query + QueryResult result; + var executor = context.GetApiService(); + if (executor == null) + { + throw new NotSupportedException(Resources.QueryExecutorMissing); + } + + if (elementType != null) + { + var query = visitor.BaseQuery.Provider.CreateQuery(expression); + var method = typeof(IQueryExecutor) + .GetMethod("ExecuteQueryAsync") + .MakeGenericMethod(elementType); + var parameters = new object[] + { + context, query, cancellationToken + }; + var task = method.Invoke(executor, parameters) as Task; + result = await task.ConfigureAwait(false); + + await CheckSubExpressionResult( + context, result.Results, visitor, executor, expression, cancellationToken).ConfigureAwait(false); + } + else + { + var method = typeof(IQueryExecutor) + .GetMethod("ExecuteExpressionAsync") + .MakeGenericMethod(expression.Type); + var parameters = new object[] + { + context, visitor.BaseQuery.Provider, expression, cancellationToken + }; + var task = method.Invoke(executor, parameters) as Task; + result = await task.ConfigureAwait(false); + } + + if (result != null) + { + result.ResultsSource = visitor.EntitySet; + } + + return result; + } + + private static async Task CheckSubExpressionResult( + QueryContext context, + IEnumerable enumerableResult, + QueryExpressionVisitor visitor, + IQueryExecutor executor, + Expression expression, + CancellationToken cancellationToken) + { + if (enumerableResult.GetEnumerator().MoveNext()) + { + // If there is some result, will not have additional processing + return; + } + + var methodCallExpression = expression as MethodCallExpression; + + // This will remove unneeded statement which includes $expand, $select,$top,$skip,$orderby + methodCallExpression = methodCallExpression.RemoveUnneededStatement(); + if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2) + { + return; + } + + if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere) + { + // Throw exception if key as last where statement, or remove $filter where statement + methodCallExpression = CheckWhereCondition(methodCallExpression); + if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2) + { + return; + } + + // Call without $filter where statement and with Key where statement + if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere) + { + // The last where from $filter is removed and run with key where statement + await ExecuteSubExpression(context, visitor, executor, methodCallExpression, cancellationToken).ConfigureAwait(false); + return; + } + } + + if (methodCallExpression.Method.Name != ExpressionMethodNameOfSelect + && methodCallExpression.Method.Name != ExpressionMethodNameOfSelectMany) + { + // If last statement is not select property, will no further checking + return; + } + + var subExpression = methodCallExpression.Arguments[0]; + + // Remove appended statement like Where(Param_0 => (Param_0.Prop != null)) if there is one + subExpression = subExpression.RemoveAppendWhereStatement(); + + await ExecuteSubExpression(context, visitor, executor, subExpression, cancellationToken).ConfigureAwait(false); + } + + private static async Task ExecuteSubExpression( + QueryContext context, + QueryExpressionVisitor visitor, + IQueryExecutor executor, + Expression expression, + CancellationToken cancellationToken) + { + // get element type + Type elementType = null; + var queryType = expression.Type.FindGenericType(typeof(IQueryable<>)); + if (queryType != null) + { + elementType = queryType.GetGenericArguments()[0]; + } + + var query = visitor.BaseQuery.Provider.CreateQuery(expression); + var method = typeof(IQueryExecutor) + .GetMethod("ExecuteQueryAsync") + .MakeGenericMethod(elementType); + var parameters = new object[] + { + context, query, cancellationToken + }; + + var task = method.Invoke(executor, parameters) as Task; + await task.ConfigureAwait(false); + + // RWM: This code currently returns 404s if there are no results, instead of returning empty queries. + // This means that legit EntitySets that just have no data in the table also return 404. No bueno. + + //var task = method.Invoke(executor, parameters) as Task; + //var result = await task.ConfigureAwait(false); + + //var any = result.Results.Cast().Any(); + //if (!any) + //{ + // // Which means previous expression does not have result, and should throw ResourceNotFoundException. + // throw new ResourceNotFoundException(Resources.ResourceNotFound); + //} + } + + private static MethodCallExpression CheckWhereCondition(MethodCallExpression methodCallExpression) + { + // This means a select for expand is appended, will remove it for resource existing check + var lastWhere = methodCallExpression.Arguments[1] as UnaryExpression; + var lambdaExpression = lastWhere.Operand as LambdaExpression; + if (lambdaExpression == null) + { + return null; + } + + var binaryExpression = lambdaExpression.Body as BinaryExpression; + if (binaryExpression == null) + { + return null; + } + + // Key segment will have ConstantExpression but $filter will not have ConstantExpression + var rightExpression = binaryExpression.Right as ConstantExpression; + if (rightExpression != null && rightExpression.Value != null) + { + // This means where statement is key segment but not for $filter + throw new StatusCodeException(HttpStatusCode.NotFound, Resources.ResourceNotFound); + } + + return methodCallExpression.Arguments[0] as MethodCallExpression; + } + + private class QueryExpressionVisitor : ExpressionVisitor + { + private readonly QueryExpressionContext context; + private readonly IDictionary processedExpressions; + private IQueryExpressionAuthorizer authorizer; + private IQueryExpressionExpander expander; + private IQueryExpressionProcessor processor; + private IQueryExpressionSourcer sourcer; + + public QueryExpressionVisitor(QueryContext context) + { + this.context = new QueryExpressionContext(context); + processedExpressions = new Dictionary(); + } + + public IQueryable BaseQuery { get; private set; } + + public IEdmEntitySet EntitySet { get; private set; } + + public override Expression Visit(Expression node) + { + if (node == null) + { + return null; + } + + // Initialize and push the visited node + var visited = node; + context.PushVisitedNode(visited); + + // If visited node has already been processed, + // skip normalization, inspection and filtering + // and simply replace with the processed node + if (processedExpressions.ContainsKey(visited)) + { + node = processedExpressions[visited]; + } + else + { + // Only visit the visited node's children if + // the visited node represents API data + if (!(context.ModelReference is DataSourceStubModelReference)) + { + // Visit visited node's children + node = base.Visit(visited); + } + + // Inspect the visited node + Inspect(); + + // Try to expand the visited node + // if it represents API data + if (context.ModelReference is DataSourceStubModelReference) + { + node = Expand(visited); + } + + // Process the visited node + node = Process(visited, node); + } + + if (visited == node) + { + if (context.ModelReference is DataSourceStubModelReference) + { + // If no processing occurred on the visited node + // and it represents API data, then it must be + // in its most primitive form, so source the node + node = Source(node); + } + + if (BaseQuery == null) + { + // The very first time control reaches here, the + // visited node represents the original starting + // point for the entire composed query, and thus + // it should produce a non-embedded expression. + if (!(node is ConstantExpression constant)) + { + throw new NotSupportedException(Resources.OriginalExpressionShouldBeConstant); + } + + BaseQuery = constant.Value as IQueryable; + if (BaseQuery == null) + { + throw new NotSupportedException(Resources.OriginalExpressionShouldBeQueryable); + } + + node = BaseQuery.Expression; + } + } + + // TODO GitHubIssue#28 : Support transformation between API types and data source proxy types + context.PopVisitedNode(); + + if (context.VisitedNode != null) + { + EntitySet = context.ModelReference != null ? + context.ModelReference.EntitySet : null; + } + + return node; + } + + private void Inspect() + { + if (authorizer == null) + { + authorizer = context.QueryContext.GetApiService(); + } + + if (authorizer != null && !authorizer.Authorize(context)) + { + throw new SecurityException("The current user does not have permission to query from the requested resource."); + } + } + + private Expression Expand(Expression visited) + { + if (expander == null) + { + expander = context.QueryContext + .GetApiService(); + } + + if (expander == null) + { + return visited; + } + + var expanded = expander.Expand(context); + var callback = context.AfterNestedVisitCallback; + context.AfterNestedVisitCallback = null; + if (expanded != null && expanded != visited) + { + if (!visited.Type.IsAssignableFrom(expanded.Type)) + { + throw new InvalidOperationException(Resources.ExpanderCannotChangeExpressionType); + } + + context.PushVisitedNode(null); + expanded = Visit(expanded); + context.PopVisitedNode(); + if (callback != null) + { + callback(); + } + + return expanded; + } + + return visited; + } + + private Expression Process(Expression visited, Expression processed) + { + if (processor == null) + { + processor = context.QueryContext.GetApiService(); + } + + if (processor != null) + { + var filtered = processor.Process(context); + var callback = context.AfterNestedVisitCallback; + context.AfterNestedVisitCallback = null; + if (filtered != null && filtered != visited) + { + if (!visited.Type.IsAssignableFrom(filtered.Type)) + { + // In order to filter on the navigation properties, + // the type is changed from ICollection<> to IQueryable<> + var collectionType = visited.Type.FindGenericType(typeof(ICollection<>)); + var queryableType = filtered.Type.FindGenericType(typeof(IQueryable<>)); + if (collectionType == null || queryableType == null) + { + throw new InvalidOperationException( + Resources.ProcessorCannotChangeExpressionType); + } + + var queryableElementType = queryableType.GenericTypeArguments[0]; + var collectionElementType = collectionType.GenericTypeArguments[0]; + if (collectionElementType != queryableElementType + && !queryableElementType.IsAssignableFrom(collectionElementType)) + { + throw new InvalidOperationException( + Resources.ProcessorCannotChangeExpressionType); + } + } + + processedExpressions.Add(visited, processed); + context.PushVisitedNode(null); + try + { + processed = Visit(filtered); + } + finally + { + context.PopVisitedNode(); + processedExpressions.Remove(visited); + } + + if (callback != null) + { + callback(); + } + } + } + + return processed; + } + + private Expression Source(Expression node) + { + if (sourcer == null) + { + sourcer = context.QueryContext + .GetApiService(); + } + + if (sourcer == null) + { + // Missing sourcer + throw new NotSupportedException(Resources.QuerySourcerMissing); + } + + node = sourcer.ReplaceQueryableSource(context, BaseQuery != null); + if (node == null) + { + // Missing source expression + throw new NotSupportedException(Resources.SourceExpressionMissing); + } + + return node; + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExecutor.cs new file mode 100644 index 0000000..e11e558 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExecutor.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a service that executes a query. + /// + /// + /// Data provider implemented IQueryExecutor should only handle queries against the specific + /// provider, and delegates all other queries to inner IQueryExecutor. + /// + public interface IQueryExecutor + { + /// + /// Asynchronously executes a query and produces a query result. + /// + /// + /// The type of the elements in the query. + /// + /// + /// The query context. + /// + /// + /// A composed query. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a query result. + /// + Task ExecuteQueryAsync( + QueryContext context, + IQueryable query, + CancellationToken cancellationToken); + + /// + /// Asynchronously executes a singleton + /// query and produces a query result. + /// + /// + /// The type of the singleton query result. + /// + /// + /// The query context. + /// + /// + /// A query provider to execute the expression. + /// + /// + /// An expression to be composed on the base query. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a query result. + /// + Task ExecuteExpressionAsync( + QueryContext context, + IQueryProvider queryProvider, + Expression expression, + CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionAuthorizer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionAuthorizer.cs new file mode 100644 index 0000000..817a684 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionAuthorizer.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a service that inspects a query expression. + /// + /// + /// + /// Query expression inspection evaluates an expression to determine + /// if it is valid according to API logic such as authorization rules. + /// + /// + /// Inspection is the first step that occurs when processing a query + /// expression after its children have been visited, so it occurs during + /// upward traversal of the query expression. This ensures that inspection + /// has a chance to take place before the node is altered in any way (with + /// the exception of normalization of expressions identifying API data). + /// + /// + public interface IQueryExpressionAuthorizer + { + /// + /// Check an expression to see whether it is authorized. + /// + /// + /// The query expression context. + /// + /// + /// true if the inspection passed; otherwise, false. + /// + bool Authorize(QueryExpressionContext context); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionExpander.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionExpander.cs new file mode 100644 index 0000000..cce692d --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionExpander.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq.Expressions; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a service that expands a query expression. + /// + /// + /// + /// Query expression expansion converts an expression that represents + /// normalized API data into an expression using more primitive nodes. + /// + /// + /// Expansion is the second step that occurs when processing a query + /// expression after its children have been visited, so it occurs during + /// upward traversal of the query expression and after inspection. Since + /// expansion fundamentally alters the query expression, the resulting + /// expression is recursively processed to ensure that all appropriate + /// normalization, inspection, expansion, filtering and sourcing occurs. + /// + /// + public interface IQueryExpressionExpander + { + /// + /// Expands an expression. + /// + /// + /// The query expression context. + /// + /// + /// An expanded expression of the same type as the visited node, or + /// if expansion did not apply, the visited node or null. + /// + Expression Expand(QueryExpressionContext context); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionProcessor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionProcessor.cs new file mode 100644 index 0000000..572eb32 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionProcessor.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq.Expressions; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a service that processes a query expression. + /// + /// + /// + /// Query expression processing converts an expression node into a + /// different expression node according to API logic such as a + /// restricting filter on top of some composable API data. + /// + /// + /// Processing is the third step that occurs when visiting a query + /// expression after its children have been visited, so it occurs during + /// upward traversal of the query expression and after inspection and + /// expansion. Since processing fundamentally alters the query expression, + /// the resulting expression is recursively processed to ensure that all + /// appropriate normalization, inspection, expansion, processing and + /// sourcing occurs. + /// + /// + public interface IQueryExpressionProcessor + { + /// + /// Processes an expression. + /// + /// + /// The query expression context. + /// + /// + /// A processed expression of the same type as the visited node, or + /// if processing did not apply, the visited node. + /// + Expression Process(QueryExpressionContext context); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionSourcer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionSourcer.cs new file mode 100644 index 0000000..fc94036 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/IQueryExpressionSourcer.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq.Expressions; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a service that replace queryable source of an expression. + /// + /// + /// + /// Query expression sourcing converts an expression that identifies + /// API data in a normalized manner to an equivalent representation + /// in terms of the underlying data source proxy. + /// + /// + /// Sourcing is the last step that occurs when processing a query + /// expression, and only happens on expressions that represent API + /// data that cannot be expanded into any more primitive of an expression. + /// + /// + public interface IQueryExpressionSourcer + { + /// + /// Replace queryable source of an expression. + /// + /// + /// The query expression context. + /// + /// + /// Indicates if the sourcing is occurring on an embedded node. + /// + /// + /// A data source expression that represents the visited node. + /// + /// + /// + /// When is false, this method + /// should produce a constant expression whose value is a queryable + /// object produced by calling into the underlying data source proxy. + /// + /// + /// When is true, this method should + /// return an expression that represents the API data identified by + /// the visited node in terms of the underlying data source proxy. + /// + /// + /// Consider an example where the data source API has a method to get + /// a query over customers, accessed through "data.GetCustomers()". + /// When is false, this method should call + /// that method and return a constant expression containing the query. + /// When is true, this method should build + /// a call expression to "GetCustomers" where the object to which it + /// applies is a constant expression whose value is the data object. + /// + /// + Expression ReplaceQueryableSource(QueryExpressionContext context, bool embedded); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/ParameterModelReference.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/ParameterModelReference.cs new file mode 100644 index 0000000..3fd9a85 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/ParameterModelReference.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a reference to parameter data in terms of a model. + /// It does not have special logic + /// + public class ParameterModelReference : QueryModelReference + { + internal ParameterModelReference(IEdmEntitySet entitySet, IEdmType type) + : base(entitySet, type) + { + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/PropertyModelReference.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/PropertyModelReference.cs new file mode 100644 index 0000000..b1bd1d0 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/PropertyModelReference.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a reference to property data in terms of a model. + /// + public class PropertyModelReference : QueryModelReference + { + private readonly string propertyName; + private IEdmProperty property; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of a property. + /// + /// + /// EDM property instance + /// + /// + /// A source query model reference. + /// + internal PropertyModelReference(string propertyName, IEdmProperty property, QueryModelReference source) + { + Ensure.NotNull(propertyName, nameof(propertyName)); + this.propertyName = propertyName; + + Ensure.NotNull(property, nameof(property)); + this.property = property; + Source = source; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A source query model reference. + /// + /// + /// The name of a property. + /// + internal PropertyModelReference(QueryModelReference source, string propertyName) + { + Ensure.NotNull(propertyName, nameof(propertyName)); + this.propertyName = propertyName; + + Ensure.NotNull(source, nameof(source)); + Source = source; + } + + /// + /// Gets the source of the derived data. + /// + public QueryModelReference Source { get; private set; } + + /// + /// Gets the entity set that contains the data. + /// + public override IEdmEntitySet EntitySet + { + get + { + if (Source != null) + { + return Source.EntitySet; + } + + return null; + } + } + + /// + /// Gets the type of the queryable data. + /// + public override IEdmType Type + { + get + { + if (Property != null) + { + return Property.Type.Definition; + } + + return null; + } + } + + /// + /// Gets the property representing the property data. + /// + public IEdmProperty Property + { + get + { + if (property != null) + { + return property; + } + + if (Source != null) + { + var structuredType = Source.Type as IEdmStructuredType; + if (structuredType != null) + { + property = structuredType.FindProperty(propertyName); + return property; + } + } + + return null; + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryContext.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryContext.cs new file mode 100644 index 0000000..d23b02b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryContext.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents context under which a query flow operates. + /// + public class QueryContext : InvocationContext + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The service provider to get services. + /// + /// + /// A query request. + /// + public QueryContext(IServiceProvider serviceProvider, QueryRequest request) + : base(serviceProvider) + { + Ensure.NotNull(request, nameof(request)); + Request = request; + } + + /// + /// Gets the model that informs this query context. + /// + public IEdmModel Model { get; internal set; } + + /// + /// Gets the query request. + /// + /// + /// The query request cannot be set if there is already a result. + /// + public QueryRequest Request { get; private set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryExpressionContext.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryExpressionContext.cs new file mode 100644 index 0000000..1264441 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryExpressionContext.cs @@ -0,0 +1,409 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core.Model; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents context for a query expression that + /// is used during query expression processing. + /// + public class QueryExpressionContext + { + private const string MethodNameOfDataSourceStubValue = "GetPropertyValue"; + private const string MethodNameOfType = "OfType"; + + private Stack visitedNodes = new Stack(); + private IDictionary modelReferences = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// + /// A query context. + /// + public QueryExpressionContext(QueryContext queryContext) + { + Ensure.NotNull(queryContext, nameof(queryContext)); + QueryContext = queryContext; + } + + /// + /// Gets the query context associated with this context. + /// + public QueryContext QueryContext { get; private set; } + + /// + /// Gets the expression node that is being visited. + /// + public Expression VisitedNode + { + get + { + if (visitedNodes.Count == 0) + { + return null; + } + + return visitedNodes.Peek(); + } + } + + /// + /// Gets a reference to the model element + /// that represents the visited node. + /// + public QueryModelReference ModelReference => GetModelReferenceForNode(VisitedNode); + + /// + /// Gets or sets an action that is invoked after an + /// expanded or filtered expression has been visited. + /// + public Action AfterNestedVisitCallback { get; set; } + + /// + /// Pushes a visited node. + /// + /// + /// A visited node. + /// + public void PushVisitedNode(Expression visitedNode) + { + visitedNodes.Push(visitedNode); + UpdateModelReference(); + } + + /// + /// Replaces the visited node. + /// + /// + /// A new visited node. + /// + public void ReplaceVisitedNode(Expression visitedNode) + { + visitedNodes.Pop(); + visitedNodes.Push(visitedNode); + UpdateModelReference(); + } + + /// + /// Pops a visited node. + /// + public void PopVisitedNode() + { + visitedNodes.Pop(); + UpdateModelReference(); + } + + /// + /// Gets a reference to the model element + /// that represents an expression node. + /// + /// + /// An expression node. + /// + /// + /// A reference to the model element + /// that represents the expression node. + /// + public QueryModelReference GetModelReferenceForNode(Expression node) + { + QueryModelReference modelReference = null; + if (node != null) + { + modelReferences.TryGetValue(node, out modelReference); + } + + return modelReference; + } + + /// + /// This method is called by method call like Where/OfType/SelectMany and so on + /// to create a model reference for whole function call. + /// + /// + /// An method call expression node. + /// + /// + /// The parameter model reference. + /// + /// + /// The edm model. + /// + /// + /// A reference to the model element + /// that represents the expression node. + /// + private static QueryModelReference ComputeQueryModelReference( + MethodCallExpression methodCall, QueryModelReference source, IEdmModel model) + { + var method = methodCall.Method; + + // source is a sequence of T and output is also a sequence of T + var sourceType = method.GetParameters()[0].ParameterType.FindGenericType(typeof(IEnumerable<>)); + var resultType = method.ReturnType.FindGenericType(typeof(IEnumerable<>)); + if (sourceType == resultType) + { + return new QueryModelReference(source.EntitySet, source.Type); + } + + Type resultElementType = null; + if (resultType != null) + { + resultElementType = resultType.GenericTypeArguments[0]; + } + + // In case sourceType IEnumerable and resultType is + // IEnumerable > + // or IEnumerable> + // or IEnumerable> + // or IEnumerable> + if (sourceType != null && resultType != null) + { + var resultGenericType = resultElementType; + if (resultGenericType.IsGenericType) + { + var resultFinalElementType = resultGenericType.GenericTypeArguments[0]; + var sourceElementType = sourceType.GenericTypeArguments[0]; + + // Handle source is type of sub class and result is a base class + if (resultFinalElementType.IsAssignableFrom(sourceElementType)) + { + return new QueryModelReference(source.EntitySet, source.Type); + } + } + } + + // In this case, the sourceType is null + if (method.Name.Equals(MethodNameOfType)) + { + // Did not consider multiple namespaces have same entity type case or customized namespace + var edmEntityType = model.FindDeclaredType(resultElementType.FullName); + var collectionType = new EdmCollectionType( + new EdmEntityTypeReference((IEdmEntityType)edmEntityType, false)); + return new QueryModelReference(source.EntitySet, collectionType); + } + + // Till here, it means the result is not part of previous result and entity set will be null + // This mean result is a collection as resultType is IEnumerable<> + if (resultType != null) + { + // Did not consider multiple namespaces have same entity type case or customized namespace + var edmElementType = model.FindDeclaredType(resultElementType.FullName); + + // This means result is collection of Entity/Complex/Enum + IEdmTypeReference edmTypeReference = null; + if (edmElementType != null) + { + var edmType = edmElementType as IEdmType; + edmTypeReference = edmType.GetTypeReference(); + + if (edmTypeReference != null) + { + var collectionType = new EdmCollectionType(edmTypeReference); + return new QueryModelReference(null, collectionType); + } + } + + // TODO Here means a collection of primitive type + } + + // TODO Need to handle single result case + // TODO GitHubIssue#29 : Handle projection operators in query expression + return null; + } + + private void UpdateModelReference() + { + if (VisitedNode != null && + !modelReferences.ContainsKey(VisitedNode)) + { + var modelReference = ComputeModelReference(); + if (modelReference != null) + { + modelReferences.Add( + VisitedNode, modelReference); + } + } + } + + private QueryModelReference ComputeModelReference() + { + QueryModelReference modelReference = null; + + var methodCall = VisitedNode as MethodCallExpression; + + if (methodCall != null) + { + var method = methodCall.Method; + if (method.DeclaringType == typeof(DataSourceStub) && method.Name != MethodNameOfDataSourceStubValue) + { + modelReference = ComputeDataSourceStubReference(methodCall); + } + else if (method.GetCustomAttributes().Any()) + { + var thisModelReference = GetModelReferenceForNode(methodCall.Arguments[0]); + if (thisModelReference != null) + { + var model = QueryContext.Model; + modelReference = ComputeQueryModelReference(methodCall, thisModelReference, model); + } + } + + return modelReference; + } + + if (VisitedNode is ParameterExpression parameter) + { + return ComputeParameterModelReference(parameter); + } + + if (VisitedNode is MemberExpression member) + { + return ComputeMemberModelReference(member); + } + + return null; + } + + private QueryModelReference ComputeParameterModelReference(ParameterExpression parameter) + { + QueryModelReference modelReference = null; + foreach (var node in GetExpressionTrail()) + { + if (!(node is MethodCallExpression methodCall)) + { + continue; + } + + modelReference = GetModelReferenceForNode(node); + if (modelReference == null) + { + continue; + } + + var method = methodCall.Method; + var sourceType = method.GetParameters()[0].ParameterType.FindGenericType(typeof(IEnumerable<>)); + var resultType = method.ReturnType.FindGenericType(typeof(IEnumerable<>)); + if (sourceType != resultType) + { + // In case sourceType IEnumerable and resultType is + // IEnumerable > + // or IEnumerable> + // or IEnumerable> + // or IEnumerable> + if (sourceType == null || resultType == null) + { + continue; + } + + var resultGenericType = resultType.GenericTypeArguments[0]; + if (!resultGenericType.IsGenericType || resultGenericType.GenericTypeArguments[0] != sourceType.GenericTypeArguments[0]) + { + continue; + } + } + + var typeOfT = sourceType.GenericTypeArguments[0]; + if (parameter.Type == typeOfT) + { + if (modelReference.Type is IEdmCollectionType collectionType) + { + modelReference = new ParameterModelReference(modelReference.EntitySet, collectionType.ElementType.Definition); + return modelReference; + } + } + } + + return modelReference; + } + + private QueryModelReference ComputeMemberModelReference(MemberExpression member) + { + QueryModelReference modelReference = null; + var memberExp = member.Expression; + + if (memberExp == null) + { + throw new Exception(string.Format(Resources.QueryMemberNotAccessible, member.ToString())); + } + + if (memberExp.NodeType == ExpressionType.Parameter) + { + modelReference = GetModelReferenceForNode(memberExp); + } + else if (memberExp.NodeType == ExpressionType.TypeAs) + { + var resultType = memberExp.Type; + var parameterExpression = (memberExp as UnaryExpression).Operand; + + // Handle result is employee, and get person's property case + // member expression will be "Param_0 As Person" + if (parameterExpression.Type.IsSubclassOf(resultType)) + { + modelReference = GetModelReferenceForNode(parameterExpression); + } + else + { + // member expression will be "Param_0 As Employee" + var emdEntityType = QueryContext.Model.FindDeclaredType(resultType.FullName); + + if (emdEntityType is IEdmStructuredType structuredType) + { + var property = structuredType.FindProperty(member.Member.Name); + modelReference = GetModelReferenceForNode(parameterExpression); + modelReference = new PropertyModelReference(member.Member.Name, property, modelReference); + return modelReference; + } + } + } + + if (modelReference != null) + { + modelReference = new PropertyModelReference(modelReference, member.Member.Name); + } + + return modelReference; + } + + private DataSourceStubModelReference ComputeDataSourceStubReference(MethodCallExpression methodCall) + { + DataSourceStubModelReference modelReference = null; + ConstantExpression namespaceName = null; + ConstantExpression name = null; + var argumentIndex = 0; + if (methodCall.Method.GetParameters().Length > 2) + { + namespaceName = methodCall.Arguments[argumentIndex++] as ConstantExpression; + } + + name = methodCall.Arguments[argumentIndex++] as ConstantExpression; + if ((argumentIndex == 1 || namespaceName != null) && name != null) + { + if (name.Value is string nameValue) + { + if (namespaceName == null) + { + modelReference = new DataSourceStubModelReference(QueryContext, nameValue); + } + else + { + modelReference = new DataSourceStubModelReference(QueryContext, namespaceName.Value as string, nameValue); + } + } + } + + return modelReference; + } + + private IEnumerable GetExpressionTrail() => visitedNodes.TakeWhile(node => node != null); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryModelReference.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryModelReference.cs new file mode 100644 index 0000000..6e59abb --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryModelReference.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a reference to query data in terms of a model. + /// + public class QueryModelReference + { + private readonly IEdmEntitySet edmEntitySet; + + private readonly IEdmType edmType; + + internal QueryModelReference() + { + } + + internal QueryModelReference(IEdmEntitySet entitySet, IEdmType type) + { + edmEntitySet = entitySet; + edmType = type; + } + + /// + /// Gets the entity set that ultimately contains the data. + /// + public virtual IEdmEntitySet EntitySet => edmEntitySet; + + /// + /// Gets the type of the data, if any. + /// + public virtual IEdmType Type => edmType; + } + + /// + /// Represents a reference to data source stub in terms of a model. + /// + public class DataSourceStubModelReference : QueryModelReference + { + private readonly QueryContext context; + private readonly string namespaceName; + private readonly string name; + + /// + /// Initializes a new instance of the class. + /// + /// + /// A query context. + /// + /// + /// The name of an entity set, singleton or function import. + /// + internal DataSourceStubModelReference(QueryContext context, string name) + { + Ensure.NotNull(context, nameof(context)); + Ensure.NotNull(name, nameof(name)); + this.context = context; + this.name = name; + } + + /// + /// Initializes a new instance of the class referring to a function. + /// + /// + /// A query context. + /// + /// + /// The name of a namespace containing the function. + /// + /// + /// The name of a function. + /// + internal DataSourceStubModelReference( + QueryContext context, + string namespaceName, + string name) + { + Ensure.NotNull(context, nameof(context)); + Ensure.NotNull(namespaceName, nameof(namespaceName)); + Ensure.NotNull(name, nameof(name)); + this.context = context; + this.namespaceName = namespaceName; + this.name = name; + } + + /// + /// Gets the entity set that ultimately contains the data. + /// + public override IEdmEntitySet EntitySet + { + get + { + var entitySet = Element as IEdmEntitySet; + if (entitySet != null) + { + return entitySet; + } + + // TODO GitHubIssue#30 : others + return null; + } + } + + /// + /// Gets the type of the data, if any. + /// + public override IEdmType Type + { + get + { + if (namespaceName == null) + { + var source = Element as IEdmNavigationSource; + if (source != null) + { + return source.Type; + } + + var function = Element as IEdmFunctionImport; + if (function != null) + { + return function.Function.ReturnType.Definition; + } + } + else + { + var function = Element as IEdmFunction; + if (function != null) + { + return function.ReturnType.Definition; + } + } + + return null; + } + } + + /// + /// Gets the element representing the API data. + /// + public IEdmElement Element + { + get + { + if (namespaceName == null) + { + return context.Model.EntityContainer.Elements + .SingleOrDefault(e => e.Name == name); + } + else + { + return context.Model.SchemaElements + .SingleOrDefault(e => + e.Namespace == namespaceName && + e.Name == name); + } + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryRequest.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryRequest.cs new file mode 100644 index 0000000..334e2ba --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryRequest.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a query request. + /// + public class QueryRequest + { + /// + /// Initializes a new instance of the class with a composed query. + /// + /// + /// A composed query that was derived from a queryable source. + /// + public QueryRequest(IQueryable query) + { + Ensure.NotNull(query, nameof(query)); + if (!(query is QueryableSource)) + { + throw new NotSupportedException( + Resources.QueryableSourceCannotBeUsedAsQuery); + } + + this.Expression = query.Expression; + } + + /// + /// Gets or sets the composed query expression. + /// + public Expression Expression { get; set; } + + /// + /// Gets or sets a value indicating whether the number + /// of the items should be returned instead of the + /// items themselves. + /// + public bool ShouldReturnCount { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryResult.cs new file mode 100644 index 0000000..24dcd7b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Query/QueryResult.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Core.Query +{ + /// + /// Represents a query result. + /// + public class QueryResult + { + private Exception exception; + private IEdmEntitySet resultsSource; + private IEnumerable results; + + /// + /// Initializes a new instance of the class with an Exception. + /// + /// + /// An Exception. + /// + public QueryResult(Exception exception) + { + Ensure.NotNull(exception, nameof(exception)); + Exception = exception; + } + + /// + /// Initializes a new instance of the class with in-memory results. + /// + /// + /// In-memory results. + /// + public QueryResult(IEnumerable results) + { + Ensure.NotNull(results, nameof(results)); + Results = results; + } + + /// + /// Gets or sets an Exception to be returned. + /// + /// + /// Setting this value will override any existing Exception or results. + /// + public Exception Exception + { + get => exception; + + set + { + Ensure.NotNull(value, nameof(value)); + exception = value; + resultsSource = null; + results = null; + } + } + + /// + /// Gets or sets the entity set from which the results were sourced. + /// + /// + /// This property will be null if the results are not instances + /// of a particular entity type that has an associated entity set. + /// + public IEdmEntitySet ResultsSource + { + get => resultsSource; + + set + { + if (exception != null) + { + throw new InvalidOperationException(Resources.CannotSetResultsSourceIfThereIsAnyError); + } + + resultsSource = value; + } + } + + /// + /// Gets or sets the in-memory results. + /// + /// + /// Setting this value will override any existing Exception or results. + /// + public IEnumerable Results + { + get => results; + + set + { + Ensure.NotNull(value, nameof(value)); + exception = null; + resultsSource = null; + results = value; + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/QueryableSource.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/QueryableSource.cs new file mode 100644 index 0000000..eed67b6 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/QueryableSource.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.Restier.Core +{ + internal abstract class QueryableSource : IOrderedQueryable, IQueryProvider + { + public QueryableSource(Expression expression) => Expression = expression; + + public abstract Type ElementType { get; } + + public Expression Expression { get; private set; } + + IQueryProvider IQueryable.Provider => this; + + public override string ToString() => Expression.ToString(); + + IQueryable IQueryProvider.CreateQuery( + Expression expression) + { + Ensure.NotNull(expression, nameof(expression)); + if (!typeof(IQueryable).IsAssignableFrom(expression.Type)) + { + throw new ArgumentException(Resources.ExpressionMustBeQueryable); + } + + return new QueryableSource(expression); + } + + IQueryable IQueryProvider.CreateQuery(Expression expression) + { + Ensure.NotNull(expression, nameof(expression)); + var type = expression.Type.FindGenericType(typeof(IQueryable<>)); + if (type == null) + { + throw new ArgumentException(Resources.ExpressionMustBeQueryable); + } + + type = typeof(QueryableSource<>).MakeGenericType( + type.GetGenericArguments()[0]); + return Activator.CreateInstance( + type, + BindingFlags.Public | BindingFlags.Instance, + null, + new object[] { expression }, + null) as IQueryable; + } + + TResult IQueryProvider.Execute(Expression expression) => throw new NotSupportedException(Resources.CallQueryableSourceMethodNotSupported); + + object IQueryProvider.Execute(Expression expression) => throw new NotSupportedException(Resources.CallQueryableSourceMethodNotSupported); + + IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException(Resources.CallQueryableSourceMethodNotSupported); + } + + internal class QueryableSource : QueryableSource, IOrderedQueryable + { + public QueryableSource(Expression expression) + : base(expression) + { + } + + public override Type ElementType => typeof(T); + + IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException(Resources.CallQueryableSourceMethodNotSupported); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs new file mode 100644 index 0000000..f9faf7c --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using DIServiceLifetime = Microsoft.Extensions.DependencyInjection.ServiceLifetime; +using ODataServiceLifetime = Microsoft.OData.ServiceLifetime; + +namespace Microsoft.Restier.Core +{ + /// + /// The default container builder implementation based on the Microsoft dependency injection framework. + /// + public class RestierContainerBuilder : IContainerBuilder + { + private readonly IServiceCollection services = new ServiceCollection(); + private readonly Type apiType; + + /// + /// Initializes a new instance of the class. + /// + /// The Api Type + public RestierContainerBuilder(Type apiType) => this.apiType = apiType; + + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The implementation type of the service. + /// The instance itself. + public virtual IContainerBuilder AddService(ODataServiceLifetime lifetime, Type serviceType, Type implementationType) + { + if (serviceType == null) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "serviceType")); + } + + if (implementationType == null) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "implementationType")); + } + + services.Add(new ServiceDescriptor(serviceType, implementationType, TranslateServiceLifetime(lifetime))); + + return this; + } + + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The factory that creates the service. + /// The instance itself. + public IContainerBuilder AddService(ODataServiceLifetime lifetime, Type serviceType, Func implementationFactory) + { + if (serviceType == null) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "serviceType")); + } + + if (implementationFactory == null) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ArgumentCanNotBeNull, "implementationFactory")); + } + + services.Add(new ServiceDescriptor(serviceType, implementationFactory, TranslateServiceLifetime(lifetime))); + + return this; + } + + /// + /// Builds a container which implements and contains + /// all the services registered. + /// + /// The container built by this builder. + public virtual IServiceProvider BuildContainer() + { + AddRestierService(); + return services.BuildServiceProvider(); + } + + internal IContainerBuilder AddRestierService() + { + IEdmModel modelFactory(IServiceProvider sp) + { + var api = sp.GetService(); + var model = api.GetModelAsync(default).GetAwaiter().GetResult(); + return model; + } + + // Configure the API via reflection call + var methodDeclaredType = apiType; + + MethodInfo method = null; + while (method == null && methodDeclaredType != null) + { + // In case the subclass does not override the method, call super class method + method = methodDeclaredType.GetMethod("ConfigureApi"); + methodDeclaredType = methodDeclaredType.BaseType; + } + + method.Invoke(null, new object[] + { + apiType, services + }); + + services.AddSingleton(modelFactory); + return this; + } + + /// + /// + /// + /// + /// + private static DIServiceLifetime TranslateServiceLifetime(ODataServiceLifetime lifetime) + { + switch (lifetime) + { + case ODataServiceLifetime.Scoped: + return DIServiceLifetime.Scoped; + case ODataServiceLifetime.Singleton: + return DIServiceLifetime.Singleton; + default: + return DIServiceLifetime.Transient; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSet.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSet.cs new file mode 100644 index 0000000..9375974 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSet.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a change set. + /// + public class ChangeSet + { + private List entries; + + /// + /// Initializes a new instance of the class. + /// + public ChangeSet() + : this(null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A set of change set entries. + /// + public ChangeSet(IEnumerable entries) + { + if (entries != null) + { + this.entries = new List(entries); + } + } + + /// + /// Gets the entries in this change set. + /// + public IList Entries + { + get + { + if (this.entries == null) + { + this.entries = new List(); + } + + return this.entries; + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs new file mode 100644 index 0000000..1d2543b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs @@ -0,0 +1,393 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Net; + +namespace Microsoft.Restier.Core.Submit +{ + + /// + /// Specifies the type of a change set item. + /// + internal enum ChangeSetItemType + { + /// + /// Specifies a data modification item. + /// + DataModification + } + + /// + /// Possible states of an resource during a ChangeSet life cycle + /// + internal enum ChangeSetItemProcessingStage + { + /// + /// If an new change set item is created + /// + Initialized, + + /// + /// The resource has been validated. + /// + Validated, + + /// + /// The resource set deleting, inserting or updating events are raised + /// + PreEventing, + + /// + /// The resource was modified within its own pre eventing interception method. This indicates that the resource + /// should be revalidated but its pre eventing interception point should not be invoked again. + /// + ChangedWithinOwnPreEventing, + + /// + /// The resource's pre events have been raised + /// + PreEvented + } + + /// + /// Represents an item in a change set. + /// + public abstract class ChangeSetItem + { + internal ChangeSetItem(ChangeSetItemType type) + { + Type = type; + ChangeSetItemProcessingStage = ChangeSetItemProcessingStage.Initialized; + } + + /// + /// Gets the type of this change set item. + /// + internal ChangeSetItemType Type { get; private set; } + + /// + /// Gets or sets the dynamic state of this change set item. + /// + internal ChangeSetItemProcessingStage ChangeSetItemProcessingStage { get; set; } + + /// + /// Indicates whether this change set item is in a changed state. + /// + /// + /// Whether this change set item is in a changed state. + /// + public bool HasChanged() + { + return ChangeSetItemProcessingStage == ChangeSetItemProcessingStage.Initialized || + ChangeSetItemProcessingStage == ChangeSetItemProcessingStage.ChangedWithinOwnPreEventing; + } + } + + /// + /// Represents a data modification item in a change set. + /// + public class DataModificationItem : ChangeSetItem + { + private const string IfNoneMatchKey = "@IfNoneMatchKey"; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of the resource set in question. + /// + /// + /// The type of the expected resource type in question. + /// + /// + /// The type of the actual resource type in question. + /// + /// + /// The RestierEntitySetOperations for the request. + /// + /// + /// The key of the resource being modified. + /// + /// + /// Any original values of the resource that are known. + /// + /// + /// The local values of the resource. + /// + public DataModificationItem( + string resourceSetName, + Type expectedResourceType, + Type actualResourceType, + RestierEntitySetOperation action, + IReadOnlyDictionary resourceKey, + IReadOnlyDictionary originalValues, + IReadOnlyDictionary localValues) + : base(ChangeSetItemType.DataModification) + { + Ensure.NotNull(resourceSetName, nameof(resourceSetName)); + Ensure.NotNull(expectedResourceType, nameof(expectedResourceType)); + ResourceSetName = resourceSetName; + ExpectedResourceType = expectedResourceType; + ActualResourceType = actualResourceType; + ResourceKey = resourceKey; + OriginalValues = originalValues; + LocalValues = localValues; + EntitySetOperation = action; + } + + /// + /// Gets the name of the resource set in question. + /// + public string ResourceSetName { get; private set; } + + /// + /// Gets the name of the expected resource type in question. + /// + public Type ExpectedResourceType { get; private set; } + + /// + /// Gets the name of the actual resource type in question. + /// In type inheritance case, this is different from expectedResourceType + /// + public Type ActualResourceType { get; private set; } + + /// + /// Gets the key of the resource being modified. + /// + public IReadOnlyDictionary ResourceKey { get; private set; } + + /// + /// Gets or sets the action to be taken. + /// + public RestierEntitySetOperation EntitySetOperation { get; set; } + + /// + /// Gets or sets a value indicating whether the resource should be fully replaced by the modification. + /// + /// + /// If true, all properties will be updated, even if the property isn't in LocalValues. + /// If false, only properties identified in LocalValues will be updated on the resource. + /// + public bool IsFullReplaceUpdateRequest { get; set; } + + /// + /// Gets or sets the resource object in question. + /// + /// + /// Initially this will be null, however after the change + /// set has been prepared it will represent the pending resource. + /// + public object Resource { get; set; } + + /// + /// Gets the original values for properties that have changed. + /// + /// + /// For new entities, this property is null. + /// + public IReadOnlyDictionary OriginalValues { get; private set; } + + /// + /// Gets the current server values for properties that have changed. + /// + /// + /// For new entities, this property is null. For updated + /// entities, it is null until the change set is prepared. + /// + public IReadOnlyDictionary ServerValues { get; private set; } + + /// + /// Gets the local values for properties that have changed. + /// + /// + /// For entities pending deletion, this property is null. + /// + public IReadOnlyDictionary LocalValues { get; private set; } + + /// + /// Applies the current DataModificationItem's KeyValues and OriginalValues to the + /// specified query and returns the new query. + /// + /// The IQueryable to apply the property values to. + /// + /// The new IQueryable with the property values applied to it in a Where condition. + /// + public IQueryable ApplyTo(IQueryable query) + { + Ensure.NotNull(query, nameof(query)); + if (EntitySetOperation == RestierEntitySetOperation.Insert) + { + throw new InvalidOperationException(Resources.DataModificationNotSupportCreateResource); + } + + var type = query.ElementType; + var param = Expression.Parameter(type); + Expression where = null; + + if (ResourceKey != null) + { + foreach (var item in ResourceKey) + { + where = ApplyPredicate(param, where, item); + } + } + + if (where == null) + { + throw new InvalidOperationException(Resources.DataModificationRequiresResourceKey); + } + + var whereLambda = Expression.Lambda(where, param); + return ExpressionHelpers.Where(query, whereLambda, type); + } + + /// + /// Validate the e-tag via applies the current DataModificationItem's OriginalValues to the + /// specified query and returns result. + /// + /// The IQueryable to apply the property values to. + /// + /// The object is e-tag checked passed. + /// + public object ValidateEtag(IQueryable query) + { + Ensure.NotNull(query, nameof(query)); + var type = query.ElementType; + var param = Expression.Parameter(type); + Expression where = null; + + if (OriginalValues != null) + { + foreach (var item in OriginalValues) + { + if (!item.Key.StartsWith("@", StringComparison.Ordinal)) + { + where = ApplyPredicate(param, where, item); + } + } + + if (OriginalValues.ContainsKey(IfNoneMatchKey)) + { + where = Expression.Not(where); + } + } + + var whereLambda = Expression.Lambda(where, param); + var queryable = ExpressionHelpers.Where(query, whereLambda, type); + + var matchedResource = queryable.SingleOrDefault(); + if (matchedResource == null) + { + // If ETAG does not match, should return 412 Precondition Failed + var message = string.Format(CultureInfo.InvariantCulture, Resources.PreconditionCheckFailed, new object[] { EntitySetOperation, query.SingleOrDefault() }); + throw new StatusCodeException(HttpStatusCode.PreconditionFailed, message); + } + + return matchedResource; + } + + /// + /// + /// + /// + /// + /// + /// + private static Expression ApplyPredicate(ParameterExpression param, Expression where, KeyValuePair item) + { + var property = Expression.Property(param, item.Key); + var itemValue = item.Value; + + if (itemValue.GetType() != property.Type) + { + itemValue = TypeConverter.ChangeType(itemValue, property.Type, CultureInfo.InvariantCulture); + } + + // TODO GitHubIssue#31 : Check if LinqParameterContainer is necessary + // Expression value = itemValue != null + // ? LinqParameterContainer.Parameterize(itemValue.GetType(), itemValue) + // : Expression.Constant(value: null); + Expression left = property; + Expression right = Expression.Constant(itemValue, property.Type); + if (property.Type == typeof(byte[])) + { + left = Expression.Call(typeof(BitConverter), "ToString", null, new Expression[] { property }); + right = Expression.Call(typeof(BitConverter), "ToString", null, new Expression[] { Expression.Constant(itemValue, property.Type) }); + } + + var equal = Expression.Equal(left, right); + return where == null ? equal : Expression.AndAlso(where, equal); + } + } + + /// + /// Represents a data modification item in a change set. + /// + /// The resource type. + public class DataModificationItem : DataModificationItem + where T : class + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of the resource set in question. + /// + /// + /// The type of the expected resource type in question. + /// + /// + /// The type of the actual resource type in question. + /// + /// + /// The RestierEntitySetOperations for the request. + /// + /// + /// The key of the resource being modified. + /// + /// + /// Any original values of the resource that are known. + /// + /// + /// The local values of the entity. + /// + public DataModificationItem( + string resourceSetName, + Type expectedResourceType, + Type actualResourceType, + RestierEntitySetOperation action, + IReadOnlyDictionary resourceKey, + IReadOnlyDictionary originalValues, + IReadOnlyDictionary localValues) + : base( + resourceSetName, + expectedResourceType, + actualResourceType, + action, + resourceKey, + originalValues, + localValues) + { + } + + /// + /// Gets or sets the resource object in question. + /// + /// + /// Initially this will be null, however after the change + /// set has been prepared it will represent the pending resource. + /// + public new T Resource + { + get => base.Resource as T; + + set => base.Resource = value; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItemValidationResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItemValidationResult.cs new file mode 100644 index 0000000..b79c974 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ChangeSetItemValidationResult.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Tracing; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a single result when validating an entity, property, etc. + /// + public class ChangeSetItemValidationResult + { + /// + /// Gets or sets the identifier for this validation result. + /// + /// + /// Id allows programmatic matching of validation results between tiers. + /// + public string Id { get; set; } + + /// + /// Gets or sets the item to which the validation result applies. + /// + public object Target { get; set; } + + /// + /// Gets or sets the name of the property to which the validation result applies. + /// If null, the validation result applies to the whole Target. + /// + public string PropertyName { get; set; } + + /// + /// Gets or sets the severity of this validation result. + /// + public EventLevel Severity { get; set; } + + /// + /// Gets or sets the message to be displayed to the end user for this validation result. + /// + public string Message { get; set; } + + /// + /// Returns the string that represents this validation result. + /// + /// + /// The string that represents this validation result. + /// + public override string ToString() + { + return this.Message; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs new file mode 100644 index 0000000..7a6828d --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Tracing; +using System.Globalization; +using System.Linq; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// The default handler for submitting changes through the . + /// + internal class DefaultSubmitHandler + { + + #region Private Members + + private readonly IChangeSetInitializer initializer; + private readonly IChangeSetItemAuthorizer authorizer; + private readonly IChangeSetItemValidator validator; + private readonly IChangeSetItemFilter filter; + private readonly ISubmitExecutor executor; + + #endregion + + #region Constructors + + /// + /// + /// + /// + public DefaultSubmitHandler(IServiceProvider serviceProvider) + { + //RWM: This stuff SHOULD be getting passed into a constructor. But the DI implementation is less than awesome. + // So we'll work around it for now and still save some allocations. + // There are certain unit te + if (serviceProvider != null) + { + initializer = serviceProvider.GetService(); + executor = serviceProvider.GetService(); + authorizer = serviceProvider.GetService(); + validator = serviceProvider.GetService(); + filter = serviceProvider.GetService(); + } + } + + #endregion + + #region Public Methods + + /// + /// Asynchronously executes the submit flow. + /// + /// The submit context. + /// A cancellation token. + /// + /// A task that represents the asynchronous operation whose result is a submit result. + /// + public async Task SubmitAsync(SubmitContext context, CancellationToken cancellationToken) + { + Ensure.NotNull(context, nameof(context)); + + if (initializer == null) + { + throw new NotSupportedException(Resources.ChangeSetPreparerMissing); + } + + await initializer.InitializeAsync(context, cancellationToken).ConfigureAwait(false); + + if (context.Result != null) + { + return context.Result; + } + + var eventsChangeSet = context.ChangeSet; + + IEnumerable currentChangeSetItems = eventsChangeSet.Entries.ToArray(); + + await PerformValidate(context, currentChangeSetItems, cancellationToken).ConfigureAwait(false); + + await PerformPreEvent(context, currentChangeSetItems, cancellationToken).ConfigureAwait(false); + + await PerformPersist(context, cancellationToken).ConfigureAwait(false); + + context.ChangeSet.Entries.Clear(); + + await PerformPostEvent(context, currentChangeSetItems, cancellationToken).ConfigureAwait(false); + + return context.Result; + } + + #endregion + + #region Private Methods + + private static string GetAuthorizeFailedMessage(ChangeSetItem item) + { + switch (item.Type) + { + case ChangeSetItemType.DataModification: + var dataModification = (DataModificationItem)item; + string message = null; + if (dataModification.EntitySetOperation == RestierEntitySetOperation.Insert) + { + message = Resources.NoPermissionToInsertEntity; + } + else if (dataModification.EntitySetOperation == RestierEntitySetOperation.Update) + { + message = Resources.NoPermissionToUpdateEntity; + } + else if (dataModification.EntitySetOperation == RestierEntitySetOperation.Delete) + { + message = Resources.NoPermissionToDeleteEntity; + } + else + { + throw new NotSupportedException(Resources.DataModificationMustBeCUD); + } + + return string.Format(CultureInfo.InvariantCulture, message, dataModification.ResourceSetName); + + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, item.Type)); + } + } + + private async Task PerformValidate(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + { + await InvokeAuthorizers(context, changeSetItems, cancellationToken).ConfigureAwait(false); + + await InvokeValidators(context, changeSetItems, cancellationToken).ConfigureAwait(false); + + foreach (var item in changeSetItems.Where(i => i.HasChanged())) + { + if (item.ChangeSetItemProcessingStage == ChangeSetItemProcessingStage.ChangedWithinOwnPreEventing) + { + item.ChangeSetItemProcessingStage = ChangeSetItemProcessingStage.PreEvented; + } + else + { + item.ChangeSetItemProcessingStage = ChangeSetItemProcessingStage.Validated; + } + } + } + + private async Task InvokeAuthorizers(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + { + if (authorizer == null) + { + return; + } + + foreach (var item in changeSetItems.Where(i => i.HasChanged())) + { + if (!await authorizer.AuthorizeAsync(context, item, cancellationToken).ConfigureAwait(false)) + { + throw new SecurityException(GetAuthorizeFailedMessage(item)); + } + } + } + + private async Task InvokeValidators(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + { + if (validator == null) + { + return; + } + + var validationResults = new Collection(); + + foreach (var entry in changeSetItems.Where(i => i.HasChanged())) + { + await validator.ValidateChangeSetItemAsync(context, entry, validationResults, cancellationToken).ConfigureAwait(false); + } + + var errors = validationResults.Where(result => result.Severity == EventLevel.Error); + + if (errors.Any()) + { + var validationErrorMessage = Resources.ValidationFailsTheOperation; + throw new ChangeSetValidationException(validationErrorMessage) + { + ValidationResults = errors + }; + } + } + + private async Task PerformPreEvent(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + { + if (filter == null) + { + return; + } + + foreach (var item in changeSetItems) + { + if (item.ChangeSetItemProcessingStage == ChangeSetItemProcessingStage.Validated) + { + item.ChangeSetItemProcessingStage = ChangeSetItemProcessingStage.PreEventing; + + + if (filter != null) + { + await filter.OnChangeSetItemProcessingAsync(context, item, cancellationToken).ConfigureAwait(false); + } + + if (item.ChangeSetItemProcessingStage == ChangeSetItemProcessingStage.PreEventing) + { + // if the state is still the intermediate state, + // the entity was not changed during processing + // and can move to the next step + item.ChangeSetItemProcessingStage = ChangeSetItemProcessingStage.PreEvented; + } + else if (item.ChangeSetItemProcessingStage == ChangeSetItemProcessingStage.Initialized /*&& + entity.Details.EntityState == originalEntityState*/) + { + item.ChangeSetItemProcessingStage = ChangeSetItemProcessingStage.ChangedWithinOwnPreEventing; + } + } + } + } + + private async Task PerformPersist(SubmitContext context, CancellationToken cancellationToken) + { + if (executor == null) + { + throw new NotSupportedException(Resources.SubmitExecutorMissing); + } + + context.Result = await executor.ExecuteSubmitAsync(context, cancellationToken).ConfigureAwait(false); + } + + private async Task PerformPostEvent(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + { + if (filter == null) + { + return; + } + + foreach (var item in changeSetItems) + { + await filter.OnChangeSetItemProcessedAsync(context, item, cancellationToken).ConfigureAwait(false); + } + } + + #endregion + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetInitializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetInitializer.cs new file mode 100644 index 0000000..a029981 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetInitializer.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a service that can initialize a change set. + /// + public interface IChangeSetInitializer + { + /// + /// Asynchronously initialize a change set for submission. + /// + /// + /// The submit context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + /// + /// Preparing a change set involves creating new entity objects for + /// new data, loading entities that are pending update or delete from + /// to get current server values, and using a data provider mechanism + /// to locally apply the supplied changes to the loaded entities. + /// + Task InitializeAsync(SubmitContext context, CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemAuthorizer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemAuthorizer.cs new file mode 100644 index 0000000..09a0184 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemAuthorizer.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a change set item authorizer. + /// + public interface IChangeSetItemAuthorizer + { + /// + /// Asynchronously authorizes the ChangeSetItem. + /// + /// + /// The submit context. + /// + /// + /// A change set item to be authorized. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task AuthorizeAsync( + SubmitContext context, + ChangeSetItem item, + CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemFilter.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemFilter.cs new file mode 100644 index 0000000..7306604 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemFilter.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a change set item filter to have logic before and after change set item processed. + /// + public interface IChangeSetItemFilter + { + /// + /// Asynchronously applies logic before a change set item is processed. + /// + /// + /// The submit context. + /// + /// + /// A change set item. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task OnChangeSetItemProcessingAsync( + SubmitContext context, + ChangeSetItem item, + CancellationToken cancellationToken); + + /// + /// Asynchronously applies logic after a change set item is processed. + /// + /// + /// The submit context. + /// + /// + /// A change set item. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task OnChangeSetItemProcessedAsync( + SubmitContext context, + ChangeSetItem item, + CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemValidator.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemValidator.cs new file mode 100644 index 0000000..e58f04f --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/IChangeSetItemValidator.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.ObjectModel; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a change set entry validator. + /// + public interface IChangeSetItemValidator + { + /// + /// Asynchronously validates a change set item. + /// + /// + /// The submit context. + /// + /// + /// The change set item to validate. + /// + /// + /// A set of validation results. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task ValidateChangeSetItemAsync( + SubmitContext context, + ChangeSetItem item, + Collection validationResults, + CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ISubmitExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ISubmitExecutor.cs new file mode 100644 index 0000000..940a1ce --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/ISubmitExecutor.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a service that executes a submission. + /// + public interface ISubmitExecutor + { + /// + /// Asynchronously executes a submission and produces a submit result. + /// + /// + /// The submit context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a submit result. + /// + Task ExecuteSubmitAsync( + SubmitContext context, + CancellationToken cancellationToken); + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitContext.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitContext.cs new file mode 100644 index 0000000..2c0a243 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitContext.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents context under which a submit flow operates. + /// + public class SubmitContext : InvocationContext + { + private ChangeSet changeSet; + private SubmitResult result; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The service provider used to get service from container + /// + /// + /// A change set. + /// + public SubmitContext(IServiceProvider provider, ChangeSet changeSet) + : base(provider) => this.changeSet = changeSet; + + /// + /// Gets or sets the change set. + /// + /// + /// The change set cannot be set if there is already a result. + /// + public ChangeSet ChangeSet + { + get => changeSet; + + set + { + if (Result != null) + { + throw new InvalidOperationException( + Resources.CannotSetChangeSetIfThereIsResult); + } + + changeSet = value; + } + } + + /// + /// Gets or sets the submit result. + /// + public SubmitResult Result + { + get => result; + + set + { + Ensure.NotNull(value, nameof(value)); + result = value; + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitResult.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitResult.cs new file mode 100644 index 0000000..a64b10c --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Submit/SubmitResult.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.Core.Submit +{ + /// + /// Represents a submit result. + /// + public class SubmitResult + { + private Exception exception; + private ChangeSet completedChangeSet; + + /// + /// Initializes a new instance of the class with an error. + /// + /// + /// An error. + /// + public SubmitResult(Exception exception) + { + Ensure.NotNull(exception, nameof(exception)); + Exception = exception; + } + + /// + /// Initializes a new instance of the class + /// + /// + /// A completed change set. + /// + public SubmitResult(ChangeSet completedChangeSet) + { + Ensure.NotNull(completedChangeSet, nameof(completedChangeSet)); + this.completedChangeSet = completedChangeSet; + } + + /// + /// Gets or sets an error to be returned. + /// + /// + /// Setting this value will override any + /// existing error or completed change set. + /// + public Exception Exception + { + get => exception; + + set + { + Ensure.NotNull(value, nameof(value)); + exception = value; + completedChangeSet = null; + } + } + + /// + /// Gets or sets the completed change set. + /// + /// + /// Setting this value will override any + /// existing error or completed change set. + /// + public ChangeSet CompletedChangeSet + { + get => completedChangeSet; + + set + { + Ensure.NotNull(value, nameof(value)); + completedChangeSet = value; + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/EntityFrameworkApi.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/EntityFrameworkApi.cs new file mode 100644 index 0000000..243c817 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/EntityFrameworkApi.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +#if !EF7 +using System.Data.Entity; +#endif +#if EF7 +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// Represents an API over a DbContext. + /// + /// The DbContext type. + /// + /// + /// This class tries to instantiate with the best matched constructor + /// base on services configured. Descendants could override by registering + /// as a scoped service. But in this case, proxy creation must be disabled in the constructors of + /// under Entity Framework 6. + /// + /// +#if EF7 + [CLSCompliant(false)] +#endif + public class EntityFrameworkApi : ApiBase where T : DbContext + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An containing all services of this . + /// + public EntityFrameworkApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + /// + /// Gets the underlying DbContext for this API. + /// + protected T DbContext + { + get + { + return (T)this.GetApiService(); + } + } + + + /// + /// Configures the API services for this API. Descendants may override this method to register + /// as a scoped service. + /// + /// + /// The Api type. + /// + /// + /// The with which to create all DI services. + /// + /// + /// The . + /// + //[CLSCompliant(false)] +#pragma warning disable CA1000 // Do not declare static members on generic types + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) +#pragma warning restore CA1000 // Do not declare static members on generic types + { + // Add core and convention's services + services = services.AddCoreServices(apiType) + .AddConventionBasedServices(apiType); + + // Add EF related services + services.AddEfProviderServices(); + + // This is used to add the publisher's services + GetPublisherServiceCallback(apiType)(services); + + return services; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Extensions/ServiceCollectionExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..ec8deca --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +#if !EF7 +using System.Data.Entity; +#else +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// Contains extension methods of . + /// + //[CLSCompliant(false)] + public static class ServiceCollectionExtensions + { + /// + /// This method is used to add entity framework providers service into container. + /// + /// The DbContext type. + /// The . + /// Current + public static IServiceCollection AddEfProviderServices(this IServiceCollection services) + where TDbContext : DbContext + { + services.AddScoped(sp => + { + var dbContext = Activator.CreateInstance(); +#if EF7 + dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; +#else + dbContext.Configuration.ProxyCreationEnabled = false; +#endif + return dbContext; + }); + + return services + .AddService() + .AddService((sp, next) => new ModelMapper(typeof(TDbContext))) + .AddService() + .AddService() + .AddService() + .AddService() + .AddService(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj new file mode 100644 index 0000000..5f1022e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj @@ -0,0 +1,35 @@ + + + + Restier is a framework for building convention-based, secure, queryable APIs with ASP.NET. This package contains runtime components for interacting with DbContexts from Entity Framework 6.1.3 and later. + + $(Summary) + + Commonly used types: + Microsoft.Restier.EntityFramework.EntityFrameworkApi + + $(PackageTags)entityframework;entityframework6 + + net462;net472 + + + + net462;net472 + true + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs new file mode 100644 index 0000000..f67b863 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +#if EF7 +using Microsoft.EntityFrameworkCore; +#else +using System.Data.Entity; +#endif +using Microsoft.Restier.Core.Model; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// Represents a model mapper based on a DbContext. + /// + internal class ModelMapper : IModelMapper + { + private readonly Type dbContextType; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The type of a DbContext class. + /// + public ModelMapper(Type dbContextType) + { + Ensure.NotNull(dbContextType, nameof(dbContextType)); + this.dbContextType = dbContextType; + } + + /// + /// Tries to get the relevant type of an entity + /// set, singleton, or composable function import. + /// + /// + /// The context for model mapper. + /// + /// + /// The name of an entity set, singleton or composable function import. + /// + /// + /// When this method returns, provides the + /// relevant type of the queryable source. + /// + /// + /// true if the relevant type was + /// provided; otherwise, false. + /// + public bool TryGetRelevantType( + ModelContext context, + string name, + out Type relevantType) + { + // TODO GitHubIssue#39 : support something beyond entity sets + relevantType = null; + var property = dbContextType.GetProperty(name); + if (property != null) + { + var type = property.PropertyType; +#if EF7 + var genericType = type.FindGenericType(typeof(DbSet<>)); +#else + var genericType = type.FindGenericType(typeof(IDbSet<>)); +#endif + if (genericType != null) + { + relevantType = genericType.GetGenericArguments()[0]; + } + } + + return relevantType != null; + } + + /// + /// Tries to get the relevant type of a composable function. + /// + /// + /// The context for model mapper. + /// + /// + /// The name of a namespace containing a composable function. + /// + /// + /// The name of composable function. + /// + /// + /// When this method returns, provides the + /// relevant type of the composable function. + /// + /// + /// true if the relevant type was + /// provided; otherwise, false. + /// + public bool TryGetRelevantType( + ModelContext context, + string namespaceName, + string name, + out Type relevantType) + { + // TODO GitHubIssue#39 : support composable function imports + relevantType = null; + return false; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs new file mode 100644 index 0000000..ea0664f --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +#if !EF7 +using System.Data.Entity; +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Infrastructure; +#endif +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +#if EF7 +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// Represents a model producer that uses the + /// metadata workspace accessible from a DbContext. + /// + internal class ModelProducer : IModelBuilder + { + public IModelBuilder InnerModelBuilder { get; set; } + + /// + /// This class will not real build a model, but only get entity set name and entity map from data source + /// Then pass the information to publisher layer to build the model. + /// + /// + /// The context for processing + /// + /// + /// An optional cancellation token. + /// + /// + /// Always a null model. + /// + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + Ensure.NotNull(context, nameof(context)); + +#if EF7 + var dbContext = context.GetApiService(); + context.ResourceSetTypeMap.AddRange(dbContext.GetType().GetProperties() + .Where(e => e.PropertyType.FindGenericType(typeof(DbSet<>)) != null) + .ToDictionary(e => e.Name, e => e.PropertyType.GetGenericArguments()[0])); + context.ResourceTypeKeyPropertiesMap.AddRange(dbContext.Model.GetEntityTypes().ToDictionary( + e => e.ClrType, + e => ((ICollection) + e.FindPrimaryKey().Properties.Select(p => e.ClrType.GetProperty(p.Name)).ToList()))); +#else + var dbContext = context.GetApiService(); + + var efModel = (dbContext as IObjectContextAdapter).ObjectContext.MetadataWorkspace; + + // @robertmclaws: The query below actually returns all registered Containers + // across all registered DbContexts. + // It is likely a bug in some other part of OData. But we can roll with it. + var efEntityContainers = efModel.GetItems(DataSpace.CSpace); + + // @robertmclaws: Because of the bug above, we should not make any assumptions about what is returned, + // and get the specific container we want to use. Even if the bug gets fixed, the next line should still + // continue to work. + var efEntityContainer = efEntityContainers.FirstOrDefault(c => c.Name == dbContext.GetType().Name); + + // @robertmclaws: Now that we're doing a proper FirstOrDefault() instead of a Single(), + // we wont' crash if more than one is returned, and we can check for null + // and inform the user specifically what happened. + if (efEntityContainer == null) + { + if (efEntityContainers.Count > 1) + { + // @robertmclaws: In this case, we have multiple DbContexts available, but none of them match up. + // Tell the user what we have, and what we were expecting, so they can fix it. + var containerNames = efEntityContainers.Aggregate( + string.Empty, (current, next) => next.Name + ", "); + throw new Exception(string.Format( + CultureInfo.InvariantCulture, + Resources.MultipleDbContextsExpectedException, + containerNames.Substring(0, containerNames.Length - 2), + efEntityContainer.Name)); + } + + // @robertmclaws: In this case, we only had one DbContext available, and if wasn't the right one. + throw new Exception(string.Format( + CultureInfo.InvariantCulture, + Resources.DbContextCouldNotBeFoundException, + dbContext.GetType().Name, + efEntityContainer.Name)); + } + + var itemCollection = (ObjectItemCollection)efModel.GetItemCollection(DataSpace.OSpace); + + foreach (var efEntitySet in efEntityContainer.EntitySets) + { + var efEntityType = efEntitySet.ElementType; + var objectSpaceType = efModel.GetObjectSpaceType(efEntityType); + var clrType = itemCollection.GetClrType(objectSpaceType); + + // As entity set name and type map + context.ResourceSetTypeMap.Add(efEntitySet.Name, clrType); + + ICollection keyProperties = new List(); + foreach (var property in efEntityType.KeyProperties) + { + keyProperties.Add(clrType.GetProperty(property.Name)); + } + + context.ResourceTypeKeyPropertiesMap.Add(clrType, keyProperties); + } +#endif + if (InnerModelBuilder != null) + { + return InnerModelBuilder.GetModelAsync(context, cancellationToken); + } + + return Task.FromResult(null); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.Designer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.Designer.cs new file mode 100644 index 0000000..d8ff4d0 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.Designer.cs @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.EntityFramework { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Restier.EntityFramework.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to A DataModificationEntry must be either New, Update or Delete.. + /// + internal static string DataModificationMustBeCUD { + get { + return ResourceManager.GetString("DataModificationMustBeCUD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the correct DbContext instance for this EntityFrameworkApi. \r\n The Context name was '{0}' but the Container expects '{1}'.. + /// + internal static string DbContextCouldNotBeFoundException { + get { + return ResourceManager.GetString("DbContextCouldNotBeFoundException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Need 'LineString type', while input is {0}.. + /// + internal static string InvalidLineStringGeographyType { + get { + return ResourceManager.GetString("InvalidLineStringGeographyType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Need 'Point type', while input is {0}.. + /// + internal static string InvalidPointGeographyType { + get { + return ResourceManager.GetString("InvalidPointGeographyType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This project has multiple EntityFrameworkApi using different DbContexts, and the correct context could not be loaded. \r\n The contexts available are '{0}' but the Container expects '{1}'.. + /// + internal static string MultipleDbContextsExpectedException { + get { + return ResourceManager.GetString("MultipleDbContextsExpectedException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The precondition check for request {0} on resource {1} is failed.. + /// + internal static string PreconditionCheckFailed { + get { + return ResourceManager.GetString("PreconditionCheckFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the specified resource.. + /// + internal static string ResourceNotFound { + get { + return ResourceManager.GetString("ResourceNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported type for property: {0}.. + /// + internal static string UnsupportedPropertyType { + get { + return ResourceManager.GetString("UnsupportedPropertyType", resourceCulture); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.resx b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.resx new file mode 100644 index 0000000..ab97510 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Properties/Resources.resx @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A DataModificationEntry must be either New, Update or Delete. + + + Could not find the correct DbContext instance for this EntityFrameworkApi. \r\n The Context name was '{0}' but the Container expects '{1}'. + + + Need 'LineString type', while input is {0}. + + + Need 'Point type', while input is {0}. + + + This project has multiple EntityFrameworkApi using different DbContexts, and the correct context could not be loaded. \r\n The contexts available are '{0}' but the Container expects '{1}'. + + + The precondition check for request {0} on resource {1} is failed. + + + Could not find the specified resource. + The target entity not found for modification + + + Unsupported type for property: {0}. + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs new file mode 100644 index 0000000..b66bb2b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#if !EF7 +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +#endif +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +#if EF7 +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.Restier.Core.Query; + +#if EF7 +using IAsyncQueryProvider = Microsoft.EntityFrameworkCore.Query.Internal.IAsyncQueryProvider; +#endif + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// Represents a query executor that uses Entity Framework methods. + /// This class only executes queries against EF provider, it'll + /// delegate other queries to inner IQueryExecutor. + /// + internal class QueryExecutor : IQueryExecutor + { + public IQueryExecutor Inner { get; set; } + + /// + /// Asynchronously executes a query and produces a query result. + /// + /// + /// The type of the elements in the query. + /// + /// + /// The query context. + /// + /// + /// A composed query. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a query result. + /// + public async Task ExecuteQueryAsync( + QueryContext context, + IQueryable query, + CancellationToken cancellationToken) + { +#if EF7 + if (query.Provider is IAsyncQueryProvider) +#else + if (query.Provider is IDbAsyncQueryProvider) +#endif + { + return new QueryResult(await query.ToArrayAsync(cancellationToken).ConfigureAwait(false)); + } + + return await Inner.ExecuteQueryAsync(context, query, cancellationToken).ConfigureAwait(false); + } + + /// + /// Asynchronously executes a singleton + /// query and produces a query result. + /// + /// + /// The type of the singleton query result. + /// + /// + /// The query context. + /// + /// + /// A query provider to execute expression. + /// + /// + /// An expression to be composed on the base query. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous + /// operation whose result is a query result. + /// + public async Task ExecuteExpressionAsync( + QueryContext context, + IQueryProvider queryProvider, + Expression expression, + CancellationToken cancellationToken) + { +#if EF7 + var provider = queryProvider as IAsyncQueryProvider; +#else + var provider = queryProvider as IDbAsyncQueryProvider; +#endif + if (provider != null) + { + var result = await provider.ExecuteAsync(expression, cancellationToken).ConfigureAwait(false); + return new QueryResult(new TResult[] { result }); + } + + return await Inner.ExecuteExpressionAsync(context, queryProvider, expression, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs new file mode 100644 index 0000000..6c4fff2 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core.Query; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// A query expression filter to handle EF related logic. + /// + internal class QueryExpressionProcessor : IQueryExpressionProcessor + { + // It will be ConventionBasedEntitySetProcessor + public IQueryExpressionProcessor Inner { get; set; } + + /// + public Expression Process(QueryExpressionContext context) + { + Ensure.NotNull(context, nameof(context)); + + if (Inner != null) + { + var innerFilteredExpression = Inner.Process(context); + if (innerFilteredExpression != null && innerFilteredExpression != context.VisitedNode) + { + return innerFilteredExpression; + } + } + + // TODO GitHubIssue#330: EF QueryExecutor will throw exception if check whether collections is null added. + // Exception message likes "Cannot compare elements of type 'ICollection`1[[EntityType]]'. + // Only primitive types, enumeration types and entity types are supported." + // EF does not support complex != null neither, and it requires complex not null. + // EF model builder set complex type not null by default, but Web Api OData does not. + if (context.VisitedNode.NodeType == ExpressionType.NotEqual) + { + var binaryExp = (BinaryExpression)context.VisitedNode; + var left = binaryExp.Left as MemberExpression; + var right = binaryExp.Right as ConstantExpression; + + bool rightCheck = right != null && right.Value == null; + + // Check right first which is simple + if (!rightCheck) + { + return context.VisitedNode; + } + + bool leftCheck = false; + if (left != null) + { + // If it is a collection, then replace coll != null with true + leftCheck = left.Type.IsGenericType + && left.Type.GetGenericTypeDefinition() == typeof(ICollection<>); + + // If it is a complex, replace complex!=null with true + if (!leftCheck) + { + var modelRef = context.GetModelReferenceForNode(left); + if (modelRef != null && modelRef.Type != null) + { + leftCheck = modelRef.Type.TypeKind.Equals(EdmTypeKind.Complex); + } + } + } + + if (leftCheck) + { + return Expression.Constant(true); + } + } + + return context.VisitedNode; + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs new file mode 100644 index 0000000..31b9f0a --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +#if !EF7 +using System.Data.Entity; +#endif +using System.Linq; +using System.Linq.Expressions; +#if EF7 +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Query; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// Represents a query expression sourcer that uses a DbContext. + /// + internal class QueryExpressionSourcer : IQueryExpressionSourcer + { + /// + /// Sources an expression. + /// + /// + /// The query expression context. + /// + /// + /// Indicates if the sourcing is occurring on an embedded node. + /// + /// + /// A data source expression that represents the visited node. + /// + public Expression ReplaceQueryableSource(QueryExpressionContext context, bool embedded) + { + Ensure.NotNull(context, nameof(context)); + + if (context.ModelReference.EntitySet == null) + { + // EF provider can only source *ResourceSet*. + return null; + } + + var dbContext = context.QueryContext.GetApiService(); + var dbSetProperty = dbContext.GetType().GetProperties() + .FirstOrDefault(prop => prop.Name == context.ModelReference.EntitySet.Name); + if (dbSetProperty == null) + { + // EF provider can only source EntitySet from *DbSet property*. + return null; + } + + if (!embedded) + { + // TODO GitHubIssue#37 : Add API entity manager for tracking entities + // the underlying DbContext shouldn't track the entities + var dbSet = dbSetProperty.GetValue(dbContext); + + ////dbSet = dbSet.GetType().GetMethod("AsNoTracking").Invoke(dbSet, null); + return Expression.Constant(dbSet); + } + else + { + return Expression.MakeMemberAccess( + Expression.Constant(dbContext), + dbSetProperty); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Spatial/GeographyConverter.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Spatial/GeographyConverter.cs new file mode 100644 index 0000000..93403cd --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Spatial/GeographyConverter.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Data.Entity.Spatial; +using System.Globalization; +using System.Text; +using Microsoft.Spatial; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// The class defined conversion between GeographyPoint and DbGeography, + /// and between GeographyLineString and DbGeography. + /// + public static class GeographyConverter + { + private const string GeographyTypeNamePoint = "Point"; + private const string GeographyTypeNameLineString = "LineString"; + private static readonly CultureInfo DefaultCulture = CultureInfo.GetCultureInfo("En-Us"); + + /// + /// Convert a DbGeography to Edm GeographyPoint + /// + /// The DbGeography to be converted + /// A Edm GeographyPoint + public static GeographyPoint ToGeographyPoint(this DbGeography geography) + { + if (geography == null) + { + return null; + } + + if (geography.SpatialTypeName != GeographyTypeNamePoint) + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + Resources.InvalidPointGeographyType, + geography.SpatialTypeName)); + } + + double lat = geography.Latitude ?? 0; + double lon = geography.Longitude ?? 0; + double? alt = geography.Elevation; + double? m = geography.Measure; + return GeographyPoint.Create(lat, lon, alt, m); + } + + /// + /// Convert a Edm GeographyPoint to DbGeography + /// + /// The Edm GeographyPoint to be converted + /// A DbGeography + public static DbGeography ToDbGeography(this GeographyPoint point) + { + if (point == null) + { + return null; + } + + string text = "POINT(" + point.Latitude.ToString(DefaultCulture) + " " + + point.Longitude.ToString(DefaultCulture); + + if (point.Z.HasValue) + { + text += " " + point.Z.Value; + } + + if (point.M.HasValue) + { + text += " " + point.M.Value; + } + + text += ")"; + + return DbGeography.FromText(text); + } + + /// + /// Convert a DbGeography to Edm GeographyPoint + /// + /// The DbGeography to be converted + /// A Edm GeographyLineString + public static GeographyLineString ToGeographyLineString(this DbGeography geography) + { + if (geography == null) + { + return null; + } + + if (geography.SpatialTypeName != GeographyTypeNameLineString) + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + Resources.InvalidLineStringGeographyType, + geography.SpatialTypeName)); + } + + SpatialBuilder builder = SpatialBuilder.Create(); + GeographyPipeline pipleLine = builder.GeographyPipeline; + pipleLine.SetCoordinateSystem(CoordinateSystem.DefaultGeography); + pipleLine.BeginGeography(SpatialType.LineString); + + int numPoints = geography.PointCount ?? 0; + if (numPoints > 0) + { + DbGeography point = geography.PointAt(1); + pipleLine.BeginFigure(new GeographyPosition( + point.Latitude ?? 0, point.Latitude ?? 0, point.Elevation, point.Measure)); + + for (int n = 2; n <= numPoints; n++) + { + point = geography.PointAt(n); + pipleLine.LineTo(new GeographyPosition( + point.Latitude ?? 0, point.Latitude ?? 0, point.Elevation, point.Measure)); + } + + pipleLine.EndFigure(); + } + + pipleLine.EndGeography(); + GeographyLineString lineString = (GeographyLineString)builder.ConstructedGeography; + return lineString; + } + + /// + /// Convert a Edm GeographyLineString to DbGeography + /// + /// The Edm GeographyLineString to be converted + /// A DbGeography + public static DbGeography ToDbGeography(this GeographyLineString lineString) + { + if (lineString == null) + { + return null; + } + + StringBuilder sb = new StringBuilder("LINESTRING("); + int n = 0; + foreach (var pt in lineString.Points) + { + double lat = pt.Latitude; + double lon = pt.Longitude; + double? alt = pt.Z; + double? m = pt.M; + + string pointStr = lat.ToString(DefaultCulture) + " " + lon.ToString(DefaultCulture); + + if (alt != null) + { + pointStr += " " + alt.Value; + } + + if (m != null) + { + pointStr += " " + m.Value; + } + + sb.Append(pointStr); + n++; + if (n != lineString.Points.Count) + { + sb.Append(","); + } + } + + sb.Append(")"); + + return DbGeography.FromText(sb.ToString()); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/ChangeSetInitializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/ChangeSetInitializer.cs new file mode 100644 index 0000000..d956558 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/ChangeSetInitializer.cs @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +using System.Data.Entity.Spatial; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; +using Microsoft.Spatial; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// To prepare changed entries for the given . + /// + public class ChangeSetInitializer : IChangeSetInitializer + { + /// + /// Asynchronously prepare the . + /// + /// The submit context class used for preparation. + /// The cancellation token. + /// The task object that represents this asynchronous operation. + public async Task InitializeAsync(SubmitContext context, CancellationToken cancellationToken) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var dbContext = context.GetApiService(); + + foreach (var entry in context.ChangeSet.Entries.OfType()) + { + var strongTypedDbSet = dbContext.GetType().GetProperty(entry.ResourceSetName).GetValue(dbContext); + var resourceType = strongTypedDbSet.GetType().GetGenericArguments()[0]; + + // This means request resource is sub type of resource type + if (entry.ActualResourceType != null && resourceType != entry.ActualResourceType) + { + // Set type to derived type + resourceType = entry.ActualResourceType; + } + + var set = dbContext.Set(resourceType); + + object resource; + + if (entry.EntitySetOperation == RestierEntitySetOperation.Insert) + { + resource = set.Create(); + SetValues(resource, resourceType, entry.LocalValues); + set.Add(resource); + } + else if (entry.EntitySetOperation == RestierEntitySetOperation.Delete) + { + resource = await FindResource(context, entry, cancellationToken).ConfigureAwait(false); + set.Remove(resource); + } + else if (entry.EntitySetOperation == RestierEntitySetOperation.Update) + { + resource = await FindResource(context, entry, cancellationToken).ConfigureAwait(false); + + var dbEntry = dbContext.Entry(resource); + SetValues(dbEntry, entry, resourceType); + } + else + { + throw new NotSupportedException(Resources.DataModificationMustBeCUD); + } + + entry.Resource = resource; + } + } + + /// + /// Convert a Edm type value to Resource Framework supported value type + /// + /// The type of the property defined in CLR class + /// The value from OData deserializer and in type of Edm + /// The converted value object + public virtual object ConvertToEfValue(Type type, object value) + { + // string[EdmType = Enum] => System.Enum + if (TypeHelper.IsEnum(type)) + { + return Enum.Parse(TypeHelper.GetUnderlyingTypeOrSelf(type), (string)value); + } + + // Edm.Date => System.DateTime[SqlType = Date] + if (value is Date dateValue) + { + return (DateTime)dateValue; + } + + // System.DateTimeOffset => System.DateTime[SqlType = DateTime or DateTime2] + if (value is DateTimeOffset && TypeHelper.IsDateTime(type)) + { + var dateTimeOffsetValue = (DateTimeOffset)value; + return dateTimeOffsetValue.DateTime; + } + + // Edm.TimeOfDay => System.TimeSpan[SqlType = Time] + if (value is TimeOfDay && TypeHelper.IsTimeSpan(type)) + { + var timeOfDayValue = (TimeOfDay)value; + return (TimeSpan)timeOfDayValue; + } + + // In case key is long type, when put an resource, key value will be from key parsing which is type of int + if (value is int && type == typeof(long)) + { + return Convert.ToInt64(value, CultureInfo.InvariantCulture); + } + + if (type == typeof(DbGeography)) + { + if (value is GeographyPoint point) + { + return point.ToDbGeography(); + } + + if (value is GeographyLineString s) + { + return s.ToDbGeography(); + } + } + + return value; + } + + private static async Task FindResource(SubmitContext context, DataModificationItem item, CancellationToken cancellationToken) + { + var apiBase = context.GetApiService(); + var query = apiBase.GetQueryableSource(item.ResourceSetName); + query = item.ApplyTo(query); + + var result = await apiBase.QueryAsync(new QueryRequest(query), cancellationToken).ConfigureAwait(false); + + var resource = result.Results.SingleOrDefault(); + if (resource == null) + { + throw new StatusCodeException(HttpStatusCode.NotFound, Resources.ResourceNotFound); + } + + // This means no If-Match or If-None-Match header + if (item.OriginalValues == null || item.OriginalValues.Count == 0) + { + return resource; + } + + resource = item.ValidateEtag(result.Results.AsQueryable()); + return resource; + } + + private void SetValues(DbEntityEntry dbEntry, DataModificationItem item, Type resourceType) + { + if (item.IsFullReplaceUpdateRequest) + { + // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource + // in WCF DS, and works as follows: + // - Create a new, blank instance of the entity. + // - Copy over the key values and set any updated values from the client on the new instance. + // - Then apply all the properties of the new instance to the instance to be updated. + // This will set any unspecified properties to their default value. + var newInstance = Activator.CreateInstance(resourceType); + + SetValues(newInstance, resourceType, item.ResourceKey); + SetValues(newInstance, resourceType, item.LocalValues); + + dbEntry.CurrentValues.SetValues(newInstance); + } + else + { + foreach (var propertyPair in item.LocalValues) + { + var propertyEntry = dbEntry.Property(propertyPair.Key); + var value = propertyPair.Value; + if (value == null) + { + // If the property value is null, we set null in the item too. + propertyEntry.CurrentValue = null; + continue; + } + + Type type = null; + if (propertyEntry.CurrentValue != null) + { + type = propertyEntry.CurrentValue.GetType(); + } + else + { + // If property does not have value now, will get property type from model + var propertyInfo = dbEntry.Entity.GetType().GetProperty(propertyPair.Key); + type = propertyInfo.PropertyType; + } + + if (propertyEntry is DbComplexPropertyEntry) + { + if (!(value is IReadOnlyDictionary dic)) + { + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resources.UnsupportedPropertyType, propertyPair.Key)); + } + + value = propertyEntry.CurrentValue; + SetValues(value, type, dic); + } + + propertyEntry.CurrentValue = ConvertToEfValue(type, value); + } + } + } + + private void SetValues(object instance, Type type, IReadOnlyDictionary values) + { + foreach (var propertyPair in values) + { + var value = propertyPair.Value; + var propertyInfo = type.GetProperty(propertyPair.Key); + if (value == null) + { + // If the property value is null, we set null in the object too. + propertyInfo.SetValue(instance, null); + continue; + } + + value = ConvertToEfValue(propertyInfo.PropertyType, value); + if (value != null && !propertyInfo.PropertyType.IsInstanceOfType(value)) + { + if (!(value is IReadOnlyDictionary dic)) + { + propertyInfo.SetValue(instance, value); + return; + //throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resources.UnsupportedPropertyType, propertyPair.Key)); + } + + // TODO GithubIssue #508 + value = Activator.CreateInstance(propertyInfo.PropertyType); + SetValues(value, propertyInfo.PropertyType, dic); + } + + propertyInfo.SetValue(instance, value); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs new file mode 100644 index 0000000..b7f13e0 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#if !EF7 +using System.Data.Entity; +#endif +using System.Threading; +using System.Threading.Tasks; +#if EF7 +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.EntityFramework +{ + /// + /// To execute submission of changes to database. + /// + internal class SubmitExecutor : ISubmitExecutor + { + /// + /// Asynchronously executes the submission. + /// + /// The submit context class used for preparation. + /// The cancellation token. + /// The task object that represents this asynchronous operation. + public async Task ExecuteSubmitAsync(SubmitContext context, CancellationToken cancellationToken) + { + var dbContext = context.GetApiService(); + await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + return new SubmitResult(context.ChangeSet); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/App.config b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/App.config new file mode 100644 index 0000000..8aa83c9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/App.config @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Microsoft.Restier.Providers.EntityFramework7.csproj_old b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Microsoft.Restier.Providers.EntityFramework7.csproj_old new file mode 100644 index 0000000..28cd6c7 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Microsoft.Restier.Providers.EntityFramework7.csproj_old @@ -0,0 +1,192 @@ + + + + + {D54A765E-4604-4C73-868B-0E0459B3A6B6} + Library + Properties + Microsoft.Restier.Providers.EntityFramework + Microsoft.Restier.Providers.EntityFramework7 + $(OutputPath)$(AssemblyName).xml + $(CodeAnalysis) + ..\Strict.ruleset + v4.5.1 + + + + false + + + true + TRACE;DEBUG;CODE_ANALYSIS;EF7 + + + true + + + + + ..\..\packages\Microsoft.EntityFrameworkCore.1.1.2\lib\net451\Microsoft.EntityFrameworkCore.dll + + + ..\..\packages\Microsoft.EntityFrameworkCore.Relational.1.1.2\lib\net451\Microsoft.EntityFrameworkCore.Relational.dll + + + ..\..\packages\Microsoft.Extensions.Caching.Abstractions.1.1.1\lib\netstandard1.0\Microsoft.Extensions.Caching.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Caching.Memory.1.1.1\lib\net451\Microsoft.Extensions.Caching.Memory.dll + + + ..\..\packages\Microsoft.Extensions.DependencyInjection.1.1.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll + + + ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Logging.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.dll + + + ..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Options.1.1.1\lib\netstandard1.0\Microsoft.Extensions.Options.dll + + + ..\..\packages\Microsoft.Extensions.Primitives.1.1.0\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll + + + ..\..\packages\Microsoft.OData.Edm.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + ..\..\packages\Remotion.Linq.2.1.1\lib\net45\Remotion.Linq.dll + True + + + + ..\..\packages\System.Collections.Immutable.1.3.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + + + + ..\..\packages\System.Diagnostics.DiagnosticSource.4.3.1\lib\portable-net45+win8+wpa81\System.Diagnostics.DiagnosticSource.dll + + + ..\..\packages\System.Interactive.Async.3.0.0\lib\net45\System.Interactive.Async.dll + True + + + + + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + + + + + + + + Properties\CommonAssemblyInfo.cs + + + GlobalSuppressions.cs + true + + + EntityFrameworkApi.cs + + + Model.Shared\ModelMapper.cs + + + Model.Shared\ModelProducer.cs + + + Query.Shared\QueryExecutor.cs + + + Query.Shared\QueryExpressionProcessor.cs + + + Query.Shared\QueryExpressionSourcer.cs + + + ServiceCollectionExtensions.cs + + + Submit.Shared\SubmitExecutor.cs + + + Shared\Ensure.cs + + + Shared\ExpressionHelperMethods.cs + + + Shared\ExpressionHelpers.cs + + + Shared\EnumerableExtensions.cs + + + Shared\TypeExtensions.cs + + + SharedResources.EntityFramework.Designer.cs + True + True + SharedResources.resx + + + + True + True + Resources.resx + + + + + + {f1beab8d-82d4-4bbb-a5c6-ba0e6872e508} + Microsoft.Restier.Core + + + + + Designer + + + Designer + + + + + CodeAnalysisDictionary.xml + + + + + + + + + + + ResXFileCodeGenerator + SharedResources.EntityFramework.Designer.cs + Microsoft.Restier.Shared + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/AssemblyInfo.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8077ef5 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RESTier Entity Framework 7 Module")] +[assembly: AssemblyDescription("A module that connects the RESTier to an Entity Framework based data source proxy.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Microsoft.Restier.Providers.EntityFramework")] +[assembly: AssemblyCopyright("Copyright © Microsoft Corporation 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5880279B-620A-4745-88D0-58685A9B93E4")] \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.Designer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.Designer.cs new file mode 100644 index 0000000..9cf9669 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Providers.EntityFramework.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Restier.Providers.EntityFramework.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to A DataModificationEntry must be either New, Update or Delete.. + /// + internal static string DataModificationMustBeCUD { + get { + return ResourceManager.GetString("DataModificationMustBeCUD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the specified resource.. + /// + internal static string ResourceNotFound { + get { + return ResourceManager.GetString("ResourceNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported type for property: {0}.. + /// + internal static string UnsupportedPropertyType { + get { + return ResourceManager.GetString("UnsupportedPropertyType", resourceCulture); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.resx b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.resx new file mode 100644 index 0000000..103f5e2 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Properties/Resources.resx @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A DataModificationEntry must be either New, Update or Delete. + + + Could not find the specified resource. + The target entity not found for modification + + + Unsupported type for property: {0}. + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Submit/ChangeSetInitializer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Submit/ChangeSetInitializer.cs new file mode 100644 index 0000000..54ee91e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFrameworkCore/Submit/ChangeSetInitializer.cs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; +using Microsoft.Restier.Providers.EntityFramework.Properties; + +namespace Microsoft.Restier.Providers.EntityFramework +{ + /// + /// To prepare changed entries for the given . + /// For this class we cannot reuse EF6 ChangeSetPreparer code, since many types used here have their type name or + /// member name changed. + /// + public class ChangeSetInitializer : IChangeSetInitializer + { + private static MethodInfo prepareEntryGeneric = typeof(ChangeSetInitializer) + .GetMethod("PrepareEntry", BindingFlags.Instance | BindingFlags.NonPublic); + + /// + /// Asynchronously prepare the . + /// + /// The submit context class used for preparation. + /// The cancellation token. + /// The task object that represents this asynchronous operation. + public async Task InitializeAsync( + SubmitContext context, + CancellationToken cancellationToken) + { + DbContext dbContext = context.GetApiService(); + + foreach (var entry in context.ChangeSet.Entries.OfType()) + { + object strongTypedDbSet = dbContext.GetType().GetProperty(entry.ResourceSetName).GetValue(dbContext); + Type entityType = strongTypedDbSet.GetType().GetGenericArguments()[0]; + + // This means request resource is sub type of resource type + if (entry.ActualResourceType != null && entityType != entry.ActualResourceType) + { + entityType = entry.ActualResourceType; + } + + MethodInfo prepareEntryMethod = prepareEntryGeneric.MakeGenericMethod(entityType); + + var task = (Task)prepareEntryMethod.Invoke( + obj: this, + parameters: new[] { context, dbContext, entry, strongTypedDbSet, cancellationToken }); + await task; + } + } + + /// + /// Convert a Edm type value to Resource Framework supported value type + /// + /// The type of the property defined in CLR class + /// The value from OData deserializer and in type of Edm + /// The converted value object + public virtual object ConvertToEfValue(Type type, object value) + { + // string[EdmType = Enum] => System.Enum + if (TypeHelper.IsEnum(type)) + { + return Enum.Parse(TypeHelper.GetUnderlyingTypeOrSelf(type), (string)value); + } + + // Edm.Date => System.DateTime[SqlType = Date] + if (value is Date) + { + var dateValue = (Date)value; + return (DateTime)dateValue; + } + + // System.DateTimeOffset => System.DateTime[SqlType = DateTime or DateTime2] + if (value is DateTimeOffset && TypeHelper.IsDateTime(type)) + { + var dateTimeOffsetValue = (DateTimeOffset)value; + return dateTimeOffsetValue.DateTime; + } + + // Edm.TimeOfDay => System.TimeSpan[SqlType = Time] + if (value is TimeOfDay && TypeHelper.IsTimeSpan(type)) + { + var timeOfDayValue = (TimeOfDay)value; + return (TimeSpan)timeOfDayValue; + } + + // In case key is long type, when put an resource, key value will be from key parsing which is type of int + if (value is int && type == typeof(long)) + { + return Convert.ToInt64(value, CultureInfo.InvariantCulture); + } + + return value; + } + + private static async Task FindResource( + SubmitContext context, + DataModificationItem item, + CancellationToken cancellationToken) + { + var apiBase = context.GetApiService(); + IQueryable query = apiBase.GetQueryableSource(item.ResourceSetName); + query = item.ApplyTo(query); + + QueryResult result = await apiBase.QueryAsync(new QueryRequest(query), cancellationToken); + + object resource = result.Results.SingleOrDefault(); + if (resource == null) + { + throw new ResourceNotFoundException(Resources.ResourceNotFound); + } + + // This means no If-Match or If-None-Match header + if (item.OriginalValues == null || item.OriginalValues.Count == 0) + { + return resource; + } + + resource = item.ValidateEtag(result.Results.AsQueryable()); + return resource; + } + + private async Task PrepareEntry( + SubmitContext context, + DbContext dbContext, + DataModificationItem entry, + DbSet set, + CancellationToken cancellationToken) where TEntity : class + { + Type entityType = typeof(TEntity); + TEntity entity; + + if (entry.DataModificationItemAction == DataModificationItemAction.Insert) + { + // TODO: See if Create method is in DbSet<> in future EF7 releases, as the one EF6 has. + entity = (TEntity)Activator.CreateInstance(typeof(TEntity)); + + SetValues(entity, entityType, entry.LocalValues); + set.Add(entity); + } + else if (entry.DataModificationItemAction == DataModificationItemAction.Remove) + { + entity = (TEntity)await ChangeSetInitializer.FindResource(context, entry, cancellationToken); + set.Remove(entity); + } + else if (entry.DataModificationItemAction == DataModificationItemAction.Update) + { + if (entry.IsFullReplaceUpdateRequest) + { + entity = (TEntity)CreateFullUpdateInstance(entry, entityType); + dbContext.Update(entity); + } + else + { + entity = (TEntity)await ChangeSetInitializer.FindResource(context, entry, cancellationToken); + + var dbEntry = dbContext.Attach(entity); + SetValues(dbEntry, entry); + } + } + else + { + throw new NotSupportedException(Resources.DataModificationMustBeCUD); + } + + entry.Resource = entity; + } + + private object CreateFullUpdateInstance(DataModificationItem entry, Type entityType) + { + // The algorithm for a "FullReplaceUpdate" is taken from ObjectContextServiceProvider.ResetResource + // in WCF DS, and works as follows: + // - Create a new, blank instance of the entity. + // - Copy over the key values and set any updated values from the client on the new instance. + // - Then apply all the properties of the new instance to the instance to be updated. + // This will set any unspecified properties to their default value. + object newInstance = Activator.CreateInstance(entityType); + + SetValues(newInstance, entityType, entry.ResourceKey); + SetValues(newInstance, entityType, entry.LocalValues); + + return newInstance; + } + + private void SetValues(EntityEntry dbEntry, DataModificationItem entry) + { + foreach (KeyValuePair propertyPair in entry.LocalValues) + { + PropertyEntry propertyEntry = dbEntry.Property(propertyPair.Key); + object value = propertyPair.Value; + if (value == null) + { + // If the property value is null, we set null in the entry too. + propertyEntry.CurrentValue = null; + continue; + } + + Type type = TypeHelper.GetUnderlyingTypeOrSelf(propertyEntry.Metadata.ClrType); + value = ConvertToEfValue(type, value); + if (value != null && !type.IsInstanceOfType(value)) + { + var dic = value as IReadOnlyDictionary; + if (dic == null) + { + throw new NotSupportedException(string.Format( + CultureInfo.InvariantCulture, + Resources.UnsupportedPropertyType, + propertyPair.Key)); + } + + value = Activator.CreateInstance(type); + SetValues(value, type, dic); + } + + propertyEntry.CurrentValue = value; + } + } + + private void SetValues(object instance, Type instanceType, IReadOnlyDictionary values) + { + foreach (KeyValuePair propertyPair in values) + { + object value = propertyPair.Value; + PropertyInfo propertyInfo = instanceType.GetProperty(propertyPair.Key); + if (value == null) + { + // If the property value is null, we set null in the object too. + propertyInfo.SetValue(instance, null); + continue; + } + + Type type = TypeHelper.GetUnderlyingTypeOrSelf(propertyInfo.PropertyType); + value = ConvertToEfValue(type, value); + if (value != null && !type.IsInstanceOfType(value)) + { + var dic = value as IReadOnlyDictionary; + if (dic == null) + { + throw new NotSupportedException(string.Format( + CultureInfo.InvariantCulture, + Resources.UnsupportedPropertyType, + propertyPair.Key)); + } + + value = Activator.CreateInstance(type); + SetValues(value, type, dic); + } + + propertyInfo.SetValue(instance, value); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/App_Start/WebApiConfig.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/App_Start/WebApiConfig.cs new file mode 100644 index 0000000..44f4470 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/App_Start/WebApiConfig.cs @@ -0,0 +1,36 @@ +using System; +using System.Web.Http; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Restier.AspNet.Batch; +using Microsoft.Restier.Samples.Northwind.AspNet.Controllers; + +namespace Microsoft.Restier.Samples.Northwind.AspNet +{ + public static class WebApiConfig + { + + public static async void Register(HttpConfiguration config) + { + + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + +#if !PROD + config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; +#endif + + config.Filter().Expand().Select().OrderBy().MaxTop(100).Count(); + config.SetTimeZoneInfo(TimeZoneInfo.Utc); + + config.MapHttpAttributeRoutes(); + + var batchHandler = new RestierBatchHandler(GlobalConfiguration.DefaultServer); +#pragma warning disable CA2007 // Do not directly await a Task + await config.MapRestierRoute("ApiV1", "", batchHandler); +#pragma warning restore CA2007 // Do not directly await a Task + + } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Controllers/NorthwindApi.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Controllers/NorthwindApi.cs new file mode 100644 index 0000000..b253072 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Controllers/NorthwindApi.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using Microsoft.Restier.EntityFramework; +using Microsoft.Restier.Samples.Northwind.AspNet.Data; + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Controllers +{ + + /// + /// + /// + public partial class NorthwindApi : EntityFrameworkApi + { + + public NorthwindApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + /// + /// + /// + /// + /// + protected internal IQueryable OnFilterCategories(IQueryable entitySet) + { + //TraceEvent("CompanyEmployee", RestierOperationTypes.Filtered); + return entitySet;//.Take(1); + } + + /// + /// + /// + /// + protected internal void OnInsertingCategory(Category entity) + { + //CompanyEmployeeManager.OnInserting(entity); + //TrackEvent(entity, RestierOperationTypes.Inserting); + Console.WriteLine("Inserting Category..."); + } + + + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Category.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Category.cs new file mode 100644 index 0000000..e9d43a0 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Category.cs @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Category + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Category() + { + this.Products = new HashSet(); + } + + public int CategoryID { get; set; } + public string CategoryName { get; set; } + public string Description { get; set; } + public byte[] Picture { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Products { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Customer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Customer.cs new file mode 100644 index 0000000..2664cb2 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Customer.cs @@ -0,0 +1,41 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Customer + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Customer() + { + this.Orders = new HashSet(); + this.CustomerDemographics = new HashSet(); + } + + public string CustomerID { get; set; } + public string CompanyName { get; set; } + public string ContactName { get; set; } + public string ContactTitle { get; set; } + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + public string Phone { get; set; } + public string Fax { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Orders { get; set; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection CustomerDemographics { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/CustomerDemographic.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/CustomerDemographic.cs new file mode 100644 index 0000000..a05a696 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/CustomerDemographic.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class CustomerDemographic + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public CustomerDemographic() + { + this.Customers = new HashSet(); + } + + public string CustomerTypeID { get; set; } + public string CustomerDesc { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Customers { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Employee.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Employee.cs new file mode 100644 index 0000000..da9fb4a --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Employee.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Employee + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Employee() + { + this.Employees1 = new HashSet(); + this.Orders = new HashSet(); + this.Territories = new HashSet(); + } + + public int EmployeeID { get; set; } + public string LastName { get; set; } + public string FirstName { get; set; } + public string Title { get; set; } + public string TitleOfCourtesy { get; set; } + public Nullable BirthDate { get; set; } + public Nullable HireDate { get; set; } + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + public string HomePhone { get; set; } + public string Extension { get; set; } + public byte[] Photo { get; set; } + public string Notes { get; set; } + public Nullable ReportsTo { get; set; } + public string PhotoPath { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Employees1 { get; set; } + public virtual Employee Employee1 { get; set; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Orders { get; set; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Territories { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.cs new file mode 100644 index 0000000..47c64d7 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.cs @@ -0,0 +1,40 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Data.Entity; + using System.Data.Entity.Infrastructure; + + public partial class NorthwindEntities : DbContext + { + public NorthwindEntities() + : base("name=NorthwindEntities") + { + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + throw new UnintentionalCodeFirstException(); + } + + public virtual DbSet Categories { get; set; } + public virtual DbSet CustomerDemographics { get; set; } + public virtual DbSet Customers { get; set; } + public virtual DbSet Employees { get; set; } + public virtual DbSet Order_Details { get; set; } + public virtual DbSet Orders { get; set; } + public virtual DbSet Products { get; set; } + public virtual DbSet Regions { get; set; } + public virtual DbSet Shippers { get; set; } + public virtual DbSet Suppliers { get; set; } + public virtual DbSet Territories { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.tt b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.tt new file mode 100644 index 0000000..7025d5c --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Context.tt @@ -0,0 +1,636 @@ +<#@ template language="C#" debug="false" hostspecific="true"#> +<#@ include file="EF6.Utility.CS.ttinclude"#><#@ + output extension=".cs"#><# + +const string inputFile = @"Northwind.edmx"; +var textTransform = DynamicTextTransformation.Create(this); +var code = new CodeGenerationTools(this); +var ef = new MetadataTools(this); +var typeMapper = new TypeMapper(code, ef, textTransform.Errors); +var loader = new EdmMetadataLoader(textTransform.Host, textTransform.Errors); +var itemCollection = loader.CreateEdmItemCollection(inputFile); +var modelNamespace = loader.GetModelNamespace(inputFile); +var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); + +var container = itemCollection.OfType().FirstOrDefault(); +if (container == null) +{ + return string.Empty; +} +#> +//------------------------------------------------------------------------------ +// +// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#> +// +// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#> +// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#> +// +//------------------------------------------------------------------------------ + +<# + +var codeNamespace = code.VsNamespaceSuggestion(); +if (!String.IsNullOrEmpty(codeNamespace)) +{ +#> +namespace <#=code.EscapeNamespace(codeNamespace)#> +{ +<# + PushIndent(" "); +} + +#> +using System; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; +<# +if (container.FunctionImports.Any()) +{ +#> +using System.Data.Entity.Core.Objects; +using System.Linq; +<# +} +#> + +<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext +{ + public <#=code.Escape(container)#>() + : base("name=<#=container.Name#>") + { +<# +if (!loader.IsLazyLoadingEnabled(container)) +{ +#> + this.Configuration.LazyLoadingEnabled = false; +<# +} + +foreach (var entitySet in container.BaseEntitySets.OfType()) +{ + // Note: the DbSet members are defined below such that the getter and + // setter always have the same accessibility as the DbSet definition + if (Accessibility.ForReadOnlyProperty(entitySet) != "public") + { +#> + <#=codeStringGenerator.DbSetInitializer(entitySet)#> +<# + } +} +#> + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + throw new UnintentionalCodeFirstException(); + } + +<# + foreach (var entitySet in container.BaseEntitySets.OfType()) + { +#> + <#=codeStringGenerator.DbSet(entitySet)#> +<# + } + + foreach (var edmFunction in container.FunctionImports) + { + WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: false); + } +#> +} +<# + +if (!String.IsNullOrEmpty(codeNamespace)) +{ + PopIndent(); +#> +} +<# +} +#> +<#+ + +private void WriteFunctionImport(TypeMapper typeMapper, CodeStringGenerator codeStringGenerator, EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) +{ + if (typeMapper.IsComposable(edmFunction)) + { +#> + + [DbFunction("<#=edmFunction.NamespaceName#>", "<#=edmFunction.Name#>")] + <#=codeStringGenerator.ComposableFunctionMethod(edmFunction, modelNamespace)#> + { +<#+ + codeStringGenerator.WriteFunctionParameters(edmFunction, WriteFunctionParameter); +#> + <#=codeStringGenerator.ComposableCreateQuery(edmFunction, modelNamespace)#> + } +<#+ + } + else + { +#> + + <#=codeStringGenerator.FunctionMethod(edmFunction, modelNamespace, includeMergeOption)#> + { +<#+ + codeStringGenerator.WriteFunctionParameters(edmFunction, WriteFunctionParameter); +#> + <#=codeStringGenerator.ExecuteFunction(edmFunction, modelNamespace, includeMergeOption)#> + } +<#+ + if (typeMapper.GenerateMergeOptionFunction(edmFunction, includeMergeOption)) + { + WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: true); + } + } +} + +public void WriteFunctionParameter(string name, string isNotNull, string notNullInit, string nullInit) +{ +#> + var <#=name#> = <#=isNotNull#> ? + <#=notNullInit#> : + <#=nullInit#>; + +<#+ +} + +public const string TemplateId = "CSharp_DbContext_Context_EF6"; + +public class CodeStringGenerator +{ + private readonly CodeGenerationTools _code; + private readonly TypeMapper _typeMapper; + private readonly MetadataTools _ef; + + public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef) + { + ArgumentNotNull(code, "code"); + ArgumentNotNull(typeMapper, "typeMapper"); + ArgumentNotNull(ef, "ef"); + + _code = code; + _typeMapper = typeMapper; + _ef = ef; + } + + public string Property(EdmProperty edmProperty) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1} {2} {{ {3}get; {4}set; }}", + Accessibility.ForProperty(edmProperty), + _typeMapper.GetTypeName(edmProperty.TypeUsage), + _code.Escape(edmProperty), + _code.SpaceAfter(Accessibility.ForGetter(edmProperty)), + _code.SpaceAfter(Accessibility.ForSetter(edmProperty))); + } + + public string NavigationProperty(NavigationProperty navProp) + { + var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType()); + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1} {2} {{ {3}get; {4}set; }}", + AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)), + navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, + _code.Escape(navProp), + _code.SpaceAfter(Accessibility.ForGetter(navProp)), + _code.SpaceAfter(Accessibility.ForSetter(navProp))); + } + + public string AccessibilityAndVirtual(string accessibility) + { + return accessibility + (accessibility != "private" ? " virtual" : ""); + } + + public string EntityClassOpening(EntityType entity) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1}partial class {2}{3}", + Accessibility.ForType(entity), + _code.SpaceAfter(_code.AbstractOption(entity)), + _code.Escape(entity), + _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType))); + } + + public string EnumOpening(SimpleType enumType) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} enum {1} : {2}", + Accessibility.ForType(enumType), + _code.Escape(enumType), + _code.Escape(_typeMapper.UnderlyingClrType(enumType))); + } + + public void WriteFunctionParameters(EdmFunction edmFunction, Action writeParameter) + { + var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); + foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable)) + { + var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"; + var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")"; + var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))"; + writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit); + } + } + + public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace) + { + var parameters = _typeMapper.GetParameters(edmFunction); + + return string.Format( + CultureInfo.InvariantCulture, + "{0} IQueryable<{1}> {2}({3})", + AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), + _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), + _code.Escape(edmFunction), + string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray())); + } + + public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace) + { + var parameters = _typeMapper.GetParameters(edmFunction); + + return string.Format( + CultureInfo.InvariantCulture, + "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});", + _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), + edmFunction.NamespaceName, + edmFunction.Name, + string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()), + _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))); + } + + public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) + { + var parameters = _typeMapper.GetParameters(edmFunction); + var returnType = _typeMapper.GetReturnType(edmFunction); + + var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()); + if (includeMergeOption) + { + paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption"; + } + + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1} {2}({3})", + AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), + returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", + _code.Escape(edmFunction), + paramList); + } + + public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) + { + var parameters = _typeMapper.GetParameters(edmFunction); + var returnType = _typeMapper.GetReturnType(edmFunction); + + var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())); + if (includeMergeOption) + { + callParams = ", mergeOption" + callParams; + } + + return string.Format( + CultureInfo.InvariantCulture, + "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});", + returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", + edmFunction.Name, + callParams); + } + + public string DbSet(EntitySet entitySet) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} virtual DbSet<{1}> {2} {{ get; set; }}", + Accessibility.ForReadOnlyProperty(entitySet), + _typeMapper.GetTypeName(entitySet.ElementType), + _code.Escape(entitySet)); + } + + public string DbSetInitializer(EntitySet entitySet) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} = Set<{1}>();", + _code.Escape(entitySet), + _typeMapper.GetTypeName(entitySet.ElementType)); + } + + public string UsingDirectives(bool inHeader, bool includeCollections = true) + { + return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion()) + ? string.Format( + CultureInfo.InvariantCulture, + "{0}using System;{1}" + + "{2}", + inHeader ? Environment.NewLine : "", + includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "", + inHeader ? "" : Environment.NewLine) + : ""; + } +} + +public class TypeMapper +{ + private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName"; + + private readonly System.Collections.IList _errors; + private readonly CodeGenerationTools _code; + private readonly MetadataTools _ef; + + public static string FixNamespaces(string typeName) + { + return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial."); + } + + public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors) + { + ArgumentNotNull(code, "code"); + ArgumentNotNull(ef, "ef"); + ArgumentNotNull(errors, "errors"); + + _code = code; + _ef = ef; + _errors = errors; + } + + public string GetTypeName(TypeUsage typeUsage) + { + return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null); + } + + public string GetTypeName(EdmType edmType) + { + return GetTypeName(edmType, isNullable: null, modelNamespace: null); + } + + public string GetTypeName(TypeUsage typeUsage, string modelNamespace) + { + return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace); + } + + public string GetTypeName(EdmType edmType, string modelNamespace) + { + return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace); + } + + public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace) + { + if (edmType == null) + { + return null; + } + + var collectionType = edmType as CollectionType; + if (collectionType != null) + { + return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace)); + } + + var typeName = _code.Escape(edmType.MetadataProperties + .Where(p => p.Name == ExternalTypeNameAttributeName) + .Select(p => (string)p.Value) + .FirstOrDefault()) + ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ? + _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) : + _code.Escape(edmType)); + + if (edmType is StructuralType) + { + return typeName; + } + + if (edmType is SimpleType) + { + var clrType = UnderlyingClrType(edmType); + if (!IsEnumType(edmType)) + { + typeName = _code.Escape(clrType); + } + + typeName = FixNamespaces(typeName); + + return clrType.IsValueType && isNullable == true ? + String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) : + typeName; + } + + throw new ArgumentException("edmType"); + } + + public Type UnderlyingClrType(EdmType edmType) + { + ArgumentNotNull(edmType, "edmType"); + + var primitiveType = edmType as PrimitiveType; + if (primitiveType != null) + { + return primitiveType.ClrEquivalentType; + } + + if (IsEnumType(edmType)) + { + return GetEnumUnderlyingType(edmType).ClrEquivalentType; + } + + return typeof(object); + } + + public object GetEnumMemberValue(MetadataItem enumMember) + { + ArgumentNotNull(enumMember, "enumMember"); + + var valueProperty = enumMember.GetType().GetProperty("Value"); + return valueProperty == null ? null : valueProperty.GetValue(enumMember, null); + } + + public string GetEnumMemberName(MetadataItem enumMember) + { + ArgumentNotNull(enumMember, "enumMember"); + + var nameProperty = enumMember.GetType().GetProperty("Name"); + return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null); + } + + public System.Collections.IEnumerable GetEnumMembers(EdmType enumType) + { + ArgumentNotNull(enumType, "enumType"); + + var membersProperty = enumType.GetType().GetProperty("Members"); + return membersProperty != null + ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null) + : Enumerable.Empty(); + } + + public bool EnumIsFlags(EdmType enumType) + { + ArgumentNotNull(enumType, "enumType"); + + var isFlagsProperty = enumType.GetType().GetProperty("IsFlags"); + return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null); + } + + public bool IsEnumType(GlobalItem edmType) + { + ArgumentNotNull(edmType, "edmType"); + + return edmType.GetType().Name == "EnumType"; + } + + public PrimitiveType GetEnumUnderlyingType(EdmType enumType) + { + ArgumentNotNull(enumType, "enumType"); + + return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null); + } + + public string CreateLiteral(object value) + { + if (value == null || value.GetType() != typeof(TimeSpan)) + { + return _code.CreateLiteral(value); + } + + return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks); + } + + public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable types, string sourceFile) + { + ArgumentNotNull(types, "types"); + ArgumentNotNull(sourceFile, "sourceFile"); + + var hash = new HashSet(StringComparer.InvariantCultureIgnoreCase); + if (types.Any(item => !hash.Add(item))) + { + _errors.Add( + new CompilerError(sourceFile, -1, -1, "6023", + String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict")))); + return false; + } + return true; + } + + public IEnumerable GetEnumItemsToGenerate(IEnumerable itemCollection) + { + return GetItemsToGenerate(itemCollection) + .Where(e => IsEnumType(e)); + } + + public IEnumerable GetItemsToGenerate(IEnumerable itemCollection) where T: EdmType + { + return itemCollection + .OfType() + .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName)) + .OrderBy(i => i.Name); + } + + public IEnumerable GetAllGlobalItems(IEnumerable itemCollection) + { + return itemCollection + .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i)) + .Select(g => GetGlobalItemName(g)); + } + + public string GetGlobalItemName(GlobalItem item) + { + if (item is EdmType) + { + return ((EdmType)item).Name; + } + else + { + return ((EntityContainer)item).Name; + } + } + + public IEnumerable GetSimpleProperties(EntityType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); + } + + public IEnumerable GetSimpleProperties(ComplexType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); + } + + public IEnumerable GetComplexProperties(EntityType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); + } + + public IEnumerable GetComplexProperties(ComplexType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); + } + + public IEnumerable GetPropertiesWithDefaultValues(EntityType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); + } + + public IEnumerable GetPropertiesWithDefaultValues(ComplexType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); + } + + public IEnumerable GetNavigationProperties(EntityType type) + { + return type.NavigationProperties.Where(np => np.DeclaringType == type); + } + + public IEnumerable GetCollectionNavigationProperties(EntityType type) + { + return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many); + } + + public FunctionParameter GetReturnParameter(EdmFunction edmFunction) + { + ArgumentNotNull(edmFunction, "edmFunction"); + + var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters"); + return returnParamsProperty == null + ? edmFunction.ReturnParameter + : ((IEnumerable)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault(); + } + + public bool IsComposable(EdmFunction edmFunction) + { + ArgumentNotNull(edmFunction, "edmFunction"); + + var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute"); + return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null); + } + + public IEnumerable GetParameters(EdmFunction edmFunction) + { + return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); + } + + public TypeUsage GetReturnType(EdmFunction edmFunction) + { + var returnParam = GetReturnParameter(edmFunction); + return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage); + } + + public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption) + { + var returnType = GetReturnType(edmFunction); + return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType; + } +} + +public static void ArgumentNotNull(T arg, string name) where T : class +{ + if (arg == null) + { + throw new ArgumentNullException(name); + } +} +#> \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Designer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Designer.cs new file mode 100644 index 0000000..8323bcc --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.Designer.cs @@ -0,0 +1,10 @@ +// T4 code generation is enabled for model 'D:\GitHub\RESTier\src\Microsoft.Restier.Samples.Northwind.AspNet\Data\Northwind.edmx'. +// To enable legacy code generation, change the value of the 'Code Generation Strategy' designer +// property to 'Legacy ObjectContext'. This property is available in the Properties Window when the model +// is open in the designer. + +// If no context and entity classes have been generated, it may be because you created an empty model but +// have not yet chosen which version of Entity Framework to use. To generate a context class and entity +// classes for your model, open the model in the designer, right-click on the designer surface, and +// select 'Update Model from Database...', 'Generate Database from Model...', or 'Add Code Generation +// Item...'. \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.cs new file mode 100644 index 0000000..7cc0662 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.cs @@ -0,0 +1,9 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx new file mode 100644 index 0000000..4d172e9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx @@ -0,0 +1,922 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx.diagram b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx.diagram new file mode 100644 index 0000000..97473e5 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.edmx.diagram @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.tt b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.tt new file mode 100644 index 0000000..1ce0819 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Northwind.tt @@ -0,0 +1,733 @@ +<#@ template language="C#" debug="false" hostspecific="true"#> +<#@ include file="EF6.Utility.CS.ttinclude"#><#@ + output extension=".cs"#><# + +const string inputFile = @"Northwind.edmx"; +var textTransform = DynamicTextTransformation.Create(this); +var code = new CodeGenerationTools(this); +var ef = new MetadataTools(this); +var typeMapper = new TypeMapper(code, ef, textTransform.Errors); +var fileManager = EntityFrameworkTemplateFileManager.Create(this); +var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile); +var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef); + +if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile)) +{ + return string.Empty; +} + +WriteHeader(codeStringGenerator, fileManager); + +foreach (var entity in typeMapper.GetItemsToGenerate(itemCollection)) +{ + fileManager.StartNewFile(entity.Name + ".cs"); + BeginNamespace(code); +#> +<#=codeStringGenerator.UsingDirectives(inHeader: false)#> +<#=codeStringGenerator.EntityClassOpening(entity)#> +{ +<# + var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity); + var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity); + var complexProperties = typeMapper.GetComplexProperties(entity); + + if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any()) + { +#> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public <#=code.Escape(entity)#>() + { +<# + foreach (var edmProperty in propertiesWithDefaultValues) + { +#> + this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>; +<# + } + + foreach (var navigationProperty in collectionNavigationProperties) + { +#> + this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>(); +<# + } + + foreach (var complexProperty in complexProperties) + { +#> + this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>(); +<# + } +#> + } + +<# + } + + var simpleProperties = typeMapper.GetSimpleProperties(entity); + if (simpleProperties.Any()) + { + foreach (var edmProperty in simpleProperties) + { +#> + <#=codeStringGenerator.Property(edmProperty)#> +<# + } + } + + if (complexProperties.Any()) + { +#> + +<# + foreach(var complexProperty in complexProperties) + { +#> + <#=codeStringGenerator.Property(complexProperty)#> +<# + } + } + + var navigationProperties = typeMapper.GetNavigationProperties(entity); + if (navigationProperties.Any()) + { +#> + +<# + foreach (var navigationProperty in navigationProperties) + { + if (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) + { +#> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] +<# + } +#> + <#=codeStringGenerator.NavigationProperty(navigationProperty)#> +<# + } + } +#> +} +<# + EndNamespace(code); +} + +foreach (var complex in typeMapper.GetItemsToGenerate(itemCollection)) +{ + fileManager.StartNewFile(complex.Name + ".cs"); + BeginNamespace(code); +#> +<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#> +<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> +{ +<# + var complexProperties = typeMapper.GetComplexProperties(complex); + var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex); + + if (propertiesWithDefaultValues.Any() || complexProperties.Any()) + { +#> + public <#=code.Escape(complex)#>() + { +<# + foreach (var edmProperty in propertiesWithDefaultValues) + { +#> + this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>; +<# + } + + foreach (var complexProperty in complexProperties) + { +#> + this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>(); +<# + } +#> + } + +<# + } + + var simpleProperties = typeMapper.GetSimpleProperties(complex); + if (simpleProperties.Any()) + { + foreach(var edmProperty in simpleProperties) + { +#> + <#=codeStringGenerator.Property(edmProperty)#> +<# + } + } + + if (complexProperties.Any()) + { +#> + +<# + foreach(var edmProperty in complexProperties) + { +#> + <#=codeStringGenerator.Property(edmProperty)#> +<# + } + } +#> +} +<# + EndNamespace(code); +} + +foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection)) +{ + fileManager.StartNewFile(enumType.Name + ".cs"); + BeginNamespace(code); +#> +<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#> +<# + if (typeMapper.EnumIsFlags(enumType)) + { +#> +[Flags] +<# + } +#> +<#=codeStringGenerator.EnumOpening(enumType)#> +{ +<# + var foundOne = false; + + foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType)) + { + foundOne = true; +#> + <#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>, +<# + } + + if (foundOne) + { + this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1); + } +#> +} +<# + EndNamespace(code); +} + +fileManager.Process(); + +#> +<#+ + +public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager) +{ + fileManager.StartHeader(); +#> +//------------------------------------------------------------------------------ +// +// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#> +// +// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#> +// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#> +// +//------------------------------------------------------------------------------ +<#=codeStringGenerator.UsingDirectives(inHeader: true)#> +<#+ + fileManager.EndBlock(); +} + +public void BeginNamespace(CodeGenerationTools code) +{ + var codeNamespace = code.VsNamespaceSuggestion(); + if (!String.IsNullOrEmpty(codeNamespace)) + { +#> +namespace <#=code.EscapeNamespace(codeNamespace)#> +{ +<#+ + PushIndent(" "); + } +} + +public void EndNamespace(CodeGenerationTools code) +{ + if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion())) + { + PopIndent(); +#> +} +<#+ + } +} + +public const string TemplateId = "CSharp_DbContext_Types_EF6"; + +public class CodeStringGenerator +{ + private readonly CodeGenerationTools _code; + private readonly TypeMapper _typeMapper; + private readonly MetadataTools _ef; + + public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef) + { + ArgumentNotNull(code, "code"); + ArgumentNotNull(typeMapper, "typeMapper"); + ArgumentNotNull(ef, "ef"); + + _code = code; + _typeMapper = typeMapper; + _ef = ef; + } + + public string Property(EdmProperty edmProperty) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1} {2} {{ {3}get; {4}set; }}", + Accessibility.ForProperty(edmProperty), + _typeMapper.GetTypeName(edmProperty.TypeUsage), + _code.Escape(edmProperty), + _code.SpaceAfter(Accessibility.ForGetter(edmProperty)), + _code.SpaceAfter(Accessibility.ForSetter(edmProperty))); + } + + public string NavigationProperty(NavigationProperty navProp) + { + var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType()); + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1} {2} {{ {3}get; {4}set; }}", + AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)), + navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType, + _code.Escape(navProp), + _code.SpaceAfter(Accessibility.ForGetter(navProp)), + _code.SpaceAfter(Accessibility.ForSetter(navProp))); + } + + public string AccessibilityAndVirtual(string accessibility) + { + return accessibility + (accessibility != "private" ? " virtual" : ""); + } + + public string EntityClassOpening(EntityType entity) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1}partial class {2}{3}", + Accessibility.ForType(entity), + _code.SpaceAfter(_code.AbstractOption(entity)), + _code.Escape(entity), + _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType))); + } + + public string EnumOpening(SimpleType enumType) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} enum {1} : {2}", + Accessibility.ForType(enumType), + _code.Escape(enumType), + _code.Escape(_typeMapper.UnderlyingClrType(enumType))); + } + + public void WriteFunctionParameters(EdmFunction edmFunction, Action writeParameter) + { + var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); + foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable)) + { + var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null"; + var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")"; + var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))"; + writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit); + } + } + + public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace) + { + var parameters = _typeMapper.GetParameters(edmFunction); + + return string.Format( + CultureInfo.InvariantCulture, + "{0} IQueryable<{1}> {2}({3})", + AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), + _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), + _code.Escape(edmFunction), + string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray())); + } + + public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace) + { + var parameters = _typeMapper.GetParameters(edmFunction); + + return string.Format( + CultureInfo.InvariantCulture, + "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});", + _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace), + edmFunction.NamespaceName, + edmFunction.Name, + string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()), + _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()))); + } + + public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) + { + var parameters = _typeMapper.GetParameters(edmFunction); + var returnType = _typeMapper.GetReturnType(edmFunction); + + var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()); + if (includeMergeOption) + { + paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption"; + } + + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1} {2}({3})", + AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)), + returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", + _code.Escape(edmFunction), + paramList); + } + + public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption) + { + var parameters = _typeMapper.GetParameters(edmFunction); + var returnType = _typeMapper.GetReturnType(edmFunction); + + var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())); + if (includeMergeOption) + { + callParams = ", mergeOption" + callParams; + } + + return string.Format( + CultureInfo.InvariantCulture, + "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});", + returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">", + edmFunction.Name, + callParams); + } + + public string DbSet(EntitySet entitySet) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0} virtual DbSet<{1}> {2} {{ get; set; }}", + Accessibility.ForReadOnlyProperty(entitySet), + _typeMapper.GetTypeName(entitySet.ElementType), + _code.Escape(entitySet)); + } + + public string UsingDirectives(bool inHeader, bool includeCollections = true) + { + return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion()) + ? string.Format( + CultureInfo.InvariantCulture, + "{0}using System;{1}" + + "{2}", + inHeader ? Environment.NewLine : "", + includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "", + inHeader ? "" : Environment.NewLine) + : ""; + } +} + +public class TypeMapper +{ + private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName"; + + private readonly System.Collections.IList _errors; + private readonly CodeGenerationTools _code; + private readonly MetadataTools _ef; + + public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors) + { + ArgumentNotNull(code, "code"); + ArgumentNotNull(ef, "ef"); + ArgumentNotNull(errors, "errors"); + + _code = code; + _ef = ef; + _errors = errors; + } + + public static string FixNamespaces(string typeName) + { + return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial."); + } + + public string GetTypeName(TypeUsage typeUsage) + { + return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null); + } + + public string GetTypeName(EdmType edmType) + { + return GetTypeName(edmType, isNullable: null, modelNamespace: null); + } + + public string GetTypeName(TypeUsage typeUsage, string modelNamespace) + { + return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace); + } + + public string GetTypeName(EdmType edmType, string modelNamespace) + { + return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace); + } + + public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace) + { + if (edmType == null) + { + return null; + } + + var collectionType = edmType as CollectionType; + if (collectionType != null) + { + return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace)); + } + + var typeName = _code.Escape(edmType.MetadataProperties + .Where(p => p.Name == ExternalTypeNameAttributeName) + .Select(p => (string)p.Value) + .FirstOrDefault()) + ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ? + _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) : + _code.Escape(edmType)); + + if (edmType is StructuralType) + { + return typeName; + } + + if (edmType is SimpleType) + { + var clrType = UnderlyingClrType(edmType); + if (!IsEnumType(edmType)) + { + typeName = _code.Escape(clrType); + } + + typeName = FixNamespaces(typeName); + + return clrType.IsValueType && isNullable == true ? + String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) : + typeName; + } + + throw new ArgumentException("edmType"); + } + + public Type UnderlyingClrType(EdmType edmType) + { + ArgumentNotNull(edmType, "edmType"); + + var primitiveType = edmType as PrimitiveType; + if (primitiveType != null) + { + return primitiveType.ClrEquivalentType; + } + + if (IsEnumType(edmType)) + { + return GetEnumUnderlyingType(edmType).ClrEquivalentType; + } + + return typeof(object); + } + + public object GetEnumMemberValue(MetadataItem enumMember) + { + ArgumentNotNull(enumMember, "enumMember"); + + var valueProperty = enumMember.GetType().GetProperty("Value"); + return valueProperty == null ? null : valueProperty.GetValue(enumMember, null); + } + + public string GetEnumMemberName(MetadataItem enumMember) + { + ArgumentNotNull(enumMember, "enumMember"); + + var nameProperty = enumMember.GetType().GetProperty("Name"); + return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null); + } + + public System.Collections.IEnumerable GetEnumMembers(EdmType enumType) + { + ArgumentNotNull(enumType, "enumType"); + + var membersProperty = enumType.GetType().GetProperty("Members"); + return membersProperty != null + ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null) + : Enumerable.Empty(); + } + + public bool EnumIsFlags(EdmType enumType) + { + ArgumentNotNull(enumType, "enumType"); + + var isFlagsProperty = enumType.GetType().GetProperty("IsFlags"); + return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null); + } + + public bool IsEnumType(GlobalItem edmType) + { + ArgumentNotNull(edmType, "edmType"); + + return edmType.GetType().Name == "EnumType"; + } + + public PrimitiveType GetEnumUnderlyingType(EdmType enumType) + { + ArgumentNotNull(enumType, "enumType"); + + return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null); + } + + public string CreateLiteral(object value) + { + if (value == null || value.GetType() != typeof(TimeSpan)) + { + return _code.CreateLiteral(value); + } + + return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks); + } + + public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable types, string sourceFile) + { + ArgumentNotNull(types, "types"); + ArgumentNotNull(sourceFile, "sourceFile"); + + var hash = new HashSet(StringComparer.InvariantCultureIgnoreCase); + if (types.Any(item => !hash.Add(item))) + { + _errors.Add( + new CompilerError(sourceFile, -1, -1, "6023", + String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict")))); + return false; + } + return true; + } + + public IEnumerable GetEnumItemsToGenerate(IEnumerable itemCollection) + { + return GetItemsToGenerate(itemCollection) + .Where(e => IsEnumType(e)); + } + + public IEnumerable GetItemsToGenerate(IEnumerable itemCollection) where T: EdmType + { + return itemCollection + .OfType() + .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName)) + .OrderBy(i => i.Name); + } + + public IEnumerable GetAllGlobalItems(IEnumerable itemCollection) + { + return itemCollection + .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i)) + .Select(g => GetGlobalItemName(g)); + } + + public string GetGlobalItemName(GlobalItem item) + { + if (item is EdmType) + { + return ((EdmType)item).Name; + } + else + { + return ((EntityContainer)item).Name; + } + } + + public IEnumerable GetSimpleProperties(EntityType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); + } + + public IEnumerable GetSimpleProperties(ComplexType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); + } + + public IEnumerable GetComplexProperties(EntityType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); + } + + public IEnumerable GetComplexProperties(ComplexType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); + } + + public IEnumerable GetPropertiesWithDefaultValues(EntityType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); + } + + public IEnumerable GetPropertiesWithDefaultValues(ComplexType type) + { + return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); + } + + public IEnumerable GetNavigationProperties(EntityType type) + { + return type.NavigationProperties.Where(np => np.DeclaringType == type); + } + + public IEnumerable GetCollectionNavigationProperties(EntityType type) + { + return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many); + } + + public FunctionParameter GetReturnParameter(EdmFunction edmFunction) + { + ArgumentNotNull(edmFunction, "edmFunction"); + + var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters"); + return returnParamsProperty == null + ? edmFunction.ReturnParameter + : ((IEnumerable)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault(); + } + + public bool IsComposable(EdmFunction edmFunction) + { + ArgumentNotNull(edmFunction, "edmFunction"); + + var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute"); + return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null); + } + + public IEnumerable GetParameters(EdmFunction edmFunction) + { + return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef); + } + + public TypeUsage GetReturnType(EdmFunction edmFunction) + { + var returnParam = GetReturnParameter(edmFunction); + return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage); + } + + public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption) + { + var returnType = GetReturnType(edmFunction); + return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType; + } +} + +public static void ArgumentNotNull(T arg, string name) where T : class +{ + if (arg == null) + { + throw new ArgumentNullException(name); + } +} +#> \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order.cs new file mode 100644 index 0000000..bb028e1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order.cs @@ -0,0 +1,44 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Order + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Order() + { + this.Order_Details = new HashSet(); + } + + public int OrderID { get; set; } + public string CustomerID { get; set; } + public Nullable EmployeeID { get; set; } + public Nullable OrderDate { get; set; } + public Nullable RequiredDate { get; set; } + public Nullable ShippedDate { get; set; } + public Nullable ShipVia { get; set; } + public Nullable Freight { get; set; } + public string ShipName { get; set; } + public string ShipAddress { get; set; } + public string ShipCity { get; set; } + public string ShipRegion { get; set; } + public string ShipPostalCode { get; set; } + public string ShipCountry { get; set; } + + public virtual Customer Customer { get; set; } + public virtual Employee Employee { get; set; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Order_Details { get; set; } + public virtual Shipper Shipper { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order_Detail.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order_Detail.cs new file mode 100644 index 0000000..d3b5345 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Order_Detail.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Order_Detail + { + public int OrderID { get; set; } + public int ProductID { get; set; } + public decimal UnitPrice { get; set; } + public short Quantity { get; set; } + public float Discount { get; set; } + + public virtual Order Order { get; set; } + public virtual Product Product { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Product.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Product.cs new file mode 100644 index 0000000..4564043 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Product.cs @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Product + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Product() + { + this.Order_Details = new HashSet(); + } + + public int ProductID { get; set; } + public string ProductName { get; set; } + public Nullable SupplierID { get; set; } + public Nullable CategoryID { get; set; } + public string QuantityPerUnit { get; set; } + public Nullable UnitPrice { get; set; } + public Nullable UnitsInStock { get; set; } + public Nullable UnitsOnOrder { get; set; } + public Nullable ReorderLevel { get; set; } + public bool Discontinued { get; set; } + + public virtual Category Category { get; set; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Order_Details { get; set; } + public virtual Supplier Supplier { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Region.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Region.cs new file mode 100644 index 0000000..b25a7e1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Region.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Region + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Region() + { + this.Territories = new HashSet(); + } + + public int RegionID { get; set; } + public string RegionDescription { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Territories { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Shipper.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Shipper.cs new file mode 100644 index 0000000..0f713e4 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Shipper.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Shipper + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Shipper() + { + this.Orders = new HashSet(); + } + + public int ShipperID { get; set; } + public string CompanyName { get; set; } + public string Phone { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Orders { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Supplier.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Supplier.cs new file mode 100644 index 0000000..b9f7450 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Supplier.cs @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Supplier + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Supplier() + { + this.Products = new HashSet(); + } + + public int SupplierID { get; set; } + public string CompanyName { get; set; } + public string ContactName { get; set; } + public string ContactTitle { get; set; } + public string Address { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + public string Phone { get; set; } + public string Fax { get; set; } + public string HomePage { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Products { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Territory.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Territory.cs new file mode 100644 index 0000000..0815b92 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Data/Territory.cs @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +// +// This code was generated from a template. +// +// Manual changes to this file may cause unexpected behavior in your application. +// Manual changes to this file will be overwritten if the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Restier.Samples.Northwind.AspNet.Data +{ + using System; + using System.Collections.Generic; + + public partial class Territory + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] + public Territory() + { + this.Employees = new HashSet(); + } + + public string TerritoryID { get; set; } + public string TerritoryDescription { get; set; } + public int RegionID { get; set; } + + public virtual Region Region { get; set; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] + public virtual ICollection Employees { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax new file mode 100644 index 0000000..6068954 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.Restier.Samples.Northwind.AspNet.Global" Language="C#" %> diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax.cs new file mode 100644 index 0000000..db789e4 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Global.asax.cs @@ -0,0 +1,20 @@ +using System.Web.Http; + +namespace Microsoft.Restier.Samples.Northwind.AspNet +{ +#pragma warning disable CA1716 // Identifiers should not match keywords + public class Global : System.Web.HttpApplication +#pragma warning restore CA1716 // Identifiers should not match keywords + { + +#pragma warning disable CA1707 // Identifiers should not contain underscores + protected void Application_Start() +#pragma warning restore CA1707 // Identifiers should not contain underscores + { + //AreaRegistration.RegisterAllAreas(); + //AuthorizationConfig.Configure(); + GlobalConfiguration.Configure(WebApiConfig.Register); + } + + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Microsoft.Restier.Samples.Northwind.AspNet.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Microsoft.Restier.Samples.Northwind.AspNet.csproj new file mode 100644 index 0000000..95387bd --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Microsoft.Restier.Samples.Northwind.AspNet.csproj @@ -0,0 +1,213 @@ + + + Debug + AnyCPU + + + 2.0 + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Microsoft.Restier.Samples.Northwind.AspNet + Microsoft.Restier.Samples.Northwind.AspNet + v4.7.2 + true + + + + + + + + + win + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + true + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + Northwind.mdf + + + TextTemplatingFileGenerator + Northwind.Context.cs + Northwind.edmx + + + TextTemplatingFileGenerator + Northwind.edmx + Northwind.cs + + + + + + + + + Northwind.tt + + + Northwind.tt + + + Northwind.tt + + + Northwind.tt + + + True + True + Northwind.Context.tt + + + True + True + Northwind.tt + + + True + True + Northwind.edmx + + + Northwind.tt + + + Northwind.tt + + + Northwind.tt + + + Northwind.tt + + + Northwind.tt + + + Northwind.tt + + + Northwind.tt + + + Global.asax + + + + + + EntityModelCodeGenerator + Northwind.Designer.cs + + + Northwind.edmx + + + Web.config + + + Web.config + + + + + {a6f9775d-f7e2-424e-8363-79644a73038f} + Microsoft.AspNet.OData + + + {8ecf4e97-1816-44ad-ad63-6acf287ed520} + Microsoft.Restier.AspNet + + + {300b769a-3513-49d0-a035-7db965c8d2a4} + Microsoft.Restier.Core + + + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B} + Microsoft.Restier.EntityFramework + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 60605 + / + http://localhost:60605/ + False + False + + + False + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Properties/AssemblyInfo.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7becbf1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Restier.Samples.Northwind.AspNet")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.Restier.Samples.Northwind.AspNet")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3eab0aed-2be2-4120-b26e-3401b86c4dc2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Debug.config b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Debug.config new file mode 100644 index 0000000..fae9cfe --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Release.config b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Release.config new file mode 100644 index 0000000..da6e960 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config new file mode 100644 index 0000000..a63ac4b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config @@ -0,0 +1,84 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiMetadata.txt b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiMetadata.txt new file mode 100644 index 0000000..8b739fe --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiMetadata.txt @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiSurface.txt b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiSurface.txt new file mode 100644 index 0000000..1bef6bd --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/LibraryApi-ApiSurface.txt @@ -0,0 +1,56 @@ +------------------------------------------------------------ +Function Name | Found? +------------------------------------------------------------ +CanInsertBook | False +CanUpdateBook | False +CanDeleteBook | False +OnInsertingBook | False +OnUpdatingBook | False +OnDeletingBook | False +OnFilterBooks | False +OnInsertedBook | False +OnUpdatedBook | False +OnDeletedBook | False +CanInsertLibraryCard | False +CanUpdateLibraryCard | False +CanDeleteLibraryCard | False +OnInsertingLibraryCard | False +OnUpdatingLibraryCard | False +OnDeletingLibraryCard | False +OnFilterLibraryCards | False +OnInsertedLibraryCard | False +OnUpdatedLibraryCard | False +OnDeletedLibraryCard | False +CanInsertPublisher | False +CanUpdatePublisher | False +CanDeletePublisher | False +OnInsertingPublisher | False +OnUpdatingPublisher | False +OnDeletingPublisher | False +OnFilterPublishers | False +OnInsertedPublisher | False +OnUpdatedPublisher | False +OnDeletedPublisher | False +CanInsertEmployee | False +CanUpdateEmployee | False +CanDeleteEmployee | False +OnInsertingEmployee | False +OnUpdatingEmployee | False +OnDeletingEmployee | False +OnFilterReaders | False +OnInsertedEmployee | False +OnUpdatedEmployee | False +OnDeletedEmployee | False +CanExecutePublishBook | False +OnExecutingPublishBook | False +OnExecutedPublishBook | False +CanExecutePublishBooks | False +OnExecutingPublishBooks | False +OnExecutedPublishBooks | False +CanExecuteSubmitTransaction | False +OnExecutingSubmitTransaction | False +OnExecutedSubmitTransaction | False +CanExecuteCheckoutBook | False +OnExecutingCheckoutBook | False +OnExecutedCheckoutBook | False +------------------------------------------------------------ diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiMetadata.txt b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiMetadata.txt new file mode 100644 index 0000000..8d43575 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiMetadata.txt @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiSurface.txt b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiSurface.txt new file mode 100644 index 0000000..6635bb4 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Baselines/StoreApi-ApiSurface.txt @@ -0,0 +1,40 @@ +------------------------------------------------------------ +Function Name | Found? +------------------------------------------------------------ +CanInsertCustomer | False +CanUpdateCustomer | False +CanDeleteCustomer | False +OnInsertingCustomer | False +OnUpdatingCustomer | False +OnDeletingCustomer | False +OnFilterCustomers | False +OnInsertedCustomer | False +OnUpdatedCustomer | False +OnDeletedCustomer | False +CanInsertProduct | False +CanUpdateProduct | False +CanDeleteProduct | False +OnInsertingProduct | False +OnUpdatingProduct | False +OnDeletingProduct | False +OnFilterProducts | False +OnInsertedProduct | False +OnUpdatedProduct | False +OnDeletedProduct | False +CanInsertStore | False +CanUpdateStore | False +CanDeleteStore | False +OnInsertingStore | False +OnUpdatingStore | False +OnDeletingStore | False +OnFilterStores | False +OnInsertedStore | False +OnUpdatedStore | False +OnDeletedStore | False +CanExecuteGetBestProduct | False +OnExecutingGetBestProduct | False +OnExecutedGetBestProduct | False +CanExecuteRemoveWorstProduct | False +OnExecutingRemoveWorstProduct | False +OnExecutedRemoveWorstProduct | False +------------------------------------------------------------ diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ExceptionHandlerTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ExceptionHandlerTests.cs new file mode 100644 index 0000000..529af6d --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ExceptionHandlerTests.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq.Expressions; +using System.Net; +using System.Net.Http; +using System.Security; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet +{ + + [TestClass] + public class ExceptionHandlerTests : RestierTestBase + { + + [TestMethod] + public async Task ShouldReturn403HandlerThrowsSecurityException() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Products"); + response.IsSuccessStatusCode.Should().BeFalse(); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + #region Test Resources + + private class SecurityExceptionApi : StoreApi + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + return StoreApi.ConfigureApi(apiType, services) + .AddService((sp, next) => new FakeSourcer()); + } + + public SecurityExceptionApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class FakeSourcer : IQueryExpressionSourcer + { + public Expression ReplaceQueryableSource(QueryExpressionContext context, bool embedded) + { + throw new SecurityException(); + } + } + + #endregion + + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ActionTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ActionTests.cs new file mode 100644 index 0000000..507b764 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ActionTests.cs @@ -0,0 +1,70 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using CloudNimble.Breakdance.WebApi; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + + [TestClass] + public class ActionTests : RestierTestBase + { + + [Ignore] + [TestMethod] + public async Task ActionParameters_MissingParameter() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Post, resource: "/CheckoutBook"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeFalse(); + content.Should().Contain("ArgumentNullException"); + } + + [TestMethod] + public async Task ActionParameters_WrongParameterName() + { + var bookPayload = new + { + john = new Book + { + Id = Guid.NewGuid(), + Title = "Constantly Frustrated: the Robert McLaws Story", + } + }; + + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Post, resource: "/CheckoutBook", acceptHeader: WebApiConstants.DefaultAcceptHeader, payload: bookPayload); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeFalse(); + content.Should().Contain("Model state is not valid"); + } + + [TestMethod] + public async Task ActionParameters_HasParameter() + { + var bookPayload = new + { + book = new Book + { + Id = Guid.NewGuid(), + Title = "Constantly Frustrated: the Robert McLaws Story", + } + }; + + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Post, resource: "/CheckoutBook", acceptHeader: WebApiConstants.DefaultAcceptHeader, payload: bookPayload); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + content.Should().Contain("Robert McLaws"); + content.Should().Contain("| Submitted"); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/AuthorizationTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/AuthorizationTests.cs new file mode 100644 index 0000000..3a2e0b6 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/AuthorizationTests.cs @@ -0,0 +1,30 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + [TestClass] + public class AuthorizationTests : RestierTestBase + { + + /// + /// Tests if the query pipeline is correctly returning 403 StatusCodes when returns . + /// + [TestMethod] + public async Task Authorization_FilterReturns403() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Books"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeFalse(); + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ExpandTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ExpandTests.cs new file mode 100644 index 0000000..f52bc13 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/ExpandTests.cs @@ -0,0 +1,31 @@ +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + + /// + /// + /// + [TestClass] + public class ExpandTests : RestierTestBase + { + + [TestMethod] + public async Task CountPlusExpandShouldntThrowExceptions() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Publishers?$expand=Books"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + content.Should().Contain("A Clockwork Orange"); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/FunctionTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/FunctionTests.cs new file mode 100644 index 0000000..0add96b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/FunctionTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using CloudNimble.Breakdance.WebApi; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + + [TestClass] + public class FunctionTests : RestierTestBase + { + + //[Ignore] + [TestMethod] + public async Task FunctionParameters_BooleanParameter () + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/PublishBook(IsActive=true)"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + content.Should().Contain("in the Hat"); + } + + //[Ignore] + [TestMethod] + public async Task FunctionParameters_IntParameter() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/PublishBooks(Count=5)"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + content.Should().Contain("Comes Back"); + } + + //[Ignore] + [TestMethod] + public async Task FunctionParameters_GuidParameter() + { + var testGuid = Guid.NewGuid(); + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: $"/SubmitTransaction(Id={testGuid})"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + content.Should().Contain(testGuid.ToString()); + content.Should().Contain("Shrugged"); + } + + /// + /// Tests if the query pipeline is correctly returning 200 StatusCodes when legitimate queries to a resource simply return no results. + /// + [Ignore] + [TestMethod] + public async Task BoundFunctions_CanHaveFilterPathSegment() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Books/$filter(endswith(Title,'The'))/DiscontinueBooks()"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var results = JsonConvert.DeserializeObject>(content); + results.Should().NotBeNull(); + results.Items.Should().NotBeNullOrEmpty(); + results.Items.Should().HaveCount(2); + results.Items.All(c => c.Title.EndsWith(" | Discontinued")).Should().BeTrue(); + } + + /// + /// Tests if the query pipeline is correctly returning 200 StatusCodes when legitimate queries to a resource simply return no results. + /// + [TestMethod] + public async Task BoundFunctions_Returns200() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Books/DiscontinueBooks()"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var results = JsonConvert.DeserializeObject>(content); + results.Should().NotBeNull(); + results.Items.Should().NotBeNullOrEmpty(); + results.Items.Should().HaveCount(3); + results.Items.All(c => c.Title.EndsWith(" | Discontinued")).Should().BeTrue(); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/MetadataTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/MetadataTests.cs new file mode 100644 index 0000000..672f57f --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/MetadataTests.cs @@ -0,0 +1,115 @@ +using System.IO; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + + [TestClass] + public class MetadataTests : RestierTestBase + { + + #region Private Members + + private const string relativePath = "..//..//..//Baselines//"; + + #endregion + + #region LibraryApi + + [Ignore] + [TestMethod] + public async Task LibraryApi_SaveMetadataDocument() + { + await RestierTestHelpers.WriteCurrentApiMetadata(relativePath); + File.Exists($"{relativePath}{typeof(LibraryApi).Name}-ApiMetadata.txt").Should().BeTrue(); + } + + [Ignore] + [TestMethod] + public async Task LibraryApi_SaveVisibilityMatrix() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + await api.WriteCurrentVisibilityMatrix(relativePath); + + File.Exists($"{relativePath}{api.GetType().Name}-ApiSurface.txt").Should().BeTrue(); + } + + [TestMethod] + public async Task LibraryApi_CompareCurrentApiMetadataToPriorRun() + { + var fileName = $"{relativePath}{typeof(LibraryApi).Name}-ApiMetadata.txt"; + File.Exists(fileName).Should().BeTrue(); + + var oldReport = File.ReadAllText(fileName); + var newReport = await RestierTestHelpers.GetApiMetadata(); + oldReport.Should().BeEquivalentTo(newReport.ToString()); + } + + [TestMethod] + public async Task LibraryApi_CompareCurrentVisibilityMatrixToPriorRun() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + var fileName = $"{relativePath}{api.GetType().Name}-ApiSurface.txt"; + + File.Exists(fileName).Should().BeTrue(); + var oldReport = File.ReadAllText(fileName); + var newReport = await api.GenerateVisibilityMatrix(); + oldReport.Should().BeEquivalentTo(newReport); + } + + #endregion + + #region StoreApi + + [Ignore] + [TestMethod] + public async Task StoreApi_SaveMetadataDocument() + { + await RestierTestHelpers.WriteCurrentApiMetadata(relativePath); + File.Exists($"{relativePath}{typeof(StoreApi).Name}-ApiMetadata.txt").Should().BeTrue(); + } + + [Ignore] + [TestMethod] + public async Task StoreApi_SaveVisibilityMatrix() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + await api.WriteCurrentVisibilityMatrix(relativePath); + + File.Exists($"{relativePath}{api.GetType().Name}-ApiSurface.txt").Should().BeTrue(); + } + + [TestMethod] + public async Task StoreApi_CompareCurrentApiMetadataToPriorRun() + { + var fileName = $"{relativePath}{typeof(StoreApi).Name}-ApiMetadata.txt"; + File.Exists(fileName).Should().BeTrue(); + + var oldReport = File.ReadAllText(fileName); + var newReport = await RestierTestHelpers.GetApiMetadata(); + oldReport.Should().BeEquivalentTo(newReport.ToString()); + } + + [TestMethod] + public async Task StoreApi_CompareCurrentVisibilityMatrixToPriorRun() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + var fileName = $"{relativePath}{api.GetType().Name}-ApiSurface.txt"; + + File.Exists(fileName).Should().BeTrue(); + var oldReport = File.ReadAllText(fileName); + var newReport = await api.GenerateVisibilityMatrix(); + oldReport.Should().BeEquivalentTo(newReport); + } + + #endregion + + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/QueryTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/QueryTests.cs new file mode 100644 index 0000000..2cb040b --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/QueryTests.cs @@ -0,0 +1,57 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + [TestClass] + public class QueryTests : RestierTestBase + { + + /// + /// Tests if the query pipeline is correctly returning 200 StatusCodes when EntitySet tables are just empty. + /// + [TestMethod] + public async Task EmptyEntitySetQueryReturns200Not404() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/LibraryCards"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + /// + /// Tests if the query pipeline is correctly returning 200 StatusCodes when legitimate queries to a resource simply return no results. + /// + [TestMethod] + public async Task EmptyFilterQueryReturns200Not404() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Books?$filter=Title eq 'Sesame Street'"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + /// + /// Tests if the query pipeline is correctly returning 404 StatusCodes when a resource does not exist. + /// + [TestMethod] + public async Task NonExistentEntitySetReturns404() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Subscribers"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeFalse(); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/UpdateTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/UpdateTests.cs new file mode 100644 index 0000000..f8feb5a --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/FeatureTests/UpdateTests.cs @@ -0,0 +1,61 @@ +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using CloudNimble.Breakdance.WebApi; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + + [TestClass] + public class UpdateTests : RestierTestBase + { + + [TestMethod] + public async Task UpdateBookWithPublisher_ShouldReturn400() + { + var bookRequest = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Books?$expand=Publisher&$top=1", acceptHeader: ODataConstants.DefaultAcceptHeader); + bookRequest.IsSuccessStatusCode.Should().BeTrue(); + var (bookList, ErrorContent) = await bookRequest.DeserializeResponseAsync>(); + + bookList.Should().NotBeNull(); + bookList.Items.Should().NotBeNullOrEmpty(); + var book = bookList.Items.First(); + + book.Should().NotBeNull(); + book.Publisher.Should().NotBeNull(); + + book.Title += " Test"; + + var bookEditRequest = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Put, resource: $"/Books({book.Id})", payload: book, acceptHeader: WebApiConstants.DefaultAcceptHeader); + bookEditRequest.IsSuccessStatusCode.Should().BeFalse(); + bookEditRequest.StatusCode.Should().Be(HttpStatusCode.BadRequest); + } + + [TestMethod] + public async Task UpdateBook() + { + var bookRequest = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Books?$top=1", acceptHeader: ODataConstants.DefaultAcceptHeader); + bookRequest.IsSuccessStatusCode.Should().BeTrue(); + var (bookList, ErrorContent) = await bookRequest.DeserializeResponseAsync>(); + + bookList.Should().NotBeNull(); + bookList.Items.Should().NotBeNullOrEmpty(); + var book = bookList.Items.First(); + + book.Should().NotBeNull(); + + book.Title += " Test"; + + var bookEditRequest = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Put, resource: $"/Books({book.Id})", payload: book, acceptHeader: WebApiConstants.DefaultAcceptHeader); + bookEditRequest.IsSuccessStatusCode.Should().BeTrue(); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Microsoft.Restier.Tests.AspNet.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Microsoft.Restier.Tests.AspNet.csproj new file mode 100644 index 0000000..c36eaeb --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Microsoft.Restier.Tests.AspNet.csproj @@ -0,0 +1,21 @@ + + + + net472 + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelBuilderTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelBuilderTests.cs new file mode 100644 index 0000000..0ab14ac --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelBuilderTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Validation; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.Model +{ + + [TestClass] + public class RestierModelBuilderTests : RestierTestBase + { + [TestMethod] + public async Task ComplexTypeShoudWork() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + + model.Validate(out var errors).Should().BeTrue(); + errors.Should().BeEmpty(); + + var address = model.FindDeclaredType("Microsoft.Restier.Tests.Shared.Scenarios.Library.Address") as IEdmComplexType; + address.Should().NotBeNull(); + address.Properties().Should().HaveCount(2); + } + + [TestMethod] + public async Task PrimitiveTypesShouldWork() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + + model.Validate(out var errors).Should().BeTrue(); + errors.Should().BeEmpty(); + + var universe = model.FindDeclaredType("Microsoft.Restier.Tests.Shared.Scenarios.Library.Universe") + as IEdmComplexType; + universe.Should().NotBeNull(); + + var propertyArray = universe.Properties().ToArray(); + var i = 0; + propertyArray[i++].Type.AsPrimitive().IsBinary().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsBoolean().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsByte().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsDate().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsDateTimeOffset().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsDecimal().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsDouble().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsDuration().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsGuid().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsInt16().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsInt32().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsInt64().Should().BeTrue(); + // propertyArray[i++].Type.AsPrimitive().IsSByte().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsSingle().Should().BeTrue(); + // propertyArray[i++].Type.AsPrimitive().IsStream().Should().BeTrue(); + propertyArray[i++].Type.AsPrimitive().IsString().Should().BeTrue(); + // propertyArray[i].Type.AsPrimitive().IsTimeOfDay().Should().BeTrue(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelExtenderTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelExtenderTests.cs new file mode 100644 index 0000000..2d30a61 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/Model/RestierModelExtenderTests.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.AspNet.Model; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.Model +{ + + [TestClass] + public class RestierModelExtenderTests : RestierTestBase + { + + [TestMethod] + public async Task ApiModelBuilder_ShouldProduceEmptyModelForEmptyApi() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.SchemaElements.Should().HaveCount(1); + model.EntityContainer.Elements.Should().BeEmpty(); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldProduceCorrectModelForBasicScenario() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("ApiConfiguration"); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("Invisible"); + model.EntityContainer.FindEntitySet("People").Should().NotBeNull(); + model.EntityContainer.FindSingleton("Me").Should().NotBeNull(); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldProduceCorrectModelForDerivedApi() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("ApiConfiguration"); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("Invisible"); + model.EntityContainer.FindEntitySet("Customers").Should().NotBeNull(); + model.EntityContainer.FindSingleton("Me").Should().NotBeNull(); + model.EntityContainer.FindEntitySet("People").Should().NotBeNull(); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldProduceCorrectModelForOverridingProperty() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("ApiConfiguration"); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("Invisible"); + model.EntityContainer.FindEntitySet("People").Should().NotBeNull(); + model.EntityContainer.FindEntitySet("Customers").EntityType().Name.Should().Be("Customer"); + model.EntityContainer.FindSingleton("Me").EntityType().Name.Should().Be("Customer"); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldProduceCorrectModelForIgnoringInheritedProperty() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("ApiConfiguration"); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("Invisible"); + model.EntityContainer.FindEntitySet("Customers").EntityType().Name.Should().Be("Customer"); + model.EntityContainer.FindSingleton("Me").EntityType().Name.Should().Be("Customer"); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldSkipEntitySetWithUndeclaredType() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.EntityContainer.FindEntitySet("People").EntityType().Name.Should().Be("Person"); + model.EntityContainer.Elements.Select(e => e.Name).Should().NotContain("Orders"); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldSkipExistingEntitySet() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.EntityContainer.FindEntitySet("VipCustomers").EntityType().Name.Should().Be("VipCustomer"); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldCorrectlyAddBindingsForCollectionNavigationProperty() + { + // In this case, only one entity set People has entity type Person. + // Bindings for collection navigation property Customer.Friends should be added. + // Bindings for singleton navigation property Customer.BestFriend should be added. + var model = await RestierTestHelpers.GetTestableModelAsync(); + + var customersBindings = model.EntityContainer.FindEntitySet("Customers").NavigationPropertyBindings.ToArray(); + + var friendsBinding = customersBindings.FirstOrDefault(c => c.NavigationProperty.Name == "Friends"); + friendsBinding.Should().NotBeNull(); + friendsBinding.Target.Name.Should().Be("People"); + + var bestFriendBinding = customersBindings.FirstOrDefault(c => c.NavigationProperty.Name == "BestFriend"); + bestFriendBinding.Should().NotBeNull(); + bestFriendBinding.Target.Name.Should().Be("People"); + + var meBindings = model.EntityContainer.FindSingleton("Me").NavigationPropertyBindings.ToArray(); + + var friendsBinding2 = meBindings.FirstOrDefault(c => c.NavigationProperty.Name == "Friends"); + friendsBinding2.Should().NotBeNull(); + friendsBinding2.Target.Name.Should().Be("People"); + + var bestFriendBinding2 = meBindings.FirstOrDefault(c => c.NavigationProperty.Name == "BestFriend"); + bestFriendBinding2.Should().NotBeNull(); + bestFriendBinding2.Target.Name.Should().Be("People"); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldCorrectlyAddBindingsForSingletonNavigationProperty() + { + // In this case, only one singleton Me has entity type Person. + // Bindings for collection navigation property Customer.Friends should NOT be added. + // Bindings for singleton navigation property Customer.BestFriend should be added. + var model = await RestierTestHelpers.GetTestableModelAsync(); + var binding = model.EntityContainer.FindEntitySet("Customers").NavigationPropertyBindings.Single(); + binding.NavigationProperty.Name.Should().Be("BestFriend"); + binding.Target.Name.Should().Be("Me"); + binding = model.EntityContainer.FindSingleton("Me2").NavigationPropertyBindings.Single(); + binding.NavigationProperty.Name.Should().Be("BestFriend"); + binding.Target.Name.Should().Be("Me"); + } + + [TestMethod] + public async Task ApiModelBuilder_ShouldNotAddAmbiguousNavigationPropertyBindings() + { + // In this case, two entity sets Employees and People have entity type Person. + // Bindings for collection navigation property Customer.Friends should NOT be added. + // Bindings for singleton navigation property Customer.BestFriend should NOT be added. + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.EntityContainer.FindEntitySet("Customers").NavigationPropertyBindings.Should().BeEmpty(); + model.EntityContainer.FindSingleton("Me").NavigationPropertyBindings.Should().BeEmpty(); + } + + } + + #region Test Resources + + public class TestModelBuilder : IModelBuilder + { + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + var model = new EdmModel(); + var ns = typeof(Person).Namespace; + var personType = new EdmEntityType(ns, "Person"); + personType.AddKeys(personType.AddStructuralProperty("PersonId", EdmPrimitiveTypeKind.Int32)); + model.AddElement(personType); + var customerType = new EdmEntityType(ns, "Customer"); + customerType.AddKeys(customerType.AddStructuralProperty("CustomerId", EdmPrimitiveTypeKind.Int32)); + customerType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo + { + Name = "Friends", + Target = personType, + TargetMultiplicity = EdmMultiplicity.Many + }); + customerType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo + { + Name = "BestFriend", + Target = personType, + TargetMultiplicity = EdmMultiplicity.One + }); + model.AddElement(customerType); + var vipCustomerType = new EdmEntityType(ns, "VipCustomer", customerType); + model.AddElement(vipCustomerType); + var container = new EdmEntityContainer(ns, "DefaultContainer"); + container.AddEntitySet("VipCustomers", vipCustomerType); + model.AddElement(container); + return Task.FromResult(model); + } + } + + public class BaseApi : ApiBase + { + public BaseApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class EmptyApi : BaseApi + { + public EmptyApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class Person + { + public int PersonId { get; set; } + } + + public class ApiA : BaseApi + { + [Resource] + public IQueryable People { get; set; } + [Resource] + public Person Me { get; set; } + public IQueryable Invisible { get; set; } + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + services.AddService((sp, next) => new TestModelBuilder()); + return BaseApi.ConfigureApi(apiType, services); + } + + public ApiA(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class ApiB : ApiA + { + [Resource] + public IQueryable Customers { get; set; } + + public ApiB(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class Customer + { + public int CustomerId { get; set; } + public ICollection Friends { get; set; } + public Person BestFriend { get; set; } + } + + public class VipCustomer : Customer + { + } + + public class ApiC : ApiB + { + [Resource] + public new IQueryable Customers { get; set; } + [Resource] + public new Customer Me { get; set; } + + public ApiC(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class ApiD : ApiC + { + public ApiD(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class Order + { + public int OrderId { get; set; } + } + + public class ApiE : BaseApi + { + [Resource] + public IQueryable People { get; set; } + [Resource] + public IQueryable Orders { get; set; } + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + services.AddService((sp, next) => new TestModelBuilder()); + return BaseApi.ConfigureApi(apiType, services); + } + + public ApiE(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class ApiF : BaseApi + { + public IQueryable VipCustomers { get; set; } + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + services.AddService((sp, next) => new TestModelBuilder()); + return BaseApi.ConfigureApi(apiType, services); + } + + public ApiF(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class ApiG : ApiC + { + [Resource] + public IQueryable Employees { get; set; } + + public ApiG(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + public class ApiH : BaseApi + { + [Resource] + public Person Me { get; set; } + [Resource] + public IQueryable Customers { get; set; } + [Resource] + public Customer Me2 { get; set; } + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + services.AddService((sp, next) => new TestModelBuilder()); + return BaseApi.ConfigureApi(apiType, services); + } + + public ApiH(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + #endregion + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ODataControllerFallbackTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ODataControllerFallbackTests.cs new file mode 100644 index 0000000..ec2cfe9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/ODataControllerFallbackTests.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Net.Http; +using System.Threading.Tasks; +using System.Web.Http; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.AspNet.Model; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet +{ + + [TestClass] + public class ODataControllerFallbackTests : RestierTestBase + { + + [TestMethod] + public async Task FallbackApi_EntitySet_ShouldFallBack() + { + // Should fallback to PeopleController. + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/People"); + TestContext.WriteLine(await response.Content.ReadAsStringAsync()); + response.IsSuccessStatusCode.Should().BeTrue(); + ((Person[])((ObjectContent)response.Content).Value).Single().Id.Should().Be(999); + } + + [TestMethod] + public async Task FallbackApi_NavigationProperty_ShouldFallBack() + { + // Should fallback to PeopleController. + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/People(1)/Orders"); + TestContext.WriteLine(await response.Content.ReadAsStringAsync()); + response.IsSuccessStatusCode.Should().BeTrue(); + ((Order[])((ObjectContent)response.Content).Value).Single().Id.Should().Be(123); + } + + [TestMethod] + public async Task FallbackApi_EntitySet_ShouldNotFallBack() + { + // Should be routed to RestierController. + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Orders"); + TestContext.WriteLine(await response.Content.ReadAsStringAsync()); + response.IsSuccessStatusCode.Should().BeTrue(); + (await response.Content.ReadAsStringAsync()).Should().Contain("\"Id\":234"); + } + + [TestMethod] + public async Task FallbackApi_Resource_ShouldNotFallBack() + { + // Should be routed to RestierController. + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/PreservedOrders"); + TestContext.WriteLine(await response.Content.ReadAsStringAsync()); + response.IsSuccessStatusCode.Should().BeTrue(); + (await response.Content.ReadAsStringAsync()).Should().Contain("\"Id\":234"); + } + + } + + #region Test Resources + + internal static class FallbackModel + { + public static EdmModel Model { get; private set; } + + static FallbackModel() + { + var builder = new ODataConventionModelBuilder + { + Namespace = "Microsoft.Restier.Tests.AspNet" + }; + builder.EntitySet("Orders"); + builder.EntitySet("People"); + Model = (EdmModel)builder.GetEdmModel(); + } + } + + internal class FallbackApi : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + services.AddService((sp, next) => new TestModelProducer(FallbackModel.Model)); + services.AddService((sp, next) => new FallbackModelMapper()); + services.AddService((sp, next) => new FallbackQueryExpressionSourcer()); + services = ApiBase.ConfigureApi(apiType, services); + return services; + } + + [Resource] + public IQueryable PreservedOrders => this.GetQueryableSource("Orders").Where(o => o.Id > 123); + + public FallbackApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + } + + public class PeopleController : ODataController + { + public IHttpActionResult Get() + { + var people = new[] + { + new Person {Id = 999} + }; + + return Ok(people); + } + + public IHttpActionResult GetOrders(int key) + { + var orders = new[] + { + new Order {Id = 123}, + }; + + return Ok(orders); + } + } + + internal class Person + { + public int Id { get; set; } + + public IEnumerable Orders { get; set; } + } + + internal class Order + { + public int Id { get; set; } + } + + internal class FallbackQueryExpressionSourcer : IQueryExpressionSourcer + { + public Expression ReplaceQueryableSource(QueryExpressionContext context, bool embedded) + { + var orders = new[] + { + new Order {Id = 234} + }; + + if (!embedded) + { + if (context.VisitedNode.ToString().StartsWith("GetQueryableSource(\"Orders\"")) + { + return Expression.Constant(orders.AsQueryable()); + } + } + + return context.VisitedNode; + } + } + + internal class FallbackModelMapper : IModelMapper + { + public bool TryGetRelevantType(ModelContext context, string name, out Type relevantType) + { + relevantType = name == "Person" ? typeof(Person) : typeof(Order); + + return true; + } + + public bool TryGetRelevantType(ModelContext context, string namespaceName, string name, out Type relevantType) => TryGetRelevantType(context, name, out relevantType); + } + + #endregion + + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RegressionTests/Issue541_CountPlusParametersFails.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RegressionTests/Issue541_CountPlusParametersFails.cs new file mode 100644 index 0000000..7df2658 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RegressionTests/Issue541_CountPlusParametersFails.cs @@ -0,0 +1,74 @@ +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet.RegressionTests +{ + + /// + /// Regression tests for https://github.com/OData/RESTier/issues/541. + /// + public class Issue541_CountPlusParametersFails : RestierTestBase + { + + [TestMethod] + public async Task CountShouldntThrowExceptions() + { + var client = await RestierTestHelpers.GetTestableHttpClient(); + var response = await client.GetStringAsync("http://localhost/api/test/Readers?$count=true"); + response.Should().Contain("\"@odata.count\":2,"); + } + + [TestMethod] + public async Task CountPlusTopShouldntThrowExceptions() + { + var client = await RestierTestHelpers.GetTestableHttpClient(); + var response = await client.GetStringAsync("http://localhost/api/test/Readers?$top=5&$count=true"); + response.Should().Contain("\"@odata.count\":2,"); + } + + [TestMethod] + public async Task CountPlusTopPlusFilterShouldntThrowExceptions() + { + var client = await RestierTestHelpers.GetTestableHttpClient(); + var response = await client.GetStringAsync("http://localhost/api/test/Readers?$top=5&$count=true&$filter=FullName eq 'p1'"); + response.Should().Contain("\"@odata.count\":1,"); + } + + [TestMethod] + public async Task CountPlusTopPlusProjectionShouldntThrowExceptions() + { + var client = await RestierTestHelpers.GetTestableHttpClient(); + var response = await client.ExecuteTestRequest(HttpMethod.Get, resource: "/Readers?$top=5&$count=true&$select=Id,FullName"); + var content = await response.Content.ReadAsStringAsync(); + + content.Should().Contain("\"@odata.count\":2,"); + } + + [TestMethod] + public async Task CountPlusSelectShouldntThrowExceptions() + { + var client = await RestierTestHelpers.GetTestableHttpClient(); + var response = await client.ExecuteTestRequest(HttpMethod.Get, resource: "/Readers?$count=true&$select=Id,FullName"); + var content = await response.Content.ReadAsStringAsync(); + + content.Should().Contain("\"@odata.count\":2,"); + } + + [TestMethod] + public async Task CountPlusExpandShouldntThrowExceptions() + { + var client = await RestierTestHelpers.GetTestableHttpClient(); + var response = await client.ExecuteTestRequest(HttpMethod.Get, resource: "/Publishers?$top=5&$count=true&$expand=Books"); + var content = await response.Content.ReadAsStringAsync(); + + content.Should().Contain("\"@odata.count\":2,"); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierControllerTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierControllerTests.cs new file mode 100644 index 0000000..078ba1f --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierControllerTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using CloudNimble.Breakdance.WebApi; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet +{ + + [TestClass] + public class RestierControllerTests : RestierTestBase + { + + [TestMethod] + public async Task GetTest() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Products(1)"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.IsSuccessStatusCode.Should().BeTrue(); + } + + [TestMethod] + public async Task GetNonExistingEntityTest() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Products(-1)"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [TestMethod] + public async Task PostTest() + { + var payload = new + { + Name = "var1", + Addr = new Address { Zip = 330 } + }; + + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Post, resource: "/Products", payload: payload, acceptHeader: WebApiConstants.DefaultAcceptHeader); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.StatusCode.Should().Be(HttpStatusCode.Created); + } + + [TestMethod] + public async Task FunctionImportNotInModelShouldReturnNotFound() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/GetBestProduct2"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [TestMethod] + public async Task FunctionImportNotInControllerShouldReturnNotImplemented() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/GetBestProduct"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.StatusCode.Should().Be(HttpStatusCode.NotImplemented); + } + + [TestMethod] + public async Task ActionImportNotInModelShouldReturnNotFound() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/RemoveWorstProduct2"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [TestMethod] + public async Task ActionImportNotInControllerShouldReturnNotImplemented() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Post, resource: "/RemoveWorstProduct"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + // TODO: standalone testing shows 501, but here is 500, will figure out detail reason + response.StatusCode.Should().Be(HttpStatusCode.InternalServerError); + } + + [TestMethod] + public async Task GetActionImportShouldReturnNotFound() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/RemoveWorstProduct"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [TestMethod] + public async Task PostFunctionImportShouldReturnNotFound() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Post, resource: "/GetBestProduct"); + var content = await response.Content.ReadAsStringAsync(); + TestContext.WriteLine(content); + // TODO: standalone testing shows 501, but here is 500, will figure out detail reason + response.StatusCode.Should().Be(HttpStatusCode.InternalServerError); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierQueryBuilderTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierQueryBuilderTests.cs new file mode 100644 index 0000000..13daf67 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/RestierQueryBuilderTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.AspNet +{ + + [TestClass] + public class RestierQueryBuilderTests : RestierTestBase + { + + [TestMethod] + public async Task TestInt16AsKey() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Customers(1)"); + response.IsSuccessStatusCode.Should().BeTrue(); + TestContext.WriteLine(await response.Content.ReadAsStringAsync()); + } + + [TestMethod] + public async Task TestInt64AsKey() + { + var response = await RestierTestHelpers.ExecuteTestRequest(HttpMethod.Get, resource: "/Stores(1)"); + response.IsSuccessStatusCode.Should().BeTrue(); + TestContext.WriteLine(await response.Content.ReadAsStringAsync()); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/DisallowEverythingAuthorizer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/DisallowEverythingAuthorizer.cs new file mode 100644 index 0000000..0f194bf --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/DisallowEverythingAuthorizer.cs @@ -0,0 +1,14 @@ +using Microsoft.Restier.Core.Query; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + + /// + /// An implementation that always returns . + /// + internal class DisallowEverythingAuthorizer : IQueryExpressionAuthorizer + { + public bool Authorize(QueryExpressionContext context) => false; + } + +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/UnauthorizedLibraryApi.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/UnauthorizedLibraryApi.cs new file mode 100644 index 0000000..5548eed --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/TestResources/UnauthorizedLibraryApi.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; + +namespace Microsoft.Restier.Tests.AspNet.FeatureTests +{ + + /// + /// A implementation that registers a with the DI container. + /// + internal class UnauthorizedLibraryApi : LibraryApi + { + public UnauthorizedLibraryApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + return LibraryApi.ConfigureApi(apiType, services) + .AddSingleton(); + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/app.config b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/app.config new file mode 100644 index 0000000..87a8acd --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNet/app.config @@ -0,0 +1,16 @@ + + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNetCore/Microsoft.Restier.Tests.AspNetCore.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNetCore/Microsoft.Restier.Tests.AspNetCore.csproj new file mode 100644 index 0000000..318b2f5 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.AspNetCore/Microsoft.Restier.Tests.AspNetCore.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiBaseTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiBaseTests.cs new file mode 100644 index 0000000..34b990e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiBaseTests.cs @@ -0,0 +1,354 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core +{ + + [TestClass] + public class ApiBaseTests : RestierTestBase + { + + [TestMethod] + public async Task DefaultApiBaseCanBeCreatedAndDisposed() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + + Action exceptionTest = () => { api.Dispose(); }; + exceptionTest.Should().NotThrow(); + } + + #region EntitySets + + [TestMethod] + public async Task GetQueryableSource_EntitySet_IsConfiguredCorrectly() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + var source = api.GetQueryableSource("Test", arguments); + + CheckQueryable(source, typeof(string), new List { "Test" }, arguments); + } + + [TestMethod] + public async Task GetQueryableSource_OfT_EntitySet_IsConfiguredCorrectly() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + var source = api.GetQueryableSource("Test", arguments); + + CheckQueryable(source, typeof(string), new List { "Test" }, arguments); + } + + [TestMethod] + public async Task GetQueryableSource_EntitySet_ThrowsIfNotMapped() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + + Action exceptionTest = () => { api.GetQueryableSource("Test", arguments); }; + exceptionTest.Should().Throw(); + } + + [TestMethod] + public async Task GetQueryableSource_OfT_ContainerElementThrowsIfWrongType() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + + Action exceptionTest = () => { api.GetQueryableSource("Test", arguments); }; + exceptionTest.Should().Throw(); + + } + + #endregion + + #region Functions + + [TestMethod] + public async Task GetQueryableSource_ComposableFunction_IsConfiguredCorrectly() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + var source = api.GetQueryableSource("Namespace", "Function", arguments); + + CheckQueryable(source, typeof(DateTime), new List { "Namespace", "Function" }, arguments); + } + + [TestMethod] + public async Task GetQueryableSource_OfT_ComposableFunction_IsConfiguredCorrectly() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + var source = api.GetQueryableSource("Namespace", "Function", arguments); + + CheckQueryable(source, typeof(DateTime), new List { "Namespace", "Function" }, arguments); + } + + [TestMethod] + public async Task GetQueryableSource_ComposableFunction_ThrowsIfNotMapped() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + + Action exceptionTest = () => { api.GetQueryableSource("Namespace", "Function", arguments); }; + exceptionTest.Should().Throw(); + } + + [TestMethod] + public async Task GetQueryableSource_OfT_ComposableFunction_ThrowsIfNotMapped() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + + Action exceptionTest = () => { api.GetQueryableSource("Namespace", "Function", arguments); }; + exceptionTest.Should().Throw(); + } + + [TestMethod] + public async Task GetQueryableSource_ComposableFunction_ThrowsIfWrongType() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var arguments = new object[0]; + + Action exceptionTest = () => { api.GetQueryableSource("Namespace", "Function", arguments); }; + exceptionTest.Should().Throw(); + + } + + #endregion + + #region QueryAsync + + [TestMethod] + public async Task QueryAsync_WithQueryReturnsResults() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + + var request = new QueryRequest(api.GetQueryableSource("Test")); + var result = await api.QueryAsync(request); + var results = result.Results.Cast(); + + results.SequenceEqual(new[] {"Test"}).Should().BeTrue(); + } + + [TestMethod] + public async Task QueryAsync_CorrectlyForwardsCall() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var queryRequest = new QueryRequest(api.GetQueryableSource("Test")); + var queryResult = await api.QueryAsync(queryRequest); + + queryResult.Results.Cast().SequenceEqual(new[] { "Test" }).Should().BeTrue(); + } + + #endregion + + #region SubmitAsync + + [TestMethod] + public async Task SubmitAsync_CorrectlyForwardsCall() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var submitResult = await api.SubmitAsync(); + + submitResult.CompletedChangeSet.Should().NotBeNull(); + } + + #endregion + + #region Exceptions + + [TestMethod] + public async Task GetQueryableSource_CannotEnumerate() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var source = api.GetQueryableSource("Test"); + + Action exceptionTest = () => { source.GetEnumerator(); }; + exceptionTest.Should().Throw(); + + } + + [TestMethod] + public async Task GetQueryableSource_CannotEnumerateIEnumerable() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var source = api.GetQueryableSource("Test"); + + Action exceptionTest = () => { (source as IEnumerable).GetEnumerator(); }; + exceptionTest.Should().Throw(); + } + + [TestMethod] + public async Task GetQueryableSource_ProviderCannotGenericExecute() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var source = api.GetQueryableSource("Test"); + + Action exceptionTest = () => { source.Provider.Execute(null); }; + exceptionTest.Should().Throw(); + + } + + [TestMethod] + public async Task GetQueryableSource_ProviderCannotExecute() + { + var api = await RestierTestHelpers.GetTestableApiInstance() as ApiBase; + var source = api.GetQueryableSource("Test"); + + Action exceptionTest = () => { source.Provider.Execute(null); }; + exceptionTest.Should().Throw(); + } + + #endregion + + #region Helpers + + /// + /// Runs a set of checks against an IQueryable to make sure it has been processed properly. + /// + /// The or to test. + /// The returned by the . + /// A containing the parts of the expression to check for. + /// An array of arguments that the we're testing requires. RWM: In the tests, this is an empty array. Not sure if that is v alid or not. + public void CheckQueryable(IQueryable source, Type elementType, List expressionValues, object[] arguments) + { + source.ElementType.Should().Be(elementType); + (source.Expression is MethodCallExpression).Should().BeTrue(); + var methodCall = source.Expression as MethodCallExpression; + methodCall.Object.Should().BeNull(); + methodCall.Method.DeclaringType.Should().Be(typeof(DataSourceStub)); + methodCall.Method.Name.Should().Be("GetQueryableSource"); + methodCall.Method.GetGenericArguments()[0].Should().Be(elementType); + methodCall.Arguments.Should().HaveCount(expressionValues.Count + 1); + + for (var i = 0; i < expressionValues.Count; i++) + { + (methodCall.Arguments[i] is ConstantExpression).Should().BeTrue(); + (methodCall.Arguments[i] as ConstantExpression).Value.Should().Be(expressionValues[i]); + source.ToString().Should().Be(source.Expression.ToString()); + } + + (methodCall.Arguments[expressionValues.Count] is ConstantExpression).Should().BeTrue(); + (methodCall.Arguments[expressionValues.Count] as ConstantExpression).Value.Should().Be(arguments); + source.ToString().Should().Be(source.Expression.ToString()); + + } + + #endregion + + #region Test Resources + + private class TestModelBuilder : IModelBuilder + { + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + var model = new EdmModel(); + var dummyType = new EdmEntityType("NS", "Dummy"); + model.AddElement(dummyType); + var container = new EdmEntityContainer("NS", "DefaultContainer"); + container.AddEntitySet("Test", dummyType); + model.AddElement(container); + return Task.FromResult((IEdmModel)model); + } + } + + private class TestModelMapper : IModelMapper + { + public bool TryGetRelevantType( + ModelContext context, + string name, out Type relevantType) + { + relevantType = typeof(string); + return true; + } + + public bool TryGetRelevantType( + ModelContext context, + string namespaceName, string name, + out Type relevantType) + { + relevantType = typeof(DateTime); + return true; + } + } + + private class TestQuerySourcer : IQueryExpressionSourcer + { + public Expression ReplaceQueryableSource(QueryExpressionContext context, bool embedded) + { + return Expression.Constant(new[] { "Test" }.AsQueryable()); + } + } + + private class TestChangeSetInitializer : IChangeSetInitializer + { + public Task InitializeAsync(SubmitContext context, CancellationToken cancellationToken) + { + context.ChangeSet = new ChangeSet(); + return Task.FromResult(null); + } + } + + private class TestSubmitExecutor : ISubmitExecutor + { + public Task ExecuteSubmitAsync(SubmitContext context, CancellationToken cancellationToken) + { + return Task.FromResult(new SubmitResult(context.ChangeSet)); + } + } + + private class TestApi : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + var modelBuilder = new TestModelBuilder(); + var modelMapper = new TestModelMapper(); + var querySourcer = new TestQuerySourcer(); + var changeSetPreparer = new TestChangeSetInitializer(); + var submitExecutor = new TestSubmitExecutor(); + + services.AddCoreServices(apiType); + services.AddService((sp, next) => modelBuilder); + services.AddService((sp, next) => modelMapper); + services.AddService((sp, next) => querySourcer); + services.AddService((sp, next) => changeSetPreparer); + services.AddService((sp, next) => submitExecutor); + + return services; + } + + public TestApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiEmpty : ApiBase + { + public TestApiEmpty(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + #endregion + + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiConfigurationTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiConfigurationTests.cs new file mode 100644 index 0000000..bd7341e --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ApiConfigurationTests.cs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core +{ + + [TestClass] + public class ApiConfigurationTests : RestierTestBase + { + [TestMethod] + public async Task ConfigurationRegistersApiServicesCorrectly() + { + var apiA = await RestierTestHelpers.GetTestableApiInstance(); + + apiA.GetApiService().Should().BeNull(); + apiA.GetApiService().Should().BeNull(); + + var apiB = await RestierTestHelpers.GetTestableApiInstance(); + + apiB.GetApiService().Should().BeSameAs(TestApiB.serviceA); + + var serviceBInstance = apiB.GetApiService(); + var serviceBInterface = apiB.GetApiService(); + serviceBInterface.Should().BeSameAs(serviceBInstance); + + var serviceBFirst = serviceBInterface as ServiceB; + serviceBFirst.Should().NotBeNull(); + + serviceBFirst.InnerHandler.Should().BeSameAs(TestApiB.serviceB); + } + + [TestMethod] + public void ServiceChainTest() + { + var container = new RestierContainerBuilder(typeof(TestApiC)); + var provider = container.BuildContainer(); + var api = provider.GetService(); + + var handler = api.GetApiService(); + handler.GetStr().Should().Be("q2Pre_q1Pre_q1Post_q2Post_"); + } + + private class TestApiA : ApiBase + { + public TestApiA(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiB : ApiBase + { + private static ServiceA _serviceA; + + private static ServiceB _serviceB; + + public static ServiceA serviceA + { + get + { + if (_serviceA == null) + { + _serviceA = new ServiceA(); + } + return _serviceA; + } + } + + public static ServiceB serviceB + { + get + { + if (_serviceB == null) + { + _serviceB = new ServiceB(); + } + return _serviceB; + } + } + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => serviceA); + services.AddService((sp, next) => serviceB); + services.AddService(); + services.AddSingleton(new ServiceB()); + return services; + } + + public TestApiB(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + private class TestApiC : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + var q1 = new ServiceB("q1Pre", "q1Post"); + var q2 = new ServiceB("q2Pre", "q2Post"); + services.AddService((sp, next) => q1) + .AddService((sp, next) => + { + q2.InnerHandler = next; + return q2; + }); + + return services; + } + + public TestApiC(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestModelBuilder : IModelBuilder + { + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } + + private interface IServiceA + { + } + + private class ServiceA : IServiceA + { + } + + private interface IServiceB + { + string GetStr(); + } + + private class ServiceB : IServiceB + { + public IServiceB InnerHandler { get; set; } + + private readonly string preStr; + + private readonly string postStr; + + public ServiceB(string preStr = "DefaultPre", string postStr = "DefaultPost") + { + this.preStr = preStr; + this.postStr = postStr; + } + + public string GetStr() + { + var services = new StringBuilder(); + services.Append(this.preStr); + services.Append("_"); + + if (this.InnerHandler != null) + { + services.Append(this.InnerHandler.GetStr()); + } + + services.Append(this.postStr); + services.Append("_"); + return services.ToString(); + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ConventionBasedMethodNameFactoryTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ConventionBasedMethodNameFactoryTests.cs new file mode 100644 index 0000000..8af0ef3 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ConventionBasedMethodNameFactoryTests.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using FluentAssertions; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Operation; +using Microsoft.Restier.Core.Submit; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core +{ + + [TestClass] + public class ConventionBasedMethodNameFactoryTests : RestierTestBase + { + + [TestMethod] + public void ConventionBasedMethodNameFactory_Insert_PreSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Insert, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PreSubmit); + name.Should().Be("OnInsertingString"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Insert_PostSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Insert, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PostSubmit); + name.Should().Be("OnInsertedString"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Update_PreSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Update, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PreSubmit); + name.Should().Be("OnUpdatingString"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Update_PostSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Update, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PostSubmit); + name.Should().Be("OnUpdatedString"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Delete_PreSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Delete, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PreSubmit); + name.Should().Be("OnDeletingString"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Delete_PostSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Delete, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PostSubmit); + name.Should().Be("OnDeletedString"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Filter_PreSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Filter, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PreSubmit); + name.Should().Be(""); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Filter_Submit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Filter, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.Submit); + name.Should().Be("OnFilterTestItems"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_Filter_PostSubmit() + { + var item = new DataModificationItem("TestItems", typeof(string), typeof(string), RestierEntitySetOperation.Filter, null, null, null); + var name = ConventionBasedMethodNameFactory.GetEntitySetMethodName(item, RestierPipelineState.PostSubmit); + name.Should().Be(""); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_ExecuteMethod_Authorize() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + + var context = new OperationContext((string test) => { return null; }, "TestMethod", null, true, null, provider); + var name = ConventionBasedMethodNameFactory.GetFunctionMethodName(context, RestierPipelineState.Authorization, RestierOperationMethod.Execute); + name.Should().Be("CanExecuteTestMethod"); + } + + + [TestMethod] + public void ConventionBasedMethodNameFactory_ExecuteMethod_PreSubmit() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + + var context = new OperationContext((string test) => { return null; }, "TestMethod", null, true, null, provider); + var name = ConventionBasedMethodNameFactory.GetFunctionMethodName(context, RestierPipelineState.PreSubmit, RestierOperationMethod.Execute); + name.Should().Be("OnExecutingTestMethod"); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_ExecuteMethod_Submit() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + + var context = new OperationContext((string test) => { return null; }, "TestMethod", null, true, null, provider); + var name = ConventionBasedMethodNameFactory.GetFunctionMethodName(context, RestierPipelineState.Submit, RestierOperationMethod.Execute); + name.Should().Be(""); + } + + [TestMethod] + public void ConventionBasedMethodNameFactory_ExecuteMethod_PostSubmit() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + + var context = new OperationContext((string test) => { return null; }, "TestMethod", null, true, null, provider); + var name = ConventionBasedMethodNameFactory.GetFunctionMethodName(context, RestierPipelineState.PostSubmit, RestierOperationMethod.Execute); + name.Should().Be("OnExecutedTestMethod"); + } + + + private class TestApi : ApiBase + { + public TestApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + + } + +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/DataSourceStubsTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/DataSourceStubsTests.cs new file mode 100644 index 0000000..c742963 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/DataSourceStubsTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using FluentAssertions; +using Microsoft.Restier.Core; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core +{ + + [TestClass] + public class DataSourceStubsTests : RestierTestBase + { + [TestMethod] + public void SourceOfEntityContainerElementIsNotCallable() + { + Action invalidOperation = () => { DataSourceStub.GetQueryableSource("EntitySet"); }; + invalidOperation.Should().Throw(); + } + + [TestMethod] + public void SourceOfComposableFunctionIsNotCallable() + { + Action invalidOperation = () => { DataSourceStub.GetQueryableSource("Namespace", "Function"); }; + invalidOperation.Should().Throw(); + } + + // TODO enable these when function/action is supported. + //[TestMethod] + //public void ResultsOfEntityContainerElementIsNotCallable() + //{ + // Assert.Throws(() => DataSourceStub.Results("EntitySet")); + //} + + //[TestMethod] + //public void ResultOfEntityContainerElementIsNotCallable() + //{ + // Assert.Throws(() => DataSourceStub.Result("Singleton")); + //} + + //[TestMethod] + //public void ResultsOfComposableFunctionIsNotCallable() + //{ + // Assert.Throws(() => DataSourceStub.Results("Namespace", "Function")); + //} + + //[TestMethod] + //public void ResultOfComposableFunctionIsNotCallable() + //{ + // Assert.Throws(() => DataSourceStub.Result("Namespace", "Function")); + //} + + [TestMethod] + public void ValueIsNotCallable() + { + Action invalidOperation = () => { DataSourceStub.GetPropertyValue(new object(), "Property"); }; + invalidOperation.Should().Throw(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/InvocationContextTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/InvocationContextTests.cs new file mode 100644 index 0000000..5315516 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/InvocationContextTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core +{ + + [TestClass] + public class InvocationContextTests : RestierTestBase + { + + [TestMethod] + public void InvocationContext_IsConfiguredCorrectly() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + var api = provider.GetService(); + var context = new InvocationContext(provider); + context.GetApiService().Should().BeSameAs(api); + } + + [TestMethod] + public void InvocationContext_GetsApiServicesCorrectly() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + var context = new InvocationContext(provider); + context.GetApiService().Should().BeSameAs(TestApi.ApiService); + } + + #region Test Resources + + private class TestApi : ApiBase + { + private static ApiServiceA _service; + + public static ApiServiceA ApiService + { + get + { + if (_service == null) + { + _service = new ApiServiceA(); + } + return _service; + } + } + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => ApiService); + + return services; + } + + public TestApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private interface IServiceA + { + } + + private class ApiServiceA : IServiceA + { + } + + #endregion + + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Microsoft.Restier.Tests.Core.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Microsoft.Restier.Tests.Core.csproj new file mode 100644 index 0000000..768e86c --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Microsoft.Restier.Tests.Core.csproj @@ -0,0 +1,21 @@ + + + + net472 + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Model/DefaultModelHandlerTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Model/DefaultModelHandlerTests.cs new file mode 100644 index 0000000..ca79b25 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/Model/DefaultModelHandlerTests.cs @@ -0,0 +1,245 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core.Model +{ + + [TestClass] + public class DefaultModelHandlerTests : RestierTestBase + { + + [TestMethod] + public async Task GetModelUsingDefaultModelHandler() + { + var model = await RestierTestHelpers.GetTestableModelAsync(); + model.SchemaElements.Should().HaveCount(4); + model.SchemaElements.SingleOrDefault(e => e.Name == "TestName").Should().NotBeNull(); + model.SchemaElements.SingleOrDefault(e => e.Name == "TestName2").Should().NotBeNull(); + model.SchemaElements.SingleOrDefault(e => e.Name == "TestName3").Should().NotBeNull(); + model.EntityContainer.Should().NotBeNull(); + model.EntityContainer.Elements.SingleOrDefault(e => e.Name == "TestEntitySet").Should().NotBeNull(); + model.EntityContainer.Elements.SingleOrDefault(e => e.Name == "TestEntitySet2").Should().NotBeNull(); + model.EntityContainer.Elements.SingleOrDefault(e => e.Name == "TestEntitySet3").Should().NotBeNull(); + } + + [TestMethod] + public async Task ModelBuilderShouldBeCalledOnlyOnceIfSucceeded() + { + using (var wait = new ManualResetEventSlim(false)) + { + for (var i = 0; i < 2; i++) + { + var container = new RestierContainerBuilder(typeof(TestApiB)); + var provider = container.BuildContainer(); + var tasks = PrepareThreads(50, provider, wait); + wait.Set(); + + var models = await Task.WhenAll(tasks); + models.All(e => object.ReferenceEquals(e, models[42])).Should().BeTrue(); + } + } + } + + [TestMethod] + public async Task GetModelAsyncRetriableAfterFailure() + { + using (var wait = new ManualResetEventSlim(false)) + { + var container = new RestierContainerBuilder(typeof(TestApiC)); + var provider = container.BuildContainer(); + + var tasks = PrepareThreads(6, provider, wait); + wait.Set(); + + await Task.WhenAll(tasks).ContinueWith(t => + { + t.IsFaulted.Should().BeTrue(); + tasks.All(e => e.IsFaulted).Should().BeTrue() ; + }); + + tasks = PrepareThreads(150, provider, wait); + + var models = await Task.WhenAll(tasks); + models.All(e => ReferenceEquals(e, models[42])).Should().BeTrue(); + } + } + + #region Test Resources + + private class TestApiA : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => new TestModelProducer()); + services.AddService((sp, next) => new TestModelExtender(2) + { + InnerHandler = next, + }); + services.AddService((sp, next) => new TestModelExtender(3) + { + InnerHandler = next, + }); + + return services; + } + + public TestApiA(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiB : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + var service = new TestSingleCallModelBuilder(); + services.AddService((sp, next) => service); + return services; + } + + public TestApiB(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiC : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + var service = new TestRetryModelBuilder(); + services.AddService((sp, next) => service); + + return services; + } + + public TestApiC(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestModelProducer : IModelBuilder + { + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + var model = new EdmModel(); + var entityType = new EdmEntityType( + "TestNamespace", "TestName"); + var entityContainer = new EdmEntityContainer( + "TestNamespace", "Entities"); + entityContainer.AddEntitySet("TestEntitySet", entityType); + model.AddElement(entityType); + model.AddElement(entityContainer); + + return Task.FromResult(model); + } + } + + private class TestModelExtender : IModelBuilder + { + private readonly int _index; + + public TestModelExtender(int index) => _index = index; + + public IModelBuilder InnerHandler { get; set; } + + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + IEdmModel innerModel = null; + if (InnerHandler != null) + { + innerModel = await InnerHandler.GetModelAsync(context, cancellationToken); + } + + var entityType = new EdmEntityType("TestNamespace", "TestName" + _index); + + var model = innerModel as EdmModel; + model.Should().NotBeNull(); + + model.AddElement(entityType); + (model.EntityContainer as EdmEntityContainer).AddEntitySet("TestEntitySet" + _index, entityType); + + return model; + } + } + + private class TestSingleCallModelBuilder : IModelBuilder + { + public int CalledCount; + + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + await Task.Delay(30); + + Interlocked.Increment(ref CalledCount); + return new EdmModel(); + } + } + + private static Task[] PrepareThreads(int count, IServiceProvider provider, ManualResetEventSlim wait) + { + var tasks = new Task[count]; + var result = Parallel.For(0, count, (inx, state) => + { + var source = new TaskCompletionSource(); + new Thread(() => + { + // To make threads better aligned. + wait.Wait(); + + var scopedProvider = provider.GetRequiredService().CreateScope().ServiceProvider; + var api = scopedProvider.GetService(); + try + { + var model = api.GetModelAsync().Result; + source.SetResult(model); + } + catch (Exception e) + { + source.SetException(e); + } + }).Start(); + tasks[inx] = source.Task; + }); + + result.IsCompleted.Should().BeTrue(); + return tasks; + } + + private class TestRetryModelBuilder : IModelBuilder + { + public int CalledCount; + + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + if (CalledCount++ == 0) + { + await Task.Delay(100); + throw new Exception("Deliberate failure"); + } + + return new EdmModel(); + } + } + + #endregion + + + + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/PropertyBagTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/PropertyBagTests.cs new file mode 100644 index 0000000..905b1f1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/PropertyBagTests.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core +{ + + [TestClass] + public class PropertyBagTests : RestierTestBase + { + [TestMethod] + public void PropertyBag_ManipulatesPropertiesCorrectly() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + var api = provider.GetService(); + + api.HasProperty("Test").Should().BeFalse(); + api.GetProperty("Test").Should().BeNull(); + api.GetProperty("Test").Should().BeNull(); + api.GetProperty("Test").Should().Be(default); + + api.SetProperty("Test", "Test"); + api.HasProperty("Test").Should().BeTrue(); + api.GetProperty("Test").Should().Be("Test"); + api.GetProperty("Test").Should().Be("Test"); + + api.RemoveProperty("Test"); + api.HasProperty("Test").Should().BeFalse(); + api.GetProperty("Test").Should().BeNull(); + api.GetProperty("Test").Should().BeNull(); + api.GetProperty("Test").Should().Be(default); + } + + [TestMethod] + public async Task PropertyBag_InstancesDoNotConflict() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + + api.SetProperty("Test", 2); + api.GetProperty("Test").Should().Be(2); + } + + [TestMethod] + public void PropertyBagsAreDisposedCorrectly() + { + var container = new RestierContainerBuilder(typeof(TestApi)); + var provider = container.BuildContainer(); + var scope = provider.GetRequiredService().CreateScope(); + var scopedProvider = scope.ServiceProvider; + var api = scopedProvider.GetService(); + + api.GetApiService().Should().NotBeNull(); + MyPropertyBag.InstanceCount.Should().Be(1); + + var scopedProvider2 = provider.GetRequiredService().CreateScope().ServiceProvider; + var api2 = scopedProvider2.GetService(); + + api2.GetApiService().Should().NotBeNull(); + MyPropertyBag.InstanceCount.Should().Be(2); + + scope.Dispose(); + + MyPropertyBag.InstanceCount.Should().Be(1); + } + + /// + /// has the same lifetime as PropertyBag thus + /// use this class to test the lifetime of PropertyBag in ApiConfiguration + /// and ApiBase. + /// + private class MyPropertyBag : IDisposable + { + public MyPropertyBag() + { + ++InstanceCount; + } + + public static int InstanceCount { get; set; } + + public void Dispose() + { + --InstanceCount; + } + } + + private class TestApi : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + return ApiBase.ConfigureApi(apiType, services).AddScoped(); + } + + public TestApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ServiceConfigurationTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ServiceConfigurationTests.cs new file mode 100644 index 0000000..b8bfc59 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Core/ServiceConfigurationTests.cs @@ -0,0 +1,485 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; +using Microsoft.Restier.Tests.Shared; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Core +{ + + [TestClass] + public class ServiceConfigurationTests : RestierTestBase + { + + [TestMethod] + public async Task ContributorsAreCalledCorrectly() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + var value = api.GetApiService().Call(); + value.Should().Be("03210"); + } + + [TestMethod] + public async Task NextInjectedViaProperty() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + var value = api.GetApiService().Call(); + value.Should().Be("01"); + } + + [TestMethod] + public async Task ContextApiScopeWorksCorrectly() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + var service1 = api.GetApiService(); + + var api2 = await RestierTestHelpers.GetTestableApiInstance(); + var service2 = api2.GetApiService(); + + service1.Should().NotBe(service2); + + var api3 = await RestierTestHelpers.GetTestableApiInstance(); + var service3 = api3.GetApiService(); + + service3.Should().NotBe(service2); + } + + //RWM: I don't think this actually tests anything of value. + [TestMethod] + public async Task NothingInjectedStillWorks() + { + // Outmost service does not call inner service + var api = await RestierTestHelpers.GetTestableApiInstance(); + + var value = api.GetApiService().Call(); + value.Should().Be("42"); + + // Test expression compilation. (RWM: I don't think this works the way they thought it did.) + value = api.GetApiService().Call(); + value.Should().Be("42"); + value = api.GetApiService().Call(); + value.Should().Be("42"); + } + + [TestMethod] + public void ServiceInjectedViaProperty() + { + var container = new RestierContainerBuilder(typeof(TestApiE)); + var provider = container.BuildContainer(); + var api = provider.GetService(); + + var expected = "Text42"; + var value = api.GetApiService().Call(); + value.Should().Be(expected); + + value = api.GetApiService().Call(); + value.Should().Be(expected); + + value = api.GetApiService().Call(); + value.Should().Be(expected); + + api.GetApiService().Should().NotBe(api.GetApiService()); + } + + [TestMethod] + public void DefaultValueInConstructorUsedIfNoService() + { + var container = new RestierContainerBuilder(typeof(TestApiF)); + var provider = container.BuildContainer(); + var api = provider.GetService(); + + var value = api.GetApiService().Call(); + value.Should().Be("42"); + + value = api.GetApiService().Call(); + value.Should().Be("42"); + value = api.GetApiService().Call(); + value.Should().Be("42"); + } + + + [TestMethod] + public void MultiInjectionViaConstructor() + { + var container = new RestierContainerBuilder(typeof(TestApiG)); + var provider = container.BuildContainer(); + var api = provider.GetService(); + + var value = api.GetApiService().Call(); + value.Should().Be("0122"); + + // Test expression compilation + value = api.GetApiService().Call(); + value.Should().Be("0122"); + value = api.GetApiService().Call(); + value.Should().Be("0122"); + } + + [TestMethod] + public void NextInjectedWithInheritedField() + { + var container = new RestierContainerBuilder(typeof(TestApiI)); + var provider = container.BuildContainer(); + var api = provider.GetService(); + + var value = api.GetApiService().Call(); + value.Should().Be("4200"); + + // Test expression compilation + value = api.GetApiService().Call(); + value.Should().Be("4200"); + value = api.GetApiService().Call(); + value.Should().Be("4200"); + } + + #region Exceptions + + [TestMethod] + public async Task ThrowOnNoServiceFound() + { + var api = await RestierTestHelpers.GetTestableApiInstance(); + + Action exceptionTest = () => { api.GetApiService(); }; + exceptionTest.Should().Throw(); + } + + #endregion + + #region Test Resources + + private class TestApiA : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + var i = 0; + services.AddService((sp, next) => new SomeService + { + Inner = next, + Value = i++ + }) + .AddService((sp, next) => new SomeService + { + Inner = next, + Value = i++ + }) + .AddService((sp, next) => new SomeService + { + Inner = next, + Value = i++ + }) + .AddService((sp, next) => new SomeService + { + Inner = next, + Value = i++ + }) + .AddService(); + return services; + } + + public TestApiA(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiB : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => new SomeService + { + Inner = next, + Value = 1 + }) + .AddService() + .MakeTransient(); + return services; + } + + public TestApiB(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiC : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.MakeScoped() + .AddService((sp, next) => new SomeService()); + return services; + } + + public TestApiC(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiD : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => new SomeService + { + Inner = next, + Value = 1 + }) + .AddService() + .MakeTransient(); + return services; + } + + public TestApiD(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiE : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + var first = new SomeService + { + Value = 42 + }; + services.MakeTransient() + .AddService((sp, next) => first) + .AddService() + .AddSingleton("Text"); + return services; + } + + public TestApiE(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiF : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => new SomeService + { + Value = 2 + }) + .MakeTransient() + .AddService(); + + return services; + } + + public TestApiF(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiG : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => new SomeService + { + Value = 1 + }) + .AddService() + .AddSingleton(new SomeService + { + Value = 2 + }) + .MakeTransient() + .AddService((sp, next) => { return "0"; }) + .MakeTransient(); + return services; + } + + public TestApiG(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiH : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => new SomeService + { + Value = 1 + }) + .AddService() + .MakeTransient() + .AddService((sp, next) => { return "0"; }) + .MakeTransient(); + return services; + } + + public TestApiH(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + private class TestApiI : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + ApiBase.ConfigureApi(apiType, services); + services.MakeTransient() + .AddService((sp, next) => new SomeService + { + Value = 2 + }) + .AddSingleton(new SomeService + { + Value = 0 + }) + .AddService(); + + return services; + } + + public TestApiI(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + + private interface ISomeService + { + string Call(); + } + + private class SomeService : ISomeService + { + public int Value { get; set; } + + public ISomeService Inner { get; set; } + + public string Call() + { + if (Inner == null) + { + return Value.ToString(); + } + + return Value + Inner.Call(); + } + } + + private class SomeService2 : ISomeService + { + private string _value; + // The string value will be retrieved via api.GetApiService() + public SomeService2(string value = "4") + { + _value = value; + } + + public string Value + { + get + { + return _value; + } + } + + protected ISomeService Inner { get; set; } + + public string Call() + { + if (Inner == null) + { + return Value; + } + + return Value + Inner.Call(); + } + } + + private class SomeService3 : ISomeService + { + private string _value; + private SomeService _service1; + private SomeService _service2; + + protected ISomeService next = null; + + public SomeService3(string value, SomeService dep1, SomeService dep2) + { + _value = value; + _service1 = dep1; + _service2 = dep2; + } + + public SomeService3(SomeService dep1) + { + _value = "4"; + _service1 = _service2 = dep1; + } + + public string Value + { + get + { + return _value; + } + } + + public SomeService Param2 + { + get + { + return _service1; + } + } + + public SomeService Param3 + { + get + { + return _service2; + } + } + + public string Call() + { + return Value + + (next == null ? string.Empty : next.Call()) + + Param2.Call() + + Param3.Call(); + } + } + + private class SomeService4 : SomeService3 + { + public SomeService4(SomeService dep1) + : base(dep1) + { + } + } + + private class SomeServiceNoChain : ISomeService + { + public string Call() + { + return "42"; + } + } + + #endregion + + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/App.config b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/App.config new file mode 100644 index 0000000..2de3c32 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/App.config @@ -0,0 +1,16 @@ + + + + +
+ + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/ChangeSetPreparerTests.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/ChangeSetPreparerTests.cs new file mode 100644 index 0000000..16150d4 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/ChangeSetPreparerTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using CloudNimble.Breakdance.Restier; +using FluentAssertions; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Submit; +using Microsoft.Restier.Tests.Shared; +using Microsoft.Restier.Tests.Shared.Scenarios.Library; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.EntityFramework.Tests +{ + + [TestClass] + public class ChangeSetPreparerTests : RestierTestBase + { + + [TestMethod] + public async Task ComplexTypeUpdate() + { + // Arrange + var provider = await RestierTestHelpers.GetTestableInjectionContainer(); + var api = provider.GetTestableApiInstance(); + + var item = new DataModificationItem( + "Readers", + typeof(Employee), + null, + RestierEntitySetOperation.Update, + new Dictionary { { "Id", new Guid("53162782-EA1B-4712-AF26-8AA1D2AC0461") } }, + new Dictionary(), + new Dictionary { { "Addr", new Dictionary { { "Zip", "332" } } } }); + var changeSet = new ChangeSet(new[] { item }); + var sc = new SubmitContext(provider, changeSet); + + // Act + var changeSetPreparer = api.GetApiService(); + await changeSetPreparer.InitializeAsync(sc, CancellationToken.None).ConfigureAwait(false); + var person = item.Resource as Employee; + + // Assert + person.Should().NotBeNull(); + person.Addr.Zip.Should().Be("332"); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/Microsoft.Restier.Tests.EntityFramework.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/Microsoft.Restier.Tests.EntityFramework.csproj new file mode 100644 index 0000000..b858aed --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.EntityFramework/Microsoft.Restier.Tests.EntityFramework.csproj @@ -0,0 +1,28 @@ + + + + net472 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Microsoft.Restier.Tests.Shared.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Microsoft.Restier.Tests.Shared.csproj new file mode 100644 index 0000000..19fe5c5 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Microsoft.Restier.Tests.Shared.csproj @@ -0,0 +1,32 @@ + + + + net472 + + + + + + + + + + + + <_Parameter1>Microsoft.Restier.Tests.AspNet + + + <_Parameter1>Microsoft.Restier.Tests.EntityFramework + + + <_Parameter1>Microsoft.Restier.Tests.Core + + + + + + + + + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/RestierTestBase.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/RestierTestBase.cs new file mode 100644 index 0000000..353d38d --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/RestierTestBase.cs @@ -0,0 +1,19 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Restier.Tests.Shared +{ + + /// + /// + /// + public class RestierTestBase + { + + /// + /// + /// + public TestContext TestContext { get; set; } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Address.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Address.cs new file mode 100644 index 0000000..dda1ef9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Address.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.ComponentModel.DataAnnotations.Schema; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + + /// + /// + /// + [ComplexType] + public class Address + { + + /// + /// + /// + public string Street { get; set; } + + /// + /// + /// + public string Zip { get; set; } + + } + +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Book.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Book.cs new file mode 100644 index 0000000..78d7468 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Book.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + + /// + /// + /// + public class Book + { + + /// + /// + /// + public Guid Id { get; set; } + + /// + /// + /// + public string Title { get; set; } + + /// + /// + /// + public Publisher Publisher { get; set; } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Employee.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Employee.cs new file mode 100644 index 0000000..c2f73d0 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Employee.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + + /// + /// + /// + public class Employee + { + + /// + /// + /// + public Guid Id { get; set; } + + /// + /// + /// + public string FullName { get; set; } + + /// + /// + /// + public Address Addr { get; set; } + + /// + /// + /// + public Universe Universe { get; set; } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryApi.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryApi.cs new file mode 100644 index 0000000..30353b9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryApi.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.Restier.AspNet.Model; +using Microsoft.Restier.EntityFramework; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + public class LibraryApi : EntityFrameworkApi + { + + public LibraryApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + + [Operation(HasSideEffects = false)] + public Book PublishBook(bool IsActive) + { + Console.WriteLine($"IsActive = {IsActive}"); + return new Book + { + Id = Guid.NewGuid(), + Title = "The Cat in the Hat" + }; + } + + [Operation(HasSideEffects = false)] + public Book PublishBooks(int Count) + { + Console.WriteLine($"Count = {Count}"); + return new Book + { + Id = Guid.NewGuid(), + Title = "The Cat in the Hat Comes Back" + }; + } + + [Operation(HasSideEffects = false)] + public Book SubmitTransaction(Guid Id) + { + Console.WriteLine($"Id = {Id}"); + return new Book + { + Id = Id, + Title = "Atlas Shrugged" + }; + } + + [Operation(HasSideEffects = true, EntitySet = "Books")] + public Book CheckoutBook(Book book) + { + if (book == null) + { + throw new ArgumentNullException(nameof(book)); + } + Console.WriteLine($"Id = {book.Id}"); + book.Title += " | Submitted"; + return book; + } + + [Operation(HasSideEffects = false, IsBound = true, IsComposable = true)] + public IQueryable DiscontinueBooks(IQueryable books) + { + if (books == null) + { + throw new ArgumentNullException(nameof(books)); + } + books.ToList().ForEach(c => + { + Console.WriteLine($"Id = {c.Id}"); + c.Title += " | Discontinued"; + }); + return books; + } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryCard.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryCard.cs new file mode 100644 index 0000000..0cb8361 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryCard.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + + /// + /// An object in the model that is supposed to remain empty for unit tests. + /// + public class LibraryCard + { + + public Guid Id { get; set; } + + public DateTimeOffset DateRegistered { get; set; } + + } + +} \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryContext.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryContext.cs new file mode 100644 index 0000000..51e0b36 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/LibraryContext.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Data.Entity; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + public class LibraryContext : DbContext + { + public LibraryContext() + : base("LibraryContext") => Database.SetInitializer(new TestInitializer()); + + public IDbSet Books { get; set; } + + public IDbSet LibraryCards { get; set; } + + public IDbSet Publishers { get; set; } + + public IDbSet Readers { get; set; } + } + + public class TestInitializer : DropCreateDatabaseAlways + { + protected override void Seed(LibraryContext context) + { + context.Readers.Add(new Employee + { + Addr = new Address { Street = "street1" }, + FullName = "p1", + Id = new Guid("53162782-EA1B-4712-AF26-8AA1D2AC0461"), + Universe = new Universe + { + BinaryProperty = new byte[] { 0x1, 0x2 }, + BooleanProperty = true, + ByteProperty = 0x3, + DateProperty = Date.Now, + DateTimeOffsetProperty = DateTimeOffset.Now, + DecimalProperty = decimal.One, + DoubleProperty = 123.45, + DurationProperty = TimeSpan.FromHours(1.0), + GuidProperty = new Guid("53162782-EA1B-4712-AF26-8AA1D2AC0461"), + Int16Property = 12345, + Int32Property = 1234567, + Int64Property = 9876543210, + // SByteProperty = -1, + SingleProperty = (float)123.45, + // StreamProperty = new FileStream("temp.txt", FileMode.OpenOrCreate), + StringProperty = "Hello", + TimeOfDayProperty = TimeOfDay.Now + } + }); + context.Readers.Add(new Employee + { + Addr = new Address { Street = "street2" }, + FullName = "p2", + Id = new Guid("8B04EA8B-37B1-4211-81CB-6196C9A1FE36"), + Universe = new Universe + { + BinaryProperty = new byte[] { 0x1, 0x2 }, + BooleanProperty = true, + ByteProperty = 0x3, + DateProperty = Date.Now, + DateTimeOffsetProperty = DateTimeOffset.Now, + DecimalProperty = decimal.One, + DoubleProperty = 123.45, + DurationProperty = TimeSpan.FromHours(1.0), + GuidProperty = new Guid("8B04EA8B-37B1-4211-81CB-6196C9A1FE36"), + Int16Property = 12345, + Int32Property = 1234567, + Int64Property = 9876543210, + // SByteProperty = -1, + SingleProperty = (float)123.45, + // StreamProperty = new FileStream("temp.txt", FileMode.OpenOrCreate), + StringProperty = "Hello", + TimeOfDayProperty = TimeOfDay.Now + } + }); + + context.Publishers.Add(new Publisher + { + Id = "Publisher1", + Addr = new Address + { + Street = "123 Sesame St.", + Zip = "00010" + }, + Books = new List + { + new Book + { + Id = Guid.NewGuid(), + Title = "A Clockwork Orange" + }, + new Book + { + Id = Guid.NewGuid(), + Title = "Jungle Book, The" + } + } + }); + + context.Publishers.Add(new Publisher + { + Id = "Publisher2", + Addr = new Address + { + Street = "234 Anystreet St.", + Zip = "10010" + }, + Books = new List + { + new Book + { + Id = Guid.NewGuid(), + Title = "Color Purple, The" + } + } + }); + + + context.SaveChanges(); + } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Publisher.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Publisher.cs new file mode 100644 index 0000000..86bbde9 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Publisher.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + + /// + /// + /// + public class Publisher + { + + public string Id { get; set; } + + public Address Addr { get; set; } + + public virtual ICollection Books { get; set; } + + } + +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Universe.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Universe.cs new file mode 100644 index 0000000..7cea6a1 --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Library/Universe.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.OData.Edm; + +namespace Microsoft.Restier.Tests.Shared.Scenarios.Library +{ + [ComplexType] + public class Universe + { + public byte[] BinaryProperty { get; set; } + + public bool BooleanProperty { get; set; } + + public byte ByteProperty { get; set; } + + public Date DateProperty { get; set; } + + public DateTimeOffset DateTimeOffsetProperty { get; set; } + + public decimal DecimalProperty { get; set; } + + public double DoubleProperty { get; set; } + + public TimeSpan DurationProperty { get; set; } + + public Guid GuidProperty { get; set; } + + public short Int16Property { get; set; } + + public int Int32Property { get; set; } + + public long Int64Property { get; set; } + + // public sbyte SByteProperty { get; set; } + + public float SingleProperty { get; set; } + + // public FileStream StreamProperty { get; set; } + + public string StringProperty { get; set; } + + public TimeOfDay TimeOfDayProperty { get; set; } + } +} diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Store/StoreApi.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Store/StoreApi.cs new file mode 100644 index 0000000..c4f00ba --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Tests.Shared/Scenarios/Store/StoreApi.cs @@ -0,0 +1,205 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; + +namespace Microsoft.Restier.Tests.AspNet +{ + internal static class StoreModel + { + public static EdmModel Model { get; private set; } + + public static IEdmEntityType Product { get; private set; } + + static StoreModel() + { + var builder = new ODataConventionModelBuilder + { + Namespace = "Microsoft.Restier.Tests.AspNet" + }; + builder.EntitySet("Products"); + builder.EntitySet("Customers"); + builder.EntitySet("Stores"); + builder.Function("GetBestProduct").ReturnsFromEntitySet("Products"); + builder.Action("RemoveWorstProduct").ReturnsFromEntitySet("Products"); + Model = (EdmModel)builder.GetEdmModel(); + Product = (IEdmEntityType)Model.FindType("Microsoft.Restier.Tests.AspNet.Product"); + } + } + + internal class StoreApi : ApiBase + { + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + services = ApiBase.ConfigureApi(apiType, services); + services.AddService((sp, next) => new TestModelProducer(StoreModel.Model)); + services.AddService((sp, next) => new TestModelMapper()); + services.AddService((sp, next) => new TestQueryExpressionSourcer()); + services.AddService((sp, next) => new TestChangeSetInitializer()); + services.AddService((sp, next) => new TestSubmitExecutor()); + return services; + } + + public StoreApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + + class Product + { + public int Id { get; set; } + + public string Name { get; set; } + + [Required] + public Address Addr { get; set; } + + public Address Addr2 { get; set; } + + public Address Addr3 { get; set; } + } + + class Customer + { + public short Id { get; set; } + } + + class Store + { + public long Id { get; set; } + } + + class Address + { + public int Zip { get; set; } + } + + class TestModelMapper : IModelMapper + { + public bool TryGetRelevantType(ModelContext context, string name, out Type relevantType) + { + if (name == "Products") + { + relevantType = typeof(Product); + } + else if (name == "Customers") + { + relevantType = typeof(Customer); + } + else if (name == "Stores") + { + relevantType = typeof(Store); + } + else + { + relevantType = null; + } + + return true; + } + + public bool TryGetRelevantType(ModelContext context, string namespaceName, string name, out Type relevantType) + { + relevantType = typeof(Product); + return true; + } + } + + class TestModelProducer : IModelBuilder + { + private readonly EdmModel model; + + public TestModelProducer(EdmModel model) + { + this.model = model; + } + + public Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + return Task.FromResult(model); + } + } + + class TestQueryExpressionSourcer : IQueryExpressionSourcer + { + public Expression ReplaceQueryableSource(QueryExpressionContext context, bool embedded) + { + var a = new[] { new Product + { + Id = 1, + Addr = new Address { Zip = 0001 }, + Addr2= new Address { Zip = 0002 } + } }; + + var b = new[] { new Customer + { + Id = 1, + } }; + + var c = new[] { new Store + { + Id = 1, + } }; + + if (!embedded) + { + if (context.VisitedNode.ToString() == "GetQueryableSource(\"Products\", null)") + { + return Expression.Constant(a.AsQueryable()); + } + + if (context.VisitedNode.ToString() == "GetQueryableSource(\"Customers\", null)") + { + return Expression.Constant(b.AsQueryable()); + } + + if (context.VisitedNode.ToString() == "GetQueryableSource(\"Stores\", null)") + { + return Expression.Constant(c.AsQueryable()); + } + } + + return context.VisitedNode; + } + } + + class TestChangeSetInitializer : IChangeSetInitializer + { + public Task InitializeAsync(SubmitContext context, CancellationToken cancellationToken) + { + var changeSetEntry = context.ChangeSet.Entries.Single(); + + var dataModificationEntry = changeSetEntry as DataModificationItem; + if (dataModificationEntry != null) + { + dataModificationEntry.Resource = new Product() + { + Name = "var1", + Addr = new Address() + { + Zip = 330 + } + }; + } + + return Task.FromResult(null); + } + } + + class TestSubmitExecutor : ISubmitExecutor + { + public Task ExecuteSubmitAsync(SubmitContext context, CancellationToken cancellationToken) + { + return Task.FromResult(new SubmitResult(context.ChangeSet)); + } + } +} diff --git a/ApiAsAService/RESTier/src/Strict.ruleset b/ApiAsAService/RESTier/src/Strict.ruleset new file mode 100644 index 0000000..5f44d37 --- /dev/null +++ b/ApiAsAService/RESTier/src/Strict.ruleset @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/RESTierAsAService.sln b/ApiAsAService/RESTierAsAService.sln new file mode 100644 index 0000000..ffa986e --- /dev/null +++ b/ApiAsAService/RESTierAsAService.sln @@ -0,0 +1,104 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.757 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{D8A3183C-1E9C-4D6C-AC72-4EF938EC9895}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataProviders", "DataProviders", "{37B52FD3-E72B-406F-8C5A-F146256D7743}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{9D3D8728-C31B-4D5E-B471-79A9DBBA0E58}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Core", "RESTier\src\Microsoft.Restier.Core\Microsoft.Restier.Core.csproj", "{300B769A-3513-49D0-A035-7DB965C8D2A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.AspNetCore", "RESTier\src\Microsoft.Restier.AspNetCore\Microsoft.Restier.AspNetCore.csproj", "{97E94F97-E73B-4074-8587-AE1B91B4D61E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.AspNet", "RESTier\src\Microsoft.Restier.AspNet\Microsoft.Restier.AspNet.csproj", "{8ECF4E97-1816-44AD-AD63-6ACF287ED520}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.EntityFramework", "RESTier\src\Microsoft.Restier.EntityFramework\Microsoft.Restier.EntityFramework.csproj", "{0E373B2A-2ED2-4566-A275-6BE81CFFE00B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{901E6A2A-23EC-4BC8-B4C6-A3EF70D72702}" + ProjectSection(SolutionItems) = preProject + RESTier\src\Directory.Build.props = RESTier\src\Directory.Build.props + Directory.Build.props = Directory.Build.props + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Samples.Northwind.AspNet", "RESTier\src\Microsoft.Restier.Samples.Northwind.AspNet\Microsoft.Restier.Samples.Northwind.AspNet.csproj", "{3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RESTier", "RESTier", "{76B4E51F-233E-4DD3-AABF-A6F47788040D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "WebAPI\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI", "WebAPI", "{B43F200F-B847-48BE-9362-36D94C48521D}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 + WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {300B769A-3513-49D0-A035-7DB965C8D2A4}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {300B769A-3513-49D0-A035-7DB965C8D2A4}.Release|Any CPU.Build.0 = Release|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Release|Any CPU.Build.0 = Release|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Release|Any CPU.Build.0 = Release|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Release|Any CPU.Build.0 = Release|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Release|Any CPU.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D8A3183C-1E9C-4D6C-AC72-4EF938EC9895} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} + {37B52FD3-E72B-406F-8C5A-F146256D7743} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} + {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} + {300B769A-3513-49D0-A035-7DB965C8D2A4} = {D8A3183C-1E9C-4D6C-AC72-4EF938EC9895} + {97E94F97-E73B-4074-8587-AE1B91B4D61E} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} + {8ECF4E97-1816-44AD-AD63-6ACF287ED520} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} + {0E373B2A-2ED2-4566-A275-6BE81CFFE00B} = {37B52FD3-E72B-406F-8C5A-F146256D7743} + {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2} = {DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F} + {A6F9775D-F7E2-424E-8363-79644A73038F} = {B43F200F-B847-48BE-9362-36D94C48521D} + {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {B43F200F-B847-48BE-9362-36D94C48521D} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5A37189C-A5E1-4871-AF65-8EBF2DA60FE3} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/ReadMe.md b/ApiAsAService/ReadMe.md new file mode 100644 index 0000000..58b3cc4 --- /dev/null +++ b/ApiAsAService/ReadMe.md @@ -0,0 +1 @@ +# DynamicEdmModelCreation ----------------------- This sample shows how to dynamically create an EDM model, and bind it into Web API OData pipeline. ## Background By default, Web API OData supports a static EDM model per OData route. In WebApiConfig.Register static method, before calling config.Routes.MapODataServiceRoute, an EDM model is created, and then assigned as a parameter to the MapODataServiceRoute method. ```C# public static class WebApiConfig { public static void Register(HttpConfiguration config) { var builder = new ODataConventionModelBuilder(); builder.EntitySet("Customers"); config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel()); } } ``` However, there are some other scenarios which require runtime EDM model binding, e.g.: 1. Multi-tenant OData service: One-model per tenant 2. Hundreds of models, want to delay load as many as possible in WebApiConfig 3. Per request model This sample describes how to create a per-request EDM model, which can be used as a foundation for scenario 1 and 2: In request Uri, there is a path segment between route prefix and entity set, e.g. `mydatasource` in http://servername/odata/mydatasource/Products. Based on datasource, the service could build EDM model on the fly, and then use an untyped controller to handle the request. The steps are: 1. Create a customized `ODataPathRouteConstraint`, which allows to set EdmModel property, before Match is called. 2. Create a customized `ODataRoute` to override GetVirtualPath logic, and generate OData links correctly. 3. Create a customized `MapODataServiceRoute` that takes a Func instead of an IEdmModel. --- ## .NET Classic It's .NET Framework Console application hosting a Web API service depending on `Microsoft.AspNet.OData` nuget package. When it runs, it will output the following result: ```json Server listening at http://localhost:54321 Sending request to: /odata/mydatasource/. Executing Query service document.... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata","value":[{"name":"Products","kind":"EntitySet","url":"Products"},{"name":"DetailInfos","kind":"EntitySet","url":"DetailInfos"}]} Sending request to: /odata/mydatasource/$metadata. Executing Query $metadata.... Result: OK Sending request to: /odata/mydatasource/Products. Executing Query the Products entity set.... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#Products","value":[{"Name":"abc","ID":1},{"Name":"def","ID":2}]} Sending request to: /odata/mydatasource/Products(1). Executing Query a Product entry.... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#Products/$entity","Name":"abc","ID":1} Sending request to: /odata/anotherdatasource/. Executing Query service document.... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata","value":[{"name":"Students","kind":"EntitySet","url":"Students"},{"name":"Schools","kind":"EntitySet","url":"Schools"}]} Sending request to: /odata/anotherdatasource/$metadata. Executing Query $metadata.... Result: OK Sending request to: /odata/anotherdatasource/Students. Executing Query the Students entity set.... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Students","value":[{"Name":"Foo","ID":100},{"Name":"Bar","ID":101}]} Sending request to: /odata/anotherdatasource/Students(100). Executing Query a Student entry.... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Students/$entity","Name":"Foo","ID":100} Sending request to: /odata/mydatasource/Products(1)/Name. Executing Query the name of Products(1).... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#Products(1)/Name","value":"abc"} Sending request to: /odata/anotherdatasource/Students(100)/Name. Executing Query the name of Students(100).... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Students(100)/Name","value":"Foo"} Sending request to: /odata/mydatasource/Products(1)/DetailInfo. Executing Query the navigation property 'DetailInfo' of Products(1).... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#DetailInfos/ns.DetailInfo/$entity","ID":88,"Title":"abc_detailinfo"} Sending request to: /odata/anotherdatasource/Students(100)/School. Executing Query the navigation proeprty 'School' of Students(100).... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Schools/ns.School/$entity","ID":99,"CreatedDay":"2016-01-19T01:02:03Z"} Press Any Key to Exit ... ``` \ No newline at end of file diff --git a/ApiAsAService/WebAPI/License.txt b/ApiAsAService/WebAPI/License.txt new file mode 100644 index 0000000..f839342 --- /dev/null +++ b/ApiAsAService/WebAPI/License.txt @@ -0,0 +1,26 @@ +OData-WebAPI + +Copyright (c) Microsoft. All rights reserved. + +Material in this repository is made available under the following terms: + 1. Code is licensed under the MIT license, reproduced below. + 2. Documentation is licensed under the Creative Commons Attribution 3.0 United States (Unported) License. + The text of the license can be found here: http://creativecommons.org/licenses/by/3.0/legalcode + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/ApiAsAService/WebAPI/README.md b/ApiAsAService/WebAPI/README.md new file mode 100644 index 0000000..035d89a --- /dev/null +++ b/ApiAsAService/WebAPI/README.md @@ -0,0 +1,110 @@ +## OData Web API + Build | Status +--------|--------- +Rolling | +Nightly | + +### Introduction +[OData Web API](http://odata.github.io/WebApi) (i.e., ASP.NET Web API OData) is a server library built upon [ODataLib](https://github.com/OData/odata.net/) and [Web API](http://www.asp.net/web-api). + +### Project structure +The project currently has the following branches: + +**[master](https://github.com/OData/Webapi/tree/master) branch** + +This is the active development branch for OData WebApi and it is currently most actively iterated. The package name is Microsoft.AspNet.OData. The is the OData WebApi for ODL v7.x releases which contain breaking changes against ODL v6. + +**[release](https://github.com/OData/Webapi/tree/release) branch** + +This is the release branch for OData WebApi, contains code base up to most recently stable WebApi release. The latest release version is [6.0](https://www.nuget.org/packages/Microsoft.AspNet.OData/6.0.0). + +**[feature/netcore](https://github.com/OData/Webapi/tree/feature/netcore) branch** + +This is the feature development branch for OData WebApi for AspNet and AspNetCore. The package names are Microsoft.AspNet.OData and Microsoft.AspNetCore.OData. The is the OData WebApi 7.0 release which contain breaking changes against OData WebApi 6.0. + +**[gh-pages](https://github.com/OData/WebApi/tree/gh-pages) branch** + +The gh-pages branch contains documentation source for OData WebApi - tutorials, guides, etc. The documention source is in Markdown format. It is hosted at [ODataLib Pages](http://odata.github.io/WebApi/ "ODataLib Pages"). + +**[maintenance-aspnetcore](https://github.com/OData/Webapi/tree/maintenance-aspnetcore)** + +This is the maintenance branch for OData WebApi with ASP.NET Core support. The package name is Microsoft.AspNetCore.OData. + + +**[maintenance-V4](https://github.com/OData/Webapi/tree/maintenance-V4) branch** + + +This is the maintenance branch for OData WebApi based on ODL 6.x, which implements the ODataV4 protocol. The package name is Microsoft.AspNet.OData, with latest maintenance release version [5.10](https://www.nuget.org/packages/Microsoft.AspNet.OData/5.10.0). + +**[maintenance-V3](https://github.com/OData/Webapi/tree/maintenance-V3) branch** + +This is the maintenance branch for OData WebApi based on ODL 5.x, which implements the ODataV3 protocol. The package name is Microsoft.AspNet.WebApi.OData, with latest maintenance release version [5.7](https://www.nuget.org/packages/Microsoft.AspNet.WebApi.OData/5.7.0). + +**[maintenance-dnx](https://github.com/OData/Webapi/tree/maintenance-dnx) branch** + +This is maintenance branch for an early prototype version of OData WebApi based on original ASP.NET Core, aka DNX. Package name is Microsoft.AspNet.OData. This is for project archive purpose only, is not active and doesn't accept contributions. It has only one release with information available [here](http://odata.github.io/WebApi/#07-07-6-0-0-alpha1). + +**[odata-v5.3-rtm](https://github.com/OData/WebApi/tree/odata-v5.3-rtm) [v2.0-rtm](https://github.com/OData/WebApi/tree/v2.0-rtm) [v3-rtm](https://github.com/OData/WebApi/tree/odata-v3-rtm) [v3.1-rtm](https://github.com/OData/WebApi/tree/v3.1-rtm) [v3.2-rtm](https://github.com/OData/WebApi/tree/v3.2-rtm) branches** + +These are maintenance branches for previous RTMs. Project archives only, contributions not accepted. + +### Building +``` +build.cmd +``` + +### Testing +Each solution contains some test projects. Test projects use xUnit runner nuget package. + +Tests will not run correctly unless SkipStrongNames is Enabled. Please run +``` +build.cmd EnableSkipStrongNames +``` + +#### Run tests in cmd +To run end-to-end tests, you need to open an **elevated** - Run as administrator - command prompt + +* `build.cmd` build projects, run unit tests, and OData end-to-end tests. + +* `build.cmd quick` build project, and run unit tests + +To disable the SkipStrongNames: +``` +build.cmd DisableSkipStrongNames +``` + +#### Run tests in Visual Studio +Open the project, build it, and then test cases should appear in test explorer. If not, this is because the assemblies are delay signed and you're missing the private key so xunit will not load them in Visual Studio. To fix, please run `build.cmd EnableSkipStrongNames`. Run all the tests in the test explorer. For running end-to-end tests you must open the solution as *Administrator*. More detail at [this](http://odata.github.io/WebApi/#09-01-unittest-e2etest). + +### Nightly builds +The nightly build process will upload a NuGet packages for WebApi to: + v7.x.x: [MyGet.org webapinetcore feed](https://www.myget.org/gallery/webapinetcore) + v6.x.x: [MyGet.org webapinightly feed](https://www.myget.org/gallery/webapinightly) + + +To connect to webapinightly feed, use this feed URL: + v7.x.x: [webapinetcore MyGet feed URL](https://www.myget.org/F/webapinetcore) + v6.x.x: [webapinightly MyGet feed URL](https://www.myget.org/F/webapinightly) + + +You can query the latest nightly NuGet packages using this query: + v7.x.x: [MAGIC WebApi query](https://www.myget.org/F/webapinetcore/Packages?$select=Id,Version&$orderby=Version%20desc&$top=4&$format=application/json) + v6.x.x: [MAGIC WebApi query](https://www.myget.org/F/webapinightly/Packages?$select=Id,Version&$orderby=Version%20desc&$top=4&$format=application/json) + +### Contribution +Please refer to the [CONTRIBUTION.md](https://github.com/OData/WebApi/blob/master/.github/CONTRIBUTION.md). + +### Documentation +Please visit the [OData Web API pages](http://odata.github.io/WebApi). + +### Samples +Please refer to the [ODataSamples Repro](https://github.com/OData/ODataSamples). +* ASP.NET Core OData samples at [here](https://github.com/OData/ODataSamples/tree/master/WebApiCore) +* ASP.NET Classic OData samples at [here](https://github.com/OData/ODataSamples/tree/master/WebApiClassic) + +### Debug +Please refer to the [How to debug](http://odata.github.io/WebApi/10-01-debug-webapi-source). + +### Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/ApiAsAService/WebAPI/Settings.StyleCop b/ApiAsAService/WebAPI/Settings.StyleCop new file mode 100644 index 0000000..3f59149 --- /dev/null +++ b/ApiAsAService/WebAPI/Settings.StyleCop @@ -0,0 +1,392 @@ + + + NoMerge + + + + + False + + + + + + + + + False + + + + + + as + db + dc + do + ef + id + if + in + is + my + no + on + sl + to + ui + vs + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + True + True + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See License.txt in the project root for license information. + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + + + Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See License.txt in the project root for license information. + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/WebApiOData.Performance.msbuild b/ApiAsAService/WebAPI/WebApiOData.Performance.msbuild new file mode 100644 index 0000000..147092d --- /dev/null +++ b/ApiAsAService/WebAPI/WebApiOData.Performance.msbuild @@ -0,0 +1,85 @@ + + + + + + + CodeAnalysis + Release + true + true + true + true + false + $(MSBuildThisFileDirectory)bin\$(Configuration)\UnitTest\ + $(MSBuildThisFileDirectory)bin\$(Configuration)\UnitTest\TestResults\ + $(MSBuildThisFileDirectory)sln\packages\Microsoft.Web.SkipStrongNames.1.0.0\tools\SkipStrongNames.exe + $(MSBuildThisFileDirectory)tools\SkipStrongNames.xml + $(MSBuildThisFileDirectory)sln\.nuget\NuGet.exe + + + + + $(BuildInParallel) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/build.cmd b/ApiAsAService/WebAPI/build.cmd new file mode 100644 index 0000000..1c1bc61 --- /dev/null +++ b/ApiAsAService/WebAPI/build.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy RemoteSigned -File "build.ps1" %* \ No newline at end of file diff --git a/ApiAsAService/WebAPI/build.ps1 b/ApiAsAService/WebAPI/build.ps1 new file mode 100644 index 0000000..aa972e7 --- /dev/null +++ b/ApiAsAService/WebAPI/build.ps1 @@ -0,0 +1,476 @@ +# Default to Debug +$Configuration = 'Debug' + +# Color +$Success = 'Green' +$Warning = 'Yellow' +$Err = 'Red' + +Function Error ($msg) +{ + Write-Host "[Error:]" $msg -ForegroundColor $Err +} + +Function Warning ($msg) +{ + Write-Host "[Warning:]" $msg -ForegroundColor $Warning +} + +Function Success ($msg) +{ + Write-Host "[Success:]" $msg -ForegroundColor $Success +} + +Function Info ($msg) +{ + Write-Host "[Info:]" $msg +} + +if ($args.Count -eq 0) +{ + $TestType = 'Nightly' + $Configuration = 'Release' +} +elseif ($args[0] -match 'quick' -or ($args[0] -match '-q')) +{ + $TestType = "Quick" +} +elseif ($args[0] -match 'DisableSkipStrongName') +{ + $TestType = "DisableSkipStrongName" +} +elseif ($args[0] -match 'EnableSkipStrongName') +{ + $TestType = "EnableSkipStrongName" +} +else +{ + Error("Unknown input ""$args"". It can be empty or ""quick|DisableSkipStrongName|EnableSkipStrongName"".") + exit +} + +$Build = 'build' +if ($args -contains 'rebuild') +{ + $Build = 'rebuild' +} + +$PROGRAMFILESX86 = [Environment]::GetFolderPath("ProgramFilesX86") +$env:ENLISTMENT_ROOT = Split-Path -Parent $MyInvocation.MyCommand.Definition +$ENLISTMENT_ROOT = Split-Path -Parent $MyInvocation.MyCommand.Definition +$LOGDIR = $ENLISTMENT_ROOT + "\bin" + +# Default to use Visual Studio 2015 +$VS14MSBUILD=$PROGRAMFILESX86 + "\MSBuild\14.0\Bin\MSBuild.exe" +$VSTEST = $PROGRAMFILESX86 + "\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" + +# Figure out the directory and path for SN.exe +$SN = $null +$SNx64 = $null +ForEach ($directory in [System.IO.Directory]::EnumerateDirectories($PROGRAMFILESX86 + "\Microsoft SDKs\Windows")) +{ + ForEach($sdkVersionDirectory in [System.IO.Directory]::EnumerateDirectories($directory + "\bin")) + { + if([System.IO.File]::Exists($sdkVersionDirectory + "\sn.exe") -and [System.IO.File]::Exists($sdkVersionDirectory + "\x64\sn.exe")) + { + $SN = $sdkVersionDirectory + "\sn.exe" + $SNx64 = $sdkVersionDirectory + "\x64\sn.exe" + break + } + } +} + +# Use Visual Studio 2017 compiler for .NET Core and .NET Standard. Because VS2017 has different paths for different +# versions, we have to check for each version. Meanwhile, the dotnet CLI is required to run the .NET Core unit tests in this script. +# Furthurmore, Visual Studio 2017 has a Preview version as well which uses Microsoft Visual Studio\Preview as path instead of \2017 +$VS15VARIANTS = "2017", "Preview" +$VS15VARIANTPATH = $null +ForEach ($variant in $VS15VARIANTS) +{ + $tempVSPath = ($PROGRAMFILESX86 + "\Microsoft Visual Studio\{0}") -f $variant + if([System.IO.Directory]::Exists($tempVSPath)) + { + $VS15VARIANTPATH = $tempVSPath + break + } +} + +$VS15VERSIONS = "Enterprise", "Professional", "Community" +$VS15MSBUILD = $null +ForEach ($version in $VS15VERSIONS) +{ + $tempMSBuildPath = ($VS15VARIANTPATH + "\{0}\MSBuild\15.0\Bin\MSBuild.exe") -f $version + if([System.IO.File]::Exists($tempMSBuildPath)) + { + $VS15MSBUILD = $tempMSBuildPath + $VSTEST = ($VS15VARIANTPATH + "\{0}\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe") -f $version + break + } +} + +$DOTNETDIR = "C:\Program Files\dotnet\" +$DOTNETEXE = $null +if ([System.IO.File]::Exists($DOTNETDIR + "dotnet.exe")) +{ + $DOTNETEXE = $DOTNETDIR + "dotnet.exe" +} +else +{ + Error("The dotnet CLI must be installed to run any .NET Core tests.") + exit +} + +# Other variables +$BUILDLOG = $LOGDIR + "\msbuild.log" +$TESTLOG = $LOGDIR + "\mstest.log" +$NUGETEXE = $ENLISTMENT_ROOT + "\sln\.nuget\NuGet.exe" +$NUGETPACK = $ENLISTMENT_ROOT + "\sln\packages" +$XUNITADAPTER = "/TestAdapterPath:" + $NUGETPACK + "\xunit.runner.visualstudio.2.3.1\build\_common" + +$ClassicUnitTestSLN = "WebApiOData.AspNet.sln" +$ClassicE2ETestSLN = "WebApiOData.E2E.AspNet.sln" +$NetCoreUnitTesSLN = "WebApiOData.AspNetCore.sln" +$NetCoreE2ETestSLN = "WebApiOData.E2E.AspNetCore.sln" +$NugetRestoreSolutions = $ClassicUnitTestSLN, $ClassicE2ETestSLN, $NetCoreUnitTesSLN, $NetCoreE2ETestSLN + +$NetCoreProductPROJ = "\src\Microsoft.AspNetCore.OData\Microsoft.AspNetCore.OData.csproj " +$NetCoreUnitTestPROJ = "\test\UnitTest\Microsoft.AspNetCore.OData.Test\Microsoft.AspNetCore.OData.Test.csproj" +$NetCoreE2ETestPROJ = "\test\E2ETest\Microsoft.Test.E2E.AspNet.OData\Build.AspNetCore\Microsoft.Test.E2E.AspNetCore.OData.csproj" +$NugetRestoreDotNetProjects = $NetCoreProductPROJ, $NetCoreUnitTestPROJ, $NetCoreE2ETestPROJ + +$ClassicProductDIR = $ENLISTMENT_ROOT + "\bin\$Configuration" +$ClassicProductDlls = "Microsoft.AspNet.OData.dll" +$ClassicUnitTestDIR = $ENLISTMENT_ROOT + "\bin\$Configuration\UnitTest\AspNet" +$ClassicUnitTestDlls = "Microsoft.AspNet.OData.Test.dll" +$ClassicE2ETestDIR = $ENLISTMENT_ROOT + "\bin\$Configuration\E2ETest\AspNet" +$ClassicE2ETestDlls = "Microsoft.Test.E2E.AspNet.OData.dll" + +$NetCoreProductDIR = $ENLISTMENT_ROOT + "\bin\$Configuration\netstandard2.0" +$NetCoreProductDlls = "Microsoft.AspNetCore.OData.dll" +$NetCoreUnitTestDIR = $ENLISTMENT_ROOT + "\bin\$Configuration\UnitTest\AspNetCore\netcoreapp2.0" +$NetCoreUnitTestDlls = "Microsoft.AspNetCore.OData.Test.dll" +$NetCoreE2ETestDIR = $ENLISTMENT_ROOT + "\bin\$Configuration\E2ETest\AspNetCore" +$NetCoreE2ETestDlls = "Microsoft.Test.E2E.AspNetCore.OData.dll" + +# .NET Core tests are different and require the dotnet tool. The tool references the .csproj (VS2017) files instead of dlls +# However, the AspNetCore E2E tests still use EF6 and are not compiled to a .NetCore binary. +$ClassicUnitTestFramework = "netfx" +$ClassicUnitTestSuite = @() +ForEach($dll in $ClassicUnitTestDlls) +{ + $ClassicUnitTestSuite += $ClassicUnitTestDIR + "\" + $dll +} + +$NetCoreUnitTestFramework = "dotnet" +$NetCoreUnitTestSuite = @() + +$NetCoreUnitTestProjs = $ENLISTMENT_ROOT + "\test\UnitTest\Microsoft.AspNetCore.OData.Test\Microsoft.AspNetCore.OData.Test.csproj" +ForEach($proj in $NetCoreUnitTestProjs) +{ + $NetCoreUnitTestSuite += $proj +} + +$ClassicE2ETestFramework = "netfx" +$ClassicE2ETestSuite = @() +ForEach($dll in $ClassicE2ETestDlls) +{ + $ClassicE2ETestSuite += $ClassicE2ETestDIR + "\" + $dll +} + +$NetCoreE2ETestFramnework = "netfx" +$NetCoreE2ETestSuite = @() +ForEach($dll in $NetCoreE2ETestDlls) +{ + $NetCoreE2ETestSuite += $NetCoreE2ETestDIR + "\" + $dll +} + +Function GetDlls +{ + $dlls = @() + + $dlls += $ClassicProductDIR + "\" + $ClassicProductDlls + $dlls += $NetCoreProductDIR + "\" + $NetCoreProductDlls + + $dlls += $ClassicUnitTestDIR + "\" + $ClassicUnitTestDlls + $dlls += $NetCoreUnitTestDIR + "\" + $NetCoreUnitTestDlls + + $dlls += $ClassicE2ETestDIR + "\" + $ClassicE2ETestDlls + $dlls += $NetCoreE2ETestDIR + "\" + $NetCoreE2ETestDlls + + return $dlls +} + +Function SkipStrongName +{ + $SnLog = $LOGDIR + "\SkipStrongName.log" + Out-File $SnLog + + Info('Skip strong name validations for assemblies...') + + $dlls = GetDlls + ForEach ($dll in $dlls) + { + & $SN /Vr $dll | Out-File $SnLog -Append + } + + ForEach ($dll in $dlls) + { + & $SNx64 /Vr $dll | Out-File $SnLog -Append + } + + Success("SkipStrongName Done") +} + +Function DisableSkipStrongName +{ + $SnLog = $LOGDIR + "\DisableSkipStrongName.log" + Out-File $SnLog + + Info("Disable skip strong name validations for assemblies...") + + $dlls = GetDlls + ForEach ($dll in $dlls) + { + & $SN /Vu $dll | Out-File $SnLog -Append + } + + ForEach ($dll in $dlls) + { + & $SNx64 /Vu $dll | Out-File $SnLog -Append + } + + Success("DisableSkipStrongName Done") +} + +Function NugetRestoreSolution +{ + Info("Pull NuGet Packages...") + foreach($solution in $NugetRestoreSolutions) + { + & $NUGETEXE "restore" ($ENLISTMENT_ROOT + "\sln\" + $solution) + } + foreach($project in $NugetRestoreDotNetProjects) + { + & $DOTNETEXE "restore" ($ENLISTMENT_ROOT + $project) + } + Success("Pull Nuget Packages Success") +} + +Function TestSummary +{ + Write-Host 'Collecting test results' + + $file = Get-Content -Path $TESTLOG + $pass = 0 + $skipped = 0 + $fail = 0 + $failedtest1 = New-Object -TypeName System.Collections.ArrayList + $failedtest2 = New-Object -TypeName System.Collections.ArrayList + $part = 1 + foreach ($line in $file) + { + # Consolidate logic for retrieving number of passed and skipped tests. Failed tests is separate due to the way + # VSTest and DotNet (for .NET Core tests) report results differently. + if ($line -match "^Total tests: .*") + { + # The line is in this format: + # Total tests: 5735. Passed: 5735. Failed: 0. Skipped: 0. + # We want to extract the total passed and total skipped. + + # Extract total passed by taking the substring between "Passed: " and "." + # The regex first extracts the string after the hardcoded "Passed: " (i.e. "#. Failed: #. Skipped: #.") + # Then we tokenize by "." and retrieve the first token which is the number for passed. + $pattern = "Passed: (.*)" + $extractedNumber = [regex]::match($line, $pattern).Groups[1].Value.Split(".")[0] + $pass += $extractedNumber + + # Extract total skipped by taking the substring between "Skipped: " and "." + # The regex first extracts the string after the hardcoded "Skipped: " (i.e. "#.") + # Then we tokenize by "." and retrieve the first token which is the number for skipped. + $pattern = "Skipped: (.*)" + $extractedNumber = [regex]::match($line, $pattern).Groups[1].Value.Split(".")[0] + $skipped += $extractedNumber + } + elseif ($line -match "^Failed\s+(.*)") + { + $fail = $fail + 1 + if ($part -eq 1) + { + [void]$failedtest1.Add($Matches[1]) + } + else + { + [void]$failedtest2.Add($Matches[1]) + } + } + } + + Write-Host "Test summary:" -ForegroundColor $Success + Write-Host "Passed :`t$pass" -ForegroundColor $Success + + if ($skipped -ne 0) + { + Write-Host "Skipped:`t$skipped" -ForegroundColor $Warning + } + + $color = $Success + if ($fail -ne 0) + { + $color = $Err + } + Write-Host "Failed :`t$fail" -ForegroundColor $color + Write-Host "---------------" -ForegroundColor $Success + Write-Host "Total :`t$($pass + $fail)" -ForegroundColor $Success + if ($fail -eq 0) + { + Write-Host "Congratulation! All of the tests passed!" -ForegroundColor $Success + } +} + +# Incremental build and rebuild +Function RunBuild ($sln, $vsToolVersion) +{ + Info("Building $sln ...") + $slnpath = $ENLISTMENT_ROOT + "\sln\$sln" + $Conf = "/p:Configuration=" + "$Configuration" + + # Default to VS2015 + $MSBUILD = $VS14MSBUILD + if($vsToolVersion -eq '15.0') + { + $MSBUILD=$VS15MSBUILD + } + + # If VS2015 is not present, try to use VS2017 + if(![System.IO.File]::Exists($MSBUILD)) + { + $MSBUILD = $VS15MSBUILD + } + + & $MSBUILD $slnpath /t:$Build /m /nr:false /fl "/p:Platform=Any CPU" $Conf /p:Desktop=true ` + /flp:LogFile=$LOGDIR/msbuild.log /flp:Verbosity=Normal 1>$null 2>$null + if($LASTEXITCODE -eq 0) + { + Success("Build $sln SUCCESS") + } + else + { + Error("Build $sln FAILED") + Info("For more information, please open the following log file:") + Info("$LOGDIR\msbuild.log") + exit + } +} + +Function BuildProcess +{ + Info("Building Asp.Net OData Projects...") + + $script:BUILD_START_TIME = Get-Date + if (Test-Path $BUILDLOG) + { + rm $BUILDLOG + } + + # Asp.Net Classic (Product & Unit Test) + RunBuild ($ClassicUnitTestSLN) + + # Asp.Net Core (Product & Unit Test) + RunBuild ($NetCoreUnitTesSLN) -vsToolVersion '15.0' + + if ($TestType -ne 'Quick') + { + # Asp.Net Classic (Product & Unit Test & E2E) + RunBuild ($ClassicE2ETestSLN) + + # Asp.Net Core (Product & Unit Test & E2E) + RunBuild ($NetCoreE2ETestSLN) -vsToolVersion '15.0' + } + + Success("Build Done!") + Write-Host + $script:BUILD_END_TIME = Get-Date +} + +Function RunTest($title, $testdir, $framework) +{ + Info("Running test $title...") + if ($framework -eq 'dotnet') + { + $Conf = "/p:Configuration=" + "$Configuration" + foreach($testProj in $testdir) + { + Info("Launching $testProj...") + & $DOTNETEXE "test" $testProj $Conf "--no-build" >> $TESTLOG + } + } + else + { + Info("Launching $testdir...") + & $VSTEST $testdir $XUNITADAPTER >> $TESTLOG + } + + if($LASTEXITCODE -ne 0) + { + Error("Run $title FAILED") + } +} + +Function TestProcess +{ + Info("Testing Asp.Net OData Projects...") + + if (Test-Path $TESTLOG) + { + rm $TESTLOG + } + + $script:TEST_START_TIME = Get-Date + + RunTest -title 'AspNetClassic UnitTest' -testdir $ClassicUnitTestSuite -framework $ClassicUnitTestFramework + + RunTest -title 'AspNetCore UnitTest' -testdir $NetCoreUnitTestSuite -framework $NetCoreUnitTestFramework + + if ($TestType -ne 'Quick') + { + RunTest -title 'AspNetClassic E2ETests' -testdir $ClassicE2ETestSuite -framework $ClassicE2ETestFramework + + RunTest -title 'AspNetCore E2ETests' -testdir $NetCoreE2ETestSuite -framework $NetCoreE2ETestFramework + } + + Info("Test Done.") + TestSummary + $script:TEST_END_TIME = Get-Date +} + +# Main Process + +if (! (Test-Path $LOGDIR)) +{ + mkdir $LOGDIR 1>$null +} + +if ($TestType -eq 'EnableSkipStrongName') +{ + NugetRestoreSolution + BuildProcess + SkipStrongName + Exit +} +elseif ($TestType -eq 'DisableSkipStrongName') +{ + NugetRestoreSolution + BuildProcess + DisableSkipStrongName + Exit +} + +NugetRestoreSolution +BuildProcess +SkipStrongName +TestProcess + +$buildTime = New-TimeSpan $script:BUILD_START_TIME -end $script:BUILD_END_TIME +$testTime = New-TimeSpan $script:TEST_START_TIME -end $script:TEST_END_TIME +Write-Host("Build time:`t" + $buildTime) +Write-Host("Test time:`t" + $testTime) \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/AspNetCoreODataSample.Web.csproj b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/AspNetCoreODataSample.Web.csproj new file mode 100644 index 0000000..21705e4 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/AspNetCoreODataSample.Web.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + + diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/MoviesController.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/MoviesController.cs new file mode 100644 index 0000000..cd33ae1 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/MoviesController.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using AspNetCoreODataSample.Web.Models; +using Microsoft.AspNet.OData; +using Microsoft.AspNetCore.Mvc; + +namespace AspNetCoreODataSample.Web.Controllers +{ + public class MoviesController : ODataController + { + private readonly MovieContext _context; + + private readonly IList _inMemoryMovies; + + public MoviesController(MovieContext context) + { + _context = context; + + if (_context.Movies.Count() == 0) + { + Movie conanMovie = new Movie + { + Title = "Conan", + ReleaseDate = new DateTimeOffset(new DateTime(2017, 3, 3)), + Genre = Genre.Comedy, + Price = 1.99m + }; + Movie dieHardMovie = new Movie + { + Title = "Die Hard", + ReleaseDate = new DateTimeOffset(new DateTime(2014, 1, 3)), + Genre = Genre.Comedy, + Price = 1.89m + }; + _context.Movies.Add(conanMovie); + _context.Movies.Add(dieHardMovie); + _context.SaveChanges(); + MovieStar s = new MovieStar + { + FirstName = "Arnold", + LastName = "Schwarzenegger", + MovieId = conanMovie.ID + }; + _context.MovieStars.Add(s); + MovieStar b = new MovieStar + { + FirstName = "Bruce", + LastName = "Willis", + MovieId = dieHardMovie.ID + }; + _context.MovieStars.Add(b); + _context.SaveChanges(); + } + + _inMemoryMovies = new List + { + new Movie + { + ID = 1, + Title = "Conan", + ReleaseDate = new DateTimeOffset(new DateTime(2018, 3, 3)), + Genre = Genre.Comedy, + Price = 1.99m, + Stars = new List{ + new MovieStar + { + FirstName = "Arnold", + LastName = "Schwarzenegger", + MovieId = 1 + }, + new MovieStar + { + FirstName = "Jackie", + LastName = "Chan", + MovieId = 1 + } + } + }, + new Movie + { + ID = 2, + Title = "James", + ReleaseDate = new DateTimeOffset(new DateTime(2017, 3, 3)), + Genre = Genre.Adult, + Price = 91.99m, + Stars = new List{ + new MovieStar + { + FirstName = "Bruce", + LastName = "Willis", + MovieId = 2 + } + } + } + }; + } + + [EnableQuery] + public IActionResult Get() + { + if (Request.Path.Value.Contains("efcore")) + { + return Ok(_context.Movies); + } + else + { + return Ok(_inMemoryMovies); + } + } + + [EnableQuery] + public IActionResult Get(int key) + { + Movie m; + if (Request.Path.Value.Contains("efcore")) + { + m = _context.Movies.FirstOrDefault(c => c.ID == key); + } + else + { + m = _inMemoryMovies.FirstOrDefault(c => c.ID == key); + } + + if (m == null) + { + return NotFound(); + } + + return Ok(m); + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/PeopleController.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/PeopleController.cs new file mode 100644 index 0000000..cf2848e --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Controllers/PeopleController.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using AspNetCoreODataSample.Web.Models; +using Microsoft.AspNet.OData; +using Microsoft.AspNetCore.Mvc; + +namespace AspNetCoreODataSample.Web.Controllers +{ + public class PeopleController : ODataController + { + [EnableQuery] + public IActionResult Get([FromODataUri]string keyFirstName, [FromODataUri]string keyLastName) + { + Person m = new Person + { + FirstName = keyFirstName, + LastName = keyLastName, + DynamicProperties = new Dictionary + { + { "abc", "abcValue" } + }, + MyLevel = Level.High + }; + + return Ok(m); + } + + [EnableQuery] + public IActionResult Post([FromBody]Person person) + { + return Created(person); + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/EdmModelBuilder.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/EdmModelBuilder.cs new file mode 100644 index 0000000..7bdf9e6 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/EdmModelBuilder.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Builder; +using Microsoft.OData.Edm; + +namespace AspNetCoreODataSample.Web.Models +{ + public static class EdmModelBuilder + { + private static IEdmModel _edmModel; + + public static IEdmModel GetEdmModel() + { + if (_edmModel == null) + { + var builder = new ODataConventionModelBuilder(); + builder.EntitySet("Movies"); + var movieStar = builder.EntitySet("MovieStars").EntityType; + movieStar.HasOptional(_ => _.Movie, + (person, movie) => person.MovieId == movie.ID, movie => movie.Stars); + movieStar.HasKey(x => new { x.FirstName, x.LastName }); + _edmModel = builder.GetEdmModel(); + } + + return _edmModel; + } + + public static IEdmModel GetCompositeModel() + { + var builder = new ODataConventionModelBuilder(); + builder.EntitySet("People"); + var type = builder.EntitySet("Person").EntityType; + type.HasKey(x => new { x.FirstName, x.LastName }); + return builder.GetEdmModel(); + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Genre.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Genre.cs new file mode 100644 index 0000000..0036c43 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Genre.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace AspNetCoreODataSample.Web.Models +{ + public enum Genre + { + Comedy, + + Cartoon, + + Adult + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Level.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Level.cs new file mode 100644 index 0000000..50da049 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Level.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Runtime.Serialization; + +namespace AspNetCoreODataSample.Web.Models +{ + [DataContract(Name = "level")] + public enum Level + { + [EnumMember(Value = "low")] + Low, + + [EnumMember(Value = "medium")] + Medium, + + [EnumMember(Value = "veryhigh")] + High + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Movie.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Movie.cs new file mode 100644 index 0000000..d584a37 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Movie.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace AspNetCoreODataSample.Web.Models +{ + public class Movie + { + public int ID { get; set; } + + public List Stars { get; set; } + + public string Title { get; set; } + + public DateTimeOffset ReleaseDate { get; set; } + + public Genre Genre { get; set; } + + public decimal Price { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieContext.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieContext.cs new file mode 100644 index 0000000..f76826a --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieContext.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore; + +namespace AspNetCoreODataSample.Web.Models +{ + public class MovieContext : DbContext + { + public MovieContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Movies { get; set; } + public DbSet MovieStars { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(_ => new + { + _.FirstName, + _.LastName + }); + base.OnModelCreating(modelBuilder); + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieStar.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieStar.cs new file mode 100644 index 0000000..99893f5 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/MovieStar.cs @@ -0,0 +1,13 @@ +namespace AspNetCoreODataSample.Web.Models +{ + public class MovieStar + { + public Movie Movie { get; set; } + + public int MovieId { get; set; } + + public string FirstName { get; set; } + + public string LastName { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Person.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Person.cs new file mode 100644 index 0000000..fbcee72 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Models/Person.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace AspNetCoreODataSample.Web.Models +{ + public class Person + { + public string FirstName { get; set; } + + public string LastName { get; set; } + + //[NotMapped] + public IDictionary DynamicProperties { get; set; } + + public Level MyLevel { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Program.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Program.cs new file mode 100644 index 0000000..4772a73 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Program.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace AspNetCoreODataSample.Web +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Properties/launchSettings.json b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Properties/launchSettings.json new file mode 100644 index 0000000..f328229 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5912/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "efcore/Movies", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "AspNetCoreODataSample.Web": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "efcore/Movies", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:5913/" + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Startup.cs b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Startup.cs new file mode 100644 index 0000000..286d321 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/Startup.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using AspNetCoreODataSample.Web.Models; + +namespace AspNetCoreODataSample.Web +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(opt => opt.UseInMemoryDatabase("MovieList")); + services.AddOData(); + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + var model = EdmModelBuilder.GetEdmModel(); + + app.UseMvc(builder => + { + builder.Select().Expand().Filter().OrderBy().MaxTop(100).Count(); + + builder.MapODataServiceRoute("odata1", "efcore", model); + + builder.MapODataServiceRoute("odata2", "inmem", model); + + builder.MapODataServiceRoute("odata3", "composite", EdmModelBuilder.GetCompositeModel()); + }); + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.Development.json b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.Development.json new file mode 100644 index 0000000..fa8ce71 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.json b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.json new file mode 100644 index 0000000..26bb0ac --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetCoreODataSample.Web/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/App_Start/WebApiConfig.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/App_Start/WebApiConfig.cs new file mode 100644 index 0000000..7a1355d --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/App_Start/WebApiConfig.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; +using AspNetODataSample.Web.Models; +using Microsoft.AspNet.OData.Extensions; + +namespace AspNetODataSample.Web +{ + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + var model = EdmModelBuilder.GetEdmModel(); + config.MapODataServiceRoute("odata", "odata", model); + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/ApplicationInsights.config b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/ApplicationInsights.config new file mode 100644 index 0000000..2e0a49a --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/ApplicationInsights.config @@ -0,0 +1,79 @@ + + + + + + + + + + + search|spider|crawl|Bot|Monitor|AlwaysOn + + + + + + + + + + + + + + + + + + + + + + System.Web.Handlers.TransferRequestHandler + Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.RequestDataHttpHandler + System.Web.StaticFileHandler + System.Web.Handlers.AssemblyResourceLoader + System.Web.Optimization.BundleHandler + System.Web.Script.Services.ScriptHandlerFactory + System.Web.Handlers.TraceHandler + System.Web.Services.Discovery.DiscoveryRequestHandler + System.Web.HttpDebugHandler + + + + + + + + 5 + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/AspNetODataSample.Web.csproj b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/AspNetODataSample.Web.csproj new file mode 100644 index 0000000..b37a554 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/AspNetODataSample.Web.csproj @@ -0,0 +1,204 @@ + + + + + + + Debug + AnyCPU + + + 2.0 + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + AspNetODataSample.Web + AspNetODataSample.Web + v4.6.1 + true + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\..\sln\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + True + + + ..\..\sln\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + True + + + ..\..\sln\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.0.6\lib\net45\Microsoft.AI.Agent.Intercept.dll + True + + + ..\..\sln\packages\Microsoft.ApplicationInsights.DependencyCollector.2.2.0\lib\net45\Microsoft.AI.DependencyCollector.dll + True + + + ..\..\sln\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.2.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll + True + + + ..\..\sln\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.2.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll + True + + + ..\..\sln\packages\Microsoft.ApplicationInsights.Web.2.2.0\lib\net45\Microsoft.AI.Web.dll + True + + + ..\..\sln\packages\Microsoft.ApplicationInsights.WindowsServer.2.2.0\lib\net45\Microsoft.AI.WindowsServer.dll + True + + + ..\..\sln\packages\Microsoft.ApplicationInsights.2.2.0\lib\net46\Microsoft.ApplicationInsights.dll + True + + + ..\..\sln\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll + True + + + + ..\..\sln\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\..\sln\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + ..\..\sln\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll + + + + + + + + + + + + + + + + + + + + + + ..\..\sln\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + + + ..\..\sln\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + + + ..\..\sln\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + + + ..\..\sln\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + + + + + + + + + + + Global.asax + + + + + + + + + + + PreserveNewest + + + Web.config + + + Web.config + + + + + + + + {a6f9775d-f7e2-424e-8363-79644a73038f} + Microsoft.AspNet.OData + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 1652 + / + http://localhost:1652/ + False + False + + + False + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Controllers/TodoItemsController.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Controllers/TodoItemsController.cs new file mode 100644 index 0000000..88848fd --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Controllers/TodoItemsController.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http; +using AspNetODataSample.Web.Models; +using Microsoft.AspNet.OData; + +namespace AspNetODataSample.Web.Controllers +{ + public class TodoItemsController : ODataController + { + private TodoItemContext _db = new TodoItemContext(); + + public TodoItemsController() + { + if (!_db.TodoItems.Any()) + { + foreach (var a in DataSource.GetTodoItems()) + { + _db.TodoItems.Add(a); + } + + _db.SaveChanges(); + } + } + + [EnableQuery] + public IHttpActionResult Get() + { + return Ok(_db.TodoItems); + } + + [EnableQuery] + public IHttpActionResult Get(int key) + { + return Ok(_db.TodoItems.FirstOrDefault(c => c.Id == key)); + } + + [HttpPost] + public IHttpActionResult Post(TodoItem item) + { + _db.TodoItems.Add(item); + _db.SaveChanges(); + return Created(item); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax new file mode 100644 index 0000000..5fa2e1d --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="AspNetODataSample.Web.WebApiApplication" Language="C#" %> diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax.cs new file mode 100644 index 0000000..a75bb47 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Global.asax.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; + +namespace AspNetODataSample.Web +{ + public class WebApiApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + GlobalConfiguration.Configure(WebApiConfig.Register); + } + } +} diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/DataSource.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/DataSource.cs new file mode 100644 index 0000000..dda8357 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/DataSource.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace AspNetODataSample.Web.Models +{ + public static class DataSource + { + private static IList _items = null; + public static IList GetTodoItems() + { + if (_items != null) + { + return _items; + } + + _items = new List + { + new TodoItem + { + Id = 1, + Name = "Walk dog", + IsComplete = true + }, + + new TodoItem + { + Id = 2, + Name = "Coooking", + IsComplete = false + }, + + new TodoItem + { + Id = 3, + Name = "Reading", + IsComplete = false + } + }; + + return _items; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/EdmModelBuilder.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/EdmModelBuilder.cs new file mode 100644 index 0000000..73404ef --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/EdmModelBuilder.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Builder; +using Microsoft.OData.Edm; + +namespace AspNetODataSample.Web.Models +{ + public static class EdmModelBuilder + { + private static IEdmModel _model = null; + + public static IEdmModel GetEdmModel() + { + if (_model == null) + { + var builder = new ODataConventionModelBuilder(); + builder.EntitySet("TodoItems"); + _model = builder.GetEdmModel(); + } + + return _model; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItem.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItem.cs new file mode 100644 index 0000000..a694c28 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItem.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace AspNetODataSample.Web.Models +{ + public class TodoItem + { + public long Id { get; set; } + public string Name { get; set; } + public bool IsComplete { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItemContext.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItemContext.cs new file mode 100644 index 0000000..9883134 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Models/TodoItemContext.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Data.Entity; + +namespace AspNetODataSample.Web.Models +{ + public class TodoItemContext : DbContext + { + public DbSet TodoItems { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Properties/AssemblyInfo.cs b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6c6e473 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AspNetODataSample.Web")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AspNetODataSample.Web")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8d2b5b0e-9ce1-4708-8024-2dbcad51d9ed")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Debug.config b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Debug.config new file mode 100644 index 0000000..2e302f9 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Release.config b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Release.config new file mode 100644 index 0000000..c358444 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.config b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.config new file mode 100644 index 0000000..aa7e606 --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/Web.config @@ -0,0 +1,72 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/packages.config b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/packages.config new file mode 100644 index 0000000..b948d1b --- /dev/null +++ b/ApiAsAService/WebAPI/samples/AspNetODataSample.Web/packages.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/sln/.nuget/NuGet.Config b/ApiAsAService/WebAPI/sln/.nuget/NuGet.Config new file mode 100644 index 0000000..90f1c43 --- /dev/null +++ b/ApiAsAService/WebAPI/sln/.nuget/NuGet.Config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/ApiAsAService/WebAPI/sln/.nuget/NuGet.exe b/ApiAsAService/WebAPI/sln/.nuget/NuGet.exe new file mode 100644 index 0000000000000000000000000000000000000000..ccb2979c5e1d27a7a36bde6e89fd874251462810 GIT binary patch literal 5432400 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

K8-h8J_M4$9YzA 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%0AeWKn)=}+cdj%+qp)?nni%gMH;taGlEzO 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

BV0G`Rm$NTGtKxl z@5*GB@nk?V8u7qB_!4O?2&Ch%FW*U*{guPF?j+S~R2c@&T8M%@|%|!`$-D6_7 zA3$a(YZBM8Ri6O)%vosF2KA`Nj6v%B0VXdUv;ZCx2R-L9=moADmY#Wl#ViBZ{GV=S z;Ze9?I^Vm)JedsnC1LkI)y?LJ{Ppit=6Z2e2f`$A(SEMq7Sxxxiq-BIitM9)6JSc7&}A6!Q# zJy~64hP$noJ_GOgHZa74m>cj#U2F8ox$+#)7Kg;W6vFHEF)nU)W|{^>VWX8~ ztQ!vluM7ClD-Q8^KP(jc+oWtOsc8o&?Q7VI<+VPz@I z*w^bpkJgI1hZANCa0w6wf9#IK#4stw>!t^Efct3uK270%!kZ{Om++cY!h0&b6XE?8-i7ckgw2llRgJr}yK?Jj?-a!fDEs&lO}$?`68R4UN}NOCOQ2#Y zFKQ%nHf9rc;-)Hp-^h$Mp^u#{g12asN47vU)H^8E zCKdG&2QXM4gRfwohw@9xTpy>9Fn8M+30P;feIK~%YCC2Eu#22nt>5Z`dz##{p5=j2DelG{l>;K_ z0P{jF%#l8gILx!6ew>nXo<3Uc&V#is%IZ||8d!@A3{FBj&GgPkQqcRK3Y~?MQ~Ytb=#_IOcXsRDG2Wg> z>3SFQ!iYepf(PxGlVWeFc|63smOwJy((p8-rBX(qL1M+roP&<0mW&XcPNE8lh!rnW zWpAYO%%pcMB7$Xdm^U5+pADqVCQ4OHML3>8j;E0$vEpTF;D{rQ2f$19MzwS##F-2s z%R6GlOP6iz7HKm?C2Hk#u&gq6yu^rK&Rr;D2j&CY0fy}x<}s0?w=1s2m~W8BVTfei znW_Y~ZV2U@sZ9S*@q@`JgGvLQs|GEs)O~DI4QcxnT(h6Pl19M8B3YNmxPXz<`R4HSxOwyE85PonFLN!;e$CS7b zPn&Z<355@}A6Gz|THPPBbf|r*K<9CY_Xf_KYjBuBM8kz&a4th6dwFjdOs@y&;<@+p zNOT4YqyTejMvbj8Y7q*VQUNxYOgpxte)<^Xo zsoo>iyV8Z8fF{);PhQ9sbOW0o4Qr#R0u)e9Sqbv#d_K64>ClB!|1+@9QnrnU_oa>w zvAo8b_CD58yzP!W=Kefw9!BBIKjLXiy(PYP55fNq?vcR18o|TqJq0)Q>mT9EWX?zP zA|K*0-U-tv|4!hkE7=!BQT!_)V3crZ~DQm5AeNZS1XKjfz#OOAC6{1TD*%j2CEy6|1*Hc9KXKC z=Q@k)l?h05Mt`u`e0`)U<1goyhSZ1D9L|CxiPS6R6`i)`x)38J9EPAPmAGf(;J|mjVY8LGdOT-*MI2?vgJYGA=c}olB z9S;{sygDxAyp@QXe+}dO=fu5h7&mUGnuKU_F2?>tQ((!4NN&Zp^OGjAy@GWp8X*&khh_1rT@F=94Op2VC|9MkSs@`4 zqSDi{;o0Y-5gJCvuvOs{-kgW7Ft`%Mosr806TvdTSy+LFvjteKaw5?j!ySX+yrH3) z+slrA6se#I?P*Zy^!}kuz2@EI0HXH?jA=3c{K5A#$x5+2JM-7n1gBR#{@j%UnRBkrAScNOTQ}h!rn$A&9a% z+$Uw5$S!bLcMcClxtJlZWy}|Wp6!TovrfZ08|A}7mkE4BwwDJd!6H9+2UP^gxr98f zBae$I*rXD_#p)(705eIrsEc1q}{=*BuF?QI0nV9}nJ!3(xg z5s_r%;!ZIujf`I8jlAlQS!CrVvf(-Do0*%gKjitc9wwQ5^P=~0e7^uXNv|@GNx&%Y zXJWX88S&|~$n1vMf+$=p+U!b12F!6e4L}~HE;8@22DOIns06%@RaxLY*I(ki+tTn$ogVi; z%w19pQlA$?j*XEII756hni|g#ABg!APAW2y$pJd59f;HqH+;UpCV2}o3JT>f;!|y> zw?9Ct;7#_8Ur_!m3+zh@o_DxXZt6#XW02rh1SSsqoGTF`xK4vX_b6^0x>Q8yzCqo{ zE7HS2Zy}ep)MZ)sVsSfqv#&U14b_1$znk@5&uq)Qm-7;JmscSB|C281@)yGVs>J7#*9BXB~t9nk`YgjZwGWciUsuF``?xaL-0LJ5*V$hNtca~!w7 zhuzF%WVqn{rYfGyh%5Ia4|1!wl>i8qnfCO$Q!(wM`}cG&9yP2b{D>^F=O*ilJH2gQ zbA9%X-Z$5}HX5tUL4`ICXc=5=Ye=j*j-?;3Zom)jm5P)<%9)-rmvLp!`w&woiypSU zTiExhNmf2zDUu?Dj{h(@TilFn4EXw z!dWVMftraTdRZsXjZbcLbFb6)w)e)^@zM7l_olwC(^lVmz}xBH3iHkx&ef)}9yes$ zBghDOXQ=_p%*2|F$x7&4Qu{~h?lGJ5rhyL{(UTynXM_u2brWpEhtn38QMi3nIG=!% zWy*8>+{&E}Uf2g(&Ju+M>tV=Uoa8@(A6cv4p>jy;GZ&x~FwT7oJSw-L6EK%yoHLhm zutOhJpI;$@0`@66-`<4vWrgK_f^b*KtH)UfQyUvnCe`EVxh8Qp3v_?%kN{rk;L1f2 z+ncM(g6J{ z5!Ay9rmSv>Nl`kl{u($kPw!X%n1r%MNjm#u@R%?Orm;gUb}v=h=pl3Up9^M7x_O5^ z<5sn#_^6rME82eT=jA!FhFW1+BDF~w1^vRobKQ%$pz`}#qIiX z=YOsBXC6*Q8aG8AYEava!=QlcK4lx?r0!fE60(1vB6M9Lq4xQ#hn&XX0b0jbx_|7p zT!DI}J-Ub-bNDM+-&k&(*U>IyGlXqjHWrgtXf|D(O=ed_x`NmQ&I~Zo)auod=4}oT zA!$c6^tsmTqV5|P+EWyiT5*VWQgAr&pxvH1T+b=_c({@C;nB{<0aUZ{qYhBii z-EiEgm@tPS%qp@pcTwgMuqItW&s3@7%6q9*LE83qxE5l!Q#QKM`)_i^o!OFZI^XsXV>9m5JEN!_y)Ca9*47X zH{?Z!06i-i#e$V^Yv4wlW+>;F+|xsyi_4oKb0Al33W~EuE(`CQfP5|&k&UZHHatdE z`3dHrnZzJTg&uKIw)R>VFw{>E3b2oP3u2V_2M6&9zNd3ni@#|Y{4a;We>Mz$R&xFP zj~)hp-7xr{4}yn76T zzjzq@W5eJ-7zS^$>yLNIF!;-d!9O+>zZiXI8|Rs$@q<3HTWTrnyt&^3qv92?6Er!F zj}}+C2Mq_7$IXgEKOJwfxRFg}B(a!x=-kB8Oa~24M315$VQ7T}E?_@q1=>?cu{~v8{0wz~c{r%tE?&RXy zI<;4wI&~-mMrZkgW2P@)FE}a>`qPaJ8P&(OPM>J34CLHXbM9$5cXt_MNze(OVq}p* zm&?4(?$VAZT~{Yz*CX<`Q(D^kQr(yi+qv`A$C+;U7EJCgl~=4PHRUv)Yit%bu%3Z# z=Bidaj2Vq}^Zwoqr<)z>d8E&qJ>zcs72<>wvX12IT)pMs{u_Mk?qw%ht&@H6nrLsIQSU71FyH%KO3tK5xSO->@lE-wrx)j#%Vo1w=QgjV* zu`Rp|Ew7{A;F5Q2)U^YB)!wF&vCgy+s$b|To91%Gu(05r9{;<4N*c?nHPe)tbFXf< zYTe7Oo9Ly+HDp?hFb>63=Q#qC5wQ)8W7X9v=K*I(x4Ht;Y32v%7Ho+Di2l#E#I>3A zDR5icSUY&W}WEbUlzpZp2$V2f?s# z^*%&ZC)u0MXXf05HxW8gunr56)b%1`{hS8tqs0>R%I9D-kt>AuJyJmOyDO%cPXLF3{EBNCy>soa6%4v;% z6-7BWtYWp@>!>eqwM**#X2ERxZlkv2Ew7K`3Pk3$33VbBy6vete!JPE+}7k^BY;-E z2ty*Yr^A+1?TC*>5Zm0N0nSGBoPcOst=muTF!!awxqU>6 zzC3HL&SA=6yFL7ZzOwffW!n&}*l&2Z3dKz~lm?ff)xQ3{J;n9!^1fVWWk*16d*h)W z6WZ$8q&M1)^*3{H&TaP@NLbZ-GS7PKqKk88)3M!@APJH-ts8!r(q->yVtiLq2h#|m zg?=0{c2%tYTu=>EjG0}vN2%Xq8cM3a?rs4zmJIGtY+X_et0ECA=h*Dq?EtTB_S7d? z)J7L~p8YjbNy3fNdA$mKXocI}iR#0SDyPv+A>W~mzHk=J=EV14k+Lg3UC>H7e3tc9 zN2$E=3nbU96N)nhLvK8<46pvjI9~0Dq>f#`s)h#<_Z}CcY zjgHyR#=^t4o<6LQw-u?Im}Ay%=BK_9;EZ?Lm5|AQN3<=+jnT zs1NE#C+Hj3kK;rA@=bv!R%u&UeVU)J0HWRP6WfwaxW3-r-0Htsdu6&t&op5+A#poq z=*+SX7j3p7j_Zz82KMi&%gwx2Y%3p82(uyLUJVAe3JLyQKb%%U01E)TT%H_V~fy5dT;I0@lR<|h>%TZ^}5;##q= z=8z#zzs>qM9yn;pq*$xKU-F71!;!$B#B3@<<#`X>dkTM4wJW(55hKX3HE5mX+B%d? zSEVi4kS{vpbM;|kT=mnZ*>r#rN-YL>hyAK#T~Jzc)iS4X*nScH z;LSl-?c(YjGCgwxTW=I>H?ej*aXN=IeP9dBJX{$`%YRn%Ap8A$L%WZ?{>@4EL|Mt8 z+hB4Rt>P9;tO4M@147tK<#z#x5n1TSeB2J6brHn2nv35LEZ?ERwT8FkPGT~~**Ys7 z;{3^~fvzQ+5)<7F87s2xdg*lcr0iI#v)%>Lyalb~bmwB;hAsbG`DeBK8_2IYVandd z_!qE~8r5#2GRXMrS4gkA10u7l%Drva7(oVlD(UnSschk{7C#*LEG^xxN;JMR>F-){ zDkO`&Os^%I-ZG}vWC^CH%shtS0Hs-c%+Kt4(7>=-$g=X0iW3telTc4(pxw;zEW-K{ z)9H@(+Qm95L`v3|+f1;Od=;X79nW(e%j^b0lyizGI)neS_@6^wWgkc|{bRi|;_TiJ zEAVIUJny4=)#~$VTa$lESz_p1vIdA)JX5JyL0}F{D1t%NRE`ZD0-H2^W zPzws5BXzXbj8y9(-|S%dQbN0Ieo(uIcuaP_!7&Mw9W^0aBpSk5GCtx2g^Q% zrE2^i8Qv$iS^&y~Rf?80x48b+F&mmnTq=$th zs0D?~K?+y!(~BaXiryMl+=Imku=PEgZ_h;Pqn*8;9_{_4H=mf-%W6kWP-|pv-5tHd z_r0w;al7n?Cu@;)4mhqPfsyAxC^dG-G4vTRq}Pc~I?Xw(LIY^%iZ%T(pau z(FASi$D(mz&$pZO>rdnHOtY;vG$k#!o8k5T+THYDgQdn!Ig+1kk`&Z}Rz|XAkFrBV z(VlaHT>nVh$3XN)LiR%z{T=pFHS^`6PXg-eu03ddZlRJqLOIChon&rZ?P+wi$)iAr z4*<2$dUN_~>TdPQF`2mv>DWkAPD$9i(?fF<1H;y%M&wi~Rq}Nxr8v~~qXha8KLZ^$ z6)Gg(AbhJ2bL!5Mf?}mKqys$q>2Jx$>mB9f+qmWc`3~Rpf9tx}rKSB#o4VKODz^~M zMSRvj*j(tgX!egHNu2(5YP2VP#rv#Ys28C!T&+APn1%N_M2tt(^Q4k0C6AF>*U%$` z_jUL67HbblV(br8lJ62yA4N==aN3rZ9+q&vM*!pI<9wt&J7u2s5(~5w2{}2KZ1=Q{ zDx$du{JQ5sIh=vyaFB*}Hc*irG1?=ecBahJDh2^%L?vol#WW$U$G}H6 z&lDTRX;c;(JCo1GF#R&rl)w`iT3&x>blxMg$*^X>P;U+qyO%j~eRYmT=|o4g5XLB?DC zbV_aoBaK@5oS@;Mv0IMJWg=spil7$MdWx~L+}J&bd%1AGFTo3HL4G&oTBxEm{U3&; z?)JtWIZ9VFDG6#pElSj4y)oX~=ATk&R9Q0dqU3!k4TRPv0rysF-&A#HTq)HX;PhiJ zw!OdpF`Ux>R#i@ZAViED`8e1$v=?$ROk#Y8h32h$Ko@%oClLo(V(+H z^KI<@RFXqP^Ds~cNUM!#PeLDhHZp$6N5*9|z&l3Jxz!+OP0LC2_xsCj6Vn75pX!Q= z^VBi*Otg2`s+VB{Hx2AvW%4Sv`4eWcp!Db0+Q74@n$udZ+bF>%UO0 z>I)urG=)+72D6+q1nrqNV4*!z!zV~Hg(d62611&?e%{M?!1FlZc^usObf53*9mK=o z}@mRVIFN9{;%;Tz&QqL!le_c`!ok= z%u7B(hD_VC`e$-z4{=lrGz2#CENBv#adIqJ_B_jwIyLuWo53WJ&AabSHDPRNl~8N4=GK-ujM8b)#qVYu&<`@`>}SaK7(QG98%@ znCw0Tb?Y^-!E$yN6C)LscJZ|FXxp3RLF;BxekVsC0D%YHdeB)>vnJk;Xk;T=ex;z= z10>&BE++T$Ef;1F>_2Hja2`v=eAoC>-=7nT&$DyPDB4BoZokmFiPu(40Bsee&T{m1IU1ht@W z9bH(lcA#{MvOD%4MCP?`lY7}C8DsQuQgn~s0cc&CPcyD3u1+-lQv9e;2Mgtaa17MC zO6ITBo?<&Os}Duln1~J${l|=cp?*f5{&&aGZ@eeR`=TcAf?AOA9+>FD#BAeXPH9y1 zC{)$IyW-?DiD2Ixnd?NxI%PpE$nv6kfb2DVstmHPX4*QdH*;a(1|ox~{V}Y{MTe?x znhH#MpVdx5UZ)i7*tMzGUVZNE;=)X!{TQ|N)Ko#sG8!p-3E~(syV=ta|Lo7-8LT}+MXDbFQkG`@KzV9spyn7JQM&!%A)!P6pp2F|oW8IJrJvow>5Bd12KNIQ zt~ng{;0$ICV8WlV;pWlZ^zVmJ4j4#Qis4~?3&}~a;7ec36N?27BfgyruM!?74*(fC z2m-W~dvS-&+1yKhLbf`F0^m~eQ{1RkiD7x5dl}v_rnXV+Kiz`-n-vT)NC6Tg_N^&v* zuH&5oq>q-Is7!LO4(a<8QoZWqdI(h?s~ZPdZ|TNfAv&$k$Q@a z4E`GHLud*WxUe2=Ps#P=BzX* zjPDR_aBC{9#_v7(mu;W(__xImezmdP`5sQ$HtM=-p6n_myz@Cy8rFi)^Yb?O@#k-( zLTT5|7oj$3xQj|tEoRp8&Z8qbP3_#rran6}KSFKTi$=%%adNjr2_y8>1uG_Y4qq1R zL z1NIwgT!S077>_n%c;BgZqP{2l*BRbiXdEg2N7V{tCG8g!+lY>U3*m86ul<@ImVXwE zDON%iU-_@bVKb6Q&-df-1eoUOADpTj=?q+RE&XvyiRc=Ks`4Pbm-69UyUshn<}z{b*wkB{Ll zXsJ(Z_C;CWAsIfmmEUA%}BQHC)Ef$O8tk=sV-{m zs#?8GPO)lRSKPSvJjCjv>%{B=NwNfB$B=eFO35#A>w;3NRjmK&3YvuRMxL22#?e;M ztPgs%-!JP(!*j|(F)UF|t$+RyiP|_|4$`8ZJUWA!1DMYI)RsO(e*BR(YZT8z#pV#z z8_AtdT5rRph7z77&~87_T1;VPQE6HVl`#$4&I=|%Z!^6%Cp%j5I%%e9sn}~0*!~s6 zLB9*OG)Xk+=8{NN%QaN_bDVYuBbQpHWDY`>-U8gmkY3ZqWs`7b=+Xeg(QC=~H)6LC)J|Q#$7ixM2WcYTB!ij5VGA>uIe=;G-IedL@VKfj`bwj$dRHc1 z^$^yyCElkEig?vCq|oWrw+%WY4(esb$ve}po!&S>$%?0HmcBVice))tk$dtnJ?uL{ z7xJ=i2eVtj^>1y2c9!`Kzi}Wf68z@(R-XNkfBZL#Gq<)B7RQX48^=w2wC-_Lvw6Hx zo|(<8*NkoPZ7%MZod_g53iuf3F;=GqgvF?lPNOtGkHdhbr2Z+`l}F~Mes}o8st?~w z=ci{f-z$yk*IV%OAIt1P#RX=pk84J$t2JSD(X)DWY>GOur*(u!DTXoi5ZZ&Q zm>$qaNRQfb22xBmfC}T+bUti;K1~m(TdY+QSfZh8+`bucW-3u{=QEyG&a zP(9xAkPJfFjf24)&(>vmnKS|Oq-171JL~1xx%EG?Gqh$7^_G>gl(W2ud?go+_@|v4 zmEduL$^J|t6NbA#SxeGW9y(2zGx0pEm&=0{1k4x1LWa)%jv)=6Gmi)-;M21)sVzwa zfw|p{Ue1JeTa!Z$+zY8l$1#`j_MStc66cH)m<{w5li9|Q>V+m-3!NX;g)oSQ-XxS| zRgAWKVRvRHA!RcNX=A!tXU7utkDT?{;YRulZ+L=O=ylA6QtLKyM{32-c1D*FEAsX& zk=XeSx9Fp-4|Thg=KhfLDqD;11?Pn2_*)Ob<<7IO7LH7;uOMfn(me3ztqH`XK%W}-)0$pB)>dNCA2JT zXBvZGy4xTb(ZY2HE^YVf3Nl-t>aEJ*L*P?qt-*W0;C+qSx>|LO_$)KjskQ9&HPs#( zABJG6`QY(($nk8nVMhI*=2s`Sc#()tqONz8&byylI$*y(jqCukP@1{)gW+Tllo#(0 z%4Xm2B=O@tW@{fQ?H31g{i5fX?H1*nO|0}yQUZ-pK(+wcT&>_`3UEwN3ktV@Y7^t?HaX_% zC)voMd_7zJ<#UCF@u(n;Q*zYMSmP0NQ4`dH!mXNlqB((7#Mdsca5D0!J(2E7oTWVp zBgF{veT^Wms6O3Bw8p79+QXv#d(jrug2L?}Apr5zAM>&cZuS#40+X@MUwV;zbgsV* zU;Sm%zWS?>Le1+B`owmRsQ#MVs8jt>-?sYe78H*P9Nyrk{wIFY^qyWJAtD=*Hbadk zmYmi^C+58P{q$2*ufm^2`VMj-_0%`z`3pat+%e@-PQFY?@)l07r_53QCePf(J(XyN z-D=W$Unvgy9@gTiSwJDY4fw=QbK+=Y`EKO>)K)$G%n{khz4%KA&}bEs5^9HGQ+ik4 zoin9CvkPyto~p$#CG{BHBoR;d4vW#T(Eg~(^zT49o2tLV7k#UL@QFtyXo~iqd}{yV zC)`P{!dLj|ER71YibcAGVIz{Y)Bkd~O9=laccFm5$}pBXnM;EWoS1M8WGycM@1)tA}q> z_>=L}die?Wk-2cc3r+1tYyTUF+aT`Zb(qLoRTwzG4Z6&(3)jn0O19PGy5*T`C)?o& z4-lU-J_)nCSUr|r!ivwjxMJ8?C29I%MQb+Q6`!nYe}t=-st=1e`*&&Mn%d6KG#)D{ z@QT+Mc$8^y>XhckNgnH!38zajh|vkJ4D5G|p5SuJ5Qco^8e%sJ_&6uOR+Do?+A1 zDlmz7$g2$ZQmsR(O*6^)n5|*uEYhh>2dKI?gD>rWRX$-p$CNbx)o|BV=O=mCD5(sm zS6_z(TWg(7pKj|Db3l)N?VJo|4u_p38264p&NsOom2>4+b-2tZi3&72morJEekS35 zCAao#8))MC6$~`#DBL%>wHOIUQh(~ATZ$X!^2vtAV>RH+g4md#7BmnZf%*F78nVdK ziyE!I3=_3G`KgO1>d&K~G(M3d^kETNQ-lPypzw8&!utrwB78Cz;UkK$mLdq+&`AzT&yfDy)57yfZ!DGg2K0jbY2cA5fbMP9bPs^UwFcZrL?|}r*UB}{i@Pm zPw5M4LE#4=HPb5D+hw0nTCb*QebLiel&19ur6m#dTYYBJ-T8jf$)r4YL{B1%PkP;X zjVP)+*FggP^Xv6#tKXmxbDkUR+gAS)Uj`63G|TL@*NLUdbQADO%gD?hmA>+-X@+uJ zzu6*+3(DsW1;2~n@I%7FQ~We8%1NLm39K&(2x>uLu~^5Xjqx{X?2Q_CqsH5)I17jS zayQLiA=-}oeaiE<0pygw4f&G4ck2_^H{ydWUFC6_QPj#AWR14ir~1R5j~}#+py7+}=b2sw20mb%Y-iAw0tm%Q#*w=vdNA z#5taDst{~z8Qb&iDtGszqRKIS)ZSCPZWqQ>9iJrgh-lBj#CW#AbK+7IWpQt*Bo2ko zA#KJ3m@iC~6qfqUlWYZq8OQ#bf$;Y9MmKU##@0-OQRk*nFZcN-C_hrL=FFp~_Ewso zuekj0a`}H%A8PyE_HC=*!?&5+zx`k5*2q*H~ogOCKZpzs_>T@`DY-R;FhQvzMxuy)K^ z)=aDLRUf3Ya-UnnL! z3(VZD80jSUGfLZoI9uC=FgqbD@r=94;$VL-skSR2by4v3_aOT~1EC@5U>k3ojTW|+ z9jwzu40XCs>%{kyp8isjyNTpwB3avA&dZIuY^0^r#ktMtV!CP5vRlo;(K)2TEbc6Q z+Ui#jIV(TP>1?1)8!pUguT(JW*19?1LFZ_|KcB(P0sN>qrjA}h^x7UU6+VN@tte@} zJ0*G*oQoy1a0QH>cRqzSV86a0b+NKH&S2E#5MUo@JCrO~5)bn!Bx@73aTQ5tbKWbI z-kzj4Ca494=T(ib&LLeXq`ibBs0D>3AcYZrlIy4ip6?P(W=qoXBZ2eiA#%)4%`v-% zGy6H{c6roFA%8b6eA)+YpG);ei@^V^^1gfq)0EvTi?%{jKsZ;)7ZRnxd`HDnc9^-b z#SWy9JP*75X42JhZ0jR@oVaG3%{I{TAu@L5AwA8sERGYQA~?@&(N2WxZ#ir1E#suz zPg#>QAK!Y*87CcA5mBkIR`@YY(4=Y|Q0BeFH1^^>{4D z(EGw#%Gfca;{dZtVYl@Mw%~&3IH+!14{UWjyxK)%KU|e^dcSk9sHZfLr}8fvk!=(a zFf|`=JA|@;Y54%9kOhRjsmP`XY^k|^1J_8(&D6>(ORc=J)XGLwce30xlS%1T*hD)y z1${(WiV2Z2}5PF+*WSWhkBK#des?xg#Ud zv#*?vogH>3{&SRXt&M1!SAK&cWPSd1eXy1y```6PRnVjAzD(sK9#uD38WorqkM}`J zW`r(nJKn%1Qb;`aCvxR*F?kr23ElIRnWd$Yczh4>!Y}C;0F(vTSAZAPrCgKiQm!IO z?Y#tz32H&%SL#wuR#vTfy-Dq3s{n=SHO#)Y15!QPO~VM#mV|? z*`ScV$YrF%Uf(JvvAF&niK^?Au@K_=Q+!b8 zvO->3nk+F`pZ;r!;o6)Su9g^VZ6~M&g%?3;rZFixhcvDr4KIhH*d z#?!JrriW$>D^H%h5LjJMk>?JR{qc3TmummgL@F(F?cq2Xu1UNf1g`+?HoCXl*lzWt zm>fXhoN}_1$W6X{fAmV1+4NLq&nWkW`cF+tDnp%*euZ>C-{e2FQ7gqHJ=%zJafcZ) zOHV%b5L4Dc7ivdQNqx7p)76n4q3>-S#xC-^j^)~cuyWC=)3kGfTl;dS26#IHhLMPmm2sH3UiZaZbZ&VkW=6J)?G0cZF0D}-S^15!Q1{XTWvqw zNoU5~>mU-I^*!*nl45+>YajESf02Ie5EuekiO|E&RM(=NtmAjra-IPbikjj*q%i56QwdwlH(BZljH<&x>zg05LVRLqt zSZBF1svwt8YV7JlQ+;LQn_IR)BlSn^VlSnfWiXcZ2SCF_;z-fAAnQqKf z3vjtKP_9V{$?y^ITIb0!E2)7qtu6s!kmOV^XPJRN`^W!vf=Hzrd z`t32q2@4c^ZZv(TsPYE=XN-I9K`vKW_}uascMI#}lex7*H8A(-uJ>?9>xlCISa3Zma&jfIQ^5L*!`+1B z&CG^-0r+yndv^mEhV}0K`?vb(iDq~HCe`O$G}Fs*!l+N(4>?!Zwxrx*tzo;x+NL(2 z$3R;$)sLL&G*OhS-m*c28s&5+wx~3tdZz)#=}dM+n#btR2^A9MEY*edkzF|}B>K&U zN9$`2qg_+q+(e73c7^YNqKFzn-f!N&VrjL&Ay*v`g676X0b6jmVRnlgpeAdhop>>I3AmUyW$u!6#DLWHzF2B^~C=>)R+0*I%V`W$yQyKF#@uj*D&+KmFln%9Puj?jJ;< z07KnQ(0MPK4Qm!~%i#+38Bnv$F5#V5CwG*E9 zIm68%Fpw;#7I*-sv26H%;C4t`$@l2y;Ng{(rM2ls_GUe7aeg` zL)_qRDUUtQk&zr)gRqIdaayT1QS3%&i{8YN9}`yZ%@S*Ll41*?U{4EIoXbM^i3`~4 zK{DpjoiQ=d_Je2Db1Fg98(ktRnmAT;h&6X}5vW5O>WU;B`T z2|+C={Fdof<10C&XN7dMkOZ}$@UoEZ${{@`q+^66s0D@Jfy6v)L(MZ3WPrw3bFrRR ztYZ~RPzwsL5KETqTkE`uQ9E_!F4dV8=IHJm#U-NnVNn#cp`Y-3F?&xA>6b$Kh>!%e zpzx}Y?#&^+Af)4jB&Y?2KM3i*9MZ3Zbi9xRwV?2tknYbR{aQ$gkOZ}$@JEos>-?aA zp~s|?g@n7alvYvoao(B=OH16o>}VP4Z;dxy(WfcOua0j|rvWNiefDRg(JpR8HUi3sKi5tvOe%7Olsf!3oFO01(fdHS>eX zQB0q#`?uXHyY4? zZif8#Alx9Y-Znw+`V{Hhq4i4R&)$hC)E`xnq!ZgXc(2lVcRI5EOC8_R(njiHc9u=K zS0R|K@xmKwWPdP-@;53%$v&x0>K%=*4W!Kcl&>Y02qTs8en!xiKo)0AtPwW7@hx*qZ+z3fS~tqh z)`O%oi@{=S7R$OQ)G2!Ugcr56YT)iH+gu8xTw2(ct7ZlFV+3@!p9Gr|Ob?*_203R< zd8{p3TBL6Il(=h&<~QS(K;s%TXU+_!F}Ft-G}kOgBF17py(A z6V5_1M(GYsBswqaakf1(6p}LlrN;Ga(O?19ry+s;J#e^KE>wdgXM*hcZ?)gE45s$8 zAA}!)ufozPr_<=sTqEc_Ifs=dZ`TNLh5)?$JF1AuyCwP3@M<1zptCcixNw4S>xz~2 zGn>Vy%j+pn*7Re>0R7K7z&RtuYHL}i-*zwS57aHjwsrt4XgeKFoX6Erv|cp_i358g zgP8-E_AgYA^yh2iRKRaH1-#t)rqV}6tCFeHAbu_!F|AJiL{C-!q&%*D9X=&i)A@KY z+SZd;X&PRI*A`{iX;Ch&geG?|wHV77NYUETRF$uZrjz-o>d>h16+R6|$$OCxv*#RS zUm)nUkpEF-rj-1dtR&}){c(723VfmEiU#T9!A0`UUEH7uoBG?I5lnJ72Xs?i?y(H1 z5H1E~ye{|GRi|7C|F62-BHrB3+miIZ(Ud_UpZ!9zT+aWZ;bwIWUY_$p@>d{Kv$Pz` z_73Pb|7&{xBYHL%X7u*=>Y37OkM@W5x1hIpUe;y(H@eCd@4Yai?Pa#89ra@s>7w?j z#YOEo^Z%5CIkIN)0-3xfLXx9);ylv+G zLUOGGn*J{?<6pFl|2p?)^saYri{1?mxGQvX<1P;KVICFz?UaWb30jM^`J3yTlKshB zX+6`KlJ---V|7fd7Lvaa)=^9zVC?H0(gL=e+ysd(9xM(wG3muk0xft+j)xG$Q!jn9>tpPEoSrP}R6(izEol6nk28c)Nv~(X5%j*>%-6vAf3|T8oqRd47D~+UHhE=P`*%LIuL$st z3r6+0SvGfxL(Rj(X2{d~E^{Uem_`{&YW?z^M2Syw+H8p&4? zO5-PFC7TfMpt^Q9&@n+RDEvcJsnqys4sA!F-6J$XEhzjGw8qbJNIMDXULgr;LE&FQ z`gsm%XCd7uBtb1GEEUo(a!9)f>3$&zYC+-OAcd%?)1*`s<>UcSuxkDwpXvn^lkMvy z`!LUzEx>lWkkOvJ#9SSlYTHq)ZW(Ys2#sfR>FuiYObUWpkV&EOTn=eBA$?6u32H%s z8n>QX{=5;3L&?@8(bFb%kZnND1#D{pQs}TUqMk}8OZxlYr(9~=6D%Et(%P(ZN~8LO zZJ}U*wA7a5SlOe=ub>V6IKRKlA?+!ohs3X-4gG`|epyKEKzg-@2?_-OjTdsU_EM}z z6iZMG3Pt5odo`V9)1@pYCc$6jqV3(}NYI9UoTFdokoFOhNl?&+eoTUc>AiBbU(L1F zr5^u4^_6!9uBY(0#4%s{`{}vw7Fra<+`hGkU?)YI>RbOoKdWXMwjv$DmbiKg? z;^fbeNq)yq^?KD49fJ=>Pq4nWoIIana`$FV1a0Uiv>|}nqXi|aGplW&2Ve4C%t zsU&OP;m77LI zlWRJPqzQvBJlZ%a{fjAHVltF1>hBHd74W z6Cdx4o2TR^<|~*qKTZTDD^KWyMvJ0Tt`*a3_@U}uA^8EoWH$Xos(mmk;ylR~zEmvL z)00^z2!Q zzO|q76R@GrjMCeK$6Ks;KPOD_e!-V`&+<{c=lIrkV%s5Bk?Jbq7~Hl=)?I2m!PO`+ep1b^~N!%)8BNZZU$lMZJgF8~&6 zzvA2M>ll+oqYyS>=z>T}vpM(EJ_Y()(T|}~NPaC6OEk8!&+;N39z@cKu-QVYl-;nm zZ~J|xx$S4OT;(E}N&8*YZVTlU*ZI)V6MtP@(T)o!Laluk;(tc+Iq4mVp)`CbfRb%7 z3~LSR&XfK@U}tG~J0NAdbjMukKyosyxk|F6_~zl&L$59_>KfJXchqZU&@Wr{wtBb+bbkV0wfCmuLO%mSJSs#=dp~vFe?lPPB09HWDSo3E zA(Z4d2%tZCNn)^jaVb3f76@`zeR^=QZWLPM^1H9eHXL}H=5stM9+H=ZOzr)hMLH_3 zy@I3udwx=u3-wnmNcK+tAn@pG{3d_or+=yMyG)Bd23tQ>dHU7JkBG~G`s+e37@q?z zK2Kc5#GkxD=!thw(NNG5$=aXzK`%4;Gv9<7(ox;aq^m=@hD&cc4wjx`&aX1uB6(vJ~3G1Zgb2O{bA zOk1XNVlh#jumk9tj@aw3r?l8_GN*Ln*$)X9i708v{h8FQJ+*fMdzSLA9h~+F(=}J* zDRXrV;Q}z}8a}A5VHWZXPcmCe_cz+2j}rGe;+n4WA>c^Yc{L)UIJ7Ds4*E@3S@pIU zE=1w1sh4}GDDnmZ)gl~5cjIbBEK;u|MeBv=q}2~$$y3W8_VX&EB0WywngDNyUD3X{ zpI1FbY@b39R5!&d4Q5Piicf)8kU*xj?kWH?6yhp}uEv)s&gb7>J<8?jM=mB>_T8Q& zA%x1<^$DnER|=YA*An<)>^jCI8`sx>eIGMud#aC{k+A+Kl21S>O+l-3AeTYWv`-zJ zSI^JV^}J7us9Mo4E$^mbnm!S(jMBBLhNZd=|BDX4OQVIfGNpchBHJndKPvU7S+>UO zLe1qMF>L|lt9%}xqN>rS5-BD>ko+~TE!o?_FGvQKVY?<`wr!Z!gbf%T*)6D5=-1Q+ zyzlFb&c*$!W}Fr1&$3#Ta(O!JS`9Mth1x-sY3GuiAkgJghL#2`Q-(r1QK+OEw5AoB z8@!##R7ob)iDi|ozKtp$P2SRVVvmZ$g%qMji}U!}6{@g`j|nr-h4?bqzZU=f;d3hN zlC2ng(bL~q+lXmqS1DXfkewAL?etRJL;DbGav^l!;W77~VpS?L=}F_I;A$=F6C>%& zIICR#vvb#OAi4?o4xL&0z?U8G4s=4X6N>BK;C**tY5zByeRtUpP)gwv$T#;R){l-% z`|#{M#TL~Ru`BK!>86U^qj zU3Px(n{jD01!tslNoi~?Th=_i#Ez^rA&p1XT~|L&ckGW3p$w*crSwV==ok}buv_M!giEqM8@csb5^Db$0Q z7}c+(+juia^$VgpNmK>3pwOqJ|B^$xMo2wE64Zji6e0aJhjguwCJRYW3kv;0`kx%q z7lqU-Btb1G3@8)Qioh6(m~5#=l#$5sl1Lvhk}3S;iqVl*pd{h_L`U>hv~=KmU$=rdOdfe#%_M9yF-oX?RxDx6akl z1ABcw7PU9Wv_i{FCrW9Ba5?3bDlRiW)-}g;m^!N!z*Hrr(-~b&datPNC8~P`x6Jug zt^zDl1u)B;f;RMHmN}U>Or;)VO47br_ILWL`npzo^~;@4VkeI?d^G-+qj{ZZ_N!6} zYC&O|D%Es;REHL-4h?`1W&mlm6~7Pgd)1NrM1t{SW%mJeE`i+bFC7jFxp(J}hhgh- zM^*!+iO{4Lnh1jfHXqYD*HMMIuxxK$n+mru%D&1pxug%$>hNpR1)CoyGnA((Br5d| zbUEbDR09-uRpIfiq_)1L3X0!#e|tBa*1VJFAd2`G$TED1AKcH$eXF~lfl=TskpHZQG-r9UGXn@87$kf!tREWSCMW`r#O!P{BKmINS)Xjeb&Z?CVcE6OLwD z@Tt{}TJW1%@M+a;TJYO|r=qo)T}spRBc(`uyii+==?&-$TCwkJQsq79>sxS%&?!|? zO7`hSf0huRYe=T;)dw^B)n{ALg|zI@9`0_&+}mQloW*pJnYvc>?G_Q?I}hCCZ)t$C z^^glNwruXu-Y8H)*@*p3;jBgz7!%Zj!ku({+%8wY$Nc|6Fq->Ly&J9>?L}GsLH&X6 z6RJdg^;Wwc)Q-4Spvj)h)6yOAQn^*Ctv%jE&4?fspik5iwGkjH>J*~p1w@6JLT(2V zw~EZt>8cEC)lsUn$}N}9S45-ID!N>dHP&iV%0`T}nGtJm2|5?!9uaGAi8>c_pK;bK zY$K);vmF9nU2c_rF46;{XZ-^AZ{h(e;*Z&PSH0=w&7uAH>d)IkAyJD6kMc9DlSFR` zg+%Sap&AzBpmtD5^y|>c230F4B>FWdD-0i(czavRk3wppJsDE35ZnG?!Oo6Mdr|xp z?cK`PDF4DqJCD0J@CZ~j9$H+8`MB9@XZwEyziBU68OSUHi|x!UW;3@~%o__%>Cu1d zY|0vz%co#_M+B2+scxj&4*x-qemex*)H5kj>)BB(tJ6x;n6Y7Bv-3P_|b}D z3M9O?&hVJpYYpFrOSZeEr&@jC`c(Njt@R_?6KX+i`h&?n;g-W!Xkw-CIMKWMx(mtb zG(fDGTuBV}lxE7;rp3rCT*Jo#m1hlsIS&h)$4mmWE5H@!uo~Y*W6;O{Zp56gsDiz=OTR=@CKXNj(bvUeEyDz z);c%K-~8yv>wMm+eJ8Cg|3v=cMG;9)!&)~r_<7U#SwTJR2(yqZd-Y|oO5q6#kSDw< zP|HS$Cjr&gM5JSaT2NSnI9-)Sdk*b~LR(8{f?80cBNzUZ!q--~pcWL?gpAAKfL!Jq zZ6JiTEUa1`ygY4I`J9IRH8+Q2-NDjU#b1=aWd;NkHk%{3b+&~>!+5d|Oq7-x2?~iu zatpG4c(XV>1R$;jkV%O7;kt719n`l`{gk?C%@@X0YOQ(An7Ehil^19$;b{=W=OgK0 zsrhNzk$BH?OS5O8=lm(y;Tc8A@b7O1r#;a){GT=PXKdITaU>A^RdR63&oraPVQqwo z6)n!oh*4XQ9E90`I&1I3*;Pu`=hHoO9*nZtF&B@9tYitu$p-SZS!<&+AQ|t2M2I&# zd&-k(FaYgjhsVA&2j-D0eZsn(z}2S!w|TN=;kLJ;r`9$i)*MJ$?{vL)C~e+5wR!IZ zFB^k2aM~{+twRn}9whs0D>NUccywV|G!GdN6CRL(!sV zBN5cQL7L6S&fhwQ|8ndGqSaPI5GbzR%iYQFH`p%(2|cd9s^V_}!nzif`sjDu|A%s5 zfAmz_U5M**wLZgam_F(Jzjgz3s86F4RbQmnXAaT?wn_#whr=diFmpJpCxe;8Vcmjh z-f%Jgx8TFCso9c8TZRvii{wF7PGz^V!SP6Uo*D(>9z~g(b65PvXuUNp+M9J`zVX*TyrhZ6+$mRxnklx?KW1MiD z)RDXZ1>FA>kH0o|IE_#88-C&h_FVm~qma(6tv^lQPhcLJ{8n+A^xa<3+a?1wM|-82 zhl9wsbn$-&a%s@GW)Ic8QV0@nyTrA5Bvw~WxUYv(cR#n}J0^MA{bKE1{9Uw@`M|UH zCbq(@*Si<+O>YdZ;Jpd;34Y#Fj%KAc=x4BQ)&rUEK$FgZ)$w}aT84VaY%QWg8bQm+ zHn_Rpoove&0}IxCe!skOR4LgGgjPCz(Hfg(V^q<`oPUr`?KG>QJZG<}*;d~~pHcnl z+;miX0tawqH%v?WTQ9dlv>~kgQ}Itv<8P{nSUuZJ-=(E`7L)xdfiV2>No`Rp{tU** zwTb^PQ0jf8(p>L&ekVn* z(6|1Z^r)MAh~dWOWNL&tRMbE7J&X6sR3Qh>M=*LZzP~S=MTNUw-g3A=t?fd7#uUxs zj$VY`NOq9aE`t}!CQ+u~Y%YKyKjZf_ZlquVwx zkmH@Hm@&USm2^yWjM(T7c+G{8vmJ5Uoe58p#OaS;C-G3G%w0Fq4F1HJ^fB!PKiO@} z6I;UWU%U?Tx0Cx8e}VZ>N4SQ99}}Wsk6w${T&3iTIMY7b_kddZ`jgf4>EqjNO%qL` zKD33uQ<-R@QfFu@+QbCo4Vr1u5h_Bs1#aiG6)|&lAQSWD@2crt*+pWzDM4E)ImDCeYNNq@Obu=)Bo@}AcXK40GO~mI4jk5YR@l1;<3!um zAnoBM7_%SVXj1~kdczQ_E1Kl1f3)^^cGP{bkEa^*07WtU({H_L@)!ualAy_P z1lv0;Nwn_O>uh8j;_0uDtbu-6(SB2_E>dYLBx@R2zu|3u7)u0Q%b@yo5-vKKg=<+kKZpylKYesB1F^L$-vf(?jUlzcn}N)3Ax#d5=S zZHw?K5zf%hz+iFd6girA@QWtjwsGHo4GwUrb59ML#>NA`8#jZ4SrX__9lw9 zH_<=ndlXX!-LD%mLgN_wePRNtcQj@I$oQBlKK56_f?7~*3b3(hBF>ypHVSM(GC+~NG>P%&OQmv@YnQ5V!N6c5@ zhZo61?HIb))8a4#L5-}@WbS(edAH5BZ@eh&vklkgDWtnY^(`jWGir}=Ff*nU2|(X` zrU*@jP#k8-sl{p>)=)VNFc)i&J`A88UhKA6vKe)K)p^qYX!)nl(|$~xa0=N`{ngVf zDu0K@ieKaF;8iaXwBsQ4FmYpb(oXgQV@h^$FI6D#?^ptY>V(>W{ti9V^~Uwa3*!wI zc~s?&wTFyeNQqr?W`-#8LW_uK098E9J1h&49}rzmUm2i`%_S z+_V2L;!d=Gf^=6|Ca$IXU&K8@1;Ah22&9m;@A*`W+5u|c zf?7~rIgcCb=CHOB*13ptOi&A|i_df)F@Kllz9?I^vrbzRoJ?-4)z*?%>o;@23ot_w zEW^!UeAmwK&Efd7GnhFXHYbCb!(r=WFmnLgrLhK}?D?)cbc99vV**ki+RcvDB*}JZ zX@RnPAVOhcg2hew4#F3{N~Ca=el`Ubhm95T5Pqf_4~wAsg9GUQMig8K!~PKg&4s$7 zjp0Lo1*5_S2_ebV#7yRK`;j*Rai-9_1syWzVv)8wtUa7D;`hL3A-i=s0sv+Lv}W6W z#vHr68{VN4w!4^E2!s3NV1DB8ZqR%OYg+O_gKPlAk6)zq!!thuBz+_uhxL~Y6CgQ+ zcYeKC1j5As9uX zVxe5y%qB{&$v`C>+uE=O}WjaFFz+XrxPqx(rpFn>QueV}v!rYLO-^=nDwd1oc8d}aBT z4F~OA^n+VS>&QrJ818Nku)36NEn?g-%*Fu4?N^$Xx&58ZugejYM-v9hqHK4@2Eg<(~Eo~s5cIS785_kb@RcTlqCHrW+u zOyYeA+18I%MIi38E5S>(Hupk^zLkmYVeOb!OvdX1qhPvWc~}n3Nl&l+fI>S>_s*15 zAvx9)Z2CXEjQ=Cc_>Wu0fBZ83WEua$W&9^BL>MTps=BYN9R-b2k zb~7iVjp6zo>i>U6&@|}S4o=6=HF%d}OAiYzu=(;h#!AWe;dw;0y}$bSDb(hp#NmJ+ zTDzwljsZ4o?39xO=ziyn&)twFfTez3E_#G~%wZ9?0;K*t$>pWFE8Em966285ajqj` zvKvir9TX=rPQ!7&`{#DyQ==+VIHkB^3}yS-SV8*OC}`Z&K3GS`pc=X2=kPA z(~>Gqd`m%XE>`xH$UcX`qk*$ zKaaLuK=Yg4pvux2t1NV9A#00sYiCkGV-~2ny4I)qi^FNij1v6>!5EP|M;}!GRhntN zn3vTHa}bBRvPA|nhr_naVCDdpt@Bn>+Z)}E;M^MVPrL%iT1`;aY8$L_ zYcCTwpYF=y^0q~>_AAUo7tJWC88nvN+~&*zOt391hzfgP8*u z_n7+n`;(i*yzVq*ZtVqXAepT)3zsX4;Yxn3>z#&!l@q&s$<2xgp4#z7dcT-mzgtrn zYbpE)Dh2e?S04!O9HY4itYPMU(ZHlOr|s#mUGW8ih!=-zanx={9lE15u`Rh12W&hB zFuBX!oJW%&o{Z;T7mQM4fZWE_wLK#}_#IgRc`==e`f}tt6ui5q@g~(ANN^;!Zxx5-=n6lO`!x%7hI5Lrp$Z?jM7X zL6h&Lz)yOUokAo#8#>|LT345bZE{?0>_uzHw%EQ0YVCBG7!%Zj!bV{CCv^>8;VQ!Y zp%kv1i7r!ww#J;e)K4x$fWld zHG84GeT+H<0)1+7E=0nYjV<0w8r4)G#g(&#OPGf9G)&G`Sd-2JO*$HaQ(QS)xSGpp zA4z{?Re8v={~RN8?c+|OSvQ%fe@h^psc8O6lkBN|T<43Z3~i%h;9`ASeK-}~PT#n` zH6L!OTj0{t+2Gd3$Z%U}R0!eQmV)YjM)TdGc}_}m2N5aOchm=(JLwzOx90;*3tU<{ zH%C(l<7iHbz6PHgS^fIo+Dh2_|E8%}A2>DK+^k&cjMIL&n4Cc#(d$sF^Mf&eam26U z+RTyvznBB|&=Xu!GyZMjg5QOtke^7)U3;ACfwQsN1?>J1cp>O@dnO6swNpvP^^dkB z%KFDuqKT_{|A<}CVkoeU9)4hyw~51(I5g8U8fqVFGT{<^xqDIGJBqLVvTdaLdsKC6 zXCm0}rXttQlJigI{J5NdHRsuKzGcpHRQ!OY>X zgEN>p9QMHsW)6oPlEKX3utPJLIUIIa1~Z4l4$olbaM%$U%p49oGJ~1JVMk>!b2#in z8O$6GJ351z!(ibjDlyikKht-I)b{=#@TAxMDYNiKit`u$jvkZIF$Z*JY`9%?>VzoT zM0GnnrP!WG~R1QsY>nXA_Ekb&aFUEXM@3AiMMyZsN>vWQIjjvSAYu zv7_eeXxe^i9fuz&XbLnusK_KCALI#!G6>E~9R8*l6##{);auu8jV}vQHbYEM=u(7I z=;l*OJ^@zsD{ttL3<=BRl6(@N2gTtxK$B1901`m*=^Q|8+8$4GUJm+uLC80of~Y9_24DF2t!0)j;wYQtYZ+a7_Pv?LJoeuRKwK-KF;AVnQr__4F;H zh0Qvn_WyJoim+=1QwVK1?FQqjbkU`}r;vH3voQ>L-8I>t?`En?jSuJQ$^e+Pr|2-p z1ht^>B|BO+%IJSIK9WP9+C&%Bf(*SA-9q@|E!*RA*wdQWf?AMa4}y1m4sW{fK1;U8 z1ht^BDRonL3v+m@Ht7j!K~7IL311+8n=60mqkc3U_Ujra! z>KKw%L@D_xZq92zLbbOBmujBAynOYDlV0Xdb~HLnWrzxmYA&(Wn~4c(L6#V*<9!OU z3!S5`vX39>D`&BWOm5PwwB{yW>Z{( zHuMuVC&9*vIiy)avek#67G$fBmi@SnLgS=dgf$eQq25JM3kn-hW0Kia_RgV`5k%RZ z0_tM^`y#NB!zI^Bm5l~}4auRqJMSGy`9{zxO;oKUv>A6a=-5G zUFH6_+=b*u+_g(+pEhqPS)b~!bp(xS4hX%7BF%<+{gU0rmN6UhrQxQO>?E4Q01C+> zoOUnJM-K-yPR`Y{H6d3!UiC~+3ktj-#-pbYNasVcO|&BgH4mBdG8Y_n49&8FyA-!J zW|HGbsGKDBdxFwe`K4|8=gYV0tbLWEjfS)K)n)71E!4hj9By6L4yVvdph4eX{U#MQ zxr{{T6iU^NreRMmxm+xilQVTZ9f$2nw!6JjOy=XTbjRI@%F-Q<`gYIKogDp(yliRd zYS6D;0mX17KOI9`6T%Aq^PYw@zq`n+Cd!pCSt(2eVU9z>RbVZhzI>t(xSF8#@AWGo z(bE37Ju{_LNa`@&I3-siYeBd61tN?IYC&Nhh0!=QhqShkt`U-;78JGsDY+I{_mIvn z+mkQiwtF#)aD?mlX?N}WmyF4A2O=83CTQmjYowDmk0#Q@uum{Mn!St5NfzMtJu*^$ zIcIo0n)8OkwGV;Pb~In@vDg-mYJMKx1$$vj_(2&$0B`4wkLGePTRFHMp2h^VpyUR; zb4mr47(Ij*!$$m$sIMx9jpfuKSSiehZf+yvug$)SErO=<;BD&0eR@<1TM;9z%hD6k zfzMVd?_my2qIl~0bds2c>z{eh)un-%8}O67%0!~Ge8DjT^VbEX_6Zi3U2J_bcCA<6 zS*y!Q@WX?ppVX@JPaubj2tv)x*Rb@*L|Z@_+^PADtzw($m!dNX+nTV{pg5wA2C=J~ zaWSvOKv#eF(B1@B+OMOhL_Y`Jn=nYyy=`&hw45~O0IS_7X$opV1Ls>5JuutTcD>vv zoi9KV?F0MMM0hU#=9DMd=F^?DZB&MF__S7wyJ^$PI4gMbi&xa(iWNT*)3j}TL-RS^ z+^DO$FItMU54o-6-pcE&EU%5xTyEA;Ze)>aOi&9l%T!GnPBeS`+lf|kygG-HKMR^v zXu#>gs&s`t)S$UpgjJ)YqlK!?j}%qV7OFq6p{v9y*Spe5&&cYAuW8A!DPLLyS1RpW z8Y_~`2QwQD+G}4(6#ez?lGjb=<}+Vy@eL4*$mY+Y#Was^!T>L zk&>#djFhxM?{d2pjU-pxGgRD~sEr9~K~0+;$HvJ*3fSMWNvb&07qXDB^zjc;rua+Q z*t6+ex$u}JWcbzn7(?7fxNGZL-(FqVgf?ZEm1Xw5*b{V(JxyudUww16?1?euR%a$g zGJ950`z&2jBwRhNb^13sgArhmwZ+!7%>fx#d^1iqMBJ%Xlscd|q5txy7+ZzlD)1h% z3L^B%3N~r13p#0-@-X{!MbmrPp7v6^2{~(@?qhjKL=vv%?pFO4;4(OR zE8p5}{Mfy&v|sbL*LG05V=HGH7_Qv`ibeRcBHXD6+XA1Hw$)1u)g69RM8>MNw%92Q z#;Z&S?=Ah1QMAJ)(#^4&KXZcx+F~qKKLVBNMf@aZ(lJ!e8SlMR z-m99ScglO>q{xvwhG;&JSaTKLll;NI3n_^w#*irXQ@Y|iU8 z(|0zx_cNbx^v3DAMzpDl@vB4}6V!sj_C_L|HCbl&KUavK{!VVI63H&Yx+N3e zyBw&h#1>Viwz{50m1*`CHJbM?P+KQW80c&jVjHEn0fhF}SqbZQZuH=*?@PW0y0hY6 z9RZ=4P90?svlj_x4hDt|-cs)=y7{s;zZ=orwpVp*&1hkJIGp|tkz6T}*ciJt_>Fol z)0-*NcPrC^T2R=5O!tv7`7X_h8S!oXvVsviZlZltW?WU~(Yp zDIKLL3p8VMxo{WLq}VkyS1>DtQgS)mcehVjPzDxv4QbdZ^tUI+B5t!B=TUfzhjB{! zX}5r|o6@i{MyXkIDfBJ}6wwNJM$#&iA&vC?T@^E0SF(GE#W-!V(vRaF#nGf>#<+d( zX5DO@UEQ8sLouN_%nTzcyVX;B@Vd=@Q%if&JZc*(S~u8R#As#-z?{Zw5M@TAO4F22 zdnA=8{l(t93N`jCvlq}YPl{~u5mzt4V^JqXg~DFuZE?AG3h_*=|0BQO;O%LCoC@A6 zr0Jb+XDO1<@D@tW2o22NQ0~<8ep5BHOwa3I9)1M{WCOpCx$`-K0urWDWz{j$_k@e7 z9b{AQOj=EGx7QlEcS$$7Yj>-&AECgcdowylJ+!@gbASizj0|QDV4TYh41@(1NA6SixL7RZGFx2U>E#jJbWwuP|^;z&k;0I+?GlaWcI6XHfTswMs4ZpXutW4|db_urHz9ay&#H^A4uxv@>QB)G3j4z4QuRP{T0<`fZ0bqPd02p5q z0GcI%yIB&r$Cm_v@g)IZd`SQpUlIVumqdk223bkqZk7b@W=Rlfd`VQ|?)$$8Bx)EIrbZWwQm|@ zjk9yn->>N3QglHrDD0-_JGJtt$+P}q-xl0G&yO|=8|UN*9w36>7C}KRDC{AE+cf!U z#C~Pg2DbEWM|#9$Mz*uN!|$OC3*pbCz~Z2BZZ44nmB@FLh@ch}9#iJhd1_B|0sQW( zIuP~(m$m&EfKv4;jrV%h*KFf3m(j$aob1AxZYk^uX4kUavvlC(RW z+LQC);0;2uI;(qV>%-H2q7(Bj@``zvS+#W3abmF1l+*WczmNE}@4*3B8pN@9%CEZr zyhkC)RmfpjyJ?PT{>CSAC36s1wg1D~d%(w4T{127frOsk<8`Wrai1A#T&}dv2shZ3(WuBbr}~3En}Z?Io|& zV|Bj_ucx$fs;oy6sHe61bpmZhr;1&cIBp*Po(&T}V|irtO2H)HRVa7HDpcNcRyp_v zDm>koPh}z%U_()XBGysS*DQ5o+m&xF*c%D&z&v-00&Fho4`1e358#ZQkH*&A#obyw zYz%ixE@!qu8Kle~QTEWauLj`cw-<83l%0!k!*IZcJ%Kyf4@3S*aMi=`Yr z9e*3*bQ4#B&w^m(Px#1qpm$GqWPAFA-4+Dain^t$IvE_=yX44@fX%V2TpzM6gl1## zXFsCb&Gk}}&`zZFp3;(umVMg`QwY`tFFP~LE*i#Whfa@RO>alWR}Gq(!9;f!f}o@0 zPf^CST}X4bqbWPAGJo1zBeS0A+-P3~P`_sPQ#TcC_5ixQ{*HQ#T}g9KASjn>Q1Es) zhAO}QDlVVc>i~c%i885d2L*vS7~O^*H`qgok-t4sb^)X<6V#M_j?$01R_d&FLP+YY zo@a?*jnj`_L)Jaw01tqj62Ziwuu~(LI23kT1QUnCPLE*XP}ms}OdJY3GlGdjVP{1! zaVYHU2qq4|uuzave)!afSWl2q!^|5|gZERxbzLt)oPFmWjCh6pAOh20pz#G$ZXM=)_H z?4}4N4u#zu!Nj4kTOyb^6n1L_6NkcXi(ukV*zFNa916Q5f{8<6cSbOADC{>8OdJZk zD}sqbVRuI`aR3IL(q+6tvw#IsJ)dZ{uY00!;?TIijbP#c4Cjl_0W8uem|H+YUM)u> z<=sjmwY{2iSVvgbs4?49+G`&`Qqg2|)|W6sLugXxR*ts++_3>lx$M)6M$ z&5iYu#&O-^pw9y(_xkUHLl%tWeV@=A34b@0ChDx@FqmdQMSiZUeE*~Y_~i%qS8)Cwr3WiOMj$x9*oB!HYz!&@vz1x_@rpobvvLi2#f_A9w#%~fKSx}@Js^Fob!)Y68J^V$PmpjQ~^RR$tdP-Z&>s$?*m*E~)O?P~}ia^W7q z4E2Y0A)uB0A(l?w2hod>Z-O2w;Up@{-&Qxy(+e)`02jLbgLLnMNYY)oPHsM;$NnAQ z@^&{J@-P!b`#+!-ZNcC!qAzRv9#a|sd!I1g%f-?*_yy_@&f!&=qFgS?$i8b@ZlTAz z?-X7-34;B8IBfFd2J)$ORP96U&pKLt2q_G{`%CB#-AO%QKwSS$>f++gmbecOnJ%F} zlE-sGaA-|6dnJ%F}l&&2R*H3qGaj~_Abbmi&x`h5vx(5%4>!;f|ZZUWh z^`gwUmDf?}VdnawWw1%lPvtx=dzV9RU->6Z0KEZxrhL#v*xBbt(3P(e43Bci$qmKK zgUUnW^!@?7*R8b{e_we0_(t)N_l3Vk6Tmv`-~36NFZhGy*jM}w&6oHQGdI1DGvSi? z4*)WLU=579*Asxsx>g*>KVVNrFmWjCsR$+xz;I9aF~FjG!Z%}HCv*rTeb9M2iX#q< z^Qaf+C`d)(5a{AO6U7mS()nWq69-`KPGTCx5#31?H_za(ZC6-^=AV>+2taNi$z|dt zkt9^1xaVY>7&~g)6N4iW4~H}mk0Ns&j~UMr=k-CZ>J|ZE@5h9U_kUeDNhKSaZzQpA z8G`154D*&oKdSegT5_R*J%o(hDu!8zm*55{CYUoBK&0R2`w1t3m$~5OzrIVHMeO)4 z@#csf>7%D&xOq)kP3c`?&WS_w9T+W{7vo6>w9lat?!}-(zGJ}6;%L6%-z_%5I1Fls z#xhIkJ>ShhG`d@S0TPSO{0>Z#dO0u2Y=?`_Stg8q)L1!GYTX-nJUE1)=vS7M@<|XlJoP=I_)7!`}!tJNxF2Y3=nGDWB zLSeg%pHPt=H-2=;yMl*7Uh$n}mB}-4dXNe7UYCwa0?TBkzdl+6PamFY_{ zF_bB8E!Pp#8`U7Pbj;V-DO&}cl^ibRNGYZ)*AQkF<_G2otXT@0?UZiXY)|kG_~7cW zgf^3rqS=vPUUPIJO?>v?OSv{4rk83P3L`Z=GA#CoGvN3tHk>1 zINAy1d&bkRY08DDk>Z!c=uyOk}mFw zAm}d{r%C7JdAOa$6VAz9lfaz3*i=6s$NPY?hBM79h!if}nt~JwC6NoO?DNfNP|MhN zkX^hrLap1Vtoa}M_|`}#ge5oeL-V;5!K?Vf%>M+;G-W!}mUo%@JNlnu-mF4CUCAKa zNDNPQH^B6bcPWAhygR@exmc({r3|xwfFSjEDf&a>=K->dN-M@^!L#-~^48pi9wZpm zY~H8Om2I)bDP2Lv*%f6*Bk-=I-fTWVjLDDbG4tj{rOudi-hN18tzGP>Mzxy1(>J^> z_X{9svHL8N`5F# ze}b8kU23oL*x#ce$nwB-p<*B4wa#_-LLEcW`=S3k8^7PaRZHy@UPO7>&0rsa4vlEV?c2sV+G`b03}t|q%IH6V&^%-l+%`3~3{_@tg_ zesly&h-xPn%Vx_VS(TpM+(NqO*oT9)0hEjB(J?fk?Rx!q-jJ0$cJo+9-7}F;+Wtcd zgr(nJs9c4e9UQ8WGDw@9*+=ezPw#d(j+m|TD^}G~s-=L9^Q*ZP^eIR{OYs?gCdr%t zI!sPVbOcA*7{_1(VTAPI)a^%Zt`)aO%2gR-s8^MJxWRT9l-f+>{=YZFkZ!UgCB;O!w5T&SPLpDI0~ zcfx3+gXObeT03Rbwk&FztM@W*aYMh07Y+U6z;@oZ`61G@XQFX0_!`P7F>zlWmBEhw z_IP}qRbzo1zPSRsM~7^T{F?c2Wz6n;xLsJflKV=-U`uSMfTac3;oO=@Vt^P+j zFTTq@Z!vup-8ACQovAbNp(TFUm7NGqD_ItJ-r@)Z#;30FsQnm+aFC&J2n-vBZ7_o&V^gaRksJ$wUky!T@=L0>YtdJ8 zTGV!0u~|tC?^=VTh6|}Sj6och9cMB&kMM@5kuH&ub|2CouJj*K`oop}aHX#;*%3M$ zH885zFUR$|tynCgW*3WfRl8V}DKTcUGQkXn$zJ{j;p&HnRCi(AIX&F?Wp(r13%ZG* z#&&hBYgapJ}{oDQQ_kn7CGp?Lnu{DPsRv)d5~XRG{5XP1jRyBnCk-`V9H4_x1MlxzoTk*x0` zH+A~FRME$L-RSbQGYI1x`!4)}J&s-Z8}{ymAJim4F3S9xDLES-5}?Z9s-`oenT(sG zEB&KM|0YL&cM?qZ?!h0>-;=*#?{4@3eL*hD-0abp096Lp0~2-hO@Nz3!7-rk-qPjt zbus<(stoc8YOFzro7kuK`#mh4>W${Oq5XPPDzzD}r|G2%#g;&Dq5-o;o+|V$ft&&; z^bD)>r>SRUhA}IEj$HG}i&HLw*2R?57d$E*Rp-|bL(lQcNSl`m#GzKB!8KYFg=>tT zm1tchwn)aZXmoL%va3VI!2Z*jkkaAcDWs#57kSJC21+ z=+dTj5y_O$#Ms?Ya(?JmMPCro~U6|DY3tKJM}8P+skMA~B`hT~Nw1gsN!Et`TP(rX<+-U=zgT_>n(PmV^^T~*Q& zi?)SRwh3+>7Dj;}wdmA?37BE+GOc>1AhIKQJY`gU4P1G;-hrgud5~1^ z%V0IE^4npt=L#)k?_c3DU!m3Y6hO;-372fRg;ioOy;uz?%JTtsTc+*c=81Sef^SE^ zj<_dEK3_$w?&rx?l}@`926!m?#l1A#*oGAMGH|zK^+*A0htjpPm`ToudzJ$*>p}JG zh-|24%DE*m1^f)b@RrVD$cUYcX`>Mo*O2=)Tsh`?D1oYMq(JHw}j zyTG`pN@`HnPE)zF?!!JdMU%2yD=hYJqyA$eJmsa*PTBuJuBg5%Ymgugvz-?P=lvLF zvJ1m@&NCSN3#+dnNt_~wQ%PEyl(!kwV=cHBAq-hE$^g{M;2O#8Q3=nT8~TxN*Ml<~ zh6c~GWXj_qQ@b3fA{C~+hosE3Ut-#_I`g7eCL?GsQ5pbMWo?kDWZASPGAj}f;GYnj zBY08_?y$?oZlp)K@@xRfF_TPxdHkM)I+eD0#69FdIUuCG+6OqtbCu&kxEUwvmsoN5 zr@TUG#FRs+?c$ba09=7G&OX|(U0=)Aj1FKg^iv=j$>z~^w4!DZ0*v>h{t5agv%ah7 zwxsALAVWRes}Ai&Y>;L)dY&e;7KR>kGEWqwYYQpJB7$IJc2i0PI>tgDp@K3iAsA3x z{wD|G3gxFRPCHXG&6TB4Xt-Om3sNkn=99SA)o)ET)#*B z%I&UPe}Onrx$Xz||D{}+?qbU22g{Y|E++neQLd(fBH^_4)cxf8G<3{YvW~W88dDdnW0V+~G7UYdDf;kuyJmev)`{mCR8Gy9So2o|BwbI0ImdOSJ? zPf?+>IFs*RXy44$6aEf5C|Gwoy6UzmV}oaqzf+U^+#BbIiddd~9gfGs%sxoBQ+mKu z?RBsF+>G648(L%^p@K4pu;Hu@KD5hB)!y(TY#v7-R8UETX6(aSbiO?kkhY|TN5=+s z#6ntcl=5n03%Hw$6noZ;<22R&=B2m=Q*0-TgbKGLQlr*r5aW zZiLGY=2(Zjpmw0~Y0N6Cs`ofaJ87mnXFzsiJ zDQ0wJ;J7eD>xPcb6`ip1UVAshbQ=4VJETtudk;X{#30V;VDJ8Lv&_w55Ku2cPZ&_|ITnU{Ce`r|{CUd;Z+LmNPK2?UEG*KjPE~C$)r4TIyK0fAD(2Oc!sJcj=|<)bE#NRO3gaSNEmE# zr{LOJFcHPvqN#mm32r!{phcsL%94PyP;UHZK+Ykaa+mEnR}amDxVkQ#Q~A0VBa0h1 z!+o%W@%zP68{>_Pv+)PfHp-M+!29R;;el5RR=4^9KQ{-jA7tj{Z`Hu5$- zb^dsJUNmB|Da^iM^HB7+p#tRgIl>(ICi)VCzK2O+t{+wj7`uo5&PwgSdoKY%Qcp1! zVa`OoOv%1;NzSwDQ>)N+o0(um^^co}{6i!*f4S#vF&m`XjOjW#*70o zPB>M^4v4Rs4fk5Z;V}S)@=)OBu6WLZ{nE55HnVsNg^EdnArw=PnLA zJ759WlFHfdG4iCah}{`DRfBLTpK7uxFuibDKY zN`N=DSQW6)+*1~s@KelSL>HdaJ5fo?zp(Z$P|>n+SMovT`Un+NuaK&}=Y`rW;vF?1*a2ej?V<^#ZA=@IvXRSC}OD+?j_vGJjjp!M)e7WfJ=J*X<$9jAP|0=| z{R(uEdeoR-cgqR=6MgznGvY5PoB4d}SKoz;ehVF)!Q)}|Q?P8uLn^h;y&N9H94<>) z5h^Iti{1m7V_V~)^$XZ`HA@|)7vbGCDjap0fQWLQT=S9(XvgKk!QoRSyBveMJZrd% zh6vYD7cL|3gI_G+N>S3X4`C-VBVTwKIhGk&o*5xjP^QL=h`lT2FDvi5UXr966Xp4z zOls6HI|&rJo8ZfKKxsQD?xp`-Tq=CyxVt#nuK?0v^)qm14V;~a;uS0~jkZ6RysreS zBIYfu5mcFj@*Gf(Y!4hiE+Y>@Cx!Q~S0uw+YUBREAE*U#GS0}J{N-`jvGFxHQz;+c`j6c9z7Po}7CxnG?V`+RL}0Sus(RpBWDpPEme;48XI3Uz232_gns?%^5;~ zcMFaK>sM+1?vwnbdMlKUP|jBA)|Z1`J1TlxD59oVGab4;p$pwM$=mIr0$jdD$mCkX zSQz(H7`X0?*|DTA_r&Y2EKC1}b#;i7E#nSTRl!*73)}=AuT~x(a6BI3@kqBQyuA}A z7yc34VlSDOMm6s-AHPfZ5VqS}Dh~qGn$Pym1*N=~i{3+-Oiqe3zbNxi6tg9#Ibi3L zuQHGrgHWe#wkLTxG#Gb|bW_knWrMkhat-oQS9I?uCFQu)l0v0>STgMMbQpPxbMr{V zQzTXLl&vz5@TAUxJWUJ6?W{cEJhtF>z*kjmRWxTy{h6MyDVRG7A=G`30)LIFXuk`}AA zZP^r!gxN1KTIunHtR7#;D)-L#z-@!;jX(xi92jO;kLCm;j*8xUTQAuDBCS&)X8Tv% z6xA=CBVWh-ilKNr746Nv_uW3|&G2F|$nAn*m^#Jxkh$xnUWQe+ukQ5$kXLm>a~4_{ zdiSF+Aarv8$=3dj5YdFJ=$~?}3fIg>SjwZ>$@(Rt4=r(d@4kFaP^Vqt8)P4?XeS`|i9EL!q ziVylkX`4F*8(pcg=(jhpZ(-dqg+<+gm6Y^O5-pk#Eq#(xU2^EwYC|~3Sx`=^(%#tCr z#*g_Wl9E#=NQiwm`^_rSxnW=%`NI%xWS-wfo~Uahwf}etU(AGIu(;4isGtlMEqb!K zb#N7jE?mo_CD?6tQkOXyzY%<;q#NcMZ+FOHaCDKGlxH?*X^YA;D{mwJn)#9nheeH; zn#P64f;k@CG-vghhOe*?QKJPo{NDqt#apr1#uwR+e>Z`?zPg!Ss%efM zp};AI@-+Bxh#@qcU|9|^tSk;OL^6QJ%?)VG_^)U*1sj5oTR@}oB#Hz!V~$3xm!1i4 zDu`Le-YN>N_AitLrU*~5+&<+B>~zcyB$L^c*anU!UOL~)2djYYt(4zfG#uXl9}j@E zh>T^6kms$PpEXfC@WxBZ@R-pUX{zl7Hb_#np2uiFWXFAH$upOuFK4zG7nV&;336JHH zl9H6s-dwJH?EySbiQ{c2eCtqX(>XZ@b~|MuYcweI=OZ4*W7IaxMPR=3Bu*V*BOl|L zxk_m=U97`H0A(F64)6n5DuRhaVT_DC(<*lm+<~-UXZm@TD9%Y8g#wKG28?q|R$7J1 z&0N70u}5nz)1-NCnXuSKE%!yY0^K`7SKpBh&D99ttQK>mg|?Uw`xDfDd3nVii-MX2 z@C^jhQ--xHn0Fv$yEcMNaL;Tgln0HboC-DD=lKTdCN#^T(C z8=f^cOGvGM!{to4giB`Jy*Xr@_pPVJLpRO|%Gi99a4RB81E#tJJyp%Ph> zhxsj}uKU@OAC=*^ZZq52*qjq=c$bd7?DOvtsQ}0tVPp8(n!1(-qnPb(LSF_4$?Xid@bfeL2-`reh7o!4E4o;ye{*_x zI$LY=c={95*%VRx2o;n$4Jp>zy+8{XXfp;PR8Z!01e!TKy`{Djsy%2IhyrQLR*pnR z%)P26(Tg>S&aHAF>X@Ae3?5Wio?GhN_0T9+(O=AW6m!m1`4Pft_bFXSldX`4*HAod zg{<8iyx<-bHobTcY76>W!ci?|OSsa~QqoCFAz~&F<{^~ILRzSpR4ll__9al14G_@T z3V|ly65ozGFutZV99v;=UeQ3>S{GtV032uQSS*9Z*NUx}e@+K7_*t;5n}3GEm%)^Y zY7+uG{ozVoVLLiBoXhynupM^@xeJc)=MQ`DsE~Bgw+Vx zs}NAs*YLf)1Hr<*!q=VPt7M*zJ29TOo$t=nYFEg47McY4*K+Pac}rv71F>MBk<})P zYQxs#f@tHxTsIEa!XxNz%orZ@!hN5l&$^ZpTXb>^xgbhcyzR;}hT6eifmC!BE%#Ez zd|$2u^B7F8aQ8`h(u2tQT6(j_YXLqhhYsWJIaSvw$NY>6`ry7___+=IxO32wWvTno za7ZKldd%7xshEV)lR+}<=;gQ}mmQ%&qT3C%FR&z~`rE@mTMN~0gL1*#jFlO7DgZZ! z@qi}hIkb&vP_Z5WKb5znZ%p-`fZi#rY>QaEtOtvwovAX0^oq@0JAm!1^olJSF;|k| zqi0q0X3+Od>J^E7VVC%0(ONFeJ zNRcyGoX~h4S*>h`Z1fQ-D02onA=8gFcv~7;6!fESvmZ@VmRAEcQ|rH-MY*-B41BrC zosIp2sTT)i4efA)VD@d%y02O0>c3<8O8e@tf1y}Y(#NE94)QWeA|q21b0+vfWsT;X zH-g@Sh>P2eun8Etx96b_a^sxp#yc1@dNHfz+1pn0k&(WzfC!5h^Hi4h!@VB6cDJNQ<&u1^dYC z!J)bqJL&A^i35L}R|#zA1jacO);@)R6E*(vxCu`A3~w5q!MfltAg}qPZ33#O`G+t( zg~Sl3xWjpC65+z8-qBz#9AjRRSjrFP%wqtRxw<$IpX<7{@cS_9O3HBB3Vk;xd#(pe zx&w542>yvpbS&Lr!a$1QPA9~o`0g7O<01UFbaaa=@ffh`&3RUF_8 z`5zm>!~xi}(7cYQygacR>Ziz`XPSR6dH5Sp-1pc8r8H>Y!w5EGcLfqXx(-@c&ix)) za{b!K;1JMyG^W+FIlSJ^fO!)MCLh&ULv;c*h|Mq)$Xr9^i z%NaQf`?%C45|ERC5T;L_tAVh}^1^-`X_mOkW?!^dWec;9^z=49tBoMcp z_5~7GlqFB_{sP__9-4{epN?}zsGz)@>1}4$i<~n=FCvvjxM}VB1-QuzH#}Fb>?{%2 z1G#rRDyUIyc!s;v$^+x79D+Y5f z)xInY?hfXyHmnt_(w^ZzS&uEJG+2aYm~cFs;(J=zU^>D*EoGuQAF3Cmv&0*`$>T+Wg74U}b^{lZ4SxN~Y4k)09K{yCY%pXyCrkD1snbNabNg%_r3he25B z%Hvo~_IARFSt@_*a|EnJK$97c6r%Y+I@nB;h|(Cie|jc6Y}r2@q%g!qu1Di^Qc6AT zsB3#&QrH>#TnPH&mBEk9DZ~Lj03(mo(UEJMaWkX9Y;-IE<>9?ipc#7-9H8XJQ{4NZ zYMb*Q?%HI;j{27gM6po&2o;p>!AE^cOThIc|J+#^t9o2bBv%KZ6#57il(~R2+zau+?63)Oajv=4i9zNr(4FbV z7}oW}fO|HfTS)h;!1Qa_UX6=#dD5Rv`h*I~TqLAxt9ijD#cU8NsDuqnMw>yn3G*XX z8nD1qp;3Ft2NQtgUnE9Mr0t);Yh9VD*j$}Z+5Kq<+1=~O4)-3VTtybB4?K^;v0?$@ zRo-!HixataO)56l6aT~Y1hXs|b@hn4=x83FGRqPE`-} z0-;?F&O)t<(rL!-&(gR|A&sHb3EwjCWzBYIu%hVKalaH?iHOe&9eH*IdZR+m8A6Q9 zpK+`7qazy@>YFS_;P|at=jlEOK`QTxcy)0HL;P}f4PzB{`$$Wkw%Wd9dwDyo_aqj9 zzD)8XuTHJUIt801g+4-S{K&J~D5CdRL>)M{sBbj7x`cN@*reojLhs2#cY?0eInQ>;eacw&Rn3;Fy=`RqM~j8*y7 zLT8Oo%gUF4aHpaqC2;rg0Pdo`KNWBW^&;th;^>|#boq51v@57r;(|)m*7UgkDY-t7 zof4sfGM9n_r1}r0dJr4$Wn@WY!u!wIM^Njy5XxATvjGn;frrqpz=nY-{O*`#nDt>+ zSuY{fE!^JG7-ioU1Dqp!6w_?Rom9sAH!Fcb0k7uLGGR5oNP zq5vXPP`Vp3A2Dm)UdW9Yat@1+P(hh1mAvrtRv1?U7E@ST%Zt1TBg@=^P(exa#Y3LV z&zfOvb-+{RDkb=N%3KYXcRcOgWO}7DFKUQGXgt!TQ`n)Y0s3nkKg=(6E`LQiz?d6P zAZU2r=(-kx^k@Q>kQi-!JXVF~I^fOqLP`77reGV;dWH3F!BM=zvo%^!VuGmP874B@ z(dy8$tVoGPA>L8dTNqe7={?V^xgkj#rjjTjY{w~>v+ybG1b!7E8S_hM z5}Olf8Kc;oS_q(ETGHjyNAuxZNlJ`ZEVw7mZZ9}(&8JNbzc3gs>-NZ3Oy|r|gyR{| zcDcc8`rs`#A**t+3OCYV=i_L=1grO@pb|j|$$d|kZ;Z=)38-_;=z(RnTHpqZ>k_r|QP zO-rZo!FojBj9C+2q1>8`fcAzF43o+TNS)72to#24o^Z_b3k(^}*>vw27=J=Nf03vw z-AJ@)Q=~-8RvFGcij+eN9V45%evDt7-I#)(CY|dFAJe54&P^fLExB&i{u~~bAC1mp*OGUCJ~x9hz%qrSGj`hv%i4!Q#{Lp% zN|>%vf)nLef(jejlxxWiWhice^dxbwUXo5mdJ|`Bj_EJvp1F3yIMkt6`tpfQx&MuA zJ9ndxcd!%dbhomk4hMGuS7Djg=qPbgQGcN4(%kw9A#t9**XUhcBK?{d$@b)#A=W?gpHMzM3 z?WhZYOUM7X^gU9~2b&@x{i$fCd|-;Z?&KG9Zpt|ZaNWa&=m)G1bH^xos?|xJyJGe> zZpCOU9Cq8&(b0V8^m?xoZw}rw=)E4^d@)lcSoIKR0$fDf%(1Z~ z17Q|#I%n-V80?@apT!YYvoZDTrXFSv^M9Df%XAi#xl$^a48+HjTVCXr>b(KUglhhQ zy;O*F3QUJb`^`b@P~|M8mSHO3sPYNx_1q?H6I{h*>_gJt;_}h6_5WZvj)wbFP-h_* z<^f11*4wS;$#O1($TEr08b30C7x+eA{S1Izqx3JBi>~jHIFuyBCj1fiV zc01ztQ|7-}^6$;KWr1-cr2!GNPy}a_5B2K-nbPAhp-$+}LE7Y5<(`(kv(HH@viQb%BO9b1^wFI?Ea5nkj2za~!-Z(7 z_ES$hhs6>mR8YDQ9R{;;aXR~;U46lHWLg-?&SJc}Wm$(h3Z=$OSKot>PiLxQ`h4PQ z8+ZvG&IHdzKKck1lz9lDYa4ojj$okk7>H0onTHu@BQMarI1r(NGLIk-=3>8R*p0og zRfd&em{37^ol$D^be@(9Ynymcj%1Yc$pfK+GLMq(3@=bG16{yCgbK<$hCt|S9%tB} zd0}fY4}=QJl=~@YQXD>uT_9A>(2L|G!wOF*K8tCU)xk-q} zKX+NTh{4K!kM7L(--K6yD;H}btY=8~5~h2pOE>agHh_;Ed?F-Txm-dZ{1uEpe?WNA zRoIU?Ub;6PkA{Fcq1V#0Ghd>7~iqD3U&S~@=)8`Vr`5;^W8R8Y0eJ^We3Uqw8jf-+CA!ffFMI-7y6W*|ZZWu9c9 zExkbJFwiv&M5v(5QwY?Yb#}!bfwa_;7~Z$C*CM1^$D^`S&e8#T9iTWFme>1qd|M(5J=FmgOiTML?0mcycjUte5&pU<{#G1A6k4dcsz>h5*F+ z9xr=f@%KvvQhn1{5GUl=7$VvY%<0YX06D{6t4GfR4>${rB~OTFs@9#MXGg=}!QiEH zhjAr^`c%Myg&_DR`>&M!1os!!RGzWeQ=z{7YoU*;!vOzp5{T#zAFlK{kLStEZ$PioD&tYu z`)iCQm?q!EpLFld{8^M4&f#bi^ca~c@sb;J8o7$b2M9m-7wL>}bZ${Pw<>{=2>~KJ z0*FT%ygN%?z6A}v`BH!gzlGFz$?2z#5$dGu2c8XM+B`)Y1;~eY%pO( zb@S}mI*9*ok)8dVb&k3p%=XNQ$VhaW>jY-X6_;s8vO^q(i1Pe%#Q#p{8AeOVU95sA zH^PjT9E(V69VhcR6UIs*0Cz|L$asR zgE4A%J(X3nPHIj!zgo~bEs{;M)Z0b>t8^ea`xSB%%{TO3(|)p|=7W{_AFZ~|M zn0KF`5k}LNFw43y_iIi0odngokd<}YFoX-oj6WAK%;^x)!ZC0aQm~CYR5ME25AMB= znT~2T7CI01B>d}QI}GhsIEc4!X*egPI<_ARhOtHZprgS7^8^3&=*r9~;ER6T&8pl0fWzGgr)=8MkMWWI&LwLz(Q(p7ZuRGI zxS3RKD*_R}mq3Q++HZaQhRDBD?{qMk`7#AI=dm`~ zUn4y{*S;$DXtaDVR;S4NBb5sgppT5yw#Fyg*t!_d%1wyXN2s9sgfZd2vM!FZ;LL;4 zV85{@;3@D|+Xlhg&CwXjMITq*8k{aT7ISmaP}|ec;Z+_NJ5zlOD&dhymh+OeZ2{qV z#y)XuYc2&BL;8o>YA0KW3_L#O-G2Jod9 z4lU1l4Z>gC0RDIb_&*!KXG|KJ&tEox-`N2EWdpcbVQBiBH-Mkq0Deyc__qz2q0sM&u@LcE6^rtp}*BZd@Z2-@#G&KG78oF?A4etHA=GY#M^lZU3?)d0Rz1Nc=9;IA}*FS*Ll>F(43{%8YuY1N_W{GtK;;s)@? z8^FUUL(`wu06wn)e0~G?GY#NdUl$J9|L0a4nx9n~!224&Uv2d_< z3#gQPTypLmI4#qfmjkl7yuBF{`*7~(xnQhpoKrcMyf3sI*XC3-L7h1jsu~{afHK7P z0+iE72s2%eBFd7?RY2_9!CI}T2bU4dFO*SfL zmH`qEUEwjRJ>cp-05pm@IrdA%PB9fPut+b@ykEL zsYjRq*w*-GeEyA{QVRZt-zKhMQ>t&c8Gg9;Wwz(Tt#?8m!kQCBCg%B;+&w;>4IvUvu`+O8f2LkhQ(0-=I3e`LQkTp4kk7h8(} zQ26#A=uqTc)3y-~BI?-Uh(U^^@sb@Wu~tW?PQfp#=7Ol2dwTiIfbgt$J0W^$RZc)S ze}{&t%0HfjkHLxFnVm=kRz0B7(`Jr`X{G-`U88Ed=fv-$^y=+L$UCc`6zq=5wJd&* z!7o#s4mAs$!Ou|i)H%Nl5BcRE;WT*A4hOY5KKyauNCS$d82*SmNbn5?Bd3VclLZsj ziw_dWlitv$?uWuqJ(k+5%d!ttB+lLvD+dK&lXHIwNnH?iO6;|B(qH0Us&wW!Vgw$r zqZlT8$eCa~KzW24jw>WhE+lP1xUi|P$?l0d;_j)o1^ozL5FWlmeYAI=>J+-RCtZ6d z+|AIG{szA->6|-}zcz49X&5d31tRA3@Vy+p$2mH&xYR1QhVpuLQcn$J?OmYJmR`tH zKW;PPvcY>mP3f6vZf3V%)`DtQaR6mNyc`dw;LIx!m)NA1W=tK z&dy`>E|`@mY|x$*j$1YIEOLo>G)0cx?Fbl#;>LZ3YoOsvO=o4~iDJ1RA@>Hr?=ZRZ z)IxqEJMHQwPvXjjo)FTOpN;phAXnmKs^NH=?|akbI1T8dZcJn(=Vz33by6`I*g- zH(b|SmSFY^j!nGT1Jx=mw@17+2V25DlJx9tI8nQlP6iG^Yes?D&u|{Y9tU{$N(kos zb9pxUJp>S?tlD|!>3D~}mo=n4=66+KphGWKRz*k5GgD7-yx1L`Ew+NDuybApPr90C z(Rg7Yp3MKrYsaZzBI1u?{M96W*!wi&@22smGXAHmHmB2gE#aPubVu3^fa^H!1AaTnTo%Sz;;s2C#)va~IDaWU^QCAT(ct zUjtmW=GzTwUqQBd@@T1RAWXJnR&y3F%S?SfWv>Pq>2-OZgmyFL3A6iU|)!h)gN0*O}-DAr4#O^+dB`KP5qwR^{qL0EB+0dR|mup{? z3&pVn(sTDM^+Z04vi#k7={X~Zi2{QvEeWTCv`R^JD-VA*JqINipeZ+J+s&i+mA0I1Z?}1PuErdE7%s7a zFUpfPc6r`lN#RAYrgn(6(wM z<6JNUw8w&WWp%bgp$3}^N-HbGdY?hdT*xIsXc4mTnhaQV#L?t`#3s9gp3T=i)|O!O zL~O1yUeFRuoD!v(mNUjnF+Y_}ben1heul0ValL49Ha&5d7Cm{vtS_|;{Q@rRjE6PI z%Kemv&Jj3SUK;dMW&;*`saZbW<9B1BLOJs;;!hE88N?g6%qf`O z41-OS(zs>ppUGUNl&$TJN)oMJH$lY814y!uP(hgm=-_aJW04qPP?{;RH$IMeH5&;p z^26=w^_Yw2ygpI)M#nS#MU|hhG;kL9ImlkdO{G@|P6Ln2lAe751iHD^n^I>RoS{7y zC6toyTI~5so#WXIG(U;6xzvKpp-Yq8f$3@N=Tg~HP5`aByo+7Jy$*JAFlu}T&hnk% zz<7Li7MDA@sqf}WQ|@&4Un85|)1r4iP+d7@u&v=pL@e}lpM3KkMS`nmpW5IK<@Ivf)tNvWU^z;UPys0NM)vWBPM$E%7@@m zR@$zJ6|crW2@x8^pP>xbW+#l|Er9Zj7T$^>7h=jiEZ;erH*y5f3O2&>4KY9tC$>!~ z20H_>Jovy(U6{+p#J_t(29*Pp4eoPx%+mXusEc^?9#Jscs-#efHClTyj;a7BRBJ~Pdfo}k&|`hiMZzCCVTFZ2yQ=f)u2Pe z2&AuC30j?)kB2*an?&wiF=<&C$!s zJ%E)Fu5uW_7&7eP_|25;5&Q}5JpMG}Z91ku7Hwp8(8ZIgMI5k@b1H0KD5D+7%t9WZ zn8Z^n+lyC@E;(N(_xd?7qEBue6O1?&S>hetl>u1=^x~mDtUQl8*`w>4CKt3W5se2Nk%9ot19viK!kXM~ zvHqIb;S83SB7^11b1E1yO6`@44OvXQRztk_EqZMgjT z(!rcQOV_(}&4EjrbNM68hNSQ4JDAvsj8Ucwm(=IR*dcU%z9iz#rR(E2fjyM2 zFPYk5bp4d!4yWtxcK|zruD{bY53cdEF${Kelu9Mei10W5A#*Pm0uOTlsUk#jvy2@} zbhC_INOZG|{foLQFcVkkI})*yTPbpc!u}f#*nWpkUoWs)S5OVw&HgA2)Lq!+NVm6} z1L%v+I+2~ac_QnEzM{_e+SFzSM#=*b2`B3$BZ=_VVWUUrL^zcKRFvdpwe{)>y^KM4=rjQ#r}KA9m{cCFdQ?H zHOY^TL#aV)c<79unIX-kA4!Jyl$&-3RU0JPMw9`@ufUsTFHqF^%{3YzYf> zQSC@Vc&0Xjv9<`TS#D}TWlm0@lf{(D*-N_!6*~h9YPTS$EGB9fu&U!|sf6M1P`enA zV$&$t3PzxAbgEGGwl%f4<+7vlrh@9%Jq56=RD&g_u{Y0_mK27Xv!lmNv5$f8W|`Dv zE!3n1^C4C_%~|ZDoXS*pMFhuEGvjK?l%n#IDL?ih9)r7;nWk1@Y^m}uRQ$GF7l-;x zd4)P`M~vm%zIGvaZ_6opxh*4g0B!4M??;PC-A-sSy{H!%=&743zrYkA9!vAVn&`)W z%=+OK(f2H(gHS522O2loU|!|!oG%)P9Pc6jGXR;?|CP{Sc85fuZN40AM|1geSYJOy ziS-dGDA7CU{q}#?lgrkw@S>g@MUt+1iy}(DUP< zgbK=I=(~D`VmS^E4ge+oK^^c73UJUI&fbi%Tb^wX?Jab2o)Eet_j1sl@}Wx{BRxE}4nt4`cL)(Qb0c=@F5vISEYV?9$~b1!xN+ozZRDj zeAbvfo_04G?crdtIcxR?39&O=%tAzGvILY2)(MnOG3N!z4GL00kQA*5X?YYy=YR%M zRPj4OVIoRe_$i_cT*VP5Yfr~4={G7Z#E~H997=Cu@FL20Etap%0pqTEZ;330HV;7e z2I4FTP8J|P5*Ek*>Lc9-Jz7Rgy3nfWsa21+9FM(Y=s>`w3aA?#Jc!d(N zbg@JAm*a!;;LtMJZmGd9LayaYkx){DB>`8BJLMgPmbye|B-AFsnN!wpj%nb6v9g}G zV=$GQBtu!Qd@lm%YYRCgCyy{pQa+u)=8~R$8m&}1uUz?&IFh6Ccnom4STSK?LCgXx zcB%Xd{PQtr`CPmn#|IabkaD4kmZovk&6RJ(v-OVE=y6Xi=bW7R{fU(GvfvU2qVW}N zJ>1LD&ARTkb~5Ejn1DI>jexuQGSy^pPB}Ekp%w##_0E)F1=QxXK{vA7!jViT2C^AZ zy9_<0eH;T}p=&Zw=!O2kQL)wrJ1eN|Pr#G1O)ORpaezDt2vngQO%eDh_$EK4HpGKP zrTbzV;&>4RTuW6SJDrL+2VjNo;S?cdnNb-+Rgob!tQ0DvV@mQZW8TPNwM)RKB1fSU z=J3p6R3R<7@{j)>kD4To6%~b6GGAi92S+v72juN%@IWsEn;eKpJUrhE74lkn8hJ63 zP_w%4j)CTQ$Wkmh*@6=U zCpRuRsU|KtwksbjGnd*&D1Unl1frodV@Ykvb=EEw(J{ZHU@V@Z%Ly|oft*w(O!KrN z={j>lFny1J1&(=WHeinUhw*X6W{5|-7)TD2>$N%82`!5i9vv7tT*AYurUT}<{Gt%r z94cqEzK;*E=M_w2KY2VDf}v`qya-V$&!8OezMj+_owe$7wF?w}Q5+h(V>)|)OnI|MQmzMpW>m=AXBpI`G3BP{Q5ec;KEy(^Eqdgpb{@)6 z{W1q-!FQV5FiFt1o(~>D66-*w@qUSz|8lEhF~#Lp#bO3b-DAXxm%5?d>^g)j=VY*y zwOO)l*s7y+UdHNa8zWqZP1=3Hh2A(CL`DFgsr(DRTO!$D45qwWFR zy2QzTW;}Ow>r1txbks0R@6+K-oykjQ+TNKpM}ksIO70`(WM(gqEQyJfG*tSWnsm&P ze9g?sgD#rpaJgH!9@6A;46g_N2?f~-D}R`Z^sswO%N9K52GPA*9;VzghZdj;#G z%&=Fmk=rYHrGBp<6>wVfQz_TV#t4JHsP{F5NtySNSU0aYredQsn{fYz_vw+}=te#3 z$1W;Q%2sN+>MIS;bV(Ng@L34Z z&2|vdaL5^?h`V{x23mp&;2VN&dcceUFO?`r$3y6%nId)plpM3G!-j^^jR0vL;L0CK z@i39b1Ye&cXPp_Kzo5gl!<4*{y%BieQX(Zwj-Cl*7cEv+#E!NsY9Cqx|@*h2uy4K%xTTCIASR?fjWrP38}Fir(i9CUB)g> zHtNDu-a}n=`NBDp_vwROt`G2=s(h$8!2eDcem`>fkMSEn_*PEZCk}A}BQ+$f{DXm! zAmV-M;(aFZoV~9CPB*koc{bP<)1?{6Px&28RAs6os-`{;D&_C$VdoPzm)a&yi8LIw zFXHW^=0e}B-Hk(crd;T1nj_i^do2K&@)*e0_rDG0mhY#s%l9wG@Si>dRf@)^)Auj? zNxlC)%#_OfxbUr*`}zJ+VLu9R3XAJ^uytj|S5Ye=Yqc@$XIkJp6I=d-xANhW|}`T>N2_*Jp`8{T%$F zs#5vV>HBY@pHo#ApHAODmwrxg9iC3#zYhJJzB)XezQ3FNER~e+u&d$c$Ay1@`Q^v? zKc=4_=U?I@`1x`EHR$KZ`KPnIJ^!A>^W*T_67TtcL;1ajkHZ%~sQ1ricn`lW@dxAM z!e2DNe=@^+_^*lQ$Ay1#fd3kX_wdWJ{P=OxcFNUpZNQzYE2q+bGE!L3ydF$kS!lgKWn}7pFsZ^34ROuw@Lhm!Kv#KR=8{* z^|VO>SglD9@J3W)a?*IqY{rs6PAYSm)!O-4naix!&M!5X)!O-`#A3Fns@y;)SEIjOAWYT{>QET571S=q|BBz~!JPbYq)MX}n~iek-(ToLMq2vGi8ggC`effLr*!hHvr*s&on$mEMSz zPN9XnU_N5}jSv?4o$HZrJUX#4eOO4%JsQW$xK`@stW^0aX3Vc4Q|ODc-LTq2<0u)N zjOsdw?xwjHaP`T%1XPN;7&&tzV{mQ5$viI+m|FnpVeyOaI`}5xl=(N{Ro;Vydp$6r zPlU;OhjU;+nHJVY`~jnzAWYBC;tMqCMWsVBn0!vf%=I-MRMj(L(}HDpRsBpD92Gn` zZJ6k4WY78rB#dxlr{D|<%3w>Bfm&h+J5wTY`ypgh=%O|$WB(2}&#k?SU#X2)YEZET z&|3hxv(_)k(X~2@AeDcyNN^^U5&wn{WW9hHO4~Q-tCO{;-yqg+5vS`0hH_%1$S-7a z(8P;GDd#o-upnKOISR6<{2Ou1$0$)?mSI$C?M2p{`qP&-%=4tKneeL}JziV-3|x%L z+wT#*n@R>;egbq6IeN%{@I3WQwv8=a+xQ#XRHn)w;oI)RruYe(BF@h)%FJW~ z`42d^!4fo%&&H1;c55!RZMqfdZRO~_NqSN=i2ZZM-VmZRPw)_D94{N30XLSe#T4xR zXYPRvE5ktF+zeMCJs5`5chk2=lvG%|3Y}iG4R~)9v5!ze35gb1h`-tkcV8TiP(ew! z)={aPnSq13ougn^SsWgZC&0Zm^R~6i+gr#RjOV;9S#&+fHq0X4k}TA&@e;hB38nz; zBUDi4A1G(YWLri^GqaxpP`lQP^#EfvF&3eMGM^!k`5Yfz=Z=L9u3Ny@HsDKpvD$SW zh2N0^_NEGbgbJ#5y@!90_$={+3d(!|;!w0RKhi#%yLjj#EA6udW|P=HVXHTXSh9te z&&gmxtKNF%x&vpfsaSJw3_d0;w(hWLNsI-mes@-|!6jNZSs%HFv@K=4os;o9iEyiQ zOUEqTvdM#k(B2W%Psnl>W!aAI+b)4mQol0tw!n6jk+~aLMzktw;aupdhe450K%xe{ zab)=GMdYw0g&pzF(A8zkorn>_cL#q{+EjDqcKEamXh;89N{fvTiE_=p8A#5OGLd26 zB~*8uz(Bfvak_diIorcNhNCbO^XO!kx8y_N_9Z1P?Tq$<4o0NyLEps#VGf^m9ekRl zKh4_daJS?#N|OCo4(AVf;Yh>gvV5F`x=&L#UFpF{|CqBxr|0tN# znN@nMDRM}|UW_9`Q2f0JD%hCLuf^eN+<4i49*3I;9m-f{N7@M$GwL`vaWM4Hk&Y@C zY={{A3w0wz<|EK_i5PUOgvLyoTuQ#Cz-A{?Icaq%ZF^K|$9ozF$vz?uRW~E7ZIM=j zvwDTnl)rb*7(}cy7z)O_MZ*z?a)Ul+1fBS0+vl&$4!6}qXE-f3l4+!Siy8rE#t1;T zsz7+2X&cbl33AsxmP~o#+R;uaUSVYDe)KnEb)zyaYh-lmB5Sw2~8#=iP<&)M1f}71;sQn!mV8bP7$S}sq^SvKy&<#{gs~- z#;>@|HdkkM@Mzi+qRfgB2}Ro>h-hyhk=JOzA+Iq^O3v63KzRuylGm=Dymm?Qj8`iW zy8k0jB8U4}(&+!#!!AxlrN#vDKv?~Z=u)Lsaro2{UQX($`C@+CT^4i;z*QjY;dt~VT&pnB3?PE!!{|gVh zICRVI^>uU!dFmDkFGVj3CY)=q|Vu)OxAytZiD=+IbR2~E2Sq9pQ4 zDB4v8F<4%ByqWS^jY)~T2%x+K63Gik-s1l2L|-Bd-T##*k*9ntY4rco!!Ax<`-!~1 z_T)7cB*H3_$;j8P4yVfNM}lG+8R6Eh0q0|{#X!$ z<<$i^&c1F?9boo?FKAc*= zekv%Ykr8g~25=6R*Br{Lb?p2rVtFMr?S_bw$Sa{}Hxk5Pd2I|hj z=U{nND6g+q4Ihl+E074QOeP~=`*S!|URw)_X=H?3yA7O!<#j0Kb@<2o zej3Xwp=oi_)RR|2(QYS*!Sb2~IOMfGlM;CmKzRuylGkCLyx#XE!qEL`Pa@y>SkmZk z^014O*Wn^Btc2qFwF5|mRVI^>uiX(&mDf&!Vj3CY*6s}FV0j%udEIl%c8ACEN@&_$ z5GAQ!2}QfBAO_28H^3pU-Iiwt$lfsMt`%1U7WmXBCi%tUi*VY zSYYAxa{zgrc>A7%Za1XmUc^xhC8sW*S3=(0L$zsU`-H~SJ{=>CzOL>}?6q|smWu#1z|FGXIXJbBFniLlCKGV--mI8|Op3W{lDgj?GS z=U{mqM|o|#{3{Q|@=9ph8loifN+{Z+1Tk1%eSkw=M>8pr7Xg%)Kq7e^@5$>mUm^_M z-{wi=Lmx{T{p}uhaq>Dr-F~x-Cyz~vaydP zjs6Y~yEu8BEbvX^&uQQmG$cq5VOCXWF`aOBo)h~wbALB`6wl6Qz=wH&qE>2#j zh`h#n@;VbF!YY%=$k(0)r^@SWK{1VtaBI(jbFjQlrM$L!>$^%UuY{&O7f}* zf*35X^8tsvE?`n3F9Ikpfkg5;&6C%WzC;+he<@EQ5BXTq=wI5yE>2#ji@cWc}RmC&@8B1$5!grdDn5QF7)IpC1j z6--LxMF8a`kVsxukzvi(el*F0Nk*O?wTZB=Slq+G_ zP+kIw>a2b?OeI|aowGQzF> z4V;7Jbs^<-^98T=#PUjL+Pe@Xkyk>|-Ytm1^126b$m_REO5{ZVyy+S*T(WnXxhgS zC6QM`(f&aYgXQ%E;E>moOiJWM0Oci+NM2Vok{3hwui{DMm%hA6qkmNoyEu8x7kRlr~Yjf`+>{|M(`d0k0)J^bgE17dk4H0`s9lE^EeX#XUL z!SY%FIOO#llM;CmKzRuylGjz9yw3L}!qEMzc@nwZ$C5_>R1dp2d0j2?THTY^^B@sc znM_8$_60aqUVj!8)5r+7_AhV_me)0u*HV)%pAgF{p=n=4ltf+$Mf;K<2FvSZz#*@{ zGAWT40hE_OB6;D0c)Z_Hr_W*N{vUY~dB&F)Y4oq*VHYQ_>qK5_dh&V&B*H3_`Tux( z5BNBW^L>1^d$(64*_I`rWXZM!M)D#TjA~T0=bd-n>9g<5*y5^}0BU`` zEMAPm7HQQh04w!%CH1v9{rGfWUja|`DuM+13OK6Q#G_JQuLBQ#y}_tNUqnz}g7Nfq z)ynim>b~_t8u=-#FY@S1hkSoaUssF1u=U2Tk8gs7nPW7t+=8p#f}{2Iws%e@kE2iN4kk>FWcqFmsH?7FT@;Q0wa>@nRgdNUJ^uSgEh; zsjsPpVR2ty0Z;V_f&}^sII2&@qf%d=0S|qB&ZtCRL{MLX@$_{=NMGGyjgY!;a!4b4 zhkePTZ%WAbxAb+R=xb_7UtfTQnPW7zxav!QT3=s@7vr!+TJ<%+N`2i#eXY@ORGqJ{ zfT#KfK>~dR9M!ktQK_%*fQP=mXH=puBB(FHc>20Iq^}dg8XrPFsP*-ecrgxJq*Xrytkl;n)Yt7JlE?Y_3V5o11PSyN za8$pDN2R`SBpUTqg@@J`5!9DpJbm37($}BD8Xk^%W5>#$k)JDhjYtU$;|V(N#NBzP)nM_c)YlN;p|3hdCHf+Q`Vx$% zue(F~`YK$4NZq%2NF(WuLcIZb^lcIH{VjdnBl_Ahq_3f10sAkwu>XRq>fva8HHa7E zfc+P}hQX=S*S*x&YhRuas3qX3QV0@w1RPbPcvR}E33%vhIHMAM5kY+k#?#lIL;9K* z)(ENlwhC$Fu&^(A^lcsT{VjdnC;HkZq_1YMfc+O-*nh!QEpW8HMu->Vfc+P}M#8Do z*ZtJjQQyxE)>i|bY7~M5`U*IzR`Kxk)$Q9CwlNN#MJE95Yq?SQftBfpRDIk2ss{8v z<%ry0{1-|8b~^n$@WNRI40L3$=K`!&r2xU15fOu179M08A6?^jC;N3M2&YCP#4Nc& zTJ5^8TSv+WNw|OX|MfP})|6eaWM5`;hN%<^98w_8oNE z#a6NdEX*9EvBgzm0P4JV#EWs*BCT2-U}fGvLcQ!acKUU`UILz~6G4K!4>+o^;^F0e zps#VjLto<=mFSBI>Ps-5zWy50S4mq;>b@BvjXWII7kTvU81nrseLX7rni*sq_3}lr<#NyfxZHcYAx}o)Ysa;LtpDK zD$y4a)R$m9eLWV^*V|!@kh*WDkVd`>`;tfBtdQ?->FaUP*Ulk*tqT@rj?viSs`UVB zeWk^Vao8fQ>H=7)uP3Ols_K&h+nIo;!s!GdeFYpngSMPj?viSs;L0AzNU#61$?K zBc$%zEu@h`*q1!|=7fBIOJC23zUGGXwFy|5IYwiPt2PCw^|hIJF%Da#Rht8>)Ym_# zuLDP(TJP&C;HkDikU(DnN42GRRO)Lh;GwUr8I|aZ2?|SJ z87}gaV+rmveFt*90)E-qz_&ZQz|HRpr{CTUZgviwByNM|u_Yb2>ITWIa=!7RjbWR| zTu@lzy3v|yo}lO!=EG^1^8q^wy94gG_kf$-ljtHnoxSMnP3K#%b}pDRI5|vr)Ehx%+_5GrIq& z_itTgT7?6^#S`*C!2R|?aI=c&B0Zh&AR5mBi5T3?^M$A>or4XlZvjeZe~PY3 zLkc-VN@~2%h_!v&VDALD5fGHc=ErrdO9zn~*YS}aP7W851Gv!4POJxvOGTn?|IFS)> zHO|lMPaRYefx9>M)yloGOt0Q&S8t4)>g}_O8IaBRx5OnUsOh+Pp$d+S;)N>MSkqEt z4#k#cLP1jedsJTX_An*ooXK~E7f0o8Le(LZ+hxd0>JPKAl;NYyW>L@_T zG`GpxDi4_IcM1R`E!mQ|>2)-`u#FUZrAxfr9J*_u9MbvIZESmzmp`>~f5NAbd8pAp z>|Wfmjuw&Sa;TiA%nT>hk<(?;=%l{>0Fb~}b4JP(xu_eSo;MpQ{=I6gD=h%Nu zI%&ZgI?Y2-`U84PH|vWLXb4WY&K|?ix($vS+%otr!>VI}*2faq>Nvo>{_A)=-JJMf zpq%jPO3Y{*~efh)B~D(Koo6NE=V{4uUK^0|;5S{NrTLD??tiPBZBRs#;f#l<*^zK4WmHRPiKCUn~1NQVt<(IxiuWg-?)vul9ZnxZmEF`Z@`8?L~SzKcGX& zOZ-~jQDYCp|0DE0)G+g3fMlp)%NMqxrAoIVh6=`8LBl85!4zSkCK6P%?84 zk{#*8K1u96KMbVRW{#>+OTe!tJq>)~>J-3eT%14Pss4dr-5fIkaViiowL4G5WG#z& zG|zyh*V~WncIdZPcIe^#*i((|Pt#=|`A(H1|Dpza2^_LoH;5ARP`KA9bX0d|BB(vI zN}UEVI1v6d1`*&RmcltEG1!`zsC5}{yofx(CK zFC1in|Uc`|W}J;p3?<0Pm7!&B@dXH{@Z>;dO_ zr{U{bm6_|`us6uXd=(UGE1x1$l(9xWBl%`NqWKm)B696u(v*}w^#~e%X9nU?C>Gd= z9>~Tt=Z7Nzk}Bg*zTt(<9$8u;m`vgd86OutmIsDX3!YeG=9sSMt!-cob zhakh*HZ^R#ywn+0D5zDq0derbM&tI3+!$uu_pD(JF@YxhFT;8qi4gcDW5_g+LwC#q zwsXvb&X$SejV!3Ep~Fmk)96A=eT}%BLz(01dw}>hiF5OL>S>x|%}lcpeLH~91o+5t zwn+>rsi-cVj9_QBlMp`wp%(L05%PApIX{^-p_X=?JQX2qt0*6h>MRuS_LN$xAC^Yi z`Vr$p?$uIf(_e$<0K|EqW-NlEia$YYwQ+qOX?v>e7o2c=fox%SenR`zE*srDvN7lr zGXS3A(xIhDICa!!>ZgZc&W@>vBO zP+ra{6YtIt5_<78^2I%G^=V_=&&Hr>$e)&p!;LY>4>a6$+5M%jH1qvzf@L>({wIKg zO=mW=ha(r`P(=P``sV0Lm#6tVgUDW^(ZL?T4&z)XNSz1AM<3{;|LCLD`2lzVz`-WM zA4I~ z>baHI|~L~9a+#WBWJ#XAvij^&4xMA21&+bo^F?!Q zgmG>|UI6%`JzK@Y37~PSdN=_zZnTFJKycE*u}xgf^AkYRw|h7NG_J$L37~OfJe&Z) zePTN70pv^$xtzmv*ca%F4TgijbfiDvvAi0TakZS-?TtV2JeKEOAg#`Ea+(I4DhwNr z*DnH}py9)u4zS0mEz6M)(v3>LZe^1|qrl8@I>oYp=k~amER*?=kk@hq3es&-#f&jYUWof6f??jNom}RqeP?qrK4*>SgUs5$PhA zwGWR-F9|arbTGH021r*UozAlM=7{b#S)DVlzGPukM77E7L}9v?h_tfHoP&KSbRB^X z;|L%+6yd-~^p+&ng{w6@#~BL%*Y*bKX`oMYVFI%k<*X=xcLr_~%eM11G5|y4Bhf(G zbES-KnE4x$;WQ(x4uClSo4oY$|L=u05U*TA;05hFcAVy{uf{Ty(vo0?#e#J`gZ>|Kt+U5iE$*cr_>q_wbE5Wb7#lZag zR)T+hCHVA~WqiDK*JkD!*Im)U|2ONdBL)cPE|#u9ja%uu>nJZ=RVY4nC4yMumae;A zrCCL?R|~;4a8X9}oL{fIwt-*vTHxEA>)__EhtqH005^LhoQic<(~Fktt~Y_g64zgM zy;)Fn3x9&+wfDCG?zeA+o4t+bB0Zhk>D)ob0$YFGm7&#LaJ+Tby8+9(>pcLl?#hR{ z7ta5E-SyAl;t6>l;C}mlxY-AYF4EJ9K(t`pbu~}_QBM(i-Szia;^|M(Em?Q%1H$T~ zJ1w;C%6E6&mEZvDu4|#vm8`oG!F5+YW!;qk@)GN=)1{Gd)??GcbyvPyeM=YzX9rw& zeUJ$e0<61g|5NDiuejk{jBa}O|?)q1}%Ub1W12C*u zcO75B@6j@TfAjdA{%icabyswG&?{E3ytEb`LwK*GJ`T9wegbax??e~rsn=bf1mvx| zJ_T6TU7rRZX*shbZmzpN120*3J*&j)e_3~(1ev++`V6`iw1_MYdfjzt32VLXdUhGk zSy#%h9*636*R@y1@0_x*Tz5UU9EWw+^U83cI0|NN5^7M%x+|*`)?JDB*In0z3>E9H z|A5Zic9ebhIOI~Abicap`cDLsb=PMZTDQS^-L;!x)pJ05>#olO=DO>@@ci%AT`}%_ zf&B4&(S5$e=Q2H+Ftab?El(0Ni*XS`Rf0l@wA4?m*XveN`d)(<&IpC8UMH>d zh9KywHwE#QAn2;M;dtZfe-q}oS^)5ZOz(I&0R*SV%NbWbEB`J?Fi&}pk7)jV2GC*Y z!F17wPw_(g#|0&fXg?52;6Eh5cm>d5Kk{$_0Ee>&4_%Jp;e0H?j0KiH9M1U!@0Oyg z&P}65mG5AJl}3fK(2lANPyYPqKSc;L`x#v4bAl=73p_ksd`Xxx34m~r^D7T0fX038 z;RMjQZ#ruRQ&)uIAnImBg2fF08d3c&SEf4nuU=SJbq6ezX%U{Mr`MJkUB9$ zY^lpcs;WkOH?OvoiDztm9xRA@WZ}hD5x6z!@j&4K;wWBft*nhlJ63&Tc&fPjlHf~{ zPBk15*Wlq@k%e~UTDB{bQ0!i|onxfga@P%ZGp1dj*A40?PB1RVEM+?LBR3qv!dRdg z4~SL^Zq3v1Z3g|c8iW^1ZBvWLHiu)W!9a9$YO3KqU}4~e{x#{Tc#8`e4pJThy81|} zKALUWIO)$W{;6?BrW14(z=LS)*1Uo2>v;_hQuE^&)tC1XqEe>wX|yDtW)L zj3DIa+<|m=vwy17=W#Zqv7CVyXA6kxmpy(ib)JI+K4x`d9ztLr(@4xcVGM^#&Y6&i z=Q%ex_iYE+NEM17NB<7$g#=vMj9Eyy%o+_-1;+}RKVO$rP`da@lzr?i; z&IY|YKISDZ3>9Cq_lji%(0DNnV0@&;Tz7~fm2Z~H`$x}@O z4g?*JkUXSaH4})Y>h1J8Adw4OTZoG30ufUq@FoQ!u8TpOWU*Q~{Ibr5(olrzqLUs< zxZPP|h~bZ-mY9CO7nk*z*(~2q5{W?1P>qMaSwhEBa?FQp&RBRRWj(gKvBnvP zx7zfd5Wm@27>^fkBsu|bzr6_noA^h`K|kWFT)fNQkNDAyw8+CoXsgfp0wjfHh8 zDAy(BdO}Ih1eUx(>eOk@Xb;Lb!@?(-?Gbu1U%tP z0oE)`CADiAq!Tg?nCu2b7wKu)PCX-SfMYw;fr)d?7i!RDbroX>^tHF-Hr1wh)Wp?hc+|p5DE%B5#8Fi@2ckY2L({zlAgPl>ZM7xfu4_Spw#)9h zOaM(Fjq({p*lH_=*qRQ@oZCnqUhc=j;@n4PAsjTPZdBLctu8iK3}!qWxmPgesrkC4 z<(*k}b{oXl4ytYC8CBa+f?hav5wsj=e-cwyg@&are_^Wa;nO&ci>|ntRtDQR)O-hq znE^*iVpFHpBsc4MK0Ct4qK=oF@(B_KWx@q!C5F|gcEU@l6O-Cm0MyR(0x{PJiy&CY zyvWT47X9rmcvM@$B%*dDrHGR1v?FRays9~HLU!GSVX;Sk5$0BMz zL$NcBsNDg|SGGu__-Y1CC?2|OB#mhZnq^(u1EfjZ9J?oj$af937r^QW`l`JF45^B# zeelFp(ky6dKftMpFWSxm0G7+I+L#G`hmqg@K!cyOgyPG#_dZ#|6?%^`S6O?(s|=m0>6TJ9RThwn(na~SYw)|)_$X8Z`e)ud|E zysq7jCDf64SBv1dNy-7l&3Gu+M*+ekkB66}0$?O5(KxF%5ubFQ?Hmn+AE&+p{%FQo z1fIHDjHhe8bqqkxhK|KErjCQ_aXlVzYT_%la{>Tg?~@_mG30e3u;leSJkwHs)ky%s zE22&Y5L3U0>oMyCoSKLs-4XySe_9_gncp1`ex^DFKJ0vD!9Aw_fLEM%9?|)5D&BNQ zIq5A|mYCRBo+M#UbHmD-OH7@v!>TiaQ1AQts4_S@T!L_gEifB}$qzf}h9Y0=&g_{8 zI%0{yX0odvupM3}#t!s8F%!LH>&bS<>sSPBx_Z=8)M2j4o4n)Xze_I{W49GCXo z0ccNiY4=ZZu5ac!!X;l0aXr49(4J;E3J;&ncp^U9w6Fl3j_{^BADpRAc6k^KT!2># z?T7>lzca8_t-262lm6lAcbIVpLY@U)`h4t2Y6{}OcSOuE3^f~H^g8S|-hUF2nRp?T>p>@@_R}4maqj9dP^~sx`~C2qg@@|QsklxV7WfQ z@{@5es7T6SxFn{v^;90ZY%-1mgA1X{0!)K29aU2&gULM1n)Th*8sS_FuJD`|cuK^A z5Kr78viH+-liB$oY|ma!7dIb}{7MaPK=^9HBh|Hds2vI0YG*!DY8O1D{$j0~Yd5Yj z&{e1(uKAjk-@(=83_iJTA^_xves}b60su!ls~z#Y1Zji4*e&2M^Q_EMKFdghQ{|%% zH$5{lGslRmh`Jd1#PSFWbAvO2xnrx15kSMhdUX`WXvU>Fp7VW%AN+^$$ZCrhnedO; zYEwpoj|R3gQa6zjS9A99C`=%Y=4>2S2P|T`XL{)t0CE7glZO*PaL$8BOQ*jsmY+ou z=qN7j%jJ+gdk&USlJ=lzek@vR8Xyd!;pDkoT)7nmzrneUEZj6ljRU~<3WQ@jzoy&H zPn2gQKS`d^{2KCvLBRwKMDuHFz|OBFfO8u-=f?{?EI$rU?CpZ94FN|X=`aK&!nuQW zkNUnYdp<;JkLITW-EQTl@exs6V8~Aa+;3mtiH!bkHG_li6_C@^GfsEZ8NBdDh!4?b z$z0SDu4}Mip2cy&xI}aj0FV7w0XQY9@5{2-qc^gLe zM+Z*!BnAsnHBW1kV3L@t;Tg~hlwDF5VE8olqOKYlzy+@<30o1YX__nuXr+|}b2(h= zaqt5BGKHcD(YoN72&O|oE|x>ovz@(qCIIlj&Gv8tXxuIyP5|K854_EO-~p*p!8$V+ zys(}HH@wa)@8}a`T;P4TM_jvldP?dIVG5S;F#XhXCI-uWwOVpqAyGZ}KNcmk6l zG_iBQ4-4;e`N-bM+B#3b2hc<0=Lg{K0^UV}Jpk%50~r~f{T~_d$)A$KC~XwZen&;OqxlW_K=U$Pp3(e9c*2~UzS*;(L|9y+3A>2x z!QGHd-NQx%ymY%AHD&|8$!)_nLm-Y-H{V?aI>I}k#G3e#h*&3~vqS(E4xE}^;y|-^)jXx=%mu#zQ?!*trfDu&clF{Q;)hOEcFdZ*|kWt0cq9?q^?bYkHhGR!-3jF z7j{q?**`Ja33a1>a8RI?6IVAA70+IQhoXi+wI#i&G|Vm6L^U3Nt{9D%ToYwGt3qxR zPCXtTW*mHiw`W4z+TZ2sF(b*g@>zLC@`uYanmPp{@L4c}DU@d1}r_3p^}ez|+q0O>4I=Hc%k7RxC&aKj+MCS-+S#H2Go6>A)y!51E!5!#Wz zGUxGpluvJbZsmzab0%2bwrqfO-h*^nhklBAiFe1*-$*A+a3&jI^%$Hk`6%(Yc##9n zWTV#;a5&=D?aDK458ZLZ*K)7XY$sr&{tl{HE;?YLo+O%K>n9GD@_8@IXXaESsmU{F z6A3Uu$5yprA4AAkAKh-Q1{_QX3U^U%UFNcSe~Ml*?Sw&`Rs_EGV>8vTPvDX0qpNuk z!q=O(ECgO3|Ym_sU4Z^Z9w`D_Lts2KseXV>1pU=rFKq#@xpb4 z;#1Ech$U{Rozp)wt4Q{rLhvkHSAK7Q=|kX`eGd3`=XtpKf5GXuUx1r^5l)4j(-<$> z7zQ^lfx;5kw{v<~P;?8g!0}d*Uj^K6zXmt^I?+XXI&aWieE?cy>-7Ga*6%J16b`1O0tFC%$RpAkBt$PC{2{=cEZdJEzB>56{j? z0;o?Am$yRsDPV2q^kYc`|C^oD6B3uUbCS3`vn1F#{RIBn&WTWv6#om|OWqh>?+1NS zc&&##aTBUOque1or_b>&wn0A+z_7y3>6r?CUzG9t(&N|vYy3Prr)PzqwsVrS7QRAw zucUqrxZnN;ZuVQEi}ciXPTv9Y?3}&_EOt&m0Fbm8)nVn(&gn;ZiJenbiP!&P=kz>e z=9=n<=*D2@M6tDj9;WIEbW}4mAT$|w!jl>7*p=}7S`Mmsi+0^H@FMWiX zgBeaYHhvhHSAflkR|b9HB8(px3+5;i2`HGJEp(h_j(*uih^kZOc^`9F6OmV>x!yPj z^lvcDM;!=4XAf9(cOL~@Pjw+Hp~C`_^G3}4g8R@H!^s8CSCEQHdPgZb#quY)n8qJ|>*g?OOc zUx=$q0LgAZGe4>0%(f<+WvC6+k+vL{-$)StzR zuGTjfh+VJy#EY)>YGXX=e!zUD^*brwAMo*ZGddNRvS9YUdO2qN}wdV{v?d z!5Hjw@uDkUrR8HPSbVsW^pTsNKE~}#a8p5Mu1~1l^z!u1l_X$bQ9F&(Ej{2-jL*}$ri_0;{*QyYgB3$zFsw5oPkcdFKZB8sb|M10{u(czh&0|c z@xX?)fS#bwM_xE;if9tn&S9OEiQ;0Rf8ank)6@^hG~dS!7|NZJfM^|beajupnuRty z!zXPP`3>q*q}g4k3OCs^p0KLo21jsTvFsL3uL%8+7i)Wwp3X!BOxh`BgJI!} z3i5|ZzKAx1MS4ova@Itc!dVrxkB~Mdg3;5N1Two(I6H@7rAp*Gy>K{^+1(F$Cq%?4N5ewM=A3QinR`>DYA_a0r|w3xxo21rZ)juR1!}1WPIJLX{?2X z>S%QE33qmi*Aw~KPv^~G;{?e1J!EAKc@)F3!Xj`(S5^z&^8L0^7@>LTCx%N3FwX_p zO_LjL5FCvcZb~ED?k*rL^&R~+DZwsk7axc~yc%8|N|t7Sq;VY{j&(!J6*G2&_m3^2tzb(BNn3ZlFI1Z_y!xl1N@W3=Sv*X>G0A?F`)+W z=9&xc;-adg+ezVz^FHwJkp26OO9n--r9WjYOKKin0ewX18*#2fC4FRBwFy(wc1EQ2 zV9A*^zpyMlCS?it`>^zw`Sb9woFhqD`F#dOV=NN3#>xJ(A;4n<>%f0;{kTTx3k4uZB0Bvw7SL zjKpTQ_Gh1lVFld8T;H5UZ z01bJ4B$j|Z*boXYG%#9Ug+x&ys<$Hvm=taY=|wcLSyl+w0fH-8nx508DcYa9(=Sbk zLHy3rewYRaezDG~5y&fJp0W0sx}2G?v^ewz4No$47Po1284C(UsINN-iLz?~SLCbU znD`07E#ZW)&6&N3J+u_tk~yB(3revgGPe->ZYg$TrtbYp?5NBJ#2ySM6kBT|^T&bE z$;_ife_KLbq!L6EZu=EAzU1hU`ZaGL$*34Ba5bC3AfldYF`p%wuKfROVe^ zGO>?K1#HZ;1CyCrhHlF2MD*ch=;4`5iT+C&x;gU((eIa`TQZ*$UG+(6lq007W$Mb% zBQvW2lNn!z9+esKIM7{X=+;a>R%kNGtznzwSK1=WxfFJ9t<^S8(N(}G9LqYuuc0kV zYGs6=i|Pi9luq^?v@rNgqtV+Ey<>oG(&)p1krNAC>fyQ!FD5lhsEcma=o5)P&1X`i z2qK3+)=C&3cy;tR%J(Ac=&&VCST{r)#*|C1nf(Z|6^3DtE3(%RKEdAPht{R^RsjH^J$Ya^mE-=Qdl6DY^lfQEbBBu5a^x&^Ha znh5Fo*tmtTZa2vuG{6?gQP?8c?)b0P*d23o{t72DzU}dbamx_hD3#WE-2|KsGF@}; zK^ZQq_VPN)D_#Lvwi!#VY|s7!j{Bi32W3mj;>tNT{U;nbnkt;*$*tQA|24~Gxbh7j zC^?0)6tmR`$u~eF65`lgVBILxp=>)nh-g`ROW|8$>I2LONkeNg)%LG$&R_kuF-$bF zkYXhXF&b59BxY#9s5Mb&A}YkYHCcGCG`iNNB`vx|XPGG^7+?r_OcwFnEQ%97I6yZQ zJ|vp}y}nQnFR*PW*rcu{wMI8ft5k>(T^*oX3U87+N_0(t9#ME}ASNRV|0X&{Y8=(% z@g7xphiH*R5~`y_+TSH!(i6rf3-1$O9%*ZJ;RE8!MQ^QttY6i61H>t4e&m46Q2h4KiA)#1E6DaWV0e|i`&*-XyU9q6rQqbC4GPuRL0Jg z(Xl**=?=qu{Y{8~`T9+i&C36Yk4XMjJP_wLW>8tN?!?m4`AOk-Z0yFO*=t@h25lk6 zH*8$lLb0*HK~d5Y;G8IFf0h+VFM$yDq>8Ydzc4J@TZZKvg<;t=FDsH>0%0vXV(clc zE!x|Y$z}%LS`(fOoGFB5VB11I?9g#6m2@p=L1ZYqtbK&c6_&M+ls;iu`zV=GEGv?+ zaF@B)!-d~7qE!%4qdS(9u2vKtjE;(=5>n{w;X+?Um;w6eBB_KFVHOmYFiaBhWH*A6 zi+mLv?X$Fv*C}S>l76gzx2K%xY(E;8n9|WWTk*{~nR&CIaEi}oWAfpv;5g5oZ+vVU z#$c{Jc#wt|T zI>c-p8pD*ZbB+(P~1C9Xh2AXhxwq;Pw;5MnG&XHHe1&y zp&5Wri`hDBC5cOvL^#51-PL0ziIcKaS~O7s=6cM+iK{3ns0;H}LRcDWtJ%7@7prg_ zAl5dsbzhHJSQUu1l-Vj~8$V5WwBalQZE0SvYPS6$L|c}cQrgjG+ZpAwuBt2eu4cBK z7oshbx0G+Y*>+($?aKIem~C%_Xv@-8noVQOw$DPelD2?vDXn9+{Su-Tc~?fey4kiF zTBa7aWvY;&y}3-uoo3s1p#T+vR0J4nw#^C!sF0>CK%V`Z?&Z+E%6x5AE0V3pNQ>`W zbAAWUkymXb zNt7A}sygibu20k@>f)#DH|b^5&NE?VjxG}IeMkNQ;rTOjr!(E}5yY&uO*-q5dTy~L z`)9#4wzle7NPuc;ZPW9W05!aITsf+_bqeisOC`XNKRpmmAl775AhSr31 z+~wh0lhR9;q4P|vPO`5H)8Hr7F(?BQ|6waWIk|4`&&G8gp1s9CV zc5fZNgh#ZtKlKDWot^QSxX9dMIfEU>nFLwhw#Pev|4(h_guBjL+DBLz5juGYipX0g z)){?>Z*`XOqt2piVjqVOC?WeeZFv@0x|z$=lCXO+?@rP{U)-}e?|F3Q0D4M0{ zme|BajJ>H?{IH6rmjH_VL@B%Bd<<72uzKqHa|r{h2Hxwrc$FDPFyGOyrRx{P^FZg8 zAMC60&-nGechQ`I`5x>I{i6inchL{^JgXH`FwCvHU;ov0`bEsq z)%Ef+rAEk-2$M{A_;BWsA-FkV?FM6^ItE(v4wrA8c!6rfd+2P2m~kLNP0(`aJ)g9c zkfM-G??8^N6Vxs^>tNWFL41Rq#wlRYkzS8)vL&=>eGw`1@CL?0=W~cM#1H{Y^%xq2 zX6Jp_7&!Pu0E0grR?3OiL);jRu8lwU+qZm`&(1>>`3q2f+>Q0}eqq`BMP=_7m%U%Y z_iPKYH@_?ZFBOoVl=GJd0j_{L^UH(x6?|V2ykE)pE6d)mDto^=cppLjSMWVMlHg?m zjv{zX0A4HLi7=$dUl)Ma6SSO*P@G{naWNmxB~rVz&JxCv&=*XA>#~tu^|$;bh@FK zIIZVZjVn+#Cqd7$exHQ3mDJNSfQ2O_c&8?>Mp>kqrY1KD%vze7%z7D-N}8Io2k4u# zHvnIaqhWM78~!Odq)lVgjqrvqD|~8l*q0d3SMv`0665)@Nhl3VjQAFAfW9sDCaRSj z<<*pCt!u22BZgw?CeU@n)XjLrr_fv}rv5}`YrbtsSD-H>=$;23OBShI#I}Oq>XnO_ zA_y*Pxrp&V#0u+Fl&}O#*e!^oy+}{zLlmyA)k!Q;G|AbDv6Kie*%fm>Vj=t(PBL8w zLdlab7Oe{Nt!3gE<0_kJgrq}YzY_>E12YDLAYN$-GQ zF#Kr43-9w;1y8jc79zH|lPAF2wJ?dKFNX+2Bih1*=iy;PoU7Sc5jQV^_n~qCa2s?sB;!09}NiOY<50%3AGyElCip^b0<7y z6H~-Z=8VSr>Q^?>WU>|YsaI%^SCy~FK&$LR>vE*7f5!c_h5vo6DXd%m}4Im zABvMafsh@<30=4%=}{o`OCZl)V|Y2riDijA;VZ@Dg#q&2R%1btns{c6?VZTFk+J5X zTD2Ur4Mh#b+W2%;;YL19Tl?ADOI9)udT?V{tia({y6^-ihN^KVyAM z!9%ROoiV^#G8WD5hp>yF5Sm@a8)wzTW0>&X7onI(BL789xBrUBl|VBfkD256gE=*x zIVHBRc5h@mC(U86nCMBEo2-n5v2Tmo8?i|I)=1rpj@(%e`iRIqC!LXDGh__F{&^y# zoXI%A-uWZtW|c8*boYU30q1)a z!p=@Wa$Or$u{*`_h<%U_6@wP1_0a}2+&K>WP!>3SzyXPWXFJOVr~wBWdad$Mm_HKF z4iAUJ78y96ZYQg_vzTuQl9B};(rhQPw4`uLSll@VMBdp|@&44Zz|t_{(5AI}-*EdJ zn6;vLSObaZkQ`=b#2l`|G{`i<>O_ zs%+=ziiLHi9yzXiN3Tm{254i@+~%zQat>|>P5ei)+ooA?drh8RaWT`B*0WZ6<%G1J z(n9K_C-g}L?@AMyknLJ-=b()@qq@T5Kw0T5ZCyEI4fS!eE(YaZ03~*HQSEUz8dhRA z8k|dvu0|@c8_zqH@eH*~I!im;`yt}dkL@5@b-kS)bI|3uhQ^N zgfG+ZOv0BFHglYW#MCTeuG1J!Lt<*@5QYj@}g6y%kFy zr*O7p5B3tF_P;D`xbzFP#=Vbueb`WA76=zey4m1bYLcXzpG0l7mYeRw7{k>%w!;Zu zqv0b6Ukk_Gj`_p@(dxda)|kA7j&ohpxr3pd+u=f}mWhMpTj&8I!)_wO+9CsIq_$c| zWXN%r1;g@n@ut>;BOSRJM#DEv-!3?=Joo>KJe$w-bfDuu=-1=t)NX+IL+JlG{s!ZC zq;#Rg&mPB8-AF<*&784S4`6hxoO{Ofjz!t-C#{>Q)`xF5hj-3M^eI`)S&}sa6UWsQ zc*bLDDjqm^T}F8q51=40^YU!L;hv5L8#&;!BI6-|EH#$Ht@Jj4Z8Z)sFTl%jh7CRJ zhIt9D!ebNL&0&pR1o$4su#_gQ-T(ZiOt4m;D4CN z^}fh84arNU?*nYB4FI>R_fXKP+tvGcV7s?ORqMY|)cQz6eN^`wv7u4vG2{RYEjE`9 zd~Y-^Z{r>P47-zP0v(K`Qu=o8I?OVaI21cdBXy>ZXNtSsO?M-t3UomRuwIX8GF;lH z6;OwvYDg2^>kM3-JzYHerk6*n8b_dS$crvGdbX6VNBo@O)*~6H$_@>wNE}pB`%-WC zB5inWnq&63j9KepY#)&pxoZKCdi;t6MWr|I=Dfyj_dPFYh^G5#8K_0|;MSJD1P!;0 z)M9a@S;n(&Oa6`?sFk$jac4tb$a@$P?~agd8A^Lh;x0BJJ5PotH0z|S0b^2@lu21P z&meOJi>nPG7CiJAF`+vVcjrkW+=+M{%$Wm-ldOrzkud%_;JALcRT-W!pnw_+@K2aOuf04$@>#c+x-RPWQtF~!#_{UaIb0?f zj<6+Yx1K62iu`x&WxdRMekbvt2(DgRFdwkn7P!25+&F7C<~DuN0F871KL(DRmDnLgy2mY zCM^Y5z)C%WHzpt~-=)7Q-#&+U@(q@K|EGw2EH##z4w;ha{YAcwC?DHZE^0uR8{-}A zDyL_XpHlToz2j;V5>+{y;vwgUGviT-FXiXC`Y6{cqz3p$aAUsly4xKr89-{UoynhE zD)Z^TB{L_Jzan$U`~Mf2kAxbQip&|&^JdUbQdS#nwK-sv15TkS+(a26|bF*)xC_h^Uaul#nzsoTZOH}yR_@Gdr+mNOiK9h^xitDY(Bsr0=f zrCvCFwuh@{INQP52}h%L>RqUM)Gf=kW{$({8VggOn`J=Cd2F`@t#Huo;`qIYqp`~% zhxFh2LzcT$Uk+tp^Nw@jwFi>)EwhX(yYl5OiN#tOu~n6M@E{6jilKJc zk8P=)UMN=f;#>UCL-{%jGUeT;UFwH$CYZsuFt(=eQP7}wB1#@0aC;PzF_DGDomR4hI zu(j?6TPAK}+>N!+YaxfUX($^qY25+Th4m`RTQ$qu3@LB)bb3(U-0!?xGIY;A!E&Co zbnb~K?@K)?Ib2T{{Q|zUm3=U&VFWu$1oU*)X9OIT*UGduA*1s82*+$KD2j3phmOog z$;lfMKT&e7hz#6IQ2~`Z;k`_xESXsAMn}y=^w9sAhM&LB{;$--at*oALc^+=Q?CLt$!xZ&c?t{@7n zdQA5heIe0q@ax{)U-iKD%ME;QUDXRX0=-PpwN&&RglsR;)0xZ$rn`b{5Xt5W89fU< z6%>O53VKSRb*7Np`W3W8NQY4%h3gIL@77 ztTI>kIi_)uv3ImehORh9yDGgxueX>+Yt@QJJos%hJ;IXVuxV^&rHdNo+Wu^4^G>!+ zvZ7;&>mRDeC18C~1KJ)=EZ9@Z8h;GIQZ@RNc|(u$U-p}&V2Qc0Gmb`4_sPx1gw4FR zC~kCt!QF`0lIcq_EsNJ~Bv^2fDvU_Oi`QuI0?uq}HQ$&qx>NIgQyR7+m(z!r+j(od zZoOq_nH1gZax~dRe&r*t2{lq5i6YxoD^DADR zWuk<1$u}&0Hi?uQTOTci+K#^VNCa@dx2*z%`@PLfxU6v7knrH}?v-XCkfo>RrK5|> z5@lR3QBnLur~%&waKGIt z-2oT2i}ZBl@+)R=MS zNob@tAqQ|Y$`vCCkhS|{A`1`ZAiBdx^Kb?~6piB(h29EL$9d%KMSALV?LuCK?JJ1K z2gLN$#26NUc!vt&2>~%ZCBe06&Y2U185PuP1l06&c&bR(HXych%oKen3e#w6M8f$8 zm6B#N@1vdx>!}Vt-Z?4wF#8<*mig+SWwOheL;db!@t!E`SRvNLKrDLd6wegmnH9uq z2E_E##H$POP8GzH0%Ce<^>}&wsRWu;5ooO-5IrS^?gv2ZrbRvrj%Hw#gfSkm@aAFu zvIHhJ0(B8z6*}oVSH!ip7rebl&v4RbR}ilg5Ysc9^j#{5*A0m2S(5Z=>}wOw>BxS+ zVw@wW4Y7RJ#l}BT*tLS?dSt1Oh@z*NV>^|~#*w#jQdD8LiXiDA2tDKobU7G#&zmU%Ce8}fK%@lJ~DRS}qD_5rYvkH4uu$&UG zq^FLbGaj$v6#fc5F2hGzMDoG_k$_as9|swM^1LpXlS zz6~K#xM4yMbL9fjJWzGfc#pyp>)hJMMsdZ0_Z|q=#rlR9%zF6Rj(K{m*s7U1b~r*e z1hRFpB?1u#zIqBHNBgnU2d4l_8!bK2uhqA7Cd4GOE($rPX1P8rOCvE?$lyyl{ine{ zR@k#blT)e5Mc~|Cq^I=o&UBWhy((y@1+?^ZHUgP953e>(fV|JJZ`vMYI77VA`6m+8 zuJ%OQ3wu{E+knjWqF##hbT$UFYMfLYa~?;q{E@(+E!crndxOf^gfi_@!Dd4e>?3UG z>1+xzbXh2i%-@!Yt;P!E@3YdUH#*OPHH^WXzsu7V5tp=yjQdtFpB~6aPmwWHN@}wE zBH~)z>EjFhr8*yZ-YCA4plC=^>TxC4exR$5rD1(Qv#AAqi{l&c{s8Lmp~B}_N&muM$|+G9NUZZbT(&hET|ycm}FTYqo=b4 z$eb-nyMG1kCZs(?XzA%}1v2KGwnaI{IPyn5UoegN#vRR76cqVcZES+)D=cS+B5YDy zI921rjlbMs#&t%cwkFhJAad6|UawsD;6Te;i|ky_8)spz0&Z_04zA6yMb@KT9F{0& zlXY`BOYAF52Q=PHG0Tmgv zDKkhn4fJ$QL*Irfxg*i|*qC%SKu40o-G;JMi}Jr1QDkSqTVEh7tG4;0C{10o-`<(w za8-`$EDK*sO<*rhP=|v264dB z9d}g`Pxxs+up;f7c_OtJ>FI1kk>gtA!a)_}n+N3d)a2N+;|}?uC|D4>FKf$-PN8c8 zDJj$$W&c9*H8>PES61y_5DO{%5W|jChDefnsK(H>rJ;R9^+u-aT&Y3rhH8UlHb9&t4CFK0|%d(MV`5u7GMo%%A-e zTeT3l)BvHIojU9S(EZ=(t_3%nQB$si=+t7#J&Be#-NiY#+R{5rCsXB?v#>KGpqm1{ zM&+Oq=UoaKoV9$nbMhuBjG+LABh@ zS%VYJ@zQm7m1`Pat7-Iy2pG`Lfv?3PTyu_fq7dC=wP{80vONBtR@ zDBmyijw$^qgN<6YiOrHJTc`A<4Ab3|!4agbc?`js&zeL6sv|HK>hS2e<+OzEdUUfP zI(^$J3agecYQU%mwxcMoCm?=A%{@?@8MOc%i=TlacMmYyPCKD*k@f9(Uvs&U*L8Xo zQM4hSZIpaBX!Zpx`aiAhuDB<%{3#*uGK9Lj7AQ!AHcUW%7tGjaQ7O) zJGYs1^INOy&y6MBp~#qQHZ}g8K*v{8sR1t9w~e0+%oV#*>rwlkwAq*96u9G z3iu;Hmov`$U}5WAS8vVKhqXetCd^XS_$?Gc=NkfWP(GO8Mj96$pcKZd%8=hU-xK;&2`@X|T?0yQ5sw^79Qv4>rtE zTIzjVS9}={yIV|Q{*A6ecP}m2o7oja$E^bBMcDu9y#jIA^2i6m(mqy7g)VP(#%$!u z(4aj0N_oR!A8tvL!sTS*-0q3_A3kqM2G@ZVU+S3K5xlr1)TsfYI@a|nL`MOLsS#+V zU^T=1?|Ku)BAV1|hs^i`Tn zuL?4}MqOXZ-B6z}VP9B{^y;!cob*_usHFR3;cucOcX zVu{OZ$DKdx>$ym%i0!$TRHKEe-477?o*>(OO_u(W66O}y|PGT$w5%P`o7%4 zYdG1dS2jjkg;xn%+vzE(*AFC(7!)@Oc_>36cSH@dJL&7ihR#L|A%nT~S=3Mu&v0sy z;p6{}3wNhT->uql|z9pmaS0Cf2{@y*q|pf#D^~{* zV=bs`JkrIsK}NkpFjR8eh-+?Le%yu`sj=Jazwyo+UdL3l-Msp+wA*rQI_tWWTigFU zBi+!`iS*fR#P+<7kE!XW5x3no|BL$3J+m!>TP|CBjh-9erP=hPFzQTSEmR-&J-6|O zqQAC#Pd~7$XE92S-8R1SfN4F=pt9RY(8I&o(u*X(@4v2^+4F?(I_1dPoqC=Zkb?Fs zVdOJ}>TkuR*$sCR!3&F)DmR8%hc7*_=XVmrhes?usONM6NzijXz;u&@y)3`<;GRL^ zwcgQ7Gd)cLl4H-Rr20aV`{j+x7rShCJ7(!2JtKr_-VMu(J!^^JB1@?(2`FCdjIT1x*>sE+?f`wOndBOm|yd{^Vo+_C%wSL0J}Uw(Yg4x(0K zdoonbTnW3^UCU4G=@YNR4!^UnCnh41O-}~}=NE0eZTU{yJ?08NcIk_k_52`O`fP_E z{?c=?h(rR>QaUaHX8vyU(>;TQ*W-V0f39b`fE2W6G}*o>Iq=Bb6`#8rAGFJgPuv*Z z+iAr|J-drYxt&*h+_P9f67*oP!bmTdu$Sz*;?tg?;e` z?-S%pKJs89uNLH|K5}0omkQGQ+!JXok&6Y{=p$zm$&zGd#`?&uiQGw$T|ROnA~zId z6}E`#WUfKvctLLMQ+5#9B*>k8WF3(>?gkO<;UnWjegenLC?EL?v*`sv=6&QhL_R3U zlYAuh)f?&S1$nlQe4EJg1bMlSe2K{81$n)Xe1^z{g5(kVIz^8Xxr-q0@+t2lauY$m z;3Kala!o;g?IV{FIYN;2UwAy15E&EX8b0zkBENuRX14Z`ipW<4xu1{Ro5;Tk@}cg$hC-MH83-;_{dHo4-w>#K5`h5a|M~g@$On7HAHS9$hCds zw=DDP2y#0g`3aG2f;`AazCdKPAW!y@PZ9YI95b`RN8Ux`8-i@aor5~|n}~c|kUc)~ z0wQk{_nEM}%9Dj5OzTX6AJtiRqJ(-UE&$2Zr{hcM_PV z`u3)`6qu*#_NF`i|tFwYC_O}{2E4+-v-v8g2|1NWvM5j>9r?oHn%FpmK4O<;z$)U-;>FEOVSmxgJdIAp> zc&xzn0=Ej>ATVbZmYn76jZK?~FCCj={TuUxv5nKvV`Aj+9_<7gFvjAV0>|C_>Fa3z zf+0$lMqR}R0gdzw40*IOPBYR6(1s4A)d$ozXY_%!EpqTQ50d5m1+D?&I%5jm5rt~YQ zF$$*_A}IO7UchTCN7wx@#hfr>{r1s7Ws4+kuplol#GZ?f#|d{#yG1xn&i3y2m}&#- zuEr!8<7u-nj^Q)^T`E|tS6liC9d++=2QFK>&@B0)l+>B6sst-Xdaa~(+m(>!@Dv~`#2>t-qmEXJfeH!>>PX)f+ zISp?9bU6L?8E~^_!l}6TeMc`^-ur$QC@gV(6ZNHnqFXo{j#rt_0o-q&3paZn(M5VX zf24Chota=O6LlP~p%_|S2*Jm8r_j})$fr}^PrGWeG%iv}& zC%Q;aXD5iJ&y86Pg<~U6|Fb+r=zHG}LJQNMqN`-Von_=jvk!Ljxc8kd?Dk+I*0_CR zr^d<=CfW;o$KX|R@B34z4kh=#6Ty4m`J81fn4c$ryu?|BQ&5S}QcuD|?tSOG)wd9F z`uDzH!Gs9GQcZ959ZY}e2zYBY-}Ghanho!L7d~=il0L%DQdlr>*@%4(*Z4PqKD>M1 zC4gFixV(GcuLP{`eb1Cc;O^slRT{>XoY_!sjPv*L#c(^BVQeOGaTVDW&by`^_r7Pr zU*G#qC`ii6?tR}vcyZX`@={k(?$EvOSL0poN-5*CD)DQm+TxZ{Gkndn3_Bdg^=MZvy1q`+hTEx%d4~03CsIFz_W zWjJR|DZjdH)%U*7SQ)=uSy=KrsvL(Cy71k(B;HUQ1vB>~YEa3&@2pa|_nml*-Kq@s zz%wC3#l7#hLT7$IpznR3MLKt>(L59@fi1o%iPg=qtaBRz$-VElGc?ob-TOY9VbvW# zd-uNI37C6(?!wc}i4O+K3G_wya$p)Uqiu|r7gfeyh>pL^5$O4fPa`>*+X+2GsGrb3 z2yM45pnnoNh0wEvJ|OfQp>?+d^gN+63H^)En}l85-l@efA%HO3M|7j7u@QAH zx4?pq_A)c@Ik@qw0>fX zF$Zz&TGICDIGvr*=i|O!3E|9kUxfEA?#paOpb)1kDM zqB~7S0uF_=6x{}A9s&lo67>^D82m0bhiz3*v|RA2|Btx$j*p{A-iOCCyR)m6EG$ba z85?W^mT5#XSrW)Q`9%Cz(r?P8zX ze~p*lR-|dY>-Jj%b0c;l^81N~9dIWcPCb~XvY}Iu` z+qUKqH2?Zk;Z$sSNI!$nDU%~_*lO%AGPJ&@yordpD|okJI?{^URe_5>xK5(Cu0j(D zPHbT3MGliP0iV%RQ7h3Z5PGDeLmD~Y1leWoSDzt+7QHpqx-vUC2uH4~yulv#+V&E8 zxm7N}UKIaTodNnsAC~Ft zf)2S0P`zCPOb6VCwE(M9}deK{!i+S%3Wb9r8+J_A(8*Rtf{Jj;^z8m~g6$*h-R zzngm!B2g<>9ghPSBoGWb^1GpUGqZ3Ah8D{S{erOeh#o~z)_0Cas8n=0s59x`kd_-O z{tPZMUS$Dn^;yl8~nTGa4nshhUPJ7aMk60v8N2hTn~E7{AD?B z^JC>X|I+@#FU1h!P;@k^cm5#!^l+pqrmJcHCM{J>`wwWSwXgw3=4AZ*V;sYN5#q*< z7UUl<5tsK1^bDdWKtepe53wy2-w%11KM;cPws~(jgKGQ>AH*6m2nfus_%*xXM-mUccvqI| zE&lZ%7c>pM;lb4NY3jK<;&zQ>VDVG#?Y$``vmBz_zta|bK30hWSU?`$&0xOBLJCgr z11U8n7SBLW5HCee2i_YVN+YFbz`MPNo@Ni~Pm5W`Ex)J#yOhH{#oqw&ak2_FX9}Cc z$mUsLL(k|rpj>+gcqtO^#i2}sY4bx%%&ld>Dv)6)zZJ&iYzu3f)nQfSi}W@$G*LE4lg6APY1ZkLi1 zR{4qCQ6;BO=S3waUNBGOo+vpJa6(V&4_yb-1=vW)J*!99MtVQaK8EcX1-~%N^O1dmG|6pp_&~iUPiTVDg*!Y|y z(;9POGt7lsB=Jpw8ak-#d8=e+7^@Yr+TvKup_peqeaEbjoH{J_)%3I#nQ?I_b_%(X zl{{D^v2PuHS+tTTp*bE&ma>(+ac&l_&6t#h+{B`=+u1ze=&&dm}Q z9i5v+Ea_IaE90GH`t2Kb9lj}b9UTwAWKr`lxC}4M!*{HoTNVkFPZK1{p6b_;i~BX( z02Qzo%$kGu45Z?qjD^pkykKXWbHbr)M z(JVGXohyVMEmRH!9|^33OpBQN;L{`MZ*gzxMFe*np}AF{v~Q-tBaai#%=CCpU*|0h zKK%0|P8b9Eov4jk)+~Yi>J#kjrDFMG5a(dX_aj;MK_2$pGIFaeoOvSF`yE3E^=66a zy;q*Wa3G#uE6z!blbZ}h16A}T=s(@HK^a5P_Ub$uQS&d(YI8aZEn&_iNSd<&B#+Qu z=Rr2gI#>|I0c}W36GUlEF-;KAc-KX2uYr$;@;Uyxlx1S$|ISXECo%V_e z<&g}})9iHa-77E`7UIDqUMa*`>;i>IgXhjug~;JSiIYe?REY1DLUPZib59`-ni=z4 zkHpP|IIa|()^K7H7miwWQl@hmIKws|EQN3wkOhbg2O_iIQA2=<9o|SF+`$h=jh^JD zechMDuVwa~`K^Zf2m1%H733S2N8z8#IsV1-_amk3WX4{5{=NVk-CXAH^e@fd=}6Cz zDAn_KlK$uU`#Wz3Q)|uNn{hvu<#-Iz5MbSp>3^pC01yT+!#^K`PCSh~4|s2Q6bBu> z>qAd5!!KUYFWAux_8mC4_s~;K;;~zc#|}=CMv^j@pgPCvqWH{ml~K#~aCd9el0EaW z?_GtPUZa3%r{00v5-kbCy$jAc>pYG%{HqRz&WL>t`hIp4==<5n#C`w#ai1;-;-c># z8Pfz&8l}n@AqZ&fui4ieFMSPn?T~rgR_S7gl{pBt*ZWwMReU#~g?(aJIX? z;W*lm9=0v6*JHOC|E$5Rs)Tf>M-3Uzp|IEv;WDDs%axuY29?-dPyYo>JAZtV@|=6-bT^_<3)Nzr)Pduz5<&WXBo z5MYG}BL3=VS-RWAad{zj5x2k(0^@P{B$QRJ1p&EHK;2Vo3Dd%ctR=JN~^M4fKt0Uncs zX=9Ky-RKWy5;vT}aGr>-mIdlSp2s*R4v2?(u8e`7q)erL%WTzMyH>KU7&&)jMLJFL z%x^Q)P?Zm(SD-sGmilr%iOH}0>Dug7c=j-rx+C@o;`Ojq<%XaV8F8}F>2{kq7ot-( zpu{69m2D^Oks#Lnz#%w}F=TAzm82-Gt-s zAHB#{Tg}|Af=x6Vp(Z$TnME{*WNlCLc;`X%SI)-SUiIk4KUf|!r%-p3}*0G zo;%;4SpCpJj>LJ_*p^Oqg1xQuk}d`FZp{dspwdL@OnB7I;IHx^h7(jrs^UQ}<>5Y^ z=hhX4cbI78@4JNM{3RLrE7WrSo-O%%E_lWH`yRsylTd!6_d(G7{Xo3PAr$V>hd?Xy z_q0GBtz0nm5EGe}z<)H>8yF zmkPW5JqI3jGx)1Kh)Mnmsfq_F{qvW74F|k)p{sn;Wnc7S|ATGL`2)mr1~s2T&6>73 z7sm1StB@CchA4sjR@>%$uB;OIFC@U1_(evu4ac@Q7lU8^E704`*Z8&H;HNkI7QgxL z@YByWXG$!VwmIJ;1l|I`Z^<_22O;sd_#=Mesn<`yd&8gcoBxI69(tNz`SWl7L||L8 z&0*~5H~hr5IlluJ+noObfo%>!^apv%^{pX z+Z-Y&OYD1ojGiA8#y2@Sc|~;xGcKnLPO5xtJO@aQ1~@DBzk>dwaP7=7<=FF@T@PD(-xk_X$%QUHSu_+B~bFKt` zwap<`PD(A?oNI)a+UBU!=x+<^# zi*3$7p)==wi(s+@+Z<-A+U8te#aeB1Zs2H(_|hLfY4 z`_L+66#c@IXhBuBIc!oeuOJwS) zA8bltG+x&Y<8rsFgCSUVbs8PzqcX+WjfY#6!suP$Tt}IbQa%-wbYBV|!==W4V=~%u zAw2Rzwc)T-+iwgl)%F`xmTLQrX)9{GF>F5Xap-+7@-IJ(+2@-tP>Z7R;L~*jQ)E7e z+fmm6)%*Z=eFZ^72sxS;EE+m_ww;cK10NjL;Zt+S4pWbxXaq>^Q-<&Mx3*5X=v;tk zba}|U#c{~=;V`>D2QH!9xAWHX7IojU@V(=_CGLpru99M#@DS3AT}^u>_9d{tYOfNQ zuvZIA+G_+NU#}I!;F>eO;wyO8UI7>u=)#-{tu%*mY^ZlA31$O5Ow%@)k#EjD3|T?f z;m|CIu3sBVlbaDR?uKpx-W#6Dosx&;QCNDK5cesH|Lhm+EC!o~jr8^&dYS}--O?}E z*$g%xvbw#8o+c0dGP3(`4j}4_GT73mGPm}Neok2#da5$F^$T`xSs8k&GPm~&c3xQ- zdWtgjWo2-o!0fOx>c=!_W)8%UYuAn5Y~DV7$J1 zCDgYxQkb6~IgxQ2s;h!${sO3k&l&1-Xm>&>KiwN%NIgd)s5uBq;3&KWL2+4tQxSbO zqAnT*@3HR0_-?6v)%%9UJNhO6e6k%aWkF9l?lcE8t%pcjhoFVQN@cSP%Jq|5vrkI> z4D5Y~GZcni_X|wgdj+CiKA=d_KB!1&A5tV`?@=UeA5o;vJ}iiN9Tm*pEqK=61sE3S z(n>rX>Yx&nVBSQ)c+H%YZ%sbs+RIZCJFw3POxR}yChgM#q1SVYB<=Hxg!TnRQuakf z(sq_0s?L*wXYIcL!vbBZPC?Zn!KqVPH+La_o_6}YF447lz9x9qz6yx-EPj>Mp~{Mc zxOtX%O#`oI9Ix3DKd|ozOxQUBllE191lmnB38c1_xC`DY_vhn%gy)=|{}r}@ z{X?Kx0F~VSE_l}d28aWH{HmzKR1^tjAq13uTJ~ggZ{m5!bI`)tANvB$BpPVq5E5-5 zYyT9UbScjs3i&Sj)N<&+iho>wFT1?1lY9+qMxeH#wBT8r0t^duX?`KsgDEb#-|B2ak_6+CO30K)=Z z%JV4YNkSD*>{afJyy0HH*@Xyn=39fDck-`vUYzd2WG7)V1WD6C0Mpk-fk`t2Fg`=* zVxH5ES`hF+Ip7^Dr3s?66=RwpN?R$W38J)>W11kKZSA+UwzcLL=j>_TP4Lr9>3AcW zZxNzO_Vu9s&Dw~^KTS`gn_!++Sr_mg2)BDzB}m%w1n5&%6PUEC3k>ZVfRM%h0O?{J z&W9O^kg*6Ei;%GhC01#l%*@AZOPGEjYz(gqOg95_Bo1^Bcns^72LDh}(!i`u0p1)Wj)(NX7VTh}q% zEGh*sxwt8U$$3eB5m0*Q6j}$$LBnD%#O3{F@Fcw;2;k-Xm)FBWms2q0lzKdxPl;VNw(QQ!j$TP*;1o!EoADnRUB6k)n)6LCP)>(>9Ndh z;xK|}ya}X%=e7=R=iv4Z?%?2#4(>$AGlzo_pMS2c!DbheInO+e_L{qvEiQ-L$LC98 z3+CCSR%j`ckGJyNY;0~a?k6~FXexnLNgdcKsRI{s)PV~*>cEAZ=-}GsM9`@JBnNjU z{7sc|<&fAqUSPVRWNe9m@+1SaGRB8!iOxLUg?w1vfSd18Qa^zpX=X?nx-{=anb2o%2PYH8{lQz(w)O$3qCfbT z_6MY*T^>`bKX|)OTxpkoGwy7OyKlAJCsi(~kb7(`xv;`TA6AYla{p%BcO>q9)pDOw zxuinwakb>W+b6Eb{Tp%1<;yO{RkLE>@*TZZpYpxA6aw{%g@}5`vO~R-qI7+8%HxUe zd(_RGjB<~r;3wYOR?2>$;Imh>TX5%sVQ~*|GAm-;*CprC#XVzp`CNtLC+9)r{{i@O z4`3mgO9Xa7feAZCpw2TE5Ik!~1BL~+$QM>TPy$ufb=v_K%{KBPw2}xsMmlSC5T6)GVE_l{1 z1{fCT(sa+zbdz9~M!@n|os>H*Upk(h5)r0o)DmZ6Ll1kc*#0mA}a zDg#eMvD`>7%T>v!le?=-gX^5iy|T;GL?b@M%qHp=*sNNL$KW@A1TqS@(kyk@TR zMuX2+&^PeSDey2uG3UhA+DX8b66POa1f7d$ef-9yZ#B_9u&WD9SS((UK26tn!LxQ% zz_376RmH2_(T42I%BQR+v2sB-kv6bLiyCq;)pi5<(qB2M@D^|&dEBVU*d2i9R|dw1aF;?Mq{6~P(Av9sOLT0AcPC!wj?5|u(mvSI ztIn(nM>HNk@;q)>Am8Y47FDzwsOAzZE|^qhAQHjR>6X`9@=j{{_&|^nR$M{PE z$6=uWPNf^`P|hLUpx|v2&ppuPi~r=}{s^Y?&|8tqJ#-4K181r&-W1p>{mOEFSIRkA zoCfXV{n7(~_l6&`4A(|pwfE4|oQ`A`59}B0BL-VXg3;5QfnaL+!H?M4JZO!HXbo&? z7Z2(e`C~@@S~gqhX>k2&(c;1Vf_=hZKDPpT=xKgqutWL<`;@`f6;(1E+X@bysv+*Ufe?JcoWS{H4F-)L6^;-{Fpc!Z9}9k++OI>2h+ zI*p-{0s3@v3$ohnz2De>=bZIg_97F5c9^8!MdVmU>&ri+{ss0>=*_)ID==w`0x?TH zOc1Sz1;Mj+I$&6!ODp0zNEtd_65@)e?WoE70y26bBVqQ3w)hqbl9v%vI^3899%zDT z8YZT6cv60SWLBFz_oz?b-herG{PHbgOm`v1p9a=pYqSA^H>9J9@EV+sCIJ-B9s))9 z0Uxo@suM#u^)gejg+CZ3kt;ES)5UWbau>wuUK8BX#q&yTIYyl}lQvGA`KOELmb`cZ zqN;sG)$k&xIS9h?ds92+wXCak&4qT8ckSo^2d6oBh=bD!ITsfMyb-4m(*#l4p)pMm z(Be69BFp)ug?GiA*sYOCo!eS9*(a{d5&ve~P~sK`luPQ|t(03UF26^aY&DN^ zY~{J!4#)%kRV|19dY;-{*=ezG(Ce{Gd~*{xoBQ$ObI|`6<*+Pa$HzUHR`WP1%=h~8 z_a&v6{||HI_{4BhEp3vP6{b=t-`q~!%pLeOO_+2vCPhguCZ#eVFR1T`am}(vnwC7y zflD_JsPg#s7LNcW`c2q0t(R+v?WyQGIHElC1(tZr;>hXsfAzQZ@fo3-do|IcwU+D z#zV|t=&Lq9qeja;fZ>@Sft}WT2PU-j_*hl`S|r-s%}h{L0#OCuoPa9kUeMi~YpC3R z7y^FDXkL)>BeBKSj^W@KFo?#$fgLE8X(*Y}jDCosM4c?e^ z;X@6dYD`y!PkKCa7z(*udmJs@WnhmHn6SqROxhy_qR@^~Bx#RRB(%pXlCmcVqTTKh zf@kgFfMJ0y?RL-AZkL3(+bzv4c;?XT^f*J}2lf+s5G$nPfR;zg|EWA)_#exoGQ5^YtILNAMeV>| zBrstw7MQf>3q(F#ph(hQqDW{jRU~CE6GW4Lp5R$~E?`)oOOt=TCZB{j`Bml7=JdEy z;^SOeAkL));#^uF^te`$q`gs*(4vK6c*f??^@^nJ4T7jbGX=-lH6YHe@v915pbC)? zE2QO-^max480Pf3RiX#>Hh~FyyTGKqLm>3IQ<0>-OAr-$i{M%NPr$H1mkPa5g_7We zs?3bX_sIFDZK>_VB~!d7*_gac+TBYP<*em4uhZ{wkrdbm1>$&AVA4J$5c)l=NYXx{ zNNAr>BxN5J#B7AtZSNO6YwrUL3v{W*7pcZ1m`xB6UA!x|jJ!W1I=AFEg_m?1jmbuQ z$wbsGg`- zQbyM*X@LnlkHDlIED(9ss7TV*DH7U@A}L$1NZK|CqM3$|SfJ)*Z4xjn(50Dnv1S?x zai+y}i|d*ZqEBEKkm%TV5ty_K3N)LuJVpziwW9#T0$s}b5@k(-*|MrUk{-{8=X1P` z#8)*J799I6fY@)rukyN7d65vSS!$E)tLJz5wt~dRUWUMgT}EKijuVJ{TTYRrU0RXQ zF0V-1E+vTSv4r4RyEtH2piA|*O!Xk4j~=1e366vw{!8ZH@R($2&b&STX~rIb#N1*2R#Hxm5y zyr&ADwGm)gpi6nrRNf?*?J9Wln}F}&*nC0o&ab|s?o}Mb$O(HefY@2pbF%mfkM^){ zwrEs9Ife!LHiQL+9o(4g;(aW1Bf}7&YcJsWb-;{kJ`h*J^h-X)ZVy4|#*}>RhCUiS zY2HKUBYw)Z39IIH5xu<5bb{!yUX=$#cdv;xcq}>%Jik6gCzThLtZP)oXh;`7M$Z_p z(O&}&(@F-}9{JMVLr-%hva75%KPvzM!;v=ZH->h4MJnK~N(KE?vMh@6-Ol-u8~7R_ zEzFwJP00d7;6r^}Ek?f~XU4)4yFZ!5-$I2NtCaSgXp@MTe`r#JZ z8%$3*Qd52O6z09YDOuXZTK!`ntx{aaV)w!KwjMO@m_f{F;Dh~+6?}(p>>u`=;v;Zp zdQJE*fi|I);6IRDy64&iFSW;lH3la1=6%#&SA?GqU})vOr#RGGXgRf^7%{HVdl*cX zVw>9(`5@nKLo)G6Zwnx`q8k<2>j?Q7+s#VFbS-kp77-*Y@K@UmSP$D(Cje{~d8<$mMg0u%NKfk}I;K=jc^D}t4TBB4D_k(50~5bc_K z1kc*T0K)=Z+PB>R#j#yOLfkc%#tQ1OoYUiMi67Y01SagM0+aT1fzaa&MX+vAB(!HK zlCtLtqI#Snc-Ec_7#8SKJ#JJzNa&{r$B5;f9ydt*z}_q{VQ&jlr+ z>j1+7T`J=yl|h1;gn;F_oz8(*a55g1`1qWnK-lC7gaw{Jr1c?1lJ;RiG-dY*p0)P^ zh6TD*>dh*Z1edaS?Bl$<)5&~6Vh8qVfeHJRz@&XfAY?wPNYXwhh{}9Ya2VAA!l(|v zD)XNzlY{|e;*C97lfEXg1N*8#Tq6^hv~LN7%r_KC+BXGJnXd?*wJ!sP1-ew`Eh>|Q zJ~CN;7`8;_M-n@*9}7&_PXs3Irvi11|4{I({Qxj5(4`V?RS6{Y8{?UmD>@lJO8mfn zFEC+$5(q0gfk@ZSiX`nXg6JCNJHfN|TfneDmrA`&rIJv!hS55+l9L%MqU|&xFkzDd zlhzjqnS&Hb+E5Uc39}ZNQ2!}1=~9`ut4tCGkh!vxIap%Du1jFT)(cG927!<{k0N+^ zL=cr(CwSJT0mA}aD)SDNNkSi)+aYgPaWa>d*nu4+XQ8;>SS#!(Q$?)5avMwlXg>qkhPg0nyd{4&)N+D!vb9@ z>n@c=f=gDZe`B2)?_}*Q(E~eKAkK9JV(uvrvi4FWY4;UGh3+AE*6t1%7U)u;cdJkm zs`4`Hy$YGDK_E=Wz zt=s0iU{BqBeYAUZaH=bA!Ch(|`qaw_nQ!jGV5_pC+aQoLOubOfkqh|Y2wM?rp1@p; zFRIHYO5M$KEFZz^J3ue1g-EzWwPsE^l*^OM%S`OxObiChdU&k+;(n zN!tAdF?VBFwNnMhY#b1?ar|oD-lKU-g1Hv~b$@OZO&u1-(XG@7HbzV>zO#HYoqU&6 z+|fd<2p~>F@TK%wF$&to4};KRG^BuQ1Gn105B}jr6L|s5hTPSM8+Q2IMV%Bw_S@Z7V|qw9F>r7 zMJxsR8$y1NJ1Fp`;%@tcVARbwJmh0r89%HUy_5!)(Z((fSBidtohdM3uM(KFR|`ab zU86|SUMq;%ogLs6f@kgJfMJ0yP1Qr1DiX||(g9Z1i%p!&+a-2j?+}QsH-Slek3h)0 zOOd3#TM(6bo8Vb{D_~flOJzQ+GD#Rf=B7^O!xB5Nj|fcI2L)n(O(0}GrbyC0E{Mu} zK=7=+A22M?r7|B;nIu%mJT1uYg+$|l*n3c5vU?6&>%BqBXVE(IlO;!3j!7xU&7hm! zoe#Qr1cAqe5e()9F*Q3LZ9$8}nV+oFTe-4+UP>*nF9=N7 zSpt*x6@e)07Zpj`mjp5UpkVBCf@kfsfMJ0yE$c_MtVuBYNeNcg>8+j2*%CXja|9;r zI|7sTeSwhqt|CeMo**joZNanlEx@oqm&$xhWs*=KbIPb8Jv7-_NOIC!V!S~&oas51 z`5ouMHcp>kgmYkj6_~I;2u#`^1wx;n6iM2j1u;{Y2j2^xwci1T1-ew9$5kH^%+yjI zj2Jh{tH+Xb0_&hIDlaRY^tt20`$`12GTryQ*rd)cehYr_p34`&d&3*JQr#aC+I#3} z{*5zi^8}bK9A1=J2<5AWHj_mb#gYPhd>_8t%iiAANj#2%Re_H3 z_yDG^5KzitfiA6S|I(U9LOiW4^>^g8gX5J{Uea=r5aERfZ@`N#<@KcUBEhvHJ@eSf zFPs21#r9Cq=oK8Uq7AipmqW&mkdYfQ0oxfmt?V7Fa|tvkv5@2=&B1 z)Ki3dQWe!VQYSO0H0HQ=JhK?)Ze*}BIl`TE+wpl-Ckdd9fKMw1edR z2^DQ{dkuY{_CpzYc!|XqnDsDk3|d<97hulp8&=--ZugLdyw=o)nw)D(dW--aa z184vFQ=pa^i3q3^-I#+I9Gr$`ln_`aH=zLhRrTLx^H|roe+gn+DOYQUCt?EX)ILVm|UWp)?zYU!}Gsn*#N|b5W zW(c0VtH8Mr|CETLzlb3)vPvz_TSmHAB2oL|JV<0b>NW!J4WEir)80c*^E8qp7Gb$Z z&`s&}jvQ(v48K@@7{@4q;~Cj#9< zk=`!ajVqczo-HZtFl(X>XZtzfu3DDAJRi(S0 zXCE(?6nqW}Hl(_6mguFsIIT!v z4yPV3i5~clB!b4RB@4Vae1qa??b_Z$PqBHGiE6&cmevefqHAmL$-jb-D9-{(MT}Z~ z7`;hGdca7}J_n2zpQB9h>xr*2SgEhUXB>LNm#ayTrTuCk2)3NgSKd0jBTuWlElW<7XG}KZbR+I1G`iGvh5r(#)WzWQH8}Wjgu( z0FITACw#`Fe6;BvGx=zssk5`;UBVl?WqdBy;PJzz$1zRsOPYe>y#3U8yQ~I1`}_PxCx#RdGbWU~?Gka0y1w;(Yx=yu%PjNCYM9CO6DtalSQoydGU8oY3x7##!Hd5RRvu|O-&{U(8$isal6iyR*H z={dAJ_rp+m9Ab2F3SQToXl+i)Nnul>DS7!4FmY-QapoNz&mgjY3I~NXCEF%6CwU*| zq`jAZ$(@Iu)BnUUj7~u4)|GcdhY_b`Auo4kTnC!GgCO`crsYFqUR4hrY^b z{*LjwcZ%!vQ13G24*wD}mchPeoBZl`UYbDlqwBawBfsOZ{&B&E^$T{r1mo}c%}c?C z_X~D|1mmx~Myx_dRs>^5>qj?9F#h6&M;ev2$z@AzAEC!_oPWMVtkijx617rAW~qj) z)M#jlTA@INoQCrITltfb+4g$OJAFlO5cCvMZz7ttw+Q7_Qf?($G05LmcArN7JH#!s z^T1w5hg|xzw+qUhLo&s*lYV}Ip`+E|S-piY44csQX87s+7V~<>#2TP2 zF!x|pDy-$+{hg$94Il_|AYSiYgZfF^@fA?xXamwI(40Zx%L|^h%K?T3x^ypR7KT#T zbC6)pLO|&|rO(qf^tF!XMiL({F9=kg8wj4Y>jQ=bx|HXO%9Dfvd0yvuZYA*pyQM(g zzuH3ZtlbW*5YAQ$e>GQ zysR=v=p&=SI}dZE8z3V;d?${R3A-~xm1cqM>=&$&!FthV+I#3}4#8Pzg!d+K0s<-XRZCf`U-WqxK&M^wG_OL6L=XMq z+WkTgDTk(~gw}M)Rk&RkAtQqCcbHJgqwY@Y;=28!4`n#b3VLc* ztk*ACGlOYX&{MJkQbzTcQd(=Mo$pPqKJ0;5@!sg}f@ke+fMJ0yE%(>7+)3zD?sf8( z!_6%B1tF5_r^$>Q*nI#xyHWA-3&Ep3wEKb3?ibjVMCY)@?F&M0IEI>@3%7Y4QtJHz zdx2DRJ}8Pu7Z5c&qC;3oR=mpMLQq@BT~>7u_*R#uJUFv`=L9C~{sPTKh+?M-j#F?z zoPy(5)A)v_kpy!I0#>$jo@?Lcc=t&Bz!n82?4bga)(XV=#$k#i?cs`qwjhYPl;W|I z0oAhh5WuiNmx_N=#gkwzr}%gr;9zk(WG7nkTda&6#%>X=Y3+mD(0-6>dg^y4!$eyn zh+Nq93+$0~Pe~@!UF#!I5M2S@8M${9Ml*rqVi8!?Cz2_=i}MQxQ|wW(h|JBF5Juj^%U{JLCd&9`esncR^_{c93U{!8R~dRp zzT$Rc$rur9p;HkwzbvYzE+S-oV$X$s0c~HGW1Z#NQp*F5PDR%PHsM0*t>~`CCN`}bnvQOW1H)MvI33-{z-g>$B(z(Jb??gE4bJ#+ zhm})0&@m&<%+@(w_q_4uN7YkYl3(*-j(`^&uKLbnbl@Ys@8>DsS>^uHW5~yVJ;D^$ z3qMSn{efUHnM$V3RG{g&-p%9PiSSP%d>)kmZ6B5YJY*4Gz8@rM>f#qiQe7+HHyO>3 z;k$b=63{)ZP@RE%ybwil6H?o{3YT$y+Tr;@j55#sMYx0e}bJ{bd7suV0x|wpWABC z=i6amxjWRLKT?C9*>PY#Th^doR)hZ5$btF%R)fCXsDZ;5YtZkgL4T(Pedy?c`R`kU zen$;@Vu6AA>{5e%PYt@a)4+VztU*7p2L1XP^bcy#hfN$E+{5t1Jl>65&oze^c!o?zo|iQ-DP08 zoi*q?)u5kHgZ@Yj`Y$!;<8~cbpXoK|chsPNRfE3ZZUgh*t_J;#8uaPo2Ig~F4f;DZ z`1IDGud@5V{HN5Q-&KSDQw{oJdkoBfuNw5*YS4eGL0@{$f%)%NgMMWV`e!xh^X)Y- z|4B9I=hdLURf9fq?}7OrQG@kbUw_|$`JYgO{zMJ>gyjb2b6^d+*>B+RYu2FeRfB#?4f=s(n; zkDW5GKC^4+KV{u&NJ-Av#K&H@#_Xg~8J1wP7$}1^hNEvtda6Ht2hbbSGu|%q zd=@;DC+%GpWAH}0zDu&UFIt+HK)_R1-XP53L8G>PY1CC{GpVkIqqa(;!b+pQedAKs zzBKBoajB}trKU=AGWf2|BATt9pZg5n0iMv(XAJPr$&a$m@*<>f#zd}QXzB-6vUWl` z=%f7T3y)^p+uQ{HFJrCh=jMaAlogBySy+K&ZJ1jHbSbRZ8YFR9xzxfOHBXi^IMnQn zWis;LNh<|bg?m1?HKO)BhJ{w{5@L@Nn@#KqV#B8b`xmjzi9HFd;}>VhTYlNpk5X8` z#RpN-E=FFc<@hx02Mm&(4?l`vv)IA>ILpVEA+gT%>fAqfMrf$kNn@mW<#Yvx0?4<; zyrV$jpC6sGHkP;1x$=7+e)GS>P7TkIpAS6xMDYuVf2R0_#J^DdGU7ieemU`96u*f0 zSBhUu{2RqDA^x4>mlFR0xZCF&QN-RyuRJ z2b%;Pr@43PW-Nov6t%yefr2dA081dGX9R4-&INphbyQ5X%)4y%r=eVPXCQ`Lroy3m z1Zp=6YYvZP@d^>#=3a=(-GNY11H#v*TFiWaVthDfZ3Ii@X2EmRuxJp-4e1thbjcg- z-}7g|R#A5X(yit`Fgt(*`xcTjM-0^FAQbFDj3QgfVewovvf@5)M|&WJCYNTLb0L_M zVJFK@%=M&1DW7{j>j%+GxE7y~-)I@JVO?5j@{ufti}!J8FS;|_{PSDPwv^5!`Vn60 z%IF1jKMHZsr~BJc*is5{KMJ=Cd%(DyVMd}vSz|A7xxQ>i&A* zjX|`x%6Y_MYsDQ8Y=9nzwCa($FvY2<#%Yjzoy|DWI!Ke5jqOW(2V0|t(J0Wm(Cb5W zdKWLQ*fzUJw#`=Mw%LzJVS5if4KBkk+7U)HlFrRKH!*cY^ff%?b>22dHzTU_4n1x! zz5^ZdN&pm#I%T*){el;S0V2* zKk7hU79T`j>ZhoiBzZ!7C?<+|H0qsxoA7<+Pivfww1W<%^z^fEW)HSiZ6k5MYP=5z zh$rPcm?HbfqbC)T<~QEtJhlch?P zKna_*4W7fYboLT2&EXz-^~QQd#5h59=r3gtcAVF_S8OBED0WewQBRi%%wv}ce%EOw z0~q5ZudshX7x90nj#Gpa=JA=|d=9fbY@GZ$YmKE%=9Sa}ns9XbZ$@ zxV19_Vqmtzr?N(^+B>KX)oP{Q^c?f?vs= zpbg>VQ`031`!B=qD7SZW{xG(+wKab{Of&G##!iSC${CSs|8=7K*8|Eii$YmMnuti! ztirai4fi6x054eAs}`_|z*ks1L<8aZMeM^u!}nSLzJB^`$h8oSf4*5AGxGeyXp#1P zWVCZXLiYz{_oH-wC~jw<@ev&f`!PV=ce5RDq-_7Me8n2s3U7nw&%!GHs0&n4>@);{ zEYPT?&;(H$RTY{bN+Va+D^Z!_X=b?CetZ$D{3ux8~y!hRb22?9UH1O2m@E{O8~ zJf;aEG<|cl!*6M6F>9lkJ3O=lzSoPK`vSqy0KOy$>{pDeeCR1k4f0nr44Jen?xyk*dPuxnme--yviTnM4a!H+=+<*6pTPgRi z;{HeC{xG0iQs*Z3w?1(z<<^QD&*AVr6;|7d@2Nb7uBHRu-6^nY3`A$q4Mb?G}Xa`8uacW1ySzcna5bPA3Ag|>IJ*`Gj_ z{zs6Cz5sr|(+}{!glyLvz@L8_J&&rYD zm;VulW?~y~7JLn+q4)y9SQ!c=?Qyx&W)S-f-k(mtyAh3faGyZD86XgP-YbZzcbDK< zdnaI6pi9+zPt_w~pgh1sn3B&AN%Vw$R3LaitO(xT6hwtQD0tRB02mhNQX%iF5E82L zK)+)!NM!i-DTy7}Cj}<#zXaktgaTE<6M|>$W7U)vWA1h}P%)g}fEVoJWPCDLSNql_kP$0f_C{THS zDR|a?0T>qOQr@2^ZxX6_cL#WbfhA=AA^Q;M&+72h4ZlaQB$|escaNcKk324xWu}+l z66RCni1`dZ9WqC3F`pCX_m?>+gp$|2*)=gG3titB^BCzamR3M{vM268mdIa@tmzS@ zke~HbjyB1HfH!CjF-;KAR`G+NE#HBqj^Sh6ux$Ly*yYwpd2a965%}0kQZ|z@ zy&Wt$to3mTvoy-h9Kfv$zO(TFG)N)8@;gXBi;r%y^5K)41it-&Ah16Y6!@!sMEM~O zy!ue0PA=(&cf(55b1J9|8$FMUZP#SUG&Udng%0i#s4nrXr9$sUclAsX0sxn>cJdCsIvedLo={6Wg4#4$da2 z@3M!nl{aiZwHJ)PPu5f;%}lG?IfNW+EOB$KGM#cWPgHHd{z^&F`jqZ3zf>zDbu*G% z94<%Ww8&Kkv7tH&*EJI2XtpMWYAg8)&<7d(T_|g!0H*DcPm@EBE>q1P|W# z_*2Rn-`f?Ku)PA478l-V>3TQz|LlK-mbJeFh6TE`K76J1frP61uyqg>QC{vr>G?X$ z>#NEnGG5y+1lfLJD01xU_G6M*WW-!VlYS^SCkI!aRe)pPzpk zV`_Wup^@HH`N{8rP$s|(BcEraXT*ET`FJyhKjNLAQuf(;UL0Fb#gF>rVjtTC(K_>S z6NraOlLGhf%Qq#0Kf*LXVtFn!Iu|cUER*CX^4EZ|WwI)!KQ3MOQzq>eDkehC>%Kl- zm_%aI^k5(AmXH=i50t1$Wt5)}I$6m=>8tRL;;#r3MDu`-j6}ZD9gS8e-IC9!fBuw9{-9JZlF7h6TE`)BIXHO%mcxGhUOiUtZS9T1KJ=c4>il>QrFTE+ zjspw}bg77MR0IjJ2uXJ_Fn=I@GQF(GI=(E_#4P1L%5l(PIp#}oxh1gSvk!*o490SuEJb?yaS}>n2HXxO;<$VfxZ#bDVR}V*^?LG921_5>F z6M7G4d6&m)!cU zk-v66Ns68y#1FpESjdim2O^@s5De=ft}NJpFp8{!1Oac*=8tKDkk)5ib&{JCzW;sv z44H4zU>HO%K*O0EU}=Uj4ZW2ScLk(5(VN{6G#3c(RL(-MF75*92`vs(r!j!Mo{|V> z5@ z;S|wQkEZm7hp=tQi#Ip`Th#mnfq1HE91@afGY?`_IqXCT!qYlofvhiU zZ}UwnB1(bFVfN}w^PL6Kt^!CymuC1CAdQ0aLD5cS`XxtTxjq?4Q>G&qV^M6Zcx9A1i zL2^&*8L#1WJ@56r6TFF^dt+v!x?ntFM(M&IVbN9=gxlpngnGblQ8sz2Ba7Bz{S@!{ zGp+TVyjq3V&{$O)S_k90lsQAjSIIlZlxGZ-)4XkAek`uY$&<?5N$${^<3^NGR1dpx_U850-&Mj_8e-tWjbOL@%9u1*>&O}h>Y>A)h>>CM6Ls~MayAgPL z+S{mWf01A5zS)(3nS)kJzHz~PHrsjlBogyN$Ha|YKeP>6Eyu(O0=2u@TJWsh3NS3t zrQOZX+TD;4cQ=w|@*EG|n}BETJoL3ai=bZTZXwn|?0I5~0xK@VNk-)iqDZpznLUdE zlW`k;{6@sNM<@t*SMR5nZM3invM>v272k|yX)b{uY`9=tNO?;j-li;v+3bhzRmFK5=4}BYi~gc-C$Y7#8T#viL>I zf&}v#iyE3xZ$E~D3lyU{<^D>?_%20yx<_NCvWKOEc% zyII^r{Q-E?jl@Jh0>hPAOnP~7_$RvXP*OA=egJC%h_MUsb%}3)jYssLxP!bBZ!GgN z@;i{xy(r^XMT2W?#4z_@{*XTvKg}tG#)oj;MR?yPOi~;vB^;z4kZPH+tQNfYQ=Wfw(l}&ek~?Is@NZ)hpf~ z_oK$UZMOEKzW0mQF*<(3D(x+Y^zDXx4x$ghJJZQtDU3b@Uhk)(j{x1}_%8;y9FMSS z|CRDaFRc7Wpg-pM%^u*1_KPaEC_j&l*E2d5|bhq2P=*#;rzbq@^Y)^G5pViJ_Qc32W{ zSnf8${QW}^jtidL8P9;{cEoN$$e!(qT|#UJVoT?t&yK_%$1JsHCt|-6n@H?zVv~TC z+j>IYo!y)HsO=IHS~R_BT%v_D?!uVcCcpZ^Pllf1sNRX@Km^cz=G9REE1{(h859Ty z^4{7V)pJFMcaMVDkd=KO*Jb;#%^fB4%7onmtrMT8z^~3LXCD@yvfz1!-tcI);~iiz znKY}i&to4o#RY9_iZ>y!Bz68HAY(_!*pKzCyw@Gbgi_qjx;2o^PSM@$iIzLoB_wH9 zhn!3zjB=3Lzd{B{GoFe-hStlKyoPnPUUHtmIeH}d$q^#9q{S==8LH}xMhF~zoLVuF z)I~4KJ=7_02mDWgf18^eJAXad(|#J~S)zMF)a~#t(lDAr)MHPrlh;L!P#X!~tU2$YsbI0x57d%?q(-&I&{sXBE;t>D~t;zr2P_Ey=txG=gHNsabI_|D-HvX-~& zATN*#l9ttk>mE~?IXr9_TVWV4ig#4>o7;F(9@jd9um5>>z3GLV-wOV&7hVON^FXWw zy#u5!aYnCp+sJs9PY_QB*rS9Irvq}75lSZD)&-LwhYUfqsFbHg!n9OzdLqTR2%@5* zojy=SOvyQm*2Ydw!4dGZ8a1*ap=zs(FC=?sU*Ux(A!A33Gv2M}2o6FzM7NAz+^b@= z-V=ClcpO{#=jevpd+2F?!$2K<0e5{TXSu29OW?38PGa_(jzaw6K{}A~=n6AtJ*J+M zDh(hRgkTJ;MacwUz4={50@Pa*=*EU~r7WwAdJ~e(gX_9D+|+3{(+s$dA8%p4hVu1k z%mHBj!&fQz`Dw$f1}>kO{&Eg_QTX(>&M19mIxOzpF9(ii4$y?Ay@#IWcjN#k6UE8> zf}X&jSF|!HJ&XJF3-J$z_*$})p60(`R@}E=uo(>YjRd2o`Gdjs>lf@q2K!cm(bM#@ zaN412bJ%$=l8tH1Si11)24*PVA)uInNu}?sV*t{>2faBRG^J!TXs&CjQv-A!!eeG3 z{4$+f=`>}OUwzP2uV;SDP*7#o^S;h{^g02EG9?L`>ZHFgYoSc!Tb11lfD_1O(-MWV z0#c@+X|&?~RCqvjGVSi-bV*mSI@Jl4FY2?b7nk++pQ-KD_ulqK{ibh$_}I-`WCNe^ zFmHHi(2JKVJ_2*-T|w(9j|1f%dE$6j7UUo_zAy6Gh zj&wv-rsm>sD)J2(*<jn`{OLKm(LrlJJ7UfW-wuGb>Lvvvw# zSfESi^?#zEu{}gWpY>YkEeJVBfv2_*tV_6*aUBuM9A^cIr~~?=!{wM=IZt4R&VHEZ zd%FW3!+g@=46mJ;EfLEN-Rr|w2kQFH<$?RDzQxQnsgKuH5l73NE9}q}Z%Rfn*WWD$ z!!OmP85>f|06!jb^QWM8^Ug<}i4W`n1o-L`L2q~}tNwae-Gv2G%vO>hrib^jwgHcS zPf(6lxw4b~+Qr?#ApQdkF)NShOWzzlv(L z_7?FN1$ubz8MF? z03&TjF!zDl)h(7M?Kw|ozRYy_ax6rmJdY#j4Xnsqt60r zwniz>;f_uwXbW{?1WOh*teOKdBXI7=zosT{*mxP|0&hv7;F=^2X|S%}oO@naop@t| zwrw#%mTOJD!-YjccEp0#aj=zBUZGdtS>8PEHz7>m8rnZ{{L4&pgiGMQFb$j5sV9e8{mknHsNKHYP1aYs@%8e|^tAa6 zm3eTgF24pA!=>`e$eF`A(64wqUdY8;ynN!f#XN+-T}@!AgtnGecL4+;_#SX5Y_N(n z6E*2>4@2K^JD=$d%JXRUS)~+>6!dD@WZEJqb&eKvl}>OlwBCz2$I{gNy{4GV#scB-Z;zX=G$FPcB7+Z@)U{2( zemP{lfI?zB;o63M&sEG*voq2jnBDQKD;&+)O35io9w=vj!nug#^1#_i9xgjYF79iS zBr!CX^*wWppx$*%w-lHkErkr4v0C7bD4v2UA8#3*47@kIg^m9QnZVFf&kF~k7@{6r zzpR)2h*WeqaF*^?$Otzh)cuI68KKl!w0+-O!yE1PjnPicH1~}`j%ggQZ)85XwkFj~ zTU^PwM)S1ZI{;yCW;$+Xzm3e7{AdnBr}DG$D;s=F?! zx#8VT)c~Z_RCH%ZDPY#otM^E&P#b1r(wD{FHK=y+K;#Jte&jK+f<0cIz0mW zq^MTjV@$W3Lmdbm{{NgqP4;DE6GIZ+t8!0+8jjbs+;k89mSHqj}Km%|Q64rR;w}mmB?xpG@brZSDLZa=lA<~e}uE^ZyXOF{`q2u z?`s30X|;Afe(_>7z3Ktzakk6vAPi3cz*8QBc4ead#fwO5emW*o@vd&zI=M9}fPhWk zFnSba*gpeNp*8P9Z{!%wJDk^JbkCT>P+M5vm&fvCG^Sno`)DzQcT=!bg;!AGE?+!D z%RYh;0FTm$e7|N*#D5ujjT(946OaUBlGafpqgTMbK5R`D->OLEo4|X+cbUrj#Y%~u zW>&&mw6!Mc>1V%;rS|r_Kz^^}%~!hjrV#V1@NT#yc5) zZ!oS{{de&YIZ(72D88J~gw>GzuE^;_YsY9DmV2su8Rd7m!q)Jly(J|L>DE=2BcoJn zS_p&FbuMcfqrg>n6p-7C@s38 z&9P6fhe{ZS%*%B#NsW|R@an@yps_rWcFrTn>%D%wjzylad>8G*YZJ0B^ZK@$7ngb; zfLH!=WGKGRVn0BR`q3(ggdyV#7TRq?k^jGgah(60BQoAcjQ6FB_aWn%&rwW>_tk*$ zMvQCqR>2_h3FCe3;(dg8xb%eI_<5h`bEpU=-;l}tFn=>&fUEfuKhe*qK=ry=ImrA1 zN3=dPMXUc6_~Kvc)42Fp;k$M5)4rIkft~q^vDZP^hR%zi`cU7j3kMc1hhwvvMJ827 z6+t1Lh|KJohJaXEglHn2djqeVb}kBmyP93w!RZ1g%?t#VN1xd!uvr+IQ$XqFhL5<8 zhifd3ME?yP#Uc#&>X5DmAkr;@LNVCSBY<}6!M|HhK{1Dcy1lIjgry|0F36tQO5UjL zR?qE9AK@;Qfw3aya?|`I6}zg$QBvC2$^QegY^J zQmke`_y7dgOTXE_lga-FNooiri=+1tba3Yj;8z#D51hHG*~=W`&K|xb6#a%6o!7zv zPsvVfVs@Pew~u_2oP_C&e6=Orgbz<0hJ48XjyNWbSrvTPvYT)Qt$EBD5nXy+ZEWXe za3|!uf(hj1_s{~5YX5=g@{mVU8bYF8Y9RY=a4>yZ-OV?sBF%L;NA4@NPB&S{4tOS= z_IpDzDJ#h*s9gE9cuTDn?`iJleY9@;zP5hH@MpGzZ(Te1nC&Ml5QyH<9&C2spxL#f zEquqe@Fx_Hye!CU=5gPWl(zUQd42DAeL`MRD2Vz4qppo?G*8mRl)6bjk`}wc|FB-b zx+Xjql{7zy%ufcEN%W^-T{nJ5L=XJpXAlq%VxI!<4S%K-MsM$-r!Z;2mp(r47xI^K zNP0@h!9#ntB8?KcJdu-j5hJk9R9{^V&G> zu-7Vn)sM%2$Rm`Dp=a^yej$Eih?In&r}+^AFjkSDw9)!bZua5~2?Q?i*+%iccp5Rf zIAp>ukOPFd8vDFrxYdT)6{p~C7vS;!=R&QVTDkeb4&GXq#&fH*+~Y|gJ^5Z}#qr=f zsN^!aK)l=;n>uLVN4LiH6gm1QlhT4NJIdmxT-QQx6X+8%pUgR|UVcnJc!~0q1@})0P-?Y zF4FzKj;fETh9C9BVl%Q|7Iw?@0ORiW2w%Y^Y!@w*9=#6kahVVHBJgeiZx_q;AJM8{ zT`&DIrs@)ma6RiMk<;j@pgz0>)+^00d&h=>M(kSUH zWhnGHG)^V#m6+c!1F|3)GL~%MD3g+%$&@)80o?Hn$z7cCgk4MWSN+Bi4JAKX74)0% zyByNKWoci+%GYFB2Y6TYAi$xbXOW7$3Pq|afo_wgWY(-IN%`d7S=O7{p*6y=hD6Vx zmPZ$0XBxXfuYt>`x)r%sH)?bz=XIm%+H@TfZZBT~8eEs(AM<1_JKkB0_wRj~+vVgfwG_gQ_O@_@(rfbs50&Q!Xk!ftdHbo{Rm`wkPt|G$r8B#U~zpdlG#CJ9W zSzERt?XBiwWHqMB=0Wu2IM(@pw0#Gd9K{jtXm)ot+?}M$k`APk;7-j6We^uYNFs|U zaz-G65n;qG0;FY@mB?7+97GT~n+ygF#(+(>0Rs*MXB(4E4(Ip(RXsbqwTAT*s48uA<_T1y5|Dfr5t4LTj$h`(mK$F zeIoYjhH`0|7jw-+=w;`%X5LJZdW#w;V=hXq$cZ__8l2R=U?1l$ctE^sQg#uSqFq$Y zrER#|bvX3Gb;y&s0p9kkzy}Pn6Ifhot(4+NhY0jWId}vIVNQ<=Ne-dYst2p$=0W-# zOjMq~WxFXKFu%9TZpuwaM|V@8PiK}IJj;M?z&uJc*W@#0`-g?6F(1e3&T(kut+Uw1 zJ+lcn`^rvXCCiUx@xZz!UXJC7EWNKb7emH7lKv-L!C%F`Bn|`)-zJfAcGFm5QA#3O z`JLd@pW}EKw}x!)n!Cy26ke48KZqn9?MoiYAr*sg`Do z#E%87;>Lp!;wFNT;%XYr0yhUuaJ@XgG>ze!1`!o$%+27Y$#XCq5r#52iAJrYZV0{dlMKX6Dw;IEC>^Re> zJ8R97{!8o7dN!|XN5XjRQ1Cc8vfwZixS#b5%*3A(u50qdj_N^4G?h=()x zRq^670X9RC;Bc3tmBb3if}}GO@%qAeEoJHX!w>~WIPq&2g)FJzN-CbKmjDg;wPOp{ zMg9zW8-ZS~5%><%gx{bUKmA?{esit($)I*o$LYi(FS0qrw1&whp|XrXh?xA^WL*8+OUkgA? z9e-`WqEKO7Or{>zyii~2tS~QZm8pl-Z!U9sU)8bg7Hk=IfJ(?@C*Xc>JbrVA=psGM zNU)~s-_%KGhw>j43c|xRq_Gvp;|O1W3xEA?v?nR+U%M*;AY$@6D}mVY)*Yev$+u{o z>LphX$6MfqA1HX!+~XH=I78np{qMnee2j=TTntQi_t?Vy0|F>Zyg~m4#sm!U&vR(t znGJr&Ruqu3-t&q3f4i6w34rsH>VE?LWg;>RYghb|JGd+vXQ>S*`ntZf8S}CBZpol8 zs*(E{H-S82J=Ab*1b+h3QcF;nHUX}|dtCi3#fk*%>dsr$N{Ea%qJuO#>uwzCp-HC? zDQqTbaTm^}#Zz6fmXy498R*x#T(cq+WkrroVR3aDE?`ofDkJS^3!$|^M9ZH@zAZr) z+YXcPT}y(?2O&7^P1}yR4$O8rw~F560ra5!D7_Q^6}`48O>!6awnFcmh`+*A#1Cug zG{F7dbo}PlBf3aW10{EYzdoQ$H+M-qe+FQR-)sOt@^W%T+Vq0WZU##s{Vk_dc-azq zc(4-EIbpA}{pn%()ZtVE&LV;52B*LIMx32OkH~t~fRhgWEvHtH1`ZYCv;jEtoYY?} zqk6fUZQknWojxEg>76kUhm!mL09;ub1vh^vT98h~+Y^|cER-hYwg$vweSe5U!=0VL zgS&WBDAcgmV^4_gJ=X)7mC9f3sCHpE>A{-1+{KtDMB!uFt@k3*@FBduo3T|#*K;1b zF|NNM(BV0cjR14&YbIP<693^j56(9)2Alk|*P#u5N0ZgG$IXe34g`lG=We_(4Fh6W z)20H(E16U(K0@p7DALoQUQP&((9qMXLU%-==_#8_xMKrR4yS0P9YR-SYSdW;377P` zmbhbp&wV{jh@p;LhLvS>s4i{lbM=@^5`hi)2xAbh4rg~jc2gg5{#Z^ zO$77jusH1rn@s@MVv=NDK)ZsH1#lwt~e z5k2P$L{RR6`ETUCugLqB3VHu|fV_VI8O}$zaykDolJlPcS~(LeY)~e^8G!q}E(&l{ z5g}D%gZb&h-3$B;*i#B=i`qh@#Z9t1H5jEf;&ImX`%LPac&Rxy8LnCZpkw}59cOU=Cp zaKAT+!d3GmdaCi)hRB9#M|MjzW(f`c7W%4aOpa*Kvxp8q1rx(4iO{YOdKzC!lr z&T~3(9vgha-YzkubAs;wOv=5B8d*Gwbq;F_U*e;{5A%^$)VC_?>EWe%YKMulN(k83 zW)Tl-F*(v2mHSW#@P4urkmjMQug7kl5nHxqEbw;oGR`*(W&Kk9c5cE+BnWi~^z9P{7Ta+%?_|gFJ zFQ%ild_J?~Q{!v{dGK#F{fwJu47D1Li!rgkHN-5p!$~bw`(gX2GYxG^cgNJp+N?jQ ztTmQa^R-aUY@<;^{mFy_C3M5_wzme%WKS=@`=9aA3>Y1kQ!=&- z+e5~7hb97v?KsP|T}8J*y;>@wU@3yCJ`)!^lpOP_6Rte{u&m%iVb65(a66JG-&p&j zmp?&H2=Z>6n3;h5)1UA}3)5a@ebiOht89XL+I34s``(WSYGlC`QFwBu#q=U#kQa`y z!rfxlRYI6AmW^NK2TkMUQ3i~2ACxkQ?m~$2978X7T#P)rW&;qT|p)RbR4{v5zU}K1>)@;?B@iTTP$J3*$d?WH!{#60iA)_GB2-|5vkG6)TQ2q_>`ufn~!MNqgoGCr1I8u z)8Z}ZuZxOY>%sbDU#^Z{f*-MJ4l(f9haNb~ld*&DbJc^c>jg|s6SekxWV-R!#p^ZsUp&x2%%RoJffD6d{TwL_jjG663CrUZ%1fxy!n!*LK4J zpG!4RC?oVY(jMlz{v`Oi{$%{>^B!1Jsdcsht>u)T?uEs$bL!7y>9oo|7@QsTYbQ8{ z`)nClsd5)>@EskUKTL4yx>Xjm%~=V3>m;NR>%qL%$!&r>!g4oiguzzGs#`b_IrQ>` zqgILircEi5otb(Es!B~aOI-Jm3tN}TZ!X||Z;MEN^wdhYO;xZhLz*2$dJ5&jwpGEl zim1_3sMX}Avj!z)E4;4z25^(st?rhZGu;<}B9NwSdy{Uj`c1v#bUbE(RIiMu9V(xqT7D-v#2c z7Cze#Zs8;KWjG}3yy853R%pXh>uiOzR(R~+L{Mq4)0i8r>)g!S*MM_i-F%>D9tf4Q{&Ym+I z&}oFuz~*`JbV9S}dj_E`34Nc?u7p+q3e!b?}mbH}>q)B&>_$ z0hCQ4Ma-Q%2YZe3XNM`>0+g|u!%I%K{4fSF4PHL2ofgka1tjk#LeyFK3djM*@Cux8 zqvOsD(U@Tk@}BBq;X1_GY)gwDs+bpVMh5LV#59c;s&p6=GR|1bx6Un(Tc5)h4^X*A z4p4nam2W*%u24>satESH7RhE8&ki)1ZI}XRrDczvb1z{h5>JQ# zn#AMD{l8FD8My76=SAi;`<4Vj=dYdyP@5VvU(oy&)>&<> z9#|viQ7?r2qcsRu$W+W{h1Yb=I^-HL} z=!$w8T#qCyctZr<7=bqtlz9&7mCiev_y0aRGI$iK74nj!lQ4_vVE^}6xZ%8$?8rG~ z0gw*(C0~LQKyhSHZ~`cfoC;0=;Mj&9V;jmdg@4(GBm~>g5(FKj4G~@4h86)E^Z8rR zhO&a@uUt7>)rKw|(1u8$vJFXGF6vdZq0zP+UM$>Q5%(XCk z@nOZ2$vw@Mz;F_{YgFMDiU;y63b3mwAl2+lL>C|bF@%SVEIzv${AH`b-@h9CA6J8~ zJ$CSPXAH!n{C9;y$bNA*y1S|jptcZ=;YP5W_p1HGV#F5K)XUZmg&b$>5_Qe4v3Uv4k#J>F?wXO~^B-?NO2zc)hdZqX45e;=aRPc&k# zcNyF(z)MqhA+i+SN~(O|wqk7iHs1&)lPma(?Jun65Q6cAMR9JA_Xer1^|#PVj=u9k zQ(+Z5me`nVz6l=b75r5igs@|2Q57`kr8EqPvaId6voA9Z`R+$Ik}uJauV5qjt`zy+ z23nzf=QCWMp*{Ka_Xnu*JwUuj!4vBK0)XZ6UH0sheXJlOGX8-G70Ndv;U7eFxqPn_ z`94u0U-Dt)dkdIMuHdiAhLFftP!+NvCXz21w(`9d9_bbQRT_juzJjWtLCm1?Js9LD z-$Up|@+BJb6>KEmt3tQs?=f7Sp>eGle*Hu7qw-xSUZmg&b^kDca|BNF}*M3>9=YLV}pNoW>EPa338OLPdAb;(U7lTBl%t<^2KF$CHXF5xI9C9^6MW3Q01Ey zFH*oYHu&{(_$im~4_^3ggq3eZ#y=XNLit7{eC&Kz%6FB>_qhuBk`F82AArf^3jV5W z2#I_JRUsQM0KsS;v(U7lTBl%u0@_i7rLirXMF3-@O{QAoPs(hD=7b$o`-9HgvxqKg+*7vlP zZ$!pF386yyMkM@`i7uD#4I;do$rb!n*$@)>3aUaj#6EB|Z)mH0J9rga<#o9FW{uhY16$=g2Z?75jIRQltF=|WD~#7wmY#n# zqQvC4(pLEzB^A%Dk^tA@7lNk#9@;A31A4jZfbTHZ<2SegKmFc~_|4sfpDJ7B31PCd zRlXS^V)9$ERlY?~{4LyypU~v(Ho*Pf?fA{zL3ELx=1%_nfIsVktX-CKea80h#!qOg zd=Fr;RlXMhY?bNw_u=RN-d6d3PzjlQ0C2zeAbxWX5nZIGnF!V*Tjd8r`A-T3p|;A; zpqBKv@VCNN`B@-hEBIS(tITg}t4weZTjj?v>8`LOHY2Ofv=LGt+LRmwpG>up{?>m zkVj~%ED`)ikXC4`{3u|xReqr&f&a}``C&;*ZIvah(6rb~Ui%{GtF1DjC@ZV8Ren@x zsjaf|{Uh>SW~=-dzR_Fsx4bk6!75wj$E)Z)K7ihjLwY~|SM)+#<(~*WuJGG}C_I7q zVNLxB;C}B({N|n_x=2s8Rel;!Xsi5Fz+$WXGXRp8mn+hyt@1PQLeJOV@=Ar*|6;5B z6nN$uFOGK7R+(Z|Tjf_PNUN>#F9+bvQ&N9*tyFE5f4Vw)uMLRH8vOb|97^tu0l2a> z3U2;KXh9XW%4|}wRVE&5mTIf~40x!rRelySv;Amz=i_q>Cq3BeY?YrwB(YU~p0TwL zR$Jv47}tLR=+IXAMZmOGehKb>XRD0y{&DgT_s4XxmTAtxj*j`2h}T(IS_fk=EUIF+ zbwVIPol~+;UQ9EJ*AO&&*M|gO2WTdvt1~>&ggkRC7mo8%McHT^gS|tVB5ACt@?k|T zV-D96H>#jnzd`Q%`fIS@;WJu?JNK5$7EehYh)ZB6YC7^gM4 zHT5z?)lsA;wlb$7?MNRX&p7uXf0}}_SlF|Q)ZP&(dYb7Vl(V9fJltUAmwN^21y4|h za`j*E6hSvnC-@0|L{=%>&gVG4fUK;sDtCPCW{&v?xpDJCBF`B9s~~Cb5flXO1e&-8 z!g!V->kMp;G#+DUavu%&dnJ^Jjp8E{tI!UH_MmYuGdu^BOQ+ zc;_h3lJ>6?QflLQC1+nL!l1^=7XbYn8f64f{tzhkhn~(nS z-^5Qk8zTMJ5kI%inlLtN@$s?*olZCzH+O9lm`Qr(BIMD02Fin;JXaf)Hm6}?$X81C z^?yyee?lzW31L2m>yF5E!q}rGnL7#0)~0grfqq@O)!(fdr>4{X2MF`#cyLtlf=9(s zu!^e>*e~J`pX;pUG|tepdD?mWCL!GH^zEQ9&sA`5kAH5qAM+_+?s3K89}hQ~e20{C zucM^Q`Y2O3J3K!iz&8x=1_R7sGhGwWY`;3nSrhg+ZzHd<9$c;ulh{^xnhg-FCm*Hu zHVCMf|0|Fw>|NF$>;<^r+mHRhui@8Gq^FTPtgzKT!T$}AVR!voz`XqR3(#AAckTM` zNO~j43MyU5isVaKp_l(AS=pzGmHA}lEn$V8$_i``CirgySu3}Pru=sR*STr)HIlN2 z_wi-zuK>C(pkVp6RJP_kriaepO))VA$VFSbCZbnYBOQJCyPt0aMCfBE^3vWoxf? zywiS#=8JZ8S0OKd)VvG{^Ahu2vOd3xodsm)Pr?p8mGzCt`uLrZ-Ap&TZIVZ2Mt9MGi2{{x7Y>iusK=Fgf(4x&Oew z0e*u3!b>;L6>6B4nKbP}n8Jdp#1CfT$AeHuk)GxPo)BPdvSquh`k0)pw%WIC?416y zuxx9bJ(14ekndrRDc}O#Ob*LVnHqeZfG@)y#s2_Y)^>Bslkx*%zeQrW0^0u&9=5Ig zNfNDSE4V$@Dgc^pyVGIsf}%a}A;<;nfxm|w;69B8=Q)snznqT)p*Pj!K4RKc+|+e1 zMeww6(-_B74P;#!m%cf=HGF|(78c_Gh3hwBuxw0p} zNO0os-fWjJV(JSdb~H1;#eHb~$TF?waC-tcvgUI6LCxj7WGXita)X`08Yn|&Z)cs8 z!#9>XowM&#+23LXUY7xql!DGaIbs;z9?Pjz|u}u;g0q;m%DJ|Lxqx7%C=}29koVk5D&7(aB%Q`XxL}6Z6@BxQ4%akw8x`Lb`^1iS1F2s7gG?#I;AE zrzGBFHe)VZr2SGq#{G!6IZSyh@W*);C9FD%?!hY#|?Ia~$S@CzJT6 zu{5OrB{Ew$vWmf@$>3Ka9eSEADHtnfotF(Ojn~;1{=)eIC19rt(vSL8L>EW7rl;8o zMAc+WW)!50vi+GY<7ces1Kx$ zsSd_6^f2fb0w&`o3P+W3y9jWT0}rkmGag|J`j$=y3yd!CYFDL)D4c4uHmnp z!W_?VhNGp9nT2dsx=Wa@TG!E2M;lQ#wn5^yc5#r!-3j1k`%wumhg zAuYdpx$jf>76z3o*N~?F4Qs{GRfO{-{H=(Do@P6c)Q*8P+op_j>Y!nL&M`++1XUC? z>w3at8nlJnKT#A*tdYe)rOhSp9R~A+=05PZnqZMdbQ{#>LPjW<@)w z?dF*$T%86_cIK>+X+Omb{SzqLufJX0ud5^_*Om~vIZLQ9j>|xW6r6#yep^jTbB?2@ zn9XEtuNmrA$2b%0y=<=Ym^0~p?Kj!hUuIjc2d6Su9OiH}>YD*=ozfcBd9TWHJ!4YNCnNAy8$kH4_(@MXF<0}}`U znwz_3M~2^tKcaDqz0`nj34<5$!o2AlCFBXF$H$jdu{_{@Zw0HEYT5KOJA-(<5LAUb zli^earl;A3OpKO9w0~b+)rpx_3^Z645@c>h1q%_MwH%z(nu=O%i#_*900)O zCX8Lj53hSP;={UsD8KM<6U_gI0faLgj$e--_=-yNtHB;d!hIGJu+$%r64#r#tM`oBm2 zV8hioMUYJ*EjRxn2;x8+vaD^Ou)Ip>=OTKp70GlI>1p<0``i#cOYR1ax}>0V;*y9A z0pNJKa?A!vjwg}h5#)FzITm%#&Tkr=Q@}u){KWiX$hTj_S>{alTzFyGc^O|$RTh&C zu0IMiC_ z6zSZWWQ^# z;K?cgtn1o*3MW;?zJRgENNjrA*n1;36#$ky$DCI<<`~cc@8Mc*9pqWAU&>m^K8VO+ zhH098<)eHpch~G^7+i`kq0Dll5Ty8XK%hAn^?NNwq4FZd-X&)!J2Xjw$6FYB-z$s- z_f@*l9yQU}qZTY_8qRrf>p6byTw&j=p6`e}bcLFEe3f~ep^1cf95x`2jAx$3EQNU_ zU9(?R9?R|FOJx`qYOH4+6tQxqb8+DST3(wB5Fwtt?Af~cI$+n3-1BUfK` zdK8A+K$iP=QrA6P1FsGb+5OVW@HOkcPM7Cs!nJkPgY9B)bo2s;Djvi7!N5}n302y@ zoK<#-2}z?oNEyd+B|-2X_9YVl^UsZy9fgVc3vkU|CFVe0yf8vB2SLp}Z-CCBmY zr=ZP0TPO6K%orfbaz7n4LW(j=NRQ3fn)@USM|6*6sI5ELYn zZ|GvwITD=b)&|eH4*Uen0?|IvokWi(`fZ{M7naqB3#2}*Vtp_O(NUyl@GFLk1(GG( zPEyVuk}KpZ|7&>n%7&N9Y8}Mv;Ypw}+4zEFmqQVXnb49dwgY9!f7%#Bz(#|CRZeWAJS@!D&jp zsgwQ1!L~Qpsni=l#FVu5o54F)23!*=^+~q+RVP`NSneW}vW}5QhV_-zWMpfryeZL| zTNeWF+E)usJIsJgOayu<>TIKP1Z2|6eoTAMhawhD7Q}mZVBz{b=**$OPk~cojt0I~ ztMoT0YEKRtW=9Nn@bFl4gzQ%7q3%J?kb8G`Y% z3*tjV{*uMb_owOKVwOk%jQ<*bsfM59;-PHbyAVbIE2P0Y9YWMNx5XTx5Hda?p~8O{zZAP~%TSvb{1O%dFrlCQYyU8h<*x>TBxbg&$Pq z|Gp^y^fc%TCKT?f3U)t(eFTynMS98=X+BJ#GT`G1IK78+otqpj*^(d;lt5SP_sbLm|)aaROWF?)P_KTdKZD|_)tT_kIuowd#r;~Hlb~_T-UfXPQf)+~ zxDxJNaG72H5x*4wy8!nR{+Ut!5#NlT{ox)1cNmfQO;A>s>6Gwtc^b;aYp6>rQ5~xY`9F3@6 zQZ4{6XQPV>kCyX_5TC~);mJDzsy%sF5}v#!pxTp1DB;PQ1FAibrDxd3A1Sr5P(v*v z`(2n*S#rR*r|(8iM&3AodoWD*7pYM5-$n!Su81tF=B($YRh?Vzk2;;pQBSd|?A!feCn2Cd@@%?)C1u7*s0BP8S zO7I0pus)%>2V#r*Aop^_d)6Hg!z+scsZ(32h>?=ZnuNMJ2c?Xi#Ox9bUuBq783$<$ z3#r_upwgK3>eK!P=)-VX23PZXM~8ck6-$IDQ)t01dZ+ik(k|-A@Hyr&>#i(vz|uP# zq{uL5_?ATn_dB|nBL@N?TifxzB~cuvv17iJ!#n2FE#?wJ4(^bOwZwDX;HWIrz;WYBPp)%B{&?EDkJUJ4?+eMsf^dcs zd`-r|FXwwp;Lc<{UJaRWW0dKJmuwix9?4tf_%f>7aGhvG(Ar~$Yei@{C*#@fGfZqb1d*!-{0y?{E-w`HC{ z1$X_8zy$8Hd65;L!>|m%VL!rlItlUSARZVi9EJdeOOdR9Fj&N+)LN}MENO}{h!kQX2LKv=9zqiu_5Y5zUE7Sfo6-bLNX zkFmN#cj{rUl&(*q=D5g+LNSU6niQ8EbP!MKAfD7g+%}Hct7#mdsC)6toEZ;*!dDl2V>{6?5|?MPwB*iXW&52;aTWyAx|ue z1up^3dE<-XnvGa@UJ$rBcpk2o=a+)7K)>zS7g&jVfY$kV7%bhZl~>6%_i?$%9gf zI1m5CE-2~{%E?{!Hy8zSaxsuya=8;PErdkOZ74qMzuy4|;^%&UKf68l*8(W+pCL{F z;Lu+;nHMw?*E2T*`$IOD< zA~5ZdHWsnhJOAx;L^+Uw^ur92m<-3=@vsUB=&nI#)*ts#g$&ZYJ=C2UI#_oCC2x~ z?1f^|HR^?8`bOSgAd!^LiGPSlIwPB?8|@X-v84GmsKuqzOs34Q@R2mQ)86Y4nPUw{ z+V&34Bx}#eHT(^ckG+wP?1Zo4i?IA6m(%f0LH}Xlu*p0ES2lx^dSfE4F3-qPiGJJr zEvLD;&yZm5dHk>*H{!ivC^OD}pdB+%$)MbOg*PFdu&w|ki#2-7hyM+sZ}B5n6zQ^z)sbt(4&flmi+csG|5LDAme60hCdVr! zky)RQl7cg;+IkCGbpKV4A}4 z?lb>ugy|hihCTpfHb-r6{d+-E{maTfZQcgj8q7Q-nX5E}RL#9)EohZT)m)_9Bu^8a zc_mxoTIcN2-QOc+uLq1k)VzKI@mL=LT#*H|~4~}U9 zAQo_m5GR1*ybvdV;*udw0L7(3oB)E8(T-zOUFau(hOZ?K`%0=wb6Ad<)6fsg^R%%* zyvfthFs=Xy#x<2c?(!%K>Qh@fm+7wy|SZq%;c43B<`ZsFg5TF+dap5t|{{m9TpU} zU^u)jSWhswC7NWgmVjFkTwB1o1Um%Wnqa4ZyvQ;bPf+uueeM>{F4PkL|D*xa1h%gZ z89;Cq5B7K+gSj0ZUj`b!_rWpWDSZFnA^)kq4>05q^gD?4AmNz8PY{+tiqo~2&u>{D zk+N27orK_f+j1EPcbHmUDJgZnn9?;}j5MpOAwRY=MBF!gwdi8yLaNkDt{qf+wt?J7z)QnO1DZ*nFewy%F3O`HODEu?P6|#~ADDwr- zkkvB`*{LBPC0wWQbA;Cf9M)yDwP6sE?8UE#ayknAIVa^(aMYRj#+BT5%EoH8LC=&s z2g}81!vP~=-u@M6%D4<#&7{TtMF7MAt|i0?pt#l$CxGHcgg5~d$9C1VBUdG;Sae2> zjr`x>=GoXpDN@jY;|Vg->vxx%7YdFukOm`?26<&2A$+i#Cs;a$#gE*U&_m(LaIA+m z9KljJew2q}jkMtimcsEP3LzJ0Teeb#0j7{_HttLz7Fb@^m)iU^1?1p@+W|XW#1=Z*JfDx zqJEiiZh?P}x`Y(8ITW39r(5_#nV$N4(NjO#4;645^qn0=dYXj@Sol*_u#XvRTM0%_ za~M1O!|{{x3V*H&{SSuTPD0bu-yUF1vUw&qxL`2513qiv&V%cE(AE8C?p^PN zyyQjqqiQD$CAvJ^-3=dC6u!dWs}lW^iS7wf9YuPYqsZd!7=&{v^z=}d`Ob=$`vZP8 zEspzNBNEnwK0*ZaQwdqCKq5$mQwzO!`{|9*vGMBoj^0u5Qax%OdaJT)xuxGS6A{wJ zl&NSbrdc08IXjnU+0yps5fBTI&)lcz<*?L|l^fzqGaj=S^5V18%}wJpLodXZDP*lY z{F9O?BurJ4W)jF-%~c=%snlGt!OOekefIAKgZk4LZ^1d5^}zs^?Gn1->0uL7>=I@3 zHOeL4sFz&_$zpv1`<)4FhsLqr`IB(Hdb%@q=WyL+0!TW}a^x!}_d@q#8P(~+B@8m- zRQtxM>6^^iRKUrPty*&q8&9ge zaf|DQ_dNyjsdi%fE&9lPhiL>tkh#830U|`c`@@GU zcj@~My2NsKVa>%C2#r)bv3%Z-(zQZKMkFVb9k9`+N+6>TltOj0V$GIQ%oh9h2|K9RxUyTI*(=98%?BkHzmDCLp zzVqCBFry@OH$@P*1)m6R7|MaG-aTxH65wowGP78S9HBDg232v##h`D=;jR7G(2 zQ$rX<@Pctgj8VjWr4Vh3poM~lXjQ})B?PyYk&3xI48ctTAh?^UQCI+7SQ((M3s)Y6 z@Tu(L!!B6SLeeYS*J(BHLOb@U=ld<|*C&B1xj%KjWs)8jMY`z4er&-KkpH~G-G1&eRjGpEg2K%Ba zSen5OkYMyQ$1>QLRlzb0wm^c>(;SCjW(j`mI#tTK5%N1p#>p}n9*7u_VI8+6W%$_u zBVbQEMV%d4i4?7S`$+3sZrF_BJXcYYzZsL4Eq(h_Z{G&AR_Gj?UK;yQ*0&JmQ> z2xUQoB+y*DWh$i+r*EZy1$LgxzxT(cMY`V`Nl_dHQJ9k;1V0PNZpp5@nk`w~Zy4je z^JtQzRr{O&4)XpY(rFHt5cFgjjQ%KB}A>OHaR6ko!AUv{n-wktn;3UWX5fn z`nX!Th&@oUEMT%MpxTQhMG!u1gX-g{vY;sm>eDU==ex>0(&|B*Ac#?J9GAjk=Lkd= zcwNt+6+2GEgzrh+J?hNyTrX@9{R{h6qIRw77$Mr$s*aIasz0lWRL0p(c5Eq32I!h? zxGacwFfy%jC*bF9%S_p867RDPof$31?vAGHu9$>W%2slfO6wk?(u4313#hoT#z>b{ z`xIb2oXxVnS9U<@>E8#i%h}wru_4#%4#Lm@8nj(NdzQa7!fbIbiP^eJ+!5KvulYSP zd#bpjvfqf?mi513HkmhFvwsN56v>m02e`ity>td@!p5lIyk|W>m7ZL?5CEyp#R*1u z;u#Y*0m_Ew!0MelEl;j5yBO2(sbHa|EVmiS?HJ}ZCykt*=48}wSuAXkOAFh&Tz@pE z%hl%!7HX@Aw3En2a%BTO%_)^cMuEt;(4Oo{euMBsg#QCBq;th}B(09MKlkK({UvB8 zxpAP2`Cl*;M7=zJt??h8quZ4QaWoh&)K&2_mi(NU z1v+W4ch3-~4G);iL>_VnNwt!)nd8P_egFnwNU{a|*gpA=);@5rB^}tbYz=%&&W`jV z(@y(2kcv*3Uz`OWyQY|}e==qdsVqcKv9x561j-kKf^0HX?;}YbW;;W-*FF_WG)&94 z5>Dp0W-b6=lmdC9{Wkd_+&E>VvjQ&vbjcqL31`c(ZEy_x^(Bm)JC@+F0v<;&FW?e_ zCkVKd;4%U81Wy!@%MfO_2;3tAj}q`ytlpY{pf+|s&}MS@mimXb-50iP0l*`08-_Rm z6t_``6F_k@L!1DL+c?AtptxBfP5{Nt4sik~Zcd03KyjOdH~|#5X^0a*ahruW0Tj1+ zh!a3@TZA|P6t`uF6F_lWg*X8eH#fuypt!9=oB)d3Cd3J#xNSq60E*i##0j9d?L(XZ zirXQ?381)nAx;3r?HJ+&P~1)-P5{O29O48}+%6$b0LASZ;sg+!*_Cw%`*FL|-CPcb z;D8D~WMdf>9)3j|<{0ZPwQj+>%hk>A7Sb00^ts301fA}B=^M5|F!KxixaK&5SlF1* zyBmVJhLj7#%evoTfcw2p_Osuiigy(0X|{mQE828#yF?=+=5%y9y=c%*`=#t6Hl_Y8 zeSvGXlf+2eTqq#Z)%=)?0Kx)CEWk3^kuaDv;3b3pIr}M2CYpsX$}@}ENxGI{Wz(F4 zh$(X!er46L&h^sfa(s!&--)23t8Y|u%niW12z?{mLAoOCOIJFFdPAs!pl_*@{m@$= zFSSYd`G=udMf8FS%zXTQFeXNRo>?FAQTydXfJ~Y*AP@6>{9sh61yL+po<{=<`eu*c z2#0^dqLse*W!Ibs?xfF-neze4IJ!m`48zJl&J90C>A4=-rtD6p@#hxmtIBRX%WfxV z&K*U1(lAf2FUG@41zZ|enwV%&n<5BznI>S4#)K4;uMI!?8revClEcao37h^nL~MeNhltUxU&jI0VanZY)a zqM@fblQo9=s2rz;?d+(kC{q|Ez(Cbeq^CIxQS5=(MaYyjg+*1vnr^0-wkwFo;^vG-PDsf4#*RvPlRMWeO{Yo%#htDDnAXcbmB*1r2RXL%-U zQ#H;epmsN87E82>`B?_5Z(=%k2`Y8GXFOT(e-1secNjo7p1tl;%<`Jd74YtIG5ZmV zxJ&tEb<4e+Jj&r-(j+l%$D3{84_G$ew^x{(?`9Xm($RXp?6+f&1DAHN$Mtm6T@aqn zU2wCP#^i3g*+*gSotym>mc8)(6_&l>gA|sX_yr1ckK7!hF!#L8p$c<{+Z?7a_p{9r z3Uk-oELNC1)8{X6oM^xv1?51Buft8I60tiIhPooPK`yVEz~9%Q>S9LEYbW2}=} zjG2{}FxLYY^IH59ARMvR#sv{?Fu}P0GMv@cQ{y!BhG{fA&x7xWkw#>E1?yvuS-B#G zz6?J#-De;W)coc6z(slYfO=gl<=+W6o$~L7W3E9A#h5M7$~iFUTtOBbLo;+8rn);p zE}lIXk(jRg%c)#)2-KiB(2QOD{!tZk0&sF7IO$zjI{JpIlKs*_Zs?^3 zv79N+VV65GG?^hT;3KcpJoDbps~^!LWZYwY2_YXx{&B3D(~c&&a&l;V%-&)wZ^V!v z*UH*&F`iGjc`Dy#CGu>}L$$%_Un1o41h3Fm)ssz~4{@FZmOF~{^iKxZ>BTVPxEds| zrVN87xrGGl#soR(_00L8Eb|bA!^9{fMqBNdmpZ83&_Ts@mB8vAEH=+vfOuh_5ts9B zPauD}^}qz}UB`d}FCMhRalJgh65jEn;g}rK#!Pd4a(fC`G#3gb2oQUJwCTJGd_4(T zF<}!%A7mVslC8kUvYY=RzNpO~jRI`$AA-|6IurNi%k(?$+T#Ts2#a$S-(wNu7Sqy# zm56(#&vma_sUS%?qZyOxgee>F_Q(~dwuvsR{DDMmcx0sT9==jy)E(yClRMLX27cY1 zxrpNqT!+(Rq8S|WY@N9d0_0{O=e&g$ll%$6Sc>EgV3Pgb1{BFhNWq)~uhP92yz4cG zh|eJnTShWO!v=a=NUft~Oy5eI2<~9t93eeDhgus;nMIIYom*G~$%K}*s+LS+Vy8-N zp=YaIoJp@T7oj{#cfa2o_C;xV-||H%m-kTvdW46VSp^sag?11f3I8;Z)P5qK^k0EI z2k9Zgxwdg?*VFdGwe&gQ{1=qt$S_}%Y3~N3w-H)wEpp%%mI;qx2~v%v4}hR>?IWIe zRNc>93wEFbu`XRy=tgsKbGO$!nK8ph`fFecIwE&b!pXEFrQ!%dAndztNs#SUkcBm? zfJO~uL(`nli7&{;L5Gkli968 z)W|d0hhWif3FrM>#mqhd%^}x7gL5pF{p2dU3@kkwHj+?JtmlaAIe2eg;<)A}2te^_ ztKQN2uM=4S23C^@-i!PX6Dc!`ul1z#K1xl7pHC; zI#j1=n=;Dz2!eQn^&mU^gh-yWOSTKoH%p6dxqr1k(K)TvmuITf5B42X}IXl`P8Df17St$y2LxJRH#o zErf@=q#EFmJCAp^s)vtay?q3}k%*l@^#t3vp>AkT&&hrR z=ENGY$ljZHIa_lZIK{`p%8QbWhszY=Mm_J)MN4~%iiQ`Z6HR+?Ac%9jU_!7v$zts= zbm`Ycd#-Nu@UZ9VK<{3%`VRIK?m%Z>0^D;D0Q>^CUx*VxaeEQR z{gCzW{Vw~rJW;VW1EVK8GQih}j`;0nKM?gPP|aUg&j>Ij>6Fld?wFji_{Me;~2 z0!K8=J!AyYl&?GqQ+C21?eTix_a58n;*YW1gY!8ItVv)#iOBZmFQ}V9Ga6=+CA6qO zRxivry$JIGXtkii3%^_gQqb>X?NS0gG8brn9aFD86(hd8muw^3Ae;3O^&|4-e;=(p zye54GU|f^#p9$B_qVxv%)vU88o%Zpe{-W=He*&7W{|PTHSp{7IRK`MPo<9!1W(j_3 zVCh1*84SyER1zjYoz!0xf2%eI%5$cxCc|Q#j@?1(NR+{6NWPgMvs@o=#=OVaTOdod-8AiMaLtzZs7o(~Vc8Jb7at9@G6DfT zhicRV1%{Jn51R|!hXPJ7z7ztW6=KhxQQ(C683OZoa?hrWkbvFd^T71Z2)}-cuTEUi z@+EleCGUV~IU}==)?SWRz%UsYLec?uTfx6~*V60}kLtii$X7 zam&cPlXSde+d-Xn{z*A+X63vN%g4*pQKQF$Sv()mx)ieBoU;BW<>@p7O{3Frj*^o5 z(1wgeeWzjX3&>1v2m?I}C=;*7VIg)Fm%3tZVFFSMXAWI}`@OB05Tkb#=_y2Nu}{2i zRmizfNP0?0JeZU+bFuM=9X(G0B*gm8Jbxds2R?J#BP#3URK&wPavGdct=fTPT`*9X zSe4|~^g9dfx1&f;b1^0}5Y9ZJ&j#9D0z_d_RlIE&;2eoZPje}PnNLvTLK$j%Ns+f5 z5th77M?kHcNMR=u*c?S}ex_UBk|(~Ma1Zj7K|JNZFu5x2Z5h=Qfzq>p`;i8Sd^?6n zN(g$Ie}FMqzOt41@8YMXdpZ)qdJte9;`=jPT-2FD47q<6>nCFPa1%!w?yI)qs?Iee z%2Y5n@u})bT8muLsrvce&Sv%=W{xYIVe-$FC*<@}M=X8IerkBdqbn@Lw zEp(%DPMu*7f;F9kfN5a4%7$+^hqNmUw2Cn&U{w#zYo6M9NOzBZ*rcJf)ab!$>~~Lik@|Fuy(En{80OHSWtJ8W;9&0Wp$B8%)isYX2hOYk zcV=sD8A&kL{2bZU3#2|kJhLUd@Rk!m4FLO%?B}hCMQypqpi&k|6@3)n=;C{Bd+O);2)Hv?5GwHDPwl9g8@4iDwBN*T;JXqVqwAS)e*Y{y@6aqXa;j z@H;5P381)xL!1DLJ0!#jpt$daI01lT|FRie{^8m`qOEp^l-?Rx`R zhUWNdI#?n5t+)yE7Tj=51fPd8Ew~Gx>x|`^+r-9%|lzDD<^iH%v z*#KRWm*=l8%0kD%a4g@;S>;MPPfM3Ye-Ne>Yu&FEWMiZD8#QYhq7@b)HT0@`u;crP zkJ9YYgn2aOZ-?cTikdsOI&jSY0nh6IoJrUm*~ zzxFfRLiaRp9m$;*3nP*jzC0<%PwJU!`R=Thp+AybNpF0Npqh%h+ zT5&vIW%>? z$#wb8H}T`halS5X-y9^JvCb46hrS$Yrog*7-i?6y-WkW7_9}j=6K89TnP>iGa?}U; zH)Tqg7T_707Vk9w9@+xtrwbr++&{BF9NMzg%DWh^Rgz4z(Kg3Ecl@|1(wV@Yj%}Y<_3D z+3Ii*1H!IJ7MQ+vwxY)+_2y%?w`8p-P*k zj&!k_iOfoM?M(w0Gl!r;bjDy;xC|b61W=kXQ^V%3cxn1B10&SStUs8x55C(Fll}wx zn+wggcE@nO?#W#pGg!Zmfv~90AA*|g`YW+Q0^uG@30?`^r&9?~ASuJYL``uaG)Q+1NT;yEko^I+-u8D(0C(9Lj)N&3HaF@}I?!hM-I+I{wQ;wIR5XUZpc?7BPDOPHe>Ny1Q&{nv@cR15nL>QCV8H~&B3{Fy*$4($yJ&p z5rgD+woUVXi5&~>5jP&(CvGBmNL-|OuL6nS0R_C^K>_p{?z;tU4(@{M<@u$FU#p1| zq1SMi=XZ`x`#Fgm3tkX69{gO~MDV=0Nc%+v62VIXXp+wg+#Eau*UR%ule|uoB*NbO zt!c&`^l;85NYjA-2)DB|UtDG9b@AXN^ilEFK8^wL5woO2 z!eTB02Pd*K1DbQzPvFi4Z6oi3e8nNrEqnqV!U5-F!2RBp^Q|_}QKYAyW@~|BP?M}} zsq4NTR8na@uFWePA3}}t4?xG=b1Hm*5SuntuJD?W80yF=jvg*<@&wD*NV&6n4-I`* z95q)$^X&I(Lr;*3Ia+ znKbh(ht^q*jGU+BWBSGpn@kf-K&XYN;-NuD%*qW6dH@jd>`&NADHP}|qLd_C<=W8N zl+|&rL#HpJcwHi`>dJUTtk@H;IDKDX0O�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%0Dtl`%DqRC8)7fs?rB@3F6 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 z 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 _-jqE4y1ys^)Ip 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 L$v2?Hz8ZbRj@g0U)iip7c!cHr$4 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 z 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

6Yr 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 c^kJO{yKlf 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ℑ=QauMG=*B-sx8;*1Il-du=OJhK=J(D2BGp0Q&Zg zEvVO7xW0sReIxMU?+D1O5yd-8+Av~p_i```^HQILc^^$(r;zw`Ef0gMOprLeXj52J zQ`ORztP)K*d!D{vU3Zr~bT;A0>i&=zOxU`v^X?U}AKHFn#0W*DQG2SvMj$(LFF8(STB?4u*SWI4LU2DZ|e#2&g$lS zXo*Ye0l@VppF7)T%HKUhDxt1t@a(}I|&D!NQ$KA!fCPI4VUPo%%Jnos-KBPSTv_rEjC!3X{eW$gV>E&k5 zo_!{V4|(G}ih({OhtED4%^7;wDK*!OO7akLoZ_2ogUPM)ASV+kcL)-pR`fQZ%ko3P zX_|)9AK#ah-3>(&*52;-1y7-XEH6G-+z5%iFWeSCp|(EK;(?$~YGs_mB7$pB+?O07 zvqNK#X`+6^1=j+S?5FH7o`ZM6*(MAYmVGQ&aCHhdxtBx8lCcS&7E2~Znz{U2ghh@c z$uVY{fl0UI+V`4nJ6y>#9PK|;z58X^a);-VKS)d({Lvn*>=f}*eA%d3ie+h7-l;4) z&%?n?qWYwvIiZc6G~2CqsQ(dR7Z2%}{wS_obTBMtW13gEP$503_E zz~Zi4Q}}H8G*bw@H2v=g_M^g!unBbqg@&27t)&l3P132w-j@3PPPH#D=U{(HFBo+h z0s!<0wrF{}w)$mj@A7*@*?cx<`sDY~7{)SgSVtR?bkMBUS4=#3IE44=^|e#wO`a8T zf^bv+oaLJ))xy^DZH-NS(~F*AeSY861pdVv-}&K7_`=;yucG9RdYY}dSE*St2B2Mk@`J*V&EvGd(5#U=V;JjZ9ST5 zkD^@Q7&bdB=vDJJ9>-xxnnzmw612mqxp=JEFV2y9*%ec6Za?^b>Sq3WT!)RRhGseJ zAwR#*eLJ^~`q$Gtf1+Fea<^MO&;Yt4k@u)xmo-e5^uv#K_(T?cN{=N?Lys_lCHZ)J zztWUJmZh@2G#1k(UolL=*6HB%vp@T{o0>fus&-M4r$0jdH7Uge_hH`^FDm-6YbbSv`u()`K!*G zd7dSNOglsw)Xh&FSOn~O%M&+9uO|!rmU|}A^QhW`{_br%=ICn}Nv&v>?DA~451Ki6 zhU2E;n(oriX8FF{vXu6fQ3oVQ)Ej%CnU9uU^(VRVxKXH_CKwep*!$=U#?ZB?e7HNq ztZ{03{`=GmrHAGHfR1l`1!!gNSvZ>I7)>qa%RyY?^~-|=6SnSFPae?niYK%S4i5B* z82q#HMAft)*sVyj77o;-snmz)>h|(1Hpa008VQ6gh%TW%EXi`$UG~!5I25P5isJL8h zw9ZjV5jK0!MtawCyOyu15$^2|VXrXB%Ph-$ZP-X3mOWy9$l9#@&3zi>uI*e-XP7Pn zYK&^#ELcm*njKbLZn&oT#$};%eK982*ZGq5<{K1PK$7q?s()s>+>Fa?og`?;Wxm?c z*8)FU!`-PKrc7M)J5Gkkh@(WaH+HHV zHT#)2#NYD5vd^d%r($YXqLTMjxT)ESvhg6PQv8yW1akzTDc{O>Xdx&9Cn5_&gR`8Y(65i{pO1Otl|~IUCpT@nip%s3 zoDw&e6kG1m7?U7{X0{UkrKdWX0PZ7$2JAwQZ})G&9eFZ^pYL?F@LvP2ZkE!$C# z4W}6V%h`m`17367XragOriPLD(8pI_7!7*XqpBz7jlS!rOVAN47j!Gk7OqL7lx^-; zvMnVm8M)7?oi^vd(L^*EN4|jErS@K>q&f6`G1i~UBx;tqqQ=n@a(X%Q)IN)ru%|v- zYJB~9n3Bd)2Z8S#t|KfeiI@~OtmaKf1AE80ocr|8*) z3pKoSy)^{>sv}Nt6FI|{-cYiXNn3E(<)0{sjObN&E|u&BUucr?LPkto@;-5m;)#-c z&3wD8m)-lpTlZ6@==-#=tsbN8@{AnpT1Fu>=@sWs@z9{zhN&UYMz{>Wpl)y2I9=IV zM)azymWs&gJ}uSKuqK`*X8VeH{gQ`Wo)r3CI;shWG1`v*HE@><=`|};y2q!ANWZ*53AqY8me_*+AdbZ4#&p+D=SCL5L7Fh z7e;7c+5w9Bl3c)~gu~Wm4hZN$1Ufk#NW!#8qXXA`RZAmE@zC3i2+Fs4Ci%A4lgGGM zN6l0Li!@LWcFuw>1Tn>Y&AhS5lKsNDTqoAuE4LIu`J~>VioYm$ML=cUZ)mas#pBBj zST**9a^q|?;f47=6|q6PFvB8OwFLPP4;U=z_$aMFUt9~*Ytw?EMeeXV2F|o$_#rEu z%$*2pkhgA-x`^bs4usKR^^pgnpa;8u!2!Xnr$NX4fO3A-Qx>vRD>)`I^4GJf98>a; z+I%RgxvaVx{XF8P@P5_H9VUFO@SD2_8gJ#K-zz75hf1MR@3g0#>P0@{Hmd7w>J9Eb zr96C!YNuS;9WuS|wGy6lk5aZP&2tAGDAf<6Qe;{lN)n{i6XXHUoZt*s==84{jSJ~(PUZNLuEJ`57D60Sjp+42 z1}dtJ6{EQ*y%A`6M{X(DkyXtAPH5~-Z*azPMjxi4f6kA(GVHJpe#FRv*Jw|6jFVKM zgt-7q;92;ZF$6a=y3Nqg;mU9?J9TnMPAy&24|>D7&|Y{HAg+xE?F{My7C&|!=s~Fa zh&VGXTMWbQ&JzG&1o|4{puT|Kq#CuY&uD*x;}@XV zlT&-Utj*)L={5MmM!w94T)yrNO!-nST7I3Y!4yQzi``>&Ruafc3pM)WS@@`GNAkGC z5EdY(yOOANwn>9d+KGFE{*CJ2b}cOZO_|K=b$6QQ?-DQV6-V#U z(!lOZ1JJiYV!rDd*DH`^h<5I4nwhmpLS8 ziAFjC%cO*k>Ovwvq0#4Z5Q*4X-(1>Ap& zZ>|-z&C-x8Pkz`+pd+z#&E~VcizTi12sSP>?piSY!N|g@<tPe5oZF@>EkKr) z0gT&Ju36S8utRi1;-LW<;ZX9R#$h+LF?C$G&1F}l*1Fod?p>!(dWK;Z+KmBY0=h|O}gPxc^i^u3m65u*hc=?1KdX{B(8LBvJGkAVce{NIs!mG6`XMNq)o#N2!$2I8^ zhT+=Z690c)Qpd-qPQ8hH6Op)#Sgwpt_r;vg^E>%x+VkzHoq6lxU8v5%sc~|N(d$si zuwvK_ARpsna-KI3nxz1sM(@NZ8Oo1CU}J{ z_sGUd(w!apY;M5rDHpO_tB-&%v2=fwd$u`cIeZK+Ae+4wAM@-;4wrpae|JjvWEsAp zsA+>_KD(*DsJ{&w3j|%m+S2EyrH5^FlEYi;5>|MxD|0>}92l4I{egDg;SGAjD0gVo zCM|GliI-D1Ed#zYQn@_%vxR}3M3QU8hC>TKDjzS`g|sqnX2i!?!jUHy)nPZePwqS5 zGCa4IQcpRh-4;mpUMS8VOtA*&4~($T&j#Z|C)mv4ifgUs!kYEbzCapaF+L)?Q>Mnd zqfe>Oiic%qcVxfFhPX^zckDk9u@4Dt=19}#^X+0GjDEgE&QQVZOzguq$z5SQp?Bre z^Os7O-_j~>DN6jKMNF?wQrDeO-HIUhEo0%)xwuyndzgdiA9O{2#2f_GscXs&e($K ztC*^9OD-4{aR|W?TZb1Am6)~v6ZQF+#p*7d)n`HupS#3~41sWLlA)2U2>)yraz4k88Mkg1BO}@;h@p9#GN-KE z-?=|iehdeZ{Z-ZElE*cxYZys`WB>SHjaU|+grHGkgjw7*gOV;uu9YkyRFK0O=$)hC z7AuilJciTn%D_l6{Ke?N{TBzZo1~6V0a{rbTDN)IOFfJD{qCGcL>|rwT#aLpm-lq3N|6C5S+b# z(~@5xdUO?^hK1;~om1}+)~Io`l*c3LR(AGzZEe`tgG566!``nPcx>pwzVM4h)<8lp z!uzRB$t$;$f;YpeKz&NJ{)YZi50~T0M@jMs{Bz>?q`j%i;rAFhvSaT@nL$5J(mrC= z61G!1RofvZt?OQh+Xilsq*j7cvKdmWMbJn4LB}SWQA@Ne1x!6U%^)g!%Fj5WM=~tv zt8rsB;y<(V{!W$hn=SgK>w{m4fZay01{KHl%=MVVIbn80y*R!h(tD@Y};Q)X6AFS7h+IO#V@T5z8ELq?WO zn&;ar*>FIlzghR;Yc;m-ca+X}lzjPvt#^kc!G4W~v_Td;>WDyiRf5Kw%TQsOa)WddlQqzPFI?A063rjHK-Kk1DF9^{n@TkeSQVm1?Y(xN) zh>1H7zTgByCk605w;PaoO0k^8Xh6^+r?elYHeMubHTi@>#$3UNYJ~JhjtC3X7!27n z$D$e{LoTdqtJZ7`HE>(g54Ta(n+z!7xjs)n?u^u7fr}d==}ezwl!X<|1zL?-hxv-6 zo9%7@VdjK|^hr@?Yhvz`*4wClO;zneP5Lws#w?L8+%a4yqM}Vh|629Cn=a&Wx2G_^ zjQhmqf$UP}hIy9GEdnTH4kvp*ekah?pYdCAm! z5ThYvZuu)x&E-wPl@rv+QcWH`@Jr&V~XXrP%BOY+>9~pTjbE@8qPVJF)WjXq3$oXv;2lg5rU-$!=$ zCcEMo!SDq+kR8o6>xqMsbL#z6elUZ06fyjspI({|T%Rln3!rzJItfdVKgd3ee?A1l zC@{nA*w}y5VPLl^5L25-zpc;2@0T}movugZo%@I5{f_jtHWaUyF6R(2hfr7Y+hWo5 z3HtkW=O3W=Ptvc~jMQ(a+^u|qUS}95;R%z;$GiVtH6+b;CLbN~jYvygROvZ0ko-N? zuzPTs?3~Q+B>g%fLq~o&;lH3O!>0Y6haAyKF{kEMKn)5VctKp}s9>^!#6QF4_nv+$ zwQaA5gdb6X_)dpZ%mBu%Q^Q~;2v4~$4kOd%SdnwMf9P>CzWHOVxc$;FfKf9fQDbec zr>0jc=klDhiIL!pFo^j?U*9&b?U$of)7?|I4Niooh`A z6Lw9H;VhsP=1-GX>MuYUbw;wWMD89r*3~&m%GbHoe=F*|t@qpV`@BMAuV|E3L%9;` zgGo_(DuvXi^v5r?Zndi@JG+`y_U>BGq-c?kWZt1gIB^!E<14!2223g2HxV&Pk;z&T zwI#guvf^LU@nJB@hk20CK6cHAa}nk<%KQ+|FyFP3nYQcU-MSQ}w#V>*gt^Qr8rIo# zMp$3)G4sR;R@`L1I2!jMqk!5qS^ftI)# zZa8PtgS=oelY?McN0*Jk1Hv8|>}xNu80fjq?wQqz(LAF%%@Nd^mU%?>Y-R0Q-#QO{ zJ&Nk~1*iDmwmDsi{g=ypXwG_Fb<@q$}umC*$Yo{usr{qlPX~{=r=Jy&~VO^67w~0cjH*xW{ z2@V=nbkmnV!Iz_X^c595I**>gMZwBOcJKjW9|!JMp*P(EPjN1HX6R*iZIT?V*Mya8 z)0=L7SbCR93_p~!5lPn|tG&IM?0w~_PlnU>HXSg-b zOlGN4&Xf2EU$1(JJ=5zYH+1alvUv{7S8;pZxK*&+0OMO3d`PAVRt8|NNj@KBNzjl7 zrphOc5q)5B*k)a?6HyofEG|svPpy^b8`kw+UOj5L7&b+u>Qi|Q7;o^hVJu9e4HhXQ zzCno%GMLJ9AMuu%P|e%X!u&kF+xgt6-j>U#KR|zYd%_g8l2{VT&BCX?|4y|E=Pyrc z((W4L^?F+iJcdPiQbp63_usa;0VZ(3PGv&o7&Fv7Fo}=N7+gC3$#g zn-!TiJT!XQM+A9}?!NOts!I?$S5k@Rx{AWXgFEc?+;l^U{V~bWq?Vo3V)*d1OHV2d z7{i!`2=NYOAGNZBVBm@VJLgJDcXL1JSRe4qV-9ag7aIT_G(ZE;D&c0euI=W!>dN}f zuI?M*bI3t|Sr^ge*f%;u&wuFhEBU<=%!iJR>3=oCD;+w0Y`{ps&4N@X4?^6)a=);A zq2lue_}dZG)mL5ynq+g>vDRaMd^yMrTy|yWGW{*n-@sy(`v>*Iv46aB=-59E4G%2y z|AyL0YQJS2y-YcmEfZnloha-u{?88?jAhHzm}A|Cx*UAq<>p*nm0LD<8I7=Fx=$}X z@6g$s^|E^}qKb@j)quGvUo5z+0J5wJ z&=#Jpgo1&G#s)Jtj15{n19Pr`3K}^k7QUomOd`Xoel?=-Dv5WeL?N)NGaz1(ytrm) zY`FlqF!HB)HsEMkE*g#!P<$ zTctx!&ykYpJ`4zhp{EUF2{)Rg%v-#SYJXaA2EX%;gMa=SaJyT*bvPn<`Y=qwD3gHw zV@LS;YfSQ9qbJGBfdTZFjiD=s2gkmw1Oxe=D7U2pJ%rf{t0?lqDm^_0FF2sn$ArpS z;ozL|^w5XB9Uqo%8q$+;EuS;=;kh)2SEAmB#ow;xdUWAjy%Y-|w`r%IqB@AJR{5vJ zNkyll;f)8mh}w{z+dTJAdd&8@DE~bWgMY^h3&cpd+pr96Q~C^9TR* zYJOk(h229mbyyr61G-ULx!BuPYNS{L0uKG@b%L}BNa9yvRB_U_*JJmNvC($?G(N*9y{^*u3cLYUO@xu9w4e+OaQGnOOhg0oQ;4K?G^{NT)^7)#W zY3$`IOoR-L%^O%YH1@4!qIoN=DQ_%uDQ`UCe`!=acV>P(O%iK!=!N`%np0m6o%=a_@|T^i`t-gYx>p>WZo55ZEaKTMfBD=Mt1(gX**K|q zTpAl3$t}8ekua)xFI34(svpA4OUUFsg%*D2kMC4b50$aFWJ z8CaGONUF>*}o- zP=Y1rP#J+j_AR?w(xmx9qW1+PhJH;C>1w&pzu=|LzksAyy}D81zY+GYc*b+DSjKZd z_l)O$PR4Vu2tgO4=YBqb{M1Dz@Yvr{Lo|Bf9I8C`ii!$>bFai4matU`KbI0jgc~qq zx^9$LBEqhlJc~75Dbk9js}fQ4j0Wx!DI!R161`RlS0F;#?x^!|5EHHE8!4&ht7Pl> zMoJo?FtV*dGU$U#bpH_8cNXl25Q!+BFzrKC5llg^Jb6h>YuxjYs zJ7`I%*K_X-y|BtUjzp*23#*h*CWpOqQp7wk)u0o+pxV`@Op64s@B9mRGDh#eQ@@;l zLDFE%@91v5_Ui0~iHY+sxIRqT^j_?E?xNZ(wR-L%kIpI=#VV+Uq4RHgrt@!_S~~w` zS3iLleJ%jsJlgOMfG#|LG57`k(&L(%-b-w}jk*&d}sjUhCvj z(O#W=O0Sjd&B?EgLX%HHAu5}ciDZ>cVg(KU>5uLApZOj8C;Rt127&kIv9HPVxHGkCm*R`}Wg%(yt@_?WZIEef#Oi zb9Uo(ys&B>kpUHYK{f^Wnkn*Kyk+=wc3S_b42eUI)`O9qesldgv3>-PI6-n!#|AAubIdrN-6e)o#l zCq~R7KC#-3CWKsS4rXK4Pl0ta~!`(gBqIZVouw4 zPx)2vEg+|K{AWEbqtxH1)GZ0lYgX0ZaceSwW7~@q)3CfUvBAw>xN9(1EOV%Hxj!t3 z5KrukH)H>jQbto{*QT4R(#?m{&DH5fD0&O$rkmG6T9V|%>lP~XTcNG7aQqgidi)kW z(VyK`U5I5*oK8SYj)J=+wZs2VyczrVNXipGGFk|Xh;@8b=Q3|t!u7;1a!w%cAPHY4`Zsu}U~74%ImtcECcgMzC{|b33QVHZU)a<2h)BL!IS=-@W*2SK39r z`XCGgndbTBO25qa%dh$63co}$MK<^lPMTWq^+??lJFNMHp#4TcXlec9Z>R9{YL80m zLa&f4NImU#Z-|Ll*ubdm5`6q3RiJpL0& z+7nOD8T{@m5NsKvf{TEkxKCG;c-@!k7BI|F;n`aVK-_TWJ%E$IxN_#f(L{fZvnea)atIxe516gruF3$Co5h6lg< z>W4^=Fiz;K8EAOoLw^14oALU3yuKB$Z^!G8;`PVz`bNA$Q^EP}PvZ4w@%r<4{YAXK z6R+>a>wCJo>>l4VG``K6r7N=A5{!>YmfRgjJ2Ql~SSnBI^YJaB+xRUeyVOe`33L~6 zDz4xCr9_+0R1$9k|Zc*&q3Y!Zb7B)V}EXhWm-6=#pUoRpclhP_nl<80?JYpwJQ|f<)!RdZBFkk z{lYRKB=#QvD`8;-HUrG>)VNh5@kctUjuy*7;zLm5Z!NpbTFLK{4!^rPm+Ae5PY4aJx5eun@p@;x-W9KVb@kfDMjia9jj*AkDy_zi zE!S0)J4)!%aK_a*sv_od93`k|@90cVQhCwzs4gncMYM}ggK*HG+AD}B{&r~i3YF%n zOlmh(s$o{89>Oy$Txs2?RdAy}oxo!mI`ND6^o~8@H>#wia@DgTNb^+Z_%(BtK~~)O zH8_6$sC;@Ef# z(0Xt1!Ug9-rE!1dWx9#?D)wc%ROwej{&Xvndv9bZynN1bQ@(cfksux%Ry$8%!h-rK zwNsX~-~1y?@Qi7P&F6KweGKGi1DiC(0L zDaP+J2D?vVLIPQ6^D5bB_jQ9$%|XI!^mJmkzA=1q+7g= zs_IIC5~16g&-+rJKIvBLGs7fyIqON)W_y9}Y z72-~=u-(NT2L9kU0i~gruL48>`2>ruco|zm*VX>QJ+kMNLn>}){Aao$Ho=X(5-e7H zm@osKpQ)%g_!+baSMwglKhK!c$W8JmpZObQdFFbNN$r%lmV9~n4lnicovKDnfB8-u zJ}=*?mgtcT8a-m`sAZIL;yTwYu52tJV)o3x($;7GH6k*us~F5D3%u*GuCv+GAJto}K< zm6pK@#6M17ggz&?(hD#DxQpvam<#l^GRbi5Cfw%8oxDvP^<_6SPTnS9*j6X^>!pP4 z$z!^L2v;cwyUHCqx!*9=l@@V{`JWb1nPEQpG(I*m7${H2x8rto=v$Tknx4VUYf*Yc z``FM)vhr%&(2faUGH%V}wZxm@NvaL8F1&w^h2OHgZ|O-iJjs>3Z$%C``OjiKO|hLE zA3FJ?!INh#^N)mfOnVD`gfO;}Ko}Q(_VObEIr}n#QGF4U1R~b(w17x?cH0i+NW-g1 z^pGuLrtQf;(N)ycb@1hP)6IJ@=dThYelK+IKGeONYj!Ol_5rW&tcCLINkRB? z?CW-dX|2kxK`J#%*N{~1w3(WY_*%0 z=PwEfdLtG_?0u*E6pH{_D4xHdE3+KGWN@j>;Ms8jQ!sOAf?l-Qk{b-gN0n2RCV}!I zq=RH!)p0MPrUlWii5m^c9Uci zo>oY|dQvw{+^9?#Pvp4Ekt6e$Hg1del*x8#+Na_-wZ}8_EqR@Qt%`W)`gQu)8 zUVO7q7%#r*(&WWAMGtB)DyWR1uX@ix4LzSTTjkvACERn0wc6P4l90?Q^kRBp`Mn1X zMz;qOLzI@KWa+Yf>(WBgLJ$T(GZ*zapo(29?6jG<&a^z3FrTOkOHT!pS z^q?$~B=XYz9`TqmVc_N$J~-a0CK^w@B^ELd|I}MHSnPEyHy}XJ>C{`UUrxORz6cUV z*;|7r@Ol|%Z^cSajEa3-cRSOl+$Q1BeE@Lx0h`+&)UnUs1(PZNAyNk{sM6Q#$*>kwq(_1NBt z*N47y1MRdMr-5$0?RF#Z>gtKT@!ei=S?(x3=#_Vr-y5u$DH7aYM0EG+Dh8N9=$L?t zc%6L!yv{xl@jClJ#KCWzNhc^g(i!^BEsoUWYYt@cwb;tZ*K82!VkG*ZUL!u#8UgqR zZY({L>N_?d)qKO`J2%W(h^o-KCTS@Re|zCKEQtArx=OuR=aBE;O1sAD>hZ$rev3IU z!K&eW*vr(3mOuKG6UW3v##)IJ)aTE`0|?{iB2nM)1t3Ec?>fUug2p%RYJytip4_i- z@mErZ-?@Pi;&)!C`>0F<>WU((MK}y-W^ToyEyY>gCTjfF0@i5=44nmUt(T5NLZ#BJ zUI{lz;K!##@%`BxQHrea=Ppb1uSVW1u+1+>`J! zj~W<~8?2&u@eNib=>B^@GtOX4z4#No7yJ0VidvdroAx-Y0^5*&X4QB<-z;Lv#EB0o zp$Yg?Z@^KC57s%mQa&kbDeqHnsECyGsW+Bm&6Tjq`#D-k*nlRaPKu_de&`K6^+TuO zB)A{vDepQKqW_dr}F8s91N zE)R3W1fexFaafD3J-I=&MGW#rn(}&mjr0<<^;p8g9!e}NAu$Plh?e2vN*T);(?16D zD+l8B<~eeE9y7C0euNQnXc9IMuh|^Gu9Lrnyx+gd`}W0Ube|;D(8{UT448(~;lkELYa3Exnu`3>WOJ zcT{7#g4H|Lt(Fqm@u{K7W!{R(WyY2(WT5qqzvi6k(6O^tQk@-y`EUoUV*3Ctl!6Artgq?q(;CK7g86Vegzg* zXWDzMng%f~vwcMcBvekn;uxHM#W2uCv{8nbJJovnm1y_qcPT+`GulywS=1;8eq{;<$X!0t2VL6Foe=pg0m0tPp z;P>ydY@fGZe5t|j?~k4Q{C&wJvKtW~i`xNKon<8gmJirwRvB-AG0MZP8aLAP z@xkhPH1Uv#2zU8=d$9Khy=dx5yNaovr=xbot8&}3s<5ki{P|;%a}GM^95#+&9gxb2 zXsLX6sr*Z?D=l?)zMK4|@~ggXhfn@8_PjyRV1DU!XKw?b%y7{SGO}fxz=gEr2FC@x z52|N9`T4Wpq|y8QS)T^|Gd)x=S5$!7k`mB@@I)7 zy}%Fc?P~&m2yy=nP_v}h%OubHhP{Bp2fvS3qAou5Ls3`0TpZ)4up@Ep>_)=krPpl; z>Z1H&g|o+0x~m+mDyN0mhlPF!m{UJY=EJEU3JIl5{DRIhNE5$^USM%ESAz+23pZx2 z>hY__DtT=2SxmDrtyK{W?YZjVsL$qV(OH#4@Zy@0x!n~aTC&z}{J zTUEPTK85Ob;h-!g9JoSL!5Dfzxi4StKA7QXh#Az0H&uc9Qi#UqxMv|f>b2Ai`%0dF zQZb3AMB4o#pWnncXP=7+*opRr8~PZ_(ae17kHPPI|3ZIzD@gv<{HO1`CVDTvJ^w&_ zd;S6AgkOmc;j?3<+C@(+n*Cm(Wp7c4Vf8D*Wi_mlz4J!@s7ih&{5ctz=fm3{J> zb)$?5n+%B47iVuJcNKL{z_);UI6H8pc+Qv$lJF2=KHKg>|A-zc@}c6BIBF-|%* zY{nF0{p#M51xI0Rq9Jy#t+yJJ@xdAN#h-YElaI#h z1AevD`zkc~QnHfl&gQcwUxJ4%Qh4!6pQB;{jHIFes??*>24dVfT`4F~HusUPyLi@H z-(6v;hPV&kmozcLK4BZnJg7p=nMh!t{EDd<6`#ByOvMUzl?Ix;2v%l%G7zy9$#pRj zG6p`h_Wceocm57D|LsxrpTC3R=kKtwu3RxjdHzPv#Ur_hdg@mOV{+G&I(gBD^TbPP z&|f*)it176r`B(g_DA3nz} zOV*^|pEC<(^OKW*;hZ`7nw4^efD;VIDgVSfQOv$m#UMI=*21QoGPVlf+tFw}TU!@T8VRnI=5R>Yu@jf0EK| z4d*{7!`ChSB>|E7dGhOGRoT=48N7JGeqqnSi?76jFVPFc*Ix3sf1!dRUYb8S<*Bc# z?OHqu(^~1b?e|AUC~Y6WEz?DrF8*_x{JHp(?%mJ2cR#l~RdMRDrj$e$#D(3fOGkXR z>C~g~`eeKw)K#yy>Y@^dD8Vb;L%#$Gf+t8jt>oZE7C7Q)`kkbcM1j$9Q4kKIr_Iib zm2#r25?&6T{Nwti&4BBrT`#`tvuX*PC%&gd5n|}%w~fIDkDWHKyd1p9+DGE(|Dda* zL<)5K!BzIV%zjrHNp%Lmx9sscF_n8|9|>YAzs*p8aD9SicWToQZc$2vG4^jt(6|`; zHyao6aqtH|PIbkM;NtmpM?PJwYI0#XpJd6k81v-6oAZ70-w~FPVnfG%s+aKPv7b7l zS@8A;dzFWNB1k!SViyC)?<5E;ZXO@iy13}USAW?ozPbo2lL>wGsoe^|5BwYo?)-cS z?)sB^aqCt z30R75l;w~>zNV|>kzGa8)31q0`gKUUI;7LD0ZEUli%6B{-$hQr7PHeF_QAW&Gcb@G zi|+1q3y0e_o-S-wa!f2RU=BUT*%cu;{Ug;2IzIwmOEA2C#P7sBRfIu)jJ?#t6btV3 z3*JaeQ#XmF>^A1On;hP#v467;H@1-lX>EletSRc<)D)%peWcW@ktVMun!IXQziNW) zw7(T$Nad1pMEM{aUQtXkAu*a*2aGxx|@Ok7m7`Tw*0X ztIFq%G1j5dMs|fQO-*P<-75VuoGpSyE-ibmyC~l#uVt~RGcUiAU!h=J_(r;UJKfM* ziU6E`Lstpe({EU3Axu4uw(^c1<$6e0%1DwMe!O>~tE;O&WyGRi-5EMFuIq@dcg1Id zBc$IIt3PwK5(N6pUCsh$?(%n{=$X4Bv@>QwCp%K<`k!%wiaT?c^NS_w3AC*;4Ix#c z&PdtCtvt~0f~-Y1w(9#_*UwIA%Q3(ufcv31VeWyG_ZMMO)- z9_gm50A__{pt(;;9J?e@n*R5dj&>UfGA?u44SZ42X*6zTAH~P@zcm&5l5kKB5+b6i zRbM?pb5hx2h5$&vTRV38MU1FpSomA=0ykYrC3>K%BP+$Xe9l}}Iwb^C^J6Cpy@T2BqOqj7xAxj*XG`@) zkr6F#F9n1JwUJlp9AJS1*H1WSs{M19D~}z>VRvetGt68OSL~z=x)C{CH+j!QAoL!< zo)R#Boj}AVl~h|b^@Dnef+TwKXT}xHQAg8$~ zkoDGsxebJai!|7ZUbb$F^t29A7&&#O98ld`s&~J2wQjADj$ul-YVIKu{MKAmiOe%u zW3mvV!cs8Kr8sRgO3s=%!AtYJmW8&P5Sm-Fx%X;fWEF58W(>PW(M=ID&h0=Ian)Gv zJcdPUx=HLNE}tb-a!HkO@x&06W;7WgZzc}MSiPQQN9bjI?n(*}^D4dm_@2Srl|M1< z>KF*=0*PIeq3X_!2;Iz$4pld|L#!GSRT{Y=BlEUpJ7JtEVJ;|CDRGVg2>iwVdr~*7 z?>zIQI`Yrrevd!1{T{X+*E;sk*!Pi2BsnS1!dV(;gudj;Q2DW5Fg`~7(_AR|-wcxRzdct#`*HxVJ>~;S9EH*OBw$A+66DBt4dPvXj#w$^IJwJQ3)iZSF zJ(v~-d(ZLsm6tp7tN8v`j2OLt_Uf4NSxGM;{Onbp@!6|f);_yxXzW{&MMYa4l=SQ> zF-!Lm+nr*V&8Ya6T$T>tD+A8w%>b!SCDktJom(Erh_F=`>Afae5!WcEb=Cz z%@npqwsWkyQCtl2-m{MuJ)7=R&!*>OI(;)Gh-nHC2MZACK7mNjT@Wc@=*%y>2%UMa zfOfcmmhKa@^xTD(62#V#oAL3@)=Dg^fm8GF0)%##YkfsRo6D|6*mra3w#kMoVmm-_KDHSlm zjEX|Dm_#;qPF9vqePf4O*m7pXJ?LtWj4kbOsM*PyDrBt!CA&LH^ zjiyC}~izHsOIDkTRIq5&^{<_HH8Ybs^)k*XT7yv}psW2iJz*yv0h1RyJ?OWq9ZtPo^p2 zZ~V}ava2lBXYbVWu6U)d?)-n*d;jn%k1I{^N|qgkR@C}#u!p1tBRjG~+!)Ia?!jr2 z-~=b?ry)^d+)HMd4vs~FC*UZCOyh0xv~V<*0Aa3#KoYtn#K<-n0WwH%QOFKGP4ALj zvdhlUyJVL1(!0#FbPqF2?~)yQm!9nVzNdbC-<9x>j+1Bp7|^Y+ex5pY>eQ)Ir|PTE z6}MMUd~oZiObjZ;nTWYqw@=^F>?|5vn)6$l0g{=T1qy^<1Yw))e@nB_Z&tHic4|C} zA!Y2sQeC8m+g?e)S4Z%~WCjJbdqZ{p)ltLm)ls|INppBTqU&wpPOojoadKx!xV*|{ z1E*IudAf3*I}; z`v48MiHLmNG~OZ%HO#QI_Y`}ix9r6@#-nm;_PyrPoWU)a$5%I0cC@ylkZl36YPH{ z%i)@a&M#r6v1=i9J5hbUbE^m$Lm`8%7S#o$q;w3S65&Ma(nCSbf|lh};Eg-n!3il~ z7$l~E!KD>aZ>?@YJ4BdMEQ57ze#o?GR7gs_HA95J?6D4LpvQrc^LiD<5|E~K29npqlEZ4{u?ja|5EwYFFlv;}Vui67 zj!C@O)Mt~y&lN!pY80uVp51&wHL@hD#P|?M`kl#vUY!|K2BF|G=eINgGcrw+R^rNl z0BYa`LR&79S;#dv=-~*v;VWaIi^f_P4aVk6_T1{%zuj|u=9lnNIR}TH9z(`C4C(cB zqrcM7rPrfNe}2mqhwc?a*F}1CuZSOXLDJ=Bka82asQEq~`mVm`xag z*1!O~Wicf?VEVDLg;_LeUgOms;{DgSW<=)K8krgOlILG*RAmy4noZG`t!~C3e1q$0 z=qk+^mA5Pwqq(GLS>Cdk2&rjXM>Y53fu5srm15nUv)OXd9i;JUgLnct7)=R6-r#i1 zCmG4{N_U^Z?(TEKv%Al>((+qdYzpWj?ua$=cK0Dc@`fIE^H$2LPWP4_m{psaejPA`C%fVJ<6Nj3$&#Uiz} zs46vlmT3OT>oqx zO=Yn4PbxW;hDuPkMB!H2bhVUutzbSV98NrJnS*K3F%d)=1BlvE2ObMSM?*x{cwX!v zcVEOG;Jsu4jR3|vv_o;xoz-Z87NSu};5~!jdCx+Q_bi~Cp{hz_MVF+tsrga6s|_|f zv;cCmdxRd&UG%`X=XIOp#lQ611>Iw6ml-{}X#Akw`I7l&u~Z1b(;Y+k?gfxdWidJp zH7T}ohJ%wWj=EBH7ed|U!^%T-TL>`wSht0EU3(_%X3R`nX*=AY!7Td(8KP zwI4%0O&DDUG^us<>I5q=De?VM$g=IeO9irVsK!_An&_*xAjU0+Wu}226r3sP!(*S> z%mBb0iVOgr_{_%C;!Iju6fooEPEV4Rpx1O$nd){InQi3-YG9Yw&hU~Z`A*B{GI#QF z8kXw(F-jq~U$VUb#h?9a)E(dxe-!TC-{896hmlkyYCg=RPFala+HAf`4=Eds(tA?X zdcVdAP)VD!hb^DX+1~Hxo)8ub(SDA_qsH88I%V+MSv0)!CulecP-RcZ!cN|31Q(qS zDs=t@yBB%T>79R}lSk@p-`HUN@e!UAvu&EC&I@_U>0`W@E#l>@Z@+=a=1Gytq^{3Q zWecS8Fgz^+wjsjxr4Lsc7$1~ue94CZ(MV`DDIyO6=Sx;&1U@i#qDmU5qJCfW1onIW>U^tASDJzl*%OYdFgMVpkld1 z57UBxY5dzgM&+9-jp#$j8V0KzzI zD;Lvi0KG}|x|fr-HbIK;a1<(mBI5$c1bV@BVU&dGwYAChvOp2hCNiz+$QIxDfa;hB zwn>|{D(g9n%%}yMq1LT0JcB4)+TsOs@LK~mpGOL7kq2E0+o-a_1y(o(C5r>KkgZF> z%}~MW^ZRBv*|?Qw6z-<7h>B5C>Qr@DO?O}M`DCWu${U9s!l*|f7|1mGAv8Cihamu? z&Q{yV(p~2*%$v`XWst$akuoj&mEX$7?k7r~;JyMpRGj^7@128K*t>DXuEt?KSb~6O z2(Tm;?64%MBqB@rZL$o!xbHXhtq z9@WZ3w&YqY)4@LY!^($eTP*C}2_a23?noy98dSZ>@owoHA@GY!)G1?3{v|50n6}fA z=zPw;F&CEELr;{Y9QaaP{adg1? z{MH5dAcEB7F@!HV`>STIEpxy`FQub#_>AwdXX3QtnFDpgM&ai3NG}l-m=-)A{9au8 zG)tCUhgeuAb5O~uv}KO-gcuz`(Gq`-)HETR!NhC0X^i=X1Y4EK`1f>nDxJ+5p_x+I zF>i17BQ3N5!q}RXYLy%2A&*l`d?QUU@gcx8rromM#=(~LGCFNp57bGCjL5Vsn=iRp zYikQV(3|sH$KZh~43C5{gESm$<=VqrAh(V=OA9;_a*$B)W^#DQVzE7bz>?s)2#iNS_W?rnSq35jpa91iGF2rB$O?IiRWT;xFqduI5xjnX0pfY{d9_b`0W)-EOgZ%?xI!yLtIL8k&KT%a4X79dPfI)Esom}ksez{i@B;8PEF7=cK~O^SIYJ9u zlv5bS^4$wGLi61aVq{ZzSQ(f{9ta2+47>I60V29}k`6&--CX473^}#AenLsg%gsgL z4p5tms%`Tg4Wc5BHfr$@Gh+}r`G$-6G!(gc%)PaJ=b$fvryj$*19>d;_znS{peZK7 zjz{dz&#UKv!AoaFc8onJCR;8-a=ug^O`h<%3_*>L^Oe=K{=dX0bQISd7eQKp;6OP> z00}fsWiROV{+IATg|Q^UEQm_1poN`;#39AdY~)H$7}#C$GYPy-N_&xjzT~a1enLP= z(usGzrlf&ypXYfW4${C!+++9#f}nZj^zsL(%H!=Il?OCML2pVdFIah}OkU;DGh`Ae zj|FMOOPA0}N@Dla?hv$68IAP9MTwBvn858DIv}a4lJyqrtF{llmJ*1iYg90OQd+_EaeiL(VK*=O zpv{Xu1eoB*B@oCwz(VQjo*VCmS_kF%b~jmSZRDl{`EAmd7bW-4#<*2ya7@3%S3m_0 zM@q6m$581UAY6GN_nLb;mjj1GjapEoYRGeG_Roa*9T`xhrd-hQ(Qu3 zK=O2Sfbqdqg9pw;s=9-pR05mI) zg6vh>%5q8-xHTY@B39+Vrb~>Kf%08Y77fI7(?B^4#yb&QR>JRzM6@XQ{mdy6$WjgF z>s|nE!{~Tn4GnBIQsf8D*Ra@OG-o~U_ah~QUsz+4K65@}lllv5xcmU{7w@F}HCNX^ zLkfrMpXteOJc#t$ksB7k7x@tYxSX`Gn+20ITZaOI^}-rIA^EB~63bEb9OU!3k?4aq z5`74zO+y9dI0R$Ro+0i)OS%JI&T${8z^PyFTNZrFgKtIfwFcj+;9DJhZNaxL_|^xX zeFOtIVXNBL))9PN!Pgyph2ZN8zJcH?2H&3G8xFpG!8a0oqro>8eD={DVs$daoeI9w z!KdA!3g$Z-;?4!%dHFzF%t*5NL%91kUJAa;!FMJ2t_I&V@7vFB;OHj#{mX+dyve-Z z-ehJ!;&dwyC}?YVTa~frwi#HU>fL;N1s+zi{cTVpP5-UMhv<>^I3_~(UHaLMqcD`X zZszMD&X$99wBR9YJ*on`Z>yJbu&o|7?r#rxbp&5m@Y$8)7+4ezxbaI?Pd$AcU*rc^ z+>07s&&T~A7U!Ql+rd1`-E#$=RETjcK5r>fL3eQo4!CGBfrM6I+UK9pL^0(cq)Wum zSXh94TC#tHYWKE2czC1TH$#fgfjc@MpA-oUcyP|@A$qMCOlX$FM1Ge+QV5uGZ$@V^ z3x(KzOKd|TkhFD>Kmp|b0mKsYYJ2YISyMLfCsnRKV#UPDnrXMebs%4Uv2 zce4K^ih+$0$M-IO9CB!Up7t;c=yIs(+cuzkFr-n52d9gx1Mt)}*4s8nMVg$|)uODb z<(ypVW*o{JP$w|WZ^Jnv$qL;Um+a?BRp@Tpcz!zB&rj!KirUt0neCFT{ns%5w*@)7 z-@a=>nQ$YF|8T$i>p8{w7CLj9?yQ7at*{W)K@APz*{BvrdVThDj)LngSE?2SB2>e>HyFIsfh^ubz37m4Xib?iz(n zRaI#tXknhDr;H5g=!H(90L=>vMf$=*Z5Ap5acpp5p?`_RH{t9kq zC|{T@$xRRPxLD?K2q0OUS*St8>wK{i?b1ne0R`V9gODd5h0tu|W-Ni)*kFkLq9<4% zv8W|fwC#qgF!S~`Q6-wL3O8a@9sEnc@bV*_CK@+rST~uC5b-R~9DuBV6Dnjhk>PV5 zP_$%E8y6C7$f$*d&O}pK$T*Hrdf0ognugwVeIkskPv)yn6oG7M10&-?#}PH)sJLjZ zQV_Y)uT&TC&Sj(ryAI+6sF)7y!5GhQFona*@}t3emc4+jIIskDefe=;CPg!gYqq9j zBAKjlwB*1@9{=uq$~0D_7KRJ>=3skxUpMj60Ft zW$~{qhX@F`axet$Y=t2k;=shlfc|CXkO*!FF#OBgXsuPxrIE%9KxE$CCOtwm)hVM? z$el7lgg9@42ob>q5n?1N9VIYG=lj>9i4uNpaAK2N=dkrn0Cg9~@9SNfK=ZG6^Qrfv zHLhPTxcELiJqQSk{Of*#%)h?J3*Fa;gKwXF08AOX+!tJd2zLFFn!z-m9$q_476X;f}!Gs}v{l5IN;rf>5K>x|+-kWxQ28@Z`+PWYP;mM{52?%}2tD=AQ@HLu*L+ePM31x^(5Hv9jGi`c z-~--l5SI)@v}cAdeG}3LfbY!r^y!nxe0YFdu&~ksE`SY?dpRK31M?lM-uV&T>(P5H zczf#9ycv+VQmurRqPTDYm3wO3t==9>;LPdV;Y_)`JA@!V)JRGlmx1pa{TNM1MG1U^ z>y4fn3i8NJa==ZfeT}I$9jLQ59RLXfjt2~FN(5fC9t!|t70}ZcP(T&TvqrV^T^JsV zm4`$>Q0MS*kQXU?vsK;0dcc@HO=8R#)PU`2q8(K8iZn*>H|3YR3zp1pTz!>=lLJcu z^BHu}HCXhp$7hktl(by#3c0OvSEUHi)8yO3PHIwmo7@hc3k~hP>T@Ze^loTzYuE={ z;l#oPB^Kr4&ft*Td&PqwEAi!+cw8fynej8estZ>&hud)3y#`C>_wE0zT8 zjNA*zO494fZSV*@ioYz^z+RVMLva;NR9mHGeIceaI=PXVMOs_sS$9P6ec6p zh&XIywld6-D-w>(mw$l+)wA03;_EI&P$u3uO|T9FI>K z^en_E4*t5-a8RO3u5*00&_rg>LKH+4WM0ogicTM~gb^ySUhW182rPC+aB+Ewni~DM zO$l9JECGOjVu&bA>|GBPOTh+T)D3EgfJ#L+mg;Zm&g;yvE;JKBfGFBjOK)8;)}ss~ zP%#iKmN3(R!5IVMZanCsSj;{af<*g_1<)Kh=kko42d6n^&5w8<8M$Ds8M)vlLR@j@ zIZ}=C9IJq6b5ZV9x!2@gSNnjD$;iv3Y_XKLk2{Urq+s)x?2TMN2~{t?QZPAK1 zeU%EZf`gP)Aoa*Oqkx_tU|6ZVp5)_X32{;HRrHs!g$r9D-5&f1#}ycndavXMsVA1e z6d=3Ky%c&Vfv0Xl2VkttKpc^`e@5`}jwHqjvz}{)Vb3*En^V@AI1`*f95ss`zQ+Ny zd)tJzB;i4gfP>2vvs|vY{h-9uLC%02$~=84g6Xp5H?^tup$-c zAND6K`uP#i(QlYjRd1cLI2s6Lc3ozwX>(dQTR51S8xjLe&}C8Gz!8% zq04pA_P%OVfH|eFT9UD^8p%r-jVCuAYq8p){fA?Y0~0!%4}dzU5XL5!4CxphkD8*K zTc4!L@(>PpB2!T^gQvpO=qVIZQMKiHbOYtVQ`Vr4vrb*>I7@X6K@)1d!&K!FV;(iW zSD!sg4r-Jhyg|Yrl7K%%(GKeL=!HNG2Qek+8g@DnA zQ#o+x(ZErBa!3R1kcQZyR*dCCtGp(D$gbUFTw91+Cm%@0@!LBL%>v7hY8*eW#YOk< zSM$!dk5n*5-X#^}K4so#gvKq@Qm!wP|L*0Gz!TObJsG}i@w+(*GJM5_{lqCT#isB^ z_b}Xhd5et3KCI&!)sPW)atY7RRRq5YW}Zj~5p{%JxLx*0Uhy6XMJ;+%oAOYSaT%nK z)G6jBXtTY_ZwG9z4!)Y;s|&vA!8arL>VvN#_!_(R*w!{T_lY$ zUud!+P-x=!>?kATG4-Tl#I#2e1*0Xuz1gjPU8pQ|UM6)O#kWej+pAIB4L{v+?SdFM z+nYfayH7Y(XdW=dFxx)Q7n(;dupkS~3xyV`_gr0zpw?Nfi_qDSATHpn1Guwzk<*D? z0wTaK=-lm#0{+_W6zbuHmu8v(6dsv02rcP)T4FsdnR;4K#MRRh>v6HHrzLg- z%{LSe5R?#1=o0Eu-tRk!nUsbd~GZDb&^NsbnPl;R;SGap&U8bZ!9-#pir zX0W5sp~uC|1Gg!T9`rWIZe0%^bsof;bqq+;pc(jNo=w6KBado@43-AsrnOqxW)=i%^o}wNri_-JTwy_$G)`?ne0<&3h*J`ajXk* zweHHG3X13*t62Mw=@Dd`cy9thq|UsZ9~0)jJBMq*Jx{qO?IeizUv=F+?0RpQVw>Da z5AvWP`o<|?47)I4tZ^{;^kFJw2sucnfNXYXZa)===Jr#ng_+#k?8fZ&Qvjj4IUPZo z;zbP)9N#7B-A4fOWNYc&7i$Tb%4<=%bS(zaY6)LIxs2&jwa_CpK@?3&ctV9hMVO$> z?~n*#0MBN28cN7u8cGP>qhS!?0dzIgwUCbLV0WGr=T6^Eir4A8P|Hr`tjPvEz{hsXtzvnq9tV#&^H12DTm*!kU`Npg1l8a@cq!+$n#+veQya%m#^Kj zvbRyu7hQYa3Tc3v&UU?3KD6&Q-$C97T|3_{>=yOcTOsid5b*r=b`ijLk&S2GK>^91 zJtluJKuyNTlN!7;+<@ia^?pBieK-u>5aD{yhuz@yL4i-fgS-WZY(zlIgc8t#%pq74 z6ZI*yyx*hc!vU=j;n4D7hn5dElqCb924dyY`Q~+53|3>Djmlzm04@}plsv=LlLZc$ zsrTc{U{R(rKj!{n(_4lWMt5liy?qSR3w1KCL-}6c_CD{~<2`N`>v|u1yz6~dpqHiq zD#54N2H7pQpQ1kesKJmG#(#Ev`)M)DX+q@0VqcTS2OQvq$afxtciNpUbqCS5H=?WD znYrNiUFFkKYt)d(&j3D;>n$JjOccUyjT*|MR-ns?|L`(5@gLR#^Y999tK^HOYCY#b z@AtOK5P_yAhg(xBPG5C;@vD{lVcv|6x*{CdG@z4sAPD&_fmDz;n2u%aYuICE-I&?1k+e z1SvN;Oz_F!8vqMyuS|L>R(>&Jx^5-`g*>P z_3;qHOzNss%5^RZn%8KJ!=4_Jy!Q%M4)V+lYlzVrLgM{tqcy~0l-pLINZ%?v0j1A< z=xG{{CV!z~nv11%FyF_GQ^4z6&S(}|jwf5VoC^~~yYy9N{gNI!D*N0h?DnP_69!Lv z_ALh@u$us=>qCLp1zfzanz*2st&lvpxo@Q5_@g z%c%)ORvQ9%yD~pGLLq1$fyyd0gBcA4iXMf?r+lEFld0;m%7IuP6KB66%sIZ|51LForov zEB3-;x7FA4WHa7w%kMnT6UR6!T3=sJCS%@nE-&RFLY_e9T-Tn<@4V`AYC~fuBa{_E zGs@IUFv%=ZPwF^BOnv<{>;=+5QeUfEQnl~InWx?Xq-@RaynqmRPER7u=0S7Q6X7@1 zRWOdcfx*e6#kFb!z;HITsVu+q41BUG?K}fUm|1xnoBYOzw~4qz*TzKZ45pVpf~YzQ2p!aOXLHycmq`bdT(0lUk{BgMxy{u51ai6 zUIbAm)sFu4Mv?v*X8Z1kE{wiGI@qIs20Q>C>^rJf_MQIqP8)O~WQ->=`|0ZWn?B&1hvC`3UOIz_L%~b~yZ_L$z6`Du zT1>(I`hN9#Fc0=~i8FzNY5R-TtM2JgiKJiFOsG5XSyY}ocFf-2Cyfpd7jCt1s}HZX@MS_1Ds|f`pLJDifXfRqw*V}7&tKh1nT7V@ zs=9{R;}c#BP~rY!9^8_xxcCK?6ARGwDvS?v&NGyZDxcyWqlpL5Pt2{-2o88Qoz zTEZ6L$?z)C%m$cd)BeSX;o#9Y6J2?qYbsb^^1GF({LF%69f-BW0!I*= zOSK>30zkQ>#_+eUuQ&45`+lrv5(gwb4*fkCG=M?|k=oFoiWqj_DThc+RsfdT)b9FY zlfD~`pkU!>C;kKrPt;=(Dg!TZ;lp6`UK4J3xcBvqN>TKSO59m+dt(aw9Vtr$Br$<{ ztiMSju)is6#Yj3T1I-xn3}__@;`JA;`TY1`J4qr>AW0yFK95U|+vC#kIo+a2D31e6 zx=6s&ql$d?VSYlF6qm;(QjjZG3B&^M-4x@BIE4|@f7)-9_J?=A`olY4{o$RjetYK& zMX9F_Ryi$kunN4893f})^OiR}!6C&3id_x(N5ZD}5!>`;+X@Dc$sq`Xe4zP1^bt+$RwFp#j zL@y6u3BAV&rB zsK&uj$3UCDWDK+EOOUSMJC65(eL)5c>?6RV6r;(}23Oq+o|Z3&mPfgEN{-H!JI6Y$ zR#vs6?BPw{!qVxR#?@F5IWY$hqM+izV|m3-N1Fv@G2UH}c`kNkauiIZ5zRAyTKCzJ z&!bDzEL=*nZb`tF$!(Qubl@y1*)SWuVB?g*qirt!Xqz%`kP8k{Z7#A)k=@jnY*;zw zZUPp8)09#EZZ8wBR&hzhaqCH(Kv3f(f0y8qVS+G(iPukW%g}VKxl#ME&FT*Z^*q#cT5aE zCI%lPgM(Uw&a8mV4b+GkM;_&#?hx4cEeCPqw-nDy3NgC5uZp>Z1+=dk{LZi*!hR1V zMzDOUuvyhZveR8+*jd%n5!5sBLc8$6xRZIIIO;tkU_URGj3=5s(a0dpj!_UvO1T_@ z)g+gPAl&SgAKc*jWdjxkiezu&0P!G?00)0M5%*k#MEMEMnE?j{Mk3RLVNoBU?5Gd| zUKN8k1~;G|K=1Z(X+C%$i~^Wsou`jR0VkXW7pVnHbN8B7`}Yq@Yl-nObo&b~>mR92Fa^I3Ph^ zGLWtvXHkrTt;m7p-0eIVi{*)p#SPfN#~kYd?qS`d&5UI1a;OXFJGfV@#_b(|34sO7 zws+XO)Ud4Rke(_~0bhXvk|4XAPyuMN@h4W%#-FHB*8@#?s0WOQ)7{NMK1;#sASXeA zQZkX{7dk=&b>a}fLIsEm4J#@II#m)Jj-{cT>rt(AlJPnsH$GkN441U)j1geh znf$IZO3_Cn-R!wSF%nMWbL1CSjn9*RzT5?J7s?ewkF$%}SGzFHNxbc9Q7X?ru~qqT zvHbXQpKqzpXYsAdtwAs@m2G?-E&(xm;6wK^GNEtsT+WqZcMk@R&TvgI;alsG9vWDPBv)I~3U^m+y#Z$_cgj?Y|JAd;@Za<&MZ5 zl{?05Kdnp!1^A4gLdVAd5K*LZTZd?}8$YL*^NwWWryR+~H6f1UV+uh$x~QuvaBWhB zrUj4RP_pKlW14G@RXNy2=oIQU%XzGZIgi!J74saMA%BD1M!8~z#YW7Zpkx_M0z=&O&rwa}nj+388)RZ73q z3vL=LsV@hq$4MnuoC8^kFZU`Mxlub@q=Q=rsRaRx!ICpe43-?b$^&W`7kQ9)?uPu9 zO>_4G$~iHalC3=`=S4DrQ?@cfEgpru2uztjHs*Tn*q9%$oW0bCwi|owq>7!E>$Uk~ zV+x*wzJBb2*Z$>G{3W?pcG;6k8xTyL&(^&+1t#ak3^ zhx~YpGD0Nj)sG>xPo76%8F#DU3lXObg=_E)UHWe(b&$@v-3$a zK%m(BDM#`{D~J+9h7x_QwL^P+vx||KdxCct$pn@iLQ{#LqnWVp#^f7jL{CKWISA!Q ziaEkjEfbA%6dHZ0oQ|7!jS94e{qX_$d1+#(1{^_rmwh(il7T zqA9(}t}D4 zo?NbNz#XDBT;N6CIOC=X_ig zG5)3@^MPf!6sJ5Y&dc8k`2!tDFJr)rVX+_t&vl2e_7PGA$dH)~s2#vWKMw<8n&5ps z!Wn&3^e?{@_3{gX)*DD?Z{Pu%tnq{6AU$dt&Cw@B}N1q7h=o4m+2B)t9dXHkd zaNaeXLwqdX&3d$=+9hFgxEdVD0`SVw#a%Igf{)#X>(O+RZSfL70lyRNc%mHX$dA8; z>PSh}tX<-mqL0HdK2wHx0S}g;H&uo~m7#|hPyW#5aTNBDtAU+WSuI+Jc%7d^Gc)zE zv93OPQ}r>Z`sl%kj_pE(qY6N=su-b97g>!lQaAI*AgcQ&3NaVF2DZ8y`{4L~OEpA` zKI4MyM%4~H1HH)yMfV{<@WXCi4#R$u-*{$sb>_W{-6V&0>UWEmEZOlYEbknhaD7>4 zV&4rsfEf~qC(l$!{*_eVHmom+qHM10#62$rV8M0hNo~Rp7j#F`dDE$ ziS%QV`kEfj0@CnZAVA}LNXD4JLS_g-LVjBjSbp9{0dXRbB4S^o3|4r6UKG0O6Nqwd ze4tTF7=tIouu@n#e8{}=+Y2&^W`UguVJRH z7UJ$2r)R5w0n&80l((_$cGl0`jab!*BWQgkVsVY{QSTv8Td?_m5y~6L&?QWG%5GYZYxJpszN4|ZGVPb=&g2y&?uU49gYcLD%Xtc`% ze~erk-*-p8{m-ySsZZMfvL1so8^Pr|v$g-5+g&yt5sD#*{nRIE@FC@V``@4{oLy3W z2GK%(aETkZyY&$$Y|3srnC#{`C3&tp&v&`x=glBJbw-Vzx_p;22=$T77#%P6AbExn zEtS?&!xb7b_0(v?*0Lqvtz)#n_&Vx55N^dG8_9)&vDE71yEC*Jj){nI;MM!&ITH=Mw58Q-wBY~u5&ie%G|ywXmEtNo`8 z(ZXvXU#U}6IJJdUjq3&HL_X^4(GhbzVUzHda{?BT0r{ z6lJ6ur3o}6`L4Fw+4|G2y8amfWU$MVklx5NKfXW`JL`V+@+i7-aWwuKMO8b5B!jlA4fw_qvFW<4RZ6oZhRM94LSs zu0yXIww0M2eT~WYs&vi-_*cU3a({1*Bw5#RYCORYkPIGL1!Z=y+fxW?`&K+(Flq8?di@*7v^b+|0ZTW3Wp z>P_h8jf=( zp~&^az3z#jRV;wPILG^qOiA5h0hLHODS*=?T70^e8^r=UwOEK%6_S^i@<#V`W<-II zU8`7|M`NiUGnL89%g{Q_=7o0uZFt<;;qYB0UdMsb5Yq+Wo|X`{0>-?+nVc?D{PJIXnpqVp~53?MEQ)g?sEb}tPAj!C3UbbMX zicvN8Ad;6yJr1MxkqQi3oC%QHgZV@qzPm}6n{q^_lH7;Ivys&^99_{-*_4@qZS5J)ot0V`j(f!9kmRM&cdqcWeNCJ)-8(M#l1ezYH|T3AHK>Phq79q!dO$W)IhEh%yGo{dnUf0D z$Cq(Q8?!vhs|Fne`CSz5X-#+_+6CR>3%Bm#c3j;@OXk{EaH^m#s0H8Ux3Az(@;8!g zivw%>iezt195Z`sfCnHHnt0hIUOmZoUK2NU^0u;>m6fa+={p-0yMu>%_=!YaJ!I_J zc@4(~$?Gi3i_;6Z>ta6Vs3n{Mu!gsLik#2S3>F``yz^SJcX|r3>BtBkwBgWs&B+f| zrYKf+2VM@qLm$oY2mr`?@x~0I@#hB3UWl;UY>k3wZb6DY-KEq~%HR@Y-fz8^3L6Lf zUZ*Q|o`R&nN~+N96JR9N*V64C;0YN^u=LtifCfTLoNBeLkZRCuEN5^ri_qszh?6Av zPK}R5Egbx~rP>Ys%4F~CAQIVgb5ii;02%a49`9c`bmv%Kb~d?-jH#a zE=#pzxko>ER`{4!pavuW%Ezn<Vtr2rY3vN{k8NspS$A>~|iHh52m_jGykao$cTj27J69qGuJE6Efw zm@@Ml53+b{Gcp!2E~M3iRZtQrufg+@r_`mG`BJi%_iDw=m(&}VvUOig_U;1^Y}9r7 zMb+MY86YDug!-|3us-2;?!7nJJL+3r@}tY}0|M~K0k(0hS!%NPWP}3XCo_dFWeQ&^ zD~#ryiiH^hX4Ek{_e^Rbml{eMPBGq@N!euKomk{rSZGgEuh5P?QK(*=;$cOH9^sz> zi9&m__mZg*T(1YFy%HIU#VzE!W84@W+Zu-Ib({sKhc9@QemHzbWB5{tyDT3n8omNLK{8<|fTu__KX+mz z5Ay@>5O4Ydgc4B#T_nm5UvtmvdUAUNRvA*2BR&4OOfD}`q&+>cy51%_mpEe;$0*O7ghht9MqGpQ4y%|6}P^8 zWdSlcg2F!H1z7t}lUHabdnMS(UNJivz^LN;@^C3K69RX5!g7A92I)ldwfqP^YJI`cu?=&xI;DLD`9%F zr8G+8%Q?;{fG_8GUl;@XZ497xy5KMb0+!1`iys_WR8yKb= z4?=Uo*GjCW=;PN)7)RlQEY!FFV+YS57?jl=TKk?L6Oynti%>}jP>7d zdB48ol1_m{!k1fQK`{>`kNffpBGcb%q14(xta|MU-X}kc!ECwUH5Rud6Ol3*qS|6v zlKrP?8Q+=(BO8E0I@W?3h|Z?QNB;&`fghx*?%sTQwv2zg-$E9Ci~Ba5VKz} z$+E2-2coTJEM)LG6Y}=9cDTvJdUTCzH`08Q(FTR0ipjLlG^9mDEN`;^TBeR`T+ZTL67`z??#Or56>N)vR>VMi^3}qj|*U$S2trGsITiH_J z^-FF>e7#IlzmmLu*(U{)HOoYuANKRtuVn$Ey0Vx{h)Lnszdlm{!PjquPU=MFR0t^C zn3Vgb2L?f^`{*GqdQx>N+AiLQx91x9DxHodpwa_g+dNR^8hLqS1I$Oj0-zE8V$-D*|ajS`qv*3Xl!nOvd zg=|DZCcBzb!!$-F2PpADfZkxtuS^a!WWe^$FOR_v!GVTbz-v_5*ub(~ssoKF>f=s( z%=2n|rp5zxxM|-Lb2eLKIYknPG{5%HTJk`hjcZ--=~M4tAf>4|X3; z4m9b|h6>qz`=B8_fxC#;yf_vPEK2$P;%IwZlWgz6W%|Au%L0_1)yV;GJBXeY846&R z=0K}2+Bzv|l~0QH1ww7hWMnU8V+gfP0&;_6EOJeg7^|nrSP@6SHnp3#_loT^!=6?= z$qz8W)7wSv^>6I<;B2pZ!LXB7G|=8@bhJe9mx*vM%J!`0VpHIHupJ~1t~uYmAbOj? zmFNe4CPEA0-e90$?A6fY!r$lN9uy*ayT~!%+A#m9Omd(j#;6=)(#)htuVVEy1#VG% zid%ePaf>BR-iAAXm%CE4qLqo0jiWj55u23jKw%Q1Fs?9LzA%zWVi(7yAXy-Wko7>( zljbU>E2<};ogaUXV*OllV2c~PhbN}JcuCI$z@L>3$LducG3ZE6mc>hG)T7-uhP~icb`ilr;EfGXuU$(%N@)+jH(x?}CMlfFTV&{Mm$&<3E zpmvUqGoB77L&oCCFgVhG<@&lqcF?*szbMpw1Cfl>k=FQJmLLYqigic{A#f=2xzd&JyL#+e z6p2HhTAPf_2<4zKq(abAMx3qQh$!Ldv72EOqxAHvUN21{;J{xgdZX7y8hnkwryBQ+5R*Brb%p&lGGT6V7rl& zGBUCtrOWS*oCaYxvQTpwH&4M1GxI&GQ(1dfLw@_AgCq{F9vVKV0l*h`atMibM?_*? zN{OFhDYg=Po!Tw{n#B@gKq#C!gBV3=&0yX`le}Mm6zha0Ups>ccG9vAasYWKA~?|% zGnV9{(^Nv<$gY&Mp!kZg2g|%>Ad?jK3|WLd(d05$MEPQSY)X4F(i%em#tr3UqRR$0 zc)0hf^DcD9@P7)@Ed)GZ3+6+kfiWXA)^Jjvv|}-(fHE$^6(&~gJ3@W!JCc!g>7D`p zu61hIdH|yiJ%SlY8;&Dv2zMSwQqwmE0qK7cNzh5~5fP+1UBieua>0}WAM#3p4<{qne2v#)vw$hK zPem*M@U0!Wkc{X!^hCGkIgl023Y~4igEf##IGm3U%J46w8V?>!k*|~-WcS0?X|r)M zQ81qKa}J_G2M}*lW~(4IX`_1%rpERLgdB~}>72NIDQ2S2wesL1vCe=Gdh`9kWjL7J`8f zYymRq5>T(y^i{`&ODaclusuWCzG}qyegO==jo>T6`S)zz#^hjkCZ>0IQp`TY_-0hX z?+KJG2;p}y5VtRtA1@z->ibh#7*Ip-;DBS-{?vxi!LTiW`t~{6_eZmQ|KjA}h_3>K z8x8b_efi)ioJG1Dm$>_$V%$}a^bK?wxU;v-MH~v|@5+QX9EIwaMXi0XUSP!=8T+cbC=Doyk^k^gUcC%T~x_^DKY3uPj>_0ziQJaWb(l zlHoA-vXY6w!H)~Zm{Jj*b$fLJ60v5^^PE;VUwsb6`4}yMpK4{QLJp)MYwO(4M!B(7mz00eID+< za2n%w#M->L6X$y%D0mu1U~ti<_5G-Q+9f|aVl&vz`Orz+wgobwhE$T*op$OI%N`K# z@zH8)1B4flGEawy%+ z3pY^~qUD7zVNXNXTw-&}`EcQ-8;$p(l9SHt!Mz^@c9!5$>kmO^t_b0se6x57bTsZt z?8G`QWW|Qaq&O#pcb2f%N9`|U7KKGl=HID*PACL3&wMxa_0!5>>Mm>8}MiaGLwvjYPU&Q6X{U3a8mG3seRi2d!W zL^$B51C@y2$x5>yE%2{j7+R@v4tH5=EK%q`;~InuyBrOV1PPA|yL{XR`E=OK-_(Su zOJQSAyqxZ)vQdenF)$;pf)ZDE;5}{|3E9a|7iRhO0WZ|z8rPKwnmr3MZYJPIzQ;Uw zL^9T8M`B7fK&q1v>I`Pa8851QdX*1qRwjFKbtXH4N4VpQ>;nso;ZzLMm9E+19%6AG zu+@kFFneE(EyVHt zWfIKYk;M&=WP-cPG}D<6cpf>67{gaLQ9;&i`V#nJ(_hoW4BU=HQCzB(-EdcgRpD;g zvMNr7D(GX$i+bjdT)@)`xRF!_kojEek3c>pN4S;DCr@d6YUhufaVhEDe`PgJbI72# z0!`SLUI9CD4JbIX2s=8eX{6&dPyHpH`qu-LO-qs^elb$)3em+b-1p@v_A^qR*OXbu z_Jmvjel%Kl+ItXkH5{!9nHcT2M2=QxqGO1VQZwMudRG}P1v7>@8Z8+wAa4v2JS7VG z>}V7r1UM&D#?U;UXmI({Z!+Tn21YZ*VrQ^Of254^32%ZsDU=u1w; zxLDERXCiX3p>Hpjay00^KzauDd8Ckt+s8=S!APvZs(2%rZb?fOfLDp?g+We<)**w9 z32!T7v|fcpIG_kS4U?DIV2FiOd20lLYGa7?#1PC2M_2irD!1B)fO&E>hFFdtQ@~PN zw_KgJTdr1OM#U|Qh{GS)nNmH3!SC734L0S549I&1GCzd*!d_VnHq!+&n@^we2*oB9 za<6EXfvS8zPgImedX%#8k?m}%f!^;M=);D_EsN^b<^U)~ymJPdpI*Wxlp@|558H{Bfaa zdGTnjA__6Y;;GQ=m^4%*8EbQ*U~oQ=;z%Ey?{S4U8C#bLt;95r}MiO#l%H1u{a>I#gfGVB!;Z4XI=;q z-T5KJv>0MqkhzqysSwi^X3E77(^9A*l;RZ7w>c}Z>>|I3iy3Iy9%*sxJ-$3-VkOJn zR5iX7xBry^T?!&36PH4MqTd=qEFD8wfQ+xoq^%AimWv_G4&$pmGV9_jGrkTzgL!d$ zeWkZKhP(Tsp)N!v5m3NsoAHh*^K_*!=$bM|x84tiZle2!0s@HD z$d~vGvG-~_W>VhrvO&Ctg2^~}E}6KjgR$P4kh(Te%3;VpC8onBCUJ}R`1;Uv_RNUs z8ROhQgSiLEtLChYKTZDSWJMQqDS`G(CdckQ|F4aKf<}A#GPn zI)wJ7(!gNHQu+_Nk1^hoH?SZ88CZ}UO9_93<`~fi=H>1FdGnY6AM(fGj&V&+l|wQ4 zp=Iy{m_y4Gri(#_prlyFHqv`&b$+NVW^7B2MTtrbn_aQ?jUiL}#^l)I5Q6e}Y_S_s zW98jS2uoQFOnj-62aH~pp*Gmz*z!ya^Q@Q@vntdy=!Wayh=V^kVrhdTxObCr@&r5y zPe&L^Zkrw`4RZSf?xbqC$%`%;&U71jJ zzmj+rCLz#g!wZQ)2s)P0q&qlH~uKlYU7WRW0ycs+Nq9RGJL#M&z%Ul9x{^+ulUSYQ_Z;!RB^#35#xoMFfAIy z<5B;>+T=L3XjXK5A@eO_K2aDaFVG^8RxQJwo0f1pk7THCkOVI-jbswD-j1hik}Bj9 zMAIAAY4}habD5Mxrpn~F*E^1TwHVi+x{k-8&g8g13~+q80O`=f4_F&+&y25MDd5|| z(X9rp^B&G?iUD`)buR9(^5#oCDH`|+3@_BA-5zTIWDP-Nglxf%kV6VLfcTs+;#p1} zw)Zt~v4YuQDD&zn?+rDrb5v#(C<}OB#)Q$XIA`t}O^)|@Km*=GNHU=CLO7j|QB%TZ zeQk0)wTpRt#8oxa;H6yyAmxYZ^Fs}RvO^8Y@ln9_8cLqGUJ65A78$BD3~@iE?~&tU z$?;PjvD0xGN$ckE^6**T=<5!XUDt`pu50;Sk$ZPtPmaea7}MMF^Px6|E@VO%J$s|N zsCbf#FO?y4*<*Pn0ARM1r2$fhICdJ5o72^I<1t;NF1D8R&dAp}!;DJ4e`T0nU^G$-VMhM*fi z^FvJ@nATK7<~I9C%(GxPckyR_NLKD4PMu`)Qj2ekH!KXzM^b)hUVbQ6KQuonE%mHX z4Zl#CCzs`*c(w0H>1L&60ZSMsN_y|-2UW<(vtp%gcrY;XyKt^93Ctd-MXkWGS&JEc{A&; zTOf1B(*Y(4BiL-wp)o%cwbr58Nhyl6p|+%SF;X7v0Bx^y-E(SXQc4}nDc$sk{;@&f zZQm&!$j9LdRBrRS9F9UKs=UXK+!HlEhJ&AD=>)GC!BB8E%5dzeHaQWFT2#aDv$J_B zH^(E4)F=U&#u!6&NU`5C^ zup&9J9P=}-Rn6*DionNSw1 zO&LdB;fGW*z)AQ}G+xce4UXVh-30(Iqd-o($kMG)aCs$`DkyLeea)+{aJ{03f7APL zy`qc5^@>&#?98~*E@C+mhZb&Hn20MAywZrf6pY#vaV^43>oc5&Q~VPf0x@w;c_N&3 zF4pUI)^t|wb7r}aE_eu20Vik7a;v{@WgE0SYP#C z4PZ%uiy?%mXX4T%DLTF$CZmvtRbSzFlpVu!(bY^lxsnM5$OTl|lFjc+CT{owR8%M! zet@B2qmZ~aXK`;%eo)oEC)b$ERki1GeYsrjj!HNPfANXO9?DhaauxVLezWj@RQ}P| zI%oaC$F}|Xruk=Pp0Au(d7@$FiZ6X)=jq;+v$~3Z{K@He{@d|SjwBBq`n7-c(Leq7 zEAIZ<1N}4p>Y;sye)zsU?|Jav3*Vmj#*cpeZ@=`?`G5V$<;9P7KmO_WT>0OByW_s; zA3yY?pY8n4H~P=~htIbBoBrjk|J#i3J^ts{zkdI3=0AJzPgefs&;F$P_J6z|XaK<} z9Nb&XSeLlq@!YeywYhKQp3AMweGbph;<+x@l6xxmbnfeLzlQLq5dTbWb*?q{JGnKv zw%j+6dmT#Eej462`2Q4uwC3(xh1hjS|1y5hA!&W?`P^LipF-ZXxj#_3Rzs@xX@s6d z?v--ySd9w4lAE>;vEM*VYjbz31&F786>X}2r3GWLh8ETHPXOghc`sB(!ifqpU(z@I#HRNllEXG;^|Eg2x zD{`MmH*;!A%uO*FY5NqgAd8Wmrahoe`D$){?!E^cwb_Ye(XXK)&mhmLT)O{iAILob z##@Q*`xdgKaGK^Byegu28{?R#>UKJJ(0LD?YC7+wbIW2#%%woZ;G6r{wJEF=6dJME zE#qRA=qt;tgm>-;1JPxQvK6_-&toX?|A#@{59gNPzxi50>ksFa;@6D-AI^OYKMsHM zag5K+eJ1n&cc*UstvaFdZ+~930f#CS9`pUADbw*^gvJz8u_*7Zcz*{Cno?VPQRgcnn8=0qK@V7yxJi))8&U8AQGOF&P z^Y`e?pz{GbAEfgebn5BMq%(`o->1_+=WaUp(D@LZMmqP>xsT3=>CC3{5jyy#yJsrW zF|P7Gx%r+;9}tI94zz+d$(bJ$SAI;weF}~oc?osA@v+KW)i{V+wFVtmojdi+{Xg;u z-1V8<6Oc5NO>OW|I<0_co{^5GXkY6RGWnOFTpRK~oco7QU<@%2Cm5R=R!bgpu6hP? zl{4>L&8cf7`JU1=gjox5oN2SM=^B^6yP&^->R0N=cD~ENpFnGERweLd?=I%CcYf~| z0bEY&$I$hh_MiX7I`P(ENs6WKCdQsSej)mnP%!K~j!y1pBUI9J%;i z#W;c`#=rk=a>Nr-S1Em3oe=xVFBd#p@U9Z486%J?pAGVNl{i_@#Qdwj2yrG$>qU_8 zQOO2YUM;qThz^hx^+h*7@##*-H4v&s&rZ-rorc-8LZ6JG-7S{6f^-&y0z)R+T1KE z9^XI;SKH+K=KzLzpUM4~2<4v;gr^bwDt;W=)gJ70TXeGqF3*{YHPtilQvU$xr;xi9 z@m~2L*xw1sb;R?VsQDRT+?uxLVjpba8u@8aXe}bS5?+$YKV^nAggS$tYM#{)BWknF%Uk+lRK?XMm%NA~j_Igs zrV?Q_h^?ms_=KJfp5%nd?SWBn|s>loJ{1F}e(%O0qPUVZsrCW`eUuY%AWkCv|h{4;e zcWze7ECNZWx6VcXlMB8H->2j=Szfyo1dnQB^{0_{El61N8Dt_QN&nxv70ky0(HQ3W z-0vd$^WbkYLvgU(VNumfoxqs(djM|Y;$2;vyLwXKYg^Q8Kjv#cpWqFp8GA;qH1x&67u+I4q0?%Nykv}QVo<;(JJuqeV z>Q92X*MJaT^oGWd`GF|Fd)Drzd;S5aH>>lJksNkzWm=M{fs zOosxF{emxRX`S;qphn&yqU*3yrQb|npMlSs;7xH2Z=Sq1m!GR9lS6%@=0)pULvE3( z`MN04mTLfeJ&`iwHmLfwpio6Q(y=vJ^rw58KcHTE8r``vH;ZPuZ$NaAJs)uW>aE7G z;WBzPXDIS^TkeDGhiBE(UxVOdqrGrcwP|EP{Yj`r3yTIj%S2P218%}^9js7h`(!6q zrv55&58DLgxqud8b_i{|l#j&E?7R^>phEFiHy8P0x2?_9(7J8YB7`k@5yss2v{$vC zyQ87%_vriPiuZh|>b(u`ZLj)R`+MGJ#8oro1XhVumoH2DsuBz~HBZ2>@mTH=XsQqU zz{3dsR&LJ2;P8jwnG3_|yj+t)jA_DeE{w`^5jz*9821h`Km1NIe>5$7r;xaqVpeJ% z2Ro1pIDcW%C+RtVaUA5SHSLnPKI;&W$>IC?#YJ$!W=$xeULpuKz zoqtZ}6rDe&^DpT937yk){*=ytP3OO%bB4~3==`^I{yREn>HL_^e^2LspmUDS|48Rw z()m|(&eQobI{y=$|C!DOIzOTFuj%|Rbh==ltlC6pGo3ATy6J4C^AeqHbP9BO==9Rr zPN$C!?o-ZH?WEIBXMoNion3T>=oIPfrt@7o-=njK&iCoOOlL2hVLGqSxk~5X(fRju z{!cpB==?RE|BKH5P3JnDpV9e$===vdH|YEgo&T54|3~L0oxi2?AL;y@P7X%dstP)l zbT+^|o7$WJgSmYU$(J&z`E!J8sJE^IpVPECJsp^fC~i+xH)Eq^3ASGDdJ;3oj$dM;fB6ar*O*TVF047GdUF%RctnQUMq6bxOA|| z7E=(FPrS@vA6{&Tq}v3vSBrq(ZmSdU>V@OR-d z=84?gbmC_ay%_&LnlZ^%1lk5qIT^@6)tu?pVgk8?p0-UuL<@CF>P@gYr@ppl=}y8x%{FAN%e9rSamIW_m@)TL#lHA zuewTW>#wYp537~G^{Z^VnsHglF5x1RW)lkfHMy`pNgqC$>;gNq}a>$p`G+84f#Y|l

Jc6ku107|qhI`!oweoUFo$YT53g2!b`Sxab|jwsFX<~B3Ov8U5C$!2cKB9Tt~ zw3=%sJW_z1ur^osq`HH)RvXxL1fM=RbH)MqZO{2Ex{3x*EXCQMv%Lw=%;+?wlCzfJ z$6?_`=?3J6f%85#Hws@49uoW?a8+H7tV(OH1yKZ;5D? zt@l)}yvG(ymDBI7 ztiSh`G|58bqH)qn@@<1OlJrd9GU}y-mp3S;h^E2W>uk4jnO-arjrSFWp5cYK?hJ6UU^X+A=NnA~NuJy5~@Fk3b;g>{v;^pw;+AwEK25EZ-LZ( z292Zw==F&Eob#Lh@oDd0pkKfC*p&G5MNDC($QNm?7Kyg$nmS%htK zgL@z3n)|#uUB0!G%~qdDZj+ME4z6e5adBN>+ZdpW4lIsEL zdyQ|M1qTAMnX=?Xa<;kUTU2x2Jb4MaiG9u`%yYShH_yB_*OboCf@Y@YqN!T=rUjOu z;%CcL-}~RJhHO97W`mQuB`qf+v(#sMFRk#e&CSUseg^F$Z+q@Z8xq&%8nQj_m&#GU z{X`bpW8eVlk$#m-*jW%+WIp#!i@!O#HJ?LY{}&p}&*mDuhdn^Oi{s*PumCl6E=14| z&w(e{zi*Xw{vwp|W@=N2xhm$eCf%8nb)^qdQqu_hEsc#hG}q=n$nk!QB+`s$E{Gdd zDO(@&)r9+g2M!N;d6R^l^>`k0GMCkCE9Fs^64{|nnr9c_W!*eELmd}gY=rOFPh=OM z>0YJoLzzG_^H`5rmbi*zYixq}CF`(nzKjn__Y8_F8gQvE(&cF;EeoEbPlvE&tZO?99iwnNcT~UO*~d>b9eFm z9>%v~RK{Z^*h8|3g2VM$gVcsel0xG18aDQtS1re99P>IP6@$~w$Y>hCN@&;50jfxs zH3vMOQn}w`+5Ah{jQG#{AHSqsp8vf6ffq8R#7oXfe-HZ~e`l+bNxN2wYM3;*_G*JB z$fH<1&;67;#$wsx5<<1(NP>GUPm*1!(Z_|26;oW+p=GTi=S(SOz{{3*ruB1jjN*io zrZ<1YNwe_@NFF<)MzJ+@rmDHbtL1kqDytg4Q~G!C-F}C?2i5Ysw_&-B(?jN*EZu$4s6~3*3Dv$hw%SxXMOnXXZ>jI9#0}3 zU8DaI_xlHUl3*WwJAK5=59R84?2X6V_~e;^hXuhJ@Rk+L&*j-Utp8zg$47D2;9(Yi zYeuqeXI@m#2Ql|8B1RTRGSW}{n1ej|cVMjFdS_@E9`C3nEb{Q*1+~9Jp?M(lB+5Th z9>o!KS9$+1-1O!w#4HcJbry&Bn{^vSiSdGUBH^m@-dQ2MSt41gqx6u)E=UQFwG+)x zoM$-oaF2=afwqA_w99eAi0`Jes9yuQsQ~Gz*mu<7Y|^}3_09c%{V$h%rR9@V13&-y ze@s=Dt-u_Erod=2XSscehKlxBotN8$X#_P-YqWL~c127QczK!Sc{%1bJ+H^WsAt%+u8;$f*)gurR`LdaB!b=k$(gZ|*0EwIw7vj=(a zMLh7f3_Ea(u_XRHcJ;mt|L5U<+*VOwCG}JW z3m&RA0|Pc^$;!{c(qU(A?2toET47Hu$!|%Vx7jhZ*;`D+^zp_LY_FLHPP5Cjxca{@ zdY6D&e0A!Xj4hBn|Fv7&6mVKHZ8}d|5wRaanM;$KKk*S>t*dSmI;(Q^e335VkTtUA zC!0l57uwuEb*FfPX1XKF4xwa?!+39vUFJKtnno$wS?|du-03>Z+!y+IA;IxBUoyg8_qi7=?^-GRz!iL^R|a=1$HPN6L|Mt};@N zE|GF1Mq;iqQjU}(Wh5dox*6$y-`bD=U61az+mLq1-+%3Y@3q%nd+oK?UVH7e`2)rt zCB(ELC)TnV6>8 ziNl8ueRO`!IUOwIovz#=W6YSGXw%}b=UG7-$M~L!GwAu5 z=X#jq2i4azibm{sA*SsI#XPItjX{$u#+pj-j0Nfq;zBtr&UCv zt@xo9>#idXdW_~ocVgzbDL9R({{4bcu#}AGmzZ>`!f8=5ha`OS3CGEa|j&R@kI|c5&c%lx_+zh7?Hfp zo`(<4-sy#-r367a^3+`!W~E@hj6falO*}& zjz|l>dHE{;6!)ce2xf7Dpq-!lAd0+=B)3NPtGUtoSvGJ&OOzdWS34hDWGT$mYlpM- z4%s31oFyvIidl9GLP4n=kS{uGLqbV?qwCfep$*Uczjr_Z^1Q`Xi-xSqDhDs3!AXG5 z7bAG5vyFm5r{XU@I#v?c=Vc6X zdNglpXx*-VQ{?CLJB2K{h5Lv~ywS&aH4svr!fquYI3FGq5}I0WN0o3LRaF|$9H6RF zeqrC=?tC_!$zQ+9bn_~a$<*%HOY8+UWce!8O20EE?t^wp0a{Xs0JwqZfF7w-@2oOF5YN1DpLGZ`d(&F(`{ z`bDyMBj6GzA~j`sHfEWyf^$_ki;aiT@Y((lQ2FUS;%f80hRU)teUqnZR}cbxMd^P_ zj$cYS$`{|U4+lFAsd#mjr+t5*#N5O{Y#Tj zW`0O#tMG}welR2V`KTJxd;fk62hZbTiH6GnUUS;6(X;cL7?tXX%ocp3R${(=kjoo#5hLq-K$xb!OhLhi5xnZT-dp741XK zHYWCm`bBK&LAx?qefWlYc-C&UzaRV17j)a8!cNgPh$mNIA0lepeCesN9o(vPi4PH7 zuAe`;_(#o$krr`ELO3l(3?@t7#LS@&p`YHQM15;X9|jxZQ}d3CBn3oX|)-_F+GS9e4xr25OvfiJV)xUg5h3`pqhH zY{cW$s2iLQ11=S~URP%9Mk(^CAb~u}slzEt)IpQ*c@yu93eWENj;!z}En!@0-c0jR zL2FOc5ci{7s!qVv4FScDVq__H9}T@!#?mI4Gafey#+_D^G=w&S5gx=JWi@5bbMt2x z=HN)O$_+D8Ft_TkX_O$zjK3)M-y6$_-O7X8=?bh4S`h8!;m;$`Aow9t z?m_e~1I>5C#MF`ed6#g%J_zIuXQOhZFvOJ0P0T1<9D9pnk3({C?1hBJY{uU8Bai@$ zWQk+#GU_rMdf0R_`<>RY!I_Z{ct}3+CkUm{LJprl6Si#$VzV(S+b2>trEX5$l3Jen zlqzd2>1vinDGk&c_rdAblMqTOcwfB|%%kGUXZZFNK4$o6#KD321QcHMTL?+c`b(LG z|0FMdkq9{MPN%<-mYW|aXD4@-irWs2x|7v>sW>uTE*-2OP#wK1@NY1M^Q1esjchMx zC)|Uj@}q0-cgq!I+;X6&eLX<5-ef-wn(MIyY?S&ULQu?d<3s+t4}I*|BNkD2zWT=NbX;IJcy53y0LyUz(W67IT>$ z`65te7Pq)=@37-sL%Ql`ko1QBLRKH|#N_&dnp0ZPK_5q(O9zLh3f25%!9~J#gT-;T zoUgh$gc$@Z+z(}a}{!(!)zkf>gbo<~?c61yhdmx{4huvy5 zU)*0Yyc>fN+V&X(M(9m)w-0LK4`qwl{cc&8pZOr=d@7n*Qgnf?ND|C9quo2OQ?j-Qt^6q=0Db@!IDwwkT%sXK1y5_X>=m$Hw6mU zM|@cze-!P**Vacn2k}sKmk-DvAKEb;NWtZGAjR%T?G9WSfZ9YK-Wn}fpBlGC3q?ut z*_5*~%FOq;g-P9Z$C>&6>Qe3MaTK`OSIWo1VENW-`wCTZaOzJzdI|d{v+XTDXnxkN zL*h?{i}^6A!Sc()#R8Q2WXfsXll@kye79S4Az@18Ydaz8JK8&H$P{n1cyBZRUZ0ft zV!nFwARf&ypRi(W=KG`%W)rM;OSo8l##>I{%%=AY}5R*6!l=G1muuvmZyc9j(!cws!>tc&N>l}UH>=3zWyF$}HY zYV?=~A%Lu&y<>YURK)byGOXOkp>+GFfz+RRO>1O@HZ_^cVyUxN{zO}bm453Demp^5`ldTty{m?Rxh+tSz%`BOsoMiL?vmy6!>|eMD^>E< zX)H_r@4BUy9Do61Ya@~Tr1Qz*ymEGCuxGKB5ZrM)-mc&YY_+*)XFj1$H;dEbJsF?uNou%F7 z(!`{+M;i_Na9fI#0=Ba**pF+5RgOyL@P3(u6&+u~tgGiSf$d@6k21*Bug;F-x zbOT_1&h9I?yR&7q#4Vt^N8L&VCPoeE&bke_StFv?7{l2E?ocUb8sbUuzLe8FJYA`} z6YWM)G11ZQVvb zwr=0pw|--1NB_p2jj|m>4cku~7VhcV*wekCbJMzktv&tgde--Mt=qV5L;t$$>~9d$;y)>h50OI}nD@ zz$_KB2=fTQdbjp;_N+%G>o#s&zkXd$_txHZTl>4a*L7{}SifQY_6^&65ZjQuP`+kw zuge3U4ccbZ><0PZn-yk9!iQ71UQ!L+UHhmTt<6kSMoVSv8LiDg6OgT~9yLtYX8N)8 zo+`UritbbuQe|ys_td^ZeiX~8k58MR!*&w>_1A^>=^(rTchW89+~Vl8hO7}&;F>1C-N#Ep@PWDreldt3_8SXRIXBvF z|D2pU5@nBBz{3=$*`gAK7oJ@j zP%eC^`g!86x{bWeS8}i@{0fZ{&2)`!BCTt9Oa(2MHAGqi^bf+?ij8xlZ)@E)FyII3 zwlxz37#)|5vwslM@YL8?{!p|dn?^}Xj3^{U|EA#$PfzTF!j=RN>Q$ZLv@+OGs((>n zTkC=e8MH-JjKWck6n<+!0tGSKS<*&iO+xIr`@sEaT?{`N2DfLb*jLOJrYm`~d9UCN z)&=$XASjK{HZ04gjeN|z2IyDog8O0+oF1fm+%bs$;;5U$5`a^@0jRKSV5t@a3n-Ir zxeCOR`lab!hO0rp_^RlE<~UfK^m-eK%5Y=%q4_NhXFgS~QB?%{`f z%vPsrTLa=y>(d&%tzVUgLr)#9PY25r5yMV9>K0xdB&?opb>c`#i9|ohU3EdO34&B0 zXicHQUwI&39WPB)Z7XE}sirodPy|6o3^fXlfb<@qU}t0fIEdw=>O-S8dQfV=`6{ma zDv)aB3eTu2@6Nh}U*X%?iD(mkEzVkO6y#YNSiw4L9~mDB!l_9sgA*>8&S4Rmp^j+pfd=yCDmHK@L+Zad%sCN{=-optJU zQv){ZVAJ=wqf;;d(0;Fo*MMD2Z16!ajswhQuy9Xg)8V=V`%HL)Ga|eET~85Pug(UP zGv9V%h?fGQ1U21IYp^$0Q$JO!vHU2uydq1O`m@lNd^r0c4Rhz88bAQ?J$1?6UIQGB zf!tIJfD|!G(qC7jyX$X&tWBtQN=#IdhWMt4S;O9r8Nw}3VxowdgB1JM_k_7U9`D`R zN(H{+LEjBas#+tXfr-Lb0WsL z*TS}GfXVDSopP%84)5BT8ODCrL^g9ETgYQ`9#SAPo~>l|xo$BtQNl5VOtqBB<5&W= zMKY6WmTre*jGGy+RwwVgb{vw|NA>{zJ`Fl9HBbF0}r zc9F~kw6`wrc7_N~!Dreth()HGEswZ|5{Gyr{1zZS=DUe(3^-qhHlUjOd^Y+j6?bA^ zVLBrG1)>jZ1BMD8Secij!@>uzfkk`3H6p^(T9@;IY`~@%067?xkWg6GduvDdzR7V1 z&)IOz=52?dqEwAfTVG|mI7%x3_LY2CG|;C3EcEI)N-QNm8(4${!0mA-=?Vf%cik>+ zKwS@j9?p)%h&o@>n+E6>#-i=^0h|RbX$-FkvjM#{&}zTcT2S*Is)w3IlI9UwGq96Y z?SsP#k6?viZmKJJl5~23G)qI0lx;vw1Iv1zLq5H)rc&cIu>lDcsDV>%mK%XF7C?d8 zfNTl?#2!Zii+D(0lDF%yP^aO^-{JF9kv%c!fs0ADfHM5VR9*M@#Kl&M*uzWlXb)wk#~7N>Ax1aK5i?UWh-eV_a=BpKe9U(x0v z@0g^2vm=6-6kBL5?~k|sg=_i3)$HTJKXN2sN8Jz=u)b4jiQ=`#aTbnte~dL>5pq(H z@E!}-556J1z=T<(M$~Asz+Dn%j(WenE)3`Nlw04I#uMjS$X8yyhN}+$hDBvdoILlR zvR$)zw_Aq2YvTScy=!8v7~LVASyif@SlEBLKIouBfdxz_2|qBQxJpPSzV{{W4Tm%~ zM)C1Ej3rK8UB+W04y+F;->c;G{f@jw66%(iONKba=O@kAjv5FyRaY2fQ-!_EFsIMP zQ8U)#--9|oZ}DbB?Krd@Cz4=_6VIWYQ#>$7tDN|qZ0ePBN*z%?jwwrO#o65w&M(zb^`E#b^H6KG5p{M=lQtbjTlzrwEcS^ zjYYgs&YGWmSF)4{wJ?4t`SunQ*|G;>15y>cxFL`q#-A9Vvytm@4)}@9_td1sETIqa zmdC)RKyvq((G=gqAt34}%+cD{Q8^?U!4pEu``HkJ3bp}k$7ZilfG!8^J(TDCrZfqk zJ`w*KswX~EBsC#HC95Ya)7Ok~77i-2h%eGMW`gsX%~X?x@$Yi>ERabhLlTnt{c!{Z zoWRaAcDA!7tu^IbS2m8+CQ%`BtBN?6=-uQ;dM@uCB^8wTnIU*)M{6!?Ht#LwRdgTC ze;)h!<(2{*u1J3SK1E(tLL}K&&YxrM?>@lff`M&Y_ud`slV}~jGBeMloEvRO#dhpG zVLxkKe_v-uch{YR3^O-@15U$L>^Nu3IXLC3P>?+MR4q?Ye?(Bck=BpjJ5|xHTG%qY z>jA91OT{l%GewvF594lT)GLrVIG!IJ&tyjltC9g6Hw$%QKl4=KkI)_=UP406WPhuD z?TwW2eG9*afBVYHr+E}oUrgb$4+)Am3}2FBH9x9%-g#CS#6AE|IEFK<*(x7?wO-Y? zZP*Wqs%S$MIa#o`nlB(?gRBhnu(2m6JWVgOCijs`RS`kLm(W=CWV5LW7YC=oB758{ z4ocg>+KI``2T;8xB=|qh?meM#jRTu`J}}VWqy(0~9RqDcXUCm`IFHHUgEOYXIFwMx z-Gk%h2!qNq@9LCPbdYT)Yh?13OtzRAyl=`aPxFL3e&sUzra6`l5GK5lDgrTsL!X(0 zaE6oNNDV$Cg;Wd1=NUPK9>6(!@gD$+lms|O%hohbutswFdd2rzyX0&#`pVRORki!F z9hzs_uI{6Q*?6XcsJ6Oqi>0dcZ@!3#4TkWQ+H~-L^-J-Hy5N528xaxpIrw^-JPj6U0Lx1}A}-s=ZlI~nXGyl9G!IKktWG(f zi;S$yw@0$&{Wvmi>_ZUrADO|0Ln%8(#<4c4a*bqnH3&|V zQpe1&Tx!qY3`u4y)WGsIav`EemKRCjwE4Yrg1C1o&sq(<+Fd!H9WkG0a6xkm|iMBY(7JC&wCm#i;lSNjp z*NyYNE?A^^J&G?MNxZKRJNbyCAFDfF7ipZiO^2aL)zkqw+zYT|AOxNwhQ~TgO zj!y{@@R`afka=?m__|Q@y~+OSAJqb zm1xfl;gYsI4zXo$YhVHHV5LkhUqK8~7e`?V1qA7Z5rBu>s&1xOT34FnX&O1OhJd?( zR4n0m8Y}{MB6e=BeUX{2EM~fF{c$^PGc+!g?Sr}RWazbgz=h|Udcnn_baP&4#dGpf zjjdZe-$!+vOOLS2)&YwZU8y7QODX5`-+p*9Tg92eqHjs|FeE3}WDggDi?YF#Gaxr) zX0~L${cx3cWx^Yjx93eDT76kkbLJPm3}vgMLTJA!Vu(i2#dmW z3u44vH{@0-+5PTd?wXxblaKBXM~%6o$W$)7c`}PZh%$%<;}&`CdZsicsXDrVe7(e+ zR=QMcshda8JUN&auvJ{*=FA!VcPKi*yqj_;Vk^N}YF z4k_2UaeXB(-7T1lKjk*dz`bcW>RAWEiU0%#=nnuuC#Vo&_GO`{ZrP#6%R=F{SZ|Y} zcjiALM(%&Go7o7WUMe8GXJ%5x!$8mxTot|VWI2z^e!nGQWN;=RU(Jjmc0Km61So=*R?E0r z!S*a&3UCo{xr_){0tgZ6hcyg3HfSs|h_gIiYCK3xh};jSoIjIY+BilMw+0&rr|eD^ zvZFvBfkMeHyLfTAZB##8CHCQ-Vjhq%u_O@z=aH22^?85~7EGw%8!6|(Syy1_37bXQJO)Y(d!ogpQ zM88BtUnFciVIv`_eF;Ld@b8(wg^K^0)d+X(`z8X<%e?_n{e5PDuwAgqgJ~pFsM}P@XFk>9rZ&(g=?~=)+zEeu@VCUmv~(thFm++mytS7?gy;U!g)X{~6@? zk#Ai!XEw}vYqYsBVg#wpBX@|=Uz=1sKYZ$BLE2zP;urw0mLVy;|BcgH_H@ zSe%!N`{D46V8x{g1Xa($R8t+#Rx{J3DFmOuv6QN3P@*wO>cWfz#T!}50(8{e8f3t- z)Ko=TeT*tDb|G^V#;EV8s8WIh#$*W>Vh|=LNu|!W<9-o+84&_#2u{|rCFV5!=VUiiwDrfP@;Lm?&Ef}#c6htr<#rVN6YnVhUOM-$!8!h} zt_^p3*AVLIG4JZDEw|cp{n5Gfv-K-INYnJl?&Y#)s!tSfvvaCE>M}5Z_W3bM6?Msy z{W;PaTar7gD{%u;e$1^@A2wGdtNm8he0GeP$C;kc7ncJ)?{@pa+#XkzW3pVL9ce-FSb+A+>;!9(v|EU=GUY5UE5Wiu^-bR> zFY9L5Ro!oD`nwIws2tWfh0-MN{__OW!t)+yR&@PnIX}s6*9ft#xKVlnPBJ=$a=q*p z+-$`KGr8c);4E%`@z#6J%;o(4aZJk}PdQ5q+2Z~wOnU0Fsw&*a9A`Bc3HOZAXjdGa zUaqeAGg8n`piiS>n-Q}kX2X{22P-?L3I#faIS6!j&HgfOn*C%FjA0jeOh@P7m0ZJi zbl$ngJ&<<~?k&>j=Xvpjj^c@_G<+0Kj$~mt(CbLHN8q0bRe%=6%G9JqwBWd=(5hSGPvyEUmRujwmx-1 zeJw4%p;3l8=K8Wb<|1y=XsqzN&VIcmp^GjD+b-ZxT{xaw7}3Nb$y48z(2&HA6V|@f z?fi5(x||a;)FE4Ecw$}{>IfrVdND24H1N1CbljejJv`^oaK`)}1!?=jkEw9;(!v}D5f*(OHi+dOdu)8OMQv>8!pK?0m zD^n*{q^51fsR_5tcm}MfcVpufB~tcY35hSz`?!(-Y-vRgDBGEG*2d$c3-yg5g2S*| z7$Z78-Hy|ha@N%>vDL-2=Gr1rQu&R6jlWyQvoHR#HqpNLzb3s)r};nH()J~}(+q)% z*t(Vlmtxor>mTWKoVuOS7cREJar~G>P*WcpU^B^bcgoos-%HRwc4w>O?S18Pc6t!5 z1c$Kufj(ZddD|gem{WDtPw8$y^X*DG_cj30+=|l#R!d~eFx)&MX=jmCh+5u$;#`VG5}D}}~z=u0`9Yd35dUQV@O*`Z@! z8fZf(3+%k~;JiWmWJz=yn(TneIgZSl2QLLi}b90Cqjij# zzMC#Em66?TgH?CJ7Ne)9^6YH473@aLA~@rCJ~mR+77^^Rw->?L?aCQvdZ+VNVn5kL@U4uyIiW)#rehP`%0~9C3UjnJ)`^~R58d^!jJ_!@ zk)bM-Px%_6mc;NTgj?)3d7yvfmm5L%C$e)~ww(SHzTBs=rl| zKIX^rZZ4Kt=s)G=ZixYU)mQSWgQS;gKQb}pZx};IxaCP)yjoSxld;XJOv$O-SmfaQ z3uzb2Lzt+QauVBQd=+IUmW43}xoHnSMl03qOYLTU7{B^-4d1KRlExFgGK+71ZEBGk z9};Q=M|tqqCGNyvWx-#es%4cjg@tPSR-X82XV@7=;}kqH#j0LR2m=OI zsF$QoR|wD;`hb95puhfccl1#XS9!5gs@Km+!zpKHh{o|S?uXC)*z`cDT6C);qM(Os zQns2|QUAV_vm=BBB@5Py5;WO}CM%2}dTU6a&Oi&j&id1ob7!cQy+!xXBm|tB^VXsI zlp<`VJ4*X4)y()4Lurto_ZG1|$f(e{#G;#1&W1?U(NdUgGzeQSrj%_d=PQjB9+q>J zB8s<{%7Zxz1r!-bIe*$Llpv7xe#Y_0IzMk;!#Xa6-Y%To$rrPQejN583uFs=m~Sr? za>Ca*-(yBTPT=~o+lFt_ILGuoxQk0@J)>*Uvu5*1nbCN;+iQWK;W2%XQ2v2z;Ze&> zIJ{t?!0z0ia_$N>G64!uC>5qS2M#vwa6j7%bXV9{>y@vG3NVf|~F^6mLScICOy# z8)$*1ziyaUdRBAf6~cBODKunDDV7b;XK@0k8?1X*%GngpeBna4!=XYw#`b(ScsH4E zg2nsks~9Y8CNr-OI#SM>1YEr3gRLHNzqtZn^X3YGvp1%k?z$*FkjLE#Q?d%k%YHI; zLAz6q-XjPLG_y*8dc?Q{5Go>!uzOU4n7aK&1{M*qJ}{SHQm@hdq0)>@Nco5Eua9P^ zBJ_ETrmls8X&U7WSA8@r*#1I)$1I;3Dak25SxDMS<@E0EvbzI2C9VXgFtqJN*T+oJ z;kpHYzfXeugC3)X-eR<94SHA|_g=G^yPd@<{1}wiLRW%P9|C;Ye(p>;gCRCo+V>t8 z(OU8}nOH4w8mg5nWY%ono-gKhT4HR>i(2--z=~Ufec{Esq}%AvuAc-wQg&6``of6Y8OP&j+ArvEZ}Y{RuBpw{3xp_ z4tp|R%o??k4oBPj3I!+=46E-cAzhFpF{+rD8hOsUTG?BL_o8^1?9g!wuqT$wi&;)^ zf&jaRTl&HID-HteNKnSq)+{A&2vBrd@kUY1WQS|G9_75G5!5?%BPG3lpeKaV6(Q4O z1Yf8)=wnj2WvogCZ9(cX{;23vZ{GuP!>u_HUO_>m-sCP|9<#RVFwUL39My9 z`q!84F$jA1UOGesd)JrMlPQ$mz7N}RxlurG0IOZ%#?Bdh%<_B~VX(9!VCJi!mCz1# z2&OrehXiV@Jb2YdXInq`JdOb3FBIxNW#U!sAsgqb)4~FpclS=}Rg;xGQP?q4r)SR> zo0YLb)k9E_=7mKeCe!mdu*5+Z8RiB}y`ArA0nDI=Y&;F(=;Ect2td2)qA9#izk@b0 z5k2w++aZQj z!W{1FD#!yf*7V|FHpRdi!G%b2ZB`qme(&{tq`0!%zkdc7FqbJ6 z3)AY_O*MDo7CbH+ap9vJ^F){W2g>E3rhoVp$MXB9pnF4X;6533p{LCE<_8A{!&A!W z*$}c0HB{QF+Zz`-=yG)LA4eVgVnU*W`3sZ6t)4H^5X3q~dsbae z$bkO62z=akpp?&1=R(-o0|@0$>*yh<5D{os2!Uq~$BdYEj+l=1DvI+?m{xTRXS%~z z28-$@=%t|2b8+I?A? zcR5JdKpmGs`hGO3Xn>X<+ANR847FKK!5I-FoU(09<-k()EBR zI$&HK9T&+#k?Bbf5t?#Ew(O~5TkS?~4#H7Xh@6Iq>o{=biWF@S^-DB);Yd~lqlOw2 z70D}vQ+SO|X7(Bzh`Y=q?rtwkPQBZDW}61aYa?*Q zeVuuBKD&DRpc1{g(&!WdaPV$Y1oJOu#u%1~%RI#DL)1Q`WFdJDW~bNssg9RR2QxWbo`b$ZSTr1ZfWc)!DxG;csj0m%EaSo;6TVa5KDN_$ zVNmcUl$MUqg)Pm!54c|hyH!;LKL{9HmLhKGN}0)m^YUeoK*Z1(!hL3Wq0l8BTZ`Z? zx%gTn@gj+N<>V#i101~anKfoNNs7~F_!7poyo6CMdz34ia-q3)0tXTDusmeM*~;I{ znU|BK>1@pLJAJBo*@c@_F;m3tR$M_-K@$Z%QyLYQr5rAZ$JVt^*20X>TkjcxS>pO! z8odo3W68#z&x)~UeR%JPdn5gbO2mLPLjO5JXq3$!OF6fAW-xVtG5;;G9TC&+j*WNj zER_*SI{z(mC*DYDFAvy{!j&Xy`}oYR0fgw64OARw;+S-+@9i=m( zqYMmUxsy|F+*|`$qkLtp+;YY=J^1uMs%-TgB-mBPNxYA0KVLcyN`yS~56Od+z1-c= zbLX&d$8`vec!yIm( zr((W~7jRIlV*esnLU4Mnfc5o#B3MTi0$eu3V+ib-*s3e@11^_T zwI9s2bbfA5#k*KMc@=P_`JjQxBBYwLYuTrEQqjyJbTK>B-s4LHZ58fmjShgteEtRb zA~hDNaTzR8wb{%UWXK+dsI+?rBZl4b0fhKgM^C*R_9P0A{@1Qy@$39k=z848F|cjx z-n-`uw0C;gi$lOO?TAxWZr>wIPwxYRR!KxH3Fsj4dUoPamp-6%>H6C{6Y1gNLsDUD z4_x_zCv3xwZN3N!{-f}XKvJH7(oP;9x4e&_0kcMFh^~hg63(a%rJQ@z>9Iyo@-=Sj zG|sCsUVw#$18j0xfSf055*p8gt4IajX|q`!3D2jToNCW@b0vL6M{MyDIy*LX_ipOO z;Z|SZB%Bw~@h@;>a@%85dDtuA5a5;S$Epm*74lmrP6SS-oJSk3NgqnKz=jZ{6b2O> zrPaOcO8f(ovzQyMLi5j-b2^ip)e9Vk^iC98fXR)tS+*UT1Y8M?DsOt=jHH}h4Ywl^ z*r0avC(yt#3}uVBdVxMUD!4H@Y(Ha!AO?^`Y_lR)hg=ve=Bv9aox`K$ z{G>0G8jc)#LM{s16I3E;!?5*lE9B-E8nhTMMo-hYVI))#rgq?NT7N}KIF2`(n+I2R z59Hm0;TA*k%mJu|hOx{zmX?eV5#C(UG^P;H8lY)8<{mFV&urig>qwRwUjsKV2YCTF zqBRT-Y*TCq5ssJ%Rm4?Tvw0UB1$lZ_3&mP&7+Zcgd%)FFJ4aOq6GK%U!$qtgFLXzP z*z}hQh3sU-MW`PFGl$`?I9bG)P#?kzFRwT{E+|5mpc6+^&YvgE5PAsc(W|BoPVCt{ z4g#yq{>l)p2!YPE?Etm9QrV1nE0Y*NtCplosDr{t*DzR&0)=Gy^OWOQ?7JI-^`pqlX&=+(p1^JFl`M( z9f6NvwOVj8?2IeUl_;3q-Ac-q4RM-HnY8o~u>ah^{ zlg36PY}7Rk0vkM4YYiETw>CYfZ+eZZZY8pVYy)PLO9B__>Lq~-shdqX4>dJ%b^nE= zm|69QDT1NabPbx)4c35>=fy&7P_XU6`j}C>gc&g0njX3dO5a8d2vL)o251diaz}>c zsEz1(0ob%cpaELFzypa+Z+a%sblr9cMks`e>4P&MA}#sH0!g_My$!c?eLGBpeW7CT z>C6IUHu?`n{tjA9s^P7QP3V%QiI)rwQptz3(W#z72+R_xQmZ>7;8lywYA4kG zE)d__n2dI3A~c{J1~qSMrNnk$As^tZq%WL^5)4F<9o@STm{Ph{{#t%x(Du0$2XEDq ztl`>WL@ih4qC{-9VTNM(t_MnG5&I0tUdt{WhNW5*QHQ;zft9wWoW7*FGz?FpgJcXy zkMC)jsx^GVU=&)qBRpgvh?{SmhT?6|dhqI~w_&WfKZBT;I7z}UQ)Lj|!s<42y3J9$>7nJJuQQP$`l_UKXgAH#zI<#vH`^aof zutz*WQwy_u;nJ;m$~KXhx!#pN0y(e{iC`8pHbP{>HJN?6TwuOJYkf<#S-{i)y2;FZSYzg1SKqQ?0GQd3@6E)`j8W56%J*e{Uy@j z^#Rjc_JC`Lkk9WF0(owr6K5}83RY`0)yEqB$j-I=$(y!niQD=R=`#}q2{mqR+@LmV ztm~tPEch&uzZ?DF8;-L70uKfFrIuYdtyVC*^OeI;n?!8?8tFX`7&MCMhcTbG_{NSo zV^a*l^Qq8I2GI%Fdz;9@SBpdr?04h(15p5)Icnf77XsDkAL3xh%X8?5tJC~`v$Q@m zMP#4yZg~glhP{KjuRJ=A$mGV}M;@a7zH@5w(fyonMoN~Ksk|d6_7n0Uh2OCK2KRL3UwlY69*!Haxl|CzYbGgI+Qua}}|2aYY1q4-V#qrdVPw znLAU?T0OH_8p5EEAA<#*WM8w{AWHf#`0aH~HPyTH$YU|nwjtcx!oUWh_I1pguG18z zAA!sp?7it8vGo|T5&+^gNN>oHE4b-l^Q&(#-{Te#4@X^@W1O^fYq{HO$1;>9pS{kG zFr76HN^ng80@V;HzVczsI0%N7rE@Be_RXn0n&>m~hI`zHzwWUZBRgiKxq`q{zut zWQY@-r@i!Ij;PTnRTfR-bwY^n;osH_j!LkqNWU5*GZt9V(7d9B=@l&J<50VX$EC02 z$te!$%bVu2=2$VukxN0tzt&YyE8{*B6U`RhedKnyd$ur)+p(0B4Qr;e0*@GbJg8U! zN^;4nS4%U?yvs{lueLbhP>T#}Hp5zJDQc%h$SmKULqq+=^)Wjt3hZ(&P&`U}Xy2AX zmMo<>nd8d>5Fl=j^m8A^x*gU`xBO-n>fwVVMmX>Pj`J9Ipm)0hezYTzaQ zIpDbj*e33o!rnx?KErNS&9DVbxj2Xtu?v`1#Vwf>Bzl6VW4ef)y_aXwM@pqa3@pPr zUYnEA8!Y0$em=LG7x9jl3IIwCD~g|i87E8)dJ0k*=dKC}KjgEjCxeR{9SWSf=om~u z6)+0GSQ(P>!NE1TUKlt3R@{kwg=vKItmJdX7Pzd~weRLoWnaErjFZ&&UM5NRma8EO`8}4DE~S}-vF-8qO%^?em0&}Tn6LyeSa5cOz_O@DQA`0jV+aBowa835UdP4-9z4S zluo0tUy|;+KIODCW6`#%r;<=fja72i{>PWESU0?86m#b=W=D*VR8BZYwzrN1gvvs%IU39 z0CNM+&?rYiujqOf^=%LONo?xUMIjj&|J);P=SCLpO*tJk>cvcS?=A^9?Ny33L>+8V z%El2f^^(v$h2&eq$6>2tV+f1LNU_z@YpqUM#YS;u&8YD0uSF~Go5Im4I4~)@SUjts z?E%+0kG~YT6JYd>BGzHq1%f%&5O6RCO#;5H4T40sPE0b!PApK<;O05^@|N(ONi!XS zh8>ztpkrXz!U_;LB%laat9Y$$0L5aup%ygT1ClAW8v@RYn^I0sXs8Xl6WPge2;AX9 ze!>iyHJcyE=DoQYQ*#X8Q!#MM52%YDc9izVvn$4o*Pk(RVz^pqgwnOF20o*Z=|O-j znP?uKLb4q(;}Kp4&dxX+Sa~2{9oKVAK<8GSSPOzS@&&P-$i3Vnx zrqDo_YpXf@-<8}U7|`2iku~(N6a6%2=`cn38?kux+JI2dm>6W5fnwU^^Vn+yndDaa z?1>go>0q%y4M-m}yuVBJFbHd$A1zah5I1-VgybN5PDooVT^zn zjWwyT3XY(M@vO?hG~IA}lR458wLWvQ;mz8swFIDG!k8hfMMV-h&>iNJWfBl{egHvG z`ZiHa<7#4*&^GW`%-ms2Br^vxAK+!#2(s>DjL2<`H^3~rGd^j1FI zV6d1`8Y~TdGKF^|TCi<@4+KxlLLgFD^+d2JI=?;T++MSxs+>%Sm@#6;b+9Hgf6&2i zZ&{AbF=$=g=K0GFih5q1mP)~1F_wT}rT0y_u`9CmYxWBZ}D4s zTz$y{qj3rr7$IiJCqlcwD*=vacvHZ-lYxERi>z&ECx=AhG*IaUO~Aoih5YJ2P~!P% zXpFYVYNQl0jX`+FC}FkXC8HL%5};VT zzk3#_*{z&J1e2g9BN*be#h?C(?aHJ^%z`6swz$8>tTPbZE{GSjwSdInVN@bB&~_K5 z_UC<`f?=CSj2avUQ452Z;$c}>t9Z=Jl^{`TL5VR9YbZf+Iy)HZc(b8rEwv;Wj}VL+ zd=X;`+mk)mcspWP!oSyKn^6OWn8}lhO}W~Kn>lT8s#?U1Q6)Up$U1r&T#`j&Hfkqo z2o9D#HS$?)AG4_^Ut$QScW@=1he0;6*_cdYOgZ&H0Mqy^oS);(PVxCYB0{gPV8_{? zEi}P$7K<6Um=R%q^RJpcpkc8gbn~6BHkz}#luVC4eVD<>6Rv>mtk2Ct-}!28FU6SH zxFN)xDsTx886jMWT@VKq3Xl>G&_-#toecX>jE3JN;Gk%>u(5!jBWz2Lh!zBX`g8AM z1Vi5@@zx#T^?*ekycLN0pllD#fDeSi585>6NEBZe2W=iagp%_@iqD29dUR|rWUKUP z%;s^im<(`BnNElW-|Y*{SCTwx;>>R{Kl@tjXX<1~wZ~r#wL4*yFf5hNFWNL(4d>e6 z62?X(_=)1Mx)75`1YCC zpbEriYlxI}m?vf(sVSMXgi*c^F(MZIlFU0bp(XJSI%PHxYw%9ZeyNFLGVf4QB=b%q zKm)wPxJ=2slMJpV?=TESjCHVXS1al6^^f5u@*|@$CG%qvq6Pv(Wd`?MV~pVO%cIs3o9GjOrU*vnJRUF+{slj=nJ!9IYgATybw_6U1w}445)z z10${;aDvf!+4G>)K-9FLWmrOalWaQ+7|$IDnh-OvH74N&ZN$LCH2n`mEDrN=mA6kZ|G^>TENon@0$91460ng#jJwRF4N-*3F}`R`C@w=13hNV7_*-)dlsR>Ku{O1C{KU1!xwJAutuGt zYV%fsHsCl|F%;$x7=}R%X%a9Kt`}v5YiorWG2*JNag^eq^p8&!A9Zsgi$JWoqr~WQ zG14%hVTQcQv7UDg%(^UkCq|7*sCNJkEBWH6Rtc5%AJyhw&Ms1q2bY090A^(L#WHk1^lYguqo(|5O<_Wg{xDEK@8rsCgbq zV>X3?aEBla^P_UWOU=~coH$V01!;?WXH@1L2#Q<9lxgdL0Esb31tDui%V_AewhL*2 zjIo#1h36^X_hc)ce?NCWV${l5a4O88^lV7Ap*eUUdA?K7bR&IGxWDP!KEs?acOW27 zv+dz(ID`rqzs8K2)E?uA>-9a9)w97DTgQCW1txw|UGiQ9WL(eQi*0{JOd(P-{N34eiBXnnf>g0I0A-9;8NQxG0|~ zfo$#!@SvdsVoVShv8&)w+{C9jabn9t)iXuRY3Z z!wh`2^x{PJXWqT%;U!L>5SGcJ;#oBDRd%fHFSpI z>4^zMP{@xGy~=p@QABFCuK^0y7`#b95JE7*k!Y_s5Mo?u#NcHe^q@bsONwTjCLGYD zmY8M8{rL)R0voPQ|>goePK*kwMTa>?t#-z zq&Rpl#v0*kfF8I(5A-#gcTZI$hyyo@W1Ls&15nrUNvA^tiCHJs2SFS#uk1r?TY&?5 z&KizxroaJdW7bfO;iKU&v%tb!7PEG33Km>@CE|y`!-$U2Q<{WF%~ZKW6DBw4V`lF9 zFr&B2q9uhIOcb`>nVEP$$N2khe-(>!)uAJ-bg5avdtTJH*hI?odK!-LW zp*#=}NrB1F(>}eGEaun+)f2qRYm>+9^G}v4)H$a`9@6Wd3We||daE`s#ey)@vJ+Ql zFE?)H1(F2H@*3FGTa0e#8y369#tQKNX8Y}?qqnz@VJmtS$pVQ|yIyY|ASvJ;SjH`d z6Z`VGMgdW+hb4vPZ!$Gb(x_t=Mj&`+er#dQapt!a+*&R@>K3i00KHiN)I%+VxX1hk zc)NMAL;#l2%DCqgzB)-TfL0aLg2sSq;(8+16pKYlQ$->WMG@Rp926%3vm^>NeDPgLJ!Wt!k zT+F;ku!RgX{f(6KpmGJpOgTZ*l)29u!yC$ptCQDkzH9X1hv^zp$WFsZP^rKNhdar^ zyP~x+#!cm>{c{#zD2&-Fn{4aDnw6dDr#;h7gcKVe?f@ycAjf?YWr#nef`JSnH>Gc; z95+Pn*;L3KdsGMw!J-F6B&pI;%K7GeRN7@K)f#}eN1Rmi0(9wOm5Ty(AqDD(k^g2c zq?~0#`O$JoR6jV7jPb&8_>$b#-`BaZtLx68Z28e4TS;4xPj)2A4Xy>;=Cw;76s51wF=swa`q_&$f`@zX7w61afG$I4Wj4z zlshr4id8C6i63=_rX-DUSc-aV-H zXN-Z%YPFqL##+WrIgcnd4z#NA#%}5oR>ra+u!CjW*&S@>qbX-BsXmQ?w*-iiR;>@Y zMhTrz#?Z6A{Lv+nw<3+UQm3G|w!8YYLbyY1tvataM0B8vgu01xa- zIp5HP)HhLi5=Nb`ldmui5V>fVb#@CHK{LthP-y_$D=ABD&Jc20W7EBrQZYiNP7vm2>n7&BuovVaMo;SDv2r!%$4JWi$t66t)u76H^vJOFXm_n0{_7&Xt%z$BfJ*sRr-D49td=;Fz z7`c#0g}_Qc;T1f5qL#G&NWx*1Hktj-_<}L3?nG=F{ZL6cY8~WJvzchH;TCkNm3+LX z_$82o;zJZ>hKW+utr5dBjB}S#p<{8T+u)@jd!yZRh2*QIoKk4ag~=r$e2n2O&XlB> zJ2*YYhPDc@$zv&}FxQ$HyhUbuUAZ@ps|he^lxL%H$b-g37!`f0QE(}*tT2)MNp)Qz z9sQ9)BPNDfIABm%7N{vc5`rkek|7#BoO1pw#G(59RQq5tCLBgo6ICN)D=w@q`hw1w zI(>#!Gv74{pZpI!S1JI<`wo(tIzmla{CUM_J8@m%xmQ^Y$mia!O-3rzra4U{N8TZo z`wOKaHQdohv1t{|BEF?M=z_Mp!*zB&POik^u$Z<;mrG``nE63_YUY?a>I9c?oCeBQ zr`zErG?A}V;JZ_?iC$V?6=$9I;d0ounF&PoDHrnl)@C$2Z|Q08;Q!WU`Y~nWY|)mY zI|YksVQpsj)V@M~6bFq)c*l!6Dq&@pkGDczgO+czgEO z%sR!Nf4FK%+jsEm{Bk^w+)iqs()2RCj)g}e;hxa0KRSC zdx)7H)UE#BBh39b%(;z^9DfqR_t?rK|H#LW@i=^pZ~w~2@A>%eeEbNH!~ctKKrVgc zzwqZFK2EUGqn{Bf96ryKpYw4Mk0bxi>d_f(hu>n_+k6m-qn&&Ma_J-g#-3mY2|<}m z#E)Ff$5K9S;De1lf(~svvYd|7zekz5Ge?06+6~9xH~?_;zq00D`O$XdU1IfXJ4TRj&61VrBk%nQ zYk$0@?R&2+ORsEMv4Yut3#bq_O9@K=J$AE!m-UM_5-kNO&?vQt4uEgU49NBK{z6Kjy@;|qz`{axBAt!{I>gQKwd^X zFa%69SK2pVzv2@PM+rW+9ep8v^aE+yW|jp_+m3O}9(|o{XG>o_mtKa{qpv`EP2f2_(3 z-(3c<59-ct#Ye(mbE_qsW90LOB?^2e0 zm$Ky8Z}|8vetv%`DfanPt5>1424gsV>~*kF+c6Md9_eH6De5lOHGdb~*7jYQ>ww>P zZzS{^HIsqJ-@k{S|Jh48x*9o-u4J}v z39{`I?5COkr82ZV0ods$er7TpQ!hy7%O(^dqECppgD?~U_5@H&KXHPOpW<=&f8sI2 zvHTY|uqSR6*1hmH5{|FH;|UI)XMe))8Gd(Dt*Y+y^F-$_b`YJD8iA8D>Ra3KOe^wo zZY9LFp0PO;xrH}tkCmrtr?Bg4e^vrLW1b#TT z(Y$mZ`3W$CqS5iM5bi{3Wh=+0s`sf!Qt78at@QC+S|tLs`q*~vm>DO}{5Jjk+c)X& zfAJoHNKMbb%fu}$Z71FqgqNiSeRkFpK=;HOL<;a&RilY)qBI_# z)JPxUFk&;LvB!VKlBDeU5f-oVaSo4*Z=&7j9sYa=j|(D_Q0lnQiZ8F)nohn(LbFl< ze39VJv14A@&APxf$Jxv0&fsz44LmMdQlW#4 z&?$^^{1?FW!s`I_{Bk}v6FSM#VP06xg&XiV{#!oY)o3XK0uMp?`S(-&mLAo~=gycKMA>?3x39=?l0g7pJl2N7RJot3E=!*{1nhGr<=h#a zLKL5Pj2wh<2=$@u@GtrJA4onig0~awh7(#TY5T!X$vau#b7E2p#LwuzpVxmk;V(0h zmrhjigQ&`jT`$a>MKWRkoR7EhILi6)#Cv#q;$QGMc@rO-iQ2!hj|uDWk1z`y9YWek zN`{j?dNi$I*4=zS>ZDH|;{zR&KKWA$N-Sou-Y{bmFZgnt>e9@2i6n``#m>`6Iq@qi zjm}d#iRvxOAo+Y!XaEir7$?b3vgkSabKNN?-zG*YTUx1vo$BEOXr)g9!Sty~K5imL zD_f9`|5%y4Q-@Y>t?ZqZEkNtPVIWB^wjPtdyyHnIkMMD|X4mwo8(VHf9t?YY7P)r{ z+`n>J+cpRqHschgMfs_KND_&3YV9Xx6y)5^7K0&yw*1uNmOp_W z3Huec@bg5B4Dke(woHH&QT-=*xO7mRzSOixnxb2DvwUS8D5TSL)rw;Hv2-b=v}Tv3 zI`7h@Na`8ACT=K<_)K#Ous3UlmW#aVcDebfH7=f)XNAk zIl`)!&)=3t3#3o|PE!>WJbj~|s^ej*ngl!OpEN1y(>M4_7Q!V>f>+X{q)&a|)qm=J zKa|NZ6qDdVF)1()X|6wggH;TU_bTpgO&{;Y=s8V;fefZ6WgL-FZ{X7N^jG-k(E!eJ zrZ_{%efAvFIeDF-3VQk;1r9GQ8s*dX;0MJbME;q_m!^M6qXEa)58qg!Mw-kGEBKo$ zLp}KUTV(1F-|$4?3Y{WDM*%v1U&;^C)cV6WP?^EtT}TD}er7Y?i6|z`w$q|tWAy*< zZ6=-f&7Q}z#FA?K=@nZNSTUhcq)-2XbwS*oUTfeU?PC%I?CDkd z<;+8Tff9aNR<$7UnTI3?gb+SZFF3One?{r&BMWdMOP~6cE@m-0^(#ZhQ@=uunsj(S z{G92rC#xt4%&jz0zma4op+lHxSV??PuvVO!?x z8;16$ceMJruNBY~ad&8#XWuaEBwAq8r{BfnOw}j>r@>-WK+cN3MP%M>)df$z(?YD- zcPP_#<`5s?zVsQR@SdSHfh{<5-e7a)yeWQmcMB`w9Dn2$Q8ClUdf7TEtX#OWYdL24 zzASy_*Z4zl$A63pWp*Qre4uMM{ei9FRny03Ud1TZ-_M@YGaEW$20e`YwujYkB&pdL zFM<-9Wq`kH34HopJ&7`tAm9VTUISny#-t*Zf<()dXLnl>^_S=LE4bIpbf?#X(+mJJ z&#r}R*8Ps}r*E)T9%D=1)rgY)&b&^PIh@bD&Yqw+JG)!uSF_~_%GK6SYuY}kegfAs zoMY6l^qI}HS&{akG>y3g0wPy9P6y~@gJE%d2A?sY>0da zQwiGjYiXBS&s;-6d*&_8E!2y2&j5wjq+4u?YV+%oXr-U}>5Ucj+bY>ls^2K8+i~Vi zGoFu3P^cft`js@=V7p=HCWyJ2OUuinGy|z}rwc-`{oxWNV=CosE1T^X!Rhs}TOmzBr^pfj3nuR) zmIL~um)H^m-qUFyfKWw>bB+HV{xjWq z>b<3b#WpH$d#!3#`fSD~PLf89arrMfRJ-&ngA#@*PQ3#Rbob!hDk@->Q}2LGuZew4 zU{bw+l~PZ`w!Lo)0np8--}Nmqh2L9rFLAjl33z&nsDY$Wqg-JBm?rps-^wPDOx(OW z^El8tYbL2PZ>H616|8pVO-rSVcFG0!=!whr_b(`4e{RqQyLmG$Dt{8X#!@pmptMdw zrlnlrqaLF8PCtW+GSg;V)E40Xwiu+G@d?Yt>{ltlAioE zi>xI+H1j?6AOxlTK_jF6ky9e{DcKW%nN*n;pL)o=JR;xI=N@4Rs(g}%ZNu;|hE?;| zvs?YuTdLesWH>SIoqGsxPrbkg#r0F?*d8$FxkvZ{?ewX~&C3kI8xt$+ngDN1?@#@K zW#7l+9EIvrzvCNQ{M1j4lJ?Xutnu#LMj6uQHnPvo^%$Goxs9-+OVU?Z5^Gzr<-ziF zZezd@b#5caEmU7IM4emB%9ir6ny6x6tNfsZvh9U`uJ#DR%wHi5DOCgkRxN8WShZ-@ zZ~N;P2$jD`s&kV_IwuRnYzhY5mL=CRVcgVQwQO*%%Iv=1hxq#tGm^gi`br1O8a=iZ zuSh)p9<#t={xrU(pMF#SI`JxtgMrc)fmQlx2ajhSMEQ%Dc-zkXoM~zb$25EH9sE2e z3pmvE^f>cMDWMRPZXiQ*-aaSuE8m_LUdBhUsGnD}GINMk4C&AOmX)mH7=zHHzH$zA zr=KD1pUwb_r`HlHSR;M$Mx@E(#1P3#ws{@SFmF+w$`4p!p^jX9OE=HHNG0cfN|3_* z$n^Rz2<&me_;2v@9dbga+9R z-e56we=Bxiyw^h>kFYB|BK?f8K<&1tKOn&6czZ^S-2nFVyXmLjV=7z!jMzpAc!los zXV`nMtmU^i_#j5Ftj6PrOSYHK&-@O*PrS|&7<}hCh$g3qW3Ll8xDvFcpOrO`dL7Vh zPXF!CWIaep#39gX==`@o6*~LN+~Yy&VLhPT`4Ki3GWW2es?wl_uy1!lsE)V8{~d@u z@xSqSj^v#ACp?~g4UcDJoj~?@O}Fz|BSfBk+oZgzU!GBf{CktT7gfkImx`b&$7XM2 zc7Bs`d=W@^dFFSNt9jX!It;zO?OAri6aPwVWKo>VyUF9r%YB7Od6 zElZZOs$byo#)Ew1_!EP%?fh@~K;C*(%r(xDfySxQz^U!I8Rp3IjYHu%p%Fn37!Z$` zmwWjAA~+EnVJ7VdNP9yN_zA!Ms%6Q?`Insg+^cw;_-}xG;=l0iM|gYw1{MTWpI^aW zYxxTu@>~ah;gNps=Nj_!1584dD_35F^poO!L-&Q}XFz|kkezo7Fd4Pri|2pBl)kH0 ztdQBZ51RG!?}%##+(K5sZARDk{4Y43p8u_7^yh!ZFB~$!3uU~)ZO13UzsHa7@*!`( z*7g60G<+U?mwx_d1S!HowtRnd4v9ZHkEZ_TVmXDk8IQDD9q-_^Siwn{rz~aJrR?C;1c`29|=_`J^hl`#qRJeX5GAw;z(wEYT znZ;LpQWt+l8pPte4Zv~piLJXJ^uzaakEoReLo7T+@~k1iziqqTl&IJ zEa_et=Wno0`r>Mh&dIOw1@_~af4X+br%!$({dYGo&v`SAVvqL)kgDy4H<3I;qvH!d z#p5Jd@dcR-F)mtVRzr({F+1?ZwZu(PhO_41Ob z%7_56Fn+ z82a%c_66{jz44cFTqSVyi|Qew?ZPtlWC5q0M-r(aWe2`yXB6Tjlz&6et zCbF^s1rc5pF-PLO#a7yS(*NmAi|Dgv*EzimyY?$BAH5<&4Jybo_V!C;sF$TOQtc&W zR{4D@{qoO*WAXOVula#p|I!&S(@W%@mwsUDc}dg_5+wcdF@E|vGyR5--{Nsxc+^d` zy^L|t_Hvbv8ItPwUMw(BhX`=-V0h_mQ{~IAnzxrk44}y`zs^FKAzr@7O3vf)^6NBu z*e(-F3klm^ek0iV!uv1(BqsZR=Fm8zwrbeccXh9$1?iVWgaiIk||g$4%fUjR2g zrT$k6@|Dztg&mR>=%_XAE0Yu{DiLejR#|R(KQY*7rxDlEng4NpdghlHE6^GEPs~sM zh-#E~X?ocT*@E=Dp^`AFta3nI8qOis8D9w~vEec+R*+W}lz+Jf7__c{i{A>H4TiCq zU#>{c{IgBb-ma}H#2O|T{%_?L>t*>@?GP(6L$Yw5`sE6dOY&RlLZez&cofiStt(`< zYV|(+I}uya+N#}ZLKv@T4PdPC`~Mkx9}ugrEZ_H3sqv`g$vMAh$8=YP%Dwh`>HE?b z!!tYs4%m=>q``p0OG5@6Fpvft4A@}6fPp$y$2#>_ORZR%RHlt>rcGiJldhyIjSV)~ zV1oe%9B{w}8yv8~0UI2!As61~`(1ng&M7m)Tq%FQz1QA*?X}llYwflF{m!!fZ{>zz z(I_w0D&se3fa4{d(Og~6*v$XGEo%xmjGFH8{z4bSioXXznX&AxV(70A@}uUpBNxpi zJwSWncC#U|ly!dRlI#Q=-JMYD;Le(5 z7B(3L?z=1PAZcPU3uAAo+xLpQ*kVst3?(h3mt_OX$5F|RRN?5YylF7IF)!&OgGcYA z^kcK>2pc1^O4`Mv+_AC1bEYIa_#I8}Zq5>Tt-tKMp{&@c2unB}smN)%IYde;fH zZKtc=cS@G$jpz2~#JLTs^Q_!()K;oKw5WQsmD=nh3-Ee# zCUbV9j9R1l+n$1fzW9f`JdS!k1<^+|jVKL(mouki^@cDOjpkqN?SG|%xW&(P-#|GX zsme#dG~`O=_8PnaG9@5$yTqEG%bxsvQ)kSt)mTSM=JdHNI8E$eF4x=un#TA)r>eY% z-lZZM^~o%(FQ%HBvCOm^SaTm`fAgeL52zC9nW$>r5Xhb_q_y^V6)wDvTJMsGEd0;U z$wkBR$#Lm@WSN|xpD)^niH)Yl$p6YtjcOTL9==UW^U;<@s+LwuT2E;95I`O9Z4Kzr zqsRC4(7~hDclGe~lb_#9&YviJx@a+3<*tz)_0-VT$;|84LHF^gbrg|rDWx6R)U(EI zeL?h|deZcMD-x3jX3dr>Cajw?`<_s#rU+@8!h=fH;Gu48{vb=!>z=Fv8u_g+7oB=V zT#EJmjB{`5+1GoOMqche;efuL8?ZsrdP<#iN}XYk`P3=FsG#5Xdrn=+gJRPoqj@A_wKXn7R12=O(=Wc}IcCvP@ zg~)HsWZ-OFF;DNBr(}HBUb@J=K$=>@VnRaiddSm~mbLC#e|~E zj9%r6i)1oIFI&kSVRE9zu!b}0g$V74IU>ZuIqLp_-^1TkV@_Af#++W>334PmMEJB6 z!X?PWCxI@6?QA9#S zm}5Cm^E0YQ=5u}qfeK?8c~ud231TJenEG~ZFOSo&BnoDBTce#z-Qs&jv?bCX(oI-j6Cekm>0(<<`(lQytWrjVSOi%t$F)`g!1%RH4xqpgwuL@F>RvDYaSo+A$F>>mhy6Qq=a2* z`SjINXcem52lj1%RRP|dklY`V2#tjdAt@{|@4LL_2X$}waj_s-pt_xE!@&Fta=Qtq3n zl+H5nt~5v|l=jkr9!okL*VhnyXBBsg zW4p-U$1n0(j+sqe^=(Q^5(A0HyyWQ&^!Da^1IIU7?%vGO{^q7)>=YwyhzqA|7>y;{ zcUk%t9dzf6HGYbk4^Ii?WGQ`nZQk7!ajFxhIjAtrv#_@BYP>+^bb_EbJX;!}&fTnM zsl?r`v!jP+#I8XX$7=b!fRTzhzu25cxw%uOZSG9s;@larx_wT0w(9Y`9^3WUk+QTm zWuI;AJo_8D;Pbspg(pj0`)=-+_U6P^T4k|kOE;6TC&<`qa=(IcH>k3GnaB7xF^e&x zy~Ig1JJg<(CU#(*ySWol%=zJ2sg)Jkcry1lsW!<&pmqj2s(n`~x3mw{wXSR_xO)gc zwi#;Y`hy=R)BPMeJd~8cSNg=t{i)fZ!SqeaaCn9eT$<*Kf^$2?ja_9cJb_TmBq?@B zXlIk8MeKy?XHrU{$du$@o0xelL32|DPc6xCGxO(W$)B4`%G}8r9B}o@3I8|~QI=5; z{$e5P|MM8}Qe=aUrBN5u_}V^YWD034_2Uc@DWf7cE;y*W64hc5=nCDDidCoL8 zNJV2$@|e|_HY=iMZ6sV=GfU&u44^o3J~2D42g6~X$2tQ8xhSj*m&s3uaUww!Ndd-A zso`T9<;HgKIIaqhjVZKU*j^P9)uQ^2Z`YW8=0-UgIdiiU^bz$Ql^NTUCYEPz6R;L) zbSf_&vuffQs3?D=5;mWE`?k@Zh&KNK8rA67nXjQ{_zhvEN&c3{m~8%g_1v1FH`TdJ zvYLO0T~C#3ythf2yv~E(`}XjA&qa~Ods~wJ)(9qk`|2#T#aJ=)$J}Zkv3YN&n*Ltw z1vwZyKn})sk80Z&_>1@UddCU|zqhyWOiIiiUo_iwi)VZ&EnJ{?Ts2C?#!HzQU(7P4 z>(W=jJ-(>NERW$QRFy*S%|}5Na{sKT#N)kl8QDVa$-YkHi^1sq!tNGzL>{g_Y_ zCZq!sYDZg3Rg)Dx`V`fI)TD@=l&Vci8@TRAjdWG)F1l*pzH7hUS#OPPwxT`VwTa}t zDQ{#6)g^TX6N6&%qLzF;MUXq9mFMQOnoVT>PhLzOA&Csx6ezD`q&Ohagi87v*OYAE z)N?$1rSX^EUH2U-<(is@axq!88CuIzwBnb~la>yIN1A1xdnQT{R&n*0yRvZko}16~ z|8j3KtCRORbRB(COx}+LC3WRZ)lFbp#$@U#JroVH=7npdrn9h zpB+&f&u$a=qCz|Mctugfgp>N>9pe?vB%-vI(%e3JVCsN|M$)+ym-D3~^0!5mM>@{l z6FhW@K_|LU_qY;+$`ko;OY3v3Pw6LY#N-XDVOLb_Yol9H6sGz9-r3ubGvN=`ut6`< zw(@vi;ymi}T<`BJZS(uPI>E4D%DN}|r8vAn=EuW`Kdf-LPb5;0kAojBJuM#@KP=WA z7Ng(yiJRHDotWn-OSEYiW*_h)4Ban9E_-k*26oK3~$JH(QuxR;`$<*)`6 zRe4^f3!Mb?lIAw<<$sq0;(1}?ecOQd7s#%qUv|V27G)8VUT-;-V2DWxgdQx>c>kUyKp5b#c9JCGD%jFyX>Lo{R@;5N1Y8q#cB@56UuJMRO9^%Y8$eI@8$pm zPnRWxfDw>lcEPbZ5q%&_)Zr}ZM2*rh*Vl}sJSZRM<55eWpMU>m?)f1N{_m&zBP5yv ze04!JSF-V-MlhB1L{u{VKKCG5igfVnGWbC(TY}=S4DknfAoRiYU?y&U{KfiC(hdVv zOcweB5AGUL;+!qw=tY#nH{=m;zVb*|t#44j%_tG83ngNxK zQr{fCQ{K^bLHEazO=W8Ku348F&udgiZx`=mmwEm|@`j6}WA;sRS0CBLLQE2dwiZsi zY#1>u-NuZ@R!xvx)sa1{LL>s->9Al=#yr2s(b0riVf%9s?@KWsdbE8NTjK%1ZOW|= zAz;zy=vdj?bk5N`oi22K_lC0RYEnp;vx#qG$U_oDjX};Y_$-g3wiYi)YJ@`FJtHsf z!&6dxdpe=Y&!VRK8bTm)EEn+27#HozK`lqMKv(L+(T-Xx&9N+z(aBm23pCQ&d6DrW6J+17y`y(8w9hBEW~oc&t*ztd$$N-q02?zt(U!`9C)%Ktfcri?C~ zfz2akA~I&V?UFABj`J+764jMxPHc0$bcue7p#?z(j=ut34N>v?5q ze$gr3q&=d|s3bP9^4#@==jbgqyknm&fbQ{g*Qx%a3VGa_5%owQu6OjYjG1MQso*+2 z`jaggdNXxmRtv7QC3%YdVDAD;%|4PX{%8x2BX?A_tU?@ZL0XRPRiPIY$Ygoh@TfXC z8pf)1IrcFb_WYrkt#-@Ytfn;|S;t`2Q7_Sbq*3A6i#(2ARvoJBjAD8mx!hg+=$R$4 zvt;;artBotVXotifW`}gAI*1X2$fUI=Cd8jF ze_W%5Gbd0EiBc_n8Da^j^aH!H$=&)$_DvXDR~*gzibu~dx}>BdH=HEX<;#5^eJ$p& zyJko9<38XYZ_s1297|Tp($(Ub`3J5RmjH5O<$5UvGc*N(ztVnraY>j~(Dq zUY`tR-V#XGU{-#UyZFuBL_ZN-pGfLH@wHPm?IO~OM5&XGU3BFdr{(&g5u_={v5R7} z)<=)|%1T!%n?py>=G3ESQ!|g=Dr_-7@d8}*e{pPid@-c_TwDn#KNnX*))&XBkWj`E z`~N$U^^F9w^(pUz1t^n07x$lLpjcgH|1$*O`2TXf|5ZlNtP`&PDqnY7{&x#-n>DeP z56k3EkX9APHay7H3@APAG7Gn4J6MjMjgAg1{SRWvfbzpS$1;RE2i49Tuj!DoMB)K( z5`m2pwCdr$l6~-F@x>}j+ofFWl7#-)MWNch)KnhZ2<6>hVJHEB zDQ@Zib6Uam^p_i{G^o#9{r1> zW3W5$pwx-=Lw}69*e54cU|x@NdYsqe5|1|r6}qa&wS;cy^O9NrBE+-nw0Pi8GJkSQ z=0t9+7Acq%tDHu>PwwgQHIMU^-0R{~8Y|Ar44kj)k>}X6J69{ZAyqoN+m-5Tc(Xe* zw`@n=e3E>#`<0ncWA=s~w-wzQWovOW_xv^uKzsvzg$&CIWq2+!G{(LZq3561!*|j# z5~FYEK5t`~MdZf$eR@zuTP-wk8kW05Q6hpgBnW7uRV|HZk)fa&;B_79j>#;|$yL#ML zR6PH5xk7NIINs7@OphrZpKerWMxmz^5-UF4tdJP+=~g|SQ}m2NqWSn)gq+bl^?t4B{VrFfA@%nYdHw)yQGo((^rIIci0l^3swKS z4P%%&&U6qxijqq8f)aXmd{N?x$9h~PKi#Lt{v^%EuNKGe2;gIzX~6HVg~)ky_wo5p z&6@e}U6%Q1>Zj*R_vX`uPB0Er3!_hhC5-r^OI(Dx{Xmio&TSCWh|9N1qar)$%8~fk zx*$3K^h%CiNs7!xi@VW|SfW=mKfP8iiQ-gxX?@OpXgNVx=gLp`&ApQSow+?_=y_Ll zoBHOSSEct;rE|Me7D(G2FhV=J+z?LtUQmgzd3;vUV|gySS4cmr>d~i%=eN2gTzc4? z+}oA9gfn$Mo%fk1E_7SO{M|6IhqF#IyUengT|%}c*V3P@<6J*E1eN`Fra7;Uf-8z- zPipQI1~01Ri1bViSw#fkGV!EY@DAjg;n zbPs)w(N8(KG-XS(&F@hm2r|{4ZG0wC{cJvS=Y(#%mO1A42rds?H#>{Z!ezwr&1C3C-#zeT-)j=WYu(2zWD4yuE7bP*$9g>k#9a}W@>J%sDE|^#S)e? zDRtzDIva0 za8gR`mUTiW)Dx!MYa=Iam031+i$&LZ1v7NZc5DJ zBl_{E;#b)P)e*C;THZ9hY}bT%xaGcx@M;NSi@q`PQAFJdmO2msN2a<#s^Kh;ld@R9 zI=7?^TLqU9`<2!SqS1oslL*DhbxKf=|LPK!F{p~u^h)jLF+Zc!n-A;(G2&~wQ}QvG zb4yw8c6r!y*B*Zpw5MjJWRsEQzZ_D zD1QgRA6Hn|rfl0OYN`HQHgTcL9L5(Io)?;(DbvXVa$FW(DWj!rJAg!DJ=O1aK2>E3 zlR}|V1o^0gaL6#7+^2`E)5)o1dQTqUu0m;R`~i=JyqvGJZ1v}#M`+Z1nsoAj0t^f% z4+yim?&K^4&@4nkd=vcSyb|-O<*^nA70W#E&_piWC(q~0L(wVAQ3N4Ku0#ODrnjCH z!zIdZy;z3CskhwJGZ{922H5;qj()i7NVF=PxSz*9yJ#V0M%2?8@}H)c5nfJlA)n`Qwsi$;0-WVBzgr>yAcbnb zNL^GUG5y@%t$u4mDPQ#e=cVb7kec@SMPbNWmdup{a5HI4#BK>ovN|clZ56whgmGRw zLIBY`HPO>kRa3;T zW2*3r#oV$lO3V9&&l-IpmsdFq_aO0y2gcj^y3%iH!rS~8@SyRU`X=Uj4*ctliI}0! zlU4ZjX7FYj;#V7f{hX_6jQvPO*$asb5X*cVP=io>=VG9jV~+rtG6*Q!_m-OR))&vRcsP5(3Iy+tOY$`PfGwUxm{d&oUp_dd(74s&*ufG=C5vExG&*~TB zrYrgysunlo+ByWiT?giA@>`cvKfiT3c>eYcHOxG}rP66s%b#btosqp$T3daxrLnk6 zk5}~At;Zfc_Uf@uV}tmq)8hIJO0hT-b@E|AKGITBqFu8XXq3}m=$U_|+d94NEejeJ zojEI1CzkH+``a!hC)MT7HJ=#u_N)S64@sDJ-LA}NQm<>ELWat!%a}Rrn7AMgzis9^ zv`yrkFW0b=eO{C>FG`UY&;5sXJu+N;(5?sgiU`E^EuPaHH)?z`U^RfkdNXBVy_#t7 z_iobn`OTT@a_bV~x}3r*i7r%o>KgZt9r#-P|ub^RzEJ z^RzExo+jeH975~!lrK&kKKl&_$1N+F!koun+tHLd0Q&xp4zezRq_6OV{3>VF+mG|< zg0U_ql_x~sF(*%?{&}aGC(-Xz^PQG=szTv&KM3K`Tbg|Okb!4(U!Ef2l1`6Zk5&vzqT&1zNe#-Kk~KcTBQN2*;(gqxDV|6h1wG+8n*DB#WQ+m!l~+ zQr}1Hj;Q^za~ke9aT-q0-z?9w=HFD4d3h&&w&XW`xludvYR5Y}7)TRz{8J4L$E>E1 zaZK9#o2NoRLvXp%zci|gzf4);Hz7~OhMU-eG6POGh<_uy^P8>tf}^h{>eK^k8>;XZ z8}*Ro`^6m|@A+V>cfx%_`j`6v{&GkUubiCSL@yINyTIdpl{oi&$J4G8|7bZ4@@PGO zKv5~$M^d1Vp4VePkB@iq_~euxLi34i#0k&UKf9WQ=rdhxhv`D(Y3Xk+bx(gT#q_8A z!v9+OsS6KfUPr3a%%u{gFQ@AEzDd#b(vqra!W=Q|AU>w5k#e1<{& z-~o@Gm4m%7hB1Gzr?-FEV5Q3c+6sXE8`cgke`LAR9_g(=vWyI@-Vaua^(zSREdicY zWvta!4lV=0J|HpP8|%F*ton~vq?39M^gdpBoU!JK9zDMc>|6C7UHOdUcy@uGcByy@ zJW%gd?!QTB^7{wN9tUA>f1P`#k!p-op&Sve)_ZzYtg>1)Dp2q3+tkzR8if0yO+7ul zR_hf6tyFNJ1ijoz{RzSPrryXXolvu?R9{tKb$!_~2OjT*i#-q5mG4!Gq;^7y6h-k) z)9OG|z4~|#^oYHaTOJ!+8Gh4YzJkT9=O90g$9FgI{hVQZ@5AI1Vv)gdwA#aiWIU4c zPPTdOmS9OJ=wTCLb8*24xl^)}(B1i76phJ(`GsYwLAi7@we&bU8%9;{y zJWcg5alQsjM#Ys0uQU;0ap998NKg%!XyPM{gZ{!9hZdC3Ut6UFT)AFkh~!n;qvPkk z9rD+NnoqB-#8q^R70rR}8b3qM8+JX*?}Jx*du#PtwUR1WUI7h{-rBNCr3cbgPfu@k zkn9-b=ctgJ3W<2CpiXxLcUj!t=DO8NI3V*@TUA-!!#7d}YWv#b!aD~TlEtv`{ico99YBNlF4kJHn@eA7VB2PDxMT7*0~H_o5jN- z)qF#>H=C}VA9yEc|EdZ|S;s(F2x&g3BN$|Rkz9QI+oa6jMv^3R3~bSZFRSX+AV>tG z;AjoEHxT$@Xhwp=Xveiz#HroK&%Z*wq+PsL^N{5JHG|6`IKE>g!safTyf=fqH^qZ7 zO9?NfjNNw1*zMZj3gH$poJhC;lv5EWB!>OYaqL39k_6w^bQ;a~g4CZu+X>hR)eiQ< z#?%7M#Rr!OT@5AHXQKL}X@u4f;<6pRBj)4(5Q4VW(*2!hzDWOB%1_7AE@n*Z$QKj}Gi zPVA1CtcGAmt{Zmbx|$@3IdZQ`XtM&AZwswJRJ}Q=7{cu|K6!uUlFb~K1x;1+-}rHP zFmh9=MhEj-(3NF;KN_?7raHXJ;8)>C+a*xF@NOB;0l2xWhYvsz>cgUt91&%zVk?&M zRJq3RdeO?YO@!7j#khePd?8T5pIS(IeB_#{I(ExNj@?qE`I;lm*AzMWlB+#(pK1Za z2&>MKua&29$Y#q%BXRI7hMHcjSwI$tzfT^( z)XEUo809^L@G~A;A(dw_{B&?{;+pi39}_1ZpPgEdY2cGa)e4V*5^WAxAct-yLsFA@ zk(>-a##b1}#gqDg*i}Uk(ABCpEw_gpBAXaz8j_(8SCAZyIq?jW+}s{xT%-9X^Xplj zgv}AZs(cYkTyFkJTl$rrz8W%3eH$vPvm9#JEQW3(=!9y06;Y#qM6g&WBR6=Gw|>ib^lK`%X8rFX=wfv^lyV^rXhQ)BM$1Eyk_WvZJs z>Me%3r%Nk|A_yjLm$MJQW>p*CkA-D4Ml+jrZmLpRLH^W4zVpmW?udGF)FT`(i88;S z(cE6QCN{UJhlhU*!eaPyzL@rD=;BK#g|w4yLpxV6v_f_m>!!V5^qp3qth|}d;rlVL zqbvuv5z zH2k@gcKGvR_$v*d4%B*UIcTzbvAGb?Du(}B4F9DV{`VD|dhm~3{I4r1-n>v3;$rv@ zj-5!cuMGQ(EBJ~Ef=zmf^8z>Z$7mM;vbe;jdXDty+59sDNezE)4X!vob&YQS0}Se| zLr=XoF#gUpyp}q?x!*{FDJ+_*DSxHq4*bEKz_bj1?j3p>FrmcE3oP>)e3;-6%4XQHUe~Z z?^=!NuOj<4el@7*in^okn9IPB{!rmHdh{`+sDZPe zLp_xhwY4<|m9kbTYaPWBlOo^>J(dTOA7MjljW-?=6>qp=mUlHi@CrJHnu=l>mkC1+ z`-2oNYYblymVTx6H--nRdNT@bT|=?}3Of;2tP+e7dH4V$gHs9Ov9@ zu^}QG90~7v_?#j?pJno_F+=k20_z;9N2KnEwXZRZxvRLYs{I1Z*}|QjBA2`e%EwE@ zxFm*zB?@KK+>T)^VOX2tYq8*g6jK*jdvWgq)EN2Engk@+0XP3tf}4NpfJc5%f=7N3 zL)V^Y#*`S{7@q2v@zk5i4ey-L9y~XezItp)({&0wtwUyS&D~0!qF0Bw+c(#tAo9~KEnOCS5-pLga0|WQaKs!SJjjBuFHXX z4pbvht=FE6?Bvdw)4R-?WGpCH)xRq1-ueiw!mLHwEL2kUR{T5l_EwENj;fLLR+

*Eatiu6zCrgNn;f+1Z5Mp{>A=QUb9>V_j3@oeiCnvJn%UHDceMpa$daTeR#AoD3m?hjyae=WfUN7h+H(ak)IIKUHO)7pqpZV03o5QG?U6U7w|$@19==Y?GT^{Kp!sjJG+>N2#Z49V66OCBmiE6Y%HqWHI>ge`Z+3l>HxO5-|o z!GBwt|e52pit{9XeScTOB3J&Ui>TVUxqM5Hj^$R_gui zY0&QOxV+$AHfZYX;w#Wo=j#QGnGBTAFr7(j%v#9SJnZp`h-m&m^Ko2*6e`yDF|txZ z*;r7E1^S4nt`Z~!LL^XH#_wPba05)PY0N7VLwKGHy|JYf!BK^BIJIdb&oJ#(a{JEs*hA8igFgMjV}OZ%qjjB zDX(J5ScB;&*zcJZ_^OCpPguAcl2*EBB`X5MH;VNz$nSb@Ro3oiG4d&+F8&wZFPtA> zX`}J;vj+XJUt~5$eyCFXfEPAOY+VlN3f21=J@`C!jxj`QSw!n2*vOH8qQ+vFvLIo7 zo6q}Dv@v{3#-dKU%kXM!02vx@tgIwcQt7E;VwUwPt6E=&H7J6n!@M*ng6VPGESWSoq>lKcsLh^c7%c~{4nx%x^#>igE)fI&jVs*?!LNk1~QCX34 zAiArR>tP_6r*%1AgC&%T=*SOnHSt`XcjO24NUN^~=L`IYR99Ga9P{awH0bVAiAO6B z$(T|{3<9)re2)y{9x>gliducnt3UQuV+IKZn0y5MUEaBHOhMXQlB;8S)AKGrEXAb8I7 zgd(}7?+)~1lcJ{Ym34l<-d~rMZ9OC7Gg6_!GPZ?2xBi%>mI{22X9hNMUMe9dJ z>xbWA(Z2OGw+McyRK81pfkvd)qf64rmf-8yP5VX}^-=3t^tovL)N>RWQ1w`At9FmR zTf@zPM=xRU6s6nnhQC|1epY42krW&Vtmu0Bidj0~wT8+*l6^qq0j~$(t%HWQ4gwGS zd`L(INT^WYn+af2f-%9ISb1yNm+a~?@zEmR`kq#W%-49j51uJnKSuenf_To$YGGqg zSZp$SYu%DS%Mc>7R1zUTkjDW6!yL4_h(VE0A-hfN#uOtPVIPGfj863TGxRTG8tHLz z=dZhp)(VtdCh?j8JRqJ=Fa&xb#hb20TJ3CYG-fA70k-Mha0JL)yY6@y9z)qDl{M{d-UblMC2t(IWJp=Z?n=&}1;irmw$tq~OY`|yq~ z`2MO2R3Q*>1V9Q_1vI}5Q%D0`^9iVEv_AJ7+hvHm11(cZhMYQoQHZ9c1^Et)_75jBBZou5fn^x~5>CgH3#quk zLWI_|dmpV-Wu0q`Ljp&k`oJ=FmQ~j=&dWLC%d(wk2Cg+QJ#h)~2~iXo z*n$4F>Ve-XRJ6X7|IYoc)^AY^o*9B_{#M2Ku}qjb@T5!Hbe%sJr!E!~n_ic-pa+@*D+sJthaeQpy^4_|YH6{)S0lBYCOF_X0Ktp(IBJ=_6r;3w>H(hA z*TL6Yybg~3;`M%Hjh}Yl2X0jKbudu!b($&%h_(3Fsj~Gs6#yj=izodn@s-echW4Ib zjwST>GSCea4TPCcxS?z-K^4V|*O}Ew5w9bKH!Ee*!Vogns|7KR1cX`9_ZJzs)o)J} zBX81yjn+ZQ+>Pc^m^sBj#=pY39dc7-os*ACd5Prh;;4;Gi90LTp z&pmq2Ba+3)eK83>L`T9TdPE5=)xewXp_d#tT*8EB@802;JBYRzjFD;mibpZ>TO(u4 zfcdJ73Q&ls=lg>XNzPo?$nF2)eJ4{zLrmz`>3t*Kt##?$m)_O%#&DYcO|U=)qxr-D zWD#jTL6hK<>}IC{tQ!U+t{1N(9BKqBW%Cb<*473s%Mq)yEC!FbK!-l88blqGEr7DU zGNb8@vVHD&SlPH7kx; z_H)GRXvN5)Iu{q5g?I=;LmItsKSHRMdTjVwcxS(JFW=X5c&qzDs{?6?YIwoY=2_qH zLI-@Y3w+UavFs9-te=qVmQl6nXoft2Mi%RX_$t3m5oPHM01KByyrCXPe)!oZiYc!W zI1D6RkhjU~LZ*??fhLXIE<<|2+)p{(~qfd{OdaTl84V=n-wb<1ui{^-z zI#51$@m;fL>k+x`$fbx}>fk-wh4*X+yrB!cp#x@528M>joQtkNA5vEk zGWy|JUU;ouxs4Z3&-AfbtqrTJV*1&_$iClQZ5_g;N0vhHQ9sS1@3Rv3q!fW3(Apdl zuBNn}vcRd*F~EDf&pL#b&d8&u@_c7-75#e+ z^H3MKEXpjR<}|JO{Re4}=Jmj(X|=V;_{a^@3%>f`L)~P_S;WYW+<$m*h2WlJQ|tOi zXtV0hR4MK8!PSDX(U?dTu-*${Ld={7DP-Z7USNzG-oPKyrxIu#H2jW4D5_F%)uGHZEOrDe^|hK}a31eWT2Lm^K^tn1MC&w;v^WqT zr;j~m72hgs>-(9kcqBM=Ch`~xWh`&1vfd)G%?O0C&Q9wAQVL5#&G^ zR3+Ki`H|S^`lrTCj!_**Y zhx)bz!cGKPS?-6diBbXF+s3*ijh&ELjM_5tFl0nWsp?HB2%av2+)#_~w+2_jIC44$ zehUb4p`M&Vw76rnQH$XO59XLz<6qA^P* zTm6>z46xTWI;|&HGHSH`fT`B|p9=hF>YCU12>>QA8Ihc_9HGz%BF z7dUL|nX_rChPSKt>uWr^g~v}_blh*jfM8uIS})m?izs%3OkkP%L^&(=%Mo5E$@E1kSoY%Z@vv#+QLt(VXYJm?4%tpjRu>m?DR z7XZYjEQR|t3~%UZw8n%uy^2$p zMB^!z$4#7EcA>#!P;mrtac~*}#sDA>jP1PQBtzvpb6g> z9l6h_Mcit<;Fxc{M3OM^0?tuR+Y1syzC@w43kHQ*oUMgIy@DN+cuytYkdRzD*3hZB zbqZuxVxD6U%YZn^$(2aW^*UL|8E)7TkQU?fL`n0)EB&!N5~uDBVKp9|i)LI|Q|E9z zRV%&D*>;Cgy#HYmy8$bm*;@=jZQf#l&zUqev(X9%s&xkTAc~kC%sRb`eBjvn>EsB$kb@v7C(47 zawj5pEI1c(a}{upxDUfYwiMElFM@uB!&We{H zoA36uZl^B#glcF*?tb#eLVmYj`YJ*A4KoOtAeJR5dI#+dNSVI^hL^>~J z=cNq1l(v$s0oBO6@dj@Oofu6OG5c7}?eK~_v<9=`ozs7~BwaC3^Ypf0>xwqY9bW;> zfQ@}cfR=ZGy2DAA*;6Y^z=H1B$Pbppe$<6%ZQy<=UMu6pYHf*%^?8NC*ackO74AzJ zI9Ssu^-x!MWmkArS9ncSmMG|pn0yhc)3IS*YhW}lEx76pA9H0dENRgTk+1QFy@23a z;|+`+g=7)(agqHP0i1V10%3BuJX>wuiUMfIt+0qshW>#>XRorQW!B{(%-~f42I+Ft zj=6BQv!$aoJcSwesKM5U@wRSy6NFKw9K>$2kjav!wtm@%Y+Yy33V;?d+2d>-;g`t! z&5@fdCZg#af(Cp&d2-kLVMbS!X3%hS{pLd+MD)^{C`5P}lqjloQ?_U1_s-Hh$cqgF zy_UyoMZEYG69HGoi!<(ut%}#`c&&+7U%b}x!q#!5AqCJzLCp8&K^r`+LqPDXBPSwq zqET`+k7&L>@+uc2JfIL@?BcLWZ-Un{oHZaUIxf{g<`&7jI#^|XNjDklQ!%9aAoIbx zsy*0;kxv!Tk-h75*3ScJDkC84@VpfA*iNCcrh2C);e(yg^@;1y0vKa~HzAYZ11oH& zvgZ=h`-2=x-MPDL{qFOc^2it2tp#HeOy-}=or8Z;7VTN7md3$<<M zd%cV4WAo48l%K1>Zo-r;4z}RaGQE7_I071B9dZut#wj}FlY+ceB}bwCI@t1SWFTVN zRc?%YuVh0@dUUYGSw}J9U`wMM(St2Thvme8DPE&Jf{m^+Y;=_)D4xiYT$WgCY?SL=91im*-vDUG!!zATmImZ^njr1x;pHQqgcnLp<(}*?~+Dy@j zV{Cwes1tdrw4qh0+<5b3R2hd zf3*lXc+28X6^O+pnLiqG$#R3!d=PC3_c_w~ogag!B}8yW9Sc|uDj{wx*G$C0Ys)dS za)%I)r~Mi`YvzodZHzusynCW}_mufKcD8tTzIgXcWPMgRMxV9q;Etf=syzrPs zyH{*7u+IhUl>zc%8K`>nSww;-f1bSZ!wuRHPhpt$HC)hzTkTuhV&R}u5hq-95l@$t z^hu8KGi+Gn@$p@D9}4hn$ycw&ZmX@MYP^^`;&thvKf)D$h}s_a=(c$6k~GCAH_CO_ z=%CT(G~$fDsHy*tb=Q!+Mr5ur`il4(YsJOrZW-fZbZ;?w87c|wh7gXOtVtv*M@CLj zBj;Pb5ojsnyj-+!!I=6c8<|IGG+YAdog-P(@gw{QB7ayo;zaG z=?$V;sz64V$cTEJb(ti{y39x~eJz zb?icr%H;5vd=wzFR_tTCv;%{VsT(m1msVHQBXK&}p)yDre zxHh`nTHq_xWGzl_mv~c-EOof%a{lp4zV(o`uRWN1rqLd>VSEMk*29 zXjkh_=mHpx=-(1(9JHLtK!%(tBywQ_#d*1q_;#kl>N8kC_X=dh)qLeWcE<8SW9BYW zSU(aPr&NDJXWlR7wu>i^4aSWTen7g=Z#j#+#!QRrog7o(2aoxNNYUO*(!(-DID>Th z>0QJ%b4&4=opcc2liulCPVaKoXUV!LvVNO!82+8!m45}zHG!+W*#+nJ>b#S&@}p%` zZH+SHG`K?YLwD?a8vZ>!SGCLI-PZ{};d zca+9yRAMO-IktFU6YPORH^`F6*BB&;ly`&Mo4+OB7b=cl(og@Ux{hDU;JF>Z__A5b zqv2p~M=0|13?W5(n{EGf!lBX6VZoxNA=QL}?r@affkzW8|M~vu*kzMFcG*1hkW#c? zaT{JF;8E=GE`IrKmhB^YFohd4GrA=^^rlOU_ae(8x0HHS|? z4Vw2@Wd$NRZM+aUGgagiG#+cQ! z0K;K?$N7bzqz)6Pr{D`kfn5CN=R}f?ZfotoIDSvhW81yprSAj8?-@E1%FVCuDf`n( zaF^tSnW+-bMK6pW-k~1L$?Xdp7%8giAnB(yA?ay9XA$PM`;U&0dZ5Fh--k~yjYZS> zxb${e?Fgkzm{NBb+~2NA+8h#am!C6{i0GQ1g!u-@5M0fNj$e!%>qYzX1cIo>~<{V8st)A`FjUr=Z&0rg?#5Rkr*$KCN@Fy|E#PK@$I`1 z=1(eRo4zEXZb8S}2l`=V`#`ic=PVP!4S&^j#p!#+={x@iE^st=7hdN_J%IkBN<KA=C6d!Bt5K{?8(*W7NF8x(3LZE%pug;0@~8nAsh#nQu$CqYX>& zQpg6Q3%fYQkJ(5{5JG;k5Orwpt<%wrLCPOs1XH6CFpH=|^aBNI)ajSTtM-BLDw-^w zgAtkJWAg512W_8QAus%7kr)|HM*Diz1IG2n8=4wqvvOA-GLX#7Z5Kxw0r+7}YMr}B zzzOY{X9mH8!9me~npQthf@9k;J<(B{7&=Q0lR1)u@YrB7Q&N~I=r0Z{iIjo-&7hou zr?b6GU7rSD`pc>tWxfHOyALLxDDZsb@QjAW-GyvYlSR=8KRMx5w*SGF{*uy30>!d6;33L+RPn( zLA|};DPOdW&$Q2RPzJUuwEtU}Eg_rhmy8PL++bD(WblYTcfy@Me}REinL_$Hg;vwc zbiZ7r^@CLe#+DGQeYM8iIqogUbm4e-D9OfjhHYm=%PoN()sVg!H1An+aiWr?t({yf zq^*rF6(ZO^Rf=Hy6!OXFJbo6Cnyp|mq5ja&+2m8MMauYnI&YNli{Yq)rB-Kutb&UX zmB-ai;dC-?#^cI6;Vs}BF>1|dd5IR_{X-GMQJd8XGeR=t{br3Pa*v0QMvo_SV9~w- z8SYDh2!=Pt7&7PjG(FTnPz-iFg1f}YGLufbC+I8bC|!n_mK@WNhXcv;ZY`sLwv&*K#<$)4XYLU)p&De z&YN;$rykeBREgCz=%;ZJ)x$%@$qN$sx%*}!pibzpS$^`ucPjiw_0{MZSI(!S@CHs^ zfc6cNU!~FK#u#ZZhgm5kk{F(YE7@ROn%j^&Y>v}!8m#EMIhNb#MS1?4Jb~bc#FZNb zv`WSlxP6rfBJGs9b4ocLNRMxDWUFg9o+l>o-e@Hdn^bh<1!bG-`yYdA%B~E)yLMTX7prrcfM0ltq<|5b!&4 zB1@BkPa^_kFUY-N35uItX;Rg)ka9CpRC(c;Il;K^fT9Dq3ECw9YQG8KTL~9MBx1RE z2TZ*s_-0mtXh5=n^clT3cK+LvvG$E6QevXVE^i%PSYHqLCp6(H#W#ARw6NJI3=JHNnGSqG&$(WGPJL*?*jgQVdxd)1v3 zoO`TQC*@noIuyrt6^zumfX5eF%<9AZM)G5f%z;>qIC|E4FyGqHZChAVD9dZ1AXqj8 z)pUhzOq-Q^)Jqey=4-tjb>i`)5I{;{E3VhRTR}Qh2tCxe9QD&DwoJ+)i&=}tYAr_}N_|XXtlnp0 zscy$((ex8KIE$51NMwpdLCR>A6JKX;*Bacr9L<(>Zdcf&UGW_q)-gm0Q(0B=tiGAr zElB^(DjZ7%sLxVs?>2G$P+@Fl|Bm~rbg zQ2`4jAFr%@03v$D5u~BL$Ma69F?`K)ng3o{4G%gfV!B?_{6^DrxJ}NAR2qWi%0>#O zF9hMr$xIL0r@SpDg$ zN4t`Oy5koI_%WpJZll3-yCix`nkwg=1qr)cyZC`5T|S1UmvyMY{qJJlwG)FbnRgB$ zSbSlY;M{ICpYM{*v_$LiD|{|1Dw*3IbzQPc$G3QAcG;7k+e^kid3hT`0Oz?*qW)kl z&7rw7gq#5{0Semlp^<7f?cM;n)PUB(-Uvu%G$|OnEXf3OE|^%54JBpNR=K+K>m;;% zo6khjiToB%8%mgTNTzoQw?kVp-4YmOfO@Td2@#%Fd*0kbL+8!iXKBe6B9x^GRtv}B zry{tn^SNKren@-1h6%tInm2K*O5_H+?O_hYEkdsAA{o3OF1zB<7Pl;|m$a|L2P(M^ zp?YB_0Kh}R#TZIt{)op}73TI2RP<#wuOmr%B?W%4T*4fT9dj-CzO%z?N=RAzPV|iy zq-6#~nFM)16-%7wwug_>{AW8P&3}?3F!rKtZ@d^}*%pd{b@cUOY+Wbz1~DNt*o2Sz zOak#yeawZ0@HYr~;fnE|RgXb=Fk{zT*VwiE6;=!bU0%#`C=JUf_XK3c>?~4}E2gSJ zR=E-*l4;mdV>Y3F%(f!KQ3bfS7+lLhtwrP1Ybn^)7mE0a4Xy2)!SdV07>h@afSE0v z@RSD9ih|uHqxP2sy-xOgO`bE2NdM}URoziG0dgOj8cUGrX4y<*faX0GJ$y!lpo?SM zvwz7K6pJ(zR>h~{_+TL~QPc13Bz~Kzh@^pq?Y*2a)8~No)MpLsk&Nvlu3LD;|GSaHJO9jnEY@> zvLyLRNV3ksvA`9Pi?M6KbRrhW7AWQ$N-SbwIq9xNEr#XLLe5qKFR6f&62ZM-h_joplS281e=ML@awNbf1Vps|XP z;KhwI<-QGZcsN{sjOx`09sS|M^AN`OrNWMC<5gpj+&_iTW)i*YrA+!BhXsjEO9bZe z%C~()7pd=pCi!CfoRc)7Wqh4q?`@JsTBK_=K22=rE1$Ym6~;lk^=%I7*9Snl!QDk3 zOj9HHIvbni;#}jsEp%fYu|xOkwdFA2?~|nu&c@gg-yo~f+G6xC#pu5&KW81FBUC16 zb#oAVGl5Oiajcg1{US~)t1-tMe-244cv9Q=W(Z=ld^6g^3BLGzsotbV%5y8Bczhe5 zH&ym+X=y^^I~P`HHrlLpkchK*7}`4vHfUXf-66`&BwWE(;5yjQHrk^4jsvJ;&H8@FwCtAGs#e#ApUAC zx!vBDGmYz1NHHGsaJ{r;%K_mPdg(T*zxjwjbC^IUgnH{Rh7m*NS;Oo2(4!Pt0#HW+ zXFD*3(krOJ@8T!g2cz(!RIXZ#FS3BofM@6My}ba`0;rOLs9t2i-?h+UR7+grz-*ib z8q!%ZzEw`EGS`NmaM#Q_&PMgvb+Az13Aglw>K=YVoR(&fpCAMgVxrQAj+iZp^K?Pe zg-KL*pDJ0*OiW${oT8POwK((lG6)y-(Mw!2MjZ(P|Bw;;-XtlUmQ=SfoNGISfR zkxMMFImk~Rk!&w=e8IW&!IBh@a_CHS-vVZoZA`6ETNNQ0Jc{ztOwq5=$EZB!jdF}N ze~f{gPBR$uZzvDRyMLxY&TtCxA}1#HCw%qW%ZQSf#fT(I7fr_KdT+0jW?@=X5$?2g z*NNlDr`PEuOeEHeUoPgKyLW^ctHxR(m-W?@HH2g_B)*q}f>Hq()LUJ{yg!1R<8U`I z)Tu7!&STf&&{UMZXc4(q2LGHZY=#ywi{av5B#agCVt-&HRWk6#V$ zUzP7m`+tV4|1lX$8p8*Eo9{ut=+wn__otLqeNB3X^>$zy>?|H0Pmo-4W@lRZd^U|U zf+x>exWysO>owXC8v0|SJwU}p`w0#v>l1+VG`VmZn5eHOh0tTB%r6Y}D)K4WA0Bpq zj_WNZTKv)EDlchvF|k=aN^}Q*k!4RYu_vmX3gbFG z`;Y+~v@N+(cchxzwU(0A-tHI!Olq@A)w(0O)Sh6@o}9LnS9?~Mh$zB_L8EQHsMWF zCy|4vokabTMZ^kqUMGQ1PfzTKIZ1RUIwD?x7UE;RsBtu~$7qU=B%`Bo2#1I7c~r>V z-7<|S6X;i~?A0APgR-g>Etd~y>(PL&FAnJdQJKQ)}n3rU^ATIoJipf&NFC z6?CyJW(E`)==zZpqGA8a)OGcqU88*4mJDANcDSFL7oRW#J z!v;8*The~IXn#LlZRp3eQOT?Ngad6*zI&Y__e$XX4)A^kPS#7{WIY2X?v_A;q2x5l zKIh~{u+wVf=3aIcCcZ9VUpr~Co|7i~ipjcX&2JWy8xcdEmMicPjNBV@?n7IFDfi@) zUB#Ysu_rslo{VBoCemtTSD~q$#l(FDKr`{R0@LU4PmIso7VIuj?5$@;kqpeB`TAOb&WnX|gVZMLx9E7_kP2wrX04 zdI&DlBcY@mQr_osUXCcHbO1Um*7SLs!pSYgRGgh~sF>#a1N0_?*5r0Ahn3+x?{tb$ zKIdwt#x!yYGcv!dnB1mdG3ia*(1+&*@!BvXPb$Ve1Iol?Y;t?|)Qn1nEltfxgGeZ$ zdo}%Z<0!8|*oZu5jgZy>1hqUoPat@{osB20f6%)2`UXESCXZDsAo|s@?q#L8S+4KH z`X}Ei@k?m=zByY$jMXs17tBQoe3*(ua8m~?4+((I|D$Hq{UnRde>_h>i(&l)u?n2g zVSrSm5E^GML0%PxOaFLY!yj3l44SI)8?P{`jZ+5*bYGJneh1oB5!~PAY1`yBX0D#f zDTM37^o~%P$~iR#t8F#LN*og|RCqlPdj1G925$|fjDEev^bVHhXup=~6icJ*)K-X7 zoWZTrL0ee|R`-rP!35XDVxn)PfJZ@)?za#P395xEt(`R5f2VP>{r8$0Lxcyw+Uk(! zwtl{EwU;a=w}k9&D<j+CL4ElDL84C%|%6hvxu#FL3Ng>_jk<;y2&aAqcIEbi<6~ zX=MuY;$Wx&3kr18AHk2 znF4ainNd!5S(p2bnkvl>Gov;pKx`!xF3sd+aH>Ss869XX*`-6J(HlW{Ni(Kr9GRX$+^7ug8-#~96mkV>@eTW$llEyQ@6!-7k)J!7zDgf4 ztLEpgRJu-JmFC^7{7&9rt_*?tqC#V8KQ`Fd(`u2JqGCxp)J7)k#gA!AV~{z2kZED8 z^$u#8*>w0daWl=_ljKgP30JZLQ*@fT_&$FQO)o;Ro4OcHU9?#_bikY&H0KmH=WfF} zMYQ6^hm))6&Gcb~;!4n<_$&Z=m^Y&z3tI|Eq-hdDnOK3zQupy42a{IiWhLfk!ZNK} zaq*bnAk=<{i$(igeL#`VOc7|V;!}wB-2WXfax8wMVe-c6D*J*g1!jXr9L&@{R>&xZ z?1{u;dbiEAR+9N+u~epYx0i=7jr5*PjO1kUj^$+XPFIB|+)9i!gC?z1z#(^C3Qh0m zwOk+CEPz*YQm zFs({3q)ne+!_eXh9%1^OC!>5UJXc3TT9L~-wd;J z!&rP0IP2zp3G9^Sv-IjduI9r0g89VMEc4f8cj^GqXm95Ey__IE1-V~7@4BXL@nRWlNqww72qWMBc_#U)kl`vg_m9()!kfCnjCj!L zJYBVk{)Z9o!qp@iR2i<-`l6eJJYG$vIktqCuEZcrGf%;;ey?lUn*aW0KbJm zk4X%hJ=`pzrg_PZiezJKkJR`CB`Z3m0z&+-_oLfRS+!Jk%8p}txhv}vJnY@$9`x#^mmw#>oe~femcJU z_v3-$$G8@{aE{RY*yC`Ut)4(fnopXpI7YOpxYl$vMZTd94=bf&$;4Y#R=ph}Daqz{tnlJ@e|13>C+7 zIQ8_>^eA&GAtCqDWCT|d6jhyxTw2kRZdpJw%o3mM`S0Ofn0fPpm<8XA%p7n3E9Vu= z`}V);R|&)#GAe*AZapK;$yB0o{6>qqsr{+AhJO_~{x%1Gq^c-^#s8GN8Tc}X)MEIjvUix{@R^2RE!~-Et*atPYn2#u zc2V!WbrYTP9Xg-$MJGv6&8U88bJYSUX zsRU6%z~vFe0P;jIWSseY8C&$GaDEEl7=j7MW$(%-R5QXeYqEF)0|{;C1Zla;GPyBX@-L~qE39<~nI6DX zJ3Cplz>vTbcRS#TyPUsA0%PAM-Yi#m0A!5E9`?)daMzb0<^#iDx!{WO;L(}37r>sJ8?n0!+4$v)eD zs*N40iLW)mBY|z9shy#QW8Wty$CIB<=Y~A%nf49tse2*U_pF$4&S%<(ex|R6h{tQH zA6an|CO5JGM^M|*g*{9RFm4_iM8gq&wrJ6v?hIU#fx77Ar72*d^E;?i_7FnYfw;?9 zD(lkHLz}2f^fC#Q=%v@8P2cev!HppEsbDzrtB$x9XDZZnB*X&UAgy>=0Eq5dKJluC zzNsStOs7U4udI`FKWKh^%Hq&M%mFg04dRZph>Dh^aaa7CKp=f0gaTOc$s4{gb7)a0 z1Hcp83^658W1;l21R%v}^p7rk-d1b+`C|Ht*vHDzSLywS$iRXfJ;txDazN3&WmPIB zZdy*JW48wFoVbNTgnRNlsYHU|x4#A2I2LddIK(k;oVck`LvQ}b$Hx0jD9ZpXumGT- znkJ4@OveV0n6vw8e7i2Ojs7da(9=sQ#|}u0#rpj2>w<;Cw&QTA`#75cOfU^ZtB3xG zE-?Q-X?0=DnYadx0$80`cXdwspncMfOfM~i*yKg+zFrpt9;*jhdReCrs|5tC3HT^i zp%ZY5Pui7mlZBwLUo*&a7mbgxh!JKV1rP;5AwD-)+OP})iLA^RC0956y%=Lj01_m9 zH+04#fm8yM*8CflE>TK!6ghO!0(NNY<0^3IvSUuTM3u3&rDAKPpE`OGT^6Owc-~8* zpjO%v5nT+SO*CIT>1K$5i6ndgehL=!fT zf+O#XzcC6%I93=lTM@iqOX8FVVlBu|*QF(T+Zfg@2Ge>mE!NVa3lLP_sZSOpo|;U< zm#M8eZY@IUZ&{VyVYyGHcQDL4VqGs+20NNBGjPg0P-q^R^*RN5q<1lW_2Gm-X9;eJ zZCz^Py0ZT(J{r)=Z&p&N*R|5CrsFP4LCTyD&Ua@u+>f{-(w+905i_@u{O>LW@fl5)Mh z9p5}H3U_?-1i)0uAe3T3F+l&lnusdJ**wxzNhM>iD6nkaOsYWq`&pTuBEGUE?#5q_dbK zm-qL|lR#^new_ix-btAp5LmCu5gN`A_+ohMD{A0ns}a>6i&k7qhqO~(ujf!khn1Cy z%m^&+JVFsSJjWAm(!KUg^M$us<455@F!CBk%{WJ1`~Mkx8xSk6Ebq6f{HRlh@fB~0Rsl=R2}QX($v?f5B1c2?ZGhS z9W&R=nEO`0*kFSVHrQZ;0RssnV1o^5upxm2(r|zO|Ju)UPH8gpCgpSX$J%SJz4qE` zul>y;5ycRt~h0vb_WQRF3&X-caZUZbIR*xHI6LvO9&*Pb<2vAIub z45OjH!hu)JFpq%A*=En!iR|nS7w8?`Qx~1#dU2--&ce_DXT=|eL_Am8sIbg7S!dXl z$8Z)9lv4D{#MF(QVBffo{R!r=m~OyyU|ztFM}{pkb|NgP&nzZZu#`pppG~JcSY@tk zL=3HUn3%Upj}?KT_u4tq!3_+b(hcTi#s>&ri?7CS18-7)FjO{%Q|l0$`wESEw4-$e z)f!X5+;^#-K@AAijW|00H-S@QsK{YefNS80WGMyXDv^mcF2Ctcsyz0khm;xXBaDYM zu+mmW&dzW5S1ay{OR3K8bLFD+c7PG3qh)#%%eGYI%;_ug!d{rs04r)P)>Dg`b1S2Y z&!9|qNGAa7hEbU@1JDlq=|MEGpYGLWNC(lr9AuA{$WHGwgra$`PEGedy+=Z|zNN=u zWF+)UqAWV^JkV0fqj3c2`GyEj5ZYfOym<6@B!vLk7>0xP5!X56f<|U>LNlgnvIeazi*zr&Nmv!L%|h)Y9QY5mj_F35XFIG zNDH!l%oCpdld|vHlqI1#eD5vjaypLC=~0a%gTafL_7 znUs%0iW_8|S4`v`QYOmJRkA;7YCpXK!M@kd(b;(I@6q+STSH~s2XyaUavzpkGF5($ z1*o_sg4IgR0pUT0+$QZzA$?tI)Npi9pM%3k3$ssYRT8DucCXx>vO7+&dWay2##?^x z5`57)4Pol)BI65O)MawR2)jNAd{D**URSbE#gW6Rwur}E!y}SQbC;XpSmwUSK>(} zOuQ18yty@upLAn+#h!^*_|X1e3$yI|I9Y}16C za_IJk{6Zj`M^t98_y5ur3IH2O7xCr!MPJar<}ye77Fr<{tDdcwB7iG)aGLNG58G!eG7`EZ_dETVSG zni0cP-28A};U~iwS^=UfMENu7ul@aY=@Bb{Rb-#3n%KsfDp73;90;fBg>t7VLr5kX z$M#6yd~9!G+nNoHV|(GtkL__a2o;@)@sCpI*vx%Y^Fy%!PtXmEs@jHzjGD+8b}|=b0##5gDNucY?fz zNbGDlsmBJK8T^Ch0645&ae;uw!es%Rwo>ofObj78H7UaTS_Z~1?C=OeC$6urUNHk&xEWV)$1ImtN?B?6Ai zte$wItI!m0Xz~Lkkb^b$n-@V0Y_f?H7$vIe9L{)7$Sfx-I+HUUZejHQUO?5g`COwd z9xg;JFR11T3Lff*g#D4eAD4{@5UD>#H2Nuyxkz!yOBYYY_*!Ka#S8l=zL4QEW_uz` zB$;s&mO64(KKv+uBQs^gta_jm=x-)PTsC>_@3lNIrQ6ZVlTUM%6IUWSp8H7y4458f zGy7+fX@sctn45J*UaYn_43=?CZ(INK>UBCjyq35`1hnRxHt)cb3Ol*a-tNeVcGIsGK?gh~Vo|B8qTD25_F{fK*|*{y-vdTMI?GJDc1K zMWi6y>GtY7ksrcjEBAyJqx9omO(Rxo^lb9Ayai2K9;x&$q#zbIDkPk2+7CX_6h4t} z0}xDiCben%jkYz(&Ak1iKT4VupOt-9+awd87Rxw&Mi6Y34373d|lquk) zfWnPajZMu*OpNa&W|(_j3Ku&el>fuRIoZ8gt8|l_6V=e0XnYvIlhNVVua|_hw#oAe z;s|OXwtCbyb)gT4V(!4f20=iOj>(oEB}$jDl3bvaT)D-hU}ZL03Z5}}>cW|%eZt!}W z8HXH@VK`%fKj&vQ@xaedHTm0kQX(j!8@Xv7qAj5tCDuRslMSUuA`j@c=OTW?BZ*T? z0|1r$D1yge*5^pXi^pl$n4@TlUP=X5g*TXK1drwM)9*s^7!=y6p!QZY&Yz}h$PGKW zg}V7JKK<0)#Yk0-Y{p8Ec7s-IbkHnhX{LjTiJXxR0iWB)&w!b?y6mua>Z zm&luZDEi+>^i)!L2^bzG7ZT=`@?1BGADRzo__us_erh7$%`$gvsF)&4HNgkKlULgl z%?cY~bLc;&3u;}t!Lh`+_^B@<32xP0ZNAQQdvtZFHgX^g3n(Y%2+j^!%q0>hgF}C(1T)1*4~tjHq=Bo7J80<8_2Eq{+8_T`NqICisBJZPU0Xyi>YKI|(BkLa;28 zFjlv6HW#nn{bP@S9_sEtB%7eP1 z7<^+(sxN}$1~=|b-MWlpD=mOS-sBOaB$4t_jMf~BB%GZy~*U9~joBB;I zA;=(eIhGhVRI0pxGzKO(SUI8{C;pfzmRT&zMwwGG2WdXdL#8YDvMYCi<;Z%%O;?EU zh_3ocDMlh?&RPbp+zH7`9+=bC_KfV;=Oy^gASrTPM-G3dzMwTVNA^kQKj((~C?rby zX}TXHYEH5g0Z=I)R!EcUu_OQ?B-+WPOx(G9ynzx(!|W{{lkkRJ#&FyB&<*Y?kM7`w z;$3mY<&Gy5!TofTCkbngnq>I^S?*e+iIB;!ef=pfN0v&~ymU6TP1ItEA5;{hBMlOn zJ7KFn_s~f>tCN%Ui*mS*!JAco!(+w2?zI|AcR7xZkb=acBPD+M(Z*&w+UUYU+Vklf z8a(V`(#nb>oGvZvBFvUD5|@%VD2HAr3C9*32I9`)(aWneLei`rLt-Y*Bxd1hia606 zaiZz%Jaoh4!?D)NaRt&Y+Q^7#@Id*qN)88 zQyNrorrW~Z923%YJOJ_sE6BSECgRXYk0p+nV&KI(`c%SXnUe0+qWfdG8V!&#dmck2 znCekkr#x9VwaIQz?@f6^XXE&U{tJY)o)_Ocyg}3oC5rRPfWfKAzbv9@aEQQp2PIa4BW+F9y>rVUg zG^JqLoi6$VRW$RINovA1`0F%dITZv<_4oiuSG-rHp5F;*jf|J2LE$rvK{bpdvb#lv z2vtJ|B9E!rZ)4bZhU!vC%kzGfp(W%bbxv4WYf?xyw-+DDaKtuBcVGBc3jz;MSNPV1$88%!)oU3ec9}(T z-B%fK6&p#h6ROwpXePW{=xG84;MHrzPCl7n+oV3HF5wUXxSZ1^9IlP2OVO+(Bum-} zb@^0Fn3Ubw)GgqxoFC_0Oy0f9bxMYt4V3DP8te(VXOJX|nA&tN>kH~## z*2Iv53k^)MZ_HVHd3N%Xv~O%G`lO{XG>gx)Z*20j2E|1(@}zQG^-wbqIiO^qra1b}{8 z%L}_Bz1JqDunvWVLFN|$^aTMRQ-EecX#%kn8DA6!A!;V5?CNi#b*XEUvZ?;2`F`oH zHA%IB2{sfyjG<;rM=kduAzd2~3^54Ycv+pY*KEO`+cWkHpuopPuexM9z!lRxw2Wac zb$BCJw^W0(t6vzo`aVDgh7q3pDzH^AClMMIA43=$ARi0R1+b`ZhqBT%{a`&~sZP zJ`h6|Ug_egxveV0^j0A0<*X%n4^z#XmpS76E{=isLjz9lO)*KuO!G|YUh5FK_7pBr z3kJgW6|Sx5V=~gpBbCpKD51C}w&Wp>-?PNGdx_6?JvODyBf)0FL_H2UHp)1?&kUH} z?K19e0>Yg$VtTih!p)T6%(O%v&g^ljJsK5G?}@)A411c~Tf-KL{JAVg785L<-qUby zE!KCZ_eA8@b&!D6(scrvJGMHzz2ZL1mYia-d&wyKw_6Y$Y1zn0mNsRl_htWfGYIxl zL0)EVh81@0W|2T$Le!UrTqdMY7FeQ)hyh*d{rXPb(syP9OW?-DOT#k$!^3SmYG*2H zAEtHW74zRiuk%htV2g>QT*x(Y-l&-nW%Njt0~rVLrr46iQ72PM`jaI|^N2=5QCr)e z*5bC{v`~+(riq|>7PiosvgtSE(quA0D6NqPF7JrGmEXG)ocbg>wfJF?uth6|(Q3!P zwF!%n5L-RiMfxUA>NyH^Io6L4m$^f$ zi?w6Dn2v{iA=(h)vJCyj5JP{7uqdjdld9$E8TyN%zv_%^Fs7s*Mkh%cvYFT5WUc^Z zHzHK(U}_pbIu$bGw=^OyR~KQeQLyP^E7J6hTxzB-<}RxzfG%6RO<(rtZu*j$d>LJk zP1jYXAJAdiCF!9Dw_2%Z)AO!@>3aNx&pj3>=gopCF+1mW;hg5^ZrhACDa)Vs09I#xcFA`UHZs4)S!rGX6 zf+Av~32I8{9K(fZ^v6x3EK(%~3PIGlT#3PU=8#h?si{T? zq{sHzV#U`Y(<@Ba+`_ z8)nltF+Q|KKGhXoqG%m|kx9ZRK4IXhZ(#D&u3t5F!FhY~k#vP+35 z7q6oT<9)h5I7|!7YTKBv2x07^-ranWG&9Uvsq(QEbLHbgS!ewpYAXu%*vB|!#aAkz zl?aW0>u;#^aSv;!t4lg(wu9-x*O2}xp3IUqPduhCSJw;sX`)?ha{`K}3=}LTXpodw zhGfe^l22_!1yAo+dOAZ|rlyNyQbsOzNv%kM+-KnHdJKUFL$*Wnlc+)s#W0N4UB={bav%A9i&$jh!pW6mC=Rs`mfU z_8zozU`W5nkIKDlLrXjVIy%)}hkgdN2e@v$gLki5mUZw`QqNF3OqB5s{ft=pX|6tO zZEx>e!3;_pNf^pPRqeI#>PZ26vzB&V#cHP>^&EkN8u=VWbk(x3_AOV-O3DpZb#|6V zen}5Qq(X=cCGfLsXk%yLe6O=a33*%#7?F4a5)pht1m~cB+;oh=vf3-1d35cBCS3$r zdqtBcD_c7{G=|ZWgJ_$|p8`i`%koZ8@$^>*S%q^|%Zfa#lXqcKZp%AGjQH2L0ycG& zE1z2~s2H+!e`KfHU+onj1S%4x?nGiZh?Bc^RQU@|FORhwymladj+RqPa}tj#u@Qoe zAc0ptiD~sD#r0`6a>xvXx{*WO{8P3ghxl8i1|(*|!Q5o1y#gb39;{vZThE^HWKJuk z!%UYQuqtq5`YMP?Ax)Xr9G>S2tu_l!Jp}upz~}WkG+IBDHngm8?H02@lIb> zTSV7G^OE2?2IS+xgw5QI;3uR)j2ny`(vi6NzKvRq{q=}|o;B5@Rq+d#Yb4b1*V;w8 z>+q_6hTit~Eq!M^8NrV{Bd=TKrk~op_})Zu!)r>{MC{00oX#70z3@>$H5#WJdCP@* zz2T+wGp{Wvwg;v#Pw`Mc9vze+I#n4;sBW4(o#{AOE zPLGy`J~9%pfseemQi3+}p6TITQyp^6X7(nqy?_-Z*vq^?FtLc=4{9ga zpAzgRffXEFpHXVZ9!RkV@_<)Tz$?x84=>@+VW&Kp0C_I=fX!RsJ&cEA)^eq2g;;MK z(zmwg(xAi+8S1qJ^%_zUE#nXx(*2LL^AQ@M65xmgBZp{n7!Fn$M{)&coW}`UX|Z^% z;LC02d?YX*0W-qDJHI=SgCqpq<=HLW2nQ*ZO;M2Bco={z=inQ9y9Fg!EwJt`Tx{RY z`Mx`(=b(!R+>&#pNw6>StTBtNS&Sqn?NU z=*C9c->KyfeZ&K4zmGUPMV#yScTzJmX!r_4K2 zT~~qe1DDYXsTlMZ%4~+;AZO+tY9Hn(bmVvZX}ScZTE40lL_Q-&Fb2`z06^s12$QWl z*%>qH4%YDV!$)}b&*a@th>kmx0J+o{=$onM+09y(bH4DdQv*L8vy%D<*WS! zl-RF?#AY)mmPj$hDbJjY;>^eN(acGg@ru6}f4rfS2@RMwLp3op&Sox>v55bjEAL*a zynCuvHMny$794!Dkz)}8Z{_H(5JDdcjH(m;Y|X6W(fYaEd%GqI1*2c zEy-r&t|1`C&)2H{J>(yHTh<5sb=pU9~61!HERVqvYF!))_gc5?Km^fpomrkUvfXQd`kdy^%8RZ8p<`nk%%do zhNta^SgSdT^3{4}N8RF4Z&;gbEKw`KUjwwW#P%dLUF{X~oaXJ{Mq zlKGJj3JVg$VrGMwhT_*seGJkuJ&NEpm~bA(&u+`EUZJxSqfFbZOG%xO1A`9i#1qh> zRY`srC{bNcyYghnFrani=^;n;n0egH7Mg9!V6QSYA$krr&6>{kBK0w9hN_s+ntj^+ z11%w$Dryrt7Gc6wnB20FF34u84a;Vwy1`kkEx{ctfZM$Is`dp%!LYDc2X}cC-W0AO zNdf1iuW5mXbmmBd8rg2U*PvF^LB@YiZCKspOYBQO?9zhDrD3LTk2>dI8)ZvV2eX^3 zQcH*|7MvuPpAM;8DJ`Z#{L3bv2*FpPwS^|N11Fuf!<7cj7dTi0Fb-~VF^*-W5_JfK z>X08bTA|?s1#NxH82|I-0RcmyR^(Wrcp7z)jNHqEFLZIR5s{Z{(mf2mK$h9;{;ra& z2#}UKB_LO&YD-eOvj;quK)>I^pl)L_0fcxo*)n?ohuAMGp-tNC>OC2~N)PD?4X?PU z?_Lt^BA7GLej-auMmk%j-2gm{XZm z&aHN@M|^S(bl{TBLaJhe&)n+9Ijiz$M=P@euoRU#cY=KQy6w*Oh*c>(-{(peCt`*I z2f8bp>v28J^@zJ9ab7*-r9lO|008C^Vm8Q4Z_DRRg#k@IcS5R$!-I1t+EHf0tUgS? zA%L2YW_Be!5LU|O#FT7q14>Ol*}&12D8$VytrnR}XM$IDkP zc2T_EIDje+1lz0E$cTjQzUH>5y+aj+gZ=zQDNj&J<}V1LLI_N5wpn?4D4V;iD8Z0= zzR5iiZcHY%C8Ugff23l$3XM>`ZR=2-yV%5Z13|7;BV+MYwB*|#xJ)c85AWzAf@%N< zyTJgX_##>UAeqfw1MhP(wzZE_%hlUFB-R$Lu5?_M6t2KM*~r!4z#b5m=XOgX+W3VK z{m3Pmf%ZAp64jvWECLTV0uL8~M;n1hi@<}8z=K6#+B_kgdxDM=v z*mYR*=K-=8OfbWVFlZn$yPvs!4)VPLt`7a7kFObBIdqI0X;9p9iNFjiAyP_7d$`tK?FP z-s?)*>w4BcF+oYlU`F+};;Ps2chnX{=(V@Z-&Rjyw5l zdGfh^g^QcK8`;$aHg&lvXv%B;Q}>&L?i<-b1#uA!PpQhKu9E&Ur}yAGwB`j9ytKmOw9GXVKPqP5(Tw4ab#hKoh?j-5acn88g$gqde_H6wbfJY9ZYja!s={5_vmxYt-`MgBUm0x?l81YH;M_y^C39bA zbKW}Im7Tk;ZdEwk2|bi6FTmWxu>T}o57kVvxi7;S{90hJf-@^!=}8-@DxS2d-p|?+f@&n(&vbjUHdsq6!Q58U@SL@2~LC z5%ZYD6ke`@H;)1S01>Wpo=k`F#o752lunM(=lOF;KMw0Dg68{E|2z7;$2)@BW$Oy+ z?eUE81FOFZp7t zLotf94r9NLCDm5ALxf=ddWh}yAHr}-W#l1S^^t7;#ztOMu@}2$p1DWtn)$1FfJJQy zMA$W2Ek`;@Vu(#!T4Q^Ya6Af;UvKsuXjHHNIhqDUj;7$7np`@Bv@xCO&ijG{P}6=9_xT%Zw^&Mm{r6kl>&pNB8O78LAiP7|5U_kJ zzeIZUc_gNo$mAN);g|C)&HUGxV{{o2KQ0GFhqBBk1uF4JPuDjKtiCzdH={4x8F-y(7neg#DKB9$nLM!34eV4D%}8VMaP-+|uKn5kJx!U4 zhx7MrrFtrkfajr(^Me~9iPMA|eGTyZJp!c>RBnDy`J|o4j34dK&x%>V*_~l-{4gzG9wv1N9v)b>jM?DQqVM z`B9hLTkv$(lSN5Jj?-@oabm;v4ePY+&n85&D`iil!#ph3<#YA8wXUwB1XQN#?XeO#S#z0tQd_Xy0rKxn(^Tr5Az4TV za##q+BT)*^iJ;A>mL+F;i17Rg4bhb&v%BYIHrq9Dq&rzRTE*1F?~xzn8WExEDm7}f z5Ju27HIg-&){%Owyopxe{$i;nEIV4gUFLFHH1D8khJW=2m>v~Szk))_-B+93SAN6} z9b|(NoIe51j7DcE&acCC`RaQf2cW3lM{8(=hO*kJb@X#5i4o;(o? zPX++b`B2oj2jSdFx{DX_tBOw+F;vddm`yzNI?L&w&&^-L0uNo^v+N~W8y^-7V~dM&8+dY3e@7AHkwe5! zXThx~mUQuPoABomp51imLyk`N6z!b0LR}Z4mQ$bA5ig>05o9q8gpD9GICv2-3?*~s zKuqxBdbTB|QKXr$D5k6Hc~EvacppJ@%<8%cQ3}Wrv3@TLE}a>wGwsG~dwowkJ(_+a zw@txeNtmbMsZvG8G<@-d;AO)d^VsXA2L zxVL_~8Y@bKW9zkM=da3!sE^oCv?uSbvh$1!t|yr+RL=SR1Qvs;LiZDlja>O$w@A?t ztA$>QVLRMMMCWV0g&rE^l|t(5!xIhF@uSpltjmf>kfID|KEQcHEA=8PfNC=b)j8J+ zIrxy1drflCPldHbc^ z%xkIIk+Amsb&LU%jVBdA2=){PB0S2qLKDXGdo??M?h50qNWD9&&*w9!YCo50JmNFO zaGU9`6iSn!BauAR;YOG1Z9^@MGG-45kvkbM1n37|D0fp<2myxdclJB*8LUm+abHsE zMTQH!%Tll~H)QhIMvSQ;7u}jdxmX2Bn~>Fv7cJa&^@TBFHu!S0PXpn0xp0zR+tYed-EM&+9*hdiZ6e{|BnVAMbB_HV5`hgam%BC* z{vq0K8C8n5$TK1=l2AW#Q6P1J>ZCx?Uy&t(#nLCAvF9|AFr|!XLKJ3OpFj-N-LgD+ z=n&cd#FXT+ghQzw>Bcy{LVn`GhsKiUrL`^)h(sioGL$qIK$}|E&Hzu3gcn8+ zO(VUMA`?jR@+s3O7Rd0m@!CcRXw1q}Q;y}8ROTu0ad{y^CON=^D+OyKNp}8Xc44oE zqa1ey?0mI3L<6+*`C+%OTC)qcXpZW3aG$@g-s-&Pn9rYa2ez;yP+rc?-_6e7%+8yC%ATBPp>;;pIiC(ZfIrI7pJ46u!WcYag49`qx(01NK$n#gN zCD5vsRugbrd62<{H^9m(%gYLn9$xjvqzikq3(Bs^dr)2hn>rKYBAjP_R6XUz;>Zt) zE3MYhxX$0o&fjsi7n#p6mtAWY>fp;&9Q`aUSNi1qZOA8vQ4$$xUJ(ll{`){_&d2;3 zNY@1DiKY39M*)AHSp$p|WqpgXw-JG>GhE#`AzXnPHD?30-h6g3bs&&%=?P{)RMPVo zVH5muc@sB607Rqbvw{kzLS zl;Dc020nRC=fa34XfEtcL!=iao1T))5z{mgfg&Gq{lZ>X(gik8V|t`jZq1jL&$GpW z)ywlyoHEE!+uS0stl8S2+yaMa?Mhb?e4 zN;4x6rd%();k+Ms4L;bRWGyVkX&>*JQji(e%!R#O0m9ok%PC6ov4+RuvRM%lh=7Mt z$*T>r!d)ZHTxztz3v4bc+GAaZCy}{}+4&nfLn(%xzwUVr8zia3!GrU)d==!v6&Nqr z3s)4yPvc5erAs2LIGSobelKXIAJ{YI!d2reU$`nh$jU@JU$`FABK8&22-*L)ewg}_kJy7yhLJnk%{WTVzPw z%SKK>DZNB}k5prQCj+hI@U?Clch#9}H^h^4#L17^Y3Qo@9&Sfl5@T?cP#qCN4*go- zI@zqn@jdGyUVM=jh9*&8$Mkbasp`@GHPj%tac$*Kk~DHp3XC5TzKB5B1fgiz`NhVd zfJ`s27=C`g$+ZG)81ZhK(xWsds>I?szpSODq-km?va6sYqJb_ABNneTtT5PI3@;Y1 zC>vA;)Mo&7v5ttWGRNfXS%Y_d*N>`j%7TDa zB$fnWg_ze%Lg5#Wz&!#gNuEED9{tQwrkuM)>AL7r6l8OzzKP*cK78{`L~i?K`CMp& z6U7jL`fakw-E1)fpY*-Bnu$?QBeklp=DN5MA9m*<4ktY)yL{O$TvQXlPgSfz$}_ z$Vrw5f|A7Numn5wa=Cs*GwwbC*6h^G5ObC~7*@Z^aF??OH`?V=ZWUphxU(vn|s8EmEw8Ek0wv`jg(oOOe)vZReGzgiww6ER-%AP-EAFtOev;U0(mJPrK7Cs-$Dub2?4d3rF$hg%&h53hVfJP*QMB| zD<^qB-l2>b6`Fns{U>fY??Ekr85 z=}y!}%xM1xfW99J`E(60l)8M2y0F|gVYr&mZs!Gy29s07g7)noCrXgJ8ux>OG7-nR zx!m9=0$kyF1}U}q)2l(HdE;0g8CQ1ja>2c4d4c6}EV_lQG*oq2;c}c0Ym*-lqj|TL@{}|m7p@4m?Niwv9>gDn8r22DEm*ePY>;AY z<>a0Y&mC7z`JfXu&Wb-g!V=*MEA^`365qlSrOw0UEOr!G?4Y-b$K9FnfXX}pIi~~- zj39vj1Z(MEQq;#yo$cOGis7S+R2FtDdA3Eja%m?y;7mqk3!9>1HX&T7YPJDql~bfc z7w(XFFe+Cu!~&QZu6?+CLxL_1^}h|?>9DYYrb<9y)Se1O^C!b5Cs0DHJpryAZ4+Q2 zsGC6b(4tMk7nJ3mY+-YABwGzYn<61)v&=2hn3R-f@F#NL91^fUqV{L$KDdSb1rTAI zS2bqD^l~4#H`MJmRNZvV_w{;k{3*T^C;?!(Xu3v56m_1`_HD6qxLD;1N?ou{Aq9SwCjo`Q$#>BD$;JJliuSN6H ziAy&8z$m#nQZvZthU|y@nbncZ=u>wbSGDl^$oIeYm>C zE|gV_egD^HL6ra3=G>&JsNA6Y)IXJzC_*MSF|U8^^_=P_4zGR^V^2=#b59CNvx*5% zbyc+sS*0cc`y)dJyVToS#{7&&Tlv3VraXYLWF>KJTf0mHpTWN&s187W@u>{eXZb@5 zLW14D?#)Ag6Lf5}|9K)QVlSfG(%RcsLIGs`zw64@p>wc;vx_Z2L((~Z*g18|lx2fk zT34;)SU_)I?^;ofxTxvmBpCMM)BdS!q(VU7*GmGL89yMZ>@r{9YKKxzB8Vgc`=W5M zs4av|w=wqi=2G!iwhnM6ja^0MixWNixv+9%Gkd@p&%P9mSpj5$aHay!3 zj_U;@)n{3CMK`*G(}Pvqt-akAkHKxw!ZX_XY0G}Kx4dB$Js|@I{wohX*VWhiqYY~$ zPfNliTwU63mqG!1V#DetFaiWa57-)5e&E^na4QBv1Rh|{d$?|{3~et2a%j6k%0q`$ zsv(Zl!6J?hPuP9O`dguzFpJRq$ykhrp_6}4H(>i==%gim$xjN==yT|vTgHo|!Nr!7 z)!EQPRIuoiOVo-0!mA-L)2e#xYWTY7L{EnU z6vO)i`h$R33p@AuAKUDPFl=MEQij34CYq*^xoP@UienMQyMF#Ise@wLhUZGPyN zTl+Ofoy{H8(hwbNk$}2P8#~jE@qhk|H}!z<64A5T$Jp%Vhz@Zn*gwvOeZq^X+<*R( zd$<|+$N;52;1Rksd91bC0D%yGzTl@7Um6ks-E=9qQ}c8KTz(u;r2kSG|MQppm0Ltp zg7Xg$dZ$3#8K1}tn+ToQLa=qn@WVa|pKFnsE!pfQ!HjT(cuOA2Yox<;a8cWB8dfD6k*9Dc8K;%jUjTY914H*Q|#}EvOUy zlpxQQ+H~zwXUyE9?<9f44;s$TO#dwoWtaoHAKvtm;{%8Ir-V;kFVu8l)$`6cCkiAL z4nRIg6K~II-fj4X!^Pli_-2t8QnvxFGb4upB3-#t6xeJ{?4G*ZNOHK5` z=@doRfm0@iS zS5<=^Dp;ZHV=9a^+0YSoE2CiSr(_MkN`j3wad2aBvpMdOy!6VZD!Gw6qs+W85ItN~ zE18QXFc){OHZme}Ui&hIs+8t@R1>;Xz5y&m!_{0Z4_Dc$rg}Rl&A?NG=6d+xFV!4w z(}L=s{RBZYEeCTHa|cnSCBFjB1+$jM>8+MV&PP}|aAh9z9+oJ$L6eWV_`-F*FB?{q zri%2F&6eC6%9jusDV&ivFfdY9wDr8sOH4-qPSyWe$ja}kkL4RpY3Q%`O4+-|l>-w9 z?;f{*l)fX0tKhIst+Yq)0ON_TNk#_j=~VefYe0ZqfTe8o8b1seYy*s5!y!T>_d?>h zp(hE#6QN2xdX4d9GWBrL?&t?k?<-uc)6-SIm5%Wzlm7HRx4d`uf;dP#t;!CGJssn) zCJa74aQdP$JAJFjqB4_bG3<@h!_Nv}tfi=f;b*h4)r6I@N}r?l*upMlXBg(R@P!EI zp=qhphwd0+0E&>klZn7bw{FD+K|mu%S?y*_kkkx#$-# zZW4eYapS?Oc?kxN>4Jek@=zfB8F;)$WQ_N+3`w=9gES+UprHOL@1y_Tt6NUWEvoV6 zkggm=1_4c@00_TtW*9$8Os8Y){DIPh-RDHzj%o^6*NwfVJlW?#egMfqV}yg_2h@M1 zzU;93viGhqL#!xKpkL+3d5Q9S6DcrU3SI(Pe(y3tbO=RaWG6Dzl+t^bh_%w*uE2x% zU=Ys2izt@gyB-FIy-QAZ zi1@_L^rIF%v9kc!sWJG(wzVy46carByt7CF>g~>H>&;B`KJ&c@* z*HQZ!L)!o%q8>&}`29FtVSY~oM5uiWt*=#{(P#8~(6@k;aw zSMKFHjn$zm_e^9X3{$+$K}snZ`rRw{$cb)7dlHJk`pHOhrwB=x_0c_X198;7-|1#5 zwd5xPC+5)-(Hin!>JuSuiXecRXbMnsor=TJ^n+9PxnYup5#gsIdF)2AIr)i2+v?a` zHYd2I%D(u56bESYU?m?o63Uy6yGf-N(O_c^E35pzv8yD!xln1dZ_dA>yc1lM96~(3 z$7D@&w@7K!QL^m@kh?xvW1j!7?R@H&UsPEm7Q`$Q~HO86BCA+mqB9O?zO zqfT~*rs!ERpsBdmEh zJDs3g3BE5?B>cYEeMazv5d2qI0?~g3MG<%h0CfMmI4uu)Cn31k^BDK?5aPM03Cwwb z`L4GUB4zJmDf19NzB?_Wayk#9kr!?ZJ1UQWr4Up%65`%y_&NPBUwM-sL?NX4CWBZv z_vuaF+1!iZBnCN@X31!helSUQ=^J(bhelobSf#S=Sk7HxS`}%{eu+XTabGxpn#E@& z6WC5&LoK3euQ^)xL%)JN6Yz>URBi*)m{wK?*OLi3R}03ERIqOa1WLC7CRGww9V!l& zK}NjpKss%_V@MAQ|09wc;9jF`fS*0+v%7O`q$##-6hU{habX(mZss`pB^0@?k?S>L z-KEB*ns-Gxgz9E9T)mz>9`&x*k(+sQ9TB%#6r&N67QS8f&on#M)QZ=(nq#!)iQI7! zfKoP#0)QL3FpE8c5P6+8%UD+diw;m%mIsK`*&$gF4TX1~a0?-cU{y|}nC z2<^(G{|Xjso3C}^`ZHf2r8gg$EoF^jR$PY)W<&94ZpOWcUBnRN#o}3Z`{qhpS0Cxh zhEU<$tQ6Hr2p1&DgwSOZ>dKG zkS46>yrAG~EtC#@J9iNWLdKtnNRfF=-=nR9Y@>^ADEA`xU0o}C7tPxqyL>4a=dw;sBq4h+-Qml%*9coq@~!=K_yF_PEgOTooiInIbDpcU>oi zH`i$AG;3$Y$zcgkcDO9`N&aox4oS6w9>tu~WN?BmbhZ;$YZ0y>n^Gsn2sUL2Q1X^XN-r6~izxh+$ZY zn4u9B9OY=!{MDvrY0Z$t_xCj30>)1N`FX8Jg;=nubtxPh(+jx&>DloqJ>RKm3m<3TE9uZ7Nk2ZE4IC<2SGn#j z-?scc=Yj!EE6rI7|KaAT{HNpn*q z4Rh29c`8Y=k$auhc8w>C$ieq=uU3sRy#C}V;G3aKVMxJZZ|GwwSRwA|B7|*L3ZWfJ z1z=Lh$trZ3>~WxnPzp;xmOoH3GFd2j9Ij1+29?AK3*|`ov?EQK>4QfR$8&V;etL?R@&fScDLL+$F z>~ptBP=r#l!W}c>kHkkBW^X+v26}1o$m33{15%x|i`;&@t2dTwl8&n+GV|D1&Ed||r~dulM~@%>2iqu-IL zy#-M$pZ;`tOH29f_7)ERbLHo1PS3a%>CjwUHy1fI@@xCM?qxc9cCKxG>v}LZVl@4# zeU0mNwgKIv5^zA}&ma{E0$ks+vb(i~D<4<3V2bqh^2P@Fvb|ZkBP)H{oy4;nJ$!j~ zV|hz!R}Y&jv;N^othHA{l8t^BC7vDaf$!ohK{uzD!(>O ztzEu$#oCo?+t(u0%kVmw`}hJ+xjI?k$nrq8L=|Y+IIsMaRheT3$&*KKSPuG>k@nq$ zTiX%y(3LQ`{~NZptSExkE@u;|Ffdc9^N;CJNP~@R&5Rmnzt0M&FG`|uOhr4QSkics zAJ*x**dW@Qtz*j-8C9M^6}54=fxTqh9?-X}eEQGYC78>LDi_u(@AWNDSuAk5@#n(i zIBta^xevt?`BNx&zwwP!Hd&Ud^dPu4%EkEzws^^L`Y(MDQa;VoY|C1;Uo12Y>o(pj z2|36^N%A1c>;Hgzx-4uWChPy4%NPq`Qc#lHM+sctia4aRYFz*9mVxAmGrxq&a^;Z3 z-^_OBR_Plyzb_r9t*ot{D!&WA2&ZGZk3_V`ZH%# zNirLElGqhB)w4nUrq{E^x_Yc1?l3-ZnNx;CIHlgI#d3Hg&*L~`*g#I?PBil_;YgY!A51n#@HAXbm$CSoTse38ma^*MhOQU_3 z>wj#q_|6pFjzXtO=jh)iW7oO>V;Kd7WF;kF(LuT%sn>uJKa1-2<=R^ODOc2lQFxL* z+6E1Qx<+C&a9Oqtfuas7pIV^WT9@gHbB?!5hxGPJ7?kSNpH?YVd3Xf|PsI@5n7;$9 zl9GWAgqxjsx(cneT=|B?Y`bINTw-m2oCq!tAS{T|Kp&ATbE1S=CA%!@S9lZ%9M9mR zv#vbjMz~Q2HhK{;w6s(hn-)=9PnDFzHGQ&T|)Q=4rK$sq!K+b`?6Qr zarPp)#2x8Nj@Q+y$(1P>(yS}Lt)GFHeQVOdn_yLoZ|A2k8>p}$k4OY!;H>T|!#%0{ zOzi-V$f~pkaHZ9@nHH1>e&t@6hcE{!k5?1|(vr2TD0%xsrF{CU^6772k-DnNu0qsz z;V~a7%NSXfmdQQv`)FAMA6X1F3&pXdfj6Z`2L2)h1AoW{erxaV?EO7yT%A$&TgVJ6CANWJEe2S34XP{9C z;xB-tPZ_gPkyK${7Xru(yvvM3@}WE-%6V5Ry3-yJ2_=aRfH4&(Zpj8;FQaNwUy5kF zB%KB!ER+nwht^V`IRy>`LHe*f)=K6jZT?@)gA-llu@yMGO9ZY+Cj!=RsXpE{kHV}G z)x>q`SCODB5B^-Pt|n9j*-LQw)aTvE-{Xi9h56pfv|u!9LS zzZt;Lwf_So;{(0b$D4fw(poyZ(2MMk{P{9a@e_X$)JZ#9qsRC3Qh;cb>KS}L>IcT1 zQv4c3sBMiCzW`j6w^Ie7Xg(Mr1eMPf(urv5ZVB>6u<3hHzFIbNcVvmzZes}V6N@1g z5V*OFZMaew(D9<^ph22UJy)=r4Eb3VKb_8g87-SV+3sh1LpFz3;8iGFYJ0QcU!fNO zTD~$H{6na$?(*AG6oV3VvVmYF1mC;l>*ow~D+gP`dDwtcuj%NE7bgkoL>Bp3^8;Qj zlhVm+YpLJWB?Q8OswAUbxRs-^rW{{1_g@U2>KXw4^df_+%v(4ZLbO7Am0iFbnX5jB zt5=dx-6ft@xo;6SyT_kntGJxVRXARR1r~azAs@x59bp<|^l#Odl&G>%Djyg3K4jHj zC@B8cbqkxHSjeg`Dkrs%>URE|{VxMfjI8od60k%;geuccPvKUN$+*I1qx!SzF+U=j zd{*OG_4RhpBGc=n)T?hUUs)dO5Oeq`L9KpW7h@g7CRgY*w(P&D#MO#K%PDY|lB*GP ziqnvpx8YL?ZLY$u)Y;mm(yHFG%1`+NQD63!Y$wZT^%OFSFzKw{s%Wt!d{>2e4s1Sn zJ~-3f0u}FZpM`$m`)cvvJ*(mBbN0Lhqs7A?G%;R&v+`vzuKFAs9vTc!wPMD;ulTnN zYG?J7I%r7vKwUr(;#lQ^Ulpb73Kmp0Mi;Y@J3VdOp%~z;bbw1kn5WbnrKM=-_V%7>{u8p`5pTVDFRrB(QG^3s}Xv>?g%sTE$(K1IJ*{adMvvN5O~{GGHDT9QK} zX~*63(VbF_(q;^t={eSAr*tF1a#xPB^#vn&;YCGgjN=E$-&IW(WpL$1OJC}YJ zXhd-G)SJ*z;sR0-_Cx9-hOg2j}6g5&p@4_tfnUo&^?N-EXXkvS*0d<2bQ@7Bb zf_c|V%6oalqL|%jtWL2V5sady>yd@jP5r41%FWtSy;b;sE<;9LQAw&8fL$<{`$fa^ zB6)KFVQDwrY3BbHi0qUXMD**hKlmH&kBEmaA3Nxe2G*5wGA7sIBhAP0^GB}fR{4|m zXS9&GLC38qy6&bZe2_yFc$49D%3D;kQ-6^;l%4u({Cvek`?72>26xM|Q{TvBrVg@G ze~Zh_2EMVEpZH~4fC(BnLZP#PBN`Oi%+H3NR3QeA%1x9PrLG6F=6iQ>{v}7pMm_|{f zN+l={{fV=TVAX_Qoc4BW9(ohM`=-iA<;ou^bH=l5W6$*Jv@#9VTYZN{@$mee1eh{! zPj{p+erW*)lK=yxkq?KEekG+e^sdI_1!-bB!Mr1=FHNEaHhZ8n^m*WY8Fb~L&$|#6 z&=D%^W8B@`R$7MMkd<5E&!Z12gXQS3mJnIxH*iSxIP}$WUR>4CE*ts=Ol{@2J@P3J z{Y`@~YOKUS&_=gdfvS`}uJJ+)c8Aw|?@%<&0cv(Y%BQS6`X@nZtqOr%F~;G(4Z-BK zsVi(f#P$HP|5PFIuc{l={NE8f*oAQfsaG@M?+I5i8|*bwULRv-_e89a^UT%Qb{a#M zUeN;nw@l4v!yDzUyot9w+!wX?ygBk@49dT|WK>b3G3-)q{I8M?|McnZWy>Wnd4F3} zWr%kgrxU7~fK_gaY8rjL%Qv*FZCl>b(kC-sX# z@QE>T&P>Hu&>b&|1yx%`m5mC-G8jWO$UI8m=?7yeSgLBKR22@tYX*X9*7TfoHaEcJ zxv)jI!)zoqHiY`e{u$9SknfQQ(Ngxqzb%iwR37^Ug~DwGR~~!Wnz=mo%iw08bmyuq z!aw{mrO@EkP8JV(JCyKK&L{n@Xnff9(&TD~RYo96_d4|vle-~m-y<(;U3N@*&1Xw1 zAni7H`oBSwF3Tqr^-Fyd?>Rf5nUv|1O3Q}-rmfuksg|;l)~KvQ;_fiIvf?tUZqVLh z$w-Hew~myFDv!O&a`G}Zbc?~|u_GaGpYpUtbVx5p6MUJTh+dOAd@~z)RJ zkBS2Aiu$u0G1cxtk*s#%AQNm(&4EhfoG=XJ;rALLHo;Rv6`Qh==e$<#;>%yDXiTbp ztXEULxlqD{5ZW$_ZyJb^fX=T2S-177OBk3t7G8FpHnhj`SQX7=byIES^QQ)^3tQXYF< z)8imTx?W$Ug#&V^FqUbWj;=f-WjFL!1nf<#wU?t$(<~lFg>GwN$ibC^Oj72HU)o4@ zM7NP?j$N$9NYGJh-fPs%2><1F&x(>pSDh9l_cH5SIx#leI#xm#&ple?Usdgn9I;V9 za?E(zG2Ya9l$aY|I$7?P##6aiQJmIRn^ITL&s)A5f!<}{O~>|>TA+{TcIjYSe?UKr zw?`ORmVw+J`rgi-2V$C&j&Xh0s`zwFn zl?}jP#h$nQec#^?MPGUBEtm3LclRT|^){y7E*Ss}i&}fgpJ|sK+s0g!^ro6N_6L9B z6uqZW{lQ;Z(_Gg2M^zkyEkAMy6CG#9(hz`?V) z#`KZDAN%XxVdQh;<_%5pbVxKqx;b1PtKiEyM zI5i$kJ|!190k?~;K@*b_clL!laBMV2FvDj9*Q>JCM&HUtkCc!mo}(Vz<;KjwfyPY3 z%ymm*5N~RtR548omU+Jv{FB=Fn4*ECGeeJfyN80qrQU??YB2yQ5B-UCaiC4ey1(!yDC4qqBG43l5eQxFq{M5@aYDMINfkx3PF^q!a zgk!nu>F!0BDxsVOu~fVZ4AX<&+IPX_fF|R%vC#j@salamv`F zf_-glVSsV@DyE>eacUSg!-eV%k?U*gUEk8HRpP?Bu&rPn-2b_FG|Y&swX?gk>{aly z!XA=FA4*4?%OFh06Y{8Jqkk^$gpAisMqi?MvKLGpZavSK)&NZF=wGSHmmAKnLTP;C zRb1)gY;`Z~qh>bg6iJMJ$1%)T{yCJI7%yUOn!;0SFVX| z?IkGK&eqw@h~Q@2Y*pFaAqQ=I^qPQ1PqfQ_sI^PJ{?CN#xSW+I9DRvzx&Lds9X@(l z?z5`}_=ItxPg(s1Z2C*%dXD>rA`g>Md0Pq4-TKZ5Zp1l{JdnHpioV>5L+n6@zLR(L zoxZPcGMb-rqm?`&WG{hN+xzh;l7gY7W0`^mT_^TN6yAS)?*&m9HKD=jEHZA4UlVZwqul zMJx~8=q$lp*?j{i9bh~Ii*}2V<$)f5&4vG7qoS7C|AfEY{+3iMDPh%bdEg9R%B%0- z9e*eNHQNTy;w}%4>N{}NU)SKka=S0`%?5VsJ9tYW6&1cbu)*Ke{;Cw^f$OeXG~B?B zP8C{Z7`zkCuW(*BuLm}@ir#GCRt{c0Du4BWLtitho69#dk!t5BPW2oOD0j53+{`kK zgc&uiM3u6mP!GK!U0>mHP<2OZuL;lz?7>aq``}@7|CtW4dGLTB zwki=Q4=y;LR(%Im71`j|`VQ<+Hez&}vXO&&Y&dV^WgL7^xV|u|jiwJ41)&UC-I}T< zH}E(g?hI0H5B>ep-+TVvY~*se;p}NRuS5=?wgyuMZnJ1u#BwQ6qIXSBSoMHneOA4C zpa{IJ%&Pj9t0VsQH^8Fejyk}j=L4&RLXw_U-!Kl;NA(Tki0|Q5o9QB|7YV*?v8mpp zI^~HUSNbWpeCEg<(`7^qRTCKxOvV?gqpGF3dc`3%f9I`8jxmuSQaGTPlW{6guP*%E zChIeOPsy!GW4Zg~9{gDWS9Per1Fy?H+~Hs=I^U~S%#D}38Z}LzJgEP zwELRfcl90GZW3Q@l{RK)10TX@&z*u~RowT1KaK8s+3?H{20p}zf{<^Dn_zk(|R1xJ`i`?@qIo$B2!gWzG6D}05 z6DoJAa#$k5p=f>QzIG_NHC*F{KX={nJ$b6Rs72Mm7S|FtN(f^34&U;e+D10pUANl)7r5M>S_sHFpj{8aMzwPf0 z3G>Kj{;K_Eqr2rE-6|z6a?7Lp6wF{pkyU^17^LR#NxLuR1t>%_!?+M4SJCKg$HR)z z8%lM0pW;qG;cxHiBsCf{g;t>ar%!Y@f=R$%p2z8`(>?9)RtvrcG%v6^2UISD@@J=y z8fa&cw5Ze5`>be{r71Gt=+|PfjzU+yST{UW8V&@)9PaZ5>%mYsRUE?U#r<~(d{ zlO?(GYZ3;}k;K#AS^|zGov)BDoj|UTC|qORMcBjKHMUwM7~5b!8NIW^{;l>y+}W%4 zyVk~B_rouX+~Hq3e4D@X{)Uv3W^9lB*NuZpu_?!0B@Vsi?`?l=B)HQX+?8iI_O!Vn zT~>Z)tK4TN^c^$r&Mqo^%%b{^M3~*o`kry~J9|&DXHNLruPn}-QEc@&eTUyI@~|a# z=87RKL1*_ljiu*bB)Zv|#T=U3IKXsPVxFDdBe=6-1oN~GpD601scO!=QiPswLIgoqAi{|X=#04_gnZvg0WAGpDpfO*g|Q%Vh)eMk{=TLZ!=D)fy*{b9Z1P&}AGs+1#C?71&T>-BgiURBh?zF^wA~^ss|94YLw1@p*`K|34gNY+)KriC6n^Ty3OjYp-$lBg zP|Q;|4S?`XY%z&S%IW(i_wFY5o+kHJWj$^2p1LLXOc3UfnR$h9NkT{%$7KswAuAlM z&tosgFv%79;F@~qW`86!P~}hHE>C~0 z9ELQe%f_AC_?ONzWLgZnD^}HZ2)eeL`fcZ5hx0*u)n3U56035DtYeNc3rnr0FB8G~ zzNPPs>cW$x>=#o%fjcl*WJ##NFm5uV5lMcM%??rt_e&_E8d^vn=%1 z=QDf9qlVcp8sa!>5uCkSp!-Y&&VJ&rnLisL_uDsfBKiTlTjIA@44bvseJ~;D526Et zFE(d6qL<^Gix`NVJ6wbvEnLB1#qLp2=YsX}AJ1W??&q$#eT6Gn1Y8LJToe=k#6fpw zXk8gf?my?E9SP~8h@HFa@4S&gaze2sX(0|)Zz=M*Sh(ti>yll56kdqx68CJ*+~BQr zHBYkQf%M$9jd)&85EPGb>-$`%DpUv9Mvt$Nvv_ga(Jw}GeTnGjm6n&R16N}XUvTpl zyZYulBVI-p*z20gcYICNG=D=V=B>o$ZBNbLGn`6(Id^g7T4Tz3C@UzFTS&M>T}T1u z9HmG_WHo~2O)f?A_Ljb9{JrY0+tm4s%6s61LN4s(%WFmn5yvCt=iMTOK5(e_FbMX% z%XC4lhPQn5tv{jgT1dBOz9h^sg5psw+&q<{?*nnomLczH6wa&057DU)+YW}HgfK3= zA%_KPjfD-uvhcKW8@{G5H&LtT>LN7V$Gy=;i+vu`3*1F4u%UbUP6-uKt1tJdDxpu- zHT7HeD}K1@3XXxNlO5ETL4>{+4Prsz@<1Wd>{$P&b|Tl=*o_{ATEt~{HJ#Tp0vF-* zPPw_vPN2SMPyRF4q@%FbxT0B(bg`U4ZL$u0WpiwMjEj?rwlftcnK#@f1ga}{{JpC$ z9VOq1Ep}TXs3nEFHfyEjC0&$J(tQ#N9=WM6H#q2PklEW&P+A1f_YlmHdcNnD`|JG9 z@0Z&(mT}Hj(#i^u3eh<2DhQ59MiFsYA-}+MU1vK5D#e6*yAGe}>`$vhS37iBJ1Yh4 zE7INz@85cnwJRPR>g1_IZ^&qETegxV=XO>UBC6EBOl#I0RBC6zaed2=+u1i13vJrH zp~k9T@8npS`a**&8XDQ3Dg@L(pQoj6oVQSid*vEf&vaP z08)9Tw5gr#66`75$d`SiFJzUU+s|?lR={p{!}hjyk*f0ZGMPo7i6mE}yd#u%e$w9V zB1_W!8~{X!i*|!h6_a9c<`JEgMf`zHo$YLWWEP9329~YNPL}0e_yopEiK%W&U~qt{ zKtXHTc_!h$b==A9q^P+6q^LjzI9F|J);*sTA?sO1qC5ipJus-e@I|?Dq}0v zDr5hdc5rf*(VlPCiUyc4-ovU#a)}BDAx3?@U8Ss@7gBV(E{e;x3Gu$X@Fl1_iBsuV z3|TEfv|h_9Z;6|4^+YkKA|vEMhA4j5D&$j6o>_Q;sxSSw_V(;#_C&!xZMz7$btwy8 ziXD}=^OVZt?bX3klAp}ppww?bngDuxUHKbL>i@$|+av#wSD#d}VWLMC(LtD9PpZ+W zl45Gy?rH6AUpeS|So7(Grmu ziIIqwmKZIOxDr=lBu1hoM%vH!TlGBeYlqC-KlY!6*UwW`PyIM`>eQ)Ir%qMHeA#^S zruxJ1T}tB_LmOi#+zFh7kDw^VU(@MFO~TwqONjsuI3pdblb_se`$8M-*Q;9X6V=(- z+B=V)qk9y}lh1&qk5;n|8Tu~?y2CUGV*W5GK3dHK7ewFmG~H@ibJauB1&-XSYC-xH z|5OTR#F7XY!I-44+?c&T!X!z2D|i4eWW*r*VN1k zTtot*_Fv*o5B=Cj-Zrc0nZi6ByA6s_!A8^y>??m&SP(wiX3Ee778=z^0hqthjTZ4T z3qg&yZ;YQ6n~ne_I;I9bouV}+sJA7u9cWTNOt~oxJEWp7Ej;PQkHeI&%n(p6 zKrfxjRkNbN!GFplvYyblI*~SzzrWJ)2bPA9b_SkQ)VZibeYEo@ke@MKoj<&Qnrm2e z%whzlDrBiRwPDspACP`LRz@D5FVjDuIa5}z=u98nZek4|?Nam4u5)MS#*krtKNXG- z6?ONidnQoOvpK{8hWgR4$>&OVO^FZbG3?An{n=F@7i}I`aYU)q^m@N$Ayosr7i{tLpzoEHMNU8U&- zVJL7L)EbV!sSgS;YjavB2Y7#lHFBO%ePC4E$L)FaSQ&?gwr@lV`^Jrg+ZoElhzoSA z6`}PCpIfhdKQASRMr>!U;zV?d!MgjGxgqr7yf{9in~sz&huF(Q)QUptLp2^cXi!kT zL7TDTl~K3`cHhKSwcpnko@6TyKfdfiN_X2rdR}aiJnpien6qq1;LX z(}Eau?$r*p^pU25nubxNJ~nAawk`u13G%J+)?w;=WQmh3VF;0hL036hyKqTuRO$uG3$LhIHDx|LPBqnBbs3YqwnH*XFY~g#De}&fTp{ExN zEH}yxY zCvm;63NYv@TqtYr(BqgMCryUWx!$_eoUkJv7u#MjC3oH2dF)R>pi3umhb9obfkP{AnH{+k*|V2;M^-xx@+_q{kGXgG1$ zUt=B>UV{sAfnZ@y7FA(LVSwO@>h3MtxqnLnDcggYK?-EFJo&H7Apek-}+ab#y z<=~Cn&qfhR1;x^0h4AY4Reb@>ocAYM%0#hyZrELfT%MM}5;2qn5$$ zNrCf~iL_o_n*9b4y`E1|X1%|LwjOABK|Y>P8|EsrKVO%H$4p(V{x`PFy zpsg$hf$zMqqgeXA_M^f)5?d&bln`@pCi))J{Px+xJP3=frKXS#Uy_;Z<eY5JPeuI-=CH7dw*;sKhRL5G-9o4b0Jz~}qT_jHx+^hjG z`D&SWV4D=SD3)N{1rtfI$} zJal(=?!+EVRDLKAX!|GFjmV_F*sc>CXc*KX+RU$Al*neadd?~y8JGvJ=f#cz_>deh zwkP#Aq+By=>cjs%7y%nFYiG}9BXa>mH8Pp4=j_>HmUh;lGAhg=QhiI|2eWHQEcOIF zG|557xuZ7(=WspdD@+tfMlf8#wEGo0LToUMFDUAJ;?Jet`1^T*{r5LL)l?q5Zp+`A8vCmlMf)-j2BqE{Po>uCj!%#V!)*DOs$z~D~w9?!l!FM!LX+SNW4hhzvd2?>R{kx&+Zga!1`q{eVYB#Tr? z=caQ>s6fbuJeU0-N&wBpBcs`$zBf?~w{aCAZB_thMidIPr8rp+T&=ae@O`&-YID0K zh!=SqK{$+f>o4l{4Hv;-?_%^$FnLZdI zO&@H)NX-^C(xoN-Ai=%Nkn*xyV>kQrC6rJ@>9i!ESk#t>0nVtjvdfWPXJf_gFUg)w z4b7AQk>lq;7PW~!ww`4iHw+pGj!dA&0|eA~v-Bz+8K)!ZR2_)*r8uJ?sgC}#NhN6` z*z+>ie02c+nUaq|z4IFj;Q78fVf#JV+q*krAO9-D`m_%es6#)f1V?}n;TRs`R6O{d zElifqfo0W*8w--9ibovK-}Lf>H;!Pj(=LJL>AZw+x+4_Mn(1a9Pch_q>%)RG+7*J zq7urNXspgeq2J{dkxk`L7BNQ@gjEoLGlH4Yp^}$|{5f{`(D-(mF^(+tssZf;M#D%8 zX-Agxpddwe;K~_+^htpm*&_oGf;xlKY_2QT_{Mwz>z>`eXr66$KZ2G3q)lg6PE^734t{oyvHgWIDtr9nxcr9rOJd>1w&(%|Tlwq|Az zmX+U$?QIp>J8&d0Pf&c)q#XPqQ5=N^)ns=kS+lTWq;kjwPKc77P};Nj4(FAKL)Mhoif4nJDJP3v1ZLX1&Eb-%sWH15itM&kt(R%f69KVohOWGyDR!XJ+&Y;^gj^ zwZy^j4NqbJm)PUTR`8^ZS+fD9jOU&ZwY3#Tw&0#k0$6q7$){&EiD!djRE%0_VtyF$ zW@0ocQPG;8(7wBei@OgwB5y3k`881#38Vjy3g3ep4`*21E@YK{o4k^^g*;G0Xh#u3 zD>X2zZ-jXCdq*92u=@xv=|w7}=#byC0YaXp6R&MX9AGbrAQ8lJ1IK zg+8d%n91gxx6AmK`NkrDZEsBP$@Csi@5`d3_7wp9(>NrD-FM^M>9MbS%Pdz?r$p>mmqe;b<_jR@1*-Cb zX38D(!hc~l+p&Nj>j{t={Zj+8su?TAiF0I0pzF564Nv#meK*Q~RFS<0;tdMqqG)l> zJd2{Gwy*g5G7DP&pp^(k<@it@Uvo03f(+`lKbHJwxqixdnUGRQMl1PSdsJW{?X8Vy zpj_U}r!#HTjFqd4`BNo!Pt}VPGKSD)ZXv?3XX8=WeIBiYg4$OB9uhV&OSh0eo%oj4 z?IqPSck~Q-_Z<`HDx&`ZEy%f&r5idy=nknG;AIQRlcLTy@h2)N)z<$AAUU{QHy7)t z9ML3fO#$s&3tF^d9fN@{Rah!}ZT0xpnD6RJQ#$eX4H~XLkvpiF(w9Qg^Mt zgi<5r#wMh_$YWdKCy*z@I$e8YG$WmnvF6kPM@9*5idR=htd5M}O9*ac;hGqwMxhLp za!$bwYhYN%#>|26sxCOfplQq!l$OY$+^2Mgv_8 z4tO;LX+qazfhf7`SGJH#J>&uf&jcs)FD%Sg8zLMCJ%&U&w1OuDDL`xIss$5XL~gZa z=y-txvIIh*ZaOrYdMTMeU4|s`9rT`s(-nyPeE}iLbdzZ1&wxbYhem-%tg7@|jhT$v zFjsmDvvRodMy>Oeo`-YxVQIUd6CXdW*f0WO@@ZRtx{^OB+f#(&^>cfF?^^1!U*gN)d4=E!AoC^YaJF2+gGr`>#yiO zBUfQ!s-QSrB^&!0@n}&xcxj_3ynQ*%sW_^vB>(w9ho4qmgdh~blE4u&Yac7N=gdsV z&AV5rb!JKU`Kf#1Xusw8k+mNrkb*9_>GM(AP;h^1VsXiP*siq-MDpMUG;8=gps#!%4sg+i9r zu`_7|_=fZgnK=-jexliGf9aYRc^#CBHb}QwR5p`&ja1L7$1e+|akwCra2FV(df@UB zKh>_|P2xRQ_+GJe7g2_SUh~d{SSWxnCXW*=AXQP$PoUd&?2A9U4}=c=T>7M2PI>?w1Lu_uTYy2*u6@Zyv%f~Z*-A%-~46qFQZA=Wr)cp1rV8$B13SB6kPM{f=4~RYz@0p%{tA6y?E5DpnyiJ<~MWiZKoqqF&H< zLo-vqj)u^&klv3()=}G3?5GPJjfC(+IMt9@zW|Mz9J6Obh>BQWKihK!R`NlyXHFd` z8bLUHeoTuc9d#+Vp~ki!X{5T%hZ-$ziq=sRYXUx(>J>XK0ulG1ElTq~E$}GAj!U8A zu4dg&Xhm@MNa$Fh84&Ab<^{izA0H9`-}vzNdVHO%3_E^{c7QN2^fiM}hbPt8UY(Wr z@bOyFLil*yLKIjZuUGm-1QMX|AHm5!-hhWHA+4fZ`FNd3i25R;;9%}0FYpR1WE;*a zl@c{7YSD600muq2%HS;zdr-g&QV&$26$pk(mYEO~2t(ibGCx&VN;nog<~B(ivU{1# zRzll<1qmhszHllONBXhc{ft&$-EBI)QU00RZS^-=T2vwGD@xk2Luii6zwKXn3{Zo6 zM%+=(2CB}*f#WL`eRQ!DPBfdZ!K+sXW$5cD@pHSBShR)bMzT?879o2R6h+-EST5 z=J-z&{SFC26}wAh8-c0iXdF~b9TP?>@u8ztT_-``x>(k^&Wk<{wn-5&&blR2b*Co_ zS>Prgsuo48&ZPdAh3~TgJak+&YCCR+j>y}n$Tjq)pu6LWf7e4t{N0F-s6u}^blen2 z2_3gyC^W>J=3F2R+W9g8u8_CuaWUgdvI=>*g-K;f)A^G2#dBuo zOL^O}t+|K|t6L|%k}Xc>OH1M)>*!cZW@#zJ(Tnf?IGSB&5h^h%QDlzR@hE(JnQ2%0 z4^$stRy{wyD(BTq4cYlpB?I6F@8~$aN<$9FcJE?22)PzNz5#80#WB#ZkB&#mRkf}U zWo1{ssxCIv7+PiYQS7`0Ryz@T=U`rqagtj+PGLUgQ~3DSpK3x&ckX1B5Mt=O$}a|7 zjU?Q8lW(E(da<)tpG?+{h4ETSnK=YN=k-iS)M*CMIizz1;@gc)GTvBl zDRh=xhMr1OvfZaZ+r^{?>9KQ{UR6#)L_@qlfrb|O5>PV2?CCdjQ!#77Pv!)GTOS|HqQl~a(P+dsc>|udVe(|p^H@6<1#$=%P8^aQW43~WpXaF zbGe+$H9?eBZwoW(-%_kze^pqh?$O#NV9Wz4gBhB0)(l)FWb*A$OPrhxwNyU44Tf~_ zV`iK?)Uu|~5RSHFmcF76u6jJWf?cX?6yygnfpk9@!g$%GF~5QmDwjS+m+Zm1>O2b~Ul;BYdRKa32*S2TKbq@eoa^5e z+89j6jRzQMFNw<_>@OixCqLNAc@=gMR< zad^Gzw#oskRpS`2TC~&aQQ_!X$j0=uA#-fR;rg3Q(9gPYI2(f>O~OeR5M9?2xA`XJ zzv(9hts!G$u6h=`YEyz*IjQ6*X)qMVN9_I))SRl7TAgnaP>*gU`Byrm0c^VkV3G$f zH;04QxM-D02xvWcg&(p*@5)Sp-n&wE`%Msq_c_pR+);h-a*FM!W&-};D(;|W-=|9* zeajY^+st&Z+zdl>J3@I3Jt!6yaBPR5%l=6~%WeNCWDV67Q#yDh^4i>!fl2-2XH!k? zf#Q>!q=^$8yiyO%iDRcv3o?Um=dh#jLx&HoH#vtsW_Eex%%cv=XiiqK?z)mTbghk{ zeV0fSI)!035ZU%!0`py~>x)Bc;0oj-Sqrwjo*tRNqP)ndh+7R`-z9L=wU$TcR$*)X z50fUca}A7MU_{f*uC>HUS#+)u{bdMTo}rxG9IXa*R7Ccd8qJIAwUG~voH)T~#A?DDv!8pjA z6yQcCRF;65pv{G%f*>tw~5I> z3vqE`ldzSn&bVdUhcI6gj*dFNA&r9H^GFm1;i#xJt=_{DjRYP)FQ7=maV+xB; zuRa}aX;#12fc+I1rgaz(u$}e2gOs{hUo`>a>+~;_7mi*hit@hRI#T#4#^3G~Amc2< z4a_+jfM=D+-kd`d_LupH0n^X}Hoejd)b~LNU21x2@r+iauh0dPcvT0<=qGD#^*(qf z@nc&k9>a?)yp^XMmhfcoElLhF(v0kZe1Dlo*ES0y>Q|c?^ zcM$2?H{Tw-%yzcWwWC5N6;R>68fX~wtG^SJBx7iwU-CrEEMiilxnnwgD81AJD}f@P z^2C2eGFP^$96bdA3rymSt)QBdcjk$o94l+E?33e>)AwUr%*-J*5dh_RwEo|PXJhJc z^gg6<8d2w77VyA85tkGR1O#yB`fAQDvWCi|RK=GtG@}+2LkT>|b1~JW<4>zytisU; zj|=`+!(uLp`P);UZw@+oUz{#ht`g|6@V=Ld{%U>s$mRsm=&(^YYg}kMXy4T=*v=Io{7iK2!0C72TF&w?q=g=*ABN#Ol#7p zP?WNp5oB*(5PQF96N#mXBD42*q?O&%M*Y8 z++!FPmK?2Sd}l0tf3KK#|2n}w)VM57th?y&f`k{jgil)dCai@aWKPbN*S3yNTEtr& z_B>2`Xs70X@cr&FryWKPyAEC61iH-h#P-_wk=Xb=EcJN=lT3{r7{Ey-nfsYyPUHHcI0Ndc~?e?R^*+x+(sJ+QXAc(P;wi>1e|G!#zF)LHJj!X zZnI5}8QX7jm3vEc|4-^3AUzYTH0%n$QMQDqZj9nw66KQgrZL}A3!F0wjdq~PEq)@w z_bUj3sZ&>rL$;+5;&k25cGl6LpTv>It(%EAO~Tj?@ACz!-WFrWV{~dW2EP4=9J(Lr zLcbrg77eA>TCf0@>#KNXFrNzVSNT57GZ_{lCB%8pXzDC02R zBwh%U$SYlU9n$-)kl}BIj8=u*cB-+ekb5SdafgtreG|#2C6eMNQ`5dB`7*`4oW*p$ zSL}=MF>;aiYIVq8#%*1c!q4a^t0cN1Z}o7Gywy(+28>y0h@gisIA{*rbvLy}c|!XH zqeTNMnmBcj*o%mDkdZf3sbgs!&B^-5!F}@3=i%|>uD=;AeC#NkJ-$%t3>Ta8;ehV zt1#+2x)McWB@@M5Sx?NWFx7u_wZ5!I(GW)0nElAV#yhX!^?i^JG%;fqYDD+l`JN5S z;v_-q{^qaD5R4MUqahJAL}y9q|Fli~X-ZtnFB`A3+3n-XaM-k|>kRtJzHJtxq+Cr_ zj4T(J0j(*^=-N#B8`Xq?FqxikZBLtC4Y)@5jGq`qmoB9Z*z^?!--R{~BH!zrQ* zTxjHt4+W=QH_WF9g`&Jlt7`Qleg$XXr790<+%=p~gagLZTjKXaOH0a#SvKqkc|$np ze4AEWBAt>;cHXgwkuWsEC^`y7qJPAr$LO;{d3CJl8|9Pq7S=zPrLibaVM^qWT@~=N zzQ|iRmBj2R_@WV@Q7sXt2ia3wv9#3090aA8zw zgks+Oo=fYXmAErw7!MAl-P|M|Bu330N{k8>gvtfN*~~(pVF7{*>BR`?W#y}$CR!H! z11azb8-P)eZ=Xf$I4!_K5j5q%22B4Ww;0&K5<9XCRLKWZ83Lv@*#4sg+M!GJO{oqN zyeqUSP#Bg_)Y&GK91ekS3{Qq6Y50v(NwFBM*OEdE#Nwpcc%?L>2-FfWBZuD2ElNW} zPMaFwbATz}?-0236}_As5}%WEnU>5`+G$afN2)jHXE7q6Zu`fzk zoHeNNTQ^L!5wo*RTu{Dlz6dvicP(R&jyN%!49F;oVp`7(gA=40oN$g~DgSrvOS0JT zh`Q?NBq9Zy6|9z1hf2Fw5|T7B2#nFGI9(V;8%U#fG6iyh*QyG?9^J0o4OQ-jkXwc? z^T5D>U0L2}Dr}T>7%Q*0{L<}-+QU#B)xINVQ^P6~E&M`ge=&4=6aS0C!u#TT#nFe_ z>L8qHuLJuR5mHoEC-;x?(7dSgnCC^E$7F}pdD6>YohPH!W?QqiljwW~8RL{kLwSUS zUa~Kbs;=$t2A%zsuLziEwTSG_U>tS+qYC;*A@G={zvNd7E1FFeWlK^#h8?qo>|;9I z$05sb&}=L{b36mP=KwkFl6RGkEw5CGXvNO|%2ZBIc35!9kEA#KGoc<^MO<|tJ3PP9 zfn1sa#qmZ~LZV~&Svd*E*4ALYag2&Ao>e>sEAxvAA44qQL+p%v!9JXVYT3IyU-zS1 z)32pT)+Sk2q~oh`k7>#2*m^eU)c{wGsqk2wACiz$9A06zxq;~KKMK!;V?dB9 z2Z!pO0teg&NJCMl6rTGXA!eECE@eSDR*vl3n21?0)OAlKG=yV4Muu{`uBv3?S@)DM zl5$Uczgr{rv9caLS&*^NJtaQZg;rQUb;s^Q{D{a~XGe7(0v9Ya^753< zdEifrqWZKj4m51-c*At1>q_5H+3jyg{!O7S$aPN*>=$1xJ5CF8p%pSGjz?CC?p*9( z8&@e~lUC>AgpM9`%$qO;Vwe2vtaeBDPyS$0?a2{2|9~%KkcOGO4Khx-5T=U2`^WM37fel;_!A z4#%!j)|Ad^0^DnQxGS{&PF7#XZm5GoAyA@*0s9o^23rDE2?^EK9Y*VDx2w>7>=rmw zL@)BR{w|yOKSx;CbEXz)5K$-2vpR8}ECM>`$!iM~y`eeNCWuvn&UtPHZXLq2=Lwav zXBQ58IdF{o-Q7oSo2Tqq@0l_kpJ?l$*>LPm*wbJvsNqnTdV2v7?^&;l2vRCJmE*Cy z{~=Ac^a%`p>t$k8D12zoda8Ucw)=fU@gU{r26;)zUyRcZX9Oen+aIFL>^l=Iwvwl% z=y;0)^@;@jBrfh*Sdaop&kv<3fhF5F@iycSjgj^4XsqaY`UiNA*tY^c5lL%n|5n3I zDx8@D{5W~Ig&}jNN>@OM3^>7wQYxiUkIS6SMFZ^g5R-p(1^-sk$W9wTpN4KRm6VZ}&-U{2+NCO;mc}!-pom0yiBn`PrkRGwaz66gw4%bQf zz(X66a7{@=Z&FxcFHUk7$a}vaM!#WZxb*T)Mwzjxr{Rn)}pXT zTqb%}IMlJ1^S8S7|0(=w1m@p-npGG{D&_xAe}5QPX7aY3QPsRE7%RfqV?A|WjK+d( zvVesbdO-4{xk>+*NgZJBTpx}%C`WAa^DdbhRbv=aP@Trhk-W_qn=>(MV+VT_@n2fZ z;Sul?<>xVQ`yT=gGXdr$?DbtPi^h)@f3F9N0amr~)_m ziL5NabG3{{J=rpLjnILn9wO;Y+>l4{xpo#A^NLM3sRKC^H9`^c($Jr*L& zEmkN)uDTH=lEt3;w$)H(b=;W5FXqe((AwX~;_a==;_Z3FZA<)Oa$b(toP>bAG|#I_ zsNeCONbKs50&&lMJ@+>FS?7=_2|cj;k)FqQJ*|-mlGw|rN5)-03&&f3NJmE5gX(}Y zrbGM1(Dfohs(%P=I+9PPhcaxGu#j#>KGoLTFWp*u8=Y;LCwpq`?(wtN!fH?NkJSjw z_4cmIUw|*4fLKSS1JVn%Kc}rR_znfGh!Uj3UZd9n>)y5D_(;UsT5ZK5K-&F!FrPwU zGr|fyXz$v55=XMiByR6IK2=SBkEZ307+7tsoU89rPjM?{XOR;aq}w9(uRQ)v#g zrD+BwG-?W!a`UW;K0;Piv0Y2+m5-0fy?3oA1YN(h7`=C$_8PU*3Pll=$>jJr9ga$< z)|sv;3Y)NvCgbAxgiGkz#XU}0NHSFq$0us}TdD^66~u|ThB7%m$qV_}UJ&YcEN+S~ zI(b2CUlQf+=a>aGrJq_0ML_ETH`^uLuF&QGSa=%!@qC%UoAqTFQ$lv2MA72-<#7D6 zLx|@0^1@R(BCBEr{vGihEahR>>-n#dfd-EU8{ohxW&%eML|w0IR&T8Y-)jDl9qOU$ z4N)9v-UNOBy$#7D7z|oCaD}eudHkNA_thcyUNyz;y-YPQ1PR9<)Ys-x zQ0_o+$(aOTcHel|bH}?f>HlgUC?@Q=>AuMua>^7eiCMP|uBIh=mZS1?y|s;wSm8Kk zAO{R|*ZYQ`>;2I6zIN;LG5?X{o|}J!vX!(DP0#W?52dN{M0M%Q=wKNO;2=(@PM~q) z_@jg!SRE9>6)fuSEt0f@F7VjbE`%NP1ln;Kd*9M8R$8PvGTSdPYent7IGA~mP6gAq~6$fNwtu%!wIgY zsIU5}F2h%KYq1pWn%l}x$l^GeHnG*NcbYjAi*98fx8((K!&G<@uO=T#jc zorW9q!D7IPtHUC5hHUO{VXHozSnA4(-|t;V>+{|R6Wy-63c*CmO-VIftEI*KiA0PX z`zC6_iDgRQIrF{=oG+wBxI7o3QBgwM@2cP7#B$IIb0$kiAWmy<;RoSFi^~w~*4tK9 zBKHG>g3~KY9!{(vvzf!#-gN*wBd|KaP&+_CW_cy8i#6qr5l+~s@WfJ3rXw*C?tIBTB6wb4e1Ni-wrf>QPLzo!I* zfs;BJJnn?9i_O}n1ZOI1I5{zEuVCmr{5!YyN-2Ezpk4uD{Z6Jg8JR?nmuR1dlL zOgTPV7LmRr;WL26j4rvc!E5^))Xm}AxddNsdz*M|a4041x#SEk6G!rnY$Ws+Gn*UU zYl0bvt}pr3Xz2(J98+~!#oP6F)H9>K^x+>kPy$BqwS~1Lgf@A&BQMs|VJip=o?vzT zotAIloo(NQ6W26aw!Mvm!@}H5l0M%XNlO91&)fm_cP&Ldah)LRj8EKfkBYRd(<@b> zu5Y4WXIN4P(?3n<^`ie8y8c^IaG$u{1PG;Ha5~RfXpK9hxU!ms#S?dQ+T9Zx40SFd z+^D2QMV<)ZjQ5-fn@7qWq~sSzyL&!ZM8<59n!<_ux=#UWNX_#AC5DK@1o4bkLf?ku ztX7gem?uo=){T>>P(Sfd;GAG=cB4*f%vn*HK4u;QKV$t_^lEx&=8(y`Vc*i2XMCzI z`Hw`VOh?DH{+`2Le;@X(u*9_jhr3Vp z3bzwVR~W|V-|-eQ^JCV}3bV)+*bO~5FgMqfUh0C#p(XD5^k7BGb zjCpD~)@X_9MVA4FGh$5ADE?adi3VYcT$+F9vRO)xN&2vpq zT{;E?FxR@}wu+f_y(!tO%``F?dN7lMSlXJK8nyD-b|ti3COtvl@M6HIDt_OgSbXKJ zT=#nIXW!kh@2-a&h`wi++t<@GtJ)9;^gqk8gbx$ZJMWpL90wI zD8hm1)l5_wPDtRDRE(Cgyf~Dbbxvg4+i|WV66CjU+~m>zH|_!BXn4fw_T16@WA7Y+ z+4Uj}$FaAAA(Qi)c=f40UI#+2?D`q!twphD?^0`?GNqT1?U5_BKc_Zj`_2)od@R7P zSkL~Q3f+4P9ZcId%}sw2+mDa20 z_{qvypXy=ivSM*;<07f;=trr0fE>A@%7jT|tPE4_Z-TLZPUEKX*f(i?C^8IOQ}c?Z z{I%~1eU=Tv82FN;w{KG5LEIeHq?wt{|JAFxo$&!Nsby9>;$Cs?w}kD6vj-hDM5nU zwb4alRE00ua%=+XC1v;*l{HFyDV?O$icgIm<=0Bs%orYAz<$ppKNo9peC(3lm;Jor z=T$$ic|ah+-ldv<#Gd03bnH5V1hS@R81W{`$iX(*izrN8FGi^`+NgHF#A=F=IG z-3UE(KpA?X{I~a(jx`v&8G0I18R-?rBi$QmaBll~$Js9Pb4zBVBBU7gWc%;c?~zzU zL$@8g4;B^Y_CzBX(IJf8t46<-`#lRa@g94S>ZKN87F>Gq57a@PkK7*$xuN4bRbVCP z<}8I+6Aal$zWlsIN0++ihKhoHz^Hhd{OwCIT`kY+#vV!fa-Z|W>7*-ua*jZe-e&Df zak@vs`f!pdr~ApG@SFBHG?oe`P?^h(kJtbCyWwOT*2$j=d%FcH2wJQ2~+=HJUw* zB(x8m%PO9;Envl&(!Nrf%viWZ7cHSp>;hhgS8NIkG8?q_p~l*{N9U8n-cQc2EBx+;P3FNI?`mET&A9y$uIBS)heTWl5Vo3(f8hr z{8^f$QD+23vRs~x8Eu0!LyL%Zk5)ZB5?R;m8FSm<^HT9?T!S9I>jgOc+<+_mT=@d4 zA}3dw7~8i;T8`#-ChKUg*Eu(d3^gl-AiABS@T6X+vFD;-0<*|b@C1lV(jY)W?#b2e zL{YLlxyI$Mg;U27qy5d;Pn~tVY)pw;RGKzV{GQ?f1;cPj1B$@#I#zN%7D2JYSY@N8e;~-InU@ z$*7HBTQfri<+s8N`^?Zaas9Xw{FpK-AEgM6<;$`ny4V%1&F_^@z6(N|UK}It6CkRN znudK>eW>RQ*Jx1c&v_msiMcbwt>m0Wro-zOk&RbBdV8~XvA5UKub5X>st~Y^QY(AK zT#D?59sXxgX@qZwS#VX_$3c>^E%t7u!r2KXPU^#von_>nDW;DgfyZTDp^F=|DZUbD z&A7b9lf%B%BzLd@%ki=g7_etfTN70Bh{HmxTQ%A=bU2jJHOJ>OX+GyqY~IY?>T1Q4 zqW~CPW4J_)C&zu~N@`ph#(nbS6n#>Nv|}+$mE**u^B}v;d`d5mLcN>JtuKqhFQdLM zp1k6omX*wSs(tnPLm&{ZzZUktrP#7V-Y<=R@#OU^iFcej=*F}lcVQMl)rC1}0f_eZ zdSuzZEAZ>xR99JJ4XPU;-xnvu)!hY3b|7GCh6Aledr|6kT zi@huPbP5#-SU@V%Uc0pGKu)wt;Z$ANubSx8c*U(#Ue~jwT5Zj9Hp17bhIBduNlAh3 z77$J~I$5I&T~bx(lFY1Fc|gZXB+aMN=3DW;9r8T(Qm)y*`Q+RQlNoFUld;F8QK+tr z67ani`%kS?9oK12B*E#wNG6UjWuU>hAlL+FW^_CaW{fba;C~g&R_9O?k3Grq$SjU4 zm6vE?U{ku>;y91O=Pb`kNk0|L)dfw1!XHSN)QGBgeJxM-41HXto?*u?@{{JW{3?ay ztttp2?|DfqQxC*KKN5KDVvkSm&gQtit3xjWn5wiotJ`W$_=p*<0AASyWx`{ug6pBjx+wQma;~E z9Oy~1%$;ph3|=j~ij`C~-kXJ|Qz6w&BN&Y}jn0WcV;QM{O1+l3)SLn`P!%tu1iN*_ zjDSxW_;}#FbLf7~X?U7L0c-=Wz=UO8IJHH^MlLh`rHXAq->T9rYJ)FPgpg(oFv&1+ zDdLX*S#4@>h213Yw9rH$+6b(CJ;AB~KxGsfGs0C{y(iiEh<`?QR$H|p*`IPl(L9Gr zRa2Jkkd7>co8o0%hf{0Askc(JWFs0*F-#;#aj2deg--3#)U%sqt>_CjIEuNa-^C{gBf^@?r zkEdjOh?9j4S1$auq@LG|Md(0S+{w?Z{;eF#mSj(9W7H{YsuZDy;|H8quhy>PArWjL ztf=M?$dqv#uw{QqhbX*Dn+J=Z za;Uj??0(86?a`qfzWF*|FFGWx^HtuJPY=?k{9XBUKYhyI`KJR7@l)k(Fd8XT$oNzX z96|;dJW%&z4UXI83J2h59QYXqmPRreSjrefXNc5AjAxX3NlLAboMIXmOCx3uG}@=E zz9QE~m)MfhP~xWz4uRVn21@=^vQhZd9WS&F!ep!n9|VD)u5@N=VrHKUivQu{w9vI^$Wt8dtw^NCOTPINlfw>>YRTlI*)S(X)gX4xrKPyJvQPfRzst zM8fsi@Z-y~=q--UR}FgUD7o=f(d}YZ$6^j_vv1ur--gU*27AddHpj5x2K%;TKIWLk z@we=YBPFeKV0EV9KCl{(%sq(##`-FuNW^$-9KI? zj5EBZ!h!X%6R3^H#VHT0mtZhnO2JB4zMD39)raxkFg}oQt`yoKJwBkZbo>x(fb)$M z;LXzG!(n_xzF|E^48fS3W0}xun&alIFg|YSXBc{vz|;(2)`<^KMd=neO_Sr}+N+*L z@mViSm>pXrEIL$L{nhf9ol!JAr89tsr$n>kbO+)B>cCO~Gm$M~sL1StVHN3*d!c6B z$eD7e2EuUR*fBKPrIsi_&khNm2o zbqR;Ksqbe*_yC7Nd8iB?`s1TW2jMhhPwPHf{if<3U80ZEb&)>CZz}iG4XM2CDo=U! zahMu{2lXFk%kcOOWqMkj<0WZR#Nsv70!%g{-=(E<&z*33S#<=*%cQrDi(4K^blE$7~!q zosA=>vvK5fHjbRuIN~OY;|SVq`yw~Mr{+41*rChUYB*({!A{0vSReM6#JNsKR^YJu z@IG=bVXba(D{lIvp6IJ{a8C@jdmy?^ABPfsFj@}n#Ez!PXR@3giBULvBAgyIg58b6 z0B73oP%TG!47Bj*-@yek#m;YPF>G$*n76l4p7K);+O#SieAP=kiG1!RioMIxcy2kJy-Wl87)8--3Kj+5 zO+rm?Yj!%VzZ=Qw^{1y0>BR5iL_zs@nF{#W1yQG}@P*6=YW3?AlShz<3wZt#7ajFf zQrGx$#G?zjqC&1<xM50@hhGx<<8@$;bWJ&&zQas}6VFuE9f6_q&;Y~qdM_j9hsp6- zHpG6WkyTKBwvd#cEzFh`58Mg|uv!cUZWDaqmd?`?bAM6qBKjKTuk`0 zr7Q-9&ot!9nlUXpOe2pXHcrGjuSi(?Kx&Ggt#)gAOdMG1khLkq-A66{*}B-`cCW8;i(HT+ zM9MT}+bmJ|XB!QEo(JWn((Ay zz~!e3^O7tU|KYP<11{4`YSee%v@A0*%xA%Jn{JxNHH_PSsOI<1b6RqEc@EiU?xmUCC>0#kqo>WBB^LH2m3B(D0dg z(`RB^pIy-uDk?Qy6g2}K{hUsrl^nYAzy|&L-!k1tA%{bqICJqH>jL~<(~cIB^o_0JGhlqdDf}_ z8=u{aC7?;J?mP)>`?%i?4NW*RrG15Mxgk+&9LMHI{<=#w-nSDtO-=oe$GL zCd6bQ0fi7RhF(99al+UkPj?)rAt!H>Q3(;Yh|Duf;3TNPQsEkD&MeW|s=0W2_B|4Y z&xkjl!MLV=sA-|Vsf5TE)F<$YU7 z^Q>Rhvi3P%s6*p9xggIjSLwfEfW#usK>chKmhlN{z%v>t&a4!d>HMeA{x=eC+P~o* zw&aDC#x4PL`l6fkpb=t1CRudl-G4dFwP-5}@S1k-N`Ky@&t5e`VRtm}DA@x|lM{jP%aOBJ`FZrqWET(tFm)$CO7vvnfO{}zJ>@%$zw>tk( zTVQs-EAeOdyDT*q&qU$WY3=XLVL|!8_koIE{N+rNBkjKAZ(QL_c$Bs(`CP4}Q`bCm zElDy1YS@954O#$!Kzu|UHPCH@VDY!mAidm{HLd5pV4+oxwy*g)Z%G+3Bpts+-OV6c z&nx+lnC70rYz-QVC(iPUKh1ROwE}qN*6MUpoMOy~m;#j8yswfv@dTRsjD)T;w`CnK zHtEB6TR$eO^I2M`mT{t-7WAxXW^gCX@+>Zr}d%LeV8l!{^WB>SbNd`dx{! zdC&*2C6?Y-HG@_L68y10i;*_lfl58&scFBtYhnI@Q z`ut6L53lXwBmTlBT%G1w5AC>p+GcroqDD%YUV*;} z9z129o?vAQ`3!8qa!JN>^LcPH7r3plp*$%Dyz8m+c{=r6Jc*MIY*jLLlFH)F_YHD@ zfb8+ep@|iWft_R;8*dXflLH%RtWEspE7?XpHmPwGZ4;NmS0eK@#hlN)qFM2xpQkE2 zut_a9u!(cM=CbHOE$AbpZ*87CZ{Ga*&Y!~)=M)OWpuJmvH(#wqBf&~TY!Bdf7{HI5u#(2?NINY(BP1y=0PlT1|7I01}ca0>ouYLW+=a*F|_=~ zPtbz8mI)cLX^jijLSS-U*xWDyDGS@1v{_%$i?Brw6H9+uAQtA1p;K3FcF+3Wx1RID zKv`9o&i1(e?r4DSfj<=0tWk`?8^W!iDj&375bkp z**4Cr&2n(S?M9`)Gxtfwk<*Xc;v@s)ew($2xm5|UMTt4w28N~Bqz_zbE)LGAEe=J9(5!lTI0ON+8ne{9t`F8L;2%SKGr-JK(2$avJR9j zA2VRrscdm#1*3}{D{<&RgMl5QA_;-+2Rnq4gBwHVKaldE^dH5+R%uuqW`Z1CEzh9W z=%LR+i3)7|cRH9wOh33m;t6!L0qM!{SJ+1fqzRhm(nq|=HMp*Mt_33Hw2oimCWGsg zt7cOwlcUQwdUMvnjVaj{DGv^Ai~wy3gIlt6VqSw=OzNo8Z9ffJ4r+(H30+?XrJpzb z&(aq8!5yJ|T0K;^I&uw(a@b*@=;MRe_$hO{8GnigTR51Qp(Gq++9GP?fLFo}J`u`i zaW@ys7rCD2Hy+1CbMzgd_TUq0#q!1Ms8ljeP4>C~q6DEJTG^|t%{f}ugh9{2^nHzh zi<3clH}lJqZq}#GuAgMF!L?QqY@gK(^=s4`T+1&;CMeIn63XXi5evYos-+=k#uLLS zA)R)u#{Os+9L-=DRl%de^&ssH_Yjr`E}On+1o3b1J*CjWyF3+rN)$4&F0vs9O?Wy@ zgS5?xR8;wB;lykRW0RIc8f)=Tl4u6dJG*~Sm`}pTmH<1A z#g|`~rMoaVUsPEByHNfrl>c7U_^;&Z;yMfeXYo#v#O)-%l-b%w0*m2bwSqmybm*lxU)SEPWs) zA9#AYhRjrczgBVVa%1*T;zW0t3P)a9JEyVqA)!8D3rer z<=+Wt*f}m|*{dvjT?w`)#)NiwveSm4p^m@Sg|@EMH%j@9TTIOr%Kxm1m76Y<{|T?Y z|C7$4GK4S@;`|=07AJ0sZ&za=1rA%t3BfLAIwGALK;` z?YqIRcyMiTa2NGt@bW>aeK*wT6C)t5I58|vGBG0HCq}HJE}wQiM`Uaw{?AX1Q6C0- zD!o1omO&;-2ZiFnGHplZQOYrs%@J-aY*$UT>x!oGJEU_y>P+tv8ndU-ixXpUrUIq0 zmMs?O9xHmv$Mep2o#EF~Modi7AHJftOW{4mgUchsYgt~H7bXzZkuoGVR5^6B%HR%{ zesB#)s;?1>Cw$6siY=izM&rxDS{D;1TNGnooL+^4K9+mpn(=pyLvK^*^clet21~GW zjRXfZ#Xh(qBj&*tRc!EDtdW^L-IqSfUzBu(MLN;k1%>$*e0hKYMtp2-xT##_h@b3! zfE!DjU`^|9~vAJ09=_L!ixU$61yW^tu86&%5&w#jBYL`ioY^9**hZFM8l%0 zw4~Io1D$WK)1eAa&aJ8CIQD}ZxgCenI46NL5dB>44kvesx==5|3WxK~A_e9`6ixm3 zXR};}u-HqlNcS;;(T5Zk9@(1RA`hTP&Ft0I{Uv#d15f;1)>~&cMtftB5GO%;#yOB1 zfb*9L;E+2__1J#H{7dthlq}PN#90;{rQOrEvyy_(Zc=GyHRzr7rHz`YsT1ATulKH==jPDabEyNtKmQAuSDHahI6}#unn)`UCcai3Cl*Q0aw&;elm)xMby#h4*f(x&i z5A9T!JL|=s>f0}vJw_}MH&3bS7NXiBWgzJ((_{?oRB30gD7z~XEy&IjBgch)J?(D) zMzQ)=NoLh@)TH4lU1AZ6lEm4i2Y7`K63>Sw!q8ID@X&-FOQqp1b}yn-UW*}WfV0Xu zmXY+&EJC8$U#5cEx5>GUYon98+)8sO3U$+rhoQep^s`sh1W2VrJLO=hpfve&CWVv{ zRqmy0=->@_EF9InfzVf1DfeAd280cby;fLk$06N9lTxeB`jN)Zp^+IMMui+yD5cT# zHwok~$#gHh*qH9*6s%vWk*RTrO9Wf|@v)G`RRpl0SVJv2kZ!Ug0^r9+7Kr#+82OF@IN33ry{@iNQcYnCvG3SrR&u>^ zEl5@*Jq;XR)Al?y1R|Zz>C)D@60=qM*BaEMULn!tJ0kvpa26KuyeNPN_hh*X+|&So z-*j+Wba30wJ96G(z~B_#p|$#B1VOJcw3aH;abuqGc32@5 zn=Ta>z{OxJyt5J>8O#ueE3#rdNtfiwQ4n;xE$FRF)*Nu1V7>MS46O#4|H_YROK4aM z9$KxbM{4jP)zLZbaE|NgM4~KCBkQ}9Hvs5z(Boh0ANOr*3Y4Vg%#vfslPyBxrQ&j6 zzRC$zk^a=%Y?>5DJDS9mdiIGl69d{9@fxlq1rDuc^&nM9szJ{55n;wGvu>Zg9o5}U zQDJ6@JZ%4#lZu-76(*{QbiDGSa;5&x2q=<~QKO*s{Nl&0R4M-`gkvW=JA4jzS{FWV zAmHaNJ`88T6_{f4)@Y`}uM?mvf$0_QyqNq{{6}S#FkzDp~3ba>YtKCSJ=`&oGhV zQ{jkj-R~7XUnQoda(o&1=c~NaV7tNle6^DdHR%NgQ!Uk!v4+tQFGe21&@7r$lbH&D zx%)0w?u`N*WR0jE-w!KSyXz`4@|076fTjH~ItxT$XqX=%L&LJe80vMaVy)-s#v3r} zPCgj4I%SsU6ir*T^&>XADcCuPJ1et9HH;0Bx`$d^Ftb6g#b(v;WvJAHP)kBXS1RlA zLsgqRhpIMt4pnXP9EzJfH5d3?jrSFoO3smn_VaD_X%bZ&Vs6Car^pOL_v4JBzvQi_T(Xv{)3X%1;t7Cw)I{h^iN&`R}c5$&Os9_io@hp4im>MK91 z8l05MJ}LAwE0Uzxc`VL;KbIE!bD6e;*7Hn$a1=kEVqzL_W~i^oWJ&X2AGE@LEa>ICDyh3IqexJettuYUPP%a6$;S#7SjUmR05pA zy_i91(!TrA_qim>;^#8ObnwF%-2LiPGAP7u9e%wJZ7nLETN)#^Kd1VfTbAl`Zh4m0k)(EYPAZ`LjnMrj znRUORhnfxR2NU!;k;geU(2>(BCo%sK?Vekk`=NNpSeJ)zM=UX*J_-SOXOaGfb$)E| z+y;k*XcK!*YEs3ZhYe3T!=r+Z31NI4;)NO2)e(q2nYz9iiVTZ)bN21$+jHZ~W!Va659%j@{r7%@{ zl>xcr5PVK^`7vp(Ri|;R>(gUtukE=TDU3VwvdnHykM>8>DZu|K{DkEsM89*lG<+h> zMy_j#Co}lXS&v%9yJgPZb*|xko5!7dkjg;m{=Ria=N_i-%Ur!jnNQW9d}3zo$<%?O z(fnj>j6Lk`GFeBVlXbr=G=<3qloIi$u`IG8KjG#IAf44q-+Jd$Nm4628|UwSIg2M4 zCl|(2-#5gQP0`o(w!baOeCRAFke2rWRC)NKZZRat$O3qaZeSep)lTjnGM*sdUv+MVF;mR#u z75oDYtcnE3t&W60i{~#(P{mHJ=D@@A9^;5=YnL#&CQPoCy>S}z-rpq%*66X;ssdXo z8}tp+L3h~3$AoQ~9wx3a8T+zPXvFMDaV*6+r?;l35`V-5p9;6l@|d+<+@pd7f-{{` z-yLVLb9x5id88hm@<*y$&85T%CQCUC87FYm$S$fG8u6gG;qfp$=Jq-y>>RRlM9vYX zo52@c&5SRY2h8)ps&!py3#+6D=cMs z;$&nYwgeeo3I?3{b>*XlO`)V;v*Iv$!%KCsy>F-p_3K@5jxc#MqY;S+t>?q!Eh&Sb zgvr~SDI5A8Mv_#fH}$xshi2-14~>dD)1fn5k(U@Q-Lqm1jz%)=xiCy24=KY2gtHI#`=d4SYLJ@^g7gXu6B4G$%t4=l8-n(4v%*D3n> zhUw8Aw(v1wjnl)F?fE8Ze%>m}^GoDh;?zs0$CN$zq0LR&q(+=Wn;mD_^f(S%{+O_g zk5r90=T|%?-pa>>t$Iw@>c@nwc}&>a>0x4%=hr(de6Zfo&gkrpu_eV|Nt^hw>xU0E zIR3WjIf^vS$F8rD7BaI0#-+1_x2Cve63(j>a~{SXiSK3mED)WKy%utkRowYQDP=b& z-a;SecRFKn_EP>;FSr?X9z7KQHrjGEh zAZU17lD#j)z7nEPa$z-%c44)F(e@x=rx>N8c;55$P38^6gB9DjSjXT zc_DTej<+Rza~>UXbI8_8NE8__Y;y=wo~Y>kaR7RM+);N`N7Xrl7h2gWr}5%Kt3fNr zlqJtWFZ5KR#~IOu-f7FA9g0V{DMm9#hZ*sq& zx#oq@|5RXSmS*88YGqFAX>Dtpl#y}HeR!2PIP%>pXKnq^;Z=B3(&<;s=fxf7tU53` zVd!<(9Ng7&xmO>i~3jil=_)`dw=Dlgou%pslc?W(AEW7OdWBoo8R zDvBp;7w%ethsn_8i)C=UnPmV85mnMMK?G=h_v9c|r;H0ae-}u)hzy%+ z7lkicKy6c##0)#tslJeDl~D9W>UNPbzgQg+7H>@{exVz^9KyK^ioBjw>J6QLZSMxW z`R2Y4uL35U`zr7J(+79rr^?&OH*oA|+9|>C1!BmK|&svM{!ASV9RQ&KQaGr|bkf=Gu*6#4Z)$rjpAkY`Bz!0D( zz)9PIb;pOcRk4E)B&n?4zZ$_Sb9@v<=UDs@ZLATTzw=KY3?~HVZ}E{YsyNSw!4@NINlZ+Um4y!`?2BwH^9@JR`d~u1 ztj>)HP(k=`T}D=;)LJ~?3tAaen{|8<`HUIN7nd_C)p+zpWH26&?7!x(6@O$j_8Y7l zF?HM<`NhrXi=)67w^Ca8YTtdwj(iBVVu3nM3p;;HnL$!N+idkwNpz*Rk z5FLKYi=XOtLwb%pPA0rhjk}RFJ)OE~Aa8+BhCP-qlpdQSf%2dlR8w^7A9g?TR|~rr zV_#vMQ)U@=4i+q5?~!?DGpi?9qcbc_xqvy1%T?7#c}rw{x0>c9m_Mv z3LHA3J`6N6tg_(D7}WN4b7=o5dj&Y~)|U`)P%4l_(lj@?xGtjkqN(zt=qjN0;q*-Y z1x~``>b~~pgaZ-H%REJP7dJT-f`Wyhi<>LsZ4A&kiH`E@6Mw9JE^QOz`Y;QG;@0fZ z3yaK59W>1=-Qu(W?DZ|t4X+5poB4JcJqOMeHgMc74`>F4TXrC+q?KVcm#uf zZJ6Z z`}Q0c76BsR~s(&hKmD{5#Nd8@?o-s%+Y~K?Ir$=0AqVH0Su~Q1mhREnb;sY zI`WTF^XghfQXj4jhXLEJ=`W$LtVfTGDSRTs#a{Ub)UooyWSEjlP(psgE4c+uwm&rcE?Ls4;z4X# zFeDxK2%G*&_j zi`a)*G4(k;7ctl=go{_vN6Xp*=CG7xqKvyP_o~jyzi29PqKkJ^LklRcX}+Ya<-5B~ zIo%U5D(vEenZ=8qu-b^R9|+a% zU%V85dCZIooQaW4gp37Tlie~|4TS=Q zQxdb*zZDCHLDMwyH?tXzBto}NO^aI)bs?2-A`2ARpuo+U(YCeK>bP{0vCC05n5idUp5r;QM$aPP5Ej)K_ZCFv$)Wl3F zyyk|$MtY9J>_bAtx2f2D8u@`mWV>oEqDtia2`^skII$HS7wZMrGXM5*B?3 zAHh9gKr6zE*zoE5owz$8ta#=BCGB0{teX1x|FvgkpEKvo%sFRH7nRDS`#tG0T_s6U z=|+YSilWFpgz8B*m+m#q3@V{}N~ID)2qE`8iIBv@Bg7NJ|MUHuFtX0g+z&BjR z1!}TWpZvi5wvh$g(0E$Q};KX~nbW=4gK3Yn<# zn7M<0YqRn0FU;b&|9Dl&_*z^l!Ph#ic*+!{Yj_;GpCLc1G0p5T%FM=Ux_d$>`8y-p{tgr<~&g-2w0KLD?~7V&oRqKa~6 z5#GAyXIIBK&Amx;QFRQ;rO+{=}Jhno7bT%XMJX*D zVsKUGO2Xf&oU|--5tSl=!3FvS7}|+EtBb1vQriAaFYaqyNuO&Gj4$phd55WIS2WAed3mQt)3DeiJ((3`I$BD^ z1P$=yR^<{`%J#;ZF07PA36xo|8E8ver6=P~oz%CC>v#QxlCw2`^I`$8oNjfc-%f-bjD%Y?YD7u&1Pq1>da0r-d+QB=>Fp@vEEBh7_$Di zpU3-elWy_$20`!{M+q0o2>u*v0NQ+&fSrAJE7gzr@!uF`;L*ysGyKy@TE1i4g@5GB z;}?Mg$>>0~HZs_wPevg@5U~yXbiB)S{$Vtba+^*PA^0opo-6IsXN*Pc7ncYi5V{^F%Ps%`v=THtf z7PK~~i*UK?9`RT@vB`&In;IGJBV_Wkj^s2#%S?6eUC-mGV+3C4W5HRN@exnnZyb_N4L@`>PyaU@%vM|lh&0zRHc2Se22pM7j zSkK)V)Q`z1hwhK0e-D^LWsounGw2Y+uvkNnh2F`9-Gu_%CEzqn3c3rC4N3+tSobZh>}zVS+J9+!o^&M5{_b#D^dvEH2gUd*~%@ zGw__~1UAD80fRtX|+hT!Z`T!w( za!0gW$0Z$3;ekPf_+#vrhcH8rN%_b)`vZ}BM0Q~j4NI^v*CwtVxCJATzjZ8lLqu5X zVEHJoo{WR09CT@TkfS@+Kr04=L22hKKGwm$^yGc+j1|QJmW;4rq=S8@O2NF?6&yMJ zt*r{*QX=5xf^hzVYuxCv;T7{N(RR2x-m4}>X9@!6Mx00~`G}WzU>&0%o6l&R;bOdp zeI|p5+u#S$IM(Xl)ih2{P2vV?1wpWa#I^x05BEudf*su*s|EuyRLS6={gGYV$GIKS zV>>!aTfUEZpGtc2R(Gi~dAk&5?nAo;H5JVRTwuX^c6##R^dp!6pE5>>4J$fce8zh< z?RiNMNkEoHYr^u!+l%PF%v6oRoO&*WcNw00S7PYg$uI#Q{DFY zh=T3gm&n4KB?Z#WFR3FUA7@x1Q~pah<-bIx{FgMLf?Vq@X+cpgxt3I?*otfMrElPr zA8IfLGI-nr|%~YOM`ty__%4AmKy3 z(Y%tGbeoOnq&qQ|yA8p|7F3Y#%&uW5aX`zQ_4q+=_T9hNPX5u*)FmcDbq>pw%G$hg#=s_z zeEj%M=}DtxjT-|szLT8U(iiLO@fH`n2M#K30+8>8uhmySaj<3us*T_h38EgkZ$2bf9kSc+Q%9F}?NjX>jO&*rMcLw7$1p6*# zyP4rab@{>IAED+@+$?87;jiKR9*Ky_5(|0tpGFxz;Jx$bW#c0kOPJed zp-A6%SgxYw@$NXYbIWM)4Jk0~jRLcUDT&EV_Eq~XFF$M~+Xl=we>6-qPSA%)U}Suh7=eB-WE@p`HXh8NN{2Y80_ zWA4RM|iHDe2=J+xv`>H6ueF^m?csz^BXnJQ1$zQe=dV^)$6b!4J zWAuAUqz~60wp*}6FokYNhVshI8Ozm!Del0xmS_!ZmEbrZ23+9S*|;ALri=T!YFXTu zQz6r*NWa16U-}QLZR^O4q0+^NEMsizK6dix_)~UO&XzIkc%a)wx~gRdnK;vNIj2u~ zxG}UH1IYWSWK7^-*c*v@$Wbu(na;wzydrA)@`{+&Lw4=93+`nYStx~PIs1a;O*o31 zQAJ)BBj*~pM%2BSsS7WYaN3@=FFk2c{zD|WE~8VL?Z)pR;`5M}GN77KM|_rH#?9h4 zJ~EvAUw`O&AU1u3Lp}iS{IcaoTvKBtzsHC#V<6%^B(;OQ;7~@MU&0WL_gU-(m9+D< zgO=0|caZKfj5{a|zm=f#w~BjDOLR_-BI#!#N%3ccK&GV&klpYJa+p4u=jWL?Jc!vfJq%n)jiCpYIg7Q0xfd`mZP(E=5k5{Ca zJa|0_`_t3J`idsDlVCFghj;-k(;sB-r#vxo!R ziWMpPN|(+_N(NIr%gf>P3cPXwuhK}l)3Rh^W-f;U&WIhE1l>7S^vY)n)LCU`Hb5%a z2=Ph-zh~Jo4Q?*rV^3j=NTg%~dTYxFE&wIdTG;dLqyhT7({gQ_E1Fk2SByZ&5``^e z*^oJl!{wd_{F3yVH6^_QtsGle`HAYtKjCr~goJ8HW(F(x>-5tzu*-)%jc+!;c>WDLPW84yF2l1ZVM2MI{HJ)lMNB3nP$rN@*EeHCxg^3JAQE8vQ1EY zvtvezx|Yq3Xlyl}vXF^9MK0HI7mf+R$Ve)2ZeV5u=Y5%`qjYGzZ!@z&;JqNZ1twX{ zU<)_}9c8Ga(9j(>ZosWbX;IaLEARj^?SPcC5x4EL8+(sK#d-rdF_Jf+R(6xDU~OTG zjXXk4S!r7Yv=SrbkdK`PEmvh5GO|w4y#&RVsUl4*@)sqU4sl7Bzhxqpe~rQoDtiVk zv?$<@^-#)=Tq?nDfvCGd+0!2ZkG;Xb{piYqH)ts~^+xVg?fTVP2f z%*zF3aaBQ}FD*3lG`PYn8;jIiRlqpSvHpzP%PWgjU2O94RPnK=de~Ds5=M(XetUZS zHdz%(^DsJoyDd*HV)lXS@i*hV9p@gyaTfDjGNX>7Ht9RYMOGmU1Cz0|1@^ZrN!?P$ z0O6HO$gx>?fx&)UEKo^mE?#&v-s>b6t+T94ToSC6JBLW?Ir3By|3XJy0GCs7Juq4s zu3Lo^&_Lno7s(`Q&RXLBV9|THkz49I4y+WjRY30C;>8ovp!SvS>sX`G@Rb|ljk;sa z2fxH)8wFMtu_`Sr3KR@VUm&}q1%aE7z@Z4qJ~9h!Dd3heaM47H(5x<0#fU=2K2V%f z2h-uw1mks0_A`4llx03O&_r8U~}9$d9sX1fwtMMD$n0uHe5jLj|cEss%7D97bh1|yM z!Q5ZHtD2PF*<-U^xZwxWL*md(T2+)ch5#E;y1#7wk?NAasKc^;4K1B}1*p68TObR?_t^-mX?T(H7WaJDSuR{j77@hn_@*LN(cPGvtE;H;T0~jJP0pJ zu{Y?}bZi#Qb5#qT&7R1XgPmu37BarK#8f^Hwn(qRY(467Ffzc;H5b?ct0auA2jz{P zt0-$O;KZ42{j}%>%0ZjhBB=Bs_s8gzbF`WW2KFb#Ek=8zYR#B780gbB>V#Ix5m&P*+sb8;z_w@fjbzQZVXZ#rm)ecOmm`$acJp1Se8 zjTY^pHc3X!9Zn{D?55{zf*bbd^Rlc1>=|1qM|z$#tuj)dS15~mu}83*wY6tXmkFzR z1WJ5yk0@hm$|U5Eu@cV zCZ=qsJ|5H&qf4C89`@%__fuZTg(4B!;}-8tKIS*JK9e;l)vI49GYIpx;rZQp9LTd= zV~5=IJoIDIRn6OkkDP@Xu72d99R9^kJ!;9mzhR%)q{*4B@(hn$lgZsZsv>3PZK^0Y zMgDGtal*?$a625!gRNg=qw&;I8hZs}8(${ey!~=_68F$I$?0_s*!|4M5M~B%m9Oij z_hV6g&>t?4;XrNa<>&9SC3QiA^nwNqWLZbl4z#~qpUAHL3B-h5JAa``u)kVs3uZTm zm1^b&3$iM7y|mr@ zF}#$fTJnim#RjT)4pPB#MpTTr%jfW^-s!0_!Nh@DV5e*fV8Wi@8kpkZupv#fnf6X^ zEg8HN7fbTv&TsMX{HeoT3gaj51s6z1;56>Brh-#}2~n_7wD}TbjWpfTf!hG?fG4i) zMbX|Dd(0Y!ly&%hq#Y@x07)zm{k)7nA zZ8LPgQ^amYnA4S>2%i>AT6>D)?gjR0Nv1o{7(i11YHZ-(|K{_8CSjj#>V6B?F4@au zhRu4-jfb#}rd;s`V{JQDk_Y4Tgj{ZH*#QmI5&UIx9I;V(a9_J)>`0&1`xs4mI2AonDH z;Lfd{m*J$WNkvnnrTXt4rWZ6x7as|iFt&FKnz;IFyR%U8N_=E(7+Thlk}K&r!xRsl zL2n4kW)UTsF%60=*9HcV6(b!?y0b_(l+pxKD8n+Ph8I24ISQS;^BLK;pbpN9`Cwvd z{LSu0x7`PD%TC+H+I{r4psA4#cl^z)zKD2PgOEj-aRIf^c0uADKL(8M1?x7P_ zQ(iCaX5PpX?dkPpK@`#8tS_r6dz(mRYs}N@S2_(;E78HD6E3asFjq%vvmVMIS01u; zo6T+G&58wc@LReqcns+ z$ZkMELwsyM9EwwUZXFjiUU1u(ixw(Mc~5TJeAoRkN#UCfQ%PU!B^TD^s2u|bX+%o; z;<0HLNO~xZ2_8PjH7d(y@e{Vuf(7fd7(@Azs|Fc9;h{{lT}QRAvAG!v7(LtPJRA7` z(zBqvN;E^BNqc95VMnC6s=yP!RB7{nDNpQ`j_)Km3s-_S-UXNUu4iS;&@Po&|8)Ng zY;-4enDxEBME!UL*$j_Kh3jCf+GZ=YVT`Qo`adXh!i9r?|7sAAm%vb4Ya_j32A7S8 z10^1Ye+T<@Y#0M~vdjm|%){qpn2E6+N?qqty zHu+#vlpsHBW^CiV-D8yOAXGA+W1eHq*~Dizb*!&q+)a}mw9P&rBPyvP(@C}piO06X zqi&FLY&ObT*l_)i2o{t_P_Bl9QfDJ*Ps$~(e8Lg5aSNaSd)&h3!f`vsL$N#Fvn8@1 zs2vQ23k=WO#M1L}gN^st8fit5>Mx8p4`m*J81Di)Yxr4tFj%CP3Md zNrasURb#o5Eoo4mK2rSL&_LAIa|mH8DX$Pqnn=?tJ6<3=HQ`yRR!N zXV3wU8!waWHZ^Q2O&FP;`UXr&Cj>iF->_xXb<{33q$NV4z=gea7Yu&_$`dvKJ&p?i zPqvpeT5izfT(KP!4_uMt!Ugj#L zxj_)*GJ_1-!8OH3Zq&c!TiX?HTyN_xEaT!r3SXdar<4wrPbsxx9t{2O0|S}3f@_&R zlE#gPWC^ELHh%0b?&Z$-tEe6>@Z9`-%V8UmvJ_Vluwb*ZkdcQ(pe$tr#_}GtoOV|d z&VZA19%17l*%bk|!ROe41Fj=wDnZiekevs$Bj=4rI6aJ83HJ9>hEw_Du@PiB%ff5I zF>@-rL5g_{a$iSA0V7B<+aT2>$BqiZbmvUP~hLaE4mSWJ2dR$s24y36s zEz3@qu9WQ!AtK=JHg;**vG18Ed4i?9bZPpj88)WanGheAji+YRk~)Nw*zckAaJuwp zx@1babejZJwsmxBrH4_DO65jJ={C6=pPp2g+DD+BaO#2Xq`IPpU9d5%k{v_G&1L$O zVpG0_HvTyO)WVu#rgRfS#QArrG5p1GAnQ))rgW*D7D<;D;3E}4j8~Vh0voNZ>8Gp_ zY~3ffXx(tUbgQJ6)Dha`4}L*`!x$(?(L3hENz&jum+6v2R7jT`#wWi7H!{WVl0))! zn5Ea5;LRR6{df3ai@$j$jT%?&9l{9?Qk;pPS$Fdk$5>(rCFY#zDBTJpL98UcjI!L; zm4F8^O3Pvg6c+af6aUA?ngQW?oM69L9v7)D?O7 zc(W8I2FVhLa&P{pO_pt1M{UJTrE-tl7zm|h6Y570eB{TbYz`z!x8hU@8Ubm_OHh)J zBHLVqOvr+gF3q)`m8R0ADR{=b z!g=^rnk!!^j=f4hPM2n3Jr*X;tv)8uOXYgYsb+~P-aIOq_%UYqRf!YrY`b7$LM_+% zbK2ilkdHAcD5q5CQ?sRLjhe_+p)5#batutD$_oO^J(XuwOIzSo?C^Ady0n`V@zNgU z=ex=I9#t6AgXz+~=?RxnWnj5VUr`x|_xQmC`c(0!v=ASP*aS*$F4=IxG)}Yv;yEc} zvcv}6v}`6^#(PeixWDMrS!~3azhynnkXEA1_3-w(S_@(+=9btX(xjE==%4aw0XL0U zI;VnblGwFMMZ`XnEvyIFgVKhdZDVL0>(rcC1eURobPVq*>YUV?5IBCFvO(57~zRYA%AR5OQY6*)}w@-NiPqk;{gs zH{qHve9enZEp6hdA-(>@MwiE5BX3&C>HEyREV#oM*h(pBNZJ+R=Tta6z>B{0FR zKG-4$Yk;tTyAwySFuPgaECV}Y0I9VZ)sMkSZ`<&D|I_Od+}+ z2CUT8h|=NKQ0Y*c?5?sG55bHx&ah>7Er1&*$#fhV1-G}R%@0>qbb73Ufm?1#VsDZa z$mJ=X6+t_B>=2Xb3|)yurF5CxQ$>xIrC#!%&%$n=h+kYAmfl1O-FfvAJFrsL&lA z8K`VdRtnOl{v3({r0nqE1WE->kxQoWJZe~@yx3hkp5lwdJ3LyGml zgu&bcGk%r5o|MO!VbP)<)N`nw^zy65*tN6id065gMpzyAl|LIREst4#%hJgLzIw2& zMonZ*$eM-jQUR9-T<(=fui*ehRytv^n~#qq8FFUL5`0bYdqk_i8M;zlVbB>p&){}2 z6Y$<&gd`#B_^?h)LnW8LQdylq^u5D#zU5LXki%U+?k=mv#~+Xz+tA zC{QZbhuTKH>%prPyZ{OxiK^?z$~rR3H}tY*5NA$CTR6l*_LOkKr$09=)MnmI%Z7iK z&p<-Iu3U0$I2c^9;?5jy@Zk2dn_b+nP1Y|40okyP1N)6TaoKDOgWQX)l-~Fr5}e_1 z>CT{S+<)xFEF0vfdgV{c&!3hj(POcCYDi!SiSpD?{IHiq7+~~*4(ryYDKyTyC3oyF z2H;IP)bWf89`=^Q=5iSovap3c-ejIpGb00g1Jv-dYUDq>B(nyZr5e(YNPCc3qo%?5A!Ye16qd`=(|;aoVMK=f59spMA=6h3Bto z)2FpDFWob6;;im%&svhzVt39X8Gji3%P_tf^w-AIVkhCpZ{De^MJ44RQByV`F+RjzOx`BFD7C5i#fD+p>qw zh&t&t)w@jfu2a2Rlud9_+{}Zt0!Q3D<)nmp!JUnmSDh3$A1eDyrSBox5d5OdKP3oX zN~M~R8sUhTPWZMi_Rfqs>1<^eK(cWgqS9!UCaH8kBx_}!N-H64xiJnu?rdw*Dq&tx z=_#Cj9!JCsSQ;}sDecAgejEw&JHGAN?3R{AOdcc~miv)=*0*Y4R=ReWZ<^p&#I#Yi z?Q7%MW10(Han|Z>X%kpE2W$Y2w7CJ__uz<|CHQ`l>@TE?W$!u`H=jTff8*wWV-fSc zV-fRO^|krP?YrQRzd3-lkt`WhXxv542I7dA5vn&D zlJ$42vPmkHK(dw}S81I}xDyMhqBqU=IVwfM#${dViwAJs}ms~~(yNY=N8kZf+X zP-$?hz{-7PLEPHv*|SvY4`~FBh?$IU$(e+C2-0dCrRI5j%fHHIC!`N>R5J(g{S%Ie zd1YJFrO==Fo!eSi6@1&UG*IbOmAXPY2S>u(QY~&Shg53D;#>ZedVy~dGY!mInF-0} z$3u=K%%hNOiCyJbso4NY{zc47klw*@orXSQesFqm^B+jk3Pen1o4|4cl8yQ6t74`; zekIKFPKucGRPPE%HdM{DrVKk0WB3wg=f%cc2aSk%0N)EKJ)_c_kgVIiDz$ztXwAM+ zmen>0RZ683A=#Q-8*6~|*0*PCWl>6)U%;{oC55WtTcn6-h;Jz`5z`Wqb+vlmCt=1y z61{}!{zew1%I1D|wz8S0QkhDRJ1JtS4o2^Tyi1t%U^Z?$Ac=1^%^pZU;J_Bs?Q%>r z95u}aDveNSj7s-IdK3prF}@`Y;^qrTQZ{OuLy*K?#KhVMF{%a0rpu{Ls%APvlDbgM z^mkIkT%u>M(X+Ry^beIvRC-*cHBNdeR2QBDlUPLZAxWsJnM>}Axv+et zdcUfk-yyJ48IlciEtQ%%DPdl{CX1ywVb-9Xl=7S~9dOo4=cx2z|EMeNZ@QMNnz>l5 zjD#dLG+}OYQZ+Npovmi(sooMu8*o%J%TF~7Rl>Z3-?j{W3CX5-TUXysHp|e{{Rkd0 zSy~6DyWTHi#%e1%L!~7u?NjL&mE!8QK&3w#1Z`(V$Do&tzhzs|$|eCO)qaFzx=49B0pB*?YC^KCkxDI8DpbAmm0jUj+>C-`<1q!2jYo--B4(M>Yh*S+ zvSspBNH#3HofJ3UK(cOsS1ERSz^XYZV(LS(VQvMx6kfioB zGV_(KQ0WDgcB}M*O0lliO2V7~$@WZ*oP<$0Bne9+Gf?%eRlSLjY=1OcrAJg++RpY- z3G=jLxJrOz!H$6m2QP(E#D7mF^-6N7T>mK zf7h`!+Ak!`0WjPD{tijpCQP(jkOpa$PJ&d3BVh(9yH2Hh^z1TaTUB~n&;F>)KQr*- zWR(h4>Z#IYD&3~iTqi}$I!HFmuj$z@RQf}u{O*BowN&chq=@MS$&LgsgCzFqnURoe z8+Hq%yKvMq(^Ps$rOhh6sM7l??N#YVC&kSlkfcUM%%5m2MF+lI>S( ztJGMf4k`^)X{eJbn>!&%pHkV}tI`~mN>o~;(k7K&bW%0*DkR(5z6Z(noS#9mBlCYN z``t;+OsYqYIT=Sw(-Gf&C=GW~Gb7iCa<-Yd0pB7e%pLd^%Pq|eNY=$dCq>L-knG6v zSx9ot88JICwz4h5dtf3Z%(swiz8!&NuRr2v2Q?tAQZqVnAldM>Qt1pQtuVj+JI*Vh zHl{b&1vuK6Yn_xZcR{jk-F;4qn0b)w74BnBN|=q1Y&-Oq4?~9vvasFrNd-wuGNv8+YtdJ$tQ6x2yDkN~J2TR_R$M zbv3U++JmF3`Nm1z%^#4GeR529QybqcaC9}xapm8e(q;Iz(ydO4n}0yE>3lyVY1=Vh z;#k}~snRPdedMHs*$+wTQCD-q`e2siFqo~4x##AXDmZ$Y2KeqkX^=|SsdT?e%T;p7{i`KUtNjg?OavpF*mk`2{`PD+^JkZc}}fg~~SYNkMv6pEM< zNHSN@)ja7~H8TpcjJCYI0_IAIldQjTwzK&T%$7Q@pOvbaXU@ZHAj?$_m`GjCiI8l5 zv~yCzoCV2-w4X5cup=SaG5S4v_7O;%ao}nU-!_&%LbB~kqJO|nge3L1y?Hl3ZtCGz zd(#8oR_gVr=cJ3lq(;TfL?=bey^y5UYi~-`#iLG&n6;1ww3HBRg=9nUA|x4S$IUyC z4&Z2SG6n>`Re>aav^S?YDPabybhAnisPrHt8-gbxNtkiP;ux;lRQg<{ADx7{I?(!- zFf}0A*1NT`&dSbLX{btft5l-W3P`qf-0Y;ddB&ZMm=7FFm~R|QnC$a{5LAX_<537n z;t?@tL$WR5U`RI4M?$iBeg`D!t&*`|mg$N}a1EA>1yaPUm}=YXKIQ?qSd62OS%>eJ zDSe{SH<0YvBPzwu4_FPAnyb`RrQRwPL9&*A!@Q`qH&V}zS80yZiSnzVb?NsQ zm~DaALb4OS~gUrJ5-vZ z(pr^vs&v3feN1MN4FP5^A&GB$V$C7h`NWQp#NYPj97s}fx|>TNNoqvQwUF%9>=;OP zOgJ8rSm|T#gJfg0)Je%$FyEOlFM&y`7me?NWPLjT$;Q%LXsuKToy`ZcXB$GYDcMcg zAeC-aX|C!mgJjcche}_n^d}@cJ6Ld0(3Z4@I~y@_XT|2*ATXOkBiz}9xz$P4%oDThjA6u#SGRK@ z*_Ln-Bs=%86p{_g^D6CjQo6!!Nr_093nAIkc^xEMIww0R zVir3+alfmpy%Lc7uDJ8+rOZc;_)5N4l4)>57Ha+()>R(v59b*VuD-H@zT^%U`=}4WAFbcFfRgnvxNtprA8AVrBO>dIBm7Hk$ z8mxvg#UM{mr-m6o-=A@$5ciY^nenjmj_TAiW9jM>B?uqMpCK{P02}%$L;Zgxrx34lzaMKa9nPPG^j{*hD-D&rgo{=2DaJ z7D21RnKoVq?kZBJx+6nCs*yBsWTd&w6p*x4oe`!z$r(znHQhb0AZ5;V#5eyiqddvM zAxfs1JLu|mM+(g}a~H`}wX@i~MAGk&VZ26~<>o_@xvI0?{7Ledk_{#!0|@g;HeN;E zHd8A@;=WyVUUcMLC9j%})cI7&JLXK1@05IEuF8-S{<|Z-`OJ*YkeH^rTfaQ-OLIM4 z)paC0@uj&XL*m}m5#JmzW2hr{`y{^(WgjqONzPN9ugxPQ*D3jzSxUd|ab&wWWHx3< z4$gPPm>Wda1ffdVZ#)^R-GUc$G6HJa;NN!D~%d)09;8E{RE=E>Lo!*DhZyuMUy+ zBpa2~_WIkXvN=OBl03^8@R^YT$GO->Kniu$I3cX9rrlrfD!I+HeZWJHKu zLY+H9Zck)htH-Z+vXb=At7lrL#GSVYe6&O`W}ttn|+CzGpgr@5m1s=VEQ$Lc}<;XAt-Ey}D_MR}DvebG}zU zEveDSks@!1cTQUTYNa|OK@4Ki(UD^Bdhc?$O7RY;q-wEugEx%im%=F1S;$o+58z&F zpfiHLpQDy<@J6P+S}Aj(BRjksz3UmCq6e(2BJXDJHj-{yeq7=Q&4YRBiow5k%@~AB07SF)urEQHWrf zA981eBds!C_A*JvI+AZ*@v)ytBfk0Fdz#^S$dO(d@r2Z^0V5Jvz4rKtE<->si!(`IwC7g zYzco4#31f1XUEJi#LYJ!J90OES@Lz}6nS4~{6?M6Ri|Pm2y}i_oywVi(XT&M=R^?E$?0RmQ{>gmtXw%8q*_2sZIE#w z4IS}K=ggBTO98iSt^D|eHG*wcR`6TUhR&r71Gt}v!WN_vyBm*58nt4g)Yb0Y-=hDm%XlJsL z%QAP9%vN$k=1(NcLgZJH7nMxSJPKmWTOsln{rc3Ap_vb4W}P5un9(GaIR36jgX zj^vr?nH5Q@hDa5$R@r3~{~jue@PGRqjAiyZOI!pwzqHAcze%ta(K9NC$*BRTMiGoLuJD@58+C!@s1U35B;R1T5T z8H;~8S3Y#kr0+)@*_ZiK=G7$00XD_N=XJfawIc5 z>mib1s*|1dFv%Fzsg(5;$pen~=7g-LNlG2LFsCr<_bO7G)~QbWEU&7B=M6^+vQEp2 zkbDy&ZAhX6Z7jxQbj=!ERZ{*$M=EBWnN?a<>U%Rsd~;@28FjiS>6f*ps-%2>NAk>o ztPLP3bEPBsAe-sxR&_NXYYWM2B?GgbVR%+JGCbqFtZgJuJ2EHZf~*g!%IHhxwO$AH z%|%&%($44j6_AW-68GfjM^;Fl`7CQcNikN*ybV2!{jv^{ELE}} zBuGQi@y%Bti%`1{;Fk~s@*Dm7*^!l5UuFGHa#YE|tP@X^`W`#qy4sobO;)oLV@T(U zjue^ivmQ86QuSmHIJl`BfdGD^#Mso)%hiB4@q}NZuXC4{dA(_>4lCo%R7?w zCv|RgWM9^AS?TH$o-vO2=C`cM)y4O_mBjp2B=Z~@o)Pn(s4i*vh$BT_p8p(mRy#5$ zqrj1;9od;x$3IA&7aZ|TJ-==Zv2%D~kn#=uMkG;9`KBNei+AB_KaOm_g(De*0&M{`2Fq>--TlrOZzPnTw;yyUm|USIZso&3F)T z^|X?E{COlh92uT5$zM=YYSX)_GuMBbcJ?~5-JkD2PjXO6vHvp3k4hH!uag{gWS3v+ zzey6k$cAv2|DgXaN!pR^{zLu;B-I_kh|&L;@oMNuk+<0YjA3r+$efI)KqT&+K;)lq zp7wK3l74U`h`lTHwx4&BjIrl9Qe@utD^lkf)p^f98P6-G%s!BR<2aQ4o?rJQNwMD? z@y+{w6S|5IwmQD~z;AVu^fFaJ8siYshORm}(kb$x--Tqbl285qB>zxyz#l@gP|3gj z8%VY(`OzOs@`;ke{sfXgmHg^IKvL;q8=g*)c;tDKR*v{45qXv5Y}H9d-eBC%Q=KZ2 z~<>Qsw-OEN}D^~jGWEkcSv0I~>&F(*Zil57i+KS*8+k*osAo!yT3a1||(l>b~w zK_r{xPbIY?xg?28tY5x4Ig%!65F(WeBs}dvy5lgWcBE#3^wd}4*R42;Kx!38o<8h| zZ|X+s7f231uB2h433WDyNOOkyEsz5^eA6h>l73aVG;U7Dfw|zwsU!nJq!r1K5NSg) zB18&FZVQo4^nHpW;@9bn#e7GKOsmK_)L90y2?zSYNZ*3-h}Vz!l{rL6`^X@YZulkS z+GNMbrF2!~NLi#)$3<~Jg@ zku<-ooQx&us-#O~0?9xnr$;7{j0%yd49|Fw**JDbx<_VEXNMy@K;}?quj=%Olv3v# zM|MSeM9Qf1n7KxSO1Jxs;8wBTJaejq%I28$BbdsMFVxBGWUno;tUvPVdMI z^lKi-A{@Ty9eIi5;$<;M21ItUj6I<`qat1{DXY&og3)axqn4D+$UT9pQISlNchuGO zk-S<`R`)BpGt#z}l<@BzDKd9P3TgQ-wLBrxl_YtE4WTg;B0XsNWJi3Evq>5&nI0KL z(kVnPq+bIZDKaII>#1{@>O2^^mE=}O4rMQk%&#T2`(D*q5qX$83ze*itRUH}WNl_R@?l4IM4pfAAW09kG2In;CGs{&V@G`RdgOhQ4ocpL ze8N;cSIOIvFBqQ7LgZ`e+@a*X$oC}0A@VEJd4(g!ych9KUW8oU5+ae4C70g}xvEI= ziINW^Cz3q3JSYhtMQW1#Y|1;9)WGM;pVL_?;EYg`e1tHR#q@$8ABZEo$DETUK zDan;ezKdLQvZU%HB|k=PBq>w!bL39CTBqc%$W)T|m6+&#jQbHsa7QgVm&Cs^D2JKR z1tj%Cq=a@lJL1Dt8A(q^K92g)#Uz7O$B#Zra+i`wbUDd1M?CDCu!rPf)u|Evlw^aF zg6IK~mzA6x{hCD94A|OTC;A=92Wq)-G^@7c>A%%glW2q_a#fH!r$qBeYAb0SO_EIB zALMf1Xcv;XN-l~Hs4cnNQe9mfJ)b&VLS!h(xk`pcN0SUwGCX=S$rvTKM5oYlsgeoN z`L(5ftyFSvbRBh`36V`CuPeDP`YtVhresF+H|qSNWM(v0M`DpXJV=e1(Ta7XjGYuB zC)N?)3zd{ao7IuJ)5{TK7Dijuk#xRPbrwcj*YV(LxFeqTMD%>>+^ITiqZiOG8Qa(# zTpPWF;hC;FPev=%72lUDSs$%NA~P`7)%s}7x)O`$LOQicWJOS*)4-CDPUE@~i^GoK zo_e%tU5Q0(M7gVGBo&oxj<%z#1|aQm7_&9niQzd9zii*MHQKqZ^j~8fDT29(;?a{&XJ#w{mWz1{QOG&DR$Pl_};)oBO%js94 zlGmeGl5`D`t4Rha*%`ftc19|BD|!=k?pE?n^fv0uRPsS|8p*;Cxu4{T5Sh*}zwU@J zA4F%8>UQhu)^ zEu1Ugd=cGNS6rRxNRjvN=<{^dUv;j@w&Y4Bw`A|7t7^~L6=g-<#O#cEQZmP=&QuVw zEKmE`@J!84)RS`fpd%QygGf2tq^=&wPLjMHA{FRrkGh(XU4`V^5UE;kJnS4+S2MEf zkYtYxO5Kd?77SqpB{Q>6r(bm)Ig~v!yBl>{sLrhHo+M|6NI#OnAu@nul#C$sT3xydJF6TkOb6b+tJAHtK9r@<{d^#^U7=SwQls zlE<}N=-hsd)Gb2CSZyp`F{(XTTc@y%LC ze!a_mOzUvl}NlFZoc)cH|~ADd3g+1FWD zNSD|=k{Tg0pRVdV;zMT{NuiRw*gBG2jYTr{3dxzOQzMpaAZgfNNu5{$$<<2g#!hY^ zc`?or+_8=|A(Z)O^CCT&8$6VYS$J&!@QJrS7Ga5+l>{8M^)|2EbC1=OZ zX&@!)S0z1Tedv46=pcQ1#?Gg!8jkp;XY2yHYO17PY&c0_h>RlX0kR3D&o}*IH>3Qc zM1kCl!#4wC6G$e7$UP(rm7E`&O!8ESOe1+GMD8c~E<|RKWLzKkb$)DCgM~=vDvn@J ztk{AE266AGIz_P(>YT4Sm&YEVofCeIxiDWITS9V+y1Fv9hIXbpQV_o;wvl!oQ#&JL zo2au{U5$)wr_MX7b9?MVTK-yfZjb$&uKseQFuowxsG-E7`VBT-#+1aGV9$({Y3qm& z(wsUMsLsQ&wj|etNFiNSofCEOdN|gOWSrCK8($q8)=}u*f>WJrUh+RXn zNp&{FM$*-5N;bwulY9{(H`3LS5V@5kdSj4ZTVfMQswnv+ww|P^lFwq#Hk2Hc)#*}l6hYh6_9Sa?4SBuQou}^8|AxC`kZR{%&xwF3o zRLDWvk!PW8Yy55O>xNR7-v^O0kNwwT|7s{Lz~_$mAm28WTKT<_A7cM$DDAUuWXl5ksy_#`ux{1S_itW9-9rxpH z=qmr_z*Qt(NK!LI+LJU7kzORHg-9Rz)z1;%MB?X?T&W}{ej&+NC3*2nN#-d@#)pzD zQ&J&5ie#gbD)CzyNxgVQN$2=W)cHV3zxd}Q2bBzne@$0^C^;|wJyWB?EjI4?=92i& z)M?@fR`|z{(9Ri3u8LPTN{bNQBdE$T?mXmxIhHzJXq(UM!^ZAheNwB+vi zCA1@BV{2zpd{kp8y>c~Sbtc8HZ7k`$OXD>uKDx1#!|#+#j^9X~j9UX&ljFBgr$UI_ zO43ZpjQB*7t|4*{$$3g<#_uH=8Y1^K_Tc+1N@mArGZr$Avay&OFJVj{P@TE)GPpA4 zAxDb5`SHbcwaO8+;f~1o$J!~0KSn#xs-2SfQj&L+JRDz5A|oQ3VhiKz80N24XLj7@toV5NS^+Jz49bAio8_Ly-mD=lzGVMcwQwTIJeD_B9I5@`v;C-ECM3Ff9;45 zGM74ksZO<=hZ&y4?KY-IbE@SmCTXaodd_OPI>QlTYUXSuxgbQICAm68wvpT&BF~e| z50Mv0)`ZB5B(I0aOC(>0$jc1jACCA4&kmB<9YHJ#a^51Tt>omKkC@J#K+eU1`$Rck zH}Ps=_Q~n^rhZO#Q_1D)962+idCsXM_o+_voHk7*-%A|9iqD+xB+EnOG7`C#vE{jK z&h4}#S0%PKwavM!skAmS*JMfmoCm41Mg2N2X9>ysN(SXDr{(?X*M&K2n@TPpQJo8O z*3ngDthGEm_wt-K>8he5*nu_YLy~48vb(92sIHC_c|&tPqMg2u_~t4h(78-?M&x`( zozbduEr_JgI7f`RKIiMEQk(8mo$GV{q^nXTH|A7sCazYitDAFLHj}(~RdsI8X@z}d z@rWWoIbS(&q)Z(HS@6?Hp3tUym&1xaJ z)4&nzq@0`GLP}k0)v1`9+d}gFbR|`DE47dm>!+l8ZdH;IA#x&#%$V5_*37L*JF=sM zB`4+9CK>Bo4bN+odpcdsR69*`ds1h4h@3+rvsl(nv)s!`WX{Tx7P(g;78q|jQslMF z9Z8*``(jR~wIf@d<$S+g?yW7PMD0?)+UMTR^pQDFv20BH+`DK;W-qN@?Q`!Y`805q z-63}(egD;wBCm7qz0`>x2z)=ok;wSKPQTn&8TV0`hqRW*<~BG*QofSv+?`uUazTy2 z)wJA8NNTFi+}ztwkvu)c5o4as-9w%3s`G5_K9b9nY|H(cWV9nkbKcEu(o#}Gu4-*~ zKFjUfQo=Lyogjpt=MHQse%-Hr9n8I~rIepiwR|vlD9Mvb4&{!do#!3dmvcCGBK_K_ zIwtQSl0AV=URK^Ry84eJ!*jFpR+9J=YzPnLRmppgB<;xX+-iCMB&n^Wdft~Lr#gaF z7J1QAr9Agh(lW0;$q*$S^LmktRnjZ(?o*i>O8Vu^Bavr9?a1N0ya(xOx#|qctI|sR zlDTD@szdS`wvy|M7gT3R-YKobvfLT5I+y3Qr7L+#)RJL&r_t5B>gvk8p48bNBIl6E z{FrrhW!`{Rl3zbM9b>M{8{A52iuVtzQ{-KlH=M3ym5z0FHHd^K?R0!|SKbKvE_1?G zXF}dBbX7-nCgj~ta;lOgc}rRu#I!qz{PWH7ylvP^+L)`9tjv3vIxCf|b>s~tPv-5U z&KF8HOx`yn{ggbHcZlRFCC}&mLUN~) z7xRvi%vSPJUPfz)=`tm+)p`T55L}5IZ|(5*u2}EZ+!6ii}BYrOs^C$xOV~T58-=Aa*Z{%)}cc?<(;VZ;>2O z5>32EvIy4>)>S<5L2IeqzdM~GFF)}Gb#kW!mMeisn;`Qo;tD&mCk{~O6xFGmI7re{ zN%h1JBv&gbNc>DPK}ns&pY*-dks_}_BD;-OkTP2x@w_IE>~y3>vT3418)+fGRXa@+ zRp=^nstuv%HBVG;BYl(HVX-l7o+xM|>0AjqHl0sNG;bs6+|!XFb4sEOb;hYqt3T!})j?74&pLl{~lj>ZM*g*1@lA^@ZBzu$$PP|0&y^>23uQ7z)y+PcECEjIz$y~Qh z=V6IGZKQ?FhmQQiHDh8QbEgo*=HQ6L0p{QkM~cjd#CO!0syZVRzti`XAToQY* zljK!Ld~;pGZ!5X{XNY8zw74&rd%r#rC%H64@xVd?WwA0-Y-%L-O z)mBQv-HsSDCvh&x(;?EAP^JSDLt+@r%>lB)p`9zK(nfVYPYfjKq2yp|Qm3*bzB$nm|BIN5SB>POg|f!A zS>VbPB%flcwg<7P+9B(#dO^{DWk_Bk$!GCjTNiqORH{ zBkiO_9r`Lpzp#!vnNN~G&-(70ZpmuxBnOMWv^qsz_vA_K79knxI~}Z<0}(rCI+E}A zN*0h@takb&Ym?ldq;Ikj$#iwqFWHoqmpXzOoMdy7Eg{m9~IjNaiTHH2Dvb z$JN#4$;l)ysm`$E10;KtT$@}*PBqLq);in%=?ab-kxM0b$(HuJ;?+uCrbj~KThUTr=gO)$qMibGwqJ# znZ3!%bah@xry5;d3NitQ51ra1C63goxG!0sWQ*#2mTW@ufszBsHXWoT`Q8y@zDahY zPNdXY&iB7bo=I|oBUnX{>`78r$xq3PJB&wa9DOnB!gDw|gt54~mq$|M{hS=iG?Y7- zHq5_+i0?APDa4pRk|StYW;m_RAIWP-WOgnf*OADaXFzVGoenNM#{8MQjbuQG+`;h3 z?3}nN^8QRtAsOy;ioDDe2x2kDk#DnOAkxcBas+F!QZuP@ zR6UY!m2`I`M_Q&+C!EYGc2`+;c4l}6zHs>tDcS&riZ6q>?otkD?senW_W6Q zM;TR4ccjP+PrXi^t*UcP>LXhI0^~OwzPTp#F-iR4fLxpUg5jwJ(jEuySEat8&bf~G zN=7KTA@x0VCM&rq^)t!CN^VXa?I9hI}87HT>e24-)Al`M%TX7;{G| zMzTLd(j>oxNEMRU!XV6dq-xP}9Y;P+-;t`{Ny=DjM|@ar)JaNjPbL3Ib)YMm^R(f) zKh>G!0;ltBdRD3@$y+A}VV;v3K+^XB?i4T<^HZ15^0jLDq115d+~bJnElG_aDOR0F zQ`dG1`ax@FS?bPC(*CS~j{Nh@iqsU6x0S3)JwWmwC2LZ1NNOw!T&+!&kes39$<#uU z5lS|s9wV8qgAU=EfzV$2JvPdiDUyHs^vNPWpv-K^y0)DN`tdWd+Pr9|yhvLh8I zIf|$AtY5FEP9XV7b>2-i?<}P*_YqqQu!=6#mZW}&bS7yXB3((&36Zl&E(wu-^y@lD zj-=mDokub?L{YTmb!lfwpI?=Hlp5Mu%JT_Jg1Y>9>Uz3r ziWQ_%-#<=&k-C|rxznj$@we3M&Qiu?U7YA_|9|+p3-Bne_kH-YgvBk{tR%aU&2DVt z?zlSy3-0b#iUx`oEneJ;dnqkY+})kxQYcQLKq>!o?wNVcGT-0(_PW~Z>1XeAX6DQp zS&iYp604BmpEQuq691K0jqT!}uj9i0@!SC6pOjPOXSCw)Tgj~?&l3>-xj0$!dftYt zA}Q$k6v98@CTEI!zAedT6#s;qEMcCJrTBL7Pp`=m?wMMOuQUJj8W&F~&NB-c{^>P2 z6Yp8N6rTzFy*61=JS#%@XVqkB^$-^E}q4DErHbs$7C}r+zwl-bE|^j*pz_ z>G>CAG-djDzAMGgv{Tt?p zv7X83S-)Ie!|y$NqZR+$h1}|U&%Ve^4$vtw)iW0vKWa7Cb0j35WWMJFw5mk1)N=_k zjY-ycE`{_aS?BpvY4!XpFMWgOn$mon!)a7Ec~&dKxBUmDRr!Kj71>Pki)WoOe8f&u z&)Yofqt!zzS-f~t&EOI49RKF+34pgTe_UNvV7?qNOWG_W%*W|AklmIm*wmH zm?WQ9JS6`Lb?F7Xk|5!Vbo4Idl~R@;P1Pw=%&QERm7~Zq?-E{R%kuMcZ$;P^c~yXn za+6AEHQTLKWyn@V^pclX^|E}99U!rI)i2BU*JY9buU3#}BzCW^*wQZ~4zJ$Gc&${o z%i+}r%ev{Qiqq>`Y^jqnp2?zI`29v>yl@EmETBJ(IS+iMFl%Se7yWGBf| zCVY+^RYWhXWWs0fRX5p&8TE%E>~EcW$@1)fB+qJjGma0*BJ8`9US}{{s*{}bx(;cnNJrx-uiKE9d34Ireah<|tml<+} zg!+{j89a=3>GP8}Jyt+XS8|3w2-$5|+gKVdMJPd;&{Cmgp zcI7h+hioLtZx~gMpQ8>?tAd8{<@lNQrXtG=6gEtS{7so6hNbAI@LIX&hSd<{3zYB6nvzu_*% za1Ld{3@;$7-Q+*WK9X=lzViIce^C+kyDJ7mc|L!hQbv_R>(nuH8BEAn-6Ry^a+9d? zd~TFfWJ!w45Ch4kOoSn>JRkWsBvFP$$Z$oKuzVR4mVrrdTDTml6zBkf}%J)cv=Y05S<#c1f*-z}GCW<5i5WL{IIrJ*O*$9sc1@+}R$ zu&fwGa&)Z?ebG-@%Cuv`=V1<$w(MW_F0f$$GGj@)7#5W0*UGshT@8yN{CDH!R)Y*j zu|E7W+w0KJ5W^W{_Ot(!WtiasGS5kJ4UduG-*1vL;|$Lr1vaWO-tY>-zwsnzzBjyu zBv59e;UlCb$t1&n@?7mBx00mE29FASE4ou=vOy2YC7Eg{09i!xgP|ydf0s&L`ZR+9 z!oN!;%S?l%0zV3NQLDKI8#0GT<{JVk@O^rRWRby%4FA5B+|Ob|7$naoRhAl}&~qe{ zitJzZi;;%73VeTcWdAn`vc`~rp803q=0LU@N<;Ru|2qoVX2^luAo> zKa$@KJt4(6tNrXY^nrwv95RfBB$6CAOomh>Ic1m$$tF2%m;-4?a?UUx@(sy(!y?Ee zk_(2VkQIvDH(fHUfE;9UnfLMGo9_L?3tHw-^l;CD@a zO!z-~>8W8KMzuN<`4zn9hW!+&^nj!t_Z` z=Ca=VAeX4;O5Ufi^j9QRyw4$%cdI(8RlF}k3?!Yri&x@f*yOalKJ4{XZy!jYl4)t~ z;~fo2rdIvDlOSEF)d26(mH4(-pv+kBw#W>p%vkSs$TXzPPu@R4y1B_pwEE6XHY2l| zWR>^MN_?OG?k2m?YN<)5jMysgbCA=NS?hfh@`Pl)_dSU27j=C0dKatAN5e+4-`fl+ zO>)pX2-1k;n0I_-em(uHY-x_}xOW;8_Pra*oM*z%tNa~9d7obN&O$#EX?!kwSBH$C zRyVwxLawb)XTl%eZ7cKRVj9cHN97&w9*|#2?s@lv+$4G6JqVI#o7(D;_i#uk$)Db1 zD)aeVo#d(aM67drH<<((K=Rgm3R;b2BA*k!cu%X$&k5@l$&tQz|A@>*%H%Pw#CE-7 zlK)qJPRL_i1My}5Crc6IuaG#BV#Xbn`5dcE;%z*F%tH41PkZ#^Z9IxhKlXpJSd3?o z8KFpKo)F_%WM)$)#CQpr6_g1#-iGXQlLwFsZt^F#>n-&YZhV2BKa#{5U!qmP-{kel z%+pX2tDC&TY>9M}f6=Ofn@Cmo{AsL+UTSF6LpqT(HWr2Su&LYL!st_lk61s-v^EA+ z;d^9)MCMmUthF(`%5b)HpZjpThvb*V{#RK6iif> zo}+7HOh>CXN+!2pS49eLSJ$wcF%$jxl5{tgM5|OcDFw+^B&uL9V?{_$%Jec;LO;1A z{fsq`nX1TGTYqCVWQ8JqZ3B&sAqT0|Kw~rX^MGWCu@xlG4t1S}8r!3l%}qK(qTHk_ z8=>kZF`zVq6DVPO{Rt9rCLpeQm3Z zJ0a&Ov&Ogwd*{9)IlA@6!`QA*l-a_BUl~j6l-F63wiu5=g52addX7{?&oU<=X^Ny3 z+-f`rsYjWe#_N#Il=a zO@idxBQHJGC)G3s5~N69TdHXWq@p4Nn9PE-q)eJ=9%O_fgKcT11&{@l$uKQ}>{BGy zmSI{3xk8yt(@&6hicGg{aJsmT8@wVOKr*+LUG50C6dqDYmkvZIE(`%(j&` zZHKg^ekz!DK}IPu&sM>-2eODV6-`GV`xO~!t7tj~xl5VKrgM;j`{eZ*X{&6y00~p1 zudRmZHl&s!?6*TrcOi=-)UBvxdI#yOWLEmLHU(DW$JYdE)yCw2tR(ryR2#C7WQeI5 z^nq-=ROrC@4UY>1Q0*O+juWi0*O*MX3&{7fhH&#sR&`&S7%zCt%Mg7b- zZH8>4%o@}0kXw{lW7-Y*LYYmbqma-;^7;(0Z89ClvPvp4*!HvO6r`adxwdVl^Jvwb zWV`7yWVj;JZNHmtK&C4)+qTH?JTUD>hQEI*xBAm`5ORw$PfW+E z^JC{ZlLANho%s{f2}qnvl~<;-kO20$ocU`j<=rG4Qr}IYAnn~Gy2fxehTRom ze{a|vSA&n(I7K}5CCn+1N8#$odz#ZAb17pn=P;3^wTk5Ee9TQC+esoI>^FWDVSnLQ zk;f#7=2qz0R?B-=a*l1Wm} zJOxsZq=9)Fq#a2k^GwK>F!m}Fn?ri3v3U;U8_F~@&xedBX>MK!nL*OhyaW5LuY&9$X>VQwxlPi+ybki7q_cSwByXC!zq**WK#HGGN29Cx7f3KkFY``F zCP{DeE=VnsKIT14?MK*SwF-XZB76Wd<_g^SQ5^97LoD_mNC}s^&g0Avv8;pCYL59cG8ah}nE!)3Az5P1Ta)+viDad@AjI>e z+_R^CwYdm{e-n^zmrl3FTnu7YGVBv><`R&A2z7ndn{7-aDMrcU=r)=IkjYD#t%{VQ zett3Ak!h%i&3mUg64He-hs>>O@=@h?MDiRvYVL;2w@SuSf6Ck)GDQ)c?u@y2O+Kni zsGl?DzQ~wq3@?~RLe@~`qIn!-H_2u5M92-2o90=Nk0iIv^B_e|scU%GyaW|=8n{0x#P~?pDt$7=yAIW?34vfYGYW2aq2buYbO!xg@-dB@fg?BNz#s2MU z`(QqZR?_K0k~jNTFMTi{Mypu%f0YzD#)SPcj3PO@FXoHrXQ(1JTVBgWjOq+Ubh^Bj z%gC&C%Up#VR)qaMGRt+y1CsofTQwK6tJS}Xu)ox3c>wV}Bd-saN09LUkRtewdAkG$7j8p6spCKi|r&OEC$v}lCF|?S-jEeks?3aj20ir zf0Qv<{2|7(@>Y22eJ#PY_&rjzB6`Ws5`j!9Mf%$OEKyi`14TyK!Yt{?^rcpjmI~O4 z@7d>9<^2_Fsa1>5@X3^kwKRe(B1yEghHQ0{c953rJ$kvHL`x^g3Aaoa$bC2I1^LHK z`a+E7)cKQW`34f?CIcY}Zt@+ZnwyNqmbP+}u^5eSNHQ!7kr_==%Cd}Q*tg*n$GfgPd z!(yn-w_-4pDePbNomqqrTKZvG=a}4N|LUcgmI1Z-x#hVck~GIM z3L;&UGh9YPJiih?&K`Rt^pokwGkjD-l??kmYRf#VGyiV8ysRZmcq{(EMcwK zvjC+P`xKMq4Em`<{j9Ux#Axte4wPGMX2SbvL#;MjZb9m@dB`*M{I*yg)gI0=eJQiu zlCKVLHHu`H#R~b6WVa=#jt5(xjZFAIdTGBUs*Z=9eRh}RfF%ZUhUAbX9&(lBh$RVf zkK~vowa#~}=SL)`ELo5jB$pIG`kNP-k8M-r?^HIfh}eCgRF5lW^FNup0JWV( z`9>oXd0tG z7N6;mla$%-GgH&^0iW5(+@n^9eC9&_Q{?ab$9(43@nA=8i7V=yKjyO-+Z9f7+GhBMr57h@sAPgkn)seTCW@pvlkZroERuG<7m=wz(o>NpB)xnu zBh!ndx9?T-Glry(?{&x&l5d&tHJqbJjx?MLn^8YgW|Z$u&4_*P`vk3yQmYxhe?!Ea zU+DWDa-TBGeLq9;T~m+c4ZdF>;Uqu%>gw|KsjUe6dw;$jkoF|M`{suXrdGRr3qlr> z9P%w(mv6;(MXpH4e2XG;i87~rOF+c3&iQ)P{d#U(@byOK6}7tNYlVn2_FZ2;NTKWM zcHQ?4gorcS8{Z&}{O#+61S_rd(tFC8=m_u35uM z)|SYKeNo-ozOI4Yv&~dmN{2@<{bri%$k)wHooLSN)h%8-qu-=`XsHa zb0A$v+FIvAz9VUGoe!Bq(#g88Zhp3`$|Kl+*grYCuGYoKY@p0QF6{U^NHWN}R5PmI zSvS|!f7Q=8>u+_7vVD41X_X^QXTmeLRng6~?#8m7Q_pjm6lSeF{!nMjJnLS~vgTWl zuvU^3d$=Ihxu~?zdK~@uDy@9-F0`J8h}pZ)dKMz~&SL9%h&Y;-SuaAw`ut?Q3=zl9 zO6yffgt9F5x|a0@M9lg1);}O+D6`3W8`6;EXX{;vI0}BT-iLIf%x~6*knc!-w?5X4 z#vbbvWadz2pY>VYQEUu1lI*v>z#d#;QRnkX>sz!EYk0=`H)I#JI%|Coxj=Hx`a!ew z^VUz0CzQEtm9qJ4c|&s5>XFUQ^Wy&ShBYrF&nIWH4@|QIbGMnVN)dAT+^3oat z5!?038U{H;nb%eqMC_e6)=147zO_apbCX(quqHy@kbJbJX7gjG;BDo&(EV#oN5-Ov zUeejpF+Q;*9=0savhvzWqg74H6t-1{v?VEOs{$EF;%Tb}nL=W))qwm&;%%!1*-2uw z)q#lfkI9yu&CdzvluV9fw$(>QoRNHNIgmf7A75J&%&4~{R$DWShW8zHG_1Ck*!EyW z*x#aM!e?D3NwBSzW;8-y35p zNIl2f`f4(Xwr@4dO0o??MqHaxZNoH~G}{PBAL=>7HVQJBB+E7yB96*Zw(lXMC{xBZ z2_lY^a<-{h`XtI!uuVs10ZA>}0%TT_)Uhpw{6^Bywi?@hT9N)0TiSlsEUTsMSG2lE ztyWST91JwC@4Q)Yq96Czrzv-#Hh`abhFn^Tk7VGGsB5nG%_ z&e$^Q@w4CJhPddl*JQ5R%0qskrC+yILMw4ayKSos5m&T3wyI3nUl34Q<>>C&s@LOZ z>@(EQGba3w=#H9^UfOCvKL3yS-cyc2%82_YwN=XhWa|Bo^!gu}^gpuZf8@wl!uN%E z&5AFbkGy!z>Xoe)M7(D8##R?1UbFh!Rv+W@KO~%J>Ix^xJkj-zlCgbP#BQkwx=|O&rFzdc2ariBP z%pr05{Zy|I+pZO~^f1458j1AVfSxx|s|3GI5HS-{{kCI$#7s!@+X)dfA;WJsM9hRN zzkT)CS2U$l?Dr@6KSiZdeh1OBc%7}3-%-dJTEp^wCzwdmHC1%key7m0IMe1Z;XVJQ zWQs~n{LZ2u-2?Sp*TnB4MBMW?^}7r)Q>K~URY(*`bHD45awIMMZfcg^((e{BIh5(* z_Y~5Dq^sX^h}hC@elHyhKpONm6%=XjQ|N1OC*RLR2y`g@V_<2IaefTCn10?T5^&GX?&j=BB-8=ov znx1$0Sv9iHFA%L_lz!N!q5K@0%u&BcjU4xj(a2f9Wb{*u*5|rk8Hl)-xbIgE(wvs{ z(60hS+^;_Js{|3R<^Jhc1tQM#PyDJu#2$R=R|C?AdVcO#3nJF%rC%M0Sf3Am*_y5R z=vN<^v9uMEe^ZD!0~GXc4iR@Uh5TDV7Er5V{%s(?llb_zgNWy7cK;5Vr3d+U(qvNo zb0ODhyVCtfVE()$$?zYk=_kv7yhcj%<= zzfjXp75`OOAMsqTy8k*&rl$W!jnwh~StHs0+t80mS$dAt(0`{U)6RbvGMSX=?tcVQ zm*hMDW03A7Klq=3h-bAw`k#V~RWf>Mq5oOP0&2C`|2$+n$y)!55V4nc_+Qqn;ZFan z$XujWyZvu!GW-2+AtPS7J?MWI@`%>wkpF#6tE2u8HFDhlPmP@P{|o(mrlnu={|NDX ztez8|`~M4xC3)xnAEYkHXMc%(v7G%yCz8AYc_81D6b{G>SwmtC$OjS63tRyOAmYf4 z2q@Ix>oZ$iKnY|HQ9sE6CWv^}mK9)uT%}Cu02|~TNu>aPh}WO$mevRegxE>41A-vp z$ZZ(lgv2Tt_B)&b!H^2ns&POlM4XY@2ZTfFQKmyc1f&Z|uYf4X5R$Hngn60ZlZm&IB|=W)Nl02eg8SXQMX)+CXMe=3YR1 z$QF{n0y;v(Yq=i+Iz!G==2Jjd$XgP9V0TENr|M`F4D6{{Lt|iXWW@SJ2Tp*9*HU5v zCqaUhe)Lj$;7mvcNoL?2NF9<=f%708NXi5*gbX1m8@L2Ajig-Qa>y!@@_{QMyGbep zu7O-6sT8;#BJQJV1#W}9rc8F=c8KXOc{KD=^T2%&@k(-wzyl5VJ&CwFvDTugIw+}ocFNMv6Atj!v^Z8g{BE&^GA46N9Y-{JqL_M>|rSX0yL z6BqvMM$Jfh>c4M@4H-r~*RW64^jz0I6`4uYs-b-rL>z<7?F%8xsh<}1#Sn27Zew2x*-n{u z_T`W>B%SRmAooam+E+1QzgVVV9#T_njC~t2;z;@4z7tZ3T1~L;hKT!(Y4#(KcC_^A_G1w7X@no`Cp62NYd?jIm}B$o z=a3O|Y`*;hM9i^;_RA14#}?bKHRR{a0knoo?Kd^cT5i9KWr=qpezm`Yi0jU8_79MW z)X#SNC&*Hg9rn+V-$-`azd%lt?6T_`eSK!zZO_~2>$BEgdwxx3pS_SKv)^6>8FBx7 z$X-H|Ic)brM(m5DcB3Y9+-^ojyheS&ZihUeb-rkKKy)wFBksCA5+d%FZ`%_!J>Rt_ zBO{*2-M6Pf#GT#)dxqQ(U3VVZvmk!7tVi}z5OFj;wwKW?>rZ<*WW@c?b9)s?JoWR! zUJX)#l_p`pwS3+bZ1h=5i}4|izGymHY8z+3?Qk@gwM=z zBtwG+L1vOn3>pPlL$WAnDr7gwlAu|T%OvZA7D1kpYzSHc`9iWWXc@%tS{;qeK`S6( zBwK=3Lb6D<2CatFCHW<2Ekx|)-9cM5nLR<-_RhKY`AxOE3&q5MMW;TNn2#@P^*fLE)X#ql^or$^tY6$?C61vI49I}41)N)RmZ26 zV+bUYq>dvOQj(;eV=P4MuN=n|NF&NLc1(kaHEiOTp=s6BF$*Hbu$f~Hqzm=a+%Xq2 zf~1pU8AQywu8tKDv0dF9Djd-$xz2Wh`1KyIu1a@kuuzI2qH#fgyRTAY{hquV_3sNf2->} z(s2SAan>5+I1dr~Vw~e5#7M2acU*>ut(fJw1rhUSw&RW_^P}S)B#Qc(=Xd}S*NcUY z$B;6VS?qWMsZX-h@fSpl*h=plcT>~j==h;2XZ@Prh2r|zAz4g*BY zgqse3NC0JSI|3mIBzGM_kV+&E98O3Rl1GjZNPm(~ju?oTKc5|OjrqH!<0BLT9I zB#$$rv7VoQNb)*MqaU#c^EoR(#2m}-tOOB9TtR0Q$R6sau(KLOtdGfAzcD`_o}`S~ z*${F?k^6;xoJ}>Ye4Wi9;wZ2=TS8t_KmN|v5HT}DoLwOK->dr~%-IcMB?))-fW(lv zoV_4oofDh`A!19DoP!(lGj=&@mEs(Vj5r@=D$<5BC7rp*h_hB1=SYY+15|L1hIFG= zRh(lX!%3<-$2aDm4HM6#YB+yDMqI6GIcGq`v0TSF3nJ!oJ?9*Z`~vE^zH=UAGf4yI zBD50g+|aoUa)>gGoIgRt$TxPbfLxz-top0n;bZ_&6^KZyGYBkOI z4)Ta(vGWT=Y}ayU-X{D!|A{g`IrBlp^NSVE{19=qTJ0`VhwLND?`N6Z#t_&#M1w8 zR)>f!z2&S45%(>(owXt2x$qrlU5GeR?mFv1#5wc6vtbkdxt$-BHO!G7ICCK4lRRAb zeiyIpKWD(uBV%B3|2h>}(DZukAc>wn9JRm4UyUZIKb5b9m-#kIV*IpBK(f z5b+xIzs@cY@w(O*XAez3d4hW(Bi1=@a9?D^>rwjP{t&Uw`GdcOh;=R)Jec((NypUn z(G?CJ)`VZtF3>(LuE-6N62Zfp@axWfwUuND9)nhIDPv^9GkHEL=T*rVJQm_ZVpb%Y z#D~e(uRd87DXV1I-)0J)fEg}6yXy>|hOMZs_M;04o{jMlukA!A(oD@rk-97!$k|%MvqTZ16USmrvLq@#zJ~Q|zM7(A`C-^v|z$f*5I5+qtM7%aQKln7nk1`8_&qBnuuMEDXS^DbW z8yZ;~d<(70Q9tX0?>71R)u(O2Paxver|pV}SD$t&B3^yE$%LO1x>3)*Ax|L#NkT&Y z#`=g)nZ}0bn(|iSRoVEEyiLEJ&j}&5z~n znxzj7X@-n=ujIRsHjpIhXJklQh*qHG3Oz6 z_cQkRkZ$Nl%=rl+Jt1PwPYUVNlwT`5)A~#a>ED!}wMNjgri6T}*{*3J12r-uWC+$r zyxu!2Bv+I9F=T{ho#%&)Mn)W`3qr<1#5yku`5q$Hc}d75^fQIla9PMy&9XLxOhZPT z{kDb7gKVN!zlJP;93VLyvPjeFLdX)xWy)L(Sq6DZay8^9&C(x-tVBltSv~VV4Ot5b zAo&=w9+F7%C1fL{4oUvd&5)KPMM8gu3?cCh{RMk)x*~eX6#A=XeJr8Bqt$Y16&QL5 za)~4)^a$i1lE~0w5a)mDRwRd>fYc!=6?zIH-V?7JdIr*$GBrZaL1vTG3%vmOO%c7+ zH1rZgeABjN=oQFm%Cri-3VA^NvE51m{&}c;)b6kPJm~q-x`Cg~AA8ZwJyVE94QL5l0u@tGGs1>z)G8vX;M zj3RnzZTNIZLy}G5Ga%il)t2yCknxn+89oQHh%&xMG)?jzyzA>wnnN5dCD_EM`8 z;fo*-Nv?%2(e!gae3_=7$KlJHeSJUnBzy%jh4ZP~{xW<$B#7io_(n(uiHB=*Gk)h# zM-jc0-}N)3JxL+g4zvraTd z=MQl`g@|v7hPs|X#Jy;k>jgx7^1K95RUd8Rd$AOeGoZ ziiE5n8S9FM{7y2?6$?49NRBk#6%P@!ZjLJvB4*}~u4K&^&UK|C^M-n!@5<0*7PzvI z5%X}7tF&fWi(O?Q1q!HRxXe`^BIffNS4D`OGV5KHn}5gla*85)X{)O$GGd*7QAG5- z%~f5~&n{O@NJ;AXpsO~d8OaG(T}XeD3$A()aR#{SY5O-Uc>-MHf8EZ41^3MX%aCQvYezv#8Ayvw28 z5hFF34iTe~`JMXd9x(|bK1050J}BD|Yu4F&*-ZWLU&}h&Xb`L@bAV zq0G356`J*#9I>%Ezg`#%$z!OOrbcW*D{(YUjo7Mb^+UuqWWtqJIntttLl7~eRz@6w zl%k&3MjV6GqgLx9PC~?4{nv=okdD-9XT({^5R%^`&O?5nes)D%goyL}$%xC4Rg^g$ zaTOxY>X##~YqslZ#7$(xYs=RoZbQUTc{k!uh}g^bBA!CTw%?C<1`*e$2N5qI;@b2u z;+1CUk0Rb6Bd$%)BR)aII=_tg53--O>s5r*;_Iu!yNEn3_!ZnvVjh=}xo zh*ub*B5e@y3PVg}fM!eMB7-!N92usOw8%(}l#GnW`iSGAbYzkyQz5b>GGbp;i7cnd zRE?~lk(!ZJHCtLIvL-TOOY26~hKMb#7nuzaTiPJ9fu`q1kvSS^64?yP`cWBYz0@|c zgC^4@vZqFRM)q&PuRG#3^L~+o(a$#O=bOk}$O)2fBS&fa85B7lnVXau6*)tb`5|(? zMy5wDLaUETEB3cUBbP!p5L{QDwLLRwR1 zf8=S%a7A*Y6OmUSi%HH!Uehf7O5_b>#QPxEB5y&&vaUzo(X_e|c@LT0wDdnBA8Imp zBcCI4nljHKKWZ|sBJ;H5$Fi7*`JyZ>zdqgzMEPo@P?R59iStjfs31+Ic$5>F+q885 zs6@zn5_?oKq*zgP?*v7qLd3mIXjB=9I75a-m4k>gWO!5s&C*>_m5>o<{0q zn0gMM8Z`nU#(8PfNJx;9VShI;Y7``eWKGm)NOh8pQDY&ksOL>l;~`?Zc1BHrh^zVD zsELrF)X)B?NswtIN1~=cR#MMLqke!KBsm{79U}JWji{NBdz85uH5;NUu5SC|s2??B z_$q3iM&3j%L@S5VN-w>OS_%>0!u~I64J4j2y6E+qe)2?bM5Z!jibwB)h`nr%J`8C_ zYiNl+3K2(2K=g6Q0Ls{-PimGG9(`IP5z*&bj$mhi8MO4+=qoMxJ==Ve%;;|Zoc&rwztHs4I{K9+(>D5TOaA_a zrG&Z%+eg1gCXu9*BGpNHM1Roq+&B7@M!t#uqLFW-^R)W<3^^!T--@3_#o1-oqKmfTzk$<})@O8dF~~5Iv5HJ5nWzZ=3)=jj9BGOoYbo=CA_qxk zC~}QtF%$mzpVzABRw+`TF;=#mg|K71Kn8nS~jucFIB#MSDb=<*QpE!t1f6(BdM)#vC+ z5Ycmym@1m}DH>A^>->S%*(;_F_Fz#jb$=OR8faQM{zrmiax@uNObaaCTj__ravRec z;v`9k>8xp$9@7UIG3T>lzR_e##(ay6IM0`k8La8KOw3SCtI9EBG?}U~;~@#OKDA?h z(Dd9O=0{DdhB5OXWvNy3nB|&GtC)3=9LjWz`9-rnU1D}2(~~mYV)kk>ePfOxGmJ9* zV@_%^Lt-vz`WY5;Rg)PVa~Byg@?&EjXfhLHo*^SfV{*((O=eEa2V}&Z)x4O`n#|Ie ze69IeYZ9&VrWhkc9H&3WSRmr=bZd-nYknU4iCX;37ZIUbXyky9~Qt-oVSe@y+HjVXimd8tTl+}W6NkS`<`Vk&6*xe`;UHUEW& zA_n=`(M#83szCxtZp74TJ%Wu-BFV#;#+rVf$24ip-=i15hV&|?B}Duhk}74DesUx( zeB{M5RtP(5iRW>zV_IWb;(6TLn065HJnmggNAx3JG5aT`i)IZ!#&knQ{FY$8*ntpn z?#&-NSktON>`-LHt43b2HM8vLuh`XK0*i{g5rj3eSqv?4SH-=S^nV@<2}u}?IuI>)}yWV*z@LPl&?x7fE3abE2n`wr5A=3&p+e<0$Xc3`Zo z&DY1^pjeMKe2$4%goecC(`1Im7C=VyGdk8&(`rnt0WyG=K0el@>1S$efJRourfOtu zY^Fwjj;)}PZLw9bEV19W$JT&MrekMkY;A}*M;(Z*tLf)ZY!hU*Q>%-yT_EC!yB6CG zt;7*`J+=o#9C0^edqcz#cPqA^W?6S)2WaG8>>!Oih#iWhpQiQsGxj^kZIbt~qab2F z>*B^h#GON)xUrg^^Tv%w<{kBuFK!}4+)EhZCTm)G$4y11gtvP3D-*X{lPMo}Skq6% zxMPq&rB#kpDeeSB?62&&QxI`n)QdX}$)HvZ;?B0=SCDG7^mcLQk!eQKA?^~SA4#{k ztB{G*PtUmP5b;Z51LAH%7E)$l+%3oslJDZ~K+cnl<-*Rr;?t_r;_e~yiZV0e9zqHj zm3u8|Ufg4dlVnxg6G$17^>Kee#4k>4j(ZMiPMIxnFCpDYcE!DhOe8rR_ZB0zUJ>@m zkhpiqi1WtzxDOC9e=f#-f{63{<+#s~{nYc-xG#|NBoE?rZTWfQHOY%O4~WI2Zu|Q< zJtU0eQ(XSGUyos)_=3oYEzKKW1R`E*Di&W1l1lxQi1&nuJ4Q>qLDREOyb+md)XEm` z18GAN5^sg{C5ejngN!6ejt_v$At@DahlmlY9PiL9y-Iwrrsrz$VUP{fPrZ1TCeu7V zvMrzU*WakmQ#!;)L&SHEI>#rq{f>?N1*ISMPH22O`guh?ca6`2*v#s_=pA1QB1UyU zd>KeGWd_BUgES%;9$!JTUE|^_A=8&K6XL5uCXmdDuK`&=vNXOHWDUuh_&Rbww9Z@O zv)krn+y1MP(M!AH>mwtsO?%=SLoQK2d*hoz#7sC7-yHIgGH2skLOzq+h;I!M`~7}= zTTMUD;@fMs{dIgtWV|ivsJ@Hurs+qQ&;t@enL-J@G#O(;ABgx2m48A%&9VX#zClLZ z5!n+4LXxTHpoGB?agP+4FjUh|bV4pNm8n%y!dOTsY=el`X}2f* z28mXBW}oFt*bXUA@_WKg&HC(0*rk!Z3H#8hF|FbLgo6;VhQ|^PYvfGAQHWT>O9{s{ zJ>N(;g-mDa`A)(Gh`5LRGvN|sHf3HWT!o0Ge@M8lkuM21HKI?vrCEC6#5>3wr=E)? zK7fdEwkAH(^kYwa(w2WKv;8*tRV=*}n)nxF07+!xbF>mywD`nV5OGJ8n)p_;thB^; znq_4r{-f!qT;fMfKh+cegWRL7sF5hO1l- z)H1O!M2vICL~l*2?ujO3ynWT9pl70wW?6$0tq^g}9GvI}5${S5OALgF?;zwR20`MK zp7ql3L?=Y7&xphjNJU!u*u*f+8cs`eAtPP~n2{Kz>1TFgEJTdYg2Z@;_!W_biHQ*L zjALt^8Tl!OC7DQa*RwR~!h^<(aSVkjT63c01XJQ42m{CU(D?!9uJ)T%q z)9Pem4UJq$tfi6LiFGvcG%;HvZxiclok?xm@vHD8nhCKpS4`>+IY5$~)E6T5 zPNSs$n!VgQX#g@$s8!pfLGAcGQm5b4b3(VIA(~b_lSZOdKC619^hp{E2_zYmG#-*g zGAd~Tq%p~qq)8Alqh=>ffr#I|T#z&kGLTv=N}2%?SBI5JvmoN?uqJ6XWCpcbpEMUD zK4q~ZX+C5#Wp*bmgoqJ4oU~XYXOfme#9qFUv>YO4@2#YjnlZeSv_>p*F+LS4pyaytF`^cQU57I@+=p~=z z1CZe)w&X*QS=3Kp@)3v_pQz+xn(c~DK7q`y)G9IgG~^sfX7X9cOOlGo=OM-X)Ge)( zd=VmERm(}f42hvkCL#B`inxt+@oMu@qQW7-MDkT}M z#Fe^TN(SUlr60Z2A*CclocX(@RDyh@W%Wv_(w?7<3kAq~SuYJtsfLWWz7I~R2{9^} z9BEieZHTzOk4vcwNu$j7DfJ)?NM@uofQYNiqLfCEzLZ&<(ik#d5%$-1Qkp`xQf5O+ z3y3&B??`C{xlWm#DQzHPCLBm<2l+sm6Db`ara*OuUrFf%5$E1JDP173l)0DE4N_4N zz4R=l2SglA?^6aq8dK&&%0NgiNuJaZ5b+7pqN(E{KT)Q5>O{yv5}(wWkf$U;sS6;5 z?CKtjOkDv9AW2T$2uUOMpaJTi;Vc*R@Kx4kXqELTIwN)_IKLq%Ct(o1QD->bx*w_3uStwUW42r z>7RN7@;AxA)IT5vgVg<=n|d1}X6ESByAW~bFgEo*#8+vhmnNh>goIE(6H*^RvPfp6 z{t0PBGB5Qhq&La3)Mt>%Bx_S&K(>)=Nqwc+UwcyDAajE<`%>RQUXvV3{Rbju=JC{z zkbDkx+s~zbhWL@(Nc{pyB6*am>%hH*22%*)h#9lpL6yiYBF%wWoVNG%MR zP2!PO6tYDTy;Lx*I7Hk>6;AVl9HdOqG;hdt>Zf>`3G#}>lxBg5cf@RIzL0!Qb=&>Y zY!EAnGtD0oM-rVD2&qDnkQM~#M3S23gp4A|N(+I^B`KE{2H8qdInAXRv6^X-$eg82 zu6dV$VAEk7F}@*u4{U8=_8TZ zMp7|-wPwUBr*A~&l#ZfR>RXzK*U|s_vxn~11K{o{R~80FXpG8gNWB;mZo2X z%%E1w(=TgAV_o`HWHwM{WBN^qxVPDsehVU=r|d|-135vhcBbFg^t>CcgQOUpWv{tA*WTpjr{>2Dz74*yE}-w*?3 zZl}M8xJaI)e}E*D{FD9(Ql2DF#%D-FlA;-3Ae~8kGISmJwMon$XNCtv%%AWKeaEk_ zO%WOSkr__?#AFnK%p)nAQ4F$!q-sV9$js~NYZvu1ydY;N(zvOekD*@bnc<6!xKj7d@Pml&u=LIFhnSRBIa2?OKu841(2O8WKO-}onr$DI z5rRxzYBeDv8q$trYDO$%7|D!`c!)SZ&&x=J%%RNujAV#-|9wS9DwZzZe_xrA4iWFa zug=JVi1**uW|Y>f^ZJZ(=y|iUEWPwgMs<1Vm(+c+E29BKyeqvsqY>mVwc3->7;=MT zZ$>lCvi4=PKt|kW9?R$mc}1;`XLN>$XNc!B`e^#On9&az@s7rgj3E%QhW9c?K*So} z&lsuc=RwBkj{H1UG(w#%e`QS4^!z+ys;1TZj9JKtttghc0V19gd1h{dn5k#4%pDN1 zUG~fakjj(^$~*)SXHi$?X-HejL}Z?Yh_h%?<~4};%f%^~HzDFInwohFBF>^EGao?2 zE8C?qA3?+%E0g&rwq4AzvYAgIVvd#1d=3$FtYYRX&4^Xbe5;YFneVYI@eWt}j&`ug6uL8cxe#<@{ueuy}SH_j{w5zn%lWEN)P{+D^1W)|)A^;xS87k1uQ zOiS;S>8;6h%e2ZpQ>I6zzb4Z=(}~PcC6god%?yP+BKbBm9HNU<&+|hvBOn1J!!x5G z;+M@vXU0IvDjB^rHZx8$V&gLtkP*Mg`h8}S+%x^k<%G->i1;k<8Dm0J%=GII|MuBgyj2Dv+Qkb$nK3 zR)b`ctjnwcX+W|mvle6k$=1v|km)49WoARxk^G)nA99T3KxRY8J(8oDIgpPer!$*q zA!Ai*SwS>qs; zNz$^uhx8>WnKcpeBT2ce$&l?Nm9wToE|Ap7ng)4BQYUK$q+qPNJ`J*Fb+SuC*#G|i zlZRxNCa`KRswHGw6w^ruS#^*-s*}!9b(^Z!RDGh#QbdxDphYsPg0}99@RJ8d}M52CF*3-jkU<)ukn{O3pQ8mAtnGvZ^~; zOlK9}Hl6e{tNO62PVQw^$@}*as|MiF*Q}Cz5!+VCOEo8};%|^Mu1SyqO@;Fyv z6`!YeDVtUDh__(XBCNAm)BZ{eog~(I3fW3l$!ofos>@V8r^?f$<`SuDM%7qW$>Xw` zs$*0=qMX63_9Et5S!UWXZA8|CstHsrS1P--hgI^H-(Zz|7I?s_0efT zJyvyc3t8ml*2%R{6-HGeRb{EFOI2&C22nMQswGP0e&qc|c9B)9v3~DZC6B!rNip^& zA9dT(SS9D`uxbO^iX%!KM_t&X@<@)OY6Yw0Gyfh|$-UgANnvvHcv{99s?JjNi7JOxU6*Q9^`vStRcoob zz$(60`SQJB6(55Jx$jxEm#s^KT*)R^I!VkwF{b(0qw+`wQkBjsc`Ms0Z4;!y%A*O= zM5-20wU(-FR2`%02CEKXT>e%}C#mCS$JKzJ_2et3eEt*1ufb1UzXVpvYgL6RaV`;O zkQVIG<5=4NwFlL`rIYwL%S-!?`cu!C3DQDld|V9DDyp_nbylhD(j!*!>rtHal2!6? z`7f*FD^7lYd7Z^wflgAdV0K9yzu_!*2J4*7s*9)wvFaMCS**InD!cSEtN8x4OQ%>R z-vL}@)jjrTob;Skk6Gm@ePPuzR@KQZ9w2W=o!oGx(n;}3WtYmciqBoU)RQklU9kKEvd#RL8(B4P=kcRXELC11-1vPxd= zR94AH_d2TfvML#CaEetKs4lXqG^$&ys(|VVtE!^%aIkOJqB65ewkWcatg44ctFua8 zZe6OHvnmIVc41X>RD)P0_cESUa?dlVTE?n2$ZezQ2&+0W^OUZ#sv91C!K&VnmScCn{Ho-2i>K9aRs45bwu5%Do@vPc`Tp3m=qf3u= zVAUQx+MiViQO)EPTT`91g>pBk$`{6#ft-a`ESD_Bu!^@xmXfF{Lsf06+ECR~so1u$ ziXVBN(h^qjqs&uUN7Y`Xa^II1m|bSM2Dvws%Kg#L%x>V(yy0@?{%8?qckrmMQn^3s zWb^7F9*t&?%2kR|x#ub}dx~6js%lf!RH@usbY}JvE%>!eu6mOVQY!Zr6Pf*u7SpMk zt5ohezRn+!Tge{fl}_5kDtQ*GuVCn;|LfVwUY3(zf4R@9FKAoT#jcX8JR}eHi;e6m ziK;AB*{mu7>&_})R_UaXtde_~%_`oDr?i<>PWEWBbe>i6+_*y3A5`6=>KRr4QB^!b zZflnetdf@z#434BE3r!6%GOj3W>qxSZz`)2Sfw4SEN1My!5)>XpQu`^RB_U;tSXCM zjw$9Lon{q3k9$a0SjD%PE3tc38R*uTdDtWA;S=AU;kyU)u+()+& zv$n7ntm5nJA^rc4`2V%vI;CoM~a8knpOPB(@FhVB`NixIS+xt*0aootb%Iq# zSjArZV%14hk6CpN)mv8GK*dMxAu2vXFHseUVMiz`K2~{G^IrUz6+LfN?~tm0#zom-1le5|r_d$5Y{1@^p=ReTJxbLX*&pL4Qv*RzVBS+jGG zu!_%@?A-gT;&UTA_cN>b+{n%~$I0il?A&-(1!0|QvP$l`E35dd%g!CdD!%2}xpP=0 zA8+b7dr0aydti3*arThZarThZahB&KA7>95XLfdCW6zi2A*o~PK{K0e9UBdKW~;N9 z%^Wg56GPFTIL`T;jDv}@fH>m$?99NU;(Wm8Pz4yDlePXIZ)YBV)%5-UeHz{rskca) zbQ(|*x44OlB88NzT#}*493?VEj%12NDx?Udh*Cm^N`oty6gTcAygwhf3i zHgY{dtf!bmfLJ36`@icUy9ly;GQ#Liqsd0IjGi-EYP8zuyOeUtUm*4+xuix*ZPT&i z2w7(x?I2@+8o7?CHgZQ9^)x!e=mL<|`vf#tQh75d5xNI-4XC_(2s8qO`7~&hXrBe$ zF0=&1SVit#koGjb*Nfb0$TW;^K;y;2?c-zW!-!m!R92Xi{OYnl%_p^>Roc}kW5jQT zYCD+haHFn9c_V)J)#3z`@q4K`V!xYDnn0_RG1@Ps zLJP))eY=LM)CTTIh3=Tj70AhaawxP)M;RTP(xw*7`+U+DTBQq8Ds;y<^ShvNR$`o$ z7-uEs0jbcIvHw8A=(CL{j>A;OahMXvVM;^bi_&!|<&qH~w&YwgKBd?_3}XLM*b6)f z*(Xw)UNCp>gFb^Sc3*(L75W9l+7!DTvYs-a-$dIA^q2T@IEdq>!W8v@Y^!O^4W>is zc$1xM#FnhuekQxzXsFRGMq`a88BH^K+UPl>*Nxsb`p9Ul(FUW9MwMIn_>~&fH`>jJ zb1O|p)?}P1sjQ>PIA>B>-ejDOsq9>nU12oTXr$2{Mh_azHhR&BJ*N81zD{XLs>NP3 zNXNyWr?SElWPRRNO0e91o+=wvPbuSSgG%8p<8}kp71|%v5LD1xFtF5&!~tudGMGI!?~ z4K}*LXbgz98Fw#8)Andev6~M%5Mjh_2}s-ihoD0whkTok-S?1n6L-IY_>IcAZTENX zILKY$TETL*Ni(V3u@t_fu988U8Pkd87s8+8Mn3wM}-o9rZz*2&927l?KgXt2;j zpkYFbKsSLhZY5|eD0V-9w7=p^j^{Em?pMej5G{#uiCy^voSP=L+kz-7b^f$NsVjwS zHngR#CWvt>Hi^0v{hM)lkJ8qvw9pV8Gu_ZmHE^p4S&MwQxm|LPmHGwNwH*l2W088;jB6w;AP zJ~HjL2YUZ@H#)-T+>|nIEQq!(-HRZOHe0$+jQ&U|my{mlzi$me%quP5g|N-KEMzZ& zBG(?It^2UlB6fb9joo#VotzRzG9b-+ez%l{K*kX=W&}oKQ;OX<5Nl54CW2l?xUri8 zdKZ*&kAglH*&-0@Q0(4Jsl=@UeF<%ejnOq7oW*3^dT4(UU;YAV>)EkAc5lSL21Xf> zju_j4%0JAwoMp1{MlTy}pgV*Sxf&fbCtFJU-5R#B+ZD1Z;!6g!v&g!F>WS=lkhaEC zK-wD525Ag9qiP`ThC-(Ka-WeOl}7Gqvv}LI--CAhFG8z!un(=3(a9j~Ev`;w81b0w zHKUI~+Hd><+CySm`w;KnK1MxLiruB4eZ<{uAhmta=;_p5B__*Ag*{tu0M$DT!r7pM)jRL7#2EOQsSsjX$&ls_&rbEnd~8>Sw{0yD(@D9SVmZ- zGy1@2HHi6F-u(&U{I|TTdZ>m`-qiuAZ409VQi|N+pw5!YQ$abQD~v`PJq$WtwDUk( zT1$++1ZfU!-N|yz=unWB*6ARvYa>CYihoal)W7$P{sgf+{;3|+?(BW`rC(U)p`=6U zhcf3Wm2)Hp?cfXJ61%>j0YZa7*9vi#f49)JklinI3ur1Ra(95VUd;n>hKAY%;won( zCt);<=rHFV5sO_xvq0tD5g^TtJ|NA%Ye8CL?=dQtj@i(v&o6>B|NQtb<5ofTq=XSx zT)%`&L#uGOZ{6F2RJI3*J!W~gzsZgUsn5MZ+CGMw>|T(rmCgatHgYRKizJml)16SL z5w(u+b>je#hTFsFJfrK4LcMwzGOj;IZYhZC&yo8Hq^+?|7hQjj+#w*g%gCJ$dQD<* zozX)^uYk1wE7s0^>xO@5Q4Aw?>tMm$h#hB7%f#nzA!Dx>xr#?R_aUgEs|s2xR13sa zs|s#c&{yKFHAwUHU=YVrvFi%@4qBXt18H3w0QyB_LqYVf)ZGkXEh%-AQ_3fgf%Hqa z(C96rPmO*us?yb4G)yVu_5uBYFfy(^sLCTUG6K~E#qJtVT~Or0H)S+r{6Zl1==tkO zOs7Dm>o2oGjm7pkP*b7BpuL1P*G6^{wQwKjO;~8F_|m9iH!rJe6i%aYF0_xtbU$dd zPUb*bPX`!{1ho@)4;wvi^dU&Ybw_z>08(ELGCB!#sQ5Anr1g0sNY|Sdm~5>P`^~rl0Z%`v#gCAYVz=GVURKMfEr?;n zvZh7b*!6+z454#C8kfsKnvNSm{h-Y!p9-?NDd%7|-`iD*L^v88CuVGFoHwEweuufDL}D;2BvHzIy| z?tv|UtET#;yII0zeO6)}Pgh*wPARmMP}_^Ol(O|+Q#2Xj-XUQ;44NP`2lS9o*a@Bo z*$k1fy*(wwSSc~ii$%uxsV!Tr(z~!_tJrj0#~5G^(L{4MpKv`}WnAM{;wqRDSId+* z{!`)_x6%fLLDJQ{y@Y%l+%#O)gM9KEER;&}O0nAo^cu8<7A-LXeM>AFLQC3I`>tt8 zvD+Q)R)V5}dAFBg*9h7*(3W>?LF?}r85U5)*L0S7b|hSSc$tWO00iMtdmNtr%J4oO00iM++$JVE{YQO zY?QdhsKnJvC9Xv(arKfExzmsi&GC@;T(LP^v_6%Q>j!sg;dkaEcQItFo!D6cu_hMg z#yH4WBl5|;M%-Ue*_2dPXu*2Vnv+kcB}MKLkbbcjgSv_A1C#w|ROvYHuES2gw041v z=l^1t1!)O(0OiG(ULaZ9EI7l=5zZ|>$B`B6bG@uSVs0-u+r?jpAaG(=+clga#^7w*&?@8ee`rHtDNbTuq;$zG;C zAf?E40_ivE^ps*Z9K_RTu^SEIxv$tw0F8t%v3m$KT8Li_Jt;N^vO6VKi;dm@-Deh4 zMC;vQWhZrq{YKEkWo&cF*1dc>N{#9o?PioUI?AYbO4!>rngG&NE=)=5{Y-?LPda>E7%j_@d*^m&CtOsTTJfK)TlV5a>VhJj-S5|t-Y}tFOj6Jmy`*EdL&D~o@D~))nPThTM zGJY#m_La#t82xTk?j&`mHR3NxIlpR}yX^0k{Hj6As1htH%)pv&)B;pVs26BEp<$+d z1XNvQ%T4x|$#y^4+vY$u#oc9~x@$#tRum~{?@hgb4`7*$0oY4y)jr|WM ztADyrd1s?bKpK~OP4=A0R-0^_Gt{=!wE%TPj^~n2Ahiu?>uIvVAjTl;CZ)1m@=!{# zn*-`8! z0Y3g$!$HB>4PKJ(s_a6We{9*!kPbxF1!jH;ZF; zI{bS_LQ7JLoWJi}c+Zu#?@0{8?bdK}aw2S18}6z;1)1Io4Rd|8&d%eYTLti>6(0mR-rk|w20iH zkZFqgf!w2%T@BKf7w%BrWwJR&%TtP-zvUdeo!~S5i=Dr%SiEld5{%t9@TC&Mja}9A zylpcOV-UNejV?8M0JNQ0ybP)?asC`sM`*kLo?3x4m6sVU25H(pF{&^CtIlG}uf{$? z4IpbPbRbA`=v0u}UTgG}eQU#4*WX;rIDZ#8_0*c)o zW?$D);uq?8sA~)0AKM4+P=dO`UF<#v9V_H-Fvjjn$h5S|o$uTUqTLzPM~G{JXA5=O z!QaHpK&vI=b24^4AnPyg&IMg4GzxSDsBpHu5Hk9Q`G?U4qsjxlwu#Y^M*UO5y+sgX zkV~e5xJQrEETC&8T=r%+3URb~i_mM(YAXF35QVnOQvYJRJB&IBnSMX2UEtGO14Li4 zuA#~JB^iS-a!IpPmP_^nX{-(dO^_JmK&*+eV?IA3vfhyCT*KEF^rw)`674Y1^FpIQ zZ-I2Jbgjrj`A&p(qsXSx9m0s*d{D_u(mSB4LLY;65Zd}erP#Fr?F<>d$)H9;13~Ps zVmA@Ar^ve6H~B5dcyl-Fz67xpvhJspA{PzP6h*E!Nbf;%)wG3#+X^zRIjoZhh|f95 z4iP#YbeK>-P*=Oj@v{#8tzUM*({K{^S9k&_abDP&)C3+)-u=8{7|TAR)W^%D!Pjom=V*l)z{a*(FiYa{9snIBk0 zu|H_O$VM7H3VKOoZ-HJD`V{oO(D$Gw(59)Pu3mRu_xsKsAMY-lOk?Of7uuW4HC?+9SnoH&9(z;L8r$ zRmjI6b{9dWE%X+nSsp0Q9cJ&r{^WYb4>7 zktg7872HMcP0(tgZ&P1zqva}}&p%i5xzQA|FT`CJ5XYUlca?gjSy45o07&4r12~EZpuFJiN z@VVH`KkH7g*be~xA~6Ul3O&Yb}VWGKpm(dgud| zub}8Lw&XuR+X-=JuZGZ8S7R?ws0v86yMT6u3@1`RErkvPuRo{_w59GM5W~$S z*MPLPj{&hqDs}gp_7S56DdE)@ldU%T#^|?{N?pZaT6YV_%5@>*+d%q)g%a=dD>XDN zZ}+Q=w@65lYnE#98V=}iNo8+PH=&C_+LtWs<7p&hJ)q4elT&RznP&8;(Hx^iMsFIe zGWx>kccZG;XlVIlmy`cHCk-6#ON)f_l!O=T5I&R(N9Jjjmlr^^Qy8@sZmX%U5s`$ z+S6zsqXUdO7#(JGl+m$9CmEe))X(Swqsxr0GP>Sqq|sQT2}YBQrWwsLddlcIqZf@{ zH(F-2(&$s8bw=MA{bKZ&QOR{a$Ez6aU{u?vfl(8qy^UHKwKM8y)WxW~(eXxojLtOb zZ*-B-6-L91ZZsNYbf?ioqbWwyjUG38)@YH@D@Jb`tuR_;^qJ9mqaTfaH!63%&*w@; z)r@vBs%O;LXb+>7M*ADJH|lKE%_whlqETO?bBqQWU22pVU6)c}UtFvQ_Wf?*eoyGL zZ-y`0Q%?o)+Y`B$jlMG4<_50~J^%lY(Dp#M8oy2;t}Nno2Z-O$!oGbNWc&){lN*ir z1ydRCWReOk#=sp%1^Hxx(Ilg3DaCFUh@oZNYoMO+C30&){0^4N?H_%qYpda!Ls?hV zs3zznSmcsDLHdo~$Ecmr!6{{2HxO5j3uAg5WT#5nE;Sl$^pMe9qZf_dPYLI)K^(ne zp9I9XWZm{RdTN?d#0xfRCwX1F_3;(VJCcQaHrA(dfO z62wsf))YYJ3e7co6*N#}?}M%aVeKEYRGpRk_uNX|Z_r*V?s%T-Mxpp7rLg8!>h^|A z!|;0@kvjmg+h9=``(sjBJ~=)mz0s#N@l?okjCnRlOLdPby03vzABZ^=Isd9{q0fV0 zaR)-nCxeZyPbuq0rT%5zM5Eb8^Ne0H`oic}qw*tssn#}XVbtE}L?i!3D0ZAomKfb> zG~H;S(GsKgQ_3gbfS5zEE4kVC8`VI%+EdfW&pr#^vW=nD{A+7;g3-mG2~xh{Wc6s% zJ_gc0>>ZH4J^K~tK3K$Vn~|C?v1@E}Fo>b)`-xhC3ZhUwnE(TuHXkCePfmNIdTyb$k++H~&StDW zP1BZ3R;Aipvc~9(l(Oz;qpG+1nf2}|;az7CS9N1|E{HLR-E|<1A5TM8me8g{rX@JX zv_1y7;g;H>2Q~^Vd85ff&8a=gmz%E9e_(KcgzxOG!1))q=#TWIS_De&e?W1+r~X*<6F)Kp}HjeIW^yK5m+i_xIH#N9N| zKA=)J8`N58E~uT*YS7_A+l3b}U^?mxG4^{3G4@J~z0!F2mlf>{qXnRYMYcSpjQa}2osd#j`A+A$ zK#LpPpq@f3^FBhyK{iMz0bM0D6*NZZ4bXIw|L1XV7ZT`mIsLyYx%~cFjTBOR=`IH_OVo^~SKx zx+c()GVVZ7bCGogv880)sYZiRDs`-XyqQ?)Snris@7eN79qYZe!J)9o!oNty0vhg3 zknyBM*4=3|$>7)>^M&4{J1ZSW_P)t%sF9gWU48e=rqXbnjFk}7w5+81<~#E? zUSsr#(K1jEDTP0b8sCFmOY!eW&>2F5Of~^DL}br{MhLA3-6yo;MDJf)kcN9&O8SDb zmd6OlwEl&&R-q0}MY-Zj0unXr9N z!u2OF3bpwv-d#9*w0US41H%_Rm-cB&@C9RFjY~NB6XLhg;#}cA@2(E$6NyVWS5+(q z;cd;PW_z&F$wq^XMuT+RInOB6jWy=(Pov2@S*-5&lr`!C`ch)r5A>tZt)M?Z`u!+* zlEfafn$V-r))#sg)KrMylzoK$gzONZ#*?teC)5ShSLi~}c|zksLxdKCh6{ZK8Y{#; z>>(lcVY7wUb1f9wVX||tgEFoSNWbZguA9TyGu%c(N5bMbp+TVWLK8sW z3NZ$ao}vZ&#Jz>sCmtfiJ~1!EzV2)x_H{#q_zk^9h<)88q37ZAqeAOJi-mT40O#*P z8P^u1r}Iw)X$=eY`2xt+!Cm)cWUB3+++#GuXui=>qg5$o+&3W2p{=H1-%4V@@!y{y zoxic|W6uFvrRGL$j4n31FQtro2E-RdN*(>vdcg4p>x<6hwtkvlKi~Fa_X;ekY$96$ z8DHSnk?&5><`O?Q(3xQjnfkXY2=D)4uBI)3Gs9*g^E1QPt%iU44<7!LW*=zvjs4c* z3+FTKMD`2a$q3I)#Ry7d;q7hC={iFeIX|b1@$#dFt7EAnp)GZ60mq8FVspC9XL6Cv z-!#{BZ01L48N9^qON*`SR0(5$$OZ^;40oA@J6wn@Z=w)ez*9nO0gHs#0+iSSUV{w# zbm^Q#bNoGV$1v7_a32HG+ChM6}#+?J=oy=Tv35aExONJZW znNnd1HmUDiZ-lF_11e30JEg}{DwXx()8MXqGC%bN>&l?BCC)2SS>fnmt;s4)!x>0% z*E}UHk4qra@61q8E;$-n#u+OtpqoU?c?H8zcee_0+^)79w=2=zUC_od|KMB$uXmv@ zCTZ*^LW>iUX&AZWL8JLbD^e<~Lq9-PUwkfk7(3*sSGlAvNLxxP&}3)}Ut1_PZsIqd zJN=P60v1aAvg;Zdzxzu3?koNKuYDc#0vfCEjSpjxQxUG#roo`;66Y{J;84UIA%oy$#B^1(1Cz^e%{fXXMzDH;Bbr$f9SN_n~Lqn7WHywMTsX>ZTOC42ZGP zKCv=vwWN0tV&7C>i2WZ+I(7%ag5lzyD&~v#S;s1Shy{DmHbU$ZyMhYq{TZ-50kV8@ zp3z{V>r*PUxCOF4Vlg@;+`q*5ieHz=-38g{(8g{m=p3Pt2Z3foJ4mz(L0a2i196-k zyXBzEMEgZbk^2**`CMx{)?c92R1OoGkM>*kUnDqX3wNWS#he(#*YPv%n3Qmi7NoW0 zDkIidy#X-JWKXA*OWrYBZS);T`=JUm@N%xixe7>Av|~!R6|l8)_exB;!a@pb(z&F5 z>Mob;Z{*hmbBSLQgoS+r{F-1c@vC{UtRZWEe*%1&CUN0v_iQ1qc2lPH>Nz2fXkQUx zEqO z#C)kITGnwThD&Ya!hD9k=Vi|?jwbmFUJZoUpQLFza^|DmVXp20NTPW>J8lT%}$oh7n+LCn)I#?&$4Jn?TP zwAvQtg5DGh{uS{*LT^FF9ZKA@Nqxba?lXN)$h=<-ccqT;`&x)~;};>0J1fkimUVJF zA||TV!1AX)nZ@c!W@Agm#P&S6#I9x$2^0Law2lCfWgTcQFXxRnUz> zlR;yJ9s}Jg^a4n0nD3=R&sFM}qKDy5Q#412DSB0i^Rg8}KBl>38T?~;go4EVK`h%Kiiu|Nb_EyYlm?<=jeFA-Y1QF}*mY!mm}h)4=rV>hIlfM=Et}`&+}m zQpZ_pEg|Of{z6=7J6eeSK|dk(2SbF|AB-1bDa;aLfAA70a*ra6kA(bwSz()bG1cai zehs4MsJ+)L9syN`ESJm%X{kO3;;W*u zqklV!#oLf^6jbW|M9rxMS*hb!ypa%RHv0*2W^=p{>(D?UwhFais~9aZwhEPTB|(XG z?QYR>zCTNd^L^EFzE4}c+KISmdYQ@>Ak&y?%GX1tFPQxfS|Zw#**d~2>_KWm_6D?( zYXs8z*9!CjWZE~a2j!9ukZHeh6zC`M?{pAH0C)oeDf&$;xQ0@)fHKCprV!)YREVo2 z`wPhmt`KXTuD`I>od8)b;n`${hSx*VaPfzLJ-saKa$jxQ`;A^P`V6F_hsuw8+ue;0 zOG#6CI>N;Z2apXA8j@0B8ysyG?5PJy>{+vK5Mp04L5N?@nc~Y7Sj+{*?g`NApu(9* z7~O<)yqboA-W&8TEV7QLZN3!$IGgxZ=zD1Oggs}Pzd?pqa-YE4I}3feRR-an17tgZ zV(f0<=8$Pg-IKbIu?}V2-XI;3wFaf{GgQL70`3Y&OJTNn1l;M2;3$xm);UI38%+dh zz4HIR)G;-E*0GO{z80kxv0DkE&v@A!v^U&g&kMA##3k%c^Q1{zk@0nCje$Q)QrJuJ z^a0eHH@;e#om&CyxDZN&~b?Ug+jbD zIz-6NAn_(q4ecYe7tnsF>}XTRog5V?aTI<%e8Js+&?uoR5Qb{`4Nzagx-xrt)pAFS z`CRJQPwE;nzumg(%Z=&8eg#g@+_75ne2KU0K%xZSM%Rpk796*niXcD|LSx4ioZq%)DSHC9_~u^kT9 zSk-R1#(n{xwb!fkl&3vF`p4GpCM&k8F$h{cn>Px?em8ReJF6+&XVTcugwIN^8~q5< z_PgWLo| zx)|MRbg7Y_AK{H|*xn)j%?3>ndINMH2>)3@eddZovH zS#At1i&Zw9)P{dq_e@H$n+NIvS?pc_^^q8`C!|bgN-ASLP#OKBEOu|h;vAuspdn(x zQP4;sj+Ul?a>?h=YEJ%aRQXwzA>4GXhCBF>adeY$jX~Ti!l_f#aqh@!-#Jr!X5Xpn ztQ<9KAIlzdu4p;hd>IsM!_E&*d|wcYRf#;yfI)0T2*O87%OXc>HouzIH2*j)m8 zUu4&VJ{7t@)#BEceQW(1K;*)TNT{caVZl1C>t^4<7pt&fMv36E`o?6z<8ba)W zbxzE0MH7*6Oihc}eGZ?w>Jqt)AYG#fF{nEiV;WfC%MLnPsH@S*AkBkfu@9-d0PaqK zZTM$);ft0G^QxbujkQVF3h0XxN8IO&mi^Eb zLhOeq%O%gGzTov0knT6KmFd@+UplQX%;)RGmS1Ppau%)e<1G4C(emr8Iqp{jbljve z`l3YJ`^AFwPh~6_CH5-nGtVe#IzC1Gw4HMwR%P4F!!2j=r6p*o&?6WDDD{F&{ks~p zMzmZn`B5mWmoT4oyuo~~^aAS;^Qx{8^Qw&y$23QS@K*@P&J>ykpU(wF?)j9oEGa8? zmPGrAOGV3YmDoZxT;_|0%RJR^nWqf5)G<%hg7e0!MaFsKI8X+wFj|5c_Zet9WVz&# zt9@UxAf?!D?`eyg4R?j(@kVIzTnoo}YWSkGA*JrgMi6UV_hje!N?8|9bcb`?%}slt zQ5U0LM&}w`ZZyhhve7J~a4vk2$%>r_eIHtmBeL!@5Jw(aSN=Ia*9{|{YLMwF)8?m2 zaDxI`?TZgF>SJ`J(Kw?gjFuVwU{t!m`?t4I&gcT85k}LD78UxalCSn`PW{MoW#pGTLepUSL2S(%Piw@Yv@lv5qUT zZ7K1K{jylFKTzTbdAZ2g7S;=~wns10XMU}!fN=g9p>a(scFjQxK-HrIK|4WP9pf!M zaarA+;VBvnsw3KwsVsK)gVdMDLG&edZYKKuX@k$5OxDZXoo%u! zO?I=%rkZSl$=)~F+En&WryPHTJAGZ^FA(p9#LlmFV|VyvoZ3Ju%evh`oV8`$p&<61 zS$9fGu}eU^N=v>8w3pCi5XVv(_c&;Ok-Z2yQ0TK%8@tk1@De#>gW~uln5$?EbeCuH6vlaDUEfc3gFD8r(48OeVnhqAt{nTj6tTM!GTx+#-A$lw5D}Nbey=dnudn6TO&Qyuj47)U4Io8%;!y0*#n_zDwWpK9b~!Wn>5^f z;+Cj?rLF^r;T9W%{8LE}fo)ItjGY0{=|azfE&vtA>K(`u$nwc5qtA`LGurA^b%$3d zK-a(`a(ku}yCXn1OBj4D^md`sA)6@fcsh%(>qPEy$kk55 znk=?2gBU-YQ3tW4OWn7iSt4_Gd@+a?K(8PPAOX*Z{(7 z&V&4mC!Bw1&F%27hKTa)s13b*ng@oJZsN4w!&F^+$vk@ zbBHJ6cbB;I#i*I##*VjPeh}hq7^SkeVbp@RVfGYT-au&q(%UetL>7GJ-Ig|x>D`w0 zqUDPJp_{nlnSAw+cUy9z4L)<Ij5S=k#cgGBq!H(Jye-e^%` z37*C_FJjlS2%+cl(cyC!zL&7rl9w>gyl z?luQYA$FnW@HU6mFy7|aAlldKd0$3M)Sf0T_3Y(MAJ7bwoBCq`j2)Id;BRj-3xTd;|20*ci6?q!D7UyU;`M zMTzHRlxCT1p%LFHSM3s$?YFO&y_w3eRt4GMtOJ9kluha#;kP2`8 z9t)Y)$zpSZe?OKV0RQwgx=|o~TW%)kLJ2p#1@{hQmx}Bs&`_aTZ(}7;s1@iI5Z+n? zjS;#UG)~ChVvpPdkliCPZxOq9Ae#hP?0x~I?d=^;Z9or-wl`?H&=nxQ@fNvzQ+HT% zOQ~>F$~H(_yu}Kw(t4v`jLI+bVT4&i*ok|;625ux1^dn^mC7mVxe}LZuw5ux?rrmh zSe$NyO#7jOjrxN0Yz5C-C=G*biTH9K=nbKTpk+e-B{uBuL-wJ_$}Pt!QK3CS-wWkH zIy)H*`dwrbKs=iqyJtWem*uGz|Koet|I5_7%O(C*hK#EZEqjZM+cTxuN)>;Zg^Z;d z-oVCKzOuvN0H(Yth%v>j50J*x=LY6cu&oStvAYVyyZ@S#)x~xOWcu!)uS3N{djsxj zip38geTlMkg}z*hw--V6McWP3SZFYa?*!rv4Ny~&ZCC7{H(VvNY9*f2kir2@TNP6xr=}QfZB=A7lIBJ?UkT($J1z1 zN;nY;()Qu&8dh+t`gz6jRGUw}2QiF)cS>Ggh~O!Co!9b|Jl}D^o7L~*JPrKAf3-lJ zC8o_m#|U)*X{=axPnQ(&lpe<$kvjoemMUg#Mt6WT*XDs(4T}j*j5>jsj;!l#bg|KJqq~e|7%eb*%jjdH zUyUk!=)FAzDNOscYk#FI%%Jej1#lSxY4-&f+UK1ut;oy7Kg#FSwa){WASkS~Z! zK52sQn-XvOD77>h@BC1PK4+@WwX2QRJg%qZ3Vr7Jt2zi5?;FQp&op8(C}*vc(8r9-Hq*hJDkl z+k9U#Z#UAiZiIz)yU`@0Sw=qZv(D#u*7-8Rovzef_nkwK`MI# zbc)FQj%@7wt^L^fR|W7tpmP!KgN*(1lp^;rZ1t7;Z$a9R{{%V%{zb0*XS%1^BdHEz z?d*~8Y>$$!u|1MHrrk58tUK7~D5DdM`WanmbfeK&qsc~(87)o;_Z&c+Rc73$ApOTp zn4K^;IBwG1AmQJ8aMw@L#yi?1+|o_`i}5#99gX0=)mmRW!%E=xkPU=I*6nPxn^B9D za4u*kwT+!Wi=B002fr=cX^+&!=meuHjc!Z{E6kVTR*J-#YcjNr-2JIGa&wH{NU7Ke zs`wu6=djTIys(EG_I1M^ZrIlid$?h*ve+K(miKEzxZ8d1_w)Y!tUToNZt&%DDa+2F zYb39Ffb{R8r-QVW@o!N46LsMZ7FVc7ifwqmeIP8f=8OT26YaC0$wEs&j|!~-%>_m7 z3lPV^I5Y8uFJJ!2R$qK?axqTSio2T7z9+V!p8BBY_&mv_;k{;|48yV3n~~R${MALi}LCIJb1q!xvI`uO*V*AhqDUR>}AG z*h#w2%T~koJ()N1HRX+h{88|{{8vla5A{qz3uQfEuJTA zBssnvV%0>5XYE@E)raf=A)dNFMCbs>js%4_QvSE!5y1{L+92nH|NixmaQ7kOdLpJg z<^A8>g3uffxzQK4TARwAGyk97j0idY-@dLB%I$i@Me9)Zet#o_|4HZRC;Y1nq}oYv zmzOePtLQDnvOG&@=qcDC7h+jnF2oxY*9fsxZx-@(61$)eV80tO^jGQ1NjQTWxtz7K za1;0i_@XOC%RqOFf1AI{gz+TC-Ynm6V<~oi)*Jqxg45O)VWcH%sz1QLP5*pS>nqFvboJw6(ESp7|5p;6(>LvO5Mzo{&nbmd%J`cbWb6fU$)6zRZsCltQGNY$k)A;0 zKaBDTParC>KUJ!_-qX%075dT;GR7~TG)<{c%P^R7yqp28(tau7?<%0FlD2Lj#xHg! zg7`&5xtTkDfAz0}*O|MqAg$R?gC3DGdI_YvxnF{G2imVJ;?CLEI_fK|2mYRG>{>#r zUqpZ775|injIqkP(~JfgU1K!P=t-m3jlMJ5?i=r4)0AS@5v1?z91hZ5&=W!Ye&A+y z>MnBqL7Lu6LE6r}MdWUTjK1K{CwKWU#-_HqSE=cEz+?-IJ~Hy>O2esB{Fx70&6i!i z#hDJt=OaMRfOMVV1yHfEV_`kW!D0#A<&)k<=Nko(H`F zU%Dr&Q^MVF5O)Q!QvRK%eT_~qy3%N((EeCfoW)Z@W8){ZOgv40>D2<5&;q1O>=DsWSVOejXp7|_mkHi1=18Mr<#vw1(OyVxC+QfLd^lZznJ(i#f-Tzr1i zXt`1J3s%OUt?u>#u^v=+?Lf4}Eg_KJcs&QC^O@nGUqpL%N+IRgWvuOU?J;P}y+&Wc zchKih?EF86V)rmC^!4TCu-Hm0egJI?!dZh~afV8$1*irnmvjYbY4rouhAiW*1o57* z{^dO@G`pp9^`XTZE*PmY?_)Q>Lc0kT>}l{eJ*cs`n*nML>Yh9aVjgr)UNBk#Vz}Lt z&rSBDQMKRHqP*M9s5z)5{44MFHQ50s>tJ+*X^-}@YSc1*tT*({@1OuGWqQM8|=q5`P4sAa98d|03ccnsiF=XAu zT}z`4$Ed8(T_ss?b{~%&}8dO_A}^Iv1O}JUtFZ2l{nv4OI%IJ*al0i7btO| z7ib7ATS|G?9CW7m$F{2R+XphGwjfPMCzJIs*(oMF&t!v5c2g=Vad(3lT8W!!viTvxMwb{}XLPI4gGNsoy<+r%(YHpG z%KKRDWt2BM%V>zv7^8=bW*a?g^per4lp^;%NXO&bSBTuXC_$`+ukdSR%^=gcn*V1` zY=2P0A1Q}vj~=_AJpvXNi7%%cT@M;8vPmX;9mH`H-kvb3ToLza#T~zDHwYEe?hbc1 zi?$Q!Hlfo%>Rhp+wB&}ytEgT{-+izW+kSqIr<(N-+M&acpJpcz8JzhYqoS!-C# z77MOUFBIws*^40UMcFFyNgv2wg)E=2c9IIU{Lc?<^T|NCQ{oz)`gbX0I-0xNXra-% zlrnB%4zEwa7yYxta-q_#@P}KIeO^YEbOWJ}MXZ&9S#5KW;I|;M_ z?lNu=NOx~Wg7{K8W;RAI7;yzxOJOx+8hgGnqUH7{WE&+eq4eu*?Mt;Sh$A{HeHf+j6={~V?u0NM*kgf(=F1w;P7?c2pDR}OF{lTs zBij8yntw-wH2=;r8Ul*pu6r^Lv?~Z}(jd(>KV~f)<#JToOnk0#m2cDjFCv9?l5^9& z;Vz#%3;#&CO%B>u=mStYp`Fji$s|zZeuRvp;X+?nv*|9MRNh7{^2zo_wTv32RA}22 zvMyrV(&#{=BaM0+4KljTXok`AMyri}HmbI*g=W;*=tQIbMnhAI-7O$(tGwBy`7*<_ zFMx8AS097;8Ye~vReVoV2c+eDfKgAQOO5U^ddX;=QRok9Zs+|w+~^WeUSe>+5o_nk zB6|h0O?xEvL)7A5W$1_0-5T@He?Rbg17tI&v({}})l7MjV zwb|(S>O4Z%|ptB@g_8Zz}X6~nB6`kRx?E~&e z#nyd_$?zS71$&j2?(#a?dRn>>Mt2)MZ1kkjD@J3?mq$|yJ{O~0;_pQl(-xEEk~g;C zbNE+4#`$~J!I$Fy5Ab)ibBVuu9V{|#B||k@ zLS3=0tly|>pdE^sMs5U1`vdMO@IP_*Yxhwg$=!#b9SthnXMY?r-Thx?w&k~v+yrq~ z8>ANPKvPB53-p-KWuRw;#+&TvR2I4afEJ2&zi$3pfh*MRHIdZZFu91iUd;!9u9FG7Pse+Z2Rm0L>x?gsI!6?PdBE@Pl8 zxZ6ORaZ{nKCiJAyBBOVVRvZ0kRDB2aIpcOQ+7qNqqAY+}(y7nL~OSWmHV@!6o(G@AhZWL$&e8CAJ&GKXs*$_DaG~*R4l!`{>9LL4e)KlJeehmJ`S-DCm{TJJ@i5FPxJ2zqb>g}i0?{r9P%8ExqVSiZaOx$TaR{d( z?nSs?OWJrULj7aPHUF5WKZtfd+-($E0n)nh6KLx<7#E(3s3KHhh@Kvc-1c>G20>&k zLA61#>jA1S)X!)Q#u56m0`s()SPX}DAE6nb)?)E8h^4Q$GvAxRaQ#W`!k*fH!6Ns0 zY7x2js`#3-!HD9_Z{DK0 z0Ac8REki;5BnG~o#%?%dng?S+I;I(KblS1V$wwLI`ye}CY^Q@XMGK6!d>WZnHnvyA2%y=3&3(FY**iCOopQN;#cwu@0Sqt-^9je4b& zPX>T=gfblTF5;JQ4}jJ_%rKq-as4#wmKv=x`rfE~L+@@UBc5zk+uc$bZm)n?GPu2B z^hz`BLGno(llc?z`Q%WObusE;)Z6Gxqy9$z6l*@Y*knVEt}_~AG{I=9(JZ4SMk|fl ze(YoK=kfVuooT-{@+WljNySD!4|XuBYqW<^OC#=2Ypxw&vW`Ywjd~iLYjjmgg(p$_ z;~T)!{@4$Jb|uOje-bbXZ{zwC(`diYvMjMuhVSpkV&PjJ=GPcMYv0K`nd_hpyFrDk zu%9-?w;NjQi^D&qpNuvdRch?DHH?}Vbw~-{?#-xsavo%?&yl+Vq_MgqmBnr`NOz`I zg0zoVYG2r&Amgr9E~&Ds??r2ZevuUI2Kob3>W(eNnT|I}tsvVLq&G`;28DATvFi&h zV~}xIff!oW-44?BHU*?{nFp#Tw(o-)f+F`DXiuR!yZIFP8=%-*g^ae5JKE?1Pz&*8 zB&dVX10XFozIn?qP=b(Y%AW`Em7rKou;;|$9mw92^@!D=JY<;Hrumm~<#tzX?1F8` zp|dfPc^|37`4{u0CM`V_9x#ld&}Ln@4IT0{_!n%0&)yg8dBK|H{Prg0=_87 zzHgU7tGO0@xvzfYUKZPHq2)VDvEv^;cpqCwBh+Tx9niiG$|ci4Om8lknNr3r0_lGr z-bo3kdmqzu1YZg%gz;(W3r@0uxNnege}J^*v3x(2`1!oTUD~Ez8&VW%dq~liwfn-C zFT`hB=$OmLC6{!9R?DahNLxU6Bi=XQH$HaHV^vMH8`1Bo%-=k~tz@`Ut-pB?yA#Zp zentb0t^%<|;Wisc_m3tSJ!-@|UF!2QkSV=nw9Lq#yv1sT$+*J&wWNGQD$6BRoB2|$ zVHBr?v*I8v-!?{_jq*l)j0PGFHoC#+cB2Q4o-}&N=sly=M!yXn#ij;Z`0-jLiF7vH~)t?~QiY)5~@-YG!nZ zQBR{YjjlAh)o8NO9HRwBON>?+eP;BX(eFl;n)~?GGYYRhH-}8;_WnLLW-)7Uw@PXb z{}WRAE!NWxa92l&ZMC`3k&qp*356D4OnU+>bT)A|=peD>n@+sbRk+G@IkfB}urmNU zT-=3{DdzLjaMw-TF$UUF7}JwBk@Y!R=L?YwbBI@A%l~=lY=(lPdub_P4;KFESRnk#@e72evalvo+bp>LqA_S< zvM?sx7qZJF2FHN(J3Yu~BuIS@>F5Ig{x{Px4PodXDHj|4@1*S`_?IAUu?s2MdT*?> zNqTn$X}%n2bSy}3$P6^PDb=Djz2d*CesvIMU7)>1!gvuhM(7>TWTB5hPYSIE?JA?R zKS9q!mUaG*idna9M&puo^;3#nQ_wQF!#Wj+`(>Mc^F9%ui+%H2V}^U;Oxku#{lh-i z7l{4e-jdw>ZY1t#!}h^h^kwtAk-4NVwEFc6{U6)WdWltkXe++WyuTE*9jNdN@$bJ2 z!{Lj5UG4yB>*feW+iHlx3}~yD2{-h`v3nj`9V@Q}aa0vMTI?nk!51HA%oU*3mtg$6 zJ$TQp1$r*AXaG7$Xm5~y0|EtipyhR+2JA!7QvSzpw)WnwK#oap?O*C zPJyi3CULnKvK}HE4(hpyc06P!Z6bRFGPZb}?Ez`-`Y%|qTQeBLnLGCPeZ{{pN_!95 zfskd~S`cdlP6UA1W{R!w#jadSA8s{}o=@Bbq+`9^K|1znZ*;uTg&?++*j)=6EO8la zvMHb&MdnL7%n@_R6VNifx#V>tKZ6WAdl|Pn)nY6P(!T`$0U9elZ@Uj}qf07lfu@79 z&ijJ%laT2eUOP}vS*<-5^d#IBp2^B#9`&sF=UaF9^H`y~Q(?P6+?@-0L44tV_Si15 znvQYj5|MQTEfaTx;BJ-B)75c*TD1J*56dH$+ySlT!Q7PaEdy!Zm)qC(b>0HK%1XV_ zS2$;C3U^mL-vEX`yTYC&^Fn~ReFcE+|6Yf6{ehfuG>M2 zmzLp9sfAHTqwYqh7+q*|ozYmM2aTRGT4eOP(OX9AjD9n!)JkJj7-znLU0veL*LsvV z>m{MDg9UR9Cjvm+9f|RfnU|el+G~s^o4bV|_2nbeZrL_i_`Pz>h0M0%evzvwvGOlp z$1Yg-cd`E|e%wXTbOhTF#_gm0n+P#Nt1F@L1gGY~mSf)<7V0keT+AY9yTL-^5@Hav zAuhhXp?5@0)bU1e7qlFWXbi$X4t)zP%&Xka&drb!a>(c4pKm>}?9J{fdBDF8vOdS| zEW}w~jTj2j5kRmlmflb|g1ZT@ZHIFih2nd6UIjA;X!Q#gdifrZ z?Jc>>FI{UP|EEcudr12xyj%r$+8198;(sV(Hx#6&sY1_nD`XnRJ)nan_Q#+HUho*x z!M<4Od{Af6@>_AV&?Tu1V_Vp2Kk4_K?iAvcej{v&od@6ZVrzcfeL@YD%$N02&2%#mC-$#2q`nMslBo z`wA?y-us#uyH6pTBEE1BO?!)PA=A-o`2&kb5INgrj@%P5que=Rh8C9!G@clw1*jEap=#zVVAVi4-H z&y8I20JNGzex157_TF9WzHjZ^3#jd}B?^xX8WoTC*esRG# z;vL9771??abEwevFUXjG*jaCb(*xoz15#NB(0Y;e1@)9KZ0Ko*L8iIpdx1iqM?tIO z-Pv97pG>(^^G8GcpO&=t3?ZNWonWn#KS``OPthFbS@z!` zL;0k>6t;@5A)_{*{9?rRp|S}#s%_X)iXC6X_)Eg5+7_*O8S^yk?JU{e-@a`Pt;WE= z;){KItoxM00yze44WY2A5!O}CgO={{$(Yoad~#Pxxn!cr7NmsN`$5cceN&OM=-7pD zL#)0|EwFxq(Ymgl@6--28pvuCXM8%-j3MJ4lU&ja#B}76BT~Y7JCKgThkzPMI(&Ix zMhRJSk=+SuDfB3)tB~&rOWgvMMAbpFKbMez5iwkynLi-4F&ai^!_YAdUbdBdv z_@{Ya>%b`e+w~9o`|;53kZF&!S4z3$9qg%VD*gIKE@=g=$~vZmTuWC?@xnnmk4ITz zUxRsHteqF8<7D_UMABPnn(q_C{^d?P`khR^o1*c%2o~3g#m%5wL4|d93S@UdmQS8c zsZh%|-X@CeQfQeD%mP8$&S$0d6s^LDwOH4*zK1()zcmiR%u9Ne42Uwkvh=X8^Uo!r`@Ud&0%5ZMZsC7y>Eo>Cd8HE)Be?J~?e88QiaQ<{coLBe9x= zzC{1WatdVn#`#4cwY>qPVT4x>`353iy2W2zpyk>F&cTB;@8^SBNoYJ_#ZhUoRhNG{ z$MZUT;mJ(Qi9sF2zb`0d+zQbXT7*Lh4CgrDLEa)o^`NDvUkj= z-7L@yp{I??#(utN8T+?|82hh;%DM}wRHD0zE0|YhQ>nSjTvK;UrRFJ9SxYRKO3f># zQgef;R2fsLGNw|Asnon;DwX~&ei{SDZx4%~`odDsSTTMFi_R*3PV&p2fZ(i!t> zAlBW;y`RcT+&7>zV1c_DskX$GJJh?Y20BOlWBjx%Nt(8@^{>CV(^&nR+7!|e@>x^P zbPN-pna`SX=CkJ7-{rIV!hF^m#(dVA!`h@&HXZ772eY6yxYLxEO_A1@vhAZ-j_dkC zu^g|R=5vW_WI5i#WUWkAwiH6{-hq56ao(baW1NFli78SlTgO8k(wt-+(o$s|QtiKK z$&W}Gv5sqAG4GX__p1Hp_{|qz*arW7{4|xU8(MRQg4DmVZBWz8_-T3>KMjp_Lw#XS zqc!JgleI^AESLBl22yu9kh){qiq#VBEy~ss%Jf_Dx%k%;zVQF6CGHduTc~Q+i}ozY zz6X`KfvNW2m9)l`y~RfHk0o98J@$3J&n|J7mkGC#XdA#?6Cr+ul=uZ}EwbUTP-4op zeAzzQLtEl*gZ5~l`%<4v+|*R2Y3n7v&_7MvW2rmU_7?4Y$W9Yq7Q!9-uoCwYWao*D zJ=X;w?BqgrF-UDM2i0&ZA>+Bj66^mqwY{o{?KNWi8EhFV^>4Vy_?>24YPfHq9Vzr1 z=uV*uoiGLw+RmsZNG%$G)M5{lWkIvVqHL>pQDhwunwA@D4rPCzj`yI&PAUA;zWr#> zha&U&QsPcbwI%LC&>HdYDiHs1qpAD`vKnp-WIqbs19I>GUAVzNmcq8;?m<{+3?2uS zLWZ>wknWW)1=SE4V^v#d4P=dlz6G%aOWe;O`cmSe&N?D1p7LVu{w^-fC5%e&ucgpV zAf_DOR1p8#gIky&jp@lC{kI8Yx}U|bgV4p$YC1-MG|rP>!56-V04;lWo1^+ZB!>giQ(^XT{_L};aXzc~glF(R(0)>7^#{#~FmAE`cG!llJnAzD0aaZ%f{-^);+wST71^TmRF z({i&d7OSr8X8liva%^UMSKb6L0<-m ztZa?AQDpQbq)5|Swp3Ni6lwmkR8__lX?pqH*Yq+)8Y`wqWlWK}<9A$ILfHu+JL5t>`>GC zK0;f!#*`(k#4?&A7EF=mM%ff;9cPN(5O-zAj-QK+Wu*SGjFebLTIMXH3Lh|jEF*0d zMg{CO~*bOE@P^7!R8!)|Fw_^8z7k`=eEDyE z!8Z`KNxyVq^#6Aw{kjrv+49g7{ar7hu`1hUG@sdKG_P15j5Efa$X)(rr^KBN(pU`w zwXpPRX)(QO%eZJ5f7c5XE00Y}i?-@B)1k!jXeHsYrRW^nr=$2-fZzSYU=c=wc%vgt z2VNUXDR!@e_;v@@c0u~5y4r_h#b0dOg7m)%<=|g;i9s*OjuSc!bc#@aP(PtdK{{Hx z8l-J&gwgFr_ku1Gi)pFs@6(aM9c~lA-8Dk5f^HW22*mX(P33JO1K=ir99R$+Rbr>BkL>`0@_v4%R=WP*F#9%(OkvkH; zEEYNi)E#SX7)OD0RCOb$?D%ifJb0~47}!&Tg{It(kv6r^d4P{gNH4~nu+_8EYd~7k z^yOU%qe8=ahzmXU^5*1s@!`4K*= zyI(=ETU*z;UMLxyM1U{+Qy6Y&fwatTOsRMtumwaPdjE7(>SZNvENuCYFa5fdz+DYD z88WT0tf$+EYzAaI2t5Vj=(UEE8VlK$!wq4SxJ9tl(d$wp&PjHIFQJ8&xYf|Iy_LAH zL0o(Nr!Y3A{%L=ut5&RkdyCK89qC8)4MF>gEDPFSsMz@a2xxWm&$^-Gd$y>9#UhOF z`@&rgGOb~Gp+S)K5*lGN8N?mRP1Dg^+yyO5hBJ{8_c$zgW>MSGnd0sl$n=kjWn*iTZKxx zda9aIiQC*3ZMFt8u>jqXd9xF(Qk`zTxQwH>ucElS+MAlB>>ccfVqYf;CWML%;F z>cNGOvCUuw1Ei~*!$5Z-9T@i*J#O?ONY7v_qfA=aCr00ZbT8j^^DWQ!KAQIvC5Ng) zJ6WhEXtq!T&|INCKw2`K1-`5@qb#(i;?!gfcMwR|eGUf|>wUDh=w|NnAjY}Gz1{$C zIHMoZ7WFp5z-a}9u>utOrcFmnTb}2I@9#$vXMTTmd~kxr?@Xf$jjlBsWfV&0-c+Xf zx8eV=_ttS+RoVOSM&Q^Y#%+Q==qR?LVs{K8BPNPsq98UVVquFqHj06Pj)hmwwUyGBtKtH3rGz%{R$ycufwS1rHWsRB(2x}XlwKDW2kn;B@AfDgA3~PJwqQ!&% zR9-3xErd|f+BF()b%d%_-T+8?r`WwhfEPqn)&^hAV8ath6+;B+NZ)NR1o*e^T!HlouJPw3=CJ3rB`TPemHoM<;E z8c(;0_r?@DJfS;MJf3z{yvM7dO54k@pdpN38E=D+AR3L(XY6FnYe0)xzI_bD{KZ+| z9b$g003?rh0_tvWP8|f)4jd|1%L7%yKS5jB&=m>YkP!Q_jutu$v@L*Y_a*6}f5Vn; z1Lzka`bCI-5u#s&=ocaSK!`pNqOXML4Wfo{KlCyOabC2FYAxVt;q75nw*jvpROo|* zzOSMzWIN)|$-^jRZlGPjAs_U#w6XRa#5gnT{-M0(1tDBrgcwO6USwTOjP11F4?(OX$if z@?9zJX=|XRa3*Iu`MSXx|k^8<1${0x2Dr0sRZ1cn==vD9gc-KyA&Z zPXe7_w0D72)_ws}PA|3#)>(~r1t8{m7B)+?9e|pn<bgEgk=YJfV?5BP|{|)N?)! zno9QQYCQS$8N{mu`tNx|uOU?E9da1$W1tBp&r8_XpwU;7@`c6YZqO8q*Q{IAHaies zuF3*=2U`Eedyp(8m?0qaPV^rNB~9{vHhIe-^mjw6194=T1+JUXe~LHzhqRu%jPn4A zRx#T8;8-frRy7*i2&b)MG`0s$+tg_7&}%qt8>6)XZ9AaiDj45o@>2(>FVOKjQi*v0 zt+(`c1yU~TR7E(I4z!gpQq+5rF}(9k2FgMBHnQ&mYxop`f0uWNvkNOdEnbrv}KuaDR^ni zGU3`PDyFxoHY?t+WOHLFmsR5MRQ_3x$8sg2R>w$K^@q)D#?(nAOG#~M^7{5~a9GP~ zGXmK>V|hid{RdM1Y56fJA?Ebupkc)TG?jzqy9#0C3M45p-#qtclt=ZnU2WQ2a+%(| zw(E^!#yNAV#hYF)Ubja&hFhrneg)7}1J+Au8zB0i+#5X*TJ%O^R^fDM@^jwf_n0kZ zDQ8JkP1(Oh8{3En!K*gnIiN~l+r-v!R`cUUv&Hq6S#=0(VY`buUdO6eP0Fl!<4xnu z=eb&%S(Epk$vY)kdTv5d2iEmK`_STD1M~$@79Ip*iKy*iTcU@ZVxfaV?|m z2U|!}yp4^PUmx1SXa^%6k>dTsXvb7(m2gT`N+k?T=+Y_@$BxD^q{<=Mu14d0hLW?u zRfEGY22p4a3%vy#S_i%dh`SzHU_b3+i+wE~`(L-Om(e&b;oeF4YAD;&gH7*akgT^v z{j>20yTu+ zfJO)%6vBgs7CtycS_)mbyW(YG4IuRan*pf|ZC9mXMh>LWoX6WI#XAy++Olv;qBSO^ z*j_ygUhnijTXyu7A}E(s;jYV_zATvT!~Qm z;8q~5W5#x<_N>*+OSup7_)28$X`pASwrHjNXG`U{WWlL_i|^60qkc^txXDNhT6+t2BF%^Uj;}l19xRA7H=KUw3okGLfZmqg_N`Cr7hkrpb70=MXpz< zJ>tzMFZ+O3IelzGGoP1bQamqDhh){1OR74Qm#dh4*Mg?f)U;g1j0haBy~wncS^6SE zJD6m)H0uCig$}e04NU^#-V<^PXe*<&+#|L_t$=ni+TuXG5u{c29!6Ugv|ff-!j3TH zdqriotOt%$5D)ED)hetBfTj@?ODy+xvcS?b5b+x9J#O8t+5_<}Fq*%-J{wYWDQMaQ z8f>UP;te%)dO~aoRbJTY39%(4N6~9{80~!U@^yE$Di0WKFlaq&SL<4!M?k~O9Z3Fr z256Mg-US+KDDIfxt`KPN80|Zt&kRiiS_Hd&cyFKwdKaUu4)haH7B;BTiuP$I#6vGe zji)^9AAy5<%WPH&o9$z2+ZplP{_B7(%1XP|gV-PyR zLV1=%Wyo9QaR^m;nF#bC;vwgOo-`E8OBS*{bqX#E938%3@|Z`%t^9c?_fu4gU0ZBz zvv54h#;2&YIQLNP7ksBR3oehVR{u5~G2d$Yt;VIq^s0nWirR>`45hv&%#Rf5b`opt zM+mJ1mY2^AF+W;NB8~Z>vc^1L+-S^mh88`(y3s0-e3#YL#w?Y6>l;T*xh&npXbn~S z@b^g|^#P5^E85cCq%@5ecOIeRC~#a=%JuhjCO27r=)o2 z04;5yTv1n@xfnF%>Qz9jJ(X}HkV5ZBp4FrXi^?eXf%*&<7C!@948-Ic`G#}6*h86`n5vW*y&oNp% zgkEQeqorF7tqYpkm#u(?8_ipW=z}cmh)})}pN0K_G=@7FXgf>gARzs{=@y`SAO)-Q zK#v%D1<3p4QAVS$M2p_Q3*3p9BdxI(Zz^c&#q({%e-K(b3jYc5)UM6BPfUlWZGO;b zpURq29#aY5p%)@AW}}eusp(~VCB)H<5ZkLCWEDcotkM}EFGKFjs<^MidOOLi942gkg|!JcjrLwR4xDz~LfUbI;~38moAF-Y%{hPcw^ma^<> z=Cxd1Z5zX?Z${HT*X9;@IyxhsYE(}k)tTdgRJWK5S_ft>sQ`?%Me@OElaHT_Fy{;jq4VYvXk+0SCzFf3!|z!RIawObr7FLk4Gr~ zVj+YzaX`AZ} z@wAN)*PCuP8czAdhFpDxulFsH+Gqwi{!N#(W!ZDCQeZ)_~(3&@d(jI@C_@ ztOB%wwTW8+acqX(8fZ1tJj4NF%Zk++pk1xqI1lK3lX5MP>fn6|y#TbF@y4;EO5JD1 z@ex8*CYk5eG6`S(XqM({faj{x5lg$*M&uym$*P}$*c+(zZc06mBhX@<_L?$tQdslU zr?BR=_Zng#dE-216*cfa6NENJw2?dZ!#=4&GK9?R9OfL1qcy8&^2 zi4$8$hf>rFG|n{LyUNw|jN?dzYW*OegIkz=UJ|`M_4Xz9N1m%VUk!Qe)iAaKVo!&8 zEs*-CM}XKH;A9ri4rW2Vto1b7#|Z5&3xLi^w)D0hF}4v|@bT$yi1$y6$CH^?7~&}H z1|Ze*;XsuzThCZ)=K;FMXceHR4J{4y3Q)0LdU;;YdNsGeYv_vL^)_4O&*QCw&S&zfA0;~S`Bt0# znGT-pueBwgj@7`Cg?f57YE;|i7WxXbZEuL$x|_Vbj-Ez~I#3$G(bqV>1=3xLKY%p1 zci*#5)=t-g`^VOMDWLww>$Bs$Hr+B<6Y=Ex=w0Oz?NhGGFL~b6&C+_j|3s+j))hdN zz?srD#*vpI3pR%ZN54Stx&_-;t-_sNAl5Csy$htB3#PoR9hbZl5l{Kcc(+3DjM704 zp+VengFy4P;u8oR{3d+XlWKc1BXJt z16m)v8J;d|7B(@&Rg&!t%>~-7h86-kz>xP9D}Z(wXiCv>K$WltXeU}c_AVD2+5j}o ziaG&lo!{H`*xRFCB9tflaia?8a^uaHwd?<+WYvn>Aw@Rd2c&z)BMtS1-sen5ll8(^ z#Wj?e4sVN9cD0(~emMc!xPFSB4ydVq8CzoCyGEOJ$+C1(6C|JKHaC)TPZ7!hIHo{y ztv6W9)S};r(C;mjE!;1Kbol*`e10_lieB^ddc4NUm3o(X!CPw!^>W@*tmod>vJ{JB z0n?keWyyjqjjykH*%*eDuV}WEc_X%tUUF1o*93?5)z&ri6r^l!h-)`p4DmgM9)`vs zw3ngrKt~wjX{!E)-Um&qV4nkN2J{_}WHu_~#oGwz zaYH2}vPkfNPJp8XKw3PITp)-{mlXAQ8v2;LjazV20ol}4c3 zjdpkyVY~|@ADml7^40w&FSa?_B`fN<3LKA{yc?@XdPgU{L|9=)=u1X>xQe7>oQ1L- zde=f@*(jF3qTUz}dq9v!>&w&28;2j8jyDlc^Qf~6uVrp!7VlR`p5M?Aj#)o3$$b)nWbGx)2ioEmkE=L}wexn0@^1QY9j@k=jHWE-noePrOES=q2)&0eQdKis*4=}{~ zp&VQZq4Zzzz0JcxE1quaZ#4SUPq&?EG(X+83gj(^)#+l|d{5*eaA=%;rJ)VM@tE}s zU4hi{vmOn#(7ixg(^k7_J?F_;I2^RQ5Q_WBRob!;K20U+}KB^sfMPr{r zTCo-AYI*6cz<;ah$ilRgBCe7Y$A6xeWj4o9wN0v9Ef2wJ#c1Zj!aynqWxUhJtCdoZ z<9G8=l#KkT>ZolU^Ss3}xdu39pGpsP1X4-dGNCR&UJ_aUYEN``N9dy9?KYrKLR;XR zrD(?{+W84xQ$@aE7bwk`!U*8aS(uuP00(cL#QGN7FdF>gGce6M)_YM;6Wm;<-({M41rZkKjAtS-1tXPc7b^KumcS zo&cf+wV`ZZzB1YHD7fGZQoovxpA9L||ka+n_~FI-XL?t1MBc6$6^lgGCcRo^2_hY|uUrSMy2-5S z1YV_Mr-XVYd3}Lyvv{WhDQ#CIG%UrtFU5NXNb3>IrR<&~{bSG3*G$64o*7me8IF^-So%gbq*Wn1oJD==6jJCUj9kS0r?8LboP# zZ$ghH^mIbcC-h1};}Uu!p?4B`KcSBj`Ya*lky6e)68buY{*(~&SD`JAjA*WeS|`NZ zSG+|NjipYsc8Ru9LTe?oQ9@fM)HR{q5@N}gynPex@PtlE=)8okOz4(`?oa5MgkDeR zohoWdD|(8x)Ly5(<@yv-)NXtWq*k=qQL;V@t%20r{|#uIwE`W0)JAL!BwpS|PDqkdJj$Ujt7$O&rI=#qd!|M?^};?+7jLd-!hX8jsNjUw~KWC!pEBB`u!sl!sc}qPRDG=#1ze&5PQUhO$XOssO_)P(I%&3@&AbqTECP@j=q`|>nUZ~Kb`XS zx%94(vkGqvVkJxaI6PymIS$`;xc!!T<%L$QnygyWkk111qq%jP7;oa!ENdKNs${QS zwOL+kT*pS1qK&|@xuLE=dL@G|l<;L4NohPqe@~V+9l1xbfal4b8&s3Ec(6_w_2_5ul?HuM$Q9>1E%s3B3n&j)i^-q;~?p1=4$h zzXR#D-#Lzpp$k?~7TN-_)H%YMH)z*D9!fCK4Tc5)4FhT&j=m{`XAJ%0Um?6~=op~? z0O`K~Dmf#;F~w-HL@4)1qo*q?i3(lXyjE5=UaDG!zBZvBfDU?udVPQJH_&83)SHDl z`!x#_Z>L_CUG_6&7O0>3*=%-salbJO3qpz{ub9wQKrh-XW+x!^U>u>SkM}kBeG>=U zu%ZsqemA`}_p!mjGL(hWiL4Y~2&6Ju#&HEg^&VsVV{o$&|16BQ;KKNCKriiFQJU>L z>flO@5Lah}u7$i|k224P1APFLh4DbM8;7sXd;(g33muBp0D0(V(D(;?&l$WM1KK>` zMSVVAbrt&;yM<~EWr`Ly$*Ut&ubOQDL=NQ)Lub^2xRkOsVl&8VYw~s^x|fpo1X3Gu zSkiVf&`K8X<3S;;Z^*4W51}91sD;{gvQS4iBlIY%Q4ay_W}z?I2qX)xPKdni%`YQ>F1C120ZHFmDfwGpEbzBMhKN(Iw#ZvNNGC| zNOhlMe%1YYR*|EA>m#f9rmQ3%mvo$-&;3RoU)Fjv-VaZb_eEHxlJj;#lYumT`7coKF^t#Z z#HeH5gc=gsB%wVLIxe9rfmBLv0a8ExIFNd}*MVfg7YSu2Vg1?s;-~7oUbaT)QwVJc zH(-3Akt+Y)I?B>J9JE&r^~5WT3SAT&?-*@0AZkNz3iP|7!>cF@mjSsaCxRmj-qO%( zd0JE2KXKd)$zPhh`+%kz8V#fxKMqJfmXwwWW zc(QmK!fHUW-rETG8m*u6d&-NUO{tgS*dR&XHlaNeqNQHT#NlPU+p?irnhphrJkEZ9 zKG;%T^QPY`Laz(n1gtMSL6qDO?t_liKx+t31F2lS0i^K(J-&`{M2~0TeT1q8c#B>Z z&-2aWeFo+r+w_%?9W$|Y04axI(W!~^mo|@d+ELbI_!$UU!ZQDZVwxBKlY|Pc| zE!6u)rf5BuoSIWAyMbdD#OpSoXBE}Z zl;@4lNDqOIhOqaeA@ntLDD?I>y-d-ChCGi>M(AaQ& z^ty!}4D^wq<1hbDAKG{;P`i_U1N182h5>2D@(@t?A+{yzC8uvoYF}A@Hc5FEsPAp8J>wF3 z2S}1X1saOl(>idpe*7rL`x&&^p-rzXs8#lOgN7(x>+o0=VZ3oF<{HKkTknQ&6?*nD zHfr(rfxUNL7#u#Utk*iimNXYy5*(TpwFlxK08OP_L&bIe)UD z0*>Ozh6baxLMz~ByjC_^JlT+immrT;X`gr<3*}kU4S|ZMF}60EuXwS2+1_aJG=|>a z9egi&$3yZcLp*z=oOu^CttPMR(E14d5D0Jl0-ZUUs1p$10?xvAK)sB%HxN0p z&=*J?PCFhnzOj&n0YLYFLwWfHICOG;ilKH$W28w&TS2y z2YLG$8U)lA2wyyja+aa%K(jhi6)tLZkx zYTsq@^p>SEO2G-Psx7#=0>ry%7@+~FZQmJ)FG;9I&HWP*Q^d3tYqu=mjW4}D;d&dt zWkp|=-6dNX+As=A`R4N6FZ;m0r7T`d5z07d?TuEZ_ta`=ZHg{RsT^8G<$BBH#nYs! z6>C6mCGf;sM?*aEMpX1w{j@cvBMbK>>v>0FQ%J^5j$}PO$vo1@)~yWrOHPaJf+H9wDCS5#os+A)e?F z;)xz1p6C(ci5?-I=n>+H9wDCS5#os+A)e?F;)xz1p6C(ci5?-I=n>+H9wDCS5#os+ zA)e?Fngc!%YLyUA`H03-K0-X@Bg9iaLOkUo#8W;(Jmn+AQ$9jG@fsQaV1n4|NwXZ5uiyxML(-I@b;Offk?xNfP~l^ zTnWi&3m`=tOz*Wu8xNYi!4=J0LBkjrBM+ur?|ibas2y4Q8BI#=VDX}N<*WNG9lSrN z9+tNPg!n?D5MM|X;tPpF6JU$brwM(N&~FK~JTt~?mC#}dEtAkHKXG()=p zE%*yd3C}-E%HE(!N1rMx=5)Sfsi`$n*K7#6S@{iH+XKXBHY)2WYrm-3X?XD zj8=mbtmjl|lDw|`vQ^~Bj|n%k(ENw)U-@4-l$XF*)YH3-NS6{&7Pb zkMnk%QZyPgwR(JIt`c}dOE1syMckJxlrI!3^k+z}zDsvj4COuHw-65}a6ps)^6Ai9 zO|22iKLKME5on@uaOV1@p;bWBn_avo!oR{|h5_39w-9X!q!qL{qpE$SY6paBZh8>V zRLDa~-&%h#(2P_!82_)VinibcEbNmH^0w$MZ48z5Wj5=TCHLB?kav~&%3lum^5TAO z%)8RYeYFnbdWLPkl>2P^1&&GKxaA52V)C!?^u^4DkLu`E-3{kSm)h-BK&7@3e zM4tYeyPn0%w^v=7z{_1)*+L6;FOkxx2~wh^Xg6W=ao|uM4KUsq3XhkhgKyZMYw^wi zFH3e71_51WG>%ZNG?Y)T z+t@}Kan)3|jH@f=T>u_6KN0eM$ z!!xU54&qd7)dHn*U8_f4Q_Awqb+|2M>2;sFEm2!-55`hbKfR4hF0-XEZCQvZS9-U= zNU;7G$$OmodAS<=_nwsv_eS)1xem{_ymvn|DIQvutFFUS>DpKur-0Q8 zm2d$H@TfN$v)u)@+}I4gxyJLXQU0i%O>?G!RIya9;{^tn35Q{|IgYI?ZTz16^k55g_S! zE}>TwdK*YDcz*=MSDPwfGLTaK6VOd2rP;ZdQ5l*W=wU+(13hbKDWKPYimk0m_Ma(N zD?svlCfRHJI-s#!>5UYYHNA8@3Gwj1wVrXGZX3k=!f3kyt#38{K%oB`Z6J_(EACLX z{GGgPky;sIOQ<%2^`ni^c$a5oL&L$#{sZ&Fs${&p4#eK7=!1^nQ2kJkldrW~S}13@ zN)at68(+zjW1&MSV!Un4mT0M;Ma+V?z)fvwqE%V&8lU%b53|&B!P}vjzj*rq_AU7a z{nGM0+?hA~S^>%TF>UZ?qH*Q3m&IEXG>%o&Dyu)D?+-V<(Uzij%A5Aw8eH! zCFl6MDe@N1)7CkqXlEe3FTX#K(tAD-*CCE&duvIormmlDytP15MslK&zUcM*>OjyD8q+Ks*&DPhMgie}HzL*+-vVZ?rc5 zjK1eTEp9j33ZUHwRP^)HMl17a^bk(>f>)Mq4kYV6&sk1iE0H{5hLTYu8kM&jLbX10 zR6-X3sW#q}&>cYhp9B6RnvkD~ZoJNT%01o~NV(}A%5xuh`SViebtaZ8wKOc*$|Lo^ zF4@!ZQL@x+F00ed>z2A+x5{g6w8gDgt@z6FBU+ESAS_VnEGzeOUQk=Rzey=l+E~_9 zo|i#9m5ud*v`V)-kV*;5AIoWN%XI`oIWzPZy>N0FwC?uu&4oa}83)H3VH)G*{nFBC z?w2xo*Fy@|+>~-kQBTs!(7gy{{^EaOKnod-H&5FedL@OjjIW_k&^9o{(ykd$Y;%hC z)gSdWKCI-@MneV+6M-SnQ8$u5@tA+t-bn`qA zSD3Mq40N=G(o(JG&?>Ebvj5eXhIO_6Y|QnR+2^_Hmb!h^TVHY+Z<&rLuXZLk_eHTM zUIJ(f>))a_jMPC}djfs1CD1A6gI+*teJ%jv3MBRhfd*RW{XiEOdI5+n4Q3KRR~xPQ z#b~Wwd2$ZG>vZP)9WoBUxHD*b_i8GzLj&2g{}(Pqh@I*BBSjE#I*;_#-1`7 z+r%-3PC)2*LuUg`G;}GDO4!3dYM2a->h2U1Dg5{Q3?!;WA=#{sqY zgS2yjxD$dkQ6TPRZ!4M#M;~^l`^U*-dndCQsRx`wuGxt@7_~!|D17(Q20I2sU8?1wn z1rD_Wp985Cm0fts`a&enbfSYjY2|jwr=_Rh#mt zO*&MkotNvoTy0}m_^S(i#c`PHRXtzV%Fyu$Ra@e_04%58Yn07Ri+Vk6 z?&s*q+7#u>!A7v4wwLsAlaFJ4Z>(%bjFxiVSUVGr=Qpz8Ql{sHUhrxx&++8n&6YCz z+^VKZjy5ahT}-muQl`U0-Ky#JD&KZBdDEvz+B7a<>Fi7ZZOT6kOH@yb%96U6Mhvp}ys2PxW{co9giNNc5yuSmaY zv_D|!yN2c%gmbQj`oMy3fQt3^ccXbaqRr*Dw1zzO;)?XU{jek{>efNWQq{X9EOovX) z>}U7DeeK~1#4CpStY|bsZ~QlTy%l&Bv=dD-btsf?6WoSThP=!A(GcE5DCa4noeGZH zT=)i|>p-5; z)(MEci}LbH@KyrfV!hT7PhH(==(eu7v2JJwa43|ovMQACu8PKgJ3Vdj_;05p&?8m? z|LCOL;vb!a_+KcYJs?jU2LO?ySlX4Mm=5*H|BhqHQ9IeP8PV_1#+gG=+gujv9^yan#2Y26r&|c|uy=F? z?p7J90I3C85lC}2TGh_v<;&^n1s%LC%fE4FVRP`Ze$>`2|C3a-0RMA9Jgr}G4L}x9 z8~atwI^<_hhxgs=D>gO@oaQKRseJEJq6P8+rKoM+!cY%brSaMkK%I-b9`V;zKLtN)#C{7_E-mUh2Q{GoMn2uc$I?Ryg zuY0IBLjBG8GG9qbW96#W&puN+0g^R4W-2+Fs~ulf<=tCf#ryc|jA-*s!#23aF zwl^gjx7oeso}~3$_4EEnZRcdO?@Q?Y)=+jO_BIT)1^Ta{4heNh=-`A-Pw2{oMgXZ_ zcmt^AY%F!ZCG@weBCS(GeG(c7B;H$q<}%(VfaGWTRBPAtX)6omnpPV_T-niTPkfgN zdwz-c$Asd#Pi<*(4>j&xkLTX?62ZB~N@mO4SI0H@_CQL9TfYftsxvzQ&5xRgR|SDo za{2+uYu6<7ctUYxq%|$2mpkryAt!JB>aaz3eYt|Bc1Wwg6Ch9Pj#Gh_c#Rg&zKx9| zmeXP@Rlk;bsmpt#i^-$cHng^hI*MyId7Gu9Ox_=`RAs}Zn;Fqw(}K$O5n}*Ar{J;?GG7h za}CA^K;_=`*EwQ~sdVVi6Zf8_x=?$w-AiJ8@4tATM776n4!Mr_b_d2iu!`Qr>t|J@ z|Ig?ZkLW+$6xDdw*tJU|0xZQAC^j3ioFiafU(x76dU3d#J#j4W+u7HpWbQHNSb_gdsto3lJP)HdBW zic+H9+Shu#UdHwce;YYfZ_%KHV6MiuXz=z1^s2`DJ28&9I#ch(7}>J2*}_|Nd@)A1 z=!D+d4P#FWZGk?ylOf)xYlhV=yafU@FA!cSfK}2__U=qfn`%lANKp@V5RhiQhZ~~T z`T@zR(}17@R#pGJh+I9}Xsm;m0Ts)ASuZIaWxXUziT9Vj!^YCCK3RQe-g@cJy+ZPvy<# z-DrzjpVhS9c@DKX-nQp?%hD^37|$h3n>g|}3GcGpXD{w=?>u6(R-pL}yirDrH}F)N z#u|;Yca6gN7fm7F6@0@&`QN1pK;rnsXsi`q8sgnj5B))*(CfDbgOC8b3R)(ZXuYH_@9RSEnD)SO1+zsX6%iZIFxdkAqX zYN6~+#lbq=#%Na~9%+iVN{Xk|M#iJ{#e0w)EM7hrHZod17dja&p9?z~jV;J-hUj}D zS+Kv+=)XRO=)a>3@gCYqhWOIT`G$B?d59tU^bSMJt&Kj7ZP+{?Pv5dWhIqB35x)3- zIz}FtL%%lM7@uS` zdgv>l*5MiG%!f#^er+sj15U5yUX;rN!=YlqkOBP6owR;ykWwsP( zT1{RBNascU)uD|+`^8SAbOX|;biafS0`l^_r&-0C%s=QV<@*_pH?t?(2rAz;dCS@x z@v!4ml~+5CuytTdQ5+vM3xg0(v%8@Qan`1}TGXK)`w;U`?6FmUhueJcKJr2`-q5Wg zjBA19zpsJ%8gKLK6i+KVUgP^)+7?3SWme(h&QKe;H!WKGM+cNZS}gJ%Lm|4oT=3pved=`|pTSIr|*6 zkA3a5kul}-7>$;;HbhI8FhooL284G>j={JPZG@z(W}zoToAP5I&{{^jETQWXx*do+ zU6pV@kR(5uLdO6}${UIHA<#vZ(_aCpZ2SbYvFVuY28{F#%?m^yV1Mgm)ywi1fc?GU ze61}jr#nKj=9j9Cjm=Z!ja|(amK@DVSaO6oKImbgoUhjo0M_o9rstE0qmug(bGd zzXD)OZQnRCXN%U`JzhSQPnr%7b;+(brd)I9*DYiMyiyGPm%8 z=m|nNn|N5HmHlIrqdAp2pB3e2<=;cEUe5RhNZxHeCuwZ6*}fEgItN0tu*gu1q|w%S zUS^^5K4@djlm6xfcSdD%>^0B=fR`S}>3X2*$OwpUgX53gK=0rckJ@$RV?fg$_#hzF zGI~uXYV+&L%q_griTXjwf6jX5rOIx!-giOE({V?#{?TOfdrh>NySepkc3Zq$xyRkU z>3#pdvlMMOYMyq^PQ|&Af0@U?sawc0 z*|KEXuU%&MxUahzkoFwzg*Ktb5*nS**o58%(hFgefHd>s-=u}6fTl6#FF>@VnDVlj z7h7PY5TknaB%bo-H(|9grJQf%)%NFQB{pB0@;ePGIVYGrFP-y4hxEE9%Ora|mQ(IZ z*N!OIZVWKVOGBReJnG<1uyWx{%LR_ndDmRDbB)GzE{*WMhbM&|z*_i)7RqyToDp;k zZ&YMy$MEfI@zt=ktJZf6+a&bt*cjS1(c*u7JBFPStw%y*6URPP+8UuZ5dVtU5Pm-` zgyrn@y+O2yxU$ zq<9adc#j$4Y)6QzLZgkwzo-)xYrOh}EB22gnPIR>V;Uc?<=d;VkmBpvzK>ll`5wso z7v{uSa2-){7NR!HP!Uh_?tcSaYilT<1HEh7SW2`)|5G(I3-kU9|2adbd@u>95}0z; z?g~OxVpmG&=wCy4&Bkk+f%XMBvd{%+ilKaYE}P}kmMrWF4tl75zfh0YIb`$qW^=o( zWOKz{pZ^6kg*KmyDPoD314y)mjmF)P#S?8=Ae>KxRrIsstzhvuQ(6_Md3bra&cZei z_bi2R_U+9<8-IET$AY7II2yb{X9Fqp0wB(hnul9~8eSn~Y&S2SSe+T0eGw^!0pBCqCk{N9MSG3@I$ zU_e4QR#EL=XBvJBVGE15W);XQGWwv|p+;S#_alV~c6p z(%x+CPQQ7;<{qG8JN~%Q4#T)Cq`>rN*w)b>_aI!kv!FSr=UFl10!1a z820^Op?!cfKR+Hwer6w4mY35^N=&7EKj+*m_w3o{GsIP^6EQzh4<;IG1!-%9mj{Nh zDB`IG+&zM?llNFFgf^gc8?aqM=fD=xE=g#@iZR}`iFRi~4=40YLZcIUGocR?`ZA&K z5^8?C^eS!bEJgEzwmgt%YZ#3!gGRo7QitonqH!IVt$;W>8VBn>*D;GD6Ok$h^2SGEAzn` zgX0zOX5kzl?Kg7uOA5RXV;hz3ijDF~mNqBE{=tG@j%*%n)}`P5^2M??Ez4Q$v^p#4?1xCvF<=6dtgvX88@_D}?fYcMahu zAdUqZ!Y!AF@CK}sj&n?MJJcEJ;3*pFz`sa`;m>GxtFaXjebo?pEfLWgiMDG(ucy#~ z37wQeuTH4N5uqw?{Us%@V;D9uu6Z1r&}eWll^w%t34M^zrwL6-sM#HnV>YDYpO!YB z%^QOBRzlwcaI*rm;`yPgEMETn(7%kvw!}|y-DR|RehB|EsA|&-wtSUZK7GXE@nnr& zAmp6*Nu&LGAV%SaSS!}Tih3oqfs`!`aTfD|h0X^g-Z8Lws?oTjG~2u^FMRP>yvtX4 zv#KD!z(S3bKNS_P=5A>Lj&)X;UXO6VPoxE1g8&N1G2T%D-n8bMeBjAZ9#Zf=hU-$i~#=+Ap^52=ztCMUOrnFrNbTRG+ zRKkr34NvI7gq{Mr?<>X|lW5};dLQU4)Ao6yaaH#sqw$^3s|nFWi0?9+$CA3>Yd_h#9`++789Reh89Gl_|Na);z=mTk^4`lP86nb4kw$k4}L=EyM|^Pf%lLM%?mUYNdIZ& zNkDn+zZS|~gC~-4j}-A(8~v=V{=G8TZan1s#_wKbKN$PQW?>cZYL%%|72#A95byG2 z;m|6=S|^bH@Ns?>spo7qAMK0hkY(RoplL4Isw8w73+3Ei=c#!PMWctSfM_#n1(4bU zmbLcA!IhW}hPdjyu}Sv77~Tx4^cEp!S)DADuMU;@fOWOj2hV`_$I(phYY9auO65Nw zPigCB7O>}3$zlH?#Fq<%IEviE;&GOxR-U7lL-0zF=F`0`l=JDM3^8qLEhfSOh5imH z3eC2Qp-lNn#=+5?e8th65XTJi8pjNhGI^gU=?&_KqCyb0eTon@^B@ zH^!vUH!YO?xl+l#ku@a?%R`FV@yC!h?RUp=uhp{mz*`<~CGeg*mbo9-(zNRG2{^Ql zq@DB*i1(#QVcD1pB%6N#(*H<*HMA+>wP-~e$Hns-;<$J@L%ge{c%4(cU4YiN(7k~a zdQb{I21rr{07=SuDcV09Y3qV`O4|;`kw4$QhuM-J8TB$+eq_|w zXmMmDZ=9Io@pigsry7lQhUHJRvy8^o+Vc$Yk1#@9|GNgXVmgK!Ew(gh6VXQK?aDil za^>KIK$Ee~>VI(YT-Becct-;JPKc*g-~*t8k^5*pfF6Mk`Ni)@yij7x>qeXJuIcr< zeSRauE%nmrWsR$J6HVS?khj_MwBBDw;40!&gjUNMkp7Izvyw#*{rVSL#hp>=&B98s zN~h2_2hty+XmgAC8QLAR1%PDVl0e#BZ!p9%B*fNHi0`@xt(VY&kRn>&g!(6RdP3(X zG$^6#61pv+dlPyrq0tGAP3YZ(CM7f_puOs3D=15?VW=9p;~OQQB0x5k)%3yb#+;&n3QJFUNhcD&WR`R<9eam9qT02*g&+&csL?j~=o zqGY0UYBioOnf${fABcE68e*N<)lj|<=wYU;t zaOV;6`WuZaC(A%e7QP2fsQJBEzsIXI8e^Vo@$x>6Q#{LTwW`rQvdF9zeG!mC9y2Mvc8Phtzz14McQzy6WSg!#8&SyL(Em$ zr&=)@G_7;J1XQ%(X55}Cdc2+m-uD!x{Kq7_H$2bRfPH)2$z0tuq3wZKVzW@@t617G zYd}2K9_dvnjwQBef$DU$pvduQNy@*;*({FVjK))3dRs1EI=zOfCR1C>*7Tp-+#5El z4jv2iEqZ&ry#}|hHIFHp%cSJBZH*iC%9*^5EIeG5g8$1_yeMP676T5%NQ z9-jzpY<-IPTju-43cmjmyc}KQ4hE3?%$04u#LItxs-2wszL-a>GoA}NnVHW8jZb6# zW?^ygDs9YtS#Z~}#S~#L5Am4FqOaPPdQp`_sod65$=%oO z4RP+gyGhRLT@tHcS%|#KL0RAhXX{FK@6K5AIFm9CDe|?d4?wHfnX$4NOXI%sx8G;pS9(wNBiN$*GydY|^m!>y zPBQD`d!Wjrr>3!e+2O1>)6AEb#;yMX@|v2{xOodpHDa9e{#aMnPiUKy;;q%gtEgBu ze3n1O(&4!u%ZqIJ-a_-~m^oh7{dzizR!uW)jd9ess>r)LyrGt+EN#6II>!Rh1heZ7=VRU7j$x4Fx*haysv;qF$DWlPzBKpJtG!TduyBB!63# zhkd&TVx67`h}zK8C$u_HaR$jgfGxkyFtazn&J@)0bA3Tu<2{ahs9uj#^saNTmt=}+b9H0*>UN_w?xCj+!AdmV9#ieP z(`@$EB94sIGTety-2~Q(D^GiA-Ik+~vXxYMylFb{jtIs>YOi*M^_q?Ks3Mh|ktvmB z9&g-3Ok2}ElrK4tn_pORUN%(LBko%*o!A3Ps9!=cZMZR5rOB4E@qOdz7*|Ng^^jLT zr|(86eX1OM6TFqcJ1g&5I=oEg)B9Z^)Jp_?@PdtZy?%H}WUcr;*~gV~tvo5WrmtnP zYPJPw)#I>KrD+te74tc4W zgO5Ao$g;k#p2BFeI4-HL!%M8^ek{9Xuk9<(*^8?bOPgEorOA67M~#<~3DtU;g)e|q zP8+w+wfzbXtr|=NVo#@kf$e4Gbb$xs$hQrU;mBSZQv>L#&c7mnD^QbGNsQ23h)~j7}ALM(#0j8JxZ)Kz5Gfi^jzzO>#&-J=C z*AZ>;Ig`>mbNyRqXxm_1?YIu_%^enVi@9*A+2>`b2RL4|HBdjt!(D*E7VkiW^0c)) zKEzVay>j01)a!9WK~v}*7RnutyNx%Wm-YHGjW-?x2TuHzSl=`$e?2dK^|YnK^C*_x zEF2C?RRg?F;jY4HaL5~Dfhyq~jEqRrE0%8<;>iZl_~yk2pcPv#Urm`A3shddGI?&j zk8R7+;nw^5&~Lc4Py32iX_htXl4ak|C6eov4U|2MXtRZ4uRAwT(W?Iq?}^A?UxlDv zwO2L%0+^?Tof*<~eZo8S;)zvy-LFlTA%!te3U(LHbHk zeB@D9KbqS5l$BHJSk^2plk8(cA19WL4&8d@)(R%?c6gE};*0s_^V&bFS9Nu5)8^@* zuMR~H;4$}^T%Zu@9&p>_!j^n{w`x3%0pz)4k7G7x=te>7$A8?hp$oto%_v||9mB-IF zk8?zEv89bECr2^mLyR_SwNYu?w_ohBn?Dp0$2r4Ha-_+syFjb%9~t6F%29@BDNkZ$ zA#!BlKZi$owEp?K8QN4!IpX;@BxA1@R&Dz4IC6W-Xp4fjfzh~jGtp@Eb~x&ncIxo` z07Xcmq} zysePlESyl~sQvSuhx!iD%=lUTd6}Otgf^b(kbS<2*h)6TzNYvkjt(^zUCel|fn?<% z{h}O<>u@vjb6#FslSe7?pGzJA9bHRG$ui0MvK_I;%gZVSZO=o;&PhiH3uO*&2-Ng+ zyag#76{vTaIjcSc@74vYT8xbHX3eKck^6KZ$ZMHWIdk?c3trD@H@dnS9=^Xqn7B64Rde!Ci63g|!BP?DV zAHWW1Q!QhPRLk}RO)C?9fOsZVbx<{LIL3}@6Z3lx$COxAEYEC<`&+!cRi}WazaL04 z_Z%p1R`(p3)3v9gUGHf~k@iXQK0Twv`awo3WrW$nvlaImnz&oD@Pz5j`%klDA05tkB=`cP$h)tU$os%( zF3(4YGb>p&YFx=br2m=`UOpYtR<@hb+|Hr+3u`l*4z+94R(pcIY@E%p@)rgFJ%`-a ziVa77%_`HE=gerVgH6#9bEY;2oi~Qoj$?h*bWF~cMVnyD^zyWRDqi=bkInce+cuDl z@g=mWjh$H4i;-8Nu`O2lbL-tc*UPuU+BLz_GTzv#$mUheD(bD@s`wvtz18zM_UsSG zemeGzwY^4DN679gN?y~f`o9^+;$Gwo+o5$~ALgc*cW-29Y4~6ppkk{$^X<@1mU4P> zw-R}OrX6z0y-Ww&-Tz%XRHp5(X@~NX{hn*l_eYrjyrpK1B5&=S-RtV~Etl#?fAf{+ zhu1-_0G(p;ye(mCcAn9El(8bp#zjW!0HnKp>j1G8$O7LNz0yMSJ=nEIi`rz1Bzrod z^*ZB!hjEmpV?$`84xG%d>cu+go$(_v>mLK^VK;D_F0q-`NAG$%t zGAYTi9k12lE9$Xys-O0A`eixoXOr`ii@f>=ZN8M$FT37vv4m+f9Oqyu~Fvsw4l0Wa?0>@;#7N{eA%|u1?D; zzx~}6?b>^`Bhe$FXq9e0w_JpIL>kw~YDa=zV)JS96ASe@{Ip>_AEB0dK4>eJ(*@0f zSObc^l}fvZx~0)R$!lX$@|G@dG`EGkYZxugNNZ2C90m_*9b^EIX8EiOWu>zzr;lBm zm(F-bL^gMXEgBQ@tcLP;Q_w1bJ9b+cY6rw!yrN&q_U^cvS6km^ZSPKhue%aH@Kvy~ zdZc+7Qz^afXRnuKey-=!qCBrNUgMjZBI+nRYwE3vY<3+@lkDErj^_@RgVZJu#q$xx z-10pyu8{hB3NEkq1X$z#W81lVL2tg?`&mwD^A@gg$(}R5OXevWiW0Fm^y=>6{y-|R zeSowdb(o=6C=vY)@toyApmJ}x4wfOVracF6k*ls)VPYBu{IY`MxT9R+mA@VKsA)~e_RJiSr0 zz_pd_zIrW-w#YAKIy{wJ2hM`mTJM=$UTnE){hTjrZnNZhjVi0Pf3=RL-N7%bWvai| zSuD}_lS^sXZ{mTz4P}bd12gU<7>%y>j3dIQ(7dUe9pXXFFCO^HHFtp zP8-Pcmj4&%Z3wTP7~dZ2fKZ+P^W9u8L*7s4ZE3bB{ZbZ>_RVJ{(ck#+QsU*0HGXbL zQAyN%!FRM{ykebJpHddj^}1DM7Q__Q>dmK3IvSVa+IEKj^hb^;xpoY~@;ASwGOqq+ z!8!7J7dM}JsOxClmd5nf?h)i~qWf-(Zl~vM(JgHEpYwWNM*B1pT()kBh5({@{swazE|itWZa#+xs(TNOAcrL)C5>tNM*tR&zceY$o26fB+7QptaK zd~79bENFVuiF;#RjW=)KUPhaB$v9^K9S6Flb#*k=X166;kN;{z$D>FaZbLqXe{H$8 zx2JD`m7>PHfmu?Clm^hwwKF30l~CSSs*N76D^_CI?)vJH%HLTz`&3HK15GbIHh!Y# zBJ*F_iJlcr+*UG&eh5+?VfV5PeRPMr%TfNlF`bf zkfZDj!5cHp@xe@UC@jwzWTf&}kGIUvTG^?`;X9drBl2scNUxpi&xXt6y=I}4 z;c=mJ61pUzYk>5Q@XbK_vmyWaqBn2v0_`LG{jU=EF1A81IH!E2_q)12eHc>i#S5Jp zn+Y*5`MP_EDSozfLisXY-vZW;RWm0vp zEJbaakfQhx%cgr{41D@u^hAy89oX^OA%-cZ^77COq>d$x&chgPywhR3lQk6C_s zI%eGZtZhD>9a4PcQC2_JH*MaAwE>5Eh?!|=)F!%=ScWi@sHV4B$hTKpn%iJe?DGY_&ZZJpHbZM~H+cNbJ*J3KvT)|1 z&BA|1*Y?xOt@^LMIfqN>30nr=o0r1#{Jy$BSIHeBS>;k(a!fB?nS$h^l)uiCeaw@r zp?>nB%zrbVM~5ZJl&5jdQK0DzK|k~TpDhQc<#ZHl)Y+yZdQv4vb40#h_fe{s7n>B$ zb$`J->P0CtpK_mv+DOa(cF2Emb{lXq^73kE>o#CuLW2?-T1EQznKyw4wsyq6(VJ<}v0>F5Z(Lc1lsyQ~&_uy5dv z%6LjSrb=rFm8a#khR|ajA+;c*ks{5MUNY1X@!m4T{m_X(GxBM^J@^J3@~-aZmEC`1 z%kZPcAHqaLcEO(aXz@Up({b_YUpO5oeezzw3i|F0mor{>V2qA-}Oak(Wj>v z&C^S(is_9WQg8at5-Dm~{gq+A!Iwp@GwW~9j~+EUCxo^xv! z-oV*GjYWB*RI>!PAWANurSite)0RrNDt}|-Z41q(a)QyCsv}BPd0{$Kt#G1IZ6f&=%!+T}4lTa;kQSIrvAxzOuU+oD0plBx`_|t9&X|iapfR8_S@a-i>cChovgfcUn$%0~SWx7*@+UB^n0qWQUd;~ChJ zw`xW@JRQCpeo2zz+T0dTueaVlR*9`+vBb)|jq7k*nyM}St9C}IbiJOUHQ_(`%C*IG zWWjwfeR^xPIqznW#}O3Hek94>GPu0`QYc3S+ou}8i`DowF(cjG5XUq^o8l}OQMpgu zf*I&Vt5+-=96iti{PPd9BE9z?eV`FVAB5K4lVeY^qwO*tf^_f|WZaQspMtv3ESv_K zcHvnLeATc1-0A{w6usst^4xN(8ZTka>v`@uSXNWY@~BLk=b-1b^Op4s&f)ROENI;3 zye;x%nFX#bZ;Mu_+yam1di|WtxYU}Jl?eKt=Q|t1(QE5HiiR-n`q+E2Q7PqdB<;N} z{^{5ge*?7=#?pRNNh$7Uv@+hoC>txKQSND05?SNVHZ+@*`M7b%t0Q?=-|3`lO+afL=6oGb|-4*3j3D#}d-u1MG@BS~@mfC{`~8Kh2!EIbYKvB0 z-n{|w@GmRSZa1_pklI*3{k{cgydk6X-ivtpOYa%@-+LwOgwThK#y>AUZKwxm`j_M% zm%##~9RQj_53h3I4~-aQv@iJ^qiBBSiMG%JyrX$^yhFruH*?uPUU}|@NUH|#KpSoa zK*vXhE(DqlB~jLYZMN{gecu`4zmkO>PiQbWM7trO(bdo_+z#6BCixzqRUcq~c|3Ni z=Wa{AUn1TcHX7zUS?HqP5j_JA(Ov|SpW{eh_RS9|^6tWh=)YwQ%`qtYlzv{*X!N~K z=kvwNHrOL=2s`%;VQYjogs}+S7AOnf0O_V^i{~O*D53TVZ3?7vK%1FcS$G0%gwQL! zBKi@C9+J&Fn=Sm$t@4-lsYK9vT2%?#K(Z{|5lEqXB*b*6ys(rgl%+(p{ZqWdfJ7S! z9g^1%H2zbs5>5ru{}%a=NX6qHBvt-+9=*F+dTx?;%=J;*AqnvnC|SU{3#DY?QgA4@ zZUUlrabpf1;y=QhUW)GpuX5p;D#EA;C0p`XvWc)lf>7ncTU7*2K)p=w&sC%u{o%&Z zY82WCLtHOm4Zw@aph@z~)Uu*2tYxx=wT!5?mUX&3wx_Xe*U3z!!|QL1mxcDQN-c1i zWRB)ewsdr?TCY~>bW8bY)ByR<#~_^%?^JuQp8d2+`);7A#P$VZp5x6aAn|&>jp`NS zor6%eIa#?R+(O6d>1XAsJ#%Mefs8=WX zN9gB`b}pOKQr`igibc(_a-Jp4%-i4pWJQ0B+y#DPc%89c}GWD+l1ODv`!T%(9Sn8-Xy$ws(4$V?W>^}9jN_U%|nl`<>xv-b7bljfw{$~A0Uf!LigYrhE zcx89e{p``>DW3kwo0sSDo@q3mm+a@w0$=~T8+pVwR&B8m%UapKy`Lr4nEJc@n}v>e z%~bv?vrm4089LN9G376trEh_@H1@p2TeiyNHjgXVJgF)Vtw%!hJ&*q~SUQ#k@*6Fb zjQ#Yhkv^;o6w8b2c(b5`z14eWY2(^DQk#v@={2?ywQJm+5UL)L`!m%Q2aG}Ld$%Vz zJ~X|QSO2!+jN|znO#j4;|H;(Y;*+F<^EcdA#tMu6_{cpa{qd1D%YR2FtGeKCBlEebs zrryxd|Ksca1D~Aw`0<~!hIb5MB}~6el0_JDTd7RK$d6H&Um3z88p29%O)BFqO`=7y z=+^v*Np35tNo5E_ScxWKBo<-md#>v|&-0z{=l*>BbMv@g=e%C8bDitjwQFlx_ord| z->mynJ>q`VQQDrxdd1de{VmM5*>Tn#=p_5!w%u#~UpZcn*j1!QoBjOHv+-K~g17bS z`0CDFeLuV9SpMICC*Q1)zJj#;vcA>1-c8n6{hIfm^Z!_x=rT=j}T3(Mn_tlzR-iy&g=Bc_cAK^7sVjTi!1sl-~teZ&=zkCg;%i9x8)DjvG-QwWb*=-%YKTXP zb-WG`6)pC0KV8Xz!mZxzsiSPmASE6V*3x8-MMn{Vm}AjV41<_s+DVMCrMbx*(@r9R z7}I-au@Et)_s*h-7_)!6h!SGV{^=st=oq^~tBXU#rk!N464g{!tBa|khwAh)C0#`? z$Vp1ZDLF!nfLy5LF(pTeC6M7t<|{c`6c5w$j8XEw62JHv@~D!UlH-N9n-w!fJf~!< zl3pTW%ebaDlvq3SBoVWvS*%uK?aY(J1RWzPkbs!JlZY-((OWFsN%mB7vRDtKG+KF$A*fYV|!6C8rA45mwAp z`?_(qk^v&JlMGfeP%N>fS-91xp-Rpa>us4P4pnlEEloaao@v6Tt-rBM>K5N*t(C3vCfopg|mk>&qjHwlB|k3SHx`5qZTU}B2rq!*(&BOB^L?z zk$Tkm$E(*7B^QgywoDV3sF-h*TqZKMOtGb={-Z6^+2tWl5fv$0HnvWvQM)U-R-_?mC2ee(1<5I~`WhzYLY7cogsi44glwcN zv1NSI-%9pZGY=Ch5VPk8>PeT9>%_q0tO}c(IxDeat`q0jvQhR>VjZvRMHtdgiM6Ay z7egTDD(S37-5^FlE>d#1lHp<$B(CIWB{zvt0V~%=IY!BeN^TbEK3XO#>8s=xk%7!n zGQgHukOE>xin);Yl?+lbBgI0Ba;umd)HDC8vOkw5poe^tf+)^%r#1^ z-kXKPNX3ZE3qmZFE&A*RAObHAR1w7 z=4SC<$VB12RLjR0HA!4?xh3Ppdc>r}63AvHR`v(fuh3gDN%4o0l*;vhs9s@@OcSj?Qh(2)iutz)*eAj?vA>e6k||=AmZqLca!RI(xriC0ByUR9IaQ!-stZ5iM6m=f#UnJ#L!G&jvtVy(|~ zu?;Z=B_FC$j|y>>m8-ew10|o>;<5#=aY`N&UM)=MkGQx*ZzbdKP(rc$t)@$WV zu|~%TQI_H-74wAXd9|HO91NK)Qo}6KxvW)vN|bEb*!lz&^P3v=l&I_!BfOT>w1}XJ z*-c4Sgs->enJUg#vagb-#jKb;v$$M|N6E87otWyMX<~$uu1e+!k1bQgeM*i}k`oOr zT-Eu)ccVRzecaDfG4n;ymW^_z8g-tMmqZEjoRWwwYj%>WmAowKT3RnwF*hrDMKo-g z!d$P2rkku>8|4}mbGseW22xcr)|NVCvy$;j7Kqg&^eFkUdM~NuRZ+90WmVOeRd2St zXxc}`SbJlkXo7f@SbJlka6=ALV(pFBga>kr5^Ha~Cc4?OT=Z6AUA6uz;yQ-s#eYS@ zmgG({*_Ms1&sC!yQx*PKOt+=E>2f7c+LG1tG!0iWPs!^d2f0JZd|MVm?p3l-NkJ@u zJgj7qEhWetCCij75@pDICD#5~B-TR~DOssxvDgIpSjqdg)FD4Au~y>^(SZD>#9ECv zMAI#LRd-mTMlBI-AbWnIp7hw_fgGg7n&(a7g&e8Gn&(Z?6VgwKb##`BUXXK?SVw26 z2tqE$Jj+A~avSDZCWhFOY=iuCS%K=_w`FJwQ>!Y}wfQH6>QAx5QY;drGWa zZ;6yG_cmGQ;CE`C zL~Kcl#~D+$rCGeLq)FvkB}Uz9jan|veqJReKtANC6yz%m3^xcYn@jK zk1gYy4qBtGKX!~4a)=ULj}LOB66?6H61{9$E=;a>#E9FinOn}>cflOg8J zeMe*?(d3)kjs=<$NgPVgj|Pt-V-Ir-I(V+vBs8`E5Q50 zJL;b`zb|~YB%2<#a;epLU-%&}Dd}QMUoE^!tQJ@3dF)OPS23%_Fv$CAl$H^Yua#J< zx>}5a{G`NM)zu;i`4cf8h!kYc&veWOVmhRg5^J6h#Vkl4#C#}nkcbj%#Xb^w$Ot9Y zihU%O*fOpuiFwLm1!N}XDT}feu|SEndp{PXJFFE;iZx1(R-Jw<{C8^kO-XMhpNbK8 zS<=ks32Q{omhnwZpX-{}h&-f?607$$VhN-@Vm=cqAcrF6Gf~!Jue^0V{9J6arDY#} zAtLH`$gF#$W7H_C<}XBXv?awRbAEjxR@l-kPEs*vsG7eJoyJ%(Q^Y_e=PFq%df75w z3|3;T*gD~9wqlya^_clfu?^BpS$&V5=OHE5{m+-8Zp-*4bCp;x8jzVPX0Xb&UNnuf zMvZTJNy(*3z7lO9ip#^$%l9-_>=rRs>=rTW^Q|7FMw!=K$}XnU7BN@aZ$(d?z3DK#>TMFeAm$!mlL*?< zas~KKOtxi;@To4wt1iA1OD0$aOc8yROjh!P$W64Q<;wP>$ZKi3P{mm5{G%v9u2*91 zmLJ6m$QZ=@BvwNvA?7DhflNcpW>JOY5VKkQ3|WeppT#!FYQ+33oRjPdo7O3@t_fR& z3$jItb$)FT?IF&Jj`>A&g6yZniupzOAYBmitMEgPL(H$DFJzz+tM|GX2)Rs&)q7op zZMl~_VXKJP(z5bf#f1BPIEdDA^{GkXCEepCGbj6J&oS)_dx|#J~rwQR8_H|4W<$>8@g| zRsBm0fgG>ITCu-G6w)6t4H1K!hnR*K1u-kXU5tg8mESH>kfE68Z;^)FhI#%LvutS= zlawq}UF;A&|83RWvZ^~o!j_i3x6y^wZ@*;V==KE&)QdqMgmW;fXvG6XTZ$q?j5 z#5Bn;WD;VU?PMj z%)5ZSV1D1NLwp5O`M|S6D95C zEXW`wYn2=ze|}h>7v>$}L2?_!yhA)lI;ZJ*E?1+zQKLLEZi~KdY*x}iIv=s;v5(GQ zN;=EQwlv!!yQ=5&(m&mbX%@?l*Y^O2$t%=j8S6Q~jcVpSRZKVeqAkr$4=AzLxtm-F z$tbbbr<*K7tS6lNsZoc^5@eo=Ilz`RwrrGdD6w{CcUiS%eA5R?tlfKrtU;=XIYMrO zm~+sl-o{%Mj&CyOpijCW=DN{Cx*_Je(L;8!WuyFE&1{{gN6Ky{<|`vTZE0@mqQu&( zN6G+VdMU9gJW2*3rzo+yI7*%aIa^65)oD*T1agUzuC_#N+1PrRk{(KqmZKmel^knJ zQi~j;zJM-XFX@h6d}J>pPZ^(C&~4;G>d(eT%aT%`@X1StY^6q zCB0?BmS)jI#SB-{S5AkV2&(_gw?)$^QzTtPYgH7yY()-%RaWzLpnFZjgCMZza|zhA~-yoUX+B#4sjTKrT>XT_uLg)sX9zSXYVRvI4mq zF*nL8qDAo?}bP9peZ&L`#!-7cfFz0Wt3a zM#vashRSaBb+e2^7AUd$x>=5ee4xZypIhVv$hS(YEB`Gr4KeQ~M#>Dtyqg#)=R(Zg zPF%hSF?Tz0S%8?kom*uQV(xZsm8&7!Rd#EgZiyEwxl2@E4f(79dZT4x+YwyzHv$Zc{M{nSeZ@q}i6$kT;Y}QZibWf6$}WE3wX| zyJZ9Nn-c5k^o7`%#ZN3o=2*f3jqXh$^wJ zQIq5vNCI-dY`ovT2Jos^wnvA=&;HOIpr?hh(S!L%M0*n49O_5&cTe_26{NSp8Uoz+Y^0^(I-#XK%&K?0QcZ`M5e`(xJnJT4Ou z^RDD^IRRom$$COghM1lCgv>xLR5M%i%#vBiRZ6UD<}CRlWCUVn%Y~59h?y;mkV#6c zHGEQ*AkQeV*6>NW22xbASgpoWay{gIB}H3mwzQlnS-IwSy<(qZo~*2F)AF+tYklU( z>>paTQ@np^+5LOQG_-V}R3XPu3fpze*_8EvThel#&XJpJQFnSO<{ee@9NB=RAWzFS zJM=tHLY|Rc$U?|m*$eU>PVqTIjLYfitk}N>XbGDad5n`URy)0Kl%rmsSTmv!B(DJehnPlZsJK+^s zgUnX4#+Gdmb1$(#{nUeAF>^1mK)N92UgA~hhM0SaSJhXq_3<*-pM^4Ji+aUZxxQ4n z7RnV6>-!tuDfzDq?qZEn@2ymfb#@ozYFnm=PnB3lxgcwZu|7}vMU7e{GrQ_hKVs%Z zvb2*lRLmQ)+ip6>JQsaa_JYWoeyw~{PKN9USt>U{j)p9gktRLQ03}w1tdvPxTDn*%Q#*-u=B|{}cM|Je z$ts!KN!qKKOLCzty7x{>-jl_hVyw^lSIe?3%Z2rwitZ}r1L@nts&JaPUsZUtk`LuD z$YV;ZpOQ6lgdNk;*Jm1u#@2@-=5yH-V!k2ph3o|}-w^mhh9SqP zQPwf7$Oz;NCDt*m$YHjq?@K5dplV(#lR8G4Pmb2g)jNrGEY?ZSUUofpjCCx&lqtxy zY98xV<}0~oC$T<(`bv8D)}yQ#Yo4#=h@HfmXM-$2ZdNh&=T@?`lUSoR%F0e+jrvyB zAPLO;oowtRA=TIS($z+1w_>bOHQ9bAIZws>BqI>3!i$w`kuh7Qh>1#~O6qbOV$w=( zREc{lOmlEq5)a-<+*l@x864w<3k z9VL4^GFqgyJ|EcP+TZR=7F3M2qxN-pY*{W>DEUIg?C0pDV_H{~e67Uo=mz;g$tGL; zkUy1dR?^lHfb70meSg)Kfwt)0t@qvS97D7;9jIcgvwMGsS{(Jy@;%Kpp+(F!p+(F! zp*=^LYeI{dYr+AHG1r6^G1r6x8Dp*qlwHgf;2;xYi?w2&e~5K99ZWG-fR6tV>sp{vp;p-6-ao(ET4`T@#M@hghR}{6nm3!ci1+ zE+0cN=kl=>b1t`tIhR|+TsQn2Wv&~?JFeJixBRKHpXf+v5r?VWvdiJ>IjzIhUZ354 zAiW(uAw!hxuVVT*(zd8?F)2A*Nk2yel2X!BNqSL&dnh?uujOm90TdQi*RZGH1}NQpA8g4~(O$_}O3reWZ7IrbN-k7#w!`1S ziYdxtl~~^(J;xEZWt!-v=v_?%67b}S=xxg_Daverp=tw~B zfedy`wq=S)E4f9D8shjFF>{pMrQ~9Vvy+~AA>Rv z&ndaqan7Myx+r-~$qkMb5TBA|N`^aLJlu-eDEla}oS%9E%dWeXXNvj$YKxffueON!{_1TUWxl_9yJPH5 zxvZTq%8|8Yoc*+LovQFoM`Nd$Z)W{WygRE%}sJKoU?^2=%=+NpW&bF7A(tztY%COU>4Wz9293{!HPlBteDPhDX` zNq;3X9O9AU_C$P&jW$T%fCRLn9* z6*5)HE=Q>EzU^%v39~+j!lr4Aj=)WzB<<%khdLu``N;K$Q6#bE&6P-&gB)3 zge_B>R;f{Yt6VD_ImkLCZEeXzzE^UPEd|JLN~{%I=_o_mY&BAY^rSfZTlGwB8bt9x zZlL%f<0&D?bV?NRJS71sQqqvmDLKf`lp#97A!PqPrMK@j@=61R!xr z7&3_xgUq5NAqyxO$ZAR+vXxSTbokwLQH7jFX+TC$+(F&NqZA)xDJ2N`ff9kZx0zgV zNN-9Cat$R5`8TBiSx6~EzM|A1yZ>QwIZxF!A4Ty%&Zqbx*Hc1}Bqa)YgpzX3C5*Xg>8 z9TYF5)8D3^0Hhx!3>iv^LB>#$kjE(*$YM$!vW`-M{7tDsI_xm@G$4H_?lW{3mr;C> zF_a+WaY_WTm=cGqqog2zQ?if_!hu(-0>n=#L(ZnuAj2rmGj+}PP&|+f#Sd9P2|-p< zqLA+?35Zjg3e%7dlpLfNr3g8XQh~%Mb;x*%E2O)aMe#xwQv#3*B@FqK5`(mNn3|K2 z<0%u{XDGf-8;yz1v@jb-{X=-Ka2|~J3B9N0QaY%%cg4{vLLZ(p) zke4ZC$ZARr@*~B0wyt@1r>W2bIgH|m1SuiNm6RxC3?%`XK}kavQgVaWknZACiWf4J5`f%I2}3fJ800ld67mTp1NoJbhqT$%R9J%apj07eP#TbH zDeiN07ZWHx$bTq7$SR8U_k>w@us=}Z5Z7*|o)n}9B?~#5Qh?kEeQQ&X>pb1Xi4-s7 zVoCsV7bOhIP-2iplq6&wB?D=-yUCu1_$VdF1(Yge45a~ihT=Y7cd?q{gZx1WLb~i> zvPU3iQsR&}B?XyD$wHP>3Xsi|GNirBzry(Iq4l#LpD-!kk_v!=Qh}U7 zsY7BE*AU&sgA_01MM?njAtel{Q(};I`}^rs{s7g5rX5tJO{eo7JYETsZjNvT6NQCyekE_Q2YD)d6SQUZ`uDPc&I z5`&DRBq2{yGLSbZdB}Q739^Gyg>+CKOX{a{4ao5ncSLt_9>oW_krISVqC_B1Q{s@N zloVtgB@6kJQh>BmABy7nA>X5}0*JZkkuPI*0uIi&*ojm~QP6v|H903{8HP;!t2r3lGTDv$!D z4yjOFSL!Yr6feZn!PFCgoInXf22)~?I3)?0OvymX0KTuBh%JOz}eQqy!*OQ^Jr> zC^5*chnQSR$nlg6c6-}lscsCVWu9}wYrPG6ffikN&qsA5{A4Zw8cP^`a~$$I*9ImH7ROYuWyQ$moXlqh5aB>{0BVX~(ohf{KpvnfT$NJ<4Vol=J^ zqPVWtU3^XPLU!|+>;Z_65{8^hi9v3oBq1{>8ORb!9`X&P1ZmyFWUoSwrZgZIQQSA^ zE}AJm$bTq7$On`NR9MsePxYyOtvf$ZTo+5M2CC?UxClqlqON&+&2 zl7_rN$w4+yijduoGubPUBPeyq85GwD-NkT<7c!X=fV@lzLq4IzAb(Jj5YO?Zo($w< zN**$lQi9w|sY2#b8jyD=?wfTNKT&*;eNHghgOKAX5y<6~IAj7P1({FDLO!PyAgy|t zTxE!lQiEJTao(b99z*d!o}u_5t0^JKACxGh%ZVm?0&*rL4Y`?;gUp~5A#YJCkj<1j zn=X0_#itdL5R1nsV4$Cof3!K zNJ&AaQnHYRlmcWer3`WOGr4MzE)?e|UGo5n2XZ~d4|$Lhg1khDLO!7+Ab(QQkPiJ# zJvm5!N)d7mr2?5msY9NpxbDzhd`R&^exn2+`<-I4harAS3~~V_3Av4ufjmOVLl#m> zkk2Sp$e)x3nB0#bJzA)=P^HNF>GKLa?JVA*=mQYfV3MC6^Pzn&wsivMX zBtWS_A{6Icy21p-1IbYQkOCzHsZgSj1|B&7~1QCtb# z#cvcZq|<38djN66*XlsaTB#dWvt;&+M{()LVKa{zKIB@8*25`)}C zNkSf=WFYe>dB{pi3Gywa3fU!O>S;g@p}5EDF8Wb?kjp7S$Y@Fg@(?8snMX-Mij*v5 zEu{eYjZ%i}eU_=Y2I)p|Hlt>W2XZmR4;e`bK~j_`WDX?(SxiYoKBeR!+bKoJVP~6~ zE0A+3b;w;5*FCz6ITSDCeM$iG2PF(SWRS@fgPcW4LT;yIAhRiX$UBr0WGkf#>3ELG z)qwP;xX0-(uA}%MlPN*StCR?29VHIgNjS_)$yTs&*LxxaNkO`D5`!3smYawoIuGzE~6A7_fjg5 z9HkEVh~m0mckw&L3+Z^7$sT~5MhQc1qQoGNP?C^0C>hANlsshb%T2Bl{P#l7{?B$w3YrYO)t0r&21A5tKS)2F3ND?qWH`3)w;m zKs;BOTw%y*lo;d|N)j@Yl7XzC_EcQKpdgS<-#LjIsc zActOUa>XI%Qc{r7lq}>wlmg^qN*N-rF*Da7Jt)q}y5@^09>~2EKV&{71X)XoLgck3 zR|3+Nl71M(k=`yt)M`xGB!Dn`dPFXW)%CVK$VpAv>#M~OiuP?C^&lnmrON*=O>Qi8Z|G})_=6DSSH z#T56$x{J{iA7mCK2w6snKsHk1kk&Vu>?w$kl7$SS6d-X*88VYngDj^wr|FtEQ#_FN zBTRNbq#q>&xtt>Uy2swdLfm}hULncyOkLWI*r+6WsQUVZh zi^&y+98QTr&Z8tDcTqBsEF}+lmr{cKMyWzNjx^aDkbxBUblt_x6d&Xsw7dAxL*h6mljd0f|x4 zkOwF^$XrSh@;0Rc*+{8FcDc>e<9bYY;iY&Xr%(crp_DMBnG%D{q9h@2P%@BplssfR zr3CTZZYr!oPNFm**HGLubQhB-KFB;u5VD#Qfo!G3AqS2!*;9~{DOpIAQh-dPlp)Vk zYLJg8&Wx^k8^r@T_zsiZ59v<{L9V4lAt_1%@-ih2`IwS}{6#53I^SusS0JZR>X6|S z*G%2TREih!8YKYvk`jjOa+k>!gB(FgLe8aRAh%QUkeQScWErIj*+6MPc1xJ-?#Fc( zM^Jo_L6jh5BqaiQgc66mPDw$&q+}tjMw{#f$YGQ+izs=>T1pABol=DyJl53Hfb^!gXX`F5rT8GDDM83gN(8cq5{Im%q#)ZV zS;)c7rk(<%H>C`@gi?cyrZ}I}H9tY|K;ESIAsZ+m$S(Jo>`};Jlmz5-N*ZzlB?ozc zQiQxfsX#uU)FHo8TuNU1>v zQ=D^j&9_oKkf{_uWIiPXd7l!6{76Ya_PEc~lZJGoQlz+^)?L&n zUWj{w$sT|NC}BvH5`&~DNl2cOfmA4Yh;yRJRf6~^RY;i9fQ+EHpV3`Rq4*#RC_%^< zln6vjGTGyhLn$f98I&yKCQ1R4rj#MCQEHHN6z5!BbF2GJb`PW*#Sb})5`x@9i9)7P z5|G7|G-M+s2iYrSvKJx8Q!0?lD0Rqqit9hRi{~j`$S0HldDx}?mCRYQ}hvI%#cX18H2YHYZge;&$AnPb`$gcl3xl)iLDOt#c zlmcW7r3`tRQiH6dIOpk_f2MdK`%gC6{g6JC5aen~6q2GOATLwWkk2VOh@4__6(QXy z704h;9daARmD63!q#iqvRp~rj#HrQL2!SDGkUr ziu*a;#X(a|Jw8YuN)U1-B?3uO;*e)3Daa~H7VTvX>#pP->71DbDA0&395f zkjE*0$WlrOvXK&n?EbLHo`4)lNkh(~l#+u?pcEm`Q!0=$r4IR%;(AGU(P@Us?u87X1Rya=81fJ$23bf+Le^0-5NF2Z z%0s$SN|19XRmjbh24ot={j%=jb&3zNo)U!YGSg&_K)O@nkU^9bBu>df9;Fl@iz#Ku z21*Um^tj3H%@lf4Q#fzp5sqqrC7 zE+$ZXkU5kfkx!cJDaav|EaX&50dg&+44Fu&LFQ4Muj-oLrFbBlDSpU4 zPnmi`kYg!P$OV)HOHFC_qZjuM7^ zOo>6ZQ<9KF=a}pn$XS#;XT7FDPk9+vm+ZImjSN5i*fdfh?oc zA=@dg#kz|VUNAFzAvaS3kja!V@(Cpg`Gb;ycwRKQ@{p4$CCE@p6>=}70hvp2 zzoEN$hvI|$LAc_Za z2gMJ0iV}jnONl~$rz9YqUpBeYkTWSc$gPwjzc2mcp#%Ge#lHp2(pM0g{-9{AloTv$iW3uPY%+XQiNPesX#_k>X4Zf z*W0>_MHDY&EhPZiP6PH%8)(YF!j_R$5NatbO9L;6r$@9HjwQ@oH2B>;Jc5{CRqi9rruZgM3dLns-@eUv<8 z0i^`_hEj#N-!?NhAg55=@98e06dzhANlsshb)h2rhass6axt!8~+(&Vjbr&yCe2`BmLC6kD1ajC1CVLz* zh?0WbPRT-MPzsRODP_n{lp17@4^1xT$GYZYC?3eU6u&Jkf5Y8MN5Gb8{2T68I${v> zH{7js#3ANyxLfH+KyJd!s~l;_!;~E4HA)fkIi&*GOsPYfKGJ=ya=1RR`kL0%nc{`~ zixPmGPYFX}lo;e*N)qxoB?Ebtl83COlpx>c{#cTx(FETs&2 zk5Yqdqd33NHFx>U1P@<3rDGA84lr&^HB?tMMQiS|XsXz|=+*DYH z98YmobQkAQypS6y0mvju81ghF23bZ)LaLMuWS1{YJ$Xo1N(pirr3x8FX+S1Y+-r3g z&r*DlcPK$fjS_+ESuyp*AxBbDkU^9zWCW!EnM5f==2B{qSU2~PjgvYF!iO4scA%GBe598U2=22etft0+;(SV{silahw~my&~gN-09> zlnP{@uT6z@$T1Yx*Sd?r6fYz}2|#93!jR>Z7~}^^64GXa$)156N6AAjrj#IeQ>u_G zr2$zq9FnA@AoD0$$cK~yFK$XApGq}3)czBl!xA%{?MkdrAz$R(5t5;)T3J2|&K3gdwedFcroi9Vtmj zZ%PJo2_+A?gHnP#La9Ptp)?>LQQY6@F1Ap7kiBcB!XTsvB?38<5{FzzNkQ(TWFd1X z1;|oL8S*8i25C^7-|LzW_|a78fgDfqL(Zp!AR{PI$ODuFrbLUHn4vLH7UIWDi0DlnCTXN*pqtl7h^mWFf051;|!P8FJtjlf4Exnd1CW*BqsI zAQLHm$n%sCL?~6rBuWFakmBB~yVywaLEKwSt{~(jN(6EZ zB@UTFNkIyfEaY2C0kZFJCRZ8Kn^J=eqd0%oHBY5@Ad4w}$aj1DIv(Ylqlq8N&+&4l7`Hu`0@=S|>ZwDH zr?~36i;E~;NP-f8JV6OVmQrGnZzxGf({__R137|{hn!6*L2jm0A!$kj@+!r>Rd?|@ z#Ru6=2|^D3+td?*^rgfhmr+uX@suoNE~Nl@n^K0Xr_>`pBQ2dZE zB?P&d5`{cSNkHaN(vTID9ON5H5#kW7>~HC;a#SE4DRoGI;`$x?hvJ3YNeMt6rGy~` zN(}NPB?)mzQ%?qRC?yXWNGU;Xpj06bQW}u?6!$jWMVaD*{7wl%4s@7$B9PvcIOGaS z3NoIOh2$v($On`%MpLLcp;-H0mwW`7_yuagM33tLY%vpdNPnhD0xVKN(pi$ zr3x8KX+WN!xc|~!ET;G%YbilUgA#!pw5zEw4*3@)1-Xclg^Z*WAX6x1$P1JjW=b0J1SJPqL@7ePpj04#Q0kD~n@okS?YfJO z6feY22|!M#gdvwvVvv!PBxC|51DQ$5LtdhkAa7HukP4*%*+Oyut-IK@wduXMizq3`O_VI;UP=KngHndfr_>;CQ=B_=&1)$h$gdPXr1kEmuMngoB?>u? zl7O5^NkguoL%ycCgu{LcD)%rIdLf-C0m#XeFyt~y3^JOMgk&fg z$m^6mEtpMHXtWc+){UO9mNNEgc5|jMTtOurNkkf_cXaukU^9zWHhA! znMWx@KBLqiyX|FWb~<#;$5A|xt0;cR6iNv41|h9FN*?0e$K)zOj-pf{7gHLL zdnsi2-$UClPdx_ni7XZC@IK&lq}>WN&&K#Qie3`XL8jb$5NcT=$fMx z59CpbAMze01Zm|qGe;r4C<(}LN*eMcB?tMGQiQnLnwcw*ev~@oHi~Ok-NihL7qXrb zfV6FAW)4G6rNkh2P?C`6C>h9Clsu&E{$}P9GWczLY%VLP`k|qf{aHP#Tcw6!)IG zi{~jm$Vy5OvWXIb?AFoL9EWtJq#&nKvXCgH02xOqL!PA6Aa7Efd+D0jQ#_Cz6hEXx zCsSbv(wh>6L?{W!Xi6IL2qg!3g;IokM5#cwQ0kDqJDYl3d+RQGP`r>cDFMiJlrZEz zN(?fGl7uXyWFTKr@{k>r66D}6rot+uJEZ|RiQ;afy9iNykV`2+$c>Z;p*Z)^HUCEOKz2LC^zMfoK?y-lr$iw)QWB8alr&@&B?s9| zDMAkPn(P%wkWz;Xr?~djT|7YXLgrHfkdG;0$RCs#vX)YSY^RhV2OnnYsX=;E zoNit7r4$cjG{p~@NeMv~QKFEwlmuiuB@H>ao2e%UIi6C445m~dw^Hhmi4<2`-NhV= z7qW;FfP6s-L$*_5kdB9&3X_n2lnmr5N*;1Ar386~QiZIfG$21v-0gH1dv-V3eUPIl zLCCq32xKHB4tbc8g3P02A#YL&kWVRP$d8m7q}36oLg)V2^Ar!{D2g9)DkTKDgc60^ zOi4iQqog4-DLKf?lpX16c)n0eu@|nK85HBSF2~fh2FeL_wQIe1(B?HM& z@{l~G1SwIfkSe7CX;9n;=q}tnOcy@LQIsHL03`y6QsR*DloVtxB@0<}Qz%JDl#+qmOUXl?rj#IWQ>u_nlm=wCqfI?-kM5#7 z#RoZy5`>JPL?BZsamWHn3i25x3u#aakdDWgddiUglp5q}it}Jy^8|_qGLPbiyhjN^ zzM(`R@>r8S0qIOhL;6v2kSi%gNHe7ZnN6uf-lVuX=q|pZcp=hn>IpzPQ^Jsblo;en zN)pmc$v|dP@{kgx1o?|ng>*m8)YE`mL~(b-v7q=MuTp}Ljg$za?eS*jI3!3(K}J%t zkS8ex$On`%M4Vt|u0f8XI6LW@ucUY&k5K%OcPSxAt6pa2DC9&+0&){24Vgp9LB60A zA^V(YX0AX^qtqdzDXz}Ci0$ajy(wkLEtDGM z8H)1|U2}!vf$Y=A%^RP6@>sNkeX@VqeLKko@VNaLyn}R zAcH7b$OuXSGKErxyhN!%KB71e*EQEE9!T4PrXD{eKnX#vr9>eQQ4)|vlr&@$B?sB> zbd#$H=|ibNuA|f;X^N}6?&1xK7xFzN0BL)M$rXn5rNkgNP?C^olnmtmBkO+PyPEz# zfZy%@`P>gfm|9GM>PbjkxF*_5|L@+d!w6j63R&DJWVj1sA)Octr5+$)lxtP*LXd@s^X>36!Vm2#jN z#jzq8lu05vl)FU=C@V#ZDc_4!Q2OWFS~ZkWBK4H3MVcuOh;&fi7U`k1bA(n&d6#5-YY)s($N>L_DG5|nF2+9(S|x+%*b{LNH=A@NXlVm6iJ1)dIn{%NDgI`NCD+Ckz&fNA{CUyA~lp1BK4H@BF&V) zL^>#YTwr_HL&*_IJ=~1qe34AbjUu^}dqoN<%SB2kpNLdaeif;u>~^8;PXi@eq?K}( zNGIi55pTHZ^Zg>}l$9dcluaUelpV&|>P3`8MM^1SMXD(?MCvGuMG};EMcODoigZ)@ zjkncPMwn3?C6Yn8SR{vXi%0?GS&?GOCn6P;zeQ>&`%bXc>nW#;G*hk@>7YC$(nEPm zB=rb0iY+3Ulztc4TDg>CL<%XFiIh<86se@VDN;-MMWlhU-^I38E9C-_PD+)Ccckg_ zn2V@C10 zNCxFEksQiim)cqdlw6Tw%4H%Il-opVC{K&jQ(8otDL;#JQ2JhGtM^bwh@>86Mo}n| zNtq#%OL<eIGBtdyhq>b{aNH-+po|i!q0AJir>qodru;3^K{;xw&D=woE|Pk@8O6&YnUvo|aw)?~Y@R~ORFM+O zb0U?LEh4p)1FyAt8YowYv{LRD>7=|b;^mq?yVuz~>6F7ovME=H{sQA`%eq|6n`r93WDNLeLPLitLhk`lYo_NSI| zfJg)7bdgrdRFO`~JtE$Trq8d6q*K<5WK-Pfwt60AUy&lpDI%qm$s*O1`66|cWg-d6 zdXYBD_NBIZH|21Vl+k7s7mH+2?i9(Pyed*a*(6d-+4Uw{tAcWjNDbu*k$TEKBF&Ul zA{~@1B0ZGd|7&Zdo@7RGl1L_{TqKurw@4x7Wswrf29Ziie1@%6OBpKCKp892N+}oV zq&y+wooxF2fk-;#cadz$US+m=9_1vFBFfbwrIdR`swpc)>L{B;5|o|GZLK!Sks{rc zOGHvmF{8LsB!lv@NDk#IkpjvNH``jpl*2_TC=*0#D04;XDKCmNQ`U=gP~tOftsY8- zNNS!L#qlDUl#4`iDdi%Cltm&Xl-ETnDPM@xQvMWaprlvW9=1}B6zQa#C*qxI`h0^( zI%T0qHf5Pe9_0g(BFYw#QpygqY=5dL2Z+>BP7+B_E)!{^%o6FQJSviMni<7PkqpXu zksQjOA_bJ)XWJeYQ$~nXP|g*ppCk$)o%# zQbg&0tL;xIB}b&1GESt9aVZ6ltbR6zQPcEz(1ILnQS~Gm0%DnUwTOTPv4xl1L%tI*}5} zqau}**F|b6>qQzUe~YwI_PpIz@1z_f;+aEwr_~v8K;wh@?|)63M1KEs{rBCsIV&;a;1$lya0vHRVc?I?4kg3Ce1bHp-tO z-IN3Gv$ayrGo!dbB!hChNDk#Skpjy1BE^&)@3)yND2IyFP{xSVQ*IJzraU6jL3vB0 zhtesMdcGOO&Na4rCgm`ZT*~<(g_JUp63SwcO3FJTwUjL)4V07zZ1q;kfg+uh(?q;N z)91+|>6CdQ*_7u*@+cpQ6j6Q_DW#-7X!}!5875LkIY%Txxn87=vQVU(^0G+E1!fd$ zMKUPcL~a9OeFO}Gm5by znUqqIT*^Ztg_Ji$N+{omR8smpY^&E&4i{;lTqx2?nI+OmsT1+WnLe)(NvHfFl1cN3TcnmURiuGZCDKZHR-}`%TEx4^^!YoHbV~d&+n;R8z9M;)T#+KmB_gGi z*&@}H$3^NWO(F@(HzIA6?H;%N>82bYl5(*bMV?3o!ulpjTE zD7!7O{i&xMCDKf}M5KdqyGRe^MUm7ZGm3R0nH2vCTPv4xphzL*Y>^VmO(K<)#Uizo z)gld)Uqo6ddpv2YcTz@(c$b(yj}=L$+#r%oxmP5Q^0G(~SfI{?t)M zh$JXuMcODgh;&o#6-l|&jN)aH49ce>Ih3D73Mkvx+5Qw$GDIpUM~T!>&Jn4nOcrUT z%n|9JJS@^fc||1kGBb)bBAJvxD4Rq|DJjp}{!~+jiquig5lK*{i?mS|iF8xm6iF#Iqxe=NgR;{Lwt5aF zTcm(8R-~BnUy%yRqarnwCXsr|Mv-PpSZ}L$Q1%z;q2!6AUTH>gl}ILKo=7g`d67a& zt4ImuSCL9e+KaY7wUi@78YqP#t(5fS|lAzov(nfhwq?_`lNXle0igu9<$~KW4O5bI+&jpkNMT#k-MJgy4 zi_}p5D^gFnSEQLzFVaDIU!;fftw`z=GYWsX?O`Tmphzy|NRdLyIU*&Lt3@g)l_Is2 z#Uc%qw?$ehKZ85-jl5&k1#Ws-)%6_lf>N%8iMG7dhM2aadh*VHEh}2MaYOtB>DMyJk zQ?3x{pxi6cLwQRib*dS~FCv+g!LQj`xs)?R3Mo@WN+|P1Dk(3C)KWebX`u9ov{Lr` zpRL|WIZnhYF@3&7B%LxxB%AV-NFL=qks`{EBBhkn*KPG`%Hbk)ltPgNWtK=AaDSNK8)tf1!MLH-~i}X8DIbaCQGOFCqU@2dwMr?+i&Rr4iPTZ<6iHBC6ltS;F49d&dehcQnPx_@ zuSf>vRFNFYRFMM8y&}bw*F`EQUyIaGyhdBSp0ckeedL|Q5DigZ%ei+DGhKK~|?PT8r+_9vUN zuSg!{7?C2%xgw>M$s*O1TSV$84~Zlw%S75J?~8O(Hi@K6H>3DlB!klLZ99q_${`{J zlruz%DN{r$D0hm~P@WN~r>qufrgVyQP_}=^_NRxkzesAS8O2E=nUsklxs+Q(3Mr3? zlu+Ijsibs@)KdDrYx~ncIZmXNa-~QoWub_7lj-v+k#x!rBH5Jw&9+t^C0C?~GFhaQ za-T>wTq?#504izb+6pEBkW{Ff%o)M{~ zd?M07aawGxR?2=Nos=_0yfV}0=_2WrT9ItZYLPt3uOda1JwLG3ODV^SR8uY!siWL3 zlAydG(nk4Iq?_`$NJ_aG#oiy<>KT+%L~mr$y4v}0+ywz4Oq#PttLODyMk}^%Cmhyl|1LX~oR>~%k zPD;v0wz@ad^m(XAI^{HxY|3PjJjy(gBFb|jrIZgvswqE<)KOADw*5&^hKaOM&JpRR zTrZMRVMeh~B!lv@NDk#Qkpjv#kz&e#Pi%iGD5FGbD8(Z6l=&jflvhMLC>un2DB)UL zD|MC`#la$(lygLKDL07}Qffs?DDR3?QhpMtrS$*QR&SsT6=|j9i*!<^ig>e4pBIRv zQ(hLyrnHIVQT`Swq73@X_NSCGN~D@HQKXJCS0q7sMx>4Mfk-!{TO{QcGm3tn+v*vV z5h6L1vqcIh*NPNV?iQ(_yeLvb`ADRm@{33_Ww$olpAO0}ksiv~BB^uCD6SRBq}(l% zOLN~uT< z6RDW{2aQf7*HcbYyg7fGjV6v?KfePc7{QBD>qqLhl1Ql1g1rnHOHQFi&( zW=>FYMcOFWiF8wzh@{LjqxeiDgOc){&74CyTBLw7MWmQ=yGRA)8Ic;w`y%y}E|F%+ z&YiY;2jviv9?DrFsa0kaB_f%WyG3#-FNqXVJ`pLQY!#`b^#9)Wr&h~+=LpE413c%Q zq#L8irQyC3jK&3mKths}W~DtI$!T}#ATW zS@PzEDo>TO-4Cua9kMPt|6U`5AQh1H$z@t*r~TEOEK{%Q34o|WXhiI`q_53O1tKkFFR z3DVji>Zo>0tId#)5o1Kf{KlAHB*xw1I_Z0PPOtQXw4v3n5;GWb^&hekF@K00 z#F#(-Azg_1`ya9ulI+@XjbN+TKSZ^P|3g%(6p`c5Dml}0cK(ONA-nuTc7~*ijA5;` ze~22ze?%@}%pU&`6|<*XU!w*xQ=h%rIAVj`RxRbu6tq%Dez5C3pkti5kRR|04tCQb zauQnY?PgLQMU0Yt-SL!VkloSe{oIKUsyyY+O2{C{{%*!YO3ax#1ag2oT1%rd=Mcw9 zL!L}`3gvCIGE!z*IqyLZMypJ>Ga|zv2f1mBOs#c@$wi)n-7#8vYh}5`T6${@bt_Fw z*oju^{2A)jFpt+|@(gvCQ}&=7;x zhx5ZPAiHTvqkIe5UCThqsmMHHKhMc_hf}T-$)`LhGLiD0NExL^WFck9k2dpC%Gn}K zl$%A`DNl-QrMxfF=MgimKSYL5_W8-y8c8`zWDKQ3WD4bJkvWvlL>5uvKika9DOn;d zlu06+DG!P`kD5NWilk9u-8OR;B}Zg5Ws=Bv$|EAvC~HOLQT$(Q<|ULPMOIL*5m`%F zB+^A$BNEn{QN(_=nFmsai43O{iR4r66`4qB7Ad1_6In>f`pwo_N|_+iM5z&Jr@SSy zmGZqvpT&9<&dyt1=Lo!)v)zeWs+@t4;~~S`w8vCTm2)WM49MYbKIIt5d6405t(Nle z6o^{mM!3ydW;z!`)Lb{h&3RnqDR-tp#-Y^_Zkv{wP9>xmlH)cnQ86=}#gOYDN4q6Y znN|%Dm3gF_UT5SJ$k|%5w9IxkLCVnTST~!v@gYMFUVjUv|_ZNxbTt?odpQEq~giM@oHe@=8;wQNk9h!`V%o>6%c&J>Z6 zT1@5z$aA7w6p;rZC%F|7Spqr5tux}>fIO-{d2R>gF3PEH`m?&v&I^#6aC}a4OSP0c zt0C%$o$fBz(&*Se=etdmFA$^pobRrsbTCi8yP5JMYoU2pZ zG9zI&V%DS86t|URGfhj4kuVkVUo9&ba}cCL%d}Tat6a!j$SgPYRXr}} zLdZRkTih~AG38dgflaGYh?+^}y181a!g~n*W&|HbK-;t#V6V*AjjOQLDpUZW*N;qF(pA z+&Nmx!=%5=8FiOiN9iZhLOB>>@~ovC1$hp&?sD5H=RlTe$yuSQH#)a`n50K>ms=l^ z*AO$`%~)w-E<>vXq}t7;TnkwZxyLQiGSis_`51DaTdk$sxf8Mua=*J=OG2+HYPP9y zyS4PLG7q@k8>&|CD)WGwt;MV|YDRiMu1Xdovs(2;c6bS*j@W~4u5Pu!c?Yr))gN@H zXerk-!1s`c+$JO82Z&Mcltpd}WdlT=;fvhOlph)Mu-ipR{@c`g*!5PaK9`5PiDYP* z9qucV%b4RtiWqaMNHL`lqRyy?-D#$ka|J~8=Mgt2p=&v6>`ESWizwG2rW;c0)=(0KS(cXPEQ9DBsxcZYnSGDllwX^F;+{#Q)Ci3#nw z@xD8Tt?aA(zT5P#RxJ@xXZZVWyOFRPqflp5i@TZfCq%6UE$&vz4#}S0leD-QAL>4b z=_2`B7B~k&Cg5naxD!o`K4MowK6I-iay_KgZD1a|etqmVQSAElvD-nh)j!6@@t@T{ zcGI;qjywd_2M)n|-W{!_cYnCnEr`erRA1|kkH~Dur|ujh;Yj3BWB=TpN4XfH)}7DY z8p>?Ow7IpEMT}{4mr`DV%tPjN?sCc+$O0{R(eY2h?}Vsx{tGw37`yxW!tJ2^gc$Ye zeBq91)#qVo_hVnUrIaKWcZji~ec@J8?0fJFx7~>BSHEz(7-Qd+U$|Q-_FegfJLDr( zt32$B%xbRt!mZXaGt3mJXUy>;2@~U7`bm=W07i;Ff1>lqd$8S|MmZa;)NyWihpaU* zS3uO!Xm@ic*Fw~}+U_o=+`?8H+!d4uMbbVsd6q&JqxuH7NlWkh>npd#NSI)&uiP#z zGjWcZ>R-9uXDZK3w6_v!DR;Jrq*HtkD-~*e(i*IUmyVVidgqUC4 z;p}wk>QxTB&6I@@HU9)& zH>D1u=7gBnLwOUT-YGFJ>`;B49e%+)q1T7<6Z3>#x)H}dn}~bEBckruZ|@aQ?6Zj- zy}F2GU=)44fnV!dj(rlbt5+Hk6|;s9^ax2jdNy7%4x&y%7h8Z+fzG1LAPv#|63=UHSVoPg>l<2WDS zEuoY^#%QUh+yYT+N~YI9xd)5!?&Gt}$RQtjLaxdC#hSMZ~%Rqi|knFTrA8y}HM$Z)UgClm7q zV$_ww5nlGsMm~TnM9h(%_nVU5GxHcPMN99jb&OZ6rQBJMR;q``c%@o;$3D_4+WOBt zBR!Qay*A3T@0pkrz0H&(k$D7SMtfb9Gazb(9qsi{E`v1S7@p*L zzw09(&V;Pg(ueX8^PKFZQC?!6lf5C7)y#8>mqq!Oc~0?0YMB|v;-2#sGUs`tDT5%Z zwG^<`Q4sYWJk_h9oC;CL`Bbl(asgvb^AeP+8FQM~N|^;|Mds5z?+-JsMY>D|wt;T1+iy%uM9#ab4GKcm$TXmzGnLWymMorRWCBTk>yNsgc8 zIcIruB9a0r@VX+>A9AkO6On7Lja7p*JrgM?x;} z7DZ$vFMqBcu0|# zu4TR+#U+qSypdX}oJ{1I44LFDinO{Ba+TK*X*B~f*=uL3qtWVC$W(9SpQ=`sqa=Z| zR~?Z>kQ=;3T1=lGgWTw?jl?_+DfK!cG4+s} zyq-u*17wDm_Lu6R$+HSl<_*zedZ_w*vzHx_cM&tw8yS%gAhW#Uh4 z42)gLt={;EtcTp@6-Q(fq|#dwkuJy`UPnZFAXQ#hM7*K6zv}f#>aDdSWPz6+k^Yc- zy<9C-&II&lAIJk<9b;~QWI+~r%Oi3GyMr1nV880u=>K4dzUQtBug1q3BMx+Mv zqPILEk3*Jw8Lp}IGHN{sdDY8`$ZL=WuQ(#jkpFqpBJwfhb+3Waf;?YC-td|t@*O1M zWqYR9dc<@?-twkVeun%FdB>}WNN@-~1?Sa8qz`1Z*GBQS_nh4!E#Bsc41#>%bw^}> z$Qp0BZ>sl0t3x3ld$|!g0`iHMACX+hr(PLlAGA6h(&kl0n17hL~PC7IF>bYl%4-G6V9BmmR1+Hzu78nFaaQ8*U^%4x-kbZ@nDK6(S?G z^sWWpdZV?>4yPeTt+3yE`IOrrYCZkd8>6K>d<3G_({H^Z6QlQsY8C#@tBlB8^rzFS ziHO=c{NUC8OX?z`R-zxgws=h^V_*KY1Nm<~y&T&-2lr zZf~oJ30FeY@%asxd+Hd@4&R5Ub@?|hg|dz@TfJ1uw~X294KyNqV)Esw}@#Qf>C zX_@Jqgv{#M=wDuTsK!3i84I~_ZzXHB%y%Y0UPr6Hz0H)XAdL{mZ_po&o9|46d42#H*!F%Uws1%elz7zNb;dd+9;<%)ShhzKSh7MaCUexMD5vj@Y5-F54oeCN3naz z9sNSeG_+E$=}vwnWtdi z@1oef=q~=i9n3Mbd(mC|EQ;NW?&^=H*uChk{uGMc_on)bD0bhQ>Mu1S=lpK|@`$MZ z?B+LVsR*0U!#Iv=U%!QUK82`vcVB-kWiv#*yZibbl;0TB&+nw{vZIOV=Wo^07(WoQ z6Edgy&W>uNZ^n;=sL!RO`6*gvhbKXHMNEG`m2wfJzn0;Y>5zet|M(**RgitO6j2_B z90=LnFQ)tta;TPhl#d|l)k*gkQoe<#qmk~{QvQYxulrtprjBV$x*ej9#$JAxmYL3l$aAHZ;$8LFlNKXJ9o0d8VXBdP zA=e{jkl(zUl9|p`Xk}zfUlY@Tm{J{++t0+zLd+~J2`!CDKO*K%$Y6g-nu%F}n1x!> z`AwCn${&zTh}qYlL+RJY#$*got!6uie($hViIxgyC}O_Xt;!d{CfQG~4fTg;ndvmK)lff|@-al6 zQA7Pg%6B3QwUj%*L)5!zsNbw*c4+U?4E0-0Oc>wU%*I3gcE;HIHi!6|8I#7CL;P;W z*!S0=eh*_F$2(5F-w*Y}LHgB^88X}N6A=}Y?WadX?U9E0Ln5N~NQe8`TFhP09dQgt z_(fXGuBkudXn#sX217>rb0U%jIo@BZrT326iGKUPWOGDR4^Q;FBcggZ+Q&!M)mf*< zrFwX>pQ6R|=V;W*^V1?S8giPS6_K+cXZX2V8sq!ns9p>?+b^RWN;$`Gpp1k}M$EZ> zf^rVzS}h%vVn``ujNeJQ9&)pmlni}F#czh(3K{FCQtpD>sbwJL5y%3_dHxW}GDwY< zY>M3#obTsQ?5^N^e>|lbtsX_ILVqG<6XXdkm6YUN@YxN>1%5SUFUU(;mQ(Cb=0bl3 z#qMM-^gAdc(dsp{8s~RX&V#Jd(r0focDu70@264h&T71$L$N!n34Sic?yM&G6DfAr zbdf)WVs}j!`PGyXRDTE6FZOFFm5>%KD=4*)Pas8p6J;f2ot93@7myCfC4S02W)$B; zzSWXSN!}IL7FseX{UEl1gCT!v8A&-9;tj)!(9Zm4-IZg&@#`&ksbySUsRNwK?&EBq-GySuo;FSB`2eE_N# z`%5YFAsJd$P@aMu0J+ldq9hA~!eRik-2a@HbQJjQxbam10+iC;cJA zO`q-R@T5PSVrRdn{D~Ah`#t4Pqu3Rs&R;^YD@dKcoMLC%r~S88d^BEss z(@^JpXy?pl{1l2^^`7-pDR$L+)*nc*tMF1khhkUZrG6RZM;x(3aKxVT=TLUq&2x^@ zQcba|@bi8R#je87`%5TxHGjchO0lc?3w{G-4W1mHgv|ARf^r~gsl7zK-$Jo#!Hd3k zgc-YC3tse7DRw@5$sa88ZpCMoAhew${ACA|(0B6D*ejmyx$OT$5wUj$&LDaR?D!+g-5u!hP z=@(P3fn0`G2|xQ7mAT6KFJv;L(I2g)+_{~3-u2rk4?}K5%)9>Ph|GXA`xVEkJmt=l zh?xa>&#$GtL}~GRw3zQssM zl&QV~wdSJwhyJ{ijI==Rf~@hEYH4)rwQ#H7lxJdoMvOXQt$q*1?`!h3`d+^2p}ng8 z$WNu%>*bI942r!H|H#kQf^TggkIMX!zl1VSB=ZbY{XmF18Xx&Zlp`c&9_2)cy4&-S zKk{tTst|JTVc22#(tciI}B<(yS zuZfhMZ{%IbqQg|H8p?Wzs`Z&)T4-W^VJpZuBThdXvyjpUqV`Ci`Q4O#Ad8XtbH8D{ zX_d`ZZT_^2jEsUjiI_Hj%p}(darT}8c@DD9p9oRs;RJ}vyxt#ug^{ZuFC(VipGdg@ zqQ3p{mETRNpmg|o#ims)M2+-oKjTUxOCf5c8~uFBD5u)anZ~bbDy;u32Uqi9? zD!=oaDCeS;I#)aW7Rsd%b%uBPos?@K>dgG!@21RzsMqv+A7AgnxXk`peWUOPzd%c) zW54&e#V?}RyDMA#6_hIEQLo?@U;RaSbyOb_>7qOjQFjKm_}SN}n1s^=QTLj<{6$(C zoevoEqo0V#>FD84{^+Ty)vDyLn7P}}*HZ2z^*7bK{bDT%r=LhCWw1yO<#3Ud5|z2! z84YQ~E7UH1d4~fWo=ueNI6OlELKmELjtcCpL7e-_q z>w2sI2uThKBcjd?Hz zcSQc!57*T}%k{lI?2DM~gY0R&k`74;(r@UMZroYgA($4C!HC&0Xo<-Fkez~*8%-%lH#kpOE8C03sJAdEo*lk~n4zeZ8Z4xI zK-n#5)zTQ+`}2K+4vM`S-#6%{*t>;&1Fux~Ika;^-yl`Xo1vW(`UYu?vCjhf1w$zI z&VRpPxE6CaemHvAFUY5;yYa_B(t>Iu;TH6Hl$Kgb@_($<%~1U*cXo%Iu4B^6jO+tB z7xJGVPm7sbE`;nJlo$zzBSt;lP7g{c$3rebOnOjDISo<_*(0daQtpg}T%)B=xv4c7 zasy;QP^+bP|1%(1PO*EN0YNjx?l%Sm?G(G;7!Y(*?A~TT5Z%4W*oU`eHk`Hhkhbl+hlw!7)i-a-4FMp7Z_IPV>FRw=1+20`wG z>=X1*MnLX{3<>(&Wm=77tNnsn%Gr?n5VL=v{-UZnTP8vtfn)}GlOz+Kt4QL)7Y-6{J+F%p2lgiwxJ&dtHzfQZqJIp|gspTwCF|)&Lh?);kOUtY<7ow^U4e}VHzPtS@G7k+BT4siMh*3|V zh6XA3m^}8qJT#a?u~&aXgLxEt^*1!Ar`W5%LxKj1z4|*OXs6h#ze9t~6nph|Xppkd zRJT`T*+DABUXf)7Ia*e^c7+`ljAYDoUJninMpNwd;IN>WVy}e{3rZ;VTKKS_jAF0# z4iBnr4DSsd9xT$*`^0c~kkB$yo+S+rHf!m9hB!RPxmS&=_Zi}dpn+naA&v+TfPWf& zyOS9aH0zk&=R_ldR*K!pj0oB(7vdFE@6!=MC&lhMW_ zGv~3Ual~cGN7PX3?X9{D`cC934!IhGFV!p zUw7R~?X`{z8nmo(Ri3xe>iA&c1Lhdsi5{xVCkD9>8d25Nv)a)?4W*W&I4P*7)I-*w z)k#4YbFo&XcUrJ67sw1NIWBI|Nh^YP88G-sM zo~pWPrS<@42FoL&_5f!EiHNBESV7Pd5w-g|J7|lDnp@5ZHb+G5wayK?Bck?NV*-3( zS*okO*4Q8=B5JR7UXT_Mwbwd7$cTvAYZV4r5m9@s3xb@8sGZe?f%-e6cBE=&H7+QK zh}v0=55`ACeHvjxP!W+f#9S0CiO5FC#X)mKx*$bCS47l)^^zd<(cbE6cY0|sJR*Of z)n!3pM7-g6<`R@eWJk!Ppf)0DkjsOnhzx>U5p+f*6H*-XsqL+P805+zJ0izHt_sFP zL3vjwWqx%Xp4y2olXsUBBHKAN`kD#rayMSdTlVAV)v`p z1-V*!pORl66lm#vN`8GXMaz7Bb#pcPGcB0&q{>|7{EGKQDP(%EL`#LU+a91Gt(IiQSa^4L7It?`+l>7fe}$XoE>CFL_L+hB^VwN^;G)S zAXkfdZaxovo*RtOVxCjq3%Mhh7?H;yRY7SakGg`c4r+}!&tnwoS8?tRQlHVIkZbCN z!FVm@j{Oasg~1dPBcr%KsEEjOsC9p^Fd{1<4+QG39^&{otDZ=5w#P^x4l15C@-nKg zLCm8;HRUZx8)R`%t7U=nG30B=m`53kn8P7227R74t&V}53|Sr&Q}Q7bAg>0k zlrfO0kk^9r7fh=OkeQGbL7|o^=Nia7$Q!|ON*SaEk_g7st5#LcV~{0~w}M(Ncwz{7 z4)V6$IqdKuM16*;Iq1?c=33@8#54yPFRDC75|GuwNG**?-=WoekoSUOO6_Il^EU4X z^C)T*t%!L)XrOF?d`eBf1Bxxsz%JX4RM(GRr z60#1< zPl828;y*+5UCUdpFIJ7aPn+tBK>z+0|s#V>@YJf8EF<#H{VEu)Wr1@RV*Z1euY!Vz41#n7 z?UZqd$%JeSx+&K{)H`ld5Wb>%xWFlc3`5N3AWch^a|a{`@=Z{uWxn$WWHjX4V8yGZ z)w7V(A>RcJ4MzS483Xw~D0odtmD32h2+|eQP(FZM1?di2R+v^DY_&DWTxnYU1Sv(# zwjiNpffK{5KMV3#Q1OO}neXfbsf7F;RBBn^q(iD9PHZ716QW+7t?Y-c)Jy@OUe$EvlIJLM4do0h4u@vBu#xigotTWlKT3CM?N)i>7to{4#ZG5uoM zEk<60tVK*(tex^UhM zo#QC`#ui3Iwc0OM8<9OA`^OT@b1_=&4atnPGNuBuKjgqzCuK2YDCD5nl8;pN`OeFb zV<1CgwI7@Qtbv>iIW#up6BF|#aqI!5{tcx-QqB0l6hO9HKDj|ASm%y-o9yF3FqKQ{0S(;p?TL&nF(tT*xu5^}je3n!`;D7o-IV>2S*=7fVxzxOnK#A{gA7BfvRJJVS*^-qVTX=MItDRn zmr@=pGZH@qax_}ajI~kDft;u%`)iYV97I*Gh|Mvg`*Rv%X2&`xcFnydmcG%{Qfuyc zh`A+JLQ!k(rI0zXftyTbyXM{&E2P*p_qNzPBYG4n&uy`+%_g&2b4ws|V@tI3uDNq# zODPl4=b1Xj`$o0e5T6UV6>@uQBIO0f+!1RqlJq&`4#dohrGKkiC54zT)k-}tR$?Tc z1yL(wRjh_G4pNO)Rk1ED8+D)6)kIY+>pN5Z7RKBat1u#K?p?72W9*t+9c!o9HMcqz zcIvUm?HN@a%huAE^c*tZhw9a_9LigeTFBk8T#8zEmO}20z)6_sm(qD}{3K@==EwLso zjgC53`(m_PVr^P_*W4|!P9x3>Xmzx1mHL~?)9Abf$%FhHn?~6P83XB#ZPrrm{06xU z@=L7Y57TNF?CGw7Y>lOFGyORLG6V8^Y>}2m=QPM1$hKHYkBJ!rSpeyYm1;3Diy(i+ z)bBZ}v6mp`8OYx;^~;Q^hqptPLz2QaE%TjwAa6mE!@R$GV?Ka*VS|?W&Qip*L;SGM z-zui}43C9rT6&L9EX>fdKp(?z(JB@WkF@#)5{3m@8l7fj?t#R^>PSp@3|_@>DPz7s z%ubM$uv<&Q>4FS^>=foW*f&P{(#1Lg=o?N+R#NU9gqXt+(=TkL z91c;lME|hJRWVgg4kQOL{lhvfv%?c1>KbDY`~i2JIXnlVu0i$)S5Pj2j6$mcVH0Hv zy&|E7h|drb0uO1hFi6ihYvum)8cuiKTkl)AbW)= zTFS#0A#=5)nHb4DD9kkCtVGN`h#3?XP~L$&0Lci;Yz$^5^^5fThSgfkXLoB6GbCIT zk!K+Lhbb|04A&sftB?c3b}b8>jgYq>2Zcl8DrSN63uF!C;4mj58?=nx&cwtHz^4o# zS>aa7&X69+&~OR9=c)4i2NI0L=M=-d6eB|*J3@wqty=JF*pR-E;bD&vx#Bn?OvN|T zaJGbB^EWY$2-7L*DX>}{jtDc1=wmntd5#Ekw3y%1+Ygcx=4$Dke{#a56g&SM6=vew zV(L6pf3M?Uv^qL0!?#(~oM7jl%_1?OYeK6xudmxuXP7UiA^C+Z5OFd(rWz1<|17lV)=Cm-um^F+!J#1!- z`l}Ht&*@<+V|KuKs%qtjZH(C$qGIyH4#phIm@~po#*Amo8DTeLWhLwO4F66Aug zo$?}N6=YmECQav&_rs&Df8Ns620>Rj;T#A3&Z6>)FGB zkWV2`hPBvjs!?P@)91M$kKeT#bCe@D#nu#}=+)8IIKvQv)>XNzyopI1ce zzV}rTyQVaV*fZ+2e~3Dx{wHG3sMr4?>Wo?uuGRhN{XD?Ruw6^<8MQL()Uv=?gmF#B zf3FPlu+pm|_9P?~qj)1+L{YzWp<-5rTPbfs_CQP`3^Bi}R%;-GA#aA$v{X6kA?i3c zg_)RPRm?AtgAwy~SU}kxNAxhryP=xjRg99OA^~?Dx&k2wdk>`Ujn{pmyO_(2%(-HGgn2xhwwYrWmYs29YQ8Ax|)mo~Y zd5BT6E>!P5)#`bOs@@)|vqVV~X(9)Y{S2=gnS)#M?@Xx zjbX}A6SD*UJryyV!WEQs{QE}8H{nP;NmO|bfZPn}4ArwmB}YSUh5Qia<0+w%d`K1K z$8bruk}Bsi$U~5yL-p+~6;lRT0{JD(jmWc*U&9j0?TA?h*&5bIWCi4pP(5K+dG1Gy zdhh%hPSjH6EQ7p*7$>ftXsehHAgz$3_{8BxHbK@w+_-vfsCxJ#q!Z%DJ=_yhtzsD8 zHb@v(Pm7f72HEa-{9jz%H&d+!Li$2d;$66Zt76oz)a(Pdp*yaeC)D64EzbZ(^L8 zXmtXlU%V$GXF&SL^Km_{W)gLuO?_`-kN7k#<<4zrr7{nQH*0Cszow-=4K*;{##U;s z8;6*I@lGS*-Dq_QWKg`DqVD@$uEoL3phmicF@xhF<#~u2``~yV%1V(mBYIrwZ*~lh zXKCr}&pvT=6|atJ{s651sI^aAU41KQMxGlW`^P&dpHMR6`M5ezG3}6Y#2geaipXt{ zq4Cm)R6~Zv7e?eE$Pw`dE%+;V$nz9rWW1Fz>UY}T9*j@v#51wVt6JY7=0(Kh#??++ z$zPBL$f$TVWk<3Zv*vawblwp)p;$1i!Ri0xZpP*G>2eX{`<`MG$Z3rkE51Zax&D<1B?a;ExhC@@ z)cOJc>)G*%lqrzkAm_xVP^LrFoOy118s%=vn0OiGVanL}9Llql^WyU;Z$Z>tcYb^! z<&74{`5Uzg`0K+YjAv;vS7aH8xiUUFBI>@xtvYvR-XU#n~4 zW&bZ`YP>!o`yEkYjXA0p%vhC@teD_p{Zs_(aM|i28Kaw0H?+ z6Ju_Omr`O_NmR@Y@i~-zA*%X~@p%;W?O_#jW4wlP8RRXjw$tOa6!nc>^*fW(<4cXm zy_C{;YeZC@(s+lKna=HKm4{K>6vq!{;yu`yv;cCZmV}l>(qoYGApebbo~&ZZo!20j zKxV{qPf?Oc`V?{nq%2;WXJUSUOof!kQ%+S<<)mPazY%hCy!$j0vkzn@q$1vMx{;$P zv*KM^5=rMl<|1Zxye418B$BR!+z*)(uRPPp0}%Cn;oIWXTFRZ5Ade$vZoHP#1bH4( z8LvId)cPFqGUWDnMuCx^Aa6kKjE_7=3Hpo`TfO7%jxV98Pp&Ch5MQgM+!+DsLaTe? zndh4RoC(f>A7w^7%<27TA%z$hUxi8*8xd*Z<f`exM7@F!#YYw@!Sh_~DO8^q#V1lyDG$e+D1#}F#0xJ_ zt*RXL$+bg~`O)~uaVF0RkP{$F;tRDTlEy;vAWy_=#+&MwL(Ydh6>p|gKrV*V#Y-os zm~v-62>Hi=2)z)}*b^U&|387gCA=4&=Ovr?>Mz%175JG5VN=XQ1 zGa-Z!LI@$mdS?iY5Hi~&y$P|9>CN}?Jm+~{*R_3aw{N%G{@DHYJm)&+T<1F1xqh8j zqY!_LdM&0&rivUXoiQy!Dl`xCsTJ6IXN*=zIS)te459mwug4^@+=buhc>pnQ#+0z^ zhZwpu`BqF7O9rGFG2JoELj3z$Ju$69>a}AL^CV(=V#+U|ZS`nNAh771}CDALN6W#0zCj>7Ax8 zARop|3h}qKHYWNa(axg9h7hwhCQ-<66y24jr;{JYG_cT}TY5U#9}`zZspPY*ACT(P zm~1JbXRs|A>%NFt!14-&#;7l1@`Y4~=$q?tr1~zG!Kp|^^t% zSquo3d0k8wOFTrpSroH|^VthR+xjM^pJV9lo6V@#`j|nEIUa%qkRc;7Mzr&`kPR{T zQ3C4Wk(U29CP9eb@-S$HB~-_tFX|ZF6p2ws-0u-lN9CVNOm$prR)RGEWS{p%_2>UZVpOaC8&kqH z{V!@k&s;RUOh`ot%Y=((E}CA$G220?4WslrjyV8AZ5X9D3aJd8$T3s&W{z3HF;nz5 zjya!Wrs^FWb3MmQ)w?<7ZjOo8dpYJwj)~R>IOcT-J*k<~dUi8S zPhj~6LbcdNPh!~tM>f@B8$DG>WoRxP{g7?-JQ*YUDm{VPR`0!nwpA(Kp4bh&E~K}Y zNGU|B6iAGod8Lrh4UpMFvRPI@=-eHnH?q(bC&lP`vykf0tB`adtt`Ee!-RCO(CUJ; z_FAmo#X_qK(%ay%dM^vDWOx)(8G1hptz<~^TSH&ZLMs{OLri^y2w4J?^{OAr+xtX%7l1H0X!Y|2Ln7R{!<8lv|pDCQtN#BvJ99HhqysSXuEHX`O=J&mO%BDpM&Mx=!0 z&4{dI`4TcJ@@Z$Gw-x>nGRQIocM<5i=3w0@l`S?MLNSNvX)Jqk%prOK%b^^Tq1UpU zz%dzmC(D@}bErPVav8@Qs>dyp+p6T4!}JW6J2>Vry@=&L$kbzSrKFdz&^rZ&kaCu{ zAT&y3>NPB1Lue$))K?1ekDVj*?l<;^xh2=`fE|3%SY$4U5RS>#ZU@>*d~AoPnN;Hq6sA%B7T|7V{vd>D@xA zLmSscY1u-0g;a#-iQ)-D1~^9ToyGck7PWU4>!U*IwTF;u5mGJDoeJ7ky?DlWJ|tgH z6C!6;)Xt~tD}_{Q+wFnT7%^w)>sjcXt{Wj|=}9+;))4i&9a5m@38~QD$F}Z+oUhjk z8IF1bxjqaj)Yk{(3CM+dTqWf*9Q6fK(OY>%dLqlWkmnF{v7W;61LP&hC3-r`uaMUu zm+Dz8laRL|#rgu4ZH~a4A99(V&tgM9gj}u{vTO&T_xZ2TOIUV+e1ez~y_{ta$N=O@ zy_RJ^$k&jo^ad8XE1SIs?wsh&Ea`~Zh?r8njpYc)_mE|JC(C@uDC8P_jS&1M7ldXh z*Xn&NMG%^kT&oXCiS(#4eRM0iPM_pdYMt%ndUTc8Um>;5_Hx}}p>?)@#n!La<5_5( z?MWd?EUF)t>nTF~eps%jbIgq>A@$V?J(K0Zh!n8A2nl6VOcBcm5KBk{%Xb`egWkjv zHBZLep!c$<`x$QW<&yLgV~RdVaNkq)^OFdbtpMm5CU-N~+P@ zrGzL~`bwfk?_@a|LSISL=xesh=Vm?rCd%2r^1Vfmt0Acj{f3-rguF#hVA=LaIYQo| zCkv?#sdcSy)l*s2y4JVqxh!ul5ajJN4! zEOdvPo?qOqSFtRF(3fSm>vb&WLH5hW+(mC-p}XL7gfz3<0?B~X>8&hHkakC-U;h4Mi zd=|RDNIj}SFJYm3hjiWDpjQd0)EYVF9(|Bw)GFxr=*BIwrfL=Rd-MbrHLJW=PhwHC z%6s)x7FreX*ldjcdO8cO3YaS-i-lGNTncH_b6DPoTqGoqg;p}8Ymoc(r7X0PAzg#q zuNSe$1n)F5%dfITe zkQSCfj(JFLWBH9^9@0BmLPyDb9@e{A=*}hO^RV8>vM+?*Eqp{DU^xOp?-o9y4+-&~ ztTpSS9P=vX3Dggp^>G$;W!J20x61vcuIwJwLoDjb?omCCMP0Q$rYEqdtG37VWEOSx z_qd+QqOSfP*E57vinWFwLcLn_0vRLDMD$GI3B4#Fbi_TOHwx+2a#7P3qt*C`|Hez0awCa;WDm41K;dRKl}l+= z(%1F)dWxyg`jP5n^oBR}3?bFpd2x99jZ|HF!JRUO?i6l&Ebc|>RV;Mh%M_BiLdMWt zuI(ZJ(I;7^Lw1Gq=(TrIOue=zBpvdBJ}RVAI|4Ef(x>OFlKIeAW5+_)>Juz^oX@BF z;Jq^DT*wl{4C+buOR0cd4f#&bdr(RPWCi4BJ>wxMEs&=mzw50mosd@`6Z)D*WDH&7 zz6be7PiU4h09gz9SI>D=%D0dWkm%UD$E1uyeuwC>1uatOo>km@{ELl#LW+r*#ZHjz zVhxt*kUb#V$F{MgK=y;|7(4!?Om#5iP{_`)lTS&Zx$|+5nX$#KQqEvWiXCFP7;*|? z_J~bxlQHFx^C2m*tX9*hI<8NT!))t8W5$d$3lA4>Tdasi|?HcyDW;-KT}+Sno~8m+QRtK*i% zrhX(-sblB**aa+f#9fYju8%Ed*^E4vLCRx?Sm@Zg4N@K3yH@6t0C@;f8=LX5l>Hzt zLF!^hS+XGSK<A zO3LRDs_Cn-<18bPIf&_sEgY0FQS)(60*$(UqFF=TCQ>o-#7LaHI3$0n`!r4jOVZ0-grv<}R(kZ)tNH%eKA zRI4HXjcxr_N)hA>$S<+&-$}U+LRW~t$HomyX@LBKm_K40zn9VinQ}ayKE|&9uN1oX z9uJ8!27i!3{ctzPG$Y|hDV<2Q4`f@TiKUlCHEXyt!+teZvZ(c+w>O$u z&M|S7jeNE@+F8{4&pR01EN>y7uaVCVMn6lhi@6wNM`MUZt^d5EG0swgRJ785f)V|* zY#+7$bAl1aQiOcyml1X{l33JQ$~zfpENY$PosBFOwNCQRMlOq5;c6G7fJLovwTn^0 zqE@&{G^$wC3Rj87N*1-k)l8$AMXhi((`aY8)`}Lt$g``_&7#&f-qq+=F=32982x4$ zLo8}Nt69c4i(20}$%y_%wvSrhILU}(QLAa~W+btw)wFgq(pc2GSi2ipEI(NCnsIj{ zmqo3MwTDrlVt8GwJ&Y0-wT^VMQN^Oxkxn*NvZ!^E_cWSW)H=y~8tpX{ z-p@=kI#_;$!vuosfF%e#p&`!;KM+X@Sss1)0Ve$Gifex5bVyv`w_FdhHXI zc}6V@y?J#P@;TCoAD1yC_d~LbF(Ioo^=|4>M#k@=%;F8DIeTM{Wc0H9g`A}%{vl)N z%t>$fA8n)y@$ZuyZ4?Ue-w-(3C>By3c|+i6qfAQV>gE`uL8gjQPfL$6(*LAwRcp!E z7JUUd-^di=U&qZi5U|DbFD3H%7k#ah!!bQk^qlBX)cyE~&~u{aAPbC6xvfZfPBOX! zLf7~w8S8}%N2%xCCmUk{q37KT4Q)b{S>!`~dXbSJ#D9{0ic!My)?V@*hCHJ;kc!qi zJIzS@OV<4$)Sd3_EH<))_^WC!HcD93s@jW?k!l8cRTDIv8|D7N*t*j8kHq{T)Yi&`mkv5_Ri zUnvyj;TW}2=n^A~g}z6lJ1|R(To$!<=n|tqil){9USgC8@mFYEVl+vKtkAf`=w(qW zG%hhFgve2XuEduZ?UQm}&~5PB(tcs z>CQE>S*V1x7Tmc;Q9#C%aaCf}1%%$&KHq2$2(2<#Xbc2|j+6_Gw154&(>iY#8hHVs z733~5$^t_7DvOM!fY6(87aQFHp*8L4$!m;c zA*-~D(R({E)?H&P4anP&GNXm%O2m8&x!!0G$RMNwKRHD?S4VzoNeju=s4~)7 z)Gx5#Xk@aeUtqt{$YG)TqVy!a+Q?<0`=azDz1mpHvK-~1n4643mRlhda}$=wmgQ;S zm>Q#u?>s&NO1z8oex^L8yn<8UrjFA>yvSv7TidBJS!Nqb%DXSBkma7-yj?EsD9_ z(4uAC_kxr`?l3|u2SaF0!aIyOmiZi0XC$zk!ZCG5GRxTVeF8jAQOH%2}dL zkTG`|wJaXwcf_qSR1vO7fF)i+vLvLG}*-)*$BoDHGXo*Rr#ma8DN+H-@k zhUFH{=N_YvuC}W|wu;(Jx!$uX$8c2qa zI+lJ2?bAn$2A1^@+NX~g%`DyjmA?VnY_zg`1feoF8y!L_L!(GV^?KCkV)+X~F^?L( zEZZ!QF^?JjEYl-0$g($tQaxsjup9xQR(;$UW66V1t3GZ_3XwAh8r55jjBRM2uF`0o z?ITgs79%GhIglreVisE6D-ZINQ67*pA+1KAkZSR}$uuW<##kSa3lQ_H5!#lv<zq$FAF(#xU@|&A)8Iv5NevjjABYFq1^^p3_&9@Clh`)B| zY9pRw)LX%;jU*Wpxhm;4QUXFfv)f1y2(2CZA0sOuw07t_#sVSo-6#5D=Ut;*#)Rgh z?mejKhei!cA%sS*4~>;9>b}-TMjMN|ul149DWoFOTl$P{DdHD(KS4fyM)Z!d<*AQ- z30Z3t38~hqv90w&$^tS1`Pirl$R=IJ1?Snl?+lw}H_Y&pNe#7^b_d>QYYgpzW z<`KxYW++9*91fxF#hAq`^lW`KB-U(XIUX^eKrAz~k4&`)vJT>!i7Xehgw1pz6IJ)(kUENJU6pZOt$5)j4Xm$!& zr9F@6GAi3S!V85sgle> zAw5xxVsT#wIVYK2ff$;P?Pd-MsnFIUAF9P3X5MVsH%3^J&5Q#@y|i%>B=#=`v<#$d!oM$80)CN;0Gzl4^Dck>_{%b(;gsej(MN!x3{cV$#e(7WEZUnmNjH zJYs0JoMxiAwz!+BMBYuk1NqG57(oNVTbnC{RENJUp< z3(b5X__ZeFT#T57=29Vk4_|1O1mr3aQxlN4u+MVMl>wnUc#F(7A*-kKU|ZKA)hXrx z%UTFs$DL}f7qUv5bqe13LCmSEQrIs%?2U&iB{10-OIU*&p;`$ZlD96~? z7NxqvoDi~liiz^jm+U2`b_BJC-+xNXkPyG_C1yN}s{55@o)CX^xU0>jEZ=@3ue+}{ zi-h?7u+%J;F_AM`saX|}Z7|-In#}<@1kZPtnQa028++(lvnwEUy>p!zKaXnR_sr#H z9*gRk*PBa)_`T(NvsX%_w^W$Mk)lV1RByS#%n{XN*X_enW2OqJ4rN02M9j@*4$BEFx0r>T>P$!~Vs167SgvHL?$JMy8J+szCi_`PfhjU>05Swgy_UWOco zRJWT&EUO_$LGCcyS=K^wAa&-Xke<+Y5Sr=Lo5s;{Tbm#Y5mRrL3h4<&Et2ELon{M5 zY(xg7Xf&=aMyfl_Q5G6|&lNH$q*|l%#ic?*$I!N_H9B8Ni4#((#UbZqA|{1p7KGk! zSz#_^nGLBz%t~(6)!V8OV3pY+VpdPhLd@-mx!Y{arfsdBdLo35?t9I;W2NLn=!m<| z>|{9~LZeNi*~@Y%ghq+`&58Lk)zuK%7Y~{Z$4R*XayPd1pxMiEJA`t6$Q)$34?Tzflxlrnwcy+La3%~k#<&N zOPiU^F^`{$*&R|nZ{`Jr#@-jq0wI;!Zpeq`WG|ZQSq@-%*~~hLN?55K38As>6|+gm zhNxV~yGZql*}_r*p*h*BW;aVaghut(%s!UQ5ZYgz<{-=Ar^@~QhB+c7rW!(f`Au`2 zN2&HsXQB^;__rWzik>U`4DQ2)n**aV-VWQtIb4~O_2XVy3G_RF^8QdkFOpx zm1F85l&Z(f5K^Jhx(;+6e8(*281*|EYs?y!5ya5e-!eD*^Lzrud+Hy5xhfzZ?BPt5^N zbtBi}Q*)5zrHHI&8IH&h=d=A1e1(LZKQ%{Lj)sgvJ~ziX)g=%bA-^yuIOYKemFEj{ zlH~&k?VT^p=v-0vn7<)(ZSbWTVoA-%T>&AuK~H@wrT{`Ye`z`_k3}So<(r7a3t6Gf zI32Buaq~+v=@iOG&KOO|*Jiqq;V7E7&~LS@GmC|6i2PQ|I_dI8OVrP z#iGt2|20>#s58h9W;=@-pMNq7PNVXyo}$LXU(B+_B$e8YD03=m@vAw&azD#&=7^Bh zQ`Br{lQ|~DpM7mI$AwgDE0D*5__xW7ULxwQEyp{+M+iw{c?mhwId|MF6w(u!BaNHw zENYJQyQ$?w@d>PqS7AqzV#cjR^5)dQ+{mGh|Ka?HVfKG%GV8w?nqE23ZbAnHwM> zYfOmz#587uDjxW=4q{PfR1APoBEh|xo{QUxov8-$s%9+*zvaM21 zwH&FQhPYO_l&G&DFAHg8S$w7(0X(Z&#Xwdg#%mxUJ0Mo5@ zDNz@lB~wke`i1y&+8NfM5P5WejeKTUqe8l)enKjmW6!XRvnc28sDD|uvr>d?i1NVF+FG>|pf|B+||lGhswNqikhz`StTrL zPP?16l0}VUyIajHY8>0cnq*P4)4i-vfy`NrV|!Z*Skx?cAFG%}-A~xZYGYC3*uGYW z5Puxo*Xk4^uWqPk?rRMRk!LHqX57yj3kY2^?r&wEOXcy$gafQ9A^uS?$LbOy$Ec&R zzvfuI0XY#e*UCFj>}Ao;wDokWKuEpzDfR`8NC#O(LVC1~5W2oN*eYgGZ}%NyRk4gA zhRz@vRt<|<2j);~4a?u0&*4_Dl#p|-{Nggx8embs`I2c3vZ${zGp!LJ-JxBPie|!@ z)|e3gj!>qholo_u&<;Qhjdht;ypSsGV8{~GeV$b!q+3&aFw0sgq*|l<5;WFjSzVlu z>Ip|#<1E?8ht9A^TXBVQTl9t6Imr2FD@jPVMm>sV%g0z5LaH^ocR@KHYt?ZK^;Nps zns0S;KBpmP+84)J>p4dC@Ej}e0SUo` z_M!3lB&&~8orRjxb-_v25X(8(`h}>)$(D8zZOfmxEVK+EvYjtO%t9+ZAhh3ett27x z*+eN~7FjEW_-FG|tVS6VqVE(c5Oa#vA@d1cgstBsq)SL;=qd;x49~{ks|YR)Uaf?Ion5Hq5t{vaDt~-D+l` zyUlbT;0&vch3+;>=@f$RE0K!s44h&0bIdvj#hhskv5Z7yTu6_Y<^7PBN{`aJH4tG3t)TIaVo)y5n(GKJ^;SU|)b-Xk`dl721RrdlPc8)ftc;$R*a&%P5tM z`3Q2UHXs`zmsuHC_%UOU%PphCmwzBvSRDZ|P^J>A;7UIx9&)AST;)p= z2&uGEmizJ)q{?au zNIT?4EBAUorVCPSY3083LT<9k0`du@#+nES^^KdY_6k4Mmx#H=O1;6C4Uk){)_{ya zYOV50KV}Sao0U-I%LL?hYcwF!a9`#QtMEoY#)8yYan-&gKth7o3LidvHvdRLI16gI&1Y{xPZmS_6XF?jR=75|BxyNb` z$mNiGt?qy;-wsY759*NUPNmkV7F)TU`Okf;?lb3CMAfXRZE#oD6BR1_N>$;*XJRny<+O6oD{g$tQyl5E#xf$}3l_12wm-4ce#FB`9NY9C0j-090 zvzwQ#EEyAXIb!I3%gfdRA=TQekfH9WQFz)WEHm6VYLfcrO_2M-MM|$>JA9S zyk_-rK3^ggt?>W4H4+fIwtC%4yp8Hrtx-%9QoUiN2jmIJn^u(&tj3R+=OJ%dbpd$| z^0w7~yJ#PA)lYk;+gcxxcOd_F^i+{jy<_DDgmQk@S}LSc8^yNPLf*4l0&>*8 zc!SOA5aNIR@uAf%CGz#hht?>I`ugKTE3Qt~bOJfk9P&deF(6-ITOV1qEYauVJ`-fE z)ew-MAs<^ULb{{Wb!ETRA!I|8i&S)_`Ki^-ax8@ILw;uUaz1B4XcqUG)z5Mfgyuh= zSvB>tgv%hbzdpA@cltv6>vOA)r3x|h^kl$l3dm-ZaKP$dsY6UO?l^y8Em$G*A))mp zzOb~FB>pV-OKVJsT>Cl(slK!(S?Hb~&6Wo(<1Ud;N1KD6LVCh6m7$w|b zb+fF2&@-uxmUg#HwU*^uD~Dx&N6uh}tn_U)IP&jofOfAG{YaZ+5dTiAt3gP& zwk95LBOZXay{#sWSqq_Sv>&YXLgc#oGqEpzw8n%~ik0^Fg#2U`HPY7Qw^Y=_f3fOV z)R#(QRx^wGnrY1HU{T+~j9Gmw>WiLVtq~UbGKk9an>85_x~|-0#oh03oxU&{w^9N^ z-#7hkWd(%3Ir_uO4+woZ^`})55c&dZ!m14jy`%V-)f^D|25Pg_84&tX>2Iq)AoNAp zq!s;uU&3jK`Ja^#5c<06A1f^&^gY_YR!%_Zt1-84guXSKVmAeZzQCGl zcLapK2#dD+0z%(gO|yrDte$fJ4j7vu+t_^%Qkmtkd;s>cZjT7z`!*26o(u@JvuPVm zluE`>Ei5}BAk-SRoe~h*7ml3~5NdhX&It(Z9na1W2(?DoE(!>>Mx0$15ZYhU?b?7) z|CwPo1cX{+JG&(y)Ee>jnt)InZf_3+gnIZ6w)PO!)PI7oqn#?mf0CYHrwOUowkgE@ z7_?Y|-6v#)b`EB+^C3IiNsm%K^;$e)7D8s)1uVNkPJ_&{Lr=A@$n1kjq3&-BYqWmqW@Sd)os-{Cj#S_Ie@J z;+JRXte0Ysu+SY&YM&H4x0Uko@8IoYmp&_Np=Lz;*&RaawHvXm+p(?v>@gwo+|&TU zx1nt^AN2%kww=MEo+0r9FdG2ibW-Hbgy(m=_@j+jT4-L0%Km z!15F1ZO9>Z6HDj<9C45gyM-kQ(g!)zZeuwd@;T%%yMtv3WF6#iyNjh1@*O18Uc*9b z!;eCau=`k^gKUD#vj=KUo5Ha*UT8>@DG5|SJNR1HO-GyW!pX2S8t@1h1ZsSyAh&cr@C)yn>laMonbZ?c< z0()qyd``AUIaTN)S)P;aaTX6k4v8OR%le?G~17$W=nxx60=XyML>EmfC}y>SUz49;uevLqaM-XFzTiG8RZhWB6Hi zLOZo#g?1rgXtr~=~CJFQE~?=$3_ztHY}OG*b~X#6R(jnz`#h0y)*Lc5gZBgiwz=K?#e zTgH41p%PwTr~XIE&k(BVh4um=mD=BscEns{FBP&v$lH*MZ8Qb`dZwu!UTk-Xn3Y;| z5ylO~TyEF)P|p6E`B&O2g;a+;#87#zv>RD=fov2aGDglR5SpuBWw&gV&((Gtr%FT2 zf062HyMrYQvPnp{lt@1;wFiXsXe#GYyZs$1q5PJLdcrchlZC#eqMoqKUc*99*8YK9 zWB0MpdFK#}W7pV&Lj0VswbS2~t(uGSMB~kmGCS`*v8_mXmfK6Egf2!5?d9cmAxjy= zLaOCZ$yJf3+#Bh= zucAD&P{If7zO8b8&>oT!x(G3Jh1g_|2=V)KlRd_%)V7-JrjNEP^CNa{-0c?WVJzo_P}SdgsRLh+i?R@)c$(a&iGP_ z+Fx(j{X+cmylD>#@$VJ9X&YZ{ne&@=#vn<(_BXbE47UEZT`R=zb=`LQ*D{8B=81?| zV;2hXYx=HTD#YLVdv-a;s9L;dSFxyCd|=lJsnFbuaUPh1`!;sMI@(r+wj;~Ob}35| z%O`g9H?kJ0?)`SWlt_>I)J_oMm*-PENlIuxr82*`E$fDL_`rIyK`GMDB`rNJ&(xa)m57=evWtmmozp^tolK3V3#?BSur~1Yo zU{R?y+KEG3rW&@Bh16@;qJ(Fo7Q=SA5I>*q?ey=q-1-l8*02=ShCkblLj3alVmAx% z`_C_SC#O>N`o$g>B3IA55L^Gvo@7~t@{~Zv?dI=kTNR=;mP7uqQ%ANe&tG<-kP58{ zsi-IX&rbTUOw|Ut6{-HQlYiK<%$k!S#4mG{lO-k8g;X@QL^(MuA4BLWGRnzg8RD2J z&Qg}YIcADeBqefAnd+1Ugz}l{)C%$2Ioi>F+_GNVI!U8jmO0kRVo_~qIHfFVFWZju z)0U~iPLdG6rg2V6KtA3V*UL_Zkl`r0io6>wHr-jk;$0$ThEu>YlVv-nlqHQN-l=6d zie-DJiRCQF1IT#?r-S7x$m5WmoIaLX$a9dH&JfG_|4h|hfy{Cy0@4LZa*Us;ro&NJ zB8F1!?j#3fCXmk{Qtj&uaLn?rx=*Wna3z6G8aUaU3 zj#Hh47`nzkz-eGP3-SZXbAZz#^NCUU9N=_t%q55!6ES&XRMQpOb&$Q9mJUt?+haVIX)x!^ti0a{oL`3!Q!y=-3_`Hay9-g&@P!B&UBC3ZU zvxQI(KURsUdU#GmR6q1Z^+R7&KlDZQ!{a0QsD9{+>UAeXVpOm5MfEygRIiK3G!|BRQ)hZb3v;uk%IqI$u<;^F{SBUsNCSMfEXXR3D4TG}XsWij+t7v6CaBj+B#~ zB+(BmLvNuUdgz%aJ1IgcLTe%MLehoEBaURDlP#ntq-vV$8N`y0u)FX2RQqT^Ia)RB9-)6Jre+@;O{i#l=(oDmjv^PRYHG5$o( zO&2)H0ih%JA}32qrg+=9jxs$=7a=yY@ zDx@OxC(4|Oe6Dbcg~*)gYl$C~+=i%2nokA)?yPyp(M7^$cdWFcE77H1W`9$`6nKQzowqE8;u&A0| z=NJ>Roz>Q_a}rrpy{>bzgj9r<|2h`rV1@q1XCjtUL2Ss2&X6Hx86*+%s*`O>xdXBn zq|=FWq&x~a81jab=1O@Pk_~y=$?>F6c@{zb<213nhnRC9?>fbCGUiLj6_5{{UY1`V z*FZjU(x+35e}ALTDH0-kINhUO>y$_d{fksLBGp=_j3xeZ>?km)68-ffs@EyK3x*jevXvD^!xV`sfn$MPD-Y;YP_`Z;EU)6AmUd85=f2iJv7U z5wZd0`O!&bNr8+&Mx88{Ign8yExXB7he0+$esV@xj)hPyes+@fkTJOt$zyrymndx# zseX29lV!}gh@sx{i!;Qs3=)NJuEw1FJ!Qdl8L*fwgo0BTU@0pt-b9H)Kp7Pn`G>e#O@n-%G zLRwkq&3rm`HaQ(EA0THc;keVqvYxj-?(_=r@7(_G^vf8HR*FbO&c8d(-c)A!B%QYY zrxPbce#wWU7Y<9X>qDti6+3fVP*bq9p|91LW zW_ED#b(p;ndcb1LLtCqYVx)^S}5iE@)z zXdTz{gru@O|7Dbjnc}9iyaAz@DQ=b&QJ!P5->15TLj1Z z$IB7GcAHt<J`xyX`D$gmm2XLgX86)IN?o#-iSEbKU5D~;$A-%N{hqxX}oq25fJ?#8pIH`AuO$t>!vv+3@FfY4|&!z~O5y>+&o+Yk_X z8!Fyy3kbdGw7okN5PGj^2X`_c^zPG+Zd|G?A#I(Giv%|%AoSMRPHt8}=&iGz-TZ*i zdse%+bpfHb&Jx|0fY4iKGu^I$(ED1ux&r~B_pD~Q+Wvld=sl|>H$EVA?%vH!4G6te zwY!@W5PFAd54SiV^qy6+TPvhmTa5ih`RwVgVJU>r5x18+CS-+n1%$o>PjS;`Q+ZZs z`+O9ooq?Er+#;4kAeE5WZZpd%kb5C>+;NtRAtG`HknDJvipv)D~KM9M6D z%~Xz5vjdzpqD62MXNofLyU3-Y{{a50zV2@1dRVCJU+7o)gD6%z%`;Z9@E+Ri!&B zq&jpWQc)XLy5lToLa0Aix>_b}t2%Tk$5go?mRmTc%8g@rlw)pm6IkBnm>b<>mTx$w z+D&DdM&yittJ^Fk^epFdtJ})b!};9m zcCdWSF|}?N%fB2`>-MtDyc**-_SbE0Kg(f|WFhNWmO%D}-0qIBTmzXSWSr$5j=95~ zWO;^T?r=l%s1|;`>fG!sk_v4#Vh%+AL2g^$k+SYw;7t4nb+SYw;FUv6J)9CiIL@krsYIN7LBtU3i-0zOC zq(i7)_q*dl{C0l8?K^hM{_}u4ASKj?v6ss8fV-Y07x_?m9&krlF63=J=#I0L^R^yz zwfQ3FkUB1!+z^X8E}Gmp7PT)PauZn8zIez@7UGxrVR!7fEzA6{J0T^q-ye3h9JzJ1 z-ye3dMxQ9#N7d^I*I-fgdcuuoQGNbNH<3m4`6t~J7S*4ha?@B;e}2l% zWKqXOtDDWDj*C_|mt_W8gZ9_cZazy2x6jjVA!?kjMJ?D0?91Ni`;d!@1!R=>R!7(qm>selcP~F?z5tff3bPTq;<19aOJ}^1 z=U3c>T#|aN9daJzH8)R4h1LV18PV%*i4cEOf5R=4sp40B5~Y2Jm^a)8PBn;BRIfMO z7M7nP(#P@-gzEK%8(JjGV_zq?^@bbAG7~~EZ@Q^0vm;W-awMmE(=BCL#4%lN6U%uV z)8)3Zlyb~l?i!YxBQnNvFQ325C4yw$+GQo*~9vNkX4%dZIpHe^67EM zSZJM-D#&|oR^FDW-gk3^_%prt-8>l+Qlr-U?ot*tYQ66kvCKjV>26Q2Tf%Y}gsy6P z-Ex-GIOYSlhNXyOK5$pET+cBdx{WLkaLk8p3(M;q^O4)e@-fGJGIm@6L*xQ@UN-jYU>ksQi#6?KXIKUv@L&ajZfToAuB@f;Y~t&Z`xK43%zYP zT}Unqy=}OokbIU4P@aQujowS3X*LbEyMQ#Ztt#WA0{aYFo-|IAH0eapW3nVTUc(lbAEvshHm{LEdz zatd;$BkprIkL5B59dVz#1uUvZ4Y)-tsz(jDr9%AjeBqX#LFK8}YLQPj_WKuZ9ZMtR z0o3$Mw?T-%Prq`J1b;o|Q;4A><^Lyd&6fAW{g{`KifZADdVj;0cM$V9w(iTo*4vsk z0(m+ZGwm6qz`Wtst$f{5xw)M4$85bf~lBfF;-?)=9pO}3SLv8qttDQ;vVmRi| zh(t??Iu-J*$j1@lk8B&<1R?(3+2AH|KB~+c++@y&R#v1kZ*bFu_+{SUW(4w~9<{;E z*(#rn?gAFIFE+ZlENXvkbn{u%e&6UWWl?*1qg%+LdcsDxNJ^C2ryJG&GFAKdqS_}S z(^UIxbgM)@{;%5%xwR~ppbe?l4Y@0&MDCCexy`(F`d;s6w9l|RB*b4s@_TojMSa0H z;-)X%@>m{mvjXxv@)>bUh4e&Sjjc~Y{_Cz}xq;;ew~d9avZtPjCt_}&kX71kh%q3e zZpK-(tyNkR%g=5L%L|b05i{ma2$4NuH^^^p=xkBK&>F;~LN>V$%f}Fk+2ke&!CDQF zgCXOQmj4+t5Au6N{)SLrjmWfjFOuI1`C{FGdv!?lhe|bVpZ_B{kdwEF$%UK=`7@G= zW%i{6F;oj*Dj`>GA$5_MTG>9LZ&ZnxoO47QihCf9 zkbm3~Ar%_c{b3=E=Tc0CM)jidXkOxZQtpR5i5Sgm5VAsRg**?L;?SlOKh26j2H5wOQ+CSX1@d-Rr#Euh&jU zwTIU&MLU7(wTIWwaz;ePSyWB;@UpLvH7!ESEaa2yb+M?r@97N)@k_XuHzdTb``%tz z3FYI*qlFlKHtIFktKBM}gS|Q#6IzZvNN3oCy#^K< zY3WPbgS{pe8fj%ri_9ldi-WzYtEujOnGcB^%eNutOl;kk#~|~!kQX2)K{C81k&pjY z!=YY_5ZOL?h&j~j64IlooDcWwbjS%CZKs z6x%w&>k{IZ=Lm0&5Luq{M9cuEQaR7_Mp#tN^SlWbmGeB+KBk`v4T!%U(@&+uPh~8l zdimv<=f#mkRr}8ey(A&>d_m8J=Xt3zM!dgqG0J?T*DPdJh}JPHfn<4Yff#zKc$C*0 z5SmpU?M(!PR$Vy8OS(pEUF1y98MD1SA*-~Tu)5&2$a%h3ND_E{I^SzzQO{3L^afbe z^V5^PtaK)5Tt`5dZmUzSk|pe|~zV*Du6>etM=iCSxMc zPnUYyb+oNYAylukyu^Ue^V4&@Y!>zWw7@H5QO{4$_4)%s&ri?u5|_)IDTbb(p6?Y0 zgr1)kdR;8)`RN5-$@Q}C>iOw~UUxv~`Du}tTuw3m^V3VcY$5*h(@VW#7WMqJ*lT4` z&rdJ+vEsy7dBz@i-_h%4ITLd9ERsH!#;@gbhikn7mM0+8SFiPkSf=2KD$NSY zyipbdLU&inya|?Gq@rzI=V>>{x=*5nw5{trgXI#=XSo;0atr6P+)HFB+z_Shvpc?X z_L5nyfE*wsjpYPLBBb2QU|9m$T}U>|W4x^jZvjg;Z>z$~XE_UR`%t}Z@CsPUAyls$ zykeHu(PHmnUsQUfET2L?5>mzTD{}rB_hhQPT9$2)&wbbzRbB&2s0y=X$cgyli5`_0}M z%UfLco4rXEwLLGySJ1@eRp$*SIBY;*ZmH!gym$E&_r*k z^U7EXAunQI)Oj^3A4n&p-m7D&hrBJMk%itg@Gyqo={2*w@|irE?({lXK7>%q-|2Nr z(c(Xj68GL$c%xDxbL^E~bQQJpDyKDS6P#y^&Jk z&OcK9h?piXu3E~gkl!H>d!;OEIp$F>_a+%L1o;Osk9!R)Q*V^g;tjEE$MS@iT0^Po zwG_y-vv76i)d{K4j(`}Dr@Z`|DaN0VJ?#}p2`xel?d7MvB9==aw3nauN?C4##37$& zymFR$$j(A)rHB^W6Y{Lr6p*=)=e+?b+I>hx^VN1Q;TBm_l1#+3ds#xNMa(gf7ro+J zWlRfV7NKvv>{ZoD>1XNi3T~6~Bg-pZ?(I@u*%+mrgnVB0((9$Xggf^8V?6Bis)W>Q zQ?TDpL(Cgq7spuDICnw1yw*D@)hcZ^qy+M|7r%m}UON_Y6J(7yaF3MJArCF#}%NBU0XhP~E@q(we1w2YCyr zzVwnGBdOGWgS-nF^im#|G1sDA{gAJ{QX%+C9r6`qoj1ra{~{F~1?#=K7MV}@Cfw6Q z%m%OH37OBXkRKo$y+I)>v^2=CkZ(QXNs3vaoxn2WWe8cNT?Cm#%y-@h%QA>@HjV_Z z;3-PAO1l-Z9c0WKXL;|(C~arRuU=-W%$a02$ZuZD)3QA0<9#Gr*?!y`6|zcu2>I-f zm_NM9XJpPVK@NvZczw^3__O!TUdnSahK}Xq5wqFr6;d7A9&d2bRlqjkewO!<54F!W z;q@#o&N39UZFqzw0YWj`hR0bxLn?|1g(q2tIiFBC^gM07I`jv}#DpD|ZENJVV!{b5 z@es;K4=1rCaf}{DCiv@_b_w2`p)oTyB7Z_?^z-FS2p!82G1b}JP^nCHHjl`(nfoXw(UR^jjh7B#a9hx1re3B%!~ zEUBpbCD?j6T*z`Hghs7!xR~X-#Hpgi!r@YulerDU;c}Luh}5vuLP|u=bu14>h4l`IKXJ4>z-Tx5z%W zN4S+`7KHlq9^np_G>%CQcd;DHG0EXxmNOvz*!rH~ewIriLqgWG(EA&|L-q=fu+aM( z{|XsrDMu>(9IP`Co@A+mgoT7&l*djJg!X$%*kO4YLOnbsoWSxSgpQqk!bvPc5USTc z;Z!M+y|Zt)kYhG;s(r)7EY__u)xO~}mfLVNQO^5?t5_N#l=FV!Iu`YYTWYw0MZMvc z8g3SXFZeM2&`f6ka4UC(guaMy=b#xyP9uQI;T8r{f z`=o`}vkdVFkQN?gQP&Q0!s9IJ+F?#udr9_iHImE?hlEsz42%ghw#*GXLMlSLLf#gV zz%l6%+WLXvB$gu~w08~+r?SwU|GiL)^l&;0-T6-wk|ks~MvVXmh4Wa{7jd;Pm~(j4h^@wOf{|0Xr4f4*m>cE4k;OE=fhE+ zBg11XG;=r+a#XnV6&Z5^Vkp(o;mp^hoB^S^%`xFVA*-~DA#`8;nDF{eimBIXAn(q? z`^Mo3A;VE>44)qky-qRy3~hec5#pb}=7&>dOi0a^=ZDi-)NFZvI4h9PDJb)C;d~+f zd&0+uYgyF$y(fgbgj8s&P{O51bz*pssHsZI_j3#rsT zN6a$FDdAcn_1ga<>;B{N8vj3lpJ?3Ci2K~<=XDbcAwIDo452ZEM%W0MW}!_cG%{sr znPz24o6rb-iG`UE3vJpoh7cN!w2&=i3ytsVx~}(i&OLfO{NcIx_4_*4`F-whAxj|B zg6$-~LY{zR1>@Re|Lj?T9)nyJoJ_(Xt07kh>x7hOCqmXjvV%hub3SAP=ja?wL$(t%=N+EcSXAbpF;M@!BvRh0LeEH zUC0p0j}SiR^MYEtNF~;m14K-`6!ELXNs!sWM3Q%K2IL~u>|ip*;;rT2G<_KBq{rX0EFi%LC zwjS?|^0t=;n>u*TC0b!Do^v>wOW%4fRaz7BX+%Ev2czHjWd)=nm?lJi@AOs31HoY- zOSD#`dJD2R7`K6^TB3ac;lEn+V6gWiDL+F7ArA#(KbAS~gz#q%9tx%lkz3`jh^YyV zN(n5(vsk>&OM}s$@KkF9>I&!5U@D2a3c55nl|)_hTpG-l;$81u8q6nA*L#-+D@b0R zFRvso3)TxM(GGb4a~1V@B-k!Q)-W&wzm*W2+_h_c9t&oWsQNq>oJykV^H?xjh|GuY z(H;xtMaYHdjmLwfLi}eBmIup8)cyD6!AcT!|9yF|jzrymUmmO{QT16KY$H+k-lnJiF(SSAvjftzpg$N%n?$f z9fw}sAAR^#u#F@QVndz|20rIG*JziJGzK$B@*oEx=GkBaNf9Iw@?0>vTc)an90O?z zHh#&aOsp+uK%Nga3z6F?|GlW^gIPT?75|#wg^(A5IYP>`Cy~!o$ckV^gycY)gPkNR z5X0};y%g-BeE2$l3u0alrfiZqzlNAH$f{t$*HS)&@ZTtE38sA`D@g{V3$i*mNHPQR6{Ia_ea}@v$tdJB#C#OY9*{9( zE73oYPlD|vheGaxbO)1vkTIu1mP0nFGoZ0*o&Uy@^AN*V0AI2oFCmpL*Z)W6L0Wc+ zQCoqZ>Y@MSto9CmjM{_v@_$J6&MrB>2I<&E)IDUff2Q-9%OQKel1;Vb`^U<*k; z%!81tAwLFtf0xn*$%p(LOxP~vb4U?nTd-9~nf5be0p!O!FuvCh7rm#KOASJ-BF+PKs?ZGA?C4mQ1u}uhR5mKY=k9@iyJA&FDJZCAJAS1yx zA!Xw3(hrb7gE@c7(oaOHVaU#43&}Z<-E+~idhA~^hX3l^{*c}EOd)GEb!T=DeJY9i z%J&|6t`NCr3?tQ^dYjD0Yga(;km9{N5YPu@s+g-#dIC}f^ihhr9&)sh=uuIhKqX{? zkT{Z8DJDiQCfQ6eF?xj*ZSN&GDo3il^yr)p}ef87` z$%f!nh6v&JCW87@DdPRO8xW)GJtTZRycrVGbN}Y0`^UzHUPz*jjZM8=NU6q;d+$Ih zOCJ;>kCwUEdg?zspAwB9E%UM*eX5Wu?HIJH1mfzILTa>0kcAMYcWHYfRh4!#WEtcD zePnkjOCakYAA;^h( zFQvK;F?EoW^vQe6d=^7qg`BKs#d0YNv_Sax(@)WJNIrn@>sqJic_iB)pNp6RDPnGH z64ES1(;t$fldAWS97%GzK0TA!N53a9>zoDWpo<42gl9qjw4Mx5{&MwlB}ef2QJGJxPe%D&vsqJiS$j z|CC_5-cGVx4fd{y7$Ln=ifFruRO$LK#awx;{G!4I`Y6c_5PrY=0zD8X`*|Pa!=HS( zP>&-CL-=vjg*qeI{{p=6jGQy{1QG^0Tu3rW5>oNK-V{BBfZ@5gKCACFzCrwAzvWFnt!X#1slsuYoGGV-}p&xnuA6DijUY%NTly zzCb>Q@R%l&TBNEFF)K-q+=_R}F%}E;RuVPt3-xxA6-dS3h+n90lp=cmLF9A4-YaBn zR4ZbZLMn92<}FHAXR#ikP@v6(gAr;Z*q7( zHQEo5Zb-G>A*4zhh4e#e^jeq4lxX}|eH-LqeR5dJzO}e}kcSmeFDE$=5(jxq9~M%j z9R)c6Qm-Qu{H-0U-kyF^$r$yn^sd6+m2OZmWBD-&f5ZA|C1X^(d{Mpe%q}s!#~SsV z19+YNcekI_^GHwuAQ>9&joNt4y);9_%3!F}Oire&Vk_@_2+@|-DsH=`|>w_ffs^i=G2+1ADnXi!V z=-R=&tg^ro2wx%J(PK%PAdAQ2d`H(wHbVY9flEBeFoYjtx9f={fu-_Vce_4;gh6=B z8hs+k5frmVpG=Yh;r9#H>KP=HApCy8T0M*8B8quW&moyfG4JX5Bt;Ou2kFoYNfto( z9;8DrB~fe3I=zDALB!mReqN{7lJM1rKTon=uP1pDG5nZqz1~Ff9E6wkzP^&=RR}NZ zeZ7t3UC8-|;TnrO0QeJ1-{_q(M&vVym~Zr6A@a!K56HLrV1$gBg*!}o{o!&f`0qjM zkS%&6$$XUM2x%r+1d%ZVpxfGK54Q$!qAZqC@&bA^tJR zke((*l*LD92p=mK{iE@>>Mln1J-!jj6oRK~Ag3T7Gn7M;1vwjHhpL6p(N`|! zVyHt%f7Bg_xd1V4Xk$c7rjXndd0AzF1&Fx?5+BMZSq7Oaq>$tl$b2EiBpsfVlk`9q ziI_?ua%);5q~#=0mi7x`RzMC76;Bi*<`2J@uM#WA5o61BfLHk3@F_7}&7CX%Ql&+(x& z5_JSSK9o*!6l%!#mE%L1Bo{#V-O2HxY?7NG-=h^1Lb)WR5b;&$Pyva$?sr_Mh-4{Z z`2PL4P&tV@HaF#u_Px#Y#|oO6i7T|Vkn+uI^<9xNhG&G_)*g-p$R1SL3pp8 z5=td`oMKK5O(tohm{UWUBzzCcNBOi+mP!TTqkLK@mqhJhQ$zVAY7d(lDk9m8oR2~c zPY;!n{0$i|q>{v~M{hyS2-T9D0!bCpK%(~4XNHT@%%r`C6E~Gz3U2RSa zb&{xAmlo=xeAKK<3-yv*jndCX>61bOBsW9&Huq8onWdXadO=2!DQlPG};@4o}iZ z#yla{u{oi15`MM$1=MFwC{v0U3%=#f3AIE>3u0~wWla_}6fvJb3PY_zYQ**4pCI!> z#p$x`2FiK^`ILkzg_H&OvDTl6xhGUh!t2bBMehmKlkjs8?Pe~GB&wzNgqo#@()SWE z-9q|f)Sa5LP%nwPQ&SciAW?T}%0fdV>P}5rXoN)FsVNI-=kr$d$EZ6sWuX9xx>HjY ziX%~XYRWi6rWNQ&}jPMBU9P3#AB|A2=9&cmP^j7D}TSb$@3; zC|yWNAQ>@-BW6KpD#fTfKINfo86$e^IK-5PqA%d>lE)-PkOxCngggwX39$?PQCeH3_NFb|NMhF&&|dOrEMn3p|OlW61hY zt&l1$5yH3S4I%v!8FL!sUc`J5Y9hH7a=(zIOJ&S_$Z|+mNWV-<4W;@r)JU=d@-kw+ z4E2$;L2ft#=YyftsWR1v5dL-PO`)XAr9AO#l=dEEbEy0ZDFcZ49P(YLk>nQ$e`4zU z&>+d~4LBD>%$87cmQ1w|=Bfyhz5>BW4lg8l#isEyy#FnMU*$**AMjOfVkOLrh8^c1Xv<;9$AjL-VLYeax$au&-My`-5joa9 z+6bk(*OLVq{jyR7H>n5c7~xPErAR7*b>Ok~~cEu#xqkO!XwB9x=7X$_QzO zEHk=DUPR2BkVg!CiA=Q?(gCS6(nz`>UqT);a!I~|d<&^J8imwo{g9s^%Z>bMo==VT z6XXxblSbennRE32VGX+-$Js`zkQ&W~@R|9v(Lpg>ViEI91l6} zNL*tzs)gX5I3$QvO@{WcNafAm7mN%cOSI{TIS?@~7+Eq#n*%u%vclLXq()l`;d_u~ zW0X?82ssKdFBy5YvRzzw%u1tF$P#TW<+IADj1ZpcRf8>+sXj*xFa0$meVLTNvr=9+ zHa;RH4B_>8!|1EyQl%XN;eFU@)IKUjwf!w)qmUXc4KXJm)muj4V?0J)HR4xbRvX1a z{9}OCMxBrvZ4OfLZE&@bRWD1w55m7p+GaE?mt{Q)nFM*;=z2m*Gs(L~)00xVNY)q= z8>IY1@}5yavfFbqpLIsuQ!*w<^1e||l0>q>=y+Pjq>+4Rq&_3%TF4aC=Od#+NR4(I zQ_WzSHkCA+Bj6N&n4M+`QzA^OYqgiUwO`)l{LWZOvd0$rJpXq_n~H((vztDngJiD(8PjKUkp!ABqYlGy zi_t@3K$=n3_r?H80;B~o{l*Z<@sL*$({GHDOos5XwiwY(ycK1EsSth+yTynj$)%V9 z!y+l9m;ob!#KnCpekA;ZkwkJdgr7nEU$W*h^@dQ&Lu45a>1b=JCoPs0#{UE;?wIZf0a41soJ{&gc zNsfo`J{&fhNYvZ7zZ)w_)Z4hf8*LYcDcNje+;~{+9cNhaC z=@35dBgPQP^$bvzM1OYNV1}O7nTtNGG`t!t>c_WRjFXcs@IgY?5E8K7SjzBx<|( z+bAHZLn=NG|1pY4o`vvv_>WOeqUNe*R+6Z>s+n~p>V1$Xvw=jt4-#cIlc@Q;o7qC5 z=JRf5JBgalyPKUPYCi97c1!W*bF|qTA^Z)9Xj5?-s_H zqa^CPg>h!|OT4Tl0rlO&I5U>y%a3;x-!0tFOi=Qfl8GemqCWiFhWnYBB;R?GM-u(3 zN>xhohZj>vV!ojASxIubC!HkMc+y95nPNmN}oV-iWieka)l>3 zB=>t#MDnU9)g)hd(nONI+wL@`?IdNM^pNcIQVo;1FRD7nz9L(Z;zV+P_mb+d29j-FOe=|#plaAflIF=E$w^*5(XYxnU+M`XDez9?7qsl#=YXQq{SRVE;A^Ue zX(ZD<$s(EONdd_to>Y*$?nwj5=bp5Z{N_m)iTS!JeUM~=C(&=pI$!JwBbn*R1d_Ww zNhhiGB!{HglOmE%PpV0_d(uP_dPCK)o#cO>^pG5Ql3G`XNj{&bB=#-Yifx%n5=c(* z@|j3-p(mLn*Ljjha=#~~BrkYUNAjsBD@k^G(n;d9s&@5}9Oua>$z`7Ct7R+ZdXhx) zuqSCGZ+MbL@|`CIB!M?o=@lf0dD1{~x+kq9Q$6V-ndiwM$#PGk+hm>Bc*02fJ()l< z<}Fp9bdn=H$szgW88w1MB;jY3RFh6-EFGUvVC z#xvBAu$e+~0HhZA9B8g2NrXHJNiYLzWUA94FF_79i%G74tcDzBc9G13dj--Y+c7= zs|qpS%KdCSuMtGet~UfFWidzEf+?#9a zAIsk0&*kzjM&4-_3h}=fInS&W;(sx6p4mpCz8HD8IZC3w7+GRwe8O|C3Cw**u7~r@ zd@0(!5dNDK^UZpahM5{klMsLXnQ!(Ak#pmA%$E74*2Qz~k9q+4JP0W>nGipw%6JRP)My5?RCU_52-NgBV-h^$ZU&{eeS?LFtZ~<4uLE-2T0yRKKxo! zrCIQq=y~lk$gzm2G&hp`0=W>f#H{VzpIh5pKvy$XQ$W_Rv)*Rpxg{PDux15gq^rrTO$j7@Uwam1H_}lw3Ga*9wmD^=z ztrYDX^Pdywiy$a7{9$r4Blq{(cKh*=GJ-fSUhL=2Y~%#0qH zPYa|2vck+0Ql)i5_3#PF?hrI{yVv|Nab`m8jIg!tq2idjmjZb8iR$#@FT zO#FuD?8mG!lZEt0l_Q4#Hq$CI=U*|es%>!W&s?}dCiO2K{39JT_f{(-HSO8!t?Rv7>aqri&G1o%)S6h9V1=)hJ zc+-oS2l;sySqvG5yyeBzLiq25`EvfnvVT^4G0PFd`^OjctCwwFjQaIDU!F%Q{yQvh zdoimZ?1Tszqn@#U$BS9bV^Eea8>n<&zJl;~=-%~G4ML*sjFg=aen+F-i`jQAy_03; z$=MR4dUcIiK%#nejaf{hdUdT?PNI5styxW?di6cCjzsn9duAhv>eUXjnMCz!huKP^ zdUc)IPNI5sow<=j_3C=Fn?&{Mdb3Z6+%EP)Z@h1gL`Vqoff?V+w;liel#OPhkQ(hE z^iLQu8_fwMN0EGNrjqb?Cl5nRmzgfa-v&Q3Gllefz51D%MWTB3Gc%V&_1NcTK8fnF z&&?td)nnaeDT(T_ZnKg^^~M)wEs5%lFU$rK)vI5cO(d#UzcgD&)F^*twvnh&{>tnm zQR_yJ*+rt(jUKa?M6DZ}%mEU$Zfr7#Nz}UWwK+tz-B%={V`)Pd-?kFjTuLx z){SpWMxxe@UNeD2tsA{&vJm;Dnqx3to6YO9;NL3dw*Bn!`f;asSyIB~j!4vl;kSwo8rsRx^%7jr&%Uk*IOsW+srRao=Vp zlc;eYGE+#@xDT0WBx>A$G1E!Zxc_2KB~j!4tC>xr#{E|_k3^08Z)O3B8u#DKViGm( z!)7^&8uwwdnnaEJ?`9o|8u#DLMiMpd+s$SYHSXKZRuVPtJIrLuZ86qkLi!3g20_Os&pcn$rT8M2av z)M!^A)qRLDtcelAf5XzUT1faeLmq>;R^0bIpBnKMk*6VHi$%!ukOQoV5wa3;pp`|E zi?ZH;#9MVjs~EaS*UMtG!>=xe77c5R+(?Z;`$66og+ZIKt{7d6Qy}v=V>dG5#8Ul$9JIe;}WutTZWs zZlvP-)1$3)AyryGg#Ui$(N?LDn!uJQj3?xOR)vi5j>G?_>O59`sm7Br+mR2SnZEo( zlB`mV+3P(SbBrf8$+4asjbBCQ`Hc63|4urW37#ZSs^dK2zsb($;qjjE@5zisD^fgB zzuSJ2C+eFsCtLNRrTtMVpNW?CBk$GzD3#BtRvd}SC)G-bh~dB6e!7>l>WwozQN3}d zC#pBnJW;(d$rIHZXL+J}<7`h2atP!KtCge-k_5@JI;Dth=Qzk!)_@TIOGj5(gAu~#>ebc|iR!~^ ztdR)evv<0s4eq*i=U4$DHCh!)A3hl8x)zI&laXqMRZH?PV)%L7Osj?D1<3bEm1`ya z%yY&H3E^K!z1~VC`H^^olEg#!s+DKOZRKTE zX~&byvYJUQBAG2_RIyeS?;`R7ly#HUD`IN28HnMnxY@F{$vWQ($wbT?D~F^Mas}iT ztA*qs{C3Pt$gNiVkW5vLn42NDTLmOM<{w;NookhnjK}puE_YffzsOX}k&3tCE~}5E z5mJnN=2=a@%9s}+Rghv!`;AMLwi?m|xyK4f@mA6KYTFsR1~L3B$rt|KF+V5rs*x4DQ|7z^!t2~4)%n z_^eeU;Vpg5O4?0IDW!VDN+o%Yq}9qK`53}~ z>-tTrLQ3E(2*1*`+Nu>&rTyheV0WH#m3BZU)>NcgZ52!LX7Afpt&kcm12Mdx-?rLG zt|EEIniwr>coWIHRuRcOl6I?!20vuh4@$JHduX>>O`dCzYe|uuM5k3&Vt+v z*=UUj@$>oETYC>ePdR0J>-?!Tk>qesGD*@s$s@VWlTwmmPwGgPd9sqE)ss$= zPd(`)`PCCt7=LTW1V6-kE*Qa2)m~=Ip(LMKqasyVAQ|!)Vm`N`_vS4v3!F(YpIdPx z>OJ~y%Od#`-woqmhwHZDh13KtMXCnm(`_Y^||uaN#I^_%0pR_<6aTQuJGcI4A*6_dOKc^|Ub zs*I3NAm3UILgZG!bN<#^86jUF<~yrfiucW#@2y^v?FYzfJKtMFB$pn7_qwp1es8gT zWUpQg;lH2KZzW05`0p=&kDLdri6lL!A&(ib(n+>JenrfWR;G{|?NJd^Bct1z>>W|g{#iSxt3{w4M4N=VbkbQ-W z$`~=G24v9E_Z2M_!uxQml_W(gKUUKYK+HC)Tu7C6Ir8Cqv~AW#Atl-j2#*=ETH<&< zCEDGdOxRD3>3xs{5i^-&Dao%^fe?JF9Ku)HVXKK^UL*P4>LvMzWV@BHzs&h-2w$;x zSg9mxU-^gCBE-LU{FgOEqV64Qc4Cm{lst3Atiw>Dl65tD5f96uk)X7$CLaC;r9nm zx06WJ*AmXKD@peGME1rR_ArS9`5^&E%=ReBA&_lC0!-E?8S)5Pai$$dat36%5GKW2 zjnnMJ2ssD!nPg9hkVd3B+fIv+6_9i7ZXxpfY<$*TXtOXc-TyvYhMi6lhvz`}Je*?Z zk*F`WO|c7vlxdl$A^$GK6gzN$Y{lgenJSKCrY9CjA%xGCDRw+b{tv3G3KBIlr`Xj} zH2%(-Ow}Sq^hOKXHN}oQke6PgRU+p$$R&22kols9e6~!pJB5@5)bC_Xv%7>~yTChr zd@QEfy)s7R%;j=>FhbTNpDS!FUbIxid_Q>^ zF=|DcZ8u4YQY+hRdyqt}Y&Y3S{}W}!sFf|>&XE$O*0FrMm15Modb2%3qSn`m*WBIRV@}u0_m5gabOb%jhw&d+Z7!RazWk9)irb z>q!oTJO(MXwFx{Qe}8eWohZcL58i8MMaU6&9-z#wloCirK73!Wz^)Zi5;z^gV;0yA zLi%I)`ur5iDz}?R`1<^ukQU14BBbJ1!|t=&NUnkKCj{=Zdt@rnV=p1qLc5P7A2Dx0 z?zhX2_cFYZ5N@l23$QFNV#}4n5zo6vDM0H1N9il!IGv@6huqWC@_Cd@S zkZM)dn0UyykcT`u2C^0MuqUTOc0!hVvK~2iCgC{1lc|V_DaO|8$#jx>Pi}^U5c7m5 z^C1UAp7LY~nQCGH`?Li^) z1KSY8x8-Jggkse7@E2|EBtC-k1ABcT&re^pV}*l05nHOD@(Q-t{a+-2tq z@wd1xyFf@uU;;*v|K>uMT_p2~QGM8Dmr~4R#PH*~F1vzcx+m2nvmyL!qRXxq;_t0K zwVNY^uOy$@tr60Q+1qW`og&9n&4jP)ZW1*UdTjkv8KcH@lU+`tX2K@BQb<|g4%COQ zHk<55AvM|}$Q77Bo9vbdnGX5dZYNoem|2j`_FzQJt&s2R;?sEPHQFk~+yfcFPoZ$B z(msJKg8X2o3aQb0A=Qu{?WEIX%s-Gu$dH|Rrp(9s64&M-zuPR0OTRZWci6Q;O0;Ce z@TW2Vw3{hL&DFo`=t(k_nuj~>EFmS@xk$y=y1(sFic$0VA3NnNnM%#)D5sG`&EDOe z#It3Ln!S5C+GHtejs={&bEK&Gytk8oE|(H*8cJV_c8ztC&*M_1&4hdkiE}2VOPLSp zf$ZlLkW@pygY54VlRO6b5fXH&NuGxM3JE#Q=gZR7wqrXZBx>7XPWJ^eMr}I>I^`Ei zQQOWzPOFd-?N#I)Re}}SNzULgCE5l^3?#wH5mKdn4bdToIYlHtkR0x`l8izQK+KU& z#uS;)*st(^kfWVuA@cXB`L(ZP$GS+yFvN^U%&|^7$&rv$$Z<|3Nh;)gNQz@!EK{XJ zvLGirrI}K$f?NkV#fiRzOO>`Ce$De%$mvdo5Zqfw%sfb%(@HV-LilqsXE~iD%OLy+ z?QBQCROa&v#Y}b*gp_Ct@GIMV4L`?8Az6=@g~<6Fr+`v@4dGwmJJ*T5Oy=_|qzWjV0J$N=8a-O42m8~!#Jm+*LU5LMGr8~V8a~NWtM5=UWgyam8^PRY9GM_6+E^rb_ zZY8;v^av>lJPF}f za;|m;DCV$zWlXj+M53+$XFH=L&rv?tIMJ8$R+I(aqol#-}l4V&du3z1hL29RpDgG}(Zc1&Os zjs|xT7sAiz{g|U5BZ#@lOO*=YSA_CCxd_5%!p%y?s&>tB+C}O9-GMnyr;z>_Kc-8@ zh_>^5<~Sn}vim(a%XX5k^xM7<9lA+qfuq`KW{6jBzr4t3`9 z`F5w79DK)S;w#c-036<>5o$5Rpe|WQR7wQ^kvKbQR8)&lX{I5HD2?aY$15} z4XqfD((iUQUMpkN2$ncQLi`o!9w#t^$JA(zNX6I8dz>_qRV4GBY9W5AQm0AC5^V~8 zMd);tRqBk8v?CS2<6PzhuH!i`(KbT(ef$NEMbZm7AF1whw3#wSJ&mx)NhVQGM=W+y zNz~I3l}@@4|LKS-H8aPY`ip#jaET}9dr~J-)o4+8cQOm5FL8QEcA!4jLaLqgT-h5j zU*k9mQtNCaaUr)u>YQPcBOoP^ddIq6raBRFKcvA)BsmYl%X->rCAk)|1oDj2asy9Q zrQJo+=%m~z^H~nzXZ`=@)RL@(Jcd;N=X8;*h47r8bqe!js;?l=L7sD3gy73vkX4W- zCux=}Ys@#e(}y#_6;6f}?>UDTopK>dGz&4Sk?KXKi&7<#yzC5;oD8|}SeyYlNwa0n zXG7K@)hZ{GBm?p(q{Yb>vP8QU(gS(TX(hP}@;#*0VK?!7mT31uwn5%PbJqX_h*E?-OP z51a-{buF%D91Yp%bVkJRz30bH7sc@PIR!BvJH-WlS!Y1HoBa_ zPOA{VKHW|`#i&%>PV{ZNrux!J5#p!%%1Ql?Or}(-6<;|MZ|C{oYxC%h%Tf9!CyitT zeJCYeir5!qi>K@~u-Tq(4f<^f~%mo^y$I08*7B=6k1ENKK&iLR{&A^gG=`s6l5|KT zWQ)^BG99u4GT;o76hc-(esJ_Vcv)521CTY4AH6y^K==p_I>{nN*7+mIR%fb^{wNi* z%^49=qP>Vz-H7?csk~FR;vJIT9IZ&oH+(n|l`*(~B*)`{6NX0Ix&V}$d^!@tW z{2zJfKk_PM*U~@oq)@c9KgMrYR|)T*{usYq+4oBE+ohLD@!OSopA^4cLnMB?@)yb& zzg>e5aH-OMM15*ROZCN4cI%U}k2{gXf;@qkI5&^vWXLO!pj$1ZKSuSB;SQ6i{xRIl zN?E$_Y>_lP;jlW!qgA>%yx4YD0_h$s7gkL?d~m?wuo z_PQ6_&n{v@j@(5KfgHVyjE5ZK$*JGt3Ik-kC(|K(i#u)?IUg}6cybqFvLGjVvJ`S7 zzbWgUEoZ-pXejI}%=1fl#JZX#=!TUr^&x3N-O+?Hi zkV$U-5-}5^)CivAmXoOQI?t^qQDb_6+d`s7FvHzQqDJsyH@#ZstVZw>H@ZfO8n4UT zv|27D+C`|(3#iXDw~gdR$ZE*tZr)NEQwHgTT;W!e)I&ByvfOqd{ZVS%uXHy?2p{*W z-R5PobRNS;dAghSh!i!-Ic_0|nq$|wEhIb@pDnrW2#J~rH@ex6%6z!+nJ~+3d`#AP z6>2yLxyh}6T*|wU9guuiub0vd;m0;Ny9p!%klo6-G>}AZkut|kST0i?2+S2lB8S^ydGuKUgM#>wI z6y$t|+e7jh$(?Rxql_7boP(GmH}U_tlxd*>j4np&I{bs=VU%=+ga%5lBjLxez%fDZ95fiJ&D?OD%@rv zCE9t&nU79|o6;oPHQkf+W-e9Q9gw*wy~3SJQUjR}S>$GuJP#>{JmBV%tb;6tEOzrr z1|d&CD%}E-y??+xB1n~6OmYNd9ppi`mE;`AX2=q^og|l}+U+D+02xHgLv9zz(~upI z8n;i1ca-w5oBSegm%s8q?B)xRdxt$1;NG8FZN{!aKC!#VSN{>dA7mn?LgdpQlk&@Fy|09?qqJ86?@@ zN$MKe(tb}eNJc!#CfV~Rm2*DHex6hd@xLMPv|A@co&oV=fiamt{>uOcmrsxAJ`{w?dXdUUpY@N|^`Y&l|6Fhe#?&UUAt58MBmRm0L*C z1mQWq>JF2vA$iS>{aB{j1WBBMI~HyN$uAH-rmb!w$zFp}-gJ{m#zFWQ{abDt$wZRX z?o=t>w$tXue!|Nt3tWPjr%|6aSC=BrVVi~I3-QlS-*Jmc)Ofw?CUo(9pCPVsqdyfj)TX0`{ETd^na3_> zvJArea13iESwXS~YZFqUwL^H%@5we&%oh|B!+I!Yh+_6)gA}vpki0?_%Z5p8lCf-* z6OqNfget*zpg(T|t{!CUZMXW#VC>=jvAVhB6 zoe-N13-PyZheiLu+wNbPblC(Fb=A>j1w#6xHsa36C&@;kiaSe6uR zf287fc*n6qk{-lxNnoWB^4U0?@3HC#DLDk+BVdgr>iv|%Si2DU7Rh$hCy{AC@pjdS zb!-%JIMX9UzYp)^v(5R(NPGzYi>aNGBY%U|Q1t3~tVf6(uTv3|&IY9fEX3p?=6r_A;BW1i zThno8TgYfcD!%Ppzyd?O&N8375i^AqND1)okKG5kgcS)X30w5QeDng3XyH+M}t?e)_udLNbF@3i0c69jg`M*XKIcMyZ_NWPN6` z(SN1NWzoOy+N-%NNr<2G^=!hwVyqZt&!q0_QBj-GpNW$MrSR-TtiJGgk*hCUFS7))wBx5H0&zBC{oe&5QM=KshW5dQ9oAM@USWcu_lsEeD#;N z{cg6BWOTlaxtq0-e1;pJLWUvYlc|SPuz5gXA$KY=Fdq@O+wqr<%`VNuHyvX+E<^n$fPq&_AUtp5$%FF+!50csbw8CQuCT zb)NIRY$C}x2(Rf<%qreXN>9jo^K(osK!1354*|SS7`%b@dTeOEKzL;t|$BF>0l)V@(uOO!cW_ zEfk~H-bYy*#Z*wtqpXu+)Jppp>!KKSyzv<8r5LqhKh6dy=26P$aW+gbY7MVvqZIQj z#niLFPB~sLL(akMUCx?FK7>pWvXX?a;g>_6V67xSAtpyi2MOO7+yr@&Z6x^{F>{6V zkQm!#Sq-fCZ&~^=5MEXTD z*Z_&BoseN6n`0V~iXUe-vbcYEeKyCu3*r0aMwUd<@5yA6*c~b+hvZlY-{Uv329irW z=_9!v!dK)*)~dybvX(>mzOs=uMoIY$!uO|*Y(R*=wluQX-FQruHf99(9FXe&SSE=L zc@6R`YZ2lfy+6m=C}sj;-a*WBY?S0u$T~<9i{4%4TnPCL@;r+tSq9kzd4VO9yaDNh ztY9f5J&<1@%`BB9_7A*;19_1ZkW7T^eLucq!b(Z#9j}wiftsxfSdt&mGzQTK+b`*umO?|NG9YprbWw^{tdYj@;XZwQlrKHh4(=q zZ?G zzR`-96_9pTw6{#P0MZIs!%}0VEQ73rtYsTXo`ZY_d5`ssl`*eDzJqkIVUmrIVaPgW z?IUBhKz6Ud^O-D#Ajn3RL{bhp7V;6xAbAvWI^<(kO!5-sBFHDKmgFPIwU92>N-_kQ1NoE% z_LF7pyAwxDkk42e$>ER+$mcAJcR=`Y%a<%yh`&{S#VSbn8ONK*xra3h@vn<+V(l^??lET26>MVNGA5vo z$2PHE5_LSbi4Bs>M}68+*4J#9LbM=nmtVtgScVXPP3UD)h16)vk?Qg!e20n^ zle`Aucd|CKZjw&O7s&Zr7SMUlHQHtfzjE>&V?wI5Z4}eThNO7C`aR1H@l>*Oetq?O zRwyOFk4gAd*?v|mq(ppqfXDQ+3L!PxUVo!@D18g77gD8#Nd{Q9Ax2O;0kQ=#Kd=^( zGa-YJAK3&`#!P_>Lk3wb$rX^lAV0GdA- zUQ2&rxkBVu(D||7FDze(d>Uc52e=dp>5o$9GQY5*f93NVD;FYrEPzzMu~sQ!Zt%1I z-&s2eKQAy4^E>Ml(jTMFmw#tN5&6W6m;~D&izLVn)+i;wXVl@io;kvrNz|G62x}4I z=RCrCgj8v7qc_GQpAqkTS>03kgZ0ULygB~|8;lTs72*#z5+VF5#GfqM;WflD8-4NS zFP11ozH5IH%KD3C3z63@QiZG(BKw)g>}0J%%EY_&lZDi|qV3+h_B&Y*iF((5CmR;x zZkAfRp-OP`6Q~&M}!+mRGpK; zy(Fs6M~AHg#N3Edbv`DXL89tBK3p!uU+X4>D~0%DIw4#a5wisCnh~TE*2u+o_-RkP71G#=c%eR{zh{%{eDm7ly!tsYnQR8(*xb`qERhoM5 z_{#9gL>Z$-@S1St;ao~I_3mv>IPM4;qsBBh+(x3t^oDTSkunv3t9Jl3yfNHEqTZ{W z9Uda#@A>XP%QM7@bz5Y9YWrsD7UMlHs3Lg7LZ{#GxS z!f+i4e_uEjGB?~9AqM2maF37@O}*zkFP#5Bnezs$nFk@}?r;&w7DytbI9w~FM2q?d zN4b!aa5ITPa!P_47 za5{;4)3!W3l|;QodtW%4M7>3OUpSA1zk|v5<_p6GB>WNI19R>*%KPp4M1j6&XrG^N(F1hjbVQ^+%^i&-W^wm_auZDu(D z@+V|jY8%UekoX#0jiz?7{DbAW)LxcTAc=^1KDA#+gEkAYH>4$Xn1$Yy^dK*$ru~EZ z)u7Rvl7~WGN?puCZ%R&wyqaoEmofCFQnOg-P01X{Td74X^rqx&$UCW}EJdh& z4&>d`ah4Lujga?JrynCTlSCjLsf{c&KB^JYncB^AJ7S)Kbfr4S%60KD`FU!lC?(GUK1H4{ zQnQ7~a~qnk@{81J7L{j1>VS|s%|r`(QEFrAsF3I$w<$GvoLFN1`ST`K`=m6K+Kf_B zITk|qWTJ8=L|mi!rSc$KV@vt7sQIPh)vRS-rY4KpqwVQSofadjaVFfCnk7XmL}ps5 zU!~@<(3{9VpssIH>sd+=GY z+kA+KDV3rb?@p_Vw+Tcr&M-a1{UM{3VTMePnBfIG>EbCc)cm zvmb_y})AWHD zIRSE@o^w9skt6C)ha9Yr3X%5;av_K6S{}vJY9+{X5#%sENk~Ln1i1{7u4l^_|7df# zo-ZW27aXn^3+d2uPr>yvN*%7xi;1E2b%b6eCHM?7mmuayy-|wx3gl)XaTic~>a@2Z z^C20!A!LE}DWn2&l%9B@jM)xZ1o?;FC8SoH7$2|I3n{-y_RE1Rg&d==5mKj-G((Qn ztMg^dbOdZdV!E=o@~8WNJRS> zrG7!o>3V$u<%zznbEdwOMZNWNrryk=-tjq8Um--+^*8dIsrL$zb;Z|_48;ij4(8}% zF_MUwv-P}7srJg?*Juxom-v@n&ob=G2us|q@#1OVzx1TbWT_Mgl{#0?V@czfbM;ao zwb~2_{T9sfk0d8S=qx4Im-8TWO%#s}3U(VCVI7U5R&eP-O%B@9>oVh?x6jB+a=gag>_6zi>9HYk4T&Noy zL(i8f=0ZJKXK<`YMi5&!8{UyEsNYgT74f;TZLNd9L2aG3xp9Tz!aR z)br&+eS~Aw^W{Q)oMY4&hRgN1LR!nwF$|aMNkRq_)P8Y=p3I{5iz{?IS`$YSwO?GJ zJ3=ahKVs{p=lECX=^Uf>lq>b=9P>M3&Yq4hi|JV$qn@o7={X#uo~;+@c^sq0EM27+ za11@~J_P&MReCYUsIgC1>+?8u~9ykAt*zO1Ap-bH3QCS1p*I^vIj&~f8BeN<$w5YIhn zoYi%D`W3XCAJTFVLr1pj^(G`~%Eal&N_LY=J3LRT+ z&|6~UB9xk^rxa0)yoY}UjuQ!uIC8p)YNh8cD;Z_ z_5F5zTu6te`aWMzx{B)R&{W^&>t#YZHMN}Q>&Dd-6Va+s7u{8xuh+0V=1a%5B2VB& z2%Qnl*VhPX&^jRW-IMux{dE*mt9=3~MVsgAO)NVgcS7#aTUe$9@p}QeQ*UKSgV0$) zxxPw@fBqBEgV$4Lxm4+DG@?(95n9d*^!ga7MdrKpoDwP(U1C*w6N_46ReHw1WsF)H z3-xXx(Pg+$A7)X@aG_pw1C@$sX?Ti9BOey(^(?2O7j)LWP;X(OrAphzeR|f7R0`V< zViu!?)p{<=e8_Uh{d&HTi1sL?4YEkDXL$kg4&(v7iA$}8d<3b{TRG+j$mft+eS~FF zLcEv<_(6TzJgQwTLt4vqdU}jd3+wb5F+yL>s8@U3BsHR}!I%7v0qtAV6_pam&`vT9 zwbS?WjC$Y6qM^P_q>i)!r6oL`aScX|>;p=*VkchU7*I=98#W8Pk%$xc! z$9xE(b=vN)Agg_en<$UQ9lp>PI%u8xGKtWQ~S*Xf6l4Z0&FdbYe# zPh(N%#vAnvS;~I{VxyibV*)gKWIgh1)XRlLuf#X$OIg%gb({1ynMd63?M10g`iPJP z!HdyCT0VXH7|V^mXt(dYjrHkCQvCJaryCrjwz@t&T}X6`>eCBZ)E4!%Ud5uesD8bJ zMJ?6MdSY30iP4dEKsRFKTeNwLo+~8!i@#OR7ZM5HhhEUWvsEt=vLN^6wVO`MkC57cwUou!O^CgGnE?){+p75oNWu-6m zEE|1kVfodU4wj@u)xtiOBYhcVndM7jx$H%WFAhtCFEdzH`jX4C(U)SD-+ZZH*>jR= z&tjH?d}(FL@nsFmHNFh6+~dnQ%W_|mBeEA=zNE1X_>#pEn5^29&vJk-^MpiyD@XO} z7)ikv^^3kVMjXiRdTWfNLH^RaVuVJO?-J^bk)seZAv6#p$3fylBSPf!i&G)NP}5!1 zFS+&7TUv>sVIh@blo!ovGAT4FM2_;Jx+aCR1yrgscm(=IXVjBJK_QXgDG)kJOb#V; zOdiKf38iq%)tO^BBVk44nmpt4^?x_7=&gTIUv*} zM7{?-f_rT#q2x-+Bc&LnQbQd=8iJFj;8`R@4-K&F&til|Sf;U9q2zl+zk(SoPAHA# zG?s8Ehh;X)v{0dt=#||;p%p?ZwPM83x;Q9QT1A;FwL5+3VyX6}n`H@v&TBF`+Uc5$yoT+mPc!#{IGvhe6gtP7aj_snkw`&>Op_ zgj$6}wDVZ9Lm7)?scRt{Q0mN3p^%7nFQgxGcBn&0omL0g2KiUWcz{YpwB?YWA+tg$ zwNh3?=+5MMp>iR$8qIi2PiD^xwX@JzzFivdq&$Qq`0JdcuDkPmQNO}bS&z(gzHxyc zqpm@sqOL)r(tvBmiKy#Bzf=n(X(!o&%(O*aBXv zQBi9-Drzk+@bjp(9F_m^GOX}prtT@%>D|5@#Zu|ZOqP3mxsIjEmxV0%`m&s5p)Z{* z_bG`VgwPtS-brYwKH$qAh?xng+exTYgD-pR6|YgLM|KiggG+W2D)po<(@^SsNRu!0 zRX!Ty`iw7+LgpYQD)c?Ot02$%G4wsV63DX9Dscpm=MwaUr8%@FMs7jO@=$M#+zoj? zG$KU);xB=`7@GMY9c|=S0;xT(gbM5B@rSh+($ia3yra;o~{f{e_UqX zfVwsz=FL!x5c!S50Z4mjh=smUxE=C#DDMebS0U=6m{p+ymidrz$h)B;mIla#hj9%X zDq(pEk_dS(RKe20(h+K8*~s#KsDU$JN%$G-wuL@*o>R`9f;7VOy%hePcwar?+$ z&*4y)EaksV@I$CuNJl~lF;wb@P!G#dzVx!>`qIa8jW7K|Due3C|3heqMIHHn2#pG< z2;PHIZ=qj5hQ@_Nf)7I86%u@!)=o#l63)CMlqjT9TLGcvydyN!B)81hAYCXm63Tvt zB%*!FG5-yX3F!!Ig?xgTpF-m-+P*T+Xh?fj)D=i(`6U#`;z2f|)USNZ>=5gsAM#r$ zC}Qr`G7+;4G8W1fQmdT<`3drSsEDNi@+ahvP#?>`A-go$KBZbq<~`d%5_iaaNxRM1Eg5()kUIZa54km$8cf>FkzW`RgBhFR1V z$3$bsa*{)NovL3TItg+zi=rs8RrkRpzuH^(U^(I{b|H^(U^(I{iN z=5l$Im}FG2+zg?ucal*fB--Z5YKcu!XJ1iKXJ1i?AP-%mO;M%dKf(FfIoNZfLQf3m zLiX@uen!lNkYry>^oyRZChW@+7SESvmTAVY zSQpXJmZe*~iwR40qokgvk3}Xh1T02J>nJjAU9BpL92(6vz zMmCFDJO4CtSk#iAVdS!?C4Zce&!U$6@kSwwTJk3t#Vl&cXBzWZ)OtVBC}mOW{UoDI zNOY`YmQf`ndJd3fw8~QcH!e>x+F8{4I>qQ_QS0jzqfba@KrQD}jd2#Wd`>g6o~O0k z86ctMbGlK$qL$B@MzN5H)`l&LV$L!;SUMoIH=Sdox5zwd4bC+BSkxN)modabTMLz% zWsJrM?cKRX=?gLsZ9n&*uGvOKjL`m`XE-m)7_~)RWHhm;wUcl32pJ5hwR5o%_Y##F z45+m;$H)-U8Bkl)B}O)j+M)`Kd=|AOTxyiCsIBEPV}+0k?Heo&I@-)NhJbb-f0;&PaKM>XJv2b&wm3 zTp@#jeL{GH7We&1jT#}F0}f=nkVYAka3nI*SlLpeiRId2+`mVjQlo|CWW@X-WQCN# zOo;Xf#dO8UWXP?C^Q!23;ED|aZC}W3#?lxuAh#PWLLyo|@_3LkBiJf>f$x#WYw1GL zS?-3M0GV$zvpfKq3Aw`x?_U23+{A9jCLWl+7iTEgP6OFVHRrP zZIB8>dtH`Vj+jbFrI9D37T?6hT|P*a(a!P_%R*zEOMMD?95K~K@*9)~?J4_0i#GrWiW&oIum}tC}BAfLU*4YFv?iwa!id;!4lz^8l#5g z3CL%ttJY`~(iv1wplXd~86(a>z7jF3+NjNPiyDGFXpF2Ri9VTq$jE+^WH6wfQaxm} zvZ!ZL4;$kw>e}HEqwg&$710ja1y2T$`Eet?UDTzmKno{58mBEaiiHdY)N{|Lj5Zea z)b$yoS4ae3%)%WQlzPS(6;h}Dy-m{&g*2M zy_f@`bN>GrX)GlWdNb%9BZK8G$TKLl%E)4=fwV&2HFAXDiuH3jXVGdSS4gGyIAY#M z%xa@h#`s6J4x?Je_;TPX%5)lCF+xvfyNsR~S&ODB8A%jM_kXmgVF?o<7V~vmq<`KsB4B2jIA5)%M&4XMD z`N0UXOov zpN(!Ib=qOcxZf#c+8Sz4r$3ry)L0{=Rx3wldb0M5(Z?}r?fhyOYpK*=K#lPjGs=Zj zYE4&aV&vYRM!gV>yiP1bO)bN5!-)}EK7Sb*Lh3MUm3->@w~-qo&miV+ zqg_Y@vmnK5Z$JWO%_m~1`p={$nC((9I}b)IAZCKuEks^TtQOKMBzna$!R!|j-Mc54 zLsI;Gd4f4CBzh(sXO0PxzZYG|9B0nxruI|@)tK#xW|k0qyPW4|n`qAD7&R_scQcP; z)R^tv%|ecO1HI@)=0vlYWi8}uA*C#8rie*qIp?7nOX$ADB(s`hzC$VcJ)LaUbIjim z`aPX&F6Ed5Qf15(vzcR#f>6v9vsFm+8Df&z$D*$2_b{FH)c5E$^&aL5A@V(!Eok!| z=CBaC#OUa^ry2ZIEHQC}q*0W6naMFSj8c1>?Lr!~bI_h&AXCi|mY49>Dvg!h&z$<1 z$fI3~m~q7HZ#pru>td1|mYWenNi~~U4#hK18d(%FTVrGoiz{KG8$ zf+V7?f^=X!LOzC^Vm7lJ0~v&zYId`n z1^FFvn%U2CDI{?To}ic`LMnqdK}^W$<`~PJ5Kl zWU3H(CUYoCooQyu82?xJEHj&9?nUN*BIYc!AVyAxoNboGl%j8i&os+9X8KW@mV=mo znJZ!>7jmw-O2}ZK==TZYcsR@K77{&f%reKNXp2!hja!~&YF|>n>ijSB&N4Gt)R%d4 z%|am!S|;ACr{nW^W|NR;=GkVuj0rAB=A$r=$!xQO&%r@r< ziOpQiF-4s?^SYffuVIM~$={3f&GfHi9uoR$e4g3Nl8-fW36{@=CMv>T=cEG=Qv|u# zm%~}+_@YJ#T;huwAy8nZe=W8Iu}U+01e-gvNBuHQR(luLtLv ztAs?a2j`kyvXsb7vmwnjdt#&|AJ2}>VJX22k(u`HE6hMX zd`U3y{1h_ZmjhVtP!dl|;}gW(xs!YeDfcBErG9|i<;!u9KOhTyp(P)99M|t=Y0NUD z_oOP#0U;6XY?QKaAK)G{?mMw|64dgkGLuqLy>DIZsF=cp2K0gf`!AHpv*VjZxq4H>VEB?ME%22h21U)zccYj72S<2hCM1 zYWdWe-7IRI)|(?NYMnl0CT*d*BHGPp5ADkh<}@L-xQl_YPiSF-SuLbadm0jgJZyGz z%-fJ@kVdm;E9JrWWgv$`9x=;@q>Mo5i2az^E+nG;4?=xkY_8#$sb;)(oRA)tgCTw8 z7>!^Kvz!h&12K=A#x|MxO2|c!C(J^YyCJthmYNkTPeUGnJZY|Ac^5*NpEA2xzJa`m zn5WHtmcKZr$sA)jz>=k&F_XTRwI2t04W*tnr?FfD>4YpZ(^(>1s@cqDc^X1>EjJ6K z_-F6W`P=jRh}nRcsC)+b8uGj!L)*qyNQ*D(`18U}LdTyMeNlG;U-IQ^Wc~$tUNW0R zd*s>a-$L4AB;g6nsA-M~84Rcvwwlwn)6y6Ws209vW((;Ie2>g~qtxqW9>=H_zG2qH z2(_@y>|#;+>cyL8x{!!AhPvqf;hSbI%ij8~@``&IA{Xn(zxjJIrHp_){Xc>sfguG++vt&U|hph6KD!qSk z5YAWM^<~B}xZ1$k@@iksMJbXFU#^7EuV<$(x3PR+CjA&~5B*wonQ2mj4K~d} zLZU7F(ClG(95Lsjg&&!jJ19?w_8g=D^08SjWH3PA3cm`n#>^X`n25F#F?8mz&MX(w zpk?6NYOPDMiseJZ{2Qe{F~@||YMUVsU}lDHGx5LCHdDXWn@)_BqSU8mdW_r+`P?j# z68sx^9)fH%ONB)5UT!qYgbW7M65D82v8W}s$*gBlOa4oTgz6nM#y`+s5>x2X2vg6DxxjH7De}Rx0y4AEYLDhJH7k1%}n`K z#?ZADE$3}!ISXASk$i6s38~d)q0}>I^LEquO_rjotydt!<_Z>dwe_RfHYQ`PLMgho z+F>sJUCM0`dY&?34zN@~Xl&Sj&9pycOe4!rW*>{XBlNSG^rwtji5NQH_{A&`Qmb`B z+R>h0%|ec$tG4$cznSG6L)U0MkTJ80;QWX@F47E>;=GEa#XBRt3kr$1xME z8jksjW8$nvjv3~dIID?c{^Xc=tAk}?Sk@J9b+haTp%L}FTD_bn9YQ1OceMsM<}?Vk zc{gjAWAZr9Zq^vbT*Wa#OZ!V~3Bmas6SNYAR0eA~Cc#SPm}fX9!7^lwwi0s7MHoS0 z_5F|XM33_mtzjW@)ct{2I}@!OZ6adu&MZnD0omQk7b5S{90N(TI)va%1~D5?#C;@d zSV;8#&lD>qKzX9?NhMjuLTa^sl*&Y&B&$q_+&1Xm&K_2`6zvc^V?7Nqds%4{C{LyK zUz8$Awi4q=YPFz;H%ZHJ|HqoavJZszi+!vzA(-U}&td7V$iCKMj!8$%xyUos%8HkH zvLJII`&s!yBHAU8|De?VRvE{XKuQp^ztzKXKjbz@iZ#Hp400bN)f#4b7qS?lTcc9^ zGntT;yerk-pnZv$7Z4M&oET|?7*=kKbV4kvB1U>4j@7=K?8Oe`*#>c~ZkBP#uaK~n z7?d$2aZlpzla;|TaT<=0kb^8V27jHC)Xc62`$G32X?u=}+T)_4_S2|1$Q(lEgRL2& zuIQYx2U}S}qWk;7)=Z93b){K(97Fd!Y2Qh+3OPpAeu!1fF=~H5#46<&)t*DGa*k2Q zjYF+!j!`W<%&HessoAqNaojk}nw}t+*m3B^f#^lL)hMJ+%Y_^TIl}4@BDY>T2gtC7 zWGR0wXIP^`8ZgTk=8{3F3@dx0T<-_W#vLQb(P}xzUy96^L8kk1J>*)*vDOMvYA~Q; zPOwH;RLn_M`tFpuPCIoit`|}2RI5ZtgOGb6*;c<0d;<%a>mg@ay_2X^v=?Vv{V}ox zF=tyNLOT80&#^KmQ>o5?s{LF`n?h2n-Gj`pqSS255Q15QAn!o(RKF%Y0imPcg}%H1 zp(A!w-h$Az`9*%rd;dp1gV1?hG-exwuGW2tSM!+Vt32_4BZlUJi^?7c;)yfnSh(0q zmu(jJQfQsdv8D@=N57BJi#b*`3*FiN98zG_3yIzhyHwRRNk?Y-enC_Yfl!;HlKFpP z&hcZGipnbZ>ibiwFQSDb*g+k=BlD~w^<2-b>ocI*( zP+3j?U!G#CJ0{OQh$*%PgfwX7XpaH8&Kir6!yz|XJ$q6M8?*-yLwEIWwMJQ%LJr3f z`!*|QFR>l^`{3cuQFoIU{Uuf@3KmT$bFFR$tsqwMtm7hR}X`uT>_bAvl7}H2dO0tA=Ir zL3qZ2^}f(*6w(<``|^F(fRMp}+L!OM#)V+M3zVY0yV}a!mzG#WJC)^rt6RumK<)2~ ztfr|l<}$?49oGk}HkO;YRE@Pph#cpTgZ9)~-7E_cL+@nOSv_2;0g{WDhpcfS(RKQe zmAD_(CFMfIG+2(1PJgb&Mk_-~P_5HOD~m;~!A2`b$bw)q>MBH@Mr(zTh(Ck zx`ezRSn0=%a!e=WYEi0Se`-&I_BrH6$RpMYA+m*&X5oI6)f*%85wqAzIw0DM`yr28 z=|bdul|8tIf5Mv1@)I&so~2ec%U>){T6rwH9~`eej66?SMJy>SPg|udhp{wSRV@Ex zdB$pFIgRC6tC?jc%QCBtPU$XKzCbJv&^RQG`SOpw&7G#-_Vi_ZT<6eZkY*oYv z9RXgo7E95pj?lE%5z}go38~dCM4k;NlVqk+Pb1pp5Q=%tnx;#+)0ZY8a*Lwh)7Pw) z7@;?jU$gp!3?``G%Ga$S854XK>w;olw?;We{d&G(jdRRvh@m6N8&)tRm%REtZL^Yu zR0g{^Pn(s(G3pm~rR8wU2F|n6N*5BnN_x}EGbnRq@D)5aroH=3t3XI3_&rL|(dJF7 zSjLE@O7fOf9V68DcB@`U^enF3TFRo%qu#Mvgw$%kB6Bo2^kEim=CQq7BySu8Y^8$bX}~qrVEMwqOG;EVq!KR^IEG!N^oMDJhpUO zr9vXX{UNlqbXyf1qu#q&Z&h=QdQ)J%)yOgGt<_Jhr5vN)TK&{&5i*#d-uL{>8e&oJ zd-hn4E!VPoZ}fAkfJMDG`h~TaMZNF2!RlpE?|b%I$&SpU-W%O$&16yUjc&54g~%oL zEtbZYR#S|85BbV!i;>?T{Z^(cGpn8sSlKM9rvp|li+U?_icNyJqCLj4 zk6kXLBd`o|2=eS}FJ@_FnQFJNtY+EI?qXTTvcEmR@)gSg_9)ACmJ~ZFP1gQ9Bm;G& z+B1YyYV>9_y=kl4*+K>bB=i=oX)lfuS{klBB4mLmbsX{>Xty6inPn;J`ysY=s90jb zghS+&RJt8xNrBLBLAsqRq*gl&l7l>l+bJ?e9G_=FjFB17&Xc()1JXGYLr)|oy{?7 zX3i7snH-~L<~-5PXQ7!n2E82XLUBWRmGbhEIY?pEj&CE$LC)*W5 z1{2iGoLP1?mr^rxX4#D#qh{tj#a_xWYG%$;>=uquGjpD5w{nb{ne$Y8700MqH&3&> zI7ZF7d79nBF>2P$Y`c$R)U2D?_7KOYSvOC&M>s~!x_P=i&M|7%%`@z{bh-VgSvSwH zlY|T=OdFN&ZJlXPV=9#v`F>2P$96OC;)U2C1_H-7Sb(5C!S$3w7NRVdTq~&~; zox^$5ESG27xm-%ka(T90z(TWJ-i1=<*hQR&X1Tmy$UM%YzRWw*F5{R_BRH2w%uKtA z^PGsi;2Frj>>7@_0J1{JVvd=!5N9ZmbL}RMp|3H1AY_FQ%mRw`(Dx8$*=-!7=Ej_5 zcL>>>puV)2Yp-EZUs}wydszOB+UYta*B)X~-(JkMM_AOC7IW<}A<=KG=Gxlfat*3) zt>)T^LIwlstHkHoOOK$KTCEm!(L77DZR1EOOCX=2U$gBzmbV~VA$j&HmM>W@u!mTF zfc%7*3+EXP5vMSCjjdX{q_H$(2Wm$F<6p&3Uj?S7Vj zL+(b*J@ydG-4GfFQe}?_!58X3#1rW<+$XW8XHvf!w6h_DkZQY_J^N3l9r=bn@s1SUG0WpsvrqM1tS(bX2GcUIL zSw2L}a>P7tFV2!Ny@)vsbv&Q{utSWm>2Bf82J|Rl06n9-$P!u^G~Ivfmx@nz_}`<)h>>a zzaX#M(@vw9T8+NyIPn?0)nd;RBFCog0eRCN&z5=K+#OG!AaB`?XUI|~VrkI#9op?~ zmOO}!n78e?Gi6LMBpvb}JC9{PgckTW2w>>-wCAQwU2wP)nWJnulJU5>FC zc0S7nND*S*vlp}c2q}TQZ_hYOmI@ppNAh&q6)ck>w;<*NyF*Bwc2iEARu1X17oROl z(NXjs$cJ{-IZ{lNS_JvXE}JRkSja<=k8SN=Qciei=Wv7qjdI`4TZ- z*iAwz{pZ~q>=q%>=U*G_HjYtm5B1usI7WRHz1LpDFctm-aYIJ?Ht-4$h*!R|aQqlh5D#>?D@C5W3#!vr||~IOZ$c zVVTb{U)kv_rJu<*e{D}^v9Wxp&0pJDEQ6?>?wEaJ=dk<%p}Vr**m+XK**n$VZx_Z0 zy@9^jE{Tz5JURN#E{lwIs|6cT-Mz1=QnQS*5Y+e=y0y9~p2 zvye)S-n{!BnTPG<^TgKTKX(|mQ-nlk938eDAGUj!{~QyMW~nj``g#VNtW+|8AGEsM+s-w=0AUCaBr(|FEk$M$LZz zhuz3BF#~Tj&%p>BdnwEQkQCJZr`^JN)b|UG*qpq#~w#PX}U0Z2R-1%}jt7|LGNfHvhwhA~? zIYwPu1ssQC)V0+vP8!FkYpY$H=^UfJXfVOa z+~(b#VHS0tHqp`YXnidR?z`8RLK7VTx15 zqP}r}HnXT1`=>a~GLNQyLH2M~2w9-zqdog#Y3$)-Uqo$=XjemMOyHhQu@HP!7NR3& zFDEXaVrsQTkOLvfP7}-HkYgZwJHtYvXTnpRQ6Ukn1u=96JJp$bvCOjyavDnQ=NK#- zAs0aQcdA%^g4_r>z-eUJ{pfgY0VKsiWAN7*??C8X*HmAQWYKpLdUG@63(dexrOcg# zVl1ac)FpdC+q2`e#|XW(>N;I9LhnI`o!%IE2rZoEXmhBiodMOtgPar=)xv|FbQaZ~ zG$$)2hT3z8QxX$Ha+uRBWH3R^WO%qUz@la{Ji;+9q1p!%)QpHnI{7SWM#K!Kkwwj9 zc$Cw_qGmEY+DR&qdDM)E|8R0x)QpJJohl)6i7i3jk8#Q_rBc!NyN`1!ghYbsS-^2l zjgY|v^-SS-r;%gSGlk=wCXP|hCQfi#I7U62IKgQXg0GKZy}y9kGo3Xob0Dt@>E%3X zF3J;~e$I0fV%|i|iOw*`sAm%=Iof5i@9NpaNlqe*dNy&glgy%?O`Pl)Eb7@rmNSh- zJ)6jKGK6#nXnsiA#!hiEV}$P5pX%hq2;Gs+b}C|o?my=^Npq=%(Ps+hIBi0r&lLXU z3>8vLg%&|i>8}2{PWj~|9f8MLW;s)@l6l^Oyo;rg>$G1j9QTq5J$-JJmuuh0s#H&KVXG z5qZXu`39%?2FfgBsD-yU<3cJ#3@yXkoZyYJg$JWO6mz>XmE}|jJ>$5;$zZvJ;o(s$=9M$V1N5 zo8{6t5qao4m=8O_TS%g#p&oIjvZ&Eek2=#>2B*mOJm%!EsL@c1oe~xr!%1I`SmLw` zsSM6VU33Pr#OV+co!w%I)61e}u2|v>3yJvWHcOm>QflFXpgOl%;uH&sXtyGB5Bl}E z)5JpG_w0i#b$VH7M9=`_DQ8qjbk>)ro#3sa@BZ8@PdiDsNl|Z7JnIbIE=A1@)9jSZ zm!jsoc+SbWLyDS-<#}gXxfC_W%L-=(i<+Bdg;T|%zT)w!lYN&gMM7Wkc+E+!Ai=#R zjLo2XjBQRf%f0CPLAZ~!(#aDt75!uB`CoaB|E|-|(ubIci+fE@;@#Bd=(){%jv*wv zjlJjOv#4#X!)atu+t~X~4~yEyI-R6SkvTYw7Sb8@2hLO>kzm3<<&o_J#}U#Q(2v1v z*yu%O@>c5?2aJP|F7QVwLDlh2~Q5BrHzEF{{C^-i~tT8-wwp%G>4ogS7< z334@r98FT1rUna?4+~Md+QYQ zos%gfI&a2+lg*;OYBb=?WKmx}+T!G~sC{{hlP{z)cm?XB_ldSRHBvMhi$&+pTbxxa zHzJ09F9w|R5_=O8~gIYKboJfsjy>_?|Y2%h*ru7iv? zJwl>ic=^d0U{ObsQOBs3>r@>{es#)(M7O%%oF*Z)+8kuQ1DStwT3G%Kse_C;tudwO z_x*QgRg64}m_Hotez6S2Hb&PM<4%(h`2?KKK>l({7Ez2GxBMbX{q4j(Kob4ZS-?$Z zQP)iYH?D?a8njBZ=Pi_);LZ>d{SsW9n^{XS9sbwi;@m10^+mZjw_6CtLLkp-AzW?XzKAGsd||brgedNg(D6#KgNLLL%B%5IRci>bA1{=*yUpIxTKS zy!H)B?dr~~r#yAqo)DV(Ht5y}snt>;TM?7sHVTPo$3jLR6WwNxxd1}nnA_cLWw`}H zPt6kD4wgE|Zzwg%UBmJcL|cyQZnsB@e;;y+Ydl2lky}CnVy3v6F_HvHa`T1Axr_IO z?BO;F8BBQmzw(~LUhYyMk>D~2Z5w;JEgX~cv%KGu?6z`DC4^#<-Blc;W}(^J?Gmy% zp%ZPU@1O4N4$GK;x~AUS9TyTEgSWRE*AVRmwRvwhNl5g)l6_o5NVL>GZiNuJ9Xcqr zkJ}uR=V(ZZ+Z`ikLTq>5!&KLT;O}T5-KjX(En}HSluv^8VMxms+H7sfrN}9V^ zNJmi3@q38dDx|{y65JtfyO2mwUGE&?c5#fFq54p_n`6`r)rY!$LOOzKw&%m#0Twme z^I`5V=TX;jhqFyQ zqGmjv;Wn~(`^x#Bk8`IzK{11Y4IPYAAT&GNX>JnBQkHCYst_DoAfqUCx?96Bs~~$lhgoRc z#Vp+r6LO~8%(59mv&`kVD_H&uIUF%(x$P_ykC(Nd?RE%}+w*CNImaCmBKPim$ZXem zntD+gOh+l219^@+jU@|0b0E)gGlX<#Y7JiEmb0k2rVHF2A+jzSk$9=wFY|~a|2))n znH$$cbw%g3E_8z|YMU=~6NOax-}@_clZ8ZrYI`np4VFB#@D60Y+?~d9E#v_q8A3WV zwIy8PX0fRG!>@E}gbW7Myw+E_#xvAH*^4KU=V~`yh+JYTAlJJ+LZW-+O>Uo%=w5k~ zJH#<+ue{kE;TYA6o856C(Y^8(H~6evK59K2&oM2w?)2fajTolF*bzmNZ;xfa7-;q(UI*ow}|Cg2*upy z&J$7*RI}gT?v`;LHT(VTZk3P@e}6A?`&iVvnD3@66MgshD3R&RhHp@I}f4|$U5E9+rE8Vf>6cf?ZdcVh=`ka&rC&=@xIZI>Nm7`kvkS6yF8DP7;fJ4 z)Sl>>aGhJgqRxBk+#(irZd~VhiB{xe*gZ2XCB$Qg= z=CS+-LUq0DE@k-uaynvOadTHtdm6M&5IR46)t$#O1euALR=1vo=A$lzyzch0{EC4 z+aZTTK6m?AeubO~+2HoPE@LL0BuA!ga{E}q5L$y@y2C7ILS~~>pF75K5tsVP)!vYK z7C`7Y_O%;ic^GmrN`2!_XL%k%XBYi$Cd)ezT3?&pY?h6XVwC#U&6VO`JACIh3Te>j zUegVT`OfWPp?!HiWUJd3BlkeIxkEAX0A#zH(I%Jt7S#19q#sc%XdMO{Bbg5gXSIwHltfUB+WJRx;j;AAO@ z;T3PmrAnptLd@iFzmNuvkh!ZZ4kw+k2xSNH38DE4P9PW>icF1Ajkr-JE$q1+Yhw7@+4ngfC{|vVasTHyr zk{Ql?N4AIVD2_r-4p+0Bj8Y`0hdWkLOs#e04rUY_+xQwM4avS7=@PLpyjjju7AQy-8J7lR2 z#L(7zNw|>Z3zmX#G0XRmr6_f2xQr#3B}-ivu3&Ll=7y_TPJ+cUG|=<5kI zCb=QpAq2->#9WV{CLK=b};d#oGgGqW= z=n2c-Li$+LcZ!#V2Usf6LK;8c93E$R)|ce9Vx0y*@Fk69z?UqR38$$%`7D+%^H@&u zrJ7~7FHI~r_|nd@$d?|LCSQhGI(&&+C)?cTOA5;$zGSc*oUPiN!;@%CGiv4i-o>8EZ=;Nu>fLy&0u-jkI7|O?MpFB zzb_Rmzx%S7W%3!S&8;k9U)Hc(?8^YlO}>n?-0Mqnx9r7oU(#4s`I5!*jW78uJA9eP zGUW@^_iC2OXR0#j?=1U>V za$m|=KJcZUrQeqpmRgr%|eCcC3)t6D0Ild%*E_-o{FAmFxxf9g!hvgAJCYNP} zFU2fh`BK3$>0H&q#Vm*U(#kT+mo+S+& ze&w?q<;TopIoFqJmMeT|V!7Lwc9v&+>0w#z%P>o?FL4`Wn}78sg=JE%YI6pQ;Y$w7 zNxl@ae7;T{Gs{>OZctLsa*UXI#?d`rH|!pUq)F*d`avTz0l}&EXXr zN$Rw}5Mx4~54UZSF?7CqION4}`PWi{=iwX@@=CZ>NS&rbE{D7p?iYellsGpog}f0C zenX}3rJ>CM?E%P};pr?#q0|eIcf$QFr$f3R?}dk0@>n{;BP>@!==|{gu+}d#-wL7g z!_IIb%Y6_!KkN#pusjZ-^TQ9r4$I3Bs_Ub08cPS{6UfKm=`0%{-$B-fGlev0H+_s} z7as0QgzH(}49F23-Qgydt3HylKD>hSR6ssX$2%C|5sn#z(2Pkv;grpCsg6VFJ+jZk z#VijsN!bu?WBDHKq0u87!@+N5-~WV+qJ^8nMM4_1$+Iy^2l8dOnZos1X$3h1bLgji49^CvKrUg8?;yVoNwpNJP8qJv?zmo41C`h1695<`ZPr#|vU*RGa12H83 z3omA&yO(oO>hEyk_cBlUffz4}yEGHLMj>_D!6-$~-*@%KIOeo(a8Ke0jHvfAx64u_ z^N=U#6~)MWNP<@rBOl&{C+S|BkXmgfN_`HQn5b9@={MSEb?$ zAv7-K0AJ>DOo}fD;>boZslL#$Zazd;GN}Tk==n}aN&GxqFE2xzqjCXCQSGK5^9^Ds zv*iuTrQz>`wwL@PEuZKfXM0AB(D$tzFD*u1LS136Op5=#n}fUxA<^&M9OTt-%+qKy zeaq%xuaRS3h0wQb4)&Tjrg2n`G)VJWIHn0gy-4%gIA+aPa>*a!t>T#R1v2IkZ;g=X z2!TVr#2wVHI_*8wwGwSU)JtJG7f0H595d6s0+t@cJcoVxaIZ}Wo*iTFCON{38PJCGu;n&kyZH{>d>p5-G*Kjdn!iDfHf2jm*BgC*{w zcx}q7xDV;AVKE_8>RPXl<#@<6#9Zg~OYx5q*L&F$<+_-QV`d+YwAXv}EEl2F43xUT z>u0$Uath=oZ|d%{6v-^eE#6F)dmxuXZuPnnWy~U$+r6F``8Q(bd&QIGQf=gza<84G z3Gx79?(#-i=3b8bf{+TYaf&SU5@Mc(+~cJuNoj|?1zG5g3u(~0An!w}z1%%yd*-93 zThP-*UaOE=?OVts9C2#A4wf~LILL#Zv!~4b8p}gotB?lmUdRsQY4mdTk}*eU_$`Gz z;^nh6BIZ}fquwyfMzp5~?Rm`0N|vRzLB578@d{Xe;g~19c`Ot1WvM5 zSI-iL#I@opO5S3Ye?s<#JnJ>FoCVP#%e)pL^4DQ^9F3d34k^I`!~`JEd23j%h3qP% zM@aN&{G2yqZ|X(#ZQSR)8W#2L?ekurkZ4`cd!tf(Q~8(AK|)IcxfyvxvmvnhLC8#R(Ok9R9!2)5f)X~ z%ifHsJJj_Z2W%V+tK>w48I6cVlLRc{T8s_RwH*iZId)z#`13yJot)vJ&a z-0fnyzFzmLSv&}>uh+eL&ZCy$>)tqvs_S)c=Ked^^@g`tNVKjuyjCf}6S=OHUOUUB zT-QpkgY&4mR(fd%$jqv)m0mfE>erjz8X?iT-t-2f_}fOiH_UP~*VXQgavoJzyO*D` zb6xG;3Kmt@+upd4XkBl6$*EMT(y!|sFNLLw>w3p?ghbcZJ6<)5>eoA7KZ~ksm6xWA z%wpsf?O&_BEGd3ntGyhSr?{@wUapX6U8}uz7FE}3FDWE@s_J^r%NG*u*L&VPDZv%U zO#AZtUKz_uuIqiTg7c_l_`WyHqUw6z%QSYbtJA9%60NJ#YmyRN%XM{mEi9jLU0q%) z=TUWac`2sMtm^9WN?26CKJ?m!MCyhI3>tnBvVB#FPzSes)SoVg{zP#Sc5)!Rzz1PB`>RRtr*dBNVH#{dWBMgCfC*D z6|;C;SC2Q3^QdLmZSa~nkE&~f zm*~mNs;&)Q0gLKauh$|ZT34^vA;s_4CT|VPudb~2Y#aL zwP`d$ENtHKIDY+=Y-rYva_YD?NQ8X*iJGz+0kZ^GCR znuTehO~{0q@8fx0&+DAC&;I##yZOU?&+B->0~lcDcg*O&fk7FE|*{$3$& zU0?Z!rTAuZzc%`Ju-waaZS=hq@wakq^=e*=rEtIt2gqU!p_pJD7;*EjyDQk-MTcm8~qC%LZg{Ii6(b$#dW zW>Iy0=bsRgeOGmD_RkjL_G`1hT#9d85qiWSS3d_u@t{?oREUI5W`n!d=b^Yky zEXAp7&_Br1&2hdGa`YtWw`*|n}ge+!GMYpZ{U5Vx+a{zQ{XRXBD1>`!K)k%Y#L zpZzI9-1YUdzlufm>t}x-i>hnLAF@PdXN(&1XG!sWiOjUVe)Z?D%>R2&QP;2jsY2Yk ze)YGrsJedjC)l#5s;=Ms`9j=&{pO!1#W|+@?k{2K=YIX}FXudJ8UF4cWKnhf?$3 z6mfNhmf_$2UKV3M%b^YznIeOQ+{`NPrsJeFe2ZgwG?eNDQL!~Nw|M2?S z>5pgG?^4;Xo&H22?)uv4FJV#r+UZ}%qU!qBpM0#y>}(tV`qQNNCL%NSD@M;?@pD}< zdX^Bkt{8nKi>fO|*V1KARb6}NIYQii?V-<-;yZ@xiq&VcoXT~@>hm~{T86QDFN>-x zRyU8^wXQh5M2K5goL(!%*)~S$4J>LIj?$YrkE&~w9)G;dtm+!27qF;)`Sg`S+`4>v zrxf2*?$;Q-o8>0%*BE^R=TUWy(KAliwXQLGHH)fiZ@pKDTi4$DuoPb<*ELq(!Lpd^ z8mq@<$jqv)vHENl)vvL72aBp}A3gp=DkbZpx3ce}Crfea+E-5z;%*81>a$r?=6&@h z7L_?c?~!?&tu8?ylHzONUhJojusp@R*iY9^+O-$^>4hw+uKo0O7FE~&x^I%(3!1&} zuTPNTYzYVGI?JnE*8#dI#O>DsdOeG(>i~U_Mb&kno}RgDT?gv9QhaN;UrBl%%ZFT7 zl3pOhtt(0IVo`M^>B%R{zN@+p)(eHWTf)J5sTALZ`28;Jx#RUp7P>-CBhq-in)9eF zVZ1)VqUsv2=S<$Uu4KJlh+9{(-Xg`fk^6O+-o`S-{W?tV;5@3X!*u-=nOW6!m|nu7 z`gOS8CB&`kaJ^58Z_ffbB2Cl>SjIr8Ula8q&ZFv@sOM$vTGvE$8Dx1T0U&4+vhG4%nKrI100ttX#O zF*Vw=kl!I``cxqcoZn?1tIrZrrmaH^y;15|y;R0%^t)`5biLXm+7Ya((`Jqt9F!yzZ>EgtbhCh6K4Zj24d)N@((!X1_qAt&px zXUe|QQauMUS)b~Wiy&Egn~*Xs5vAz4xYP9>A$8i3kSm2~XHlLyEd;q4lC5X390RF> zoT*m}k=w?Rxb~H!4+!ax*#l2r-E#-7KkMl^lt;!`h{@HnJVIX~pQ7gqk-wQuN6dM8 zm5gz|T|QN>Wf_NUY%*e|>J6Se)QhQllMwfrz^QttkTPE~N@a^u-9q|D+aJrf@J-b> zc=F_mn7p&8&HXXk*I|j`?PnM2-7I5JY8qsQUUd$|l=;HQ?86>@iC!y3%Yft~&n0>f z%lVKwkjwQUAvIbdWIp67eFw|UEZ6Aq=ThbxZDI`W0afE_v!2YdkYncP=~A4l&DZPM zLfkj(U9aa#i8&SPs|m~JdVQV{xh`ml73-)7e{08{gc$mDj3aTXRI%RRDRmFpe21Q% zE0&M*PVWVJrWB{H1^T=x6jR|-znNX6m#{2BFX-Kdi}Z3KW!e*v2atJ@o_Ic$S}VSV zy#i9D=S?Lk)7lX8EaZN@L`aSH9;6jgt+xr0{aOvF(Yrj-1*z4uE)ea}K0>J;NWI=C zq|B$9;=4SEU7SmdF)8uUsj; zwdzG4px0lU6|41DmVGb7Qw>7eh16&# zLFmp}yWT6s83A6`hlIF$_v`wIN2rCb>#@_^UQi3)&=Z8XE$q;fg}6^mzpbYTS>il3 z{kERRqMn+5TQ6WyPffqA7qVPTy};8f@93p03mvIvdD@W{mX93iV)@IFK9&iWtIQ)T z*^VSkr=BkHUF(R>@}MJ`ENdLeWBJ{YA}P+CCs1Ij58D6;t)SP6aUc{p2Bwy>j9-%qOCSAWoW~LaLll1Au9-%qOH+mO~nv;C1ms~3Q zuI41)>D?ZoIm!3BSwJ!FoMb@H72?iG2J~VUH7EH&Z)H()lAm%Y?&ZxCj&yk{~o`<)iiI{3Gm4i~bkZpQB%cYR%LYi1^;FzEF zl`IQ5=4ZW4ic{B+-rKaNA&aw#Ej?-Qhd~&Gtr(u^(Gc-55@ecx3H+5{-w9ET#CLwi)kBxLug(6t@p5e0cnHm(EC^hAZvvTvit%06!MQg%rfc=)R;vJ>(m%PV`wO&tb&rvxV4AnI~P zq^v-?6kp6$a{tN-WU}l7q5Uf>ki#;bV@?fBWidGB)Ifn0C-Z56LXX^y+D{9Va;dj* zopwCtHratnmJ^VNMv3e|wGemw$qqEKsL|$(K&ucrBGn-C83FA^>X*Cco*9@Rq)y90 z=6b}OiSLcj-`coqAuAwfIZ_O1hU6$2JNZ$0&UlU^^p5(^ug7_=lDM-GvvC)p?@9gv zIp2v{0U37#-f8K`50G;q`GHo^LOF|TMePNF4MO@ytJ-G@;lfKs1AiUWB< z%CxT`Ly+488(6kMl2_q3m;vo(%3P=Y133v&5y-iPq()0D#CQm)4CEJ!+I^=S9H&iz zEDX$Ii6G`8Aw?`HxRydk&qaY^mJGzu(Q{FtoJF1I-V>-|QSXMjCr~fM-FmA6%|hh1 zdI{>fH_*v3YJ0vfutDZ=_MQ6zy;6MHsQn6*x-YPqLWWrAz27%L?hlNx(0jj2 zgv8#u>yoby#Czm!#8d}LrT8v-5br^`7SC%1Dp~0Lw5dmtRI}ux_8OF09H?h0hCC#s zk!7(X%`DGC9v3k!Ebl_-e0g!8jpa)SZQ+XpokHrgt&rDIYDpmZzj7_dUX5?NLzV_A zSq_05@*3V?85m}XK#qev7AUz*mO2%3I;1HOUm|4&WIp8CKqt%fkQI;@1L?QRn0p{^ zL)rqhLfo^Y)qw^f?pf06z&aLnmb5z1!=lcTRtNf6oZifwhg+9-X54B#NEc;2+U(qThyC@l`QJ2?3;nSJ1Dbz#r4fVHH$jC=?rwS zsI!}Q14*T_lsdb4FOXa&MV;Mz5XfdxXEz@ON`$y)Hy;ODg}7%o8v>m|+_Revfj$}I zoZWmH81e|6-Fy~ExRYv^=R0(E(-TN%QD-+_1oBwa+0B=MPLI&p%~yeh1u~C1yXg%S zc!bVwHU?T))Y;9~fm!9U@3frhitDC8t4HYU=9@s$T@>S<-E0n|3vthGHU|n=)Y;AV zfd&?JcC#fgB&5Q3!61GIb2WZR5g1|l0NW28J%0#j6;zizg7jls{UI=0ina+c!;qf> zr9#}T<)=V3i`rTS16g-d9yvz+g;Iloa*xEU#<^9XkA+I@4f#2cR_WGG*Q|yD86MdW zF++i=Li%Iqxzj@+zXnP?F;r?eP|h)QZz2UT!+{A4WqYWuFl2k6iiKj1hWruG7P&E# zAb$pOg}7Hm{tD!Aj4Jh4pz{Be+7W2^Kc)T&wCyGxTuSxgpFq<+qVK+g(L!1m{{~jF zjPzm7E2LG5*mF-q3;zv-s;JajO)dGDV44uQ=jJDk3{}L{|?BgV3rV>=Pn_$h4jbJQ{}Xr zeZi#rWu6xxRgf{k7M8bJ;)4^aWy~fnwNJ2(k(m(ofl$wQJqy&o}pi=##+Z`#Xm+~oOA5kiKsg$1}NkWnuq{M!wVj5Y-LuBTJ zEtfQXNA2N3Zz- zPuxp+n(8Xk{)F5iq+3Y;=&v}&40b*v+e1sN8Zl;Y-Aa-gjh4?NLWYIN7Cr~Dg9*=4 zjN3vxm?Xr#17!yhfWNhIY9Dk(?M+b?GgfUIM?0dn4Oi5*;fmTeTv6MGD{9+tMQt0d zsCDX!TBok4b?S;*r>>}V>PTD_mf?;07&}zI;+C@<>&Q!xjpew1>d0Fx8IEj#e2wNvM?&6$oW6^E2svXH>4oI%B0oaTb>t-E`2#Y= zk;@=7N}Ruo#J-L<0y%O!V)lnzxH=c*A>Bp=iPmf?r~fZO!f%PC9VvH zJVHmbD}xzQw97D$q9e#v!Kp&Zw5wSPgR_O;m#vTt)P8laorR7Lr$DX=4zMgi%nA5? z-0Yy%LhY&3UWCNog1fiDTo&3^=@(%|!5)^caMfrd7~11%f~_oSk6RKPU{U+~1Hq)#ZYkPx9}MQPsJ(Jou&rIjkkDS) z5KMSOw(u3S@KMOaLEoEF-hn&|c_diE(gUF_sxer_G5~2qOk*&xGQKQz2!3pol7&U6O22CL~S{gFPyn$cP1nW6Qjbp2V9V}`bdo4J? zqQ9&G8AG50_gBIb)= z;zlWtLmq>C70hON1M&i7W3Ywge-JvC`8qhTNtXHzXU8Oc!4a0x*GTy`=Kug$?EmhZde6CIm_6Qqdqf;Uk6=3uT6xpvk<`h&GX+~eSuU=xd4I|IQs z7Im&S5F8XDzq$DV@(cvEZ>WX+G3)Ro#(E*yLh7{n@8XGuSvXe-7O@(+r9--s%Z^8I)#WEC2 zBkyjU(Fc<}QUKW=ER`abGwlU`1}jzKrHE!%-#16>52T(ix`hHI%Ur3#H8^+zy z*Wj3H%=X9tmSLRH%|gHHCmCgQ|44c2w6h^pYP8Yo5sDdOWDm-^rXl86NW2lhP0CG> zWvG20Bjsl)^C6#~g0DFk1uRu82}TvmQkHSX0Lw}UW!}%2H6-(_VM#QaN2F|o(6^os zG&-a>M~8!qfWd?Z3C-58MKn}L{INYEH!ndiue%;T$uTqt6+9aNXxHu8le2ywR` z!^jrmUfDK`Tp{k2ZNn&MQCGGNql!gc**1(?mgT6Ou55>ll`Jng(#i6PBfTs~V~moe zhFLCgB>o@j#S-ntd6-{cOqp|8Qt@00Jvp3eRI;e+*tXHZqOM~fZ6xfJdDM06bR+R! zDe5}*2}UN1x{iIKF;9ql9ea|oQiyvUd$Q3k#J!F^+31lm&UNfli~*0(b?hv|r^TaR z?se?b43kA&$3ET2Wl`6$vyFC-&~@xH3}1}QqpoA0Y2M$E=|&UFlaSAZw6LiAdNYhR6@w!z9noeOohzx1h`YoJj4B!9EU^NkmPIYG0;7?|kM`b- z_RKVzSxiWYkXDvf^nC&3GNYZP6S7c97t02Yx!hRCvV~(VH+otAf>fi_EMqfE+_iXC zM92_}nj>9dXnV=^o`4uy7grcQmj6I#U0h)#N^!2QUTI`|^rn&&NW(D)OI-6XlGG#$hk(R6w$BuQP*6<98GoM85s0^BjiRSljXI&F@uBLWaO~W zF?R=~*k~3~qn(aYdv@RrAx6>|%2T6V2-zEQo3VLsnfXdcBBaDm6Ffuk4K){jU*uz;_G9yoaY<55IHM23Nd#WjT}Qy(^-&GqeF;X&Zk1| zG$!mrncbQ20>flcbCPl+lSR#h%Z(BtvOTm{mK#+<@QlZuxO#`ocNy(mO0DGzV~9m% zzT1f1SJtjF-)+PTahJy3MvIUN-#jeC^O5;(qm5;OBOSYysvRemv-SXDXdk@WSSh4N zYlK`Z^7IOky_gMIWb6>KMD*eg$h}6^ew1g4m}}KSs*Nfk_*Me)JOrsRI$5r|0Bt_;qlATK=C7gDLq<8v zB1ngjY8D!Q-i0(6^(-|0d?2Jrh`Z-5H-?1BZDRvsmK%AAa_y)!_^?sPqSoLeMvD+R z%l!(a9x>WkXqHQ|!sud2yb@QEA&(hp2T*2t-n0$UWDE$Y)84}p`yKMQ(R2{S)M>Qu zXm8;;MWZlD3dNM)jJLiRJA}9+_ESde!4%_eKTjEnLcB349vOwqPZ_31_JurS%#z}) zi|35lENWdmXB4xnM|%#z`18C`%CeaRd7d|_Sbm30gtQp7EWX)snqNqx5O-a?U`#!P zdQqW`M+}`?y2E5c7sH%t9?B=`dmsrGC})jMw$?OT?=id_Pk@{OL5LGI*kD#?ku;{2puMtyz{HfHAWhX`c>u{BSVVVyXnc0 zHO5pS?vj7kXl7CSPM6WgqDH?iqv~*3m->}omr={2e&yF?G)Qsk>M~k|xOKf}_$KVS zmftrrSyWx`8-qgJr%>KEQYKO<_jiBmjC>X~dtYy?WKpyC&y1EMWGOX!|H9~HQM322 zjD-JCj5~YZXk-g8GKG<*NnXz~a>g|gY`W>K^E?~Oqg zHGA(j@=~Zh?(hD#7)>6bXY2=zVUN)4{RboKNLfnF-hVVkgt)W!pN#CIEeA0X z$RVNGLY8RLA?c9up^6 zYLPM8FDUg7VonOhS|X3;nTn48;p^kIQF;Qao}p@|+dQ z@W|Pab3?UKe4CKxNu0-B5NcrA4xuB_1)(OE__<0}vP^(niaZyDT3L>TTp^^L<#Y%= zdwD^qQ;Jy5DOn`F9CHb0z9_U=NSW^@2%R@w6dK}~Dvrqujc`l@$K-{uDDby7ZYAVe z)IKc~8x>oEsOyZ0B*{{oHLYDO``JpCNisj-^AIq;S zmxPkjsP+Zg9z}85?Z|U!D2-)*mV!`@5O;me3{4f%AESCPGn6l6iEm2`&IyobW~g|# zQl%bQBx0(i_>Mqb4?r$+mXF$ZTnQtF_MOX}7`5-ra^!5pJdG<T+cbB;h}JgQY4pb~EKc%+-#Jx?ajPj-*0ptGiZ7+*Al{366}N2YCf~W~-R753pS4 z$STO&h?(QaCoFRv*#`L#F-4BV-yr3BC2^^cFA;NtBiWFxka><=3W@8)wFyTSLiU9e zJMuhaBILGEJ+Gbkd-3$mtXQoy)F`CPci#bWL@Euf?gY2nx#26c zMNBoz6OfOE)U$L#&P$x9GPDUr5sDWDD$b=*Q!v6<$jc+xz?&s z8jG4)tqNrb=^w3*4y!`NEb8d+TBwyp9UWGOhFH|mp*<8zr`r2RtE0o~p+XjQba*4w zBt(vEVYK5aF?}R!yMjd6=gp!XJd1BO2=G{<- zkTUHt^!)_X^}kT!2^6z7Mjd6|3l+1dqs#}PP9bGl7fPLvQlEr|h16*jGaa%%l$$|$ zYP3y=nFIMO)XK60axdh|P|}Gq=D=di@F8D^npuv8bU?ld#h)Z&&Vqan`94%A#kmeJ z5ULd-m)K8;83?VE;#0@CA49DyS0m3Ki1{(p&N3fDGmsxcot``~w~?R=_*)zI2x4N^ z;&}i^UV|I}`8l*%l#)-q`XRrCv`N(F{uov2w@_846jkbv(2$TCjbc(!>hI9t$rR(x z*>;A8g}B@5&d?4PwXN<9#ZIPDWj?j7?hM5XsqlS?%*P}1&QK!D?~qI($t;PtVowpG zvqT|hL;ek!EEht~7m~qpD`YxEOU+_=5K;rljZMvzBKF*?kU1_j$0IjDe5q|7nGcCi?efTdkc8C2ESdRbw2;oJ4@gZqRV*>> z3kY5NIv}-BNQJg1z6J0IN*$Ei;*oa9VX1u{>4hAXT6mhsEK226FWBqO!RBbPz4Qb#;;7v!we%4}IG@8=lp8OZsmJ6N`(&2K>RQuQ-r49RB5rKv3} z5 z%(Ey@nYLgBmJOsTwTk5o#GDAZH#ISbVsHfyG6(WN>O3K3+JhWZml}JvY)?DnCd53P zTF9~qat~xhYNwDIE$+5Btr7BAYEQ1ry!uf*V+DC8HGPVd35eMOc`h~SJeir6kMgUT+X(%-n@>Xhrlwu!#vJrM#=6vImJd+Mg!HE-UL<4wfSd*SF||}k znKrQmzh8!IOC4qjLhgY4mfD*qOVKELA7p!K?ldXKA?Ba}Mw`?EmMM^@5wkP3$Rn)~ zEnF(3Oq+|C)sUF*4weOw&mp72MbjyBowgJ*1c?v#3Mtc0Pmpzu3un%dG3|)is~dNw z!}%VW2stF&=#dP_gm7ZMTWSX6sBoo_8toI#6AJGTQl^c52wN^<>~PA(veY1A7DLj) zg)IL-8X(7nn}n2U3Ae{-k3o(NcM7TS=@2?^IyT%bV|*D9x{`BjxQAnMAy1)Hdbp2c zu7x}=WRPR%d+Ric9v2?w82a8ijiSeewM(dlcyB7+aYu8%IIF)50k%>KE42!XcK#`KnZ!kTTzY zAat%bEu1N1#5g|%bxjYqdgOA*#o_qNMD3c5Qkf`qX*iE13vw-DE)6#c>5nP=b5Ai3 zzck#=G5ddnIiyZ8MVC{aGVOenx*4Sk!mUE&F_*5G%?x*XWZoUPz7gKwk=s#fRyb{z zTpC};$$niGp3O4oNC!*oUb3fGg*US#LueTmhJ9DaJRy!L42Oi^nRdt`)OB^ZRK|$q zLuW46hO33F)jm8XMq7-2%?|gl{Jk{>@87}`KjD-sXo7>%BgoF7hNc@CNB*nLNM0?S*F&B%O5IK;An zV@ktmEZ=cVX*iSRR|xHuW#Md=z3-5FWm$MCOA3T$gLj7WS&oCyZ1B!-AQ{^uaDgw?q)dzLUmP!H?RaDR99uVkL6Na38dexFAOJKO>3u4J03CP zP}jn6ibwpAd&6nh$e1e-b26kR+|IHHG8IxAF1c35JPElD@?dzKkQ!|bqzbYuoI9Ih zaHarhfHZ_Fuah#a3}5?#JQAKdN9H*K@)6{*@BoX=@_0CNu8cVovIQ|uhD%tkg8U76 zD!h`Vl4G6;53oE68HI>x=2;Dy2zfr-#_}=ec_CbKy^PrmISw%|g_CX|snPy{ zOohA>?iNy~9dIX()R0x-v>Pc#-gPa6ycQl3QsGNM44rqc4v(-*f@BHt&7)HI9yDae zT-Yu_L zEL#pj-x2duxXV-OVqsBV zOOA}NeC|lX9kLh8K9$e7ZVT%yTQFYAHfOTzeV1xs9*gNn5lf*Xl`K_`G_t(oNE^#f zj;v!ztWb3guuO79E0t|lUl`vOPGV7C7~d8)S=9H$w}rD=)c3@#J7cqSk(8#w}pLWvKQ)m;@iR#SZ;87 zn$A-1NG{9Ejuf(NaHN#wH%IDO_Pbjx)fN`hkuH|Wj`Xpp?|^R$kFZ?f#3bA)dr{$t z&a%vrOqSJ-xx$ZyhOS`O}eVmi-s17B;g)9qC}1;z$q64UP=4JmiS4T=wF1 zM<%fJI+D)vwi{(~F`dF4bGQzUT zk%YT=FK|R>iM>a)Fq0+4kvx{u94TTca-@=_){#b*R~>0%+2F`JmOmXCU^%QxwMVOv zZO(8ciRCg!OqKX-aTO~(GzjiBt)3uam??I)v0|g*by; zjq|~nh*m|J>$C!7PDEXMM06obwEN$R)dq|4`>#m55I0Y3WY)d1J-46~y-hSWGFyu1 z7rjk1Hc~4@o+;4za$F?mK2b`$1EuJ^ATClUq)w}X9E#faip&$@*5!+o%2FaTJ^Ss8 zR0)yKaHgQt=tv6-Jp~tr>>cU!l%m?>BRxXuv=yl9XvD-v67H9MCz%AYhz55qMjbWF>7PSL~!LBc@A{sK*++= z@a~#O2e;={wC8ff91=;ZmM!dp&>Nx0M+!Z14PufbgC4maa%d#KhDz0FpQ6;mxc+l^ zq>^P5=a~@cVA;YlF)|_q?_Pu4iabX|8W+pjcS7!jq(o*fA(4Gw1UV{FCB?UYO`LcK zMIchk5^|)0<#b3BN(CZKESEs&*A{`uN-1J3KQCgmTG@;15VHzmL<(7Mg}e<3McP@a zAn!v`BO^j&3pYT*k=zH|Hq-htBP~*V%TVfT#F&vbmX{zu3h89&hWr7sBHb+CK=%BE zq=#iYBmrVa`dIc|jOXHn46>;A=|v;MEb4AoG_pfTe+*p>`xw{0q7ki*+T0)0g;DD( z$T5*vDWeU@3y@&OTmgAQh|W?9=@gPCC8ixh zd)#r6bdKqTe2bXlBbh8eK?a26u+aGPGvtKGRF*Ng2SisdPlyxz)*8U>OsV*1LM* zZb~Fi#`vB=W?HA`MGAzJ`(B68{fqM=v!#epg7)R}BlEb_|1`PY&ySR_{N_lx6j6$v zbv!@PCq$0(Gtj~dBFPU?zuc$yFLYY?5o*5-F|K?IDS=Fn)Bdshd&)i7Ta>`Sq{fgRGAi4ZqWairWM z%U9s}x=51{H}kELgoi0le~ij}d!$@QjkfoaIBh*L-x=xi$QH<5k&H*ERGD@VV%|jN zO0|5(Mj$kgTBIcIG)NqNN9T$f+3s;-)Yx*LBWkR>U&&ZC)>S*A#yVHjSXbl3sIkr! zHP*SJ#yVHj$hO$YqeeDY)X28PiBTill1PqNJ{7(h=sR7roq-CuFU* z9kVYQS09K}vK;sGo1DKb&G7jI^*E%u*leWcio#ERFQAggMW$$RJA!mwG6o zHPRZC&v+b!wbKys3F#kw9ol@Tka(6d$bW<+uq=iIge0;&35f_vVp;GHo)-|3%(4nG zCkUCq@;-#F;WtE5q{RFHIa$PnSjN=i9tY&%NE*vIzv3H4kj6+ROVTGX+U>~mXe5gz z1*L95%wv%pmg6Bwsdz3fQpj=^gr1FWiWIS21)=M-k4K7GiXmml^F*YSWiez4WM!mM zh`YzV5UCd8&R1WEG{_iV*%k7)OD{y4SXz*W*69n879nN6Rgfo<`Nc>Z%g2ysg>=eN zV*8;|FGV(Zq!lr(kU z=uY|@k*Pu|v>#AA-8X+LQsfc3xBhly)?-wvLi>YbIwPGddp#gat%>YlQTOQAMhco_ zslyTTF>3!`B=K=6C$PL1DPqZEc|X#{aw|)BB>o9mO5KtFAW|fxOk0GQ9%NqU?3K$Q zw2gi2NZW3r?%BC99eA#i#xYmc?ItUClm6XAJ+bNLQBQ0-5;p~R3@4(6pQskboeZJ1 zyup!ku!WO+>c}R&P^& z{cA^PkE7qVZ*t@|2be}#@5Jm4nGX5Ek>5~?8mPn;TmwmPE5pWl>>Rio&Lk~lSHx}wI+znmB~((Z6XjfXoOQRCsikxIG8 z`P6J#Gdo$-Y&pi%o)mkfGh5!n%wSQoyj8@sJxz7FWADCZmyil=?>admC73-dYQIP@2ZYpUDTvvQ zJPBsPGnA)Bo5V8COc%0NJf}xv_&?RhBm4`L2Bbs_j&+bF(zV0N&4 zax9McPvZDw`ktjy{V{6JmTcw=kp zb+oe0K_PMm^2O~`s`h!g-qrF+Gqo1p68QL@ZWg{EMJ=Bb%wi!G+BK+)T9{#0vD^Wn zD+3v3!b?=DOsj#=mXKkVurxwWMc*^b)Q zvd!3+sm*m-6mpx8ES9q%cS6oE^I53WLdcnBB@1Py{q!ueg=H3E79%Fd+`+OC(gZo% z%zj1IMe-u#9J83^A;>Dox#nh;w;*dFxn@qAEVcPiEQu93gEkvk#$Skkhn#QvR*5C= zEawZ%0U>o-AM#jOgBO^g*JPfoBwSDM;~dt^WSIzg@lsriH1k+!PO=`gPd6JqvI%mD z*)F6;YeOlL%gprEqIRtl@-yU0vq?yuM(gVjNTJ#45&Bl>wPvqJ^cV1ymzmTqTe!6z zqXcA*IhBQu@9`)#*DUbJL695F^4DdlRIdGIGv^H{>5%^*rr6AUOUnB1Vl*3ao7v6s zH|m-Mx!s)bwv3@I>O#mJ=2Vt*k%yMpon|4+Q>WspEMm&dK9*Y{vmtkzDeuTUG)o~_ zWY)9L3}jId&l;I}C&et$NNz&R{br_+8m$j`=01<7kIXzFb=oG>PHSh0*}|pnMGP(H z2h2e(^%LX{HMnB`^z z$K-O%ahGqx7(Gn|o`dGE>Eugk$52FOch zt&k6L9<*G`m=6{Lwzgvqomp#}reiU5*$U6NXIPBN50iW;zS)-N!+G zGYeQAz7X%|fc$Q@3aQbagiy?OvybH^$eEB4bBN^~j``E9{)B7ag!7?0@tnEY%Q9m| zoR;wt?$DX~dKq&gI+wg(O(rEYBn63CR9dVULVyhpdJiXr+H4Ywv=rfh1Yg zEVrYl-=n98SVKZ;v~M9}7hry9XqTLdeg^lVX*AB}>^F z-qQd%(yC^uN6dD}QC3o~jQIlX*?{)=ts<5mAYVfQR+W%C?R1nn*Ty{{YlBDTVGV|? z5s%ysNwtzU%AQiG|Dmpkl_dmk28MhJv8)1)NrLQ)tv(J^jwv#F_FbFC5?<5RuJwaQskPjjs*A)PTFqZC~S$hB%^jC0K@*XrY#VeZ!yYk*@^ zzou9j-^doKew}C43#rf!eke{m13kUS8sr#trZC;g{Z_0CErJ+&Z|cR?EFopuNswtM zb&1u(avJ1PAt~QcDLi!#xkgAa%S@I_t!|c^AvYqX!0HpSR#WS1rZphM-4bS6LqgoQ z;>@)4%~Y5BR-7xX0v7dFoU5%C7WGz~*;ecKvXpu&&RnaHW$8yT+91}>^;Tj(#kg<9 zxzWlI;=UEP0)G&T1SYaj%&_Xsr|?`$d@_v^qTU4oW>} zjR=w7F!}(p)Y5*Dnd!}4>xERaP+c1#4_Vp2QcRh)0!yB1Z?N)&xOFYJ3O%wJG0UxD zkNgCA*s2#IYyTDUh}9@W_H;zZ4i>6?j~>i~t=!*a3+YHb8uF;sBBa7sKP>MKG+Avd z-58%~6m7CPSU$U3#dNW3huj~fm~|{kl}dV~h`IXysH;gFWxUU&JjEiPOF0TL&p1NQ zr9>dlIYQ5+oCJBvN*bnK$R)P_<(T(c8B%=ozEipa{IXRjMLY0t9AQxE6_JOo+q{e= zf8H*#7E%Ct)rr{znFDEaWEfHkS>?!{4VeExR_`K@K-wJ{hnN<~8;(qXya9RBktpOn zNQWczMO|9*YaF3(cF=S0u1rR$UFAYZKk~RS^rQ;ixpsxFEYa7RT-iP5|0nY$zu|YT z_1-*mra)uMTBpqsHd9YsnFkp{FB};=pJTdJsj>G$Mty;E z5Jw)~E#@i6lc>v$d1W{0bV?O_wy4)}hpyWy_Xs_O@_|(?WNi$cY0^0Mq1EV#p>x;| zt!|G{o{y{^k9^yNyF!-sySw#L=1;5%Le|Ep^}gPk&7#)(daIp9t)2DO2#Z?p>n-1Q z%Dgsa9r{J1`g$vg7Rs+j;$SpHS z^byeuUkE~1=QdcTkhL*ti`rmi3BlJl5JNNJ4OX)d{L%zpccAEt8c9ki#l3;YZVA_`}M6gTZkOT zGLZROtJou1kj++uN6v%vTisH8^p0QJ4u7yV2$3zEiI^X(%|hH3{%FPjvug`~vXWU; z3xBfOg}5#J$?6v3w(uuwSc>yjuC3M%7WGEftyb(#xxO;6-f27BY9$JhEu{OoTdfo! zYsDO(5N+OOrEv^>OM;G8LspiI(bStTf3b>K)C}ZTt5isxc-uT32Y;-)T;#Vu>U)e(SCb3~F#G=0VFl?m>ahKt+l_f;JDf9-k`FCp;3%!SvFTZ?@A>jK35W#tQTm;B#W_imCBL#1RdXzTsk%Jj%0svP~9x zr!KW;l%2tH7S=n(`0Ol}^C1-DvvXNyaLi~skL5~^8EwyExt?Rj*t1zmIA)ApEW}+q zd)t*l~a?M zJF;<(vccho)BS(f=esm;0mfF0;x5vXOa|+9N zM>2#|_=@x86VdzGSuEv{y|6C!vvZ_~@sQryx*sMBa%reNF41mdQCsf;_7IEOzYee) z;^ek*9BQYhuMe=BSXOae2iPm6ICULhcM5UW;6ZlMUc0V~Bs-Ty)sN?C$7%h9M>N?DB7gFIHh0L@J53@U2W^!GJ+1*l{x(>59a~`$6 zCfMm?c3p-O?IIRc*F-ycZ`m)kZA`RNSSq=$iFQbeQ`ba0ONiUA|Jd~`s$VJgIu=z| zid_=FYh5XJIm@G5SBhOF#i=XBZW7|wb(B5AqU!S7=Ga}Aq2KNmQsGnEq2J!jG7VcM zZS#J6P>NHR-;Ui!=26QqVCS)@x`K8!i|SX<&e&J>OTDQ$XlJpgHx&o%94Ss+L3@@E zw_hQD_i1gguuCX;G!U~#@PV#kk@G3uJk(RMzIx+aru7wjiv)HRtC z>?#&@P3A_0VnJjy%N9dZ& zX?6*Vx+ZhF-N2%*$z?|o_+n^)kMfOyWv>_(XE)wFtEquCN%c8cH>GnDnwe?Q7`-I42Tqp9( zu(Ojydo=o%#QTtZ8_mPt+PKfpkjHLUO3^~<`^8So{g9RU|5xbQr%QKSJ;UMQw#fJRP9&VnJlXIEA81Hq1p@WdXG@;*Vvsb zs`lCTkdO*-HI?QhbL`kdQxY4Ld44$EhbohhVDYeUT4xI;JBE@1fp zLcN%4&l7@oyg+(TYOdYtkxh^yd)Olbkn8Q7@l<=Awhg6zf!tvCu~42rA@l6?WH)Ay zFY)yayYx_!8clm7PK$@!V&@ztB?)pMQ0dkvdvYY`4LT?zGpjybURWEU<@IHbLe?%I(-As8oe>rf`=X z&q8M|)rh&vPL$&81$Wsg9%(>KguLy+_HOs{Q=|dyqwq z33YawU&g4O*4f2EDtzfnagKsKb#^IBGWxaWS0t4z0m!~WYNa@P?t^x-N0Jfqpxx#X zKcwF7@`w#tYMVN>NB;6I3-XX%DMYTFt}Ahc(~b>LjO^)j#4NWr1W78iW6&PDj`WD# zC!|cvf?SE1MtcWK9>=V(6Af8v4rCIpCp>EB2`SS`AahZw$u8xX#gLmJkJ||$(L(KM z$eoZU?N%Xj?c5J(wsTV{#@*_kvI|($l7HH65aOQ4J#DuNDbwCVW@_`(b{ESQZqGCJ z29}*%`%1e{h`aARYY(ufs~OMQLsG=rp=%e<+P<*tJB{;osOxzyCT{IUMrFE^<8Ntz9G^vSt^#3DRY65L=6TU839W<1I>EVferv{y#Av*jhwvt77Y= z>k=Q@@gAWW(K9ulRRJn{tO zGrP|tG=_g}cbjr=iXMaI6UNm-yVoPnqSROR4wlS=u&qKi+AS89>L0Dv@+P~JMXlvc zb`OhM%bV;$7PXc)*~3EQQhgJdH`(2`$n3n0?K?X?Dn)Gx-`TY+YD@UuPDqn6YD?H+ zhgj5>Fkq((Dbv2gmhe96`oW&ZG7R}xNHt6BV{-oUgWW8oPD_M*E@JefsjfQBfP4-4 z(Ju7J_mH3LMwV>E3_=F&*kh=ayZsE>i5~eCFQ5{}eeQ{b8rE%yvp;vE1UwRF*p&nZ;7?m2zQm?DZ3<#`+B!=ZLidt(+9pdT zgtm>Hc7_nSMUnh#=X&HKHZ(lZ=>MTd40Q$L%Jk?Iw2l|E((tcZzq-d~#}>_8Zo+Tl-bW^Up5wC6+wRcHEd> zzLsCmcI6Um38TLD#{7&@<93mSqvdFWQd#a+H(tcVd*ld6Fgi<$_^pQt2}NhKbd}-X zFDIGDqCGB0tx&W?igq{dQ`0CBidK5$ONGea|_RiPGwO&Ju{lmqI!B}v`~sz&NL>R6)l$HwCAj7 zsSx*k@T}3@Wh<|XoM`;H6w^Oi?O$g{6NQxd z_QI$|UtKvnI)P)i+tW6q0qNYR4WR%v~mAMNH+YFwQf z9pIRwIc92fm}AsPdqFfIS1d#A6ppzdnk=M$v>JOajHa`wvG>AgrYz+<1MQ)s@P*MF zj!~oWMbW7ob0x=I6fNKwH9qG>3pwWb59L;u7oEp3YQ&xvE#a6_nVHcE95W5aox2e;GaBL;b=lboX-p`L773}-Cj5=xC?KXV+AE|+ONDHKTpewlN~LPF;~_(kYoa}QQgR^w zLavJ@PnU8rBp%Op&xvjjQm5^>0l%$;%#F^PA!Ftu=19o((fE8R|3eFH$PLjhmSz8s zth|T<2WpIyVtAu@FKCArotaY_V*$lF%r%mT6nFkgZuJ zTP8M>$+QWfS+iKXL7Nal?Du+qKJRm`i(mijvFGFUxjXmw7c%EV9`u&qEHld?DUdvG z%PmsAMyVSh4|&^e72+P5PxlT8!Pl#>N4qf&U%K{&W>Tg`?BlF>T%-0Tv2-G{buOOE z_2#jB1EKFnJ?t$L64*jM;_VkAkKXBC(j(p>DIVHJ(Q~IWy(2=3JhY9X%uKK6Hre_i z|6nOVp9{QELMoLLSRVEEvh2gS_Mktrys5X#k&b!^XQPnW-aeMAQ0i{fQ{+v$LuPJ* z(?{h zE+MO;g#M2&N$ofO7Addob~isuX3m8p~qQIpY?{ZoPeo8 zN1j#Q2o@DW{i*Us3)wx^*%wrMV>#pO3#z>;3+)R+@54J{URy|!hxP>zV`|LvCUb`N z1(d1rrgDb%1(d1rW(e6m*4gsU_hxaa1Y{=Yxc=m&gEb!KI#@QDv@HTPA*&aUcZQ+cwJ$&A~o-@w=qR!jF8E1b{=k4Z< zv%gsA?d6QKzgXzq%Nb{XvB*2f8E1d7$UDLrXMeHS8#+bKQ)hp%*c&cn_gH6NQ16Z8 zjI%GO_r|bH#$2W&q9xuqmK!0oPg&yiv(Wy6=GP0}L?J~U+F#K8dcm8*nQw8-PtOf5 z^`>#=*+hJ?LrA8O-D92oSA#d3OF8?m25%kA0Q;Y4oeWX)3`TOixLz0*mGl$nqnkUno=zLa?o+9rJKZ50yOQ-AC26e9O=bcFw{w|63DZ9MU4^z#h9{PsK9<;T`8_VLnv9iY-FU38#+T%@>;+|XW@g@h!kD{J* zA@a$+fhX{rW!`KdMV=;%;x}Zz^X9U=4WUxsc?)Dco^0%iXr$kJOIT(=XzTaAw}NFZ zUlsYmTgy_%N8LYodxh*C>s+%M@bCrCjrXcbGHIRjhqp_?1pPPlP zo41{%5AUDSGWL_Vo8=CyH}wAL&)(<)S&#F!>95{&7U$j4-@WS}rA*+x(LcOlv!pn0 ziT>qnVR7CP{l}X+TV|YhOGDH$7U#XugVgjwnQ`9J9IZABsZmDXfH(c|ocY0OW)Wp- zlo*Jz3RiB`9+qd$lxtj=I?PfJp|2B+Q`I@5X7?!fFg00-eBYDa?mSG*2@-my{cyEH zmU7?aJVI?0QtENOv3aE0%#yJc*Br1PJW_3C$%ULEq@AU9KDI4W@y@K;$#TeI9P?wJ zdz9M4vIR@;*^mgek7X~!D`bFWd_ArzL5^03SP~$KLX=_}X{l$_EAmM17}dja9E6Va zj!`3|h&65mYs#@|4rk)IRFs;>VnOJ4G@{f3mMbBLe?wBj@_o6y#u%lRv(UGKWvObG zTTzOBeIrV(XUT!kwcHcbMwSv+nx%*y9*wq6P}_s#49KbKkPw`I;q6yCv1Y2ztuorZ%9>*_)L2}eOmR88^kO$PrXJm$?1oE(2E~HZV z7((CZn5{-v$c#~dcdn5sQp<%@DkN(mrRp%tc9i-8@|2oWNu?^4KOjFqD%Cn6au546 zM)y`*SssBM}Ud&UYt4KmCo7P8#O@}gS7GO`Kp>!aqERJ(@S3fv=WR6X-a0{78Ys9{0^ z_rG3MBUqgKUn|up7U%v~vl_$V-2Ymo#tA9)%s_wW?6^f$rHH9YPb9Xexk4UuzqPnp z&6o9rJdIMHe1>CDwTz|h5GiZa23e2t5@%Y~UX~A7-c$#$vbMABWNogQ+1tMC#1x)2Vy|hs|_rFLmqpJ zq?zTYCfvP6=3TXw+Q^R@ttW%9*ao(8gRO48jH|9E3o5guEu~SW9ahC8-HABkSOE9h!jH^>E zSr-Xz_kHnTWyl76hNXK~&n{Y32w5_*&LQ?-x9d6TqT9b|Fd zB>h}f7Ra$XZ<2nYMhFS~a^E(dV(p`y`^($Z7%t^J4b`ivEY8zVy=vm~vS#OLs9rUd z#d#X4SIuN`o^UbSMOtl4>@tXHjPah@pKt~RqcPn3PFu4i$c zDBGcSu{ckZ^{IU<&J$(dsDmud6J_73$|BkN2V>+{;&-aKi>0JvE?sd0fw4wO2@q@;Zd- z`A!{Tc^5JV^1WJCPd%(wzJSnPW8P{5}Bc;H}+0EC!=-=*%RwTEjdC(-u0>mas7WtSe`*vu zB6<}vqSgzM+k_2}18P%{bV5S3wjlWu5~_6uNk3$)7P^#b4*a_4;aV7r53L_R4M*l$ z1j}U*n(~Kh(JaoH=HXf_%g!h#6VGy+TguNe{sbqJC?s(Hb-0!<1b1CgY6v|%TwBlb z7~~(wky=uNoEq~XhxX$bTPqY2IQBbE8x|s`4?Wj(oaT8^j8yDpB2emhEip*w&UKWQ z5+rn=`UEW_NKQnlle88oinBdDMT>b!)U42NQ=W;;DO!Axc!fl~5@?Y zSbpk+leHcp zfp;e-YhiCtp98j4H%X(Lu zSa!P7&Jx<}wBE}y!IdGF%UuauEyp#(l^B*KuGlPFTuEawR?1)QOVM&z#=PdVUcz#U zD|IZ%uC%Z`;7TV;xhwrFD_j|2>2W1ujT}YPDyQ{0mdUOpvE1WI2Fv5Fg^ zoZ(6n%SEoVv)tlJFUu@fhFF%m685GX#cEe#SUz*bX8FmLG?uV8oF3+|M7vVL67NbK z%cZWguw=T@$x`Y{Kg$cQjIeBQC1R}{#U5AUSWa2(^e~C#I#)7S9&ja(rPh^lmKIkU zSUz>7jU~CoS)009zHu`HEY3UEDO%`Tvd^Q|I6aJFao)Mc_J`$EHPj!mXRZvf{OU?rn;b<{t22ri7Q+>rWyV!bCXMB4 zHCsX&D61!yVAn4(v?n@PFMO_es^Vr<@7h5{zSYjM{$iSaV)c3Nn&Ym zC4;5il{}U~SISu?t#Ia71Ir<6oz~k}&T*xiPmb;>|eBw$9 z%aAKsEXTg(^rwI&$(3rBX|6P}taPQFWt%I#EJ~YG^AO9au7s_VqeyclhUE!YY?d{y zq_KSAN)F4Ix1F|1SmIr&W4XeW7M3DcI$2h@($BKll@XSZcbv8&*2__x>q;EUy{;s& z)Vq?wvfY(DmOou7XE|z}(^dnE^VNkEt&Jtl&2+P*xH7=-5KF ziFGB7~tlLCGq*u6gX;Ml^`vUC z?^9bf%HzoFfLyI*Z48LA3r|sMt&wF7UYXvgnDS;@s`!-!G<4g@_W@r_hd5tqOv^vgoawcDE z;LL8$`J*dT%oGH`#IWv_rW!fNTWSoSp~UKNa1F&?uab_UArjP%A|O1J{NCfpyqO| zg5@3v)m*OCv8?6R=V}csUvTSlwPu!|IrFsE$};vt*~6!`c9v)eZIPbQI$10T-EDtH z>tVSLLj9@G`dIGddMdO5mORc>YC|lKbEZ;Lwuq5>7C`7(*=IEm%L)iREBmY#A;p~< zRa%sg8f85)cVdcFX>meue;G0rQmy5)d8qoN<=t#abnf2hrzxEwPKH7`_{i{uDr#XyKno`4CbJ zc|o&<$m_SgkWAEL(_SwmLjCc^CyJ1KTEZA&K%q+GYwiMXCfe!Y0z>wvph?# z%P(qqoN0m3`u?I;C`5io`U$l4lGY?du7j13SG1^4sn1gCAdtzKFyH)|~{Pa;FV z-?Uk4XQB1|E0o%z^?WJoiF*+5_@alOXm*d3c_{TQGM{McSr$NOZ}pkh#d12{iCBpz zQMhpH3nWY^<^J}}-&a%Um zK^Eux0NXXsS5$M6@-s5DN87GN3aL>-Hsd)xw7y;IWf>0{wHwc!Y8Bh4RE?rS#zA&! zT`cE8j)Cmb%C=LcN=b*D1lg^{eoa!TWJAt`?A4-oNGXAw3;9lKWodw%2l+uu?UR{z zA<2+^T0hHPNE+lPE$$oH)(|8U^1BxHtt@rOmeI;I$e&s@%juAO$Uj;Giw-G*D8BTa zveb=`xscJm9+nEo0>~l0oPL>E3t0*Y^R-KH_psx9T|!DdoyaUhW}L5wWiO;fNIy&H z$8yRa<{My%hS1V-m~WWH=FH(fWf!$x>bZ_Hhx@`IAw zux;`CdO4#)C}a5gIdeH@4BsGUrf|me4RfZ1Go~-}M;d#v=LJX+>al!bLW(>ckY2Q9 z`68u=Q9Ox^?Mo9PkD8W2F7lNRimB>OpUZspLNyOL#}k?kk9be7~~pP;vom^#q|weo2sr;tE@ zZt`^nwSGM6xyje`6OE$OlZ#UH1%pgq7t76+&v1H&7}Sa;LBC7b+FF2YrukNJypfAu{b~>mFa&kgS=mtDlgM zUsdx(uuMUJq(rf7L#bO)^Hg69%MXw{h0s*SbROr-ovFS!ktz0!>Bc#T$f!b!JmHZ0 zg(Px4$3yZVIlg3;Ga<8tqzREnr!-He`9_2U#+B>K`ISadtoTvNdph3q^EC*O%f@4< z=YC&{kli89lJI~p`Zqa!oF!qpuUbf@as%oqN2y1AJuJD9=OK^!B7Ub*GP4X)=!*%G zR!Fh0jfF~Wgp~TChGi*|&mm9v+J)fv5F!1La$ndVlqpi0St@)PLgXkG;i#b6S1m;e$Dr_38_&AP-^sd zIQ#Pr3X!cJ0eQ|B{};7Ys*E5*-)yY&rLc_oTpn*M@}&tWQX*Z+5>lz0458nlTjZ-3 z66jC8uUXdPIfa+HdS5F`97@r3(R$x{DPozALVxOgkt1@7IbVTT>Z=!0s+cH6*De}- z143$)Q?FB$Gf=9*m;Se$VkGB6Uh>8LBjqBLIuFw5OJYfdTnc%`*UB;#awDY4=Q$vz zhUgC+YrX1=l7e5Z!&e}XdDWL5B-0=(ePt|l$UF#n&DY7Y0+J6|7x*POWLEnIMv>tC zWk?-ljW6?{fGmZy`uc_7m<^d_kT-ovp_D0BZiTFZto22YmU17Y4f2++gryj=5%RXL z!Xq%$dgltA;ov)7NQ^;1x24Bu0vec>*=)L4nrQMe=Brtv2#WuW9sfj)V zz1oFR0kI*!K{mOWs~}^($6H#i+zB}n((THFkYgdAyD|rI3S=uwRW!*tkgr{-f}9W8 z>B@_c6v+3kya7pv?04lu$P~!0u55?oKz?`SZwU2p*p*29z}@|jKYV3kYE&uvFvTv2 z#aCT?%~Cwdmw2uTnSXq3LN+Dfc|zot_D$4t zwB99TcZgH-v3lGQRL|}Zr}g9XLKdg3?4$Dc zWuo+0mJg5-Z}jN#EMGyy8$G(s^341A65AsuK9d#v8ha``nF6KamtJ6W6_#_HWd z>MTp_a0_dzbu`-K#XKGQb*LR~pV zwtg?SexdGRc?u#k;X-8V`KaeYJywXEK1Glu-7iG8^#tT%y-Y};tx0-=5d7`{*E~sY zVp#!s7NsWXEka~lwL&_D$hMY1lJ#C8vaMGjm+EoH%2Cj9%PPoay2|3Tb(wCnIBi{~ zCkc^lwTe<%LgZ9!hg_~Vvd~oRf?TP$3X!e%KvH!zl3JG|r6u7iy+8~a5Y>sn`9H_kar3W#%-bgf=5N|k!L z&>znL-Wt;zS-#|OZKuD>( zJl~>6M^UL#_nh$-JywdSnZ9>%i(W58j^cXMbF1DgMAm#OBvX$%LDb{ce4Cyt#dFYB zx!t%;&u58*WTVt=dZ7?G4IdCvD@3+c2)RRV6e8OygJkJLLhv;k)Kdw$Q?EEtj^aXY z>n^>P#aS!w((8rDwrH)qOK%k-+gglz?$$em$hKaF+@nXHBx_Db&CQUhdNj)v$Z8?6 zLIPW@sd~K3xL+rjs^_yfNBB8B6PW;5hoy+p`HWhPpu zt<8OUrx1Cj`2{lf=@BPWsdi;9GIW%3zaAx|T6qaV=T`UY@j@z?W3JJ_@o|sx|kYcojv1|dYF)6T+^{jv_*25;ssj&;P7E-S#2`TkZ>mNXt z=qW5t>r3=BA+q&PL?%aw-16^)EY%xXXy5cBgv>0zTjB1?&>N=Hqv=$?40r$+f1qZo%$%iMh&eVvJpqXH7KO&(bV|^)!4l|C7E7Q%IV^$x6F58e-^@elhTz(PtTnt&M4@(ihQb9-5D|N*yGM=@_&>FW= z_pqFCxV(C>QV$m*Tfaio6E8%zl@3{@mvN~NP|t0U*Y%2^Qd5Q03z2KebV!Td98}LN z$Q!yxmHly!4_E8qEMK8!+V8B^BZbKRJdRSU^>`t&<_buwo+w22uom*BUMK5uk1p2g zks7tNJH)w;{gz%Kq*NI|TT4)Cou1>P4Bk?QG($G%;kuNVUcB1{Y1b9M6a%sevQbZG zxd!q%C&@>$WkMaPxX3> zGNtbK;(GLEDar#VMN_Ot@8C=kgywsXuGq5FJXicul;v)z49>JbXxZq|b67rcrJPIc zgdFxGW$Iaefef4&?Aoqt6`zS0LSkV59##vhS>zzWXlo*tH2tC}dM_)?)3C#CFJzhwa5|7MmWPZ|fSk8ygyTCu|;g?aV z8s#!b88W}mj$`3e1q+$ub2}fy}RZ)MP4Eg)evF3KirxJ(p!Mq#iP?x3ati zSq}MA?BfcRs%R5)c@^X@S7^R}2pR21wfM3}O*CD-{0cJ8mHUwS9umP)742NBjB=$I z8M?xGf-BEL=$hV1t}KSoHP2{&N(zl4u$MX6pDm=+^EvuV^YmnYE=#{F`7FPWPkPL)Wc$N1p61peu}^D zDw4py@^pVwkkIw9)BUYMLf6M){p~?Q*T>HCcS~`+${^mUEDy zc@gi=V@ZI-iBbhDmqBR#iuaeWTo0l3G~Qp%a+@pFEO$d_+Z*q%6C(GF_~}ZBat)0_ z?inwKnEr4fa?f}z#PYYM$xJ@#xdmeTm20KUhuj58@VBruLUJMJ`IE1cnfDnQ8!RMmC6H<_aK-0(^(#dbU`ljw+N|G3eUkY3uLlC?nc@AT*!9F z<^F1xMJV+HTNPp38{hP`b$_=vfS@)bL7wvk$J%1$?_303sLGpf7T<^&Aa( z%HJUbPX$A$o(g|Vw(KDZ)l=zj5>lybhn$X5RsOKMW#-_W_)->RzCTY$m2w&+0aEKP z6jCYVGRX6Ol0EcRzklMnMTi97q*6(POoMd$<7Z0AhR~N2zwj5b%z-=s+3If<5*Wo-{`EqN zl&6uQw!ZRr%8VzwN#2*)=I>_t5%0Lr^Ca8+y+QTVpwu@1u#nv$&bW5?Lknb|m!oDH z*G_+?kU;CZ{nLUxBZrGD~9JSwNg zv=ls>iOjG5R3RJ3Zb!}EKz{e9v+O9xS1^TS3Xwge{`~GQk)rHDsh=Q!_y<^qT}hiI zM(R0um;9BSKl~X&0wevypCcqNeg5#5$WqGjDD@X={@dR_TTZbA$ieuI?E!yMp_DX8 zG$hn$5`w1$*UD$8MjI_G^wt=SYqZhEG8?7NK&jD2I}06ApDm=5U@`o!8EJyElYHnhg;7SXNb9FAlXk(e|X4+YDUFl@0gv`b$B8+a9m9F$k5mW35 zk@3t4OtJZp6O95PHOhx5^*UsNQP0xN5@)ot{KzuV*voS09$6~h2rCZMOiSjuMx+!E z9Wj1@npGp3jfz zR4Nyso}CcGs3@TxRw-9NzJr)XKg*qvL5O8UJSH{K|(#e!6; z#elqT;uz8he@14`gDgPiE+d~M8S>>txF==ARmjX85NhilLk$vYYpM}dDKhSJaXCh^ zkW$Y>C^d8@jzx`BA@Z!`Pa&B?}U*BsK zN)cOtS5fLdqfwR;Lf6n9G+Kj%-V4YxI)X~AL8%!=VimPjrM!fiH$Y|@sX~f9Yaq0q z78sdQl=mQeGf3)KK7wpQsRE-hNIro)YILxCiVU3#&oV|>dR-Z+79&-LAarIo%SfLm zW$gE|^;t$E%MsIXWPzGz8LccwBSX_?w$aaWGKA`xZ49xT>q=pbtU1w@3YJs|T}PU2 zRL_^0ObDIT&NdoYvfa#}kRoLUdOU;GsffO29EDK!8WqE7p{1qJNM-rNl}wiHt~9g!2u;IcBZb8W`5l>JqeMuhk^uP!Qey1o%p?fSi^q-VI@wk##Pb_T=t40r zv1C$zo-)FO1ilURl#$5t^-zd%^jVZiW^uj^^^}pyayx3K@2->^*(|eNDP(!km0BV4 ziXydMZqy5@@oYj{hoh}>qnpKPz1-+!aau1o`dK=-=DEfQ%YZ8pi)f@Zo%Fl7H7+Wh4p-)Ldnx%8XlcwUI5P zNI4&8Y-gb6c}CfP>Zvg*{!>qlQU9NM<{O1C{JZs9qfAJk^;)A=X57}FGb)x+sY>N) zv`$xd7Z{x^cR?niKhGPX4U`G=aG?<+BrpvZ8F4}aZ7nivnQ_}%Y@~5LCt(`WNb8Nf z|J1X@DELo3ON{dW)boPT!1XMgI9kzgt#YX`#FE4{HyG$J{x*(w=KG6|jB}QjmyC!P z|2@TCGNOb8M)8snCo}FSUN(~cQ>xKO`A?}vBjZ1%UNQ276e%z8TwZ2W3#s(ni$0&< zfVWnSdRfX{o|hYqLIV9+ZnVmb+n*+*OGuIO2-m#A7!*<|+VWvKziNcNB<8Z))=DE% zNMH`GG@^wB+FEJE%Z%Grvym*MNa^IZUNf?V1lEF8MxHF?j$)NjAS6)pDx+Lx+?roE z8iZhffinPFHd>5!AvK=am_7-RH;hh}1+H`p3AFx(vG?VF&(k-IK_P+G-!PO$k#SpJ zZA1zwQhwrbtug#U0yVc9NwSnXu2v&ONTB9cBSU7~n%^{Xgao!5YmM^%)YE2E|EHcd zqv1dGylu2`J&8Ph-Z6TG1bVp6h)2nqCXouSH%+r#xnijX4ZV_tXOHFAX1 zc6F2R)FV1zECnz7%(?*&{gBtnYv=IMB!5%QkVv4S#{$}gxV6Y{=MzEVoWkGTE} z*=#hksE`*R9~;fhGIK5DRmdlX=QSz$kar-T8JR3~kS&nUjhIz3vkvkV;Co9fhD z&T^$I4J-w&w6VP6N;k`>tDJfUST0O+ByF%>QVVvAhg1A%jLe%WB9ake`fZmiHi6Lw+#|-lBSnl+Piz zLVh)>g;Xm0Aar!`yU{5`ZjtC{dD!Uwe=;P+L(}Jub(9%lIr0jeccSKDBeYHQS&W_L z&ae>?B#$EVhY=GbPeJ}N)F7D;`P)bek|u~^rUeQ8(om?G6(pOG8Dr)J$#%$Cvm{9N zLc+}IAQ^##o5NB(yZG0Zk1>_E<&ya)rq8`M;JeMHhh^MPat}>KnU(qL1;Q3XZqifHD3ksA#=Q$$Z{7XQAo0ojUh824R}NEcr%@aen}?lG!Qi5mWveWFYHgTTh{$dm*Qo-9m~z3nBSJdO5So&Gd8TJvTGJnJ*!;MX4c{ zA0bZ(QP#`0HsN@lj`>eDJuC&2;NI7{!gfiROS5(dSQ5&qOnBgQ!^uwSJD7D8+LkM!FT5bIfFx36Pya(*9HG zTr-<9&iZw(nae`kpB$`l=b8mU8JZVfvm~e3ND)1}R7hNh?BQ*wht|sT%>p5n z%43jJWG*m!Se|3K$c+C`mUdw&cyYq8IR-Yu9+sqy$+db_I6UHN_j93*B5Wb^1iu0lC+V{Y2J7rOt)iXXdkv=?YO2Ah~AT zr*fq9J8M@!?l%j9`11F5fJ>3(L==X1tJ6CG9he z8#2pGlHzX1W|=8cJQriBqpkWZGmXXhW!_n428;8{ytB+~7U%cBW}CSzX{d*ec4nIe zEYIRdkB*26%?2r=b(({P=6WHr&w1!!ks0+ljbfwXjBAb=$Ks5=*vu9Z*a8%rxkBWX z_L-=s*ld;=@y1*kq}23$A?FT#OJ^SB2{S!N7DApfYlT!QlzAC4*WAmJiE+IKdB#lq zlIp2c?uD#{RG76amp+K671D5p%B*KO@aI8F()IX0o7uuL6QyFV#WNLV8%r_d!|U+w zfZ4%P4q1nK=9yhA^B^`dHD-^Hz){?MvrkB|XE8GGp+ED@s2=KJv1d6;t(h)GOnJJ4 z@SIs7WTO}<-9cDjj<7gi7FuYAZI$!F`O?uMGfha5vKBSdGQY^o6(ajH566g$%#I+T z%wlsyif1EA(N{7Sn_*v3&842*pJCZRpBJ0aET1BC!QX6tNR z#WK^jiF%aXke$dZG1s$fn1in&L0&NXdntowo00hyvec~JE=wIbB+oBiG#gk>fLPgh zF2(eGO&RR-f$Q1&3lAb`6#xvqwm&LQfXaTDihZ{zleAOXlflYlWHkt?VIv zqty>tX=V#4RnA4tJ@?|N7_)=rLdd1aG@FS#sZ^T{bJ#Zm_;#Xi2x%wcJQ(2~$*=CS;* z$|+UAQuDAJdz)FpvdOKvoMk<>qOz?PA%U~DHnWYz*^;!Goh;6lDf)A*d5Y^t=2;r*E^;yBLQ?DpjOx=GJ>n{1<;4N1uV|^C2<;(IDNB?QX_d;{tNyD~kB` z+IG_~YA*HE4q?s4(%WYyvNS;GEzv$Rg{56^%gi@sCQA}pr+U6Mvstc% zP(9z8c`Q>nv(qeKnZcQzW|;RZ0|uG6z{@ED4Y)kkM9{a=7TvZIA~cW2}0X8IVUI9;+!t zW-1_aA%|LZqoh0!SqK?tb+Rmj&{@D?RyWHUNCPs5TO&cT0&=94aS+uL*uNibWeEu! z?Hp}22*FdhsAn}w9c_6+MeFW)!7)~hkQ(Jt%Tfb|b@oYf*^ z_gLqP3gfLd7U%m3}mCeV%BAd8p^pINDtzjwdGViz0J^XDP=PbPeahBeII7@FpoTWD)&axU1XUPnRv-||a zSy}?(EE@rFmV|&f%X2`SrPpr_j1^0Q`;La+8e(zI$WZDKnQ^Xu8V(G*Y{*93HtQ?l<$Ycr0XK{`p&$kL$N|B*s_4BQADM}6GK9owd>VjlCLIV4OtE{ji zMaEs`ud;){Dx9=9pE~vhvnch9OVfa5E9tOU2jDnMI)8t z%EsOk5<#*%WGd#)l2$x_XvMP7{Az^UU@1parbv0~1>AK+4{x$!gw!Zg(4QY5H(O0C z(;sjdCk0c4*Nq|dRv*jpklP?jtn@gUiGj?3EVa5={E){WFIt|nWo8nj7V@$c!*Ug* z8M54}4a&R^S!oRiWwt?HvtlR8dNNUJKjd{QizNoj&M@Q+tCwXOGKU|)S-&+LBxgY0 zveN%2>!GVy36Qs~LKeFAH3_obYGiS)n7wB;vpkHNuSBNZ>Sdv8UpGKDS_3T3RkaT- zC0@2Q7o~1TW|I}kvKTTI@{#3dxpEU~#nz_NN@6+TBPpA$R3U*a?G`ItX52097ON~M zLtD`;R)ZAxtp8)HiG}Vv9C8Pqvani&1dfqDw%UZqeM%mB_^}m#4viwP2kEjBS)4t{ zCsu(Fc}Hv}N_}FL2q{tM3!n5WFrQkDETN)heXaz3=(CwVq|?4tW**bE}i( z42+462EVYnS>o|O8tE5S9}8U*UW;FP{nFaYLPsF<+pk|*LoBN>8?6A^QF$!^A{Rx!XVf9II&zE;t zLqS60+F>c0C?ztTxZ>JpMN07;`Inp;JFQrj6CqS;r{!mvz?pt4nZ?hUek)x_VDGuh z%H@pno5;JYe3s>XA<9?p;JtpUgypw#Ip23%Re^1r#tVR}Rs(x=}>4E7(*BrjL@};;-@Aps(mPDXMV9%mVY?&i-;G7htbL&A9R6huu{g`&h?RT6zn8-iE9^p& z8l@gJk5O>eYGnzL`-^bMKUTy=|1NdFijm@R&KVC_aV*Z&j02Y65!6geuVN>&IBT3@ zr?NQ9ykcjt3}72hr9$j1mfv`*9%ARRILrJfJD**M~hb5LX z2itut<(xU#9$=yO)#w{a9(#zzgG{UtB}vQ+Pcvu6+8&k&&WyDqSiV4p`g4dK#WIoG zI>e4;d5bfL+VLz2oH^9CSw7%Qn4QFO5@*8fRF+-H&~zSWr?Xs&3{B^8b{0z{w$Id` z!|WWE(;+l{4zu%F44yuR+l4F(xvj(Pa+YKM#uGuf+ZAqCvz!4Lk7YI7u4kEqdT1IR zVK=fYLp?MNkFZ-~dDX94|kR&04oJobyQPa`(FlRC$M+phNSkBX2$kixyj2*^O2uT+b$#VI0oYg^&wWEa; zd2WZ$dU~uK$C)=Pa1R7e7e(4CO9kqo=RhLuM3yCxyHV;mJDKGT$bCZ6SU!YIha7Kb zuxy7E2+0-_xEep+?wUlS2ps8+xBG;Yiu>Jk?IOxfyi|--99_^|vlHxr$s~A28Z{TA z<`eA96q3Lm|3te$NR4tV?mH|%=0tmhj0`f-F1%9oP_a;I7!q&Svs?i=A_QOL zw?k89W(wpah-Npjtqua%3*Ib8etgHA)QRImi`u2a5`!rvy{&)Ei}n&fk|HbCq3ilaxuw&@{ZpwlhfZ zh6rRiGS}L5EVn~eL9VmYZ;_dYAZ?IzyP4%FmK*KPOqqFwCBx3VO-dWfEq3(nQa)kH zv=c&8keh_03kj_6 zv+a5o=j!)tyIF`lCbqFx?3%(bIf201g=j%67YGDhq@pSI&!j)BnL^J&{=nZTK6>?D@+IP;91 z%0hdzLiDi0PG_M#+LJ=ESgz)JD(xJW`?#J;JD;T-LPsFa+J!8OA#?=ttXnrC;ge9vvw*j+3KjgtMT zv3pszU`sm=Uw5Bx_p?MH^F}7gppd{CS8I>lPjfJ^gxA_(50J>UU>SP&oSl^?=CWAh zXy3iS&YUj9UE>zm?NU6auaI+jf!)cHfYxb^TVQtwm7*sW7T94k0&Ts9w(9IGA%XrZ zwDVY;HExmJ%o2{L4XNfub}QFIPocD;o<(+-5P6S&9b~cHFGXwt#$&23wg*_QKo990 zf3ZEx@($L5_fe|eR`SJ2J>8Ixg@j3Q$G*gl4ic(oiERrBT*Z39PGNCI@q%5(;%w_) zuxo|LDgPDPT55+q9GLRoKpN~kDIRCdZLkYioHe(>E@N@FHZR&0EY8;EMZ1nA6JsyN ze)%Q4f#m_n6GEC$@QAu%W}jPd>sV)!BzGUODE=z zl(0u>6jh$RuEek$aK&bcI>;%N#$vmY!;18Ywjw0n8i7_dfirLiMiu(&aGaz!&rVn&D(b1%}qOkcLm-)Z2)V10fvjfZg zC|sw0)9#bvE_H9(N?~9*r2YGwc36zW~)KsLCtU5$t;&+6eR2I zS|LTs?GW11zGpXaW+vn)WZtv;Ia3We5wg*)D3Vit1>{V~2X-yXpZDOmDTUOtY(R$g zupiisEMGxrI)7ldNO4EnVfO}!7xi@5adW7xz_#ThJDbIssvp_;LgZAPgi@V$j}WJ{4-XZRFC?%f`PA-Y zapwDHc4&$0v$L)H%x)G^>M<6{wm!34SzhM0KC{>D*wIqlw!X0aLIUIJvCCPUac#BRS)8`E+PRO* zww!6W)y`*mhuhj}7fNy4+G^Jd3ADA%9$<0W>a}Ca{yhzQ?JgmK`PFOpu>6cAiso0Z z-6zFutJfapdYozawVm?Bzo+32yM)CV*A6@WNy^kHf1&2(ISo>_Ss2KiK!u4r_4UPUdZmT4hFF~UQ^q93K24dx`zfA;Od)~yQw~kY z6%u$q<=7EYABWM<;|=%CXb(L+_^?laLuC^nOZYLc5T_`zhlS z`h^7EPZ^&O_AJ#?qd4!UL?tA%IPa&Nm{2D~E(uRyo<=7qRaB}}d|#cmq7xE4EY8*D z2?^mW(WB*-c0xiV%Q+C*s!vFWVY!eqXC=h3T;qz$a;qyg%e|14op{SHA%%s`O6YmN zvl22`ocoMtCFHO;*Zs~)C=e3Zqn(w|!;(7y~20tsIk0op__LCUJImcGGBehq)b+?1?tU)LRZzp-x{|ft zshov7*zY`vWx9m8F(v?OpQ$Uun>D{EOw7jMox{z?wqG4 zPL0dld0d>Sk?k&+l*x9NlgN6mayOF5d2y|~ZBjkC?v6>BTz9V!yNAo%eUyl#YkljZ*r#zvA=77vpbzc zEp650A~?r|vy=rS50i|4tKN=yR8-O7!ujm3U@4(l5MSU=aa~` z?s6A$aoW1uT}l}_uDji}ljfOCUlwI?12PHD~?h!6d4_CS48`u=9 zQGP)^*C4aXokz0Q^l8fVkZ0V(T&k3rkVhadx>KJMrPzI*^^hj_1}^xTFf!{PZ@5!l z5Sh~;n;>txJGfLS?1_wDAZy&=jUsb4GGman?hGzf%9UHgl<8BaD(&u4lFN`e0J6^A zA0jOCKlg?gSv^(CRmjYTeCUpQ$&p0J$L`pdg)BqnbV!%Gf{R!$*ipeJ?%EJxPnKt2^A)A@Ushv%}pGBF&INcZU#H0_tI3MBVA`A~_Ahj)Zr*dq|$9Uk7%& z`$+uAurIytbPtf617Tl!-RU0TQll(_yo0uWbXUJBmX@0_hdVI#pWR8X3Aqs&Ccn5V zxzs2RLOw*P5%&Pe(~vEY-EOm4lxl(S_rSR8xQID8gv^+`hfAlc6Pe#2f4TcedLd(6 z2DsF?9{3_m`IpNu$+M6d*aH6L9wpg@Qga}GyT?iPnK4Z{f=l>nK9}t&Kkkm@(y1Ja z%t^?MyR*1dDW^az$iME0*PT)sxaa>rccKti&h{|nI9%}vQ!J*M~HnaFqts`RXtjXkG02x(ib!693~(tYc^~p4muiw82wT4nQtL>zL)iLtklILc@m=D( zz;o4Rl4TI~UEsND8_CZo#nzOA)ee$>AS`pR+D#I%x5&h(y(F;^{(WI}fFud>D|&c{ zI!xk+>@}Uq7#C+Q&r_q`5>w+mWTKFnr^b+60h!Aso+KZ_*4#tYWFdUXJPesb)ixoD zfxTxk?&)Y!&sqip&rsS*_+$ ztgMBML(WuV-eH+y~EZe8{bLj|U zTZyAl%C8P{5zm?>K?3S{h`1p^HS1m0y7Qc`rIvCjR*pl>7BZIF$3-leiy>#J{qM0- z9SU1|uYoL3Q`*ERvQg@G$U?P|q!3aCIbR(jc?9wpm&`Sy)GLtVag3Cq=5ul8^2KU7 zWj;cNwRN#t%|-O*HAto!yO!01?_9@=`|Fmd@g&_S#irO2HId|^$MCx_N-a^7Nwy)g zmWxI*3SrxpC8|l{iV*vzC2Bf}Tz9h6OcJ^7WT`nMF(|bGHD9LYkt9L3a495_>(}LK zDT!RaE>|l_M)IJh9otLRYBy##JS4T+X^jWTslgN2-of_UQmIOI3u2UmPWQWwl&OvTeE4VoO z!5h_x4@95kk;6@D6qjO`JaV{6jin6xQo|h7e6t!)B9B0BRuhHrJ|E08X(4hrzpe0Uq!b34;i){E3suW zgpIvKZ4mYF_dcD1wo23{E`woCsTLBaR4W%}&sd_ib8+TZiP{^IIg{5M-NE|oY@^E5 zd@fbWR`kb2rd(|&*$r6;xmO(^nKBdSJCOU-$p5i=iWRnHTLQUXEhISxnHwPwsY6_x z{yd_NQ$}vJ9#LJLte!HLg;H#v`=}Z%gwL;NTv2>fP2u89jT*I_L{5zwwU$ebaxv<; z53SdzjUn|^a%th>jQue+@}Ef0m%l`d>-Uub(l-B(hFG**{t?#n%LH->IfI7txr{bvy-_Er9M-$xpXRP z@4%gFkRG)+B*Ui9m+B~GnLwEj3seQnEL`wH2vn%z!Y&B*+q=5gs%<{;Aw z*{ODsoI$cnjsKLDDpt;ce2C0XYAMOpkY30y>JS%at^7@m+2XXtrr~d@&ZSdXfl@z0 zM%8wbO31$Z;Cx3-`HYq7X1x2O3n0i ziOh^Tw8g#`Fw4^;GOi{FJ8qff=_6@_uw(35o&heUu1_KC_<5FRL}cvcC(@&TF8VCz zMWm;mi!(L$^R#jiTL8B7?&pd5LX;Xr&6()o98YbC+yXh+6TWrgNDuYIa&bm_s3$)p z!)iXv(@U8@QS*bSC(e`JJF%W)Jb7H4dXDjQgk)YssRYk}$hc;IF7BZ^!81&yrp%hA zv>J(Qil;Xuvk9e6^YnbhOS$CJ7O9>-68S7cs%L;iJ^_*H875)R zDX_aSQ$3?3?5PAH<0SI=f>clV*P<=?G(oB-f(4o!_K;HXJku2-iI6mp`HkqIT%ykRq;V;B#b6m@*Xz#rWQvT! zei1tzrP4hcNHWk?8YIJ`f6MDpm|P3F)RWF-FidX!F86ek$gN+FC$(RcIvd-bQj}Wm z*+8-!^*jy9_vqV2W(DM3$Zei>F4%uTK8F-}Qom!FV&zH5Af&{T^gWX*eC0Jv*$pZ4 zWRtvv%s-HFPc4@+<#ouuGjX)zi5(R6oXvhw^x%1FPZF0BWj!+Uk$J?^%;gcK8?yIv zxE}7Q`+=2uglF8yJmDGQ;%seJdPa7#OqudEN}Y|&O3&DjLVkp>CtIKPMD7wYC32eb z>1W|ey(gdK0LTR>waU{m%reEwF_5bv&v}MPP9}NY6ZMnGxFI(p^Ma?1#DWw-Ui3u& zEHW2DDj+X;@=30Nu>QR486_!(9C|$7*yc(6h4;|j(!T0R=HiU>RZkX|Vx^W!z3R#3 zQlqp&YSC7+r$>}h*j}a?@|MROnOJk1CryZ}c&S*w+B_LtN?py>;0Z&jVC`uzJauRFuCy8Ih8HFJ0a^Q$QY#47WRwt^nLO4f-UUVF44c^MB_At?%$;lE~HvJ-H-ust$RIxKt@yFbCr? zu3er{lCL2?$gn4OH*2d(`O1T95|E!f@xKdUW4{XWvnPe5WCwoThKzVNaB)WZn`eZJ zv$y)qllsTRk&b#Yxi};J-BUm!r}LPnJVe+?|Mt|A$dUf*Y2)Iwu4uiKk<(ex68@an z`d(TJ7pL_pS~`hreVUdXBCPcpS|N#SJwmJI;XgZf-We9VJ9fu#RHFGIdB4YH@p@mls@te#Fq9(5n5>3=g3N8Jr5b&}S|#TjXm*1^STD@ltUclyIdda9Pp z#X0IeO&bfzus)|~QU8e6(Rph6{X}+uAya;uw|Yd<({n- zh6p>#P1EYQIHR~wYo&~A{X#9`KW7xpXgx!#{%45hSS|b-{8ZOl~P(~gbU#Z1}iBe41v2m`J!=*~eLF-?ltvoG{i!;Tp z*D9!#oMP8&{UI4Pzi!l&y+qCOXgOaik2XN^9OMAJnX6nI<>K`DUM+H}=#T94y;?RG zktxQN+xxW^%Itgw_sHCU_qS-;G*N0TYCZ}zS88n}pF=cAjg~fD^jSL)OA2JAR>7rH zS%rFLqn@X=D3{0#qf`zu&uWz<Mvme= zZInx~awOV%5N*A$weHI@#md)>c=iIaPK%o*gsqjUA)Q)1$>}Jy4)T#!87VRrge~D4 zwR$ej*f(meT%56Q)FNiHQX?mck7QR297p~h;icRNkErLt2s|Hf| z4U=dtgEQp3_*6?Ek@MnHEs09Wr|&=2G|I@ivqdv0a}3U{263%mido-egg%E;yCD{Y+WkT}=@mz{s@|^}>Ye|%m%UGY5LK*g~ zJwH0tOv=cmZkv`v8TmekZCXAT=gy*Uv_i_rJBz;2$|>_crr02s)o-;*%4~uB#HEhQ z;0(EB_G=B4`5qZ|mqowUEHd^ozg=smQgR93u5F-FV^nIp*2Bfw+I*)C2yso_U)<^R zoi@T#8+C-5J(mxH#+1Pg)#h-JwdofvlQQxe(TJ8!nG+~8qUCWJ zoFUiBU$p|t$hGoUt&}oyt^7@^pp0BAf75CyBiG8^T0LdtTDeSa} zaleh0M43g9{kUjc2JJO&kCx7*O36ot?Pd08`64qzu7iJRg(MHu;oSU7yx&GE(D3To>=ZHO}KsIC9BQ7+DwMDdOb;lJav{k!4~ zpUbxiF8RJD#T!BLDe9Szu`AvvA^eyvflE<{u(PBo-XS5%4wO0#nJM1bgL%!$Zx9_a z)tg45MB&#H$TV*e$t=hP*k(@mwv)s_GLdn4`?ypodu<6*av(FjsWGhPD&<6E*fI9r z-UbqW){o3S-XSiIo_Me#p!drw|P?L7;o34%rV~BxQX>7cvHAI z^(1&JCuNTJHciSL?{yu?>M2&<#N2rdb0^W8%caaEe>qC>=5uj=IZE;tiHv=2eu}q; zi_`ik-o*K$<}Xom6KX!qTSu}RvL14#cZ7?xth&9fqb82a?TzE&)a>>aaKU@XpTTc~ zI1=`FD@d+|Z0Ax>avS6aE-fVTioM6%#zoB29`wiKEsAHYJNG&0-ZqklQO{4PNB2e_ zEix}a{(=O&l_c*$rsEfR)7wO{8Nz;}x4f-fij^IZjgYgv8%Xw>gR9nD#!14T6<{bccbPd z-fE*EEhE%UZ>c|=)? zQYWI+b>7AkCYHLv+rh;tb%QrLk!74MNxrv$L~coL@%C^Lb5KRiw|a|CH|m6IpEnOsDs1ob@a z?Gobp8}n*GC$0r}d#IGW=CI1EohIs$BYn=>LL!%qm%NF|A|prI?5*P>M*0BSYVk&% z&N5~RII&U@?XD+YvwuWTZqMi{>Gc-7UOD1pL-$S zc?-CRnuj0*-d--w)zlxnx++Tbq15ll4123deui9_i~F&?BV0s1{~I|3 z*zcY!X3UNJksJPmr`trGSh8=}^9z#a;b%n@N5RWe=DB5C~Mu^u} zPr|Osu7>!05vIs6*#HUn%0pxaB-IybiOlh+Cv1P*zv@fkBF4pzs55*yLKF=d*7_n} zB^SKQ3&M{5F7Y*qjD58^)7L`s9sSed!BXJyptdoDV$-$@OImvG2TE<}2soylG^auab-Nrjcd74ifpMk!8Lv68WZ) zWxgJg@6bAXugG%WFiF^fQo_?%e;#qow9eJeXLOQB zY{?{PvL&CS%a#g~AzKV7 z&pOG;wq%l+w&as6v!#Nh&Xxv}PFvbYw%XD|@{29QBzw=3{RzKN^!W^1Vn}YcC5hxY zTT)5>XG=E8PFo5|rXMQXswRoIrHMqdrGw-$Tlz>=*fL7eWJ|Q$BpzFONUpMFnB*Q?!WW4?H`o$Gvd)$ylJFyB z>!~D`E!iZO+EPezr!CbakJ-{h@|rCjT*Pq{dj@m4FXm#_!%jthf%JM`90~jC%2(gx z*?(U)$>*r25l032zFd-TA#Xu$_2rZN09gaM&DYPRLt*bIW50T=@QqOB(Jq`NpyoS$ zV7<7s!ghed8f=(fQ%Z`@V?FSt;i%yWJPhMZBNnN7U2q%OdGR&3{1F`6@#sd=Ab=ea$34 zBC|i_Lto4lqCZRygKYFwW()a~>e=LL3=vjOw{HWYip~oi84%%hkWf*t`cos0yzV+&6gY^I^hMdlC4Es)*5 zf?Uy7FT4-qz1$9?%E=OObR zrs03S3NBU3)etsS6@Scf(Vrs73n&%l*GOt1?B10r{xmLC$|3_#V;~dmuMUy5kZJxt zl3ZjqL-zK^UMEJ;gnG6?X8Kb})NoP|=0kvYtt6C!s&j`VkN5m!a3AxHc5n^-C5s_b$8#G6Gu z?CI$SWRCM!b17CXLCwWS;@g7$=v!E(Sh<4r5L4_Ve>Rsg*G=_YI3pjK zHZF}MMIc!q6(K>lEE!_Pcp9A zo$Al$QtbK*($6zRT*Map2gqsuh+8L){Y-x*Ny4e(%kpRXd%2V;-DsU%c{|geaGNN_ zp0oQC^`!XANwy&~1$RxU{$Vb~_&gcb0*Kcie!G}r`$6VG{Qej&Wv&IySQ{JveW{94;L|=*)@=J z{Cz@P9_&3v4JHF5m!dxgYCgw598&6RF4Uiya`{P?FpkJ~@;D;D`C^MbcN{4~&09ii zp86nU0CJmbYwFXGe<62Dnb`)3jK+PQwtPfVV#}8hHuh3m21xF<mk!#bvQtat-pDhPb>$c3SC=XZiFUQ-` z?aX1w#9(STath>l$b)vq3kgE1Y%w9{LLRYYAtV#>s4a^iYPbEYZ-Vk?b8+^tEB(zPqg;t?CVTJPN`GPzTb^r_K5W_Uz`r_w8cE72 zigE>d_@qCN1}$s}^@&-%wmR+m<){?Id#B@|HiLl$F{Zb{oyFxBbanij@|~3XJPre-jDo&m)lc{gHRG zQpF1E&ohv9{z4MgpB6}`e~4rq>S3im_NSJKQad1>kWc*WByzvJ#XoY7$oz-Qx5)JP zW6Oo?f2@!%{5r{el3srY$(fLyDD{;;{9aKi9r7FGYrjUa46+B(=Px9=2g2s^Hh&|@ zvn1d6dq~S@rk|YRwQgYazMh450Ba~Y;gDYdoRKT0C|v)eyTBKx!3A6_Ba zlKt83j}XFZ?!eW9-Tr3E$kzY#w}xbvqxC=ikq?M^Q{Lw;qJ!}q+`E|<3*7x{R zNfL18w;j)|@A0RHl)4Eu@9}q2CU2*Bw&HJpFNy5W-~N7*i$4?hWB=_R3aRG~)bqDL z?LpB)IW_+EXOPJL{OivmkyGPee~u773idt3fBi$0VefHe^F2%-A(8zF)5k(evAGkb z7d<3emu*ea%Smq9hGU*IR|efi=B@*Tv^!JtV`%euN%fE9#L;W}F^NBA1^yJ)T6a7jb%`5Z)Hs0>tUfl#y*6rMHG; zSX)Qwk&lbEwxZ94C*yl{dbAK;&#$;r8n5eIs+2)whEeKhy_rhM{v4yXlE|&!F?u`6 z7)r5a;~0Gd$==6F=_Zle!(;SbA-wfJ(AF_};u90+=?QwW5T0S9I6<$Zj9gk0_1cik zd@MhS`pBeuPSnRjGOV5x^|Y0&E%7d|{~#yn1tAhK7jJ~tyGdf$C?KcnQFW};;0$@b zbA}#ABF}fu)RRc$`3^p8NFvX7+DSBJ=b$Xu;&;Nq-t*XkoA^7_WLy51;8k%+b)MyXsq`b8lg2s>_Brkh;K zT<1ZaMrN6wMj}VCOwSO)d-xpB6j4Tw>pHzWB*V5p*XfNR!bXv&w}c3LH}Ca&dx)^> znK$anORR^^xbpQn5_y#-UypoQjN&r%nO(QJRWIdIqudC21+A~p`zgb=Kde8+dQOul zRfWt4kh}C|l2wq8Af#o(aN#uRq zkLx*HiWPa!_7i#$7t#7CT7N=MTFvU|R4(0(rJxpfr|BsqqbsK>MfmRIQ+h24dme-> zz4dxM$rL;b^AIxi`e;bzVr*Ae>Ej`pr;&L^&v;$5m5)**kOn=Auj)M{Z=yfvA@iyp|Awf?hf?faI+E{w zTY3vgKI(ZN=Z$aauD3)zGjKmYtLJS!lH^pX=WRWkbWEqWm@%8lD!@d=k>g! zSCCvnnRoOCk_s#v*%-yUdK1Y#=+AYK_w;TOAKGG12d~k4NorBgn_Lp!7JZJR{;buL zNb;zywR$Q^B4ys!(@Bac^S<6l@(oR&cDi}kws z4$BlPFEt3V;>cKt`CsNtLUHWqa^Yw z`saFh8*eL2UPb?0Pvugq+>EI)2Q`1L=W%iN?_2dAE@k$wU0d}&61Hb#*W$M7148&V zd@r1TZPmlqu(m{>4?)endVGkmdGVE=6e4Ut__eNaDRteC{;;)an{IL`cGW`I-8I|v zbjmzOnQ!z=k`~H*qvvpOeyjafZy=Gs4)p8&B=XmR?Rv~wF)!q=1K;TxTsp(#vDOaz zI9y(PcSN3F?9j7$sZwP<`oqS)LvJS80%1q^-|Jl@yCCdHcu*f8k$Zi|z4$ z)+5`+@*_w3i=Ip(e+T(RuNGq0vs?|I|mh6f1KQu`Z*Yzx9lDtW>cQ4>2MC>TM)`NG2rAh+fY!RmvrhWsoU`#-&)v zhujPaH}bg*+I^mC6moIKKGkRx8GDVJW^{{;-REgWMu(U_vd`0v4k32U`xsqZM9nAT z+;F*_da9 ze{?T)XERJtXp%wfRQFM%KD>6c=x_ zaS^S@qpf&jh)XBGCy@2$Xd}0amFiSv%|{#kT#A)F7#BM`KHA9mgikTP6@3RIJ;o^D z;?#4jQAZ;Ce5}z!B5OX@NZcfP7X zDn&|F?2BQF@+BnENa7jiUWk*8aW2kuPBOx~oj$XXCK<6@oaeTZjC?M*R}HPR?}MCb zl!plWK1i}r7b5KYAZHkjTtr(t(4RAno)Ee6S-hFb2>;Y+{YPX}BRWKWhjN|~ z`X7kjNFzB4{b73F98--k)a|hk% z5g9&D*oeZE{k@WOBa-B=-Y`B5(~W47YShe* z%+rlnlHhf?r{G#9@m$0;#^X_QI$!G8T{`kjycgOc-^80?i+sZIVkt8(!dDAVM_ZY; z$ZIK%ynz0&vm{4epDa`5H|FfjRQW}@iNd}px7cn=em`!BE%GfQj>vb0I3nK=;)r}7 zh$HgtACAa(eK;cD_2GzoBZnjM4H%9*gY%tkTXNZ+EHRqJ@}sn2et95SMjOd*-S`?N-XD6I5%D=& zGHaBNkU1Bb%Zw75H@}Cj5Hy9+rqZJ*BcpJoTK;ajchJ8t^-e+rksMO-L5xsMaFdm?7Ng7GK#o}C4oIX{g5%j z#d)&+VPlL$K5t%Y6nx8ScAn6$GX_ZHbL;g+M!y&Zd)EFUwEmpYLBifwz~n_EcDu;5 zpojU8R}9y8LOz1r3u!hANaVBIZx|a$QBk z^jc$xM1G@rtr7b@D^=s#gPLDNJ!_445e*t{a_O8Q_xN8J^(6AB;0vRXL>@VOVKkG-ql+)({(Y)E0{Oyd<(Voa z0exmq6ZF~{`7VMlZISOH*k<(dQqCFu4m%^?Mc{~h7r~IxFZvV4guRR4Cu3+*J-^!Z z$gd~-YK%&{Kd^b>jBD=z z{)@lPnet0;j>s>;?Hx$`|CO35kNxaY_VHmvAbC=$ePvry<-1jFnJKUHM+LM=rJ@2R zmoon5Qua2{D7zl{mV@X(3uV~55iZ0?=h+$gwuHk2u|s@0U8(*mxY6Q`luE+yZnFj3eW75st7Nu`2zDf#|{U?6%@W!ZJ0=E07a$@4Z+KqAStXp6n2@a;ef$;}Y9WVQx$k_RBHt=2#qNdtr(dA<|KAbB0ajy&H9 zWRtW**z+>)1`4>~JAIJXF|Kz5-CR0n$YbobK>QzUq@6SRkzq?_TOgTBnQIKf`m-jW zkwl&<`m-jGDoTaP_GD0MiOA{BopX575xd!r8 zAmuMss!F*ArAi@R2eSSaBA?;y3lwk>YxiTwYzvf!$V-rK107rj!{il@?SU>5dBtOU zpf^O=6_4)%LqcZAD;@)Z5iZ5NhwR*QAdo-K`ctf2haR%)#sh&uF3!?27^vh@quhba z8uVu{&@9AN31Pp-{SassrCbdVwx#_c&`zZ?QELZE4FzKVVQq<#vetJ75<`To7dr!K zLR_t=haFM>7|0N!Y=&$^JwFDrNw$;h3gnUe)~)bT!vXDIF|OaKp5Z_?7iV0ol?E4hJep;%uoU3EEOmaun7Sw!RMs8inv> zehbDm97y|*^|?mbS&exP*&T>eW^+0DRD7WW@<%`?$wJL+I*$e7!$jsf$gjxk36zqQ zL&hNEfnF|E${{adUZC}V1KM7!)FaAqo5Pe#F`fSl6p)+=DS?Csn@KK&JOP;=RHleh z%OI;Edk4eAg{**VhRh5mk~|I>hRh0PbE#5Zf&31c9jxP0;(8y#?mLYNHgPFdx*_bo z)2QGEkx}|c<^+2~M8WcNKyWNXWKFPJ)AjGdjeodY>ESQjD}K@JNlE|w`)qEL!0 z^KrpgE;Y)LkSn>EBngn4Ax8#FNlt}SK#mIbihB6c5`Gx&Xbsly&1%N?88Md+gCqo- zx!^Y-$O1@WFnu4mg?ZN4bdWBv(I;r&fdEk*pq(VROeFjGQEDFlLf?g7HF> zuW`r13e>Cxhv%?*s+9lG`hyT(F#G@^bCU6F4nz-@k|aW!Ac0^ji3M2)F@uHCPJh_k zIV)HiBE86*70f+|XIxjJ)cVWAmGgr6B>A=!lH3nDdVpn0NuGkZ4#&MI!3vTV$bMX^ zNxof`Rs+66O3m}Vv!(7T-%IRV|c1dtl2(OvVolAnsVSMadQt*c1%wSZA zEJi(-2BQvlGRq-X1T`VbKB)N?$d$o7F3x`Ws$e@8aaCjmGFJtMDI>3ey8n^mV}Lv8-p>&3Q0n#&mcDibrQDx z?1tPL%p_TY%=9CsDz^vgxzs33W<%}>ZlKIfkb@z22KygmoNdbnl8bHWC%MsPX(h+;d9X5$E^y+adGx>tAd)y*!#F= zf@vYb_HoY!^FoB}Ev0tzc$|+yQwzm=hxRK;8*<2yvBRzOy5%wqOs*JUk1-?rdxe_L0;g z^Ds)a1qVo8g0SPvw&1W3W!g{SN-fXip2_-Cquh>BJ`GtDtRQK`@5awV)&^tTEaPn1 z+Jm~vL~Pk!L#91g#HEu@Lw2Ln;~8hq_(8BMMA$D->w@DU;(jz- zSs#q?I8*E`wAC3*43RiwJ__m~k^uQ6m=PjtP-;ssFGOOH*%~Yjk&lu2I#?GXJ&^6e zmJs<4@?M)g-qBz`iF~i)@4+#WW6%~mZ~8qL>38NJdqc?Y zK{G_yJpCh>86s?+?g{3F2%D$>1WQAN&C|WiIxgbum@O^S&884x^K@^sBShFdjWqj0 zWG1H10cMsi=1%z%ynzOD=O8nWcaS;2rE|s= z5VqzXWF{J{t&Zc80cDsxpTXK{A~hOm6Asw^UQH7)s3%@jhv2SelsG#dl<$f4y6t?qlGB)C?(d6rwkKzf7D@S za)=y@Qiq#nNIj=O;>-*#HLiUz3J+wynN2d6B;L#;i6=S6EF?)HNiZu&f+WYAwImmk zB$|y}aJ?75U|MMFB(qgy__pqRNRl~9!q%pXA*Yxwla2imr3zDQs~_hDW;)3WB*|tW z340UEeV^ccrsf8c*N|ayrkQPtdftOvftuZB!&ySc(AF*Yge$5!&ZSCWYvm}mzaBH@ zY?g7(-!(Iyi`Zteqj$|r4v_&o^Qf677qKRfJ+S@`)v@vIZ7Ee-`PA3nB%Fe zEpbGB4tw!HCKlen99Yho*IS80?2J@8TZ*~i@j|T5I`$ObO^svGl36U2d51I{^vr#y|>{puIByyipW%hFs zTcp(}Rb`HFDdT&PR>;F<#1(vMa9IbbHWNam3sPgIlAMFFvwCXH%n)Ij$IXIlR!^xb zi>A*?vyV%aQULh^nU!YbQfJDut!SMY8zO8gT4yGP$S%l}W*V0oSKC5dX+}LynHeN^ zqh=vlT*OF+c&49(O^x3m^=A5&6I)+p=5TSgW2?;6t60X_jy0HtT#6O8joO2Ho->Pb zSO(u#M(bhoaXrN}uNK0#bxaz~_-ll`K$#cK9+Ed8E@WOZi>?(Jc6`X>WiusL2wS^Z zJxylPG9epKY8K=bvxtkcJzQ;;b16~2MCM>*R-3h4oGs)VX2YaXZo7s2M#2&Vp5jXRiXIw|Ex0z8w>>jQ$ zV<;ngxWQW*wIjWhY9p>tk!p+*?@d&NBACS-{09 z^}boer9>G=Dfa93`)2v1OuJc0nJ6!=q2cJg-E8OL)ck?jHL27GW)GE$r&1r7eUmcl z%mK<+$gn3I*O}v!GV9Io0yc^g<%56mObIgU&1f!8>+8)pF3z~tn+a5E32MF`({R0+ zLUJ=nhnY(90K~+&{%59>tRm?&Gr2f5e`sb?=1pXJu(W(=wvv2GvcViB`3bTDt$$>O z-#T$#d~8N?acch9jOJ3pe|=-q`C~J7Qf8wWPnj7${Dz2{H=2bcb4j|)4ld60`NZs= zRL>`7FO@nLrS3yLpO`~jwuiChnXO-&%*fmM(i^5CQ;EzbGnR`}^JX)Fi!+MNW)hbY zy7aYyQ+6Dx6sJr{)+Jr{*nY_#G_c)V#%v;8KEjI*a9Siy6hmDfO8d zJE_!XW;~U8h(`LEnM|?@@)*X|V`?OCK%RnpZsw8v57Gen!Yn1(PO{Z(Ao&NvreUwy zLUM>-)cmE{M&gFB%vWX?$wiRWkgrW;g_s(0`t+H@cTSu>edah9XKHLS!;4s^M7bXI ztVccD%ovgfAYG7e%mk7bAw7_OQ|IDLvF&CW7pLa!W(Jk|A4+|VQrpd3k^#s74jCgO>(AIwHB&Pa#MmPw_C%r+{u0HxR(H)M8jamKaNOsbgJ`c6~lQl|L( z!<3&eHGVYHA7B|=M>+}jBtv$Y`h!ApQ1jHIaP`UT=i>DFCvzx7W+U^Hnez}UH8|rb zTu)$ov|r3TF2$}FAnaF|U(7%hi z`g17S8ZnbAMGvbXNs!;n0xp9yW_}u`?2l*GcAG_9id`>bqz7@S;Npy8)U4)GqCAay z+^A>NY#@1qpP5Cn5pn}cDOL{2AcT!H%qk@L7jg$Ods(F@$Jk=Tx$*`lZX;$>ZY#KU8U(>A^61EPqqp#^!TQ$$v z$6C{^4k7lD%yg@ZGV(}fy46D&d8{?vihESFC68pLTM1mmxLALtTP9`Xv6jm!Cz1Ve zS?Q0ldWsc!H0ZKQxj09IE~`R_-9wjEO&QrkmsLj@*+Z8#Mk0IYvXolZmNVsDmWxY? zl7^{Tg{kVY;wEKgSP7KLxB$PwVj9k{QYK~gwsguYK|N2Pp1rLsF3$F6A1m_-F~#Iw zcpod5i_@P7t5JySX4J#xSA^9{QVn5Sk_fAvq#44tBoS5@Nf%{iT0JB?C^OUQC;1Dq zd=cIbXAP0;7sNNzmNOY4IS%p-mvIskvI^sxWreTg)6lgT@&cDgk{clGm-I+0nxq7> zkxLvGXB$4-N)#FX8#FsgnQbL=DN(9X>Q&S{+tNu^L*9h!XJwFl4rznzZ)K6}fv|mC zl$B3%h$+^}IaWDIfaCzHlH@v)1Fc$;$06%cbF|e+vYs*rS*={0c{L3X~K1Zo})&R*>5O!1$YmIVo z`g6Dy{`|!L9BxIC$i46pR^Kt9?>E@zw^){6m>|Yk-S0uA?ojX<~nlw$eyspO3K;UzwOW z#?rWmK5xS`Jl4wK(y46tIZXK)_gNih^>Qgz;?BbI13BJmepS>XYd*m$Ts^Vo6RZj@ z&eTY>s=1UXC!(Go&{m?=$i-RaPqbPll{(RCqf!=1u_<<<)lHHO`3d!$Wc8BV3E2%f z+3F{G0>X}VlB^+;Hz9kFImH^|;!ML+t+Y2Mj^b1+i;FXg)2tjWCCV1m!}@cYRX{Qf zQR4A*uvJd=$mw&smH+O7BF+$} zqpcJxc~VBTG|C);df2{FwbHpbBlTD#Z4>+Bv6MAToOz*H5nPW?Nd# zN+1bBW}!ctrB5p5wNfc_2{L!$sL^ZXPRjVKe9GK_%w7NCJsnm#Ng3n-)a6u0tvD_v$|BU9g#IkF zbdsAO8YIn1BdLc3Am>`?Bx@jSX*tizB>4)m5SjC=Y?9dvgq&~Xlbi%$Ujs?E`bf@) zuqk$d75yO_h10_etynHNU#853Rx%f7>=#)L8zzqZBCCaqQ%{D~#-&8L5A|G#9%fkG zlQN5}Udp_V3>(EFYj{%TVr!H#8!2>a<^R>dT_(y9v)cGQ$(HE_Y5v8ee9^f|{G4au;#ZCq<5Y~($3g)J0E zjJZ}aNi2l*XPKpuoJpBwR;m!?LI`VXxs^e()Rrug8*IrXS$?3n0=(QR5aPNM8Fm-J za;u1>3c}75mRl8EIu-T>uD8+W>#Pwj#mckDFv+uOyVxj-mDeEGV5HYuLnQA(u7})U zjgf4Ius+{tMSdbm_1Th3G6Z2Y-)J?G{6m?1D{&Jm)#>^UN5O9&hhOn5O^E%?vV1Fz zM85qa-^$`5?l37pfAXy%$}Eb&SH&O&R{3VppZ(IXFM!-?b&$kC?uFcD^>P^u+slRb z0Au;N-RcuEW9DAC8yZvWc58qn#+G3&csCU45v4}CY!5pJat}s&yXER;t#6-E4Po=W z(2C~b^tsT|xHzpBS-KFr^&%^kM7CaJp;~1MZM9-ivy{%TMEokm*4(=&(;2omezmT| zNK0%v0HWe8VP#f2ui07S?y)ktlqlQKXI9TWR?(zPxm8Y?Uuj(BR>S|vG*KqvT>Nr` z9+q3}|0lB{B*T7XD7OawPiB}hN24CLjFnriPuWy;`dn^Ba>4H@$Xu7iGSMRAS^{BH zzTAo>xdp=ZjOA7W$wLq}m+!TbNS=YPHRWDQBY6|T=JI`(N%A3tJ@Is(l}_>{^K`@-yUEE;%Gq{vTQAAD8#^{_*#?H}{Cenl(0`zFWwI5Mr4=#JbP9&e0|o8pB2` zgb+g8oe80l34I9JVj+YOLMDV*Bg7hQ3$f6LkdN##B-}>lD@~Ol`aip8 zwI=@)v*u4iS1Xb`Lw%aKeRySPP!rd~%FwVTVJ{C|kKpK)p}+lB!UOCM$y}op?(u*BFA5C{td|k zp~xA*y$^)yG;wqB zK&Vj@Hy00tnl%Y~yP|7E=YvB>ZSOUaZCP#aJL5h02O~sn?~{={6d`JRPeAfWsM|!= zeLeumqoDy!)_WcJB;Ckd{1Rqp$d}KW&-dBt+D4q4L;X%p(b*rqbmjH^tn)nPtz3YB8@=J z(Wq$(^=M+=&ifEo`17IM&s6QJyhW%v4mHn*dWl?%L}^|KwR9`ZYOeyxiKuxYG(==I zk>*hH=SpLaj1o03hd>jzUA!8~F&eKHT?;eteb3MmpXRkt0coB@joRK{3zhmbuZPM> z^D=6}sChk9r-|#>8=*#@>y1z|x!zHuV}@^pT8Vsv36F1h|p%l`bftpKk zthYnp)4UVPACL>luYC|(tH?7C9?BrCeDYU3_~K*3xs6KB9WfA$6NLMJ==h_%rOq%$Gc<&GM*%&G$atM*nLS;VJ=b;MH z*wylRsFsLbEuV*)#<*HYWA^}`hdOVFVsS0a<0*A2_=0UUDwu7iY8(2aMY;vvo%!i z(`*Y>k_J$to{rcSY9Mkkk^WHoDA!vU=etm!CR@ktiBI5cK=OTPNE7!=&iA1aO@Xl-~X>)YWzlKsY342$g=B1PHu4|~+ zrx^;BlIC92sQ+b#LX|$ve?!%zd4V+l4b}TJzl9n}^9gFyGeW$4nk>}q33}tI#&S`(aj{OxX(ZqFZBvhtJ*xT(4`~?B5 zeI!)p)BG>gK$;ZPRHEj8p-xRUk6VbO?nJzQ%DRc%k3>D0za#7Q#R;(fF=TKI8P+81 zeS>lSz?=gt=37;lTXo}Eye6)S@hm};us7*U{O=|!;EiWVM2;gefu#_+6p1%`ytfle zC2}*8iAaJhO%oS+BFiAnL#R>v)rl;%FFK!{S%xOgwKL1oBUoyw5-@CJB(wu-~DgM{CJ8LrOZK^@vQ*McLCqX=O=Vd`_9YU} z@-%U)Wg5%(#hJzm$#n#}j>kCDSh3Hw7c29*_F@&}nuo3sy7poA>n!qah)mZL%fCSb+qzNOd)snzEG}%1y zO(YXAasulzB=Uqn0_!1-eL^6C4UlG7#lZ{{Sk`xHEO%^5VA-0uc~4+@nz%|6*b+^` zUdkdfg9)s7ltxwhf2>LqSLsaFMZ{J*lhuA7UFl5Ls)?&>CTlk&QrAq@Ng7+%Ox8;p zTh~mM@xd2V>gb( zD@(IjwIPw5XR%t+*qmpvdeS7|J)mFls?jVK_$fN)Su94A_1;vx%DxQeYZgl&!jN2z zWIvWe#4USmG$6wt|ybh9+(wHIKCtu~p1tl_M&SJHpOmjYRBTeID!7 z#O>ARv2H^mmCj?mq_LIGWBsJDD`6hX{#$n}@D{5)1f8r%6S)N4wo+ipyF^zC42alD zf#qxBDg{<(NTgC=#iX&70xKnrtrS>45nCy+K~3E30E>w+8QR$qtb~ZoS+KHQqjMIl zj)=`!ur^Ix&VqFq63JPxF4EYX1?wS=%~`Ot-Aq06WKBeD&XV=T zM&~S9V0Rrka!ipd#*j#clEsn6W+>To(%1|oD<)zyl&n;fmBBf@y@eR(9gK4_Yw>BaSsQ6CAWb&w9;F$`3wZNc|0tsN*bCSo zx$OIc3z)Zu>W|wBa#)NeZY#)PahilZ`~F}Ki}$%sWr;r5sVtdX_Wi+AS(?wakY)N@ z3mK5hzCXB-VR}(_`f`bwJNqAEf6j;yb*HZv$clvI@t38*GiE~9nqwW|EqwC5D zQE_%U5WmF~AvVr!W8zdqG%C(?blnjlD$agLRz--7vwBRNdm|bZXAb_4abJX}I7ecf z2O`AAd5Goux?0PY__|ul3aG1B;c7VnT^BJ%E@&Hy42Oehgk)YN0F#! zZXaQFMA}I6C~MNht*AQIs)<`sb*!CSz2vH69YlUc!Z5?fSQn8U78|mL^=RTEuVww3 zxX5eSAh{-!Yb_fll88iAQO`z*96{u9=1o>x0bXe*@&pTL;>KFXVl{DNtz+@zT1aE9 zV+lT2155U~8dxg1^2yb}(tVmISte<&L5;dA^d!q6ax;=yXDQN7KgGN_(~Wi{?~lTO@=0?PYVy$aJS)@0jr9Vn^toPO z)#R$|3V7$C>jhRzWC6xewa8^Z?S43mtYlY zyLgK=kJ0pv(FCTcN=+5&ob4@^pou$YYhz1@*mH?CR*158IAP__E7D^KW6nr8jz?bY(HUbnz((!2G%me^x3}g zw1IVK;(FM{x;1f~?_#~0guNGNtgbP2ZH(ls>gvEb{VYOMU7sTPJVI1meMr7yeZH~2 zW&6RXf9?Adp_HnDo2rk6F6#=i5|%i4UJ&8&ko{Ww-A~AVg01p;XHi%19Sd{dH+)RxQ>0xVl;6b`Mh7^ESJcAMEcocBCjJ+^Zp$xBC?Ul_pFo14@7=o zeMH7BF|GkNtck1QN9HA{O7U2Lnzq*i-j6Jv$YDf&Vkw$9*LIdh8iAT4KF9ZJS+-9z z$Z|c-EkP7}9!e_?$yqgU@QW7grXku|2);br*t(0?ODt;4^ttG`Ex z-M;=}Lpt)xU_R!&Q!>7h%0`G>g=AMvg0s}<=37;9Nd9I(L_KY|50Zaa1rhb#l_VtN zc)cdBKRfUiO)L_W6H)tKwdQx^X|tm>JMt_|T;u@H*2HWVhv4V|USKrA zc04+$ufvV!MMTQ5t|L)1o|h1*85-}c#NVYF&&v$a|D&aAO`Rrg%?EkoD2>W6$eT3@ zd#f?BS}j4|M&xlMMgQPER^CITg~-l)h{#7s)au=Z#~-LF4SNGbVt5jfX-iGyU3sb| zu7|tv&eZ7s?8bXFas8RZ`!&IHT8cA?#~)!_w$HIV?wIH}u{==|7iV{#tO-8#igDCi zKfCiHBDWz??+Wd~TZpVdqTVo?%mZ^&WY@zu9;=Cq9LM7|!S}7urM7}NKAlK65{`2* zg{KhNPMSS=swOV-RGy)Ui#(NQk!!;F6TAf&XDVMjN~6vJ;(3WC?i^qmZy{pO0jBY? zbXA2r2bjj|G;!wu(|Cg+k#m4)yoogS8~`Je#-0OA<1xpY&f9ZL~WJ(@&O_@B6$!s`|%+njYu9tvOf>ZHFIIN zizFWRZ$s>vLlRHa#MN~GPu2wARm3<4;g{A9;JH4{Y`&N@KcS`p$C}Mce3}D!8EJOT zH#r~3YkitzUQe1N)I5)IlKF^Fa}W<4uX4t#MyUB~2mD=E9VXo|YNipA?>@i5u%so~=pPy9eWZjd2d;i#2hpli*jnk!K=1M@kC$A!#Tc8$&9 zF+6(oIea=1t4Zgv5Uok)37WVJkL5|4;Ct(o&#^pB6W8atJk#fz%Yj^L(RCDNIF~Ob z@*0wpkQ~SJiEKoYizI`W5E&rXzj-B*9WR>TosXL1c`cE>h@8Odi5!YV#mVH2M6!@v zjO0Y#Oyq1NYCAoNw-dPqiJIkkyo1PdNYs{_#e0Z+MXnGZAhOdk<6?Y}$e~D-i*rw? zu7qw7m_E#gB)6P$~`TcQ5zKZ}nLDMO9A z+jSNXoUE!?8N3TgAG*%sF^1^#y6-i~97O^LbXUFKktoe2y!;eJR(U&IGQnGfyP_y9`{BkMJCsKz*ol#%K8#Qrj^=jVYi*q$^ zBiBdh+TkL6num7~`5wuoSXVLc^2ND^_xj>o!~4ngC%V-3ehnY=xvu3SKG(H8aH{H0 z*xUV5d@lgUx|YWgIU31XxLU5`$wbaUqRyyGc%~-q_;o$6I$g!t9J%IsJ+ITm)qVqS z)WmiE2Hva*em4Unm*MC)@Kz$VNYq*Pjl7*mGm`bM1-w$;O{5cv>hp5mN2DLg?HFeT z?XF>UM>KJDt>g`hqw8A9TZq^r}e&;&NWa+b@pJXBF?(#Eo?q@6*I(co!egB<%f(k=5F` zix2rUck>a_{NH60yzlXulDm1#C2A}er;5jEg6{>OrUx^u;whTA(eL5uKG!`wlUxE_ z>MK3>aPYZS^IV^6HD64wi_oQVUd;=9u6ud0&vh>^CD%3R+J+h4%PWZ7iR33F)x3(x zV@mTE-o@j!L|#Fnj_>#LW+GoB`5j#k@OC2OE;q;b8s1H07Lpg2;MZ|^ACVJ@JjjQL zT!3W9L+}kp9=ud_HSCokQTvTro%9E~&UI~x# zG$MBO)^WHxT2sgKh*-^IJmZ>Z&0{>9h}Ep&CD%o3*6<1wp5RqJ%{pE~ni16OgFRfw8;R_8 z1s<1?H1HlG2}qRYNj^yAFw#85r{AXX3457Hl2P+C&(g%L`9_|jiOaB&=aK6)bg8q! zMxIaPQY30W_6#p1QjX*ZjQlJwCh{m@!^8LfGV2UZzkH81nD zyP`EO^DH7(^9ql>J6iJ!Pte5G)xwj;kQ7bujf^6^j)axA@GOdBBe!sGb#&ww9;=Cq z{3?&v1mCm6IEygOt2~)pHqNWOt~xr-tGtPb)x5?#Yoax;@m?ZU^E!`vFk16EPb6YB ztvvUkXiY26*Tk*5H+YdIu8KE!i6;2JFjkR|RlLFLD2|Q%CU1N=I`W&mm59~6#fKh= z*1W|7j~Ze%Z9J_mTGPg}h*-_ryymfJ&D*?zh}Ep;-D{&Y>v_K>uF`k-kS4Cucld}V zVK0Q0UWS#v!(-}I96V1za&3OVYv=JqmLe%e&AU8-$mK-d<4HtrL=r~L`#gn61(I8l zbnrAH_aRw@w)*yKp$%i~w6O*C3difDA(!^c8?Bq>ERPE}2qE24;xXRgGz3k+b znz(NpcJgXNB3Cavc`a$|)yqy^Pa6Ae!%jX-#9qDZcBF$TEf+V zsnOlpau@H^#O1S*_h{ns*~t4e341?dfXI1B)PHu{xYw$x2zx7#sI$0!9#7<9BYzz2xzbG6a@$g|!sW6dS<6R#t37LpQ-vz<2( zxdBNS$slhc@*om*RqbcqLgZB>|3S?!yn{$L5_R7DEAJ)pD`|%KAd%gQ@k$Q5{>#(e zH2EBWM0NEyo<$@R$*V|y=XsjA864(INK=3sbyhIU8+@8ScoS)^LyhY5AH0o7HIYAg z7m??YsGR@eeMCMX%?KYNvYj-4^T1ms=a_3usdr(W7lTf41 ztUTx-axRj7bd7_=HWk^qc7S9}aIK=|oX-Q^4v<0Qek6aRYe!h@a|IxuG%un?otX!q zkw_Pkoe#zPmC);RjfZ~H{EQlPpK&~l5ZUco^M$<$5c_s?hC4yLCSmVD)F{_ZkW3_t zTtUe2xh6suX_k^}BIFTSL9U&l(C69(ib?YjxpsjHA}!>Kfm)wySEwh==ctLr+INK( zBEKP-iext!9OY8S!ATIaURCOjgRuZa)c!{u2V4?#`B-bJWU*YM*Z&8L|L8KhZ( znp-f=G|2X8_JUl}Jc1fk`(98^4#jT*Jrnhu?sxVmOQkIyv&`pC5% zT`K1pFhnGHz1h?512G+{(y;e`NR;MZkV51n(j-6{k!48KS2zC;GKk!PL{%{pa)>-b zWEK<<=|rOLnxc_VdT}==lvk? zp<=*?nXzn!`$K+b zbcXvwIT8DI!~RgOiOYF^Xfz~}^Zw9G8oPb%53QuJIqwfiADhu_&ig}(CT{!MAJR0z zvt_JQ&CdRiN2HZV5|oZ|skt}+YBX_kF&k=$*h*(Z(grE>$T$@q$EcaR);Jk+YFB;OK`y5|Lsg&ml>H6e6pT zs5=pdLK=~$i5vzQL^_eEyOW1Q7LlKkyo_;DA(zO6QWNJ0SWILF67~P`k&sX11SIO% zbQBb7;yQK|6ptaLkwGM1qvk{yA~IzK&JL23AoEkxv15>^Co$$h z4w17-lLaM2%8;lGLr|uP%ZEV)X&O+Y&JP*XY2yALs=lOguoXwAuxK*VaYp`di_gPf1%*B$?-0p@K_Wj9Sp*}RxY;=idcTjJowHz& zh+RKtL-kM5nzNx^6IaDzXw)R^O(~n;{THiP3~fHmInY5G`!?J;(4&d#&w0@AbDal+ z*=OvRK%6FStn(qk=Q^idkM?OX(Dnz(Ie8PpN6+s-mb_(P55&ODbv z1`&H+w+t3*;{N+v2Kk0Wwy$MSNE*9+ErVjx*zIc>v=g!0*D~nT#MGs7UIqiCvFD-% zFi6DaTmX%K>W)RW_X21(B$7`7w35c=QvmIxvH280{9mT?HlG4W(8T3a0O_Q$`CJSo zL~K46L*htu4=;vHOU`r;Xd`kXX$ql($it+$47xON z9lIO`HF2x%3K$|{J9Y)M{?E+dTNr2PTKuLrv>OuX&lS)~8rz>MpqDhe--OqD55s3* zAmyLv8N340G;x()0U4Tvy>rmDyLRP}%l5Dcvc1W;6}W5NMX*>C7w1aI*Cgy+MRBf# zGIH5CS3&K#=r~tFBN3~)8U}ZW)?5wVj)quGF$@HvHN`NZiJQ}FAUIxWT!z;`tR`Wv z0y9+4SzQD1L~0@=kw`;?B%3%v``c&NKq`?I)Tn*SHIQzIe$MJ-O<)vJXHC~ZjwX1% zfiCq0!t0>QrzwFN(sZL{2F^tZG-%@Lx*nQ+uIr(dT;HK95nb0q2a%mt;`gzU+yLD~ z_9b#7^!ehH!T@PbK#f}Ar7%L|Y$D4ceuC?&+9R!iR88D^4nq+UHD9VfVaVG_x!hhf z3?-VlJwO=B42i7pFjSDnuJABak;d);!q7*=?g7FupoyFJFbruD_O8P!4#RGQAuedv zk6lq^kUr56yO$^f(8R^L334>SU)DIr{BqV!utXC#J1e2k=UNHHRugwFaSKe>#Eo?u zB>7yoK?=F-xx{UdZZyFU@d-HfouvxMB+`vUt(FSNCQ`IB{!erneqSAOi9CVbSbl>d zOALuTwRi^WW+PFq zRZvUhBqYjp7t|9u7m0eu`EF<=QidcOHC50?q=v{n&`qR?$Z8lM(m`?Vg}^RqF2Y_v zl1G;ZylRLg5-2wr-UsnS5|EsMW8Du4MAC^o07*o0k*Ex7AcM%2ME(PLMD8N;AmkHy z3duPb`5`FO#I5;SC??H&`+HgdKYv`ZRGXa~%x&;;e&Va=nMHtI)L$yj`O!T?au;!rm9CQG5P%5U+`g z+yIF_R|6!I>j!kHvy=u%B@(z5@9SZlCn3}4dJ2FvGf<;C{}kkF;>LO!ihQo8p@dw= zq6==sxA>vdr+Eg-Nuz!fS?yb%fqG3`LrK-nafk2?;wK%FM;IM@OWhD45oEzm?7 zdmLtqpdMyyAiK#+m*a9h}vFGZqLMjoP;j2(2~PLU8x!d|riEO*3WV9|8AUWH_z<~2wqjs0!z*C1<*CYv@}fAZKrQQizcqpH=*6R9j= z^b$F+!t80=pr6P|NYwMIZ-X~kjqZB59)g;Ly>rR69+G{UcOcae?|Rf6P=?>Ahispw z9db$Y0BSA@<5Nsf?bEyqwWN89;=BtzKFxd3N18U$yaz*?Y#z56$$MDo`;Z$Ky`tWS z#YF7teII(JXwAf>-l|1)5L8%IJzdd!91&Z^2T)_k#Bq1vHDT?l zBaPh#KY#`zwu%p+kzBTl51?5SSH%a=J&LHDKZIUGyz_A^btdy6jF>p%Rv_s^@)0bK zkIwlc$R}cRMzR<6@J{^Qz?<-iGAJ}8()o{|n262!BN(=>4D$uTkHFho#c?@*1PPif z_ewCb%DEHLiQE<;d81rLv&0bZe$=SB=!7Dl=3^)!O$%xk;cu3G43$33Cs0kAO{n<= zyYUG$_%s`!i8TL1&EKfm0BxGMt-A|a5=;;6dhUXR|BLQw7bFp}UG0L3S*ELYJ$FHs zA(5_jK@Abx)h>unG%mXma4bz+SGyoXljYvjO4HSiu$aic5mIX6M7F_=&_~4nx3v)l zh}fJrLPk<_&Kn`ikVwuOA)AQJc_XAA5S{ZzNY}*WybBmR~q?-L^9k8 zy+mw=TOlzmx`$gKSreDxRsc=hO4tSk#ue$|Hs~Z`Gu#H8s)@^R8`P4^cB~%)$Cw`4eEK1Vh|Q-TU`}*C{g7iwB%glBBVzOE zhcGU**tCruIGc$^#hC$v6cP+fw`u3Tj>u_XvoCt&@~y~KKlWRNn_8@ zet=RUw$dMHGlH6S4UWKm)mKJ_FFCiOXjIy2xeo`4N);9i7jQkV?en^CJ|W5S`DDP-;jd zpC6%|h|T9ms34ck=SQg0#O3oNG>sx^27iJUL%gT4_IX(QPY}p78QPq;Lmm;^!|kwy zh|PIBWS$tE^L7A3A~|n|93nR7?T||@oAY*9tclBcJCu$hD(68cH$?B}CFVQ`)jrM7 zP)nLNtm{nF{0xmg%`ebQnom)qey#f#=+MNi=U*Z5r0Dtj6_SbA+JA*=Ln7<>SEwb8 zUC+NlJrP^`uh2j)Tl=rjq=~EjSLh;_-M)q(d0uorLy$_u<}(DfhD7oif_l=}e1@Qr zh|OmRn#g7I8G;r~Ts}k4Gm5CW_%HMs;;E})hu*1(m!&c^E~WVmf`)kNSCR`?E0X8a z{0>VF>~>$>wqP%E>SULec#hh8QBYKf_SWv?g+uW*ABh ziLBLOC?{h3GYlQ%vi%u`E=^p2hG9q(_utkZ5XVhEHp4%lfQa3i{(vGPHp4$)$dE{e zf4~T7>%=hsBgjymtpFg3Vh|T9u$daaG zS?E%~<@YCK8xrZ*pO8z$=JO}ikjr-LPiWA@PvePt(maeBb#Cm5>TI1)B+fWdYl!zOYSfkOaiZRkU<;BJ7Ev>wXAyCu7Z7oyR}yifHxY58cWW|w z^j=LykKR9q3>xC?aSv`6cd2f8r?~l2TgpTcG{ifAG!sSqD9!d&0dHrKZAkF3z<93^ zcnp9}A%KHP! z*GP5~!5o$IDsSJ_xaN^e5>=-fl7%Fgitk^D8Y0&snS^9_QAeZ!$#f)phz3nu$0my= zL%dC>xeDu=EZTgUIMG3xe^7IC6@Jl9^bnbLuj%R(F{sJrafc&05Xah6q@SiTboT=G z6q!Wq{%}uGWyr+a(RGA&)sV*SpZ64XMC|@>Pf<@UyFc7hG-_h%Qg<2l6rJSSf|2K7 zf%MaA>LXXi{n`FqK8N;k!fPk=h{mQljd{O4A$dY8zPvi>T+|sw}>^w`vo;Q7agfsFX|j1-0+Mqu@(j^R#2)o#h;|~jt24yF z8M^Zmm!hlkUVQsQ3>gwR>dg=%L~K`Qh|DvM%XW2!08Lz1XNUq#-1WnKM8znL>hnIL z$`G#}M}P7xMH+~_5g~13TpeRb7rDMb*F(6y?<30dqC1};Dh=_5NRuFXe43e}k2HJS zKSA&LXNmzsf@f?n`_-9ZNR#Ek!NunIHB)$t^jN{d@66YkW{IFC%Y)Y=QOB=YB91i0 z7)L$hktn8eHl%a?*6s=qaMk zr#VzKkmgU)94dNzn!`jNX?AwooNYt-KrHKI|cE6D(hKShxMw;k9N7cT0 z;_K*AIj4z1Ln8Z)G%-xX?l;oJ2)XQjBTdAdt9z*HQb&h0kvxj1y~NQX)evtO$7;Zw zj~1Ce%`pNU^3xqJcEiP}72A%@M6c_C=y1 zr;A>nD_!)H<}lQ}i>`Dr;?o=}0_W*Uy%SX&)Eq0~4T;R@T#-PUQz*_{k>%4IC$dRX zfSS)S&T*pJr^yhtq`3(-+fb7sf=gVVRagHlVh!5lW z6#FzMh*HwLNSYHwn@^J|I!N;_Y9<|l-(wX0KFx_@kTjc6qh{wsk+jrw>_;S1(RGr@ zG(>kS3CTQ>XGk#ScKo_K?ny#oi6+Z~cT5@Y{du<{g{1il8>e344TRxw|A%S=A^;gg^0f4%u4 zXh@`r`68Bxtzy1tC6}#YzG&CPRWV=mYqH$C2QySTFA%{3HP&*kE<%!sJQpEZnz-Z6 z0+DTq_a>fWjuSM*`zE4k9;0a`%^wj>k0zVPO?(iap~BU& zKxAAJ-PHvmi-_&&0ugh$>FR0dI!(Lc42g7gftXIjc6EVROfK8i1tMP)*VP50T$APA z!x&k0^%PM*N@GZ)A>Pxd$-~i45rHeByOASe4DnhiPL9YNrMV1UIU-+^trM?#6aSmX zwXsl?Y7!1!jaM$!Tr3ogMicqJd!cC6WOL+-&_dBk8rz?RBELxGvvu4~r5sD|P3+GthM(W|$|^ z42ifFi44-H^90rBMIwKUrjRrx7)Sl`<04V5$>wqPS0)#UK_Yg0UnGW!*v@0*tIf)^ zzi7Eg6dDp)nTtd*5xX)Mi6L^?mAOa+irtD*=Lw5Mq9)6|TQNhm4$l&qMC@8UOB9WA z8BK{HUNySZUi~alVMy>PB=y*_vqcq=79>rY)Dn3QiJIlbqMpd-NYpGZ7EOjk`h1RP z8AUSg2zcj+E=}6Jr*<+|pUx9KqckeR^F*H^UcVX(kGbcGVV`D+@UAhl{1+Yi7s(_qS98uG1tkJ4;0j5j#6eMUEknEpDmE)5L8% zOGSw$%e`Q&nVs`R%_xnk^n6igi1+mj{IVDJ`Fzpr(_A20NwX)$x&BQ2&aLS3Y4Sx6 zX=bD5LyVIzhBVncZUK^wNG=q)C8jRhjSIzMBDNbBiiGP;H)_$fNxPB^iFD&akwV0F z<3dqHF1uDQ6eXItZd@p8G;#MLFA^=IG^!gHi8e#L0UTZZFMN^c8KdbVjrXwCjEvC) zZctUM^ma$h7OdhT5od^AnLi;}Ch`mks{c6D)_sX6AlFQE&B8OHOGF)M4o0HZ{H3C6 zR2-G_rJ{#i$Dl@?eO)R7H|o(NTfwCw#t=_NG^t}W>7+RqHTumd0h+k8i$YON#Li%$ zs3l@&uuv3~ni;fb7loq8kjM-ciV`Ar1`9J!dLn80^TqYWc*mry` z6V0Ps>K&iUMEfYBI(CKVG$itl&lMtmd348$M1mpSHQ0?2oUbBL@6%i<8c9<{9lKJr z5@{xKmFUrAb#U7_yn=q@c<*Y_r^(iFSNyf3H}w$w3bE)nuE=~9i$P7?lWE0bM3d#C zG=UW=XY(cJT`+R7$RMJ=;5`M&wIW*+7w0;Wr-|vR>c(|qi6LGO<~$u;*NHNprbJYX zAywr13tjKy)%X(8=F?m+I!Lp}BPR0oV!)@lK@5@RDAcIcdxJ;~N6*C#BHa-0)QF~J zjHYZ1sTf15#*msZq|OlU3LH!I=LXR{M$<}~TTn9#J9dNU^=V2)KWQF9%|g7AQz}M$ zn&l!;rYrTHAzD z9yNQUn?$cB?!Ng-k#>{np}UH{Qe+UZd!&`3!H~#(>Xo92H1?|XO3^~Z?vYlCR&v=r z(n`^;iQ6Ns6#bfny}AoLy;^P--b$5E*t@(J&l(;NcsGk6k$DsG>J)b4W|2zd0IW;h zF)SCQM2p!B4G}og>b@e}@-=}#{3~J)eGanR-Z&j7Lt>8hCPsDBo4~m}Kv?g-x;z7}8 zNaVQupco)xw}J;nN`-ORt>8hCrit4M9u&Enxbw`1MDZw%+KWCUN)7Suq`Dpw)kK~| z@-%j%Rx}fNA4w~chebD$?~rUp@`%X1J$g3@Ien40NeJ z*NHBl<}uMjn!{0}?u|Ys27Q_}Vwg0ipk^2|Tq9!ch|X}Wh%>~yM8&~wtQ9F^G-;$M zk7z&>clNbb)Df{~Uu#7J5j)FkMa`XNzUS2w{wc;WB$8ph$RJ`f ztQVQ&vKiKk98Fw?^`dALQGI?~lo;Y=pM>w)ABArMibhRVc`0k~3;B)sm2c7PbFCAt z607kz0s7Eeeb)a(r(TMHHt7 zHM`+hjiPRpW(JaHM3W|MUgj+PV$eeY?^)3|N~0=$P7F|-r!mede5&I);oYrsj*QhL zf`)kSphlhHH;H(k=6R7ony*o#&HMyl5n1XZd+CL@qna&x;XF+$=vYrr)C?yL*~1h;&ViMs?!_k!grGgjFPB4_^>@ znrt4o``QWKF-Tq%O+;)JFNzi-wu%=;*1e{RMd*4RpQU+GWE&Ex;zf~5#8&a5$Rn4n z;zhAU6IaEHqHGjVIX8<6L%g*(mfG7iiyEKiB~eG3S5T9QRlFoxe43X<8)-I@=4H|2 z)4U@3NV6R^9OJwqyz1!H(jtO}cq6D;fSMK&@6)^{5=gUKy?JxSef$0$5%1Hqiv-e~jg_jc zvR$NV;;ML8WcXa~iY#)e-<2-L9=<5lvhbog(<3=>Bwy zSWUuSCAwCkt5YQVG#`sp(%5e-d@M3GagjHOY@cg`$R(GZYrheJaX*u1`fJx$G=|Drz)Y?LCZj-Gg;~CfbRtL$XGb-cg!nB;6wBLDiqtp3=Od zNg|PEB%dMqTx1hzL$X^N0D9VW3 zgJi8Hl|){^IO?j}PokPg8xnOT`6p3lNaX0bT{LJC_C7()3m9j+Xwk%Naf2drP4pHw zC~`D$8U8HtGzoj(P@JDd?%L?>;uo=4ld$(YYF@{&ei7xGxUqf}wNFHk^{Z$kqK;o` zI~@{94bhq*kw(O7{wwmIiPrpA6l>yg{!Nr>f?vja0y~EJ{3hy&q!RgEbP~xSGAxEQ zackob5qLJb(mzCuCSk7#UA-9j4-rpfHIY9>f+nu^zeMfx(K-Jm8Z~h_kBH_mq*ar! z*Nl<3;aDRg<^>bSb_2;v(UC{QbWL33zeS=Z?zr=}NY%tu`j5!)x&9GZRJh_-$c8}!A0-tLKS?qJ|AWO+*_eeX)3ZG_2Sw$N4=D7Om=8m#a6IVq*w)k8D z*+#A}u!l2W#V3blJCQ$;s5s+gCy_nYnXh+FkljRPBl!h8zmp8Stoj`Gjz==?=^b!?K%)&zg80N-s_-Iyc`G;wnpD~o-uSXoN0 zN2&H$S?+V~E~|X5-DM5An$R^BGu&O)j?$=(?IBwd zXR-uCBK?^xb4X+RGg;=5M!mCtuy(Z(vHh7WJ2Y{%PnHF3YAn++mEmMru8GSqPL2?< z8OF(+ccSMsP8JcdUl)v%Rhqb2j*~TpM5>6Bb)>OX#K{KI*jbK~!S?7`j+3#PxSZo; zGHL8OoFe;(*qo=x;rF9+o+9Hr46!*+k!eIe!x>b^izzafh<*EYiY(T|)ip(y8WO2% ziYzCMt!s*`B#o_WiX0$fXJ?8W(!|Zq6zP4SGIZ;CiVSKJ_O@V#X*kPRg-^4mOdyT@ zHo~4VT@x31s?74arpjz`4P#{WeUhm%kI2*peAYS{pFoyNh#ZO}14o}G3y7SCB!pxy zSxDq+BxfMmTb2;1MpB4mx@;!$5|VNxGh{1~FNo|T+ll;z{V8cBle z)x@oY|C1w{xYavT=6`6`k6j5fWk+Z9N|-78iP)7eQwBfQu8GIuSWn_uGi9tHky)N8 z<4I#@d8SMt&GYDbQM*cr*jb(_%QSIwI#X6?67~*x%IvXc${L?$maHR9Hfr+l8^5z; zvrm&KTS;>{YTm}t6J?iAv#;zSjrwAM+8^#Khl!{!1}L(hjQd2@9`@810~Fa`rVvqI z3{WIV0+C9}`2bn1iCbf{Wt}Fj(%G_sTu+c|wrnBtHjx8m2a#SP$+C;cUqlX)y+rnW z+T?Sv93XNikwfGV5g?KxM~LJTIaCHWm`clt946z4)Dk&dPAAerBvmF7=_Yc7Od&Ev z*j#3Qs^dhyV8A74 z;#N49IhusMLCiT8E9ElZrvX_=nwV$J^$n2qK8=u#q}c~GsvAPK`!rH^k|u>TQuY%$ zp2*44`^N&>h{?Gh~b=IPY`u$^ibF!x=JG6X!Zp zPWQRal!@fB|I3^yQ+%#PGTrA|Bs0ln|Cd=Lvo$eKKFq{bca~g2L_PVCjAXGa^2Iqv zmT0ot8~5Av_o8_f4fn5KG zt`pF;R3`Z}=gSn*9EKW(n)4-S;v!!lbA7H0>?~g* z`!sR?^G*+iZs*A+6C$a-=W$wDHV$aSSGBQlKS42*M?tkA^ebG57@&E8G;ju>jL zmi3yrN{eOV*U^<0%T^+4Kd<&|*T}5iXw5Y;R}(kZwQ{j0VJ{uWQtv=rD+|eG<6I{z zHb=*~PSy~yni3i8i`JCLcuidAua}9MxY4hd$(n?{Y#ja4k$`u-%qCJmEDV3c>tY*2){XSZ=T;>z8niVqjhiJ_TnMuTI!ZLOsS`(HD zM69Mv4)`=>azvAr!S^pW@50<913#)bD}&ug)HTMNWGoT&-o*H~@teako``yHB3_e3 zLn7B9Z~lV@v72QUkr!0wk(A5DM7EIT7MV|E=jZWVD%9L6 z3yB?L9~cgg&VJ%MMM#-i|Ne^Vsj>H;`qo zCT<wKNe(2X<3WG#{lkvt(QiEJdzI$29($L0y%$M_y^ zgRCcVC=%75CuI+jvyrIZaehh;5V;-6Z1a)`()6sJ*+5cv*?8vPj=*j3ezb-iTB zvoeNACX#k)M!E!VF)N^+cw= zVz$%QWfPGMBCWEO$Q4NTM%Np%oyZy_>NnKhl$}JrAo7;%CbDY_-gCkKzuIIUkqjh> z80T#{MC1x2JK(oh*2@thYmgj-ns;PiH!~NXBS}ZnE@OzqylQfOSH=@Lj>vm5k;vsl z-j^vv9z(M2FZ}weOefNfMEzdd2Qrh$F0Yvx{7~i)NkS6B(La)TMCKu>#^-W6Wj>Mf zkt{&X$Fh*fO-N2h@`)@aQjg?ZBpYO@CT>60CDZnd-j=&$mL_hC+bFX&348Bj9JM#z zD3_4S?h`(h)l;M6d@AdSSj}g$Cq7#9nHjnyrOGzoh@;aF<>>XvcSl*?&8m(w-D z-==)sXg-(Onz*{Ykc|n^V|^i8HF0silq3TiHz{kw~8$@VU0gA<~>knk_Pzs;Y32x5`*e!ru9$*(&pVnr(6k zX>LZ%GF*q-WU)`vFH1>NPjUKX6_Gc|^_{HK#Le>evf{|-u6{3TG;taJAnP;(1l|AY+ulWV*6}gFi2IV&mRj?7Px)>Sdf8Y`~0~e3nk=jh51|w zJND;-9I7&X1{Vwnx6j~$DCvvI{Iy_7(9Qg{U=@lT>+c1nC?RjcTmG&3-wUd!%JkW> zpgYIyvtvO&iOCEt=sVNR3@sQUF`0iB^cA?7e-;d(*!ldsAZoGBguH`cKGzk%w|gy! zL$PPee+#;oxqbdy(2rvKcx(^_e!q#<<+14He&;)=^9YuRVmpsy1tGWdNLEZ@RxygD ztadY_SO$s7M6u3NHxtErNla!mE4bOsjAq3oCNqY`mbsZRED^=7bSz6o33+eAy7bvQ zmSs?t=@VdGx4C@+tdGQG#<99`H#3ekk(kVQ)^vxP8PD2K?0hD$j$zV;67qh48Lou$ zc>+ti)9+(CPhgo9Zs!Rsh+;cOvs{#rH|A~smL!_hQI+X4k#*kf_L<0fQEZ<{tbdpc zqJ+GCp|gHGIEiK6&37oBr8Ey!#?lAibyIzZU>pdR*^gnQVBAZm6E&* z@*qeoD@U={#jb3i#$DI0Eb38T%-W~1l*io6G?qbPGP|+N$KA|sEQsQ-s}@F&V}-s1 z-uiq5zMe9J6_e=aZ4QBNhnc}jNOsvY0)H=Q1}j7HpQC;O`pjVcDDQd~=Xl;!crGrU zd9`}kKP1TGg+Z8;n5RVF-bej=d;(NyaZNCa!iZ=iRM|XoaEUl z{_}pbSQUwx;a;qUBp&9hf6-|#R*&M(c|DB2H;aA3pP`v^B1rYs@j*;L}d13b(HxRGWxbaV zW!{r|&UQZgvj9rS%cIQxEFmIu0866G9gxwT4`7)jO;mLt%ZaE;W_gtP9x{5R$*e3Q zGn-XVX8b#TW;Sa;lZojGiPM1tr?e+RK4lI4_1VR29CwTHZW zNe*VIB%gq6fEgab(n%)13!lpdIh19RoCxwYNGi)lv3od|1u1hqWb{hsvXY3*VXTZY zk5c9^Ruz$%$7(3^1!d;3frv~R8=}nblu2Vt>fB>GpB15mylLzG`OIe>5t+kT7iEs5 z%;7BfwA<$hmWvYd7E;XAP7orc64Eea7u`B#TE0d6kqol4V3>j$&Dqd6qIq zv5K8Czrn5^&FXv!=3aDxUYbUv$su=4extlVa^^!aV zk_mkT>nC{wwB@9qR&CJWNVZ;87clej$v00c#(Y>7>jTkhuag{Slc9*&t?Z^X04@#hwY5v(|QZ4=-n(D0V(eST{<@Yo$I*Sn@{qSS(@r zAG>{)utF5u=L%Mg67s%;KKj-FD_9p*nPYk->-og(b0r%fF`1=osKd=HWzn15`4qC; z&2FZU6_A)dSFx5(H**#1ATgO`toL&_vy2U**mW&uUYE{-yA4O@a@``pZmQ0yvhW@S`m`rN_>18$#NSYVtlCR4_GNX)xX%Gf|e=2q4=-tBWM z>q4=|{WjK%VrO_8>qiNB^{}fQ%~9TMENX)81J^sqt#AZyXK^UDs+=WI#(ZyTIZHvY zW8J~hBdYFTnN$_s2H#T(o$p}TB(p&DZOff3NOBm6{&e15ET7~QkhM@%!IqHZfxG~6 zH(Nz=70A0F_pnkFd)8I5&S-akDp@azUDv&=A0_1741M%FUhZYl6Lrv;EY@{j3_r{^IBTtk#$CyD0Bx^^`I1 zqP(9qQpWrW!~HCFvcFREj_CVYJc_>xJ;VE18fDD;`KwquiJ4&)Yw=~A`P6(BYom<$ z4wEX@K^ZfjD%L|8^9lVbmJ#F5r;25v*j=q+93|wvOgmr2@^{MUIaje&)W^(uEo&w* zb6(3zcX4-hEvrS@I&K4W-VEo)T2}8%cvsi5M#`8~KxfMA|KS+#3#^Kn;;v#Xi$k&J z{92ZXV%N2nC8LDA)zIhsAbht5OGWu^#Q7IQdEZ0l2UtNwRW&OdCdJg}0qCP|pQ~9p ziXHtSRuxh85UZi8SE1@B80#TcNAeknK6@W#4J3Uae?aCD)C& z)=n~qn2$UqOavz)=#nwL|@BKupyG$LG-o!6pNm!_c`R%g6M0xj>VF^ z38F`Tn#GfR2BQBrJ;Rbv?Df8mmG0^vFLRr@j#Z-Aqf^hSQ9|Az^wGOf&$=Qq&$1rM zM1SPpAA6Psruj2W2um`G9sOC>w!1s}v#blnj{Y3$K?!-O&_|E{9Lt!lt8C_ZmW2Y( zi_%!nv)o~se9HVUoGC)F^Ld`t%yQ@RJZnI)V>PfQl#sUq`siD~2G&kh=08ybE85%b z)4)nmY@ZicIf}g%eSy`W*mLy-RzIw&k*e;3(Kl{~Z-HRVJ7x4Oz>BO4#on&I#NreE z`IuV(kbSh++trs?I*PqreTilI621j^iDgs9+ycDBf|N10t1qz{5_7xy601Y8Yk!F~ zpoBd0Z3!>2wunq4>xjrSvc8DS%WQx$HLx4{+Y(-8G5hLu*|A<>aVR10S;**bOL&DP zMP!;-3T57gjQ*YLCRTuAkHu@OD5B~$wu-7cpz0sE;$CAFBt0Z=uv(G)^^hzCNr$TS%-hf1 zjaC*w33*E(qrbhOmBpdhtKdCWkmR2&<~HFyR*YhI<9$|&V(0TdD@TDxcF zN%APjDKMW8SQp8QAg6(R$ddN=$1>N>M=W@dJNic~pTuO^S?0lRrkw>zOlBjCKh(`^ zWXU8Z^Dzsgx|xqz9Er(%!qVrunNL_Yid}mL3!;R)HdxnuxQ*&ywNz#LY+_}HxqUXV zDiV|Vl(o)tGoP|f6g$>t){SC6kRMoA|4cNmkSOLk;Bww;s zBtszM;aP~USSiUa8^?Ih<-%{9vN9CAuC1&R#ja~BtEQ@bp{fb)Cv0W4B!^RM~Oy8s4#wrij8GFmWjWwd!`+nP4voGOW{%x$4 zGUk?l8*8VGx$n1)B^=?8Zf^Ovu_P3`u5Bz8#ja}`OGklwGO#ZF%{kjxF3D>k`EV=o z11lu?m@+@I@`yfttdcT)kXZs%eXJhEj{XyCim3XDwNTZ_kNweqVqGLNKy;sQACo7An8f29b zRfDXWs;|pIAEg%&j zL#&IW2c#P0AJ$6}^~o6T8IXV30Li`}uYml=yd(874S880CGZYOj|WID0XZcZe&vnF zp!mmKUxOogGK#(THHsIIm}_tpZ#v4o21oHOl&#~+p!0|D+VLpf<4gD&9L4)6W3Itb ze1J0FK~*cvc@)n)+Pwxx@oW@({*2-|DE3?(#q&_$J0CjWRs#DhBHf-bz&;rod~O&}S@fCs_r3^mP&79VF!-`UsBW-6Yi@ z+d#(iUX;7Nbs)ciOyIE@dhK_6uY&AANhWz8Wb{$+yJkFxy-Z6S~`<{Uqo{3^R$M9^DUudit9`q&LIfj>` z_?<8R9KNf8cc9qLQ+eF6dMvxoQ+Xna-RG%1871WX1!L(eWhzf432gGOlvtilvKz>r zu!>!ICds}aNg&gBHpyI&gF$xV`6QVjM}oxhT9OMu^zquAH;}9bITbR~c{9n~AZLQi z;B6@O+KK1ADE8W!$@@smwKJ1fX5twh{$f833-d5&sEr` zhhzHEW%l9mBqKNbo%iL*B>REr&inB+l57zD ztvE?Mo#YA-eQUEn&mg&*0_hTWLWvwR63i`kr0#vF^;Jcly7cEWFoVpT1PITo{dJ&K*-Y~Ds0bGFRk(cC>- z=I{#P&TtNIK-oI(N*JpQ#+t*MdQ1bRm;P90hI4o#iaj^x z@MM&b_c^@cqmS1dp0!g(|35v5=TjfEiWFW$V)i+O7b|xaDZC10>$n59z&n0nK2YUL zcoiwUjxuHyDZGI)S3}jqSQQ_1SCPUKN%ZLY`JWU%c$%LvS8fUqobHRs9L%G0+|0o| z9>rcehwvm6yFZ8U6cl*W2JfoU-}!k6PaBrWpv*?Nhf)V~K7@BhWDe!slxc%?>2v;2 z-j8BOPvt`qRjEAcOg(2fI#BfjbWY_tDE1sXj2EKVt6&~4A~8p29`8O2*B-vbp2vHA z2_K7jyq_}WSj^*tlrgv1^LWq2L7eP+s10+9!jLm>|I`P5t z{L!Pj;2RY{PU4CG^JNbZ{eS0Vo=uVpqWAd}9wa##{nS5xk4!FOd5|Bp)Q1@&!EN08u>k0>ATtAo@BD z@^q3+lGAu5irx9sc{XJhLS`fMIh_}w*yD8uFNvr+gO^d&6;Ra)RcG*m3-#!>Du)-L z*s*f>DwL3SJ5=fGB8OL#JPV@F@H2Tc$tNI9@Quf3@u-V*XWQp&9*qLGY>+t{e&h3O z9*<&2&*e!GRk=KcswRBtUthUAjbu8=eQ?Dsoih4AYyre^bp>yssxM&=_4E8!@J^DSNtW_Hl7B(|fManLA0(OD4bN2`1K%dW zy~Y0dlLVrl=P%*`l5`OLJpT$FgJRcyHIJi=d7l4joZo(GrsN7>v*x}N8g=yOc>xq+u#>1J-=87TJXgm^ZJ-JcK-qJ+FB zVRZd|i4e~v*+^2t^HJ=4Zsavf-LY=u4JfwrYTkqr^8TiEt>$f1W#)Vn=U2IXZsK_; zejj~(mGWZBn0v5mc-%6-%3NP-c-wL<_WD}G`$)`p!L8xZMSjM7E8rTQjAF0BH9Xaq z@HM!Gr&GpUgKKyuWz03WhF6oA|M+WoEsEWZHM|}rQyz$QP(oeZvf zi>PYK*YJx5aQ0U5GLj^aV?fsOik+(Tt;7So9>x9_evmhi=vC-j><4*UC9Wd;Na{h} z;Y;{>e~@=k#$4|Y@*c{V>-|BVaj(A{=6ZjSXQ9}&Kgc;s$U6q+tZ&sHw)UDZeV8J>t@kMcU6PhyVpI-XIb``G^l>v$fDeb#6lU*t>p zxUb_&C}WQMI$lH>^Q_T2-a%rH`#Ro*VvqYe-h%?40jE{0;{#M>uBLk4zt%s0%&llW z_a4w<`#j47DDX|`)aO~Aiehgip5tu~x_zGGT`2Z!d7k&8*v`-MeiZo5^B(^jIiBZ( z5mgO5s@k1T1CK@tc}Y;E?|n7!Sdx=TUf}U01t3{)oxaEuNLGXBN0Be_bd@&bg5%KV(Ng9Wxxoe?MC;$jiKZr_6;Qukv~n+vhdjv{OdkM|zF7puoKy7+t@r z_!@7EsCu1uMpV7dyQyk3R4st~wzAc~#On>^}ayDmMSH+eKl z$U6b53Ze6xJdUK0CYus~VJr6wUi#ZQl`M~3D zrjI0riRVPjHujLPTdPJs;XHsUj@BC-y z+IW!U;IQPQ*mbq>#3$W3xA9aGv#t+$^;2% zk9m4T)yF)Os*0fMQK9lSIm zvx%2S^x4F#hGlA~Pamyo6K{*C`jmG@RDH_3scHzSUWL&=<-H`keh;tJf^6pfBy&LY z@#^G*5q-9B?`gfyA&)`kBdFTK<528z|BNS4=6uL}3YpJ%DvI5o&v`~f)#p5ms&0ZR zeY`&Bc_ei-pDtcNvJpi8kNbizL9v~`1B@o>^#v8aA-nYXC zN%jEIk3GNQ-ZQw;z+vG`faH{LCWhp~a3;?FSys>w>;`aJm&*d-@-FA15w_0JeuTw=+g&%zTwpneTZ5 z$!j3LL*{#)OmhBb{x=Zz@>G(IdOonvy*z_t8wkE{hi8#QZSynRc#vdI5WR;#@LZC^ zK=ijt{=gTJoJyG=`4W-~DDxvP_9gs?zK@rn*gfpy<-;+fitW?S zyM{>*O2}Ieou@|qQVXx&4DfQ2U4QU1+j$j9GKkLn!D~?bUoJifM*ox7 zq4@VM=AtCOspss=(I9{E3>3SH9Xt!g&Upt94wJlLRf|wU-f=LW?@nuIoRZh610p*Hv&I z=|7&aQ%2tp_e3s=y&pb8aHNRy zB|O8CB7rhyh9gB1Wy}mmiV_kt!;zv4#qRS+QHf&rd8DXD33=w2juf>d=9rEWoe@=0 zqMI__o&Ir;5?-r&Oh=1o6g$>v5sMP?&VXIj=g(*nACVa&5-DRkj}aL=W%NFe6}c$( zxCcZPiP`6XXn4=v=YZ%y*&03*0;0>8@ID7b4`s|g2Sgub%svN1*86z8!tXf=2##X+ zIUsUT?Aik&AI0BieLe?7!A=>yu5qFi#jb0-C?hfJ8ZYubaL<I4yoV)t-@NI(gB=G>SdQg+JdJ&YFF)W^(uq9`LVbDk*LH|VkKH8@f9 zqu6s}q8Rigyo!m!`^aCZS;a&V@MVNq#YB-$Vs>?+C_u5hI#HBT#>{Y%C?hd5oFpVqe1Rnj%UlW7aiAlu^d) z^AyobV%9Z9^rP5yO%X#Vc3o3MRHvQ~d`^niHAO_D*rPL5B%#=K#fn8F=IF$Vwk__u zVnrW{U019a@Fl#iSTRHyv#wYX^_jn8W?iu&kHoACMn|#hiWP+@c3rWe7{wm1SW&W5 zMjx+TMKy|D*EG>hV%9ZHBz3v#nkKSP?7F7Gn+E(jn@^HX6FHPI>zXF=C}Y+&O*E01 zbxjj3D0W@bL_3OI*EG?IV*ks)oOjCTb?qjGQ0%(mg!hF%L$j_p(e6w5e`TELq>Ncb zoam;ESw)=arHolcoJjl9?`&2PCo)j%D&j;oid{vV2%^BJ>gcSC6S+HO^eT22MJRR^ z(?uhRS;cgb)a@Rf=^_io9-Zle`w~9uri&cPm~~ATd6Y5hnl741%(|wF78JX#>7pIQ zu4}sJL_LGq>ffPLqu)WbM~wBO5;TWie2eUkw;=yI#bkt?XGmDXh*Rt zohdqf39odf=%$QW=}gf}8MD%vBJ&&f2+kDQD0ZbYMGlHxDa;Thne_F=ub8sQ5w8(Eis;6%8m`$Mr+!H=r|A z`4T>RQ$-79%-NeN+9;FtGyG;5RwezYJKOiAq>2<2dwr#fY|5B>7js1!iJ9SCQQ7Ct zaIR=X**flF80#Y#Yp!VaB|O8qqLngchI2(bWwt}rCag;S$(`X`k&0rE&Rmg!V$ZR; zA`2zto%{=2JI6=C=)s7pc_J^OYMxj`Rr;N%U%}|}#1fLTppTX`Q54ZKFe133;PN92;^?tphtqU4io4QC2S z{tina$q(VGVv^FZl#opAH)EBNB!;DecrOwo@5&n&^{Ie)=B3B>@31gSnO$~#*Okz7uhT;XlEM_J$2og)G$_7k$_h&U8` zl=DPlL{*+hrYiFpx;&8*QFWe3kEl9NWKxy+4BdHxM^v3JawDqF7x`3WK0|lDSQ3%R z7e$o04pypvqbFZfqS#ejAZjA2E)aE8RSs49Hw7*bO(ZoSC&Br1p=c**1UU`lBGEGs`Ph{EER<$KadoPVv;ex`I)Ok3CUg{*N7-@nW!K+9HbmZUoL7$ zB*l<{lWUSPY@q zm0lwPf4bMjH6jKj*BFy|6cN%9ED(;zpB zYLe$c8bDTyT9UUxUIn>H)RTNlQYsorwo#uoqM77h>T|PbC7ChckA912C&>iSoy$Zg ziak2Fif+m*giHTpOT_P3*UKxQZeTLs95=g!P(eGHO5J@C|f#@^g|3os$uG`0W`zFD6Rf$xRgGufY z=_IF;REkWJi%ISk*(5iD6vOB2?h`?hhbVKu$R&A=q)OzIYytTIc4Mt5AlXit2Sg#s zlt19r0mwWkib-aJYyqhjB_t<+l)%w>NR*M}g6Q8eeppnJECuO?sz*f3-~Mgp%^-R= zYD65#LnM!i1d^9Y9utWq8%Z7)$td>d)QU6|dvt0=236@#_3HW5itLE0Cqzy})e|C* zs$YSN$ZYALM!DN#bQ2T7eMi|F&TsEp|Iw5X=4 zL!nBaQBR9nlH)-19zG-LNEC=Zqt=NAlJiOGMH9(Vl4nH=Nr>b*(MEC?h(4p97ab&z zfatTOL3EKkPgO679+J07UKD*KpOCyH21t59^eP&~5XoO4dKE7VZ-;+wOd9l8@rnqL z>`n5jh#^TMX%cZHr;@xT5=j0BqF3>{NFrGdqF3>TNFgbss%DW!@({_JB7@{blD9+_ ziap2P7JQiGP*odL{SITjEecWWx%!S+6;bt$D5a_|q3Y;WQQkYElH?bVf1uBLQBCq6 z$e2v{?IBSc(dRu;PnpSo`Tx=06U``g^!G(uMAiGEgQ}9C3ct-pbVX!75IvMR4lWU>IEvlFk3?=n)kh+qs_ulUnK0HzVo^kLBKAS}{irt@1kxH3X%5;igL}rV~rObB9Y!M|S2}5JN6EBPMJ`3^7tkeod53q(II=oS?u zy&z3+o4Hj~k&ONa_7JMR7S$wsfpCy-L@mkDAo@N@k7yvtrK)d5BgqP?`c5>H+)Y*A zi#C$yLC%2Ay`qEUeGq+hZxg*F-;n$uhDe4$^jY_#h#IMP6@I<$Uw_VhB0w?+M9=vr z5kqn!h@SJ$BAz4vc|sHz33^b!1BrDRT(h4KFa@!ZW0cn|6BbhdLybl*-x2sAyW=x zc``7{KR0d$xf>-7#lDAXgp42UUzO(F*CS*yie1-8nTitfUVuIiL!XhdkmNm(r$9!@ z3X(4<6D2!Hex=N4IY0v6(}I1*$kZ|Z45x$WK4WE&?+GimV)Tf zr^#lLTd2=&vK7T1vLD6n*zR%=#Xi%uy9`XyRd%0e$XFCxHABXu*zbay zArm61;$?C~RlH24D)UTNyv&HG+CyeXRP7;yRArv&+C%0=RP8AXBC7V3g;aIaL_SM_aXf~<?3Qz>K^6&WCDuqv!6^t33+2jj>YdQ*iWXQ*f}T3_&9eyNirG5_Ss*iqJ+G?q0cU` z_WdPCvFF$UGB={?0GUr!2SHUFR2?7-NRA0hA&Cl0G06pCDIr-NmNJqxVW}XwKP*)w zPlu(3C`uhVfCL!Uk1 zYC2FZ@+EwK{y@2eGUopLfwIVCplW}tY9cZ3JvdOdpxA5qK-rFBZvhUJoha}bgfU~i zH0%?nK> zNH$U*bGw=%lTzHyDKZVk?<}C|V7bJXKn3(U8{|+~gkpcedrY;mZI1`b7eV7 z$lCyYRzRP*vWKcnpTlH-s@vx<>CJV=nkV}Xb2Ibg5Q;tSX)-WRXYA-{G6n^H`3Oc| z4Wp;YBoup0=gY)2xAT0NN@8YsxU4$d%^WW4NKEDknUU^hj*y(hWYT5+k!~hk7NXcW zA1RAbLSA(czKIUze55R+D%0mk*?qLz=SbO4Vlqd`jAPx*QIeCG%+azu)6E<$t5N*< zJarv>laFljCGhaaQQo5<$H*3vYvES{e@W8Pwo{cp>yD8fDDdrP|3=|Cc8u&BmKhk9 zi9TM>$L_{4G8P5i^*VN}_aN-XF)|s&9>HT}S(dwsV`UYIS?O`I!rjg%$qE$P=VV!h67phT$2N?EZw-%JxAUp855;y~ zAO}!F-XYLO-#c9(qol5~>tZqn#a1yHhXU``fU4=!;Jb!o0!cndJkpVrF=zY&_S^ zoGDvLOy(@vbDodA@`@=gCDVcY8;|=&!*!mM6V@y^1@$O$qS7^a1#OB$;@DFa01bFvIg@ z9*W)he3^EkyJPt>i^S~e1v23xH*2s0HUF7z; zNEVQo%p%!(v71>WJ5lU8cCqY1v7=us`%vI_+x5|b(Jz+$B&$I5^8lB~K@_|8OJzxc zJJzMLg2c?HKn^T+GX*m0GG9z)u}r?)%`BGbBqnp2Ok3h+E|Xa#CUdzgxYEsBE{joC z2P$9{pToMA$P$uTkgX`?BuyX#`@`>=$x4zBK>kLl@g;ozTq)~@Ndt=ipQsl)Un!eV z?2Z*mzRaCrq0B?E$Koov2qolw2BZH5eXf#4RApAWOja#-`z(`nBqp<5))u*$<+72) zWQt_#)o!LpcB0t%tdQL(@ab}xkA9wQg^XUQtLzzdwTwftRaeUd6!;ynasG2eSIZ=l z-9YqnL@Q+u$$=pH94nT2D0auLk*U|XGrUG-l9*LoD}&d$nQLV}iOF0in{IG3*U2^# zlUXIBZ*()OWIT#p*Yz?HCFC6qbJp*|y zfHL}*izlA|-|{3&Q0#7$$cmfX8J5Tz6g$HkWgQBg?$f$BRbBByYvB%<08IKb3rj8%$oe2ALr%a2;+$A$8vo~b)_ix-KgD7@? zDrCZ4?pPHv1;vi_e=-dvpL`@j$ZJrG8U6GwAc7_kh zo?%seRFw!-`ZnqzIe=o1&cib4es}E;%QO+zeyABL_&tzXw9jjHAp@h5- zV5}cttX5h7tgZ@qU(gI&WiN@oFZwHF-jjjn+&RB5V^H85?WpQ~nSf&F^MTBG-kr|} zlB3x9w8_M^T>IT^#(Px7k zq^hYxGMApCTcK$>*qu4$jvK0k>y?3&IbUI{r zL}ruhrOY(Q%!YMsl0ziPAZZ|<%IH`0ShjPgj75Rx;vl2H|FBagMr5|gWXj}1M*lzE zA~S|%vM93*GW!4N7RgcUxw=K>zUr=Yi!2~9yYZPUc+JgxCW}$*K7TGtQS2%{m*prS zuN+1{8CLqatc=KX$!f~zGh9KYOE!?yL)AGTU&s~|JLj)t$LsEVzLGsCcJyx9hXS9N zgFZ{3Pq&PH!`DW-^%C~H}kEGCo!4tWa4@^^PNmZu{;00Oh*ZMm&1IDVduY>Wh84s zZa}GysOpuql=*j4J2mHKgi5BH}iuGl9@5ER#Fj%+E5N#AJSvJ)7LjFLHpyWcp?K zW;fF>t4U1eSJ~L@odamY{^Z{de&n{rxU0NEFB|a7+he zFZD5r3{&zSuw>wO77V>T!o zheyjd@#3gUS3l0&kH_$M9{OV=`s)EF1#;Jzi-OM=ELSi!GRpxd# zGhPKz?9rK^a#2Fw0L=LvnDYcxLNaN}SZ@nRw5p7#nxv{JlLVQ6ATvpIq1cs9Qo%pn z(I=^V6g&E4RWM8nQQ-atbk@)9Ojac%`dPl1tkGVK>O`?)?V>VvxMS_2IEo!>ipoI= zdFMdqeWA}3RYX;0$EK>1f80J(RRxL3#H!Z++)S+MBr%y?Rrko<;7r&$!n~_L;lNNdt*jgDCbW&s2G%-CdojmXMg8-$PZ5bu)XY8WNM)Q#FrsGkdCb5|c?#*%RDM zg33j)$6}VsM+tdv)0}6iVyZHI_EKfhZlAqW6^iY%x2i#bM}N@gXjuE+sv#nisG2DA z4`lRpk*L}tGW)0w%FLWP*2{uE`>3Re{^({t`>NDk-1+RQGD%EkKecFzo7qnlq1a=Z zq)JfiDw0$gO2|7BMi0X1Nvewan9lpFs93l2{wkKlWDZbq)7;DfDv88o4pf#qNme~5cFwa^ABukjAD#*CbW%~%{VLOEj*6Y(_L-v+NlfM-RT%GP4pJo~ zCX=EP_i!^QDwV`!4pu1%ZsuT>fnw);h{{3-*}5s2r*?eGXMkd%1lMRc$0D zld4h^-At;=Br%z}s&5}RGgl3fn9O0SWIs1^n5sarXUjZQjbc|ZPt~G?yaHIoLO5II zsivJW`cZ$H>O`@R`sb^-B!4VDx_;C@U#0A?#XjnvuQ&EWr=P0K{>)c>$?pElS3@Ya z&*3U+w$8w-(y{*Il*3gl$+aN*amwK;3B}$X9-(69xSfws2_$CpbhRYK&7`YUBqnpD z;)l4IBUK)XJ%UH60u(#vqf{YE$h#Y6xD0meC{;#cURys}m7~}>XQOaiQ9IL!}zL?B$sy5Bd9H$yl>?$%74cm(8R)lAX?nROtis8$qvys}mHk?y*( zRW6EM*QqKW#Xcu|swyGT&kZ(0=LM<)#mWDr})lyX_%tyaQ##9r@k0e~Rq1YJ; zRey{-L!p{U%+Zmm{a806RX2&rDAjwMn^9_z#AJf1H`C1o)gXzezC<;k*v^-#CX|qOG4z=UeJ)j9RAu@U zsOGcXJ_V{B#r9dOI#EL2YUpzy^jWNubKR9*raI1b`&_1aNX%H5tLF3E%;l;b#g4T^ zb)tm4M_?>{CM;0{RAu^Hp~}y9`&^-_QT#soin~%ZQO0~n*-|w`qWkFcaH&eUK=-j% z+)~9+?CT&)RgN#=D{iUEql~%Yma0XRF|UIxRqZ6^id(8WQS3^Ws%{iG=V7J#^@63U zf2WLIMWKqm&|j%p#Z@YX#H`{f{7#>ZOdC&oad?_SbIavrOfn z*gagP@=)M;B-lg!ddf1jgybs_{p!gwwQ8p-y{j;16nksDTy>F{RV-JDm+H~&xv^Yj zqHG=a8+4uzSHW_X?MrxF%T=s*%L3Yq@GhvHJ}3p^Vw*BGpY| zW>};Gi~W7R0Xm-qor_eAFX8zVsW{4*`4p)H%6tG-9IJ{+%zTPe35uOhk*cPQna>K< zO=9M=LiJzf?$`FKJ21v{duU7e2xHG(3m7v(`{c2U_OL&G?s|w1P8D6cbC}YmmtJNThnc>ySyHby3 zXLz-WN3mz!N|iujX1G%2F775IMxWWW5p_i#LTByC0*t2Sh31Nu{&0*xG&)u7ONb}m>CwUJj$3E7OQ#^Gs9xl zh+^;i6{{8$f6n@TLa}N?33=1v;L}WS_7k845`_3`-D_FitSUPGEw|K`ZpX)R3%lJJ~yheYu!FKswxtbS*<#+b2F<|FNw+A zq@r(dGdHPt5|b%anISh*s)8tX&TCXI3cPm()~=t0Sfgr(W$G#OemFBUEE84Yk7Y(* zqk=cOqpwl-RSlet9&O5MyYDvrct%2f0kH&dqKNlfNe<=yONZdEZP zCUcu=D04HnsTLG_hTpE*QS2Vxt~yct?*+|=Yw&h8Kz&T-a@BLI+qql~keJLJD!JUv z+@aD@>{xfIOq7uK4IGP0VXQkm)*yDH^F@FR`K`fs*sloqR+8=R5FUa-tSR)m2T&I z)DjfC^OdR?#g0{}N>Jd}K%ldJeXvrMQ6DqsdsWkYZs&Vd8;Qx>r;@7N%zY{i#s4l- z{Yu~cDhtJaO0-H<_!;k&3Gg1GE_hd&szI?ctWtH9xe#V}7wkrr>WIj!Rb7->37Hzm ztX1Axe?Df0YgN$$?hMzeQWBGSKn1Ja%mXSP#m?tJRe%ETW`VKvGr$k3QmQh2s#Wzv zZl7vZPhv6;sg6h7%tNY&#AF^;%{6Z3VbxAzGLNXbN8QXLs)@v8YEUNa|EH$wCnQeO6DaIFcJc^ly?rqvA;( z2hqoLok}EG52AlHwO*x>d{0%+s&tZ3GyG@Fo>Lhp_NshdRjhM&<9Ss>Vs^Dbt*Uo3 z4XT{PWL{7O&$^ixR56O(!xvQvO32$2=A&2fqN*fGBY8>HQXkW~QDru`of}n<#AIGp zDKEI0msJLe9sL!Rg%a`vjrEGkr7F|sRTcfR+vimkPhv7nYA_!dbs&`a7WuAge3uN9=ohbGkTdxXQ z+|k#oViGgMR@M8Cn`u>pBqsBoYF_VV-c#))CiA{Zc+bteuToI#N6?~H;O$sHmSZ2cZQqP5Q^VN&*xJWx5>|# zM{S!`4vAi=eqL*{ivCn*>~j~JRWi!f@Hc5}R;j*(AGK{(>69^#+BU09lc8_Y*sMxP z%%ir=svPCJaW%jD&-ZRtl_;wNf$dTFuA$AUhB7z8FGH^XJj&}-b(HxLGR0qNX{5~0 zZT>rywy0*xtlQwfLurd@qs(bzMtjHq4Bs56Iw*5MD*OuNJT2Xnc@^eUdM$jHpz5W} zkucT)kojBt)fgk91H#Y{5PtUGOK9L->3@8oIsf#RYjR6 zDbu5BDbx3rf4sg`^_2MoL|-Z2swT=Tn&O|C->DYLd<&xI^POrZ*#@@&o8T7Ud(}xY zB7UsbgVN*o8F2P*w*9sdKN&^d)?CZ&TSQp}_9YS+9MY z3X+_d?7!!2o67YCt4_j9Aa?irS*r74ni{hEXSv_I_3=B&UHah0k~WqS8?8?Zz*v^>bZi z-$V6_>O`?)^{eh-(u)%Eu7%F}aZ0~R>hedg2}=&i`yeqepMF(N(hss9$gipm#m?tf zRlC)l&#$VH#M~PHrW(I?Gry@;5|jB|4SeHfepgXFTI_or2UIkPxpD_ooXODpwYIAS z67zno?JAkXykF}Nl}cjXul0w@@Fjfy{Hd~r2}gnFqh|Vh_@^pBvAgl7YW&V!#hZ2;N8-pt5N4L+QN3uuQXH>9}UDMh9bl_19(2s4>BQ67vXgOwjwy z&5R9plbFodV1Gm=5N!Y5-LXKho5YMYE*QPt&5R4ilbFo-V8NemW_+-i#AGG}v;T54 z6N0%UCKDa3``gV#2b)MtW@4~>$jwX)R+E^_q+r>4WtY2mN>>8{OxP5jFHlx@+(}JxiA@54+Gc7noRi@9hVB2`N&$M6{%Id&5N$?xZ zyN&jC3-*xgGG`3__uMVmhvMJEuZLaTEf_UH_pzhz9*iC)u_z(06y~GQn4@OPaqr+o8ko6!lgV7`}gKPrXBN$7v9;6Fo&tM|SCX$3;DvDkE ztYCbMyRKQmWD>LXy@G=gnZ1I6UHmGO**jP?#qG0quoT7aPhzkf1)dXz`RHFqN(@$0 zmFcrjuy~r=XCL^=2Q9YGzQGC<`z^Bj25V64J|_k1BdU^ujZ|fRFDWV56j8N*ur;D; z|6n^+nQxKZKiCyfbzrbJqUyk4KUJA;kv%Xt6j7BN4D9BvD>)d00?&DNz`c7og30iP zU!8&XES)#T`we7vFo9$c_He{0@GE%1B$Bav!Z(C~9286;nMRTlOe5I`WDm$39Lyj| z1vwPtkYFx~zt8#+>!HCS6#KeoYOt3?A20nlB{f*EyPlza#G3m5?41j|uGO^g$MZbz zTKiIbo0B_9E=4X;5|K`}E-I4e=(cs)rHe|bbd%b(DXKyMDhleq+or#~gFaImevOytc;&<6X}^XwhQrBcajw--BQ_zk;uduj0S4ZC)GZSd@_v3~me6l;}7r?m3z?rXQJ8zt9` zwc9m~qW6z!zFw<DyH=!Smd9e(-FgJbSIDw-mMu3Z8Y^U4v)0;8~}=Bq#?4<$3Mk zG2Ieew_VXFdIN^?q&G6wZ69ltTr;2Fu3p4?e!FJGI!R;gsj;5lt{arIm9nws;sxyn zLAgXJN35!E%(a^b>QMrwtENV4MEwc zJv=B!1!d#*grJ-plug<*jgnK}v^_6)&QYGZdOOOd?PWpvR8U^l-V~JUg0flrlc4;2 zP&RK@d3?8KZqcr8l*RHZ<=IK|wMDy0!SnKV%i#HM<=IPlUf%9xlx(qAw0jq^UeO*H zu^#*cz41n4y`nuPD61-Eky5s7?+(g3N;yd>uWauL%FC3J>graltdexrvr9@zJ7Ztf z4uZ13QvR$wZM%|D++y!nIseu^-6^wIe23o8p_HxLwS#i9#`=U&UN|9ES~uUioo^If z2T`8XnznAY3Cb6PvQ4{pP<|McZQDgb`9n~)Yu^==hpg(gW&8FLqhyOMXs<9zF69O7 zwGrzX8Y`u~puIULFH=ftm9J@k5|rJQlJdSodv{QdQA)DUj_vP*a!$nBsr^e(t_;e~ z?cfP+)gLJ(*?D2RVo>f0o?Y6Ng7WYuI?t}{V}r7`Qj$KeZJ!jBt%7H__US>{Pbo>C z-P<*Ta=cQ$w%S1UXx9zO*-A<8DeT#98I;SElDyXI+C@S6u~L$Td$q?0MV>TEE?1tt z+cSgmNTnowUf-SE5t6whMxCr&5xJ2eo?#<*!Ql?o)KvS$jfI{^iMf zH?PJzq&+VvPYueU?M+6>`W)8Y96T>po}|xV?L9%+FJir^edH5epL2q8c)Pk$Tu$=X zZ*J!sCHLk>w8sPmk9|aY!jqGn+?yZKUTl=?v5#mkb;{6VAJJYBJb3IQ+G~Rck9|bD z?o*QF+;<<*ZeWz0(<9nVjIvl#zmsx$M7zfXo|KCt+halw{m!EH+Mtk&MeU`lb#t+( z{jO1RE*7;vcFHgpi`t(B54l*>{zveTi$(1wt9Ns;sNK>iITwrCZH%&5z8&SYsNHV@ zPs+tn?Fk`=TpZor7!-1GboJi5KqC^;8Lw^tZtv0R|BQdu0`-V~G@g0i^%WfAL`_U_>Mb?_Y1 z&R!$sB1=BDooAHAvf^qk`Pg>XpscEt)S8ZK&kV}C!Sj~(;-I`dD95)~1m(3sd29P! zqvW*S*8Vtn-V{78x2qT>%Q>N4J$Qbw{*V&Bh{YGj$?Zu+ ztW(-Ei&&?$=S8dwHSgclx^YT-aS`j(_Oc?@sqIw}>kAqy-Nkfjdu>p@p_H3d&b!(h zgK}F?PHS%t${&Jqdi#^0tg!klxlLoeyZvQQo}iQ@=RNJ+L3v*Aytlo_D7kdbXyqAR zf?GtaGupL-vQtpr*Y05ykCoOq&TJ2NiX5OkCuslm{`SWcc+xoG1MSa3&dJL2OV#HC z?ds3$*5|X^HI3r;ydAA8I%l`@C-H0^Jm;&NkM1x~o&}S5b`72@lqc;gIJ-U2D7lWE z(=IAvozos4v3_@>?sL>w=d>pT<(nbr-1elP+^Upwb-n3>?R$dq8>ReK^L1YPp`fhz zRK3$uDd)GB8pSP^yw(NnwMNN3$c63TS;-ncpOC%>FKoYSl-z?{*#6#m=B%NTXKk+c zytRLcSU-v#k}79SlQRdebz!@bQF7idY*#T#F82%D)s3Q~IF+2<>b~gAipDWbeB%McfoT>dtmU~89bM?Crsc; z?ducm*}+5m`eZBXq||d-m$tK=qWALZ9wD{QrR^#O&!^kfgXc+4)0>4(AIPWM`328s z+RcM!edRgn-MS~I-E|Vr-odlA@~ow4eWpF8;Q4HOLh$SxJfCfUQt({X{xW!OK2~1| z)bm!Cwf7V}pKIlLmQy)O5ckA+et=%ao@Z8ug+P%wj zV|$WOvS<5xd!|ux%l&$Lo>3Od8Ct{V>&?JlZ?6c-hn2Fvwy$rrvPY7n~70Po{yC5h(RLbj>@~!spp!`}Xi2_CrCrH{^V$y(%bk*YI0*zuVp%lt(Ki z>GQqz?x4I#Dd(!3@3*t}bbWSG%BPg_@9n&xERI+=x2p%`0;Qz;dw;6%D!t1(L{ZV^nP_X=u+x_eV0)d z%da){?`Z0`v}cAKB;VRT^7UQGx3;SX1)kg5!M}PpcHTi;DR_R> zid6E9+qG#(E$tI4g_Yh5>u~HDvyA@mtrN`jl?|`urdK(i2I{YNAWgtQz#I>`_Z9q< z>0Qf{L*HkIo=$IM{URTIdX({rxBshCertKP$!TPP!g=#_Zpom|vxNMKeDmft^5^+( zr|Ht)GKHLE|B3NSyjHuP7V*w3;8}*(Lp>a?HIceDd*2dAIR)&y#79Hs}#S|S0xjyM3{UT;)H=cl{4BHUpu_Z;H<&xv~Zr;Q!o$K=&=c%*Y|z*7Sf|0R}wDHpt!SD4)z zf$z^RP4f9w^4^ZTN?Z@(SK@k<^c?>&p|{7&bmUwR@*U>zPX^uXSIL!z*J1Zct_yr& z_?b(sTx$W}U+JkFkdyFKZr=~N*tu-KcZS^G2Or^M+qxe+&g@mm!vlBvWc(=>Udc0q zZ=C}F^1mg08hNSF2Sb0kdGNvS`Kjep)4!1q85~5sv0=Og5s&;6&)Y#I?!V9{L-O~J zhWm&9_mv)RHu-ADNaywDA13!J7f1Nz!GCSwKvXmHAz03nCH1d4o8wlaRQs2#r^vm#3_;#T$<+)3w z`#Od5^xk04XC)6h-}Q9=p6f6E{*cfEe)^NEqMjTd;q=2@Sj%Y<{dQA`NcqI7$82K;Z zHY3R4b3}Gps{$k7JoZoArycn0#&bxRe+gQ5waQsR;{Y#YVUZI!EtK{Io7Z>r$ ze1ZEoy^^y1IK7tZ<2&QbkauX5$AK}vJ$4_j#|y0=t%l=3>eD$9@8bp=*(3bW6^75e zTuVL42XJY_zZiNV$6@v--SB2O9{zSGUZ&q1bPsQ0yf_%fyDh?hZLpFq-iVKbs^RzW zZ-Wm=xdHzY;gk#cD8H+w`+e5e?mV43zVC8=(E~{Oz6}i-EyXW zj;;sHJ5WB|^6W3)$G+bCt<%m)u#rvT`}osac)J3gZ+I=28*BtR{b_TV^REzgX8z=n zfnRW_hm(KcKA&;=Q;e?`$D0_JR`R9z?joFi`B51{@i#`{ylT5gVf z`gd6+CG`4S%leTH_4_#UOZCtW%ny8F8_&-cq2J+_ej~q#bnu@$TDbZngL)1v!Vide zuiC@qOwC`(VZry#kaI@J`LMxCUSac1{oj8+PR!%m8zNqp-pF@?PI;B^wg|r?;4cII zXTZM%^m!1SFEGEI>ofQkMsMVBgL?CrLEX1#P;YFt{zl)xFgRPbGx{9a+u&TGKbrL# z|6YHr!A72LQ1^aXJl&sXa6IDc{jnB*_Aoy4J5I0U`6180HyUw0E7{EGc|PQ2F%R`r zlUK<%R!!puo$rfz7N2jaq>XqEYhhlY#P|4v*BfjfV%PFBR>0%G&Ehzs!y0`yYUKiuyQ$s$`%NxIzVc&N-=>chHXPVqxkG6SZ zYA~zZ$ zlFjL_^!wD>Q6-Nv{W8utoA9TF{=l^YcYW)jzE3oJRkCKz9TESo;QxKVlcRhc>inkpk4FB~39rS=JJaXde1xv}?3nyO zBRf1X)!TvWrBG)BCDj{XvhNDUR|)BDZv2&$?T+wdr{v#Ky2wfR!&OegpJK3(xsk*(>_=q@qFm|OYnPrs-(2*D=Tca&L_9 zUmt$Rzuz0HXUU#@bl0a6=H*DAeBxVLheaBD|bG_(bUGkp3)>M<`dI>s=4^ z{%pj*I?_$`Ipt^Dr=)sP%l8BK`kedS<1M_BpGQ2#4dA~udNUkP$WBRa9$!%Z<^}z+ z1}kyAk`IOarw4ugXs-@w_mkT%^D5XCyo97f``~*UdwV%lL;dj+*s+AfV|>upzm|OK zME;%L4977`%QxM0`}pREz3KP-JFu25jh^d!Nq<}T-yI@;X`d25H2AK&-rJ*x*K%~w z-)gXy_ZV#Ce1ik|RD@sKfwg=q@E->Jsli&#Grb!5o#Fao*3MqP`L3K7IP3oCxpL5% z*N1*z!6m0t$Gl=A&j`J`dJx{4V6 z)0I~Z$0^gxhwfkKNpGg~*LLL8;&>zbcIe~>Sj#Rk&U1ZhIi|x;cr%IK{eEQrr;(EG z@+Z^1T{n^bU{XB7PweQ^ulLmHWd2ewmrppbma{tPflt=IwA0k`C)AsYr{Fwjh%7HKEgMQi}<;=Jfypd0K?2+~SQiooPNdI{Bz%B?|}EvTES?Iq%={_bdHvIlehP##{cq*~Is5PN&ZwtW%^% zy?j~F!M8BHmaT)nwCVNfvApQ4YcDT(yGMSJ$3D2}!hIsX!+N-nr;9goQ1Ahd4xD*+ z#$V`B!s*r%#!Ft0n%M6!i20JS;rs*j7`a`%8Ga8=T@MDsaN>89y-8yuJ4 z7@Q@4Q<%P=&@07D^Ajmv@|P)I+RvHdiFmxUpObh#Z0SOx zx_IWp|9O9#^UM0z|JM4|moMrA;Sagize`pP`>$@Wl4l40T!T8FU~nKWF{rm7#k}1X zhF9WH_mu~}&|n>OUFR=w()mX0D+hNyDmftd-)yjv6AkA6kaRx~bmCtW@h%DaWgV#F z48t4wR?x8nu+#^+K=?*M=0%x@q#poxn7;#EUS6L>{;p?E{B9)wI?m|PpLq8-$owet zq3CzZhiw zwun!@mlk&GPoCdqJ^_2+m!A~rd;P<|R>O7hE?=YXfv5ZqWQ&N0Jp5=0sn^>FKlK&3 zwE6u1HD1|H7&r30vX9xB{XvyL$6jSQ9~9+6y&)Vr5ltom)-Gb)~g)OtK?6?SHITBC*ZV$ zxj}!FL2dUPT=z2s-Ot(N_SePpxuJ!&FAly;hkw4w%XHVL9?s{(=k+1;L1$m9%c*6v z&<}k3kpG5)UgDnrMh*==mtP5xbbvnZSj!Q?51o2jLSG-${V=hfHr?Z}67{-RC28f3@K{?`%+CCeiUmt>01b&y>&QG$BX#RTb&=g(v-!p1UZ2 z(m&ZF)eG#or2M6F7?ls?qYv`>^x4z&u5I5d`lNc(?f-Nh@1v=H>wYxjAB%QaNm-A* zJ`AFq^%i+6$MJ|inIAp-HMMTHGO@LH@oZixWPtFGB}XaBOT6ZmeALG z=s)tlLe_y2Oy!E-nMlvy-?|Sb&Vv9;zY2ZH>Gh>k3G(ps$J_j8B@T6;kDc4pc_W3X z-r=_KB-f4Tnn z{=yQjl= z(;?xNzJB5Mb(rbD54ywA^gcNcjr3Oy=<|`fzp{t!^-|A?ML6p?d}ovp|G{{H@c@3w z_r2-*X~^5yU@e;)Y~+;&2eN&H(=J9q^6|OW?bpR~JtW-6eYNZs>45JadO)X~NdK_l zFX3XN*K$I{cX_%`Jm?&UHSl}Imr|0VzXEX7lQv0m^IzNO)Mk3@t!pWde&e6KTD%Nq;a_bFCF_?s3+ocE-K|X zUpS2>enDZ-#uSoKihX`_ohYd_Q|7l%MQ(YNzqYr~?|VJ* zD35i_Zq4u>A-ZnO*UI?_ z{HU)7<#dQoxPP~DzYhDt^7&Z5_ok8m2|YMR*rn@RKds-3&-P%B{qG+%)<7lzAKKC-vmMg|+;P+9|b{ zx5s%(PtWOToDa_VL(f+&tAu^g@0o_zGC%0dyZ6zz5B@!Z{}pyFO82eDJxADY6ntaE zM-SG|u_HL)iy~e89pS*YM)+wV_kzH`5IB5k9Fy$h@__YlJeJZ+{^)BOFWL782KD@; z!7=%x!EqVeYhwRW_7~M~KLF{@6f!;pVh6^_zF)Qx_ooASkm;i@-h^K8JFJC$SJ)4K z58eBZTHKH8x|+44fjleZCcawtMZ75FBZqj7H^XrDMQs)Q;MA|~zHi;f9OwC*p1+g9 zJv{ec(5YuTMLL{^;=I$|N>AmP=&3xR6VK0kt3R~!<@NB~H~Y$|kBrBN=XfP=vUK%a zPxu>OFB-_(LLc~-7M>LH&kA{?^r8RtdXf8M>O-jq-}@g4`FzKA%V~7^AeV9}%d5}6 zeRf6OOrd|j7K7jy7=_RVA0+Be=OzL#5W zA5+t3DmUlX_0jpoLZ9i{cWL=37xFW8Ib=Pj7q070QQuFo`_d`_F0G!#Lto~(p#y1G zK=^&$sy4ry{1p5X=@aKs*$3hMSQGvye}8%WJU;;L>+vPu-ybL6K=Qk+u#{Kg%PRk$ zB|iHT@cXQP@SRA1ocxCMkFkE@^jfZn_Q5`K`k}slj&SPZ*DSuS|JXW-zVjFNBG9iQ zmwHTpioVc&+|>nj(-6iIKv|aq1!uT*B*qi)# zJM(qmv_9(LW5fLkj5}X8Wx43B+IG%+e0W}DCj7PT`(0pmo-O-?y$>@uSKe;v<^1`% ziR`ak?>x^yy7&$9m-3VR{8@#(CDE7U|B>(gP96Dg!fw=8eFrh_!()Gq>(kFSQQwrl zw{z}YV4O8Ny;6?1%fV0|_IY{x&-yrDwzt;<`nw#zl<)0xApYH+^L@G5JL~K1Z!q)+ zE?>VFjIRkh4u;=zWjfat<@fD~=YFPZ_w@ZldEZ{>;pt|7MLCh4*CV~ZCeA-Wzbn#5 zZ~U`|>s#&?uI(nqZBz4A(x=ZKBfh6UF3mp456+Sm4d!w|zsDBzXBn>RAfYFIzis07Q@bQT z>h{Tc^~LK;2Y=x4vK+<}N#(K7E#4 zzo;jp^`CAzQND*pJD`5y-(8>FUPkHPXHWlLa{beKCHu#bhkpU5UE?qN=+vwK?eLve zK9zhRApM8;f7y>?uhKtX67l$+V%>*!$h?%x)A_LAhtJQQ>OJ~Fzr$e0{}^=Sje?}d zJ^hql-#nAgTjcdt))O4+IhI&IEax}Szi(a*J=|`2-V!^`dA-jQqu;{{9QyqovfrNd zBjzuY%%S%>65LAo?Sh`FhHeeNwLodEhgJWxFTc5-!_$kICncOZf{TA63BUA%vt#Pf}x9|iSY8J(}p{Q~@x>F5je zbSCqa>Eo|TI_a{`&3)-W>H+?d@fGLgOTP=9?{bH=aQ_SC#=R?~?=X)u*pFVGJEFe0 ze)*g}bojgY^!L#duau9yv6GJvbp0^y@17dx9+q71M&-ND&ZIwGh@RBj5_0a)-?yrU z`SASL^7-&b*rRLLW*AO6b@}pnG3Z|n|Mi_hF6R%9i2XomUNY_LO7p>~Jy3r)t<30`OCiFjQV|BD>-LVhvk!wAQI zK(u@^ z`|q+}>MK{|`TqM#o*wy~8fJSFPQ5~Jhq{j`e^wE9& zabFGc9)R9|Jz$4rvjg>%`o?(H@jOm}&irE+&*Na)m&5G0`udHMkNDghM!#F$8}9oP z@;&yyi|>p+I{q8@>S*WP`2xPnE7>{x!7jl^|IYZ#p`KfbaQqJYlS@cE>MP?^ApRFV zaOW@Y?J4m}urKp{q;r3Tl)K*_Gx=T^@=f{R$N%2>p?`Qo)Q1D3y!~89Eq+d?5zgt9 z=XSnyn2)a~@=ZMNTju8$tH-&2bv)1el>NxTq1R!d7j~k)axVPM5zhX9aM}a>K<4TH zUhss-2kDh|0mnYH_fd4hfn|KgU+=T@^?u~AQy>2*x}Rs%cOUlla{pw=y&>d&BjBwC zWStl~^QG=r^gK=YH{?29?_&;nnXhgEjo?pP76=D*XffP@mjUa+X*8WtGGEu%*oh`SklEvR$zg^3fAJ1D7^kEE8)_nx3ux@Yy12BPF_zy&wHc&cJZ>_aF6BWa_ZVu z@3rc=w;20!zMS-&KK=A4w`GmbI|;nc!Z@kKq4VyUzeCdq@3Zi|E&HW;i$>-c9LPfr zR*nCQppP<9`WY~PJG~&L1)|oB>XiIPCkL? z3l6-$*a7{4r9GgNKk!j@qF#_+;QiemEU@D!ugwsKak@hU!>a??)1F=zU2Nz-|@5eb8z~bqag26xxIMDPj_0r!TXQL8qD@B>CcYv^c|eu%X(qp zoD=6>yN!d+y~)5*Kl&Z!8Gz1Lo2&4RL%q86O13tBy+0(@o62x->_oYt z7ygX-5av06(D7?7FXsa}r1Q@LOS|!XxPQzK_;y~M@*b4e>qdOva3!PYzMh%SffJAS%{?96>Aju%9p-(frVr$tj(&N4 z)Z>HaacUp^@73OT7x}`_`@!4!{LA%*tDiSG7`{XH_tNX1Egj}_ZVx+py`;REZ`bib zyze~<_T}H-U)K9lqkR1R=7Ib*(tXJOzAoGse-z!@OWA*o#_>nA%SVJBK;CVBe9*bS z>F05u75!03_jQR{R*QJ$JT2!p-S5+H>N;%jf8Op(spSO)zGZ=bHQpOPb(^mJkdNJV zi+KCTK3Cq!1>euY^_-zWWyQ2hLZ^Ng?Nh!T-s?uL}IqxaZ}%!2cNfl{o2s zBlxfr5dFLMspUt;-^lF-2l5{Vv0okZW=Q`-gqQgx{l9kNSHt&%3CDiT0d8OPAsoE4 zFZ429uFveQ-AvO$p3Sq{s5mJ z^bG^zAAH=P@A^dlOgm#7;OXUlx;xHzV4Sn8;~d_7_Vv_8HjeUswZSpj(ct*-es#+K zpB?f&>iMqa(rweYK;J*!I6=Ly((ti3w=gcJ7(PqhYjC!F+u$7ekiogb@xyDQegpS2 zT-Sqx&b#;Ce)L|%pqG5mOXz%+99_sQ%VT95&(-n{G6e*3ab^leyezhAd~ zf0ZOWjpL_=dVjykCtUB3jC+6R&#sF2;J1BXqP#lZ>(%>O?Huz!d_HkZb}_l*Qu1Rj zf9IF;!eip!hvN}{R>Yq@jGy|uG%xT8&42n1`9j#?nvW%Y^uAsj4>rSmdOF!I;Ml?K zQxEy~h%?8KhGW|}TH*Z{i9`U`N z>X*OSxe59p;*H5~g3rslGP$WdDSy&U-y^Bqxi9&VF+S!!1@OOwyt2KVV*G<>Cu7l0 z#^oTR&yqb2&X&an=g3(G=ZYMd`pFfd9j!QQKV?1c!>NCS^WHS}@Om_m*O@)leo-I4 zYU%5~hi;lC=!ZmQ4-LksS@rm7PD6>d^{vh`|+QAA=7X zzH>u*4r_V-_TCQA`yT~M`^{7j>JjZ@N%l|oc3^+to6LT?&&^;p^dE$u9^v1Q@#;Cj zhn}B}@N0t3cl9W^!a{HNzuh@Oy{FRfL3~G7@*XQ6y@%3ZBPT?8eJ|4U_N()gL0@ly z%LBh9^!ddWUd|p~Ny!J!ytBvG_m51TzV{SzfarCIg=f8>6OZ#m?r*qlzY?csf9Y}tGC$&bIDD0yZ~33h|LWl9yBz*fF9pkER2x`^lF)kfwX z=jBK}2Qr>@SoiSN^6ij|zvX)o$amu%mwGzhAL=_Galc22Q_o%<-<6MloyUU?#yyw9%v@_gTs-a8-ZZenn9x}IK^ze1$v z`EFz_^D~t^Cg|AD?W*S(EqpBUMZMN@Q8sSS{Wa#VN?gyqMLwS%a^ds#U(0$%=e^eK z-)KMXr*ym$;}iTVe$OH8sFDpM9sCe-HtwO9{G06S^|hoUhwoC_G5P?vuyiWfHX!}L zBaTjT8`-Czm$>s);_>h=OAp=twJeTwSJ@-!QOjwDH*%rDfqc>6nEWXCerFK7HbZ)S zu-8-89T}&tcc|xYmW2=GQ3fk{R=^iTJTDjRKc;wZEa}C2V@nE8$_6jz@c+CA%6vd+6_o z@9jeOlkAlA8wCGYFCYErJmc4Un+@i4(4YL^Z%exCtLHt#|E_OxbiK~>Y~*F358>$R zep>exhFv{<{Mxu&Y4lk`x%hSWf0bZ&_dAVzIpmb{w$F%ix!J;N*(S=3{@KH8`Kg6h z!*J)zbPw0>L(5MkzYRa_cpd&%-%klWszXxy9Fqqbti*BnvVY$DKxRddOzDE`QkA4w9=<};O-yZ3{H}ZjAK*|xn1;h_Hp4+>(JL+{K&X>n2AB^;f z&-iYpIO7{1AL+XY*8T=D-WiK@_1-F@&l=7XxF6DY9m5|n9weWRSMmL(_W^_-z`vj` zb^tPt^?4;7pW1zwdj2Z%>+8Ced_L?<`bXLMvw^(9;Fx?R_&KpF{RHn(>U^NBJLz|~(R07*^z@Fn``JTGkK8|Tk2C!V^$!2NZ=~aR zZdYA;*)LVHmc93x&+(M_hDG?{hU@;)fTZv5hUR{ecSPOJygO5i`)$3CE9`ytJ5zqg zWR~qG(f6LB{+4_vM7eCRS>hib@_W0g<#Y{C^OzTg-AJGISIVQmEbEKdehkK$IzMmi zUEfbMf1LfpQ;lzyzJr_enJxaEI7dEV>Ccswj9!I5sbd_Kzccy%aJl+!K+Ln?SG+!q z#eBo~a9-p;-<8s z&)=cY^R6S(Wq!x=o9&2RcUeA=Q-wSo*W0|ien-AL*{zE2YUPXXYtP?cm_P3q>!E+V zU>o<3e=_-vREK%Lil2txzlZf*jonf@gYXMFZZrPzsMq@LnT6}RrG1Z$=J$1@{JlZC z4mpQht|O@qS9(2pY)4on zc>?|ZF!^IK&K?hY%#w|bZ}!m6+nJnN-VpL$ZQ+%$AL$1X{%(WK@cW5*2lh!YufTp0 z^ke>qc86Ra2jp=F^I3dXvu_C;Kg@WEaT4hRyYn1Pd`}K!*T~nB!^h+R;~S6qJ4@ak z@z8^OjFRK?lKMW5&3nxXKQdcRHhFVmUUIISHBG%{Di3`MuBk0Tnd?Loz)Kgzy%XXlAzY=n(S36(q?G@;JwfKCqzB3*CZl`P?uXh8v-NN-A zT!Z8C$Kc0bfDipl%$K@dD4*OenXi>MCBLY4GdPgP8f1R9mcDS>;nRZudvRV7oc)7c zSj*axp8Ie7R3)@?- zhdw~3>v}=-Tkrv?FU*g@-^D4P?6*mu@jB_T|HYx|8+P40?1X&wFZg#0;}6EC86Ify zC?Dv|3siDMgr5-d`Ceq4#&=Pdj+|V+h^Z3Y{8zrnoTlJ-HQ?~NM_=6OWts{}c-4)<{( zdOh0kTGlk!$Z_%RBz|?}gOgm{2NB`USIPW{SJHjIkd9L}^6~`teGio|Pqa zZ1|Y$Yp{xaM_CT@4~*aFclfSiy|2WvBlUY<^K<&$VB>fl_eHv=862078Js28h8{~7 zZ?@cG@#n}NL(YTW>~=?Q#>=Z&yVG+zF@7)Y1`foJtZwU@wX7BEoI3_QH|#zN!teW3 z@RN;v+~f@8vXIC9!MsQ6^&@|WKd|r6{pRvIU-5XWC+zENey$_W!*uDD>=<(J3&1^s&iuvmzu@hib4i>Bb*S^l!S8(eyl)q8 zus%S3`f!h@`_TVE<%QlKw)FM=N`v|P z>#ERe$$aRq?{9bHWV*{O%drt(=N=6AKa}OVWPEab?CAI6(oa;<#T)rnh9kS2&_DI+9y#E(E=SYEFzf=wH9T-KYJ-}Du-Flka+jRNin<=C{ z{Wk0{Q$1#y9{hic^nj#G`%EzHFJ9pY_n(CK*{!6P-+TTpH19fB^6-#L{(;0}{93}5 zEuNk$iujyEp$91~;!H7pY0^St#TYJLU2jo!i2@bw4-U#uTpZRl#uaenEdcGWT?zVi7 zJm2%?=W#QRzQBi?yd2KCc!tL zP%6jeci!CFUoF>L{mtzW`uC3VdhPRyyxW)80pWYDmRs7thaCDNhgwf|^!i8r0kZz; z^@IEPGlXw>!)|*1Zf=?%b2_-b+ZOe5YWhGn*6?}r#$>DDEBDC|Zzl_HWKV+wIVkwc z@V7rHE8lvUAIB^CFALZEKNh9(Y2?_b2lR(4 z8NCu9@+ddT+w+0_GDLsi<4s;IPYAhQzjD2Mf$c}n^^thw^GOl!o!7YkUNi7A9{7z` ze=8}|q2F;nz0WH4!INGIziIpSv!0xv-5}D3-}~QMcHG?c+r+~Cyn5P~ux0Q&UDu}- zPkypX*ZKDj-(=kVbH49@bXXth!xx&|e2)WsGo^E1gU8Q)96R(u>hV62zmgAqlKjc` znyH=KzR6!Mt-Xj>+O^MKrCrER$xpt0pJpZO*ZlKKz20>3Mh@$-R~i5Mo!nn@Zvk?g zp3~{_>HQ%le@xDcbUqi5@lIDxBVVy_?nfAl_~XO)9$rVjv|lj2GC%1cm+>n0D$D;z zp-&2*++HYW;NU<(hpKz zjd*w^q~mtZ{AD`ukq=KV+mC#&rTQd)LOYsBPvzsd-X|3OE%+MoJ%r!-P4th<*Y`o! zhw*VE8(8`Sd3o5e4By)5gWwyR7GK&gw}Z(YAM#`GQILJSdt17-9AdDMV+>X@H9hOk zI0yJn;=SDc*TS$P=uLd+K+2&nJ&#w**`W{VBlnV^f9klTPc2sk z&i4iNf_pK*Q+TRB>AvH1KKl4rS0dcyP5#bwx%oRA{Tm;FWbC% zIxY=8Y44}&I4br;kHalOY_u+*6 zlCQ*Rf6GeeT_EOdXeY?$-MURouUh^s^c&LST-oU`)1Ln)Nd5->&LZh8-8~be;LmDFX#On*5dlp z!hTD}wcbt1m+9E=NKHTKacbz1V8RnUwbQc<*78ZC>plMkJ>ls)JcTFzXXrd*ieGKw z<%^!+&aeB9gZ~E>KjVaBPft(hp(348^uLC@iF`?)e_8DMC4CZ3cse&b5hon}l>Zd| z^oU=!pAz@{=5wvI(~ToO{V?@o8>OfDSNtsLQyzWx?<=R7mPcv-gr{;OAK1yuLH8?8 zVc*oRmE|+49FRjjaQTzvU=Q>t%Zqx9eZlW5=-=hEZ@1IL`bB-&X`1z=TYnloSJSmW z<!FVc}M79+Mo7M zKJer88-$+~@f~J6Uu5)*_wgn9sXR)%aZhduKdW*l@|QUKEV;)E{Y!{lz8w0KFY?L! zBz8f+60*rE+u3lb~+bRB=^O)e=Pw~>w8#$$)*v8sfo?l}9nRV(v zoBtii?AUj*oyn_Z0sBchIOj)M*Q;ZlQ17QNm9@=o$1yK^zFvW>mQ|D>dmNi zlWsif)AZM6ST}Mxm4J_0NAhx{{D686Bj$st2af0Z!8#4&X~wbT`i$GDe_aN)wOLm%hrJtVQ7$hen&7zlkY`<|b2f2MW z?LeIm5BxUUZ<*&&=oc49f8lMMt*;CHH268+^q+yFPhbDZIuZMB`t*VRr$~S7EvcVuWF>%~cn;NGbR&r&W z8{@kj`(0x7Cd&su$LNi08uI=9`Z~T-@_rrve$;zVq8!lE<>YtQpB8puzUoH%KGyqD z!cV06;j}LHxuAQ!Wq&~AUQ4k-nd=ujQ>e{z>bg=&^={*TOqn-SnDX`m-&b-kWCUnXxb9D_tkGahCeo7;o)r zu$Fy;{u{eLwUR|aPkN<(@qGpd!8ax!GaUIjpZMcn9OC1cjU%63{U^us@P0kF4>{jC z*5y*3Wqq4Eoxb$4-n7TEogklfwnvo9>kT$?puvG07W(ytFP>Jo-n(?Zj}u6b{)PBm zyeatge%;W^r}sz>M49v;kG`c?-`|fi+sl2r98&})Q@va zFFnr~`YmsqbVzrUe80a;zdJ%7a6k8($3fTwxG=^wS4KR4|Ac#@8(D1aU?3+N92<@! z;d{N&`}w?{Bd>~gBlO-v?J?<;$eo@D-mR=+Hsj2A$8NAK?d)FT{5q~g@|1ZX;<06AuZW+(Rb3DTR zJFJQLIUK#79dcjPqmPFVgz&$&{m6&6*U9!Fyv)aR^EXqwZP`;Eo}TXWEcR=9`>SM! zu&+bCUtmhS5ze7>`JfMm=TX>)$@vlBu_3ol5A>Po`4!H$5FdDIq*p@18K>cQkyAp# zOUQiBE25u2Ez&FL-k(qx*d9GvoDe9yWNdclVr zpxa^cxYzmfxR87R>F=(Na)jOo=?7jC?F!t_={C}(Pu?%gJ(4#?dh{DzSjYQQ#LxSf z`{FN*ewA}fU3tiv49O??=<2WQ3pQ{0kIUC|^HJuX{r%nik^h<6H`~9|r>{Kv?76h| zEz|A8*>7Ejm-g{~O5d@MdIfdfxB|DyNT8$Hik`#n(uxi|RB@HuZ!{%9a;n!g!~ zdu+#LC8JkC?}xmf?C(=#zGqr|y>BbNyO>veK|x5;AV!yBUAW_v6%fGrWJ|iIFbr;$Mw&?do0HDc=)G zId=IQ@%gNh4?6ju9PZ<*!O$MQ4=2A*<>?JV{@8H7YeZ>5zUYzf8Z(*LI)wa(`x|zg`E{@~)^yK-S@@$G~!58vD}-2fi%ig9Eqdq4$+z zN$=K+D&9M&#p{jw@sMA}n=VefqP-k!^7Q_)kjpu1Aop36{Z@(NkAdieoD!D%#23eX zO?;oTe*$^_{hjwIof7(%^p6-_=b0m%_`p){nd0=Dz%oAR^uf{(!cb^Rjh<)e&0{asnewWNoen#1m^qHUPlmDk( zlm7MaUVQMk$2yy@AL+b|?PG7`g2+cnuiumGHs}qXX*}Nl=KijPr~dXiVK43#cD_vC zFv3gyW|}_ySMBfixwL1UiT>o}k#6aa9Is@Z&nElmJWb3;Zm;}l|JiO4FNIHBmva6} ze4L~Hdx`RE*(JV%d1q#^@#l9)d|asSg2(uv#Q z$bD|3>L=?hqxky5v3m(Ahf~8|X9XGWo>7rU*UdC-$c1O?{;5boV<=V({)Ge z?BAi?b+vT=8taqrvEIQxSjHbEoNhjRy$(M_e)YTA=JE1+V^_Z3M;h~d=r#4aUtX7B zyfa-$dY+H|b=SUiRpNO&oV@Nz_&3eJOb+i`@1?mr7;U*d0mrEXMvV#OzM?b)N9`lMo_xF`-TEv4sT!%ft^@k3m9x|T{zu!lw_kPFt6232Q?c;IJ z_w?@r#$Md_3xw|X_Ud=Ajcao~?}q2{fq&G!z}trX_cW;QARp)BVo#@(H+dd-e=Fa- z?u)a?`#(U6D3_0MOF9YY?i_5Ep zb8yH%Gvb3^T*#xIx*ckH+ULD~U>DNgGS1z8I^q)#dbyvR`cAz>-m=1lQJxOj56<`* ze0ukrCO^zLlJDDP)$f06eCGGrH+)Uh1N>>3Z`SF)qkI$htJ1!xAFQu%p8ycKK>BO^ zX&;^OBkMBs!*_(ftOeQa<$v`S;B@a!gi=e2?0%=60Ao z&K)&gF7w4bGT4=V|5>4jLwz?baKatd!|}pvA{;o^`dw|wv94Lxch(vBzGt0hs}FjA zQuYsJ{64&lSK`>GZyx~VTb6ehpZ-2})|blt@o7DBI{Wp@d>(3cuH!sbBjktq*lzjg zcr@12xCeywl2UFT&U}p5k4Bi^IX3ht>F9&q-xc+uobM@d{5xq%;55MChKXRVP_jT*L5Frn{d3fHp z;rEGToPLb{82yp+h;zN&eaqzLc{cbsPvqg1l;0zalL-exr(f;6=L9~Vm#JgkhI%6)A8Xx%#xq=dEa+67V(%zoz)wU`B#sh_hI?`%&eG)ojr_Cy_qh=j$K&G_s!ng zju*N88L$5;%Hj7Bp5mqZ_?nZu-=&PJfyht$mXd#Yr1IOmeZ<=);LZm5&Thoh(|vAn zUTZks+Sm0u@koc%%RWfFvRpi$gV?{7?d$uvv2PXga=HA=cwRo^u}^MR?2n&4oF}I| zumceLGR|~4ljr}scFgU#D<_v5`}=%6F*cm%A|1-Nte4I|KIBL5nL_k(I9cB^Ka?~4 z;9b5v4njWV3hXO?{EY8EX`&rh;_t>#u9NLfI>4#@WFGI8{qc1B)2`gf`+Yo|bTZ_- z61De8R% zvF=EEt{>madE9ew$Y+1#TMGPc^B1~5u!nw1hhB;E*8=@43)lH3gU#@ME9R@{FR=Hd zdKv36WjPU_d(M1(*2MY6!SMVd?H4FItd>$mcF0o=bUbe=r=HRx_XorkshJ~}_6_}CBZ`eA4G z-O=yU{?L~$n{0eeBys>k_sotdXxM>|J9os|J_*O40ApM<D#il6$SZ6dwh3fN~a?ri`P&+V-Hl0pvW6CG50#{Ll)hV8e>yhq9m1Z>bCr;Efcr$e!wpvA zIQC;-KQNEKIVa%bjz&&2d*yXr%BK%@<8z;4PM3X7zp#2!%Uw}_{2uX2-W}ybc}z?v z*=3_$yu1l_Ju4wTko$VT;RiC#PvxBC;$N_z!&*M5`XoO^IQJ5#@!drK#XSv2**?8m zJ`woLvTE+20HNYw(Sq>f?)ZV?CLCKd7Ml{kXa=Q_%gs+*+Ph(EXf3E%OVy zpI6ZFb3rHFy?W?}_Rvo(=>GkjzxT=4uwRe)3Fz>1e-!kEQBS=;$@4n+560K<`}%<1 z16Szd=aY0jsi4E}el^SY^GfVL%XIiZR>;B5^c(1NnepkpYXv`YUa((L?w6(ZS<8(* z{P<7ge?R1({*ta7?Ck5CS%2uvZy@LL%Qaa|ckX8r%>1{8eCF@qcYjsO#qV%`0{yqa ze{Z2b>6UW5UFgX2U0wYjIp6K@P=i??r)Rtzf1?NKF@A*Z{y4vb1AQe+U*B!Ly_^3> z1%35Gzp{NJhj}00$I-|FT~|u`xb`qOCI{I%lfHlVYR_NdOY`TPvz{*fEmO!zc6pyO>v+s(aUZJhN3Mi>sezYu#P@hQ&t&->*tj5lAE8Id=i`RKh;YVp zd`~nn-hdA53(xmzm-%u%$HNY@hVQ4NXP-U#;`w(n^8k7el#Pc5@`$6oJeF3j?>pD= zeasWTGRi;Uss3&s^??29-p+MA6MTL@BJDr9*EhRAJJa5| zuj70kTp9o`-3Pgf4{x_;;Z zKiKt)ja(Q0@`ed8%{S2xd?)-U=P>y$!@j_8T6@!b8O_f%a$b~|-&dUbN%+~vFx+Qo z{m^7S_)ExrF1*Kik)@~O`bd{^sf?qihWL&Aj^xyQlfy~(@<`9)<@N>c`x=^IyW>2$ zhi5wa!jB%L1O3KGZ)&>U1OH?nH+MO6X@j|N7ok z@YSa!f0J=94?X8p=)pWF`Ea}$zLSIfN;==MGlgaSN$=PtJ^6kv>m%dCxt5CyfOfBRwH9$atcYX+g;ShUyiVL6xi^L)^*bG>?d)GO@J zSN~jIuD|dj_YLN+b)SmCaoNe>tfgVyYeq}y<{HynkSUbu4VyM6HJ72#4 z1v~c5Pdw1Sd;PjP`tN1!-@Sh5yFc-sU|H|W{e^_{F5&&{J>*gEAoCt1^MkxYH&c9H zT}Mv+$^G>mW6Jfxv7WnRJik-i<;(O@@0K!ONqok654?Bm>kz!hne3C+C6?EF$Jo2C z{qp@py?6fdec628oOd0*7Vmye_x)4+!N&NJm}hdjo?BLWTBpOmV}}RcT~2-^ziUiA z`TzCZw9OH`S%CL+sk*nP5(Dt$b4G) z4%l?#!^ig)>jzt#KOM*}2FGN7gX3~&_}QD{-b30I<2k4Q^lB~JS8r<&n?lIUAP9a=PsU9uwx1T%O*05b-uNSc`v` zHsbSgl^k`Pf4BQQWFyZod?1fBI2L-0%X)^-lKBQ_%R>y#kuPkM;?EtX>vYxurZ3Os z%qiq#{oHPioUnaLFV6?MzT=_qtk8FM=sQ_I@{2sr_aO2;7IL!vJ>RnyY2be(!Ys{r!(I`RT!_d`tUQvA(GLf+C&W0&<_dr-Oa+y3xT=&K}-~ufq=H zB%_ZBa`0E9;73E=RR(pP#O`e!49^QJIebiRvUuZ`@3en3org>7Yn-9mh4-_eK6TR*vt1R>3P`?B)%rvO*Mq2{2!bC*}i3XAHIRL$9h#NJ*$dfv$5>3JE0<8rpO<5}|7kmvWY>Nys}8~J$9 zkz2ygS-9S-ZRhs&eXtSyWqfej`*6R7`Q=Kex74e$o|QQDvZOCdhz?vMKF%i}{{S&y8q=aLR^|51j!KOM+( zdg6D(^?YK;Nph3_2gg2~Pxx!t&+E&WeC-{{&g1&-Y=X0fesNShuV>>CU(b(Q{Mplv z|9^~s>C^T2{pnJkGtF;byy@n*FaD_f_QfOJQTe5OpJjF(M0?Nu*QoMb+IrBJucz8M z*IL~E*}pjdSk#yCVSRD^Yw>tZ#LM~UOULat^LWn-{brgTdV2gO^c{r0V`2AkaeXJ( zPrgsO{a+>DigUrGd@r}@%V(eXvdW>pr2Z=H+g;QAME1w@W8`NW3vc8Ag9F*r{8qNd z8x5Z&KeKV0uKR~yb6od9M*LUDceU>W%kP1>y$0gpW3u}~_nYY1*M3IT>%MrS>YI;G z^7jV&GSJ8E-^jB@v`5k(#pnHQzdY*m^R)k*SYEV4?^km>^zWp}{$XkP+)rhB|9*1n zPxSr->(6t#)SFS^Lpz?Dp$hjc*OO~^&L6r2mh#iz(@a{&gC>?{xaf zney|U?ehzh<(K-*lz-ty$^Mh|_x7phgRFh&K7aEk*rf_TJbE1G_O4@|sIha$=^TUK z6PWGoxQFNZP{%oC-aW0Oo;30OIFJunxlP`W#65(RH}$iR-@m77d6a#BkCy-8u*YW& zvfnS;|DuvFHiQ)_YL^`4D>4IJAldl?uY&R2weM%pffJ+!u+nB&#N&X zlcDQV%N>^{ee%3N{Tt!mgUKOB7U`mZeP8JF6>PB)|O7T|IHR&4ac{f_kbUN&AQBn@Ai7&>!f3{nc>-= zy5G#_)jd4>9qPH$^&aT37wH`~LBDjqZ!9%}}n-C-II$C40TW(|hz*sr)zxU&*Q#Pw#aOeb^UO?weq} z$@_b~r*TV{!@LrBU%ZvKb~|x@1Lr%L=l%2^DO~RlGQMhf?w$Lt7lz+ou&w9A@mj$B ze3RY_5%~b7@U*XN{h&L4EiVhaHCW4@1{*mt=)ON%`w2T|uIGg8Jk@0AO~3r z-r2z3C4V2CdP#qCU%$J7Ud*dc7qUJ<{0|)G<$sC6ypH)_d#8F-zIT-8&C2k)^X^gJ zXI=97{VGc@$K&h%yw`d?U*7V0hvk>yPf3S!{L3r7t<7F_yc0D!KV?44b#wZkQT4a4 zzEb|cQ=)x!?_tq@R$`F-f|a`=+t!&gGSWAZwc%gN(V&OvgYi{qTT%+T+Bsf6{uE@V8xy)L?b9p&lw zyr>JYUoPh|9lxK6^y=X`8uTmQbN};8y?l^E{Q+Y4QT3)<9<{(<)*JY!&*1bg;M9L` z>N7a??Y@HFvwE)UF$NnMf1kH2(x?1^*lDKq*W>4PQ|>3iA3Z+OE%Ujw>2>wb>6Uu1 ze<{H>JpPY%wq(4CcafN1%l_2kH*(ccNuTM5 z)Bed{H~nm{(yokefMq=H|C^dV82X=5?jE)uLGKgTKIJ2)kDkwb|3tr3i;oku9HN}d zc-{1~y}UhT`RL*I0*M{R zTQ^aDd7Mi4na1CU$KyW1Jl=)x&fxo=!9nnk#eIVLJSF^IPF0KtvmVsfE?*-~&-ImZ z_Ik+sq?7fZI-N4z$@p~Z!8^mgQ{8)*#!uv9S?^6u>lCRxsbBarw+NqUJp3%}ue@K8 z@e}iUe*PbSKZyOMlh>X4#&2g?JJfp(qCMgVXPOTBxc|@J6O5Nidg+&wf7Vkqaz*Iz z!1)YsC(G+RG~Xrozj8l2;jBxP;opsN>6;(?Rn((2o z?)lE={h*ikQtdo#IDv%EKxdmj7l&7AK2nCSDN!X6KthjBl9U!RA; zKhR!28|6T~qaBod)HC9Z!g&vMdC9qXd$$kcL;T=cF`w=9I_~eu-=`ig)4`bspDE7w z>6ib_zc<1Ap!+@iZ1#uP8M|{%+M(Wu66FH^g97*PMxZ0FTvtFo<#px3ZpZy&U!CK6 zkADwd%A0$Z=evYw?BV_4Z>di@ZnN#EaNFe$(_dKr&?rwemG(?`c% z>?7cvP~Q(<$yPghIUQk;cbIa&$@c;Mc!@8x_2F84UO>;UT0R-~==q-++LitFzz4nG z>nGzK#%E6sI_;kE4&xjTZ;J6yEyTMt>Mi}`b=Q0OlsM;9pJ(aSLOj|J>6hvHz3ch? zc*bS$Io^am1A*V?mGoT-n^!{LW*DCQTAH6r`AGXXiN|;nJ1vOu$Ea~B_0QkQ(tCd` zpLstR?`JVT>gTrdIvMe=jPWt}0dG#_mgmd&JjToMaps?E`9a+0f*pa(*E{5W59|o6 zC%x-^U%mgmeBSTQ>k{O1x^QzVkEM_Q%80*hz+IP7-gNbbZ{MEu-W>Ez8A8=f}58xZa;^?ML@zML2%K!*jjw@>fHC^!(or z@wdPEm6}cc3;R7;XZClkD|uJcFK{o|9Rgr`(0Ci zKKWgk$?w7VxQp?bzF!mPA9)`ry%#j`eV|cxB;5C>)bbxup1$v4Apc|dm^|z(_gC<* zWw@SCHrU9DyQg}q=Mb&D$K+*3AD0~>-Y9eKEnIg)eKf4uMYXSKKpxC*}gB^Db)*K&q(hZa!!Eu z;P!6B;|-!*wEkE;-`6~E-mKnul;fy$DEI%ecee+!?gw43zH}&ut&Fb;{z2$9IX`bT z`_{6C@v~2>j{G!H&${u`cVJ(Ao0y-(hrQf>P4stJ-l>*u-UsgdRm?-vuE8tey%XfR zA7Xz;CBAQ_622$cN8{g3<@d(q?-S}H5INvL+6($j_dA8}8oo1t<@X2Q?}Q(s?IC?P z-4Xqpmow!xedzPpRh&D?@y@mWJ@-FuuPVOp>fv`9`NuBP&3E5ChRe%-5I&bbnNIkq zbm7C^;L8fjd+YmIX%y}oEql^*l%=W=-b7) zS1^Cyz(+ccXFk5my7N$Z|8CcQ^n;ar-_qB6-9nFF7Who%c|VGLozD!pAAP;|m!t#T z^Igf{10McJgYMtc_;0NVc&fkPlqcgfKiAU8X2w4d#)S()F7KB3xR`e|8reJe_m6Z~ zFYfX&?$76@jt>4aW4;~u)}TAob{crsjy7eaeY3zG{gWp&+)q+|M zSm1tPY4u}W<18y5z1QxDBv1WBl-JVAE#oD9(|85=;){}>?6>m;q0h$+>bsLWcs^5p zQ@$??{Yw1Wi1*!y_j7{-`I|xgUO79-8JFiOto6PU-{>8j&*;>_57^u$IbTd_yHEK=M)VNrKT@O*{%1RHQOjPPbgQA?!T&JN z@R4HtGhO;8Encoq2~X|(s7TktE9onbuHACJsBfo)9O}sj0(W{Xp9%a+2DRJ*$6kH* zgg&*u8pz<>R4!xk1cT$Uj=@>7r9s^f8~&>6=LX__Yb^ZMc=)YZ=C_jHy!%Z_ZY`d_ zjPGx98+mKU=iIQ5Yx&O4>y3n;9r2joVLa^gTAXikeDvt1JCIM9yfL{Z^!S#+T7DMk z+^}bAR|B~x_&nW89v@*1LnIeq*dp40Vw>U)URzfLY^!aZINr{7^;^vhn6@}c+H zn_sDgb@(n{BlJ@qp3mKs_=_U{w8vLP{#~BVKbRlZ^~DZ7%<$ zm3VmO-|k*jU-f@Yr2E>C%RRe{C-&Xk>%+dm_o*0XyfOHX2zXLJ=2br#IORwATpx6x zr=#~b8@-WRBK#)?>mmO0!2c9{|6_0^H@+;#Tmp>SsCI1ug=AQ3<)%o<>*YFbR zJAy|4f9$;nm{vv7HryvXlZPZKA_kCE!k`F<2&jPMi~@>cLd=Q@5nV)33@EyyfC&?_ zD#3t@2!a7cOqek)1{4E`8Btkuzq`8Yp7TtPXP?3Fz5D(DJ9E`l)pe??tGlb?>8B6Q z>)CiXucz^MRrzwivi^nN@q7HeG|cZ6(vO_KRPG;~eN$qw?>2 zQjd?1(0usv74;{-cb_oc+2zkeIqz`onfD1qX1LXldyXy6m_9>YfB82}_cn_IyoCk87;Y_fq+^a^m>WP376Ilke&e{;ba#9%92$`FO*6WO7pvA`U7*Z zjTf3p8h?g{7g!vbXDyD+2m1UYoriAJFw2kr;bUJum%sY)S#JNf>0t$bm-tg;YFk{{ zpLo8lzRIzmqF(o&x^g_WuO=ya+bEy@!TFn;UnTP@&IdW~V!wI3&U1TFzx~bx=Gm4X zm;siLb-KloIY#BqRP6X}{xAEp5elvx~`_WHbY1&nW|YE!9-*6PaBsj*T1VGNy$M=b5$^ z=bHm8F3@&ZXnNalQF(jv`#JYJ4*b5<$^|-3Bw>z|e7@IRt{rh)Vn68moxq%H{U5)F zQn|LLyLRNxwcvN<8usP{dBT^KU(cPLowxqFYWiQ7RP3iK6m97npeEv=+x$dTF9NqDuU@A$F#PWAm; z)1f@=Vc6x5`y@Wb@5N`k@?|+x3{&3UZ^8RKTdslG+Q!H4cvKJH-|&1;8_SQ3n`h#F zavR1yZ7R=s0mqX;%6H=(zT0WXJI+^7FFM}-=jR!7`ft?l$74FKo#8tlUiuaJae2Co zNBhsW_Qd8Ui!&zQp8LH2eEq=rn(H6Y&)IV3 z^Ncyc&J*|^F4TRV2AhRIGmf+wI)frW2Nz3-jah!ScoZ2Wk(WSIloR4?Dz@%hn_FCZ0cX`Qx}0 zm^3@m(qXx>ycu3-`?sW?{B`39wa<+QuD=vNa=$OYJyyDJaekHL0$uO;dXoImevY@3 ztlrSvXmMojvN$#iEXF$&0|MW%(tf%#+g6`OumZ;2TW zEaw;{EayLdOIZ2`zg{c)G3E>UdnAXke`EJ4a8BS@*ABRjApWc7+hP2E%zN*4xjyH- z^?Dml=DCb_hbJfVcCnZ90EWj6cYKE{r^j=z#1CkC4Es2jzTf+NiRO#gPv7MW?_Caa z`FimnHy>E0c7LzGk0SqN)kplchCfuydihf0yK;}TUgGk4WPLcClq2i;=N$2mUgGS` zyT{p0{Pe+&UrYJ4XL}pQxtZgfJfAyEe@Nbs`gXk!|C4q8E}mZM-&6Su?R+;det3UP zKTV$J?fiItnCGipI{1#6o^NyQsB*e#eET zblGg@FAj&Mi>;@~^tU)RLoLpj5fZua5S!>z8k#s+&ORF6PUaQa`!TYY8Upkt)C>{)&9@VGp725E?t&aRq^Zg{?Y*R zOc$S+zdOBC?f1h@4)23C9_x$#?Qm!w(D)25^1|+UWd1WhHqU$U-t@xMvn%U1mOIN~qfHk(jrY2KthU9WX<#wxS^0b~*Gc*Pl}~*K z>;1#{A9xt~%21oN(WR@dNX} z_wR_`gyxUBe}{g=-yQLH=HwHfr+&fbl;`{OM-Gwt<$Y6pPZa8RHxfSYE#Q5QjNeUv zPxZb%XNP<1HGHwA%P{X9*x@4QpSi-cpV*xP!+uA~`7+8;?<7sXn%=kPhuwM>-<$NF z_p$F0v7f2&*$-2%AHGxNcrI&!hIx+F-Or7EUR^&EKVjwYzT+@g{x8@t-fw7pKL6H+ zaj%{}cXF9>Io2oZm$>44$>jI=EGHi`U(0QK$9FIF+=>fxTuuDJ>95SEJoOdc=lVTsz`EXf4(6&sTZwkm;o1`WoLaN7}{ub@k8rChH@)zk}_;#pCnX zl&Su<7W?s&=gtrC`!$u%6?S%UeVDB8-T0F+JyehE%gFl!zVDaw_A<`-eD2?*rR#o2 z5E<7GB-v@RD`!s@o4`wHjLkwPj&q{!{*V7VScBW{Q>RDg*ndfd*$tp6}|lZ zO7cB<{=S9%QTy|pfBRv#E(^_QJuh*S+E2R}_Qyl|yTcq8lk?Gx&-U-fcYdAxK8Rso zj{2Ez>SLJWE5pPbXBp->HI7&O&OO(q{&VO5p=qV>6WGr&?CLLh?#HKopPwsC`@TKg z`HL^lc|ukB73J_dSpD4)^)O#^)Q=fvJ{k6(JH3%{`$n;u@5N7h?&NVVi^}`qN7esa z{}>pCtD3&A&-nrGdq{pamBwfJGv6*BNk7Ycv7J^d$G|X5|MxNb4fYp&&VG&k4E6tB zoR*$TH#F`ZsK{*8{-LVr`Fg7w@6VF+?Q!?5s!@ZQ6BGA>^dbGf1lKRupHgIj_dxg zeBR4>1mp8}EDUpejqi8kHrG+^dGdP}|M~9?`}%8p>AU_3=es;U+ZXNh!;HsvLb+Ug zu5Wz#TzsbEW50dOy2!1YSZ{1Ml=I{Nw}fdQ?Y-tuHxJxR`*HGpIp2Pl|Kxro#`ERU z!fE<_{%>7}`sw<5H{*BbvTk!Y`CaQ+owrr|T`Ipj>HpreGT+@-=<}0xMa zE$+X#Ps%Ma7cLSU>%LZUpZWfJKk{%pUrg>#Ie3A{XUtHG^UN_efBEKU8^(8_?RZ;Q zet+U0rRRV5NnJnnN9~jP?ZV9maemC6C(W3%ZN281^R<3Yu>MeB-2DxOrk&jnEGoaR zwcPK`l z49dTQI85I^RK&@7Zu0+iv>Si9K6PPy&uN(OWAn8>=lJBxAu8ufe<0@~j?wQx`s)jx zOW}Hh=N8$IT%-ANII-^r%MZ)H*T(o&{O*Nz9Fc}T|v*FIyNZ{xmyJMYFhXZv0xzr26HPyO&QizD;0>Z_*T z_t{c?t5qN8$5qwiz9*Eg_7#-dw_fe}x9a6REDR5^b_T|k2ft$+8S-<<)tKkXk#Y>P zeAC`H@OM-^C&%Amk?;0tl6wft`vp55laG1ZuZEL*sOqSF#NW(w{i3VK%6?Vf%A*`C z#&-ks_XX{h-^$J}BXfZ2r9bx3=T|)``XkfYju-enmae}kN9?{s8ycrKDt|xb_Txk2 z?2ODNYj5&9bgqkz()31nc2NH?+rKCG*64ukm>PvLX)6#0t-o-`l$H z@`mRAQ7)e>$ExzF*Ow>X*}?tP52E_Hr%_Y}dr zr$1Sz`|;g+Jv2-0c{{xCuzBr$B?I7u2SkAtE zY3WwgFW4XYdg&*1wO!L+sP|OskCE;h#b&hnr)!@{yY=<5-j12z`irJke{B9@zaN${ zEo~V0*IJx!+XE;rrvK*Z|0tj4 z7p#{*tKZmC{mlQX>L;!rjE(CDEB9lR_xsDV@!&jff2f6B_j3OTCs*yg7&tepe4fvF zQ|r?Yzh~`C){mQPI5z9lubmyqyp!#b@%DaL`o&}(!+D#FpWg4eeDK@_&Rywx@Ym~+ z`Qx~jTYk0X*Nr2I{Kv}QsF-1weqa_&cJl|mKlj6|FIT?F{<7;glKN%;vZeAfbbpWi zg+K1IU-kLir}X)0df2Xfc^6K`MR$KP%b)M1lX=LNrt9nX`^i5$y|nV+`A%Zm!TY26 zUc(Rn-?smMI=%QG(mww9dG2ZY-iGs)s?M98T>8Ay*_-S;u|4LR@3NiclB=k%^1Pho zMZfd=dDk9t*^l+CeP8hZ{=Aaq;QEco)Uy5*8~2_jV|G#hYGpCkS4sa})%h38o%Kk+ z$~7NneWuMf-1GE${?FrBGCpt|ovnUPz5O)b9Dit!`@TamFXs3{|8{c8c;vT><~O=_ z>-vw%_Ph0V($Bknm*k!YrpIx^?Q^^D%*guA$!GLEYVuurU!MDG^!qfu73FyUJZ*PUlb)=gElbFlUgHI8!Y$S-vM?cRUmUMB57iQRkfK;MVsdmG+!*AIhl z-CUZGj>q%i?sab3{E= z9Ve>V|5fbAD%+LzoaO86ip=x2{sP6x`j_u-zqI?7$vTMnImeb4>o2LNE%kfuZ~wni z?ySdL^_^D!EWcdO`P~q|{2BiLTX}IF>gF$rzti7XF6@8Y`MA)W{)Fo%F4F$~kA5$~ zbe#TVACTiO*G=>ne?H22c~#};H~zY?pB?{_cFOVPZ`w{duj9HSZT-daWPHvS9ZtUU zQ!(Fxo=3!aCM{?8Jb4e|>`nGda@pb6BkONV*MCfh`>f87(%=77b$!HotEzm(@`}t{ zEvGHjL%(8s@cn@I@jq_$<9Z!2SPrUl!+K?0c;A^HhvSe@kok?`t`7 zJYasv|6J?O&7YF*80F1(<>BsCPu@$_Rlb`C%lm65$1v}AAfK4e8RmWMY=4}`rJXBE z``!Al^YQ-o1hFe)KJMq{)4QpiY4Y?R-i!TPzqi8vkK-o&#jQVhzM}H`Szpe5C#Lc` zpXKGt5487I{dw~4yidCRSM+_Yvm@z$*}j~;n8(@rh>aT;GWwn-PxmpB^MdYqGT(Fa z)#SU9jL-7meAd19Nsq_(LhjzU%IUgusDWvz{=)rs*Z(Eo|MbJ0=ce&pyW@C}=qWFk zL!6&-zdKEj&-ddyI}-i0%TNCjd%l|I$N2q7d`Hoqe@MdA=k68ayge{KbrE|R#yRHW zo&EdSaw9*t9n7zv5Ayx|RtyKGuhvtVJo8^MUXpLd^X1&{WCBwWhi0&*SMj|?Wqlm~ zE<05Eq005n=d_F8J6vMhaZ+x4?%V0t=gC^`l;`&{i23~suFG8ilC)EvhpLEikFwhH zrNz8Q8ovY8{)X)@*YgwYeQU}0XwJ6wCiClD&)JW2oXCa8YI(T(E-pdC_)1GTI-5D0+9w|-NoioJ!YB;AS=jGVX z@SG|!=flhO9uewI!~C5-^OI&j&kMTefvFhA`3k)c&p)rl@JRbSDG&0`^Z04$Q~o(E z7uxIUJus~Qcf99}$MRsflcqaB?M^En@|m71XWTz&&l^W(sf{1&J+K+`it6LNQS~2@ zel5v2_5D`Ne7bx@#@U~=1D0P?dk;ip+Uk7}zZP_c!bO$(0lCH`nwl`pr#&6)zDe@CZFlYw-(P#s&EL1OI53SZ4ozzfr^Oq8i<^fydG_ytp}ad+o#;F2 z0!b$_ms_03x%E|KoIQy=$7$+w`#yo`l*2CG56^t?dk56x(o53$;cT&s_9T3!S8<$4 zo;!YExZb+|7?(eeKP(sC$HRIc-}wc9KZf7wpr4U@mN`E3(0o$9qWu-iOUAituX6by zru;!x55^sfW8>-}Q(h0$@9bp$=&u#+lXChsyJg-*yO~e+cWKy-zm?w;ab4)zGtW8W z{fdqkYu)|Tz+pudDa8N4rkrNruH&lxyJLV%5j`?^F-WB zq4i69{CwvMGoFuW56d%czTx79hU;JV9rIZGvn_xBL47Vh?*~e-|GhVU zzliH9za9Hwo=fokaJjA@98TI*YJ1Z6sJ{NRaGG8>zrei#zj6M`cH;M^{ydp}%-_fG zcU12EOfv6af9%KS`#?7zNapv)X#Kh837_(;PtGIQerR`9)93q+T=}FNvC9wN>*vm= z?-xwZtxF^0<_oyjS?Bf4ALY`@Z@%`wFDQ2F2hO(=|K~cF{al*AzH0Sg|4H@9_$mM2 zAsp-+njZyYo)#@|^9k2~Ci{GBFTegf*W^4R?=|D^#JC?aO#Po>*Uu*J*<3i$!*-F@ zp0@ zerJEx%P{u``QC=}REGQ5dmb4-G^c4l#P<#kV;@P+gZp8YYZ^XC%VVh8<=#&P`g^O; zoMQQrapjcEbRHnyxoKNw+g4?I${T8{;1JbYfzRL9@N&3{6>$$6^%I_~|Z&mfArQ`gF z`^LG?i?DpyFFKs`JFM@gwZ2@tip`5&J@XzT-lOrhK6e=Be{XgD4a1*%&s{ju{j|VP z&iRiYPUPn6JmP(BkBN){5cCe6Q*LIzKj>^*Q~P{UP^%|LF4wd_P>(^8{6u zqkp)5EBPHK>oe^<0rOi`Kdh=hQ!ed&9Q{96JL5R$_s4#i@2}I&T|4>YcW-89!7^QzjlrROoWRR5OB)Bn@*$9nwr-&ZB)0O&t# zw|sBRd)h`F>ef~7=(ur<4QI?c8_qKuEY3Ht+TRP~`;__~k@<1^SCPucrm4MmBct;C zof+SACFA2YR$r`g8NDZg^2vKz|NWTD2j-PkjL&>K`N*`i_T^63#Y^UYj6YE0UxfLi zy#IgI+L5RB=j(gs8Y^WPA+u_xQVBPgBR*osWx(|{KftbG_PFVl{4-^zR1<F{a z$>F0n{KjTq8!w~z#QP$ZbMp93mJKK6_8hp1?%e$|ZgR>(^ zcYx)ipHh3+KmKn&*yzF&5NJx7f1&{`asYc-u;=i?snDm~BS&>x!{te)I*=jl1;G<)3hjM|l# zGhNEN^b>hkPB^D;?PB@F=AuiazAMhVq`Xt^DN9@WgX^-TyA9Wb# z&NRO3CtX<1d&qO~`>!mO)kEaN3L=ZyI8Ic zC+&w}rjyqG_&lvW?xgjCg%5j*#8hy&+bEfv^ZUkpPW?XS`(g4Y>i6GS zuf$`XcJ)k5{lxETJcduc(A69Lg7Fxx=wC_sF&^z^PnEI;C5+8!pW9>(K3l$d-! zKMb=Rsh9GsSNc!yE1bP)VaofM^6qyx80Yl&GL+*vYo<$kSbmI8KEt%v$JFD;XFOus zxEj+3yf|0L}46`F1A_iZEdl(z35ws2o}iqE-)rr{edhMOZeZ3arr-P6AJ-PxekwG#Xn)0VoAY>%2dvM9S`L2xc0bo*viweZ}I)tk!hX{DR}tjECL$L`**IA$H{un*SWecbRqmM0wip zhdEDRx@`Z@7spG=o}X_sqH$@lD;59%i-pP2nq6K(Im z-YybjDG*+v^!k;;0g}3us#?jzRdo93cm|SKOpNUejhoTFY8U(@A!dXIZ+Si zp~SPaza6Xn82QBfJ$l73-g|2O@;TEj*6*sj@h31$Hy6&%$A8M7^baA%8|FWk-aBl6 znr;W}X1NpdeVeOK^z&6*2kq#MUwPJFLepuK_zms@wBgve`C!Jl^2#$iT7JH1YH@+t z$KpaW>I{ijr04XDO*Jj|KSytgjz6W9#@9bOUvBo*ZS*EjXC+_{eCI+vK^1Tp1_xxV!A-DsR@GRQf69(lF(y z*AH{OM$C06+a={0=J^i#Da)I3|F^K8pIqr)f0%2>l-ttyRgFiz#Ei#s<-QO5oh9mj zw2zq2AJ^wBcj9XHy)xg=<2;In$!EI6Ooy2I8ISub{BAt$c*>@a@5QJcT=&s$Kh@`- z>+`QP{GEo`?%n(mzsI%w$mE|R{v4a_EKbe~@OfM1GaaVOc10X4cKuPQ#g)G!%Xq{^ zm$>~QhSv>u@xQhhzi-ujSz?wSG26ionjXXVo+o+&bC=r9a-<*l?Q>_1|9v0lm-Gu? zj$!KYG2?NZ+G(7#qmtZd75(4W%XZB1i|6;4Z~6!OQTj76<%nt5b7whwi0N1KLt?Iv zeY>j~=KO(nrs1Yq?zE4X>nVnrF7xw(?&lKo`R~QlUsXNBbYGJ8y6=zR{yP2Li;vk~ zG}iZ8?6)WDck$>4*Iw@00oQZ1hwpPZUn4(Fp3mLy_>%or@`>qh#4Lwgn0}rn*H!mF z({Nfm^4XplCT2OZzoZ|e`B@sDc2RGc+RHH0X{~-r{vU-||4fH^$>+L|<>2Za_nK(` z#`el`V0`)!G4tO+^Fhpc9Wm1*rk~M1+C@w`V)7Y}n0EP?{Vnt7Kc`*(ILh|s%QK(s z@0ow<=Q!eH>S20(PJS9rlVd;3_ElA!mJZ`HJ?4kyead*(uQR{IEGHjRAMpX|7fx^0 z_Z>JNpdMn{(&Q02JA;By!#!thPOT+XRA5&jiJn}iuaX2*f zb>74I3Fo)OOlOEK-_RVP*N8iySw(-*$}Zp*-u6c!lnZP%kn2FUDiOiPS+e&Yl@Fof1$ozuW|D(`hzd; zhu!^7ftjb@1tnf_&abD(_7}djgd=EkY z^24;#$L{`~Q19=F%qYE2$(N&@KBj-suY5dA>!q*WC$wJsJ6}J;ZS_4p^Yx6rx1ke3wu=9~R3=aIm4_k_kF#}-`%4;?Pr*n_4=~?USG10 z#c_`Nl6?P2J;b!*INOgVFAcu0R`IxHuK7pOncAARiJmxrdC zzBi!$A83cx2jjVZsp|7!_TT&4bGpfV^_GLBUh!seq3d7#Fyk>lTbgdWbDdt|aH{j8 z`8M6axO|4?_hZw4$mj2b7%t+KF&wU&xL6xaW45k`#J6Kaay|me3@a^H}#~&XE`z+h5qVg>lcxEU;X1Vj@LTBV!QS2+fqB&F1g>#_D`I~FS?}Se2sdD zKUaGhCT95(lV4Tr_g|^)O53sj+|38l`?XwlP@ga7?&}B)=N-h{-{<^^*pHtp?4P@3 zK9~-}v@aL-{f6>PhuGK4dZRp_6H_mrQxD%qFn-+=od3{%A5)I=9UrrQO~YyWsF#@Y zYGT^W{1CH#{4n{AEaUp6U3{X&?C;w4E}{c*K-X3#XL_ z%b)Rle1-0xlh69#x}W@9*taiDKFv!X<^nc<8eLBa2lo@)Az9-kMdl<5i?${_lQ4hdnIN(cTYoL zxV|86aHCuQG_g22FUA+?LL_S+B&jGYxY-m4>(0_L{3c=$C2OPk&4E#qoxEiuC&c z#9W7^VeU_C>*a_3Mt@}3$1ERTuCD64PxoVN9xWH9{}3~MhF?C>^`Eqp`iW^L!@gg$Kj3)a@7FTkW^2PEXpXo46Oh5O-Pao{o6P(ZZ`}_10V!!{*75=ULGyDI>ww`hC z%S^E|GVQhAnIGQ|X(utCv-}vQKeBxhAEf#jKP?^V;rxZ=nv2ipzC8J~lbC)$dubOj z>ziS=r>bJ6LrguyEA)K_)A#LW|3Uj%UcUbi)pF(hmiHw1^BSIW?YZ3fCG9v)Cw9n_ae8AOvJH@n{{XWCFF!d9E zq<%uoc1+BD0=9SVNBH%{_a;17%JLw7{7kVQ_hj1Nf%A7h3HJTOrO)5T;QjNx5Cnn$DXC!kElNlyWf5DzMtfM zvtN#EFC0hcw`u(&<;$PXb>+_WI^Rq1{TQ+Tob%3;^}RFm!SkWS)JM$n;&Zl7o@e-@ zFy|>gW<4?gEUz@o^#=P%VwMLn^TFrrAAT?Xv-Hv*Sw4Qd@$F{$kk2sdll>UWgX=;* z|3%w7G5wI3{U|Z{jL$HiGd=bLlw+8fd}8veirL;+55&Gc_j}}IACUbR)1e;`(@%)q zer;g5pAtOc`e~LI_4wGggZ8nWc#e1Qlg@@YT)&aDGO^OBygBc}WmO^@LZ?Rjt9k1|i^>)!cq z@+nX3+Uak9ugd$k`27{?&4u0XRzov;l(U!ed_V4=hvswo9r2dh&vK{#P_K`xIzMlx z=K~!M&6M+9eu#a&oTvGG)<3cTJT2_o>Gb0}fO?*i@p9?)+bPQ@S3F-o`yW3Z!^Ery z?$;1+rR|)U`m2gL?ovPF(NEY8i20njyPiv=o<G&jY-vBz`Qvml7Q5cLw+!h2%RXRh46U#7u|f<6}OjUBryfF#Ai& z^Et!R>)zk;_aFHFp59Bu`3dETYhCZg$EOy#_MG;9kACK3)-(0_xSsksvH$${hUxDd zkEoCR0&!Y@krtnNi2eO=>h-a^uaW1CbB{N<%010Kwtw2^W0p6uZzsbXUvlZAe&6pH zFU?NIV>%3{VPAimTvhdzssH)yhvkx{pLP(Z$&t_X9mB-r6LTKLc1cWmhKc>(ty3-+ zrrmyfO$)Pp{P|Wl`#ZeQG}m%uJu`hjOupY9Dd+S3-|KBV!}UYdL(KRL(_Z#J4F6V4 zJ*+2|A2HKS!+yD?$!#gWqn5iLFReeP9Ost|`~4UBTqmXFlj~E;|5p5a-w!ap@4spG z0(k(a`W7khJ*Z56dCnEv5b$|IkgCw%{HM_UGiY9gzRu zf_vHggyr9@=00!v`)qvQRr2HeciMV_`Ay3opA+XAZ`tqi9tFl@*dI5ze&9Grzhk`L zi#dPyaaHy5dt2`Np_S(&Z1=>pKMkj)%lLfm+E>;0kGWq^Rs4*;Kck-?s_le!_?Y`S z2rUG^XijSe|dU6+&D#jRmD6{mxk$2qcuH-iP;a*PU@kZ4&y#$jbD+^@6c2o z|GNIF$7eq~OxNkew2SrP+E4PHj^)X7CJYlZ|I|aw=M3}y0EUTKpOhzN{qi}(jOT~R zC+0Z<+DH3+%<+Q!Ep0FKU-nm9de6QuU-MAc@A4cC?;~Zu#`hvDCt}tw!^A8nKkVm| z{Y4t)@1pp-m^3>ltH0A8-VaU8dCYYxmzLiDG@tcK+*9}6xNpSsKE#wu!;I%YC*RL6 z>&xf=k>Mg!r+TqzQ(S6l)Y{6-EZo|(ske=(9n>*n%&w*+e^;};>7@ zW<$+3pgWj>1qYZlglz7Nj;eIwv@(@gqozU4plL89Qx;S!Ekpb=JTD8j zDQy<)2xtJ<6|mYg3ogzy3(f~j%(M)yDrpH_Ex~VzbXx`wf;Ta46^tm|3*k1wsM0pU z#ZjA}Po_<99_V?|A;HMZA;E;qkYF5OLgvt5G+;vJuwV>eLS`u7aKI6OBLPPNhCyZ+ z@G+1%26#Ah3i*FedL_ze-6e~FAQfE6ovH)YKPqm>V|J@yM4HB+g-!A3Yvts z0j3u;4etir2AEz@2D%J%8R*@C_YCI(?gj`wy`V*SKj3b_ZGh>$A(t}CY$5Ji_OVlN#QBs-G!$DP77xio*q5}*tp$Uz-NV< zwi_A#vfbI?vcj?9u5~X6o75c_?p}9%*rM)*fQt~G5Vo#+Nm#q+vanv!q_C{$s<2hj zUmr-m19e^Yql_BV&00M>1PdswaBoN#*4T);hmdja;pw zc)4g%_(9Rb;TpgPMURJ#^Ou5N3VJE%CoPQFVHxl<;rgOyA^#lUdBlAlctu!NyaMzJ z&@Y8;ieCvk6u%01J?vlnUU<@uAB1CyKSa5FjAx&OlZw9%XY9BEHvfP$en1*Ogl`r9 z7_I`WDf$uN9})g{_+zn&ek=|mQxX7&z#(uxpa4(=D2_fTDvqw&sRYkT5H5)tmXv}n z1zkPrlc^p(uv7JD!A@I8{Y$ouUM|`tx~!xjVAp6$$!^i#^LL9n?Yvvm6VRYwx9A>( z=K)rmM$pkH8V+a#__4TAR2#TqNn^xof@e*@YZ}!nX$o8hUbE=N`YobCrcJb}WFP3- z2Rhn8e>>=J2mS59YX|-9puZiSwMV!;@P5&Tk`B@4l8(_Y^*aK0hQ2P*hue0KYLs>d z-6LwUOP{EDY5%Bw=>Wii(FY|5B8`KhX}b(UdV`~TcR4ibUV13_hk}22^zbf+M|BGh zhyKH%|LCZH=`n~mJX%wEi64f>RoaM^qv8|XP_L;KpC77)d4gF7{HIkXGA5y7w4Y^xw9a5c2w&xqoR=| zqwstbo{x@J7L7)^pBt4Gp9}h2(C0;`myChT`M_hNodE{$WARv&<5=jsFe)j%G0=?e~Nyr{;#N@MigIFRv2%tUKBTIRuYc@oY|~e zJgQlZ_{L^+;&AtRanbHO$NK>~?Y>Lgb@zsG&)xTqU#`(5USFd}ymQU|@gQ?T{6_PW z;&+;#9&e~QGG12moOomNbK?(co(K3VU|jrJ!T7jNt?_X^z|Me%fJT5aK=WD`A>Nho z*EMIvlWNTX{yXrjxOIzJv8gdT?%85C@SOO-7IWgkE#}6DwzwxA*5clHZmoO4yBE9% zV8eXCL-C4Qi{cAgJRD!rVsU&q;Hnl&;%flYT09!hEO;!wvBeAVNiAQB+idkp{B6ru ze>z?5Cv~%#%3WUjA$Ugo5|&&v$n_PorQz^l!9kh>uB+13|;e*yRxfPVq_7l1zwFg{ann+br+ zGIh4O9QcOJxNU9(+ys~oxEXK@;8wuxfEj>005bu92iyg?8!!tn8}LMC@IFsw4&CSJ z%)NjI_IWnbW~*nB&hvm3nTPjzIn#aHmowk)^GaspKCfkd+2{4lnCfq3Hnx2`v#ISr zGrzQ5m8sTlHF#?>H@5pYb4$BVGBetJnppsNxZP)&N87E$M+voq%afkd?^>)l}UT-JB&Vc%W27p~Gl+|kp{1?EkfZYI%0F437 z5WjhTn|k}?f4FUj{A&*Al)wCd?)m$5?wNmFi=O$LYWB)MqD8;_i=%$|eKP&>yLRrE z-?MZ7{6;(U$MgPpeo+3!!9n?dFB}M&LHT1l56&;!VKCwx4F19356gcs7?!`<49o8u z4ntbQ@&}j`^EU=3f_Gy6HqnXToe17v^Jf*F4;T-a0JsD&5pYHRl&xk!#|+5NfQ}iE zp8@%q;LQYYCU`T!n+e_=$lnXNA21K_0N_Eu0>DDRV!$JSrGRG<=Xu~40q^Gj9KMU$ z*?Yk2@-OW2MgC9G7tptTL9OEL3s#$k1v_@#t)M~Ib_FLDv_rUEL0`~=fCrd;!P^(S zeZkuoynVsjuV7R0e&Fp_un~9=@Bq`X;LGBUpgV%@R4^*PQ^9$FO~suc*BQ98#T$VK zAw0lz0j~>qU7({2cwNBj3SL+6x`NjgysqGND`-&A4ZLmzO@Ps_fY-fXW6&ME?giUK z-NEY)UQfjB30_ac?Fn8_@OpvQ3%p+7^#ZRKc)h{v4PI~XdV|*+yguOd0k02ueZcDj zUO({qf!7bbe&F>3uRnPG!RrrRfAIQ)Hvqf=;0*w80C)qyJG)?3;duq4^Uf<6m-p9# z%K+o@#un7AJ{EFg3zl~s3%Rk7yBKoU0Hze=b(>O9)NLy0X@DC6(*d_3&TWWu8{*uC zIJY6rOz`Id?gKmk_y=HN!KiMF3&wVPq~OACPa^yjU^(Dvz;l4-0WShx2fP7z6Yv(` zZNNK#e*)eItOk4t_z18D@F`#|U>)FdzrZYby*Zh)N| zVCRqEZvt!v{0#Uvz!c6Z3;?BtF9xNBt4(QP->|fB5byv~z3}0!tAkg)@HyZ?zyr*7 zg$rtJ2i|stJGR{pyzRiN3tnCD>Vj7nyt?4k1Fs%<^}wqKUOn)3MLN3y8UY#ungE&t zngN<4{pOHwj`W*DzB%Mug5L_z4zM4f17tctrUPU;K&As^I)c{`ypG^?1g|4_oxtk^ zUMKK6f!7JV0}#JU;fJ-lfZwI?6JYo^_}#$o0q6%hAnyz9Zc9=z+pyB@q7z`Fsw8^F5(yc@u~sjx}Co4~uN@VOp0fp-&l(+fM*n-1Re z!WVl?2X8ufH-mRGcsGN0Gk7VkEZ#H>Khh;R|H=5qVK~RMPqw5Dr!=%5uP2 z>i``c!0P}V9l+}VUdN(v`;OpsEb7{#BX}Lb>jAkQ;Prr95Ab?`*Q==SZoR2=}Z)(x%rEqV?ZZ2-KRAa@gZH$mhK zpcqgBs0OGGr~%jtur;6#U^~DLfE@ul0d_84*Qb8*mwomI-44(murFYLzyW~HfF6K8 z#TYYz4+0DZ91J)FFa&TYU?|{lz>$EX0LK811sn%B0dNxFWWXta(*b7{zZjfVjJ>Ji zzTsKWeOB=RGqO0`ek6D!i@UZM3EoKXMisY?M}ar0_)OqIzyr+a;$eON3LRqs7Xl^# zE``2JA$KYCT?)BNA$K+S*8r{sOaV*t(yyf662k#~DUIOnW@LmG%CGg%t{I>!B1iTA)4>IpT<~_)~ z2buRE^AY%K03QR^0lor!4fq!D9bhBkY($)mh_exKFmp1SkgrYPZ9={_frnX@*$mlV z0AWekFTVu-2q*@Y;db z4!m~YwF9pMcpbp&0A2_1I)K*+yiVYC0^c;B^773wT|? z>jGZak{5%n;B_tO8+HY+D|p=?*A2XGkn09sH}JYct~+?$A=e$e?%?%=Tu<c zyn)~i1aBaC1Hl_o(zoAXC3n_14E)1NW&;lb9${@ductcAX0HaR@@A8tK?JfuJ z@{+#5m@9xc8F`ru-elxuGI*20yB>9M6W|WOOu*fMIVD$xb09aTWN`mEkedU!d%>Rv zcmUxCfgb`a0xSkB0XzbD6!17;Dc}jf(|~6H&jOwUJP&vQ@Dkuvz-xe&fY$+U0Nw_? z1NbN4eZU8R4*?$mJ_dXO_!RINU@c%h;7h<)fUg1H0X6`>2W$jv0{jg41@LcxDaCvi z5CSrQd_V!91W*dt8n6vuTR>evJ-`lt9RWK5>H~HGG%S5%+rN|^QKK>FGC=dv12fG_ zF+VMxwqNs7%uh=Pm_5O73D^hF7O*d%GoTBgE1(CUC!iOgH=qxoFQ6ZwKVSghK)^u2 z!GIxvLji{Y4hI|sI2teva17u$z=?p90jB^?1)K>u3osIJHsBn8`-&W5Ih0a!-Nx6y%-)?L*H`nmP6li@Ro!3Gwq@^Zvy@aSOs_&@E+iOzz2X20UrTA z27Che6tEWXIbc2D3+VqE_#41?fDM4}0UH570Dc7g1lR=F4EP!F3&2!EdjLd$7?1(v z0}23zfD%9{pcgMbH^2G!cv+ojqi z2mJ-l8vz;vngE&s_5id1v;yo6*cY%LV1GadKu16)zyW~HfG&U@fS!O}fIfhJfP(-7 z0S5yP0So~g1~|G}gMy=B&(YPI01pBlU{0vkIz9os6RMpFjJ;s+MuImIypiCI1aBmG zqre*l-YD=!fj0`g(bbMPU^IB6tGx(32pH?CYS$D`0zC=zRn;~YUj_Oq&{re<$$)DB z*8*+<+zPl2a64cI;10k{z~2FP0`3Cb4VVR(4VVL%3%CbxFW^4F{eXV}<^vW09s(=` zECM_XSPWPKcm(h$;4#4CfTe(XIK_2-u`!R_85yA`fnEx_5cDrAaF14l*vO}um-LIx zs@@q(pLmEdAA>H-;tNf_$j2|>Th4nr8PcM&2Ke>r$7UZK%X#jX$b11koxz`>HVFL> zwc(FSSFXaWTRmgCb#m#xu|sU0-6b~l!Dk-ZfWGIYz<--xWt+xz92?RU+X z4JyAg^p`-MX{`dk^;gRv!54c(PZ?-Qck4cx z3VD&dH$&c!fXqnH+w2sZ5BG^pE6AVwL~H`^CEdYDw;R$G+nS>ucZ8nXL0`Cko_Q7J zIScd&KSkDOh9Yit*fSRP2ragqrF0?Wo4~f=;L|p-;Z)c#4{3>?h%apgdTE~XB`N3W z;Ai`i(86B?{x+zi*5^A~_(i*?=Ow%Dro#rw*KWwm63{H?xmkP^pQ!`;r^9}sB`=ee zE<;}0A}?#eXI|((&7gA`_$<#2pjn=^LEp5>m95abRgX;`^3pJLdP)zf=x^dn^sBTw zYK6E`FE66*)~b9f=oJ4iL-}-pJ>P)-7@3eZDCNBLtH|sF`X|u)L8cF6*ftLY9l*9~ zXgjwxjd7a7n68i!A8ri#7|@48X1LlcJ|=k|0sisepW^XHfl!NeZ zM%*J0h)oaBcb{p@8M7j5^C5_PAM^|Wf6*I(*{89SX?9?29yk*D0{=Ytq@;T^=z*=# zmLM-Wr5}3|G}G#Vv`z${=?X3O6dmqt5c%h_;=T-8^xp;BmO;PdWexf$@d45Q5AdbF zgkRPw&%6ow!kWe`1i$4A=&M0X9lfjkN5JpAL<oxXVF*1isL(fL^Qg2cWN5=ki$c zd~DN|GFcD4*f1XS!^N@r67rKl{|Nq%peMlw;n(cz=uESGJ9bO~-}VXMPtTGOnlf{< z_(GFEAGE~X3vp*EEq#lmTLS$Zz+aLTS7^o+neA0(MV5@v*)o#`=9xjzvl4u!wM=QI z^(e~t1jww)(j#=Xo>yz+nX@6YCM)iSEP0_RzgcA@UvZR_o6wZOIR{s6$nR3*Mf!@* z;lsD19Z6rYE$X6nR$QSOx1Pqm6#5%x$p}rEGL;bge`8nlQJ|&1PHT+* z8FZ@;P+yRdxF2iWA)q&d-U@u7Wn8NZ`e>2S@lNtM`Dx7iL5SxivdiDYRT$9+` z0+|5xFvxF!-hpbf9k0P(3K^+8DJz-R$#^X?9IwSrsjmho*x}&Q_k^ZjodTL|c4U@} z(3BaIB{Kyy>u7?~cfwAgWv(T2TCqpw1Dl6B`fS)g4>EJJ;@*bxxpZY<_GlR6d`>b? zle{lD0P&zl%238%Szlben;X0LzuoEVkNSEDI+r0X^CdKGUIAM2C4KBX=*&l3d<^p9 z1Lwn@r$MiT4D&J_ZJG1Q>ELrdxn5yt6(ku{FUuuVx^@nIP|hAJ)d&*51*UqM`{ z_r0J;=u7u9rk&D{Kz|1v7nXzOoU?u%_=}{4GJFSo$&0j+_dy?yHYoIZtmC9FdK>fx z@DG4ap`$%qA0>SP`@NBBkCfXOrEOct;!n!5S!lLRp;`V?r&~e4`12I-#SeQT-RYpk z4+n#m@sw*Bp}EEp8Lks0uGEqAX`)m5T==wkCTNa3!l(U9;8$Ghtx!EuCabb&N$V%99nVBsE5T=5TLYSHO>CAv ze*$FIgU_@!Wa){qZe`rfS#hNgVVTr~{m;D6h?*aLTAirIf4Z@#`!WN$q{?U-J;~n^CgMI|^B6A_= zCqXwteq}E40?J?be*^zz@Wns5J{6kl(~+=Ed`!mQ;aPc{0GfTf&@AUkpjpmSvSfs& z%ygBJ`BG!ei`Xpb9tpdoZxOmR=r>{8OvsCGd<1$fX!?f8urCrm+uD54^ciUv)e%?h zUy{XN2AV!B{xEp0TmOpAna87VMOzbkf7tL^8M}}55#5#;xg|-GheH+_(GGv zK8r6j`5Qn>S$&0ktx;O$w=0#F@o_U~rX@7v#`sB;#Fcqz324d)O_|y%BRcD4$p}rE zhAJcTc3FeS+JDwev-mW_MEP0_RKR-*pGx|o^hq3;dCBGy~ewoTkJx+%G zcfx*I>#fL=7n=H4X32{^^z&6&@p|1Ugl60gpy|V#vt)#( zOk9xoYHiT8N7@wU74^VpT?pL^?QRG7Ml0n@-`G2g7TYL4Jc~avidGvcRar8pWbxNz@z;Z9`ELMC-w>Mhz8SRmhO|wQr(a3EKZ$f@%o3XG zIHB8Me@^%!&%Qk_O!Ny){UxBOzjl_4(3GjCGSYS$g695eS(d!ely8+KKYAa`$6Du^ zI?XUY0blYfe%LvSrjH$ldKn77)Wv@7(e}}Gvdo8r&oZxNS7`dr zDH>P&XC!F)kMPTV05okAdtOD^UJ9KPz^4r}LDPmwpgA9#4w_@!6wn+ig{Cjf2TlD; zvS{f`-$7b3Z$A;^LJiclox6hnDfkDU=jO38Z=4HzRv<3@L}-@zO3+e|G6t(X%CuZH86`F4qEaq`h{j(p&7RjWhnb0??FcLDEkJVWzmvf&R4$y zU*x|=IiIsN`bmr1#V_}dT0w}GHXX#B{aoR?f>X*8k;L-FYks-Yl{$GT23!q2nTA=0qh3FTbsRsVD;7eMfa}oGc zv55KbWB3*5?IHhf%&~>O{h8S8*(f%zKwjqawGmf*pj(r`t?N-%Z2MEPY!jOH$oh+E zk(PNW={nF)dS}qnRgd^*02`VX=9wLlF720j6kZLQMzFI9_(Hc=IzWA~E`;A7{N^h2 z!}`F!%MgCv7lD~O#_8D_w7laG{$H~AM}poPace^7drgeFW_Dz@1APp1?yLM=u~#-8 z^(y=m!7sr2Lga-nc_iHqJ|^_>&?#k3TH=zXyvT^W*z?*q_%5*8KLYwYgO+l*5VY(s zAqn$6)>yriFMC7-l%50`*^d%EVvp3L(EosbS)&O(2lQboe>c`>Ct?1%Cv;9h+=t2> zeJg0O`P{5}M0DHN4MSX!=Q}ddIT!LQ&xb*u02$gaLgnfIqGttUs<(9ZNZwyWzbExR zcHcbP?}>i;Pe+eF2I~tcH{rhpJrj_}xmo@*KZ{-lntg@P>??$3Uoj8%OIo6{2Xu1Z zV-b8_`i=d^p?txg^A+AfBQHZhe~xtjs`SsG4?%jI8=R$rGBmElmjYw-lR$ioE`UQO1mlHkJS~xv7Ag%2{@6|LmLN`*nHqM4fnMnCG%%WRm z(VaoF|L+Z&{eOGV+r1FjIiAoBLCgGhC}`>&37TFUJL#ZrN03^ z477~Ph3Muat@t2}ze*Q_uCMe~ps`JF&WHStN?!xI5$HX^zZLYJpnrl+(bEPrT)fX*C(ke?6v4v>+&KMGp<7D@MM(7nKKg|cEfi#*RJvA>nQ&GuR4xd>&o5^?1mk@&+0 zpa+RP$opE*6VMlRgl)1vCS!rnVyB$moASf|kBlXn8j`7HN%#e(5VN1uZ(I?~-->K+x=qgy#Ba z2y760Bwe8|Yk~Kgut(@;@eYo(%s0fI6QRF_!QSHm$Ro-@_Jk)RE%7~>w_Fce&ZW?o zmY_^zuRzx3XJ}edUqdy&(r=%u{6=V-LN`Rdq@NiNTI`W=MCg{_3oSCGp*znbbUmd- zhGV_xmo_5%PQvf5am6;4$?&W)nFyJokhvN5-vD|@No+1`g!M>PS=|mk^Cfz?hj5DK zMaocU?v04Nq$TG!L?>+%8Lsz*<{pdesg|!Xv(gei_c2C7UUbfcont^to!UJa(5zFD zXPwRieLV6dbJ`)G%UUOOP5&>1|C|n|7W<#oxc5UpX{j&Lx1f$liw`f#N^7aocSA<_ zUvG-cYfA46TJ#HlZ@fQU1pRM=9u3>XuY^zAgnm!$5qb@1c@HA=H=yrRoj-#X+k{^L z{W8}Tx(?_iO78-iGR;9RQ+_|twCAWS`t&UM3$pmvf|hnI`tQw>5t?Z|1X{)yk$E1p z9V?Y)TJL4a{2MfFEB;qx-qyGcK?`5@=Vi|Jj{48GXpe(Y<|l$rKU@p?EXZ%WM{Ekv z9y|XWnJXak6ZmW+LbHwB0D7X9`D)P9KtHBFf5~aFc?R?(_!9j`?^Na&@Hc_34?gQcXpu1kF|L884YwoR;=S@sKKN2cQeU!f_Bi^G zhmf!Bk>Bx=&I=!QU3?E>#<*A&)}uw^eL*%;HObBKbWY=K+>!9?u5-0%*}c75*Ul$Af17AoBFj zned-%x_D4*{sR4T!I$!vIgp!SAm0SD+=2qI3GO&VI4y z0HiDBc{^zFpMj8Y-Eq+ZPfKqSf~CAJ}2@6Uvp)3H0pw7BI$}RNqe+5-0bG< zDxGKa2RY58TL6sa~zR*&XSAWJ71yeK#$lj zbbY18&OJag@9neb=ZE9$A>^fhka>ml4@*!ck{8+Mmwra}zK($nqqEu(+p@$J8JTyT z2$^LXSIS@bGCw&Je75($sto%yX|pe$`??j7VP7XS z`?tx+S9j!hN0j*)u%`y*C$}Er-ldBEE1+j4(q*47d8ci?!5@OS;?JwV=eu>Gv-zS& zd~!{ejL?)3nld#}r?RIc0%A%KM(G#=i zXR~NoV^YuCpif4=rtS@Y20bc^rp%sM^0#Nvle1`9*HE5$k+MA(GLqkEDBH#;^PgJg zncG0G2LIwGVsj_x(@_pr6~*S7J>zovWTh8@{|M+-rAeC-nrEnl9?>7;&dV6HL6;23 zvvt&2h7V;u~F8dxIfG$KH=}Y2Q z*CD^>pq~}_J3wFk75eHd{lfnUed8C1D}1IUG|%TtTlfzB()W-N`8=e%8T5SBBW+gv zUu=j6VSf#@oH3{gTI8kg-466Dv{R8e5^GZ7Zw9|U_)D_%NV^c3rVc@GD1^EXv)OZ zl6(nG8KEh&9(`BdHU!ObE6bu=Wzp@k z=+0Sm?<{&CXxboSpZJozBP&t<8K^JOL;HoM{gM{>LX$7HkuNm)Vkh}RlP|WBFEsgL zo6yn+ip`V}nli(cmi8-tz6}008a8)?@4W?jJZMC>XY=H}$q;7Y?FD2(Eoy-J5*lU z;C)KxA?^au(tiI6+ZThD@|U=>PLwg1Yg?f?rxlr^z4Ohnut)eZE{nYAk-kXAvXLkg z`i9UfxA|FqD>V7i#{Um=!HqgmVIlC#Z!mX%s3!2E$N^N+AfvpdjG@8jxxb(4Z8@80Xz zeLXqlc}{u5`|?LWpKq`H*0ruR-s@iXy6?S#d|ue1wYS)F8smCX*_jjI?ZiiGtyv89 zkKOu(f!n1yY@PCZRq#<_`Z?@A46}EHy;DrtIs4HSG`7>eJ+gD%;S5H%*w>w2v7eie z*7NE>(Dwy>zu3?2S>xr}vZX$coqGZHNjk(HgT96`UlB8L63?L6^$cTYeuiDQL&;7b zwja{shq64Nb{`hIEbJe$KfH_ljLPn^Fh9&p_On&b1zO9sg5l;e8T6EuY+*|4w%L68=YaF88bbqoK%5J8-X3Ao2 z2u$7DPJNmS%4-D&dol2Q&AmseZ%m(8o->aVPdZ#@ht#+I{&FsGcS?Jku6^bu;=W|( zyf^mW>N(4w^jyAwoc$%)Uy&Wnn_`{~!L>U34Y!L|%nyOF(+@c-+ZA{~vAM6l+dH#p z+=tm$KV4l$~T>VanpFz*U&qk4mXDf{iZPQjd^ohf@>es)n?zoR|? z&lJBcZU}kdpL}1Df9&3q$8N^%b3WK%-X+KGd+q5e7o5>PId;bnXEdL)=R;aEcJI|= zhZm@vO|tu1M$B2|!`?gW`ho2qu9fECEz6o1C;JtY3;!$bS;VXxUrY8URByzFpBN9% zAkbiU8R7j^hp%Ytcn!b1>bX^RkE7dE4?YhtUwIv>*zVBUW1+4ctY`L-{vq+7wEuME zo?4SAE%OG~BVS*hN!J(b>*&7TcGgbj$Ajiqfu9Pzfzn>C81Tb=E@`=kIzTa)4-b4y z;8yY3Dhp{jn?$*Y{|mCizty_#3^Dh2XvnKdeGfiIcE&jGpq($iMBRe34VNV5{KHjO zs7@5yJtDP#J5I=1)yED}AA{c#|3-5@8rM~0%8Nb*|4lKUrSH|#XoHSF#a!1KEFv>l3yg z7(X}24;=kGE zzN)lWDlPUGQrgat7Is>gw09~kyouQDQmy*@p8R0XG?)8!m)J4D_5~xn7QbI z#EsHyum003J9#k|ogzO!(cJRcG>4q3J(JPzlx=&`J-oH}zr^**>o%?Buhe`_{Cmm1#*te4NkiG!4qPW*E=}D& zTARoxn*F7pA^U92`SZlviI>TKvUnHt;(~aOWIspz$z(rooPGafzcATJ+c8cvH)*~x z&VF>Ve>>Q(7N00P_q;cVxgNob6I0$_1pbYfauNSBahuYft?SxflZJTS6jK(sYPbKr zT!my`bDaI$WM41YNxNa-jT2Lc?qg^+Pxi;e+lV=zgMC&=J3H|dUF`Q2Q^uP0Dq|Bn z->+c3gndhmP41`Q12n#7X?%^T4|sf`cm0spp~*jWLfd^Um1SPY_job&hW?Am4u2(Z zG4U*w@d7bt$fqi=d3uJvx!#MTuhI`U(406^>@y+oH>9UrFmaa7kch99{c^=KO@6w? zn_R0i$ssN5w6N1I68}(o&l{YtxJG`k-h$`_x^Cv&w{ ze^`3nL1+r~-zfGvvq8)?k@X(^kTv{|<&*o<_2l!*ng@nLTg*`Z@re6oWtkaL5^pc;^|Ks+9;RS{LHJp z$-bHPc?ZP477U6V!%#>&oS19Sh}h*C6}w#M?I$nx{dKc=vFej|&&R}$84b++a?OWz zzpOPX`-JdwIlUhuW}bguZTgJrd5-$qWt-~T8RA-LT+e7mq~RI?|Kz@G=2dCnH-i1B z4(&(DUZ=E<)B4r)KnmuXwNBu=#9Xs-V$!;P>O%|-V%JGd>^f-_$Gq^ftNb*DwD7;^ zOu|DdFI=bg*mqBM;>_$mIg?MZO%)%Jn7TSa+$ueFg&*d$4W(gRc)Z-5 zuH9Sd@627cKbGv&!vHigAlkm#=B=#qhf7tyqTU+r1zaY&V#RISGnp+$6@Tl|$ zN)NA0Umx_a_3)=umg`i;x-ic)1U>9{;Fams4NA|czs`Q9=63cFexN$%%n9%8u#Si4{$m{7T?|1kN5+(XSJDTHq}LZzrCw ze&W2$d~a$$4wxAL&y0_j!!H_tE{PG_!Xszs~~C zP2321VCP&e`=WJXu6HnLy>7IhI@xDv z%>G1k4C9ynwx7n=6^fI3+amQ(c%Q(B20l&9m_<_z_KO2w5x6_?nBF7v_-a&~9$(Gk zTldyIo66NB_PXn2UGEqpH)>q8$nH8v1GDaG6T2Q@x1*Wz!mfux($Kf*x5QKW4vFlJ z2fO!#i?aJ%1o1qze>ooNjXCow)mvxq$y{-}%F8;|W1RS@E1uzgPx>C|-Oqc)9t+&7 z`o0PNv-;W(rN4YiCbP$s@?6&;y<;8{J7(4oZo2{5T@SG5pTS^1 zHL&k%=BezjN$+oO;^$8M$Ue4Jb>`mWljmy>iffeCeXLeYc_|Ban101|&Gk@~-WR6q zQ^iyEn4Eb;ex{`U%==c;#2d=a+zxLoeq0*#JBoRa7`xA6O&8lg<((}J&w#jpWc;$W zY}Wc2W)0k+*xYCORB!Gx*!?U6c6&7jeSgqnw;r}WFZS4M61!i~f85`igB^CCX$@&x zf~H;Ux-BH8Zaad#80=`=F4)ZrG~e=`_;aeyH8ci)sk*spzw)_d&a3d9`EKbQf0x+t z&sIJEPUDYnWcA4IxdL|m!=4{{lf86SM7-XCdY?}DV)vW?dryFT{aglieWE!~Z91y7 zePZW38rlMOoAyiNa~fGOv2k{Vet5k4*X_CnbIszc!aMTWna=i5_V&OXfd`Tw`Yr5p zbg=V0E--5*$=z-LeoHRbqemrK-dk5M1^wD!a(i^pue*nPEL{#_?I z*&Q3~*vQxCG3Wzdkx!ppVQ+A%+6a4lvd;~3%Tmp^@%$4jl8bND`u|Cn>1;T3fvMnC-(Sl5IZgGzSI~rzSduHfYy%bx_`C!bj5&vbH#bK z_?s#t{dqyMZzH>TrkFW@p){p?4e<{X7el_phEL|A_FyL-+dC4I7wmS2{Y^Rer}9t# zhpF3L6+@@kZ3?>%-_kt4uKE@4x%9|RUij|~+$UZ!juNxqh}|)CiQON(-e6y`N`2q~ z#nUhU-0N|c{*#Iaz55Je4E9NKQDH9o5Yln>vc=g6GL0jw+21)#$JkTf5iiHHg7|5 zA*3yeJq0&tcppE{&E2O_!~nf!Q^5|joyCnYd zO7tz#Y$gqQ82#RfFItJdD2-#=Ro9*GtBwzyt2t48jd+{HoMGD}G0%KIBkoFR*`I;; zQ9bx#7GMcM&rh?Bfv=at>4 zY^QH97rDJ)j}gY{e2vo!G)`-zcRMc8T){Q&Q1!#biH{a9O?-lQl>JquWt~4FwtxH( zPidb-cE6|dw9;b#g3>Nae1@2`o}*xoO>2rNFR$}$zesl034YHMepvnXQrX{%lSqhhl3^cE)Lo*!2dxK4IrIL*JIc5Ak!|ftmlCr14m86nou1GwC_=IZaGE zUax#-1^e`5Uli))Q@oecSu-5z>}J^6W9 z_Ic8*Sc8c-JF(nnC?k62x`NnqW}BEgu&! zR#}ccr+g0C?}UCw^Vr!ZsXyqQ?jF^N>$W#BeTnjpDed0!GedQ<`kvYwnXczJNke4ZXyCC>C$Q@Trv9n7(^VG#9$bxL!%kb&CT2Xs zUNbCEz46}Pd5XDSbEEBbK|@`!hX0y0q%DmLt%d5vE7lBRj|7<^;_-F6ya`C5RXPu13F)R=>hg>N= zOc}2iqi>e}M)CFHTAj1~QL-N+dvCIn_9x=oq&X$|C$0U%XUR^S@I~S!(k~K|_Q8}E z{jZXp7@kP>OQmNYn;4i&`K|@`+J27=`<2qrb_>O%9a37x)OSV(L z>@~+|k4S!~6V6T)#g45b#XxM>9S`p_-mc$8sV$Z$p7o9{$Kd`zJYI87k>2e!HSn~+_(a3Et)?gDoz|s_4g0C8=f}kKv4WU< z7o4bhM)|^zDTa@T@z4B2{lNYn%W~B--$6xBJl4S0kT2iTBrSYXtM>L1_ljvR*E!>N zhBQy8U5bjQTjgc$VlCPu_FRgFc3GftLJa=8f?<2vik@(%o0i~sV(ZgOt!qgjUZ2WsIhrLuTt>p&AUds)MDGU3q zu=fPexV*%(mSSKZk3Qpk8Os~UeznRqQ?aowB_873GQ~{VnPSE=JX=iPfDaebSJAT< zcaXk6V_q2e_`v4`-X`$0z}<>xwtV{e4(xaBVcxH2><_1UV2{Z5HFeE3SBpo2W~uri zaq`}fHM|dH=G|&D=S$6{>$uu|waRYIH_m+%uUSq znno3m>mR$zh23op`<@i`+yZ;9E2TXnn|ZNCV@&zduMShEC5AH;!_O2?m99H{i)pDC zy}uhaFU7;SMqiYkJ#aMNmd{B##{z#>%>6aoBjz_x;ST8^Pd@LLy;k;YT_rZyV-R+E z>w*Th2DXMehpDUQH2?c|oO`rR@H3qOvFj?@2W2Pi_}^>J1^-!Hf9(gmf3F!{mEUV_ zl75ob{QO=s%$~_R+B1Py<@cH?<9q$R=DhM+NBQ!*%rG&m`G+#E?01>T%fHLqrMhB` z&GVpwn6_XJZ&3Ys4v+QxKlyFtZpGl=R)&dZeZ>Q>?6;Nw|N1@U`87H>uIJZpY7fxN z_uk=81Uqc|=Hg!^JzS^X0_R;+ez);SjfK;d_FEbk%#HB(#Jqn8@1y68rSEZxZz|}! z7W-))P@T|MSu18Yu5gp~F5DKivb!ycn!CK`0Q(xjdXn#|ZLMp3P4dGW4x?dD>1^eD zTvayntm1?{*6KpOl)b&Wd@iCs_*szjS8I$n$nMy3V#n4P{G9bMy?6fyt^a?fc}Z#S zP(5&ssMT{Q&s|Ny|0T&kbCL5JR-9;}-uDqlO3!n;_G7iT67p>c+!{EanD4H`Tr0U= zaGsXEDY!@d-*W=&{hxl#pZ;xy>y_{G+AAo0R_iO(`4W|j>nZj(b}e7WVSi5#c0Df^ z*SxB0k;=us%Gb2#F)A*EI8Tw!XXJC;W7V&eFZYSG;~dS;{w<+}w7WFCGuy7TZVT>N z_}+N)kM+HA>B*P+yjx|Y4m0x8lk&o6uh@0lA7WUn*!Yc9|MvPD%FE1e%bIKVF7s&c zncbvP-kQYp?Rv5I&2m8l`wUy7*kx%FyI-MkoO!Y96Fd6Sw~>RsC27d_BP#pd=j+;c zTsa2&Xv)EOe>C>lt~uQKGVcEVnD%Yb805Z*@z|wt;cMl+>Q@u7 zCK*)>uAjIj!R|E)yeeyw8lCa?ngr(h`=t63yeexF%J^Q_B(=)RYZ90k{-7A(m90t0 z>-}7lv?*V&Nnql6S@FOhq&3Oq>U;EuP1N7`Ry^P9<6Eh=Zz((bKkOUxTM~>@_TjnL z=vP@hfA)zzM-2!2V6YE~SKKp-ea|=+^rJyv_pwS}txsHfMyzYP`*4lyjv*&@42?<8 z+|?lVxQ6}Q0roQy*!w3x9 zmgJxLvsLWadP6<5g|x+_Cm!^kTMA<5)e-bl)L#7V)u{Y;hP2E%{*9{cVDAyTj9p^l z;Tq8!HK87clb!fS0{3e@;OkMp?2c_z?7A9Dddh;{Wgn1zXYJYWTq!%foR;4wtr0sd z8sn7`1^5Pp8uzfa4!}@1X`}44WCkXcM1T~4> z_wr)L&@6VGt>P7JqZt!S|H?Q{wuKD}IJU+Cj1Xj|4k$;-C9j_+YICzAYY2cG`U`G0z+eX|I5G!S3=} z&z=c(%EdJlyW0gjb;z^A>}KUU2hIX|t*OA&&)yJfF+rjvxztu^f(|+=d zJG3TGeptgd1V1_PM^CEG94t+v7@zygCmPoeerC(hF48oKUAK9$>+5)~-CyMYEzF#O_yIYdqJD$?m?9t*_);6Sz+7IT`kx40}$_1$~3q z^G2iC^8~6cJV9y6e<1x}KUJ<|8F=KZ>X%Rb~*1$!v%L|j1aa{;@INIIc(3^V$cZoe$^d&p< z40ij0U7y`S58FPZ{*0cw^>q_JXzZt7>~3%s`D6XBd+yc51@D1VV`rZ-Lh;a&N11YFMe|J^OfX>___bh1${$e`a@%|X9J@@ zZi{jo!LBRP`rI{k=Sy01jrgD1?_@s#yXz3U$Fh#BuUN0bw&UMS+MN{x>&YfD_0RP; zFLwQ-actP#52+u=QzxblOV=Q=%Y{8^94B>RI~?s!OPp>C%H=kKzo@mA%kHwgqO($; z?$mEfouY5|sErQ)byem{^?#>D->kImW36JBu|@285BA(h*&R<&?6hrSr!9z`7Ixas zpl=WQj-ZFFr;oV~VdqQy)DL5)N9=kSO?vM2dS!P#^d(K{-XYir#2zpB`K;cHyj=6x zL#nGG)zw<+tMJ?69mLe(Lh*>|a8P>BW3Xd*OLIGC|9-FU91aCLOw5c6*lDv{Rjw(p zuNSbd7qvkHTLW8DFLn&D^Wt|Xj?o(6I+d$2XqpmJC(ViJ4=urt-hN=m+!i#&#Ek3q zVDA+Bp01|4+)r2objj|1g2w)_dmIrDcG|aF>~X<8m$^rFmzUUJ*6{30|Lm#qd4Qp0 zr;Gz)x9>=>j|LtLnz3Z3Tzc)fG~PKsgWdg*_}#~_`?`?bx*QMr7L?XwvrGMlzRjAa zFZk~dOq`U3d4qV&HS+0kL7!xQ?+C@#scZC#dXUEJlSZ-o3G9B77o#V(b+!L+<3riZ zSBts^CA-(X8*6R$(K}Rj*~yJz}CRlbcA~DmfhvT?tTbIyZij0 z)=sWx*!n*C@f_GIcG(BS)(onRyidZq%RDHJ+hQm&{S9{7TpBy9|A%F_{~8@cbJ{W4 z-M4FleKgqXWOuoo7vrTq=tqJcc6spwm#+JP8&cZ4G`BPbKh1$zLz0%cr8U^w5@T-{ zuV`Pf+YY!M-VT_>>Zqsh*g z8WX#ov)feqMvd5QS1ZO3di+=qdmPmTJ?ybiAJXOmHw5PXg6~=^QM=@Wy)|%)*!{Lm z?AV&co@d|{*Kx7SUJUGZEY1JHUKj4&I)lBS{@{GOWOu&k*+bq}b47R3Q$NfPJ8NyZ z(OtUM2YX-eIS_bQ?7jrMO<}j`Xwbmcz}93xUhxB416xxoc01Mu&WRlZ?6k1cE=$i_ z`EBL4P$v!2xK0|yu5;LPLQ}F+Kl#9m)K@t##4~-)vke_qKF`(~G;M*4fja^h#BQTb zvE%6q8t?PJs6NISEq?c{pz}Q4(zrjjBt3njFQgp^JQ8>`Fljyajs<&Nu2Sdqfw>Po zOXHfd*T}wD{h#Y>E@(Voz0Q0U>_hVJ>vBHWxyJe0-IHQq4r~q@?6eDOl}@#z*C#M` zuBY60(~hKVQCgP^_IPg?OIopmh}&84eWej_s{ka19q3UE7|D}uw1P=xA4yC9n5w@R^HOh6u-t~a= zS$zlf&6oL2*77sEza@U|+)1S~OGC0#SF{V7wWU8qFWSRzNOP{<8Q0cfMWZ>~Wgieb z-;CZDDE>faEzT_0q3zw(6@7Q8H`tny6a#fNnwYj2)7c)6_c7Vs57E0nTTdUuj!*hF z_UPa5_0()zDGS%5jlMXk^nEbK6nRZLPJ6S`I4{`!xnAse>Vl?0?6p%t>ojW`gJw+q z*73k-SeG^h4eY#_6JAaG26@@tMy-L1iK#2tWp59fPO;s8XaETk>@)P6;um>75_(l7RP9QO5nK~|bszx&Xl*jy*M zq^G>tiJ917*CE$Rb4ObHmCiIOt>bJAewxHCOLNfV#cto0pl=QOLeLk3rc>;-3hcEC z?6pdF(7@Kf)>N%kF5|lD1Ms(>$!1P`QD^CE^j(qJ)tQZw-RFm~yH3zwqHFgSXtb8N zOw2i6G?X_d`&4PDljD+4>TQQ)cOUCfJg)!V5HswUVaMDbG_W78`I^pEiaDn@OEC}0C-cBo3)Sb7fBMO-n%gJhtjuHb+G%a z4!kO7b+X%4&g#INOF2#FO5s&Gt3w&z>sg%|<>j+FFfp8?7~qwi)giC<^Q=y*^7UCA zn0PK!Jn#qUtj;uzohvn_j+m);oW+a8&nLcHJacQUNfh&6&d+43bS4x2r+Dqex2vyg zARdyPvG6Zv>$;zqK8AhM3-q2t(wr#fHz2axSLUlRX*{R(h%eJv{$}#yIj}~0&b9K6 zCqCDbA7U65lP~KQv-6!S%_b?|nc^MAgCVb4`Q%;KZ>U}1hxE;asLI@`yx=+Q`tI&Vx?f0|(zPHl zY3=6@aYwS_2QJDF-w=lXZ+>Ple{NEp*;{kVOtIf7>r&c_M)JEKVBN4;sν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

GClpfiHD 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 H^8y0^1@hGs literal 0 HcmV?d00001 diff --git a/ApiAsAService/WebAPI/sln/.nuget/packages.config b/ApiAsAService/WebAPI/sln/.nuget/packages.config new file mode 100644 index 0000000..d681840 --- /dev/null +++ b/ApiAsAService/WebAPI/sln/.nuget/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/sln/WebApiOData.AspNet.sln b/ApiAsAService/WebAPI/sln/WebApiOData.AspNet.sln new file mode 100644 index 0000000..c03a266 --- /dev/null +++ b/ApiAsAService/WebAPI/sln/WebApiOData.AspNet.sln @@ -0,0 +1,98 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C40883CD-366D-4534-8B58-3EA0D13136DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "..\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Test.Shared", "..\test\UnitTest\Microsoft.AspNet.OData.Test.Shared\Microsoft.AspNet.OData.Test.Shared.shproj", "{D909E7AB-3281-46EA-929B-F35FE7E94B0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData.Test", "..\test\UnitTest\Microsoft.AspNet.OData.Test\Microsoft.AspNet.OData.Test.csproj", "{D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 + ..\test\UnitTest\Microsoft.AspNet.OData.Test.Shared\Microsoft.AspNet.OData.Test.Shared.projitems*{d5dbe2c8-ce32-43ef-b39e-0b11f6629f44}*SharedItemsImports = 4 + ..\test\UnitTest\Microsoft.AspNet.OData.Test.Shared\Microsoft.AspNet.OData.Test.Shared.projitems*{d909e7ab-3281-46ea-929b-f35fe7e94b0f}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + CodeAnalysis|x64 = CodeAnalysis|x64 + CodeAnalysis|x86 = CodeAnalysis|x86 + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x64.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x64.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x64.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x64.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x86.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x86.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x64.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x64.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x64.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.Build.0 = Release|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.CodeAnalysis|x64.ActiveCfg = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.CodeAnalysis|x64.Build.0 = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.CodeAnalysis|x86.ActiveCfg = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.CodeAnalysis|x86.Build.0 = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Cover|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Cover|Any CPU.Build.0 = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Cover|x64.ActiveCfg = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Cover|x64.Build.0 = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Cover|x86.ActiveCfg = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Cover|x86.Build.0 = CodeAnalysis|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Debug|x64.Build.0 = Debug|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Debug|x86.Build.0 = Debug|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Release|Any CPU.Build.0 = Release|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Release|x64.ActiveCfg = Release|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Release|x64.Build.0 = Release|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Release|x86.ActiveCfg = Release|Any CPU + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A6F9775D-F7E2-424E-8363-79644A73038F} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} + {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} + {D909E7AB-3281-46EA-929B-F35FE7E94B0F} = {C40883CD-366D-4534-8B58-3EA0D13136DF} + {D5DBE2C8-CE32-43EF-B39E-0B11F6629F44} = {C40883CD-366D-4534-8B58-3EA0D13136DF} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/WebAPI/sln/WebApiOData.AspNetCore.sln b/ApiAsAService/WebAPI/sln/WebApiOData.AspNetCore.sln new file mode 100644 index 0000000..9dc43cc --- /dev/null +++ b/ApiAsAService/WebAPI/sln/WebApiOData.AspNetCore.sln @@ -0,0 +1,65 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{31D1EFDE-8A05-4489-9621-3B163D48B0F0}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OData", "..\src\Microsoft.AspNetCore.OData\Microsoft.AspNetCore.OData.csproj", "{0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2F6228EC-37EE-4FCB-A38A-B94EE472E70E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3789AD84-4A87-45C1-BD5C-3B4BE39157C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreODataSample.Web", "..\samples\AspNetCoreODataSample.Web\AspNetCoreODataSample.Web.csproj", "{0D53FCCE-2173-4FDB-A552-C3BBB71DDC49}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Test.Shared", "..\test\UnitTest\Microsoft.AspNet.OData.Test.Shared\Microsoft.AspNet.OData.Test.Shared.shproj", "{D909E7AB-3281-46EA-929B-F35FE7E94B0F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OData.Test", "..\test\UnitTest\Microsoft.AspNetCore.OData.Test\Microsoft.AspNetCore.OData.Test.csproj", "{2B8085F7-3625-489E-A963-E29C2BD1AA76}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 + ..\test\UnitTest\Microsoft.AspNet.OData.Test.Shared\Microsoft.AspNet.OData.Test.Shared.projitems*{d909e7ab-3281-46ea-929b-f35fe7e94b0f}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C}.Release|Any CPU.Build.0 = Release|Any CPU + {0D53FCCE-2173-4FDB-A552-C3BBB71DDC49}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {0D53FCCE-2173-4FDB-A552-C3BBB71DDC49}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {0D53FCCE-2173-4FDB-A552-C3BBB71DDC49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D53FCCE-2173-4FDB-A552-C3BBB71DDC49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D53FCCE-2173-4FDB-A552-C3BBB71DDC49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D53FCCE-2173-4FDB-A552-C3BBB71DDC49}.Release|Any CPU.Build.0 = Release|Any CPU + {2B8085F7-3625-489E-A963-E29C2BD1AA76}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {2B8085F7-3625-489E-A963-E29C2BD1AA76}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {2B8085F7-3625-489E-A963-E29C2BD1AA76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B8085F7-3625-489E-A963-E29C2BD1AA76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B8085F7-3625-489E-A963-E29C2BD1AA76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B8085F7-3625-489E-A963-E29C2BD1AA76}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {31D1EFDE-8A05-4489-9621-3B163D48B0F0} + {0FC1BF5E-F882-43D8-9451-ED9FA90DEA4C} = {31D1EFDE-8A05-4489-9621-3B163D48B0F0} + {0D53FCCE-2173-4FDB-A552-C3BBB71DDC49} = {3789AD84-4A87-45C1-BD5C-3B4BE39157C8} + {D909E7AB-3281-46EA-929B-F35FE7E94B0F} = {2F6228EC-37EE-4FCB-A38A-B94EE472E70E} + {2B8085F7-3625-489E-A963-E29C2BD1AA76} = {2F6228EC-37EE-4FCB-A38A-B94EE472E70E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {731B144D-90AE-4567-B337-7CFE9965AACC} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNet.sln b/ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNet.sln new file mode 100644 index 0000000..607b81c --- /dev/null +++ b/ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNet.sln @@ -0,0 +1,127 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DD61AE62-B084-4A8C-AE1E-615A62BA645D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F4C937D2-3BD0-4F6B-B75F-A526A7FD978B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{2EB8D1F9-C505-4FA8-8D1C-F167B5B1AB8E}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "..\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.E2E.AspNet.OData", "..\test\E2ETest\Microsoft.Test.E2E.AspNet.OData\Build.AspNet\Microsoft.Test.E2E.AspNet.OData.csproj", "{436F2E52-4CBC-41E5-BD47-66C16184A214}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{D7B0F2DE-65A1-4AA1-889A-02A2DAC75EF8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetODataSample.Web", "..\samples\AspNetODataSample.Web\AspNetODataSample.Web.csproj", "{8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + CodeAnalysis|Mixed Platforms = CodeAnalysis|Mixed Platforms + CodeAnalysis|x86 = CodeAnalysis|x86 + CodeCoverage|Any CPU = CodeCoverage|Any CPU + CodeCoverage|Mixed Platforms = CodeCoverage|Mixed Platforms + CodeCoverage|x86 = CodeCoverage|x86 + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Mixed Platforms.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Mixed Platforms.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeCoverage|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeCoverage|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeCoverage|Mixed Platforms.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeCoverage|Mixed Platforms.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeCoverage|x86.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeCoverage|x86.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeAnalysis|Mixed Platforms.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeAnalysis|Mixed Platforms.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeAnalysis|x86.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeAnalysis|x86.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeCoverage|Mixed Platforms.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeCoverage|Mixed Platforms.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeCoverage|x86.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.CodeCoverage|x86.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Debug|Any CPU.Build.0 = Debug|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Debug|x86.ActiveCfg = Debug|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Debug|x86.Build.0 = Debug|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Release|Any CPU.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Release|Any CPU.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Release|x86.ActiveCfg = Release|Any CPU + {436F2E52-4CBC-41E5-BD47-66C16184A214}.Release|x86.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeAnalysis|Mixed Platforms.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeAnalysis|Mixed Platforms.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeAnalysis|x86.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeAnalysis|x86.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeCoverage|Any CPU.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeCoverage|Mixed Platforms.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeCoverage|Mixed Platforms.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeCoverage|x86.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.CodeCoverage|x86.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Debug|x86.ActiveCfg = Debug|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Debug|x86.Build.0 = Debug|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Release|Any CPU.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Release|x86.ActiveCfg = Release|Any CPU + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A6F9775D-F7E2-424E-8363-79644A73038F} = {F4C937D2-3BD0-4F6B-B75F-A526A7FD978B} + {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {F4C937D2-3BD0-4F6B-B75F-A526A7FD978B} + {436F2E52-4CBC-41E5-BD47-66C16184A214} = {DD61AE62-B084-4A8C-AE1E-615A62BA645D} + {8D2B5B0E-9CE1-4708-8024-2DBCAD51D9ED} = {D7B0F2DE-65A1-4AA1-889A-02A2DAC75EF8} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNetCore.sln b/ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNetCore.sln new file mode 100644 index 0000000..956b0fe --- /dev/null +++ b/ApiAsAService/WebAPI/sln/WebApiOData.E2E.AspNetCore.sln @@ -0,0 +1,100 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DD61AE62-B084-4A8C-AE1E-615A62BA645D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F4C937D2-3BD0-4F6B-B75F-A526A7FD978B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{2EB8D1F9-C505-4FA8-8D1C-F167B5B1AB8E}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OData", "..\src\Microsoft.AspNetCore.OData\Microsoft.AspNetCore.OData.csproj", "{3571A6B6-B859-4FA2-A96F-40956BFC3837}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.E2E.AspNetCore.OData", "..\test\E2ETest\Microsoft.Test.E2E.AspNet.OData\Build.AspNetCore\Microsoft.Test.E2E.AspNetCore.OData.csproj", "{0073FACD-2887-4D0A-8D3E-BAA17749B629}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + CodeAnalysis|Mixed Platforms = CodeAnalysis|Mixed Platforms + CodeAnalysis|x86 = CodeAnalysis|x86 + CodeCoverage|Any CPU = CodeCoverage|Any CPU + CodeCoverage|Mixed Platforms = CodeCoverage|Mixed Platforms + CodeCoverage|x86 = CodeCoverage|x86 + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeAnalysis|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeAnalysis|Mixed Platforms.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeAnalysis|x86.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeAnalysis|x86.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeCoverage|Any CPU.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeCoverage|Any CPU.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeCoverage|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeCoverage|Mixed Platforms.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeCoverage|x86.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.CodeCoverage|x86.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Debug|x86.ActiveCfg = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Debug|x86.Build.0 = Debug|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Release|Any CPU.Build.0 = Release|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Release|x86.ActiveCfg = Release|Any CPU + {3571A6B6-B859-4FA2-A96F-40956BFC3837}.Release|x86.Build.0 = Release|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeAnalysis|Mixed Platforms.ActiveCfg = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeAnalysis|Mixed Platforms.Build.0 = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeAnalysis|x86.ActiveCfg = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeAnalysis|x86.Build.0 = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeCoverage|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeCoverage|Any CPU.Build.0 = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeCoverage|Mixed Platforms.ActiveCfg = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeCoverage|Mixed Platforms.Build.0 = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeCoverage|x86.ActiveCfg = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.CodeCoverage|x86.Build.0 = CodeAnalysis|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Debug|x86.ActiveCfg = Debug|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Debug|x86.Build.0 = Debug|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Release|Any CPU.Build.0 = Release|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Release|x86.ActiveCfg = Release|Any CPU + {0073FACD-2887-4D0A-8D3E-BAA17749B629}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {F4C937D2-3BD0-4F6B-B75F-A526A7FD978B} + {3571A6B6-B859-4FA2-A96F-40956BFC3837} = {F4C937D2-3BD0-4F6B-B75F-A526A7FD978B} + {0073FACD-2887-4D0A-8D3E-BAA17749B629} = {DD61AE62-B084-4A8C-AE1E-615A62BA645D} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {77BB0BF0-A97E-4853-B078-5B4A5AD50668} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/WebAPI/sln/WebApiOData.Performance.Official.sln b/ApiAsAService/WebAPI/sln/WebApiOData.Performance.Official.sln new file mode 100644 index 0000000..d802727 --- /dev/null +++ b/ApiAsAService/WebAPI/sln/WebApiOData.Performance.Official.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiPerformance.Service", "..\test\PerfTest\WebApiPerformance.Service\WebApiPerformance.Service.csproj", "{F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{0C3B5E95-47F3-42FE-AFBD-9FA823808470}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiPerformance.Test", "..\test\PerfTest\WebApiPerformance.Test\WebApiPerformance.Test.csproj", "{5C4C506B-9BFE-429D-A48C-77CEC613877A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Debug|Any CPU.Build.0 = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Release|Any CPU.Build.0 = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Debug|Any CPU.Build.0 = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/WebAPI/sln/WebApiOData.Performance.sln b/ApiAsAService/WebAPI/sln/WebApiOData.Performance.sln new file mode 100644 index 0000000..fe39581 --- /dev/null +++ b/ApiAsAService/WebAPI/sln/WebApiOData.Performance.sln @@ -0,0 +1,52 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiPerformance.Service", "..\test\PerfTest\WebApiPerformance.Service\WebApiPerformance.Service.csproj", "{F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{0C3B5E95-47F3-42FE-AFBD-9FA823808470}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiPerformance.Test", "..\test\PerfTest\WebApiPerformance.Test\WebApiPerformance.Test.csproj", "{5C4C506B-9BFE-429D-A48C-77CEC613877A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "..\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 + ..\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Debug|Any CPU.Build.0 = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7B5A5D5-0B2F-493B-94B2-039B881FD4D3}.Release|Any CPU.Build.0 = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Debug|Any CPU.Build.0 = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C4C506B-9BFE-429D-A48C-77CEC613877A}.Release|Any CPU.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/WebAPI/src/CodeAnalysisDictionary.xml b/ApiAsAService/WebAPI/src/CodeAnalysisDictionary.xml new file mode 100644 index 0000000..fdd4e0e --- /dev/null +++ b/ApiAsAService/WebAPI/src/CodeAnalysisDictionary.xml @@ -0,0 +1,76 @@ + + + + + Multi + Bitly + Digg + Facebook + Reddit + Captcha + Facebook + Gravatar + JSON + Lookahead + MVC + Param + Params + Pluralizer + Pragma + Pragmas + Templating + Unvalidated + Validator + Validators + Validatable + WebPage + cshtml + vbhtml + asax + Eval + Src + Charset + Coords + Rel + Dto + Tokenizer + ReDim + OAuth + OpenID + Yadis + fwlink + Edm + Deserializer + Api + ws + enc + dir + Auth + bg + Cors + Owin + Unbuffered + Rfc + Realtime + ModelName + BSON + Untyped + + + WebPage + WebPages + TimeLine + oAuth + userName + modelName + HasId + + + + + ID + Db + Dto + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/CommonAssemblyInfo.cs b/ApiAsAService/WebAPI/src/CommonAssemblyInfo.cs new file mode 100644 index 0000000..522a341 --- /dev/null +++ b/ApiAsAService/WebAPI/src/CommonAssemblyInfo.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +#if NETCORE +[assembly: AssemblyProduct("Microsoft OData Web API for ASP.NET Core")] +#else +[assembly: AssemblyProduct("Microsoft OData Web API for ASP.NET")] +#endif +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: ComVisible(false)] +#if !NOT_CLS_COMPLIANT +[assembly: CLSCompliant(true)] +#endif +[assembly: NeutralResourcesLanguage("en-US")] +[assembly: AssemblyMetadata("Serviceable", "True")] diff --git a/ApiAsAService/WebAPI/src/GlobalSuppressions.cs b/ApiAsAService/WebAPI/src/GlobalSuppressions.cs new file mode 100644 index 0000000..ccb18be --- /dev/null +++ b/ApiAsAService/WebAPI/src/GlobalSuppressions.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay-signed")] diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchContent.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchContent.cs new file mode 100644 index 0000000..902eb9b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchContent.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + ///

+ /// Encapsulates a collection of OData batch responses. + /// + public partial class ODataBatchContent + { + private IServiceProvider _requestContainer; + private ODataMessageWriterSettings _writerSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The batch responses. + /// The dependency injection container for the request. + private void Initialize(IEnumerable responses, IServiceProvider requestContainer) + { + if (responses == null) + { + throw Error.ArgumentNull("responses"); + } + + Responses = responses; + _requestContainer = requestContainer; + _writerSettings = requestContainer.GetRequiredService(); + } + + /// + /// Gets the batch responses. + /// + public IEnumerable Responses { get; private set; } + + /// + /// Serialize the batch responses to an . + /// + /// The response message. + /// + private async Task WriteToResponseMessageAsync(IODataResponseMessage responseMessage) + { + ODataMessageWriter messageWriter = new ODataMessageWriter(responseMessage, _writerSettings); + ODataBatchWriter writer = messageWriter.CreateODataBatchWriter(); + + writer.WriteStartBatch(); + + foreach (ODataBatchResponseItem response in Responses) + { + await response.WriteResponseAsync(writer); + } + + writer.WriteEndBatch(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchHandler.cs new file mode 100644 index 0000000..1deddc2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Batch/ODataBatchHandler.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Defines the abstraction for handling OData batch requests. + /// + public abstract partial class ODataBatchHandler + { + // Maxing out the received message size as we depend on the hosting layer to enforce this limit. + private ODataMessageQuotas _messageQuotas = new ODataMessageQuotas { MaxReceivedMessageSize = Int64.MaxValue }; + + // Preference odata.continue-on-error. + internal const string PreferenceContinueOnError = "odata.continue-on-error"; + + /// + /// Gets the used for reading/writing the batch request/response. + /// + public ODataMessageQuotas MessageQuotas + { + get { return _messageQuotas; } + } + + /// + /// Gets or sets the name of the OData route associated with this batch handler. + /// + public string ODataRouteName { get; set; } + + /// + /// Gets or sets if the continue-on-error header is enable or not. + /// + internal bool ContinueOnError { get; private set; } + + /// + /// Set ContinueOnError based on the request and headers. + /// + /// The request header. + /// Flag indicating if continue on error header is enabled. + internal void SetContinueOnError(IWebApiHeaders header, bool enableContinueOnErrorHeader) + { + string preferHeader = RequestPreferenceHelpers.GetRequestPreferHeader(header); + if ((preferHeader != null && preferHeader.Contains(PreferenceContinueOnError)) || (!enableContinueOnErrorHeader)) + { + ContinueOnError = true; + } + else + { + ContinueOnError = false; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionConfiguration.cs new file mode 100644 index 0000000..f725080 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionConfiguration.cs @@ -0,0 +1,292 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// ActionConfiguration represents an OData action that you wish to expose via your service. + /// + /// ActionConfigurations are exposed via $metadata as a element for bound action and element for unbound action. + /// + /// + public class ActionConfiguration : OperationConfiguration + { + /// + /// Initializes a new instance of class. + /// + /// The ODataModelBuilder to which this ActionConfiguration should be added. + /// The name of this ActionConfiguration. + internal ActionConfiguration(ODataModelBuilder builder, string name) + : base(builder, name) + { + } + + /// + public override OperationKind Kind + { + get { return OperationKind.Action; } + } + + /// + public override bool IsSideEffecting + { + get { return true; } + } + + /// + /// Register a factory that creates actions links. + /// + public ActionConfiguration HasActionLink(Func actionLinkFactory, bool followsConventions) + { + if (actionLinkFactory == null) + { + throw Error.ArgumentNull("actionLinkFactory"); + } + + if (!IsBindable || BindingParameter.TypeConfiguration.Kind != EdmTypeKind.Entity) + { + throw Error.InvalidOperation(SRResources.HasActionLinkRequiresBindToEntity, Name); + } + + OperationLinkBuilder = new OperationLinkBuilder(actionLinkFactory, followsConventions); + FollowsConventions = followsConventions; + return this; + } + + /// + /// Retrieves the currently registered action link factory. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")] + public Func GetActionLink() + { + if (OperationLinkBuilder == null) + { + return null; + } + + return OperationLinkBuilder.LinkFactory; + } + + /// + /// Register a factory that creates feed actions links. + /// + public ActionConfiguration HasFeedActionLink(Func actionLinkFactory, bool followsConventions) + { + if (actionLinkFactory == null) + { + throw Error.ArgumentNull("actionLinkFactory"); + } + + if (!IsBindable || + BindingParameter.TypeConfiguration.Kind != EdmTypeKind.Collection || + ((CollectionTypeConfiguration)BindingParameter.TypeConfiguration).ElementType.Kind != EdmTypeKind.Entity) + { + throw Error.InvalidOperation(SRResources.HasActionLinkRequiresBindToCollectionOfEntity, Name); + } + + OperationLinkBuilder = new OperationLinkBuilder(actionLinkFactory, followsConventions); + FollowsConventions = followsConventions; + return this; + } + + /// + /// Retrieves the currently registered feed action link factory. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")] + public Func GetFeedActionLink() + { + if (OperationLinkBuilder == null) + { + return null; + } + + return OperationLinkBuilder.FeedLinkFactory; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The name of the entity set which contains the returned entity. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsFromEntitySet(string entitySetName) where TEntityType : class + { + ReturnsFromEntitySetImplementation(entitySetName); + return this; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entity set which contains the returned entity. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsFromEntitySet(EntitySetConfiguration entitySetConfiguration) where TEntityType : class + { + if (entitySetConfiguration == null) + { + throw Error.ArgumentNull("entitySetConfiguration"); + } + + NavigationSource = entitySetConfiguration.EntitySet; + ReturnType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TEntityType)); + return this; + } + + /// + /// Sets the return type to a collection of entities. + /// + /// The entity type. + /// The name of the entity set which contains the returned entities. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsCollectionFromEntitySet(string entitySetName) where TElementEntityType : class + { + ReturnsCollectionFromEntitySetImplementation(entitySetName); + return this; + } + + /// + /// Sets the return type to a collection of entities. + /// + /// The entity type. + /// The entity set which contains the returned entities. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsCollectionFromEntitySet( + EntitySetConfiguration entitySetConfiguration) where TElementEntityType : class + { + if (entitySetConfiguration == null) + { + throw Error.ArgumentNull("entitySetConfiguration"); + } + + Type clrCollectionType = typeof(IEnumerable); + NavigationSource = entitySetConfiguration.EntitySet; + IEdmTypeConfiguration elementType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TElementEntityType)); + ReturnType = new CollectionTypeConfiguration(elementType, clrCollectionType); + return this; + } + + /// + /// Established the return type of the Action. + /// Used when the return type is a single Primitive or ComplexType. + /// + public ActionConfiguration Returns(Type clrReturnType) + { + if (clrReturnType == null) + { + throw Error.ArgumentNull("clrReturnType"); + } + + IEdmTypeConfiguration configuration = ModelBuilder.GetTypeConfigurationOrNull(clrReturnType); + + if (configuration is EntityTypeConfiguration) + { + throw Error.InvalidOperation(SRResources.ReturnEntityWithoutEntitySet, configuration.FullName); + } + + ReturnsImplementation(clrReturnType); + return this; + } + + /// + /// Established the return type of the Action. + /// Used when the return type is a single Primitive or ComplexType. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration Returns() + { + Type returnType = typeof(TReturnType); + return this.Returns(returnType); + } + + /// + /// Establishes the return type of the Action + /// Used when the return type is a collection of either Primitive or ComplexTypes. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsCollection() + { + Type clrElementType = typeof(TReturnElementType); + IEdmTypeConfiguration edmElementType = ModelBuilder.GetTypeConfigurationOrNull(clrElementType); + + if (edmElementType is EntityTypeConfiguration) + { + throw Error.InvalidOperation(SRResources.ReturnEntityCollectionWithoutEntitySet, edmElementType.FullName); + } + + ReturnsCollectionImplementation(); + return this; + } + + /// + /// Specifies the bindingParameter name, type and whether it is alwaysBindable, use only if the Action "isBindable". + /// + public ActionConfiguration SetBindingParameter(string name, IEdmTypeConfiguration bindingParameterType) + { + SetBindingParameterImplementation(name, bindingParameterType); + return this; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the return EntityType instance + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsEntityViaEntitySetPath(string entitySetPath) where TEntityType : class + { + if (String.IsNullOrEmpty(entitySetPath)) + { + throw Error.ArgumentNull("entitySetPath"); + } + ReturnsEntityViaEntitySetPathImplementation(entitySetPath.Split('/')); + return this; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the return EntityType instance + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsEntityViaEntitySetPath(params string[] entitySetPath) where TEntityType : class + { + ReturnsEntityViaEntitySetPathImplementation(entitySetPath); + return this; + } + + /// + /// Sets the return type to a collection of EntityType instances. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the returned EntityType instances + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsCollectionViaEntitySetPath(string entitySetPath) where TElementEntityType : class + { + if (String.IsNullOrEmpty(entitySetPath)) + { + throw Error.ArgumentNull("entitySetPath"); + } + ReturnsCollectionViaEntitySetPathImplementation(entitySetPath.Split('/')); + return this; + } + + /// + /// Sets the return type to a collection of EntityType instances. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the returned EntityType instances + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ActionConfiguration ReturnsCollectionViaEntitySetPath(params string[] entitySetPath) where TElementEntityType : class + { + ReturnsCollectionViaEntitySetPathImplementation(entitySetPath); + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs new file mode 100644 index 0000000..7de1e0b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be placed on a navigation property to specify the applied + /// action whether delete should also remove the associated item on the other end of the association. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + public sealed class ActionOnDeleteAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The action applied on delete. + public ActionOnDeleteAttribute(EdmOnDeleteAction onDeleteAction) + { + OnDeleteAction = onDeleteAction; + } + + /// + /// Gets the action whether delete should also remove the associated item on the other end of the association. + /// + public EdmOnDeleteAction OnDeleteAction { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/AutoExpandAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/AutoExpandAttribute.cs new file mode 100644 index 0000000..dfa4159 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/AutoExpandAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be placed on a navigation property to specify it + /// is auto expanded, or placed on a class to specify all navigation properties are auto expanded. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] + public sealed class AutoExpandAttribute : Attribute + { + /// + /// Gets or sets whether the automatic expand will be disabled if there is a $select specify by client. + /// + public bool DisableWhenSelectPresent { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindableOperationFinder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindableOperationFinder.cs new file mode 100644 index 0000000..bc1bcab --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindableOperationFinder.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// This class builds a cache that allows for efficient look up of bindable operation by EntityType. + /// + internal class BindableOperationFinder + { + private Dictionary> _map = new Dictionary>(); + + private Dictionary> _collectionMap = new Dictionary>(); + + /// + /// Constructs a concurrent cache for looking up bindable operations for any EntityType in the provided model. + /// + public BindableOperationFinder(IEdmModel model) + { + var operationGroups = + from op in model.SchemaElements.OfType() + where op.IsBound && (op.Parameters.First().Type.TypeKind() == EdmTypeKind.Entity || op.Parameters.First().Type.TypeKind() == EdmTypeKind.Collection) + group op by op.Parameters.First().Type.Definition; + + foreach (var operationGroup in operationGroups) + { + var entityType = operationGroup.Key as IEdmEntityType; + if (entityType != null) + { + _map[entityType] = operationGroup.ToList(); + } + + var collectionType = operationGroup.Key as IEdmCollectionType; + if (collectionType != null) + { + var elementType = collectionType.ElementType.Definition as IEdmEntityType; + if (elementType != null) + { + // because collection type is temp instance. + List value; + if (_collectionMap.TryGetValue(elementType, out value)) + { + value.AddRange(operationGroup); + } + else + { + _collectionMap[elementType] = operationGroup.ToList(); + } + } + } + } + } + + /// + /// Finds operations that can be invoked on the given entity type. This would include all the operations that are bound + /// to the given type and its base types. + /// + /// The EDM entity type. + /// A collection of operations bound to the entity type. + public virtual IEnumerable FindOperations(IEdmEntityType entityType) + { + return GetTypeHierarchy(entityType).SelectMany(FindDeclaredOperations); + } + + /// + /// Finds operations that can be invoked on the feed. This would include all the operations that are bound to the given + /// type and its base types. + /// + /// The EDM entity type. + /// A collection of operations bound to the feed. + public virtual IEnumerable FindOperationsBoundToCollection(IEdmEntityType entityType) + { + return GetTypeHierarchy(entityType).SelectMany(FindDeclaredOperationsBoundToCollection); + } + + private static IEnumerable GetTypeHierarchy(IEdmEntityType entityType) + { + IEdmEntityType current = entityType; + while (current != null) + { + yield return current; + current = current.BaseEntityType(); + } + } + + private IEnumerable FindDeclaredOperations(IEdmEntityType entityType) + { + List results; + + if (_map.TryGetValue(entityType, out results)) + { + return results; + } + else + { + return Enumerable.Empty(); + } + } + + private IEnumerable FindDeclaredOperationsBoundToCollection(IEdmEntityType entityType) + { + List results; + + if (_collectionMap.TryGetValue(entityType, out results)) + { + return results; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingParameterConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingParameterConfiguration.cs new file mode 100644 index 0000000..e14b343 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingParameterConfiguration.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents a BindingParameter. + /// + /// Actions/Functions can have at most one BindingParameter. + /// This parameter has similar semantics to the 'this' keyword in C# extensions methods. + /// + /// For example given a url that identifies a Movie, if there is an action that has a bindingParameter that is a Movie, + /// you can bind the Action to the url. + /// + /// i.e. if ~/Movies(1) identifies a Movie, and there exists a Checkout action that has a Movie BindingParameter, + /// you can invoke that Action at this url ~/Movies(1)/Checkout + /// + /// The BindingParameter type must either be an EntityType or a Collection of EntityTypes. + /// + /// + public class BindingParameterConfiguration : ParameterConfiguration + { + /// + /// The default parameter name for an action's binding parameter. + /// + public const string DefaultBindingParameterName = "bindingParameter"; + + /// + /// Create a BindingParameterConfiguration + /// + /// The name of the Binding Parameter + /// The type of the Binding Parameter + public BindingParameterConfiguration(string name, IEdmTypeConfiguration parameterType) + : base(name, parameterType) + { + EdmTypeKind kind = parameterType.Kind; + if (kind == EdmTypeKind.Collection) + { + kind = (parameterType as CollectionTypeConfiguration).ElementType.Kind; + } + if (kind != EdmTypeKind.Entity) + { + throw Error.Argument("parameterType", SRResources.InvalidBindingParameterType, parameterType.FullName); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathConfigurationOfTStructuralType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathConfigurationOfTStructuralType.cs new file mode 100644 index 0000000..ebd9a9f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathConfigurationOfTStructuralType.cs @@ -0,0 +1,632 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents the configuration for the binding path that can be built using . + /// The structural type of the current binding path property. + /// + public class BindingPathConfiguration where TStructuralType : class + { + private readonly NavigationSourceConfiguration _navigationSource; + private readonly StructuralTypeConfiguration _structuralType; + private readonly ODataModelBuilder _modelBuilder; + private readonly IList _bindingPath; + + /// + /// Initializes a new instance of the class. + /// + /// The model builder. + /// The type configuration. + /// The navigation source configuration. + public BindingPathConfiguration(ODataModelBuilder modelBuilder, + StructuralTypeConfiguration structuralType, + NavigationSourceConfiguration navigationSource) + : this(modelBuilder, structuralType, navigationSource, new List()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The model builder. + /// The type configuration. + /// The navigation source configuration. + /// The binding path. + public BindingPathConfiguration(ODataModelBuilder modelBuilder, + StructuralTypeConfiguration structuralType, + NavigationSourceConfiguration navigationSource, + IList bindingPath) + { + if (modelBuilder == null) + { + throw Error.ArgumentNull("modelBuilder"); + } + + if (structuralType == null) + { + throw Error.ArgumentNull("structuralType"); + } + + if (navigationSource == null) + { + throw Error.ArgumentNull("navigationSource"); + } + + if (bindingPath == null) + { + throw Error.ArgumentNull("bindingPath"); + } + + _modelBuilder = modelBuilder; + _navigationSource = navigationSource; + _structuralType = structuralType; + _bindingPath = bindingPath; + } + + /// + /// Gets the list of binding path information. + /// + public IList Path + { + get { return _bindingPath; } + } + + /// + /// Gets the string of binding path information. like "A.B/C/D.E". + /// + public string BindingPath + { + get { return _bindingPath.ConvertBindingPath(); } + } + + /// + /// Configures an one-to-many path for this binding path. + /// + /// The target property type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasManyPath( + Expression>> pathExpression) + where TTargetType : class + { + return HasManyPath(pathExpression, contained: false); + } + + /// + /// Configures an one-to-many path for this binding path. + /// + /// The target property type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A flag representing the target property as containment. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasManyPath( + Expression>> pathExpression, + bool contained) + where TTargetType : class + { + if (pathExpression == null) + { + throw Error.ArgumentNull("pathExpression"); + } + + PropertyInfo pathProperty = PropertySelectorVisitor.GetSelectedProperty(pathExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(pathProperty); + + StructuralTypeConfiguration target; + if (contained) + { + target = _modelBuilder.EntityType(); + _structuralType.ContainsMany(pathExpression); // add a containment navigation property + } + else + { + target = _modelBuilder.ComplexType(); + _structuralType.CollectionProperty(pathExpression); // add a collection complex property + } + + return new BindingPathConfiguration(_modelBuilder, target, _navigationSource, bindingPath); + } + + /// + /// Configures an one-to-many path of the derived type for this binding path. + /// + /// The target property type. + /// The derived structural type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasManyPath( + Expression>> pathExpression) + where TTargetType : class + where TDerivedType : class, TStructuralType + { + return HasManyPath(pathExpression, contained: false); + } + + /// + /// Configures an one-to-many path of the derived type for this binding path. + /// + /// The target property type. + /// The derived structural type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A flag representing the target property as containment. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasManyPath( + Expression>> pathExpression, + bool contained) + where TTargetType : class + where TDerivedType : class, TStructuralType + { + if (pathExpression == null) + { + throw Error.ArgumentNull("pathExpression"); + } + + PropertyInfo pathProperty = PropertySelectorVisitor.GetSelectedProperty(pathExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(TypeHelper.AsMemberInfo(typeof(TDerivedType))); + bindingPath.Add(pathProperty); + + // make sure the derived type has the same type kind with the resource type. + StructuralTypeConfiguration derivedConfiguration; + if (_structuralType.Configuration.Kind == EdmTypeKind.Entity) + { + derivedConfiguration = _modelBuilder.EntityType().DerivesFrom(); + } + else + { + derivedConfiguration = _modelBuilder.ComplexType().DerivesFrom(); + } + + StructuralTypeConfiguration target; + if (contained) + { + target = _modelBuilder.EntityType(); + derivedConfiguration.ContainsMany(pathExpression); // add a containment navigation property + } + else + { + target = _modelBuilder.ComplexType(); + derivedConfiguration.CollectionProperty(pathExpression); // add a collection complex property + } + + return new BindingPathConfiguration(_modelBuilder, target, _navigationSource, bindingPath); + } + + /// + /// Configures an one-to-one path for this binding path. + /// + /// The target property type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasSinglePath( + Expression> pathExpression) + where TTargetType : class + { + return HasSinglePath(pathExpression, required: false, contained: false); + } + + /// + /// Configures an one-to-one path for this binding path. + /// + /// The target property type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A flag representing the target property required or optional. + /// A flag representing the target property as containment. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasSinglePath( + Expression> pathExpression, + bool required, + bool contained) + where TTargetType : class + { + if (pathExpression == null) + { + throw Error.ArgumentNull("pathExpression"); + } + + PropertyInfo pathProperty = PropertySelectorVisitor.GetSelectedProperty(pathExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(pathProperty); + + StructuralTypeConfiguration target; + if (contained) + { + target = _modelBuilder.EntityType(); + + if (required) + { + _structuralType.ContainsRequired(pathExpression); + } + else + { + _structuralType.ContainsOptional(pathExpression); + } + } + else + { + target = _modelBuilder.ComplexType(); + _structuralType.ComplexProperty(pathExpression).OptionalProperty = !required; + } + + return new BindingPathConfiguration(_modelBuilder, target, _navigationSource, bindingPath); + } + + /// + /// Configures a required one-to-one path of the derived type for this binding path. + /// + /// The target property type. + /// The derived structural type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasSinglePath( + Expression> pathExpression) + where TTargetType : class + where TDerivedType : class, TStructuralType + { + return HasSinglePath(pathExpression, required: false, contained: false); + } + + /// + /// Configures a required one-to-one path of the derived type for this binding path. + /// + /// The target property type. + /// The derived structural type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A flag representing the target property required or optional. + /// A flag representing the target property as containment. + /// A configuration object + /// that can be used to further configure the binding path or end the binding. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public BindingPathConfiguration HasSinglePath( + Expression> pathExpression, + bool required, + bool contained) + where TTargetType : class + where TDerivedType : class, TStructuralType + { + if (pathExpression == null) + { + throw Error.ArgumentNull("pathExpression"); + } + + PropertyInfo pathProperty = PropertySelectorVisitor.GetSelectedProperty(pathExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(TypeHelper.AsMemberInfo(typeof(TDerivedType))); + bindingPath.Add(pathProperty); + + // make sure the derived type has the same type kind with the resource type. + StructuralTypeConfiguration derivedConfiguration; + if (_structuralType.Configuration.Kind == EdmTypeKind.Entity) + { + derivedConfiguration = _modelBuilder.EntityType().DerivesFrom(); + } + else + { + derivedConfiguration = _modelBuilder.ComplexType().DerivesFrom(); + } + + StructuralTypeConfiguration target; + if (contained) + { + target = _modelBuilder.EntityType(); + + if (required) + { + derivedConfiguration.ContainsRequired(pathExpression); + } + else + { + derivedConfiguration.ContainsOptional(pathExpression); + } + } + else + { + target = _modelBuilder.ComplexType(); + derivedConfiguration.ComplexProperty(pathExpression).OptionalProperty = !required; + } + + return new BindingPathConfiguration(_modelBuilder, target, _navigationSource, bindingPath); + } + + /// + /// Configures an one-to-many path for this binding path and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target property type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object + /// that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasManyBinding( + Expression>> navigationExpression, + string targetEntitySet) + where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(targetEntitySet)) + { + throw Error.ArgumentNullOrEmpty("targetEntitySet"); + } + + NavigationPropertyConfiguration navigation = _structuralType.HasMany(navigationExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(navigation.PropertyInfo); + + NavigationSourceConfiguration entitySet = _modelBuilder.EntitySet(targetEntitySet).Configuration; + return this._navigationSource.AddBinding(navigation, entitySet, bindingPath); + } + + /// + /// Configures an one-to-many path of the derived type for this binding path and binds the corresponding + /// navigation property to the given entity set. + /// + /// The target property type. + /// The derived structural type. + /// A lambda expression representing the binding path property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object + /// that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasManyBinding( + Expression>> navigationExpression, + string targetEntitySet) + where TTargetType : class + where TDerivedType : class, TStructuralType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(targetEntitySet)) + { + throw Error.ArgumentNullOrEmpty("targetEntitySet"); + } + + StructuralTypeConfiguration derivedConfiguration; + if (this._structuralType.Configuration.Kind == EdmTypeKind.Entity) + { + derivedConfiguration = _modelBuilder.EntityType().DerivesFrom(); + } + else + { + derivedConfiguration = _modelBuilder.ComplexType().DerivesFrom(); + } + + NavigationPropertyConfiguration navigation = derivedConfiguration.HasMany(navigationExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(TypeHelper.AsMemberInfo(typeof(TDerivedType))); + bindingPath.Add(navigation.PropertyInfo); + + NavigationSourceConfiguration entitySet = _modelBuilder.EntitySet(targetEntitySet).Configuration; + + return _navigationSource.AddBinding(navigation, entitySet, bindingPath); + } + + /// + /// Configures a required one-to-one path for this binding path and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object + /// that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasRequiredBinding( + Expression> navigationExpression, + string targetEntitySet) + where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(targetEntitySet)) + { + throw Error.ArgumentNullOrEmpty("targetEntitySet"); + } + + NavigationPropertyConfiguration navigation = this._structuralType.HasRequired(navigationExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(navigation.PropertyInfo); + + NavigationSourceConfiguration entitySet = _modelBuilder.EntitySet(targetEntitySet).Configuration; + return this._navigationSource.AddBinding(navigation, entitySet, bindingPath); + } + + /// + /// Configures a required one-to-one path of the derived type for this binding path and binds the corresponding + /// navigation property to the given entity set. + /// + /// The target navigation source type. + /// The derived structural type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object + /// that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasRequiredBinding( + Expression> navigationExpression, + string targetEntitySet) + where TTargetType : class + where TDerivedType : class, TStructuralType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(targetEntitySet)) + { + throw Error.ArgumentNullOrEmpty("targetEntitySet"); + } + + StructuralTypeConfiguration derivedConfiguration; + if (this._structuralType.Configuration.Kind == EdmTypeKind.Entity) + { + derivedConfiguration = _modelBuilder.EntityType().DerivesFrom(); + } + else + { + derivedConfiguration = _modelBuilder.ComplexType().DerivesFrom(); + } + + NavigationPropertyConfiguration navigation = derivedConfiguration.HasRequired(navigationExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(TypeHelper.AsMemberInfo(typeof(TDerivedType))); + bindingPath.Add(navigation.PropertyInfo); + + NavigationSourceConfiguration entitySet = _modelBuilder.EntitySet(targetEntitySet).Configuration; + return this._navigationSource.AddBinding(navigation, entitySet, bindingPath); + } + + /// + /// Configures an optional one-to-one path for this binding path and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object + /// that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasOptionalBinding( + Expression> navigationExpression, + string targetEntitySet) + where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(targetEntitySet)) + { + throw Error.ArgumentNullOrEmpty("targetEntitySet"); + } + + NavigationPropertyConfiguration navigation = this._structuralType.HasOptional(navigationExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(navigation.PropertyInfo); + + NavigationSourceConfiguration entitySet = _modelBuilder.EntitySet(targetEntitySet).Configuration; + return this._navigationSource.AddBinding(navigation, entitySet, bindingPath); + } + + /// + /// Configures an one-to-one path of the derived type for this binding path and binds the corresponding + /// navigation property to the given entity set. + /// + /// The target navigation source type. + /// The derived structural type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object + /// that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasOptionalBinding( + Expression> navigationExpression, + string targetEntitySet) + where TTargetType : class + where TDerivedType : class, TStructuralType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(targetEntitySet)) + { + throw Error.ArgumentNullOrEmpty("targetEntitySet"); + } + + StructuralTypeConfiguration derivedConfiguration; + if (this._structuralType.Configuration.Kind == EdmTypeKind.Entity) + { + derivedConfiguration = _modelBuilder.EntityType().DerivesFrom(); + } + else + { + derivedConfiguration = _modelBuilder.ComplexType().DerivesFrom(); + } + + NavigationPropertyConfiguration navigation = derivedConfiguration.HasOptional(navigationExpression); + + IList bindingPath = new List(_bindingPath); + bindingPath.Add(TypeHelper.AsMemberInfo(typeof(TDerivedType))); + bindingPath.Add(navigation.PropertyInfo); + + NavigationSourceConfiguration entitySet = _modelBuilder.EntitySet(targetEntitySet).Configuration; + return this._navigationSource.AddBinding(navigation, entitySet, bindingPath); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathHelper.cs new file mode 100644 index 0000000..3fe7eb6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/BindingPathHelper.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + internal static class BindingPathHelper + { + /// + /// Converts the binding path list to string. like "A.B/C/D.E". + /// + /// The binding path list. + /// The binding path string. like "A.B/C/D.E". + public static string ConvertBindingPath(this IEnumerable bindingPath) + { + if (bindingPath == null) + { + throw Error.ArgumentNull("bindingPath"); + } + + return String.Join("/", bindingPath.Select(e => TypeHelper.GetQualifiedName(e))); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesNavigationType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesNavigationType.cs new file mode 100644 index 0000000..7aa60ad --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesNavigationType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Enumerates the navigation type can apply on navigation restrictions. + /// + internal enum CapabilitiesNavigationType + { + /// + /// Navigation properties can be recursively navigated. + /// + Recursive, + + /// + /// Navigation properties can be navigated to a single level. + /// + Single, + + /// + /// Navigation properties are not navigable. + /// + None + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyConstants.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyConstants.cs new file mode 100644 index 0000000..0a4f19a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyConstants.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Constant values for Capabilities Vocabulary + /// + internal static class CapabilitiesVocabularyConstants + { + /// Org.OData.Capabilities.V1.CountRestrictions + public const string CountRestrictions = "Org.OData.Capabilities.V1.CountRestrictions"; + + /// Property Countable of Org.OData.Capabilities.V1.CountRestrictions + public const string CountRestrictionsCountable = "Countable"; + + /// Property NonCountableProperties of Org.OData.Capabilities.V1.CountRestrictions + public const string CountRestrictionsNonCountableProperties = "NonCountableProperties"; + + /// Property NonCountableNavigationProperties of Org.OData.Capabilities.V1.CountRestrictions + public const string CountRestrictionsNonCountableNavigationProperties = "NonCountableNavigationProperties"; + + /// Org.OData.Capabilities.V1.NavigationRestrictions + public const string NavigationRestrictions = "Org.OData.Capabilities.V1.NavigationRestrictions"; + + /// Property Navigability of Org.OData.Capabilities.V1.NavigationRestrictions + public const string NavigationRestrictionsNavigability = "Navigability"; + + /// Property RestrictedProperties of Org.OData.Capabilities.V1.NavigationRestrictions + public const string NavigationRestrictionsRestrictedProperties = "RestrictedProperties"; + + /// Property NavigationProperty of Org.OData.Capabilities.V1.NavigationPropertyRestriction + public const string NavigationPropertyRestrictionNavigationProperty = "NavigationProperty"; + + /// Org.OData.Capabilities.V1.NavigationType + public const string NavigationType = "Org.OData.Capabilities.V1.NavigationType"; + + /// Org.OData.Capabilities.V1.FilterRestrictions + public const string FilterRestrictions = "Org.OData.Capabilities.V1.FilterRestrictions"; + + /// Property Filterable of Org.OData.Capabilities.V1.FilterRestrictions + public const string FilterRestrictionsFilterable = "Filterable"; + + /// Property RequiresFilter of Org.OData.Capabilities.V1.FilterRestrictions + public const string FilterRestrictionsRequiresFilter = "RequiresFilter"; + + /// Property RequiredProperties of Org.OData.Capabilities.V1.FilterRestrictions + public const string FilterRestrictionsRequiredProperties = "RequiredProperties"; + + /// Property NonFilterableProperties of Org.OData.Capabilities.V1.FilterRestrictions + public const string FilterRestrictionsNonFilterableProperties = "NonFilterableProperties"; + + /// Org.OData.Capabilities.V1.SortRestrictions + public const string SortRestrictions = "Org.OData.Capabilities.V1.SortRestrictions"; + + /// Property Sortable of Org.OData.Capabilities.V1.FilterRestrictions + public const string SortRestrictionsSortable = "Sortable"; + + /// Property AscendingOnlyProperties of Org.OData.Capabilities.V1.FilterRestrictions + public const string SortRestrictionsAscendingOnlyProperties = "AscendingOnlyProperties"; + + /// Property DescendingOnlyProperties of Org.OData.Capabilities.V1.FilterRestrictions + public const string SortRestrictionsDescendingOnlyProperties = "DescendingOnlyProperties"; + + /// Property NonSortableProperties of Org.OData.Capabilities.V1.FilterRestrictions + public const string SortRestrictionsNonSortableProperties = "NonSortableProperties"; + + /// Org.OData.Capabilities.V1.ExpandRestrictions + public const string ExpandRestrictions = "Org.OData.Capabilities.V1.ExpandRestrictions"; + + /// Property Expandable of Org.OData.Capabilities.V1.ExpandRestrictions + public const string ExpandRestrictionsExpandable = "Expandable"; + + /// Property NonExpandableProperties of Org.OData.Capabilities.V1.ExpandRestrictions + public const string ExpandRestrictionsNonExpandableProperties = "NonExpandableProperties"; + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyExtensionMethods.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyExtensionMethods.cs new file mode 100644 index 0000000..df75bb3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CapabilitiesVocabularyExtensionMethods.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Contains extension methods for to set the query capabilities vocabulary. + /// + internal static class CapabilitiesVocabularyExtensionMethods + { + private static readonly IEnumerable EmptyStructuralProperties = Enumerable.Empty(); + private static readonly IEnumerable EmptyNavigationProperties = Enumerable.Empty(); + + private static IEdmEnumType _navigationType; + + /// + /// Set Org.OData.Capabilities.V1.CountRestrictions to target. + /// + /// The model referenced to. + /// The target entity set to set the inline annotation. + /// This entity set can be counted. + /// These collection properties do not allow /$count segments. + /// These navigation properties do not allow /$count segments. + public static void SetCountRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, bool isCountable, + IEnumerable nonCountableProperties, + IEnumerable nonCountableNavigationProperties) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (target == null) + { + throw Error.ArgumentNull("target"); + } + + nonCountableProperties = nonCountableProperties ?? EmptyStructuralProperties; + nonCountableNavigationProperties = nonCountableNavigationProperties ?? EmptyNavigationProperties; + + IList properties = new List + { + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.CountRestrictionsCountable, + new EdmBooleanConstant(isCountable)), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.CountRestrictionsNonCountableProperties, + new EdmCollectionExpression( + nonCountableProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray())), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.CountRestrictionsNonCountableNavigationProperties, + new EdmCollectionExpression( + nonCountableNavigationProperties.Select(p => new EdmNavigationPropertyPathExpression(p.Name)).ToArray())) + }; + + model.SetVocabularyAnnotation(target, properties, CapabilitiesVocabularyConstants.CountRestrictions); + } + + /// + /// Set Org.OData.Capabilities.V1.NavigationRestrictions to target. + /// + /// The model referenced to. + /// The target entity set to set the inline annotation. + /// This entity set supports navigability. + /// These properties have navigation restrictions on. + public static void SetNavigationRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, + CapabilitiesNavigationType navigability, + IEnumerable> restrictedProperties) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (target == null) + { + throw Error.ArgumentNull("target"); + } + + IEdmEnumType navigationType = model.GetCapabilitiesNavigationType(); + if (navigationType == null) + { + return; + } + + restrictedProperties = restrictedProperties ?? new Tuple[0]; + + string type = new EdmEnumTypeReference(navigationType, false).ToStringLiteral((long)navigability); + + IEnumerable propertiesExpression = restrictedProperties.Select(p => + { + var name = new EdmEnumTypeReference(navigationType, false).ToStringLiteral((long)p.Item2); + return new EdmRecordExpression(new IEdmPropertyConstructor[] + { + new EdmPropertyConstructor( + CapabilitiesVocabularyConstants.NavigationPropertyRestrictionNavigationProperty, + new EdmNavigationPropertyPathExpression(p.Item1.Name)), + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.NavigationRestrictionsNavigability, + new EdmEnumMemberExpression(navigationType.Members.Single(m => m.Name == name))) + }); + }); + + IList properties = new List + { + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.NavigationRestrictionsNavigability, + new EdmEnumMemberExpression(navigationType.Members.Single(m => m.Name == type))), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.NavigationRestrictionsRestrictedProperties, + new EdmCollectionExpression(propertiesExpression)) + }; + + model.SetVocabularyAnnotation(target, properties, CapabilitiesVocabularyConstants.NavigationRestrictions); + } + + /// + /// Set Org.OData.Capabilities.V1.FilterRestrictions to target. + /// + /// The model referenced to. + /// The target entity set to set the inline annotation. + /// This entity set supports the $filter expressions. + /// This entity set requires $filter expressions. + /// These properties must be specified in the $filter clause. + /// These properties cannot be used in $filter expressions. + public static void SetFilterRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, bool isFilterable, + bool isRequiresFilter, IEnumerable requiredProperties, + IEnumerable nonFilterableProperties) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (target == null) + { + throw Error.ArgumentNull("target"); + } + + requiredProperties = requiredProperties ?? EmptyStructuralProperties; + nonFilterableProperties = nonFilterableProperties ?? EmptyStructuralProperties; + + IList properties = new List + { + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.FilterRestrictionsFilterable, + new EdmBooleanConstant(isFilterable)), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.FilterRestrictionsRequiresFilter, + new EdmBooleanConstant(isRequiresFilter)), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.FilterRestrictionsRequiredProperties, + new EdmCollectionExpression( + requiredProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray())), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.FilterRestrictionsNonFilterableProperties, + new EdmCollectionExpression( + nonFilterableProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray())) + }; + + model.SetVocabularyAnnotation(target, properties, CapabilitiesVocabularyConstants.FilterRestrictions); + } + + /// + /// Set Org.OData.Capabilities.V1.SortRestrictions to target. + /// + /// The model referenced to. + /// The target entity set to set the inline annotation. + /// This entity set supports the $orderby expressions. + /// These properties can only be used for sorting in ascending order. + /// These properties can only be used for sorting in descending order. + /// These properties cannot be used in $orderby expressions. + public static void SetSortRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, bool isSortable, + IEnumerable ascendingOnlyProperties, IEnumerable descendingOnlyProperties, + IEnumerable nonSortableProperties) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (target == null) + { + throw Error.ArgumentNull("target"); + } + + ascendingOnlyProperties = ascendingOnlyProperties ?? EmptyStructuralProperties; + descendingOnlyProperties = descendingOnlyProperties ?? EmptyStructuralProperties; + nonSortableProperties = nonSortableProperties ?? EmptyStructuralProperties; + + IList properties = new List + { + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.SortRestrictionsSortable, + new EdmBooleanConstant(isSortable)), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.SortRestrictionsAscendingOnlyProperties, + new EdmCollectionExpression( + ascendingOnlyProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray())), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.SortRestrictionsDescendingOnlyProperties, + new EdmCollectionExpression( + descendingOnlyProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray())), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.SortRestrictionsNonSortableProperties, + new EdmCollectionExpression( + nonSortableProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray())) + }; + + model.SetVocabularyAnnotation(target, properties, CapabilitiesVocabularyConstants.SortRestrictions); + } + + /// + /// Set Org.OData.Capabilities.V1.ExpandRestrictions to target. + /// + /// The model referenced to. + /// The target entity set to set the inline annotation. + /// This entity set supports the expand expressions. + /// These properties cannot be used in $expand expressions. + public static void SetExpandRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, bool isExpandable, + IEnumerable nonExpandableProperties) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (target == null) + { + throw Error.ArgumentNull("target"); + } + + nonExpandableProperties = nonExpandableProperties ?? EmptyNavigationProperties; + + IList properties = new List + { + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.ExpandRestrictionsExpandable, + new EdmBooleanConstant(isExpandable)), + + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.ExpandRestrictionsNonExpandableProperties, + new EdmCollectionExpression( + nonExpandableProperties.Select(p => new EdmNavigationPropertyPathExpression(p.Name)).ToArray())) + }; + + model.SetVocabularyAnnotation(target, properties, CapabilitiesVocabularyConstants.ExpandRestrictions); + } + + private static void SetVocabularyAnnotation(this EdmModel model, IEdmVocabularyAnnotatable target, + IList properties, string qualifiedName) + { + Contract.Assert(model != null); + Contract.Assert(target != null); + + IEdmTerm term = model.FindTerm(qualifiedName); + if (term != null) + { + IEdmRecordExpression record = new EdmRecordExpression(properties); + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(target, term, record); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + } + + private static IEdmEnumType GetCapabilitiesNavigationType(this EdmModel model) + { + return _navigationType ?? + (_navigationType = model.FindType(CapabilitiesVocabularyConstants.NavigationType) as IEdmEnumType); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionPropertyConfiguration.cs new file mode 100644 index 0000000..95868c8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionPropertyConfiguration.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// CollectionPropertyConfiguration represents a CollectionProperty on either an EntityType or ComplexType. + /// + public class CollectionPropertyConfiguration : StructuralPropertyConfiguration + { + private Type _elementType; + + /// + /// Constructs a CollectionPropertyConfiguration using the property provided. + /// + public CollectionPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + if (!TypeHelper.IsCollection(property.PropertyType, out _elementType)) + { + throw Error.Argument("property", SRResources.CollectionPropertiesMustReturnIEnumerable, property.Name, property.DeclaringType.FullName); + } + } + + /// + public override PropertyKind Kind + { + get { return PropertyKind.Collection; } + } + + /// + public override Type RelatedClrType + { + get { return ElementType; } + } + + /// + /// Returns the type of Elements in the Collection + /// + public Type ElementType + { + get { return _elementType; } + } + + /// + /// Sets the CollectionProperty to optional (i.e. nullable). + /// + public CollectionPropertyConfiguration IsOptional() + { + OptionalProperty = true; + return this; + } + + /// + /// Sets the CollectionProperty to required (i.e. non-nullable). + /// + public CollectionPropertyConfiguration IsRequired() + { + OptionalProperty = false; + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionTypeConfiguration.cs new file mode 100644 index 0000000..8c86672 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/CollectionTypeConfiguration.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents a Collection of some named type. + /// + /// Collection(Namespace.Customer) or Collection(Namespace.Address). + /// + /// + public class CollectionTypeConfiguration : IEdmTypeConfiguration + { + private IEdmTypeConfiguration _elementType; + private Type _clrType; + + /// + /// Constructs a collection that contains elements of the specified ElementType + /// and that is represented in CLR using the specified clrType. + /// + /// The EdmTypeConfiguration of the elements in the collection + /// The type of this collection when manifested in CLR. + public CollectionTypeConfiguration(IEdmTypeConfiguration elementType, Type clrType) + { + if (elementType == null) + { + throw Error.ArgumentNull("elementType"); + } + if (clrType == null) + { + throw Error.ArgumentNull("clrType"); + } + _elementType = elementType; + _clrType = clrType; + } + + /// + /// Gets the of elements in this collection. + /// + public IEdmTypeConfiguration ElementType + { + get { return _elementType; } + } + + /// + /// Gets the CLR type associated with this collection type. + /// + public Type ClrType + { + get { return _clrType; } + } + + /// + /// Gets the fullname (including namespace) of this collection type. + /// + public string FullName + { + get + { + // There is no need to include the Namespace when it comes from the Edm Namespace. + return Name; + } + } + + /// + /// Gets the namespace of this collection type. + /// + public string Namespace + { + get { return "Edm"; } + } + + /// + /// Gets the name of this collection type. + /// + public string Name + { + get { return String.Format(CultureInfo.InvariantCulture, "Collection({0})", ElementType.FullName); } + } + + /// + /// Gets the kind of the . In this case, it is . + /// + public EdmTypeKind Kind + { + get { return EdmTypeKind.Collection; } + } + + /// + /// Gets the used to create this configuration. + /// + public ODataModelBuilder ModelBuilder + { + get { return _elementType.ModelBuilder; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexPropertyConfiguration.cs new file mode 100644 index 0000000..5c6d4d8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexPropertyConfiguration.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents the configuration for a complex property of a structural type (an entity type or a complex type). + /// + public class ComplexPropertyConfiguration : StructuralPropertyConfiguration + { + /// + /// Instantiates a new instance of the class. + /// + /// The property of the configuration. + /// The declaring type of the property. + public ComplexPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + } + + /// + public override PropertyKind Kind + { + get { return PropertyKind.Complex; } + } + + /// + public override Type RelatedClrType + { + get { return PropertyInfo.PropertyType; } + } + + /// + /// Marks the current complex property as optional. + /// + /// Returns itself so that multiple calls can be chained. + public ComplexPropertyConfiguration IsOptional() + { + OptionalProperty = true; + return this; + } + + /// + /// Marks the current complex property as required. + /// + /// Returns itself so that multiple calls can be chained. + public ComplexPropertyConfiguration IsRequired() + { + OptionalProperty = false; + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfiguration.cs new file mode 100644 index 0000000..ead59e4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfiguration.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Allows configuration to be performed for a complex type in a model. A + /// can be obtained by using the method . + /// + public class ComplexTypeConfiguration : StructuralTypeConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The default constructor is intended for use by unit testing only. + public ComplexTypeConfiguration() + { + } + + /// + /// Initializes a new instance of the class. + /// The being used. + /// The backing CLR type for this entity type. + /// + public ComplexTypeConfiguration(ODataModelBuilder modelBuilder, Type clrType) + : base(modelBuilder, clrType) + { + } + + /// + public override EdmTypeKind Kind + { + get { return EdmTypeKind.Complex; } + } + + /// + /// Gets or sets the base type of this complex type. + /// + public virtual ComplexTypeConfiguration BaseType + { + get + { + return BaseTypeInternal as ComplexTypeConfiguration; + } + set + { + DerivesFrom(value); + } + } + + /// + /// Marks this complex type as abstract. + /// + /// Returns itself so that multiple calls can be chained. + public virtual ComplexTypeConfiguration Abstract() + { + AbstractImpl(); + return this; + } + + /// + /// Sets the base type of this complex type to null meaning that this complex type + /// does not derive from anything. + /// + /// Returns itself so that multiple calls can be chained. + public virtual ComplexTypeConfiguration DerivesFromNothing() + { + DerivesFromNothingImpl(); + return this; + } + + /// + /// Sets the base type of this complex type. + /// + /// The base complex type. + /// Returns itself so that multiple calls can be chained. + public virtual ComplexTypeConfiguration DerivesFrom(ComplexTypeConfiguration baseType) + { + DerivesFromImpl(baseType); + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfigurationOfTComplexType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfigurationOfTComplexType.cs new file mode 100644 index 0000000..88e3d39 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ComplexTypeConfigurationOfTComplexType.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// + public class ComplexTypeConfiguration : StructuralTypeConfiguration where TComplexType : class + { + private ComplexTypeConfiguration _configuration; + private ODataModelBuilder _modelBuilder; + + internal ComplexTypeConfiguration(ComplexTypeConfiguration configuration) + : base(configuration) + { + } + + internal ComplexTypeConfiguration(ODataModelBuilder modelBuilder) + : this(modelBuilder, new ComplexTypeConfiguration(modelBuilder, typeof(TComplexType))) + { + } + + internal ComplexTypeConfiguration(ODataModelBuilder modelBuilder, ComplexTypeConfiguration configuration) + : base(configuration) + { + Contract.Assert(modelBuilder != null); + Contract.Assert(configuration != null); + + _modelBuilder = modelBuilder; + _configuration = configuration; + } + + /// + /// Marks this complex type as abstract. + /// + /// Returns itself so that multiple calls can be chained. + public ComplexTypeConfiguration Abstract() + { + _configuration.IsAbstract = true; + return this; + } + + /// + /// Gets the base type of this complex type. + /// + public ComplexTypeConfiguration BaseType + { + get + { + return _configuration.BaseType; + } + } + + /// + /// Sets the base type of this complex type to null meaning that this complex type + /// does not derive from anything. + /// + /// Returns itself so that multiple calls can be chained. + public ComplexTypeConfiguration DerivesFromNothing() + { + _configuration.DerivesFromNothing(); + return this; + } + + /// + /// Sets the base type of this complex type. + /// + /// The base complex type. + /// Returns itself so that multiple calls can be chained. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", + Justification = "typeof(TBaseType) is used and getting it as a generic argument is cleaner")] + public ComplexTypeConfiguration DerivesFrom() where TBaseType : class + { + ComplexTypeConfiguration baseEntityType = _modelBuilder.ComplexType(); + _configuration.DerivesFrom(baseEntityType._configuration); + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainedAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainedAttribute.cs new file mode 100644 index 0000000..1a12515 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainedAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Mark a navigation property as containment. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class ContainedAttribute : Attribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainmentPathBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainmentPathBuilder.cs new file mode 100644 index 0000000..0d76f38 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ContainmentPathBuilder.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Builder +{ + internal class ContainmentPathBuilder + { + private List _segments; + + public ODataPath TryComputeCanonicalContainingPath(ODataPath path) + { + Contract.Assert(path != null); + Contract.Assert(path.Segments.Count >= 2); + + _segments = path.Segments.ToList(); + + RemoveAllTypeCasts(); + + // New ODataPath will be extended later to include any final required key or cast. + RemovePathSegmentsAfterTheLastNavigationProperty(); + + RemoveRedundantContainingPathSegments(); + + AddTypeCastsIfNecessary(); + + // Also remove the last navigation property segment, since it is not part of the containing path segments. + if (_segments.Count > 0) + { + _segments.RemoveAt(_segments.Count - 1); + } + + return new ODataPath(_segments); + } + + private void RemovePathSegmentsAfterTheLastNavigationProperty() + { + // Find the last navigation property segment. + ODataPathSegment lastNavigationProperty = _segments.OfType().LastOrDefault(); + List newSegments = new List(); + foreach (ODataPathSegment segment in _segments) + { + newSegments.Add(segment); + if (segment == lastNavigationProperty) + { + break; + } + } + + _segments = newSegments; + } + + private void RemoveRedundantContainingPathSegments() + { + // Find the last non-contained navigation property segment: + // Collection valued: entity set + // -or- + // Single valued: singleton + // Copy over other path segments such as: not a navigation path segment, contained navigation property, + // single valued navigation property with navigation source targetting an entity set (we won't have key + // information for that navigation property.) + _segments.Reverse(); + NavigationPropertySegment navigationPropertySegment = null; + List newSegments = new List(); + foreach (ODataPathSegment segment in _segments) + { + navigationPropertySegment = segment as NavigationPropertySegment; + if (navigationPropertySegment != null) + { + EdmNavigationSourceKind navigationSourceKind = + navigationPropertySegment.NavigationSource.NavigationSourceKind(); + if ((navigationPropertySegment.NavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many && + navigationSourceKind == EdmNavigationSourceKind.EntitySet) || + (navigationSourceKind == EdmNavigationSourceKind.Singleton)) + { + break; + } + } + + newSegments.Insert(0, segment); + } + + // Start the path with the navigation source of the navigation property found above. + if (navigationPropertySegment != null) + { + IEdmNavigationSource navigationSource = navigationPropertySegment.NavigationSource; + Contract.Assert(navigationSource != null); + if (navigationSource.NavigationSourceKind() == EdmNavigationSourceKind.Singleton) + { + SingletonSegment singletonSegment = new SingletonSegment((IEdmSingleton)navigationSource); + newSegments.Insert(0, singletonSegment); + } + else + { + Contract.Assert(navigationSource.NavigationSourceKind() == EdmNavigationSourceKind.EntitySet); + EntitySetSegment entitySetSegment = new EntitySetSegment((IEdmEntitySet)navigationSource); + newSegments.Insert(0, entitySetSegment); + } + } + + _segments = newSegments; + } + + private void RemoveAllTypeCasts() + { + List newSegments = new List(); + foreach (ODataPathSegment segment in _segments) + { + if (!(segment is TypeSegment)) + { + newSegments.Add(segment); + } + } + + _segments = newSegments; + } + + private void AddTypeCastsIfNecessary() + { + IEdmEntityType owningType = null; + List newSegments = new List(); + foreach (ODataPathSegment segment in _segments) + { + NavigationPropertySegment navProp = segment as NavigationPropertySegment; + if (navProp != null && owningType != null && + owningType.FindProperty(navProp.NavigationProperty.Name) == null) + { + // need a type cast + TypeSegment typeCast = new TypeSegment( + navProp.NavigationProperty.DeclaringType, + navigationSource: null); + newSegments.Add(typeCast); + } + + newSegments.Add(segment); + IEdmEntityType targetEntityType = GetTargetEntityType(segment); + if (targetEntityType != null) + { + owningType = targetEntityType; + } + } + + _segments = newSegments; + } + + private static IEdmEntityType GetTargetEntityType(ODataPathSegment segment) + { + Contract.Assert(segment != null); + + EntitySetSegment entitySetSegment = segment as EntitySetSegment; + if (entitySetSegment != null) + { + return entitySetSegment.EntitySet.EntityType(); + } + + SingletonSegment singletonSegment = segment as SingletonSegment; + if (singletonSegment != null) + { + return singletonSegment.Singleton.EntityType(); + } + + NavigationPropertySegment navigationPropertySegment = segment as NavigationPropertySegment; + if (navigationPropertySegment != null) + { + return navigationPropertySegment.NavigationSource.EntityType(); + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractEntityTypeDiscoveryConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractEntityTypeDiscoveryConvention.cs new file mode 100644 index 0000000..809520d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractEntityTypeDiscoveryConvention.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// to figure out if an entity is abstract or not. + /// This convention configures all entity types backed by an abstract CLR type as abstract entities. + /// + internal class AbstractEntityTypeDiscoveryConvention : EntityTypeConvention + { + public override void Apply(EntityTypeConfiguration entity, ODataConventionModelBuilder model) + { + if (entity.IsAbstract == null) + { + entity.IsAbstract = entity.ClrType.IsAbstract; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractTypeDiscoveryConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractTypeDiscoveryConvention.cs new file mode 100644 index 0000000..40c984a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AbstractTypeDiscoveryConvention.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// to figure out if a structural type is abstract or not. + /// This convention configures all structural types backed by an abstract CLR type as abstract. + /// + internal class AbstractTypeDiscoveryConvention : IEdmTypeConvention + { + public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model) + { + StructuralTypeConfiguration structuralType = edmTypeConfiguration as StructuralTypeConfiguration; + if (structuralType != null && structuralType.IsAbstract == null) + { + structuralType.IsAbstract = TypeHelper.IsAbstract(structuralType.ClrType); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs new file mode 100644 index 0000000..554b183 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// The ActionLinkGenerationConvention calls action.HasActionLink(..) if the action binds to a single entity and has not previously been configured. + /// + internal class ActionLinkGenerationConvention : IOperationConvention + { + public void Apply(OperationConfiguration configuration, ODataModelBuilder model) + { + ActionConfiguration action = configuration as ActionConfiguration; + + if (action == null || !action.IsBindable) + { + return; + } + + // You only need to create links for bindable actions that bind to a single entity. + if (action.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Entity && action.GetActionLink() == null) + { + if (action.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Entity && + action.GetActionLink() == null) + { + string bindingParameterType = action.BindingParameter.TypeConfiguration.FullName; + action.HasActionLink( + entityContext => + entityContext.GenerateActionLink(bindingParameterType, action.FullyQualifiedName), + followsConventions: true); + } + } + else if (action.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Collection && + action.GetFeedActionLink() == null) + { + if (((CollectionTypeConfiguration)action.BindingParameter.TypeConfiguration).ElementType.Kind == + EdmTypeKind.Entity) + { + string bindingParameterType = action.BindingParameter.TypeConfiguration.FullName; + action.HasFeedActionLink( + feedContext => + feedContext.GenerateActionLink(bindingParameterType, action.FullyQualifiedName), + followsConventions: true); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AssociationSetDiscoveryConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AssociationSetDiscoveryConvention.cs new file mode 100644 index 0000000..f3732a2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/AssociationSetDiscoveryConvention.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// to configure the EDM association sets for the given entity set. + /// This convention adds an association set for each EDM navigation property defined in this type, its base types and all its derived types. + /// The target navigation source chosen is the default navigation source for the navigation property's target entity type. + /// The default navigation source for an entity type is the navigation source that contains entity of that entity type. + /// If more than one navigation source match, the default navigation source is none. + /// If no navigation sources match the default navigation source is the default navigation source of the base type. + /// + internal class AssociationSetDiscoveryConvention : INavigationSourceConvention + { + public void Apply(NavigationSourceConfiguration configuration, ODataModelBuilder model) + { + IList, NavigationPropertyConfiguration>> navigations = + new List, NavigationPropertyConfiguration>>(); + Stack path = new Stack(); + model.FindAllNavigationProperties(configuration.EntityType, navigations, path); + foreach (var navigation in navigations) + { + NavigationSourceConfiguration targetNavigationSource = GetTargetNavigationSource( + navigation.Item3, model); + if (targetNavigationSource != null) + { + configuration.AddBinding(navigation.Item3, targetNavigationSource, navigation.Item2); + } + } + } + + // Get the default target navigation source for this navigation property. + internal static NavigationSourceConfiguration GetTargetNavigationSource(NavigationPropertyConfiguration navigationProperty, + ODataModelBuilder model) + { + EntityTypeConfiguration targetEntityType = + model + .StructuralTypes + .OfType().SingleOrDefault(e => e.ClrType == navigationProperty.RelatedClrType); + + if (targetEntityType == null) + { + throw Error.InvalidOperation(SRResources.TargetEntityTypeMissing, navigationProperty.Name, + TypeHelper.GetReflectedType(navigationProperty.PropertyInfo).FullName); + } + + bool hasSingletonAttribute = navigationProperty.PropertyInfo.GetCustomAttributes().Any(); + + return GetDefaultNavigationSource(targetEntityType, model, hasSingletonAttribute); + } + + private static NavigationSourceConfiguration GetDefaultNavigationSource( + EntityTypeConfiguration targetEntityType, + ODataModelBuilder model, bool isSingleton) + { + if (targetEntityType == null) + { + return null; + } + + NavigationSourceConfiguration[] matchingNavigationSources = null; + if (isSingleton) + { + matchingNavigationSources = model.Singletons.Where(e => e.EntityType == targetEntityType).ToArray(); + } + else + { + matchingNavigationSources = model.EntitySets.Where(e => e.EntityType == targetEntityType).ToArray(); + } + + if (matchingNavigationSources.Length > 1) + { + if (model.BindingOptions == NavigationPropertyBindingOption.Auto) + { + return matchingNavigationSources[0]; + } + + return null; + } + else if (matchingNavigationSources.Length == 1) + { + return matchingNavigationSources[0]; + } + else + { + // default navigation source is the same as the default navigation source for the base type. + return GetDefaultNavigationSource(targetEntityType.BaseType as EntityTypeConfiguration, + model, isSingleton); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ActionOnDeleteAttributeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ActionOnDeleteAttributeConvention.cs new file mode 100644 index 0000000..3d556fc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ActionOnDeleteAttributeConvention.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class ActionOnDeleteAttributeConvention : AttributeEdmPropertyConvention + { + public ActionOnDeleteAttributeConvention() + : base(attribute => attribute.GetType() == typeof(ActionOnDeleteAttribute), allowMultiple: false) + { + } + + public override void Apply(NavigationPropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute, ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + ActionOnDeleteAttribute actionOnDelete = attribute as ActionOnDeleteAttribute; + if (actionOnDelete != null && !edmProperty.AddedExplicitly && edmProperty.DependentProperties.Any()) + { + edmProperty.OnDeleteAction = actionOnDelete.OnDeleteAction; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeConvention.cs new file mode 100644 index 0000000..fba65b6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeConvention.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Base class for all attribute based conventions. + /// + internal abstract class AttributeConvention : IConvention + { + /// + /// Initializes a new instance of the class. + /// + /// A function to test whether this convention applies to an attribute or not. + /// true if the convention allows multiple attribues; otherwise, false. + protected AttributeConvention(Func attributeFilter, bool allowMultiple) + { + if (attributeFilter == null) + { + throw Error.ArgumentNull("attributeFilter"); + } + + AllowMultiple = allowMultiple; + AttributeFilter = attributeFilter; + } + + /// + /// Gets the filter that finds the attributes that this convention applies to. + /// + public Func AttributeFilter { get; private set; } + + /// + /// Gets whether this convention allows multiple instances of the attribute. + /// + public bool AllowMultiple { get; private set; } + + /// + /// Returns the attributes on that this convention applies to. + /// + /// + /// + public Attribute[] GetAttributes(MemberInfo member) + { + if (member == null) + { + throw Error.ArgumentNull("member"); + } + + Attribute[] attributes = + member + .GetCustomAttributes(inherit: true) + .OfType() + .Where(AttributeFilter) + .ToArray(); + + if (!AllowMultiple && attributes.Length > 1) + { + throw Error.Argument( + "member", + SRResources.MultipleAttributesFound, + member.Name, + TypeHelper.GetReflectedType(member).Name, + attributes.First().GetType().Name); + } + + return attributes; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..ef3348c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmPropertyConvention.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Base class for all attribute based 's. + /// + /// The type of the property this configuration applies to. + internal abstract class AttributeEdmPropertyConvention : AttributeConvention, IEdmPropertyConvention + where TPropertyConfiguration : PropertyConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// A function to test whether this convention applies to an attribute or not. + /// true if the convention allows multiple attributes; otherwise, false. + protected AttributeEdmPropertyConvention(Func attributeFilter, bool allowMultiple) + : base(attributeFilter, allowMultiple) + { + } + + /// + /// Applies the convention. + /// + /// The property being configured. + /// The type being configured. + /// The that contains the type this property is being applied to. + public void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (structuralTypeConfiguration == null) + { + throw Error.ArgumentNull("structuralTypeConfiguration"); + } + + TPropertyConfiguration property = edmProperty as TPropertyConfiguration; + if (property != null) + { + Apply(property, structuralTypeConfiguration, model); + } + } + + /// + /// Applies the convention. + /// + /// The property being configured. + /// The type being configured. + /// The that contains the type this property is being applied to. + public void Apply(TPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (structuralTypeConfiguration == null) + { + throw Error.ArgumentNull("structuralTypeConfiguration"); + } + + foreach (Attribute attribute in GetAttributes(edmProperty.PropertyInfo)) + { + Apply(edmProperty, structuralTypeConfiguration, attribute, model); + } + } + + /// + /// Applies the convention. + /// + /// The property being configured. + /// The type being configured. + /// The attribute to be used during configuration. + /// The ODataConventionModelBuilder used to build the model. + public abstract void Apply(TPropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmTypeConvention.cs new file mode 100644 index 0000000..f4ac7ac --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AttributeEdmTypeConvention.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Base class for all 's based on a attribute on the type. + /// + /// The kind of Edm type that this convention must be applied to. + internal abstract class AttributeEdmTypeConvention : AttributeConvention, IEdmTypeConvention + where TEdmTypeConfiguration : class, IEdmTypeConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// A function to test whether this convention applies to an attribute or not. + /// true if the convention allows multiple attributes; otherwise, false. + protected AttributeEdmTypeConvention(Func attributeFilter, bool allowMultiple) + : base(attributeFilter, allowMultiple) + { + } + + /// + /// Applies the convention. + /// + /// The edm type to apply the convention to. + /// The model that this edm type belongs to. + public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model) + { + TEdmTypeConfiguration type = edmTypeConfiguration as TEdmTypeConfiguration; + if (type != null) + { + Apply(type, model); + } + } + + /// + /// Applies the convention. + /// + /// The edm type to apply the convention to. + /// The model that this edm type belongs to. + public void Apply(TEdmTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + foreach (Attribute attribute in GetAttributes(TypeHelper.AsMemberInfo(edmTypeConfiguration.ClrType))) + { + Apply(edmTypeConfiguration, model, attribute); + } + } + + /// + /// Applies the convention. + /// + /// The edm type to apply the convention to. + /// The model that this edm type belongs to. + /// The attribute found on this edm type. + public abstract void Apply(TEdmTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..a5ef2e7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class AutoExpandAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public AutoExpandAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(AutoExpandAttribute), allowMultiple: false) + { + } + + public override void Apply(NavigationPropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + AutoExpandAttribute autoExpandAttribute = attribute as AutoExpandAttribute; + edmProperty.AutomaticallyExpand(autoExpandAttribute.DisableWhenSelectPresent); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..ab4ec21 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/AutoExpandAttributeEdmTypeConvention.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Configures classes that have the to specify all navigation properties are auto expanded. + /// + internal class AutoExpandAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public AutoExpandAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(AutoExpandAttribute), allowMultiple: false) + { + } + + /// + /// Set all navigation properties auto expand. + /// + /// The edm type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + EntityTypeConfiguration entityTypeConfiguration = edmTypeConfiguration as EntityTypeConfiguration; + AutoExpandAttribute autoExpandAttribute = attribute as AutoExpandAttribute; + foreach (var property in entityTypeConfiguration.NavigationProperties) + { + if (!property.AddedExplicitly) + { + property.AutomaticallyExpand(autoExpandAttribute.DisableWhenSelectPresent); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ColumnAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ColumnAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..f5048b6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ColumnAttributeEdmPropertyConvention.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Marks properties that have as the target EDM type. + /// + internal class ColumnAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public ColumnAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(ColumnAttribute), allowMultiple: false) + { + } + + /// + /// Marks the property with the target EDM type. + /// + /// The EDM property. + /// The EDM type being configured. + /// The found. + /// The ODataConventionModelBuilder used to build the model. + public override void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (edmProperty.AddedExplicitly) + { + return; + } + + var primitiveProperty = edmProperty as PrimitivePropertyConfiguration; + if (primitiveProperty == null) + { + return; // ignore non-primitive property + } + + var columnAttribute = attribute as ColumnAttribute; + if (columnAttribute != null && columnAttribute.Order > 0) + { + primitiveProperty.Order = columnAttribute.Order; + } + + if (columnAttribute == null || columnAttribute.TypeName == null) + { + return; // ignore the column type + } + + string typeName = columnAttribute.TypeName; + if (String.Compare(typeName, "date", StringComparison.OrdinalIgnoreCase) == 0) + { + primitiveProperty.AsDate(); + } + else if (String.Compare(typeName, "time", StringComparison.OrdinalIgnoreCase) == 0) + { + primitiveProperty.AsTimeOfDay(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs new file mode 100644 index 0000000..7ec7f93 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ComplexTypeAttributeConvention.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class ComplexTypeAttributeConvention : AttributeEdmTypeConvention + { + public ComplexTypeAttributeConvention() + : base(attr => attr.GetType() == typeof(ComplexTypeAttribute), false) + { + } + + public override void Apply(EntityTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (!edmTypeConfiguration.AddedExplicitly) + { + PrimitivePropertyConfiguration[] keys = edmTypeConfiguration.Keys.ToArray(); + foreach (PrimitivePropertyConfiguration key in keys) + { + edmTypeConfiguration.RemoveKey(key); + } + + EnumPropertyConfiguration[] enumKeys = edmTypeConfiguration.EnumKeys.ToArray(); + foreach (EnumPropertyConfiguration key in enumKeys) + { + edmTypeConfiguration.RemoveKey(key); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ConcurrencyCheckAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ConcurrencyCheckAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..cbea8b6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ConcurrencyCheckAttributeEdmPropertyConvention.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Marks properties that have as non-optional on their EDM type. + /// + internal class ConcurrencyCheckAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public ConcurrencyCheckAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(ConcurrencyCheckAttribute), allowMultiple: false) + { + } + + /// + /// Marks the property with concurrency token on the EDM type. + /// + /// The EDM property. + /// The EDM type being configured. + /// The found. + /// The ODataConventionModelBuilder used to build the model. + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + EntityTypeConfiguration entityType = structuralTypeConfiguration as EntityTypeConfiguration; + PrimitivePropertyConfiguration primitiveProperty = edmProperty as PrimitivePropertyConfiguration; + if (entityType != null && primitiveProperty != null) + { + primitiveProperty.ConcurrencyToken = true; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..8828782 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmPropertyConvention.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class CountAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public CountAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(CountAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + CountAttribute countAttribute = attribute as CountAttribute; + if (countAttribute.Disabled) + { + edmProperty.QueryConfiguration.GetModelBoundQuerySettingsOrDefault().Countable = false; + } + else + { + edmProperty.QueryConfiguration.GetModelBoundQuerySettingsOrDefault().Countable = true; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..27c4762 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/CountAttributeEdmTypeConvention.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class CountAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public CountAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(CountAttribute), allowMultiple: false) + { + } + + /// + /// Set whether the $count can be applied on the edm type. + /// + /// The edm type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!edmTypeConfiguration.AddedExplicitly) + { + CountAttribute countAttribute = attribute as CountAttribute; + if (countAttribute.Disabled) + { + edmTypeConfiguration.QueryConfiguration.GetModelBoundQuerySettingsOrDefault().Countable = false; + } + else + { + edmTypeConfiguration.QueryConfiguration.GetModelBoundQuerySettingsOrDefault().Countable = true; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..642581d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEdmTypeConvention.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Configures classes that have the to follow DataContract serialization/deserialization rules. + /// + internal class DataContractAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public DataContractAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(DataContractAttribute), allowMultiple: false) + { + } + + /// + /// Removes properties that do not have the attribute from the edm type. + /// + /// The edm type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!edmTypeConfiguration.AddedExplicitly && + model.ModelAliasingEnabled) + { + // set the name, and namespace, if not null + DataContractAttribute dataContractAttribute = attribute as DataContractAttribute; + if (dataContractAttribute != null) + { + if (dataContractAttribute.Name != null) + { + edmTypeConfiguration.Name = dataContractAttribute.Name; + } + + if (dataContractAttribute.Namespace != null) + { + edmTypeConfiguration.Namespace = dataContractAttribute.Namespace; + } + } + edmTypeConfiguration.AddedExplicitly = false; + } + + IEnumerable allProperties = edmTypeConfiguration.Properties.ToArray(); + foreach (PropertyConfiguration property in allProperties) + { + if (!property.PropertyInfo.GetCustomAttributes(typeof(DataMemberAttribute), inherit: true).Any()) + { + if (!property.AddedExplicitly) + { + edmTypeConfiguration.RemoveProperty(property.PropertyInfo); + } + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEnumTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEnumTypeConvention.cs new file mode 100644 index 0000000..1cc236c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataContractAttributeEnumTypeConvention.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Configures classes that have the to follow DataContract serialization/deserialization rules. + /// + internal class DataContractAttributeEnumTypeConvention : AttributeEdmTypeConvention + { + public DataContractAttributeEnumTypeConvention() + : base(attribute => attribute.GetType() == typeof(DataContractAttribute), allowMultiple: false) + { + } + + /// + /// Removes properties that do not have the attribute from the enum type. + /// + /// The enum type to configure. + /// The enum model that this type belongs to. + /// The found on this type. + public override void Apply(EnumTypeConfiguration enumTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (enumTypeConfiguration == null) + { + throw Error.ArgumentNull("enumTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!enumTypeConfiguration.AddedExplicitly && + model.ModelAliasingEnabled) + { + // set the name, and namespace, if not null + DataContractAttribute dataContractAttribute = attribute as DataContractAttribute; + if (dataContractAttribute != null) + { + if (dataContractAttribute.Name != null) + { + enumTypeConfiguration.Name = dataContractAttribute.Name; + } + + if (dataContractAttribute.Namespace != null) + { + enumTypeConfiguration.Namespace = dataContractAttribute.Namespace; + } + } + enumTypeConfiguration.AddedExplicitly = false; + } + + IEnumerable allMembers = enumTypeConfiguration.Members.ToArray(); + foreach (EnumMemberConfiguration member in allMembers) + { + EnumMemberAttribute enumMemberAttribute = + enumTypeConfiguration.ClrType.GetField(member.Name) + .GetCustomAttributes(typeof(EnumMemberAttribute), inherit: true) + .FirstOrDefault() as EnumMemberAttribute; + if (!member.AddedExplicitly) + { + if (model.ModelAliasingEnabled && enumMemberAttribute != null) + { + if (!String.IsNullOrWhiteSpace(enumMemberAttribute.Value)) + { + member.Name = enumMemberAttribute.Value; + } + } + else + { + enumTypeConfiguration.RemoveMember(member.MemberInfo); + } + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataMemberAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataMemberAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..2269869 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DataMemberAttributeEdmPropertyConvention.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Configures properties that have as optional or required on their edm type. + /// + internal class DataMemberAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public DataMemberAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(DataMemberAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (structuralTypeConfiguration == null) + { + throw Error.ArgumentNull("structuralTypeConfiguration"); + } + + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + bool isTypeDataContract = TypeHelper.AsMemberInfo(structuralTypeConfiguration.ClrType).GetCustomAttributes(typeof(DataContractAttribute), inherit: true).Any(); + DataMemberAttribute dataMember = attribute as DataMemberAttribute; + + if (isTypeDataContract && dataMember != null && !edmProperty.AddedExplicitly) + { + // set the name alias + if (model.ModelAliasingEnabled && + !String.IsNullOrWhiteSpace(dataMember.Name)) + { + edmProperty.Name = dataMember.Name; + } + + StructuralPropertyConfiguration structuralProperty = edmProperty as StructuralPropertyConfiguration; + if (structuralProperty != null) + { + structuralProperty.OptionalProperty = !dataMember.IsRequired; + } + + NavigationPropertyConfiguration navigationProperty = edmProperty as NavigationPropertyConfiguration; + if (navigationProperty != null && navigationProperty.Multiplicity != EdmMultiplicity.Many) + { + if (dataMember.IsRequired) + { + navigationProperty.Required(); + } + else + { + navigationProperty.Optional(); + } + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..427fdb0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmPropertyConvention.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class ExpandAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public ExpandAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(ExpandAttribute), allowMultiple: true) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + ExpandAttribute expandAttribute = attribute as ExpandAttribute; + ModelBoundQuerySettings querySettings = edmProperty.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.ExpandConfigurations.Count == 0) + { + querySettings.CopyExpandConfigurations(expandAttribute.ExpandConfigurations); + } + else + { + foreach (var property in expandAttribute.ExpandConfigurations.Keys) + { + querySettings.ExpandConfigurations[property] = + expandAttribute.ExpandConfigurations[property]; + } + } + + if (expandAttribute.ExpandConfigurations.Count == 0) + { + querySettings.DefaultExpandType = expandAttribute.DefaultExpandType; + querySettings.DefaultMaxDepth = expandAttribute.DefaultMaxDepth ?? ODataValidationSettings.DefaultMaxExpansionDepth; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..29d4816 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ExpandAttributeEdmTypeConvention.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class ExpandAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public ExpandAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(ExpandAttribute), allowMultiple: true) + { + } + + /// + /// Set the s of navigation properties of this structural type. + /// + /// The entity type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!edmTypeConfiguration.AddedExplicitly) + { + ExpandAttribute expandAttribute = attribute as ExpandAttribute; + ModelBoundQuerySettings querySettings = + edmTypeConfiguration.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.ExpandConfigurations.Count == 0) + { + querySettings.CopyExpandConfigurations( + expandAttribute.ExpandConfigurations); + } + else + { + foreach (var property in expandAttribute.ExpandConfigurations.Keys) + { + querySettings.ExpandConfigurations[property] = + expandAttribute.ExpandConfigurations[property]; + } + } + + if (expandAttribute.ExpandConfigurations.Count == 0) + { + querySettings.DefaultExpandType = expandAttribute.DefaultExpandType; + querySettings.DefaultMaxDepth = expandAttribute.DefaultMaxDepth ?? ODataValidationSettings.DefaultMaxExpansionDepth; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..ad67cdc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmPropertyConvention.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class FilterAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public FilterAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(FilterAttribute), allowMultiple: true) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + FilterAttribute filterAttribute = attribute as FilterAttribute; + ModelBoundQuerySettings querySettings = edmProperty.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.FilterConfigurations.Count == 0) + { + querySettings.CopyFilterConfigurations(filterAttribute.FilterConfigurations); + } + else + { + foreach (var property in filterAttribute.FilterConfigurations.Keys) + { + querySettings.FilterConfigurations[property] = + filterAttribute.FilterConfigurations[property]; + } + } + + if (filterAttribute.FilterConfigurations.Count == 0) + { + querySettings.DefaultEnableFilter = filterAttribute.DefaultEnableFilter; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..54a28c2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/FilterAttributeEdmTypeConvention.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class FilterAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public FilterAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(FilterAttribute), allowMultiple: true) + { + } + + /// + /// Set whether the $filter can be applied on those properties of this structural type. + /// + /// The structural type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!edmTypeConfiguration.AddedExplicitly) + { + FilterAttribute filterAttribute = attribute as FilterAttribute; + ModelBoundQuerySettings querySettings = + edmTypeConfiguration.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.FilterConfigurations.Count == 0) + { + querySettings.CopyFilterConfigurations( + filterAttribute.FilterConfigurations); + } + else + { + foreach (var property in filterAttribute.FilterConfigurations.Keys) + { + querySettings.FilterConfigurations[property] = + filterAttribute.FilterConfigurations[property]; + } + } + + if (filterAttribute.FilterConfigurations.Count == 0) + { + querySettings.DefaultEnableFilter = filterAttribute.DefaultEnableFilter; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs new file mode 100644 index 0000000..7ded2e3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/ForeignKeyAttributeConvention.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + // Denotes a property used as a foreign key in a relationship. The annotation may be placed on: + // 1. the foreign key property and specify the associated navigation property name, or + // 2. a navigation property and specify the associated foreign key name. + internal class ForeignKeyAttributeConvention : AttributeEdmPropertyConvention + { + public ForeignKeyAttributeConvention() + : base(attribute => attribute.GetType() == typeof(ForeignKeyAttribute), allowMultiple: false) + { + } + + /// + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (structuralTypeConfiguration == null) + { + throw Error.ArgumentNull("structuralTypeConfiguration"); + } + + if (attribute == null) + { + throw Error.ArgumentNull("attribute"); + } + + EntityTypeConfiguration declaringEntityType = structuralTypeConfiguration as EntityTypeConfiguration; + if (declaringEntityType == null) + { + return; + } + + ForeignKeyAttribute foreignKeyAttribute = (ForeignKeyAttribute)attribute; + switch (edmProperty.Kind) + { + case PropertyKind.Navigation: + ApplyNavigation((NavigationPropertyConfiguration)edmProperty, declaringEntityType, + foreignKeyAttribute); + break; + case PropertyKind.Primitive: + ApplyPrimitive((PrimitivePropertyConfiguration)edmProperty, declaringEntityType, + foreignKeyAttribute); + break; + } + } + + private static void ApplyNavigation(NavigationPropertyConfiguration navProperty, EntityTypeConfiguration entityType, + ForeignKeyAttribute foreignKeyAttribute) + { + Contract.Assert(navProperty != null); + Contract.Assert(entityType != null); + Contract.Assert(foreignKeyAttribute != null); + + if (navProperty.AddedExplicitly || navProperty.Multiplicity == EdmMultiplicity.Many) + { + return; + } + + EntityTypeConfiguration principalEntity = entityType.ModelBuilder.StructuralTypes + .OfType().FirstOrDefault(e => e.ClrType == navProperty.RelatedClrType); + if (principalEntity == null) + { + return; + } + + // if a navigation property has multiple foreign keys, use comma to separate the list of foreign key names. + IEnumerable dependentPropertyNames = foreignKeyAttribute.Name.Split(',').Select(p => p.Trim()); + foreach (string dependentPropertyName in dependentPropertyNames) + { + if (String.IsNullOrWhiteSpace(dependentPropertyName)) + { + continue; + } + + PrimitivePropertyConfiguration dependent = + entityType.Properties.OfType() + .SingleOrDefault(p => p.Name.Equals(dependentPropertyName, StringComparison.Ordinal)); + + if (dependent != null) + { + Type dependentType = Nullable.GetUnderlyingType(dependent.PropertyInfo.PropertyType) ?? dependent.PropertyInfo.PropertyType; + PrimitivePropertyConfiguration principal = principalEntity.Keys.FirstOrDefault( + k => k.PropertyInfo.PropertyType == dependentType && navProperty.PrincipalProperties.All(p => p != k.PropertyInfo)); + + if (principal != null) + { + navProperty.HasConstraint(dependent.PropertyInfo, principal.PropertyInfo); + } + } + } + } + + private static void ApplyPrimitive(PrimitivePropertyConfiguration dependent, EntityTypeConfiguration entityType, + ForeignKeyAttribute foreignKeyAttribute) + { + Contract.Assert(dependent != null); + Contract.Assert(entityType != null); + Contract.Assert(foreignKeyAttribute != null); + + string navName = foreignKeyAttribute.Name.Trim(); + NavigationPropertyConfiguration navProperty = entityType.NavigationProperties + .FirstOrDefault(n => n.Name.Equals(navName, StringComparison.Ordinal)); + if (navProperty == null) + { + return; + } + + if (navProperty.Multiplicity == EdmMultiplicity.Many || navProperty.AddedExplicitly) + { + return; + } + + EntityTypeConfiguration principalEntity = entityType.ModelBuilder.StructuralTypes + .OfType().FirstOrDefault(e => e.ClrType == navProperty.RelatedClrType); + if (principalEntity == null) + { + return; + } + + Type dependentType = Nullable.GetUnderlyingType(dependent.PropertyInfo.PropertyType) ?? dependent.PropertyInfo.PropertyType; + PrimitivePropertyConfiguration principal = principalEntity.Keys.FirstOrDefault( + k => k.PropertyInfo.PropertyType == dependentType && navProperty.PrincipalProperties.All(p => p != k.PropertyInfo)); + if (principal != null) + { + navProperty.HasConstraint(dependent.PropertyInfo, principal.PropertyInfo); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..21e9a6d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/IgnoreDataMemberAttributeEdmPropertyConvention.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Removes properties that have from their edm type. + /// + internal class IgnoreDataMemberAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public IgnoreDataMemberAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(IgnoreDataMemberAttribute), allowMultiple: false) + { + } + + /// + /// Removes the property from the edm type. + /// + /// The property being removed. + /// The edm type from which the property is being removed. + /// The found on this type. + /// The ODataConventionModelBuilder used to build the model. + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (structuralTypeConfiguration == null) + { + throw Error.ArgumentNull("structuralTypeConfiguration"); + } + + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + bool isTypeDataContract = TypeHelper.AsMemberInfo(structuralTypeConfiguration.ClrType).GetCustomAttributes(typeof(DataContractAttribute), inherit: true).Any(); + bool isPropertyDataMember = edmProperty.PropertyInfo.GetCustomAttributes(typeof(DataMemberAttribute), inherit: true).Any(); + + if (isTypeDataContract && isPropertyDataMember) + { + // both Datamember and IgnoreDataMember. DataMember wins as this a DataContract + return; + } + else + { + structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..745c1e6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/KeyAttributeEdmPropertyConvention.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Configures properties that have the as keys in the . + /// + internal class KeyAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public KeyAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(KeyAttribute), allowMultiple: false) + { + } + + /// + /// Configures the property as a key on the edm type. + /// + /// The key property. + /// The edm type being configured. + /// The found on the property. + /// The ODataConventionModelBuilder used to build the model. + public override void Apply(StructuralPropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (edmProperty.Kind == PropertyKind.Primitive || edmProperty.Kind == PropertyKind.Enum) + { + EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration; + if (entity != null) + { + entity.HasKey(edmProperty.PropertyInfo); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MaxLengthAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MaxLengthAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..d897a16 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MaxLengthAttributeEdmPropertyConvention.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Configures string or binary properties that have the . + /// + internal class MaxLengthAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public MaxLengthAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(MaxLengthAttribute), allowMultiple: false) + { + } + + /// + /// Configures string or binary propertie's maxLength. + /// + /// The key property. + /// The edm type being configured. + /// The found on the property. + /// The ODataConventionModelBuilder used to build the model. + public override void Apply(StructuralPropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + MaxLengthAttribute maxLengthAttribute = attribute as MaxLengthAttribute; + LengthPropertyConfiguration lengthProperty = edmProperty as LengthPropertyConfiguration; + if (lengthProperty != null && maxLengthAttribute != null) + { + lengthProperty.MaxLength = maxLengthAttribute.Length; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MediaTypeAttributeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MediaTypeAttributeConvention.cs new file mode 100644 index 0000000..3102769 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/MediaTypeAttributeConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class MediaTypeAttributeConvention : AttributeEdmTypeConvention + { + public MediaTypeAttributeConvention() + : base(attr => attr.GetType() == typeof(MediaTypeAttribute), false) + { + } + + public override void Apply(EntityTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + edmTypeConfiguration.MediaType(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NonFilterableAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NonFilterableAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..5a891bc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NonFilterableAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class NonFilterableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public NonFilterableAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(NonFilterableAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + edmProperty.IsNotFilterable(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotCountableAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotCountableAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..5c6d681 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotCountableAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class NotCountableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public NotCountableAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(NotCountableAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + edmProperty.IsNotCountable(); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotExpandableAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotExpandableAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..920574c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotExpandableAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class NotExpandableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public NotExpandableAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(NotExpandableAttribute), allowMultiple: false) + { + } + + public override void Apply(NavigationPropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + edmProperty.IsNotExpandable(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotFilterableAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotFilterableAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..cdf5d32 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotFilterableAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class NotFilterableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public NotFilterableAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(NotFilterableAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + edmProperty.IsNotFilterable(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..2b2b189 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotMappedAttributeEdmPropertyConvention.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Ignores properties with the NotMappedAttribute from . + /// + internal class NotMappedAttributeConvention : AttributeEdmPropertyConvention + { + // .net 4.5 NotMappedAttribute has the same name. + private const string EntityFrameworkNotMappedAttributeTypeName = "System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute"; + + private static Func _filter = attribute => + { + return attribute.GetType().FullName.Equals(EntityFrameworkNotMappedAttributeTypeName, StringComparison.Ordinal); + }; + + public NotMappedAttributeConvention() + : base(_filter, allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (structuralTypeConfiguration == null) + { + throw Error.ArgumentNull("structuralTypeConfiguration"); + } + + if (!edmProperty.AddedExplicitly) + { + structuralTypeConfiguration.RemoveProperty(edmProperty.PropertyInfo); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotNavigableAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotNavigableAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..e972a1d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotNavigableAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class NotNavigableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public NotNavigableAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(NotNavigableAttribute), allowMultiple: false) + { + } + + public override void Apply(NavigationPropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + edmProperty.IsNotNavigable(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotSortableAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotSortableAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..e4c0323 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/NotSortableAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class NotSortableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public NotSortableAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(NotSortableAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + edmProperty.IsNotSortable(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..a50e02d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmPropertyConvention.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class OrderByAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public OrderByAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(OrderByAttribute), allowMultiple: true) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + OrderByAttribute orderByAttribute = attribute as OrderByAttribute; + ModelBoundQuerySettings querySettings = + edmProperty.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.OrderByConfigurations.Count == 0) + { + querySettings.CopyOrderByConfigurations(orderByAttribute.OrderByConfigurations); + } + else + { + foreach (var property in orderByAttribute.OrderByConfigurations.Keys) + { + querySettings.OrderByConfigurations[property] = + orderByAttribute.OrderByConfigurations[property]; + } + } + + if (orderByAttribute.OrderByConfigurations.Count == 0) + { + querySettings.DefaultEnableOrderBy = orderByAttribute.DefaultEnableOrderBy; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..f508456 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/OrderByAttributeEdmTypeConvention.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class OrderByAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public OrderByAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(OrderByAttribute), allowMultiple: true) + { + } + + /// + /// Set whether the $orderby can be applied on those properties of this structural type. + /// + /// The structural type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!edmTypeConfiguration.AddedExplicitly) + { + OrderByAttribute orderByAttribute = attribute as OrderByAttribute; + ModelBoundQuerySettings querySettings = + edmTypeConfiguration.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.OrderByConfigurations.Count == 0) + { + querySettings.CopyOrderByConfigurations( + orderByAttribute.OrderByConfigurations); + } + else + { + foreach (var property in orderByAttribute.OrderByConfigurations.Keys) + { + querySettings.OrderByConfigurations[property] = + orderByAttribute.OrderByConfigurations[property]; + } + } + + if (orderByAttribute.OrderByConfigurations.Count == 0) + { + querySettings.DefaultEnableOrderBy = + orderByAttribute.DefaultEnableOrderBy; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..6952ee8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmPropertyConvention.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class PageAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public PageAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(PageAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + PageAttribute pageAttribute = attribute as PageAttribute; + ModelBoundQuerySettings querySettings = edmProperty.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (pageAttribute.MaxTop < 0) + { + querySettings.MaxTop = null; + } + else + { + querySettings.MaxTop = pageAttribute.MaxTop; + } + + if (pageAttribute.PageSize > 0) + { + querySettings.PageSize = pageAttribute.PageSize; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..d6e7523 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/PageAttributeEdmTypeConvention.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class PageAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public PageAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(PageAttribute), allowMultiple: false) + { + } + + /// + /// Set page size of the entity type. + /// + /// The structural type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!edmTypeConfiguration.AddedExplicitly) + { + PageAttribute pageAttribute = attribute as PageAttribute; + ModelBoundQuerySettings querySettings = + edmTypeConfiguration.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + + if (pageAttribute.MaxTop < 0) + { + querySettings.MaxTop = null; + } + else + { + querySettings.MaxTop = pageAttribute.MaxTop; + } + + if (pageAttribute.PageSize > 0) + { + querySettings.PageSize = pageAttribute.PageSize; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/RequiredAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/RequiredAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..dfc86cc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/RequiredAttributeEdmPropertyConvention.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + /// + /// Marks properties that have as non-optional on their edm type. + /// + internal class RequiredAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public RequiredAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(RequiredAttribute), allowMultiple: false) + { + } + + /// + /// Marks the property non-optional on the edm type. + /// + /// The edm property. + /// The edm type being configured. + /// The found. + /// The ODataConventionModelBuilder used to build the model. + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + StructuralPropertyConfiguration structuralProperty = edmProperty as StructuralPropertyConfiguration; + if (structuralProperty != null) + { + structuralProperty.OptionalProperty = false; + } + + NavigationPropertyConfiguration navigationProperty = edmProperty as NavigationPropertyConfiguration; + if (navigationProperty != null && navigationProperty.Multiplicity != EdmMultiplicity.Many) + { + navigationProperty.Required(); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..6115b08 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmPropertyConvention.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class SelectAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public SelectAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(SelectAttribute), allowMultiple: true) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + SelectAttribute selectAttribute = attribute as SelectAttribute; + ModelBoundQuerySettings querySettings = + edmProperty.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.SelectConfigurations.Count == 0) + { + querySettings.CopySelectConfigurations(selectAttribute.SelectConfigurations); + } + else + { + foreach (var property in selectAttribute.SelectConfigurations.Keys) + { + querySettings.SelectConfigurations[property] = + selectAttribute.SelectConfigurations[property]; + } + } + + if (selectAttribute.SelectConfigurations.Count == 0) + { + querySettings.DefaultSelectType = selectAttribute.DefaultSelectType; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmTypeConvention.cs new file mode 100644 index 0000000..7a4a742 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/SelectAttributeEdmTypeConvention.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class SelectAttributeEdmTypeConvention : AttributeEdmTypeConvention + { + public SelectAttributeEdmTypeConvention() + : base(attribute => attribute.GetType() == typeof(SelectAttribute), allowMultiple: true) + { + } + + /// + /// Set whether the $select can be applied on those properties of this structural type. + /// + /// The structural type to configure. + /// The edm model that this type belongs to. + /// The found on this type. + public override void Apply(StructuralTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model, + Attribute attribute) + { + if (edmTypeConfiguration == null) + { + throw Error.ArgumentNull("edmTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (!edmTypeConfiguration.AddedExplicitly) + { + SelectAttribute selectAttribute = attribute as SelectAttribute; + ModelBoundQuerySettings querySettings = + edmTypeConfiguration.QueryConfiguration.GetModelBoundQuerySettingsOrDefault(); + if (querySettings.SelectConfigurations.Count == 0) + { + querySettings.CopySelectConfigurations( + selectAttribute.SelectConfigurations); + } + else + { + foreach (var property in selectAttribute.SelectConfigurations.Keys) + { + querySettings.SelectConfigurations[property] = + selectAttribute.SelectConfigurations[property]; + } + } + + if (selectAttribute.SelectConfigurations.Count == 0) + { + querySettings.DefaultSelectType = + selectAttribute.DefaultSelectType; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..044e64c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/TimestampAttributeEdmPropertyConvention.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class TimestampAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public TimestampAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(TimestampAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration; + if (entity != null) + { + PrimitivePropertyConfiguration[] timestampProperties = GetPropertiesWithTimestamp(entity); + + // We only support one Timestamp column per type, as a SQL table (the underlying concept this attribute + // is bounded to) only supports one row version column per table. + if (timestampProperties.Length == 1) + { + timestampProperties[0].IsConcurrencyToken(); + } + } + } + + private static PrimitivePropertyConfiguration[] GetPropertiesWithTimestamp(EntityTypeConfiguration config) + { + IEnumerable properties = config.ThisAndBaseTypes().SelectMany(p => p.Properties); + return properties.OfType() + .Where(pc => pc.PropertyInfo.GetCustomAttributes(typeof(TimestampAttribute), inherit: true).Any()) + .ToArray(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/UnsortableAttributeEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/UnsortableAttributeEdmPropertyConvention.cs new file mode 100644 index 0000000..e077686 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/UnsortableAttributeEdmPropertyConvention.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes +{ + internal class UnsortableAttributeEdmPropertyConvention : AttributeEdmPropertyConvention + { + public UnsortableAttributeEdmPropertyConvention() + : base(attribute => attribute.GetType() == typeof(UnsortableAttribute), allowMultiple: false) + { + } + + public override void Apply(PropertyConfiguration edmProperty, + StructuralTypeConfiguration structuralTypeConfiguration, + Attribute attribute, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (!edmProperty.AddedExplicitly) + { + edmProperty.IsNotSortable(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ConventionsHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ConventionsHelpers.cs new file mode 100644 index 0000000..21a4194 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ConventionsHelpers.cs @@ -0,0 +1,237 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + internal static class ConventionsHelpers + { + public static IEnumerable> GetEntityKey(ResourceContext resourceContext) + { + Contract.Assert(resourceContext != null); + Contract.Assert(resourceContext.StructuredType != null); + Contract.Assert(resourceContext.EdmObject != null); + + IEdmEntityType entityType = resourceContext.StructuredType as IEdmEntityType; + if (entityType == null) + { + return Enumerable.Empty>(); + } + + IEnumerable keys = entityType.Key(); + return keys.Select(k => new KeyValuePair(k.Name, GetKeyValue(k, resourceContext))); + } + + private static object GetKeyValue(IEdmProperty key, ResourceContext resourceContext) + { + Contract.Assert(key != null); + Contract.Assert(resourceContext != null); + + object value = resourceContext.GetPropertyValue(key.Name); + if (value == null) + { + IEdmTypeReference edmType = resourceContext.EdmObject.GetEdmType(); + throw Error.InvalidOperation(SRResources.KeyValueCannotBeNull, key.Name, edmType.Definition); + } + + return ConvertValue(value); + } + + public static object ConvertValue(object value) + { + Contract.Assert(value != null); + + Type type = value.GetType(); + if (TypeHelper.IsEnum(type)) + { + value = new ODataEnumValue(value.ToString(), type.EdmFullName()); + } + else + { + Contract.Assert(EdmLibHelpers.GetEdmPrimitiveTypeOrNull(type) != null); + value = ODataPrimitiveSerializer.ConvertUnsupportedPrimitives(value); + } + + return value; + } + + public static string GetEntityKeyValue(ResourceContext resourceContext) + { + Contract.Assert(resourceContext != null); + Contract.Assert(resourceContext.StructuredType != null); + Contract.Assert(resourceContext.EdmObject != null); + + IEdmEntityType entityType = resourceContext.StructuredType as IEdmEntityType; + if (entityType == null) + { + return String.Empty; + } + + IEnumerable keys = entityType.Key(); + if (keys.Count() == 1) + { + return GetUriRepresentationForKeyValue(keys.First(), resourceContext); + } + else + { + IEnumerable keyValues = + keys.Select(key => String.Format( + CultureInfo.InvariantCulture, "{0}={1}", key.Name, GetUriRepresentationForKeyValue(key, resourceContext))); + return String.Join(",", keyValues); + } + } + + // Get properties of this structural type that are not already declared in the base structural type and are not already ignored. + public static IEnumerable GetProperties(StructuralTypeConfiguration structural, bool includeReadOnly) + { + IEnumerable allProperties = GetAllProperties(structural, includeReadOnly); + if (structural.BaseTypeInternal != null) + { + IEnumerable baseTypeProperties = GetAllProperties(structural.BaseTypeInternal, includeReadOnly); + return allProperties.Except(baseTypeProperties, PropertyEqualityComparer.Instance); + } + else + { + return allProperties; + } + } + + // Get all properties of this type (that are not already ignored). + public static IEnumerable GetAllProperties(StructuralTypeConfiguration type, bool includeReadOnly) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + return type + .ClrType + .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(p => p.IsValidStructuralProperty() && !type.IgnoredProperties().Any(p1 => p1.Name == p.Name) + && (includeReadOnly || p.GetSetMethod() != null || TypeHelper.IsCollection(p.PropertyType))); + } + + public static bool IsValidStructuralProperty(this PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + // ignore any indexer properties. + if (propertyInfo.GetIndexParameters().Any()) + { + return false; + } + + if (propertyInfo.CanRead) + { + // non-public getters are not valid properties + MethodInfo publicGetter = propertyInfo.GetGetMethod(); + if (publicGetter != null && propertyInfo.PropertyType.IsValidStructuralPropertyType()) + { + return true; + } + } + return false; + } + + // Gets the ignored properties from this type and the base types. + public static IEnumerable IgnoredProperties(this StructuralTypeConfiguration structuralType) + { + if (structuralType == null) + { + return Enumerable.Empty(); + } + + EntityTypeConfiguration entityType = structuralType as EntityTypeConfiguration; + if (entityType != null) + { + return entityType.IgnoredProperties.Concat(entityType.BaseType.IgnoredProperties()); + } + else + { + return structuralType.IgnoredProperties; + } + } + + public static bool IsValidStructuralPropertyType(this Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + Type elementType; + + return !(TypeHelper.IsGenericTypeDefinition(type) + || type.IsPointer + || type == typeof(object) + || (TypeHelper.IsCollection(type, out elementType) && elementType == typeof(object))); + } + + // gets the primitive odata uri representation. + public static string GetUriRepresentationForValue(object value) + { + Contract.Assert(value != null); + + Type type = value.GetType(); + if (TypeHelper.IsEnum(type)) + { + value = new ODataEnumValue(value.ToString(), type.EdmFullName()); + } + else + { + Contract.Assert(EdmLibHelpers.GetEdmPrimitiveTypeOrNull(type) != null); + value = ODataPrimitiveSerializer.ConvertUnsupportedPrimitives(value); + } + + return ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V4); + } + + private static string GetUriRepresentationForKeyValue(IEdmProperty key, ResourceContext resourceContext) + { + Contract.Assert(key != null); + Contract.Assert(resourceContext != null); + + object value = resourceContext.GetPropertyValue(key.Name); + if (value == null) + { + IEdmTypeReference edmType = resourceContext.EdmObject.GetEdmType(); + throw Error.InvalidOperation(SRResources.KeyValueCannotBeNull, key.Name, edmType.Definition); + } + + return GetUriRepresentationForValue(value); + } + + private class PropertyEqualityComparer : IEqualityComparer + { + public static PropertyEqualityComparer Instance = new PropertyEqualityComparer(); + + public bool Equals(PropertyInfo x, PropertyInfo y) + { + Contract.Assert(x != null); + Contract.Assert(y != null); + + return x.Name == y.Name; + } + + public int GetHashCode(PropertyInfo obj) + { + Contract.Assert(obj != null); + return obj.Name.GetHashCode(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs new file mode 100644 index 0000000..14703f3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityKeyConvention.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// for figuring out the entity keys. + /// This convention configures properties that are named 'ID' (case-insensitive) or {EntityName}+ID (case-insensitive) as the key. + /// + internal class EntityKeyConvention : EntityTypeConvention + { + /// + /// Figures out the key properties and marks them as Keys in the EDM model. + /// + /// The entity type being configured. + /// The . + public override void Apply(EntityTypeConfiguration entity, ODataConventionModelBuilder model) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + // Suppress the EntityKeyConvention if there is any key in EntityTypeConfiguration. + if (entity.Keys.Any() || entity.EnumKeys.Any()) + { + return; + } + + // Suppress the EntityKeyConvention if base type has any key. + if (entity.BaseType != null && entity.BaseType.Keys().Any()) + { + return; + } + + PropertyConfiguration key = GetKeyProperty(entity); + if (key != null) + { + entity.HasKey(key.PropertyInfo); + } + } + + private static PropertyConfiguration GetKeyProperty(EntityTypeConfiguration entityType) + { + IEnumerable keys = + entityType.Properties + .Where(p => (p.Name.Equals(entityType.Name + "Id", StringComparison.OrdinalIgnoreCase) || p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase)) + && (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(p.PropertyInfo.PropertyType) != null || TypeHelper.IsEnum(p.PropertyInfo.PropertyType))); + + if (keys.Count() == 1) + { + return keys.Single(); + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityTypeConvention.cs new file mode 100644 index 0000000..0188c81 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/EntityTypeConvention.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// An is used to configure an in the + /// . + /// + internal abstract class EntityTypeConvention : IEdmTypeConvention + { + protected EntityTypeConvention() + { + } + + public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model) + { + EntityTypeConfiguration entity = edmTypeConfiguration as EntityTypeConfiguration; + if (entity != null) + { + Apply(entity, model); + } + } + + /// + /// Applies the convention. + /// + /// The to apply the convention on. + /// The instance. + public abstract void Apply(EntityTypeConfiguration entity, ODataConventionModelBuilder model); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ForeignKeyDiscoveryConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ForeignKeyDiscoveryConvention.cs new file mode 100644 index 0000000..3eabb7b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ForeignKeyDiscoveryConvention.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + // Denotes a property convention used to discover foreign key properties if there is no any foreign key configured + // on the navigation property. + // The basic rule to discover the foreign key is: with the same property type and follow up the naming convention. + // The naming convention is: + // 1) "Principal class name + principal key name" equals the dependent property name + // For example: Customer (Id) <--> Order (CustomerId) + // 2) or "Principal key name" equals the dependent property name. + // For example: Customer (CustomerId) <--> Order (CustomerId) + internal class ForeignKeyDiscoveryConvention : IEdmPropertyConvention + { + public void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + NavigationPropertyConfiguration navigationProperty = edmProperty as NavigationPropertyConfiguration; + if (navigationProperty != null) + { + Apply(navigationProperty, structuralTypeConfiguration, model); + } + } + + public void Apply(NavigationPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, + ODataConventionModelBuilder model) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (structuralTypeConfiguration == null) + { + throw Error.ArgumentNull("structuralTypeConfiguration"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + EntityTypeConfiguration principalEntityType = model.StructuralTypes.OfType() + .FirstOrDefault(e => e.ClrType == edmProperty.RelatedClrType); + if (principalEntityType == null) + { + return; + } + + // Suppress the foreign key discovery convention for the following scenarios. + if (edmProperty.DependentProperties.Any() || edmProperty.Multiplicity == EdmMultiplicity.Many) + { + return; + } + + EntityTypeConfiguration dependentEntityType = structuralTypeConfiguration as EntityTypeConfiguration; + if (dependentEntityType == null) + { + return; + } + + IDictionary typeNameForeignKeys = + GetForeignKeys(principalEntityType, dependentEntityType); + + if (typeNameForeignKeys.Any() && typeNameForeignKeys.Count() == principalEntityType.Keys.Count()) + { + foreach (KeyValuePair foreignKey + in typeNameForeignKeys) + { + edmProperty.HasConstraint(foreignKey.Key.PropertyInfo, foreignKey.Value.PropertyInfo); + } + } + } + + private static IDictionary GetForeignKeys( + EntityTypeConfiguration principalEntityType, + EntityTypeConfiguration dependentEntityType) + { + IDictionary typeNameForeignKeys = + new Dictionary(); + + foreach (PrimitivePropertyConfiguration principalKey in principalEntityType.Keys) + { + foreach (PrimitivePropertyConfiguration dependentProperty in + dependentEntityType.Properties.OfType()) + { + Type dependentType = Nullable.GetUnderlyingType(dependentProperty.PropertyInfo.PropertyType) ?? + dependentProperty.PropertyInfo.PropertyType; + if (dependentType == principalKey.PropertyInfo.PropertyType) + { + if (String.Equals(dependentProperty.Name, principalEntityType.Name + principalKey.Name, + StringComparison.Ordinal)) + { + // Customer (Id) <--> Order (CustomerId) + typeNameForeignKeys.Add(dependentProperty, principalKey); + } + else if (String.Equals(dependentProperty.Name, principalKey.Name, StringComparison.Ordinal) && + String.Equals(principalKey.Name, principalEntityType.Name + "Id", StringComparison.OrdinalIgnoreCase)) + { + // Customer (CustomerId) <--> Order (CustomerId) + typeNameForeignKeys.Add(dependentProperty, principalKey); + } + } + } + } + + return typeNameForeignKeys; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/FunctionLinkGenerationConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/FunctionLinkGenerationConvention.cs new file mode 100644 index 0000000..e16e043 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/FunctionLinkGenerationConvention.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// The FunctionLinkGenerationConvention calls function.HasFunctionLink(..) if the function binds to a single entity and has not previously been configured. + /// + internal class FunctionLinkGenerationConvention : IOperationConvention + { + public void Apply(OperationConfiguration configuration, ODataModelBuilder model) + { + FunctionConfiguration function = configuration as FunctionConfiguration; + + if (function == null || !function.IsBindable) + { + return; + } + + // You only need to create links for bindable functions that bind to a single entity. + if (function.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Entity && function.GetFunctionLink() == null) + { + string bindingParamterType = function.BindingParameter.TypeConfiguration.FullName; + + function.HasFunctionLink(entityContext => + entityContext.GenerateFunctionLink(bindingParamterType, function.FullyQualifiedName, function.Parameters.Select(p => p.Name)), + followsConventions: true); + } + else if (function.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Collection && function.GetFeedFunctionLink() == null) + { + if (((CollectionTypeConfiguration)function.BindingParameter.TypeConfiguration).ElementType.Kind == + EdmTypeKind.Entity) + { + string bindingParamterType = function.BindingParameter.TypeConfiguration.FullName; + function.HasFeedFunctionLink( + feedContext => + feedContext.GenerateFunctionLink(bindingParamterType, function.FullyQualifiedName, function.Parameters.Select(p => p.Name)), + followsConventions: true); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IConvention.cs new file mode 100644 index 0000000..a4788af --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IConvention.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + [SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Justification = "Marker interface acceptable here for derivation")] + internal interface IConvention + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConvention.cs new file mode 100644 index 0000000..8b8c69b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConvention.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// Convention to process properties of . + /// + internal interface IEdmPropertyConvention : IConvention + { + /// + /// Applies the convention. + /// + /// The property the convention is applied on. + /// The the edmProperty belongs to. + /// The that contains the type this property is being applied to. + void Apply(PropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, + ODataConventionModelBuilder model); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConventionOfTPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConventionOfTPropertyConfiguration.cs new file mode 100644 index 0000000..7072c3b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmPropertyConventionOfTPropertyConfiguration.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// Convention to process properties of . + /// + /// + internal interface IEdmPropertyConvention : IEdmPropertyConvention where TPropertyConfiguration : PropertyConfiguration + { + /// + /// Applies the convention. + /// + /// The property the convention is applied on. + /// The the edmProperty belongs to. + /// The that contains the type this property is being applied to. + void Apply(TPropertyConfiguration edmProperty, StructuralTypeConfiguration structuralTypeConfiguration, + ODataConventionModelBuilder model); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmTypeConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmTypeConvention.cs new file mode 100644 index 0000000..c5ff8cf --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IEdmTypeConvention.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + internal interface IEdmTypeConvention : IConvention + { + void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/INavigationSourceConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/INavigationSourceConvention.cs new file mode 100644 index 0000000..b94728f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/INavigationSourceConvention.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + internal interface INavigationSourceConvention : IConvention + { + void Apply(NavigationSourceConfiguration configuration, ODataModelBuilder model); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IOperationConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IOperationConvention.cs new file mode 100644 index 0000000..4a7aa0a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/IOperationConvention.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + /// + /// Convention to apply to instances in the model + /// + internal interface IOperationConvention : IConvention + { + void Apply(OperationConfiguration configuration, ODataModelBuilder model); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/NavigationLinksGenerationConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/NavigationLinksGenerationConvention.cs new file mode 100644 index 0000000..ea6a56d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/NavigationLinksGenerationConvention.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + internal class NavigationLinksGenerationConvention : INavigationSourceConvention + { + public void Apply(NavigationSourceConfiguration configuration, ODataModelBuilder model) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + // generate links without cast for declared and inherited navigation properties + foreach (EntityTypeConfiguration entity in configuration.EntityType.ThisAndBaseTypes()) + { + foreach (NavigationPropertyConfiguration property in entity.NavigationProperties) + { + if (configuration.GetNavigationPropertyLink(property) == null) + { + configuration.HasNavigationPropertyLink( + property, + new NavigationLinkBuilder( + (entityContext, navigationProperty) => + entityContext.GenerateNavigationPropertyLink(navigationProperty, includeCast: false), followsConventions: true)); + } + } + } + + // generate links with cast for navigation properties in derived types. + foreach (EntityTypeConfiguration entity in model.DerivedTypes(configuration.EntityType)) + { + foreach (NavigationPropertyConfiguration property in entity.NavigationProperties) + { + if (configuration.GetNavigationPropertyLink(property) == null) + { + configuration.HasNavigationPropertyLink( + property, + new NavigationLinkBuilder( + (entityContext, navigationProperty) => + entityContext.GenerateNavigationPropertyLink(navigationProperty, includeCast: true), followsConventions: true)); + } + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/SelfLinksGenerationConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/SelfLinksGenerationConvention.cs new file mode 100644 index 0000000..2ba44ee --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/SelfLinksGenerationConvention.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + internal class SelfLinksGenerationConvention : INavigationSourceConvention + { + public void Apply(NavigationSourceConfiguration configuration, ODataModelBuilder model) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + // Configure the self link for the feed + EntitySetConfiguration entitySet = configuration as EntitySetConfiguration; + if (entitySet != null && (entitySet.GetFeedSelfLink() == null)) + { + entitySet.HasFeedSelfLink(feedContext => + { + string selfLink = feedContext.InternalUrlHelper.CreateODataLink(new EntitySetSegment(feedContext.EntitySetBase as IEdmEntitySet)); + + if (selfLink == null) + { + return null; + } + return new Uri(selfLink); + }); + } + + if (configuration.GetIdLink() == null) + { + configuration.HasIdLink(new SelfLinkBuilder((entityContext) => entityContext.GenerateSelfLink(includeCast: false), followsConventions: true)); + } + + if (configuration.GetEditLink() == null) + { + bool derivedTypesDefineNavigationProperty = model.DerivedTypes(configuration.EntityType) + .OfType().Any(e => e.NavigationProperties.Any()); + + // generate links with cast if any of the derived types define a navigation property + if (derivedTypesDefineNavigationProperty) + { + configuration.HasEditLink( + new SelfLinkBuilder( + entityContext => entityContext.GenerateSelfLink(includeCast: true), + followsConventions: true)); + } + else + { + configuration.HasEditLink( + new SelfLinkBuilder( + entityContext => entityContext.GenerateSelfLink(includeCast: false), + followsConventions: true)); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DecimalPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DecimalPropertyConfiguration.cs new file mode 100644 index 0000000..e271844 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DecimalPropertyConfiguration.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Used to configure a decimal property of an entity type or complex type. + /// This configuration functionality is exposed by the model builder Fluent API, see . + /// + public class DecimalPropertyConfiguration : PrecisionPropertyConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The declaring EDM type of the property. + public DecimalPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + } + + /// + /// Gets or sets the maximum number of digits allowed to the right of the decimal point. + /// + public int? Scale { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DynamicPropertyDictionaryAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DynamicPropertyDictionaryAnnotation.cs new file mode 100644 index 0000000..5daafd8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/DynamicPropertyDictionaryAnnotation.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// This annotation indicates the mapping from a to a . + /// The is an open type and the is the specific + /// property which is used in an open type to save/retrieve the dynamic properties. + /// + public class DynamicPropertyDictionaryAnnotation + { + /// + /// Initializes a new instance of class. + /// + /// The backing . + public DynamicPropertyDictionaryAnnotation(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (!typeof(IDictionary).IsAssignableFrom(propertyInfo.PropertyType)) + { + throw Error.Argument("propertyInfo", SRResources.InvalidPropertyInfoForDynamicPropertyAnnotation, + propertyInfo.PropertyType.Name, + "IDictionary"); + } + + PropertyInfo = propertyInfo; + } + + /// + /// Gets the which backs the dynamic properties of the open type. + /// + public PropertyInfo PropertyInfo + { + get; + private set; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs new file mode 100644 index 0000000..0339a1c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs @@ -0,0 +1,969 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.AspNet.OData.Builder +{ + internal static class EdmModelHelperMethods + { + public static IEdmModel BuildEdmModel(ODataModelBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + EdmModel model = new EdmModel(); + EdmEntityContainer container = new EdmEntityContainer(builder.Namespace, builder.ContainerName); + + // add types and sets, building an index on the way. + IEnumerable configTypes = builder.StructuralTypes.Concat(builder.EnumTypes); + EdmTypeMap edmMap = EdmTypeBuilder.GetTypesAndProperties(configTypes); + Dictionary edmTypeMap = model.AddTypes(edmMap); + + // Add EntitySets and build the mapping between the EdmEntitySet and the NavigationSourceConfiguration + NavigationSourceAndAnnotations[] entitySets = container.AddEntitySetAndAnnotations(builder, edmTypeMap); + + // Add Singletons and build the mapping between the EdmSingleton and the NavigationSourceConfiguration + NavigationSourceAndAnnotations[] singletons = container.AddSingletonAndAnnotations(builder, edmTypeMap); + + // Merge EntitySets and Singletons together + IEnumerable navigationSources = entitySets.Concat(singletons); + + // Build the navigation source map + IDictionary navigationSourceMap = model.GetNavigationSourceMap(edmMap, navigationSources); + + // Add the core vocabulary annotations + model.AddCoreVocabularyAnnotations(navigationSources, edmMap); + + // TODO: support etags on contained nav props + // Support for this in 5.x adds annotations to navigation properties. Ideally would add annotations to entity set/singleton for + // containing type(s) with nav paths to the contained nav property. + // model.AddNavPropAnnotations(builder, edmMap); // implemented in EdmModelHelperMethods.cs of 5.x branch + + // Add the capabilities vocabulary annotations + model.AddCapabilitiesVocabularyAnnotations(navigationSources, edmMap); + + // add operations + model.AddOperations(builder.Operations, container, edmTypeMap, navigationSourceMap); + + // finish up + model.AddElement(container); + + // build the map from IEdmEntityType to IEdmFunctionImport + model.SetAnnotationValue(model, new BindableOperationFinder(model)); + + return model; + } + + private static void AddTypes(this EdmModel model, Dictionary types) + { + Contract.Assert(model != null); + Contract.Assert(types != null); + + foreach (IEdmType type in types.Values) + { + model.AddType(type); + } + } + + private static NavigationSourceAndAnnotations[] AddEntitySetAndAnnotations(this EdmEntityContainer container, + ODataModelBuilder builder, Dictionary edmTypeMap) + { + IEnumerable configurations = builder.EntitySets; + + // build the entitysets + IEnumerable> entitySets = AddEntitySets(configurations, container, edmTypeMap); + + // return the annotation array + return entitySets.Select(e => new NavigationSourceAndAnnotations() + { + NavigationSource = e.Item1, + Configuration = e.Item2, + LinkBuilder = new NavigationSourceLinkBuilderAnnotation(e.Item2), + Url = new NavigationSourceUrlAnnotation { Url = e.Item2.GetUrl() } + }).ToArray(); + } + + private static NavigationSourceAndAnnotations[] AddSingletonAndAnnotations(this EdmEntityContainer container, + ODataModelBuilder builder, Dictionary edmTypeMap) + { + IEnumerable configurations = builder.Singletons; + + // build the singletons + IEnumerable> singletons = AddSingletons(configurations, container, edmTypeMap); + + // return the annotation array + return singletons.Select(e => new NavigationSourceAndAnnotations() + { + NavigationSource = e.Item1, + Configuration = e.Item2, + LinkBuilder = new NavigationSourceLinkBuilderAnnotation(e.Item2), + Url = new NavigationSourceUrlAnnotation { Url = e.Item2.GetUrl() } + }).ToArray(); + } + + private static IDictionary GetNavigationSourceMap(this EdmModel model, EdmTypeMap edmMap, + IEnumerable navigationSourceAndAnnotations) + { + // index the navigation source by name + Dictionary edmNavigationSourceMap = navigationSourceAndAnnotations.ToDictionary(e => e.NavigationSource.Name, e => e.NavigationSource); + + // apply the annotations + foreach (NavigationSourceAndAnnotations navigationSourceAndAnnotation in navigationSourceAndAnnotations) + { + EdmNavigationSource navigationSource = navigationSourceAndAnnotation.NavigationSource; + model.SetAnnotationValue(navigationSource, navigationSourceAndAnnotation.Url); + model.SetNavigationSourceLinkBuilder(navigationSource, navigationSourceAndAnnotation.LinkBuilder); + + AddNavigationBindings(edmMap, navigationSourceAndAnnotation.Configuration, navigationSource, navigationSourceAndAnnotation.LinkBuilder, + edmNavigationSourceMap); + } + + return edmNavigationSourceMap; + } + + private static void AddNavigationBindings(EdmTypeMap edmMap, + NavigationSourceConfiguration navigationSourceConfiguration, + EdmNavigationSource navigationSource, + NavigationSourceLinkBuilderAnnotation linkBuilder, + Dictionary edmNavigationSourceMap) + { + foreach (var binding in navigationSourceConfiguration.Bindings) + { + NavigationPropertyConfiguration navigationProperty = binding.NavigationProperty; + bool isContained = navigationProperty.ContainsTarget; + + IEdmType edmType = edmMap.EdmTypes[navigationProperty.DeclaringType.ClrType]; + IEdmStructuredType structuraType = edmType as IEdmStructuredType; + IEdmNavigationProperty edmNavigationProperty = structuraType.NavigationProperties() + .Single(np => np.Name == navigationProperty.Name); + + string bindingPath = ConvertBindingPath(edmMap, binding); + if (!isContained) + { + // calculate the binding path + navigationSource.AddNavigationTarget( + edmNavigationProperty, + edmNavigationSourceMap[binding.TargetNavigationSource.Name], + new EdmPathExpression(bindingPath)); + } + + NavigationLinkBuilder linkBuilderFunc = navigationSourceConfiguration.GetNavigationPropertyLink(navigationProperty); + if (linkBuilderFunc != null) + { + linkBuilder.AddNavigationPropertyLinkBuilder(edmNavigationProperty, linkBuilderFunc); + } + } + } + + private static string ConvertBindingPath(EdmTypeMap edmMap, NavigationPropertyBindingConfiguration binding) + { + IList bindings = new List(); + foreach (MemberInfo bindingInfo in binding.Path) + { + Type typeCast = TypeHelper.AsType(bindingInfo); + PropertyInfo propertyInfo = bindingInfo as PropertyInfo; + + if (typeCast != null) + { + IEdmType edmType = edmMap.EdmTypes[typeCast]; + bindings.Add(edmType.FullTypeName()); + } + else if (propertyInfo != null) + { + bindings.Add(edmMap.EdmProperties[propertyInfo].Name); + } + } + + return String.Join("/", bindings); + } + + private static void AddOperationParameters(EdmOperation operation, OperationConfiguration operationConfiguration, Dictionary edmTypeMap) + { + foreach (ParameterConfiguration parameter in operationConfiguration.Parameters) + { + bool isParameterNullable = parameter.Nullable; + IEdmTypeReference parameterTypeReference = GetEdmTypeReference(edmTypeMap, parameter.TypeConfiguration, nullable: isParameterNullable); + if (parameter.IsOptional) + { + if (parameter.DefaultValue != null) + { + operation.AddOptionalParameter(parameter.Name, parameterTypeReference, parameter.DefaultValue); + } + else + { + operation.AddOptionalParameter(parameter.Name, parameterTypeReference); + } + } + else + { + IEdmOperationParameter operationParameter = new EdmOperationParameter(operation, parameter.Name, parameterTypeReference); + operation.AddParameter(operationParameter); + } + } + } + + private static void AddOperationLinkBuilder(IEdmModel model, IEdmOperation operation, OperationConfiguration operationConfiguration) + { + ActionConfiguration actionConfiguration = operationConfiguration as ActionConfiguration; + IEdmAction action = operation as IEdmAction; + FunctionConfiguration functionConfiguration = operationConfiguration as FunctionConfiguration; + IEdmFunction function = operation as IEdmFunction; + if (operationConfiguration.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Entity) + { + if (actionConfiguration != null && actionConfiguration.GetActionLink() != null && action != null) + { + model.SetOperationLinkBuilder( + action, + new OperationLinkBuilder(actionConfiguration.GetActionLink(), actionConfiguration.FollowsConventions)); + } + else if (functionConfiguration != null && functionConfiguration.GetFunctionLink() != null && function != null) + { + model.SetOperationLinkBuilder( + function, + new OperationLinkBuilder(functionConfiguration.GetFunctionLink(), functionConfiguration.FollowsConventions)); + } + } + else if (operationConfiguration.BindingParameter.TypeConfiguration.Kind == EdmTypeKind.Collection) + { + CollectionTypeConfiguration collectionTypeConfiguration = + (CollectionTypeConfiguration)operationConfiguration.BindingParameter.TypeConfiguration; + + if (collectionTypeConfiguration.ElementType.Kind == EdmTypeKind.Entity) + { + if (actionConfiguration != null && actionConfiguration.GetFeedActionLink() != null && action != null) + { + model.SetOperationLinkBuilder( + action, + new OperationLinkBuilder(actionConfiguration.GetFeedActionLink(), actionConfiguration.FollowsConventions)); + } + else if (functionConfiguration != null && functionConfiguration.GetFeedFunctionLink() != null && function != null) + { + model.SetOperationLinkBuilder( + function, + new OperationLinkBuilder(functionConfiguration.GetFeedFunctionLink(), functionConfiguration.FollowsConventions)); + } + } + } + } + + private static void ValidateOperationEntitySetPath(IEdmModel model, IEdmOperationImport operationImport, OperationConfiguration operationConfiguration) + { + IEdmOperationParameter operationParameter; + Dictionary relativeNavigations; + IEnumerable edmErrors; + if (operationConfiguration.EntitySetPath != null && !operationImport.TryGetRelativeEntitySetPath(model, out operationParameter, out relativeNavigations, out edmErrors)) + { + throw Error.InvalidOperation(SRResources.OperationHasInvalidEntitySetPath, String.Join("/", operationConfiguration.EntitySetPath), operationConfiguration.FullyQualifiedName); + } + } + + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", + Justification = "The majority of types referenced by this method are EdmLib types this method needs to know about to operate correctly")] + private static void AddOperations(this EdmModel model, IEnumerable configurations, EdmEntityContainer container, + Dictionary edmTypeMap, IDictionary edmNavigationSourceMap) + { + Contract.Assert(model != null, "Model can't be null"); + + ValidateActionOverload(configurations.OfType()); + + foreach (OperationConfiguration operationConfiguration in configurations) + { + IEdmTypeReference returnReference = GetEdmTypeReference(edmTypeMap, + operationConfiguration.ReturnType, + operationConfiguration.ReturnType != null && operationConfiguration.ReturnNullable); + IEdmExpression expression = GetEdmEntitySetExpression(edmNavigationSourceMap, operationConfiguration); + IEdmPathExpression pathExpression = operationConfiguration.EntitySetPath != null + ? new EdmPathExpression(operationConfiguration.EntitySetPath) + : null; + + EdmOperationImport operationImport; + + switch (operationConfiguration.Kind) + { + case OperationKind.Action: + operationImport = CreateActionImport(operationConfiguration, container, returnReference, expression, pathExpression); + break; + case OperationKind.Function: + operationImport = CreateFunctionImport((FunctionConfiguration)operationConfiguration, container, returnReference, expression, pathExpression); + break; + case OperationKind.ServiceOperation: + Contract.Assert(false, "ServiceOperations are not supported."); + goto default; + default: + Contract.Assert(false, "Unsupported OperationKind"); + return; + } + + EdmOperation operation = (EdmOperation)operationImport.Operation; + if (operationConfiguration.IsBindable && operationConfiguration.Title != null && operationConfiguration.Title != operationConfiguration.Name) + { + model.SetOperationTitleAnnotation(operation, new OperationTitleAnnotation(operationConfiguration.Title)); + } + + if (operationConfiguration.IsBindable && + operationConfiguration.NavigationSource != null && + edmNavigationSourceMap.ContainsKey(operationConfiguration.NavigationSource.Name)) + { + model.SetAnnotationValue(operation, new ReturnedEntitySetAnnotation(operationConfiguration.NavigationSource.Name)); + } + + AddOperationParameters(operation, operationConfiguration, edmTypeMap); + + if (operationConfiguration.IsBindable) + { + AddOperationLinkBuilder(model, operation, operationConfiguration); + ValidateOperationEntitySetPath(model, operationImport, operationConfiguration); + } + else + { + container.AddElement(operationImport); + } + + model.AddElement(operation); + } + } + + private static EdmOperationImport CreateActionImport( + OperationConfiguration operationConfiguration, + EdmEntityContainer container, + IEdmTypeReference returnReference, + IEdmExpression expression, + IEdmPathExpression pathExpression) + { + EdmAction operation = new EdmAction( + operationConfiguration.Namespace, + operationConfiguration.Name, + returnReference, + operationConfiguration.IsBindable, + pathExpression); + return new EdmActionImport(container, operationConfiguration.Name, operation, expression); + } + + private static EdmOperationImport CreateFunctionImport( + FunctionConfiguration function, + EdmEntityContainer container, + IEdmTypeReference returnReference, + IEdmExpression expression, + IEdmPathExpression pathExpression) + { + EdmFunction operation = new EdmFunction( + function.Namespace, + function.Name, + returnReference, + function.IsBindable, + pathExpression, + function.IsComposable); + return new EdmFunctionImport(container, function.Name, operation, expression, includeInServiceDocument: function.IncludeInServiceDocument); + } + + // 11.5.4.2 Action Overload Resolution + // The same action name may be used multiple times within a schema provided there is at most one unbound overload, + // and each bound overload specifies a different binding parameter type. If the action is bound and the binding + // parameter type is part of an inheritance hierarchy, the action overload is selected based on the type of the + // URL segment preceding the action name. A type-cast segment can be used to select an action defined on a + // particular type in the hierarchy. + private static void ValidateActionOverload(IEnumerable configurations) + { + // 1. validate at most one unbound overload + ActionConfiguration[] unboundActions = configurations.Where(a => !a.IsBindable).ToArray(); + if (unboundActions.Length > 0) + { + HashSet unboundActionNames = new HashSet(); + foreach (ActionConfiguration action in unboundActions) + { + if (!unboundActionNames.Contains(action.Name)) + { + unboundActionNames.Add(action.Name); + } + else + { + throw Error.InvalidOperation(SRResources.MoreThanOneUnboundActionFound, action.Name); + } + } + } + + // 2. validate each bound overload action specifies a different binding parameter type + ActionConfiguration[] boundActions = configurations.Where(a => a.IsBindable).ToArray(); + if (boundActions.Length > 0) + { + var actionNamesToBindingTypes = new Dictionary>(); + foreach (ActionConfiguration action in boundActions) + { + IEdmTypeConfiguration newBindingType = action.BindingParameter.TypeConfiguration; + if (actionNamesToBindingTypes.ContainsKey(action.Name)) + { + IList bindingTypes = actionNamesToBindingTypes[action.Name]; + foreach (IEdmTypeConfiguration type in bindingTypes) + { + if (type == newBindingType) + { + throw Error.InvalidOperation(SRResources.MoreThanOneOverloadActionBoundToSameTypeFound, + action.Name, type.FullName); + } + } + + bindingTypes.Add(newBindingType); + } + else + { + IList bindingTypes = new List(); + bindingTypes.Add(newBindingType); + actionNamesToBindingTypes.Add(action.Name, bindingTypes); + } + } + } + } + + private static Dictionary AddTypes(this EdmModel model, EdmTypeMap edmTypeMap) + { + // build types + Dictionary edmTypes = edmTypeMap.EdmTypes; + + // Add an annotate types + model.AddTypes(edmTypes); + model.AddClrTypeAnnotations(edmTypes); + + // add annotation for properties + Dictionary edmProperties = edmTypeMap.EdmProperties; + model.AddClrPropertyInfoAnnotations(edmProperties); + model.AddClrEnumMemberInfoAnnotations(edmTypeMap); + model.AddPropertyRestrictionsAnnotations(edmTypeMap.EdmPropertiesRestrictions); + model.AddPropertiesQuerySettings(edmTypeMap.EdmPropertiesQuerySettings); + model.AddStructuredTypeQuerySettings(edmTypeMap.EdmStructuredTypeQuerySettings); + + // add dynamic dictionary property annotation for open types + model.AddDynamicPropertyDictionaryAnnotations(edmTypeMap.OpenTypes); + + return edmTypes; + } + + private static void AddType(this EdmModel model, IEdmType type) + { + if (type.TypeKind == EdmTypeKind.Complex) + { + model.AddElement(type as IEdmComplexType); + } + else if (type.TypeKind == EdmTypeKind.Entity) + { + model.AddElement(type as IEdmEntityType); + } + else if (type.TypeKind == EdmTypeKind.Enum) + { + model.AddElement(type as IEdmEnumType); + } + else + { + Contract.Assert(false, "Only ComplexTypes, EntityTypes and EnumTypes are supported."); + } + } + + private static EdmEntitySet AddEntitySet(this EdmEntityContainer container, EntitySetConfiguration entitySet, IDictionary edmTypeMap) + { + return container.AddEntitySet(entitySet.Name, (IEdmEntityType)edmTypeMap[entitySet.EntityType.ClrType]); + } + + private static IEnumerable> AddEntitySets(IEnumerable entitySets, EdmEntityContainer container, Dictionary edmTypeMap) + { + return entitySets.Select(es => Tuple.Create(container.AddEntitySet(es, edmTypeMap), es)); + } + + private static EdmSingleton AddSingleton(this EdmEntityContainer container, SingletonConfiguration singletonType, IDictionary edmTypeMap) + { + return container.AddSingleton(singletonType.Name, (IEdmEntityType)edmTypeMap[singletonType.EntityType.ClrType]); + } + + private static IEnumerable> AddSingletons(IEnumerable singletons, EdmEntityContainer container, Dictionary edmTypeMap) + { + return singletons.Select(sg => Tuple.Create(container.AddSingleton(sg, edmTypeMap), sg)); + } + + private static void AddClrTypeAnnotations(this EdmModel model, Dictionary edmTypes) + { + foreach (KeyValuePair map in edmTypes) + { + // pre-populate the model with clr-type annotations so that we dont have to scan + // all loaded assemblies to find the clr type for an edm type that we build. + IEdmType edmType = map.Value; + Type clrType = map.Key; + model.SetAnnotationValue(edmType, new ClrTypeAnnotation(clrType)); + } + } + + private static void AddClrPropertyInfoAnnotations(this EdmModel model, Dictionary edmProperties) + { + foreach (KeyValuePair edmPropertyMap in edmProperties) + { + IEdmProperty edmProperty = edmPropertyMap.Value; + PropertyInfo clrProperty = edmPropertyMap.Key; + if (edmProperty.Name != clrProperty.Name) + { + model.SetAnnotationValue(edmProperty, new ClrPropertyInfoAnnotation(clrProperty)); + } + } + } + + private static void AddClrEnumMemberInfoAnnotations(this EdmModel model, EdmTypeMap edmTypeMap) + { + if (edmTypeMap.EnumMembers == null || !edmTypeMap.EnumMembers.Any()) + { + return; + } + + var enumGroupBy = edmTypeMap.EnumMembers.GroupBy(e => e.Key.GetType(), e => e); + foreach (var enumGroup in enumGroupBy) + { + IEdmType edmType = edmTypeMap.EdmTypes[enumGroup.Key]; + model.SetAnnotationValue(edmType, new ClrEnumMemberAnnotation(enumGroup.ToDictionary(e => e.Key, e => e.Value))); + } + } + + private static void AddDynamicPropertyDictionaryAnnotations(this EdmModel model, + Dictionary openTypes) + { + foreach (KeyValuePair openType in openTypes) + { + IEdmStructuredType edmStructuredType = openType.Key; + PropertyInfo propertyInfo = openType.Value; + model.SetAnnotationValue(edmStructuredType, new DynamicPropertyDictionaryAnnotation(propertyInfo)); + } + } + + private static void AddPropertiesQuerySettings(this EdmModel model, + Dictionary edmPropertiesQuerySettings) + { + foreach (KeyValuePair edmPropertiesQuerySetting in + edmPropertiesQuerySettings) + { + IEdmProperty edmProperty = edmPropertiesQuerySetting.Key; + ModelBoundQuerySettings querySettings = edmPropertiesQuerySetting.Value; + model.SetAnnotationValue(edmProperty, querySettings); + } + } + + private static void AddStructuredTypeQuerySettings(this EdmModel model, + Dictionary edmStructuredTypeQuerySettings) + { + foreach ( + KeyValuePair edmStructuredTypeQuerySetting in + edmStructuredTypeQuerySettings) + { + IEdmStructuredType structuredType = edmStructuredTypeQuerySetting.Key; + ModelBoundQuerySettings querySettings = edmStructuredTypeQuerySetting.Value; + model.SetAnnotationValue(structuredType, querySettings); + } + } + + private static void AddPropertyRestrictionsAnnotations(this EdmModel model, Dictionary edmPropertiesRestrictions) + { + foreach (KeyValuePair edmPropertyRestriction in edmPropertiesRestrictions) + { + IEdmProperty edmProperty = edmPropertyRestriction.Key; + QueryableRestrictions restrictions = edmPropertyRestriction.Value; + model.SetAnnotationValue(edmProperty, new QueryableRestrictionsAnnotation(restrictions)); + } + } + + private static void AddCoreVocabularyAnnotations(this EdmModel model, IEnumerable navigationSources, EdmTypeMap edmTypeMap) + { + Contract.Assert(model != null); + Contract.Assert(edmTypeMap != null); + + if (navigationSources == null) + { + return; + } + + foreach (NavigationSourceAndAnnotations source in navigationSources) + { + IEdmVocabularyAnnotatable navigationSource = source.NavigationSource as IEdmVocabularyAnnotatable; + if (navigationSource == null) + { + continue; + } + + NavigationSourceConfiguration navigationSourceConfig = source.Configuration as NavigationSourceConfiguration; + if (navigationSourceConfig == null) + { + continue; + } + + model.AddOptimisticConcurrencyAnnotation(navigationSource, navigationSourceConfig, edmTypeMap); + } + } + + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", + Justification = "Relies on many ODataLib classes.")] + private static void AddOptimisticConcurrencyAnnotation(this EdmModel model, IEdmVocabularyAnnotatable target, + NavigationSourceConfiguration navigationSourceConfiguration, EdmTypeMap edmTypeMap) + { + EntityTypeConfiguration entityTypeConfig = navigationSourceConfiguration.EntityType; + + IEnumerable concurrencyProperties = + entityTypeConfig.Properties.OfType().Where(property => property.ConcurrencyToken); + foreach (var baseType in entityTypeConfig.BaseTypes()) + { + concurrencyProperties = concurrencyProperties.Concat( + baseType.Properties.OfType().Where(property => property.ConcurrencyToken)); + } + + IList edmProperties = new List(); + + foreach (StructuralPropertyConfiguration property in concurrencyProperties) + { + IEdmProperty value; + if (edmTypeMap.EdmProperties.TryGetValue(property.PropertyInfo, out value)) + { + var item = value as IEdmStructuralProperty; + if (item != null) + { + edmProperties.Add(item); + } + } + } + + if (edmProperties.Any()) + { + IEdmCollectionExpression collectionExpression = new EdmCollectionExpression(edmProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray()); + IEdmTerm term = Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyModel.ConcurrencyTerm; + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(target, term, collectionExpression); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + } + + private static void AddCapabilitiesVocabularyAnnotations(this EdmModel model, IEnumerable navigationSources, EdmTypeMap edmTypeMap) + { + Contract.Assert(model != null); + Contract.Assert(edmTypeMap != null); + + if (navigationSources == null) + { + return; + } + + foreach (NavigationSourceAndAnnotations source in navigationSources) + { + IEdmEntitySet entitySet = source.NavigationSource as IEdmEntitySet; + if (entitySet == null) + { + continue; + } + + EntitySetConfiguration entitySetConfig = source.Configuration as EntitySetConfiguration; + if (entitySetConfig == null) + { + continue; + } + + model.AddCountRestrictionsAnnotation(entitySet, entitySetConfig, edmTypeMap); + model.AddNavigationRestrictionsAnnotation(entitySet, entitySetConfig, edmTypeMap); + model.AddFilterRestrictionsAnnotation(entitySet, entitySetConfig, edmTypeMap); + model.AddSortRestrictionsAnnotation(entitySet, entitySetConfig, edmTypeMap); + model.AddExpandRestrictionsAnnotation(entitySet, entitySetConfig, edmTypeMap); + } + } + + private static void AddCountRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, + EntitySetConfiguration entitySetConfiguration, EdmTypeMap edmTypeMap) + { + EntityTypeConfiguration entityTypeConfig = entitySetConfiguration.EntityType; + + IEnumerable notCountableProperties = entityTypeConfig.Properties.Where(property => property.NotCountable); + + IList nonCountableProperties = new List(); + IList nonCountableNavigationProperties = new List(); + foreach (PropertyConfiguration property in notCountableProperties) + { + IEdmProperty value; + if (edmTypeMap.EdmProperties.TryGetValue(property.PropertyInfo, out value)) + { + if (value != null && value.Type.TypeKind() == EdmTypeKind.Collection) + { + if (value.PropertyKind == EdmPropertyKind.Navigation) + { + nonCountableNavigationProperties.Add((IEdmNavigationProperty)value); + } + else + { + nonCountableProperties.Add(value); + } + } + } + } + + if (nonCountableProperties.Any() || nonCountableNavigationProperties.Any()) + { + model.SetCountRestrictionsAnnotation(target, true, nonCountableProperties, nonCountableNavigationProperties); + } + } + + private static void AddNavigationRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, + EntitySetConfiguration entitySetConfiguration, EdmTypeMap edmTypeMap) + { + EntityTypeConfiguration entityTypeConfig = entitySetConfiguration.EntityType; + + IEnumerable notNavigableProperties = entityTypeConfig.Properties.Where(property => property.NotNavigable); + + IList> properties = + new List>(); + foreach (PropertyConfiguration property in notNavigableProperties) + { + IEdmProperty value; + if (edmTypeMap.EdmProperties.TryGetValue(property.PropertyInfo, out value)) + { + if (value != null && value.PropertyKind == EdmPropertyKind.Navigation) + { + properties.Add(new Tuple( + (IEdmNavigationProperty)value, CapabilitiesNavigationType.Recursive)); + } + } + } + + if (properties.Any()) + { + model.SetNavigationRestrictionsAnnotation(target, CapabilitiesNavigationType.Recursive, properties); + } + } + + private static void AddFilterRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, + EntitySetConfiguration entitySetConfiguration, EdmTypeMap edmTypeMap) + { + EntityTypeConfiguration entityTypeConfig = entitySetConfiguration.EntityType; + + IEnumerable notFilterProperties = entityTypeConfig.Properties.Where(property => property.NonFilterable); + + IList properties = new List(); + foreach (PropertyConfiguration property in notFilterProperties) + { + IEdmProperty value; + if (edmTypeMap.EdmProperties.TryGetValue(property.PropertyInfo, out value)) + { + if (value != null) + { + properties.Add(value); + } + } + } + + if (properties.Any()) + { + model.SetFilterRestrictionsAnnotation(target, true, true, null, properties); + } + } + + private static void AddSortRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, + EntitySetConfiguration entitySetConfiguration, EdmTypeMap edmTypeMap) + { + EntityTypeConfiguration entityTypeConfig = entitySetConfiguration.EntityType; + IEnumerable nonSortableProperties = entityTypeConfig.Properties.Where(property => property.Unsortable); + IList properties = new List(); + foreach (PropertyConfiguration property in nonSortableProperties) + { + IEdmProperty value; + if (edmTypeMap.EdmProperties.TryGetValue(property.PropertyInfo, out value)) + { + if (value != null) + { + properties.Add(value); + } + } + } + + if (properties.Any()) + { + model.SetSortRestrictionsAnnotation(target, true, null, null, properties); + } + } + + private static void AddExpandRestrictionsAnnotation(this EdmModel model, IEdmEntitySet target, + EntitySetConfiguration entitySetConfiguration, EdmTypeMap edmTypeMap) + { + EntityTypeConfiguration entityTypeConfig = entitySetConfiguration.EntityType; + IEnumerable nonExpandableProperties = entityTypeConfig.Properties.Where(property => property.NotExpandable); + IList properties = new List(); + foreach (PropertyConfiguration property in nonExpandableProperties) + { + IEdmProperty value; + if (edmTypeMap.EdmProperties.TryGetValue(property.PropertyInfo, out value)) + { + if (value != null && value.PropertyKind == EdmPropertyKind.Navigation) + { + properties.Add((IEdmNavigationProperty)value); + } + } + } + + if (properties.Any()) + { + model.SetExpandRestrictionsAnnotation(target, true, properties); + } + } + + private static IEdmExpression GetEdmEntitySetExpression(IDictionary navigationSources, OperationConfiguration operationConfiguration) + { + if (operationConfiguration.NavigationSource != null) + { + EdmNavigationSource navigationSource; + if (navigationSources.TryGetValue(operationConfiguration.NavigationSource.Name, out navigationSource)) + { + EdmEntitySet entitySet = navigationSource as EdmEntitySet; + if (entitySet != null) + { + return new EdmPathExpression(entitySet.Name); + } + } + else + { + throw Error.InvalidOperation(SRResources.EntitySetNotFoundForName, operationConfiguration.NavigationSource.Name); + } + } + else if (operationConfiguration.EntitySetPath != null) + { + return new EdmPathExpression(operationConfiguration.EntitySetPath); + } + + return null; + } + + private static IEdmTypeReference GetEdmTypeReference(Dictionary availableTypes, IEdmTypeConfiguration configuration, bool nullable) + { + Contract.Assert(availableTypes != null); + + if (configuration == null) + { + return null; + } + + EdmTypeKind kind = configuration.Kind; + if (kind == EdmTypeKind.Collection) + { + CollectionTypeConfiguration collectionType = (CollectionTypeConfiguration)configuration; + EdmCollectionType edmCollectionType = + new EdmCollectionType(GetEdmTypeReference(availableTypes, collectionType.ElementType, nullable)); + return new EdmCollectionTypeReference(edmCollectionType); + } + else + { + Type configurationClrType = TypeHelper.GetUnderlyingTypeOrSelf(configuration.ClrType); + + if (!TypeHelper.IsEnum(configurationClrType)) + { + configurationClrType = configuration.ClrType; + } + + IEdmType type; + + if (availableTypes.TryGetValue(configurationClrType, out type)) + { + if (kind == EdmTypeKind.Complex) + { + return new EdmComplexTypeReference((IEdmComplexType)type, nullable); + } + else if (kind == EdmTypeKind.Entity) + { + return new EdmEntityTypeReference((IEdmEntityType)type, nullable); + } + else if (kind == EdmTypeKind.Enum) + { + return new EdmEnumTypeReference((IEdmEnumType)type, nullable); + } + else + { + throw Error.InvalidOperation(SRResources.UnsupportedEdmTypeKind, kind.ToString()); + } + } + else if (configuration.Kind == EdmTypeKind.Primitive) + { + PrimitiveTypeConfiguration primitiveTypeConfiguration = (PrimitiveTypeConfiguration)configuration; + EdmPrimitiveTypeKind typeKind = EdmTypeBuilder.GetTypeKind(primitiveTypeConfiguration.ClrType); + return EdmCoreModel.Instance.GetPrimitive(typeKind, nullable); + } + else + { + throw Error.InvalidOperation(SRResources.NoMatchingIEdmTypeFound, configuration.FullName); + } + } + } + + internal static string GetNavigationSourceUrl(this IEdmModel model, IEdmNavigationSource navigationSource) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (navigationSource == null) + { + throw Error.ArgumentNull("navigationSource"); + } + + NavigationSourceUrlAnnotation annotation = model.GetAnnotationValue(navigationSource); + if (annotation == null) + { + return navigationSource.Name; + } + else + { + return annotation.Url; + } + } + + internal static IEnumerable GetAvailableActions(this IEdmModel model, IEdmEntityType entityType) + { + return model.GetAvailableOperations(entityType, false).OfType(); + } + + internal static IEnumerable GetAvailableFunctions(this IEdmModel model, IEdmEntityType entityType) + { + return model.GetAvailableOperations(entityType, false).OfType(); + } + + internal static IEnumerable GetAvailableOperationsBoundToCollection(this IEdmModel model, IEdmEntityType entityType) + { + return model.GetAvailableOperations(entityType, true); + } + + internal static IEnumerable GetAvailableOperations(this IEdmModel model, IEdmEntityType entityType, bool boundToCollection = false) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (entityType == null) + { + throw Error.ArgumentNull("entityType"); + } + + BindableOperationFinder annotation = model.GetAnnotationValue(model); + if (annotation == null) + { + annotation = new BindableOperationFinder(model); + model.SetAnnotationValue(model, annotation); + } + + if (boundToCollection) + { + return annotation.FindOperationsBoundToCollection(entityType); + } + else + { + return annotation.FindOperations(entityType); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs new file mode 100644 index 0000000..a8af88e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeBuilder.cs @@ -0,0 +1,566 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// builds 's from 's. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling acceptable")] + internal class EdmTypeBuilder + { + private readonly List _configurations; + private readonly Dictionary _types = new Dictionary(); + private readonly Dictionary _properties = new Dictionary(); + private readonly Dictionary _propertiesRestrictions = new Dictionary(); + private readonly Dictionary _propertiesQuerySettings = new Dictionary(); + private readonly Dictionary _structuredTypeQuerySettings = new Dictionary(); + private readonly Dictionary _members = new Dictionary(); + private readonly Dictionary _openTypes = new Dictionary(); + + internal EdmTypeBuilder(IEnumerable configurations) + { + _configurations = configurations.ToList(); + } + + private Dictionary GetEdmTypes() + { + // Reset + _types.Clear(); + _properties.Clear(); + _members.Clear(); + _openTypes.Clear(); + + // Create headers to allow CreateEdmTypeBody to blindly references other things. + foreach (IEdmTypeConfiguration config in _configurations) + { + CreateEdmTypeHeader(config); + } + + foreach (IEdmTypeConfiguration config in _configurations) + { + CreateEdmTypeBody(config); + } + + foreach (StructuralTypeConfiguration structrual in _configurations.OfType()) + { + CreateNavigationProperty(structrual); + } + + return _types; + } + + private void CreateEdmTypeHeader(IEdmTypeConfiguration config) + { + IEdmType edmType = GetEdmType(config.ClrType); + if (edmType == null) + { + if (config.Kind == EdmTypeKind.Complex) + { + ComplexTypeConfiguration complex = (ComplexTypeConfiguration)config; + IEdmComplexType baseType = null; + if (complex.BaseType != null) + { + CreateEdmTypeHeader(complex.BaseType); + baseType = GetEdmType(complex.BaseType.ClrType) as IEdmComplexType; + + Contract.Assert(baseType != null); + } + + EdmComplexType complexType = new EdmComplexType(config.Namespace, config.Name, + baseType, complex.IsAbstract ?? false, complex.IsOpen); + + _types.Add(config.ClrType, complexType); + + if (complex.IsOpen) + { + // add a mapping between the open complex type and its dynamic property dictionary. + _openTypes.Add(complexType, complex.DynamicPropertyDictionary); + } + edmType = complexType; + } + else if (config.Kind == EdmTypeKind.Entity) + { + EntityTypeConfiguration entity = config as EntityTypeConfiguration; + Contract.Assert(entity != null); + + IEdmEntityType baseType = null; + if (entity.BaseType != null) + { + CreateEdmTypeHeader(entity.BaseType); + baseType = GetEdmType(entity.BaseType.ClrType) as IEdmEntityType; + + Contract.Assert(baseType != null); + } + + EdmEntityType entityType = new EdmEntityType(config.Namespace, config.Name, baseType, + entity.IsAbstract ?? false, entity.IsOpen, entity.HasStream); + _types.Add(config.ClrType, entityType); + + if (entity.IsOpen) + { + // add a mapping between the open entity type and its dynamic property dictionary. + _openTypes.Add(entityType, entity.DynamicPropertyDictionary); + } + edmType = entityType; + } + else + { + EnumTypeConfiguration enumTypeConfiguration = config as EnumTypeConfiguration; + + // The config has to be enum. + Contract.Assert(enumTypeConfiguration != null); + + _types.Add(enumTypeConfiguration.ClrType, + new EdmEnumType(enumTypeConfiguration.Namespace, enumTypeConfiguration.Name, + GetTypeKind(enumTypeConfiguration.UnderlyingType), enumTypeConfiguration.IsFlags)); + } + } + + IEdmStructuredType structuredType = edmType as IEdmStructuredType; + StructuralTypeConfiguration structuralTypeConfiguration = config as StructuralTypeConfiguration; + if (structuredType != null && structuralTypeConfiguration != null && + !_structuredTypeQuerySettings.ContainsKey(structuredType)) + { + ModelBoundQuerySettings querySettings = + structuralTypeConfiguration.QueryConfiguration.ModelBoundQuerySettings; + if (querySettings != null) + { + _structuredTypeQuerySettings.Add(structuredType, + structuralTypeConfiguration.QueryConfiguration.ModelBoundQuerySettings); + } + } + } + + private void CreateEdmTypeBody(IEdmTypeConfiguration config) + { + IEdmType edmType = GetEdmType(config.ClrType); + + if (edmType.TypeKind == EdmTypeKind.Complex) + { + CreateComplexTypeBody((EdmComplexType)edmType, (ComplexTypeConfiguration)config); + } + else if (edmType.TypeKind == EdmTypeKind.Entity) + { + CreateEntityTypeBody((EdmEntityType)edmType, (EntityTypeConfiguration)config); + } + else + { + Contract.Assert(edmType.TypeKind == EdmTypeKind.Enum); + CreateEnumTypeBody((EdmEnumType)edmType, (EnumTypeConfiguration)config); + } + } + + private static IEdmTypeReference AddPrecisionConfigInPrimitiveTypeReference( + PrecisionPropertyConfiguration precisionProperty, + IEdmTypeReference primitiveTypeReference) + { + if (primitiveTypeReference is EdmTemporalTypeReference && precisionProperty.Precision.HasValue) + { + return new EdmTemporalTypeReference( + (IEdmPrimitiveType)primitiveTypeReference.Definition, + primitiveTypeReference.IsNullable, + precisionProperty.Precision); + } + return primitiveTypeReference; + } + + private static IEdmTypeReference AddLengthConfigInPrimitiveTypeReference( + LengthPropertyConfiguration lengthProperty, + IEdmTypeReference primitiveTypeReference) + { + if (lengthProperty.MaxLength.HasValue) + { + if (primitiveTypeReference is EdmStringTypeReference) + { + return new EdmStringTypeReference( + (IEdmPrimitiveType)primitiveTypeReference.Definition, + primitiveTypeReference.IsNullable, + false, + lengthProperty.MaxLength, + true); + } + if (primitiveTypeReference is EdmBinaryTypeReference) + { + return new EdmBinaryTypeReference( + (IEdmPrimitiveType)primitiveTypeReference.Definition, + primitiveTypeReference.IsNullable, + false, + lengthProperty.MaxLength); + } + } + return primitiveTypeReference; + } + + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling acceptable")] + private void CreateStructuralTypeBody(EdmStructuredType type, StructuralTypeConfiguration config) + { + foreach (PropertyConfiguration property in config.Properties) + { + IEdmProperty edmProperty = null; + + switch (property.Kind) + { + case PropertyKind.Primitive: + PrimitivePropertyConfiguration primitiveProperty = (PrimitivePropertyConfiguration)property; + EdmPrimitiveTypeKind typeKind = primitiveProperty.TargetEdmTypeKind ?? + GetTypeKind(primitiveProperty.PropertyInfo.PropertyType); + IEdmTypeReference primitiveTypeReference = EdmCoreModel.Instance.GetPrimitive( + typeKind, + primitiveProperty.OptionalProperty); + + if (typeKind == EdmPrimitiveTypeKind.Decimal) + { + DecimalPropertyConfiguration decimalProperty = + primitiveProperty as DecimalPropertyConfiguration; + if (decimalProperty.Precision.HasValue || decimalProperty.Scale.HasValue) + { + primitiveTypeReference = new EdmDecimalTypeReference( + (IEdmPrimitiveType)primitiveTypeReference.Definition, + primitiveTypeReference.IsNullable, + decimalProperty.Precision, + decimalProperty.Scale.HasValue ? decimalProperty.Scale : 0); + } + } + else if (EdmLibHelpers.HasPrecision(typeKind)) + { + PrecisionPropertyConfiguration precisionProperty = + primitiveProperty as PrecisionPropertyConfiguration; + primitiveTypeReference = AddPrecisionConfigInPrimitiveTypeReference( + precisionProperty, + primitiveTypeReference); + } + else if (EdmLibHelpers.HasLength(typeKind)) + { + LengthPropertyConfiguration lengthProperty = + primitiveProperty as LengthPropertyConfiguration; + primitiveTypeReference = AddLengthConfigInPrimitiveTypeReference( + lengthProperty, + primitiveTypeReference); + } + edmProperty = type.AddStructuralProperty( + primitiveProperty.Name, + primitiveTypeReference, + defaultValue: null); + break; + + case PropertyKind.Complex: + ComplexPropertyConfiguration complexProperty = property as ComplexPropertyConfiguration; + IEdmComplexType complexType = GetEdmType(complexProperty.RelatedClrType) as IEdmComplexType; + + edmProperty = type.AddStructuralProperty( + complexProperty.Name, + new EdmComplexTypeReference(complexType, complexProperty.OptionalProperty)); + break; + + case PropertyKind.Collection: + edmProperty = CreateStructuralTypeCollectionPropertyBody(type, (CollectionPropertyConfiguration)property); + break; + + case PropertyKind.Enum: + edmProperty = CreateStructuralTypeEnumPropertyBody(type, (EnumPropertyConfiguration)property); + break; + + default: + break; + } + + if (edmProperty != null) + { + if (property.PropertyInfo != null) + { + _properties[property.PropertyInfo] = edmProperty; + } + + if (property.IsRestricted) + { + _propertiesRestrictions[edmProperty] = new QueryableRestrictions(property); + } + + if (property.QueryConfiguration.ModelBoundQuerySettings != null) + { + _propertiesQuerySettings.Add(edmProperty, property.QueryConfiguration.ModelBoundQuerySettings); + } + } + } + } + + private IEdmProperty CreateStructuralTypeCollectionPropertyBody(EdmStructuredType type, CollectionPropertyConfiguration collectionProperty) + { + IEdmTypeReference elementTypeReference = null; + Type clrType = TypeHelper.GetUnderlyingTypeOrSelf(collectionProperty.ElementType); + + if (TypeHelper.IsEnum(clrType)) + { + IEdmType edmType = GetEdmType(clrType); + + if (edmType == null) + { + throw Error.InvalidOperation(SRResources.EnumTypeDoesNotExist, clrType.Name); + } + + IEdmEnumType enumElementType = (IEdmEnumType)edmType; + bool isNullable = collectionProperty.ElementType != clrType; + elementTypeReference = new EdmEnumTypeReference(enumElementType, isNullable); + } + else + { + IEdmType edmType = GetEdmType(collectionProperty.ElementType); + if (edmType != null) + { + IEdmComplexType elementType = edmType as IEdmComplexType; + Contract.Assert(elementType != null); + elementTypeReference = new EdmComplexTypeReference(elementType, collectionProperty.OptionalProperty); + } + else + { + elementTypeReference = + EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(collectionProperty.ElementType); + Contract.Assert(elementTypeReference != null); + } + } + + return type.AddStructuralProperty( + collectionProperty.Name, + new EdmCollectionTypeReference(new EdmCollectionType(elementTypeReference))); + } + + private IEdmProperty CreateStructuralTypeEnumPropertyBody(EdmStructuredType type, EnumPropertyConfiguration enumProperty) + { + Type enumPropertyType = TypeHelper.GetUnderlyingTypeOrSelf(enumProperty.RelatedClrType); + IEdmType edmType = GetEdmType(enumPropertyType); + + if (edmType == null) + { + throw Error.InvalidOperation(SRResources.EnumTypeDoesNotExist, enumPropertyType.Name); + } + + IEdmEnumType enumType = (IEdmEnumType)edmType; + IEdmTypeReference enumTypeReference = new EdmEnumTypeReference(enumType, enumProperty.OptionalProperty); + + return type.AddStructuralProperty( + enumProperty.Name, + enumTypeReference, + defaultValue: null); + } + + private void CreateComplexTypeBody(EdmComplexType type, ComplexTypeConfiguration config) + { + Contract.Assert(type != null); + Contract.Assert(config != null); + + CreateStructuralTypeBody(type, config); + } + + private void CreateEntityTypeBody(EdmEntityType type, EntityTypeConfiguration config) + { + Contract.Assert(type != null); + Contract.Assert(config != null); + + CreateStructuralTypeBody(type, config); + IEnumerable keys = config.Keys.Select(p => type.DeclaredProperties.OfType().First(dp => dp.Name == p.Name)); + type.AddKeys(keys); + + // Add the Enum keys + keys = config.EnumKeys.Select(p => type.DeclaredProperties.OfType().First(dp => dp.Name == p.Name)); + type.AddKeys(keys); + } + + private void CreateNavigationProperty(StructuralTypeConfiguration config) + { + Contract.Assert(config != null); + + EdmStructuredType type = (EdmStructuredType)(GetEdmType(config.ClrType)); + + foreach (NavigationPropertyConfiguration navProp in config.NavigationProperties) + { + Func getInfo = nav => + { + EdmNavigationPropertyInfo info = new EdmNavigationPropertyInfo + { + Name = nav.Name, + TargetMultiplicity = nav.Multiplicity, + Target = GetEdmType(nav.RelatedClrType) as IEdmEntityType, + ContainsTarget = nav.ContainsTarget, + OnDelete = nav.OnDeleteAction + }; + + // Principal properties + if (nav.PrincipalProperties.Any()) + { + info.PrincipalProperties = GetDeclaringPropertyInfo(nav.PrincipalProperties); + } + + // Dependent properties + if (nav.DependentProperties.Any()) + { + info.DependentProperties = GetDeclaringPropertyInfo(nav.DependentProperties); + } + + return info; + }; + + var navInfo = getInfo(navProp); + var props = new Dictionary(); + EdmEntityType entityType = type as EdmEntityType; + if (entityType != null && navProp.Partner != null) + { + var edmProperty = entityType.AddBidirectionalNavigation(navInfo, getInfo(navProp.Partner)); + var partnerEdmProperty = (navInfo.Target as EdmEntityType).Properties().Single(p => p.Name == navProp.Partner.Name); + props.Add(edmProperty, navProp); + props.Add(partnerEdmProperty, navProp.Partner); + } + else + { + // Do not add this if we have have a partner relationship configured, as this + // property will be added automatically through the AddBidirectionalNavigation + var targetConfig = config.ModelBuilder.GetTypeConfigurationOrNull(navProp.RelatedClrType) as StructuralTypeConfiguration; + if (!targetConfig.NavigationProperties.Any(p => p.Partner != null && p.Partner.Name == navInfo.Name)) + { + var edmProperty = type.AddUnidirectionalNavigation(navInfo); + props.Add(edmProperty, navProp); + } + } + + foreach (var item in props) + { + var edmProperty = item.Key; + var prop = item.Value; + if (prop.PropertyInfo != null) + { + _properties[prop.PropertyInfo] = edmProperty; + } + + if (prop.IsRestricted) + { + _propertiesRestrictions[edmProperty] = new QueryableRestrictions(prop); + } + + if (prop.QueryConfiguration.ModelBoundQuerySettings != null) + { + _propertiesQuerySettings.Add(edmProperty, prop.QueryConfiguration.ModelBoundQuerySettings); + } + } + } + } + + private IList GetDeclaringPropertyInfo(IEnumerable propertyInfos) + { + IList edmProperties = new List(); + foreach (PropertyInfo propInfo in propertyInfos) + { + IEdmProperty edmProperty; + if (_properties.TryGetValue(propInfo, out edmProperty)) + { + edmProperties.Add(edmProperty); + } + else + { + Contract.Assert(TypeHelper.GetReflectedType(propInfo) != null); + Type baseType = TypeHelper.GetBaseType(TypeHelper.GetReflectedType(propInfo)); + while (baseType != null) + { + PropertyInfo basePropInfo = baseType.GetProperty(propInfo.Name); + if (_properties.TryGetValue(basePropInfo, out edmProperty)) + { + edmProperties.Add(edmProperty); + break; + } + + baseType = TypeHelper.GetBaseType(baseType); + } + + Contract.Assert(baseType != null); + } + } + + return edmProperties.OfType().ToList(); + } + + private void CreateEnumTypeBody(EdmEnumType type, EnumTypeConfiguration config) + { + Contract.Assert(type != null); + Contract.Assert(config != null); + + foreach (EnumMemberConfiguration member in config.Members) + { + // EdmIntegerConstant can only support a value of long type. + long value; + try + { + value = Convert.ToInt64(member.MemberInfo, CultureInfo.InvariantCulture); + } + catch + { + throw Error.Argument("value", SRResources.EnumValueCannotBeLong, Enum.GetName(member.MemberInfo.GetType(), member.MemberInfo)); + } + + EdmEnumMember edmMember = new EdmEnumMember(type, member.Name, + new EdmEnumMemberValue(value)); + type.AddMember(edmMember); + _members[member.MemberInfo] = edmMember; + } + } + + private IEdmType GetEdmType(Type clrType) + { + Contract.Assert(clrType != null); + + IEdmType edmType; + _types.TryGetValue(clrType, out edmType); + + return edmType; + } + + /// + /// Builds and 's from + /// + /// A collection of 's + /// The built dictionary of 's indexed by their backing CLR type, + /// and dictionary of 's indexed by their backing CLR property info + public static EdmTypeMap GetTypesAndProperties(IEnumerable configurations) + { + if (configurations == null) + { + throw Error.ArgumentNull("configurations"); + } + + EdmTypeBuilder builder = new EdmTypeBuilder(configurations); + return new EdmTypeMap(builder.GetEdmTypes(), + builder._properties, + builder._propertiesRestrictions, + builder._propertiesQuerySettings, + builder._structuredTypeQuerySettings, + builder._members, + builder._openTypes); + } + + /// + /// Gets the that maps to the + /// + /// The clr type + /// The corresponding Edm primitive kind. + public static EdmPrimitiveTypeKind GetTypeKind(Type clrType) + { + IEdmPrimitiveType primitiveType = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(clrType); + if (primitiveType == null) + { + throw Error.Argument("clrType", SRResources.MustBePrimitiveType, clrType.FullName); + } + + return primitiveType.PrimitiveKind; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeConfigurationExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeConfigurationExtensions.cs new file mode 100644 index 0000000..ab05bf4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeConfigurationExtensions.cs @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + internal static class EdmTypeConfigurationExtensions + { + // returns all the properties declared in the base types of this type. + public static IEnumerable DerivedProperties( + this StructuralTypeConfiguration structuralType) + { + if (structuralType == null) + { + throw Error.ArgumentNull("structuralType"); + } + + if (structuralType.Kind == EdmTypeKind.Entity) + { + return DerivedProperties((EntityTypeConfiguration)structuralType); + } + + if (structuralType.Kind == EdmTypeKind.Complex) + { + return DerivedProperties((ComplexTypeConfiguration)structuralType); + } + + return Enumerable.Empty(); + } + + public static IEnumerable DerivedProperties( + this EntityTypeConfiguration entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + EntityTypeConfiguration baseType = entity.BaseType; + while (baseType != null) + { + foreach (PropertyConfiguration property in baseType.Properties) + { + yield return property; + } + + baseType = baseType.BaseType; + } + } + + public static IEnumerable DerivedProperties( + this ComplexTypeConfiguration complex) + { + if (complex == null) + { + throw Error.ArgumentNull("complex"); + } + + ComplexTypeConfiguration baseType = complex.BaseType; + while (baseType != null) + { + foreach (PropertyConfiguration property in baseType.Properties) + { + yield return property; + } + + baseType = baseType.BaseType; + } + } + + // returns the keys declared or inherited for this entity type. + public static IEnumerable Keys(this EntityTypeConfiguration entity) + { + Contract.Assert(entity != null); + if (entity.Keys.Any() || entity.EnumKeys.Any()) + { + return entity.Keys.OfType().Concat(entity.EnumKeys); + } + + if (entity.BaseType == null) + { + return Enumerable.Empty(); + } + + return Keys(entity.BaseType); + } + + // Returns the base types, this type. + public static IEnumerable ThisAndBaseTypes( + this StructuralTypeConfiguration structuralType) + { + Contract.Assert(structuralType != null); + return structuralType.BaseTypes().Concat(new[] { structuralType }); + } + + // Returns the base types, this type and all the derived types of this type. + public static IEnumerable ThisAndBaseAndDerivedTypes( + this ODataModelBuilder modelBuilder, StructuralTypeConfiguration structuralType) + { + Contract.Assert(modelBuilder != null); + Contract.Assert(structuralType != null); + + return structuralType.BaseTypes() + .Concat(new[] { structuralType }) + .Concat(modelBuilder.DerivedTypes(structuralType)); + } + + // Returns the base types for this type. + public static IEnumerable BaseTypes( + this StructuralTypeConfiguration structuralType) + { + Contract.Assert(structuralType != null); + + if (structuralType.Kind == EdmTypeKind.Entity) + { + EntityTypeConfiguration entity = (EntityTypeConfiguration)structuralType; + + entity = entity.BaseType; + while (entity != null) + { + yield return entity; + entity = entity.BaseType; + } + } + + if (structuralType.Kind == EdmTypeKind.Complex) + { + ComplexTypeConfiguration complex = (ComplexTypeConfiguration)structuralType; + + complex = complex.BaseType; + while (complex != null) + { + yield return complex; + complex = complex.BaseType; + } + } + } + + // Returns all the derived types of this type. + public static IEnumerable DerivedTypes(this ODataModelBuilder modelBuilder, + StructuralTypeConfiguration structuralType) + { + if (modelBuilder == null) + { + throw Error.ArgumentNull("modelBuilder"); + } + + if (structuralType == null) + { + throw Error.ArgumentNull("structuralType"); + } + + if (structuralType.Kind == EdmTypeKind.Entity) + { + return DerivedTypes(modelBuilder, (EntityTypeConfiguration)structuralType); + } + + if (structuralType.Kind == EdmTypeKind.Complex) + { + return DerivedTypes(modelBuilder, (ComplexTypeConfiguration)structuralType); + } + + return Enumerable.Empty(); + } + + public static IEnumerable DerivedTypes(this ODataModelBuilder modelBuilder, + EntityTypeConfiguration entity) + { + if (modelBuilder == null) + { + throw Error.ArgumentNull("modelBuilder"); + } + + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + IEnumerable derivedEntities = modelBuilder.StructuralTypes + .OfType().Where(e => e.BaseType == entity); + + foreach (EntityTypeConfiguration derivedType in derivedEntities) + { + yield return derivedType; + foreach (EntityTypeConfiguration derivedDerivedType in modelBuilder.DerivedTypes(derivedType)) + { + yield return derivedDerivedType; + } + } + } + + public static IEnumerable DerivedTypes(this ODataModelBuilder modelBuilder, + ComplexTypeConfiguration complex) + { + if (modelBuilder == null) + { + throw Error.ArgumentNull("modelBuilder"); + } + + if (complex == null) + { + throw Error.ArgumentNull("complex"); + } + + IEnumerable derivedComplexs = + modelBuilder.StructuralTypes.OfType().Where(e => e.BaseType == complex); + + foreach (ComplexTypeConfiguration derivedType in derivedComplexs) + { + yield return derivedType; + foreach (ComplexTypeConfiguration derivedDerivedType in modelBuilder.DerivedTypes(derivedType)) + { + yield return derivedDerivedType; + } + } + } + + public static bool IsAssignableFrom(this StructuralTypeConfiguration baseStructuralType, + StructuralTypeConfiguration structuralType) + { + if (structuralType.Kind == EdmTypeKind.Entity && baseStructuralType.Kind == EdmTypeKind.Entity) + { + EntityTypeConfiguration entity = (EntityTypeConfiguration)structuralType; + while (entity != null) + { + if (baseStructuralType == entity) + { + return true; + } + + entity = entity.BaseType; + } + } + else if (structuralType.Kind == EdmTypeKind.Complex && baseStructuralType.Kind == EdmTypeKind.Complex) + { + ComplexTypeConfiguration complex = (ComplexTypeConfiguration)structuralType; + while (complex != null) + { + if (baseStructuralType == complex) + { + return true; + } + + complex = complex.BaseType; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs new file mode 100644 index 0000000..b2c8169 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EdmTypeMap.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + internal class EdmTypeMap + { + public EdmTypeMap( + Dictionary edmTypes, + Dictionary edmProperties, + Dictionary edmPropertiesRestrictions, + Dictionary edmPropertiesQuerySettings, + Dictionary edmStructuredTypeQuerySettings, + Dictionary enumMembers, + Dictionary openTypes) + { + EdmTypes = edmTypes; + EdmProperties = edmProperties; + EdmPropertiesRestrictions = edmPropertiesRestrictions; + EdmPropertiesQuerySettings = edmPropertiesQuerySettings; + EdmStructuredTypeQuerySettings = edmStructuredTypeQuerySettings; + EnumMembers = enumMembers; + OpenTypes = openTypes; + } + + public Dictionary EdmTypes { get; private set; } + + public Dictionary EdmProperties { get; private set; } + + public Dictionary EdmPropertiesRestrictions { get; private set; } + + public Dictionary EdmPropertiesQuerySettings { get; private set; } + + public Dictionary EdmStructuredTypeQuerySettings { get; private set; } + + public Dictionary EnumMembers { get; private set; } + + public Dictionary OpenTypes { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityCollectionConfigurationOfTEntityType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityCollectionConfigurationOfTEntityType.cs new file mode 100644 index 0000000..0759c82 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityCollectionConfigurationOfTEntityType.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// EntityCollectionConfiguration represents a Collection of Entities. + /// This class can be used to configure things that get bound to entities, like Actions bound to a collection. + /// + /// The EntityType that is the ElementType of the EntityCollection + public class EntityCollectionConfiguration : CollectionTypeConfiguration + { + internal EntityCollectionConfiguration(EntityTypeConfiguration elementType) + : base(elementType, typeof(IEnumerable)) + { + } + + /// + /// Creates a new Action that binds to Collection(EntityType). + /// + /// The name of the Action + /// An to allow further configuration of the Action. + public ActionConfiguration Action(string name) + { + Contract.Assert(ModelBuilder != null); + ActionConfiguration configuration = ModelBuilder.Action(name); + configuration.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, this); + return configuration; + } + + /// + /// Creates a new Function that binds to Collection(EntityType). + /// + /// The name of the Function + /// A to allow further configuration of the Function. + public FunctionConfiguration Function(string name) + { + Contract.Assert(ModelBuilder != null); + FunctionConfiguration configuration = ModelBuilder.Function(name); + configuration.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, this); + return configuration; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfiguration.cs new file mode 100644 index 0000000..5768ff9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfiguration.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Allows configuration to be performed for an entity set in a model. + /// A can be obtained by using the method . + /// + public class EntitySetConfiguration : NavigationSourceConfiguration + { + private Func _feedSelfLinkFactory; + + /// + /// Initializes a new instance of the class. + /// The default constructor is intended for use by unit testing only. + /// + public EntitySetConfiguration() + : base() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The of the entity type contained in this entity set. + /// The name of the entity set. + public EntitySetConfiguration(ODataModelBuilder modelBuilder, Type entityClrType, string name) + : base(modelBuilder, entityClrType, name) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The entity type contained in this entity set. + /// The name of the entity set. + public EntitySetConfiguration(ODataModelBuilder modelBuilder, EntityTypeConfiguration entityType, string name) + : base(modelBuilder, entityType, name) + { + } + + /// + /// Adds a self link to the feed. + /// + /// The builder used to generate the link URL. + /// The navigation source configuration currently being configured. + public virtual NavigationSourceConfiguration HasFeedSelfLink(Func feedSelfLinkFactory) + { + _feedSelfLinkFactory = feedSelfLinkFactory; + return this; + } + + /// + /// Gets the builder used to generate self links for feeds for this navigation source. + /// + /// The link builder. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Consistent with EF Has/Get pattern")] + public virtual Func GetFeedSelfLink() + { + return _feedSelfLinkFactory; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfigurationOfTEntityType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfigurationOfTEntityType.cs new file mode 100644 index 0000000..30f1a2a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntitySetConfigurationOfTEntityType.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// The element type of the entity set. + /// + public class EntitySetConfiguration : NavigationSourceConfiguration + where TEntityType : class + { + internal EntitySetConfiguration(ODataModelBuilder modelBuilder, string name) + : base(modelBuilder, new EntitySetConfiguration(modelBuilder, typeof(TEntityType), name)) + { + } + + internal EntitySetConfiguration(ODataModelBuilder modelBuilder, EntitySetConfiguration configuration) + : base(modelBuilder, configuration) + { + } + + internal EntitySetConfiguration EntitySet + { + get { return (EntitySetConfiguration)Configuration; } + } + + /// + /// Adds a self link to the feed. + /// + /// The builder used to generate the link URL. + public virtual void HasFeedSelfLink(Func feedSelfLinkFactory) + { + if (feedSelfLinkFactory == null) + { + throw Error.ArgumentNull("feedSelfLinkFactory"); + } + + EntitySet.HasFeedSelfLink(feedContext => new Uri(feedSelfLinkFactory(feedContext))); + } + + /// + /// Adds a self link to the feed. + /// + /// The builder used to generate the link URL. + public virtual void HasFeedSelfLink(Func feedSelfLinkFactory) + { + if (feedSelfLinkFactory == null) + { + throw Error.ArgumentNull("feedSelfLinkFactory"); + } + + EntitySet.HasFeedSelfLink(feedSelfLinkFactory); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfiguration.cs new file mode 100644 index 0000000..4caef94 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfiguration.cs @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + // TODO: add support for FK properties + // CUT: support for bi-directional properties + + /// + /// Represents an that can be built using . + /// + public class EntityTypeConfiguration : StructuralTypeConfiguration + { + private List _keys = new List(); + private List _enumKeys = new List(); + + /// + /// Initializes a new instance of the class. + /// + /// The default constructor is intended for use by unit testing only. + public EntityTypeConfiguration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The being used. + /// The backing CLR type for this entity type. + public EntityTypeConfiguration(ODataModelBuilder modelBuilder, Type clrType) + : base(modelBuilder, clrType) + { + } + + /// + /// Gets the of this + /// + public override EdmTypeKind Kind + { + get { return EdmTypeKind.Entity; } + } + + /// + /// Gets or sets a value indicating whether this type is a media type. + /// + public virtual bool HasStream { get; set; } + + /// + /// Gets the collection of keys for this entity type. + /// + public virtual IEnumerable Keys + { + get + { + return _keys; + } + } + + /// + /// Gets the collection of enum keys for this entity type. + /// + public virtual IEnumerable EnumKeys + { + get { return _enumKeys; } + } + + /// + /// Gets or sets the base type of this entity type. + /// + public virtual EntityTypeConfiguration BaseType + { + get + { + return BaseTypeInternal as EntityTypeConfiguration; + } + set + { + DerivesFrom(value); + } + } + + /// + /// Marks this entity type as abstract. + /// + /// Returns itself so that multiple calls can be chained. + public virtual EntityTypeConfiguration Abstract() + { + AbstractImpl(); + return this; + } + + /// + /// Marks this entity type as media type. + /// + /// Returns itself so that multiple calls can be chained. + public virtual EntityTypeConfiguration MediaType() + { + HasStream = true; + return this; + } + + /// + /// Configures the key property(s) for this entity type. + /// + /// The property to be added to the key properties of this entity type. + /// Returns itself so that multiple calls can be chained. + public virtual EntityTypeConfiguration HasKey(PropertyInfo keyProperty) + { + if (BaseType != null && BaseType.Keys().Any()) + { + throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, BaseType.FullName); + } + + // Add the enum key if the property type is enum + if (TypeHelper.IsEnum(keyProperty.PropertyType)) + { + ModelBuilder.AddEnumType(keyProperty.PropertyType); + EnumPropertyConfiguration enumConfig = AddEnumProperty(keyProperty); + + // keys are always required + enumConfig.IsRequired(); + + if (!_enumKeys.Contains(enumConfig)) + { + _enumKeys.Add(enumConfig); + } + } + else + { + PrimitivePropertyConfiguration propertyConfig = AddProperty(keyProperty); + + // keys are always required + propertyConfig.IsRequired(); + + if (!_keys.Contains(propertyConfig)) + { + _keys.Add(propertyConfig); + } + } + + return this; + } + + /// + /// Removes the property from the entity keys collection. + /// + /// The key to be removed. + /// This method just disable the property to be not a key anymore. It does not remove the property all together. + /// To remove the property completely, use the method + public virtual void RemoveKey(PrimitivePropertyConfiguration keyProperty) + { + if (keyProperty == null) + { + throw Error.ArgumentNull("keyProperty"); + } + + _keys.Remove(keyProperty); + } + + /// + /// Removes the enum property from the entity enum keys collection. + /// + /// The key to be removed. + /// This method just disable the property to be not a key anymore. It does not remove the property all together. + /// To remove the property completely, use the method + public virtual void RemoveKey(EnumPropertyConfiguration enumKeyProperty) + { + if (enumKeyProperty == null) + { + throw Error.ArgumentNull("enumKeyProperty"); + } + + _enumKeys.Remove(enumKeyProperty); + } + + /// + /// Sets the base type of this entity type to null meaning that this entity type + /// does not derive from anything. + /// + /// Returns itself so that multiple calls can be chained. + public virtual EntityTypeConfiguration DerivesFromNothing() + { + DerivesFromNothingImpl(); + return this; + } + + /// + /// Sets the base type of this entity type. + /// + /// The base entity type. + /// Returns itself so that multiple calls can be chained. + public virtual EntityTypeConfiguration DerivesFrom(EntityTypeConfiguration baseType) + { + if ((Keys.Any() || EnumKeys.Any()) && baseType.Keys().Any()) + { + throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, baseType.FullName); + } + + DerivesFromImpl(baseType); + return this; + } + + /// + /// Removes the property from the entity. + /// + /// The of the property to be removed. + public override void RemoveProperty(PropertyInfo propertyInfo) + { + base.RemoveProperty(propertyInfo); + _keys.RemoveAll(p => p.PropertyInfo == propertyInfo); + _enumKeys.RemoveAll(p => p.PropertyInfo == propertyInfo); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs new file mode 100644 index 0000000..e6cd007 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EntityTypeConfigurationOfTEntityType.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// + /// The backing CLR type for this . + public class EntityTypeConfiguration : StructuralTypeConfiguration where TEntityType : class + { + private EntityTypeConfiguration _configuration; + private EntityCollectionConfiguration _collection; + private ODataModelBuilder _modelBuilder; + + /// + /// Initializes a new instance of . + /// + /// The being used. + internal EntityTypeConfiguration(ODataModelBuilder modelBuilder) + : this(modelBuilder, new EntityTypeConfiguration(modelBuilder, typeof(TEntityType))) + { + } + + internal EntityTypeConfiguration(ODataModelBuilder modelBuilder, EntityTypeConfiguration configuration) + : base(configuration) + { + Contract.Assert(modelBuilder != null); + Contract.Assert(configuration != null); + + _modelBuilder = modelBuilder; + _configuration = configuration; + _collection = new EntityCollectionConfiguration(configuration); + } + + /// + /// Gets the base type of this entity type. + /// + public EntityTypeConfiguration BaseType + { + get + { + return _configuration.BaseType; + } + } + + /// + /// Gets the collection of of this entity type. + /// + public IEnumerable NavigationProperties + { + get { return _configuration.NavigationProperties; } + } + + /// + /// Used to access a Collection of Entities through which you can configure + /// actions and functions that are bindable to EntityCollections. + /// + public EntityCollectionConfiguration Collection + { + get { return _collection; } + } + + /// + /// Marks this entity type as abstract. + /// + /// Returns itself so that multiple calls can be chained. + public EntityTypeConfiguration Abstract() + { + _configuration.IsAbstract = true; + return this; + } + + /// + /// Marks this entity type as media type. + /// + /// Returns itself so that multiple calls can be chained. + public EntityTypeConfiguration MediaType() + { + _configuration.HasStream = true; + return this; + } + + /// + /// Sets the base type of this entity type to null meaning that this entity type + /// does not derive from anything. + /// + /// Returns itself so that multiple calls can be chained. + public EntityTypeConfiguration DerivesFromNothing() + { + _configuration.DerivesFromNothing(); + return this; + } + + /// + /// Sets the base type of this entity type. + /// + /// The base entity type. + /// Returns itself so that multiple calls can be chained. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "typeof(TBaseType) is used and getting it as a generic argument is cleaner")] + public EntityTypeConfiguration DerivesFrom() where TBaseType : class + { + EntityTypeConfiguration baseEntityType = _modelBuilder.EntityType(); + _configuration.DerivesFrom(baseEntityType._configuration); + return this; + } + + /// + /// Configures the key property(s) for this entity type. + /// + /// The type of key. + /// A lambda expression representing the property to be used as the primary key. For example, in C# t => t.Id and in Visual Basic .Net Function(t) t.Id. + /// Returns itself so that multiple calls can be chained. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")] + public EntityTypeConfiguration HasKey(Expression> keyDefinitionExpression) + { + ICollection properties = PropertySelectorVisitor.GetSelectedProperties(keyDefinitionExpression); + foreach (PropertyInfo property in properties) + { + _configuration.HasKey(property); + } + return this; + } + + /// + /// Create an Action that binds to this EntityType. + /// + /// The name of the action. + /// The ActionConfiguration to allow further configuration of the new Action. + public ActionConfiguration Action(string name) + { + Contract.Assert(_configuration != null && _configuration.ModelBuilder != null); + + ActionConfiguration action = _configuration.ModelBuilder.Action(name); + action.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, _configuration); + return action; + } + + /// + /// Create a Function that binds to this EntityType. + /// + /// The name of the function. + /// The FunctionConfiguration to allow further configuration of the new Function. + public FunctionConfiguration Function(string name) + { + Contract.Assert(_configuration != null && _configuration.ModelBuilder != null); + + FunctionConfiguration function = _configuration.ModelBuilder.Function(name); + function.SetBindingParameter(BindingParameterConfiguration.DefaultBindingParameterName, _configuration); + return function; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumMemberConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumMemberConfiguration.cs new file mode 100644 index 0000000..c9509c0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumMemberConfiguration.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents the configuration for an enum member of an enum type. + /// + public class EnumMemberConfiguration + { + private string _name; + + /// + /// Initializes a new instance of the class. + /// + /// The member of the enum type. + /// The declaring type of the member. + public EnumMemberConfiguration(Enum member, EnumTypeConfiguration declaringType) + { + if (member == null) + { + throw Error.ArgumentNull("member"); + } + + if (declaringType == null) + { + throw Error.ArgumentNull("declaringType"); + } + + Contract.Assert(member.GetType() == declaringType.ClrType); + + MemberInfo = member; + DeclaringType = declaringType; + AddedExplicitly = true; + _name = Enum.GetName(member.GetType(), member); + } + + /// + /// Gets or sets the name of the member. + /// + public string Name + { + get + { + return _name; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + + _name = value; + } + } + + /// + /// Gets the declaring type. + /// + public EnumTypeConfiguration DeclaringType { get; private set; } + + /// + /// Gets the mapping CLR . + /// + public Enum MemberInfo { get; private set; } + + /// + /// Gets or sets a value that is true if the member was added by the user; false if it was inferred through conventions. + /// + /// The default value is true + public bool AddedExplicitly { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumPropertyConfiguration.cs new file mode 100644 index 0000000..1d16f9f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumPropertyConfiguration.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Used to configure an enum property of an entity type or complex type. + /// This configuration functionality is exposed by the model builder Fluent API, see . + /// + public class EnumPropertyConfiguration : StructuralPropertyConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The property of the configuration. + /// The declaring type of the property. + public EnumPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + } + + /// + /// Gets the type of this property. + /// + public override PropertyKind Kind + { + get { return PropertyKind.Enum; } + } + + /// + /// Gets the backing CLR type of this property type. + /// + public override Type RelatedClrType + { + get { return PropertyInfo.PropertyType; } + } + + /// + /// Configures the property to be optional. + /// + /// Returns itself so that multiple calls can be chained. + public EnumPropertyConfiguration IsOptional() + { + OptionalProperty = true; + return this; + } + + /// + /// Configures the property to be required. + /// + /// Returns itself so that multiple calls can be chained. + public EnumPropertyConfiguration IsRequired() + { + OptionalProperty = false; + return this; + } + + /// + /// Configures the property to be used in concurrency checks. For OData this means to be part of the ETag. + /// + /// Returns itself so that multiple calls can be chained. + public EnumPropertyConfiguration IsConcurrencyToken() + { + ConcurrencyToken = true; + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfiguration.cs new file mode 100644 index 0000000..48279da --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfiguration.cs @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// + public class EnumTypeConfiguration : IEdmTypeConfiguration + { + private string _namespace; + private string _name; + private NullableEnumTypeConfiguration nullableEnumTypeConfiguration = null; + + /// + /// Initializes a new instance of the class. + /// + public EnumTypeConfiguration(ODataModelBuilder builder, Type clrType) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + if (clrType == null) + { + throw Error.ArgumentNull("clrType"); + } + + if (!TypeHelper.IsEnum(clrType)) + { + throw Error.Argument("clrType", SRResources.TypeCannotBeEnum, clrType.FullName); + } + + ClrType = clrType; + IsFlags = TypeHelper.AsMemberInfo(clrType).GetCustomAttributes(typeof(FlagsAttribute), false).Any(); + UnderlyingType = Enum.GetUnderlyingType(clrType); + ModelBuilder = builder; + _name = clrType.EdmName(); + + // Use the namespace if one was provided in builder by the user, otherwise fallback to CLR Namespace. + // If CLR Namespace is null we fallback to "Default" + // This can still be overriden by using DataContract attribute. + _namespace = builder.HasAssignedNamespace ? builder.Namespace : clrType.Namespace ?? builder.Namespace; + + ExplicitMembers = new Dictionary(); + RemovedMembers = new List(); + } + + /// + /// Gets the of this EDM type. + /// + public EdmTypeKind Kind + { + get + { + return EdmTypeKind.Enum; + } + } + + /// + /// Gets the of this enum type. + /// If it is true, a combined value is equivalent to the bitwise OR of the discrete values. + /// + [SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", Justification = "It is clearer to use IsFlags here and it is corresponding to the Flags attribute")] + public bool IsFlags { get; private set; } + + /// + /// Gets the backing CLR . + /// + public Type ClrType { get; private set; } + + /// + /// Gets this enum underlying . + /// + public Type UnderlyingType { get; private set; } + + /// + /// Gets the full name of this EDM type. + /// + public string FullName + { + get + { + return Namespace + "." + Name; + } + } + + /// + /// Gets or sets the namespace of this EDM type. + /// + public string Namespace + { + get + { + return _namespace; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + + _namespace = value; + AddedExplicitly = true; + } + } + + /// + /// Gets or sets the name of this EDM type. + /// + public string Name + { + get + { + return _name; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + + _name = value; + AddedExplicitly = true; + } + } + + /// + /// Gets all possible members(defined values) of this enum type, which will be added to the EDM model as edm:Member elements. + /// + public IEnumerable Members + { + get + { + return ExplicitMembers.Values; + } + } + + /// + /// Gets the members from the backing CLR type that are to be ignored on this enum type. + /// + public ReadOnlyCollection IgnoredMembers + { + get + { + return new ReadOnlyCollection(RemovedMembers); + } + } + + /// + /// Gets or sets a value that is true if the type's name or namespace was set by the user; + /// false if it was inferred through conventions. + /// + /// The default value is false. + public bool AddedExplicitly { get; set; } + + /// + /// Get the . + /// + public ODataModelBuilder ModelBuilder { get; private set; } + + /// + /// Gets the collection of explicitly removed members. + /// + protected internal IList RemovedMembers { get; private set; } + + /// + /// Gets the collection of explicitly added members. + /// + protected internal IDictionary ExplicitMembers { get; private set; } + + /// + /// Adds an enum member to this enum type. + /// + /// The member being added. + /// The so that the member can be configured further. + public EnumMemberConfiguration AddMember(Enum member) + { + if (member == null) + { + throw Error.ArgumentNull("member"); + } + + if (member.GetType() != ClrType) + { + throw Error.Argument("member", SRResources.PropertyDoesNotBelongToType, member.ToString(), ClrType.FullName); + } + + // Remove from the ignored members + if (RemovedMembers.Contains(member)) + { + RemovedMembers.Remove(member); + } + + EnumMemberConfiguration memberConfiguration; + if (ExplicitMembers.ContainsKey(member)) + { + memberConfiguration = ExplicitMembers[member]; + } + else + { + memberConfiguration = new EnumMemberConfiguration(member, this); + ExplicitMembers[member] = memberConfiguration; + } + + return memberConfiguration; + } + + /// + /// Removes the given member. + /// + /// The member being removed. + public void RemoveMember(Enum member) + { + if (member == null) + { + throw Error.ArgumentNull("member"); + } + + if (member.GetType() != ClrType) + { + throw Error.Argument("member", SRResources.PropertyDoesNotBelongToType, member.ToString(), ClrType.FullName); + } + + if (ExplicitMembers.ContainsKey(member)) + { + ExplicitMembers.Remove(member); + } + + if (!RemovedMembers.Contains(member)) + { + RemovedMembers.Add(member); + } + } + + internal NullableEnumTypeConfiguration GetNullableEnumTypeConfiguration() + { + if (nullableEnumTypeConfiguration == null) + { + nullableEnumTypeConfiguration = new NullableEnumTypeConfiguration(this); + } + + return nullableEnumTypeConfiguration; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfigurationOfTEnumType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfigurationOfTEnumType.cs new file mode 100644 index 0000000..881b392 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/EnumTypeConfigurationOfTEnumType.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// + public class EnumTypeConfiguration + { + private EnumTypeConfiguration _configuration; + + internal EnumTypeConfiguration(EnumTypeConfiguration configuration) + { + Contract.Assert(configuration != null); + Contract.Assert(configuration.ClrType == typeof(TEnumType)); + _configuration = configuration; + } + + /// + /// Gets the collection of EDM enum members that belong to this type. + /// + public IEnumerable Members + { + get { return _configuration.Members; } + } + + /// + /// Gets the full name of this EDM type. + /// + public string FullName + { + get + { + return _configuration.FullName; + } + } + + /// + /// Gets or sets the namespace of this EDM type. + /// + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Justification = "Follow StructuralTypeConfiguration's naming")] + public string Namespace + { + get + { + return _configuration.Namespace; + } + set + { + _configuration.Namespace = value; + } + } + + /// + /// Gets or sets the name of this EDM type. + /// + public string Name + { + get + { + return _configuration.Name; + } + set + { + _configuration.Name = value; + } + } + + /// + /// Excludes a member from the type. + /// + /// The member being excluded. + /// This method is used to exclude members from the enum type that would have been added by convention during model discovery. + public virtual void RemoveMember(TEnumType member) + { + _configuration.RemoveMember((Enum)(object)member); + } + + /// + /// Adds a required enum member to the EDM type. + /// + /// The member being added. + /// A configuration object that can be used to further configure the type. + public EnumMemberConfiguration Member(TEnumType enumMember) + { + return _configuration.AddMember((Enum)(object)enumMember); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/FunctionConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/FunctionConfiguration.cs new file mode 100644 index 0000000..124ddb7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/FunctionConfiguration.cs @@ -0,0 +1,261 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// FunctionConfiguration represents an OData function that you wish to expose via your service. + /// + /// FunctionConfigurations are exposed via $metadata as a element for bound function and element for unbound function. + /// + /// + public class FunctionConfiguration : OperationConfiguration + { + /// + /// Initializes a new instance of class. + /// + /// The ODataModelBuilder to which this FunctionConfiguration should be added. + /// The name of this FunctionConfiguration. + internal FunctionConfiguration(ODataModelBuilder builder, string name) : base(builder, name) + { + // By default, function import is included in service document + IncludeInServiceDocument = true; + } + + /// + public override OperationKind Kind + { + get { return OperationKind.Function; } + } + + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Copies existing spelling used in EdmLib.")] + public new bool IsComposable + { + get { return base.IsComposable; } + set { base.IsComposable = value; } + } + + /// + public override bool IsSideEffecting + { + get { return false; } + } + + /// + /// Gets/Sets a value indicating whether the function is supported in $filter. + /// + public bool SupportedInFilter { get; set; } + + /// + /// Gets/Sets a value indicating whether the function is supported in $orderby. + /// + public bool SupportedInOrderBy { get; set; } + + /// + /// Gets/Set a value indicating whether the operation is included in service document or not. + /// Meaningful only for function imports; ignore for bound functions. + /// + public bool IncludeInServiceDocument { get; set; } + + /// + /// Register a factory that creates functions links. + /// + public FunctionConfiguration HasFunctionLink(Func functionLinkFactory, bool followsConventions) + { + if (functionLinkFactory == null) + { + throw Error.ArgumentNull("functionLinkFactory"); + } + + if (!IsBindable || BindingParameter.TypeConfiguration.Kind != EdmTypeKind.Entity) + { + throw Error.InvalidOperation(SRResources.HasFunctionLinkRequiresBindToEntity, Name); + } + + OperationLinkBuilder = new OperationLinkBuilder(functionLinkFactory, followsConventions); + FollowsConventions = followsConventions; + return this; + } + + /// + /// Retrieves the currently registered function link factory. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")] + public Func GetFunctionLink() + { + if (OperationLinkBuilder == null) + { + return null; + } + + return OperationLinkBuilder.LinkFactory; + } + + /// + /// Register a factory that creates feed functions links. + /// + public FunctionConfiguration HasFeedFunctionLink(Func functionLinkFactory, bool followsConventions) + { + if (functionLinkFactory == null) + { + throw Error.ArgumentNull("functionLinkFactory"); + } + + if (!IsBindable || + BindingParameter.TypeConfiguration.Kind != EdmTypeKind.Collection || + ((CollectionTypeConfiguration)BindingParameter.TypeConfiguration).ElementType.Kind != EdmTypeKind.Entity) + { + throw Error.InvalidOperation(SRResources.HasFunctionLinkRequiresBindToCollectionOfEntity, Name); + } + + OperationLinkBuilder = new OperationLinkBuilder(functionLinkFactory, followsConventions); + FollowsConventions = followsConventions; + return this; + } + + /// + /// Retrieves the currently registered feed function link factory. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Consistent with EF Has/Get pattern")] + public Func GetFeedFunctionLink() + { + if (OperationLinkBuilder == null) + { + return null; + } + + return OperationLinkBuilder.FeedLinkFactory; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entitySetName which contains the return EntityType instance + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration ReturnsFromEntitySet(string entitySetName) where TEntityType : class + { + ReturnsFromEntitySetImplementation(entitySetName); + return this; + } + + /// + /// Sets the return type to a collection of EntityType instances. + /// + /// The type that is an EntityType + /// The entitySetName which contains the returned EntityType instances + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration ReturnsCollectionFromEntitySet(string entitySetName) where TElementEntityType : class + { + ReturnsCollectionFromEntitySetImplementation(entitySetName); + return this; + } + + /// + /// Established the return type of the Function. + /// Used when the return type is a single Primitive or ComplexType. + /// + public FunctionConfiguration Returns(Type clrReturnType) + { + if (clrReturnType == null) + { + throw Error.ArgumentNull("clrReturnType"); + } + + ReturnsImplementation(clrReturnType); + return this; + } + + /// + /// Established the return type of the Function. + /// Used when the return type is a single Primitive or ComplexType. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration Returns() + { + return this.Returns(typeof(TReturnType)); + } + + /// + /// Establishes the return type of the Function + /// Used when the return type is a collection of either Primitive or ComplexTypes. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration ReturnsCollection() + { + ReturnsCollectionImplementation(); + return this; + } + + /// + /// Specifies the bindingParameter name, type and whether it is alwaysBindable, use only if the Function "isBindable". + /// + public FunctionConfiguration SetBindingParameter(string name, IEdmTypeConfiguration bindingParameterType) + { + SetBindingParameterImplementation(name, bindingParameterType); + return this; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the return EntityType instance + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration ReturnsEntityViaEntitySetPath(string entitySetPath) where TEntityType : class + { + if (String.IsNullOrEmpty(entitySetPath)) + { + throw Error.ArgumentNull("entitySetPath"); + } + ReturnsEntityViaEntitySetPathImplementation(entitySetPath.Split('/')); + return this; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the return EntityType instance + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration ReturnsEntityViaEntitySetPath(params string[] entitySetPath) where TEntityType : class + { + ReturnsEntityViaEntitySetPathImplementation(entitySetPath); + return this; + } + + /// + /// Sets the return type to a collection of EntityType instances. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the returned EntityType instances + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration ReturnsCollectionViaEntitySetPath(string entitySetPath) where TElementEntityType : class + { + if (String.IsNullOrEmpty(entitySetPath)) + { + throw Error.ArgumentNull("entitySetPath"); + } + ReturnsCollectionViaEntitySetPathImplementation(entitySetPath.Split('/')); + return this; + } + + /// + /// Sets the return type to a collection of EntityType instances. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the returned EntityType instances + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public FunctionConfiguration ReturnsCollectionViaEntitySetPath(params string[] entitySetPath) where TElementEntityType : class + { + ReturnsCollectionViaEntitySetPathImplementation(entitySetPath); + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/IEdmTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/IEdmTypeConfiguration.cs new file mode 100644 index 0000000..7f76ce1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/IEdmTypeConfiguration.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an EdmType + /// + public interface IEdmTypeConfiguration + { + /// + /// The CLR type associated with the EdmType. + /// + Type ClrType { get; } + + /// + /// The fullname (including namespace) of the EdmType. + /// + string FullName { get; } + + /// + /// The namespace of the EdmType. + /// + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Justification = "Namespace matches the EF naming scheme")] + string Namespace { get; } + + /// + /// The name of the EdmType. + /// + string Name { get; } + + /// + /// The kind of the EdmType. + /// Examples include EntityType, ComplexType, PrimitiveType, CollectionType, EnumType. + /// + EdmTypeKind Kind { get; } + + /// + /// The ODataModelBuilder used to create this IEdmType. + /// + ODataModelBuilder ModelBuilder { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LengthPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LengthPropertyConfiguration.cs new file mode 100644 index 0000000..2e64646 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LengthPropertyConfiguration.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Used to configure a string or binary property length of an entity type or complex type. + /// This configuration functionality is exposed by the model builder Fluent API, see . + /// + public class LengthPropertyConfiguration : PrimitivePropertyConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The declaring EDM type of the property. + public LengthPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + } + + /// + /// Gets or sets the maximum length of the value of the property on a type instance. + /// + public int? MaxLength { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs new file mode 100644 index 0000000..d1d50e9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs @@ -0,0 +1,543 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Builder.Conventions; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Contains helper methods for generating OData links that follow OData URL conventions. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class LinkGenerationHelpers + { + /// + /// Generates a self link following the OData URL conventions for the entity represented by . + /// + /// The representing the entity for which the self link needs to be generated. + /// Represents whether the generated link should have a cast segment representing a type cast. + /// The self link following the OData URL conventions. + public static Uri GenerateSelfLink(this ResourceContext resourceContext, bool includeCast) + { + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + + if (resourceContext.InternalUrlHelper == null) + { + throw Error.Argument("resourceContext", SRResources.UrlHelperNull, typeof(ResourceContext).Name); + } + + IList idLinkPathSegments = resourceContext.GenerateBaseODataPathSegments(); + + bool isSameType = resourceContext.StructuredType == resourceContext.NavigationSource.EntityType(); + if (includeCast && !isSameType) + { + idLinkPathSegments.Add(new TypeSegment(resourceContext.StructuredType, navigationSource: null)); + } + + string idLink = resourceContext.InternalUrlHelper.CreateODataLink(idLinkPathSegments); + if (idLink == null) + { + return null; + } + + return new Uri(idLink); + } + + /// + /// Generates a navigation link following the OData URL conventions for the entity represented by and the given + /// navigation property. + /// + /// The representing the entity for which the navigation link needs to be generated. + /// The EDM navigation property. + /// Represents whether the generated link should have a cast segment representing a type cast. + /// The navigation link following the OData URL conventions. + public static Uri GenerateNavigationPropertyLink(this ResourceContext resourceContext, + IEdmNavigationProperty navigationProperty, bool includeCast) + { + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + if (resourceContext.InternalUrlHelper == null) + { + throw Error.Argument("resourceContext", SRResources.UrlHelperNull, typeof(ResourceContext).Name); + } + + IList navigationPathSegments = resourceContext.GenerateBaseODataPathSegments(); + + if (includeCast) + { + navigationPathSegments.Add(new TypeSegment(resourceContext.StructuredType, navigationSource: null)); + } + + navigationPathSegments.Add(new NavigationPropertySegment(navigationProperty, navigationSource: null)); + + string link = resourceContext.InternalUrlHelper.CreateODataLink(navigationPathSegments); + if (link == null) + { + return null; + } + + return new Uri(link); + } + + /// + /// Generates an action link following the OData URL conventions for the action and bound to the + /// collection of entity represented by . + /// + /// The representing the feed for which the action link needs to be generated. + /// The action for which the action link needs to be generated. + /// The generated action link following OData URL conventions. + public static Uri GenerateActionLink(this ResourceSetContext resourceSetContext, IEdmOperation action) + { + if (resourceSetContext == null) + { + throw Error.ArgumentNull("resourceSetContext"); + } + + if (action == null) + { + throw Error.ArgumentNull("action"); + } + + IEdmOperationParameter bindingParameter = action.Parameters.FirstOrDefault(); + if (bindingParameter == null || + !bindingParameter.Type.IsCollection() || + !((IEdmCollectionType)bindingParameter.Type.Definition).ElementType.IsEntity()) + { + throw Error.Argument("action", SRResources.ActionNotBoundToCollectionOfEntity, action.Name); + } + + return GenerateActionLink(resourceSetContext, bindingParameter.Type, action); + } + + internal static Uri GenerateActionLink(this ResourceSetContext feedContext, string bindingParameterType, + string actionName) + { + Contract.Assert(feedContext != null); + + if (feedContext.EntitySetBase is IEdmContainedEntitySet) + { + return null; + } + + if (feedContext.EdmModel == null) + { + return null; + } + + IEdmModel model = feedContext.EdmModel; + string elementType = DeserializationHelpers.GetCollectionElementTypeName(bindingParameterType, + isNested: false); + Contract.Assert(elementType != null); + + IEdmTypeReference typeReference = model.FindDeclaredType(elementType).ToEdmTypeReference(true); + IEdmTypeReference collection = new EdmCollectionTypeReference(new EdmCollectionType(typeReference)); + + IEdmOperation operation = model.FindDeclaredOperations(actionName).First(); + return feedContext.GenerateActionLink(collection, operation); + } + + internal static Uri GenerateActionLink(this ResourceSetContext resourceSetContext, IEdmTypeReference bindingParameterType, + IEdmOperation action) + { + Contract.Assert(resourceSetContext != null); + + if (resourceSetContext.EntitySetBase is IEdmContainedEntitySet) + { + return null; + } + + IList actionPathSegments = new List(); + resourceSetContext.GenerateBaseODataPathSegmentsForFeed(actionPathSegments); + + // generate link with cast if the navigation source doesn't match the type the action is bound to. + if (resourceSetContext.EntitySetBase.Type.FullTypeName() != bindingParameterType.FullName()) + { + actionPathSegments.Add(new TypeSegment(bindingParameterType.Definition, resourceSetContext.EntitySetBase)); + } + + OperationSegment operationSegment = new OperationSegment(action, entitySet: null); + actionPathSegments.Add(operationSegment); + + string actionLink = resourceSetContext.InternalUrlHelper.CreateODataLink(actionPathSegments); + return actionLink == null ? null : new Uri(actionLink); + } + + /// + /// Generates a function link following the OData URL conventions for the function and bound to the + /// collection of entity represented by . + /// + /// The representing the feed for which the function link needs to be generated. + /// The function for which the function link needs to be generated. + /// The generated function link following OData URL conventions. + public static Uri GenerateFunctionLink(this ResourceSetContext resourceSetContext, IEdmOperation function) + { + if (resourceSetContext == null) + { + throw Error.ArgumentNull("resourceSetContext"); + } + + if (function == null) + { + throw Error.ArgumentNull("function"); + } + + IEdmOperationParameter bindingParameter = function.Parameters.FirstOrDefault(); + if (bindingParameter == null || + !bindingParameter.Type.IsCollection() || + !((IEdmCollectionType)bindingParameter.Type.Definition).ElementType.IsEntity()) + { + throw Error.Argument("function", SRResources.FunctionNotBoundToCollectionOfEntity, function.Name); + } + + return GenerateFunctionLink(resourceSetContext, bindingParameter.Type, function, + function.Parameters.Select(p => p.Name)); + } + + internal static Uri GenerateFunctionLink(this ResourceSetContext resourceSetContext, IEdmTypeReference bindingParameterType, + IEdmOperation functionImport, IEnumerable parameterNames) + { + Contract.Assert(resourceSetContext != null); + + if (resourceSetContext.EntitySetBase is IEdmContainedEntitySet) + { + return null; + } + + IList functionPathSegments = new List(); + resourceSetContext.GenerateBaseODataPathSegmentsForFeed(functionPathSegments); + + // generate link with cast if the navigation source type doesn't match the entity type the function is bound to. + if (resourceSetContext.EntitySetBase.Type.FullTypeName() != bindingParameterType.Definition.FullTypeName()) + { + functionPathSegments.Add(new TypeSegment(bindingParameterType.Definition, null)); + } + + IList parameters = new List(); + // skip the binding parameter + foreach (string param in parameterNames.Skip(1)) + { + string value = "@" + param; + parameters.Add(new OperationSegmentParameter(param, new ConstantNode(value, value))); + } + + OperationSegment segment = new OperationSegment(new[] { functionImport }, parameters, null); + functionPathSegments.Add(segment); + + string functionLink = resourceSetContext.InternalUrlHelper.CreateODataLink(functionPathSegments); + return functionLink == null ? null : new Uri(functionLink); + } + + internal static Uri GenerateFunctionLink(this ResourceSetContext feedContext, string bindingParameterType, + string functionName, IEnumerable parameterNames) + { + Contract.Assert(feedContext != null); + + if (feedContext.EntitySetBase is IEdmContainedEntitySet) + { + return null; + } + + if (feedContext.EdmModel == null) + { + return null; + } + + IEdmModel model = feedContext.EdmModel; + + string elementType = DeserializationHelpers.GetCollectionElementTypeName(bindingParameterType, + isNested: false); + Contract.Assert(elementType != null); + + IEdmTypeReference typeReference = model.FindDeclaredType(elementType).ToEdmTypeReference(true); + IEdmTypeReference collection = new EdmCollectionTypeReference(new EdmCollectionType(typeReference)); + IEdmOperation operation = model.FindDeclaredOperations(functionName).First(); + return feedContext.GenerateFunctionLink(collection, operation, parameterNames); + } + + /// + /// Generates an action link following the OData URL conventions for the action and bound to the entity + /// represented by . + /// + /// The representing the entity for which the action link needs to be generated. + /// The action for which the action link needs to be generated. + /// The generated action link following OData URL conventions. + public static Uri GenerateActionLink(this ResourceContext resourceContext, IEdmOperation action) + { + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + if (action == null) + { + throw Error.ArgumentNull("action"); + } + + IEdmOperationParameter bindingParameter = action.Parameters.FirstOrDefault(); + if (bindingParameter == null || !bindingParameter.Type.IsEntity()) + { + throw Error.Argument("action", SRResources.ActionNotBoundToEntity, action.Name); + } + + return GenerateActionLink(resourceContext, bindingParameter.Type, action); + } + + internal static Uri GenerateActionLink(this ResourceContext resourceContext, + IEdmTypeReference bindingParameterType, IEdmOperation action) + { + Contract.Assert(resourceContext != null); + if (resourceContext.NavigationSource is IEdmContainedEntitySet) + { + return null; + } + + IList actionPathSegments = resourceContext.GenerateBaseODataPathSegments(); + + // generate link with cast if the navigation source doesn't match the entity type the action is bound to. + if (resourceContext.NavigationSource.EntityType() != bindingParameterType.Definition) + { + actionPathSegments.Add(new TypeSegment((IEdmEntityType)bindingParameterType.Definition, null)); + // entity set can be null + } + + OperationSegment operationSegment = new OperationSegment(new[] { action }, null); + actionPathSegments.Add(operationSegment); + + string actionLink = resourceContext.InternalUrlHelper.CreateODataLink(actionPathSegments); + return actionLink == null ? null : new Uri(actionLink); + } + + internal static Uri GenerateActionLink(this ResourceContext resourceContext, string bindingParameterType, + string actionName) + { + Contract.Assert(resourceContext != null); + if (resourceContext.NavigationSource is IEdmContainedEntitySet) + { + return null; + } + + if (resourceContext.EdmModel == null) + { + return null; + } + + IEdmModel model = resourceContext.EdmModel; + IEdmTypeReference typeReference = model.FindDeclaredType(bindingParameterType).ToEdmTypeReference(true); + IEdmOperation operation = model.FindDeclaredOperations(actionName).First(); + return resourceContext.GenerateActionLink(typeReference, operation); + } + + /// + /// Generates an function link following the OData URL conventions for the function and bound to the entity + /// represented by . + /// + /// The representing the entity for which the function link needs to be generated. + /// The function for which the function link needs to be generated. + /// The generated function link following OData URL conventions. + public static Uri GenerateFunctionLink(this ResourceContext resourceContext, IEdmOperation function) + { + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + if (function == null) + { + throw Error.ArgumentNull("function"); + } + + IEdmOperationParameter bindingParameter = function.Parameters.FirstOrDefault(); + if (bindingParameter == null || !bindingParameter.Type.IsEntity()) + { + throw Error.Argument("function", SRResources.FunctionNotBoundToEntity, function.Name); + } + + return GenerateFunctionLink(resourceContext, bindingParameter.Type.FullName(), function.FullName(), + function.Parameters.Select(p => p.Name)); + } + + internal static Uri GenerateFunctionLink(this ResourceContext resourceContext, + IEdmTypeReference bindingParameterType, IEdmOperation function, + IEnumerable parameterNames) + { + IList functionPathSegments = resourceContext.GenerateBaseODataPathSegments(); + + // generate link with cast if the navigation source type doesn't match the entity type the function is bound to. + if (resourceContext.NavigationSource.EntityType() != bindingParameterType.Definition) + { + functionPathSegments.Add(new TypeSegment(bindingParameterType.Definition, null)); + } + + IList parameters = new List(); + // skip the binding parameter + foreach (string param in parameterNames.Skip(1)) + { + string value = "@" + param; + parameters.Add(new OperationSegmentParameter(param, new ConstantNode(value, value))); + } + + OperationSegment segment = new OperationSegment(new[] { function }, parameters, null); + functionPathSegments.Add(segment); + + string functionLink = resourceContext.InternalUrlHelper.CreateODataLink(functionPathSegments); + return functionLink == null ? null : new Uri(functionLink); + } + + internal static Uri GenerateFunctionLink(this ResourceContext resourceContext, string bindingParameterType, + string functionName, IEnumerable parameterNames) + { + Contract.Assert(resourceContext.EdmModel != null); + + if (resourceContext.EdmModel == null) + { + return null; + } + + IEdmModel model = resourceContext.EdmModel; + IEdmTypeReference typeReference = model.FindDeclaredType(bindingParameterType).ToEdmTypeReference(true); + IEdmOperation operation = model.FindDeclaredOperations(functionName).First(); + return resourceContext.GenerateFunctionLink(typeReference, operation, parameterNames); + } + + internal static IList GenerateBaseODataPathSegments(this ResourceContext resourceContext) + { + IList odataPath = new List(); + + if (resourceContext.NavigationSource.NavigationSourceKind() == EdmNavigationSourceKind.Singleton) + { + // Per the OData V4 specification, a singleton is expected to be a child of the entity container, and + // as a result we can make the assumption that it is the only segment in the generated path. + odataPath.Add(new SingletonSegment((IEdmSingleton)resourceContext.NavigationSource)); + } + else + { + resourceContext.GenerateBaseODataPathSegmentsForEntity(odataPath); + } + + return odataPath; + } + + private static void GenerateBaseODataPathSegmentsForNonSingletons( + ODataPath path, + IEdmNavigationSource navigationSource, + IList odataPath) + { + // If the navigation is not a singleton we need to walk all of the path segments to generate a + // contextually accurate URI. + bool segmentFound = false; + bool containedFound = false; + if (path != null) + { + var segments = path.Segments; + int length = segments.Count; + int previousNavigationPathIndex = -1; + for (int i = 0; i < length; i++) + { + ODataPathSegment pathSegment = segments[i]; + IEdmNavigationSource currentNavigationSource = null; + + var entitySetPathSegment = pathSegment as EntitySetSegment; + if (entitySetPathSegment != null) + { + currentNavigationSource = entitySetPathSegment.EntitySet; + } + + var navigationPathSegment = pathSegment as NavigationPropertySegment; + if (navigationPathSegment != null) + { + currentNavigationSource = navigationPathSegment.NavigationSource; + } + if (containedFound) + { + odataPath.Add(pathSegment); + } + else + { + if (navigationPathSegment != null && + navigationPathSegment.NavigationProperty.ContainsTarget) + { + containedFound = true; + //The path should have the last non-contained navigation property + if (previousNavigationPathIndex != -1) + { + for (int j = previousNavigationPathIndex; j <= i; j++) + { + odataPath.Add(segments[j]); + } + } + } + } + + // If we've found our target navigation in the path that means we've correctly populated the + // segments up to the navigation and we can ignore the remaining segments. + if (currentNavigationSource != null) + { + previousNavigationPathIndex = i; + if (currentNavigationSource == navigationSource) + { + segmentFound = true; + break; + } + } + } + } + + if (!segmentFound || !containedFound) + { + // If the target navigation was not found in the current path that means we lack any context that + // would suggest a scenario other than directly accessing an entity set, so we must assume that's + // the case. + odataPath.Clear(); + + IEdmContainedEntitySet containmnent = navigationSource as IEdmContainedEntitySet; + if (containmnent != null) + { + EdmEntityContainer container = new EdmEntityContainer("NS", "Default"); + IEdmEntitySet entitySet = new EdmEntitySet(container, navigationSource.Name, + navigationSource.EntityType()); + odataPath.Add(new EntitySetSegment(entitySet)); + } + else + { + odataPath.Add(new EntitySetSegment((IEdmEntitySet)navigationSource)); + } + } + } + + private static void GenerateBaseODataPathSegmentsForEntity( + this ResourceContext resourceContext, + IList odataPath) + { + // If the navigation is not a singleton we need to walk all of the path segments to generate a + // contextually accurate URI. + GenerateBaseODataPathSegmentsForNonSingletons( + resourceContext.SerializerContext.Path, resourceContext.NavigationSource, odataPath); + + odataPath.Add(new KeySegment(ConventionsHelpers.GetEntityKey(resourceContext), resourceContext.StructuredType as IEdmEntityType, + null)); + } + + private static void GenerateBaseODataPathSegmentsForFeed( + this ResourceSetContext feedContext, + IList odataPath) + { + GenerateBaseODataPathSegmentsForNonSingletons(feedContext.InternalRequest.Context.Path, + feedContext.EntitySetBase, + odataPath); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LowerCamelCaser.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LowerCamelCaser.cs new file mode 100644 index 0000000..9bd1b4e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/LowerCamelCaser.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Default lower camel caser to resolve property names for . + /// The rule is to convert the leading upper case characters to lower case, + /// until a character, which is not the first character and is followed by a non-upper case character, is met. + /// id => id, ID => id, MyName => myName, IOStream => ioStream, MyID => myid, yourID => yourID + /// + public class LowerCamelCaser + { + private readonly NameResolverOptions _options; + + /// + /// Initializes a new instance of the class. + /// + public LowerCamelCaser() + : this(NameResolverOptions.ProcessReflectedPropertyNames | + NameResolverOptions.ProcessDataMemberAttributePropertyNames | + NameResolverOptions.ProcessExplicitPropertyNames) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Name resolver options for camelizing. + public LowerCamelCaser(NameResolverOptions options) + { + this._options = options; + } + + /// + /// Applies lower camel case. + /// + /// The to be applied on. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")] + public void ApplyLowerCamelCase(ODataConventionModelBuilder builder) + { + foreach (StructuralTypeConfiguration typeConfiguration in builder.StructuralTypes) + { + foreach (PropertyConfiguration property in typeConfiguration.Properties) + { + if (ShouldApplyLowerCamelCase(property)) + { + property.Name = ToLowerCamelCase(property.Name); + } + } + } + } + + /// + /// Converts a to lower camel case. + /// + /// The name to be converted with lower camel case. + /// The converted name. + public virtual string ToLowerCamelCase(string name) + { + if (String.IsNullOrEmpty(name)) + { + return name; + } + + if (!Char.IsUpper(name[0])) + { + return name; + } + + StringBuilder stringBuilder = new StringBuilder(); + + for (int index = 0; index < name.Length; index++) + { + if (index != 0 && index + 1 < name.Length && !Char.IsUpper(name[index + 1])) + { + stringBuilder.Append(name.Substring(index)); + break; + } + else + { + stringBuilder.Append(Char.ToLowerInvariant(name[index])); + } + } + + return stringBuilder.ToString(); + } + + private bool ShouldApplyLowerCamelCase(PropertyConfiguration property) + { + if (property.AddedExplicitly) + { + return _options.HasFlag(NameResolverOptions.ProcessExplicitPropertyNames); + } + else + { + DataMemberAttribute attribute = property.PropertyInfo.GetCustomAttribute(inherit: false); + + if (attribute != null && !String.IsNullOrWhiteSpace(attribute.Name)) + { + return _options.HasFlag(NameResolverOptions.ProcessDataMemberAttributePropertyNames); + } + + return _options.HasFlag(NameResolverOptions.ProcessReflectedPropertyNames); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/MediaTypeAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/MediaTypeAttribute.cs new file mode 100644 index 0000000..fb91b33 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/MediaTypeAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Marks this entity type as media type. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class MediaTypeAttribute : Attribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NameResolverOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NameResolverOptions.cs new file mode 100644 index 0000000..41305f8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NameResolverOptions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Options for resolving names. + /// + [Flags] + public enum NameResolverOptions + { + /// + /// Process reflected property names. + /// + ProcessReflectedPropertyNames = 1, + + /// + /// Process property names in DataMemberAttribute + /// such as [DataMember(Name = "DataMemberCustomerName")]. + /// + ProcessDataMemberAttributePropertyNames = 2, + + /// + /// Process explicit property names + /// such as entityTypeConfiguration.Property(e => e.Key).Name="Id". + /// + ProcessExplicitPropertyNames = 4 + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationLinkBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationLinkBuilder.cs new file mode 100644 index 0000000..1b125e4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationLinkBuilder.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Encapsulates a navigation link factory and whether the link factory follows conventions or not. + /// + public class NavigationLinkBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The navigation link factory for creating navigation links. + /// Represents whether this factory follows OData conventions or not. + public NavigationLinkBuilder(Func navigationLinkFactory, bool followsConventions) + { + if (navigationLinkFactory == null) + { + throw Error.ArgumentNull("navigationLinkFactory"); + } + + Factory = navigationLinkFactory; + FollowsConventions = followsConventions; + } + + /// + /// Gets the navigation link factory for creating navigation links. + /// + public Func Factory { get; private set; } + + /// + /// Gets a value representing whether this factory follows OData conventions or not. + /// + public bool FollowsConventions { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingConfiguration.cs new file mode 100644 index 0000000..0124591 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingConfiguration.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Used to configure the binding for a navigation property for a navigation source. + /// This configuration functionality is exposed by the model builder Fluent API, see . + /// + public class NavigationPropertyBindingConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The navigation property for the binding. + /// The target navigation source of the binding. + public NavigationPropertyBindingConfiguration(NavigationPropertyConfiguration navigationProperty, + NavigationSourceConfiguration navigationSource) + : this(navigationProperty, navigationSource, new MemberInfo[] { navigationProperty.PropertyInfo }) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The navigation property for the binding. + /// The target navigation source of the binding. + /// The path of current binding. + public NavigationPropertyBindingConfiguration(NavigationPropertyConfiguration navigationProperty, + NavigationSourceConfiguration navigationSource, IList path) + { + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + + if (navigationSource == null) + { + throw Error.ArgumentNull("navigationSource"); + } + + if (path == null) + { + throw Error.ArgumentNull("path"); + } + + NavigationProperty = navigationProperty; + TargetNavigationSource = navigationSource; + Path = path; + } + + /// + /// Gets the navigation property of the binding. + /// + public NavigationPropertyConfiguration NavigationProperty { get; private set; } + + /// + /// Gets the target navigation source of the binding. + /// + public NavigationSourceConfiguration TargetNavigationSource { get; private set; } + + /// + /// Gets the path of current binding. + /// + public IList Path { get; private set; } + + /// + /// Gets the path of current binding, like "A.B/C/D.E". + /// + public string BindingPath + { + get { return Path.ConvertBindingPath(); } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingOption.cs new file mode 100644 index 0000000..3962997 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyBindingOption.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Options for navigation property binding. + /// + public enum NavigationPropertyBindingOption + { + /// + /// Default behavior. It won't auto create the navigation property binding if multiple navigation sources. + /// + None = 0, + + /// + /// Auto binding behavior. It will automatically pick the first one of target navigation sources. + /// + Auto = 1, + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs new file mode 100644 index 0000000..be44a28 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyConfiguration.cs @@ -0,0 +1,298 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents the configuration for a navigation property of a structural type. + /// + /// This configuration functionality is exposed by the model builder Fluent API, see . + public class NavigationPropertyConfiguration : PropertyConfiguration + { + private readonly Type _relatedType; + private readonly IDictionary _referentialConstraint = + new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// The backing CLR property. + /// The . + /// The declaring structural type. + public NavigationPropertyConfiguration(PropertyInfo property, EdmMultiplicity multiplicity, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + if (property == null) + { + throw Error.ArgumentNull("property"); + } + + Multiplicity = multiplicity; + + _relatedType = property.PropertyType; + if (multiplicity == EdmMultiplicity.Many) + { + Type elementType; + if (!TypeHelper.IsCollection(_relatedType, out elementType)) + { + throw Error.Argument("property", SRResources.ManyToManyNavigationPropertyMustReturnCollection, property.Name, TypeHelper.GetReflectedType(property).Name); + } + + _relatedType = elementType; + } + + OnDeleteAction = EdmOnDeleteAction.None; + } + + /// + /// The partner relationship of this navigation property. + /// + public NavigationPropertyConfiguration Partner { get; internal set; } + + /// + /// Gets the of this navigation property. + /// + public EdmMultiplicity Multiplicity { get; private set; } + + /// + /// Gets whether this navigation property is a containment, default to false. + /// + public bool ContainsTarget { get; private set; } + + /// + /// Gets the backing CLR type of this property type. + /// + public override Type RelatedClrType + { + get { return _relatedType; } + } + + /// + /// Gets the of this property. + /// + public override PropertyKind Kind + { + get { return PropertyKind.Navigation; } + } + + /// + /// Gets or sets the delete action for this navigation property. + /// + public EdmOnDeleteAction OnDeleteAction { get; set; } + + /// + /// Gets the foreign keys in the referential constraint of this navigation property. + /// + public IEnumerable DependentProperties + { + get { return _referentialConstraint.Keys; } + } + + /// + /// Gets the target keys in the referential constraint of this navigation property. + /// + public IEnumerable PrincipalProperties + { + get { return _referentialConstraint.Values; } + } + + /// + /// Marks the navigation property as optional. + /// + public NavigationPropertyConfiguration Optional() + { + if (Multiplicity == EdmMultiplicity.Many) + { + throw Error.InvalidOperation(SRResources.ManyNavigationPropertiesCannotBeChanged, Name); + } + + Multiplicity = EdmMultiplicity.ZeroOrOne; + return this; + } + + /// + /// Marks the navigation property as required. + /// + public NavigationPropertyConfiguration Required() + { + if (Multiplicity == EdmMultiplicity.Many) + { + throw Error.InvalidOperation(SRResources.ManyNavigationPropertiesCannotBeChanged, Name); + } + + Multiplicity = EdmMultiplicity.One; + return this; + } + + /// + /// Marks the navigation property as containment. + /// + public NavigationPropertyConfiguration Contained() + { + ContainsTarget = true; + return this; + } + + /// + /// Marks the navigation property as non-contained. + /// + public NavigationPropertyConfiguration NonContained() + { + ContainsTarget = false; + return this; + } + + /// + /// Marks the navigation property is automatic expanded. + /// + /// If set to true then automatic expand will be disabled + /// if there is a $select specify by client. + /// + public NavigationPropertyConfiguration AutomaticallyExpand(bool disableWhenSelectIsPresent) + { + AutoExpand = true; + DisableAutoExpandWhenSelectIsPresent = disableWhenSelectIsPresent; + return this; + } + + /// + /// Configures cascade delete to be on for the navigation property. + /// + public NavigationPropertyConfiguration CascadeOnDelete() + { + CascadeOnDelete(cascade: true); + return this; + } + + /// + /// Configures whether or not cascade delete is on for the navigation property. + /// + /// true indicates delete should also remove the associated items; + /// false indicates no additional action on delete. + public NavigationPropertyConfiguration CascadeOnDelete(bool cascade) + { + OnDeleteAction = cascade ? EdmOnDeleteAction.Cascade : EdmOnDeleteAction.None; + return this; + } + + /// + /// Configures the referential constraint with the specified + /// and . + /// + /// The dependent property info for the referential constraint. + /// The principal property info for the referential constraint. + public NavigationPropertyConfiguration HasConstraint(PropertyInfo dependentPropertyInfo, + PropertyInfo principalPropertyInfo) + { + return HasConstraint(new KeyValuePair(dependentPropertyInfo, + principalPropertyInfo)); + } + + /// + /// Configures the referential constraint with the dependent and principal property pair. + /// + /// The dependent and principal property pair. + public NavigationPropertyConfiguration HasConstraint(KeyValuePair constraint) + { + if (constraint.Key == null) + { + throw Error.ArgumentNull("dependentPropertyInfo"); + } + + if (constraint.Value == null) + { + throw Error.ArgumentNull("principalPropertyInfo"); + } + + if (Multiplicity == EdmMultiplicity.Many) + { + throw Error.NotSupported(SRResources.ReferentialConstraintOnManyNavigationPropertyNotSupported, + Name, DeclaringType.ClrType.FullName); + } + + if (ValidateConstraint(constraint)) + { + return this; + } + + EntityTypeConfiguration principalEntity = DeclaringType.ModelBuilder.StructuralTypes + .OfType().FirstOrDefault(e => e.ClrType == RelatedClrType); + Contract.Assert(principalEntity != null); + + PrimitivePropertyConfiguration principal = principalEntity.AddProperty(constraint.Value); + PrimitivePropertyConfiguration dependent = DeclaringType.AddProperty(constraint.Key); + + // If the navigation property on which the referential constraint is defined or the principal property + // is nullable, then the dependent property MUST be nullable. + if (Multiplicity == EdmMultiplicity.ZeroOrOne || principal.OptionalProperty) + { + dependent.OptionalProperty = true; + } + + // If both the navigation property and the principal property are not nullable, + // then the dependent property MUST be marked with the Nullable="false" attribute value. + if (Multiplicity == EdmMultiplicity.One && !principal.OptionalProperty) + { + dependent.OptionalProperty = false; + } + + _referentialConstraint.Add(constraint); + return this; + } + + private bool ValidateConstraint(KeyValuePair constraint) + { + if (_referentialConstraint.Contains(constraint)) + { + return true; + } + + PropertyInfo value; + if (_referentialConstraint.TryGetValue(constraint.Key, out value)) + { + throw Error.InvalidOperation(SRResources.ReferentialConstraintAlreadyConfigured, "dependent", + constraint.Key.Name, "principal", value.Name); + } + + if (PrincipalProperties.Any(p => p == constraint.Value)) + { + PropertyInfo foundDependent = _referentialConstraint.First(r => r.Value == constraint.Value).Key; + + throw Error.InvalidOperation(SRResources.ReferentialConstraintAlreadyConfigured, "principal", + constraint.Value.Name, "dependent", foundDependent.Name); + } + + Type dependentType = Nullable.GetUnderlyingType(constraint.Key.PropertyType) ?? constraint.Key.PropertyType; + Type principalType = Nullable.GetUnderlyingType(constraint.Value.PropertyType) ?? constraint.Value.PropertyType; + + // The principal property and the dependent property must have the same data type. + if (dependentType != principalType) + { + throw Error.InvalidOperation(SRResources.DependentAndPrincipalTypeNotMatch, + constraint.Key.PropertyType.FullName, constraint.Value.PropertyType.FullName); + } + + // OData V4 spec says that the principal and dependent property MUST be a path expression resolving to a primitive + // property of the dependent entity type itself or to a primitive property of a complex property (recursively) of + // the dependent entity type. + // So far, ODL doesn't support to allow a primitive property of a complex property to be the dependent/principal property. + // There's an issue tracking on: https://github.com/OData/odata.net/issues/22 + if (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(constraint.Key.PropertyType) == null) + { + throw Error.InvalidOperation(SRResources.ReferentialConstraintPropertyTypeNotValid, + constraint.Key.PropertyType.FullName); + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyExtensions.cs new file mode 100644 index 0000000..2c26940 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationPropertyExtensions.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + internal static class NavigationPropertyExtensions + { + public static void FindAllNavigationProperties(this ODataModelBuilder builder, + StructuralTypeConfiguration configuration, + IList, NavigationPropertyConfiguration>> navigations, + Stack path) + { + builder.FindAllNavigationPropertiesRecursive(configuration, navigations, path, new HashSet()); + } + + private static void FindAllNavigationPropertiesRecursive(this ODataModelBuilder builder, + StructuralTypeConfiguration configuration, + IList, NavigationPropertyConfiguration>> navigations, + Stack path, + HashSet typesAlreadyProcessed) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (navigations == null) + { + throw Error.ArgumentNull("navigations"); + } + + if (path == null) + { + throw Error.ArgumentNull("path"); + } + + IEnumerable thisAndBaseTypes = configuration.ThisAndBaseTypes(); + foreach (var config in thisAndBaseTypes) + { + builder.FindNavigationProperties(config, navigations, path, typesAlreadyProcessed); + } + + IEnumerable derivedTypes = builder.DerivedTypes(configuration); + foreach (var config in derivedTypes) + { + if (path.OfType().Any(p => p == config.ClrType)) + { + continue; + } + + path.Push(TypeHelper.AsMemberInfo(config.ClrType)); + + builder.FindNavigationProperties(config, navigations, path, typesAlreadyProcessed); + + path.Pop(); + } + } + + private static void FindNavigationProperties(this ODataModelBuilder builder, StructuralTypeConfiguration configuration, + IList, NavigationPropertyConfiguration>> navs, + Stack path, HashSet typesAlreadyProcessed) + { + Contract.Assert(builder != null); + Contract.Assert(configuration != null); + Contract.Assert(navs != null); + Contract.Assert(path != null); + + foreach (var property in configuration.Properties) + { + path.Push(property.PropertyInfo); + + NavigationPropertyConfiguration nav = property as NavigationPropertyConfiguration; + ComplexPropertyConfiguration complex = property as ComplexPropertyConfiguration; + CollectionPropertyConfiguration collection = property as CollectionPropertyConfiguration; + + if (nav != null) + { + // how about the containment? + IList bindingPath = path.Reverse().ToList(); + + navs.Add( + new Tuple, NavigationPropertyConfiguration>(configuration, + bindingPath, nav)); + } + else if (complex != null && !typesAlreadyProcessed.Contains(complex.RelatedClrType)) + { + StructuralTypeConfiguration complexType = builder.GetTypeConfigurationOrNull(complex.RelatedClrType) as StructuralTypeConfiguration; + + // Prevent infinite recursion on self-referential complex types. + typesAlreadyProcessed.Add(complex.RelatedClrType); + builder.FindAllNavigationPropertiesRecursive(complexType, navs, path, typesAlreadyProcessed); + typesAlreadyProcessed.Remove(complex.RelatedClrType); + } + else if (collection != null && !typesAlreadyProcessed.Contains(collection.ElementType)) + { + IEdmTypeConfiguration edmType = builder.GetTypeConfigurationOrNull(collection.ElementType); + if (edmType != null && edmType.Kind == EdmTypeKind.Complex) + { + StructuralTypeConfiguration complexType = (StructuralTypeConfiguration)edmType; + + // Prevent infinite recursion on self-referential complex types. + typesAlreadyProcessed.Add(collection.ElementType); + builder.FindAllNavigationPropertiesRecursive(complexType, navs, path, typesAlreadyProcessed); + typesAlreadyProcessed.Remove(collection.ElementType); + } + } + + path.Pop(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceAndAnnotations.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceAndAnnotations.cs new file mode 100644 index 0000000..56873ba --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceAndAnnotations.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// This class is used in internal as a helper class to build the Edm model. + /// This class wrappers a relationship between Edm type and the CLR type configuration. + /// This relationship is used to builder the navigation property and the corresponding links. + /// + internal class NavigationSourceAndAnnotations + { + public EdmNavigationSource NavigationSource { get; set; } + public NavigationSourceConfiguration Configuration { get; set; } + public NavigationSourceLinkBuilderAnnotation LinkBuilder { get; set; } + public NavigationSourceUrlAnnotation Url { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs new file mode 100644 index 0000000..0d03f91 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfiguration.cs @@ -0,0 +1,578 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Allows configuration to be performed for a navigation source (entity set, singleton) in a model. + /// + public abstract class NavigationSourceConfiguration + { + private readonly ODataModelBuilder _modelBuilder; + private string _url; + + private SelfLinkBuilder _editLinkBuilder; + private SelfLinkBuilder _readLinkBuilder; + private SelfLinkBuilder _idLinkBuilder; + + private readonly + Dictionary> + _navigationPropertyBindings = new Dictionary>(); + + private readonly Dictionary _navigationPropertyLinkBuilders; + + /// + /// Initializes a new instance of the class. + /// The default constructor is intended for use by unit testing only. + /// + protected NavigationSourceConfiguration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The of the entity type contained in this navigation source. + /// The name of the navigation source. + protected NavigationSourceConfiguration(ODataModelBuilder modelBuilder, Type entityClrType, string name) + : this(modelBuilder, new EntityTypeConfiguration(modelBuilder, entityClrType), name) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The entity type contained in this navigation source. + /// The name of the navigation source. + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", + Justification = "The property being set, ClrType, is on a different object.")] + protected NavigationSourceConfiguration(ODataModelBuilder modelBuilder, EntityTypeConfiguration entityType, string name) + { + if (modelBuilder == null) + { + throw Error.ArgumentNull("modelBuilder"); + } + + if (entityType == null) + { + throw Error.ArgumentNull("entityType"); + } + + if (String.IsNullOrEmpty(name)) + { + throw Error.ArgumentNullOrEmpty("name"); + } + + _modelBuilder = modelBuilder; + Name = name; + EntityType = entityType; + ClrType = entityType.ClrType; + _url = Name; + + _editLinkBuilder = null; + _readLinkBuilder = null; + _navigationPropertyLinkBuilders = new Dictionary(); + } + + /// + /// Gets the navigation targets of . + /// + public IEnumerable Bindings + { + get { return _navigationPropertyBindings.Values.SelectMany(e => e.Values); } + } + + /// + /// Gets the entity type contained in this navigation source. + /// + public virtual EntityTypeConfiguration EntityType { get; private set; } + + /// + /// Gets the backing for the entity type contained in this navigation source. + /// + public Type ClrType { get; private set; } + + /// + /// Gets the name of this navigation source. + /// + public string Name { get; private set; } + + /// + /// Configures the navigation source URL. + /// + /// The navigation source URL. + /// Returns itself so that multiple calls can be chained. + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", + MessageId = "0#", Justification = "This Url property is not required to be a valid Uri")] + public virtual NavigationSourceConfiguration HasUrl(string url) + { + _url = url; + return this; + } + + /// + /// Configures the edit link for this navigation source. + /// + /// The builder used to generate the edit link. + /// Returns itself so that multiple calls can be chained. + public virtual NavigationSourceConfiguration HasEditLink(SelfLinkBuilder editLinkBuilder) + { + if (editLinkBuilder == null) + { + throw Error.ArgumentNull("editLinkBuilder"); + } + + _editLinkBuilder = editLinkBuilder; + return this; + } + + /// + /// Configures the read link for this navigation source. + /// + /// The builder used to generate the read link. + /// Returns itself so that multiple calls can be chained. + public virtual NavigationSourceConfiguration HasReadLink(SelfLinkBuilder readLinkBuilder) + { + if (readLinkBuilder == null) + { + throw Error.ArgumentNull("readLinkBuilder"); + } + + _readLinkBuilder = readLinkBuilder; + return this; + } + + /// + /// Configures the ID link for this navigation source. + /// + /// The builder used to generate the ID. + /// Returns itself so that multiple calls can be chained. + public virtual NavigationSourceConfiguration HasIdLink(SelfLinkBuilder idLinkBuilder) + { + if (idLinkBuilder == null) + { + throw Error.ArgumentNull("idLinkBuilder"); + } + + _idLinkBuilder = idLinkBuilder; + return this; + } + + /// + /// Configures the navigation link for the given navigation property for this navigation source. + /// + /// The navigation property for which the navigation link is being generated. + /// The builder used to generate the navigation link. + /// Returns itself so that multiple calls can be chained. + public virtual NavigationSourceConfiguration HasNavigationPropertyLink(NavigationPropertyConfiguration navigationProperty, + NavigationLinkBuilder navigationLinkBuilder) + { + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + + if (navigationLinkBuilder == null) + { + throw Error.ArgumentNull("navigationLinkBuilder"); + } + + StructuralTypeConfiguration declaringType = navigationProperty.DeclaringType; + if (!(declaringType.IsAssignableFrom(EntityType) || EntityType.IsAssignableFrom(declaringType))) + { + throw Error.Argument("navigationProperty", SRResources.NavigationPropertyNotInHierarchy, + declaringType.FullName, EntityType.FullName, Name); + } + + _navigationPropertyLinkBuilders[navigationProperty] = navigationLinkBuilder; + return this; + } + + /// + /// Configures the navigation link for the given navigation properties for this navigation source. + /// + /// The navigation properties for which the navigation link is being generated. + /// The builder used to generate the navigation link. + /// Returns itself so that multiple calls can be chained. + public virtual NavigationSourceConfiguration HasNavigationPropertiesLink( + IEnumerable navigationProperties, NavigationLinkBuilder navigationLinkBuilder) + { + if (navigationProperties == null) + { + throw Error.ArgumentNull("navigationProperties"); + } + + if (navigationLinkBuilder == null) + { + throw Error.ArgumentNull("navigationLinkBuilder"); + } + + foreach (NavigationPropertyConfiguration navigationProperty in navigationProperties) + { + HasNavigationPropertyLink(navigationProperty, navigationLinkBuilder); + } + + return this; + } + + /// + /// Binds the given navigation property to the target navigation source. + /// + /// The navigation property. + /// The target navigation source. + /// The so that it can be further configured. + public virtual NavigationPropertyBindingConfiguration AddBinding(NavigationPropertyConfiguration navigationConfiguration, + NavigationSourceConfiguration targetNavigationSource) + { + if (navigationConfiguration == null) + { + throw Error.ArgumentNull("navigationConfiguration"); + } + + if (targetNavigationSource == null) + { + throw Error.ArgumentNull("targetNavigationSource"); + } + + IList bindingPath = new List { navigationConfiguration.PropertyInfo }; + if (navigationConfiguration.DeclaringType != EntityType) + { + bindingPath.Insert(0, TypeHelper.AsMemberInfo(navigationConfiguration.DeclaringType.ClrType)); + } + + return AddBinding(navigationConfiguration, targetNavigationSource, bindingPath); + } + + /// + /// Binds the given navigation property to the target navigation source. + /// + /// The navigation property. + /// The target navigation source. + /// The binding path. + /// The so that it can be further configured. + public virtual NavigationPropertyBindingConfiguration AddBinding(NavigationPropertyConfiguration navigationConfiguration, + NavigationSourceConfiguration targetNavigationSource, IList bindingPath) + { + if (navigationConfiguration == null) + { + throw Error.ArgumentNull("navigationConfiguration"); + } + + if (targetNavigationSource == null) + { + throw Error.ArgumentNull("targetNavigationSource"); + } + + if (bindingPath == null || !bindingPath.Any()) + { + throw Error.ArgumentNull("bindingPath"); + } + + VerifyBindingPath(navigationConfiguration, bindingPath); + + string path = bindingPath.ConvertBindingPath(); + + Dictionary navigationPropertyBindingMap; + NavigationPropertyBindingConfiguration navigationPropertyBinding; + if (_navigationPropertyBindings.TryGetValue(navigationConfiguration, out navigationPropertyBindingMap)) + { + if (navigationPropertyBindingMap.TryGetValue(path, out navigationPropertyBinding)) + { + if (navigationPropertyBinding.TargetNavigationSource != targetNavigationSource) + { + throw Error.NotSupported(SRResources.RebindingNotSupported); + } + } + else + { + navigationPropertyBinding = new NavigationPropertyBindingConfiguration(navigationConfiguration, + targetNavigationSource, bindingPath); + _navigationPropertyBindings[navigationConfiguration][path] = navigationPropertyBinding; + } + } + else + { + _navigationPropertyBindings[navigationConfiguration] = + new Dictionary(); + navigationPropertyBinding = new NavigationPropertyBindingConfiguration(navigationConfiguration, + targetNavigationSource, bindingPath); + _navigationPropertyBindings[navigationConfiguration][path] = navigationPropertyBinding; + } + + return navigationPropertyBinding; + } + + /// + /// Removes the bindings for the given navigation property. + /// + /// The navigation property + public virtual void RemoveBinding(NavigationPropertyConfiguration navigationConfiguration) + { + if (navigationConfiguration == null) + { + throw Error.ArgumentNull("navigationConfiguration"); + } + + _navigationPropertyBindings.Remove(navigationConfiguration); + } + + /// + /// Removes the binding for the given navigation property and the given binding path. + /// + /// The navigation property. + /// The binding path. + public virtual void RemoveBinding(NavigationPropertyConfiguration navigationConfiguration, string bindingPath) + { + if (navigationConfiguration == null) + { + throw Error.ArgumentNull("navigationConfiguration"); + } + + Dictionary navigationPropertyBindingMap; + if (_navigationPropertyBindings.TryGetValue(navigationConfiguration, out navigationPropertyBindingMap)) + { + navigationPropertyBindingMap.Remove(bindingPath); + + if (!navigationPropertyBindingMap.Any()) + { + _navigationPropertyBindings.Remove(navigationConfiguration); + } + } + } + + /// + /// Finds the bindings for the given navigation property. + /// + /// The navigation property. + /// The list of so that it can be further configured. + public virtual IEnumerable FindBinding(NavigationPropertyConfiguration navigationConfiguration) + { + if (navigationConfiguration == null) + { + throw Error.ArgumentNull("navigationConfiguration"); + } + + Dictionary navigationPropertyBindings; + if (_navigationPropertyBindings.TryGetValue(navigationConfiguration, out navigationPropertyBindings)) + { + return navigationPropertyBindings.Values; + } + + return null; + } + + /// + /// Finds the binding for the given navigation property and tries to create it if it does not exist. + /// + /// The navigation property. + /// The binding path. + /// The so that it can be further configured. + public virtual NavigationPropertyBindingConfiguration FindBinding(NavigationPropertyConfiguration navigationConfiguration, + IList bindingPath) + { + if (navigationConfiguration == null) + { + throw Error.ArgumentNull("navigationConfiguration"); + } + + if (bindingPath == null) + { + throw Error.ArgumentNullOrEmpty("bindingPath"); + } + + string path = bindingPath.ConvertBindingPath(); + + Dictionary navigationPropertyBindings; + if (_navigationPropertyBindings.TryGetValue(navigationConfiguration, out navigationPropertyBindings)) + { + NavigationPropertyBindingConfiguration bindingConfiguration; + if (navigationPropertyBindings.TryGetValue(path, out bindingConfiguration)) + { + return bindingConfiguration; + } + } + + if (_modelBuilder.BindingOptions == NavigationPropertyBindingOption.None) + { + return null; + } + + bool hasSingletonAttribute = navigationConfiguration.PropertyInfo.GetCustomAttributes().Any(); + Type entityType = navigationConfiguration.RelatedClrType; + + NavigationSourceConfiguration[] matchedNavigationSources; + if (hasSingletonAttribute) + { + matchedNavigationSources = _modelBuilder.Singletons.Where(es => es.EntityType.ClrType == entityType).ToArray(); + } + else + { + matchedNavigationSources = _modelBuilder.EntitySets.Where(es => es.EntityType.ClrType == entityType).ToArray(); + } + + if (matchedNavigationSources.Length >= 1) + { + if (matchedNavigationSources.Length == 1 || + _modelBuilder.BindingOptions == NavigationPropertyBindingOption.Auto) + { + return AddBinding(navigationConfiguration, matchedNavigationSources[0], bindingPath); + } + + throw Error.NotSupported( + SRResources.CannotAutoCreateMultipleCandidates, + path, + navigationConfiguration.DeclaringType.FullName, + Name, + String.Join(", ", matchedNavigationSources.Select(s => s.Name))); + } + + return null; + } + + /// + /// Gets the bindings for the navigation property with the given name. + /// + /// The name of the navigation property. + /// The bindings . + public virtual IEnumerable FindBindings(string propertyName) + { + foreach (var navigationPropertyBinding in _navigationPropertyBindings) + { + if (navigationPropertyBinding.Key.Name == propertyName) + { + return navigationPropertyBinding.Value.Values; + } + } + + return Enumerable.Empty(); + } + + /// + /// Gets the navigation source URL. + /// + /// The navigation source URL. + [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", + Justification = "This Url property is not required to be a valid Uri")] + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Consistent with EF Has/Get pattern")] + public virtual string GetUrl() + { + return _url; + } + + /// + /// Gets the builder used to generate edit links for this navigation source. + /// + /// The link builder. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Consistent with EF Has/Get pattern")] + public virtual SelfLinkBuilder GetEditLink() + { + return _editLinkBuilder; + } + + /// + /// Gets the builder used to generate read links for this navigation source. + /// + /// The link builder. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Consistent with EF Has/Get pattern")] + public virtual SelfLinkBuilder GetReadLink() + { + return _readLinkBuilder; + } + + /// + /// Gets the builder used to generate ID for this navigation source. + /// + /// The builder. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Consistent with EF Has/Get pattern")] + public virtual SelfLinkBuilder GetIdLink() + { + return _idLinkBuilder; + } + + /// + /// Gets the builder used to generate navigation link for the given navigation property for this navigation source. + /// + /// The navigation property. + /// The link builder. + public virtual NavigationLinkBuilder GetNavigationPropertyLink(NavigationPropertyConfiguration navigationProperty) + { + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + + NavigationLinkBuilder navigationPropertyLinkBuilder; + _navigationPropertyLinkBuilders.TryGetValue(navigationProperty, out navigationPropertyLinkBuilder); + return navigationPropertyLinkBuilder; + } + + private void VerifyBindingPath(NavigationPropertyConfiguration navigationConfiguration, IList bindingPath) + { + Contract.Assert(navigationConfiguration != null); + Contract.Assert(bindingPath != null); + + PropertyInfo navigation = bindingPath.Last() as PropertyInfo; + if (navigation == null || navigation != navigationConfiguration.PropertyInfo) + { + throw Error.Argument("navigationConfiguration", SRResources.NavigationPropertyBindingPathIsNotValid, + bindingPath.ConvertBindingPath(), navigationConfiguration.Name); + } + + bindingPath.Aggregate(EntityType.ClrType, VerifyBindingSegment); + } + + private static Type VerifyBindingSegment(Type current, MemberInfo info) + { + Contract.Assert(current != null); + Contract.Assert(info != null); + + TypeInfo derivedType = info as TypeInfo; + if (derivedType != null) + { + if (!(derivedType.IsAssignableFrom(current) || current.IsAssignableFrom(derivedType.BaseType))) + { + throw Error.InvalidOperation(SRResources.NavigationPropertyBindingPathNotInHierarchy, + derivedType.FullName, info.Name, current.FullName); + } + + return derivedType.BaseType; + } + + PropertyInfo propertyInfo = info as PropertyInfo; + if (propertyInfo == null) + { + throw Error.NotSupported(SRResources.NavigationPropertyBindingPathNotSupported, info.Name, info.MemberType); + } + + Type declaringType = propertyInfo.DeclaringType; + if (declaringType == null || + !(declaringType.IsAssignableFrom(current) || current.IsAssignableFrom(declaringType))) + { + throw Error.InvalidOperation(SRResources.NavigationPropertyBindingPathNotInHierarchy, + declaringType == null ? "Unknown Type" : declaringType.FullName, info.Name, current.FullName); + } + + Type elementType; + if (TypeHelper.IsCollection(propertyInfo.PropertyType, out elementType)) + { + return elementType; + } + + return propertyInfo.PropertyType; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfigurationOfTEntityType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfigurationOfTEntityType.cs new file mode 100644 index 0000000..98cc80b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceConfigurationOfTEntityType.cs @@ -0,0 +1,778 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// The entity type of the navigation source. + /// + public abstract class NavigationSourceConfiguration where TEntityType : class + { + private readonly NavigationSourceConfiguration _configuration; + private readonly EntityTypeConfiguration _entityType; + private readonly ODataModelBuilder _modelBuilder; + private readonly BindingPathConfiguration _binding; + + internal NavigationSourceConfiguration(ODataModelBuilder modelBuilder, NavigationSourceConfiguration configuration) + { + if (modelBuilder == null) + { + throw Error.ArgumentNull("modelBuilder"); + } + + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + _configuration = configuration; + _modelBuilder = modelBuilder; + _entityType = new EntityTypeConfiguration(modelBuilder, _configuration.EntityType); + _binding = new BindingPathConfiguration(modelBuilder, _entityType, _configuration); + } + + /// + /// Gets the entity type contained in this navigation source configuration. + /// + public EntityTypeConfiguration EntityType + { + get + { + return _entityType; + } + } + + internal NavigationSourceConfiguration Configuration + { + get { return _configuration; } + } + + /// + /// Gets a binding path configuration through which you can configure + /// binding paths for the navigation property of this navigation source. + /// + public BindingPathConfiguration Binding + { + get + { + return _binding; + } + } + + /// + /// Configures an one-to-many relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasManyBinding( + Expression>> navigationExpression, string entitySetName) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(entitySetName)) + { + throw Error.ArgumentNullOrEmpty("entitySetName"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasMany(navigationExpression); + + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, + _modelBuilder.EntitySet(entitySetName)._configuration, bindingPath); + } + + /// + /// Configures an one-to-many relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasManyBinding( + Expression>> navigationExpression, string entitySetName) + where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(entitySetName)) + { + throw Error.ArgumentNullOrEmpty("entitySetName"); + } + + return this.Configuration.AddBinding(EntityType.HasMany(navigationExpression), + _modelBuilder.EntitySet(entitySetName)._configuration); + } + + /// + /// Configures an one-to-many relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasManyBinding( + Expression>> navigationExpression, + NavigationSourceConfiguration targetEntitySet) where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetEntitySet == null) + { + throw Error.ArgumentNull("targetEntitySet"); + } + + return this.Configuration.AddBinding(EntityType.HasMany(navigationExpression), targetEntitySet.Configuration); + } + + /// + /// Configures an one-to-many relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasManyBinding( + Expression>> navigationExpression, + NavigationSourceConfiguration targetEntitySet) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetEntitySet == null) + { + throw Error.ArgumentNull("targetEntitySet"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasMany(navigationExpression); + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, targetEntitySet.Configuration, bindingPath); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasRequiredBinding( + Expression> navigationExpression, string entitySetName) + where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(entitySetName)) + { + throw Error.ArgumentNullOrEmpty("entitySetName"); + } + + return this.Configuration.AddBinding(EntityType.HasRequired(navigationExpression), + _modelBuilder.EntitySet(entitySetName).Configuration); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasRequiredBinding( + Expression> navigationExpression, string entitySetName) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(entitySetName)) + { + throw Error.ArgumentNullOrEmpty("entitySetName"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasRequired(navigationExpression); + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, + _modelBuilder.EntitySet(entitySetName).Configuration, bindingPath); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasRequiredBinding( + Expression> navigationExpression, + NavigationSourceConfiguration targetEntitySet) where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetEntitySet == null) + { + throw Error.ArgumentNull("targetEntitySet"); + } + + return this.Configuration.AddBinding(EntityType.HasRequired(navigationExpression), targetEntitySet.Configuration); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasRequiredBinding( + Expression> navigationExpression, + NavigationSourceConfiguration targetEntitySet) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetEntitySet == null) + { + throw Error.ArgumentNull("targetEntitySet"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasRequired(navigationExpression); + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, targetEntitySet.Configuration, bindingPath); + } + + /// + /// Configures an optional relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasOptionalBinding( + Expression> navigationExpression, string entitySetName) + where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(entitySetName)) + { + throw Error.ArgumentNullOrEmpty("entitySetName"); + } + + return this.Configuration.AddBinding(EntityType.HasOptional(navigationExpression), + _modelBuilder.EntitySet(entitySetName).Configuration); + } + + /// + /// Configures an optional relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) name for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasOptionalBinding( + Expression> navigationExpression, string entitySetName) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(entitySetName)) + { + throw Error.ArgumentNullOrEmpty("entitySetName"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasOptional(navigationExpression); + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, + _modelBuilder.EntitySet(entitySetName).Configuration, bindingPath); + } + + /// + /// Configures an optional relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasOptionalBinding( + Expression> navigationExpression, + NavigationSourceConfiguration targetEntitySet) where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetEntitySet == null) + { + throw Error.ArgumentNull("targetEntitySet"); + } + + return this.Configuration.AddBinding(EntityType.HasOptional(navigationExpression), targetEntitySet.Configuration); + } + + /// + /// Configures an optional relationship from this entity type and binds the corresponding navigation property to + /// the given entity set. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (entity set) for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasOptionalBinding( + Expression> navigationExpression, + NavigationSourceConfiguration targetEntitySet) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetEntitySet == null) + { + throw Error.ArgumentNull("targetEntitySet"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasOptional(navigationExpression); + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, targetEntitySet.Configuration, bindingPath); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given singleton. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (singleton) name for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasSingletonBinding( + Expression> navigationExpression, string singletonName) + where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(singletonName)) + { + throw Error.ArgumentNullOrEmpty("singletonName"); + } + + return this.Configuration.AddBinding(EntityType.HasRequired(navigationExpression), + _modelBuilder.Singleton(singletonName).Configuration); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given singleton. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (singleton) name for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasSingletonBinding( + Expression> navigationExpression, string singletonName) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (String.IsNullOrEmpty(singletonName)) + { + throw Error.ArgumentNullOrEmpty("singletonName"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasRequired(navigationExpression); + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, + _modelBuilder.Singleton(singletonName).Configuration, bindingPath); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given singleton. + /// + /// The target navigation source type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target navigation source (singleton) for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasSingletonBinding( + Expression> navigationExpression, + NavigationSourceConfiguration targetSingleton) where TTargetType : class + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetSingleton == null) + { + throw Error.ArgumentNull("targetSingleton"); + } + + return this.Configuration.AddBinding(EntityType.HasRequired(navigationExpression), targetSingleton.Configuration); + } + + /// + /// Configures a required relationship from this entity type and binds the corresponding navigation property to + /// the given singleton. + /// + /// The target navigation source type. + /// The target entity type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// The target singleton for the binding. + /// A configuration object that can be used to further configure the relationship further. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public NavigationPropertyBindingConfiguration HasSingletonBinding( + Expression> navigationExpression, + NavigationSourceConfiguration targetSingleton) + where TTargetType : class + where TDerivedEntityType : class, TEntityType + { + if (navigationExpression == null) + { + throw Error.ArgumentNull("navigationExpression"); + } + + if (targetSingleton == null) + { + throw Error.ArgumentNull("targetSingleton"); + } + + EntityTypeConfiguration derivedEntityType = + _modelBuilder.EntityType().DerivesFrom(); + + NavigationPropertyConfiguration navigation = derivedEntityType.HasRequired(navigationExpression); + IList bindingPath = new List + { + TypeHelper.AsMemberInfo(typeof(TDerivedEntityType)), + navigation.PropertyInfo + }; + + return this.Configuration.AddBinding(navigation, targetSingleton.Configuration, bindingPath); + } + + /// + /// Configures the edit link for the entities from this navigation source. + /// + /// The factory used to generate the edit link. + /// true if the factory follows OData edit link conventions; + /// otherwise, false. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public void HasEditLink(Func, Uri> editLinkFactory, bool followsConventions) + { + if (editLinkFactory == null) + { + throw Error.ArgumentNull("editLinkFactory"); + } + + _configuration.HasEditLink(new SelfLinkBuilder((context) => editLinkFactory(UpCastEntityContext(context)), followsConventions)); + } + + /// + /// Configures the read link for the entities from this navigation source. + /// + /// The factory used to generate the read link. + /// true if the factory follows OData read link conventions; + /// otherwise, false. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public void HasReadLink(Func, Uri> readLinkFactory, bool followsConventions) + { + if (readLinkFactory == null) + { + throw Error.ArgumentNull("readLinkFactory"); + } + + _configuration.HasReadLink(new SelfLinkBuilder((context) => + readLinkFactory(UpCastEntityContext(context)), followsConventions)); + } + + /// + /// Configures the ID link for the entities from this navigation source. + /// + /// The factory used to generate the ID link. + /// true if the factory follows OData ID link conventions; + /// otherwise, false. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public void HasIdLink(Func, Uri> idLinkFactory, bool followsConventions) + { + if (idLinkFactory == null) + { + throw Error.ArgumentNull("idLinkFactory"); + } + + _configuration.HasIdLink(new SelfLinkBuilder((context) => + idLinkFactory(UpCastEntityContext(context)), followsConventions)); + } + + /// + /// Configures the navigation link for the given navigation property for entities from this navigation source. + /// + /// The navigation property for which the navigation link is being generated. + /// The factory used to generate the navigation link. + /// true if the factory follows OData navigation link conventions; + /// otherwise, false. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public void HasNavigationPropertyLink(NavigationPropertyConfiguration navigationProperty, + Func, IEdmNavigationProperty, Uri> navigationLinkFactory, bool followsConventions) + { + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + + if (navigationLinkFactory == null) + { + throw Error.ArgumentNull("navigationLinkFactory"); + } + + _configuration.HasNavigationPropertyLink(navigationProperty, new NavigationLinkBuilder((context, property) => + navigationLinkFactory(UpCastEntityContext(context), property), followsConventions)); + } + + /// + /// Configures the navigation link for the given navigation properties for entities from this navigation source. + /// + /// The navigation properties for which the navigation link is being generated. + /// The factory used to generate the navigation link. + /// true if the factory follows OData navigation link conventions; + /// otherwise, false. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + public void HasNavigationPropertiesLink(IEnumerable navigationProperties, + Func, IEdmNavigationProperty, Uri> navigationLinkFactory, bool followsConventions) + { + if (navigationProperties == null) + { + throw Error.ArgumentNull("navigationProperties"); + } + + if (navigationLinkFactory == null) + { + throw Error.ArgumentNull("navigationLinkFactory"); + } + + _configuration.HasNavigationPropertiesLink(navigationProperties, new NavigationLinkBuilder((entity, property) => + navigationLinkFactory(UpCastEntityContext(entity), property), followsConventions)); + } + + /// + /// Finds the bindings for the navigation property with the given name. + /// + /// The name of the navigation property. + /// The bindings, if found; otherwise, empty. + public IEnumerable FindBindings(string propertyName) + { + return _configuration.FindBindings(propertyName); + } + + /// + /// Finds the bindings for the given navigation property + /// + /// The navigation property. + /// The bindings if found + public IEnumerable FindBinding(NavigationPropertyConfiguration navigationConfiguration) + { + return _configuration.FindBinding(navigationConfiguration); + } + + /// + /// Finds the for the given navigation property. + /// + /// The navigation property. + /// The navigation binding path. + /// The binding if found. + public NavigationPropertyBindingConfiguration FindBinding(NavigationPropertyConfiguration navigationConfiguration, + IList bindingPath) + { + return _configuration.FindBinding(navigationConfiguration, bindingPath); + } + + private static ResourceContext UpCastEntityContext(ResourceContext context) + { + return new ResourceContext + { + SerializerContext = context.SerializerContext, + EdmObject = context.EdmObject, + StructuredType = context.StructuredType + }; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceLinkBuilderAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceLinkBuilderAnnotation.cs new file mode 100644 index 0000000..b2f4ed9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceLinkBuilderAnnotation.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// is a class used to annotate an inside an + /// with information about how to build links related to that navigation source. + /// + public class NavigationSourceLinkBuilderAnnotation + { + private readonly SelfLinkBuilder _idLinkBuilder; + private readonly SelfLinkBuilder _editLinkBuilder; + private readonly SelfLinkBuilder _readLinkBuilder; + + private readonly Dictionary _navigationPropertyLinkBuilderLookup = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// The default constructor is intended for use by unit testing only. + public NavigationSourceLinkBuilderAnnotation() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The navigation source for which the link builder is being constructed. + /// The EDM model that this navigation source belongs to. + /// This constructor creates a link builder that generates URL's that follow OData conventions for the given navigation source. + public NavigationSourceLinkBuilderAnnotation(IEdmNavigationSource navigationSource, IEdmModel model) + { + if (navigationSource == null) + { + throw Error.ArgumentNull("navigationSource"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + IEdmEntityType elementType = navigationSource.EntityType(); + IEnumerable derivedTypes = model.FindAllDerivedTypes(elementType).Cast(); + + // Add navigation link builders for all navigation properties of entity. + foreach (IEdmNavigationProperty navigationProperty in elementType.NavigationProperties()) + { + Func navigationLinkFactory = + (resourceContext, navProperty) => resourceContext.GenerateNavigationPropertyLink(navProperty, includeCast: false); + AddNavigationPropertyLinkBuilder(navigationProperty, new NavigationLinkBuilder(navigationLinkFactory, followsConventions: true)); + } + + // Add navigation link builders for all navigation properties in derived types. + bool derivedTypesDefineNavigationProperty = false; + foreach (IEdmEntityType derivedEntityType in derivedTypes) + { + foreach (IEdmNavigationProperty navigationProperty in derivedEntityType.DeclaredNavigationProperties()) + { + derivedTypesDefineNavigationProperty = true; + Func navigationLinkFactory = + (resourceContext, navProperty) => resourceContext.GenerateNavigationPropertyLink(navProperty, includeCast: true); + AddNavigationPropertyLinkBuilder(navigationProperty, new NavigationLinkBuilder(navigationLinkFactory, followsConventions: true)); + } + } + + Func selfLinkFactory = + (resourceContext) => resourceContext.GenerateSelfLink(includeCast: derivedTypesDefineNavigationProperty); + _idLinkBuilder = new SelfLinkBuilder(selfLinkFactory, followsConventions: true); + } + + /// + /// Constructs an instance of an class. + /// + /// The navigation source for which the link builder is being constructed. + /// The ID link builder which is used to build the ID link. + /// The Edit link builder which is used to build the Edit link. + /// The Read link builder which is used to build the Read link. + public NavigationSourceLinkBuilderAnnotation( + IEdmNavigationSource navigationSource, + SelfLinkBuilder idLinkBuilder, + SelfLinkBuilder editLinkBuilder, + SelfLinkBuilder readLinkBuilder) + { + if (navigationSource == null) + { + throw Error.ArgumentNull("navigationSource"); + } + + _idLinkBuilder = idLinkBuilder; + _editLinkBuilder = editLinkBuilder; + _readLinkBuilder = readLinkBuilder; + } + + /// + /// Constructs an instance of an from an . + /// + public NavigationSourceLinkBuilderAnnotation(NavigationSourceConfiguration navigationSource) + { + if (navigationSource == null) + { + throw Error.ArgumentNull("navigationSource"); + } + + _idLinkBuilder = navigationSource.GetIdLink(); + _editLinkBuilder = navigationSource.GetEditLink(); + _readLinkBuilder = navigationSource.GetReadLink(); + } + + /// + /// Register a link builder for a that navigates from Entities in this navigation source. + /// + public void AddNavigationPropertyLinkBuilder(IEdmNavigationProperty navigationProperty, NavigationLinkBuilder linkBuilder) + { + _navigationPropertyLinkBuilderLookup[navigationProperty] = linkBuilder; + } + + /// + /// Constructs the for a particular and . + /// + public virtual EntitySelfLinks BuildEntitySelfLinks(ResourceContext instanceContext, ODataMetadataLevel metadataLevel) + { + EntitySelfLinks selfLinks = new EntitySelfLinks(); + selfLinks.IdLink = BuildIdLink(instanceContext, metadataLevel); + selfLinks.EditLink = BuildEditLink(instanceContext, metadataLevel, selfLinks.IdLink); + selfLinks.ReadLink = BuildReadLink(instanceContext, metadataLevel, selfLinks.EditLink); + return selfLinks; + } + + /// + /// Constructs the IdLink for a particular and . + /// + public virtual Uri BuildIdLink(ResourceContext instanceContext, ODataMetadataLevel metadataLevel) + { + if (instanceContext == null) + { + throw Error.ArgumentNull("instanceContext"); + } + + if (_idLinkBuilder != null && + (metadataLevel == ODataMetadataLevel.FullMetadata || + (metadataLevel == ODataMetadataLevel.MinimalMetadata && !_idLinkBuilder.FollowsConventions))) + { + return _idLinkBuilder.Factory(instanceContext); + } + + // Return null to let ODL decide when and how to build the id link. + return null; + } + + // Build an id link unconditionally, it doesn't depend on metadata level but does require a non-null link builder. + internal Uri BuildIdLink(ResourceContext instanceContext) + { + return BuildIdLink(instanceContext, ODataMetadataLevel.FullMetadata); + } + + /// + /// Constructs the EditLink URL for a particular and . + /// + public virtual Uri BuildEditLink(ResourceContext instanceContext, ODataMetadataLevel metadataLevel, Uri idLink) + { + if (instanceContext == null) + { + throw Error.ArgumentNull("instanceContext"); + } + + if (_editLinkBuilder != null && + (metadataLevel == ODataMetadataLevel.FullMetadata || + (metadataLevel == ODataMetadataLevel.MinimalMetadata && !_editLinkBuilder.FollowsConventions))) + { + // edit link is the not the same as id link. Generate if the client asked for it (full metadata modes) or + // if the client cannot infer it (not follow conventions). + return _editLinkBuilder.Factory(instanceContext); + } + + // Return null to let ODL decide when and how to build the edit link. + return null; + } + + // Build an edit link unconditionally, it doesn't depend on metadata level but does require a non-null link builder. + internal Uri BuildEditLink(ResourceContext instanceContext) + { + return BuildEditLink(instanceContext, ODataMetadataLevel.FullMetadata, null); + } + + /// + /// Constructs a ReadLink URL for a particular and . + /// + public virtual Uri BuildReadLink(ResourceContext instanceContext, ODataMetadataLevel metadataLevel, Uri editLink) + { + if (instanceContext == null) + { + throw Error.ArgumentNull("instanceContext"); + } + + if (_readLinkBuilder != null && + (metadataLevel == ODataMetadataLevel.FullMetadata || + (metadataLevel == ODataMetadataLevel.MinimalMetadata && !_readLinkBuilder.FollowsConventions))) + { + // read link is not the same as edit link. Generate if the client asked for it (full metadata modes) or + // if the client cannot infer it (not follow conventions). + return _readLinkBuilder.Factory(instanceContext); + } + + // Return null to let ODL decide when and how to build the read link. + return null; + } + + // Build a read link unconditionally, it doesn't depend on metadata level but does require a non-null link builder. + internal Uri BuildReadLink(ResourceContext instanceContext) + { + return BuildReadLink(instanceContext, ODataMetadataLevel.FullMetadata, null); + } + + /// + /// Constructs a NavigationLink for a particular , and . + /// + public virtual Uri BuildNavigationLink(ResourceContext instanceContext, IEdmNavigationProperty navigationProperty, ODataMetadataLevel metadataLevel) + { + if (instanceContext == null) + { + throw Error.ArgumentNull("instanceContext"); + } + + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + + NavigationLinkBuilder navigationLinkBuilder; + if (_navigationPropertyLinkBuilderLookup.TryGetValue(navigationProperty, out navigationLinkBuilder) + && !navigationLinkBuilder.FollowsConventions + && (metadataLevel == ODataMetadataLevel.MinimalMetadata || metadataLevel == ODataMetadataLevel.FullMetadata)) + { + return navigationLinkBuilder.Factory(instanceContext, navigationProperty); + } + + // Return null to let ODL decide when and how to build the navigation link. + return null; + } + + // Build a naviation link unconditionally, it doesn't depend on metadata level but does require a non-null link builder. + internal Uri BuildNavigationLink(ResourceContext instanceContext, IEdmNavigationProperty navigationProperty) + { + if (instanceContext == null) + { + throw Error.ArgumentNull("instanceContext"); + } + + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + + NavigationLinkBuilder navigationLinkBuilder; + if (_navigationPropertyLinkBuilderLookup.TryGetValue(navigationProperty, out navigationLinkBuilder)) + { + return navigationLinkBuilder.Factory(instanceContext, navigationProperty); + } + + // Return null to let ODL decide when and how to build the navigation link. + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceUrlAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceUrlAnnotation.cs new file mode 100644 index 0000000..14e9190 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NavigationSourceUrlAnnotation.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + // An internal class used in class. + internal class NavigationSourceUrlAnnotation + { + public string Url { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NonBindingParameterConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NonBindingParameterConfiguration.cs new file mode 100644 index 0000000..30a3159 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NonBindingParameterConfiguration.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents a non-binding operation parameter. + /// + /// Non binding parameters are provided in the POST body for Actions + /// Non binding parameters are provided in 3 ways for Functions + /// - ~/.../Function(p1=value) + /// - ~/.../Function(p1=@x)?@x=value + /// - ~/.../Function?p1=value (only allowed if the Function is the last url path segment). + /// + /// + public class NonbindingParameterConfiguration : ParameterConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the parameter. + /// The EDM type of the parameter. + public NonbindingParameterConfiguration(string name, IEdmTypeConfiguration parameterType) + : base(name, parameterType) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NullableEnumTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NullableEnumTypeConfiguration.cs new file mode 100644 index 0000000..74c0285 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/NullableEnumTypeConfiguration.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + internal class NullableEnumTypeConfiguration : IEdmTypeConfiguration + { + internal NullableEnumTypeConfiguration(EnumTypeConfiguration enumTypeConfiguration) + { + this.ClrType = typeof(Nullable<>).MakeGenericType(enumTypeConfiguration.ClrType); + this.FullName = enumTypeConfiguration.FullName; + this.Namespace = enumTypeConfiguration.Namespace; + this.Name = enumTypeConfiguration.Name; + this.Kind = enumTypeConfiguration.Kind; + this.ModelBuilder = enumTypeConfiguration.ModelBuilder; + this.EnumTypeConfiguration = enumTypeConfiguration; + } + + /// + /// The CLR type associated with the nullable enum type. + /// + public Type ClrType { get; private set; } + + /// + /// The fullname (including namespace) of the EdmType. + /// + public string FullName { get; private set; } + + /// + /// The namespace of the EdmType. + /// + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Justification = "Namespace matches the EF naming scheme")] + public string Namespace { get; private set; } + + /// + /// The name of the EdmType. + /// + public string Name { get; private set; } + + /// + /// The kind of the EdmType. + /// Examples include EntityType, ComplexType, PrimitiveType, CollectionType, EnumType. + /// + public EdmTypeKind Kind { get; private set; } + + /// + /// The ODataModelBuilder used to create this IEdmType. + /// + public ODataModelBuilder ModelBuilder { get; private set; } + + /// + /// The EnumTypeConfiguration used to create this class. + /// + internal EnumTypeConfiguration EnumTypeConfiguration { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs new file mode 100644 index 0000000..fea2b9e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs @@ -0,0 +1,1093 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Builder.Conventions; +using Microsoft.AspNet.OData.Builder.Conventions.Attributes; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// is used to automatically map CLR classes to an EDM model based on a set of . + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Most of the referenced types are helper types needed for operation.")] + public partial class ODataConventionModelBuilder : ODataModelBuilder + { + private static readonly List _conventions = new List + { + // type and property conventions (ordering is important here). + new AbstractTypeDiscoveryConvention(), + new DataContractAttributeEdmTypeConvention(), + new NotMappedAttributeConvention(), // NotMappedAttributeConvention has to run before EntityKeyConvention + new DataMemberAttributeEdmPropertyConvention(), + new RequiredAttributeEdmPropertyConvention(), + new ConcurrencyCheckAttributeEdmPropertyConvention(), + new TimestampAttributeEdmPropertyConvention(), + new ColumnAttributeEdmPropertyConvention(), + new KeyAttributeEdmPropertyConvention(), // KeyAttributeEdmPropertyConvention has to run before EntityKeyConvention + new EntityKeyConvention(), + new ComplexTypeAttributeConvention(), // This has to run after Key conventions, basically overrules them if there is a ComplexTypeAttribute + new IgnoreDataMemberAttributeEdmPropertyConvention(), + new NotFilterableAttributeEdmPropertyConvention(), + new NonFilterableAttributeEdmPropertyConvention(), + new NotSortableAttributeEdmPropertyConvention(), + new UnsortableAttributeEdmPropertyConvention(), + new NotNavigableAttributeEdmPropertyConvention(), + new NotExpandableAttributeEdmPropertyConvention(), + new NotCountableAttributeEdmPropertyConvention(), + new MediaTypeAttributeConvention(), + new AutoExpandAttributeEdmPropertyConvention(), + new AutoExpandAttributeEdmTypeConvention(), + new MaxLengthAttributeEdmPropertyConvention(), + new PageAttributeEdmPropertyConvention(), + new PageAttributeEdmTypeConvention(), + new ExpandAttributeEdmPropertyConvention(), + new ExpandAttributeEdmTypeConvention(), + new CountAttributeEdmPropertyConvention(), + new CountAttributeEdmTypeConvention(), + new OrderByAttributeEdmTypeConvention(), + new FilterAttributeEdmTypeConvention(), + new OrderByAttributeEdmPropertyConvention(), + new FilterAttributeEdmPropertyConvention(), + new SelectAttributeEdmTypeConvention(), + new SelectAttributeEdmPropertyConvention(), + + // INavigationSourceConvention's + new SelfLinksGenerationConvention(), + new NavigationLinksGenerationConvention(), + new AssociationSetDiscoveryConvention(), + + // IEdmFunctionImportConventions's + new ActionLinkGenerationConvention(), + new FunctionLinkGenerationConvention(), + }; + + // These hashset's keep track of edmtypes/navigation sources for which conventions + // have been applied or being applied so that we don't run a convention twice on the + // same type/set. + private HashSet _mappedTypes; + private HashSet _configuredNavigationSources; + private HashSet _ignoredTypes; + + private IEnumerable _explicitlyAddedTypes; + + private bool _isModelBeingBuilt; + private bool _isQueryCompositionMode; + + // build the mapping between type and its derived types to be used later. + private Lazy>> _allTypesWithDerivedTypeMapping; + + /// + /// Initializes a new . + /// + /// The to use. + internal ODataConventionModelBuilder(IWebApiAssembliesResolver resolver) + : this(resolver, isQueryCompositionMode: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + /// If the model is being built for only querying. + /// The model built if is true has more relaxed + /// inference rules and also treats all types as entity types. This constructor is intended for use by unit testing only. + internal ODataConventionModelBuilder(IWebApiAssembliesResolver resolver, bool isQueryCompositionMode) + { + if (resolver == null) + { + throw Error.ArgumentNull("resolver"); + } + + Initialize(resolver, isQueryCompositionMode); + } + + /// + /// Gets or sets if model aliasing is enabled or not. The default value is true. + /// + public bool ModelAliasingEnabled { get; set; } + + /// + /// This action is invoked after the has run all the conventions, but before the configuration is locked + /// down and used to build the . + /// + /// Use this action to modify the configuration that has been inferred by convention. + public Action OnModelCreating { get; set; } + + internal void Initialize(IWebApiAssembliesResolver assembliesResolver, bool isQueryCompositionMode) + { + _isQueryCompositionMode = isQueryCompositionMode; + _configuredNavigationSources = new HashSet(); + _mappedTypes = new HashSet(); + _ignoredTypes = new HashSet(); + ModelAliasingEnabled = true; + _allTypesWithDerivedTypeMapping = new Lazy>>( + () => BuildDerivedTypesMapping(assembliesResolver), + isThreadSafe: false); + } + + /// + /// Excludes a type from the model. This is used to remove types from the model that were added by convention during initial model discovery. + /// + /// + /// The same so that multiple calls can be chained. + [SuppressMessage("Microsoft.Design", "CA1004: GenericMethodsShouldProvideTypeParameter", Justification = "easier to call the generic version than using typeof().")] + public ODataConventionModelBuilder Ignore() + { + _ignoredTypes.Add(typeof(T)); + return this; + } + + /// + /// Excludes a type or types from the model. This is used to remove types from the model that were added by convention during initial model discovery. + /// + /// The types to be excluded from the model. + /// The same so that multiple calls can be chained. + public ODataConventionModelBuilder Ignore(params Type[] types) + { + foreach (Type type in types) + { + _ignoredTypes.Add(type); + } + + return this; + } + + /// + public override EntityTypeConfiguration AddEntityType(Type type) + { + EntityTypeConfiguration entityTypeConfiguration = base.AddEntityType(type); + if (_isModelBeingBuilt) + { + MapType(entityTypeConfiguration); + } + + return entityTypeConfiguration; + } + + /// + public override ComplexTypeConfiguration AddComplexType(Type type) + { + ComplexTypeConfiguration complexTypeConfiguration = base.AddComplexType(type); + if (_isModelBeingBuilt) + { + MapType(complexTypeConfiguration); + } + + return complexTypeConfiguration; + } + + /// + public override EntitySetConfiguration AddEntitySet(string name, EntityTypeConfiguration entityType) + { + EntitySetConfiguration entitySetConfiguration = base.AddEntitySet(name, entityType); + if (_isModelBeingBuilt) + { + ApplyNavigationSourceConventions(entitySetConfiguration); + } + + return entitySetConfiguration; + } + + /// + public override SingletonConfiguration AddSingleton(string name, EntityTypeConfiguration entityType) + { + SingletonConfiguration singletonConfiguration = base.AddSingleton(name, entityType); + if (_isModelBeingBuilt) + { + ApplyNavigationSourceConventions(singletonConfiguration); + } + + return singletonConfiguration; + } + + /// + public override EnumTypeConfiguration AddEnumType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (!TypeHelper.IsEnum(type)) + { + throw Error.Argument("type", SRResources.TypeCannotBeEnum, type.FullName); + } + + EnumTypeConfiguration enumTypeConfiguration = EnumTypes.SingleOrDefault(e => e.ClrType == type); + + if (enumTypeConfiguration == null) + { + enumTypeConfiguration = base.AddEnumType(type); + + foreach (object member in Enum.GetValues(type)) + { + bool addedExplicitly = enumTypeConfiguration.Members.Any(m => m.Name.Equals(member.ToString())); + EnumMemberConfiguration enumMemberConfiguration = enumTypeConfiguration.AddMember((Enum)member); + enumMemberConfiguration.AddedExplicitly = addedExplicitly; + } + ApplyEnumTypeConventions(enumTypeConfiguration); + } + + return enumTypeConfiguration; + } + + /// + public override IEdmModel GetEdmModel() + { + if (_isModelBeingBuilt) + { + throw Error.NotSupported(SRResources.GetEdmModelCalledMoreThanOnce); + } + + // before we begin, get the set of types the user had added explicitly. + _explicitlyAddedTypes = new List(StructuralTypes); + + _isModelBeingBuilt = true; + + MapTypes(); + + DiscoverInheritanceRelationships(); + + // Don't RediscoverComplexTypes() and treat everything as an entity type if building a model for EnableQueryAttribute. + if (!_isQueryCompositionMode) + { + RediscoverComplexTypes(); + } + + // prune unreachable types + PruneUnreachableTypes(); + + // Apply navigation source conventions. + IEnumerable explictlyConfiguredNavigationSource = + new List(NavigationSources); + foreach (NavigationSourceConfiguration navigationSource in explictlyConfiguredNavigationSource) + { + ApplyNavigationSourceConventions(navigationSource); + } + + foreach (OperationConfiguration operation in Operations) + { + ApplyOperationConventions(operation); + } + + if (OnModelCreating != null) + { + OnModelCreating(this); + } + + return base.GetEdmModel(); + } + + internal bool IsIgnoredType(Type type) + { + Contract.Requires(type != null); + + return _ignoredTypes.Contains(type); + } + + // patch up the base type for all types that don't have any yet. + internal void DiscoverInheritanceRelationships() + { + Dictionary entityMap = StructuralTypes.OfType().ToDictionary(e => e.ClrType); + + foreach (EntityTypeConfiguration entity in StructuralTypes.OfType().Where(e => !e.BaseTypeConfigured)) + { + Type baseClrType = TypeHelper.GetBaseType(entity.ClrType); + while (baseClrType != null) + { + // see if we there is an entity that we know mapping to this clr types base type. + EntityTypeConfiguration baseEntityType; + if (entityMap.TryGetValue(baseClrType, out baseEntityType)) + { + RemoveBaseTypeProperties(entity, baseEntityType); + + // disable derived type key check if we are building a model for query composition. + if (_isQueryCompositionMode) + { + // modifying the collection in the iterator, hence the ToArray(). + foreach (PrimitivePropertyConfiguration keyProperty in entity.Keys.ToArray()) + { + entity.RemoveKey(keyProperty); + } + + foreach (EnumPropertyConfiguration enumKeyProperty in entity.EnumKeys.ToArray()) + { + entity.RemoveKey(enumKeyProperty); + } + } + + entity.DerivesFrom(baseEntityType); + break; + } + + baseClrType = TypeHelper.GetBaseType(baseClrType); + } + } + + Dictionary complexMap = + StructuralTypes.OfType().ToDictionary(e => e.ClrType); + foreach (ComplexTypeConfiguration complex in + StructuralTypes.OfType().Where(e => !e.BaseTypeConfigured)) + { + Type baseClrType = TypeHelper.GetBaseType(complex.ClrType); + while (baseClrType != null) + { + ComplexTypeConfiguration baseComplexType; + if (complexMap.TryGetValue(baseClrType, out baseComplexType)) + { + RemoveBaseTypeProperties(complex, baseComplexType); + complex.DerivesFrom(baseComplexType); + break; + } + + baseClrType = TypeHelper.GetBaseType(baseClrType); + } + } + } + + // remove the base type properties from the derived types. + internal void RemoveBaseTypeProperties(StructuralTypeConfiguration derivedStructrualType, + StructuralTypeConfiguration baseStructuralType) + { + IEnumerable typesToLift = new[] { derivedStructrualType } + .Concat(this.DerivedTypes(derivedStructrualType)); + + foreach (PropertyConfiguration property in baseStructuralType.Properties + .Concat(baseStructuralType.DerivedProperties())) + { + foreach (StructuralTypeConfiguration structuralType in typesToLift) + { + PropertyConfiguration derivedPropertyToRemove = structuralType.Properties.SingleOrDefault( + p => p.PropertyInfo.Name == property.PropertyInfo.Name); + if (derivedPropertyToRemove != null) + { + structuralType.RemoveProperty(derivedPropertyToRemove.PropertyInfo); + } + } + } + + foreach (PropertyInfo ignoredProperty in baseStructuralType.IgnoredProperties()) + { + foreach (StructuralTypeConfiguration structuralType in typesToLift) + { + PropertyConfiguration derivedPropertyToRemove = structuralType.Properties.SingleOrDefault( + p => p.PropertyInfo.Name == ignoredProperty.Name); + if (derivedPropertyToRemove != null) + { + structuralType.RemoveProperty(derivedPropertyToRemove.PropertyInfo); + } + } + } + } + + private void RediscoverComplexTypes() + { + Contract.Assert(_explicitlyAddedTypes != null); + + EntityTypeConfiguration[] misconfiguredEntityTypes = StructuralTypes + .Except(_explicitlyAddedTypes) + .OfType() + .Where(entity => !entity.Keys().Any()) + .ToArray(); + + ReconfigureEntityTypesAsComplexType(misconfiguredEntityTypes); + + DiscoverInheritanceRelationships(); + } + + private void ReconfigureEntityTypesAsComplexType(EntityTypeConfiguration[] misconfiguredEntityTypes) + { + IList actualEntityTypes = + StructuralTypes.OfType() + .Where(entity => entity.Keys().Any()) + .Concat(_explicitlyAddedTypes.OfType()) + .Except(misconfiguredEntityTypes) + .ToList(); + + HashSet visitedEntityType = new HashSet(); + foreach (EntityTypeConfiguration misconfiguredEntityType in misconfiguredEntityTypes) + { + if (visitedEntityType.Contains(misconfiguredEntityType)) + { + continue; + } + + // If one of the base types is already configured as entity type, we should keep this type as entity type. + IEnumerable basedTypes = misconfiguredEntityType + .BaseTypes().OfType(); + if (actualEntityTypes.Any(e => basedTypes.Any(a => a.ClrType == e.ClrType))) + { + visitedEntityType.Add(misconfiguredEntityType); + continue; + } + + // Make sure to remove current type and all the derived types + IList thisAndDerivedTypes = this.DerivedTypes(misconfiguredEntityType) + .Concat(new[] { misconfiguredEntityType }).OfType().ToList(); + foreach (EntityTypeConfiguration subEnityType in thisAndDerivedTypes) + { + if (actualEntityTypes.Any(e => e.ClrType == subEnityType.ClrType)) + { + throw Error.InvalidOperation(SRResources.CannotReconfigEntityTypeAsComplexType, + misconfiguredEntityType.ClrType.FullName, subEnityType.ClrType.FullName); + } + + RemoveStructuralType(subEnityType.ClrType); + } + + // this is a wrongly inferred type. so just ignore any pending configuration from it. + AddComplexType(misconfiguredEntityType.ClrType); + + foreach (EntityTypeConfiguration subEnityType in thisAndDerivedTypes) + { + visitedEntityType.Add(subEnityType); + + // go through all structural types to remove all properties defined by this mis-configed type. + IList allTypes = StructuralTypes.ToList(); + foreach (StructuralTypeConfiguration structuralToBePatched in allTypes) + { + NavigationPropertyConfiguration[] propertiesToBeRemoved = structuralToBePatched + .NavigationProperties + .Where(navigationProperty => navigationProperty.RelatedClrType == subEnityType.ClrType) + .ToArray(); + + foreach (NavigationPropertyConfiguration propertyToBeRemoved in propertiesToBeRemoved) + { + string propertyNameAlias = propertyToBeRemoved.Name; + PropertyConfiguration propertyConfiguration; + + structuralToBePatched.RemoveProperty(propertyToBeRemoved.PropertyInfo); + + if (propertyToBeRemoved.Multiplicity == EdmMultiplicity.Many) + { + propertyConfiguration = + structuralToBePatched.AddCollectionProperty(propertyToBeRemoved.PropertyInfo); + } + else + { + propertyConfiguration = + structuralToBePatched.AddComplexProperty(propertyToBeRemoved.PropertyInfo); + } + + Contract.Assert(propertyToBeRemoved.AddedExplicitly == false); + + // The newly added property must be marked as added implicitly. This can make sure the property + // conventions can be re-applied to the new property. + propertyConfiguration.AddedExplicitly = false; + + ReapplyPropertyConvention(propertyConfiguration, structuralToBePatched); + + propertyConfiguration.Name = propertyNameAlias; + } + } + } + } + } + + private void MapTypes() + { + foreach (StructuralTypeConfiguration edmType in _explicitlyAddedTypes) + { + MapType(edmType); + } + + // Apply foreign key conventions after the type mapping, because foreign key conventions depend on + // entity key setting to be finished. + ApplyForeignKeyConventions(); + } + + private void ApplyForeignKeyConventions() + { + ForeignKeyAttributeConvention foreignKeyAttributeConvention = new ForeignKeyAttributeConvention(); + ForeignKeyDiscoveryConvention foreignKeyDiscoveryConvention = new ForeignKeyDiscoveryConvention(); + ActionOnDeleteAttributeConvention actionOnDeleteConvention = new ActionOnDeleteAttributeConvention(); + foreach (EntityTypeConfiguration edmType in StructuralTypes.OfType()) + { + foreach (PropertyConfiguration property in edmType.Properties) + { + // ForeignKeyDiscoveryConvention has to run after ForeignKeyAttributeConvention + foreignKeyAttributeConvention.Apply(property, edmType, this); + foreignKeyDiscoveryConvention.Apply(property, edmType, this); + + actionOnDeleteConvention.Apply(property, edmType, this); + } + } + } + + private void MapType(StructuralTypeConfiguration edmType) + { + if (!_mappedTypes.Contains(edmType)) + { + _mappedTypes.Add(edmType); + + MapStructuralType(edmType); + + ApplyTypeAndPropertyConventions(edmType); + } + } + + private void MapStructuralType(StructuralTypeConfiguration structuralType) + { + IEnumerable properties = ConventionsHelpers.GetProperties(structuralType, includeReadOnly: _isQueryCompositionMode); + foreach (PropertyInfo property in properties) + { + bool isCollection; + IEdmTypeConfiguration mappedType; + + PropertyKind propertyKind = GetPropertyType(property, out isCollection, out mappedType); + + if (propertyKind == PropertyKind.Primitive || propertyKind == PropertyKind.Complex || propertyKind == PropertyKind.Enum) + { + MapStructuralProperty(structuralType, property, propertyKind, isCollection); + } + else if (propertyKind == PropertyKind.Dynamic) + { + structuralType.AddDynamicPropertyDictionary(property); + } + else + { + // don't add this property if the user has already added it. + if (structuralType.NavigationProperties.All(p => p.Name != property.Name)) + { + NavigationPropertyConfiguration addedNavigationProperty; + if (!isCollection) + { + addedNavigationProperty = structuralType.AddNavigationProperty(property, EdmMultiplicity.ZeroOrOne); + } + else + { + addedNavigationProperty = structuralType.AddNavigationProperty(property, EdmMultiplicity.Many); + } + + ContainedAttribute containedAttribute = property.GetCustomAttribute(); + if (containedAttribute != null) + { + addedNavigationProperty.Contained(); + } + + addedNavigationProperty.AddedExplicitly = false; + } + } + } + + MapDerivedTypes(structuralType); + } + + internal void MapDerivedTypes(StructuralTypeConfiguration structuralType) + { + HashSet visitedTypes = new HashSet(); + + Queue typeToBeVisited = new Queue(); + typeToBeVisited.Enqueue(structuralType); + + // populate all the derived complex types + while (typeToBeVisited.Count != 0) + { + StructuralTypeConfiguration baseType = typeToBeVisited.Dequeue(); + visitedTypes.Add(baseType.ClrType); + + List derivedTypes; + if (_allTypesWithDerivedTypeMapping.Value.TryGetValue(baseType.ClrType, out derivedTypes)) + { + foreach (Type derivedType in derivedTypes) + { + if (!visitedTypes.Contains(derivedType) && !IsIgnoredType(derivedType)) + { + StructuralTypeConfiguration derivedStructuralType; + if (baseType.Kind == EdmTypeKind.Entity) + { + derivedStructuralType = AddEntityType(derivedType); + } + else + { + derivedStructuralType = AddComplexType(derivedType); + } + + typeToBeVisited.Enqueue(derivedStructuralType); + } + } + } + } + } + + private void MapStructuralProperty(StructuralTypeConfiguration type, PropertyInfo property, PropertyKind propertyKind, bool isCollection) + { + Contract.Assert(type != null); + Contract.Assert(property != null); + Contract.Assert(propertyKind == PropertyKind.Complex || propertyKind == PropertyKind.Primitive || propertyKind == PropertyKind.Enum); + + bool addedExplicitly = type.Properties.Any(p => p.PropertyInfo.Name == property.Name); + + PropertyConfiguration addedEdmProperty; + if (!isCollection) + { + if (propertyKind == PropertyKind.Primitive) + { + addedEdmProperty = type.AddProperty(property); + } + else if (propertyKind == PropertyKind.Enum) + { + AddEnumType(TypeHelper.GetUnderlyingTypeOrSelf(property.PropertyType)); + addedEdmProperty = type.AddEnumProperty(property); + } + else + { + addedEdmProperty = type.AddComplexProperty(property); + } + } + else + { + if (_isQueryCompositionMode) + { + Contract.Assert(propertyKind != PropertyKind.Complex, "we don't create complex types in query composition mode."); + } + + if (TypeHelper.IsGenericType(property.PropertyType)) + { + Type elementType = property.PropertyType.GetGenericArguments().First(); + Type elementUnderlyingTypeOrSelf = TypeHelper.GetUnderlyingTypeOrSelf(elementType); + + if (TypeHelper.IsEnum(elementUnderlyingTypeOrSelf)) + { + AddEnumType(elementUnderlyingTypeOrSelf); + } + } + else + { + Type elementType; + if (TypeHelper.IsCollection(property.PropertyType, out elementType)) + { + Type elementUnderlyingTypeOrSelf = TypeHelper.GetUnderlyingTypeOrSelf(elementType); + if (TypeHelper.IsEnum(elementUnderlyingTypeOrSelf)) + { + AddEnumType(elementUnderlyingTypeOrSelf); + } + } + } + + addedEdmProperty = type.AddCollectionProperty(property); + } + + addedEdmProperty.AddedExplicitly = addedExplicitly; + } + + // figures out the type of the property (primitive, complex, navigation) and the corresponding edm type if we have seen this type + // earlier or the user told us about it. + private PropertyKind GetPropertyType(PropertyInfo property, out bool isCollection, out IEdmTypeConfiguration mappedType) + { + Contract.Assert(property != null); + + // IDictionary is used as a container to save/retrieve dynamic properties for an open type. + // It is different from other collections (for example, IEnumerable or IDictionary) + // which are used as navigation properties. + if (typeof(IDictionary).IsAssignableFrom(property.PropertyType)) + { + mappedType = null; + isCollection = false; + return PropertyKind.Dynamic; + } + + PropertyKind propertyKind; + if (TryGetPropertyTypeKind(property.PropertyType, out mappedType, out propertyKind)) + { + isCollection = false; + return propertyKind; + } + + Type elementType; + if (TypeHelper.IsCollection(property.PropertyType, out elementType)) + { + isCollection = true; + if (TryGetPropertyTypeKind(elementType, out mappedType, out propertyKind)) + { + return propertyKind; + } + + // if we know nothing about this type we assume it to be collection of entities + // and patch up later + return PropertyKind.Navigation; + } + + // if we know nothing about this type we assume it to be an entity + // and patch up later + isCollection = false; + return PropertyKind.Navigation; + } + + private bool TryGetPropertyTypeKind(Type propertyType, out IEdmTypeConfiguration mappedType, out PropertyKind propertyKind) + { + Contract.Assert(propertyType != null); + + if (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(propertyType) != null) + { + mappedType = null; + propertyKind = PropertyKind.Primitive; + return true; + } + + mappedType = GetStructuralTypeOrNull(propertyType); + if (mappedType != null) + { + if (mappedType is ComplexTypeConfiguration) + { + propertyKind = PropertyKind.Complex; + } + else if (mappedType is EnumTypeConfiguration) + { + propertyKind = PropertyKind.Enum; + } + else + { + propertyKind = PropertyKind.Navigation; + } + + return true; + } + + // If one of the base types is configured as complex type, the type of this property + // should be configured as complex type too. + Type baseType = TypeHelper.GetBaseType(propertyType); + while (baseType != null && baseType != typeof(object)) + { + IEdmTypeConfiguration baseMappedType = GetStructuralTypeOrNull(baseType); + if (baseMappedType != null) + { + if (baseMappedType is ComplexTypeConfiguration) + { + propertyKind = PropertyKind.Complex; + return true; + } + } + + baseType = TypeHelper.GetBaseType(baseType); + } + + // refer the Edm type from the derived types + PropertyKind referedPropertyKind = PropertyKind.Navigation; + if (InferEdmTypeFromDerivedTypes(propertyType, ref referedPropertyKind)) + { + if (referedPropertyKind == PropertyKind.Complex) + { + ReconfigInferedEntityTypeAsComplexType(propertyType); + } + + propertyKind = referedPropertyKind; + return true; + } + + if (TypeHelper.IsEnum(propertyType)) + { + propertyKind = PropertyKind.Enum; + return true; + } + + propertyKind = PropertyKind.Navigation; + return false; + } + + internal void ReconfigInferedEntityTypeAsComplexType(Type propertyType) + { + HashSet visitedTypes = new HashSet(); + + Queue typeToBeVisited = new Queue(); + typeToBeVisited.Enqueue(propertyType); + + IList foundMappedTypes = new List(); + while (typeToBeVisited.Count != 0) + { + Type currentType = typeToBeVisited.Dequeue(); + visitedTypes.Add(currentType); + + List derivedTypes; + if (_allTypesWithDerivedTypeMapping.Value.TryGetValue(currentType, out derivedTypes)) + { + foreach (Type derivedType in derivedTypes) + { + if (!visitedTypes.Contains(derivedType)) + { + StructuralTypeConfiguration structuralType = StructuralTypes.Except(_explicitlyAddedTypes) + .FirstOrDefault(c => c.ClrType == derivedType); + + if (structuralType != null && structuralType.Kind == EdmTypeKind.Entity) + { + foundMappedTypes.Add((EntityTypeConfiguration)structuralType); + } + + typeToBeVisited.Enqueue(derivedType); + } + } + } + } + + if (foundMappedTypes.Any()) + { + ReconfigureEntityTypesAsComplexType(foundMappedTypes.ToArray()); + } + } + + internal bool InferEdmTypeFromDerivedTypes(Type propertyType, ref PropertyKind propertyKind) + { + HashSet visitedTypes = new HashSet(); + + Queue typeToBeVisited = new Queue(); + typeToBeVisited.Enqueue(propertyType); + + IList foundMappedTypes = new List(); + while (typeToBeVisited.Count != 0) + { + Type currentType = typeToBeVisited.Dequeue(); + visitedTypes.Add(currentType); + + List derivedTypes; + if (_allTypesWithDerivedTypeMapping.Value.TryGetValue(currentType, out derivedTypes)) + { + foreach (Type derivedType in derivedTypes) + { + if (!visitedTypes.Contains(derivedType)) + { + StructuralTypeConfiguration structuralType = + _explicitlyAddedTypes.FirstOrDefault(c => c.ClrType == derivedType); + + if (structuralType != null) + { + foundMappedTypes.Add(structuralType); + } + + typeToBeVisited.Enqueue(derivedType); + } + } + } + } + + if (!foundMappedTypes.Any()) + { + return false; + } + + IEnumerable foundMappedEntityType = + foundMappedTypes.OfType().ToList(); + IEnumerable foundMappedComplexType = + foundMappedTypes.OfType().ToList(); + + if (!foundMappedEntityType.Any()) + { + propertyKind = PropertyKind.Complex; + return true; + } + else if (!foundMappedComplexType.Any()) + { + propertyKind = PropertyKind.Navigation; + return true; + } + else + { + throw Error.InvalidOperation(SRResources.CannotInferEdmType, + propertyType.FullName, + String.Join(",", foundMappedEntityType.Select(e => e.ClrType.FullName)), + String.Join(",", foundMappedComplexType.Select(e => e.ClrType.FullName))); + } + } + + // the convention model builder MapTypes() method might have went through deep object graphs and added a bunch of types + // only to realise after applying the conventions that the user has ignored some of the properties. So, prune the unreachable stuff. + private void PruneUnreachableTypes() + { + Contract.Assert(_explicitlyAddedTypes != null); + + // Do a BFS starting with the types the user has explicitly added to find out the unreachable nodes. + Queue reachableTypes = new Queue(_explicitlyAddedTypes); + HashSet visitedTypes = new HashSet(); + + while (reachableTypes.Count != 0) + { + StructuralTypeConfiguration currentType = reachableTypes.Dequeue(); + + // go visit other end of each of this node's edges. + foreach (PropertyConfiguration property in currentType.Properties.Where(property => property.Kind != PropertyKind.Primitive)) + { + if (property.Kind == PropertyKind.Collection) + { + // if the elementType is primitive we don't need to do anything. + CollectionPropertyConfiguration colProperty = property as CollectionPropertyConfiguration; + if (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(colProperty.ElementType) != null) + { + continue; + } + } + + IEdmTypeConfiguration propertyType = GetStructuralTypeOrNull(property.RelatedClrType); + Contract.Assert(propertyType != null, "we should already have seen this type"); + + var structuralTypeConfiguration = propertyType as StructuralTypeConfiguration; + if (structuralTypeConfiguration != null && !visitedTypes.Contains(propertyType)) + { + reachableTypes.Enqueue(structuralTypeConfiguration); + } + } + + // all derived types and the base type are also reachable + if (currentType.Kind == EdmTypeKind.Entity) + { + EntityTypeConfiguration currentEntityType = (EntityTypeConfiguration)currentType; + if (currentEntityType.BaseType != null && !visitedTypes.Contains(currentEntityType.BaseType)) + { + reachableTypes.Enqueue(currentEntityType.BaseType); + } + + foreach (EntityTypeConfiguration derivedType in this.DerivedTypes(currentEntityType)) + { + if (!visitedTypes.Contains(derivedType)) + { + reachableTypes.Enqueue(derivedType); + } + } + } + else if (currentType.Kind == EdmTypeKind.Complex) + { + ComplexTypeConfiguration currentComplexType = (ComplexTypeConfiguration)currentType; + if (currentComplexType.BaseType != null && !visitedTypes.Contains(currentComplexType.BaseType)) + { + reachableTypes.Enqueue(currentComplexType.BaseType); + } + + foreach (ComplexTypeConfiguration derivedType in this.DerivedTypes(currentComplexType)) + { + if (!visitedTypes.Contains(derivedType)) + { + reachableTypes.Enqueue(derivedType); + } + } + } + + visitedTypes.Add(currentType); + } + + StructuralTypeConfiguration[] allConfiguredTypes = StructuralTypes.ToArray(); + foreach (StructuralTypeConfiguration type in allConfiguredTypes) + { + if (!visitedTypes.Contains(type)) + { + // we don't have to fix up any properties because this type is unreachable and cannot be a property of any reachable type. + RemoveStructuralType(type.ClrType); + } + } + } + + private void ApplyTypeAndPropertyConventions(StructuralTypeConfiguration edmTypeConfiguration) + { + foreach (IConvention convention in _conventions) + { + IEdmTypeConvention typeConvention = convention as IEdmTypeConvention; + if (typeConvention != null) + { + typeConvention.Apply(edmTypeConfiguration, this); + } + + IEdmPropertyConvention propertyConvention = convention as IEdmPropertyConvention; + if (propertyConvention != null) + { + ApplyPropertyConvention(propertyConvention, edmTypeConfiguration); + } + } + } + + private void ApplyEnumTypeConventions(EnumTypeConfiguration enumTypeConfiguration) + { + DataContractAttributeEnumTypeConvention typeConvention = new DataContractAttributeEnumTypeConvention(); + typeConvention.Apply(enumTypeConfiguration, this); + } + + private void ApplyNavigationSourceConventions(NavigationSourceConfiguration navigationSourceConfiguration) + { + if (!_configuredNavigationSources.Contains(navigationSourceConfiguration)) + { + _configuredNavigationSources.Add(navigationSourceConfiguration); + + foreach (INavigationSourceConvention convention in _conventions.OfType()) + { + if (convention != null) + { + convention.Apply(navigationSourceConfiguration, this); + } + } + } + } + + private void ApplyOperationConventions(OperationConfiguration operation) + { + foreach (IOperationConvention convention in _conventions.OfType()) + { + convention.Apply(operation, this); + } + } + + private IEdmTypeConfiguration GetStructuralTypeOrNull(Type clrType) + { + IEdmTypeConfiguration configuration = StructuralTypes.SingleOrDefault(edmType => edmType.ClrType == clrType); + if (configuration == null) + { + Type type = TypeHelper.GetUnderlyingTypeOrSelf(clrType); + configuration = EnumTypes.SingleOrDefault(edmType => edmType.ClrType == type); + } + + return configuration; + } + + private void ApplyPropertyConvention(IEdmPropertyConvention propertyConvention, StructuralTypeConfiguration edmTypeConfiguration) + { + Contract.Assert(propertyConvention != null); + Contract.Assert(edmTypeConfiguration != null); + + foreach (PropertyConfiguration property in edmTypeConfiguration.Properties.ToArray()) + { + propertyConvention.Apply(property, edmTypeConfiguration, this); + } + } + + private void ReapplyPropertyConvention(PropertyConfiguration property, + StructuralTypeConfiguration edmTypeConfiguration) + { + foreach (IEdmPropertyConvention propertyConvention in _conventions.OfType()) + { + propertyConvention.Apply(property, edmTypeConfiguration, this); + } + } + + private static Dictionary> BuildDerivedTypesMapping(IWebApiAssembliesResolver assemblyResolver) + { + IEnumerable allTypes = TypeHelper.GetLoadedTypes(assemblyResolver).Where(t => TypeHelper.IsVisible(t) && TypeHelper.IsClass(t) && t != typeof(object)); + Dictionary> allTypeMapping = allTypes.Distinct().ToDictionary(k => k, k => new List()); + + foreach (Type type in allTypes) + { + List derivedTypes; + if (TypeHelper.GetBaseType(type) != null && allTypeMapping.TryGetValue(TypeHelper.GetBaseType(type), out derivedTypes)) + { + derivedTypes.Add(type); + } + } + + return allTypeMapping; + } + + /// + public override void ValidateModel(IEdmModel model) + { + if (!_isQueryCompositionMode) + { + base.ValidateModel(model); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilderExtensions.cs new file mode 100644 index 0000000..154cd79 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilderExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.ComponentModel; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ODataConventionModelBuilderExtensions + { + /// + /// Enable lower camel case with default + /// NameResolverOptions.ProcessReflectedPropertyNames | + /// NameResolverOptions.ProcessDataMemberAttributePropertyNames | + /// NameResolverOptions.ProcessExplicitPropertyNames. + /// + /// The to be enabled with lower camel case. + /// Returns itself so that multiple calls can be chained. + public static ODataConventionModelBuilder EnableLowerCamelCase(this ODataConventionModelBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + return builder.EnableLowerCamelCase( + NameResolverOptions.ProcessReflectedPropertyNames | + NameResolverOptions.ProcessDataMemberAttributePropertyNames | + NameResolverOptions.ProcessExplicitPropertyNames); + } + + /// + /// Enable lower camel case with given . + /// + /// The to be enabled with lower camel case. + /// The for the lower camel case. + /// Returns itself so that multiple calls can be chained. + public static ODataConventionModelBuilder EnableLowerCamelCase( + this ODataConventionModelBuilder builder, + NameResolverOptions options) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + builder.OnModelCreating += new LowerCamelCaser(options).ApplyLowerCamelCase; + return builder; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataModelBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataModelBuilder.cs new file mode 100644 index 0000000..7a8244d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ODataModelBuilder.cs @@ -0,0 +1,671 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// is used to map CLR classes to an EDM model. + /// + public class ODataModelBuilder + { + private const string DefaultNamespace = "Default"; + + private static readonly Version _defaultDataServiceVersion = EdmConstants.EdmVersion4; + private static readonly Version _defaultMaxDataServiceVersion = EdmConstants.EdmVersion4; + + private Dictionary _enumTypes = new Dictionary(); + private Dictionary _structuralTypes = new Dictionary(); + private Dictionary _navigationSources + = new Dictionary(); + private Dictionary _primitiveTypes = new Dictionary(); + private List _operations = new List(); + + private Version _dataServiceVersion; + private Version _maxDataServiceVersion; + private string _namespace; + + /// + /// Initializes a new instance of the class. + /// + public ODataModelBuilder() + { + _namespace = DefaultNamespace; + ContainerName = "Container"; + DataServiceVersion = _defaultDataServiceVersion; + MaxDataServiceVersion = _defaultMaxDataServiceVersion; + BindingOptions = NavigationPropertyBindingOption.None; + HasAssignedNamespace = false; + } + + /// + /// Gets or sets the namespace that will be used for the resulting model + /// + public string Namespace + { + get + { + return _namespace; + } + set + { + // If user chooses to set the namespace to null, we revert back to 'Default' namespace. + if (String.IsNullOrEmpty(value)) + { + this.HasAssignedNamespace = false; + _namespace = DefaultNamespace; + } + else + { + this.HasAssignedNamespace = true; + _namespace = value; + } + } + } + + /// + /// Gets or sets the name of the container that will hold all the navigation sources, actions and functions + /// + public string ContainerName { get; set; } + + /// + /// Gets or sets the data service version of the model. The default value is 4.0. + /// + public Version DataServiceVersion + { + get + { + return _dataServiceVersion; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + _dataServiceVersion = value; + } + } + + /// + /// Gets or sets the maximum data service version of the model. The default value is 4.0. + /// + public Version MaxDataServiceVersion + { + get + { + return _maxDataServiceVersion; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + _maxDataServiceVersion = value; + } + } + + /// + /// Gets the collection of EDM entity sets in the model to be built. + /// + public virtual IEnumerable EntitySets + { + get { return _navigationSources.Values.OfType(); } + } + + /// + /// Gets the collection of EDM types in the model to be built. + /// + public virtual IEnumerable StructuralTypes + { + get { return _structuralTypes.Values; } + } + + /// + /// Gets the collection of EDM types in the model to be built. + /// + public virtual IEnumerable EnumTypes + { + get { return _enumTypes.Values; } + } + + /// + /// Gets the collection of EDM singletons in the model to be built. + /// + public virtual IEnumerable Singletons + { + get { return _navigationSources.Values.OfType(); } + } + + /// + /// Gets the collection of EDM navigation sources (entity sets and singletons) in the model to be built. + /// + public virtual IEnumerable NavigationSources + { + get { return _navigationSources.Values; } + } + + /// + /// Gets the collection of Operations (i.e. Actions, Functions and ServiceOperations) in the model to be built. + /// + public virtual IEnumerable Operations + { + get { return _operations; } + } + + /// + /// Gets or sets the navigation property binding options. + /// + public NavigationPropertyBindingOption BindingOptions { get; set; } + + /// + /// Gets or sets a value indicating whether the was auto assigned or is using a value assigned by user. + /// + /// + /// true if the is using user assigned value; otherwise, false. + /// + internal bool HasAssignedNamespace { get; private set; } + + /// + /// Registers an entity type as part of the model and returns an object that can be used to configure the entity type. + /// This method can be called multiple times for the same entity to perform multiple lines of configuration. + /// + /// The type to be registered or configured. + /// The configuration object for the specified entity type. + public EntityTypeConfiguration EntityType() where TEntityType : class + { + return new EntityTypeConfiguration(this, AddEntityType(typeof(TEntityType))); + } + + /// + /// Registers a type as a complex type in the model and returns an object that can be used to configure the complex type. + /// This method can be called multiple times for the same type to perform multiple lines of configuration. + /// + /// The type to be registered or configured. + /// The configuration object for the specified complex type. + public ComplexTypeConfiguration ComplexType() where TComplexType : class + { + return new ComplexTypeConfiguration(this, AddComplexType(typeof(TComplexType))); + } + + /// + /// Registers an entity set as a part of the model and returns an object that can be used to configure the entity set. + /// This method can be called multiple times for the same type to perform multiple lines of configuration. + /// + /// The entity type of the entity set. + /// The name of the entity set. + /// The configuration object for the specified entity set. + public EntitySetConfiguration EntitySet(string name) where TEntityType : class + { + EntityTypeConfiguration entity = AddEntityType(typeof(TEntityType)); + return new EntitySetConfiguration(this, AddEntitySet(name, entity)); + } + + /// + /// Registers an enum type as part of the model and returns an object that can be used to configure the enum. + /// + /// The enum type to be registered or configured. + /// The configuration object for the specified enum type. + public EnumTypeConfiguration EnumType() + { + return new EnumTypeConfiguration(AddEnumType(typeof(TEnumType))); + } + + /// + /// Registers a singleton as a part of the model and returns an object that can be used to configure the singleton. + /// This method can be called multiple times for the same type to perform multiple lines of configuration. + /// + /// The entity type of the singleton. + /// The name of the singleton. + /// The configuration object for the specified singleton. + public SingletonConfiguration Singleton(string name) where TEntityType : class + { + EntityTypeConfiguration entity = AddEntityType(typeof(TEntityType)); + return new SingletonConfiguration(this, AddSingleton(name, entity)); + } + + /// + /// Adds an unbound action to the builder. + /// + /// The name of the action. + /// The configuration object for the specified action. + public virtual ActionConfiguration Action(string name) + { + ActionConfiguration action = new ActionConfiguration(this, name); + _operations.Add(action); + return action; + } + + /// + /// Adds an unbound function to the builder. + /// + /// The name of the function. + /// The configuration object for the specified function. + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", Justification = "Consistent with term in EdmLib.")] + public virtual FunctionConfiguration Function(string name) + { + FunctionConfiguration function = new FunctionConfiguration(this, name); + _operations.Add(function); + return function; + } + + /// + /// Registers an entity type as part of the model and returns an object that can be used to configure the entity. + /// This method can be called multiple times for the same entity to perform multiple lines of configuration. + /// + /// The type to be registered or configured. + /// The configuration object for the specified entity type. + public virtual EntityTypeConfiguration AddEntityType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (!_structuralTypes.ContainsKey(type)) + { + EntityTypeConfiguration entityTypeConfig = new EntityTypeConfiguration(this, type); + _structuralTypes.Add(type, entityTypeConfig); + return entityTypeConfig; + } + else + { + EntityTypeConfiguration config = _structuralTypes[type] as EntityTypeConfiguration; + if (config == null || config.ClrType != type) + { + throw Error.Argument("type", SRResources.TypeCannotBeEntityWasComplex, type.FullName); + } + + return config; + } + } + + /// + /// Registers an complex type as part of the model and returns an object that can be used to configure the entity. + /// This method can be called multiple times for the same entity to perform multiple lines of configuration. + /// + /// The type to be registered or configured. + /// The configuration object for the specified complex type. + public virtual ComplexTypeConfiguration AddComplexType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (!_structuralTypes.ContainsKey(type)) + { + ComplexTypeConfiguration complexTypeConfig = new ComplexTypeConfiguration(this, type); + _structuralTypes.Add(type, complexTypeConfig); + return complexTypeConfig; + } + else + { + ComplexTypeConfiguration complexTypeConfig = _structuralTypes[type] as ComplexTypeConfiguration; + if (complexTypeConfig == null || complexTypeConfig.ClrType != type) + { + throw Error.Argument("type", SRResources.TypeCannotBeComplexWasEntity, type.FullName); + } + + return complexTypeConfig; + } + } + + /// + /// Registers an enum type as part of the model and returns an object that can be used to configure the enum type. + /// + /// The type to be registered or configured. + /// The configuration object for the specified enum type. + public virtual EnumTypeConfiguration AddEnumType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (!TypeHelper.IsEnum(type)) + { + throw Error.Argument("type", SRResources.TypeCannotBeEnum, type.FullName); + } + + if (!_enumTypes.ContainsKey(type)) + { + EnumTypeConfiguration enumTypeConfig = new EnumTypeConfiguration(this, type); + _enumTypes.Add(type, enumTypeConfig); + return enumTypeConfig; + } + else + { + EnumTypeConfiguration enumTypeConfig = _enumTypes[type]; + if (enumTypeConfig.ClrType != type) + { + throw Error.Argument("type", SRResources.TypeCannotBeEnum, type.FullName); + } + + return enumTypeConfig; + } + } + + /// + /// Adds a operation to the model. + /// + public virtual void AddOperation(OperationConfiguration operation) + { + _operations.Add(operation); + } + + /// + /// Registers an entity set as a part of the model and returns an object that can be used to configure the entity set. + /// This method can be called multiple times for the same type to perform multiple lines of configuration. + /// + /// The name of the entity set. + /// The type to be registered or configured. + /// The configuration object for the specified entity set. + public virtual EntitySetConfiguration AddEntitySet(string name, EntityTypeConfiguration entityType) + { + if (String.IsNullOrWhiteSpace(name)) + { + throw Error.ArgumentNullOrEmpty("name"); + } + + if (entityType == null) + { + throw Error.ArgumentNull("entityType"); + } + + if (name.Contains(".")) + { + throw Error.NotSupported(SRResources.InvalidEntitySetName, name); + } + + EntitySetConfiguration entitySet = null; + if (_navigationSources.ContainsKey(name)) + { + entitySet = _navigationSources[name] as EntitySetConfiguration; + if (entitySet == null) + { + throw Error.Argument("name", SRResources.EntitySetNameAlreadyConfiguredAsSingleton, name); + } + + if (entitySet.EntityType != entityType) + { + throw Error.Argument("entityType", SRResources.EntitySetAlreadyConfiguredDifferentEntityType, + entitySet.Name, entitySet.EntityType.Name); + } + } + else + { + entitySet = new EntitySetConfiguration(this, entityType, name); + _navigationSources[name] = entitySet; + } + + return entitySet; + } + + /// + /// Registers a singleton as a part of the model and returns an object that can be used to configure the singleton. + /// This method can be called multiple times for the same type to perform multiple lines of configuration. + /// + /// The name of the singleton. + /// The type to be registered or configured. + /// The configuration object for the specified singleton. + public virtual SingletonConfiguration AddSingleton(string name, EntityTypeConfiguration entityType) + { + if (String.IsNullOrWhiteSpace(name)) + { + throw Error.ArgumentNullOrEmpty("name"); + } + + if (entityType == null) + { + throw Error.ArgumentNull("entityType"); + } + + if (name.Contains(".")) + { + throw Error.NotSupported(SRResources.InvalidSingletonName, name); + } + + SingletonConfiguration singleton = null; + if (_navigationSources.ContainsKey(name)) + { + singleton = _navigationSources[name] as SingletonConfiguration; + if (singleton == null) + { + throw Error.Argument("name", SRResources.SingletonNameAlreadyConfiguredAsEntitySet, name); + } + + if (singleton.EntityType != entityType) + { + throw Error.Argument("entityType", SRResources.SingletonAlreadyConfiguredDifferentEntityType, + singleton.Name, singleton.EntityType.Name); + } + } + else + { + singleton = new SingletonConfiguration(this, entityType, name); + _navigationSources[name] = singleton; + } + + return singleton; + } + + /// + /// Removes the type from the model. + /// + /// The type to be removed. + /// true if the type is present in the model and false otherwise. + public virtual bool RemoveStructuralType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + return _structuralTypes.Remove(type); + } + + /// + /// Removes the type from the model. + /// + /// The type to be removed. + /// true if the type is present in the model and false otherwise. + public virtual bool RemoveEnumType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + return _enumTypes.Remove(type); + } + + /// + /// Removes the entity set from the model. + /// + /// The name of the entity set to be removed. + /// true if the entity set is present in the model and false otherwise. + public virtual bool RemoveEntitySet(string name) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + + if (_navigationSources.ContainsKey(name)) + { + EntitySetConfiguration entitySet = _navigationSources[name] as EntitySetConfiguration; + if (entitySet != null) + { + return _navigationSources.Remove(name); + } + } + + return false; + } + + /// + /// Removes the singleton from the model. + /// + /// The name of the singleton to be removed. + /// true if the singleton is present in the model and false otherwise. + public virtual bool RemoveSingleton(string name) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + + if (_navigationSources.ContainsKey(name)) + { + SingletonConfiguration singleton = _navigationSources[name] as SingletonConfiguration; + if (singleton != null) + { + return _navigationSources.Remove(name); + } + } + + return false; + } + + /// + /// Remove the operation from the model + /// + /// If there is more than one operation with the name specified this method will not work. + /// You need to use the other RemoveOperation(..) overload instead. + /// + /// + /// The name of the operation to be removed. + /// true if the operation is present in the model and false otherwise. + public virtual bool RemoveOperation(string name) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + + OperationConfiguration[] toRemove = _operations.Where(p => p.Name == name).ToArray(); + int count = toRemove.Count(); + if (count == 1) + { + return RemoveOperation(toRemove[0]); + } + else if (count == 0) + { + // For consistency with RemoveStructuralType(). + // uses same semantics as Dictionary.Remove(key). + return false; + } + else + { + throw Error.InvalidOperation(SRResources.MoreThanOneOperationFound, name); + } + } + + /// + /// Remove the operation from the model + /// + /// The operation to be removed. + /// true if the operation is present in the model and false otherwise. + public virtual bool RemoveOperation(OperationConfiguration operation) + { + if (operation == null) + { + throw Error.ArgumentNull("operation"); + } + return _operations.Remove(operation); + } + + /// + /// Attempts to find a pre-configured structural type or a primitive type or an enum type that matches the T. + /// If no matches are found NULL is returned. + /// + public IEdmTypeConfiguration GetTypeConfigurationOrNull(Type type) + { + if (_primitiveTypes.ContainsKey(type)) + { + return _primitiveTypes[type]; + } + else + { + IEdmPrimitiveType edmType = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(type); + PrimitiveTypeConfiguration primitiveType = null; + if (edmType != null) + { + primitiveType = new PrimitiveTypeConfiguration(this, edmType, type); + _primitiveTypes[type] = primitiveType; + return primitiveType; + } + else if (_structuralTypes.ContainsKey(type)) + { + return _structuralTypes[type]; + } + else if (_enumTypes.ContainsKey(type)) + { + return _enumTypes[type]; + } + } + + return null; + } + + /// + /// Creates a based on the configuration performed using this builder. + /// + /// The model that was built. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Property is not appropriate, method does work")] + public virtual IEdmModel GetEdmModel() + { + IEdmModel model = EdmModelHelperMethods.BuildEdmModel(this); + ValidateModel(model); + return model; + } + + /// + /// Validates the that is being created. + /// + /// The that will be validated. + public virtual void ValidateModel(IEdmModel model) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + // The type of entity set should have key(s) defined. + foreach (IEdmEntitySet entitySet in model.EntityContainer.Elements.OfType()) + { + if (!entitySet.EntityType().Key().Any()) + { + throw Error.InvalidOperation(SRResources.EntitySetTypeHasNoKeys, entitySet.Name, + entitySet.EntityType().FullName()); + } + } + + // The type of collection navigation property should have key(s) defined. + foreach (IEdmStructuredType structuredType in model.SchemaElementsAcrossModels().OfType()) + { + foreach (var navigationProperty in structuredType.DeclaredNavigationProperties()) + { + if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + IEdmEntityType entityType = navigationProperty.ToEntityType(); + if (!entityType.Key().Any()) + { + throw Error.InvalidOperation(SRResources.CollectionNavigationPropertyEntityTypeDoesntHaveKeyDefined, entityType.FullTypeName(), navigationProperty.Name, structuredType.FullTypeName()); + } + } + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationConfiguration.cs new file mode 100644 index 0000000..1408e98 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationConfiguration.cs @@ -0,0 +1,369 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents a Operation that is exposed in the model + /// + public abstract class OperationConfiguration + { + private List _parameters = new List(); + private BindingParameterConfiguration _bindingParameter; + private string _namespace; + + /// + /// Initializes a new instance of class. + /// + /// The ODataModelBuilder to which this OperationConfiguration should be added. + /// The name of this OperationConfiguration. + internal OperationConfiguration(ODataModelBuilder builder, string name) + { + Name = name; + ModelBuilder = builder; + } + + /// + /// Gets or sets the currently registered operation link builder. + /// + protected OperationLinkBuilder OperationLinkBuilder { get; set; } + + /// + /// Gets a value indicating whether operation links follow OData conventions. + /// + public bool FollowsConventions { get; protected set; } + + /// + /// The Name of the operation + /// + public string Name { get; protected set; } + + /// + /// The Title of the operation. When customized, the title of the operation + /// will be sent back when the OData client asks for an entity or a feed in + /// JSON full metadata. + /// + public string Title { get; set; } + + /// + /// The Kind of operation, which can be either Action or Function + /// + public abstract OperationKind Kind { get; } + + /// + /// Can the operation be composed upon. + /// + /// For example can a URL that invokes the operation be used as the base URL for + /// a request that invokes the operation and does something else with the results + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Copies existing spelling used in EdmLib.")] + public virtual bool IsComposable { get; internal set; } + + /// + /// Does the operation have side-effects. + /// + public abstract bool IsSideEffecting { get; } + + /// + /// The FullyQualifiedName is the Name further qualified using the Namespace. + /// + public string FullyQualifiedName + { + get { return Namespace + "." + Name; } + } + + /// + /// The Namespace by default is the ModelBuilder's Namespace. + /// + public string Namespace + { + get { return _namespace ?? ModelBuilder.Namespace; } + set { _namespace = value; } + } + + /// + /// The type returned when the operation is invoked. + /// + public IEdmTypeConfiguration ReturnType { get; set; } + + /// + /// Gets or sets a value indicating whether the return is nullable or not. + /// + public bool ReturnNullable { get; set; } + + /// + /// The Navigation Source that are returned from. + /// + public NavigationSourceConfiguration NavigationSource { get; set; } + + /// + /// The EntitySetPathExpression that entities are returned from. + /// + public IEnumerable EntitySetPath { get; internal set; } + + /// + /// Get the bindingParameter. + /// Null means the operation has no bindingParameter. + /// + public virtual BindingParameterConfiguration BindingParameter + { + get { return _bindingParameter; } + } + + /// + /// The parameters the operation takes + /// + public virtual IEnumerable Parameters + { + get + { + if (_bindingParameter != null) + { + yield return _bindingParameter; + } + foreach (ParameterConfiguration parameter in _parameters) + { + yield return parameter; + } + } + } + + /// + /// Can the operation be bound to a URL representing the BindingParameter. + /// + public virtual bool IsBindable + { + get + { + return _bindingParameter != null; + } + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entitySetName which contains the return EntityType instance + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + internal void ReturnsFromEntitySetImplementation(string entitySetName) where TEntityType : class + { + ModelBuilder.EntitySet(entitySetName); + NavigationSource = ModelBuilder.EntitySets.Single(s => s.Name == entitySetName); + ReturnType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TEntityType)); + ReturnNullable = true; + } + + /// + /// Sets the return type to a collection of EntityType instances. + /// + /// The type that is an EntityType + /// The entitySetName which contains the returned EntityType instances + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + internal void ReturnsCollectionFromEntitySetImplementation(string entitySetName) where TElementEntityType : class + { + Type clrCollectionType = typeof(IEnumerable); + ModelBuilder.EntitySet(entitySetName); + NavigationSource = ModelBuilder.EntitySets.Single(s => s.Name == entitySetName); + IEdmTypeConfiguration elementType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TElementEntityType)); + ReturnType = new CollectionTypeConfiguration(elementType, clrCollectionType); + ReturnNullable = true; + } + + /// + /// Sets the return type to a single EntityType instance. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the return EntityType instance + internal void ReturnsEntityViaEntitySetPathImplementation(IEnumerable entitySetPath) where TEntityType : class + { + ReturnType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TEntityType)); + EntitySetPath = entitySetPath; + ReturnNullable = true; + } + + /// + /// Sets the return type to a collection of EntityType instances. + /// + /// The type that is an EntityType + /// The entitySetPath which contains the returned EntityType instances + internal void ReturnsCollectionViaEntitySetPathImplementation(IEnumerable entitySetPath) where TElementEntityType : class + { + Type clrCollectionType = typeof(IEnumerable); + IEdmTypeConfiguration elementType = ModelBuilder.GetTypeConfigurationOrNull(typeof(TElementEntityType)); + ReturnType = new CollectionTypeConfiguration(elementType, clrCollectionType); + EntitySetPath = entitySetPath; + ReturnNullable = true; + } + + /// + /// Established the return type of the operation. + /// Used when the return type is a single Primitive or ComplexType. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + internal void ReturnsImplementation(Type clrReturnType) + { + IEdmTypeConfiguration configuration = GetOperationTypeConfiguration(clrReturnType); + ReturnType = configuration; + ReturnNullable = EdmLibHelpers.IsNullable(clrReturnType); + } + + /// + /// Establishes the return type of the operation + /// Used when the return type is a collection of either Primitive or ComplexTypes. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + internal void ReturnsCollectionImplementation() + { + // TODO: I don't like this temporary solution that says the CLR type of the collection is IEnumerable. + // It basically has no meaning. That said the CLR type is meaningful for IEdmTypeConfiguration + // because I still think it is useful for IEdmPrimitiveTypes too. + // You can imagine the override of this that takes a delegate using the correct CLR type for the return type. + Type clrCollectionType = typeof(IEnumerable); + Type clrElementType = typeof(TReturnElementType); + IEdmTypeConfiguration edmElementType = GetOperationTypeConfiguration(clrElementType); + ReturnType = new CollectionTypeConfiguration(edmElementType, clrCollectionType); + ReturnNullable = EdmLibHelpers.IsNullable(clrElementType); + } + + /// + /// Specifies the bindingParameter name and type. + /// + internal void SetBindingParameterImplementation(string name, IEdmTypeConfiguration bindingParameterType) + { + _bindingParameter = new BindingParameterConfiguration(name, bindingParameterType); + } + + /// + /// Adds a new non-binding parameter. + /// + public ParameterConfiguration AddParameter(string name, IEdmTypeConfiguration parameterType) + { + ParameterConfiguration parameter = new NonbindingParameterConfiguration(name, parameterType); + _parameters.Add(parameter); + return parameter; + } + + /// + /// Adds a new non-binding parameter + /// + public ParameterConfiguration Parameter(Type clrParameterType, string name) + { + if (clrParameterType == null) + { + throw Error.ArgumentNull("clrParameterType"); + } + + IEdmTypeConfiguration parameterType = GetOperationTypeConfiguration(clrParameterType); + return AddParameter(name, parameterType); + } + + /// + /// Adds a new non-binding parameter + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ParameterConfiguration Parameter(string name) + { + return this.Parameter(typeof(TParameter), name); + } + + /// + /// Adds a new non-binding collection parameter + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "In keeping with rest of API")] + public ParameterConfiguration CollectionParameter(string name) + { + Type elementType = typeof(TElementType); + IEdmTypeConfiguration elementTypeConfiguration = GetOperationTypeConfiguration(typeof(TElementType)); + CollectionTypeConfiguration parameterType = new CollectionTypeConfiguration(elementTypeConfiguration, typeof(IEnumerable<>).MakeGenericType(elementType)); + return AddParameter(name, parameterType); + } + + /// + /// Adds a new non-binding entity type parameter. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", + Justification = "In keeping with rest of API")] + public ParameterConfiguration EntityParameter(string name) where TEntityType : class + { + Type entityType = typeof(TEntityType); + IEdmTypeConfiguration parameterType = + ModelBuilder.StructuralTypes.FirstOrDefault(t => t.ClrType == entityType) ?? + ModelBuilder.AddEntityType(entityType); + + return AddParameter(name, parameterType); + } + + /// + /// Adds a new non-binding collection of entity type parameter. + /// + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", + Justification = "In keeping with rest of API")] + public ParameterConfiguration CollectionEntityParameter(string name) where TElementEntityType : class + { + Type elementType = typeof(TElementEntityType); + IEdmTypeConfiguration elementTypeConfiguration = + ModelBuilder.StructuralTypes.FirstOrDefault(t => t.ClrType == elementType) ?? + ModelBuilder.AddEntityType(elementType); + + CollectionTypeConfiguration parameterType = new CollectionTypeConfiguration(elementTypeConfiguration, + typeof(IEnumerable<>).MakeGenericType(elementType)); + + return AddParameter(name, parameterType); + } + + /// + /// Gets or sets the used to create this configuration. + /// + protected ODataModelBuilder ModelBuilder { get; set; } + + private IEdmTypeConfiguration GetOperationTypeConfiguration(Type clrType) + { + Type type = TypeHelper.GetUnderlyingTypeOrSelf(clrType); + IEdmTypeConfiguration edmTypeConfiguration; + + if (TypeHelper.IsEnum(type)) + { + edmTypeConfiguration = ModelBuilder.GetTypeConfigurationOrNull(type); + + if (edmTypeConfiguration != null && EdmLibHelpers.IsNullable(clrType)) + { + edmTypeConfiguration = ((EnumTypeConfiguration)edmTypeConfiguration).GetNullableEnumTypeConfiguration(); + } + } + else + { + edmTypeConfiguration = ModelBuilder.GetTypeConfigurationOrNull(clrType); + } + + if (edmTypeConfiguration == null) + { + if (TypeHelper.IsEnum(type)) + { + EnumTypeConfiguration enumTypeConfiguration = ModelBuilder.AddEnumType(type); + + if (EdmLibHelpers.IsNullable(clrType)) + { + edmTypeConfiguration = enumTypeConfiguration.GetNullableEnumTypeConfiguration(); + } + else + { + edmTypeConfiguration = enumTypeConfiguration; + } + } + else + { + edmTypeConfiguration = ModelBuilder.AddComplexType(clrType); + } + } + + return edmTypeConfiguration; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationKind.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationKind.cs new file mode 100644 index 0000000..89ff963 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationKind.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// The Kind of OData Operation. + /// One of Action, Function or ServiceOperation. + /// + public enum OperationKind + { + /// + /// An action + /// + Action = 0, + + /// + /// A function + /// + Function = 1, + + /// + /// A service operation + /// + ServiceOperation = 2 + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationLinkBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationLinkBuilder.cs new file mode 100644 index 0000000..9f72f40 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationLinkBuilder.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// can be used to annotate an action or a function. + /// This is how formatters create links to invoke bound actions or functions. + /// + public class OperationLinkBuilder + { + private Func _linkFactory; // bound to entity + private readonly Func _feedLinkFactory; // bound to collection of entity + + /// + /// Create a new based on an entity link factory. + /// + /// The link factory this should use when building links. + /// + /// A value indicating whether the link factory generates links that follow OData conventions. + /// + public OperationLinkBuilder(Func linkFactory, bool followsConventions) + { + if (linkFactory == null) + { + throw Error.ArgumentNull("linkFactory"); + } + + _linkFactory = linkFactory; + FollowsConventions = followsConventions; + } + + /// + /// Create a new based on a feed link factory. + /// + /// The link factory this should use when building links. + /// + /// A value indicating whether the action link factory generates links that follow OData conventions. + /// + public OperationLinkBuilder(Func linkFactory, bool followsConventions) + { + if (linkFactory == null) + { + throw Error.ArgumentNull("linkFactory"); + } + + _feedLinkFactory = linkFactory; + FollowsConventions = followsConventions; + } + + /// + /// Gets the resource link factory. + /// + internal Func LinkFactory + { + get { return _linkFactory; } + } + + /// + /// Gets the feed link factory. + /// + internal Func FeedLinkFactory + { + get { return _feedLinkFactory; } + } + + /// + /// Gets a Boolean indicating whether the link factory follows OData conventions or not. + /// + public bool FollowsConventions { get; private set; } + + /// + /// Builds the operation link for the given resource. + /// + /// An instance context wrapping the resource instance. + /// The generated link. + public virtual Uri BuildLink(ResourceContext context) + { + if (_linkFactory == null) + { + return null; + } + + return _linkFactory(context); + } + + /// + /// Builds the operation link for the given feed. + /// + /// An feed context wrapping the feed instance. + /// The generated link. + public virtual Uri BuildLink(ResourceSetContext context) + { + if (_feedLinkFactory == null) + { + return null; + } + + return _feedLinkFactory(context); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationTitleAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationTitleAnnotation.cs new file mode 100644 index 0000000..c2638db --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/OperationTitleAnnotation.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + internal class OperationTitleAnnotation + { + public OperationTitleAnnotation(string title) + { + Title = title; + } + + public string Title { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ParameterConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ParameterConfiguration.cs new file mode 100644 index 0000000..b280cb6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ParameterConfiguration.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents a parameter to a Operation + /// + public abstract class ParameterConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the parameter. + /// The EDM type of the parameter. + protected ParameterConfiguration(string name, IEdmTypeConfiguration parameterType) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + if (parameterType == null) + { + throw Error.ArgumentNull("parameterType"); + } + + Name = name; + TypeConfiguration = parameterType; + + Type elementType; + Nullable = TypeHelper.IsCollection(parameterType.ClrType, out elementType) + ? EdmLibHelpers.IsNullable(elementType) + : EdmLibHelpers.IsNullable(parameterType.ClrType); + } + + /// + /// The name of the parameter + /// + public string Name { get; protected set; } + + /// + /// The type of the parameter + /// + public IEdmTypeConfiguration TypeConfiguration { get; protected set; } + + /// + /// Gets or sets a value indicating whether this parameter is nullable or not. + /// + public bool Nullable { get; set; } + + /// + /// Gets or sets a value indicating whether this parameter is optional or not. + /// + public bool IsOptional { get; protected set; } + + /// + /// Gets or sets a default value for optional parameter. + /// + public string DefaultValue { get; protected set; } + + /// + /// Sets the optional value as true. + /// + public ParameterConfiguration Optional() + { + IsOptional = true; + return this; + } + + /// + /// Sets the optional value as false. + /// + public ParameterConfiguration Required() + { + IsOptional = false; + return this; + } + + /// + /// Sets the optional value as true, default value as given value. + /// + /// The default value. + public ParameterConfiguration HasDefaultValue(string defaultValue) + { + IsOptional = true; + DefaultValue = defaultValue; + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrecisionPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrecisionPropertyConfiguration.cs new file mode 100644 index 0000000..0a68dc1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrecisionPropertyConfiguration.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Used to configure a datetime-with-offset, decimal, duration, or time-of-day property precision of an entity type or complex type. + /// This configuration functionality is exposed by the model builder Fluent API, see . + /// + public class PrecisionPropertyConfiguration : PrimitivePropertyConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The declaring EDM type of the property. + public PrecisionPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + } + + /// + /// Get or set the maximum number of digits allowed in the property’s value for decimal property. + /// Get or set the number of decimal places allowed in the seconds portion of the property’s value for temporal property. + /// + public int? Precision { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfiguration.cs new file mode 100644 index 0000000..38aaa43 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfiguration.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Used to configure a primitive property of an entity type or complex type. + /// This configuration functionality is exposed by the model builder Fluent API, see . + /// + public class PrimitivePropertyConfiguration : StructuralPropertyConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The declaring EDM type of the property. + public PrimitivePropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + } + + /// + /// Gets the type of this property. + /// + public override PropertyKind Kind + { + get { return PropertyKind.Primitive; } + } + + /// + /// Gets the backing CLR type of this property type. + /// + public override Type RelatedClrType + { + get { return PropertyInfo.PropertyType; } + } + + /// + /// Gets the target Edm type kind of this property. Call the extension methods to set this property. + /// + public EdmPrimitiveTypeKind? TargetEdmTypeKind { get; internal set; } + + /// + /// Configures the property to be optional. + /// + /// Returns itself so that multiple calls can be chained. + public PrimitivePropertyConfiguration IsOptional() + { + OptionalProperty = true; + return this; + } + + /// + /// Configures the property to be required. + /// + /// Returns itself so that multiple calls can be chained. + public PrimitivePropertyConfiguration IsRequired() + { + OptionalProperty = false; + return this; + } + + /// + /// Configures the property to be used in concurrency checks. For OData this means to be part of the ETag. + /// + /// Returns itself so that multiple calls can be chained. + public PrimitivePropertyConfiguration IsConcurrencyToken() + { + ConcurrencyToken = true; + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfigurationExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfigurationExtensions.cs new file mode 100644 index 0000000..300a3db --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitivePropertyConfigurationExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Extensions method for . + /// + public static class PrimitivePropertyConfigurationExtensions + { + /// + /// If this primitive property is , this method will make the target + /// Edm type kind as + /// + /// Reference to the calling primitive property configuration. + /// Returns itself so that multiple calls can be chained. + public static PrimitivePropertyConfiguration AsDate(this PrimitivePropertyConfiguration property) + { + if (property == null) + { + throw Error.ArgumentNull("property"); + } + + if (!TypeHelper.IsDateTime(property.RelatedClrType)) + { + throw Error.Argument("property", SRResources.MustBeDateTimeProperty, property.PropertyInfo.Name, + property.DeclaringType.FullName); + } + + property.TargetEdmTypeKind = EdmPrimitiveTypeKind.Date; + return property; + } + + /// + /// If this primitive property is , this method will make the target + /// Edm type kind as + /// + /// Reference to the calling primitive property configuration. + /// Returns itself so that multiple calls can be chained. + public static PrimitivePropertyConfiguration AsTimeOfDay(this PrimitivePropertyConfiguration property) + { + if (property == null) + { + throw Error.ArgumentNull("property"); + } + + if (!TypeHelper.IsTimeSpan(property.RelatedClrType)) + { + throw Error.Argument("property", SRResources.MustBeTimeSpanProperty, property.PropertyInfo.Name, + property.DeclaringType.FullName); + } + + property.TargetEdmTypeKind = EdmPrimitiveTypeKind.TimeOfDay; + return property; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitiveTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitiveTypeConfiguration.cs new file mode 100644 index 0000000..7d6124e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PrimitiveTypeConfiguration.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents a PrimitiveType + /// + public class PrimitiveTypeConfiguration : IEdmTypeConfiguration + { + private Type _clrType; + private IEdmPrimitiveType _edmType; + private ODataModelBuilder _builder; + + /// + /// This constructor is public only for unit testing purposes. + /// To get a PrimitiveTypeConfiguration use ODataModelBuilder.GetTypeConfigurationOrNull(Type) + /// + public PrimitiveTypeConfiguration(ODataModelBuilder builder, IEdmPrimitiveType edmType, Type clrType) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + if (clrType == null) + { + throw Error.ArgumentNull("clrType"); + } + _builder = builder; + _clrType = clrType; + _edmType = edmType; + } + + /// + /// Gets the backing CLR type of this EDM type. + /// + public Type ClrType + { + get { return _clrType; } + } + + /// + /// Gets the full name of this EDM type. + /// + public string FullName + { + get { return _edmType.FullName(); } + } + + /// + /// Gets the namespace of this EDM type. + /// + public string Namespace + { + get { return _edmType.Namespace; } + } + + /// + /// Gets the name of this EDM type. + /// + public string Name + { + get { return _edmType.Name; } + } + + /// + /// Gets the of this EDM type. + /// + public EdmTypeKind Kind + { + get { return EdmTypeKind.Primitive; } + } + + /// + /// Gets the used to create this configuration. + /// + public ODataModelBuilder ModelBuilder + { + get { return _builder; } + } + + /// + /// Returns the IEdmPrimitiveType associated with this PrimitiveTypeConfiguration + /// + public IEdmPrimitiveType EdmPrimitiveType + { + get { return _edmType; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs new file mode 100644 index 0000000..8891e03 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs @@ -0,0 +1,489 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Base class for all property configurations. + /// + public abstract class PropertyConfiguration + { + private string _name; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The declaring EDM type of the property. + protected PropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + { + if (property == null) + { + throw Error.ArgumentNull("property"); + } + + if (declaringType == null) + { + throw Error.ArgumentNull("declaringType"); + } + + PropertyInfo = property; + DeclaringType = declaringType; + AddedExplicitly = true; + _name = property.Name; + QueryConfiguration = new QueryConfiguration(); + } + + /// + /// Gets or sets the name of the property. + /// + public string Name + { + get + { + return _name; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + + _name = value; + } + } + + /// + /// Gets the declaring type. + /// + public StructuralTypeConfiguration DeclaringType { get; private set; } + + /// + /// Gets the mapping CLR . + /// + public PropertyInfo PropertyInfo { get; private set; } + + /// + /// Gets the CLR of the property. + /// + public abstract Type RelatedClrType { get; } + + /// + /// Gets the of the property. + /// + public abstract PropertyKind Kind { get; } + + /// + /// Gets or sets a value that is true if the property was added by the user; false if it was inferred through conventions. + /// + /// The default value is true + public bool AddedExplicitly { get; set; } + + /// + /// Gets whether the property is restricted, i.e. not filterable, not sortable, not navigable, + /// not expandable, not countable, or automatically expand. + /// + public bool IsRestricted + { + get { return NotFilterable || NotSortable || NotNavigable || NotExpandable || NotCountable || AutoExpand; } + } + + /// + /// Gets or sets whether the property is not filterable. default is false. + /// + public bool NotFilterable { get; set; } + + /// + /// Gets or sets whether the property is automatically expanded. default is false. + /// + public bool AutoExpand { get; set; } + + /// + /// Gets or sets whether the automatic expand will be disabled if there is a $select specify by client. + /// + public bool DisableAutoExpandWhenSelectIsPresent { get; set; } + + /// + /// Gets or sets whether the property is nonfilterable. default is false. + /// + public bool NonFilterable + { + get { return NotFilterable; } + set { NotFilterable = value; } + } + + /// + /// Gets or sets whether the property is not sortable. default is false. + /// + public bool NotSortable { get; set; } + + /// + /// Gets or sets whether the property is unsortable. default is false. + /// + public bool Unsortable + { + get { return NotSortable; } + set { NotSortable = value; } + } + + /// + /// Gets or sets whether the property is not navigable. default is false. + /// + public bool NotNavigable { get; set; } + + /// + /// Gets or sets whether the property is not expandable. default is false. + /// + public bool NotExpandable { get; set; } + + /// + /// Gets or sets whether the property is not countable. default is false. + /// + public bool NotCountable { get; set; } + + /// + /// Get or sets order in "order by" expression. + /// + public int Order { get; set; } + + /// + /// Gets or sets the . + /// + public QueryConfiguration QueryConfiguration { get; set; } + + /// + /// Sets the property as not filterable. + /// + public PropertyConfiguration IsNotFilterable() + { + NotFilterable = true; + return this; + } + + /// + /// Sets the property as nonfilterable. + /// + public PropertyConfiguration IsNonFilterable() + { + return IsNotFilterable(); + } + + /// + /// Sets the property as filterable. + /// + public PropertyConfiguration IsFilterable() + { + NotFilterable = false; + return this; + } + + /// + /// Sets the property as not sortable. + /// + public PropertyConfiguration IsNotSortable() + { + NotSortable = true; + return this; + } + + /// + /// Sets the property as unsortable. + /// + public PropertyConfiguration IsUnsortable() + { + return IsNotSortable(); + } + + /// + /// Sets the property as sortable. + /// + public PropertyConfiguration IsSortable() + { + NotSortable = false; + return this; + } + + /// + /// Sets the property as not navigable. + /// + public PropertyConfiguration IsNotNavigable() + { + IsNotSortable(); + IsNotFilterable(); + NotNavigable = true; + return this; + } + + /// + /// Sets the property as navigable. + /// + public PropertyConfiguration IsNavigable() + { + NotNavigable = false; + return this; + } + + /// + /// Sets the property as not expandable. + /// + public PropertyConfiguration IsNotExpandable() + { + NotExpandable = true; + return this; + } + + /// + /// Sets the property as expandable. + /// + public PropertyConfiguration IsExpandable() + { + NotExpandable = false; + return this; + } + + /// + /// Sets the property as not countable. + /// + public PropertyConfiguration IsNotCountable() + { + NotCountable = true; + return this; + } + + /// + /// Sets the property as countable. + /// + public PropertyConfiguration IsCountable() + { + NotCountable = false; + return this; + } + + /// + /// Sets this property is countable. + /// + public PropertyConfiguration Count() + { + QueryConfiguration.SetCount(true); + return this; + } + + /// + /// Sets whether this property is countable. + /// + public PropertyConfiguration Count(QueryOptionSetting queryOptionSetting) + { + QueryConfiguration.SetCount(queryOptionSetting == QueryOptionSetting.Allowed); + return this; + } + + /// + /// Sets sortable properties depends on of this property. + /// + public PropertyConfiguration OrderBy(QueryOptionSetting setting, params string[] properties) + { + QueryConfiguration.SetOrderBy(properties, setting == QueryOptionSetting.Allowed); + return this; + } + + /// + /// Sets sortable properties of this property. + /// + public PropertyConfiguration OrderBy(params string[] properties) + { + QueryConfiguration.SetOrderBy(properties, true); + return this; + } + + /// + /// Sets whether all properties of this property is sortable. + /// + public PropertyConfiguration OrderBy(QueryOptionSetting setting) + { + QueryConfiguration.SetOrderBy(null, setting == QueryOptionSetting.Allowed); + return this; + } + + /// + /// Sets all properties of this property is sortable. + /// + public PropertyConfiguration OrderBy() + { + QueryConfiguration.SetOrderBy(null, true); + return this; + } + + /// + /// Sets filterable properties depends on of this property. + /// + public PropertyConfiguration Filter(QueryOptionSetting setting, params string[] properties) + { + QueryConfiguration.SetFilter(properties, setting == QueryOptionSetting.Allowed); + return this; + } + + /// + /// Sets filterable properties of this property. + /// + public PropertyConfiguration Filter(params string[] properties) + { + QueryConfiguration.SetFilter(properties, true); + return this; + } + + /// + /// Sets whether all properties of this property is filterable. + /// + public PropertyConfiguration Filter(QueryOptionSetting setting) + { + QueryConfiguration.SetFilter(null, setting == QueryOptionSetting.Allowed); + return this; + } + + /// + /// Sets all properties of this property is filterable. + /// + public PropertyConfiguration Filter() + { + QueryConfiguration.SetFilter(null, true); + return this; + } + + /// + /// Sets selectable properties depends on of this property. + /// + public PropertyConfiguration Select(SelectExpandType selectType, params string[] properties) + { + QueryConfiguration.SetSelect(properties, selectType); + return this; + } + + /// + /// Sets selectable properties of this property. + /// + public PropertyConfiguration Select(params string[] properties) + { + QueryConfiguration.SetSelect(properties, SelectExpandType.Allowed); + return this; + } + + /// + /// Sets of all properties of this property is selectable. + /// + public PropertyConfiguration Select(SelectExpandType selectType) + { + QueryConfiguration.SetSelect(null, selectType); + return this; + } + + /// + /// Sets all properties of this property is selectable. + /// + public PropertyConfiguration Select() + { + QueryConfiguration.SetSelect(null, SelectExpandType.Allowed); + return this; + } + + /// + /// Sets the max value of $top of this property that a client can request + /// and the maximum number of query results of this property to return. + /// + public PropertyConfiguration Page(int? maxTopValue, int? pageSizeValue) + { + QueryConfiguration.SetMaxTop(maxTopValue); + QueryConfiguration.SetPageSize(pageSizeValue); + return this; + } + + /// + /// Sets this property enable paging. + /// + public PropertyConfiguration Page() + { + QueryConfiguration.SetMaxTop(null); + QueryConfiguration.SetPageSize(null); + return this; + } + + /// + /// Sets the maximum depth of expand result, + /// expandable properties and their of this navigation property. + /// + public PropertyConfiguration Expand(int maxDepth, SelectExpandType expandType, params string[] properties) + { + QueryConfiguration.SetExpand(properties, maxDepth, expandType); + return this; + } + + /// + /// Sets the expandable properties of this navigation property. + /// + public PropertyConfiguration Expand(params string[] properties) + { + QueryConfiguration.SetExpand(properties, null, SelectExpandType.Allowed); + return this; + } + + /// + /// Sets the maximum depth of expand result, + /// expandable properties of this navigation property. + /// + public PropertyConfiguration Expand(int maxDepth, params string[] properties) + { + QueryConfiguration.SetExpand(properties, maxDepth, SelectExpandType.Allowed); + return this; + } + + /// + /// Sets the expandable properties and their of this navigation property. + /// + public PropertyConfiguration Expand(SelectExpandType expandType, params string[] properties) + { + QueryConfiguration.SetExpand(properties, null, expandType); + return this; + } + + /// + /// Sets of all properties with maximum depth of expand result. + /// + public PropertyConfiguration Expand(SelectExpandType expandType, int maxDepth) + { + QueryConfiguration.SetExpand(null, maxDepth, expandType); + return this; + } + + /// + /// Sets all properties expandable with maximum depth of expand result. + /// + public PropertyConfiguration Expand(int maxDepth) + { + QueryConfiguration.SetExpand(null, maxDepth, SelectExpandType.Allowed); + return this; + } + + /// + /// Sets of all properties. + /// + public PropertyConfiguration Expand(SelectExpandType expandType) + { + QueryConfiguration.SetExpand(null, null, expandType); + return this; + } + + /// + /// Sets all properties expandable. + /// + public PropertyConfiguration Expand() + { + QueryConfiguration.SetExpand(null, null, SelectExpandType.Allowed); + return this; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs new file mode 100644 index 0000000..5e66646 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyKind.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// The kind of the EDM property. + /// + public enum PropertyKind + { + /// + /// Represents an EDM primitive property. + /// + Primitive = 0, + + /// + /// Represents an EDM complex property. + /// + Complex = 1, + + /// + /// Represents an EDM collection property. + /// + Collection = 2, + + /// + /// Represents an EDM navigation property. + /// + Navigation = 3, + + /// + /// Represents an EDM enum property. + /// + Enum = 4, + + /// + /// Represents a dynamic property dictionary for an open type. + /// + Dynamic = 5 + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyPairSelectorVisitor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyPairSelectorVisitor.cs new file mode 100644 index 0000000..ae1d252 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertyPairSelectorVisitor.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + internal class PropertyPairSelectorVisitor : ExpressionVisitor + { + private readonly IDictionary _properties = + new Dictionary(); + + public IDictionary Properties + { + get { return _properties; } + } + + public static IDictionary GetSelectedProperty(Expression exp) + { + PropertyPairSelectorVisitor visitor = new PropertyPairSelectorVisitor(); + visitor.Visit(exp); + return visitor.Properties; + } + + public override Expression Visit(Expression exp) + { + if (exp == null) + { + return null; + } + + switch (exp.NodeType) + { + case ExpressionType.Lambda: + return base.Visit(exp); + + case ExpressionType.And: // & + case ExpressionType.AndAlso: // && + BinaryExpression node = (BinaryExpression)exp; + Visit(node.Left); + return Visit(node.Right); + + case ExpressionType.Equal: // == + return VisitEqual(exp); + + default: + throw Error.NotSupported(SRResources.UnsupportedExpressionNodeTypeWithName, exp.NodeType); + } + } + + protected override Expression VisitLambda(Expression lambda) + { + if (lambda == null) + { + throw Error.ArgumentNull("lambda"); + } + + if (lambda.Parameters.Count != 2) + { + throw Error.InvalidOperation(SRResources.LambdaExpressionMustHaveExactlyTwoParameters); + } + + Expression body = Visit(lambda.Body); + + if (body != lambda.Body) + { + return Expression.Lambda(lambda.Type, body, lambda.Parameters); + } + + return lambda; + } + + private Expression VisitEqual(Expression exp) + { + Contract.Assert(exp != null && exp.NodeType == ExpressionType.Equal); + + BinaryExpression node = (BinaryExpression)exp; + + PropertyInfo left = VisitMemberProperty(node.Left); + PropertyInfo right = VisitMemberProperty(node.Right); + + if (left != null && right != null) + { + Type leftType = Nullable.GetUnderlyingType(left.PropertyType) ?? left.PropertyType; + Type rightType = Nullable.GetUnderlyingType(right.PropertyType) ?? right.PropertyType; + if (leftType != rightType) + { + throw Error.InvalidOperation(SRResources.EqualExpressionsMustHaveSameTypes, + TypeHelper.GetReflectedType(left).FullName, left.Name, left.PropertyType.FullName, + TypeHelper.GetReflectedType(right).FullName, right.Name, right.PropertyType.FullName); + } + + _properties.Add(left, right); + } + + return exp; + } + + private PropertyInfo VisitMemberProperty(Expression node) + { + Contract.Assert(node != null); + + switch (node.NodeType) + { + case ExpressionType.MemberAccess: + return GetPropertyInfo((MemberExpression)node); + + case ExpressionType.Convert: + return VisitMemberProperty(((UnaryExpression)node).Operand); + } + + return null; + } + + private static PropertyInfo GetPropertyInfo(MemberExpression memberNode) + { + Contract.Assert(memberNode != null); + + PropertyInfo propertyInfo = memberNode.Member as PropertyInfo; + if (propertyInfo == null) + { + throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeProperties, + TypeHelper.GetReflectedType(memberNode.Member).FullName, memberNode.Member.Name); + } + + if (memberNode.Expression.NodeType != ExpressionType.Parameter) + { + throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeBoundToLambdaParameter); + } + + return propertyInfo; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs new file mode 100644 index 0000000..25896a8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/PropertySelectorVisitor.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + internal class PropertySelectorVisitor : ExpressionVisitor + { + private List _properties = new List(); + + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Class is internal, virtual call okay")] + internal PropertySelectorVisitor(Expression exp) + { + Visit(exp); + } + + public PropertyInfo Property + { + get + { + return _properties.SingleOrDefault(); + } + } + + public ICollection Properties + { + get + { + return _properties; + } + } + + protected override Expression VisitMember(MemberExpression node) + { + if (node == null) + { + throw Error.ArgumentNull("node"); + } + + PropertyInfo pinfo = node.Member as PropertyInfo; + + if (pinfo == null) + { + throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeProperties, TypeHelper.GetReflectedType(node.Member).FullName, node.Member.Name); + } + + if (node.Expression.NodeType != ExpressionType.Parameter) + { + throw Error.InvalidOperation(SRResources.MemberExpressionsMustBeBoundToLambdaParameter); + } + + _properties.Add(pinfo); + return node; + } + + public static PropertyInfo GetSelectedProperty(Expression exp) + { + return new PropertySelectorVisitor(exp).Property; + } + + public static ICollection GetSelectedProperties(Expression exp) + { + return new PropertySelectorVisitor(exp).Properties; + } + + public override Expression Visit(Expression exp) + { + if (exp == null) + { + return exp; + } + + switch (exp.NodeType) + { + case ExpressionType.New: + case ExpressionType.MemberAccess: + case ExpressionType.Lambda: + return base.Visit(exp); + default: + throw Error.NotSupported(SRResources.UnsupportedExpressionNodeType); + } + } + + protected override Expression VisitLambda(Expression lambda) + { + if (lambda == null) + { + throw Error.ArgumentNull("lambda"); + } + + if (lambda.Parameters.Count != 1) + { + throw Error.InvalidOperation(SRResources.LambdaExpressionMustHaveExactlyOneParameter); + } + + Expression body = Visit(lambda.Body); + + if (body != lambda.Body) + { + return Expression.Lambda(lambda.Type, body, lambda.Parameters); + } + return lambda; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/QueryConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/QueryConfiguration.cs new file mode 100644 index 0000000..cd25f01 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/QueryConfiguration.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Query configuration which contains . + /// + public class QueryConfiguration + { + private ModelBoundQuerySettings _querySettings; + + /// + /// Gets or sets the . + /// + public ModelBoundQuerySettings ModelBoundQuerySettings + { + get + { + return _querySettings; + } + set + { + _querySettings = value; + } + } + + /// + /// Sets the Countable in . + /// + public virtual void SetCount(bool enableCount) + { + GetModelBoundQuerySettingsOrDefault().Countable = enableCount; + } + + /// + /// Sets the MaxTop in . + /// + public virtual void SetMaxTop(int? maxTop) + { + GetModelBoundQuerySettingsOrDefault().MaxTop = maxTop; + } + + /// + /// Sets the PageSize in . + /// + public virtual void SetPageSize(int? pageSize) + { + GetModelBoundQuerySettingsOrDefault().PageSize = pageSize; + } + + /// + /// Sets the ExpandConfigurations in . + /// + public virtual void SetExpand(IEnumerable properties, int? maxDepth, SelectExpandType expandType) + { + GetModelBoundQuerySettingsOrDefault(); + if (properties == null) + { + ModelBoundQuerySettings.DefaultExpandType = expandType; + ModelBoundQuerySettings.DefaultMaxDepth = maxDepth ?? ODataValidationSettings.DefaultMaxExpansionDepth; + } + else + { + foreach (var property in properties) + { + ModelBoundQuerySettings.ExpandConfigurations[property] = new ExpandConfiguration + { + ExpandType = expandType, + MaxDepth = maxDepth ?? ODataValidationSettings.DefaultMaxExpansionDepth + }; + } + } + } + + /// + /// Sets the SelectConfigurations in . + /// + public virtual void SetSelect(IEnumerable properties, SelectExpandType selectType) + { + GetModelBoundQuerySettingsOrDefault(); + if (properties == null) + { + ModelBoundQuerySettings.DefaultSelectType = selectType; + } + else + { + foreach (var property in properties) + { + ModelBoundQuerySettings.SelectConfigurations[property] = selectType; + } + } + } + + /// + /// Sets the OrderByConfigurations in . + /// + public virtual void SetOrderBy(IEnumerable properties, bool enableOrderBy) + { + GetModelBoundQuerySettingsOrDefault(); + if (properties == null) + { + ModelBoundQuerySettings.DefaultEnableOrderBy = enableOrderBy; + } + else + { + foreach (var property in properties) + { + ModelBoundQuerySettings.OrderByConfigurations[property] = enableOrderBy; + } + } + } + + /// + /// Sets the FilterConfigurations in . + /// + public virtual void SetFilter(IEnumerable properties, bool enableFilter) + { + GetModelBoundQuerySettingsOrDefault(); + if (properties == null) + { + ModelBoundQuerySettings.DefaultEnableFilter = enableFilter; + } + else + { + foreach (var property in properties) + { + ModelBoundQuerySettings.FilterConfigurations[property] = enableFilter; + } + } + } + + /// + /// Gets the or create it depends on the default settings. + /// + internal ModelBoundQuerySettings GetModelBoundQuerySettingsOrDefault() + { + if (_querySettings == null) + { + _querySettings = new ModelBoundQuerySettings(ModelBoundQuerySettings.DefaultModelBoundQuerySettings); + } + + return _querySettings; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ReturnedEntitySetAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ReturnedEntitySetAnnotation.cs new file mode 100644 index 0000000..1d715b7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/ReturnedEntitySetAnnotation.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// This annotation indicates the mapping from an to a . + /// The is a bound action/function and the is the + /// entity set name given by user to indicate the entity set returned from this action/function. + /// + internal class ReturnedEntitySetAnnotation + { + public ReturnedEntitySetAnnotation(string entitySetName) + { + if (String.IsNullOrEmpty(entitySetName)) + { + throw Error.ArgumentNullOrEmpty("entitySetName"); + } + + EntitySetName = entitySetName; + } + + public string EntitySetName + { + get; + private set; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SelfLinkBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SelfLinkBuilder.cs new file mode 100644 index 0000000..b5311e5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SelfLinkBuilder.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Encapsulates a self link factory and whether the link factory follows conventions or not. + /// + /// The type of the self link generated. This should be for ID links and for read and edit links. + public class SelfLinkBuilder + { + /// + /// Constructs a new instance of . + /// + /// The link factory. + /// Whether the factory follows odata conventions for link generation. + public SelfLinkBuilder(Func linkFactory, bool followsConventions) + { + if (linkFactory == null) + { + throw Error.ArgumentNull("linkFactory"); + } + + Factory = linkFactory; + FollowsConventions = followsConventions; + } + + /// + /// Gets the factory for generating links. + /// + public Func Factory { get; private set; } + + /// + /// Gets a boolean indicating whether the link factory follows OData conventions or not. + /// + public bool FollowsConventions { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonAttribute.cs new file mode 100644 index 0000000..2b741d8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be placed on a property to specify + /// that the property must bind to a singleton. It's used in convention model builder. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class SingletonAttribute : Attribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfiguration.cs new file mode 100644 index 0000000..5ec7548 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfiguration.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Allows configuration to be performed for a singleton in a model. + /// A can be obtained by using the method . + /// + public class SingletonConfiguration : NavigationSourceConfiguration + { + /// + /// Initializes a new instance of the class. + /// The default constructor is intended for use by unit testing only. + /// + public SingletonConfiguration() + : base() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The of the entity type contained in this singleton. + /// The name of the singleton. + public SingletonConfiguration(ODataModelBuilder modelBuilder, Type entityClrType, string name) + : base(modelBuilder, entityClrType, name) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The entity type contained in this singleton. + /// The name of the singleton. + public SingletonConfiguration(ODataModelBuilder modelBuilder, EntityTypeConfiguration entityType, string name) + : base(modelBuilder, entityType, name) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfigurationOfTEntityType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfigurationOfTEntityType.cs new file mode 100644 index 0000000..1a84147 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/SingletonConfigurationOfTEntityType.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// + public class SingletonConfiguration : NavigationSourceConfiguration where TEntityType : class + { + internal SingletonConfiguration(ODataModelBuilder modelBuilder, string name) + : base(modelBuilder, new SingletonConfiguration(modelBuilder, typeof(TEntityType), name)) + { + } + + internal SingletonConfiguration(ODataModelBuilder modelBuilder, SingletonConfiguration configuration) + : base(modelBuilder, configuration) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs new file mode 100644 index 0000000..f31d7a1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralPropertyConfiguration.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Base class for all structural property configurations. + /// + public abstract class StructuralPropertyConfiguration : PropertyConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The property of the configuration. + /// The declaring type of the property. + protected StructuralPropertyConfiguration(PropertyInfo property, StructuralTypeConfiguration declaringType) + : base(property, declaringType) + { + OptionalProperty = EdmLibHelpers.IsNullable(property.PropertyType); + } + + /// + /// Gets or sets a value indicating whether this property is optional or not. + /// + public bool OptionalProperty { get; set; } + + /// + /// Gets or sets a value indicating whether this property is a concurrency token or not. + /// + public bool ConcurrencyToken { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs new file mode 100644 index 0000000..a9b6123 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs @@ -0,0 +1,639 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// + public abstract class StructuralTypeConfiguration : IEdmTypeConfiguration + { + private string _namespace; + private string _name; + private PropertyInfo _dynamicPropertyDictionary; + private StructuralTypeConfiguration _baseType; + private bool _baseTypeConfigured; + + /// + /// Initializes a new instance of the class. + /// + /// The default constructor is intended for use by unit testing only. + protected StructuralTypeConfiguration() + { + ExplicitProperties = new Dictionary(); + RemovedProperties = new List(); + QueryConfiguration = new QueryConfiguration(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The backing CLR type for this EDM structural type. + /// The associated . + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")] + protected StructuralTypeConfiguration(ODataModelBuilder modelBuilder, Type clrType) + : this() + { + if (modelBuilder == null) + { + throw Error.ArgumentNull("modelBuilder"); + } + + if (clrType == null) + { + throw Error.ArgumentNull("clrType"); + } + + ClrType = clrType; + ModelBuilder = modelBuilder; + _name = clrType.EdmName(); + + // Use the namespace if one was provided in builder by the user, otherwise fallback to CLR Namespace. + // If CLR Namespace is null we fallback to "Default" + // This can still be overriden by using DataContract attribute. + _namespace = modelBuilder.HasAssignedNamespace ? modelBuilder.Namespace : clrType.Namespace ?? modelBuilder.Namespace; + } + + /// + /// Gets the of this edm type. + /// + public abstract EdmTypeKind Kind { get; } + + /// + /// Gets the backing CLR . + /// + public virtual Type ClrType { get; private set; } + + /// + /// Gets the full name of this edm type. + /// + public virtual string FullName + { + get + { + return Namespace + "." + Name; + } + } + + /// + /// Gets or sets the namespace of this EDM type. + /// + public virtual string Namespace + { + get + { + return _namespace; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + + _namespace = value; + AddedExplicitly = true; + } + } + + /// + /// Gets or sets the name of this EDM type. + /// + public virtual string Name + { + get + { + return _name; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + + _name = value; + AddedExplicitly = true; + } + } + + /// + /// Gets a value indicating whether this type is open or not. + /// + public bool IsOpen + { + get { return _dynamicPropertyDictionary != null; } + } + + /// + /// Gets the CLR property info of the dynamic property dictionary on this structural type. + /// + public PropertyInfo DynamicPropertyDictionary + { + get { return _dynamicPropertyDictionary; } + } + + /// + /// Gets or sets a value indicating whether this type is abstract. + /// + public virtual bool? IsAbstract { get; set; } + + /// + /// Gets a value that represents whether the base type is explicitly configured or inferred. + /// + public virtual bool BaseTypeConfigured + { + get + { + return _baseTypeConfigured; + } + } + + /// + /// Gets the declared properties on this edm type. + /// + public IEnumerable Properties + { + get + { + return ExplicitProperties.Values; + } + } + + /// + /// Gets the properties from the backing CLR type that are to be ignored on this edm type. + /// + public ReadOnlyCollection IgnoredProperties + { + get + { + return new ReadOnlyCollection(RemovedProperties); + } + } + + /// + /// Gets the collection of of this entity type. + /// + public virtual IEnumerable NavigationProperties + { + get + { + return ExplicitProperties.Values.OfType(); + } + } + + /// + /// Gets or sets the . + /// + public QueryConfiguration QueryConfiguration { get; set; } + + /// + /// Gets or sets a value that is true if the type's name or namespace was set by the user; false if it was inferred through conventions. + /// + /// The default value is false. + public bool AddedExplicitly { get; set; } + + /// + /// The . + /// + public virtual ODataModelBuilder ModelBuilder { get; private set; } + + /// + /// Gets the collection of explicitly removed properties. + /// + protected internal IList RemovedProperties { get; private set; } + + /// + /// Gets the collection of explicitly added properties. + /// + protected internal IDictionary ExplicitProperties { get; private set; } + + /// + /// Gets the base type of this structural type. + /// + protected internal virtual StructuralTypeConfiguration BaseTypeInternal + { + get + { + return _baseType; + } + } + + internal virtual void AbstractImpl() + { + IsAbstract = true; + } + + internal virtual void DerivesFromNothingImpl() + { + _baseType = null; + _baseTypeConfigured = true; + } + + internal virtual void DerivesFromImpl(StructuralTypeConfiguration baseType) + { + if (baseType == null) + { + throw Error.ArgumentNull("baseType"); + } + + _baseType = baseType; + _baseTypeConfigured = true; + + if (!baseType.ClrType.IsAssignableFrom(ClrType) || baseType.ClrType == ClrType) + { + throw Error.Argument("baseType", SRResources.TypeDoesNotInheritFromBaseType, + ClrType.FullName, baseType.ClrType.FullName); + } + + foreach (PropertyConfiguration property in Properties) + { + ValidatePropertyNotAlreadyDefinedInBaseTypes(property.PropertyInfo); + } + + foreach (PropertyConfiguration property in this.DerivedProperties()) + { + ValidatePropertyNotAlreadyDefinedInDerivedTypes(property.PropertyInfo); + } + } + + /// + /// Adds a primitive property to this edm type. + /// + /// The property being added. + /// The so that the property can be configured further. + public virtual PrimitivePropertyConfiguration AddProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (!TypeHelper.GetReflectedType(propertyInfo).IsAssignableFrom(ClrType)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType, propertyInfo.Name, ClrType.FullName); + } + + ValidatePropertyNotAlreadyDefinedInBaseTypes(propertyInfo); + ValidatePropertyNotAlreadyDefinedInDerivedTypes(propertyInfo); + + // Remove from the ignored properties + if (RemovedProperties.Any(prop => prop.Name.Equals(propertyInfo.Name))) + { + RemovedProperties.Remove(RemovedProperties.First(prop => prop.Name.Equals(propertyInfo.Name))); + } + + PrimitivePropertyConfiguration propertyConfiguration = + ValidatePropertyNotAlreadyDefinedOtherTypes(propertyInfo, + SRResources.MustBePrimitiveProperty); + if (propertyConfiguration == null) + { + propertyConfiguration = new PrimitivePropertyConfiguration(propertyInfo, this); + var primitiveType = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(propertyInfo.PropertyType); + if (primitiveType != null) + { + if (primitiveType.PrimitiveKind == EdmPrimitiveTypeKind.Decimal) + { + propertyConfiguration = new DecimalPropertyConfiguration(propertyInfo, this); + } + else if (EdmLibHelpers.HasLength(primitiveType.PrimitiveKind)) + { + propertyConfiguration = new LengthPropertyConfiguration(propertyInfo, this); + } + else if (EdmLibHelpers.HasPrecision(primitiveType.PrimitiveKind)) + { + propertyConfiguration = new PrecisionPropertyConfiguration(propertyInfo, this); + } + } + ExplicitProperties[propertyInfo] = propertyConfiguration; + } + + return propertyConfiguration; + } + + /// + /// Adds an enum property to this edm type. + /// + /// The property being added. + /// The so that the property can be configured further. + public virtual EnumPropertyConfiguration AddEnumProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (!TypeHelper.GetReflectedType(propertyInfo).IsAssignableFrom(ClrType)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType, propertyInfo.Name, ClrType.FullName); + } + + if (!TypeHelper.IsEnum(propertyInfo.PropertyType)) + { + throw Error.Argument("propertyInfo", SRResources.MustBeEnumProperty, propertyInfo.Name, ClrType.FullName); + } + + ValidatePropertyNotAlreadyDefinedInBaseTypes(propertyInfo); + ValidatePropertyNotAlreadyDefinedInDerivedTypes(propertyInfo); + + // Remove from the ignored properties + if (RemovedProperties.Any(prop => prop.Name.Equals(propertyInfo.Name))) + { + RemovedProperties.Remove(RemovedProperties.First(prop => prop.Name.Equals(propertyInfo.Name))); + } + + EnumPropertyConfiguration propertyConfiguration = + ValidatePropertyNotAlreadyDefinedOtherTypes(propertyInfo, + SRResources.MustBeEnumProperty); + if (propertyConfiguration == null) + { + propertyConfiguration = new EnumPropertyConfiguration(propertyInfo, this); + ExplicitProperties[propertyInfo] = propertyConfiguration; + } + + return propertyConfiguration; + } + + /// + /// Adds a complex property to this edm type. + /// + /// The property being added. + /// The so that the property can be configured further. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Helper validates non null propertyInfo")] + public virtual ComplexPropertyConfiguration AddComplexProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (!TypeHelper.GetReflectedType(propertyInfo).IsAssignableFrom(ClrType)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType, propertyInfo.Name, ClrType.FullName); + } + + ValidatePropertyNotAlreadyDefinedInBaseTypes(propertyInfo); + ValidatePropertyNotAlreadyDefinedInDerivedTypes(propertyInfo); + + // Remove from the ignored properties + if (RemovedProperties.Any(prop => prop.Name.Equals(propertyInfo.Name))) + { + RemovedProperties.Remove(RemovedProperties.First(prop => prop.Name.Equals(propertyInfo.Name))); + } + + ComplexPropertyConfiguration propertyConfiguration = + ValidatePropertyNotAlreadyDefinedOtherTypes(propertyInfo, + SRResources.MustBeComplexProperty); + if (propertyConfiguration == null) + { + propertyConfiguration = new ComplexPropertyConfiguration(propertyInfo, this); + ExplicitProperties[propertyInfo] = propertyConfiguration; + // Make sure the complex type is in the model. + + ModelBuilder.AddComplexType(propertyInfo.PropertyType); + } + + return propertyConfiguration; + } + + /// + /// Adds a collection property to this edm type. + /// + /// The property being added. + /// The so that the property can be configured further. + public virtual CollectionPropertyConfiguration AddCollectionProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (!propertyInfo.DeclaringType.IsAssignableFrom(ClrType)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType); + } + + ValidatePropertyNotAlreadyDefinedInBaseTypes(propertyInfo); + ValidatePropertyNotAlreadyDefinedInDerivedTypes(propertyInfo); + + // Remove from the ignored properties + if (RemovedProperties.Any(prop => prop.Name.Equals(propertyInfo.Name))) + { + RemovedProperties.Remove(RemovedProperties.First(prop => prop.Name.Equals(propertyInfo.Name))); + } + + CollectionPropertyConfiguration propertyConfiguration = + ValidatePropertyNotAlreadyDefinedOtherTypes(propertyInfo, + SRResources.MustBeCollectionProperty); + if (propertyConfiguration == null) + { + propertyConfiguration = new CollectionPropertyConfiguration(propertyInfo, this); + ExplicitProperties[propertyInfo] = propertyConfiguration; + + // If the ElementType is not primitive or enum treat as a ComplexType and Add to the model. + IEdmPrimitiveTypeReference edmType = + EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(propertyConfiguration.ElementType); + if (edmType == null) + { + if (!TypeHelper.IsEnum(propertyConfiguration.ElementType)) + { + ModelBuilder.AddComplexType(propertyConfiguration.ElementType); + } + } + } + + return propertyConfiguration; + } + + /// + /// Adds the property info of the dynamic properties to this structural type. + /// + /// The property being added. + public virtual void AddDynamicPropertyDictionary(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (!typeof(IDictionary).IsAssignableFrom(propertyInfo.PropertyType)) + { + throw Error.Argument("propertyInfo", SRResources.ArgumentMustBeOfType, + "IDictionary"); + } + + if (!propertyInfo.DeclaringType.IsAssignableFrom(ClrType)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType); + } + + // Remove from the ignored properties + if (IgnoredProperties.Contains(propertyInfo)) + { + RemovedProperties.Remove(propertyInfo); + } + + if (_dynamicPropertyDictionary != null) + { + throw Error.Argument("propertyInfo", SRResources.MoreThanOneDynamicPropertyContainerFound, ClrType.Name); + } + + _dynamicPropertyDictionary = propertyInfo; + } + + /// + /// Removes the given property. + /// + /// The property being removed. + public virtual void RemoveProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (!TypeHelper.GetReflectedType(propertyInfo).IsAssignableFrom(ClrType)) + { + throw Error.Argument("propertyInfo", SRResources.PropertyDoesNotBelongToType, propertyInfo.Name, ClrType.FullName); + } + + if (ExplicitProperties.Keys.Any(key => key.Name.Equals(propertyInfo.Name))) + { + ExplicitProperties.Remove(ExplicitProperties.Keys.First(key => key.Name.Equals(propertyInfo.Name))); + } + + if (!RemovedProperties.Any(prop => prop.Name.Equals(propertyInfo.Name))) + { + RemovedProperties.Add(propertyInfo); + } + + if (_dynamicPropertyDictionary == propertyInfo) + { + _dynamicPropertyDictionary = null; + } + } + + /// + /// Adds a non-contained EDM navigation property to this entity type. + /// + /// The backing CLR property. + /// The of the navigation property. + /// Returns the of the added property. + public virtual NavigationPropertyConfiguration AddNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity) + { + return AddNavigationProperty(navigationProperty, multiplicity, containsTarget: false); + } + + /// + /// Adds a contained EDM navigation property to this entity type. + /// + /// The backing CLR property. + /// The of the navigation property. + /// Returns the of the added property. + public virtual NavigationPropertyConfiguration AddContainedNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity) + { + return AddNavigationProperty(navigationProperty, multiplicity, containsTarget: true); + } + + private NavigationPropertyConfiguration AddNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity, bool containsTarget) + { + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + + if (!TypeHelper.GetReflectedType(navigationProperty).IsAssignableFrom(ClrType)) + { + throw Error.Argument("navigationProperty", SRResources.PropertyDoesNotBelongToType, navigationProperty.Name, ClrType.FullName); + } + + ValidatePropertyNotAlreadyDefinedInBaseTypes(navigationProperty); + ValidatePropertyNotAlreadyDefinedInDerivedTypes(navigationProperty); + + PropertyConfiguration propertyConfig; + NavigationPropertyConfiguration navigationPropertyConfig; + + if (ExplicitProperties.ContainsKey(navigationProperty)) + { + propertyConfig = ExplicitProperties[navigationProperty]; + if (propertyConfig.Kind != PropertyKind.Navigation) + { + throw Error.Argument("navigationProperty", SRResources.MustBeNavigationProperty, navigationProperty.Name, ClrType.FullName); + } + + navigationPropertyConfig = propertyConfig as NavigationPropertyConfiguration; + if (navigationPropertyConfig.Multiplicity != multiplicity) + { + throw Error.Argument("navigationProperty", SRResources.MustHaveMatchingMultiplicity, navigationProperty.Name, multiplicity); + } + } + else + { + navigationPropertyConfig = new NavigationPropertyConfiguration( + navigationProperty, + multiplicity, + this); + if (containsTarget) + { + navigationPropertyConfig = navigationPropertyConfig.Contained(); + } + + ExplicitProperties[navigationProperty] = navigationPropertyConfig; + // make sure the related type is configured + ModelBuilder.AddEntityType(navigationPropertyConfig.RelatedClrType); + } + return navigationPropertyConfig; + } + + internal T ValidatePropertyNotAlreadyDefinedOtherTypes(PropertyInfo propertyInfo, string typeErrorMessage) where T : class + { + T propertyConfiguration = default(T); + var explicitPropertyInfo = ExplicitProperties.Keys.FirstOrDefault(key => key.Name.Equals(propertyInfo.Name)); + if (explicitPropertyInfo != null) + { + propertyConfiguration = ExplicitProperties[explicitPropertyInfo] as T; + if (propertyConfiguration == default(T)) + { + throw Error.Argument("propertyInfo", typeErrorMessage, propertyInfo.Name, ClrType.FullName); + } + } + + return propertyConfiguration; + } + + internal void ValidatePropertyNotAlreadyDefinedInBaseTypes(PropertyInfo propertyInfo) + { + PropertyConfiguration baseProperty = + this.DerivedProperties().FirstOrDefault(p => p.Name == propertyInfo.Name); + if (baseProperty != null) + { + throw Error.Argument("propertyInfo", SRResources.CannotRedefineBaseTypeProperty, + propertyInfo.Name, TypeHelper.GetReflectedType(baseProperty.PropertyInfo).FullName); + } + } + + internal void ValidatePropertyNotAlreadyDefinedInDerivedTypes(PropertyInfo propertyInfo) + { + foreach (StructuralTypeConfiguration derivedType in ModelBuilder.DerivedTypes(this)) + { + PropertyConfiguration propertyInDerivedType = + derivedType.Properties.FirstOrDefault(p => p.Name == propertyInfo.Name); + if (propertyInDerivedType != null) + { + throw Error.Argument("propertyInfo", SRResources.PropertyAlreadyDefinedInDerivedType, + propertyInfo.Name, FullName, derivedType.FullName); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs new file mode 100644 index 0000000..4ad57a6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs @@ -0,0 +1,954 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be built using . + /// + public abstract class StructuralTypeConfiguration where TStructuralType : class + { + private StructuralTypeConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The inner configuration of the structural type. + protected StructuralTypeConfiguration(StructuralTypeConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + _configuration = configuration; + } + + /// + /// Gets the collection of EDM structural properties that belong to this type. + /// + public IEnumerable Properties + { + get { return _configuration.Properties; } + } + + /// + /// Gets the full name of this EDM type. + /// + public string FullName + { + get + { + return _configuration.FullName; + } + } + + /// + /// Gets and sets the namespace of this EDM type. + /// + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Justification = "Follow StructuralTypeConfiguration's naming")] + public string Namespace + { + get + { + return _configuration.Namespace; + } + set + { + _configuration.Namespace = value; + } + } + + /// + /// Gets and sets the name of this EDM type. + /// + public string Name + { + get + { + return _configuration.Name; + } + set + { + _configuration.Name = value; + } + } + + /// + /// Gets an indicator whether this EDM type is an open type or not. + /// Returns true if this is an open type; false otherwise. + /// + public bool IsOpen + { + get + { + return _configuration.IsOpen; + } + } + + internal StructuralTypeConfiguration Configuration + { + get { return _configuration; } + } + + /// + /// Excludes a property from the type. + /// + /// The property type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// This method is used to exclude properties from the type that would have been added by convention during model discovery. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + public virtual void Ignore(Expression> propertyExpression) + { + PropertyInfo ignoredProperty = PropertySelectorVisitor.GetSelectedProperty(propertyExpression); + _configuration.RemoveProperty(ignoredProperty); + } + + /// + /// Adds a string property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public LengthPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true) as LengthPropertyConfiguration; + } + + /// + /// Adds a binary property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public LengthPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true) as LengthPropertyConfiguration; + } + + /// + /// Adds a stream property the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrimitivePropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true); + } + + /// + /// Adds an deciaml primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public DecimalPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true) as DecimalPropertyConfiguration; + } + + /// + /// Adds an deciaml primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public DecimalPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: false) as DecimalPropertyConfiguration; + } + + /// + /// Adds an time-of-day primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrecisionPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true) as PrecisionPropertyConfiguration; + } + + /// + /// Adds an time-of-day primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrecisionPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: false) as PrecisionPropertyConfiguration; + } + + /// + /// Adds an duration primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrecisionPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true) as PrecisionPropertyConfiguration; + } + + /// + /// Adds an duration primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrecisionPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: false) as PrecisionPropertyConfiguration; + } + + /// + /// Adds an datetime-with-offset primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrecisionPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true) as PrecisionPropertyConfiguration; + } + + /// + /// Adds an datetime-with-offset primitive property to the EDM type. + /// + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrecisionPropertyConfiguration Property(Expression> propertyExpression) + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: false) as PrecisionPropertyConfiguration; + } + + /// + /// Adds an optional primitive property to the EDM type. + /// + /// The primitive property type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrimitivePropertyConfiguration Property(Expression> propertyExpression) where T : struct + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: true); + } + + /// + /// Adds a required primitive property to the EDM type. + /// + /// The primitive property type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public PrimitivePropertyConfiguration Property(Expression> propertyExpression) where T : struct + { + return GetPrimitivePropertyConfiguration(propertyExpression, optional: false); + } + + /// + /// Adds an optional enum property to the EDM type. + /// + /// The enum property type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public EnumPropertyConfiguration EnumProperty(Expression> propertyExpression) where T : struct + { + return GetEnumPropertyConfiguration(propertyExpression, optional: true); + } + + /// + /// Adds a required enum property to the EDM type. + /// + /// The enum property type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public EnumPropertyConfiguration EnumProperty(Expression> propertyExpression) where T : struct + { + return GetEnumPropertyConfiguration(propertyExpression, optional: false); + } + + /// + /// Adds a complex property to the EDM type. + /// + /// The complex type. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public ComplexPropertyConfiguration ComplexProperty(Expression> propertyExpression) + { + return GetComplexPropertyConfiguration(propertyExpression); + } + + /// + /// Adds a collection property to the EDM type. + /// + /// The element type of the collection. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the property. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "More specific expression type is clearer")] + public CollectionPropertyConfiguration CollectionProperty(Expression>> propertyExpression) + { + return GetCollectionPropertyConfiguration(propertyExpression); + } + + /// + /// Adds a dynamic property dictionary property. + /// + /// A lambda expression representing the dynamic property dictionary for the relationship. + /// For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generics appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "More specific expression type is clearer")] + public void HasDynamicProperties(Expression>> propertyExpression) + { + PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression); + + _configuration.AddDynamicPropertyDictionary(propertyInfo); + } + + /// + /// Configures a many relationship from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasMany(Expression>> navigationPropertyExpression) where TTargetEntity : class + { + return GetOrCreateNavigationProperty(navigationPropertyExpression, EdmMultiplicity.Many); + } + + /// + /// Configures an optional relationship from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasOptional(Expression> navigationPropertyExpression) where TTargetEntity : class + { + return GetOrCreateNavigationProperty(navigationPropertyExpression, EdmMultiplicity.ZeroOrOne); + } + + /// + /// Configures an optional relationship with referential constraint from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.Customer and in Visual Basic .NET Function(t) t.Customer. + /// A lambda expression representing the referential constraint. For example, + /// in C# (o, c) => o.CustomerId == c.Id and in Visual Basic .NET Function(o, c) c.CustomerId == c.Id. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasOptional( + Expression> navigationPropertyExpression, + Expression> referentialConstraintExpression) where TTargetEntity : class + { + return HasNavigationProperty( + navigationPropertyExpression, + referentialConstraintExpression, + EdmMultiplicity.ZeroOrOne, + null); + } + + /// + /// Configures an optional relationship with referential constraint from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.Customer and in Visual Basic .NET Function(t) t.Customer. + /// A lambda expression representing the referential constraint. For example, + /// in C# (o, c) => o.CustomerId == c.Id and in Visual Basic .NET Function(o, c) c.CustomerId == c.Id. + /// The partner expression for this relationship. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasOptional( + Expression> navigationPropertyExpression, + Expression> referentialConstraintExpression, + Expression>> partnerExpression) where TTargetEntity : class + { + return HasNavigationProperty( + navigationPropertyExpression, + referentialConstraintExpression, + EdmMultiplicity.ZeroOrOne, + partnerExpression); + } + + /// + /// Configures an optional relationship with referential constraint from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.Customer and in Visual Basic .NET Function(t) t.Customer. + /// A lambda expression representing the referential constraint. For example, + /// in C# (o, c) => o.CustomerId == c.Id and in Visual Basic .NET Function(o, c) c.CustomerId == c.Id. + /// The partner expression for this relationship. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasOptional( + Expression> navigationPropertyExpression, + Expression> referentialConstraintExpression, + Expression> partnerExpression) where TTargetEntity : class + { + return HasNavigationProperty( + navigationPropertyExpression, + referentialConstraintExpression, + EdmMultiplicity.ZeroOrOne, + partnerExpression); + } + + /// + /// Configures a required relationship from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. For example, in C# t => t.MyProperty and in Visual Basic .NET Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasRequired(Expression> navigationPropertyExpression) where TTargetEntity : class + { + return GetOrCreateNavigationProperty(navigationPropertyExpression, EdmMultiplicity.One); + } + + /// + /// Configures a required relationship with referential constraint from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.Customer and in Visual Basic .NET Function(t) t.Customer. + /// A lambda expression representing the referential constraint. For example, + /// in C# (o, c) => o.CustomerId == c.Id and in Visual Basic .NET Function(o, c) c.CustomerId == c.Id. + /// The partner expression for this relationship. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasRequired( + Expression> navigationPropertyExpression, + Expression> referentialConstraintExpression, + Expression>> partnerExpression) where TTargetEntity : class + { + return HasNavigationProperty(navigationPropertyExpression, referentialConstraintExpression, EdmMultiplicity.One, partnerExpression); + } + + /// + /// Configures a required relationship with referential constraint from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.Customer and in Visual Basic .NET Function(t) t.Customer. + /// A lambda expression representing the referential constraint. For example, + /// in C# (o, c) => o.CustomerId == c.Id and in Visual Basic .NET Function(o, c) c.CustomerId == c.Id. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasRequired( + Expression> navigationPropertyExpression, + Expression> referentialConstraintExpression) where TTargetEntity : class + { + return HasNavigationProperty(navigationPropertyExpression, referentialConstraintExpression, EdmMultiplicity.One, null); + } + + /// + /// Configures a required relationship with referential constraint from this structural type. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for the relationship. + /// For example, in C# t => t.Customer and in Visual Basic .NET Function(t) t.Customer. + /// A lambda expression representing the referential constraint. For example, + /// in C# (o, c) => o.CustomerId == c.Id and in Visual Basic .NET Function(o, c) c.CustomerId == c.Id. + /// + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration HasRequired( + Expression> navigationPropertyExpression, + Expression> referentialConstraintExpression, + Expression> partnerExpression) where TTargetEntity : class + { + return HasNavigationProperty(navigationPropertyExpression, referentialConstraintExpression, EdmMultiplicity.One, partnerExpression); + } + + private NavigationPropertyConfiguration HasNavigationProperty(Expression> navigationPropertyExpression, + Expression> referentialConstraintExpression, EdmMultiplicity multiplicity, Expression partnerProperty) + where TTargetEntity : class + { + NavigationPropertyConfiguration navigation = + GetOrCreateNavigationProperty(navigationPropertyExpression, multiplicity); + + IDictionary referentialConstraints = + PropertyPairSelectorVisitor.GetSelectedProperty(referentialConstraintExpression); + + foreach (KeyValuePair constraint in referentialConstraints) + { + navigation.HasConstraint(constraint); + } + + if (partnerProperty != null) + { + var partnerPropertyInfo = PropertySelectorVisitor.GetSelectedProperty(partnerProperty); + if (typeof(IEnumerable).IsAssignableFrom(partnerPropertyInfo.PropertyType)) + { + _configuration.ModelBuilder + .EntityType().HasMany((Expression>>)partnerProperty); + } + else + { + _configuration.ModelBuilder + .EntityType().HasRequired((Expression>)partnerProperty); + } + var prop = _configuration.ModelBuilder + .EntityType() + .Properties + .First(p => p.Name == partnerPropertyInfo.Name) + as NavigationPropertyConfiguration; + + navigation.Partner = prop; + } + + return navigation; + } + + /// + /// Configures a relationship from this structural type to a contained collection navigation property. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for + /// the relationship. For example, in C# t => t.MyProperty and in Visual Basic .NET + /// Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration ContainsMany( + Expression>> navigationPropertyExpression) + where TTargetEntity : class + { + return GetOrCreateContainedNavigationProperty(navigationPropertyExpression, EdmMultiplicity.Many); + } + + /// + /// Configures an optional relationship from this structural type to a single contained navigation property. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for + /// the relationship. For example, in C# t => t.MyProperty and in Visual Basic .NET + /// Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration ContainsOptional( + Expression> navigationPropertyExpression) where TTargetEntity : class + { + return GetOrCreateContainedNavigationProperty(navigationPropertyExpression, EdmMultiplicity.ZeroOrOne); + } + + /// + /// Configures a required relationship from this structural type to a single contained navigation property. + /// + /// The type of the entity at the other end of the relationship. + /// A lambda expression representing the navigation property for + /// the relationship. For example, in C# t => t.MyProperty and in Visual Basic .NET + /// Function(t) t.MyProperty. + /// A configuration object that can be used to further configure the relationship. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Nested generic appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "Explicit Expression generic type is more clear")] + public NavigationPropertyConfiguration ContainsRequired( + Expression> navigationPropertyExpression) where TTargetEntity : class + { + return GetOrCreateContainedNavigationProperty(navigationPropertyExpression, EdmMultiplicity.One); + } + + /// + /// Sets this property is countable of this structural type. + /// + public StructuralTypeConfiguration Count() + { + _configuration.QueryConfiguration.SetCount(true); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets whether this property is countable of this structural type. + /// + public StructuralTypeConfiguration Count(QueryOptionSetting setting) + { + _configuration.QueryConfiguration.SetCount(setting == QueryOptionSetting.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets sortable properties depends on of this structural type. + /// + public StructuralTypeConfiguration OrderBy(QueryOptionSetting setting, params string[] properties) + { + _configuration.QueryConfiguration.SetOrderBy(properties, setting == QueryOptionSetting.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets sortable properties of this structural type. + /// + public StructuralTypeConfiguration OrderBy(params string[] properties) + { + _configuration.QueryConfiguration.SetOrderBy(properties, true); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets whether all properties of this structural type is sortable. + /// + public StructuralTypeConfiguration OrderBy(QueryOptionSetting setting) + { + _configuration.QueryConfiguration.SetOrderBy(null, setting == QueryOptionSetting.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets all properties of this structural type is sortable. + /// + public StructuralTypeConfiguration OrderBy() + { + _configuration.QueryConfiguration.SetOrderBy(null, true); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets filterable properties depends on of this structural type. + /// + public StructuralTypeConfiguration Filter(QueryOptionSetting setting, params string[] properties) + { + _configuration.QueryConfiguration.SetFilter(properties, setting == QueryOptionSetting.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets filterable properties of this structural type. + /// + public StructuralTypeConfiguration Filter(params string[] properties) + { + _configuration.QueryConfiguration.SetFilter(properties, true); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets whether all properties of this structural type is filterable. + /// + public StructuralTypeConfiguration Filter(QueryOptionSetting setting) + { + _configuration.QueryConfiguration.SetFilter(null, setting == QueryOptionSetting.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets all properties of this structural type is filterable. + /// + public StructuralTypeConfiguration Filter() + { + _configuration.QueryConfiguration.SetFilter(null, true); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets selectable properties depends on of this structural type. + /// + public StructuralTypeConfiguration Select(SelectExpandType selectType, + params string[] properties) + { + _configuration.QueryConfiguration.SetSelect(properties, selectType); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets selectable properties of this structural type. + /// + public StructuralTypeConfiguration Select(params string[] properties) + { + _configuration.QueryConfiguration.SetSelect(properties, SelectExpandType.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets of all properties of this structural type is selectable. + /// + public StructuralTypeConfiguration Select(SelectExpandType selectType) + { + _configuration.QueryConfiguration.SetSelect(null, selectType); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets all properties of this structural type is selectable. + /// + public StructuralTypeConfiguration Select() + { + _configuration.QueryConfiguration.SetSelect(null, SelectExpandType.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets the max value of $top of this structural type that a client can request + /// and the maximum number of query results of this entity type to return. + /// + public StructuralTypeConfiguration Page(int? maxTopValue, int? pageSizeValue) + { + _configuration.QueryConfiguration.SetMaxTop(maxTopValue); + _configuration.QueryConfiguration.SetPageSize(pageSizeValue); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets the properties of this structural type enable paging. + /// + public StructuralTypeConfiguration Page() + { + _configuration.QueryConfiguration.SetMaxTop(null); + _configuration.QueryConfiguration.SetPageSize(null); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets the maximum depth of expand result, + /// expandable properties and their of this structural type. + /// + public StructuralTypeConfiguration Expand(int maxDepth, SelectExpandType expandType, params string[] properties) + { + _configuration.QueryConfiguration.SetExpand(properties, maxDepth, expandType); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets the expandable properties of this structural type. + /// + public StructuralTypeConfiguration Expand(params string[] properties) + { + _configuration.QueryConfiguration.SetExpand(properties, null, SelectExpandType.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets the maximum depth of expand result, + /// expandable properties of this structural type. + /// + public StructuralTypeConfiguration Expand(int maxDepth, params string[] properties) + { + _configuration.QueryConfiguration.SetExpand(properties, maxDepth, SelectExpandType.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets the expandable properties and their of this structural type. + /// + public StructuralTypeConfiguration Expand(SelectExpandType expandType, params string[] properties) + { + _configuration.QueryConfiguration.SetExpand(properties, null, expandType); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets of all properties with maximum depth of expand result of this structural type. + /// + public StructuralTypeConfiguration Expand(SelectExpandType expandType, int maxDepth) + { + _configuration.QueryConfiguration.SetExpand(null, maxDepth, expandType); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets all properties expandable with maximum depth of expand result of this structural type. + /// + public StructuralTypeConfiguration Expand(int maxDepth) + { + _configuration.QueryConfiguration.SetExpand(null, maxDepth, SelectExpandType.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets of all properties of this structural type. + /// + public StructuralTypeConfiguration Expand(SelectExpandType expandType) + { + _configuration.QueryConfiguration.SetExpand(null, null, expandType); + _configuration.AddedExplicitly = true; + return this; + } + + /// + /// Sets all properties expandable of this structural type. + /// + public StructuralTypeConfiguration Expand() + { + _configuration.QueryConfiguration.SetExpand(null, null, SelectExpandType.Allowed); + _configuration.AddedExplicitly = true; + return this; + } + + internal NavigationPropertyConfiguration GetOrCreateNavigationProperty(Expression navigationPropertyExpression, EdmMultiplicity multiplicity) + { + PropertyInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression); + return _configuration.AddNavigationProperty(navigationProperty, multiplicity); + } + + internal NavigationPropertyConfiguration GetOrCreateContainedNavigationProperty(Expression navigationPropertyExpression, EdmMultiplicity multiplicity) + { + PropertyInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression); + return _configuration.AddContainedNavigationProperty(navigationProperty, multiplicity); + } + + private PrimitivePropertyConfiguration GetPrimitivePropertyConfiguration(Expression propertyExpression, bool optional) + { + PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression); + PrimitivePropertyConfiguration property = _configuration.AddProperty(propertyInfo); + if (optional) + { + property.IsOptional(); + } + + return property; + } + + private EnumPropertyConfiguration GetEnumPropertyConfiguration(Expression propertyExpression, bool optional) + { + PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression); + + EnumPropertyConfiguration property = _configuration.AddEnumProperty(propertyInfo); + if (optional) + { + property.IsOptional(); + } + + return property; + } + + private ComplexPropertyConfiguration GetComplexPropertyConfiguration(Expression propertyExpression, bool optional = false) + { + PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression); + ComplexPropertyConfiguration property = _configuration.AddComplexProperty(propertyInfo); + if (optional) + { + property.IsOptional(); + } + else + { + property.IsRequired(); + } + + return property; + } + + private CollectionPropertyConfiguration GetCollectionPropertyConfiguration(Expression propertyExpression, bool optional = false) + { + PropertyInfo propertyInfo = PropertySelectorVisitor.GetSelectedProperty(propertyExpression); + CollectionPropertyConfiguration property; + + property = _configuration.AddCollectionProperty(propertyInfo); + + if (optional) + { + property.IsOptional(); + } + else + { + property.IsRequired(); + } + + return property; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrEnumMemberAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrEnumMemberAnnotation.cs new file mode 100644 index 0000000..c5d7454 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrEnumMemberAnnotation.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a mapping betwwen an and a CLR Enum member. + /// + public class ClrEnumMemberAnnotation + { + private IDictionary _map; + private IDictionary _reverseMap; + + /// + /// Initializes a new instance of class. + /// + /// The mapping between CLR Enum member and the EDM . + public ClrEnumMemberAnnotation(IDictionary map) + { + if (map == null) + { + throw Error.ArgumentNull("map"); + } + + _map = map; + _reverseMap = new Dictionary(); + foreach (var item in map) + { + _reverseMap.Add(item.Value, item.Key); + } + } + + /// + /// Gets the for the CLR Enum member. + /// + /// The backing CLR Enum member info. + /// The Edm . + public IEdmEnumMember GetEdmEnumMember(Enum clrEnumMemberInfo) + { + IEdmEnumMember value; + _map.TryGetValue(clrEnumMemberInfo, out value); + return value; + } + + /// + /// Gets the CLR Enum member for . + /// + /// The Edm . + /// The backing CLR Enum member info. + public Enum GetClrEnumMember(IEdmEnumMember edmEnumMember) + { + Enum value; + _reverseMap.TryGetValue(edmEnumMember, out value); + return value; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs new file mode 100644 index 0000000..51dfc22 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrPropertyInfoAnnotation.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a mapping from an to a CLR property info. + /// + public class ClrPropertyInfoAnnotation + { + /// + /// Initializes a new instance of class. + /// + /// The backing CLR property info for the EDM property. + public ClrPropertyInfoAnnotation(PropertyInfo clrPropertyInfo) + { + if (clrPropertyInfo == null) + { + throw Error.ArgumentNull("clrPropertyInfo"); + } + + ClrPropertyInfo = clrPropertyInfo; + } + + /// + /// Gets the backing CLR property info for the EDM property. + /// + public PropertyInfo ClrPropertyInfo { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrTypeAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrTypeAnnotation.cs new file mode 100644 index 0000000..f41bd4b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ClrTypeAnnotation.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a mapping from an to a CLR type. + /// + public class ClrTypeAnnotation + { + /// + /// Initializes a new instance of class. + /// + /// The backing CLR type for the EDM type. + public ClrTypeAnnotation(Type clrType) + { + ClrType = clrType; + } + + /// + /// Gets the backing CLR type for the EDM type. + /// + public Type ClrType { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CollectionExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CollectionExtensions.cs new file mode 100644 index 0000000..68c6530 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CollectionExtensions.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Microsoft.AspNet.OData.Common +{ + /// + /// Helper extension methods for fast use of collections. + /// + internal static class CollectionExtensions + { + /// + /// Return a new array with the value added to the end. Slow and best suited to long lived arrays with few writes relative to reads. + /// + public static T[] AppendAndReallocate(this T[] array, T value) + { + Contract.Assert(array != null); + + int originalLength = array.Length; + T[] newArray = new T[originalLength + 1]; + array.CopyTo(newArray, 0); + newArray[originalLength] = value; + return newArray; + } + + /// + /// Return the enumerable as an Array, copying if required. Optimized for common case where it is an Array. + /// Avoid mutating the return value. + /// + public static T[] AsArray(this IEnumerable values) + { + Contract.Assert(values != null); + + T[] array = values as T[]; + if (array == null) + { + array = values.ToArray(); + } + return array; + } + + /// + /// Return the enumerable as a Collection of T, copying if required. Optimized for the common case where it is + /// a Collection of T and avoiding a copy if it implements IList of T. Avoid mutating the return value. + /// + public static Collection AsCollection(this IEnumerable enumerable) + { + Contract.Assert(enumerable != null); + + Collection collection = enumerable as Collection; + if (collection != null) + { + return collection; + } + // Check for IList so that collection can wrap it instead of copying + IList list = enumerable as IList; + if (list == null) + { + list = new List(enumerable); + } + return new Collection(list); + } + + /// + /// Return the enumerable as a IList of T, copying if required. Avoid mutating the return value. + /// + public static IList AsIList(this IEnumerable enumerable) + { + Contract.Assert(enumerable != null); + + IList list = enumerable as IList; + if (list != null) + { + return list; + } + return new List(enumerable); + } + + /// + /// Return the enumerable as a List of T, copying if required. Optimized for common case where it is an List of T + /// or a ListWrapperCollection of T. Avoid mutating the return value. + /// + public static List AsList(this IEnumerable enumerable) + { + Contract.Assert(enumerable != null); + + List list = enumerable as List; + if (list != null) + { + return list; + } + ListWrapperCollection listWrapper = enumerable as ListWrapperCollection; + if (listWrapper != null) + { + return listWrapper.ItemsList; + } + return new List(enumerable); + } + + /// + /// Remove values from the list starting at the index start. + /// + public static void RemoveFrom(this List list, int start) + { + Contract.Assert(list != null); + Contract.Assert(start >= 0 && start <= list.Count); + + list.RemoveRange(start, list.Count - start); + } + + /// + /// Return the only value from list, the type's default value if empty, or call the errorAction for 2 or more. + /// + public static T SingleDefaultOrError(this IList list, Action errorAction, TArg1 errorArg1) + { + Contract.Assert(list != null); + Contract.Assert(errorAction != null); + + switch (list.Count) + { + case 0: + return default(T); + + case 1: + T value = list[0]; + return value; + + default: + errorAction(errorArg1); + return default(T); + } + } + + /// + /// Returns a single value in list matching type TMatch if there is only one, null if there are none of type TMatch or calls the + /// errorAction with errorArg1 if there is more than one. + /// + public static TMatch SingleOfTypeDefaultOrError(this IList list, Action errorAction, TArg1 errorArg1) where TMatch : class + { + Contract.Assert(list != null); + Contract.Assert(errorAction != null); + + TMatch result = null; + for (int i = 0; i < list.Count; i++) + { + TMatch typedValue = list[i] as TMatch; + if (typedValue != null) + { + if (result == null) + { + result = typedValue; + } + else + { + errorAction(errorArg1); + return null; + } + } + } + return result; + } + + /// + /// Convert an ICollection to an array, removing null values. Fast path for case where there are no null values. + /// + public static T[] ToArrayWithoutNulls(this ICollection collection) where T : class + { + Contract.Assert(collection != null); + + T[] result = new T[collection.Count]; + int count = 0; + foreach (T value in collection) + { + if (value != null) + { + result[count] = value; + count++; + } + } + if (count == collection.Count) + { + return result; + } + else + { + T[] trimmedResult = new T[count]; + Array.Copy(result, trimmedResult, count); + return trimmedResult; + } + } + + /// + /// Convert the array to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for array input. + /// + public static Dictionary ToDictionaryFast(this TValue[] array, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(array != null); + Contract.Assert(keySelector != null); + + Dictionary dictionary = new Dictionary(array.Length, comparer); + for (int i = 0; i < array.Length; i++) + { + TValue value = array[i]; + dictionary.Add(keySelector(value), value); + } + return dictionary; + } + + /// + /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input with fast path for array. + /// + public static Dictionary ToDictionaryFast(this IList list, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(list != null); + Contract.Assert(keySelector != null); + + TValue[] array = list as TValue[]; + if (array != null) + { + return ToDictionaryFast(array, keySelector, comparer); + } + return ToDictionaryFastNoCheck(list, keySelector, comparer); + } + + /// + /// Convert the enumerable to a Dictionary using the keySelector to extract keys from values and the specified comparer. Fast paths for array and IList of T. + /// + public static Dictionary ToDictionaryFast(this IEnumerable enumerable, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(enumerable != null); + Contract.Assert(keySelector != null); + + TValue[] array = enumerable as TValue[]; + if (array != null) + { + return ToDictionaryFast(array, keySelector, comparer); + } + IList list = enumerable as IList; + if (list != null) + { + return ToDictionaryFastNoCheck(list, keySelector, comparer); + } + Dictionary dictionary = new Dictionary(comparer); + foreach (TValue value in enumerable) + { + dictionary.Add(keySelector(value), value); + } + return dictionary; + } + + /// + /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input. No checking for other types. + /// + private static Dictionary ToDictionaryFastNoCheck(IList list, Func keySelector, IEqualityComparer comparer) + { + Contract.Assert(list != null); + Contract.Assert(keySelector != null); + + int listCount = list.Count; + Dictionary dictionary = new Dictionary(listCount, comparer); + for (int i = 0; i < listCount; i++) + { + TValue value = list[i]; + dictionary.Add(keySelector(value), value); + } + return dictionary; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.Designer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.Designer.cs new file mode 100644 index 0000000..7d07931 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.Designer.cs @@ -0,0 +1,138 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.AspNet.OData.Common +{ + using System; + using System.Linq; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CommonWebApiResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CommonWebApiResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + Assembly assembly = TypeHelper.GetAssembly(typeof(CommonWebApiResources)); + + // Find the CommonResources.resources file's full resource name in this assembly + string commonResourcesName = assembly.GetManifestResourceNames().Where(s => s.EndsWith("CommonWebApiResources.resources", StringComparison.OrdinalIgnoreCase)).Single(); + + // Trim off the ".resources" + commonResourcesName = commonResourcesName.Substring(0, commonResourcesName.Length - 10); + + // Load the resource manager + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(commonResourcesName, assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Relative URI values are not supported: '{0}'. The URI must be absolute.. + /// + internal static string ArgumentInvalidAbsoluteUri { + get { + return ResourceManager.GetString("ArgumentInvalidAbsoluteUri", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported URI scheme: '{0}'. The URI scheme must be either '{1}' or '{2}'.. + /// + internal static string ArgumentInvalidHttpUriScheme { + get { + return ResourceManager.GetString("ArgumentInvalidHttpUriScheme", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be greater than or equal to {0}.. + /// + internal static string ArgumentMustBeGreaterThanOrEqualTo { + get { + return ResourceManager.GetString("ArgumentMustBeGreaterThanOrEqualTo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be less than or equal to {0}.. + /// + internal static string ArgumentMustBeLessThanOrEqualTo { + get { + return ResourceManager.GetString("ArgumentMustBeLessThanOrEqualTo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument '{0}' is null or empty.. + /// + internal static string ArgumentNullOrEmpty { + get { + return ResourceManager.GetString("ArgumentNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to URI must not contain a query component or a fragment identifier.. + /// + internal static string ArgumentUriHasQueryOrFragment { + get { + return ResourceManager.GetString("ArgumentUriHasQueryOrFragment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.. + /// + internal static string InvalidEnumArgument { + get { + return ResourceManager.GetString("InvalidEnumArgument", resourceCulture); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.resx b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.resx new file mode 100644 index 0000000..3fa56b1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/CommonWebApiResources.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Relative URI values are not supported: '{0}'. The URI must be absolute. + + + Unsupported URI scheme: '{0}'. The URI scheme must be either '{1}' or '{2}'. + + + Value must be greater than or equal to {0}. + + + Value must be less than or equal to {0}. + + + The argument '{0}' is null or empty. + + + URI must not contain a query component or a fragment identifier. + + + The value of argument '{0}' ({1}) is invalid for Enum type '{2}'. + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/Error.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/Error.cs new file mode 100644 index 0000000..90a7261 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/Error.cs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +namespace Microsoft.AspNet.OData.Common +{ + /// + /// Utility class for creating and unwrapping instances. + /// + internal static class Error + { + private const string HttpScheme = "http"; + private const string HttpsScheme = "https"; + + /// + /// Formats the specified resource string using . + /// + /// A composite format string. + /// An object array that contains zero or more objects to format. + /// The formatted string. + internal static string Format(string format, params object[] args) + { + return String.Format(CultureInfo.CurrentCulture, format, args); + } + + /// + /// Creates an with the provided properties. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentException Argument(string messageFormat, params object[] messageArgs) + { + return new ArgumentException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentException Argument(string parameterName, string messageFormat, params object[] messageArgs) + { + return new ArgumentException(Error.Format(messageFormat, messageArgs), parameterName); + } + + /// + /// Creates an with a message saying that the argument must be an "http" or "https" URI. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The logged . + internal static ArgumentException ArgumentUriNotHttpOrHttpsScheme(string parameterName, Uri actualValue) + { + return new ArgumentException(Error.Format(CommonWebApiResources.ArgumentInvalidHttpUriScheme, actualValue, HttpScheme, HttpsScheme), parameterName); + } + + /// + /// Creates an with a message saying that the argument must be an absolute URI. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The logged . + internal static ArgumentException ArgumentUriNotAbsolute(string parameterName, Uri actualValue) + { + return new ArgumentException(Error.Format(CommonWebApiResources.ArgumentInvalidAbsoluteUri, actualValue), parameterName); + } + + /// + /// Creates an with a message saying that the argument must be an absolute URI + /// without a query or fragment identifier and then logs it with . + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The logged . + internal static ArgumentException ArgumentUriHasQueryOrFragment(string parameterName, Uri actualValue) + { + return new ArgumentException(Error.Format(CommonWebApiResources.ArgumentUriHasQueryOrFragment, actualValue), parameterName); + } + + /// + /// Creates an with the provided properties. + /// + /// The logged . + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "The purpose of this API is to return an error for properties")] + internal static ArgumentNullException PropertyNull() + { + return new ArgumentNullException("value"); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// The logged . + internal static ArgumentNullException ArgumentNull(string parameterName) + { + return new ArgumentNullException(parameterName); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentNullException ArgumentNull(string parameterName, string messageFormat, params object[] messageArgs) + { + return new ArgumentNullException(parameterName, Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an with a default message. + /// + /// The name of the parameter that caused the current exception. + /// The logged . + internal static ArgumentException ArgumentNullOrEmpty(string parameterName) + { + return Error.Argument(parameterName, CommonWebApiResources.ArgumentNullOrEmpty, parameterName); + } + + /// + /// Creates an with the provided properties. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ArgumentOutOfRangeException ArgumentOutOfRange(string parameterName, object actualValue, string messageFormat, params object[] messageArgs) + { + return new ArgumentOutOfRangeException(parameterName, actualValue, Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an with a message saying that the argument must be greater than or equal to . + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The minimum size of the argument. + /// The logged . + internal static ArgumentOutOfRangeException ArgumentMustBeGreaterThanOrEqualTo(string parameterName, object actualValue, object minValue) + { + return new ArgumentOutOfRangeException(parameterName, actualValue, Error.Format(CommonWebApiResources.ArgumentMustBeGreaterThanOrEqualTo, minValue)); + } + + /// + /// Creates an with a message saying that the argument must be less than or equal to . + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that causes this exception. + /// The maximum size of the argument. + /// The logged . + internal static ArgumentOutOfRangeException ArgumentMustBeLessThanOrEqualTo(string parameterName, object actualValue, object maxValue) + { + return new ArgumentOutOfRangeException(parameterName, actualValue, Error.Format(CommonWebApiResources.ArgumentMustBeLessThanOrEqualTo, maxValue)); + } + + /// + /// Creates an with a message saying that the key was not found. + /// + /// The logged . + internal static KeyNotFoundException KeyNotFound() + { + return new KeyNotFoundException(); + } + + /// + /// Creates an with a message saying that the key was not found. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static KeyNotFoundException KeyNotFound(string messageFormat, params object[] messageArgs) + { + return new KeyNotFoundException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an initialized according to guidelines. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static ObjectDisposedException ObjectDisposed(string messageFormat, params object[] messageArgs) + { + // Pass in null, not disposedObject.GetType().FullName as per the above guideline + return new ObjectDisposedException(null, Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an initialized with the provided parameters. + /// + /// The logged . + internal static OperationCanceledException OperationCanceled() + { + return new OperationCanceledException(); + } + + /// + /// Creates an initialized with the provided parameters. + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static OperationCanceledException OperationCanceled(string messageFormat, params object[] messageArgs) + { + return new OperationCanceledException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an for an invalid enum argument. + /// + /// The name of the parameter that caused the current exception. + /// The value of the argument that failed. + /// A that represents the enumeration class with the valid values. + /// The logged . + internal static ArgumentException InvalidEnumArgument(string parameterName, int invalidValue, Type enumClass) + { + return new InvalidEnumArgumentException(parameterName, invalidValue, enumClass); + } + + /// + /// Creates an . + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static InvalidOperationException InvalidOperation(string messageFormat, params object[] messageArgs) + { + return new InvalidOperationException(Error.Format(messageFormat, messageArgs)); + } + + /// + /// Creates an . + /// + /// Inner exception + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static InvalidOperationException InvalidOperation(Exception innerException, string messageFormat, params object[] messageArgs) + { + return new InvalidOperationException(Error.Format(messageFormat, messageArgs), innerException); + } + + /// + /// Creates an . + /// + /// A composite format string explaining the reason for the exception. + /// An object array that contains zero or more objects to format. + /// The logged . + internal static NotSupportedException NotSupported(string messageFormat, params object[] messageArgs) + { + return new NotSupportedException(Error.Format(messageFormat, messageArgs)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/ListWrapperCollection.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/ListWrapperCollection.cs new file mode 100644 index 0000000..16fba86 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/ListWrapperCollection.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Microsoft.AspNet.OData.Common +{ + /// + /// A class that inherits from Collection of T but also exposes its underlying data as List of T for performance. + /// + internal sealed class ListWrapperCollection : Collection + { + private readonly List _items; + + internal ListWrapperCollection() + : this(new List()) + { + } + + internal ListWrapperCollection(List list) + : base(list) + { + _items = list; + } + + internal List ItemsList + { + get { return _items; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/PropertyHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/PropertyHelper.cs new file mode 100644 index 0000000..5dd6b41 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/PropertyHelper.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNet.OData.Common +{ + internal class PropertyHelper + { + private static ConcurrentDictionary _reflectionCache = new ConcurrentDictionary(); + + private Func _valueGetter; + + /// + /// Initializes a fast property helper. This constructor does not cache the helper. + /// + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "This is intended the Name is auto set differently per type and the type is internal")] + public PropertyHelper(PropertyInfo property) + { + Contract.Assert(property != null); + + Name = property.Name; + _valueGetter = MakeFastPropertyGetter(property); + } + + /// + /// Creates a single fast property setter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast setter. + /// This method is more memory efficient than a dynamically compiled lambda, and about the same speed. + public static Action MakeFastPropertySetter(PropertyInfo propertyInfo) + where TDeclaringType : class + { + Contract.Assert(propertyInfo != null); + + MethodInfo setMethod = propertyInfo.GetSetMethod(); + + Contract.Assert(setMethod != null); + Contract.Assert(!setMethod.IsStatic); + Contract.Assert(setMethod.GetParameters().Length == 1); + Contract.Assert(!TypeHelper.IsValueType(TypeHelper.GetReflectedType(propertyInfo))); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "this". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + Type typeInput = TypeHelper.GetReflectedType(propertyInfo); + Type typeValue = setMethod.GetParameters()[0].ParameterType; + + Delegate callPropertySetterDelegate; + + // Create a delegate TValue -> "TDeclaringType.Property" + var propertySetterAsAction = setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, typeValue)); + var callPropertySetterClosedGenericMethod = _callPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, typeValue); + callPropertySetterDelegate = callPropertySetterClosedGenericMethod.CreateDelegate(typeof(Action), propertySetterAsAction); + + return (Action)callPropertySetterDelegate; + } + + public virtual string Name { get; protected set; } + + public object GetValue(object instance) + { + Contract.Assert(_valueGetter != null, "Must call Initialize before using this object"); + + return _valueGetter(instance); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the underlying type. + /// + /// the instance to extract property accessors for. + /// a cached array of all public property getters from the underlying type of this instance. + public static PropertyHelper[] GetProperties(object instance) + { + return GetProperties(instance, CreateInstance, _reflectionCache); + } + + /// + /// Creates a single fast property getter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// This method is more memory efficient than a dynamically compiled lambda, and about the same speed. + public static Func MakeFastPropertyGetter(PropertyInfo propertyInfo) + { + Contract.Assert(propertyInfo != null); + + MethodInfo getMethod = propertyInfo.GetGetMethod(); + Contract.Assert(getMethod != null); + Contract.Assert(!getMethod.IsStatic); + Contract.Assert(getMethod.GetParameters().Length == 0); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "this". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + Type typeInput = TypeHelper.GetReflectedType(getMethod); + Type typeOutput = getMethod.ReturnType; + + Delegate callPropertyGetterDelegate; + if (TypeHelper.IsValueType(typeInput)) + { + // Create a delegate (ref TDeclaringType) -> TValue + Delegate propertyGetterAsFunc = getMethod.CreateDelegate(typeof(ByRefFunc<,>).MakeGenericType(typeInput, typeOutput)); + MethodInfo callPropertyGetterClosedGenericMethod = _callPropertyGetterByReferenceOpenGenericMethod.MakeGenericMethod(typeInput, typeOutput); + callPropertyGetterDelegate = callPropertyGetterClosedGenericMethod.CreateDelegate(typeof(Func), propertyGetterAsFunc); + } + else + { + // Create a delegate TDeclaringType -> TValue + Delegate propertyGetterAsFunc = getMethod.CreateDelegate(typeof(Func<,>).MakeGenericType(typeInput, typeOutput)); + MethodInfo callPropertyGetterClosedGenericMethod = _callPropertyGetterOpenGenericMethod.MakeGenericMethod(typeInput, typeOutput); + callPropertyGetterDelegate = callPropertyGetterClosedGenericMethod.CreateDelegate(typeof(Func), propertyGetterAsFunc); + } + + return (Func)callPropertyGetterDelegate; + } + + private static PropertyHelper CreateInstance(PropertyInfo property) + { + return new PropertyHelper(property); + } + + // Implementation of the fast getter. + private delegate TValue ByRefFunc(ref TDeclaringType arg); + + private static readonly MethodInfo _callPropertyGetterOpenGenericMethod = typeof(PropertyHelper).GetMethod("CallPropertyGetter", BindingFlags.NonPublic | BindingFlags.Static); + private static readonly MethodInfo _callPropertyGetterByReferenceOpenGenericMethod = typeof(PropertyHelper).GetMethod("CallPropertyGetterByReference", BindingFlags.NonPublic | BindingFlags.Static); + + private static object CallPropertyGetter(Func getter, object @this) + { + return getter((TDeclaringType)@this); + } + + private static object CallPropertyGetterByReference(ByRefFunc getter, object @this) + { + TDeclaringType unboxed = (TDeclaringType)@this; + return getter(ref unboxed); + } + + // Implementation of the fast setter. + private static readonly MethodInfo _callPropertySetterOpenGenericMethod = typeof(PropertyHelper).GetMethod("CallPropertySetter", BindingFlags.NonPublic | BindingFlags.Static); + + private static void CallPropertySetter(Action setter, object @this, object value) + { + setter((TDeclaringType)@this, (TValue)value); + } + + protected static PropertyHelper[] GetProperties(object instance, + Func createPropertyHelper, + ConcurrentDictionary cache) + { + // Using an array rather than IEnumerable, as this will be called on the hot path numerous times. + PropertyHelper[] helpers; + + Type type = instance.GetType(); + + if (!cache.TryGetValue(type, out helpers)) + { + // We avoid loading indexed properties using the where statement. + // Indexed properties are not useful (or valid) for grabbing properties off an anonymous object. + IEnumerable properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(prop => prop.GetIndexParameters().Length == 0 && + prop.GetMethod != null); + + var newHelpers = new List(); + + foreach (PropertyInfo property in properties) + { + PropertyHelper propertyHelper = createPropertyHelper(property); + + newHelpers.Add(propertyHelper); + } + + helpers = newHelpers.ToArray(); + cache.TryAdd(type, helpers); + } + + return helpers; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs new file mode 100644 index 0000000..a2f7118 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.Designer.cs @@ -0,0 +1,2452 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.AspNet.OData.Common +{ + using System; + using System.Linq; + using System.Reflection; + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SRResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SRResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + Assembly assembly = TypeHelper.GetAssembly(typeof(CommonWebApiResources)); + + // Find the CommonResources.resources file's full resource name in this assembly + string srResourcesName = assembly.GetManifestResourceNames().Where(s => s.EndsWith("SRResources.resources", StringComparison.OrdinalIgnoreCase)).Single(); + + // Trim off the ".resources" + srResourcesName = srResourcesName.Substring(0, srResourcesName.Length - 10); + + // Load the resource manager + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(srResourcesName, assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The HttpActionContext.ActionDescriptor is null.. + /// + internal static string ActionContextMustHaveDescriptor { + get { + return ResourceManager.GetString("ActionContextMustHaveDescriptor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The HttpActionContext.Request is null.. + /// + internal static string ActionContextMustHaveRequest { + get { + return ResourceManager.GetString("ActionContextMustHaveRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The HttpActionExecutedContext.ActionContext is null.. + /// + internal static string ActionExecutedContextMustHaveActionContext { + get { + return ResourceManager.GetString("ActionExecutedContextMustHaveActionContext", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The HttpExecutedActionContext.Request is null.. + /// + internal static string ActionExecutedContextMustHaveRequest { + get { + return ResourceManager.GetString("ActionExecutedContextMustHaveRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The action '{0}' is not bound to the collection of entity. Only actions that are bound to entities can have action links.. + /// + internal static string ActionNotBoundToCollectionOfEntity { + get { + return ResourceManager.GetString("ActionNotBoundToCollectionOfEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The action '{0}' is not bound to an entity. Only actions that are bound to entities can have action links.. + /// + internal static string ActionNotBoundToEntity { + get { + return ResourceManager.GetString("ActionNotBoundToEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} type of aggregation is not supported.. + /// + internal static string AggregateKindNotSupported + { + get + { + return ResourceManager.GetString("AggregateKindNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Aggregation method '{0}' is not supported.. + /// + internal static string AggregationMethodNotSupported { + get { + return ResourceManager.GetString("AggregationMethodNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Aggregation '{0}' not supported for property '{1}' of type '{2}'.. + /// + internal static string AggregationNotSupportedForType { + get { + return ResourceManager.GetString("AggregationNotSupportedForType", resourceCulture); + } + } + + /// + /// $apply query options not supported for LINQ to SQL providers + /// + internal static string ApplyQueryOptionNotSupportedForLinq2SQL + { + get + { + return ResourceManager.GetString("ApplyQueryOptionNotSupportedForLinq2SQL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The query option is not bound to any CLR type. '{0}' is only supported with a query option bound to a CLR type.. + /// + internal static string ApplyToOnUntypedQueryOption { + get { + return ResourceManager.GetString("ApplyToOnUntypedQueryOption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument must be of type '{0}'.. + /// + internal static string ArgumentMustBeOfType { + get { + return ResourceManager.GetString("ArgumentMustBeOfType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The batch request must have '{0}' as the media type.. + /// + internal static string BatchRequestInvalidMediaType { + get { + return ResourceManager.GetString("BatchRequestInvalidMediaType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The batch request must have a boundary specification in the "Content-Type" header.. + /// + internal static string BatchRequestMissingBoundary { + get { + return ResourceManager.GetString("BatchRequestMissingBoundary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'Content' property on the batch request cannot be null.. + /// + internal static string BatchRequestMissingContent { + get { + return ResourceManager.GetString("BatchRequestMissingContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The batch request must have a "Content-Type" header.. + /// + internal static string BatchRequestMissingContentType { + get { + return ResourceManager.GetString("BatchRequestMissingContentType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A binary operator with incompatible types was detected. Found operand types '{0}' and '{1}' for operator kind '{2}'.. + /// + internal static string BinaryOperatorNotSupported { + get { + return ResourceManager.GetString("BinaryOperatorNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on type '{1}' returned a null value. The input stream contains collection items which cannot be added if the instance is null.. + /// + internal static string CannotAddToNullCollection { + get { + return ResourceManager.GetString("CannotAddToNullCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot apply {0} of '{1}' to {2} of '{3}'.. + /// + internal static string CannotApplyETagOfT { + get { + return ResourceManager.GetString("CannotApplyETagOfT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot apply {0} of '{1}' to {2} of '{3}'.. + /// + internal static string CannotApplyODataQueryOptionsOfT { + get { + return ResourceManager.GetString("CannotApplyODataQueryOptionsOfT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot automatically bind the navigation property '{0}' on type '{1}' for the entity set or singleton '{2}' because there are two or more matching target entity sets or singletons. The matching entity sets or singletons are: {3}.. + /// + internal static string CannotAutoCreateMultipleCandidates { + get { + return ResourceManager.GetString("CannotAutoCreateMultipleCandidates", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot cast $filter of type '{0}' to type '{1}'.. + /// + internal static string CannotCastFilter { + get { + return ResourceManager.GetString("CannotCastFilter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' does not exist on type '{1}'. Make sure to only use property names that are defined by the type.. + /// + internal static string CannotDeserializeUnknownProperty { + get { + return ResourceManager.GetString("CannotDeserializeUnknownProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot define keys on type '{0}' deriving from '{1}'. The base type in the entity inheritance hierarchy already contains keys.. + /// + internal static string CannotDefineKeysOnDerivedTypes { + get { + return ResourceManager.GetString("CannotDefineKeysOnDerivedTypes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot determine the Edm type for the CLR type '{0}' because the derived type '{1}' is configured as entity type and another derived type '{2}' is configured as complex type.. + /// + internal static string CannotInferEdmType { + get { + return ResourceManager.GetString("CannotInferEdmType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An instance of the abstract resource type '{0}' was found. Abstract resource types cannot be instantiated.. + /// + internal static string CannotInstantiateAbstractResourceType { + get { + return ResourceManager.GetString("CannotInstantiateAbstractResourceType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot apply PATCH to navigation property '{0}' on entity type '{1}'.. + /// + internal static string CannotPatchNavigationProperties { + get { + return ResourceManager.GetString("CannotPatchNavigationProperties", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' does not recognize the node with type '{1}'.. + /// + internal static string CannotRecognizeNodeType { + get { + return ResourceManager.GetString("CannotRecognizeNodeType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity type '{0}' cannot be configured as a complex type because the derived type '{1}' is already configured as an entity type.. + /// + internal static string CannotReconfigEntityTypeAsComplexType { + get { + return ResourceManager.GetString("CannotReconfigEntityTypeAsComplexType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot redefine property '{0}' already defined on the base type '{1}'.. + /// + internal static string CannotRedefineBaseTypeProperty { + get { + return ResourceManager.GetString("CannotRedefineBaseTypeProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot re-enable dependency injection for HTTP routes.. + /// + internal static string CannotReEnableDependencyInjection { + get { + return ResourceManager.GetString("CannotReEnableDependencyInjection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot serialize a null '{0}'.. + /// + internal static string CannotSerializerNull { + get { + return ResourceManager.GetString("CannotSerializerNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The dynamic dictionary property '{0}' of type '{1}' cannot be set. The dynamic property dictionary must have a setter.. + /// + internal static string CannotSetDynamicPropertyDictionary { + get { + return ResourceManager.GetString("CannotSetDynamicPropertyDictionary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} cannot write an object of type '{1}'.. + /// + internal static string CannotWriteType { + get { + return ResourceManager.GetString("CannotWriteType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given model does not contain the type '{0}'.. + /// + internal static string ClrTypeNotInModel { + get { + return ResourceManager.GetString("ClrTypeNotInModel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of the parameter '{1}' does not have an Add method. Consider using a collection type that does have an Add method - for example IList<T> or ICollection<T>.. + /// + internal static string CollectionParameterShouldHaveAddMethod { + get { + return ResourceManager.GetString("CollectionParameterShouldHaveAddMethod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CollectionProperties must implement IEnumerable<>. The property '{0}' declared on '{1}' does not implement IEnumerable<>.. + /// + internal static string CollectionPropertiesMustReturnIEnumerable { + get { + return ResourceManager.GetString("CollectionPropertiesMustReturnIEnumerable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of the property '{1}' on type '{2}' does not have an Add method. Consider using a collection type that does have an Add method - for example IList<T> or ICollection<T>.. + /// + internal static string CollectionShouldHaveAddMethod { + get { + return ResourceManager.GetString("CollectionShouldHaveAddMethod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of the property '{1}' on type '{2}' does not have a Clear method. Consider using a collection type that does have a Clear method, such as IList<T> or ICollection<T>.. + /// + internal static string CollectionShouldHaveClearMethod { + get { + return ResourceManager.GetString("CollectionShouldHaveClearMethod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot compare an enum of type '{0}' to an expression of type '{1}'.. + /// + internal static string ConvertToEnumFailed { + get { + return ResourceManager.GetString("ConvertToEnumFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} does not support CreateODataValue.. + /// + internal static string CreateODataValueNotSupported { + get { + return ResourceManager.GetString("CreateODataValueNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The actual entity type '{0}' is not assignable to the expected type '{1}'.. + /// + internal static string DeltaEntityTypeNotAssignable { + get { + return ResourceManager.GetString("DeltaEntityTypeNotAssignable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot use Delta of type '{0}' on an entity of type '{1}'.. + /// + internal static string DeltaTypeMismatch { + get { + return ResourceManager.GetString("DeltaTypeMismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find nested resource name '{0}' in parent resource type '{1}'.. + /// + internal static string DeltaNestedResourceNameNotFound + { + get + { + return ResourceManager.GetString("DeltaNestedResourceNameNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The dependent property type '{0}' is not same as the principal property type '{1}. The dependent and principal properties must have must have same types in the same order.. + /// + internal static string DependentAndPrincipalTypeNotMatch { + get { + return ResourceManager.GetString("DependentAndPrincipalTypeNotMatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' does not support Read.. + /// + internal static string DeserializerDoesNotSupportRead { + get { + return ResourceManager.GetString("DeserializerDoesNotSupportRead", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type '{0}' does not support ReadInline.. + /// + internal static string DoesNotSupportReadInLine { + get { + return ResourceManager.GetString("DoesNotSupportReadInLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate dynamic property name '{0}' found in open type '{1}'. Each dynamic property name must be unique.. + /// + internal static string DuplicateDynamicPropertyNameFound { + get { + return ResourceManager.GetString("DuplicateDynamicPropertyNameFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate key '{0}' found in segment '{1}'.. + /// + internal static string DuplicateKeyInSegment { + get { + return ResourceManager.GetString("DuplicateKeyInSegment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The dynamic property '{0}' with Edm type '{1}' cannot be serialized.. + /// + internal static string DynamicPropertyCannotBeSerialized { + get { + return ResourceManager.GetString("DynamicPropertyCannotBeSerialized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The name of dynamic property '{0}' was already used as the declared property name of open type '{1}'.. + /// + internal static string DynamicPropertyNameAlreadyUsedAsDeclaredPropertyName { + get { + return ResourceManager.GetString("DynamicPropertyNameAlreadyUsedAsDeclaredPropertyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type name of the dynamic resource set '{0}' is required and cannot be empty. Consider using a OData type annotation explicitly.. + /// + internal static string DynamicResourceSetTypeNameIsRequired { + get { + return ResourceManager.GetString("DynamicResourceSetTypeNameIsRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The edit link builder for the entity set '{0}' returned null. An edit link is required for the location header.. + /// + internal static string EditLinkNullForLocationHeader { + get { + return ResourceManager.GetString("EditLinkNullForLocationHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot get property '{0}' of a null EDM object of type '{1}'.. + /// + internal static string EdmComplexObjectNullRef { + get { + return ResourceManager.GetString("EdmComplexObjectNullRef", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property 'EdmObject' of {0} cannot be null.. + /// + internal static string EdmObjectNull { + get { + return ResourceManager.GetString("EdmObjectNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The EDM type of the object of type '{0}' is null. The EDM type of an {1} cannot be null.. + /// + internal static string EdmTypeCannotBeNull { + get { + return ResourceManager.GetString("EdmTypeCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not a supported EDM type.. + /// + internal static string EdmTypeNotSupported { + get { + return ResourceManager.GetString("EdmTypeNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property 'ElementClrType' of {0} cannot be null.. + /// + internal static string ElementClrTypeNull { + get { + return ResourceManager.GetString("ElementClrTypeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Key template value '{0}' for key segment '{1}' is empty.. + /// + internal static string EmptyKeyTemplate { + get { + return ResourceManager.GetString("EmptyKeyTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter alias '{0}' in segment '{1}' is empty.. + /// + internal static string EmptyParameterAlias { + get { + return ResourceManager.GetString("EmptyParameterAlias", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Uri '{0}' in the parameter is invalid.. + /// + internal static string EntityReferenceMustHasKeySegment { + get { + return ResourceManager.GetString("EntityReferenceMustHasKeySegment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity set '{0}' was already configured with a different EntityType ('{1}').. + /// + internal static string EntitySetAlreadyConfiguredDifferentEntityType { + get { + return ResourceManager.GetString("EntitySetAlreadyConfiguredDifferentEntityType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The related entity set could not be found from the OData path. The related entity set is required to serialize the payload.. + /// + internal static string EntitySetMissingDuringSerialization { + get { + return ResourceManager.GetString("EntitySetMissingDuringSerialization", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity set name '{0}' was already configured as a singleton.. + /// + internal static string EntitySetNameAlreadyConfiguredAsSingleton { + get { + return ResourceManager.GetString("EntitySetNameAlreadyConfiguredAsSingleton", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No EntitySet exists in the EdmModel with entitySetName '{0}'.. + /// + internal static string EntitySetNotFoundForName { + get { + return ResourceManager.GetString("EntitySetNotFoundForName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity '{0}' does not have a key defined.. + /// + internal static string EntityTypeDoesntHaveKeyDefined { + get { + return ResourceManager.GetString("EntityTypeDoesntHaveKeyDefined", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity type '{0}' of navigation property '{1}' on structural type '{2}' does not have a key defined.. + /// + internal static string CollectionNavigationPropertyEntityTypeDoesntHaveKeyDefined + { + get + { + return ResourceManager.GetString("CollectionNavigationPropertyEntityTypeDoesntHaveKeyDefined", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity type '{0}' does not match the expected entity type '{1}' as set on the query context.. + /// + internal static string EntityTypeMismatch { + get { + return ResourceManager.GetString("EntityTypeMismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The enum type '{0}' does not exist.. + /// + internal static string EnumTypeDoesNotExist { + get { + return ResourceManager.GetString("EnumTypeDoesNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of enum member '{0}' cannot be converted to a long type.. + /// + internal static string EnumValueCannotBeLong { + get { + return ResourceManager.GetString("EnumValueCannotBeLong", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type of left property '{0}.{1}' is '{2}' while the type of right property '{3}.{4}' is '{5}'. The left and right type of the equal expression must be same.. + /// + internal static string EqualExpressionsMustHaveSameTypes { + get { + return ResourceManager.GetString("EqualExpressionsMustHaveSameTypes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' is not supported by the ODataErrorSerializer. The type must be ODataError or HttpError.. + /// + internal static string ErrorTypeMustBeODataErrorOrHttpError { + get { + return ResourceManager.GetString("ErrorTypeMustBeODataErrorOrHttpError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ETag is not well-formed.. + /// + internal static string ETagNotWellFormed { + get { + return ResourceManager.GetString("ETagNotWellFormed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to $filter in $expand of reference navigation property '{0}' is not expected type '{1}'. + /// + internal static string ExpandFilterExpressionNotLambdaExpression { + get { + return ResourceManager.GetString("ExpandFilterExpressionNotLambdaExpression", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot create an EDM model as the action '{0}' on controller '{1}' has a void return type.. + /// + internal static string FailedToBuildEdmModelBecauseReturnTypeIsNull { + get { + return ResourceManager.GetString("FailedToBuildEdmModelBecauseReturnTypeIsNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot create an EDM model as the action '{0}' on controller '{1}' has a return type '{2}' that does not implement IEnumerable<T>.. + /// + internal static string FailedToRetrieveTypeToBuildEdmModel { + get { + return ResourceManager.GetString("FailedToRetrieveTypeToBuildEdmModel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reading type '{0}' is not supported by '{1}'.. + /// + internal static string FormatterReadIsNotSupportedForType { + get { + return ResourceManager.GetString("FormatterReadIsNotSupportedForType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The function '{0}' is not bound to the collection of entity. Only functions that are bound to entities can have function links.. + /// + internal static string FunctionNotBoundToCollectionOfEntity { + get { + return ResourceManager.GetString("FunctionNotBoundToCollectionOfEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The function '{0}' is not bound to an entity. Only functions that are bound to entities can have function links.. + /// + internal static string FunctionNotBoundToEntity { + get { + return ResourceManager.GetString("FunctionNotBoundToEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '{0}' function cannot be applied to an enumeration-typed argument.. + /// + internal static string FunctionNotSupportedOnEnum { + get { + return ResourceManager.GetString("FunctionNotSupportedOnEnum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The function parameter '{0}' cannot be found.. + /// + internal static string FunctionParameterNotFound { + get { + return ResourceManager.GetString("FunctionParameterNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to GetEdmModel cannot be called more than once. Consider using a different instance of the model builder or reusing the model you have built earlier.. + /// + internal static string GetEdmModelCalledMoreThanOnce { + get { + return ResourceManager.GetString("GetEdmModelCalledMoreThanOnce", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of the property '{0}' on type '{1}' is an array. Consider adding a setter for the property.. + /// + internal static string GetOnlyCollectionCannotBeArray { + get { + return ResourceManager.GetString("GetOnlyCollectionCannotBeArray", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to To register an action link factory, actions must be bindable to the collection of entity. Action '{0}' does not meet this requirement.. + /// + internal static string HasActionLinkRequiresBindToCollectionOfEntity { + get { + return ResourceManager.GetString("HasActionLinkRequiresBindToCollectionOfEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to To register an action link factory, actions must be bindable to a single entity. Action '{0}' does not meet this requirement.. + /// + internal static string HasActionLinkRequiresBindToEntity { + get { + return ResourceManager.GetString("HasActionLinkRequiresBindToEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to To register a function link factory, functions must be bindable to the collection of entity. Function '{0}' does not meet this requirement.. + /// + internal static string HasFunctionLinkRequiresBindToCollectionOfEntity { + get { + return ResourceManager.GetString("HasFunctionLinkRequiresBindToCollectionOfEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to To register a function link factory, functions must be bindable to a single entity. Function '{0}' does not meet this requirement.. + /// + internal static string HasFunctionLinkRequiresBindToEntity { + get { + return ResourceManager.GetString("HasFunctionLinkRequiresBindToEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Id link builder for the entity set '{0}' returned null. An Id link is required for the OData-EntityId header.. + /// + internal static string IdLinkNullForEntityIdHeader { + get { + return ResourceManager.GetString("IdLinkNullForEntityIdHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The attribute routing template contains invalid segment '{0}'.. + /// + internal static string InvalidAttributeRoutingTemplateSegment { + get { + return ResourceManager.GetString("InvalidAttributeRoutingTemplateSegment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current batch reader state '{0}' is invalid. The expected state is '{1}'.. + /// + internal static string InvalidBatchReaderState { + get { + return ResourceManager.GetString("InvalidBatchReaderState", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid bindingParameter type '{0}'. A bindingParameter must be either an EntityType or a Collection of EntityTypes.. + /// + internal static string InvalidBindingParameterType { + get { + return ResourceManager.GetString("InvalidBindingParameterType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value of $id '{0}' is invalid.. + /// + internal static string InvalidDollarId { + get { + return ResourceManager.GetString("InvalidDollarId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is not a valid entity set name.. + /// + internal static string InvalidEntitySetName { + get { + return ResourceManager.GetString("InvalidEntitySetName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ETag handler set in the configuration property is of type '{0}', which does not implement IETagHandler.. + /// + internal static string InvalidETagHandler { + get { + return ResourceManager.GetString("InvalidETagHandler", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' should be less than or equal to '{1}'.. + /// + internal static string InvalidExpansionDepthValue { + get { + return ResourceManager.GetString("InvalidExpansionDepthValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given OData path template '{0}' is invalid.. + /// + internal static string InvalidODataPathTemplate { + get { + return ResourceManager.GetString("InvalidODataPathTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The path template '{0}' on the action '{1}' in controller '{2}' is not a valid OData path template. {3}. + /// + internal static string InvalidODataRouteOnAction { + get { + return ResourceManager.GetString("InvalidODataRouteOnAction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given untyped value '{0}' in payload is invalid. Consider using a OData type annotation explicitly.. + /// + internal static string InvalidODataUntypedValue { + get { + return ResourceManager.GetString("InvalidODataUntypedValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The URI segment '{0}' is invalid after the segment '{1}'.. + /// + internal static string InvalidPathSegment { + get { + return ResourceManager.GetString("InvalidPathSegment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type '{0}' is not supported as dynamic property annotation. Referenced property must be of type '{1}'.. + /// + internal static string InvalidPropertyInfoForDynamicPropertyAnnotation { + get { + return ResourceManager.GetString("InvalidPropertyInfoForDynamicPropertyAnnotation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The mapper provider must return a valid '{0}' instance for the given '{1}' IEdmType.. + /// + internal static string InvalidPropertyMapper { + get { + return ResourceManager.GetString("InvalidPropertyMapper", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The key mapping for the property '{0}' can't be null or empty.. + /// + internal static string InvalidPropertyMapping { + get { + return ResourceManager.GetString("InvalidPropertyMapping", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The count of single quotes in non-string literal '{0}' must be 0 or 2 in segment '{1}'.. + /// + internal static string InvalidSingleQuoteCountForNonStringLiteral { + get { + return ResourceManager.GetString("InvalidSingleQuoteCountForNonStringLiteral", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is not a valid singleton name. The singleton name cannot contain '.'.. + /// + internal static string InvalidSingletonName { + get { + return ResourceManager.GetString("InvalidSingletonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The time zone info set in the configuration property is of type '{0}'. The time zone info type must be type '{1}'.. + /// + internal static string InvalidTimeZoneInfo { + get { + return ResourceManager.GetString("InvalidTimeZoneInfo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Key template value '{0}' for key segment '{1}' does not start with '{{' or ends with '}}'.. + /// + internal static string KeyTemplateMustBeInCurlyBraces { + get { + return ResourceManager.GetString("KeyTemplateMustBeInCurlyBraces", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Key property '{0}' of type '{1}' is null. Key properties cannot have null values.. + /// + internal static string KeyValueCannotBeNull { + get { + return ResourceManager.GetString("KeyValueCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The LambdaExpression must have exactly one parameter.. + /// + internal static string LambdaExpressionMustHaveExactlyOneParameter { + get { + return ResourceManager.GetString("LambdaExpressionMustHaveExactlyOneParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The LambdaExpression must have exactly two parameters.. + /// + internal static string LambdaExpressionMustHaveExactlyTwoParameters { + get { + return ResourceManager.GetString("LambdaExpressionMustHaveExactlyTwoParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The literal '{0}' has a bad format in segment '{1}'.. + /// + internal static string LiteralHasABadFormat { + get { + return ResourceManager.GetString("LiteralHasABadFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot change multiplicity of the collection navigation property '{0}'.. + /// + internal static string ManyNavigationPropertiesCannotBeChanged { + get { + return ResourceManager.GetString("ManyNavigationPropertiesCannotBeChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on the type '{1}' is being configured as a Many-to-Many navigation property. Many to Many navigation properties must be collections.. + /// + internal static string ManyToManyNavigationPropertyMustReturnCollection { + get { + return ResourceManager.GetString("ManyToManyNavigationPropertyMustReturnCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The provided mapping does not contain a resource for the resource type '{0}'.. + /// + internal static string MappingDoesNotContainResourceType { + get { + return ResourceManager.GetString("MappingDoesNotContainResourceType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Any/All nesting limit of '{0}' has been exceeded. '{1}' can be configured on ODataQuerySettings or EnableQueryAttribute.. + /// + internal static string MaxAnyAllExpressionLimitExceeded { + get { + return ResourceManager.GetString("MaxAnyAllExpressionLimitExceeded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request includes a $expand path which is too deep. The maximum depth allowed is {0}. To increase the limit, set the '{1}' property on EnableQueryAttribute or ODataValidationSettings, or set the 'MaxDepth' property in ExpandAttribute.. + /// + internal static string MaxExpandDepthExceeded { + get { + return ResourceManager.GetString("MaxExpandDepthExceeded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The node count limit of '{0}' has been exceeded. To increase the limit, set the '{1}' property on EnableQueryAttribute or ODataValidationSettings.. + /// + internal static string MaxNodeLimitExceeded { + get { + return ResourceManager.GetString("MaxNodeLimitExceeded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to MemberExpressions must be bound to the LambdaExpression parameter.. + /// + internal static string MemberExpressionsMustBeBoundToLambdaParameter { + get { + return ResourceManager.GetString("MemberExpressionsMustBeBoundToLambdaParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Member '{0}.{1}' is not a property.. + /// + internal static string MemberExpressionsMustBeProperties { + get { + return ResourceManager.GetString("MemberExpressionsMustBeProperties", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find '{0}'. OData services have not been configured. Are you missing a call to AddOData()?. + /// + internal static string MissingODataServices + { + get + { + return ResourceManager.GetString("MissingODataServices", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find the services container for route '{0}'. This should not happen and represents a bug. + /// + internal static string MissingODataContainer + { + get + { + return ResourceManager.GetString("MissingODataContainer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find the services container for the non-OData route. This can occur when using OData components on the non-OData route and is usually a configuration issue. Call EnableDependencyInjection() to enable OData components on non-OData routes. This may also occur when a request was mistakenly handled by the ASP.NET Core routing layer instead of the OData routing layer, for instance the URL does not include the OData route prefix configured via a call to MapODataServiceRoute(). + /// + internal static string MissingNonODataContainer + { + get + { + return ResourceManager.GetString("MissingNonODataContainer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The binding context cannot have a null ModelMetadata.. + /// + internal static string ModelBinderUtil_ModelMetadataCannotBeNull { + get { + return ResourceManager.GetString("ModelBinderUtil_ModelMetadataCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The binding value '{0}' cannot be bound to the enum type '{1}'.. + /// + internal static string ModelBinderUtil_ValueCannotBeEnum { + get { + return ResourceManager.GetString("ModelBinderUtil_ValueCannotBeEnum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The EDM model is missing on the read context. The model is required on the read context to deserialize the payload.. + /// + internal static string ModelMissingFromReadContext { + get { + return ResourceManager.GetString("ModelMissingFromReadContext", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found more than one dynamic property container in type '{0}'. Each open type must have at most one dynamic property container.. + /// + internal static string MoreThanOneDynamicPropertyContainerFound { + get { + return ResourceManager.GetString("MoreThanOneDynamicPropertyContainerFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to More than one Operation called '{0}' was found. Try using the other RemoveOperation override.. + /// + internal static string MoreThanOneOperationFound { + get { + return ResourceManager.GetString("MoreThanOneOperationFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found more than one action with name '{0}' bound to the same type '{1}'. Each bound action must have a different binding type or name.. + /// + internal static string MoreThanOneOverloadActionBoundToSameTypeFound { + get { + return ResourceManager.GetString("MoreThanOneOverloadActionBoundToSameTypeFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found more than one unbound action with name '{0}'. Each unbound action must have an unique action name.. + /// + internal static string MoreThanOneUnboundActionFound { + get { + return ResourceManager.GetString("MoreThanOneUnboundActionFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The member '{0}' on type '{1}' contains multiple instances of the attribute '{2}'.. + /// + internal static string MultipleAttributesFound { + get { + return ResourceManager.GetString("MultipleAttributesFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to More than one matching CLR type found for the EDM type {0}.\nThe matching CLR types are {1}.. + /// + internal static string MultipleMatchingClrTypesForEdmType { + get { + return ResourceManager.GetString("MultipleMatchingClrTypesForEdmType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '{0}' property on '{1}' must be a Collection property.. + /// + internal static string MustBeCollectionProperty { + get { + return ResourceManager.GetString("MustBeCollectionProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on type '{1}' must be a Complex property.. + /// + internal static string MustBeComplexProperty { + get { + return ResourceManager.GetString("MustBeComplexProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on type '{1}' must be a System.DateTime property.. + /// + internal static string MustBeDateTimeProperty { + get { + return ResourceManager.GetString("MustBeDateTimeProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on type '{1}' must be an Enum property.. + /// + internal static string MustBeEnumProperty { + get { + return ResourceManager.GetString("MustBeEnumProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on type '{1}' must be a Navigation property.. + /// + internal static string MustBeNavigationProperty { + get { + return ResourceManager.GetString("MustBeNavigationProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on type '{1}' must be a Primitive property.. + /// + internal static string MustBePrimitiveProperty { + get { + return ResourceManager.GetString("MustBePrimitiveProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' must be a primitive type.. + /// + internal static string MustBePrimitiveType { + get { + return ResourceManager.GetString("MustBePrimitiveType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' on type '{1}' must be a System.TimeSpan property.. + /// + internal static string MustBeTimeSpanProperty { + get { + return ResourceManager.GetString("MustBeTimeSpanProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The multiplicity of the '{0}' property must be '{1}'.. + /// + internal static string MustHaveMatchingMultiplicity { + get { + return ResourceManager.GetString("MustHaveMatchingMultiplicity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The navigation property binding path '{0}' is not valid. The last segment must be the navigation property '{1}'.. + /// + internal static string NavigationPropertyBindingPathIsNotValid { + get { + return ResourceManager.GetString("NavigationPropertyBindingPathIsNotValid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of the binding type cast segment '{1}' is not a part of the type '{2}' hierarchy.. + /// + internal static string NavigationPropertyBindingPathNotInHierarchy { + get { + return ResourceManager.GetString("NavigationPropertyBindingPathNotInHierarchy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The navigation property binding path segment '{0}' of member type '{1}' is not supported.. + /// + internal static string NavigationPropertyBindingPathNotSupported { + get { + return ResourceManager.GetString("NavigationPropertyBindingPathNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The declaring entity type '{0}' of the given navigation property is not a part of the entity type '{1}' hierarchy of the entity set or singleton '{2}'.. + /// + internal static string NavigationPropertyNotInHierarchy { + get { + return ResourceManager.GetString("NavigationPropertyNotInHierarchy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The related entity set or singleton cannot be found from the OData path. The related entity set or singleton is required to deserialize the payload.. + /// + internal static string NavigationSourceMissingDuringDeserialization { + get { + return ResourceManager.GetString("NavigationSourceMissingDuringDeserialization", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The related entity set or singleton cannot be found from the OData path. The related entity set or singleton is required to serialize the payload.. + /// + internal static string NavigationSourceMissingDuringSerialization { + get { + return ResourceManager.GetString("NavigationSourceMissingDuringSerialization", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity set or singleton '{0}' is based on type '{1}' that has no keys defined.. + /// + internal static string NavigationSourceTypeHasNoKeys { + get { + return ResourceManager.GetString("NavigationSourceTypeHasNoKeys", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity set '{0}' is based on type '{1}' that has no keys defined.. + /// + internal static string EntitySetTypeHasNoKeys + { + get + { + return ResourceManager.GetString("EntitySetTypeHasNoKeys", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' is a nested collection type. Nested collection types are not allowed.. + /// + internal static string NestedCollectionsNotSupported { + get { + return ResourceManager.GetString("NestedCollectionsNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find nested property '{0}' on the resource type '{1}'.. + /// + internal static string NestedPropertyNotfound { + get { + return ResourceManager.GetString("NestedPropertyNotfound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No key name was found at {0} in segment '{1}'.. + /// + internal static string NoKeyNameFoundInSegment { + get { + return ResourceManager.GetString("NoKeyNameFoundInSegment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No IEdmType could be found for '{0}'.. + /// + internal static string NoMatchingIEdmTypeFound { + get { + return ResourceManager.GetString("NoMatchingIEdmTypeFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No HTTP resource was found that matches the request URI '{0}'.. + /// + internal static string NoMatchingResource { + get { + return ResourceManager.GetString("NoMatchingResource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A non-null request URI must be provided to determine if a '{0}' matches a given request or response message.. + /// + internal static string NonNullUriRequiredForMediaTypeMapping { + get { + return ResourceManager.GetString("NonNullUriRequiredForMediaTypeMapping", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No non-OData HTTP route registered.. + /// + internal static string NoNonODataHttpRouteRegistered { + get { + return ResourceManager.GetString("NoNonODataHttpRouteRegistered", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The requested resource is not a collection. Query options $filter, $orderby, $count, $skip, and $top can be applied only on collections.. + /// + internal static string NonSelectExpandOnSingleEntity { + get { + return ResourceManager.GetString("NonSelectExpandOnSingleEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No routing convention was found to select an action for the OData path with template '{0}'.. + /// + internal static string NoRoutingHandlerToSelectAction { + get { + return ResourceManager.GetString("NoRoutingHandlerToSelectAction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Arithmetic operator '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings.. + /// + internal static string NotAllowedArithmeticOperator { + get { + return ResourceManager.GetString("NotAllowedArithmeticOperator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Function '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings.. + /// + internal static string NotAllowedFunction { + get { + return ResourceManager.GetString("NotAllowedFunction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logical operator '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings.. + /// + internal static string NotAllowedLogicalOperator { + get { + return ResourceManager.GetString("NotAllowedLogicalOperator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Order by '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings.. + /// + internal static string NotAllowedOrderByProperty { + get { + return ResourceManager.GetString("NotAllowedOrderByProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Query option '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings.. + /// + internal static string NotAllowedQueryOption { + get { + return ResourceManager.GetString("NotAllowedQueryOption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The entity set '{0}' cannot be used for $count.. + /// + internal static string NotCountableEntitySetUsedForCount { + get { + return ResourceManager.GetString("NotCountableEntitySetUsedForCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' cannot be used for $count.. + /// + internal static string NotCountablePropertyUsedForCount { + get { + return ResourceManager.GetString("NotCountablePropertyUsedForCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' cannot be used in the $expand query option.. + /// + internal static string NotExpandablePropertyUsedInExpand { + get { + return ResourceManager.GetString("NotExpandablePropertyUsedInExpand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' cannot be used in the $filter query option.. + /// + internal static string NotFilterablePropertyUsedInFilter { + get { + return ResourceManager.GetString("NotFilterablePropertyUsedInFilter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' cannot be used for navigation.. + /// + internal static string NotNavigablePropertyUsedInNavigation { + get { + return ResourceManager.GetString("NotNavigablePropertyUsedInNavigation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' cannot be used in the $select query option.. + /// + internal static string NotSelectablePropertyUsedInSelect { + get { + return ResourceManager.GetString("NotSelectablePropertyUsedInSelect", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' cannot be used in the $orderby query option.. + /// + internal static string NotSortablePropertyUsedInOrderBy { + get { + return ResourceManager.GetString("NotSortablePropertyUsedInOrderBy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transformation kind {0} is not supported.. + /// + internal static string NotSupportedTransformationKind { + get { + return ResourceManager.GetString("NotSupportedTransformationKind", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No value for key '{0}' was found at {1} in segment '{2}'.. + /// + internal static string NoValueLiteralFoundInSegment { + get { + return ResourceManager.GetString("NoValueLiteralFoundInSegment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The container built by the container builder must not be null.. + /// + internal static string NullContainer { + get { + return ResourceManager.GetString("NullContainer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The container builder created by the container builder factory must not be null.. + /// + internal static string NullContainerBuilder { + get { + return ResourceManager.GetString("NullContainerBuilder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Collections cannot contain null elements.. + /// + internal static string NullElementInCollection { + get { + return ResourceManager.GetString("NullElementInCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ETag handler set in the configuration property must not be null.. + /// + internal static string NullETagHandler { + get { + return ResourceManager.GetString("NullETagHandler", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type verification failed. Expected non-nullable type '{0}' but received a null value.. + /// + internal static string NullOnNonNullableFunctionParameter { + get { + return ResourceManager.GetString("NullOnNonNullableFunctionParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.. + /// + internal static string Object_NotYetInitialized { + get { + return ResourceManager.GetString("Object_NotYetInitialized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown function '{0}'.. + /// + internal static string ODataFunctionNotSupported { + get { + return ResourceManager.GetString("ODataFunctionNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The operation cannot be completed because no ODataPath is available for the request.. + /// + internal static string ODataPathMissing { + get { + return ResourceManager.GetString("ODataPathMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request URI '{0}' does not contain OData path '{1}'.. + /// + internal static string ODataPathNotFound { + get { + return ResourceManager.GetString("ODataPathNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid EntitySetPath detected. '{0}' is not a valid entity set path for operation '{1}'.. + /// + internal static string OperationHasInvalidEntitySetPath { + get { + return ResourceManager.GetString("OperationHasInvalidEntitySetPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The operation import segment must be a function import segment.. + /// + internal static string OperationImportSegmentMustBeFunction { + get { + return ResourceManager.GetString("OperationImportSegmentMustBeFunction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The operation segment must be a function segment.. + /// + internal static string OperationSegmentMustBeFunction { + get { + return ResourceManager.GetString("OperationSegmentMustBeFunction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only ordering by properties is supported for non-primitive collections. Expressions are not supported.. + /// + internal static string OrderByClauseNotSupported { + get { + return ResourceManager.GetString("OrderByClauseNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Multiple '$it' nodes are not supported in '$orderby'.. + /// + internal static string OrderByDuplicateIt { + get { + return ResourceManager.GetString("OrderByDuplicateIt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate property named '{0}' is not supported in '$orderby'.. + /// + internal static string OrderByDuplicateProperty { + get { + return ResourceManager.GetString("OrderByDuplicateProperty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of clauses in $orderby query option exceeded the maximum number allowed. The maximum number of $orderby clauses allowed is {0}.. + /// + internal static string OrderByNodeCountExceeded { + get { + return ResourceManager.GetString("OrderByNodeCountExceeded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter alias '{0}' in segment '{1}' does not start with '{{' or ends with '}}'.. + /// + internal static string ParameterAliasMustBeInCurlyBraces { + get { + return ResourceManager.GetString("ParameterAliasMustBeInCurlyBraces", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of the parameter '{1}' must be a collection.. + /// + internal static string ParameterTypeIsNotCollection { + get { + return ResourceManager.GetString("ParameterTypeIsNotCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot define property '{0}' in the base type '{1}' as the derived type '{2}' already defines it.. + /// + internal static string PropertyAlreadyDefinedInDerivedType { + get { + return ResourceManager.GetString("PropertyAlreadyDefinedInDerivedType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property '{0}' does not belong to the type '{1}'.. + /// + internal static string PropertyDoesNotBelongToType { + get { + return ResourceManager.GetString("PropertyDoesNotBelongToType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of the property '{1}' on type '{2}' must be a collection.. + /// + internal static string PropertyIsNotCollection { + get { + return ResourceManager.GetString("PropertyIsNotCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value must be a DateTimeOffset or Date.. + /// + internal static string PropertyMustBeDateTimeOffsetOrDate { + get { + return ResourceManager.GetString("PropertyMustBeDateTimeOffsetOrDate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value must be a boolean.. + /// + internal static string PropertyMustBeBoolean + { + get + { + return ResourceManager.GetString("PropertyMustBeBoolean", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value with type '{0}' must have type '{1}'.. + /// + internal static string PropertyMustBeEnum { + get { + return ResourceManager.GetString("PropertyMustBeEnum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value must be a string.. + /// + internal static string PropertyMustBeString { + get { + return ResourceManager.GetString("PropertyMustBeString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value must be a string with a length of 1.. + /// + internal static string PropertyMustBeStringLengthOne { + get { + return ResourceManager.GetString("PropertyMustBeStringLengthOne", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value must be a string with a maximum length of 1.. + /// + internal static string PropertyMustBeStringMaxLengthOne { + get { + return ResourceManager.GetString("PropertyMustBeStringMaxLengthOne", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value must be a Edm.TimeOfDay.. + /// + internal static string PropertyMustBeTimeOfDay { + get { + return ResourceManager.GetString("PropertyMustBeTimeOfDay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The PropertyInfo provided must have public 'get' and 'set' accessor methods.. + /// + internal static string PropertyMustHavePublicGetterAndSetter { + get { + return ResourceManager.GetString("PropertyMustHavePublicGetterAndSetter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The EDM instance of type '{0}' is missing the property '{1}'.. + /// + internal static string PropertyNotFound { + get { + return ResourceManager.GetString("PropertyNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Property or path {0} isn't available in the current context. It was removed in earlier transformation.. + /// + internal static string PropertyOrPathWasRemovedFromContext { + get { + return ResourceManager.GetString("PropertyOrPathWasRemovedFromContext", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value for OData query '{0}' cannot be empty.. + /// + internal static string QueryCannotBeEmpty { + get { + return ResourceManager.GetString("QueryCannotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A model is required for querying. Consider implementing the EnableQueryAttribute.GetModel method never to return a null value.. + /// + internal static string QueryGetModelMustNotReturnNull { + get { + return ResourceManager.GetString("QueryGetModelMustNotReturnNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queries can not be applied to a response content of type '{0}'. The response content must be an ObjectContent.. + /// + internal static string QueryingRequiresObjectContent { + get { + return ResourceManager.GetString("QueryingRequiresObjectContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Binding OData QueryNode of kind {0} is not supported by {1}.. + /// + internal static string QueryNodeBindingNotSupported { + get { + return ResourceManager.GetString("QueryNodeBindingNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validating OData QueryNode of kind {0} is not supported by {1}.. + /// + internal static string QueryNodeValidationNotSupported { + get { + return ResourceManager.GetString("QueryNodeValidationNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The query parameter '{0}' is not supported.. + /// + internal static string QueryParameterNotSupported { + get { + return ResourceManager.GetString("QueryParameterNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The OData formatter requires an attached request in order to deserialize. Controller classes must derive from ODataController or be marked with ODataFormattingAttribute. Custom parameter bindings must call GetPerRequestFormatterInstance on each formatter and use these per-request instances.. + /// + internal static string ReadFromStreamAsyncMustHaveRequest { + get { + return ResourceManager.GetString("ReadFromStreamAsyncMustHaveRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rebinding is not supported.. + /// + internal static string RebindingNotSupported { + get { + return ResourceManager.GetString("RebindingNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found unexpected parameter '{0}'.. + /// + internal static string ReferenceNavigationPropertyExpandFilterVisitorUnexpectedParameter { + get { + return ResourceManager.GetString("ReferenceNavigationPropertyExpandFilterVisitorUnexpectedParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '{0}' property '{1}' is already configured to have a relationship with '{2}' property '{3}' in the referential constraint.. + /// + internal static string ReferentialConstraintAlreadyConfigured { + get { + return ResourceManager.GetString("ReferentialConstraintAlreadyConfigured", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Referential constraint for navigation property '{0}' on type '{1}' is not supported. Only required or optional navigation properties support referential constraint.. + /// + internal static string ReferentialConstraintOnManyNavigationPropertyNotSupported { + get { + return ResourceManager.GetString("ReferentialConstraintOnManyNavigationPropertyNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property type '{0}' of the referential constraint is not valid. The referential constraint property type must be primitive type.. + /// + internal static string ReferentialConstraintPropertyTypeNotValid { + get { + return ResourceManager.GetString("ReferentialConstraintPropertyTypeNotValid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A request container already exists on the request.. + /// + internal static string RequestContainerAlreadyExists { + get { + return ResourceManager.GetString("RequestContainerAlreadyExists", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request message does not contain an HttpConfiguration object.. + /// + internal static string RequestMustContainConfiguration { + get { + return ResourceManager.GetString("RequestMustContainConfiguration", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request must have an associated EDM model. Consider using the extension method HttpConfiguration.MapODataServiceRoute to register a route that parses the OData URI and attaches the model information.. + /// + internal static string RequestMustHaveModel { + get { + return ResourceManager.GetString("RequestMustHaveModel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request must have an associated OData route name. Consider using the extension request.ODataProperties().RouteName to set a route name.. + /// + internal static string RequestMustHaveODataRouteName { + get { + return ResourceManager.GetString("RequestMustHaveODataRouteName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The last segment of the request URI '{0}' was not recognized as an OData action.. + /// + internal static string RequestNotActionInvocation { + get { + return ResourceManager.GetString("RequestNotActionInvocation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request URI '{0}' too short to contain OData path '{1}'.. + /// + internal static string RequestUriTooShortForODataPath { + get { + return ResourceManager.GetString("RequestUriTooShortForODataPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find the resource type '{0}' in the model.. + /// + internal static string ResourceTypeNotInModel { + get { + return ResourceManager.GetString("ResourceTypeNotInModel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The EDM type '{0}' is already declared as an entity type. Use the method 'ReturnsCollectionFromEntitySet' if the return type is an entity collection.. + /// + internal static string ReturnEntityCollectionWithoutEntitySet { + get { + return ResourceManager.GetString("ReturnEntityCollectionWithoutEntitySet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The EDM type '{0}' is already declared as an entity type. Use the method 'ReturnsFromEntitySet' if the return type is an entity.. + /// + internal static string ReturnEntityWithoutEntitySet { + get { + return ResourceManager.GetString("ReturnEntityWithoutEntitySet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'RootElementName' property is required on '{0}'.. + /// + internal static string RootElementNameMissing { + get { + return ResourceManager.GetString("RootElementNameMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The OData route prefix '{0}' on the controller '{1}' starts with a '/'. Route prefixes cannot start with a '/'.. + /// + internal static string RoutePrefixStartsWithSlash { + get { + return ResourceManager.GetString("RoutePrefixStartsWithSlash", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'select' and 'expand' cannot be both null or empty.. + /// + internal static string SelectExpandEmptyOrNull { + get { + return ResourceManager.GetString("SelectExpandEmptyOrNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to $select does not support selections of type '{0}'.. + /// + internal static string SelectionTypeNotSupported { + get { + return ResourceManager.GetString("SelectionTypeNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' is not an entity type. Only entity types support $select and $expand.. + /// + internal static string SelectNonEntity { + get { + return ResourceManager.GetString("SelectNonEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The action '{0}' on controller '{1}' returned a {2} containing more than one element. {2} must have zero or one elements.. + /// + internal static string SingleResultHasMoreThanOneEntity { + get { + return ResourceManager.GetString("SingleResultHasMoreThanOneEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The singleton '{0}' was already configured with a different EntityType ('{1}').. + /// + internal static string SingletonAlreadyConfiguredDifferentEntityType { + get { + return ResourceManager.GetString("SingletonAlreadyConfiguredDifferentEntityType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The singleton name '{0}' was already configured as an entity set.. + /// + internal static string SingletonNameAlreadyConfiguredAsEntitySet { + get { + return ResourceManager.GetString("SingletonNameAlreadyConfiguredAsEntitySet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The limit of '{0}' for {1} query has been exceeded. The value from the incoming request is '{2}'.. + /// + internal static string SkipTopLimitExceeded { + get { + return ResourceManager.GetString("SkipTopLimitExceeded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to parse the skiptoken value. Skiptoken value should always be server generated.. + /// + internal static string SkipTokenParseError + { + get + { + return ResourceManager.GetString("SkipTokenParseError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the target entity type for the navigation property '{0}' on entity type '{1}'.. + /// + internal static string TargetEntityTypeMissing { + get { + return ResourceManager.GetString("TargetEntityTypeMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' of kind '{1}' is not implemented.. + /// + internal static string TargetKindNotImplemented { + get { + return ResourceManager.GetString("TargetKindNotImplemented", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' cannot be configured as a ComplexType. It was previously configured as an EntityType.. + /// + internal static string TypeCannotBeComplexWasEntity { + get { + return ResourceManager.GetString("TypeCannotBeComplexWasEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' cannot be deserialized using the ODataMediaTypeFormatter.. + /// + internal static string TypeCannotBeDeserialized { + get { + return ResourceManager.GetString("TypeCannotBeDeserialized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' cannot be configured as an EntityType. It was previously configured as a ComplexType.. + /// + internal static string TypeCannotBeEntityWasComplex { + get { + return ResourceManager.GetString("TypeCannotBeEntityWasComplex", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' cannot be configured as an enum type.. + /// + internal static string TypeCannotBeEnum { + get { + return ResourceManager.GetString("TypeCannotBeEnum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' cannot be serialized using the ODataMediaTypeFormatter.. + /// + internal static string TypeCannotBeSerialized { + get { + return ResourceManager.GetString("TypeCannotBeSerialized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' does not inherit from '{1}'.. + /// + internal static string TypeDoesNotInheritFromBaseType { + get { + return ResourceManager.GetString("TypeDoesNotInheritFromBaseType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not an entity type. Only entity types are supported.. + /// + internal static string TypeMustBeEntity { + get { + return ResourceManager.GetString("TypeMustBeEntity", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' must be an enum or Nullable<T> where T is an enum type.. + /// + internal static string TypeMustBeEnumOrNullableEnum { + get { + return ResourceManager.GetString("TypeMustBeEnumOrNullableEnum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not a resource set type. Only resource set are supported.. + /// + internal static string TypeMustBeResourceSet { + get { + return ResourceManager.GetString("TypeMustBeResourceSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' of dynamic property '{1}' is not supported.. + /// + internal static string TypeOfDynamicPropertyNotSupported { + get { + return ResourceManager.GetString("TypeOfDynamicPropertyNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ODataMediaTypeFormatter was unable to determine the base URI for the request. The request must be processed by an OData route for the OData formatter to serialize the response.. + /// + internal static string UnableToDetermineBaseUrl { + get { + return ResourceManager.GetString("UnableToDetermineBaseUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ODataMediaTypeFormatter must be able to determine the metadata URL for a request in order to serialize the response.. + /// + internal static string UnableToDetermineMetadataUrl { + get { + return ResourceManager.GetString("UnableToDetermineMetadataUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validating OData UnaryOperatorNode of kind {0} is not supported by {1}.. + /// + internal static string UnaryNodeValidationNotSupported { + get { + return ResourceManager.GetString("UnaryNodeValidationNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The element type '{0}' of the given collection type '{1}' is not of the type '{2}'.. + /// + internal static string UnexpectedElementType { + get { + return ResourceManager.GetString("UnexpectedElementType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found an unresolved path segment '{0}' in the OData path template '{1}'.. + /// + internal static string UnresolvedPathSegmentInTemplate { + get { + return ResourceManager.GetString("UnresolvedPathSegmentInTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The EDM type '{0}' of kind '{1}' is not supported.. + /// + internal static string UnsupportedEdmType { + get { + return ResourceManager.GetString("UnsupportedEdmType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found unsupported EdmTypeKind '{0}' in list of available types.. + /// + internal static string UnsupportedEdmTypeKind { + get { + return ResourceManager.GetString("UnsupportedEdmTypeKind", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported Expression NodeType.. + /// + internal static string UnsupportedExpressionNodeType { + get { + return ResourceManager.GetString("UnsupportedExpressionNodeType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported Expression NodeType '{0}'.. + /// + internal static string UnsupportedExpressionNodeTypeWithName { + get { + return ResourceManager.GetString("UnsupportedExpressionNodeTypeWithName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A path within the select or expand query option is not supported.. + /// + internal static string UnsupportedSelectExpandPath { + get { + return ResourceManager.GetString("UnsupportedSelectExpandPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unterminated string literal at {0} in segment '{1}'.. + /// + internal static string UnterminatedStringLiteral { + get { + return ResourceManager.GetString("UnterminatedStringLiteral", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The function '{0}' is already bound to another method.. + /// + internal static string UriFunctionClrBinderAlreadyBound { + get { + return ResourceManager.GetString("UriFunctionClrBinderAlreadyBound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The query specified in the URI is not valid. {0}. + /// + internal static string UriQueryStringInvalid { + get { + return ResourceManager.GetString("UriQueryStringInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The property 'Url' of {0} cannot be null.. + /// + internal static string UrlHelperNull { + get { + return ResourceManager.GetString("UrlHelperNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value '{0}' is invalid. {1}. + /// + internal static string ValueIsInvalid { + get { + return ResourceManager.GetString("ValueIsInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} does not support WriteObjectInline.. + /// + internal static string WriteObjectInlineNotSupported { + get { + return ResourceManager.GetString("WriteObjectInlineNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} does not support WriteObject.. + /// + internal static string WriteObjectNotSupported { + get { + return ResourceManager.GetString("WriteObjectNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The OData formatter does not support writing client requests. This formatter instance must have an associated request.. + /// + internal static string WriteToStreamAsyncMustHaveRequest { + get { + return ResourceManager.GetString("WriteToStreamAsyncMustHaveRequest", resourceCulture); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx new file mode 100644 index 0000000..68d2caa --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/SRResources.resx @@ -0,0 +1,907 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The property '{0}' does not exist on type '{1}'. Make sure to only use property names that are defined by the type. + + + Cannot serialize a null '{0}'. + + + '{0}' cannot be serialized using the ODataMediaTypeFormatter. + + + {0} does not support WriteObjectInline. + + + The PropertyInfo provided must have public 'get' and 'set' accessor methods. + + + {0} does not support CreateODataValue. + + + {0} is not a resource set type. Only resource set are supported. + + + {0} does not support WriteObject. + + + The query specified in the URI is not valid. {0} + + + The LambdaExpression must have exactly one parameter. + + + The LambdaExpression must have exactly two parameters. + + + MemberExpressions must be bound to the LambdaExpression parameter. + + + Member '{0}.{1}' is not a property. + + + The type of left property '{0}.{1}' is '{2}' while the type of right property '{3}.{4}' is '{5}'. The left and right type of the equal expression must be same. + + + Unsupported Expression NodeType. + + + Unsupported Expression NodeType '{0}'. + + + Cannot create an EDM model as the action '{0}' on controller '{1}' has a void return type. + + + Cannot create an EDM model as the action '{0}' on controller '{1}' has a return type '{2}' that does not implement IEnumerable<T>. + + + The query parameter '{0}' is not supported. + + + Request message does not contain an HttpConfiguration object. + + + The HttpActionContext.ActionDescriptor is null. + + + The HttpActionContext.Request is null. + + + The HttpActionExecutedContext.ActionContext is null. + + + The HttpExecutedActionContext.Request is null. + + + Only ordering by properties is supported for non-primitive collections. Expressions are not supported. + + + No EntitySet exists in the EdmModel with entitySetName '{0}'. + + + The value for OData query '{0}' cannot be empty. + + + {0} is not a supported EDM type. + + + More than one matching CLR type found for the EDM type {0}.\nThe matching CLR types are {1}. + + + Unknown function '{0}'. + + + Binding OData QueryNode of kind {0} is not supported by {1}. + + + Validating OData QueryNode of kind {0} is not supported by {1}. + + + Validating OData UnaryOperatorNode of kind {0} is not supported by {1}. + + + Duplicate property named '{0}' is not supported in '$orderby'. + + + '{0}' does not recognize the node with type '{1}'. + + + '{0}' of kind '{1}' is not implemented. + + + The value of $id '{0}' is invalid. + + + The type '{0}' is not supported by the ODataErrorSerializer. The type must be ODataError or HttpError. + + + Cannot automatically bind the navigation property '{0}' on type '{1}' for the entity set or singleton '{2}' because there are two or more matching target entity sets or singletons. The matching entity sets or singletons are: {3}. + + + The property '{0}' does not belong to the type '{1}'. + + + The entity set '{0}' was already configured with a different EntityType ('{1}'). + + + The type '{0}' cannot be configured as a ComplexType. It was previously configured as an EntityType. + + + The type '{0}' cannot be configured as an EntityType. It was previously configured as a ComplexType. + + + The type '{0}' cannot be configured as an enum type. + + + The value of enum member '{0}' cannot be converted to a long type. + + + The enum type '{0}' does not exist. + + + '{0}' does not support Read. + + + The provided mapping does not contain a resource for the resource type '{0}'. + + + The property '{0}' on the type '{1}' is being configured as a Many-to-Many navigation property. Many to Many navigation properties must be collections. + + + Type '{0}' does not support ReadInline. + + + Reading type '{0}' is not supported by '{1}'. + + + The property '{0}' on type '{1}' must be a Complex property. + + + The property '{0}' on type '{1}' must be a Navigation property. + + + The property '{0}' on type '{1}' must be a Primitive property. + + + The property '{0}' on type '{1}' must be an Enum property. + + + The property '{0}' on type '{1}' must be a System.DateTime property. + + + The property '{0}' on type '{1}' must be a System.TimeSpan property. + + + The multiplicity of the '{0}' property must be '{1}'. + + + The type '{0}' must be a primitive type. + + + Rebinding is not supported. + + + The node count limit of '{0}' has been exceeded. To increase the limit, set the '{1}' property on EnableQueryAttribute or ODataValidationSettings. + + + '{0}' is not a valid entity set name. + + + Cannot cast $filter of type '{0}' to type '{1}'. + + + The member '{0}' on type '{1}' contains multiple instances of the attribute '{2}'. + + + GetEdmModel cannot be called more than once. Consider using a different instance of the model builder or reusing the model you have built earlier. + + + CollectionProperties must implement IEnumerable<>. The property '{0}' declared on '{1}' does not implement IEnumerable<>. + + + The '{0}' property on '{1}' must be a Collection property. + + + Cannot apply PATCH to navigation property '{0}' on entity type '{1}'. + + + The type '{0}' must be an enum or Nullable<T> where T is an enum type. + + + The value with type '{0}' must have type '{1}'. + + + The value must be a string. + + + The value must be a DateTimeOffset or Date. + + + The value must be a Edm.TimeOfDay. + + + The value must be a string with a length of 1. + + + The value must be a string with a maximum length of 1. + + + The argument must be of type '{0}'. + + + The type '{0}' of the property '{1}' on type '{2}' does not have an Add method. Consider using a collection type that does have an Add method - for example IList<T> or ICollection<T>. + + + Invalid bindingParameter type '{0}'. A bindingParameter must be either an EntityType or a Collection of EntityTypes. + + + More than one Operation called '{0}' was found. Try using the other RemoveOperation override. + + + No IEdmType could be found for '{0}'. + + + Found unsupported EdmTypeKind '{0}' in list of available types. + + + Cannot define keys on type '{0}' deriving from '{1}'. The base type in the entity inheritance hierarchy already contains keys. + + + Cannot redefine property '{0}' already defined on the base type '{1}'. + + + '{0}' does not inherit from '{1}'. + + + Cannot define property '{0}' in the base type '{1}' as the derived type '{2}' already defines it. + + + Cannot compare an enum of type '{0}' to an expression of type '{1}'. + + + The '{0}' function cannot be applied to an enumeration-typed argument. + + + An instance of the abstract resource type '{0}' was found. Abstract resource types cannot be instantiated. + + + Collections cannot contain null elements. + + + The OData formatter does not support writing client requests. This formatter instance must have an associated request. + + + The value '{0}' is invalid. {1} + + + The binding value '{0}' cannot be bound to the enum type '{1}'. + + + The binding context cannot have a null ModelMetadata. + + + The entity type '{0}' does not match the expected entity type '{1}' as set on the query context. + + + Cannot apply {0} of '{1}' to {2} of '{3}'. + + + To register an action link factory, actions must be bindable to a single entity. Action '{0}' does not meet this requirement. + + + To register an action link factory, actions must be bindable to the collection of entity. Action '{0}' does not meet this requirement. + + + To register a function link factory, functions must be bindable to a single entity. Function '{0}' does not meet this requirement. + + + To register a function link factory, functions must be bindable to the collection of entity. Function '{0}' does not meet this requirement. + + + The declaring entity type '{0}' of the given navigation property is not a part of the entity type '{1}' hierarchy of the entity set or singleton '{2}'. + + + Could not find the target entity type for the navigation property '{0}' on entity type '{1}'. + + + Key property '{0}' of type '{1}' is null. Key properties cannot have null values. + + + A binary operator with incompatible types was detected. Found operand types '{0}' and '{1}' for operator kind '{2}'. + + + Queries can not be applied to a response content of type '{0}'. The response content must be an ObjectContent. + + + The Any/All nesting limit of '{0}' has been exceeded. '{1}' can be configured on ODataQuerySettings or EnableQueryAttribute. + + + The property '{0}' on type '{1}' returned a null value. The input stream contains collection items which cannot be added if the instance is null. + + + The type '{0}' of the property '{1}' on type '{2}' must be a collection. + + + The value of the property '{0}' on type '{1}' is an array. Consider adding a setter for the property. + + + The URI segment '{0}' is invalid after the segment '{1}'. + + + The last segment of the request URI '{0}' was not recognized as an OData action. + + + No HTTP resource was found that matches the request URI '{0}'. + + + No routing convention was found to select an action for the OData path with template '{0}'. + + + Cannot use Delta of type '{0}' on an entity of type '{1}'. + + + The actual entity type '{0}' is not assignable to the expected type '{1}'. + + + Cannot find nested resource name '{0}' in parent resource type '{1}' + + + Arithmetic operator '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings. + + + Function '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings. + + + Logical operator '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings. + + + Order by '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings. + + + Query option '{0}' is not allowed. To allow it, set the '{1}' property on EnableQueryAttribute or QueryValidationSettings. + + + The limit of '{0}' for {1} query has been exceeded. The value from the incoming request is '{2}'. + + + The related entity set could not be found from the OData path. The related entity set is required to serialize the payload. + + + The ODataMediaTypeFormatter must be able to determine the metadata URL for a request in order to serialize the response. + + + The operation cannot be completed because no ODataPath is available for the request. + + + Multiple '$it' nodes are not supported in '$orderby'. + + + The given model does not contain the type '{0}'. + + + The OData formatter requires an attached request in order to deserialize. Controller classes must derive from ODataController or be marked with ODataFormattingAttribute. Custom parameter bindings must call GetPerRequestFormatterInstance on each formatter and use these per-request instances. + + + The request must have an associated EDM model. Consider using the extension method HttpConfiguration.MapODataServiceRoute to register a route that parses the OData URI and attaches the model information. + + + A model is required for querying. Consider implementing the EnableQueryAttribute.GetModel method never to return a null value. + + + The request must have an associated OData route name. Consider using the extension request.ODataProperties().RouteName to set a route name. + + + Cannot change multiplicity of the collection navigation property '{0}'. + + + The ODataMediaTypeFormatter was unable to determine the base URI for the request. The request must be processed by an OData route for the OData formatter to serialize the response. + + + {0} cannot write an object of type '{1}'. + + + The 'RootElementName' property is required on '{0}'. + + + '{0}' cannot be deserialized using the ODataMediaTypeFormatter. + + + Cannot find the resource type '{0}' in the model. + + + The EDM model is missing on the read context. The model is required on the read context to deserialize the payload. + + + Cannot find nested property '{0}' on the resource type '{1}'. + + + 'select' and 'expand' cannot be both null or empty. + + + $select does not support selections of type '{0}'. + + + The type '{0}' is not an entity type. Only entity types support $select and $expand. + + + The EDM instance of type '{0}' is missing the property '{1}'. + + + The EDM type of the object of type '{0}' is null. The EDM type of an {1} cannot be null. + {1} may be Microsoft.AspNet.OData.IEdmObject which does not scan. https://aspnetwebstack.codeplex.com/workitem/1558 + + + The property 'EdmObject' of {0} cannot be null. + + + The batch request must have '{0}' or '{1}' as the media type. + + + The batch request must have a boundary specification in the "Content-Type" header. + + + The batch request must have a "Content-Type" header. + + + The current batch reader state '{0}' is invalid. The expected state is '{1}'. + + + The 'Content' property on the batch request cannot be null. + + + '{0}' should be less than or equal to '{1}'. + + + The request includes a $expand path which is too deep. The maximum depth allowed is {0}. To increase the limit, set the '{1}' property on EnableQueryAttribute or ODataValidationSettings, or set the 'MaxDepth' property in ExpandAttribute. + + + The requested resource is not a collection. Query options $filter, $orderby, $count, $skip, and $top can be applied only on collections. + + + The action '{0}' on controller '{1}' returned a {2} containing more than one element. {2} must have zero or one elements. + + + A path within the select or expand query option is not supported. + + + The property 'Url' of {0} cannot be null. + + + Cannot get property '{0}' of a null EDM object of type '{1}'. + + + The EDM type '{0}' of kind '{1}' is not supported. + + + The element type '{0}' of the given collection type '{1}' is not of the type '{2}'. + + + The action '{0}' is not bound to an entity. Only actions that are bound to entities can have action links. + + + The action '{0}' is not bound to the collection of entity. Only actions that are bound to entities can have action links. + + + {0} is not an entity type. Only entity types are supported. + + + The edit link builder for the entity set '{0}' returned null. An edit link is required for the location header. + + + The query option is not bound to any CLR type. '{0}' is only supported with a query option bound to a CLR type. + + + The property 'ElementClrType' of {0} cannot be null. + + + The type '{0}' of the property '{1}' on type '{2}' does not have a Clear method. Consider using a collection type that does have a Clear method, such as IList<T> or ICollection<T>. + + + The number of clauses in $orderby query option exceeded the maximum number allowed. The maximum number of $orderby clauses allowed is {0}. + + + The EDM type '{0}' is already declared as an entity type. Use the method 'ReturnsCollectionFromEntitySet' if the return type is an entity collection. + + + The EDM type '{0}' is already declared as an entity type. Use the method 'ReturnsFromEntitySet' if the return type is an entity. + + + Unterminated string literal at {0} in segment '{1}'. + + + The count of single quotes in non-string literal '{0}' must be 0 or 2 in segment '{1}'. + + + The literal '{0}' has a bad format in segment '{1}'. + + + No key name was found at {0} in segment '{1}'. + + + No value for key '{0}' was found at {1} in segment '{2}'. + + + Duplicate key '{0}' found in segment '{1}'. + + + A non-null request URI must be provided to determine if a '{0}' matches a given request or response message. + + + Parameter alias '{0}' in segment '{1}' is empty. + + + Parameter alias '{0}' in segment '{1}' does not start with '{{' or ends with '}}'. + + + Key template value '{0}' for key segment '{1}' is empty. + + + Key template value '{0}' for key segment '{1}' does not start with '{{' or ends with '}}'. + + + Found an unresolved path segment '{0}' in the OData path template '{1}'. + + + The given OData path template '{0}' is invalid. + + + The path template '{0}' on the action '{1}' in controller '{2}' is not a valid OData path template. {3} + + + The OData route prefix '{0}' on the controller '{1}' starts with a '/'. Route prefixes cannot start with a '/'. + + + The function '{0}' is not bound to an entity. Only functions that are bound to entities can have function links. + + + The function '{0}' is not bound to the collection of entity. Only functions that are bound to entities can have function links. + + + Found more than one action with name '{0}' bound to the same type '{1}'. Each bound action must have a different binding type or name. + + + Found more than one unbound action with name '{0}'. Each unbound action must have an unique action name. + + + Invalid EntitySetPath detected. '{0}' is not a valid entity set path for operation '{1}'. + + + The function parameter '{0}' cannot be found. + + + The operation segment must be a function segment. + + + The operation import segment must be a function import segment. + + + The ETag handler set in the configuration property is of type '{0}', which does not implement IETagHandler. + + + The ETag handler set in the configuration property must not be null. + + + The time zone info set in the configuration property is of type '{0}'. The time zone info type must be type '{1}'. + + + Cannot apply {0} of '{1}' to {2} of '{3}'. + + + The ETag is not well-formed. + + + The property '{0}' cannot be used in the $filter query option. + + + The property '{0}' cannot be used in the $orderby query option. + + + The property '{0}' cannot be used in the $expand query option. + + + The property '{0}' cannot be used in the $select query option. + + + The property '{0}' cannot be used for navigation. + + + The property '{0}' cannot be used for $count. + + + The entity set '{0}' cannot be used for $count. + + + The entity '{0}' does not have a key defined. + + + The entity type '{0}' of navigation property '{1}' on structural type '{2}' does not have a key defined. + + + The mapper provider must return a valid '{0}' instance for the given '{1}' IEdmType. + + + The key mapping for the property '{0}' can't be null or empty. + + + '{0}' is not a valid singleton name. The singleton name cannot contain '.'. + + + The singleton '{0}' was already configured with a different EntityType ('{1}'). + + + The entity set name '{0}' was already configured as a singleton. + + + The singleton name '{0}' was already configured as an entity set. + + + The related entity set or singleton cannot be found from the OData path. The related entity set or singleton is required to serialize the payload. + + + The related entity set or singleton cannot be found from the OData path. The related entity set or singleton is required to deserialize the payload. + + + The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code. + + + Found more than one dynamic property container in type '{0}'. Each open type must have at most one dynamic property container. + + + The name of dynamic property '{0}' was already used as the declared property name of open type '{1}'. + + + Duplicate dynamic property name '{0}' found in open type '{1}'. Each dynamic property name must be unique. + + + The dynamic dictionary property '{0}' of type '{1}' cannot be set. The dynamic property dictionary must have a setter. + + + Type '{0}' is not supported as dynamic property annotation. Referenced property must be of type '{1}'. + + + Request URI '{0}' too short to contain OData path '{1}'. + + + Request URI '{0}' does not contain OData path '{1}'. + + + The Id link builder for the entity set '{0}' returned null. An Id link is required for the OData-EntityId header. + + + The type '{0}' is a nested collection type. Nested collection types are not allowed. + + + The dynamic property '{0}' with Edm type '{1}' cannot be serialized. + + + The type '{0}' of dynamic property '{1}' is not supported. + + + The entity type '{0}' cannot be configured as a complex type because the derived type '{1}' is already configured as an entity type. + + + Cannot determine the Edm type for the CLR type '{0}' because the derived type '{1}' is configured as entity type and another derived type '{2}' is configured as complex type. + + + The dependent property type '{0}' is not same as the principal property type '{1}. The dependent and principal properties must have must have same types in the same order. + + + The property type '{0}' of the referential constraint is not valid. The referential constraint property type must be primitive type. + + + Referential constraint for navigation property '{0}' on type '{1}' is not supported. Only required or optional navigation properties support referential constraint. + + + The '{0}' property '{1}' is already configured to have a relationship with '{2}' property '{3}' in the referential constraint. + + + The entity set or singleton '{0}' is based on type '{1}' that has no keys defined. + + + The entity set '{0}' is based on type '{1}' that has no keys defined. + + + The type '{0}' of the parameter '{1}' must be a collection. + + + The Uri '{0}' in the parameter is invalid. + + + The type '{0}' of the parameter '{1}' does not have an Add method. Consider using a collection type that does have an Add method - for example IList<T> or ICollection<T>. + + + The attribute routing template contains invalid segment '{0}'. + + + Aggregation method '{0}' is not supported. + + + Aggregation '{0}' not supported for property '{1}' of type '{2}'. + + + Transformation kind {0} is not supported. + + + The function '{0}' is already bound to another method. + + + Type verification failed. Expected non-nullable type '{0}' but received a null value. + + + The container built by the container builder must not be null. + + + The container builder created by the container builder factory must not be null. + + + The given untyped value '{0}' in payload is invalid. Consider using a OData type annotation explicitly. + + + The type name of the dynamic resource set '{0}' is required and cannot be empty. Consider using a OData type annotation explicitly. + + + Cannot re-enable dependency injection for HTTP routes. + + + No non-OData HTTP route registered. + + + A request container already exists on the request. + + + The navigation property binding path '{0}' is not valid. The last segment must be the navigation property '{1}'. + + + The navigation property binding path segment '{0}' of member type '{1}' is not supported. + + + The type '{0}' of the binding type cast segment '{1}' is not a part of the type '{2}' hierarchy. + + + Property or path {0} isn't available in the current context. It was removed in earlier transformation. + + + $filter in $expand of reference navigation property '{0}' is not expected type '{1}' + + + Found unexpected parameter '{0}'. + + + Cannot find '{0}'. The OData services have not been configured. Are you missing a call to AddOData()? + + + Cannot find the services container for route '{0}'. This should not happen and represents a bug. + + + Cannot find the services container for the non-OData route. This can occur when using OData components on the non-OData route and is usually a configuration issue. Call EnableDependencyInjection() to enable OData components on non-OData routes. This may also occur when a request was mistakenly handled by the ASP.NET Core routing layer instead of the OData routing layer, for instance the URL does not include the OData route prefix configured via a call to MapODataServiceRoute(). + + + {0} type of aggregation is not supported. + + + The value must be a boolean. + + + Unable to parse the skiptoken value. Skiptoken value should always be server generated. + + + $apply query options not supported for LINQ to SQL providers + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpers.cs new file mode 100644 index 0000000..c2b34cd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpers.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.OData.Common +{ + /// + /// Helpers for safely using Task libraries. + /// + internal static class TaskHelpers + { +#if NETFX // These are only used in the AspNet version. + private static readonly Task _defaultCompleted = Task.FromResult(default(AsyncVoid)); + + /// + /// Returns a canceled Task. The task is completed, IsCanceled = True, IsFaulted = False. + /// + internal static Task Canceled() + { + return CancelCache.Canceled; + } +#endif + + /// + /// Returns a completed task that has no result. + /// + internal static Task Completed() + { +#if NETCORE + return Task.CompletedTask; +#else + return _defaultCompleted; +#endif + } + + /// + /// Returns an error task. The task is Completed, IsCanceled = False, IsFaulted = True + /// + internal static Task FromError(Exception exception) + { +#if NETCORE + return Task.FromException(exception); +#else + return FromError(exception); +#endif + } + + /// + /// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True + /// + /// + internal static Task FromError(Exception exception) + { +#if NETCORE + return Task.FromException(exception); +#else + TaskCompletionSource tcs = new TaskCompletionSource(); + tcs.SetException(exception); + return tcs.Task; +#endif + } + +#if NETFX // These are only used in the AspNet version. + /// + /// Used as the T in a "conversion" of a Task into a Task{T} + /// + private struct AsyncVoid + { + } + + /// + /// This class is a convenient cache for per-type cancelled tasks + /// + private static class CancelCache + { + public static readonly Task Canceled = GetCancelledTask(); + + private static Task GetCancelledTask() + { + TaskCompletionSource tcs = new TaskCompletionSource(); + tcs.SetCanceled(); + return tcs.Task; + } + } +#endif + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpersExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpersExtensions.cs new file mode 100644 index 0000000..544248a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Common/TaskHelpersExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.OData.Common +{ + internal static class TaskHelpersExtensions + { + /// + /// Cast Task to Task of object + /// + internal static async Task CastToObject(this Task task) + { + await task; + return null; + } + + /// + /// Cast Task of T to Task of object + /// + internal static async Task CastToObject(this Task task) + { + return (object)await task; + } + + /// + /// Throws the first faulting exception for a task which is faulted. It preserves the original stack trace when + /// throwing the exception. Note: It is the caller's responsibility not to pass incomplete tasks to this + /// method, because it does degenerate into a call to the equivalent of .Wait() on the task when it hasn't yet + /// completed. + /// + internal static void ThrowIfFaulted(this Task task) + { + task.GetAwaiter().GetResult(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CompiledPropertyAccessor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CompiledPropertyAccessor.cs new file mode 100644 index 0000000..4fb8630 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CompiledPropertyAccessor.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Formatter.Deserialization; + +namespace Microsoft.AspNet.OData +{ + /// + /// CompiledPropertyAccessor is a that pre-compiles (using expression) + /// a Getter and Setter for the PropertyInfo of TEntityType provided via the constructor. + /// + /// The type on which the PropertyInfo exists + internal class CompiledPropertyAccessor : PropertyAccessor where TEntityType : class + { + private bool _isCollection; + private PropertyInfo _property; + private Action _setter; + private Func _getter; + + public CompiledPropertyAccessor(PropertyInfo property) + : base(property) + { + _property = property; + _isCollection = property.PropertyType.IsCollection(); + _getter = MakeGetter(Property); + + if (!_isCollection) + { + _setter = MakeSetter(property); + } + } + + public override object GetValue(TEntityType entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + return _getter(entity); + } + + public override void SetValue(TEntityType entity, object value) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + if (_isCollection) + { + DeserializationHelpers.SetCollectionProperty(entity, _property.Name, edmPropertyType: null, + value: value, clearCollection: true); + } + else + { + _setter(entity, value); + } + } + + private static Action MakeSetter(PropertyInfo property) + { + Type type = typeof(TEntityType); + ParameterExpression entityParameter = Expression.Parameter(type); + ParameterExpression objectParameter = Expression.Parameter(typeof(object)); + MemberExpression toProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property); + UnaryExpression fromValue = Expression.Convert(objectParameter, property.PropertyType); + BinaryExpression assignment = Expression.Assign(toProperty, fromValue); + Expression> lambda = Expression.Lambda>(assignment, entityParameter, objectParameter); + return lambda.Compile(); + } + + private static Func MakeGetter(PropertyInfo property) + { + Type type = typeof(TEntityType); + ParameterExpression entityParameter = Expression.Parameter(type); + MemberExpression fromProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property); + UnaryExpression convert = Expression.Convert(fromProperty, typeof(Object)); + Expression> lambda = Expression.Lambda>(convert, entityParameter); + return lambda.Compile(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ConcurrencyPropertiesAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ConcurrencyPropertiesAnnotation.cs new file mode 100644 index 0000000..c41e85c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ConcurrencyPropertiesAnnotation.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Annotation to store cache for concurrency properties + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Annotation suffix is more appropriate here.")] + public class ConcurrencyPropertiesAnnotation : ConcurrentDictionary> + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ContentIdHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ContentIdHelpers.cs new file mode 100644 index 0000000..1ce4054 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ContentIdHelpers.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.OData +{ + internal static class ContentIdHelpers + { + public static string ResolveContentId(string url, IDictionary contentIdToLocationMapping) + { + Contract.Assert(url != null); + Contract.Assert(contentIdToLocationMapping != null); + + int startIndex = 0; + + while (true) + { + startIndex = url.IndexOf('$', startIndex); + + if (startIndex == -1) + { + break; + } + + int keyLength = 0; + + while (startIndex + keyLength < url.Length - 1 && IsContentIdCharacter(url[startIndex + keyLength + 1])) + { + keyLength++; + } + + if (keyLength > 0) + { + // Might have matched a $ alias. + string locationKey = url.Substring(startIndex + 1, keyLength); + string locationValue; + + if (contentIdToLocationMapping.TryGetValue(locationKey, out locationValue)) + { + // As location headers MUST be absolute URL's, we can ignore everything + // before the $content-id while resolving it. + return locationValue + url.Substring(startIndex + 1 + keyLength); + } + } + + startIndex++; + } + + return url; + } + + public static void AddLocationHeaderToMapping( + Uri location, + IDictionary contentIdToLocationMapping, + string contentId) + { + Contract.Assert(contentIdToLocationMapping != null); + Contract.Assert(contentId != null); + + if (location != null) + { + contentIdToLocationMapping.Add(contentId, location.AbsoluteUri); + } + } + + private static bool IsContentIdCharacter(char c) + { + // According to the OData ABNF grammar, Content-IDs follow the scheme. + // content-id = "Content-ID" ":" OWS 1*unreserved + // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + switch (c) + { + case '-': + case '.': + case '_': + case '~': + return true; + default: + return Char.IsLetterOrDigit(c); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CustomAggregateMethodAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CustomAggregateMethodAnnotation.cs new file mode 100644 index 0000000..e510840 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/CustomAggregateMethodAnnotation.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace Microsoft.AspNet.OData +{ + /// + /// Allows client to tell OData which are the custom aggregation methods defined. + /// In order to do it, it must receive a methodToken - that is the full identifier + /// of the method in the OData URL - and an IDictionary that maps the input type + /// of the aggregation method to its MethodInfo. + /// + public class CustomAggregateMethodAnnotation + { + private readonly Dictionary> _tokenToMethodMap + = new Dictionary>(); + + /// + /// Initializes a new instance of the class. + /// + public CustomAggregateMethodAnnotation() + { + } + + /// + /// Adds all implementations of a method that share the same methodToken. + /// + /// The given method token. + /// The given method dictionary. + /// + public CustomAggregateMethodAnnotation AddMethod(string methodToken, IDictionary methods) + { + _tokenToMethodMap.Add(methodToken, methods); + return this; + } + + /// + /// Get an implementation of a method with the specifies returnType and methodToken. + /// If there's no method that matches the requirements, returns null. + /// + /// The given method token. + /// The given return type. + /// The output of method info. + /// True if the method info was found, false otherwise. + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Out param is appropriate here")] + public bool GetMethodInfo(string methodToken, Type returnType, out MethodInfo methodInfo) + { + IDictionary methodWrapper; + methodInfo = null; + + if (_tokenToMethodMap.TryGetValue(methodToken, out methodWrapper)) + { + return methodWrapper.TryGetValue(returnType, out methodInfo); + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DefaultContainerBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DefaultContainerBuilder.cs new file mode 100644 index 0000000..aa335b5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DefaultContainerBuilder.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData +{ + /// + /// The default container builder implementation based on the Microsoft dependency injection framework. + /// + public class DefaultContainerBuilder : IContainerBuilder + { + private readonly IServiceCollection services = new ServiceCollection(); + + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The implementation type of the service. + /// The instance itself. + public virtual IContainerBuilder AddService( + Microsoft.OData.ServiceLifetime lifetime, + Type serviceType, + Type implementationType) + { + if (serviceType == null) + { + throw Error.ArgumentNull("serviceType"); + } + + if (implementationType == null) + { + throw Error.ArgumentNull("implementationType"); + } + + services.Add(new ServiceDescriptor( + serviceType, implementationType, TranslateServiceLifetime(lifetime))); + + return this; + } + + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The factory that creates the service. + /// The instance itself. + public Microsoft.OData.IContainerBuilder AddService( + Microsoft.OData.ServiceLifetime lifetime, + Type serviceType, + Func implementationFactory) + { + if (serviceType == null) + { + throw Error.ArgumentNull("serviceType"); + } + + if (implementationFactory == null) + { + throw Error.ArgumentNull("implementationFactory"); + } + + services.Add(new ServiceDescriptor( + serviceType, implementationFactory, TranslateServiceLifetime(lifetime))); + + return this; + } + + /// + /// Builds a container which implements and contains + /// all the services registered. + /// + /// The container built by this builder. + public virtual IServiceProvider BuildContainer() + { + /* "services.BuildServiceProvider()" returns IServiceProvider in Microsoft.Extensions.DependencyInjection 1.0 and ServiceProvider in Microsoft.Extensions.DependencyInjection 2.0 + * (This is a breaking change)[https://github.com/aspnet/DependencyInjection/issues/550]. + * To support both versions with the same code base in OData/WebAPI we decided to call that extension method using reflection. + * More info at https://github.com/OData/WebApi/pull/1082 + */ + + Assembly microsoftExtensionsDependencyInjectionAssembly = services.GetType().GetTypeInfo().Assembly; + TypeInfo serviceCollectionContainerBuilderExtensionsType = microsoftExtensionsDependencyInjectionAssembly.GetType(typeof(ServiceCollectionContainerBuilderExtensions).GetTypeInfo().FullName).GetTypeInfo(); + MethodInfo buildServiceProviderMethod = serviceCollectionContainerBuilderExtensionsType.GetMethod("BuildServiceProvider", new[] { typeof(IServiceCollection) }); + + return (IServiceProvider)buildServiceProviderMethod.Invoke(null, new object[] { services }); + } + + private static Microsoft.Extensions.DependencyInjection.ServiceLifetime TranslateServiceLifetime( + Microsoft.OData.ServiceLifetime lifetime) + { + switch (lifetime) + { + case Microsoft.OData.ServiceLifetime.Scoped: + return Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped; + case Microsoft.OData.ServiceLifetime.Singleton: + return Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton; + default: + return Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Delta.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Delta.cs new file mode 100644 index 0000000..7675def --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Delta.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Dynamic; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + /// + /// A class the tracks changes (i.e. the Delta) for an entity. + /// + [NonValidatingParameterBinding] + public abstract class Delta : DynamicObject, IDelta + { + /// + /// Clears the Delta and resets the underlying Entity. + /// + public abstract void Clear(); + + /// + /// Attempts to set the Property called to the specified. + /// + /// Only properties that exist on Entity can be set. + /// If there is a type mismatch the request will fail. + /// + /// + /// The name of the Property + /// The new value of the Property + /// True if successful + public abstract bool TrySetPropertyValue(string name, object value); + + /// + /// Attempts to get the value of the Property called from the underlying Entity. + /// + /// Only properties that exist on Entity can be retrieved. + /// Both modified and unmodified properties can be retrieved. + /// + /// + /// The name of the Property + /// The value of the Property + /// True if the Property was found + public abstract bool TryGetPropertyValue(string name, out object value); + + /// + /// Attempts to get the of the Property called from the underlying Entity. + /// + /// Only properties that exist on Entity can be retrieved. + /// Both modified and unmodified properties can be retrieved. + /// + /// + /// The name of the Property + /// The type of the Property + /// Returns true if the Property was found and false if not. + public abstract bool TryGetPropertyType(string name, out Type type); + + /// + /// Overrides the DynamicObject TrySetMember method, so that only the properties + /// of Entity can be set. + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (binder == null) + { + throw Error.ArgumentNull("binder"); + } + + return TrySetPropertyValue(binder.Name, value); + } + + /// + /// Overrides the DynamicObject TryGetMember method, so that only the properties + /// of Entity can be got. + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (binder == null) + { + throw Error.ArgumentNull("binder"); + } + + return TryGetPropertyValue(binder.Name, out result); + } + + /// + /// Returns the Properties that have been modified through this Delta as an + /// enumeration of Property Names + /// + public abstract IEnumerable GetChangedPropertyNames(); + + /// + /// Returns the Properties that have not been modified through this Delta as an + /// enumeration of Property Names + /// + public abstract IEnumerable GetUnchangedPropertyNames(); + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DeltaOfTStructuralType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DeltaOfTStructuralType.cs new file mode 100644 index 0000000..9d4c0c4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/DeltaOfTStructuralType.cs @@ -0,0 +1,661 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.AspNet.OData.Builder.Conventions.Attributes; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData +{ + /// + /// A class the tracks changes (i.e. the Delta) for a particular . + /// + /// TStructuralType is the type of the instance this delta tracks changes for. + [NonValidatingParameterBinding] + public class Delta : TypedDelta, IDelta where TStructuralType : class + { + // cache property accessors for this type and all its derived types. + private static ConcurrentDictionary>> _propertyCache + = new ConcurrentDictionary>>(); + + private Dictionary> _allProperties; + private HashSet _updatableProperties; + + private HashSet _changedProperties; + + // Nested resources or structures changed at this level. + private IDictionary _deltaNestedResources; + + private TStructuralType _instance; + private Type _structuredType; + + private PropertyInfo _dynamicDictionaryPropertyinfo; + private HashSet _changedDynamicProperties; + private IDictionary _dynamicDictionaryCache; + + /// + /// Initializes a new instance of . + /// + public Delta() + : this(typeof(TStructuralType)) + { + } + + /// + /// Initializes a new instance of . + /// + /// The derived entity type or complex type for which the changes would be tracked. + /// should be assignable to instances of . + /// + public Delta(Type structuralType) + : this(structuralType, updatableProperties: null) + { + } + + /// + /// Initializes a new instance of . + /// + /// The derived entity type or complex type for which the changes would be tracked. + /// should be assignable to instances of . + /// + /// The set of properties that can be updated or reset. Unknown property + /// names, including those of dynamic properties, are ignored. + public Delta(Type structuralType, IEnumerable updatableProperties) + : this(structuralType, updatableProperties: updatableProperties, dynamicDictionaryPropertyInfo: null) + { + } + + /// + /// Initializes a new instance of . + /// + /// The derived entity type or complex type for which the changes would be tracked. + /// should be assignable to instances of . + /// + /// The set of properties that can be updated or reset. Unknown property + /// names, including those of dynamic properties, are ignored. + /// The property info that is used as dictionary of dynamic + /// properties. null means this entity type is not open. + public Delta(Type structuralType, IEnumerable updatableProperties, + PropertyInfo dynamicDictionaryPropertyInfo) + { + _dynamicDictionaryPropertyinfo = dynamicDictionaryPropertyInfo; + Reset(structuralType); + InitializeProperties(updatableProperties); + } + + /// + public override Type StructuredType + { + get + { + return _structuredType; + } + } + + /// + public override Type ExpectedClrType + { + get { return typeof(TStructuralType); } + } + + /// + public override void Clear() + { + Reset(_structuredType); + } + + /// + public override bool TrySetPropertyValue(string name, object value) + { + if (value is IDelta) + { + return TrySetNestedResourceInternal(name, value); + } + else + { + return TrySetPropertyValueInternal(name, value); + } + } + + /// + public override bool TryGetPropertyValue(string name, out object value) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + + if (_dynamicDictionaryPropertyinfo != null) + { + if (_dynamicDictionaryCache == null) + { + _dynamicDictionaryCache = + GetDynamicPropertyDictionary(_dynamicDictionaryPropertyinfo, _instance, create: false); + } + + if (_dynamicDictionaryCache != null && _dynamicDictionaryCache.TryGetValue(name, out value)) + { + return true; + } + } + + if (this._deltaNestedResources.ContainsKey(name)) + { + // If this is a nested resource, get the value from the dictionary of nested resources. + object deltaNestedResource = _deltaNestedResources[name]; + + Contract.Assert(deltaNestedResource != null, "deltaNestedResource != null"); + Contract.Assert(IsDeltaOfT(deltaNestedResource.GetType())); + + // Get the Delta<{NestedResourceType}>._instance using Reflection. + FieldInfo field = deltaNestedResource.GetType().GetField("_instance", BindingFlags.NonPublic | BindingFlags.Instance); + Contract.Assert(field != null, "field != null"); + value = field.GetValue(deltaNestedResource); + return true; + } + else + { + // try to retrieve the value of property. + PropertyAccessor cacheHit; + if (_allProperties.TryGetValue(name, out cacheHit)) + { + value = cacheHit.GetValue(_instance); + return true; + } + } + + value = null; + return false; + } + + /// + public override bool TryGetPropertyType(string name, out Type type) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + + if (_dynamicDictionaryPropertyinfo != null) + { + if (_dynamicDictionaryCache == null) + { + _dynamicDictionaryCache = + GetDynamicPropertyDictionary(_dynamicDictionaryPropertyinfo, _instance, create: false); + } + + object dynamicValue; + if (_dynamicDictionaryCache != null && + _dynamicDictionaryCache.TryGetValue(name, out dynamicValue)) + { + if (dynamicValue == null) + { + type = null; + return false; + } + + type = dynamicValue.GetType(); + return true; + } + } + + PropertyAccessor value; + if (_allProperties.TryGetValue(name, out value)) + { + type = value.Property.PropertyType; + return true; + } + + type = null; + return false; + } + + /// + /// Returns the instance that holds all the changes (and original values) being tracked by this Delta. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not appropriate to be a property")] + public TStructuralType GetInstance() + { + return _instance; + } + + /// + /// Returns the known properties that have been modified through this as an + /// of property Names. + /// Includes the structural properties at current level. + /// Does not include the names of the changed dynamic properties. + /// + public override IEnumerable GetChangedPropertyNames() + { + return _changedProperties.Concat(_deltaNestedResources.Keys); + } + + /// + /// Returns the known properties that have not been modified through this as an + /// of property Names. Does not include the names of the changed dynamic + /// properties. + /// + public override IEnumerable GetUnchangedPropertyNames() + { + return _updatableProperties.Except(GetChangedPropertyNames()); + } + + /// + /// Copies the changed property values from the underlying entity (accessible via ) + /// to the entity recursively. + /// + /// The entity to be updated. + public void CopyChangedValues(TStructuralType original) + { + if (original == null) + { + throw Error.ArgumentNull("original"); + } + + // Delta parameter type cannot be derived type of original + // to prevent unrecognizable information from being applied to original resource. + if (!_structuredType.IsAssignableFrom(original.GetType())) + { + throw Error.Argument("original", SRResources.DeltaTypeMismatch, _structuredType, original.GetType()); + } + + RuntimeHelpers.EnsureSufficientExecutionStack(); + + // For regular non-structural properties at current level. + PropertyAccessor[] propertiesToCopy = + this._changedProperties.Select(s => _allProperties[s]).ToArray(); + foreach (PropertyAccessor propertyToCopy in propertiesToCopy) + { + propertyToCopy.Copy(_instance, original); + } + + CopyChangedDynamicValues(original); + + // For nested resources. + foreach (string nestedResourceName in _deltaNestedResources.Keys) + { + // Patch for each nested resource changed under this TStructuralType. + dynamic deltaNestedResource = _deltaNestedResources[nestedResourceName]; + dynamic originalNestedResource = null; + if (!TryGetPropertyRef(original, nestedResourceName, out originalNestedResource)) + { + throw Error.Argument(nestedResourceName, SRResources.DeltaNestedResourceNameNotFound, + nestedResourceName, original.GetType()); + } + + if (originalNestedResource == null) + { + // When patching original target of null value, directly set nested resource. + dynamic deltaObject = _deltaNestedResources[nestedResourceName]; + dynamic instance = deltaObject.GetInstance(); + + // Recursively patch up the instance with the nested resources. + deltaObject.CopyChangedValues(instance); + + _allProperties[nestedResourceName].SetValue(original, instance); + } + else + { + // Recursively patch the subtree. + bool isDeltaType = TypedDelta.IsDeltaOfT(deltaNestedResource.GetType()); + Contract.Assert(isDeltaType, nestedResourceName + "'s corresponding value should be Delta type but is not."); + + deltaNestedResource.CopyChangedValues(originalNestedResource); + } + } + } + + /// + /// Copies the unchanged property values from the underlying entity (accessible via ) + /// to the entity. + /// + /// The entity to be updated. + public void CopyUnchangedValues(TStructuralType original) + { + if (original == null) + { + throw Error.ArgumentNull("original"); + } + + if (!_structuredType.IsInstanceOfType(original)) + { + throw Error.Argument("original", SRResources.DeltaTypeMismatch, _structuredType, original.GetType()); + } + + IEnumerable> propertiesToCopy = GetUnchangedPropertyNames().Select(s => _allProperties[s]); + foreach (PropertyAccessor propertyToCopy in propertiesToCopy) + { + propertyToCopy.Copy(_instance, original); + } + + CopyUnchangedDynamicValues(original); + } + + /// + /// Overwrites the entity with the changes tracked by this Delta. + /// The semantics of this operation are equivalent to a HTTP PATCH operation, hence the name. + /// + /// The entity to be updated. + public void Patch(TStructuralType original) + { + CopyChangedValues(original); + } + + /// + /// Overwrites the entity with the values stored in this Delta. + /// The semantics of this operation are equivalent to a HTTP PUT operation, hence the name. + /// + /// The entity to be updated. + public void Put(TStructuralType original) + { + CopyChangedValues(original); + CopyUnchangedValues(original); + } + + private static void CopyDynamicPropertyDictionary(IDictionary source, + IDictionary dest, PropertyInfo dynamicPropertyInfo, TStructuralType targetEntity) + { + Contract.Assert(source != null); + Contract.Assert(dynamicPropertyInfo != null); + Contract.Assert(targetEntity != null); + + if (source.Count == 0) + { + if (dest != null) + { + dest.Clear(); + } + } + else + { + if (dest == null) + { + dest = GetDynamicPropertyDictionary(dynamicPropertyInfo, targetEntity, create: true); + } + else + { + dest.Clear(); + } + + foreach (KeyValuePair item in source) + { + dest.Add(item); + } + } + } + + private static IDictionary GetDynamicPropertyDictionary(PropertyInfo propertyInfo, + TStructuralType entity, bool create) + { + if (propertyInfo == null) + { + throw Error.ArgumentNull("propertyInfo"); + } + + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + object propertyValue = propertyInfo.GetValue(entity); + if (propertyValue != null) + { + return (IDictionary)propertyValue; + } + + if (create) + { + if (!propertyInfo.CanWrite) + { + throw Error.InvalidOperation(SRResources.CannotSetDynamicPropertyDictionary, propertyInfo.Name, + entity.GetType().FullName); + } + IDictionary newPropertyValue = new Dictionary(); + + propertyInfo.SetValue(entity, newPropertyValue); + return newPropertyValue; + } + + return null; + } + + /// + /// Attempts to get the property by the specified name. + /// + /// The structural object. + /// Name of the property. + /// Output for property value. + /// true if the property is found; false otherwise. + private static bool TryGetPropertyRef(TStructuralType structural, string propertyName, + out dynamic propertyRef) + { + propertyRef = null; + PropertyInfo propertyInfo = structural.GetType().GetProperty(propertyName); + if (propertyInfo != null) + { + propertyRef = propertyInfo.GetValue(structural, null); + return true; + } + + return false; + } + + private void Reset(Type structuralType) + { + if (structuralType == null) + { + throw Error.ArgumentNull("structuralType"); + } + + if (!typeof(TStructuralType).IsAssignableFrom(structuralType)) + { + throw Error.InvalidOperation(SRResources.DeltaEntityTypeNotAssignable, structuralType, typeof(TStructuralType)); + } + + _instance = Activator.CreateInstance(structuralType) as TStructuralType; + _changedProperties = new HashSet(); + _deltaNestedResources = new Dictionary(); + _structuredType = structuralType; + + _changedDynamicProperties = new HashSet(); + _dynamicDictionaryCache = null; + } + + private void InitializeProperties(IEnumerable updatableProperties) + { + _allProperties = _propertyCache.GetOrAdd( + _structuredType, + (backingType) => backingType + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => (p.GetSetMethod() != null || TypeHelper.IsCollection(p.PropertyType)) && p.GetGetMethod() != null) + .Select>(p => new FastPropertyAccessor(p)) + .ToDictionary(p => p.Property.Name)); + + if (updatableProperties != null) + { + _updatableProperties = new HashSet(updatableProperties); + _updatableProperties.IntersectWith(_allProperties.Keys); + } + else + { + _updatableProperties = new HashSet(_allProperties.Keys); + } + + if (_dynamicDictionaryPropertyinfo != null) + { + _updatableProperties.Remove(_dynamicDictionaryPropertyinfo.Name); + } + } + + // Copy changed dynamic properties and leave the unchanged dynamic properties + private void CopyChangedDynamicValues(TStructuralType targetEntity) + { + if (_dynamicDictionaryPropertyinfo == null) + { + return; + } + + if (_dynamicDictionaryCache == null) + { + _dynamicDictionaryCache = + GetDynamicPropertyDictionary(_dynamicDictionaryPropertyinfo, _instance, create: false); + } + + IDictionary fromDictionary = _dynamicDictionaryCache; + if (fromDictionary == null) + { + return; + } + + IDictionary toDictionary = + GetDynamicPropertyDictionary(_dynamicDictionaryPropertyinfo, targetEntity, create: false); + + IDictionary tempDictionary = toDictionary != null + ? new Dictionary(toDictionary) + : new Dictionary(); + + foreach (string dynamicPropertyName in _changedDynamicProperties) + { + object dynamicPropertyValue = fromDictionary[dynamicPropertyName]; + + // a dynamic property value equal to null, it means to remove this dynamic property + if (dynamicPropertyValue == null) + { + tempDictionary.Remove(dynamicPropertyName); + } + else + { + tempDictionary[dynamicPropertyName] = dynamicPropertyValue; + } + } + + CopyDynamicPropertyDictionary(tempDictionary, toDictionary, _dynamicDictionaryPropertyinfo, + targetEntity); + } + + // Missing dynamic structural properties MUST be removed or set to null in *Put* + private void CopyUnchangedDynamicValues(TStructuralType targetEntity) + { + if (_dynamicDictionaryPropertyinfo == null) + { + return; + } + + if (_dynamicDictionaryCache == null) + { + _dynamicDictionaryCache = + GetDynamicPropertyDictionary(_dynamicDictionaryPropertyinfo, _instance, create: false); + } + + IDictionary toDictionary = + GetDynamicPropertyDictionary(_dynamicDictionaryPropertyinfo, targetEntity, create: false); + + if (_dynamicDictionaryCache == null) + { + if (toDictionary != null) + { + toDictionary.Clear(); + } + } + else + { + IDictionary tempDictionary = toDictionary != null + ? new Dictionary(toDictionary) + : new Dictionary(); + + List removedSet = tempDictionary.Keys.Except(_changedDynamicProperties).ToList(); + + foreach (string name in removedSet) + { + tempDictionary.Remove(name); + } + + CopyDynamicPropertyDictionary(tempDictionary, toDictionary, _dynamicDictionaryPropertyinfo, + targetEntity); + } + } + + private bool TrySetPropertyValueInternal(string name, object value) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + + if (_dynamicDictionaryPropertyinfo != null) + { + // Dynamic property can have the same name as the dynamic property dictionary. + if (name == _dynamicDictionaryPropertyinfo.Name || + !_allProperties.ContainsKey(name)) + { + if (_dynamicDictionaryCache == null) + { + _dynamicDictionaryCache = + GetDynamicPropertyDictionary(_dynamicDictionaryPropertyinfo, _instance, create: true); + } + + _dynamicDictionaryCache[name] = value; + _changedDynamicProperties.Add(name); + return true; + } + } + + if (!_updatableProperties.Contains(name)) + { + return false; + } + + PropertyAccessor cacheHit = _allProperties[name]; + + if (value == null && !EdmLibHelpers.IsNullable(cacheHit.Property.PropertyType)) + { + return false; + } + + Type propertyType = cacheHit.Property.PropertyType; + if (value != null && !TypeHelper.IsCollection(propertyType) && !propertyType.IsAssignableFrom(value.GetType())) + { + return false; + } + + cacheHit.SetValue(_instance, value); + _changedProperties.Add(name); + return true; + } + + private bool TrySetNestedResourceInternal(string name, object deltaNestedResource) + { + if (name == null) + { + throw Error.ArgumentNull("name"); + } + + if (!_updatableProperties.Contains(name)) + { + return false; + } + + if (_deltaNestedResources.ContainsKey(name)) + { + // Ignore duplicated nested resource. + return false; + } + + // Add the nested resource in the hierarchy. + // Note: We shouldn't add the structural properties to the _changedProperties, which + // is used for keeping track of changed non-structural properties at current level. + _deltaNestedResources[name] = deltaNestedResource; + + return true; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ETagMessageHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ETagMessageHandler.cs new file mode 100644 index 0000000..4c65664 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ETagMessageHandler.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData +{ + /// + /// Partial implementation of the ETagMessageHandler. + /// + public partial class ETagMessageHandler + { + private static EntityTagHeaderValue GetETag( + int? statusCode, + ODataPath path, + IEdmModel model, + object value, + IETagHandler etagHandler) + { + if (path == null) + { + throw Error.ArgumentNull("path"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (etagHandler == null) + { + throw Error.ArgumentNull("etagHandler"); + } + + // Do not interfere with null responses, we want to bubble it up to the top. + // Do not handle 204 responses as the spec says a 204 response must not include an ETag header + // unless the request's representation data was saved without any transformation applied to the body + // (i.e., the resource's new representation data is identical to the representation data received in the + // PUT request) and the ETag value reflects the new representation. + // Even in that case returning an ETag is optional and it requires access to the original object which is + // not possible with the current architecture, so if the user is interested he can set the ETag in that + // case by himself on the response. + if (statusCode == null || !((int)statusCode.Value >= 200 && (int)statusCode.Value < 300) || statusCode.Value == (int)HttpStatusCode.NoContent) + { + return null; + } + + IEdmEntityType edmType = GetSingleEntityEntityType(path); + + IEdmEntityTypeReference typeReference = GetTypeReference(model, edmType, value); + if (typeReference != null) + { + ResourceContext context = CreateInstanceContext(model, typeReference, value); + context.EdmModel = model; + context.NavigationSource = path.NavigationSource; + return CreateETag(context, etagHandler); + } + + return null; + } + + private static IEdmEntityTypeReference GetTypeReference(IEdmModel model, IEdmEntityType edmType, object value) + { + if (model == null || edmType == null || value == null) + { + return null; + } + + IEdmObject edmObject = value as IEdmEntityObject; + if (edmObject != null) + { + IEdmTypeReference edmTypeReference = edmObject.GetEdmType(); + return edmTypeReference.AsEntity(); + } + + IEdmTypeReference reference = EdmLibHelpers.GetEdmTypeReference(model, value.GetType()); + if (reference != null && reference.Definition.IsOrInheritsFrom(edmType)) + { + return (IEdmEntityTypeReference)reference; + } + + return null; + } + + private static EntityTagHeaderValue CreateETag( + ResourceContext resourceContext, + IETagHandler handler) + { + IEdmModel model = resourceContext.EdmModel; + + IEnumerable concurrencyProperties; + if (model != null && resourceContext.NavigationSource != null) + { + concurrencyProperties = model.GetConcurrencyProperties(resourceContext.NavigationSource).OrderBy(c => c.Name); + } + else + { + concurrencyProperties = Enumerable.Empty(); + } + + IDictionary properties = new Dictionary(); + foreach (IEdmStructuralProperty etagProperty in concurrencyProperties) + { + properties.Add(etagProperty.Name, resourceContext.GetPropertyValue(etagProperty.Name)); + } + return handler.CreateETag(properties); + } + + private static ResourceContext CreateInstanceContext(IEdmModel model, IEdmEntityTypeReference reference, object value) + { + Contract.Assert(reference != null); + Contract.Assert(value != null); + + ODataSerializerContext serializerCtx = new ODataSerializerContext + { + Model = model + }; + return new ResourceContext(serializerCtx, reference, value); + } + + // Retrieves the IEdmEntityType from the path only in the case that we are addressing a single entity. + // We iterate the path backwards and we return as soon as we realize we are referencing a single entity. + // That is, as soon as we find a singleton segment, a key segment or a navigation segment with target + // multiplicity 0..1 or 1. + internal static IEdmEntityType GetSingleEntityEntityType(ODataPath path) + { + if (path == null || path.Segments.Count == 0) + { + return null; + } + + int currentSegmentIndex = path.Segments.Count - 1; + + // Skip a possible sequence of casts at the end of the path. + while (currentSegmentIndex >= 0 && + path.Segments[currentSegmentIndex] is TypeSegment) + { + currentSegmentIndex--; + } + if (currentSegmentIndex < 0) + { + return null; + } + + ODataPathSegment currentSegment = path.Segments[currentSegmentIndex]; + + if (currentSegment is SingletonSegment || currentSegment is KeySegment) + { + return (IEdmEntityType)path.EdmType; + } + + NavigationPropertySegment navigationPropertySegment = currentSegment as NavigationPropertySegment; + if (navigationPropertySegment != null) + { + if (navigationPropertySegment.NavigationProperty.TargetMultiplicity() == EdmMultiplicity.ZeroOrOne || + navigationPropertySegment.NavigationProperty.TargetMultiplicity() == EdmMultiplicity.One) + { + return (IEdmEntityType)path.EdmType; + } + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmChangedObjectCollection.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmChangedObjectCollection.cs new file mode 100644 index 0000000..1af4915 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmChangedObjectCollection.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that is a collection of s. + /// + [NonValidatingParameterBinding] + public class EdmChangedObjectCollection : Collection, IEdmObject + { + private IEdmEntityType _entityType; + private EdmDeltaCollectionType _edmType; + private IEdmCollectionTypeReference _edmTypeReference; + + /// + /// Initializes a new instance of the class. + /// + /// The Edm entity type of the collection. + public EdmChangedObjectCollection(IEdmEntityType entityType) + : base(Enumerable.Empty().ToList()) + { + Initialize(entityType); + } + + /// + /// Initializes a new instance of the class. + /// + /// The Edm type of the collection. + /// The list that is wrapped by the new collection. + public EdmChangedObjectCollection(IEdmEntityType entityType, IList changedObjectList) + : base(changedObjectList) + { + Initialize(entityType); + } + + /// + public IEdmTypeReference GetEdmType() + { + return _edmTypeReference; + } + + private void Initialize(IEdmEntityType entityType) + { + if (entityType == null) + { + throw Error.ArgumentNull("entityType"); + } + + _entityType = entityType; + _edmType = new EdmDeltaCollectionType(new EdmEntityTypeReference(_entityType, isNullable: true)); + _edmTypeReference = new EdmCollectionTypeReference(_edmType); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexCollectionObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexCollectionObject.cs new file mode 100644 index 0000000..ab322a6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexCollectionObject.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that is a collection of s. + /// + [NonValidatingParameterBinding] + public class EdmComplexObjectCollection : Collection, IEdmObject + { + private IEdmCollectionTypeReference _edmType; + + /// + /// Initialzes a new instance of the class. + /// + /// The edm type of the collection. + public EdmComplexObjectCollection(IEdmCollectionTypeReference edmType) + { + Initialize(edmType); + } + + /// + /// Initialzes a new instance of the class. + /// + /// The edm type of the collection. + /// The list that is wrapped by the new collection. + public EdmComplexObjectCollection(IEdmCollectionTypeReference edmType, IList list) + : base(list) + { + Initialize(edmType); + } + + /// + public IEdmTypeReference GetEdmType() + { + return _edmType; + } + + private void Initialize(IEdmCollectionTypeReference edmType) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + if (!edmType.ElementType().IsComplex()) + { + throw Error.Argument("edmType", + SRResources.UnexpectedElementType, edmType.ElementType().ToTraceString(), edmType.ToTraceString(), typeof(IEdmComplexType).Name); + } + + _edmType = edmType; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexObject.cs new file mode 100644 index 0000000..a7ee0a6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmComplexObject.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// + [NonValidatingParameterBinding] + public class EdmComplexObject : EdmStructuredObject, IEdmComplexObject + { + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + public EdmComplexObject(IEdmComplexType edmType) + : this(edmType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + public EdmComplexObject(IEdmComplexTypeReference edmType) + : this(edmType.ComplexDefinition(), edmType.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + /// true if this object can be nullable; otherwise, false. + public EdmComplexObject(IEdmComplexType edmType, bool isNullable) + : base(edmType, isNullable) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaCollectionType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaCollectionType.cs new file mode 100644 index 0000000..638d083 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaCollectionType.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Implementing IEdmCollectionType to identify collection of DeltaFeed. + /// + internal class EdmDeltaCollectionType : IEdmCollectionType + { + private IEdmTypeReference _entityTypeReference; + + internal EdmDeltaCollectionType(IEdmTypeReference entityTypeReference) + { + if (entityTypeReference == null) + { + throw Error.ArgumentNull("entityTypeReference"); + } + _entityTypeReference = entityTypeReference; + } + + /// + public EdmTypeKind TypeKind + { + get + { + return EdmTypeKind.Collection; + } + } + + /// + public IEdmTypeReference ElementType + { + get + { + return _entityTypeReference; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaComplexObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaComplexObject.cs new file mode 100644 index 0000000..e5fd531 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaComplexObject.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// Used to hold the Entry object in the Delta Feed Payload. + /// + [NonValidatingParameterBinding] + public class EdmDeltaComplexObject : EdmComplexObject + { + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + public EdmDeltaComplexObject(IEdmComplexType edmType) + : this(edmType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + public EdmDeltaComplexObject(IEdmComplexTypeReference edmType) + : this(edmType.ComplexDefinition(), edmType.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + /// true if this object can be nullable; otherwise, false. + public EdmDeltaComplexObject(IEdmComplexType edmType, bool isNullable) + : base(edmType, isNullable) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedEntityObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedEntityObject.cs new file mode 100644 index 0000000..25494d7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedEntityObject.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// Used to hold the Deleted Entry object in the Delta Feed Payload. + /// + [NonValidatingParameterBinding] + public class EdmDeltaDeletedEntityObject : EdmEntityObject, IEdmDeltaDeletedEntityObject + { + private string _id; + private DeltaDeletedEntryReason _reason; + private EdmDeltaType _edmType; + private IEdmNavigationSource _navigationSource; + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaDeletedEntityObject. + public EdmDeltaDeletedEntityObject(IEdmEntityType entityType) + : this(entityType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaDeletedEntityObject. + public EdmDeltaDeletedEntityObject(IEdmEntityTypeReference entityTypeReference) + : this(entityTypeReference.EntityDefinition(), entityTypeReference.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaDeletedEntityObject. + /// true if this object can be nullable; otherwise, false. + public EdmDeltaDeletedEntityObject(IEdmEntityType entityType, bool isNullable) + : base(entityType, isNullable) + { + _edmType = new EdmDeltaType(entityType, EdmDeltaEntityKind.DeletedEntry); + } + + /// + public string Id + { + get + { + return _id; + } + set + { + _id = value; + } + } + + /// + public DeltaDeletedEntryReason Reason + { + get + { + return _reason; + } + set + { + _reason = (DeltaDeletedEntryReason)value; + } + } + + /// + public EdmDeltaEntityKind DeltaKind + { + get + { + Contract.Assert(_edmType != null); + return _edmType.DeltaKind; + } + } + + /// + /// The navigation source of the deleted entity. If null, then the deleted entity is from the current feed. + /// + public IEdmNavigationSource NavigationSource + { + get + { + return _navigationSource; + } + set + { + _navigationSource = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedLink.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedLink.cs new file mode 100644 index 0000000..ba64398 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaDeletedLink.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// Used to hold the Deleted Link object in the Delta Feed Payload. + /// + [NonValidatingParameterBinding] + public class EdmDeltaDeletedLink : EdmEntityObject, IEdmDeltaDeletedLink + { + private Uri _source; + private Uri _target; + private string _relationship; + private EdmDeltaType _edmType; + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaDeletedLink. + public EdmDeltaDeletedLink(IEdmEntityType entityType) + : this(entityType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaDeletedLink. + public EdmDeltaDeletedLink(IEdmEntityTypeReference entityTypeReference) + : this(entityTypeReference.EntityDefinition(), entityTypeReference.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaDeletedLink. + /// true if this object can be nullable; otherwise, false. + public EdmDeltaDeletedLink(IEdmEntityType entityType, bool isNullable) + : base(entityType, isNullable) + { + _edmType = new EdmDeltaType(entityType, EdmDeltaEntityKind.DeletedLinkEntry); + } + + /// + public Uri Source + { + get + { + return _source; + } + set + { + _source = value; + } + } + + /// + public Uri Target + { + get + { + return _target; + } + set + { + _target = value; + } + } + + /// + public string Relationship + { + get + { + return _relationship; + } + set + { + _relationship = value; + } + } + + /// + public EdmDeltaEntityKind DeltaKind + { + get + { + Contract.Assert(_edmType != null); + return _edmType.DeltaKind; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityKind.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityKind.cs new file mode 100644 index 0000000..627d3cc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityKind.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// The Kind of the object within the DeltaPayload used to distinguish between Entry/DeletedEntry/DeltaLink/AddedLink. + /// + public enum EdmDeltaEntityKind + { + /// + /// Corresponds to EdmEntityObject (Equivalent of ODataEntry in ODL). + /// + Entry = 0, + + /// + /// Corresponds to EdmDeltaDeletedEntityObject (Equivalent of ODataDeltaDeletedEntry in ODL). + /// + DeletedEntry = 1, + + /// + /// Corresponds to EdmDeltaDeletedLink (Equivalent of ODataDeltaDeletedLink in ODL). + /// + DeletedLinkEntry = 2, + + /// + /// Corresponds to EdmDeltaLink (Equivalent of ODataDeltaLink in ODL). + /// + LinkEntry = 3, + + /// + /// Corresponds to any Unknown item added. + /// + Unknown = 4 + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityObject.cs new file mode 100644 index 0000000..8c0d0bd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaEntityObject.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// Used to hold the Entry object in the Delta Feed Payload. + /// + [NonValidatingParameterBinding] + public class EdmDeltaEntityObject : EdmEntityObject, IEdmChangedObject + { + private EdmDeltaType _edmType; + private IEdmNavigationSource _navigationSource; + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaEntityObject. + public EdmDeltaEntityObject(IEdmEntityType entityType) + : this(entityType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaEntityObject. + public EdmDeltaEntityObject(IEdmEntityTypeReference entityTypeReference) + : this(entityTypeReference.EntityDefinition(), entityTypeReference.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaEntityObject. + /// true if this object can be nullable; otherwise, false. + public EdmDeltaEntityObject(IEdmEntityType entityType, bool isNullable) + : base(entityType, isNullable) + { + _edmType = new EdmDeltaType(entityType, EdmDeltaEntityKind.Entry); + } + + /// + public EdmDeltaEntityKind DeltaKind + { + get + { + Contract.Assert(_edmType != null); + return _edmType.DeltaKind; + } + } + + /// + /// The navigation source of the entity. If null, then the entity is from the current feed. + /// + public IEdmNavigationSource NavigationSource + { + get + { + return _navigationSource; + } + set + { + _navigationSource = value; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaLink.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaLink.cs new file mode 100644 index 0000000..75d750c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaLink.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// Used to hold the Added/Modified Link object in the Delta Feed Payload. + /// + [NonValidatingParameterBinding] + public class EdmDeltaLink : EdmEntityObject, IEdmDeltaLink + { + private Uri _source; + private Uri _target; + private string _relationship; + private EdmDeltaType _edmType; + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaLink. + public EdmDeltaLink(IEdmEntityType entityType) + : this(entityType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaLink. + public EdmDeltaLink(IEdmEntityTypeReference entityTypeReference) + : this(entityTypeReference.EntityDefinition(), entityTypeReference.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this DeltaLink. + /// true if this object can be nullable; otherwise, false. + public EdmDeltaLink(IEdmEntityType entityType, bool isNullable) + : base(entityType, isNullable) + { + _edmType = new EdmDeltaType(entityType, EdmDeltaEntityKind.LinkEntry); + } + + /// + public Uri Source + { + get + { + return _source; + } + set + { + _source = value; + } + } + + /// + public Uri Target + { + get + { + return _target; + } + set + { + _target = value; + } + } + + /// + public string Relationship + { + get + { + return _relationship; + } + set + { + _relationship = value; + } + } + + /// + public EdmDeltaEntityKind DeltaKind + { + get + { + Contract.Assert(_edmType != null); + return _edmType.DeltaKind; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaType.cs new file mode 100644 index 0000000..c787b3d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmDeltaType.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Implementing IEdmType to identify objects which are part of DeltaFeed Payload. + /// + internal class EdmDeltaType : IEdmType + { + private IEdmEntityType _entityType; + private EdmDeltaEntityKind _deltaKind; + + internal EdmDeltaType(IEdmEntityType entityType, EdmDeltaEntityKind deltaKind) + { + if (entityType == null) + { + throw Error.ArgumentNull("entityType"); + } + _entityType = entityType; + _deltaKind = deltaKind; + } + + /// + public EdmTypeKind TypeKind + { + get + { + return EdmTypeKind.Entity; + } + } + + public IEdmEntityType EntityType + { + get + { + return _entityType; + } + } + + /// + /// Returning DeltaKind of the object within DeltaFeed payload + /// + public EdmDeltaEntityKind DeltaKind + { + get + { + return _deltaKind; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityCollectionObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityCollectionObject.cs new file mode 100644 index 0000000..5370a91 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityCollectionObject.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that is a collection of s. + /// + [NonValidatingParameterBinding] + public class EdmEntityObjectCollection : Collection, IEdmObject + { + private IEdmCollectionTypeReference _edmType; + + /// + /// Initializes a new instance of the class. + /// + /// The edm type of the collection. + public EdmEntityObjectCollection(IEdmCollectionTypeReference edmType) + { + Initialize(edmType); + } + + /// + /// Initializes a new instance of the class. + /// + /// The edm type of the collection. + /// The list that is wrapped by the new collection. + public EdmEntityObjectCollection(IEdmCollectionTypeReference edmType, IList list) + : base(list) + { + Initialize(edmType); + } + + /// + public IEdmTypeReference GetEdmType() + { + return _edmType; + } + + private void Initialize(IEdmCollectionTypeReference edmType) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + if (!edmType.ElementType().IsEntity()) + { + throw Error.Argument("edmType", + SRResources.UnexpectedElementType, edmType.ElementType().ToTraceString(), edmType.ToTraceString(), typeof(IEdmEntityType).Name); + } + + _edmType = edmType; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityObject.cs new file mode 100644 index 0000000..bd5cd4c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEntityObject.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// + [NonValidatingParameterBinding] + public class EdmEntityObject : EdmStructuredObject, IEdmEntityObject + { + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + public EdmEntityObject(IEdmEntityType edmType) + : this(edmType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + public EdmEntityObject(IEdmEntityTypeReference edmType) + : this(edmType.EntityDefinition(), edmType.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + /// true if this object can be nullable; otherwise, false. + public EdmEntityObject(IEdmEntityType edmType, bool isNullable) + : base(edmType, isNullable) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObject.cs new file mode 100644 index 0000000..bb32316 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObject.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// + [NonValidatingParameterBinding] + public class EdmEnumObject : IEdmEnumObject + { + private readonly IEdmType _edmType; + + /// + /// Gets the value of the enumeration type. + /// + public string Value { get; set; } + + /// + /// Gets or sets whether the enum object is nullable or not. + /// + public bool IsNullable { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + /// The value of the enumeration type. + public EdmEnumObject(IEdmEnumType edmType, string value) + : this(edmType, value, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + /// The value of the enumeration type. + public EdmEnumObject(IEdmEnumTypeReference edmType, string value) + : this(edmType.EnumDefinition(), value, edmType.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + /// The value of the enumeration type. + /// true if this object can be nullable; otherwise, false. + public EdmEnumObject(IEdmEnumType edmType, string value, bool isNullable) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + _edmType = edmType; + Value = value; + IsNullable = isNullable; + } + + /// + public IEdmTypeReference GetEdmType() + { + return new EdmEnumTypeReference(_edmType as IEdmEnumType, IsNullable); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObjectCollection.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObjectCollection.cs new file mode 100644 index 0000000..ff7f8e0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmEnumObjectCollection.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that is a collection of s. + /// + [NonValidatingParameterBinding] + public class EdmEnumObjectCollection : Collection, IEdmObject + { + private IEdmCollectionTypeReference _edmType; + + /// + /// Initialzes a new instance of the class. + /// + /// The edm type of the collection. + public EdmEnumObjectCollection(IEdmCollectionTypeReference edmType) + : this(edmType, Enumerable.Empty().ToList()) + { + } + + /// + /// Initialzes a new instance of the class. + /// + /// The edm type of the collection. + /// The list that is wrapped by the new collection. + public EdmEnumObjectCollection(IEdmCollectionTypeReference edmType, IList list) + : base(list) + { + Initialize(edmType); + } + + /// + public IEdmTypeReference GetEdmType() + { + return _edmType; + } + + private void Initialize(IEdmCollectionTypeReference edmType) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + if (!edmType.ElementType().IsEnum()) + { + throw Error.Argument("edmType", + SRResources.UnexpectedElementType, edmType.ElementType().ToTraceString(), edmType.ToTraceString(), typeof(IEdmEnumType).Name); + } + + _edmType = edmType; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmModelExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmModelExtensions.cs new file mode 100644 index 0000000..36e57f0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmModelExtensions.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Provides extension methods for the interface. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class EdmModelExtensions + { + /// + /// Gets the to be used while generating self and navigation + /// links for the given navigation source. + /// + /// The containing the navigation source. + /// The navigation source. + /// The if set for the given the singleton; otherwise, + /// a new that generates URLs that follow OData URL conventions. + /// + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "IEdmNavigationSource is more relevant here.")] + public static NavigationSourceLinkBuilderAnnotation GetNavigationSourceLinkBuilder(this IEdmModel model, + IEdmNavigationSource navigationSource) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + NavigationSourceLinkBuilderAnnotation annotation = model + .GetAnnotationValue(navigationSource); + if (annotation == null) + { + // construct and set a navigation source link builder that follows OData URL conventions. + annotation = new NavigationSourceLinkBuilderAnnotation(navigationSource, model); + model.SetNavigationSourceLinkBuilder(navigationSource, annotation); + } + + return annotation; + } + + /// + /// Sets the to be used while generating self and navigation + /// links for the given navigation source. + /// + /// The containing the navigation source. + /// The navigation source. + /// The to set. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "IEdmNavigationSource is more relevant here.")] + public static void SetNavigationSourceLinkBuilder(this IEdmModel model, IEdmNavigationSource navigationSource, + NavigationSourceLinkBuilderAnnotation navigationSourceLinkBuilder) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + model.SetAnnotationValue(navigationSource, navigationSourceLinkBuilder); + } + + /// + /// Gets the to be used while generating operation links for the given action. + /// + /// The containing the operation. + /// The operation for which the link builder is needed. + /// The for the given operation if one is set; otherwise, a new + /// that generates operation links following OData URL conventions. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "IEdmActionImport is more relevant here.")] + public static OperationLinkBuilder GetOperationLinkBuilder(this IEdmModel model, IEdmOperation operation) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + if (operation == null) + { + throw Error.ArgumentNull("operation"); + } + + OperationLinkBuilder linkBuilder = model.GetAnnotationValue(operation); + if (linkBuilder == null) + { + linkBuilder = GetDefaultOperationLinkBuilder(operation); + model.SetOperationLinkBuilder(operation, linkBuilder); + } + + return linkBuilder; + } + + /// + /// Sets the to be used for generating the OData operation link for the given operation. + /// + /// The containing the entity set. + /// The operation for which the operation link is to be generated. + /// The to set. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "IEdmActionImport is more relevant here.")] + public static void SetOperationLinkBuilder(this IEdmModel model, IEdmOperation operation, OperationLinkBuilder operationLinkBuilder) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + model.SetAnnotationValue(operation, operationLinkBuilder); + } + + internal static ClrTypeCache GetTypeMappingCache(this IEdmModel model) + { + Contract.Assert(model != null); + + ClrTypeCache typeMappingCache = model.GetAnnotationValue(model); + if (typeMappingCache == null) + { + typeMappingCache = new ClrTypeCache(); + model.SetAnnotationValue(model, typeMappingCache); + } + + return typeMappingCache; + } + + internal static void SetOperationTitleAnnotation(this IEdmModel model, IEdmOperation action, OperationTitleAnnotation title) + { + Contract.Assert(model != null); + model.SetAnnotationValue(action, title); + } + + internal static OperationTitleAnnotation GetOperationTitleAnnotation(this IEdmModel model, IEdmOperation operation) + { + Contract.Assert(model != null); + return model.GetAnnotationValue(operation); + } + + private static OperationLinkBuilder GetDefaultOperationLinkBuilder(IEdmOperation operation) + { + OperationLinkBuilder linkBuilder = null; + if (operation.Parameters != null) + { + if (operation.Parameters.First().Type.IsEntity()) + { + if (operation is IEdmAction) + { + linkBuilder = new OperationLinkBuilder( + (ResourceContext resourceContext) => + resourceContext.GenerateActionLink(operation), followsConventions: true); + } + else + { + linkBuilder = new OperationLinkBuilder( + (ResourceContext resourceContext) => + resourceContext.GenerateFunctionLink(operation), followsConventions: true); + } + } + else if (operation.Parameters.First().Type.IsCollection()) + { + if (operation is IEdmAction) + { + linkBuilder = + new OperationLinkBuilder( + (ResourceSetContext reseourceSetContext) => + reseourceSetContext.GenerateActionLink(operation), followsConventions: true); + } + else + { + linkBuilder = + new OperationLinkBuilder( + (ResourceSetContext reseourceSetContext) => + reseourceSetContext.GenerateFunctionLink(operation), followsConventions: true); + } + } + } + return linkBuilder; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmStructuredObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmStructuredObject.cs new file mode 100644 index 0000000..b902532 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmStructuredObject.cs @@ -0,0 +1,279 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an with no backing CLR . + /// + [NonValidatingParameterBinding] + public abstract class EdmStructuredObject : Delta, IEdmStructuredObject + { + private Dictionary _container = new Dictionary(); + private HashSet _setProperties = new HashSet(); + + private IEdmStructuredType _expectedEdmType; + private IEdmStructuredType _actualEdmType; + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + protected EdmStructuredObject(IEdmStructuredType edmType) + : this(edmType, isNullable: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + protected EdmStructuredObject(IEdmStructuredTypeReference edmType) + : this(edmType.StructuredDefinition(), edmType.IsNullable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The of this object. + /// true if this object can be nullable; otherwise, false. + protected EdmStructuredObject(IEdmStructuredType edmType, bool isNullable) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + _expectedEdmType = edmType; + _actualEdmType = edmType; + IsNullable = isNullable; + } + + /// + /// Gets or sets the expected of the entity or complex type of this object. + /// + public IEdmStructuredType ExpectedEdmType + { + get { return _expectedEdmType; } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + if (!_actualEdmType.IsOrInheritsFrom(value)) + { + throw Error.InvalidOperation(SRResources.DeltaEntityTypeNotAssignable, + _actualEdmType.ToTraceString(), value.ToTraceString()); + } + + _expectedEdmType = value; + } + } + + /// + /// Gets or sets the actual of the entity or complex type of this object. + /// + public IEdmStructuredType ActualEdmType + { + get { return _actualEdmType; } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + if (!value.IsOrInheritsFrom(_expectedEdmType)) + { + throw Error.InvalidOperation(SRResources.DeltaEntityTypeNotAssignable, + value.ToTraceString(), _expectedEdmType.ToTraceString()); + } + + _actualEdmType = value; + } + } + + /// + /// Gets or sets whether the EDM object is nullable or not. + /// + public bool IsNullable { get; set; } + + /// + public override void Clear() + { + _container.Clear(); + _setProperties.Clear(); + } + + /// + public override bool TrySetPropertyValue(string name, object value) + { + IEdmProperty property = _actualEdmType.FindProperty(name); + if (property != null || _actualEdmType.IsOpen) + { + _setProperties.Add(name); + _container[name] = value; + return true; + } + + return false; + } + + /// + public override bool TryGetPropertyValue(string name, out object value) + { + IEdmProperty property = _actualEdmType.FindProperty(name); + if (property != null || _actualEdmType.IsOpen) + { + if (_container.ContainsKey(name)) + { + value = _container[name]; + return true; + } + else + { + value = GetDefaultValue(property.Type); + // store the default value (but don't update the list of 'set properties'). + _container[name] = value; + return true; + } + } + else + { + value = null; + return false; + } + } + + /// + public override bool TryGetPropertyType(string name, out Type type) + { + IEdmProperty property = _actualEdmType.FindProperty(name); + if (property != null) + { + type = GetClrTypeForUntypedDelta(property.Type); + return true; + } + else if (_actualEdmType.IsOpen && _container.ContainsKey(name)) + { + type = _container[name].GetType(); + return true; + } + else + { + type = null; + return false; + } + } + + /// + /// Get all dynamic properties + /// + public Dictionary TryGetDynamicProperties() + { + if (!_actualEdmType.IsOpen) + { + return new Dictionary(); + } + else + { + return _container.Where(p => _actualEdmType.FindProperty(p.Key) == null).ToDictionary(property => property.Key, property => property.Value); + } + } + + /// + public override IEnumerable GetChangedPropertyNames() + { + return _setProperties; + } + + /// + public override IEnumerable GetUnchangedPropertyNames() + { + return _actualEdmType.Properties().Select(p => p.Name).Except(GetChangedPropertyNames()); + } + + /// + public IEdmTypeReference GetEdmType() + { + return EdmLibHelpers.ToEdmTypeReference(_actualEdmType, IsNullable); + } + + internal static object GetDefaultValue(IEdmTypeReference propertyType) + { + Contract.Assert(propertyType != null); + + bool isCollection = propertyType.IsCollection(); + if (!propertyType.IsNullable || isCollection) + { + Type clrType = GetClrTypeForUntypedDelta(propertyType); + + if (propertyType.IsPrimitive() || + (isCollection && propertyType.AsCollection().ElementType().IsPrimitive())) + { + // primitive or primitive collection + return Activator.CreateInstance(clrType); + } + else + { + // IEdmObject + return Activator.CreateInstance(clrType, propertyType); + } + } + + return null; + } + + internal static Type GetClrTypeForUntypedDelta(IEdmTypeReference edmType) + { + Contract.Assert(edmType != null); + + switch (edmType.TypeKind()) + { + case EdmTypeKind.Primitive: + return EdmLibHelpers.GetClrType(edmType.AsPrimitive(), EdmCoreModel.Instance); + + case EdmTypeKind.Complex: + return typeof(EdmComplexObject); + + case EdmTypeKind.Entity: + return typeof(EdmEntityObject); + + case EdmTypeKind.Enum: + return typeof(EdmEnumObject); + + case EdmTypeKind.Collection: + IEdmTypeReference elementType = edmType.AsCollection().ElementType(); + if (elementType.IsPrimitive()) + { + Type elementClrType = GetClrTypeForUntypedDelta(elementType); + return typeof(List<>).MakeGenericType(elementClrType); + } + else if (elementType.IsComplex()) + { + return typeof(EdmComplexObjectCollection); + } + else if (elementType.IsEntity()) + { + return typeof(EdmEntityObjectCollection); + } + else if (elementType.IsEnum()) + { + return typeof(EdmEnumObjectCollection); + } + break; + } + + throw Error.InvalidOperation(SRResources.UnsupportedEdmType, edmType.ToTraceString(), edmType.TypeKind()); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmTypeExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmTypeExtensions.cs new file mode 100644 index 0000000..cd4cb7c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EdmTypeExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Extension methods for the interface. + /// + public static class EdmTypeExtensions + { + /// + /// Method to determine whether the current type is a Delta Feed + /// + /// IEdmType to be compared + /// True or False if type is same as + public static bool IsDeltaFeed(this IEdmType type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + return (type.GetType() == typeof(EdmDeltaCollectionType)); + } + + /// + /// Method to determine whether the current Edm object is a Delta Entry + /// + /// IEdmObject to be compared + /// True or False if type is same as or + public static bool IsDeltaResource(this IEdmObject resource) + { + if (resource == null) + { + throw Error.ArgumentNull("resource"); + } + return (resource is EdmDeltaEntityObject || resource is EdmDeltaComplexObject); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EnableQueryAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EnableQueryAttribute.cs new file mode 100644 index 0000000..5f732fd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/EnableQueryAttribute.cs @@ -0,0 +1,818 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Partial implementation of the EnableQueryAttribute. + /// + public partial class EnableQueryAttribute + { + private const char CommaSeparator = ','; + + // validation settings + private ODataValidationSettings _validationSettings; + private string _allowedOrderByProperties; + + // query settings + private ODataQuerySettings _querySettings; + + /// + /// Enables a controller action to support OData query parameters. + /// + public EnableQueryAttribute() + { + _validationSettings = new ODataValidationSettings(); + _querySettings = new ODataQuerySettings(); + } + + /// + /// Gets or sets a value indicating whether query composition should + /// alter the original query when necessary to ensure a stable sort order. + /// + /// A true value indicates the original query should + /// be modified when necessary to guarantee a stable sort order. + /// A false value indicates the sort order can be considered + /// stable without modifying the query. Query providers that ensure + /// a stable sort order should set this value to false. + /// The default value is true. + public bool EnsureStableOrdering + { + get + { + return _querySettings.EnsureStableOrdering; + } + set + { + _querySettings.EnsureStableOrdering = value; + } + } + + /// + /// Gets or sets a value indicating how null propagation should + /// be handled during query composition. + /// + /// + /// The default is . + /// + public HandleNullPropagationOption HandleNullPropagation + { + get + { + return _querySettings.HandleNullPropagation; + } + set + { + _querySettings.HandleNullPropagation = value; + } + } + + /// + /// Gets or sets a value indicating whether constants should be parameterized. Parameterizing constants + /// would result in better performance with Entity framework. + /// + /// The default value is true. + public bool EnableConstantParameterization + { + get + { + return _querySettings.EnableConstantParameterization; + } + set + { + _querySettings.EnableConstantParameterization = value; + } + } + + /// + /// Gets or sets a value indicating whether queries with expanded navigations should be formulated + /// to encourage correlated subquery results to be buffered. + /// Buffering correlated subquery results can reduce the number of queries from N + 1 to 2 + /// by buffering results from the subquery. + /// + /// The default value is false. + public bool EnableCorrelatedSubqueryBuffering + { + get + { + return _querySettings.EnableCorrelatedSubqueryBuffering; + } + set + { + _querySettings.EnableCorrelatedSubqueryBuffering = value; + } + } + + /// + /// Gets or sets the maximum depth of the Any or All elements nested inside the query. This limit helps prevent + /// Denial of Service attacks. + /// + /// + /// The maximum depth of the Any or All elements nested inside the query. The default value is 1. + /// + public int MaxAnyAllExpressionDepth + { + get + { + return _validationSettings.MaxAnyAllExpressionDepth; + } + set + { + _validationSettings.MaxAnyAllExpressionDepth = value; + } + } + + /// + /// Gets or sets the maximum number of nodes inside the $filter syntax tree. + /// + /// The default value is 100. + public int MaxNodeCount + { + get + { + return _validationSettings.MaxNodeCount; + } + set + { + _validationSettings.MaxNodeCount = value; + } + } + + /// + /// Gets or sets the maximum number of query results to send back to clients. + /// + /// + /// The maximum number of query results to send back to clients. + /// + public int PageSize + { + get + { + return _querySettings.PageSize ?? default(int); + } + set + { + _querySettings.PageSize = value; + } + } + + /// + /// Honor $filter inside $expand of non-collection navigation property. + /// The expanded property is only populated when the filter evaluates to true. + /// This setting is false by default. + /// + public bool HandleReferenceNavigationPropertyExpandFilter + { + get + { + return _querySettings.HandleReferenceNavigationPropertyExpandFilter; + } + set + { + _querySettings.HandleReferenceNavigationPropertyExpandFilter = value; + } + } + + /// + /// Gets or sets the query parameters that are allowed in queries. + /// + /// The default includes all query options: $filter, $skip, $top, $orderby, $expand, $select, $count, + /// $format, $skiptoken and $deltatoken. + public AllowedQueryOptions AllowedQueryOptions + { + get + { + return _validationSettings.AllowedQueryOptions; + } + set + { + _validationSettings.AllowedQueryOptions = value; + } + } + + /// + /// Gets or sets a value that represents a list of allowed functions used in the $filter query. Supported + /// functions include the following: + /// + /// + /// String related: + /// contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim, + /// concat e.g. ~/Customers?$filter=length(CompanyName) eq 19 + /// + /// + /// DateTime related: + /// year, month, day, hour, minute, second, fractionalseconds, date, time + /// e.g. ~/Employees?$filter=year(BirthDate) eq 1971 + /// + /// + /// Math related: + /// round, floor, ceiling + /// + /// + /// Type related: + /// isof, cast + /// + /// + /// Collection related: + /// any, all + /// + /// + /// + public AllowedFunctions AllowedFunctions + { + get + { + return _validationSettings.AllowedFunctions; + } + set + { + _validationSettings.AllowedFunctions = value; + } + } + + /// + /// Gets or sets a value that represents a list of allowed arithmetic operators including 'add', 'sub', 'mul', + /// 'div', 'mod'. + /// + public AllowedArithmeticOperators AllowedArithmeticOperators + { + get + { + return _validationSettings.AllowedArithmeticOperators; + } + set + { + _validationSettings.AllowedArithmeticOperators = value; + } + } + + /// + /// Gets or sets a value that represents a list of allowed logical Operators such as 'eq', 'ne', 'gt', 'ge', + /// 'lt', 'le', 'and', 'or', 'not'. + /// + public AllowedLogicalOperators AllowedLogicalOperators + { + get + { + return _validationSettings.AllowedLogicalOperators; + } + set + { + _validationSettings.AllowedLogicalOperators = value; + } + } + + /// + /// Gets or sets a string with comma separated list of property names. The queryable result can only be + /// ordered by those properties defined in this list. + /// + /// Note, by default this string is null, which means it can be ordered by any property. + /// + /// For example, setting this value to null or empty string means that we allow ordering the queryable + /// result by any properties. Setting this value to "Name" means we only allow queryable result to be ordered + /// by Name property. + /// + public string AllowedOrderByProperties + { + get + { + return _allowedOrderByProperties; + } + set + { + _allowedOrderByProperties = value; + + if (String.IsNullOrEmpty(value)) + { + _validationSettings.AllowedOrderByProperties.Clear(); + } + else + { + // now parse the value and set it to validationSettings + string[] properties = _allowedOrderByProperties.Split(CommaSeparator); + for (int i = 0; i < properties.Length; i++) + { + _validationSettings.AllowedOrderByProperties.Add(properties[i].Trim()); + } + } + } + } + + /// + /// Gets or sets the max value of $skip that a client can request. + /// + public int MaxSkip + { + get + { + return _validationSettings.MaxSkip ?? default(int); + } + set + { + _validationSettings.MaxSkip = value; + } + } + + /// + /// Gets or sets the max value of $top that a client can request. + /// + public int MaxTop + { + get + { + return _validationSettings.MaxTop ?? default(int); + } + set + { + _validationSettings.MaxTop = value; + } + } + + /// + /// Gets or sets the max expansion depth for the $expand query option. To disable the maximum expansion depth + /// check, set this property to 0. + /// + public int MaxExpansionDepth + { + get { return _validationSettings.MaxExpansionDepth; } + set + { + _validationSettings.MaxExpansionDepth = value; + } + } + + /// + /// Gets or sets the maximum number of expressions that can be present in the $orderby. + /// + public int MaxOrderByNodeCount + { + get { return _validationSettings.MaxOrderByNodeCount; } + set + { + _validationSettings.MaxOrderByNodeCount = value; + } + } + + /// + /// Performs the query composition after action is executed. It first tries to retrieve the IQueryable from the + /// returning response message. It then validates the query from uri based on the validation settings on + /// . It finally applies the query appropriately, and reset it back on + /// the response message. + /// + /// The response content value. + /// The content as SingleResult.Queryable. + /// The action context, i.e. action and controller name. + /// The internal request. + /// A function to get the model. + /// A function used to create and validate query options. + /// An action used to create a response. + /// A function used to generate error response. + private object OnActionExecuted( + object responseValue, + IQueryable singleResultCollection, + IWebApiActionDescriptor actionDescriptor, + IWebApiRequestMessage request, + Func modelFunction, + Func createQueryOptionFunction, + Action createResponseAction, + Action createErrorAction) + { + if (!_querySettings.PageSize.HasValue && responseValue != null) + { + GetModelBoundPageSize(responseValue, singleResultCollection, actionDescriptor, modelFunction, request.Context.Path, createErrorAction); + } + + // Apply the query if there are any query options, if there is a page size set, in the case of + // SingleResult or in the case of $count request. + bool shouldApplyQuery = responseValue != null && + request.RequestUri != null && + (!String.IsNullOrWhiteSpace(request.RequestUri.Query) || + _querySettings.PageSize.HasValue || + _querySettings.ModelBoundPageSize.HasValue || + singleResultCollection != null || + request.IsCountRequest() || + ContainsAutoSelectExpandProperty(responseValue, singleResultCollection, actionDescriptor, modelFunction, request.Context.Path)); + + object returnValue = null; + if (shouldApplyQuery) + { + try + { + object queryResult = ExecuteQuery(responseValue, singleResultCollection, actionDescriptor, modelFunction, request, createQueryOptionFunction); + if (queryResult == null && (request.Context.Path == null || singleResultCollection != null)) + { + // This is the case in which a regular OData service uses the EnableQuery attribute. + // For OData services ODataNullValueMessageHandler should be plugged in for the service + // if this behavior is desired. + // For non OData services this behavior is equivalent as the one in the v3 version in order + // to reduce the friction when they decide to move to use the v4 EnableQueryAttribute. + createResponseAction(HttpStatusCode.NotFound); + } + + returnValue = queryResult; + } + catch (ArgumentOutOfRangeException e) + { + createErrorAction( + HttpStatusCode.BadRequest, + Error.Format(SRResources.QueryParameterNotSupported, e.Message), + e); + } + catch (NotImplementedException e) + { + createErrorAction( + HttpStatusCode.BadRequest, + Error.Format(SRResources.UriQueryStringInvalid, e.Message), + e); + } + catch (NotSupportedException e) + { + createErrorAction( + HttpStatusCode.BadRequest, + Error.Format(SRResources.UriQueryStringInvalid, e.Message), + e); + } + catch (InvalidOperationException e) + { + // Will also catch ODataException here because ODataException derives from InvalidOperationException. + createErrorAction( + HttpStatusCode.BadRequest, + Error.Format(SRResources.UriQueryStringInvalid, e.Message), + e); + } + } + + return returnValue; + } + + /// + /// Applies the query to the given IQueryable based on incoming query from uri and query settings. By default, + /// the implementation supports $top, $skip, $orderby and $filter. Override this method to perform additional + /// query composition of the query. + /// + /// The original queryable instance from the response message. + /// + /// The instance constructed based on the incoming request. + /// + public virtual IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions) + { + if (queryable == null) + { + throw Error.ArgumentNull("queryable"); + } + if (queryOptions == null) + { + throw Error.ArgumentNull("queryOptions"); + } + + return queryOptions.ApplyTo(queryable, _querySettings); + } + + /// + /// Applies the query to the given entity based on incoming query from uri and query settings. + /// + /// The original entity from the response message. + /// + /// The instance constructed based on the incoming request. + /// + /// The new entity after the $select and $expand query has been applied to. + public virtual object ApplyQuery(object entity, ODataQueryOptions queryOptions) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + if (queryOptions == null) + { + throw Error.ArgumentNull("queryOptions"); + } + + return queryOptions.ApplyTo(entity, _querySettings); + } + + /// + /// Get the ODaya query context. + /// + /// The response value. + /// The content as SingleResult.Queryable. + /// The action context, i.e. action and controller name. + /// A function to get the model. + /// The OData path. + /// + private static ODataQueryContext GetODataQueryContext( + object responseValue, + IQueryable singleResultCollection, + IWebApiActionDescriptor actionDescriptor, + Func modelFunction, + ODataPath path) + { + Type elementClrType = GetElementType(responseValue, singleResultCollection, actionDescriptor); + + IEdmModel model = modelFunction(elementClrType); + if (model == null) + { + throw Error.InvalidOperation(SRResources.QueryGetModelMustNotReturnNull); + } + + return new ODataQueryContext(model, elementClrType, path); + } + + /// + /// Get the page size. + /// + /// The response value. + /// The content as SingleResult.Queryable. + /// The action context, i.e. action and controller name. + /// A function to get the model. + /// The OData path. + /// A function used to generate error response. + private void GetModelBoundPageSize( + object responseValue, + IQueryable singleResultCollection, + IWebApiActionDescriptor actionDescriptor, + Func modelFunction, + ODataPath path, + Action createErrorAction) + { + ODataQueryContext queryContext = null; + + try + { + queryContext = GetODataQueryContext(responseValue, singleResultCollection, actionDescriptor, modelFunction, path); + } + catch (InvalidOperationException e) + { + createErrorAction( + HttpStatusCode.BadRequest, + Error.Format(SRResources.UriQueryStringInvalid, e.Message), + e); + return; + } + + ModelBoundQuerySettings querySettings = EdmLibHelpers.GetModelBoundQuerySettings(queryContext.TargetProperty, + queryContext.TargetStructuredType, + queryContext.Model); + if (querySettings != null && querySettings.PageSize.HasValue) + { + _querySettings.ModelBoundPageSize = querySettings.PageSize; + } + } + + /// + /// Execute the query. + /// + /// The response value. + /// The content as SingleResult.Queryable. + /// The action context, i.e. action and controller name. + /// A function to get the model. + /// The internal request. + /// A function used to create and validate query options. + /// + private object ExecuteQuery( + object responseValue, + IQueryable singleResultCollection, + IWebApiActionDescriptor actionDescriptor, + Func modelFunction, + IWebApiRequestMessage request, + Func createQueryOptionFunction) + { + ODataQueryContext queryContext = GetODataQueryContext(responseValue, singleResultCollection, actionDescriptor, modelFunction, request.Context.Path); + + // Create and validate the query options. + ODataQueryOptions queryOptions = createQueryOptionFunction(queryContext); + + // apply the query + IEnumerable enumerable = responseValue as IEnumerable; + if (enumerable == null || responseValue is string || responseValue is byte[]) + { + // response is not a collection; we only support $select and $expand on single entities. + ValidateSelectExpandOnly(queryOptions); + + if (singleResultCollection == null) + { + // response is a single entity. + return ApplyQuery(entity: responseValue, queryOptions: queryOptions); + } + else + { + IQueryable queryable = singleResultCollection as IQueryable; + queryable = ApplyQuery(queryable, queryOptions); + return SingleOrDefault(queryable, actionDescriptor); + } + } + else + { + // response is a collection. + IQueryable queryable = (enumerable as IQueryable) ?? enumerable.AsQueryable(); + queryable = ApplyQuery(queryable, queryOptions); + + if (request.IsCountRequest()) + { + long? count = request.Context.TotalCount; + + if (count.HasValue) + { + // Return the count value if it is a $count request. + return count.Value; + } + } + + return queryable; + } + } + + /// + /// Get the element type. + /// + /// The response value. + /// The content as SingleResult.Queryable. + /// The action context, i.e. action and controller name. + /// + internal static Type GetElementType( + object responseValue, + IQueryable singleResultCollection, + IWebApiActionDescriptor actionDescriptor) + { + Contract.Assert(responseValue != null); + + IEnumerable enumerable = responseValue as IEnumerable; + if (enumerable == null) + { + if (singleResultCollection == null) + { + return responseValue.GetType(); + } + + enumerable = singleResultCollection as IEnumerable; + } + + Type elementClrType = TypeHelper.GetImplementedIEnumerableType(enumerable.GetType()); + if (elementClrType == null) + { + // The element type cannot be determined because the type of the content + // is not IEnumerable or IQueryable. + throw Error.InvalidOperation( + SRResources.FailedToRetrieveTypeToBuildEdmModel, + typeof(EnableQueryAttribute).Name, + actionDescriptor.ActionName, + actionDescriptor.ControllerName, + responseValue.GetType().FullName); + } + + return elementClrType; + } + + /// + /// Get a single or default value from a collection. + /// + /// The response value as . + /// The action context, i.e. action and controller name. + /// + internal static object SingleOrDefault( + IQueryable queryable, + IWebApiActionDescriptor actionDescriptor) + { + var enumerator = queryable.GetEnumerator(); + try + { + var result = enumerator.MoveNext() ? enumerator.Current : null; + + if (enumerator.MoveNext()) + { + throw new InvalidOperationException(Error.Format( + SRResources.SingleResultHasMoreThanOneEntity, + actionDescriptor.ActionName, + actionDescriptor.ControllerName, + "SingleResult")); + } + + return result; + } + finally + { + // Ensure any active/open database objects that were created + // iterating over the IQueryable object are properly closed. + var disposable = enumerator as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + } + } + + /// + /// Validate the select and expand options. + /// + /// The query options. + internal static void ValidateSelectExpandOnly(ODataQueryOptions queryOptions) + { + if (queryOptions.Filter != null || queryOptions.Count != null || queryOptions.OrderBy != null + || queryOptions.Skip != null || queryOptions.Top != null) + { + throw new ODataException(Error.Format(SRResources.NonSelectExpandOnSingleEntity)); + } + } + + /// + /// Determine if the + /// + /// The response value. + /// The content as SingleResult.Queryable. + /// The action context, i.e. action and controller name. + /// A function to get the model. + /// The OData path. + /// + private static bool ContainsAutoSelectExpandProperty( + object responseValue, + IQueryable singleResultCollection, + IWebApiActionDescriptor actionDescriptor, + Func modelFunction, + ODataPath path) + { + Type elementClrType = GetElementType(responseValue, singleResultCollection, actionDescriptor); + + IEdmModel model = modelFunction(elementClrType); + if (model == null) + { + throw Error.InvalidOperation(SRResources.QueryGetModelMustNotReturnNull); + } + + IEdmEntityType baseEntityType = model.GetEdmType(elementClrType) as IEdmEntityType; + IEdmStructuredType structuredType = model.GetEdmType(elementClrType) as IEdmStructuredType; + IEdmProperty property = null; + if (path != null) + { + string name; + EdmLibHelpers.GetPropertyAndStructuredTypeFromPath(path.Segments, out property, + out structuredType, + out name); + } + + if (baseEntityType != null) + { + List entityTypes = new List(); + entityTypes.Add(baseEntityType); + entityTypes.AddRange(EdmLibHelpers.GetAllDerivedEntityTypes(baseEntityType, model)); + foreach (var entityType in entityTypes) + { + IEnumerable navigationProperties = entityType == baseEntityType + ? entityType.NavigationProperties() + : entityType.DeclaredNavigationProperties(); + if (navigationProperties != null) + { + if (navigationProperties.Any( + navigationProperty => + EdmLibHelpers.IsAutoExpand(navigationProperty, property, entityType, model))) + { + return true; + } + } + + IEnumerable properties = entityType == baseEntityType + ? entityType.StructuralProperties() + : entityType.DeclaredStructuralProperties(); + if (properties != null) + { + foreach (var edmProperty in properties) + { + if (EdmLibHelpers.IsAutoSelect(edmProperty, property, entityType, model)) + { + return true; + } + } + } + } + } + else if (structuredType != null) + { + IEnumerable properties = structuredType.StructuralProperties(); + if (properties != null) + { + foreach (var edmProperty in properties) + { + if (EdmLibHelpers.IsAutoSelect(edmProperty, property, structuredType, model)) + { + return true; + } + } + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelperMethods.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelperMethods.cs new file mode 100644 index 0000000..20b39fd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelperMethods.cs @@ -0,0 +1,426 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.AspNet.OData +{ + internal class ExpressionHelperMethods + { + private static MethodInfo _enumerableWhereMethod = GenericMethodOf(_ => Enumerable.Where(default(IEnumerable), default(Func))); + private static MethodInfo _queryableToListMethod = GenericMethodOf(_ => Enumerable.ToList(default(IEnumerable))); + private static MethodInfo _orderByMethod = GenericMethodOf(_ => Queryable.OrderBy(default(IQueryable), default(Expression>))); + private static MethodInfo _enumerableOrderByMethod = GenericMethodOf(_ => Enumerable.OrderBy(default(IEnumerable), default(Func))); + private static MethodInfo _orderByDescendingMethod = GenericMethodOf(_ => Queryable.OrderByDescending(default(IQueryable), default(Expression>))); + private static MethodInfo _enumerableOrderByDescendingMethod = GenericMethodOf(_ => Enumerable.OrderByDescending(default(IEnumerable), default(Func))); + private static MethodInfo _thenByMethod = GenericMethodOf(_ => Queryable.ThenBy(default(IOrderedQueryable), default(Expression>))); + private static MethodInfo _enumerableThenByMethod = GenericMethodOf(_ => Enumerable.ThenBy(default(IOrderedEnumerable), default(Func))); + private static MethodInfo _thenByDescendingMethod = GenericMethodOf(_ => Queryable.ThenByDescending(default(IOrderedQueryable), default(Expression>))); + private static MethodInfo _enumerableThenByDescendingMethod = GenericMethodOf(_ => Enumerable.ThenByDescending(default(IOrderedEnumerable), default(Func))); + private static MethodInfo _countMethod = GenericMethodOf(_ => Queryable.LongCount(default(IQueryable))); + private static MethodInfo _enumerableGroupByMethod = GenericMethodOf(_ => Enumerable.GroupBy(default(IQueryable), default(Func))); + private static MethodInfo _groupByMethod = GenericMethodOf(_ => Queryable.GroupBy(default(IQueryable), default(Expression>))); + private static MethodInfo _aggregateMethod = GenericMethodOf(_ => Queryable.Aggregate(default(IQueryable), default(int), default(Expression>))); + private static MethodInfo _skipMethod = GenericMethodOf(_ => Queryable.Skip(default(IQueryable), default(int))); + private static MethodInfo _enumerableSkipMethod = GenericMethodOf(_ => Enumerable.Skip(default(IEnumerable), default(int))); + private static MethodInfo _whereMethod = GenericMethodOf(_ => Queryable.Where(default(IQueryable), default(Expression>))); + + private static MethodInfo _queryableContainsMethod = GenericMethodOf(_ => Queryable.Contains(default(IQueryable), default(int))); + private static MethodInfo _enumerableContainsMethod = GenericMethodOf(_ => Enumerable.Contains(default(IEnumerable), default(int))); + + private static MethodInfo _queryableEmptyAnyMethod = GenericMethodOf(_ => Queryable.Any(default(IQueryable))); + private static MethodInfo _queryableNonEmptyAnyMethod = GenericMethodOf(_ => Queryable.Any(default(IQueryable), default(Expression>))); + private static MethodInfo _queryableAllMethod = GenericMethodOf(_ => Queryable.All(default(IQueryable), default(Expression>))); + + private static MethodInfo _enumerableEmptyAnyMethod = GenericMethodOf(_ => Enumerable.Any(default(IEnumerable))); + private static MethodInfo _enumerableNonEmptyAnyMethod = GenericMethodOf(_ => Enumerable.Any(default(IEnumerable), default(Func))); + private static MethodInfo _enumerableAllMethod = GenericMethodOf(_ => Enumerable.All(default(IEnumerable), default(Func))); + + private static MethodInfo _enumerableOfTypeMethod = GenericMethodOf(_ => Enumerable.OfType(default(IEnumerable))); + private static MethodInfo _queryableOfTypeMethod = GenericMethodOf(_ => Queryable.OfType(default(IQueryable))); + + private static MethodInfo _enumerableSelectManyMethod = GenericMethodOf(_ => Enumerable.SelectMany(default(IEnumerable), default(Func>))); + private static MethodInfo _queryableSelectManyMethod = GenericMethodOf(_ => Queryable.SelectMany(default(IQueryable), default(Expression>>))); + + private static MethodInfo _enumerableSelectMethod = GenericMethodOf(_ => Enumerable.Select(default(IEnumerable), i => i)); + private static MethodInfo _queryableSelectMethod = GenericMethodOf(_ => Queryable.Select(default(IQueryable), i => i)); + + private static MethodInfo _queryableTakeMethod = GenericMethodOf(_ => Queryable.Take(default(IQueryable), default(int))); + private static MethodInfo _enumerableTakeMethod = GenericMethodOf(_ => Enumerable.Take(default(IEnumerable), default(int))); + + private static MethodInfo _queryableAsQueryableMethod = GenericMethodOf(_ => Queryable.AsQueryable(default(IEnumerable))); + + private static MethodInfo _toQueryableMethod = GenericMethodOf(_ => ExpressionHelperMethods.ToQueryable(default(int))); + + private static Dictionary _queryableSumMethods = GetQueryableAggregationMethods("Sum"); + private static Dictionary _enumerableSumMethods = GetEnumerableAggregationMethods("Sum"); + + private static MethodInfo _enumerableMinMethod = GenericMethodOf(_ => Enumerable.Min(default(IQueryable), default(Func))); + private static MethodInfo _enumerableMaxMethod = GenericMethodOf(_ => Enumerable.Max(default(IQueryable), default(Func))); + + private static MethodInfo _enumerableDistinctMethod = GenericMethodOf(_ => Enumerable.Distinct(default(IEnumerable))); + + private static MethodInfo _queryableMinMethod = GenericMethodOf(_ => Queryable.Min(default(IQueryable), default(Expression>))); + private static MethodInfo _queryableMaxMethod = GenericMethodOf(_ => Queryable.Max(default(IQueryable), default(Expression>))); + + private static MethodInfo _queryableDistinctMethod = GenericMethodOf(_ => Queryable.Distinct(default(IQueryable))); + + private static MethodInfo _createQueryGenericMethod = GetCreateQueryGenericMethod(); + + //Unlike the Sum method, the return types are not unique and do not match the input type of the expression. + //Inspecting the 2nd parameters expression's function's 2nd argument is too specific for the GetQueryableAggregationMethods + private static Dictionary _enumerableAverageMethods = new Dictionary() + { + { typeof(int), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(int?), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(long), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(long?), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(float), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(float?), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(decimal), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(decimal?), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(double), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + { typeof(double?), GenericMethodOf(_ => Enumerable.Average(default(IEnumerable), default(Func))) }, + }; + + private static Dictionary _queryableAverageMethods = new Dictionary() + { + { typeof(int), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(int?), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(long), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(long?), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(float), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(float?), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(decimal), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(decimal?), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(double), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + { typeof(double?), GenericMethodOf(_ => Queryable.Average(default(IQueryable), default(Expression>))) }, + }; + + private static MethodInfo _enumerableCountMethod = GenericMethodOf(_ => Enumerable.LongCount(default(IEnumerable))); + + private static MethodInfo _safeConvertToDecimalMethod = typeof(ExpressionHelperMethods).GetMethod("SafeConvertToDecimal"); + + public static MethodInfo EnumerableWhereGeneric + { + get { return _enumerableWhereMethod; } + } + + public static MethodInfo QueryableToList + { + get { return _queryableToListMethod; } + } + + public static MethodInfo QueryableOrderByGeneric + { + get { return _orderByMethod; } + } + + public static MethodInfo EnumerableOrderByGeneric + { + get { return _enumerableOrderByMethod; } + } + + public static MethodInfo QueryableOrderByDescendingGeneric + { + get { return _orderByDescendingMethod; } + } + + public static MethodInfo EnumerableOrderByDescendingGeneric + { + get { return _enumerableOrderByDescendingMethod; } + } + + public static MethodInfo QueryableThenByGeneric + { + get { return _thenByMethod; } + } + + public static MethodInfo EnumerableThenByGeneric + { + get { return _enumerableThenByMethod; } + } + + public static MethodInfo QueryableThenByDescendingGeneric + { + get { return _thenByDescendingMethod; } + } + + public static MethodInfo EnumerableThenByDescendingGeneric + { + get { return _enumerableThenByDescendingMethod; } + } + + public static MethodInfo QueryableCountGeneric + { + get { return _countMethod; } + } + + public static Dictionary QueryableSumGenerics + { + get { return _queryableSumMethods; } + } + + public static Dictionary EnumerableSumGenerics + { + get { return _enumerableSumMethods; } + } + + public static MethodInfo QueryableMin + { + get { return _queryableMinMethod; } + } + + public static MethodInfo EnumerableMin + { + get { return _enumerableMinMethod; } + } + + public static MethodInfo QueryableMax + { + get { return _queryableMaxMethod; } + } + + public static MethodInfo EnumerableMax + { + get { return _enumerableMaxMethod; } + } + + public static Dictionary QueryableAverageGenerics + { + get { return _queryableAverageMethods; } + } + + public static Dictionary EnumerableAverageGenerics + { + get { return _enumerableAverageMethods; } + } + + public static MethodInfo QueryableDistinct + { + get { return _queryableDistinctMethod; } + } + + public static MethodInfo EnumerableDistinct + { + get { return _enumerableDistinctMethod; } + } + + public static MethodInfo QueryableGroupByGeneric + { + get { return _groupByMethod; } + } + + public static MethodInfo EnumerableGroupByGeneric + { + get { return _enumerableGroupByMethod; } + } + + public static MethodInfo QueryableAggregateGeneric + { + get { return _aggregateMethod; } + } + + public static MethodInfo QueryableTakeGeneric + { + get { return _queryableTakeMethod; } + } + + public static MethodInfo EnumerableTakeGeneric + { + get { return _enumerableTakeMethod; } + } + + public static MethodInfo QueryableSkipGeneric + { + get { return _skipMethod; } + } + + public static MethodInfo EnumerableSkipGeneric + { + get { return _enumerableSkipMethod; } + } + + public static MethodInfo QueryableWhereGeneric + { + get { return _whereMethod; } + } + + public static MethodInfo QueryableContainsGeneric + { + get { return _queryableContainsMethod; } + } + + public static MethodInfo EnumerableContainsGeneric + { + get { return _enumerableContainsMethod; } + } + + public static MethodInfo QueryableSelectGeneric + { + get { return _queryableSelectMethod; } + } + + public static MethodInfo EnumerableSelectGeneric + { + get { return _enumerableSelectMethod; } + } + + public static MethodInfo QueryableSelectManyGeneric + { + get { return _queryableSelectManyMethod; } + } + + public static MethodInfo EnumerableSelectManyGeneric + { + get { return _enumerableSelectManyMethod; } + } + + public static MethodInfo QueryableEmptyAnyGeneric + { + get { return _queryableEmptyAnyMethod; } + } + + public static MethodInfo QueryableNonEmptyAnyGeneric + { + get { return _queryableNonEmptyAnyMethod; } + } + + public static MethodInfo QueryableAllGeneric + { + get { return _queryableAllMethod; } + } + + public static MethodInfo EnumerableEmptyAnyGeneric + { + get { return _enumerableEmptyAnyMethod; } + } + + public static MethodInfo EnumerableNonEmptyAnyGeneric + { + get { return _enumerableNonEmptyAnyMethod; } + } + + public static MethodInfo EnumerableAllGeneric + { + get { return _enumerableAllMethod; } + } + + public static MethodInfo EnumerableOfType + { + get { return _enumerableOfTypeMethod; } + } + + public static MethodInfo QueryableOfType + { + get { return _queryableOfTypeMethod; } + } + + public static MethodInfo QueryableAsQueryable + { + get { return _queryableAsQueryableMethod; } + } + + public static MethodInfo EntityAsQueryable + { + get { return _toQueryableMethod; } + } + + public static IQueryable ToQueryable(T value) + { + return (new List { value }).AsQueryable(); + } + + public static MethodInfo EnumerableCountGeneric + { + get { return _enumerableCountMethod; } + } + + public static MethodInfo ConvertToDecimal + { + get { return _safeConvertToDecimalMethod; } + } + + public static MethodInfo CreateQueryGeneric + { + get { return _createQueryGenericMethod; } + } + + public static decimal? SafeConvertToDecimal(object value) + { + if (value == null || value == DBNull.Value) + { + return null; + } + + Type type = value.GetType(); + type = Nullable.GetUnderlyingType(type) ?? type; + if (type == typeof(short) || + type == typeof(int) || + type == typeof(long) || + type == typeof(decimal) || + type == typeof(double) || + type == typeof(float)) + { + return (decimal?)Convert.ChangeType(value, typeof(decimal), CultureInfo.InvariantCulture); + } + + return null; + } + + private static MethodInfo GenericMethodOf(Expression> expression) + { + return GenericMethodOf(expression as Expression); + } + + private static MethodInfo GenericMethodOf(Expression expression) + { + LambdaExpression lambdaExpression = expression as LambdaExpression; + + Contract.Assert(expression.NodeType == ExpressionType.Lambda); + Contract.Assert(lambdaExpression != null); + Contract.Assert(lambdaExpression.Body.NodeType == ExpressionType.Call); + + return (lambdaExpression.Body as MethodCallExpression).Method.GetGenericMethodDefinition(); + } + + private static Dictionary GetQueryableAggregationMethods(string methodName) + { + //Sum to not have generic by property method return type so have to generate a table + // Looking for methods like + // Queryable.Sum(default(IQueryable), default(Expression>))) + + return typeof(Queryable).GetMethods() + .Where(m => m.Name == methodName) + .Where(m => m.GetParameters().Count() == 2) + .ToDictionary(m => m.ReturnType); + } + + private static Dictionary GetEnumerableAggregationMethods(string methodName) + { + //Sum to not have generic by property method return type so have to generate a table + // Looking for methods like + // Queryable.Sum(default(IQueryable), default(Expression>))) + + return typeof(Enumerable).GetMethods() + .Where(m => m.Name == methodName) + .Where(m => m.GetParameters().Count() == 2) + .ToDictionary(m => m.ReturnType); + } + + private static MethodInfo GetCreateQueryGenericMethod() + { + return typeof(IQueryProvider).GetTypeInfo() + .GetDeclaredMethods("CreateQuery") + .Where(m => m.IsGenericMethod) + .FirstOrDefault(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelpers.cs new file mode 100644 index 0000000..72f8998 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ExpressionHelpers.cs @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData +{ + internal static class ExpressionHelpers + { + public static Func Count(IQueryable query, Type type) + { + MethodInfo countMethod = ExpressionHelperMethods.QueryableCountGeneric.MakeGenericMethod(type); + Func func = () => (long)countMethod.Invoke(null, new object[] { query }); + return func; + } + + public static IQueryable Skip(IQueryable query, int count, Type type, bool parameterize) + { + MethodInfo skipMethod = ExpressionHelperMethods.QueryableSkipGeneric.MakeGenericMethod(type); + Expression skipValueExpression = parameterize ? LinqParameterContainer.Parameterize(typeof(int), count) : Expression.Constant(count); + + Expression skipQuery = Expression.Call(null, skipMethod, new[] { query.Expression, skipValueExpression }); + + var createMethod = ExpressionHelperMethods.CreateQueryGeneric.MakeGenericMethod(type); + + return createMethod.Invoke(query.Provider, new[] { skipQuery }) as IQueryable; + } + + public static IQueryable Take(IQueryable query, int count, Type type, bool parameterize) + { + Expression takeQuery = Take(query.Expression, count, type, parameterize); + var createMethod = ExpressionHelperMethods.CreateQueryGeneric.MakeGenericMethod(type); + + return createMethod.Invoke(query.Provider, new[] { takeQuery }) as IQueryable; + } + + public static Expression Skip(Expression source, int count, Type type, bool parameterize) + { + MethodInfo skipMethod; + if (typeof(IQueryable).IsAssignableFrom(source.Type)) + { + skipMethod = ExpressionHelperMethods.QueryableSkipGeneric.MakeGenericMethod(type); + } + else + { + skipMethod = ExpressionHelperMethods.EnumerableSkipGeneric.MakeGenericMethod(type); + } + + Expression skipValueExpression = parameterize ? LinqParameterContainer.Parameterize(typeof(int), count) : Expression.Constant(count); + Expression skipQuery = Expression.Call(null, skipMethod, new[] { source, skipValueExpression }); + return skipQuery; + } + + public static Expression Take(Expression source, int count, Type elementType, bool parameterize) + { + MethodInfo takeMethod; + if (typeof(IQueryable).IsAssignableFrom(source.Type)) + { + takeMethod = ExpressionHelperMethods.QueryableTakeGeneric.MakeGenericMethod(elementType); + } + else + { + takeMethod = ExpressionHelperMethods.EnumerableTakeGeneric.MakeGenericMethod(elementType); + } + + Expression takeValueExpression = parameterize ? LinqParameterContainer.Parameterize(typeof(int), count) : Expression.Constant(count); + Expression takeQuery = Expression.Call(null, takeMethod, new[] { source, takeValueExpression }); + return takeQuery; + } + + public static Expression OrderByPropertyExpression( + Expression source, + string propertyName, + Type elementType, + bool alreadyOrdered = false) + { + LambdaExpression orderByLambda = GetPropertyAccessLambda(elementType, propertyName); + return OrderBy(source, orderByLambda, elementType, OrderByDirection.Ascending, alreadyOrdered); + } + + public static Expression OrderBy( + Expression source, + LambdaExpression orderByLambda, + Type elementType, + OrderByDirection direction, + bool alreadyOrdered = false) + { + Type returnType = orderByLambda.Body.Type; + MethodInfo orderByMethod; + if (!alreadyOrdered) + { + if (typeof(IQueryable).IsAssignableFrom(source.Type)) + { + if (direction == OrderByDirection.Ascending) + { + orderByMethod = ExpressionHelperMethods.QueryableOrderByGeneric.MakeGenericMethod(elementType, + returnType); + } + else + { + orderByMethod = ExpressionHelperMethods.QueryableOrderByDescendingGeneric.MakeGenericMethod(elementType, + returnType); + } + } + else + { + if (direction == OrderByDirection.Ascending) + { + orderByMethod = ExpressionHelperMethods.EnumerableOrderByGeneric.MakeGenericMethod(elementType, + returnType); + } + else + { + orderByMethod = ExpressionHelperMethods.EnumerableOrderByDescendingGeneric.MakeGenericMethod(elementType, + returnType); + } + } + } + else + { + if (typeof(IQueryable).IsAssignableFrom(source.Type)) + { + if (direction == OrderByDirection.Ascending) + { + orderByMethod = ExpressionHelperMethods.QueryableThenByGeneric.MakeGenericMethod(elementType, + returnType); + } + else + { + orderByMethod = ExpressionHelperMethods.QueryableThenByDescendingGeneric.MakeGenericMethod(elementType, + returnType); + } + } + else + { + if (direction == OrderByDirection.Ascending) + { + orderByMethod = ExpressionHelperMethods.EnumerableThenByGeneric.MakeGenericMethod(elementType, + returnType); + } + else + { + orderByMethod = ExpressionHelperMethods.EnumerableThenByDescendingGeneric.MakeGenericMethod(elementType, + returnType); + } + } + } + return Expression.Call(null, orderByMethod, new[] { source, orderByLambda }); + } + + public static IQueryable OrderByIt(IQueryable query, OrderByDirection direction, Type type, bool alreadyOrdered = false) + { + ParameterExpression odataItParameter = Expression.Parameter(type, "$it"); + LambdaExpression orderByLambda = Expression.Lambda(odataItParameter, odataItParameter); + return OrderBy(query, orderByLambda, direction, type, alreadyOrdered); + } + + public static IQueryable OrderByProperty(IQueryable query, IEdmModel model, IEdmProperty property, OrderByDirection direction, Type type, bool alreadyOrdered = false) + { + // property aliasing + string propertyName = EdmLibHelpers.GetClrPropertyName(property, model); + LambdaExpression orderByLambda = GetPropertyAccessLambda(type, propertyName); + return OrderBy(query, orderByLambda, direction, type, alreadyOrdered); + } + + public static IQueryable OrderBy(IQueryable query, LambdaExpression orderByLambda, OrderByDirection direction, Type type, bool alreadyOrdered = false) + { + Type returnType = orderByLambda.Body.Type; + + MethodInfo orderByMethod = null; + IOrderedQueryable orderedQuery = null; + + // unfortunately unordered L2O.AsQueryable implements IOrderedQueryable + // so we can't try casting to IOrderedQueryable to provide a clue to whether + // we should be calling ThenBy or ThenByDescending + if (alreadyOrdered) + { + if (direction == OrderByDirection.Ascending) + { + orderByMethod = ExpressionHelperMethods.QueryableThenByGeneric.MakeGenericMethod(type, returnType); + } + else + { + orderByMethod = ExpressionHelperMethods.QueryableThenByDescendingGeneric.MakeGenericMethod(type, returnType); + } + + orderedQuery = query as IOrderedQueryable; + orderedQuery = orderByMethod.Invoke(null, new object[] { orderedQuery, orderByLambda }) as IOrderedQueryable; + } + else + { + if (direction == OrderByDirection.Ascending) + { + orderByMethod = ExpressionHelperMethods.QueryableOrderByGeneric.MakeGenericMethod(type, returnType); + } + else + { + orderByMethod = ExpressionHelperMethods.QueryableOrderByDescendingGeneric.MakeGenericMethod(type, returnType); + } + + orderedQuery = orderByMethod.Invoke(null, new object[] { query, orderByLambda }) as IOrderedQueryable; + } + + return orderedQuery; + } + + public static IQueryable GroupBy(IQueryable query, Expression expression, Type type, Type wrapperType) + { + MethodInfo groupByMethod = ExpressionHelperMethods.QueryableGroupByGeneric.MakeGenericMethod(type, wrapperType); + return groupByMethod.Invoke(null, new object[] { query, expression }) as IQueryable; + } + + public static IQueryable Select(IQueryable query, LambdaExpression expression, Type type) + { + MethodInfo selectMethod = ExpressionHelperMethods.QueryableSelectGeneric.MakeGenericMethod(type, expression.Body.Type); + return selectMethod.Invoke(null, new object[] { query, expression }) as IQueryable; + } + + public static IQueryable SelectMany(IQueryable query, LambdaExpression expression, Type type) + { + MethodInfo selectManyMethod = ExpressionHelperMethods.QueryableSelectManyGeneric.MakeGenericMethod(type, expression.Body.Type); + return selectManyMethod.Invoke(null, new object[] { query, expression }) as IQueryable; + } + + public static IQueryable Aggregate(IQueryable query, object init, LambdaExpression sumLambda, Type type, Type wrapperType) + { + Type returnType = sumLambda.Body.Type; + MethodInfo sumMethod = ExpressionHelperMethods.QueryableAggregateGeneric.MakeGenericMethod(type, returnType); + var agg = sumMethod.Invoke(null, new object[] { query, init, sumLambda }); + + MethodInfo converterMethod = ExpressionHelperMethods.EntityAsQueryable.MakeGenericMethod(wrapperType); + + return converterMethod.Invoke(null, new object[] { agg }) as IQueryable; + } + + public static IQueryable Where(IQueryable query, Expression where, Type type) + { + MethodInfo whereMethod = ExpressionHelperMethods.QueryableWhereGeneric.MakeGenericMethod(type); + return whereMethod.Invoke(null, new object[] { query, where }) as IQueryable; + } + + // If the expression is not a nullable type, cast it to one. + public static Expression ToNullable(Expression expression) + { + if (!TypeHelper.IsNullable(expression.Type)) + { + return Expression.Convert(expression, TypeHelper.ToNullable(expression.Type)); + } + + return expression; + } + + // Entity Framework does not understand default(T) expression. Hence, generate a constant expression with the default value. + public static Expression Default(Type type) + { + if (TypeHelper.IsValueType(type)) + { + return Expression.Constant(Activator.CreateInstance(type), type); + } + else + { + return Expression.Constant(null, type); + } + } + + public static LambdaExpression GetPropertyAccessLambda(Type type, string propertyName) + { + ParameterExpression odataItParameter = Expression.Parameter(type, "$it"); + MemberExpression propertyAccess = Expression.Property(odataItParameter, propertyName); + return Expression.Lambda(propertyAccess, odataItParameter); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs new file mode 100644 index 0000000..7214791 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using ServiceLifetime = Microsoft.OData.ServiceLifetime; + +namespace Microsoft.AspNet.OData.Extensions +{ + internal static class ContainerBuilderExtensions + { + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "DI services")] + public static IContainerBuilder AddDefaultWebApiServices(this IContainerBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + builder.AddService(ServiceLifetime.Singleton); + + // ReaderSettings and WriterSettings are registered as prototype services. + // There will be a copy (if it is accessed) of each prototype for each request. + builder.AddServicePrototype(new ODataMessageReaderSettings + { + EnableMessageStreamDisposal = false, + MessageQuotas = new ODataMessageQuotas { MaxReceivedMessageSize = Int64.MaxValue }, + }); + builder.AddServicePrototype(new ODataMessageWriterSettings + { + EnableMessageStreamDisposal = false, + MessageQuotas = new ODataMessageQuotas { MaxReceivedMessageSize = Int64.MaxValue }, + }); + + // QueryValidators. + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Scoped); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + + builder.AddService(ServiceLifetime.Singleton); + + // SerializerProvider and DeserializerProvider. + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + + // Deserializers. + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + + // Serializers. + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + + // Binders. + builder.AddService(ServiceLifetime.Scoped); + builder.AddService(ServiceLifetime.Transient); + + // HttpRequestScope. + builder.AddService(ServiceLifetime.Scoped); + builder.AddService(ServiceLifetime.Scoped, sp => sp.GetRequiredService().HttpRequest); + return builder; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FastPropertyAccessor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FastPropertyAccessor.cs new file mode 100644 index 0000000..01791e3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FastPropertyAccessor.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter.Deserialization; + +namespace Microsoft.AspNet.OData +{ + /// + /// FastPropertyAccessor is a that speeds up (compares to reflection) + /// a Getter and Setter for the PropertyInfo of TEntityType provided via the constructor. + /// + /// The type on which the PropertyInfo exists + internal class FastPropertyAccessor : PropertyAccessor where TStructuralType : class + { + private bool _isCollection; + private PropertyInfo _property; + private Action _setter; + private Func _getter; + + public FastPropertyAccessor(PropertyInfo property) + : base(property) + { + _property = property; + _isCollection = TypeHelper.IsCollection(property.PropertyType); + + if (!_isCollection) + { + _setter = PropertyHelper.MakeFastPropertySetter(property); + } + _getter = PropertyHelper.MakeFastPropertyGetter(property); + } + + public override object GetValue(TStructuralType instance) + { + if (instance == null) + { + throw Error.ArgumentNull("instance"); + } + return _getter(instance); + } + + public override void SetValue(TStructuralType instance, object value) + { + if (instance == null) + { + throw Error.ArgumentNull("instance"); + } + + if (_isCollection) + { + DeserializationHelpers.SetCollectionProperty(instance, _property.Name, edmPropertyType: null, + value: value, clearCollection: true); + } + else + { + _setter(instance, value); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ClrTypeCache.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ClrTypeCache.cs new file mode 100644 index 0000000..62810f6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ClrTypeCache.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal class ClrTypeCache + { + private ConcurrentDictionary _cache = + new ConcurrentDictionary(); + + public IEdmTypeReference GetEdmType(Type clrType, IEdmModel model) + { + IEdmTypeReference edmType; + if (!_cache.TryGetValue(clrType, out edmType)) + { + edmType = model.GetEdmTypeReference(clrType); + _cache[clrType] = edmType; + } + + return edmType; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/DefaultODataETagHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/DefaultODataETagHandler.cs new file mode 100644 index 0000000..9ffc54e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/DefaultODataETagHandler.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net.Http.Headers; +using System.Text; +using Microsoft.AspNet.OData.Builder.Conventions; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal class DefaultODataETagHandler : IETagHandler + { + /// null literal that needs to be return in ETag value when the value is null + private const string NullLiteralInETag = "null"; + + private const char Separator = ','; + + public EntityTagHeaderValue CreateETag(IDictionary properties) + { + if (properties == null) + { + throw Error.ArgumentNull("properties"); + } + + if (properties.Count == 0) + { + return null; + } + + StringBuilder builder = new StringBuilder(); + builder.Append('\"'); + bool firstProperty = true; + + foreach (object propertyValue in properties.Values) + { + if (firstProperty) + { + firstProperty = false; + } + else + { + builder.Append(Separator); + } + + string str = propertyValue == null + ? NullLiteralInETag + : ConventionsHelpers.GetUriRepresentationForValue(propertyValue); + + // base64 encode + byte[] bytes = Encoding.UTF8.GetBytes(str); + string etagValueText = Convert.ToBase64String(bytes); + builder.Append(etagValueText); + } + + builder.Append('\"'); + string tag = builder.ToString(); + return new EntityTagHeaderValue(tag, isWeak: true); + } + + public IDictionary ParseETag(EntityTagHeaderValue etagHeaderValue) + { + if (etagHeaderValue == null) + { + throw Error.ArgumentNull("etagHeaderValue"); + } + + string tag = etagHeaderValue.Tag.Trim('\"'); + + // split etag + string[] rawValues = tag.Split(Separator); + IDictionary properties = new Dictionary(); + for (int index = 0; index < rawValues.Length; index++) + { + string rawValue = rawValues[index]; + + // base64 decode + byte[] bytes = Convert.FromBase64String(rawValue); + string valueString = Encoding.UTF8.GetString(bytes); + object obj = ODataUriUtils.ConvertFromUriLiteral(valueString, ODataVersion.V4); + if (obj is ODataNullValue) + { + obj = null; + } + properties.Add(index.ToString(CultureInfo.InvariantCulture), obj); + } + + return properties; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/CollectionDeserializationHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/CollectionDeserializationHelpers.cs new file mode 100644 index 0000000..59bbccf --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/CollectionDeserializationHelpers.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + internal static class CollectionDeserializationHelpers + { + private static readonly Type[] _emptyTypeArray = new Type[0]; + private static readonly object[] _emptyObjectArray = new object[0]; + private static readonly MethodInfo _toArrayMethodInfo = typeof(Enumerable).GetMethod("ToArray"); + + public static void AddToCollection(this IEnumerable items, IEnumerable collection, Type elementType, + Type resourceType, string propertyName, Type propertyType) + { + Contract.Assert(items != null); + Contract.Assert(collection != null); + Contract.Assert(elementType != null); + Contract.Assert(resourceType != null); + Contract.Assert(propertyName != null); + Contract.Assert(propertyType != null); + + MethodInfo addMethod = null; + IList list = collection as IList; + + if (list == null) + { + addMethod = collection.GetType().GetMethod("Add", new Type[] { elementType }); + if (addMethod == null) + { + string message = Error.Format(SRResources.CollectionShouldHaveAddMethod, propertyType.FullName, propertyName, resourceType.FullName); + throw new SerializationException(message); + } + } + else if (list.GetType().IsArray) + { + string message = Error.Format(SRResources.GetOnlyCollectionCannotBeArray, propertyName, resourceType.FullName); + throw new SerializationException(message); + } + + items.AddToCollectionCore(collection, elementType, list, addMethod); + } + + public static void AddToCollection(this IEnumerable items, IEnumerable collection, Type elementType, string paramName, Type paramType) + { + Contract.Assert(items != null); + Contract.Assert(collection != null); + Contract.Assert(elementType != null); + Contract.Assert(paramType != null); + + MethodInfo addMethod = null; + IList list = collection as IList; + + if (list == null) + { + addMethod = collection.GetType().GetMethod("Add", new Type[] { elementType }); + if (addMethod == null) + { + string message = Error.Format(SRResources.CollectionParameterShouldHaveAddMethod, paramType, paramName); + throw new SerializationException(message); + } + } + + items.AddToCollectionCore(collection, elementType, list, addMethod); + } + + private static void AddToCollectionCore(this IEnumerable items, IEnumerable collection, Type elementType, IList list, MethodInfo addMethod) + { + bool isNonstandardEdmPrimitiveCollection; + EdmLibHelpers.IsNonstandardEdmPrimitive(elementType, out isNonstandardEdmPrimitiveCollection); + + foreach (object item in items) + { + object element = item; + + if (isNonstandardEdmPrimitiveCollection && element != null) + { + // convert non-standard edm primitives if required. + element = EdmPrimitiveHelpers.ConvertPrimitiveValue(element, elementType); + } + + if (list != null) + { + list.Add(element); + } + else + { + Contract.Assert(addMethod != null); + addMethod.Invoke(collection, new object[] { element }); + } + } + } + + public static void Clear(this IEnumerable collection, string propertyName, Type resourceType) + { + Contract.Assert(collection != null); + + MethodInfo clearMethod = collection.GetType().GetMethod("Clear", _emptyTypeArray); + if (clearMethod == null) + { + string message = Error.Format(SRResources.CollectionShouldHaveClearMethod, collection.GetType().FullName, + propertyName, resourceType.FullName); + throw new SerializationException(message); + } + + clearMethod.Invoke(collection, _emptyObjectArray); + } + + public static bool TryCreateInstance(Type collectionType, IEdmCollectionTypeReference edmCollectionType, Type elementType, out IEnumerable instance) + { + Contract.Assert(collectionType != null); + + if (collectionType == typeof(EdmComplexObjectCollection)) + { + instance = new EdmComplexObjectCollection(edmCollectionType); + return true; + } + else if (collectionType == typeof(EdmEntityObjectCollection)) + { + instance = new EdmEntityObjectCollection(edmCollectionType); + return true; + } + else if (collectionType == typeof(EdmEnumObjectCollection)) + { + instance = new EdmEnumObjectCollection(edmCollectionType); + return true; + } + else if (TypeHelper.IsGenericType(collectionType)) + { + Type genericDefinition = collectionType.GetGenericTypeDefinition(); + if (genericDefinition == typeof(IEnumerable<>) || + genericDefinition == typeof(ICollection<>) || + genericDefinition == typeof(IList<>)) + { + instance = Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)) as IEnumerable; + return true; + } + } + + if (collectionType.IsArray) + { + // We dont know the size of the collection in advance. So, create a list and later call ToArray. + instance = Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)) as IEnumerable; + return true; + } + + if (collectionType.GetConstructor(Type.EmptyTypes) != null && !TypeHelper.IsAbstract(collectionType)) + { + instance = Activator.CreateInstance(collectionType) as IEnumerable; + return true; + } + + instance = null; + return false; + } + + public static IEnumerable ToArray(IEnumerable value, Type elementType) + { + return _toArrayMethodInfo.MakeGenericMethod(elementType).Invoke(null, new object[] { value }) as IEnumerable; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DefaultODataDeserializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DefaultODataDeserializerProvider.cs new file mode 100644 index 0000000..cab4a6f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DefaultODataDeserializerProvider.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// The default . + /// + public partial class DefaultODataDeserializerProvider : ODataDeserializerProvider + { + private readonly IServiceProvider _rootContainer; + + /// + /// Initializes a new instance of the class. + /// + /// The root container. + public DefaultODataDeserializerProvider(IServiceProvider rootContainer) + { + if (rootContainer == null) + { + throw Error.ArgumentNull("rootContainer"); + } + + _rootContainer = rootContainer; + } + + /// + public override ODataEdmTypeDeserializer GetEdmTypeDeserializer(IEdmTypeReference edmType) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + switch (edmType.TypeKind()) + { + case EdmTypeKind.Entity: + case EdmTypeKind.Complex: + return _rootContainer.GetRequiredService(); + + case EdmTypeKind.Enum: + return _rootContainer.GetRequiredService(); + + case EdmTypeKind.Primitive: + return _rootContainer.GetRequiredService(); + + case EdmTypeKind.Collection: + IEdmCollectionTypeReference collectionType = edmType.AsCollection(); + if (collectionType.ElementType().IsEntity() || collectionType.ElementType().IsComplex()) + { + return _rootContainer.GetRequiredService(); + } + else + { + return _rootContainer.GetRequiredService(); + } + + default: + return null; + } + } + + /// + internal ODataDeserializer GetODataDeserializerImpl(Type type, Func modelFunction) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (modelFunction == null) + { + throw Error.ArgumentNull("modelFunction"); + } + + if (type == typeof(Uri)) + { + return _rootContainer.GetRequiredService(); + } + + if (type == typeof(ODataActionParameters) || type == typeof(ODataUntypedActionParameters)) + { + return _rootContainer.GetRequiredService(); + } + + // Get the model. Using a Func to delay evaluation of the model + // until after the above checks have passed. + IEdmModel model = modelFunction(); + ClrTypeCache typeMappingCache = model.GetTypeMappingCache(); + IEdmTypeReference edmType = typeMappingCache.GetEdmType(type, model); + + if (edmType == null) + { + return null; + } + else + { + return GetEdmTypeDeserializer(edmType); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs new file mode 100644 index 0000000..80b08f6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/DeserializationHelpers.cs @@ -0,0 +1,449 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + internal static class DeserializationHelpers + { + internal static void ApplyProperty(ODataProperty property, IEdmStructuredTypeReference resourceType, object resource, + ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext) + { + IEdmProperty edmProperty = resourceType.FindProperty(property.Name); + + bool isDynamicProperty = false; + string propertyName = property.Name; + if (edmProperty != null) + { + propertyName = EdmLibHelpers.GetClrPropertyName(edmProperty, readContext.Model); + } + else + { + IEdmStructuredType structuredType = resourceType.StructuredDefinition(); + isDynamicProperty = structuredType != null && structuredType.IsOpen; + } + + if (!isDynamicProperty && edmProperty == null) + { + throw new ODataException( + Error.Format(SRResources.CannotDeserializeUnknownProperty, property.Name, resourceType.Definition)); + } + + // dynamic properties have null values + IEdmTypeReference propertyType = edmProperty != null ? edmProperty.Type : null; + + EdmTypeKind propertyKind; + object value = ConvertValue(property.Value, ref propertyType, deserializerProvider, readContext, + out propertyKind); + + if (isDynamicProperty) + { + SetDynamicProperty(resource, resourceType, propertyKind, propertyName, value, propertyType, + readContext.Model); + } + else + { + SetDeclaredProperty(resource, propertyKind, propertyName, value, edmProperty, readContext); + } + } + + internal static void SetDynamicProperty(object resource, IEdmStructuredTypeReference resourceType, + EdmTypeKind propertyKind, string propertyName, object propertyValue, IEdmTypeReference propertyType, + IEdmModel model) + { + if (propertyKind == EdmTypeKind.Collection && propertyValue.GetType() != typeof(EdmComplexObjectCollection) + && propertyValue.GetType() != typeof(EdmEnumObjectCollection)) + { + SetDynamicCollectionProperty(resource, propertyName, propertyValue, propertyType.AsCollection(), + resourceType.StructuredDefinition(), model); + } + else + { + SetDynamicProperty(resource, propertyName, propertyValue, resourceType.StructuredDefinition(), + model); + } + } + + internal static void SetDeclaredProperty(object resource, EdmTypeKind propertyKind, string propertyName, + object propertyValue, IEdmProperty edmProperty, ODataDeserializerContext readContext) + { + if (propertyKind == EdmTypeKind.Collection) + { + SetCollectionProperty(resource, edmProperty, propertyValue, propertyName); + } + else + { + if (!readContext.IsUntyped) + { + if (propertyKind == EdmTypeKind.Primitive) + { + propertyValue = EdmPrimitiveHelpers.ConvertPrimitiveValue(propertyValue, + GetPropertyType(resource, propertyName)); + } + } + + SetProperty(resource, propertyName, propertyValue); + } + } + + internal static void SetCollectionProperty(object resource, IEdmProperty edmProperty, object value, string propertyName) + { + Contract.Assert(edmProperty != null); + + SetCollectionProperty(resource, propertyName, edmProperty.Type.AsCollection(), value, clearCollection: false); + } + + internal static void SetCollectionProperty(object resource, string propertyName, + IEdmCollectionTypeReference edmPropertyType, object value, bool clearCollection) + { + if (value != null) + { + IEnumerable collection = value as IEnumerable; + Contract.Assert(collection != null, + "SetCollectionProperty is always passed the result of ODataFeedDeserializer or ODataCollectionDeserializer"); + + Type resourceType = resource.GetType(); + Type propertyType = GetPropertyType(resource, propertyName); + + Type elementType; + if (!TypeHelper.IsCollection(propertyType, out elementType)) + { + string message = Error.Format(SRResources.PropertyIsNotCollection, propertyType.FullName, propertyName, resourceType.FullName); + throw new SerializationException(message); + } + + IEnumerable newCollection; + if (CanSetProperty(resource, propertyName) && + CollectionDeserializationHelpers.TryCreateInstance(propertyType, edmPropertyType, elementType, out newCollection)) + { + // settable collections + collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType); + if (propertyType.IsArray) + { + newCollection = CollectionDeserializationHelpers.ToArray(newCollection, elementType); + } + + SetProperty(resource, propertyName, newCollection); + } + else + { + // get-only collections. + newCollection = GetProperty(resource, propertyName) as IEnumerable; + if (newCollection == null) + { + string message = Error.Format(SRResources.CannotAddToNullCollection, propertyName, resourceType.FullName); + throw new SerializationException(message); + } + + if (clearCollection) + { + newCollection.Clear(propertyName, resourceType); + } + + collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType); + } + } + } + + internal static void SetDynamicCollectionProperty(object resource, string propertyName, object value, + IEdmCollectionTypeReference edmPropertyType, IEdmStructuredType structuredType, + IEdmModel model) + { + Contract.Assert(value != null); + Contract.Assert(model != null); + + IEnumerable collection = value as IEnumerable; + Contract.Assert(collection != null); + + Type resourceType = resource.GetType(); + Type elementType = EdmLibHelpers.GetClrType(edmPropertyType.ElementType(), model); + Type propertyType = typeof(ICollection<>).MakeGenericType(elementType); + IEnumerable newCollection; + if (CollectionDeserializationHelpers.TryCreateInstance(propertyType, edmPropertyType, elementType, + out newCollection)) + { + collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType); + SetDynamicProperty(resource, propertyName, newCollection, structuredType, model); + } + } + + internal static void SetProperty(object resource, string propertyName, object value) + { + IDelta delta = resource as IDelta; + if (delta == null) + { + resource.GetType().GetProperty(propertyName).SetValue(resource, value, index: null); + } + else + { + delta.TrySetPropertyValue(propertyName, value); + } + } + + internal static void SetDynamicProperty(object resource, string propertyName, object value, + IEdmStructuredType structuredType, IEdmModel model) + { + IDelta delta = resource as IDelta; + if (delta != null) + { + delta.TrySetPropertyValue(propertyName, value); + } + else + { + PropertyInfo propertyInfo = EdmLibHelpers.GetDynamicPropertyDictionary(structuredType, + model); + if (propertyInfo == null) + { + return; + } + + IDictionary dynamicPropertyDictionary; + object dynamicDictionaryObject = propertyInfo.GetValue(resource); + if (dynamicDictionaryObject == null) + { + if (!propertyInfo.CanWrite) + { + throw Error.InvalidOperation(SRResources.CannotSetDynamicPropertyDictionary, propertyName, + resource.GetType().FullName); + } + + dynamicPropertyDictionary = new Dictionary(); + propertyInfo.SetValue(resource, dynamicPropertyDictionary); + } + else + { + dynamicPropertyDictionary = (IDictionary)dynamicDictionaryObject; + } + + if (dynamicPropertyDictionary.ContainsKey(propertyName)) + { + throw Error.InvalidOperation(SRResources.DuplicateDynamicPropertyNameFound, + propertyName, structuredType.FullTypeName()); + } + + dynamicPropertyDictionary.Add(propertyName, value); + } + } + + internal static object ConvertValue(object oDataValue, ref IEdmTypeReference propertyType, ODataDeserializerProvider deserializerProvider, + ODataDeserializerContext readContext, out EdmTypeKind typeKind) + { + if (oDataValue == null) + { + typeKind = EdmTypeKind.None; + return null; + } + + ODataEnumValue enumValue = oDataValue as ODataEnumValue; + if (enumValue != null) + { + typeKind = EdmTypeKind.Enum; + return ConvertEnumValue(enumValue, ref propertyType, deserializerProvider, readContext); + } + + ODataCollectionValue collection = oDataValue as ODataCollectionValue; + if (collection != null) + { + typeKind = EdmTypeKind.Collection; + return ConvertCollectionValue(collection, ref propertyType, deserializerProvider, readContext); + } + + ODataUntypedValue untypedValue = oDataValue as ODataUntypedValue; + if (untypedValue != null) + { + Contract.Assert(!String.IsNullOrEmpty(untypedValue.RawValue)); + + if (untypedValue.RawValue.StartsWith("[", StringComparison.Ordinal) || + untypedValue.RawValue.StartsWith("{", StringComparison.Ordinal)) + { + throw new ODataException(Error.Format(SRResources.InvalidODataUntypedValue, untypedValue.RawValue)); + } + + oDataValue = ConvertPrimitiveValue(untypedValue.RawValue); + } + + typeKind = EdmTypeKind.Primitive; + return oDataValue; + } + + internal static Type GetPropertyType(object resource, string propertyName) + { + Contract.Assert(resource != null); + Contract.Assert(propertyName != null); + + IDelta delta = resource as IDelta; + if (delta != null) + { + Type type; + delta.TryGetPropertyType(propertyName, out type); + return type; + } + else + { + PropertyInfo property = resource.GetType().GetProperty(propertyName); + return property == null ? null : property.PropertyType; + } + } + + private static bool CanSetProperty(object resource, string propertyName) + { + IDelta delta = resource as IDelta; + if (delta != null) + { + return true; + } + else + { + PropertyInfo property = resource.GetType().GetProperty(propertyName); + return property != null && property.GetSetMethod() != null; + } + } + + private static object GetProperty(object resource, string propertyName) + { + IDelta delta = resource as IDelta; + if (delta != null) + { + object value; + delta.TryGetPropertyValue(propertyName, out value); + return value; + } + else + { + PropertyInfo property = resource.GetType().GetProperty(propertyName); + Contract.Assert(property != null, "ODataLib should have already verified that the property exists on the type."); + return property.GetValue(resource, index: null); + } + } + + private static object ConvertCollectionValue(ODataCollectionValue collection, + ref IEdmTypeReference propertyType, ODataDeserializerProvider deserializerProvider, + ODataDeserializerContext readContext) + { + IEdmCollectionTypeReference collectionType; + if (propertyType == null) + { + // dynamic collection property + Contract.Assert(!String.IsNullOrEmpty(collection.TypeName), + "ODataLib should have verified that dynamic collection value has a type name " + + "since we provided metadata."); + + string elementTypeName = GetCollectionElementTypeName(collection.TypeName, isNested: false); + IEdmModel model = readContext.Model; + IEdmSchemaType elementType = model.FindType(elementTypeName); + Contract.Assert(elementType != null); + collectionType = + new EdmCollectionTypeReference( + new EdmCollectionType(elementType.ToEdmTypeReference(isNullable: false))); + propertyType = collectionType; + } + else + { + collectionType = propertyType as IEdmCollectionTypeReference; + Contract.Assert(collectionType != null, "The type for collection must be a IEdmCollectionType."); + } + + ODataEdmTypeDeserializer deserializer = deserializerProvider.GetEdmTypeDeserializer(collectionType); + return deserializer.ReadInline(collection, collectionType, readContext); + } + + private static object ConvertPrimitiveValue(string value) + { + double doubleValue; + int intValue; + decimal decimalValue; + + if (String.CompareOrdinal(value, "null") == 0) + { + return null; + } + + if (Int32.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out intValue)) + { + return intValue; + } + + // todo: if it is Ieee754Compatible, parse decimal after double + if (Decimal.TryParse(value, NumberStyles.Number, NumberFormatInfo.InvariantInfo, out decimalValue)) + { + return decimalValue; + } + + if (Double.TryParse(value, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out doubleValue)) + { + return doubleValue; + } + + if (!value.StartsWith("\"", StringComparison.Ordinal) || !value.EndsWith("\"", StringComparison.Ordinal)) + { + throw new ODataException(Error.Format(SRResources.InvalidODataUntypedValue, value)); + } + + return value.Substring(1, value.Length - 2); + } + + private static object ConvertEnumValue(ODataEnumValue enumValue, ref IEdmTypeReference propertyType, + ODataDeserializerProvider deserializerProvider, ODataDeserializerContext readContext) + { + IEdmEnumTypeReference edmEnumType; + if (propertyType == null) + { + // dynamic enum property + Contract.Assert(!String.IsNullOrEmpty(enumValue.TypeName), + "ODataLib should have verified that dynamic enum value has a type name since we provided metadata."); + IEdmModel model = readContext.Model; + IEdmType edmType = model.FindType(enumValue.TypeName); + Contract.Assert(edmType.TypeKind == EdmTypeKind.Enum, "ODataLib should have verified that enum value has a enum resource type."); + edmEnumType = new EdmEnumTypeReference(edmType as IEdmEnumType, isNullable: true); + propertyType = edmEnumType; + } + else + { + edmEnumType = propertyType.AsEnum(); + } + + ODataEdmTypeDeserializer deserializer = deserializerProvider.GetEdmTypeDeserializer(edmEnumType); + return deserializer.ReadInline(enumValue, propertyType, readContext); + } + + // The same logic from ODL to get the element type name in a collection. + internal static string GetCollectionElementTypeName(string typeName, bool isNested) + { + const string CollectionTypeQualifier = "Collection"; + int collectionTypeQualifierLength = CollectionTypeQualifier.Length; + + // A collection type name must not be null, it has to start with "Collection(" and end with ")" + // and must not be "Collection()" + if (typeName != null && + typeName.StartsWith(CollectionTypeQualifier + "(", StringComparison.Ordinal) && + typeName[typeName.Length - 1] == ')' && + typeName.Length != collectionTypeQualifierLength + 2) + { + if (isNested) + { + throw new ODataException(Error.Format(SRResources.NestedCollectionsNotSupported, typeName)); + } + + string innerTypeName = typeName.Substring(collectionTypeQualifierLength + 1, + typeName.Length - (collectionTypeQualifierLength + 2)); + + // Check if it is not a nested collection and throw if it is + GetCollectionElementTypeName(innerTypeName, true); + + return innerTypeName; + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/EnumDeserializationHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/EnumDeserializationHelpers.cs new file mode 100644 index 0000000..88eccdb --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/EnumDeserializationHelpers.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + internal static class EnumDeserializationHelpers + { + public static object ConvertEnumValue(object value, Type type) + { + if (value == null) + { + throw Error.ArgumentNull("value"); + } + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + Type enumType = TypeHelper.GetUnderlyingTypeOrSelf(type); + + // if value is of the requested type nothing to do here. + if (value.GetType() == enumType) + { + return value; + } + + ODataEnumValue enumValue = value as ODataEnumValue; + + if (enumValue == null) + { + throw new ValidationException(Error.Format(SRResources.PropertyMustBeEnum, value.GetType().Name, "ODataEnumValue")); + } + + if (!TypeHelper.IsEnum(enumType)) + { + throw Error.InvalidOperation(Error.Format(SRResources.TypeMustBeEnumOrNullableEnum, type.Name)); + } + + return Enum.Parse(enumType, enumValue.Value); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataActionPayloadDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataActionPayloadDeserializer.cs new file mode 100644 index 0000000..a3bb4ed --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataActionPayloadDeserializer.cs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents an for reading OData action parameters. + /// + public class ODataActionPayloadDeserializer : ODataDeserializer + { + private static readonly MethodInfo _castMethodInfo = typeof(Enumerable).GetMethod("Cast"); + + /// + /// Initializes a new instance of the class. + /// + /// The deserializer provider to use to read inner objects. + public ODataActionPayloadDeserializer(ODataDeserializerProvider deserializerProvider) + : base(ODataPayloadKind.Parameter) + { + if (deserializerProvider == null) + { + throw Error.ArgumentNull("deserializerProvider"); + } + + DeserializerProvider = deserializerProvider; + } + + /// + /// Gets the deserializer provider to use to read inner objects. + /// + public ODataDeserializerProvider DeserializerProvider { get; private set; } + + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", + Justification = "The majority of types referenced by this method are EdmLib types this method needs to know about to operate correctly")] + public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + if (messageReader == null) + { + throw Error.ArgumentNull("messageReader"); + } + + IEdmAction action = GetAction(readContext); + Contract.Assert(action != null); + + // Create the correct resource type; + Dictionary payload; + if (type == typeof(ODataActionParameters)) + { + payload = new ODataActionParameters(); + } + else + { + payload = new ODataUntypedActionParameters(action); + } + + ODataParameterReader reader = messageReader.CreateODataParameterReader(action); + + while (reader.Read()) + { + string parameterName = null; + IEdmOperationParameter parameter = null; + + switch (reader.State) + { + case ODataParameterReaderState.Value: + parameterName = reader.Name; + parameter = action.Parameters.SingleOrDefault(p => p.Name == parameterName); + // ODataLib protects against this but asserting just in case. + Contract.Assert(parameter != null, String.Format(CultureInfo.InvariantCulture, "Parameter '{0}' not found.", parameterName)); + if (parameter.Type.IsPrimitive()) + { + payload[parameterName] = reader.Value; + } + else + { + ODataEdmTypeDeserializer deserializer = DeserializerProvider.GetEdmTypeDeserializer(parameter.Type); + payload[parameterName] = deserializer.ReadInline(reader.Value, parameter.Type, readContext); + } + break; + + case ODataParameterReaderState.Collection: + parameterName = reader.Name; + parameter = action.Parameters.SingleOrDefault(p => p.Name == parameterName); + // ODataLib protects against this but asserting just in case. + Contract.Assert(parameter != null, String.Format(CultureInfo.InvariantCulture, "Parameter '{0}' not found.", parameterName)); + IEdmCollectionTypeReference collectionType = parameter.Type as IEdmCollectionTypeReference; + Contract.Assert(collectionType != null); + ODataCollectionValue value = ODataCollectionDeserializer.ReadCollection(reader.CreateCollectionReader()); + ODataCollectionDeserializer collectionDeserializer = (ODataCollectionDeserializer)DeserializerProvider.GetEdmTypeDeserializer(collectionType); + payload[parameterName] = collectionDeserializer.ReadInline(value, collectionType, readContext); + break; + + case ODataParameterReaderState.Resource: + parameterName = reader.Name; + parameter = action.Parameters.SingleOrDefault(p => p.Name == parameterName); + Contract.Assert(parameter != null, String.Format(CultureInfo.InvariantCulture, "Parameter '{0}' not found.", parameterName)); + Contract.Assert(parameter.Type.IsStructured()); + + ODataReader resourceReader = reader.CreateResourceReader(); + object item = resourceReader.ReadResourceOrResourceSet(); + ODataResourceDeserializer resourceDeserializer = (ODataResourceDeserializer)DeserializerProvider.GetEdmTypeDeserializer(parameter.Type); + payload[parameterName] = resourceDeserializer.ReadInline(item, parameter.Type, readContext); + break; + + case ODataParameterReaderState.ResourceSet: + parameterName = reader.Name; + parameter = action.Parameters.SingleOrDefault(p => p.Name == parameterName); + Contract.Assert(parameter != null, String.Format(CultureInfo.InvariantCulture, "Parameter '{0}' not found.", parameterName)); + + IEdmCollectionTypeReference resourceSetType = parameter.Type as IEdmCollectionTypeReference; + Contract.Assert(resourceSetType != null); + + ODataReader resourceSetReader = reader.CreateResourceSetReader(); + object feed = resourceSetReader.ReadResourceOrResourceSet(); + ODataResourceSetDeserializer resourceSetDeserializer = (ODataResourceSetDeserializer)DeserializerProvider.GetEdmTypeDeserializer(resourceSetType); + + object result = resourceSetDeserializer.ReadInline(feed, resourceSetType, readContext); + + IEdmTypeReference elementTypeReference = resourceSetType.ElementType(); + Contract.Assert(elementTypeReference.IsStructured()); + + IEnumerable enumerable = result as IEnumerable; + if (enumerable != null) + { + if (readContext.IsUntyped) + { + payload[parameterName] = enumerable.ConvertToEdmObject(resourceSetType); + } + else + { + Type elementClrType = EdmLibHelpers.GetClrType(elementTypeReference, readContext.Model); + IEnumerable castedResult = + _castMethodInfo.MakeGenericMethod(elementClrType) + .Invoke(null, new[] { result }) as IEnumerable; + payload[parameterName] = castedResult; + } + } + break; + } + } + + return payload; + } + + internal static IEdmAction GetAction(ODataDeserializerContext readContext) + { + if (readContext == null) + { + throw Error.ArgumentNull("readContext"); + } + + ODataPath path = readContext.Path; + if (path == null || path.Segments.Count == 0) + { + throw new SerializationException(SRResources.ODataPathMissing); + } + + IEdmAction action = null; + if (path.PathTemplate == "~/unboundaction") + { + // only one segment, it may be an unbound action + OperationImportSegment unboundActionSegment = path.Segments.Last() as OperationImportSegment; + if (unboundActionSegment != null) + { + IEdmActionImport actionImport = unboundActionSegment.OperationImports.First() as IEdmActionImport; + if (actionImport != null) + { + action = actionImport.Action; + } + } + } + else + { + // otherwise, it may be a bound action + OperationSegment actionSegment = path.Segments.Last() as OperationSegment; + if (actionSegment != null) + { + action = actionSegment.Operations.First() as IEdmAction; + } + } + + if (action == null) + { + string message = Error.Format(SRResources.RequestNotActionInvocation, path.ToString()); + throw new SerializationException(message); + } + + return action; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataCollectionDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataCollectionDeserializer.cs new file mode 100644 index 0000000..422c34d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataCollectionDeserializer.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents an that can read OData collection payloads. + /// + public class ODataCollectionDeserializer : ODataEdmTypeDeserializer + { + private static readonly MethodInfo _castMethodInfo = typeof(Enumerable).GetMethod("Cast"); + + /// + /// Initializes a new instance of the class. + /// + /// The deserializer provider to use to read inner objects. + public ODataCollectionDeserializer(ODataDeserializerProvider deserializerProvider) + : base(ODataPayloadKind.Collection, deserializerProvider) + { + } + + /// + public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + if (messageReader == null) + { + throw Error.ArgumentNull("messageReader"); + } + + IEdmTypeReference edmType = readContext.GetEdmType(type); + Contract.Assert(edmType != null); + + if (!edmType.IsCollection()) + { + throw Error.Argument("type", SRResources.ArgumentMustBeOfType, EdmTypeKind.Collection); + } + + IEdmCollectionTypeReference collectionType = edmType.AsCollection(); + IEdmTypeReference elementType = collectionType.ElementType(); + ODataCollectionReader reader = messageReader.CreateODataCollectionReader(elementType); + return ReadInline(ReadCollection(reader), edmType, readContext); + } + + /// + public sealed override object ReadInline(object item, IEdmTypeReference edmType, ODataDeserializerContext readContext) + { + if (item == null) + { + return null; + } + + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + if (!edmType.IsCollection()) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeDeserialized, edmType.ToTraceString())); + } + + IEdmCollectionTypeReference collectionType = edmType.AsCollection(); + IEdmTypeReference elementType = collectionType.ElementType(); + + ODataCollectionValue collection = item as ODataCollectionValue; + + if (collection == null) + { + throw Error.Argument("item", SRResources.ArgumentMustBeOfType, typeof(ODataCollectionValue).Name); + } + // Recursion guard to avoid stack overflows + RuntimeHelpers.EnsureSufficientExecutionStack(); + + IEnumerable result = ReadCollectionValue(collection, elementType, readContext); + if (result != null) + { + if (readContext.IsUntyped && elementType.IsEnum()) + { + return result.ConvertToEdmObject(collectionType); + } + else + { + Type elementClrType = EdmLibHelpers.GetClrType(elementType, readContext.Model); + IEnumerable castedResult = _castMethodInfo.MakeGenericMethod(elementClrType).Invoke(null, new object[] { result }) as IEnumerable; + return castedResult; + } + } + return null; + } + + /// + /// Deserializes the given under the given . + /// + /// The to deserialize. + /// The element type of the collection to read. + /// The deserializer context. + /// The deserialized collection. + public virtual IEnumerable ReadCollectionValue(ODataCollectionValue collectionValue, IEdmTypeReference elementType, + ODataDeserializerContext readContext) + { + if (collectionValue == null) + { + throw Error.ArgumentNull("collectionValue"); + } + if (elementType == null) + { + throw Error.ArgumentNull("elementType"); + } + + ODataEdmTypeDeserializer deserializer = DeserializerProvider.GetEdmTypeDeserializer(elementType); + if (deserializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeDeserialized, elementType.FullName())); + } + + foreach (object item in collectionValue.Items) + { + if (elementType.IsPrimitive()) + { + yield return item; + } + else + { + yield return deserializer.ReadInline(item, elementType, readContext); + } + } + } + + internal static ODataCollectionValue ReadCollection(ODataCollectionReader reader) + { + ArrayList items = new ArrayList(); + string typeName = null; + + while (reader.Read()) + { + if (ODataCollectionReaderState.Value == reader.State) + { + items.Add(reader.Item); + } + else if (ODataCollectionReaderState.CollectionStart == reader.State) + { + typeName = reader.Item.ToString(); + } + } + + return new ODataCollectionValue { Items = items.Cast(), TypeName = typeName }; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializer.cs new file mode 100644 index 0000000..3165039 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializer.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// An is used to read an ODataMessage into a CLR object. + /// + /// + /// Each supported CLR type has a corresponding . A CLR type is supported if it is one of + /// the special types or if it has a backing EDM type. Some of the special types are Uri which maps to ODataReferenceLink payload, + /// Uri[] which maps to ODataReferenceLinks payload, ODataWorkspace which maps to ODataServiceDocument payload. + /// + public abstract class ODataDeserializer + { + /// + /// Initializes a new instance of the class. + /// + /// The kind of payload this deserializer handles. + protected ODataDeserializer(ODataPayloadKind payloadKind) + { + ODataPayloadKind = payloadKind; + } + + /// + /// The kind of ODataPayload this deserializer handles. + /// + public ODataPayloadKind ODataPayloadKind { get; private set; } + + /// + /// Reads an using messageReader. + /// + /// The messageReader to use. + /// The type of the object to read into. + /// The read context. + /// The deserialized object. + public virtual object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + throw Error.NotSupported(SRResources.DeserializerDoesNotSupportRead, GetType().Name); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerContext.cs new file mode 100644 index 0000000..ee1cd83 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerContext.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// This class encapsulates the state and settings that get passed to . + /// + public partial class ODataDeserializerContext + { + private bool? _isDeltaOfT; + private bool? _isUntyped; + + /// + /// Gets or sets the type of the top-level object the request needs to be deserialized into. + /// + public Type ResourceType { get; set; } + + /// + /// Gets or sets the of the top-level object the request needs to be deserialized into. + /// + public IEdmTypeReference ResourceEdmType { get; set; } + + /// + /// Gets or sets the of the request. + /// + public ODataPath Path { get; set; } + + /// + /// Gets or sets the EDM model associated with the request. + /// + public IEdmModel Model { get; set; } + + /// + /// Gets or sets the HTTP Request that is being deserialized. + /// + internal IWebApiRequestMessage InternalRequest { get; private set; } + + /// + /// Gets or sets the to be used for generating links while serializing this + /// feed instance. + /// + internal IWebApiUrlHelper InternalUrlHelper { get; private set; } + + internal bool IsDeltaOfT + { + get + { + if (!_isDeltaOfT.HasValue) + { + _isDeltaOfT = ResourceType != null && TypeHelper.IsGenericType(ResourceType) && ResourceType.GetGenericTypeDefinition() == typeof(Delta<>); + } + + return _isDeltaOfT.Value; + } + } + + internal bool IsUntyped + { + get + { + if (!_isUntyped.HasValue) + { + _isUntyped = TypeHelper.IsTypeAssignableFrom(typeof(IEdmObject), ResourceType) || + typeof(ODataUntypedActionParameters) == ResourceType; + } + + return _isUntyped.Value; + } + } + + internal IEdmTypeReference GetEdmType(Type type) + { + if (ResourceEdmType != null) + { + return ResourceEdmType; + } + + return EdmLibHelpers.GetExpectedPayloadType(type, Path, Model); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerProvider.cs new file mode 100644 index 0000000..1a7e17a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataDeserializerProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents a factory that creates an . + /// + public abstract partial class ODataDeserializerProvider + { + /// + /// Gets the for the given EDM type. + /// + /// The EDM type. + /// An that can deserialize the given EDM type. + public abstract ODataEdmTypeDeserializer GetEdmTypeDeserializer(IEdmTypeReference edmType); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEdmTypeDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEdmTypeDeserializer.cs new file mode 100644 index 0000000..14a4c6f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEdmTypeDeserializer.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Base class for all s that deserialize into an object backed by . + /// + public abstract class ODataEdmTypeDeserializer : ODataDeserializer + { + /// + /// Initializes a new instance of the class. + /// + /// The kind of OData payload that this deserializer reads. + protected ODataEdmTypeDeserializer(ODataPayloadKind payloadKind) + : base(payloadKind) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The kind of OData payload this deserializer handles. + /// The . + protected ODataEdmTypeDeserializer(ODataPayloadKind payloadKind, ODataDeserializerProvider deserializerProvider) + : this(payloadKind) + { + if (deserializerProvider == null) + { + throw Error.ArgumentNull("deserializerProvider"); + } + + DeserializerProvider = deserializerProvider; + } + + /// + /// The to use for deserializing inner items. + /// + public ODataDeserializerProvider DeserializerProvider { get; private set; } + + /// + /// Deserializes the item into a new object of type corresponding to . + /// + /// The item to deserialize. + /// The EDM type of the object to read into. + /// The . + /// The deserialized object. + public virtual object ReadInline(object item, IEdmTypeReference edmType, ODataDeserializerContext readContext) + { + throw Error.NotSupported(SRResources.DoesNotSupportReadInLine, GetType().Name); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkBase.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkBase.cs new file mode 100644 index 0000000..eff07d1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkBase.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Encapuslates an . + /// + public class ODataEntityReferenceLinkBase : ODataItemBase + { + /// + /// Initializes a new instance of . + /// + /// The wrapped item. + public ODataEntityReferenceLinkBase(ODataEntityReferenceLink item) + : base(item) + { + } + + /// + /// Gets the wrapped . + /// + public ODataEntityReferenceLink EntityReferenceLink + { + get + { + return Item as ODataEntityReferenceLink; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkDeserializer.cs new file mode 100644 index 0000000..35e15f0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEntityReferenceLinkDeserializer.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents an that can read OData entity reference link payloads. + /// + public class ODataEntityReferenceLinkDeserializer : ODataDeserializer + { + /// + /// Initializes a new instance of the class. + /// + public ODataEntityReferenceLinkDeserializer() + : base(ODataPayloadKind.EntityReferenceLink) + { + } + + /// + public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + if (messageReader == null) + { + throw Error.ArgumentNull("messageReader"); + } + + if (readContext == null) + { + throw Error.ArgumentNull("readContext"); + } + + ODataEntityReferenceLink entityReferenceLink = messageReader.ReadEntityReferenceLink(); + + if (entityReferenceLink != null) + { + return ResolveContentId(entityReferenceLink.Url, readContext); + } + + return null; + } + + private static Uri ResolveContentId(Uri uri, ODataDeserializerContext readContext) + { + if (uri != null) + { + IDictionary contentIDToLocationMapping = readContext.InternalRequest.ODataContentIdMapping; + if (contentIDToLocationMapping != null) + { + Uri baseAddress = new Uri(readContext.InternalUrlHelper.CreateODataLink()); + string relativeUrl = uri.IsAbsoluteUri ? baseAddress.MakeRelativeUri(uri).OriginalString : uri.OriginalString; + string resolvedUrl = ContentIdHelpers.ResolveContentId(relativeUrl, contentIDToLocationMapping); + Uri resolvedUri = new Uri(resolvedUrl, UriKind.RelativeOrAbsolute); + if (!resolvedUri.IsAbsoluteUri) + { + resolvedUri = new Uri(baseAddress, uri); + } + return resolvedUri; + } + } + + return uri; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEnumDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEnumDeserializer.cs new file mode 100644 index 0000000..e81ea5d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataEnumDeserializer.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents an that can read OData enum types. + /// + public class ODataEnumDeserializer : ODataEdmTypeDeserializer + { + /// + /// Initializes a new instance of the class. + /// + public ODataEnumDeserializer() + : base(ODataPayloadKind.Property) + { + } + + /// + public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + if (messageReader == null) + { + throw Error.ArgumentNull("messageReader"); + } + if (type == null) + { + throw Error.ArgumentNull("type"); + } + if (readContext == null) + { + throw Error.ArgumentNull("readContext"); + } + + IEdmTypeReference edmType = readContext.GetEdmType(type); + Contract.Assert(edmType != null); + + ODataProperty property = messageReader.ReadProperty(edmType); + return ReadInline(property, edmType, readContext); + } + + /// + public override object ReadInline(object item, IEdmTypeReference edmType, ODataDeserializerContext readContext) + { + if (item == null) + { + return null; + } + + ODataProperty property = item as ODataProperty; + if (property != null) + { + item = property.Value; + } + + IEdmEnumTypeReference enumTypeReference = edmType.AsEnum(); + ODataEnumValue enumValue = item as ODataEnumValue; + if (readContext.IsUntyped) + { + Contract.Assert(edmType.TypeKind() == EdmTypeKind.Enum); + return new EdmEnumObject(enumTypeReference, enumValue.Value); + } + + IEdmEnumType enumType = enumTypeReference.EnumDefinition(); + + // Enum member supports model alias case. So, try to use the Edm member name to retrieve the Enum value. + var memberMapAnnotation = readContext.Model.GetClrEnumMemberAnnotation(enumType); + if (memberMapAnnotation != null) + { + if (enumValue != null) + { + IEdmEnumMember enumMember = enumType.Members.FirstOrDefault(m => m.Name == enumValue.Value); + if (enumMember != null) + { + var clrMember = memberMapAnnotation.GetClrEnumMember(enumMember); + if (clrMember != null) + { + return clrMember; + } + } + } + } + + Type clrType = EdmLibHelpers.GetClrType(edmType, readContext.Model); + return EnumDeserializationHelpers.ConvertEnumValue(item, clrType); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataItemBase.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataItemBase.cs new file mode 100644 index 0000000..8860176 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataItemBase.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Base class for all classes that wrap an . + /// + public abstract class ODataItemBase + { + private ODataItem _item; + + /// + /// Initializes a new instance of . + /// + /// The wrapped item. + protected ODataItemBase(ODataItem item) + { + _item = item; + } + + /// + /// Gets the wrapped . + /// + public ODataItem Item + { + get + { + return _item; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataNestedResourceInfoWrapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataNestedResourceInfoWrapper.cs new file mode 100644 index 0000000..5dc286a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataNestedResourceInfoWrapper.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Encapsulates an and the list of nested items. + /// + /// + /// A nested resource info for a singleton nested property can only contain one item - either ODataResource or ODataEntityReferenceLink. + /// A nested resource info for a collection nested property can contain any number of items - each is either ODataResource or ODataEntityReferenceLink. + /// + public sealed class ODataNestedResourceInfoWrapper : ODataItemBase + { + /// + /// Initializes a new instance of . + /// + /// The wrapped item. + public ODataNestedResourceInfoWrapper(ODataNestedResourceInfo item) + : base(item) + { + NestedItems = new List(); + } + + /// + /// Gets the wrapped . + /// + public ODataNestedResourceInfo NestedResourceInfo + { + get + { + return Item as ODataNestedResourceInfo; + } + } + + /// + /// Gets the nested items that are part of this nested resource info. + /// + public IList NestedItems { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataPrimitiveDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataPrimitiveDeserializer.cs new file mode 100644 index 0000000..085d098 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataPrimitiveDeserializer.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents an that can read OData primitve types. + /// + public class ODataPrimitiveDeserializer : ODataEdmTypeDeserializer + { + /// + /// Initializes a new instance of the class. + /// + public ODataPrimitiveDeserializer() + : base(ODataPayloadKind.Property) + { + } + + /// + public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + if (messageReader == null) + { + throw Error.ArgumentNull("messageReader"); + } + + IEdmTypeReference edmType = readContext.GetEdmType(type); + Contract.Assert(edmType != null); + + ODataProperty property = messageReader.ReadProperty(edmType); + return ReadInline(property, edmType, readContext); + } + + /// + public sealed override object ReadInline(object item, IEdmTypeReference edmType, ODataDeserializerContext readContext) + { + if (item == null) + { + return null; + } + + ODataProperty property = item as ODataProperty; + if (property == null) + { + throw Error.Argument("item", SRResources.ArgumentMustBeOfType, typeof(ODataProperty).Name); + } + + return ReadPrimitive(property, readContext); + } + + /// + /// Deserializes the primitive from the given under the given . + /// + /// The primitive property to deserialize. + /// The deserializer context. + /// The deserialized OData primitive value. + public virtual object ReadPrimitive(ODataProperty primitiveProperty, ODataDeserializerContext readContext) + { + if (primitiveProperty == null) + { + throw Error.ArgumentNull("primitiveProperty"); + } + + if (readContext == null) + { + throw Error.ArgumentNull("readContext"); + } + + //Try and change the value appropriately if type is specified + if (readContext.ResourceType != null && primitiveProperty.Value != null) + { + return EdmPrimitiveHelpers.ConvertPrimitiveValue(primitiveProperty.Value, readContext.ResourceType); + } + + return primitiveProperty.Value; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataReaderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataReaderExtensions.cs new file mode 100644 index 0000000..57b69d4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataReaderExtensions.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Extension methods for . + /// + public static class ODataReaderExtensions + { + /// + /// Reads a or object. + /// + /// The OData reader to read from. + /// The read resource or resource set. + public static ODataItemBase ReadResourceOrResourceSet(this ODataReader reader) + { + if (reader == null) + { + throw Error.ArgumentNull("reader"); + } + + ODataItemBase topLevelItem = null; + Stack itemsStack = new Stack(); + + while (reader.Read()) + { + switch (reader.State) + { + case ODataReaderState.ResourceStart: + ODataResource resource = (ODataResource)reader.Item; + ODataResourceWrapper resourceWrapper = null; + if (resource != null) + { + resourceWrapper = new ODataResourceWrapper(resource); + } + + if (itemsStack.Count == 0) + { + Contract.Assert(resource != null, "The top-level resource can never be null."); + topLevelItem = resourceWrapper; + } + else + { + ODataItemBase parentItem = itemsStack.Peek(); + ODataResourceSetWrapper parentResourceSet = parentItem as ODataResourceSetWrapper; + if (parentResourceSet != null) + { + parentResourceSet.Resources.Add(resourceWrapper); + } + else + { + ODataNestedResourceInfoWrapper parentNestedResource = (ODataNestedResourceInfoWrapper)parentItem; + Contract.Assert(parentNestedResource.NestedResourceInfo.IsCollection == false, "Only singleton nested properties can contain resource as their child."); + Contract.Assert(parentNestedResource.NestedItems.Count == 0, "Each nested property can contain only one resource as its direct child."); + parentNestedResource.NestedItems.Add(resourceWrapper); + } + } + + itemsStack.Push(resourceWrapper); + break; + + case ODataReaderState.ResourceEnd: + Contract.Assert( + itemsStack.Count > 0 && (reader.Item == null || itemsStack.Peek().Item == reader.Item), + "The resource which is ending should be on the top of the items stack."); + itemsStack.Pop(); + break; + + case ODataReaderState.NestedResourceInfoStart: + ODataNestedResourceInfo nestedResourceInfo = (ODataNestedResourceInfo)reader.Item; + Contract.Assert(nestedResourceInfo != null, "nested resource info should never be null."); + + ODataNestedResourceInfoWrapper nestedResourceInfoWrapper = new ODataNestedResourceInfoWrapper(nestedResourceInfo); + Contract.Assert(itemsStack.Count > 0, "nested resource info can't appear as top-level item."); + { + ODataResourceWrapper parentResource = (ODataResourceWrapper)itemsStack.Peek(); + parentResource.NestedResourceInfos.Add(nestedResourceInfoWrapper); + } + + itemsStack.Push(nestedResourceInfoWrapper); + break; + + case ODataReaderState.NestedResourceInfoEnd: + Contract.Assert(itemsStack.Count > 0 && itemsStack.Peek().Item == reader.Item, + "The nested resource info which is ending should be on the top of the items stack."); + itemsStack.Pop(); + break; + + case ODataReaderState.ResourceSetStart: + ODataResourceSet resourceSet = (ODataResourceSet)reader.Item; + Contract.Assert(resourceSet != null, "ResourceSet should never be null."); + + ODataResourceSetWrapper resourceSetWrapper = new ODataResourceSetWrapper(resourceSet); + if (itemsStack.Count > 0) + { + ODataNestedResourceInfoWrapper parentNestedResourceInfo = (ODataNestedResourceInfoWrapper)itemsStack.Peek(); + Contract.Assert(parentNestedResourceInfo != null, "this has to be an inner resource set. inner resource sets always have a nested resource info."); + Contract.Assert(parentNestedResourceInfo.NestedResourceInfo.IsCollection == true, "Only collection nested properties can contain resource set as their child."); + parentNestedResourceInfo.NestedItems.Add(resourceSetWrapper); + } + else + { + topLevelItem = resourceSetWrapper; + } + + itemsStack.Push(resourceSetWrapper); + break; + + case ODataReaderState.ResourceSetEnd: + Contract.Assert(itemsStack.Count > 0 && itemsStack.Peek().Item == reader.Item, "The resource set which is ending should be on the top of the items stack."); + itemsStack.Pop(); + break; + + case ODataReaderState.EntityReferenceLink: + ODataEntityReferenceLink entityReferenceLink = (ODataEntityReferenceLink)reader.Item; + Contract.Assert(entityReferenceLink != null, "Entity reference link should never be null."); + ODataEntityReferenceLinkBase entityReferenceLinkWrapper = new ODataEntityReferenceLinkBase(entityReferenceLink); + + Contract.Assert(itemsStack.Count > 0, "Entity reference link should never be reported as top-level item."); + { + ODataNestedResourceInfoWrapper parentNavigationLink = (ODataNestedResourceInfoWrapper)itemsStack.Peek(); + parentNavigationLink.NestedItems.Add(entityReferenceLinkWrapper); + } + + break; + + default: + Contract.Assert(false, "We should never get here, it means the ODataReader reported a wrong state."); + break; + } + } + + Contract.Assert(reader.State == ODataReaderState.Completed, "We should have consumed all of the input by now."); + Contract.Assert(topLevelItem != null, "A top level resource or resource set should have been read by now."); + return topLevelItem; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs new file mode 100644 index 0000000..35842ca --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceDeserializer.cs @@ -0,0 +1,622 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents an for reading OData resource payloads. + /// + public class ODataResourceDeserializer : ODataEdmTypeDeserializer + { + /// + /// Initializes a new instance of the class. + /// + /// The deserializer provider to use to read inner objects. + public ODataResourceDeserializer(ODataDeserializerProvider deserializerProvider) + : base(ODataPayloadKind.Resource, deserializerProvider) + { + } + + /// + public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + if (messageReader == null) + { + throw Error.ArgumentNull("messageReader"); + } + + if (readContext == null) + { + throw Error.ArgumentNull("readContext"); + } + + IEdmTypeReference edmType = readContext.GetEdmType(type); + Contract.Assert(edmType != null); + + if (!edmType.IsStructured()) + { + throw Error.Argument("type", SRResources.ArgumentMustBeOfType, "Structured"); + } + + IEdmStructuredTypeReference structuredType = edmType.AsStructured(); + + IEdmNavigationSource navigationSource = null; + if (structuredType.IsEntity()) + { + if (readContext.Path == null) + { + throw Error.Argument("readContext", SRResources.ODataPathMissing); + } + + navigationSource = readContext.Path.NavigationSource; + if (navigationSource == null) + { + throw new SerializationException(SRResources.NavigationSourceMissingDuringDeserialization); + } + } + + ODataReader odataReader = messageReader.CreateODataResourceReader(navigationSource, structuredType.StructuredDefinition()); + ODataResourceWrapper topLevelResource = odataReader.ReadResourceOrResourceSet() as ODataResourceWrapper; + Contract.Assert(topLevelResource != null); + + return ReadInline(topLevelResource, structuredType, readContext); + } + + /// + public sealed override object ReadInline(object item, IEdmTypeReference edmType, ODataDeserializerContext readContext) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + if (edmType.IsComplex() && item == null) + { + return null; + } + + if (item == null) + { + throw Error.ArgumentNull("item"); + } + + if (!edmType.IsStructured()) + { + throw Error.Argument("edmType", SRResources.ArgumentMustBeOfType, "Entity or Complex"); + } + + ODataResourceWrapper resourceWrapper = item as ODataResourceWrapper; + if (resourceWrapper == null) + { + throw Error.Argument("item", SRResources.ArgumentMustBeOfType, typeof(ODataResource).Name); + } + + // Recursion guard to avoid stack overflows + RuntimeHelpers.EnsureSufficientExecutionStack(); + + return ReadResource(resourceWrapper, edmType.AsStructured(), readContext); + } + + /// + /// Deserializes the given under the given . + /// + /// The OData resource to deserialize. + /// The type of the resource to deserialize. + /// The deserializer context. + /// The deserialized resource. + public virtual object ReadResource(ODataResourceWrapper resourceWrapper, IEdmStructuredTypeReference structuredType, + ODataDeserializerContext readContext) + { + if (resourceWrapper == null) + { + throw Error.ArgumentNull("resourceWrapper"); + } + + if (readContext == null) + { + throw Error.ArgumentNull("readContext"); + } + + if (!String.IsNullOrEmpty(resourceWrapper.Resource.TypeName) && structuredType.FullName() != resourceWrapper.Resource.TypeName) + { + // received a derived type in a base type deserializer. delegate it to the appropriate derived type deserializer. + IEdmModel model = readContext.Model; + + if (model == null) + { + throw Error.Argument("readContext", SRResources.ModelMissingFromReadContext); + } + + IEdmStructuredType actualType = model.FindType(resourceWrapper.Resource.TypeName) as IEdmStructuredType; + if (actualType == null) + { + throw new ODataException(Error.Format(SRResources.ResourceTypeNotInModel, resourceWrapper.Resource.TypeName)); + } + + if (actualType.IsAbstract) + { + string message = Error.Format(SRResources.CannotInstantiateAbstractResourceType, resourceWrapper.Resource.TypeName); + throw new ODataException(message); + } + + IEdmTypeReference actualStructuredType; + IEdmEntityType actualEntityType = actualType as IEdmEntityType; + if (actualEntityType != null) + { + actualStructuredType = new EdmEntityTypeReference(actualEntityType, isNullable: false); + } + else + { + actualStructuredType = new EdmComplexTypeReference(actualType as IEdmComplexType, isNullable: false); + } + + ODataEdmTypeDeserializer deserializer = DeserializerProvider.GetEdmTypeDeserializer(actualStructuredType); + if (deserializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeDeserialized, actualEntityType.FullName())); + } + + object resource = deserializer.ReadInline(resourceWrapper, actualStructuredType, readContext); + + EdmStructuredObject structuredObject = resource as EdmStructuredObject; + if (structuredObject != null) + { + structuredObject.ExpectedEdmType = structuredType.StructuredDefinition(); + } + + return resource; + } + else + { + object resource = CreateResourceInstance(structuredType, readContext); + ApplyResourceProperties(resource, resourceWrapper, structuredType, readContext); + return resource; + } + } + + /// + /// Creates a new instance of the backing CLR object for the given resource type. + /// + /// The EDM type of the resource to create. + /// The deserializer context. + /// The created CLR object. + public virtual object CreateResourceInstance(IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + if (readContext == null) + { + throw Error.ArgumentNull("readContext"); + } + + if (structuredType == null) + { + throw Error.ArgumentNull("structuredType"); + } + + IEdmModel model = readContext.Model; + if (model == null) + { + throw Error.Argument("readContext", SRResources.ModelMissingFromReadContext); + } + + if (readContext.IsUntyped) + { + if (structuredType.IsEntity()) + { + return new EdmEntityObject(structuredType.AsEntity()); + } + + return new EdmComplexObject(structuredType.AsComplex()); + } + else + { + Type clrType = EdmLibHelpers.GetClrType(structuredType, model); + if (clrType == null) + { + throw new ODataException( + Error.Format(SRResources.MappingDoesNotContainResourceType, structuredType.FullName())); + } + + if (readContext.IsDeltaOfT) + { + IEnumerable structuralProperties = structuredType.StructuralProperties() + .Select(edmProperty => EdmLibHelpers.GetClrPropertyName(edmProperty, model)); + + if (structuredType.IsOpen()) + { + PropertyInfo dynamicDictionaryPropertyInfo = EdmLibHelpers.GetDynamicPropertyDictionary( + structuredType.StructuredDefinition(), model); + + return Activator.CreateInstance(readContext.ResourceType, clrType, structuralProperties, + dynamicDictionaryPropertyInfo); + } + else + { + return Activator.CreateInstance(readContext.ResourceType, clrType, structuralProperties); + } + } + else + { + return Activator.CreateInstance(clrType); + } + } + } + + /// + /// Deserializes the nested properties from into . + /// + /// The object into which the nested properties should be read. + /// The resource object containing the nested properties. + /// The type of the resource. + /// The deserializer context. + public virtual void ApplyNestedProperties(object resource, ODataResourceWrapper resourceWrapper, + IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + if (resourceWrapper == null) + { + throw Error.ArgumentNull("resourceWrapper"); + } + + foreach (ODataNestedResourceInfoWrapper nestedResourceInfo in resourceWrapper.NestedResourceInfos) + { + ApplyNestedProperty(resource, nestedResourceInfo, structuredType, readContext); + } + } + + /// + /// Deserializes the nested property from into . + /// + /// The object into which the nested property should be read. + /// The nested resource info. + /// The type of the resource. + /// The deserializer context. + public virtual void ApplyNestedProperty(object resource, ODataNestedResourceInfoWrapper resourceInfoWrapper, + IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + if (resource == null) + { + throw Error.ArgumentNull("resource"); + } + + if (resourceInfoWrapper == null) + { + throw Error.ArgumentNull("resourceInfoWrapper"); + } + + IEdmProperty edmProperty = structuredType.FindProperty(resourceInfoWrapper.NestedResourceInfo.Name); + if (edmProperty == null) + { + if (!structuredType.IsOpen()) + { + throw new ODataException( + Error.Format(SRResources.NestedPropertyNotfound, resourceInfoWrapper.NestedResourceInfo.Name, + structuredType.FullName())); + } + } + + foreach (ODataItemBase childItem in resourceInfoWrapper.NestedItems) + { + // it maybe null. + if (childItem == null) + { + if (edmProperty == null) + { + // for the dynamic, OData.net has a bug. see https://github.com/OData/odata.net/issues/977 + ApplyDynamicResourceInNestedProperty(resourceInfoWrapper.NestedResourceInfo.Name, resource, + structuredType, null, readContext); + } + else + { + ApplyResourceInNestedProperty(edmProperty, resource, null, readContext); + } + } + + ODataEntityReferenceLinkBase entityReferenceLink = childItem as ODataEntityReferenceLinkBase; + if (entityReferenceLink != null) + { + // ignore entity reference links. + continue; + } + + ODataResourceSetWrapper resourceSetWrapper = childItem as ODataResourceSetWrapper; + if (resourceSetWrapper != null) + { + if (edmProperty == null) + { + ApplyDynamicResourceSetInNestedProperty(resourceInfoWrapper.NestedResourceInfo.Name, + resource, structuredType, resourceSetWrapper, readContext); + } + else + { + ApplyResourceSetInNestedProperty(edmProperty, resource, resourceSetWrapper, readContext); + } + + continue; + } + + // It must be resource by now. + ODataResourceWrapper resourceWrapper = (ODataResourceWrapper)childItem; + if (resourceWrapper != null) + { + if (edmProperty == null) + { + ApplyDynamicResourceInNestedProperty(resourceInfoWrapper.NestedResourceInfo.Name, resource, + structuredType, resourceWrapper, readContext); + } + else + { + ApplyResourceInNestedProperty(edmProperty, resource, resourceWrapper, readContext); + } + } + } + } + + /// + /// Deserializes the structural properties from into . + /// + /// The object into which the structural properties should be read. + /// The resource object containing the structural properties. + /// The type of the resource. + /// The deserializer context. + public virtual void ApplyStructuralProperties(object resource, ODataResourceWrapper resourceWrapper, + IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + if (resourceWrapper == null) + { + throw Error.ArgumentNull("resourceWrapper"); + } + + foreach (ODataProperty property in resourceWrapper.Resource.Properties) + { + ApplyStructuralProperty(resource, property, structuredType, readContext); + } + } + + /// + /// Deserializes the given into . + /// + /// The object into which the structural property should be read. + /// The structural property. + /// The type of the resource. + /// The deserializer context. + public virtual void ApplyStructuralProperty(object resource, ODataProperty structuralProperty, + IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + if (resource == null) + { + throw Error.ArgumentNull("resource"); + } + + if (structuralProperty == null) + { + throw Error.ArgumentNull("structuralProperty"); + } + + DeserializationHelpers.ApplyProperty(structuralProperty, structuredType, resource, DeserializerProvider, readContext); + } + + private void ApplyResourceProperties(object resource, ODataResourceWrapper resourceWrapper, + IEdmStructuredTypeReference structuredType, ODataDeserializerContext readContext) + { + ApplyStructuralProperties(resource, resourceWrapper, structuredType, readContext); + ApplyNestedProperties(resource, resourceWrapper, structuredType, readContext); + } + + private void ApplyResourceInNestedProperty(IEdmProperty nestedProperty, object resource, + ODataResourceWrapper resourceWrapper, ODataDeserializerContext readContext) + { + Contract.Assert(nestedProperty != null); + Contract.Assert(resource != null); + Contract.Assert(readContext != null); + + if (readContext.IsDeltaOfT) + { + IEdmNavigationProperty navigationProperty = nestedProperty as IEdmNavigationProperty; + if (navigationProperty != null) + { + string message = Error.Format(SRResources.CannotPatchNavigationProperties, navigationProperty.Name, + navigationProperty.DeclaringEntityType().FullName()); + throw new ODataException(message); + } + } + + object value = ReadNestedResourceInline(resourceWrapper, nestedProperty.Type, readContext); + + // First resolve Data member alias or annotation, then set the regular + // or delta resource accordingly. + string propertyName = EdmLibHelpers.GetClrPropertyName(nestedProperty, readContext.Model); + + DeserializationHelpers.SetProperty(resource, propertyName, value); + } + + private void ApplyDynamicResourceInNestedProperty(string propertyName, object resource, IEdmStructuredTypeReference resourceStructuredType, + ODataResourceWrapper resourceWrapper, ODataDeserializerContext readContext) + { + Contract.Assert(resource != null); + Contract.Assert(readContext != null); + + object value = null; + if (resourceWrapper != null) + { + IEdmSchemaType elementType = readContext.Model.FindDeclaredType(resourceWrapper.Resource.TypeName); + IEdmTypeReference edmTypeReference = elementType.ToEdmTypeReference(true); + + value = ReadNestedResourceInline(resourceWrapper, edmTypeReference, readContext); + } + + DeserializationHelpers.SetDynamicProperty(resource, propertyName, value, + resourceStructuredType.StructuredDefinition(), readContext.Model); + } + + private object ReadNestedResourceInline(ODataResourceWrapper resourceWrapper, IEdmTypeReference edmType, ODataDeserializerContext readContext) + { + Contract.Assert(edmType != null); + Contract.Assert(readContext != null); + + if (resourceWrapper == null) + { + return null; + } + + ODataEdmTypeDeserializer deserializer = DeserializerProvider.GetEdmTypeDeserializer(edmType); + if (deserializer == null) + { + throw new SerializationException(Error.Format(SRResources.TypeCannotBeDeserialized, edmType.FullName())); + } + + IEdmStructuredTypeReference structuredType = edmType.AsStructured(); + + var nestedReadContext = new ODataDeserializerContext + { + Path = readContext.Path, + Model = readContext.Model, + }; + + Type clrType = null; + if (readContext.IsUntyped) + { + clrType = structuredType.IsEntity() + ? typeof(EdmEntityObject) + : typeof(EdmComplexObject); + } + else + { + clrType = EdmLibHelpers.GetClrType(structuredType, readContext.Model); + + if (clrType == null) + { + throw new ODataException( + Error.Format(SRResources.MappingDoesNotContainResourceType, structuredType.FullName())); + } + } + + nestedReadContext.ResourceType = readContext.IsDeltaOfT + ? typeof(Delta<>).MakeGenericType(clrType) + : clrType; + return deserializer.ReadInline(resourceWrapper, edmType, nestedReadContext); + } + + private void ApplyResourceSetInNestedProperty(IEdmProperty nestedProperty, object resource, + ODataResourceSetWrapper resourceSetWrapper, ODataDeserializerContext readContext) + { + Contract.Assert(nestedProperty != null); + Contract.Assert(resource != null); + Contract.Assert(readContext != null); + + if (readContext.IsDeltaOfT) + { + IEdmNavigationProperty navigationProperty = nestedProperty as IEdmNavigationProperty; + if (navigationProperty != null) + { + string message = Error.Format(SRResources.CannotPatchNavigationProperties, navigationProperty.Name, + navigationProperty.DeclaringEntityType().FullName()); + throw new ODataException(message); + } + } + + object value = ReadNestedResourceSetInline(resourceSetWrapper, nestedProperty.Type, readContext); + + string propertyName = EdmLibHelpers.GetClrPropertyName(nestedProperty, readContext.Model); + DeserializationHelpers.SetCollectionProperty(resource, nestedProperty, value, propertyName); + } + + private void ApplyDynamicResourceSetInNestedProperty(string propertyName, object resource, IEdmStructuredTypeReference structuredType, + ODataResourceSetWrapper resourceSetWrapper, ODataDeserializerContext readContext) + { + Contract.Assert(resource != null); + Contract.Assert(readContext != null); + + if (String.IsNullOrEmpty(resourceSetWrapper.ResourceSet.TypeName)) + { + string message = Error.Format(SRResources.DynamicResourceSetTypeNameIsRequired, propertyName); + throw new ODataException(message); + } + + string elementTypeName = + DeserializationHelpers.GetCollectionElementTypeName(resourceSetWrapper.ResourceSet.TypeName, + isNested: false); + IEdmSchemaType elementType = readContext.Model.FindDeclaredType(elementTypeName); + + IEdmTypeReference edmTypeReference = elementType.ToEdmTypeReference(true); + EdmCollectionTypeReference collectionType = new EdmCollectionTypeReference(new EdmCollectionType(edmTypeReference)); + + ODataEdmTypeDeserializer deserializer = DeserializerProvider.GetEdmTypeDeserializer(collectionType); + if (deserializer == null) + { + throw new SerializationException(Error.Format(SRResources.TypeCannotBeDeserialized, collectionType.FullName())); + } + + IEnumerable value = ReadNestedResourceSetInline(resourceSetWrapper, collectionType, readContext) as IEnumerable; + object result = value; + if (value != null) + { + if (readContext.IsUntyped) + { + result = value.ConvertToEdmObject(collectionType); + } + } + + DeserializationHelpers.SetDynamicProperty(resource, structuredType, EdmTypeKind.Collection, propertyName, + result, collectionType, readContext.Model); + } + + private object ReadNestedResourceSetInline(ODataResourceSetWrapper resourceSetWrapper, IEdmTypeReference edmType, + ODataDeserializerContext readContext) + { + Contract.Assert(resourceSetWrapper != null); + Contract.Assert(edmType != null); + Contract.Assert(readContext != null); + + ODataEdmTypeDeserializer deserializer = DeserializerProvider.GetEdmTypeDeserializer(edmType); + if (deserializer == null) + { + throw new SerializationException(Error.Format(SRResources.TypeCannotBeDeserialized, edmType.FullName())); + } + + IEdmStructuredTypeReference structuredType = edmType.AsCollection().ElementType().AsStructured(); + var nestedReadContext = new ODataDeserializerContext + { + Path = readContext.Path, + Model = readContext.Model, + }; + + if (readContext.IsUntyped) + { + if (structuredType.IsEntity()) + { + nestedReadContext.ResourceType = typeof(EdmEntityObjectCollection); + } + else + { + nestedReadContext.ResourceType = typeof(EdmComplexObjectCollection); + } + } + else + { + Type clrType = EdmLibHelpers.GetClrType(structuredType, readContext.Model); + + if (clrType == null) + { + throw new ODataException( + Error.Format(SRResources.MappingDoesNotContainResourceType, structuredType.FullName())); + } + + nestedReadContext.ResourceType = typeof(List<>).MakeGenericType(clrType); + } + + return deserializer.ReadInline(resourceSetWrapper, edmType, nestedReadContext); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetDeserializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetDeserializer.cs new file mode 100644 index 0000000..2c01c19 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetDeserializer.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents an that can read OData resource sets. + /// + public class ODataResourceSetDeserializer : ODataEdmTypeDeserializer + { + private static readonly MethodInfo CastMethodInfo = typeof(Enumerable).GetMethod("Cast"); + + /// + /// Initializes a new instance of the class. + /// + /// The deserializer provider to use to read inner objects. + public ODataResourceSetDeserializer(ODataDeserializerProvider deserializerProvider) + : base(ODataPayloadKind.ResourceSet, deserializerProvider) + { + } + + /// + public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) + { + if (messageReader == null) + { + throw Error.ArgumentNull("messageReader"); + } + + IEdmTypeReference edmType = readContext.GetEdmType(type); + Contract.Assert(edmType != null); + + // TODO: is it ok to read the top level collection of entity? + if (!(edmType.IsCollection() && edmType.AsCollection().ElementType().IsStructured())) + { + throw Error.Argument("edmType", SRResources.ArgumentMustBeOfType, EdmTypeKind.Complex + " or " + EdmTypeKind.Entity); + } + + ODataReader resourceSetReader = messageReader.CreateODataResourceSetReader(); + object resourceSet = resourceSetReader.ReadResourceOrResourceSet(); + return ReadInline(resourceSet, edmType, readContext); + } + + /// + public sealed override object ReadInline(object item, IEdmTypeReference edmType, ODataDeserializerContext readContext) + { + if (item == null) + { + return null; + } + + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + if (!edmType.IsCollection() || !edmType.AsCollection().ElementType().IsStructured()) + { + throw Error.Argument("edmType", SRResources.TypeMustBeResourceSet, edmType.ToTraceString()); + } + + ODataResourceSetWrapper resourceSet = item as ODataResourceSetWrapper; + if (resourceSet == null) + { + throw Error.Argument("item", SRResources.ArgumentMustBeOfType, typeof(ODataResourceSetWrapper).Name); + } + + // Recursion guard to avoid stack overflows + RuntimeHelpers.EnsureSufficientExecutionStack(); + + IEdmStructuredTypeReference elementType = edmType.AsCollection().ElementType().AsStructured(); + + IEnumerable result = ReadResourceSet(resourceSet, elementType, readContext); + if (result != null && elementType.IsComplex()) + { + if (readContext.IsUntyped) + { + EdmComplexObjectCollection complexCollection = new EdmComplexObjectCollection(edmType.AsCollection()); + foreach (EdmComplexObject complexObject in result) + { + complexCollection.Add(complexObject); + } + return complexCollection; + } + else + { + Type elementClrType = EdmLibHelpers.GetClrType(elementType, readContext.Model); + IEnumerable castedResult = + CastMethodInfo.MakeGenericMethod(elementClrType).Invoke(null, new object[] { result }) as + IEnumerable; + return castedResult; + } + } + else + { + return result; + } + } + + /// + /// Deserializes the given under the given . + /// + /// The resource set to deserialize. + /// The deserializer context. + /// The element type of the resource set being read. + /// The deserialized resource set object. + public virtual IEnumerable ReadResourceSet(ODataResourceSetWrapper resourceSet, IEdmStructuredTypeReference elementType, ODataDeserializerContext readContext) + { + ODataEdmTypeDeserializer deserializer = DeserializerProvider.GetEdmTypeDeserializer(elementType); + if (deserializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeDeserialized, elementType.FullName())); + } + + foreach (ODataResourceWrapper resourceWrapper in resourceSet.Resources) + { + yield return deserializer.ReadInline(resourceWrapper, elementType, readContext); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetWrapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetWrapper.cs new file mode 100644 index 0000000..7c7e4b8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceSetWrapper.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Encapsulates an and the 's that are part of it. + /// + public sealed class ODataResourceSetWrapper : ODataItemBase + { + /// + /// Initializes a new instance of . + /// + /// The wrapped item. + public ODataResourceSetWrapper(ODataResourceSet item) + : base(item) + { + Resources = new List(); + } + + /// + /// Gets the wrapped . + /// + public ODataResourceSet ResourceSet + { + get + { + return Item as ODataResourceSet; + } + } + + /// + /// Gets the nested resources of this ResourceSet. + /// + public IList Resources { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceWrapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceWrapper.cs new file mode 100644 index 0000000..f78be99 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataResourceWrapper.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Encapsulates an and the inner nested resource infos. + /// + public sealed class ODataResourceWrapper : ODataItemBase + { + /// + /// Initializes a new instance of . + /// + /// The wrapped item. + public ODataResourceWrapper(ODataResource item) + : base(item) + { + NestedResourceInfos = new List(); + } + + /// + /// Gets the wrapped . + /// + public ODataResource Resource + { + get + { + return Item as ODataResource; + } + } + + /// + /// Gets the inner nested resource infos. + /// + public IList NestedResourceInfos { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETag.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETag.cs new file mode 100644 index 0000000..fb77643 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETag.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Expressions; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// The ETag parsed from request. + /// + public class ETag : DynamicObject + { + private IDictionary _concurrencyProperties = new Dictionary(); + + /// + /// Create an instance of . + /// + public ETag() + { + IsWellFormed = true; + } + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + public object this[string key] + { + get + { + if (!IsWellFormed) + { + throw Error.InvalidOperation(SRResources.ETagNotWellFormed); + } + return ConcurrencyProperties[key]; + } + set + { + ConcurrencyProperties[key] = value; + } + } + + /// + /// Gets or sets whether the ETag is well-formed. + /// + public bool IsWellFormed { get; set; } + + /// + /// Gets or sets an entity type of the ETag. + /// + public Type EntityType { get; set; } + + /// + /// Gets or sets whether the ETag is corresponding to "*". + /// + public bool IsAny { get; set; } + + /// + /// Gets or sets whether If-None-Match set in the request header. + /// + public bool IsIfNoneMatch { get; set; } + + internal IDictionary ConcurrencyProperties + { + get + { + return _concurrencyProperties; + } + set + { + _concurrencyProperties = value; + } + } + + /// + /// Gets a property value from the ETag. + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (binder == null) + { + throw Error.ArgumentNull("binder"); + } + + if (!IsWellFormed) + { + throw Error.InvalidOperation(SRResources.ETagNotWellFormed); + } + + string name = binder.Name; + return ConcurrencyProperties.TryGetValue(name, out result); + } + + /// + /// Sets a property value to ETag. + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (binder == null) + { + throw Error.ArgumentNull("binder"); + } + + ConcurrencyProperties[binder.Name] = value; + return true; + } + + /// + /// Apply the ETag to the given IQueryable. + /// + /// The original . + /// The new after the ETag has been applied to. + public virtual IQueryable ApplyTo(IQueryable query) + { + if (IsAny) + { + return query; + } + + Type type = EntityType; + ParameterExpression param = Expression.Parameter(type); + Expression where = null; + foreach (KeyValuePair item in ConcurrencyProperties) + { + MemberExpression name = Expression.Property(param, item.Key); + object itemValue = item.Value; + Expression value = itemValue != null + ? LinqParameterContainer.Parameterize(itemValue.GetType(), itemValue) + : Expression.Constant(value: null); + BinaryExpression equal = Expression.Equal(name, value); + where = where == null ? equal : Expression.AndAlso(where, equal); + } + + if (where == null) + { + return query; + } + + if (IsIfNoneMatch) + { + where = Expression.Not(where); + } + + Expression whereLambda = Expression.Lambda(where, param); + return ExpressionHelpers.Where(query, whereLambda, type); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETagOfTEntity.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETagOfTEntity.cs new file mode 100644 index 0000000..7385e9c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ETagOfTEntity.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// OData ETag of an entity type . + /// + /// TEntity is the type of entity. + public class ETag : ETag + { + /// + /// Creates an instance of . + /// + public ETag() + { + EntityType = typeof(TEntity); + } + + /// + public override IQueryable ApplyTo(IQueryable query) + { + ValidateQuery(query); + return base.ApplyTo(query); + } + + /// + /// Apply the ETag to the given . + /// + /// The original . + /// The new after the ETag has been applied. + public IQueryable ApplyTo(IQueryable query) + { + if (query == null) + { + throw Error.ArgumentNull("query"); + } + + return (IQueryable)base.ApplyTo(query); + } + + private static void ValidateQuery(IQueryable query) + { + if (query == null) + { + throw Error.ArgumentNull("query"); + } + + if (!TypeHelper.IsTypeAssignableFrom(typeof(TEntity), query.ElementType)) + { + throw Error.Argument( + "query", + SRResources.CannotApplyETagOfT, + typeof(ETag).Name, + typeof(TEntity).FullName, + typeof(IQueryable).Name, + query.ElementType.FullName); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs new file mode 100644 index 0000000..f00f98d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs @@ -0,0 +1,1063 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. +using System.Data.Linq; +#endif +using System.Diagnostics.Contracts; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; +using Microsoft.OData.UriParser; +using Microsoft.Spatial; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal static class EdmLibHelpers + { + private static readonly EdmCoreModel _coreModel = EdmCoreModel.Instance; + + private static readonly Dictionary _builtInTypesMapping = + new[] + { + new KeyValuePair(typeof(string), GetPrimitiveType(EdmPrimitiveTypeKind.String)), + new KeyValuePair(typeof(bool), GetPrimitiveType(EdmPrimitiveTypeKind.Boolean)), + new KeyValuePair(typeof(bool?), GetPrimitiveType(EdmPrimitiveTypeKind.Boolean)), + new KeyValuePair(typeof(byte), GetPrimitiveType(EdmPrimitiveTypeKind.Byte)), + new KeyValuePair(typeof(byte?), GetPrimitiveType(EdmPrimitiveTypeKind.Byte)), + new KeyValuePair(typeof(decimal), GetPrimitiveType(EdmPrimitiveTypeKind.Decimal)), + new KeyValuePair(typeof(decimal?), GetPrimitiveType(EdmPrimitiveTypeKind.Decimal)), + new KeyValuePair(typeof(double), GetPrimitiveType(EdmPrimitiveTypeKind.Double)), + new KeyValuePair(typeof(double?), GetPrimitiveType(EdmPrimitiveTypeKind.Double)), + new KeyValuePair(typeof(Guid), GetPrimitiveType(EdmPrimitiveTypeKind.Guid)), + new KeyValuePair(typeof(Guid?), GetPrimitiveType(EdmPrimitiveTypeKind.Guid)), + new KeyValuePair(typeof(short), GetPrimitiveType(EdmPrimitiveTypeKind.Int16)), + new KeyValuePair(typeof(short?), GetPrimitiveType(EdmPrimitiveTypeKind.Int16)), + new KeyValuePair(typeof(int), GetPrimitiveType(EdmPrimitiveTypeKind.Int32)), + new KeyValuePair(typeof(int?), GetPrimitiveType(EdmPrimitiveTypeKind.Int32)), + new KeyValuePair(typeof(long), GetPrimitiveType(EdmPrimitiveTypeKind.Int64)), + new KeyValuePair(typeof(long?), GetPrimitiveType(EdmPrimitiveTypeKind.Int64)), + new KeyValuePair(typeof(sbyte), GetPrimitiveType(EdmPrimitiveTypeKind.SByte)), + new KeyValuePair(typeof(sbyte?), GetPrimitiveType(EdmPrimitiveTypeKind.SByte)), + new KeyValuePair(typeof(float), GetPrimitiveType(EdmPrimitiveTypeKind.Single)), + new KeyValuePair(typeof(float?), GetPrimitiveType(EdmPrimitiveTypeKind.Single)), + new KeyValuePair(typeof(byte[]), GetPrimitiveType(EdmPrimitiveTypeKind.Binary)), + new KeyValuePair(typeof(Stream), GetPrimitiveType(EdmPrimitiveTypeKind.Stream)), + new KeyValuePair(typeof(Geography), GetPrimitiveType(EdmPrimitiveTypeKind.Geography)), + new KeyValuePair(typeof(GeographyPoint), GetPrimitiveType(EdmPrimitiveTypeKind.GeographyPoint)), + new KeyValuePair(typeof(GeographyLineString), GetPrimitiveType(EdmPrimitiveTypeKind.GeographyLineString)), + new KeyValuePair(typeof(GeographyPolygon), GetPrimitiveType(EdmPrimitiveTypeKind.GeographyPolygon)), + new KeyValuePair(typeof(GeographyCollection), GetPrimitiveType(EdmPrimitiveTypeKind.GeographyCollection)), + new KeyValuePair(typeof(GeographyMultiLineString), GetPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiLineString)), + new KeyValuePair(typeof(GeographyMultiPoint), GetPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiPoint)), + new KeyValuePair(typeof(GeographyMultiPolygon), GetPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiPolygon)), + new KeyValuePair(typeof(Geometry), GetPrimitiveType(EdmPrimitiveTypeKind.Geometry)), + new KeyValuePair(typeof(GeometryPoint), GetPrimitiveType(EdmPrimitiveTypeKind.GeometryPoint)), + new KeyValuePair(typeof(GeometryLineString), GetPrimitiveType(EdmPrimitiveTypeKind.GeometryLineString)), + new KeyValuePair(typeof(GeometryPolygon), GetPrimitiveType(EdmPrimitiveTypeKind.GeometryPolygon)), + new KeyValuePair(typeof(GeometryCollection), GetPrimitiveType(EdmPrimitiveTypeKind.GeometryCollection)), + new KeyValuePair(typeof(GeometryMultiLineString), GetPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiLineString)), + new KeyValuePair(typeof(GeometryMultiPoint), GetPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiPoint)), + new KeyValuePair(typeof(GeometryMultiPolygon), GetPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiPolygon)), + new KeyValuePair(typeof(DateTimeOffset), GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset)), + new KeyValuePair(typeof(DateTimeOffset?), GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset)), + new KeyValuePair(typeof(TimeSpan), GetPrimitiveType(EdmPrimitiveTypeKind.Duration)), + new KeyValuePair(typeof(TimeSpan?), GetPrimitiveType(EdmPrimitiveTypeKind.Duration)), + new KeyValuePair(typeof(Date), GetPrimitiveType(EdmPrimitiveTypeKind.Date)), + new KeyValuePair(typeof(Date?), GetPrimitiveType(EdmPrimitiveTypeKind.Date)), + new KeyValuePair(typeof(TimeOfDay), GetPrimitiveType(EdmPrimitiveTypeKind.TimeOfDay)), + new KeyValuePair(typeof(TimeOfDay?), GetPrimitiveType(EdmPrimitiveTypeKind.TimeOfDay)), + + // Keep the Binary and XElement in the end, since there are not the default mappings for Edm.Binary and Edm.String. + new KeyValuePair(typeof(XElement), GetPrimitiveType(EdmPrimitiveTypeKind.String)), +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. + new KeyValuePair(typeof(Binary), GetPrimitiveType(EdmPrimitiveTypeKind.Binary)), +#endif + new KeyValuePair(typeof(ushort), GetPrimitiveType(EdmPrimitiveTypeKind.Int32)), + new KeyValuePair(typeof(ushort?), GetPrimitiveType(EdmPrimitiveTypeKind.Int32)), + new KeyValuePair(typeof(uint), GetPrimitiveType(EdmPrimitiveTypeKind.Int64)), + new KeyValuePair(typeof(uint?), GetPrimitiveType(EdmPrimitiveTypeKind.Int64)), + new KeyValuePair(typeof(ulong), GetPrimitiveType(EdmPrimitiveTypeKind.Int64)), + new KeyValuePair(typeof(ulong?), GetPrimitiveType(EdmPrimitiveTypeKind.Int64)), + new KeyValuePair(typeof(char[]), GetPrimitiveType(EdmPrimitiveTypeKind.String)), + new KeyValuePair(typeof(char), GetPrimitiveType(EdmPrimitiveTypeKind.String)), + new KeyValuePair(typeof(char?), GetPrimitiveType(EdmPrimitiveTypeKind.String)), + new KeyValuePair(typeof(DateTime), GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset)), + new KeyValuePair(typeof(DateTime?), GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset)), + } + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + public static IEdmType GetEdmType(this IEdmModel edmModel, Type clrType) + { + if (edmModel == null) + { + throw Error.ArgumentNull("edmModel"); + } + + if (clrType == null) + { + throw Error.ArgumentNull("clrType"); + } + + return GetEdmType(edmModel, clrType, testCollections: true); + } + + private static IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollections) + { + Contract.Assert(edmModel != null); + Contract.Assert(clrType != null); + + IEdmPrimitiveType primitiveType = GetEdmPrimitiveTypeOrNull(clrType); + if (primitiveType != null) + { + return primitiveType; + } + else + { + if (testCollections) + { + Type enumerableOfT = ExtractGenericInterface(clrType, typeof(IEnumerable<>)); + if (enumerableOfT != null) + { + Type elementClrType = enumerableOfT.GetGenericArguments()[0]; + + // IEnumerable> is a collection of T. + Type entityType; + if (IsSelectExpandWrapper(elementClrType, out entityType)) + { + elementClrType = entityType; + } + + IEdmType elementType = GetEdmType(edmModel, elementClrType, testCollections: false); + if (elementType != null) + { + return new EdmCollectionType(elementType.ToEdmTypeReference(IsNullable(elementClrType))); + } + } + } + + Type underlyingType = TypeHelper.GetUnderlyingTypeOrSelf(clrType); + if (TypeHelper.IsEnum(underlyingType)) + { + clrType = underlyingType; + } + + // search for the ClrTypeAnnotation and return it if present + IEdmType returnType = + edmModel + .SchemaElements + .OfType() + .Select(edmType => new { EdmType = edmType, Annotation = edmModel.GetAnnotationValue(edmType) }) + .Where(tuple => tuple.Annotation != null && tuple.Annotation.ClrType == clrType) + .Select(tuple => tuple.EdmType) + .SingleOrDefault(); + + // default to the EdmType with the same name as the ClrType name + returnType = returnType ?? edmModel.FindType(clrType.EdmFullName()); + + if (TypeHelper.GetBaseType(clrType) != null) + { + // go up the inheritance tree to see if we have a mapping defined for the base type. + returnType = returnType ?? GetEdmType(edmModel, TypeHelper.GetBaseType(clrType), testCollections); + } + return returnType; + } + } + + public static IEdmTypeReference GetEdmTypeReference(this IEdmModel edmModel, Type clrType) + { + IEdmType edmType = edmModel.GetEdmType(clrType); + if (edmType != null) + { + bool isNullable = IsNullable(clrType); + return ToEdmTypeReference(edmType, isNullable); + } + + return null; + } + + public static IEdmTypeReference ToEdmTypeReference(this IEdmType edmType, bool isNullable) + { + Contract.Assert(edmType != null); + + switch (edmType.TypeKind) + { + case EdmTypeKind.Collection: + return new EdmCollectionTypeReference(edmType as IEdmCollectionType); + case EdmTypeKind.Complex: + return new EdmComplexTypeReference(edmType as IEdmComplexType, isNullable); + case EdmTypeKind.Entity: + return new EdmEntityTypeReference(edmType as IEdmEntityType, isNullable); + case EdmTypeKind.EntityReference: + return new EdmEntityReferenceTypeReference(edmType as IEdmEntityReferenceType, isNullable); + case EdmTypeKind.Enum: + return new EdmEnumTypeReference(edmType as IEdmEnumType, isNullable); + case EdmTypeKind.Primitive: + return _coreModel.GetPrimitive((edmType as IEdmPrimitiveType).PrimitiveKind, isNullable); + default: + throw Error.NotSupported(SRResources.EdmTypeNotSupported, edmType.ToTraceString()); + } + } + + public static IEdmCollectionType GetCollection(this IEdmEntityType entityType) + { + return new EdmCollectionType(new EdmEntityTypeReference(entityType, isNullable: false)); + } + + public static Type GetClrType(IEdmTypeReference edmTypeReference, IEdmModel edmModel) + { + return GetClrType(edmTypeReference, edmModel, WebApiAssembliesResolver.Default); + } + + public static Type GetClrType(IEdmTypeReference edmTypeReference, IEdmModel edmModel, IWebApiAssembliesResolver assembliesResolver) + { + if (edmTypeReference == null) + { + throw Error.ArgumentNull("edmTypeReference"); + } + + Type primitiveClrType = _builtInTypesMapping + .Where(kvp => edmTypeReference.Definition.IsEquivalentTo(kvp.Value) && (!edmTypeReference.IsNullable || IsNullable(kvp.Key))) + .Select(kvp => kvp.Key) + .FirstOrDefault(); + + if (primitiveClrType != null) + { + return primitiveClrType; + } + else + { + Type clrType = GetClrType(edmTypeReference.Definition, edmModel, assembliesResolver); + if (clrType != null && TypeHelper.IsEnum(clrType) && edmTypeReference.IsNullable) + { + return TypeHelper.ToNullable(clrType); + } + + return clrType; + } + } + + public static Type GetClrType(IEdmType edmType, IEdmModel edmModel) + { + return GetClrType(edmType, edmModel, WebApiAssembliesResolver.Default); + } + + public static Type GetClrType(IEdmType edmType, IEdmModel edmModel, IWebApiAssembliesResolver assembliesResolver) + { + IEdmSchemaType edmSchemaType = edmType as IEdmSchemaType; + + Contract.Assert(edmSchemaType != null); + + ClrTypeAnnotation annotation = edmModel.GetAnnotationValue(edmSchemaType); + if (annotation != null) + { + return annotation.ClrType; + } + + string typeName = edmSchemaType.FullName(); + IEnumerable matchingTypes = GetMatchingTypes(typeName, assembliesResolver); + + if (matchingTypes.Count() > 1) + { + throw Error.Argument("edmTypeReference", SRResources.MultipleMatchingClrTypesForEdmType, + typeName, String.Join(",", matchingTypes.Select(type => type.AssemblyQualifiedName))); + } + + edmModel.SetAnnotationValue(edmSchemaType, new ClrTypeAnnotation(matchingTypes.SingleOrDefault())); + + return matchingTypes.SingleOrDefault(); + } + + public static bool IsNotFilterable(IEdmProperty edmProperty, IEdmProperty pathEdmProperty, + IEdmStructuredType pathEdmStructuredType, + IEdmModel edmModel, bool enableFilter) + { + QueryableRestrictionsAnnotation annotation = GetPropertyRestrictions(edmProperty, edmModel); + if (annotation != null && annotation.Restrictions.NotFilterable) + { + return true; + } + else + { + if (pathEdmStructuredType == null) + { + pathEdmStructuredType = edmProperty.DeclaringType; + } + + ModelBoundQuerySettings querySettings = GetModelBoundQuerySettings(pathEdmProperty, + pathEdmStructuredType, edmModel); + if (!enableFilter) + { + return !querySettings.Filterable(edmProperty.Name); + } + + bool enable; + if (querySettings.FilterConfigurations.TryGetValue(edmProperty.Name, out enable)) + { + return !enable; + } + else + { + return querySettings.DefaultEnableFilter == false; + } + } + } + + public static bool IsNotSortable(IEdmProperty edmProperty, IEdmProperty pathEdmProperty, + IEdmStructuredType pathEdmStructuredType, IEdmModel edmModel, bool enableOrderBy) + { + QueryableRestrictionsAnnotation annotation = GetPropertyRestrictions(edmProperty, edmModel); + if (annotation != null && annotation.Restrictions.NotSortable) + { + return true; + } + else + { + if (pathEdmStructuredType == null) + { + pathEdmStructuredType = edmProperty.DeclaringType; + } + + ModelBoundQuerySettings querySettings = GetModelBoundQuerySettings(pathEdmProperty, + pathEdmStructuredType, edmModel); + if (!enableOrderBy) + { + return !querySettings.Sortable(edmProperty.Name); + } + + bool enable; + if (querySettings.OrderByConfigurations.TryGetValue(edmProperty.Name, out enable)) + { + return !enable; + } + else + { + return querySettings.DefaultEnableOrderBy == false; + } + } + } + + public static bool IsNotSelectable(IEdmProperty edmProperty, IEdmProperty pathEdmProperty, + IEdmStructuredType pathEdmStructuredType, IEdmModel edmModel, bool enableSelect) + { + if (pathEdmStructuredType == null) + { + pathEdmStructuredType = edmProperty.DeclaringType; + } + + ModelBoundQuerySettings querySettings = GetModelBoundQuerySettings(pathEdmProperty, + pathEdmStructuredType, edmModel); + if (!enableSelect) + { + return !querySettings.Selectable(edmProperty.Name); + } + + SelectExpandType enable; + if (querySettings.SelectConfigurations.TryGetValue(edmProperty.Name, out enable)) + { + return enable == SelectExpandType.Disabled; + } + else + { + return querySettings.DefaultSelectType == SelectExpandType.Disabled; + } + } + + public static bool IsNotNavigable(IEdmProperty edmProperty, IEdmModel edmModel) + { + QueryableRestrictionsAnnotation annotation = GetPropertyRestrictions(edmProperty, edmModel); + return annotation == null ? false : annotation.Restrictions.NotNavigable; + } + + public static bool IsNotExpandable(IEdmProperty edmProperty, IEdmModel edmModel) + { + QueryableRestrictionsAnnotation annotation = GetPropertyRestrictions(edmProperty, edmModel); + return annotation == null ? false : annotation.Restrictions.NotExpandable; + } + + public static bool IsAutoSelect(IEdmProperty property, IEdmProperty pathProperty, + IEdmStructuredType pathStructuredType, IEdmModel edmModel, ModelBoundQuerySettings querySettings = null) + { + if (querySettings == null) + { + querySettings = GetModelBoundQuerySettings(pathProperty, pathStructuredType, edmModel); + } + + if (querySettings != null && querySettings.IsAutomaticSelect(property.Name)) + { + return true; + } + + return false; + } + + public static bool IsAutoExpand(IEdmProperty navigationProperty, + IEdmProperty pathProperty, IEdmStructuredType pathStructuredType, IEdmModel edmModel, + bool isSelectPresent = false, ModelBoundQuerySettings querySettings = null) + { + QueryableRestrictionsAnnotation annotation = GetPropertyRestrictions(navigationProperty, edmModel); + if (annotation != null && annotation.Restrictions.AutoExpand) + { + return !annotation.Restrictions.DisableAutoExpandWhenSelectIsPresent || !isSelectPresent; + } + + if (querySettings == null) + { + querySettings = GetModelBoundQuerySettings(pathProperty, pathStructuredType, edmModel); + } + + if (querySettings != null && querySettings.IsAutomaticExpand(navigationProperty.Name)) + { + return true; + } + + return false; + } + + public static IEnumerable GetAutoExpandNavigationProperties( + IEdmProperty pathProperty, IEdmStructuredType pathStructuredType, IEdmModel edmModel, + bool isSelectPresent = false, ModelBoundQuerySettings querySettings = null) + { + List autoExpandNavigationProperties = new List(); + IEdmEntityType baseEntityType = pathStructuredType as IEdmEntityType; + if (baseEntityType != null) + { + List entityTypes = new List(); + entityTypes.Add(baseEntityType); + entityTypes.AddRange(GetAllDerivedEntityTypes(baseEntityType, edmModel)); + foreach (var entityType in entityTypes) + { + IEnumerable navigationProperties = entityType == baseEntityType + ? entityType.NavigationProperties() + : entityType.DeclaredNavigationProperties(); + + if (navigationProperties != null) + { + autoExpandNavigationProperties.AddRange( + navigationProperties.Where( + navigationProperty => + IsAutoExpand(navigationProperty, pathProperty, entityType, edmModel, + isSelectPresent, querySettings))); + } + } + } + + return autoExpandNavigationProperties; + } + + public static IEnumerable GetAutoSelectProperties( + IEdmProperty pathProperty, + IEdmStructuredType pathStructuredType, + IEdmModel edmModel, + ModelBoundQuerySettings querySettings = null) + { + List autoSelectProperties = new List(); + IEdmEntityType baseEntityType = pathStructuredType as IEdmEntityType; + if (baseEntityType != null) + { + List entityTypes = new List(); + entityTypes.Add(baseEntityType); + entityTypes.AddRange(GetAllDerivedEntityTypes(baseEntityType, edmModel)); + foreach (var entityType in entityTypes) + { + IEnumerable properties = entityType == baseEntityType + ? entityType.StructuralProperties() + : entityType.DeclaredStructuralProperties(); + if (properties != null) + { + autoSelectProperties.AddRange( + properties.Where( + property => + IsAutoSelect(property, pathProperty, entityType, edmModel, + querySettings))); + } + } + } + else if (pathStructuredType != null) + { + IEnumerable properties = pathStructuredType.StructuralProperties(); + if (properties != null) + { + autoSelectProperties.AddRange( + properties.Where( + property => + IsAutoSelect(property, pathProperty, pathStructuredType, edmModel, + querySettings))); + } + } + + return autoSelectProperties; + } + + public static bool IsTopLimitExceeded(IEdmProperty property, IEdmStructuredType structuredType, + IEdmModel edmModel, int top, DefaultQuerySettings defaultQuerySettings, out int maxTop) + { + maxTop = 0; + ModelBoundQuerySettings querySettings = GetModelBoundQuerySettings(property, structuredType, edmModel, + defaultQuerySettings); + if (querySettings != null && top > querySettings.MaxTop) + { + maxTop = querySettings.MaxTop.Value; + return true; + } + return false; + } + + public static bool IsNotCountable(IEdmProperty property, IEdmStructuredType structuredType, IEdmModel edmModel, + bool enableCount) + { + if (property != null) + { + QueryableRestrictionsAnnotation annotation = GetPropertyRestrictions(property, edmModel); + if (annotation != null && annotation.Restrictions.NotCountable) + { + return true; + } + } + + ModelBoundQuerySettings querySettings = GetModelBoundQuerySettings(property, structuredType, edmModel); + if (querySettings != null && + ((!querySettings.Countable.HasValue && !enableCount) || + querySettings.Countable == false)) + { + return true; + } + + return false; + } + + public static bool IsExpandable(string propertyName, IEdmProperty property, IEdmStructuredType structuredType, + IEdmModel edmModel, + out ExpandConfiguration expandConfiguration) + { + expandConfiguration = null; + ModelBoundQuerySettings querySettings = GetModelBoundQuerySettings(property, structuredType, edmModel); + if (querySettings != null) + { + bool result = querySettings.Expandable(propertyName); + if (!querySettings.ExpandConfigurations.TryGetValue(propertyName, out expandConfiguration) && result) + { + expandConfiguration = new ExpandConfiguration + { + ExpandType = querySettings.DefaultExpandType.Value, + MaxDepth = querySettings.DefaultMaxDepth + }; + } + + return result; + } + + return false; + } + + public static ModelBoundQuerySettings GetModelBoundQuerySettings(IEdmProperty property, + IEdmStructuredType structuredType, IEdmModel edmModel, DefaultQuerySettings defaultQuerySettings = null) + { + Contract.Assert(edmModel != null); + + ModelBoundQuerySettings querySettings = GetModelBoundQuerySettings(structuredType, edmModel, + defaultQuerySettings); + if (property == null) + { + return querySettings; + } + else + { + ModelBoundQuerySettings propertyQuerySettings = GetModelBoundQuerySettings(property, edmModel, + defaultQuerySettings); + return GetMergedPropertyQuerySettings(propertyQuerySettings, + querySettings); + } + } + + public static IEnumerable GetAllDerivedEntityTypes( + IEdmEntityType entityType, IEdmModel edmModel) + { + List derivedEntityTypes = new List(); + if (entityType != null) + { + List typeList = new List(); + typeList.Add(entityType); + while (typeList.Count > 0) + { + var head = typeList[0]; + derivedEntityTypes.Add(head as IEdmEntityType); + var derivedTypes = edmModel.FindDirectlyDerivedTypes(head); + if (derivedTypes != null) + { + typeList.AddRange(derivedTypes); + } + + typeList.RemoveAt(0); + } + } + + derivedEntityTypes.RemoveAt(0); + return derivedEntityTypes; + } + + public static IEdmType GetElementType(IEdmTypeReference edmTypeReference) + { + if (edmTypeReference.IsCollection()) + { + return edmTypeReference.AsCollection().ElementType().Definition; + } + + return edmTypeReference.Definition; + } + + public static void GetPropertyAndStructuredTypeFromPath(IEnumerable segments, + out IEdmProperty property, out IEdmStructuredType structuredType, out string name) + { + property = null; + structuredType = null; + name = String.Empty; + string typeCast = String.Empty; + if (segments != null) + { + IEnumerable reverseSegments = segments.Reverse(); + foreach (var segment in reverseSegments) + { + NavigationPropertySegment navigationPathSegment = segment as NavigationPropertySegment; + if (navigationPathSegment != null) + { + property = navigationPathSegment.NavigationProperty; + if (structuredType == null) + { + structuredType = navigationPathSegment.NavigationProperty.ToEntityType(); + } + + name = navigationPathSegment.NavigationProperty.Name + typeCast; + return; + } + + PropertySegment propertyAccessPathSegment = segment as PropertySegment; + if (propertyAccessPathSegment != null) + { + property = propertyAccessPathSegment.Property; + if (structuredType == null) + { + structuredType = GetElementType(property.Type) as IEdmStructuredType; + } + name = property.Name + typeCast; + return; + } + + EntitySetSegment entitySetSegment = segment as EntitySetSegment; + if (entitySetSegment != null) + { + if (structuredType == null) + { + structuredType = entitySetSegment.EntitySet.EntityType(); + } + name = entitySetSegment.EntitySet.Name + typeCast; + return; + } + + TypeSegment typeSegment = segment as TypeSegment; + if (typeSegment != null) + { + structuredType = GetElementType(typeSegment.EdmType.ToEdmTypeReference(false)) as IEdmStructuredType; + typeCast = "/" + structuredType; + } + } + } + } + + public static string GetClrPropertyName(IEdmProperty edmProperty, IEdmModel edmModel) + { + if (edmProperty == null) + { + throw Error.ArgumentNull("edmProperty"); + } + + if (edmModel == null) + { + throw Error.ArgumentNull("edmModel"); + } + + string propertyName = edmProperty.Name; + ClrPropertyInfoAnnotation annotation = edmModel.GetAnnotationValue(edmProperty); + if (annotation != null) + { + PropertyInfo propertyInfo = annotation.ClrPropertyInfo; + if (propertyInfo != null) + { + propertyName = propertyInfo.Name; + } + } + + return propertyName; + } + + public static ClrEnumMemberAnnotation GetClrEnumMemberAnnotation(this IEdmModel edmModel, IEdmEnumType enumType) + { + if (edmModel == null) + { + throw Error.ArgumentNull("edmModel"); + } + + ClrEnumMemberAnnotation annotation = edmModel.GetAnnotationValue(enumType); + if (annotation != null) + { + return annotation; + } + + return null; + } + + public static PropertyInfo GetDynamicPropertyDictionary(IEdmStructuredType edmType, IEdmModel edmModel) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + if (edmModel == null) + { + throw Error.ArgumentNull("edmModel"); + } + + DynamicPropertyDictionaryAnnotation annotation = + edmModel.GetAnnotationValue(edmType); + if (annotation != null) + { + return annotation.PropertyInfo; + } + + return null; + } + + public static bool HasLength(EdmPrimitiveTypeKind primitiveTypeKind) + { + return (primitiveTypeKind == EdmPrimitiveTypeKind.Binary || + primitiveTypeKind == EdmPrimitiveTypeKind.String); + } + + public static bool HasPrecision(EdmPrimitiveTypeKind primitiveTypeKind) + { + return (primitiveTypeKind == EdmPrimitiveTypeKind.Decimal || + primitiveTypeKind == EdmPrimitiveTypeKind.DateTimeOffset || + primitiveTypeKind == EdmPrimitiveTypeKind.Duration || + primitiveTypeKind == EdmPrimitiveTypeKind.TimeOfDay); + } + + public static IEdmPrimitiveType GetEdmPrimitiveTypeOrNull(Type clrType) + { + IEdmPrimitiveType primitiveType; + return _builtInTypesMapping.TryGetValue(clrType, out primitiveType) ? primitiveType : null; + } + + public static IEdmPrimitiveTypeReference GetEdmPrimitiveTypeReferenceOrNull(Type clrType) + { + IEdmPrimitiveType primitiveType = GetEdmPrimitiveTypeOrNull(clrType); + return primitiveType != null ? _coreModel.GetPrimitive(primitiveType.PrimitiveKind, IsNullable(clrType)) : null; + } + + // figures out if the given clr type is nonstandard edm primitive like uint, ushort, char[] etc. + // and returns the corresponding clr type to which we map like uint => long. + public static Type IsNonstandardEdmPrimitive(Type type, out bool isNonstandardEdmPrimitive) + { + IEdmPrimitiveTypeReference edmType = GetEdmPrimitiveTypeReferenceOrNull(type); + if (edmType == null) + { + isNonstandardEdmPrimitive = false; + return type; + } + + Type reverseLookupClrType = GetClrType(edmType, EdmCoreModel.Instance); + isNonstandardEdmPrimitive = (type != reverseLookupClrType); + + return reverseLookupClrType; + } + + // Mangle the invalid EDM literal Type.FullName (System.Collections.Generic.IEnumerable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]) + // to a valid EDM literal (the C# type name IEnumerable). + public static string EdmName(this Type clrType) + { + // We cannot use just Type.Name here as it doesn't work for generic types. + return MangleClrTypeName(clrType); + } + + public static string EdmFullName(this Type clrType) + { + return String.Format(CultureInfo.InvariantCulture, "{0}.{1}", clrType.Namespace, clrType.EdmName()); + } + + public static IEnumerable GetConcurrencyProperties(this IEdmModel model, IEdmNavigationSource navigationSource) + { + Contract.Assert(model != null); + Contract.Assert(navigationSource != null); + + // Ensure that concurrency properties cache is attached to model as an annotation to avoid expensive calculations each time + ConcurrencyPropertiesAnnotation concurrencyProperties = model.GetAnnotationValue(model); + if (concurrencyProperties == null) + { + concurrencyProperties = new ConcurrencyPropertiesAnnotation(); + model.SetAnnotationValue(model, concurrencyProperties); + } + + IEnumerable cachedProperties; + if (concurrencyProperties.TryGetValue(navigationSource, out cachedProperties)) + { + return cachedProperties; + } + + IList results = new List(); + IEdmEntityType entityType = navigationSource.EntityType(); + IEdmVocabularyAnnotatable annotatable = navigationSource as IEdmVocabularyAnnotatable; + if (annotatable != null) + { + var annotations = model.FindVocabularyAnnotations(annotatable, CoreVocabularyModel.ConcurrencyTerm); + IEdmVocabularyAnnotation annotation = annotations.FirstOrDefault(); + if (annotation != null) + { + IEdmCollectionExpression properties = annotation.Value as IEdmCollectionExpression; + if (properties != null) + { + foreach (var property in properties.Elements) + { + IEdmPathExpression pathExpression = property as IEdmPathExpression; + if (pathExpression != null) + { + // So far, we only consider the single path, because only the direct properties from declaring type are used. + // However we have an issue tracking on: https://github.com/OData/WebApi/issues/472 + string propertyName = pathExpression.PathSegments.First(); + IEdmProperty edmProperty = entityType.FindProperty(propertyName); + IEdmStructuralProperty structuralProperty = edmProperty as IEdmStructuralProperty; + if (structuralProperty != null) + { + results.Add(structuralProperty); + } + } + } + } + } + } + + concurrencyProperties[navigationSource] = results; + return results; + } + + public static bool IsDynamicTypeWrapper(Type type) + { + return (type != null && typeof(DynamicTypeWrapper).IsAssignableFrom(type)); + } + + public static bool IsNullable(Type type) + { + return !TypeHelper.IsValueType(type) || Nullable.GetUnderlyingType(type) != null; + } + + /// + /// Get the expected payload type of an OData path. + /// + /// The Type to use. + /// The path to use. + /// The EdmModel to use. + /// The expected payload type of an OData path. + internal static IEdmTypeReference GetExpectedPayloadType(Type type, ODataPath path, IEdmModel model) + { + IEdmTypeReference expectedPayloadType = null; + + if (typeof(IEdmObject).IsAssignableFrom(type)) + { + // typeless mode. figure out the expected payload type from the OData Path. + IEdmType edmType = path.EdmType; + if (edmType != null) + { + expectedPayloadType = EdmLibHelpers.ToEdmTypeReference(edmType, isNullable: false); + if (expectedPayloadType.TypeKind() == EdmTypeKind.Collection) + { + IEdmTypeReference elementType = expectedPayloadType.AsCollection().ElementType(); + if (elementType.IsEntity()) + { + // collection of entities cannot be CREATE/UPDATEd. Instead, the request would contain a single entry. + expectedPayloadType = elementType; + } + } + } + } + else + { + TryGetInnerTypeForDelta(ref type); + expectedPayloadType = model.GetEdmTypeReference(type); + } + + return expectedPayloadType; + } + + /// + /// Try to return the inner type of a generic Delta. + /// + /// in: The type to test; out: inner type of a generic Delta. + /// True if the type was generic Delta; false otherwise. + internal static bool TryGetInnerTypeForDelta(ref Type type) + { + if (TypeHelper.IsGenericType(type) && type.GetGenericTypeDefinition() == typeof(Delta<>)) + { + type = type.GetGenericArguments()[0]; + return true; + } + + return false; + } + + private static ModelBoundQuerySettings GetMergedPropertyQuerySettings( + ModelBoundQuerySettings propertyQuerySettings, ModelBoundQuerySettings propertyTypeQuerySettings) + { + ModelBoundQuerySettings mergedQuerySettings = new ModelBoundQuerySettings(propertyQuerySettings); + if (propertyTypeQuerySettings != null) + { + if (!mergedQuerySettings.PageSize.HasValue) + { + mergedQuerySettings.PageSize = + propertyTypeQuerySettings.PageSize; + } + + if (mergedQuerySettings.MaxTop == 0 && propertyTypeQuerySettings.MaxTop != 0) + { + mergedQuerySettings.MaxTop = + propertyTypeQuerySettings.MaxTop; + } + + if (!mergedQuerySettings.Countable.HasValue) + { + mergedQuerySettings.Countable = propertyTypeQuerySettings.Countable; + } + + if (mergedQuerySettings.OrderByConfigurations.Count == 0 && + !mergedQuerySettings.DefaultEnableOrderBy.HasValue) + { + mergedQuerySettings.CopyOrderByConfigurations(propertyTypeQuerySettings.OrderByConfigurations); + mergedQuerySettings.DefaultEnableOrderBy = propertyTypeQuerySettings.DefaultEnableOrderBy; + } + + if (mergedQuerySettings.FilterConfigurations.Count == 0 && + !mergedQuerySettings.DefaultEnableFilter.HasValue) + { + mergedQuerySettings.CopyFilterConfigurations(propertyTypeQuerySettings.FilterConfigurations); + mergedQuerySettings.DefaultEnableFilter = propertyTypeQuerySettings.DefaultEnableFilter; + } + + if (mergedQuerySettings.SelectConfigurations.Count == 0 && + !mergedQuerySettings.DefaultSelectType.HasValue) + { + mergedQuerySettings.CopySelectConfigurations(propertyTypeQuerySettings.SelectConfigurations); + mergedQuerySettings.DefaultSelectType = propertyTypeQuerySettings.DefaultSelectType; + } + + if (mergedQuerySettings.ExpandConfigurations.Count == 0 && + !mergedQuerySettings.DefaultExpandType.HasValue) + { + mergedQuerySettings.CopyExpandConfigurations( + propertyTypeQuerySettings.ExpandConfigurations); + mergedQuerySettings.DefaultExpandType = propertyTypeQuerySettings.DefaultExpandType; + mergedQuerySettings.DefaultMaxDepth = propertyTypeQuerySettings.DefaultMaxDepth; + } + } + return mergedQuerySettings; + } + + private static ModelBoundQuerySettings GetModelBoundQuerySettings(T key, IEdmModel edmModel, + DefaultQuerySettings defaultQuerySettings = null) + where T : IEdmElement + { + Contract.Assert(edmModel != null); + + if (key == null) + { + return null; + } + else + { + ModelBoundQuerySettings querySettings = edmModel.GetAnnotationValue(key); + if (querySettings == null) + { + querySettings = new ModelBoundQuerySettings(); + if (defaultQuerySettings != null && + (!defaultQuerySettings.MaxTop.HasValue || defaultQuerySettings.MaxTop > 0)) + { + querySettings.MaxTop = defaultQuerySettings.MaxTop; + } + } + return querySettings; + } + } + + private static QueryableRestrictionsAnnotation GetPropertyRestrictions(IEdmProperty edmProperty, + IEdmModel edmModel) + { + Contract.Assert(edmProperty != null); + Contract.Assert(edmModel != null); + + return edmModel.GetAnnotationValue(edmProperty); + } + + private static IEdmPrimitiveType GetPrimitiveType(EdmPrimitiveTypeKind primitiveKind) + { + return _coreModel.GetPrimitiveType(primitiveKind); + } + + private static bool IsSelectExpandWrapper(Type type, out Type entityType) + { + if (type == null) + { + entityType = null; + return false; + } + + if (TypeHelper.IsGenericType(type) && type.GetGenericTypeDefinition() == typeof(SelectExpandWrapper<>)) + { + entityType = type.GetGenericArguments()[0]; + return true; + } + + return IsSelectExpandWrapper(TypeHelper.GetBaseType(type), out entityType); + } + + private static Type ExtractGenericInterface(Type queryType, Type interfaceType) + { + Func matchesInterface = t => TypeHelper.IsGenericType(t) && t.GetGenericTypeDefinition() == interfaceType; + return matchesInterface(queryType) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface); + } + + private static IEnumerable GetMatchingTypes(string edmFullName, IWebApiAssembliesResolver assembliesResolver) + { + return TypeHelper.GetLoadedTypes(assembliesResolver).Where(t => TypeHelper.IsPublic(t) && t.EdmFullName() == edmFullName); + } + + // TODO (workitem 336): Support nested types and anonymous types. + private static string MangleClrTypeName(Type type) + { + Contract.Assert(type != null); + + if (!TypeHelper.IsGenericType(type)) + { + return type.Name; + } + else + { + return String.Format( + CultureInfo.InvariantCulture, + "{0}Of{1}", + type.Name.Replace('`', '_'), + String.Join("_", type.GetGenericArguments().Select(t => MangleClrTypeName(t)))); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmObjectHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmObjectHelper.cs new file mode 100644 index 0000000..bbf0fd9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmObjectHelper.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; +using System.Diagnostics.Contracts; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal static class EdmObjectHelper + { + public static IEdmObject ConvertToEdmObject(this IEnumerable enumerable, IEdmCollectionTypeReference collectionType) + { + Contract.Assert(enumerable != null); + Contract.Assert(collectionType != null); + + IEdmTypeReference elementType = collectionType.ElementType(); + + if (elementType.IsEntity()) + { + EdmEntityObjectCollection entityCollection = + new EdmEntityObjectCollection(collectionType); + + foreach (EdmEntityObject entityObject in enumerable) + { + entityCollection.Add(entityObject); + } + + return entityCollection; + } + else if (elementType.IsComplex()) + { + EdmComplexObjectCollection complexCollection = + new EdmComplexObjectCollection(collectionType); + + foreach (EdmComplexObject complexObject in enumerable) + { + complexCollection.Add(complexObject); + } + + return complexCollection; + } + else if (elementType.IsEnum()) + { + EdmEnumObjectCollection enumCollection = + new EdmEnumObjectCollection(collectionType); + + foreach (EdmEnumObject enumObject in enumerable) + { + enumCollection.Add(enumObject); + } + + return enumCollection; + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmPrimitiveHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmPrimitiveHelpers.cs new file mode 100644 index 0000000..b93c16d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmPrimitiveHelpers.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. +using System.Data.Linq; +#endif +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Xml.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal static class EdmPrimitiveHelpers + { + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These are simple conversion function and cannot be split up.")] + public static object ConvertPrimitiveValue(object value, Type type) + { + Contract.Assert(value != null); + Contract.Assert(type != null); + + // if value is of the same type nothing to do here. + if (value.GetType() == type || value.GetType() == Nullable.GetUnderlyingType(type)) + { + return value; + } + + if (type.IsInstanceOfType(value)) + { + return value; + } + + string str = value as string; + + if (type == typeof(char)) + { + if (str == null || str.Length != 1) + { + throw new ValidationException(Error.Format(SRResources.PropertyMustBeStringLengthOne)); + } + + return str[0]; + } + else if (type == typeof(char?)) + { + if (str == null || str.Length > 1) + { + throw new ValidationException(Error.Format(SRResources.PropertyMustBeStringMaxLengthOne)); + } + + return str.Length > 0 ? str[0] : (char?)null; + } + else if (type == typeof(char[])) + { + if (str == null) + { + throw new ValidationException(Error.Format(SRResources.PropertyMustBeString)); + } + + return str.ToCharArray(); + } +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. + else if (type == typeof(Binary)) + { + return new Binary((byte[])value); + } +#endif + else if (type == typeof(XElement)) + { + if (str == null) + { + throw new ValidationException(Error.Format(SRResources.PropertyMustBeString)); + } + + return XElement.Parse(str); + } + else + { + type = Nullable.GetUnderlyingType(type) ?? type; + if (TypeHelper.IsEnum(type)) + { + if (str == null) + { + throw new ValidationException(Error.Format(SRResources.PropertyMustBeString)); + } + + return Enum.Parse(type, str); + } + else if (type == typeof(DateTime)) + { + if (value is DateTimeOffset) + { + DateTimeOffset dateTimeOffsetValue = (DateTimeOffset)value; + TimeZoneInfo timeZone = TimeZoneInfoHelper.TimeZone; + dateTimeOffsetValue = TimeZoneInfo.ConvertTime(dateTimeOffsetValue, timeZone); + return dateTimeOffsetValue.DateTime; + } + + if (value is Date) + { + Date dt = (Date)value; + return (DateTime)dt; + } + + throw new ValidationException(Error.Format(SRResources.PropertyMustBeDateTimeOffsetOrDate)); + } + else if (type == typeof(TimeSpan)) + { + if (value is TimeOfDay) + { + TimeOfDay tod = (TimeOfDay)value; + return (TimeSpan)tod; + } + + throw new ValidationException(Error.Format(SRResources.PropertyMustBeTimeOfDay)); + } + else if (type == typeof(bool)) + { + bool result; + if (str != null && Boolean.TryParse(str, out result)) + { + return result; + } + + throw new ValidationException(Error.Format(SRResources.PropertyMustBeBoolean)); + } + else + { + Contract.Assert(type == typeof(uint) || type == typeof(ushort) || type == typeof(ulong)); + + // Note that we are not casting the return value to nullable as even if we do it + // CLR would unbox it back to T. + return Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmTypeReferenceEqualityComparer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmTypeReferenceEqualityComparer.cs new file mode 100644 index 0000000..f502275 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/EdmTypeReferenceEqualityComparer.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// An equality comparer for . + /// + internal class EdmTypeReferenceEqualityComparer : IEqualityComparer + { + public bool Equals(IEdmTypeReference x, IEdmTypeReference y) + { + Contract.Assert(x != null); + return x.IsEquivalentTo(y); + } + + public int GetHashCode(IEdmTypeReference obj) + { + Contract.Assert(obj != null); + + string fullName = obj.FullName(); + if (fullName == null) + { + // EdmTypeReferences without an IEdmSchemaElement Definition will all be hashed to 0 + // This is mostly so unit tests don't cause this method to null-ref + return 0; + } + else + { + return fullName.GetHashCode(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/IETagHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/IETagHandler.cs new file mode 100644 index 0000000..4e98d05 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/IETagHandler.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http.Headers; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Exposes the ability to convert a collection of concurrency property names and values into an + /// and parse an into a list of concurrency property values. + /// + public interface IETagHandler + { + /// + /// Creates an ETag from concurrency property names and values. + /// + /// The input property names and values. + /// The generated ETag string. + EntityTagHeaderValue CreateETag(IDictionary properties); + + /// + /// Parses an ETag header value into concurrency property names and values. + /// + /// The ETag header value. + /// Concurrency property names and values. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "ETag is defined in http://www.ietf.org/rfc/rfc2616.txt")] + IDictionary ParseETag(EntityTagHeaderValue etagHeaderValue); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataBinaryValueMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataBinaryValueMediaTypeMapping.cs new file mode 100644 index 0000000..ab02aec --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataBinaryValueMediaTypeMapping.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests for the raw value of binary properties to + /// the application/octet-stream content type. + /// + public class ODataBinaryValueMediaTypeMapping : ODataRawValueMediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + public ODataBinaryValueMediaTypeMapping() + : base("application/octet-stream") + { + } + + /// + protected override bool IsMatch(PropertySegment propertySegment) + { + return propertySegment != null && propertySegment.Property.Type.IsBinary(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataCountMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataCountMediaTypeMapping.cs new file mode 100644 index 0000000..5ff4597 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataCountMediaTypeMapping.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests with $count. + /// + public partial class ODataCountMediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + public ODataCountMediaTypeMapping() + : base("text/plain") + { + } + + internal static bool IsCountRequest(ODataPath path) + { + return path != null && path.Segments.LastOrDefault() is CountSegment; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataEnumValueMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataEnumValueMediaTypeMapping.cs new file mode 100644 index 0000000..3527518 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataEnumValueMediaTypeMapping.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests for the raw value of enum properties with + /// the text/plain content type. + /// + public class ODataEnumValueMediaTypeMapping : ODataRawValueMediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + public ODataEnumValueMediaTypeMapping() + : base("text/plain") + { + } + + /// + protected override bool IsMatch(PropertySegment propertySegment) + { + return propertySegment != null && propertySegment.Property.Type.IsEnum(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs new file mode 100644 index 0000000..62a55f2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataInputFormatterHelper.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal static class ODataInputFormatterHelper + { + internal static bool CanReadType( + Type type, + IEdmModel model, + ODataPath path, + IEnumerable payloadKinds, + Func getEdmTypeDeserializer, + Func getODataPayloadDeserializer) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + IEdmTypeReference expectedPayloadType; + ODataDeserializer deserializer = GetDeserializer(type, path, model, + getEdmTypeDeserializer, getODataPayloadDeserializer, out expectedPayloadType); + if (deserializer != null) + { + return payloadKinds.Contains(deserializer.ODataPayloadKind); + } + + return false; + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is sent to the logger, which may throw it.")] + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "oDataMessageReader mis registered for disposal.")] + internal static object ReadFromStream( + Type type, + object defaultValue, + IEdmModel model, + Uri baseAddress, + IWebApiRequestMessage internalRequest, + Func getODataRequestMessage, + Func getEdmTypeDeserializer, + Func getODataPayloadDeserializer, + Func getODataDeserializerContext, + Action registerForDisposeAction, + Action logErrorAction) + { + object result; + + IEdmTypeReference expectedPayloadType; + ODataDeserializer deserializer = GetDeserializer(type, internalRequest.Context.Path, model, getEdmTypeDeserializer, getODataPayloadDeserializer, out expectedPayloadType); + if (deserializer == null) + { + throw Error.Argument("type", SRResources.FormatterReadIsNotSupportedForType, type.FullName, typeof(ODataInputFormatterHelper).FullName); + } + + try + { + ODataMessageReaderSettings oDataReaderSettings = internalRequest.ReaderSettings; + oDataReaderSettings.BaseUri = baseAddress; + oDataReaderSettings.Validations = oDataReaderSettings.Validations & ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; + + IODataRequestMessage oDataRequestMessage = getODataRequestMessage(); + ODataMessageReader oDataMessageReader = new ODataMessageReader(oDataRequestMessage, oDataReaderSettings, model); + registerForDisposeAction(oDataMessageReader); + + ODataPath path = internalRequest.Context.Path; + ODataDeserializerContext readContext = getODataDeserializerContext(); + readContext.Path = path; + readContext.Model = model; + readContext.ResourceType = type; + readContext.ResourceEdmType = expectedPayloadType; + + result = deserializer.Read(oDataMessageReader, type, readContext); + } + catch (Exception e) + { + logErrorAction(e); + result = defaultValue; + } + + return result; + } + + private static ODataDeserializer GetDeserializer( + Type type, + ODataPath path, + IEdmModel model, + Func getEdmTypeDeserializer, + Func getODataPayloadDeserializer, + out IEdmTypeReference expectedPayloadType) + { + expectedPayloadType = EdmLibHelpers.GetExpectedPayloadType(type, path, model); + + // Get the deserializer using the CLR type first from the deserializer provider. + ODataDeserializer deserializer = getODataPayloadDeserializer(type); + if (deserializer == null && expectedPayloadType != null) + { + // we are in typeless mode, get the deserializer using the edm type from the path. + deserializer = getEdmTypeDeserializer(expectedPayloadType); + } + + return deserializer; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMediaTypes.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMediaTypes.cs new file mode 100644 index 0000000..3dbc452 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMediaTypes.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Contains media types used by the OData formatter. + /// + internal static class ODataMediaTypes + { + public static readonly string ApplicationJson = "application/json"; + public static readonly string ApplicationJsonODataFullMetadata = "application/json;odata.metadata=full"; + public static readonly string ApplicationJsonODataFullMetadataStreamingFalse = "application/json;odata.metadata=full;odata.streaming=false"; + public static readonly string ApplicationJsonODataFullMetadataStreamingTrue = "application/json;odata.metadata=full;odata.streaming=true"; + public static readonly string ApplicationJsonODataMinimalMetadata = "application/json;odata.metadata=minimal"; + public static readonly string ApplicationJsonODataMinimalMetadataStreamingFalse = "application/json;odata.metadata=minimal;odata.streaming=false"; + public static readonly string ApplicationJsonODataMinimalMetadataStreamingTrue = "application/json;odata.metadata=minimal;odata.streaming=true"; + public static readonly string ApplicationJsonODataNoMetadata = "application/json;odata.metadata=none"; + public static readonly string ApplicationJsonODataNoMetadataStreamingFalse = "application/json;odata.metadata=none;odata.streaming=false"; + public static readonly string ApplicationJsonODataNoMetadataStreamingTrue = "application/json;odata.metadata=none;odata.streaming=true"; + public static readonly string ApplicationJsonStreamingFalse = "application/json;odata.streaming=false"; + public static readonly string ApplicationJsonStreamingTrue = "application/json;odata.streaming=true"; + public static readonly string ApplicationXml = "application/xml"; + + public static ODataMetadataLevel GetMetadataLevel(string mediaType, IEnumerable> parameters) + { + if (mediaType == null) + { + return ODataMetadataLevel.MinimalMetadata; + } + + if (!String.Equals(ODataMediaTypes.ApplicationJson, mediaType, + StringComparison.Ordinal)) + { + return ODataMetadataLevel.MinimalMetadata; + } + + Contract.Assert(parameters != null); + KeyValuePair odataParameter = + parameters.FirstOrDefault( + (p) => String.Equals("odata.metadata", p.Key, StringComparison.OrdinalIgnoreCase)); + + if (!odataParameter.Equals(default(KeyValuePair))) + { + if (String.Equals("full", odataParameter.Value, StringComparison.OrdinalIgnoreCase)) + { + return ODataMetadataLevel.FullMetadata; + } + if (String.Equals("none", odataParameter.Value, StringComparison.OrdinalIgnoreCase)) + { + return ODataMetadataLevel.NoMetadata; + } + } + + // Minimal is the default metadata level + return ODataMetadataLevel.MinimalMetadata; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMessageWrapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMessageWrapper.cs new file mode 100644 index 0000000..02ebb8c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMessageWrapper.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Wrapper for IODataRequestMessage and IODataResponseMessage. + /// + internal class ODataMessageWrapper : IODataRequestMessage, IODataResponseMessage, IODataPayloadUriConverter, IContainerProvider, IDisposable + { + private Stream _stream; + private Dictionary _headers; + private IDictionary _contentIdMapping; + private static readonly Regex ContentIdReferencePattern = new Regex(@"\$\d", RegexOptions.Compiled); + + public ODataMessageWrapper() + : this(stream: null, headers: null) + { + } + + public ODataMessageWrapper(Stream stream) + : this(stream: stream, headers: null) + { + } + + public ODataMessageWrapper(Stream stream, Dictionary headers) + : this(stream: stream, headers: headers, contentIdMapping: null) + { + } + + public ODataMessageWrapper(Stream stream, Dictionary headers, IDictionary contentIdMapping) + { + _stream = stream; + if (headers != null) + { + _headers = headers; + } + else + { + _headers = new Dictionary(); + } + _contentIdMapping = contentIdMapping ?? new Dictionary(); + } + + public IEnumerable> Headers + { + get + { + return _headers; + } + } + + public string Method + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public Uri Url + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public int StatusCode + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public IServiceProvider Container { get; set; } + + public string GetHeader(string headerName) + { + string value; + if (_headers.TryGetValue(headerName, out value)) + { + return value; + } + + return null; + } + + public Stream GetStream() + { + return _stream; + } + + public void SetHeader(string headerName, string headerValue) + { + _headers[headerName] = headerValue; + } + + public Uri ConvertPayloadUri(Uri baseUri, Uri payloadUri) + { + if (payloadUri == null) + { + throw new ArgumentNullException("payloadUri"); + } + + string originalPayloadUri = payloadUri.OriginalString; + if (ContentIdReferencePattern.IsMatch(originalPayloadUri)) + { + string resolvedUri = ContentIdHelpers.ResolveContentId(originalPayloadUri, _contentIdMapping); + return new Uri(resolvedUri, UriKind.RelativeOrAbsolute); + } + + // Returning null for default resolution. + return null; + } + + /// + public void Dispose() + { + Dispose(true); + } + + /// + protected void Dispose(bool disposing) + { + if (disposing) + { + if (_stream != null) + { + _stream.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMetadataLevel.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMetadataLevel.cs new file mode 100644 index 0000000..1cde7a7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataMetadataLevel.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// The amount of metadata information to serialize in an OData response (for JSON). + /// + public enum ODataMetadataLevel + { + /// + /// JSON minimal metadata. + /// + MinimalMetadata = 0, + + /// + /// JSON full metadata. + /// + FullMetadata = 1, + + /// + /// JSON no metadata. + /// + NoMetadata = 2 + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataModelBinderConverter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataModelBinderConverter.cs new file mode 100644 index 0000000..7ac3f06 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataModelBinderConverter.cs @@ -0,0 +1,416 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Expose functionality to convert an function parameter value into a CLR object. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", + Justification = "Relies on many ODataLib classes.")] + public static class ODataModelBinderConverter + { + private static readonly MethodInfo EnumTryParseMethod = typeof(Enum).GetMethods() + .Single(m => m.Name == "TryParse" && m.GetParameters().Length == 2); + + private static readonly MethodInfo CastMethodInfo = typeof(Enumerable).GetMethod("Cast"); + + /// + /// Convert an OData value into a CLR object. + /// + /// The given object. + /// The EDM type of the given object. + /// The CLR type of the given object. + /// The parameter name of the given object. + /// The use to convert. + /// The dependency injection container for the request. + /// The converted object. + public static object Convert(object graph, IEdmTypeReference edmTypeReference, + Type clrType, string parameterName, ODataDeserializerContext readContext, + IServiceProvider requestContainer) + { + if (graph == null || graph is ODataNullValue) + { + return null; + } + + // collection of primitive, enum + ODataCollectionValue collectionValue = graph as ODataCollectionValue; + if (collectionValue != null) + { + return ConvertCollection(collectionValue, edmTypeReference, clrType, parameterName, readContext, + requestContainer); + } + + // enum value + ODataEnumValue enumValue = graph as ODataEnumValue; + if (enumValue != null) + { + IEdmEnumTypeReference edmEnumType = edmTypeReference.AsEnum(); + Contract.Assert(edmEnumType != null); + + ODataDeserializerProvider deserializerProvider = + requestContainer.GetRequiredService(); + + ODataEnumDeserializer deserializer = + (ODataEnumDeserializer)deserializerProvider.GetEdmTypeDeserializer(edmEnumType); + + return deserializer.ReadInline(enumValue, edmEnumType, readContext); + } + + // primitive value + if (edmTypeReference.IsPrimitive()) + { + ConstantNode node = graph as ConstantNode; + return EdmPrimitiveHelpers.ConvertPrimitiveValue(node != null ? node.Value : graph, clrType); + } + + // Resource, ResourceSet, Entity Reference or collection of entity reference + return ConvertResourceOrResourceSet(graph, edmTypeReference, readContext); + } + + internal static object ConvertTo(string valueString, Type type) + { + if (valueString == null) + { + return null; + } + + if (TypeHelper.IsNullable(type) && String.Equals(valueString, "null", StringComparison.Ordinal)) + { + return null; + } + + // TODO 1668: ODL beta1's ODataUriUtils.ConvertFromUriLiteral does not support converting uri literal + // to ODataEnumValue, but beta1's ODataUriUtils.ConvertToUriLiteral supports converting ODataEnumValue + // to uri literal. + if (TypeHelper.IsEnum(type)) + { + string[] values = valueString.Split(new[] { '\'' }, StringSplitOptions.None); + if (values.Length == 3 && String.IsNullOrEmpty(values[2])) + { + // Remove the type name if the enum value is a fully qualified literal. + valueString = values[1]; + } + + Type enumType = TypeHelper.GetUnderlyingTypeOrSelf(type); + object[] parameters = new[] { valueString, Enum.ToObject(enumType, 0) }; + bool isSuccessful = (bool)EnumTryParseMethod.MakeGenericMethod(enumType).Invoke(null, parameters); + + if (!isSuccessful) + { + throw Error.InvalidOperation(SRResources.ModelBinderUtil_ValueCannotBeEnum, valueString, type.Name); + } + + return parameters[1]; + } + + // The logic of "public static object ConvertFromUriLiteral(string value, ODataVersion version);" treats + // the date value string (for example: 2015-01-02) as DateTimeOffset literal, and return a DateTimeOffset + // object. However, the logic of + // "object ConvertFromUriLiteral(string value, ODataVersion version, IEdmModel model, IEdmTypeReference typeReference);" + // can return the correct Date object. + if (type == typeof(Date) || type == typeof(Date?)) + { + EdmCoreModel model = EdmCoreModel.Instance; + IEdmPrimitiveTypeReference dateTypeReference = EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(type); + return ODataUriUtils.ConvertFromUriLiteral(valueString, ODataVersion.V4, model, dateTypeReference); + } + + object value; + try + { + value = ODataUriUtils.ConvertFromUriLiteral(valueString, ODataVersion.V4); + } + catch + { + if (type == typeof(string)) + { + return valueString; + } + + throw; + } + + bool isNonStandardEdmPrimitive; + EdmLibHelpers.IsNonstandardEdmPrimitive(type, out isNonStandardEdmPrimitive); + + if (isNonStandardEdmPrimitive) + { + return EdmPrimitiveHelpers.ConvertPrimitiveValue(value, type); + } + else + { + type = Nullable.GetUnderlyingType(type) ?? type; + return System.Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + } + } + + private static object ConvertCollection(ODataCollectionValue collectionValue, + IEdmTypeReference edmTypeReference, Type clrType, string parameterName, + ODataDeserializerContext readContext, IServiceProvider requestContainer) + { + Contract.Assert(collectionValue != null); + + IEdmCollectionTypeReference collectionType = edmTypeReference as IEdmCollectionTypeReference; + Contract.Assert(collectionType != null); + + ODataDeserializerProvider deserializerProvider = + requestContainer.GetRequiredService(); + ODataCollectionDeserializer deserializer = + (ODataCollectionDeserializer)deserializerProvider.GetEdmTypeDeserializer(collectionType); + + object value = deserializer.ReadInline(collectionValue, collectionType, readContext); + if (value == null) + { + return null; + } + + IEnumerable collection = value as IEnumerable; + Contract.Assert(collection != null); + + Type elementType; + if (!TypeHelper.IsCollection(clrType, out elementType)) + { + // EdmEntityCollectionObject and EdmComplexCollectionObject are collection types. + throw new ODataException(String.Format(CultureInfo.InvariantCulture, + SRResources.ParameterTypeIsNotCollection, parameterName, clrType)); + } + + IEnumerable newCollection; + if (CollectionDeserializationHelpers.TryCreateInstance(clrType, collectionType, elementType, + out newCollection)) + { + collection.AddToCollection(newCollection, elementType, parameterName, clrType); + if (clrType.IsArray) + { + newCollection = CollectionDeserializationHelpers.ToArray(newCollection, elementType); + } + + return newCollection; + } + + return null; + } + + private static object ConvertResourceOrResourceSet(object oDataValue, IEdmTypeReference edmTypeReference, + ODataDeserializerContext readContext) + { + string valueString = oDataValue as string; + Contract.Assert(valueString != null); + + if (edmTypeReference.IsNullable && String.Equals(valueString, "null", StringComparison.Ordinal)) + { + return null; + } + + IWebApiRequestMessage request = readContext.InternalRequest; + ODataMessageReaderSettings oDataReaderSettings = request.ReaderSettings; + + using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(valueString))) + { + stream.Seek(0, SeekOrigin.Begin); + + IODataRequestMessage oDataRequestMessage = new ODataMessageWrapper(stream, null, + request.ODataContentIdMapping); + using ( + ODataMessageReader oDataMessageReader = new ODataMessageReader(oDataRequestMessage, + oDataReaderSettings, readContext.Model)) + { + if (edmTypeReference.IsCollection()) + { + return ConvertResourceSet(oDataMessageReader, edmTypeReference, readContext); + } + else + { + return ConvertResource(oDataMessageReader, edmTypeReference, readContext); + } + } + } + } + + private static object ConvertResourceSet(ODataMessageReader oDataMessageReader, + IEdmTypeReference edmTypeReference, ODataDeserializerContext readContext) + { + IEdmCollectionTypeReference collectionType = edmTypeReference.AsCollection(); + + EdmEntitySet tempEntitySet = null; + if (collectionType.ElementType().IsEntity()) + { + tempEntitySet = new EdmEntitySet(readContext.Model.EntityContainer, "temp", + collectionType.ElementType().AsEntity().EntityDefinition()); + } + + // TODO: Sam xu, can we use the parameter-less overload + ODataReader odataReader = oDataMessageReader.CreateODataUriParameterResourceSetReader(tempEntitySet, + collectionType.ElementType().AsStructured().StructuredDefinition()); + ODataResourceSetWrapper resourceSet = + odataReader.ReadResourceOrResourceSet() as ODataResourceSetWrapper; + + ODataDeserializerProvider deserializerProvider = readContext.InternalRequest.DeserializerProvider; + + ODataResourceSetDeserializer resourceSetDeserializer = + (ODataResourceSetDeserializer)deserializerProvider.GetEdmTypeDeserializer(collectionType); + + object result = resourceSetDeserializer.ReadInline(resourceSet, collectionType, readContext); + IEnumerable enumerable = result as IEnumerable; + if (enumerable != null) + { + IEnumerable newEnumerable = enumerable; + if (collectionType.ElementType().IsEntity()) + { + newEnumerable = CovertResourceSetIds(enumerable, resourceSet, collectionType, readContext); + } + + if (readContext.IsUntyped) + { + return newEnumerable.ConvertToEdmObject(collectionType); + } + else + { + IEdmTypeReference elementTypeReference = collectionType.ElementType(); + + Type elementClrType = EdmLibHelpers.GetClrType(elementTypeReference, + readContext.Model); + IEnumerable castedResult = + CastMethodInfo.MakeGenericMethod(elementClrType) + .Invoke(null, new object[] { newEnumerable }) as IEnumerable; + return castedResult; + } + } + + return null; + } + + private static object ConvertResource(ODataMessageReader oDataMessageReader, IEdmTypeReference edmTypeReference, + ODataDeserializerContext readContext) + { + EdmEntitySet tempEntitySet = null; + if (edmTypeReference.IsEntity()) + { + IEdmEntityTypeReference entityType = edmTypeReference.AsEntity(); + tempEntitySet = new EdmEntitySet(readContext.Model.EntityContainer, "temp", + entityType.EntityDefinition()); + } + + // TODO: Sam xu, can we use the parameter-less overload + ODataReader resourceReader = oDataMessageReader.CreateODataUriParameterResourceReader(tempEntitySet, + edmTypeReference.ToStructuredType()); + + object item = resourceReader.ReadResourceOrResourceSet(); + + ODataResourceWrapper topLevelResource = item as ODataResourceWrapper; + Contract.Assert(topLevelResource != null); + + ODataDeserializerProvider deserializerProvider = readContext.InternalRequest.DeserializerProvider; + + ODataResourceDeserializer entityDeserializer = + (ODataResourceDeserializer)deserializerProvider.GetEdmTypeDeserializer(edmTypeReference); + object value = entityDeserializer.ReadInline(topLevelResource, edmTypeReference, readContext); + + if (edmTypeReference.IsEntity()) + { + IEdmEntityTypeReference entityType = edmTypeReference.AsEntity(); + return CovertResourceId(value, topLevelResource.Resource, entityType, readContext); + } + + return value; + } + + private static IEnumerable CovertResourceSetIds(IEnumerable sources, ODataResourceSetWrapper resourceSet, + IEdmCollectionTypeReference collectionType, ODataDeserializerContext readContext) + { + IEdmEntityTypeReference entityTypeReference = collectionType.ElementType().AsEntity(); + int i = 0; + foreach (object item in sources) + { + object newItem = CovertResourceId(item, resourceSet.Resources[i].Resource, entityTypeReference, + readContext); + i++; + yield return newItem; + } + } + + private static object CovertResourceId(object source, ODataResource resource, + IEdmEntityTypeReference entityTypeReference, ODataDeserializerContext readContext) + { + Contract.Assert(resource != null); + Contract.Assert(source != null); + + if (resource.Id == null || resource.Properties.Any()) + { + return source; + } + + IWebApiRequestMessage request = readContext.InternalRequest; + IWebApiUrlHelper urlHelper = readContext.InternalUrlHelper; + + DefaultODataPathHandler pathHandler = new DefaultODataPathHandler(); + string serviceRoot = urlHelper.CreateODataLink( + request.Context.RouteName, + request.PathHandler, + new List()); + + IEnumerable> keyValues = GetKeys(pathHandler, serviceRoot, resource.Id, + request.RequestContainer); + + IList keys = entityTypeReference.Key().ToList(); + + if (keys.Count == 1 && keyValues.Count() == 1) + { + // TODO: make sure the enum key works + object propertyValue = keyValues.First().Value; + DeserializationHelpers.SetDeclaredProperty(source, EdmTypeKind.Primitive, keys[0].Name, propertyValue, + keys[0], readContext); + return source; + } + + IDictionary keyValuesDic = keyValues.ToDictionary(e => e.Key, e => e.Value); + foreach (IEdmStructuralProperty key in keys) + { + object value; + if (keyValuesDic.TryGetValue(key.Name, out value)) + { + // TODO: make sure the enum key works + DeserializationHelpers.SetDeclaredProperty(source, EdmTypeKind.Primitive, key.Name, value, key, + readContext); + } + } + + return source; + } + + private static IEnumerable> GetKeys(DefaultODataPathHandler pathHandler, + string serviceRoot, Uri uri, IServiceProvider requestContainer) + { + ODataPath odataPath = pathHandler.Parse(serviceRoot, uri.ToString(), requestContainer); + KeySegment segment = odataPath.Segments.OfType().Last(); + if (segment == null) + { + throw Error.InvalidOperation(SRResources.EntityReferenceMustHasKeySegment, uri); + } + + return segment.Keys; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataOutputFormatterHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataOutputFormatterHelper.cs new file mode 100644 index 0000000..4043f5e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataOutputFormatterHelper.cs @@ -0,0 +1,354 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Http.Headers; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal static class ODataOutputFormatterHelper + { + internal static bool TryGetContentHeader(Type type, MediaTypeHeaderValue mediaType, out MediaTypeHeaderValue newMediaType) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + newMediaType = null; + + // When the user asks for application/json we really need to set the content type to + // application/json; odata.metadata=minimal. If the user provides the media type and is + // application/json we are going to add automatically odata.metadata=minimal. Otherwise we are + // going to fallback to the default implementation. + + // When calling this formatter as part of content negotiation the content negotiator will always + // pick a non null media type. In case the user creates a new ObjectContent and doesn't pass in a + // media type, we delegate to the base class to rely on the default behavior. It's the user's + // responsibility to pass in the right media type. + + if (mediaType != null) + { + if (mediaType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase) && + !mediaType.Parameters.Any(p => p.Name.Equals("odata.metadata", StringComparison.OrdinalIgnoreCase))) + { + mediaType.Parameters.Add(new NameValueHeaderValue("odata.metadata", "minimal")); + } + + newMediaType = (MediaTypeHeaderValue)((ICloneable)mediaType).Clone(); + return true; + } + else + { + // This is the case when a user creates a new ObjectContent passing in a null mediaType + return false; + } + } + + internal static bool TryGetCharSet(MediaTypeHeaderValue mediaType, IEnumerable acceptCharsetValues, out string charSet) + { + charSet = String.Empty; + + // In general, in Web API we pick a default charset based on the supported character sets + // of the formatter. However, according to the OData spec, the service shouldn't be sending + // a character set unless explicitly specified, so if the client didn't send the charset we chose + // we just clean it. + if (mediaType != null && + !acceptCharsetValues + .Any(cs => cs.Equals(mediaType.CharSet, StringComparison.OrdinalIgnoreCase))) + { + return true; + } + + return false; + } + + internal static bool CanWriteType( + Type type, + IEnumerable payloadKinds, + bool isGenericSingleResult, + IWebApiRequestMessage internalRequest, + Func getODataPayloadSerializer) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + ODataPayloadKind? payloadKind; + + Type elementType; + if (typeof(IEdmObject).IsAssignableFrom(type) || + (TypeHelper.IsCollection(type, out elementType) && typeof(IEdmObject).IsAssignableFrom(elementType))) + { + payloadKind = GetEdmObjectPayloadKind(type, internalRequest); + } + else + { + payloadKind = GetClrObjectResponsePayloadKind(type, isGenericSingleResult, getODataPayloadSerializer); + } + + return payloadKind == null ? false : payloadKinds.Contains(payloadKind.Value); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling acceptable")] + internal static void WriteToStream( + Type type, + object value, + IEdmModel model, + ODataVersion version, + Uri baseAddress, + MediaTypeHeaderValue contentType, + IWebApiUrlHelper internaUrlHelper, + IWebApiRequestMessage internalRequest, + IWebApiHeaders internalRequestHeaders, + Func getODataMessageWrapper, + Func getEdmTypeSerializer, + Func getODataPayloadSerializer, + Func getODataSerializerContext) + { + if (model == null) + { + throw Error.InvalidOperation(SRResources.RequestMustHaveModel); + } + + ODataSerializer serializer = GetSerializer(type, value, internalRequest, getEdmTypeSerializer, getODataPayloadSerializer); + + ODataPath path = internalRequest.Context.Path; + IEdmNavigationSource targetNavigationSource = path == null ? null : path.NavigationSource; + + // serialize a response + string preferHeader = RequestPreferenceHelpers.GetRequestPreferHeader(internalRequestHeaders); + string annotationFilter = null; + if (!String.IsNullOrEmpty(preferHeader)) + { + ODataMessageWrapper messageWrapper = getODataMessageWrapper(null); + messageWrapper.SetHeader(RequestPreferenceHelpers.PreferHeaderName, preferHeader); + annotationFilter = messageWrapper.PreferHeader().AnnotationFilter; + } + + ODataMessageWrapper responseMessageWrapper = getODataMessageWrapper(internalRequest.RequestContainer); + IODataResponseMessage responseMessage = responseMessageWrapper; + if (annotationFilter != null) + { + responseMessage.PreferenceAppliedHeader().AnnotationFilter = annotationFilter; + } + + ODataMessageWriterSettings writerSettings = internalRequest.WriterSettings; + writerSettings.BaseUri = baseAddress; + writerSettings.Version = version; + writerSettings.Validations = writerSettings.Validations & ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; + + string metadataLink = internaUrlHelper.CreateODataLink(MetadataSegment.Instance); + + if (metadataLink == null) + { + throw new SerializationException(SRResources.UnableToDetermineMetadataUrl); + } + + //Set this variable if the SelectExpandClause is different from the processed clause on the Query options + SelectExpandClause selectExpandDifferentFromQueryOptions = null; + if (internalRequest.Context.QueryOptions != null && internalRequest.Context.QueryOptions.SelectExpand != null) + { + if (internalRequest.Context.QueryOptions.SelectExpand.ProcessedSelectExpandClause != internalRequest.Context.ProcessedSelectExpandClause) + { + selectExpandDifferentFromQueryOptions = internalRequest.Context.ProcessedSelectExpandClause; + } + } + else if (internalRequest.Context.ProcessedSelectExpandClause != null) + { + selectExpandDifferentFromQueryOptions = internalRequest.Context.ProcessedSelectExpandClause; + } + + writerSettings.ODataUri = new ODataUri + { + ServiceRoot = baseAddress, + + // TODO: 1604 Convert webapi.odata's ODataPath to ODL's ODataPath, or use ODL's ODataPath. + SelectAndExpand = internalRequest.Context.ProcessedSelectExpandClause, + Apply = internalRequest.Context.ApplyClause, + Path = (path == null || IsOperationPath(path)) ? null : path.Path, + }; + + ODataMetadataLevel metadataLevel = ODataMetadataLevel.MinimalMetadata; + if (contentType != null) + { + IEnumerable> parameters = + contentType.Parameters.Select(val => new KeyValuePair(val.Name, val.Value)); + metadataLevel = ODataMediaTypes.GetMetadataLevel(contentType.MediaType, parameters); + } + + using (ODataMessageWriter messageWriter = new ODataMessageWriter(responseMessage, writerSettings, model)) + { + ODataSerializerContext writeContext = getODataSerializerContext(); + writeContext.NavigationSource = targetNavigationSource; + writeContext.Model = model; + writeContext.RootElementName = GetRootElementName(path) ?? "root"; + writeContext.SkipExpensiveAvailabilityChecks = serializer.ODataPayloadKind == ODataPayloadKind.ResourceSet; + writeContext.Path = path; + writeContext.MetadataLevel = metadataLevel; + writeContext.QueryOptions = internalRequest.Context.QueryOptions; + + //Set the SelectExpandClause on the context if it was explicitly specified. + if (selectExpandDifferentFromQueryOptions != null) + { + writeContext.SelectExpandClause = selectExpandDifferentFromQueryOptions; + } + + serializer.WriteObject(value, type, messageWriter, writeContext); + } + } + + private static ODataPayloadKind? GetClrObjectResponsePayloadKind(Type type, bool isGenericSingleResult, Func getODataPayloadSerializer) + { + // SingleResult should be serialized as T. + if (isGenericSingleResult) + { + type = type.GetGenericArguments()[0]; + } + + ODataSerializer serializer = getODataPayloadSerializer(type); + return serializer == null ? null : (ODataPayloadKind?)serializer.ODataPayloadKind; + } + + private static ODataPayloadKind? GetEdmObjectPayloadKind(Type type, IWebApiRequestMessage internalRequest) + { + if (internalRequest.IsCountRequest()) + { + return ODataPayloadKind.Value; + } + + Type elementType; + if (TypeHelper.IsCollection(type, out elementType)) + { + if (typeof(IEdmComplexObject).IsAssignableFrom(elementType) || typeof(IEdmEnumObject).IsAssignableFrom(elementType)) + { + return ODataPayloadKind.Collection; + } + else if (typeof(IEdmEntityObject).IsAssignableFrom(elementType)) + { + return ODataPayloadKind.ResourceSet; + } + else if (typeof(IEdmChangedObject).IsAssignableFrom(elementType)) + { + return ODataPayloadKind.Delta; + } + } + else + { + if (typeof(IEdmComplexObject).IsAssignableFrom(elementType) || typeof(IEdmEnumObject).IsAssignableFrom(elementType)) + { + return ODataPayloadKind.Property; + } + else if (typeof(IEdmEntityObject).IsAssignableFrom(elementType)) + { + return ODataPayloadKind.Resource; + } + } + + return null; + } + + private static ODataSerializer GetSerializer(Type type, object value, IWebApiRequestMessage internalRequest, Func getEdmTypeSerializer, Func getODataPayloadSerializer) + { + ODataSerializer serializer; + + IEdmObject edmObject = value as IEdmObject; + if (edmObject != null) + { + IEdmTypeReference edmType = edmObject.GetEdmType(); + if (edmType == null) + { + throw new SerializationException(Error.Format(SRResources.EdmTypeCannotBeNull, + edmObject.GetType().FullName, typeof(IEdmObject).Name)); + } + + serializer = getEdmTypeSerializer(edmType); + if (serializer == null) + { + string message = Error.Format(SRResources.TypeCannotBeSerialized, edmType.ToTraceString()); + throw new SerializationException(message); + } + } + else + { + var applyClause = internalRequest.Context.ApplyClause; + // get the most appropriate serializer given that we support inheritance. + if (applyClause == null) + { + type = value == null ? type : value.GetType(); + } + + serializer = getODataPayloadSerializer(type); + if (serializer == null) + { + string message = Error.Format(SRResources.TypeCannotBeSerialized, type.Name); + throw new SerializationException(message); + } + } + + return serializer; + } + + private static string GetRootElementName(ODataPath path) + { + if (path != null) + { + ODataPathSegment lastSegment = path.Segments.LastOrDefault(); + if (lastSegment != null) + { + OperationSegment actionSegment = lastSegment as OperationSegment; + if (actionSegment != null) + { + IEdmAction action = actionSegment.Operations.Single() as IEdmAction; + if (action != null) + { + return action.Name; + } + } + + PropertySegment propertyAccessSegment = lastSegment as PropertySegment; + if (propertyAccessSegment != null) + { + return propertyAccessSegment.Property.Name; + } + } + } + return null; + } + + // This function is used to determine whether an OData path includes operation (import) path segments. + // We use this function to make sure the value of ODataUri.Path in ODataMessageWriterSettings is null + // when any path segment is an operation. ODL will try to calculate the context URL if the ODataUri.Path + // equals to null. + private static bool IsOperationPath(ODataPath path) + { + if (path == null) + { + return false; + } + + foreach (ODataPathSegment segment in path.Segments) + { + if (segment is OperationSegment || + segment is OperationImportSegment) + { + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataPrimitiveValueMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataPrimitiveValueMediaTypeMapping.cs new file mode 100644 index 0000000..4b0696c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataPrimitiveValueMediaTypeMapping.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests for the raw value of non-binary primitive properties to + /// the text/plain content type. + /// + public class ODataPrimitiveValueMediaTypeMapping : ODataRawValueMediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + public ODataPrimitiveValueMediaTypeMapping() + : base("text/plain") + { + } + + /// + protected override bool IsMatch(PropertySegment propertySegment) + { + return propertySegment != null && + propertySegment.Property.Type.IsPrimitive() && + !propertySegment.Property.Type.IsBinary(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataRawValueMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataRawValueMediaTypeMapping.cs new file mode 100644 index 0000000..12d22cd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataRawValueMediaTypeMapping.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Net.Http; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests for the raw value of properties. + /// + public abstract partial class ODataRawValueMediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + protected ODataRawValueMediaTypeMapping(string mediaType) + : base(mediaType) + { + } + + /// + /// This method determines if the is an OData Raw value request. + /// + /// The of the path. + /// True if the request is an OData raw value request. + protected abstract bool IsMatch(PropertySegment propertySegment); + + internal static bool IsRawValueRequest(ODataPath path) + { + return path != null && path.Segments.LastOrDefault() is ValueSegment; + } + + private static PropertySegment GetProperty(ODataPath odataPath) + { + if (odataPath == null || odataPath.Segments.Count < 2) + { + return null; + } + return odataPath.Segments[odataPath.Segments.Count - 2] as PropertySegment; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataValueExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataValueExtensions.cs new file mode 100644 index 0000000..784fc9d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/ODataValueExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + internal static class ODataValueExtensions + { + public static object GetInnerValue(this ODataValue odataValue) + { + if (odataValue is ODataNullValue) + { + return null; + } + + ODataPrimitiveValue oDataPrimitiveValue = odataValue as ODataPrimitiveValue; + if (oDataPrimitiveValue != null) + { + return oDataPrimitiveValue.Value; + } + + return odataValue; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/QueryStringMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/QueryStringMediaTypeMapping.cs new file mode 100644 index 0000000..3de58c1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/QueryStringMediaTypeMapping.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Class that provides s from query strings. + /// + public partial class QueryStringMediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the query string parameter to match, if present. + /// The media type to use if the query parameter specified by is present + /// and assigned the value specified by . + public QueryStringMediaTypeMapping(string queryStringParameterName, string mediaType) + : base(mediaType) + { + if (queryStringParameterName == null) + { + throw Error.ArgumentNull("queryStringParameterName"); + } + + QueryStringParameterName = queryStringParameterName; + } + + /// + /// Gets the query string parameter name. + /// + public string QueryStringParameterName { get; private set; } + + private bool DoesQueryStringMatch(IEnumerable> queryString) + { + if (queryString != null) + { + string queryValue = queryString.Where(kvp => kvp.Key == QueryStringParameterName) + .FirstOrDefault() + .Value; + + if (queryValue != null) + { + // construct a media type from the query value + MediaTypeHeaderValue parsedValue; + bool success = MediaTypeHeaderValue.TryParse(queryValue, out parsedValue); + if (success && MediaType.Equals(parsedValue)) + { + return true; + } + } + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/DefaultODataSerializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/DefaultODataSerializerProvider.cs new file mode 100644 index 0000000..1f183f1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/DefaultODataSerializerProvider.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// The default . + /// + public partial class DefaultODataSerializerProvider : ODataSerializerProvider + { + private readonly IServiceProvider _rootContainer; + + /// + /// Initializes a new instance of the class. + /// + /// The root container. + public DefaultODataSerializerProvider(IServiceProvider rootContainer) + { + if (rootContainer == null) + { + throw Error.ArgumentNull("rootContainer"); + } + + _rootContainer = rootContainer; + } + + /// + public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + switch (edmType.TypeKind()) + { + case EdmTypeKind.Enum: + return _rootContainer.GetRequiredService(); + + case EdmTypeKind.Primitive: + return _rootContainer.GetRequiredService(); + + case EdmTypeKind.Collection: + IEdmCollectionTypeReference collectionType = edmType.AsCollection(); + if (collectionType.Definition.IsDeltaFeed()) + { + return _rootContainer.GetRequiredService(); + } + else if (collectionType.ElementType().IsEntity() || collectionType.ElementType().IsComplex()) + { + return _rootContainer.GetRequiredService(); + } + else + { + return _rootContainer.GetRequiredService(); + } + + case EdmTypeKind.Complex: + case EdmTypeKind.Entity: + return _rootContainer.GetRequiredService(); + + default: + return null; + } + } + + /// + internal ODataSerializer GetODataPayloadSerializerImpl(Type type, Func modelFunction, ODataPath path, Type errorType) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + if (modelFunction == null) + { + throw Error.ArgumentNull("modelFunction"); + } + + // handle the special types. + if (type == typeof(ODataServiceDocument)) + { + return _rootContainer.GetRequiredService(); + } + else if (type == typeof(Uri) || type == typeof(ODataEntityReferenceLink)) + { + return _rootContainer.GetRequiredService(); + } + else if (TypeHelper.IsTypeAssignableFrom(typeof(IEnumerable), type) || type == typeof(ODataEntityReferenceLinks)) + { + return _rootContainer.GetRequiredService(); + } + else if (type == typeof(ODataError) || type == errorType) + { + return _rootContainer.GetRequiredService(); + } + else if (TypeHelper.IsTypeAssignableFrom(typeof(IEdmModel), type)) + { + return _rootContainer.GetRequiredService(); + } + + // Get the model. Using a Func to delay evaluation of the model + // until after the above checks have passed. + IEdmModel model = modelFunction(); + + // if it is not a special type, assume it has a corresponding EdmType. + ClrTypeCache typeMappingCache = model.GetTypeMappingCache(); + IEdmTypeReference edmType = typeMappingCache.GetEdmType(type, model); + + if (edmType != null) + { + bool isCountRequest = path != null && path.Segments.LastOrDefault() is CountSegment; + bool isRawValueRequest = path != null && path.Segments.LastOrDefault() is ValueSegment; + + if (((edmType.IsPrimitive() || edmType.IsEnum()) && isRawValueRequest) || isCountRequest) + { + return _rootContainer.GetRequiredService(); + } + else + { + return GetEdmTypeSerializer(edmType); + } + } + else + { + return null; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/EntitySelfLinks.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/EntitySelfLinks.cs new file mode 100644 index 0000000..a0c1fea --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/EntitySelfLinks.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// EntitySelfLinks contains the Id, Edit and Read links for an entity. + /// + public class EntitySelfLinks + { + /// + /// A string that uniquely identifies the resource. + /// + public Uri IdLink { get; set; } + + /// + /// A URL that can be used to edit a copy of the resource. + /// + public Uri EditLink { get; set; } + + /// + /// A URL that can be used to retrieve a copy of the resource. + /// + public Uri ReadLink { get; set; } + } + } diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs new file mode 100644 index 0000000..84e10e2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataCollectionSerializer.cs @@ -0,0 +1,289 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// ODataSerializer for serializing collection of primitive or enum types. + /// + public class ODataCollectionSerializer : ODataEdmTypeSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// The serializer provider to use to serialize nested objects. + public ODataCollectionSerializer(ODataSerializerProvider serializerProvider) + : base(ODataPayloadKind.Collection, serializerProvider) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + IEdmTypeReference collectionType = writeContext.GetEdmType(graph, type); + Contract.Assert(collectionType != null); + + IEdmTypeReference elementType = GetElementType(collectionType); + ODataCollectionWriter writer = messageWriter.CreateODataCollectionWriter(elementType); + WriteCollection(writer, graph, collectionType.AsCollection(), writeContext); + } + + /// + public sealed override ODataValue CreateODataValue(object graph, IEdmTypeReference expectedType, + ODataSerializerContext writeContext) + { + IEnumerable enumerable = graph as IEnumerable; + if (enumerable == null && graph != null) + { + throw Error.Argument("graph", SRResources.ArgumentMustBeOfType, typeof(IEnumerable).Name); + } + + if (expectedType == null) + { + throw Error.ArgumentNull("expectedType"); + } + + IEdmTypeReference elementType = GetElementType(expectedType); + return CreateODataCollectionValue(enumerable, elementType, writeContext); + } + + /// + /// Writes the given using the given . + /// + /// The to use. + /// The collection to write. + /// The EDM type of the collection. + /// The serializer context. + public void WriteCollection(ODataCollectionWriter writer, object graph, IEdmTypeReference collectionType, + ODataSerializerContext writeContext) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + + ODataCollectionStart collectionStart = new ODataCollectionStart { Name = writeContext.RootElementName }; + + if (writeContext.Request != null) + { + if (writeContext.InternalRequest.Context.NextLink != null) + { + collectionStart.NextPageLink = writeContext.InternalRequest.Context.NextLink; + } + else if (writeContext.InternalRequest.Context.QueryOptions != null) + { + // Collection serializer is called only for collection of primitive values - A null object will be supplied since it is a non-entity value + SkipTokenHandler skipTokenHandler = writeContext.QueryOptions.Context.GetSkipTokenHandler(); + collectionStart.NextPageLink = skipTokenHandler.GenerateNextPageLink(writeContext.InternalRequest.RequestUri, writeContext.InternalRequest.Context.PageSize, null, writeContext); + } + + if (writeContext.InternalRequest.Context.TotalCount != null) + { + collectionStart.Count = writeContext.InternalRequest.Context.TotalCount; + } + } + + writer.WriteStart(collectionStart); + + if (graph != null) + { + ODataCollectionValue collectionValue = CreateODataValue(graph, collectionType, writeContext) as ODataCollectionValue; + if (collectionValue != null) + { + foreach (object item in collectionValue.Items) + { + writer.WriteItem(item); + } + } + } + + writer.WriteEnd(); + } + + /// + /// Creates an for the enumerable represented by . + /// + /// The value of the collection to be created. + /// The element EDM type of the collection. + /// The serializer context to be used while creating the collection. + /// The created . + public virtual ODataCollectionValue CreateODataCollectionValue(IEnumerable enumerable, IEdmTypeReference elementType, + ODataSerializerContext writeContext) + { + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + if (elementType == null) + { + throw Error.ArgumentNull("elementType"); + } + + ArrayList valueCollection = new ArrayList(); + + if (enumerable != null) + { + ODataEdmTypeSerializer itemSerializer = null; + foreach (object item in enumerable) + { + if (item == null) + { + if (elementType.IsNullable) + { + valueCollection.Add(value: null); + continue; + } + + throw new SerializationException(SRResources.NullElementInCollection); + } + + IEdmTypeReference actualType = writeContext.GetEdmType(item, item.GetType()); + Contract.Assert(actualType != null); + + itemSerializer = itemSerializer ?? SerializerProvider.GetEdmTypeSerializer(actualType); + if (itemSerializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, actualType.FullName())); + } + + // ODataCollectionWriter expects the individual elements in the collection to be the underlying + // values and not ODataValues. + valueCollection.Add( + itemSerializer.CreateODataValue(item, actualType, writeContext).GetInnerValue()); + } + } + + // Ideally, we'd like to do this: + // string typeName = _edmCollectionType.FullName(); + // But ODataLib currently doesn't support .FullName() for collections. As a workaround, we construct the + // collection type name the hard way. + string typeName = "Collection(" + elementType.FullName() + ")"; + + // ODataCollectionValue is only a V3 property, arrays inside Complex Types or Entity types are only supported in V3 + // if a V1 or V2 Client requests a type that has a collection within it ODataLib will throw. + ODataCollectionValue value = new ODataCollectionValue + { + Items = valueCollection.Cast(), + TypeName = typeName + }; + + AddTypeNameAnnotationAsNeeded(value, writeContext.MetadataLevel); + return value; + } + + internal override ODataProperty CreateProperty(object graph, IEdmTypeReference expectedType, string elementName, + ODataSerializerContext writeContext) + { + Contract.Assert(elementName != null); + var property = CreateODataValue(graph, expectedType, writeContext); + if (property != null) + { + return new ODataProperty + { + Name = elementName, + Value = property + }; + } + else + { + return null; + } + } + + /// + /// Adds the type name annotations required for proper json light serialization. + /// + /// The collection value for which the annotations have to be added. + /// The OData metadata level of the response. + protected internal static void AddTypeNameAnnotationAsNeeded(ODataCollectionValue value, ODataMetadataLevel metadataLevel) + { + // ODataLib normally has the caller decide whether or not to serialize properties by leaving properties + // null when values should not be serialized. The TypeName property is different and should always be + // provided to ODataLib to enable model validation. A separate annotation is used to decide whether or not + // to serialize the type name (a null value prevents serialization). + + Contract.Assert(value != null); + + // Only add an annotation if we want to override ODataLib's default type name serialization behavior. + if (ShouldAddTypeNameAnnotation(metadataLevel)) + { + string typeName; + + // Provide the type name to serialize (or null to force it not to serialize). + if (ShouldSuppressTypeNameSerialization(metadataLevel)) + { + typeName = null; + } + else + { + typeName = value.TypeName; + } + + value.TypeAnnotation = new ODataTypeAnnotation(typeName); + } + } + + internal static bool ShouldAddTypeNameAnnotation(ODataMetadataLevel metadataLevel) + { + switch (metadataLevel) + { + // For collections, the default behavior matches the requirements for minimal metadata mode, so no + // annotation is necessary. + case ODataMetadataLevel.MinimalMetadata: + return false; + // In other cases, this class must control the type name serialization behavior. + case ODataMetadataLevel.FullMetadata: + case ODataMetadataLevel.NoMetadata: + default: // All values already specified; just keeping the compiler happy. + return true; + } + } + + internal static bool ShouldSuppressTypeNameSerialization(ODataMetadataLevel metadataLevel) + { + Contract.Assert(metadataLevel != ODataMetadataLevel.MinimalMetadata); + + switch (metadataLevel) + { + case ODataMetadataLevel.NoMetadata: + return true; + case ODataMetadataLevel.FullMetadata: + default: // All values already specified; just keeping the compiler happy. + return false; + } + } + + private static IEdmTypeReference GetElementType(IEdmTypeReference feedType) + { + if (feedType.IsCollection()) + { + return feedType.AsCollection().ElementType(); + } + + string message = Error.Format(SRResources.CannotWriteType, typeof(ODataResourceSetSerializer).Name, feedType.FullName()); + throw new SerializationException(message); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataDeltaFeedSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataDeltaFeedSerializer.cs new file mode 100644 index 0000000..efc94e3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataDeltaFeedSerializer.cs @@ -0,0 +1,378 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// OData serializer for serializing a collection of + /// The Collection is of which is the base interface implemented by all objects which are a part of the DeltaFeed payload. + /// + public class ODataDeltaFeedSerializer : ODataEdmTypeSerializer + { + private const string DeltaFeed = "deltafeed"; + + /// + /// Initializes a new instance of . + /// + /// The to use to write nested entries. + public ODataDeltaFeedSerializer(ODataSerializerProvider serializerProvider) + : base(ODataPayloadKind.Delta, serializerProvider) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + if (graph == null) + { + throw new SerializationException(Error.Format(SRResources.CannotSerializerNull, DeltaFeed)); + } + + IEdmEntitySetBase entitySet = writeContext.NavigationSource as IEdmEntitySetBase; + if (entitySet == null) + { + throw new SerializationException(SRResources.EntitySetMissingDuringSerialization); + } + + IEdmTypeReference feedType = writeContext.GetEdmType(graph, type); + Contract.Assert(feedType != null); + + IEdmEntityTypeReference entityType = GetResourceType(feedType).AsEntity(); + ODataWriter writer = messageWriter.CreateODataDeltaResourceSetWriter(entitySet, entityType.EntityDefinition()); + + WriteDeltaFeedInline(graph, feedType, writer, writeContext); + } + + /// + /// Writes the given object specified by the parameter graph as a part of an existing OData message using the given + /// messageWriter and the writeContext. + /// + /// The object to be written. + /// The expected EDM type of the object represented by . + /// The to be used for writing. + /// The . + public virtual void WriteDeltaFeedInline(object graph, IEdmTypeReference expectedType, ODataWriter writer, + ODataSerializerContext writeContext) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + if (expectedType == null) + { + throw Error.ArgumentNull("expectedType"); + } + if (graph == null) + { + throw new SerializationException(Error.Format(SRResources.CannotSerializerNull, DeltaFeed)); + } + + IEnumerable enumerable = graph as IEnumerable; // Data to serialize + if (enumerable == null) + { + throw new SerializationException( + Error.Format(SRResources.CannotWriteType, GetType().Name, graph.GetType().FullName)); + } + + WriteFeed(enumerable, expectedType, writer, writeContext); + } + + private void WriteFeed(IEnumerable enumerable, IEdmTypeReference feedType, ODataWriter writer, + ODataSerializerContext writeContext) + { + Contract.Assert(writer != null); + Contract.Assert(writeContext != null); + Contract.Assert(enumerable != null); + Contract.Assert(feedType != null); + + IEdmStructuredTypeReference elementType = GetResourceType(feedType); + + if (elementType.IsComplex()) + { + ODataResourceSet resourceSet = new ODataResourceSet() + { + TypeName = feedType.FullName() + }; + + writer.WriteStart(resourceSet); + + ODataResourceSerializer entrySerializer = SerializerProvider.GetEdmTypeSerializer(elementType) as ODataResourceSerializer; + if (entrySerializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, elementType.FullName())); + } + + foreach (object entry in enumerable) + { + entrySerializer.WriteDeltaObjectInline(entry, elementType, writer, writeContext); + } + } + else + { + ODataDeltaResourceSet deltaFeed = CreateODataDeltaFeed(enumerable, feedType.AsCollection(), writeContext); + if (deltaFeed == null) + { + throw new SerializationException(Error.Format(SRResources.CannotSerializerNull, DeltaFeed)); + } + + // save the next page link for later to support JSON odata.streaming. + Func nextLinkGenerator = GetNextLinkGenerator(deltaFeed, enumerable, writeContext); + deltaFeed.NextPageLink = null; + + //Start writing of the Delta Feed + writer.WriteStart(deltaFeed); + + object lastResource = null; + //Iterate over all the entries present and select the appropriate write method. + //Write method creates ODataDeltaDeletedEntry / ODataDeltaDeletedLink / ODataDeltaLink or ODataEntry. + foreach (object entry in enumerable) + { + if (entry == null) + { + throw new SerializationException(SRResources.NullElementInCollection); + } + + lastResource = entry; + IEdmChangedObject edmChangedObject = entry as IEdmChangedObject; + if (edmChangedObject == null) + { + throw new SerializationException(Error.Format(SRResources.CannotWriteType, GetType().Name, enumerable.GetType().FullName)); + } + + switch (edmChangedObject.DeltaKind) + { + case EdmDeltaEntityKind.DeletedEntry: + WriteDeltaDeletedEntry(entry, writer, writeContext); + break; + case EdmDeltaEntityKind.DeletedLinkEntry: + WriteDeltaDeletedLink(entry, writer, writeContext); + break; + case EdmDeltaEntityKind.LinkEntry: + WriteDeltaLink(entry, writer, writeContext); + break; + case EdmDeltaEntityKind.Entry: + { + ODataResourceSerializer entrySerializer = SerializerProvider.GetEdmTypeSerializer(elementType) as ODataResourceSerializer; + if (entrySerializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, elementType.FullName())); + } + entrySerializer.WriteDeltaObjectInline(entry, elementType, writer, writeContext); + break; + } + default: + break; + } + } + + // Subtle and surprising behavior: If the NextPageLink property is set before calling WriteStart(feed), + // the next page link will be written early in a manner not compatible with odata.streaming=true. Instead, if + // the next page link is not set when calling WriteStart(feed) but is instead set later on that feed + // object before calling WriteEnd(), the next page link will be written at the end, as required for + // odata.streaming=true support. + + deltaFeed.NextPageLink = nextLinkGenerator(lastResource); + } + + //End Writing of the Delta Feed + writer.WriteEnd(); + } + + /// + /// Creates a function that takes in an object and generates nextlink uri. + /// + /// The resource set describing a collection of structured objects. + /// >The instance representing the resourceSet being written. + /// The serializer context. + /// The function that generates the NextLink from an object. + /// + internal static Func GetNextLinkGenerator(ODataDeltaResourceSet deltaFeed, IEnumerable enumerable, ODataSerializerContext writeContext) + { + return ODataResourceSetSerializer.GetNextLinkGenerator(deltaFeed, enumerable, writeContext); + } + + /// + /// Create the to be written for the given feed instance. + /// + /// The instance representing the feed being written. + /// The EDM type of the feed being written. + /// The serializer context. + /// The created object. + public virtual ODataDeltaResourceSet CreateODataDeltaFeed(IEnumerable feedInstance, IEdmCollectionTypeReference feedType, + ODataSerializerContext writeContext) + { + ODataDeltaResourceSet feed = new ODataDeltaResourceSet(); + + if (writeContext.ExpandedResource == null) + { + // If we have more OData format specific information apply it now, only if we are the root feed. + PageResult odataFeedAnnotations = feedInstance as PageResult; + if (odataFeedAnnotations != null) + { + feed.Count = odataFeedAnnotations.Count; + feed.NextPageLink = odataFeedAnnotations.NextPageLink; + } + else if (writeContext.Request != null) + { + feed.NextPageLink = writeContext.InternalRequest.Context.NextLink; + feed.DeltaLink = writeContext.InternalRequest.Context.DeltaLink; + + long? countValue = writeContext.InternalRequest.Context.TotalCount; + if (countValue.HasValue) + { + feed.Count = countValue.Value; + } + } + } + return feed; + } + + /// + /// Writes the given deltaDeletedEntry specified by the parameter graph as a part of an existing OData message using the given + /// messageWriter and the writeContext. + /// + /// The object to be written. + /// The to be used for writing. + /// The . + public virtual void WriteDeltaDeletedEntry(object graph, ODataWriter writer, ODataSerializerContext writeContext) + { + EdmDeltaDeletedEntityObject edmDeltaDeletedEntity = graph as EdmDeltaDeletedEntityObject; + if (edmDeltaDeletedEntity == null) + { + throw new SerializationException(Error.Format(SRResources.CannotWriteType, GetType().Name, graph.GetType().FullName)); + } + + Uri id = StringToUri(edmDeltaDeletedEntity.Id); + ODataDeletedResource deletedResource = new ODataDeletedResource(id, edmDeltaDeletedEntity.Reason); + + if (edmDeltaDeletedEntity.NavigationSource != null) + { + ODataResourceSerializationInfo serializationInfo = new ODataResourceSerializationInfo + { + NavigationSourceName = edmDeltaDeletedEntity.NavigationSource.Name + }; + deletedResource.SetSerializationInfo(serializationInfo); + } + + if (deletedResource != null) + { + writer.WriteStart(deletedResource); + writer.WriteEnd(); + } + } + + /// + /// Writes the given deltaDeletedLink specified by the parameter graph as a part of an existing OData message using the given + /// messageWriter and the writeContext. + /// + /// The object to be written. + /// The to be used for writing. + /// The . + public virtual void WriteDeltaDeletedLink(object graph, ODataWriter writer, ODataSerializerContext writeContext) + { + EdmDeltaDeletedLink edmDeltaDeletedLink = graph as EdmDeltaDeletedLink; + if (edmDeltaDeletedLink == null) + { + throw new SerializationException(Error.Format(SRResources.CannotWriteType, GetType().Name, graph.GetType().FullName)); + } + + ODataDeltaDeletedLink deltaDeletedLink = new ODataDeltaDeletedLink( + edmDeltaDeletedLink.Source, + edmDeltaDeletedLink.Target, + edmDeltaDeletedLink.Relationship); + + if (deltaDeletedLink != null) + { + writer.WriteDeltaDeletedLink(deltaDeletedLink); + } + } + + /// + /// Writes the given deltaLink specified by the parameter graph as a part of an existing OData message using the given + /// messageWriter and the writeContext. + /// + /// The object to be written. + /// The to be used for writing. + /// The . + public virtual void WriteDeltaLink(object graph, ODataWriter writer, ODataSerializerContext writeContext) + { + EdmDeltaLink edmDeltaLink = graph as EdmDeltaLink; + if (edmDeltaLink == null) + { + throw new SerializationException(Error.Format(SRResources.CannotWriteType, GetType().Name, graph.GetType().FullName)); + } + + ODataDeltaLink deltaLink = new ODataDeltaLink( + edmDeltaLink.Source, + edmDeltaLink.Target, + edmDeltaLink.Relationship); + + if (deltaLink != null) + { + writer.WriteDeltaLink(deltaLink); + } + } + + private static IEdmStructuredTypeReference GetResourceType(IEdmTypeReference feedType) + { + if (feedType.IsCollection()) + { + IEdmTypeReference elementType = feedType.AsCollection().ElementType(); + if (elementType.IsEntity() || elementType.IsComplex()) + { + return elementType.AsStructured(); + } + } + + string message = Error.Format(SRResources.CannotWriteType, typeof(ODataResourceSetSerializer).Name, feedType.FullName()); + throw new SerializationException(message); + } + + /// + /// Safely returns the specified string as a relative or absolute Uri. + /// + /// The string to convert to a Uri. + /// The string as a Uri. + internal static Uri StringToUri(string uriString) + { + Uri uri = null; + try + { + uri = new Uri(uriString, UriKind.RelativeOrAbsolute); + } + catch (FormatException) + { + // The Uri constructor throws a format exception if it can't figure out the type of Uri + uri = new Uri(uriString, UriKind.Relative); + } + + return uri; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEdmTypeSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEdmTypeSerializer.cs new file mode 100644 index 0000000..4235e01 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEdmTypeSerializer.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an that serializes instances of objects backed by an . + /// + public abstract class ODataEdmTypeSerializer : ODataSerializer + { + /// + /// Initializes a new instance of the class. + /// + /// The kind of OData payload that this serializer generates. + protected ODataEdmTypeSerializer(ODataPayloadKind payloadKind) + : base(payloadKind) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The kind of OData payload that this serializer generates. + /// The to use to write inner objects. + protected ODataEdmTypeSerializer(ODataPayloadKind payloadKind, ODataSerializerProvider serializerProvider) + : this(payloadKind) + { + if (serializerProvider == null) + { + throw Error.ArgumentNull("serializerProvider"); + } + + SerializerProvider = serializerProvider; + } + + /// + /// Gets the that can be used to write inner objects. + /// + public ODataSerializerProvider SerializerProvider { get; private set; } + + /// + /// Writes the given object specified by the parameter graph as a part of an existing OData message using the given + /// messageWriter and the writeContext. + /// + /// The object to be written. + /// The expected EDM type of the object represented by . + /// The to be used for writing. + /// The . + public virtual void WriteObjectInline(object graph, IEdmTypeReference expectedType, ODataWriter writer, + ODataSerializerContext writeContext) + { + throw Error.NotSupported(SRResources.WriteObjectInlineNotSupported, GetType().Name); + } + + /// + /// Creates an for the object represented by . + /// + /// The value of the to be created. + /// The expected EDM type of the object represented by . + /// The . + /// The created. + public virtual ODataValue CreateODataValue(object graph, IEdmTypeReference expectedType, ODataSerializerContext writeContext) + { + throw Error.NotSupported(SRResources.CreateODataValueNotSupported, GetType().Name); + } + + internal virtual ODataProperty CreateProperty(object graph, IEdmTypeReference expectedType, string elementName, + ODataSerializerContext writeContext) + { + Contract.Assert(elementName != null); + return new ODataProperty + { + Name = elementName, + Value = CreateODataValue(graph, expectedType, writeContext) + }; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinkSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinkSerializer.cs new file mode 100644 index 0000000..90ea75d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinkSerializer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing $ref response. + /// + // For example, the response to the url http://localhost/Products(10)/Category/$ref gets serialized using this. + public class ODataEntityReferenceLinkSerializer : ODataSerializer + { + /// + /// Initializes a new instance of . + /// + public ODataEntityReferenceLinkSerializer() + : base(ODataPayloadKind.EntityReferenceLink) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + if (graph != null) + { + ODataEntityReferenceLink entityReferenceLink = graph as ODataEntityReferenceLink; + if (entityReferenceLink == null) + { + Uri uri = graph as Uri; + if (uri == null) + { + throw new SerializationException(Error.Format(SRResources.CannotWriteType, GetType().Name, graph.GetType().FullName)); + } + + entityReferenceLink = new ODataEntityReferenceLink { Url = uri }; + } + + messageWriter.WriteEntityReferenceLink(entityReferenceLink); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinksSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinksSerializer.cs new file mode 100644 index 0000000..64a4381 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEntityReferenceLinksSerializer.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing $ref response for a collection navigation property. + /// + public class ODataEntityReferenceLinksSerializer : ODataSerializer + { + /// + /// Initializes a new instance of the class. + /// + public ODataEntityReferenceLinksSerializer() + : base(ODataPayloadKind.EntityReferenceLinks) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + if (graph != null) + { + ODataEntityReferenceLinks entityReferenceLinks = graph as ODataEntityReferenceLinks; + if (entityReferenceLinks == null) + { + IEnumerable uris = graph as IEnumerable; + if (uris == null) + { + throw new SerializationException(Error.Format(SRResources.CannotWriteType, GetType().Name, graph.GetType().FullName)); + } + + entityReferenceLinks = new ODataEntityReferenceLinks + { + Links = uris.Select(uri => new ODataEntityReferenceLink { Url = uri }) + }; + + if (writeContext.Request != null) + { + entityReferenceLinks.Count = writeContext.InternalRequest.Context.TotalCount; + } + } + + messageWriter.WriteEntityReferenceLinks(entityReferenceLinks); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEnumSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEnumSerializer.cs new file mode 100644 index 0000000..eb8f839 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataEnumSerializer.cs @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing 's. + /// + public class ODataEnumSerializer : ODataEdmTypeSerializer + { + /// + /// Initializes a new instance of . + /// + public ODataEnumSerializer(ODataSerializerProvider serializerProvider) + : base(ODataPayloadKind.Property, serializerProvider) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + if (writeContext.RootElementName == null) + { + throw Error.Argument("writeContext", SRResources.RootElementNameMissing, typeof(ODataSerializerContext).Name); + } + + IEdmTypeReference edmType = writeContext.GetEdmType(graph, type); + Contract.Assert(edmType != null); + + messageWriter.WriteProperty(CreateProperty(graph, edmType, writeContext.RootElementName, writeContext)); + } + + /// + public sealed override ODataValue CreateODataValue(object graph, IEdmTypeReference expectedType, ODataSerializerContext writeContext) + { + if (!expectedType.IsEnum()) + { + throw Error.InvalidOperation(SRResources.CannotWriteType, typeof(ODataEnumSerializer).Name, expectedType.FullName()); + } + + ODataEnumValue value = CreateODataEnumValue(graph, expectedType.AsEnum(), writeContext); + if (value == null) + { + return new ODataNullValue(); + } + + return value; + } + + /// + /// Creates an for the object represented by . + /// + /// The enum value. + /// The EDM enum type of the value. + /// The serializer write context. + /// The created . + public virtual ODataEnumValue CreateODataEnumValue(object graph, IEdmEnumTypeReference enumType, + ODataSerializerContext writeContext) + { + if (graph == null) + { + return null; + } + + string value = null; + if (TypeHelper.IsEnum(graph.GetType())) + { + value = graph.ToString(); + } + else + { + if (graph.GetType() == typeof(EdmEnumObject)) + { + value = ((EdmEnumObject)graph).Value; + } + } + + // Enum member supports model alias case. So, try to use the Edm member name to create Enum value. + var memberMapAnnotation = writeContext.Model.GetClrEnumMemberAnnotation(enumType.EnumDefinition()); + if (memberMapAnnotation != null) + { + var edmEnumMember = memberMapAnnotation.GetEdmEnumMember((Enum)graph); + if (edmEnumMember != null) + { + value = edmEnumMember.Name; + } + } + + ODataEnumValue enumValue = new ODataEnumValue(value, enumType.FullName()); + + ODataMetadataLevel metadataLevel = writeContext != null + ? writeContext.MetadataLevel + : ODataMetadataLevel.MinimalMetadata; + AddTypeNameAnnotationAsNeeded(enumValue, enumType, metadataLevel); + + return enumValue; + } + + internal static void AddTypeNameAnnotationAsNeeded(ODataEnumValue enumValue, IEdmEnumTypeReference enumType, ODataMetadataLevel metadataLevel) + { + // ODataLib normally has the caller decide whether or not to serialize properties by leaving properties + // null when values should not be serialized. The TypeName property is different and should always be + // provided to ODataLib to enable model validation. A separate annotation is used to decide whether or not + // to serialize the type name (a null value prevents serialization). + + Contract.Assert(enumValue != null); + + // Only add an annotation if we want to override ODataLib's default type name serialization behavior. + if (ShouldAddTypeNameAnnotation(metadataLevel)) + { + string typeName; + + // Provide the type name to serialize (or null to force it not to serialize). + if (ShouldSuppressTypeNameSerialization(metadataLevel)) + { + typeName = null; + } + else + { + typeName = enumType.FullName(); + } + + enumValue.TypeAnnotation = new ODataTypeAnnotation(typeName); + } + } + + private static bool ShouldAddTypeNameAnnotation(ODataMetadataLevel metadataLevel) + { + switch (metadataLevel) + { + case ODataMetadataLevel.MinimalMetadata: + return false; + case ODataMetadataLevel.FullMetadata: + case ODataMetadataLevel.NoMetadata: + default: + return true; + } + } + + private static bool ShouldSuppressTypeNameSerialization(ODataMetadataLevel metadataLevel) + { + Contract.Assert(metadataLevel != ODataMetadataLevel.MinimalMetadata); + + switch (metadataLevel) + { + case ODataMetadataLevel.NoMetadata: + return true; + case ODataMetadataLevel.FullMetadata: + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataErrorSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataErrorSerializer.cs new file mode 100644 index 0000000..74a530f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataErrorSerializer.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an to serialize s. + /// + public partial class ODataErrorSerializer : ODataSerializer + { + /// + /// Initializes a new instance of the class . + /// + public ODataErrorSerializer() + : base(ODataPayloadKind.Error) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (graph == null) + { + throw Error.ArgumentNull("graph"); + } + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + ODataError oDataError = graph as ODataError; + if (oDataError == null) + { + if (!IsHttpError(graph)) + { + string message = Error.Format(SRResources.ErrorTypeMustBeODataErrorOrHttpError, graph.GetType().FullName); + throw new SerializationException(message); + } + else + { + oDataError = CreateODataError(graph); + } + } + + bool includeDebugInformation = oDataError.InnerError != null; + messageWriter.WriteError(oDataError, includeDebugInformation); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataMetadataSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataMetadataSerializer.cs new file mode 100644 index 0000000..c3f16bc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataMetadataSerializer.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing $metadata. + /// + public class ODataMetadataSerializer : ODataSerializer + { + /// + /// Initializes a new instance of . + /// + public ODataMetadataSerializer() + : base(ODataPayloadKind.MetadataDocument) + { + } + + /// + /// The metadata written is from the model set on the . The + /// is not used. + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + // NOTE: ODataMessageWriter doesn't have a way to set the IEdmModel. So, there is an underlying assumption here that + // the model received by this method and the model passed(from configuration) while building ODataMessageWriter is the same (clr object). + messageWriter.WriteMetadataDocument(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPayloadKindHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPayloadKindHelper.cs new file mode 100644 index 0000000..840b347 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPayloadKindHelper.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + internal static class ODataPayloadKindHelper + { + public static bool IsDefined(ODataPayloadKind payloadKind) + { + return payloadKind == ODataPayloadKind.Batch + || payloadKind == ODataPayloadKind.BinaryValue + || payloadKind == ODataPayloadKind.Collection + || payloadKind == ODataPayloadKind.EntityReferenceLink + || payloadKind == ODataPayloadKind.EntityReferenceLinks + || payloadKind == ODataPayloadKind.Resource + || payloadKind == ODataPayloadKind.Error + || payloadKind == ODataPayloadKind.ResourceSet + || payloadKind == ODataPayloadKind.MetadataDocument + || payloadKind == ODataPayloadKind.Parameter + || payloadKind == ODataPayloadKind.Property + || payloadKind == ODataPayloadKind.ServiceDocument + || payloadKind == ODataPayloadKind.Value + || payloadKind == ODataPayloadKind.IndividualProperty + || payloadKind == ODataPayloadKind.Delta + || payloadKind == ODataPayloadKind.Asynchronous + || payloadKind == ODataPayloadKind.Unsupported; + } + + public static void Validate(ODataPayloadKind payloadKind, string parameterName) + { + if (!IsDefined(payloadKind)) + { + throw Error.InvalidEnumArgument(parameterName, (int)payloadKind, typeof(ODataPayloadKind)); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPrimitiveSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPrimitiveSerializer.cs new file mode 100644 index 0000000..b9e0611 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataPrimitiveSerializer.cs @@ -0,0 +1,279 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. +using System.Data.Linq; +#endif +using System.Diagnostics.Contracts; +using System.Xml.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing 's. + /// + public class ODataPrimitiveSerializer : ODataEdmTypeSerializer + { + /// + /// Initializes a new instance of . + /// + public ODataPrimitiveSerializer() + : base(ODataPayloadKind.Property) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + if (writeContext.RootElementName == null) + { + throw Error.Argument("writeContext", SRResources.RootElementNameMissing, typeof(ODataSerializerContext).Name); + } + + IEdmTypeReference edmType = writeContext.GetEdmType(graph, type); + Contract.Assert(edmType != null); + + messageWriter.WriteProperty(CreateProperty(graph, edmType, writeContext.RootElementName, writeContext)); + } + + /// + public sealed override ODataValue CreateODataValue(object graph, IEdmTypeReference expectedType, ODataSerializerContext writeContext) + { + if (!expectedType.IsPrimitive()) + { + throw Error.InvalidOperation(SRResources.CannotWriteType, typeof(ODataPrimitiveSerializer), expectedType.FullName()); + } + + ODataPrimitiveValue value = CreateODataPrimitiveValue(graph, expectedType.AsPrimitive(), writeContext); + if (value == null) + { + return new ODataNullValue(); + } + + return value; + } + + /// + /// Creates an for the object represented by . + /// + /// The primitive value. + /// The EDM primitive type of the value. + /// The serializer write context. + /// The created . + public virtual ODataPrimitiveValue CreateODataPrimitiveValue(object graph, IEdmPrimitiveTypeReference primitiveType, + ODataSerializerContext writeContext) + { + // TODO: Bug 467598: validate the type of the object being passed in here with the underlying primitive type. + return CreatePrimitive(graph, primitiveType, writeContext); + } + + internal static void AddTypeNameAnnotationAsNeeded(ODataPrimitiveValue primitive, IEdmPrimitiveTypeReference primitiveType, + ODataMetadataLevel metadataLevel) + { + // ODataLib normally has the caller decide whether or not to serialize properties by leaving properties + // null when values should not be serialized. The TypeName property is different and should always be + // provided to ODataLib to enable model validation. A separate annotation is used to decide whether or not + // to serialize the type name (a null value prevents serialization). + + Contract.Assert(primitive != null); + + object value = primitive.Value; + string typeName = null; // Set null to force the type name not to serialize. + + // Provide the type name to serialize. + if (!ShouldSuppressTypeNameSerialization(value, metadataLevel)) + { + typeName = primitiveType.FullName(); + } + + primitive.TypeAnnotation = new ODataTypeAnnotation(typeName); + } + + internal static ODataPrimitiveValue CreatePrimitive(object value, IEdmPrimitiveTypeReference primitiveType, + ODataSerializerContext writeContext) + { + if (value == null) + { + return null; + } + + object supportedValue = ConvertPrimitiveValue(value, primitiveType); + ODataPrimitiveValue primitive = new ODataPrimitiveValue(supportedValue); + + if (writeContext != null) + { + AddTypeNameAnnotationAsNeeded(primitive, primitiveType, writeContext.MetadataLevel); + } + + return primitive; + } + + internal static object ConvertPrimitiveValue(object value, IEdmPrimitiveTypeReference primitiveType) + { + if (value == null) + { + return null; + } + + Type type = value.GetType(); + if (primitiveType != null && primitiveType.IsDate() && TypeHelper.IsDateTime(type)) + { + Date dt = (DateTime)value; + return dt; + } + + if (primitiveType != null && primitiveType.IsTimeOfDay() && TypeHelper.IsTimeSpan(type)) + { + TimeOfDay tod = (TimeSpan)value; + return tod; + } + + return ConvertUnsupportedPrimitives(value); + } + + internal static object ConvertUnsupportedPrimitives(object value) + { + if (value != null) + { + Type type = value.GetType(); + + // Note that type cannot be a nullable type as value is not null and it is boxed. + switch (Type.GetTypeCode(type)) + { + case TypeCode.Char: + return new String((char)value, 1); + + case TypeCode.UInt16: + return (int)(ushort)value; + + case TypeCode.UInt32: + return (long)(uint)value; + + case TypeCode.UInt64: + return checked((long)(ulong)value); + + case TypeCode.DateTime: + DateTime dateTime = (DateTime)value; + + TimeZoneInfo timeZone = TimeZoneInfoHelper.TimeZone; + TimeSpan utcOffset = timeZone.GetUtcOffset(dateTime); + if (utcOffset >= TimeSpan.Zero) + { + if (dateTime <= DateTime.MinValue + utcOffset) + { + return DateTimeOffset.MinValue; + } + } + else + { + if (dateTime >= DateTime.MaxValue + utcOffset) + { + return DateTimeOffset.MaxValue; + } + } + + if (dateTime.Kind == DateTimeKind.Local) + { + TimeZoneInfo localTimeZoneInfo = TimeZoneInfo.Local; + TimeSpan localTimeSpan = localTimeZoneInfo.GetUtcOffset(dateTime); + if (localTimeSpan < TimeSpan.Zero) + { + if (dateTime >= DateTime.MaxValue + localTimeSpan) + { + return DateTimeOffset.MaxValue; + } + } + else + { + if (dateTime <= DateTime.MinValue + localTimeSpan) + { + return DateTimeOffset.MinValue; + } + } + + return TimeZoneInfo.ConvertTime(new DateTimeOffset(dateTime), timeZone); + } + + if (dateTime.Kind == DateTimeKind.Utc) + { + return TimeZoneInfo.ConvertTime(new DateTimeOffset(dateTime), timeZone); + } + + return new DateTimeOffset(dateTime, timeZone.GetUtcOffset(dateTime)); + + default: + if (type == typeof(char[])) + { + return new String(value as char[]); + } + else if (type == typeof(XElement)) + { + return ((XElement)value).ToString(); + } +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. + else if (type == typeof(Binary)) + { + return ((Binary)value).ToArray(); + } +#endif + break; + } + } + + return value; + } + + internal static bool CanTypeBeInferredInJson(object value) + { + Contract.Assert(value != null); + + TypeCode typeCode = Type.GetTypeCode(value.GetType()); + + switch (typeCode) + { + // The type for a Boolean, Int32 or String can always be inferred in JSON. + case TypeCode.Boolean: + case TypeCode.Int32: + case TypeCode.String: + return true; + // The type for a Double can be inferred in JSON ... + case TypeCode.Double: + double doubleValue = (double)value; + // ... except for NaN or Infinity (positive or negative). + if (Double.IsNaN(doubleValue) || Double.IsInfinity(doubleValue)) + { + return false; + } + else + { + return true; + } + default: + return false; + } + } + + internal static bool ShouldSuppressTypeNameSerialization(object value, ODataMetadataLevel metadataLevel) + { + // For dynamic properties in minimal metadata level, the type name always appears as declared property. + if (metadataLevel != ODataMetadataLevel.FullMetadata) + { + return true; + } + + return CanTypeBeInferredInJson(value); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataRawValueSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataRawValueSerializer.cs new file mode 100644 index 0000000..ebd3a2d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataRawValueSerializer.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing the raw value of an . + /// + public class ODataRawValueSerializer : ODataSerializer + { + /// + /// Initializes a new instance of . + /// + public ODataRawValueSerializer() + : base(ODataPayloadKind.Value) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + if (graph == null) + { + throw Error.ArgumentNull("graph"); + } + + if (TypeHelper.IsEnum(graph.GetType())) + { + messageWriter.WriteValue(graph.ToString()); + } + else + { + messageWriter.WriteValue(ODataPrimitiveSerializer.ConvertUnsupportedPrimitives(graph)); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs new file mode 100644 index 0000000..6ef7f31 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSerializer.cs @@ -0,0 +1,1337 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// ODataSerializer for serializing instances of or + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")] + public class ODataResourceSerializer : ODataEdmTypeSerializer + { + private const string Resource = "Resource"; + + /// + public ODataResourceSerializer(ODataSerializerProvider serializerProvider) + : base(ODataPayloadKind.Resource, serializerProvider) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, + ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + IEdmTypeReference edmType = writeContext.GetEdmType(graph, type); + Contract.Assert(edmType != null); + + IEdmNavigationSource navigationSource = writeContext.NavigationSource; + ODataWriter writer = messageWriter.CreateODataResourceWriter(navigationSource, edmType.ToStructuredType()); + WriteObjectInline(graph, edmType, writer, writeContext); + } + + /// + public override void WriteObjectInline(object graph, IEdmTypeReference expectedType, ODataWriter writer, + ODataSerializerContext writeContext) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + if (graph == null || graph is NullEdmComplexObject) + { + throw new SerializationException(Error.Format(SRResources.CannotSerializerNull, Resource)); + } + else + { + WriteResource(graph, writer, writeContext, expectedType); + } + } + + /// + /// Writes the given object specified by the parameter graph as a part of an existing OData message using the given + /// deltaWriter and the writeContext. + /// + /// The object to be written. + /// The expected EDM type of the object represented by . + /// The to be used for writing. + /// The . + public virtual void WriteDeltaObjectInline(object graph, IEdmTypeReference expectedType, ODataWriter writer, + ODataSerializerContext writeContext) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + if (graph == null) + { + throw new SerializationException(Error.Format(SRResources.CannotSerializerNull, Resource)); + } + else + { + WriteDeltaResource(graph, writer, writeContext); + } + } + + private void WriteDeltaResource(object graph, ODataWriter writer, ODataSerializerContext writeContext) + { + Contract.Assert(writeContext != null); + + IEdmStructuredTypeReference structuredType = GetResourceType(graph, writeContext); + ResourceContext resourceContext = new ResourceContext(writeContext, structuredType, graph); + EdmDeltaEntityObject deltaResource = graph as EdmDeltaEntityObject; + if (deltaResource != null && deltaResource.NavigationSource != null) + { + resourceContext.NavigationSource = deltaResource.NavigationSource; + } + + SelectExpandNode selectExpandNode = CreateSelectExpandNode(resourceContext); + if (selectExpandNode != null) + { + ODataResource resource = CreateResource(selectExpandNode, resourceContext); + + if (resource != null) + { + writer.WriteStart(resource); + WriteDeltaComplexProperties(selectExpandNode.SelectedComplexProperties, resourceContext, writer); + //TODO: Need to add support to write Navigation Links, etc. using Delta Writer + //https://github.com/OData/odata.net/issues/155 + //CLEANUP: merge delta logic with regular logic; requires common base between ODataWriter and ODataDeltaWriter + //WriteDynamicComplexProperties(resourceContext, writer); + //WriteNavigationLinks(selectExpandNode.SelectedNavigationProperties, resourceContext, writer); + //WriteExpandedNavigationProperties(selectExpandNode.ExpandedNavigationProperties, resourceContext, writer); + + writer.WriteEnd(); + } + } + } + + private void WriteDeltaComplexProperties(IEnumerable complexProperties, + ResourceContext resourceContext, ODataWriter writer) + { + Contract.Assert(complexProperties != null); + Contract.Assert(resourceContext != null); + Contract.Assert(writer != null); + + if (null != resourceContext.EdmObject && resourceContext.EdmObject.IsDeltaResource()) + { + IDelta deltaObject = resourceContext.EdmObject as IDelta; + IEnumerable changedProperties = deltaObject.GetChangedPropertyNames(); + complexProperties = complexProperties.Where(p => changedProperties.Contains(p.Name)); + } + + foreach (IEdmStructuralProperty complexProperty in complexProperties) + { + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo + { + IsCollection = complexProperty.Type.IsCollection(), + Name = complexProperty.Name + }; + + writer.WriteStart(nestedResourceInfo); + WriteDeltaComplexAndExpandedNavigationProperty(complexProperty, null, resourceContext, writer); + writer.WriteEnd(); + } + } + + private void WriteDeltaComplexAndExpandedNavigationProperty(IEdmProperty edmProperty, SelectExpandClause selectExpandClause, + ResourceContext resourceContext, ODataWriter writer) + { + Contract.Assert(edmProperty != null); + Contract.Assert(resourceContext != null); + Contract.Assert(writer != null); + + object propertyValue = resourceContext.GetPropertyValue(edmProperty.Name); + + if (propertyValue == null || propertyValue is NullEdmComplexObject) + { + if (edmProperty.Type.IsCollection()) + { + // A complex or navigation property whose Type attribute specifies a collection, the collection always exists, + // it may just be empty. + // If a collection of complex or entities can be related, it is represented as a JSON array. An empty + // collection of resources (one that contains no resource) is represented as an empty JSON array. + writer.WriteStart(new ODataResourceSet + { + TypeName = edmProperty.Type.FullName() + }); + } + else + { + // If at most one resource can be related, the value is null if no resource is currently related. + writer.WriteStart(resource: null); + } + + writer.WriteEnd(); + } + else + { + // create the serializer context for the complex and expanded item. + ODataSerializerContext nestedWriteContext = new ODataSerializerContext(resourceContext, selectExpandClause, edmProperty); + + // write object. + + // TODO: enable overriding serializer based on type. Currentlky requires serializer supports WriteDeltaObjectinline, because it takes an ODataDeltaWriter + // ODataEdmTypeSerializer serializer = SerializerProvider.GetEdmTypeSerializer(edmProperty.Type); + // if (serializer == null) + // { + // throw new SerializationException( + // Error.Format(SRResources.TypeCannotBeSerialized, edmProperty.Type.ToTraceString())); + // } + if (edmProperty.Type.IsCollection()) + { + ODataDeltaFeedSerializer serializer = new ODataDeltaFeedSerializer(SerializerProvider); + serializer.WriteDeltaFeedInline(propertyValue, edmProperty.Type, writer, nestedWriteContext); + } + else + { + ODataResourceSerializer serializer = new ODataResourceSerializer(SerializerProvider); + serializer.WriteDeltaObjectInline(propertyValue, edmProperty.Type, writer, nestedWriteContext); + } + } + } + + private static IEnumerable CreateODataPropertiesFromDynamicType(EdmEntityType entityType, object graph, + Dictionary dynamicTypeProperties) + { + Contract.Assert(dynamicTypeProperties != null); + + var properties = new List(); + var dynamicObject = graph as DynamicTypeWrapper; + if (dynamicObject == null) + { + var dynamicEnumerable = (graph as IEnumerable); + if (dynamicEnumerable != null) + { + dynamicObject = dynamicEnumerable.SingleOrDefault(); + } + } + if (dynamicObject != null) + { + foreach (var prop in dynamicObject.Values) + { + if (prop.Value != null + && (prop.Value is DynamicTypeWrapper || (prop.Value is IEnumerable))) + { + IEdmProperty edmProperty = entityType.Properties() + .FirstOrDefault(p => p.Name.Equals(prop.Key)); + if (edmProperty != null) + { + dynamicTypeProperties.Add(edmProperty, prop.Value); + } + } + else + { + var property = new ODataProperty + { + Name = prop.Key, + Value = prop.Value + }; + + properties.Add(property); + } + } + } + + return properties; + } + + private void WriteDynamicTypeResource(object graph, ODataWriter writer, IEdmTypeReference expectedType, + ODataSerializerContext writeContext) + { + var dynamicTypeProperties = new Dictionary(); + var entityType = expectedType.Definition as EdmEntityType; + var resource = new ODataResource() + { + TypeName = expectedType.FullName(), + Properties = CreateODataPropertiesFromDynamicType(entityType, graph, dynamicTypeProperties) + }; + + resource.IsTransient = true; + writer.WriteStart(resource); + foreach (var property in dynamicTypeProperties.Keys) + { + var resourceContext = new ResourceContext(writeContext, expectedType.AsEntity(), graph); + if (entityType.NavigationProperties().Any(p => p.Type.Equals(property.Type)) && !(property.Type is EdmCollectionTypeReference)) + { + var navigationProperty = entityType.NavigationProperties().FirstOrDefault(p => p.Type.Equals(property.Type)); + var navigationLink = CreateNavigationLink(navigationProperty, resourceContext); + if (navigationLink != null) + { + writer.WriteStart(navigationLink); + WriteDynamicTypeResource(dynamicTypeProperties[property], writer, property.Type, writeContext); + writer.WriteEnd(); + } + } + else + { + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo + { + IsCollection = property.Type.IsCollection(), + Name = property.Name + }; + + writer.WriteStart(nestedResourceInfo); + WriteDynamicComplexProperty(dynamicTypeProperties[property], property.Type, resourceContext, writer); + writer.WriteEnd(); + } + } + + writer.WriteEnd(); + } + + private void WriteResource(object graph, ODataWriter writer, ODataSerializerContext writeContext, + IEdmTypeReference expectedType) + { + Contract.Assert(writeContext != null); + + if (EdmLibHelpers.IsDynamicTypeWrapper(graph.GetType())) + { + WriteDynamicTypeResource(graph, writer, expectedType, writeContext); + return; + } + + IEdmStructuredTypeReference structuredType = GetResourceType(graph, writeContext); + ResourceContext resourceContext = new ResourceContext(writeContext, structuredType, graph); + + SelectExpandNode selectExpandNode = CreateSelectExpandNode(resourceContext); + if (selectExpandNode != null) + { + ODataResource resource = CreateResource(selectExpandNode, resourceContext); + if (resource != null) + { + if (resourceContext.SerializerContext.ExpandReference) + { + writer.WriteEntityReferenceLink(new ODataEntityReferenceLink + { + Url = resource.Id + }); + } + else + { + writer.WriteStart(resource); + WriteComplexProperties(selectExpandNode.SelectedComplexProperties, resourceContext, writer); + WriteDynamicComplexProperties(resourceContext, writer); + WriteNavigationLinks(selectExpandNode.SelectedNavigationProperties, resourceContext, writer); + WriteExpandedNavigationProperties(selectExpandNode.ExpandedProperties, resourceContext, writer); + WriteReferencedNavigationProperties(selectExpandNode.ReferencedNavigationProperties, resourceContext, writer); + writer.WriteEnd(); + } + } + } + } + + /// + /// Creates the that describes the set of properties and actions to select and expand while writing this entity. + /// + /// Contains the entity instance being written and the context. + /// + /// The that describes the set of properties and actions to select and expand while writing this entity. + /// + public virtual SelectExpandNode CreateSelectExpandNode(ResourceContext resourceContext) + { + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + + ODataSerializerContext writeContext = resourceContext.SerializerContext; + IEdmStructuredType structuredType = resourceContext.StructuredType; + + object selectExpandNode; + Tuple key = Tuple.Create(writeContext.SelectExpandClause, structuredType); + if (!writeContext.Items.TryGetValue(key, out selectExpandNode)) + { + // cache the selectExpandNode so that if we are writing a feed we don't have to construct it again. + selectExpandNode = new SelectExpandNode(structuredType, writeContext); + writeContext.Items[key] = selectExpandNode; + } + + return selectExpandNode as SelectExpandNode; + } + + /// + /// Creates the to be written while writing this resource. + /// + /// The describing the response graph. + /// The context for the resource instance being written. + /// The created . + public virtual ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext) + { + if (selectExpandNode == null) + { + throw Error.ArgumentNull("selectExpandNode"); + } + + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + + if (resourceContext.SerializerContext.ExpandReference) + { + return new ODataResource + { + Id = resourceContext.GenerateSelfLink(false) + }; + } + + string typeName = resourceContext.StructuredType.FullTypeName(); + ODataResource resource = new ODataResource + { + TypeName = typeName, + Properties = CreateStructuralPropertyBag(selectExpandNode.SelectedStructuralProperties, resourceContext), + }; + + if (resourceContext.EdmObject is EdmDeltaEntityObject && resourceContext.NavigationSource != null) + { + ODataResourceSerializationInfo serializationInfo = new ODataResourceSerializationInfo(); + serializationInfo.NavigationSourceName = resourceContext.NavigationSource.Name; + serializationInfo.NavigationSourceKind = resourceContext.NavigationSource.NavigationSourceKind(); + IEdmEntityType sourceType = resourceContext.NavigationSource.EntityType(); + if (sourceType != null) + { + serializationInfo.NavigationSourceEntityTypeName = sourceType.Name; + } + resource.SetSerializationInfo(serializationInfo); + } + + // Try to add the dynamic properties if the structural type is open. + AppendDynamicProperties(resource, selectExpandNode, resourceContext); + + IEnumerable actions = CreateODataActions(selectExpandNode.SelectedActions, resourceContext); + foreach (ODataAction action in actions) + { + resource.AddAction(action); + } + + IEnumerable functions = CreateODataFunctions(selectExpandNode.SelectedFunctions, resourceContext); + foreach (ODataFunction function in functions) + { + resource.AddFunction(function); + } + + IEdmStructuredType pathType = GetODataPathType(resourceContext.SerializerContext); + if (resourceContext.StructuredType.TypeKind == EdmTypeKind.Complex) + { + AddTypeNameAnnotationAsNeededForComplex(resource, resourceContext.SerializerContext.MetadataLevel); + } + else + { + AddTypeNameAnnotationAsNeeded(resource, pathType, resourceContext.SerializerContext.MetadataLevel); + } + + if (resourceContext.StructuredType.TypeKind == EdmTypeKind.Entity && resourceContext.NavigationSource != null) + { + if (!(resourceContext.NavigationSource is IEdmContainedEntitySet)) + { + IEdmModel model = resourceContext.SerializerContext.Model; + NavigationSourceLinkBuilderAnnotation linkBuilder = model.GetNavigationSourceLinkBuilder(resourceContext.NavigationSource); + EntitySelfLinks selfLinks = linkBuilder.BuildEntitySelfLinks(resourceContext, resourceContext.SerializerContext.MetadataLevel); + + if (selfLinks.IdLink != null) + { + resource.Id = selfLinks.IdLink; + } + + if (selfLinks.ReadLink != null) + { + resource.ReadLink = selfLinks.ReadLink; + } + + if (selfLinks.EditLink != null) + { + resource.EditLink = selfLinks.EditLink; + } + } + + string etag = CreateETag(resourceContext); + if (etag != null) + { + resource.ETag = etag; + } + } + + return resource; + } + + /// + /// Appends the dynamic properties of primitive, enum or the collection of them into the given . + /// If the dynamic property is a property of the complex or collection of complex, it will be saved into + /// the dynamic complex properties dictionary of and be written later. + /// + /// The describing the resource. + /// The describing the response graph. + /// The context for the resource instance being written. + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many classes.")] + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These are simple conversion function and cannot be split up.")] + public virtual void AppendDynamicProperties(ODataResource resource, SelectExpandNode selectExpandNode, + ResourceContext resourceContext) + { + Contract.Assert(resource != null); + Contract.Assert(selectExpandNode != null); + Contract.Assert(resourceContext != null); + + if (!resourceContext.StructuredType.IsOpen || // non-open type + (!selectExpandNode.SelectAllDynamicProperties && !selectExpandNode.SelectedDynamicProperties.Any())) + { + return; + } + + bool nullDynamicPropertyEnabled = false; + if (resourceContext.EdmObject is EdmDeltaComplexObject || resourceContext.EdmObject is EdmDeltaEntityObject) + { + nullDynamicPropertyEnabled = true; + } + else if (resourceContext.InternalRequest != null) + { + nullDynamicPropertyEnabled = resourceContext.InternalRequest.Options.NullDynamicPropertyIsEnabled; + } + + IEdmStructuredType structuredType = resourceContext.StructuredType; + IEdmStructuredObject structuredObject = resourceContext.EdmObject; + object value; + IDelta delta = structuredObject as IDelta; + if (delta == null) + { + PropertyInfo dynamicPropertyInfo = EdmLibHelpers.GetDynamicPropertyDictionary(structuredType, + resourceContext.EdmModel); + if (dynamicPropertyInfo == null || structuredObject == null || + !structuredObject.TryGetPropertyValue(dynamicPropertyInfo.Name, out value) || value == null) + { + return; + } + } + else + { + value = ((EdmStructuredObject)structuredObject).TryGetDynamicProperties(); + } + + IDictionary dynamicPropertyDictionary = (IDictionary)value; + + // Build a HashSet to store the declared property names. + // It is used to make sure the dynamic property name is different from all declared property names. + HashSet declaredPropertyNameSet = new HashSet(resource.Properties.Select(p => p.Name)); + List dynamicProperties = new List(); + IEnumerable> dynamicPropertiesToSelect = + dynamicPropertyDictionary.Where( + x => + !selectExpandNode.SelectedDynamicProperties.Any() || + selectExpandNode.SelectedDynamicProperties.Contains(x.Key)); + foreach (KeyValuePair dynamicProperty in dynamicPropertiesToSelect) + { + if (String.IsNullOrEmpty(dynamicProperty.Key)) + { + continue; + } + + if (dynamicProperty.Value == null) + { + if (nullDynamicPropertyEnabled) + { + dynamicProperties.Add(new ODataProperty + { + Name = dynamicProperty.Key, + Value = new ODataNullValue() + }); + } + + continue; + } + + if (declaredPropertyNameSet.Contains(dynamicProperty.Key)) + { + throw Error.InvalidOperation(SRResources.DynamicPropertyNameAlreadyUsedAsDeclaredPropertyName, + dynamicProperty.Key, structuredType.FullTypeName()); + } + + IEdmTypeReference edmTypeReference = resourceContext.SerializerContext.GetEdmType(dynamicProperty.Value, + dynamicProperty.Value.GetType()); + if (edmTypeReference == null) + { + throw Error.NotSupported(SRResources.TypeOfDynamicPropertyNotSupported, + dynamicProperty.Value.GetType().FullName, dynamicProperty.Key); + } + + if (edmTypeReference.IsStructured() || + (edmTypeReference.IsCollection() && edmTypeReference.AsCollection().ElementType().IsStructured())) + { + if (resourceContext.DynamicComplexProperties == null) + { + resourceContext.DynamicComplexProperties = new ConcurrentDictionary(); + } + + resourceContext.DynamicComplexProperties.Add(dynamicProperty); + } + else + { + ODataEdmTypeSerializer propertySerializer = SerializerProvider.GetEdmTypeSerializer(edmTypeReference); + if (propertySerializer == null) + { + throw Error.NotSupported(SRResources.DynamicPropertyCannotBeSerialized, dynamicProperty.Key, + edmTypeReference.FullName()); + } + + dynamicProperties.Add(propertySerializer.CreateProperty( + dynamicProperty.Value, edmTypeReference, dynamicProperty.Key, resourceContext.SerializerContext)); + } + } + + if (dynamicProperties.Any()) + { + resource.Properties = resource.Properties.Concat(dynamicProperties); + } + } + + /// + /// Creates the ETag for the given entity. + /// + /// The context for the resource instance being written. + /// The created ETag. + public virtual string CreateETag(ResourceContext resourceContext) + { + if (resourceContext.InternalRequest != null) + { + IEdmModel model = resourceContext.EdmModel; + IEdmNavigationSource navigationSource = resourceContext.NavigationSource; + + IEnumerable concurrencyProperties; + if (model != null && navigationSource != null) + { + concurrencyProperties = model.GetConcurrencyProperties(navigationSource).OrderBy(c => c.Name); + } + else + { + concurrencyProperties = Enumerable.Empty(); + } + + IDictionary properties = new Dictionary(); + foreach (IEdmStructuralProperty etagProperty in concurrencyProperties) + { + properties.Add(etagProperty.Name, resourceContext.GetPropertyValue(etagProperty.Name)); + } + + return resourceContext.InternalRequest.CreateETag(properties); + } + + return null; + } + + private void WriteNavigationLinks( + IEnumerable navigationProperties, ResourceContext resourceContext, ODataWriter writer) + { + Contract.Assert(resourceContext != null); + + IEnumerable navigationLinks = CreateNavigationLinks(navigationProperties, resourceContext); + foreach (ODataNestedResourceInfo navigationLink in navigationLinks) + { + writer.WriteStart(navigationLink); + writer.WriteEnd(); + } + } + + private void WriteComplexProperties(IEnumerable complexProperties, + ResourceContext resourceContext, ODataWriter writer) + { + Contract.Assert(complexProperties != null); + Contract.Assert(resourceContext != null); + Contract.Assert(writer != null); + + if (null != resourceContext.EdmObject && resourceContext.EdmObject.IsDeltaResource()) + { + IDelta deltaObject = resourceContext.EdmObject as IDelta; + IEnumerable changedProperties = deltaObject.GetChangedPropertyNames(); + complexProperties = complexProperties.Where(p => changedProperties.Contains(p.Name)); + } + + foreach (IEdmStructuralProperty complexProperty in complexProperties) + { + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo + { + IsCollection = complexProperty.Type.IsCollection(), + Name = complexProperty.Name + }; + + writer.WriteStart(nestedResourceInfo); + WriteComplexAndExpandedNavigationProperty(complexProperty, null, resourceContext, writer); + writer.WriteEnd(); + } + } + + private void WriteDynamicComplexProperties(ResourceContext resourceContext, ODataWriter writer) + { + Contract.Assert(resourceContext != null); + Contract.Assert(resourceContext.EdmModel != null); + + if (resourceContext.DynamicComplexProperties == null) + { + return; + } + + foreach (var dynamicComplexProperty in resourceContext.DynamicComplexProperties) + { + // If the dynamic property is "null", it should be treated ahead by creating an ODataProperty with ODataNullValue. + // However, it's safety here to skip the null dynamic property. + if (String.IsNullOrEmpty(dynamicComplexProperty.Key) || dynamicComplexProperty.Value == null) + { + continue; + } + + IEdmTypeReference edmTypeReference = + resourceContext.SerializerContext.GetEdmType(dynamicComplexProperty.Value, + dynamicComplexProperty.Value.GetType()); + + if (edmTypeReference.IsStructured() || + (edmTypeReference.IsCollection() && edmTypeReference.AsCollection().ElementType().IsStructured())) + { + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo + { + IsCollection = edmTypeReference.IsCollection(), + Name = dynamicComplexProperty.Key, + }; + + writer.WriteStart(nestedResourceInfo); + WriteDynamicComplexProperty(dynamicComplexProperty.Value, edmTypeReference, resourceContext, writer); + writer.WriteEnd(); + } + } + } + + private void WriteDynamicComplexProperty(object propertyValue, IEdmTypeReference edmType, ResourceContext resourceContext, ODataWriter writer) + { + Contract.Assert(resourceContext != null); + Contract.Assert(writer != null); + + // If the dynamic property is "null", it should be treated ahead by creating an ODataProperty with ODataNullValue. + Contract.Assert(propertyValue != null); + + // Create the serializer context for the nested and expanded item. + ODataSerializerContext nestedWriteContext = new ODataSerializerContext(resourceContext, null, null); + + // Write object. + ODataEdmTypeSerializer serializer = SerializerProvider.GetEdmTypeSerializer(edmType); + if (serializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, edmType.ToTraceString())); + } + + serializer.WriteObjectInline(propertyValue, edmType, writer, nestedWriteContext); + } + + private void WriteExpandedNavigationProperties( + IDictionary navigationPropertiesToExpand, + ResourceContext resourceContext, + ODataWriter writer) + { + Contract.Assert(navigationPropertiesToExpand != null); + Contract.Assert(resourceContext != null); + Contract.Assert(writer != null); + + foreach (KeyValuePair navPropertyToExpand in navigationPropertiesToExpand) + { + IEdmNavigationProperty navigationProperty = navPropertyToExpand.Key; + + ODataNestedResourceInfo navigationLink = CreateNavigationLink(navigationProperty, resourceContext); + if (navigationLink != null) + { + writer.WriteStart(navigationLink); + WriteComplexAndExpandedNavigationProperty(navPropertyToExpand.Key, navPropertyToExpand.Value, resourceContext, writer); + writer.WriteEnd(); + } + } + } + + private void WriteComplexAndExpandedNavigationProperty(IEdmProperty edmProperty, ExpandedNavigationSelectItem expandedNavigationSelectItem, + ResourceContext resourceContext, + ODataWriter writer, bool expandReference = false) + { + Contract.Assert(edmProperty != null); + Contract.Assert(resourceContext != null); + Contract.Assert(writer != null); + + object propertyValue = resourceContext.GetPropertyValue(edmProperty.Name); + + if (propertyValue == null || propertyValue is NullEdmComplexObject) + { + if (edmProperty.Type.IsCollection()) + { + // A complex or navigation property whose Type attribute specifies a collection, the collection always exists, + // it may just be empty. + // If a collection of complex or entities can be related, it is represented as a JSON array. An empty + // collection of resources (one that contains no resource) is represented as an empty JSON array. + writer.WriteStart(new ODataResourceSet + { + TypeName = edmProperty.Type.FullName() + }); + } + else + { + // If at most one resource can be related, the value is null if no resource is currently related. + writer.WriteStart(resource: null); + } + + writer.WriteEnd(); + } + else + { + // create the serializer context for the complex and expanded item. + + ODataSerializerContext nestedWriteContext = new ODataSerializerContext(resourceContext, edmProperty, resourceContext.SerializerContext.QueryContext, expandedNavigationSelectItem); + nestedWriteContext.ExpandReference = expandReference; + + // write object. + ODataEdmTypeSerializer serializer = SerializerProvider.GetEdmTypeSerializer(edmProperty.Type); + if (serializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, edmProperty.Type.ToTraceString())); + } + + serializer.WriteObjectInline(propertyValue, edmProperty.Type, writer, nestedWriteContext); + } + } + + private void WriteReferencedNavigationProperties(ISet referencedPropertiesToExpand, + ResourceContext resourceContext, ODataWriter writer) + { + Contract.Assert(referencedPropertiesToExpand != null); + Contract.Assert(resourceContext != null); + Contract.Assert(writer != null); + + foreach (IEdmNavigationProperty navigationProperty in referencedPropertiesToExpand) + { + ODataNestedResourceInfo nestedResourceInfo = CreateNavigationLink(navigationProperty, resourceContext); + if (nestedResourceInfo != null) + { + writer.WriteStart(nestedResourceInfo); + WriteComplexAndExpandedNavigationProperty(navigationProperty, null, resourceContext, writer, true); + writer.WriteEnd(); + } + } + } + + private IEnumerable CreateNavigationLinks( + IEnumerable navigationProperties, ResourceContext resourceContext) + { + Contract.Assert(navigationProperties != null); + Contract.Assert(resourceContext != null); + + foreach (IEdmNavigationProperty navProperty in navigationProperties) + { + ODataNestedResourceInfo navigationLink = CreateNavigationLink(navProperty, resourceContext); + if (navigationLink != null) + { + yield return navigationLink; + } + } + } + + /// + /// Creates the to be written while writing this entity. + /// + /// The navigation property for which the navigation link is being created. + /// The context for the entity instance being written. + /// The navigation link to be written. + public virtual ODataNestedResourceInfo CreateNavigationLink(IEdmNavigationProperty navigationProperty, ResourceContext resourceContext) + { + if (navigationProperty == null) + { + throw Error.ArgumentNull("navigationProperty"); + } + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + + ODataSerializerContext writeContext = resourceContext.SerializerContext; + ODataNestedResourceInfo navigationLink = null; + + if (writeContext.NavigationSource != null) + { + IEdmTypeReference propertyType = navigationProperty.Type; + IEdmModel model = writeContext.Model; + NavigationSourceLinkBuilderAnnotation linkBuilder = model.GetNavigationSourceLinkBuilder(writeContext.NavigationSource); + Uri navigationUrl = linkBuilder.BuildNavigationLink(resourceContext, navigationProperty, writeContext.MetadataLevel); + + navigationLink = new ODataNestedResourceInfo + { + IsCollection = propertyType.IsCollection(), + Name = navigationProperty.Name, + }; + + if (navigationUrl != null) + { + navigationLink.Url = navigationUrl; + } + } + + return navigationLink; + } + + private IEnumerable CreateStructuralPropertyBag( + IEnumerable structuralProperties, ResourceContext resourceContext) + { + Contract.Assert(structuralProperties != null); + Contract.Assert(resourceContext != null); + + List properties = new List(); + + if (null != resourceContext.EdmObject && resourceContext.EdmObject.IsDeltaResource()) + { + IDelta deltaObject = resourceContext.EdmObject as IDelta; + IEnumerable changedProperties = deltaObject.GetChangedPropertyNames(); + structuralProperties = structuralProperties.Where(p => changedProperties.Contains(p.Name)); + } + + foreach (IEdmStructuralProperty structuralProperty in structuralProperties) + { + ODataProperty property = CreateStructuralProperty(structuralProperty, resourceContext); + if (property != null) + { + properties.Add(property); + } + } + + return properties; + } + + /// + /// Creates the to be written for the given entity and the structural property. + /// + /// The EDM structural property being written. + /// The context for the entity instance being written. + /// The to write. + public virtual ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext) + { + if (structuralProperty == null) + { + throw Error.ArgumentNull("structuralProperty"); + } + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + + ODataSerializerContext writeContext = resourceContext.SerializerContext; + + ODataEdmTypeSerializer serializer = SerializerProvider.GetEdmTypeSerializer(structuralProperty.Type); + if (serializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, structuralProperty.Type.FullName())); + } + + object propertyValue = resourceContext.GetPropertyValue(structuralProperty.Name); + + IEdmTypeReference propertyType = structuralProperty.Type; + if (propertyValue != null) + { + if (!propertyType.IsPrimitive() && !propertyType.IsEnum()) + { + IEdmTypeReference actualType = writeContext.GetEdmType(propertyValue, propertyValue.GetType()); + if (propertyType != null && propertyType != actualType) + { + propertyType = actualType; + } + } + } + + return serializer.CreateProperty(propertyValue, propertyType, structuralProperty.Name, writeContext); + } + + private IEnumerable CreateODataActions( + IEnumerable actions, ResourceContext resourceContext) + { + Contract.Assert(actions != null); + Contract.Assert(resourceContext != null); + + foreach (IEdmAction action in actions) + { + ODataAction oDataAction = CreateODataAction(action, resourceContext); + if (oDataAction != null) + { + yield return oDataAction; + } + } + } + + private IEnumerable CreateODataFunctions( + IEnumerable functions, ResourceContext resourceContext) + { + Contract.Assert(functions != null); + Contract.Assert(resourceContext != null); + + foreach (IEdmFunction function in functions) + { + ODataFunction oDataFunction = CreateODataFunction(function, resourceContext); + if (oDataFunction != null) + { + yield return oDataFunction; + } + } + } + + /// + /// Creates an to be written for the given action and the entity instance. + /// + /// The OData action. + /// The context for the entity instance being written. + /// The created action or null if the action should not be written. + [SuppressMessage("Microsoft.Usage", "CA2234: Pass System.Uri objects instead of strings", Justification = "This overload is equally good")] + public virtual ODataAction CreateODataAction(IEdmAction action, ResourceContext resourceContext) + { + if (action == null) + { + throw Error.ArgumentNull("action"); + } + + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + + IEdmModel model = resourceContext.EdmModel; + OperationLinkBuilder builder = model.GetOperationLinkBuilder(action); + + if (builder == null) + { + return null; + } + + return CreateODataOperation(action, builder, resourceContext) as ODataAction; + } + + /// + /// Creates an to be written for the given action and the entity instance. + /// + /// The OData function. + /// The context for the entity instance being written. + /// The created function or null if the action should not be written. + [SuppressMessage("Microsoft.Usage", "CA2234: Pass System.Uri objects instead of strings", + Justification = "This overload is equally good")] + [SuppressMessage("Microsoft.Naming", "CA1716: Use function as parameter name", Justification = "Function")] + public virtual ODataFunction CreateODataFunction(IEdmFunction function, ResourceContext resourceContext) + { + if (function == null) + { + throw Error.ArgumentNull("function"); + } + + if (resourceContext == null) + { + throw Error.ArgumentNull("resourceContext"); + } + + IEdmModel model = resourceContext.EdmModel; + OperationLinkBuilder builder = model.GetOperationLinkBuilder(function); + + if (builder == null) + { + return null; + } + + return CreateODataOperation(function, builder, resourceContext) as ODataFunction; + } + + private static ODataOperation CreateODataOperation(IEdmOperation operation, OperationLinkBuilder builder, ResourceContext resourceContext) + { + Contract.Assert(operation != null); + Contract.Assert(builder != null); + Contract.Assert(resourceContext != null); + + ODataMetadataLevel metadataLevel = resourceContext.SerializerContext.MetadataLevel; + IEdmModel model = resourceContext.EdmModel; + + if (ShouldOmitOperation(operation, builder, metadataLevel)) + { + return null; + } + + Uri target = builder.BuildLink(resourceContext); + if (target == null) + { + return null; + } + + Uri baseUri = new Uri(resourceContext.InternalUrlHelper.CreateODataLink(MetadataSegment.Instance)); + Uri metadata = new Uri(baseUri, "#" + CreateMetadataFragment(operation)); + + ODataOperation odataOperation; + if (operation is IEdmAction) + { + odataOperation = new ODataAction(); + } + else + { + odataOperation = new ODataFunction(); + } + odataOperation.Metadata = metadata; + + // Always omit the title in minimal/no metadata modes. + if (metadataLevel == ODataMetadataLevel.FullMetadata) + { + EmitTitle(model, operation, odataOperation); + } + + // Omit the target in minimal/no metadata modes unless it doesn't follow conventions. + if (!builder.FollowsConventions || metadataLevel == ODataMetadataLevel.FullMetadata) + { + odataOperation.Target = target; + } + + return odataOperation; + } + + internal static void EmitTitle(IEdmModel model, IEdmOperation operation, ODataOperation odataOperation) + { + // The title should only be emitted in full metadata. + OperationTitleAnnotation titleAnnotation = model.GetOperationTitleAnnotation(operation); + if (titleAnnotation != null) + { + odataOperation.Title = titleAnnotation.Title; + } + else + { + odataOperation.Title = operation.Name; + } + } + + internal static string CreateMetadataFragment(IEdmOperation operation) + { + // There can only be one entity container in OData V4. + string actionName = operation.Name; + string fragment = operation.Namespace + "." + actionName; + + return fragment; + } + + private static IEdmStructuredType GetODataPathType(ODataSerializerContext serializerContext) + { + Contract.Assert(serializerContext != null); + if (serializerContext.EdmProperty != null) + { + // we are in an nested complex or expanded navigation property. + if (serializerContext.EdmProperty.Type.IsCollection()) + { + return serializerContext.EdmProperty.Type.AsCollection().ElementType().ToStructuredType(); + } + else + { + return serializerContext.EdmProperty.Type.AsStructured().StructuredDefinition(); + } + } + else + { + if (serializerContext.ExpandedResource != null) + { + // we are in dynamic complex. + return null; + } + + IEdmType edmType = null; + + // figure out the type from the navigation source + if (serializerContext.NavigationSource != null) + { + edmType = serializerContext.NavigationSource.EntityType(); + if (edmType.TypeKind == EdmTypeKind.Collection) + { + edmType = (edmType as IEdmCollectionType).ElementType.Definition; + } + } + + // figure out the type from the path. + if (serializerContext.Path != null) + { + // Note: The navigation source may be different from the path if the instance has redefined the context + // (for example, in a flattended delta response) + if (serializerContext.NavigationSource == null || serializerContext.NavigationSource == serializerContext.Path.NavigationSource) + { + edmType = serializerContext.Path.EdmType; + if (edmType != null && edmType.TypeKind == EdmTypeKind.Collection) + { + edmType = (edmType as IEdmCollectionType).ElementType.Definition; + } + } + } + + return edmType as IEdmStructuredType; + } + } + + internal static void AddTypeNameAnnotationAsNeeded(ODataResource resource, IEdmStructuredType odataPathType, + ODataMetadataLevel metadataLevel) + { + // ODataLib normally has the caller decide whether or not to serialize properties by leaving properties + // null when values should not be serialized. The TypeName property is different and should always be + // provided to ODataLib to enable model validation. A separate annotation is used to decide whether or not + // to serialize the type name (a null value prevents serialization). + + // Note: In the current version of ODataLib the default behavior likely now matches the requirements for + // minimal metadata mode. However, there have been behavior changes/bugs there in the past, so the safer + // option is for this class to take control of type name serialization in minimal metadata mode. + + Contract.Assert(resource != null); + + string typeName = null; // Set null to force the type name not to serialize. + + // Provide the type name to serialize. + if (!ShouldSuppressTypeNameSerialization(resource, odataPathType, metadataLevel)) + { + typeName = resource.TypeName; + } + + resource.TypeAnnotation = new ODataTypeAnnotation(typeName); + } + + internal static void AddTypeNameAnnotationAsNeededForComplex(ODataResource resource, ODataMetadataLevel metadataLevel) + { + // ODataLib normally has the caller decide whether or not to serialize properties by leaving properties + // null when values should not be serialized. The TypeName property is different and should always be + // provided to ODataLib to enable model validation. A separate annotation is used to decide whether or not + // to serialize the type name (a null value prevents serialization). + Contract.Assert(resource != null); + + // Only add an annotation if we want to override ODataLib's default type name serialization behavior. + if (ShouldAddTypeNameAnnotationForComplex(metadataLevel)) + { + string typeName; + + // Provide the type name to serialize (or null to force it not to serialize). + if (ShouldSuppressTypeNameSerializationForComplex(metadataLevel)) + { + typeName = null; + } + else + { + typeName = resource.TypeName; + } + + resource.TypeAnnotation = new ODataTypeAnnotation(typeName); + } + } + + internal static bool ShouldAddTypeNameAnnotationForComplex(ODataMetadataLevel metadataLevel) + { + switch (metadataLevel) + { + // For complex types, the default behavior matches the requirements for minimal metadata mode, so no + // annotation is necessary. + case ODataMetadataLevel.MinimalMetadata: + return false; + // In other cases, this class must control the type name serialization behavior. + case ODataMetadataLevel.FullMetadata: + case ODataMetadataLevel.NoMetadata: + default: // All values already specified; just keeping the compiler happy. + return true; + } + } + + internal static bool ShouldSuppressTypeNameSerializationForComplex(ODataMetadataLevel metadataLevel) + { + Contract.Assert(metadataLevel != ODataMetadataLevel.MinimalMetadata); + + switch (metadataLevel) + { + case ODataMetadataLevel.NoMetadata: + return true; + case ODataMetadataLevel.FullMetadata: + default: // All values already specified; just keeping the compiler happy. + return false; + } + } + + internal static bool ShouldOmitOperation(IEdmOperation operation, OperationLinkBuilder builder, + ODataMetadataLevel metadataLevel) + { + Contract.Assert(builder != null); + + switch (metadataLevel) + { + case ODataMetadataLevel.MinimalMetadata: + case ODataMetadataLevel.NoMetadata: + return operation.IsBound && builder.FollowsConventions; + + case ODataMetadataLevel.FullMetadata: + default: // All values already specified; just keeping the compiler happy. + return false; + } + } + + internal static bool ShouldSuppressTypeNameSerialization(ODataResource resource, IEdmStructuredType edmType, + ODataMetadataLevel metadataLevel) + { + Contract.Assert(resource != null); + + switch (metadataLevel) + { + case ODataMetadataLevel.NoMetadata: + return true; + case ODataMetadataLevel.FullMetadata: + return false; + case ODataMetadataLevel.MinimalMetadata: + default: // All values already specified; just keeping the compiler happy. + string pathTypeName = null; + if (edmType != null) + { + pathTypeName = edmType.FullTypeName(); + } + string resourceTypeName = resource.TypeName; + return String.Equals(resourceTypeName, pathTypeName, StringComparison.Ordinal); + } + } + + private IEdmStructuredTypeReference GetResourceType(object graph, ODataSerializerContext writeContext) + { + Contract.Assert(graph != null); + + IEdmTypeReference edmType = writeContext.GetEdmType(graph, graph.GetType()); + Contract.Assert(edmType != null); + + if (!edmType.IsStructured()) + { + throw new SerializationException( + Error.Format(SRResources.CannotWriteType, GetType().Name, edmType.FullName())); + } + + return edmType.AsStructured(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSetSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSetSerializer.cs new file mode 100644 index 0000000..ae43df0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSetSerializer.cs @@ -0,0 +1,440 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// OData serializer for serializing a collection of or + /// + public class ODataResourceSetSerializer : ODataEdmTypeSerializer + { + private const string ResourceSet = "ResourceSet"; + + /// + /// Initializes a new instance of . + /// + /// The to use to write nested entries. + public ODataResourceSetSerializer(ODataSerializerProvider serializerProvider) + : base(ODataPayloadKind.ResourceSet, serializerProvider) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + IEdmEntitySetBase entitySet = writeContext.NavigationSource as IEdmEntitySetBase; + + IEdmTypeReference resourceSetType = writeContext.GetEdmType(graph, type); + Contract.Assert(resourceSetType != null); + + IEdmStructuredTypeReference resourceType = GetResourceType(resourceSetType); + ODataWriter writer = messageWriter.CreateODataResourceSetWriter(entitySet, resourceType.StructuredDefinition()); + WriteObjectInline(graph, resourceSetType, writer, writeContext); + } + + /// + public override void WriteObjectInline(object graph, IEdmTypeReference expectedType, ODataWriter writer, + ODataSerializerContext writeContext) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + if (expectedType == null) + { + throw Error.ArgumentNull("expectedType"); + } + if (graph == null) + { + throw new SerializationException(Error.Format(SRResources.CannotSerializerNull, ResourceSet)); + } + + IEnumerable enumerable = graph as IEnumerable; // Data to serialize + if (enumerable == null) + { + throw new SerializationException( + Error.Format(SRResources.CannotWriteType, GetType().Name, graph.GetType().FullName)); + } + + WriteResourceSet(enumerable, expectedType, writer, writeContext); + } + + private void WriteResourceSet(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, + ODataSerializerContext writeContext) + { + Contract.Assert(writer != null); + Contract.Assert(writeContext != null); + Contract.Assert(enumerable != null); + Contract.Assert(resourceSetType != null); + + IEdmStructuredTypeReference elementType = GetResourceType(resourceSetType); + ODataResourceSet resourceSet = CreateResourceSet(enumerable, resourceSetType.AsCollection(), writeContext); + + Func nextLinkGenerator = GetNextLinkGenerator(resourceSet, enumerable, writeContext); + + if (resourceSet == null) + { + throw new SerializationException(Error.Format(SRResources.CannotSerializerNull, ResourceSet)); + } + + IEdmEntitySetBase entitySet = writeContext.NavigationSource as IEdmEntitySetBase; + if (entitySet == null) + { + resourceSet.SetSerializationInfo(new ODataResourceSerializationInfo + { + IsFromCollection = true, + NavigationSourceEntityTypeName = elementType.FullName(), + NavigationSourceKind = EdmNavigationSourceKind.UnknownEntitySet, + NavigationSourceName = null + }); + } + + ODataEdmTypeSerializer resourceSerializer = SerializerProvider.GetEdmTypeSerializer(elementType); + if (resourceSerializer == null) + { + throw new SerializationException( + Error.Format(SRResources.TypeCannotBeSerialized, elementType.FullName())); + } + + // set the nextpagelink to null to support JSON odata.streaming. + resourceSet.NextPageLink = null; + writer.WriteStart(resourceSet); + object lastResource = null; + foreach (object item in enumerable) + { + lastResource = item; + if (item == null || item is NullEdmComplexObject) + { + if (elementType.IsEntity()) + { + throw new SerializationException(SRResources.NullElementInCollection); + } + + // for null complex element, it can be serialized as "null" in the collection. + writer.WriteStart(resource: null); + writer.WriteEnd(); + } + else + { + resourceSerializer.WriteObjectInline(item, elementType, writer, writeContext); + } + } + + // Subtle and surprising behavior: If the NextPageLink property is set before calling WriteStart(resourceSet), + // the next page link will be written early in a manner not compatible with odata.streaming=true. Instead, if + // the next page link is not set when calling WriteStart(resourceSet) but is instead set later on that resourceSet + // object before calling WriteEnd(), the next page link will be written at the end, as required for + // odata.streaming=true support. + + resourceSet.NextPageLink = nextLinkGenerator(lastResource); + + writer.WriteEnd(); + } + + /// + /// Create the to be written for the given resourceSet instance. + /// + /// The instance representing the resourceSet being written. + /// The EDM type of the resourceSet being written. + /// The serializer context. + /// The created object. + public virtual ODataResourceSet CreateResourceSet(IEnumerable resourceSetInstance, IEdmCollectionTypeReference resourceSetType, + ODataSerializerContext writeContext) + { + ODataResourceSet resourceSet = new ODataResourceSet + { + TypeName = resourceSetType.FullName() + }; + + IEdmStructuredTypeReference structuredType = GetResourceType(resourceSetType).AsStructured(); + if (writeContext.NavigationSource != null && structuredType.IsEntity()) + { + ResourceSetContext resourceSetContext = ResourceSetContext.Create(writeContext, resourceSetInstance); + IEdmEntityType entityType = structuredType.AsEntity().EntityDefinition(); + var operations = writeContext.Model.GetAvailableOperationsBoundToCollection(entityType); + var odataOperations = CreateODataOperations(operations, resourceSetContext, writeContext); + foreach (var odataOperation in odataOperations) + { + ODataAction action = odataOperation as ODataAction; + if (action != null) + { + resourceSet.AddAction(action); + } + else + { + resourceSet.AddFunction((ODataFunction)odataOperation); + } + } + } + + if (writeContext.ExpandedResource == null) + { + // If we have more OData format specific information apply it now, only if we are the root feed. + PageResult odataResourceSetAnnotations = resourceSetInstance as PageResult; + if (odataResourceSetAnnotations != null) + { + resourceSet.Count = odataResourceSetAnnotations.Count; + resourceSet.NextPageLink = odataResourceSetAnnotations.NextPageLink; + } + else if (writeContext.Request != null) + { + resourceSet.NextPageLink = writeContext.InternalRequest.Context.NextLink; + resourceSet.DeltaLink = writeContext.InternalRequest.Context.DeltaLink; + + long? countValue = writeContext.InternalRequest.Context.TotalCount; + if (countValue.HasValue) + { + resourceSet.Count = countValue.Value; + } + } + } + else + { + ICountOptionCollection countOptionCollection = resourceSetInstance as ICountOptionCollection; + if (countOptionCollection != null && countOptionCollection.TotalCount != null) + { + resourceSet.Count = countOptionCollection.TotalCount; + } + } + + return resourceSet; + } + + /// + /// Creates a function that takes in an object and generates nextlink uri. + /// + /// The resource set describing a collection of structured objects. + /// The instance representing the resourceSet being written. + /// The serializer context. + /// The function that generates the NextLink from an object. + internal static Func GetNextLinkGenerator(ODataResourceSetBase resourceSet, IEnumerable resourceSetInstance, ODataSerializerContext writeContext) + { + if (resourceSet != null && resourceSet.NextPageLink != null) + { + Uri defaultUri = resourceSet.NextPageLink; + return (obj) => { return defaultUri; }; + } + + if (writeContext.ExpandedResource == null) + { + if (writeContext.InternalRequest != null && writeContext.QueryContext != null) + { + SkipTokenHandler handler = writeContext.QueryContext.GetSkipTokenHandler(); + return (obj) => { return handler.GenerateNextPageLink(writeContext.InternalRequest.RequestUri, writeContext.InternalRequest.Context.PageSize, obj, writeContext); }; + } + } + else + { + // nested resourceSet + ITruncatedCollection truncatedCollection = resourceSetInstance as ITruncatedCollection; + if (truncatedCollection != null && truncatedCollection.IsTruncated) + { + return (obj) => { return GetNestedNextPageLink(writeContext, truncatedCollection.PageSize, obj); }; + } + } + + return (obj) => { return null; }; + } + + /// + /// Creates an to be written for the given operation and the resourceSet instance. + /// + /// The OData operation. + /// The context for the resourceSet instance being written. + /// The serializer context. + /// The created operation or null if the operation should not be written. + [SuppressMessage("Microsoft.Usage", "CA2234: Pass System.Uri objects instead of strings", Justification = "This overload is equally good")] + public virtual ODataOperation CreateODataOperation(IEdmOperation operation, ResourceSetContext resourceSetContext, ODataSerializerContext writeContext) + { + if (operation == null) + { + throw Error.ArgumentNull("operation"); + } + + if (resourceSetContext == null) + { + throw Error.ArgumentNull("resourceSetContext"); + } + + if (writeContext == null) + { + throw Error.ArgumentNull("writeContext"); + } + + ODataMetadataLevel metadataLevel = writeContext.MetadataLevel; + IEdmModel model = writeContext.Model; + + if (metadataLevel != ODataMetadataLevel.FullMetadata) + { + return null; + } + + OperationLinkBuilder builder; + builder = model.GetOperationLinkBuilder(operation); + + if (builder == null) + { + return null; + } + + Uri target = builder.BuildLink(resourceSetContext); + if (target == null) + { + return null; + } + + Uri baseUri = new Uri(writeContext.InternalUrlHelper.CreateODataLink(MetadataSegment.Instance)); + Uri metadata = new Uri(baseUri, "#" + operation.FullName()); + + ODataOperation odataOperation; + IEdmAction action = operation as IEdmAction; + if (action != null) + { + odataOperation = new ODataAction(); + } + else + { + odataOperation = new ODataFunction(); + } + odataOperation.Metadata = metadata; + + // Always omit the title in minimal/no metadata modes. + ODataResourceSerializer.EmitTitle(model, operation, odataOperation); + + // Omit the target in minimal/no metadata modes unless it doesn't follow conventions. + if (metadataLevel == ODataMetadataLevel.FullMetadata || !builder.FollowsConventions) + { + odataOperation.Target = target; + } + + return odataOperation; + } + + private IEnumerable CreateODataOperations(IEnumerable operations, ResourceSetContext resourceSetContext, ODataSerializerContext writeContext) + { + Contract.Assert(operations != null); + Contract.Assert(resourceSetContext != null); + Contract.Assert(writeContext != null); + + foreach (IEdmOperation operation in operations) + { + ODataOperation oDataOperation = CreateODataOperation(operation, resourceSetContext, writeContext); + if (oDataOperation != null) + { + yield return oDataOperation; + } + } + } + + private static Uri GetNestedNextPageLink(ODataSerializerContext writeContext, int pageSize, object obj) + { + Contract.Assert(writeContext.ExpandedResource != null); + IEdmNavigationSource sourceNavigationSource = writeContext.ExpandedResource.NavigationSource; + NavigationSourceLinkBuilderAnnotation linkBuilder = writeContext.Model.GetNavigationSourceLinkBuilder(sourceNavigationSource); + Uri navigationLink = + linkBuilder.BuildNavigationLink(writeContext.ExpandedResource, writeContext.NavigationProperty); + Uri nestedNextLink = GenerateQueryFromExpandedItem(writeContext, navigationLink); + SkipTokenHandler nextLinkGenerator = null; + if (writeContext.QueryContext != null) + { + nextLinkGenerator = writeContext.QueryContext.GetSkipTokenHandler(); + } + + if (nestedNextLink != null) + { + if (nextLinkGenerator != null) + { + return nextLinkGenerator.GenerateNextPageLink(nestedNextLink, pageSize, obj, writeContext); + } + + return GetNextPageHelper.GetNextPageLink(nestedNextLink, pageSize); + } + + return null; + } + + private static Uri GenerateQueryFromExpandedItem(ODataSerializerContext writeContext, Uri navigationLink) + { + IWebApiUrlHelper urlHelper = writeContext.InternalUrlHelper; + if (urlHelper == null) + { + return navigationLink; + } + string serviceRoot = urlHelper.CreateODataLink( + writeContext.InternalRequest.Context.RouteName, + writeContext.InternalRequest.PathHandler, + new List()); + Uri serviceRootUri = new Uri(serviceRoot); + ODataUriParser parser = new ODataUriParser(writeContext.Model, serviceRootUri, navigationLink); + ODataUri newUri = parser.ParseUri(); + newUri.SelectAndExpand = writeContext.SelectExpandClause; + if (writeContext.CurrentExpandedSelectItem != null) + { + newUri.OrderBy = writeContext.CurrentExpandedSelectItem.OrderByOption; + newUri.Filter = writeContext.CurrentExpandedSelectItem.FilterOption; + newUri.Skip = writeContext.CurrentExpandedSelectItem.SkipOption; + newUri.Top = writeContext.CurrentExpandedSelectItem.TopOption; + + if (writeContext.CurrentExpandedSelectItem.CountOption != null) + { + if (writeContext.CurrentExpandedSelectItem.CountOption.HasValue) + { + newUri.QueryCount = writeContext.CurrentExpandedSelectItem.CountOption.Value; + } + } + + ExpandedNavigationSelectItem expandedNavigationItem = writeContext.CurrentExpandedSelectItem as ExpandedNavigationSelectItem; + if (expandedNavigationItem != null) + { + newUri.SelectAndExpand = expandedNavigationItem.SelectAndExpand; + } + } + + ODataUrlKeyDelimiter keyDelimiter = writeContext.InternalRequest.Options.UrlKeyDelimiter == ODataUrlKeyDelimiter.Slash ? ODataUrlKeyDelimiter.Slash : ODataUrlKeyDelimiter.Parentheses; + return newUri.BuildUri(keyDelimiter); + } + + private static IEdmStructuredTypeReference GetResourceType(IEdmTypeReference resourceSetType) + { + if (resourceSetType.IsCollection()) + { + IEdmTypeReference elementType = resourceSetType.AsCollection().ElementType(); + if (elementType.IsEntity() || elementType.IsComplex()) + { + return elementType.AsStructured(); + } + } + + string message = Error.Format(SRResources.CannotWriteType, typeof(ODataResourceSetSerializer).Name, resourceSetType.FullName()); + throw new SerializationException(message); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializer.cs new file mode 100644 index 0000000..01df81d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializer.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// An ODataSerializer is used to write a CLR object to an ODataMessage. + /// + /// + /// Each supported CLR type has a corresponding . A CLR type is supported if it is one of + /// the special types or if it has a backing EDM type. Some of the special types are Uri which maps to ODataReferenceLink payload, + /// Uri[] which maps to ODataReferenceLinks payload, ODataWorkspace which maps to ODataServiceDocument payload. + /// + public abstract class ODataSerializer + { + /// + /// Constructs an ODataSerializer that can generate OData payload of the specified kind. + /// + /// The kind of OData payload that this serializer generates. + protected ODataSerializer(ODataPayloadKind payloadKind) + { + ODataPayloadKindHelper.Validate(payloadKind, "payloadKind"); + + ODataPayloadKind = payloadKind; + } + + /// + /// Gets the that this serializer generates. + /// + public ODataPayloadKind ODataPayloadKind { get; private set; } + + /// + /// Writes the given object specified by the parameter graph as a whole using the given messageWriter and writeContext. + /// + /// The object to be written + /// The type of the object to be written. + /// The to be used for writing. + /// The . + public virtual void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + throw Error.NotSupported(SRResources.WriteObjectNotSupported, GetType().Name); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerContext.cs new file mode 100644 index 0000000..f99ed0a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerContext.cs @@ -0,0 +1,293 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; +using SelectExpandClause = Microsoft.OData.UriParser.SelectExpandClause; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Context information used by the when serializing objects in OData message format. + /// + public partial class ODataSerializerContext + { + private ClrTypeCache _typeMappingCache; + private IDictionary _items; + private ODataQueryContext _queryContext; + private SelectExpandClause _selectExpandClause; + private bool _isSelectExpandClauseSet; + + /// + /// Initializes a new instance of the class. + /// + public ODataSerializerContext() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The resource whose navigation property is being expanded. + /// The for the navigation property being expanded. + /// The complex property being nested or the navigation property being expanded. + /// If the resource property is the dynamic complex, the resource property is null. + /// + /// This constructor is used to construct the serializer context for writing nested and expanded properties. + public ODataSerializerContext(ResourceContext resource, SelectExpandClause selectExpandClause, IEdmProperty edmProperty) + : this(resource, edmProperty, null, null) + { + SelectExpandClause = selectExpandClause; + } + + /// + /// Initializes a new instance of the class for nested resources. + /// + /// The resource whose navigation property is being expanded. + /// The complex property being nested or the navigation property being expanded. + /// If the resource property is the dynamic complex, the resource property is null. + /// + /// The for the navigation property being expanded. + /// The for the navigation property being expanded.> + internal ODataSerializerContext(ResourceContext resource, IEdmProperty edmProperty, ODataQueryContext queryContext, ExpandedReferenceSelectItem expandedItem) + { + if (resource == null) + { + throw Error.ArgumentNull("resource"); + } + + // Clone the resource's context. Use a helper function so it can + // handle platform-specific differences in ODataSerializerContext. + ODataSerializerContext context = resource.SerializerContext; + this.CopyPlatformSpecificProperties(context); + + Model = context.Model; + Path = context.Path; + RootElementName = context.RootElementName; + SkipExpensiveAvailabilityChecks = context.SkipExpensiveAvailabilityChecks; + MetadataLevel = context.MetadataLevel; + Items = context.Items; + ExpandReference = context.ExpandReference; + + QueryContext = queryContext; + ExpandedResource = resource; // parent resource + CurrentExpandedSelectItem = expandedItem; + + var expandedNavigationSelectItem = expandedItem as ExpandedNavigationSelectItem; + if (expandedNavigationSelectItem != null) + { + SelectExpandClause = expandedNavigationSelectItem.SelectAndExpand; + } + + EdmProperty = edmProperty; // should be nested property + + if (context.NavigationSource != null) + { + IEdmNavigationProperty navigationProperty = edmProperty as IEdmNavigationProperty; + if (navigationProperty != null) + { + NavigationSource = context.NavigationSource.FindNavigationTarget(NavigationProperty); + } + } + } + + internal IWebApiRequestMessage InternalRequest { get; private set; } + + /// + /// Gets or sets the to use for generating OData links. + /// + internal IWebApiUrlHelper InternalUrlHelper { get; private set; } + + /// + /// ODataQueryContext object, retrieved from query options for top-level context and passed down to nested serializer context as is. + /// + internal ODataQueryContext QueryContext + { + get + { + if (QueryOptions != null) + { + return QueryOptions.Context; + } + + return _queryContext; + } + private set { _queryContext = value; } + } + + /// + /// Gets or sets the navigation source. + /// + public IEdmNavigationSource NavigationSource { get; set; } + + /// + /// Gets or sets the EDM model associated with the request. + /// + public IEdmModel Model { get; set; } + + /// + /// Gets or sets the of the request. + /// + public ODataPath Path { get; set; } + + /// + /// Gets or sets the root element name which is used when writing primitive and enum types + /// + public string RootElementName { get; set; } + + /// + /// Get or sets whether expensive links should be calculated. + /// + public bool SkipExpensiveAvailabilityChecks { get; set; } + + /// + /// Gets or sets the metadata level of the response. + /// + public ODataMetadataLevel MetadataLevel { get; set; } + + /// + /// Gets or sets the . + /// + public SelectExpandClause SelectExpandClause + { + get + { + // private backing field to be removed once public setter from ODataFeature is removed. + if (_isSelectExpandClauseSet) + { + return _selectExpandClause; + } + + if (QueryOptions != null) + { + if (QueryOptions.SelectExpand != null) + { + return QueryOptions.SelectExpand.ProcessedSelectExpandClause; + } + + return null; + } + + ExpandedNavigationSelectItem expandedItem = CurrentExpandedSelectItem as ExpandedNavigationSelectItem; + if (expandedItem != null) + { + return expandedItem.SelectAndExpand; + } + + return null; + } + set + { + _isSelectExpandClauseSet = true; + _selectExpandClause = value; + } + } + + /// + /// Gets or sets the . + /// + internal ExpandedReferenceSelectItem CurrentExpandedSelectItem { get; set; } + + /// + /// Gets or sets the . + /// + public ODataQueryOptions QueryOptions { get; internal set; } + + /// + /// Gets or sets the resource that is being expanded. + /// + public ResourceContext ExpandedResource { get; set; } + + /// + /// Gets or sets the boolean value indicating whether it's $ref expanded. + /// + public bool ExpandReference { get; set; } + + /// + /// Gets or sets the complex property being nested or navigation property being expanded. + /// + public IEdmProperty EdmProperty { get; set; } + + /// + /// Gets or sets the navigation property being expanded. + /// + public IEdmNavigationProperty NavigationProperty + { + get + { + return EdmProperty as IEdmNavigationProperty; + } + } + + /// + /// Gets a property bag associated with this context to store any generic data. + /// + public IDictionary Items + { + get + { + _items = _items ?? new Dictionary(); + return _items; + } + private set + { + _items = value; + } + } + + internal IEdmTypeReference GetEdmType(object instance, Type type) + { + IEdmTypeReference edmType; + + IEdmObject edmObject = instance as IEdmObject; + if (edmObject != null) + { + edmType = edmObject.GetEdmType(); + if (edmType == null) + { + throw Error.InvalidOperation(SRResources.EdmTypeCannotBeNull, edmObject.GetType().FullName, + typeof(IEdmObject).Name); + } + } + else + { + if (Model == null) + { + throw Error.InvalidOperation(SRResources.RequestMustHaveModel); + } + + _typeMappingCache = _typeMappingCache ?? Model.GetTypeMappingCache(); + edmType = _typeMappingCache.GetEdmType(type, Model); + + if (edmType == null) + { + if (instance != null) + { + edmType = _typeMappingCache.GetEdmType(instance.GetType(), Model); + } + + if (edmType == null) + { + throw Error.InvalidOperation(SRResources.ClrTypeNotInModel, type); + } + } + else if (instance != null) + { + IEdmTypeReference actualType = _typeMappingCache.GetEdmType(instance.GetType(), Model); + if (actualType != null && actualType != edmType) + { + edmType = actualType; + } + } + } + + return edmType; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProvider.cs new file mode 100644 index 0000000..a2d1089 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// An ODataSerializerProvider is a factory for creating s. + /// + public abstract partial class ODataSerializerProvider + { + /// + /// Gets an for the given edmType. + /// + /// The . + /// The . + public abstract ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProviderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProviderExtensions.cs new file mode 100644 index 0000000..38008b6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataSerializerProviderExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + internal static class ODataSerializerProviderExtensions + { + public static ODataEdmTypeSerializer GetEdmTypeSerializer(this ODataSerializerProvider serializerProvider, + object instance, HttpRequestMessage request) + { + Contract.Assert(serializerProvider != null); + Contract.Assert(instance != null); + + IEdmObject edmObject = instance as IEdmObject; + if (edmObject != null) + { + return serializerProvider.GetEdmTypeSerializer(edmObject.GetEdmType()); + } + + return serializerProvider.GetODataPayloadSerializer(instance.GetType(), request) as ODataEdmTypeSerializer; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataServiceDocumentSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataServiceDocumentSerializer.cs new file mode 100644 index 0000000..6ef9b2b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataServiceDocumentSerializer.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an for serializing 's for generating servicedoc's. + /// + public class ODataServiceDocumentSerializer : ODataSerializer + { + /// + /// Initializes a new instance of . + /// + public ODataServiceDocumentSerializer() + : base(ODataPayloadKind.ServiceDocument) + { + } + + /// + public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) + { + if (messageWriter == null) + { + throw Error.ArgumentNull("messageWriter"); + } + if (graph == null) + { + throw Error.ArgumentNull("graph"); + } + + ODataServiceDocument serviceDocument = graph as ODataServiceDocument; + if (serviceDocument == null) + { + throw new SerializationException(Error.Format(SRResources.CannotWriteType, GetType().Name, type.Name)); + } + + messageWriter.WriteServiceDocument(serviceDocument); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/SelectExpandNode.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/SelectExpandNode.cs new file mode 100644 index 0000000..2eeddbe --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/SelectExpandNode.cs @@ -0,0 +1,452 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Describes the set of structural properties and navigation properties and actions to select and navigation properties to expand while + /// writing an in the response. + /// + public class SelectExpandNode + { + /// + /// Exists to support backward compatibility as we introduced ExpandedProperties. + /// + private Dictionary cachedExpandedClauses; + + /// + /// Creates a new instance of the class. + /// + /// The default constructor is for unit testing only. + public SelectExpandNode() + { + SelectedStructuralProperties = new HashSet(); + SelectedComplexProperties = new HashSet(); + SelectedNavigationProperties = new HashSet(); + ExpandedProperties = new Dictionary(); + ReferencedNavigationProperties = new HashSet(); + SelectedActions = new HashSet(); + SelectedFunctions = new HashSet(); + SelectedDynamicProperties = new HashSet(); + } + + /// + /// Creates a new instance of the class by copying the state of another instance. This is + /// intended for scenarios that wish to modify state without updating the values cached within ODataResourceSerializer. + /// + /// The instance from which the state for the new instance will be copied. + public SelectExpandNode(SelectExpandNode selectExpandNodeToCopy) + { + ExpandedProperties = new Dictionary(selectExpandNodeToCopy.ExpandedProperties); + ReferencedNavigationProperties = new HashSet(selectExpandNodeToCopy.ReferencedNavigationProperties); + + SelectedActions = new HashSet(selectExpandNodeToCopy.SelectedActions); + SelectAllDynamicProperties = selectExpandNodeToCopy.SelectAllDynamicProperties; + SelectedComplexProperties = new HashSet(selectExpandNodeToCopy.SelectedComplexProperties); + SelectedDynamicProperties = new HashSet(selectExpandNodeToCopy.SelectedDynamicProperties); + SelectedFunctions = new HashSet(selectExpandNodeToCopy.SelectedFunctions); + SelectedNavigationProperties = new HashSet(selectExpandNodeToCopy.SelectedNavigationProperties); + SelectedStructuralProperties = new HashSet(selectExpandNodeToCopy.SelectedStructuralProperties); + } + + /// + /// Creates a new instance of the class describing the set of structural properties, + /// nested properties, navigation properties, and actions to select and expand for the given . + /// + /// The structural type of the resource that would be written. + /// The serializer context to be used while creating the collection. + /// The default constructor is for unit testing only. + public SelectExpandNode(IEdmStructuredType structuredType, ODataSerializerContext writeContext) + : this(writeContext.SelectExpandClause, structuredType, writeContext.Model, writeContext.ExpandReference) + { + } + + /// + /// Creates a new instance of the class describing the set of structural properties, + /// nested properties, navigation properties, and actions to select and expand for the given . + /// + /// The parsed $select and $expand query options. + /// The structural type of the resource that would be written. + /// The that contains the given structural type. + public SelectExpandNode(SelectExpandClause selectExpandClause, IEdmStructuredType structuredType, IEdmModel model) + : this(selectExpandClause, structuredType, model, false) + { + } + + /// + /// Creates a new instance of the class describing the set of structural properties, + /// nested properties, navigation properties, and actions to select and expand for the given . + /// + /// The parsed $select and $expand query options. + /// The structural type of the resource that would be written. + /// The that contains the given structural type. + /// a boolean value indicating whether it's expanded reference. + public SelectExpandNode(SelectExpandClause selectExpandClause, IEdmStructuredType structuredType, IEdmModel model, bool expandedReference) + : this() + { + if (structuredType == null) + { + throw Error.ArgumentNull("structuredType"); + } + + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + // So far, it includes all properties of primitive, enum and collection of them + HashSet allStructuralProperties = new HashSet(); + + IEdmEntityType entityType = structuredType as IEdmEntityType; + if (expandedReference) + { + SelectAllDynamicProperties = false; + if (entityType != null) + { + // only need to include the key properties. + SelectedStructuralProperties = new HashSet(entityType.Key()); + } + } + else + { + // So far, it includes all properties of complex and collection of complex + HashSet allComplexStructuralProperties = new HashSet(); + GetStructuralProperties(structuredType, allStructuralProperties, allComplexStructuralProperties); + + // So far, it includes all navigation properties + HashSet allNavigationProperties; + HashSet allActions; + HashSet allFunctions; + + if (entityType != null) + { + allNavigationProperties = new HashSet(entityType.NavigationProperties()); + allActions = new HashSet(model.GetAvailableActions(entityType)); + allFunctions = new HashSet(model.GetAvailableFunctions(entityType)); + } + else + { + allNavigationProperties = new HashSet(); + allActions = new HashSet(); + allFunctions = new HashSet(); + } + + if (selectExpandClause == null) + { + SelectedStructuralProperties = allStructuralProperties; + SelectedComplexProperties = allComplexStructuralProperties; + SelectedNavigationProperties = allNavigationProperties; + SelectedActions = allActions; + SelectedFunctions = allFunctions; + SelectAllDynamicProperties = true; + } + else + { + if (selectExpandClause.AllSelected) + { + SelectedStructuralProperties = allStructuralProperties; + SelectedComplexProperties = allComplexStructuralProperties; + SelectedNavigationProperties = allNavigationProperties; + SelectedActions = allActions; + SelectedFunctions = allFunctions; + SelectAllDynamicProperties = true; + } + else + { + // Explicitly set SelectAllDynamicProperties as false, while the BuildSelections method will set it as true + // if it meets the select all condition. + SelectAllDynamicProperties = false; + BuildSelections(selectExpandClause, allStructuralProperties, allComplexStructuralProperties, allNavigationProperties, allActions, allFunctions); + } + + BuildExpansions(selectExpandClause, allNavigationProperties); + + // remove expanded navigation properties from the selected navigation properties. + SelectedNavigationProperties.ExceptWith(ExpandedProperties.Keys); + + // remove expanded navigation properties from the selected navigation properties. + SelectedNavigationProperties.ExceptWith(ReferencedNavigationProperties); + } + } + } + + /// + /// Gets the list of EDM structural properties (primitive, enum or collection of them) to be included in the response. + /// + public ISet SelectedStructuralProperties { get; private set; } + + /// + /// Gets the list of EDM navigation properties to be included as links in the response. It is deprecated in favor of ExpandedProperties + /// + public ISet SelectedNavigationProperties { get; private set; } + + /// + /// Gets the list of EDM navigation properties to be expanded in the response. + /// + [Obsolete("This property is deprecated in favor of ExpandedProperties as this property only contains a subset of the information.")] + public IDictionary ExpandedNavigationProperties + { + get + { + if (this.cachedExpandedClauses == null) + { + this.cachedExpandedClauses = ExpandedProperties.ToDictionary(item => item.Key, + item => item.Value != null ? item.Value.SelectAndExpand : null); + } + + return this.cachedExpandedClauses; + } + } + + /// + /// Gets the list of EDM navigation properties to be expanded in the response along with the nested query options embedded in the expand. + /// + public IDictionary ExpandedProperties { get; private set; } + + /// + /// Gets the list of EDM navigation properties to be expand referenced in the response. + /// + public ISet ReferencedNavigationProperties { get; private set; } + + /// + /// Gets the list of EDM nested properties (complex or collection of complex) to be included in the response. + /// + public ISet SelectedComplexProperties { get; private set; } + + /// + /// Gets the list of dynamic properties to select. + /// + public ISet SelectedDynamicProperties { get; private set; } + + /// + /// Gets the flag to indicate the dynamic property to be included in the response or not. + /// + public bool SelectAllDynamicProperties { get; private set; } + + /// + /// Gets the list of OData actions to be included in the response. + /// + public ISet SelectedActions { get; private set; } + + /// + /// Gets the list of OData functions to be included in the response. + /// + public ISet SelectedFunctions { get; private set; } + + private void BuildExpansions(SelectExpandClause selectExpandClause, HashSet allNavigationProperties) + { + foreach (SelectItem selectItem in selectExpandClause.SelectedItems) + { + ExpandedReferenceSelectItem expandReferenceItem = selectItem as ExpandedReferenceSelectItem; + if (expandReferenceItem != null) + { + ValidatePathIsSupported(expandReferenceItem.PathToNavigationProperty); + NavigationPropertySegment navigationSegment = (NavigationPropertySegment)expandReferenceItem.PathToNavigationProperty.LastSegment; + IEdmNavigationProperty navigationProperty = navigationSegment.NavigationProperty; + if (allNavigationProperties.Contains(navigationProperty)) + { + ExpandedNavigationSelectItem expandItem = selectItem as ExpandedNavigationSelectItem; + if (expandItem != null) + { + ExpandedProperties.Add(navigationProperty, expandItem); + } + else + { + ReferencedNavigationProperties.Add(navigationProperty); + } + } + } + } + } + + private void BuildSelections( + SelectExpandClause selectExpandClause, + HashSet allStructuralProperties, + HashSet allNestedProperties, + HashSet allNavigationProperties, + HashSet allActions, + HashSet allFunctions) + { + foreach (SelectItem selectItem in selectExpandClause.SelectedItems) + { + if (selectItem is ExpandedNavigationSelectItem) + { + continue; + } + + PathSelectItem pathSelectItem = selectItem as PathSelectItem; + if (pathSelectItem != null) + { + ValidatePathIsSupported(pathSelectItem.SelectedPath); + ODataPathSegment segment = pathSelectItem.SelectedPath.LastSegment; + + NavigationPropertySegment navigationPropertySegment = segment as NavigationPropertySegment; + if (navigationPropertySegment != null) + { + IEdmNavigationProperty navigationProperty = navigationPropertySegment.NavigationProperty; + if (allNavigationProperties.Contains(navigationProperty)) + { + SelectedNavigationProperties.Add(navigationProperty); + } + continue; + } + + PropertySegment structuralPropertySegment = segment as PropertySegment; + if (structuralPropertySegment != null) + { + IEdmStructuralProperty structuralProperty = structuralPropertySegment.Property; + if (allStructuralProperties.Contains(structuralProperty)) + { + SelectedStructuralProperties.Add(structuralProperty); + } + else if (allNestedProperties.Contains(structuralProperty)) + { + SelectedComplexProperties.Add(structuralProperty); + } + continue; + } + + OperationSegment operationSegment = segment as OperationSegment; + if (operationSegment != null) + { + AddOperations(allActions, allFunctions, operationSegment); + continue; + } + + DynamicPathSegment dynamicPathSegment = segment as DynamicPathSegment; + if (dynamicPathSegment != null) + { + SelectedDynamicProperties.Add(dynamicPathSegment.Identifier); + continue; + } + throw new ODataException(Error.Format(SRResources.SelectionTypeNotSupported, segment.GetType().Name)); + } + + WildcardSelectItem wildCardSelectItem = selectItem as WildcardSelectItem; + if (wildCardSelectItem != null) + { + SelectedStructuralProperties = allStructuralProperties; + SelectedComplexProperties = allNestedProperties; + SelectedNavigationProperties = allNavigationProperties; + SelectAllDynamicProperties = true; + continue; + } + + NamespaceQualifiedWildcardSelectItem wildCardActionSelection = selectItem as NamespaceQualifiedWildcardSelectItem; + if (wildCardActionSelection != null) + { + SelectedActions = allActions; + SelectedFunctions = allFunctions; + continue; + } + + throw new ODataException(Error.Format(SRResources.SelectionTypeNotSupported, selectItem.GetType().Name)); + } + } + + private void AddOperations(HashSet allActions, HashSet allFunctions, OperationSegment operationSegment) + { + foreach (IEdmOperation operation in operationSegment.Operations) + { + IEdmAction action = operation as IEdmAction; + if (action != null && allActions.Contains(action)) + { + SelectedActions.Add(action); + } + + IEdmFunction function = operation as IEdmFunction; + if (function != null && allFunctions.Contains(function)) + { + SelectedFunctions.Add(function); + } + } + } + + // we only support paths of type 'cast/structuralOrNavPropertyOrAction' and 'structuralOrNavPropertyOrAction'. + internal static void ValidatePathIsSupported(ODataPath path) + { + int segmentCount = path.Count(); + + if (segmentCount > 2) + { + throw new ODataException(SRResources.UnsupportedSelectExpandPath); + } + + if (segmentCount == 2) + { + if (!(path.FirstSegment is TypeSegment)) + { + throw new ODataException(SRResources.UnsupportedSelectExpandPath); + } + } + + ODataPathSegment lastSegment = path.LastSegment; + if (!(lastSegment is NavigationPropertySegment + || lastSegment is PropertySegment + || lastSegment is OperationSegment + || lastSegment is DynamicPathSegment)) + { + throw new ODataException(SRResources.UnsupportedSelectExpandPath); + } + } + + /// + /// Separate the structural properties into two parts: + /// 1. Complex and collection of complex are nested structural properties. + /// 2. Others are non-nested structural properties. + /// + /// The structural type of the resource. + /// The non-nested structural properties of the structural type. + /// The nested structural properties of the structural type. + public static void GetStructuralProperties(IEdmStructuredType structuredType, HashSet structuralProperties, + HashSet nestedStructuralProperties) + { + if (structuredType == null) + { + throw Error.ArgumentNull("structuredType"); + } + + if (structuralProperties == null) + { + throw Error.ArgumentNull("structuralProperties"); + } + + if (nestedStructuralProperties == null) + { + throw Error.ArgumentNull("nestedStructuralProperties"); + } + + foreach (var edmStructuralProperty in structuredType.StructuralProperties()) + { + if (edmStructuralProperty.Type.IsComplex()) + { + nestedStructuralProperties.Add(edmStructuralProperty); + } + else if (edmStructuralProperty.Type.IsCollection()) + { + if (edmStructuralProperty.Type.AsCollection().ElementType().IsComplex()) + { + nestedStructuralProperties.Add(edmStructuralProperty); + } + else + { + structuralProperties.Add(edmStructuralProperty); + } + } + else + { + structuralProperties.Add(edmStructuralProperty); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmComplexObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmComplexObject.cs new file mode 100644 index 0000000..66a690f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmComplexObject.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an backed by a CLR object with a one-to-one mapping. + /// + internal class TypedEdmComplexObject : TypedEdmStructuredObject, IEdmComplexObject + { + /// + /// Initializes a new instance of the class. + /// + /// The backing CLR instance. + /// The of this object. + /// The . + public TypedEdmComplexObject(object instance, IEdmComplexTypeReference edmType, IEdmModel edmModel) + : base(instance, edmType, edmModel) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmEntityObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmEntityObject.cs new file mode 100644 index 0000000..0af48af --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmEntityObject.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an backed by a CLR object with a one-to-one mapping. + /// + internal class TypedEdmEntityObject : TypedEdmStructuredObject, IEdmEntityObject + { + /// + /// Initializes a new instance of the class. + /// + /// The backing CLR instance. + /// The of this object. + /// The . + public TypedEdmEntityObject(object instance, IEdmEntityTypeReference edmType, IEdmModel edmModel) + : base(instance, edmType, edmModel) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmStructuredObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmStructuredObject.cs new file mode 100644 index 0000000..7ea75f1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/TypedEdmStructuredObject.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Diagnostics.Contracts; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an backed by a CLR object with a one-to-one mapping. + /// + internal abstract class TypedEdmStructuredObject : IEdmStructuredObject + { + private static readonly ConcurrentDictionary, Func> _propertyGetterCache = + new ConcurrentDictionary, Func>(); + + private IEdmStructuredTypeReference _edmType; + private Type _type; + + /// + /// Initializes a new instance of the class. + /// + /// The backing CLR instance. + /// The of this object. + /// The . + protected TypedEdmStructuredObject(object instance, IEdmStructuredTypeReference edmType, IEdmModel edmModel) + { + Contract.Assert(edmType != null); + + Instance = instance; + _edmType = edmType; + _type = instance == null ? null : instance.GetType(); + Model = edmModel; + } + + /// + /// Gets the backing CLR object. + /// + public object Instance { get; private set; } + + /// + /// Gets the EDM model. + /// + public IEdmModel Model { get; private set; } + + /// + public IEdmTypeReference GetEdmType() + { + return _edmType; + } + + /// + public bool TryGetPropertyValue(string propertyName, out object value) + { + if (Instance == null) + { + value = null; + return false; + } + + Contract.Assert(_type != null); + + Func getter = GetOrCreatePropertyGetter(_type, propertyName, _edmType, Model); + if (getter == null) + { + value = null; + return false; + } + else + { + value = getter(Instance); + return true; + } + } + + internal static Func GetOrCreatePropertyGetter( + Type type, + string propertyName, + IEdmStructuredTypeReference edmType, + IEdmModel model) + { + Tuple key = Tuple.Create(propertyName, type); + Func getter; + + if (!_propertyGetterCache.TryGetValue(key, out getter)) + { + IEdmProperty property = edmType.FindProperty(propertyName); + if (property != null && model != null) + { + propertyName = EdmLibHelpers.GetClrPropertyName(property, model) ?? propertyName; + } + + getter = CreatePropertyGetter(type, propertyName); + _propertyGetterCache[key] = getter; + } + + return getter; + } + + private static Func CreatePropertyGetter(Type type, string propertyName) + { + PropertyInfo property = type.GetProperty(propertyName); + + if (property == null) + { + return null; + } + + var helper = new PropertyHelper(property); + + return helper.GetValue; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FunctionImportComparer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FunctionImportComparer.cs new file mode 100644 index 0000000..5fc44c2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/FunctionImportComparer.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + internal class FunctionImportComparer : IEqualityComparer + { + public bool Equals(IEdmFunctionImport left, IEdmFunctionImport right) + { + if (Object.ReferenceEquals(left, right)) + { + return true; + } + + if (Object.ReferenceEquals(left, null) || Object.ReferenceEquals(right, null)) + { + return false; + } + + return left.Name == right.Name; + } + + public int GetHashCode(IEdmFunctionImport functionImport) + { + if (Object.ReferenceEquals(functionImport, null)) + { + return 0; + } + + return functionImport.Function.Name == null ? 0 : functionImport.Function.Name.GetHashCode(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/GetNextPageHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/GetNextPageHelper.cs new file mode 100644 index 0000000..900cd94 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/GetNextPageHelper.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Text; + +namespace Microsoft.AspNet.OData +{ + /// + /// Helper to generate next page links. + /// + internal static partial class GetNextPageHelper + { + internal static Uri GetNextPageLink(Uri requestUri, IEnumerable> queryParameters, int pageSize, object instance = null, Func objectToSkipTokenValue = null) + { + Contract.Assert(requestUri != null); + Contract.Assert(queryParameters != null); + + StringBuilder queryBuilder = new StringBuilder(); + + int nextPageSkip = pageSize; + + String skipTokenValue = objectToSkipTokenValue == null ? null : objectToSkipTokenValue(instance); + //If no value for skiptoken can be extracted; revert to using skip + bool useDefaultSkip = String.IsNullOrWhiteSpace(skipTokenValue); + + foreach (KeyValuePair kvp in queryParameters) + { + string key = kvp.Key.ToLowerInvariant(); + string value = kvp.Value; + + switch (key) + { + case "$top": + int top; + if (Int32.TryParse(value, out top)) + { + // We decrease top by the pageSize because that's the number of results we're returning in the current page. If the $top query option's value is less than or equal to the page size, there is no next page. + if (top > pageSize) + { + value = (top - pageSize).ToString(CultureInfo.InvariantCulture); + } + else + { + return null; + } + } + break; + case "$skip": + if (useDefaultSkip) + { + //Need to increment skip only if we are not using skiptoken + int skip; + if (Int32.TryParse(value, out skip)) + { + // We increase skip by the pageSize because that's the number of results we're returning in the current page + nextPageSkip += skip; + } + } + continue; + case "$skiptoken": + continue; + default: + break; + } + + if (key.Length > 0 && key[0] == '$') + { + // $ is a legal first character in query keys + key = '$' + Uri.EscapeDataString(key.Substring(1)); + } + else + { + key = Uri.EscapeDataString(key); + } + + value = Uri.EscapeDataString(value); + + queryBuilder.Append(key); + queryBuilder.Append('='); + queryBuilder.Append(value); + queryBuilder.Append('&'); + } + + if (useDefaultSkip) + { + queryBuilder.AppendFormat("$skip={0}", nextPageSkip); + } + else + { + queryBuilder.AppendFormat("$skiptoken={0}", skipTokenValue); + } + + UriBuilder uriBuilder = new UriBuilder(requestUri) + { + Query = queryBuilder.ToString() + }; + + return uriBuilder.Uri; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IDelta.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IDelta.cs new file mode 100644 index 0000000..30f966d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IDelta.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.OData +{ + /// + /// allows and tracks changes to an object. + /// + public interface IDelta + { + /// + /// Returns the Properties that have been modified through this IDelta as an + /// enumerable of Property Names + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not appropriate to be a property")] + IEnumerable GetChangedPropertyNames(); + + /// + /// Returns the Properties that have not been modified through this IDelta as an + /// enumerable of Property Names + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Not appropriate to be a property")] + IEnumerable GetUnchangedPropertyNames(); + + /// + /// Attempts to set the Property called to the specified. + /// + /// The name of the Property + /// The new value of the Property + /// Returns true if successful and false if not. + bool TrySetPropertyValue(string name, object value); + + /// + /// Attempts to get the value of the Property called from the underlying Entity. + /// + /// The name of the Property + /// The value of the Property + /// Returns true if the Property was found and false if not. + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Generics not appropriate here")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Out param is appropriate here")] + bool TryGetPropertyValue(string name, out object value); + + /// + /// Attempts to get the of the Property called from the underlying Entity. + /// + /// The name of the Property + /// The type of the Property + /// Returns true if the Property was found and false if not. + bool TryGetPropertyType(string name, out Type type); + + /// + /// Clears the . + /// + void Clear(); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmChangedObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmChangedObject.cs new file mode 100644 index 0000000..6dc0b68 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmChangedObject.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// Base interface to be implemented by any Delta object required to be part of the DeltaFeed Payload. + /// + public interface IEdmChangedObject : IEdmStructuredObject + { + /// + /// DeltaKind for the objects part of the DeltaFeed Payload. + /// Used to determine which Delta object to create during serialization. + /// + EdmDeltaEntityKind DeltaKind { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmComplexObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmComplexObject.cs new file mode 100644 index 0000000..a91d9ac --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmComplexObject.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// + public interface IEdmComplexObject : IEdmStructuredObject + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedEntityObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedEntityObject.cs new file mode 100644 index 0000000..ed673e5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedEntityObject.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// Holds the properties necessary to create the ODataDeltaDeletedEntry. + /// + public interface IEdmDeltaDeletedEntityObject : IEdmChangedObject + { + /// + /// The id of the deleted entity (same as the odata.id returned or computed when calling GET on resource), which may be absolute or relative. + /// + string Id { get; set; } + + /// + /// Optional. Either deleted, if the entity was deleted (destroyed), or changed if the entity was removed from membership in the result (i.e., due to a data change). + /// + DeltaDeletedEntryReason Reason { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedLink.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedLink.cs new file mode 100644 index 0000000..e173486 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaDeletedLink.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// Holds the properties necessary to create the ODataDeltaDeletedLink. + /// + public interface IEdmDeltaDeletedLink : IEdmDeltaLinkBase, IEdmChangedObject + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLink.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLink.cs new file mode 100644 index 0000000..a03f8af --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLink.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// Holds the properties necessary to create the ODataDeltaLink. + /// + public interface IEdmDeltaLink : IEdmDeltaLinkBase, IEdmChangedObject + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLinkBase.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLinkBase.cs new file mode 100644 index 0000000..dde7174 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmDeltaLinkBase.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// Holds the properties necessary to create either ODataDeltaLink or ODataDeltaDeletedLink. + /// + public interface IEdmDeltaLinkBase : IEdmChangedObject + { + /// + /// The Uri of the entity from which the relationship is defined, which may be absolute or relative. + /// + Uri Source { get; set; } + + /// + /// The Uri of the related entity, which may be absolute or relative. + /// + Uri Target { get; set; } + + /// + /// The name of the relationship property on the parent object. + /// + string Relationship { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEntityObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEntityObject.cs new file mode 100644 index 0000000..e1f7ee8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEntityObject.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// + public interface IEdmEntityObject : IEdmStructuredObject + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEnumObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEnumObject.cs new file mode 100644 index 0000000..7441ec5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmEnumObject.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an enum value. + /// + [SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Justification = "Marker interface acceptable here for derivation")] + public interface IEdmEnumObject : IEdmObject + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmObject.cs new file mode 100644 index 0000000..dd26d9f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmObject.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// + public interface IEdmObject + { + /// + /// Gets the of this instance. + /// + /// The of this instance. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "This should not be serialized. Having it as a method is more appropriate.")] + IEdmTypeReference GetEdmType(); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmStructuredObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmStructuredObject.cs new file mode 100644 index 0000000..e4810ca --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IEdmStructuredObject.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an instance of an . + /// + public interface IEdmStructuredObject : IEdmObject + { + /// + /// Gets the value of the property with the given name. + /// + /// The name of the property to get. + /// When this method returns, contains the value of the property with the given name, if the property is found; + /// otherwise, null. The parameter is passed uninitialized. + /// true if the instance contains the property with the given name; otherwise, false. + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", + Justification = "Generics not appropriate here as this interface supports typeless")] + bool TryGetPropertyValue(string propertyName, out object value); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IPerRouteContainer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IPerRouteContainer.cs new file mode 100644 index 0000000..2d6a8fc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/IPerRouteContainer.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData +{ + /// + /// An interface for managing per-route service containers. + /// + public interface IPerRouteContainer + { + /// + /// Gets or sets a function to build an + /// + Func BuilderFactory { get; set; } + + /// + /// Create a root container for a given route name. + /// + /// The route name. + /// The configuration actions to apply to the container. + /// An instance of to manage services for a route. + IServiceProvider CreateODataRootContainer(string routeName, Action configureAction); + + /// + /// Check if the root container for a given route name exists. + /// + /// The route name. + /// true if root container for the route name exists, false otherwise. + bool HasODataRootContainer(string routeName); + + /// + /// Get the root container for a given route name. + /// + /// The route name. + /// The root container for the route name. + IServiceProvider GetODataRootContainer(string routeName); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionDescriptor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionDescriptor.cs new file mode 100644 index 0000000..a6001ac --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionDescriptor.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Provides information about the action methods. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiActionDescriptor + { + /// + /// Gets the name of the controller. + /// + string ControllerName { get; } + + /// + /// Gets the name of the action. + /// + string ActionName { get; } + + /// + /// Returns the custom attributes associated with the action descriptor. + /// + /// The type of attribute to search for. + /// true to search this action's inheritance chain to find the attributes; otherwise, false. + /// A list of attributes of type T. + IEnumerable GetCustomAttributes(bool inherit) where T : Attribute; + + /// + /// Determine if the Http method is a match. + /// + /// Method to test. + bool IsHttpMethodSupported(ODataRequestMethod method); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionMap.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionMap.cs new file mode 100644 index 0000000..26eae99 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiActionMap.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// An interface used to search for an available action. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiActionMap + { + /// + /// Determines whether a specified key exists. + /// + /// The key. + /// True if the key exist; false otherwise. + bool Contains(string name); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiAssembliesResolver.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiAssembliesResolver.cs new file mode 100644 index 0000000..289035a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiAssembliesResolver.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Provides an abstraction for managing the assemblies of an application. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiAssembliesResolver + { + /// + /// Gets a list of assemblies available for the application. + /// + /// A list of assemblies available for the application. + IEnumerable Assemblies { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiContext.cs new file mode 100644 index 0000000..0406ca7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiContext.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// General context for WebApi. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiContext + { + /// + /// Gets or sets the parsed OData of the request. + /// + ApplyClause ApplyClause { get; set; } + + /// + /// Gets or sets the next link for the OData response. + /// + Uri NextLink { get; set; } + + /// + /// Gets or sets the delta link for the OData response. + /// + Uri DeltaLink { get; set; } + + /// + /// Page size to be used by skiptoken implementation for the top-level resource for the request. + /// + int PageSize { get; set; } + + /// + /// Gets the OData path. + /// + ODataPath Path { get; } + + /// + /// Gets the route name for generating OData links. + /// + string RouteName { get; } + + /// + /// Gets the data store used by s to store any custom route data. + /// + /// Initially an empty IDictionary<string, object>. + IDictionary RoutingConventionsStore { get; } + + /// + /// Gets or sets the processed OData of the request. + /// + SelectExpandClause ProcessedSelectExpandClause { get; set; } + + /// + /// Gets or sets the of the request. + /// + ODataQueryOptions QueryOptions { get; set; } + + /// + /// Gets or sets the total count for the OData response. + /// + /// null if no count should be sent back to the client. + long? TotalCount { get; } + + /// + /// Gets or sets the total count function for the OData response. + /// + Func TotalCountFunc { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiControllerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiControllerContext.cs new file mode 100644 index 0000000..f5f07f2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiControllerContext.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Routing.Conventions; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Contains information for a single HTTP operation. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiControllerContext + { + /// + /// The selected controller result. + /// + SelectControllerResult ControllerResult { get; } + + /// + /// Gets the request. + /// + IWebApiRequestMessage Request { get; } + + /// + /// Gets the route data. + /// + IDictionary RouteData { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiHeaders.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiHeaders.cs new file mode 100644 index 0000000..03e7523 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiHeaders.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Represents the collection of Request Headers as defined in RFC 2616. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiHeaders + { + /// + /// Return if a specified header and specified values are stored in the collection. + /// + /// The specified header. + /// The specified header values. + /// true is the specified header name and values are stored in the collection; otherwise false. + bool TryGetValues(string key, out IEnumerable values); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiOptions.cs new file mode 100644 index 0000000..8e082db --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiOptions.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// An interface for WebApi options. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiOptions + { + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not. + /// + ODataUrlKeyDelimiter UrlKeyDelimiter { get; } + + /// + /// Gets or Sets a value indicating if value should be emitted for dynamic properties which are null. + /// + bool NullDynamicPropertyIsEnabled { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiRequestMessage.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiRequestMessage.cs new file mode 100644 index 0000000..9a01c41 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiRequestMessage.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Represents a HTTP request message. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiRequestMessage + { + /// + /// Gets the contents of the HTTP message. + /// + IWebApiContext Context { get; } + + /// + /// Gets a value indicating if this is a count request. + /// + /// + bool IsCountRequest(); + + /// + /// Gets the HTTP method used by the HTTP request message. + /// + ODataRequestMethod Method { get; } + + /// + /// Get the options associated with the request. + /// + IWebApiOptions Options { get; } + + /// + /// Get the options associated with the request. + /// + IWebApiHeaders Headers { get; } + + /// + /// The request container associated with the request. + /// + IServiceProvider RequestContainer { get; } + + /// + /// Gets the Uri used for the HTTP request. + /// + Uri RequestUri { get; } + + /// + /// Gets the deserializer provider associated with the request. + /// + /// + ODataDeserializerProvider DeserializerProvider { get; } + + /// + /// Creates an ETag from concurrency property names and values. + /// + /// The input property names and values. + /// The generated ETag string. + string CreateETag(IDictionary properties); + + /// + /// Gets the EntityTagHeaderValue ETag. + /// + ETag GetETag(EntityTagHeaderValue etagHeaderValue); + + /// + /// Gets the EntityTagHeaderValue ETag. + /// + ETag GetETag(EntityTagHeaderValue etagHeaderValue); + + /// + /// Get the next page link for a given page size. + /// + /// The page size. + /// The instance based on which the skiptoken value is generated + /// Function that takes in the last object and returns the skiptoken value string. + /// + Uri GetNextPageLink(int pageSize, object instance, Func objToSkipTokenValue); + + /// + /// Get a list of content Id mappings associated with the request. + /// + /// + IDictionary ODataContentIdMapping { get; } + + /// + /// Get the path handler associated with the request. + /// + /// + IODataPathHandler PathHandler { get; } + + /// + /// Get the name value pairs from the query. + /// + /// + IDictionary QueryParameters { get; } + + /// + /// Get the reader settings associated with the request. + /// + /// + ODataMessageReaderSettings ReaderSettings { get; } + + /// + /// Gets the writer settings associated with the request. + /// + /// + ODataMessageWriterSettings WriterSettings { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiUrlHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiUrlHelper.cs new file mode 100644 index 0000000..a4c1546 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Interfaces/IWebApiUrlHelper.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Contains methods to build URLs for ASP.NET within an application. + /// + /// + /// This class is not intended to be exposed publicly; it used for the internal + /// implementations of SelectControl(). Any design which makes this class public + /// should find an alternative design. + /// + internal interface IWebApiUrlHelper + { + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The OData path segments. + /// The generated OData link. + string CreateODataLink(IList segments); + + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The OData path segments. + /// The generated OData link. + string CreateODataLink(params ODataPathSegment[] segments); + + /// + /// Generates an OData link using the given OData route name, path handler, and segments. + /// + /// The name of the OData route. + /// The path handler to use for generating the link. + /// The OData path segments. + /// The generated OData link. + string CreateODataLink(string routeName, IODataPathHandler pathHandler, IList segments); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/MetadataController.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/MetadataController.cs new file mode 100644 index 0000000..857f706 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/MetadataController.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a controller for generating OData servicedoc and metadata document ($metadata). + /// + public class MetadataController : ODataController + { + private static readonly Version _defaultEdmxVersion = new Version(4, 0); + + /// + /// Generates the OData $metadata document. + /// + /// The representing $metadata. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Property not appropriate")] + public IEdmModel GetMetadata() + { + return GetModel(); + } + + /// + /// Generates the OData service document. + /// + /// The service document for the service. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Property not appropriate")] + public ODataServiceDocument GetServiceDocument() + { + IEdmModel model = GetModel(); + ODataServiceDocument serviceDocument = new ODataServiceDocument(); + IEdmEntityContainer container = model.EntityContainer; + + // Add EntitySets into service document + serviceDocument.EntitySets = container.EntitySets().Select( + e => GetODataEntitySetInfo(model.GetNavigationSourceUrl(e).ToString(), e.Name)); + + // Add Singletons into the service document + IEnumerable singletons = container.Elements.OfType(); + serviceDocument.Singletons = singletons.Select( + e => GetODataSingletonInfo(model.GetNavigationSourceUrl(e).ToString(), e.Name)); + + // Add FunctionImports into service document + // ODL spec says: + // The edm:FunctionImport for a parameterless function MAY include the IncludeInServiceDocument attribute + // whose Boolean value indicates whether the function import is advertised in the service document. + // If no value is specified for this attribute, its value defaults to false. + + // Find all parameterless functions with "IncludeInServiceDocument = true" + IEnumerable functionImports = container.Elements.OfType() + .Where(f => !f.Function.Parameters.Any() && f.IncludeInServiceDocument); + + serviceDocument.FunctionImports = functionImports.Distinct(new FunctionImportComparer()) + .Select(f => GetODataFunctionImportInfo(f.Name)); + + return serviceDocument; + } + + private static ODataEntitySetInfo GetODataEntitySetInfo(string url, string name) + { + ODataEntitySetInfo info = new ODataEntitySetInfo + { + Name = name, // Required for JSON support + Url = new Uri(url, UriKind.Relative) + }; + + return info; + } + + private static ODataSingletonInfo GetODataSingletonInfo(string url, string name) + { + ODataSingletonInfo info = new ODataSingletonInfo + { + Name = name, + Url = new Uri(url, UriKind.Relative) + }; + + return info; + } + + private static ODataFunctionImportInfo GetODataFunctionImportInfo(string name) + { + ODataFunctionImportInfo info = new ODataFunctionImportInfo + { + Name = name, + Url = new Uri(name, UriKind.Relative) // Relative to the OData root + }; + + return info; + } + + private IEdmModel GetModel() + { + IEdmModel model = Request.GetModel(); + if (model == null) + { + throw Error.InvalidOperation(SRResources.RequestMustHaveModel); + } + + model.SetEdmxVersion(_defaultEdmxVersion); + return model; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems new file mode 100644 index 0000000..5dd8aa7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems @@ -0,0 +1,444 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileDirectory) + true + c56fbe13-ce70-49e7-8260-2b9ee82e962c + + + Microsoft.AspNet.OData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + PropertyContainer.generated.tt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextTemplatingFileGenerator + PropertyContainer.generated.cs + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.shproj b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.shproj new file mode 100644 index 0000000..8c7bf51 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.shproj @@ -0,0 +1,12 @@ + + + + b6b951b6-c3f0-4b8e-8955-e039145e7dec + 14.0 + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfiguration.cs new file mode 100644 index 0000000..dfdc408 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfiguration.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Builder; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a queryable configuration on an EDM navigation property, including auto expanded. + /// + public class NavigationPropertyQueryableConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The NavigationPropertyConfiguration containing queryable configuration. + public NavigationPropertyQueryableConfiguration(NavigationPropertyConfiguration propertyConfiguration) + { + AutoExpand = propertyConfiguration.Expand; + } + + /// + /// Gets or sets whether the property is auto expanded. + /// + public bool AutoExpand { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfigurationAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfigurationAnnotation.cs new file mode 100644 index 0000000..2208671 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NavigationPropertyQueryableConfigurationAnnotation.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an annotation to add the queryable configuration on an EDM navigation property, including auto expanded. + /// + public class NavigationPropertyQueryableConfigurationAnnotation + { + /// + /// Initializes a new instance of class. + /// + /// The queryable configuration for the EDM navigation property. + public NavigationPropertyQueryableConfigurationAnnotation(NavigationPropertyQueryableConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + Configuration = configuration; + } + + /// + /// Gets the configuration for the EDM property. + /// + public NavigationPropertyQueryableConfiguration Configuration { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NonValidatingParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NonValidatingParameterBindingAttribute.cs new file mode 100644 index 0000000..5e850be --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NonValidatingParameterBindingAttribute.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// An attribute to disable WebApi model validation for a particular type. + /// + internal sealed partial class NonValidatingParameterBindingAttribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NullEdmComplexObject.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NullEdmComplexObject.cs new file mode 100644 index 0000000..e864bb0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/NullEdmComplexObject.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that is null. + /// + public class NullEdmComplexObject : IEdmComplexObject + { + private IEdmComplexTypeReference _edmType; + + /// + /// Initializes a new instance of the class. + /// + /// The EDM type of this object. + public NullEdmComplexObject(IEdmComplexTypeReference edmType) + { + if (edmType == null) + { + throw Error.ArgumentNull("edmType"); + } + + _edmType = edmType; + } + + /// + public bool TryGetPropertyValue(string propertyName, out object value) + { + throw Error.InvalidOperation(SRResources.EdmComplexObjectNullRef, propertyName, _edmType.ToTraceString()); + } + + /// + public IEdmTypeReference GetEdmType() + { + return _edmType; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataActionParameters.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataActionParameters.cs new file mode 100644 index 0000000..0a082fa --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataActionParameters.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.OData +{ + /// + /// ActionPayload holds the Parameter names and values provided by a client in a POST request + /// to invoke a particular Action. The Parameter values are stored in the dictionary keyed using the Parameter name. + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "ODataActionParameters is more appropriate here.")] + [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "ODataActionParameters is not serializable.")] + [NonValidatingParameterBinding] + public class ODataActionParameters : Dictionary + { + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataController.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataController.cs new file mode 100644 index 0000000..02132e9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataController.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// The base controller for OData WebApi. + /// + /// This class exist in shared in order to implement . + public abstract partial class ODataController + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataNullValueMessageHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataNullValueMessageHandler.cs new file mode 100644 index 0000000..a60d571 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataNullValueMessageHandler.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.ObjectModel; +using System.Net; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that converts null values in OData responses to + /// HTTP NotFound responses or NoContent responses following the OData specification. + /// + public partial class ODataNullValueMessageHandler + { + // Determines if a status code needs to be changed based on the path of the request and returns the new + // status code or null if no change is required. + internal static HttpStatusCode? GetUpdatedResponseStatusCodeOrNull(ODataPath oDataPath) + { + if (oDataPath == null || oDataPath.Segments == null || oDataPath.Segments.Count == 0) + { + return null; + } + + // Skip any sequence of cast segments at the end of the path. + int currentIndex = oDataPath.Segments.Count - 1; + ReadOnlyCollection segments = oDataPath.Segments; + while (currentIndex >= 0 && segments[currentIndex] is TypeSegment) + { + currentIndex--; + } + + // Null value properties should be treated in the same way independent of whether the user asked for the + // raw value of the property or a specific format, so we skip the $value segment as it can only be + // preceded by a property access segment. + if (currentIndex >= 0 && segments[currentIndex] is ValueSegment) + { + currentIndex--; + } + + // Protect ourselves against malformed path segments. + if (currentIndex < 0) + { + return null; + } + + KeySegment keySegment = segments[currentIndex] as KeySegment; + if (keySegment != null) + { + // Look at the previous segment to decide, but skip any possible sequence of cast segments in + // between. + currentIndex--; + while (currentIndex >= 0 && segments[currentIndex] is TypeSegment) + { + currentIndex--; + } + if (currentIndex < 0) + { + return null; + } + + if (segments[currentIndex] is EntitySetSegment) + { + // Return 404 if we were trying to retrieve a specific entity from an entity set. + return HttpStatusCode.NotFound; + } + + if (segments[currentIndex] is NavigationPropertySegment) + { + // Return 204 if we were trying to retrieve a related entity via a navigation property. + return HttpStatusCode.NoContent; + } + + return null; + } + + PropertySegment propertySegment = segments[currentIndex] as PropertySegment; + if (propertySegment != null) + { + // Return 204 only if the property is single valued (not a collection of values). + return GetChangedStatusCodeForProperty(propertySegment); + } + + NavigationPropertySegment navigationSegment = segments[currentIndex] as NavigationPropertySegment; + if (navigationSegment != null) + { + // Return 204 only if the navigation property is a single related entity and not a collection + // of entities. + return GetChangedStatusCodeForNavigationProperty(navigationSegment); + } + + SingletonSegment singletonSegment = segments[currentIndex] as SingletonSegment; + if (singletonSegment != null) + { + // Return 404 for a singleton with a null value. + return HttpStatusCode.NotFound; + } + + return null; + } + + private static HttpStatusCode? GetChangedStatusCodeForNavigationProperty(NavigationPropertySegment navigation) + { + EdmMultiplicity multiplicity = navigation.NavigationProperty.TargetMultiplicity(); + return multiplicity == EdmMultiplicity.ZeroOrOne || multiplicity == EdmMultiplicity.One ? + HttpStatusCode.NoContent : + (HttpStatusCode?)null; + } + + private static HttpStatusCode? GetChangedStatusCodeForProperty(PropertySegment propertySegment) + { + IEdmTypeReference type = propertySegment.Property.Type; + return type.IsPrimitive() || type.IsComplex() ? HttpStatusCode.NoContent : (HttpStatusCode?)null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContext.cs new file mode 100644 index 0000000..0799db4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContext.cs @@ -0,0 +1,201 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// This defines some context information used to perform query composition. + /// + public class ODataQueryContext + { + private DefaultQuerySettings _defaultQuerySettings; + + /// + /// Constructs an instance of with , element CLR type, + /// and . + /// + /// The EdmModel that includes the corresponding to + /// the given . + /// The CLR type of the element of the collection being queried. + /// The parsed . + /// + /// This is a public constructor used for stand-alone scenario; in this case, the services + /// container may not be present. + /// + public ODataQueryContext(IEdmModel model, Type elementClrType, ODataPath path) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (elementClrType == null) + { + throw Error.ArgumentNull("elementClrType"); + } + + ElementType = model.GetEdmType(elementClrType); + + if (ElementType == null) + { + throw Error.Argument("elementClrType", SRResources.ClrTypeNotInModel, elementClrType.FullName); + } + + ElementClrType = elementClrType; + Model = model; + Path = path; + NavigationSource = GetNavigationSource(Model, ElementType, path); + GetPathContext(); + } + + /// + /// Constructs an instance of with , element EDM type, + /// and . + /// + /// The EDM model the given EDM type belongs to. + /// The EDM type of the element of the collection being queried. + /// The parsed . + public ODataQueryContext(IEdmModel model, IEdmType elementType, ODataPath path) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + if (elementType == null) + { + throw Error.ArgumentNull("elementType"); + } + + Model = model; + ElementType = elementType; + Path = path; + NavigationSource = GetNavigationSource(Model, ElementType, path); + GetPathContext(); + } + + internal ODataQueryContext(IEdmModel model, Type elementClrType) + : this(model, elementClrType, path: null) + { + } + + internal ODataQueryContext(IEdmModel model, IEdmType elementType) + : this(model, elementType, path: null) + { + } + + /// + /// Gets the given . + /// + public DefaultQuerySettings DefaultQuerySettings + { + get + { + if (_defaultQuerySettings == null) + { + _defaultQuerySettings = RequestContainer == null + ? new DefaultQuerySettings() + : RequestContainer.GetRequiredService(); + } + + return _defaultQuerySettings; + } + } + + /// + /// Gets the given that contains the EntitySet. + /// + public IEdmModel Model { get; private set; } + + /// + /// Gets the of the element. + /// + public IEdmType ElementType { get; private set; } + + /// + /// Gets the that contains the element. + /// + public IEdmNavigationSource NavigationSource { get; private set; } + + /// + /// Gets the CLR type of the element. + /// + public Type ElementClrType { get; internal set; } + + /// + /// Gets the . + /// + public ODataPath Path { get; private set; } + + /// + /// Gets the request container. + /// + /// + /// The services container may not be present. See the constructor in this file for + /// use in stand-alone scenarios. + /// + public IServiceProvider RequestContainer { get; internal set; } + + internal IEdmProperty TargetProperty { get; private set; } + + internal IEdmStructuredType TargetStructuredType { get; private set; } + + internal string TargetName { get; private set; } + + private static IEdmNavigationSource GetNavigationSource(IEdmModel model, IEdmType elementType, ODataPath odataPath) + { + Contract.Assert(model != null); + Contract.Assert(elementType != null); + + IEdmNavigationSource navigationSource = (odataPath != null) ? odataPath.NavigationSource : null; + if (navigationSource != null) + { + return navigationSource; + } + + IEdmEntityContainer entityContainer = model.EntityContainer; + if (entityContainer == null) + { + return null; + } + + List matchedNavigationSources = + entityContainer.EntitySets().Where(e => e.EntityType() == elementType).ToList(); + + return (matchedNavigationSources.Count != 1) ? null : matchedNavigationSources[0]; + } + + private void GetPathContext() + { + if (Path != null) + { + IEdmProperty property; + IEdmStructuredType structuredType; + string name; + EdmLibHelpers.GetPropertyAndStructuredTypeFromPath( + Path.Segments, + out property, + out structuredType, + out name); + + TargetProperty = property; + TargetStructuredType = structuredType; + TargetName = name; + } + else + { + TargetStructuredType = ElementType as IEdmStructuredType; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContextExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContextExtensions.cs new file mode 100644 index 0000000..04f1e8f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryContextExtensions.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.OData +{ + internal static class ODataQueryContextExtensions + { + public static ODataQuerySettings UpdateQuerySettings(this ODataQueryContext context, ODataQuerySettings querySettings, IQueryable query) + { + ODataQuerySettings updatedSettings = (context == null || context.RequestContainer == null) + ? new ODataQuerySettings() + : context.RequestContainer.GetRequiredService(); + + updatedSettings.CopyFrom(querySettings); + + if (updatedSettings.HandleNullPropagation == HandleNullPropagationOption.Default) + { + updatedSettings.HandleNullPropagation = query != null + ? HandleNullPropagationOptionHelper.GetDefaultHandleNullPropagationOption(query) + : HandleNullPropagationOption.True; + } + + return updatedSettings; + } + + public static SkipTokenHandler GetSkipTokenHandler(this ODataQueryContext context) + { + if (context == null || context.RequestContainer == null) + { + return DefaultSkipTokenHandler.Instance; + } + + return context.RequestContainer.GetRequiredService(); + } + + public static SkipTokenQueryValidator GetSkipTokenQueryValidator(this ODataQueryContext context) + { + if (context == null || context.RequestContainer == null) + { + return new SkipTokenQueryValidator(); + } + + return context.RequestContainer.GetRequiredService(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryParameterBindingAttribute.cs new file mode 100644 index 0000000..3aa5cfd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataQueryParameterBindingAttribute.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.AspNet.OData +{ + /// + /// A ParameterBindingAttribute to bind parameters of type ODataQueryOptions to the OData query from the incoming request. + /// + public partial class ODataQueryParameterBindingAttribute + { + internal static Type GetEntityClrTypeFromParameterType(Type parameterType) + { + Contract.Assert(parameterType != null); + + if (parameterType.IsGenericType && + parameterType.GetGenericTypeDefinition() == typeof(ODataQueryOptions<>)) + { + return parameterType.GetGenericArguments().Single(); + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerConverter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerConverter.cs new file mode 100644 index 0000000..b345c68 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerConverter.cs @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using Newtonsoft.Json.Linq; + +namespace Microsoft.AspNet.OData +{ + /// + /// QualityBand : Preview + /// Represents an used to converter an Edm model to Swagger model. + /// + public class ODataSwaggerConverter + { + private static readonly Uri DefaultMetadataUri = new Uri("http://localhost"); + private const string DefaultHost = "default"; + private const string DefaultbasePath = "/odata"; + + /// + /// Gets or sets the metadata Uri in the Swagger model. + /// + public Uri MetadataUri { get; set; } + + /// + /// Gets or sets the host in the Swagger model. + /// + public string Host { get; set; } + + /// + /// Gets or sets the base path in the Swagger model. + /// + public string BasePath { get; set; } + + /// + /// Gets or sets the Edm model. + /// + public IEdmModel EdmModel { get; private set; } + + /// + /// Gets the version of Swagger spec. + /// + public virtual Version SwaggerVersion + { + get + { + return new Version(2, 0); + } + } + + /// + /// Gets the document in the Swagger. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:EnableSetterForProperty", Justification = "Enable setter for virtual property")] + protected virtual JObject SwaggerDocument { get; set; } + + /// + /// Gets the paths in the Swagger. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:EnableSetterForProperty", Justification = "Enable setter for virtual property")] + protected virtual JObject SwaggerPaths { get; set; } + + /// + /// Gets the definitions in the Swagger. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:EnableSetterForProperty", Justification = "Enable setter for virtual property")] + protected virtual JObject SwaggerTypeDefinitions { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The Edm model. + public ODataSwaggerConverter(IEdmModel model) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + EdmModel = model; + MetadataUri = DefaultMetadataUri; + Host = DefaultHost; + BasePath = DefaultbasePath; + } + + /// + /// Converts the Edm model to Swagger model. + /// + /// The represents the Swagger model. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Property is not appropriate, method does work")] + public virtual JObject GetSwaggerModel() + { + if (SwaggerDocument != null) + { + return SwaggerDocument; + } + + InitializeStart(); + InitializeDocument(); + InitializeContainer(); + InitializeTypeDefinitions(); + InitializeOperations(); + InitializeEnd(); + + return SwaggerDocument; + } + + /// + /// Start to initialize the Swagger model. + /// + protected virtual void InitializeStart() + { + SwaggerDocument = null; + SwaggerPaths = null; + SwaggerTypeDefinitions = null; + } + + /// + /// Initialize the document of Swagger model. + /// + protected virtual void InitializeDocument() + { + SwaggerDocument = new JObject() + { + { "swagger", SwaggerVersion.ToString() }, + { "info", new JObject() + { + { "title", "OData Service" }, + { "description", "The OData Service at " + MetadataUri }, + { "version", "0.1.0" }, + { "x-odata-version", "4.0" } + } + }, + { "host", Host }, + { "schemes", new JArray("http") }, + { "basePath", BasePath }, + { "consumes", new JArray("application/json") }, + { "produces", new JArray("application/json") }, + }; + } + + /// + /// Initialize the entity container to Swagger model. + /// + protected virtual void InitializeContainer() + { + Contract.Assert(SwaggerDocument != null); + Contract.Assert(EdmModel != null); + + SwaggerPaths = new JObject(); + SwaggerDocument.Add("paths", SwaggerPaths); + + if (EdmModel.EntityContainer == null) + { + return; + } + + foreach (var entitySet in EdmModel.EntityContainer.EntitySets()) + { + SwaggerPaths.Add("/" + entitySet.Name, ODataSwaggerUtilities.CreateSwaggerPathForEntitySet(entitySet)); + + SwaggerPaths.Add(ODataSwaggerUtilities.GetPathForEntity(entitySet), + ODataSwaggerUtilities.CreateSwaggerPathForEntity(entitySet)); + } + + foreach (var operationImport in EdmModel.EntityContainer.OperationImports()) + { + SwaggerPaths.Add(ODataSwaggerUtilities.GetPathForOperationImport(operationImport), + ODataSwaggerUtilities.CreateSwaggerPathForOperationImport(operationImport)); + } + } + + /// + /// Initialize the type definitions to Swagger model. + /// + protected virtual void InitializeTypeDefinitions() + { + Contract.Assert(SwaggerDocument != null); + Contract.Assert(EdmModel != null); + + SwaggerTypeDefinitions = new JObject(); + SwaggerDocument.Add("definitions", SwaggerTypeDefinitions); + + foreach (var type in EdmModel.SchemaElements.OfType()) + { + SwaggerTypeDefinitions.Add(type.FullTypeName(), + ODataSwaggerUtilities.CreateSwaggerTypeDefinitionForStructuredType(type)); + } + } + + /// + /// Initialize the operations to Swagger model. + /// + protected virtual void InitializeOperations() + { + Contract.Assert(SwaggerDocument != null); + Contract.Assert(EdmModel != null); + Contract.Assert(SwaggerPaths != null); + + if (EdmModel.EntityContainer == null) + { + return; + } + + foreach (var operation in EdmModel.SchemaElements.OfType()) + { + // skip unbound operation + if (!operation.IsBound) + { + continue; + } + + var boundParameter = operation.Parameters.First(); + var boundType = boundParameter.Type.Definition; + + // skip operation bound to non entity (or entity collection) + if (boundType.TypeKind == EdmTypeKind.Entity) + { + IEdmEntityType entityType = (IEdmEntityType)boundType; + foreach (var entitySet in + EdmModel.EntityContainer.EntitySets().Where(es => es.EntityType().Equals(entityType))) + { + SwaggerPaths.Add(ODataSwaggerUtilities.GetPathForOperationOfEntity(operation, entitySet), + ODataSwaggerUtilities.CreateSwaggerPathForOperationOfEntity(operation, entitySet)); + } + } + else if (boundType.TypeKind == EdmTypeKind.Collection) + { + IEdmCollectionType collectionType = boundType as IEdmCollectionType; + + if (collectionType != null && collectionType.ElementType.Definition.TypeKind == EdmTypeKind.Entity) + { + IEdmEntityType entityType = (IEdmEntityType)collectionType.ElementType.Definition; + foreach (var entitySet in + EdmModel.EntityContainer.EntitySets().Where(es => es.EntityType().Equals(entityType))) + { + SwaggerPaths.Add(ODataSwaggerUtilities.GetPathForOperationOfEntitySet(operation, entitySet), + ODataSwaggerUtilities.CreateSwaggerPathForOperationOfEntitySet(operation, entitySet)); + } + } + } + } + } + + /// + /// End to initialize the Swagger model. + /// + protected virtual void InitializeEnd() + { + Contract.Assert(SwaggerTypeDefinitions != null); + + SwaggerTypeDefinitions.Add("_Error", new JObject() + { + { + "properties", new JObject() + { + { "error", new JObject() + { + { "$ref", "#/definitions/_InError" } + } + } + } + } + }); + + SwaggerTypeDefinitions.Add("_InError", new JObject() + { + { + "properties", new JObject() + { + { "code", new JObject() + { + { "type", "string" } + } + }, + { "message", new JObject() + { + { "type", "string" } + } + } + } + } + }); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerUtilities.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerUtilities.cs new file mode 100644 index 0000000..07f33e0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataSwaggerUtilities.cs @@ -0,0 +1,648 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.OData.Edm; +using Newtonsoft.Json.Linq; + +namespace Microsoft.AspNet.OData +{ + /// + /// Utility methods used to convert the Swagger model. + /// + internal static class ODataSwaggerUtilities + { + /// + /// Create the Swagger path for the Edm entity set. + /// + /// The Edm navigation source. + /// The represents the related Edm entity set. + public static JObject CreateSwaggerPathForEntitySet(IEdmNavigationSource navigationSource) + { + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + if (entitySet == null) + { + return new JObject(); + } + + return new JObject() + { + { + "get", new JObject() + .Summary("Get EntitySet " + entitySet.Name) + .OperationId(entitySet.Name + "_Get") + .Description("Returns the EntitySet " + entitySet.Name) + .Tags(entitySet.Name) + .Parameters(new JArray() + .Parameter("$expand", "query", "Expand navigation property", "string") + .Parameter("$select", "query", "select structural property", "string") + .Parameter("$orderby", "query", "order by some property", "string") + .Parameter("$top", "query", "top elements", "integer") + .Parameter("$skip", "query", "skip elements", "integer") + .Parameter("$count", "query", "include count in response", "boolean")) + .Responses(new JObject() + .Response("200", "EntitySet " + entitySet.Name, entitySet.EntityType()) + .DefaultErrorResponse()) + }, + { + "post", new JObject() + .Summary("Post a new entity to EntitySet " + entitySet.Name) + .OperationId(entitySet.Name + "_Post") + .Description("Post a new entity to EntitySet " + entitySet.Name) + .Tags(entitySet.Name) + .Parameters(new JArray() + .Parameter(entitySet.EntityType().Name, "body", "The entity to post", + entitySet.EntityType())) + .Responses(new JObject() + .Response("200", "EntitySet " + entitySet.Name, entitySet.EntityType()) + .DefaultErrorResponse()) + } + }; + } + + /// + /// Create the Swagger path for the Edm entity. + /// + /// The Edm navigation source. + /// The represents the related Edm entity. + public static JObject CreateSwaggerPathForEntity(IEdmNavigationSource navigationSource) + { + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + if (entitySet == null) + { + return new JObject(); + } + + var keyParameters = new JArray(); + foreach (var key in entitySet.EntityType().Key()) + { + string format; + string type = GetPrimitiveTypeAndFormat(key.Type.Definition as IEdmPrimitiveType, out format); + keyParameters.Parameter(key.Name, "path", "key: " + key.Name, type, format); + } + + return new JObject() + { + { + "get", new JObject() + .Summary("Get entity from " + entitySet.Name + " by key.") + .OperationId(entitySet.Name + "_GetById") + .Description("Returns the entity with the key from " + entitySet.Name) + .Tags(entitySet.Name) + .Parameters((keyParameters.DeepClone() as JArray) + .Parameter("$select", "query", "description", "string")) + .Responses(new JObject() + .Response("200", "EntitySet " + entitySet.Name, entitySet.EntityType()) + .DefaultErrorResponse()) + }, + { + "patch", new JObject() + .Summary("Update entity in EntitySet " + entitySet.Name) + .OperationId(entitySet.Name + "_PatchById") + .Description("Update entity in EntitySet " + entitySet.Name) + .Tags(entitySet.Name) + .Parameters((keyParameters.DeepClone() as JArray) + .Parameter(entitySet.EntityType().Name, "body", "The entity to patch", + entitySet.EntityType())) + .Responses(new JObject() + .Response("204", "Empty response") + .DefaultErrorResponse()) + }, + { + "delete", new JObject() + .Summary("Delete entity in EntitySet " + entitySet.Name) + .OperationId(entitySet.Name + "_DeleteById") + .Description("Delete entity in EntitySet " + entitySet.Name) + .Tags(entitySet.Name) + .Parameters((keyParameters.DeepClone() as JArray) + .Parameter("If-Match", "header", "If-Match header", "string")) + .Responses(new JObject() + .Response("204", "Empty response") + .DefaultErrorResponse()) + } + }; + } + + /// + /// Create the Swagger path for the Edm operation import. + /// + /// The Edm operation import + /// The represents the related Edm operation import. + public static JObject CreateSwaggerPathForOperationImport(IEdmOperationImport operationImport) + { + if (operationImport == null) + { + return new JObject(); + } + + bool isFunctionImport = operationImport is IEdmFunctionImport; + JArray swaggerParameters = new JArray(); + foreach (var parameter in operationImport.Operation.Parameters) + { + swaggerParameters.Parameter(parameter.Name, isFunctionImport ? "path" : "body", + "parameter: " + parameter.Name, parameter.Type.Definition); + } + + JObject swaggerResponses = new JObject(); + if (operationImport.Operation.ReturnType == null) + { + swaggerResponses.Response("204", "Empty response"); + } + else + { + swaggerResponses.Response("200", "Response from " + operationImport.Name, + operationImport.Operation.ReturnType.Definition); + } + + JObject swaggerOperationImport = new JObject() + .Summary("Call operation import " + operationImport.Name) + .OperationId(operationImport.Name + (isFunctionImport ? "_FunctionImportGet" : "_ActionImportPost")) + .Description("Call operation import " + operationImport.Name) + .Tags(isFunctionImport ? "Function Import" : "Action Import"); + + if (swaggerParameters.Count > 0) + { + swaggerOperationImport.Parameters(swaggerParameters); + } + swaggerOperationImport.Responses(swaggerResponses.DefaultErrorResponse()); + + return new JObject() + { + { isFunctionImport ? "get" : "post", swaggerOperationImport } + }; + } + + /// + /// Create the Swagger path for the Edm operation bound to the Edm entity set. + /// + /// The Edm operation. + /// The Edm navigation source. + /// The represents the related Edm operation bound to the Edm entity set. + public static JObject CreateSwaggerPathForOperationOfEntitySet(IEdmOperation operation, IEdmNavigationSource navigationSource) + { + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + if (operation == null || entitySet == null) + { + return new JObject(); + } + + bool isFunction = operation is IEdmFunction; + JArray swaggerParameters = new JArray(); + foreach (var parameter in operation.Parameters.Skip(1)) + { + swaggerParameters.Parameter(parameter.Name, isFunction ? "path" : "body", + "parameter: " + parameter.Name, parameter.Type.Definition); + } + + JObject swaggerResponses = new JObject(); + if (operation.ReturnType == null) + { + swaggerResponses.Response("204", "Empty response"); + } + else + { + swaggerResponses.Response("200", "Response from " + operation.Name, + operation.ReturnType.Definition); + } + + JObject swaggerOperation = new JObject() + .Summary("Call operation " + operation.Name) + .OperationId(operation.Name + (isFunction ? "_FunctionGet" : "_ActionPost")) + .Description("Call operation " + operation.Name) + .Tags(entitySet.Name, isFunction ? "Function" : "Action"); + + if (swaggerParameters.Count > 0) + { + swaggerOperation.Parameters(swaggerParameters); + } + swaggerOperation.Responses(swaggerResponses.DefaultErrorResponse()); + + return new JObject() + { + { isFunction ? "get" : "post", swaggerOperation } + }; + } + + /// + /// Create the Swagger path for the Edm operation bound to the Edm entity. + /// + /// The Edm operation. + /// The Edm navigation source. + /// The represents the related Edm operation bound to the Edm entity. + public static JObject CreateSwaggerPathForOperationOfEntity(IEdmOperation operation, IEdmNavigationSource navigationSource) + { + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + if (operation == null || entitySet == null) + { + return new JObject(); + } + + bool isFunction = operation is IEdmFunction; + JArray swaggerParameters = new JArray(); + + foreach (var key in entitySet.EntityType().Key()) + { + string format; + string type = GetPrimitiveTypeAndFormat(key.Type.Definition as IEdmPrimitiveType, out format); + swaggerParameters.Parameter(key.Name, "path", "key: " + key.Name, type, format); + } + + foreach (var parameter in operation.Parameters.Skip(1)) + { + swaggerParameters.Parameter(parameter.Name, isFunction ? "path" : "body", + "parameter: " + parameter.Name, parameter.Type.Definition); + } + + JObject swaggerResponses = new JObject(); + if (operation.ReturnType == null) + { + swaggerResponses.Response("204", "Empty response"); + } + else + { + swaggerResponses.Response("200", "Response from " + operation.Name, + operation.ReturnType.Definition); + } + + JObject swaggerOperation = new JObject() + .Summary("Call operation " + operation.Name) + .OperationId(operation.Name + (isFunction ? "_FunctionGetById" : "_ActionPostById")) + .Description("Call operation " + operation.Name) + .Tags(entitySet.Name, isFunction ? "Function" : "Action"); + + if (swaggerParameters.Count > 0) + { + swaggerOperation.Parameters(swaggerParameters); + } + swaggerOperation.Responses(swaggerResponses.DefaultErrorResponse()); + return new JObject() + { + { isFunction ? "get" : "post", swaggerOperation } + }; + } + + /// + /// Get the Uri Swagger path for the Edm entity set. + /// + /// The Edm navigation source. + /// The path represents the related Edm entity set. + public static string GetPathForEntity(IEdmNavigationSource navigationSource) + { + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + if (entitySet == null) + { + return String.Empty; + } + + string singleEntityPath = "/" + entitySet.Name + "("; + foreach (var key in entitySet.EntityType().Key()) + { + if (key.Type.Definition.TypeKind == EdmTypeKind.Primitive && + ((IEdmPrimitiveType)key.Type.Definition).PrimitiveKind == EdmPrimitiveTypeKind.String) + { + singleEntityPath += "'{" + key.Name + "}', "; + } + else + { + singleEntityPath += "{" + key.Name + "}, "; + } + } + singleEntityPath = singleEntityPath.Substring(0, singleEntityPath.Length - 2); + singleEntityPath += ")"; + + return singleEntityPath; + } + + /// + /// Get the Uri Swagger path for Edm operation import. + /// + /// The Edm operation import. + /// The path represents the related Edm operation import. + public static string GetPathForOperationImport(IEdmOperationImport operationImport) + { + if (operationImport == null) + { + return String.Empty; + } + + string swaggerOperationImportPath = "/" + operationImport.Name + "("; + if (operationImport.IsFunctionImport()) + { + foreach (var parameter in operationImport.Operation.Parameters) + { + swaggerOperationImportPath += parameter.Name + "=" + "{" + parameter.Name + "},"; + } + } + if (swaggerOperationImportPath.EndsWith(",", StringComparison.Ordinal)) + { + swaggerOperationImportPath = swaggerOperationImportPath.Substring(0, + swaggerOperationImportPath.Length - 1); + } + swaggerOperationImportPath += ")"; + + return swaggerOperationImportPath; + } + + /// + /// Get the Uri Swagger path for Edm operation bound to entity set. + /// + /// The Edm operation. + /// The Edm navigation source. + /// The path represents the related Edm operation. + public static string GetPathForOperationOfEntitySet(IEdmOperation operation, IEdmNavigationSource navigationSource) + { + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + if (operation == null || entitySet == null) + { + return String.Empty; + } + + string swaggerOperationPath = "/" + entitySet.Name + "/" + operation.FullName() + "("; + if (operation.IsFunction()) + { + foreach (var parameter in operation.Parameters.Skip(1)) + { + if (parameter.Type.Definition.TypeKind == EdmTypeKind.Primitive && + ((IEdmPrimitiveType)parameter.Type.Definition).PrimitiveKind == EdmPrimitiveTypeKind.String) + { + swaggerOperationPath += parameter.Name + "=" + "'{" + parameter.Name + "}',"; + } + else + { + swaggerOperationPath += parameter.Name + "=" + "{" + parameter.Name + "},"; + } + } + } + if (swaggerOperationPath.EndsWith(",", StringComparison.Ordinal)) + { + swaggerOperationPath = swaggerOperationPath.Substring(0, swaggerOperationPath.Length - 1); + } + swaggerOperationPath += ")"; + + return swaggerOperationPath; + } + + /// + /// Get the Uri Swagger path for Edm operation bound to entity. + /// + /// The Edm operation. + /// The Edm navigation source. + /// The path represents the related Edm operation. + public static string GetPathForOperationOfEntity(IEdmOperation operation, IEdmNavigationSource navigationSource) + { + IEdmEntitySet entitySet = navigationSource as IEdmEntitySet; + if (operation == null || entitySet == null) + { + return String.Empty; + } + + string swaggerOperationPath = GetPathForEntity(entitySet) + "/" + operation.FullName() + "("; + if (operation.IsFunction()) + { + foreach (var parameter in operation.Parameters.Skip(1)) + { + if (parameter.Type.Definition.TypeKind == EdmTypeKind.Primitive && + ((IEdmPrimitiveType)parameter.Type.Definition).PrimitiveKind == EdmPrimitiveTypeKind.String) + { + swaggerOperationPath += parameter.Name + "=" + "'{" + parameter.Name + "}',"; + } + else + { + swaggerOperationPath += parameter.Name + "=" + "{" + parameter.Name + "},"; + } + } + } + if (swaggerOperationPath.EndsWith(",", StringComparison.Ordinal)) + { + swaggerOperationPath = swaggerOperationPath.Substring(0, swaggerOperationPath.Length - 1); + } + swaggerOperationPath += ")"; + + return swaggerOperationPath; + } + + /// + /// Create the Swagger definition for the structure Edm type. + /// + /// The structure Edm type. + /// The represents the related structure Edm type. + public static JObject CreateSwaggerTypeDefinitionForStructuredType(IEdmStructuredType edmType) + { + if (edmType == null) + { + return new JObject(); + } + + JObject swaggerProperties = new JObject(); + foreach (var property in edmType.StructuralProperties()) + { + JObject swaggerProperty = new JObject().Description(property.Name); + SetSwaggerType(swaggerProperty, property.Type.Definition); + swaggerProperties.Add(property.Name, swaggerProperty); + } + + return new JObject() + { + { "properties", swaggerProperties } + }; + } + + private static void SetSwaggerType(JObject obj, IEdmType edmType) + { + Contract.Assert(obj != null); + Contract.Assert(edmType != null); + + if (edmType.TypeKind == EdmTypeKind.Complex || edmType.TypeKind == EdmTypeKind.Entity) + { + obj.Add("$ref", "#/definitions/" + edmType.FullTypeName()); + } + else if (edmType.TypeKind == EdmTypeKind.Primitive) + { + string format; + string type = GetPrimitiveTypeAndFormat((IEdmPrimitiveType)edmType, out format); + obj.Add("type", type); + if (format != null) + { + obj.Add("format", format); + } + } + else if (edmType.TypeKind == EdmTypeKind.Enum) + { + obj.Add("type", "string"); + } + else if (edmType.TypeKind == EdmTypeKind.Collection) + { + IEdmType itemEdmType = ((IEdmCollectionType)edmType).ElementType.Definition; + JObject nestedItem = new JObject(); + SetSwaggerType(nestedItem, itemEdmType); + obj.Add("type", "array"); + obj.Add("items", nestedItem); + } + } + + private static string GetPrimitiveTypeAndFormat(IEdmPrimitiveType primitiveType, out string format) + { + Contract.Assert(primitiveType != null); + + format = null; + switch (primitiveType.PrimitiveKind) + { + case EdmPrimitiveTypeKind.String: + return "string"; + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + format = "int32"; + return "integer"; + case EdmPrimitiveTypeKind.Int64: + format = "int64"; + return "integer"; + case EdmPrimitiveTypeKind.Boolean: + return "boolean"; + case EdmPrimitiveTypeKind.Byte: + format = "byte"; + return "string"; + case EdmPrimitiveTypeKind.Date: + format = "date"; + return "string"; + case EdmPrimitiveTypeKind.DateTimeOffset: + format = "date-time"; + return "string"; + case EdmPrimitiveTypeKind.Double: + format = "double"; + return "number"; + case EdmPrimitiveTypeKind.Single: + format = "float"; + return "number"; + default: + return "string"; + } + } + + private static JObject Responses(this JObject obj, JObject responses) + { + obj.Add("responses", responses); + return obj; + } + + private static JObject ResponseRef(this JObject responses, string name, string description, string refType) + { + responses.Add(name, new JObject() + { + { "description", description }, + { + "schema", new JObject() + { + { "$ref", refType } + } + } + }); + + return responses; + } + + private static JObject Response(this JObject responses, string name, string description, IEdmType type) + { + var schema = new JObject(); + SetSwaggerType(schema, type); + + responses.Add(name, new JObject() + { + { "description", description }, + { "schema", schema } + }); + + return responses; + } + + private static JObject DefaultErrorResponse(this JObject responses) + { + return responses.ResponseRef("default", "Unexpected error", "#/definitions/_Error"); + } + + private static JObject Response(this JObject responses, string name, string description) + { + responses.Add(name, new JObject() + { + { "description", description }, + }); + + return responses; + } + + private static JObject Parameters(this JObject obj, JArray parameters) + { + obj.Add("parameters", parameters); + return obj; + } + + private static JArray Parameter(this JArray parameters, string name, string kind, string description, string type, string format = null) + { + var newParameter = new JObject() + { + { "name", name }, + { "in", kind }, + { "description", description }, + { "type", type }, + }; + + if (!String.IsNullOrEmpty(format)) + { + newParameter.Add("format", format); + } + + parameters.Add(newParameter); + + return parameters; + } + + private static JArray Parameter(this JArray parameters, string name, string kind, string description, IEdmType type) + { + var parameter = new JObject() + { + { "name", name }, + { "in", kind }, + { "description", description }, + }; + + if (kind != "body") + { + SetSwaggerType(parameter, type); + } + else + { + var schema = new JObject(); + SetSwaggerType(schema, type); + parameter.Add("schema", schema); + } + + parameters.Add(parameter); + return parameters; + } + + private static JObject Tags(this JObject obj, params string[] tags) + { + obj.Add("tags", new JArray(tags)); + return obj; + } + + private static JObject Summary(this JObject obj, string summary) + { + obj.Add("summary", summary); + return obj; + } + + private static JObject Description(this JObject obj, string description) + { + obj.Add("description", description); + return obj; + } + + private static JObject OperationId(this JObject obj, string operationId) + { + obj.Add("operationId", operationId); + return obj; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUntypedActionParameters.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUntypedActionParameters.cs new file mode 100644 index 0000000..f64f157 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUntypedActionParameters.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// ActionPayload holds the Parameter names and values provided by a client in a POST request + /// to invoke a particular Action. The Parameter values are stored in the dictionary keyed using the Parameter name. + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "ODataUntypedActionParameters is more appropriate here.")] + [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "ODataUntypedActionParameters is not serializable.")] + [NonValidatingParameterBinding] + public class ODataUntypedActionParameters : Dictionary + { + /// + /// Initializes a new instance of the class. + /// + /// The OData action of this parameters. + public ODataUntypedActionParameters(IEdmAction action) + { + if (action == null) + { + throw Error.ArgumentNull("action"); + } + + Action = action; + } + + /// + /// Gets the OData action of this parameters. + /// + public IEdmAction Action { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUriFunctions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUriFunctions.cs new file mode 100644 index 0000000..a7f599f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ODataUriFunctions.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData +{ + /// + /// OData UriFunctions helper. + /// + public static class ODataUriFunctions + { + /// + /// This is a shortcut of adding the custom FunctionSignature through 'CustomUriFunctions' class and + /// binding the function name to it's MethodInfo through 'UriFunctionsBinder' class. + /// See these classes documentations. + /// In case of an exception, both operations(adding the signature and binding the function) will be undone. + /// + /// The uri function name that appears in the OData request uri. + /// The new custom function signature. + /// The MethodInfo to bind the given function name. + /// Any exception thrown by 'CustomUriFunctions.AddCustomUriFunction' and 'UriFunctionBinder.BindUriFunctionName' methods. + public static void AddCustomUriFunction(string functionName, + FunctionSignatureWithReturnType functionSignature, MethodInfo methodInfo) + { + try + { + // Add to OData.Libs function signature + CustomUriFunctions.AddCustomUriFunction(functionName, functionSignature); + + // Bind the method to it's MethoInfo + UriFunctionsBinder.BindUriFunctionName(functionName, methodInfo); + } + catch + { + // Clear in case of excpetion + RemoveCustomUriFunction(functionName, functionSignature, methodInfo); + throw; + } + } + + /// + /// This is a shortcut of removing the FunctionSignature through 'CustomUriFunctions' class and + /// unbinding the function name from it's MethodInfo through 'UriFunctionsBinder' class. + /// See these classes documentations. + /// + /// The uri function name that appears in the OData request uri. + /// The new custom function signature. + /// The MethodInfo to bind the given function name. + /// Any exception thrown by 'CustomUriFunctions.RemoveCustomUriFunction' and 'UriFunctionsBinder.UnbindUriFunctionName' methods. + /// 'True' if the fucntion signature has successfully removed and unbinded. 'False' otherwise. + public static bool RemoveCustomUriFunction(string functionName, + FunctionSignatureWithReturnType functionSignature, MethodInfo methodInfo) + { + return + CustomUriFunctions.RemoveCustomUriFunction(functionName, functionSignature) && + UriFunctionsBinder.UnbindUriFunctionName(functionName, methodInfo); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResult.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResult.cs new file mode 100644 index 0000000..409d5e1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResult.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a feed of entities that includes additional information that OData formats support. + /// + /// + /// Currently limited to: + /// + /// The Count of all matching entities on the server (requested using $count=true). + /// The NextLink to retrieve the next page of results (added if the server enforces Server Driven Paging). + /// + /// + [DataContract] + public abstract class PageResult + { + private long? _count; + + /// + /// Initializes a new instance of the class. + /// + /// The link for the next page of items in the feed. + /// The total count of items in the feed. + protected PageResult(Uri nextPageLink, long? count) + { + NextPageLink = nextPageLink; + Count = count; + } + + /// + /// Gets the link for the next page of items in the feed. + /// + [DataMember] + public Uri NextPageLink + { + get; + private set; + } + + /// + /// Gets the total count of items in the feed. + /// + [DataMember] + public long? Count + { + get + { + return _count; + } + private set + { + if (value.HasValue && value.Value < 0) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value.Value, 0); + } + _count = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResultOfT.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResultOfT.cs new file mode 100644 index 0000000..5f1e87d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PageResultOfT.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Common; +using Newtonsoft.Json; + +namespace Microsoft.AspNet.OData +{ + /// Represents a feed of entities that includes additional information that OData formats support. + /// + /// Currently limited to: + /// + /// The Count of all matching entities on the server (requested using $count=true). + /// The NextLink to retrieve the next page of results (added if the server enforces Server Driven Paging). + /// + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Collection suffix not appropriate")] + [DataContract] + [JsonObject] + public class PageResult : PageResult, IEnumerable + { + /// + /// Creates a partial set of results - used when server driven paging is enabled. + /// + /// The subset of matching results that should be serialized in this page. + /// A link to the next page of matching results (if more exists). + /// A total count of matching results so clients can know the number of matches on the server. + public PageResult(IEnumerable items, Uri nextPageLink, long? count) + : base(nextPageLink, count) + { + if (items == null) + { + throw Error.ArgumentNull("data"); + } + + Items = items; + } + + /// + /// Gets the collection of entities for this feed. + /// + [DataMember] + public IEnumerable Items { get; private set; } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An object that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return Items.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return Items.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PerRouteContainerBase.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PerRouteContainerBase.cs new file mode 100644 index 0000000..0c4defb --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PerRouteContainerBase.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData +{ + /// + /// A base class for for managing per-route service containers. + /// + public abstract class PerRouteContainerBase : IPerRouteContainer + { + /// + /// Gets or sets a function to build an + /// + public Func BuilderFactory { get; set; } + + /// + /// Create a root container for a given route name. + /// + /// The route name. + /// The configuration actions to apply to the container. + /// An instance of to manage services for a route. + public IServiceProvider CreateODataRootContainer(string routeName, Action configureAction) + { + IServiceProvider rootContainer = this.CreateODataRootContainer(configureAction); + this.SetContainer(routeName, rootContainer); + + return rootContainer; + } + + /// + /// Create a root container not associated with a route. + /// + /// The configuration actions to apply to the container. + /// An instance of to manage services for a route. + public IServiceProvider CreateODataRootContainer(Action configureAction) + { + IContainerBuilder builder = CreateContainerBuilderWithCoreServices(); + + if (configureAction != null) + { + configureAction(builder); + } + + IServiceProvider rootContainer = builder.BuildContainer(); + if (rootContainer == null) + { + throw Error.InvalidOperation(SRResources.NullContainer); + } + + return rootContainer; + } + + /// + /// Check if the root container for a given route name exists. + /// + /// The route name. + /// true if root container for the route name exists, false otherwise. + public bool HasODataRootContainer(string routeName) + { + IServiceProvider rootContainer = this.GetContainer(routeName); + return rootContainer != null; + } + + /// + /// Get the root container for a given route name. + /// + /// The route name. + /// The root container for the route name. + /// + /// This function will throw an exception if no container is found + /// in order to localize the failure and provide a consistent error + /// message. Use to test of a container + /// exists without throwing an exception. + /// + public IServiceProvider GetODataRootContainer(string routeName) + { + IServiceProvider rootContainer = this.GetContainer(routeName); + if (rootContainer == null) + { + if (String.IsNullOrEmpty(routeName)) + { + throw Error.InvalidOperation(SRResources.MissingNonODataContainer); + } + else + { + throw Error.InvalidOperation(SRResources.MissingODataContainer, routeName); + } + } + + return rootContainer; + } + + /// + /// Set the root container for a given route name. + /// + /// The route name. + /// The root container to set. + /// Used by unit tests to insert root containers. + internal void SetODataRootContainer(string routeName, IServiceProvider rootContainer) + { + this.SetContainer(routeName, rootContainer); + } + + /// + /// Get the root container for a given route name. + /// + /// The route name. + protected abstract IServiceProvider GetContainer(string routeName); + + /// + /// Set the root container for a given route name. + /// + /// The route name. + /// The root container to set. + protected abstract void SetContainer(string routeName, IServiceProvider rootContainer); + + /// + /// Create a container builder with the default OData services. + /// + /// An instance of to manage services. + protected IContainerBuilder CreateContainerBuilderWithCoreServices() + { + IContainerBuilder builder; + if (this.BuilderFactory != null) + { + builder = this.BuilderFactory(); + if (builder == null) + { + throw Error.InvalidOperation(SRResources.NullContainerBuilder); + } + } + else + { + builder = new DefaultContainerBuilder(); + } + + builder.AddDefaultODataServices(); + + // Set Uri resolver to by default enabling unqualified functions/actions and case insensitive match. + builder.AddService( + ServiceLifetime.Singleton, + typeof(ODataUriResolver), + sp => new UnqualifiedODataUriResolver { EnableCaseInsensitive = true }); + + return builder; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PropertyAccessor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PropertyAccessor.cs new file mode 100644 index 0000000..0d73a58 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/PropertyAccessor.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a strategy for Getting and Setting a PropertyInfo on + /// + /// The type that contains the PropertyInfo + internal abstract class PropertyAccessor where TStructuralType : class + { + protected PropertyAccessor(PropertyInfo property) + { + if (property == null) + { + throw Error.ArgumentNull("property"); + } + Property = property; + if (Property.GetGetMethod() == null || + (!TypeHelper.IsCollection(property.PropertyType) && Property.GetSetMethod() == null)) + { + throw Error.Argument("property", SRResources.PropertyMustHavePublicGetterAndSetter); + } + } + + public PropertyInfo Property + { + get; + private set; + } + + public void Copy(TStructuralType from, TStructuralType to) + { + if (from == null) + { + throw Error.ArgumentNull("from"); + } + if (to == null) + { + throw Error.ArgumentNull("to"); + } + SetValue(to, GetValue(from)); + } + + public abstract object GetValue(TStructuralType instance); + + public abstract void SetValue(TStructuralType instance, object value); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedArithmeticOperators.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedArithmeticOperators.cs new file mode 100644 index 0000000..c0aecc2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedArithmeticOperators.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Arithmetic operators to allow for querying using $filter. + /// + [Flags] + public enum AllowedArithmeticOperators + { + /// + /// A value that corresponds to allowing no arithmetic operators in $filter. + /// + None = 0x0, + + /// + /// A value that corresponds to allowing 'Add' arithmetic operator in $filter. + /// + Add = 0x1, + + /// + /// A value that corresponds to allowing 'Subtract' arithmetic operator in $filter. + /// + Subtract = 0x2, + + /// + /// A value that corresponds to allowing 'Multiply' arithmetic operator in $filter. + /// + Multiply = 0x4, + + /// + /// A value that corresponds to allowing 'Divide' arithmetic operator in $filter. + /// + Divide = 0x8, + + /// + /// A value that corresponds to allowing 'Modulo' arithmetic operator in $filter. + /// + Modulo = 0x10, + + /// + /// A value that corresponds to allowing all arithmetic operators in $filter. + /// + All = Add | Subtract | Multiply | Divide | Modulo + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedFunctions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedFunctions.cs new file mode 100644 index 0000000..1e17858 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedFunctions.cs @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Functions to allow for querying using $filter. + /// + [Flags] + public enum AllowedFunctions + { + /// + /// A value that corresponds to allowing no functions in $filter. + /// + None = 0x0, + + /// + /// A value that corresponds to allowing 'StartsWith' function in $filter. + /// + StartsWith = 0x1, + + /// + /// A value that corresponds to allowing 'EndsWith' function in $filter. + /// + EndsWith = 0x2, + + /// + /// A value that corresponds to allowing 'Contains' function in $filter. + /// + Contains = 0x4, + + /// + /// A value that corresponds to allowing 'Length' function in $filter. + /// + Length = 0x8, + + /// + /// A value that corresponds to allowing 'IndexOf' function in $filter. + /// + IndexOf = 0x10, + + /// + /// A value that corresponds to allowing 'Concat' function in $filter. + /// + Concat = 0x20, + + /// + /// A value that corresponds to allowing 'Substring' function in $filter. + /// + Substring = 0x40, + + /// + /// A value that corresponds to allowing 'ToLower' function in $filter. + /// + ToLower = 0x80, + + /// + /// A value that corresponds to allowing 'ToUpper' function in $filter. + /// + ToUpper = 0x100, + + /// + /// A value that corresponds to allowing 'Trim' function in $filter. + /// + Trim = 0x200, + + /// + /// A value that corresponds to allowing 'Cast' function in $filter. + /// + Cast = 0x400, + + /// + /// A value that corresponds to allowing 'Year' function in $filter. + /// + Year = 0x800, + + /// + /// A value that corresponds to allowing 'Date' function in $filter. + /// + Date = 0x1000, + + /// + /// A value that corresponds to allowing 'Month' function in $filter. + /// + Month = 0x2000, + + /// + /// A value that corresponds to allowing 'Time' function in $filter. + /// + Time = 0x4000, + + /// + /// A value that corresponds to allowing 'Day' function in $filter. + /// + Day = 0x8000, + + /// + /// A value that corresponds to allowing 'Hour' function in $filter. + /// + Hour = 0x20000, + + /// + /// A value that corresponds to allowing 'Minute' function in $filter. + /// + Minute = 0x80000, + + /// + /// A value that corresponds to allowing 'Second' function in $filter. + /// + Second = 0x200000, + + /// + /// A value that corresponds to allowing 'Fractionalseconds' function in $filter. + /// + FractionalSeconds = 0x400000, + + /// + /// A value that corresponds to allowing 'Round' function in $filter. + /// + Round = 0x800000, + + /// + /// A value that corresponds to allowing 'Floor' function in $filter. + /// + Floor = 0x1000000, + + /// + /// A value that corresponds to allowing 'Ceiling' function in $filter. + /// + Ceiling = 0x2000000, + + /// + /// A value that corresponds to allowing 'IsOf' function in $filter. + /// + IsOf = 0x4000000, + + /// + /// A value that corresponds to allowing 'Any' function in $filter. + /// + Any = 0x8000000, + + /// + /// A value that corresponds to allowing 'All' function in $filter. + /// + All = 0x10000000, + + /// + /// A value that corresponds to allowing all string related functions in $filter. + /// + AllStringFunctions = StartsWith | EndsWith | Contains | Length | IndexOf | Concat | Substring | ToLower | ToUpper | Trim, + + /// + /// A value that corresponds to allowing all datetime related functions in $filter. + /// + AllDateTimeFunctions = Year | Month | Day | Hour | Minute | Second | FractionalSeconds | Date | Time, + + /// + /// A value that corresponds to allowing math related functions in $filter. + /// + AllMathFunctions = Round | Floor | Ceiling, + + /// + /// A value that corresponds to allowing all functions in $filter. + /// + AllFunctions = AllStringFunctions | AllDateTimeFunctions | AllMathFunctions | Cast | IsOf | Any | All + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedLogicalOperators.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedLogicalOperators.cs new file mode 100644 index 0000000..44c19fb --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedLogicalOperators.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Logical operators to allow for querying using $filter. + /// + [Flags] + public enum AllowedLogicalOperators + { + /// + /// A value that corresponds to allowing no logical operators in $filter. + /// + None = 0x0, + + /// + /// A value that corresponds to allowing 'Or' logical operator in $filter. + /// + Or = 0x1, + + /// + /// A value that corresponds to allowing 'And' logical operator in $filter. + /// + And = 0x2, + + /// + /// A value that corresponds to allowing 'Equal' logical operator in $filter. + /// + Equal = 0x4, + + /// + /// A value that corresponds to allowing 'NotEqual' logical operator in $filter. + /// + NotEqual = 0x8, + + /// + /// A value that corresponds to allowing 'GreaterThan' logical operator in $filter. + /// + GreaterThan = 0x10, + + /// + /// A value that corresponds to allowing 'GreaterThanOrEqual' logical operator in $filter. + /// + GreaterThanOrEqual = 0x20, + + /// + /// A value that corresponds to allowing 'LessThan' logical operator in $filter. + /// + LessThan = 0x40, + + /// + /// A value that corresponds to allowing 'LessThanOrEqual' logical operator in $filter. + /// + LessThanOrEqual = 0x80, + + /// + /// A value that corresponds to allowing 'Not' logical operator in $filter. + /// + Not = 0x100, + + /// + /// A value that corresponds to allowing 'Has' logical operator in $filter. + /// + Has = 0x200, + + /// + /// A value that corresponds to allowing all logical operators in $filter. + /// + All = Or | And | Equal | NotEqual | GreaterThan | GreaterThanOrEqual | LessThan | LessThanOrEqual | Not | Has + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedQueryOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedQueryOptions.cs new file mode 100644 index 0000000..d17e22b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/AllowedQueryOptions.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// OData query options to allow for querying. + /// + [Flags] + public enum AllowedQueryOptions + { + /// + /// A value that corresponds to allowing no query options. + /// + None = 0x0, + + /// + /// A value that corresponds to allowing the $filter query option. + /// + Filter = 0x1, + + /// + /// A value that corresponds to allowing the $expand query option. + /// + Expand = 0x2, + + /// + /// A value that corresponds to allowing the $select query option. + /// + Select = 0x4, + + /// + /// A value that corresponds to allowing the $orderby query option. + /// + OrderBy = 0x8, + + /// + /// A value that corresponds to allowing the $top query option. + /// + Top = 0x10, + + /// + /// A value that corresponds to allowing the $skip query option. + /// + Skip = 0x20, + + /// + /// A value that corresponds to allowing the $count query option. + /// + Count = 0x40, + + /// + /// A value that corresponds to allowing the $format query option. + /// + Format = 0x80, + + /// + /// A value that corresponds to allowing the $skiptoken query option. + /// + SkipToken = 0x100, + + /// + /// A value that corresponds to allowing the $deltatoken query option. + /// + DeltaToken = 0x200, + + /// + /// A value that corresponds to allowing the $apply query option. + /// + Apply = 0x400, + + /// + /// A value that corresponds to the default query options supported. + /// + Supported = Filter | OrderBy | Top | Skip | SkipToken | Count | Select | Expand | Format | Apply, + + /// + /// A value that corresponds to allowing all query options. + /// + All = Filter | Expand | Select | OrderBy | Top | Skip | Count | Format | SkipToken | DeltaToken | Apply + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs new file mode 100644 index 0000000..3e7a9dc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a $apply OData query option for querying. + /// + public class ApplyQueryOption + { + private ApplyClause _applyClause; + private ODataQueryOptionParser _queryOptionParser; + + /// + /// Initialize a new instance of based on the raw $apply value and + /// an EdmModel from . + /// + /// The raw value for $filter query. It can be null or empty. + /// The which contains the and some type information + /// The which is used to parse the query option. + public ApplyQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + Context = context; + RawValue = rawValue; + // TODO: Implement and add validator + //Validator = new FilterQueryValidator(); + _queryOptionParser = queryOptionParser; + ResultClrType = Context.ElementClrType; + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// ClrType for result of transformations + /// + public Type ResultClrType { get; private set; } + + /// + /// Gets the parsed for this query option. + /// + public ApplyClause ApplyClause + { + get + { + if (_applyClause == null) + { + _applyClause = _queryOptionParser.ParseApply(); + } + + return _applyClause; + } + } + + /// + /// Gets the raw $apply value. + /// + public string RawValue { get; private set; } + + /// + /// Apply the apply query to the given IQueryable. + /// + /// + /// The property specifies + /// how this method should handle null propagation. + /// + /// The original . + /// The that contains all the query application related settings. + /// The new after the filter query has been applied to. + public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + if (query == null) + { + throw Error.ArgumentNull("query"); + } + + if (querySettings == null) + { + throw Error.ArgumentNull("querySettings"); + } + + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + // Linq to SQL not supported for $apply + if (query.Provider.GetType().Namespace == HandleNullPropagationOptionHelper.Linq2SqlQueryProviderNamespace) + { + throw Error.NotSupported(SRResources.ApplyQueryOptionNotSupportedForLinq2SQL); + } + + ApplyClause applyClause = ApplyClause; + Contract.Assert(applyClause != null); + + ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query); + + // The IWebApiAssembliesResolver service is internal and can only be injected by WebApi. + // This code path may be used in cases when the service container is not available + // and the service container is available but may not contain an instance of IWebApiAssembliesResolver. + IWebApiAssembliesResolver assembliesResolver = WebApiAssembliesResolver.Default; + if (Context.RequestContainer != null) + { + IWebApiAssembliesResolver injectedResolver = Context.RequestContainer.GetService(); + if (injectedResolver != null) + { + assembliesResolver = injectedResolver; + } + } + + foreach (var transformation in applyClause.Transformations) + { + if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy) + { + var binder = new AggregationBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, transformation); + query = binder.Bind(query); + this.ResultClrType = binder.ResultClrType; + } + else if (transformation.Kind == TransformationNodeKind.Filter) + { + var filterTransformation = transformation as FilterTransformationNode; + Expression filter = FilterBinder.Bind(query, filterTransformation.FilterClause, ResultClrType, Context, querySettings); + query = ExpressionHelpers.Where(query, filter, ResultClrType); + } + } + + return query; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountAttribute.cs new file mode 100644 index 0000000..8330280 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a class or property + /// correlate to OData's $count query option settings. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] + public sealed class CountAttribute : Attribute + { + /// + /// Represents whether the $count can be applied on the property or the entityset. + /// + public bool Disabled { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountQueryOption.cs new file mode 100644 index 0000000..a2ce501 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/CountQueryOption.cs @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents the value of the $count query option and exposes a way to retrieve the number of entities that satisfy a query. + /// + public class CountQueryOption + { + private bool? _value; + private ODataQueryOptionParser _queryOptionParser; + + /// + /// Initializes a new instance of the class. + /// + /// The raw value for the $count query option. + /// The which contains the query context. + /// The which is used to parse the query option. + public CountQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + Context = context; + RawValue = rawValue; + Validator = CountQueryValidator.GetCountQueryValidator(context); + _queryOptionParser = queryOptionParser; + } + + // This constructor is intended for unit testing only. + internal CountQueryOption(string rawValue, ODataQueryContext context) + { + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + Context = context; + RawValue = rawValue; + Validator = CountQueryValidator.GetCountQueryValidator(context); + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$count", rawValue } }, + context.RequestContainer); + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets the raw $count value. + /// + public string RawValue { get; private set; } + + /// + /// Gets the value of the $count in a parsed form. + /// + public bool Value + { + get + { + if (_value == null) + { + _value = _queryOptionParser.ParseCount(); + } + + Contract.Assert(_value.HasValue); + return _value.Value; + } + } + + /// + /// Gets or sets the $count query validator. + /// + public CountQueryValidator Validator { get; set; } + + /// + /// Validate the count query based on the given . + /// It throws an ODataException if validation failed. + /// + /// The instance + /// which contains all the validation settings. + public void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + + /// + /// Gets the number of entities that satisfy the given query if the response should include a count query option, or null otherwise. + /// + /// The query to compute the count for. + /// The number of entities that satisfy the specified query if the response should include a count query option, or null otherwise. + public long? GetEntityCount(IQueryable query) + { + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "GetEntityCount"); + } + + if (Value) + { + return ExpressionHelpers.Count(query, Context.ElementClrType)(); + } + else + { + return null; + } + } + + /// + /// Gets the Func of entities number that satisfy the given query if the response should include a count query option, or null otherwise. + /// + /// The query to compute the count for. + /// The the Func of entities number that satisfy the specified query if the response should include a count query option, or null otherwise. + internal Func GetEntityCountFunc(IQueryable query) + { + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "GetEntityCount"); + } + + if (Value) + { + return ExpressionHelpers.Count(query, Context.ElementClrType); + } + else + { + return null; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultQuerySettings.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultQuerySettings.cs new file mode 100644 index 0000000..1912b3a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultQuerySettings.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This class describes the default settings to use during query composition. + /// + public class DefaultQuerySettings + { + private int? _maxTop = 0; + + /// + /// Gets or sets a value indicating whether navigation property can be expanded. + /// + public bool EnableExpand { get; set; } + + /// + /// Gets or sets a value indicating whether property can be selected. + /// + public bool EnableSelect { get; set; } + + /// + /// Gets or sets a value indicating whether entity set and property can apply $count. + /// + public bool EnableCount { get; set; } + + /// + /// Gets or sets a value indicating whether property can apply $orderby. + /// + public bool EnableOrderBy { get; set; } + + /// + /// Gets or sets a value indicating whether property can apply $filter. + /// + public bool EnableFilter { get; set; } + + /// + /// Gets or sets the max value of $top that a client can request. + /// + /// + /// The max value of $top that a client can request, or null if there is no limit. + /// + public int? MaxTop + { + get + { + return _maxTop; + } + set + { + if (value.HasValue && value < 0) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 0); + } + + _maxTop = value; + } + } + + /// + /// Gets or sets a value indicating whether the service will use skiptoken or not. + /// + public bool EnableSkipToken { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultSkipTokenHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultSkipTokenHandler.cs new file mode 100644 index 0000000..d741992 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/DefaultSkipTokenHandler.cs @@ -0,0 +1,424 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Default implementation of SkipTokenHandler for the service. + /// + public class DefaultSkipTokenHandler : SkipTokenHandler + { + private const char CommaDelimiter = ','; + private static char propertyDelimiter = '-'; + internal static DefaultSkipTokenHandler Instance = new DefaultSkipTokenHandler(); + + /// + /// Returns the URI for NextPageLink + /// + /// BaseUri for nextlink. It should be request URI for top level resource and navigation link for nested resource. + /// Maximum number of records in the set of partial results for a resource. + /// Instance based on which SkipToken value will be generated. + /// Serializer context + /// Returns the URI for NextPageLink. If a null object is passed for the instance, resorts to the default paging mechanism of using $skip and $top. + public override Uri GenerateNextPageLink(Uri baseUri, int pageSize, Object instance, ODataSerializerContext context) + { + if (context == null) + { + return null; + } + + if (pageSize <= 0) + { + return null; + } + + Func skipTokenGenerator = null; + IList orderByNodes = null; + ExpandedReferenceSelectItem expandedItem = context.CurrentExpandedSelectItem; + IEdmModel model = context.Model; + + DefaultQuerySettings settings = context.QueryContext.DefaultQuerySettings; + if (settings.EnableSkipToken) + { + if (expandedItem != null) + { + // Handle Delta resource; currently not value based. + if (TypedDelta.IsDeltaOfT(context.ExpandedResource.GetType())) + { + return GetNextPageHelper.GetNextPageLink(baseUri, pageSize); + } + + if (expandedItem.OrderByOption != null) + { + orderByNodes = OrderByNode.CreateCollection(expandedItem.OrderByOption); + } + + skipTokenGenerator = (obj) => + { + return GenerateSkipTokenValue(obj, model, orderByNodes); + }; + + return GetNextPageHelper.GetNextPageLink(baseUri, pageSize, instance, skipTokenGenerator); + } + + if (context.QueryOptions != null && context.QueryOptions.OrderBy != null) + { + orderByNodes = context.QueryOptions.OrderBy.OrderByNodes; + } + + skipTokenGenerator = (obj) => + { + return GenerateSkipTokenValue(obj, model, orderByNodes); + }; + } + + return context.InternalRequest.GetNextPageLink(pageSize, instance, skipTokenGenerator); + } + + /// + /// Generates a string to be used as the skip token value within the next link. + /// + /// Object based on which SkipToken value will be generated. + /// The edm model. + /// List of orderByNodes used to generate the skiptoken value. + /// Value for the skiptoken to be used in the next link. + private static string GenerateSkipTokenValue(Object lastMember, IEdmModel model, IList orderByNodes) + { + if (lastMember == null) + { + return String.Empty; + } + + IEnumerable propertiesForSkipToken = GetPropertiesForSkipToken(lastMember, model, orderByNodes); + StringBuilder skipTokenBuilder = new StringBuilder(String.Empty); + if (propertiesForSkipToken == null) + { + return skipTokenBuilder.ToString(); + } + + int count = 0; + string uriLiteral; + object value; + int lastIndex = propertiesForSkipToken.Count() - 1; + IEdmStructuredObject obj = lastMember as IEdmStructuredObject; + + foreach (IEdmProperty property in propertiesForSkipToken) + { + bool islast = count == lastIndex; + string propertyName = EdmLibHelpers.GetClrPropertyName(property, model); + if (obj != null) + { + obj.TryGetPropertyValue(propertyName, out value); + } + else + { + value = lastMember.GetType().GetProperty(propertyName).GetValue(lastMember); + } + + if (value == null) + { + uriLiteral = ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V401); + } + else if (property.Type.IsEnum()) + { + ODataEnumValue enumValue = new ODataEnumValue(value.ToString(), value.GetType().FullName); + uriLiteral = ODataUriUtils.ConvertToUriLiteral(enumValue, ODataVersion.V401, model); + } + else + { + uriLiteral = ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V401, model); + } + + skipTokenBuilder.Append(propertyName).Append(propertyDelimiter).Append(uriLiteral).Append(islast ? String.Empty : CommaDelimiter.ToString()); + count++; + } + + return skipTokenBuilder.ToString(); + } + + /// + /// Apply the $skiptoken query to the given IQueryable. + /// + /// The original . + /// The skiptoken query option which needs to be applied to this query option. + /// The new after the skiptoken query has been applied to. + public override IQueryable ApplyTo(IQueryable query, SkipTokenQueryOption skipTokenQueryOption) + { + return ApplyTo(query, skipTokenQueryOption); + } + + /// + /// Apply the $skiptoken query to the given IQueryable. + /// + /// The original . + /// The skiptoken query option which needs to be applied to this query option. + /// The new after the skiptoken query has been applied to. + public override IQueryable ApplyTo(IQueryable query, SkipTokenQueryOption skipTokenQueryOption) + { + if (skipTokenQueryOption == null) + { + throw Error.ArgumentNullOrEmpty("skipTokenQueryOption"); + } + + ODataQuerySettings querySettings = skipTokenQueryOption.QuerySettings; + ODataQueryOptions queryOptions = skipTokenQueryOption.QueryOptions; + IList orderByNodes = null; + + if (queryOptions != null) + { + OrderByQueryOption orderBy = queryOptions.GenerateStableOrder(); + if (orderBy != null) + { + orderByNodes = orderBy.OrderByNodes; + } + } + + return ApplyToCore(query, querySettings, orderByNodes, skipTokenQueryOption.Context, skipTokenQueryOption.RawValue); + } + + /// + /// Core logic for applying the query option to the IQueryable. + /// + /// The original . + /// Query setting used for validating the query option. + /// OrderBy information required to correctly apply the query option for default implementation. + /// The which contains the and some type information + /// The raw string value of the skiptoken query parameter. + /// + private static IQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings, IList orderByNodes, ODataQueryContext context, string skipTokenRawValue) + { + if (context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + IDictionary directionMap; + if (orderByNodes != null) + { + directionMap = + orderByNodes.OfType().ToDictionary(node => node.Property.Name, node => node.Direction); + } + else + { + directionMap = new Dictionary(); + } + + IDictionary propertyValuePairs = PopulatePropertyValuePairs(skipTokenRawValue, context); + + if (propertyValuePairs.Count == 0) + { + throw Error.InvalidOperation("Unable to get property values from the skiptoken value."); + } + + ExpressionBinderBase binder = new FilterBinder(context.RequestContainer); + bool parameterizeConstant = querySettings.EnableConstantParameterization; + ParameterExpression param = Expression.Parameter(context.ElementClrType); + Expression where = null; + /* We will create a where lambda of the following form - + * Where (Prop1>Value1) + * OR (Prop1=Value1 AND Prop2>Value2) + * OR (Prop1=Value1 AND Prop2=Value2 AND Prop3>Value3) + * and so on... + * Adding the first true to simplify implementation. + */ + Expression lastEquality = null; + bool firstProperty = true; + + foreach (KeyValuePair item in propertyValuePairs) + { + string key = item.Key; + MemberExpression property = Expression.Property(param, key); + object value = item.Value; + + Expression compare = null; + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + value = enumValue.Value; + } + + Expression constant = parameterizeConstant ? LinqParameterContainer.Parameterize(value.GetType(), value) : Expression.Constant(value); + if (directionMap.ContainsKey(key) && directionMap[key] == OrderByDirection.Descending) + { + compare = binder.CreateBinaryExpression(BinaryOperatorKind.LessThan, property, constant, true); + } + else + { + compare = binder.CreateBinaryExpression(BinaryOperatorKind.GreaterThan, property, constant, true); + } + + if (firstProperty) + { + lastEquality = binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true); + where = compare; + firstProperty = false; + } + else + { + Expression condition = Expression.AndAlso(lastEquality, compare); + where = Expression.OrElse(where, condition); + lastEquality = Expression.AndAlso(lastEquality, binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true)); + } + } + + Expression whereLambda = Expression.Lambda(where, param); + return ExpressionHelpers.Where(query, whereLambda, query.ElementType); + } + + /// + /// Generates a dictionary with property name and property values specified in the skiptoken value. + /// + /// The skiptoken string value. + /// The which contains the and some type information + /// Dictionary with property name and property value in the skiptoken value. + private static IDictionary PopulatePropertyValuePairs(string value, ODataQueryContext context) + { + Contract.Assert(context != null); + + IDictionary propertyValuePairs = new Dictionary(); + IList keyValuesPairs = ParseValue(value, CommaDelimiter); + + IEdmStructuredType type = context.ElementType as IEdmStructuredType; + Debug.Assert(type != null); + + foreach (string pair in keyValuesPairs) + { + string[] pieces = pair.Split(new char[] { propertyDelimiter }, 2); + if (pieces.Length > 1 && !String.IsNullOrWhiteSpace(pieces[0])) + { + object propValue = null; + + IEdmTypeReference propertyType = null; + IEdmProperty property = type.FindProperty(pieces[0]); + if (property != null) + { + propertyType = property.Type; + } + + propValue = ODataUriUtils.ConvertFromUriLiteral(pieces[1], ODataVersion.V401, context.Model, propertyType); + propertyValuePairs.Add(pieces[0], propValue); + } + else + { + throw Error.InvalidOperation(SRResources.SkipTokenParseError); + } + } + + return propertyValuePairs; + } + + private static IList ParseValue(string value, char delim) + { + IList results = new List(); + StringBuilder escapedStringBuilder = new StringBuilder(); + for (int i = 0; i < value.Length; i++) + { + if (value[i] == '\'' || value[i] == '"') + { + escapedStringBuilder.Append(value[i]); + char openingQuoteChar = value[i]; + i++; + while (i < value.Length && value[i] != openingQuoteChar) + { + escapedStringBuilder.Append(value[i++]); + } + + if (i != value.Length) + { + escapedStringBuilder.Append(value[i]); + } + } + else if (value[i] == delim) + { + results.Add(escapedStringBuilder.ToString()); + escapedStringBuilder.Clear(); + } + else + { + escapedStringBuilder.Append(value[i]); + } + } + + string lastPair = escapedStringBuilder.ToString(); + if (!String.IsNullOrWhiteSpace(lastPair)) + { + results.Add(lastPair); + } + + return results; + } + + /// + /// Returns the list of properties that should be used for generating the skiptoken value. + /// + /// The last record that will be returned in the response. + /// IEdmModel + /// OrderBy nodes in the original request. + /// List of properties that should be used for generating the skiptoken value. + private static IEnumerable GetPropertiesForSkipToken(object lastMember, IEdmModel model, IList orderByNodes) + { + IEdmType edmType = GetTypeFromObject(lastMember, model); + IEdmEntityType entity = edmType as IEdmEntityType; + if (entity == null) + { + return null; + } + + IEnumerable key = entity.Key(); + if (orderByNodes != null) + { + if (orderByNodes.OfType().Any()) + { + //SkipToken will not support ordering on dynamic properties + return null; + } + + IList orderByProps = orderByNodes.OfType().Select(p => p.Property).AsList(); + foreach (IEdmProperty subKey in key) + { + if (!orderByProps.Contains(subKey)) + { + orderByProps.Add(subKey); + } + } + + return orderByProps.AsEnumerable(); + } + + return key; + } + + /// + /// Gets the EdmType from the Instance which may be a select expand wrapper. + /// + /// Instance for which the edmType needs to be computed. + /// IEdmModel + /// The EdmType of the underlying instance. + private static IEdmType GetTypeFromObject(object value, IEdmModel model) + { + SelectExpandWrapper selectExpand = value as SelectExpandWrapper; + if (selectExpand != null) + { + IEdmTypeReference typeReference = selectExpand.GetEdmType(); + return typeReference.Definition; + } + + Type clrType = value.GetType(); + return EdmLibHelpers.GetEdmType(model, clrType); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandAttribute.cs new file mode 100644 index 0000000..94195e1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandAttribute.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property or a class + /// correlate to OData's $expand query option settings. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)] + [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", + Justification = "Don't want those argument to be retrievable")] + public sealed class ExpandAttribute : Attribute + { + private readonly Dictionary _expandConfigurations = new Dictionary(); + private SelectExpandType _expandType; + private SelectExpandType? _defaultExpandType; + private int? _defaultMaxDepth; + private int _maxDepth; + + /// + /// Initializes a new instance of the class. + /// + public ExpandAttribute() + { + _defaultExpandType = SelectExpandType.Allowed; + _defaultMaxDepth = ODataValidationSettings.DefaultMaxExpansionDepth; + } + + /// + /// Initializes a new instance of the class + /// with the name of allowed expand properties. + /// + public ExpandAttribute(params string[] properties) + { + foreach (var property in properties) + { + if (!_expandConfigurations.ContainsKey(property)) + { + _expandConfigurations.Add(property, new ExpandConfiguration + { + ExpandType = SelectExpandType.Allowed, + MaxDepth = ODataValidationSettings.DefaultMaxExpansionDepth + }); + } + } + } + + /// + /// Gets or sets the of navigation properties. + /// + public Dictionary ExpandConfigurations + { + get + { + return _expandConfigurations; + } + } + + /// + /// Gets or sets the of navigation properties. + /// + public SelectExpandType ExpandType + { + get + { + return _expandType; + } + set + { + _expandType = value; + foreach (var key in _expandConfigurations.Keys) + { + _expandConfigurations[key].ExpandType = _expandType; + } + + if (_expandConfigurations.Count == 0) + { + _defaultExpandType = _expandType; + } + } + } + + /// + /// Gets or sets the maximum expand depth of navigation properties. + /// + public int MaxDepth + { + get + { + return _maxDepth; + } + set + { + _maxDepth = value; + foreach (var key in _expandConfigurations.Keys) + { + _expandConfigurations[key].MaxDepth = _maxDepth; + } + + if (_expandConfigurations.Count == 0) + { + _defaultMaxDepth = _maxDepth; + } + } + } + + internal SelectExpandType? DefaultExpandType + { + get + { + return _defaultExpandType; + } + set + { + _defaultExpandType = value; + } + } + + internal int? DefaultMaxDepth + { + get + { + return _defaultMaxDepth; + } + set + { + _defaultMaxDepth = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandConfiguration.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandConfiguration.cs new file mode 100644 index 0000000..3ea1dae --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ExpandConfiguration.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents a configuration of an expandable property. + /// + public class ExpandConfiguration + { + /// + /// Gets or sets the . + /// + public SelectExpandType ExpandType { get; set; } + + /// + /// Gets or sets the maximum depth. + /// + public int MaxDepth { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs new file mode 100644 index 0000000..d6c1fbf --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs @@ -0,0 +1,670 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + internal class AggregationBinder : ExpressionBinderBase + { + private Type _elementType; + private TransformationNode _transformation; + + private ParameterExpression _lambdaParameter; + + private IEnumerable _aggregateExpressions; + private IEnumerable _groupingProperties; + + private Type _groupByClrType; + + private bool _classicEF = false; + + internal AggregationBinder(ODataQuerySettings settings, IWebApiAssembliesResolver assembliesResolver, Type elementType, + IEdmModel model, TransformationNode transformation) + : base(model, assembliesResolver, settings) + { + Contract.Assert(elementType != null); + Contract.Assert(transformation != null); + + _elementType = elementType; + _transformation = transformation; + + this._lambdaParameter = Expression.Parameter(this._elementType, "$it"); + + switch (transformation.Kind) + { + case TransformationNodeKind.Aggregate: + var aggregateClause = this._transformation as AggregateTransformationNode; + _aggregateExpressions = FixCustomMethodReturnTypes(aggregateClause.AggregateExpressions); + ResultClrType = typeof(NoGroupByAggregationWrapper); + break; + case TransformationNodeKind.GroupBy: + var groupByClause = this._transformation as GroupByTransformationNode; + _groupingProperties = groupByClause.GroupingProperties; + if (groupByClause.ChildTransformations != null) + { + if (groupByClause.ChildTransformations.Kind == TransformationNodeKind.Aggregate) + { + var aggregationNode = (AggregateTransformationNode)groupByClause.ChildTransformations; + _aggregateExpressions = FixCustomMethodReturnTypes(aggregationNode.AggregateExpressions); + } + else + { + throw new NotImplementedException(); + } + } + + _groupByClrType = typeof(GroupByWrapper); + ResultClrType = typeof(AggregationWrapper); + break; + default: + throw new NotSupportedException(String.Format(CultureInfo.InvariantCulture, + SRResources.NotSupportedTransformationKind, transformation.Kind)); + } + + _groupByClrType = _groupByClrType ?? typeof(NoGroupByWrapper); + } + + private static Expression WrapDynamicCastIfNeeded(Expression propertyAccessor) + { + if (propertyAccessor.Type == typeof(object)) + { + return Expression.Call(null, ExpressionHelperMethods.ConvertToDecimal, propertyAccessor); + } + + return propertyAccessor; + } + + private IEnumerable FixCustomMethodReturnTypes(IEnumerable aggregateExpressions) + { + return aggregateExpressions.Select(x => + { + var ae = x as AggregateExpression; + return ae != null ? FixCustomMethodReturnType(ae) : x; + }); + } + + private AggregateExpression FixCustomMethodReturnType(AggregateExpression expression) + { + if (expression.Method != AggregationMethod.Custom) + { + return expression; + } + + var customMethod = GetCustomMethod(expression); + var typeReference = EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(customMethod.ReturnType); + return new AggregateExpression(expression.Expression, expression.MethodDefinition, expression.Alias, typeReference); + } + + private MethodInfo GetCustomMethod(AggregateExpression expression) + { + var propertyLambda = Expression.Lambda(BindAccessor(expression.Expression), this._lambdaParameter); + Type inputType = propertyLambda.Body.Type; + + string methodToken = expression.MethodDefinition.MethodLabel; + var customFunctionAnnotations = Model.GetAnnotationValue(Model); + + MethodInfo customMethod; + if (!customFunctionAnnotations.GetMethodInfo(methodToken, inputType, out customMethod)) + { + throw new ODataException( + Error.Format( + SRResources.AggregationNotSupportedForType, + expression.Method, + expression.Expression, + inputType)); + } + + return customMethod; + } + + /// + /// Gets CLR type returned from the query. + /// + public Type ResultClrType + { + get; private set; + } + + public IEdmTypeReference ResultType + { + get; private set; + } + + public IQueryable Bind(IQueryable query) + { + Contract.Assert(query != null); + + this._classicEF = IsClassicEF(query); + this.BaseQuery = query; + EnsureFlattenedPropertyContainer(this._lambdaParameter); + + // Answer is query.GroupBy($it => new DynamicType1() {...}).Select($it => new DynamicType2() {...}) + // We are doing Grouping even if only aggregate was specified to have a IQuaryable after aggregation + IQueryable grouping = BindGroupBy(query); + + IQueryable result = BindSelect(grouping); + + return result; + } + + /// + /// Checks IQueryable provider for need of EF6 optimization + /// + /// + /// True if EF6 optimization are needed. + internal virtual bool IsClassicEF(IQueryable query) + { + var providerNS = query.Provider.GetType().Namespace; + return (providerNS == HandleNullPropagationOptionHelper.ObjectContextQueryProviderNamespaceEF6 + || providerNS == HandleNullPropagationOptionHelper.EntityFrameworkQueryProviderNamespace); + } + + private IQueryable BindSelect(IQueryable grouping) + { + // Should return following expression + // .Select($it => New DynamicType2() + // { + // GroupByContainer = $it.Key.GroupByContainer // If groupby section present + // Container => new AggregationPropertyContainer() { + // Name = "Alias1", + // Value = $it.AsQuaryable().Sum(i => i.AggregatableProperty), + // Next = new LastInChain() { + // Name = "Alias2", + // Value = $it.AsQuaryable().Sum(i => i.AggregatableProperty) + // } + // } + // }) + var groupingType = typeof(IGrouping<,>).MakeGenericType(this._groupByClrType, this._elementType); + ParameterExpression accum = Expression.Parameter(groupingType, "$it"); + + List wrapperTypeMemberAssignments = new List(); + + // Setting GroupByContainer property when previous step was grouping + if (this._groupingProperties != null && this._groupingProperties.Any()) + { + var wrapperProperty = this.ResultClrType.GetProperty("GroupByContainer"); + + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, Expression.Property(Expression.Property(accum, "Key"), "GroupByContainer"))); + } + + // Setting Container property when we have aggregation clauses + if (_aggregateExpressions != null) + { + var properties = new List(); + foreach (var aggExpression in _aggregateExpressions) + { + properties.Add(new NamedPropertyExpression(Expression.Constant(aggExpression.Alias), CreateAggregationExpression(accum, aggExpression, this._elementType))); + } + + var wrapperProperty = ResultClrType.GetProperty("Container"); + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(properties))); + } + + var initilizedMember = + Expression.MemberInit(Expression.New(ResultClrType), wrapperTypeMemberAssignments); + var selectLambda = Expression.Lambda(initilizedMember, accum); + + var result = ExpressionHelpers.Select(grouping, selectLambda, groupingType); + return result; + } + + private List CreateSelectMemberAssigments(Type type, MemberExpression propertyAccessor, + IEnumerable properties) + { + var wrapperTypeMemberAssignments = new List(); + if (_groupingProperties != null) + { + foreach (var node in properties) + { + var nodePropertyAccessor = Expression.Property(propertyAccessor, node.Name); + var member = type.GetMember(node.Name).Single(); + if (node.Expression != null) + { + wrapperTypeMemberAssignments.Add(Expression.Bind(member, nodePropertyAccessor)); + } + else + { + var memberType = (member as PropertyInfo).PropertyType; + var expr = Expression.MemberInit(Expression.New(memberType), + CreateSelectMemberAssigments(memberType, nodePropertyAccessor, node.ChildTransformations)); + wrapperTypeMemberAssignments.Add(Expression.Bind(member, expr)); + } + } + } + + return wrapperTypeMemberAssignments; + } + + private Expression CreateAggregationExpression(ParameterExpression accum, AggregateExpressionBase expression, Type baseType) + { + switch (expression.AggregateKind) + { + case AggregateExpressionKind.PropertyAggregate: + return CreatePropertyAggregateExpression(accum, expression as AggregateExpression, baseType); + case AggregateExpressionKind.EntitySetAggregate: + return CreateEntitySetAggregateExpression(accum, expression as EntitySetAggregateExpression, baseType); + default: + throw new ODataException(Error.Format(SRResources.AggregateKindNotSupported, expression.AggregateKind)); + } + } + + private Expression CreateEntitySetAggregateExpression( + ParameterExpression accum, EntitySetAggregateExpression expression, Type baseType) + { + // Should return following expression + // $it => $it.AsQueryable() + // .SelectMany($it => $it.SomeEntitySet) + // .GroupBy($gr => new Object()) + // .Select($p => new DynamicTypeWrapper() + // { + // AliasOne = $p.AsQueryable().AggMethodOne($it => $it.SomePropertyOfSomeEntitySet), + // AliasTwo = $p.AsQueryable().AggMethodTwo($it => $it.AnotherPropertyOfSomeEntitySet), + // ... + // AliasN = ... , // A nested expression of this same format. + // ... + // }) + + List wrapperTypeMemberAssignments = new List(); + var asQueryableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType); + Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accum); + + // Create lambda to access the entity set from expression + var source = BindAccessor(expression.Expression.Source); + string propertyName = EdmLibHelpers.GetClrPropertyName(expression.Expression.NavigationProperty, Model); + + var property = Expression.Property(source, propertyName); + + var baseElementType = source.Type; + var selectedElementType = property.Type.GenericTypeArguments.Single(); + + // Create method to get property collections to aggregate + MethodInfo selectManyMethod + = ExpressionHelperMethods.EnumerableSelectManyGeneric.MakeGenericMethod(baseElementType, selectedElementType); + + // Create the lambda that acceses the property in the selectMany clause. + var selectManyParam = Expression.Parameter(baseElementType, "$it"); + var propertyExpression = Expression.Property(selectManyParam, expression.Expression.NavigationProperty.Name); + var selectManyLambda = Expression.Lambda(propertyExpression, selectManyParam); + + // Get expression to get collection of entities + var entitySet = Expression.Call(null, selectManyMethod, asQueryableExpression, selectManyLambda); + + // Getting method and lambda expression of groupBy + var groupKeyType = typeof(object); + MethodInfo groupByMethod = + ExpressionHelperMethods.EnumerableGroupByGeneric.MakeGenericMethod(selectedElementType, groupKeyType); + var groupByLambda = Expression.Lambda( + Expression.New(groupKeyType), + Expression.Parameter(selectedElementType, "$gr")); + + // Group entities in a single group to apply select + var groupedEntitySet = Expression.Call(null, groupByMethod, entitySet, groupByLambda); + + var groupingType = typeof(IGrouping<,>).MakeGenericType(groupKeyType, selectedElementType); + ParameterExpression innerAccum = Expression.Parameter(groupingType, "$p"); + + // Nested properties + // Create dynamicTypeWrapper to encapsulate the aggregate result + var properties = new List(); + foreach (var aggExpression in expression.Children) + { + properties.Add(new NamedPropertyExpression(Expression.Constant(aggExpression.Alias), CreateAggregationExpression(innerAccum, aggExpression, selectedElementType))); + } + + var nestedResultType = typeof(EntitySetAggregationWrapper); + var wrapperProperty = nestedResultType.GetProperty("Container"); + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(properties))); + + var initializedMember = + Expression.MemberInit(Expression.New(nestedResultType), wrapperTypeMemberAssignments); + var selectLambda = Expression.Lambda(initializedMember, innerAccum); + + // Get select method + MethodInfo selectMethod = + ExpressionHelperMethods.EnumerableSelectGeneric.MakeGenericMethod( + groupingType, + selectLambda.Body.Type); + + return Expression.Call(null, selectMethod, groupedEntitySet, selectLambda); + } + + private Expression CreatePropertyAggregateExpression(ParameterExpression accum, AggregateExpression expression, Type baseType) + { + // accum type is IGrouping<,baseType> that implements IEnumerable + // we need cast it to IEnumerable during expression building (IEnumerable)$it + // however for EF6 we need to use $it.AsQueryable() due to limitations in types of casts that will properly translated + Expression asQuerableExpression = null; + if (_classicEF) + { + var asQuerableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType); + asQuerableExpression = Expression.Call(null, asQuerableMethod, accum); + } + else + { + var queryableType = typeof(IEnumerable<>).MakeGenericType(baseType); + asQuerableExpression = Expression.Convert(accum, queryableType); + } + + // $count is a virtual property, so there's not a propertyLambda to create. + if (expression.Method == AggregationMethod.VirtualPropertyCount) + { + var countMethod = (_classicEF + ? ExpressionHelperMethods.QueryableCountGeneric + : ExpressionHelperMethods.EnumerableCountGeneric).MakeGenericMethod(baseType); + return WrapConvert(Expression.Call(null, countMethod, asQuerableExpression)); + } + + var lambdaParameter = baseType == this._elementType ? this._lambdaParameter : Expression.Parameter(baseType, "$it"); + Expression propertyAccessor = BindAccessor(expression.Expression, lambdaParameter); + LambdaExpression propertyLambda = Expression.Lambda(propertyAccessor, + lambdaParameter); + + Expression aggregationExpression; + + switch (expression.Method) + { + case AggregationMethod.Min: + { + var minMethod = (_classicEF + ? ExpressionHelperMethods.QueryableMin + : ExpressionHelperMethods.EnumerableMin).MakeGenericMethod(baseType, + propertyLambda.Body.Type); + aggregationExpression = Expression.Call(null, minMethod, asQuerableExpression, propertyLambda); + } + break; + case AggregationMethod.Max: + { + var maxMethod = (_classicEF + ? ExpressionHelperMethods.QueryableMax + : ExpressionHelperMethods.EnumerableMax).MakeGenericMethod(baseType, + propertyLambda.Body.Type); + aggregationExpression = Expression.Call(null, maxMethod, asQuerableExpression, propertyLambda); + } + break; + case AggregationMethod.Sum: + { + MethodInfo sumGenericMethod; + // For Dynamic properties cast to decimal + Expression propertyExpression = WrapDynamicCastIfNeeded(propertyAccessor); + propertyLambda = Expression.Lambda(propertyExpression, lambdaParameter); + + if ( + !(_classicEF + ? ExpressionHelperMethods.QueryableSumGenerics + : ExpressionHelperMethods.EnumerableSumGenerics).TryGetValue(propertyExpression.Type, + out sumGenericMethod)) + { + throw new ODataException(Error.Format(SRResources.AggregationNotSupportedForType, + expression.Method, expression.Expression, propertyExpression.Type)); + } + + var sumMethod = sumGenericMethod.MakeGenericMethod(baseType); + aggregationExpression = Expression.Call(null, sumMethod, asQuerableExpression, propertyLambda); + + // For Dynamic properties cast back to object + if (propertyAccessor.Type == typeof(object)) + { + aggregationExpression = Expression.Convert(aggregationExpression, typeof(object)); + } + } + break; + case AggregationMethod.Average: + { + MethodInfo averageGenericMethod; + // For Dynamic properties cast to decimal + Expression propertyExpression = WrapDynamicCastIfNeeded(propertyAccessor); + propertyLambda = Expression.Lambda(propertyExpression, lambdaParameter); + + if ( + !(_classicEF + ? ExpressionHelperMethods.QueryableAverageGenerics + : ExpressionHelperMethods.EnumerableAverageGenerics).TryGetValue(propertyExpression.Type, + out averageGenericMethod)) + { + throw new ODataException(Error.Format(SRResources.AggregationNotSupportedForType, + expression.Method, expression.Expression, propertyExpression.Type)); + } + + var averageMethod = averageGenericMethod.MakeGenericMethod(baseType); + aggregationExpression = Expression.Call(null, averageMethod, asQuerableExpression, propertyLambda); + + // For Dynamic properties cast back to object + if (propertyAccessor.Type == typeof(object)) + { + aggregationExpression = Expression.Convert(aggregationExpression, typeof(object)); + } + } + break; + case AggregationMethod.CountDistinct: + { + // I select the specific field + var selectMethod = + (_classicEF + ? ExpressionHelperMethods.QueryableSelectGeneric + : ExpressionHelperMethods.EnumerableSelectGeneric).MakeGenericMethod(this._elementType, + propertyLambda.Body.Type); + Expression queryableSelectExpression = Expression.Call(null, selectMethod, asQuerableExpression, + propertyLambda); + + // I run distinct over the set of items + var distinctMethod = + (_classicEF + ? ExpressionHelperMethods.QueryableDistinct + : ExpressionHelperMethods.EnumerableDistinct).MakeGenericMethod(propertyLambda.Body.Type); + Expression distinctExpression = Expression.Call(null, distinctMethod, queryableSelectExpression); + + // I count the distinct items as the aggregation expression + var countMethod = + (_classicEF + ? ExpressionHelperMethods.QueryableCountGeneric + : ExpressionHelperMethods.EnumerableCountGeneric).MakeGenericMethod(propertyLambda.Body.Type); + aggregationExpression = Expression.Call(null, countMethod, distinctExpression); + } + break; + case AggregationMethod.Custom: + { + MethodInfo customMethod = GetCustomMethod(expression); + var selectMethod = + (_classicEF + ? ExpressionHelperMethods.QueryableSelectGeneric + : ExpressionHelperMethods.EnumerableSelectGeneric).MakeGenericMethod(this._elementType, propertyLambda.Body.Type); + var selectExpression = Expression.Call(null, selectMethod, asQuerableExpression, propertyLambda); + aggregationExpression = Expression.Call(null, customMethod, selectExpression); + } + break; + default: + throw new ODataException(Error.Format(SRResources.AggregationMethodNotSupported, expression.Method)); + } + + return WrapConvert(aggregationExpression); + } + + private Expression WrapConvert(Expression expression) + { + // Expression that we are generating looks like Value = $it.PropertyName where Value is defined as object and PropertyName can be value + // Proper .NET expression must look like as Value = (object) $it.PropertyName for proper boxing or AccessViolationException will be thrown + // Cast to object isn't translatable by EF6 as a result skipping (object) in that case + return (this._classicEF || !expression.Type.IsValueType) + ? expression + : Expression.Convert(expression, typeof(object)); + } + + private Expression BindAccessor(QueryNode node, Expression baseElement = null) + { + switch (node.Kind) + { + case QueryNodeKind.ResourceRangeVariableReference: + return baseElement ?? this._lambdaParameter; + case QueryNodeKind.SingleValuePropertyAccess: + var propAccessNode = node as SingleValuePropertyAccessNode; + return CreatePropertyAccessExpression(BindAccessor(propAccessNode.Source, baseElement), propAccessNode.Property, GetFullPropertyPath(propAccessNode)); + case QueryNodeKind.AggregatedCollectionPropertyNode: + var aggPropAccessNode = node as AggregatedCollectionPropertyNode; + return CreatePropertyAccessExpression(BindAccessor(aggPropAccessNode.Source, baseElement), aggPropAccessNode.Property); + case QueryNodeKind.SingleComplexNode: + var singleComplexNode = node as SingleComplexNode; + return CreatePropertyAccessExpression(BindAccessor(singleComplexNode.Source, baseElement), singleComplexNode.Property, GetFullPropertyPath(singleComplexNode)); + case QueryNodeKind.SingleValueOpenPropertyAccess: + var openNode = node as SingleValueOpenPropertyAccessNode; + return GetFlattenedPropertyExpression(openNode.Name) ?? CreateOpenPropertyAccessExpression(openNode); + case QueryNodeKind.None: + case QueryNodeKind.SingleNavigationNode: + var navNode = (SingleNavigationNode)node; + return CreatePropertyAccessExpression(BindAccessor(navNode.Source), navNode.NavigationProperty); + case QueryNodeKind.BinaryOperator: + var binaryNode = (BinaryOperatorNode)node; + var leftExpression = BindAccessor(binaryNode.Left, baseElement); + var rightExpression = BindAccessor(binaryNode.Right, baseElement); + return CreateBinaryExpression(binaryNode.OperatorKind, leftExpression, rightExpression, + liftToNull: true); + case QueryNodeKind.Convert: + var convertNode = (ConvertNode)node; + return CreateConvertExpression(convertNode, BindAccessor(convertNode.Source, baseElement)); + case QueryNodeKind.CollectionNavigationNode: + return baseElement ?? this._lambdaParameter; + default: + throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, + typeof(AggregationBinder).Name); + } + } + + private Expression CreatePropertyAccessExpression(Expression source, IEdmProperty property, string propertyPath = null) + { + string propertyName = EdmLibHelpers.GetClrPropertyName(property, Model); + propertyPath = propertyPath ?? propertyName; + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) && + source != this._lambdaParameter) + { + Expression cleanSource = RemoveInnerNullPropagation(source); + Expression propertyAccessExpression = null; + propertyAccessExpression = GetFlattenedPropertyExpression(propertyPath) ?? Expression.Property(cleanSource, propertyName); + + // source.property => source == null ? null : [CastToNullable]RemoveInnerNullPropagation(source).property + // Notice that we are checking if source is null already. so we can safely remove any null checks when doing source.Property + + Expression ifFalse = ToNullable(ConvertNonStandardPrimitives(propertyAccessExpression)); + return + Expression.Condition( + test: Expression.Equal(source, NullConstant), + ifTrue: Expression.Constant(null, ifFalse.Type), + ifFalse: ifFalse); + } + else + { + return GetFlattenedPropertyExpression(propertyPath) ?? ConvertNonStandardPrimitives(Expression.Property(source, propertyName)); + } + } + + private Expression CreateOpenPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode) + { + Expression sourceAccessor = BindAccessor(openNode.Source); + + // First check that property exists in source + // It's the case when we are apply transformation based on earlier transformation + if (sourceAccessor.Type.GetProperty(openNode.Name) != null) + { + return Expression.Property(sourceAccessor, openNode.Name); + } + + // Property doesn't exists go for dynamic properties dictionary + PropertyInfo prop = GetDynamicPropertyContainer(openNode); + MemberExpression propertyAccessExpression = Expression.Property(sourceAccessor, prop.Name); + IndexExpression readDictionaryIndexerExpression = Expression.Property(propertyAccessExpression, + DictionaryStringObjectIndexerName, Expression.Constant(openNode.Name)); + MethodCallExpression containsKeyExpression = Expression.Call(propertyAccessExpression, + propertyAccessExpression.Type.GetMethod("ContainsKey"), Expression.Constant(openNode.Name)); + ConstantExpression nullExpression = Expression.Constant(null); + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + var dynamicDictIsNotNull = Expression.NotEqual(propertyAccessExpression, Expression.Constant(null)); + var dynamicDictIsNotNullAndContainsKey = Expression.AndAlso(dynamicDictIsNotNull, containsKeyExpression); + return Expression.Condition( + dynamicDictIsNotNullAndContainsKey, + readDictionaryIndexerExpression, + nullExpression); + } + else + { + return Expression.Condition( + containsKeyExpression, + readDictionaryIndexerExpression, + nullExpression); + } + } + + private IQueryable BindGroupBy(IQueryable query) + { + LambdaExpression groupLambda = null; + if (_groupingProperties != null && _groupingProperties.Any()) + { + // Generates expression + // .GroupBy($it => new DynamicTypeWrapper() + // { + // GroupByContainer => new AggregationPropertyContainer() { + // Name = "Prop1", + // Value = $it.Prop1, + // Next = new AggregationPropertyContainer() { + // Name = "Prop2", + // Value = $it.Prop2, // int + // Next = new LastInChain() { + // Name = "Prop3", + // Value = $it.Prop3 + // } + // } + // } + // }) + List properties = CreateGroupByMemberAssignments(_groupingProperties); + + var wrapperProperty = typeof(GroupByWrapper).GetProperty("GroupByContainer"); + List wta = new List(); + wta.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(properties))); + groupLambda = Expression.Lambda(Expression.MemberInit(Expression.New(typeof(GroupByWrapper)), wta), _lambdaParameter); + } + else + { + // We do not have properties to aggregate + // .GroupBy($it => new NoGroupByWrapper()) + groupLambda = Expression.Lambda(Expression.New(this._groupByClrType), this._lambdaParameter); + } + + return ExpressionHelpers.GroupBy(query, groupLambda, this._elementType, this._groupByClrType); + } + + private List CreateGroupByMemberAssignments(IEnumerable nodes) + { + var properties = new List(); + foreach (var grpProp in nodes) + { + var propertyName = grpProp.Name; + if (grpProp.Expression != null) + { + properties.Add(new NamedPropertyExpression(Expression.Constant(propertyName), WrapConvert(BindAccessor(grpProp.Expression)))); + } + else + { + var wrapperProperty = typeof(GroupByWrapper).GetProperty("GroupByContainer"); + List wta = new List(); + wta.Add(Expression.Bind(wrapperProperty, AggregationPropertyContainer.CreateNextNamedPropertyContainer(CreateGroupByMemberAssignments(grpProp.ChildTransformations)))); + properties.Add(new NamedPropertyExpression(Expression.Constant(propertyName), Expression.MemberInit(Expression.New(typeof(GroupByWrapper)), wta))); + } + } + + return properties; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationPropertyContainer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationPropertyContainer.cs new file mode 100644 index 0000000..d34f990 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationPropertyContainer.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq.Expressions; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Reperesent properties used in groupby and aggregate clauses to make them accessiable in further clauses/transformations + /// + /// + /// When we have $apply=groupby((Prop1,Prop2, Prop3))&$orderby=Prop1, Prop2 + /// We will have following expression in .GroupBy + /// $it => new AggregationPropertyContainer() { + /// Name = "Prop1", + /// Value = $it.Prop1, // string + /// Next = new AggregationPropertyContainer() { + /// Name = "Prop2", + /// Value = $it.Prop2, // int + /// Next = new LastInChain() { + /// Name = "Prop3", + /// Value = $it.Prop3 + /// } + /// } + /// } + /// when in $orderby (see AggregationBinder CollectProperties method) + /// Prop1 could be referenced us $it => (string)$it.Value + /// Prop2 could be referenced us $it => (int)$it.Next.Value + /// Prop3 could be referenced us $it => (int)$it.Next.Next.Value + /// Generic type for Value is used to avoid type casts for on primitive types that not supported in EF + /// + /// Also we have 4 use cases and base type have all requiered properties to support no cast usage. + /// 1. Primitive property with Next + /// 2. Primitive property without Next + /// 3. Nested property with Next + /// 4. Nested property without Next + /// However, EF doesn't allow to set different properties for the same type in two places in an lamba-expression => using new type with just new name to workaround that issue + /// + /// + internal class AggregationPropertyContainer : PropertyContainer.NamedProperty + { + public GroupByWrapper NestedValue + { + get + { + return (GroupByWrapper)this.Value; + } + set + { + Value = value; + } + } + + public AggregationPropertyContainer Next { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + if (Next != null) + { + Next.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + + public override object GetValue() + { + // Value is object and when Value is populated form the DB by EF or other ORM, it will not auto converted to null as in case of real type + if (Value == DBNull.Value) + { + return null; + } + + return base.GetValue(); + } + + private class LastInChain : AggregationPropertyContainer + { + } + + private class NestedPropertyLastInChain : AggregationPropertyContainer + { + } + + private class NestedProperty : AggregationPropertyContainer + { + } + + public static Expression CreateNextNamedPropertyContainer(IList properties) + { + Expression container = null; + + // build the linked list of properties. + foreach (NamedPropertyExpression property in properties) + { + container = CreateNextNamedPropertyCreationExpression(property, container); + } + + return container; + } + + private static Expression CreateNextNamedPropertyCreationExpression(NamedPropertyExpression property, Expression next) + { + Contract.Assert(property != null); + Contract.Assert(property.Value != null); + + Type namedPropertyType = null; + if (next != null) + { + if (property.Value.Type == typeof(GroupByWrapper)) + { + namedPropertyType = typeof(NestedProperty); + } + else + { + namedPropertyType = typeof(AggregationPropertyContainer); + } + } + else + { + if (property.Value.Type == typeof(GroupByWrapper)) + { + namedPropertyType = typeof(NestedPropertyLastInChain); + } + else + { + namedPropertyType = typeof(LastInChain); + } + } + + List memberBindings = new List(); + + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("Name"), property.Name)); + + if (property.Value.Type == typeof(GroupByWrapper)) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("NestedValue"), property.Value)); + } + else + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("Value"), property.Value)); + } + + if (next != null) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("Next"), next)); + } + + if (property.NullCheck != null) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("IsNull"), property.NullCheck)); + } + + return Expression.MemberInit(Expression.New(namedPropertyType), memberBindings); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrCanonicalFunctions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrCanonicalFunctions.cs new file mode 100644 index 0000000..bbd1fd5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrCanonicalFunctions.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + internal class ClrCanonicalFunctions + { + private static string _defaultString = default(string); + private static Enum _defaultEnum = default(Enum); + + // function names + internal const string StartswithFunctionName = "startswith"; + internal const string EndswithFunctionName = "endswith"; + internal const string ContainsFunctionName = "contains"; + internal const string SubstringFunctionName = "substring"; + internal const string LengthFunctionName = "length"; + internal const string IndexofFunctionName = "indexof"; + internal const string TolowerFunctionName = "tolower"; + internal const string ToupperFunctionName = "toupper"; + internal const string TrimFunctionName = "trim"; + internal const string ConcatFunctionName = "concat"; + internal const string YearFunctionName = "year"; + internal const string MonthFunctionName = "month"; + internal const string DayFunctionName = "day"; + internal const string HourFunctionName = "hour"; + internal const string MinuteFunctionName = "minute"; + internal const string SecondFunctionName = "second"; + internal const string MillisecondFunctionName = "millisecond"; + internal const string FractionalSecondsFunctionName = "fractionalseconds"; + internal const string RoundFunctionName = "round"; + internal const string FloorFunctionName = "floor"; + internal const string CeilingFunctionName = "ceiling"; + internal const string CastFunctionName = "cast"; + internal const string IsofFunctionName = "isof"; + internal const string DateFunctionName = "date"; + internal const string TimeFunctionName = "time"; + internal const string NowFunctionName = "now"; + + // string functions + public static readonly MethodInfo StartsWith = MethodOf(_ => _defaultString.StartsWith(default(string))); + public static readonly MethodInfo EndsWith = MethodOf(_ => _defaultString.EndsWith(default(string))); + public static readonly MethodInfo Contains = MethodOf(_ => _defaultString.Contains(default(string))); + public static readonly MethodInfo SubstringStart = MethodOf(_ => _defaultString.Substring(default(int))); + public static readonly MethodInfo SubstringStartAndLength = MethodOf(_ => _defaultString.Substring(default(int), default(int))); + public static readonly MethodInfo SubstringStartNoThrow = MethodOf(_ => ClrSafeFunctions.SubstringStart(default(string), default(int))); + public static readonly MethodInfo SubstringStartAndLengthNoThrow = MethodOf(_ => ClrSafeFunctions.SubstringStartAndLength(default(string), default(int), default(int))); + public static readonly MethodInfo IndexOf = MethodOf(_ => _defaultString.IndexOf(default(string))); + public static readonly MethodInfo ToLower = MethodOf(_ => _defaultString.ToLower()); + public static readonly MethodInfo ToUpper = MethodOf(_ => _defaultString.ToUpper()); + public static readonly MethodInfo Trim = MethodOf(_ => _defaultString.Trim()); + public static readonly MethodInfo Concat = MethodOf(_ => String.Concat(default(string), default(string))); + + // math functions + public static readonly MethodInfo CeilingOfDouble = MethodOf(_ => Math.Ceiling(default(double))); + public static readonly MethodInfo RoundOfDouble = MethodOf(_ => Math.Round(default(double))); + public static readonly MethodInfo FloorOfDouble = MethodOf(_ => Math.Floor(default(double))); + + public static readonly MethodInfo CeilingOfDecimal = MethodOf(_ => Math.Ceiling(default(decimal))); + public static readonly MethodInfo RoundOfDecimal = MethodOf(_ => Math.Round(default(decimal))); + public static readonly MethodInfo FloorOfDecimal = MethodOf(_ => Math.Floor(default(decimal))); + + // enum functions + public static readonly MethodInfo HasFlag = MethodOf(_ => _defaultEnum.HasFlag(default(Enum))); + + // Date properties + public static readonly Dictionary DateProperties = new[] + { + new KeyValuePair(YearFunctionName, typeof(Date).GetProperty("Year")), + new KeyValuePair(MonthFunctionName, typeof(Date).GetProperty("Month")), + new KeyValuePair(DayFunctionName, typeof(Date).GetProperty("Day")), + }.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + // DateTimeproperties + public static readonly Dictionary DateTimeProperties = new[] + { + new KeyValuePair(YearFunctionName, typeof(DateTime).GetProperty("Year")), + new KeyValuePair(MonthFunctionName, typeof(DateTime).GetProperty("Month")), + new KeyValuePair(DayFunctionName, typeof(DateTime).GetProperty("Day")), + new KeyValuePair(HourFunctionName, typeof(DateTime).GetProperty("Hour")), + new KeyValuePair(MinuteFunctionName, typeof(DateTime).GetProperty("Minute")), + new KeyValuePair(SecondFunctionName, typeof(DateTime).GetProperty("Second")), + new KeyValuePair(MillisecondFunctionName, typeof(DateTime).GetProperty("Millisecond")), + }.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + // DateTimeOffset properties + public static readonly Dictionary DateTimeOffsetProperties = new[] + { + new KeyValuePair(YearFunctionName, typeof(DateTimeOffset).GetProperty("Year")), + new KeyValuePair(MonthFunctionName, typeof(DateTimeOffset).GetProperty("Month")), + new KeyValuePair(DayFunctionName, typeof(DateTimeOffset).GetProperty("Day")), + new KeyValuePair(HourFunctionName, typeof(DateTimeOffset).GetProperty("Hour")), + new KeyValuePair(MinuteFunctionName, typeof(DateTimeOffset).GetProperty("Minute")), + new KeyValuePair(SecondFunctionName, typeof(DateTimeOffset).GetProperty("Second")), + new KeyValuePair(MillisecondFunctionName, typeof(DateTimeOffset).GetProperty("Millisecond")), + }.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + // TimeOfDay properties + // ODL uses the Hour(s), Minute(s), Second(s), It's the wrong property name. It should be Hour, Minute, Second. + public static readonly Dictionary TimeOfDayProperties = new[] + { + new KeyValuePair(HourFunctionName, typeof(TimeOfDay).GetProperty("Hours")), + new KeyValuePair(MinuteFunctionName, typeof(TimeOfDay).GetProperty("Minutes")), + new KeyValuePair(SecondFunctionName, typeof(TimeOfDay).GetProperty("Seconds")), + new KeyValuePair(MillisecondFunctionName, typeof(TimeOfDay).GetProperty("Milliseconds")), + }.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + // TimeSpan properties + public static readonly Dictionary TimeSpanProperties = new[] + { + new KeyValuePair(HourFunctionName, typeof(TimeSpan).GetProperty("Hours")), + new KeyValuePair(MinuteFunctionName, typeof(TimeSpan).GetProperty("Minutes")), + new KeyValuePair(SecondFunctionName, typeof(TimeSpan).GetProperty("Seconds")), + new KeyValuePair(MillisecondFunctionName, typeof(TimeSpan).GetProperty("Milliseconds")), + }.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + // String Properties + public static readonly PropertyInfo Length = typeof(string).GetProperty("Length"); + + // PropertyInfo and MethodInfo of DateTime & DateTimeOffset related. + public static readonly PropertyInfo DateTimeKindPropertyInfo = typeof(DateTime).GetProperty("Kind"); + public static readonly MethodInfo ToUniversalTimeDateTime = typeof(DateTime).GetMethod("ToUniversalTime", BindingFlags.Instance | BindingFlags.Public); + public static readonly MethodInfo ToUniversalTimeDateTimeOffset = typeof(DateTimeOffset).GetMethod("ToUniversalTime", BindingFlags.Instance | BindingFlags.Public); + public static readonly MethodInfo ToOffsetFunction = typeof(DateTimeOffset).GetMethod("ToOffset", BindingFlags.Instance | BindingFlags.Public); + public static readonly MethodInfo GetUtcOffset = typeof(TimeZoneInfo).GetMethod("GetUtcOffset", new[] { typeof(DateTime) }); + + private static MethodInfo MethodOf(Expression> expression) + { + return MethodOf(expression as Expression); + } + + private static MethodInfo MethodOf(Expression expression) + { + LambdaExpression lambdaExpression = expression as LambdaExpression; + Contract.Assert(lambdaExpression != null); + Contract.Assert(expression.NodeType == ExpressionType.Lambda); + Contract.Assert(lambdaExpression.Body.NodeType == ExpressionType.Call); + return (lambdaExpression.Body as MethodCallExpression).Method; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrSafeFunctions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrSafeFunctions.cs new file mode 100644 index 0000000..b7e30c5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ClrSafeFunctions.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// This class contains safe equivalents of CLR functions that + /// could throw exceptions at runtime. + /// + internal class ClrSafeFunctions + { + public static string SubstringStart(string str, int startIndex) + { + Contract.Assert(str != null); + + if (startIndex < 0) + { + startIndex = 0; + } + + // String.Substring(int) accepts startIndex==length + return startIndex <= str.Length + ? str.Substring(startIndex) + : String.Empty; + } + + public static string SubstringStartAndLength(string str, int startIndex, int length) + { + Contract.Assert(str != null); + + if (startIndex < 0) + { + startIndex = 0; + } + + int strLength = str.Length; + + // String.Substring(int, int) accepts startIndex==length + if (startIndex > strLength) + { + return String.Empty; + } + + length = Math.Min(length, strLength - startIndex); + return length >= 0 + ? str.Substring(startIndex, length) + : String.Empty; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapper.cs new file mode 100644 index 0000000..c90c86f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapper.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Newtonsoft.Json; +[module: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Extra needed to workaorund EF issue with expression shape.")] + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Represents a container class that contains properties that are grouped by using $apply. + /// + public abstract class DynamicTypeWrapper + { + /// + /// Gets values stored in the wrapper + /// + public abstract Dictionary Values { get; } + + /// + /// Attempts to get the value of the Property called from the underlying Entity. + /// + /// The name of the Property + /// The new value of the Property + /// True if successful + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Generics not appropriate here")] + public bool TryGetPropertyValue(string propertyName, out object value) + { + return this.Values.TryGetValue(propertyName, out value); + } + } + + [JsonConverter(typeof(DynamicTypeWrapperConverter))] + internal class GroupByWrapper : DynamicTypeWrapper + { + private Dictionary _values; + protected static readonly IPropertyMapper DefaultPropertyMapper = new IdentityPropertyMapper(); + + /// + /// Gets or sets the property container that contains the properties being expanded. + /// + public virtual AggregationPropertyContainer GroupByContainer { get; set; } + + /// + /// Gets or sets the property container that contains the properties being expanded. + /// + public virtual AggregationPropertyContainer Container { get; set; } + + public override Dictionary Values + { + get + { + EnsureValues(); + return this._values; + } + } + + /// + public override bool Equals(object obj) + { + var compareWith = obj as GroupByWrapper; + if (compareWith == null) + { + return false; + } + var dictionary1 = this.Values; + var dictionary2 = compareWith.Values; + return dictionary1.Count() == dictionary2.Count() && !dictionary1.Except(dictionary2).Any(); + } + + /// + public override int GetHashCode() + { + EnsureValues(); + long hash = 1870403278L; //Arbitrary number from Anonymous Type GetHashCode implementation + foreach (var v in this.Values.Values) + { + hash = (hash * -1521134295L) + (v == null ? 0 : v.GetHashCode()); + } + + return (int)hash; + } + + private void EnsureValues() + { + if (_values == null) + { + if (this.GroupByContainer != null) + { + this._values = this.GroupByContainer.ToDictionary(DefaultPropertyMapper); + } + else + { + this._values = new Dictionary(); + } + + if (this.Container != null) + { + _values = _values.Concat(this.Container.ToDictionary(DefaultPropertyMapper)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + } + } + } + + internal class NoGroupByWrapper : GroupByWrapper + { + } + + internal class AggregationWrapper : GroupByWrapper + { + } + + internal class NoGroupByAggregationWrapper : GroupByWrapper + { + } + + internal class EntitySetAggregationWrapper : GroupByWrapper + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapperConverter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapperConverter.cs new file mode 100644 index 0000000..6bf0bdc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DynamicTypeWrapperConverter.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Newtonsoft.Json; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Represents a custom to serialize instances to JSON. + /// + internal class DynamicTypeWrapperConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + if (objectType == null) + { + throw Error.ArgumentNull("objectType"); + } + + return objectType.IsAssignableFrom(typeof(DynamicTypeWrapper)); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + Contract.Assert(false, "DynamicTypeWrapper is internal and should never be deserialized into."); + throw new NotImplementedException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + DynamicTypeWrapper dynamicTypeWrapper = value as DynamicTypeWrapper; + if (dynamicTypeWrapper != null) + { + serializer.Serialize(writer, dynamicTypeWrapper.Values); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ExpressionBinderBase.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ExpressionBinderBase.cs new file mode 100644 index 0000000..9517c7d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ExpressionBinderBase.cs @@ -0,0 +1,1083 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. +using System.Data.Linq; +#endif +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Xml.Linq; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// The base class for all expression binders. + /// + public abstract class ExpressionBinderBase + { + internal static readonly MethodInfo StringCompareMethodInfo = typeof(string).GetMethod("Compare", new[] { typeof(string), typeof(string), typeof(StringComparison) }); + internal static readonly MethodInfo GuidCompareMethodInfo = typeof(ExpressionBinderBase).GetMethod("GuidCompare", new[] { typeof(Guid), typeof(Guid) }); + internal static readonly string DictionaryStringObjectIndexerName = typeof(Dictionary).GetDefaultMembers()[0].Name; + + internal static readonly Expression NullConstant = Expression.Constant(null); + internal static readonly Expression FalseConstant = Expression.Constant(false); + internal static readonly Expression TrueConstant = Expression.Constant(true); + internal static readonly Expression ZeroConstant = Expression.Constant(0); + internal static readonly Expression OrdinalStringComparisonConstant = Expression.Constant(StringComparison.Ordinal); + + internal static readonly MethodInfo EnumTryParseMethod = typeof(Enum).GetMethods() + .Single(m => m.Name == "TryParse" && m.GetParameters().Length == 2); + + internal static readonly Dictionary BinaryOperatorMapping = new Dictionary + { + { BinaryOperatorKind.Add, ExpressionType.Add }, + { BinaryOperatorKind.And, ExpressionType.AndAlso }, + { BinaryOperatorKind.Divide, ExpressionType.Divide }, + { BinaryOperatorKind.Equal, ExpressionType.Equal }, + { BinaryOperatorKind.GreaterThan, ExpressionType.GreaterThan }, + { BinaryOperatorKind.GreaterThanOrEqual, ExpressionType.GreaterThanOrEqual }, + { BinaryOperatorKind.LessThan, ExpressionType.LessThan }, + { BinaryOperatorKind.LessThanOrEqual, ExpressionType.LessThanOrEqual }, + { BinaryOperatorKind.Modulo, ExpressionType.Modulo }, + { BinaryOperatorKind.Multiply, ExpressionType.Multiply }, + { BinaryOperatorKind.NotEqual, ExpressionType.NotEqual }, + { BinaryOperatorKind.Or, ExpressionType.OrElse }, + { BinaryOperatorKind.Subtract, ExpressionType.Subtract }, + }; + + internal IEdmModel Model { get; set; } + + internal ODataQuerySettings QuerySettings { get; set; } + + internal IWebApiAssembliesResolver InternalAssembliesResolver { get; set; } + + /// + /// Base query used for the binder. + /// + internal IQueryable BaseQuery; + + /// + /// Flattened list of properties from base query, for case when binder is applied for aggregated query. + /// + internal IDictionary FlattenedPropertyContainer; + + /// + /// Initializes a new instance of the class. + /// + /// The request container. + protected ExpressionBinderBase(IServiceProvider requestContainer) + { + Contract.Assert(requestContainer != null); + + QuerySettings = requestContainer.GetRequiredService(); + Model = requestContainer.GetRequiredService(); + + // The IWebApiAssembliesResolver service is internal and can only be injected by WebApi. + // This code path may be used in the cases when the service container available + // but may not contain an instance of IWebApiAssembliesResolver. + IWebApiAssembliesResolver injectedResolver = requestContainer.GetService(); + InternalAssembliesResolver = (injectedResolver != null) ? injectedResolver : WebApiAssembliesResolver.Default; + } + + internal ExpressionBinderBase(IEdmModel model, IWebApiAssembliesResolver assembliesResolver, ODataQuerySettings querySettings) + : this(model, querySettings) + { + InternalAssembliesResolver = assembliesResolver; + } + + internal ExpressionBinderBase(IEdmModel model, ODataQuerySettings querySettings) + { + Contract.Assert(model != null); + Contract.Assert(querySettings != null); + Contract.Assert(querySettings.HandleNullPropagation != HandleNullPropagationOption.Default); + + QuerySettings = querySettings; + Model = model; + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These are simple conversion function and cannot be split up.")] + internal Expression CreateBinaryExpression(BinaryOperatorKind binaryOperator, Expression left, Expression right, bool liftToNull) + { + ExpressionType binaryExpressionType; + + // When comparing an enum to a string, parse the string, convert both to the enum underlying type, and compare the values + // When comparing an enum to an enum with the same type, convert both to the underlying type, and compare the values + Type leftUnderlyingType = Nullable.GetUnderlyingType(left.Type) ?? left.Type; + Type rightUnderlyingType = Nullable.GetUnderlyingType(right.Type) ?? right.Type; + + // Convert to integers unless Enum type is required + if ((TypeHelper.IsEnum(leftUnderlyingType) || TypeHelper.IsEnum(rightUnderlyingType)) && binaryOperator != BinaryOperatorKind.Has) + { + Type enumType = TypeHelper.IsEnum(leftUnderlyingType) ? leftUnderlyingType : rightUnderlyingType; + Type enumUnderlyingType = Enum.GetUnderlyingType(enumType); + left = ConvertToEnumUnderlyingType(left, enumType, enumUnderlyingType); + right = ConvertToEnumUnderlyingType(right, enumType, enumUnderlyingType); + } + + if (leftUnderlyingType == typeof(DateTime) && rightUnderlyingType == typeof(DateTimeOffset)) + { + right = DateTimeOffsetToDateTime(right); + } + else if (rightUnderlyingType == typeof(DateTime) && leftUnderlyingType == typeof(DateTimeOffset)) + { + left = DateTimeOffsetToDateTime(left); + } + + if ((IsDateOrOffset(leftUnderlyingType) && IsDate(rightUnderlyingType)) || + (IsDate(leftUnderlyingType) && IsDateOrOffset(rightUnderlyingType))) + { + left = CreateDateBinaryExpression(left); + right = CreateDateBinaryExpression(right); + } + + if ((IsDateOrOffset(leftUnderlyingType) && IsTimeOfDay(rightUnderlyingType)) || + (IsTimeOfDay(leftUnderlyingType) && IsDateOrOffset(rightUnderlyingType)) || + (IsTimeSpan(leftUnderlyingType) && IsTimeOfDay(rightUnderlyingType)) || + (IsTimeOfDay(leftUnderlyingType) && IsTimeSpan(rightUnderlyingType))) + { + left = CreateTimeBinaryExpression(left); + right = CreateTimeBinaryExpression(right); + } + + if (left.Type != right.Type) + { + // one of them must be nullable and the other is not. + left = ToNullable(left); + right = ToNullable(right); + } + + if ((left.Type == typeof(Guid) || right.Type == typeof(Guid))) + { + left = ConvertNull(left, typeof(Guid)); + right = ConvertNull(right, typeof(Guid)); + + switch (binaryOperator) + { + case BinaryOperatorKind.GreaterThan: + case BinaryOperatorKind.GreaterThanOrEqual: + case BinaryOperatorKind.LessThan: + case BinaryOperatorKind.LessThanOrEqual: + left = Expression.Call(GuidCompareMethodInfo, left, right); + right = ZeroConstant; + break; + default: + break; + } + } + + if (left.Type == typeof(string) || right.Type == typeof(string)) + { + // convert nulls of type object to nulls of type string to make the String.Compare call work + left = ConvertNull(left, typeof(string)); + right = ConvertNull(right, typeof(string)); + + // Use string.Compare instead of comparison for gt, ge, lt, le between two strings since direct comparisons are not supported + switch (binaryOperator) + { + case BinaryOperatorKind.GreaterThan: + case BinaryOperatorKind.GreaterThanOrEqual: + case BinaryOperatorKind.LessThan: + case BinaryOperatorKind.LessThanOrEqual: + left = Expression.Call(StringCompareMethodInfo, left, right, OrdinalStringComparisonConstant); + right = ZeroConstant; + break; + default: + break; + } + } + + if (BinaryOperatorMapping.TryGetValue(binaryOperator, out binaryExpressionType)) + { + if (left.Type == typeof(byte[]) || right.Type == typeof(byte[])) + { + left = ConvertNull(left, typeof(byte[])); + right = ConvertNull(right, typeof(byte[])); + + switch (binaryExpressionType) + { + case ExpressionType.Equal: + return Expression.MakeBinary(binaryExpressionType, left, right, liftToNull, method: Linq2ObjectsComparisonMethods.AreByteArraysEqualMethodInfo); + case ExpressionType.NotEqual: + return Expression.MakeBinary(binaryExpressionType, left, right, liftToNull, method: Linq2ObjectsComparisonMethods.AreByteArraysNotEqualMethodInfo); + default: + IEdmPrimitiveType binaryType = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(byte[])); + throw new ODataException(Error.Format(SRResources.BinaryOperatorNotSupported, binaryType.FullName(), binaryType.FullName(), binaryOperator)); + } + } + else + { + return Expression.MakeBinary(binaryExpressionType, left, right, liftToNull, method: null); + } + } + else + { + // Enum has a "has" operator + // {(c1, c2) => c1.HasFlag(Convert(c2))} + if (TypeHelper.IsEnum(left.Type) && TypeHelper.IsEnum(right.Type) && binaryOperator == BinaryOperatorKind.Has) + { + UnaryExpression flag = Expression.Convert(right, typeof(Enum)); + return BindHas(left, flag); + } + else + { + throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, binaryOperator, typeof(FilterBinder).Name); + } + } + } + + internal Expression CreateConvertExpression(ConvertNode convertNode, Expression source) + { + Type conversionType = EdmLibHelpers.GetClrType(convertNode.TypeReference, Model, InternalAssembliesResolver); + + if (conversionType == typeof(bool?) && source.Type == typeof(bool)) + { + // we handle null propagation ourselves. So, if converting from bool to Nullable ignore. + return source; + } + else if (conversionType == typeof(Date?) && + (source.Type == typeof(DateTimeOffset?) || source.Type == typeof(DateTime?))) + { + return source; + } + if ((conversionType == typeof(TimeOfDay?) && source.Type == typeof(TimeOfDay)) || + ((conversionType == typeof(Date?) && source.Type == typeof(Date)))) + { + return source; + } + else if (conversionType == typeof(TimeOfDay?) && + (source.Type == typeof(DateTimeOffset?) || source.Type == typeof(DateTime?) || source.Type == typeof(TimeSpan?))) + { + return source; + } + else if (IsDateAndTimeRelated(conversionType) && IsDateAndTimeRelated(source.Type)) + { + return source; + } + else if (source == NullConstant) + { + return source; + } + else + { + if (TypeHelper.IsEnum(source.Type)) + { + // we handle enum conversions ourselves + return source; + } + else + { + // if a cast is from Nullable to Non-Nullable we need to check if source is null + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True + && IsNullable(source.Type) && !IsNullable(conversionType)) + { + // source == null ? null : source.Value + return + Expression.Condition( + test: CheckForNull(source), + ifTrue: Expression.Constant(null, ToNullable(conversionType)), + ifFalse: Expression.Convert(ExtractValueFromNullableExpression(source), ToNullable(conversionType))); + } + else + { + return Expression.Convert(source, conversionType); + } + } + } + } + + // If the expression is of non-standard edm primitive type (like uint), convert the expression to its standard edm type. + // Also, note that only expressions generated for ushort, uint and ulong can be understood by linq2sql and EF. + // The rest (char, char[], Binary) would cause issues with linq2sql and EF. + internal Expression ConvertNonStandardPrimitives(Expression source) + { + bool isNonstandardEdmPrimitive; + Type conversionType = EdmLibHelpers.IsNonstandardEdmPrimitive(source.Type, out isNonstandardEdmPrimitive); + + if (isNonstandardEdmPrimitive) + { + Type sourceType = TypeHelper.GetUnderlyingTypeOrSelf(source.Type); + + Contract.Assert(sourceType != conversionType); + + Expression convertedExpression = null; + + if (TypeHelper.IsEnum(sourceType)) + { + // we handle enum conversions ourselves + convertedExpression = source; + } + else + { + switch (Type.GetTypeCode(sourceType)) + { + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + convertedExpression = Expression.Convert(ExtractValueFromNullableExpression(source), conversionType); + break; + + case TypeCode.Char: + convertedExpression = Expression.Call(ExtractValueFromNullableExpression(source), "ToString", typeArguments: null, arguments: null); + break; + + case TypeCode.DateTime: + convertedExpression = source; + break; + + case TypeCode.Object: + if (sourceType == typeof(char[])) + { + convertedExpression = Expression.New(typeof(string).GetConstructor(new[] { typeof(char[]) }), source); + } + else if (sourceType == typeof(XElement)) + { + convertedExpression = Expression.Call(source, "ToString", typeArguments: null, arguments: null); + } +#if NETFX // System.Data.Linq.Binary is only supported in the AspNet version. + else if (sourceType == typeof(Binary)) + { + convertedExpression = Expression.Call(source, "ToArray", typeArguments: null, arguments: null); + } +#endif + break; + + default: + Contract.Assert(false, Error.Format("missing non-standard type support for {0}", sourceType.Name)); + break; + } + } + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type)) + { + // source == null ? null : source + return Expression.Condition( + CheckForNull(source), + ifTrue: Expression.Constant(null, ToNullable(convertedExpression.Type)), + ifFalse: ToNullable(convertedExpression)); + } + else + { + return convertedExpression; + } + } + + return source; + } + + internal Expression MakePropertyAccess(PropertyInfo propertyInfo, Expression argument) + { + Expression propertyArgument = argument; + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // we don't have to check if the argument is null inside the function call as we do it already + // before calling the function. So remove the redundant null checks. + propertyArgument = RemoveInnerNullPropagation(argument); + } + + // if the argument is of type Nullable, then translate the argument to Nullable.Value as none + // of the canonical functions have overloads for Nullable<> arguments. + propertyArgument = ExtractValueFromNullableExpression(propertyArgument); + + return Expression.Property(propertyArgument, propertyInfo); + } + + // creates an expression for the corresponding OData function. + internal Expression MakeFunctionCall(MemberInfo member, params Expression[] arguments) + { + Contract.Assert(member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method); + + IEnumerable functionCallArguments = arguments; + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // we don't have to check if the argument is null inside the function call as we do it already + // before calling the function. So remove the redundant null checks. + functionCallArguments = arguments.Select(a => RemoveInnerNullPropagation(a)); + } + + // if the argument is of type Nullable, then translate the argument to Nullable.Value as none + // of the canonical functions have overloads for Nullable<> arguments. + functionCallArguments = ExtractValueFromNullableArguments(functionCallArguments); + + Expression functionCall; + if (member.MemberType == MemberTypes.Method) + { + MethodInfo method = member as MethodInfo; + if (method.IsStatic) + { + functionCall = Expression.Call(null, method, functionCallArguments); + } + else + { + functionCall = Expression.Call(functionCallArguments.First(), method, functionCallArguments.Skip(1)); + } + } + else + { + // property + functionCall = Expression.Property(functionCallArguments.First(), member as PropertyInfo); + } + + return CreateFunctionCallWithNullPropagation(functionCall, arguments); + } + + internal Expression CreateFunctionCallWithNullPropagation(Expression functionCall, Expression[] arguments) + { + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + Expression test = CheckIfArgumentsAreNull(arguments); + + if (test == FalseConstant) + { + // none of the arguments are/can be null. + // so no need to do any null propagation + return functionCall; + } + else + { + // if one of the arguments is null, result is null (not defined) + return + Expression.Condition( + test: test, + ifTrue: Expression.Constant(null, ToNullable(functionCall.Type)), + ifFalse: ToNullable(functionCall)); + } + } + else + { + return functionCall; + } + } + + // we don't have to do null checks inside the function for arguments as we do the null checks before calling + // the function when null propagation is enabled. + // this method converts back "arg == null ? null : convert(arg)" to "arg" + // Also, note that we can do this generically only because none of the odata functions that we support can take null + // as an argument. + internal Expression RemoveInnerNullPropagation(Expression expression) + { + Contract.Assert(expression != null); + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // only null propagation generates conditional expressions + if (expression.NodeType == ExpressionType.Conditional) + { + // make sure to skip the DateTime IFF clause + ConditionalExpression conditionalExpr = (ConditionalExpression)expression; + if (conditionalExpr.Test.NodeType != ExpressionType.OrElse) + { + expression = conditionalExpr.IfFalse; + Contract.Assert(expression != null); + + if (expression.NodeType == ExpressionType.Convert) + { + UnaryExpression unaryExpression = expression as UnaryExpression; + Contract.Assert(unaryExpression != null); + + if (Nullable.GetUnderlyingType(unaryExpression.Type) == unaryExpression.Operand.Type) + { + // this is a cast from T to Nullable which is redundant. + expression = unaryExpression.Operand; + } + } + } + } + } + + return expression; + } + + internal string GetFullPropertyPath(SingleValueNode node) + { + string path = null; + SingleValueNode parent = null; + switch (node.Kind) + { + case QueryNodeKind.SingleComplexNode: + path = ((SingleComplexNode)node).Property.Name; + break; + case QueryNodeKind.SingleValuePropertyAccess: + var propertyNode = ((SingleValuePropertyAccessNode)node); + path = propertyNode.Property.Name; + parent = propertyNode.Source; + break; + case QueryNodeKind.SingleNavigationNode: + var navNode = ((SingleNavigationNode)node); + path = navNode.NavigationProperty.Name; + parent = navNode.Source; + break; + } + + if (parent != null) + { + var parentPath = GetFullPropertyPath(parent); + if (parentPath != null) + { + path = parentPath + "\\" + path; + } + } + + return path; + } + + /// + /// Gets property for dynamic properties dictionary. + /// + /// + /// Returns CLR property for dynamic properties container. + protected PropertyInfo GetDynamicPropertyContainer(SingleValueOpenPropertyAccessNode openNode) + { + IEdmStructuredType edmStructuredType; + IEdmTypeReference edmTypeReference = openNode.Source.TypeReference; + if (edmTypeReference.IsEntity()) + { + edmStructuredType = edmTypeReference.AsEntity().EntityDefinition(); + } + else if (edmTypeReference.IsComplex()) + { + edmStructuredType = edmTypeReference.AsComplex().ComplexDefinition(); + } + else + { + throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, openNode.Kind, typeof(FilterBinder).Name); + } + + return EdmLibHelpers.GetDynamicPropertyDictionary(edmStructuredType, Model); + } + + private static Expression CheckIfArgumentsAreNull(Expression[] arguments) + { + if (arguments.Any(arg => arg == NullConstant)) + { + return TrueConstant; + } + + arguments = + arguments + .Select(arg => CheckForNull(arg)) + .Where(arg => arg != null) + .ToArray(); + + if (arguments.Any()) + { + return arguments + .Aggregate((left, right) => Expression.OrElse(left, right)); + } + else + { + return FalseConstant; + } + } + + internal static Expression CheckForNull(Expression expression) + { + if (IsNullable(expression.Type) && expression.NodeType != ExpressionType.Constant) + { + return Expression.Equal(expression, Expression.Constant(null)); + } + else + { + return null; + } + } + + private static IEnumerable ExtractValueFromNullableArguments(IEnumerable arguments) + { + return arguments.Select(arg => ExtractValueFromNullableExpression(arg)); + } + + internal static Expression ExtractValueFromNullableExpression(Expression source) + { + return Nullable.GetUnderlyingType(source.Type) != null ? Expression.Property(source, "Value") : source; + } + + internal Expression BindHas(Expression left, Expression flag) + { + Contract.Assert(TypeHelper.IsEnum(left.Type)); + Contract.Assert(flag.Type == typeof(Enum)); + + Expression[] arguments = new[] { left, flag }; + return MakeFunctionCall(ClrCanonicalFunctions.HasFlag, arguments); + } + + /// + /// Analyze previous query and extract grouped properties. + /// + /// + protected void EnsureFlattenedPropertyContainer(ParameterExpression source) + { + if (this.BaseQuery != null) + { + this.FlattenedPropertyContainer = this.FlattenedPropertyContainer ?? this.GetFlattenedProperties(source); + } + } + + internal IDictionary GetFlattenedProperties(ParameterExpression source) + { + if (this.BaseQuery == null) + { + return null; + } + + if (!typeof(GroupByWrapper).IsAssignableFrom(BaseQuery.ElementType)) + { + return null; + } + + var expression = BaseQuery.Expression as MethodCallExpression; + if (expression == null) + { + return null; + } + + // After $apply we could have other clauses, like $filter, $orderby etc. + // Skip of filter expressions + while (expression.Method.Name == "Where") + { + expression = expression.Arguments.FirstOrDefault() as MethodCallExpression; + } + + if (expression == null) + { + return null; + } + + var result = new Dictionary(); + CollectAssigments(result, Expression.Property(source, "GroupByContainer"), ExtractContainerExpression(expression.Arguments.FirstOrDefault() as MethodCallExpression, "GroupByContainer")); + CollectAssigments(result, Expression.Property(source, "Container"), ExtractContainerExpression(expression, "Container")); + + return result; + } + + private static MemberInitExpression ExtractContainerExpression(MethodCallExpression expression, string containerName) + { + var memberInitExpression = ((expression.Arguments[1] as UnaryExpression).Operand as LambdaExpression).Body as MemberInitExpression; + if (memberInitExpression != null) + { + var containerAssigment = memberInitExpression.Bindings.FirstOrDefault(m => m.Member.Name == containerName) as MemberAssignment; + if (containerAssigment != null) + { + return containerAssigment.Expression as MemberInitExpression; + } + } + return null; + } + + private static void CollectAssigments(IDictionary flattenPropertyContainer, Expression source, MemberInitExpression expression, string prefix = null) + { + if (expression == null) + { + return; + } + + string nameToAdd = null; + Type resultType = null; + MemberInitExpression nextExpression = null; + Expression nestedExpression = null; + foreach (var expr in expression.Bindings.OfType()) + { + var initExpr = expr.Expression as MemberInitExpression; + if (initExpr != null && expr.Member.Name == "Next") + { + nextExpression = initExpr; + } + else if (expr.Member.Name == "Name") + { + nameToAdd = (expr.Expression as ConstantExpression).Value as string; + } + else if (expr.Member.Name == "Value" || expr.Member.Name == "NestedValue") + { + resultType = expr.Expression.Type; + if (resultType == typeof(object) && expr.Expression.NodeType == ExpressionType.Convert) + { + resultType = ((UnaryExpression)expr.Expression).Operand.Type; + } + + if (typeof(GroupByWrapper).IsAssignableFrom(resultType)) + { + nestedExpression = expr.Expression; + } + } + } + + if (prefix != null) + { + nameToAdd = prefix + "\\" + nameToAdd; + } + + if (typeof(GroupByWrapper).IsAssignableFrom(resultType)) + { + flattenPropertyContainer.Add(nameToAdd, Expression.Property(source, "NestedValue")); + } + else + { + flattenPropertyContainer.Add(nameToAdd, Expression.Convert(Expression.Property(source, "Value"), resultType)); + } + + if (nextExpression != null) + { + CollectAssigments(flattenPropertyContainer, Expression.Property(source, "Next"), nextExpression, prefix); + } + + if (nestedExpression != null) + { + var nestedAccessor = ((nestedExpression as MemberInitExpression).Bindings.First() as MemberAssignment).Expression as MemberInitExpression; + var newSource = Expression.Property(Expression.Property(source, "NestedValue"), "GroupByContainer"); + CollectAssigments(flattenPropertyContainer, newSource, nestedAccessor, nameToAdd); + } + } + + /// + /// Gets expression for property from previously aggregated query + /// + /// + /// Returns null if no aggregations were used so far + protected Expression GetFlattenedPropertyExpression(string propertyPath) + { + if (FlattenedPropertyContainer == null) + { + return null; + } + + Expression expression; + if (FlattenedPropertyContainer.TryGetValue(propertyPath, out expression)) + { + return expression; + } + + throw new ODataException(Error.Format(SRResources.PropertyOrPathWasRemovedFromContext, propertyPath)); + } + + private Expression GetProperty(Expression source, string propertyName) + { + if (IsDateOrOffset(source.Type)) + { + if (IsDateTime(source.Type)) + { + return MakePropertyAccess(ClrCanonicalFunctions.DateTimeProperties[propertyName], source); + } + else + { + return MakePropertyAccess(ClrCanonicalFunctions.DateTimeOffsetProperties[propertyName], source); + } + } + else if (IsDate(source.Type)) + { + return MakePropertyAccess(ClrCanonicalFunctions.DateProperties[propertyName], source); + } + else if (IsTimeOfDay(source.Type)) + { + return MakePropertyAccess(ClrCanonicalFunctions.TimeOfDayProperties[propertyName], source); + } + else if (IsTimeSpan(source.Type)) + { + return MakePropertyAccess(ClrCanonicalFunctions.TimeSpanProperties[propertyName], source); + } + + return source; + } + + private Expression CreateDateBinaryExpression(Expression source) + { + source = ConvertToDateTimeRelatedConstExpression(source); + + // Year, Month, Day + Expression year = GetProperty(source, ClrCanonicalFunctions.YearFunctionName); + Expression month = GetProperty(source, ClrCanonicalFunctions.MonthFunctionName); + Expression day = GetProperty(source, ClrCanonicalFunctions.DayFunctionName); + + // return (year * 10000 + month * 100 + day) + Expression result = + Expression.Add( + Expression.Add(Expression.Multiply(year, Expression.Constant(10000)), + Expression.Multiply(month, Expression.Constant(100))), day); + + return CreateFunctionCallWithNullPropagation(result, new[] { source }); + } + + private Expression CreateTimeBinaryExpression(Expression source) + { + source = ConvertToDateTimeRelatedConstExpression(source); + + // Hour, Minute, Second, Millisecond + Expression hour = GetProperty(source, ClrCanonicalFunctions.HourFunctionName); + Expression minute = GetProperty(source, ClrCanonicalFunctions.MinuteFunctionName); + Expression second = GetProperty(source, ClrCanonicalFunctions.SecondFunctionName); + Expression milliSecond = GetProperty(source, ClrCanonicalFunctions.MillisecondFunctionName); + + Expression hourTicks = Expression.Multiply(Expression.Convert(hour, typeof(long)), Expression.Constant(TimeOfDay.TicksPerHour)); + Expression minuteTicks = Expression.Multiply(Expression.Convert(minute, typeof(long)), Expression.Constant(TimeOfDay.TicksPerMinute)); + Expression secondTicks = Expression.Multiply(Expression.Convert(second, typeof(long)), Expression.Constant(TimeOfDay.TicksPerSecond)); + + // return (hour * TicksPerHour + minute * TicksPerMinute + second * TicksPerSecond + millisecond) + Expression result = Expression.Add(hourTicks, Expression.Add(minuteTicks, Expression.Add(secondTicks, Expression.Convert(milliSecond, typeof(long))))); + + return CreateFunctionCallWithNullPropagation(result, new[] { source }); + } + + private static Expression ConvertToDateTimeRelatedConstExpression(Expression source) + { + var parameterizedConstantValue = ExtractParameterizedConstant(source); + if (parameterizedConstantValue != null && TypeHelper.IsNullable(source.Type)) + { + var dateTimeOffset = parameterizedConstantValue as DateTimeOffset?; + if (dateTimeOffset != null) + { + return Expression.Constant(dateTimeOffset.Value, typeof(DateTimeOffset)); + } + + var dateTime = parameterizedConstantValue as DateTime?; + if (dateTime != null) + { + return Expression.Constant(dateTime.Value, typeof(DateTime)); + } + + var date = parameterizedConstantValue as Date?; + if (date != null) + { + return Expression.Constant(date.Value, typeof(Date)); + } + + var timeOfDay = parameterizedConstantValue as TimeOfDay?; + if (timeOfDay != null) + { + return Expression.Constant(timeOfDay.Value, typeof(TimeOfDay)); + } + } + + return source; + } + + internal static Expression ConvertToEnumUnderlyingType(Expression expression, Type enumType, Type enumUnderlyingType) + { + object parameterizedConstantValue = ExtractParameterizedConstant(expression); + if (parameterizedConstantValue != null) + { + string enumStringValue = parameterizedConstantValue as string; + if (enumStringValue != null) + { + return Expression.Constant( + Convert.ChangeType( + Enum.Parse(enumType, enumStringValue), enumUnderlyingType, CultureInfo.InvariantCulture)); + } + else + { + // enum member value + return Expression.Constant( + Convert.ChangeType( + parameterizedConstantValue, enumUnderlyingType, CultureInfo.InvariantCulture)); + } + } + else if (expression.Type == enumType) + { + return Expression.Convert(expression, enumUnderlyingType); + } + else if (Nullable.GetUnderlyingType(expression.Type) == enumType) + { + return Expression.Convert(expression, typeof(Nullable<>).MakeGenericType(enumUnderlyingType)); + } + else if (expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Value == null) + { + return expression; + } + else + { + throw Error.NotSupported(SRResources.ConvertToEnumFailed, enumType, expression.Type); + } + } + + // Extract the constant that would have been encapsulated into LinqParameterContainer if this + // expression represents it else return null. + internal static object ExtractParameterizedConstant(Expression expression) + { + if (expression.NodeType == ExpressionType.MemberAccess) + { + MemberExpression memberAccess = expression as MemberExpression; + Contract.Assert(memberAccess != null); + + PropertyInfo propertyInfo = memberAccess.Member as PropertyInfo; + if (propertyInfo != null && propertyInfo.GetMethod.IsStatic) + { + return propertyInfo.GetValue(new object()); + } + + if (memberAccess.Expression.NodeType == ExpressionType.Constant) + { + ConstantExpression constant = memberAccess.Expression as ConstantExpression; + Contract.Assert(constant != null); + Contract.Assert(constant.Value != null); + LinqParameterContainer value = constant.Value as LinqParameterContainer; + Contract.Assert(value != null, "Constants are already embedded into LinqParameterContainer"); + + return value.Property; + } + } + + return null; + } + + internal static Expression DateTimeOffsetToDateTime(Expression expression) + { + var unaryExpression = expression as UnaryExpression; + if (unaryExpression != null) + { + if (Nullable.GetUnderlyingType(unaryExpression.Type) == unaryExpression.Operand.Type) + { + // this is a cast from T to Nullable which is redundant. + expression = unaryExpression.Operand; + } + } + var parameterizedConstantValue = ExtractParameterizedConstant(expression); + var dto = parameterizedConstantValue as DateTimeOffset?; + if (dto != null) + { + expression = Expression.Constant(EdmPrimitiveHelpers.ConvertPrimitiveValue(dto.Value, typeof(DateTime))); + } + return expression; + } + + internal static bool IsNullable(Type t) + { + if (!TypeHelper.IsValueType(t) || (TypeHelper.IsGenericType(t) && t.GetGenericTypeDefinition() == typeof(Nullable<>))) + { + return true; + } + + return false; + } + + internal static Type ToNullable(Type t) + { + if (IsNullable(t)) + { + return t; + } + else + { + return typeof(Nullable<>).MakeGenericType(t); + } + } + + internal static Expression ToNullable(Expression expression) + { + if (!IsNullable(expression.Type)) + { + return Expression.Convert(expression, ToNullable(expression.Type)); + } + + return expression; + } + + internal static bool IsIQueryable(Type type) + { + return typeof(IQueryable).IsAssignableFrom(type); + } + + internal static bool IsDoubleOrDecimal(Type type) + { + return IsType(type) || IsType(type); + } + + internal static bool IsDateAndTimeRelated(Type type) + { + return IsType(type) || + IsType(type) || + IsType(type) || + IsType(type) || + IsType(type); + } + + internal static bool IsDateRelated(Type type) + { + return IsType(type) || IsType(type) || IsType(type); + } + + internal static bool IsTimeRelated(Type type) + { + return IsType(type) || IsType(type) || IsType(type) || IsType(type); + } + + internal static bool IsDateOrOffset(Type type) + { + return IsType(type) || IsType(type); + } + + internal static bool IsDateTime(Type type) + { + return IsType(type); + } + + internal static bool IsTimeSpan(Type type) + { + return IsType(type); + } + + internal static bool IsTimeOfDay(Type type) + { + return IsType(type); + } + + internal static bool IsDate(Type type) + { + return IsType(type); + } + + internal static bool IsInteger(Type type) + { + return IsType(type) || IsType(type) || IsType(type); + } + + internal static bool IsType(Type type) where T : struct + { + return type == typeof(T) || type == typeof(T?); + } + + internal static Expression ConvertNull(Expression expression, Type type) + { + ConstantExpression constantExpression = expression as ConstantExpression; + if (constantExpression != null && constantExpression.Value == null) + { + return Expression.Constant(null, type); + } + else + { + return expression; + } + } + + /// + /// Compares two guids + /// + /// + /// + /// An integer value based on the Guid's CompareTo method + public static int GuidCompare(Guid firstValue, Guid secondValue) + { + if (firstValue != null) + { + return firstValue.CompareTo(secondValue); + } + + if (secondValue != null) + { + return (-1) * secondValue.CompareTo(firstValue); + } + + return 0; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs new file mode 100644 index 0000000..7d61f5e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/FilterBinder.cs @@ -0,0 +1,1693 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Translates an OData $filter parse tree represented by to + /// an and applies it to an . + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")] + public class FilterBinder : ExpressionBinderBase + { + private const string ODataItParameterName = "$it"; + + private Stack> _parametersStack = new Stack>(); + private Dictionary _lambdaParameters; + private Type _filterType; + + /// + /// Initializes a new instance of the class. + /// + /// The request container. + public FilterBinder(IServiceProvider requestContainer) + : base(requestContainer) + { + } + + internal FilterBinder(ODataQuerySettings settings, IWebApiAssembliesResolver assembliesResolver, IEdmModel model) + : base(model, assembliesResolver, settings) + { + } + + internal static Expression Bind( + IQueryable baseQuery, + FilterClause filterClause, + Type filterType, + ODataQueryContext context, + ODataQuerySettings querySettings) + { + if (filterClause == null) + { + throw Error.ArgumentNull("filterClause"); + } + if (filterType == null) + { + throw Error.ArgumentNull("filterType"); + } + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + FilterBinder binder = GetOrCreateFilterBinder(context, querySettings); + + binder._filterType = filterType; + binder.BaseQuery = baseQuery; + + return BindFilterClause(binder, filterClause, filterType); + } + + internal static LambdaExpression Bind( + IQueryable baseQuery, + OrderByClause orderBy, + Type elementType, + ODataQueryContext context, + ODataQuerySettings querySettings) + { + Contract.Assert(orderBy != null); + Contract.Assert(elementType != null); + Contract.Assert(context != null); + + FilterBinder binder = GetOrCreateFilterBinder(context, querySettings); + + binder._filterType = elementType; + binder.BaseQuery = baseQuery; + + return BindOrderByClause(binder, orderBy, elementType); + } + + private static FilterBinder GetOrCreateFilterBinder(ODataQueryContext context, ODataQuerySettings querySettings) + { + FilterBinder binder = null; + if (context.RequestContainer != null) + { + binder = context.RequestContainer.GetRequiredService(); + if (binder != null && binder.Model != context.Model && binder.Model == EdmCoreModel.Instance) + { + binder.Model = context.Model; + } + } + + return binder ?? new FilterBinder(querySettings, WebApiAssembliesResolver.Default, context.Model); + } + + #region For testing purposes only. + + private FilterBinder( + IEdmModel model, + IWebApiAssembliesResolver assembliesResolver, + ODataQuerySettings querySettings, + Type filterType) + : base(model, assembliesResolver, querySettings) + { + _filterType = filterType; + } + + internal static Expression> Bind(FilterClause filterClause, IEdmModel model, + IWebApiAssembliesResolver assembliesResolver, ODataQuerySettings querySettings) + { + return Bind(filterClause, typeof(TEntityType), model, assembliesResolver, querySettings) as Expression>; + } + + internal static Expression Bind(FilterClause filterClause, Type filterType, IEdmModel model, + IWebApiAssembliesResolver assembliesResolver, ODataQuerySettings querySettings) + { + if (filterClause == null) + { + throw Error.ArgumentNull("filterClause"); + } + if (filterType == null) + { + throw Error.ArgumentNull("filterType"); + } + if (model == null) + { + throw Error.ArgumentNull("model"); + } + if (assembliesResolver == null) + { + throw Error.ArgumentNull("assembliesResolver"); + } + + FilterBinder binder = new FilterBinder(model, assembliesResolver, querySettings, filterType); + + return BindFilterClause(binder, filterClause, filterType); + } + + #endregion + + private static LambdaExpression BindFilterClause(FilterBinder binder, FilterClause filterClause, Type filterType) + { + LambdaExpression filter = binder.BindExpression(filterClause.Expression, filterClause.RangeVariable, filterType); + filter = Expression.Lambda(binder.ApplyNullPropagationForFilterBody(filter.Body), filter.Parameters); + + Type expectedFilterType = typeof(Func<,>).MakeGenericType(filterType, typeof(bool)); + if (filter.Type != expectedFilterType) + { + throw Error.Argument("filterType", SRResources.CannotCastFilter, filter.Type.FullName, expectedFilterType.FullName); + } + + return filter; + } + + private static LambdaExpression BindOrderByClause(FilterBinder binder, OrderByClause orderBy, Type elementType) + { + LambdaExpression orderByLambda = binder.BindExpression(orderBy.Expression, orderBy.RangeVariable, elementType); + return orderByLambda; + } + + /// + /// Binds a to create a LINQ that represents the semantics + /// of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression Bind(QueryNode node) + { + // Recursion guard to avoid stack overflows + RuntimeHelpers.EnsureSufficientExecutionStack(); + + CollectionNode collectionNode = node as CollectionNode; + SingleValueNode singleValueNode = node as SingleValueNode; + + if (collectionNode != null) + { + return BindCollectionNode(collectionNode); + } + else if (singleValueNode != null) + { + return BindSingleValueNode(singleValueNode); + } + else + { + throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name); + } + } + + private Expression BindCountNode(CountNode node) + { + Expression source = Bind(node.Source); + Expression countExpression = Expression.Constant(null, typeof(long?)); + Type elementType; + if (!TypeHelper.IsCollection(source.Type, out elementType)) + { + return countExpression; + } + + MethodInfo countMethod; + if (typeof(IQueryable).IsAssignableFrom(source.Type)) + { + countMethod = ExpressionHelperMethods.QueryableCountGeneric.MakeGenericMethod(elementType); + } + else + { + countMethod = ExpressionHelperMethods.EnumerableCountGeneric.MakeGenericMethod(elementType); + } + + // call Count() method. + countExpression = Expression.Call(null, countMethod, new[] { source }); + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // source == null ? null : countExpression + return Expression.Condition( + test: Expression.Equal(source, Expression.Constant(null)), + ifTrue: Expression.Constant(null, typeof(long?)), + ifFalse: ExpressionHelpers.ToNullable(countExpression)); + } + else + { + return countExpression; + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindDynamicPropertyAccessQueryNode(SingleValueOpenPropertyAccessNode openNode) + { + if (EdmLibHelpers.IsDynamicTypeWrapper(_filterType)) + { + return GetFlattenedPropertyExpression(openNode.Name) ?? Expression.Property(Bind(openNode.Source), openNode.Name); + } + PropertyInfo prop = GetDynamicPropertyContainer(openNode); + + var propertyAccessExpression = BindPropertyAccessExpression(openNode, prop); + var readDictionaryIndexerExpression = Expression.Property(propertyAccessExpression, + DictionaryStringObjectIndexerName, Expression.Constant(openNode.Name)); + var containsKeyExpression = Expression.Call(propertyAccessExpression, + propertyAccessExpression.Type.GetMethod("ContainsKey"), Expression.Constant(openNode.Name)); + var nullExpression = Expression.Constant(null); + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + var dynamicDictIsNotNull = Expression.NotEqual(propertyAccessExpression, Expression.Constant(null)); + var dynamicDictIsNotNullAndContainsKey = Expression.AndAlso(dynamicDictIsNotNull, containsKeyExpression); + return Expression.Condition( + dynamicDictIsNotNullAndContainsKey, + readDictionaryIndexerExpression, + nullExpression); + } + else + { + return Expression.Condition( + containsKeyExpression, + readDictionaryIndexerExpression, + nullExpression); + } + } + + private Expression BindPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode, PropertyInfo prop) + { + var source = Bind(openNode.Source); + Expression propertyAccessExpression; + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && + IsNullable(source.Type) && source != _lambdaParameters[ODataItParameterName]) + { + propertyAccessExpression = Expression.Property(RemoveInnerNullPropagation(source), prop.Name); + } + else + { + propertyAccessExpression = Expression.Property(source, prop.Name); + } + return propertyAccessExpression; + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindSingleResourceFunctionCallNode(SingleResourceFunctionCallNode node) + { + switch (node.Name) + { + case ClrCanonicalFunctions.CastFunctionName: + return BindSingleResourceCastFunctionCall(node); + default: + throw Error.NotSupported(SRResources.ODataFunctionNotSupported, node.Name); + } + } + + private Expression BindSingleResourceCastFunctionCall(SingleResourceFunctionCallNode node) + { + Contract.Assert(ClrCanonicalFunctions.CastFunctionName == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + + Contract.Assert(arguments.Length == 2); + + string targetEdmTypeName = (string)((ConstantNode)node.Parameters.Last()).Value; + IEdmType targetEdmType = Model.FindType(targetEdmTypeName); + Type targetClrType = null; + + if (targetEdmType != null) + { + targetClrType = EdmLibHelpers.GetClrType(targetEdmType.ToEdmTypeReference(false), Model); + } + + if (arguments[0].Type == targetClrType) + { + // We only support to cast Entity type to the same type now. + return arguments[0]; + } + else + { + // Cast fails and return null. + return NullConstant; + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindSingleResourceCastNode(SingleResourceCastNode node) + { + IEdmStructuredTypeReference structured = node.StructuredTypeReference; + Contract.Assert(structured != null, "NS casts can contain only structured types"); + + Type clrType = EdmLibHelpers.GetClrType(structured, Model); + + Expression source = BindCastSourceNode(node.Source); + return Expression.TypeAs(source, clrType); + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindCollectionResourceCastNode(CollectionResourceCastNode node) + { + IEdmStructuredTypeReference structured = node.ItemStructuredType; + Contract.Assert(structured != null, "NS casts can contain only structured types"); + + Type clrType = EdmLibHelpers.GetClrType(structured, Model); + + Expression source = BindCastSourceNode(node.Source); + return OfType(source, clrType); + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindCollectionConstantNode(CollectionConstantNode node) + { + // It's fine if the collection is empty; the returned value will be an empty list. + ConstantNode firstNode = node.Collection.FirstOrDefault(); + object value = null; + if (firstNode != null) + { + value = firstNode.Value; + } + + Type constantType = RetrieveClrTypeForConstant(node.ItemType, ref value); + Type listType = typeof(List<>).MakeGenericType(constantType); + IList castedList = Activator.CreateInstance(listType) as IList; + + // Getting a LINQ expression to dynamically cast each item in the Collection during runtime is tricky, + // so using a foreach loop and doing an implicit cast from object to the CLR type of ItemType. + foreach (ConstantNode item in node.Collection) + { + castedList.Add(item.Value); + } + + return Expression.Constant(castedList); + } + + private Expression BindCastSourceNode(QueryNode sourceNode) + { + Expression source; + if (sourceNode == null) + { + // if the cast is on the root i.e $it (~/Products?$filter=NS.PopularProducts/.....), + // source would be null. So bind null to '$it'. + source = _lambdaParameters[ODataItParameterName]; + } + else + { + source = Bind(sourceNode); + } + + return source; + } + + private static Expression OfType(Expression source, Type elementType) + { + Contract.Assert(source != null); + Contract.Assert(elementType != null); + + if (IsIQueryable(source.Type)) + { + return Expression.Call(null, ExpressionHelperMethods.QueryableOfType.MakeGenericMethod(elementType), source); + } + else + { + return Expression.Call(null, ExpressionHelperMethods.EnumerableOfType.MakeGenericMethod(elementType), source); + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node that represents the navigation source. + /// The navigation property to bind. + /// The LINQ created. + public virtual Expression BindNavigationPropertyNode(QueryNode sourceNode, IEdmNavigationProperty navigationProperty) + { + Expression source; + + // TODO: bug in uri parser is causing this property to be null for the root property. + if (sourceNode == null) + { + source = _lambdaParameters[ODataItParameterName]; + } + else + { + source = Bind(sourceNode); + } + + return CreatePropertyAccessExpression(source, navigationProperty); + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindBinaryOperatorNode(BinaryOperatorNode binaryOperatorNode) + { + Expression left = Bind(binaryOperatorNode.Left); + Expression right = Bind(binaryOperatorNode.Right); + + // handle null propagation only if either of the operands can be null + bool isNullPropagationRequired = QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && (IsNullable(left.Type) || IsNullable(right.Type)); + if (isNullPropagationRequired) + { + // |----------------------------------------------------------------| + // |SQL 3VL truth table. | + // |----------------------------------------------------------------| + // |p | q | p OR q | p AND q | p = q | + // |----------------------------------------------------------------| + // |True | True | True | True | True | + // |True | False | True | False | False | + // |True | NULL | True | NULL | NULL | + // |False | True | True | False | False | + // |False | False | False | False | True | + // |False | NULL | NULL | False | NULL | + // |NULL | True | True | NULL | NULL | + // |NULL | False | NULL | False | NULL | + // |NULL | NULL | Null | NULL | NULL | + // |--------|-----------|---------------|---------------|-----------| + + // before we start with null propagation, convert the operators to nullable if already not. + left = ToNullable(left); + right = ToNullable(right); + + bool liftToNull = true; + if (left == NullConstant || right == NullConstant) + { + liftToNull = false; + } + + // Expression trees do a very good job of handling the 3VL truth table if we pass liftToNull true. + return CreateBinaryExpression(binaryOperatorNode.OperatorKind, left, right, liftToNull: liftToNull); + } + else + { + return CreateBinaryExpression(binaryOperatorNode.OperatorKind, left, right, liftToNull: false); + } + } + + /// + /// Binds an to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindInNode(InNode inNode) + { + Expression singleValue = Bind(inNode.Left); + Expression collection = Bind(inNode.Right); + + if (IsIQueryable(collection.Type)) + { + return Expression.Call(null, ExpressionHelperMethods.QueryableContainsGeneric.MakeGenericMethod(singleValue.Type), collection, singleValue); + } + else + { + return Expression.Call(null, ExpressionHelperMethods.EnumerableContainsGeneric.MakeGenericMethod(singleValue.Type), collection, singleValue); + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindConstantNode(ConstantNode constantNode) + { + Contract.Assert(constantNode != null); + + // no need to parameterize null's as there cannot be multiple values for null. + if (constantNode.Value == null) + { + return NullConstant; + } + + object value = constantNode.Value; + Type constantType = RetrieveClrTypeForConstant(constantNode.TypeReference, ref value); + + if (QuerySettings.EnableConstantParameterization) + { + return LinqParameterContainer.Parameterize(constantType, value); + } + else + { + return Expression.Constant(value, constantType); + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindConvertNode(ConvertNode convertNode) + { + Contract.Assert(convertNode != null); + Contract.Assert(convertNode.TypeReference != null); + + Expression source = Bind(convertNode.Source); + + return CreateConvertExpression(convertNode, source); + } + + private LambdaExpression BindExpression(SingleValueNode expression, RangeVariable rangeVariable, Type elementType) + { + ParameterExpression filterParameter = Expression.Parameter(elementType, rangeVariable.Name); + _lambdaParameters = new Dictionary(); + _lambdaParameters.Add(rangeVariable.Name, filterParameter); + + EnsureFlattenedPropertyContainer(filterParameter); + + Expression body = Bind(expression); + return Expression.Lambda(body, filterParameter); + } + + private Expression ApplyNullPropagationForFilterBody(Expression body) + { + if (IsNullable(body.Type)) + { + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // handle null as false + // body => body == true. passing liftToNull:false would convert null to false. + body = Expression.Equal(body, Expression.Constant(true, typeof(bool?)), liftToNull: false, method: null); + } + else + { + body = Expression.Convert(body, typeof(bool)); + } + } + + return body; + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The range variable to bind. + /// The LINQ created. + public virtual Expression BindRangeVariable(RangeVariable rangeVariable) + { + ParameterExpression parameter = _lambdaParameters[rangeVariable.Name]; + return ConvertNonStandardPrimitives(parameter); + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindCollectionPropertyAccessNode(CollectionPropertyAccessNode propertyAccessNode) + { + Expression source = Bind(propertyAccessNode.Source); + return CreatePropertyAccessExpression(source, propertyAccessNode.Property); + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindCollectionComplexNode(CollectionComplexNode collectionComplexNode) + { + Expression source = Bind(collectionComplexNode.Source); + return CreatePropertyAccessExpression(source, collectionComplexNode.Property); + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindPropertyAccessQueryNode(SingleValuePropertyAccessNode propertyAccessNode) + { + Expression source = Bind(propertyAccessNode.Source); + return CreatePropertyAccessExpression(source, propertyAccessNode.Property, GetFullPropertyPath(propertyAccessNode)); + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindSingleComplexNode(SingleComplexNode singleComplexNode) + { + Expression source = Bind(singleComplexNode.Source); + return CreatePropertyAccessExpression(source, singleComplexNode.Property, GetFullPropertyPath(singleComplexNode)); + } + + private Expression CreatePropertyAccessExpression(Expression source, IEdmProperty property, string propertyPath = null) + { + string propertyName = EdmLibHelpers.GetClrPropertyName(property, Model); + propertyPath = propertyPath ?? propertyName; + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) && source != _lambdaParameters[ODataItParameterName]) + { + var cleanSource = RemoveInnerNullPropagation(source); + Expression propertyAccessExpression = null; + + propertyAccessExpression = GetFlattenedPropertyExpression(propertyPath) ?? Expression.Property(cleanSource, propertyName); + + // source.property => source == null ? null : [CastToNullable]RemoveInnerNullPropagation(source).property + // Notice that we are checking if source is null already. so we can safely remove any null checks when doing source.Property + + Expression ifFalse = ToNullable(ConvertNonStandardPrimitives(propertyAccessExpression)); + return + Expression.Condition( + test: Expression.Equal(source, NullConstant), + ifTrue: Expression.Constant(null, ifFalse.Type), + ifFalse: ifFalse); + } + else + { + return GetFlattenedPropertyExpression(propertyPath) ?? ConvertNonStandardPrimitives(Expression.Property(source, propertyName)); + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindUnaryOperatorNode(UnaryOperatorNode unaryOperatorNode) + { + // No need to handle null-propagation here as CLR already handles it. + // !(null) = null + // -(null) = null + Expression inner = Bind(unaryOperatorNode.Operand); + switch (unaryOperatorNode.OperatorKind) + { + case UnaryOperatorKind.Negate: + return Expression.Negate(inner); + + case UnaryOperatorKind.Not: + return Expression.Not(inner); + + default: + throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, unaryOperatorNode.Kind, typeof(FilterBinder).Name); + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "These are simple binding functions and cannot be split up.")] + public virtual Expression BindSingleValueFunctionCallNode(SingleValueFunctionCallNode node) + { + switch (node.Name) + { + case ClrCanonicalFunctions.StartswithFunctionName: + return BindStartsWith(node); + + case ClrCanonicalFunctions.EndswithFunctionName: + return BindEndsWith(node); + + case ClrCanonicalFunctions.ContainsFunctionName: + return BindContains(node); + + case ClrCanonicalFunctions.SubstringFunctionName: + return BindSubstring(node); + + case ClrCanonicalFunctions.LengthFunctionName: + return BindLength(node); + + case ClrCanonicalFunctions.IndexofFunctionName: + return BindIndexOf(node); + + case ClrCanonicalFunctions.TolowerFunctionName: + return BindToLower(node); + + case ClrCanonicalFunctions.ToupperFunctionName: + return BindToUpper(node); + + case ClrCanonicalFunctions.TrimFunctionName: + return BindTrim(node); + + case ClrCanonicalFunctions.ConcatFunctionName: + return BindConcat(node); + + case ClrCanonicalFunctions.YearFunctionName: + case ClrCanonicalFunctions.MonthFunctionName: + case ClrCanonicalFunctions.DayFunctionName: + return BindDateRelatedProperty(node); // Date & DateTime & DateTimeOffset + + case ClrCanonicalFunctions.HourFunctionName: + case ClrCanonicalFunctions.MinuteFunctionName: + case ClrCanonicalFunctions.SecondFunctionName: + return BindTimeRelatedProperty(node); // TimeOfDay & DateTime & DateTimeOffset + + case ClrCanonicalFunctions.FractionalSecondsFunctionName: + return BindFractionalSeconds(node); + + case ClrCanonicalFunctions.RoundFunctionName: + return BindRound(node); + + case ClrCanonicalFunctions.FloorFunctionName: + return BindFloor(node); + + case ClrCanonicalFunctions.CeilingFunctionName: + return BindCeiling(node); + + case ClrCanonicalFunctions.CastFunctionName: + return BindCastSingleValue(node); + + case ClrCanonicalFunctions.IsofFunctionName: + return BindIsOf(node); + + case ClrCanonicalFunctions.DateFunctionName: + return BindDate(node); + + case ClrCanonicalFunctions.TimeFunctionName: + return BindTime(node); + + case ClrCanonicalFunctions.NowFunctionName: + return BindNow(node); + + default: + // Get Expression of custom binded method. + Expression expression = BindCustomMethodExpressionOrNull(node); + if (expression != null) + { + return expression; + } + + throw new NotImplementedException(Error.Format(SRResources.ODataFunctionNotSupported, node.Name)); + } + } + + private Expression BindCastSingleValue(SingleValueFunctionCallNode node) + { + Contract.Assert(ClrCanonicalFunctions.CastFunctionName == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + Contract.Assert(arguments.Length == 1 || arguments.Length == 2); + + Expression source = arguments.Length == 1 ? _lambdaParameters[ODataItParameterName] : arguments[0]; + string targetTypeName = (string)((ConstantNode)node.Parameters.Last()).Value; + IEdmType targetEdmType = Model.FindType(targetTypeName); + Type targetClrType = null; + + if (targetEdmType != null) + { + IEdmTypeReference targetEdmTypeReference = targetEdmType.ToEdmTypeReference(false); + targetClrType = EdmLibHelpers.GetClrType(targetEdmTypeReference, Model); + + if (source != NullConstant) + { + if (source.Type == targetClrType) + { + return source; + } + + if ((!targetEdmTypeReference.IsPrimitive() && !targetEdmTypeReference.IsEnum()) || + (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(source.Type) == null && !TypeHelper.IsEnum(source.Type))) + { + // Cast fails and return null. + return NullConstant; + } + } + } + + if (targetClrType == null || source == NullConstant) + { + return NullConstant; + } + + if (targetClrType == typeof(string)) + { + return BindCastToStringType(source); + } + else if (TypeHelper.IsEnum(targetClrType)) + { + return BindCastToEnumType(source.Type, targetClrType, node.Parameters.First(), arguments.Length); + } + else + { + if (TypeHelper.IsNullable(source.Type) && !TypeHelper.IsNullable(targetClrType)) + { + // Make the target Clr type nullable to avoid failure while casting + // nullable source, whose value may be null, to a non-nullable type. + // For example: cast(NullableInt32Property,Edm.Int64) + // The target Clr type should be Nullable rather than Int64. + targetClrType = typeof(Nullable<>).MakeGenericType(targetClrType); + } + + try + { + return Expression.Convert(source, targetClrType); + } + catch (InvalidOperationException) + { + // Cast fails and return null. + return NullConstant; + } + } + } + + private static Expression BindCastToStringType(Expression source) + { + Expression sourceValue; + + if (TypeHelper.IsGenericType(source.Type) && source.Type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + if (TypeHelper.IsEnum(source.Type)) + { + // Entity Framework doesn't have ToString method for enum types. + // Convert enum types to their underlying numeric types. + sourceValue = Expression.Convert( + Expression.Property(source, "Value"), + Enum.GetUnderlyingType(TypeHelper.GetUnderlyingTypeOrSelf(source.Type))); + } + else + { + // Entity Framework has ToString method for numeric types. + sourceValue = Expression.Property(source, "Value"); + } + + // Entity Framework doesn't have ToString method for nullable numeric types. + // Call ToString method on non-nullable numeric types. + return Expression.Condition( + Expression.Property(source, "HasValue"), + Expression.Call(sourceValue, "ToString", typeArguments: null, arguments: null), + Expression.Constant(null, typeof(string))); + } + else + { + sourceValue = TypeHelper.IsEnum(source.Type) ? + Expression.Convert(source, Enum.GetUnderlyingType(source.Type)) : + source; + return Expression.Call(sourceValue, "ToString", typeArguments: null, arguments: null); + } + } + + private Expression BindCastToEnumType(Type sourceType, Type targetClrType, QueryNode firstParameter, int parameterLength) + { + Type enumType = TypeHelper.GetUnderlyingTypeOrSelf(targetClrType); + ConstantNode sourceNode = firstParameter as ConstantNode; + + if (parameterLength == 1 || sourceNode == null || sourceType != typeof(string)) + { + // We only support to cast Enumeration type from constant string now, + // because LINQ to Entities does not recognize the method Enum.TryParse. + return NullConstant; + } + else + { + object[] parameters = new[] { sourceNode.Value, Enum.ToObject(enumType, 0) }; + bool isSuccessful = (bool)EnumTryParseMethod.MakeGenericMethod(enumType).Invoke(null, parameters); + + if (isSuccessful) + { + if (QuerySettings.EnableConstantParameterization) + { + return LinqParameterContainer.Parameterize(targetClrType, parameters[1]); + } + else + { + return Expression.Constant(parameters[1], targetClrType); + } + } + else + { + return NullConstant; + } + } + } + + private Expression BindIsOf(SingleValueFunctionCallNode node) + { + Contract.Assert(ClrCanonicalFunctions.IsofFunctionName == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + + // Edm.Boolean isof(type) or + // Edm.Boolean isof(expression,type) + Contract.Assert(arguments.Length == 1 || arguments.Length == 2); + + Expression source = arguments.Length == 1 ? _lambdaParameters[ODataItParameterName] : arguments[0]; + if (source == NullConstant) + { + return FalseConstant; + } + + string typeName = (string)((ConstantNode)node.Parameters.Last()).Value; + + IEdmType edmType = Model.FindType(typeName); + Type clrType = null; + if (edmType != null) + { + // bool nullable = source.Type.IsNullable(); + IEdmTypeReference edmTypeReference = edmType.ToEdmTypeReference(false); + clrType = EdmLibHelpers.GetClrType(edmTypeReference, Model); + } + + if (clrType == null) + { + return FalseConstant; + } + + bool isSourcePrimitiveOrEnum = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(source.Type) != null || + TypeHelper.IsEnum(source.Type); + + bool isTargetPrimitiveOrEnum = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(clrType) != null || + TypeHelper.IsEnum(clrType); + + if (isSourcePrimitiveOrEnum && isTargetPrimitiveOrEnum) + { + if (TypeHelper.IsNullable(source.Type)) + { + clrType = TypeHelper.ToNullable(clrType); + } + } + + // Be caution: Type method of LINQ to Entities only supports entity type. + return Expression.Condition(Expression.TypeIs(source, clrType), TrueConstant, FalseConstant); + } + + private Expression BindCeiling(SingleValueFunctionCallNode node) + { + Contract.Assert("ceiling" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + + Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type)); + + MethodInfo ceiling = IsType(arguments[0].Type) + ? ClrCanonicalFunctions.CeilingOfDouble + : ClrCanonicalFunctions.CeilingOfDecimal; + return MakeFunctionCall(ceiling, arguments); + } + + private Expression BindFloor(SingleValueFunctionCallNode node) + { + Contract.Assert("floor" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + + Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type)); + + MethodInfo floor = IsType(arguments[0].Type) + ? ClrCanonicalFunctions.FloorOfDouble + : ClrCanonicalFunctions.FloorOfDecimal; + return MakeFunctionCall(floor, arguments); + } + + private Expression BindRound(SingleValueFunctionCallNode node) + { + Contract.Assert("round" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + + Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type)); + + MethodInfo round = IsType(arguments[0].Type) + ? ClrCanonicalFunctions.RoundOfDouble + : ClrCanonicalFunctions.RoundOfDecimal; + return MakeFunctionCall(round, arguments); + } + + private Expression BindDate(SingleValueFunctionCallNode node) + { + Contract.Assert("date" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + + // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec. + Contract.Assert(arguments.Length == 1 && IsDateOrOffset(arguments[0].Type)); + + // EF doesn't support new Date(int, int, int), also doesn't support other property access, for example DateTime.Date. + // Therefore, we just return the source (DateTime or DateTimeOffset). + return arguments[0]; + } + + private Expression BindNow(SingleValueFunctionCallNode node) + { + Contract.Assert("now" == node.Name); + + // Function Now() does not take any arguemnts. + Expression[] arguments = BindArguments(node.Parameters); + Contract.Assert(arguments.Length == 0); + + return Expression.Property(null, typeof(DateTimeOffset), "UtcNow"); + } + + private Expression BindTime(SingleValueFunctionCallNode node) + { + Contract.Assert("time" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + + // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec. + Contract.Assert(arguments.Length == 1 && IsDateOrOffset(arguments[0].Type)); + + // EF doesn't support new TimeOfDay(int, int, int, int), also doesn't support other property access, for example DateTimeOffset.DateTime. + // Therefore, we just return the source (DateTime or DateTimeOffset). + return arguments[0]; + } + + private Expression BindFractionalSeconds(SingleValueFunctionCallNode node) + { + Contract.Assert("fractionalseconds" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + Contract.Assert(arguments.Length == 1 && (IsTimeRelated(arguments[0].Type))); + + // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec. + Expression parameter = arguments[0]; + + PropertyInfo property; + if (IsTimeOfDay(parameter.Type)) + { + property = ClrCanonicalFunctions.TimeOfDayProperties[ClrCanonicalFunctions.MillisecondFunctionName]; + } + else if (IsDateTime(parameter.Type)) + { + property = ClrCanonicalFunctions.DateTimeProperties[ClrCanonicalFunctions.MillisecondFunctionName]; + } + else if (IsTimeSpan(parameter.Type)) + { + property = ClrCanonicalFunctions.TimeSpanProperties[ClrCanonicalFunctions.MillisecondFunctionName]; + } + else + { + property = ClrCanonicalFunctions.DateTimeOffsetProperties[ClrCanonicalFunctions.MillisecondFunctionName]; + } + + // Millisecond + Expression milliSecond = MakePropertyAccess(property, parameter); + Expression decimalMilliSecond = Expression.Convert(milliSecond, typeof(decimal)); + Expression fractionalSeconds = Expression.Divide(decimalMilliSecond, Expression.Constant(1000m, typeof(decimal))); + + return CreateFunctionCallWithNullPropagation(fractionalSeconds, arguments); + } + + private Expression BindDateRelatedProperty(SingleValueFunctionCallNode node) + { + Expression[] arguments = BindArguments(node.Parameters); + Contract.Assert(arguments.Length == 1 && IsDateRelated(arguments[0].Type)); + + // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec. + Expression parameter = arguments[0]; + + PropertyInfo property; + if (IsDate(parameter.Type)) + { + Contract.Assert(ClrCanonicalFunctions.DateProperties.ContainsKey(node.Name)); + property = ClrCanonicalFunctions.DateProperties[node.Name]; + } + else if (IsDateTime(parameter.Type)) + { + Contract.Assert(ClrCanonicalFunctions.DateTimeProperties.ContainsKey(node.Name)); + property = ClrCanonicalFunctions.DateTimeProperties[node.Name]; + } + else + { + Contract.Assert(ClrCanonicalFunctions.DateTimeOffsetProperties.ContainsKey(node.Name)); + property = ClrCanonicalFunctions.DateTimeOffsetProperties[node.Name]; + } + + return MakeFunctionCall(property, parameter); + } + + private Expression BindTimeRelatedProperty(SingleValueFunctionCallNode node) + { + Expression[] arguments = BindArguments(node.Parameters); + Contract.Assert(arguments.Length == 1 && (IsTimeRelated(arguments[0].Type))); + + // We should support DateTime & DateTimeOffset even though DateTime is not part of OData v4 Spec. + Expression parameter = arguments[0]; + + PropertyInfo property; + if (IsTimeOfDay(parameter.Type)) + { + Contract.Assert(ClrCanonicalFunctions.TimeOfDayProperties.ContainsKey(node.Name)); + property = ClrCanonicalFunctions.TimeOfDayProperties[node.Name]; + } + else if (IsDateTime(parameter.Type)) + { + Contract.Assert(ClrCanonicalFunctions.DateTimeProperties.ContainsKey(node.Name)); + property = ClrCanonicalFunctions.DateTimeProperties[node.Name]; + } + else if (IsTimeSpan(parameter.Type)) + { + Contract.Assert(ClrCanonicalFunctions.TimeSpanProperties.ContainsKey(node.Name)); + property = ClrCanonicalFunctions.TimeSpanProperties[node.Name]; + } + else + { + Contract.Assert(ClrCanonicalFunctions.DateTimeOffsetProperties.ContainsKey(node.Name)); + property = ClrCanonicalFunctions.DateTimeOffsetProperties[node.Name]; + } + + return MakeFunctionCall(property, parameter); + } + + private Expression BindConcat(SingleValueFunctionCallNode node) + { + Contract.Assert("concat" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.Concat, arguments); + } + + private Expression BindTrim(SingleValueFunctionCallNode node) + { + Contract.Assert("trim" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.Trim, arguments); + } + + private Expression BindToUpper(SingleValueFunctionCallNode node) + { + Contract.Assert("toupper" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.ToUpper, arguments); + } + + private Expression BindToLower(SingleValueFunctionCallNode node) + { + Contract.Assert("tolower" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.ToLower, arguments); + } + + private Expression BindIndexOf(SingleValueFunctionCallNode node) + { + Contract.Assert("indexof" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.IndexOf, arguments); + } + + private Expression BindSubstring(SingleValueFunctionCallNode node) + { + Contract.Assert("substring" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + if (arguments[0].Type != typeof(string)) + { + throw new ODataException(Error.Format(SRResources.FunctionNotSupportedOnEnum, node.Name)); + } + + Expression functionCall; + if (arguments.Length == 2) + { + Contract.Assert(IsInteger(arguments[1].Type)); + + // When null propagation is allowed, we use a safe version of String.Substring(int). + // But for providers that would not recognize custom expressions like this, we map + // directly to String.Substring(int) + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // Safe function is static and takes string "this" as first argument + functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartNoThrow, arguments); + } + else + { + functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStart, arguments); + } + } + else + { + // arguments.Length == 3 implies String.Substring(int, int) + Contract.Assert(arguments.Length == 3 && IsInteger(arguments[1].Type) && IsInteger(arguments[2].Type)); + + // When null propagation is allowed, we use a safe version of String.Substring(int, int). + // But for providers that would not recognize custom expressions like this, we map + // directly to String.Substring(int, int) + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // Safe function is static and takes string "this" as first argument + functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLengthNoThrow, arguments); + } + else + { + functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLength, arguments); + } + } + + return functionCall; + } + + private Expression BindLength(SingleValueFunctionCallNode node) + { + Contract.Assert("length" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.Length, arguments); + } + + private Expression BindContains(SingleValueFunctionCallNode node) + { + Contract.Assert("contains" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.Contains, arguments[0], arguments[1]); + } + + private Expression BindStartsWith(SingleValueFunctionCallNode node) + { + Contract.Assert("startswith" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.StartsWith, arguments); + } + + private Expression BindEndsWith(SingleValueFunctionCallNode node) + { + Contract.Assert("endswith" == node.Name); + + Expression[] arguments = BindArguments(node.Parameters); + ValidateAllStringArguments(node.Name, arguments); + + Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); + + return MakeFunctionCall(ClrCanonicalFunctions.EndsWith, arguments); + } + + private Expression[] BindArguments(IEnumerable nodes) + { + return nodes.OfType().Select(n => Bind(n)).ToArray(); + } + + private static void ValidateAllStringArguments(string functionName, Expression[] arguments) + { + if (arguments.Any(arg => arg.Type != typeof(string))) + { + throw new ODataException(Error.Format(SRResources.FunctionNotSupportedOnEnum, functionName)); + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindAllNode(AllNode allNode) + { + ParameterExpression allIt = HandleLambdaParameters(allNode.RangeVariables); + + Expression source; + Contract.Assert(allNode.Source != null); + source = Bind(allNode.Source); + + Expression body = source; + Contract.Assert(allNode.Body != null); + + body = Bind(allNode.Body); + body = ApplyNullPropagationForFilterBody(body); + body = Expression.Lambda(body, allIt); + + Expression all = All(source, body); + + ExitLamdbaScope(); + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type)) + { + // IFF(source == null) null; else Any(body); + all = ToNullable(all); + return Expression.Condition( + test: Expression.Equal(source, NullConstant), + ifTrue: Expression.Constant(null, all.Type), + ifFalse: all); + } + else + { + return all; + } + } + + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + public virtual Expression BindAnyNode(AnyNode anyNode) + { + ParameterExpression anyIt = HandleLambdaParameters(anyNode.RangeVariables); + + Expression source; + Contract.Assert(anyNode.Source != null); + source = Bind(anyNode.Source); + + Expression body = null; + // uri parser places an Constant node with value true for empty any() body + if (anyNode.Body != null && anyNode.Body.Kind != QueryNodeKind.Constant) + { + body = Bind(anyNode.Body); + body = ApplyNullPropagationForFilterBody(body); + body = Expression.Lambda(body, anyIt); + } + else if (anyNode.Body != null && anyNode.Body.Kind == QueryNodeKind.Constant + && (bool)(anyNode.Body as ConstantNode).Value == false) + { + // any(false) is the same as just false + ExitLamdbaScope(); + return FalseConstant; + } + + Expression any = Any(source, body); + + ExitLamdbaScope(); + + if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type)) + { + // IFF(source == null) null; else Any(body); + any = ToNullable(any); + return Expression.Condition( + test: Expression.Equal(source, NullConstant), + ifTrue: Expression.Constant(null, any.Type), + ifFalse: any); + } + else + { + return any; + } + } + + private Expression BindCustomMethodExpressionOrNull(SingleValueFunctionCallNode node) + { + Expression[] arguments = BindArguments(node.Parameters); + IEnumerable methodArgumentsType = arguments.Select(argument => argument.Type); + + // Search for custom method info that are binded to the node name + MethodInfo methodInfo; + if (UriFunctionsBinder.TryGetMethodInfo(node.Name, methodArgumentsType, out methodInfo)) + { + return MakeFunctionCall(methodInfo, arguments); + } + + return null; + } + + /// + /// Binds a to create a LINQ that represents the semantics + /// of the . + /// + /// The node to bind. + /// The LINQ created. + private Expression BindSingleValueNode(SingleValueNode node) + { + switch (node.Kind) + { + case QueryNodeKind.BinaryOperator: + return BindBinaryOperatorNode(node as BinaryOperatorNode); + + case QueryNodeKind.Constant: + return BindConstantNode(node as ConstantNode); + + case QueryNodeKind.Convert: + return BindConvertNode(node as ConvertNode); + + case QueryNodeKind.ResourceRangeVariableReference: + return BindRangeVariable((node as ResourceRangeVariableReferenceNode).RangeVariable); + + case QueryNodeKind.NonResourceRangeVariableReference: + return BindRangeVariable((node as NonResourceRangeVariableReferenceNode).RangeVariable); + + case QueryNodeKind.SingleValuePropertyAccess: + return BindPropertyAccessQueryNode(node as SingleValuePropertyAccessNode); + + case QueryNodeKind.SingleComplexNode: + return BindSingleComplexNode(node as SingleComplexNode); + + case QueryNodeKind.SingleValueOpenPropertyAccess: + return BindDynamicPropertyAccessQueryNode(node as SingleValueOpenPropertyAccessNode); + + case QueryNodeKind.UnaryOperator: + return BindUnaryOperatorNode(node as UnaryOperatorNode); + + case QueryNodeKind.SingleValueFunctionCall: + return BindSingleValueFunctionCallNode(node as SingleValueFunctionCallNode); + + case QueryNodeKind.SingleNavigationNode: + SingleNavigationNode navigationNode = node as SingleNavigationNode; + return BindNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty); + + case QueryNodeKind.Any: + return BindAnyNode(node as AnyNode); + + case QueryNodeKind.All: + return BindAllNode(node as AllNode); + + case QueryNodeKind.SingleResourceCast: + return BindSingleResourceCastNode(node as SingleResourceCastNode); + + case QueryNodeKind.SingleResourceFunctionCall: + return BindSingleResourceFunctionCallNode(node as SingleResourceFunctionCallNode); + + case QueryNodeKind.In: + return BindInNode(node as InNode); + + case QueryNodeKind.Count: + return BindCountNode(node as CountNode); + + case QueryNodeKind.NamedFunctionParameter: + case QueryNodeKind.ParameterAlias: + case QueryNodeKind.EntitySet: + case QueryNodeKind.KeyLookup: + case QueryNodeKind.SearchTerm: + // Unused or have unknown uses. + default: + throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name); + } + } + + /// + /// Binds a to create a LINQ that represents the semantics + /// of the . + /// + /// The node to bind. + /// The LINQ created. + private Expression BindCollectionNode(CollectionNode node) + { + switch (node.Kind) + { + case QueryNodeKind.CollectionNavigationNode: + CollectionNavigationNode navigationNode = node as CollectionNavigationNode; + return BindNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty); + + case QueryNodeKind.CollectionPropertyAccess: + return BindCollectionPropertyAccessNode(node as CollectionPropertyAccessNode); + + case QueryNodeKind.CollectionComplexNode: + return BindCollectionComplexNode(node as CollectionComplexNode); + + case QueryNodeKind.CollectionResourceCast: + return BindCollectionResourceCastNode(node as CollectionResourceCastNode); + + case QueryNodeKind.CollectionConstant: + return BindCollectionConstantNode(node as CollectionConstantNode); + + case QueryNodeKind.CollectionFunctionCall: + case QueryNodeKind.CollectionResourceFunctionCall: + case QueryNodeKind.CollectionOpenPropertyAccess: + default: + throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name); + } + } + + private Type RetrieveClrTypeForConstant(IEdmTypeReference edmTypeReference, ref object value) + { + Type constantType = EdmLibHelpers.GetClrType(edmTypeReference, Model, InternalAssembliesResolver); + + if (value != null && edmTypeReference != null && edmTypeReference.IsEnum()) + { + ODataEnumValue odataEnumValue = (ODataEnumValue)value; + string strValue = odataEnumValue.Value; + Contract.Assert(strValue != null); + + constantType = Nullable.GetUnderlyingType(constantType) ?? constantType; + value = Enum.Parse(constantType, strValue); + } + + if (edmTypeReference != null && + edmTypeReference.IsNullable && + (edmTypeReference.IsDate() || edmTypeReference.IsTimeOfDay())) + { + constantType = Nullable.GetUnderlyingType(constantType) ?? constantType; + } + + return constantType; + } + + private ParameterExpression HandleLambdaParameters(IEnumerable rangeVariables) + { + ParameterExpression lambdaIt = null; + + EnterLambdaScope(); + + Dictionary newParameters = new Dictionary(); + foreach (RangeVariable rangeVariable in rangeVariables) + { + ParameterExpression parameter; + if (!_lambdaParameters.TryGetValue(rangeVariable.Name, out parameter)) + { + // Work-around issue 481323 where UriParser yields a collection parameter type + // for primitive collections rather than the inner element type of the collection. + // Remove this block of code when 481323 is resolved. + IEdmTypeReference edmTypeReference = rangeVariable.TypeReference; + IEdmCollectionTypeReference collectionTypeReference = edmTypeReference as IEdmCollectionTypeReference; + if (collectionTypeReference != null) + { + IEdmCollectionType collectionType = collectionTypeReference.Definition as IEdmCollectionType; + if (collectionType != null) + { + edmTypeReference = collectionType.ElementType; + } + } + + parameter = Expression.Parameter(EdmLibHelpers.GetClrType(edmTypeReference, Model, InternalAssembliesResolver), rangeVariable.Name); + Contract.Assert(lambdaIt == null, "There can be only one parameter in an Any/All lambda"); + lambdaIt = parameter; + } + newParameters.Add(rangeVariable.Name, parameter); + } + + _lambdaParameters = newParameters; + return lambdaIt; + } + + private void EnterLambdaScope() + { + Contract.Assert(_lambdaParameters != null); + _parametersStack.Push(_lambdaParameters); + } + + private void ExitLamdbaScope() + { + if (_parametersStack.Count != 0) + { + _lambdaParameters = _parametersStack.Pop(); + } + else + { + _lambdaParameters = null; + } + } + + private static Expression Any(Expression source, Expression filter) + { + Contract.Assert(source != null); + Type elementType; + TypeHelper.IsCollection(source.Type, out elementType); + Contract.Assert(elementType != null); + + if (filter == null) + { + if (IsIQueryable(source.Type)) + { + return Expression.Call(null, ExpressionHelperMethods.QueryableEmptyAnyGeneric.MakeGenericMethod(elementType), source); + } + else + { + return Expression.Call(null, ExpressionHelperMethods.EnumerableEmptyAnyGeneric.MakeGenericMethod(elementType), source); + } + } + else + { + if (IsIQueryable(source.Type)) + { + return Expression.Call(null, ExpressionHelperMethods.QueryableNonEmptyAnyGeneric.MakeGenericMethod(elementType), source, filter); + } + else + { + return Expression.Call(null, ExpressionHelperMethods.EnumerableNonEmptyAnyGeneric.MakeGenericMethod(elementType), source, filter); + } + } + } + + private static Expression All(Expression source, Expression filter) + { + Contract.Assert(source != null); + Contract.Assert(filter != null); + + Type elementType; + TypeHelper.IsCollection(source.Type, out elementType); + Contract.Assert(elementType != null); + + if (IsIQueryable(source.Type)) + { + return Expression.Call(null, ExpressionHelperMethods.QueryableAllGeneric.MakeGenericMethod(elementType), source, filter); + } + else + { + return Expression.Call(null, ExpressionHelperMethods.EnumerableAllGeneric.MakeGenericMethod(elementType), source, filter); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/IdentityPropertyMapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/IdentityPropertyMapper.cs new file mode 100644 index 0000000..e961391 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/IdentityPropertyMapper.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + internal class IdentityPropertyMapper : IPropertyMapper + { + public string MapProperty(string propertyName) + { + return propertyName; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/Linq2ObjectsComparisonMethods.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/Linq2ObjectsComparisonMethods.cs new file mode 100644 index 0000000..d63417e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/Linq2ObjectsComparisonMethods.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + internal static class Linq2ObjectsComparisonMethods + { + /// Method info for byte array comparison. + public static readonly MethodInfo AreByteArraysEqualMethodInfo = + typeof(Linq2ObjectsComparisonMethods).GetMethod("AreByteArraysEqual"); + + /// Method info for byte array comparison. + public static readonly MethodInfo AreByteArraysNotEqualMethodInfo = + typeof(Linq2ObjectsComparisonMethods).GetMethod("AreByteArraysNotEqual"); + + /// Compares two byte arrays for equality. + /// First byte array. + /// Second byte array. + /// true if the arrays are equal; false otherwise. + public static bool AreByteArraysEqual(byte[] left, byte[] right) + { + if (Object.ReferenceEquals(left, right)) + { + return true; + } + + if (left == null || right == null) + { + return false; + } + + if (left.Length != right.Length) + { + return false; + } + + for (int i = 0; i < left.Length; i++) + { + if (left[i] != right[i]) + { + return false; + } + } + + return true; + } + + /// Compares two byte arrays for equality. + /// First byte array. + /// Second byte array. + /// true if the arrays are not equal; false otherwise. + public static bool AreByteArraysNotEqual(byte[] left, byte[] right) + { + return !AreByteArraysEqual(left, right); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/LinqParameterContainer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/LinqParameterContainer.cs new file mode 100644 index 0000000..cf63f9b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/LinqParameterContainer.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + // wraps a constant value so that EntityFramework paramterizes the constant. + internal abstract class LinqParameterContainer + { + private static ConcurrentDictionary> _ctors = new ConcurrentDictionary>(); + + // the value of the constant. + public abstract object Property { get; } + + public static Expression Parameterize(Type type, object value) + { + // () => new LinqParameterContainer(constant).Property + // instead of returning a constant expression node, wrap that constant in a class the way compiler + // does a closure, so that EF can parameterize the constant (resulting in better performance due to expression translation caching). + LinqParameterContainer containedValue = LinqParameterContainer.Create(type, value); + return Expression.Property(Expression.Constant(containedValue), "TypedProperty"); + } + + private static LinqParameterContainer Create(Type type, object value) + { + return _ctors.GetOrAdd(type, t => + { + MethodInfo createMethod = typeof(LinqParameterContainer).GetMethod("CreateInternal").MakeGenericMethod(t); + ParameterExpression valueParameter = Expression.Parameter(typeof(object)); + return + Expression.Lambda>( + Expression.Call( + createMethod, + Expression.Convert(valueParameter, t)), + valueParameter) + .Compile(); + })(value); + } + + // invoked dynamically at runtime. + public static LinqParameterContainer CreateInternal(T value) + { + return new TypedLinqParameterContainer(value); + } + + // having a strongly typed property avoids the a cast in the property access expression that would be + // generated for this constant. + internal class TypedLinqParameterContainer : LinqParameterContainer + { + public TypedLinqParameterContainer(T value) + { + TypedProperty = value; + } + + public T TypedProperty { get; set; } + + public override object Property + { + get { return TypedProperty; } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ModelContainer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ModelContainer.cs new file mode 100644 index 0000000..6d90862 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ModelContainer.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// EntityFramework does not let you inject non primitive constant values (like IEdmModel) in Select queries. Primitives like strings and guids can be + /// injected as they can be translated into a SQL query. This container associates a unique string with each EDM model, so that, given the string the model + /// can be retrieved later. + /// + internal static class ModelContainer + { + private static ConcurrentDictionary _map = new ConcurrentDictionary(); + private static ConcurrentDictionary _reverseMap = new ConcurrentDictionary(); + + public static string GetModelID(IEdmModel model) + { + string index = _map.GetOrAdd(model, m => Guid.NewGuid().ToString()); + _reverseMap.TryAdd(index, model); + return index; + } + + public static IEdmModel GetModel(string id) + { + return _reverseMap[id]; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/NamedPropertyExpression.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/NamedPropertyExpression.cs new file mode 100644 index 0000000..13190d0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/NamedPropertyExpression.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using System.Linq.Expressions; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Represents a container that captures a named property that is a part of the select expand query. + /// + internal class NamedPropertyExpression + { + public NamedPropertyExpression(Expression name, Expression value) + { + Contract.Assert(name != null); + Contract.Assert(value != null); + + Name = name; + Value = value; + } + + public Expression Name { get; private set; } + + public Expression Value { get; private set; } + + public Expression TotalCount { get; set; } + + // Checks whether this property is null or not. This is required for expanded navigation properties that are null as entityframework cannot + // create null's of type SelectExpandWrapper i.e. an expression like + // => new NamedProperty { Value = order.Customer == null : null : new SelectExpandWrapper { .... } } + // cannot be translated by EF. So, we generate the following expression instead, + // => new ExpandProperty { Value = new SelectExpandWrapper { .... }, IsNull = nullCheck } + // and use Value only if IsNull is false. + public Expression NullCheck { get; set; } + + public int? PageSize { get; set; } + + public bool AutoSelected { get; set; } + + public bool? CountOption { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.cs new file mode 100644 index 0000000..068fff0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.cs @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// A container of property names and property values. + /// + /// + /// EntityFramework understands only member initializations in Select expressions. Also, it doesn't understand type casts for non-primitive types. So, + /// SelectExpandBinder has to generate strongly types expressions that involve only property access. This class represents the base class for a bunch of + /// generic derived types that are used in the expressions that SelectExpandBinder generates. + /// Also, Expression.Compile() could fail with stack overflow if expression is to deep and causes too many levels of recursion. To avoid that we are b-tree property container. + /// + internal abstract partial class PropertyContainer + { + /// + /// Initializes a new instance of the class. + /// + protected PropertyContainer() + { + } + + /// + /// Builds the dictionary of properties in this container keyed by the property name. + /// + /// The dictionary of properties in this container keyed by the property name. + public Dictionary ToDictionary(IPropertyMapper propertyMapper, bool includeAutoSelected = true) + { + Contract.Assert(propertyMapper != null); + Dictionary result = new Dictionary(); + ToDictionaryCore(result, propertyMapper, includeAutoSelected); + return result; + } + + /// + /// Adds the properties in this container to the given dictionary. + /// + /// The dictionary to add the properties to. + /// Specifies whether auto selected properties should be included. + /// An object responsible to map the properties in this property container to the + /// the value that will be used as the key in the dictionary we are adding properties to. + public abstract void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected); + + // Expression: + // new NamedProperty + // { + // Name = properties[0].Key, + // Value = properties[0].Value, + // + // Next0 = new NamedProperty<> { ..... } + // Next1 = new NamedProperty<> { ..... }, + // ... + // } + public static Expression CreatePropertyContainer(IList properties) + { + Expression container = null; + + // build the linked list of properties. + if (properties.Count >= 1) + { + NamedPropertyExpression property = properties.First(); + int count = properties.Count - 1; + List nextExpressions = new List(); + int parts = SingleExpandedPropertyTypes.Count - 1; + int offset = 0; + for (int step = parts; step > 0; step--) + { + int leftSize = GetLeftSize(count - offset, step); + nextExpressions.Add(CreatePropertyContainer(properties.Skip(1 + offset).Take(leftSize).ToList())); + offset += leftSize; + } + + container = CreateNamedPropertyCreationExpression(property, nextExpressions.Where(e => e != null).ToList()); + } + + return container; + } + + private static int GetLeftSize(int count, int parts) + { + if (count % parts != 0) + { + return (count / parts) + 1; + } + return count / parts; + } + + // Expression: + // new NamedProperty { Name = property.Name, Value = property.Value, Next0 = next0, Next1 = next1, .... }. + private static Expression CreateNamedPropertyCreationExpression(NamedPropertyExpression property, IList expressions) + { + Contract.Assert(property != null); + Contract.Assert(property.Value != null); + + Type namedPropertyType = GetNamedPropertyType(property, expressions); + List memberBindings = new List(); + + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("Name"), property.Name)); + + if (property.PageSize != null || property.CountOption != null) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("Collection"), property.Value)); + + if (property.PageSize != null) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("PageSize"), + Expression.Constant(property.PageSize))); + } + + if (property.CountOption != null && property.CountOption.Value) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("TotalCount"), ExpressionHelpers.ToNullable(property.TotalCount))); + } + } + else + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("Value"), property.Value)); + } + + for (int i = 0; i < expressions.Count; i++) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("Next" + i), expressions[i])); + } + + if (property.NullCheck != null) + { + memberBindings.Add(Expression.Bind(namedPropertyType.GetProperty("IsNull"), property.NullCheck)); + } + + return Expression.MemberInit(Expression.New(namedPropertyType), memberBindings); + } + + private static Type GetNamedPropertyType(NamedPropertyExpression property, IList expressions) + { + Type namedPropertyGenericType; + + if (property.NullCheck != null) + { + namedPropertyGenericType = SingleExpandedPropertyTypes[expressions.Count]; + } + else if (property.PageSize != null || property.CountOption != null) + { + namedPropertyGenericType = CollectionExpandedPropertyTypes[expressions.Count]; + } + else if (property.AutoSelected) + { + namedPropertyGenericType = AutoSelectedNamedPropertyTypes[expressions.Count]; + } + else + { + namedPropertyGenericType = NamedPropertyTypes[expressions.Count]; + } + + Type elementType = (property.PageSize == null && property.CountOption == null) + ? property.Value.Type + : TypeHelper.GetInnerElementType(property.Value.Type); + return namedPropertyGenericType.MakeGenericType(elementType); + } + + internal class NamedProperty : PropertyContainer + { + public string Name { get; set; } + + public T Value { get; set; } + + public bool AutoSelected { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + Contract.Assert(dictionary != null); + + if (Name != null && (includeAutoSelected || !AutoSelected)) + { + string mappedName = propertyMapper.MapProperty(Name); + if (String.IsNullOrEmpty(mappedName)) + { + throw Error.InvalidOperation(SRResources.InvalidPropertyMapping, Name); + } + + dictionary.Add(mappedName, GetValue()); + } + } + + public virtual object GetValue() + { + return Value; + } + } + + private class AutoSelectedNamedProperty : NamedProperty + { + public AutoSelectedNamedProperty() + { + AutoSelected = true; + } + } + + private class SingleExpandedProperty : NamedProperty + { + public bool IsNull { get; set; } + + public override object GetValue() + { + return IsNull ? (object)null : Value; + } + } + + private class CollectionExpandedProperty : NamedProperty + { + public int PageSize { get; set; } + + public long? TotalCount { get; set; } + + public IEnumerable Collection { get; set; } + + public override object GetValue() + { + if (TotalCount == null) + { + return new TruncatedCollection(Collection, PageSize); + } + else + { + return new TruncatedCollection(Collection, PageSize, TotalCount); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.cs new file mode 100644 index 0000000..4faea28 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.cs @@ -0,0 +1,1570 @@ +// +// This code was generated by a tool (PropertyContainer.generated.tt). +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + internal abstract partial class PropertyContainer + { + // Entityframework requires that the two different type initializers for a given type in the same query have the same set of properties in the same order. + // A $select=Prop1,Prop2,Prop3 where Prop1 and Prop2 are of the same type without this extra NamedPropertyWithNext type results in an select expression that looks like, + // c => new NamedProperty { Name = "Prop1", Value = c.Prop1, Next0 = new NamedProperty { Name = "Prop2", Value = c.Prop2 }, Next2 = new NamedProperty { Name = "Prop3", Value = c.Prop3 } }; + // Entityframework cannot translate this expression as the first NamedProperty initialization has Next and the second one doesn't. Also, Entityframework cannot + // create null's of NamedProperty. So, you cannot generate an expression like new NamedProperty { Next = null }. The exception that EF throws looks like this, + // "The type 'NamedProperty`1[SystemInt32...]' appears in two structurally incompatible initializations within a single LINQ to Entities query. + // A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order." + + private class SingleExpandedPropertyWithNext0 : SingleExpandedProperty + { + public PropertyContainer Next0 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next0.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext1 : SingleExpandedPropertyWithNext0 + { + public PropertyContainer Next1 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next1.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext2 : SingleExpandedPropertyWithNext1 + { + public PropertyContainer Next2 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next2.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext3 : SingleExpandedPropertyWithNext2 + { + public PropertyContainer Next3 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next3.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext4 : SingleExpandedPropertyWithNext3 + { + public PropertyContainer Next4 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next4.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext5 : SingleExpandedPropertyWithNext4 + { + public PropertyContainer Next5 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next5.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext6 : SingleExpandedPropertyWithNext5 + { + public PropertyContainer Next6 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next6.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext7 : SingleExpandedPropertyWithNext6 + { + public PropertyContainer Next7 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next7.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext8 : SingleExpandedPropertyWithNext7 + { + public PropertyContainer Next8 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next8.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext9 : SingleExpandedPropertyWithNext8 + { + public PropertyContainer Next9 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next9.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext10 : SingleExpandedPropertyWithNext9 + { + public PropertyContainer Next10 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next10.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext11 : SingleExpandedPropertyWithNext10 + { + public PropertyContainer Next11 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next11.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext12 : SingleExpandedPropertyWithNext11 + { + public PropertyContainer Next12 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next12.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext13 : SingleExpandedPropertyWithNext12 + { + public PropertyContainer Next13 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next13.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext14 : SingleExpandedPropertyWithNext13 + { + public PropertyContainer Next14 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next14.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext15 : SingleExpandedPropertyWithNext14 + { + public PropertyContainer Next15 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next15.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext16 : SingleExpandedPropertyWithNext15 + { + public PropertyContainer Next16 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next16.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext17 : SingleExpandedPropertyWithNext16 + { + public PropertyContainer Next17 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next17.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext18 : SingleExpandedPropertyWithNext17 + { + public PropertyContainer Next18 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next18.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext19 : SingleExpandedPropertyWithNext18 + { + public PropertyContainer Next19 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next19.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext20 : SingleExpandedPropertyWithNext19 + { + public PropertyContainer Next20 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next20.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext21 : SingleExpandedPropertyWithNext20 + { + public PropertyContainer Next21 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next21.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext22 : SingleExpandedPropertyWithNext21 + { + public PropertyContainer Next22 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next22.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext23 : SingleExpandedPropertyWithNext22 + { + public PropertyContainer Next23 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next23.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext24 : SingleExpandedPropertyWithNext23 + { + public PropertyContainer Next24 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next24.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext25 : SingleExpandedPropertyWithNext24 + { + public PropertyContainer Next25 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next25.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext26 : SingleExpandedPropertyWithNext25 + { + public PropertyContainer Next26 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next26.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext27 : SingleExpandedPropertyWithNext26 + { + public PropertyContainer Next27 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next27.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext28 : SingleExpandedPropertyWithNext27 + { + public PropertyContainer Next28 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next28.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext29 : SingleExpandedPropertyWithNext28 + { + public PropertyContainer Next29 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next29.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext30 : SingleExpandedPropertyWithNext29 + { + public PropertyContainer Next30 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next30.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class SingleExpandedPropertyWithNext31 : SingleExpandedPropertyWithNext30 + { + public PropertyContainer Next31 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next31.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private static List SingleExpandedPropertyTypes = new List { + typeof(SingleExpandedProperty<>), + typeof(SingleExpandedPropertyWithNext0<>), + typeof(SingleExpandedPropertyWithNext1<>), + typeof(SingleExpandedPropertyWithNext2<>), + typeof(SingleExpandedPropertyWithNext3<>), + typeof(SingleExpandedPropertyWithNext4<>), + typeof(SingleExpandedPropertyWithNext5<>), + typeof(SingleExpandedPropertyWithNext6<>), + typeof(SingleExpandedPropertyWithNext7<>), + typeof(SingleExpandedPropertyWithNext8<>), + typeof(SingleExpandedPropertyWithNext9<>), + typeof(SingleExpandedPropertyWithNext10<>), + typeof(SingleExpandedPropertyWithNext11<>), + typeof(SingleExpandedPropertyWithNext12<>), + typeof(SingleExpandedPropertyWithNext13<>), + typeof(SingleExpandedPropertyWithNext14<>), + typeof(SingleExpandedPropertyWithNext15<>), + typeof(SingleExpandedPropertyWithNext16<>), + typeof(SingleExpandedPropertyWithNext17<>), + typeof(SingleExpandedPropertyWithNext18<>), + typeof(SingleExpandedPropertyWithNext19<>), + typeof(SingleExpandedPropertyWithNext20<>), + typeof(SingleExpandedPropertyWithNext21<>), + typeof(SingleExpandedPropertyWithNext22<>), + typeof(SingleExpandedPropertyWithNext23<>), + typeof(SingleExpandedPropertyWithNext24<>), + typeof(SingleExpandedPropertyWithNext25<>), + typeof(SingleExpandedPropertyWithNext26<>), + typeof(SingleExpandedPropertyWithNext27<>), + typeof(SingleExpandedPropertyWithNext28<>), + typeof(SingleExpandedPropertyWithNext29<>), + typeof(SingleExpandedPropertyWithNext30<>), + typeof(SingleExpandedPropertyWithNext31<>), + }; + private class CollectionExpandedPropertyWithNext0 : CollectionExpandedProperty + { + public PropertyContainer Next0 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next0.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext1 : CollectionExpandedPropertyWithNext0 + { + public PropertyContainer Next1 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next1.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext2 : CollectionExpandedPropertyWithNext1 + { + public PropertyContainer Next2 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next2.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext3 : CollectionExpandedPropertyWithNext2 + { + public PropertyContainer Next3 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next3.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext4 : CollectionExpandedPropertyWithNext3 + { + public PropertyContainer Next4 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next4.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext5 : CollectionExpandedPropertyWithNext4 + { + public PropertyContainer Next5 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next5.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext6 : CollectionExpandedPropertyWithNext5 + { + public PropertyContainer Next6 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next6.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext7 : CollectionExpandedPropertyWithNext6 + { + public PropertyContainer Next7 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next7.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext8 : CollectionExpandedPropertyWithNext7 + { + public PropertyContainer Next8 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next8.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext9 : CollectionExpandedPropertyWithNext8 + { + public PropertyContainer Next9 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next9.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext10 : CollectionExpandedPropertyWithNext9 + { + public PropertyContainer Next10 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next10.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext11 : CollectionExpandedPropertyWithNext10 + { + public PropertyContainer Next11 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next11.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext12 : CollectionExpandedPropertyWithNext11 + { + public PropertyContainer Next12 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next12.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext13 : CollectionExpandedPropertyWithNext12 + { + public PropertyContainer Next13 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next13.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext14 : CollectionExpandedPropertyWithNext13 + { + public PropertyContainer Next14 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next14.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext15 : CollectionExpandedPropertyWithNext14 + { + public PropertyContainer Next15 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next15.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext16 : CollectionExpandedPropertyWithNext15 + { + public PropertyContainer Next16 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next16.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext17 : CollectionExpandedPropertyWithNext16 + { + public PropertyContainer Next17 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next17.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext18 : CollectionExpandedPropertyWithNext17 + { + public PropertyContainer Next18 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next18.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext19 : CollectionExpandedPropertyWithNext18 + { + public PropertyContainer Next19 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next19.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext20 : CollectionExpandedPropertyWithNext19 + { + public PropertyContainer Next20 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next20.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext21 : CollectionExpandedPropertyWithNext20 + { + public PropertyContainer Next21 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next21.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext22 : CollectionExpandedPropertyWithNext21 + { + public PropertyContainer Next22 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next22.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext23 : CollectionExpandedPropertyWithNext22 + { + public PropertyContainer Next23 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next23.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext24 : CollectionExpandedPropertyWithNext23 + { + public PropertyContainer Next24 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next24.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext25 : CollectionExpandedPropertyWithNext24 + { + public PropertyContainer Next25 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next25.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext26 : CollectionExpandedPropertyWithNext25 + { + public PropertyContainer Next26 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next26.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext27 : CollectionExpandedPropertyWithNext26 + { + public PropertyContainer Next27 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next27.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext28 : CollectionExpandedPropertyWithNext27 + { + public PropertyContainer Next28 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next28.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext29 : CollectionExpandedPropertyWithNext28 + { + public PropertyContainer Next29 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next29.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext30 : CollectionExpandedPropertyWithNext29 + { + public PropertyContainer Next30 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next30.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class CollectionExpandedPropertyWithNext31 : CollectionExpandedPropertyWithNext30 + { + public PropertyContainer Next31 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next31.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private static List CollectionExpandedPropertyTypes = new List { + typeof(CollectionExpandedProperty<>), + typeof(CollectionExpandedPropertyWithNext0<>), + typeof(CollectionExpandedPropertyWithNext1<>), + typeof(CollectionExpandedPropertyWithNext2<>), + typeof(CollectionExpandedPropertyWithNext3<>), + typeof(CollectionExpandedPropertyWithNext4<>), + typeof(CollectionExpandedPropertyWithNext5<>), + typeof(CollectionExpandedPropertyWithNext6<>), + typeof(CollectionExpandedPropertyWithNext7<>), + typeof(CollectionExpandedPropertyWithNext8<>), + typeof(CollectionExpandedPropertyWithNext9<>), + typeof(CollectionExpandedPropertyWithNext10<>), + typeof(CollectionExpandedPropertyWithNext11<>), + typeof(CollectionExpandedPropertyWithNext12<>), + typeof(CollectionExpandedPropertyWithNext13<>), + typeof(CollectionExpandedPropertyWithNext14<>), + typeof(CollectionExpandedPropertyWithNext15<>), + typeof(CollectionExpandedPropertyWithNext16<>), + typeof(CollectionExpandedPropertyWithNext17<>), + typeof(CollectionExpandedPropertyWithNext18<>), + typeof(CollectionExpandedPropertyWithNext19<>), + typeof(CollectionExpandedPropertyWithNext20<>), + typeof(CollectionExpandedPropertyWithNext21<>), + typeof(CollectionExpandedPropertyWithNext22<>), + typeof(CollectionExpandedPropertyWithNext23<>), + typeof(CollectionExpandedPropertyWithNext24<>), + typeof(CollectionExpandedPropertyWithNext25<>), + typeof(CollectionExpandedPropertyWithNext26<>), + typeof(CollectionExpandedPropertyWithNext27<>), + typeof(CollectionExpandedPropertyWithNext28<>), + typeof(CollectionExpandedPropertyWithNext29<>), + typeof(CollectionExpandedPropertyWithNext30<>), + typeof(CollectionExpandedPropertyWithNext31<>), + }; + private class AutoSelectedNamedPropertyWithNext0 : AutoSelectedNamedProperty + { + public PropertyContainer Next0 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next0.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext1 : AutoSelectedNamedPropertyWithNext0 + { + public PropertyContainer Next1 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next1.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext2 : AutoSelectedNamedPropertyWithNext1 + { + public PropertyContainer Next2 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next2.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext3 : AutoSelectedNamedPropertyWithNext2 + { + public PropertyContainer Next3 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next3.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext4 : AutoSelectedNamedPropertyWithNext3 + { + public PropertyContainer Next4 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next4.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext5 : AutoSelectedNamedPropertyWithNext4 + { + public PropertyContainer Next5 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next5.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext6 : AutoSelectedNamedPropertyWithNext5 + { + public PropertyContainer Next6 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next6.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext7 : AutoSelectedNamedPropertyWithNext6 + { + public PropertyContainer Next7 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next7.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext8 : AutoSelectedNamedPropertyWithNext7 + { + public PropertyContainer Next8 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next8.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext9 : AutoSelectedNamedPropertyWithNext8 + { + public PropertyContainer Next9 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next9.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext10 : AutoSelectedNamedPropertyWithNext9 + { + public PropertyContainer Next10 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next10.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext11 : AutoSelectedNamedPropertyWithNext10 + { + public PropertyContainer Next11 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next11.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext12 : AutoSelectedNamedPropertyWithNext11 + { + public PropertyContainer Next12 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next12.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext13 : AutoSelectedNamedPropertyWithNext12 + { + public PropertyContainer Next13 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next13.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext14 : AutoSelectedNamedPropertyWithNext13 + { + public PropertyContainer Next14 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next14.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext15 : AutoSelectedNamedPropertyWithNext14 + { + public PropertyContainer Next15 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next15.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext16 : AutoSelectedNamedPropertyWithNext15 + { + public PropertyContainer Next16 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next16.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext17 : AutoSelectedNamedPropertyWithNext16 + { + public PropertyContainer Next17 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next17.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext18 : AutoSelectedNamedPropertyWithNext17 + { + public PropertyContainer Next18 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next18.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext19 : AutoSelectedNamedPropertyWithNext18 + { + public PropertyContainer Next19 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next19.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext20 : AutoSelectedNamedPropertyWithNext19 + { + public PropertyContainer Next20 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next20.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext21 : AutoSelectedNamedPropertyWithNext20 + { + public PropertyContainer Next21 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next21.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext22 : AutoSelectedNamedPropertyWithNext21 + { + public PropertyContainer Next22 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next22.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext23 : AutoSelectedNamedPropertyWithNext22 + { + public PropertyContainer Next23 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next23.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext24 : AutoSelectedNamedPropertyWithNext23 + { + public PropertyContainer Next24 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next24.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext25 : AutoSelectedNamedPropertyWithNext24 + { + public PropertyContainer Next25 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next25.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext26 : AutoSelectedNamedPropertyWithNext25 + { + public PropertyContainer Next26 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next26.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext27 : AutoSelectedNamedPropertyWithNext26 + { + public PropertyContainer Next27 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next27.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext28 : AutoSelectedNamedPropertyWithNext27 + { + public PropertyContainer Next28 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next28.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext29 : AutoSelectedNamedPropertyWithNext28 + { + public PropertyContainer Next29 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next29.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext30 : AutoSelectedNamedPropertyWithNext29 + { + public PropertyContainer Next30 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next30.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class AutoSelectedNamedPropertyWithNext31 : AutoSelectedNamedPropertyWithNext30 + { + public PropertyContainer Next31 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next31.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private static List AutoSelectedNamedPropertyTypes = new List { + typeof(AutoSelectedNamedProperty<>), + typeof(AutoSelectedNamedPropertyWithNext0<>), + typeof(AutoSelectedNamedPropertyWithNext1<>), + typeof(AutoSelectedNamedPropertyWithNext2<>), + typeof(AutoSelectedNamedPropertyWithNext3<>), + typeof(AutoSelectedNamedPropertyWithNext4<>), + typeof(AutoSelectedNamedPropertyWithNext5<>), + typeof(AutoSelectedNamedPropertyWithNext6<>), + typeof(AutoSelectedNamedPropertyWithNext7<>), + typeof(AutoSelectedNamedPropertyWithNext8<>), + typeof(AutoSelectedNamedPropertyWithNext9<>), + typeof(AutoSelectedNamedPropertyWithNext10<>), + typeof(AutoSelectedNamedPropertyWithNext11<>), + typeof(AutoSelectedNamedPropertyWithNext12<>), + typeof(AutoSelectedNamedPropertyWithNext13<>), + typeof(AutoSelectedNamedPropertyWithNext14<>), + typeof(AutoSelectedNamedPropertyWithNext15<>), + typeof(AutoSelectedNamedPropertyWithNext16<>), + typeof(AutoSelectedNamedPropertyWithNext17<>), + typeof(AutoSelectedNamedPropertyWithNext18<>), + typeof(AutoSelectedNamedPropertyWithNext19<>), + typeof(AutoSelectedNamedPropertyWithNext20<>), + typeof(AutoSelectedNamedPropertyWithNext21<>), + typeof(AutoSelectedNamedPropertyWithNext22<>), + typeof(AutoSelectedNamedPropertyWithNext23<>), + typeof(AutoSelectedNamedPropertyWithNext24<>), + typeof(AutoSelectedNamedPropertyWithNext25<>), + typeof(AutoSelectedNamedPropertyWithNext26<>), + typeof(AutoSelectedNamedPropertyWithNext27<>), + typeof(AutoSelectedNamedPropertyWithNext28<>), + typeof(AutoSelectedNamedPropertyWithNext29<>), + typeof(AutoSelectedNamedPropertyWithNext30<>), + typeof(AutoSelectedNamedPropertyWithNext31<>), + }; + private class NamedPropertyWithNext0 : NamedProperty + { + public PropertyContainer Next0 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next0.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext1 : NamedPropertyWithNext0 + { + public PropertyContainer Next1 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next1.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext2 : NamedPropertyWithNext1 + { + public PropertyContainer Next2 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next2.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext3 : NamedPropertyWithNext2 + { + public PropertyContainer Next3 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next3.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext4 : NamedPropertyWithNext3 + { + public PropertyContainer Next4 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next4.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext5 : NamedPropertyWithNext4 + { + public PropertyContainer Next5 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next5.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext6 : NamedPropertyWithNext5 + { + public PropertyContainer Next6 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next6.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext7 : NamedPropertyWithNext6 + { + public PropertyContainer Next7 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next7.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext8 : NamedPropertyWithNext7 + { + public PropertyContainer Next8 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next8.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext9 : NamedPropertyWithNext8 + { + public PropertyContainer Next9 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next9.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext10 : NamedPropertyWithNext9 + { + public PropertyContainer Next10 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next10.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext11 : NamedPropertyWithNext10 + { + public PropertyContainer Next11 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next11.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext12 : NamedPropertyWithNext11 + { + public PropertyContainer Next12 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next12.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext13 : NamedPropertyWithNext12 + { + public PropertyContainer Next13 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next13.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext14 : NamedPropertyWithNext13 + { + public PropertyContainer Next14 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next14.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext15 : NamedPropertyWithNext14 + { + public PropertyContainer Next15 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next15.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext16 : NamedPropertyWithNext15 + { + public PropertyContainer Next16 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next16.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext17 : NamedPropertyWithNext16 + { + public PropertyContainer Next17 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next17.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext18 : NamedPropertyWithNext17 + { + public PropertyContainer Next18 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next18.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext19 : NamedPropertyWithNext18 + { + public PropertyContainer Next19 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next19.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext20 : NamedPropertyWithNext19 + { + public PropertyContainer Next20 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next20.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext21 : NamedPropertyWithNext20 + { + public PropertyContainer Next21 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next21.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext22 : NamedPropertyWithNext21 + { + public PropertyContainer Next22 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next22.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext23 : NamedPropertyWithNext22 + { + public PropertyContainer Next23 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next23.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext24 : NamedPropertyWithNext23 + { + public PropertyContainer Next24 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next24.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext25 : NamedPropertyWithNext24 + { + public PropertyContainer Next25 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next25.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext26 : NamedPropertyWithNext25 + { + public PropertyContainer Next26 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next26.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext27 : NamedPropertyWithNext26 + { + public PropertyContainer Next27 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next27.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext28 : NamedPropertyWithNext27 + { + public PropertyContainer Next28 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next28.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext29 : NamedPropertyWithNext28 + { + public PropertyContainer Next29 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next29.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext30 : NamedPropertyWithNext29 + { + public PropertyContainer Next30 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next30.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private class NamedPropertyWithNext31 : NamedPropertyWithNext30 + { + public PropertyContainer Next31 { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next31.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } + private static List NamedPropertyTypes = new List { + typeof(NamedProperty<>), + typeof(NamedPropertyWithNext0<>), + typeof(NamedPropertyWithNext1<>), + typeof(NamedPropertyWithNext2<>), + typeof(NamedPropertyWithNext3<>), + typeof(NamedPropertyWithNext4<>), + typeof(NamedPropertyWithNext5<>), + typeof(NamedPropertyWithNext6<>), + typeof(NamedPropertyWithNext7<>), + typeof(NamedPropertyWithNext8<>), + typeof(NamedPropertyWithNext9<>), + typeof(NamedPropertyWithNext10<>), + typeof(NamedPropertyWithNext11<>), + typeof(NamedPropertyWithNext12<>), + typeof(NamedPropertyWithNext13<>), + typeof(NamedPropertyWithNext14<>), + typeof(NamedPropertyWithNext15<>), + typeof(NamedPropertyWithNext16<>), + typeof(NamedPropertyWithNext17<>), + typeof(NamedPropertyWithNext18<>), + typeof(NamedPropertyWithNext19<>), + typeof(NamedPropertyWithNext20<>), + typeof(NamedPropertyWithNext21<>), + typeof(NamedPropertyWithNext22<>), + typeof(NamedPropertyWithNext23<>), + typeof(NamedPropertyWithNext24<>), + typeof(NamedPropertyWithNext25<>), + typeof(NamedPropertyWithNext26<>), + typeof(NamedPropertyWithNext27<>), + typeof(NamedPropertyWithNext28<>), + typeof(NamedPropertyWithNext29<>), + typeof(NamedPropertyWithNext30<>), + typeof(NamedPropertyWithNext31<>), + }; + + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.tt b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.tt new file mode 100644 index 0000000..2c2f6fc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/PropertyContainer.generated.tt @@ -0,0 +1,64 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// +// This code was generated by a tool (PropertyContainer.generated.tt). +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + internal abstract partial class PropertyContainer + { + // Entityframework requires that the two different type initializers for a given type in the same query have the same set of properties in the same order. + // A $select=Prop1,Prop2,Prop3 where Prop1 and Prop2 are of the same type without this extra NamedPropertyWithNext type results in an select expression that looks like, + // c => new NamedProperty { Name = "Prop1", Value = c.Prop1, Next0 = new NamedProperty { Name = "Prop2", Value = c.Prop2 }, Next2 = new NamedProperty { Name = "Prop3", Value = c.Prop3 } }; + // Entityframework cannot translate this expression as the first NamedProperty initialization has Next and the second one doesn't. Also, Entityframework cannot + // create null's of NamedProperty. So, you cannot generate an expression like new NamedProperty { Next = null }. The exception that EF throws looks like this, + // "The type 'NamedProperty`1[SystemInt32...]' appears in two structurally incompatible initializations within a single LINQ to Entities query. + // A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order." + +<# int levels = 32; +string[] types = new string[] { "SingleExpandedProperty", "CollectionExpandedProperty", "AutoSelectedNamedProperty", "NamedProperty"}; +foreach(var type in types) { +var baseClass = type; +for(int level = 0; level < levels; level++) { + var newClass = type + "WithNext" + level.ToString(); +#> + private class <#= newClass #> : <#= baseClass #> + { + public PropertyContainer Next<#= level #> { get; set; } + + public override void ToDictionaryCore(Dictionary dictionary, IPropertyMapper propertyMapper, + bool includeAutoSelected) + { + base.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + Next<#= level #>.ToDictionaryCore(dictionary, propertyMapper, includeAutoSelected); + } + } +<# +baseClass = newClass; +} + +baseClass = type; +#> + private static List <#= type #>Types = new List { + typeof(<#= type #><>), + <# +for(int level = 0; level < levels; level++) { + var newClass = type + "WithNext" + level.ToString(); + #> + typeof(<#= newClass #><>), + <# + #> + <#}#>}; + <# + }#> + + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs new file mode 100644 index 0000000..cb5b00a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs @@ -0,0 +1,897 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Applies the given to the given . + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", + Justification = "Class coupling acceptable.")] + internal class SelectExpandBinder + { + private SelectExpandQueryOption _selectExpandQuery; + private ODataQueryContext _context; + private IEdmModel _model; + private ODataQuerySettings _settings; + private string _modelID; + + public SelectExpandBinder(ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) + { + Contract.Assert(settings != null); + Contract.Assert(selectExpandQuery != null); + Contract.Assert(selectExpandQuery.Context != null); + Contract.Assert(selectExpandQuery.Context.Model != null); + Contract.Assert(settings.HandleNullPropagation != HandleNullPropagationOption.Default); + + _selectExpandQuery = selectExpandQuery; + _context = selectExpandQuery.Context; + _model = _context.Model; + _modelID = ModelContainer.GetModelID(_model); + _settings = settings; + } + + public static IQueryable Bind(IQueryable queryable, ODataQuerySettings settings, + SelectExpandQueryOption selectExpandQuery) + { + Contract.Assert(queryable != null); + + SelectExpandBinder binder = new SelectExpandBinder(settings, selectExpandQuery); + return binder.Bind(queryable); + } + + public static object Bind(object entity, ODataQuerySettings settings, + SelectExpandQueryOption selectExpandQuery) + { + Contract.Assert(entity != null); + + SelectExpandBinder binder = new SelectExpandBinder(settings, selectExpandQuery); + return binder.Bind(entity); + } + + private object Bind(object entity) + { + Contract.Assert(entity != null); + + LambdaExpression projectionLambda = GetProjectionLambda(); + + // TODO: cache this ? + return projectionLambda.Compile().DynamicInvoke(entity); + } + + private IQueryable Bind(IQueryable queryable) + { + Type elementType = _selectExpandQuery.Context.ElementClrType; + + LambdaExpression projectionLambda = GetProjectionLambda(); + + MethodInfo selectMethod = ExpressionHelperMethods.QueryableSelectGeneric.MakeGenericMethod(elementType, projectionLambda.Body.Type); + return selectMethod.Invoke(null, new object[] { queryable, projectionLambda }) as IQueryable; + } + + private LambdaExpression GetProjectionLambda() + { + Type elementType = _selectExpandQuery.Context.ElementClrType; + IEdmNavigationSource navigationSource = _selectExpandQuery.Context.NavigationSource; + ParameterExpression source = Expression.Parameter(elementType); + + // expression looks like -> new Wrapper { Instance = source , Properties = "...", Container = new PropertyContainer { ... } } + Expression projectionExpression = ProjectElement(source, _selectExpandQuery.SelectExpandClause, _context.ElementType as IEdmEntityType, navigationSource); + + // expression looks like -> source => new Wrapper { Instance = source .... } + LambdaExpression projectionLambdaExpression = Expression.Lambda(projectionExpression, source); + + return projectionLambdaExpression; + } + + internal Expression ProjectAsWrapper(Expression source, SelectExpandClause selectExpandClause, + IEdmEntityType entityType, IEdmNavigationSource navigationSource, ExpandedReferenceSelectItem expandedItem = null, + int? modelBoundPageSize = null) + { + Type elementType; + if (TypeHelper.IsCollection(source.Type, out elementType)) + { + // new CollectionWrapper { Instance = source.Select(s => new Wrapper { ... }) }; + return ProjectCollection(source, elementType, selectExpandClause, entityType, navigationSource, expandedItem, + modelBoundPageSize); + } + else + { + // new Wrapper { v1 = source.property ... } + return ProjectElement(source, selectExpandClause, entityType, navigationSource); + } + } + + internal Expression CreatePropertyNameExpression(IEdmEntityType elementType, IEdmProperty property, Expression source) + { + Contract.Assert(elementType != null); + Contract.Assert(property != null); + Contract.Assert(source != null); + + IEdmEntityType declaringType = property.DeclaringType as IEdmEntityType; + Contract.Assert(declaringType != null, "only entity types are projected."); + + // derived navigation property using cast + if (elementType != declaringType) + { + Type originalType = EdmLibHelpers.GetClrType(elementType, _model); + Type castType = EdmLibHelpers.GetClrType(declaringType, _model); + if (castType == null) + { + throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, declaringType.FullName())); + } + + if (!castType.IsAssignableFrom(originalType)) + { + // Expression + // source is navigationPropertyDeclaringType ? propertyName : null + return Expression.Condition( + test: Expression.TypeIs(source, castType), + ifTrue: Expression.Constant(property.Name), + ifFalse: Expression.Constant(null, typeof(string))); + } + } + + // Expression + // "propertyName" + return Expression.Constant(property.Name); + } + + internal Expression CreatePropertyValueExpression(IEdmEntityType elementType, IEdmProperty property, Expression source) + { + Contract.Assert(elementType != null); + Contract.Assert(property != null); + Contract.Assert(source != null); + + return CreatePropertyValueExpressionWithFilter(elementType, property, source, filterClause: null); + } + + internal Expression CreatePropertyValueExpressionWithFilter(IEdmEntityType elementType, IEdmProperty property, + Expression source, FilterClause filterClause) + { + Contract.Assert(elementType != null); + Contract.Assert(property != null); + Contract.Assert(source != null); + + IEdmEntityType declaringType = property.DeclaringType as IEdmEntityType; + Contract.Assert(declaringType != null, "only entity types are projected."); + + // derived property using cast + if (elementType != declaringType) + { + Type castType = EdmLibHelpers.GetClrType(declaringType, _model); + if (castType == null) + { + throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, + declaringType.FullName())); + } + + source = Expression.TypeAs(source, castType); + } + + string propertyName = EdmLibHelpers.GetClrPropertyName(property, _model); + Expression propertyValue = Expression.Property(source, propertyName); + Type nullablePropertyType = TypeHelper.ToNullable(propertyValue.Type); + Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); + + if (filterClause != null) + { + bool isCollection = property.Type.IsCollection(); + + IEdmTypeReference edmElementType = (isCollection ? property.Type.AsCollection().ElementType() : property.Type); + Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model); + if (clrElementType == null) + { + throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, + edmElementType.FullName())); + } + + Expression filterResult = nullablePropertyValue; + + ODataQuerySettings querySettings = new ODataQuerySettings() + { + HandleNullPropagation = HandleNullPropagationOption.True, + }; + + if (isCollection) + { + Expression filterSource = nullablePropertyValue; + + // TODO: Implement proper support for $select/$expand after $apply + Expression filterPredicate = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings); + filterResult = Expression.Call( + ExpressionHelperMethods.EnumerableWhereGeneric.MakeGenericMethod(clrElementType), + filterSource, + filterPredicate); + + nullablePropertyType = filterResult.Type; + } + else if (_settings.HandleReferenceNavigationPropertyExpandFilter) + { + LambdaExpression filterLambdaExpression = FilterBinder.Bind(null, filterClause, clrElementType, _context, querySettings) as LambdaExpression; + if (filterLambdaExpression == null) + { + throw new ODataException(Error.Format(SRResources.ExpandFilterExpressionNotLambdaExpression, + property.Name, "LambdaExpression")); + } + + ParameterExpression filterParameter = filterLambdaExpression.Parameters.First(); + Expression predicateExpression = new ReferenceNavigationPropertyExpandFilterVisitor(filterParameter, nullablePropertyValue).Visit(filterLambdaExpression.Body); + + // create expression similar to: 'predicateExpression == true ? nullablePropertyValue : null' + filterResult = Expression.Condition( + test: predicateExpression, + ifTrue: nullablePropertyValue, + ifFalse: Expression.Constant(value: null, type: nullablePropertyType)); + } + + if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // create expression similar to: 'nullablePropertyValue == null ? null : filterResult' + nullablePropertyValue = Expression.Condition( + test: Expression.Equal(nullablePropertyValue, Expression.Constant(value: null)), + ifTrue: Expression.Constant(value: null, type: nullablePropertyType), + ifFalse: filterResult); + } + else + { + nullablePropertyValue = filterResult; + } + } + + if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // create expression similar to: 'source == null ? null : propertyValue' + propertyValue = Expression.Condition( + test: Expression.Equal(source, Expression.Constant(value: null)), + ifTrue: Expression.Constant(value: null, type: nullablePropertyType), + ifFalse: nullablePropertyValue); + } + else + { + // need to cast this to nullable as EF would fail while materializing if the property is not nullable and source is null. + propertyValue = nullablePropertyValue; + } + + return propertyValue; + } + + private class ReferenceNavigationPropertyExpandFilterVisitor : ExpressionVisitor + { + private Expression _source; + private ParameterExpression _parameterExpression; + + public ReferenceNavigationPropertyExpandFilterVisitor(ParameterExpression parameterExpression, Expression source) + { + _source = source; + _parameterExpression = parameterExpression; + } + + protected override Expression VisitParameter(ParameterExpression node) + { + if (node != _parameterExpression) + { + throw new ODataException(Error.Format(SRResources.ReferenceNavigationPropertyExpandFilterVisitorUnexpectedParameter, node.Name)); + } + + return _source; + } + } + + // Generates the expression + // source => new Wrapper { Instance = source, Container = new PropertyContainer { ..expanded properties.. } } + private Expression ProjectElement(Expression source, SelectExpandClause selectExpandClause, IEdmEntityType entityType, IEdmNavigationSource navigationSource) + { + Contract.Assert(source != null); + + Type elementType = source.Type; + Type wrapperType = typeof(SelectExpandWrapper<>).MakeGenericType(elementType); + List wrapperTypeMemberAssignments = new List(); + + PropertyInfo wrapperProperty; + bool isInstancePropertySet = false; + bool isTypeNamePropertySet = false; + bool isContainerPropertySet = false; + + // Initialize property 'ModelID' on the wrapper class. + // source = new Wrapper { ModelID = 'some-guid-id' } + wrapperProperty = wrapperType.GetProperty("ModelID"); + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, Expression.Constant(_modelID))); + + if (IsSelectAll(selectExpandClause)) + { + // Initialize property 'Instance' on the wrapper class + wrapperProperty = wrapperType.GetProperty("Instance"); + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, source)); + + wrapperProperty = wrapperType.GetProperty("UseInstanceForProperties"); + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, Expression.Constant(true))); + isInstancePropertySet = true; + } + else + { + // Initialize property 'TypeName' on the wrapper class as we don't have the instance. + Expression typeName = CreateTypeNameExpression(source, entityType, _model); + if (typeName != null) + { + isTypeNamePropertySet = true; + wrapperProperty = wrapperType.GetProperty("InstanceType"); + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, typeName)); + } + } + + // Initialize the property 'Container' on the wrapper class + // source => new Wrapper { Container = new PropertyContainer { .... } } + if (selectExpandClause != null) + { + Dictionary propertiesToExpand = GetPropertiesToExpandInQuery(selectExpandClause); + ISet autoSelectedProperties; + + ISet propertiesToInclude = GetPropertiesToIncludeInQuery(selectExpandClause, entityType, navigationSource, _model, out autoSelectedProperties); + bool isSelectingOpenTypeSegments = GetSelectsOpenTypeSegments(selectExpandClause, entityType); + + if (propertiesToExpand.Count > 0 || propertiesToInclude.Count > 0 || autoSelectedProperties.Count > 0 || isSelectingOpenTypeSegments) + { + Expression propertyContainerCreation = + BuildPropertyContainer(entityType, source, propertiesToExpand, propertiesToInclude, autoSelectedProperties, isSelectingOpenTypeSegments); + + if (propertyContainerCreation != null) + { + wrapperProperty = wrapperType.GetProperty("Container"); + Contract.Assert(wrapperProperty != null); + + wrapperTypeMemberAssignments.Add(Expression.Bind(wrapperProperty, propertyContainerCreation)); + isContainerPropertySet = true; + } + } + } + + Type wrapperGenericType = GetWrapperGenericType(isInstancePropertySet, isTypeNamePropertySet, isContainerPropertySet); + wrapperType = wrapperGenericType.MakeGenericType(elementType); + return Expression.MemberInit(Expression.New(wrapperType), wrapperTypeMemberAssignments); + } + + private static bool GetSelectsOpenTypeSegments(SelectExpandClause selectExpandClause, IEdmEntityType entityType) + { + if (!entityType.IsOpen) + { + return false; + } + + if (IsSelectAll(selectExpandClause)) + { + return true; + } + + return selectExpandClause.SelectedItems.OfType().Any(x => x.SelectedPath.LastSegment is DynamicPathSegment); + } + + private Expression CreateTotalCountExpression(Expression source, ExpandedReferenceSelectItem expandItem) + { + Expression countExpression = Expression.Constant(null, typeof(long?)); + if (expandItem.CountOption == null || !expandItem.CountOption.Value) + { + return countExpression; + } + + Type elementType; + if (!TypeHelper.IsCollection(source.Type, out elementType)) + { + return countExpression; + } + + MethodInfo countMethod; + if (typeof(IQueryable).IsAssignableFrom(source.Type)) + { + countMethod = ExpressionHelperMethods.QueryableCountGeneric.MakeGenericMethod(elementType); + } + else + { + countMethod = ExpressionHelperMethods.EnumerableCountGeneric.MakeGenericMethod(elementType); + } + + // call Count() method. + countExpression = Expression.Call(null, countMethod, new[] { source }); + + if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // source == null ? null : countExpression + return Expression.Condition( + test: Expression.Equal(source, Expression.Constant(null)), + ifTrue: Expression.Constant(null, typeof(long?)), + ifFalse: ExpressionHelpers.ToNullable(countExpression)); + } + else + { + return countExpression; + } + } + + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling acceptable")] + private Expression BuildPropertyContainer(IEdmEntityType elementType, Expression source, + Dictionary propertiesToExpand, + ISet propertiesToInclude, ISet autoSelectedProperties, bool isSelectingOpenTypeSegments) + { + IList includedProperties = new List(); + + foreach (KeyValuePair kvp in propertiesToExpand) + { + IEdmNavigationProperty propertyToExpand = kvp.Key; + ExpandedReferenceSelectItem expandItem = kvp.Value; + + SelectExpandClause projection = GetOrCreateSelectExpandClause(kvp); + + ModelBoundQuerySettings querySettings = EdmLibHelpers.GetModelBoundQuerySettings(propertyToExpand, + propertyToExpand.ToEntityType(), + _context.Model); + + Expression propertyName = CreatePropertyNameExpression(elementType, propertyToExpand, source); + Expression propertyValue = CreatePropertyValueExpressionWithFilter(elementType, propertyToExpand, source, + expandItem.FilterOption); + Expression nullCheck = GetNullCheckExpression(propertyToExpand, propertyValue, projection); + + Expression countExpression = CreateTotalCountExpression(propertyValue, expandItem); + + // projection can be null if the expanded navigation property is not further projected or expanded. + if (projection != null) + { + int? modelBoundPageSize = querySettings == null ? null : querySettings.PageSize; + propertyValue = ProjectAsWrapper(propertyValue, projection, propertyToExpand.ToEntityType(), expandItem.NavigationSource, expandItem, modelBoundPageSize); + } + + NamedPropertyExpression propertyExpression = new NamedPropertyExpression(propertyName, propertyValue); + if (projection != null) + { + if (!propertyToExpand.Type.IsCollection()) + { + propertyExpression.NullCheck = nullCheck; + } + else if (_settings.PageSize.HasValue) + { + propertyExpression.PageSize = _settings.PageSize.Value; + } + else + { + if (querySettings != null && querySettings.PageSize.HasValue) + { + propertyExpression.PageSize = querySettings.PageSize.Value; + } + } + + propertyExpression.TotalCount = countExpression; + propertyExpression.CountOption = expandItem.CountOption; + } + + includedProperties.Add(propertyExpression); + } + + foreach (IEdmStructuralProperty propertyToInclude in propertiesToInclude) + { + Expression propertyName = CreatePropertyNameExpression(elementType, propertyToInclude, source); + Expression propertyValue = CreatePropertyValueExpression(elementType, propertyToInclude, source); + includedProperties.Add(new NamedPropertyExpression(propertyName, propertyValue)); + } + + foreach (IEdmStructuralProperty propertyToInclude in autoSelectedProperties) + { + Expression propertyName = CreatePropertyNameExpression(elementType, propertyToInclude, source); + Expression propertyValue = CreatePropertyValueExpression(elementType, propertyToInclude, source); + includedProperties.Add(new NamedPropertyExpression(propertyName, propertyValue) { AutoSelected = true }); + } + + if (isSelectingOpenTypeSegments) + { + var dynamicPropertyDictionary = EdmLibHelpers.GetDynamicPropertyDictionary(elementType, _model); + if (dynamicPropertyDictionary != null) + { + Expression propertyName = Expression.Constant(dynamicPropertyDictionary.Name); + Expression propertyValue = Expression.Property(source, dynamicPropertyDictionary.Name); + Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); + if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // source == null ? null : propertyValue + propertyValue = Expression.Condition( + test: Expression.Equal(source, Expression.Constant(value: null)), + ifTrue: Expression.Constant(value: null, type: TypeHelper.ToNullable(propertyValue.Type)), + ifFalse: nullablePropertyValue); + } + else + { + propertyValue = nullablePropertyValue; + } + + includedProperties.Add(new NamedPropertyExpression(propertyName, propertyValue)); + } + } + + // create a property container that holds all these property names and values. + return PropertyContainer.CreatePropertyContainer(includedProperties); + } + + private static SelectExpandClause GetOrCreateSelectExpandClause(KeyValuePair propertyToExpand) + { + // for normal $expand=.... + ExpandedNavigationSelectItem expandNavigationSelectItem = propertyToExpand.Value as ExpandedNavigationSelectItem; + if (expandNavigationSelectItem != null) + { + return expandNavigationSelectItem.SelectAndExpand; + } + + // for $expand=..../$ref, just includes the keys properties. + IList selectItems = new List(); + foreach (IEdmStructuralProperty keyProperty in propertyToExpand.Key.ToEntityType().Key()) + { + selectItems.Add(new PathSelectItem(new ODataSelectPath(new PropertySegment(keyProperty)))); + } + + return new SelectExpandClause(selectItems, false); + } + + private Expression AddOrderByQueryForSource(Expression source, OrderByClause orderbyClause, Type elementType) + { + if (orderbyClause != null) + { + // TODO: Implement proper support for $select/$expand after $apply + ODataQuerySettings querySettings = new ODataQuerySettings() + { + HandleNullPropagation = HandleNullPropagationOption.True, + }; + + LambdaExpression orderByExpression = + FilterBinder.Bind(null, orderbyClause, elementType, _context, querySettings); + source = ExpressionHelpers.OrderBy(source, orderByExpression, elementType, orderbyClause.Direction); + } + + return source; + } + + private Expression GetNullCheckExpression(IEdmNavigationProperty propertyToExpand, Expression propertyValue, + SelectExpandClause projection) + { + if (projection == null || propertyToExpand.Type.IsCollection()) + { + return null; + } + + if (IsSelectAll(projection) || !propertyToExpand.ToEntityType().Key().Any()) + { + return Expression.Equal(propertyValue, Expression.Constant(null)); + } + + Expression keysNullCheckExpression = null; + foreach (var key in propertyToExpand.ToEntityType().Key()) + { + var propertyValueExpression = CreatePropertyValueExpressionWithFilter(propertyToExpand.ToEntityType(), key, propertyValue, null); + var keyExpression = Expression.Equal( + propertyValueExpression, + Expression.Constant(null, propertyValueExpression.Type)); + + keysNullCheckExpression = keysNullCheckExpression == null + ? keyExpression + : Expression.And(keysNullCheckExpression, keyExpression); + } + + return keysNullCheckExpression; + } + + // new CollectionWrapper { Instance = source.Select((ElementType element) => new Wrapper { }) } + private Expression ProjectCollection(Expression source, Type elementType, SelectExpandClause selectExpandClause, IEdmEntityType entityType, IEdmNavigationSource navigationSource, ExpandedReferenceSelectItem expandedItem, int? modelBoundPageSize) + { + ParameterExpression element = Expression.Parameter(elementType); + + // expression + // new Wrapper { } + Expression projection = ProjectElement(element, selectExpandClause, entityType, navigationSource); + + // expression + // (ElementType element) => new Wrapper { } + LambdaExpression selector = Expression.Lambda(projection, element); + + if (expandedItem != null) + { + source = AddOrderByQueryForSource(source, expandedItem.OrderByOption, elementType); + } + + if (_settings.PageSize.HasValue || modelBoundPageSize.HasValue || + (expandedItem != null && (expandedItem.TopOption.HasValue || expandedItem.SkipOption.HasValue))) + { + // nested paging. Need to apply order by first, and take one more than page size as we need to know + // whether the collection was truncated or not while generating next page links. + IEnumerable properties = + entityType.Key().Any() + ? entityType.Key() + : entityType + .StructuralProperties() + .Where(property => property.Type.IsPrimitive() && !property.Type.IsStream()) + .OrderBy(property => property.Name); + + if (expandedItem == null || expandedItem.OrderByOption == null) + { + bool alreadyOrdered = false; + foreach (var prop in properties) + { + source = ExpressionHelpers.OrderByPropertyExpression(source, prop.Name, elementType, + alreadyOrdered); + if (!alreadyOrdered) + { + alreadyOrdered = true; + } + } + } + + if (expandedItem != null && expandedItem.SkipOption.HasValue) + { + Contract.Assert(expandedItem.SkipOption.Value <= Int32.MaxValue); + source = ExpressionHelpers.Skip(source, (int)expandedItem.SkipOption.Value, elementType, + _settings.EnableConstantParameterization); + } + + if (expandedItem != null && expandedItem.TopOption.HasValue) + { + Contract.Assert(expandedItem.TopOption.Value <= Int32.MaxValue); + source = ExpressionHelpers.Take(source, (int)expandedItem.TopOption.Value, elementType, + _settings.EnableConstantParameterization); + } + + // don't page nested collections if EnableCorrelatedSubqueryBuffering is enabled + if (expandedItem == null || !_settings.EnableCorrelatedSubqueryBuffering) + { + if (_settings.PageSize.HasValue) + { + source = ExpressionHelpers.Take(source, _settings.PageSize.Value + 1, elementType, + _settings.EnableConstantParameterization); + } + else if (_settings.ModelBoundPageSize.HasValue) + { + source = ExpressionHelpers.Take(source, modelBoundPageSize.Value + 1, elementType, + _settings.EnableConstantParameterization); + } + } + } + + // expression + // source.Select((ElementType element) => new Wrapper { }) + var selectMethod = GetSelectMethod(elementType, projection.Type); + Expression selectedExpresion = Expression.Call(selectMethod, source, selector); + + // Append ToList() to collection as a hint to LINQ provider to buffer correlated subqueries in memory and avoid executing N+1 queries + if (_settings.EnableCorrelatedSubqueryBuffering) + { + selectedExpresion = Expression.Call(ExpressionHelperMethods.QueryableToList.MakeGenericMethod(projection.Type), selectedExpresion); + } + + if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) + { + // source == null ? null : projectedCollection + return Expression.Condition( + test: Expression.Equal(source, Expression.Constant(null)), + ifTrue: Expression.Constant(null, selectedExpresion.Type), + ifFalse: selectedExpresion); + } + else + { + return selectedExpresion; + } + } + + // OData formatter requires the type name of the entity that is being written if the type has derived types. + // Expression + // source is GrandChild ? "GrandChild" : ( source is Child ? "Child" : "Root" ) + // Notice that the order is important here. The most derived type must be the first to check. + // If entity framework had a way to figure out the type name without selecting the whole object, we don't have to do this magic. + internal static Expression CreateTypeNameExpression(Expression source, IEdmEntityType elementType, IEdmModel model) + { + IReadOnlyList derivedTypes = GetAllDerivedTypes(elementType, model); + if (derivedTypes.Count == 0) + { + // no inheritance. + return null; + } + else + { + Expression expression = Expression.Constant(elementType.FullName()); + for (int i = 0; i < derivedTypes.Count; i++) + { + Type clrType = EdmLibHelpers.GetClrType(derivedTypes[i], model); + if (clrType == null) + { + throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, derivedTypes[0].FullName())); + } + + expression = Expression.Condition( + test: Expression.TypeIs(source, clrType), + ifTrue: Expression.Constant(derivedTypes[i].FullName()), + ifFalse: expression); + } + + return expression; + } + } + + // returns all the derived types (direct and indirect) of baseType ordered according to their depth. The direct children + // are the first in the list. + private static IReadOnlyList GetAllDerivedTypes(IEdmEntityType baseType, IEdmModel model) + { + IEnumerable allEntityTypes = model.SchemaElements.OfType(); + + List> derivedTypes = new List>(); + foreach (IEdmEntityType entityType in allEntityTypes) + { + int distance = IsDerivedTypeOf(entityType, baseType); + if (distance > 0) + { + derivedTypes.Add(Tuple.Create(distance, entityType)); + } + } + + return derivedTypes.OrderBy(tuple => tuple.Item1).Select(tuple => tuple.Item2).ToList(); + } + + // returns -1 if type does not derive from baseType and a positive number representing the distance + // between them if it does. + private static int IsDerivedTypeOf(IEdmEntityType type, IEdmEntityType baseType) + { + int distance = 0; + while (type != null) + { + if (baseType == type) + { + return distance; + } + + type = type.BaseEntityType(); + distance++; + } + + return -1; + } + + private static MethodInfo GetSelectMethod(Type elementType, Type resultType) + { + return ExpressionHelperMethods.EnumerableSelectGeneric.MakeGenericMethod(elementType, resultType); + } + + private static Dictionary GetPropertiesToExpandInQuery(SelectExpandClause selectExpandClause) + { + Dictionary properties = new Dictionary(); + + foreach (SelectItem selectItem in selectExpandClause.SelectedItems) + { + ExpandedReferenceSelectItem expandItem = selectItem as ExpandedReferenceSelectItem; + if (expandItem != null) + { + SelectExpandNode.ValidatePathIsSupported(expandItem.PathToNavigationProperty); + NavigationPropertySegment navigationSegment = expandItem.PathToNavigationProperty.LastSegment as NavigationPropertySegment; + if (navigationSegment == null) + { + throw new ODataException(SRResources.UnsupportedSelectExpandPath); + } + + properties[navigationSegment.NavigationProperty] = expandItem; + } + } + + return properties; + } + + private static ISet GetPropertiesToIncludeInQuery( + SelectExpandClause selectExpandClause, IEdmEntityType entityType, IEdmNavigationSource navigationSource, IEdmModel model, out ISet autoSelectedProperties) + { + autoSelectedProperties = new HashSet(); + HashSet propertiesToInclude = new HashSet(); + + IEnumerable selectedItems = selectExpandClause.SelectedItems; + if (!IsSelectAll(selectExpandClause)) + { + // only select requested properties and keys. + foreach (PathSelectItem pathSelectItem in selectedItems.OfType()) + { + SelectExpandNode.ValidatePathIsSupported(pathSelectItem.SelectedPath); + PropertySegment structuralPropertySegment = pathSelectItem.SelectedPath.LastSegment as PropertySegment; + if (structuralPropertySegment != null) + { + propertiesToInclude.Add(structuralPropertySegment.Property); + } + } + + // add keys + foreach (IEdmStructuralProperty keyProperty in entityType.Key()) + { + if (!propertiesToInclude.Contains(keyProperty)) + { + autoSelectedProperties.Add(keyProperty); + } + } + + // add concurrency properties, if not added + if (navigationSource != null && model != null) + { + IEnumerable concurrencyProperties = model.GetConcurrencyProperties(navigationSource); + foreach (IEdmStructuralProperty concurrencyProperty in concurrencyProperties) + { + if (!propertiesToInclude.Contains(concurrencyProperty)) + { + autoSelectedProperties.Add(concurrencyProperty); + } + } + } + } + + return propertiesToInclude; + } + + private static bool IsSelectAll(SelectExpandClause selectExpandClause) + { + if (selectExpandClause == null) + { + return true; + } + + if (selectExpandClause.AllSelected || selectExpandClause.SelectedItems.OfType().Any()) + { + return true; + } + + return false; + } + + private static Type GetWrapperGenericType(bool isInstancePropertySet, bool isTypeNamePropertySet, bool isContainerPropertySet) + { + if (isInstancePropertySet) + { + // select all + Contract.Assert(!isTypeNamePropertySet, "we don't set type name if we set instance as it can be figured from instance"); + + return isContainerPropertySet ? typeof(SelectAllAndExpand<>) : typeof(SelectAll<>); + } + else + { + Contract.Assert(isContainerPropertySet, "if it is not select all, container should hold something"); + + return isTypeNamePropertySet ? typeof(SelectSomeAndInheritance<>) : typeof(SelectSome<>); + } + } + + /* Entityframework requires that the two different type initializers for a given type in the same query have the + same set of properties in the same order. + + A ~/People?$select=Name&$expand=Friend results in a select expression that has two SelectExpandWrapper + expressions, one for the root level person and the second for the expanded Friend person. + The first wrapper has the Container property set (contains Name and Friend values) where as the second wrapper + has the Instance property set as it contains all the properties of the expanded person. + + The below four classes workaround that entity framework limitation by defining a seperate type for each + property selection combination possible. */ + + private class SelectAllAndExpand : SelectExpandWrapper + { + } + + private class SelectAll : SelectExpandWrapper + { + } + + private class SelectSomeAndInheritance : SelectExpandWrapper + { + } + + private class SelectSome : SelectAllAndExpand + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapper.cs new file mode 100644 index 0000000..ff23998 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapper.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + internal abstract class SelectExpandWrapper : IEdmEntityObject, ISelectExpandWrapper + { + private static readonly IPropertyMapper DefaultPropertyMapper = new IdentityPropertyMapper(); + private static readonly Func _mapperProvider = + (IEdmModel m, IEdmStructuredType t) => DefaultPropertyMapper; + + private Dictionary _containerDict; + private TypedEdmEntityObject _typedEdmEntityObject; + + /// + /// Gets or sets the property container that contains the properties being expanded. + /// + public PropertyContainer Container { get; set; } + + /// + /// An ID to uniquely identify the model in the . + /// + public string ModelID { get; set; } + + /// + public object UntypedInstance { get; set; } + + /// + /// Gets or sets the instance type name + /// + public string InstanceType { get; set; } + + /// + /// Indicates whether the underlying instance can be used to obtain property values. + /// + public bool UseInstanceForProperties { get; set; } + + /// + public IEdmTypeReference GetEdmType() + { + IEdmModel model = GetModel(); + + if (InstanceType != null) + { + IEdmEntityType entityType = model.FindType(InstanceType) as IEdmEntityType; + return entityType.ToEdmTypeReference(true); + } + + Type elementType = GetElementType(); + return model.GetEdmTypeReference(elementType); + } + + /// + public bool TryGetPropertyValue(string propertyName, out object value) + { + // look into the container first to see if it has that property. container would have it + // if the property was expanded. + if (Container != null) + { + _containerDict = _containerDict ?? Container.ToDictionary(DefaultPropertyMapper, includeAutoSelected: true); + if (_containerDict.TryGetValue(propertyName, out value)) + { + return true; + } + } + + // fall back to the instance. + if (UseInstanceForProperties && UntypedInstance != null) + { + _typedEdmEntityObject = _typedEdmEntityObject ?? + new TypedEdmEntityObject(UntypedInstance, GetEdmType() as IEdmEntityTypeReference, GetModel()); + + return _typedEdmEntityObject.TryGetPropertyValue(propertyName, out value); + } + + value = null; + return false; + } + + public IDictionary ToDictionary() + { + return ToDictionary(_mapperProvider); + } + + public IDictionary ToDictionary(Func mapperProvider) + { + if (mapperProvider == null) + { + throw Error.ArgumentNull("mapperProvider"); + } + + Dictionary dictionary = new Dictionary(); + IEdmStructuredType type = GetEdmType().AsStructured().StructuredDefinition(); + + IPropertyMapper mapper = mapperProvider(GetModel(), type); + if (mapper == null) + { + throw Error.InvalidOperation(SRResources.InvalidPropertyMapper, typeof(IPropertyMapper).FullName, + type.FullTypeName()); + } + + if (Container != null) + { + dictionary = Container.ToDictionary(mapper, includeAutoSelected: false); + } + + // The user asked for all the structural properties on this instance. + if (UseInstanceForProperties && UntypedInstance != null) + { + foreach (IEdmStructuralProperty property in type.StructuralProperties()) + { + object propertyValue; + if (TryGetPropertyValue(property.Name, out propertyValue)) + { + string mappingName = mapper.MapProperty(property.Name); + if (String.IsNullOrWhiteSpace(mappingName)) + { + throw Error.InvalidOperation(SRResources.InvalidPropertyMapping, property.Name); + } + + dictionary[mappingName] = propertyValue; + } + } + } + + return dictionary; + } + + protected abstract Type GetElementType(); + + private IEdmModel GetModel() + { + Contract.Assert(ModelID != null); + + return ModelContainer.GetModel(ModelID); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs new file mode 100644 index 0000000..355754a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperConverter.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using Newtonsoft.Json; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Represents a custom to serialize instances to JSON. + /// + internal class SelectExpandWrapperConverter : JsonConverter + { + private static readonly Func _mapperProvider = + (IEdmModel model, IEdmStructuredType type) => new JsonPropertyNameMapper(model, type); + + public override bool CanConvert(Type objectType) + { + if (objectType == null) + { + throw Error.ArgumentNull("objectType"); + } + + return objectType.IsAssignableFrom(typeof(ISelectExpandWrapper)); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + Contract.Assert(false, "SelectExpandWrapper is internal and should never be deserialized into."); + throw new NotImplementedException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + ISelectExpandWrapper selectExpandWrapper = value as ISelectExpandWrapper; + if (selectExpandWrapper != null) + { + serializer.Serialize(writer, selectExpandWrapper.ToDictionary(_mapperProvider)); + } + } + + private class JsonPropertyNameMapper : IPropertyMapper + { + private IEdmModel _model; + private IEdmStructuredType _type; + + public JsonPropertyNameMapper(IEdmModel model, IEdmStructuredType type) + { + _model = model; + _type = type; + } + + public string MapProperty(string propertyName) + { + IEdmProperty property = _type.Properties().Single(s => s.Name == propertyName); + PropertyInfo info = GetPropertyInfo(property); + JsonPropertyAttribute jsonProperty = GetJsonProperty(info); + if (jsonProperty != null && !String.IsNullOrWhiteSpace(jsonProperty.PropertyName)) + { + return jsonProperty.PropertyName; + } + else + { + return property.Name; + } + } + + private PropertyInfo GetPropertyInfo(IEdmProperty property) + { + ClrPropertyInfoAnnotation clrPropertyAnnotation = _model.GetAnnotationValue(property); + if (clrPropertyAnnotation != null) + { + return clrPropertyAnnotation.ClrPropertyInfo; + } + + ClrTypeAnnotation clrTypeAnnotation = _model.GetAnnotationValue(property.DeclaringType); + Contract.Assert(clrTypeAnnotation != null); + + PropertyInfo info = clrTypeAnnotation.ClrType.GetProperty(property.Name); + Contract.Assert(info != null); + + return info; + } + + private static JsonPropertyAttribute GetJsonProperty(PropertyInfo property) + { + return property.GetCustomAttributes(typeof(JsonPropertyAttribute), inherit: false) + .OfType().SingleOrDefault(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperOfT.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperOfT.cs new file mode 100644 index 0000000..a12fe90 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandWrapperOfT.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// Represents a container class that contains properties that are either selected or expanded using $select and $expand. + /// + /// The element being selected and expanded. + [JsonConverter(typeof(SelectExpandWrapperConverter))] + internal class SelectExpandWrapper : SelectExpandWrapper + { + /// + /// Gets or sets the instance of the element being selected and expanded. + /// + public TElement Instance + { + get { return (TElement)UntypedInstance; } + set { UntypedInstance = value; } + } + + protected override Type GetElementType() + { + return UntypedInstance == null ? typeof(TElement) : UntypedInstance.GetType(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/UriFunctionsBinder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/UriFunctionsBinder.cs new file mode 100644 index 0000000..d2674c6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Expressions/UriFunctionsBinder.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// This class helps to bind uri functions to CLR. + /// For creating an Expression and apply it on a Queryable collection, we must get the CLR information, + /// i.e MethodInfo of each EdmFunction which is mentioned in the EdmModel. + /// If you add a custom uri function in OData.Core via 'CustomUriFunctions' class, you must bind it to it's MethodInfo. + /// + internal static class UriFunctionsBinder + { + private static Dictionary methodLiteralSignaturesToMethodInfo = new Dictionary(); + + private static object locker = new object(); + + /// + /// Bind the given function name to the given MethodInfo. + /// The binding helps to create an Expression out of the method. + /// You can bind a static method, a static extension method, and an instance method. + /// You should be carefull about binding instance methods - the declaring type of the method i.e the instance type, + /// will be considered as the first argument of the function. + /// + /// The uri function name that appears in the OData request uri. + /// The MethodInfo to bind the given function name. + /// Function name argument is Null or empty + /// MethodInfo argument is Null + /// The given FunctionName is already binded to another MethodInfo. + public static void BindUriFunctionName(string functionName, MethodInfo methodInfo) + { + // Validation + if (String.IsNullOrEmpty(functionName)) + { + throw Error.ArgumentNull("functionName"); + } + + if (methodInfo == null) + { + throw Error.ArgumentNull("methodInfo"); + } + + // Get literal description of the method + string methodLiteralSignature = GetMethodLiteralSignature(functionName, methodInfo); + + lock (locker) + { + if (methodLiteralSignaturesToMethodInfo.ContainsKey(methodLiteralSignature)) + { + throw new ODataException(String.Format(CultureInfo.InvariantCulture, + SRResources.UriFunctionClrBinderAlreadyBound, + methodLiteralSignature)); + } + + methodLiteralSignaturesToMethodInfo.Add(methodLiteralSignature, methodInfo); + } + } + + /// + /// Unbind the given function name from the given MethodInfo. + /// + /// The uri function name to unbind. + /// The MethodInfo to unbind from the given function name. + /// Function name argument is Null or empty + /// MethodInfo argument is Null + /// 'True' if function has unbinded. 'False' otherwise. + public static bool UnbindUriFunctionName(string functionName, MethodInfo methodInfo) + { + // Validation + if (String.IsNullOrEmpty(functionName)) + { + throw Error.ArgumentNull("functionName"); + } + + if (methodInfo == null) + { + throw Error.ArgumentNull("methodInfo"); + } + + // Get literal description of the method + string methodLiteralSignature = GetMethodLiteralSignature(functionName, methodInfo); + + lock (locker) + { + return methodLiteralSignaturesToMethodInfo.Remove(methodLiteralSignature); + } + } + + public static bool TryGetMethodInfo(string functionName, IEnumerable methodArgumentsType, + out MethodInfo methodInfo) + { + // Validation + if (String.IsNullOrEmpty(functionName)) + { + throw Error.ArgumentNull("functionName"); + } + + if (methodArgumentsType == null) + { + throw Error.ArgumentNull("methodArgumentsType"); + } + + // Get literal description of the method + string methodLiteralSignature = GetMethodLiteralSignature(functionName, methodArgumentsType); + + lock (locker) + { + return methodLiteralSignaturesToMethodInfo.TryGetValue(methodLiteralSignature, out methodInfo); + } + } + + /// + /// Get a string describing the given method. + /// + private static string GetMethodLiteralSignature(string methodName, MethodInfo methodInfo) + { + // Get the arguments type of the given MethodInfo + IEnumerable methodArgumentsType = + methodInfo.GetParameters().Select(parameter => parameter.ParameterType); + + // If method is not static - instance, the declaring type is the first argument. + if (!methodInfo.IsStatic) + { + methodArgumentsType = new Type[] { methodInfo.DeclaringType }.Concat(methodArgumentsType); + } + + return GetMethodLiteralSignature(methodName, methodArgumentsType); + } + + /// + /// Creates a string describing the function signature. + /// 'methodName(argTypeName1,argTypeName2,argTypeName3..)' + /// + private static string GetMethodLiteralSignature(string methodName, IEnumerable methodArgumentsType) + { + StringBuilder builder = new StringBuilder(); + string parameterSeparator = String.Empty; + builder.Append(methodName); + builder.Append('('); + foreach (Type type in methodArgumentsType) + { + builder.Append(parameterSeparator); + parameterSeparator = ", "; + + builder.Append(type.FullName); + } + + builder.Append(')'); + return builder.ToString(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterAttribute.cs new file mode 100644 index 0000000..838e336 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterAttribute.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a class or property + /// correlate to OData's $filter query option settings. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] + [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", + Justification = "Don't want those argument to be retrievable")] + public sealed class FilterAttribute : Attribute + { + private bool? _defaultEnableFilter; + private bool _disable; + private readonly Dictionary _filterConfigurations = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + public FilterAttribute() + { + _defaultEnableFilter = true; + } + + /// + /// Initializes a new instance of the class + /// with the name of allowed $filter properties. + /// + public FilterAttribute(params string[] properties) + { + foreach (var property in properties) + { + _filterConfigurations.Add(property, true); + } + } + + /// + /// Gets or sets the $filter configuration of properties. + /// + public Dictionary FilterConfigurations + { + get + { + return _filterConfigurations; + } + } + + /// + /// Represents whether the $filter can be applied on those properties. + /// + public bool Disabled + { + get + { + return _disable; + } + set + { + _disable = value; + List keys = _filterConfigurations.Keys.ToList(); + foreach (var property in keys) + { + _filterConfigurations[property] = !_disable; + } + + if (_filterConfigurations.Count == 0) + { + _defaultEnableFilter = !_disable; + } + } + } + + internal bool? DefaultEnableFilter + { + get + { + return _defaultEnableFilter; + } + set + { + _defaultEnableFilter = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterQueryOption.cs new file mode 100644 index 0000000..a7e1fd5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/FilterQueryOption.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a $filter OData query option for querying. + /// + public class FilterQueryOption + { + private FilterClause _filterClause; + private ODataQueryOptionParser _queryOptionParser; + + /// + /// Initialize a new instance of based on the raw $filter value and + /// an EdmModel from . + /// + /// The raw value for $filter query. It can be null or empty. + /// The which contains the and some type information + /// The which is used to parse the query option. + public FilterQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + Context = context; + RawValue = rawValue; + Validator = FilterQueryValidator.GetFilterQueryValidator(context); + _queryOptionParser = queryOptionParser; + } + + // This constructor is intended for unit testing only. + internal FilterQueryOption(string rawValue, ODataQueryContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + Context = context; + RawValue = rawValue; + Validator = FilterQueryValidator.GetFilterQueryValidator(context); + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$filter", rawValue } }, + context.RequestContainer); + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets or sets the Filter Query Validator + /// + public FilterQueryValidator Validator { get; set; } + + /// + /// Gets the parsed for this query option. + /// + public FilterClause FilterClause + { + get + { + if (_filterClause == null) + { + _filterClause = _queryOptionParser.ParseFilter(); + SingleValueNode filterExpression = _filterClause.Expression.Accept( + new ParameterAliasNodeTranslator(_queryOptionParser.ParameterAliasNodes)) as SingleValueNode; + filterExpression = filterExpression ?? new ConstantNode(null); + _filterClause = new FilterClause(filterExpression, _filterClause.RangeVariable); + } + + return _filterClause; + } + } + + /// + /// Gets the raw $filter value. + /// + public string RawValue { get; private set; } + + /// + /// Apply the filter query to the given IQueryable. + /// + /// + /// The property specifies + /// how this method should handle null propagation. + /// + /// The original . + /// The that contains all the query application related settings. + /// The new after the filter query has been applied to. + public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + if (query == null) + { + throw Error.ArgumentNull("query"); + } + if (querySettings == null) + { + throw Error.ArgumentNull("querySettings"); + } + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + FilterClause filterClause = FilterClause; + Contract.Assert(filterClause != null); + + ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query); + + Expression filter = FilterBinder.Bind(query, filterClause, Context.ElementClrType, Context, updatedSettings); + query = ExpressionHelpers.Where(query, filter, Context.ElementClrType); + return query; + } + + /// + /// Validate the filter query based on the given . It throws an ODataException if validation failed. + /// + /// The instance which contains all the validation settings. + public void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOption.cs new file mode 100644 index 0000000..26a41ca --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOption.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This enum defines how to handle null propagation in queryable support. + /// + public enum HandleNullPropagationOption + { + /// + /// Determine how to handle null propagation based on the + /// query provider during query composition. This is the + /// default value used in + /// + Default = 0, + + /// + /// Handle null propagation during query composition. + /// + True = 1, + + /// + /// Do not handle null propagation during query composition. + /// + False = 2 + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOptionHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOptionHelper.cs new file mode 100644 index 0000000..055da46 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/HandleNullPropagationOptionHelper.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query +{ + internal static class HandleNullPropagationOptionHelper + { + internal const string EntityFrameworkQueryProviderNamespace = "System.Data.Entity.Internal.Linq"; + + internal const string ObjectContextQueryProviderNamespaceEF5 = "System.Data.Objects.ELinq"; + internal const string ObjectContextQueryProviderNamespaceEF6 = "System.Data.Entity.Core.Objects.ELinq"; + internal const string ObjectContextQueryProviderNamespaceEFCore2 = "Microsoft.EntityFrameworkCore.Query.Internal"; + + internal const string Linq2SqlQueryProviderNamespace = "System.Data.Linq"; + internal const string Linq2ObjectsQueryProviderNamespace = "System.Linq"; + + public static bool IsDefined(HandleNullPropagationOption value) + { + return value == HandleNullPropagationOption.Default || + value == HandleNullPropagationOption.True || + value == HandleNullPropagationOption.False; + } + + public static void Validate(HandleNullPropagationOption value, string parameterValue) + { + if (!IsDefined(value)) + { + throw Error.InvalidEnumArgument(parameterValue, (int)value, typeof(HandleNullPropagationOption)); + } + } + + public static HandleNullPropagationOption GetDefaultHandleNullPropagationOption(IQueryable query) + { + Contract.Assert(query != null); + + HandleNullPropagationOption options; + + string queryProviderNamespace = query.Provider.GetType().Namespace; + switch (queryProviderNamespace) + { + case EntityFrameworkQueryProviderNamespace: + case Linq2SqlQueryProviderNamespace: + case ObjectContextQueryProviderNamespaceEF5: + case ObjectContextQueryProviderNamespaceEF6: + case ObjectContextQueryProviderNamespaceEFCore2: + options = HandleNullPropagationOption.False; + break; + + case Linq2ObjectsQueryProviderNamespace: + default: + options = HandleNullPropagationOption.True; + break; + } + + return options; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ICountOptionCollection.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ICountOptionCollection.cs new file mode 100644 index 0000000..30e3267 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ICountOptionCollection.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents a collection that has total count. + /// + internal interface ICountOptionCollection : IEnumerable + { + /// + /// Gets a value representing the total count of the collection. + /// + long? TotalCount { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/IPropertyMapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/IPropertyMapper.cs new file mode 100644 index 0000000..ba66506 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/IPropertyMapper.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// The result of a $select and $expand projection is represented as an + /// instance. That instance can be projected into an instance by calling + /// . + /// That method will use the function to construct an that will map the property + /// names in that projection to the keys in the returned . + /// The main purpose of converting an instance into an + /// (using the method mentioned above) is to allow changing the names of the + /// properties in the that will be used during the serialization of the $select + /// and $expand projection by a given formatter. For example, to support custom serialization attributes of a + /// particular formatter. + /// + public interface IPropertyMapper + { + /// + /// Defines a mapping between the name of an of an + /// and the name that should be used in other contexts, for example, when projecting an instance of an + /// into an instance of an + /// + /// + /// The name of the property in the represented + /// by this instance of . + /// + /// + /// The value that will be used as the key for this property in the + /// resulting from calling ToDictionary on an instance. + /// + string MapProperty(string propertyName); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ISelectExpandWrapper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ISelectExpandWrapper.cs new file mode 100644 index 0000000..33e5cfe --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ISelectExpandWrapper.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents the result of a $select and $expand query operation. + /// + public interface ISelectExpandWrapper + { + /// + /// Projects the result of a $select and $expand query to a . + /// + /// An representing the $select and $expand result. + IDictionary ToDictionary(); + + /// + /// Projects the result of a $select and/or $expand query to an using + /// the given . The is used + /// to obtain an for the that this + /// instance represents. This will be used to + /// map the properties of the instance to the keys of the + /// returned . This method can be used, for example, to map the property + /// names in the to the names that should be used to serialize the properties + /// that this projection contains. + /// + /// + /// A function that provides a new instance of an for a given + /// and a given . + /// + /// An representing the $select and $expand result. + IDictionary ToDictionary(Func propertyMapperProvider); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ITruncatedCollection.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ITruncatedCollection.cs new file mode 100644 index 0000000..a40f4e3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ITruncatedCollection.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents a collection that is truncated to a given page size. + /// + public interface ITruncatedCollection : IEnumerable + { + /// + /// Gets the page size the collection is truncated to. + /// + int PageSize { get; } + + /// + /// Gets a value representing if the collection is truncated or not. + /// + bool IsTruncated { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ModelBoundQuerySettings.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ModelBoundQuerySettings.cs new file mode 100644 index 0000000..1c38575 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ModelBoundQuerySettings.cs @@ -0,0 +1,298 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This class describes the model bound settings to use during query composition. + /// + public class ModelBoundQuerySettings + { + private int? _pageSize; + private int? _maxTop = 0; + private Dictionary _expandConfigurations = new Dictionary(); + private Dictionary _selectConfigurations = new Dictionary(); + private Dictionary _orderByConfigurations = new Dictionary(); + private Dictionary _filterConfigurations = new Dictionary(); + + internal static ModelBoundQuerySettings DefaultModelBoundQuerySettings = new ModelBoundQuerySettings + { + _maxTop = 0 + }; + + /// + /// Instantiates a new instance of the class + /// + public ModelBoundQuerySettings() + { + } + + /// + /// Copy and create new instance of the class + /// + public ModelBoundQuerySettings(ModelBoundQuerySettings querySettings) + { + _maxTop = querySettings.MaxTop; + PageSize = querySettings.PageSize; + Countable = querySettings.Countable; + DefaultEnableFilter = querySettings.DefaultEnableFilter; + DefaultEnableOrderBy = querySettings.DefaultEnableOrderBy; + DefaultExpandType = querySettings.DefaultExpandType; + DefaultMaxDepth = querySettings.DefaultMaxDepth; + DefaultSelectType = querySettings.DefaultSelectType; + CopyOrderByConfigurations(querySettings.OrderByConfigurations); + CopyFilterConfigurations(querySettings.FilterConfigurations); + CopyExpandConfigurations(querySettings.ExpandConfigurations); + CopySelectConfigurations(querySettings.SelectConfigurations); + } + + /// + /// Gets or sets the max value of $top that a client can request. + /// + /// + /// The max value of $top that a client can request, or null if there is no limit. + /// + public int? MaxTop + { + get + { + return _maxTop; + } + set + { + if (value.HasValue && value <= 0) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 0); + } + + _maxTop = value; + } + } + + /// + /// Gets or sets the maximum number of query results to return. + /// + /// + /// The maximum number of query results to return, or null if there is no limit. + /// + public int? PageSize + { + get + { + return _pageSize; + } + set + { + if (value.HasValue && value <= 0) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 1); + } + + _pageSize = value; + } + } + + /// + /// Represents the $count can be applied or not. + /// + public bool? Countable { get; set; } + + /// + /// Gets the s of navigation properties. + /// + public Dictionary ExpandConfigurations + { + get + { + return _expandConfigurations; + } + } + + /// + /// Gets or sets the default of navigation properties. + /// + public SelectExpandType? DefaultExpandType { get; set; } + + /// + /// Gets or sets the default maxDepth of navigation properties. + /// + public int DefaultMaxDepth { get; set; } + + /// + /// Gets or sets whether the properties can apply $orderby by default. + /// + public bool? DefaultEnableOrderBy { get; set; } + + /// + /// Gets or sets whether the properties can apply $filter by default. + /// + public bool? DefaultEnableFilter { get; set; } + + /// + /// Gets or sets whether the properties can apply $select by default. + /// + public SelectExpandType? DefaultSelectType { get; set; } + + /// + /// Gets the $orderby configuration of properties. + /// + public Dictionary OrderByConfigurations + { + get + { + return _orderByConfigurations; + } + } + + /// + /// Gets the $filter configuration of properties. + /// + public Dictionary FilterConfigurations + { + get + { + return _filterConfigurations; + } + } + + /// + /// Gets the $select configuration of properties. + /// + public Dictionary SelectConfigurations + { + get + { + return _selectConfigurations; + } + } + + /// + /// Copy the s of navigation properties. + /// + internal void CopyExpandConfigurations(Dictionary expandConfigurations) + { + _expandConfigurations.Clear(); + foreach (var expandConfiguration in expandConfigurations) + { + _expandConfigurations.Add(expandConfiguration.Key, expandConfiguration.Value); + } + } + + /// + /// Copy the $orderby configuration of properties. + /// + internal void CopyOrderByConfigurations(Dictionary orderByConfigurations) + { + _orderByConfigurations.Clear(); + foreach (var orderByConfiguration in orderByConfigurations) + { + _orderByConfigurations.Add(orderByConfiguration.Key, orderByConfiguration.Value); + } + } + + /// + /// Copy the $select configuration of properties. + /// + internal void CopySelectConfigurations(Dictionary selectConfigurations) + { + _selectConfigurations.Clear(); + foreach (var selectConfiguration in selectConfigurations) + { + _selectConfigurations.Add(selectConfiguration.Key, selectConfiguration.Value); + } + } + + /// + /// Copy the $filter configuration of properties. + /// + internal void CopyFilterConfigurations(Dictionary filterConfigurations) + { + _filterConfigurations.Clear(); + foreach (var filterConfiguration in filterConfigurations) + { + _filterConfigurations.Add(filterConfiguration.Key, filterConfiguration.Value); + } + } + + internal bool Expandable(string propertyName) + { + ExpandConfiguration expandConfiguration; + if (ExpandConfigurations.TryGetValue(propertyName, out expandConfiguration)) + { + return expandConfiguration.ExpandType != SelectExpandType.Disabled; + } + else + { + return DefaultExpandType.HasValue && DefaultExpandType != SelectExpandType.Disabled; + } + } + + internal bool Selectable(string propertyName) + { + SelectExpandType selectExpandType; + if (SelectConfigurations.TryGetValue(propertyName, out selectExpandType)) + { + return selectExpandType != SelectExpandType.Disabled; + } + else + { + return DefaultSelectType.HasValue && DefaultSelectType != SelectExpandType.Disabled; + } + } + + internal bool Sortable(string propertyName) + { + bool enable; + if (OrderByConfigurations.TryGetValue(propertyName, out enable)) + { + return enable; + } + else + { + return DefaultEnableOrderBy == true; + } + } + + internal bool Filterable(string propertyName) + { + bool enable; + if (FilterConfigurations.TryGetValue(propertyName, out enable)) + { + return enable; + } + else + { + return DefaultEnableFilter == true; + } + } + + internal bool IsAutomaticExpand(string propertyName) + { + ExpandConfiguration expandConfiguration; + if (ExpandConfigurations.TryGetValue(propertyName, out expandConfiguration)) + { + return expandConfiguration.ExpandType == SelectExpandType.Automatic; + } + else + { + return DefaultExpandType.HasValue && DefaultExpandType == SelectExpandType.Automatic; + } + } + + internal bool IsAutomaticSelect(string propertyName) + { + SelectExpandType selectExpandType; + if (SelectConfigurations.TryGetValue(propertyName, out selectExpandType)) + { + return selectExpandType == SelectExpandType.Automatic; + } + else + { + return DefaultSelectType.HasValue && DefaultSelectType == SelectExpandType.Automatic; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NonFilterableAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NonFilterableAttribute.cs new file mode 100644 index 0000000..a0fc52f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NonFilterableAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property to specify that the property cannot be used in the $filter OData query option. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class NonFilterableAttribute : Attribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotCountableAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotCountableAttribute.cs new file mode 100644 index 0000000..e8c4df9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotCountableAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property to specify that + /// the $count cannot be applied on the property. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class NotCountableAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotExpandableAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotExpandableAttribute.cs new file mode 100644 index 0000000..c2bf055 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotExpandableAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property to specify that the property cannot be used in the $expand OData query option. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class NotExpandableAttribute : Attribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotFilterableAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotFilterableAttribute.cs new file mode 100644 index 0000000..bb56547 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotFilterableAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property to specify that + /// the property cannot be used in the $filter OData query option. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class NotFilterableAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotNavigableAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotNavigableAttribute.cs new file mode 100644 index 0000000..1f0534b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotNavigableAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property to specify that the property cannot be navigated in OData query. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class NotNavigableAttribute : Attribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotSortableAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotSortableAttribute.cs new file mode 100644 index 0000000..ad915c4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/NotSortableAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property to specify that + /// the property cannot be used in the $orderby OData query option. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class NotSortableAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs new file mode 100644 index 0000000..5e2d64d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs @@ -0,0 +1,1028 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Net.Http.Headers; +using System.Reflection; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a composite OData query options that can be used to perform query composition. + /// Currently this only supports $filter, $orderby, $top, $skip, and $count. + /// + [ODataQueryParameterBinding] + public partial class ODataQueryOptions + { + private static readonly MethodInfo _limitResultsGenericMethod = typeof(ODataQueryOptions).GetMethod("LimitResults"); + + private ODataQueryOptionParser _queryOptionParser; + + private AllowedQueryOptions _ignoreQueryOptions = AllowedQueryOptions.None; + + private ETag _etagIfMatch; + + private bool _etagIfMatchChecked; + + private ETag _etagIfNoneMatch; + + private bool _etagIfNoneMatchChecked; + + private bool _enableNoDollarSignQueryOptions = false; + + private OrderByQueryOption _stableOrderBy; + + /// + /// Initializes a new instance of the class based on the incoming request and some metadata information from + /// the . + /// + /// The which contains the and some type information. + private void Initialize(ODataQueryContext context) + { + Contract.Assert(context != null); + + ODataUriResolver uriResolver = context.RequestContainer.GetRequiredService(); + if (uriResolver != null) + { + _enableNoDollarSignQueryOptions = uriResolver.EnableNoDollarQueryOptions; + } + + // Parse the query from request Uri, including only keys which are OData query parameters or parameter alias + // OData query parameters are normalized with the $-sign prefixes when the + // EnableNoDollarSignPrefixSystemQueryOption option is used. + RawValues = new ODataRawQueryOptions(); + IDictionary normalizedQueryParameters = GetODataQueryParameters(); + + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + normalizedQueryParameters); + + // Note: the context.RequestContainer must be set by the ODataQueryOptions constructor. + Contract.Assert(context.RequestContainer != null); + _queryOptionParser.Resolver = context.RequestContainer.GetRequiredService(); + + BuildQueryOptions(normalizedQueryParameters); + + Validator = ODataQueryValidator.GetODataQueryValidator(context); + } + + /// + /// Gets the request message associated with this instance. + /// + internal IWebApiRequestMessage InternalRequest { get; private set; } + + /// + /// Gets the given + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets the raw string of all the OData query options + /// + public ODataRawQueryOptions RawValues { get; private set; } + + /// + /// Gets the . + /// + public SelectExpandQueryOption SelectExpand { get; private set; } + + /// + /// Gets the . + /// + public ApplyQueryOption Apply { get; private set; } + + /// + /// Gets the . + /// + public FilterQueryOption Filter { get; private set; } + + /// + /// Gets the . + /// + public OrderByQueryOption OrderBy { get; private set; } + + /// + /// Gets the . + /// + public SkipQueryOption Skip { get; private set; } + + /// + /// Gets the . + /// + public SkipTokenQueryOption SkipToken { get; private set; } + + /// + /// Gets the . + /// + public TopQueryOption Top { get; private set; } + + /// + /// Gets the . + /// + public CountQueryOption Count { get; private set; } + + /// + /// Gets or sets the query validator. + /// + public ODataQueryValidator Validator { get; set; } + + /// + /// Gets or sets the request headers. + /// + private IWebApiHeaders InternalHeaders { get; set; } + + /// + /// Check if the given query option is an OData system query option using $-prefix-required theme. + /// + /// The name of the query option. + /// Returns true if the query option is an OData system query option. + public static bool IsSystemQueryOption(string queryOptionName) + { + return IsSystemQueryOption(queryOptionName, false); + } + + /// + /// Check if the given query option is an OData system query option. + /// + /// The name of the query option. + /// Whether the optional-$-prefix scheme is used for OData system query. + /// Returns true if the query option is an OData system query option. + public static bool IsSystemQueryOption(string queryOptionName, bool isDollarSignOptional) + { + string fixedQueryOptionName = queryOptionName; + if (isDollarSignOptional && !queryOptionName.StartsWith("$", StringComparison.Ordinal)) + { + fixedQueryOptionName = "$" + queryOptionName; + } + + return fixedQueryOptionName.Equals("$orderby", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$filter", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$top", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$skip", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$count", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$expand", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$select", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$format", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$skiptoken", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$deltatoken", StringComparison.Ordinal) || + fixedQueryOptionName.Equals("$apply", StringComparison.Ordinal); + } + + /// + /// Gets the from IfMatch header. + /// + public virtual ETag IfMatch + { + get + { + if (!_etagIfMatchChecked && _etagIfMatch == null) + { + IEnumerable ifMatchValues; + if (InternalHeaders.TryGetValues("If-Match", out ifMatchValues)) + { + EntityTagHeaderValue etagHeaderValue = EntityTagHeaderValue.Parse(ifMatchValues.SingleOrDefault()); + _etagIfMatch = GetETag(etagHeaderValue); + _etagIfMatchChecked = true; + } + } + + return _etagIfMatch; + } + } + + /// + /// Gets the from IfNoneMatch header. + /// + public virtual ETag IfNoneMatch + { + get + { + if (!_etagIfNoneMatchChecked && _etagIfNoneMatch == null) + { + IEnumerable ifNoneMatchValues; + if (InternalHeaders.TryGetValues("If-None-Match", out ifNoneMatchValues)) + { + EntityTagHeaderValue etagHeaderValue = EntityTagHeaderValue.Parse(ifNoneMatchValues.SingleOrDefault()); + _etagIfNoneMatch = GetETag(etagHeaderValue); + if (_etagIfNoneMatch != null) + { + _etagIfNoneMatch.IsIfNoneMatch = true; + } + _etagIfNoneMatchChecked = true; + } + + _etagIfNoneMatchChecked = true; + } + + return _etagIfNoneMatch; + } + } + + /// + /// Gets the EntityTagHeaderValue ETag. + /// + internal virtual ETag GetETag(EntityTagHeaderValue etagHeaderValue) + { + return InternalRequest.GetETag(etagHeaderValue); + } + + /// + /// Check if the given query option is the supported query option. + /// + /// The name of the query option. + /// Returns true if the query option is the supported query option. + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", + Justification = "Need lower case string here.")] + public bool IsSupportedQueryOption(string queryOptionName) + { + ODataUriResolver resolver = _queryOptionParser != null + ? _queryOptionParser.Resolver + : Request.GetRequestContainer().GetRequiredService(); + + if (!resolver.EnableCaseInsensitive) + { + return IsSystemQueryOption(queryOptionName, this._enableNoDollarSignQueryOptions); + } + + string lowcaseQueryOptionName = queryOptionName.ToLowerInvariant(); + return IsSystemQueryOption(lowcaseQueryOptionName, this._enableNoDollarSignQueryOptions); + } + + /// + /// Apply the individual query to the given IQueryable in the right order. + /// + /// The original . + /// The new after the query has been applied to. + public virtual IQueryable ApplyTo(IQueryable query) + { + return ApplyTo(query, new ODataQuerySettings()); + } + + /// + /// Apply the individual query to the given IQueryable in the right order. + /// + /// The original . + /// The query parameters that are already applied in queries. + /// The new after the query has been applied to. + public virtual IQueryable ApplyTo(IQueryable query, AllowedQueryOptions ignoreQueryOptions) + { + _ignoreQueryOptions = ignoreQueryOptions; + return ApplyTo(query, new ODataQuerySettings()); + } + + /// + /// Apply the individual query to the given IQueryable in the right order. + /// + /// The original . + /// The settings to use in query composition. + /// The query parameters that are already applied in queries. + /// The new after the query has been applied to. + public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, + AllowedQueryOptions ignoreQueryOptions) + { + _ignoreQueryOptions = ignoreQueryOptions; + return ApplyTo(query, querySettings); + } + + /// + /// Apply the individual query to the given IQueryable in the right order. + /// + /// The original . + /// The settings to use in query composition. + /// The new after the query has been applied to. + [SuppressMessage( + "Microsoft.Maintainability", + "CA1502:AvoidExcessiveComplexity", + Justification = "These are simple conversion function and cannot be split up.")] + public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + if (query == null) + { + throw Error.ArgumentNull("query"); + } + + if (querySettings == null) + { + throw Error.ArgumentNull("querySettings"); + } + + IQueryable result = query; + + // First apply $apply + // Section 3.15 of the spec http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326311 + if (IsAvailableODataQueryOption(Apply, AllowedQueryOptions.Apply)) + { + result = Apply.ApplyTo(result, querySettings); + InternalRequest.Context.ApplyClause = Apply.ApplyClause; + this.Context.ElementClrType = Apply.ResultClrType; + } + + // Construct the actual query and apply them in the following order: filter, orderby, skip, top + if (IsAvailableODataQueryOption(Filter, AllowedQueryOptions.Filter)) + { + result = Filter.ApplyTo(result, querySettings); + } + + if (IsAvailableODataQueryOption(Count, AllowedQueryOptions.Count)) + { + if (InternalRequest.Context.TotalCountFunc == null) + { + Func countFunc = Count.GetEntityCountFunc(result); + if (countFunc != null) + { + InternalRequest.Context.TotalCountFunc = countFunc; + } + } + + if (InternalRequest.IsCountRequest()) + { + return result; + } + } + + OrderByQueryOption orderBy = OrderBy; + + // $skip or $top require a stable sort for predictable results. + // Result limits require a stable sort to be able to generate a next page link. + // If either is present in the query and we have permission, + // generate an $orderby that will produce a stable sort. + if (querySettings.EnsureStableOrdering && + (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip) || + IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top) || + querySettings.PageSize.HasValue)) + { + // If there is no OrderBy present, we manufacture a default. + // If an OrderBy is already present, we add any missing + // properties necessary to make a stable sort. + // Instead of failing early here if we cannot generate the OrderBy, + // let the IQueryable backend fail (if it has to). + + orderBy = GenerateStableOrder(); + } + + if (IsAvailableODataQueryOption(orderBy, AllowedQueryOptions.OrderBy)) + { + result = orderBy.ApplyTo(result, querySettings); + } + + if (IsAvailableODataQueryOption(SkipToken, AllowedQueryOptions.SkipToken)) + { + result = SkipToken.ApplyTo(result, querySettings, this); + } + + AddAutoSelectExpandProperties(); + + if (SelectExpand != null) + { + var tempResult = ApplySelectExpand(result, querySettings); + if (tempResult != default(IQueryable)) + { + result = tempResult; + } + } + + if (IsAvailableODataQueryOption(Skip, AllowedQueryOptions.Skip)) + { + result = Skip.ApplyTo(result, querySettings); + } + + if (IsAvailableODataQueryOption(Top, AllowedQueryOptions.Top)) + { + result = Top.ApplyTo(result, querySettings); + } + + result = ApplyPaging(result, querySettings); + + return result; + } + + internal IQueryable ApplyPaging(IQueryable result, ODataQuerySettings querySettings) + { + int pageSize = -1; + if (querySettings.PageSize.HasValue) + { + pageSize = querySettings.PageSize.Value; + } + else if (querySettings.ModelBoundPageSize.HasValue) + { + pageSize = querySettings.ModelBoundPageSize.Value; + } + + int preferredPageSize = -1; + if (RequestPreferenceHelpers.RequestPrefersMaxPageSize(InternalRequest.Headers, out preferredPageSize)) + { + pageSize = Math.Min(pageSize, preferredPageSize); + } + + if (pageSize > 0) + { + bool resultsLimited; + result = LimitResults(result, pageSize, out resultsLimited); + if (resultsLimited && InternalRequest.RequestUri != null && + InternalRequest.Context.NextLink == null) + { + InternalRequest.Context.PageSize = pageSize; + } + } + + InternalRequest.Context.QueryOptions = this; + + return result; + } + + /// + /// Generates the Stable OrderBy query option based on the existing OrderBy and other query options. + /// + /// An order by query option that ensures stable ordering of the results. + public virtual OrderByQueryOption GenerateStableOrder() + { + if (_stableOrderBy != null) + { + return _stableOrderBy; + } + + ApplyClause apply = Apply != null ? Apply.ApplyClause : null; + List applySortOptions = GetApplySortOptions(apply); + + _stableOrderBy = OrderBy == null + ? GenerateDefaultOrderBy(Context, applySortOptions) + : EnsureStableSortOrderBy(OrderBy, Context, applySortOptions); + + return _stableOrderBy; + } + + private static List GetApplySortOptions(ApplyClause apply) + { + Func transformPredicate = t => t.Kind == TransformationNodeKind.Aggregate || t.Kind == TransformationNodeKind.GroupBy; + if (apply == null || !apply.Transformations.Any(transformPredicate)) + { + return null; + } + + var result = new List(); + var lastTransform = apply.Transformations.Last(transformPredicate); + if (lastTransform.Kind == TransformationNodeKind.Aggregate) + { + var aggregateClause = lastTransform as AggregateTransformationNode; + foreach (var expr in aggregateClause.AggregateExpressions.OfType()) + { + result.Add(expr.Alias); + } + } + else if (lastTransform.Kind == TransformationNodeKind.GroupBy) + { + var groupByClause = lastTransform as GroupByTransformationNode; + var groupingProperties = groupByClause.GroupingProperties; + ExtractGroupingProperties(result, groupingProperties); + } + + return result; + } + + private static void ExtractGroupingProperties(List result, IEnumerable groupingProperties, string prefix = null) + { + foreach (var gp in groupingProperties) + { + var fullPath = prefix != null ? prefix + "/" + gp.Name : gp.Name; + if (gp.ChildTransformations != null && gp.ChildTransformations.Any()) + { + ExtractGroupingProperties(result, gp.ChildTransformations, fullPath); + } + else + { + result.Add(fullPath); + } + } + } + + /// + /// Apply the individual query to the given IQueryable in the right order. + /// + /// The original entity. + /// The that contains all the query application related settings. + /// The query parameters that are already applied in queries. + /// The new entity after the $select and $expand query has been applied to. + /// Only $select and $expand query options can be applied on single entities. This method throws if the query contains any other + /// query options. + public virtual object ApplyTo(object entity, ODataQuerySettings querySettings, AllowedQueryOptions ignoreQueryOptions) + { + _ignoreQueryOptions = ignoreQueryOptions; + return ApplyTo(entity, new ODataQuerySettings()); + } + + /// + /// Applies the query to the given entity using the given . + /// + /// The original entity. + /// The that contains all the query application related settings. + /// The new entity after the $select and $expand query has been applied to. + /// Only $select and $expand query options can be applied on single entities. This method throws if the query contains any other + /// query options. + public virtual object ApplyTo(object entity, ODataQuerySettings querySettings) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + if (querySettings == null) + { + throw Error.ArgumentNull("querySettings"); + } + + if (Filter != null || OrderBy != null || Top != null || Skip != null || Count != null) + { + throw Error.InvalidOperation(SRResources.NonSelectExpandOnSingleEntity); + } + + AddAutoSelectExpandProperties(); + + if (SelectExpand != null) + { + var result = ApplySelectExpand(entity, querySettings); + if (result != default(object)) + { + return result; + } + } + + return entity; + } + + /// + /// Validate all OData queries, including $skip, $top, $orderby and $filter, based on the given . + /// It throws an ODataException if validation failed. + /// + /// The instance which contains all the validation settings. + public virtual void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + + private static void ThrowIfEmpty(string queryValue, string queryName) + { + if (String.IsNullOrWhiteSpace(queryValue)) + { + throw new ODataException(Error.Format(SRResources.QueryCannotBeEmpty, queryName)); + } + } + + // Returns a sorted list of all properties that may legally appear + // in an OrderBy. If the entity type has keys, all are returned. + // Otherwise, when no keys are present, all primitive properties are returned. + private static IEnumerable GetAvailableOrderByProperties(ODataQueryContext context) + { + Contract.Assert(context != null); + + var entityType = context.ElementType as IEdmEntityType; + if (entityType == null) + { + return Enumerable.Empty(); + } + var properties = + entityType.Key().Any() + ? entityType.Key() + : entityType + .StructuralProperties() + .Where(property => property.Type.IsPrimitive() && !property.Type.IsStream()); + + return properties.OrderBy(o => + { + var value = o.DeclaringType as PrimitivePropertyConfiguration; + return value == null ? 0 : value.Order; + }).ThenBy(o => o.Name).ToList(); + } + + // Generates the OrderByQueryOption to use by default for $skip or $top + // when no other $orderby is available. It will produce a stable sort. + // This may return a null if there are no available properties. + private OrderByQueryOption GenerateDefaultOrderBy(ODataQueryContext context, List applySortOptions) + { + string orderByRaw = String.Empty; + if (applySortOptions != null) + { + orderByRaw = String.Join(",", applySortOptions); + return new OrderByQueryOption(orderByRaw, context, Apply.RawValue); + } + else + { + orderByRaw = String.Join(",", + GetAvailableOrderByProperties(context) + .Select(property => property.Name)); + } + + return String.IsNullOrEmpty(orderByRaw) + ? null + : new OrderByQueryOption(orderByRaw, context); + } + + /// + /// Ensures the given will produce a stable sort. + /// If it will, the input will be returned + /// unmodified. If the given will not produce a + /// stable sort, a new instance will be created + /// and returned. + /// + /// The to evaluate. + /// The . + /// + /// An that will produce a stable sort. + private OrderByQueryOption EnsureStableSortOrderBy(OrderByQueryOption orderBy, ODataQueryContext context, List applySortOptions) + { + Contract.Assert(orderBy != null); + Contract.Assert(context != null); + + // Strategy: create a hash of all properties already used in the given OrderBy + // and remove them from the list of properties we need to add to make the sort stable. + Func propertyFunc = null; + if (applySortOptions != null) + { + propertyFunc = node => node.PropertyPath; + } + else + { + propertyFunc = node => node.Property.Name; + } + + HashSet usedPropertyNames = + new HashSet(orderBy.OrderByNodes.OfType().Select(propertyFunc)); + + if (applySortOptions != null) + { + var propertyPathsToAdd = applySortOptions.Where(p => !usedPropertyNames.Contains(p)).OrderBy(p => p); + if (propertyPathsToAdd.Any()) + { + var orderByRaw = orderBy.RawValue + "," + String.Join(",", propertyPathsToAdd); + orderBy = new OrderByQueryOption(orderByRaw, context, Apply.RawValue); + } + } + else + { + IEnumerable propertiesToAdd = GetAvailableOrderByProperties(context).Where(prop => !usedPropertyNames.Contains(prop.Name)); + if (propertiesToAdd.Any()) + { + // The existing query options has too few properties to create a stable sort. + // Clone the given one and add the remaining properties to end, thereby making + // the sort stable but preserving the user's original intent for the major + // sort order. + orderBy = new OrderByQueryOption(orderBy); + + foreach (IEdmStructuralProperty property in propertiesToAdd) + { + orderBy.OrderByNodes.Add(new OrderByPropertyNode(property, OrderByDirection.Ascending)); + } + } + } + + return orderBy; + } + + internal static IQueryable LimitResults(IQueryable queryable, int limit, out bool resultsLimited) + { + MethodInfo genericMethod = _limitResultsGenericMethod.MakeGenericMethod(queryable.ElementType); + object[] args = new object[] { queryable, limit, null }; + IQueryable results = genericMethod.Invoke(null, args) as IQueryable; + resultsLimited = (bool)args[2]; + return results; + } + + /// + /// Limits the query results to a maximum number of results. + /// + /// The entity CLR type + /// The queryable to limit. + /// The query result limit. + /// true if the query results were limited; false otherwise + /// The limited query results. + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "Not intended for public use, only public to enable invocation without security issues.")] + public static IQueryable LimitResults(IQueryable queryable, int limit, out bool resultsLimited) + { + TruncatedCollection truncatedCollection = new TruncatedCollection(queryable, limit); + resultsLimited = truncatedCollection.IsTruncated; + return truncatedCollection.AsQueryable(); + } + + internal void AddAutoSelectExpandProperties() + { + bool containsAutoSelectExpandProperties = false; + var autoExpandRawValue = GetAutoExpandRawValue(); + var autoSelectRawValue = GetAutoSelectRawValue(); + + IDictionary queryParameters = GetODataQueryParameters(); + + if (!String.IsNullOrEmpty(autoExpandRawValue) && !autoExpandRawValue.Equals(RawValues.Expand)) + { + queryParameters["$expand"] = autoExpandRawValue; + containsAutoSelectExpandProperties = true; + } + else + { + autoExpandRawValue = RawValues.Expand; + } + + if (!String.IsNullOrEmpty(autoSelectRawValue) && !autoSelectRawValue.Equals(RawValues.Select)) + { + queryParameters["$select"] = autoSelectRawValue; + containsAutoSelectExpandProperties = true; + } + else + { + autoSelectRawValue = RawValues.Select; + } + + if (containsAutoSelectExpandProperties) + { + _queryOptionParser = new ODataQueryOptionParser( + Context.Model, + Context.ElementType, + Context.NavigationSource, + queryParameters, + Context.RequestContainer); + var originalSelectExpand = SelectExpand; + SelectExpand = new SelectExpandQueryOption( + autoSelectRawValue, + autoExpandRawValue, + Context, + _queryOptionParser); + if (originalSelectExpand != null && originalSelectExpand.LevelsMaxLiteralExpansionDepth > 0) + { + SelectExpand.LevelsMaxLiteralExpansionDepth = originalSelectExpand.LevelsMaxLiteralExpansionDepth; + } + } + } + + private IDictionary GetODataQueryParameters() + { + Dictionary result = new Dictionary(); + + foreach (KeyValuePair kvp in InternalRequest.QueryParameters) + { + string key = kvp.Key.Trim(); + // Check supported system query options per $-sign-prefix option. + if (!_enableNoDollarSignQueryOptions) + { + // This is the original case for required $-sign prefix. + if (key.StartsWith("$", StringComparison.Ordinal)) + { + result.Add(key, kvp.Value); + } + } + else + { + if (IsSupportedQueryOption(kvp.Key)) + { + // Normalized the supported system query key by adding the $-prefix if needed. + result.Add( + !key.StartsWith("$", StringComparison.Ordinal) ? "$" + key : key, + kvp.Value); + } + } + + // check parameter alias + if (key.StartsWith("@", StringComparison.Ordinal)) + { + result.Add(key, kvp.Value); + } + } + + return result; + } + + private string GetAutoSelectRawValue() + { + var selectRawValue = RawValues.Select; + var autoSelectRawValue = String.Empty; + IEdmEntityType baseEntityType = Context.TargetStructuredType as IEdmEntityType; + if (String.IsNullOrEmpty(selectRawValue)) + { + var autoSelectProperties = EdmLibHelpers.GetAutoSelectProperties(Context.TargetProperty, + Context.TargetStructuredType, Context.Model); + + foreach (var property in autoSelectProperties) + { + if (!String.IsNullOrEmpty(autoSelectRawValue)) + { + autoSelectRawValue += ","; + } + + if (baseEntityType != null && property.DeclaringType != baseEntityType) + { + autoSelectRawValue += String.Format(CultureInfo.InvariantCulture, "{0}/", + property.DeclaringType.FullTypeName()); + } + + autoSelectRawValue += property.Name; + } + + if (!String.IsNullOrEmpty(autoSelectRawValue)) + { + if (!String.IsNullOrEmpty(selectRawValue)) + { + selectRawValue = String.Format(CultureInfo.InvariantCulture, "{0},{1}", + autoSelectRawValue, selectRawValue); + } + else + { + selectRawValue = autoSelectRawValue; + } + } + } + + return selectRawValue; + } + + private string GetAutoExpandRawValue() + { + var expandRawValue = RawValues.Expand; + IEdmEntityType baseEntityType = Context.TargetStructuredType as IEdmEntityType; + var autoExpandRawValue = String.Empty; + var autoExpandNavigationProperties = EdmLibHelpers.GetAutoExpandNavigationProperties( + Context.TargetProperty, Context.TargetStructuredType, Context.Model, + !String.IsNullOrEmpty(RawValues.Select)); + + foreach (var property in autoExpandNavigationProperties) + { + if (!String.IsNullOrEmpty(autoExpandRawValue)) + { + autoExpandRawValue += ","; + } + + if (property.DeclaringEntityType() != baseEntityType) + { + autoExpandRawValue += String.Format(CultureInfo.InvariantCulture, "{0}/", + property.DeclaringEntityType().FullTypeName()); + } + + autoExpandRawValue += property.Name; + } + + if (!String.IsNullOrEmpty(autoExpandRawValue)) + { + if (!String.IsNullOrEmpty(expandRawValue)) + { + expandRawValue = String.Format(CultureInfo.InvariantCulture, "{0},{1}", + autoExpandRawValue, expandRawValue); + } + else + { + expandRawValue = autoExpandRawValue; + } + } + + return expandRawValue; + } + + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", + Justification = "Need lower case string here.")] + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "These are simple and flat processing functions based on parameter key value and cannot be split up.")] + private void BuildQueryOptions(IDictionary queryParameters) + { + foreach (KeyValuePair kvp in queryParameters) + { + switch (kvp.Key.ToLowerInvariant()) + { + case "$filter": + ThrowIfEmpty(kvp.Value, "$filter"); + RawValues.Filter = kvp.Value; + Filter = new FilterQueryOption(kvp.Value, Context, _queryOptionParser); + break; + case "$orderby": + ThrowIfEmpty(kvp.Value, "$orderby"); + RawValues.OrderBy = kvp.Value; + OrderBy = new OrderByQueryOption(kvp.Value, Context, _queryOptionParser); + break; + case "$top": + ThrowIfEmpty(kvp.Value, "$top"); + RawValues.Top = kvp.Value; + Top = new TopQueryOption(kvp.Value, Context, _queryOptionParser); + break; + case "$skip": + ThrowIfEmpty(kvp.Value, "$skip"); + RawValues.Skip = kvp.Value; + Skip = new SkipQueryOption(kvp.Value, Context, _queryOptionParser); + break; + case "$select": + RawValues.Select = kvp.Value; + break; + case "$count": + ThrowIfEmpty(kvp.Value, "$count"); + RawValues.Count = kvp.Value; + Count = new CountQueryOption(kvp.Value, Context, _queryOptionParser); + break; + case "$expand": + RawValues.Expand = kvp.Value; + break; + case "$format": + RawValues.Format = kvp.Value; + break; + case "$skiptoken": + RawValues.SkipToken = kvp.Value; + SkipToken = new SkipTokenQueryOption(kvp.Value, Context, _queryOptionParser); + break; + case "$deltatoken": + RawValues.DeltaToken = kvp.Value; + break; + case "$apply": + ThrowIfEmpty(kvp.Value, "$apply"); + RawValues.Apply = kvp.Value; + Apply = new ApplyQueryOption(kvp.Value, Context, _queryOptionParser); + break; + default: + // we don't throw if we can't recognize the query + break; + } + } + + if (RawValues.Select != null || RawValues.Expand != null) + { + SelectExpand = new SelectExpandQueryOption(RawValues.Select, RawValues.Expand, + Context, _queryOptionParser); + } + + if (InternalRequest.IsCountRequest()) + { + Count = new CountQueryOption( + "true", + Context, + new ODataQueryOptionParser( + Context.Model, + Context.ElementType, + Context.NavigationSource, + new Dictionary { { "$count", "true" } }, + Context.RequestContainer)); + } + } + + private bool IsAvailableODataQueryOption(object queryOption, AllowedQueryOptions queryOptionFlag) + { + return ((queryOption != null) && ((_ignoreQueryOptions & queryOptionFlag) == AllowedQueryOptions.None)); + } + + private T ApplySelectExpand(T entity, ODataQuerySettings querySettings) + { + var result = default(T); + bool selectAvailable = IsAvailableODataQueryOption(SelectExpand.RawSelect, AllowedQueryOptions.Select); + bool expandAvailable = IsAvailableODataQueryOption(SelectExpand.RawExpand, AllowedQueryOptions.Expand); + if (selectAvailable || expandAvailable) + { + if ((!selectAvailable && SelectExpand.RawSelect != null) || + (!expandAvailable && SelectExpand.RawExpand != null)) + { + SelectExpand = new SelectExpandQueryOption( + selectAvailable ? RawValues.Select : null, + expandAvailable ? RawValues.Expand : null, + SelectExpand.Context); + } + + SelectExpandClause processedClause = SelectExpand.ProcessedSelectExpandClause; + SelectExpandQueryOption newSelectExpand = new SelectExpandQueryOption( + SelectExpand.RawSelect, + SelectExpand.RawExpand, + SelectExpand.Context, + processedClause); + + InternalRequest.Context.ProcessedSelectExpandClause = processedClause; + InternalRequest.Context.QueryOptions = this; + + var type = typeof(T); + if (type == typeof(IQueryable)) + { + result = (T)newSelectExpand.ApplyTo((IQueryable)entity, querySettings); + } + else if (type == typeof(object)) + { + result = (T)newSelectExpand.ApplyTo(entity, querySettings); + } + } + + return result; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptionsOfTEntity.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptionsOfTEntity.cs new file mode 100644 index 0000000..57b2fa5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptionsOfTEntity.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a composite OData query options that can be used to perform query composition. + /// Currently this only supports $filter, $orderby, $top, $skip. + /// + [ODataQueryParameterBinding] + public partial class ODataQueryOptions : ODataQueryOptions + { + /// + /// Gets the from IfMatch header. + /// + public new ETag IfMatch + { + get + { + return base.IfMatch as ETag; + } + } + + /// + /// Gets the from IfNoneMatch header. + /// + public new ETag IfNoneMatch + { + get + { + return base.IfNoneMatch as ETag; + } + } + + /// + /// Gets the EntityTagHeaderValue ETag>. + /// + /// This signature uses types that are AspNetCore-specific. + internal override ETag GetETag(EntityTagHeaderValue etagHeaderValue) + { + return InternalRequest.GetETag(etagHeaderValue); + } + + /// + /// Apply the individual query to the given IQueryable in the right order. + /// + /// The original . + /// The new after the query has been applied to. + public override IQueryable ApplyTo(IQueryable query) + { + ValidateQuery(query); + return base.ApplyTo(query); + } + + /// + /// Apply the individual query to the given IQueryable in the right order. + /// + /// The original . + /// The settings to use in query composition. + /// The new after the query has been applied to. + public override IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + ValidateQuery(query); + return base.ApplyTo(query, querySettings); + } + + private static void ValidateQuery(IQueryable query) + { + if (query == null) + { + throw Error.ArgumentNull("query"); + } + + if (!TypeHelper.IsTypeAssignableFrom(typeof(TEntity), query.ElementType)) + { + throw Error.Argument("query", SRResources.CannotApplyODataQueryOptionsOfT, typeof(ODataQueryOptions).Name, typeof(TEntity).FullName, typeof(IQueryable).Name, query.ElementType.FullName); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQuerySettings.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQuerySettings.cs new file mode 100644 index 0000000..bbeead3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataQuerySettings.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This class describes the settings to use during query composition. + /// + public class ODataQuerySettings + { + private HandleNullPropagationOption _handleNullPropagationOption = HandleNullPropagationOption.Default; + private int? _pageSize; + private int? _modelBoundPageSize; + + /// + /// Instantiates a new instance of the class + /// and initializes the default settings. + /// + public ODataQuerySettings() + { + EnsureStableOrdering = true; + EnableConstantParameterization = true; + } + + /// + /// Gets or sets the maximum number of query results to return based on the type or property. + /// + /// + /// The maximum number of query results to return based on the type or property, + /// or null if there is no limit. + /// + internal int? ModelBoundPageSize + { + get + { + return _modelBoundPageSize; + } + set + { + if (value.HasValue && value <= 0) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 1); + } + + _modelBoundPageSize = value; + } + } + + /// + /// Gets or sets a value indicating whether query composition should + /// alter the original query when necessary to ensure a stable sort order. + /// + /// A true value indicates the original query should + /// be modified when necessary to guarantee a stable sort order. + /// A false value indicates the sort order can be considered + /// stable without modifying the query. Query providers that ensure + /// a stable sort order should set this value to false. + /// The default value is true. + public bool EnsureStableOrdering { get; set; } + + /// + /// Gets or sets a value indicating how null propagation should + /// be handled during query composition. + /// + /// + /// The default is . + /// + public HandleNullPropagationOption HandleNullPropagation + { + get + { + return _handleNullPropagationOption; + } + set + { + HandleNullPropagationOptionHelper.Validate(value, "value"); + _handleNullPropagationOption = value; + } + } + + /// + /// Gets or sets a value indicating whether constants should be parameterized. Parameterizing constants + /// would result in better performance with Entity framework. + /// + /// The default value is true. + public bool EnableConstantParameterization { get; set; } + + /// + /// Gets or sets a value indicating whether queries with expanded navigations should be formulated + /// to encourage correlated subquery results to be buffered. + /// Buffering correlated subquery results can reduce the number of queries from N + 1 to 2 + /// by buffering results from the subquery. + /// + /// The default value is false. + public bool EnableCorrelatedSubqueryBuffering { get; set; } + + /// + /// Gets or sets the maximum number of query results to return. + /// + /// + /// The maximum number of query results to return, or null if there is no limit. + /// + public int? PageSize + { + get + { + return _pageSize; + } + set + { + if (value.HasValue && value <= 0) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, 1); + } + + _pageSize = value; + } + } + + /// + /// Honor $filter inside $expand of non-collection navigation property. + /// The expanded property is only populated when the filter evaluates to true. + /// This setting is false by default. + /// + public bool HandleReferenceNavigationPropertyExpandFilter { get; set; } + + internal void CopyFrom(ODataQuerySettings settings) + { + EnsureStableOrdering = settings.EnsureStableOrdering; + EnableConstantParameterization = settings.EnableConstantParameterization; + HandleNullPropagation = settings.HandleNullPropagation; + PageSize = settings.PageSize; + ModelBoundPageSize = settings.ModelBoundPageSize; + HandleReferenceNavigationPropertyExpandFilter = settings.HandleReferenceNavigationPropertyExpandFilter; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataRawQueryOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataRawQueryOptions.cs new file mode 100644 index 0000000..ac643c3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataRawQueryOptions.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents the raw query values in the string format from the incoming request. + /// + public class ODataRawQueryOptions + { + /// + /// Gets the raw $filter query value from the incoming request Uri if exists. + /// + public string Filter { get; internal set; } + + /// + /// Gets the raw $apply query value from the incoming request Uri if exists. + /// + public string Apply { get; internal set; } + + /// + /// Gets the raw $orderby query value from the incoming request Uri if exists. + /// + public string OrderBy { get; internal set; } + + /// + /// Gets the raw $top query value from the incoming request Uri if exists. + /// + public string Top { get; internal set; } + + /// + /// Gets the raw $skip query value from the incoming request Uri if exists. + /// + public string Skip { get; internal set; } + + /// + /// Gets the raw $select query value from the incoming request Uri if exists. + /// + public string Select { get; internal set; } + + /// + /// Gets the raw $expand query value from the incoming request Uri if exists. + /// + public string Expand { get; internal set; } + + /// + /// Gets the raw $count query value from the incoming request Uri if exists. + /// + public string Count { get; internal set; } + + /// + /// Gets the raw $format query value from the incoming request Uri if exists. + /// + public string Format { get; internal set; } + + /// + /// Gets the raw $skiptoken query value from the incoming request Uri if exists. + /// + public string SkipToken { get; internal set; } + + /// + /// Gets the raw $deltatoken query value from the incoming request Uri if exists. + /// + public string DeltaToken { get; internal set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataValidationSettings.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataValidationSettings.cs new file mode 100644 index 0000000..50b8090 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ODataValidationSettings.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.ObjectModel; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This class describes the validation settings for querying. + /// + public class ODataValidationSettings + { + private const int MinMaxSkip = 0; + private const int MinMaxTop = 0; + private const int MinMaxExpansionDepth = 0; + private const int MinMaxNodeCount = 1; + private const int MinMaxAnyAllExpressionDepth = 1; + private const int MinMaxOrderByNodeCount = 1; + internal const int DefaultMaxExpansionDepth = 2; + + private AllowedArithmeticOperators _allowedArithmeticOperators = AllowedArithmeticOperators.All; + private AllowedFunctions _allowedFunctions = AllowedFunctions.AllFunctions; + private AllowedLogicalOperators _allowedLogicalOperators = AllowedLogicalOperators.All; + private AllowedQueryOptions _allowedQueryParameters = AllowedQueryOptions.Supported; + private Collection _allowedOrderByProperties = new Collection(); + private int? _maxSkip; + private int? _maxTop; + private int _maxAnyAllExpressionDepth = 1; + private int _maxNodeCount = 100; + private int _maxExpansionDepth = DefaultMaxExpansionDepth; + private int _maxOrderByNodeCount = 5; + + /// + /// Gets or sets a list of allowed arithmetic operators including 'add', 'sub', 'mul', 'div', 'mod'. + /// + public AllowedArithmeticOperators AllowedArithmeticOperators + { + get + { + return _allowedArithmeticOperators; + } + set + { + if (value > AllowedArithmeticOperators.All || value < AllowedArithmeticOperators.None) + { + throw Error.InvalidEnumArgument("value", (Int32)value, typeof(AllowedArithmeticOperators)); + } + + _allowedArithmeticOperators = value; + } + } + + /// + /// Gets or sets a list of allowed functions used in the $filter query. + /// + /// The allowed functions include the following: + /// + /// String related: contains, endswith, startswith, length, indexof, substring, tolower, toupper, trim, concat + /// + /// e.g. ~/Customers?$filter=length(CompanyName) eq 19 + /// + /// Date and Time related: year, month, day, hour, minute, second, fractionalseconds, date, time + /// + /// e.g. ~/Employees?$filter=year(BirthDate) eq 1971 + /// + /// Math related: round, floor, ceiling + /// + /// Type related:isof, cast, + /// + /// Collection related: any, all + /// + /// + public AllowedFunctions AllowedFunctions + { + get + { + return _allowedFunctions; + } + set + { + if (value > AllowedFunctions.AllFunctions || value < AllowedFunctions.None) + { + throw Error.InvalidEnumArgument("value", (Int32)value, typeof(AllowedFunctions)); + } + + _allowedFunctions = value; + } + } + + /// + /// Gets or sets a list of allowed logical operators such as 'eq', 'ne', 'gt', 'ge', 'lt', 'le', 'and', 'or', 'not'. + /// + public AllowedLogicalOperators AllowedLogicalOperators + { + get + { + return _allowedLogicalOperators; + } + set + { + if (value > AllowedLogicalOperators.All || value < AllowedLogicalOperators.None) + { + throw Error.InvalidEnumArgument("value", (Int32)value, typeof(AllowedLogicalOperators)); + } + + _allowedLogicalOperators = value; + } + } + + /// + /// Gets a list of properties one can orderby the result with. Note, by default this list is empty, + /// it means it can be ordered by any property. + /// + /// For example, having an empty collection means client can order the queryable result by any properties. + /// Adding "Name" to this list means that it only allows queryable result to be ordered by Name property. + /// + public Collection AllowedOrderByProperties + { + get + { + return _allowedOrderByProperties; + } + } + + /// + /// Gets or sets the query parameters that are allowed inside query. The default is all query options, + /// including $filter, $skip, $top, $orderby, $expand, $select, $count, $format, $skiptoken and $deltatoken. + /// + public AllowedQueryOptions AllowedQueryOptions + { + get + { + return _allowedQueryParameters; + } + set + { + if (value > AllowedQueryOptions.All || value < AllowedQueryOptions.None) + { + throw Error.InvalidEnumArgument("value", (Int32)value, typeof(AllowedQueryOptions)); + } + + _allowedQueryParameters = value; + } + } + + /// + /// Gets or sets the maximum number of expressions that can be present in the $orderby. + /// + public int MaxOrderByNodeCount + { + get + { + return _maxOrderByNodeCount; + } + set + { + if (value < MinMaxOrderByNodeCount) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinMaxOrderByNodeCount); + } + + _maxOrderByNodeCount = value; + } + } + + /// + /// Gets or sets the maximum depth of the Any or All elements nested inside the query. + /// + /// + /// The default value is 1. + /// + /// + /// The maximum depth of the Any or All elements nested inside the query. + /// + public int MaxAnyAllExpressionDepth + { + get + { + return _maxAnyAllExpressionDepth; + } + set + { + if (value < MinMaxAnyAllExpressionDepth) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinMaxAnyAllExpressionDepth); + } + + _maxAnyAllExpressionDepth = value; + } + } + + /// + /// Gets or sets the maximum number of the nodes inside the $filter syntax tree. + /// + /// + /// The default value is 100. + /// + public int MaxNodeCount + { + get + { + return _maxNodeCount; + } + set + { + if (value < MinMaxNodeCount) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinMaxNodeCount); + } + + _maxNodeCount = value; + } + } + + /// + /// Gets or sets the max value of $skip that a client can request. + /// + public int? MaxSkip + { + get + { + return _maxSkip; + } + set + { + if (value.HasValue && value < MinMaxSkip) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinMaxSkip); + } + + _maxSkip = value; + } + } + + /// + /// Gets or sets the max value of $top that a client can request. + /// + public int? MaxTop + { + get + { + return _maxTop; + } + set + { + if (value.HasValue && value < MinMaxTop) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinMaxTop); + } + + _maxTop = value; + } + } + + /// + /// Gets or sets the max expansion depth for the $expand query option. + /// + /// To disable the maximum expansion depth check, set this property to 0. + public int MaxExpansionDepth + { + get { return _maxExpansionDepth; } + set + { + if (value < MinMaxExpansionDepth) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinMaxExpansionDepth); + } + _maxExpansionDepth = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByAttribute.cs new file mode 100644 index 0000000..ac64441 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByAttribute.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a class or property + /// correlate to OData's $orderby query option settings. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] + [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", + Justification = "Don't want those argument to be retrievable")] + public sealed class OrderByAttribute : Attribute + { + private bool? _defaultEnableOrderBy; + private bool _disable; + private readonly Dictionary _orderByConfigurations = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + public OrderByAttribute() + { + _defaultEnableOrderBy = true; + } + + /// + /// Initializes a new instance of the class + /// with the name of allowed $orderby properties. + /// + public OrderByAttribute(params string[] properties) + { + foreach (var property in properties) + { + if (!_orderByConfigurations.ContainsKey(property)) + { + _orderByConfigurations.Add(property, true); + } + } + } + + /// + /// Gets or sets the $orderby configuration of properties. + /// + public Dictionary OrderByConfigurations + { + get + { + return _orderByConfigurations; + } + } + + /// + /// Represents whether the $orderby can be applied on those properties. + /// + public bool Disabled + { + get + { + return _disable; + } + set + { + _disable = value; + List keys = _orderByConfigurations.Keys.ToList(); + foreach (var property in keys) + { + _orderByConfigurations[property] = !_disable; + } + + if (_orderByConfigurations.Count == 0) + { + _defaultEnableOrderBy = !_disable; + } + } + } + + internal bool? DefaultEnableOrderBy + { + get + { + return _defaultEnableOrderBy; + } + set + { + _defaultEnableOrderBy = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByCountNode.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByCountNode.cs new file mode 100644 index 0000000..b1782f6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByCountNode.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an order by expression. + /// + public class OrderByCountNode : OrderByNode + { + /// + /// Initializes a new instance of the class. + /// + /// The orderby clause representing property access. + public OrderByCountNode(OrderByClause orderByClause) + { + if (orderByClause == null) + { + throw Error.ArgumentNull("orderByClause"); + } + + OrderByClause = orderByClause; + Direction = orderByClause.Direction; + } + + /// + /// Gets the of this node. + /// + public OrderByClause OrderByClause { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByItNode.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByItNode.cs new file mode 100644 index 0000000..c8b1255 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByItNode.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents the order by expression '$it' in the $orderby clause. + /// + public class OrderByItNode : OrderByNode + { + /// + /// Instantiates a new instance of class. + /// + /// The for this node. + public OrderByItNode(OrderByDirection direction) + : base(direction) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByNode.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByNode.cs new file mode 100644 index 0000000..6b4f72c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByNode.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents a single order by expression in the $orderby clause. + /// + public abstract class OrderByNode + { + /// + /// Initializes a new instance of the class. + /// + /// The direction of the sort order. + protected OrderByNode(OrderByDirection direction) + { + Direction = direction; + PropertyPath = String.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// The clause of the sort order. + protected OrderByNode(OrderByClause orderByClause) + { + if (orderByClause == null) + { + throw Error.ArgumentNull("orderByClause"); + } + + Direction = orderByClause.Direction; + PropertyPath = RestorePropertyPath(orderByClause.Expression); + } + + internal OrderByNode() + { + } + + /// + /// Gets the for the current node. + /// + public OrderByDirection Direction { get; internal set; } + + internal string PropertyPath { get; set; } + + /// + /// Creates a list of instances from a linked list of instances. + /// + /// The head of the linked list. + /// The list of new instances. + public static IList CreateCollection(OrderByClause orderByClause) + { + List result = new List(); + for (OrderByClause clause = orderByClause; clause != null; clause = clause.ThenBy) + { + if (clause.Expression is CountNode) + { + result.Add(new OrderByCountNode(clause)); + continue; + } + + if (clause.Expression is NonResourceRangeVariableReferenceNode || + clause.Expression is ResourceRangeVariableReferenceNode) + { + result.Add(new OrderByItNode(clause.Direction)); + continue; + } + + if (clause.Expression is SingleValueOpenPropertyAccessNode) + { + result.Add(new OrderByOpenPropertyNode(clause)); + } + else + { + result.Add(new OrderByPropertyNode(clause)); + } + } + + return result; + } + + internal static string RestorePropertyPath(SingleValueNode expression) + { + if (expression == null) + { + return String.Empty; + } + + string propertyName = String.Empty; + SingleValueNode source = null; + + var accessNode = expression as SingleValuePropertyAccessNode; + if (accessNode != null) + { + propertyName = accessNode.Property.Name; + source = accessNode.Source; + } + else + { + var complexNode = expression as SingleComplexNode; + if (complexNode != null) + { + propertyName = complexNode.Property.Name; + source = complexNode.Source; + } + else + { + var navNode = expression as SingleNavigationNode; + if (navNode != null) + { + propertyName = navNode.NavigationProperty.Name; + source = navNode.Source; + } + } + } + + var parentPath = RestorePropertyPath(source); + if (String.IsNullOrEmpty(parentPath)) + { + return propertyName; + } + else + { + return String.Format(CultureInfo.CurrentCulture, "{0}/{1}", parentPath, propertyName); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByOpenPropertyNode.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByOpenPropertyNode.cs new file mode 100644 index 0000000..a8c5c89 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByOpenPropertyNode.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents ordering on a dynamic property + /// + public class OrderByOpenPropertyNode : OrderByNode + { + /// + /// Initializes a new instance of the class. + /// + /// The order by clause for this open property. + public OrderByOpenPropertyNode(OrderByClause orderByClause) + : base(orderByClause) + { + if (orderByClause == null) + { + throw Error.ArgumentNull("orderByClause"); + } + + OrderByClause = orderByClause; + + var openPropertyExpression = orderByClause.Expression as SingleValueOpenPropertyAccessNode; + if (openPropertyExpression == null) + { + throw new ODataException(SRResources.OrderByClauseNotSupported); + } + + PropertyName = openPropertyExpression.Name; + } + + /// + /// The order by clause + /// + public OrderByClause OrderByClause { get; private set; } + + /// + /// The name of the dynamic property + /// + public string PropertyName { get; private set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByPropertyNode.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByPropertyNode.cs new file mode 100644 index 0000000..9865510 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByPropertyNode.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an order by expression. + /// + public class OrderByPropertyNode : OrderByNode + { + /// + /// Initializes a new instance of the class. + /// + /// The orderby clause representing property access. + public OrderByPropertyNode(OrderByClause orderByClause) + : base(orderByClause) + { + if (orderByClause == null) + { + throw Error.ArgumentNull("orderByClause"); + } + + OrderByClause = orderByClause; + Direction = orderByClause.Direction; + + SingleValuePropertyAccessNode propertyExpression = orderByClause.Expression as SingleValuePropertyAccessNode; + if (propertyExpression == null) + { + throw new ODataException(SRResources.OrderByClauseNotSupported); + } + else + { + Property = propertyExpression.Property; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The for this node. + /// The for this node. + public OrderByPropertyNode(IEdmProperty property, OrderByDirection direction) + : base(direction) + { + if (property == null) + { + throw Error.ArgumentNull("property"); + } + + Property = property; + } + + /// + /// Gets the of this node. + /// + public OrderByClause OrderByClause { get; private set; } + + /// + /// Gets the for the current node. + /// + public IEdmProperty Property { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByQueryOption.cs new file mode 100644 index 0000000..e5b8ba1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/OrderByQueryOption.cs @@ -0,0 +1,339 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a $orderby OData query option for querying. + /// + public class OrderByQueryOption + { + private OrderByClause _orderByClause; + private IList _orderByNodes; + private ODataQueryOptionParser _queryOptionParser; + + /// + /// Initialize a new instance of based on the raw $orderby value and + /// an EdmModel from . + /// + /// The raw value for $orderby query. It can be null or empty. + /// The which contains the and some type information + /// The which is used to parse the query option. + public OrderByQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + Context = context; + RawValue = rawValue; + Validator = OrderByQueryValidator.GetOrderByQueryValidator(context); + _queryOptionParser = queryOptionParser; + } + + internal OrderByQueryOption(string rawValue, ODataQueryContext context, string applyRaw) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (applyRaw == null) + { + throw Error.ArgumentNullOrEmpty("applyRaw"); + } + + Context = context; + RawValue = rawValue; + Validator = OrderByQueryValidator.GetOrderByQueryValidator(context); + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$orderby", rawValue }, { "$apply", applyRaw } }, + context.RequestContainer); + _queryOptionParser.ParseApply(); + } + + // This constructor is intended for unit testing only. + internal OrderByQueryOption(string rawValue, ODataQueryContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + Context = context; + RawValue = rawValue; + Validator = OrderByQueryValidator.GetOrderByQueryValidator(context); + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$orderby", rawValue } }, + context.RequestContainer); + } + + internal OrderByQueryOption(OrderByQueryOption orderBy) + { + Context = orderBy.Context; + RawValue = orderBy.RawValue; + Validator = orderBy.Validator; + _queryOptionParser = orderBy._queryOptionParser; + _orderByClause = orderBy._orderByClause; + _orderByNodes = orderBy._orderByNodes; + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets the mutable list of instances for this query option. + /// + public IList OrderByNodes + { + get + { + if (_orderByNodes == null) + { + _orderByNodes = OrderByNode.CreateCollection(OrderByClause); + } + return _orderByNodes; + } + } + + /// + /// Gets the raw $orderby value. + /// + public string RawValue { get; private set; } + + /// + /// Gets or sets the OrderBy Query Validator. + /// + public OrderByQueryValidator Validator { get; set; } + + /// + /// Gets the parsed for this query option. + /// + public OrderByClause OrderByClause + { + get + { + if (_orderByClause == null) + { + _orderByClause = _queryOptionParser.ParseOrderBy(); + _orderByClause = TranslateParameterAlias(_orderByClause); + } + + return _orderByClause; + } + } + + /// + /// Apply the $orderby query to the given IQueryable. + /// + /// The original . + /// The new after the orderby query has been applied to. + public IOrderedQueryable ApplyTo(IQueryable query) + { + return ApplyToCore(query, new ODataQuerySettings()) as IOrderedQueryable; + } + + /// + /// Apply the $orderby query to the given IQueryable. + /// + /// The original . + /// The that contains all the query application related settings. + /// The new after the orderby query has been applied to. + public IOrderedQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + return ApplyToCore(query, querySettings) as IOrderedQueryable; + } + + /// + /// Apply the $orderby query to the given IQueryable. + /// + /// The original . + /// The new after the orderby query has been applied to. + public IOrderedQueryable ApplyTo(IQueryable query) + { + return ApplyToCore(query, new ODataQuerySettings()); + } + + /// + /// Apply the $orderby query to the given IQueryable. + /// + /// The original . + /// The that contains all the query application related settings. + /// The new after the orderby query has been applied to. + public IOrderedQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + return ApplyToCore(query, querySettings); + } + + /// + /// Validate the orderby query based on the given . It throws an ODataException if validation failed. + /// + /// The instance which contains all the validation settings. + public void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + + private IOrderedQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings) + { + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + ICollection nodes = OrderByNodes; + + bool alreadyOrdered = false; + IQueryable querySoFar = query; + + HashSet propertiesSoFar = new HashSet(); + HashSet openPropertiesSoFar = new HashSet(); + bool orderByItSeen = false; + + foreach (OrderByNode node in nodes) + { + OrderByPropertyNode propertyNode = node as OrderByPropertyNode; + OrderByOpenPropertyNode openPropertyNode = node as OrderByOpenPropertyNode; + OrderByCountNode countNode = node as OrderByCountNode; + + if (propertyNode != null) + { + // Use autonomy class to achieve value equality for HasSet. + var edmPropertyWithPath = new { propertyNode.Property, propertyNode.PropertyPath }; + OrderByDirection direction = propertyNode.Direction; + + // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows + if (propertiesSoFar.Contains(edmPropertyWithPath)) + { + throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, edmPropertyWithPath.PropertyPath)); + } + + propertiesSoFar.Add(edmPropertyWithPath); + + if (propertyNode.OrderByClause != null) + { + querySoFar = AddOrderByQueryForProperty(query, querySettings, propertyNode.OrderByClause, querySoFar, direction, alreadyOrdered); + } + else + { + querySoFar = ExpressionHelpers.OrderByProperty(querySoFar, Context.Model, edmPropertyWithPath.Property, direction, Context.ElementClrType, alreadyOrdered); + } + + alreadyOrdered = true; + } + else if (openPropertyNode != null) + { + // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows + if (openPropertiesSoFar.Contains(openPropertyNode.PropertyName)) + { + throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, openPropertyNode.PropertyPath)); + } + + openPropertiesSoFar.Add(openPropertyNode.PropertyName); + Contract.Assert(openPropertyNode.OrderByClause != null); + querySoFar = AddOrderByQueryForProperty(query, querySettings, openPropertyNode.OrderByClause, querySoFar, openPropertyNode.Direction, alreadyOrdered); + alreadyOrdered = true; + } + else if (countNode != null) + { + Contract.Assert(countNode.OrderByClause != null); + querySoFar = AddOrderByQueryForProperty(query, querySettings, countNode.OrderByClause, querySoFar, countNode.Direction, alreadyOrdered); + alreadyOrdered = true; + } + else + { + // This check prevents queries with duplicate nodes (e.g. $orderby=$it,$it,$it,$it...) from causing stack overflows + if (orderByItSeen) + { + throw new ODataException(Error.Format(SRResources.OrderByDuplicateIt)); + } + + querySoFar = ExpressionHelpers.OrderByIt(querySoFar, node.Direction, Context.ElementClrType, alreadyOrdered); + alreadyOrdered = true; + orderByItSeen = true; + } + } + + return querySoFar as IOrderedQueryable; + } + + private IQueryable AddOrderByQueryForProperty(IQueryable query, ODataQuerySettings querySettings, + OrderByClause orderbyClause, IQueryable querySoFar, OrderByDirection direction, bool alreadyOrdered) + { + ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query); + + LambdaExpression orderByExpression = + FilterBinder.Bind(query, orderbyClause, Context.ElementClrType, Context, updatedSettings); + querySoFar = ExpressionHelpers.OrderBy(querySoFar, orderByExpression, direction, Context.ElementClrType, + alreadyOrdered); + return querySoFar; + } + + private OrderByClause TranslateParameterAlias(OrderByClause orderBy) + { + if (orderBy == null) + { + return null; + } + + SingleValueNode orderByExpression = orderBy.Expression.Accept( + new ParameterAliasNodeTranslator(_queryOptionParser.ParameterAliasNodes)) as SingleValueNode; + orderByExpression = orderByExpression ?? new ConstantNode(null, "null"); + + return new OrderByClause( + TranslateParameterAlias(orderBy.ThenBy), + orderByExpression, + orderBy.Direction, + orderBy.RangeVariable); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/PageAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/PageAttribute.cs new file mode 100644 index 0000000..fcda592 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/PageAttribute.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property or a class to specify that + /// the maximum value of $top and query result return number of that property or type. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] + public sealed class PageAttribute : Attribute + { + private int _maxTop = -1; + + /// + /// Sets the max value of $top that a client can request. + /// + public int MaxTop + { + get + { + return _maxTop; + } + set + { + _maxTop = value; + } + } + + /// + /// Sets the maximum number of query results to return. + /// + public int PageSize { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ParameterAliasNodeTranslator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ParameterAliasNodeTranslator.cs new file mode 100644 index 0000000..bce7431 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/ParameterAliasNodeTranslator.cs @@ -0,0 +1,404 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a translator to translate parameter alias nodes. + /// + public class ParameterAliasNodeTranslator : QueryNodeVisitor + { + private IDictionary _parameterAliasNode; + + /// + /// Initialize a new instance of . + /// + /// Parameter alias nodes mapping. + public ParameterAliasNodeTranslator(IDictionary parameterAliasNodes) + { + if (parameterAliasNodes == null) + { + throw Error.ArgumentNull("parameterAliasNodes"); + } + + _parameterAliasNode = parameterAliasNodes; + } + + /// + /// Translate an AllNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(AllNode nodeIn) + { + AllNode allNode = new AllNode(nodeIn.RangeVariables, nodeIn.CurrentRangeVariable); + + if (nodeIn.Source != null) + { + allNode.Source = (CollectionNode)nodeIn.Source.Accept(this); + } + + if (nodeIn.Body != null) + { + allNode.Body = (SingleValueNode)nodeIn.Body.Accept(this); + } + + return allNode; + } + + /// + /// Translate an AnyNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(AnyNode nodeIn) + { + AnyNode anyNode = new AnyNode(nodeIn.RangeVariables, nodeIn.CurrentRangeVariable); + + if (nodeIn.Source != null) + { + anyNode.Source = (CollectionNode)nodeIn.Source.Accept(this); + } + + if (nodeIn.Body != null) + { + anyNode.Body = (SingleValueNode)nodeIn.Body.Accept(this); + } + + return anyNode; + } + + /// + /// Translate a BinaryOperatorNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(BinaryOperatorNode nodeIn) + { + return new BinaryOperatorNode( + nodeIn.OperatorKind, + (SingleValueNode)nodeIn.Left.Accept(this), + (SingleValueNode)nodeIn.Right.Accept(this)); + } + + /// + /// Translate an InNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(InNode nodeIn) + { + return new InNode( + (SingleValueNode)nodeIn.Left.Accept(this), + (CollectionNode)nodeIn.Right.Accept(this)); + } + + /// + /// Translate a CollectionFunctionCallNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CollectionFunctionCallNode nodeIn) + { + return new CollectionFunctionCallNode( + nodeIn.Name, + nodeIn.Functions, + nodeIn.Parameters.Select(p => p.Accept(this)), + nodeIn.CollectionType, + nodeIn.Source == null ? null : nodeIn.Source.Accept(this)); + } + + /// + /// Translate a CollectionNavigationNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CollectionNavigationNode nodeIn) + { + return nodeIn.Source == null ? + nodeIn : + new CollectionNavigationNode( + (SingleResourceNode)nodeIn.Source.Accept(this), + nodeIn.NavigationProperty, + nodeIn.BindingPath ?? new EdmPathExpression(nodeIn.NavigationProperty.Name)); + } + + /// + /// Translate a CollectionOpenPropertyAccessNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CollectionOpenPropertyAccessNode nodeIn) + { + return new CollectionOpenPropertyAccessNode( + (SingleValueNode)nodeIn.Source.Accept(this), + nodeIn.Name); + } + + /// + /// Translate a CollectionComplexNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CollectionComplexNode nodeIn) + { + return new CollectionComplexNode( + (SingleResourceNode)nodeIn.Source.Accept(this), + nodeIn.Property); + } + + /// + /// Translate a CollectionPropertyAccessNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CollectionPropertyAccessNode nodeIn) + { + return new CollectionPropertyAccessNode( + (SingleValueNode)nodeIn.Source.Accept(this), + nodeIn.Property); + } + + /// + /// Translate a ConstantNode. + /// + /// The node to be translated. + /// The original node. + public override QueryNode Visit(ConstantNode nodeIn) + { + return nodeIn; + } + + /// + /// Translate a CollectionConstantNode. + /// + /// The node to be translated. + /// The original node. + public override QueryNode Visit(CollectionConstantNode nodeIn) + { + return nodeIn; + } + + /// + /// Translate a ConvertNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(ConvertNode nodeIn) + { + return new ConvertNode((SingleValueNode)nodeIn.Source.Accept(this), nodeIn.TypeReference); + } + + /// + /// Translate an CollectionResourceCastNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CollectionResourceCastNode nodeIn) + { + return new CollectionResourceCastNode( + (CollectionResourceNode)nodeIn.Source.Accept(this), + (IEdmStructuredType)nodeIn.ItemType.Definition); + } + + /// + /// Translate an CollectionResourceFunctionCallNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CollectionResourceFunctionCallNode nodeIn) + { + return new CollectionResourceFunctionCallNode( + nodeIn.Name, + nodeIn.Functions, + nodeIn.Parameters.Select(p => p.Accept(this)), + nodeIn.CollectionType, + (IEdmEntitySetBase)nodeIn.NavigationSource, + nodeIn.Source == null ? null : nodeIn.Source.Accept(this)); + } + + /// + /// Translate an ResourceRangeVariableReferenceNode. + /// + /// The node to be translated. + /// The original node. + public override QueryNode Visit(ResourceRangeVariableReferenceNode nodeIn) + { + return nodeIn; + } + + /// + /// Translate a NamedFunctionParameterNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(NamedFunctionParameterNode nodeIn) + { + return new NamedFunctionParameterNode( + nodeIn.Name, + nodeIn.Value == null ? null : nodeIn.Value.Accept(this)); + } + + /// + /// Translate a NonResourceRangeVariableReferenceNode. + /// + /// The node to be translated. + /// The original node. + public override QueryNode Visit(NonResourceRangeVariableReferenceNode nodeIn) + { + return nodeIn; + } + + /// + /// Translate a ParameterAliasNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(ParameterAliasNode nodeIn) + { + SingleValueNode node = ODataPathSegmentTranslator.TranslateParameterAlias(nodeIn, _parameterAliasNode); + + if (node == null) + { + return new ConstantNode(null); + } + else + { + return node.Accept(this); + } + } + + /// + /// Translate a SearchTermNode. + /// + /// The node to be translated. + /// The original node. + public override QueryNode Visit(SearchTermNode nodeIn) + { + return nodeIn; + } + + /// + /// Translate a SingleResourceCastNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(SingleResourceCastNode nodeIn) + { + return nodeIn.Source == null ? + nodeIn : + new SingleResourceCastNode( + (SingleResourceNode)nodeIn.Source.Accept(this), + (IEdmStructuredType)nodeIn.TypeReference.Definition); + } + + /// + /// Translate a SingleResourceFunctionCallNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(SingleResourceFunctionCallNode nodeIn) + { + return new SingleResourceFunctionCallNode( + nodeIn.Name, + nodeIn.Functions, + nodeIn.Parameters.Select(p => p.Accept(this)), + nodeIn.StructuredTypeReference, + nodeIn.NavigationSource, + nodeIn.Source == null ? null : nodeIn.Source.Accept(this)); + } + + /// + /// Translate a SingleNavigationNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(SingleNavigationNode nodeIn) + { + return nodeIn.Source == null ? + nodeIn : + new SingleNavigationNode( + (SingleResourceNode)nodeIn.Source.Accept(this), + nodeIn.NavigationProperty, + nodeIn.BindingPath ?? new EdmPathExpression(nodeIn.NavigationProperty.Name)); + } + + /// + /// Translate a SingleValueFunctionCallNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(SingleValueFunctionCallNode nodeIn) + { + return new SingleValueFunctionCallNode( + nodeIn.Name, + nodeIn.Functions, + nodeIn.Parameters.Select(p => p.Accept(this)), + nodeIn.TypeReference, + nodeIn.Source == null ? null : nodeIn.Source.Accept(this)); + } + + /// + /// Translate a SingleValueOpenPropertyAccessNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(SingleValueOpenPropertyAccessNode nodeIn) + { + return new SingleValueOpenPropertyAccessNode( + (SingleValueNode)nodeIn.Source.Accept(this), + nodeIn.Name); + } + + /// + /// Translate a SingleValuePropertyAccessNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(SingleValuePropertyAccessNode nodeIn) + { + return new SingleValuePropertyAccessNode( + (SingleValueNode)nodeIn.Source.Accept(this), + nodeIn.Property); + } + + /// + /// Translate a SingleComplexNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(SingleComplexNode nodeIn) + { + return new SingleComplexNode( + (SingleResourceNode)nodeIn.Source.Accept(this), + nodeIn.Property); + } + + /// + /// Translate an UnaryOperatorNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(UnaryOperatorNode nodeIn) + { + return new UnaryOperatorNode(nodeIn.OperatorKind, (SingleValueNode)nodeIn.Operand.Accept(this)); + } + + /// + /// Translate a CountNode. + /// + /// The node to be translated. + /// The translated node. + public override QueryNode Visit(CountNode nodeIn) + { + return new CountNode((CollectionNode)nodeIn.Source.Accept(this)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/QueryOptionSetting.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/QueryOptionSetting.cs new file mode 100644 index 0000000..bf51fc1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/QueryOptionSetting.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents the setting of a query option. + /// + public enum QueryOptionSetting + { + /// + /// Allowed to be applied. + /// + Allowed, + + /// + /// Disallowed to be applied. + /// + Disabled + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectAttribute.cs new file mode 100644 index 0000000..dbc4f4c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectAttribute.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property or a class + /// correlate to OData's $select query option settings. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)] + [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", + Justification = "Don't want those argument to be retrievable")] + public sealed class SelectAttribute : Attribute + { + private readonly Dictionary _selectConfigurations = new Dictionary(); + private SelectExpandType _selectType; + private SelectExpandType? _defaultSelectType; + + /// + /// Initializes a new instance of the class. + /// + public SelectAttribute() + { + _defaultSelectType = SelectExpandType.Allowed; + } + + /// + /// Initializes a new instance of the class + /// with the name of allowed select properties. + /// + public SelectAttribute(params string[] properties) + { + foreach (var property in properties) + { + if (!_selectConfigurations.ContainsKey(property)) + { + _selectConfigurations.Add(property, SelectExpandType.Allowed); + } + } + } + + /// + /// Gets or sets the of properties. + /// + public Dictionary SelectConfigurations + { + get + { + return _selectConfigurations; + } + } + + /// + /// Gets or sets the of properties. + /// + public SelectExpandType SelectType + { + get + { + return _selectType; + } + set + { + _selectType = value; + List keys = _selectConfigurations.Keys.ToList(); + foreach (var property in keys) + { + _selectConfigurations[property] = _selectType; + } + + if (_selectConfigurations.Count == 0) + { + _defaultSelectType = _selectType; + } + } + } + + internal SelectExpandType? DefaultSelectType + { + get + { + return _defaultSelectType; + } + set + { + _defaultSelectType = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs new file mode 100644 index 0000000..2f420d6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs @@ -0,0 +1,693 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents the OData $select and $expand query options. + /// + public class SelectExpandQueryOption + { + private SelectExpandClause _selectExpandClause; + private ODataQueryOptionParser _queryOptionParser; + private SelectExpandClause _processedSelectExpandClause; + + // Give _levelsMaxLiteralExpansionDepth a negative value meaning it is uninitialized, and it will be set to: + // 1. LevelsMaxLiteralExpansionDepth or + // 2. ODataValidationSettings.MaxExpansionDepth + private int _levelsMaxLiteralExpansionDepth = -1; + + /// + /// Initializes a new instance of the class. + /// + /// The $select query parameter value. + /// The $expand query parameter value. + /// The which contains the and some type information. + /// The which is used to parse the query option. + public SelectExpandQueryOption(string select, string expand, ODataQueryContext context, + ODataQueryOptionParser queryOptionParser) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(select) && String.IsNullOrEmpty(expand)) + { + throw Error.Argument(SRResources.SelectExpandEmptyOrNull); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + IEdmEntityType entityType = context.ElementType as IEdmEntityType; + if (entityType == null) + { + throw Error.Argument("context", SRResources.SelectNonEntity, context.ElementType.ToTraceString()); + } + + Context = context; + RawSelect = select; + RawExpand = expand; + Validator = SelectExpandQueryValidator.GetSelectExpandQueryValidator(context); + _queryOptionParser = queryOptionParser; + } + + internal SelectExpandQueryOption( + string select, + string expand, + ODataQueryContext context, + SelectExpandClause selectExpandClause) + : this(select, expand, context) + { + _selectExpandClause = selectExpandClause; + } + + // This constructor is intended for unit testing only. + internal SelectExpandQueryOption(string select, string expand, ODataQueryContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(select) && String.IsNullOrEmpty(expand)) + { + throw Error.Argument(SRResources.SelectExpandEmptyOrNull); + } + + IEdmEntityType entityType = context.ElementType as IEdmEntityType; + if (entityType == null) + { + throw Error.Argument("context", SRResources.SelectNonEntity, context.ElementType.ToTraceString()); + } + + Context = context; + RawSelect = select; + RawExpand = expand; + Validator = SelectExpandQueryValidator.GetSelectExpandQueryValidator(context); + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$select", select }, { "$expand", expand } }, + context.RequestContainer); + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets the raw $select value. + /// + public string RawSelect { get; private set; } + + /// + /// Gets the raw $expand value. + /// + public string RawExpand { get; private set; } + + /// + /// Gets or sets the $select and $expand query validator. + /// + public SelectExpandQueryValidator Validator { get; set; } + + /// + /// Gets the parsed for this query option. + /// + public SelectExpandClause SelectExpandClause + { + get + { + if (_selectExpandClause == null) + { + _selectExpandClause = _queryOptionParser.ParseSelectAndExpand(); + } + + return _selectExpandClause; + } + } + + internal SelectExpandClause ProcessedSelectExpandClause + { + get + { + if (_processedSelectExpandClause != null) + { + return _processedSelectExpandClause; + } + + _processedSelectExpandClause = this.ProcessLevels(); + return _processedSelectExpandClause; + } + } + + /// + /// Gets or sets the number of levels that a top level $expand=NavigationProperty($levels=max) + /// will be expanded. + /// This value will decrease by one with each nesting level in the $expand clause. + /// For example, with a property value 5, the following query $expand=A($expand=B($expand=C($levels=max))) + /// will be interpreted as $expand=A($expand=B($expand=C($levels=3))). + /// If the query gets validated, the value + /// must be greater than or equal to this value. + /// + public int LevelsMaxLiteralExpansionDepth + { + get + { + return _levelsMaxLiteralExpansionDepth; + } + set + { + if (value < 0) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("LevelsMaxLiteralExpansionDepth", value, 0); + } + + _levelsMaxLiteralExpansionDepth = value; + } + } + + /// + /// Applies the $select and $expand query options to the given using the given + /// . + /// + /// The original . + /// The that contains all the query application related settings. + /// The new after the filter query has been applied to. + public IQueryable ApplyTo(IQueryable queryable, ODataQuerySettings settings) + { + if (queryable == null) + { + throw Error.ArgumentNull("queryable"); + } + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(settings, queryable); + + return SelectExpandBinder.Bind(queryable, updatedSettings, this); + } + + /// + /// Applies the $select and $expand query options to the given entity using the given . + /// + /// The original entity. + /// The that contains all the query application related settings. + /// The new entity after the $select and $expand query has been applied to. + public object ApplyTo(object entity, ODataQuerySettings settings) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(settings, query: null); + + return SelectExpandBinder.Bind(entity, updatedSettings, this); + } + + /// + /// Validate the $select and $expand query based on the given . It throws an ODataException if validation failed. + /// + /// The instance which contains all the validation settings. + public void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + + internal SelectExpandClause ProcessLevels() + { + bool levelsEncountered; + bool isMaxLevel; + ModelBoundQuerySettings querySettings = EdmLibHelpers.GetModelBoundQuerySettings(Context.TargetProperty, + Context.TargetStructuredType, Context.Model, Context.DefaultQuerySettings); + return ProcessLevels(SelectExpandClause, + LevelsMaxLiteralExpansionDepth < 0 ? ODataValidationSettings.DefaultMaxExpansionDepth : LevelsMaxLiteralExpansionDepth, + querySettings, + out levelsEncountered, + out isMaxLevel); + } + + // Process $levels in SelectExpandClause. + private SelectExpandClause ProcessLevels( + SelectExpandClause selectExpandClause, + int levelsMaxLiteralExpansionDepth, + ModelBoundQuerySettings querySettings, + out bool levelsEncountered, + out bool isMaxLevel) + { + levelsEncountered = false; + isMaxLevel = false; + + if (selectExpandClause == null) + { + return null; + } + + // Process $levels in SelectItems of SelectExpandClause. + IEnumerable selectItems = ProcessLevels( + selectExpandClause.SelectedItems, + levelsMaxLiteralExpansionDepth, + querySettings, + out levelsEncountered, + out isMaxLevel); + + if (selectItems == null) + { + return null; + } + else if (levelsEncountered) + { + return new SelectExpandClause(selectItems, selectExpandClause.AllSelected); + } + else + { + // Return the original SelectExpandClause if no $levels is found. + return selectExpandClause; + } + } + + // Process $levels in SelectedItems. + private IEnumerable ProcessLevels( + IEnumerable selectItems, + int levelsMaxLiteralExpansionDepth, + ModelBoundQuerySettings querySettings, + out bool levelsEncountered, + out bool isMaxLevel) + { + levelsEncountered = false; + isMaxLevel = false; + IList items = new List(); + + foreach (SelectItem selectItem in selectItems) + { + ExpandedNavigationSelectItem item = selectItem as ExpandedNavigationSelectItem; + + if (item == null) + { + // There is no $levels in non-ExpandedNavigationSelectItem. + items.Add(selectItem); + } + else + { + bool levelsEncouteredInExpand; + bool isMaxLevelInExpand; + // Process $levels in ExpandedNavigationSelectItem. + ExpandedNavigationSelectItem expandItem = ProcessLevels( + item, + levelsMaxLiteralExpansionDepth, + querySettings, + out levelsEncouteredInExpand, + out isMaxLevelInExpand); + + if (item.LevelsOption != null && item.LevelsOption.Level > 0 && expandItem == null) + { + // Abandon this attempt if any of the items failed to expand + return null; + } + else if (item.LevelsOption != null) + { + // The expansion would be volatile if any of the expand item is max level + isMaxLevel = isMaxLevel || isMaxLevelInExpand; + } + + levelsEncountered = levelsEncountered || levelsEncouteredInExpand; + + if (expandItem != null) + { + items.Add(expandItem); + } + } + } + return items; + } + + private void GetAutoSelectExpandItems( + IEdmEntityType baseEntityType, + IEdmModel model, + IEdmNavigationSource navigationSource, + bool isAllSelected, + ModelBoundQuerySettings modelBoundQuerySettings, + int depth, + out List autoSelectItems, + out List autoExpandItems) + { + autoSelectItems = new List(); + var autoSelectProperties = EdmLibHelpers.GetAutoSelectProperties(null, + baseEntityType, model, modelBoundQuerySettings); + foreach (var autoSelectProperty in autoSelectProperties) + { + List pathSegments = new List() + { + new PropertySegment(autoSelectProperty) + }; + + PathSelectItem pathSelectItem = new PathSelectItem( + new ODataSelectPath(pathSegments)); + autoSelectItems.Add(pathSelectItem); + } + + autoExpandItems = new List(); + depth--; + if (depth < 0) + { + return; + } + + var autoExpandNavigationProperties = EdmLibHelpers.GetAutoExpandNavigationProperties(null, baseEntityType, + model, !isAllSelected, modelBoundQuerySettings); + + foreach (var navigationProperty in autoExpandNavigationProperties) + { + IEdmNavigationSource currentEdmNavigationSource = + navigationSource.FindNavigationTarget(navigationProperty); + + if (currentEdmNavigationSource != null) + { + List pathSegments = new List() + { + new NavigationPropertySegment(navigationProperty, currentEdmNavigationSource) + }; + + ODataExpandPath expandPath = new ODataExpandPath(pathSegments); + SelectExpandClause selectExpandClause = new SelectExpandClause(new List(), + true); + ExpandedNavigationSelectItem item = new ExpandedNavigationSelectItem(expandPath, + currentEdmNavigationSource, selectExpandClause); + modelBoundQuerySettings = EdmLibHelpers.GetModelBoundQuerySettings(navigationProperty, + navigationProperty.ToEntityType(), model); + List nestedSelectItems; + List nestedExpandItems; + + int maxExpandDepth = GetMaxExpandDepth(modelBoundQuerySettings, navigationProperty.Name); + if (maxExpandDepth != 0 && maxExpandDepth < depth) + { + depth = maxExpandDepth; + } + + GetAutoSelectExpandItems( + currentEdmNavigationSource.EntityType(), + model, + item.NavigationSource, + true, + modelBoundQuerySettings, + depth, + out nestedSelectItems, + out nestedExpandItems); + + selectExpandClause = new SelectExpandClause(nestedSelectItems.Concat(nestedExpandItems), + nestedSelectItems.Count == 0); + item = new ExpandedNavigationSelectItem(expandPath, currentEdmNavigationSource, + selectExpandClause); + + autoExpandItems.Add(item); + if (!isAllSelected || autoSelectProperties.Count() != 0) + { + PathSelectItem pathSelectItem = new PathSelectItem( + new ODataSelectPath(pathSegments)); + autoExpandItems.Add(pathSelectItem); + } + } + } + } + + // Process $levels in ExpandedNavigationSelectItem. + private ExpandedNavigationSelectItem ProcessLevels( + ExpandedNavigationSelectItem expandItem, + int levelsMaxLiteralExpansionDepth, + ModelBoundQuerySettings querySettings, + out bool levelsEncounteredInExpand, + out bool isMaxLevelInExpand) + { + int level; + isMaxLevelInExpand = false; + + if (expandItem.LevelsOption == null) + { + levelsEncounteredInExpand = false; + level = 1; + } + else + { + levelsEncounteredInExpand = true; + if (expandItem.LevelsOption.IsMaxLevel) + { + isMaxLevelInExpand = true; + level = levelsMaxLiteralExpansionDepth; + } + else + { + level = (int)expandItem.LevelsOption.Level; + } + } + + // Do not expand when: + // 1. $levels is equal to or less than 0. + // 2. $levels value is greater than current MaxExpansionDepth + if (level <= 0 || level > levelsMaxLiteralExpansionDepth) + { + return null; + } + + ExpandedNavigationSelectItem item = null; + SelectExpandClause currentSelectExpandClause = null; + SelectExpandClause selectExpandClause = null; + bool levelsEncounteredInInnerExpand = false; + bool isMaxLevelInInnerExpand = false; + var entityType = expandItem.NavigationSource.EntityType(); + IEdmNavigationProperty navigationProperty = + (expandItem.PathToNavigationProperty.LastSegment as NavigationPropertySegment).NavigationProperty; + ModelBoundQuerySettings nestQuerySettings = EdmLibHelpers.GetModelBoundQuerySettings(navigationProperty, + navigationProperty.ToEntityType(), + Context.Model); + + // Try different expansion depth until expandItem.SelectAndExpand is successfully expanded + while (selectExpandClause == null && level > 0) + { + selectExpandClause = ProcessLevels( + expandItem.SelectAndExpand, + levelsMaxLiteralExpansionDepth - level, + nestQuerySettings, + out levelsEncounteredInInnerExpand, + out isMaxLevelInInnerExpand); + level--; + } + + if (selectExpandClause == null) + { + return null; + } + + // Correct level value + level++; + List originAutoSelectItems; + List originAutoExpandItems; + int maxDepth = GetMaxExpandDepth(querySettings, navigationProperty.Name); + if (maxDepth == 0 || levelsMaxLiteralExpansionDepth > maxDepth) + { + maxDepth = levelsMaxLiteralExpansionDepth; + } + + GetAutoSelectExpandItems( + entityType, + Context.Model, + expandItem.NavigationSource, + selectExpandClause.AllSelected, + nestQuerySettings, + maxDepth - 1, + out originAutoSelectItems, + out originAutoExpandItems); + if (expandItem.SelectAndExpand.SelectedItems.Any(it => it is PathSelectItem)) + { + originAutoSelectItems.Clear(); + } + + if (level > 1) + { + RemoveSameExpandItem(navigationProperty, originAutoExpandItems); + } + + List autoExpandItems = new List(originAutoExpandItems); + bool hasAutoSelectExpandInExpand = (originAutoSelectItems.Count() + originAutoExpandItems.Count() != 0); + bool allSelected = originAutoSelectItems.Count == 0 && selectExpandClause.AllSelected; + + while (level > 0) + { + autoExpandItems = RemoveExpandItemExceedMaxDepth(maxDepth - level, originAutoExpandItems); + if (item == null) + { + if (hasAutoSelectExpandInExpand) + { + currentSelectExpandClause = new SelectExpandClause( + new SelectItem[] { }.Concat(selectExpandClause.SelectedItems) + .Concat(originAutoSelectItems).Concat(autoExpandItems), + allSelected); + } + else + { + currentSelectExpandClause = selectExpandClause; + } + } + else if (selectExpandClause.AllSelected) + { + // Concat the processed items + currentSelectExpandClause = new SelectExpandClause( + new SelectItem[] { item }.Concat(selectExpandClause.SelectedItems) + .Concat(originAutoSelectItems).Concat(autoExpandItems), + allSelected); + } + else + { + // PathSelectItem is needed for the expanded item if AllSelected is false. + PathSelectItem pathSelectItem = new PathSelectItem( + new ODataSelectPath(expandItem.PathToNavigationProperty)); + + // Keep default SelectItems before expanded item to keep consistent with normal SelectExpandClause + SelectItem[] items = new SelectItem[] { item, pathSelectItem }; + currentSelectExpandClause = new SelectExpandClause( + new SelectItem[] { }.Concat(selectExpandClause.SelectedItems) + .Concat(items) + .Concat(originAutoSelectItems).Concat(autoExpandItems), + allSelected); + } + + // Construct a new ExpandedNavigationSelectItem with current SelectExpandClause. + item = new ExpandedNavigationSelectItem( + expandItem.PathToNavigationProperty, + expandItem.NavigationSource, + currentSelectExpandClause); + + level--; + + // Need expand and construct selectExpandClause every time if it is max level in inner expand + if (isMaxLevelInInnerExpand) + { + selectExpandClause = ProcessLevels( + expandItem.SelectAndExpand, + levelsMaxLiteralExpansionDepth - level, + nestQuerySettings, + out levelsEncounteredInInnerExpand, + out isMaxLevelInInnerExpand); + } + } + + levelsEncounteredInExpand = levelsEncounteredInExpand || levelsEncounteredInInnerExpand || hasAutoSelectExpandInExpand; + isMaxLevelInExpand = isMaxLevelInExpand || isMaxLevelInInnerExpand; + + return item; + } + + private static List RemoveExpandItemExceedMaxDepth(int depth, IEnumerable autoExpandItems) + { + List selectItems = new List(); + if (depth <= 0) + { + foreach (SelectItem autoSelectItem in autoExpandItems) + { + if (!(autoSelectItem is ExpandedNavigationSelectItem)) + { + selectItems.Add(autoSelectItem); + } + } + } + else + { + foreach (var autoExpandItem in autoExpandItems) + { + ExpandedNavigationSelectItem expandItem = autoExpandItem as ExpandedNavigationSelectItem; + if (expandItem != null) + { + SelectExpandClause selectExpandClause = + new SelectExpandClause( + RemoveExpandItemExceedMaxDepth(depth - 1, expandItem.SelectAndExpand.SelectedItems), + expandItem.SelectAndExpand.AllSelected); + expandItem = new ExpandedNavigationSelectItem(expandItem.PathToNavigationProperty, + expandItem.NavigationSource, selectExpandClause); + selectItems.Add(expandItem); + } + else + { + selectItems.Add(autoExpandItem); + } + } + } + + return selectItems; + } + + private static void RemoveSameExpandItem(IEdmNavigationProperty navigationProperty, List autoExpandItems) + { + for (int i = 0; i < autoExpandItems.Count; i++) + { + ExpandedNavigationSelectItem expandItem = autoExpandItems[i] as ExpandedNavigationSelectItem; + IEdmNavigationProperty autoExpandNavigationProperty = + (expandItem.PathToNavigationProperty.LastSegment as NavigationPropertySegment).NavigationProperty; + if (navigationProperty.Name.Equals(autoExpandNavigationProperty.Name)) + { + autoExpandItems.RemoveAt(i); + return; + } + } + } + + private static int GetMaxExpandDepth(ModelBoundQuerySettings querySettings, string propertyName) + { + int result = 0; + if (querySettings != null) + { + ExpandConfiguration expandConfiguration; + if (querySettings.ExpandConfigurations.TryGetValue(propertyName, out expandConfiguration)) + { + result = expandConfiguration.MaxDepth; + } + else + { + if (querySettings.DefaultExpandType.HasValue && + querySettings.DefaultExpandType != SelectExpandType.Disabled) + { + result = querySettings.DefaultMaxDepth; + } + } + } + + return result; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandType.cs new file mode 100644 index 0000000..845f5e4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents the type of expand and select. + /// + public enum SelectExpandType + { + /// + /// Allowed to be expanded and selected. + /// + Allowed, + + /// + /// Automatic expanded and selected. + /// + Automatic, + + /// + /// Disallowed to be expanded and selected. + /// + Disabled + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipQueryOption.cs new file mode 100644 index 0000000..8372ccf --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipQueryOption.cs @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a $skip OData query option for querying. + /// + public class SkipQueryOption + { + private int? _value; + private ODataQueryOptionParser _queryOptionParser; + + /// + /// Initialize a new instance of based on the raw $skip value and + /// an EdmModel from . + /// + /// The raw value for $skip query. It can be null or empty. + /// The which contains the and some type information + /// The which is used to parse the query option. + public SkipQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + Context = context; + RawValue = rawValue; + Validator = SkipQueryValidator.GetSkipQueryValidator(context); + _queryOptionParser = queryOptionParser; + } + + // This constructor is intended for unit testing only. + internal SkipQueryOption(string rawValue, ODataQueryContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + Context = context; + RawValue = rawValue; + Validator = SkipQueryValidator.GetSkipQueryValidator(context); + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$skip", rawValue } }); + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets the raw $skip value. + /// + public string RawValue { get; private set; } + + /// + /// Gets the value of the $skip as a parsed integer. + /// + public int Value + { + get + { + if (_value == null) + { + long? skipValue = _queryOptionParser.ParseSkip(); + + if (skipValue.HasValue && skipValue > Int32.MaxValue) + { + throw new ODataException(Error.Format( + SRResources.SkipTopLimitExceeded, + Int32.MaxValue, + AllowedQueryOptions.Skip, + RawValue)); + } + + _value = (int?)skipValue; + } + + Contract.Assert(_value.HasValue); + return _value.Value; + } + } + + /// + /// Gets or sets the Skip Query Validator. + /// + public SkipQueryValidator Validator { get; set; } + + /// + /// Apply the $skip query to the given IQueryable. + /// + /// The original . + /// The query settings to use while applying this query option. + /// The new after the skip query has been applied to. + public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + return ApplyToCore(query, querySettings) as IOrderedQueryable; + } + + /// + /// Apply the $skip query to the given IQueryable. + /// + /// The original . + /// The query settings to use while applying this query option. + /// The new after the skip query has been applied to. + public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + return ApplyToCore(query, querySettings); + } + + /// + /// Validate the skip query based on the given . It throws an ODataException if validation failed. + /// + /// The instance which contains all the validation settings. + public void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + + private IQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings) + { + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + return ExpressionHelpers.Skip(query, Value, query.ElementType, querySettings.EnableConstantParameterization); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenHandler.cs new file mode 100644 index 0000000..cf4254f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenHandler.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNet.OData.Formatter.Serialization; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents how NextLink for paging is generated. + /// + public abstract class SkipTokenHandler + { + /// + /// Apply the $skiptoken query to the given IQueryable. + /// + /// The original . + /// The query option that contains all the relevant information for applying skiptoken. + /// The new after the skiptoken query has been applied to. + public abstract IQueryable ApplyTo(IQueryable query, SkipTokenQueryOption skipTokenQueryOption); + + /// + /// Apply the $skiptoken query to the given IQueryable. + /// + /// The original . + /// The query option that contains all the relevant information for applying skiptoken. + /// The new after the skiptoken query has been applied to. + public abstract IQueryable ApplyTo(IQueryable query, SkipTokenQueryOption skipTokenQueryOption); + + /// + /// Returns the URI for NextPageLink + /// + /// BaseUri for nextlink. + /// Maximum number of records in the set of partial results for a resource. + /// Instance based on which SkipToken value will be generated. + /// Serializer context + /// URI for the NextPageLink. + public abstract Uri GenerateNextPageLink(Uri baseUri, int pageSize, Object instance, ODataSerializerContext context); + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenQueryOption.cs new file mode 100644 index 0000000..cd08e2c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/SkipTokenQueryOption.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a $skiptoken OData query option for querying. + /// + public class SkipTokenQueryOption + { + /// + /// Generates the nextlink value and consumes the skiptoken value. + /// + private SkipTokenHandler skipTokenHandler; + + /// + /// Initialize a new instance of based on the raw $skiptoken value and + /// an EdmModel from . + /// + /// The raw value for $skiptoken query. + /// The which contains the and some type information + /// The which is used to parse the query option. + public SkipTokenQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + RawValue = rawValue; + Validator = context.GetSkipTokenQueryValidator(); + skipTokenHandler = context.GetSkipTokenHandler(); + Context = context; + } + + /// + /// Gets the raw $skiptoken value. + /// + public string RawValue { get; private set; } + + /// + /// Gets and sets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets or sets the SkipToken Query Validator. + /// + public SkipTokenQueryValidator Validator { get; } + + /// + /// Gets or sets the query setting + /// + public ODataQuerySettings QuerySettings { get; private set; } + + /// + /// Gets or sets the QueryOptions + /// + public ODataQueryOptions QueryOptions { get; private set; } + + /// + /// Apply the $skiptoken query to the given IQueryable. + /// + /// The original . + /// The query settings to use while applying this query option. + /// Information about the other query options. + /// The new after the skiptoken query has been applied to. + public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, ODataQueryOptions queryOptions) + { + QuerySettings = querySettings; + QueryOptions = queryOptions; + return skipTokenHandler.ApplyTo(query, this) as IOrderedQueryable; + } + + /// + /// Apply the $skiptoken query to the given IQueryable. + /// + /// The original . + /// The query settings to use while applying this query option. + /// Information about the other query options. + /// The new after the skiptoken query has been applied to. + public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, ODataQueryOptions queryOptions) + { + QuerySettings = querySettings; + QueryOptions = queryOptions; + return skipTokenHandler.ApplyTo(query, this); + } + + /// + /// Validate the skiptoken query based on the given . It throws an ODataException if validation failed. + /// + /// The instance which contains all the validation settings. + public void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TopQueryOption.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TopQueryOption.cs new file mode 100644 index 0000000..a480dfa --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TopQueryOption.cs @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a $top OData query option for querying. + /// + public class TopQueryOption + { + private int? _value; + private ODataQueryOptionParser _queryOptionParser; + + /// + /// Initialize a new instance of based on the raw $top value and + /// an EdmModel from . + /// + /// The raw value for $top query. It can be null or empty. + /// The which contains the and some type information + /// The which is used to parse the query option. + public TopQueryOption(string rawValue, ODataQueryContext context, ODataQueryOptionParser queryOptionParser) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + if (queryOptionParser == null) + { + throw Error.ArgumentNull("queryOptionParser"); + } + + Context = context; + RawValue = rawValue; + Validator = TopQueryValidator.GetTopQueryValidator(context); + _queryOptionParser = queryOptionParser; + } + + // This constructor is intended for unit testing only. + internal TopQueryOption(string rawValue, ODataQueryContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (String.IsNullOrEmpty(rawValue)) + { + throw Error.ArgumentNullOrEmpty("rawValue"); + } + + Context = context; + RawValue = rawValue; + Validator = TopQueryValidator.GetTopQueryValidator(context); + _queryOptionParser = new ODataQueryOptionParser( + context.Model, + context.ElementType, + context.NavigationSource, + new Dictionary { { "$top", rawValue } }); + } + + /// + /// Gets the given . + /// + public ODataQueryContext Context { get; private set; } + + /// + /// Gets the raw $top value. + /// + public string RawValue { get; private set; } + + /// + /// Gets the value of the $top as a parsed integer. + /// + public int Value + { + get + { + if (_value == null) + { + long? topValue = _queryOptionParser.ParseTop(); + + if (topValue.HasValue && topValue > Int32.MaxValue) + { + throw new ODataException(Error.Format( + SRResources.SkipTopLimitExceeded, + Int32.MaxValue, + AllowedQueryOptions.Top, + RawValue)); + } + + _value = (int?)topValue; + } + + Contract.Assert(_value.HasValue); + return _value.Value; + } + } + + /// + /// Gets or sets the Top Query Validator. + /// + public TopQueryValidator Validator { get; set; } + + /// + /// Apply the $top query to the given IQueryable. + /// + /// The original . + /// The query settings to use while applying this query option. + /// The new after the top query has been applied to. + public IOrderedQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + return ApplyToCore(query, querySettings) as IOrderedQueryable; + } + + /// + /// Apply the $top query to the given IQueryable. + /// + /// The original . + /// The query settings to use while applying this query option. + /// The new after the top query has been applied to. + public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) + { + return ApplyToCore(query, querySettings); + } + + /// + /// Validate the top query based on the given . It throws an ODataException if validation failed. + /// + /// The instance which contains all the validation settings. + public void Validate(ODataValidationSettings validationSettings) + { + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (Validator != null) + { + Validator.Validate(this, validationSettings); + } + } + + private IQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings) + { + if (Context.ElementClrType == null) + { + throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo"); + } + + return ExpressionHelpers.Take(query, Value, query.ElementType, querySettings.EnableConstantParameterization); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TruncatedCollectionOfT.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TruncatedCollectionOfT.cs new file mode 100644 index 0000000..061510d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/TruncatedCollectionOfT.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents a class that truncates a collection to a given page size. + /// + /// The collection element type. + public class TruncatedCollection : List, ITruncatedCollection, IEnumerable, ICountOptionCollection + { + private const int MinPageSize = 1; + + private bool _isTruncated; + private int _pageSize; + private long? _totalCount; + + /// + /// Initializes a new instance of the class. + /// + /// The collection to be truncated. + /// The page size. + public TruncatedCollection(IEnumerable source, int pageSize) + : base(source.Take(checked(pageSize + 1))) + { + Initialize(pageSize); + } + + /// + /// Initializes a new instance of the class. + /// + /// The queryable collection to be truncated. + /// The page size. + // NOTE: The queryable version calls Queryable.Take which actually gets translated to the backend query where as + // the enumerable version just enumerates and is inefficient. + public TruncatedCollection(IQueryable source, int pageSize) + : base(source.Take(checked(pageSize + 1))) + { + Initialize(pageSize); + } + + /// + /// Initializes a new instance of the class. + /// + /// The queryable collection to be truncated. + /// The page size. + /// The total count. + public TruncatedCollection(IEnumerable source, int pageSize, long? totalCount) + : base(pageSize > 0 ? source.Take(checked(pageSize + 1)) : source) + { + if (pageSize > 0) + { + Initialize(pageSize); + } + + _totalCount = totalCount; + } + + /// + /// Initializes a new instance of the class. + /// + /// The queryable collection to be truncated. + /// The page size. + /// The total count. + // NOTE: The queryable version calls Queryable.Take which actually gets translated to the backend query where as + // the enumerable version just enumerates and is inefficient. + public TruncatedCollection(IQueryable source, int pageSize, long? totalCount) + : base(pageSize > 0 ? source.Take(checked(pageSize + 1)) : source) + { + if (pageSize > 0) + { + Initialize(pageSize); + } + + _totalCount = totalCount; + } + + private void Initialize(int pageSize) + { + if (pageSize < MinPageSize) + { + throw Error.ArgumentMustBeGreaterThanOrEqualTo("pageSize", pageSize, MinPageSize); + } + + _pageSize = pageSize; + + if (Count > pageSize) + { + _isTruncated = true; + RemoveAt(Count - 1); + } + } + + /// + public int PageSize + { + get { return _pageSize; } + } + + /// + public bool IsTruncated + { + get { return _isTruncated; } + } + + /// + public long? TotalCount + { + get { return _totalCount; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/UnsortableAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/UnsortableAttribute.cs new file mode 100644 index 0000000..0e81ed2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/UnsortableAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// Represents an that can be placed on a property to specify that the property cannot be used in the $orderby OData query option. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class UnsortableAttribute : Attribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/CountQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/CountQueryValidator.cs new file mode 100644 index 0000000..cf2fd07 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/CountQueryValidator.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate a + /// based on the . + /// + public class CountQueryValidator + { + private readonly DefaultQuerySettings _defaultQuerySettings; + + /// + /// Initializes a new instance of the class based on + /// the . + /// + /// The . + public CountQueryValidator(DefaultQuerySettings defaultQuerySettings) + { + _defaultQuerySettings = defaultQuerySettings; + } + + /// + /// Validates a . + /// + /// The $count query. + /// The validation settings. + public virtual void Validate(CountQueryOption countQueryOption, ODataValidationSettings validationSettings) + { + if (countQueryOption == null) + { + throw Error.ArgumentNull("countQueryOption"); + } + + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + ODataPath path = countQueryOption.Context.Path; + + if (path != null && path.Segments.Count > 0) + { + IEdmProperty property = countQueryOption.Context.TargetProperty; + IEdmStructuredType structuredType = countQueryOption.Context.TargetStructuredType; + string name = countQueryOption.Context.TargetName; + if (EdmLibHelpers.IsNotCountable(property, structuredType, + countQueryOption.Context.Model, + _defaultQuerySettings.EnableCount)) + { + if (property == null) + { + throw new InvalidOperationException(Error.Format( + SRResources.NotCountableEntitySetUsedForCount, + name)); + } + else + { + throw new InvalidOperationException(Error.Format( + SRResources.NotCountablePropertyUsedForCount, + name)); + } + } + } + } + + internal static CountQueryValidator GetCountQueryValidator(ODataQueryContext context) + { + if (context == null) + { + return new CountQueryValidator(new DefaultQuerySettings()); + } + + return context.RequestContainer == null + ? new CountQueryValidator(context.DefaultQuerySettings) + : context.RequestContainer.GetRequiredService(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/FilterQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/FilterQueryValidator.cs new file mode 100644 index 0000000..d717256 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/FilterQueryValidator.cs @@ -0,0 +1,1039 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate a based on the . + /// + /// + /// Please note this class is not thread safe. + /// + public class FilterQueryValidator + { + private int _currentAnyAllExpressionDepth; + private int _currentNodeCount; + private readonly DefaultQuerySettings _defaultQuerySettings; + private IEdmProperty _property; + private IEdmStructuredType _structuredType; + + private IEdmModel _model; + + /// + /// Initializes a new instance of the class based on + /// the . + /// + /// The . + public FilterQueryValidator(DefaultQuerySettings defaultQuerySettings) + { + _defaultQuerySettings = defaultQuerySettings; + } + + /// + /// Validates a . + /// + /// The $filter query. + /// The validation settings. + /// + /// Please note this method is not thread safe. + /// + public virtual void Validate(FilterQueryOption filterQueryOption, ODataValidationSettings settings) + { + if (filterQueryOption == null) + { + throw Error.ArgumentNull("filterQueryOption"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + if (filterQueryOption.Context.Path != null) + { + _property = filterQueryOption.Context.TargetProperty; + _structuredType = filterQueryOption.Context.TargetStructuredType; + } + + Validate(filterQueryOption.FilterClause, settings, filterQueryOption.Context.Model); + } + + /// + /// Validates a . + /// + /// The . + /// The validation settings. + /// The EdmModel. + /// + /// Please note this method is not thread safe. + /// + public virtual void Validate(FilterClause filterClause, ODataValidationSettings settings, IEdmModel model) + { + _currentAnyAllExpressionDepth = 0; + _currentNodeCount = 0; + _model = model; + + ValidateQueryNode(filterClause.Expression, settings); + } + + internal virtual void Validate(IEdmProperty property, IEdmStructuredType structuredType, + FilterClause filterClause, ODataValidationSettings settings, IEdmModel model) + { + _property = property; + _structuredType = structuredType; + Validate(filterClause, settings, model); + } + + /// + /// Override this method to restrict the 'all' query inside the filter query. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateAllNode(AllNode allNode, ODataValidationSettings settings) + { + if (allNode == null) + { + throw Error.ArgumentNull("allNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + ValidateFunction("all", settings); + EnterLambda(settings); + + try + { + ValidateQueryNode(allNode.Source, settings); + + ValidateQueryNode(allNode.Body, settings); + } + finally + { + ExitLambda(); + } + } + + /// + /// Override this method to restrict the 'any' query inside the filter query. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateAnyNode(AnyNode anyNode, ODataValidationSettings settings) + { + if (anyNode == null) + { + throw Error.ArgumentNull("anyNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + ValidateFunction("any", settings); + EnterLambda(settings); + + try + { + ValidateQueryNode(anyNode.Source, settings); + + if (anyNode.Body != null && anyNode.Body.Kind != QueryNodeKind.Constant) + { + ValidateQueryNode(anyNode.Body, settings); + } + } + finally + { + ExitLambda(); + } + } + + /// + /// override this method to restrict the binary operators inside the filter query. That includes all the logical operators except 'not' and all math operators. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateBinaryOperatorNode(BinaryOperatorNode binaryOperatorNode, ODataValidationSettings settings) + { + if (binaryOperatorNode == null) + { + throw Error.ArgumentNull("binaryOperatorNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // base case goes + switch (binaryOperatorNode.OperatorKind) + { + case BinaryOperatorKind.Equal: + case BinaryOperatorKind.NotEqual: + case BinaryOperatorKind.And: + case BinaryOperatorKind.GreaterThan: + case BinaryOperatorKind.GreaterThanOrEqual: + case BinaryOperatorKind.LessThan: + case BinaryOperatorKind.LessThanOrEqual: + case BinaryOperatorKind.Or: + case BinaryOperatorKind.Has: + // binary logical operators + ValidateLogicalOperator(binaryOperatorNode, settings); + break; + default: + // math operators + ValidateArithmeticOperator(binaryOperatorNode, settings); + break; + } + } + + /// + /// Override this method to validate the LogicalOperators such as 'eq', 'ne', 'gt', 'ge', 'lt', 'le', 'and', 'or'. + /// + /// Please note that 'not' is not included here. Please override ValidateUnaryOperatorNode to customize 'not'. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateLogicalOperator(BinaryOperatorNode binaryNode, ODataValidationSettings settings) + { + if (binaryNode == null) + { + throw Error.ArgumentNull("binaryNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + AllowedLogicalOperators logicalOperator = ToLogicalOperator(binaryNode); + + if ((settings.AllowedLogicalOperators & logicalOperator) != logicalOperator) + { + // this means the given logical operator is not allowed + throw new ODataException(Error.Format(SRResources.NotAllowedLogicalOperator, logicalOperator, "AllowedLogicalOperators")); + } + + // recursion case goes here + ValidateQueryNode(binaryNode.Left, settings); + ValidateQueryNode(binaryNode.Right, settings); + } + + /// + /// Override this method for the Arithmetic operators, including add, sub, mul, div, mod. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateArithmeticOperator(BinaryOperatorNode binaryNode, ODataValidationSettings settings) + { + if (binaryNode == null) + { + throw Error.ArgumentNull("binaryNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + AllowedArithmeticOperators arithmeticOperator = ToArithmeticOperator(binaryNode); + + if ((settings.AllowedArithmeticOperators & arithmeticOperator) != arithmeticOperator) + { + // this means the given logical operator is not allowed + throw new ODataException(Error.Format(SRResources.NotAllowedArithmeticOperator, arithmeticOperator, "AllowedArithmeticOperators")); + } + + // recursion case goes here + ValidateQueryNode(binaryNode.Left, settings); + ValidateQueryNode(binaryNode.Right, settings); + } + + /// + /// Override this method to restrict the 'constant' inside the filter query. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateConstantNode(ConstantNode constantNode, ODataValidationSettings settings) + { + if (constantNode == null) + { + throw Error.ArgumentNull("constantNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // No default validation logic here. + } + + /// + /// Override this method to restrict the 'cast' inside the filter query. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateConvertNode(ConvertNode convertNode, ODataValidationSettings settings) + { + if (convertNode == null) + { + throw Error.ArgumentNull("convertNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // Validate child nodes but not the ConvertNode itself. + ValidateQueryNode(convertNode.Source, settings); + } + + /// + /// Override this method for the navigation property node. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + /// + public virtual void ValidateNavigationPropertyNode(QueryNode sourceNode, IEdmNavigationProperty navigationProperty, ODataValidationSettings settings) + { + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // Check whether the property is not filterable + if (EdmLibHelpers.IsNotFilterable(navigationProperty, _property, _structuredType, _model, + _defaultQuerySettings.EnableFilter)) + { + throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, + navigationProperty.Name)); + } + + // recursion + if (sourceNode != null) + { + ValidateQueryNode(sourceNode, settings); + } + } + + /// + /// Override this method to validate the parameter used in the filter query. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateRangeVariable(RangeVariable rangeVariable, ODataValidationSettings settings) + { + if (rangeVariable == null) + { + throw Error.ArgumentNull("rangeVariable"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // No default validation logic here. + } + + /// + /// Override this method to validate property accessor. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateSingleValuePropertyAccessNode(SingleValuePropertyAccessNode propertyAccessNode, ODataValidationSettings settings) + { + if (propertyAccessNode == null) + { + throw Error.ArgumentNull("propertyAccessNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // Check whether the property is filterable. + IEdmProperty property = propertyAccessNode.Property; + bool notFilterable = false; + if (propertyAccessNode.Source != null) + { + if (propertyAccessNode.Source.Kind == QueryNodeKind.SingleNavigationNode) + { + SingleNavigationNode singleNavigationNode = propertyAccessNode.Source as SingleNavigationNode; + notFilterable = EdmLibHelpers.IsNotFilterable(property, singleNavigationNode.NavigationProperty, + singleNavigationNode.NavigationProperty.ToEntityType(), _model, + _defaultQuerySettings.EnableFilter); + } + else if (propertyAccessNode.Source.Kind == QueryNodeKind.SingleComplexNode) + { + SingleComplexNode singleComplexNode = propertyAccessNode.Source as SingleComplexNode; + notFilterable = EdmLibHelpers.IsNotFilterable(property, singleComplexNode.Property, + property.DeclaringType, _model, _defaultQuerySettings.EnableFilter); + } + else + { + notFilterable = EdmLibHelpers.IsNotFilterable(property, _property, _structuredType, _model, + _defaultQuerySettings.EnableFilter); + } + } + + if (notFilterable) + { + throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name)); + } + + ValidateQueryNode(propertyAccessNode.Source, settings); + } + + /// + /// Override this method to validate single complex property accessor. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateSingleComplexNode(SingleComplexNode singleComplexNode, ODataValidationSettings settings) + { + if (singleComplexNode == null) + { + throw Error.ArgumentNull("singleComplexNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // Check whether the property is filterable. + IEdmProperty property = singleComplexNode.Property; + if (EdmLibHelpers.IsNotFilterable(property, _property, _structuredType, _model, + _defaultQuerySettings.EnableFilter)) + { + throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name)); + } + + ValidateQueryNode(singleComplexNode.Source, settings); + } + + /// + /// Override this method to validate collection property accessor. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateCollectionPropertyAccessNode(CollectionPropertyAccessNode propertyAccessNode, ODataValidationSettings settings) + { + if (propertyAccessNode == null) + { + throw Error.ArgumentNull("propertyAccessNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // Check whether the property is filterable. + IEdmProperty property = propertyAccessNode.Property; + if (EdmLibHelpers.IsNotFilterable(property, _property, _structuredType, _model, + _defaultQuerySettings.EnableFilter)) + { + throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name)); + } + + ValidateQueryNode(propertyAccessNode.Source, settings); + } + + /// + /// Override this method to validate collection complex property accessor. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateCollectionComplexNode(CollectionComplexNode collectionComplexNode, ODataValidationSettings settings) + { + if (collectionComplexNode == null) + { + throw Error.ArgumentNull("collectionComplexNode"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + // Check whether the property is filterable. + IEdmProperty property = collectionComplexNode.Property; + if (EdmLibHelpers.IsNotFilterable(property, _property, _structuredType, _model, + _defaultQuerySettings.EnableFilter)) + { + throw new ODataException(Error.Format(SRResources.NotFilterablePropertyUsedInFilter, property.Name)); + } + + ValidateQueryNode(collectionComplexNode.Source, settings); + } + + /// + /// Override this method to validate Function calls, such as 'length', 'year', etc. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateSingleValueFunctionCallNode(SingleValueFunctionCallNode node, ODataValidationSettings settings) + { + if (node == null) + { + throw Error.ArgumentNull("node"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + ValidateFunction(node.Name, settings); + + foreach (QueryNode argumentNode in node.Parameters) + { + ValidateQueryNode(argumentNode, settings); + } + } + + /// + /// Override this method to validate single resource function calls, such as 'cast'. + /// + /// The node to validate. + /// The settings to use while validating. + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit + /// testing scenarios and is not intended to be called from user code. Call the Validate method to validate a + /// instance. + /// + public virtual void ValidateSingleResourceFunctionCallNode(SingleResourceFunctionCallNode node, ODataValidationSettings settings) + { + if (node == null) + { + throw Error.ArgumentNull("node"); + } + + if (settings == null) + { + throw Error.ArgumentNull("settings"); + } + + ValidateFunction(node.Name, settings); + foreach (QueryNode argumentNode in node.Parameters) + { + ValidateQueryNode(argumentNode, settings); + } + } + + /// + /// Override this method to validate the Not operator. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateUnaryOperatorNode(UnaryOperatorNode unaryOperatorNode, ODataValidationSettings settings) + { + ValidateQueryNode(unaryOperatorNode.Operand, settings); + + switch (unaryOperatorNode.OperatorKind) + { + case UnaryOperatorKind.Negate: + case UnaryOperatorKind.Not: + if ((settings.AllowedLogicalOperators & AllowedLogicalOperators.Not) != AllowedLogicalOperators.Not) + { + throw new ODataException(Error.Format(SRResources.NotAllowedLogicalOperator, unaryOperatorNode.OperatorKind, "AllowedLogicalOperators")); + } + break; + + default: + throw Error.NotSupported(SRResources.UnaryNodeValidationNotSupported, unaryOperatorNode.OperatorKind, typeof(FilterQueryValidator).Name); + } + } + + /// + /// Override this method if you want to visit each query node. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateQueryNode(QueryNode node, ODataValidationSettings settings) + { + // Recursion guard to avoid stack overflows + RuntimeHelpers.EnsureSufficientExecutionStack(); + + SingleValueNode singleNode = node as SingleValueNode; + CollectionNode collectionNode = node as CollectionNode; + + IncrementNodeCount(settings); + + if (singleNode != null) + { + ValidateSingleValueNode(singleNode, settings); + } + else if (collectionNode != null) + { + ValidateCollectionNode(collectionNode, settings); + } + } + + /// + /// Override this method if you want to validate casts on resource collections. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateCollectionResourceCastNode(CollectionResourceCastNode collectionResourceCastNode, ODataValidationSettings settings) + { + if (collectionResourceCastNode == null) + { + throw Error.ArgumentNull("collectionResourceCastNode"); + } + + ValidateQueryNode(collectionResourceCastNode.Source, settings); + } + + /// + /// Override this method if you want to validate casts on single resource. + /// + /// + /// This method is intended to be called from method overrides in subclasses. This method also supports unit-testing scenarios and is not intended to be called from user code. + /// Call the Validate method to validate a instance. + /// + /// + /// + public virtual void ValidateSingleResourceCastNode(SingleResourceCastNode singleResourceCastNode, ODataValidationSettings settings) + { + if (singleResourceCastNode == null) + { + throw Error.ArgumentNull("singleResourceCastNode"); + } + + ValidateQueryNode(singleResourceCastNode.Source, settings); + } + + internal static FilterQueryValidator GetFilterQueryValidator(ODataQueryContext context) + { + if (context == null) + { + return new FilterQueryValidator(new DefaultQuerySettings()); + } + + return context.RequestContainer == null + ? new FilterQueryValidator(context.DefaultQuerySettings) + : context.RequestContainer.GetRequiredService(); + } + + private void EnterLambda(ODataValidationSettings validationSettings) + { + if (_currentAnyAllExpressionDepth >= validationSettings.MaxAnyAllExpressionDepth) + { + throw new ODataException(Error.Format(SRResources.MaxAnyAllExpressionLimitExceeded, validationSettings.MaxAnyAllExpressionDepth, "MaxAnyAllExpressionDepth")); + } + + _currentAnyAllExpressionDepth++; + } + + private void ExitLambda() + { + Contract.Assert(_currentAnyAllExpressionDepth > 0); + _currentAnyAllExpressionDepth--; + } + + private void IncrementNodeCount(ODataValidationSettings validationSettings) + { + if (_currentNodeCount >= validationSettings.MaxNodeCount) + { + throw new ODataException(Error.Format(SRResources.MaxNodeLimitExceeded, validationSettings.MaxNodeCount, "MaxNodeCount")); + } + + _currentNodeCount++; + } + + private void ValidateCollectionNode(CollectionNode node, ODataValidationSettings settings) + { + switch (node.Kind) + { + case QueryNodeKind.CollectionPropertyAccess: + CollectionPropertyAccessNode propertyAccessNode = node as CollectionPropertyAccessNode; + ValidateCollectionPropertyAccessNode(propertyAccessNode, settings); + break; + + case QueryNodeKind.CollectionComplexNode: + CollectionComplexNode collectionComplexNode = node as CollectionComplexNode; + ValidateCollectionComplexNode(collectionComplexNode, settings); + break; + + case QueryNodeKind.CollectionNavigationNode: + CollectionNavigationNode navigationNode = node as CollectionNavigationNode; + ValidateNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty, settings); + break; + + case QueryNodeKind.CollectionResourceCast: + ValidateCollectionResourceCastNode(node as CollectionResourceCastNode, settings); + break; + + case QueryNodeKind.CollectionFunctionCall: + case QueryNodeKind.CollectionResourceFunctionCall: + case QueryNodeKind.CollectionOpenPropertyAccess: + // Unused or have unknown uses. + default: + throw Error.NotSupported(SRResources.QueryNodeValidationNotSupported, node.Kind, typeof(FilterQueryValidator).Name); + } + } + + /// + /// The recursive method that validate most of the query node type is of SingleValueNode type. + /// + /// + /// + private void ValidateSingleValueNode(SingleValueNode node, ODataValidationSettings settings) + { + switch (node.Kind) + { + case QueryNodeKind.BinaryOperator: + ValidateBinaryOperatorNode(node as BinaryOperatorNode, settings); + break; + + case QueryNodeKind.Constant: + ValidateConstantNode(node as ConstantNode, settings); + break; + + case QueryNodeKind.Convert: + ValidateConvertNode(node as ConvertNode, settings); + break; + + case QueryNodeKind.ResourceRangeVariableReference: + ValidateRangeVariable((node as ResourceRangeVariableReferenceNode).RangeVariable, settings); + break; + + case QueryNodeKind.NonResourceRangeVariableReference: + ValidateRangeVariable((node as NonResourceRangeVariableReferenceNode).RangeVariable, settings); + break; + + case QueryNodeKind.SingleValuePropertyAccess: + ValidateSingleValuePropertyAccessNode(node as SingleValuePropertyAccessNode, settings); + break; + + case QueryNodeKind.SingleComplexNode: + ValidateSingleComplexNode(node as SingleComplexNode, settings); + break; + + case QueryNodeKind.UnaryOperator: + ValidateUnaryOperatorNode(node as UnaryOperatorNode, settings); + break; + + case QueryNodeKind.SingleValueFunctionCall: + ValidateSingleValueFunctionCallNode(node as SingleValueFunctionCallNode, settings); + break; + + case QueryNodeKind.SingleResourceFunctionCall: + ValidateSingleResourceFunctionCallNode((SingleResourceFunctionCallNode)node, settings); + break; + + case QueryNodeKind.SingleNavigationNode: + SingleNavigationNode navigationNode = node as SingleNavigationNode; + ValidateNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty, settings); + break; + + case QueryNodeKind.SingleResourceCast: + ValidateSingleResourceCastNode(node as SingleResourceCastNode, settings); + break; + + case QueryNodeKind.Any: + ValidateAnyNode(node as AnyNode, settings); + break; + + case QueryNodeKind.All: + ValidateAllNode(node as AllNode, settings); + break; + + case QueryNodeKind.SingleValueOpenPropertyAccess: + //no validation on open values? + break; + + case QueryNodeKind.In: + // No setting validations + break; + + case QueryNodeKind.NamedFunctionParameter: + case QueryNodeKind.ParameterAlias: + case QueryNodeKind.EntitySet: + case QueryNodeKind.KeyLookup: + case QueryNodeKind.SearchTerm: + // Unused or have unknown uses. + default: + throw Error.NotSupported(SRResources.QueryNodeValidationNotSupported, node.Kind, typeof(FilterQueryValidator).Name); + } + } + + private static void ValidateFunction(string functionName, ODataValidationSettings settings) + { + AllowedFunctions convertedFunction = ToODataFunction(functionName); + if ((settings.AllowedFunctions & convertedFunction) != convertedFunction) + { + // this means the given function is not allowed + throw new ODataException(Error.Format(SRResources.NotAllowedFunction, functionName, "AllowedFunctions")); + } + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These are simple conversion function and cannot be split up.")] + private static AllowedFunctions ToODataFunction(string functionName) + { + AllowedFunctions result = AllowedFunctions.None; + + switch (functionName) + { + case "any": + result = AllowedFunctions.Any; + break; + case "all": + result = AllowedFunctions.All; + break; + case "cast": + result = AllowedFunctions.Cast; + break; + case ClrCanonicalFunctions.CeilingFunctionName: + result = AllowedFunctions.Ceiling; + break; + case ClrCanonicalFunctions.ConcatFunctionName: + result = AllowedFunctions.Concat; + break; + case ClrCanonicalFunctions.ContainsFunctionName: + result = AllowedFunctions.Contains; + break; + case ClrCanonicalFunctions.DayFunctionName: + result = AllowedFunctions.Day; + break; + case ClrCanonicalFunctions.EndswithFunctionName: + result = AllowedFunctions.EndsWith; + break; + case ClrCanonicalFunctions.FloorFunctionName: + result = AllowedFunctions.Floor; + break; + case ClrCanonicalFunctions.HourFunctionName: + result = AllowedFunctions.Hour; + break; + case ClrCanonicalFunctions.IndexofFunctionName: + result = AllowedFunctions.IndexOf; + break; + case "isof": + result = AllowedFunctions.IsOf; + break; + case ClrCanonicalFunctions.LengthFunctionName: + result = AllowedFunctions.Length; + break; + case ClrCanonicalFunctions.MinuteFunctionName: + result = AllowedFunctions.Minute; + break; + case ClrCanonicalFunctions.MonthFunctionName: + result = AllowedFunctions.Month; + break; + case ClrCanonicalFunctions.RoundFunctionName: + result = AllowedFunctions.Round; + break; + case ClrCanonicalFunctions.SecondFunctionName: + result = AllowedFunctions.Second; + break; + case ClrCanonicalFunctions.StartswithFunctionName: + result = AllowedFunctions.StartsWith; + break; + case ClrCanonicalFunctions.SubstringFunctionName: + result = AllowedFunctions.Substring; + break; + case ClrCanonicalFunctions.TolowerFunctionName: + result = AllowedFunctions.ToLower; + break; + case ClrCanonicalFunctions.ToupperFunctionName: + result = AllowedFunctions.ToUpper; + break; + case ClrCanonicalFunctions.TrimFunctionName: + result = AllowedFunctions.Trim; + break; + case ClrCanonicalFunctions.YearFunctionName: + result = AllowedFunctions.Year; + break; + case ClrCanonicalFunctions.DateFunctionName: + result = AllowedFunctions.Date; + break; + case ClrCanonicalFunctions.TimeFunctionName: + result = AllowedFunctions.Time; + break; + case ClrCanonicalFunctions.FractionalSecondsFunctionName: + result = AllowedFunctions.FractionalSeconds; + break; + default: + // should never be here + Contract.Assert(true, "ToODataFunction should never be here."); + break; + } + + return result; + } + + private static AllowedLogicalOperators ToLogicalOperator(BinaryOperatorNode binaryNode) + { + AllowedLogicalOperators result = AllowedLogicalOperators.None; + + switch (binaryNode.OperatorKind) + { + case BinaryOperatorKind.Equal: + result = AllowedLogicalOperators.Equal; + break; + + case BinaryOperatorKind.NotEqual: + result = AllowedLogicalOperators.NotEqual; + break; + + case BinaryOperatorKind.And: + result = AllowedLogicalOperators.And; + break; + + case BinaryOperatorKind.GreaterThan: + result = AllowedLogicalOperators.GreaterThan; + break; + + case BinaryOperatorKind.GreaterThanOrEqual: + result = AllowedLogicalOperators.GreaterThanOrEqual; + break; + + case BinaryOperatorKind.LessThan: + result = AllowedLogicalOperators.LessThan; + break; + + case BinaryOperatorKind.LessThanOrEqual: + result = AllowedLogicalOperators.LessThanOrEqual; + break; + + case BinaryOperatorKind.Or: + result = AllowedLogicalOperators.Or; + break; + + case BinaryOperatorKind.Has: + result = AllowedLogicalOperators.Has; + break; + + default: + // should never be here + Contract.Assert(false, "ToLogicalOperator should never be here."); + break; + } + + return result; + } + + private static AllowedArithmeticOperators ToArithmeticOperator(BinaryOperatorNode binaryNode) + { + AllowedArithmeticOperators result = AllowedArithmeticOperators.None; + + switch (binaryNode.OperatorKind) + { + case BinaryOperatorKind.Add: + result = AllowedArithmeticOperators.Add; + break; + + case BinaryOperatorKind.Divide: + result = AllowedArithmeticOperators.Divide; + break; + + case BinaryOperatorKind.Modulo: + result = AllowedArithmeticOperators.Modulo; + break; + + case BinaryOperatorKind.Multiply: + result = AllowedArithmeticOperators.Multiply; + break; + + case BinaryOperatorKind.Subtract: + result = AllowedArithmeticOperators.Subtract; + break; + + default: + // should never be here + Contract.Assert(false, "ToArithmeticOperator should never be here."); + break; + } + + return result; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/ODataQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/ODataQueryValidator.cs new file mode 100644 index 0000000..ce907a7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/ODataQueryValidator.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate OData queries based on the . + /// + public class ODataQueryValidator + { + /// + /// Validates the OData query. + /// + /// The OData query to validate. + /// The validation settings. + public virtual void Validate(ODataQueryOptions options, ODataValidationSettings validationSettings) + { + if (options == null) + { + throw Error.ArgumentNull("options"); + } + + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + // Validate each query options + if (options.Apply != null) + { + if (options.Apply.ApplyClause != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Apply, validationSettings.AllowedQueryOptions); + } + } + + if (options.Skip != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Skip, validationSettings.AllowedQueryOptions); + options.Skip.Validate(validationSettings); + } + + if (options.Top != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Top, validationSettings.AllowedQueryOptions); + options.Top.Validate(validationSettings); + } + + if (options.OrderBy != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.OrderBy, validationSettings.AllowedQueryOptions); + options.OrderBy.Validate(validationSettings); + } + + if (options.Filter != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Filter, validationSettings.AllowedQueryOptions); + options.Filter.Validate(validationSettings); + } + + if (options.Count != null || options.InternalRequest.IsCountRequest()) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Count, validationSettings.AllowedQueryOptions); + + if (options.Count != null) + { + options.Count.Validate(validationSettings); + } + } + + if (options.SkipToken != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.SkipToken, validationSettings.AllowedQueryOptions); + options.SkipToken.Validate(validationSettings); + } + + if (options.RawValues.Expand != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Expand, validationSettings.AllowedQueryOptions); + } + + if (options.RawValues.Select != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Select, validationSettings.AllowedQueryOptions); + } + + if (options.SelectExpand != null) + { + options.SelectExpand.Validate(validationSettings); + } + + if (options.RawValues.Format != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.Format, validationSettings.AllowedQueryOptions); + } + + if (options.RawValues.SkipToken != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.SkipToken, validationSettings.AllowedQueryOptions); + } + + if (options.RawValues.DeltaToken != null) + { + ValidateQueryOptionAllowed(AllowedQueryOptions.DeltaToken, validationSettings.AllowedQueryOptions); + } + } + + internal static ODataQueryValidator GetODataQueryValidator(ODataQueryContext context) + { + if (context == null || context.RequestContainer == null) + { + return new ODataQueryValidator(); + } + + return context.RequestContainer.GetRequiredService(); + } + + private static void ValidateQueryOptionAllowed(AllowedQueryOptions queryOption, AllowedQueryOptions allowed) + { + if ((queryOption & allowed) == AllowedQueryOptions.None) + { + throw new ODataException(Error.Format(SRResources.NotAllowedQueryOption, queryOption, "AllowedQueryOptions")); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByModelLimitationsValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByModelLimitationsValidator.cs new file mode 100644 index 0000000..ac05bb5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByModelLimitationsValidator.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + internal class OrderByModelLimitationsValidator : QueryNodeVisitor + { + private readonly IEdmModel _model; + private readonly bool _enableOrderBy; + private IEdmProperty _property; + private IEdmStructuredType _structuredType; + + public OrderByModelLimitationsValidator(ODataQueryContext context, bool enableOrderBy) + { + _model = context.Model; + _enableOrderBy = enableOrderBy; + + if (context.Path != null) + { + _property = context.TargetProperty; + _structuredType = context.TargetStructuredType; + } + } + + public bool TryValidate(IEdmProperty property, IEdmStructuredType structuredType, OrderByClause orderByClause, + bool explicitPropertiesDefined) + { + _property = property; + _structuredType = structuredType; + return TryValidate(orderByClause, explicitPropertiesDefined); + } + + // Visits the expression to find the first node if any, that is not sortable and throws + // an exception only if no explicit properties have been defined in AllowedOrderByProperties + // on the ODataValidationSettings instance associated with this OrderByValidator. + public bool TryValidate(OrderByClause orderByClause, bool explicitPropertiesDefined) + { + SingleValueNode invalidNode = orderByClause.Expression.Accept(this); + if (invalidNode != null && !explicitPropertiesDefined) + { + throw new ODataException(Error.Format(SRResources.NotSortablePropertyUsedInOrderBy, + GetPropertyName(invalidNode))); + } + return invalidNode == null; + } + + public override SingleValueNode Visit(SingleValuePropertyAccessNode nodeIn) + { + if (nodeIn.Source != null) + { + if (nodeIn.Source.Kind == QueryNodeKind.SingleNavigationNode) + { + SingleNavigationNode singleNavigationNode = nodeIn.Source as SingleNavigationNode; + if (EdmLibHelpers.IsNotSortable(nodeIn.Property, singleNavigationNode.NavigationProperty, + singleNavigationNode.NavigationProperty.ToEntityType(), _model, _enableOrderBy)) + { + return nodeIn; + } + } + else if (nodeIn.Source.Kind == QueryNodeKind.SingleComplexNode) + { + SingleComplexNode singleComplexNode = nodeIn.Source as SingleComplexNode; + if (EdmLibHelpers.IsNotSortable(nodeIn.Property, singleComplexNode.Property, + nodeIn.Property.DeclaringType, _model, _enableOrderBy)) + { + return nodeIn; + } + } + else if (EdmLibHelpers.IsNotSortable(nodeIn.Property, _property, _structuredType, _model, _enableOrderBy)) + { + return nodeIn; + } + } + + if (nodeIn.Source != null) + { + return nodeIn.Source.Accept(this); + } + + return null; + } + + public override SingleValueNode Visit(SingleComplexNode nodeIn) + { + if (EdmLibHelpers.IsNotSortable(nodeIn.Property, _property, _structuredType, _model, _enableOrderBy)) + { + return nodeIn; + } + + if (nodeIn.Source != null) + { + return nodeIn.Source.Accept(this); + } + + return null; + } + + public override SingleValueNode Visit(SingleNavigationNode nodeIn) + { + if (EdmLibHelpers.IsNotSortable(nodeIn.NavigationProperty, _property, _structuredType, _model, + _enableOrderBy)) + { + return nodeIn; + } + + if (nodeIn.Source != null) + { + return nodeIn.Source.Accept(this); + } + + return null; + } + + public override SingleValueNode Visit(ResourceRangeVariableReferenceNode nodeIn) + { + return null; + } + + public override SingleValueNode Visit(NonResourceRangeVariableReferenceNode nodeIn) + { + return null; + } + + private static string GetPropertyName(SingleValueNode node) + { + if (node.Kind == QueryNodeKind.SingleNavigationNode) + { + return ((SingleNavigationNode)node).NavigationProperty.Name; + } + else if (node.Kind == QueryNodeKind.SingleValuePropertyAccess) + { + return ((SingleValuePropertyAccessNode)node).Property.Name; + } + else if (node.Kind == QueryNodeKind.SingleComplexNode) + { + return ((SingleComplexNode)node).Property.Name; + } + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByQueryValidator.cs new file mode 100644 index 0000000..d26aca9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/OrderByQueryValidator.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate an based on the . + /// + public class OrderByQueryValidator + { + private readonly DefaultQuerySettings _defaultQuerySettings; + + /// + /// Initializes a new instance of the class based on + /// the . + /// + /// The . + public OrderByQueryValidator(DefaultQuerySettings defaultQuerySettings) + { + _defaultQuerySettings = defaultQuerySettings; + } + + /// + /// Validates an . + /// + /// The $orderby query. + /// The validation settings. + public virtual void Validate(OrderByQueryOption orderByOption, ODataValidationSettings validationSettings) + { + if (orderByOption == null) + { + throw Error.ArgumentNull("orderByOption"); + } + + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + int nodeCount = 0; + for (OrderByClause clause = orderByOption.OrderByClause; clause != null; clause = clause.ThenBy) + { + nodeCount++; + if (nodeCount > validationSettings.MaxOrderByNodeCount) + { + throw new ODataException(Error.Format(SRResources.OrderByNodeCountExceeded, + validationSettings.MaxOrderByNodeCount)); + } + } + + OrderByModelLimitationsValidator validator = new OrderByModelLimitationsValidator(orderByOption.Context, _defaultQuerySettings.EnableOrderBy); + bool explicitAllowedProperties = validationSettings.AllowedOrderByProperties.Count > 0; + + foreach (OrderByNode node in orderByOption.OrderByNodes) + { + string propertyName = null; + OrderByPropertyNode propertyNode = node as OrderByPropertyNode; + if (propertyNode != null) + { + propertyName = propertyNode.Property.Name; + bool isValidPath = !validator.TryValidate(propertyNode.OrderByClause, explicitAllowedProperties); + if (propertyName != null && isValidPath && explicitAllowedProperties) + { + // Explicit allowed properties were specified, but this one isn't within the list of allowed + // properties. + if (!IsAllowed(validationSettings, propertyName)) + { + throw new ODataException(Error.Format(SRResources.NotAllowedOrderByProperty, propertyName, + "AllowedOrderByProperties")); + } + } + else if (propertyName != null) + { + // The property wasn't limited but it wasn't contained in the set of explicitly allowed + // properties. + if (!IsAllowed(validationSettings, propertyName)) + { + throw new ODataException(Error.Format(SRResources.NotAllowedOrderByProperty, propertyName, + "AllowedOrderByProperties")); + } + } + } + else + { + propertyName = "$it"; + if (!IsAllowed(validationSettings, propertyName)) + { + throw new ODataException(Error.Format(SRResources.NotAllowedOrderByProperty, propertyName, + "AllowedOrderByProperties")); + } + } + } + } + + internal static OrderByQueryValidator GetOrderByQueryValidator(ODataQueryContext context) + { + if (context == null) + { + return new OrderByQueryValidator(new DefaultQuerySettings()); + } + + return context.RequestContainer == null + ? new OrderByQueryValidator(context.DefaultQuerySettings) + : context.RequestContainer.GetRequiredService(); + } + + private static bool IsAllowed(ODataValidationSettings validationSettings, string propertyName) + { + return validationSettings.AllowedOrderByProperties.Count == 0 || + validationSettings.AllowedOrderByProperties.Contains(propertyName); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SelectExpandQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SelectExpandQueryValidator.cs new file mode 100644 index 0000000..278998e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SelectExpandQueryValidator.cs @@ -0,0 +1,377 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate a based on the . + /// + public class SelectExpandQueryValidator + { + private readonly DefaultQuerySettings _defaultQuerySettings; + private readonly FilterQueryValidator _filterQueryValidator; + private OrderByModelLimitationsValidator _orderByQueryValidator; + private SelectExpandQueryOption _selectExpandQueryOption; + + /// + /// Initializes a new instance of the class based on + /// the . + /// + /// The . + public SelectExpandQueryValidator(DefaultQuerySettings defaultQuerySettings) + { + _defaultQuerySettings = defaultQuerySettings; + _filterQueryValidator = new FilterQueryValidator(_defaultQuerySettings); + } + + /// + /// Validates a . + /// + /// The $select and $expand query. + /// The validation settings. + public virtual void Validate(SelectExpandQueryOption selectExpandQueryOption, ODataValidationSettings validationSettings) + { + if (selectExpandQueryOption == null) + { + throw Error.ArgumentNull("selectExpandQueryOption"); + } + + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + _orderByQueryValidator = new OrderByModelLimitationsValidator(selectExpandQueryOption.Context, + _defaultQuerySettings.EnableOrderBy); + _selectExpandQueryOption = selectExpandQueryOption; + ValidateRestrictions(null, 0, selectExpandQueryOption.SelectExpandClause, null, validationSettings); + + if (validationSettings.MaxExpansionDepth > 0) + { + if (selectExpandQueryOption.LevelsMaxLiteralExpansionDepth < 0) + { + selectExpandQueryOption.LevelsMaxLiteralExpansionDepth = validationSettings.MaxExpansionDepth; + } + else if (selectExpandQueryOption.LevelsMaxLiteralExpansionDepth > validationSettings.MaxExpansionDepth) + { + throw new ODataException(Error.Format( + SRResources.InvalidExpansionDepthValue, + "LevelsMaxLiteralExpansionDepth", + "MaxExpansionDepth")); + } + + ValidateDepth(selectExpandQueryOption.SelectExpandClause, validationSettings.MaxExpansionDepth); + } + } + + internal static SelectExpandQueryValidator GetSelectExpandQueryValidator(ODataQueryContext context) + { + if (context == null) + { + return new SelectExpandQueryValidator(new DefaultQuerySettings()); + } + + return context.RequestContainer == null + ? new SelectExpandQueryValidator(context.DefaultQuerySettings) + : context.RequestContainer.GetRequiredService(); + } + + private static void ValidateDepth(SelectExpandClause selectExpand, int maxDepth) + { + // do a DFS to see if there is any node that is too deep. + Stack> nodesToVisit = new Stack>(); + nodesToVisit.Push(Tuple.Create(0, selectExpand)); + while (nodesToVisit.Count > 0) + { + Tuple tuple = nodesToVisit.Pop(); + int currentDepth = tuple.Item1; + SelectExpandClause currentNode = tuple.Item2; + + ExpandedNavigationSelectItem[] expandItems = currentNode.SelectedItems.OfType().ToArray(); + + if (expandItems.Length > 0 && + ((currentDepth == maxDepth && + expandItems.Any(expandItem => + expandItem.LevelsOption == null || + expandItem.LevelsOption.IsMaxLevel || + expandItem.LevelsOption.Level != 0)) || + expandItems.Any(expandItem => + expandItem.LevelsOption != null && + !expandItem.LevelsOption.IsMaxLevel && + (expandItem.LevelsOption.Level > Int32.MaxValue || + expandItem.LevelsOption.Level + currentDepth > maxDepth)))) + { + throw new ODataException( + Error.Format(SRResources.MaxExpandDepthExceeded, maxDepth, "MaxExpansionDepth")); + } + + foreach (ExpandedNavigationSelectItem expandItem in expandItems) + { + int depth = currentDepth + 1; + + if (expandItem.LevelsOption != null && !expandItem.LevelsOption.IsMaxLevel) + { + // Add the value of $levels for next depth. + depth = depth + (int)expandItem.LevelsOption.Level - 1; + } + + nodesToVisit.Push(Tuple.Create(depth, expandItem.SelectAndExpand)); + } + } + } + + private void ValidateTopInExpand(IEdmProperty property, IEdmStructuredType structuredType, + IEdmModel edmModel, long? topOption) + { + if (topOption != null) + { + Contract.Assert(topOption.Value <= Int32.MaxValue); + int maxTop; + if (EdmLibHelpers.IsTopLimitExceeded( + property, + structuredType, + edmModel, + (int)topOption.Value, + _defaultQuerySettings, + out maxTop)) + { + throw new ODataException(Error.Format(SRResources.SkipTopLimitExceeded, maxTop, + AllowedQueryOptions.Top, topOption.Value)); + } + } + } + + private void ValidateCountInExpand(IEdmProperty property, IEdmStructuredType structuredType, IEdmModel edmModel, + bool? countOption) + { + if (countOption == true) + { + if (EdmLibHelpers.IsNotCountable( + property, + structuredType, + edmModel, + _defaultQuerySettings.EnableCount)) + { + throw new InvalidOperationException(Error.Format( + SRResources.NotCountablePropertyUsedForCount, + property.Name)); + } + } + } + + private void ValidateOrderByInExpand(IEdmProperty property, IEdmStructuredType structuredType, + OrderByClause orderByClause) + { + if (orderByClause != null) + { + _orderByQueryValidator.TryValidate(property, structuredType, orderByClause, false); + } + } + + private void ValidateFilterInExpand(IEdmProperty property, IEdmStructuredType structuredType, IEdmModel edmModel, + FilterClause filterClause, ODataValidationSettings validationSettings) + { + if (filterClause != null) + { + _filterQueryValidator.Validate(property, structuredType, filterClause, validationSettings, edmModel); + } + } + + private void ValidateSelectItem(SelectItem selectItem, IEdmProperty pathProperty, IEdmStructuredType pathStructuredType, + IEdmModel edmModel) + { + PathSelectItem pathSelectItem = selectItem as PathSelectItem; + if (pathSelectItem != null) + { + ODataPathSegment segment = pathSelectItem.SelectedPath.LastSegment; + NavigationPropertySegment navigationPropertySegment = segment as NavigationPropertySegment; + if (navigationPropertySegment != null) + { + IEdmNavigationProperty property = navigationPropertySegment.NavigationProperty; + if (EdmLibHelpers.IsNotNavigable(property, edmModel)) + { + throw new ODataException(Error.Format(SRResources.NotNavigablePropertyUsedInNavigation, + property.Name)); + } + } + else + { + PropertySegment propertySegment = segment as PropertySegment; + if (propertySegment != null) + { + if (EdmLibHelpers.IsNotSelectable(propertySegment.Property, pathProperty, pathStructuredType, edmModel, + _defaultQuerySettings.EnableSelect)) + { + throw new ODataException(Error.Format(SRResources.NotSelectablePropertyUsedInSelect, + propertySegment.Property.Name)); + } + } + } + } + else + { + WildcardSelectItem wildCardSelectItem = selectItem as WildcardSelectItem; + if (wildCardSelectItem != null) + { + foreach (var property in pathStructuredType.StructuralProperties()) + { + if (EdmLibHelpers.IsNotSelectable(property, pathProperty, pathStructuredType, edmModel, + _defaultQuerySettings.EnableSelect)) + { + throw new ODataException(Error.Format(SRResources.NotSelectablePropertyUsedInSelect, + property.Name)); + } + } + } + } + } + + private void ValidateLevelsOption(LevelsClause levelsClause, int depth, int currentDepth, + IEdmModel edmModel, IEdmNavigationProperty property) + { + ExpandConfiguration expandConfiguration; + bool isExpandable = EdmLibHelpers.IsExpandable(property.Name, + property, + property.ToEntityType(), + edmModel, + out expandConfiguration); + if (isExpandable) + { + int maxDepth = expandConfiguration.MaxDepth; + if (maxDepth > 0 && maxDepth < depth) + { + depth = maxDepth; + } + + if ((depth == 0 && levelsClause.IsMaxLevel) || (depth < levelsClause.Level)) + { + throw new ODataException( + Error.Format(SRResources.MaxExpandDepthExceeded, currentDepth + depth, "MaxExpansionDepth")); + } + } + else + { + if (!_defaultQuerySettings.EnableExpand || + (expandConfiguration != null && expandConfiguration.ExpandType == SelectExpandType.Disabled)) + { + throw new ODataException(Error.Format(SRResources.NotExpandablePropertyUsedInExpand, + property.Name)); + } + } + } + + private void ValidateOtherQueryOptionInExpand( + IEdmNavigationProperty property, + IEdmModel edmModel, + ExpandedNavigationSelectItem expandItem, + ODataValidationSettings validationSettings) + { + ValidateTopInExpand(property, property.ToEntityType(), edmModel, expandItem.TopOption); + ValidateCountInExpand(property, property.ToEntityType(), edmModel, expandItem.CountOption); + ValidateOrderByInExpand(property, property.ToEntityType(), expandItem.OrderByOption); + ValidateFilterInExpand(property, property.ToEntityType(), edmModel, expandItem.FilterOption, validationSettings); + } + + private void ValidateRestrictions( + int? remainDepth, + int currentDepth, + SelectExpandClause selectExpandClause, + IEdmNavigationProperty navigationProperty, + ODataValidationSettings validationSettings) + { + IEdmModel edmModel = _selectExpandQueryOption.Context.Model; + int? depth = remainDepth; + if (remainDepth < 0) + { + throw new ODataException( + Error.Format(SRResources.MaxExpandDepthExceeded, currentDepth - 1, "MaxExpansionDepth")); + } + + IEdmProperty pathProperty; + IEdmStructuredType pathStructuredType; + + if (navigationProperty == null) + { + pathProperty = _selectExpandQueryOption.Context.TargetProperty; + pathStructuredType = _selectExpandQueryOption.Context.TargetStructuredType; + } + else + { + pathProperty = navigationProperty; + pathStructuredType = navigationProperty.ToEntityType(); + } + + foreach (SelectItem selectItem in selectExpandClause.SelectedItems) + { + ExpandedNavigationSelectItem expandItem = selectItem as ExpandedNavigationSelectItem; + if (expandItem != null) + { + NavigationPropertySegment navigationSegment = + (NavigationPropertySegment)expandItem.PathToNavigationProperty.LastSegment; + IEdmNavigationProperty property = navigationSegment.NavigationProperty; + if (EdmLibHelpers.IsNotExpandable(property, edmModel)) + { + throw new ODataException(Error.Format(SRResources.NotExpandablePropertyUsedInExpand, + property.Name)); + } + + if (edmModel != null) + { + ValidateOtherQueryOptionInExpand(property, edmModel, expandItem, validationSettings); + bool isExpandable; + ExpandConfiguration expandConfiguration; + isExpandable = EdmLibHelpers.IsExpandable(property.Name, + pathProperty, + pathStructuredType, + edmModel, + out expandConfiguration); + if (isExpandable) + { + int maxDepth = expandConfiguration.MaxDepth; + if (maxDepth > 0 && (remainDepth == null || maxDepth < remainDepth)) + { + remainDepth = maxDepth; + } + } + else if (!isExpandable) + { + if (!_defaultQuerySettings.EnableExpand || + (expandConfiguration != null && expandConfiguration.ExpandType == SelectExpandType.Disabled)) + { + throw new ODataException(Error.Format(SRResources.NotExpandablePropertyUsedInExpand, + property.Name)); + } + } + } + + if (remainDepth.HasValue) + { + remainDepth--; + if (expandItem.LevelsOption != null) + { + ValidateLevelsOption(expandItem.LevelsOption, remainDepth.Value, currentDepth + 1, edmModel, + property); + } + } + + ValidateRestrictions(remainDepth, currentDepth + 1, expandItem.SelectAndExpand, property, + validationSettings); + remainDepth = depth; + } + + ValidateSelectItem(selectItem, pathProperty, pathStructuredType, edmModel); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipQueryValidator.cs new file mode 100644 index 0000000..6a2c114 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipQueryValidator.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate a based on the . + /// + public class SkipQueryValidator + { + /// + /// Validates a . + /// + /// The $skip query. + /// The validation settings. + public virtual void Validate(SkipQueryOption skipQueryOption, ODataValidationSettings validationSettings) + { + if (skipQueryOption == null) + { + throw Error.ArgumentNull("skipQueryOption"); + } + + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (skipQueryOption.Value > validationSettings.MaxSkip) + { + throw new ODataException(Error.Format(SRResources.SkipTopLimitExceeded, validationSettings.MaxSkip, AllowedQueryOptions.Skip, skipQueryOption.Value)); + } + } + + internal static SkipQueryValidator GetSkipQueryValidator(ODataQueryContext context) + { + if (context == null || context.RequestContainer == null) + { + return new SkipQueryValidator(); + } + + return context.RequestContainer.GetRequiredService(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipTokenQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipTokenQueryValidator.cs new file mode 100644 index 0000000..be8d3db --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/SkipTokenQueryValidator.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate a based on the . + /// + public class SkipTokenQueryValidator + { + /// + /// Validates a . + /// + /// The $skiptoken query. + /// The validation settings. + public virtual void Validate(SkipTokenQueryOption skipToken, ODataValidationSettings validationSettings) + { + if (skipToken == null) + { + throw Error.ArgumentNull("skipQueryOption"); + } + + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (skipToken.Context != null) + { + DefaultQuerySettings defaultSetting = skipToken.Context.DefaultQuerySettings; + if (!defaultSetting.EnableSkipToken) + { + throw new ODataException(Error.Format(SRResources.NotAllowedQueryOption, AllowedQueryOptions.SkipToken, "AllowedQueryOptions")); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/TopQueryValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/TopQueryValidator.cs new file mode 100644 index 0000000..c5c0c64 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Query/Validators/TopQueryValidator.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query.Validators +{ + /// + /// Represents a validator used to validate a based on the . + /// + public class TopQueryValidator + { + /// + /// Validates a . + /// + /// The $top query. + /// The validation settings. + public virtual void Validate(TopQueryOption topQueryOption, ODataValidationSettings validationSettings) + { + if (topQueryOption == null) + { + throw Error.ArgumentNull("topQueryOption"); + } + + if (validationSettings == null) + { + throw Error.ArgumentNull("validationSettings"); + } + + if (topQueryOption.Value > validationSettings.MaxTop) + { + throw new ODataException(Error.Format(SRResources.SkipTopLimitExceeded, validationSettings.MaxTop, + AllowedQueryOptions.Top, topQueryOption.Value)); + } + + int maxTop; + IEdmProperty property = topQueryOption.Context.TargetProperty; + IEdmStructuredType structuredType = topQueryOption.Context.TargetStructuredType; + + if (EdmLibHelpers.IsTopLimitExceeded( + property, + structuredType, + topQueryOption.Context.Model, + topQueryOption.Value, topQueryOption.Context.DefaultQuerySettings, + out maxTop)) + { + throw new ODataException(Error.Format(SRResources.SkipTopLimitExceeded, maxTop, + AllowedQueryOptions.Top, topQueryOption.Value)); + } + } + + internal static TopQueryValidator GetTopQueryValidator(ODataQueryContext context) + { + if (context == null || context.RequestContainer == null) + { + return new TopQueryValidator(); + } + + return context.RequestContainer.GetRequiredService(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictions.cs new file mode 100644 index 0000000..e35171d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictions.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Builder; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a queryable restriction on an EDM property, including not filterable, not sortable, + /// not navigable, not expandable, not countable, automatically expand. + /// + public class QueryableRestrictions + { + private bool _autoExpand; + + /// + /// Initializes a new instance of the class. + /// + public QueryableRestrictions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The PropertyConfiguration containing queryable restrictions. + public QueryableRestrictions(PropertyConfiguration propertyConfiguration) + { + NotFilterable = propertyConfiguration.NotFilterable; + NotSortable = propertyConfiguration.NotSortable; + NotNavigable = propertyConfiguration.NotNavigable; + NotExpandable = propertyConfiguration.NotExpandable; + NotCountable = propertyConfiguration.NotCountable; + DisableAutoExpandWhenSelectIsPresent = propertyConfiguration.DisableAutoExpandWhenSelectIsPresent; + _autoExpand = propertyConfiguration.AutoExpand; + } + + /// + /// Gets or sets whether the property is not filterable. default is false. + /// + public bool NotFilterable { get; set; } + + /// + /// Gets or sets whether the property is nonfilterable. default is false. + /// + public bool NonFilterable + { + get { return NotFilterable; } + set { NotFilterable = value; } + } + + /// + /// Gets or sets whether the property is not sortable. default is false. + /// + public bool NotSortable { get; set; } + + /// + /// Gets or sets whether the property is unsortable. default is false. + /// + public bool Unsortable + { + get { return NotSortable; } + set { NotSortable = value; } + } + + /// + /// Gets or sets whether the property is not navigable. default is false. + /// + public bool NotNavigable { get; set; } + + /// + /// Gets or sets whether the property is not expandable. default is false. + /// + public bool NotExpandable { get; set; } + + /// + /// Gets or sets whether the property is not countable. default is false. + /// + public bool NotCountable { get; set; } + + /// + /// Gets or sets whether the property is automatically expanded. default is false. + /// + public bool AutoExpand + { + get { return !NotExpandable && _autoExpand; } + set { _autoExpand = value; } + } + + /// + /// If set to true then automatic expand will be disabled if there is a $select specify by client. + /// + public bool DisableAutoExpandWhenSelectIsPresent { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictionsAnnotation.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictionsAnnotation.cs new file mode 100644 index 0000000..42a0d4b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/QueryableRestrictionsAnnotation.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an annotation to add the queryable restrictions on an EDM property, including not filterable, + /// not sortable, not navigable, not expandable, not countable, automatically expand. + /// + public class QueryableRestrictionsAnnotation + { + /// + /// Initializes a new instance of class. + /// + /// The queryable restrictions for the EDM property. + public QueryableRestrictionsAnnotation(QueryableRestrictions restrictions) + { + if (restrictions == null) + { + throw Error.ArgumentNull("restrictions"); + } + + Restrictions = restrictions; + } + + /// + /// Gets the restrictions for the EDM property. + /// + public QueryableRestrictions Restrictions { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestMethod.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestMethod.cs new file mode 100644 index 0000000..38d49f6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestMethod.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData +{ + /// + /// An enumeration for request methods. + /// + internal enum ODataRequestMethod + { + /// + /// An unknown method. + /// + Unknown = -1, + + /// + /// "Get" + /// + Get = 0, + + /// + /// "Delete" + /// + Delete, + + /// + /// "Merge" + /// + Merge, + + /// + /// "Patch" + /// + Patch, + + /// + /// "Post" + /// + Post, + + /// + /// "Put" + /// + Put, + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestPreferenceHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestPreferenceHelpers.cs new file mode 100644 index 0000000..f3186fd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/RequestPreferenceHelpers.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData +{ + internal static class RequestPreferenceHelpers + { + public const string PreferHeaderName = "Prefer"; + public const string ReturnContentHeaderValue = "return=representation"; + public const string ReturnNoContentHeaderValue = "return=minimal"; + public const string ODataMaxPageSize = "odata.maxpagesize"; + public const string MaxPageSize = "maxpagesize"; + + internal static bool RequestPrefersReturnContent(IWebApiHeaders headers) + { + IEnumerable preferences = null; + if (headers.TryGetValues(PreferHeaderName, out preferences)) + { + return (preferences.FirstOrDefault(s => s.IndexOf(ReturnContentHeaderValue, StringComparison.OrdinalIgnoreCase) >= 0) != null); + } + return false; + } + + internal static bool RequestPrefersReturnNoContent(IWebApiHeaders headers) + { + IEnumerable preferences = null; + if (headers.TryGetValues(PreferHeaderName, out preferences)) + { + return (preferences.FirstOrDefault(s => s.IndexOf(ReturnNoContentHeaderValue, StringComparison.OrdinalIgnoreCase) >= 0) != null); + } + return false; + } + + internal static bool RequestPrefersMaxPageSize(IWebApiHeaders headers, out int pageSize) + { + pageSize = -1; + IEnumerable preferences = null; + if (headers.TryGetValues(PreferHeaderName, out preferences)) + { + pageSize = GetMaxPageSize(preferences, MaxPageSize); + if (pageSize >= 0) + { + return true; + } + //maxpagesize supersedes odata.maxpagesize + pageSize = GetMaxPageSize(preferences, ODataMaxPageSize); + if (pageSize >= 0) + { + return true; + } + } + + return false; + } + + private static int GetMaxPageSize(IEnumerable preferences, string preferenceHeaderName) + { + const int Failed = -1; + string maxPageSize = preferences.FirstOrDefault(s => s.IndexOf(preferenceHeaderName, StringComparison.OrdinalIgnoreCase) >= 0); + if (String.IsNullOrEmpty(maxPageSize)) + { + return Failed; + } + else + { + int index = maxPageSize.IndexOf(preferenceHeaderName, StringComparison.OrdinalIgnoreCase) + preferenceHeaderName.Length; + String value = String.Empty; + if (maxPageSize[index++] == '=') + { + while (index < maxPageSize.Length && Char.IsDigit(maxPageSize[index])) + { + value += maxPageSize[index++]; + } + } + int pageSize = -1; + if (Int32.TryParse(value, out pageSize)) + { + return pageSize; + } + } + return Failed; + } + internal static string GetRequestPreferHeader(IWebApiHeaders headers) + { + IEnumerable values; + if (headers.TryGetValues(PreferHeaderName, out values)) + { + // If there are many "Prefer" headers, pick up the first one. + return values.FirstOrDefault(); + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContext.cs new file mode 100644 index 0000000..dd838f4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContext.cs @@ -0,0 +1,281 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query.Expressions; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// An instance of gets passed to the self link ( + /// , + /// , + /// + /// ) and navigation link ( + /// , + /// + /// ) builders and can be used by the link builders to generate links. + /// + public partial class ResourceContext + { + private object _resourceInstance; + + /// + /// Initializes a new instance of the class. + /// + public ResourceContext() + { + SerializerContext = new ODataSerializerContext(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The backing . + /// The EDM structured type of this instance context. + /// The object representing the instance of this context. + public ResourceContext(ODataSerializerContext serializerContext, IEdmStructuredTypeReference structuredType, object resourceInstance) + : this(serializerContext, structuredType, AsEdmResourceObject(resourceInstance, structuredType, serializerContext.Model)) + { + } + + private ResourceContext(ODataSerializerContext serializerContext, IEdmStructuredTypeReference structuredType, IEdmStructuredObject edmObject) + { + if (serializerContext == null) + { + throw Error.ArgumentNull("serializerContext"); + } + + SerializerContext = serializerContext; + StructuredType = structuredType.StructuredDefinition(); + EdmObject = edmObject; + } + + /// + /// Gets or sets the . + /// + public ODataSerializerContext SerializerContext { get; set; } + + /// + /// Gets or sets the HTTP request that caused this instance to be generated. + /// + internal IWebApiRequestMessage InternalRequest + { + get + { + return SerializerContext.InternalRequest; + } + } + + /// + /// Gets or sets the to which this instance belongs. + /// + public IEdmModel EdmModel + { + get + { + return SerializerContext.Model; + } + set + { + SerializerContext.Model = value; + } + } + + /// + /// Gets or sets the to which this instance belongs. + /// + public IEdmNavigationSource NavigationSource + { + get + { + return SerializerContext.NavigationSource; + } + set + { + SerializerContext.NavigationSource = value; + } + } + + /// + /// Gets or sets the of this resource instance. + /// + public IEdmStructuredType StructuredType { get; set; } + + /// + /// Gets or sets the backing this instance. + /// + public IEdmStructuredObject EdmObject { get; set; } + + /// + /// Gets or sets the value of this resource instance. + /// + public object ResourceInstance + { + get + { + if (_resourceInstance == null) + { + _resourceInstance = BuildResourceInstance(); + } + + return _resourceInstance; + } + set + { + _resourceInstance = value; + } + } + + /// + /// Gets or sets a that may be used to generate links while serializing this resource + /// instance. + /// + internal IWebApiUrlHelper InternalUrlHelper + { + get + { + return SerializerContext.InternalUrlHelper; + } + } + + /// + /// Gets or sets a value indicating whether ActionAvailabilityChecks should be performed or not. + /// + /// + /// This value is used to tell the formatter whether to check availability of an action before including a link + /// to it. When in a feed we skip this check. + /// + public bool SkipExpensiveAvailabilityChecks + { + get + { + return SerializerContext.SkipExpensiveAvailabilityChecks; + } + set + { + SerializerContext.SkipExpensiveAvailabilityChecks = value; + } + } + + /// + /// Gets or sets the dynamic complex or collection of complex properties should be nested in this instance. + /// + /// + /// The key is the dynamic property name. + /// The value is the dynamic property value. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:EnableSetterForProperty", Justification = "Enable setter for dictionary property")] + public IDictionary DynamicComplexProperties { get; set; } + + /// + /// Gets the value of the property with the given name from the of this instance if present; throws if the property is + /// not present. + /// + /// The name of the property to get. + /// The value of the property if present. + public object GetPropertyValue(string propertyName) + { + if (EdmObject == null) + { + throw Error.InvalidOperation(SRResources.EdmObjectNull, typeof(ResourceContext).Name); + } + + object value; + if (EdmObject.TryGetPropertyValue(propertyName, out value)) + { + return value; + } + else + { + IEdmTypeReference edmType = EdmObject.GetEdmType(); + if (edmType == null) + { + // Provide general guidance in the message. typeof(IEdmTypeReference).Name would be too specific. + throw Error.InvalidOperation(SRResources.EdmTypeCannotBeNull, EdmObject.GetType().FullName, + typeof(IEdmObject).Name); + } + + throw Error.InvalidOperation(SRResources.PropertyNotFound, edmType.ToTraceString(), propertyName); + } + } + + private object BuildResourceInstance() + { + if (EdmObject == null) + { + return null; + } + + TypedEdmStructuredObject edmStructruredObject = EdmObject as TypedEdmStructuredObject; + if (edmStructruredObject != null) + { + return edmStructruredObject.Instance; + } + + SelectExpandWrapper selectExpandWrapper = EdmObject as SelectExpandWrapper; + if (selectExpandWrapper != null && selectExpandWrapper.UntypedInstance != null) + { + return selectExpandWrapper.UntypedInstance; + } + + Type clrType = EdmLibHelpers.GetClrType(StructuredType, EdmModel); + if (clrType == null) + { + throw new InvalidOperationException(Error.Format(SRResources.MappingDoesNotContainResourceType, StructuredType.FullTypeName())); + } + + object resource = Activator.CreateInstance(clrType); + foreach (IEdmStructuralProperty property in StructuredType.StructuralProperties()) + { + object value; + if (EdmObject.TryGetPropertyValue(property.Name, out value) && value != null) + { + string propertyName = EdmLibHelpers.GetClrPropertyName(property, EdmModel); + + if (TypeHelper.IsCollection(value.GetType())) + { + DeserializationHelpers.SetCollectionProperty(resource, property, value, propertyName); + } + else + { + DeserializationHelpers.SetProperty(resource, propertyName, value); + } + } + } + + return resource; + } + + private static IEdmStructuredObject AsEdmResourceObject(object resourceInstance, IEdmStructuredTypeReference structuredType, IEdmModel model) + { + if (structuredType == null) + { + throw Error.ArgumentNull("structuredType"); + } + + IEdmStructuredObject edmStructuredObject = resourceInstance as IEdmStructuredObject; + if (edmStructuredObject != null) + { + return edmStructuredObject; + } + + if (structuredType.IsEntity()) + { + return new TypedEdmEntityObject(resourceInstance, structuredType.AsEntity(), model); + } + + Contract.Assert(structuredType.IsComplex()); + return new TypedEdmComplexObject(resourceInstance, structuredType.AsComplex(), model); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContextOfTStructuredType.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContextOfTStructuredType.cs new file mode 100644 index 0000000..6f80d02 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceContextOfTStructuredType.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData +{ + /// + /// An instance of gets passed to the self link (, , ) + /// and navigation link (, ) builders and can be used by the link builders to generate links. + /// + /// The structural type + public class ResourceContext : ResourceContext + { + /// + /// Initializes a new instance of the class. + /// + public ResourceContext() + : base() + { + } + + /// + /// Gets or sets the resource instance. + /// + [Obsolete("Resource instance might not be available when the incoming uri has a $select. Use the EdmObject property instead.")] + public new TStructuredType ResourceInstance + { + get + { + return (TStructuredType)base.ResourceInstance; + } + set + { + base.ResourceInstance = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceSetContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceSetContext.cs new file mode 100644 index 0000000..4da7961 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/ResourceSetContext.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Contains context information about the resource set currently being serialized. + /// + public partial class ResourceSetContext + { + /// + /// Gets the this instance belongs to. + /// + public IEdmEntitySetBase EntitySetBase { get; set; } + + /// + /// Gets the value of this feed instance. + /// + public object ResourceSetInstance { get; set; } + + /// + /// Gets or sets the HTTP request that caused this instance to be generated. + /// + internal IWebApiRequestMessage InternalRequest { get; private set; } + + /// + /// Gets or sets the to be used for generating links while serializing this + /// feed instance. + /// + internal IWebApiUrlHelper InternalUrlHelper { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Results/ResultHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Results/ResultHelpers.cs new file mode 100644 index 0000000..5f8b7ed --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Results/ResultHelpers.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Builder.Conventions; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Results +{ + internal static partial class ResultHelpers + { + public const string EntityIdHeaderName = "OData-EntityId"; + + public static Uri GenerateODataLink(ResourceContext resourceContext, bool isEntityId) + { + Contract.Assert(resourceContext != null); + + // Generate location or entityId header from request Uri and key, if Post to a containment. + // Link builder is not used, since it is also for generating ID, Edit, Read links, etc. scenarios, where + // request Uri is not used. + if (resourceContext.NavigationSource.NavigationSourceKind() == EdmNavigationSourceKind.ContainedEntitySet) + { + return GenerateContainmentODataPathSegments(resourceContext, isEntityId); + } + + NavigationSourceLinkBuilderAnnotation linkBuilder = + resourceContext.EdmModel.GetNavigationSourceLinkBuilder(resourceContext.NavigationSource); + Contract.Assert(linkBuilder != null); + + Uri idLink = linkBuilder.BuildIdLink(resourceContext); + if (isEntityId) + { + if (idLink == null) + { + throw Error.InvalidOperation( + SRResources.IdLinkNullForEntityIdHeader, + resourceContext.NavigationSource.Name); + } + + return idLink; + } + + Uri editLink = linkBuilder.BuildEditLink(resourceContext); + if (editLink == null) + { + if (idLink != null) + { + return idLink; + } + + throw Error.InvalidOperation( + SRResources.EditLinkNullForLocationHeader, + resourceContext.NavigationSource.Name); + } + + return editLink; + } + + private static Uri GenerateContainmentODataPathSegments(ResourceContext resourceContext, bool isEntityId) + { + Contract.Assert(resourceContext != null); + Contract.Assert( + resourceContext.NavigationSource.NavigationSourceKind() == EdmNavigationSourceKind.ContainedEntitySet); + Contract.Assert(resourceContext.Request != null); + + ODataPath path = resourceContext.InternalRequest.Context.Path; + if (path == null) + { + throw Error.InvalidOperation(SRResources.ODataPathMissing); + } + + path = new ContainmentPathBuilder().TryComputeCanonicalContainingPath(path); + + List odataPath = path.Segments.ToList(); + + // create a template entity set if it's contained entity set + IEdmEntitySet entitySet = resourceContext.NavigationSource as IEdmEntitySet; + if (entitySet == null) + { + EdmEntityContainer container = new EdmEntityContainer("NS", "Default"); + entitySet = new EdmEntitySet(container, resourceContext.NavigationSource.Name, resourceContext.NavigationSource.EntityType()); + } + + odataPath.Add(new EntitySetSegment(entitySet)); + odataPath.Add(new KeySegment(ConventionsHelpers.GetEntityKey(resourceContext), + resourceContext.StructuredType as IEdmEntityType, resourceContext.NavigationSource)); + + if (!isEntityId) + { + bool isSameType = resourceContext.StructuredType == resourceContext.NavigationSource.EntityType(); + if (!isSameType) + { + odataPath.Add(new TypeSegment(resourceContext.StructuredType, resourceContext.NavigationSource)); + } + } + + string odataLink = resourceContext.InternalUrlHelper.CreateODataLink(odataPath); + return odataLink == null ? null : new Uri(odataLink); + } + + private static IEdmEntityTypeReference GetEntityType(IEdmModel model, object entity) + { + Type entityType = entity.GetType(); + IEdmTypeReference edmType = model.GetEdmTypeReference(entityType); + if (edmType == null) + { + throw Error.InvalidOperation(SRResources.ResourceTypeNotInModel, entityType.FullName); + } + if (!edmType.IsEntity()) + { + throw Error.InvalidOperation(SRResources.TypeMustBeEntity, edmType.FullName()); + } + + return edmType.AsEntity(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionMapExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionMapExtensions.cs new file mode 100644 index 0000000..4cbea41 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionMapExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Provides helper methods for querying an action map. + /// + internal static class ActionMapExtensions + { + public static string FindMatchingAction(this IWebApiActionMap actionMap, params string[] targetActionNames) + { + foreach (string targetActionName in targetActionNames) + { + if (actionMap.Contains(targetActionName)) + { + return targetActionName; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionRoutingConvention.cs new file mode 100644 index 0000000..7d0b3e6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/ActionRoutingConvention.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles action invocations. + /// + public partial class ActionRoutingConvention : NavigationSourceRoutingConvention + { + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, IWebApiActionMap actionMap) + { + if (ODataRequestMethod.Post == controllerContext.Request.Method) + { + switch (odataPath.PathTemplate) + { + case "~/entityset/key/cast/action": + case "~/entityset/key/action": + string actionName = GetAction(odataPath).SelectAction(actionMap, isCollection: false); + if (actionName != null) + { + KeySegment keySegment = (KeySegment)odataPath.Segments[1]; + controllerContext.AddKeyValueToRouteData(keySegment); + } + return actionName; + case "~/entityset/cast/action": + case "~/entityset/action": + return GetAction(odataPath).SelectAction(actionMap, isCollection: true); + case "~/singleton/action": + case "~/singleton/cast/action": + return GetAction(odataPath).SelectAction(actionMap, isCollection: false); + } + } + + return null; + } + + private static IEdmAction GetAction(ODataPath odataPath) + { + ODataPathSegment odataSegment = odataPath.Segments.Last(); + IEdmAction action = null; + OperationSegment actionSegment = odataSegment as OperationSegment; + if (actionSegment != null) + { + action = actionSegment.Operations.First() as IEdmAction; + } + + return action; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/AttributeRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/AttributeRoutingConvention.cs new file mode 100644 index 0000000..7c0d74e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/AttributeRoutingConvention.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing.Template; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Represents a routing convention that looks for s to match an + /// to a controller and an action. + /// + public partial class AttributeRoutingConvention + { + private readonly string _routeName; + + private IDictionary _attributeMappings; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + private AttributeRoutingConvention(string routeName) + { + if (routeName == null) + { + throw Error.ArgumentNull("routeName"); + } + + _routeName = routeName; + } + + /// + /// Gets the to be used for parsing the route templates. + /// + public IODataPathTemplateHandler ODataPathTemplateHandler { get; private set; } + + /// + internal static SelectControllerResult SelectControllerImpl(ODataPath odataPath, IWebApiRequestMessage request, + IDictionary attributeMappings) + { + Dictionary values = new Dictionary(); + + foreach (KeyValuePair attributeMapping in attributeMappings) + { + ODataPathTemplate template = attributeMapping.Key; + IWebApiActionDescriptor action = attributeMapping.Value; + + if (action.IsHttpMethodSupported(request.Method) && template.TryMatch(odataPath, values)) + { + values["action"] = action.ActionName; + SelectControllerResult result = new SelectControllerResult(action.ControllerName, values); + + return result; + } + } + + return null; + } + + /// + internal static string SelectActionImpl(IWebApiControllerContext controllerContext) + { + var routeData = controllerContext.RouteData; + var routingConventionsStore = controllerContext.Request.Context.RoutingConventionsStore; + + IDictionary attributeRouteData = controllerContext.ControllerResult.Values; + if (attributeRouteData != null) + { + foreach (var item in attributeRouteData) + { + if (item.Key.StartsWith(ODataParameterValue.ParameterValuePrefix, StringComparison.Ordinal) && + item.Value is ODataParameterValue) + { + routingConventionsStore.Add(item); + } + else + { + routeData.Add(item); + } + } + + return attributeRouteData["action"] as string; + } + + return null; + } + + private static IEnumerable GetODataRoutePrefixes(IEnumerable prefixAttributes, string controllerName) + { + Contract.Assert(prefixAttributes != null); + + if (!prefixAttributes.Any()) + { + yield return null; + } + else + { + foreach (ODataRoutePrefixAttribute prefixAttribute in prefixAttributes) + { + string prefix = prefixAttribute.Prefix; + + if (prefix != null && prefix.StartsWith("/", StringComparison.Ordinal)) + { + throw Error.InvalidOperation(SRResources.RoutePrefixStartsWithSlash, prefix, controllerName); + } + + if (prefix != null && prefix.EndsWith("/", StringComparison.Ordinal)) + { + prefix = prefix.TrimEnd('/'); + } + + yield return prefix; + } + } + } + + private ODataPathTemplate GetODataPathTemplate(string prefix, string pathTemplate, + IServiceProvider requestContainer, string controllerName, string actionName) + { + if (prefix != null && !pathTemplate.StartsWith("/", StringComparison.Ordinal)) + { + if (String.IsNullOrEmpty(pathTemplate)) + { + pathTemplate = prefix; + } + else if (pathTemplate.StartsWith("(", StringComparison.Ordinal)) + { + // We don't need '/' when the pathTemplate starts with a key segment. + pathTemplate = prefix + pathTemplate; + } + else + { + pathTemplate = prefix + "/" + pathTemplate; + } + } + + if (pathTemplate.StartsWith("/", StringComparison.Ordinal)) + { + pathTemplate = pathTemplate.Substring(1); + } + + ODataPathTemplate odataPathTemplate; + + try + { + // We are NOT in a request but establishing the attribute routing convention. + // So use the root container rather than the request container. + odataPathTemplate = ODataPathTemplateHandler.ParseTemplate(pathTemplate, requestContainer); + } + catch (ODataException e) + { + throw Error.InvalidOperation(SRResources.InvalidODataRouteOnAction, pathTemplate, actionName, controllerName, e.Message); + } + + return odataPathTemplate; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/DynamicPropertyRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/DynamicPropertyRoutingConvention.cs new file mode 100644 index 0000000..b60e752 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/DynamicPropertyRoutingConvention.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles dynamic properties for open type. + /// + public partial class DynamicPropertyRoutingConvention : NavigationSourceRoutingConvention + { + private const string ActionName = "DynamicProperty"; + + /// + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "These is simple conversion function based on OData path value and cannot be split up.")] + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, + IWebApiActionMap actionMap) + { + string actionName = null; + DynamicPathSegment dynamicPropertSegment = null; + + switch (odataPath.PathTemplate) + { + case "~/entityset/key/dynamicproperty": + case "~/entityset/key/cast/dynamicproperty": + case "~/singleton/dynamicproperty": + case "~/singleton/cast/dynamicproperty": + dynamicPropertSegment = odataPath.Segments.Last() as DynamicPathSegment; + if (dynamicPropertSegment == null) + { + return null; + } + + if (ODataRequestMethod.Get == controllerContext.Request.Method) + { + string actionNamePrefix = String.Format(CultureInfo.InvariantCulture, "Get{0}", ActionName); + actionName = actionMap.FindMatchingAction(actionNamePrefix); + } + break; + case "~/entityset/key/property/dynamicproperty": + case "~/entityset/key/cast/property/dynamicproperty": + case "~/singleton/property/dynamicproperty": + case "~/singleton/cast/property/dynamicproperty": + dynamicPropertSegment = odataPath.Segments.Last() as DynamicPathSegment; + if (dynamicPropertSegment == null) + { + return null; + } + + PropertySegment propertyAccessSegment = odataPath.Segments[odataPath.Segments.Count - 2] + as PropertySegment; + if (propertyAccessSegment == null) + { + return null; + } + + EdmComplexType complexType = propertyAccessSegment.Property.Type.Definition as EdmComplexType; + if (complexType == null) + { + return null; + } + + if (ODataRequestMethod.Get == controllerContext.Request.Method) + { + string actionNamePrefix = String.Format(CultureInfo.InvariantCulture, "Get{0}", ActionName); + actionName = actionMap.FindMatchingAction(actionNamePrefix + "From" + propertyAccessSegment.Property.Name); + } + break; + default: break; + } + + if (actionName != null) + { + if (odataPath.PathTemplate.StartsWith("~/entityset/key", StringComparison.Ordinal)) + { + KeySegment keyValueSegment = (KeySegment)odataPath.Segments[1]; + controllerContext.AddKeyValueToRouteData(keyValueSegment); + } + + controllerContext.RouteData.Add(ODataRouteConstants.DynamicProperty, dynamicPropertSegment.Identifier); + var key = ODataParameterValue.ParameterValuePrefix + ODataRouteConstants.DynamicProperty; + var value = new ODataParameterValue(dynamicPropertSegment.Identifier, EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(typeof(string))); + controllerContext.RouteData.Add(key, value); + controllerContext.Request.Context.RoutingConventionsStore.Add(key, value); + return actionName; + } + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntityRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntityRoutingConvention.cs new file mode 100644 index 0000000..e08ffd6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntityRoutingConvention.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles operating on entities by key. + /// + public partial class EntityRoutingConvention : NavigationSourceRoutingConvention + { + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, IWebApiActionMap actionMap) + { + if (odataPath.PathTemplate == "~/entityset/key" || + odataPath.PathTemplate == "~/entityset/key/cast") + { + string httpMethodName; + + switch (controllerContext.Request.Method) + { + case ODataRequestMethod.Get: + httpMethodName = "Get"; + break; + case ODataRequestMethod.Put: + httpMethodName = "Put"; + break; + case ODataRequestMethod.Patch: + case ODataRequestMethod.Merge: + httpMethodName = "Patch"; + break; + case ODataRequestMethod.Delete: + httpMethodName = "Delete"; + break; + default: + return null; + } + + Contract.Assert(httpMethodName != null); + + IEdmEntityType entityType = (IEdmEntityType)odataPath.EdmType; + + // e.g. Try GetCustomer first, then fallback on Get action name + string actionName = actionMap.FindMatchingAction( + httpMethodName + entityType.Name, + httpMethodName); + + if (actionName != null) + { + KeySegment keySegment = (KeySegment)odataPath.Segments[1]; + controllerContext.AddKeyValueToRouteData(keySegment); + return actionName; + } + } + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntitySetRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntitySetRoutingConvention.cs new file mode 100644 index 0000000..4169f5b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/EntitySetRoutingConvention.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles entity sets. + /// + public partial class EntitySetRoutingConvention : NavigationSourceRoutingConvention + { + /// + /// Selects the action for OData requests. + /// + /// The OData path. + /// The controller context. + /// The action map. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected action + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, IWebApiActionMap actionMap) + { + if (odataPath.PathTemplate == "~/entityset") + { + EntitySetSegment entitySetSegment = (EntitySetSegment)odataPath.Segments[0]; + IEdmEntitySetBase entitySet = entitySetSegment.EntitySet; + + if (ODataRequestMethod.Get == controllerContext.Request.Method) + { + // e.g. Try GetCustomers first, then fall back to Get action name + return actionMap.FindMatchingAction( + "Get" + entitySet.Name, + "Get"); + } + else if (ODataRequestMethod.Post == controllerContext.Request.Method) + { + // e.g. Try PostCustomer first, then fall back to Post action name + return actionMap.FindMatchingAction( + "Post" + entitySet.EntityType().Name, + "Post"); + } + } + else if (odataPath.PathTemplate == "~/entityset/$count" && + ODataRequestMethod.Get == controllerContext.Request.Method) + { + EntitySetSegment entitySetSegment = (EntitySetSegment)odataPath.Segments[0]; + IEdmEntitySetBase entitySet = entitySetSegment.EntitySet; + + // e.g. Try GetCustomers first, then fall back to Get action name + return actionMap.FindMatchingAction( + "Get" + entitySet.Name, + "Get"); + } + else if (odataPath.PathTemplate == "~/entityset/cast") + { + EntitySetSegment entitySetSegment = (EntitySetSegment)odataPath.Segments[0]; + IEdmEntitySetBase entitySet = entitySetSegment.EntitySet; + IEdmCollectionType collectionType = (IEdmCollectionType)odataPath.EdmType; + IEdmEntityType entityType = (IEdmEntityType)collectionType.ElementType.Definition; + + if (ODataRequestMethod.Get == controllerContext.Request.Method) + { + // e.g. Try GetCustomersFromSpecialCustomer first, then fall back to GetFromSpecialCustomer + return actionMap.FindMatchingAction( + "Get" + entitySet.Name + "From" + entityType.Name, + "GetFrom" + entityType.Name); + } + else if (ODataRequestMethod.Post == controllerContext.Request.Method) + { + // e.g. Try PostCustomerFromSpecialCustomer first, then fall back to PostFromSpecialCustomer + return actionMap.FindMatchingAction( + "Post" + entitySet.EntityType().Name + "From" + entityType.Name, + "PostFrom" + entityType.Name); + } + } + else if (odataPath.PathTemplate == "~/entityset/cast/$count" && + ODataRequestMethod.Get == controllerContext.Request.Method) + { + EntitySetSegment entitySetSegment = (EntitySetSegment)odataPath.Segments[0]; + IEdmEntitySetBase entitySet = entitySetSegment.EntitySet; + IEdmCollectionType collectionType = (IEdmCollectionType)odataPath.Segments[1].EdmType; + IEdmEntityType entityType = (IEdmEntityType)collectionType.ElementType.Definition; + + // e.g. Try GetCustomersFromSpecialCustomer first, then fall back to GetFromSpecialCustomer + return actionMap.FindMatchingAction( + "Get" + entitySet.Name + "From" + entityType.Name, + "GetFrom" + entityType.Name); + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/FunctionRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/FunctionRoutingConvention.cs new file mode 100644 index 0000000..891fbd0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/FunctionRoutingConvention.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles function invocations. + /// + public partial class FunctionRoutingConvention : NavigationSourceRoutingConvention + { + /// + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "These is simple conversion function based on context and OData path value and cannot be split up.")] + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, + IWebApiActionMap actionMap) + { + if (ODataRequestMethod.Get == controllerContext.Request.Method) + { + string actionName = null; + OperationSegment function = null; + switch (odataPath.PathTemplate) + { + case "~/entityset/key/cast/function": + case "~/entityset/key/function": + function = odataPath.Segments.Last() as OperationSegment; + actionName = GetFunction(function).SelectAction(actionMap, isCollection: false); + if (actionName != null) + { + controllerContext.AddKeyValueToRouteData((KeySegment)odataPath.Segments[1]); + } + break; + case "~/entityset/key/cast/function/$count": + case "~/entityset/key/function/$count": + function = odataPath.Segments[odataPath.Segments.Count - 2] as OperationSegment; + actionName = GetFunction(function).SelectAction(actionMap, isCollection: false); + if (actionName != null) + { + controllerContext.AddKeyValueToRouteData((KeySegment)odataPath.Segments[1]); + } + break; + case "~/entityset/cast/function": + case "~/entityset/function": + function = odataPath.Segments.Last() as OperationSegment; + actionName = GetFunction(function).SelectAction(actionMap, isCollection: true); + break; + case "~/entityset/cast/function/$count": + case "~/entityset/function/$count": + function = odataPath.Segments[odataPath.Segments.Count - 2] as OperationSegment; + actionName = GetFunction(function).SelectAction(actionMap, isCollection: true); + break; + case "~/singleton/function": + case "~/singleton/cast/function": + function = odataPath.Segments.Last() as OperationSegment; + actionName = GetFunction(function).SelectAction(actionMap, isCollection: false); + break; + case "~/singleton/function/$count": + case "~/singleton/cast/function/$count": + function = odataPath.Segments[odataPath.Segments.Count - 2] as OperationSegment; + actionName = GetFunction(function).SelectAction(actionMap, isCollection: false); + break; + } + + if (actionName != null) + { + controllerContext.AddFunctionParameterToRouteData(function); + return actionName; + } + } + + return null; + } + + private static IEdmFunction GetFunction(OperationSegment segment) + { + if (segment != null) + { + IEdmFunction function = segment.Operations.First() as IEdmFunction; + return function; + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/MetadataRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/MetadataRoutingConvention.cs new file mode 100644 index 0000000..818e544 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/MetadataRoutingConvention.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles OData metadata requests. + /// + public partial class MetadataRoutingConvention + { + /// + /// Selects the controller for OData requests. + /// + /// The OData path. + /// The request. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected controller + /// + internal static SelectControllerResult SelectControllerImpl(ODataPath odataPath, IWebApiRequestMessage request) + { + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (odataPath.PathTemplate == "~" || + odataPath.PathTemplate == "~/$metadata") + { + return new SelectControllerResult("Metadata", null); + } + + return null; + } + + /// + /// Selects the action for OData requests. + /// + /// The OData path. + /// The controller context. + /// The action map. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected action + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, IWebApiActionMap actionMap) + { + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + if (controllerContext == null) + { + throw Error.ArgumentNull("controllerContext"); + } + + if (actionMap == null) + { + throw Error.ArgumentNull("actionMap"); + } + + if (odataPath.PathTemplate == "~") + { + return "GetServiceDocument"; + } + + if (odataPath.PathTemplate == "~/$metadata") + { + return "GetMetadata"; + } + + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationRoutingConvention.cs new file mode 100644 index 0000000..e498924 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationRoutingConvention.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles navigation properties. + /// + public partial class NavigationRoutingConvention : NavigationSourceRoutingConvention + { + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, IWebApiActionMap actionMap) + { + ODataRequestMethod method = controllerContext.Request.Method; + string actionNamePrefix = GetActionMethodPrefix(method); + if (actionNamePrefix == null) + { + return null; + } + + if (odataPath.PathTemplate == "~/entityset/key/navigation" || + odataPath.PathTemplate == "~/entityset/key/navigation/$count" || + odataPath.PathTemplate == "~/entityset/key/cast/navigation" || + odataPath.PathTemplate == "~/entityset/key/cast/navigation/$count" || + odataPath.PathTemplate == "~/singleton/navigation" || + odataPath.PathTemplate == "~/singleton/navigation/$count" || + odataPath.PathTemplate == "~/singleton/cast/navigation" || + odataPath.PathTemplate == "~/singleton/cast/navigation/$count") + { + NavigationPropertySegment navigationSegment = + (odataPath.Segments.Last() as NavigationPropertySegment) ?? + odataPath.Segments[odataPath.Segments.Count - 2] as NavigationPropertySegment; + IEdmNavigationProperty navigationProperty = navigationSegment.NavigationProperty; + IEdmEntityType declaringType = navigationProperty.DeclaringType as IEdmEntityType; + + // It is not valid to *Post* to any non-collection valued navigation property. + if (navigationProperty.TargetMultiplicity() != EdmMultiplicity.Many && + ODataRequestMethod.Post == method) + { + return null; + } + + // It is not valid to *Put/Patch" to any collection-valued navigation property. + if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many && + (ODataRequestMethod.Put == method || ODataRequestMethod.Patch == method)) + { + return null; + } + + // *Get* is the only supported method for $count request. + if (odataPath.Segments.Last() is CountSegment && ODataRequestMethod.Get != method) + { + return null; + } + + if (declaringType != null) + { + // e.g. Try GetNavigationPropertyFromDeclaringType first, then fallback on GetNavigationProperty action name + string actionName = actionMap.FindMatchingAction( + actionNamePrefix + navigationProperty.Name + "From" + declaringType.Name, + actionNamePrefix + navigationProperty.Name); + + if (actionName != null) + { + if (odataPath.PathTemplate.StartsWith("~/entityset/key", StringComparison.Ordinal)) + { + KeySegment keyValueSegment = (KeySegment)odataPath.Segments[1]; + controllerContext.AddKeyValueToRouteData(keyValueSegment); + } + + return actionName; + } + } + } + + return null; + } + + private static string GetActionMethodPrefix(ODataRequestMethod method) + { + switch (method) + { + case ODataRequestMethod.Get: + return "Get"; + case ODataRequestMethod.Post: + return "PostTo"; + case ODataRequestMethod.Put: + return "PutTo"; + case ODataRequestMethod.Patch: + return "PatchTo"; + default: + return null; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationSourceRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationSourceRoutingConvention.cs new file mode 100644 index 0000000..b77305a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/NavigationSourceRoutingConvention.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles navigation sources + /// (entity sets or singletons) + /// + public abstract partial class NavigationSourceRoutingConvention + { + /// + /// Selects the controller for OData requests. + /// + /// The OData path. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected controller + /// + internal static SelectControllerResult SelectControllerImpl(ODataPath odataPath) + { + // entity set + EntitySetSegment entitySetSegment = odataPath.Segments.FirstOrDefault() as EntitySetSegment; + if (entitySetSegment != null) + { + return new SelectControllerResult(entitySetSegment.EntitySet.Name, null); + } + + // singleton + SingletonSegment singletonSegment = odataPath.Segments.FirstOrDefault() as SingletonSegment; + if (singletonSegment != null) + { + return new SelectControllerResult(singletonSegment.Singleton.Name, null); + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/PropertyRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/PropertyRoutingConvention.cs new file mode 100644 index 0000000..707440d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/PropertyRoutingConvention.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles reading structural properties. + /// + public partial class PropertyRoutingConvention : NavigationSourceRoutingConvention + { + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, IWebApiActionMap actionMap) + { + string prefix; + TypeSegment cast; + IEdmProperty property = GetProperty(odataPath, controllerContext.Request.Method, out prefix, out cast); + IEdmEntityType declaringType = property == null ? null : property.DeclaringType as IEdmEntityType; + + if (declaringType != null) + { + string actionName; + if (cast == null) + { + actionName = actionMap.FindMatchingAction( + prefix + property.Name + "From" + declaringType.Name, + prefix + property.Name); + } + else + { + IEdmComplexType typeCast; + if (cast.EdmType.TypeKind == EdmTypeKind.Collection) + { + typeCast = ((IEdmCollectionType)cast.EdmType).ElementType.AsComplex().ComplexDefinition(); + } + else + { + typeCast = (IEdmComplexType)cast.EdmType; + } + + // for example: GetCityOfSubAddressFromVipCustomer or GetCityOfSubAddress + actionName = actionMap.FindMatchingAction( + prefix + property.Name + "Of" + typeCast.Name + "From" + declaringType.Name, + prefix + property.Name + "Of" + typeCast.Name); + } + + if (actionName != null) + { + if (odataPath.PathTemplate.StartsWith("~/entityset/key", StringComparison.Ordinal)) + { + KeySegment keyValueSegment = (KeySegment)odataPath.Segments[1]; + controllerContext.AddKeyValueToRouteData(keyValueSegment); + } + + return actionName; + } + } + + return null; + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "These are simple conversion function and cannot be split up.")] + private static IEdmProperty GetProperty(ODataPath odataPath, ODataRequestMethod method, out string prefix, + out TypeSegment cast) + { + prefix = String.Empty; + cast = null; + PropertySegment segment = null; + + if (odataPath.PathTemplate == "~/entityset/key/property" || + odataPath.PathTemplate == "~/entityset/key/cast/property" || + odataPath.PathTemplate == "~/singleton/property" || + odataPath.PathTemplate == "~/singleton/cast/property") + { + PropertySegment tempSegment = + (PropertySegment)odataPath.Segments[odataPath.Segments.Count - 1]; + + switch (method) + { + case ODataRequestMethod.Get: + prefix = "Get"; + segment = tempSegment; + break; + case ODataRequestMethod.Post: + //Allow post only to collection properties + if (tempSegment.Property.Type.IsCollection()) + { + prefix = "PostTo"; + segment = tempSegment; + } + break; + case ODataRequestMethod.Put: + prefix = "PutTo"; + segment = tempSegment; + break; + case ODataRequestMethod.Patch: + // OData Spec: PATCH is not supported for collection properties. + if (!tempSegment.Property.Type.IsCollection()) + { + prefix = "PatchTo"; + segment = tempSegment; + } + break; + case ODataRequestMethod.Delete: + // OData spec: A successful DELETE request to the edit URL for a structural property, ... sets the property to null. + // The request body is ignored and should be empty. + // DELETE request to a non-nullable value MUST fail and the service respond with 400 Bad Request or other appropriate error. + if (tempSegment.Property.Type.IsNullable) + { + prefix = "DeleteTo"; + segment = tempSegment; + } + break; + } + } + else if (odataPath.PathTemplate == "~/entityset/key/property/cast" || + odataPath.PathTemplate == "~/entityset/key/cast/property/cast" || + odataPath.PathTemplate == "~/singleton/property/cast" || + odataPath.PathTemplate == "~/singleton/cast/property/cast") + { + PropertySegment tempSegment = + (PropertySegment)odataPath.Segments[odataPath.Segments.Count - 2]; + TypeSegment tempCast = (TypeSegment)odataPath.Segments.Last(); + switch (method) + { + case ODataRequestMethod.Get: + prefix = "Get"; + segment = tempSegment; + cast = tempCast; + break; + case ODataRequestMethod.Post: + //Allow post only to collection properties + if (tempSegment.Property.Type.IsCollection()) + { + prefix = "PostTo"; + segment = tempSegment; + cast = tempCast; + } + break; + case ODataRequestMethod.Put: + prefix = "PutTo"; + segment = tempSegment; + cast = tempCast; + break; + case ODataRequestMethod.Patch: + // PATCH is not supported for collection properties. + if (!tempSegment.Property.Type.IsCollection()) + { + prefix = "PatchTo"; + segment = tempSegment; + cast = tempCast; + } + break; + } + } + else if (odataPath.PathTemplate == "~/entityset/key/property/$value" || + odataPath.PathTemplate == "~/entityset/key/cast/property/$value" || + odataPath.PathTemplate == "~/singleton/property/$value" || + odataPath.PathTemplate == "~/singleton/cast/property/$value" || + odataPath.PathTemplate == "~/entityset/key/property/$count" || + odataPath.PathTemplate == "~/entityset/key/cast/property/$count" || + odataPath.PathTemplate == "~/singleton/property/$count" || + odataPath.PathTemplate == "~/singleton/cast/property/$count") + { + PropertySegment tempSegment = (PropertySegment)odataPath.Segments[odataPath.Segments.Count - 2]; + switch (method) + { + case ODataRequestMethod.Get: + prefix = "Get"; + segment = tempSegment; + break; + } + } + + return segment == null ? null : segment.Property; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RefRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RefRoutingConvention.cs new file mode 100644 index 0000000..c41caa3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RefRoutingConvention.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles entity reference manipulations. + /// + public partial class RefRoutingConvention : NavigationSourceRoutingConvention + { + private const string DeleteRefActionNamePrefix = "DeleteRef"; + private const string CreateRefActionNamePrefix = "CreateRef"; + private const string GetRefActionNamePrefix = "GetRef"; + + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, IWebApiActionMap actionMap) + { + ODataRequestMethod requestMethod = controllerContext.Request.Method; + + if (!IsSupportedRequestMethod(requestMethod)) + { + return null; + } + + if (odataPath.PathTemplate == "~/entityset/key/navigation/$ref" || + odataPath.PathTemplate == "~/entityset/key/cast/navigation/$ref" || + odataPath.PathTemplate == "~/singleton/navigation/$ref" || + odataPath.PathTemplate == "~/singleton/cast/navigation/$ref") + { + NavigationPropertyLinkSegment navigationLinkSegment = (NavigationPropertyLinkSegment)odataPath.Segments.Last(); + IEdmNavigationProperty navigationProperty = navigationLinkSegment.NavigationProperty; + IEdmEntityType declaringType = navigationProperty.DeclaringEntityType(); + + string refActionName = FindRefActionName(actionMap, navigationProperty, declaringType, requestMethod); + if (refActionName != null) + { + if (odataPath.PathTemplate.StartsWith("~/entityset/key", StringComparison.Ordinal)) + { + controllerContext.AddKeyValueToRouteData((KeySegment)odataPath.Segments[1]); + } + + controllerContext.RouteData.Add(ODataRouteConstants.NavigationProperty, navigationLinkSegment.NavigationProperty.Name); + return refActionName; + } + } + else if ((ODataRequestMethod.Delete == requestMethod) && ( + odataPath.PathTemplate == "~/entityset/key/navigation/key/$ref" || + odataPath.PathTemplate == "~/entityset/key/cast/navigation/key/$ref" || + odataPath.PathTemplate == "~/singleton/navigation/key/$ref" || + odataPath.PathTemplate == "~/singleton/cast/navigation/key/$ref")) + { + // the second key segment is the last segment in the path. + // So the previous of last segment is the navigation property link segment. + NavigationPropertyLinkSegment navigationLinkSegment = (NavigationPropertyLinkSegment)odataPath.Segments[odataPath.Segments.Count - 2]; + IEdmNavigationProperty navigationProperty = navigationLinkSegment.NavigationProperty; + IEdmEntityType declaringType = navigationProperty.DeclaringEntityType(); + + string refActionName = FindRefActionName(actionMap, navigationProperty, declaringType, requestMethod); + if (refActionName != null) + { + if (odataPath.PathTemplate.StartsWith("~/entityset/key", StringComparison.Ordinal)) + { + controllerContext.AddKeyValueToRouteData((KeySegment)odataPath.Segments[1]); + } + + controllerContext.RouteData.Add(ODataRouteConstants.NavigationProperty, navigationLinkSegment.NavigationProperty.Name); + controllerContext.AddKeyValueToRouteData((KeySegment)odataPath.Segments.Last(e => e is KeySegment), ODataRouteConstants.RelatedKey); + return refActionName; + } + } + + return null; + } + + private static string FindRefActionName(IWebApiActionMap actionMap, + IEdmNavigationProperty navigationProperty, IEdmEntityType declaringType, ODataRequestMethod method) + { + string actionNamePrefix; + switch (method) + { + case ODataRequestMethod.Delete: + actionNamePrefix = DeleteRefActionNamePrefix; + break; + + case ODataRequestMethod.Get: + actionNamePrefix = GetRefActionNamePrefix; + break; + + default: + actionNamePrefix = CreateRefActionNamePrefix; + break; + } + + // Examples: CreateRefToOrdersFromCustomer, CreateRefToOrders, CreateRef. + return actionMap.FindMatchingAction( + actionNamePrefix + "To" + navigationProperty.Name + "From" + declaringType.Name, + actionNamePrefix + "To" + navigationProperty.Name, + actionNamePrefix); + } + + private static bool IsSupportedRequestMethod(ODataRequestMethod method) + { + return (ODataRequestMethod.Delete == method || + ODataRequestMethod.Put == method || + ODataRequestMethod.Post == method || + ODataRequestMethod.Get == method); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RoutingConventionHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RoutingConventionHelpers.cs new file mode 100644 index 0000000..e819104 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/RoutingConventionHelpers.cs @@ -0,0 +1,355 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + internal static class RoutingConventionHelpers + { + public static string SelectAction(this IEdmOperation operation, IWebApiActionMap actionMap, bool isCollection) + { + Contract.Assert(actionMap != null); + + if (operation == null) + { + return null; + } + + // The binding parameter is the first parameter by convention + IEdmOperationParameter bindingParameter = operation.Parameters.FirstOrDefault(); + if (operation.IsBound && bindingParameter != null) + { + IEdmEntityType entityType = null; + if (!isCollection) + { + entityType = bindingParameter.Type.Definition as IEdmEntityType; + } + else + { + IEdmCollectionType bindingParameterType = bindingParameter.Type.Definition as IEdmCollectionType; + if (bindingParameterType != null) + { + entityType = bindingParameterType.ElementType.Definition as IEdmEntityType; + } + } + + if (entityType == null) + { + return null; + } + + string targetActionName = isCollection + ? operation.Name + "OnCollectionOf" + entityType.Name + : operation.Name + "On" + entityType.Name; + return actionMap.FindMatchingAction(targetActionName, operation.Name); + } + + return null; + } + + public static bool TryMatch( + IDictionary templateParameters, + IDictionary parameters, + IDictionary matches) + { + Contract.Assert(templateParameters != null); + Contract.Assert(parameters != null); + Contract.Assert(matches != null); + + if (templateParameters.Count != parameters.Count) + { + return false; + } + + Dictionary routeData = new Dictionary(); + foreach (KeyValuePair templateParameter in templateParameters) + { + string nameInSegment = templateParameter.Key; + + object value; + if (!parameters.TryGetValue(nameInSegment, out value)) + { + // parameter not found. not a match. + return false; + } + + string nameInRouteData = templateParameter.Value; + routeData.Add(nameInRouteData, value); + } + + foreach (KeyValuePair kvp in routeData) + { + matches[kvp.Key] = kvp.Value; + } + + return true; + } + + public static bool TryMatch(this KeySegment keySegment, IDictionary mapping, IDictionary values) + { + Contract.Assert(keySegment != null); + Contract.Assert(mapping != null); + Contract.Assert(values != null); + + if (keySegment.Keys.Count() != mapping.Count) + { + return false; + } + + IEdmEntityType entityType = keySegment.EdmType as IEdmEntityType; + + Dictionary routeData = new Dictionary(); + foreach (KeyValuePair key in keySegment.Keys) + { + string mappingName; + if (!mapping.TryGetValue(key.Key, out mappingName)) + { + // mapping name is not found. it's not a match. + return false; + } + + IEdmTypeReference typeReference; + // get the key property from the entity type + if (entityType != null) + { + IEdmStructuralProperty keyProperty = entityType.Key().FirstOrDefault(k => k.Name == key.Key); + if (keyProperty == null) + { + // If it's an alternate key + keyProperty = entityType.Properties().OfType().FirstOrDefault(p => p.Name == key.Key); + } + + Contract.Assert(keyProperty != null); + typeReference = keyProperty.Type; + } + else + { + typeReference = EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(key.Value.GetType()); + } + + AddKeyValues(mappingName, key.Value, typeReference, routeData, routeData); + } + + foreach (KeyValuePair kvp in routeData) + { + values[kvp.Key] = kvp.Value; + } + + return true; + } + + public static void AddKeyValueToRouteData(this IWebApiControllerContext controllerContext, KeySegment segment, string keyName = "key") + { + Contract.Assert(controllerContext != null); + Contract.Assert(segment != null); + + IDictionary routingConventionsStore = controllerContext.Request.Context.RoutingConventionsStore; + + IEdmEntityType entityType = segment.EdmType as IEdmEntityType; + Contract.Assert(entityType != null); + + int keyCount = segment.Keys.Count(); + foreach (var keyValuePair in segment.Keys) + { + bool alternateKey = false; + // get the key property from the entity type + IEdmStructuralProperty keyProperty = entityType.Key().FirstOrDefault(k => k.Name == keyValuePair.Key); + if (keyProperty == null) + { + // If it's alternate key. + keyProperty = entityType.Properties().OfType().FirstOrDefault(p => p.Name == keyValuePair.Key); + alternateKey = true; + } + Contract.Assert(keyProperty != null); + + // if there's only one key, just use the given key name, for example: "key, relatedKey" + // otherwise, to append the key name after the given key name. + // so for multiple keys, the parameter name is "keyId1, keyId2..." + // for navigation property, the parameter name is "relatedKeyId1, relatedKeyId2 ..." + string newKeyName; + if (alternateKey || keyCount > 1) + { + newKeyName = keyName + keyValuePair.Key; + } + else + { + newKeyName = keyName; + } + + AddKeyValues(newKeyName, keyValuePair.Value, keyProperty.Type, controllerContext.RouteData, routingConventionsStore); + } + } + + private static void AddKeyValues(string name, object value, IEdmTypeReference edmTypeReference, IDictionary routeValues, + IDictionary odataValues) + { + Contract.Assert(routeValues != null); + Contract.Assert(odataValues != null); + + object routeValue = null; + object odataValue = null; + ConstantNode node = value as ConstantNode; + if (node != null) + { + ODataEnumValue enumValue = node.Value as ODataEnumValue; + if (enumValue != null) + { + odataValue = new ODataParameterValue(enumValue, edmTypeReference); + routeValue = enumValue.Value; + } + } + else + { + odataValue = new ODataParameterValue(value, edmTypeReference); + routeValue = value; + } + + // for without FromODataUri + routeValues[name] = routeValue; + + // For FromODataUri + string prefixName = ODataParameterValue.ParameterValuePrefix + name; + odataValues[prefixName] = odataValue; + } + + public static void AddFunctionParameterToRouteData(this IWebApiControllerContext controllerContext, OperationSegment functionSegment) + { + Contract.Assert(controllerContext != null); + Contract.Assert(functionSegment != null); + + IDictionary routingConventionsStore = controllerContext.Request.Context.RoutingConventionsStore; + + IEdmFunction function = functionSegment.Operations.First() as IEdmFunction; + if (function == null) + { + return; + } + + foreach (OperationSegmentParameter parameter in functionSegment.Parameters) + { + string name = parameter.Name; + object value = functionSegment.GetParameterValue(name); + + AddFunctionParameters(function, name, value, controllerContext.RouteData, + routingConventionsStore, null); + } + + // Append the optional parameters into RouteData. + ODataOptionalParameter optional = new ODataOptionalParameter(); + foreach (var optionalParameter in function.Parameters.OfType()) + { + if (!functionSegment.Parameters.Any(c => c.Name == optionalParameter.Name)) + { + optional.Add(optionalParameter); + } + } + + if (optional.OptionalParameters.Any()) + { + controllerContext.RouteData.Add(ODataRouteConstants.OptionalParameters, optional); + } + } + + public static void AddFunctionParameters(IEdmFunction function, string paramName, object paramValue, + IDictionary routeData, IDictionary values, IDictionary paramMapping) + { + Contract.Assert(function != null); + + // using the following codes to support [FromODataUriAttribute] + IEdmOperationParameter edmParam = function.FindParameter(paramName); + Contract.Assert(edmParam != null); + ODataParameterValue parameterValue = new ODataParameterValue(paramValue, edmParam.Type); + + string name = paramName; + if (paramMapping != null) + { + Contract.Assert(paramMapping.ContainsKey(paramName)); + name = paramMapping[paramName]; + } + + string prefixName = ODataParameterValue.ParameterValuePrefix + name; + values[prefixName] = parameterValue; + + // using the following codes to support [FromUriAttribute] + if (!routeData.ContainsKey(name)) + { + routeData.Add(name, paramValue); + } + + ODataNullValue nullValue = paramValue as ODataNullValue; + if (nullValue != null) + { + routeData[name] = null; + } + + ODataEnumValue enumValue = paramValue as ODataEnumValue; + if (enumValue != null) + { + // Remove the type name of the ODataEnumValue and keep the value. + routeData[name] = enumValue.Value; + } + } + + public static IDictionary BuildParameterMappings( + IEnumerable parameters, string segment) + { + Contract.Assert(parameters != null); + + Dictionary parameterMappings = new Dictionary(); + + foreach (OperationSegmentParameter parameter in parameters) + { + string parameterName = parameter.Name; + string nameInRouteData = null; + + ConstantNode node = parameter.Value as ConstantNode; + if (node != null) + { + UriTemplateExpression uriTemplateExpression = node.Value as UriTemplateExpression; + if (uriTemplateExpression != null) + { + nameInRouteData = uriTemplateExpression.LiteralText.Trim(); + } + } + else + { + // Just for easy constructor the function parameters + nameInRouteData = parameter.Value as string; + } + + if (nameInRouteData == null || !IsRouteParameter(nameInRouteData)) + { + throw new ODataException( + Error.Format(SRResources.ParameterAliasMustBeInCurlyBraces, parameter.Value, segment)); + } + + nameInRouteData = nameInRouteData.Substring(1, nameInRouteData.Length - 2); + if (String.IsNullOrEmpty(nameInRouteData)) + { + throw new ODataException( + Error.Format(SRResources.EmptyParameterAlias, parameter.Value, segment)); + } + + parameterMappings[parameterName] = nameInRouteData; + } + + return parameterMappings; + } + + public static bool IsRouteParameter(string parameterName) + { + return parameterName.StartsWith("{", StringComparison.Ordinal) && + parameterName.EndsWith("}", StringComparison.Ordinal); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SelectControllerResult.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SelectControllerResult.cs new file mode 100644 index 0000000..0909724 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SelectControllerResult.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An return value for SelectController. + /// + public class SelectControllerResult + { + /// + /// Initializes a new instance of the class. + /// + /// The controller name selected. + /// The properties associated with the selected controller.4. + public SelectControllerResult(string controllerName, IDictionary values) + { + this.ControllerName = controllerName; + this.Values = values; + } + + /// + /// Gets the controller name selected. + /// + public string ControllerName { get; private set; } + + /// + /// Gets or sets the properties associated with the selected controller. + /// + /// By default, Values is null. + public IDictionary Values { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SingletonRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SingletonRoutingConvention.cs new file mode 100644 index 0000000..eeff414 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/SingletonRoutingConvention.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles the singleton. + /// + public partial class SingletonRoutingConvention : NavigationSourceRoutingConvention + { + /// + internal static string SelectActionImpl(ODataPath odataPath, IWebApiControllerContext controllerContext, + IWebApiActionMap actionMap) + { + if (odataPath.PathTemplate == "~/singleton") + { + SingletonSegment singletonSegment = (SingletonSegment)odataPath.Segments[0]; + string httpMethodName = GetActionNamePrefix(controllerContext.Request.Method); + + if (httpMethodName != null) + { + // e.g. Try Get{SingletonName} first, then fallback on Get action name + return actionMap.FindMatchingAction( + httpMethodName + singletonSegment.Singleton.Name, + httpMethodName); + } + } + else if (odataPath.PathTemplate == "~/singleton/cast") + { + SingletonSegment singletonSegment = (SingletonSegment)odataPath.Segments[0]; + IEdmEntityType entityType = (IEdmEntityType)odataPath.EdmType; + string httpMethodName = GetActionNamePrefix(controllerContext.Request.Method); + + if (httpMethodName != null) + { + // e.g. Try Get{SingletonName}From{EntityTypeName} first, then fallback on Get action name + return actionMap.FindMatchingAction( + httpMethodName + singletonSegment.Singleton.Name + "From" + entityType.Name, + httpMethodName + "From" + entityType.Name); + } + } + + return null; + } + + private static string GetActionNamePrefix(ODataRequestMethod method) + { + string actionNamePrefix; + switch (method) + { + case ODataRequestMethod.Get: + actionNamePrefix = "Get"; + break; + case ODataRequestMethod.Put: + actionNamePrefix = "Put"; + break; + case ODataRequestMethod.Patch: + case ODataRequestMethod.Merge: + actionNamePrefix = "Patch"; + break; + default: + return null; + } + + return actionNamePrefix; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/UnmappedRequestRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/UnmappedRequestRoutingConvention.cs new file mode 100644 index 0000000..f4b6448 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Conventions/UnmappedRequestRoutingConvention.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that always selects the action named HandleUnmappedRequest if that action is present. + /// + public partial class UnmappedRequestRoutingConvention : NavigationSourceRoutingConvention + { + private const string UnmappedRequestActionName = "HandleUnmappedRequest"; + + /// + internal static string SelectActionImpl(IWebApiActionMap actionMap) + { + if (actionMap.Contains(UnmappedRequestActionName)) + { + return UnmappedRequestActionName; + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathHandler.cs new file mode 100644 index 0000000..a9d8393 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathHandler.cs @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing.Template; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODL = Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Parses an OData path as an into an OData link. + /// + public class DefaultODataPathHandler : IODataPathHandler, IODataPathTemplateHandler + { + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not. + /// + public ODataUrlKeyDelimiter UrlKeyDelimiter { get; set; } + + /// + /// Parses the specified OData path as an that contains additional information about the EDM type and entity set for the path. + /// + /// The service root of the OData path. + /// The OData path to parse. + /// The dependency injection container for the request. + /// A parsed representation of the path, or null if the path does not match the model. + public virtual ODataPath Parse(string serviceRoot, string odataPath, IServiceProvider requestContainer) + { + if (serviceRoot == null) + { + throw Error.ArgumentNull("serviceRoot"); + } + + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + if (requestContainer == null) + { + throw Error.ArgumentNull("requestContainer"); + } + + return Parse(serviceRoot, odataPath, requestContainer, template: false); + } + + /// + /// Parses the specified OData path template as an that can be matched to an . + /// + /// The OData path template to parse. + /// The dependency injection container for the request. + /// A parsed representation of the path template, or null if the path does not match the model. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "odata", Justification = "odata is spelled correctly")] + public virtual ODataPathTemplate ParseTemplate(string odataPathTemplate, IServiceProvider requestContainer) + { + if (odataPathTemplate == null) + { + throw Error.ArgumentNull("odataPathTemplate"); + } + + if (requestContainer == null) + { + throw Error.ArgumentNull("requestContainer"); + } + + return Templatify(Parse(serviceRoot: null, odataPath: odataPathTemplate, + requestContainer: requestContainer, template: true), odataPathTemplate); + } + + /// + /// Converts an instance of into an OData link. + /// + /// The OData path to convert into a link. + /// + /// The generated OData link. + /// + public virtual string Link(ODataPath path) + { + if (path == null) + { + throw Error.ArgumentNull("path"); + } + + return path.ToString(); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Function is difficult to split up.")] + private ODataPath Parse(string serviceRoot, string odataPath, IServiceProvider requestContainer, bool template) + { + ODataUriParser uriParser; + Uri serviceRootUri = null; + Uri fullUri = null; + IEdmModel model = requestContainer.GetRequiredService(); + if (template) + { + uriParser = new ODataUriParser(model, new Uri(odataPath, UriKind.Relative), requestContainer); + uriParser.EnableUriTemplateParsing = true; + } + else + { + Contract.Assert(serviceRoot != null); + + serviceRootUri = new Uri( + serviceRoot.EndsWith("/", StringComparison.Ordinal) + ? serviceRoot + : serviceRoot + "/"); + + // Concatenate the root and path and create a Uri. Using Uri to build a Uri from + // a root and relative path changes the casing on .NetCore. However, odataPath may + // be a full Uri. + if (!Uri.TryCreate(odataPath, UriKind.Absolute, out fullUri)) + { + fullUri = new Uri(serviceRootUri + odataPath); + } + + uriParser = new ODataUriParser(model, serviceRootUri, fullUri, requestContainer); + } + + if (UrlKeyDelimiter != null) + { + uriParser.UrlKeyDelimiter = UrlKeyDelimiter; + } + else + { + uriParser.UrlKeyDelimiter = ODataUrlKeyDelimiter.Slash; + } + + ODL.ODataPath path; + UnresolvedPathSegment unresolvedPathSegment = null; + ODL.KeySegment id = null; + try + { + path = uriParser.ParsePath(); + } + catch (ODataUnrecognizedPathException ex) + { + if (ex.ParsedSegments != null && + ex.ParsedSegments.Any() && + (ex.ParsedSegments.Last().EdmType is IEdmComplexType || + ex.ParsedSegments.Last().EdmType is IEdmEntityType) && + ex.CurrentSegment != ODataSegmentKinds.Count) + { + if (!ex.UnparsedSegments.Any()) + { + path = new ODL.ODataPath(ex.ParsedSegments); + unresolvedPathSegment = new UnresolvedPathSegment(ex.CurrentSegment); + } + else + { + // Throw ODataException if there is some segment following the unresolved segment. + throw new ODataException(Error.Format( + SRResources.InvalidPathSegment, + ex.UnparsedSegments.First(), + ex.CurrentSegment)); + } + } + else + { + throw; + } + } + + if (!template && path.LastSegment is ODL.NavigationPropertyLinkSegment) + { + IEdmCollectionType lastSegmentEdmType = path.LastSegment.EdmType as IEdmCollectionType; + + if (lastSegmentEdmType != null) + { + ODL.EntityIdSegment entityIdSegment = null; + bool exceptionThrown = false; + + try + { + entityIdSegment = uriParser.ParseEntityId(); + + if (entityIdSegment != null) + { + // Create another ODataUriParser to parse $id, which is absolute or relative. + ODataUriParser parser = new ODataUriParser(model, serviceRootUri, entityIdSegment.Id, requestContainer); + id = parser.ParsePath().LastSegment as ODL.KeySegment; + } + } + catch (ODataException) + { + // Exception was thrown while parsing the $id. + // We will throw another exception about the invalid $id. + exceptionThrown = true; + } + + if (exceptionThrown || + (entityIdSegment != null && + (id == null || + !(id.EdmType.IsOrInheritsFrom(lastSegmentEdmType.ElementType.Definition) || + lastSegmentEdmType.ElementType.Definition.IsOrInheritsFrom(id.EdmType))))) + { + // System.Net.Http on NetCore does not have the Uri extension method + // ParseQueryString(), to avoid a platform-specific call, extract $id manually. + string idValue = fullUri.Query; + string idParam = "$id="; + int start = idValue.IndexOf(idParam, StringComparison.OrdinalIgnoreCase); + if (start >= 0) + { + int end = idValue.IndexOf("&", start, StringComparison.OrdinalIgnoreCase); + if (end >= 0) + { + idValue = idValue.Substring(start + idParam.Length, end - 1); + } + else + { + idValue = idValue.Substring(start + idParam.Length); + } + } + + throw new ODataException(Error.Format(SRResources.InvalidDollarId, idValue)); + } + } + } + + // do validation for the odata path + path.WalkWith(new DefaultODataPathValidator(model)); + + // do segment translator (for example parameter alias, key & function parameter template, etc) + var segments = + ODataPathSegmentTranslator.Translate(model, path, uriParser.ParameterAliasNodes).ToList(); + + if (unresolvedPathSegment != null) + { + segments.Add(unresolvedPathSegment); + } + + if (!template) + { + AppendIdForRef(segments, id); + } + + return new ODataPath(segments) + { + Path = path + }; + } + + // We need to append the key value path segment from $id. + private static void AppendIdForRef(IList segments, ODL.KeySegment id) + { + if (id == null || !(segments.Last() is ODL.NavigationPropertyLinkSegment)) + { + return; + } + + segments.Add(id); + } + + private static ODataPathTemplate Templatify(ODataPath path, string pathTemplate) + { + if (path == null) + { + throw new ODataException(Error.Format(SRResources.InvalidODataPathTemplate, pathTemplate)); + } + + ODataPathSegmentTemplateTranslator translator = new ODataPathSegmentTemplateTranslator(); + var newPath = path.Segments.Select(e => + { + UnresolvedPathSegment unresolvedPathSegment = e as UnresolvedPathSegment; + if (unresolvedPathSegment != null) + { + throw new ODataException( + Error.Format(SRResources.UnresolvedPathSegmentInTemplate, unresolvedPathSegment, pathTemplate)); + } + + return e.TranslateWith(translator); + }); + + return new ODataPathTemplate(newPath); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathValidator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathValidator.cs new file mode 100644 index 0000000..3b82f79 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/DefaultODataPathValidator.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Validator an OData path by walking throw all its segments. + /// + public class DefaultODataPathValidator : PathSegmentHandler + { + private readonly IEdmModel _edmModel; + + /// + /// Initializes a new instance of the class. + /// + public DefaultODataPathValidator(IEdmModel model) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + _edmModel = model; + } + + /// + /// Handle a EntitySetSegment + /// + /// the segment to handle + public override void Handle(EntitySetSegment segment) + { + } + + /// + /// Handle a KeySegment + /// + /// the segment to handle + public override void Handle(KeySegment segment) + { + } + + /// + /// Handle a NavigationPropertyLinkSegment + /// + /// the segment to Handle + public override void Handle(NavigationPropertyLinkSegment segment) + { + if (EdmLibHelpers.IsNotNavigable(segment.NavigationProperty, _edmModel)) + { + throw new ODataException(Error.Format( + SRResources.NotNavigablePropertyUsedInNavigation, + segment.NavigationProperty.Name)); + } + } + + /// + /// Handle a NavigationPropertySegment + /// + /// the segment to Handle + public override void Handle(NavigationPropertySegment segment) + { + if (EdmLibHelpers.IsNotNavigable(segment.NavigationProperty, _edmModel)) + { + throw new ODataException(Error.Format( + SRResources.NotNavigablePropertyUsedInNavigation, + segment.NavigationProperty.Name)); + } + } + + /// + /// Handle a OpenPropertySegment + /// + /// the segment to Handle + public override void Handle(DynamicPathSegment segment) + { + } + + /// + /// Handle a OperationImportSegment + /// + /// the segment to Handle + public override void Handle(OperationImportSegment segment) + { + } + + /// + /// Handle an OperationSegment + /// + /// the segment to handle + public override void Handle(OperationSegment segment) + { + } + + /// + /// Handle a PropertySegment + /// + /// the segment to handle + public override void Handle(PathTemplateSegment segment) + { + string value; + switch (segment.TranslatePathTemplateSegment(out value)) + { + case ODataSegmentKinds.DynamicProperty: + break; + default: + throw new ODataException(Error.Format( + SRResources.InvalidAttributeRoutingTemplateSegment, + segment.LiteralText)); + } + } + + /// + /// Handle a PropertySegment + /// + /// the segment to handle + public override void Handle(PropertySegment segment) + { + } + + /// + /// Handle a SingletonSegment + /// + /// the segment to handle + public override void Handle(SingletonSegment segment) + { + } + + /// + /// Handle a TypeSegment, we use "cast" for type segment. + /// + /// the segment to handle + public override void Handle(TypeSegment segment) + { + } + + /// + /// Handle a ValueSegment + /// + /// the segment to handle + public override void Handle(ValueSegment segment) + { + } + + /// + /// Handle a CountSegment + /// + /// the segment to handle + public override void Handle(CountSegment segment) + { + } + + /// + /// Handle a BatchSegment + /// + /// the segment to handle + public override void Handle(BatchSegment segment) + { + } + + /// + /// Handle a MetadataSegment + /// + /// the segment to handle + public override void Handle(MetadataSegment segment) + { + } + + /// + /// Handle a UnresolvedPathSegment + /// + /// the segment to handle + public virtual void Handle(UnresolvedPathSegment segment) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathHandler.cs new file mode 100644 index 0000000..7eaaa46 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathHandler.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Exposes the ability to parse an OData path as an and convert an into an OData link. + /// + public interface IODataPathHandler + { + /// + /// Parses the specified OData path as an that contains additional information about the EDM type and entity set for the path. + /// + /// The service root of the OData path. + /// The OData path to parse. + /// The dependency injection container for the request. + /// A parsed representation of the URI, or null if the URI does not match the model. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "odata", Justification = "odata is spelled correctly")] + ODataPath Parse(string serviceRoot, string odataPath, IServiceProvider requestContainer); + + /// + /// Converts an instance of into an OData link. + /// + /// The OData path to convert into a link. + /// The generated OData link. + string Link(ODataPath path); + + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not. + /// + ODataUrlKeyDelimiter UrlKeyDelimiter { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathTemplateHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathTemplateHandler.cs new file mode 100644 index 0000000..947070e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/IODataPathTemplateHandler.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.OData.Routing.Template; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Exposes the ability to parse an OData path template as an . + /// + public interface IODataPathTemplateHandler + { + /// + /// Parses the specified OData path template as an . + /// + /// The OData path template to parse. + /// The dependency injection container for the request. + /// A parsed representation of the template, or null if the template does not match the model. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "odata", Justification = "odata is spelled correctly")] + ODataPathTemplate ParseTemplate(string odataPathTemplate, IServiceProvider requestContainer); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/KeyValueParser.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/KeyValueParser.cs new file mode 100644 index 0000000..06dac87 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/KeyValueParser.cs @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Parses the OData multi-key value (same for function call parameters) as a dictionary. + /// + internal static class KeyValueParser + { + private static readonly Regex _stringLiteralRegex = new Regex(@"^'([^']|'')*'$", RegexOptions.Compiled); + + // TODO 1656: Make this method support more format in OData Uri BNF + public static Dictionary ParseKeys(string segment) + { + Dictionary dictionary = new Dictionary(); + int currentIndex = 0; + int startIndex = 0; + + while (currentIndex < segment.Length) + { + if (segment[currentIndex] == '=') + { + string key = segment.Substring(startIndex, currentIndex - startIndex); + + if (String.IsNullOrWhiteSpace(key)) + { + throw new ODataException( + Error.Format(SRResources.NoKeyNameFoundInSegment, startIndex, segment)); + } + + // Simple key which contains '='. + if (key.Contains("'")) + { + if (dictionary.Count != 0) + { + throw new ODataException( + Error.Format(SRResources.NoKeyNameFoundInSegment, startIndex, segment)); + } + + CheckSingleQuote(segment, segment); + dictionary.Add(String.Empty, segment); + return dictionary; + } + + currentIndex++; + startIndex = currentIndex; + + while (currentIndex <= segment.Length) + { + if (currentIndex == segment.Length || segment[currentIndex] == ',') + { + string value = segment.Substring(startIndex, currentIndex - startIndex); + + if (String.IsNullOrWhiteSpace(value)) + { + throw new ODataException( + Error.Format(SRResources.NoValueLiteralFoundInSegment, key, startIndex, segment)); + } + + if (dictionary.ContainsKey(key)) + { + throw new ODataException(Error.Format(SRResources.DuplicateKeyInSegment, key, segment)); + } + + CheckSingleQuote(value, segment); + dictionary.Add(key, value); + startIndex = currentIndex + 1; + break; + } + + if (segment[currentIndex] == '\'') + { + currentIndex++; + while (currentIndex <= segment.Length) + { + if (currentIndex == segment.Length) + { + throw new ODataException( + Error.Format(SRResources.UnterminatedStringLiteral, startIndex, segment)); + } + + if (segment[currentIndex] == '\'') + { + if (currentIndex + 1 == segment.Length) + { + break; + } + + if (segment[currentIndex + 1] == '\'') + { + currentIndex++; + } + else + { + break; + } + } + + currentIndex++; + } + } + + currentIndex++; + } + } + + currentIndex++; + } + + // Simple key. + if (dictionary.Count == 0 && !String.IsNullOrWhiteSpace(segment)) + { + CheckSingleQuote(segment, segment); + dictionary.Add(String.Empty, segment); + } + + return dictionary; + } + + private static void CheckSingleQuote(string value, string segment) + { + if (value.StartsWith("'", StringComparison.Ordinal)) + { + // String literal + if (!_stringLiteralRegex.IsMatch(value)) + { + throw new ODataException( + Error.Format(SRResources.LiteralHasABadFormat, value, segment)); + } + } + else + { + int singleQuoteCount = value.Count(c => c == '\''); + + if (singleQuoteCount != 0 && singleQuoteCount != 2) + { + throw new ODataException( + Error.Format(SRResources.InvalidSingleQuoteCountForNonStringLiteral, value, segment)); + } + + if (singleQuoteCount != 0 && !value.EndsWith("'", StringComparison.Ordinal)) + { + throw new ODataException( + Error.Format(SRResources.LiteralHasABadFormat, value, segment)); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataOptionalParameter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataOptionalParameter.cs new file mode 100644 index 0000000..82431b2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataOptionalParameter.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Routing +{ + internal class ODataOptionalParameter + { + private List _optionalParameters = new List(); + + public IReadOnlyList OptionalParameters + { + get + { + return _optionalParameters; + } + } + + public void Add(IEdmOptionalParameter parameter) + { + _optionalParameters.Add(parameter); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterHelper.cs new file mode 100644 index 0000000..0d3d666 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterHelper.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Provides the extension method for odata parameter + /// + public static class ODataParameterHelper + { + /// + /// Gets the parameter value. + /// + /// The function segment + /// The parameter name + /// The parameter value + /// The true ok, false not find. + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Generics not appropriate here")] + public static bool TryGetParameterValue(this OperationSegment segment, string parameterName, out object parameterValue) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + if (String.IsNullOrEmpty(parameterName)) + { + throw Error.ArgumentNullOrEmpty("parameterName"); + } + + parameterValue = null; + OperationSegmentParameter parameter = segment.Parameters.FirstOrDefault(p => p.Name == parameterName); + if (parameter == null) + { + return false; + } + + parameterValue = TranslateNode(parameter.Value); + return true; + } + + /// + /// Gets the parameter value. + /// + /// The function segment + /// The parameter name + /// The parameter value + public static object GetParameterValue(this OperationSegment segment, string parameterName) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + if (String.IsNullOrEmpty(parameterName)) + { + throw Error.ArgumentNullOrEmpty("parameterName"); + } + + if (!segment.Operations.Any() || !segment.Operations.First().IsFunction()) + { + throw Error.Argument("segment", SRResources.OperationSegmentMustBeFunction); + } + + OperationSegmentParameter parameter = segment.Parameters.FirstOrDefault(p => p.Name == parameterName); + if (parameter == null) + { + throw Error.Argument("parameterName", SRResources.FunctionParameterNotFound, parameterName); + } + + object value = TranslateNode(parameter.Value); + + if (value == null || value is ODataNullValue) + { + IEdmOperation operation = segment.Operations.First(); + IEdmOperationParameter operationParameter = operation.Parameters.First(p => p.Name == parameterName); + Contract.Assert(operationParameter != null); + + if (!operationParameter.Type.IsNullable) + { + throw new ODataException(String.Format(CultureInfo.InvariantCulture, + SRResources.NullOnNonNullableFunctionParameter, operationParameter.Type.FullName())); + } + } + + return value; + } + + /// + /// Gets the parameter value. + /// + /// The function segment + /// The parameter name + /// The parameter value + /// The true ok, false not find. + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Generics not appropriate here")] + public static bool TryGetParameterValue(this OperationImportSegment segment, string parameterName, out object parameterValue) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + if (String.IsNullOrEmpty(parameterName)) + { + throw Error.ArgumentNullOrEmpty("parameterName"); + } + + parameterValue = null; + OperationSegmentParameter parameter = segment.Parameters.FirstOrDefault(p => p.Name == parameterName); + if (parameter == null) + { + return false; + } + + parameterValue = TranslateNode(parameter.Value); + return true; + } + + /// + /// Gets the parameter value. + /// + /// The function import segment + /// The parameter name + /// The parameter value + public static object GetParameterValue(this OperationImportSegment segment, string parameterName) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + if (String.IsNullOrEmpty(parameterName)) + { + throw Error.ArgumentNullOrEmpty("parameterName"); + } + + if (!segment.OperationImports.Any() || !segment.OperationImports.First().IsFunctionImport()) + { + throw Error.Argument("segment", SRResources.OperationImportSegmentMustBeFunction); + } + + OperationSegmentParameter parameter = segment.Parameters.FirstOrDefault(p => p.Name == parameterName); + if (parameter == null) + { + throw Error.Argument("parameterName", SRResources.FunctionParameterNotFound, parameterName); + } + + object value = TranslateNode(parameter.Value); + + if (value == null || value is ODataNullValue) + { + IEdmOperationImport operation = segment.OperationImports.First(); + IEdmOperationParameter operationParameter = operation.Operation.Parameters.First(p => p.Name == parameterName); + Contract.Assert(operationParameter != null); + + if (!operationParameter.Type.IsNullable) + { + throw new ODataException(String.Format(CultureInfo.InvariantCulture, + SRResources.NullOnNonNullableFunctionParameter, operationParameter.Type.FullName())); + } + } + + return value; + } + + internal static object TranslateNode(object value) + { + if (value == null) + { + return null; + } + + ConstantNode node = value as ConstantNode; + if (node != null) + { + return node.Value; + } + + ConvertNode convertNode = value as ConvertNode; + if (convertNode != null) + { + object source = TranslateNode(convertNode.Source); + return source; + } + + ParameterAliasNode parameterAliasNode = value as ParameterAliasNode; + if (parameterAliasNode != null) + { + return parameterAliasNode.Alias; + } + + throw Error.NotSupported(SRResources.CannotRecognizeNodeType, typeof(ODataParameterHelper), value.GetType().FullName); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterValue.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterValue.cs new file mode 100644 index 0000000..19e0f5b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataParameterValue.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Routing +{ + internal class ODataParameterValue + { + // This prefix is used to identify parameters in [FromODataUri] binding scenario. + public const string ParameterValuePrefix = "DF908045-6922-46A0-82F2-2F6E7F43D1B1_"; + + public ODataParameterValue(object paramValue, IEdmTypeReference paramType) + { + if (paramType == null) + { + throw Error.ArgumentNull("paramType"); + } + + Value = paramValue; + EdmType = paramType; + } + + public IEdmTypeReference EdmType { get; private set; } + + public object Value { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPath.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPath.cs new file mode 100644 index 0000000..0acf3a8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPath.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using ODataPathSegment = Microsoft.OData.UriParser.ODataPathSegment; +using Semantic = Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Provides an object representation for an OData path with additional information about the EDM type and entity set for the path. + /// + [ODataPathParameterBinding] + public class ODataPath + { + private readonly ReadOnlyCollection _segments; + private readonly IEdmType _edmType; + private readonly IEdmNavigationSource _navigationSource; + private readonly string _pathTemplate; + private readonly string _pathLiteral; + + /// + /// Initializes a new instance of the class. + /// + /// The path segments for the path. + public ODataPath(params ODataPathSegment[] segments) + : this(segments as IEnumerable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The path segments for the path. + public ODataPath(IEnumerable segments) + { + if (segments == null) + { + throw Error.ArgumentNull("segments"); + } + + var oDataPathSegments = segments as IList ?? segments.ToList(); + + _edmType = oDataPathSegments.Any() ? oDataPathSegments.Last().EdmType : null; + + _segments = new ReadOnlyCollection(oDataPathSegments); + + ODataPathSegmentHandler handler = new ODataPathSegmentHandler(); + foreach (var segment in oDataPathSegments) + { + UnresolvedPathSegment pathSegment = segment as UnresolvedPathSegment; + if (pathSegment != null) + { + handler.Handle(pathSegment); + } + else + { + segment.HandleWith(handler); + } + } + + _navigationSource = handler.NavigationSource; + _pathTemplate = handler.PathTemplate; + _pathLiteral = handler.PathLiteral; + } + + /// + /// Gets the EDM type of the path. + /// + public IEdmType EdmType + { + get { return _edmType; } + } + + /// + /// Gets the navigation source of the path. + /// + public IEdmNavigationSource NavigationSource + { + get { return _navigationSource; } + } + + /// + /// Gets the path segments for the OData path. + /// + public ReadOnlyCollection Segments + { + get { return _segments; } + } + + /// + /// Gets the path template describing the types of segments in the path. + /// + public virtual string PathTemplate + { + get { return _pathTemplate; } + } + + /// + public override string ToString() + { + return _pathLiteral; + } + + /// + /// Gets the ODL path. + /// + public Semantic.ODataPath Path { get; internal set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathParameterBindingAttribute.cs new file mode 100644 index 0000000..5be9be4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathParameterBindingAttribute.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Implementation of ParameterBindingAttribute used to bind an instance of as an action parameter. + /// + public sealed partial class ODataPathParameterBindingAttribute + { + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathRouteConstraint.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathRouteConstraint.cs new file mode 100644 index 0000000..e7b60be --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathRouteConstraint.cs @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of route constraint that only matches OData paths. + /// + public partial class ODataPathRouteConstraint + { + // "%2F" + private static readonly string _escapedSlash = Uri.EscapeDataString("/"); + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route this constraint is associated with. + public ODataPathRouteConstraint(string routeName) + { + if (routeName == null) + { + throw Error.ArgumentNull("routeName"); + } + + RouteName = routeName; + } + + /// + /// Gets the name of the route this constraint is associated with. + /// + public string RouteName + { + get; + private set; + } + + /// + /// Get the OData path from the url and query string. + /// + /// The ODataPath from the route values. + /// The Uri from start to end of path, i.e. the left portion. + /// The Uri from the query string to the end, i.e. the right portion. + /// The request container factory. + /// The OData path. + private static ODataPath GetODataPath(string oDataPathString, string uriPathString, string queryString, Func requestContainerFactory) + { + ODataPath path = null; + + try + { + // Service root is the current RequestUri, less the query string and the ODataPath (always the + // last portion of the absolute path). ODL expects an escaped service root and other service + // root calculations are calculated using AbsoluteUri (also escaped). But routing exclusively + // uses unescaped strings, determined using + // address.GetComponents(UriComponents.Path, UriFormat.Unescaped) + // + // For example if the AbsoluteUri is + // , the + // oDataPathString will contain "FunctionCall(p0='Chinese西雅图Chars')". + // + // Due to this decoding and the possibility of unnecessarily-escaped characters, there's no + // reliable way to determine the original string from which oDataPathString was derived. + // Therefore a straightforward string comparison won't always work. See RemoveODataPath() for + // details of chosen approach. + string serviceRoot = uriPathString; + + if (!String.IsNullOrEmpty(oDataPathString)) + { + serviceRoot = RemoveODataPath(serviceRoot, oDataPathString); + } + + // As mentioned above, we also need escaped ODataPath. + // The requestLeftPart and request.QueryString are both escaped. + // The ODataPath for service documents is empty. + string oDataPathAndQuery = uriPathString.Substring(serviceRoot.Length); + + if (!String.IsNullOrEmpty(queryString)) + { + // Ensure path handler receives the query string as well as the path. + oDataPathAndQuery += queryString; + } + + // Leave an escaped '/' out of the service route because DefaultODataPathHandler will add a + // literal '/' to the end of this string if not already present. That would double the slash + // in response links and potentially lead to later 404s. + if (serviceRoot.EndsWith(_escapedSlash, StringComparison.OrdinalIgnoreCase)) + { + serviceRoot = serviceRoot.Substring(0, serviceRoot.Length - _escapedSlash.Length); + } + + IServiceProvider requestContainer = requestContainerFactory(); + IODataPathHandler pathHandler = requestContainer.GetRequiredService(); + path = pathHandler.Parse(serviceRoot, oDataPathAndQuery, requestContainer); + } + catch (ODataException) + { + path = null; + } + + return path; + } + + // Find the substring of the given URI string before the given ODataPath. Tests rely on the following: + // 1. ODataPath comes at the end of the processed Path + // 2. Virtual path root, if any, comes at the beginning of the Path and a '/' separates it from the rest + // 3. OData prefix, if any, comes between the virtual path root and the ODataPath and '/' characters separate + // it from the rest + // 4. Even in the case of Unicode character corrections, the only differences between the escaped Path and the + // unescaped string used for routing are %-escape sequences which may be present in the Path + // + // Therefore, look for the '/' character at which to lop off the ODataPath. Can't just unescape the given + // uriString because subsequent comparisons would only help to check whether a match is _possible_, not where + // to do the lopping. + private static string RemoveODataPath(string uriString, string oDataPathString) + { + // Potential index of oDataPathString within uriString. + int endIndex = uriString.Length - oDataPathString.Length - 1; + if (endIndex <= 0) + { + // Bizarre: oDataPathString is longer than uriString. Likely the values collection passed to Match() + // is corrupt. + throw Error.InvalidOperation(SRResources.RequestUriTooShortForODataPath, uriString, oDataPathString); + } + + string startString = uriString.Substring(0, endIndex + 1); // Potential return value. + string endString = uriString.Substring(endIndex + 1); // Potential oDataPathString match. + if (String.Equals(endString, oDataPathString, StringComparison.Ordinal)) + { + // Simple case, no escaping in the ODataPathString portion of the Path. In this case, don't do extra + // work to look for trailing '/' in startString. + return startString; + } + + while (true) + { + // Escaped '/' is a derivative case but certainly possible. + int slashIndex = startString.LastIndexOf('/', endIndex - 1); + int escapedSlashIndex = + startString.LastIndexOf(_escapedSlash, endIndex - 1, StringComparison.OrdinalIgnoreCase); + if (slashIndex > escapedSlashIndex) + { + endIndex = slashIndex; + } + else if (escapedSlashIndex >= 0) + { + // Include the escaped '/' (three characters) in the potential return value. + endIndex = escapedSlashIndex + 2; + } + else + { + // Failure, unable to find the expected '/' or escaped '/' separator. + throw Error.InvalidOperation(SRResources.ODataPathNotFound, uriString, oDataPathString); + } + + startString = uriString.Substring(0, endIndex + 1); + endString = uriString.Substring(endIndex + 1); + + // Compare unescaped strings to avoid both arbitrary escaping and use of lowercase 'a' through 'f' in + // %-escape sequences. + endString = Uri.UnescapeDataString(endString); + if (String.Equals(endString, oDataPathString, StringComparison.Ordinal)) + { + return startString; + } + + if (endIndex == 0) + { + // Failure, could not match oDataPathString after an initial '/' or escaped '/'. + throw Error.InvalidOperation(SRResources.ODataPathNotFound, uriString, oDataPathString); + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentExtensions.cs new file mode 100644 index 0000000..82cc695 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentExtensions.cs @@ -0,0 +1,352 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Provides an object representation for an OData path with additional information about the EDM type and entity set for the path. + /// + internal static class ODataPathSegmentExtensions + { + public static string TranslatePathTemplateSegment(this PathTemplateSegment pathTemplatesegment, out string value) + { + if (pathTemplatesegment == null) + { + throw Error.ArgumentNull("pathTemplatesegment"); + } + + string pathTemplateSegmentLiteralText = pathTemplatesegment.LiteralText; + if (pathTemplateSegmentLiteralText == null) + { + throw new ODataException(Error.Format(SRResources.InvalidAttributeRoutingTemplateSegment, String.Empty)); + } + + if (pathTemplateSegmentLiteralText.StartsWith("{", StringComparison.Ordinal) + && pathTemplateSegmentLiteralText.EndsWith("}", StringComparison.Ordinal)) + { + string[] keyValuePair = pathTemplateSegmentLiteralText.Substring(1, + pathTemplateSegmentLiteralText.Length - 2).Split(':'); + if (keyValuePair.Length != 2) + { + throw new ODataException(Error.Format( + SRResources.InvalidAttributeRoutingTemplateSegment, + pathTemplateSegmentLiteralText)); + } + value = "{" + keyValuePair[0] + "}"; + return keyValuePair[1]; + } + + value = String.Empty; + return String.Empty; + } + + #region Uri Literal + + public static string ToUriLiteral(this MetadataSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return ODataSegmentKinds.Metadata; + } + + public static string ToUriLiteral(this ValueSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return ODataSegmentKinds.Value; + } + + public static string ToUriLiteral(this BatchSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return ODataSegmentKinds.Batch; + } + + public static string ToUriLiteral(this CountSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return ODataSegmentKinds.Count; + } + + public static string ToUriLiteral(this TypeSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + IEdmType elementType = segment.EdmType; + if (segment.EdmType.TypeKind == EdmTypeKind.Collection) + { + elementType = ((IEdmCollectionType)segment.EdmType).ElementType.Definition; + } + + return elementType.FullTypeName(); + } + + public static string ToUriLiteral(this SingletonSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.Singleton.Name; + } + + public static string ToUriLiteral(this PropertySegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.Property.Name; + } + + public static string ToUriLiteral(this PathTemplateSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.LiteralText; + } + + public static string ToUriLiteral(this DynamicPathSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.Identifier; + } + + public static string ToUriLiteral(this NavigationPropertySegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.NavigationProperty.Name; + } + + public static string ToUriLiteral(this NavigationPropertyLinkSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.NavigationProperty.Name + "/$ref"; + } + + public static string ToUriLiteral(this KeySegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return ConvertKeysToUriLiteral(segment.Keys, segment.EdmType); + } + + public static string ToUriLiteral(this EntitySetSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.EntitySet.Name; + } + + public static string ToUriLiteral(this OperationSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + IEdmAction action = segment.Operations.Single() as IEdmAction; + + if (action != null) + { + return action.FullName(); + } + else + { + // Translate the nodes in ODL path to string literals as parameter of BoundFunctionPathSegment. + Dictionary parameterValues = segment.Parameters.ToDictionary( + parameterValue => parameterValue.Name, + parameterValue => TranslateNode(parameterValue.Value)); + + IEdmFunction function = (IEdmFunction)segment.Operations.Single(); + + IEnumerable parameters = parameterValues.Select(v => String.Format(CultureInfo.InvariantCulture, "{0}={1}", v.Key, v.Value)); + return String.Format(CultureInfo.InvariantCulture, "{0}({1})", function.FullName(), String.Join(",", parameters)); + } + } + + public static string ToUriLiteral(this OperationImportSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + IEdmActionImport action = segment.OperationImports.Single() as IEdmActionImport; + + if (action != null) + { + return action.Name; + } + else + { + // Translate the nodes in ODL path to string literals as parameter of BoundFunctionPathSegment. + Dictionary parameterValues = segment.Parameters.ToDictionary( + parameterValue => parameterValue.Name, + parameterValue => TranslateNode(parameterValue.Value)); + + // TODO: refactor the function literal for parameter alias + IEdmFunctionImport function = (IEdmFunctionImport)segment.OperationImports.Single(); + + IEnumerable parameters = parameterValues.Select(v => String.Format(CultureInfo.InvariantCulture, "{0}={1}", v.Key, v.Value)); + return String.Format(CultureInfo.InvariantCulture, "{0}({1})", function.Name, String.Join(",", parameters)); + } + } + + public static string ToUriLiteral(this UnresolvedPathSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + return segment.SegmentValue; + } + + private static string ConvertKeysToUriLiteral(IEnumerable> keys, IEdmType edmType) + { + Contract.Assert(keys != null); + + IEdmEntityType entityType = edmType as IEdmEntityType; + Contract.Assert(entityType != null); + + if (keys.Count() < 2) + { + var keyValue = keys.First(); + bool isDeclaredKey = entityType.Key().Any(k => k.Name == keyValue.Key); + + if (isDeclaredKey) + { + return String.Join( + ",", + keys.Select(keyValuePair => + TranslateKeySegmentValue(keyValuePair.Value)).ToArray()); + } + } + + return String.Join( + ",", + keys.Select(keyValuePair => + (keyValuePair.Key + + "=" + + TranslateKeySegmentValue(keyValuePair.Value))).ToArray()); + } + + // Translate the object of key in ODL path to string literal. + private static string TranslateKeySegmentValue(object value) + { + if (value == null) + { + throw Error.ArgumentNull("value"); + } + + UriTemplateExpression uriTemplateExpression = value as UriTemplateExpression; + if (uriTemplateExpression != null) + { + return uriTemplateExpression.LiteralText; + } + + ConstantNode constantNode = value as ConstantNode; + if (constantNode != null) + { + ODataEnumValue enumValue = constantNode.Value as ODataEnumValue; + if (enumValue != null) + { + return ODataUriUtils.ConvertToUriLiteral(enumValue, ODataVersion.V4); + } + } + + return ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V4); + } + + private static string TranslateNode(object node) + { + if (node == null) + { + throw Error.ArgumentNull("node"); + } + + ConstantNode constantNode = node as ConstantNode; + if (constantNode != null) + { + UriTemplateExpression uriTemplateExpression = constantNode.Value as UriTemplateExpression; + if (uriTemplateExpression != null) + { + return uriTemplateExpression.LiteralText; + } + + // Make the enum prefix free to work. + ODataEnumValue enumValue = constantNode.Value as ODataEnumValue; + if (enumValue != null) + { + return ODataUriUtils.ConvertToUriLiteral(enumValue, ODataVersion.V4); + } + + return constantNode.LiteralText; + } + + ConvertNode convertNode = node as ConvertNode; + if (convertNode != null) + { + return TranslateNode(convertNode.Source); + } + + throw Error.NotSupported( + SRResources.CannotRecognizeNodeType, + typeof(ODataPathSegmentTranslator), + node.GetType().FullName); + } + + #endregion + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentHandler.cs new file mode 100644 index 0000000..f1c2c3c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentHandler.cs @@ -0,0 +1,457 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// A handler used to calculate some values based on the odata path. + /// + public class ODataPathSegmentHandler : PathSegmentHandler + { + private readonly IList _pathTemplate; + private readonly IList _pathUriLiteral; + private IEdmNavigationSource _navigationSource; // used to record the navigation source in the last segment. + + /// + /// Initializes a new instance of the class. + /// + public ODataPathSegmentHandler() + { + _navigationSource = null; + _pathTemplate = new List { ODataSegmentKinds.ServiceBase }; // ~ + _pathUriLiteral = new List(); + } + + /// + /// Gets the path navigation source. + /// + public IEdmNavigationSource NavigationSource + { + get { return _navigationSource; } + } + + /// + /// Gets the path template. + /// + public string PathTemplate + { + get { return String.Join("/", _pathTemplate); } + } + + /// + /// Gets the path literal. + /// + public string PathLiteral + { + get { return String.Join("/", _pathUriLiteral); } + } + + /// + /// Handle a EntitySetSegment + /// + /// the segment to handle + public override void Handle(EntitySetSegment segment) + { + _navigationSource = segment.EntitySet; + + _pathTemplate.Add(ODataSegmentKinds.EntitySet); // entityset + + _pathUriLiteral.Add(segment.EntitySet.Name); + } + + /// + /// Handle a KeySegment + /// + /// the segment to handle + public override void Handle(KeySegment segment) + { + _navigationSource = segment.NavigationSource; + + if (_pathTemplate.Last() == ODataSegmentKinds.Ref) + { + _pathTemplate.Insert(_pathTemplate.Count - 1, ODataSegmentKinds.Key); + } + else + { + _pathTemplate.Add(ODataSegmentKinds.Key); + } + + string value = ConvertKeysToString(segment.Keys, segment.EdmType); + + // update the previous segment Uri literal + if (!_pathUriLiteral.Any()) + { + _pathUriLiteral.Add("(" + value + ")"); + return; + } + + if (_pathUriLiteral.Last() == ODataSegmentKinds.Ref) + { + _pathUriLiteral[_pathUriLiteral.Count - 2] = + _pathUriLiteral[_pathUriLiteral.Count - 2] + "(" + value + ")"; + } + else + { + _pathUriLiteral[_pathUriLiteral.Count - 1] = + _pathUriLiteral[_pathUriLiteral.Count - 1] + "(" + value + ")"; + } + } + + /// + /// Handle a NavigationPropertyLinkSegment + /// + /// the segment to Handle + public override void Handle(NavigationPropertyLinkSegment segment) + { + _navigationSource = segment.NavigationSource; + + // TODO: do we really need to add $ref? + _pathTemplate.Add(ODataSegmentKinds.Navigation); // navigation + _pathTemplate.Add(ODataSegmentKinds.Ref); // $ref + + _pathUriLiteral.Add(segment.NavigationProperty.Name); + _pathUriLiteral.Add(ODataSegmentKinds.Ref); + } + + /// + /// Handle a NavigationPropertySegment + /// + /// the segment to Handle + public override void Handle(NavigationPropertySegment segment) + { + _navigationSource = segment.NavigationSource; + + _pathTemplate.Add(ODataSegmentKinds.Navigation); // navigation + + _pathUriLiteral.Add(segment.NavigationProperty.Name); + } + + /// + /// Handle a OpenPropertySegment + /// + /// the segment to Handle + public override void Handle(DynamicPathSegment segment) + { + _navigationSource = null; + + _pathTemplate.Add(ODataSegmentKinds.DynamicProperty); // dynamic property + + _pathUriLiteral.Add(segment.Identifier); + } + + /// + /// Handle a OperationImportSegment + /// + /// the segment to Handle + public override void Handle(OperationImportSegment segment) + { + _navigationSource = segment.EntitySet; + + IEdmActionImport actionImport = segment.OperationImports.Single() as IEdmActionImport; + + if (actionImport != null) + { + _pathTemplate.Add(ODataSegmentKinds.UnboundAction); // unbound action + _pathUriLiteral.Add(actionImport.Name); + } + else + { + _pathTemplate.Add(ODataSegmentKinds.UnboundFunction); // unbound function + + // Translate the nodes in ODL path to string literals as parameter of UnboundFunctionPathSegment. + Dictionary parameterValues = segment.Parameters.ToDictionary( + parameterValue => parameterValue.Name, + parameterValue => TranslateNode(parameterValue.Value)); + + IEdmFunctionImport function = (IEdmFunctionImport)segment.OperationImports.Single(); + + IEnumerable parameters = parameterValues.Select(v => String.Format(CultureInfo.InvariantCulture, "{0}={1}", v.Key, v.Value)); + string literal = String.Format(CultureInfo.InvariantCulture, "{0}({1})", function.Name, String.Join(",", parameters)); + + _pathUriLiteral.Add(literal); + } + } + + /// + /// Handle an OperationSegment + /// + /// the segment to handle + public override void Handle(OperationSegment segment) + { + _navigationSource = segment.EntitySet; + + IEdmAction action = segment.Operations.Single() as IEdmAction; + + if (action != null) + { + _pathTemplate.Add(ODataSegmentKinds.Action); // action + _pathUriLiteral.Add(action.FullName()); + } + else + { + _pathTemplate.Add(ODataSegmentKinds.Function); // function + + // Translate the nodes in ODL path to string literals as parameter of BoundFunctionPathSegment. + Dictionary parameterValues = segment.Parameters.ToDictionary( + parameterValue => parameterValue.Name, + parameterValue => TranslateNode(parameterValue.Value)); + + // TODO: refactor the function literal for parameter alias + IEdmFunction function = (IEdmFunction)segment.Operations.Single(); + + IEnumerable parameters = parameterValues.Select(v => String.Format(CultureInfo.InvariantCulture, "{0}={1}", v.Key, v.Value)); + string literal = String.Format(CultureInfo.InvariantCulture, "{0}({1})", function.FullName(), String.Join(",", parameters)); + + _pathUriLiteral.Add(literal); + } + } + + /// + /// Handle a PropertySegment + /// + /// the segment to handle + public override void Handle(PathTemplateSegment segment) + { + _navigationSource = null; + + _pathTemplate.Add(ODataSegmentKinds.Property); // path template + + _pathUriLiteral.Add(segment.LiteralText); + } + + /// + /// Handle a PropertySegment + /// + /// the segment to handle + public override void Handle(PropertySegment segment) + { + _navigationSource = null; + + _pathTemplate.Add(ODataSegmentKinds.Property); // property + + _pathUriLiteral.Add(segment.Property.Name); + } + + /// + /// Handle a SingletonSegment + /// + /// the segment to handle + public override void Handle(SingletonSegment segment) + { + _navigationSource = segment.Singleton; + + _pathTemplate.Add(ODataSegmentKinds.Singleton); // singleton + + _pathUriLiteral.Add(segment.Singleton.Name); + } + + /// + /// Handle a TypeSegment, we use "cast" for type segment. + /// + /// the segment to handle + public override void Handle(TypeSegment segment) + { + _navigationSource = segment.NavigationSource; + + _pathTemplate.Add(ODataSegmentKinds.Cast); // cast + + // Uri literal does not use the collection type. + IEdmType elementType = segment.EdmType; + if (segment.EdmType.TypeKind == EdmTypeKind.Collection) + { + elementType = ((IEdmCollectionType)segment.EdmType).ElementType.Definition; + } + + _pathUriLiteral.Add(elementType.FullTypeName()); + } + + /// + /// Handle a ValueSegment + /// + /// the segment to handle + public override void Handle(ValueSegment segment) + { + // do nothing for the navigation source for $value. + // It means to use the previous the navigation source + + _pathTemplate.Add(ODataSegmentKinds.Value); // $value + _pathUriLiteral.Add(ODataSegmentKinds.Value); + } + + /// + /// Handle a CountSegment + /// + /// the segment to handle + public override void Handle(CountSegment segment) + { + _navigationSource = null; + + _pathTemplate.Add(ODataSegmentKinds.Count); // $count + _pathUriLiteral.Add(ODataSegmentKinds.Count); + } + + /// + /// Handle a BatchSegment + /// + /// the segment to handle + public override void Handle(BatchSegment segment) + { + _navigationSource = null; + + _pathTemplate.Add(ODataSegmentKinds.Batch); + _pathUriLiteral.Add(ODataSegmentKinds.Batch); + } + + /// + /// Handle a MetadataSegment + /// + /// the segment to handle + public override void Handle(MetadataSegment segment) + { + _navigationSource = null; + + _pathTemplate.Add(ODataSegmentKinds.Metadata); // $metadata + _pathUriLiteral.Add(ODataSegmentKinds.Metadata); + } + + /// + /// Handle a general path segment + /// + /// the segment to handle + public override void Handle(ODataPathSegment segment) + { + // ODL doesn't provide the handle function for general path segment + _navigationSource = null; + + _pathTemplate.Add(segment.ToString()); + _pathUriLiteral.Add(segment.ToString()); + } + + /// + /// Handle a UnresolvedPathSegment + /// + /// the segment to handle + public virtual void Handle(UnresolvedPathSegment segment) + { + // ODL doesn't provide the handle function for unresolved path segment + _navigationSource = null; + + _pathTemplate.Add(ODataSegmentKinds.Unresolved); // unresolved + _pathUriLiteral.Add(segment.SegmentValue); + } + + // Convert the objects of keys in ODL path to string literals. + private static string ConvertKeysToString(IEnumerable> keys, IEdmType edmType) + { + Contract.Assert(keys != null); + + IEdmEntityType entityType = edmType as IEdmEntityType; + Contract.Assert(entityType != null); + + IList> keyValuePairs = keys as IList> ?? keys.ToList(); + if (keyValuePairs.Count < 1) + { + return String.Empty; + } + + if (keyValuePairs.Count < 2) + { + var keyValue = keyValuePairs.First(); + bool isDeclaredKey = entityType.Key().Any(k => k.Name == keyValue.Key); + + // To support the alternate key + if (isDeclaredKey) + { + return String.Join( + ",", + keyValuePairs.Select(keyValuePair => + TranslateKeySegmentValue(keyValuePair.Value)).ToArray()); + } + } + + return String.Join( + ",", + keyValuePairs.Select(keyValuePair => + (keyValuePair.Key + + "=" + + TranslateKeySegmentValue(keyValuePair.Value))).ToArray()); + } + + // Translate the object of key in ODL path to string literal. + private static string TranslateKeySegmentValue(object value) + { + if (value == null) + { + throw Error.ArgumentNull("value"); + } + + UriTemplateExpression uriTemplateExpression = value as UriTemplateExpression; + if (uriTemplateExpression != null) + { + return uriTemplateExpression.LiteralText; + } + + ConstantNode constantNode = value as ConstantNode; + if (constantNode != null) + { + ODataEnumValue enumValue = constantNode.Value as ODataEnumValue; + if (enumValue != null) + { + return ODataUriUtils.ConvertToUriLiteral(enumValue, ODataVersion.V4); + } + } + + return ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V4); + } + + private static string TranslateNode(object node) + { + Contract.Assert(node != null); + + ConstantNode constantNode = node as ConstantNode; + if (constantNode != null) + { + UriTemplateExpression uriTemplateExpression = constantNode.Value as UriTemplateExpression; + if (uriTemplateExpression != null) + { + return uriTemplateExpression.LiteralText; + } + + // Make the enum prefix free to work. + ODataEnumValue enumValue = constantNode.Value as ODataEnumValue; + if (enumValue != null) + { + return ODataUriUtils.ConvertToUriLiteral(enumValue, ODataVersion.V4); + } + + return constantNode.LiteralText; + } + + ConvertNode convertNode = node as ConvertNode; + if (convertNode != null) + { + return TranslateNode(convertNode.Source); + } + + ParameterAliasNode parameterAliasNode = node as ParameterAliasNode; + if (parameterAliasNode != null) + { + return parameterAliasNode.Alias; + } + + //return node.ToString(); + throw Error.NotSupported(SRResources.CannotRecognizeNodeType, typeof(ODataPathSegmentHandler), + node.GetType().FullName); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentTranslator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentTranslator.cs new file mode 100644 index 0000000..0637e63 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataPathSegmentTranslator.cs @@ -0,0 +1,390 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using Semantic = Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Translator the parameter alias, convert node, returned entity set into OData path segment. + /// + public class ODataPathSegmentTranslator : PathSegmentTranslator + { + /// + /// Translate the parameter alias, convert node, returned entity set into OData path segment. + /// + /// The EDM model + /// The odata path segments + /// The parameter alias + /// The translated odata path segments. + public static IEnumerable Translate(IEdmModel model, Semantic.ODataPath path, + IDictionary parameterAliasNodes) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + if (path == null) + { + throw Error.ArgumentNull("path"); + } + + var translator = new ODataPathSegmentTranslator(model, parameterAliasNodes); + return path.WalkWith(translator); + } + + private readonly IDictionary _parameterAliasNodes; + private readonly IEdmModel _model; + + /// + /// Initializes a new instance of the class. + /// + /// The model used to parse the path. + /// The parameter alias nodes info. + public ODataPathSegmentTranslator(IEdmModel model, IDictionary parameterAliasNodes) + { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + + _model = model; + _parameterAliasNodes = parameterAliasNodes ?? new Dictionary(); + } + + /// + /// Translate a TypeSegment + /// + /// the segment to Translate + /// Translated odata path segment + public override ODataPathSegment Translate(TypeSegment segment) + { + return segment; + } + + /// + /// Translate an EntitySetSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(EntitySetSegment segment) + { + return segment; + } + + /// + /// Translate an SingletonSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(SingletonSegment segment) + { + return segment; + } + + /// + /// Translate a PropertySegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(PropertySegment segment) + { + return segment; + } + + /// + /// Translate an OpenPropertySegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(DynamicPathSegment segment) + { + return segment; + } + + /// + /// Translate a CountSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(CountSegment segment) + { + return segment; + } + + /// + /// Translate a NavigationPropertySegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(NavigationPropertySegment segment) + { + return segment; + } + + /// + /// Visit a NavigationPropertyLinkSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(NavigationPropertyLinkSegment segment) + { + return segment; + } + + /// + /// Translate a ValueSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(ValueSegment segment) + { + return segment; + } + + /// + /// Translate a BatchSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(BatchSegment segment) + { + return segment; + } + + /// + /// Translate a MetadataSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(MetadataSegment segment) + { + return segment; + } + + /// + /// Translate a PathTemplateSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(PathTemplateSegment segment) + { + return segment; + } + + /// + /// Translate a KeySegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(KeySegment segment) + { + return segment; + + /* + KeySegment newKeySegment = segment; + if (_enableUriTemplate) + { + var newKeys = + segment.Keys.Select(e => new KeyValuePair(e.Key, TranslateKeyValue(e.Value))); + newKeySegment = new KeySegment(newKeys, (IEdmEntityType)segment.EdmType, segment.NavigationSource); + } + + return newKeySegment;*/ + } + /* + private static object TranslateKeyValue(object value) + { + UriTemplateExpression uriTemplateExpression = value as UriTemplateExpression; + if (uriTemplateExpression != null) + { + return uriTemplateExpression.LiteralText; + } + + return value; + }*/ + + /// + /// Translate a OperationImportSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(OperationImportSegment segment) + { + IEdmActionImport actionImport = segment.OperationImports.Single() as IEdmActionImport; + + if (actionImport != null) + { + return segment; + } + + OperationImportSegment newSegment = segment; + if (segment.Parameters.Any(p => p.Value is ParameterAliasNode || p.Value is ConvertNode)) + { + var newParameters = + segment.Parameters.Select(e => new OperationSegmentParameter(e.Name, TranslateNode(e.Value))); + newSegment = new OperationImportSegment(segment.OperationImports, segment.EntitySet, newParameters); + } + + return newSegment; + } + + /// + /// Translate a OperationSegment + /// + /// the segment to Translate + /// Translated odata path segment. + public override ODataPathSegment Translate(OperationSegment segment) + { + Contract.Assert(segment != null); + + IEdmFunction function = segment.Operations.Single() as IEdmFunction; + + if (function != null) + { + OperationSegment newSegment = segment; + if (segment.Parameters.Any(p => p.Value is ParameterAliasNode || p.Value is ConvertNode)) + { + var newParameters = + segment.Parameters.Select(e => new OperationSegmentParameter(e.Name, TranslateNode(e.Value))); + newSegment = new OperationSegment(segment.Operations, newParameters, segment.EntitySet); + } + + segment = newSegment; + } + + // Try to use the entity set annotation to get the target navigation source. + ReturnedEntitySetAnnotation entitySetAnnotation = + _model.GetAnnotationValue(segment.Operations.Single()); + + IEdmEntitySet returnedEntitySet = null; + if (entitySetAnnotation != null) + { + returnedEntitySet = _model.EntityContainer.FindEntitySet(entitySetAnnotation.EntitySetName); + } + + if (returnedEntitySet != null) + { + segment = new OperationSegment(segment.Operations, segment.Parameters, returnedEntitySet); + } + + return segment; + } + + private object TranslateNode(object node) + { + if (node == null) + { + throw Error.ArgumentNull("node"); + } + + ConstantNode constantNode = node as ConstantNode; + if (constantNode != null) + { + return constantNode; + } + + ConvertNode convertNode = node as ConvertNode; + if (convertNode != null) + { + object value = TranslateNode(convertNode.Source); + return ConvertNode(value, convertNode.TypeReference); + } + + ParameterAliasNode parameterAliasNode = node as ParameterAliasNode; + if (parameterAliasNode != null) + { + SingleValueNode singleValueNode; + + if (_parameterAliasNodes.TryGetValue(parameterAliasNode.Alias, out singleValueNode) && singleValueNode != null) + { + return TranslateNode(singleValueNode); + } + + // if not found the parameter alias, return null + return null; + } + + throw Error.NotSupported(SRResources.CannotRecognizeNodeType, typeof(ODataPathSegmentTranslator), + node.GetType().FullName); + } + + private object ConvertNode(object node, IEdmTypeReference typeReference) + { + // TODO: maybe find a better solution to do the convert. + if (node == null) + { + return null; + } + + ConstantNode constantNode = node as ConstantNode; + if (constantNode == null) + { + return node; + } + + if (constantNode.Value is UriTemplateExpression) + { + return node; + } + + if (constantNode.Value is ODataEnumValue) + { + return node; + } + + string literal = constantNode.LiteralText; + object convertValue = ODataUriUtils.ConvertFromUriLiteral(literal, ODataVersion.V4, _model, typeReference); + return new ConstantNode(convertValue, literal, typeReference); + } + + internal static SingleValueNode TranslateParameterAlias( + SingleValueNode node, + IDictionary parameterAliasNodes) + { + if (node == null) + { + throw Error.ArgumentNull("node"); + } + + if (parameterAliasNodes == null) + { + throw Error.ArgumentNull("parameterAliasNodes"); + } + + ParameterAliasNode parameterAliasNode = node as ParameterAliasNode; + + if (parameterAliasNode == null) + { + return node; + } + + SingleValueNode singleValueNode; + + if (parameterAliasNodes.TryGetValue(parameterAliasNode.Alias, out singleValueNode) && + singleValueNode != null) + { + if (singleValueNode is ParameterAliasNode) + { + singleValueNode = TranslateParameterAlias(singleValueNode, parameterAliasNodes); + } + + return singleValueNode; + } + + // Parameter alias value is assumed to be null if it is not found. + // Do not need to translate the parameter alias node from the query string + // because this method only deals with the parameter alias node mapping from ODL parser. + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoute.cs new file mode 100644 index 0000000..c10a9d5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoute.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// A route implementation for OData routes. It supports passing in a route prefix for the route as well + /// as a path constraint that parses the request path as OData. + /// + public partial class ODataRoute + { + private static readonly string _escapedHashMark = Uri.EscapeDataString("#"); + private static readonly string _escapedQuestionMark = Uri.EscapeDataString("?"); + + /// + /// Initializes a new instance of the class. + /// + /// The route prefix. + /// The OData path route constraint. + private void Initialize(string routePrefix, ODataPathRouteConstraint pathRouteConstraint) + { + RoutePrefix = routePrefix; + PathRouteConstraint = pathRouteConstraint; + + // We can only use our fast-path for link generation if there are no open brackets in the route prefix + // that need to be replaced. If there are, fall back to the slow path. + CanGenerateDirectLink = routePrefix == null || routePrefix.IndexOf('{') == -1; + } + + /// + /// Gets the route prefix. + /// + public string RoutePrefix { get; private set; } + + /// + /// Gets the on this route. + /// + public ODataPathRouteConstraint PathRouteConstraint { get; private set; } + + internal bool CanGenerateDirectLink { get; private set; } + + private static string GetRouteTemplate(string prefix) + { + return String.IsNullOrEmpty(prefix) ? + ODataRouteConstants.ODataPathTemplate : + prefix + '/' + ODataRouteConstants.ODataPathTemplate; + } + + private static string CombinePathSegments(string routePrefix, string odataPath) + { + if (String.IsNullOrEmpty(routePrefix)) + { + return odataPath; + } + else + { + return String.IsNullOrEmpty(odataPath) ? routePrefix : routePrefix + '/' + odataPath; + } + } + + private static string UriEncode(string str) + { + Contract.Assert(str != null); + + string escape = Uri.EscapeUriString(str); + escape = escape.Replace("#", _escapedHashMark); + escape = escape.Replace("?", _escapedQuestionMark); + return escape; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteAttribute.cs new file mode 100644 index 0000000..f68f169 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteAttribute.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Represents an attribute that can be placed on an action of an ODataController to specify + /// the OData URLs that the action handles. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public sealed class ODataRouteAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public ODataRouteAttribute() + : this(pathTemplate: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The OData URL path template that this action handles. + public ODataRouteAttribute(string pathTemplate) + { + PathTemplate = pathTemplate ?? String.Empty; + } + + /// + /// Gets the OData URL path template that this action handles. + /// + public string PathTemplate { get; private set; } + + /// + /// Gets or sets the OData route with which to associate the attribute. + /// + public string RouteName { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteConstants.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteConstants.cs new file mode 100644 index 0000000..daba807 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRouteConstants.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// This class contains route constants for OData. + /// + public static class ODataRouteConstants + { + /// + /// Route variable name for the OData path. + /// + public static readonly string ODataPath = "odataPath"; + + /// + /// Wildcard route template for the OData path route variable. + /// + public static readonly string ODataPathTemplate = "{*" + ODataRouteConstants.ODataPath + "}"; + + /// + /// Parameter name to use for the OData path route constraint. + /// + public static readonly string ConstraintName = "ODataConstraint"; + + /// + /// Parameter name to use for the OData version route constraint. + /// + public static readonly string VersionConstraintName = "ODataVersionConstraint"; + + /// + /// Route data key for the action name. + /// + public static readonly string Action = "action"; + + /// + /// Route data key for the controller name. + /// + public static readonly string Controller = "controller"; + + /// + /// Route data key for entity keys. + /// + public static readonly string Key = "key"; + + /// + /// Route data key for the related key when deleting links. + /// + public static readonly string RelatedKey = "relatedKey"; + + /// + /// Route data key for the navigation property name when manipulating links. + /// + public static readonly string NavigationProperty = "navigationProperty"; + + /// + /// Route template suffix for OData batch. + /// + public static readonly string Batch = "$batch"; + + /// + /// Route data key for the dynamic property name when manipulating open type. + /// + public static readonly string DynamicProperty = "dynamicProperty"; + + /// + /// Route data key for the OData optional parameters. + /// + public static readonly string OptionalParameters = typeof(ODataOptionalParameter).FullName; + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoutePrefixAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoutePrefixAttribute.cs new file mode 100644 index 0000000..54fd9bd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataRoutePrefixAttribute.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Represents an attribute that can be placed on an OData controller to specify + /// the prefix that will be used for all actions of that controller. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class ODataRoutePrefixAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The OData URL path template that this action handles. + public ODataRoutePrefixAttribute(string prefix) + { + if (String.IsNullOrEmpty(prefix)) + { + throw Error.ArgumentNullOrEmpty("prefix"); + } + + Prefix = prefix; + } + + /// + /// Gets the OData URL path template that this action handles. + /// + public string Prefix { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataSegmentKinds.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataSegmentKinds.cs new file mode 100644 index 0000000..215505b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataSegmentKinds.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Provides the values of segment kinds for implementations of odata path template. + /// + public static class ODataSegmentKinds + { + /// + /// Represents the service root segment (for OData service document). + /// + public const string ServiceBase = "~"; + + /// + /// Represents the OData $batch segment. + /// + public const string Batch = "$batch"; + + /// + /// Represents the OData $ref segment. + /// + public const string Ref = "$ref"; + + /// + /// Represents the OData $metadata segment. + /// + public const string Metadata = "$metadata"; + + /// + /// Represents the OData $value segment. + /// + public const string Value = "$value"; + + /// + /// Represents the OData $count segment. + /// + public const string Count = "$count"; + + /// + /// Represents a segment indicating a bound OData action. + /// + public const string Action = "action"; + + /// + /// Represents a segment indicating a bound OData function. + /// + public const string Function = "function"; + + /// + /// Represents a segment indicating an unbound OData action. + /// + public const string UnboundAction = "unboundaction"; + + /// + /// Represents a segment indicating an unbound OData function. + /// + public const string UnboundFunction = "unboundfunction"; + + /// + /// Represents a segment indicating a type cast. + /// + public const string Cast = "cast"; + + /// + /// Represents a segment indicating an entity set. + /// + public const string EntitySet = "entityset"; + + /// + /// Represents a segment indicating a singleton. + /// + public const string Singleton = "singleton"; + + /// + /// Represents a segment indicating an index by key operation. + /// + public const string Key = "key"; + + /// + /// Represents a segment indicating a navigation. + /// + public const string Navigation = "navigation"; + + /// + /// Represents a segment indicating a navigation link. + /// + public const string PathTemplate = "template"; + + /// + /// Represents a segment indicating a property access. + /// + public const string Property = "property"; + + /// + /// Represents a segment indicating an dynamic property access. + /// + public const string DynamicProperty = "dynamicproperty"; + + /// + /// Represents a segment that is not understood. + /// + public const string Unresolved = "unresolved"; + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataVersionConstraint.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataVersionConstraint.cs new file mode 100644 index 0000000..ca9a36d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/ODataVersionConstraint.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of route constraint that only matches a specific OData protocol + /// version. This constraint won't match incoming requests that contain any of the previous OData version + /// headers (for OData versions 1.0 to 3.0) regardless of the version in the current version headers. + /// + public partial class ODataVersionConstraint + { + // The header names used for versioning in the versions 4.0+ of the OData protocol. + internal const string ODataServiceVersionHeader = "OData-Version"; + internal const string ODataMaxServiceVersionHeader = "OData-MaxVersion"; + internal const string ODataMinServiceVersionHeader = "OData-MinVersion"; + internal const ODataVersion DefaultODataVersion = ODataVersion.V4; + + // The header names used for versioning in the versions 1.0 to 3.0 of the OData protocol. + private const string PreviousODataVersionHeaderName = "DataServiceVersion"; + private const string PreviousODataMaxVersionHeaderName = "MaxDataServiceVersion"; + private const string PreviousODataMinVersionHeaderName = "MinDataServiceVersion"; + + /// + /// Creates a new instance of the class that will have a default version + /// of 4.0. + /// + public ODataVersionConstraint() + { + Version = DefaultODataVersion; + IsRelaxedMatch = true; + } + + /// + /// The (minimum) version of the OData protocol that an OData-Version or OData-MaxVersion request header must have + /// in order to be processed by the OData service with this route constraint. + /// + public ODataVersion Version { get; private set; } + + /// + /// If set to true, allow passing in both OData V4 and previous version headers. + /// + public bool IsRelaxedMatch { get; set; } + + /// + /// Determine if there is a version match. + /// + /// The request headers. + /// The supported service version. + /// The max supported service version. + /// True if there is a match; false otherwise. + private bool IsVersionMatch(IDictionary> headers, ODataVersion? serviceVersion, ODataVersion? maxServiceVersion) + { + // The match behavior depends on value of IsRelaxedMatch. + // If users select using relaxed match logic, the header contains both V3 (or before) and V4 style version + // will be regarded as valid. While under non-relaxed match logic, both version headers presented will be + // regarded as invalid. The behavior for other situations are the same. When non version headers present, + // assume using V4 version. + if (!ValidateVersionHeaders(headers)) + { + return false; + } + + ODataVersion? requestVersion = GetVersion(headers, serviceVersion, maxServiceVersion); + return requestVersion.HasValue && requestVersion.Value >= Version; + } + + private bool ValidateVersionHeaders(IDictionary> headers) + { + bool containPreviousVersionHeaders = + headers.ContainsKey(PreviousODataVersionHeaderName) || + headers.ContainsKey(PreviousODataMinVersionHeaderName) || + headers.ContainsKey(PreviousODataMaxVersionHeaderName); + + bool containPreviousMaxVersionHeaderOnly = + headers.ContainsKey(PreviousODataMaxVersionHeaderName) && + !headers.ContainsKey(PreviousODataVersionHeaderName) && + !headers.ContainsKey(PreviousODataMinVersionHeaderName); + + bool containCurrentMaxVersionHeader = headers.ContainsKey(ODataMaxServiceVersionHeader); + + return IsRelaxedMatch + ? !containPreviousVersionHeaders || (containCurrentMaxVersionHeader && containPreviousMaxVersionHeaderOnly) + : !containPreviousVersionHeaders; + } + + private ODataVersion? GetVersion(IDictionary> headers, ODataVersion? serviceVersion, ODataVersion? maxServiceVersion) + { + // The logic is as follows. We check OData-Version first and if not present we check OData-MaxVersion. + // If both OData-Version and OData-MaxVersion are not present, we assume the version is the service version + + int versionHeaderCount = GetHeaderCount(ODataServiceVersionHeader, headers); + int maxVersionHeaderCount = GetHeaderCount(ODataMaxServiceVersionHeader, headers); + + if ((versionHeaderCount == 1 && serviceVersion != null)) + { + return serviceVersion; + } + else if ((versionHeaderCount == 0 && maxVersionHeaderCount == 1 && maxServiceVersion != null)) + { + return maxServiceVersion; + } + else if (versionHeaderCount == 0 && maxVersionHeaderCount == 0) + { + return this.Version; + } + else + { + return null; + } + } + + private static int GetHeaderCount(string headerName, IDictionary> headers) + { + IEnumerable values; + if (headers.TryGetValue(headerName, out values)) + { + return values.Count(); + } + return 0; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/DynamicSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/DynamicSegmentTemplate.cs new file mode 100644 index 0000000..87ceb19 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/DynamicSegmentTemplate.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class DynamicSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The open property segment + public DynamicSegmentTemplate(DynamicPathSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + + PropertyName = segment.Identifier; + TreatPropertyNameAsParameterName = false; + + if (RoutingConventionHelpers.IsRouteParameter(PropertyName)) + { + PropertyName = PropertyName.Substring(1, PropertyName.Length - 2); + TreatPropertyNameAsParameterName = true; + + if (String.IsNullOrEmpty(PropertyName)) + { + throw new ODataException( + Error.Format(SRResources.EmptyParameterAlias, PropertyName, segment.Identifier)); + } + } + } + + /// + /// Gets or sets the open property segment. + /// + public DynamicPathSegment Segment { get; private set; } + + /// + /// The parameter name of the dynamic property. + /// + private string PropertyName { get; set; } + + /// + /// Indicates whether the template should match the name, or treat it as a parameter. + /// + private bool TreatPropertyNameAsParameterName { get; set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + DynamicPathSegment other = pathSegment as DynamicPathSegment; + if (other == null) + { + return false; + } + + // If we're treating the property name as a parameter store the provided name in our values collection + // using the name from the template as the key. + if (TreatPropertyNameAsParameterName) + { + values[PropertyName] = other.Identifier; + values[ODataParameterValue.ParameterValuePrefix + PropertyName] = + new ODataParameterValue(other.Identifier, + EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(typeof(string))); + return true; + } + + if (PropertyName == other.Identifier) + { + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/EntitySetSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/EntitySetSegmentTemplate.cs new file mode 100644 index 0000000..2ac3afb --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/EntitySetSegmentTemplate.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class EntitySetSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The entity set segment + public EntitySetSegmentTemplate(EntitySetSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + } + + /// + /// Gets or sets the entity set segment. + /// + public EntitySetSegment Segment { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + EntitySetSegment otherEntitySet = pathSegment as EntitySetSegment; + return otherEntitySet != null && otherEntitySet.EntitySet == Segment.EntitySet; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/KeySegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/KeySegmentTemplate.cs new file mode 100644 index 0000000..525ab91 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/KeySegmentTemplate.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class KeySegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The key segment + public KeySegmentTemplate(KeySegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + + ParameterMappings = BuildKeyMappings(segment.Keys); + } + + /// + /// Gets or sets the key segment. + /// + public KeySegment Segment { get; set; } + + /// + /// Gets the dictionary representing the mappings from the key names in the segment to the key names in route data. + /// + public IDictionary ParameterMappings { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + KeySegment keySegment = pathSegment as KeySegment; + if (keySegment != null) + { + return keySegment.TryMatch(ParameterMappings, values); + } + + return false; + } + + internal static IDictionary BuildKeyMappings(IEnumerable> keys) + { + Contract.Assert(keys != null); + + Dictionary parameterMappings = new Dictionary(); + + foreach (KeyValuePair key in keys) + { + string nameInRouteData; + + UriTemplateExpression uriTemplateExpression = key.Value as UriTemplateExpression; + if (uriTemplateExpression != null) + { + nameInRouteData = uriTemplateExpression.LiteralText.Trim(); + } + else + { + // just for easy construct the key segment template + // it must start with "{" and end with "}" + nameInRouteData = key.Value as string; + } + + if (nameInRouteData == null || !RoutingConventionHelpers.IsRouteParameter(nameInRouteData)) + { + throw new ODataException( + Error.Format(SRResources.KeyTemplateMustBeInCurlyBraces, key.Value, key.Key)); + } + + nameInRouteData = nameInRouteData.Substring(1, nameInRouteData.Length - 2); + if (String.IsNullOrEmpty(nameInRouteData)) + { + throw new ODataException( + Error.Format(SRResources.EmptyKeyTemplate, key.Value, key.Key)); + } + + parameterMappings[key.Key] = nameInRouteData; + } + + return parameterMappings; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertyLinkSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertyLinkSegmentTemplate.cs new file mode 100644 index 0000000..d98f2a1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertyLinkSegmentTemplate.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class NavigationPropertyLinkSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The navigation property link segment + public NavigationPropertyLinkSegmentTemplate(NavigationPropertyLinkSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + } + + /// + /// Gets or sets the navigation property link segment. + /// + public NavigationPropertyLinkSegment Segment { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + NavigationPropertyLinkSegment other = pathSegment as NavigationPropertyLinkSegment; + return other != null && other.NavigationProperty == Segment.NavigationProperty; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertySegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertySegmentTemplate.cs new file mode 100644 index 0000000..70cb2ad --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/NavigationPropertySegmentTemplate.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class NavigationPropertySegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The navigation property segment + public NavigationPropertySegmentTemplate(NavigationPropertySegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + } + + /// + /// Gets or sets the navigation property segment. + /// + public NavigationPropertySegment Segment { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + NavigationPropertySegment otherNavPropSegment = pathSegment as NavigationPropertySegment; + return otherNavPropSegment != null && otherNavPropSegment.NavigationProperty == Segment.NavigationProperty; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplate.cs new file mode 100644 index 0000000..2abd4ba --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplate.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that could match an . + /// + public abstract class ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + protected ODataPathSegmentTemplate() + { + } + + /// + /// Matches the template with an . + /// + /// The path segment to match this template with. + /// The dictionary of matches to be updated if the segment matches the template. + /// true if the segment matches the template; otherwise, false. + public virtual bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateOfT.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateOfT.cs new file mode 100644 index 0000000..06c13ca --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateOfT.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a general path segment. + /// + public class ODataPathSegmentTemplate : ODataPathSegmentTemplate where T : ODataPathSegment + { + /// + /// Matches the template with an . + /// + /// The path segment to match this template with. + /// The dictionary of matches to be updated if the segment matches the template. + /// true if the segment matches the template; otherwise, false. + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + return pathSegment is T; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateTranslator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateTranslator.cs new file mode 100644 index 0000000..38d0bb8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathSegmentTemplateTranslator.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Translator an OData path to a path segment templates. + /// + public class ODataPathSegmentTemplateTranslator : PathSegmentTranslator + { + /// + /// Translate a TypeSegment + /// + /// the segment to Translate + /// Translated the path segment template + public override ODataPathSegmentTemplate Translate(TypeSegment segment) + { + return new TypeSegmentTemplate(segment); + } + + /// + /// Translate a NavigationPropertySegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(NavigationPropertySegment segment) + { + return new NavigationPropertySegmentTemplate(segment); + } + + /// + /// Translate an EntitySetSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(EntitySetSegment segment) + { + return new EntitySetSegmentTemplate(segment); + } + + /// + /// Translate an SingletonSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(SingletonSegment segment) + { + return new SingletonSegmentTemplate(segment); + } + + /// + /// Translate a KeySegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(KeySegment segment) + { + return new KeySegmentTemplate(segment); + } + + /// + /// Translate a PropertySegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(PropertySegment segment) + { + return new PropertySegmentTemplate(segment); + } + + /// + /// Handle a PropertySegment + /// + /// the segment to handle + public override ODataPathSegmentTemplate Translate(PathTemplateSegment segment) + { + return new PathTemplateSegmentTemplate(segment); + } + + /// + /// Translate a OperationImportSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(OperationImportSegment segment) + { + return new OperationImportSegmentTemplate(segment); + } + + /// + /// Translate a OperationSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(OperationSegment segment) + { + return new OperationSegmentTemplate(segment); + } + + /// + /// Translate an OpenPropertySegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(DynamicPathSegment segment) + { + return new DynamicSegmentTemplate(segment); + } + + /// + /// Visit a NavigationPropertyLinkSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(NavigationPropertyLinkSegment segment) + { + return new NavigationPropertyLinkSegmentTemplate(segment); + } + + /// + /// Translate a CountSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(CountSegment segment) + { + return new ODataPathSegmentTemplate(); + } + + /// + /// Translate a ValueSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(ValueSegment segment) + { + return new ODataPathSegmentTemplate(); + } + + /// + /// Translate a BatchSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(BatchSegment segment) + { + return new ODataPathSegmentTemplate(); + } + + /// + /// Translate a MetadataSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(MetadataSegment segment) + { + return new ODataPathSegmentTemplate(); + } + + /// + /// Translate a BatchReferenceSegment + /// + /// the segment to Translate + /// Translated the path segment template. + public override ODataPathSegmentTemplate Translate(BatchReferenceSegment segment) + { + throw new ODataException(Error.Format(SRResources.TargetKindNotImplemented, "ODataPathSegment", "BatchReferenceSegment")); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathTemplate.cs new file mode 100644 index 0000000..50452bd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/ODataPathTemplate.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template for an that can be matched to an actual . + /// + public class ODataPathTemplate + { + private ReadOnlyCollection _segments; + + /// + /// Initializes a new instance of the class. + /// + /// The path segment templates for the path. + public ODataPathTemplate(params ODataPathSegmentTemplate[] segments) + : this((IList)segments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The path segment templates for the path. + public ODataPathTemplate(IEnumerable segments) + : this(segments.AsList()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The path segments for the path. + public ODataPathTemplate(IList segments) + { + if (segments == null) + { + throw Error.ArgumentNull("segments"); + } + + _segments = new ReadOnlyCollection(segments); + } + + /// + /// Gets the path segments for the OData path. + /// + public ReadOnlyCollection Segments + { + get + { + return _segments; + } + } + + /// + /// Matches the current template with an OData path. + /// + /// The OData path to be matched against. + /// The dictionary of matches to be updated in case of a match. + /// true in case of a match; otherwise, false. + public bool TryMatch(ODataPath path, IDictionary values) + { + if (path.Segments.Count != Segments.Count) + { + return false; + } + + for (int index = 0; index < Segments.Count; index++) + { + if (!Segments[index].TryMatch(path.Segments[index], values)) + { + return false; + } + } + + return true; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationImportSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationImportSegmentTemplate.cs new file mode 100644 index 0000000..94deb63 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationImportSegmentTemplate.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class OperationImportSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The operation import segment + public OperationImportSegmentTemplate(OperationImportSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + + IEdmOperationImport operation = Segment.OperationImports.First(); + if (operation.IsFunctionImport()) + { + ParameterMappings = RoutingConventionHelpers.BuildParameterMappings(segment.Parameters, operation.Name); + } + } + + /// + /// Gets and sets the operation import segment. + /// + public OperationImportSegment Segment { get; private set; } + + /// + /// Gets the dictionary representing the mappings from the parameter names in the current function segment to the + /// parameter names in route data. + /// + public IDictionary ParameterMappings { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + OperationImportSegment other = pathSegment as OperationImportSegment; + if (other == null) + { + return false; + } + + IEdmOperationImport operationImport = Segment.OperationImports.First(); + IEdmOperationImport otherImport = other.OperationImports.First(); + + // for unbound action, just compare the action import + if (operationImport.IsActionImport() && otherImport.IsActionImport()) + { + return operationImport == otherImport; + } + else if (operationImport.IsFunctionImport() && otherImport.IsFunctionImport()) + { + // but for unbound function, we should compare the parameter names and + // process the parameter values into odata routes. + if (operationImport.Name != otherImport.Name) + { + return false; + } + + IDictionary parameterValues = new Dictionary(); + foreach (var parameter in other.Parameters) + { + object value = other.GetParameterValue(parameter.Name); + parameterValues[parameter.Name] = value; + } + + if (RoutingConventionHelpers.TryMatch(ParameterMappings, parameterValues, values)) + { + foreach (var operationSegmentParameter in other.Parameters) + { + string name = operationSegmentParameter.Name; + object value = parameterValues[name]; + + RoutingConventionHelpers.AddFunctionParameters((IEdmFunction)otherImport.Operation, name, + value, values, values, ParameterMappings); + } + + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationSegmentTemplate.cs new file mode 100644 index 0000000..d2d30cd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/OperationSegmentTemplate.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class OperationSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The operation segment + public OperationSegmentTemplate(OperationSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + + IEdmOperation operation = Segment.Operations.First(); + if (operation.IsFunction()) + { + ParameterMappings = RoutingConventionHelpers.BuildParameterMappings(segment.Parameters, operation.FullName()); + } + } + + /// + /// Gets or sets the operation segment. + /// + public OperationSegment Segment { get; private set; } + + /// + /// Gets the dictionary representing the mappings from the parameter names in the current function segment to the + /// parameter names in route data. + /// + public IDictionary ParameterMappings { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + OperationSegment other = pathSegment as OperationSegment; + if (other == null) + { + return false; + } + + IEdmOperation operation = Segment.Operations.First(); + IEdmOperation otherOperation = other.Operations.First(); + + if (operation.IsAction() && otherOperation.IsAction()) + { + return operation == otherOperation; + } + else if (operation.IsFunction() && otherOperation.IsFunction()) + { + if (operation.FullName() != otherOperation.FullName()) + { + return false; + } + + IDictionary parameterValues = new Dictionary(); + foreach (var parameter in other.Parameters) + { + object value = other.GetParameterValue(parameter.Name); + parameterValues[parameter.Name] = value; + } + + if (RoutingConventionHelpers.TryMatch(ParameterMappings, parameterValues, values)) + { + foreach (var operationSegmentParameter in other.Parameters) + { + string name = operationSegmentParameter.Name; + object value = parameterValues[name]; + + RoutingConventionHelpers.AddFunctionParameters((IEdmFunction)otherOperation, name, + value, values, values, ParameterMappings); + } + + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PathTemplateSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PathTemplateSegmentTemplate.cs new file mode 100644 index 0000000..95817a7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PathTemplateSegmentTemplate.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class PathTemplateSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The path template segment to be parsed as a template. + public PathTemplateSegmentTemplate(PathTemplateSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + TemplateSegment = segment; + + string value; + SegmentName = segment.TranslatePathTemplateSegment(out value); + + PropertyName = value; + TreatPropertyNameAsParameterName = false; + + if (RoutingConventionHelpers.IsRouteParameter(PropertyName)) + { + PropertyName = PropertyName.Substring(1, PropertyName.Length - 2); + TreatPropertyNameAsParameterName = true; + + if (String.IsNullOrEmpty(PropertyName)) + { + Error.Format(SRResources.EmptyParameterAlias, PropertyName, segment.LiteralText); + } + } + } + + /// + /// The parameter name of the dynamic property. + /// + public string PropertyName { get; private set; } + + /// + /// Gets the segment name + /// + public string SegmentName { get; private set; } + + /// + /// Indicates whether the template should match the name, or treat it as a parameter. + /// + private bool TreatPropertyNameAsParameterName { get; set; } + + /// + /// The parameter name of the dynamic property. + /// + public PathTemplateSegment TemplateSegment { get; private set; } + + /// + /// + /// + /// + /// + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + // So far, we only support the dynamic property segment template + DynamicPathSegment openPropertySegment = pathSegment as DynamicPathSegment; + if (openPropertySegment == null) + { + return false; + } + + // If we're treating the property name as a parameter store the provided name in our values collection + // using the name from the template as the key. + if (TreatPropertyNameAsParameterName) + { + values[PropertyName] = openPropertySegment.Identifier; + values[ODataParameterValue.ParameterValuePrefix + PropertyName] = + new ODataParameterValue(openPropertySegment.Identifier, + EdmLibHelpers.GetEdmPrimitiveTypeReferenceOrNull(typeof(string))); + return true; + } + + if (PropertyName == openPropertySegment.Identifier) + { + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PropertySegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PropertySegmentTemplate.cs new file mode 100644 index 0000000..ab38d6a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/PropertySegmentTemplate.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class PropertySegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The property segment + public PropertySegmentTemplate(PropertySegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + } + + /// + /// Gets or sets the property segment. + /// + public PropertySegment Segment { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + PropertySegment other = pathSegment as PropertySegment; + return other != null && other.Property == Segment.Property; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/SingletonSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/SingletonSegmentTemplate.cs new file mode 100644 index 0000000..f23f5dd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/SingletonSegmentTemplate.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class SingletonSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The singleton segment + public SingletonSegmentTemplate(SingletonSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + } + + /// + /// Gets or sets the singleton segment. + /// + public SingletonSegment Segment { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + SingletonSegment otherSingleton = pathSegment as SingletonSegment; + return otherSingleton != null && otherSingleton.Singleton == Segment.Singleton; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/TypeSegmentTemplate.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/TypeSegmentTemplate.cs new file mode 100644 index 0000000..85ce336 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/Template/TypeSegmentTemplate.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing.Template +{ + /// + /// Represents a template that can match a . + /// + public class TypeSegmentTemplate : ODataPathSegmentTemplate + { + /// + /// Initializes a new instance of the class. + /// + /// The type cast segment. + public TypeSegmentTemplate(TypeSegment segment) + { + if (segment == null) + { + throw Error.ArgumentNull("segment"); + } + + Segment = segment; + } + + /// + /// Gets or sets the type cast segment. + /// + public TypeSegment Segment { get; private set; } + + /// + public override bool TryMatch(ODataPathSegment pathSegment, IDictionary values) + { + TypeSegment otherType = pathSegment as TypeSegment; + return otherType != null && otherType.EdmType.FullTypeName() == Segment.EdmType.FullTypeName(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/UnresolvedPathSegment.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/UnresolvedPathSegment.cs new file mode 100644 index 0000000..e9e2e49 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/Routing/UnresolvedPathSegment.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation representing a segment that could not be resolved. + /// + public class UnresolvedPathSegment : ODataPathSegment + { + /// + /// Initializes a new instance of the class. + /// + /// The unresolved segment value. + public UnresolvedPathSegment(string segmentValue) + { + if (segmentValue == null) + { + throw Error.ArgumentNull("segmentValue"); + } + + SegmentValue = segmentValue; + } + + /// + /// Gets the segment kind for the current segment. + /// + public virtual string SegmentKind + { + get + { + return ODataSegmentKinds.Unresolved; + } + } + + /// + /// Gets the unresolved segment value. + /// + public string SegmentValue + { + get; + private set; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return SegmentValue; + } + + /// + /// Translate a an implementation of + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + public override T TranslateWith(PathSegmentTranslator translator) + { + if (typeof(T) == typeof(string)) + { + return (T)(object)SegmentValue; + } + + return default(T); + } + + /// + /// Handle a an implementation of + /// + /// An implementation of the handler interface. + public override void HandleWith(PathSegmentHandler handler) + { + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return null; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TimeZoneInfoHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TimeZoneInfoHelper.cs new file mode 100644 index 0000000..916d7f2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TimeZoneInfoHelper.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData +{ + internal class TimeZoneInfoHelper + { + private static TimeZoneInfo _defaultTimeZoneInfo; + + public static TimeZoneInfo TimeZone + { + get + { + if (_defaultTimeZoneInfo == null) + { + return TimeZoneInfo.Local; + } + + return _defaultTimeZoneInfo; + } + set { _defaultTimeZoneInfo = value; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs new file mode 100644 index 0000000..822451c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypeHelper.cs @@ -0,0 +1,482 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData +{ + internal static class TypeHelper + { + /// + /// Return the memberInfo from a type. + /// + /// The type to convert. + /// The memberInfo from a type. + public static MemberInfo AsMemberInfo(Type clrType) + { + return clrType as MemberInfo; + } + + /// + /// Return the type from a MemberInfo. + /// + /// The MemberInfo to convert. + /// The type from a MemberInfo. + public static Type AsType(MemberInfo memberInfo) + { + return memberInfo as Type; + } + + /// + /// Return the assembly from a type. + /// + /// The type to convert. + /// The assembly from a type. + public static Assembly GetAssembly(Type clrType) + { + return clrType.Assembly; + } + + /// + /// Return the base type from a type. + /// + /// The type to convert. + /// The base type from a type. + public static Type GetBaseType(Type clrType) + { + return clrType.BaseType; + } + + /// + /// Return the qualified name from a member info. + /// + /// The member info to convert. + /// The qualified name from a member info. + public static string GetQualifiedName(MemberInfo memberInfo) + { + Contract.Assert(memberInfo != null); + Type type = memberInfo as Type; + return type != null ? (type.Namespace + "." + type.Name) : memberInfo.Name; + } + + /// + /// Return the reflected type from a member info. + /// + /// The member info to convert. + /// The reflected type from a member info. + public static Type GetReflectedType(MemberInfo memberInfo) + { + return memberInfo.ReflectedType; + } + + /// + /// Determine if a type is abstract. + /// + /// The type to test. + /// True if the type is abstract; false otherwise. + public static bool IsAbstract(Type clrType) + { + return clrType.IsAbstract; + } + + /// + /// Determine if a type is a class. + /// + /// The type to test. + /// True if the type is a class; false otherwise. + public static bool IsClass(Type clrType) + { + return clrType.IsClass; + } + + /// + /// Determine if a type is a generic type. + /// + /// The type to test. + /// True if the type is a generic type; false otherwise. + public static bool IsGenericType(this Type clrType) + { + return clrType.IsGenericType; + } + + /// + /// Determine if a type is a generic type definition. + /// + /// The type to test. + /// True if the type is a generic type definition; false otherwise. + public static bool IsGenericTypeDefinition(this Type clrType) + { + return clrType.IsGenericTypeDefinition; + } + + /// + /// Determine if a type is an interface. + /// + /// The type to test. + /// True if the type is an interface; false otherwise. + public static bool IsInterface(Type clrType) + { + return clrType.IsInterface; + } + + /// + /// Determine if a type is null-able. + /// + /// The type to test. + /// True if the type is null-able; false otherwise. + public static bool IsNullable(Type clrType) + { + if (TypeHelper.IsValueType(clrType)) + { + // value types are only nullable if they are Nullable + return TypeHelper.IsGenericType(clrType) && clrType.GetGenericTypeDefinition() == typeof(Nullable<>); + } + else + { + // reference types are always nullable + return true; + } + } + + /// + /// Determine if a type is public. + /// + /// The type to test. + /// True if the type is public; false otherwise. + public static bool IsPublic(Type clrType) + { + return clrType.IsPublic; + } + + /// + /// Determine if a type is a primitive. + /// + /// The type to test. + /// True if the type is a primitive; false otherwise. + public static bool IsPrimitive(Type clrType) + { + return clrType.IsPrimitive; + } + + /// + /// Determine if a type is assignable from another type. + /// + /// The type to test. + /// The type to assign from. + /// True if the type is assignable; false otherwise. + public static bool IsTypeAssignableFrom(Type clrType, Type fromType) + { + return clrType.IsAssignableFrom(fromType); + } + + /// + /// Determine if a type is a value type. + /// + /// The type to test. + /// True if the type is a value type; false otherwise. + public static bool IsValueType(Type clrType) + { + return clrType.IsValueType; + } + + /// + /// Determine if a type is visible. + /// + /// The type to test. + /// True if the type is visible; false otherwise. + public static bool IsVisible(Type clrType) + { + return clrType.IsVisible; + } + + /// + /// Return the type from a nullable type. + /// + /// The type to convert. + /// The type from a nullable type. + public static Type ToNullable(Type clrType) + { + if (TypeHelper.IsNullable(clrType)) + { + return clrType; + } + else + { + return typeof(Nullable<>).MakeGenericType(clrType); + } + } + + /// + /// Return the collection element type. + /// + /// The type to convert. + /// The collection element type from a type. + public static Type GetInnerElementType(Type clrType) + { + Type elementType; + TypeHelper.IsCollection(clrType, out elementType); + Contract.Assert(elementType != null); + + return elementType; + } + + /// + /// Determine if a type is a collection. + /// + /// The type to test. + /// True if the type is an enumeration; false otherwise. + public static bool IsCollection(Type clrType) + { + Type elementType; + return TypeHelper.IsCollection(clrType, out elementType); + } + + /// + /// Determine if a type is a collection. + /// + /// The type to test. + /// out: the element type of the collection. + /// True if the type is an enumeration; false otherwise. + public static bool IsCollection(Type clrType, out Type elementType) + { + if (clrType == null) + { + throw Error.ArgumentNull("clrType"); + } + + elementType = clrType; + + // see if this type should be ignored. + if (clrType == typeof(string)) + { + return false; + } + + Type collectionInterface + = clrType.GetInterfaces() + .Union(new[] { clrType }) + .FirstOrDefault( + t => TypeHelper.IsGenericType(t) + && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + + if (collectionInterface != null) + { + elementType = collectionInterface.GetGenericArguments().Single(); + return true; + } + + return false; + } + + public static Type GetUnderlyingTypeOrSelf(Type type) + { + return Nullable.GetUnderlyingType(type) ?? type; + } + + /// + /// Determine if a type is an enumeration. + /// + /// The type to test. + /// True if the type is an enumeration; false otherwise. + public static bool IsEnum(Type clrType) + { + Type underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(clrType); + return underlyingTypeOrSelf.IsEnum; + } + + /// + /// Determine if a type is a DateTime. + /// + /// The type to test. + /// True if the type is a DateTime; false otherwise. + public static bool IsDateTime(Type clrType) + { + Type underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(clrType); + return Type.GetTypeCode(underlyingTypeOrSelf) == TypeCode.DateTime; + } + + /// + /// Determine if a type is a TimeSpan. + /// + /// The type to test. + /// True if the type is a TimeSpan; false otherwise. + public static bool IsTimeSpan(Type clrType) + { + Type underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(clrType); + return underlyingTypeOrSelf == typeof(TimeSpan); + } + + /// + /// Determines whether the given type is IQueryable. + /// + /// The type + /// true if the type is IQueryable. + internal static bool IsIQueryable(Type type) + { + return type == typeof(IQueryable) || + (type != null && TypeHelper.IsGenericType(type) && type.GetGenericTypeDefinition() == typeof(IQueryable<>)); + } + + /// + /// Determines whether the given type is a primitive type or + /// is a , , , + /// , or . + /// + /// The type + /// true if the type is a primitive type. + internal static bool IsQueryPrimitiveType(Type type) + { + Contract.Assert(type != null); + + type = GetInnerMostElementType(type); + + return TypeHelper.IsEnum(type) || + TypeHelper.IsPrimitive(type) || + type == typeof(Uri) || + (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(type) != null); + } + + /// + /// Returns the innermost element type for a given type, dealing with + /// nullables, arrays, etc. + /// + /// The type from which to get the innermost type. + /// The innermost element type + internal static Type GetInnerMostElementType(Type type) + { + Contract.Assert(type != null); + + while (true) + { + Type nullableUnderlyingType = Nullable.GetUnderlyingType(type); + if (nullableUnderlyingType != null) + { + type = nullableUnderlyingType; + } + else if (type.HasElementType) + { + type = type.GetElementType(); + } + else + { + return type; + } + } + } + + /// + /// Returns type of T if the type implements IEnumerable of T, otherwise, return null. + /// + /// + /// + internal static Type GetImplementedIEnumerableType(Type type) + { + // get inner type from Task + if (TypeHelper.IsGenericType(type) && type.GetGenericTypeDefinition() == typeof(Task<>)) + { + type = type.GetGenericArguments().First(); + } + + if (TypeHelper.IsGenericType(type) && TypeHelper.IsInterface(type) && + (type.GetGenericTypeDefinition() == typeof(IEnumerable<>) || + type.GetGenericTypeDefinition() == typeof(IQueryable<>))) + { + // special case the IEnumerable + return GetInnerGenericType(type); + } + else + { + // for the rest of interfaces and strongly Type collections + Type[] interfaces = type.GetInterfaces(); + foreach (Type interfaceType in interfaces) + { + if (TypeHelper.IsGenericType(interfaceType) && + (interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>) || + interfaceType.GetGenericTypeDefinition() == typeof(IQueryable<>))) + { + // special case the IEnumerable + return GetInnerGenericType(interfaceType); + } + } + } + + return null; + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Catching all exceptions in this case is the right to do.")] + // This code is copied from DefaultHttpControllerTypeResolver.GetControllerTypes. + internal static IEnumerable GetLoadedTypes(IWebApiAssembliesResolver assembliesResolver) + { + List result = new List(); + + if (assembliesResolver == null) + { + return result; + } + + // Go through all assemblies referenced by the application and search for types matching a predicate + IEnumerable assemblies = assembliesResolver.Assemblies; + foreach (Assembly assembly in assemblies) + { + Type[] exportedTypes = null; + if (assembly == null || assembly.IsDynamic) + { + // can't call GetTypes on a null (or dynamic?) assembly + continue; + } + + try + { + exportedTypes = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + exportedTypes = ex.Types; + } + catch + { + continue; + } + + if (exportedTypes != null) + { + result.AddRange(exportedTypes.Where(t => t != null && TypeHelper.IsVisible(t))); + } + } + + return result; + } + + internal static Type GetTaskInnerTypeOrSelf(Type type) + { + if (IsGenericType(type) && type.GetGenericTypeDefinition() == typeof(Task<>)) + { + return type.GetGenericArguments().First(); + } + + return type; + } + + private static Type GetInnerGenericType(Type interfaceType) + { + // Getting the type T definition if the returning type implements IEnumerable + Type[] parameterTypes = interfaceType.GetGenericArguments(); + + if (parameterTypes.Length == 1) + { + return parameterTypes[0]; + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypedDelta.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypedDelta.cs new file mode 100644 index 0000000..c070416 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/TypedDelta.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents a that can be used when a backing CLR type exists for + /// the entity type and complex type whose changes are tracked. + /// + public abstract class TypedDelta : Delta + { + /// + /// Gets the actual type of the structural object for which the changes are tracked. + /// + public abstract Type StructuredType { get; } + + /// + /// Gets the expected type of the entity for which the changes are tracked. + /// + public abstract Type ExpectedClrType { get; } + + /// + /// Helper method to check whether the given type is Delta generic type. + /// + /// The type to check. + /// True if it is a Delta generic type; false otherwise. + internal static bool IsDeltaOfT(Type type) + { + return type != null && TypeHelper.IsGenericType(type) && type.GetGenericTypeDefinition() == typeof(Delta<>); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/UnqualifiedCallAndEnumPrefixFreeResolver.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/UnqualifiedCallAndEnumPrefixFreeResolver.cs new file mode 100644 index 0000000..bb2083b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/UnqualifiedCallAndEnumPrefixFreeResolver.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData +{ + /// + /// The OData uri resolver wrapper for Enum prefix free and unqualified function call. + /// + public class UnqualifiedCallAndEnumPrefixFreeResolver : ODataUriResolver + { + private readonly StringAsEnumResolver _stringAsEnum = new StringAsEnumResolver(); + private readonly UnqualifiedODataUriResolver _unqualified = new UnqualifiedODataUriResolver(); + + private bool _enableCaseInsensitive; + + /// + public override bool EnableCaseInsensitive + { + get + { + return _enableCaseInsensitive; + } + set + { + _enableCaseInsensitive = value; + _stringAsEnum.EnableCaseInsensitive = this._enableCaseInsensitive; + _unqualified.EnableCaseInsensitive = this._enableCaseInsensitive; + } + } + + /// + public override IEnumerable ResolveUnboundOperations(IEdmModel model, string identifier) + { + return _unqualified.ResolveUnboundOperations(model, identifier); + } + + /// + public override IEnumerable ResolveBoundOperations(IEdmModel model, string identifier, + IEdmType bindingType) + { + return _unqualified.ResolveBoundOperations(model, identifier, bindingType); + } + + /// + public override void PromoteBinaryOperandTypes(BinaryOperatorKind binaryOperatorKind, + ref SingleValueNode leftNode, ref SingleValueNode rightNode, out IEdmTypeReference typeReference) + { + _stringAsEnum.PromoteBinaryOperandTypes(binaryOperatorKind, ref leftNode, ref rightNode, out typeReference); + } + + /// + public override IEnumerable> ResolveKeys(IEdmEntityType type, + IDictionary namedValues, Func convertFunc) + { + return _stringAsEnum.ResolveKeys(type, namedValues, convertFunc); + } + + /// + public override IEnumerable> ResolveKeys(IEdmEntityType type, + IList positionalValues, Func convertFunc) + { + return _stringAsEnum.ResolveKeys(type, positionalValues, convertFunc); + } + + /// + public override IDictionary ResolveOperationParameters( + IEdmOperation operation, IDictionary input) + { + return _stringAsEnum.ResolveOperationParameters(operation, input); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/WebApiAssembliesResolver.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/WebApiAssembliesResolver.cs new file mode 100644 index 0000000..96de373 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData.Shared/WebApiAssembliesResolver.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi assembly resolver to OData WebApi. + /// + internal partial class WebApiAssembliesResolver + { + /// + /// This static instance is used in the shared code in places where the request container context + /// is not known or does not contain an instance of IWebApiAssembliesResolver. + /// + public static IWebApiAssembliesResolver Default = new WebApiAssembliesResolver(); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionDescriptor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionDescriptor.cs new file mode 100644 index 0000000..b87c7e0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionDescriptor.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi action description to OData WebApi. + /// + internal class WebApiActionDescriptor : IWebApiActionDescriptor + { + /// + /// Gets the collection of supported HTTP methods for the descriptor. + /// + private IList supportedHttpMethods; + + /// + /// The inner action wrapped by this instance. + /// + private HttpActionDescriptor innerDescriptor; + + /// + /// Initializes a new instance of the class. + /// + /// The inner descriptor. + public WebApiActionDescriptor(HttpActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw Error.ArgumentNull("actionDescriptor"); + } + + this.innerDescriptor = actionDescriptor; + + if (actionDescriptor.SupportedHttpMethods != null) + { + this.supportedHttpMethods = new List(); + foreach (HttpMethod method in actionDescriptor.SupportedHttpMethods) + { + bool ignoreCase = true; + ODataRequestMethod methodEnum = ODataRequestMethod.Unknown; + if (Enum.TryParse(method.Method, ignoreCase, out methodEnum)) + { + this.supportedHttpMethods.Add(methodEnum); + } + } + } + } + + /// + /// Gets the name of the controller. + /// + public string ControllerName + { + get + { + return this.innerDescriptor.ControllerDescriptor != null + ? this.innerDescriptor.ControllerDescriptor.ControllerName + : null; + } + } + + /// + /// Gets the name of the action. + /// + public string ActionName + { + get { return this.innerDescriptor.ActionName; } + } + + /// + /// Returns the custom attributes associated with the action descriptor. + /// + /// The type of attribute to search for. + /// true to search this action's inheritance chain to find the attributes; otherwise, false. + /// A list of attributes of type T. + public IEnumerable GetCustomAttributes(bool inherit) where T : Attribute + { + return this.innerDescriptor.GetCustomAttributes(inherit); + } + + /// + /// Determine if the Http method is a match. + /// + public bool IsHttpMethodSupported(ODataRequestMethod method) + { + if (this.supportedHttpMethods == null) + { + // Assume all methods are supported if not specified. + return true; + } + + return this.supportedHttpMethods.Contains(method); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionMap.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionMap.cs new file mode 100644 index 0000000..ae0cda1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiActionMap.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi action map to OData WebApi. + /// + internal class WebApiActionMap : IWebApiActionMap + { + /// + /// The inner map wrapped by this instance. + /// + private ILookup innerMap; + + /// + /// Initializes a new instance of the WebApiActionMap class. + /// + /// The inner map. + public WebApiActionMap(ILookup actionMap) + { + if (actionMap == null) + { + throw Error.ArgumentNull("actionMap"); + } + + this.innerMap = actionMap; + } + + /// + /// Determines whether a specified action exists. + /// + /// The action name. + /// True if the action name exist; false otherwise. + public bool Contains(string name) + { + return this.innerMap.Contains(name); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiAssembliesResolver.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiAssembliesResolver.cs new file mode 100644 index 0000000..0e1195f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiAssembliesResolver.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Reflection; +using System.Web.Http.Dispatcher; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi assembly resolver to OData WebApi. + /// + internal partial class WebApiAssembliesResolver : IWebApiAssembliesResolver + { + /// + /// The inner resolver wrapped by this instance. + /// + private IAssembliesResolver innerResolver; + + /// + /// Initializes a new instance of the WebApiAssembliesResolver class. + /// + public WebApiAssembliesResolver() + { + this.innerResolver = new DefaultAssembliesResolver(); + } + + /// + /// Initializes a new instance of the WebApiAssembliesResolver class. + /// + /// The inner resolver. + public WebApiAssembliesResolver(IAssembliesResolver resolver) + { + if (resolver == null) + { + throw Error.ArgumentNull("resolver"); + } + + this.innerResolver = resolver; + } + + /// + /// Returns a list of assemblies available for the application. + /// + /// A list of assemblies available for the application. + public IEnumerable Assemblies + { + get + { + return this.innerResolver.GetAssemblies(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiContext.cs new file mode 100644 index 0000000..16c84d2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiContext.cs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi OData properties to OData WebApi. + /// + internal class WebApiContext : IWebApiContext + { + /// + /// The inner context wrapped by this instance. + /// + private HttpRequestMessageProperties innerContext; + + /// + /// Initializes a new instance of the WebApiContext class. + /// + /// The inner context. + public WebApiContext(HttpRequestMessageProperties context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + this.innerContext = context; + } + + /// + /// Gets or sets the parsed OData of the request. + /// + public ApplyClause ApplyClause + { + get { return this.innerContext.ApplyClause; } + set { this.innerContext.ApplyClause = value; } + } + + /// + /// Gets or sets the next link for the OData response. + /// + public Uri NextLink + { + get { return this.innerContext.NextLink; } + set { this.innerContext.NextLink = value; } + } + + /// + /// Gets or sets the delta link for the OData response. + /// + public Uri DeltaLink + { + get { return this.innerContext.DeltaLink; } + set { this.innerContext.DeltaLink = value; } + } + + /// + /// Gets the OData path. + /// + public ODataPath Path + { + get { return this.innerContext.Path; } + } + + /// + /// Gets the route name for generating OData links. + /// + public string RouteName + { + get { return this.innerContext.RouteName; } + } + + /// + /// Gets the data store used by s to store any custom route data. + /// + /// Initially an empty IDictionary<string, object>. + public IDictionary RoutingConventionsStore + { + get { return this.innerContext.RoutingConventionsStore; } + } + + /// + /// Gets or sets the parsed OData of the request. + /// + public SelectExpandClause ProcessedSelectExpandClause + { + get { return this.innerContext.SelectExpandClause; } + set { this.innerContext.SelectExpandClause = value; } + } + + /// + /// Gets or sets the parsed OData of the request. + /// + public ODataQueryOptions QueryOptions + { + get { return this.innerContext.QueryOptions; } + set { this.innerContext.QueryOptions = value; } + } + + /// + /// Gets or sets the total count for the OData response. + /// + /// null if no count should be sent back to the client. + public long? TotalCount + { + get { return this.innerContext.TotalCount; } + } + + /// + /// Gets or sets the page size. + /// + public int PageSize + { + get { return this.innerContext.PageSize; } + set { this.innerContext.PageSize = value; } + } + + /// + /// Gets or sets the total count function for the OData response. + /// + public Func TotalCountFunc + { + get { return this.innerContext.TotalCountFunc; } + set { this.innerContext.TotalCountFunc = value; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiControllerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiControllerContext.cs new file mode 100644 index 0000000..624fd44 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiControllerContext.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net.Http; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing.Conventions; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi controller context to OData WebApi. + /// + internal class WebApiControllerContext : IWebApiControllerContext + { + /// + /// The inner context wrapped by this instance. + /// + private HttpControllerContext innerContext; + + /// + /// Initializes a new instance of the WebApiControllerContext class. + /// + /// The inner context. + /// The selected controller result. + public WebApiControllerContext(HttpControllerContext controllerContext, SelectControllerResult controllerResult) + { + if (controllerContext == null) + { + throw Error.ArgumentNull("controllerContext"); + } + + if (controllerResult == null) + { + throw Error.ArgumentNull("controllerResult"); + } + + this.innerContext = controllerContext; + this.ControllerResult = controllerResult; + + HttpRequestMessage request = controllerContext.Request; + if (request != null) + { + this.Request = new WebApiRequestMessage(request); + } + } + + /// + /// The selected controller result. + /// + public SelectControllerResult ControllerResult { get; private set; } + + /// + /// Gets the request. + /// + public IWebApiRequestMessage Request { get; private set; } + + /// + /// Gets the route data. + /// + public IDictionary RouteData + { + get { return this.innerContext.RouteData.Values; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiOptions.cs new file mode 100644 index 0000000..8158c94 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiOptions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi options to OData WebApi. + /// + internal class WebApiOptions : IWebApiOptions + { + /// + /// Initializes a new instance of the WebApiOptions class. + /// + /// The inner configuration. + public WebApiOptions(HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + this.NullDynamicPropertyIsEnabled = configuration.HasEnabledNullDynamicProperty(); + this.UrlKeyDelimiter = configuration.GetUrlKeyDelimiter(); + } + + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not. + /// + public ODataUrlKeyDelimiter UrlKeyDelimiter { get; private set; } + + /// + /// Gets or Sets a value indicating if value should be emitted for dynamic properties which are null. + /// + public bool NullDynamicPropertyIsEnabled { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestHeaders.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestHeaders.cs new file mode 100644 index 0000000..be67398 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestHeaders.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi request headers to OData WebApi. + /// + internal class WebApiRequestHeaders : IWebApiHeaders + { + /// + /// The inner collection wrapped by this instance. + /// + private HttpRequestHeaders innerCollection; + + /// + /// Initializes a new instance of the WebApiRequestMessage class. + /// + /// The inner collection. + public WebApiRequestHeaders(HttpRequestHeaders headers) + { + if (headers == null) + { + throw Error.ArgumentNull("headers"); + } + + this.innerCollection = headers; + } + + /// + /// Return if a specified header and specified values are stored in the collection. + /// + /// The specified header. + /// The specified header values. + /// true is the specified header name and values are stored in the collection; otherwise false. + public bool TryGetValues(string key, out IEnumerable values) + { + return this.innerCollection.TryGetValues(key, out values); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestMessage.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestMessage.cs new file mode 100644 index 0000000..d505c42 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiRequestMessage.cs @@ -0,0 +1,228 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Web.Http; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi request message to OData WebApi. + /// + internal class WebApiRequestMessage : IWebApiRequestMessage + { + /// + /// The inner request wrapped by this instance. + /// + private HttpRequestMessage innerRequest; + + /// + /// Initializes a new instance of the WebApiRequestMessage class. + /// + /// The inner request. + public WebApiRequestMessage(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + this.innerRequest = request; + this.Headers = new WebApiRequestHeaders(request.Headers); + + HttpRequestMessageProperties context = request.ODataProperties(); + if (context != null) + { + this.Context = new WebApiContext(context); + } + + HttpConfiguration configuration = request.GetConfiguration(); + if (configuration != null) + { + this.Options = new WebApiOptions(configuration); + } + } + + /// + /// Gets the contents of the HTTP message. + /// + public IWebApiContext Context { get; private set; } + + /// + /// WebAPI headers associated with the request + /// + public IWebApiHeaders Headers { get; private set; } + + /// + /// Gets a value indicating if this is a count request. + /// + /// + public bool IsCountRequest() + { + return ODataCountMediaTypeMapping.IsCountRequest(this.innerRequest.ODataProperties().Path); + } + + /// + /// Gets the HTTP method used by the HTTP request message. + /// + public ODataRequestMethod Method + { + get + { + bool ignoreCase = true; + ODataRequestMethod methodEnum = ODataRequestMethod.Unknown; + if (Enum.TryParse(this.innerRequest.Method.ToString(), ignoreCase, out methodEnum)) + { + return methodEnum; + } + + return ODataRequestMethod.Unknown; + } + } + + /// + /// Get the options associated with the request. + /// + public IWebApiOptions Options { get; private set; } + + /// + /// The request container associated with the request. + /// + public IServiceProvider RequestContainer + { + get { return this.innerRequest.GetRequestContainer(); } + } + + /// + /// Gets the Uri used for the HTTP request. + /// + public Uri RequestUri + { + get { return this.innerRequest.RequestUri; } + } + + /// + /// Gets the deserializer provider associated with the request. + /// + /// + public ODataDeserializerProvider DeserializerProvider + { + get { return this.innerRequest.GetDeserializerProvider(); } + } + + /// + /// Get the next page link for a given page size. + /// + /// The page size. + /// The instance based on which the skiptoken value is generated + /// Function that takes in the last object and returns the skiptoken value string. + /// Returns the uri for the nextlink + public Uri GetNextPageLink(int pageSize, object instance = null, Func objToSkipTokenValue = null) + { + return this.innerRequest.GetNextPageLink(pageSize, instance, objToSkipTokenValue); + } + + /// + /// Creates an ETag from concurrency property names and values. + /// + /// The input property names and values. + /// The generated ETag string. + public string CreateETag(IDictionary properties) + { + HttpConfiguration configuration = this.innerRequest.GetConfiguration(); + if (configuration == null) + { + throw Error.InvalidOperation(SRResources.RequestMustContainConfiguration); + } + + EntityTagHeaderValue etag = configuration.GetETagHandler().CreateETag(properties); + return etag != null ? etag.ToString() : null; + } + + /// + /// Gets the EntityTagHeaderValue ETag>. + /// + /// This function uses types that are AspNet-specific. + public ETag GetETag(EntityTagHeaderValue etagHeaderValue) + { + return this.innerRequest.GetETag(etagHeaderValue); + } + + /// + /// Gets the EntityTagHeaderValue ETag>. + /// + /// This function uses types that are AspNet-specific. + public ETag GetETag(EntityTagHeaderValue etagHeaderValue) + { + return this.innerRequest.GetETag(etagHeaderValue); + } + + /// + /// Gets a list of content Id mappings associated with the request. + /// + /// + public IDictionary ODataContentIdMapping + { + get { return this.innerRequest.GetODataContentIdMapping(); } + } + + /// + /// Gets the path handler associated with the request. + /// + /// + public IODataPathHandler PathHandler + { + get { return this.innerRequest.GetPathHandler(); } + } + + /// + /// Gets the query parameters from the query with duplicated key ignored. + /// + /// + public IDictionary QueryParameters + { + get + { + IDictionary result = new Dictionary(); + foreach (var pair in this.innerRequest.GetQueryNameValuePairs()) + { + if (!result.ContainsKey(pair.Key)) + { + result.Add(pair.Key, pair.Value); + } + } + return result; + } + } + + /// + /// Gets the reader settings associated with the request. + /// + /// + public ODataMessageReaderSettings ReaderSettings + { + get { return this.innerRequest.GetReaderSettings(); } + } + + /// + /// Gets the writer settings associated with the request. + /// + /// + public ODataMessageWriterSettings WriterSettings + { + get { return this.innerRequest.GetWriterSettings(); } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiUrlHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiUrlHelper.cs new file mode 100644 index 0000000..0b0c67e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Adapters/WebApiUrlHelper.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData.UriParser; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi Url helper to OData WebApi. + /// + internal class WebApiUrlHelper : IWebApiUrlHelper + { + /// + /// The inner helper wrapped by this instance. + /// + private UrlHelper innerHelper; + + /// + /// Initializes a new instance of the WebApiUrlHelper class. + /// + /// The inner helper. + public WebApiUrlHelper(UrlHelper helper) + { + if (helper == null) + { + throw Error.ArgumentNull("helper"); + } + + this.innerHelper = helper; + } + + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The OData path segments. + /// The generated OData link. + public string CreateODataLink(params ODataPathSegment[] segments) + { + return this.innerHelper.CreateODataLink(segments); + } + + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The OData path segments. + /// The generated OData link. + public string CreateODataLink(IList segments) + { + return this.innerHelper.CreateODataLink(segments); + } + + /// + /// Generates an OData link using the given OData route name, path handler, and segments. + /// + /// The name of the OData route. + /// The path handler to use for generating the link. + /// The OData path segments. + /// The generated OData link. + public string CreateODataLink(string routeName, IODataPathHandler pathHandler, IList segments) + { + return this.innerHelper.CreateODataLink(routeName, pathHandler, segments); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetRequestItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetRequestItem.cs new file mode 100644 index 0000000..4c8a377 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetRequestItem.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents a ChangeSet request. + /// + public class ChangeSetRequestItem : ODataBatchRequestItem + { + /// + /// Initializes a new instance of the class. + /// + /// The request messages in the ChangeSet. + public ChangeSetRequestItem(IEnumerable requests) + { + if (requests == null) + { + throw Error.ArgumentNull("requests"); + } + + Requests = requests; + } + + /// + /// Gets the request messages in the ChangeSet. + /// + public IEnumerable Requests { get; private set; } + + /// + /// Sends the ChangeSet request. + /// + /// The invoker. + /// The token to monitor for cancellation requests. + /// A . + public override async Task SendRequestAsync(HttpMessageInvoker invoker, CancellationToken cancellationToken) + { + if (invoker == null) + { + throw Error.ArgumentNull("invoker"); + } + + Dictionary contentIdToLocationMapping = new Dictionary(); + List responses = new List(); + + try + { + foreach (HttpRequestMessage request in Requests) + { + HttpResponseMessage response = await SendMessageAsync(invoker, request, cancellationToken, contentIdToLocationMapping); + if (response.IsSuccessStatusCode) + { + responses.Add(response); + } + else + { + DisposeResponses(responses); + responses.Clear(); + responses.Add(response); + return new ChangeSetResponseItem(responses); + } + } + } + catch + { + DisposeResponses(responses); + throw; + } + + return new ChangeSetResponseItem(responses); + } + + /// + /// Gets the resources registered for dispose on each request messages of the ChangeSet. + /// + /// A collection of resources registered for dispose. + public override IEnumerable GetResourcesForDisposal() + { + List resources = new List(); + foreach (HttpRequestMessage request in Requests) + { + if (request != null) + { + resources.AddRange(request.GetResourcesForDisposal()); + } + } + return resources; + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + foreach (HttpRequestMessage request in Requests) + { + if (request != null) + { + request.Dispose(); + } + } + } + } + + internal static void DisposeResponses(List responses) + { + foreach (HttpResponseMessage response in responses) + { + if (response != null) + { + response.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetResponseItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetResponseItem.cs new file mode 100644 index 0000000..f32f231 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ChangeSetResponseItem.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents a ChangeSet response. + /// + public class ChangeSetResponseItem : ODataBatchResponseItem + { + /// + /// Initializes a new instance of the class. + /// + /// The response messages for the ChangeSet requests. + public ChangeSetResponseItem(IEnumerable responses) + { + if (responses == null) + { + throw Error.ArgumentNull("responses"); + } + + Responses = responses; + } + + /// + /// Gets the response messages for the ChangeSet. + /// + public IEnumerable Responses { get; private set; } + + /// + /// Writes the responses as a ChangeSet. + /// + /// The . + /// The token to monitor for cancellation requests. + public override async Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + + writer.WriteStartChangeset(); + + foreach (HttpResponseMessage responseMessage in Responses) + { + await WriteMessageAsync(writer, responseMessage, cancellationToken); + } + + writer.WriteEndChangeset(); + } + + /// + /// Gets a value that indicates if the responses in this item are successful. + /// + internal override bool IsResponseSuccessful() + { + return Responses.All(r => r.IsSuccessStatusCode); + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + foreach (HttpResponseMessage response in Responses) + { + if (response != null) + { + response.Dispose(); + } + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/DefaultODataBatchHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/DefaultODataBatchHandler.cs new file mode 100644 index 0000000..ba8ada3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/DefaultODataBatchHandler.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Batch; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Default implementation of for handling OData batch request. + /// + /// + /// By default, it buffers the request content stream. + /// + public class DefaultODataBatchHandler : ODataBatchHandler + { + /// + /// Initializes a new instance of the class. + /// + /// The for handling the individual batch requests. + public DefaultODataBatchHandler(HttpServer httpServer) + : base(httpServer) + { + } + + /// + public override async Task ProcessBatchAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + ValidateRequest(request); + + IList subRequests = await ParseBatchRequestsAsync(request, cancellationToken); + + HttpConfiguration configuration = request.GetConfiguration(); + bool enableContinueOnErrorHeader = (configuration != null) + ? configuration.HasEnabledContinueOnErrorHeader() + : false; + + SetContinueOnError(new WebApiRequestHeaders(request.Headers), enableContinueOnErrorHeader); + + try + { + IList responses = await ExecuteRequestMessagesAsync(subRequests, cancellationToken); + return await CreateResponseMessageAsync(responses, request, cancellationToken); + } + finally + { + foreach (ODataBatchRequestItem subRequest in subRequests) + { + request.RegisterForDispose(subRequest.GetResourcesForDisposal()); + request.RegisterForDispose(subRequest); + } + } + } + + /// + /// Executes the OData batch requests. + /// + /// The collection of OData batch requests. + /// The token to monitor for cancellation requests. + /// A collection of for the batch requests. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of response messages asynchronously.")] + public virtual async Task> ExecuteRequestMessagesAsync(IEnumerable requests, CancellationToken cancellationToken) + { + if (requests == null) + { + throw Error.ArgumentNull("requests"); + } + + IList responses = new List(); + + try + { + foreach (ODataBatchRequestItem request in requests) + { + ODataBatchResponseItem responseItem = await request.SendRequestAsync(Invoker, cancellationToken); + responses.Add(responseItem); + if (responseItem != null && responseItem.IsResponseSuccessful() == false && ContinueOnError == false) + { + break; + } + } + } + catch + { + foreach (ODataBatchResponseItem response in responses) + { + if (response != null) + { + response.Dispose(); + } + } + throw; + } + + return responses; + } + + /// + /// Converts the incoming OData batch request into a collection of request messages. + /// + /// The request containing the batch request messages. + /// The token to monitor for cancellation requests. + /// A collection of . + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")] + public virtual async Task> ParseBatchRequestsAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + IServiceProvider requestContainer = request.CreateRequestContainer(ODataRouteName); + requestContainer.GetRequiredService().BaseUri = GetBaseUri(request); + + ODataMessageReader reader = await request.Content.GetODataMessageReaderAsync(requestContainer, cancellationToken); + request.RegisterForDispose(reader); + + List requests = new List(); + ODataBatchReader batchReader = reader.CreateODataBatchReader(); + Guid batchId = Guid.NewGuid(); + while (batchReader.Read()) + { + if (batchReader.State == ODataBatchReaderState.ChangesetStart) + { + IList changeSetRequests = await batchReader.ReadChangeSetRequestAsync(batchId, cancellationToken); + foreach (HttpRequestMessage changeSetRequest in changeSetRequests) + { + changeSetRequest.CopyBatchRequestProperties(request); + changeSetRequest.DeleteRequestContainer(false); + } + requests.Add(new ChangeSetRequestItem(changeSetRequests)); + } + else if (batchReader.State == ODataBatchReaderState.Operation) + { + HttpRequestMessage operationRequest = await batchReader.ReadOperationRequestAsync(batchId, bufferContentStream: true, cancellationToken: cancellationToken); + operationRequest.CopyBatchRequestProperties(request); + operationRequest.DeleteRequestContainer(false); + requests.Add(new OperationRequestItem(operationRequest)); + } + } + + return requests; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/LazyStreamContent.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/LazyStreamContent.cs new file mode 100644 index 0000000..420addc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/LazyStreamContent.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.OData.Batch +{ + internal class LazyStreamContent : HttpContent + { + private Func _getStream; + private StreamContent _streamContent; + + public LazyStreamContent(Func getStream) + { + _getStream = getStream; + } + + private StreamContent StreamContent + { + get + { + if (_streamContent == null) + { + _streamContent = new StreamContent(_getStream()); + } + + return _streamContent; + } + } + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + return StreamContent.CopyToAsync(stream, context); + } + + protected override bool TryComputeLength(out long length) + { + length = -1; + return false; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchContent.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchContent.cs new file mode 100644 index 0000000..ba5549d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchContent.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Web.Http; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Encapsulates a collection of OData batch responses. + /// + /// + /// In AspNet, derives from . + /// + public partial class ODataBatchContent : HttpContent + { + /// + /// Initializes a new instance of the class. + /// + /// The batch responses. + /// The dependency injection container for the request. + /// This signature uses types that are AspNet-specific. + public ODataBatchContent(IEnumerable responses, IServiceProvider requestContainer) + : this(responses, requestContainer, null /*contentType*/) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The batch responses. + /// The dependency injection container for the request. + /// The response content type. + /// This signature uses types that are AspNet-specific. + public ODataBatchContent(IEnumerable responses, IServiceProvider requestContainer, + MediaTypeHeaderValue contentType) + { + this.Initialize(responses, requestContainer); + + if (contentType == null) + { + contentType = MediaTypeHeaderValue.Parse( + String.Format(CultureInfo.InvariantCulture, "multipart/mixed;boundary=batchresponse_{0}", Guid.NewGuid())); + } + + Headers.ContentType = contentType; + ODataVersion version = _writerSettings.Version ?? ODataVersionConstraint.DefaultODataVersion; + Headers.TryAddWithoutValidation(ODataVersionConstraint.ODataServiceVersionHeader, ODataUtils.ODataVersionToString(version)); + } + + /// + /// This function uses types that are AspNet-specific. + protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + IODataResponseMessage responseMessage = ODataMessageWrapperHelper.Create(stream, this.Headers, _requestContainer); + return WriteToResponseMessageAsync(responseMessage); + } + + /// + protected override bool TryComputeLength(out long length) + { + length = -1; + return false; + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + foreach (ODataBatchResponseItem response in Responses) + { + if (response != null) + { + response.Dispose(); + } + } + } + + base.Dispose(disposing); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHandler.cs new file mode 100644 index 0000000..cbafe78 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHandler.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Batch; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Defines the abstraction for handling OData batch requests. + /// + /// + /// This class implements a BatchHandler semantics for AspNet, which uses + /// an for dispatching requests. + /// + public abstract partial class ODataBatchHandler : HttpBatchHandler + { + /// + /// Initializes a new instance of the class. + /// + /// The for handling the individual batch requests. + protected ODataBatchHandler(HttpServer httpServer) + : base(httpServer) + { + } + + /// + /// Creates the batch response message. + /// + /// The responses for the batch requests. + /// The original request containing all the batch requests. + /// The token to monitor for cancellation requests. + /// The batch response message. + public virtual Task CreateResponseMessageAsync( + IEnumerable responses, + HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.CreateODataBatchResponseAsync(responses, MessageQuotas); + } + + /// + /// Validates the incoming request that contains the batch request messages. + /// + /// The request containing the batch request messages. + public virtual void ValidateRequest(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.ValidateODataBatchRequest(); + } + + /// + /// Gets the base URI for the batched requests. + /// + /// The original request containing all the batch requests. + /// The base URI. + public virtual Uri GetBaseUri(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetODataBatchBaseUri(ODataRouteName); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs new file mode 100644 index 0000000..422e600 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Results; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ODataBatchHttpRequestMessageExtensions + { + private const string BatchIdKey = "BatchId"; + private const string ChangeSetIdKey = "ChangesetId"; + private const string ContentIdKey = "ContentId"; + private const string ContentIdMappingKey = "ContentIdMapping"; + private const string BatchMediaTypeMime = "multipart/mixed"; + private const string BatchMediaTypeJson = "application/json"; + private const string Boundary = "boundary"; + + /// + /// Retrieves the Batch ID associated with the request. + /// + /// The request. + /// The Batch ID associated with this request, or null if there isn't one. + public static Guid? GetODataBatchId(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + object batchId; + if (request.Properties.TryGetValue(BatchIdKey, out batchId)) + { + return (Guid)batchId; + } + + return null; + } + + /// + /// Associates a given Batch ID with the request. + /// + /// The request. + /// The Batch ID. + public static void SetODataBatchId(this HttpRequestMessage request, Guid batchId) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.Properties[BatchIdKey] = batchId; + } + + /// + /// Retrieves the ChangeSet ID associated with the request. + /// + /// The request. + /// The ChangeSet ID associated with this request, or null if there isn't one. + public static Guid? GetODataChangeSetId(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + object changeSetId; + if (request.Properties.TryGetValue(ChangeSetIdKey, out changeSetId)) + { + return (Guid)changeSetId; + } + + return null; + } + + /// + /// Associates a given ChangeSet ID with the request. + /// + /// The request. + /// The ChangeSet ID. + public static void SetODataChangeSetId(this HttpRequestMessage request, Guid changeSetId) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.Properties[ChangeSetIdKey] = changeSetId; + } + + /// + /// Retrieves the Content-ID associated with the sub-request of a batch. + /// + /// The request. + /// The Content-ID associated with this request, or null if there isn't one. + public static string GetODataContentId(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + object contentId; + if (request.Properties.TryGetValue(ContentIdKey, out contentId)) + { + return (string)contentId; + } + + return null; + } + + /// + /// Associates a given Content-ID with the sub-request of a batch. + /// + /// The request. + /// The Content-ID. + public static void SetODataContentId(this HttpRequestMessage request, string contentId) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.Properties[ContentIdKey] = contentId; + } + + /// + /// Retrieves the Content-ID to Location mapping associated with the request. + /// + /// The request. + /// The Content-ID to Location mapping associated with this request, or null if there isn't one. + public static IDictionary GetODataContentIdMapping(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + object contentIdMapping; + if (request.Properties.TryGetValue(ContentIdMappingKey, out contentIdMapping)) + { + return contentIdMapping as IDictionary; + } + + return null; + } + + /// + /// Associates a given Content-ID to Location mapping with the request. + /// + /// The request. + /// The Content-ID to Location mapping. + public static void SetODataContentIdMapping(this HttpRequestMessage request, IDictionary contentIdMapping) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.Properties[ContentIdMappingKey] = contentIdMapping; + } + + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")] + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")] + internal static Task CreateODataBatchResponseAsync(this HttpRequestMessage request, IEnumerable responses, ODataMessageQuotas messageQuotas) + { + Contract.Assert(request != null); + + ODataVersion odataVersion = ResultHelpers.GetODataResponseVersion(request); + IServiceProvider requestContainer = request.GetRequestContainer(); + ODataMessageWriterSettings writerSettings = + requestContainer.GetRequiredService(); + writerSettings.Version = odataVersion; + writerSettings.MessageQuotas = messageQuotas; + + MediaTypeHeaderValue responseContentType = null; + if (request.Headers.Accept.Any( + t => t.MediaType.Equals(ODataBatchHttpRequestMessageExtensions.BatchMediaTypeMime, StringComparison.OrdinalIgnoreCase))) + { + responseContentType = MediaTypeHeaderValue.Parse( + String.Format(CultureInfo.InvariantCulture, "multipart/mixed;boundary=batchresponse_{0}", + Guid.NewGuid())); + } + else if (request.Headers.Accept.Any( + t => t.MediaType.Equals(ODataBatchHttpRequestMessageExtensions.BatchMediaTypeJson, StringComparison.OrdinalIgnoreCase))) + { + responseContentType = MediaTypeHeaderValue.Parse(ODataBatchHttpRequestMessageExtensions.BatchMediaTypeJson); + } + + HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK); + response.Content = new ODataBatchContent(responses, requestContainer, responseContentType); + return Task.FromResult(response); + } + + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")] + internal static void ValidateODataBatchRequest(this HttpRequestMessage request) + { + Contract.Assert(request != null); + + if (request.Content == null) + { + throw new HttpResponseException(request.CreateErrorResponse( + HttpStatusCode.BadRequest, + SRResources.BatchRequestMissingContent)); + } + + MediaTypeHeaderValue contentType = request.Content.Headers.ContentType; + if (contentType == null) + { + throw new HttpResponseException(request.CreateErrorResponse( + HttpStatusCode.BadRequest, + SRResources.BatchRequestMissingContentType)); + } + + bool isMimeBatch = String.Equals(contentType.MediaType, BatchMediaTypeMime, StringComparison.OrdinalIgnoreCase); + bool isJsonBatch = String.Equals(contentType.MediaType, BatchMediaTypeJson, StringComparison.OrdinalIgnoreCase); + + if (!isMimeBatch && !isJsonBatch) + { + throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, + Error.Format( + SRResources.BatchRequestInvalidMediaType, + BatchMediaTypeMime, + BatchMediaTypeJson))); + } + + if (isMimeBatch) + { + NameValueHeaderValue boundary = contentType.Parameters.FirstOrDefault( + p => String.Equals(p.Name, Boundary, StringComparison.OrdinalIgnoreCase)); + + if (boundary == null || String.IsNullOrEmpty(boundary.Value)) + { + throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, + SRResources.BatchRequestMissingBoundary)); + } + } + } + + internal static Uri GetODataBatchBaseUri(this HttpRequestMessage request, string oDataRouteName) + { + Contract.Assert(request != null); + + if (oDataRouteName == null) + { + // Return request's base address. + return new Uri(request.RequestUri, new Uri("/", UriKind.Relative)); + } + else + { + UrlHelper helper = request.GetUrlHelper() ?? new UrlHelper(request); + string baseAddress = helper.Link(oDataRouteName, new HttpRouteValueDictionary() { { ODataRouteConstants.ODataPath, String.Empty } }); + if (baseAddress == null) + { + throw new InvalidOperationException(SRResources.UnableToDetermineBaseUrl); + } + return new Uri(baseAddress); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchReaderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchReaderExtensions.cs new file mode 100644 index 0000000..196a84a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchReaderExtensions.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ODataBatchReaderExtensions + { + /// + /// Reads a ChangeSet request. + /// + /// The . + /// The Batch Id. + /// A collection of in the ChangeSet. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")] + public static Task> ReadChangeSetRequestAsync(this ODataBatchReader reader, Guid batchId) + { + return reader.ReadChangeSetRequestAsync(batchId, CancellationToken.None); + } + + /// + /// Reads a ChangeSet request. + /// + /// The . + /// The Batch Id. + /// The token to monitor for cancellation requests. + /// A collection of in the ChangeSet. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")] + public static async Task> ReadChangeSetRequestAsync(this ODataBatchReader reader, Guid batchId, CancellationToken cancellationToken) + { + if (reader == null) + { + throw Error.ArgumentNull("reader"); + } + if (reader.State != ODataBatchReaderState.ChangesetStart) + { + throw Error.InvalidOperation( + SRResources.InvalidBatchReaderState, + reader.State.ToString(), + ODataBatchReaderState.ChangesetStart.ToString()); + } + + Guid changeSetId = Guid.NewGuid(); + List requests = new List(); + while (reader.Read() && reader.State != ODataBatchReaderState.ChangesetEnd) + { + if (reader.State == ODataBatchReaderState.Operation) + { + requests.Add(await ReadOperationInternalAsync(reader, batchId, changeSetId, cancellationToken)); + } + } + return requests; + } + + /// + /// Reads an Operation request. + /// + /// The . + /// The Batch ID. + /// if set to true then the request content stream will be buffered. + /// A representing the operation. + public static Task ReadOperationRequestAsync(this ODataBatchReader reader, Guid batchId, bool bufferContentStream) + { + return reader.ReadOperationRequestAsync(batchId, bufferContentStream, CancellationToken.None); + } + + /// + /// Reads an Operation request. + /// + /// The . + /// The Batch ID. + /// if set to true then the request content stream will be buffered. + /// The token to monitor for cancellation requests. + /// A representing the operation. + public static Task ReadOperationRequestAsync(this ODataBatchReader reader, Guid batchId, bool bufferContentStream, CancellationToken cancellationToken) + { + if (reader == null) + { + throw Error.ArgumentNull("reader"); + } + if (reader.State != ODataBatchReaderState.Operation) + { + throw Error.InvalidOperation( + SRResources.InvalidBatchReaderState, + reader.State.ToString(), + ODataBatchReaderState.Operation.ToString()); + } + + return ReadOperationInternalAsync(reader, batchId, changeSetId: null, cancellationToken: cancellationToken, bufferContentStream: bufferContentStream); + } + + /// + /// Reads an Operation request in a ChangeSet. + /// + /// The . + /// The Batch ID. + /// The ChangeSet ID. + /// if set to true then the request content stream will be buffered. + /// A representing a ChangeSet operation + public static Task ReadChangeSetOperationRequestAsync(this ODataBatchReader reader, Guid batchId, Guid changeSetId, bool bufferContentStream) + { + return reader.ReadChangeSetOperationRequestAsync(batchId, changeSetId, bufferContentStream, CancellationToken.None); + } + + /// + /// Reads an Operation request in a ChangeSet. + /// + /// The . + /// The Batch ID. + /// The ChangeSet ID. + /// if set to true then the request content stream will be buffered. + /// The token to monitor for cancellation requests. + /// A representing a ChangeSet operation + public static Task ReadChangeSetOperationRequestAsync( + this ODataBatchReader reader, Guid batchId, Guid changeSetId, bool bufferContentStream, CancellationToken cancellationToken) + { + if (reader == null) + { + throw Error.ArgumentNull("reader"); + } + if (reader.State != ODataBatchReaderState.Operation) + { + throw Error.InvalidOperation( + SRResources.InvalidBatchReaderState, + reader.State.ToString(), + ODataBatchReaderState.Operation.ToString()); + } + + return ReadOperationInternalAsync(reader, batchId, changeSetId, cancellationToken, bufferContentStream); + } + + private static async Task ReadOperationInternalAsync( + ODataBatchReader reader, Guid batchId, Guid? changeSetId, CancellationToken cancellationToken, bool bufferContentStream = true) + { + ODataBatchOperationRequestMessage batchRequest = reader.CreateOperationRequestMessage(); + HttpRequestMessage request = new HttpRequestMessage(); + request.Method = new HttpMethod(batchRequest.Method); + request.RequestUri = batchRequest.Url; + + if (bufferContentStream) + { + using (Stream stream = batchRequest.GetStream()) + { + MemoryStream bufferedStream = new MemoryStream(); + // Passing in the default buffer size of 81920 so that we can also pass in a cancellation token + await stream.CopyToAsync(bufferedStream, bufferSize: 81920, cancellationToken: cancellationToken); + bufferedStream.Position = 0; + request.Content = new StreamContent(bufferedStream); + } + } + else + { + request.Content = new LazyStreamContent(() => batchRequest.GetStream()); + } + + foreach (var header in batchRequest.Headers) + { + string headerName = header.Key; + string headerValue = header.Value; + if (!request.Headers.TryAddWithoutValidation(headerName, headerValue)) + { + request.Content.Headers.TryAddWithoutValidation(headerName, headerValue); + } + } + + request.SetODataBatchId(batchId); + request.SetODataContentId(batchRequest.ContentId); + + if (changeSetId != null && changeSetId.HasValue) + { + request.SetODataChangeSetId(changeSetId.Value); + } + + return request; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchRequestItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchRequestItem.cs new file mode 100644 index 0000000..7d13125 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchRequestItem.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an OData batch request. + /// + public abstract class ODataBatchRequestItem : IDisposable + { + /// + /// Sends a single OData batch request. + /// + /// The invoker. + /// The request. + /// The token to monitor for cancellation requests. + /// The Content-ID to Location mapping. + /// + public static async Task SendMessageAsync(HttpMessageInvoker invoker, HttpRequestMessage request, CancellationToken cancellationToken, Dictionary contentIdToLocationMapping) + { + if (invoker == null) + { + throw Error.ArgumentNull("invoker"); + } + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (contentIdToLocationMapping != null) + { + string resolvedRequestUrl = ContentIdHelpers.ResolveContentId(request.RequestUri.OriginalString, contentIdToLocationMapping); + request.RequestUri = new Uri(resolvedRequestUrl); + + request.SetODataContentIdMapping(contentIdToLocationMapping); + } + + HttpResponseMessage response = await invoker.SendAsync(request, cancellationToken); + string contentId = request.GetODataContentId(); + + if (contentIdToLocationMapping != null && contentId != null) + { + AddLocationHeaderToMapping(response, contentIdToLocationMapping, contentId); + } + + return response; + } + + private static void AddLocationHeaderToMapping( + HttpResponseMessage response, + IDictionary contentIdToLocationMapping, + string contentId) + { + Contract.Assert(response != null); + Contract.Assert(response.Headers != null); + Contract.Assert(contentIdToLocationMapping != null); + Contract.Assert(contentId != null); + + if (response.Headers.Location != null) + { + contentIdToLocationMapping.Add(contentId, response.Headers.Location.AbsoluteUri); + } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Gets the resources for disposal. + /// + /// A collection of resources for disposal. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "The order of execution matters. The result can be different after calling SendMessageAsync.")] + public abstract IEnumerable GetResourcesForDisposal(); + + /// + /// Sends the request. + /// + /// The invoker. + /// The token to monitor for cancellation requests. + /// A . + public abstract Task SendRequestAsync(HttpMessageInvoker invoker, CancellationToken cancellationToken); + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected abstract void Dispose(bool disposing); + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchResponseItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchResponseItem.cs new file mode 100644 index 0000000..f60bfd6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataBatchResponseItem.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an OData batch response. + /// + public abstract class ODataBatchResponseItem : IDisposable + { + /// + /// Writes a single OData batch response. + /// + /// The . + /// The response message. + /// A task object representing writing the given batch response using the given writer. + public static Task WriteMessageAsync(ODataBatchWriter writer, HttpResponseMessage response) + { + return WriteMessageAsync(writer, response, CancellationToken.None); + } + + /// + /// Writes a single OData batch response. + /// + /// The . + /// The response message. + /// The token to monitor for cancellation requests. + /// A task object representing writing the given batch response using the given writer. + public static async Task WriteMessageAsync(ODataBatchWriter writer, HttpResponseMessage response, + CancellationToken cancellationToken) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + if (response == null) + { + throw Error.ArgumentNull("response"); + } + + HttpRequestMessage request = response.RequestMessage; + string contentId = (request != null) ? request.GetODataContentId() : String.Empty; + + ODataBatchOperationResponseMessage batchResponse = writer.CreateOperationResponseMessage(contentId); + + batchResponse.StatusCode = (int)response.StatusCode; + + foreach (KeyValuePair> header in response.Headers) + { + batchResponse.SetHeader(header.Key, String.Join(",", header.Value)); + } + + if (response.Content != null) + { + foreach (KeyValuePair> header in response.Content.Headers) + { + batchResponse.SetHeader(header.Key, String.Join(",", header.Value)); + } + + using (Stream stream = batchResponse.GetStream()) + { + cancellationToken.ThrowIfCancellationRequested(); + await response.Content.CopyToAsync(stream); + } + } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Writes the response. + /// + /// The . + /// The token to monitor for cancellation requests. + public abstract Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken); + + /// + /// Writes the response. + /// + /// The . + /// + /// This method exists to provide a consistent API to . + /// The AspNetCore call does not need the CancellationToken passed in and instead of + /// adding an internal call on that side, I opted to add the internal call here since + /// the AspNetCore call would ignore the parameter and this one just assumes one. + /// + internal Task WriteResponseAsync(ODataBatchWriter writer) + { + return WriteResponseAsync(writer, CancellationToken.None); + } + + /// + /// Gets a value that indicates if the responses in this item are successful. + /// + internal virtual bool IsResponseSuccessful() + { + return false; + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected abstract void Dispose(bool disposing); + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataHttpContentExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataHttpContentExtensions.cs new file mode 100644 index 0000000..cf05428 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/ODataHttpContentExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ODataHttpContentExtensions + { + /// + /// Gets the for the stream. + /// + /// The dependency injection container for the request. + /// The . + /// A task object that produces an when completed. + public static Task GetODataMessageReaderAsync(this HttpContent content, + IServiceProvider requestContainer) + { + return GetODataMessageReaderAsync(content, requestContainer, CancellationToken.None); + } + + /// + /// Gets the for the stream. + /// + /// The dependency injection container for the request. + /// The . + /// The token to monitor for cancellation requests. + /// A task object that produces an when completed. + public static async Task GetODataMessageReaderAsync(this HttpContent content, + IServiceProvider requestContainer, CancellationToken cancellationToken) + { + if (content == null) + { + throw Error.ArgumentNull("content"); + } + + cancellationToken.ThrowIfCancellationRequested(); + Stream contentStream = await content.ReadAsStreamAsync(); + + IODataRequestMessage oDataRequestMessage = ODataMessageWrapperHelper.Create(contentStream, content.Headers, + requestContainer); + ODataMessageReaderSettings settings = requestContainer.GetRequiredService(); + ODataMessageReader oDataMessageReader = new ODataMessageReader(oDataRequestMessage, settings); + return oDataMessageReader; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationRequestItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationRequestItem.cs new file mode 100644 index 0000000..3ea9d4f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationRequestItem.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an Operation request. + /// + public class OperationRequestItem : ODataBatchRequestItem + { + /// + /// Initializes a new instance of the class. + /// + /// The Operation request. + public OperationRequestItem(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + Request = request; + } + + /// + /// Gets the Operation request. + /// + public HttpRequestMessage Request { get; private set; } + + /// + /// Sends the Operation request. + /// + /// The invoker. + /// The token to monitor for cancellation requests. + /// A . + public override async Task SendRequestAsync(HttpMessageInvoker invoker, CancellationToken cancellationToken) + { + if (invoker == null) + { + throw Error.ArgumentNull("invoker"); + } + + HttpResponseMessage response = await SendMessageAsync(invoker, Request, cancellationToken, null); + return new OperationResponseItem(response); + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + Request.Dispose(); + } + } + + /// + /// Gets the resources registered for dispose on the Operation request message. + /// + /// A collection of resources registered for dispose. + public override IEnumerable GetResourcesForDisposal() + { + return Request.GetResourcesForDisposal(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationResponseItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationResponseItem.cs new file mode 100644 index 0000000..a7fd0ca --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/OperationResponseItem.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an Operation response. + /// + public class OperationResponseItem : ODataBatchResponseItem + { + /// + /// Initializes a new instance of the class. + /// + /// The response messages for the Operation request. + public OperationResponseItem(HttpResponseMessage response) + { + if (response == null) + { + throw Error.ArgumentNull("response"); + } + + Response = response; + } + + /// + /// Gets the response messages for the Operation. + /// + public HttpResponseMessage Response { get; private set; } + + /// + /// Writes the response as an Operation. + /// + /// The . + /// The token to monitor for cancellation requests. + public override Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + + return WriteMessageAsync(writer, Response, cancellationToken); + } + + /// + /// Gets a value that indicates if the responses in this item are successful. + /// + internal override bool IsResponseSuccessful() + { + return Response.IsSuccessStatusCode; + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + Response.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/UnbufferedODataBatchHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/UnbufferedODataBatchHandler.cs new file mode 100644 index 0000000..8f87ee0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Batch/UnbufferedODataBatchHandler.cs @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Batch; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// An implementation of that doesn't buffer the request content stream. + /// + public class UnbufferedODataBatchHandler : ODataBatchHandler + { + /// + /// Initializes a new instance of the class. + /// + /// The for handling the individual batch requests. + public UnbufferedODataBatchHandler(HttpServer httpServer) + : base(httpServer) + { + } + + /// + public override async Task ProcessBatchAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + ValidateRequest(request); + + // This container is for the overall batch request. + IServiceProvider requestContainer = request.CreateRequestContainer(ODataRouteName); + requestContainer.GetRequiredService().BaseUri = GetBaseUri(request); + + ODataMessageReader reader = await request.Content.GetODataMessageReaderAsync(requestContainer, cancellationToken); + request.RegisterForDispose(reader); + + ODataBatchReader batchReader = reader.CreateODataBatchReader(); + List responses = new List(); + Guid batchId = Guid.NewGuid(); + + HttpConfiguration configuration = request.GetConfiguration(); + bool enableContinueOnErrorHeader = (configuration != null) + ? configuration.HasEnabledContinueOnErrorHeader() + : false; + + SetContinueOnError(new WebApiRequestHeaders(request.Headers), enableContinueOnErrorHeader); + + try + { + while (batchReader.Read()) + { + ODataBatchResponseItem responseItem = null; + if (batchReader.State == ODataBatchReaderState.ChangesetStart) + { + responseItem = await ExecuteChangeSetAsync(batchReader, batchId, request, cancellationToken); + } + else if (batchReader.State == ODataBatchReaderState.Operation) + { + responseItem = await ExecuteOperationAsync(batchReader, batchId, request, cancellationToken); + } + if (responseItem != null) + { + responses.Add(responseItem); + if (responseItem.IsResponseSuccessful() == false && ContinueOnError == false) + { + break; + } + } + } + } + catch + { + foreach (ODataBatchResponseItem response in responses) + { + if (response != null) + { + response.Dispose(); + } + } + throw; + } + + return await CreateResponseMessageAsync(responses, request, cancellationToken); + } + + /// + /// Executes the operation. + /// + /// The batch reader. + /// The batch id. + /// The original request containing all the batch requests. + /// The token to monitor for cancellation requests. + /// The response for the operation. + public virtual async Task ExecuteOperationAsync(ODataBatchReader batchReader, Guid batchId, HttpRequestMessage originalRequest, CancellationToken cancellationToken) + { + if (batchReader == null) + { + throw Error.ArgumentNull("batchReader"); + } + if (originalRequest == null) + { + throw Error.ArgumentNull("originalRequest"); + } + + cancellationToken.ThrowIfCancellationRequested(); + HttpRequestMessage operationRequest = await batchReader.ReadOperationRequestAsync(batchId, bufferContentStream: false); + + operationRequest.CopyBatchRequestProperties(originalRequest); + operationRequest.DeleteRequestContainer(false); + OperationRequestItem operation = new OperationRequestItem(operationRequest); + try + { + ODataBatchResponseItem response = await operation.SendRequestAsync(Invoker, cancellationToken); + return response; + } + finally + { + originalRequest.RegisterForDispose(operation.GetResourcesForDisposal()); + originalRequest.RegisterForDispose(operation); + } + } + + /// + /// Executes the ChangeSet. + /// + /// The batch reader. + /// The batch id. + /// The original request containing all the batch requests. + /// The token to monitor for cancellation requests. + /// The response for the ChangeSet. + public virtual async Task ExecuteChangeSetAsync(ODataBatchReader batchReader, Guid batchId, HttpRequestMessage originalRequest, CancellationToken cancellationToken) + { + if (batchReader == null) + { + throw Error.ArgumentNull("batchReader"); + } + if (originalRequest == null) + { + throw Error.ArgumentNull("originalRequest"); + } + + Guid changeSetId = Guid.NewGuid(); + List changeSetResponse = new List(); + Dictionary contentIdToLocationMapping = new Dictionary(); + try + { + while (batchReader.Read() && batchReader.State != ODataBatchReaderState.ChangesetEnd) + { + if (batchReader.State == ODataBatchReaderState.Operation) + { + HttpRequestMessage changeSetOperationRequest = await batchReader.ReadChangeSetOperationRequestAsync(batchId, changeSetId, bufferContentStream: false); + changeSetOperationRequest.CopyBatchRequestProperties(originalRequest); + changeSetOperationRequest.DeleteRequestContainer(false); + try + { + HttpResponseMessage response = await ODataBatchRequestItem.SendMessageAsync(Invoker, changeSetOperationRequest, cancellationToken, contentIdToLocationMapping); + if (response.IsSuccessStatusCode) + { + changeSetResponse.Add(response); + } + else + { + ChangeSetRequestItem.DisposeResponses(changeSetResponse); + changeSetResponse.Clear(); + changeSetResponse.Add(response); + return new ChangeSetResponseItem(changeSetResponse); + } + } + finally + { + originalRequest.RegisterForDispose(changeSetOperationRequest.GetResourcesForDisposal()); + originalRequest.RegisterForDispose(changeSetOperationRequest); + } + } + } + } + catch + { + ChangeSetRequestItem.DisposeResponses(changeSetResponse); + throw; + } + + return new ChangeSetResponseItem(changeSetResponse); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Builder/ODataConventionModelBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Builder/ODataConventionModelBuilder.cs new file mode 100644 index 0000000..4186812 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Builder/ODataConventionModelBuilder.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; +using System.Web.Http.Dispatcher; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// is used to automatically map CLR classes to an EDM model based on a set of. + /// + public partial class ODataConventionModelBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// + /// This constructor will work stand-alone scenarios but it does require using the + /// to get a list of assemblies to build + /// the model. + /// + public ODataConventionModelBuilder() + : this(WebApiAssembliesResolver.Default) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + /// This function uses types that are AspNet-specific. + public ODataConventionModelBuilder(HttpConfiguration configuration) + : this(configuration, isQueryCompositionMode: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + /// If the model is being built for only querying. + /// The model built if is true has more relaxed + /// inference rules and also treats all types as entity types. This constructor is intended for use by unit testing only. + /// This signature uses types that are AspNet-specific. + public ODataConventionModelBuilder(HttpConfiguration configuration, bool isQueryCompositionMode) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + // Create an IWebApiAssembliesResolver from configuration and initialize. + IAssembliesResolver aspnetResolver = configuration.Services.GetAssembliesResolver(); + IWebApiAssembliesResolver internalResolver = new WebApiAssembliesResolver(aspnetResolver); + Initialize(internalResolver, isQueryCompositionMode); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ETagMessageHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ETagMessageHandler.cs new file mode 100644 index 0000000..e3b4c97 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ETagMessageHandler.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; + +namespace Microsoft.AspNet.OData +{ + /// + /// Defines a to add an ETag header value to an OData response when the response + /// is a single resource that has an ETag defined. + /// + public partial class ETagMessageHandler : DelegatingHandler + { + /// + protected async override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + HttpConfiguration configuration = request.GetConfiguration(); + if (configuration == null) + { + throw Error.InvalidOperation(SRResources.RequestMustContainConfiguration); + } + + HttpResponseMessage response = await base.SendAsync(request, cancellationToken); + + ObjectContent content = (response == null) ? null : response.Content as ObjectContent; + if (content != null) + { + int? responseCode = (response == null) ? null : (int?)response.StatusCode; + EntityTagHeaderValue etag = GetETag( + responseCode, + request.ODataProperties().Path, + request.GetModel(), + content.Value, + configuration.GetETagHandler()); + + if (etag != null) + { + response.Headers.ETag = etag; + } + } + + return response; + } + + // This overload is for unit testing purposes only. + internal Task SendAsync(HttpRequestMessage request) + { + return SendAsync(request, CancellationToken.None); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/EnableQueryAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/EnableQueryAttribute.cs new file mode 100644 index 0000000..27f2a7a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/EnableQueryAttribute.cs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// This class defines an attribute that can be applied to an action to enable querying using the OData query + /// syntax. To avoid processing unexpected or malicious queries, use the validation settings on + /// to validate incoming queries. For more information, visit + /// http://go.microsoft.com/fwlink/?LinkId=279712. + /// + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", + Justification = "We want to be able to subclass this type.")] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public partial class EnableQueryAttribute : ActionFilterAttribute + { + /// + /// Performs the query composition after action is executed. It first tries to retrieve the IQueryable from the + /// returning response message. It then validates the query from uri based on the validation settings on + /// . It finally applies the query appropriately, and reset it back on + /// the response message. + /// + /// The context related to this action, including the response message, + /// request message and HttpConfiguration etc. + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", + Justification = "The majority of types referenced by this method result from HttpActionExecutedContext")] + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext == null) + { + throw Error.ArgumentNull("actionExecutedContext"); + } + + HttpRequestMessage request = actionExecutedContext.Request; + if (request == null) + { + throw Error.Argument("actionExecutedContext", SRResources.ActionExecutedContextMustHaveRequest); + } + + HttpConfiguration configuration = request.GetConfiguration(); + if (configuration == null) + { + throw Error.Argument("actionExecutedContext", SRResources.RequestMustContainConfiguration); + } + + if (actionExecutedContext.ActionContext == null) + { + throw Error.Argument("actionExecutedContext", SRResources.ActionExecutedContextMustHaveActionContext); + } + + HttpActionDescriptor actionDescriptor = actionExecutedContext.ActionContext.ActionDescriptor; + if (actionDescriptor == null) + { + throw Error.Argument("actionExecutedContext", SRResources.ActionContextMustHaveDescriptor); + } + + HttpResponseMessage response = actionExecutedContext.Response; + + if (response != null && response.IsSuccessStatusCode && response.Content != null) + { + ObjectContent responseContent = response.Content as ObjectContent; + if (responseContent == null) + { + throw Error.Argument("actionExecutedContext", SRResources.QueryingRequiresObjectContent, + response.Content.GetType().FullName); + } + + // Get collection from SingleResult. + IQueryable singleResultCollection = null; + SingleResult singleResult = responseContent.Value as SingleResult; + if (singleResult != null) + { + // This could be a SingleResult, which has the property Queryable. + // But it could be a SingleResult() or SingleResult. Sort by number of parameters + // on the property and get the one with the most parameters. + PropertyInfo propInfo = responseContent.Value.GetType().GetProperties() + .OrderBy(p => p.GetIndexParameters().Count()) + .Where(p => p.Name.Equals("Queryable")) + .LastOrDefault(); + + singleResultCollection = propInfo.GetValue(singleResult) as IQueryable; + } + + // Execution the action. + object queryResult = OnActionExecuted( + responseContent.Value, + singleResultCollection, + new WebApiActionDescriptor(actionDescriptor), + new WebApiRequestMessage(request), + (elementClrType) => GetModel(elementClrType, request, actionDescriptor), + (queryContext) => CreateAndValidateQueryOptions(request, queryContext), + (statusCode) => actionExecutedContext.Response = request.CreateResponse(statusCode), + (statusCode, message, exception) => actionExecutedContext.Response = request.CreateErrorResponse(statusCode, message, exception)); + + if (queryResult != null) + { + responseContent.Value = queryResult; + } + } + } + + /// + /// Create and validate a new instance of from a query and context. + /// + /// The incoming request. + /// The query context. + /// + private ODataQueryOptions CreateAndValidateQueryOptions(HttpRequestMessage request, ODataQueryContext queryContext) + { + ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, request); + ValidateQuery(request, queryOptions); + + return queryOptions; + } + + /// + /// Validates the OData query in the incoming request. By default, the implementation throws an exception if + /// the query contains unsupported query parameters. Override this method to perform additional validation of + /// the query. + /// + /// The incoming request. + /// + /// The instance constructed based on the incoming request. + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", + Justification = "Response disposed after being sent.")] + public virtual void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (queryOptions == null) + { + throw Error.ArgumentNull("queryOptions"); + } + + IEnumerable> queryParameters = request.GetQueryNameValuePairs(); + foreach (KeyValuePair kvp in queryParameters) + { + if (!queryOptions.IsSupportedQueryOption(kvp.Key) && + kvp.Key.StartsWith("$", StringComparison.Ordinal)) + { + // we don't support any custom query options that start with $ + throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, + Error.Format(SRResources.QueryParameterNotSupported, kvp.Key))); + } + } + + queryOptions.Validate(_validationSettings); + } + + /// + /// Gets the EDM model for the given type and request. Override this method to customize the EDM model used for + /// querying. + /// + /// The CLR type to retrieve a model for. + /// The request message to retrieve a model for. + /// The action descriptor for the action being queried on. + /// The EDM model for the given type and request. + public virtual IEdmModel GetModel(Type elementClrType, HttpRequestMessage request, + HttpActionDescriptor actionDescriptor) + { + // Get model for the request + IEdmModel model = request.GetModel(); + + if (model == EdmCoreModel.Instance || model.GetEdmType(elementClrType) == null) + { + // user has not configured anything or has registered a model without the element type + // let's create one just for this type and cache it in the action descriptor + model = actionDescriptor.GetEdmModel(elementClrType); + } + + Contract.Assert(model != null); + return model; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpActionDescriptorExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpActionDescriptorExtensions.cs new file mode 100644 index 0000000..4591903 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpActionDescriptorExtensions.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Dispatcher; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Extensions +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class HttpActionDescriptorExtensions + { + // Maintain the Microsoft.AspNet.OData. prefix in any new properties to avoid conflicts with user properties + // and those of the v3 assembly. Concern is reduced here due to addition of user type name but prefix + // also clearly ties the property to code in this assembly. + private const string ModelKeyPrefix = "Microsoft.AspNet.OData.Model+"; + + internal static IEdmModel GetEdmModel(this HttpActionDescriptor actionDescriptor, Type entityClrType) + { + if (actionDescriptor == null) + { + throw Error.ArgumentNull("actionDescriptor"); + } + + if (entityClrType == null) + { + throw Error.ArgumentNull("entityClrType"); + } + + // save the EdmModel to the action descriptor + return actionDescriptor.Properties.GetOrAdd(ModelKeyPrefix + entityClrType.FullName, _ => + { + IAssembliesResolver resolver = actionDescriptor.Configuration.Services.GetAssembliesResolver(); + ODataConventionModelBuilder builder = + new ODataConventionModelBuilder(new WebApiAssembliesResolver(resolver), isQueryCompositionMode: true); + EntityTypeConfiguration entityTypeConfiguration = builder.AddEntityType(entityClrType); + builder.AddEntitySet(entityClrType.Name, entityTypeConfiguration); + IEdmModel edmModel = builder.GetEdmModel(); + Contract.Assert(edmModel != null); + return edmModel; + }) as IEdmModel; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs new file mode 100644 index 0000000..c5eb03a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs @@ -0,0 +1,871 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Dispatcher; +using System.Web.Http.Filters; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ServiceLifetime = Microsoft.OData.ServiceLifetime; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class HttpConfigurationExtensions + { + // Maintain the Microsoft.AspNet.OData. prefix in any new properties to avoid conflicts with user properties + // and those of the v3 assembly. + private const string ETagHandlerKey = "Microsoft.AspNet.OData.ETagHandler"; + + private const string TimeZoneInfoKey = "Microsoft.AspNet.OData.TimeZoneInfo"; + + private const string UrlKeyDelimiterKey = "Microsoft.AspNet.OData.UrlKeyDelimiterKey"; + + private const string ContinueOnErrorKey = "Microsoft.AspNet.OData.ContinueOnErrorKey"; + + private const string NullDynamicPropertyKey = "Microsoft.AspNet.OData.NullDynamicPropertyKey"; + + private const string ContainerBuilderFactoryKey = "Microsoft.AspNet.OData.ContainerBuilderFactoryKey"; + + private const string PerRouteContainerKey = "Microsoft.AspNet.OData.PerRouteContainerKey"; + + private const string DefaultQuerySettingsKey = "Microsoft.AspNet.OData.DefaultQuerySettings"; + + private const string NonODataRootContainerKey = "Microsoft.AspNet.OData.NonODataRootContainerKey"; + + /// + /// Enables query support for actions with an or return + /// type. To avoid processing unexpected or malicious queries, use the validation settings on + /// to validate incoming queries. For more information, visit + /// http://go.microsoft.com/fwlink/?LinkId=279712. + /// + /// The server configuration. + public static void AddODataQueryFilter(this HttpConfiguration configuration) + { + AddODataQueryFilter(configuration, new EnableQueryAttribute()); + } + + /// + /// Sets the in the configuration. + /// + public static void SetDefaultQuerySettings(this HttpConfiguration configuration, DefaultQuerySettings defaultQuerySettings) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (defaultQuerySettings == null) + { + throw Error.ArgumentNull("defaultQuerySettings"); + } + + if (!defaultQuerySettings.MaxTop.HasValue || defaultQuerySettings.MaxTop > 0) + { + ModelBoundQuerySettings.DefaultModelBoundQuerySettings.MaxTop = defaultQuerySettings.MaxTop; + } + + configuration.Properties[DefaultQuerySettingsKey] = defaultQuerySettings; + } + + /// + /// Gets the from the configuration. + /// + /// The server configuration. + public static DefaultQuerySettings GetDefaultQuerySettings(this HttpConfiguration configuration) + { + object instance; + if (!configuration.Properties.TryGetValue(DefaultQuerySettingsKey, out instance)) + { + DefaultQuerySettings defaultQuerySettings = new DefaultQuerySettings(); + configuration.SetDefaultQuerySettings(defaultQuerySettings); + return defaultQuerySettings; + } + + return instance as DefaultQuerySettings; + } + + /// + /// Sets the MaxTop of in the configuration. + /// + public static HttpConfiguration MaxTop(this HttpConfiguration configuration, int? maxTopValue) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.MaxTop = maxTopValue; + if (!maxTopValue.HasValue || maxTopValue > 0) + { + ModelBoundQuerySettings.DefaultModelBoundQuerySettings.MaxTop = maxTopValue; + } + + return configuration; + } + + /// + /// Sets the EnableExpand of in the configuration, + /// depends on . + /// Todo: change QueryOptionSetting to SelectExpandType. + /// + public static HttpConfiguration Expand(this HttpConfiguration configuration, QueryOptionSetting setting) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableExpand = setting == QueryOptionSetting.Allowed; + return configuration; + } + + /// + /// Sets the EnableExpand to true of in the configuration. + /// + public static HttpConfiguration Expand(this HttpConfiguration configuration) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableExpand = true; + return configuration; + } + + /// + /// Sets the SelectType of in the configuration, + /// depends on . + /// Todo: change QueryOptionSetting to SelectExpandType. + /// + public static HttpConfiguration Select(this HttpConfiguration configuration, QueryOptionSetting setting) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSelect = setting == QueryOptionSetting.Allowed; + return configuration; + } + + /// + /// Sets the EnableSelect to true of in the configuration. + /// + public static HttpConfiguration Select(this HttpConfiguration configuration) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSelect = true; + return configuration; + } + + /// + /// Sets the EnableFilter of in the configuration, + /// depends on . + /// + public static HttpConfiguration Filter(this HttpConfiguration configuration, QueryOptionSetting setting) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableFilter = setting == QueryOptionSetting.Allowed; + return configuration; + } + + /// + /// Sets the EnableFilter to true of in the configuration. + /// + public static HttpConfiguration Filter(this HttpConfiguration configuration) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableFilter = true; + return configuration; + } + + /// + /// Sets the EnableOrderBy of in the configuration, + /// depends on . + /// + public static HttpConfiguration OrderBy(this HttpConfiguration configuration, QueryOptionSetting setting) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableOrderBy = setting == QueryOptionSetting.Allowed; + return configuration; + } + + /// + /// Sets the EnableOrderBy to true of in the configuration. + /// + public static HttpConfiguration OrderBy(this HttpConfiguration configuration) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableOrderBy = true; + return configuration; + } + + /// + /// Sets the EnableSkipToken of in the configuration, + /// depends on . + /// + public static HttpConfiguration SkipToken(this HttpConfiguration configuration, QueryOptionSetting setting) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSkipToken = setting == QueryOptionSetting.Allowed; + return configuration; + } + + /// + /// Sets the EnableSkipToken to true of in the configuration. + /// + public static HttpConfiguration SkipToken(this HttpConfiguration configuration) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSkipToken = true; + return configuration; + } + + /// + /// Sets the EnableCount of in the configuration, + /// depends on . + /// + public static HttpConfiguration Count(this HttpConfiguration configuration, QueryOptionSetting setting) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableCount = setting == QueryOptionSetting.Allowed; + return configuration; + } + + /// + /// Sets the EnableCount to true of in the configuration. + /// + public static HttpConfiguration Count(this HttpConfiguration configuration) + { + DefaultQuerySettings defaultQuerySettings = configuration.GetDefaultQuerySettings(); + defaultQuerySettings.EnableCount = true; + return configuration; + } + + /// + /// Enables query support for actions with an or return + /// type. To avoid processing unexpected or malicious queries, use the validation settings on + /// to validate incoming queries. For more information, visit + /// http://go.microsoft.com/fwlink/?LinkId=279712. + /// + /// The server configuration. + /// The action filter that executes the query. + public static void AddODataQueryFilter(this HttpConfiguration configuration, IActionFilter queryFilter) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + configuration.Services.Add(typeof(IFilterProvider), new QueryFilterProvider(queryFilter)); + } + + /// + /// Gets the from the configuration. + /// + /// The server configuration. + /// The for the configuration. + public static IETagHandler GetETagHandler(this HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + object handler; + if (!configuration.Properties.TryGetValue(ETagHandlerKey, out handler)) + { + IETagHandler defaultETagHandler = new DefaultODataETagHandler(); + configuration.SetETagHandler(defaultETagHandler); + return defaultETagHandler; + } + + if (handler == null) + { + throw Error.InvalidOperation(SRResources.NullETagHandler); + } + + IETagHandler etagHandler = handler as IETagHandler; + if (etagHandler == null) + { + throw Error.InvalidOperation(SRResources.InvalidETagHandler, handler.GetType()); + } + + return etagHandler; + } + + /// + /// Sets the on the configuration. + /// + /// The server configuration. + /// The for the configuration. + public static void SetETagHandler(this HttpConfiguration configuration, IETagHandler handler) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + if (handler == null) + { + throw Error.ArgumentNull("handler"); + } + + configuration.Properties[ETagHandlerKey] = handler; + } + + /// + /// Gets the from the configuration. + /// + /// The server configuration. + /// The for the configuration. + public static TimeZoneInfo GetTimeZoneInfo(this HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + object value; + TimeZoneInfo timeZoneInfo; + if (!configuration.Properties.TryGetValue(TimeZoneInfoKey, out value)) + { + timeZoneInfo = TimeZoneInfo.Local; + configuration.SetTimeZoneInfo(timeZoneInfo); + return timeZoneInfo; + } + + timeZoneInfo = value as TimeZoneInfo; + if (timeZoneInfo == null) + { + throw Error.InvalidOperation(SRResources.InvalidTimeZoneInfo, value.GetType(), typeof(TimeZoneInfo)); + } + + return timeZoneInfo; + } + + /// + /// Sets the on the configuration. + /// + /// The server configuration. + /// The for the configuration. + /// + public static void SetTimeZoneInfo(this HttpConfiguration configuration, TimeZoneInfo timeZoneInfo) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (timeZoneInfo == null) + { + throw Error.ArgumentNull("timeZoneInfo"); + } + + configuration.Properties[TimeZoneInfoKey] = timeZoneInfo; + TimeZoneInfoHelper.TimeZone = timeZoneInfo; + } + + /// + /// Enable the continue-on-error header. + /// + public static void EnableContinueOnErrorHeader(this HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + configuration.Properties[ContinueOnErrorKey] = true; + } + + /// + /// Check the continue-on-error header is enable or not. + /// + /// True if continue-on-error header is enable; false otherwise + internal static bool HasEnabledContinueOnErrorHeader(this HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + object value; + if (configuration.Properties.TryGetValue(ContinueOnErrorKey, out value)) + { + return (bool)value; + } + return false; + } + + /// + /// Sets whether or not the null dynamic property to be serialized. + /// + /// The server configuration. + /// true to serialize null dynamic property, false otherwise. + public static void SetSerializeNullDynamicProperty(this HttpConfiguration configuration, bool serialize) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + configuration.Properties[NullDynamicPropertyKey] = serialize; + } + + /// + /// Set the UrlKeyDelimiter in DefaultODataPathHandler. + /// + /// The server configuration. + /// The + public static void SetUrlKeyDelimiter(this HttpConfiguration configuration, ODataUrlKeyDelimiter urlKeyDelimiter) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + configuration.Properties[UrlKeyDelimiterKey] = urlKeyDelimiter; + } + + /// + /// Check the null dynamic property is enable or not. + /// + /// + internal static bool HasEnabledNullDynamicProperty(this HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + object value; + if (configuration.Properties.TryGetValue(NullDynamicPropertyKey, out value)) + { + return (bool)value; + } + + return false; + } + + internal static ODataUrlKeyDelimiter GetUrlKeyDelimiter(this HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + object value; + if (configuration.Properties.TryGetValue(UrlKeyDelimiterKey, out value)) + { + return value as ODataUrlKeyDelimiter; + } + + configuration.Properties[UrlKeyDelimiterKey] = null; + return null; + } + + /// + /// Specifies a custom container builder. + /// + /// The server configuration. + /// The factory to create a container builder. + /// The server configuration. + public static HttpConfiguration UseCustomContainerBuilder(this HttpConfiguration configuration, Func builderFactory) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (builderFactory == null) + { + throw Error.ArgumentNull("builderFactory"); + } + + configuration.Properties[ContainerBuilderFactoryKey] = builderFactory; + + return configuration; + } + + /// + /// Enables dependency injection support for HTTP routes. + /// + /// The server configuration. + public static void EnableDependencyInjection(this HttpConfiguration configuration) + { + configuration.EnableDependencyInjection(null); + } + + /// + /// Enables dependency injection support for HTTP routes. + /// + /// The server configuration. + /// The configuring action to add the services to the root container. + public static void EnableDependencyInjection(this HttpConfiguration configuration, + Action configureAction) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (configuration.Properties.ContainsKey(NonODataRootContainerKey)) + { + throw Error.InvalidOperation(SRResources.CannotReEnableDependencyInjection); + } + + // Get the per-route container and create a new non-route container. + IPerRouteContainer perRouteContainer = GetPerRouteContainer(configuration); + perRouteContainer.CreateODataRootContainer(null, ConfigureDefaultServices(configuration, configureAction)); + } + + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The configuring action to add the services to the root container. + /// The added . + public static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, + string routePrefix, Action configureAction) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (routeName == null) + { + throw Error.ArgumentNull("routeName"); + } + + // 1) Build and configure the root container. + IServiceProvider rootContainer = configuration.CreateODataRootContainer(routeName, configureAction); + + // 2) Resolve the path handler and set URI resolver to it. + IODataPathHandler pathHandler = rootContainer.GetRequiredService(); + + // if settings is not on local, use the global configuration settings. + if (pathHandler != null && pathHandler.UrlKeyDelimiter == null) + { + ODataUrlKeyDelimiter urlKeyDelimiter = configuration.GetUrlKeyDelimiter(); + pathHandler.UrlKeyDelimiter = urlKeyDelimiter; + } + + // 3) Resolve some required services and create the route constraint. + ODataPathRouteConstraint routeConstraint = new ODataPathRouteConstraint(routeName); + + // Attribute routing must initialized before configuration.EnsureInitialized is called. + rootContainer.GetServices(); + + // 4) Resolve HTTP handler, create the OData route and register it. + ODataRoute route; + HttpRouteCollection routes = configuration.Routes; + routePrefix = RemoveTrailingSlash(routePrefix); + HttpMessageHandler messageHandler = rootContainer.GetService(); + if (messageHandler != null) + { + route = new ODataRoute( + routePrefix, + routeConstraint, + defaults: null, + constraints: null, + dataTokens: null, + handler: messageHandler); + } + else + { + ODataBatchHandler batchHandler = rootContainer.GetService(); + if (batchHandler != null) + { + batchHandler.ODataRouteName = routeName; + string batchTemplate = String.IsNullOrEmpty(routePrefix) ? ODataRouteConstants.Batch + : routePrefix + '/' + ODataRouteConstants.Batch; + routes.MapHttpBatchRoute(routeName + "Batch", batchTemplate, batchHandler); + } + + route = new ODataRoute(routePrefix, routeConstraint); + } + + routes.Add(routeName, route); + return route; + } + + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The added . + public static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, + string routePrefix, IEdmModel model) + { + return configuration.MapODataServiceRoute(routeName, routePrefix, builder => + builder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService>(ServiceLifetime.Singleton, sp => + ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, configuration))); + } + + /// + /// Maps the specified OData route and the OData route attributes. When the is + /// non-null, it will create a '$batch' endpoint to handle the batch requests. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The . + /// The added . + public static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, + string routePrefix, IEdmModel model, ODataBatchHandler batchHandler) + { + return configuration.MapODataServiceRoute(routeName, routePrefix, builder => + builder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService(ServiceLifetime.Singleton, sp => batchHandler) + .AddService>(ServiceLifetime.Singleton, sp => + ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, configuration))); + } + + /// + /// Maps the specified OData route and the OData route attributes. When the + /// is non-null, it will map it as the default handler for the route. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The default for this route. + /// The added . + public static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, + string routePrefix, IEdmModel model, HttpMessageHandler defaultHandler) + { + return configuration.MapODataServiceRoute(routeName, routePrefix, builder => + builder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService(ServiceLifetime.Singleton, sp => defaultHandler) + .AddService>(ServiceLifetime.Singleton, sp => + ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, configuration))); + } + + /// + /// Maps the specified OData route. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The to use for parsing the OData path. + /// + /// The OData routing conventions to use for controller and action selection. + /// + /// The added . + public static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, + string routePrefix, IEdmModel model, IODataPathHandler pathHandler, + IEnumerable routingConventions) + { + return configuration.MapODataServiceRoute(routeName, routePrefix, builder => + builder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService(ServiceLifetime.Singleton, sp => pathHandler) + .AddService(ServiceLifetime.Singleton, sp => routingConventions.ToList().AsEnumerable())); + } + + /// + /// Maps the specified OData route. When the is non-null, it will + /// create a '$batch' endpoint to handle the batch requests. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The to use for parsing the OData path. + /// + /// The OData routing conventions to use for controller and action selection. + /// + /// The . + /// The added . + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", + Justification = "We want the handler to be a batch handler.")] + public static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, + string routePrefix, IEdmModel model, IODataPathHandler pathHandler, + IEnumerable routingConventions, ODataBatchHandler batchHandler) + { + return configuration.MapODataServiceRoute(routeName, routePrefix, builder => + builder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService(ServiceLifetime.Singleton, sp => pathHandler) + .AddService(ServiceLifetime.Singleton, sp => routingConventions.ToList().AsEnumerable()) + .AddService(ServiceLifetime.Singleton, sp => batchHandler)); + } + + /// + /// Maps the specified OData route. When the is non-null, it will map + /// it as the handler for the route. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The to use for parsing the OData path. + /// + /// The OData routing conventions to use for controller and action selection. + /// + /// The default for this route. + /// The added . + public static ODataRoute MapODataServiceRoute(this HttpConfiguration configuration, string routeName, + string routePrefix, IEdmModel model, IODataPathHandler pathHandler, + IEnumerable routingConventions, HttpMessageHandler defaultHandler) + { + return configuration.MapODataServiceRoute(routeName, routePrefix, builder => + builder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService(ServiceLifetime.Singleton, sp => pathHandler) + .AddService(ServiceLifetime.Singleton, sp => routingConventions.ToList().AsEnumerable()) + .AddService(ServiceLifetime.Singleton, sp => defaultHandler)); + } + + private static string RemoveTrailingSlash(string routePrefix) + { + if (!String.IsNullOrEmpty(routePrefix)) + { + int prefixLastIndex = routePrefix.Length - 1; + if (routePrefix[prefixLastIndex] == '/') + { + // Remove the last trailing slash if it has one. + routePrefix = routePrefix.Substring(0, routePrefix.Length - 1); + } + } + return routePrefix; + } + + /// + /// Create the per-route container from the configuration for a given route. + /// + /// The configuration. + /// The route name. + /// The configuring action to add the services to the root container. + /// The per-route container from the configuration + internal static IServiceProvider CreateODataRootContainer(this HttpConfiguration configuration, + string routeName, Action configureAction) + { + IPerRouteContainer perRouteContainer = configuration.GetPerRouteContainer(); + return perRouteContainer.CreateODataRootContainer(routeName, ConfigureDefaultServices(configuration, configureAction)); + } + + /// + /// Get the per-route container from the configuration. + /// + /// The configuration. + /// The per-route container from the configuration + internal static IPerRouteContainer GetPerRouteContainer(this HttpConfiguration configuration) + { + return (IPerRouteContainer)configuration.Properties.GetOrAdd( + PerRouteContainerKey, + key => + { + IPerRouteContainer perRouteContainer = new PerRouteContainer(configuration); + + // Attach the build factory if there is one. + object value; + if (configuration.Properties.TryGetValue(ContainerBuilderFactoryKey, out value)) + { + Func builderFactory = (Func)value; + perRouteContainer.BuilderFactory = builderFactory; + } + + return perRouteContainer; + }); + } + + /// + /// Get the OData root container for a given route. + /// + /// The configuration. + /// The route name. + /// The OData root container for a given route. + internal static IServiceProvider GetODataRootContainer(this HttpConfiguration configuration, string routeName) + { + IPerRouteContainer perRouteContainer = GetPerRouteContainer(configuration); + return perRouteContainer.GetODataRootContainer(routeName); + } + + /// + /// Get the OData root container for HTTP routes. + /// + /// The configuration. + /// The OData root container for HTTP routes. + internal static IServiceProvider GetNonODataRootContainer(this HttpConfiguration configuration) + { + object value; + if (configuration.Properties.TryGetValue(NonODataRootContainerKey, out value)) + { + return (IServiceProvider)value; + } + + throw Error.InvalidOperation(SRResources.NoNonODataHttpRouteRegistered); + } + + /// + /// Enables dependency injection support for HTTP routes. + /// + /// The server configuration. + /// The root container. + internal static void SetNonODataRootContainer(this HttpConfiguration configuration, + IServiceProvider rootContainer) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (rootContainer == null) + { + throw Error.ArgumentNull("rootContainer"); + } + + if (configuration.Properties.ContainsKey(NonODataRootContainerKey)) + { + throw Error.InvalidOperation(SRResources.CannotReEnableDependencyInjection); + } + + configuration.Properties[NonODataRootContainerKey] = rootContainer; + } + + /// + /// Configure the default services. + /// + /// The configuration. + /// The configuring action to add the services to the root container. + /// A configuring action to add the services to the root container. + private static Action ConfigureDefaultServices(HttpConfiguration configuration, Action configureAction) + { + return (builder => + { + // Add platform-specific services here. Add Configuration first as other services may rely on it. + // For assembly resolution, add both the public (IAssembliesResolver) and internal (IWebApiAssembliesResolver) + // where IWebApiAssembliesResolver is transient and instantiated from IAssembliesResolver by DI. + IAssembliesResolver resolver = configuration.Services.GetAssembliesResolver() ?? new DefaultAssembliesResolver(); + builder.AddService(ServiceLifetime.Singleton, sp => resolver); + builder.AddService(ServiceLifetime.Transient); + + builder.AddService(ServiceLifetime.Singleton, sp => configuration); + builder.AddService(ServiceLifetime.Singleton, sp => configuration.GetDefaultQuerySettings()); + + // Currently, the ETagHandler is attached to the configuration. + //builder.AddService(ServiceLifetime.Singleton); + + // Add the default webApi services. + builder.AddDefaultWebApiServices(); + + // Add custom actions. + if (configureAction != null) + { + configureAction.Invoke(builder); + } + }); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpErrorExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpErrorExtensions.cs new file mode 100644 index 0000000..b50cb31 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpErrorExtensions.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Web.Http; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class HttpErrorExtensions + { + /// + /// Converts the to an . + /// + /// The instance to convert. + /// The converted + public static ODataError CreateODataError(this HttpError httpError) + { + if (httpError == null) + { + throw Error.ArgumentNull("httpError"); + } + + return new ODataError + { + Message = httpError.GetPropertyValue(HttpErrorKeys.MessageKey), + ErrorCode = httpError.GetPropertyValue(HttpErrorKeys.ErrorCodeKey), + InnerError = ToODataInnerError(httpError) + }; + } + + private static ODataInnerError ToODataInnerError(HttpError httpError) + { + string innerErrorMessage = httpError.GetPropertyValue(HttpErrorKeys.ExceptionMessageKey); + if (innerErrorMessage == null) + { + string messageDetail = httpError.GetPropertyValue(HttpErrorKeys.MessageDetailKey); + if (messageDetail == null) + { + HttpError modelStateError = httpError.GetPropertyValue(HttpErrorKeys.ModelStateKey); + return (modelStateError == null) ? null + : new ODataInnerError { Message = ConvertModelStateErrors(modelStateError) }; + } + else + { + return new ODataInnerError() { Message = messageDetail }; + } + } + else + { + ODataInnerError innerError = new ODataInnerError(); + innerError.Message = innerErrorMessage; + innerError.TypeName = httpError.GetPropertyValue(HttpErrorKeys.ExceptionTypeKey); + innerError.StackTrace = httpError.GetPropertyValue(HttpErrorKeys.StackTraceKey); + HttpError innerExceptionError = httpError.GetPropertyValue(HttpErrorKeys.InnerExceptionKey); + if (innerExceptionError != null) + { + innerError.InnerError = ToODataInnerError(innerExceptionError); + } + return innerError; + } + } + + // Convert the model state errors in to a string (for debugging only). + // This should be improved once ODataError allows more details. + private static string ConvertModelStateErrors(HttpError error) + { + StringBuilder builder = new StringBuilder(); + foreach (KeyValuePair modelStateError in error) + { + if (modelStateError.Value != null) + { + builder.Append(modelStateError.Key); + builder.Append(" : "); + + IEnumerable errorMessages = modelStateError.Value as IEnumerable; + if (errorMessages != null) + { + foreach (string errorMessage in errorMessages) + { + builder.AppendLine(errorMessage); + } + } + else + { + builder.AppendLine(modelStateError.Value.ToString()); + } + } + } + + return builder.ToString(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageExtensions.cs new file mode 100644 index 0000000..cec65ce --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageExtensions.cs @@ -0,0 +1,432 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Web.Http; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class HttpRequestMessageExtensions + { + private const string PropertiesKey = "Microsoft.AspNet.OData.Properties"; + private const string RequestContainerKey = "Microsoft.AspNet.OData.RequestContainer"; + private const string RequestScopeKey = "Microsoft.AspNet.OData.RequestScope"; + + /// + /// Gets the instance containing OData methods and properties + /// for given . + /// + /// The request of interest. + /// + /// An object through which OData methods and properties for given are available. + /// + public static HttpRequestMessageProperties ODataProperties(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + // Cache HttpRequestMessageProperties value to avoid lots of identical objects with no instance data. + HttpRequestMessageProperties properties; + object value; + if (request.Properties.TryGetValue(PropertiesKey, out value)) + { + properties = value as HttpRequestMessageProperties; + Contract.Assert(properties != null); + } + else + { + properties = new HttpRequestMessageProperties(request); + + // Avoid race conditions: Do not use Add(). Worst case here is an extra HttpRequestMessageProperties + // instance which will soon go out of scope. + request.Properties[PropertiesKey] = properties; + } + + return properties; + } + + /// + /// Helper method that performs content negotiation and creates a + /// representing an error with an instance of wrapping + /// as the content. If no formatter is found, this method returns a response with + /// status 406 NotAcceptable. + /// + /// This method requires that has been associated with an instance of + /// . + /// + /// The request of interest. + /// The status code of the created response. + /// The OData error to wrap. + /// + /// An error response wrapping with status code . + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "o", + Justification = "oDataError is spelled correctly.")] + public static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, + HttpStatusCode statusCode, ODataError oDataError) + { + if (request.ShouldIncludeErrorDetail()) + { + return request.CreateResponse(statusCode, oDataError); + } + else + { + return request.CreateResponse( + statusCode, + new ODataError + { + ErrorCode = oDataError.ErrorCode, + Message = oDataError.Message, + }); + } + } + + /// + /// Gets the OData from the given request. + /// + /// The request. + /// The entity tag header value. + /// The parsed . + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")] + public static ETag GetETag(this HttpRequestMessage request, EntityTagHeaderValue entityTagHeaderValue) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (entityTagHeaderValue != null) + { + if (entityTagHeaderValue.Equals(EntityTagHeaderValue.Any)) + { + return new ETag { IsAny = true }; + } + + HttpConfiguration configuration = request.GetConfiguration(); + if (configuration == null) + { + throw Error.InvalidOperation(SRResources.RequestMustContainConfiguration); + } + + // get the etag handler, and parse the etag + IDictionary properties = + configuration.GetETagHandler().ParseETag(entityTagHeaderValue) ?? new Dictionary(); + IList parsedETagValues = properties.Select(property => property.Value).AsList(); + + // get property names from request + ODataPath odataPath = request.ODataProperties().Path; + IEdmModel model = request.GetModel(); + IEdmNavigationSource source = odataPath.NavigationSource; + if (model != null && source != null) + { + IList concurrencyProperties = model.GetConcurrencyProperties(source).ToList(); + IList concurrencyPropertyNames = concurrencyProperties.OrderBy(c => c.Name).Select(c => c.Name).AsList(); + ETag etag = new ETag(); + + if (parsedETagValues.Count != concurrencyPropertyNames.Count) + { + etag.IsWellFormed = false; + } + + IEnumerable> nameValues = concurrencyPropertyNames.Zip( + parsedETagValues, + (name, value) => new KeyValuePair(name, value)); + foreach (var nameValue in nameValues) + { + IEdmStructuralProperty property = concurrencyProperties.SingleOrDefault(e => e.Name == nameValue.Key); + Contract.Assert(property != null); + + Type clrType = EdmLibHelpers.GetClrType(property.Type, model); + Contract.Assert(clrType != null); + + if (nameValue.Value != null) + { + Type valueType = nameValue.Value.GetType(); + etag[nameValue.Key] = valueType != clrType + ? Convert.ChangeType(nameValue.Value, clrType, CultureInfo.InvariantCulture) + : nameValue.Value; + } + else + { + etag[nameValue.Key] = nameValue.Value; + } + } + + return etag; + } + } + + return null; + } + + /// + /// Gets the from the given request. + /// + /// The request. + /// The entity tag header value. + /// The parsed . + public static ETag GetETag(this HttpRequestMessage request, EntityTagHeaderValue entityTagHeaderValue) + { + ETag etag = request.GetETag(entityTagHeaderValue); + return etag != null + ? new ETag + { + ConcurrencyProperties = etag.ConcurrencyProperties, + IsWellFormed = etag.IsWellFormed, + IsAny = etag.IsAny, + } + : null; + } + + /// + /// Creates a link for the next page of results; To be used as the value of @odata.nextLink. + /// + /// The request on which to base the next page link. + /// The number of results allowed per page. + /// A next page link. + public static Uri GetNextPageLink(this HttpRequestMessage request, int pageSize) + { + return request.GetNextPageLink(pageSize, null, null); + } + + /// + /// Creates a link for the next page of results; To be used as the value of @odata.nextLink. + /// + /// The request on which to base the next page link. + /// The number of results allowed per page. + /// The instance based on which the skiptoken value is generated. + /// Function that extracts out the skiptoken value from the instance. + /// A next page link. + public static Uri GetNextPageLink(this HttpRequestMessage request, int pageSize, object instance, Func objToSkipTokenValue) + { + if (request == null || request.RequestUri == null) + { + throw Error.ArgumentNull("request"); + } + + return GetNextPageHelper.GetNextPageLink(request.RequestUri, request.GetQueryNameValuePairs(), pageSize, instance, objToSkipTokenValue); + } + + /// + /// Gets the dependency injection container for the OData request. + /// + /// The request. + /// The dependency injection container. + public static IServiceProvider GetRequestContainer(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + object value; + if (request.Properties.TryGetValue(RequestContainerKey, out value)) + { + return (IServiceProvider)value; + } + + // HTTP routes will not have chance to call CreateRequestContainer. + // We have to call it. + return request.CreateRequestContainer(null); + } + + /// + /// Creates a request container that associates with the . + /// + /// The request. + /// The name of the route. + /// The request container created. + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", + Justification = "requestScope will be disposed when the request ends.")] + public static IServiceProvider CreateRequestContainer(this HttpRequestMessage request, string routeName) + { + if (request.Properties.ContainsKey(RequestContainerKey)) + { + throw Error.InvalidOperation(SRResources.RequestContainerAlreadyExists); + } + + IServiceScope requestScope = request.CreateRequestScope(routeName); + IServiceProvider requestContainer = requestScope.ServiceProvider; + + request.Properties[RequestScopeKey] = requestScope; + request.Properties[RequestContainerKey] = requestContainer; + + return requestContainer; + } + + /// + /// Deletes the request container from the and disposes + /// the container if is true. + /// + /// The request. + /// + /// Returns true to dispose the request container after deletion; false otherwise. + /// + public static void DeleteRequestContainer(this HttpRequestMessage request, bool dispose) + { + object value; + if (request.Properties.TryGetValue(RequestScopeKey, out value)) + { + IServiceScope requestScope = (IServiceScope)value; + Contract.Assert(requestScope != null); + + request.Properties.Remove(RequestScopeKey); + request.Properties.Remove(RequestContainerKey); + + if (dispose) + { + requestScope.Dispose(); + } + } + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static IEdmModel GetModel(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static ODataMessageWriterSettings GetWriterSettings(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static ODataMessageReaderSettings GetReaderSettings(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static IODataPathHandler GetPathHandler(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static ODataSerializerProvider GetSerializerProvider(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static ODataDeserializerProvider GetDeserializerProvider(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the set of from the request container. + /// + /// The request. + /// The set of from the request container. + public static IEnumerable GetRoutingConventions(this HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetServices(); + } + + private static IServiceScope CreateRequestScope(this HttpRequestMessage request, string routeName) + { + HttpConfiguration configuration = request.GetConfiguration(); + if (configuration == null) + { + throw Error.Argument("request", SRResources.RequestMustContainConfiguration); + } + + IServiceProvider rootContainer = configuration.GetODataRootContainer(routeName); + IServiceScope scope = rootContainer.GetRequiredService().CreateScope(); + + // Bind scoping request into the OData container. + if (routeName != null) + { + scope.ServiceProvider.GetRequiredService().HttpRequest = request; + } + + return scope; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageProperties.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageProperties.cs new file mode 100644 index 0000000..b4db1f5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpRequestMessageProperties.cs @@ -0,0 +1,331 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.OData; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides properties for use with the extension + /// method. + /// + public class HttpRequestMessageProperties + { + // Maintain the Microsoft.AspNet.OData. prefix in any new properties to avoid conflicts with user properties + // and those of the v3 assembly. + private const string DeltaLinkKey = "Microsoft.AspNet.OData.DeltaLink"; + private const string NextLinkKey = "Microsoft.AspNet.OData.NextLink"; + private const string PathKey = "Microsoft.AspNet.OData.Path"; + private const string RouteNameKey = "Microsoft.AspNet.OData.RouteName"; + private const string RoutingConventionsStoreKey = "Microsoft.AspNet.OData.RoutingConventionsStore"; + private const string SelectExpandClauseKey = "Microsoft.AspNet.OData.SelectExpandClause"; + private const string ApplyClauseKey = "Microsoft.AspNet.OData.ApplyClause"; + private const string TotalCountKey = "Microsoft.AspNet.OData.TotalCount"; + private const string TotalCountFuncKey = "Microsoft.AspNet.OData.TotalCountFunc"; + private const string PageSizeKey = "Microsoft.AspNet.OData.PageSize"; + private const string QueryOptionsKey = "Microsoft.AspNet.OData.QueryOptions"; + + private HttpRequestMessage _request; + + internal HttpRequestMessageProperties(HttpRequestMessage request) + { + Contract.Assert(request != null); + _request = request; + } + + internal Func TotalCountFunc + { + get + { + object totalCountFunc; + if (_request.Properties.TryGetValue(TotalCountFuncKey, out totalCountFunc)) + { + return (Func)totalCountFunc; + } + + return null; + } + set + { + _request.Properties[TotalCountFuncKey] = value; + } + } + + /// + /// Page size to be used by skiptoken implementation for the top-level resource for the request. + /// + internal int PageSize + { + get + { + object pageSize; + if (_request.Properties.TryGetValue(PageSizeKey, out pageSize)) + { + return (int)pageSize; + } + return -1; + } + set + { + _request.Properties[PageSizeKey] = value; + } + } + + /// + /// Gets or sets the parsed OData of the request. The + /// will use this information (if any) while writing the response for + /// this request. + /// + internal ODataQueryOptions QueryOptions + { + get + { + return GetValueOrNull(QueryOptionsKey); + } + set + { + if (value == null) + { + throw Error.ArgumentNull("value"); + } + + _request.Properties[QueryOptionsKey] = value; + } + } + + /// + /// Gets or sets the route name for generating OData links. + /// + public string RouteName + { + get + { + return GetValueOrNull(RouteNameKey); + } + set + { + _request.Properties[RouteNameKey] = value; + } + } + + /// + /// Gets or sets the OData path of the request. + /// + public ODataPath Path + { + get + { + return GetValueOrNull(PathKey); + } + set + { + _request.Properties[PathKey] = value; + } + } + + /// + /// Gets or sets the total count for the OData response. + /// + /// null if no count should be sent back to the client. + public long? TotalCount + { + get + { + object totalCount; + if (_request.Properties.TryGetValue(TotalCountKey, out totalCount)) + { + // TotalCount is defined as null-able long, allowed to be set as null, so it should be + // safe to cast to null-able type of long as result. + return (long?)totalCount; + } + + if (this.TotalCountFunc != null) + { + long count = this.TotalCountFunc(); + _request.Properties[TotalCountKey] = count; + return count; + } + + return null; + } + set + { + _request.Properties[TotalCountKey] = value; + } + } + + /// + /// Gets or sets the next link for the OData response. + /// + /// null if no next link should be sent back to the client. + public Uri NextLink + { + get + { + return GetValueOrNull(NextLinkKey); + } + set + { + _request.Properties[NextLinkKey] = value; + } + } + + /// + /// Gets or sets the delta link for the OData response. + /// + /// null if no delta link should be sent back to the client. + public Uri DeltaLink + { + get + { + return GetValueOrNull(DeltaLinkKey); + } + set + { + _request.Properties[DeltaLinkKey] = value; + } + } + + /// + /// Gets or sets the parsed OData of the request. The + /// will use this information (if any) while writing the response for + /// this request. + /// + public SelectExpandClause SelectExpandClause + { + get + { + return GetValueOrNull(SelectExpandClauseKey); + } + set + { + if (value == null) + { + throw Error.ArgumentNull("value"); + } + + _request.Properties[SelectExpandClauseKey] = value; + } + } + + /// + /// Gets or sets the parsed OData of the request. The + /// will use this information (if any) while writing the response for + /// this request. + /// + public ApplyClause ApplyClause + { + get + { + return GetValueOrNull(ApplyClauseKey); + } + set + { + if (value == null) + { + throw Error.ArgumentNull("value"); + } + + _request.Properties[ApplyClauseKey] = value; + } + } + + /// + /// Gets the data store used by s to store any custom route data. + /// + /// Initially an empty IDictionary<string, object>. + public IDictionary RoutingConventionsStore + { + get + { + IDictionary store = + GetValueOrNull>(RoutingConventionsStoreKey); + if (store == null) + { + store = new Dictionary(); + RoutingConventionsStore = store; + } + + return store; + } + private set + { + _request.Properties[RoutingConventionsStoreKey] = value; + } + } + + internal ODataVersion? ODataServiceVersion + { + get + { + return GetODataVersionFromHeader(_request.Headers, ODataVersionConstraint.ODataServiceVersionHeader); + } + } + + internal ODataVersion? ODataMaxServiceVersion + { + get + { + return GetODataVersionFromHeader(_request.Headers, ODataVersionConstraint.ODataMaxServiceVersionHeader); + } + } + + internal ODataVersion? ODataMinServiceVersion + { + get + { + return GetODataVersionFromHeader(_request.Headers, ODataVersionConstraint.ODataMinServiceVersionHeader); + } + } + + private static ODataVersion? GetODataVersionFromHeader(HttpHeaders headers, string headerName) + { + IEnumerable values; + if (headers.TryGetValues(headerName, out values)) + { + string value = values.FirstOrDefault(); + if (value != null) + { + string trimmedValue = value.Trim(' ', ';'); + try + { + return ODataUtils.StringToODataVersion(trimmedValue); + } + catch (ODataException) + { + // Parsing the odata version failed. + } + } + } + + return null; + } + + private T GetValueOrNull(string propertyName) where T : class + { + object value; + if (_request.Properties.TryGetValue(propertyName, out value)) + { + // Fairly big problem if following cast fails. Indicates something else is writing properties with + // names we've chosen. Do not silently return null because that will hide the problem. + return (T)value; + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/UrlHelperExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/UrlHelperExtensions.cs new file mode 100644 index 0000000..5a8019e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/UrlHelperExtensions.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Net.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing; +using ODataPathSegment = Microsoft.OData.UriParser.ODataPathSegment; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class UrlHelperExtensions + { + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The URL helper. + /// The OData path segments. + /// The generated OData link. + public static string CreateODataLink(this UrlHelper urlHelper, params ODataPathSegment[] segments) + { + return CreateODataLink(urlHelper, segments as IList); + } + + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The URL helper. + /// The OData path segments. + /// The generated OData link. + public static string CreateODataLink(this UrlHelper urlHelper, IList segments) + { + if (urlHelper == null) + { + throw Error.ArgumentNull("urlHelper"); + } + + HttpRequestMessage request = urlHelper.Request; + Contract.Assert(request != null); + + string routeName = request.ODataProperties().RouteName; + if (String.IsNullOrEmpty(routeName)) + { + throw Error.InvalidOperation(SRResources.RequestMustHaveODataRouteName); + } + + IODataPathHandler pathHandler = request.GetPathHandler(); + return CreateODataLink(urlHelper, routeName, pathHandler, segments); + } + + /// + /// Generates an OData link using the given OData route name, path handler, and segments. + /// + /// The URL helper. + /// The name of the OData route. + /// The path handler to use for generating the link. + /// The OData path segments. + /// The generated OData link. + public static string CreateODataLink(this UrlHelper urlHelper, string routeName, IODataPathHandler pathHandler, + IList segments) + { + if (urlHelper == null) + { + throw Error.ArgumentNull("urlHelper"); + } + + if (pathHandler == null) + { + throw Error.ArgumentNull("pathHandler"); + } + + string odataPath = pathHandler.Link(new ODataPath(segments)); + return urlHelper.Link( + routeName, + new HttpRouteValueDictionary() { { ODataRouteConstants.ODataPath, odataPath } }); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs new file mode 100644 index 0000000..51ce178 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using Microsoft.AspNet.OData.Extensions; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// The default . + /// + public partial class DefaultODataDeserializerProvider : ODataDeserializerProvider + { + /// + /// This signature uses types that are AspNet-specific. + public override ODataDeserializer GetODataDeserializer(Type type, HttpRequestMessage request) + { + // Using a Func to delay evaluation of the model. + return GetODataDeserializerImpl(type, () => request.GetModel()); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerContext.cs new file mode 100644 index 0000000..a1fbe37 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerContext.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// This class encapsulates the state and settings that get passed to + /// from the . + /// + public partial class ODataDeserializerContext + { + private HttpRequestMessage _request; + + /// + /// Gets or sets the HTTP Request that is being deserialized. + /// + /// This signature uses types that are AspNet-specific. + public HttpRequestMessage Request + { + get { return _request; } + set + { + _request = value; + InternalRequest = _request != null ? new WebApiRequestMessage(_request) : null; + InternalUrlHelper = _request != null ? new WebApiUrlHelper(_request.GetUrlHelper()) : null; + } + } + + /// Gets or sets the request context. + /// This signature uses types that are AspNet-specific. + public HttpRequestContext RequestContext { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerProvider.cs new file mode 100644 index 0000000..963d135 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Deserialization/ODataDeserializerProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents a factory that creates an . + /// + public abstract partial class ODataDeserializerProvider + { + /// + /// Gets an for the given type. + /// + /// The CLR type. + /// The request being deserialized. + /// An that can deserialize the given type. + /// This signature uses types that are AspNet-specific. + public abstract ODataDeserializer GetODataDeserializer(Type type, HttpRequestMessage request); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataCountMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataCountMediaTypeMapping.cs new file mode 100644 index 0000000..effb51e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataCountMediaTypeMapping.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Net.Http.Formatting; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests with $count. + /// + public partial class ODataCountMediaTypeMapping : MediaTypeMapping + { + /// + public override double TryMatchMediaType(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return IsCountRequest(request.ODataProperties().Path) ? 1 : 0; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatter.cs new file mode 100644 index 0000000..b66175f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatter.cs @@ -0,0 +1,375 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Results; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// class to handle OData. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling acceptable")] + public class ODataMediaTypeFormatter : MediaTypeFormatter + { + private readonly IEnumerable _payloadKinds; + + /// + /// Initializes a new instance of the class. + /// + /// The kind of payloads this formatter supports. + public ODataMediaTypeFormatter(IEnumerable payloadKinds) + { + if (payloadKinds == null) + { + throw Error.ArgumentNull("payloadKinds"); + } + + _payloadKinds = payloadKinds; + } + + /// + /// Initializes a new instance of the class. + /// + /// The to copy settings from. + /// The for the per-request formatter instance. + /// This is a copy constructor to be used in . + internal ODataMediaTypeFormatter(ODataMediaTypeFormatter formatter, HttpRequestMessage request) + : base(formatter) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + Contract.Assert(formatter._payloadKinds != null); + + // Parameter 1: formatter + // Except for the other two parameters, this constructor is a copy constructor, and we need to copy + // everything on the other instance. + _payloadKinds = formatter._payloadKinds; + + // Parameter 2: request + Request = request; + + // BaseAddressFactory + BaseAddressFactory = formatter.BaseAddressFactory; + } + + /// + /// Gets or sets a method that allows consumers to provide an alternate base + /// address for OData Uri. + /// + public Func BaseAddressFactory { get; set; } + + /// + /// The request message associated with the per-request formatter instance. + /// + public HttpRequestMessage Request { get; set; } + + /// + public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType) + { + // call base to validate parameters + base.GetPerRequestFormatterInstance(type, request, mediaType); + + if (Request != null && Request == request) + { + // If the request is already set on this formatter, return itself. + return this; + } + else + { + return new ODataMediaTypeFormatter(this, request); + } + } + + /// + public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType) + { + // Determine the content type or let base class handle it. + MediaTypeHeaderValue newMediaType = null; + if (ODataOutputFormatterHelper.TryGetContentHeader(type, mediaType, out newMediaType)) + { + headers.ContentType = newMediaType; + } + else + { + // This is the case when a user creates a new ObjectContent passing in a null mediaType + base.SetDefaultContentHeaders(type, headers, mediaType); + } + + // Set the character set. + IEnumerable acceptCharsetValues = Request.Headers.AcceptCharset.Select(cs => cs.Value); + + string newCharSet = String.Empty; + if (ODataOutputFormatterHelper.TryGetCharSet(headers.ContentType, acceptCharsetValues, out newCharSet)) + { + headers.ContentType.CharSet = newCharSet; + } + + // Add version header. + headers.TryAddWithoutValidation( + ODataVersionConstraint.ODataServiceVersionHeader, + ODataUtils.ODataVersionToString(ResultHelpers.GetODataResponseVersion(Request))); + } + + /// + public override bool CanReadType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (Request != null) + { + ODataDeserializerProvider deserializerProvider = Request.GetRequestContainer() + .GetRequiredService(); + + return ODataInputFormatterHelper.CanReadType( + type, + Request.GetModel(), + Request.ODataProperties().Path, + _payloadKinds, + (objectType) => deserializerProvider.GetEdmTypeDeserializer(objectType), + (objectType) => deserializerProvider.GetODataDeserializer(objectType, Request)); + } + + return false; + } + + /// + public override bool CanWriteType(Type type) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (Request != null) + { + ODataSerializerProvider serializerProvider = Request.GetRequestContainer() + .GetRequiredService(); + + // See if this type is a SingleResult or is derived from SingleResult. + bool isSingleResult = false; + if (type.IsGenericType) + { + Type genericType = type.GetGenericTypeDefinition(); + Type baseType = TypeHelper.GetBaseType(type); + isSingleResult = (genericType == typeof(SingleResult<>) || baseType == typeof(SingleResult)); + } + + return ODataOutputFormatterHelper.CanWriteType( + type, + _payloadKinds, + isSingleResult, + new WebApiRequestMessage(Request), + (objectType) => serializerProvider.GetODataPayloadSerializer(objectType, Request)); + } + + return false; + } + + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")] + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling results for making ReadFromStream platform-agnostic.")] + public override Task ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + if (readStream == null) + { + throw Error.ArgumentNull("readStream"); + } + + if (Request == null) + { + throw Error.InvalidOperation(SRResources.ReadFromStreamAsyncMustHaveRequest); + } + + object defaultValue = GetDefaultValueForType(type); + + // If content length is 0 then return default value for this type + HttpContentHeaders contentHeaders = (content == null) ? null : content.Headers; + if (contentHeaders == null || contentHeaders.ContentLength == 0) + { + return Task.FromResult(defaultValue); + } + + try + { + Func getODataDeserializerContext = () => + { + return new ODataDeserializerContext + { + Request = Request, + }; + }; + + Action logErrorAction = (ex) => + { + if (formatterLogger == null) + { + throw ex; + } + + formatterLogger.LogError(String.Empty, ex); + }; + + ODataDeserializerProvider deserializerProvider = Request.GetRequestContainer() + .GetRequiredService(); + + return Task.FromResult(ODataInputFormatterHelper.ReadFromStream( + type, + defaultValue, + Request.GetModel(), + GetBaseAddressInternal(Request), + new WebApiRequestMessage(Request), + () => ODataMessageWrapperHelper.Create(readStream, contentHeaders, Request.GetODataContentIdMapping(), Request.GetRequestContainer()), + (objectType) => deserializerProvider.GetEdmTypeDeserializer(objectType), + (objectType) => deserializerProvider.GetODataDeserializer(objectType, Request), + getODataDeserializerContext, + (disposable) => Request.RegisterForDispose(disposable), + logErrorAction)); + } + catch (Exception ex) + { + return TaskHelpers.FromError(ex); + } + } + + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")] + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling results for making WriteToStream platform-agnostic.")] + public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, + TransportContext transportContext, CancellationToken cancellationToken) + { + if (type == null) + { + throw Error.ArgumentNull("type"); + } + if (writeStream == null) + { + throw Error.ArgumentNull("writeStream"); + } + if (Request == null) + { + throw Error.InvalidOperation(SRResources.WriteToStreamAsyncMustHaveRequest); + } + if (cancellationToken.IsCancellationRequested) + { + return TaskHelpers.Canceled(); + } + + try + { + HttpConfiguration configuration = Request.GetConfiguration(); + if (configuration == null) + { + throw Error.InvalidOperation(SRResources.RequestMustContainConfiguration); + } + + HttpContentHeaders contentHeaders = (content == null) ? null : content.Headers; + UrlHelper urlHelper = Request.GetUrlHelper() ?? new UrlHelper(Request); + + Func getODataSerializerContext = () => + { + return new ODataSerializerContext() + { + Request = Request, + Url = urlHelper, + }; + }; + + ODataSerializerProvider serializerProvider = Request.GetRequestContainer() + .GetRequiredService(); + + ODataOutputFormatterHelper.WriteToStream( + type, + value, + Request.GetModel(), + ResultHelpers.GetODataResponseVersion(Request), + GetBaseAddressInternal(Request), + contentHeaders == null ? null : contentHeaders.ContentType, + new WebApiUrlHelper(urlHelper), + new WebApiRequestMessage(Request), + new WebApiRequestHeaders(Request.Headers), + (services) => ODataMessageWrapperHelper.Create(writeStream, contentHeaders, services), + (edmType) => serializerProvider.GetEdmTypeSerializer(edmType), + (objectType) => serializerProvider.GetODataPayloadSerializer(objectType, Request), + getODataSerializerContext); + + return TaskHelpers.Completed(); + } + catch (Exception ex) + { + return TaskHelpers.FromError(ex); + } + } + + // To factor out request, just pass in a function to get base address. We'd get rid of + // BaseAddressFactory and request. + private Uri GetBaseAddressInternal(HttpRequestMessage request) + { + if (BaseAddressFactory != null) + { + return BaseAddressFactory(request); + } + else + { + return ODataMediaTypeFormatter.GetDefaultBaseAddress(request); + } + } + + /// + /// Returns a base address to be used in the service root when reading or writing OData uris. + /// + /// The HttpRequestMessage object for the given request. + /// The base address to be used as part of the service root in the OData uri; must terminate with a trailing '/'. + public static Uri GetDefaultBaseAddress(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + UrlHelper urlHelper = request.GetUrlHelper() ?? new UrlHelper(request); + + string baseAddress = urlHelper.CreateODataLink(); + if (baseAddress == null) + { + throw new SerializationException(SRResources.UnableToDetermineBaseUrl); + } + + return baseAddress[baseAddress.Length - 1] != '/' ? new Uri(baseAddress + '/') : new Uri(baseAddress); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatters.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatters.cs new file mode 100644 index 0000000..d6471ad --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataMediaTypeFormatters.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Text; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// classes to handle OData. + /// + public static class ODataMediaTypeFormatters + { + private const string DollarFormat = "$format"; + + private const string JsonFormat = "json"; + + private const string XmlFormat = "xml"; + + /// + /// Creates a list of media type formatters to handle OData. + /// + /// A list of media type formatters to handle OData. + public static IList Create() + { + return new List() + { + // Place JSON formatter first so it gets used when the request doesn't ask for a specific content type + CreateApplicationJson(), + CreateApplicationXml(), + CreateRawValue() + }; + } + + private static void AddSupportedEncodings(MediaTypeFormatter formatter) + { + formatter.SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, + throwOnInvalidBytes: true)); + formatter.SupportedEncodings.Add(new UnicodeEncoding(bigEndian: false, byteOrderMark: true, + throwOnInvalidBytes: true)); + } + + private static ODataMediaTypeFormatter CreateRawValue() + { + ODataMediaTypeFormatter formatter = CreateFormatterWithoutMediaTypes(ODataPayloadKind.Value); + formatter.MediaTypeMappings.Add(new ODataPrimitiveValueMediaTypeMapping()); + formatter.MediaTypeMappings.Add(new ODataEnumValueMediaTypeMapping()); + formatter.MediaTypeMappings.Add(new ODataBinaryValueMediaTypeMapping()); + formatter.MediaTypeMappings.Add(new ODataCountMediaTypeMapping()); + return formatter; + } + + private static ODataMediaTypeFormatter CreateApplicationJson() + { + ODataMediaTypeFormatter formatter = CreateFormatterWithoutMediaTypes( + ODataPayloadKind.ResourceSet, + ODataPayloadKind.Resource, + ODataPayloadKind.Property, + ODataPayloadKind.EntityReferenceLink, + ODataPayloadKind.EntityReferenceLinks, + ODataPayloadKind.Collection, + ODataPayloadKind.ServiceDocument, + ODataPayloadKind.Error, + ODataPayloadKind.Parameter, + ODataPayloadKind.Delta); + + // Add minimal metadata as the first media type so it gets used when the request doesn't + // ask for a specific content type + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataMinimalMetadataStreamingTrue)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataMinimalMetadataStreamingFalse)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataMinimalMetadata)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataFullMetadataStreamingTrue)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataFullMetadataStreamingFalse)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataFullMetadata)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataNoMetadataStreamingTrue)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataNoMetadataStreamingFalse)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonODataNoMetadata)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonStreamingTrue)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJsonStreamingFalse)); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationJson)); + + formatter.AddDollarFormatQueryStringMappings(); + formatter.AddQueryStringMapping(DollarFormat, JsonFormat, ODataMediaTypes.ApplicationJson); + + return formatter; + } + + private static ODataMediaTypeFormatter CreateApplicationXml() + { + ODataMediaTypeFormatter formatter = CreateFormatterWithoutMediaTypes( + ODataPayloadKind.MetadataDocument); + formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ODataMediaTypes.ApplicationXml)); + + formatter.AddDollarFormatQueryStringMappings(); + formatter.AddQueryStringMapping(DollarFormat, XmlFormat, ODataMediaTypes.ApplicationXml); + + return formatter; + } + + private static ODataMediaTypeFormatter CreateFormatterWithoutMediaTypes(params ODataPayloadKind[] payloadKinds) + { + ODataMediaTypeFormatter formatter = new ODataMediaTypeFormatter(payloadKinds); + AddSupportedEncodings(formatter); + return formatter; + } + + private static void AddDollarFormatQueryStringMappings(this ODataMediaTypeFormatter formatter) + { + ICollection supportedMediaTypes = formatter.SupportedMediaTypes; + foreach (MediaTypeHeaderValue supportedMediaType in supportedMediaTypes) + { + QueryStringMediaTypeMapping mapping = new QueryStringMediaTypeMapping(DollarFormat, supportedMediaType); + formatter.MediaTypeMappings.Add(mapping); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataModelBinderProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataModelBinderProvider.cs new file mode 100644 index 0000000..74d50f2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataModelBinderProvider.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using System.Web.Http.ValueProviders; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Provides a for EDM primitive types. + /// + /// + /// This class is similar to ODataModelBinder in AspNetCore. The flow is similar but the + /// type are dissimilar enough making a common version more complex than separate versions. + /// + public class ODataModelBinderProvider : ModelBinderProvider + { + /// + public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (modelType == null) + { + throw Error.ArgumentNull("modelType"); + } + + return new ODataModelBinder(); + } + + internal class ODataModelBinder : IModelBinder + { + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't want to fail in model binding.")] + public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw Error.ArgumentNull("bindingContext"); + } + + if (bindingContext.ModelMetadata == null) + { + throw Error.Argument("bindingContext", SRResources.ModelBinderUtil_ModelMetadataCannotBeNull); + } + + string modelName = ODataParameterValue.ParameterValuePrefix + bindingContext.ModelName; + ValueProviderResult value = bindingContext.ValueProvider.GetValue(modelName); + if (value == null) + { + value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (value == null) + { + return false; + } + } + + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value); + + try + { + ODataParameterValue paramValue = value.RawValue as ODataParameterValue; + if (paramValue != null) + { + bindingContext.Model = ConvertTo(paramValue, actionContext, bindingContext, + actionContext.Request.GetRequestContainer()); + return true; + } + + string valueString = value.RawValue as string; + if (valueString != null) + { + bindingContext.Model = ODataModelBinderConverter.ConvertTo(valueString, bindingContext.ModelType); + return true; + } + + return false; + } + catch (ODataException ex) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message); + return false; + } + catch (ValidationException ex) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, Error.Format(SRResources.ValueIsInvalid, value.RawValue, ex.Message)); + return false; + } + catch (FormatException ex) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, Error.Format(SRResources.ValueIsInvalid, value.RawValue, ex.Message)); + return false; + } + catch (Exception e) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, e); + return false; + } + } + + internal static object ConvertTo(ODataParameterValue parameterValue, HttpActionContext actionContext, ModelBindingContext bindingContext, + IServiceProvider requestContainer) + { + Contract.Assert(parameterValue != null && parameterValue.EdmType != null); + + object oDataValue = parameterValue.Value; + if (oDataValue == null || oDataValue is ODataNullValue) + { + return null; + } + + IEdmTypeReference edmTypeReference = parameterValue.EdmType; + ODataDeserializerContext readContext = BuildDeserializerContext(actionContext, bindingContext, edmTypeReference); + return ODataModelBinderConverter.Convert(oDataValue, edmTypeReference, bindingContext.ModelType, + bindingContext.ModelName, readContext, requestContainer); + } + + internal static ODataDeserializerContext BuildDeserializerContext(HttpActionContext actionContext, + ModelBindingContext bindingContext, IEdmTypeReference edmTypeReference) + { + HttpRequestMessage request = actionContext.Request; + ODataPath path = request.ODataProperties().Path; + IEdmModel edmModel = request.GetModel(); + + return new ODataDeserializerContext + { + Path = path, + Model = edmModel, + Request = request, + ResourceType = bindingContext.ModelType, + ResourceEdmType = edmTypeReference, + }; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataRawValueMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataRawValueMediaTypeMapping.cs new file mode 100644 index 0000000..9d1e134 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/ODataRawValueMediaTypeMapping.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Net.Http.Formatting; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests for the raw value of properties. + /// + public abstract partial class ODataRawValueMediaTypeMapping : MediaTypeMapping + { + /// + public override double TryMatchMediaType(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + ODataPath odataPath = request.ODataProperties().Path; + return (IsRawValueRequest(odataPath) && IsMatch(GetProperty(odataPath))) ? 1 : 0; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/QueryStringMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/QueryStringMediaTypeMapping.cs new file mode 100644 index 0000000..3469479 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/QueryStringMediaTypeMapping.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Class that provides s from query strings. + /// + public partial class QueryStringMediaTypeMapping : MediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the query string parameter to match, if present. + /// The to use if the query parameter specified by is present + /// and assigned the value specified by . + public QueryStringMediaTypeMapping(string queryStringParameterName, MediaTypeHeaderValue mediaType) + : base(mediaType) + { + if (queryStringParameterName == null) + { + throw Error.ArgumentNull("queryStringParameterName"); + } + + QueryStringParameterName = queryStringParameterName; + } + + /// + public override double TryMatchMediaType(HttpRequestMessage request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + FormDataCollection queryString = GetQueryString(request.RequestUri); + return DoesQueryStringMatch(queryString) ? 1 : 0; + } + + private static FormDataCollection GetQueryString(Uri uri) + { + if (uri == null) + { + throw Error.InvalidOperation( + SRResources.NonNullUriRequiredForMediaTypeMapping, + typeof(QueryStringMediaTypeMapping).Name); + } + + return new FormDataCollection(uri); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs new file mode 100644 index 0000000..158de73 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Web.Http; +using Microsoft.AspNet.OData.Extensions; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// The default . + /// + public partial class DefaultODataSerializerProvider : ODataSerializerProvider + { + /// + /// This signature uses types that are AspNet-specific. + public override ODataSerializer GetODataPayloadSerializer(Type type, HttpRequestMessage request) + { + // Using a Func to delay evaluation of the model. + return GetODataPayloadSerializerImpl(type, () => request.GetModel(), request.ODataProperties().Path, typeof(HttpError)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataErrorSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataErrorSerializer.cs new file mode 100644 index 0000000..3d20d70 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataErrorSerializer.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an to serialize s. + /// + public partial class ODataErrorSerializer + { + /// + /// Return true of the object is an HttpError. + /// + /// The error to test. + /// true of the object is an HttpError + /// This function uses types that are AspNet-specific. + internal static bool IsHttpError(object error) + { + return error is HttpError; + } + + /// + /// Create an ODataError from an HttpError. + /// + /// The error to use. + /// an ODataError. + /// This function uses types that are AspNet-specific. + internal static ODataError CreateODataError(object error) + { + HttpError httpError = error as HttpError; + return httpError.CreateODataError(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerContext.cs new file mode 100644 index 0000000..78bb92f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerContext.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Context information used by the when serializing objects in OData message format. + /// + public partial class ODataSerializerContext + { + private HttpRequestMessage _request; + private UrlHelper _urlHelper; + + /// + /// Gets or sets the HTTP Request whose response is being serialized. + /// + /// This signature uses types that are AspNet-specific. + public HttpRequestMessage Request + { + get { return _request; } + + set + { + _request = value; + InternalRequest = _request != null ? new WebApiRequestMessage(_request) : null; + } + } + + /// Gets or sets the request context. + /// This signature uses types that are AspNet-specific. + public HttpRequestContext RequestContext { get; set; } + + /// + /// Gets or sets the to use for generating OData links. + /// + /// This signature uses types that are AspNet-specific. + public UrlHelper Url + { + get { return _urlHelper; } + + set + { + _urlHelper = value; + InternalUrlHelper = _urlHelper != null ? new WebApiUrlHelper(_urlHelper) : null; + } + } + + /// + /// Copy the properties this instance of from an existing instance. + /// + /// + /// This function uses types that are AspNet-specific. + private void CopyPlatformSpecificProperties(ODataSerializerContext context) + { + Request = context.Request; + Url = context.Url; + // TODO: This property is not copied in the Aspnet version of the product. + //RequestContext = context.RequestContext; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerProvider.cs new file mode 100644 index 0000000..0506d43 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Formatter/Serialization/ODataSerializerProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// An ODataSerializerProvider is a factory for creating s. + /// + public abstract partial class ODataSerializerProvider + { + /// + /// Gets an for the given . + /// + /// The for which the serializer is being requested. + /// The request for which the response is being serialized. + /// The for the given type. + /// This signature uses types that are AspNet-specific. + public abstract ODataSerializer GetODataPayloadSerializer(Type type, HttpRequestMessage request); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/FromODataUriAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/FromODataUriAttribute.cs new file mode 100644 index 0000000..e471bbf --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/FromODataUriAttribute.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using System.Web.Http.ValueProviders; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData +{ + /// + /// An implementation of that can bind URI parameters using OData conventions. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] + public sealed class FromODataUriAttribute : ModelBinderAttribute + { + private static readonly ModelBinderProvider _provider = new ODataModelBinderProvider(); + + /// + /// Gets the binding for a parameter. + /// + /// The parameter to bind. + /// + /// The that contains the binding. + /// + public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) + { + if (parameter == null) + { + throw Error.ArgumentNull("parameter"); + } + + IModelBinder binder = _provider.GetBinder(parameter.Configuration, parameter.ParameterType); + IEnumerable valueProviderFactories = GetValueProviderFactories(parameter.Configuration); + return new ModelBinderParameterBinding(parameter, binder, valueProviderFactories); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GetNextPageHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GetNextPageHelper.cs new file mode 100644 index 0000000..fdf77fe --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GetNextPageHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Net.Http.Formatting; + +namespace Microsoft.AspNet.OData +{ + /// + /// Helper to generate next page links. + /// + internal static partial class GetNextPageHelper + { + /// This signature uses types that are AspNet-specific. + internal static Uri GetNextPageLink(Uri requestUri, int pageSize, object instance = null, Func objectToSkipTokenValue = null) + { + Contract.Assert(requestUri != null); + + return GetNextPageLink(requestUri, new FormDataCollection(requestUri), pageSize, instance, objectToSkipTokenValue); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs new file mode 100644 index 0000000..6cee9b4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Net.Http", Justification = "Follows System.Net.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Http", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Formatter", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Formatter.Serialization", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Builder.Conventions.Attributes", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Results", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Query", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Query.Expressions", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Query", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.AspNet.OData.Results", Justification = "Follows System.Web.Http naming")] +[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "These assemblies are delay-signed.")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.AspNet.OData.Formatter.EdmLibHelpers.#.cctor()", Justification = "Class coupling necessary in this class")] +[assembly: SuppressMessage("Microsoft.Web.FxCop", "MW1000:UnusedResourceUsageRule", MessageId = "172567", Justification = "Resource used by framework")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "orderby", Scope = "resource", Target = "Microsoft.AspNet.OData.Properties.SRResources.resources", Justification = "$orderby is an odata keyword")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "it", Scope = "resource", Target = "Microsoft.AspNet.OData.Properties.SRResources.resources", Justification = "$it is an odata keyword")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "Microsoft.AspNet.OData.Builder.EdmModelHelperMethods", Justification = "Static helper class. Class coupling acceptable here.")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "Microsoft.AspNet.OData.Formatter.EdmLibHelpers", Justification = "Static helper class. Class coupling acceptable here.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.PropertyConfiguration.#Unsortable", Justification = "spelled correctly")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.PropertyConfiguration.#IsUnsortable()", Justification = "spelled correctly")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "member", Target = "Microsoft.AspNet.OData.QueryableRestrictions.#Unsortable", Justification = "spelled correctly")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unsortable", Scope = "type", Target = "Microsoft.AspNet.OData.Query.UnsortableAttribute", Justification = "spelled correctly")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Infos", Scope = "member", Target = "Microsoft.AspNet.OData.Formatter.Deserialization.ODataResourceWrapper.#NestedResourceInfos", Justification = "spelled correctly")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.AspNet.OData.Builder.ODataConventionModelBuilder.#.cctor()")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "Microsoft.AspNet.OData.Query.Expressions.PropertyContainer", Justification = "Using generated classes to simulate b-tree.")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.AspNet.OData.Query.Expressions.PropertyContainer.#.cctor()", Justification = "Using generated classes to simulate b-tree.")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.AspNet.OData.GetNextPageHelper.#GetNextPageLink(System.Uri,System.Collections.Generic.IEnumerable`1>,System.Int32,System.String)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.AspNet.OData.GetNextPageHelper.#GetNextPageLink(System.Uri,System.Collections.Generic.IEnumerable`1>,System.Int32,System.Object,System.Func`2)")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "Microsoft.AspNet.OData.Query.ODataQueryOptions")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.StartsWith(System.String)", Scope = "member", Target = "Microsoft.AspNet.OData.Query.DefaultSkipTokenHandler.#PopulatePropertyValuePairs(System.String,Microsoft.AspNet.OData.ODataQueryContext)")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Skiptoken", Scope = "resource", Target = "Microsoft.AspNet.OData.Properties.SRResources.resources")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "skiptoken", Scope = "resource", Target = "Microsoft.AspNet.OData.Properties.SRResources.resources")][assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Subquery", Scope = "member", Target = "Microsoft.AspNet.OData.Query.ODataQuerySettings.#EnableCorrelatedSubqueryBuffering")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Subquery", Scope = "member", Target = "Microsoft.AspNet.OData.EnableQueryAttribute.#EnableCorrelatedSubqueryBuffering")] \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/HttpRequestScope.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/HttpRequestScope.cs new file mode 100644 index 0000000..7717cfa --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/HttpRequestScope.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; + +namespace Microsoft.AspNet.OData +{ + /// + /// Provides access to the + /// to which the OData service container instance is scoped. + /// + public class HttpRequestScope + { + /// + /// Provides access to the + /// to which the OData service container instance is scoped. + /// + public HttpRequestMessage HttpRequest { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Nightly.Release.nuspec b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Nightly.Release.nuspec new file mode 100644 index 0000000..eb31988 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Nightly.Release.nuspec @@ -0,0 +1,35 @@ + + + + Microsoft.AspNet.OData + Microsoft ASP.NET Web API 2.2 for OData v4.0 + $VersionFullSemantic$-Nightly$NightlyBuildVersion$ + Microsoft + © Microsoft Corporation. All rights reserved. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Web API and to support OData query syntax for your web APIs. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Web API. + en-US + http://odata.github.io + https://raw.githubusercontent.com/OData/WebApi/master/License.txt + true + Microsoft AspNet WebApi AspNetWebApi OData + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Release.nuspec b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Release.nuspec new file mode 100644 index 0000000..099703a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.Release.nuspec @@ -0,0 +1,35 @@ + + + + Microsoft.AspNet.OData + Microsoft ASP.NET Web API 2.2 for OData v4.0 + $VersionNuGetSemantic$ + Microsoft + © Microsoft Corporation. All rights reserved. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Web API and to support OData query syntax for your web APIs. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Web API. + en-US + http://odata.github.io + https://raw.githubusercontent.com/OData/WebApi/master/License.txt + true + Microsoft AspNet WebApi AspNetWebApi OData + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj new file mode 100644 index 0000000..2767a17 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj @@ -0,0 +1,188 @@ + + + + + {A6F9775D-F7E2-424E-8363-79644A73038F} + Library + Microsoft.AspNet.OData + Microsoft.AspNet.OData + $(OutputPath)$(AssemblyName).xml + + true + ..\Strict.ruleset + true + v4.5 + $(DefineConstants);ASPNETODATA;ASPNETWEBAPI;NETFX;NETFX45 + + + + + ..\..\sln\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll + True + + + ..\..\sln\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + True + + + ..\..\sln\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\..\sln\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + ..\..\sln\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll + + + ..\..\sln\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + False + ..\..\sln\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + + + + False + ..\..\sln\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + + + + + + Properties\CommonAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Properties\CommonWebApiResources.resx + ResXFileCodeGenerator + Designer + + + Properties\SRResources.resx + ResXFileCodeGenerator + Designer + SRResources1.Designer.cs + + + Properties\CommonWebApiResources.Designer.cs + True + True + CommonWebApiResources.resx + + + Properties\SRResources.Designer.cs + True + True + SRResources.resx + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/NonValidatingParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/NonValidatingParameterBindingAttribute.cs new file mode 100644 index 0000000..f3c165e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/NonValidatingParameterBindingAttribute.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http.Formatting; +using System.Web.Http; +using System.Web.Http.Controllers; + +namespace Microsoft.AspNet.OData +{ + /// + /// An attribute to disable WebApi model validation for a particular type. + /// + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + internal sealed partial class NonValidatingParameterBindingAttribute : ParameterBindingAttribute + { + public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) + { + IEnumerable formatters = parameter.Configuration.Formatters; + + return new NonValidatingParameterBinding(parameter, formatters); + } + + private sealed class NonValidatingParameterBinding : PerRequestParameterBinding + { + public NonValidatingParameterBinding(HttpParameterDescriptor descriptor, + IEnumerable formatters) + : base(descriptor, formatters) + { + } + + protected override HttpParameterBinding CreateInnerBinding(IEnumerable perRequestFormatters) + { + return Descriptor.BindWithFormatter(perRequestFormatters, bodyModelValidator: null); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataController.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataController.cs new file mode 100644 index 0000000..34d6bbd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataController.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Web.Http; +using System.Web.Http.Description; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Results; + +namespace Microsoft.AspNet.OData +{ + /// + /// Defines a base class for OData controllers that support writing and reading data using the OData formats. + /// + /// These attributes and this signature uses types that are AspNet-specific. + [ODataFormatting] + [ODataRouting] + [ApiExplorerSettings(IgnoreApi = true)] + public abstract partial class ODataController : ApiController + { + /// + /// Releases the unmanaged resources that are used by the object and, optionally, + /// releases the managed resources. + /// + /// + /// True to release both managed and unmanaged resources; false to release only unmanaged resources. + /// + /// These method is unique to AspNet. + protected override void Dispose(bool disposing) + { + if (disposing && Request != null) + { + Request.DeleteRequestContainer(true); + } + + base.Dispose(disposing); + } + + /// + /// Creates an action result with the specified values that is a response to a POST operation with an entity + /// to an entity set. + /// + /// The created entity type. + /// The created entity. + /// A with the specified values. + /// These function uses types that are AspNet-specific. + protected virtual CreatedODataResult Created(TEntity entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + return new CreatedODataResult(entity, this); + } + + /// + /// Creates an action result with the specified values that is a response to a PUT, PATCH, or a MERGE operation + /// on an OData entity. + /// + /// The updated entity type. + /// The updated entity. + /// An with the specified values. + /// These function uses types that are AspNet-specific. + protected virtual UpdatedODataResult Updated(TEntity entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + return new UpdatedODataResult(entity, this); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataFormattingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataFormattingAttribute.cs new file mode 100644 index 0000000..52112ed --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataFormattingAttribute.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net.Http.Formatting; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Services; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData +{ + /// + /// An attribute to be placed on controllers that enables the OData formatters. + /// + /// + /// This attribute does the following actions: + /// + /// + /// + /// It inserts the ODataMediaTypeFormatters into the collection. + /// + /// + /// It attaches the request to the OData formatter instance. + /// + /// + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want to be able to subclass this type.")] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class ODataFormattingAttribute : Attribute, IControllerConfiguration + { + /// + /// Callback invoked to set per-controller overrides for this controllerDescriptor. + /// + /// The controller settings to initialize. + /// The controller descriptor. Note that the can be associated with the derived + /// controller type given that is + /// inherited. + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + if (controllerSettings == null) + { + throw Error.ArgumentNull("controllerSettings"); + } + + if (controllerDescriptor == null) + { + throw Error.ArgumentNull("controllerDescriptor"); + } + + // If any OData formatters are registered globally, do nothing and use those instead + MediaTypeFormatterCollection controllerFormatters = controllerSettings.Formatters; + if (!controllerFormatters.Where(f => f != null && Decorator.GetInner(f) is ODataMediaTypeFormatter).Any()) + { + // Remove Xml and Json formatters to avoid media type conflicts. + RemoveFormatters(controllerFormatters, + controllerFormatters.Where(f => f is XmlMediaTypeFormatter || f is JsonMediaTypeFormatter)); + + // Then add our formatters. + controllerFormatters.InsertRange(0, CreateODataFormatters()); + } + + ServicesContainer services = controllerSettings.Services; + Contract.Assert(services != null); + + // Replace the action value binder with one that attaches the request to the formatter. + IActionValueBinder originalActionValueBinder = services.GetActionValueBinder(); + IActionValueBinder actionValueBinder = new PerRequestActionValueBinder(originalActionValueBinder); + controllerSettings.Services.Replace(typeof(IActionValueBinder), actionValueBinder); + + // Replace the content negotiator with one that uses the per-request formatters. + // This negotiator is required to allow CanReadType to have access to the model. + IContentNegotiator originalContentNegotiator = services.GetContentNegotiator(); + IContentNegotiator contentNegotiator = new PerRequestContentNegotiator(originalContentNegotiator); + controllerSettings.Services.Replace(typeof(IContentNegotiator), contentNegotiator); + } + + /// + /// Creates the OData formatters. + /// + /// A collection of OData formatters. + public virtual IList CreateODataFormatters() + { + return ODataMediaTypeFormatters.Create(); + } + + private static void RemoveFormatters(MediaTypeFormatterCollection formatterCollection, + IEnumerable formattersToRemove) + { + Contract.Assert(formatterCollection != null); + Contract.Assert(formattersToRemove != null); + + // Instantiate a separate array to isolate enumeration from deletions. This code would otherwise throw + // after the first removal. + foreach (MediaTypeFormatter formatter in formattersToRemove.ToArray()) + { + formatterCollection.Remove(formatter); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataMessageWrapperHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataMessageWrapperHelper.cs new file mode 100644 index 0000000..8e954dd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataMessageWrapperHelper.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Formatter; + +namespace Microsoft.AspNet.OData +{ + internal static class ODataMessageWrapperHelper + { + internal static ODataMessageWrapper Create(Stream stream, HttpContentHeaders headers) + { + return ODataMessageWrapperHelper.Create(stream, headers, contentIdMapping: null); + } + + internal static ODataMessageWrapper Create(Stream stream, HttpContentHeaders headers, IServiceProvider container) + { + return ODataMessageWrapperHelper.Create(stream, headers, null, container); + } + + internal static ODataMessageWrapper Create(Stream stream, HttpContentHeaders headers, IDictionary contentIdMapping, IServiceProvider container) + { + ODataMessageWrapper responseMessageWrapper = ODataMessageWrapperHelper.Create(stream, headers, contentIdMapping); + responseMessageWrapper.Container = container; + + return responseMessageWrapper; + } + + internal static ODataMessageWrapper Create(Stream stream, HttpContentHeaders headers, IDictionary contentIdMapping) + { + return new ODataMessageWrapper( + stream, + headers.ToDictionary(kvp => kvp.Key, kvp => String.Join(";", kvp.Value)), + contentIdMapping); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataNullValueMessageHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataNullValueMessageHandler.cs new file mode 100644 index 0000000..b6ce56f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataNullValueMessageHandler.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that converts null values in OData responses to + /// HTTP NotFound responses or NoContent responses following the OData specification. + /// + public partial class ODataNullValueMessageHandler : DelegatingHandler + { + /// + /// This signature uses types that are AspNet-specific. + protected async override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + HttpResponseMessage response = await base.SendAsync(request, cancellationToken); + + // This message handler is intended for helping with queries that return a null value, for example in a + // get request for a particular entity on an entity set, for a single valued navigation property or for + // a structural property of a given entity. The only case in which a data modification request will result + // in a 204 response status code, is when a primitive property is set to null through a PUT request to the + // property URL and in that case, the user can return the right status code himself. + ObjectContent content = response == null ? null : response.Content as ObjectContent; + if (request.Method == System.Net.Http.HttpMethod.Get && content != null && content.Value == null && + response.StatusCode == HttpStatusCode.OK) + { + HttpStatusCode? newStatusCode = GetUpdatedResponseStatusCodeOrNull(request.ODataProperties().Path); + if (newStatusCode.HasValue) + { + response = request.CreateResponse(newStatusCode.Value); + } + } + + return response; + } + + /// This signature uses types that are AspNet-specific. + /// This method is intended for unit testing purposes only. + internal Task SendAsync(HttpRequestMessage request) + { + return SendAsync(request, CancellationToken.None); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataQueryParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataQueryParameterBindingAttribute.cs new file mode 100644 index 0000000..559d05f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataQueryParameterBindingAttribute.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Metadata; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// A to bind parameters of type to the OData query from the incoming request. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] + public sealed partial class ODataQueryParameterBindingAttribute : ParameterBindingAttribute + { + /// + public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) + { + return new ODataQueryParameterBinding(parameter); + } + + internal class ODataQueryParameterBinding : HttpParameterBinding + { + private static MethodInfo _createODataQueryOptions = typeof(ODataQueryParameterBinding).GetMethod("CreateODataQueryOptions"); + private const string CreateODataQueryOptionsCtorKey = "MS_CreateODataQueryOptionsOfT"; + + public ODataQueryParameterBinding(HttpParameterDescriptor parameterDescriptor) + : base(parameterDescriptor) + { + } + + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Response disposed later")] + public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) + { + if (actionContext == null) + { + throw Error.ArgumentNull("actionContext"); + } + + HttpRequestMessage request = actionContext.Request; + + if (request == null) + { + throw Error.Argument("actionContext", SRResources.ActionContextMustHaveRequest); + } + + HttpActionDescriptor actionDescriptor = actionContext.ActionDescriptor; + + if (actionDescriptor == null) + { + throw Error.Argument("actionContext", SRResources.ActionContextMustHaveDescriptor); + } + + HttpConfiguration configuration = request.GetConfiguration(); + + if (configuration == null) + { + throw Error.Argument("actionContext", SRResources.RequestMustContainConfiguration); + } + + // Get the entity type from the parameter type if it is ODataQueryOptions. + // Fall back to the return type if not. Also, note that the entity type from the return type and ODataQueryOptions + // can be different (example implementing $select or $expand). + Type entityClrType = GetEntityClrTypeFromParameterType(Descriptor.ParameterType) ?? GetEntityClrTypeFromActionReturnType(actionDescriptor); + + IEdmModel userModel = request.GetModel(); + IEdmModel model = userModel != EdmCoreModel.Instance ? userModel : actionDescriptor.GetEdmModel(entityClrType); + ODataQueryContext entitySetContext = new ODataQueryContext(model, entityClrType, request.ODataProperties().Path); + + Func createODataQueryOptions = + (Func)Descriptor.Properties.GetOrAdd(CreateODataQueryOptionsCtorKey, _ => + { + return Delegate.CreateDelegate(typeof(Func), _createODataQueryOptions.MakeGenericMethod(entityClrType)); + }); + + ODataQueryOptions parameterValue = createODataQueryOptions(entitySetContext, request); + SetValue(actionContext, parameterValue); + + return TaskHelpers.Completed(); + } + + public static ODataQueryOptions CreateODataQueryOptions(ODataQueryContext context, HttpRequestMessage request) + { + return new ODataQueryOptions(context, request); + } + + internal static Type GetEntityClrTypeFromActionReturnType(HttpActionDescriptor actionDescriptor) + { + // It is a developer programming error to use this binding attribute + // on actions that return void. + if (actionDescriptor.ReturnType == null) + { + throw Error.InvalidOperation( + SRResources.FailedToBuildEdmModelBecauseReturnTypeIsNull, + actionDescriptor.ActionName, + actionDescriptor.ControllerDescriptor.ControllerName); + } + + Type entityClrType = TypeHelper.GetImplementedIEnumerableType(actionDescriptor.ReturnType); + + if (entityClrType == null) + { + // It is a developer programming error to use this binding attribute + // on actions that return a collection whose element type cannot be + // determined, such as a non-generic IQueryable or IEnumerable. + throw Error.InvalidOperation( + SRResources.FailedToRetrieveTypeToBuildEdmModel, + actionDescriptor.ActionName, + actionDescriptor.ControllerDescriptor.ControllerName, + actionDescriptor.ReturnType.FullName); + } + + return entityClrType; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataRoutingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataRoutingAttribute.cs new file mode 100644 index 0000000..58f4970 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ODataRoutingAttribute.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ValueProviders; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing; + +namespace Microsoft.AspNet.OData +{ + /// + /// Defines a controller-level attribute that can be used to enable OData action selection based on routing conventions. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class ODataRoutingAttribute : Attribute, IControllerConfiguration + { + /// + /// Callback invoked to set per-controller overrides for this controllerDescriptor. + /// + /// The controller settings to initialize. + /// The controller descriptor. Note that the can be associated with the derived + /// controller type given that is + /// inherited. + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + if (controllerSettings == null) + { + throw Error.ArgumentNull("controllerSettings"); + } + + if (controllerDescriptor == null) + { + throw Error.ArgumentNull("controllerDescriptor"); + } + + ServicesContainer services = controllerSettings.Services; + Contract.Assert(services != null); + + // Replace the action selector with one that is based on the OData routing conventions + IHttpActionSelector originalActionSelector = services.GetActionSelector(); + IHttpActionSelector actionSelector = new ODataActionSelector(originalActionSelector); + services.Replace(typeof(IHttpActionSelector), actionSelector); + + services.Insert(typeof(ValueProviderFactory), 0, new ODataValueProviderFactory()); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestActionValueBinder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestActionValueBinder.cs new file mode 100644 index 0000000..af8ff66 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestActionValueBinder.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + internal class PerRequestActionValueBinder : IActionValueBinder + { + private IActionValueBinder _innerActionValueBinder; + + public PerRequestActionValueBinder(IActionValueBinder innerActionValueBinder) + { + if (innerActionValueBinder == null) + { + throw Error.ArgumentNull("innerActionValueBinder"); + } + + _innerActionValueBinder = innerActionValueBinder; + } + + public HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw Error.ArgumentNull("actionDescriptor"); + } + + HttpActionBinding binding = _innerActionValueBinder.GetBinding(actionDescriptor); + + if (binding == null) + { + return null; + } + + HttpParameterBinding[] parameterBindings = binding.ParameterBindings; + + if (parameterBindings != null) + { + for (int i = 0; i < binding.ParameterBindings.Length; i++) + { + HttpParameterBinding parameterBinding = binding.ParameterBindings[i]; + + // Replace the formatter parameter binding with one that will attach the request. + // Note that we do not replace any other types, including derived types, as we do not have a way to + // decorate/compose these instances; there is no way we can add request attachment behavior to an + // arbitrary implementation of HttpParameterBinding. Any custom parameter bindings that do not + // attach the request may fail when using with OData (and the exception retured in that instance + // will explain the necessity of providing this behavior when implementing HttpParameterBinding for + // OData). + if (parameterBinding != null && parameterBinding is FormatterParameterBinding) + { + Contract.Assert(parameterBinding.Descriptor != null); + Contract.Assert(actionDescriptor.Configuration != null); + Contract.Assert(actionDescriptor.Configuration.Formatters != null); + binding.ParameterBindings[i] = new PerRequestParameterBinding(parameterBinding.Descriptor, + actionDescriptor.Configuration.Formatters); + } + } + } + + return binding; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestContentNegotiator.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestContentNegotiator.cs new file mode 100644 index 0000000..e72d641 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestContentNegotiator.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + /// + /// Content negotiator that uses per-request formatters to run the content negotiation. + /// + internal class PerRequestContentNegotiator : IContentNegotiator + { + private IContentNegotiator _innerContentNegotiator; + + public PerRequestContentNegotiator(IContentNegotiator innerContentNegotiator) + { + if (innerContentNegotiator == null) + { + throw Error.ArgumentNull("innerContentNegotiator"); + } + + _innerContentNegotiator = innerContentNegotiator; + } + + public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable formatters) + { + MediaTypeHeaderValue mediaType = request.Content == null ? null : request.Content.Headers.ContentType; + + List perRequestFormatters = new List(); + foreach (MediaTypeFormatter formatter in formatters) + { + if (formatter != null) + { + perRequestFormatters.Add(formatter.GetPerRequestFormatterInstance(type, request, mediaType)); + } + } + return _innerContentNegotiator.Negotiate(type, request, perRequestFormatters); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestParameterBinding.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestParameterBinding.cs new file mode 100644 index 0000000..553e6a7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRequestParameterBinding.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Net.Http.Formatting; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.Metadata; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + /// + /// A special HttpParameterBinding that uses a Per Request formatter instance with access to the Request. + /// + /// + /// This class is needed by the OData deserializers, since they actually need access to more than just the Request + /// body; they also need to interrogate the RequestUri etc. + /// + internal class PerRequestParameterBinding : HttpParameterBinding + { + private IEnumerable _formatters; + + public PerRequestParameterBinding(HttpParameterDescriptor descriptor, + IEnumerable formatters) + : base(descriptor) + { + if (formatters == null) + { + throw Error.ArgumentNull("formatters"); + } + + _formatters = formatters; + } + + public override bool WillReadBody + { + get + { + return true; + } + } + + public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) + { + List perRequestFormatters = new List(); + + foreach (MediaTypeFormatter formatter in _formatters) + { + MediaTypeFormatter perRequestFormatter = formatter.GetPerRequestFormatterInstance(Descriptor.ParameterType, actionContext.Request, actionContext.Request.Content.Headers.ContentType); + perRequestFormatters.Add(perRequestFormatter); + } + + HttpParameterBinding innerBinding = CreateInnerBinding(perRequestFormatters); + Contract.Assert(innerBinding != null); + + return innerBinding.ExecuteBindingAsync(metadataProvider, actionContext, cancellationToken); + } + + protected virtual HttpParameterBinding CreateInnerBinding(IEnumerable perRequestFormatters) + { + return Descriptor.BindWithFormatter(perRequestFormatters); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRouteContainer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRouteContainer.cs new file mode 100644 index 0000000..502de2e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/PerRouteContainer.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Web.Http; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Serialization; + +namespace Microsoft.AspNet.OData +{ + /// + /// A class for managing per-route service containers. + /// + public class PerRouteContainer : PerRouteContainerBase + { + private const string RootContainerMappingsKey = "Microsoft.AspNet.OData.RootContainerMappingsKey"; + + private readonly HttpConfiguration configuration; + + /// + /// Initializes a new instance of the class. + /// + public PerRouteContainer(HttpConfiguration configuration) + { + this.configuration = configuration; + } + + /// + /// Gets the root container for a given route name. + /// + /// The route name. + /// The root container for the route name. + protected override IServiceProvider GetContainer(string routeName) + { + if (String.IsNullOrEmpty(routeName)) + { + return configuration.GetNonODataRootContainer(); + } + + IServiceProvider rootContainer; + if (GetRootContainerMappings().TryGetValue(routeName, out rootContainer)) + { + return rootContainer; + } + + throw Error.InvalidOperation(SRResources.NullContainer); + } + + /// + /// Sets the root container for a given route name. + /// + /// The route name. + /// The root container to set. + /// The root container for the route name. + /// Used by unit tests to insert root containers. + protected override void SetContainer(string routeName, IServiceProvider rootContainer) + { + if (rootContainer == null) + { + throw Error.InvalidOperation(SRResources.NullContainer); + } + + if (String.IsNullOrEmpty(routeName)) + { + configuration.SetNonODataRootContainer(rootContainer); + } + else + { + this.GetRootContainerMappings()[routeName] = rootContainer; + } + } + + /// + /// Gets the root container mappings from the configuration. + /// + /// The root container mappings from the configuration. + private ConcurrentDictionary GetRootContainerMappings() + { + return (ConcurrentDictionary)configuration.Properties.GetOrAdd( + RootContainerMappingsKey, key => new ConcurrentDictionary()); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Properties/AssemblyInfo.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9520935 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("Microsoft.AspNet.OData")] +[assembly: AssemblyDescription("")] + +[assembly: InternalsVisibleTo("Microsoft.AspNet.OData.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptions.cs new file mode 100644 index 0000000..77d66d7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using System.Net.Http; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a composite OData query options that can be used to perform query composition. + /// Currently this only supports $filter, $orderby, $top, $skip, and $count. + /// + public partial class ODataQueryOptions + { + /// + /// Initializes a new instance of the class based on the incoming request and some metadata information from + /// the . + /// + /// The which contains the and some type information. + /// The incoming request message. + /// This signature uses types that are AspNet-specific. + public ODataQueryOptions(ODataQueryContext context, HttpRequestMessage request) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + Contract.Assert(context.RequestContainer == null); + context.RequestContainer = request.GetRequestContainer(); + + Context = context; + Request = request; + InternalRequest = new WebApiRequestMessage(request); + InternalHeaders = new WebApiRequestHeaders(request.Headers); + + Initialize(context); + } + + /// + /// Gets the request message associated with this instance. + /// + /// This signature uses types that are AspNet-specific. + public HttpRequestMessage Request { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptionsOfTEntity.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptionsOfTEntity.cs new file mode 100644 index 0000000..682fe44 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/ODataQueryOptionsOfTEntity.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a composite OData query options that can be used to perform query composition. + /// Currently this only supports $filter, $orderby, $top, $skip. + /// + public partial class ODataQueryOptions : ODataQueryOptions + { + /// + /// Initializes a new instance of the class based on the incoming request and some metadata information from + /// the . + /// + /// The which contains the and some type information + /// The incoming request message + /// This signature uses types that are AspNet-specific. + public ODataQueryOptions(ODataQueryContext context, HttpRequestMessage request) + : base(context, request) + { + if (Context.ElementClrType == null) + { + throw Error.Argument("context", SRResources.ElementClrTypeNull, typeof(ODataQueryContext).Name); + } + + if (context.ElementClrType != typeof(TEntity)) + { + throw Error.Argument("context", SRResources.EntityTypeMismatch, context.ElementClrType.FullName, typeof(TEntity).FullName); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/QueryFilterProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/QueryFilterProvider.cs new file mode 100644 index 0000000..e11ce94 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Query/QueryFilterProvider.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// An implementation of that applies an action filter to + /// any action with an or return type + /// that doesn't bind a parameter of type . + /// + public class QueryFilterProvider : IFilterProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The action filter that executes the query. + public QueryFilterProvider(IActionFilter queryFilter) + { + if (queryFilter == null) + { + throw Error.ArgumentNull("queryFilter"); + } + + QueryFilter = queryFilter; + } + + /// + /// Gets the action filter that executes the query. + /// + public IActionFilter QueryFilter { get; private set; } + + /// + /// Provides filters to apply to the specified action. + /// + /// The server configuration. + /// The action descriptor for the action to provide filters for. + /// + /// The filters to apply to the specified action. + /// + public IEnumerable GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor) + { + // Actions with a bound parameter of type ODataQueryOptions do not support the query filter + // The assumption is that the action will handle the querying within the action implementation + if (actionDescriptor != null && + (TypeHelper.IsIQueryable(actionDescriptor.ReturnType) || typeof(SingleResult).IsAssignableFrom(actionDescriptor.ReturnType)) && + !actionDescriptor.GetParameters().Any(parameter => typeof(ODataQueryOptions).IsAssignableFrom(parameter.ParameterType))) + { + return new FilterInfo[] { new FilterInfo(QueryFilter, FilterScope.Global) }; + } + + return Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceContext.cs new file mode 100644 index 0000000..b467ff7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceContext.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Web.Http.Routing; + +namespace Microsoft.AspNet.OData +{ + /// + /// An instance of gets passed to the self link ( + /// , + /// , + /// + /// ) and navigation link ( + /// , + /// + /// ) builders and can be used by the link builders to generate links. + /// + public partial class ResourceContext + { + /// + /// Gets or sets the HTTP request that caused this instance to be generated. + /// + /// This signature uses types that are AspNet-specific. + public HttpRequestMessage Request + { + get + { + return SerializerContext.Request; + } + set + { + SerializerContext.Request = value; + } + } + + /// + /// Gets or sets a that may be used to generate links while serializing this resource + /// instance. + /// + /// This signature uses types that are AspNet-specific. + public UrlHelper Url + { + get + { + return SerializerContext.Url; + } + set + { + SerializerContext.Url = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceSetContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceSetContext.cs new file mode 100644 index 0000000..0ad2118 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/ResourceSetContext.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Contains context information about the resource set currently being serialized. + /// + public partial class ResourceSetContext + { + private HttpRequestMessage _request; + private UrlHelper _urlHelper; + + /// + /// Gets or sets the HTTP request that caused this instance to be generated. + /// + /// This signature uses types that are AspNet-specific. + public HttpRequestMessage Request + { + get { return _request; } + set + { + _request = value; + InternalRequest = _request != null ? new WebApiRequestMessage(_request) : null; + } + } + + /// + /// Gets or sets the request context. + /// + /// This signature uses types that are AspNet-specific. + public HttpRequestContext RequestContext { get; set; } + + /// + /// Gets or sets the to be used for generating links while serializing this + /// feed instance. + /// + /// This signature uses types that are AspNet-specific. + public UrlHelper Url + { + get { return _urlHelper; } + set + { + _urlHelper = value; + InternalUrlHelper = _urlHelper != null ? new WebApiUrlHelper(_urlHelper) : null; + } + } + + /// + /// Gets or sets the to which this instance belongs. + /// + /// This function uses types that are AspNet-specific. + public IEdmModel EdmModel + { + get { return Request.GetModel(); } + } + + /// + /// Create a from an and . + /// + /// The instance representing the resource set being written. + /// The serializer context. + /// A new . + /// This signature uses types that are AspNet-specific. + internal static ResourceSetContext Create(ODataSerializerContext writeContext, IEnumerable resourceSetInstance) + { + ResourceSetContext resourceSetContext = new ResourceSetContext + { + Request = writeContext.Request, + EntitySetBase = writeContext.NavigationSource as IEdmEntitySetBase, + Url = writeContext.Url, + ResourceSetInstance = resourceSetInstance + }; + + return resourceSetContext; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/CreatedODataResult.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/CreatedODataResult.cs new file mode 100644 index 0000000..a2030a7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/CreatedODataResult.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Results; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Results +{ + /// + /// Represents an action result that is a response to a create operation that adds an entity to an entity set. + /// + /// The entity type. + /// This action result handles content negotiation and the HTTP prefer header. It generates a location + /// header containing the edit link of the created entity and, if response has status code: NoContent, also + /// generates an OData-EntityId header. + public class CreatedODataResult : IHttpActionResult + { + private readonly NegotiatedContentResult _innerResult; + private Uri _locationHeader; + + /// + /// Initializes a new instance of the class. + /// + /// The created entity. + /// The controller from which to obtain the dependencies needed for execution. + public CreatedODataResult(T entity, ApiController controller) + : this(new NegotiatedContentResult(HttpStatusCode.Created, CheckNull(entity), controller)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The created entity. + /// The content negotiator to handle content negotiation. + /// The request message which led to this result. + /// The formatters to use to negotiate and format the content. + /// The location header for the created entity. + public CreatedODataResult(T entity, IContentNegotiator contentNegotiator, HttpRequestMessage request, + IEnumerable formatters, Uri locationHeader) + : this(new NegotiatedContentResult(HttpStatusCode.Created, CheckNull(entity), contentNegotiator, request, formatters)) + { + if (locationHeader == null) + { + throw Error.ArgumentNull("locationHeader"); + } + + _locationHeader = locationHeader; + } + + private CreatedODataResult(NegotiatedContentResult innerResult) + { + Contract.Assert(innerResult != null); + _innerResult = innerResult; + } + + /// + /// Gets the entity that was created. + /// + public T Entity + { + get + { + return _innerResult.Content; + } + } + + /// + /// Gets the content negotiator to handle content negotiation. + /// + public IContentNegotiator ContentNegotiator + { + get + { + return _innerResult.ContentNegotiator; + } + } + + /// + /// Gets the request message which led to this result. + /// + public HttpRequestMessage Request + { + get + { + return _innerResult.Request; + } + } + + /// + /// Gets the formatters to use to negotiate and format the created entity. + /// + public IEnumerable Formatters + { + get + { + return _innerResult.Formatters; + } + } + + /// + /// Gets the location header of the created entity. + /// + public Uri LocationHeader + { + get + { + _locationHeader = _locationHeader ?? GenerateLocationHeader(Request); + return _locationHeader; + } + } + + /// + public virtual async Task ExecuteAsync(CancellationToken cancellationToken) + { + IHttpActionResult result = GetInnerActionResult(Request); + HttpResponseMessage response = await result.ExecuteAsync(cancellationToken); + response.Headers.Location = LocationHeader; + ResultHelpers.AddEntityId(response, () => GenerateEntityId(Request)); + ResultHelpers.AddServiceVersion(response, () => ODataUtils.ODataVersionToString(ResultHelpers.GetODataResponseVersion(Request))); + return response; + } + + internal IHttpActionResult GetInnerActionResult(HttpRequestMessage request) + { + WebApiRequestHeaders headers = new WebApiRequestHeaders(request.Headers); + if (RequestPreferenceHelpers.RequestPrefersReturnNoContent(headers)) + { + return new StatusCodeResult(HttpStatusCode.NoContent, request); + } + else + { + return _innerResult; + } + } + + internal Uri GenerateEntityId(HttpRequestMessage request) + { + return ResultHelpers.GenerateODataLink(request, Entity, isEntityId: true); + } + + internal Uri GenerateLocationHeader(HttpRequestMessage request) + { + return ResultHelpers.GenerateODataLink(request, Entity, isEntityId: false); + } + + private static T CheckNull(T entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); + } + + return entity; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/ResultHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/ResultHelpers.cs new file mode 100644 index 0000000..d10bd14 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/ResultHelpers.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Results +{ + internal static partial class ResultHelpers + { + /// This signature uses types that are AspNet-specific. + public static Uri GenerateODataLink(HttpRequestMessage request, object entity, bool isEntityId) + { + IEdmModel model = request.GetModel(); + if (model == null) + { + throw new InvalidOperationException(SRResources.RequestMustHaveModel); + } + + ODataPath path = request.ODataProperties().Path; + if (path == null) + { + throw new InvalidOperationException(SRResources.ODataPathMissing); + } + + IEdmNavigationSource navigationSource = path.NavigationSource; + if (navigationSource == null) + { + throw new InvalidOperationException(SRResources.NavigationSourceMissingDuringSerialization); + } + + ODataSerializerContext serializerContext = new ODataSerializerContext + { + NavigationSource = navigationSource, + Model = model, + Url = request.GetUrlHelper() ?? new UrlHelper(request), + MetadataLevel = ODataMetadataLevel.FullMetadata, // Used internally to always calculate the links. + Request = request, + Path = path + }; + + IEdmEntityTypeReference entityType = GetEntityType(model, entity); + ResourceContext resourceContext = new ResourceContext(serializerContext, entityType, entity); + + return GenerateODataLink(resourceContext, isEntityId); + } + + /// This signature uses types that are AspNet-specific. + public static void AddEntityId(HttpResponseMessage response, Func entityId) + { + if (response.StatusCode == HttpStatusCode.NoContent) + { + response.Headers.TryAddWithoutValidation(EntityIdHeaderName, entityId().ToString()); + } + } + + public static void AddServiceVersion(HttpResponseMessage response, Func version) + { + if (response.StatusCode == HttpStatusCode.NoContent) + { + response.Headers.TryAddWithoutValidation(ODataVersionConstraint.ODataServiceVersionHeader, version()); + } + } + + internal static ODataVersion GetODataResponseVersion(HttpRequestMessage request) + { + if (request == null) + { + return ODataVersionConstraint.DefaultODataVersion; + } + + HttpRequestMessageProperties properties = request.ODataProperties(); + return properties.ODataMaxServiceVersion ?? + properties.ODataMinServiceVersion ?? + properties.ODataServiceVersion ?? + ODataVersionConstraint.DefaultODataVersion; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/UpdatedODataResult.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/UpdatedODataResult.cs new file mode 100644 index 0000000..d9a5b5c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Results/UpdatedODataResult.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Results; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Results +{ + /// + /// Represents an action result that is a response to a PUT, PATCH, or a MERGE operation on an OData entity. + /// + /// The entity type. + /// This action result handles content negotiation and the HTTP prefer header. + public class UpdatedODataResult : IHttpActionResult + { + private readonly NegotiatedContentResult _innerResult; + + /// + /// Initializes a new instance of the class. + /// + /// The updated entity. + /// The controller from which to obtain the dependencies needed for execution. + public UpdatedODataResult(T entity, ApiController controller) + : this(new NegotiatedContentResult(HttpStatusCode.OK, CheckNull(entity), controller)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The updated entity. + /// The content negotiator to handle content negotiation. + /// The request message which led to this result. + /// The formatters to use to negotiate and format the content. + public UpdatedODataResult(T entity, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable formatters) + : this(new NegotiatedContentResult(HttpStatusCode.OK, CheckNull(entity), contentNegotiator, request, formatters)) + { + } + + private UpdatedODataResult(NegotiatedContentResult innerResult) + { + Contract.Assert(innerResult != null); + _innerResult = innerResult; + } + + /// + /// Gets the entity that was updated. + /// + public T Entity + { + get + { + return _innerResult.Content; + } + } + + /// + /// Gets the content negotiator to handle content negotiation. + /// + public IContentNegotiator ContentNegotiator + { + get + { + return _innerResult.ContentNegotiator; + } + } + + /// + /// Gets the request message which led to this result. + /// + public HttpRequestMessage Request + { + get + { + return _innerResult.Request; + } + } + + /// + /// Gets the formatters to use to negotiate and format the content. + /// + public IEnumerable Formatters + { + get + { + return _innerResult.Formatters; + } + } + + /// + public virtual async Task ExecuteAsync(CancellationToken cancellationToken) + { + IHttpActionResult result = GetInnerActionResult(); + var response = await result.ExecuteAsync(cancellationToken); + ResultHelpers.AddServiceVersion(response, () => ODataUtils.ODataVersionToString(ResultHelpers.GetODataResponseVersion(Request))); + return response; + } + + internal IHttpActionResult GetInnerActionResult() + { + if (RequestPreferenceHelpers.RequestPrefersReturnContent(new WebApiRequestHeaders(_innerResult.Request.Headers))) + { + return _innerResult; + } + else + { + return new StatusCodeResult(HttpStatusCode.NoContent, _innerResult.Request); + } + } + + private static T CheckNull(T entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); + } + + return entity; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ActionRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ActionRoutingConvention.cs new file mode 100644 index 0000000..bb75d74 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ActionRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles action invocations. + /// + public partial class ActionRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, + ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/AttributeRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/AttributeRoutingConvention.cs new file mode 100644 index 0000000..d3742c4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/AttributeRoutingConvention.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Dispatcher; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing.Template; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Represents a routing convention that looks for s to match an + /// to a controller and an action. + /// + public partial class AttributeRoutingConvention : IODataRoutingConvention + { + private static readonly DefaultODataPathHandler _defaultPathHandler = new DefaultODataPathHandler(); + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + /// The to use for figuring out all the controllers to + /// look for a match. + /// This signature uses types that are AspNet-specific. + public AttributeRoutingConvention(string routeName, HttpConfiguration configuration) + : this(routeName, configuration, _defaultPathHandler) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + /// The to use for figuring out all the controllers to + /// look for a match. + /// The path template handler to be used for parsing the path templates. + /// This signature uses types that are AspNet-specific. + public AttributeRoutingConvention(string routeName, HttpConfiguration configuration, + IODataPathTemplateHandler pathTemplateHandler) + : this(routeName) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (pathTemplateHandler == null) + { + throw Error.ArgumentNull("pathTemplateHandler"); + } + + ODataPathTemplateHandler = pathTemplateHandler; + + // if settings is not on local, use the global configuration settings. + IODataPathHandler pathHandler = pathTemplateHandler as IODataPathHandler; + if (pathHandler != null && pathHandler.UrlKeyDelimiter == null) + { + ODataUrlKeyDelimiter urlKeyDelimiter = configuration.GetUrlKeyDelimiter(); + pathHandler.UrlKeyDelimiter = urlKeyDelimiter; + } + + Action oldInitializer = configuration.Initializer; + bool initialized = false; + configuration.Initializer = (config) => + { + if (!initialized) + { + initialized = true; + oldInitializer(config); + IHttpControllerSelector controllerSelector = config.Services.GetHttpControllerSelector(); + _attributeMappings = BuildAttributeMappings(controllerSelector.GetControllerMapping().Values); + } + }; + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + /// The collection of controllers to search for a match. + /// This signature uses types that are AspNet-specific and is only used for unit tests. + public AttributeRoutingConvention(string routeName, + IEnumerable controllers) + : this(routeName, controllers, _defaultPathHandler) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + /// The collection of controllers to search for a match. + /// The path template handler to be used for parsing the path templates. + /// This signature uses types that are AspNet-specific and is only used for unit tests. + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", + Justification = "See note on method.")] + public AttributeRoutingConvention(string routeName, + IEnumerable controllers, + IODataPathTemplateHandler pathTemplateHandler) + : this(routeName) + { + if (controllers == null) + { + throw Error.ArgumentNull("controllers"); + } + + if (pathTemplateHandler == null) + { + throw Error.ArgumentNull("pathTemplateHandler"); + } + + ODataPathTemplateHandler = pathTemplateHandler; + + _attributeMappings = BuildAttributeMappings(controllers); + } + + /// + /// Gets the attribute mappings. + /// + internal IDictionary AttributeMappings + { + get + { + if (_attributeMappings == null) + { + // Will throw an InvalidOperationException if this class is constructed with an HttpConfiguration + // but EnsureInitialized() hasn't been called yet. + throw Error.InvalidOperation(SRResources.Object_NotYetInitialized); + } + + return _attributeMappings; + } + } + + /// + /// Specifies whether OData route attributes on this controller should be mapped. + /// This method will execute before the derived type's instance constructor executes. Derived types must + /// be aware of this and should plan accordingly. For example, the logic in ShouldMapController() should be simple + /// enough so as not to depend on the "this" pointer referencing a fully constructed object. + /// + /// The controller. + /// true if this controller should be included in the map; false otherwise. + /// This signature uses types that are AspNet-specific. + public virtual bool ShouldMapController(HttpControllerDescriptor controller) + { + return true; + } + + /// + /// This signature uses types that are AspNet-specific. + public string SelectController(ODataPath odataPath, HttpRequestMessage request) + { + SelectControllerResult controllerResult = SelectControllerImpl( + odataPath, + new WebApiRequestMessage(request), + this.AttributeMappings); + + if (controllerResult != null) + { + request.Properties["AttributeRouteData"] = controllerResult.Values; + } + + return controllerResult != null ? controllerResult.ControllerName : null; + } + + /// + /// This signature uses types that are AspNet-specific. + public string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + if (controllerContext == null) + { + throw Error.ArgumentNull("controllerContext"); + } + + if (actionMap == null) + { + throw Error.ArgumentNull("actionMap"); + } + + object value = null; + controllerContext.Request.Properties.TryGetValue("AttributeRouteData", out value); + + SelectControllerResult controllerResult = new SelectControllerResult( + controllerContext.ControllerDescriptor.ControllerName, + value as IDictionary); + + return SelectActionImpl(new WebApiControllerContext(controllerContext, controllerResult)); + } + + /// This signature uses types that are AspNet-specific. + private IDictionary BuildAttributeMappings(IEnumerable controllers) + { + Dictionary attributeMappings = + new Dictionary(); + + foreach (HttpControllerDescriptor controller in controllers) + { + if (IsODataController(controller) && ShouldMapController(controller)) + { + IHttpActionSelector actionSelector = controller.Configuration.Services.GetActionSelector(); + ILookup actionMapping = actionSelector.GetActionMapping(controller); + HttpActionDescriptor[] actions = actionMapping.SelectMany(a => a).ToArray(); + + foreach (string prefix in GetODataRoutePrefixes(controller)) + { + foreach (HttpActionDescriptor action in actions) + { + IEnumerable pathTemplates = GetODataPathTemplates(prefix, action); + foreach (ODataPathTemplate pathTemplate in pathTemplates) + { + attributeMappings.Add(pathTemplate, new WebApiActionDescriptor(action)); + } + } + } + } + } + + return attributeMappings; + } + + /// This signature uses types that are AspNet-specific. + private static bool IsODataController(HttpControllerDescriptor controller) + { + return typeof(ODataController).IsAssignableFrom(controller.ControllerType); + } + + /// This signature uses types that are AspNet-specific. + private static IEnumerable GetODataRoutePrefixes(HttpControllerDescriptor controllerDescriptor) + { + Contract.Assert(controllerDescriptor != null); + + var prefixAttributes = controllerDescriptor.GetCustomAttributes(inherit: false); + + return GetODataRoutePrefixes(prefixAttributes, controllerDescriptor.ControllerType.FullName); + } + + /// This signature uses types that are AspNet-specific. + private IEnumerable GetODataPathTemplates(string prefix, HttpActionDescriptor action) + { + Contract.Assert(action != null); + + IEnumerable routeAttributes = + action.GetCustomAttributes(inherit: false); + + IServiceProvider requestContainer = action.Configuration.GetODataRootContainer(_routeName); + + string controllerName = action.ControllerDescriptor.ControllerName; + string actionName = action.ActionName; + + return + routeAttributes + .Where(route => String.IsNullOrEmpty(route.RouteName) || route.RouteName == _routeName) + .Select(route => GetODataPathTemplate(prefix, route.PathTemplate, requestContainer, controllerName, actionName)) + .Where(template => template != null); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs new file mode 100644 index 0000000..e3c0a91 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles dynamic properties for open type. + /// + public partial class DynamicPropertyRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "These are simple conversion function and cannot be split up.")] + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntityRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntityRoutingConvention.cs new file mode 100644 index 0000000..253cf0c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntityRoutingConvention.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles operating on entities by key. + /// + public partial class EntityRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntitySetRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntitySetRoutingConvention.cs new file mode 100644 index 0000000..f7a5c8d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/EntitySetRoutingConvention.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles entity sets. + /// + public partial class EntitySetRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/FunctionRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/FunctionRoutingConvention.cs new file mode 100644 index 0000000..380e0de --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/FunctionRoutingConvention.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles function invocations. + /// + public partial class FunctionRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "These are simple conversion function and cannot be split up.")] + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/IODataRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/IODataRoutingConvention.cs new file mode 100644 index 0000000..7cc67ba --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/IODataRoutingConvention.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net.Http; +using System.Web.Http.Controllers; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Provides an abstraction for selecting a controller and an action for OData requests. + /// + public interface IODataRoutingConvention + { + /// + /// Selects the controller for OData requests. + /// + /// The OData path. + /// The request. + /// null if the request isn't handled by this convention; otherwise, the name of the selected controller + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "odata", Justification = "odata is spelled correctly")] + string SelectController(ODataPath odataPath, HttpRequestMessage request); + + /// + /// Selects the action for OData requests. + /// + /// The OData path. + /// The controller context. + /// The action map. + /// null if the request isn't handled by this convention; otherwise, the name of the selected action + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "odata", Justification = "odata is spelled correctly")] + string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap); + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/MetadataRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/MetadataRoutingConvention.cs new file mode 100644 index 0000000..54aa071 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/MetadataRoutingConvention.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Net.Http; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles OData metadata requests. + /// + public partial class MetadataRoutingConvention : IODataRoutingConvention + { + /// + /// Selects the controller for OData requests. + /// + /// The OData path. + /// The request. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected controller + /// + public string SelectController(ODataPath odataPath, HttpRequestMessage request) + { + SelectControllerResult controllerResult = SelectControllerImpl( + odataPath, + new WebApiRequestMessage(request)); + + return controllerResult != null ? controllerResult.ControllerName : null; + } + + /// + /// Selects the action for OData requests. + /// + /// The OData path. + /// The controller context. + /// The action map. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected action + /// + public string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + if (controllerContext == null) + { + throw Error.ArgumentNull("controllerContext"); + } + + if (actionMap == null) + { + throw Error.ArgumentNull("actionMap"); + } + + SelectControllerResult controllerResult = + new SelectControllerResult( + controllerContext.ControllerDescriptor.ControllerName, + null); + + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, controllerResult), + new WebApiActionMap(actionMap)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationRoutingConvention.cs new file mode 100644 index 0000000..6321f56 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationRoutingConvention.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles navigation properties. + /// + public partial class NavigationRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs new file mode 100644 index 0000000..c34d857 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles navigation sources + /// (entity sets or singletons) + /// + public abstract partial class NavigationSourceRoutingConvention : IODataRoutingConvention + { + /// + /// Selects the controller for OData requests. + /// + /// The OData path. + /// The request. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected controller + /// + /// This signature uses types that are AspNet-specific. + public virtual string SelectController(ODataPath odataPath, HttpRequestMessage request) + { + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + SelectControllerResult controllerResult = SelectControllerImpl(odataPath); + + if (controllerResult != null) + { + request.Properties["AttributeRouteData"] = controllerResult.Values; + } + + return controllerResult != null ? controllerResult.ControllerName : null; + } + + /// + /// Selects the action for OData requests. + /// + /// The OData path. + /// The controller context. + /// The action map. + /// + /// null if the request isn't handled by this convention; otherwise, the name of the selected action + /// + /// This signature uses types that are AspNet-specific. + public abstract string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, + ILookup actionMap); + + /// + /// Validate the parameters passed to SelectAction. + /// + /// The OData path. + /// The controller context. + /// The action map. + internal static void ValidateSelectActionParameters(ODataPath odataPath, HttpControllerContext controllerContext, + ILookup actionMap) + { + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + if (controllerContext == null) + { + throw Error.ArgumentNull("controllerContext"); + } + + if (actionMap == null) + { + throw Error.ArgumentNull("actionMap"); + } + } + + /// + /// Get the controller result used to call the shared version of SelectAction() + /// + /// The controller context. + internal static SelectControllerResult GetControllerResult(HttpControllerContext controllerContext) + { + string controllerName = null; + object value = null; + + if (controllerContext != null) + { + if (controllerContext.Request != null) + { + controllerContext.Request.Properties.TryGetValue("AttributeRouteData", out value); + } + + if (controllerContext.ControllerDescriptor != null) + { + controllerName = controllerContext.ControllerDescriptor.ControllerName; + } + } + + return new SelectControllerResult(controllerName, value as IDictionary); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ODataRoutingConventions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ODataRoutingConventions.cs new file mode 100644 index 0000000..1fde113 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/ODataRoutingConventions.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Web.Http; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Provides helper methods for creating routing conventions. + /// + public static class ODataRoutingConventions + { + /// + /// Creates a mutable list of the default OData routing conventions with attribute routing enabled. + /// + /// The name of the route. + /// The server configuration. + /// A mutable list of the default OData routing conventions. + public static IList CreateDefaultWithAttributeRouting( + string routeName, + HttpConfiguration configuration) + { + if (configuration == null) + { + throw Error.ArgumentNull("configuration"); + } + + if (routeName == null) + { + throw Error.ArgumentNull("routeName"); + } + + IList routingConventions = CreateDefault(); + AttributeRoutingConvention routingConvention = new AttributeRoutingConvention(routeName, configuration); + routingConventions.Insert(0, routingConvention); + return routingConventions; + } + + /// + /// Creates a mutable list of the default OData routing conventions. + /// + /// A mutable list of the default OData routing conventions. + public static IList CreateDefault() + { + return new List() + { + new MetadataRoutingConvention(), + new EntitySetRoutingConvention(), + new SingletonRoutingConvention(), + new EntityRoutingConvention(), + new NavigationRoutingConvention(), + new PropertyRoutingConvention(), + new DynamicPropertyRoutingConvention(), + new RefRoutingConvention(), + new ActionRoutingConvention(), + new FunctionRoutingConvention(), + new UnmappedRequestRoutingConvention() + }; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/PropertyRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/PropertyRoutingConvention.cs new file mode 100644 index 0000000..9106aee --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/PropertyRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles reading structural properties. + /// + public partial class PropertyRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, + ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/RefRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/RefRoutingConvention.cs new file mode 100644 index 0000000..b90766a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/RefRoutingConvention.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles entity reference manipulations. + /// + public partial class RefRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/SingletonRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/SingletonRoutingConvention.cs new file mode 100644 index 0000000..8a34078 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/SingletonRoutingConvention.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles the singleton. + /// + public partial class SingletonRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl( + odataPath, + new WebApiControllerContext(controllerContext, GetControllerResult(controllerContext)), + new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs new file mode 100644 index 0000000..042310e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Web.Http.Controllers; +using Microsoft.AspNet.OData.Adapters; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that always selects the action named HandleUnmappedRequest if that action is present. + /// + public partial class UnmappedRequestRoutingConvention + { + /// + /// This signature uses types that are AspNet-specific. + public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup actionMap) + { + ValidateSelectActionParameters(odataPath, controllerContext, actionMap); + return SelectActionImpl(new WebApiActionMap(actionMap)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataActionSelector.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataActionSelector.cs new file mode 100644 index 0000000..8dbb933 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataActionSelector.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing.Conventions; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of that uses the server's OData routing conventions to select an action for OData requests. + /// + public class ODataActionSelector : IHttpActionSelector + { + private const string MessageDetailKey = "MessageDetail"; + private readonly IHttpActionSelector _innerSelector; + + /// + /// Initializes a new instance of the class. + /// + /// The inner controller selector to call. + public ODataActionSelector(IHttpActionSelector innerSelector) + { + if (innerSelector == null) + { + throw Error.ArgumentNull("innerSelector"); + } + + _innerSelector = innerSelector; + } + + /// + /// Returns a map, keyed by action string, of all that the selector can select. This is primarily called by to discover all the possible actions in the controller. + /// + /// The controller descriptor. + /// + /// A map of that the selector can select, or null if the selector does not have a well-defined mapping of . + /// + /// + public ILookup GetActionMapping(HttpControllerDescriptor controllerDescriptor) + { + return _innerSelector.GetActionMapping(controllerDescriptor); + } + + /// + /// Selects an action for the . + /// + /// The controller context. + /// + /// The selected action. + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Response disposed later")] + public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) + { + if (controllerContext == null) + { + throw Error.ArgumentNull("controllerContext"); + } + + HttpRequestMessage request = controllerContext.Request; + ODataPath odataPath = request.ODataProperties().Path; + IEnumerable routingConventions = request.GetRoutingConventions(); + IHttpRouteData routeData = controllerContext.RouteData; + + if (odataPath == null || routingConventions == null || routeData.Values.ContainsKey(ODataRouteConstants.Action)) + { + return _innerSelector.SelectAction(controllerContext); + } + + ILookup actionMap = _innerSelector.GetActionMapping(controllerContext.ControllerDescriptor); + + foreach (IODataRoutingConvention routingConvention in routingConventions) + { + string actionName = routingConvention.SelectAction( + odataPath, + controllerContext, + actionMap); + + if (actionName != null) + { + routeData.Values[ODataRouteConstants.Action] = actionName; + return _innerSelector.SelectAction(controllerContext); + } + } + + throw new HttpResponseException(CreateErrorResponse(request, HttpStatusCode.NotFound, + Error.Format(SRResources.NoMatchingResource, controllerContext.Request.RequestUri), + Error.Format(SRResources.NoRoutingHandlerToSelectAction, odataPath.PathTemplate))); + } + + private static HttpResponseMessage CreateErrorResponse(HttpRequestMessage request, HttpStatusCode statusCode, string message, string messageDetail) + { + HttpError error = new HttpError(message); + if (request.ShouldIncludeErrorDetail()) + { + error.Add(MessageDetailKey, messageDetail); + } + + return request.CreateErrorResponse(statusCode, error); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathParameterBindingAttribute.cs new file mode 100644 index 0000000..dd8cb29 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathParameterBindingAttribute.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Metadata; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Implementation of used to bind an instance of as an action parameter. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] + public sealed partial class ODataPathParameterBindingAttribute : ParameterBindingAttribute + { + /// + /// Gets the parameter binding. + /// + /// The parameter description. + /// + /// The parameter binding. + /// + public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) + { + return new ODataPathParameterBinding(parameter); + } + + internal class ODataPathParameterBinding : HttpParameterBinding + { + public ODataPathParameterBinding(HttpParameterDescriptor parameterDescriptor) + : base(parameterDescriptor) + { + } + + public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) + { + if (actionContext == null) + { + throw Error.ArgumentNull("actionContext"); + } + + HttpRequestMessage request = actionContext.Request; + + if (request == null) + { + throw Error.Argument("actionContext", SRResources.ActionContextMustHaveRequest); + } + + SetValue(actionContext, request.ODataProperties().Path); + + return TaskHelpers.Completed(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathRouteConstraint.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathRouteConstraint.cs new file mode 100644 index 0000000..df5b93e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataPathRouteConstraint.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing.Conventions; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of that only matches OData paths. + /// + public partial class ODataPathRouteConstraint : IHttpRouteConstraint + { + /// + /// Determines whether this instance equals a specified route. + /// + /// The request. + /// The route to compare. + /// The name of the parameter. + /// A list of parameter values. + /// The route direction. + /// + /// True if this instance equals a specified route; otherwise, false. + /// + /// This signature uses types that are AspNet-specific. + public virtual bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (values == null) + { + throw Error.ArgumentNull("values"); + } + + if (routeDirection == HttpRouteDirection.UriResolution) + { + ODataPath path = null; + + object oDataPathValue; + if (values.TryGetValue(ODataRouteConstants.ODataPath, out oDataPathValue)) + { + string requestLeftPart = request.RequestUri.GetLeftPart(UriPartial.Path); + string queryString = request.RequestUri.Query; + + path = GetODataPath(oDataPathValue as string, requestLeftPart, queryString, () => request.CreateRequestContainer(RouteName)); + } + + if (path != null) + { + // Set all the properties we need for routing, querying, formatting + HttpRequestMessageProperties properties = request.ODataProperties(); + properties.Path = path; + properties.RouteName = RouteName; + + if (!values.ContainsKey(ODataRouteConstants.Controller)) + { + // Select controller name using the routing conventions + string controllerName = SelectControllerName(path, request); + if (controllerName != null) + { + values[ODataRouteConstants.Controller] = controllerName; + } + } + + return true; + } + + // The request doesn't match this route so dispose the request container. + request.DeleteRequestContainer(true); + return false; + } + else + { + // This constraint only applies to URI resolution + return true; + } + } + + /// + /// Selects the name of the controller to dispatch the request to. + /// + /// The OData path of the request. + /// The request. + /// The name of the controller to dispatch to, or null if one cannot be resolved. + /// This signature uses types that are AspNet-specific. + protected virtual string SelectControllerName(ODataPath path, HttpRequestMessage request) + { + foreach (IODataRoutingConvention routingConvention in request.GetRoutingConventions()) + { + string controllerName = routingConvention.SelectController(path, request); + if (controllerName != null) + { + return controllerName; + } + } + + return null; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataRoute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataRoute.cs new file mode 100644 index 0000000..305f8e1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataRoute.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net.Http; +using System.Web.Http.Routing; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// A route implementation for OData routes. It supports passing in a route prefix for the route as well + /// as a path constraint that parses the request path as OData. + /// + public partial class ODataRoute : HttpRoute + { + /// + /// Initializes a new instance of the class. + /// + /// The route prefix. + /// The OData path constraint. + public ODataRoute(string routePrefix, ODataPathRouteConstraint pathConstraint) + : this( + routePrefix, (IHttpRouteConstraint)pathConstraint, defaults: null, constraints: null, dataTokens: null, + handler: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The route prefix. + /// The route constraint. + /// This signature uses types that are AspNet-specific. + public ODataRoute(string routePrefix, IHttpRouteConstraint routeConstraint) + : this(routePrefix, routeConstraint, defaults: null, constraints: null, dataTokens: null, handler: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The route prefix. + /// The OData path constraint. + /// The default values for the route. + /// The route constraints. + /// The data tokens. + /// The message handler for the route. + /// This signature uses types that are AspNet-specific. + public ODataRoute( + string routePrefix, + ODataPathRouteConstraint pathConstraint, + HttpRouteValueDictionary defaults, + HttpRouteValueDictionary constraints, + HttpRouteValueDictionary dataTokens, + HttpMessageHandler handler) + : this(routePrefix, (IHttpRouteConstraint)pathConstraint, defaults, constraints, dataTokens, handler) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The route prefix. + /// The route constraint. + /// The default values for the route. + /// The route constraints. + /// The data tokens. + /// The message handler for the route. + /// This signature uses types that are AspNet-specific. + public ODataRoute( + string routePrefix, + IHttpRouteConstraint routeConstraint, + HttpRouteValueDictionary defaults, + HttpRouteValueDictionary constraints, + HttpRouteValueDictionary dataTokens, + HttpMessageHandler handler) + : base(GetRouteTemplate(routePrefix), defaults, constraints, dataTokens, handler) + { + RouteConstraint = routeConstraint; + Initialize(routePrefix, routeConstraint as ODataPathRouteConstraint); + + if (routeConstraint != null) + { + Constraints.Add(ODataRouteConstants.ConstraintName, routeConstraint); + } + + Constraints.Add(ODataRouteConstants.VersionConstraintName, new ODataVersionConstraint()); + } + + /// + /// Gets the on this route. + /// + /// This signature uses types that are AspNet-specific. + public IHttpRouteConstraint RouteConstraint { get; private set; } + + /// + /// This signature uses types that are AspNet-specific. + public override IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary values) + { + // Only perform URL generation if the "httproute" key was specified. This allows these + // routes to be ignored when a regular MVC app tries to generate URLs. Without this special + // key an HTTP route used for Web API would normally take over almost all the routes in a + // typical app. + if (values != null && values.Keys.Contains(HttpRoute.HttpRouteKey, StringComparer.OrdinalIgnoreCase)) + { + // Fast path link generation where we recognize an OData route of the form "prefix/{*odataPath}". + // Link generation using HttpRoute.GetVirtualPath can consume up to 30% of processor time + object odataPathValue; + if (values.TryGetValue(ODataRouteConstants.ODataPath, out odataPathValue)) + { + string odataPath = odataPathValue as string; + if (odataPath != null) + { + // Try to generate an optimized direct link + // Otherwise, fall back to the base implementation + return CanGenerateDirectLink + ? GenerateLinkDirectly(odataPath) + : base.GetVirtualPath(request, values); + } + } + } + + return null; + } + + /// + /// Relax the version constraint. The service will allow clients to send both OData V4 and previous max version headers. + /// Headers for the previous max version will be ignored. + /// + /// Returns itself so that multiple calls can be chained. + [Obsolete("The version constraint is relaxed by default")] + public ODataRoute HasRelaxedODataVersionConstraint() + { + return SetODataVersionConstraint(true); + } + + private ODataRoute SetODataVersionConstraint(bool isRelaxedMatch) + { + object constraint; + if (Constraints.TryGetValue(ODataRouteConstants.VersionConstraintName, out constraint)) + { + ((ODataVersionConstraint)constraint).IsRelaxedMatch = isRelaxedMatch; + } + return this; + } + + /// This signature uses types that are AspNet-specific. + internal HttpVirtualPathData GenerateLinkDirectly(string odataPath) + { + Contract.Assert(odataPath != null); + Contract.Assert(CanGenerateDirectLink); + + string link = CombinePathSegments(RoutePrefix, odataPath); + link = UriEncode(link); + return new HttpVirtualPathData(this, link); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataValueProviderFactory.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataValueProviderFactory.cs new file mode 100644 index 0000000..bacd3aa --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataValueProviderFactory.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Globalization; +using System.Web.Http.Controllers; +using System.Web.Http.ValueProviders; +using System.Web.Http.ValueProviders.Providers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; + +namespace Microsoft.AspNet.OData.Routing +{ + internal class ODataValueProviderFactory : ValueProviderFactory, IUriValueProviderFactory + { + public override IValueProvider GetValueProvider(HttpActionContext actionContext) + { + if (actionContext == null) + { + throw Error.ArgumentNull("actionContext"); + } + + return new ODataValueProvider(actionContext.Request.ODataProperties().RoutingConventionsStore); + } + + private class ODataValueProvider : NameValuePairsValueProvider + { + public ODataValueProvider(IDictionary routeData) + : base(routeData, CultureInfo.InvariantCulture) + { + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataVersionConstraint.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataVersionConstraint.cs new file mode 100644 index 0000000..cf67ab4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Routing/ODataVersionConstraint.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of that only matches a specific OData protocol + /// version. This constraint won't match incoming requests that contain any of the previous OData version + /// headers (for OData versions 1.0 to 3.0) regardless of the version in the current version headers. + /// + public partial class ODataVersionConstraint : IHttpRouteConstraint + { + /// + /// This signature uses types that are AspNet-specific. + public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, + IDictionary values, HttpRouteDirection routeDirection) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (routeDirection == HttpRouteDirection.UriGeneration) + { + return true; + } + + IDictionary> headers = request.Headers.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + ODataVersion? serviceVersion = request.ODataProperties().ODataServiceVersion; + ODataVersion? maxServiceVersion = request.ODataProperties().ODataMaxServiceVersion; + + return IsVersionMatch(headers, serviceVersion, maxServiceVersion); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Settings.StyleCop b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Settings.StyleCop new file mode 100644 index 0000000..8289a8a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Settings.StyleCop @@ -0,0 +1,29 @@ + + + + + + + + + + + o + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config new file mode 100644 index 0000000..dde2c3c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config new file mode 100644 index 0000000..ac131a1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionDescriptor.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionDescriptor.cs new file mode 100644 index 0000000..09905b5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionDescriptor.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi action description to OData WebApi. + /// + internal class WebApiActionDescriptor : IWebApiActionDescriptor + { + /// + /// Gets the collection of supported HTTP methods for the descriptor. + /// + private IList supportedHttpMethods; + + /// + /// Gets the collection of supported HTTP methods for conventions. + /// + private static readonly string[] SupportedHttpMethodConventions = new string[] + { + "GET", + "PUT", + "POST", + "DELETE", + "PATCH", + "HEAD", + "OPTIONS", + }; + + /// + /// The inner action wrapped by this instance. + /// + private ControllerActionDescriptor innerDescriptor; + + /// + /// Initializes a new instance of the WebApiActionDescriptor class. + /// + /// The inner descriptor. + public WebApiActionDescriptor(ControllerActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw Error.ArgumentNull("actionDescriptor"); + } + + this.innerDescriptor = actionDescriptor; + this.supportedHttpMethods = new List(); + + // Determine the supported methods. + IEnumerable actionMethods = actionDescriptor.ActionConstraints? + .OfType() + .FirstOrDefault()? + .HttpMethods; + + if (actionMethods == null) + { + // If no HttpMethodActionConstraint is specified, fall back to convention the way AspNet does. + actionMethods = SupportedHttpMethodConventions + .Where(method => actionDescriptor.MethodInfo.Name.StartsWith(method, StringComparison.OrdinalIgnoreCase)); + + // Use POST as the default method. + if (!actionMethods.Any()) + { + actionMethods = new string[] { "POST" }; + } + } + + foreach (string method in actionMethods) + { + bool ignoreCase = true; + ODataRequestMethod methodEnum = ODataRequestMethod.Unknown; + if (Enum.TryParse(method, ignoreCase, out methodEnum)) + { + this.supportedHttpMethods.Add(methodEnum); + } + } + } + + /// + /// Gets the name of the controller. + /// + public string ControllerName + { + get { return this.innerDescriptor.ControllerName; } + } + + /// + /// Gets the name of the action. + /// + public string ActionName + { + get { return this.innerDescriptor.ActionName; } + } + + /// + /// Returns the custom attributes associated with the action descriptor. + /// + /// The type of attribute to search for. + /// true to search this action's inheritance chain to find the attributes; otherwise, false. + /// A list of attributes of type T. + public IEnumerable GetCustomAttributes(bool inherit) where T : Attribute + { + return this.innerDescriptor.ControllerTypeInfo.GetCustomAttributes(inherit); + } + + /// + /// Determine if the Http method is a match. + /// + public bool IsHttpMethodSupported(ODataRequestMethod method) + { + return this.supportedHttpMethods.Contains(method); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionMap.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionMap.cs new file mode 100644 index 0000000..5a10816 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiActionMap.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi action map to OData WebApi. + /// + internal class WebApiActionMap : IWebApiActionMap + { + /// + /// The inner map wrapped by this instance. + /// + private IEnumerable innerMap; + + /// + /// Initializes a new instance of the WebApiActionMap class. + /// + /// The inner map. + public WebApiActionMap(IEnumerable actionMap) + { + if (actionMap == null) + { + throw Error.ArgumentNull("actionMap"); + } + + this.innerMap = actionMap; + } + + /// + /// Determines whether a specified action exists. + /// + /// The action name. + /// True if the action name exist; false otherwise. + public bool Contains(string name) + { + return this.innerMap.Any(a => String.Equals(a.ActionName, name, StringComparison.InvariantCultureIgnoreCase)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiAssembliesResolver.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiAssembliesResolver.cs new file mode 100644 index 0000000..253815c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiAssembliesResolver.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Mvc.ApplicationParts; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi assembly resolver to OData WebApi. + /// + internal partial class WebApiAssembliesResolver : IWebApiAssembliesResolver + { + /// + /// The inner manager wrapped by this instance. + /// + private ApplicationPartManager innerManager; + + /// + /// Initializes a new instance of the WebApiAssembliesResolver class. + /// + public WebApiAssembliesResolver() + { + this.innerManager = null; + } + + /// + /// Initializes a new instance of the WebApiAssembliesResolver class. + /// + /// The inner manager. + public WebApiAssembliesResolver(ApplicationPartManager applicationPartManager) + { + this.innerManager = applicationPartManager; + } + + /// + /// Returns a list of assemblies available for the application. + /// + /// A list of assemblies available for the application. + public IEnumerable Assemblies + { + get + { + if (this.innerManager != null) + { + IList parts = this.innerManager.ApplicationParts; + IEnumerable assemblies = parts + .OfType() + .Select(p => (p as AssemblyPart).Assembly) + .Distinct(); + + if (assemblies.Any()) + { + return assemblies; + } + } + + // Without an ApplicationPartManager, fall back to a list of assemblies for the entire process. + // We cannot provide one on a per-route basis because that would require a request container, + // which would already have a part manager. The AppDomain provides a list of all assemblies + // in the domain; .NET Core 1.x does not support AppDomain but .NET Core 2.x does. + return AppDomain.CurrentDomain.GetAssemblies().Distinct(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiContext.cs new file mode 100644 index 0000000..7b01302 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiContext.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; +using IODataRoutingConvention = Microsoft.AspNet.OData.Routing.Conventions.IODataRoutingConvention; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi OData properties to OData WebApi. + /// + internal class WebApiContext : IWebApiContext + { + /// + /// The inner context wrapped by this instance. + /// + private IODataFeature innerFeature; + + /// + /// Initializes a new instance of the WebApiContext class. + /// + /// The inner feature. + public WebApiContext(IODataFeature feature) + { + this.innerFeature = feature; + } + + /// + /// Gets or sets the parsed OData of the request. + /// + public ApplyClause ApplyClause + { + get { return this.innerFeature.ApplyClause; } + set { this.innerFeature.ApplyClause = value; } + } + + /// + /// Gets or sets the next link for the OData response. + /// + public Uri NextLink + { + get { return this.innerFeature.NextLink; } + set { this.innerFeature.NextLink = value; } + } + + /// + /// Gets or sets the delta link for the OData response. + /// + public Uri DeltaLink + { + get { return this.innerFeature.DeltaLink; } + set { this.innerFeature.DeltaLink = value; } + } + + /// + /// Gets the OData path. + /// + public ODataPath Path + { + get { return this.innerFeature.Path; } + } + + /// + /// Gets the route name for generating OData links. + /// + public string RouteName + { + get { return this.innerFeature.RouteName; } + } + + /// + /// Gets the data store used by s to store any custom route data. + /// + /// Initially an empty IDictionary<string, object>. + public IDictionary RoutingConventionsStore + { + get { return this.innerFeature.RoutingConventionsStore; } + } + + /// + /// Gets or sets the processed OData of the request. It will be different from QueryOptions.SelectExpand. SelectExpandClause as the $level is processed in this clause. + /// + public SelectExpandClause ProcessedSelectExpandClause + { + get { return this.innerFeature.SelectExpandClause; } + set { this.innerFeature.SelectExpandClause = value; } + } + + /// + /// Gets or sets the parsed of the request. + /// + public ODataQueryOptions QueryOptions + { + get + { + // since we wanted to avoid a breaking change by modifying the interface, we cast to check if it is our ODataFeature class before we access the internal property. To be cleaned up with 8.x. + ODataFeature feature = this.innerFeature as ODataFeature; + if (feature != null) + { + return feature.QueryOptions; + } + + return null; + } + set + { + ODataFeature feature = this.innerFeature as ODataFeature; + if (feature != null) + { + feature.QueryOptions = value; + } + } + } + + /// + /// Gets or sets the total count for the OData response. + /// + /// null if no count should be sent back to the client. + public long? TotalCount + { + get { return this.innerFeature.TotalCount; } + } + + /// + /// Page size to be used by skiptoken implementation for the top-level resource for the request. + /// + public int PageSize + { + get + { + // since we wanted to avoid a breaking change by modifying the interface, we cast to check if it is our ODataFeature class before we access the internal property. + ODataFeature feature = this.innerFeature as ODataFeature; + if (feature != null) + { + return feature.PageSize; + } + + return 0; + } + set + { + ODataFeature feature = this.innerFeature as ODataFeature; + if (feature != null) + { + feature.PageSize = value; + } + } + } + + /// + /// Gets or sets the total count function for the OData response. + /// + public Func TotalCountFunc + { + get { return this.innerFeature.TotalCountFunc; } + set { this.innerFeature.TotalCountFunc = value; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiControllerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiControllerContext.cs new file mode 100644 index 0000000..b89d135 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiControllerContext.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi controller context to OData WebApi. + /// + internal class WebApiControllerContext : IWebApiControllerContext + { + /// + /// The inner context wrapped by this instance. + /// + private RouteContext innerContext; + + /// + /// Initializes a new instance of the WebApiControllerContext class. + /// + /// The inner context. + /// The selected controller result. + public WebApiControllerContext(RouteContext routeContext, SelectControllerResult controllerResult) + { + if (routeContext == null) + { + throw Error.ArgumentNull("routeContext"); + } + + if (controllerResult == null) + { + throw Error.ArgumentNull("controllerResult"); + } + + this.innerContext = routeContext; + this.ControllerResult = controllerResult; + + HttpRequest request = routeContext.HttpContext.Request; + if (request != null) + { + this.Request = new WebApiRequestMessage(request); + } + } + + /// + /// The selected controller result. + /// + public SelectControllerResult ControllerResult { get; private set; } + + /// + /// Gets the request. + /// + public IWebApiRequestMessage Request { get; private set; } + + /// + /// Gets the route data. + /// + public IDictionary RouteData + { + get { return this.innerContext.RouteData.Values; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiOptions.cs new file mode 100644 index 0000000..6586a6d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiOptions.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi options to OData WebApi. + /// + internal class WebApiOptions : IWebApiOptions + { + /// + /// Initializes a new instance of the WebApiOptions class. + /// + /// The inner options. + public WebApiOptions(ODataOptions options) + { + if (options == null) + { + throw Error.ArgumentNull("options"); + } + + this.NullDynamicPropertyIsEnabled = options.NullDynamicPropertyIsEnabled; + this.UrlKeyDelimiter = options.UrlKeyDelimiter; + } + + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not. + /// + public ODataUrlKeyDelimiter UrlKeyDelimiter { get; private set; } + + /// + /// Gets or Sets a value indicating if value should be emitted for dynamic properties which are null. + /// + public bool NullDynamicPropertyIsEnabled { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestHeaders.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestHeaders.cs new file mode 100644 index 0000000..012703f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestHeaders.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi request headers to OData WebApi. + /// + internal class WebApiRequestHeaders : IWebApiHeaders + { + /// + /// The inner collection wrapped by this instance. + /// + private IHeaderDictionary innerCollection; + + /// + /// Initializes a new instance of the WebApiRequestMessage class. + /// + /// The inner collection. + public WebApiRequestHeaders(IHeaderDictionary headers) + { + if (headers == null) + { + throw Error.ArgumentNull("headers"); + } + + this.innerCollection = headers; + } + + /// + /// Return if a specified header and specified values are stored in the collection. + /// + /// The specified header. + /// The specified header values. + /// true is the specified header name and values are stored in the collection; otherwise false. + public bool TryGetValues(string key, out IEnumerable values) + { + StringValues stringValues; + bool found = this.innerCollection.TryGetValue(key, out stringValues); + + values = stringValues.AsEnumerable(); + return found; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestMessage.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestMessage.cs new file mode 100644 index 0000000..678b0b2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiRequestMessage.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to convert Asp.Net WebApi request message to OData WebApi. + /// + internal class WebApiRequestMessage : IWebApiRequestMessage + { + /// + /// The inner request wrapped by this instance. + /// + internal HttpRequest innerRequest; + + /// + /// Initializes a new instance of the WebApiRequestMessage class. + /// + /// The inner request. + public WebApiRequestMessage(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + this.innerRequest = request; + this.Headers = new WebApiRequestHeaders(request.Headers); + + IODataFeature feature = request.ODataFeature(); + if (feature != null) + { + this.Context = new WebApiContext(feature); + } + + // Get the ODataOptions from the global service provider. + ODataOptions options = request.HttpContext.RequestServices.GetRequiredService(); + if (options != null) + { + this.Options = new WebApiOptions(options); + } + } + + /// + /// Gets the contents of the HTTP message. + /// + public IWebApiContext Context { get; private set; } + + /// + /// WebAPI headers associated with the request + /// + public IWebApiHeaders Headers { get; private set;} + + /// + /// Gets a value indicating if this is a count request. + /// + /// + public bool IsCountRequest() + { + ODataPath path = this.innerRequest.ODataFeature().Path; + return path != null && path.Segments.LastOrDefault() is CountSegment; + } + + /// + /// Gets the HTTP method used by the HTTP request message. + /// + public ODataRequestMethod Method + { + get + { + string method = this.innerRequest.Method.ToUpperInvariant(); + + bool ignoreCase = true; + ODataRequestMethod methodEnum = ODataRequestMethod.Unknown; + if (Enum.TryParse(method, ignoreCase, out methodEnum)) + { + return methodEnum; + } + + return ODataRequestMethod.Unknown; + } + } + + /// + /// Get the options associated with the request. + /// + public IWebApiOptions Options { get; private set; } + + /// + /// The request container associated with the request. + /// + public IServiceProvider RequestContainer + { + get { return this.innerRequest.GetRequestContainer(); } + } + + /// + /// Gets the Uri used for the HTTP request. + /// + public Uri RequestUri + { + get { return new Uri(this.innerRequest.GetEncodedUrl()); } + } + + /// + /// get the deserializer provider associated with the request. + /// + /// + public ODataDeserializerProvider DeserializerProvider + { + get + { + return this.innerRequest.GetRequestContainer().GetRequiredService(); + } + } + + /// + /// Get the next page link for a given page size. + /// + /// The page size. + /// Object which will be used to generate the skiptoken value. + /// Function that takes in an instance and returns the skiptoken value string. + /// + public Uri GetNextPageLink(int pageSize, object instance = null, Func objToSkipTokenValue = null) + { + return this.innerRequest.GetNextPageLink(pageSize, instance, objToSkipTokenValue); + } + + /// + /// Creates an ETag from concurrency property names and values. + /// + /// The input property names and values. + /// The generated ETag string. + public string CreateETag(IDictionary properties) + { + IODataFeature feature = this.innerRequest.ODataFeature(); + if (feature == null) + { + throw Error.InvalidOperation(SRResources.RequestMustContainConfiguration); + } + + return this.innerRequest.GetETagHandler().CreateETag(properties)?.ToString(); + } + + /// + /// Gets the EntityTagHeaderValue ETag>. + /// + /// This function uses types that are AspNet-specific. + public ETag GetETag(EntityTagHeaderValue etagHeaderValue) + { + return this.innerRequest.GetETag(etagHeaderValue); + } + + /// + /// Gets the EntityTagHeaderValue ETag>. + /// + /// This function uses types that are AspNet-specific. + public ETag GetETag(EntityTagHeaderValue etagHeaderValue) + { + return this.innerRequest.GetETag(etagHeaderValue); + } + + /// + /// Gets a list of content Id mappings associated with the request. + /// + /// + public IDictionary ODataContentIdMapping + { + get { return null; } + } + + /// + /// Gets the path handler associated with the request. + /// + /// + public IODataPathHandler PathHandler + { + get { return this.innerRequest.GetPathHandler(); } + } + + /// + /// Gets the query parameters from the query with duplicated key ignored. + /// + /// + public IDictionary QueryParameters + { + get + { + IDictionary result = new Dictionary(); + foreach (var pair in this.innerRequest.Query) + { + if (!result.ContainsKey(pair.Key)) + { + result.Add(pair.Key, pair.Value); + } + } + return result; + } + } + + /// + /// Gets the reader settings associated with the request. + /// + /// + public ODataMessageReaderSettings ReaderSettings + { + get { return this.innerRequest.GetReaderSettings(); } + } + + /// + /// Gets the writer settings associated with the request. + /// + /// + public ODataMessageWriterSettings WriterSettings + { + get { return this.innerRequest.GetWriterSettings(); } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiUrlHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiUrlHelper.cs new file mode 100644 index 0000000..43618ff --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Adapters/WebApiUrlHelper.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Adapters +{ + /// + /// Adapter class to abstract the Asp.Net Url helper. + /// + internal class WebApiUrlHelper : IWebApiUrlHelper + { + /// + /// The inner request wrapped by this instance. + /// + internal IUrlHelper innerHelper; + + /// + /// Initializes a new instance of the WebApiUrlHelper class. + /// + /// The inner helper. + public WebApiUrlHelper(IUrlHelper helper) + { + if (helper == null) + { + throw Error.ArgumentNull("helper"); + } + + this.innerHelper = helper; + } + + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The OData path segments. + /// The generated OData link. + public string CreateODataLink(params ODataPathSegment[] segments) + { + return this.CreateODataLink(segments as IList); + } + + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The OData path segments. + /// The generated OData link. + public string CreateODataLink(IList segments) + { + string routeName = this.innerHelper.ActionContext.HttpContext.Request.ODataFeature().RouteName; + if (String.IsNullOrEmpty(routeName)) + { + throw Error.InvalidOperation(SRResources.RequestMustHaveODataRouteName); + } + + IODataPathHandler pathHandler = this.innerHelper.ActionContext.HttpContext.Request.GetPathHandler(); + return CreateODataLink(routeName, pathHandler, segments); + } + + /// + /// Generates an OData link using the given OData route name, path handler, and segments. + /// + /// The name of the OData route. + /// The path handler to use for generating the link. + /// The OData path segments. + /// The generated OData link. + public string CreateODataLink(string routeName, IODataPathHandler pathHandler, IList segments) + { + if (String.IsNullOrEmpty(routeName)) + { + throw Error.InvalidOperation(SRResources.RequestMustHaveODataRouteName); + } + + if (pathHandler == null) + { + throw Error.ArgumentNull("pathHandler"); + } + + string odataPath = pathHandler.Link(new ODataPath(segments)); + + return this.innerHelper.Link( + routeName, + new RouteValueDictionary() { { ODataRouteConstants.ODataPath, odataPath } }); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetRequestItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetRequestItem.cs new file mode 100644 index 0000000..e8becc8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetRequestItem.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents a ChangeSet request. + /// + public class ChangeSetRequestItem : ODataBatchRequestItem + { + /// + /// Initializes a new instance of the class. + /// + /// The request contexts in the ChangeSet. + public ChangeSetRequestItem(IEnumerable contexts) + { + if (contexts == null) + { + throw Error.ArgumentNull("contexts"); + } + + Contexts = contexts; + } + + /// + /// Gets the request contexts in the ChangeSet. + /// + public IEnumerable Contexts { get; private set; } + + /// + /// Sends the ChangeSet request. + /// + /// The handler for processing a message. + /// A . + public override async Task SendRequestAsync(RequestDelegate handler) + { + if (handler == null) + { + throw Error.ArgumentNull("handler"); + } + + Dictionary contentIdToLocationMapping = new Dictionary(); + List responseContexts = new List(); + + foreach (HttpContext context in Contexts) + { + await SendRequestAsync(handler, context, contentIdToLocationMapping); + + HttpResponse response = context.Response; + if (response.IsSuccessStatusCode()) + { + responseContexts.Add(context); + } + else + { + responseContexts.Clear(); + responseContexts.Add(context); + return new ChangeSetResponseItem(responseContexts); + } + } + + return new ChangeSetResponseItem(responseContexts); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetResponseItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetResponseItem.cs new file mode 100644 index 0000000..d9706ad --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ChangeSetResponseItem.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents a ChangeSet response. + /// + public class ChangeSetResponseItem : ODataBatchResponseItem + { + /// + /// Initializes a new instance of the class. + /// + /// The response contexts for the ChangeSet requests. + public ChangeSetResponseItem(IEnumerable contexts) + { + if (contexts == null) + { + throw Error.ArgumentNull("contexts"); + } + + Contexts = contexts; + } + + /// + /// Gets the response contexts for the ChangeSet. + /// + public IEnumerable Contexts { get; private set; } + + /// + /// Writes the responses as a ChangeSet. + /// + /// The . + public override async Task WriteResponseAsync(ODataBatchWriter writer) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + + writer.WriteStartChangeset(); + + foreach (HttpContext context in Contexts) + { + await WriteMessageAsync(writer, context); + } + + writer.WriteEndChangeset(); + } + + /// + /// Gets a value that indicates if the responses in this item are successful. + /// + internal override bool IsResponseSuccessful() + { + return Contexts.All(c => c.Response.IsSuccessStatusCode()); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/DefaultODataBatchHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/DefaultODataBatchHandler.cs new file mode 100644 index 0000000..2701bed --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/DefaultODataBatchHandler.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Default implementation of for handling OData batch request. + /// + /// + /// By default, it buffers the request content stream. + /// + public class DefaultODataBatchHandler : ODataBatchHandler + { + /// + public override async Task ProcessBatchAsync(HttpContext context, RequestDelegate nextHandler) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + if (nextHandler == null) + { + throw Error.ArgumentNull("nextHandler"); + } + + if (!await ValidateRequest(context.Request)) + { + return; + } + + IList subRequests = await ParseBatchRequestsAsync(context); + + ODataOptions options = context.RequestServices.GetRequiredService(); + bool enableContinueOnErrorHeader = (options != null) + ? options.EnableContinueOnErrorHeader + : false; + + SetContinueOnError(new WebApiRequestHeaders(context.Request.Headers), enableContinueOnErrorHeader); + + IList responses = await ExecuteRequestMessagesAsync(subRequests, nextHandler); + await CreateResponseMessageAsync(responses, context.Request); + } + + /// + /// Executes the OData batch requests. + /// + /// The collection of OData batch requests. + /// The handler for processing a message. + /// A collection of for the batch requests. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of response messages asynchronously.")] + public virtual async Task> ExecuteRequestMessagesAsync(IEnumerable requests, RequestDelegate handler) + { + if (requests == null) + { + throw Error.ArgumentNull("requests"); + } + if (handler == null) + { + throw Error.ArgumentNull("handler"); + } + + IList responses = new List(); + + foreach (ODataBatchRequestItem request in requests) + { + ODataBatchResponseItem responseItem = await request.SendRequestAsync(handler); + responses.Add(responseItem); + + if (responseItem != null && responseItem.IsResponseSuccessful() == false && ContinueOnError == false) + { + break; + } + } + + return responses; + } + + /// + /// Converts the incoming OData batch request into a collection of request messages. + /// + /// The context containing the batch request messages. + /// A collection of . + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")] + public virtual async Task> ParseBatchRequestsAsync(HttpContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + HttpRequest request = context.Request; + IServiceProvider requestContainer = request.CreateRequestContainer(ODataRouteName); + requestContainer.GetRequiredService().BaseUri = GetBaseUri(request); + + ODataMessageReader reader = request.GetODataMessageReader(requestContainer); + + CancellationToken cancellationToken = context.RequestAborted; + List requests = new List(); + ODataBatchReader batchReader = reader.CreateODataBatchReader(); + Guid batchId = Guid.NewGuid(); + while (batchReader.Read()) + { + if (batchReader.State == ODataBatchReaderState.ChangesetStart) + { + IList changeSetContexts = await batchReader.ReadChangeSetRequestAsync(context, batchId, cancellationToken); + foreach (HttpContext changeSetContext in changeSetContexts) + { + changeSetContext.Request.CopyBatchRequestProperties(request); + changeSetContext.Request.DeleteRequestContainer(false); + } + requests.Add(new ChangeSetRequestItem(changeSetContexts)); + } + else if (batchReader.State == ODataBatchReaderState.Operation) + { + HttpContext operationContext = await batchReader.ReadOperationRequestAsync(context, batchId, true, cancellationToken); + operationContext.Request.CopyBatchRequestProperties(request); + operationContext.Request.DeleteRequestContainer(false); + requests.Add(new OperationRequestItem(operationContext)); + } + } + + return requests; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/HttpRequestExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/HttpRequestExtensions.cs new file mode 100644 index 0000000..e27e882 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/HttpRequestExtensions.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class HttpRequestExtensions + { + /// + /// Gets the from the services container. + /// + /// The request. + /// The from the services container. + public static IODataBatchFeature ODataBatchFeature(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.HttpContext.ODataBatchFeature(); + } + + /// + /// Gets the for the stream. + /// + /// The request. + /// The dependency injection container for the request. + /// A task object that produces an when completed. + public static ODataMessageReader GetODataMessageReader(this HttpRequest request, IServiceProvider requestContainer) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + IODataRequestMessage oDataRequestMessage = ODataMessageWrapperHelper.Create(request.Body, request.Headers, requestContainer); + ODataMessageReaderSettings settings = requestContainer.GetRequiredService(); + ODataMessageReader oDataMessageReader = new ODataMessageReader(oDataRequestMessage, settings); + return oDataMessageReader; + } + + /// + /// Copy an absolute Uri to a stream. + /// + /// The request. + /// The absolute uri to copy. + public static void CopyAbsoluteUrl(this HttpRequest request, Uri uri) + { + request.Scheme = uri.Scheme; + request.Host = uri.IsDefaultPort ? + new HostString(uri.Host) : + new HostString(uri.Host, uri.Port); + request.QueryString = new QueryString(uri.Query); + var path = new PathString(uri.AbsolutePath); + if (path.StartsWithSegments(request.PathBase, out PathString remainingPath)) + { + path = remainingPath; + } + request.Path = path; + } + + /// + /// Copies the properties from another . + /// + /// The sub-request. + /// The batch request that contains the properties to copy. + /// + /// Currently, this method is unused but is retained to keep a similar API surface area + /// between the AspNet and AspNetCore versions of OData WebApi. + /// + public static void CopyBatchRequestProperties(this HttpRequest subRequest, HttpRequest batchRequest) + { + if (subRequest == null) + { + throw new ArgumentNullException("subRequest"); + } + if (batchRequest == null) + { + throw new ArgumentNullException("batchRequest"); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchContent.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchContent.cs new file mode 100644 index 0000000..f3f8760 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchContent.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Encapsulates a collection of OData batch responses. + /// + /// + /// In AspNet, derives from . + /// + public partial class ODataBatchContent + { + /// + /// Initializes a new instance of the class. + /// + /// The batch responses. + /// The dependency injection container for the request. + /// This signature uses types that are AspNetCore-specific. + public ODataBatchContent(IEnumerable responses, IServiceProvider requestContainer) + : this(responses, requestContainer, null/*contentType*/) + { + } + /// + /// Initializes a new instance of the class. + /// + /// The batch responses. + /// The dependency injection container for the request. + /// The response content type. + /// This signature uses types that are AspNetCore-specific. + public ODataBatchContent(IEnumerable responses, IServiceProvider requestContainer, + string contentType) + { + Initialize(responses, requestContainer); + + // Set the Content-Type header for existing batch formats + if (contentType == null) + { + contentType = String.Format( + CultureInfo.InvariantCulture, "multipart/mixed;boundary=batchresponse_{0}", Guid.NewGuid()); + } + + Headers[HeaderNames.ContentType] = contentType; + ODataVersion version = _writerSettings.Version ?? ODataVersionConstraint.DefaultODataVersion; + Headers.Add(ODataVersionConstraint.ODataServiceVersionHeader, ODataUtils.ODataVersionToString(version)); + } + + /// + /// Gets the Headers for the batch content. + /// + public HeaderDictionary Headers { get; } = new HeaderDictionary(); + + /// + /// Serialize the batch content to a stream. + /// + /// The stream to serialize to. + /// A that can be awaited. + /// This function uses types that are AspNetCore-specific. + public Task SerializeToStreamAsync(Stream stream) + { + IODataResponseMessage responseMessage = ODataMessageWrapperHelper.Create(stream, Headers, _requestContainer); + return WriteToResponseMessageAsync(responseMessage); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHandler.cs new file mode 100644 index 0000000..f02dd6b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHandler.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Defines the abstraction for handling OData batch requests. + /// + /// + /// This class implements a BatchHandler semantics for AspNetCore, which uses + /// an for dispatching requests. + /// + public abstract partial class ODataBatchHandler + { + /// + /// Gets or sets the OData route associated with this batch handler. + /// + public ODataRoute ODataRoute { get; set; } + + /// + /// Abstract method for processing a batch request. + /// + /// The http content. + /// >The next handler in the middleware chain. + /// + public abstract Task ProcessBatchAsync(HttpContext context, RequestDelegate nextHandler); + + /// + /// Creates the batch response message. + /// + /// The responses for the batch requests. + /// The original request containing all the batch requests. + /// The batch response message. + public virtual Task CreateResponseMessageAsync(IEnumerable responses, HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.CreateODataBatchResponseAsync(responses, MessageQuotas); + } + + /// + /// Validates the incoming request that contains the batch request messages. + /// + /// The request containing the batch request messages. + /// true if the request is valid, otherwise false. + public virtual Task ValidateRequest(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.ValidateODataBatchRequest(); + } + + /// + /// Gets the base URI for the batched requests. + /// + /// The original request containing all the batch requests. + /// The base URI. + public virtual Uri GetBaseUri(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetODataBatchBaseUri(ODataRouteName, ODataRoute); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs new file mode 100644 index 0000000..9f30d60 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchHttpRequestMessageExtensions.cs @@ -0,0 +1,310 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ODataBatchHttpRequestExtensions + { + private const string BatchIdKey = "BatchId"; + private const string ChangeSetIdKey = "ChangesetId"; + private const string ContentIdKey = "ContentId"; + private const string ContentIdMappingKey = "ContentIdMapping"; + private const string BatchMediaTypeMime = "multipart/mixed"; + private const string BatchMediaTypeJson = "application/json"; + private const string Boundary = "boundary"; + + /// + /// Determine if the request is a batch request. + /// + /// + /// + public static bool IsODataBatchRequest(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return (request.ContentType != null && + (request.ContentType.StartsWith(BatchMediaTypeMime) || request.ContentType.StartsWith(BatchMediaTypeJson))); + } + + /// + /// Retrieves the Batch ID associated with the request. + /// + /// The request. + /// The Batch ID associated with this request, or null if there isn't one. + public static Guid? GetODataBatchId(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.ODataBatchFeature().BatchId; + } + + /// + /// Associates a given Batch ID with the request. + /// + /// The request. + /// The Batch ID. + public static void SetODataBatchId(this HttpRequest request, Guid batchId) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.ODataBatchFeature().BatchId = batchId; + } + + /// + /// Retrieves the ChangeSet ID associated with the request. + /// + /// The request. + /// The ChangeSet ID associated with this request, or null if there isn't one. + public static Guid? GetODataChangeSetId(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.ODataBatchFeature().ChangeSetId; + } + + /// + /// Associates a given ChangeSet ID with the request. + /// + /// The request. + /// The ChangeSet ID. + public static void SetODataChangeSetId(this HttpRequest request, Guid changeSetId) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.ODataBatchFeature().ChangeSetId = changeSetId; + } + + /// + /// Retrieves the Content-ID associated with the sub-request of a batch. + /// + /// The request. + /// The Content-ID associated with this request, or null if there isn't one. + public static string GetODataContentId(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.ODataBatchFeature().ContentId; + } + + /// + /// Associates a given Content-ID with the sub-request of a batch. + /// + /// The request. + /// The Content-ID. + public static void SetODataContentId(this HttpRequest request, string contentId) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.ODataBatchFeature().ContentId = contentId; + } + + /// + /// Retrieves the Content-ID to Location mapping associated with the request. + /// + /// The request. + /// The Content-ID to Location mapping associated with this request, or null if there isn't one. + public static IDictionary GetODataContentIdMapping(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.ODataBatchFeature().ContentIdMapping; + } + + /// + /// Associates a given Content-ID to Location mapping with the request. + /// + /// The request. + /// The Content-ID to Location mapping. + public static void SetODataContentIdMapping(this HttpRequest request, IDictionary contentIdMapping) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + request.ODataBatchFeature().ContentIdMapping = contentIdMapping; + } + + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")] + internal static Task CreateODataBatchResponseAsync(this HttpRequest request, IEnumerable responses, ODataMessageQuotas messageQuotas) + { + Contract.Assert(request != null); + + ODataVersion odataVersion = ODataInputFormatter.GetODataResponseVersion(request); + + IServiceProvider requestContainer = request.GetRequestContainer(); + ODataMessageWriterSettings writerSettings = requestContainer.GetRequiredService(); + writerSettings.Version = odataVersion; + writerSettings.MessageQuotas = messageQuotas; + + HttpResponse response = request.HttpContext.Response; + + StringValues acceptHeader = request.Headers["Accept"]; + string contentType = null; + if (StringValues.IsNullOrEmpty(acceptHeader) + || acceptHeader.Any( h => h.Equals(ODataBatchHttpRequestExtensions.BatchMediaTypeMime, StringComparison.OrdinalIgnoreCase))) + { + contentType = String.Format(CultureInfo.InvariantCulture, "multipart/mixed;boundary=batchresponse_{0}", Guid.NewGuid()); + } + else if (acceptHeader.Any( h => h.Equals(ODataBatchHttpRequestExtensions.BatchMediaTypeJson, StringComparison.OrdinalIgnoreCase))) + { + contentType = ODataBatchHttpRequestExtensions.BatchMediaTypeJson; + } + + response.StatusCode = (int)HttpStatusCode.OK; + ODataBatchContent batchContent = new ODataBatchContent(responses, requestContainer, contentType); + foreach (var header in batchContent.Headers) + { + // Copy headers from batch content, overwriting any existing headers. + response.Headers[header.Key] = header.Value; + } + + return batchContent.SerializeToStreamAsync(response.Body); + } + + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the object.")] + internal static async Task ValidateODataBatchRequest(this HttpRequest request) + { + Contract.Assert(request != null); + + HttpResponse response = request.HttpContext.Response; + + if (request.Body == null) + { + response.StatusCode = (int)HttpStatusCode.BadRequest; + await response.WriteAsync(SRResources.BatchRequestMissingContent); + return false; + } + + RequestHeaders headers = request.GetTypedHeaders(); + MediaTypeHeaderValue contentType = headers.ContentType; + + if (contentType == null) + { + response.StatusCode = (int)HttpStatusCode.BadRequest; + await response.WriteAsync(SRResources.BatchRequestMissingContentType); + return false; + } + + string mediaType = contentType.MediaType.ToString(); + bool isMimeBatch = String.Equals(mediaType, BatchMediaTypeMime, StringComparison.OrdinalIgnoreCase); + bool isJsonBatch = String.Equals(mediaType, BatchMediaTypeJson, StringComparison.OrdinalIgnoreCase); + + if (!isMimeBatch && !isJsonBatch) + { + response.StatusCode = (int)HttpStatusCode.BadRequest; + await response.WriteAsync(Error.Format(SRResources.BatchRequestInvalidMediaType, + $"{BatchMediaTypeMime} or {BatchMediaTypeJson}")); + return false; + } + + if (isMimeBatch) + { + NameValueHeaderValue boundary = contentType.Parameters.FirstOrDefault(p => String.Equals(p.Name.ToString(), Boundary, StringComparison.OrdinalIgnoreCase)); + if (boundary == null || String.IsNullOrEmpty(boundary.Value.ToString())) + { + response.StatusCode = (int)HttpStatusCode.BadRequest; + await response.WriteAsync(SRResources.BatchRequestMissingBoundary); + return false; + } + } + + return true; + } + + internal static Uri GetODataBatchBaseUri(this HttpRequest request, string oDataRouteName, IRouter route) + { + Contract.Assert(request != null); + + if (oDataRouteName == null) + { + // Return request's base address. + return new Uri(request.GetDisplayUrl()); + } + + // The IActionContextAccessor and ActionContext will be present after routing but not before + // GetUrlHelper only uses the HttpContext and the Router, which we have so construct a dummy + // action context. + ActionContext actionContext = new ActionContext + { + HttpContext = request.HttpContext, + RouteData = new RouteData(), + ActionDescriptor = new ActionDescriptor() + }; + + actionContext.RouteData.Routers.Add(route); + IUrlHelperFactory factory = request.HttpContext.RequestServices.GetRequiredService(); + IUrlHelper helper = factory.GetUrlHelper(actionContext); + + RouteValueDictionary routeData = new RouteValueDictionary() { { ODataRouteConstants.ODataPath, String.Empty } }; + RouteValueDictionary batchRouteData = request.ODataFeature().BatchRouteData; + if (batchRouteData != null && batchRouteData.Any()) + { + foreach (var data in batchRouteData) + { + routeData.Add(data.Key, data.Value); + } + } + + string baseAddress = helper.Link(oDataRouteName, routeData); + if (baseAddress == null) + { + throw new InvalidOperationException(SRResources.UnableToDetermineBaseUrl); + } + return new Uri(baseAddress); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchMiddleware.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchMiddleware.cs new file mode 100644 index 0000000..1a984eb --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchMiddleware.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Defines the middleware for handling OData batch requests. This middleware essentially + /// acts like branching middleware, , and redirects OData batch + /// requests to the appropriate ODataBatchHandler. + /// + public class ODataBatchMiddleware + { + private readonly RequestDelegate next; + + /// + /// Instantiates a new instance of . + /// + public ODataBatchMiddleware(RequestDelegate next) + { + this.next = next; + } + + /// + /// Invoke the middleware. + /// + /// The http context. + /// A task that can be awaited. + public async Task Invoke(HttpContext context) + { + // Attempt to match the path to a bach route. + ODataBatchPathMapping batchMapping = context.RequestServices.GetRequiredService(); + + string routeName; + if (batchMapping.TryGetRouteName(context, out routeName)) + { + // Get the per-route continer and retrieve the batch handler. + IPerRouteContainer perRouteContainer = context.RequestServices.GetRequiredService(); + if (perRouteContainer == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(IPerRouteContainer)); + } + + IServiceProvider rootContainer = perRouteContainer.GetODataRootContainer(routeName); + ODataBatchHandler batchHandler = rootContainer.GetRequiredService(); + + await batchHandler.ProcessBatchAsync(context, next); + } + else + { + await this.next(context); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchPathMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchPathMapping.cs new file mode 100644 index 0000000..36d4967 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchPathMapping.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Template; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// A class for storing batch route names and prefixes used to determine if a route is a + /// batch route. + /// + public class ODataBatchPathMapping + { + private Dictionary templateMappings = new Dictionary(); + + /// + /// Add a route name and template for batching. + /// + /// The route name. + /// The route template. + public void AddRoute(string routeName, string routeTemplate) + { + string newRouteTemplate = routeTemplate.StartsWith("/") ? routeTemplate.Substring(1) : routeTemplate; + RouteTemplate parsedTemplate = TemplateParser.Parse(newRouteTemplate); + TemplateMatcher matcher = new TemplateMatcher(parsedTemplate, new RouteValueDictionary()); + templateMappings[matcher] = routeName; + } + + /// + /// Try and get the batch handler for a given path. + /// + /// The http context. + /// The route name if found or null. + /// true if a route name is found, otherwise false. + public bool TryGetRouteName(HttpContext context, out string routeName) + { + if (context == null) + { + throw Error.ArgumentNull(nameof(context)); + } + + routeName = null; + string path = context.Request.Path; + foreach (var item in templateMappings) + { + RouteValueDictionary routeData = new RouteValueDictionary(); + if (item.Key.TryMatch(path, routeData)) + { + routeName = item.Value; + if (routeData.Count > 0) + { + context.ODataFeature().BatchRouteData = routeData; + } + + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchReaderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchReaderExtensions.cs new file mode 100644 index 0000000..7dfdcce --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchReaderExtensions.cs @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ODataBatchReaderExtensions + { + /// + /// Reads a ChangeSet request. + /// + /// The . + /// The context containing the batch request messages. + /// The Batch Id. + /// The token to monitor for cancellation requests. + /// A collection of in the ChangeSet. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "We need to return a collection of request messages asynchronously.")] + public static async Task> ReadChangeSetRequestAsync( + this ODataBatchReader reader, HttpContext context, Guid batchId, CancellationToken cancellationToken) + { + if (reader == null) + { + throw Error.ArgumentNull("reader"); + } + if (reader.State != ODataBatchReaderState.ChangesetStart) + { + throw Error.InvalidOperation( + SRResources.InvalidBatchReaderState, + reader.State.ToString(), + ODataBatchReaderState.ChangesetStart.ToString()); + } + + Guid changeSetId = Guid.NewGuid(); + List contexts = new List(); + while (reader.Read() && reader.State != ODataBatchReaderState.ChangesetEnd) + { + if (reader.State == ODataBatchReaderState.Operation) + { + contexts.Add(await ReadOperationInternalAsync(reader, context, batchId, changeSetId, cancellationToken)); + } + } + return contexts; + } + + /// + /// Reads an Operation request. + /// + /// The . + /// The context containing the batch request messages. + /// The Batch ID. + /// if set to true then the request content stream will be buffered. + /// The token to monitor for cancellation requests. + /// A representing the operation. + public static Task ReadOperationRequestAsync( + this ODataBatchReader reader, HttpContext context, Guid batchId, bool bufferContentStream, CancellationToken cancellationToken) + { + if (reader == null) + { + throw Error.ArgumentNull("reader"); + } + if (reader.State != ODataBatchReaderState.Operation) + { + throw Error.InvalidOperation( + SRResources.InvalidBatchReaderState, + reader.State.ToString(), + ODataBatchReaderState.Operation.ToString()); + } + + return ReadOperationInternalAsync(reader, context, batchId, null, cancellationToken, bufferContentStream); + } + + /// + /// Reads an Operation request in a ChangeSet. + /// + /// The . + /// The context containing the batch request messages. + /// The Batch ID. + /// The ChangeSet ID. + /// if set to true then the request content stream will be buffered. + /// The token to monitor for cancellation requests. + /// A representing a ChangeSet operation + public static Task ReadChangeSetOperationRequestAsync( + this ODataBatchReader reader, HttpContext context, Guid batchId, Guid changeSetId, bool bufferContentStream, CancellationToken cancellationToken) + { + if (reader == null) + { + throw Error.ArgumentNull("reader"); + } + if (reader.State != ODataBatchReaderState.Operation) + { + throw Error.InvalidOperation( + SRResources.InvalidBatchReaderState, + reader.State.ToString(), + ODataBatchReaderState.Operation.ToString()); + } + + return ReadOperationInternalAsync(reader, context, batchId, changeSetId, cancellationToken, bufferContentStream); + } + + private static async Task ReadOperationInternalAsync( + ODataBatchReader reader, HttpContext originalContext, Guid batchId, Guid? changeSetId, CancellationToken cancellationToken, bool bufferContentStream = true) + { + ODataBatchOperationRequestMessage batchRequest = reader.CreateOperationRequestMessage(); + + HttpContext context = CreateHttpContext(originalContext); + HttpRequest request = context.Request; + + request.Method = batchRequest.Method; + request.CopyAbsoluteUrl(batchRequest.Url); + + // Not using bufferContentStream. Unlike AspNet, AspNetCore cannot guarantee the disposal + // of the stream in the context of execution so there is no choice but to copy the stream + // from the batch reader. + using (Stream stream = batchRequest.GetStream()) + { + MemoryStream bufferedStream = new MemoryStream(); + // Passing in the default buffer size of 81920 so that we can also pass in a cancellation token + await stream.CopyToAsync(bufferedStream, bufferSize: 81920, cancellationToken: cancellationToken); + bufferedStream.Position = 0; + request.Body = bufferedStream; + } + + foreach (var header in batchRequest.Headers) + { + // Copy headers from batch, overwriting any existing headers. + string headerName = header.Key; + string headerValue = header.Value; + request.Headers[headerName] = headerValue; + } + + request.SetODataBatchId(batchId); + request.SetODataContentId(batchRequest.ContentId); + + if (changeSetId != null && changeSetId.HasValue) + { + request.SetODataChangeSetId(changeSetId.Value); + } + + return context; + } + + private static HttpContext CreateHttpContext(HttpContext originalContext) + { + // Clone the features so that a new set is used for each context. + // The features themselves will be reused but not the collection. We + // store the request container as a feature of the request and we don't want + // the features added to one context/request to be visible on another. + // + // Note that just about everything inm the HttpContext and HttpRequest is + // backed by one of these features. So reusing the features means the HttContext + // and HttpRequests are the same without needing to copy properties. To make them + // different, we need to avoid copying certain features to that the objects don't + // share the same storage/ + IFeatureCollection features = new FeatureCollection(); + string pathBase = ""; + foreach (KeyValuePair kvp in originalContext.Features) + { + // Don't include the OData features. They may already + // be present. This will get re-created later. + // + // Also, clear out the items feature, which is used + // to store a few object, the one that is an issue here is the Url + // helper, which has an affinity to the context. If we leave it, + // the context of the helper no longer matches the new context and + // the resulting url helper doesn't have access to the OData feature + // because it's looking in the wrong context. + // + // Because we need a different request and response, leave those features + // out as well. + if (kvp.Key == typeof(IHttpRequestFeature)) + { + pathBase = ((IHttpRequestFeature)kvp.Value).PathBase; + } + + if (kvp.Key == typeof(IODataBatchFeature) || + kvp.Key == typeof(IODataFeature) || + kvp.Key == typeof(IItemsFeature) || + kvp.Key == typeof(IHttpRequestFeature) || + kvp.Key == typeof(IHttpResponseFeature)) + { + continue; + } + + features[kvp.Key] = kvp.Value; + } + + // Add in an items, request and response feature. + features[typeof(IItemsFeature)] = new ItemsFeature(); + features[typeof(IHttpRequestFeature)] = new HttpRequestFeature + { + PathBase = pathBase + }; + features[typeof(IHttpResponseFeature)] = new HttpResponseFeature(); + + // Create a context from the factory or use the default context. + HttpContext context = null; + IHttpContextFactory httpContextFactory = originalContext.RequestServices.GetRequiredService(); + if (httpContextFactory != null) + { + context = httpContextFactory.Create(features); + } + else + { + context = new DefaultHttpContext(features); + } + + // Clone parts of the request. All other parts of the request will be + // populated during batch processing. + context.Request.Cookies = originalContext.Request.Cookies; + foreach (KeyValuePair header in originalContext.Request.Headers) + { + context.Request.Headers.Add(header); + } + + // Create a response body as the default response feature does not + // have a valid stream. + context.Response.Body = new MemoryStream(); + + return context; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchRequestItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchRequestItem.cs new file mode 100644 index 0000000..8d7c64c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchRequestItem.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an OData batch request. + /// + public abstract class ODataBatchRequestItem + { + /// + /// Routes a single OData batch request. + /// + /// The handler for processing a message. + /// The context. + /// The Content-ID to Location mapping. + /// + public static async Task SendRequestAsync(RequestDelegate handler, HttpContext context, Dictionary contentIdToLocationMapping) + { + if (handler == null) + { + throw Error.ArgumentNull("handler"); + } + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (contentIdToLocationMapping != null) + { + string queryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : String.Empty; + string resolvedRequestUrl = ContentIdHelpers.ResolveContentId(queryString, contentIdToLocationMapping); + if (!string.IsNullOrEmpty(resolvedRequestUrl)) + { + Uri resolvedUri = new Uri(resolvedRequestUrl, UriKind.RelativeOrAbsolute); + if (resolvedUri.IsAbsoluteUri) + { + context.Request.CopyAbsoluteUrl(resolvedUri); + } + else + { + context.Request.QueryString = new QueryString(resolvedRequestUrl); + } + } + + context.Request.SetODataContentIdMapping(contentIdToLocationMapping); + } + + try + { + await handler(context); + + string contentId = context.Request.GetODataContentId(); + + if (contentIdToLocationMapping != null && contentId != null) + { + AddLocationHeaderToMapping(context.Response, contentIdToLocationMapping, contentId); + } + } + catch (Exception) + { + // Unlike AspNet, the exception handling is (by default) upstream of this middleware + // so we need to trap exceptions on our own. This code is similar to the + // ExceptionHandlerMiddleware class in AspNetCore. + context.Response.Clear(); + context.Response.StatusCode = 500; + } + } + + private static void AddLocationHeaderToMapping( + HttpResponse response, + IDictionary contentIdToLocationMapping, + string contentId) + { + Contract.Assert(response != null); + Contract.Assert(response.Headers != null); + Contract.Assert(contentIdToLocationMapping != null); + Contract.Assert(contentId != null); + + var headers = response.GetTypedHeaders(); + if (headers.Location != null) + { + contentIdToLocationMapping.Add(contentId, headers.Location.AbsoluteUri); + } + } + + /// + /// Routes the request. + /// + /// The handler for processing a message. + /// A . + public abstract Task SendRequestAsync(RequestDelegate handler); + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchResponseItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchResponseItem.cs new file mode 100644 index 0000000..34e4726 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/ODataBatchResponseItem.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an OData batch response. + /// + public abstract class ODataBatchResponseItem + { + /// + /// Writes a single OData batch response. + /// + /// The . + /// The message context. + public static async Task WriteMessageAsync(ODataBatchWriter writer, HttpContext context) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + string contentId = (context.Request != null) ? context.Request.GetODataContentId() : String.Empty; + + ODataBatchOperationResponseMessage batchResponse = writer.CreateOperationResponseMessage(contentId); + + batchResponse.StatusCode = context.Response.StatusCode; + + foreach (KeyValuePair header in context.Response.Headers) + { + batchResponse.SetHeader(header.Key, String.Join(",", header.Value.ToArray())); + } + + if (context.Response.Body != null && context.Response.Body.Length != 0) + { + using (Stream stream = batchResponse.GetStream()) + { + context.RequestAborted.ThrowIfCancellationRequested(); + context.Response.Body.Seek(0L, SeekOrigin.Begin); + await context.Response.Body.CopyToAsync(stream); + } + } + } + + /// + /// Writes the response. + /// + /// The . + public abstract Task WriteResponseAsync(ODataBatchWriter writer); + + /// + /// Gets a value that indicates if the responses in this item are successful. + /// + internal virtual bool IsResponseSuccessful() + { + return false; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationRequestItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationRequestItem.cs new file mode 100644 index 0000000..c5cc126 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationRequestItem.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an Operation request. + /// + public class OperationRequestItem : ODataBatchRequestItem + { + /// + /// Initializes a new instance of the class. + /// + /// The Operation request context. + public OperationRequestItem(HttpContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + Context = context; + } + + /// + /// Gets the Operation request context. + /// + public HttpContext Context { get; private set; } + + /// + /// Sends the Operation request. + /// + /// The handler for processing a message. + /// A . + public override async Task SendRequestAsync(RequestDelegate handler) + { + if (handler == null) + { + throw Error.ArgumentNull("handler"); + } + + await SendRequestAsync(handler, Context, null); + return new OperationResponseItem(Context); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationResponseItem.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationResponseItem.cs new file mode 100644 index 0000000..2138493 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/OperationResponseItem.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// Represents an Operation response. + /// + public class OperationResponseItem : ODataBatchResponseItem + { + /// + /// Initializes a new instance of the class. + /// + /// The response context for the Operation request. + public OperationResponseItem(HttpContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + Context = context; + } + + /// + /// Gets the response messages for the Operation. + /// + public HttpContext Context { get; private set; } + + /// + /// Writes the response as an Operation. + /// + /// The . + public override Task WriteResponseAsync(ODataBatchWriter writer) + { + if (writer == null) + { + throw Error.ArgumentNull("writer"); + } + + return WriteMessageAsync(writer, Context); + } + + /// + /// Gets a value that indicates if the responses in this item are successful. + /// + internal override bool IsResponseSuccessful() + { + return Context.Response.IsSuccessStatusCode(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/UnbufferedODataBatchHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/UnbufferedODataBatchHandler.cs new file mode 100644 index 0000000..3c8e6a9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Batch/UnbufferedODataBatchHandler.cs @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Batch +{ + /// + /// An implementation of that doesn't buffer the request content stream. + /// + public class UnbufferedODataBatchHandler : ODataBatchHandler + { + /// + public override async Task ProcessBatchAsync(HttpContext context, RequestDelegate nextHandler) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + if (nextHandler == null) + { + throw Error.ArgumentNull("nextHandler"); + } + + if (!await ValidateRequest(context.Request)) + { + return; + } + + // This container is for the overall batch request. + HttpRequest request = context.Request; + IServiceProvider requestContainer = request.CreateRequestContainer(ODataRouteName); + requestContainer.GetRequiredService().BaseUri = GetBaseUri(request); + + ODataMessageReader reader = request.GetODataMessageReader(requestContainer); + + ODataBatchReader batchReader = reader.CreateODataBatchReader(); + List responses = new List(); + Guid batchId = Guid.NewGuid(); + + ODataOptions options = context.RequestServices.GetRequiredService(); + bool enableContinueOnErrorHeader = (options != null) + ? options.EnableContinueOnErrorHeader + : false; + + SetContinueOnError(new WebApiRequestHeaders(request.Headers), enableContinueOnErrorHeader); + + while (batchReader.Read()) + { + ODataBatchResponseItem responseItem = null; + if (batchReader.State == ODataBatchReaderState.ChangesetStart) + { + responseItem = await ExecuteChangeSetAsync(batchReader, batchId, request, nextHandler); + } + else if (batchReader.State == ODataBatchReaderState.Operation) + { + responseItem = await ExecuteOperationAsync(batchReader, batchId, request, nextHandler); + } + if (responseItem != null) + { + responses.Add(responseItem); + if (responseItem.IsResponseSuccessful() == false && ContinueOnError == false) + { + break; + } + } + } + + await CreateResponseMessageAsync(responses, request); + } + + /// + /// Executes the operation. + /// + /// The batch reader. + /// The batch id. + /// The original request containing all the batch requests. + /// The handler for processing a message. + /// The response for the operation. + public virtual async Task ExecuteOperationAsync(ODataBatchReader batchReader, Guid batchId, HttpRequest originalRequest, RequestDelegate handler) + { + if (batchReader == null) + { + throw Error.ArgumentNull("batchReader"); + } + if (originalRequest == null) + { + throw Error.ArgumentNull("originalRequest"); + } + if (handler == null) + { + throw Error.ArgumentNull("handler"); + } + + CancellationToken cancellationToken = originalRequest.HttpContext.RequestAborted; + cancellationToken.ThrowIfCancellationRequested(); + HttpContext operationContext = await batchReader.ReadOperationRequestAsync(originalRequest.HttpContext, batchId, false, cancellationToken); + + operationContext.Request.CopyBatchRequestProperties(originalRequest); + operationContext.Request.DeleteRequestContainer(false); + OperationRequestItem operation = new OperationRequestItem(operationContext); + + ODataBatchResponseItem responseItem = await operation.SendRequestAsync(handler); + + return responseItem; + } + + /// + /// Executes the ChangeSet. + /// + /// The batch reader. + /// The batch id. + /// The original request containing all the batch requests. + /// The handler for processing a message. + /// The response for the ChangeSet. + public virtual async Task ExecuteChangeSetAsync(ODataBatchReader batchReader, Guid batchId, HttpRequest originalRequest, RequestDelegate handler) + { + if (batchReader == null) + { + throw Error.ArgumentNull("batchReader"); + } + if (originalRequest == null) + { + throw Error.ArgumentNull("originalRequest"); + } + if (handler == null) + { + throw Error.ArgumentNull("handler"); + } + + Guid changeSetId = Guid.NewGuid(); + List changeSetResponse = new List(); + Dictionary contentIdToLocationMapping = new Dictionary(); + while (batchReader.Read() && batchReader.State != ODataBatchReaderState.ChangesetEnd) + { + if (batchReader.State == ODataBatchReaderState.Operation) + { + CancellationToken cancellationToken = originalRequest.HttpContext.RequestAborted; + HttpContext changeSetOperationContext = await batchReader.ReadChangeSetOperationRequestAsync(originalRequest.HttpContext, batchId, changeSetId, false, cancellationToken); + changeSetOperationContext.Request.CopyBatchRequestProperties(originalRequest); + changeSetOperationContext.Request.DeleteRequestContainer(false); + + await ODataBatchRequestItem.SendRequestAsync(handler, changeSetOperationContext, contentIdToLocationMapping); + if (changeSetOperationContext.Response.IsSuccessStatusCode()) + { + changeSetResponse.Add(changeSetOperationContext); + } + else + { + changeSetResponse.Clear(); + changeSetResponse.Add(changeSetOperationContext); + return new ChangeSetResponseItem(changeSetResponse); + } + } + } + + return new ChangeSetResponseItem(changeSetResponse); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Builder/ODataConventionModelBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Builder/ODataConventionModelBuilder.cs new file mode 100644 index 0000000..1cb243c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Builder/ODataConventionModelBuilder.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// is used to automatically map CLR classes to an EDM model based on a set of. + /// + public partial class ODataConventionModelBuilder + { + /// + /// Initializes a new instance of the class. + /// This constructor will work stand-alone scenarios and require using the + /// to get a list of assemblies in the domain. + /// + public ODataConventionModelBuilder() + : this(WebApiAssembliesResolver.Default) + { + } + + /// + /// Initializes a new instance of the class. + /// This constructor uses the from AspNetCore obtained + /// from the to get a list of assemblies for modeling. + /// + /// The service provider to use. + public ODataConventionModelBuilder(IServiceProvider provider) + : this(provider, null, isQueryCompositionMode: false) + { + } + + /// + /// Initializes a new instance of the class. + /// This constructor uses the from AspNetCore + /// to get a list of assemblies for modeling. + /// + /// The application part manager to use. + /// + /// This function uses types that are AspNetCore-specific. + /// + public ODataConventionModelBuilder(ApplicationPartManager applicationPartManager) + : this(null, applicationPartManager, isQueryCompositionMode: false) + { + } + + /// + /// Initializes a new instance of the class. + /// This constructor uses the from AspNetCore obtained + /// from the to get a list of assemblies for modeling. + /// The model built if is true has more relaxed + /// inference rules and also treats all types as entity types. This constructor is intended + /// for use by unit testing only. + /// + /// The service provider to use. + /// If the model is being built for only querying. + public ODataConventionModelBuilder(IServiceProvider provider, bool isQueryCompositionMode) + : this(provider, null, isQueryCompositionMode) + { + } + + /// + /// Initializes a new instance of the class. + /// The model built if is true has more relaxed + /// inference rules and also treats all types as entity types. + /// + /// The service provider to use. + /// + /// The application part manager to use. If null, the service + /// provider will be queried for the application part manager. + /// + /// If the model is being built for only querying. + private ODataConventionModelBuilder( + IServiceProvider provider, + ApplicationPartManager applicationPartManager, + bool isQueryCompositionMode) + { + + // Create an IWebApiAssembliesResolver from configuration and initialize. + if (applicationPartManager == null) + { + if (provider == null) + { + throw Error.ArgumentNull("provider"); + } + + applicationPartManager = provider.GetRequiredService(); + } + + IWebApiAssembliesResolver internalResolver = new WebApiAssembliesResolver(applicationPartManager); + Initialize(internalResolver, isQueryCompositionMode); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ETagMessageHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ETagMessageHandler.cs new file mode 100644 index 0000000..66cac21 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ETagMessageHandler.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNet.OData +{ + /// + /// Defines a to add an ETag header value to an OData response when the response + /// is a single resource that has an ETag defined. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public partial class ETagMessageHandler : ActionFilterAttribute + { + /// + public override void OnActionExecuted(ActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext == null) + { + throw Error.ArgumentNull("actionExecutedContext"); + } + + // Need a value to operate on. + ObjectResult result = actionExecutedContext.Result as ObjectResult; + if (result == null) + { + return; + } + + HttpResponse response = actionExecutedContext.HttpContext.Response; + HttpRequest request = actionExecutedContext.HttpContext.Request; + + EntityTagHeaderValue etag = GetETag( + response?.StatusCode, + request.ODataFeature().Path, + request.GetModel(), + result.Value, + request.GetETagHandler()); + + if (etag != null) + { + response.Headers["ETag"] = etag.ToString(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/EnableQueryAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/EnableQueryAttribute.cs new file mode 100644 index 0000000..ff5cb93 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/EnableQueryAttribute.cs @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Primitives; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// This class defines an attribute that can be applied to an action to enable querying using the OData query + /// syntax. To avoid processing unexpected or malicious queries, use the validation settings on + /// to validate incoming queries. For more information, visit + /// http://go.microsoft.com/fwlink/?LinkId=279712. + /// + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", + Justification = "We want to be able to subclass this type.")] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public partial class EnableQueryAttribute : ActionFilterAttribute + { + // Maintain the Microsoft.AspNet.OData. prefix in any new properties to avoid conflicts with user properties + // and those of the v3 assembly. Concern is reduced here due to addition of user type name but prefix + // also clearly ties the property to code in this assembly. + private const string ModelKeyPrefix = "Microsoft.AspNet.OData.Model+"; + + /// + /// Performs the query composition after action is executed. It first tries to retrieve the IQueryable from the + /// returning response message. It then validates the query from uri based on the validation settings on + /// . It finally applies the query appropriately, and reset it back on + /// the response message. + /// + /// The context related to this action, including the response message, + /// request message and HttpConfiguration etc. + public override void OnActionExecuted(ActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext == null) + { + throw Error.ArgumentNull("actionExecutedContext"); + } + + HttpRequest request = actionExecutedContext.HttpContext.Request; + if (request == null) + { + throw Error.Argument("actionExecutedContext", SRResources.ActionExecutedContextMustHaveRequest); + } + + ActionDescriptor actionDescriptor = actionExecutedContext.ActionDescriptor; + if (actionDescriptor == null) + { + throw Error.Argument("actionExecutedContext", SRResources.ActionContextMustHaveDescriptor); + } + + HttpResponse response = actionExecutedContext.HttpContext.Response; + + // Check is the response is set and successful. + if (response != null && IsSuccessStatusCode(response.StatusCode) && actionExecutedContext.Result != null) + { + // actionExecutedContext.Result might also indicate a status code that has not yet + // been applied to the result; make sure it's also successful. + StatusCodeResult statusCodeResult = actionExecutedContext.Result as StatusCodeResult; + if (statusCodeResult == null || IsSuccessStatusCode(statusCodeResult.StatusCode)) + { + ObjectResult responseContent = actionExecutedContext.Result as ObjectResult; + if (responseContent != null) + { + //throw Error.Argument("actionExecutedContext", SRResources.QueryingRequiresObjectContent, + // actionExecutedContext.Result.GetType().FullName); + + // Get collection from SingleResult. + IQueryable singleResultCollection = null; + SingleResult singleResult = responseContent.Value as SingleResult; + if (singleResult != null) + { + // This could be a SingleResult, which has the property Queryable. + // But it could be a SingleResult() or SingleResult. Sort by number of parameters + // on the property and get the one with the most parameters. + PropertyInfo propInfo = responseContent.Value.GetType().GetProperties() + .OrderBy(p => p.GetIndexParameters().Count()) + .Where(p => p.Name.Equals("Queryable")) + .LastOrDefault(); + + singleResultCollection = propInfo.GetValue(singleResult) as IQueryable; + } + + // Execution the action. + object queryResult = OnActionExecuted( + responseContent.Value, + singleResultCollection, + new WebApiActionDescriptor(actionDescriptor as ControllerActionDescriptor), + new WebApiRequestMessage(request), + (elementClrType) => GetModel(elementClrType, request, actionDescriptor), + (queryContext) => CreateAndValidateQueryOptions(request, queryContext), + (statusCode) => actionExecutedContext.Result = new StatusCodeResult((int)statusCode), + (statusCode, message, exception) => actionExecutedContext.Result = CreateBadRequestResult(message, exception)); + + if (queryResult != null) + { + responseContent.Value = queryResult; + } + } + } + } + } + + /// + /// Create and validate a new instance of from a query and context. + /// + /// The incoming request. + /// The query context. + /// + private ODataQueryOptions CreateAndValidateQueryOptions(HttpRequest request, ODataQueryContext queryContext) + { + ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, request); + ValidateQuery(request, queryOptions); + + return queryOptions; + } + + /// + /// Determine if the status code indicates success. + /// + /// The status code. + /// True if the response has a success status code; false otherwise. + private static bool IsSuccessStatusCode(int statusCode) + { + return statusCode >= 200 && statusCode < 300; + } + + /// + /// Create an error response. + /// + /// The message of the error. + /// The error exception if any. + /// A SerializableError. + /// This function is recursive. + public static SerializableError CreateErrorResponse(string message, Exception exception = null) + { + // The key values mimic the behavior of HttpError in AspNet. It's a fine format + // and many of the test cases expect it. + SerializableError error = new SerializableError(); + if (!String.IsNullOrEmpty(message)) + { + error.Add(SerializableErrorKeys.MessageKey, message); + } + + if (exception != null) + { + error.Add(SerializableErrorKeys.ExceptionMessageKey, exception.Message); + error.Add(SerializableErrorKeys.ExceptionTypeKey, exception.GetType().FullName); + error.Add(SerializableErrorKeys.StackTraceKey, exception.StackTrace); + if (exception.InnerException != null) + { + error.Add(SerializableErrorKeys.InnerExceptionKey, CreateErrorResponse(String.Empty, exception.InnerException)); + } + } + + return error; + } + + /// + /// Create a BadRequestObjectResult. + /// + /// The error message. + /// The exception. + /// A BadRequestObjectResult. + private static BadRequestObjectResult CreateBadRequestResult(string message, Exception exception) + { + SerializableError error = CreateErrorResponse(message, exception); + return new BadRequestObjectResult(error); + } + + /// + /// Validates the OData query in the incoming request. By default, the implementation throws an exception if + /// the query contains unsupported query parameters. Override this method to perform additional validation of + /// the query. + /// + /// The incoming request. + /// + /// The instance constructed based on the incoming request. + /// + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", + Justification = "Response disposed after being sent.")] + public virtual void ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (queryOptions == null) + { + throw Error.ArgumentNull("queryOptions"); + } + + IEnumerable> queryParameters = request.Query; + foreach (KeyValuePair kvp in queryParameters) + { + if (!queryOptions.IsSupportedQueryOption(kvp.Key) && + kvp.Key.StartsWith("$", StringComparison.Ordinal)) + { + // we don't support any custom query options that start with $ + // this should be caught be OnActionExecuted(). + throw new ArgumentOutOfRangeException(kvp.Key); + } + } + + queryOptions.Validate(_validationSettings); + } + /// + /// Gets the EDM model for the given type and request.Override this method to customize the EDM model used for + /// querying. + /// + /// The CLR type to retrieve a model for. + /// The request message to retrieve a model for. + /// The action descriptor for the action being queried on. + /// The EDM model for the given type and request. + public virtual IEdmModel GetModel( + Type elementClrType, + HttpRequest request, + ActionDescriptor actionDescriptor) + { + // Get model for the request + IEdmModel model = request.GetModel(); + + if (model == EdmCoreModel.Instance || model.GetEdmType(elementClrType) == null) + { + // user has not configured anything or has registered a model without the element type + // let's create one just for this type and cache it in the action descriptor + model = actionDescriptor.GetEdmModel(request, elementClrType); + } + + Contract.Assert(model != null); + return model; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ActionDescriptorExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ActionDescriptorExtensions.cs new file mode 100644 index 0000000..b89211f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ActionDescriptorExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Extensions +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class ActionDescriptorExtensions + { + // Maintain the Microsoft.AspNet.OData. prefix in any new properties to avoid conflicts with user properties + // and those of the v3 assembly. Concern is reduced here due to addition of user type name but prefix + // also clearly ties the property to code in this assembly. + private const string ModelKeyPrefix = "Microsoft.AspNet.OData.Model+"; + + private static readonly object SyncLock = new object(); + + internal static IEdmModel GetEdmModel(this ActionDescriptor actionDescriptor, HttpRequest request, Type entityClrType) + { + if (actionDescriptor == null) + { + throw Error.ArgumentNull("actionDescriptor"); + } + + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (entityClrType == null) + { + throw Error.ArgumentNull("entityClrType"); + } + + IEdmModel model = null; + + string key = ModelKeyPrefix + entityClrType.FullName; + object modelAsObject = null; + if (actionDescriptor.Properties.TryGetValue(key, out modelAsObject)) + { + model = modelAsObject as IEdmModel; + } + else + { + ODataConventionModelBuilder builder = + new ODataConventionModelBuilder(request.HttpContext.RequestServices, isQueryCompositionMode: true); + EntityTypeConfiguration entityTypeConfiguration = builder.AddEntityType(entityClrType); + builder.AddEntitySet(entityClrType.Name, entityTypeConfiguration); + model = builder.GetEdmModel(); + + lock (SyncLock) + { + if (!actionDescriptor.Properties.ContainsKey(key)) + { + actionDescriptor.Properties.Add(key, model); + } + } + } + + return model; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpContextExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpContextExtensions.cs new file mode 100644 index 0000000..573de81 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpContextExtensions.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the . + /// + public static class HttpContextExtensions + { + /// + /// Extension method to return the from the . + /// + /// The Http context. + /// The . + public static IODataFeature ODataFeature(this HttpContext httpContext) + { + if (httpContext == null) + { + throw Error.ArgumentNull("httpContext"); + } + + IODataFeature odataFeature = httpContext.Features.Get(); + if (odataFeature == null) + { + odataFeature = new ODataFeature(); + httpContext.Features.Set(odataFeature); + } + + return odataFeature; + } + + /// + /// Extension method to return the from the . + /// + /// The Http context. + /// The . + public static IODataBatchFeature ODataBatchFeature(this HttpContext httpContext) + { + if (httpContext == null) + { + throw Error.ArgumentNull("httpContext"); + } + + IODataBatchFeature odataBatchFeature = httpContext.Features.Get(); + if (odataBatchFeature == null) + { + odataBatchFeature = new ODataBatchFeature(); + httpContext.Features.Set(odataBatchFeature); + } + + return odataBatchFeature; + } + + /// + /// Extension method to return the from the . + /// + /// The Http context. + /// The . + public static IUrlHelper GetUrlHelper(this HttpContext httpContext) + { + if (httpContext == null) + { + throw Error.ArgumentNull("httpContext"); + } + + // Get an IUrlHelper from the global service provider. + ActionContext actionContext = httpContext.RequestServices.GetRequiredService().ActionContext; + return httpContext.RequestServices.GetRequiredService().GetUrlHelper(actionContext); + } + + /// + /// Extension method to return the from the . + /// + /// The Http context. + /// The . + public static IETagHandler GetETagHandler(this HttpContext httpContext) + { + if (httpContext == null) + { + throw Error.ArgumentNull("httpContext"); + } + + // Get an IETagHandler from the global service provider. + return httpContext.RequestServices.GetRequiredService(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs new file mode 100644 index 0000000..1e5a0d7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs @@ -0,0 +1,466 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using Microsoft.OData; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the . + /// + public static class HttpRequestExtensions + { + /// + /// Gets the from the services container. + /// + /// The request. + /// The from the services container. + public static IODataFeature ODataFeature(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.HttpContext.ODataFeature(); + } + + /// + /// Extension method to return the from the . + /// + /// The Http request. + /// The . + public static IUrlHelper GetUrlHelper(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + IODataFeature feature = request.ODataFeature(); + if (feature.UrlHelper == null) + { + // if not set, get it from global. + feature.UrlHelper = request.HttpContext.GetUrlHelper(); + } + + return feature.UrlHelper; + } + + /// + /// Gets the from the services container. + /// + /// The request. + /// The from the services container. + public static IETagHandler GetETagHandler(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static IODataPathHandler GetPathHandler(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static IEdmModel GetModel(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static ODataMessageReaderSettings GetReaderSettings(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + /// + /// Gets the from the request container. + /// + /// The request. + /// The from the request container. + public static ODataMessageWriterSettings GetWriterSettings(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetRequiredService(); + } + + internal static bool IsCountRequest(this HttpRequest request) + { + ODataPath path = request.ODataFeature().Path; + return path != null && path.Segments.LastOrDefault() is CountSegment; + } + + internal static bool IsRawValueRequest(this HttpRequest request) + { + ODataPath path = request.ODataFeature().Path; + return path != null && path.Segments.LastOrDefault() is ValueSegment; + } + + /// + /// Gets the set of from the request container. + /// + /// The request. + /// The set of from the request container. + public static IEnumerable GetRoutingConventions(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return request.GetRequestContainer().GetServices(); + } + + /// + /// Gets the OData from the given request. + /// + /// The request. + /// The entity tag header value. + /// The parsed . + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")] + public static ETag GetETag(this HttpRequest request, EntityTagHeaderValue entityTagHeaderValue) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (entityTagHeaderValue != null) + { + if (entityTagHeaderValue.Equals(EntityTagHeaderValue.Any)) + { + return new ETag { IsAny = true }; + } + + // get the etag handler, and parse the etag + IETagHandler etagHandler = request.GetRequestContainer().GetRequiredService(); + IDictionary properties = etagHandler.ParseETag(entityTagHeaderValue) ?? new Dictionary(); + IList parsedETagValues = properties.Select(property => property.Value).AsList(); + + // get property names from request + ODataPath odataPath = request.ODataFeature().Path; + IEdmModel model = request.GetModel(); + IEdmNavigationSource source = odataPath.NavigationSource; + if (model != null && source != null) + { + IList concurrencyProperties = model.GetConcurrencyProperties(source).ToList(); + IList concurrencyPropertyNames = concurrencyProperties.OrderBy(c => c.Name).Select(c => c.Name).AsList(); + ETag etag = new ETag(); + + if (parsedETagValues.Count != concurrencyPropertyNames.Count) + { + etag.IsWellFormed = false; + } + + IEnumerable> nameValues = concurrencyPropertyNames.Zip( + parsedETagValues, + (name, value) => new KeyValuePair(name, value)); + foreach (var nameValue in nameValues) + { + IEdmStructuralProperty property = concurrencyProperties.SingleOrDefault(e => e.Name == nameValue.Key); + Contract.Assert(property != null); + + Type clrType = EdmLibHelpers.GetClrType(property.Type, model); + Contract.Assert(clrType != null); + + if (nameValue.Value != null) + { + Type valueType = nameValue.Value.GetType(); + etag[nameValue.Key] = valueType != clrType + ? Convert.ChangeType(nameValue.Value, clrType, CultureInfo.InvariantCulture) + : nameValue.Value; + } + else + { + etag[nameValue.Key] = nameValue.Value; + } + } + + return etag; + } + } + + return null; + } + + /// + /// Gets the from the given request. + /// + /// The request. + /// The entity tag header value. + /// The parsed . + public static ETag GetETag(this HttpRequest request, EntityTagHeaderValue entityTagHeaderValue) + { + ETag etag = request.GetETag(entityTagHeaderValue); + return etag != null + ? new ETag + { + ConcurrencyProperties = etag.ConcurrencyProperties, + IsWellFormed = etag.IsWellFormed, + IsAny = etag.IsAny, + } + : null; + } + /// + /// Creates a link for the next page of results; To be used as the value of @odata.nextLink. + /// + /// The request on which to base the next page link. + /// The number of results allowed per page. + /// A next page link. + public static Uri GetNextPageLink(this HttpRequest request, int pageSize) + { + return GetNextPageLink(request, pageSize, null, null); + } + + /// + /// Creates a link for the next page of results; To be used as the value of @odata.nextLink. + /// + /// The request on which to base the next page link. + /// The number of results allowed per page. + /// Object which can be used to generate the skiptoken value. + /// Function that takes in the last object and returns the skiptoken value string. + /// A next page link. + public static Uri GetNextPageLink(this HttpRequest request, int pageSize, object instance, Func objectToSkipTokenValue) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + UriBuilder uriBuilder = new UriBuilder(request.Scheme, request.Host.Host) + { + Path = (request.PathBase + request.Path).ToUriComponent() + }; + if (request.Host.Port.HasValue) + { + uriBuilder.Port = request.Host.Port.Value; + } + + IEnumerable> queryParameters = request.Query.SelectMany(kvp => kvp.Value, (kvp, value) => new KeyValuePair(kvp.Key, value)); + return GetNextPageHelper.GetNextPageLink(uriBuilder.Uri, queryParameters, pageSize, instance, objectToSkipTokenValue); + } + + /// + /// Retrieves the Content-ID to Location mapping associated with the request. + /// + /// The request. + /// The Content-ID to Location mapping associated with this request, or null if there isn't one. + public static IDictionary GetODataContentIdMapping(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return null; + } + + internal static ODataVersion? ODataServiceVersion(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return GetODataVersionFromHeader(request.Headers, ODataVersionConstraint.ODataServiceVersionHeader); + } + + internal static ODataVersion? ODataMaxServiceVersion(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return GetODataVersionFromHeader(request.Headers, ODataVersionConstraint.ODataMaxServiceVersionHeader); + } + + internal static ODataVersion? ODataMinServiceVersion(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return GetODataVersionFromHeader(request.Headers, ODataVersionConstraint.ODataMinServiceVersionHeader); + } + + private static ODataVersion? GetODataVersionFromHeader(IHeaderDictionary headers, string headerName) + { + StringValues values; + if (headers.TryGetValue(headerName, out values)) + { + string value = values.FirstOrDefault(); + if (value != null) + { + string trimmedValue = value.Trim(' ', ';'); + try + { + return ODataUtils.StringToODataVersion(trimmedValue); + } + catch (ODataException) + { + // Parsing the odata version failed. + } + } + } + + return null; + } + + /// + /// Gets the dependency injection container for the OData request. + /// + /// The request. + /// The dependency injection container. + public static IServiceProvider GetRequestContainer(this HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + IServiceProvider requestContainer = request.ODataFeature().RequestContainer; + if (requestContainer != null) + { + return requestContainer; + } + + // HTTP routes will not have chance to call CreateRequestContainer. We have to call it. + return request.CreateRequestContainer(request.ODataFeature().RouteName); + } + + /// + /// Creates a request container that associates with the . + /// + /// The request. + /// The name of the route. + /// The request container created. + public static IServiceProvider CreateRequestContainer(this HttpRequest request, string routeName) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + if (request.ODataFeature().RequestContainer != null) + { + throw Error.InvalidOperation(SRResources.RequestContainerAlreadyExists); + } + + IServiceScope requestScope = request.CreateRequestScope(routeName); + IServiceProvider requestContainer = requestScope.ServiceProvider; + + request.ODataFeature().RequestScope = requestScope; + request.ODataFeature().RequestContainer = requestContainer; + + return requestContainer; + } + + /// + /// Deletes the request container from the and disposes + /// the container if is true. + /// + /// The request. + /// + /// Returns true to dispose the request container after deletion; false otherwise. + /// + public static void DeleteRequestContainer(this HttpRequest request, bool dispose) + { + if (request.ODataFeature().RequestScope != null) + { + IServiceScope requestScope = request.ODataFeature().RequestScope; + request.ODataFeature().RequestScope = null; + request.ODataFeature().RequestContainer = null; + + if (dispose) + { + requestScope.Dispose(); + } + } + } + + /// + /// Create a scoped request. + /// + /// The request. + /// The route name. + /// + private static IServiceScope CreateRequestScope(this HttpRequest request, string routeName) + { + IPerRouteContainer perRouteContainer = request.HttpContext.RequestServices.GetRequiredService(); + if (perRouteContainer == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(IPerRouteContainer)); + } + + IServiceProvider rootContainer = perRouteContainer.GetODataRootContainer(routeName); + IServiceScope scope = rootContainer.GetRequiredService().CreateScope(); + + // Bind scoping request into the OData container. + if (!string.IsNullOrEmpty(routeName)) + { + scope.ServiceProvider.GetRequiredService().HttpRequest = request; + } + + return scope; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpResponseExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpResponseExtensions.cs new file mode 100644 index 0000000..9993a95 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/HttpResponseExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the . + /// + public static class HttpResponseExtensions + { + /// + /// Determine if the response has a success status code. + /// + /// The response. + /// True if the response has a success status code; false otherwise. + public static bool IsSuccessStatusCode(this HttpResponse response) + { + return response?.StatusCode >= 200 && response.StatusCode < 300; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataApplicationBuilderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataApplicationBuilderExtensions.cs new file mode 100644 index 0000000..c9f75ca --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataApplicationBuilderExtensions.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Builder; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for to add OData routes. + /// + public static class ODataApplicationBuilderExtensions + { + private static readonly int MaxTop = 100; + + /// + /// The default OData route name. + /// + public readonly static string DefaultRouteName = "odata"; + + /// + /// The default OData route prefix. + /// + public readonly static string DefaultRoutePrefix = "odata"; + + /// + /// Use OData batching middleware. + /// + /// The to use. + /// The . + public static IApplicationBuilder UseODataBatching(this IApplicationBuilder app) + { + if (app == null) + { + throw Error.ArgumentNull("app"); + } + + return app.UseMiddleware(); + } + + /// + /// Use OData route with default route name and route prefix. + /// + /// The to use. + /// The to use. + /// The . + public static IApplicationBuilder UseOData(this IApplicationBuilder app, IEdmModel model) + { + return app.UseOData(DefaultRouteName, DefaultRoutePrefix, model); + } + + /// + /// Use OData route with given route name and route prefix. + /// + /// The to use. + /// The given OData route name. + /// The given OData route prefix. + /// The to use. + /// The . + public static IApplicationBuilder UseOData(this IApplicationBuilder app, string routeName, string routePrefix, IEdmModel model) + { + if (app == null) + { + throw Error.ArgumentNull("app"); + } + + VerifyODataIsRegistered(app); + + return app.UseMvc(b => + { + b.Select().Expand().Filter().OrderBy().MaxTop(MaxTop).Count(); + + b.MapODataServiceRoute(routeName, routePrefix, model); + }); + } + + private static void VerifyODataIsRegistered(IApplicationBuilder app) + { + // We use the IPerRouteContainer to verify if AddOData() was called before calling UseOData + if (app.ApplicationServices.GetService(typeof(IPerRouteContainer)) == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(IPerRouteContainer)); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataRouteBuilderExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataRouteBuilderExtensions.cs new file mode 100644 index 0000000..621fe77 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataRouteBuilderExtensions.cs @@ -0,0 +1,731 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ServiceLifetime = Microsoft.OData.ServiceLifetime; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for to add OData routes. + /// + public static class ODataRouteBuilderExtensions + { + /// + /// Sets the in route builder. + /// + /// The . + /// The default query settings. + public static IRouteBuilder SetDefaultQuerySettings(this IRouteBuilder builder, DefaultQuerySettings defaultQuerySettings) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + if (defaultQuerySettings == null) + { + throw Error.ArgumentNull("defaultQuerySettings"); + } + + if (!defaultQuerySettings.MaxTop.HasValue || defaultQuerySettings.MaxTop > 0) + { + ModelBoundQuerySettings.DefaultModelBoundQuerySettings.MaxTop = defaultQuerySettings.MaxTop; + } + + DefaultQuerySettings querySettings = builder.ServiceProvider.GetRequiredService(); + if (querySettings == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(DefaultQuerySettings)); + } + + querySettings = defaultQuerySettings; + return builder; + } + + /// + /// Gets the from route builder. + /// + /// The . + public static DefaultQuerySettings GetDefaultQuerySettings(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings querySettings = builder.ServiceProvider.GetRequiredService(); + if (querySettings == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(DefaultQuerySettings)); + } + + return querySettings; + } + + /// + /// Sets the MaxTop of in route builder. + /// + public static IRouteBuilder MaxTop(this IRouteBuilder builder, int? maxTopValue) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.MaxTop = maxTopValue; + if (!maxTopValue.HasValue || maxTopValue > 0) + { + ModelBoundQuerySettings.DefaultModelBoundQuerySettings.MaxTop = maxTopValue; + } + + return builder; + } + + /// + /// Sets the EnableExpand of in route builder, + /// depends on . + /// Todo: change QueryOptionSetting to SelectExpandType. + /// + public static IRouteBuilder Expand(this IRouteBuilder builder, QueryOptionSetting setting) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableExpand = setting == QueryOptionSetting.Allowed; + return builder; + } + + /// + /// Sets the EnableExpand to true of in route builder. + /// + public static IRouteBuilder Expand(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableExpand = true; + return builder; + } + + /// + /// Sets the SelectType of in route builder, + /// depends on . + /// Todo: change QueryOptionSetting to SelectExpandType. + /// + public static IRouteBuilder Select(this IRouteBuilder builder, QueryOptionSetting setting) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSelect = setting == QueryOptionSetting.Allowed; + return builder; + } + + /// + /// Sets the EnableSelect to true of in route builder. + /// + public static IRouteBuilder Select(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSelect = true; + return builder; + } + + /// + /// Sets the EnableFilter of in route builder, + /// depends on . + /// + public static IRouteBuilder Filter(this IRouteBuilder builder, QueryOptionSetting setting) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableFilter = setting == QueryOptionSetting.Allowed; + return builder; + } + + /// + /// Sets the EnableFilter to true of in route builder. + /// + public static IRouteBuilder Filter(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableFilter = true; + return builder; + } + + /// + /// Sets the EnableOrderBy of in route builder, + /// depends on . + /// + public static IRouteBuilder OrderBy(this IRouteBuilder builder, QueryOptionSetting setting) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableOrderBy = setting == QueryOptionSetting.Allowed; + return builder; + } + + /// + /// Sets the EnableOrderBy to true of in route builder. + /// + public static IRouteBuilder OrderBy(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableOrderBy = true; + return builder; + } + + /// + /// Sets the EnableCount of in route builder, + /// depends on . + /// + public static IRouteBuilder Count(this IRouteBuilder builder, QueryOptionSetting setting) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableCount = setting == QueryOptionSetting.Allowed; + return builder; + } + + /// + /// Sets the EnableCount to true of in route builder. + /// + public static IRouteBuilder Count(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableCount = true; + return builder; + } + + /// + /// Sets the EnableSkipToken to true of in route builder. + /// + public static IRouteBuilder SkipToken(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSkipToken = true; + + return builder; + } + + /// + /// Sets the EnableSkipToken to true of in route builder. + /// + public static IRouteBuilder SkipToken(this IRouteBuilder builder, QueryOptionSetting setting) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings(); + defaultQuerySettings.EnableSkipToken = setting == QueryOptionSetting.Allowed; + + return builder; + } + + /// + /// Sets the in route builder. + /// + /// The . + /// The default options. + public static IRouteBuilder SetDefaultODataOptions(this IRouteBuilder builder, ODataOptions defaultOptions) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + if (defaultOptions == null) + { + throw Error.ArgumentNull("defaultOptions"); + } + + ODataOptions options = builder.ServiceProvider.GetRequiredService(); + if (options == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(ODataOptions)); + } + + options = defaultOptions; + return builder; + } + + /// + /// Gets the from route builder. + /// + /// The . + public static ODataOptions GetDefaultODataOptions(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + ODataOptions options = builder.ServiceProvider.GetRequiredService(); + if (options == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(ODataOptions)); + } + + return options; + } + + /// + /// Enable the continue-on-error header. + /// + public static IRouteBuilder EnableContinueOnErrorHeader(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + ODataOptions defaultOptions = builder.GetDefaultODataOptions(); + defaultOptions.EnableContinueOnErrorHeader = true; + return builder; + } + + /// + /// Check the continue-on-error header is enable or not. + /// + /// + public static bool HasEnabledContinueOnErrorHeader(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + ODataOptions defaultOptions = builder.GetDefaultODataOptions(); + return defaultOptions.EnableContinueOnErrorHeader; + } + + /// + /// Sets whether or not the null dynamic property to be serialized. + /// + /// The . + /// true to serialize null dynamic property, false otherwise. + public static IRouteBuilder SetSerializeNullDynamicProperty(this IRouteBuilder builder, bool serialize) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + ODataOptions defaultOptions = builder.GetDefaultODataOptions(); + defaultOptions.NullDynamicPropertyIsEnabled = serialize; + return builder; + } + + /// + /// Check the null dynamic property is enable or not. + /// + /// The . + /// + public static bool HasEnabledNullDynamicProperty(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + ODataOptions defaultOptions = builder.GetDefaultODataOptions(); + return defaultOptions.NullDynamicPropertyIsEnabled; + } + + /// + /// Set the UrlKeyDelimiter in DefaultODataPathHandler. + /// + /// The . + /// The + public static IRouteBuilder SetUrlKeyDelimiter(this IRouteBuilder builder, ODataUrlKeyDelimiter urlKeyDelimiter) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + if (urlKeyDelimiter == null) + { + throw Error.ArgumentNull("urlKeyDelimiter"); + } + + ODataOptions defaultOptions = builder.GetDefaultODataOptions(); + defaultOptions.UrlKeyDelimiter = urlKeyDelimiter; + return builder; + } + + /// + /// Get the UrlKeyDelimiter in DefaultODataPathHandler. + /// + /// The . + internal static ODataUrlKeyDelimiter GetUrlKeyDelimiter(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + ODataOptions defaultOptions = builder.GetDefaultODataOptions(); + return defaultOptions.UrlKeyDelimiter; + } + + /// + /// Sets the in route builder. + /// + /// The . + /// The + /// + public static IRouteBuilder SetTimeZoneInfo(this IRouteBuilder builder, TimeZoneInfo timeZoneInfo) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + if (timeZoneInfo == null) + { + throw Error.ArgumentNull("timeZoneInfo"); + } + + TimeZoneInfoHelper.TimeZone = timeZoneInfo; + return builder; + } + + /// + /// Gets the from route builder. + /// + /// The . + /// + public static TimeZoneInfo GetTimeZoneInfo(this IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + return TimeZoneInfoHelper.TimeZone; + } + + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The to add the route to. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The configuring action to add the services to the root container. + /// The added . + public static ODataRoute MapODataServiceRoute(this IRouteBuilder builder, string routeName, + string routePrefix, Action configureAction) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + if (routeName == null) + { + throw Error.ArgumentNull("routeName"); + } + + // Build and configure the root container. + IPerRouteContainer perRouteContainer = builder.ServiceProvider.GetRequiredService(); + if (perRouteContainer == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(IPerRouteContainer)); + } + + // Create an service provider for this route. Add the default services to the custom configuration actions. + Action builderAction = ConfigureDefaultServices(builder, configureAction); + IServiceProvider serviceProvider = perRouteContainer.CreateODataRootContainer(routeName, builderAction); + + // Make sure the MetadataController is registered with the ApplicationPartManager. + ApplicationPartManager applicationPartManager = builder.ServiceProvider.GetRequiredService(); + applicationPartManager.ApplicationParts.Add(new AssemblyPart(typeof(MetadataController).Assembly)); + + // Resolve the path handler and set URI resolver to it. + IODataPathHandler pathHandler = serviceProvider.GetRequiredService(); + + // If settings is not on local, use the global configuration settings. + ODataOptions options = builder.ServiceProvider.GetRequiredService(); + if (pathHandler != null && pathHandler.UrlKeyDelimiter == null) + { + pathHandler.UrlKeyDelimiter = options.UrlKeyDelimiter; + } + + // Resolve some required services and create the route constraint. + ODataPathRouteConstraint routeConstraint = new ODataPathRouteConstraint(routeName); + + // Get constraint resolver. + IInlineConstraintResolver inlineConstraintResolver = builder + .ServiceProvider + .GetRequiredService(); + + // Resolve HTTP handler, create the OData route and register it. + ODataRoute route = null; + routePrefix = RemoveTrailingSlash(routePrefix); + + IRouter customRouter = serviceProvider.GetService(); + route = new ODataRoute( + customRouter != null ? customRouter : builder.DefaultHandler, + routeName, + routePrefix, + routeConstraint, + inlineConstraintResolver); + + // If a batch handler is present, register the route with the batch path mapper. This will be used + // by the batching middleware to handle the batch request. Batching still requires the injection + // of the batching middleware via UseODataBatching(). + ODataBatchHandler batchHandler = serviceProvider.GetService(); + if (batchHandler != null) + { + batchHandler.ODataRoute = route; + batchHandler.ODataRouteName = routeName; + + string batchPath = String.IsNullOrEmpty(routePrefix) + ? '/' + ODataRouteConstants.Batch + : '/' + routePrefix + '/' + ODataRouteConstants.Batch; + + ODataBatchPathMapping batchMapping = builder.ServiceProvider.GetRequiredService(); + batchMapping.AddRoute(routeName, batchPath); + } + + builder.Routes.Add(route); + return route; + } + + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The to add the route to. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The added . + public static ODataRoute MapODataServiceRoute(this IRouteBuilder builder, string routeName, + string routePrefix, IEdmModel model) + { + return builder.MapODataServiceRoute(routeName, routePrefix, containerBuilder => + containerBuilder.AddService(Microsoft.OData.ServiceLifetime.Singleton, sp => model) + .AddService>(Microsoft.OData.ServiceLifetime.Singleton, sp => + ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, builder))); + } + + /// + /// Maps the specified OData route and the OData route attributes. When the is + /// non-null, it will create a '$batch' endpoint to handle the batch requests. + /// + /// The to add the route to. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The . + /// The added . + public static ODataRoute MapODataServiceRoute(this IRouteBuilder builder, string routeName, + string routePrefix, IEdmModel model, ODataBatchHandler batchHandler) + { + return builder.MapODataServiceRoute(routeName, routePrefix, containerBuilder => + containerBuilder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService(ServiceLifetime.Singleton, sp => batchHandler) + .AddService>(ServiceLifetime.Singleton, sp => + ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, builder))); + } + + /// + /// Maps the specified OData route. + /// + /// The to add the route to. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The to use for parsing the OData path. + /// + /// The OData routing conventions to use for controller and action selection. + /// + /// The added . + public static ODataRoute MapODataServiceRoute(this IRouteBuilder builder, string routeName, + string routePrefix, IEdmModel model, IODataPathHandler pathHandler, + IEnumerable routingConventions) + { + return builder.MapODataServiceRoute(routeName, routePrefix, containerBuilder => + containerBuilder.AddService(Microsoft.OData.ServiceLifetime.Singleton, sp => model) + .AddService(Microsoft.OData.ServiceLifetime.Singleton, sp => pathHandler) + .AddService(Microsoft.OData.ServiceLifetime.Singleton, sp => routingConventions.ToList().AsEnumerable())); + } + + /// + /// Maps the specified OData route. When the is non-null, it will + /// create a '$batch' endpoint to handle the batch requests. + /// + /// The to add the route to. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The to use for parsing the OData path. + /// + /// The OData routing conventions to use for controller and action selection. + /// + /// The . + /// The added . + public static ODataRoute MapODataServiceRoute(this IRouteBuilder builder, string routeName, + string routePrefix, IEdmModel model, IODataPathHandler pathHandler, + IEnumerable routingConventions, ODataBatchHandler batchHandler) + { + return builder.MapODataServiceRoute(routeName, routePrefix, containerBuilder => + containerBuilder.AddService(ServiceLifetime.Singleton, sp => model) + .AddService(ServiceLifetime.Singleton, sp => pathHandler) + .AddService(ServiceLifetime.Singleton, sp => routingConventions.ToList().AsEnumerable()) + .AddService(ServiceLifetime.Singleton, sp => batchHandler)); + } + + /// + /// Enables dependency injection support for HTTP routes. + /// + /// The to add the container to. + public static void EnableDependencyInjection(this IRouteBuilder builder) + { + builder.EnableDependencyInjection(null); + } + + /// + /// Enables dependency injection support for HTTP routes. + /// + /// The to add the container to. + /// The configuring action to add the services to the root container. + public static void EnableDependencyInjection(this IRouteBuilder builder, + Action configureAction) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + IPerRouteContainer perRouteContainer = builder.ServiceProvider.GetRequiredService(); + if (perRouteContainer == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(IPerRouteContainer)); + } + + if (perRouteContainer.HasODataRootContainer(null)) + { + throw Error.InvalidOperation(SRResources.CannotReEnableDependencyInjection); + } + + // Get the per-route container and create a new non-route container. + perRouteContainer.CreateODataRootContainer(null, ConfigureDefaultServices(builder, configureAction)); + } + + /// + /// Remote the trailing slash from a route prefix string. + /// + /// The route prefix string. + /// The route prefix string without a trailing slash. + private static string RemoveTrailingSlash(string routePrefix) + { + if (!String.IsNullOrEmpty(routePrefix)) + { + int prefixLastIndex = routePrefix.Length - 1; + if (routePrefix[prefixLastIndex] == '/') + { + // Remove the last trailing slash if it has one. + routePrefix = routePrefix.Substring(0, routePrefix.Length - 1); + } + } + + return routePrefix; + } + + /// + /// Configure the default services. + /// + /// The . + /// The configuring action to add the services to the root container. + /// A configuring action to add the services to the root container. + internal static Action ConfigureDefaultServices(IRouteBuilder routeBuilder, Action configureAction) + { + return (builder => + { + // Add platform-specific services here. Add Configuration first as other services may rely on it. + // For assembly resolution, add the and internal (IWebApiAssembliesResolver) where IWebApiAssembliesResolver + // is transient and instantiated from ApplicationPartManager by DI. + builder.AddService(ServiceLifetime.Transient); + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton); + + // Access the default query settings and options from the global container. + builder.AddService(ServiceLifetime.Singleton, sp => routeBuilder.GetDefaultQuerySettings()); + builder.AddService(ServiceLifetime.Singleton, sp => routeBuilder.GetDefaultODataOptions()); + + // Add the default webApi services. + builder.AddDefaultWebApiServices(); + + // Add custom actions. + configureAction?.Invoke(builder); + }); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataServiceCollectionExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataServiceCollectionExtensions.cs new file mode 100644 index 0000000..f331329 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/ODataServiceCollectionExtensions.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods to add odata services. + /// + public static class ODataServiceCollectionExtensions + { + /// + /// Adds essential OData services to the specified . + /// + /// The to add services to. + /// An that can be used to further configure the OData services. + public static IODataBuilder AddOData(this IServiceCollection services) + { + if (services == null) + { + throw Error.ArgumentNull(nameof(services)); + } + + // Setup per-route dependency injection. When routes are added, additional + // per-route classes will be injected, such as IEdmModel and IODataRoutingConventions. + services.AddSingleton(); + + // Add OData and query options. Opting not to use IConfigurationOptions in favor of + // fluent extensions APIs to IRouteBuilder. + services.AddSingleton(); + services.AddSingleton(); + + // Add the batch path mapping class to store batch route names and prefixes. + services.AddSingleton(); + + // Configure MvcCore to use formatters. The OData formatters do go into the global service + // provider and get picked up by the AspNetCore MVC framework. However, they ignore non-OData + // requests so they won't be used for non-OData formatting. + services.AddMvcCore(options => + { + // Add OData input formatters at index 0, which overrides the built-in json and xml formatters. + // Add in reverse order at index 0 to preserve order from the factory in the final list. + foreach (ODataInputFormatter inputFormatter in ODataInputFormatterFactory.Create().Reverse()) + { + options.InputFormatters.Insert(0, inputFormatter); + } + + // Add OData output formatters at index 0, which overrides the built-in json and xml formatters. + // Add in reverse order at index 0 to preserve order from the factory in the final list. + foreach (ODataOutputFormatter outputFormatter in ODataOutputFormatterFactory.Create().Reverse()) + { + options.OutputFormatters.Insert(0, outputFormatter); + } + + // Add the value provider. + options.ValueProviderFactories.Add(new ODataValueProviderFactory()); + }); + + // Add our action selector. The ODataActionSelector creates an ActionSelector in it's constructor + // and pass all non-OData calls to this inner selector. + services.AddSingleton(); + + // Add the ActionContextAccessor; this allows access to the ActionContext which is needed + // during the formatting process to construct a IUrlHelper. + services.AddSingleton(); + + return new ODataBuilder(services); + } + + /// + /// Enables query support for actions with an or return + /// type. To avoid processing unexpected or malicious queries, use the validation settings on + /// to validate incoming queries. For more information, visit + /// http://go.microsoft.com/fwlink/?LinkId=279712. + /// + /// The services collection. + public static IServiceCollection AddODataQueryFilter(this IServiceCollection services) + { + return AddODataQueryFilter(services, new EnableQueryAttribute()); + } + + /// + /// Enables query support for actions with an or return + /// type. To avoid processing unexpected or malicious queries, use the validation settings on + /// to validate incoming queries. For more information, visit + /// http://go.microsoft.com/fwlink/?LinkId=279712. + /// + /// The services collection. + /// The action filter that executes the query. + public static IServiceCollection AddODataQueryFilter(this IServiceCollection services, IActionFilter queryFilter) + { + if (services == null) + { + throw Error.ArgumentNull("services"); + } + + services.TryAddEnumerable(ServiceDescriptor.Singleton(new QueryFilterProvider(queryFilter))); + return services; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/SerializableErrorExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/SerializableErrorExtensions.cs new file mode 100644 index 0000000..c230655 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/SerializableErrorExtensions.cs @@ -0,0 +1,230 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class SerializableErrorExtensions + { + /// + /// Converts the to an . + /// + /// The instance to convert. + /// The converted + public static ODataError CreateODataError(this SerializableError serializableError) + { + if (serializableError == null) + { + throw Error.ArgumentNull("serializableError"); + } + + //Clone for removal of handled entries + var errors = serializableError.ToDictionary(pair => pair.Key, pair => pair.Value); + + var innerError = errors.ToODataInnerError(); + + string errorCode = errors.GetPropertyValue(SerializableErrorKeys.ErrorCodeKey); + string message = errors.GetPropertyValue(SerializableErrorKeys.MessageKey); + + errors.Remove(SerializableErrorKeys.ErrorCodeKey); + errors.Remove(SerializableErrorKeys.MessageKey); + + return new ODataError + { + ErrorCode = string.IsNullOrWhiteSpace(errorCode) ? null : errorCode, + Message = string.IsNullOrWhiteSpace(message) ? errors.ConvertModelStateErrors() : message, + Details = errors.CreateErrorDetails(), + InnerError = innerError + }; + } + + private static ODataInnerError ToODataInnerError(this Dictionary errors) + { + string innerErrorMessage = errors.GetPropertyValue(SerializableErrorKeys.ExceptionMessageKey); + + if (innerErrorMessage == null) + { + string messageDetail = errors.GetPropertyValue(SerializableErrorKeys.MessageDetailKey); + + if (messageDetail == null) + { + SerializableError modelStateError = errors.GetPropertyValue(SerializableErrorKeys.ModelStateKey); + + errors.Remove(SerializableErrorKeys.ModelStateKey); + + return (modelStateError == null) ? null + : new ODataInnerError { Message = ConvertModelStateErrors(modelStateError) }; + } + + errors.Remove(SerializableErrorKeys.MessageDetailKey); + + return new ODataInnerError { Message = messageDetail }; + } + + errors.Remove(SerializableErrorKeys.ExceptionMessageKey); + + ODataInnerError innerError = new ODataInnerError + { + Message = innerErrorMessage, + TypeName = errors.GetPropertyValue(SerializableErrorKeys.ExceptionTypeKey), + StackTrace = errors.GetPropertyValue(SerializableErrorKeys.StackTraceKey) + }; + + errors.Remove(SerializableErrorKeys.ExceptionTypeKey); + errors.Remove(SerializableErrorKeys.StackTraceKey); + + SerializableError innerExceptionError = errors.GetPropertyValue(SerializableErrorKeys.InnerExceptionKey); + + errors.Remove(SerializableErrorKeys.InnerExceptionKey); + + if (innerExceptionError != null) + { + innerError.InnerError = ToODataInnerError(innerExceptionError); + } + + return innerError; + } + + // Convert the model state errors in to a string (for debugging only). + // This should be improved once ODataError allows more details. + private static string ConvertModelStateErrors(this IReadOnlyDictionary errors) + { + StringBuilder builder = new StringBuilder(); + + foreach (KeyValuePair modelStateError in errors.Where(kvp => kvp.Value != null)) + { + if (builder.Length > 0) + { + builder.AppendLine(); + } + + if (!string.IsNullOrWhiteSpace(modelStateError.Key)) + { + builder.AppendLine($"{modelStateError.Key}:"); + } + + IEnumerable errorMessages = modelStateError.Value as IEnumerable; + + if (errorMessages != null) + { + foreach (string errorMessage in errorMessages) + { + builder.AppendLine(errorMessage); + } + } + else + { + builder.AppendLine(modelStateError.Value.ToString()); + } + } + + var result = builder.ToString(); + + return !result.EndsWith(Environment.NewLine) ? result : result.Substring(0, result.Length - Environment.NewLine.Length); + } + + private static ICollection CreateErrorDetails(this IReadOnlyDictionary errors) + { + return errors.SelectMany(CreateErrorDetails).ToList(); + } + + private static IEnumerable CreateErrorDetails(KeyValuePair pair) + { + var errors = pair.Value as IEnumerable; + + if (errors != null) + { + return errors.Select(error => new ODataErrorDetail + { + Target = string.IsNullOrWhiteSpace(pair.Key) ? null : pair.Key, + Message = error + }); + } + + return new[] + { + new ODataErrorDetail + { + Target = pair.Key, + Message = pair.Value?.ToString() + } + }; + } + + private static TValue GetPropertyValue(this IReadOnlyDictionary error, string errorKey) + { + object value; + + if (error.TryGetValue(errorKey, out value) && value is TValue) + { + return (TValue)value; + } + + return default(TValue); + } + } + + /// + /// Different keys for adding entries to an instance so + /// that it can be parsed to a instance + /// + public static class SerializableErrorKeys + { + /// + /// Provides a key for the Message. + /// + public static readonly string MessageKey = "Message"; + + /// + /// Provides a key for the MessageDetail. + /// + public static readonly string MessageDetailKey = "MessageDetail"; + + /// + /// Provides a key for the ModelState. + /// + public static readonly string ModelStateKey = "ModelState"; + + /// + /// Provides a key for the ExceptionMessage. + /// + public static readonly string ExceptionMessageKey = "ExceptionMessage"; + + /// + /// Provides a key for the ExceptionType. + /// + public static readonly string ExceptionTypeKey = "ExceptionType"; + + /// + /// Provides a key for the StackTrace. + /// + public static readonly string StackTraceKey = "StackTrace"; + + /// + /// Provides a key for the InnerException. + /// + public static readonly string InnerExceptionKey = "InnerException"; + + /// + /// Provides a key for the MessageLanguage. + /// + public static readonly string MessageLanguageKey = "MessageLanguage"; + + /// + /// Provides a key for the ErrorCode. + /// + public static readonly string ErrorCodeKey = "ErrorCode"; + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/UrlHelperExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/UrlHelperExtensions.cs new file mode 100644 index 0000000..0c9790d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Extensions/UrlHelperExtensions.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using ODataPathSegment = Microsoft.OData.UriParser.ODataPathSegment; + +namespace Microsoft.AspNet.OData.Extensions +{ + /// + /// Provides extension methods for the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class UrlHelperExtensions + { + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The URL helper. + /// The OData path segments. + /// The generated OData link. + public static string CreateODataLink(this IUrlHelper urlHelper, params ODataPathSegment[] segments) + { + return CreateODataLink(urlHelper, segments as IList); + } + + /// + /// Generates an OData link using the request's OData route name and path handler and given segments. + /// + /// The URL helper. + /// The OData path segments. + /// The generated OData link. + public static string CreateODataLink(this IUrlHelper urlHelper, IList segments) + { + if (urlHelper == null) + { + throw Error.ArgumentNull("urlHelper"); + } + + HttpRequest request = urlHelper.ActionContext.HttpContext.Request; + Contract.Assert(request != null); + + string routeName = request.ODataFeature().RouteName; + if (String.IsNullOrEmpty(routeName)) + { + throw Error.InvalidOperation(SRResources.RequestMustHaveODataRouteName); + } + + IODataPathHandler pathHandler = request.GetPathHandler(); + return CreateODataLink(urlHelper, routeName, pathHandler, segments); + } + + /// + /// Generates an OData link using the given OData route name, path handler, and segments. + /// + /// The URL helper. + /// The name of the OData route. + /// The path handler to use for generating the link. + /// The OData path segments. + /// The generated OData link. + public static string CreateODataLink(this IUrlHelper urlHelper, string routeName, IODataPathHandler pathHandler, + IList segments) + { + if (urlHelper == null) + { + throw Error.ArgumentNull("urlHelper"); + } + + if (pathHandler == null) + { + throw Error.ArgumentNull("pathHandler"); + } + + string odataPath = pathHandler.Link(new ODataPath(segments)); + return urlHelper.Link( + routeName, + new RouteValueDictionary() { { ODataRouteConstants.ODataPath, odataPath } }); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs new file mode 100644 index 0000000..82183b6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DefaultODataDeserializerProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// The default . + /// + public partial class DefaultODataDeserializerProvider : ODataDeserializerProvider + { + /// + /// This signature uses types that are AspNetCore-specific. + public override ODataDeserializer GetODataDeserializer(Type type, HttpRequest request) + { + // Using a Func to delay evaluation of the model. + return GetODataDeserializerImpl(type, () => request.GetModel()); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerContext.cs new file mode 100644 index 0000000..6b88acd --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerContext.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// This class encapsulates the state and settings that get passed to + /// from the . + /// + public partial class ODataDeserializerContext + { + private HttpRequest _request; + + /// + /// Gets or sets the HTTP Request that is being deserialized. + /// + /// This signature uses types that are AspNetCore-specific. + public HttpRequest Request + { + get { return _request; } + set + { + _request = value; + InternalRequest = _request != null ? new WebApiRequestMessage(_request) : null; + InternalUrlHelper = _request != null ? new WebApiUrlHelper(_request.GetUrlHelper()) : null; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs new file mode 100644 index 0000000..1fcb920 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Formatter.Deserialization +{ + /// + /// Represents a factory that creates an . + /// + public abstract partial class ODataDeserializerProvider + { + /// + /// Gets an for the given type. + /// + /// The CLR type. + /// The request being deserialized. + /// An that can deserialize the given type. + /// This signature uses types that are AspNetCore-specific. + public abstract ODataDeserializer GetODataDeserializer(Type type, HttpRequest request); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/IMediaTypeMappingCollection.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/IMediaTypeMappingCollection.cs new file mode 100644 index 0000000..736c966 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/IMediaTypeMappingCollection.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// An interface that defines a property to access a collection of objects. + /// + /// + /// MediaTypeMapping is part of the platform in AspNet but defined here for AspNetCore to allow for reusing + /// the classes derive form it for managing media type mapping. + /// + interface IMediaTypeMappingCollection + { + /// + /// Gets a collection of objects. + /// + ICollection MediaTypeMappings { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/MediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/MediaTypeMapping.cs new file mode 100644 index 0000000..9fd2631 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/MediaTypeMapping.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// A class to support matching media types. + /// + /// + /// This is part of the platform in AspNet but defined here for AspNetCore to allow for reusing + /// the classes derive form it for managing media type mapping. + /// + public abstract class MediaTypeMapping + { + /// + /// Initializes a new instance of a System.Net.Http.Formatting.MediaTypeMapping with + /// the given mediaType value. + /// + /// The mediaType that is associated with the request. + protected MediaTypeMapping(string mediaType) + { + MediaTypeHeaderValue value = null; + if (!MediaTypeHeaderValue.TryParse(mediaType, out value)) + { + throw Error.ArgumentNull("mediaType"); + } + + this.MediaType = value; + } + + /// + /// Gets the media type that is associated with request. + /// + public MediaTypeHeaderValue MediaType { get; protected set; } + + /// + /// Returns a value indicating whether this instance can provide a + /// for the given . + /// + /// The to check. + /// If this 's route data contains it returns 1.0 otherwise 0.0. + public abstract double TryMatchMediaType(HttpRequest request); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataCountMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataCountMediaTypeMapping.cs new file mode 100644 index 0000000..b0e7768 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataCountMediaTypeMapping.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests with $count. + /// + /// This class derives from a platform-specific class. + public partial class ODataCountMediaTypeMapping : MediaTypeMapping + { + /// + public override double TryMatchMediaType(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + return IsCountRequest(request.ODataFeature().Path) ? 1 : 0; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatter.cs new file mode 100644 index 0000000..f7bb10d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatter.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// class to handle OData. + /// + public class ODataInputFormatter : TextInputFormatter + { + /// + /// The set of payload kinds this formatter will accept in CanReadType. + /// + private readonly IEnumerable _payloadKinds; + + /// + /// Initializes a new instance of the class. + /// + /// The kind of payloads this formatter supports. + public ODataInputFormatter(IEnumerable payloadKinds) + { + if (payloadKinds == null) + { + throw Error.ArgumentNull("payloadKinds"); + } + + _payloadKinds = payloadKinds; + } + + /// + /// Gets or sets a method that allows consumers to provide an alternate base + /// address for OData Uri. + /// + public Func BaseAddressFactory { get; set; } + + /// + public override bool CanRead(InputFormatterContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + HttpRequest request = context.HttpContext.Request; + if (request == null) + { + throw Error.InvalidOperation(SRResources.ReadFromStreamAsyncMustHaveRequest); + } + + // Ignore non-OData requests. + if (request.ODataFeature().Path == null) + { + return false; + } + + Type type = context.ModelType; + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + ODataDeserializerProvider deserializerProvider = request.GetRequestContainer().GetRequiredService(); + + return ODataInputFormatterHelper.CanReadType( + type, + request.GetModel(), + request.ODataFeature().Path, + _payloadKinds, + (objectType) => deserializerProvider.GetEdmTypeDeserializer(objectType), + (objectType) => deserializerProvider.GetODataDeserializer(objectType, request)); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")] + public override Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + Type type = context.ModelType; + if (type == null) + { + throw Error.ArgumentNull("type"); + } + + HttpRequest request = context.HttpContext.Request; + if (request == null) + { + throw Error.InvalidOperation(SRResources.ReadFromStreamAsyncMustHaveRequest); + } + + // If content length is 0 then return default value for this type + RequestHeaders contentHeaders = request.GetTypedHeaders(); + object defaultValue = GetDefaultValueForType(type); + if (contentHeaders == null || contentHeaders.ContentLength == 0) + { + return Task.FromResult(InputFormatterResult.Success(defaultValue)); + } + + try + { + Func getODataDeserializerContext = () => + { + return new ODataDeserializerContext + { + Request = request, + }; + }; + + Action logErrorAction = (ex) => + { + ILogger logger = context.HttpContext.RequestServices.GetService(); + if (logger == null) + { + throw ex; + } + + logger.LogError(ex, String.Empty); + }; + + List toDispose = new List(); + + ODataDeserializerProvider deserializerProvider = request.GetRequestContainer().GetRequiredService(); + + object result = ODataInputFormatterHelper.ReadFromStream( + type, + defaultValue, + request.GetModel(), + GetBaseAddressInternal(request), + new WebApiRequestMessage(request), + () => ODataMessageWrapperHelper.Create(request.Body, request.Headers, request.GetODataContentIdMapping(), request.GetRequestContainer()), + (objectType) => deserializerProvider.GetEdmTypeDeserializer(objectType), + (objectType) => deserializerProvider.GetODataDeserializer(objectType, request), + getODataDeserializerContext, + (disposable) => toDispose.Add(disposable), + logErrorAction); + + foreach (IDisposable obj in toDispose) + { + obj.Dispose(); + } + + return Task.FromResult(InputFormatterResult.Success(result)); + } + catch (Exception ex) + { + context.ModelState.AddModelError(context.ModelName, ex, context.Metadata); + return Task.FromResult(InputFormatterResult.Failure()); + } + } + + /// + /// Internal method used for selecting the base address to be used with OData uris. + /// If the consumer has provided a delegate for overriding our default implementation, + /// we call that, otherwise we default to existing behavior below. + /// + /// The HttpRequest object for the given request. + /// The base address to be used as part of the service root; must terminate with a trailing '/'. + private Uri GetBaseAddressInternal(HttpRequest request) + { + if (BaseAddressFactory != null) + { + return BaseAddressFactory(request); + } + else + { + return ODataInputFormatter.GetDefaultBaseAddress(request); + } + } + + /// + /// Returns a base address to be used in the service root when reading or writing OData uris. + /// + /// The HttpRequest object for the given request. + /// The base address to be used as part of the service root in the OData uri; must terminate with a trailing '/'. + public static Uri GetDefaultBaseAddress(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + string baseAddress = request.GetUrlHelper().CreateODataLink(); + if (baseAddress == null) + { + throw new SerializationException(SRResources.UnableToDetermineBaseUrl); + } + + return baseAddress[baseAddress.Length - 1] != '/' ? new Uri(baseAddress + '/') : new Uri(baseAddress); + } + + internal static ODataVersion GetODataResponseVersion(HttpRequest request) + { + // OData protocol requires that you send the minimum version that the client needs to know to + // understand the response. There is no easy way we can figure out the minimum version that the client + // needs to understand our response. We send response headers much ahead generating the response. So if + // the requestMessage has a OData-MaxVersion, tell the client that our response is of the same + // version; else use the DataServiceVersionHeader. Our response might require a higher version of the + // client and it might fail. If the client doesn't send these headers respond with the default version + // (V4). + return request.ODataMaxServiceVersion() ?? + request.ODataServiceVersion() ?? + ODataVersionConstraint.DefaultODataVersion; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatterFactory.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatterFactory.cs new file mode 100644 index 0000000..2b1a09c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatterFactory.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Text; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Factory for classes to handle OData. + /// + public static class ODataInputFormatterFactory + { + /// + /// Creates a list of media type formatters to handle OData. + /// + /// A list of media type formatters to handle OData. + public static IList Create() + { + return new List() + { + // Place JSON formatter first so it gets used when the request doesn't ask for a specific content type + CreateApplicationJson(), + CreateApplicationXml(), + CreateRawValue() + }; + } + + private static void AddSupportedEncodings(ODataInputFormatter formatter) + { + formatter.SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, + throwOnInvalidBytes: true)); + formatter.SupportedEncodings.Add(new UnicodeEncoding(bigEndian: false, byteOrderMark: true, + throwOnInvalidBytes: true)); + } + + private static ODataInputFormatter CreateRawValue() + { + return CreateFormatterWithoutMediaTypes(ODataPayloadKind.Value); + } + + private static ODataInputFormatter CreateApplicationJson() + { + ODataInputFormatter formatter = CreateFormatterWithoutMediaTypes( + ODataPayloadKind.ResourceSet, + ODataPayloadKind.Resource, + ODataPayloadKind.Property, + ODataPayloadKind.EntityReferenceLink, + ODataPayloadKind.EntityReferenceLinks, + ODataPayloadKind.Collection, + ODataPayloadKind.ServiceDocument, + ODataPayloadKind.Error, + ODataPayloadKind.Parameter, + ODataPayloadKind.Delta); + + // Add minimal metadata as the first media type so it gets used when the request doesn't + // ask for a specific content type + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataMinimalMetadataStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataMinimalMetadataStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataMinimalMetadata); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataFullMetadataStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataFullMetadataStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataFullMetadata); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataNoMetadataStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataNoMetadataStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataNoMetadata); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJson); + + return formatter; + } + + private static ODataInputFormatter CreateApplicationXml() + { + ODataInputFormatter formatter = CreateFormatterWithoutMediaTypes( + ODataPayloadKind.MetadataDocument); + + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationXml); + + return formatter; + } + + private static ODataInputFormatter CreateFormatterWithoutMediaTypes(params ODataPayloadKind[] payloadKinds) + { + ODataInputFormatter formatter = new ODataInputFormatter(payloadKinds); + AddSupportedEncodings(formatter); + return formatter; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs new file mode 100644 index 0000000..15936f7 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Deserialization; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Primitives; +using Microsoft.OData; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// A model binder for ODataParameterValue values. + /// + /// + /// This class is similar to ODataModelBinderProvider in AspNet. The flow is similar but the + /// type are dissimilar enough making a common version more complex than separate versions. + /// + internal class ODataModelBinder : IModelBinder + { + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't want to fail in model binding.")] + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw Error.ArgumentNull("bindingContext"); + } + + if (bindingContext.ModelMetadata == null) + { + throw Error.Argument("bindingContext", SRResources.ModelBinderUtil_ModelMetadataCannotBeNull); + } + + ValueProviderResult valueProviderResult = ValueProviderResult.None; + string modelName = ODataParameterValue.ParameterValuePrefix + bindingContext.ModelName; + try + { + // Look in route data for a ODataParameterValue. + object valueAsObject = null; + if (!bindingContext.HttpContext.Request.ODataFeature().RoutingConventionsStore.TryGetValue(modelName, out valueAsObject)) + { + bindingContext.ActionContext.RouteData.Values.TryGetValue(modelName, out valueAsObject); + } + + if (valueAsObject != null) + { + StringValues stringValues = new StringValues(valueAsObject.ToString()); + valueProviderResult = new ValueProviderResult(stringValues); + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + + ODataParameterValue paramValue = valueAsObject as ODataParameterValue; + if (paramValue != null) + { + HttpRequest request = bindingContext.HttpContext.Request; + object model = ConvertTo(paramValue, bindingContext, request.GetRequestContainer()); + bindingContext.Result = ModelBindingResult.Success(model); + return Task.CompletedTask; + } + } + else + { + // If not in the route data, ask the value provider. + valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + if (valueProviderResult == ValueProviderResult.None) + { + valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + } + + if (valueProviderResult != ValueProviderResult.None) + { + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + + object model = ODataModelBinderConverter.ConvertTo(valueProviderResult.FirstValue, bindingContext.ModelType); + if (model != null) + { + bindingContext.Result = ModelBindingResult.Success(model); + return Task.CompletedTask; + } + } + } + + // No matches, binding failed. + bindingContext.Result = ModelBindingResult.Failed(); + } + catch (ODataException ex) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message); + bindingContext.Result = ModelBindingResult.Failed(); + } + catch (ValidationException ex) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, Error.Format(SRResources.ValueIsInvalid, valueProviderResult.FirstValue, ex.Message)); + bindingContext.Result = ModelBindingResult.Failed(); + } + catch (FormatException ex) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, Error.Format(SRResources.ValueIsInvalid, valueProviderResult.FirstValue, ex.Message)); + bindingContext.Result = ModelBindingResult.Failed(); + } + catch (Exception e) + { + bindingContext.ModelState.AddModelError(bindingContext.ModelName, e.Message); + bindingContext.Result = ModelBindingResult.Failed(); + } + + return Task.CompletedTask; + } + + internal static object ConvertTo(ODataParameterValue parameterValue, ModelBindingContext bindingContext, IServiceProvider requestContainer) + { + Contract.Assert(parameterValue != null && parameterValue.EdmType != null); + + object oDataValue = parameterValue.Value; + if (oDataValue == null || oDataValue is ODataNullValue) + { + return null; + } + + IEdmTypeReference edmTypeReference = parameterValue.EdmType; + ODataDeserializerContext readContext = BuildDeserializerContext(bindingContext, edmTypeReference); + return ODataModelBinderConverter.Convert(oDataValue, edmTypeReference, bindingContext.ModelType, + bindingContext.ModelName, readContext, requestContainer); + } + + internal static ODataDeserializerContext BuildDeserializerContext(ModelBindingContext bindingContext, IEdmTypeReference edmTypeReference) + { + HttpRequest request = bindingContext.HttpContext.Request; + ODataPath path = request.ODataFeature().Path; + IEdmModel edmModel = request.GetModel(); + + return new ODataDeserializerContext + { + Path = path, + Model = edmModel, + Request = request, + ResourceType = bindingContext.ModelType, + ResourceEdmType = edmTypeReference, + }; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs new file mode 100644 index 0000000..f284662 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs @@ -0,0 +1,294 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Results; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using Microsoft.OData; +using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// class to handle OData. + /// + public class ODataOutputFormatter : TextOutputFormatter, IMediaTypeMappingCollection + { + /// + /// The set of payload kinds this formatter will accept in CanWriteType. + /// + private readonly IEnumerable _payloadKinds; + + /// + /// Initializes a new instance of the class. + /// + /// The kind of payloads this formatter supports. + public ODataOutputFormatter(IEnumerable payloadKinds) + { + if (payloadKinds == null) + { + throw Error.ArgumentNull("payloadKinds"); + } + + _payloadKinds = payloadKinds; + } + + /// + /// Gets or sets a method that allows consumers to provide an alternate base + /// address for OData Uri. + /// + public Func BaseAddressFactory { get; set; } + + /// + /// Gets a collection of objects. + /// + public ICollection MediaTypeMappings { get; } = new List(); + + /// + public override bool CanWriteResult(OutputFormatterCanWriteContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + // Ensure we have a valid request. + HttpRequest request = context.HttpContext.Request; + if (request == null) + { + throw Error.InvalidOperation(SRResources.ReadFromStreamAsyncMustHaveRequest); + } + + // Ignore non-OData requests. + if (request.ODataFeature().Path == null) + { + return false; + } + + // Allow the base class to make its determination, which includes + // checks for SupportedMediaTypes. + bool suportedMediaTypeFound = false; + if (SupportedMediaTypes.Any()) + { + suportedMediaTypeFound = base.CanWriteResult(context); + } + + // See if the request satisfies any mappings. + IEnumerable matchedMappings = (MediaTypeMappings == null) ? null : MediaTypeMappings + .Where(m => (m.TryMatchMediaType(request) > 0)); + + // Now pick the best content type. If a media mapping was found, use that and override the + // value specified by the controller, if any. Otherwise, let the base class decide. + if (matchedMappings != null && matchedMappings.Any()) + { + context.ContentType = matchedMappings.First().MediaType.ToString(); + } + else if (!suportedMediaTypeFound) + { + return false; + } + + // We need the type in order to write it. + Type type = context.ObjectType ?? context.Object?.GetType(); + if (type == null) + { + return false; + } + type = TypeHelper.GetTaskInnerTypeOrSelf(type); + + ODataSerializerProvider serializerProvider = request.GetRequestContainer().GetRequiredService(); + + // See if this type is a SingleResult or is derived from SingleResult. + bool isSingleResult = false; + if (type.IsGenericType) + { + Type genericType = type.GetGenericTypeDefinition(); + Type baseType = TypeHelper.GetBaseType(type); + isSingleResult = (genericType == typeof(SingleResult<>) || baseType == typeof(SingleResult)); + } + + return ODataOutputFormatterHelper.CanWriteType( + type, + _payloadKinds, + isSingleResult, + new WebApiRequestMessage(request), + (objectType) => serializerProvider.GetODataPayloadSerializer(objectType, request)); + } + + /// + public override void WriteResponseHeaders(OutputFormatterWriteContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + Type type = context.ObjectType; + if (type == null) + { + throw Error.ArgumentNull("type"); + } + type = TypeHelper.GetTaskInnerTypeOrSelf(type); + + HttpRequest request = context.HttpContext.Request; + if (request == null) + { + throw Error.InvalidOperation(SRResources.WriteToStreamAsyncMustHaveRequest); + } + + HttpResponse response = context.HttpContext.Response; + response.ContentType = context.ContentType.Value; + + MediaTypeHeaderValue contentType = GetContentType(response.Headers[HeaderNames.ContentType].FirstOrDefault()); + + // Determine the content type. + MediaTypeHeaderValue newMediaType = null; + if (ODataOutputFormatterHelper.TryGetContentHeader(type, contentType, out newMediaType)) + { + response.Headers[HeaderNames.ContentType] = new StringValues(newMediaType.ToString()); + } + + // Set the character set. + MediaTypeHeaderValue currentContentType = GetContentType(response.Headers[HeaderNames.ContentType].FirstOrDefault()); + RequestHeaders requestHeader = request.GetTypedHeaders(); + if (requestHeader != null && requestHeader.AcceptCharset != null) + { + IEnumerable acceptCharsetValues = requestHeader.AcceptCharset.Select(cs => cs.Value.Value); + + string newCharSet = string.Empty; + if (ODataOutputFormatterHelper.TryGetCharSet(currentContentType, acceptCharsetValues, out newCharSet)) + { + currentContentType.CharSet = newCharSet; + response.Headers[HeaderNames.ContentType] = new StringValues(currentContentType.ToString()); + } + } + + // Add version header. + response.Headers[ODataVersionConstraint.ODataServiceVersionHeader] = ODataUtils.ODataVersionToString(ResultHelpers.GetODataResponseVersion(request)); + } + + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The caught exception type is reflected into a faulted task.")] + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + Type type = context.ObjectType; + if (type == null) + { + throw Error.ArgumentNull("type"); + } + type = TypeHelper.GetTaskInnerTypeOrSelf(type); + + HttpRequest request = context.HttpContext.Request; + if (request == null) + { + throw Error.InvalidOperation(SRResources.WriteToStreamAsyncMustHaveRequest); + } + + try + { + HttpResponse response = context.HttpContext.Response; + Uri baseAddress = GetBaseAddressInternal(request); + MediaTypeHeaderValue contentType = GetContentType(response.Headers[HeaderNames.ContentType].FirstOrDefault()); + + Func getODataSerializerContext = () => + { + return new ODataSerializerContext() + { + Request = request, + }; + }; + + ODataSerializerProvider serializerProvider = request.GetRequestContainer().GetRequiredService(); + + ODataOutputFormatterHelper.WriteToStream( + type, + context.Object, + request.GetModel(), + ResultHelpers.GetODataResponseVersion(request), + baseAddress, + contentType, + new WebApiUrlHelper(request.GetUrlHelper()), + new WebApiRequestMessage(request), + new WebApiRequestHeaders(request.Headers), + (services) => ODataMessageWrapperHelper.Create(response.Body, response.Headers, services), + (edmType) => serializerProvider.GetEdmTypeSerializer(edmType), + (objectType) => serializerProvider.GetODataPayloadSerializer(objectType, request), + getODataSerializerContext); + + return TaskHelpers.Completed(); + } + catch (Exception ex) + { + return TaskHelpers.FromError(ex); + } + } + + /// + /// Internal method used for selecting the base address to be used with OData uris. + /// If the consumer has provided a delegate for overriding our default implementation, + /// we call that, otherwise we default to existing behavior below. + /// + /// The HttpRequest object for the given request. + /// The base address to be used as part of the service root; must terminate with a trailing '/'. + private Uri GetBaseAddressInternal(HttpRequest request) + { + if (BaseAddressFactory != null) + { + return BaseAddressFactory(request); + } + else + { + return ODataOutputFormatter.GetDefaultBaseAddress(request); + } + } + + /// + /// Returns a base address to be used in the service root when reading or writing OData uris. + /// + /// The HttpRequest object for the given request. + /// The base address to be used as part of the service root in the OData uri; must terminate with a trailing '/'. + public static Uri GetDefaultBaseAddress(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + string baseAddress = request.GetUrlHelper().CreateODataLink(); + if (baseAddress == null) + { + throw new SerializationException(SRResources.UnableToDetermineBaseUrl); + } + + return baseAddress[baseAddress.Length - 1] != '/' ? new Uri(baseAddress + '/') : new Uri(baseAddress); + } + + private MediaTypeHeaderValue GetContentType(string contentTypeValue) + { + MediaTypeHeaderValue contentType = null; + if (!string.IsNullOrEmpty(contentTypeValue)) + { + MediaTypeHeaderValue.TryParse(contentTypeValue, out contentType); + } + + return contentType; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterFactory.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterFactory.cs new file mode 100644 index 0000000..1e08605 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterFactory.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Factory for classes to handle OData. + /// + public static class ODataOutputFormatterFactory + { + private const string DollarFormat = "$format"; + + private const string JsonFormat = "json"; + + private const string XmlFormat = "xml"; + + /// + /// Creates a list of media type formatters to handle OData. + /// + /// A list of media type formatters to handle OData. + public static IList Create() + { + return new List() + { + // Place JSON formatter first so it gets used when the request doesn't ask for a specific content type + CreateApplicationJson(), + CreateApplicationXml(), + CreateRawValue() + }; + } + + private static void AddSupportedEncodings(ODataOutputFormatter formatter) + { + formatter.SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, + throwOnInvalidBytes: true)); + formatter.SupportedEncodings.Add(new UnicodeEncoding(bigEndian: false, byteOrderMark: true, + throwOnInvalidBytes: true)); + } + + private static ODataOutputFormatter CreateRawValue() + { + ODataOutputFormatter formatter = CreateFormatterWithoutMediaTypes(ODataPayloadKind.Value); + formatter.MediaTypeMappings.Add(new ODataPrimitiveValueMediaTypeMapping()); + formatter.MediaTypeMappings.Add(new ODataEnumValueMediaTypeMapping()); + formatter.MediaTypeMappings.Add(new ODataBinaryValueMediaTypeMapping()); + formatter.MediaTypeMappings.Add(new ODataCountMediaTypeMapping()); + return formatter; + } + + private static ODataOutputFormatter CreateApplicationJson() + { + ODataOutputFormatter formatter = CreateFormatterWithoutMediaTypes( + ODataPayloadKind.ResourceSet, + ODataPayloadKind.Resource, + ODataPayloadKind.Property, + ODataPayloadKind.EntityReferenceLink, + ODataPayloadKind.EntityReferenceLinks, + ODataPayloadKind.Collection, + ODataPayloadKind.ServiceDocument, + ODataPayloadKind.Error, + ODataPayloadKind.Parameter, + ODataPayloadKind.Delta); + + // Add minimal metadata as the first media type so it gets used when the request doesn't + // ask for a specific content type + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataMinimalMetadataStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataMinimalMetadataStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataMinimalMetadata); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataFullMetadataStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataFullMetadataStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataFullMetadata); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataNoMetadataStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataNoMetadataStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataNoMetadata); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonStreamingTrue); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonStreamingFalse); + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJson); + + formatter.AddDollarFormatQueryStringMappings(); + formatter.AddQueryStringMapping(DollarFormat, JsonFormat, ODataMediaTypes.ApplicationJson); + + return formatter; + } + + private static ODataOutputFormatter CreateApplicationXml() + { + ODataOutputFormatter formatter = CreateFormatterWithoutMediaTypes( + ODataPayloadKind.MetadataDocument); + + formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationXml); + + formatter.AddDollarFormatQueryStringMappings(); + formatter.AddQueryStringMapping(DollarFormat, XmlFormat, ODataMediaTypes.ApplicationXml); + + return formatter; + } + + private static ODataOutputFormatter CreateFormatterWithoutMediaTypes(params ODataPayloadKind[] payloadKinds) + { + ODataOutputFormatter formatter = new ODataOutputFormatter(payloadKinds); + AddSupportedEncodings(formatter); + return formatter; + } + + private static void AddDollarFormatQueryStringMappings(this ODataOutputFormatter formatter) + { + MediaTypeCollection supportedMediaTypes = formatter.SupportedMediaTypes; + foreach (string supportedMediaType in supportedMediaTypes) + { + QueryStringMediaTypeMapping mapping = new QueryStringMediaTypeMapping(DollarFormat, supportedMediaType); + formatter.MediaTypeMappings.Add(mapping); + } + } + + private static void AddQueryStringMapping(this ODataOutputFormatter formatter, string queryStringParameterName, + string queryStringParameterValue, string mediaType) + { + QueryStringMediaTypeMapping mapping = new QueryStringMediaTypeMapping(queryStringParameterName, queryStringParameterValue, mediaType); + formatter.MediaTypeMappings.Add(mapping); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataRawValueMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataRawValueMediaTypeMapping.cs new file mode 100644 index 0000000..71ce897 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/ODataRawValueMediaTypeMapping.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Media type mapping that associates requests for the raw value of properties. + /// + /// This class derives from a platform-specific class. + public abstract partial class ODataRawValueMediaTypeMapping : MediaTypeMapping + { + /// + public override double TryMatchMediaType(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + ODataPath odataPath = request.ODataFeature().Path; + return (IsRawValueRequest(odataPath) && IsMatch(GetProperty(odataPath))) ? 1 : 0; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/QueryStringMediaTypeMapping.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/QueryStringMediaTypeMapping.cs new file mode 100644 index 0000000..dd4bc13 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/QueryStringMediaTypeMapping.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNet.OData.Formatter +{ + /// + /// Class that provides s from query strings. + /// + /// This class derives from a platform-specific class. + public partial class QueryStringMediaTypeMapping : MediaTypeMapping + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the query string parameter to match, if present. + /// The value of the query string parameter to match, if present. + /// The media type to use if the query parameter specified by is present + /// and assigned the value specified by . + public QueryStringMediaTypeMapping(string queryStringParameterName, string queryStringParameterValue, string mediaType) + : base(mediaType) + { + if (queryStringParameterName == null) + { + throw Error.ArgumentNull("queryStringParameterName"); + } + + QueryStringParameterName = queryStringParameterName; + QueryStringParameterValue = queryStringParameterValue; + } + + /// + /// Gets the query string parameter value. + /// + public string QueryStringParameterValue { get; private set; } + + /// + public override double TryMatchMediaType(HttpRequest request) + { + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + double quality = 0; + + QueryString queryString = request.QueryString; + if (queryString.HasValue) + { + Dictionary parsedQuery = QueryHelpers.ParseNullableQuery(queryString.Value); + if (parsedQuery != null) + { + IDictionary queryValues =parsedQuery + .Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.FirstOrDefault())) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + quality = DoesQueryStringMatch(queryValues) ? 1 : 0; + if (quality < 1) + { + string queryValue = queryValues.Where(kvp => kvp.Key == QueryStringParameterName) + .FirstOrDefault() + .Value; + + quality = (!string.IsNullOrEmpty(queryValue) && (queryValue == QueryStringParameterValue)) ? 1 : 0; + } + } + } + + return quality; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs new file mode 100644 index 0000000..d17afba --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/DefaultODataSerializerProvider.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// The default . + /// + public partial class DefaultODataSerializerProvider : ODataSerializerProvider + { + /// + /// This signature uses types that are AspNetCore-specific. + public override ODataSerializer GetODataPayloadSerializer(Type type, HttpRequest request) + { + // Using a Func to delay evaluation of the model. + return GetODataPayloadSerializerImpl(type, () => request.GetModel(), request.ODataFeature().Path, typeof(SerializableError)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataErrorSerializer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataErrorSerializer.cs new file mode 100644 index 0000000..1fae777 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataErrorSerializer.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Represents an to serialize s. + /// + public partial class ODataErrorSerializer + { + /// + /// Return true of the object is an HttpError. + /// + /// The error to test. + /// true of the object is an HttpError + /// This function uses types that are AspNetCore-specific. + internal static bool IsHttpError(object error) + { + return error is SerializableError; + } + + /// + /// Create an ODataError from an HttpError. + /// + /// The error to use. + /// an ODataError. + /// This function uses types that are AspNetCore-specific. + internal static ODataError CreateODataError(object error) + { + SerializableError serializableError = error as SerializableError; + return serializableError.CreateODataError(); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs new file mode 100644 index 0000000..21bf0e6 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// Context information used by the when serializing objects in OData message format. + /// + public partial class ODataSerializerContext + { + private HttpRequest _request; + private IUrlHelper _urlHelper; + + /// + /// Gets or sets the HTTP Request whose response is being serialized. + /// + /// This signature uses types that are AspNetCore-specific. + public HttpRequest Request + { + get + { + return _request; + } + set + { + _request = value; + InternalRequest = _request != null ? new WebApiRequestMessage(_request) : null; + Url = _request != null ? _request.GetUrlHelper() : null; + } + } + + /// + /// Clone this instance of from an existing instance. + /// + /// + /// This function uses types that are AspNetCore-specific. + private void CopyPlatformSpecificProperties(ODataSerializerContext context) + { + Request = context.Request; + } + + /// + /// Gets or sets the to use for generating OData links. + /// + public IUrlHelper Url + { + get + { + return _urlHelper; + } + set + { + _urlHelper = value; + InternalUrlHelper = value != null ? new WebApiUrlHelper(value) : null; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs new file mode 100644 index 0000000..4047ad9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData.Formatter.Serialization +{ + /// + /// An ODataSerializerProvider is a factory for creating s. + /// + public abstract partial class ODataSerializerProvider + { + /// + /// Gets an for the given . + /// + /// The for which the serializer is being requested. + /// The request for which the response is being serialized. + /// The for the given type. + /// This signature uses types that are AspNetCore-specific. + public abstract ODataSerializer GetODataPayloadSerializer(Type type, HttpRequest request); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/FromODataUriAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/FromODataUriAttribute.cs new file mode 100644 index 0000000..664f9b9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/FromODataUriAttribute.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNet.OData +{ + /// + /// An implementation of that can bind URI parameters using OData conventions. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] + public sealed class FromODataUriAttribute : ModelBinderAttribute + { + /// + /// Instantiates a new instance of the class. + /// + public FromODataUriAttribute() + : base(typeof(ODataModelBinder)) + { + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/GetNextPageHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/GetNextPageHelper.cs new file mode 100644 index 0000000..4520b25 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/GetNextPageHelper.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNet.OData +{ + /// + /// Helper to generate next page links. + /// + internal static partial class GetNextPageHelper + { + /// This signature uses types that are AspNetCore-specific. + internal static Uri GetNextPageLink(Uri requestUri, int pageSize, object instance = null, Func objectToSkipTokenValue = null) + { + Contract.Assert(requestUri != null); + + Dictionary queryValues = QueryHelpers.ParseQuery(requestUri.Query); + IEnumerable> queryParameters = queryValues.SelectMany( + kvp => kvp.Value, (kvp, value) => new KeyValuePair(kvp.Key, value)); + + return GetNextPageLink(requestUri, queryParameters, pageSize, instance, objectToSkipTokenValue); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/HttpRequestScope.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/HttpRequestScope.cs new file mode 100644 index 0000000..85b1ca8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/HttpRequestScope.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData +{ + /// + /// Provides access to the + /// to which the OData service container instance is scoped. + /// + public class HttpRequestScope + { + /// + /// Provides access to the + /// to which the OData service container instance is scoped. + /// + public HttpRequest HttpRequest { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataBatchFeature.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataBatchFeature.cs new file mode 100644 index 0000000..caea855 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataBatchFeature.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Provide the interface for the details of a given OData batch request. + /// + public interface IODataBatchFeature + { + /// + /// Gets or sets the batch id. + /// + Guid? BatchId { get; set; } + + /// + /// Gets or sets the change set id. + /// + Guid? ChangeSetId { get; set; } + + /// + /// Gets or sets the content id. + /// + string ContentId { get; set; } + + /// + /// Gets or sets the content id mapping. + /// + IDictionary ContentIdMapping { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataCoreBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataCoreBuilder.cs new file mode 100644 index 0000000..46a840b --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataCoreBuilder.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// An interface for configuring essential OData services. + /// + public interface IODataBuilder + { + /// + /// Gets the where essential OData services are configured. + /// + IServiceCollection Services { get; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataFeature.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataFeature.cs new file mode 100644 index 0000000..5e4176c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Interfaces/IODataFeature.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; +using IODataRoutingConvention = Microsoft.AspNet.OData.Routing.Conventions.IODataRoutingConvention; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Interfaces +{ + /// + /// Provide the interface for the details of a given OData request. + /// + public interface IODataFeature + { + /// + /// Gets or sets the OData path. + /// + ODataPath Path { get; set; } + + /// + /// Gets or sets the route name. + /// + string RouteName { get; set; } + + /// + /// Gets or sets the request scope. + /// + IServiceScope RequestScope { get; set; } + + /// + /// Gets or sets the request container. + /// + IServiceProvider RequestContainer { get; set; } + + /// + /// Gets or sets the next link for the OData response. + /// + Uri NextLink { get; set; } + + /// + /// Gets or sets the batch route data. + /// + RouteValueDictionary BatchRouteData { get; set; } + + /// + /// Gets or sets the delta link for the OData response. + /// + Uri DeltaLink { get; set; } + + /// + /// Gets or sets the Url helper. + /// + IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the total count for the OData response. + /// + /// null if no count should be sent back to the client. + long? TotalCount { get; set; } + + /// + /// Gets or sets the total count function for the OData response. + /// + Func TotalCountFunc { get; set; } + + /// + /// Gets or sets the parsed OData of the request. + /// + ApplyClause ApplyClause { get; set; } + + /// + /// Gets or sets the parsed OData of the request. + /// + SelectExpandClause SelectExpandClause { get; set; } + + /// + /// Gets the data store used by s to store any custom route data. + /// + /// Initially an empty IDictionary<string, object>. + IDictionary RoutingConventionsStore { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Nightly.Release.nuspec b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Nightly.Release.nuspec new file mode 100644 index 0000000..99d69ce --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Nightly.Release.nuspec @@ -0,0 +1,32 @@ + + + + Microsoft.AspNetCore.OData + Microsoft ASP.NET Core 2.0 for OData v4.0 + $VersionFullSemantic$-Nightly$NightlyBuildVersion$ + Microsoft + © Microsoft Corporation. All rights reserved. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Core MVC and to support OData query syntax for your web APIs. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Core MVC. + en-US + http://odata.github.io + https://raw.githubusercontent.com/OData/WebApi/master/License.txt + true + Microsoft AspNetCore WebApi OData + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Release.nuspec b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Release.nuspec new file mode 100644 index 0000000..7214db4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.Release.nuspec @@ -0,0 +1,32 @@ + + + + Microsoft.AspNetCore.OData + Microsoft ASP.NET Core 2.0 for OData v4.0 + $VersionNuGetSemantic$ + Microsoft + © Microsoft Corporation. All rights reserved. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Core MVC and to support OData query syntax for your web APIs. + This package contains everything you need to create OData v4.0 endpoints using ASP.NET Core MVC. + en-US + http://odata.github.io + https://raw.githubusercontent.com/OData/WebApi/master/License.txt + true + Microsoft AspNetCore WebApi OData + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj new file mode 100644 index 0000000..87ad085 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj @@ -0,0 +1,67 @@ + + + netstandard2.0 + + + + $(OutputPath)$(AssemblyName).xml + ..\Strict.ruleset + $(DefineConstants);ASPNETODATA;ASPNETWEBAPI;NETCORE;NETCORE2x;NOT_CLS_COMPLIANT + Microsoft.AspNet.OData + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + Properties\CommonAssemblyInfo.cs + + + + + + Properties\CommonWebApiResources.resx + ResXFileCodeGenerator + Designer + + + Properties\SRResources.resx + ResXFileCodeGenerator + Designer + + + Properties\CommonWebApiResources.Designer.cs + True + True + CommonWebApiResources.resx + + + Properties\SRResources.Designer.cs + True + True + SRResources.resx + + + diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/NonValidatingParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/NonValidatingParameterBindingAttribute.cs new file mode 100644 index 0000000..a72736d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/NonValidatingParameterBindingAttribute.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNet.OData +{ + /// + /// An attribute to disable WebApi model validation for a particular type. + /// + /// + /// This is essentially a . + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + internal sealed partial class NonValidatingParameterBindingAttribute : ModelBinderAttribute, IPropertyValidationFilter + { + /// + public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEntry) + { + return false; + } + + /// + public override BindingSource BindingSource + { + get + { + return BindingSource.Body; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataBatchFeature.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataBatchFeature.cs new file mode 100644 index 0000000..215b305 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataBatchFeature.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Interfaces; + +namespace Microsoft.AspNet.OData +{ + /// + /// Provide the interface for the details of a given OData batch request. + /// + public class ODataBatchFeature : IODataBatchFeature + { + /// + /// Gets or sets the batch id. + /// + public Guid? BatchId { get; set; } + + /// + /// Gets or sets the change set id. + /// + public Guid? ChangeSetId { get; set; } + + /// + /// Gets or sets the content id. + /// + public string ContentId { get; set; } + + /// + /// Gets or sets the content id mapping. + /// + public IDictionary ContentIdMapping { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataController.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataController.cs new file mode 100644 index 0000000..7147fda --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataController.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Results; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNet.OData +{ + /// + /// Defines a base class for OData controllers that support writing and reading data using the OData formats. + /// + /// These attributes and signature uses types that are AspNetCore-specific. + [ODataFormatting] + [ODataRouting] + [ApiExplorerSettings(IgnoreApi = true)] + public abstract partial class ODataController : ControllerBase + { + /// + /// Creates an action result with the specified values that is a response to a POST operation with an entity + /// to an entity set. + /// + /// The created entity type. + /// The created entity. + /// A with the specified values. + /// These function uses types that are AspNetCore-specific. + protected virtual CreatedODataResult Created(TEntity entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + return new CreatedODataResult(entity); + } + + /// + /// Creates an action result with the specified values that is a response to a PUT, PATCH, or a MERGE operation + /// on an OData entity. + /// + /// The updated entity type. + /// The updated entity. + /// An with the specified values. + /// These function uses types that are AspNetCore-specific. + protected virtual UpdatedODataResult Updated(TEntity entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + return new UpdatedODataResult(entity); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFeature.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFeature.cs new file mode 100644 index 0000000..2d7a70c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFeature.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; +using IODataRoutingConvention = Microsoft.AspNet.OData.Routing.Conventions.IODataRoutingConvention; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData +{ + /// + /// Contains the details of a given OData request. These properties should all be mutable. + /// None of these properties should ever be set to null. + /// + public class ODataFeature : IODataFeature, IDisposable + { + internal const string ODataServiceVersionHeader = "OData-Version"; + internal const ODataVersion DefaultODataVersion = ODataVersion.V4; + + private long? totalCount; + private bool totalCountSet; + + /// + /// Instantiates a new instance of the class. + /// + public ODataFeature() + { + totalCountSet = false; + } + + /// + /// Gets or sets the OData path. + /// + public ODataPath Path { get; set; } + + /// + /// Gets or sets the route name. + /// + public string RouteName { get; set; } + + /// + /// Gets or sets the request scope. + /// + public IServiceScope RequestScope { get; set; } + + /// + /// Gets or sets the request container. + /// + public IServiceProvider RequestContainer { get; set; } + + /// + /// Gets or sets the next link for the OData response. + /// + public Uri NextLink { get; set; } + + /// + /// Gets or sets the batch route data. + /// + public RouteValueDictionary BatchRouteData { get; set; } + + /// + /// Gets or sets the delta link for the OData response. + /// + public Uri DeltaLink { get; set; } + + /// + /// Gets or sets the Url helper. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the total count for the OData response. + /// + /// null if no count should be sent back to the client. + public long? TotalCount + { + get + { + if (this.totalCountSet) + { + return this.totalCount; + } + + if (this.TotalCountFunc != null) + { + this.totalCount = this.TotalCountFunc(); + this.totalCountSet = true; + return this.totalCount; + } + + return null; + } + + set + { + this.totalCount = value; + this.totalCountSet = value.HasValue; + } + } + + /// + /// Gets or sets the total count function for the OData response. + /// + public Func TotalCountFunc { get; set; } + + /// + /// Gets or sets the parsed OData of the request. + /// + public ApplyClause ApplyClause { get; set; } + + /// + /// Gets or sets the parsed OData of the request. + /// + public SelectExpandClause SelectExpandClause { get; set; } + + /// + /// Gets the data store used by s to store any custom route data. + /// + /// Initially an empty IDictionary<string, object>. + public IDictionary RoutingConventionsStore { get; set; } = new Dictionary(); + + /// + /// Gets or sets the parsed of the request. + /// + internal ODataQueryOptions QueryOptions { get; set; } + + /// + /// Page size to be used by skiptoken implementation for the top-level resource for the request. + /// + internal int PageSize { get; set; } + + /// + public void Dispose() + { + Dispose(true); + } + + /// + protected void Dispose(bool disposing) + { + if (disposing) + { + RequestScope?.Dispose(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFormattingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFormattingAttribute.cs new file mode 100644 index 0000000..2e4dedf --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataFormattingAttribute.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNet.OData +{ + /// + /// An attribute to be placed on controllers that enables the OData formatters. + /// + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want to be able to subclass this type.")] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class ODataFormattingAttribute : Attribute + { + // This class is not needed; Formatters are injected in ODataServiceCollectionExtensions::AddOdata() + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataMessageWrapperHelper.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataMessageWrapperHelper.cs new file mode 100644 index 0000000..5b9c2b5 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataMessageWrapperHelper.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData +{ + internal static class ODataMessageWrapperHelper + { + internal static ODataMessageWrapper Create(Stream stream, IHeaderDictionary headers) + { + return ODataMessageWrapperHelper.Create(stream, headers, contentIdMapping: null); + } + + internal static ODataMessageWrapper Create(Stream stream, IHeaderDictionary headers, IServiceProvider container) + { + return ODataMessageWrapperHelper.Create(stream, headers, null, container); + } + + internal static ODataMessageWrapper Create(Stream stream, IHeaderDictionary headers, IDictionary contentIdMapping, IServiceProvider container) + { + ODataMessageWrapper responseMessageWrapper = ODataMessageWrapperHelper.Create(stream, headers, contentIdMapping); + responseMessageWrapper.Container = container; + + return responseMessageWrapper; + } + + internal static ODataMessageWrapper Create(Stream stream, IHeaderDictionary headers, IDictionary contentIdMapping) + { + return new ODataMessageWrapper( + stream, + headers.ToDictionary(kvp => kvp.Key, kvp => String.Join(";", kvp.Value)), + contentIdMapping); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataNullValueMessageHandler.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataNullValueMessageHandler.cs new file mode 100644 index 0000000..6cfacdc --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataNullValueMessageHandler.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using System.Net.Http; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an that converts null values in OData responses to + /// HTTP NotFound responses or NoContent responses following the OData specification. + /// + public partial class ODataNullValueMessageHandler : IResultFilter + { + /// + public void OnResultExecuting(ResultExecutingContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + HttpRequest request = context.HttpContext.Request; + HttpResponse response = context.HttpContext.Response; + + // Only operate on OData requests. + if (request.ODataFeature().Path != null) + { + // This message handler is intended for helping with queries that return a null value, for example in a + // get request for a particular entity on an entity set, for a single valued navigation property or for + // a structural property of a given entity. The only case in which a data modification request will result + // in a 204 response status code, is when a primitive property is set to null through a PUT request to the + // property URL and in that case, the user can return the right status code himself. + ObjectResult objectResult = context.Result as ObjectResult; + if (request.Method == HttpMethod.Get.ToString() && objectResult != null && objectResult.Value == null && + response.StatusCode == (int)HttpStatusCode.OK) + { + HttpStatusCode? newStatusCode = GetUpdatedResponseStatusCodeOrNull(request.ODataFeature().Path); + if (newStatusCode.HasValue) + { + response.StatusCode = (int)newStatusCode.Value; + } + } + } + } + + /// + public void OnResultExecuted(ResultExecutedContext context) + { + // Can't add to headers here because response has already begun. + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataOptions.cs new file mode 100644 index 0000000..366c5e8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataOptions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData; + +namespace Microsoft.AspNet.OData +{ + /// + /// Provides programmatic configuration for the OData service. + /// + public class ODataOptions + { + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not in DefaultODataPathHandler. + /// + /// Default value is unspecified (null). + public ODataUrlKeyDelimiter UrlKeyDelimiter { get; set; } + + /// + /// Gets or Sets a value indicating if value should be emitted for dynamic properties which are null. + /// + public bool NullDynamicPropertyIsEnabled { get; set; } + + /// + /// Gets or Sets a value indicating if batch requests should continue on error. + /// + public bool EnableContinueOnErrorHeader { get; set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataQueryParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataQueryParameterBindingAttribute.cs new file mode 100644 index 0000000..7654037 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataQueryParameterBindingAttribute.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// A to bind parameters of type to the OData query from the incoming request. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] + public sealed partial class ODataQueryParameterBindingAttribute : ModelBinderAttribute + { + /// + /// Instantiates a new instance of the class. + /// + public ODataQueryParameterBindingAttribute() + :base(typeof(ODataQueryParameterBinding)) + { + } + + internal class ODataQueryParameterBinding : IModelBinder + { + private static MethodInfo _createODataQueryOptions = typeof(ODataQueryParameterBinding).GetMethod("CreateODataQueryOptions"); + private const string CreateODataQueryOptionsCtorKey = "MS_CreateODataQueryOptionsOfT"; + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't want to fail in model binding.")] + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw Error.ArgumentNull("bindingContext"); + } + + HttpRequest request = bindingContext.HttpContext.Request; + + if (request == null) + { + throw Error.Argument("actionContext", SRResources.ActionContextMustHaveRequest); + } + + ActionDescriptor actionDescriptor = bindingContext.ActionContext.ActionDescriptor; + + if (actionDescriptor == null) + { + throw Error.Argument("actionContext", SRResources.ActionContextMustHaveDescriptor); + } + + + // Get the parameter description of the parameter to bind. + ParameterDescriptor paramDescriptor = bindingContext.ActionContext.ActionDescriptor.Parameters + .Where(p => p.Name == bindingContext.FieldName) + .FirstOrDefault(); + + // Now make sure parameter type is ODataQueryOptions or ODataQueryOptions. + Type parameterType = paramDescriptor?.ParameterType; + if (IsODataQueryOptions(parameterType)) + { + + // Get the entity type from the parameter type if it is ODataQueryOptions. + // Fall back to the return type if not. Also, note that the entity type from the return type and ODataQueryOptions + // can be different (example implementing $select or $expand). + Type entityClrType = null; + if (paramDescriptor != null) + { + entityClrType = GetEntityClrTypeFromParameterType(parameterType); + } + + if (entityClrType == null) + { + entityClrType = GetEntityClrTypeFromActionReturnType(actionDescriptor); + } + + IEdmModel userModel = request.GetModel(); + IEdmModel model = userModel != EdmCoreModel.Instance ? userModel : actionDescriptor.GetEdmModel(request, entityClrType); + ODataQueryContext entitySetContext = new ODataQueryContext(model, entityClrType, request.ODataFeature().Path); + + Func createODataQueryOptions; + object constructorAsObject = null; + if (actionDescriptor.Properties.TryGetValue(CreateODataQueryOptionsCtorKey, out constructorAsObject)) + { + createODataQueryOptions = (Func)constructorAsObject; + } + else + { + createODataQueryOptions = (Func) + Delegate.CreateDelegate(typeof(Func), + _createODataQueryOptions.MakeGenericMethod(entityClrType)); + }; + + ODataQueryOptions parameterValue = createODataQueryOptions(entitySetContext, request); + bindingContext.Result = ModelBindingResult.Success(parameterValue); + } + + return TaskHelpers.Completed(); + } + + public static bool IsODataQueryOptions(Type parameterType) + { + if (parameterType == null) + { + return false; + } + return ((parameterType == typeof(ODataQueryOptions)) || + (parameterType.IsGenericType && + parameterType.GetGenericTypeDefinition() == typeof(ODataQueryOptions<>))); + } + + public static ODataQueryOptions CreateODataQueryOptions(ODataQueryContext context, HttpRequest request) + { + return new ODataQueryOptions(context, request); + } + + internal static Type GetEntityClrTypeFromActionReturnType(ActionDescriptor actionDescriptor) + { + // It is a developer programming error to use this binding attribute + // on actions that return void. + ControllerActionDescriptor controllerActionDescriptor = actionDescriptor as ControllerActionDescriptor; + if (controllerActionDescriptor == null) + { + throw Error.InvalidOperation( + SRResources.FailedToBuildEdmModelBecauseReturnTypeIsNull, + actionDescriptor.DisplayName, + actionDescriptor.ToString()); + } + + if (controllerActionDescriptor.MethodInfo.ReturnType == null) + { + throw Error.InvalidOperation( + SRResources.FailedToBuildEdmModelBecauseReturnTypeIsNull, + controllerActionDescriptor.ActionName, + controllerActionDescriptor.ControllerName); + } + + Type entityClrType = TypeHelper.GetImplementedIEnumerableType(controllerActionDescriptor.MethodInfo.ReturnType); + + if (entityClrType == null) + { + // It is a developer programming error to use this binding attribute + // on actions that return a collection whose element type cannot be + // determined, such as a non-generic IQueryable or IEnumerable. + throw Error.InvalidOperation( + SRResources.FailedToRetrieveTypeToBuildEdmModel, + controllerActionDescriptor.ActionName, + controllerActionDescriptor.ControllerName, + controllerActionDescriptor.MethodInfo.ReturnType.FullName); + } + + return entityClrType; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataRoutingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataRoutingAttribute.cs new file mode 100644 index 0000000..d2de840 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataRoutingAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.OData +{ + /// + /// Defines a controller-level attribute that can be used to enable OData action selection based on routing conventions. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class ODataRoutingAttribute : Attribute + { + // This class is not needed; Routing is injected in ODataServiceCollectionExtensions::AddOdata() + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataServiceBuilder.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataServiceBuilder.cs new file mode 100644 index 0000000..641b289 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataServiceBuilder.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.OData +{ + /// + /// Allows fine grained configuration of essential OData services. + /// + public class ODataBuilder : IODataBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// The service collection. + public ODataBuilder(IServiceCollection serviceCollection) + { + if (serviceCollection == null) + { + throw Error.ArgumentNull("serviceCollection"); + } + + this.Services = serviceCollection; + } + + /// + /// Gets the services collection. + /// + public IServiceCollection Services { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataValueProviderFactory.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataValueProviderFactory.cs new file mode 100644 index 0000000..a5c5f11 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ODataValueProviderFactory.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNet.OData.Routing +{ + internal class ODataValueProviderFactory : IValueProviderFactory + { + public Task CreateValueProviderAsync(ValueProviderFactoryContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + ODataValueProvider valueProvider = new ODataValueProvider( + context.ActionContext.HttpContext.Request.ODataFeature().RoutingConventionsStore); + + context.ValueProviders.Add(valueProvider); + + return Task.CompletedTask; + } + + private class ODataValueProvider : BindingSourceValueProvider + { + private IDictionary values; + private PrefixContainer _prefixContainer; + + public ODataValueProvider(IDictionary values) + : base(BindingSource.Path) + { + this.values = values; + } + + /// + public override bool ContainsPrefix(string key) + { + if (_prefixContainer == null) + { + _prefixContainer = new PrefixContainer(values.Keys); + } + + return _prefixContainer.ContainsPrefix(key); + } + + /// + public override ValueProviderResult GetValue(string key) + { + if (key == null) + { + throw Error.ArgumentNull("key"); + } + + object value; + if (values.TryGetValue(key, out value)) + { + var stringValue = value as string ?? value?.ToString() ?? string.Empty; + return new ValueProviderResult(stringValue); + } + else + { + return ValueProviderResult.None; + } + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/PerRouteContainer.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/PerRouteContainer.cs new file mode 100644 index 0000000..c6930ca --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/PerRouteContainer.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Formatter.Serialization; + +namespace Microsoft.AspNet.OData +{ + /// + /// A class for managing per-route service containers. + /// + public class PerRouteContainer : PerRouteContainerBase + { + private ConcurrentDictionary _perRouteContainers; + private IServiceProvider _nonODataRouteContainer; + + /// + /// Initializes a new instance of the class. + /// + public PerRouteContainer() + { + this._perRouteContainers = new ConcurrentDictionary(); + } + + /// + /// Gets the container for a given route name. + /// + /// The route name. + /// The root container for the route name. + protected override IServiceProvider GetContainer(string routeName) + { + if (String.IsNullOrEmpty(routeName)) + { + return _nonODataRouteContainer; + } + + IServiceProvider rootContainer; + if (_perRouteContainers.TryGetValue(routeName, out rootContainer)) + { + return rootContainer; + } + + return null; + } + + /// + /// Sets the container for a given route name. + /// + /// The route name. + /// The root container to set. + /// Used by unit tests to insert root containers. + protected override void SetContainer(string routeName, IServiceProvider rootContainer) + { + if (rootContainer == null) + { + throw Error.InvalidOperation(SRResources.NullContainer); + } + + if (String.IsNullOrEmpty(routeName)) + { + _nonODataRouteContainer = rootContainer; + } + else + { + this._perRouteContainers.AddOrUpdate(routeName, rootContainer, (k, v) => rootContainer); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Properties/AssemblyInfo.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..70c5223 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("Microsoft.AspNetCore.OData")] +[assembly: AssemblyDescription("")] + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.OData.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs new file mode 100644 index 0000000..bb23365 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a composite OData query options that can be used to perform query composition. + /// Currently this only supports $filter, $orderby, $top, $skip, and $count. + /// + [NonValidatingParameterBinding] + public partial class ODataQueryOptions + { + /// + /// Initializes a new instance of the class based on the incoming request and some metadata information from + /// the . + /// + /// The which contains the and some type information. + /// The incoming request message. + /// + /// This signature uses types that are AspNetCore-specific. + /// While the AspNet version of this class makes the HttpRequest available, AspNetCore + /// is unhappy when it sees the HttpRequest during validation so HttpRequest is not part + /// of the public Api for ODataQueryOptions. + /// + public ODataQueryOptions(ODataQueryContext context, HttpRequest request) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + if (request == null) + { + throw Error.ArgumentNull("request"); + } + + Contract.Assert(context.RequestContainer == null); + context.RequestContainer = request.GetRequestContainer(); + + Context = context; + Request = request; + InternalRequest = new WebApiRequestMessage(request); + InternalHeaders = new WebApiRequestHeaders(request.Headers); + + Initialize(context); + } + + /// + /// Gets the request message associated with this instance. + /// + /// This signature uses types that are AspNetCore-specific. + public HttpRequest Request { get; private set; } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptionsOfTEntity.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptionsOfTEntity.cs new file mode 100644 index 0000000..b086480 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptionsOfTEntity.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// This defines a composite OData query options that can be used to perform query composition. + /// Currently this only supports $filter, $orderby, $top, $skip. + /// + public partial class ODataQueryOptions : ODataQueryOptions + { + /// + /// Initializes a new instance of the class based on the incoming request and some metadata information from + /// the . + /// + /// The which contains the and some type information + /// The incoming request message + /// This signature uses types that are AspNetCore-specific. + public ODataQueryOptions(ODataQueryContext context, HttpRequest request) + : base(context, request) + { + if (Context.ElementClrType == null) + { + throw Error.Argument("context", SRResources.ElementClrTypeNull, typeof(ODataQueryContext).Name); + } + + if (context.ElementClrType != typeof(TEntity)) + { + throw Error.Argument("context", SRResources.EntityTypeMismatch, context.ElementClrType.FullName, typeof(TEntity).FullName); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/QueryFilterProvider.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/QueryFilterProvider.cs new file mode 100644 index 0000000..0d48649 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Query/QueryFilterProvider.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNet.OData.Query +{ + /// + /// An implementation of that applies an action filter to + /// any action with an or return type + /// that doesn't bind a parameter of type . + /// + public class QueryFilterProvider : IFilterProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The action filter that executes the query. + public QueryFilterProvider(IActionFilter queryFilter) + { + if (queryFilter == null) + { + throw Error.ArgumentNull("queryFilter"); + } + + QueryFilter = queryFilter; + } + + /// + /// Gets the action filter that executes the query. + /// + public IActionFilter QueryFilter { get; private set; } + + /// + /// Gets the order value for determining the order of execution of providers. Providers + /// execute in ascending numeric value of the Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order + /// property. + /// + public int Order + { + get + { + // Providers are executed in an ordering determined by an ascending sort of the + // Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order property. A provider with + // a lower numeric value of Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order + // will have its Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.OnProvidersExecuting(Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext) + // called before that of a provider with a higher numeric value of Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order. + // The Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.OnProvidersExecuted(Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext) + // method is called in the reverse ordering after all calls to Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.OnProvidersExecuting(Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext). + // A provider with a lower numeric value of Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order + // will have its Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.OnProvidersExecuted(Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext) + // method called after that of a provider with a higher numeric value of Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order. + // If two providers have the same numeric value of Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order, + // then their relative execution order is undefined. + return 0; + } + } + + /// + /// Provides filters to apply to the specified action. + /// + /// The filter context. + public void OnProvidersExecuting(FilterProviderContext context) + { + // Actions with a bound parameter of type ODataQueryOptions do not support the query filter + // The assumption is that the action will handle the querying within the action implementation + ControllerActionDescriptor controllerActionDescriptor = context.ActionContext.ActionDescriptor as ControllerActionDescriptor; + if (controllerActionDescriptor != null) + { + Type returnType = controllerActionDescriptor.MethodInfo.ReturnType; + if (ShouldAddFilter(context, returnType, controllerActionDescriptor)) + { + var filterDesc = new FilterDescriptor(QueryFilter, FilterScope.Global); + context.Results.Add(new FilterItem(filterDesc, QueryFilter)); + } + } + } + + /// + /// Summary: + /// Called in decreasing Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order, + /// after all Microsoft.AspNetCore.Mvc.Filters.IFilterProviders have executed once. + /// + /// The Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext. + public void OnProvidersExecuted(FilterProviderContext context) + { + } + + private bool ShouldAddFilter(FilterProviderContext context, Type returnType, ControllerActionDescriptor controllerActionDescriptor) + { + // Get the inner return type if type is a task. + Type innerReturnType = returnType; + if (TypeHelper.IsGenericType(returnType) && returnType.GetGenericTypeDefinition() == typeof(Task<>)) + { + innerReturnType = returnType.GetGenericArguments().First(); + } + + // See if this type is a SingleResult or is derived from SingleResult. + bool isSingleResult = false; + if (innerReturnType.IsGenericType) + { + Type genericType = innerReturnType.GetGenericTypeDefinition(); + Type baseType = TypeHelper.GetBaseType(innerReturnType); + isSingleResult = (genericType == typeof(SingleResult<>) || baseType == typeof(SingleResult)); + } + + // Don't apply the filter if the result is not IQueryable() or SingleReult(). + if (!TypeHelper.IsIQueryable(innerReturnType) && !isSingleResult) + { + return false; + } + + // If the controller takes a ODataQueryOptions, don't apply the filter. + if (controllerActionDescriptor.Parameters + .Any(parameter => TypeHelper.IsTypeAssignableFrom(typeof(ODataQueryOptions), parameter.ParameterType))) + { + return false; + } + + // Don't apply a global filter if one of the same type exists. + if (context.Results.Where(f => f.Filter?.GetType() == QueryFilter.GetType()).Any()) + { + return false; + } + + return true; + } +} +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceContext.cs new file mode 100644 index 0000000..d54d72a --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceContext.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNet.OData +{ + /// + /// An instance of gets passed to the self link ( + /// , + /// , + /// + /// ) and navigation link ( + /// , + /// + /// ) builders and can be used by the link builders to generate links. + /// + public partial class ResourceContext + { + /// + /// Gets or sets the HTTP request that caused this instance to be generated. + /// + /// This signature uses types that are AspNetCore-specific. + public HttpRequest Request + { + get + { + return SerializerContext.Request; + } + set + { + SerializerContext.Request = value; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceSetContext.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceSetContext.cs new file mode 100644 index 0000000..d7a9913 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/ResourceSetContext.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNet.OData +{ + /// + /// Contains context information about the resource set currently being serialized. + /// + public partial class ResourceSetContext + { + private HttpRequest _request; + private IUrlHelper _urlHelper; + + /// + /// Gets or sets the HTTP request that caused this instance to be generated. + /// + /// This signature uses types that are AspNetCore-specific. + public HttpRequest Request + { + get + { + return _request; + } + set + { + _request = value; + InternalRequest = _request != null ? new WebApiRequestMessage(_request) : null; + Url = _request != null ? Request.GetUrlHelper() : null; + } + } + + /// + /// Gets or sets the to which this instance belongs. + /// + /// This signature uses types that are AspNetCore-specific. + public IEdmModel EdmModel + { + get { return Request.GetModel(); } + } + + /// + /// Gets or sets the to use for generating OData links. + /// + public IUrlHelper Url + { + get + { + return _urlHelper; + } + set + { + _urlHelper = value; + InternalUrlHelper = value != null ? new WebApiUrlHelper(value) : null; + } + } + + /// + /// Create a from an and . + /// + /// The instance representing the resourceSet being written. + /// The serializer context. + /// A new . + /// This signature uses types that are AspNetCore-specific. + internal static ResourceSetContext Create(ODataSerializerContext writeContext, IEnumerable resourceSetInstance) + { + ResourceSetContext resourceSetContext = new ResourceSetContext + { + Request = writeContext.Request, + EntitySetBase = writeContext.NavigationSource as IEdmEntitySetBase, + Url = writeContext.Url, + ResourceSetInstance = resourceSetInstance + }; + + return resourceSetContext; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/CreatedODataResult.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/CreatedODataResult.cs new file mode 100644 index 0000000..e6e25b2 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/CreatedODataResult.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Results +{ + /// + /// Represents an action result that is a response to a create operation that adds an entity to an entity set. + /// + /// The entity type. + /// This action result handles content negotiation and the HTTP prefer header. It generates a location + /// header containing the edit link of the created entity and, if response has status code: NoContent, also + /// generates an OData-EntityId header. + public class CreatedODataResult : IActionResult + { + private readonly T _innerResult; + + /// + /// Initializes a new instance of the class. + /// + /// The created entity. + public CreatedODataResult(T entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + this._innerResult = entity; + } + + /// + public async virtual Task ExecuteResultAsync(ActionContext context) + { + HttpRequest request = context.HttpContext.Request; + HttpResponse response = context.HttpContext.Response; + IActionResult result = GetInnerActionResult(request); + response.Headers["Location"] = GenerateLocationHeader(request).ToString(); + + // Since AddEntityId relies on the response, make sure to execute the result + // before calling AddEntityId() to ensure the response code is set correctly. + await result.ExecuteResultAsync(context); + ResultHelpers.AddEntityId(response, () => GenerateEntityId(request)); + ResultHelpers.AddServiceVersion(response, () => ODataUtils.ODataVersionToString(ResultHelpers.GetODataResponseVersion(request))); + } + + // internal just for unit test. + internal IActionResult GetInnerActionResult(HttpRequest request) + { + if (RequestPreferenceHelpers.RequestPrefersReturnNoContent(new WebApiRequestHeaders(request.Headers))) + { + return new StatusCodeResult((int)HttpStatusCode.NoContent); + } + else + { + ObjectResult objectResult = new ObjectResult(_innerResult) + { + StatusCode = StatusCodes.Status201Created + }; + + return objectResult; + } + } + + // internal just for unit test. + internal Uri GenerateEntityId(HttpRequest request) + { + return ResultHelpers.GenerateODataLink(request, _innerResult, isEntityId: true); + } + + // internal just for unit test. + internal Uri GenerateLocationHeader(HttpRequest request) + { + return ResultHelpers.GenerateODataLink(request, _innerResult, isEntityId: false); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs new file mode 100644 index 0000000..1384d9f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.Net; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Formatter; +using Microsoft.AspNet.OData.Formatter.Serialization; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.OData; +using Microsoft.OData.Edm; +using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; + +namespace Microsoft.AspNet.OData.Results +{ + internal static partial class ResultHelpers + { + /// This signature uses types that are AspNetCore-specific. + public static Uri GenerateODataLink(HttpRequest request, object entity, bool isEntityId) + { + IEdmModel model = request.GetModel(); + if (model == null) + { + throw new InvalidOperationException(SRResources.RequestMustHaveModel); + } + + ODataPath path = request.ODataFeature().Path; + if (path == null) + { + throw new InvalidOperationException(SRResources.ODataPathMissing); + } + + IEdmNavigationSource navigationSource = path.NavigationSource; + if (navigationSource == null) + { + throw new InvalidOperationException(SRResources.NavigationSourceMissingDuringSerialization); + } + + ODataSerializerContext serializerContext = new ODataSerializerContext + { + NavigationSource = navigationSource, + Model = model, + MetadataLevel = ODataMetadataLevel.FullMetadata, // Used internally to always calculate the links. + Request = request, + Path = path + }; + + IEdmEntityTypeReference entityType = GetEntityType(model, entity); + ResourceContext resourceContext = new ResourceContext(serializerContext, entityType, entity); + + return GenerateODataLink(resourceContext, isEntityId); + } + + /// This signature uses types that are AspNetCore-specific. + public static void AddEntityId(HttpResponse response, Func entityId) + { + if (response.StatusCode == (int)HttpStatusCode.NoContent) + { + response.Headers.Add(EntityIdHeaderName, entityId().ToString()); + } + } + + public static void AddServiceVersion(HttpResponse response, Func version) + { + if (response.StatusCode == (int)HttpStatusCode.NoContent) + { + response.Headers[ODataVersionConstraint.ODataServiceVersionHeader] = version(); + } + } + + internal static ODataVersion GetODataResponseVersion(HttpRequest request) + { + Contract.Assert(request != null, "GetODataResponseVersion called with a null request"); + return request.ODataMaxServiceVersion() ?? + request.ODataMinServiceVersion() ?? + request.ODataServiceVersion() ?? + ODataVersionConstraint.DefaultODataVersion; + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/UpdatedODataResult.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/UpdatedODataResult.cs new file mode 100644 index 0000000..3aa2a3e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Results/UpdatedODataResult.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Results +{ + /// + /// Represents an action result that is a response to a PUT, PATCH, or a MERGE operation on an OData entity. + /// + /// The entity type. + /// This action result handles content negotiation and the HTTP prefer header. + public class UpdatedODataResult : IActionResult + { + private readonly T _innerResult; + + /// + /// Initializes a new instance of the class. + /// + /// The updated entity. + public UpdatedODataResult(T entity) + { + if (entity == null) + { + throw Error.ArgumentNull("entity"); + } + + this._innerResult = entity; + } + + /// + public async virtual Task ExecuteResultAsync(ActionContext context) + { + HttpResponse response = context.HttpContext.Response; + HttpRequest request = context.HttpContext.Request; + IActionResult result = GetInnerActionResult(request); + await result.ExecuteResultAsync(context); + ResultHelpers.AddServiceVersion(response, () => ODataUtils.ODataVersionToString(ResultHelpers.GetODataResponseVersion(request))); + } + + internal IActionResult GetInnerActionResult(HttpRequest request) + { + if (RequestPreferenceHelpers.RequestPrefersReturnContent(new WebApiRequestHeaders(request.Headers))) + { + ObjectResult objectResult = new ObjectResult(_innerResult) + { + StatusCode = StatusCodes.Status200OK + }; + + return objectResult; + } + else + { + return new StatusCodeResult((int)HttpStatusCode.NoContent); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ActionRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ActionRoutingConvention.cs new file mode 100644 index 0000000..b422754 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ActionRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles action invocations. + /// + public partial class ActionRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/AttributeRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/AttributeRoutingConvention.cs new file mode 100644 index 0000000..e2476ff --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/AttributeRoutingConvention.cs @@ -0,0 +1,254 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNet.OData.Routing.Template; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Represents a routing convention that looks for s to match an + /// to a controller and an action. + /// + public partial class AttributeRoutingConvention : IODataRoutingConvention + { + private readonly IServiceProvider _serviceProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + /// The to use for figuring out all the controllers to + /// look for a match. + /// The path template handler to be used for parsing the path templates. + /// + /// While this function does not use types that are AspNetCore-specific, + /// the functionality is due to the way assembly resolution is done in AspNet vs AspnetCore. + /// + public AttributeRoutingConvention(string routeName, IServiceProvider serviceProvider, + IODataPathTemplateHandler pathTemplateHandler = null) + : this(routeName) + { + if (serviceProvider == null) + { + throw Error.ArgumentNull("serviceProvider"); + } + + _serviceProvider = serviceProvider; + + if (pathTemplateHandler != null) + { + ODataPathTemplateHandler = pathTemplateHandler; + } + else + { + IPerRouteContainer perRouteContainer = _serviceProvider.GetRequiredService(); + if (perRouteContainer == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(IPerRouteContainer)); + } + + IServiceProvider rootContainer = perRouteContainer.GetODataRootContainer(routeName); + ODataPathTemplateHandler = rootContainer.GetRequiredService(); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + /// The collection of controllers to search for a match. + /// This signature uses types that are AspNetCore-specific and is only used for unit tests. + internal AttributeRoutingConvention(string routeName, + IEnumerable controllers) + : this(routeName, controllers, (IODataPathTemplateHandler)null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the route. + /// The collection of controllers to search for a match. + /// The path template handler to be used for parsing the path templates. + /// This signature uses types that are AspNetCore-specific and is only used for unit tests. + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", + Justification = "See note on method.")] + internal AttributeRoutingConvention(string routeName, + IEnumerable controllers, + IODataPathTemplateHandler pathTemplateHandler) + : this(routeName) + { + if (controllers == null) + { + throw Error.ArgumentNull("controllers"); + } + + if (pathTemplateHandler == null) + { + throw Error.ArgumentNull("pathTemplateHandler"); + } + + ODataPathTemplateHandler = pathTemplateHandler; + + // Look for a service provider on the ControllerActionDescriptor, which the unit test will setup + // so the BuildAttributeMappings can find the perRouteContainer. + _serviceProvider = controllers.FirstOrDefault()?.Properties["serviceProvider"] as IServiceProvider; + + _attributeMappings = BuildAttributeMappings(controllers); + } + + /// + /// Gets the attribute mappings. + /// + internal IDictionary AttributeMappings + { + get + { + if (_attributeMappings == null) + { + // Get the IActionDescriptorCollectionProvider from the global services provider. + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = + _serviceProvider.GetRequiredService(); + + IEnumerable actionDescriptors = + actionDescriptorCollectionProvider.ActionDescriptors.Items.OfType(); + + _attributeMappings = BuildAttributeMappings(actionDescriptors); + } + + return _attributeMappings; + } + } + + /// + /// Specifies whether OData route attributes on this controller should be mapped. + /// This method will execute before the derived type's instance constructor executes. Derived types must + /// be aware of this and should plan accordingly. For example, the logic in ShouldMapController() should be simple + /// enough so as not to depend on the "this" pointer referencing a fully constructed object. + /// + /// The controller action. + /// true if this controller should be included in the map; false otherwise. + /// This signature uses types that are AspNetCore-specific. + public virtual bool ShouldMapController(ControllerActionDescriptor controllerAction) + { + return true; + } + + /// + /// This signature uses types that are AspNetCore-specific. + public IEnumerable SelectAction(RouteContext routeContext) + { + // Get a IActionDescriptorCollectionProvider from the global service provider. + IActionDescriptorCollectionProvider actionCollectionProvider = + routeContext.HttpContext.RequestServices.GetRequiredService(); + Contract.Assert(actionCollectionProvider != null); + + ODataPath odataPath = routeContext.HttpContext.ODataFeature().Path; + HttpRequest request = routeContext.HttpContext.Request; + + SelectControllerResult controllerResult = SelectControllerImpl( + odataPath, + new WebApiRequestMessage(request), + this.AttributeMappings); + + if (controllerResult != null) + { + IEnumerable actionDescriptors = actionCollectionProvider + .ActionDescriptors.Items.OfType() + .Where(c => c.ControllerName == controllerResult.ControllerName); + + string actionName = SelectActionImpl(new WebApiControllerContext(routeContext, controllerResult)); + + if (!String.IsNullOrEmpty(actionName)) + { + return actionDescriptors.Where( + c => String.Equals(c.ActionName, actionName, StringComparison.OrdinalIgnoreCase)); + } + } + + return null; + } + + /// This signature uses types that are AspNetCore-specific. + private IDictionary BuildAttributeMappings(IEnumerable controllerActions) + { + Dictionary attributeMappings = + new Dictionary(); + + foreach (ControllerActionDescriptor controllerAction in controllerActions) + { + if (IsODataController(controllerAction) && ShouldMapController(controllerAction)) + { + foreach (string prefix in GetODataRoutePrefixes(controllerAction)) + { + IEnumerable pathTemplates = GetODataPathTemplates(prefix, controllerAction); + foreach (ODataPathTemplate pathTemplate in pathTemplates) + { + attributeMappings.Add(pathTemplate, new WebApiActionDescriptor(controllerAction)); + } + } + } + } + + return attributeMappings; + } + + /// This signature uses types that are AspNetCore-specific. + private static bool IsODataController(ControllerActionDescriptor controllerAction) + { + return typeof(ODataController).IsAssignableFrom(controllerAction.ControllerTypeInfo.AsType()); + } + + /// This signature uses types that are AspNetCore-specific. + private static IEnumerable GetODataRoutePrefixes(ControllerActionDescriptor controllerAction) + { + Contract.Assert(controllerAction != null); + + IEnumerable prefixAttributes = controllerAction.ControllerTypeInfo.GetCustomAttributes(inherit: false); + + return GetODataRoutePrefixes(prefixAttributes, controllerAction.ControllerTypeInfo.FullName); + } + + /// This signature uses types that are AspNetCore-specific. + private IEnumerable GetODataPathTemplates(string prefix, ControllerActionDescriptor controllerAction) + { + Contract.Assert(controllerAction != null); + + IEnumerable routeAttributes = + controllerAction.MethodInfo.GetCustomAttributes(inherit: false); + + IPerRouteContainer perRouteContainer = _serviceProvider.GetRequiredService(); + if (perRouteContainer == null) + { + throw Error.InvalidOperation(SRResources.MissingODataServices, nameof(IPerRouteContainer)); + } + + IServiceProvider requestContainer = perRouteContainer.GetODataRootContainer(_routeName); + + string controllerName = controllerAction.ControllerName; + string actionName = controllerAction.ActionName; + + return + routeAttributes + .Where(route => string.IsNullOrEmpty(route.RouteName) || route.RouteName == _routeName) + .Select(route => GetODataPathTemplate(prefix, route.PathTemplate, requestContainer, controllerName, actionName)) + .Where(template => template != null); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs new file mode 100644 index 0000000..f280ad1 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/DynamicPropertyRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles dynamic properties for open type. + /// + public partial class DynamicPropertyRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntityRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntityRoutingConvention.cs new file mode 100644 index 0000000..d5817d8 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntityRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles operating on entities by key. + /// + public partial class EntityRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs new file mode 100644 index 0000000..e29941c --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles entity sets. + /// + public partial class EntitySetRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/FunctionRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/FunctionRoutingConvention.cs new file mode 100644 index 0000000..bddba58 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/FunctionRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles function invocations. + /// + public partial class FunctionRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/IODataRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/IODataRoutingConvention.cs new file mode 100644 index 0000000..2040add --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/IODataRoutingConvention.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Provides an abstraction for selecting a controller and an action for OData requests. + /// + public interface IODataRoutingConvention + { + /// + /// Selects the controller and action for OData requests. + /// + /// The route context. + /// + /// null if the request isn't handled by this convention; + /// otherwise, the of the selected controller. + /// + IEnumerable SelectAction(RouteContext routeContext); + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/MetadataRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/MetadataRoutingConvention.cs new file mode 100644 index 0000000..c685e70 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/MetadataRoutingConvention.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles OData metadata requests. + /// + public partial class MetadataRoutingConvention : IODataRoutingConvention + { + /// + public IEnumerable SelectAction(RouteContext routeContext) + { + // Get a IActionDescriptorCollectionProvider from the global service provider. + IActionDescriptorCollectionProvider actionCollectionProvider = + routeContext.HttpContext.RequestServices.GetRequiredService(); + Contract.Assert(actionCollectionProvider != null); + + ODataPath odataPath = routeContext.HttpContext.ODataFeature().Path; + HttpRequest request = routeContext.HttpContext.Request; + + SelectControllerResult controllerResult = SelectControllerImpl( + odataPath, + new WebApiRequestMessage(request)); + + if (controllerResult != null) + { + IEnumerable actionDescriptors = actionCollectionProvider + .ActionDescriptors.Items.OfType() + .Where(c => c.ControllerName == controllerResult.ControllerName); + + string actionName = SelectActionImpl( + odataPath, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + + if (!String.IsNullOrEmpty(actionName)) + { + return actionDescriptors.Where( + c => String.Equals(c.ActionName, actionName, StringComparison.OrdinalIgnoreCase)); + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs new file mode 100644 index 0000000..fd89404 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles navigation properties. + /// + public partial class NavigationRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs new file mode 100644 index 0000000..7b8dfa9 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationSourceRoutingConvention.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles navigation sources + /// (entity sets or singletons) + /// + public abstract partial class NavigationSourceRoutingConvention : IODataRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public IEnumerable SelectAction(RouteContext routeContext) + { + if (routeContext == null) + { + throw Error.ArgumentNull("routeContext"); + } + + ODataPath odataPath = routeContext.HttpContext.ODataFeature().Path; + if (odataPath == null) + { + throw Error.ArgumentNull("odataPath"); + } + + HttpRequest request = routeContext.HttpContext.Request; + + SelectControllerResult controllerResult = SelectControllerImpl(odataPath); + if (controllerResult != null) + { + // Get a IActionDescriptorCollectionProvider from the global service provider. + IActionDescriptorCollectionProvider actionCollectionProvider = + routeContext.HttpContext.RequestServices.GetRequiredService(); + Contract.Assert(actionCollectionProvider != null); + + IEnumerable actionDescriptors = actionCollectionProvider + .ActionDescriptors.Items.OfType() + .Where(c => c.ControllerName == controllerResult.ControllerName); + + if (actionDescriptors != null) + { + string actionName = SelectAction(routeContext, controllerResult, actionDescriptors); + if (!String.IsNullOrEmpty(actionName)) + { + return actionDescriptors.Where( + c => String.Equals(c.ActionName, actionName, StringComparison.OrdinalIgnoreCase)); + } + } + } + + return null; + } + + /// + /// Selects the action for OData requests. + /// + /// The route context. + /// The result of selecting a controller. + /// The list of action descriptors. + /// + /// null if the request isn't handled by this convention; otherwise, the action descriptor of the selected action. + /// + /// This signature uses types that are AspNetCore-specific. + public abstract string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors); + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ODataRoutingConventions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ODataRoutingConventions.cs new file mode 100644 index 0000000..9b27201 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/ODataRoutingConventions.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// Provides helper methods for creating routing conventions. + /// + public static class ODataRoutingConventions + { + /// + /// Creates a mutable list of the default OData routing conventions with attribute routing enabled. + /// + /// The name of the route. + /// The to add the route to. + /// A mutable list of the default OData routing conventions. + public static IList CreateDefaultWithAttributeRouting( + string routeName, + IRouteBuilder builder) + { + if (builder == null) + { + throw Error.ArgumentNull("builder"); + } + + if (routeName == null) + { + throw Error.ArgumentNull("routeName"); + } + + IList routingConventions = CreateDefault(); + AttributeRoutingConvention routingConvention = new AttributeRoutingConvention(routeName, builder.ServiceProvider); + routingConventions.Insert(0, routingConvention); + return routingConventions; + } + + /// + /// Creates a mutable list of the default OData routing conventions. + /// + /// A mutable list of the default OData routing conventions. + public static IList CreateDefault() + { + return new List() + { + new MetadataRoutingConvention(), + new EntitySetRoutingConvention(), + new SingletonRoutingConvention(), + new EntityRoutingConvention(), + new NavigationRoutingConvention(), + new PropertyRoutingConvention(), + new DynamicPropertyRoutingConvention(), + new RefRoutingConvention(), + new ActionRoutingConvention(), + new FunctionRoutingConvention(), + new UnmappedRequestRoutingConvention() + }; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs new file mode 100644 index 0000000..bb6952d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles reading structural properties. + /// + public partial class PropertyRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/RefRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/RefRoutingConvention.cs new file mode 100644 index 0000000..f8e996d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/RefRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles entity reference manipulations. + /// + public partial class RefRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/SingletonRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/SingletonRoutingConvention.cs new file mode 100644 index 0000000..213fe0e --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/SingletonRoutingConvention.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that handles the singleton. + /// + public partial class SingletonRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl( + routeContext.HttpContext.ODataFeature().Path, + new WebApiControllerContext(routeContext, controllerResult), + new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs new file mode 100644 index 0000000..5a28ac4 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/Conventions/UnmappedRequestRoutingConvention.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNet.OData.Adapters; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing.Conventions +{ + /// + /// An implementation of that always selects the action named HandleUnmappedRequest if that action is present. + /// + public partial class UnmappedRequestRoutingConvention + { + /// + /// This signature uses types that are AspNetCore-specific. + public override string SelectAction(RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable actionDescriptors) + { + return SelectActionImpl(new WebApiActionMap(actionDescriptors)); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataActionSelector.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataActionSelector.cs new file mode 100644 index 0000000..70a7a8d --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataActionSelector.cs @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of that uses the server's OData routing conventions + /// to select an action for OData requests. + /// + public class ODataActionSelector : IActionSelector + { + private readonly IActionSelector _innerSelector; + + /// + /// Initializes a new instance of the class. + /// + /// IActionDescriptorCollectionProvider instance from dependency injection. + /// ActionConstraintCache instance from dependency injection. + /// ILoggerFactory instance from dependency injection. + public ODataActionSelector( + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, + ActionConstraintCache actionConstraintProviders, + ILoggerFactory loggerFactory) + { + _innerSelector = new ActionSelector(actionDescriptorCollectionProvider, actionConstraintProviders, loggerFactory); + } + + /// + public IReadOnlyList SelectCandidates(RouteContext context) + { + if (context == null) + { + throw Error.ArgumentNull("context"); + } + + HttpRequest request = context.HttpContext.Request; + ODataPath odataPath = context.HttpContext.ODataFeature().Path; + RouteData routeData = context.RouteData; + + if (odataPath == null || routeData.Values.ContainsKey(ODataRouteConstants.Action)) + { + // If there is no path, no routing conventions or there is already and indication we routed it, + // let the _innerSelector handle the request. + return _innerSelector.SelectCandidates(context); + } + + IEnumerable routingConventions = request.GetRoutingConventions(); + if (routingConventions != null) + { + foreach (IODataRoutingConvention convention in routingConventions) + { + IEnumerable actionDescriptor = convention.SelectAction(context); + if (actionDescriptor != null && actionDescriptor.Any()) + { + // All actions have the same name but may differ by number of parameters. + context.RouteData.Values[ODataRouteConstants.Action] = actionDescriptor.First().ActionName; + return actionDescriptor.ToList(); + } + } + } + + return _innerSelector.SelectCandidates(context); + } + + /// + public ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList candidates) + { + RouteData routeData = context.RouteData; + ODataPath odataPath = context.HttpContext.ODataFeature().Path; + if (odataPath != null && routeData.Values.ContainsKey(ODataRouteConstants.Action)) + { + // Get the available parameter names from the route data. Ignore case of key names. + IList availableKeys = routeData.Values.Keys.Select(k => k.ToLowerInvariant()).AsList(); + + // Filter out types we know how to bind out of the parameter lists. These values + // do not show up in RouteData() but will bind properly later. + var considerCandidates = candidates + .Select(c => new ActionIdAndParameters(c.Id, c.Parameters.Count, c.Parameters + .Where(p => + { + return p.ParameterType != typeof(ODataPath) && + !ODataQueryParameterBindingAttribute.ODataQueryParameterBinding.IsODataQueryOptions(p.ParameterType); + }))); + + // retrieve the optional parameters + routeData.Values.TryGetValue(ODataRouteConstants.OptionalParameters, out object wrapper); + ODataOptionalParameter optionalWrapper = wrapper as ODataOptionalParameter; + + // Find the action with the all matched parameters from available keys including + // matches with no parameters. Ordered first by the total number of matched + // parameters followed by the total number of parameters. Ignore case of + // parameter names. The first one is the best match. + // + // Assume key,relatedKey exist in RouteData. 1st one wins: + // Method(ODataPath,ODataQueryOptions) vs Method(ODataPath). + // Method(key,ODataQueryOptions) vs Method(key). + // Method(key,ODataQueryOptions) vs Method(key). + // Method(key,relatedKey) vs Method(key). + // Method(key,relatedKey,ODataPath) vs Method(key,relatedKey). + var matchedCandidates = considerCandidates + .Where(c => !c.FilteredParameters.Any() || TryMatch(c.FilteredParameters, availableKeys, optionalWrapper)) + .OrderByDescending(c => c.FilteredParameters.Count) + .ThenByDescending(c => c.TotalParameterCount); + + // Return either the best matched candidate or the first + // candidate if none matched. + return (matchedCandidates.Any()) + ? candidates.Where(c => c.Id == matchedCandidates.FirstOrDefault().Id).FirstOrDefault() + : candidates.FirstOrDefault(); + } + + return _innerSelector.SelectBestCandidate(context, candidates); + } + + private bool TryMatch(IList parameters, IList availableKeys, ODataOptionalParameter optionalWrapper) + { + // use the parameter name to match. + foreach(var p in parameters) + { + string parameterName = p.Name.ToLowerInvariant(); + if (availableKeys.Contains(parameterName)) + { + continue; + } + + ControllerParameterDescriptor cP = p as ControllerParameterDescriptor; + if (cP != null && optionalWrapper != null) + { + if (cP.ParameterInfo.IsOptional && optionalWrapper.OptionalParameters.Any(o => o.Name.ToLowerInvariant() == parameterName)) + { + continue; + } + } + + return false; + } + + return true; + } + + private class ActionIdAndParameters + { + public ActionIdAndParameters(string id, int parameterCount, IEnumerable filteredParameters) + { + Id = id; + TotalParameterCount = parameterCount; + FilteredParameters = filteredParameters.ToList(); + } + + public string Id { get; set; } + + public int TotalParameterCount { get; set; } + + public IList FilteredParameters { get; private set; } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathParameterBindingAttribute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathParameterBindingAttribute.cs new file mode 100644 index 0000000..1bad81f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathParameterBindingAttribute.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// Implementation of used to bind an instance of as an action parameter. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] + public sealed partial class ODataPathParameterBindingAttribute : ModelBinderAttribute + { + /// + /// Initializes a new instance of the class. + /// + public ODataPathParameterBindingAttribute() + { + this.BinderType = typeof(ODataPathParameterModelBinder); + } + + /// + /// Implementation of used to bind an instance of as an action parameter. + /// + internal class ODataPathParameterModelBinder : IModelBinder + { + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw Error.ArgumentNull("bindingContext"); + } + + ODataPath odataPath = bindingContext.HttpContext.ODataFeature().Path; + bindingContext.Result = ModelBindingResult.Success(odataPath); + + return TaskHelpers.Completed(); + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathRouteConstraint.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathRouteConstraint.cs new file mode 100644 index 0000000..eeeaeb0 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataPathRouteConstraint.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of that only matches OData paths. + /// + public partial class ODataPathRouteConstraint : IRouteConstraint + { + /// + /// Determines whether the URL parameter contains a valid value for this constraint. + /// + /// The Http context. + /// The route to compare. + /// The name of the parameter. + /// A list of parameter values. + /// The route direction. + /// + /// True if this instance equals a specified route; otherwise, false. + /// + /// This signature uses types that are AspNetCore-specific. + public virtual bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) + { + if (httpContext == null) + { + throw Error.ArgumentNull("httpContext"); + } + + if (values == null) + { + throw Error.ArgumentNull("values"); + } + + if (routeDirection == RouteDirection.IncomingRequest) + { + ODataPath path = null; + HttpRequest request = httpContext.Request; + + object oDataPathValue; + if (values.TryGetValue(ODataRouteConstants.ODataPath, out oDataPathValue)) + { + // We need to call Uri.GetLeftPart(), which returns an encoded Url. + // The ODL parser does not like raw values. + Uri requestUri = new Uri(request.GetEncodedUrl()); + string requestLeftPart = requestUri.GetLeftPart(UriPartial.Path); + string queryString = request.QueryString.HasValue ? request.QueryString.ToString() : null; + + path = GetODataPath(oDataPathValue as string, requestLeftPart, queryString, () => request.CreateRequestContainer(RouteName)); + } + + if (path != null) + { + // Set all the properties we need for routing, querying, formatting + IODataFeature odataFeature = httpContext.ODataFeature(); + odataFeature.Path = path; + odataFeature.RouteName = RouteName; + + return true; + } + + // The request doesn't match this route so dispose the request container. + request.DeleteRequestContainer(true); + return false; + } + else + { + // This constraint only applies to incoming request. + return true; + } + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataRoute.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataRoute.cs new file mode 100644 index 0000000..f6abcc3 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataRoute.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Contracts; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// A route implementation for OData routes. It supports passing in a route prefix for the route as well + /// as a path constraint that parses the request path as OData. + /// + public partial class ODataRoute : Route + { + /// + /// Initializes a new instance of the class. + /// + /// The target router. + /// The route name. + /// The route prefix. + /// The OData route constraint. + /// The inline constraint resolver. + /// This signature uses types that are AspNetCore-specific. + public ODataRoute(IRouter target, string routeName, string routePrefix, ODataPathRouteConstraint routeConstraint, IInlineConstraintResolver resolver) + : this(target, routeName, routePrefix, (IRouteConstraint)routeConstraint, resolver) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target router. + /// The route name. + /// The route prefix. + /// The OData route constraint. + /// The inline constraint resolver. + /// This signature uses types that are AspNetCore-specific. + public ODataRoute(IRouter target, string routeName, string routePrefix, IRouteConstraint routeConstraint, IInlineConstraintResolver resolver) + : base(target, routeName, GetRouteTemplate(routePrefix), defaults: null, constraints: null, dataTokens: null, inlineConstraintResolver: resolver) + { + RouteConstraint = routeConstraint; + Initialize(routePrefix, routeConstraint as ODataPathRouteConstraint); + + if (routeConstraint != null) + { + Constraints.Add(ODataRouteConstants.ConstraintName, routeConstraint); + } + + Constraints.Add(ODataRouteConstants.VersionConstraintName, new ODataVersionConstraint()); + } + + /// + /// Gets the on this route. + /// + /// This signature uses types that are AspNetCore-specific. + public IRouteConstraint RouteConstraint { get; private set; } + + /// + /// This signature uses types that are AspNetCore-specific. + public override VirtualPathData GetVirtualPath(VirtualPathContext context) + { + // Fast path link generation where we recognize an OData route of the form "prefix/{*odataPath}". + // Link generation using HttpRoute.GetVirtualPath can consume up to 30% of processor time + object odataPathValue; + if (context.Values.TryGetValue(ODataRouteConstants.ODataPath, out odataPathValue)) + { + string odataPath = odataPathValue as string; + if (odataPath != null) + { + // Try to generate an optimized direct link + // Otherwise, fall back to the base implementation + return CanGenerateDirectLink + ? GenerateLinkDirectly(odataPath) + : base.GetVirtualPath(context); + } + } + + return null; + } + + /// This signature uses types that are AspNetCore-specific. + internal VirtualPathData GenerateLinkDirectly(string odataPath) + { + Contract.Assert(odataPath != null); + Contract.Assert(CanGenerateDirectLink); + + string link = CombinePathSegments(RoutePrefix, odataPath); + link = UriEncode(link); + return new VirtualPathData(this, link); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataVersionConstraint.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataVersionConstraint.cs new file mode 100644 index 0000000..cd65aec --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/Routing/ODataVersionConstraint.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData.Common; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.OData; + +namespace Microsoft.AspNet.OData.Routing +{ + /// + /// An implementation of that only matches a specific OData protocol + /// version. This constraint won't match incoming requests that contain any of the previous OData version + /// headers (for OData versions 1.0 to 3.0) regardless of the version in the current version headers. + /// + public partial class ODataVersionConstraint : IRouteConstraint + { + /// + /// This signature uses types that are AspNetCore-specific. + public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) + { + if (httpContext == null) + { + throw Error.ArgumentNull("httpContext"); + } + + if (routeDirection == RouteDirection.UrlGeneration) + { + return true; + } + + IDictionary> headers = httpContext.Request.Headers + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToList() as IEnumerable); + + ODataVersion? serviceVersion = httpContext.Request.ODataServiceVersion(); + ODataVersion? maxServiceVersion = httpContext.Request.ODataMaxServiceVersion(); + + return IsVersionMatch(headers, serviceVersion, maxServiceVersion); + } + } +} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResult.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResult.cs new file mode 100644 index 0000000..f81744f --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResult.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.AspNet.OData.Common; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an containing zero or one entities. Use together with an + /// [EnableQuery]. + /// + public abstract class SingleResult + { + /// + /// Initializes a new instance of the class. + /// + /// The containing zero or one entities. + protected SingleResult(IQueryable queryable) + { + if (queryable == null) + { + throw Error.ArgumentNull("queryable"); + } + + Queryable = queryable; + } + + /// + /// The containing zero or one entities. + /// + public IQueryable Queryable { get; private set; } + + /// + /// Creates a from an . A helper method to instantiate + /// a object without having to explicitly specify the type + /// . + /// + /// The type of the data in the data source. + /// The containing zero or one entities. + /// The created . + public static SingleResult Create(IQueryable queryable) + { + return new SingleResult(queryable); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResultOfT.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResultOfT.cs new file mode 100644 index 0000000..9bc2b91 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNetCore.OData/SingleResultOfT.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; + +namespace Microsoft.AspNet.OData +{ + /// + /// Represents an containing zero or one entities. Use together with an + /// [EnableQuery]. + /// + /// The type of the data in the data source. + public sealed class SingleResult : SingleResult + { + /// + /// Initializes a new instance of the class. + /// + /// The containing zero or one entities. + public SingleResult(IQueryable queryable) + : base(queryable) + { + } + + /// + /// The containing zero or one entities. + /// + public new IQueryable Queryable + { + get + { + return base.Queryable as IQueryable; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Settings.StyleCop b/ApiAsAService/WebAPI/src/Settings.StyleCop new file mode 100644 index 0000000..42b4729 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Settings.StyleCop @@ -0,0 +1,5 @@ + + + Parent + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/src/Strict.ruleset b/ApiAsAService/WebAPI/src/Strict.ruleset new file mode 100644 index 0000000..7abc697 --- /dev/null +++ b/ApiAsAService/WebAPI/src/Strict.ruleset @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/tools/35MSSharedLib1024.snk b/ApiAsAService/WebAPI/tools/35MSSharedLib1024.snk new file mode 100644 index 0000000000000000000000000000000000000000..695f1b38774e839e5b90059bfb7f32df1dff4223 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa50098C{E+7Ye`kjtcRG*W zi8#m|)B?I?xgZ^2Sw5D;l4TxtPwG;3)3^j?qDHjEteSTF{rM+4WI`v zCD?tsZ^;k+S&r1&HRMb=j738S=;J$tCKNrc$@P|lZ + + + $([System.DateTime]::Now.ToString("yyyyMMddHHmm")) + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/tools/MeasureCodeSharing.ps1 b/ApiAsAService/WebAPI/tools/MeasureCodeSharing.ps1 new file mode 100644 index 0000000..089d9b3 --- /dev/null +++ b/ApiAsAService/WebAPI/tools/MeasureCodeSharing.ps1 @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. + +Param ( + [Parameter(Mandatory=$true)] + [string] $RootPath +) + +if (Test-Path "$RootPath\src") +{ + $aspnetCount = Get-ChildItem -Path "$RootPath\src\Microsoft.AspNet.OData" -Filter "*.cs" -Recurse | Get-Content | Measure-Object -line + $sharedCount = Get-ChildItem -Path "$RootPath\src\Microsoft.AspNet.OData.Shared" -Filter "*.cs" -Recurse | Get-Content | Measure-Object -line + $coreCount = Get-ChildItem -Path "$RootPath\src\Microsoft.AspNetCore.OData" -Filter "*.cs" -Recurse | Get-Content | Measure-Object -line + + $aspnetUnique = $aspnetCount.Lines / ($aspnetCount.Lines + $sharedCount.Lines) + $aspnetShared = $sharedCount.Lines / ($aspnetCount.Lines + $sharedCount.Lines) + "OData WebApi for ASP.NET has {0,5:p} unique lines and {1,5:p} shared lines" -f $aspnetUnique, $aspnetShared + + $coreUnique = $coreCount.Lines / ($coreCount.Lines + $sharedCount.Lines) + $coreShared = $sharedCount.Lines / ($coreCount.Lines + $sharedCount.Lines) + "OData WebApi for ASP.NET Core has {0,5:p} unique lines and {1,5:p} shared lines" -f $coreUnique, $coreShared + + "OData WebApi has rougly {0,5:p} shared lines of product code" -f (($aspnetShared + $coreShared) / 2) +} +else +{ + "Cannot find source code in '{0}'" -f "$RootPath\src" +} + diff --git a/ApiAsAService/WebAPI/tools/PoliCheck/RunPoliCheck.ps1 b/ApiAsAService/WebAPI/tools/PoliCheck/RunPoliCheck.ps1 new file mode 100644 index 0000000..7210bcf --- /dev/null +++ b/ApiAsAService/WebAPI/tools/PoliCheck/RunPoliCheck.ps1 @@ -0,0 +1,35 @@ + +param( + [string]$BuildSourceDir, + [string]$folderName, + [string]$branchName, + [string]$resultRoot, + [string]$PoliCheckPath +) + +# +#Example: +# RunPoliCheck.ps1 -BuildSourceDir "C:\BuildAgent\_work\32\s" +# -folderName "src" +# -branchName "odata.net-master" +# -resultRoot "C:\Users\ODatabld\Documents\PoliCheck\LatestRunResult" +# -PoliCheckPath "C:\Program Files (x86)\Microsoft\PoliCheck\" +# + +$targetPath= "${BuildSourceDir}\${folderName}" +Write-Output "targetPath: ${targetPath}" +$result="${resultRoot}\${branchName}\poli_result_${folderName}.xml" + +cd "${PoliCheckPath}" + +.\Policheck.exe /F:$targetPath /T:9 /Sev:"1|2" /PE:2 /O:$result + +$FileContent = Get-Content $result +$PassResult = Select-String -InputObject $FileContent -Pattern "" + +If ($PassResult.Matches.Count -eq 0) { + Write-Error "PoliCheck failed for target ${targetPath}. For details, please check this result file on build machine: ${result}: section ." + exit 1 +} + +Write-Output "PoliCheck pass for target ${targetPath}" \ No newline at end of file diff --git a/ApiAsAService/WebAPI/tools/SkipStrongNames.xml b/ApiAsAService/WebAPI/tools/SkipStrongNames.xml new file mode 100644 index 0000000..cf37ea2 --- /dev/null +++ b/ApiAsAService/WebAPI/tools/SkipStrongNames.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/WebAPI/tools/WebStack.StyleCop.targets b/ApiAsAService/WebAPI/tools/WebStack.StyleCop.targets new file mode 100644 index 0000000..f2df30e --- /dev/null +++ b/ApiAsAService/WebAPI/tools/WebStack.StyleCop.targets @@ -0,0 +1,80 @@ + + + + + + + + + + + $(PrepareForRunDependsOn);StyleCop + + + + $(StyleCopEnabled) + false + true + true + + $(IntermediateOutputPath)StyleCopViolations.xml + $(IntermediateOutputPath)StyleCop.success + + 0 + + + + + + @(StyleCopMsBuildRunner) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/WebAPI/tools/WebStack.settings.targets b/ApiAsAService/WebAPI/tools/WebStack.settings.targets new file mode 100644 index 0000000..52a312b --- /dev/null +++ b/ApiAsAService/WebAPI/tools/WebStack.settings.targets @@ -0,0 +1,59 @@ + + + + + + + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\')) + $(MSBuildThisFileDirectory) + + + $(WebStackToolsPath)WebStack.targets + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + Debug + $(WebStackRootPath)bin\$(Configuration)\ + AnyCPU + 512 + prompt + 4 + true + + + ENU + + + + false + false + + + true + true + $(WebStackRootPath)\tools\35MSSharedLib1024.snk + + + + + true + full + false + TRACE;DEBUG;CODE_ANALYSIS + + + pdbonly + true + TRACE + + + pdbonly + true + TRACE;CODE_ANALYSIS + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/tools/WebStack.targets b/ApiAsAService/WebAPI/tools/WebStack.targets new file mode 100644 index 0000000..353c6c0 --- /dev/null +++ b/ApiAsAService/WebAPI/tools/WebStack.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/tools/WebStack.tasks.targets b/ApiAsAService/WebAPI/tools/WebStack.tasks.targets new file mode 100644 index 0000000..f3a36db --- /dev/null +++ b/ApiAsAService/WebAPI/tools/WebStack.tasks.targets @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + testFailures = new List(); + + foreach (string testResultFile in testResultFiles) + { + XElement xml; + using (FileStream fileStream = File.OpenRead(testResultFile)) + { + xml = XElement.Load(fileStream); + } + + var assemblies = xml.Elements(XName.Get("assembly")); + foreach (XElement assembly in assemblies) + { + int failures = Int32.Parse(assembly.Attribute(XName.Get("failed")).Value); + + testsPassed += Int32.Parse(assembly.Attribute(XName.Get("passed")).Value); + testsFailed += failures; + testsSkipped += Int32.Parse(assembly.Attribute(XName.Get("skipped")).Value); + timeSpent += Decimal.Parse(assembly.Attribute(XName.Get("time")).Value); + + if (failures > 0) + { + foreach (XElement classWithFailure in assembly.Elements(XName.Get("class")) + .Where(c => Int32.Parse(c.Attribute(XName.Get("failed")).Value) > 0)) + { + foreach (XElement failure in classWithFailure.Elements(XName.Get("test")) + .Where(test => test.Attribute(XName.Get("result")).Value == "Fail")) + { + testFailures.Add(failure.Attribute("name").Value); + } + } + } + } + } + + if (testFailures.Count > 0) + { + Console.WriteLine(); + Console.WriteLine(" Test Failures:"); + ConsoleColor originalColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + foreach (string testFailure in testFailures) + { + Console.WriteLine(" " + testFailure); + } + Console.ForegroundColor = originalColor; + } + + Console.WriteLine(); + Console.WriteLine(" Tests passed: {0}, Tests failed: {1}, Tests skipped: {2}", testsPassed, testsFailed, testsSkipped); + Console.WriteLine(" Time spent running tests: {0} seconds", timeSpent); + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex); + return false; + } + ]]> + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets b/ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets new file mode 100644 index 0000000..bb713b6 --- /dev/null +++ b/ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets @@ -0,0 +1,128 @@ + + + + + + 7 + 1 + 1 + + + + + + [5.2.2, 5.3.0) + [2.0.0, 3.0.0) + 1.0.0 + 2.0.0 + [7.5.0, 8.0.0) + + + + + 2017 + $([System.Convert]::ToInt16('$([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd"))')) + $([System.Convert]::ToString($(VersionDateCode))) + + + + + $(VersionMajor).$(VersionMinor).$(VersionBuild) + $(VersionFullSemantic).$(VersionRevision) + + + + + $(VersionFullSemantic) + $(VersionFullSemantic)-$(VersionRelease) + + + + + true + false + + + + 2017 + INVALID_VersionMajor + INVALID_VersionMajor + INVALID_VersionMinor + INVALID_VersionBuild + $([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd")) + 0 + + + + + $(VersionMajor).$(VersionMinor).$(VersionBuild).$(VersionRevision) + $(VersionMajor).$(VersionMinor).$(VersionBuild).$(VersionRevision) + $(AssemblyFileVersion) + $(IntermediateOutputPath)$(MSBuildProjectName).version.cs + + + + + + + + + + + + Microsoft Corporation. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + @(VersionText) + !$(VersionText.Contains('$(AssemblyFileVersion)')) + + + + + + + $([System.Convert]::ToInt16('$(VersionMajor)')) + $([System.Convert]::ToInt16('$(VersionMinor)')) + $([System.Convert]::ToUInt16('$(VersionBuild)')) + $([System.Convert]::ToInt16('$(VersionRevision)')) + + + + + ValidateVersionValues;ShouldGenerateVersionFile;GenerateVersionFileCore + + + + diff --git a/ApiAsAService/WebAPI/tools/WebStack.xunit.targets b/ApiAsAService/WebAPI/tools/WebStack.xunit.targets new file mode 100644 index 0000000..8112e90 --- /dev/null +++ b/ApiAsAService/WebAPI/tools/WebStack.xunit.targets @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/WebAPI/tools/perf/PerfRegression.ps1 b/ApiAsAService/WebAPI/tools/perf/PerfRegression.ps1 new file mode 100644 index 0000000..6c61f09 --- /dev/null +++ b/ApiAsAService/WebAPI/tools/perf/PerfRegression.ps1 @@ -0,0 +1,59 @@ +<# +Name: PerfRegression.ps1 +Usage: -Workspace [workspace] -BuildId [Num] $RunnerParams +CopyRight: Copyright (C) Microsoft Corporation. All rights reserved. +#> + +Param +( + [String]$Workspace = $(throw "Workspace path is required."), + [String]$BuildId = $(throw "BuildId is required."), + [String]$RunnerParams +) + +# In which has the base running bits +$BaseBitsPath = Join-Path $Workspace "BaseBits" +If((Test-Path $BaseBitsPath) -eq $False) +{ + throw "~/BaseBits is required." +} + +# In which has the test running bits +$TestBitsPath = Join-Path $Workspace "TestBits" +If((Test-Path $TestBitsPath) -eq $False) +{ + throw "~/TestBits is required." +} + +<# +Description: Run the performance test cases +#> +Function Execute([string]$testFolder, [string]$runid) +{ + $location = Get-Location + Set-Location (Join-Path $testFolder "bin") + + $testDllName = "WebApiPerformance.Test.dll" + $result = $runid + ".xml" + $analysisResult = $runid + ".analysisResult.xml" + .\xunit.performance.run.exe $testDllName -runner .\xunit.console.exe -runnerargs "-parallel none $RunnerParams" -runid $runid + .\xunit.performance.analysis.exe $result -xml $analysisResult + + Set-Location $location +} + +# Step 1. Get the running date & test result name +$RunDate = Get-Date -Format yyyyMMdd +$TestRunId = "WebApiOData." + $RunDate + "." + $BuildId + +# Step 2. Run the current tests +Execute $TestBitsPath $TestRunId +$TempPath = Join-Path $TestBitsPath "bin" +$TestResult = Join-Path $TempPath ($TestRunId + ".analysisResult.xml") +Move-Item -Path $TestResult -Destination (Join-Path $TestBitsPath "test.xml") -Force + +# Step 3. Run the base tests +Execute $BaseBitsPath $TestRunId +$TempPath = Join-Path $BaseBitsPath "bin" +$BaseResult = Join-Path $TempPath ($TestRunId + ".analysisResult.xml") +Move-Item -Path $BaseResult -Destination (Join-Path $BaseBitsPath "base.xml") -Force diff --git a/ApiAsAService/WebAPIAsAService.sln b/ApiAsAService/WebAPIAsAService.sln new file mode 100644 index 0000000..ea4a200 --- /dev/null +++ b/ApiAsAService/WebAPIAsAService.sln @@ -0,0 +1,178 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI", "WebAPI", "{A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "WebAPI\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DDADB8FF-887D-48BE-8510-B463C3C4CAAB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAService\ApiAsAService.csproj", "{A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OData.Net", "OData.Net", "{E81D641A-FF79-40B6-A482-F73457938A23}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core", "odata.net\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj", "{989A83CC-B864-4A75-8BF3-5EDA99203A86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm", "odata.net\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj", "{7D921888-FE03-4C3F-80FE-2F624505461C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial", "odata.net\src\Microsoft.Spatial\Microsoft.Spatial.csproj", "{5D921888-FE03-4C3F-40FE-2F624505461D}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 + WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + CodeAnalysis|x64 = CodeAnalysis|x64 + CodeAnalysis|x86 = CodeAnalysis|x86 + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x64.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x64.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|Any CPU.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|Any CPU.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x64.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x64.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x86.ActiveCfg = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x86.Build.0 = CodeAnalysis|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x64.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.Build.0 = Debug|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x64.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x64.Build.0 = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.ActiveCfg = Release|Any CPU + {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x86.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x86.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|Any CPU.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x64.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x64.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x86.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x86.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.ActiveCfg = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.Build.0 = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x86.ActiveCfg = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x86.Build.0 = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x86.ActiveCfg = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x86.Build.0 = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|Any CPU.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|Any CPU.Build.0 = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x64.ActiveCfg = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x64.Build.0 = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x86.ActiveCfg = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x86.Build.0 = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.Build.0 = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.ActiveCfg = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.Build.0 = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.ActiveCfg = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.Build.0 = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.ActiveCfg = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.Build.0 = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.ActiveCfg = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.Build.0 = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.Build.0 = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.ActiveCfg = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.Build.0 = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.Build.0 = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|Any CPU.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|Any CPU.Build.0 = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x64.ActiveCfg = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x64.Build.0 = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x86.ActiveCfg = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x86.Build.0 = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.ActiveCfg = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.Build.0 = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.ActiveCfg = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.Build.0 = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.ActiveCfg = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.Build.0 = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.ActiveCfg = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.Build.0 = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.Build.0 = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.ActiveCfg = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.Build.0 = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.Build.0 = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|Any CPU.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|Any CPU.Build.0 = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x64.ActiveCfg = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x64.Build.0 = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x86.ActiveCfg = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x86.Build.0 = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.ActiveCfg = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.Build.0 = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.ActiveCfg = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.Build.0 = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.ActiveCfg = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.Build.0 = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.ActiveCfg = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.Build.0 = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.Build.0 = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.ActiveCfg = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.Build.0 = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} = {DDADB8FF-887D-48BE-8510-B463C3C4CAAB} + {A6F9775D-F7E2-424E-8363-79644A73038F} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} + {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925} = {DDADB8FF-887D-48BE-8510-B463C3C4CAAB} + {E81D641A-FF79-40B6-A482-F73457938A23} = {DDADB8FF-887D-48BE-8510-B463C3C4CAAB} + {989A83CC-B864-4A75-8BF3-5EDA99203A86} = {E81D641A-FF79-40B6-A482-F73457938A23} + {7D921888-FE03-4C3F-80FE-2F624505461C} = {E81D641A-FF79-40B6-A482-F73457938A23} + {5D921888-FE03-4C3F-40FE-2F624505461D} = {E81D641A-FF79-40B6-A482-F73457938A23} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/.markdownlint.json b/ApiAsAService/odata.net/.markdownlint.json new file mode 100644 index 0000000..57ec225 --- /dev/null +++ b/ApiAsAService/odata.net/.markdownlint.json @@ -0,0 +1,7 @@ +{ + "MD013": false, + "MD029": { + "style": "ordered" + }, + "MD034": false +} diff --git a/ApiAsAService/odata.net/Build.props b/ApiAsAService/odata.net/Build.props new file mode 100644 index 0000000..837b990 --- /dev/null +++ b/ApiAsAService/odata.net/Build.props @@ -0,0 +1,111 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + $(MSBuildThisFileDirectory.TrimEnd('\')) + + $(MSBuildProgramFiles32)\Microsoft SDKs\Silverlight\$(TargetFrameworkVersion)\Libraries\Client + + + $(SigningScenarioForRelease) + Test + Test + + + + + true + true + false + false + false + false + false + false + true + + + + + $(DefineConstants);ASSEMBLY_ATTRIBUTE_PRODUCT_VS + $(DefineConstants);SUPPRESS_SECURITY_RULES + $(DefineConstants);ASSEMBLY_ATTRIBUTE_CLS_COMPLIANT + $(DefineConstants);SECURITY_MIGRATION + $(DefineConstants);ASSEMBLY_ATTRIBUTE_COM_VISIBLE + $(DefineConstants);ASSEMBLY_ATTRIBUTE_COM_COMPATIBLE_SIDEBYSIDE + $(DefineConstants);ASSEMBLY_ATTRIBUTE_TRANSPARENT_ASSEMBLY + $(DefineConstants);ASSEMBLY_ATTRIBUTE_ALLOW_PARTIALLY_TRUSTED_CALLERS + $(DefineConstants);ASSEMBLY_ATTRIBUTE_CONDITIONAL_APTCA_L2 + $(DefineConstants);ASSEMBLY_ATTRIBUTE_SKIP_VERIFICATION_IN_FULLTRUST + $(DefineConstants);ASSEMBLY_ATTRIBUTE_NO_BUILD_NUM_IN_VERSION + + + + $(DefineConstants);PORTABLELIB;SUPPRESS_SECURITY_RULES; + + + + $(DefineConstants);ORCAS;SUPPRESS_SECURITY_RULES + + + + $(NoWarn);1699;1570;1572;1573;1591;1607 + $(WarningsNotAsErrors);1058 + + + + + $([MSBuild]::Unescape($(DefineConstants.Replace(';',',')))) + + + + + pdbonly + + + + + + + $(EnlistmentRoot)\sln + $(SolutionDir)\packages + $(SolutionDir)\.nuget + $(NuGetToolsPath)\NuGet.exe + $(NuGetToolsPath)\packages.config + + + + + + + + + $(NuGetPack)\StyleCop.MSBuild.4.7.49.0\build\StyleCop.MSBuild.Targets + + + + + $(IntermediateOutputPath.TrimEnd('\'))\$(TargetFrameworkFolderName) + + + $(EnlistmentRoot)\src\AssemblyInfo + + + $(DataFxIncPath)\AssemblyKeys.cs + + + $(VS120COMNTOOLS)..\IDE\ + $(VS140COMNTOOLS)..\IDE\ + + + + + + + + diff --git a/ApiAsAService/odata.net/LICENSE.txt b/ApiAsAService/odata.net/LICENSE.txt new file mode 100644 index 0000000..6445497 --- /dev/null +++ b/ApiAsAService/odata.net/LICENSE.txt @@ -0,0 +1,26 @@ +OData .NET Libraries - ODataLib + +Copyright (c) 2018 Microsoft. All rights reserved. + +Material in this repository is made available under the following terms: + 1. Code is licensed under the MIT license, reproduced below. + 2. Documentation is licensed under the Creative Commons Attribution 3.0 United States (Unported) License. + The text of the license can be found here: http://creativecommons.org/licenses/by/3.0/legalcode + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/ApiAsAService/odata.net/OData.sln.lnk b/ApiAsAService/odata.net/OData.sln.lnk new file mode 100644 index 0000000000000000000000000000000000000000..5ddc2606e7419e0f5fa061e4b49df0bbfa3e9cff GIT binary patch literal 1531 zcmeZaU|?VrVFHp23!Vi%U{-jg1%>oRG`~$$`v`6~<=n!^b>~!3>o^^GX;}8FCqn8GzauBpAX#G|=m@ zu3g*;49U4EdZ`tuaQEtZ=&OJ*$lSh*2y^S0L2M9Mxn(0mGSIvf20fs;6+jGfF9Sme z5X%5D$bTT(9Ed?67>K2T7-T?@EM+5z{K(!ngt(irtF~!h`KsXWTn~?!jfoT(xN;84s3W*Wa)D?q~E=ESk zj4WS*@-ML9T%Zc1?*cI>uX6+W<$edHo-@VzZFkYzxGr$Uc92{G5CgN&f(RfD0*N_^ z<@tHK1<4RDLzCUX!gt$kyl2>++<&O%A-7s)O#3tTXMzp;JowMsbeth14>AS-y-lrL literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/PerformanceBuild.ps1 b/ApiAsAService/odata.net/PerformanceBuild.ps1 new file mode 100644 index 0000000..0dfd32d --- /dev/null +++ b/ApiAsAService/odata.net/PerformanceBuild.ps1 @@ -0,0 +1,82 @@ +Param +( + [String] + $Config = "Debug" +) + +If("Debug","Release" -notcontains $Config) +{ + Write-Host "Usage: Performancebuild.ps1 -Config Debug|Release" -ForegroundColor Red + exit +} + +$ProgramFilesX86 = [Environment]::GetFolderPath("ProgramFilesX86") +$EnlistmentRoot = Split-Path -Parent $MyInvocation.MyCommand.Definition +$LogDir = $EnlistmentRoot + "\bin" +$Msbuild= $ProgramFilesX86 + "\MSBuild\12.0\Bin\MSBuild.exe" +$TestDir = $EnlistmentRoot + "\bin\AnyCPU\$Config\Test\Desktop\Performance\bin" +$PackagesPath = $EnlistmentRoot + "\sln\packages\" + +$Item = Get-ChildItem -Filter xunit.performance.run.exe -Recurse -path $PackagesPath +$PerfRunPath = $Item.FullName + +$Item = Get-ChildItem -Filter xunit.performance.analysis.exe -Recurse -path $PackagesPath +$PerfAnalysisPath = $Item.FullName + +$Item = Get-ChildItem -Filter xunit.console.exe -Recurse -path $PackagesPath +$XunitConsoleRunnerPath = $Item.FullName + +Function RunBuild ($sln, $type, $conf) +{ + Write-Host "*** Building $sln ***" + $slnpath = $EnlistmentRoot + "\sln\$sln" + & $Msbuild $slnpath /t:rebuild /m /nr:false /fl "/p:Platform=Any CPU" /p:Configuration=$conf /p:Desktop=true /flp:LogFile=$LogDir/msbuild.log /flp:Verbosity=Normal 1>$null + if($LASTEXITCODE -eq 0) + { + Write-Host "Build $sln SUCCESS" -ForegroundColor Green + write-host "`n" + } + else + { + Write-Host "Build $sln FAILED" -ForegroundColor Red + Write-Host "For more information, please open the following test result files:" + Write-Host "$LogDir\msbuild.log" + exit + } +} + +Function ExecuteTests +{ + Param( + [string]$testFolder, + [string]$perfRunPath, + [string]$perfAnalysisPath, + [string]$xunitConsoleRunnerPath + ) + + $location = Get-Location + Set-Location $testFolder + $testDlls = Get-ChildItem -Filter Microsoft.OData.Performance.*.Tests.dll + $time = Get-Date -Format yyyyMMdd.hhmmss + + foreach ($dll in $testDlls) + { + $dllName = $dll.Name; + Write-Host "*** Run test for $dllName ***" + $rawName = $dllName.Replace("Microsoft.OData.Performance.","") + $rawName = $rawName.Replace(".Tests.dll", "") + $runid = $rawName + "." + $time + $result = $runid + ".xml" + $analysisResult = $runid + ".analysisResult.xml" + $resultPath = $testfolder + "\" + $analysisResult + &$perfRunPath $dll.Name -runner $xunitConsoleRunnerPath -runnerargs "-parallel none" -runid $runid + &$perfAnalysisPath $result -xml $analysisResult + Write-Host "See result for $dllName in $resultPath" + } + + Set-Location $location +} + +RunBuild 'OData.Tests.Performance.sln' $TestType $Config + +ExecuteTests $TestDir $PerfRunPath $PerfAnalysisPath $XunitConsoleRunnerPath \ No newline at end of file diff --git a/ApiAsAService/odata.net/README.md b/ApiAsAService/odata.net/README.md new file mode 100644 index 0000000..b710a84 --- /dev/null +++ b/ApiAsAService/odata.net/README.md @@ -0,0 +1,160 @@ +# OData .NET Libraries + Build | Status +--------|--------- +Rolling | +Nightly | + +## 1. Introduction + +The [OData .NET Libraries](http://odata.github.io/odata.net) (or OData .NET, for short) project includes the implementation of core functionalities of OData protocol on the .NET platform which includes URI parsing, request and response reading and writing, Entity Data Model (EDM) building, and also a .Net OData client which can be used to consume OData service. It is a fully open sourced project maintained by Microsoft OData team. The libraries are used by [WebApi](https://github.com/OData/WebApi/ "WebApi") and [RESTier](https://github.com/odata/RESTier/ "RESTier") which are recommended to be adopted to build new OData Services. + +[OData](http://www.odata.org/ "OData") stands for the Open Data Protocol. It was initiated by Microsoft and is now an [ISO](https://www.oasis-open.org/news/pr/iso-iec-jtc-1-approves-oasis-odata-standard-for-open-data-exchange) approved and [OASIS](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=odata) standard. OData enables the creation and consumption of REST APIs, which allow resources, identified using URLs and defined in a data model, to be published and edited by Web clients using simple HTTP requests. + +For more information about OData, please refer to the following resources: + +- [OData.org](http://www.odata.org/) +- [OASIS Open Data Protocol (OData) Technical Committee](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=odata) + +**For how to adopt this and related libraries to build or consume OData service, please refer to the following resources:** + +- [Build an OData v4 Service with RESTier Library](http://odata.github.io/RESTier/#01-01-Introduction) +- [Build an OData v4 Service with OData WebApi Library](http://odata.github.io/WebApi/#01-02-getting-started) +- [OData .Net Client](http://odata.github.io/odata.net/#04-01-basic-crud-operations) + +## 2. Project structure + +The project currently has six branches: [master](https://github.com/OData/odata.net/tree/master), [release](https://github.com/OData/odata.net/tree/release), [gh-pages](https://github.com/OData/odata.net/tree/gh-pages), [maintenance-6.x](https://github.com/OData/odata.net/tree/maintenance-6.x), [maintenance-5.x](https://github.com/OData/odata.net/tree/maintenance-5.x), and [maintenance-wcf-dataservice-v4](https://github.com/OData/odata.net/tree/maintenance-wcf-dataservice-v4). + +**master branch:** + +This master branch is the development branch for ODataV4 7.x and is now most actively iterated. It builds upon the OData 6.15 release which is now on [maintenance-6.x branch](https://github.com/OData/odata.net/tree/maintenance-6.x) and produces both [PCL (Portable Class Libraries) Profile111](https://msdn.microsoft.com/library/gg597391.aspx) and [.NET Standard 1.1](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) libraries. The branch builds mostly with Visual Studio 2015, and it is undergoing migration towards Visual Studio 2017; currently, the .NET Standard and .NET Core projects have been migrated. Due to the number of test projects, complete migration to the latest version of Visual Studio will be broken down into multiple steps. The code is shared between the .NET Framework and .NET Standard platforms, and you may use either Visual Studio 2015/2017 to contribute. + +For each profile above, it has the following libraries: + +- [ODataLib](http://www.nuget.org/packages/Microsoft.OData.Core/) (namespace `Microsoft.OData.Core`): The ODataLib contains classes to serialize, deserialize and validate OData JSON payloads. +- [EdmLib](http://www.nuget.org/packages/Microsoft.OData.Edm/) (namespace `Microsoft.OData.Edm`): The EdmLib contains classes to represent, construct, parse, serialize and validate entity data models. +- [Microsoft.Spatial](http://www.nuget.org/packages/Microsoft.Spatial/) (namespace `Microsoft.Spatial`): The spatial library contains classes and methods that facilitate geography and geometry spatial operations. +- [OData Client for .NET](http://www.nuget.org/packages/Microsoft.OData.Client/) (namespace `Microsoft.OData.Client`): The client library is built on top of ODataLib and EdmLib that has LINQ-enabled client APIs for issuing OData queries and consuming OData JSON payloads. + +For these libraries, we accept bug reports and pull requests. The corresponding fixes and implementations will be included into every new release. + +Note: Per the [.NET Standard 1.1](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) support chart, OData supports apps for Windows 8.0; however, Visual Studio 2013 was the last version that supported apps for Windows 8.0. Keeping Visual Studio 2013 conflicted with several goals (one of which is to simplify number of installations on a dev machine) and therefore, all tests for Windows 8.0 apps have been removed. Please be forewarned that if you choose to develop applications for Windows 8.0 (which has been superseded by Windows 8.1 and 10), OData no longer offers tests for that platform. You may, however, review the commit history to retrieve the tests housed in `Microsoft.Test.OData.Tests.Client.Portable.WindowsStore.csproj`. + +**release branch:** + +This branch is for ODataV4 7.x release developed on the [master branch](https://github.com/OData/odata.net/tree/master), contains most recently stable ODataV4 7.x release code base. + +**gh-pages branch:** + +The gh-pages branch contains documentation source for OData v4 Lib - tutorials, guides, etc. The documention source is in Markdown format. It is hosted at [ODataLib Pages](http://odata.github.io/odata.net "ODataLib Pages"). + +**maintenance-6.x branch:** (maintenance mode) + +The maintenance-6.x branch includes the .NET libraries for ODataV4 6.x maintenance releases. + +**maintenance-5.x branch:** (maintenance mode) + +The maintenance-5.x branch includes the .NET libraries for OData V1-3 releases only. It has the similar libraries as the maintenance-6.x branch except for some differences in namespaces and two additional libraries: + +- [ODataLib for OData v1-3](http://www.nuget.org/packages/Microsoft.Data.OData/) (namespace `Microsoft.Data.Core`): It contains classes to serialize, deserialize and validate OData payloads. Enables construction of OData producers and consumers. +- [EdmLib for OData v1-3](http://www.nuget.org/packages/Microsoft.Data.Edm/) (namespace `Microsoft.Data.Edm`): It contains classes to represent, construct, parse, serialize and validate entity data models. +- [System.Spatial for OData v1-3](http://www.nuget.org/packages/System.Spatial/) (namespace `System.Spatial`): It contains classes and methods that facilitate geography and geometry spatial operations. +- [WCF Data Services Client for OData v1-3](http://www.nuget.org/packages/Microsoft.Data.Services.Client/) (namespace `Microsoft.Data.Services.Client`): It contains LINQ-enabled client API for issuing OData queries and consuming OData payloads. +- [WCF Data Services Server for OData v1-3](http://www.nuget.org/packages/Microsoft.Data.Services/) (namespace `Microsoft.Data.Services`): Fully-featured server API for responding to OData queries and consuming/producing OData payloads. +- [WCF Data Services EntityFramework Provider](http://www.nuget.org/packages/Microsoft.OData.EntityFrameworkProvider/) (namespace `Microsoft.OData.EntityFrameworkProvider`): Server API for responding to OData queries and consuming/producing OData payloads based on entity framework version 6.0 or higher. + +These libraries are in maintenance mode. Only security bugs will be accepted. The release will be irregular depends on the bugs fixed. + +**maintenance-wcf-dataservice-v4 branch:** (maintenance mode) + +The maintenance-wcf-dataservice-v4 branch has the source code of the OData V4 parity of the WCF Data Services Server for OData v1-3. It is only for cloning and doesn't accept contributions. There is no binary release of it either. WCF DS is not recommended to be adopted now, instead [WebApi](https://github.com/OData/WebApi/ "WebApi") or [RESTier](https://github.com/odata/RESTier/ "RESTier") is recommended to be adopted to build new OData Services. + +## 3. Building, Testing, Debugging and Release + +In the case of VS2013, [SQL Express 2008](https://www.microsoft.com/en-sg/download/details.aspx?id=30438) or above must be installed. In the case of VS2015, LocalDB v12.0 or above will be used which is part of VS2015 and no additional installation is needed. The Database will be automatically initialized by the test code if it doesn't exist. + +Note: The project T4CrossPlatformTests.WindowsStore.csproj will not be loaded unless you have installed the Windows 8.1 and Windows Phone 8.0 / 8.1 tools. + +### 3.1 Building and Testing in Visual Studio + +Simply open the shortcut `OData.sln` at the root level folder to launch a solution that contains the product source and relevant unit tests. Should you see the need to modify or add additional tests, please see the `sln` folder for the whole set of solution files. + +Here is the usage of each solution file (the `OData.sln` shortcut opens the one marked default): + +- OData.CodeGen.sln - OData T4 client code generator product source. +- OData.Net35.sln - Product source built with .Net Framework 3.5. +- OData.Net45.sln (default) - Product source built with .Net Framework Portable 4.5 and contains corresponding unit tests. _Recommended_ for doing general bug fixes and feature development. +- OData.NetStandard.sln - Product source built with .Net Standard 1.1. +- OData.Tests.E2E.sln - Product source built with .Net Framework 4.5. Contains exhaustive list of tests (unit, E2E, and regression) and not intended to be opened frequently. The `Build.cmd` script will run all tests from here and this solution is used to _fully_ test your code. +- OData.Tests.E2E.NetCore.VS2017.sln - Product source built with .Net Standard 1.1 and E2E OData Client tests build with .Net Core 1.0-2.0. The purpose of this solution is to house .Net Core E2E test cases. The `Build.cmd` script will run all tests from here and this solution is used to _fully_ test the .Net Standard versions of the product code. +- OData.Tests.NetStandard.VS2017.sln - Product source built with .Net Standard 1.1 and contains corresponding unit tests written in .NET Core. Note that once OData has migrated to VS2017, this solution will replace OData.NetStandard.sln (and take its name) to reduce the number of solutions. +- OData.Tests.Performance.sln - Product source and performance tests built with .Net Framework version 4.6. +- OData.Tests.WindowsApps.sln - Product source and test harnesses written in Windows Portable and Windows Phone 8.1. + +Each solution contains some test projects. Please open it, build it and run all the tests in the test explorer. For running tests within OData.Tests.E2E.sln and OData.Tests.E2E.NetCore.VS2017.sln, you need to open Visual Studio IDE as **_Administrator_** so that the test services can be started properly. + +### 3.2 One-click build and test script in command line + +Open Command Line Window with "**Run as administrator**", `cd` to the root folder and run following command: + +`build.cmd` + +This will build the full product and run all tests. It will take about 60 minutes. Use the to ensure your change compiles and passes tests before submitting a pull request. + +Optionally, you can run following command: + +`build.cmd quick` + +This will build a single set of product Dlls and run unit tests. It will take about 5 minutes. Use this for quickly testing a change. + +Here are some other usages or `build.cmd`: + +- `build.cmd` or `build.cmd Nightly` - Build and run all nightly test suites. +- `build.cmd Quick` or `build.cmd -q` - Build and run all unit test suites (with less legacy tests thus faster). +- `build.cmd EnableSkipStrongName` - Configure strong name skip of OData libraries on your machine and build (no test run). +- `build.cmd DisableSkipStrongName` - Disable strong name skip of OData libraries on your machine and build (no test run). + +Notes: If there is build error with message "build.ps1 cannot be loaded", right click "build.ps1" -> Properties -> "Unlock". + +### 3.3 Debug + +Please refer to the [How to debug](http://odata.github.io/WebApi/10-01-debug-webapi-source). + +### 3.4 Nightly Builds + +The nightly build process will upload a NuGet packages for ODataLib (Core, Edm, Spatial, Client) to the [MyGet.org odlnightly feed](https://www.myget.org/gallery/odlnightly). + +To connect to odlnightly feed, use this feed URL: [odlnightly MyGet feed URL](https://www.myget.org/F/odlnightly). + +You can query the latest nightly NuGet packages using this query: [MAGIC OData query](https://www.myget.org/F/odlnightly/Packages?$select=Id,Version&$orderby=Version%20desc&$top=4&$format=application/json) + +### 3.5 Official Release + +The release of the component binaries is carried out regularly through [Nuget](http://www.nuget.org/). + +## 4. Documentation + +Please visit the [ODataLib pages](http://odata.github.io/odata.net). It has detailed descriptions on each feature provided by OData lib, how to use the OData .Net Client to consume OData service etc. + +## 5. Community + +### 5.1 Contribution + +There are many ways for you to contribute to OData .NET. The easiest way is to participate in discussion of features and issues. You can also contribute by sending pull requests of features or bug fixes to us. Contribution to the documentations is also highly welcomed. Please refer to the [CONTRIBUTING.md](https://github.com/OData/odata.net/blob/master/.github/CONTRIBUTING.md) for more details. + +### 5.2 Support + +- Issues: Report issues on [Github issues](https://github.com/OData/odata.net/issues). +- Questions: Ask questions on [Stack Overflow](http://stackoverflow.com/questions/ask?tags=odata). +- Feedback: Please send mails to [odatafeedback@microsoft.com](mailto:odatafeedback@microsoft.com). +- Team blog: Please visit [http://blogs.msdn.com/b/odatateam/](http://blogs.msdn.com/b/odatateam/) and [http://www.odata.org/blog/](http://www.odata.org/blog/). + +### Thank you + +We’re using NDepend to analyze and increase code quality. + +[![NDepend](images/ndependlogo.png)](http://www.ndepend.com) + +### Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/ApiAsAService/odata.net/build.cmd b/ApiAsAService/odata.net/build.cmd new file mode 100644 index 0000000..1c1bc61 --- /dev/null +++ b/ApiAsAService/odata.net/build.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy RemoteSigned -File "build.ps1" %* \ No newline at end of file diff --git a/ApiAsAService/odata.net/build.ps1 b/ApiAsAService/odata.net/build.ps1 new file mode 100644 index 0000000..eb7e902 --- /dev/null +++ b/ApiAsAService/odata.net/build.ps1 @@ -0,0 +1,650 @@ +# Default to Debug +$Configuration = 'Debug' + +# Color +$Success = 'Green' +$Warning = 'Yellow' +$Err = 'Red' + +if (($args.Count -eq 0) -or ($args[0] -match 'Nightly')) +{ + $TestType = 'Nightly' + $Configuration = 'Release' +} +elseif ($args[0] -match 'Quick' -or ($args[0] -match '-q')) +{ + $TestType = "Quick" +} +elseif ($args[0] -match 'Rolling') +{ + # Rolling is a legacy options - run all tests. + $TestType = "Nightly" +} +elseif ($args[0] -match 'E2E') +{ + # Rolling is a legacy options - run all tests. + $TestType = "Nightly" +} +elseif ($args[0] -match 'DisableSkipStrongName') +{ + $TestType = "DisableSkipStrongName" +} +elseif ($args[0] -match 'EnableSkipStrongName') +{ + $TestType = "EnableSkipStrongName" +} +elseif ($args[0] -match 'SkipStrongName') +{ + # SkipStrongName is a legacy options. + $TestType = "EnableSkipStrongName" +} +else +{ + Write-Host 'Please choose Nightly Test or Quick Test!' -ForegroundColor $Err + exit +} + +$Build = 'build' +if ($args -contains 'rebuild') +{ + $Build = 'rebuild' +} + +$PROGRAMFILESX86 = [Environment]::GetFolderPath("ProgramFilesX86") +$env:ENLISTMENT_ROOT = Split-Path -Parent $MyInvocation.MyCommand.Definition +$ENLISTMENT_ROOT = Split-Path -Parent $MyInvocation.MyCommand.Definition +$LOGDIR = $ENLISTMENT_ROOT + "\bin" + +# Default to use Visual Studio 2015 +$VS14MSBUILD=$PROGRAMFILESX86 + "\MSBuild\14.0\Bin\MSBuild.exe" +$VSTEST = $PROGRAMFILESX86 + "\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" +$FXCOPDIR = $PROGRAMFILESX86 + "\Microsoft Visual Studio 14.0\Team Tools\Static Analysis Tools\FxCop" +$SN = $PROGRAMFILESX86 + "\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\sn.exe" +$SNx64 = $PROGRAMFILESX86 + "\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\x64\sn.exe" + +# Use Visual Studio 2017 compiler for .NET Core and .NET Standard. Because VS2017 has different paths for different +# versions, we have to check for each version. Meanwhile, the dotnet CLI is required to run the .NET Core unit tests in this script. +$VS15VERSIONS = "Enterprise", + "Professional", + "Community" +$VS15MSBUILD = $null +ForEach ($version in $VS15VERSIONS) +{ + $tempMSBuildPath = ($PROGRAMFILESX86 + "\Microsoft Visual Studio\2017\{0}\MSBuild\15.0\Bin\MSBuild.exe") -f $version + if([System.IO.File]::Exists($tempMSBuildPath)) + { + $VS15MSBUILD = $tempMSBuildPath + break + } +} +$DOTNETDIR = "C:\Program Files\dotnet\" +$DOTNETTEST = $null +if ([System.IO.File]::Exists($DOTNETDIR + "dotnet.exe")) +{ + $DOTNETTEST = $DOTNETDIR + "dotnet.exe" +} + +# Other variables +$FXCOP = $FXCOPDIR + "\FxCopCmd.exe" +$BUILDLOG = $LOGDIR + "\msbuild.log" +$TESTLOG = $LOGDIR + "\mstest.log" +$TESTDIR = $ENLISTMENT_ROOT + "\bin\AnyCPU\$Configuration\Test\Desktop" +$NETCORETESTDIR = $ENLISTMENT_ROOT + "\bin\AnyCPU\$Configuration\Test\.NETPortable\netcoreapp1.0" +$PRODUCTDIR = $ENLISTMENT_ROOT + "\bin\AnyCPU\$Configuration\Product\Desktop" +$NUGETEXE = $ENLISTMENT_ROOT + "\sln\.nuget\NuGet.exe" +$NUGETPACK = $ENLISTMENT_ROOT + "\sln\packages" +$XUNITADAPTER = "/TestAdapterPath:" + $NUGETPACK + "\xunit.runner.visualstudio.2.1.0\build\_common" + +$NugetRestoreSolutions = "OData.NetStandard.sln" + +$ProductDlls = "Microsoft.OData.Client.dll", + "Microsoft.OData.Core.dll", + "Microsoft.OData.Edm.dll", + "Microsoft.OData.Service.Design.T4.dll", + "Microsoft.Spatial.dll" + +$XUnitTestDlls = "Microsoft.OData.Core.Tests.dll", + "Microsoft.OData.Edm.Tests.dll", + "Microsoft.Spatial.Tests.dll", + "Microsoft.OData.Client.Tests.dll" + +$NetCoreXUnitTestDlls = "Microsoft.OData.Core.Tests.dll", + "Microsoft.OData.Edm.Tests.dll", + "Microsoft.Spatial.Tests.dll" + +$TestSupportDlls = "Microsoft.OData.Service.Design.T4.dll", + "Microsoft.OData.Service.dll", + "Microsoft.OData.Service.Test.Common.dll" + +$NightlyTestDlls = "Microsoft.Test.Data.Services.DDBasics.dll", + "Microsoft.OData.Client.Design.T4.UnitTests.dll", + "AstoriaUnitTests.TDDUnitTests.dll", + "EdmLibTests.dll", + "Microsoft.OData.Client.TDDUnitTests.dll", + "Microsoft.Test.Taupo.OData.Common.Tests.dll", + "Microsoft.Test.Taupo.OData.Query.Tests.dll", + "Microsoft.Test.Taupo.OData.Reader.Tests.dll", + "Microsoft.Test.Taupo.OData.Writer.Tests.dll", + "Microsoft.Test.Taupo.OData.Scenario.Tests.dll", + "AstoriaUnitTests.ClientCSharp.dll", + "Microsoft.Data.NamedStream.UnitTests.dll", + "Microsoft.Data.ServerUnitTests1.UnitTests.dll", + "Microsoft.Data.ServerUnitTests2.UnitTests.dll", + "RegressionUnitTests.dll", + "Microsoft.Test.OData.PluggableFormat.Tests.dll", + "Microsoft.Data.MetadataObjectModel.UnitTests.dll", + "AstoriaUnitTests.dll", + "AstoriaClientUnitTests.dll" + +# .NET Core tests are different and require the dotnet tool. The tool references the .csproj (VS2017) files instead of dlls +$NetCoreXUnitTestProjs = "\test\FunctionalTests\Microsoft.Spatial.Tests\Microsoft.Spatial.Tests.NetCore.csproj", + "\test\FunctionalTests\Microsoft.OData.Edm.Tests\Microsoft.OData.Edm.Tests.NetCore.csproj", + "\test\FunctionalTests\Microsoft.OData.Core.Tests\Microsoft.OData.Core.Tests.NetCore.csproj", + "\test\FunctionalTests\Microsoft.OData.Client.Tests\Microsoft.OData.Client.Tests.NetCore.csproj", + "\test\FunctionalTests\Tests\DataServices\UnitTests\Client.TDD.Tests\Microsoft.OData.Client.TDDUnitTests.NetCore.csproj" + +$QuickTestSuite = @() +$NightlyTestSuite = @() +ForEach($dll in $XUnitTestDlls) +{ + $QuickTestSuite += $TESTDIR + "\" + $dll + $NightlyTestSuite += $TESTDIR + "\" + $dll +} + +ForEach($dll in $NightlyTestDlls) +{ + $NightlyTestSuite += $TESTDIR + "\" + $dll +} + +$E2eTestDlls = @("Microsoft.Test.OData.Tests.Client.dll") +ForEach ($dll in $E2eTestDlls) +{ + $NightlyTestSuite += $TESTDIR + "\" + $dll +} + +$FxCopRulesOptions = "/rule:$FxCopDir\Rules\DesignRules.dll", + "/rule:$FxCopDir\Rules\NamingRules.dll", + "/rule:$FxCopDir\Rules\PerformanceRules.dll", + "/rule:$FxCopDir\Rules\SecurityRules.dll", + "/rule:$FxCopDir\Rules\GlobalizationRules.dll", + "/dictionary:$ENLISTMENT_ROOT\src\CustomDictionary.xml", + "/ruleid:-Microsoft.Design#CA1006", + "/ruleid:-Microsoft.Design#CA1016", + "/ruleid:-Microsoft.Design#CA1020", + "/ruleid:-Microsoft.Design#CA1021", + "/ruleid:-Microsoft.Design#CA1045", + "/ruleid:-Microsoft.Design#CA2210", + "/ruleid:-Microsoft.Performance#CA1814" +$DataWebRulesOption = "/rule:$TESTDIR\DataWebRules.dll" + +Function GetDlls +{ + $dlls = @() + + ForEach($dll in $ProductDlls) + { + $dlls += $PRODUCTDIR + "\" + $dll + } + + ForEach($dll in $XUnitTestDlls) + { + $dlls += $TESTDIR + "\" + $dll + } + + ForEach($dll in $NetCoreXUnitTestDlls) + { + $dlls += $NETCORETESTDIR + "\" + $dll + } + + ForEach($dll in $TestSupportDlls) + { + $dlls += $TESTDIR + "\" + $dll + } + + ForEach($dll in $NightlyTestDlls) + { + $dlls += $TESTDIR + "\" + $dll + } + + ForEach($dll in $E2eTestDlls) + { + $dlls += $TESTDIR + "\" + $dll + } + + return $dlls +} + +Function SkipStrongName +{ + $SnLog = $LOGDIR + "\SkipStrongName.log" + Out-File $SnLog + + Write-Host 'Skip strong name validations for ODataLib assemblies...' + + $dlls = GetDlls + ForEach ($dll in $dlls) + { + & $SN /Vr $dll | Out-File $SnLog -Append + } + + ForEach ($dll in $dlls) + { + & $SNx64 /Vr $dll | Out-File $SnLog -Append + } + + Write-Host "SkipStrongName Done" -ForegroundColor $Success +} + +Function DisableSkipStrongName +{ + $SnLog = $LOGDIR + "\DisableSkipStrongName.log" + Out-File $SnLog + + Write-Host 'Disable skip strong name validations for ODataLib assemblies...' + + $dlls = GetDlls + ForEach ($dll in $dlls) + { + & $SN /Vu $dll | Out-File $SnLog -Append + } + + ForEach ($dll in $dlls) + { + & $SNx64 /Vu $dll | Out-File $SnLog -Append + } + + Write-Host "DisableSkipStrongName Done" -ForegroundColor $Success +} + +Function Cleanup +{ + cd $ENLISTMENT_ROOT\tools\Scripts + Write-Host "Dropping stale databases..." + cscript "$ENLISTMENT_ROOT\tools\Scripts\artdbclean.js" //Nologo + cd $ENLISTMENT_ROOT + Write-Host "Clean Done" -ForegroundColor $Success +} + +Function CleanBeforeScorch +{ + Write-Host 'Stopping TaupoAstoriaRunner as it should no longer be running' + taskkill /F /IM "TaupoAstoriaRunner.exe" 1>$null 2>$null + + Write-Host 'Stopping TaupoConsoleRunner as it should no longer be running' + taskkill /F /IM "TaupoConsoleRunner.exe" 1>$null 2>$null + + Write-Host 'Stopping VSTest as it should no longer be running' + taskkill /F /IM "vstest.console.exe" 1>$null 2>$null + + Write-Host 'Stopping MSbuild as it should no longer be running' + taskkill /F /IM "MSbuild.exe" 1>$null 2>$null + + Write-Host 'Stopping code coverage gathering...' + taskkill /f /im VSPerfMon.exe 1>$null 2>$null + + Write-Host 'Stopping WinHttpAutoProxySvc as it overflows due to large amount of web calls' + taskkill /F /FI "SERVICES eq WinHttpAutoProxySvc" >$null + + net stop w3svc 1>$null 2>$null + + Write-Host 'Minimize SQLExpress memory footprint' + net stop "SQL Server (SQLEXPRESS)" 1>$null 2>$null + net start "SQL Server (SQLEXPRESS)" 1>$null 2>$null + + Write-Host "Clean Done" -ForegroundColor $Success +} + +# Incremental build and rebuild +Function RunBuild ($sln, $vsToolVersion) +{ + Write-Host "*** Building $sln ***" + $slnpath = $ENLISTMENT_ROOT + "\sln\$sln" + $Conf = "/p:Configuration=" + "$Configuration" + + # Default to VS2015 + $MSBUILD = $VS14MSBUILD + + if($vsToolVersion -eq '15.0') + { + $MSBUILD=$VS15MSBUILD + } + + & $MSBUILD $slnpath /t:$Build /m /nr:false /fl "/p:Platform=Any CPU" $Conf /p:Desktop=true ` + /flp:LogFile=$LOGDIR/msbuild.log /flp:Verbosity=Normal 1>$null 2>$null + if($LASTEXITCODE -eq 0) + { + Write-Host "Build $sln SUCCESS" -ForegroundColor $Success + } + else + { + Write-Host "Build $sln FAILED" -ForegroundColor $Err + Write-Host "For more information, please open the following test result files:" + Write-Host "$LOGDIR\msbuild.log" + Cleanup + exit + } +} + +Function FailedTestLog ($playlist , $reruncmd , $failedtest1 ,$failedtest2) +{ + Write-Output "" | Out-File $playlist + Write-Output "@echo off" | Out-File -Encoding ascii $reruncmd + Write-Output "cd $TESTDIR" | Out-File -Append -Encoding ascii $reruncmd + $rerun = "`"$VSTEST`"" + if ($TestType -eq 'Nightly') + { + foreach ($dll in $NightlyTestSuite) + { + $rerun += " $dll" + } + } + else + { + foreach ($dll in $QuickTestSuite) + { + $rerun += " $dll" + } + } + if ($failedtest1.count -gt 0) + { + $rerun += " /Tests:" + } + foreach($case in $failedtest1) + { + $name = $case.split('.')[-1] + $rerun += $name + "," + $output = "" + Write-Output $output | Out-File -Append $playlist + } + # build the command only if failed tests exist + if ($failedtest1.count -gt 0) + { + $rerun += " " + $XUNITADAPTER + Write-Output $rerun | Out-File -Append -Encoding ascii $reruncmd + } + $rerun = "`"$VSTEST`"" + foreach ($dll in $E2eTestSuite) + { + $rerun += " $dll" + } + if ($failedtest2.count -gt 0) + { + $rerun += " /Tests:" + } + foreach($case in $failedtest2) + { + $name = $case.split('.')[-1] + $rerun += $name + "," + $output = "" + Write-Output $output | Out-File -Append $playlist + } + # build the command only if failed tests exist + if ($failedtest2.count -gt 0) + { + Write-Output $rerun | Out-File -Append -Encoding ascii $reruncmd + } + Write-Output "cd $LOGDIR" | Out-File -Append -Encoding ascii $reruncmd + Write-Output "" | Out-File -Append $playlist + Write-Host "There are some test cases failed!" -ForegroundColor $Err + Write-Host "To replay failed tests, please open the following playlist file:" -ForegroundColor $Err + Write-Host $playlist -ForegroundColor $Err + Write-Host "To rerun failed tests, please run the following script:" -ForegroundColor $Err + Write-Host $reruncmd -ForegroundColor $Err +} + +Function TestSummary +{ + Write-Host 'Collecting test results' + $playlist = "$LOGDIR\FailedTests.playlist" + $reruncmd = "$LOGDIR\rerun.cmd" + if (Test-Path $playlist) + { + rm $playlist + } + if (Test-Path $reruncmd) + { + rm $reruncmd + } + + $file = Get-Content -Path $TESTLOG + $pass = 0 + $skipped = 0 + $fail = 0 + $trxfile = New-Object -TypeName System.Collections.ArrayList + $failedtest1 = New-Object -TypeName System.Collections.ArrayList + $failedtest2 = New-Object -TypeName System.Collections.ArrayList + $part = 1 + foreach ($line in $file) + { + # Consolidate logic for retrieving number of passed and skipped tests. Failed tests is separate due to the way + # VSTest and DotNet (for .NET Core tests) report results differently. + if ($line -match "^Total tests: .*") + { + # The line is in this format: + # Total tests: 5735. Passed: 5735. Failed: 0. Skipped: 0. + # We want to extract the total passed and total skipped. + + # Extract total passed by taking the substring between "Passed: " and "." + # The regex first extracts the string after the hardcoded "Passed: " (i.e. "#. Failed: #. Skipped: #.") + # Then we tokenize by "." and retrieve the first token which is the number for passed. + $pattern = "Passed: (.*)" + $extractedNumber = [regex]::match($line, $pattern).Groups[1].Value.Split(".")[0] + $pass += $extractedNumber + + # Extract total skipped by taking the substring between "Skipped: " and "." + # The regex first extracts the string after the hardcoded "Skipped: " (i.e. "#.") + # Then we tokenize by "." and retrieve the first token which is the number for skipped. + $pattern = "Skipped: (.*)" + $extractedNumber = [regex]::match($line, $pattern).Groups[1].Value.Split(".")[0] + $skipped += $extractedNumber + } + elseif ($line -match "^Failed\s+(.*)") + { + $fail = $fail + 1 + if ($part -eq 1) + { + [void]$failedtest1.Add($Matches[1]) + } + else + { + [void]$failedtest2.Add($Matches[1]) + } + } + elseif ($line -match "^Results file: (.*)") + { + [void]$trxfile.Add($Matches[1]) + $part = 2 + } + } + + Write-Host "Test summary:" -ForegroundColor $Success + Write-Host "Passed :`t$pass" -ForegroundColor $Success + + if ($skipped -ne 0) + { + Write-Host "Skipped:`t$skipped" -ForegroundColor $Warning + } + + $color = $Success + if ($fail -ne 0) + { + $color = $Err + } + Write-Host "Failed :`t$fail" -ForegroundColor $color + Write-Host "---------------" -ForegroundColor $Success + Write-Host "Total :`t$($pass + $fail)" -ForegroundColor $Success + Write-Host "For more information, please open the following test result files:" + foreach ($trx in $trxfile) + { + Write-Host $trx + } + if ($fail -gt 0) + { + FailedTestLog -playlist $playlist -reruncmd $reruncmd -failedtest1 $failedtest1 -failedtest2 $failedtest2 + } + else + { + Write-Host "Congratulation! All of the tests passed!" -ForegroundColor $Success + } +} + +Function RunTest($title, $testdir, $framework) +{ + Write-Host "**********Running $title***********" + if ($framework -eq 'dotnet') + { + foreach($testProj in $testdir) + { + Write-Host "Launching $testProj..." + & $DOTNETTEST "test" ($ENLISTMENT_ROOT + $testProj) "--no-build" >> $TESTLOG + } + } + else + { + & $VSTEST $testdir $XUNITADAPTER >> $TESTLOG + } + + if($LASTEXITCODE -ne 0) + { + Write-Host "Run $title FAILED" -ForegroundColor $Err + } +} + +Function NugetRestoreSolution +{ + Write-Host '**********Pull NuGet Packages*********' + foreach($solution in $NugetRestoreSolutions) + { + & $NUGETEXE "restore" ($ENLISTMENT_ROOT + "\sln\" + $solution) + } +} + +Function BuildProcess +{ + Write-Host '**********Start To Build The Project*********' + + $script:BUILD_START_TIME = Get-Date + if (Test-Path $BUILDLOG) + { + rm $BUILDLOG + } + + RunBuild ('OData.Net45.sln') + + if ($TestType -ne 'Quick') + { + # OData.Tests.E2E.sln contains the product code for Net45 framework and a comprehensive list of test projects + RunBuild ('OData.Tests.E2E.sln') + RunBuild ('OData.Net35.sln') + # Solutions that contain .NET Core projects require VS2017 for full support. VS2015 supports only .NET Standard. + if($VS15MSBUILD) + { + Write-Host "Found VS2017 version: $VS15MSBUILD" + RunBuild ('OData.Tests.E2E.NetCore.VS2017.sln') -vsToolVersion '15.0' + RunBuild ('OData.CodeGen.sln') -vsToolVersion '15.0' + } + else + { + Write-Host ('Warning! Skipping build for .NET Core tests because no versions of VS2017 found. ' + ` + 'Building only product in .NET Standard.') -ForegroundColor $Warning + RunBuild ('OData.NetStandard.sln') + } + RunBuild ('OData.Tests.WindowsApps.sln') + } + + Write-Host "Build Done" -ForegroundColor $Success + $script:BUILD_END_TIME = Get-Date +} + +Function TestProcess +{ + Write-Host '**********Start To Run The Test*********' + if (Test-Path $TESTLOG) + { + rm $TESTLOG + } + $script:TEST_START_TIME = Get-Date + cd $TESTDIR + if ($TestType -eq 'Nightly') + { + RunTest -title 'NightlyTests' -testdir $NightlyTestSuite + } + elseif ($TestType -eq 'Quick') + { + RunTest -title 'XUnitTests' -testdir $QuickTestSuite + } + else + { + Write-Host 'Error : TestType' -ForegroundColor $Err + Cleanup + exit + } + + if ($DOTNETTEST) + { + RunTest -title 'NetCoreTests' -testdir $NetCoreXUnitTestProjs -framework 'dotnet' + } + else + { + Write-Host 'The dotnet CLI must be installed to run any .NET Core tests.' -ForegroundColor $Warning + } + + Write-Host "Test Done" -ForegroundColor $Success + TestSummary + $script:TEST_END_TIME = Get-Date + cd $ENLISTMENT_ROOT +} + +Function FxCopProcess +{ + Write-Host '**********Start To FxCop*********' + & $FXCOP "/f:$ProductDir\Microsoft.Spatial.dll" "/o:$LOGDIR\SpatialFxCopReport.xml" $DataWebRulesOption ` + $FxCopRulesOptions 1>$null 2>$null + & $FXCOP "/f:$ProductDir\Microsoft.OData.Core.dll" "/o:$LOGDIR\CoreFxCopReport.xml" $FxCopRulesOptions 1>$null 2>$null + & $FXCOP "/f:$ProductDir\Microsoft.OData.Edm.dll" "/o:$LOGDIR\EdmFxCopReport.xml" $FxCopRulesOptions 1>$null 2>$null + & $FXCOP "/f:$ProductDir\Microsoft.OData.Client.dll" "/o:$LOGDIR\ClientFxCopReport.xml" $DataWebRulesOption ` + $FxCopRulesOptions 1>$null 2>$null + Write-Host "For more information, please open the following test result files:" + Write-Host "$LOGDIR\SpatialFxCopReport.xml" + Write-Host "$LOGDIR\CoreFxCopReport.xml" + Write-Host "$LOGDIR\EdmFxCopReport.xml" + Write-Host "$LOGDIR\ClientFxCopReport.xml" + Write-Host "FxCop Done" -ForegroundColor $Success +} + +# Main Process + +if (! (Test-Path $LOGDIR)) +{ + mkdir $LOGDIR 1>$null +} + +if ($TestType -eq 'EnableSkipStrongName') +{ + CleanBeforeScorch + NugetRestoreSolution + BuildProcess + SkipStrongName + Exit +} +elseif ($TestType -eq 'DisableSkipStrongName') +{ + CleanBeforeScorch + NugetRestoreSolution + BuildProcess + DisableSkipStrongName + Exit +} + +CleanBeforeScorch +NugetRestoreSolution +BuildProcess +SkipStrongName +TestProcess +FxCopProcess +Cleanup + +$buildTime = New-TimeSpan $script:BUILD_START_TIME -end $script:BUILD_END_TIME +$testTime = New-TimeSpan $script:TEST_START_TIME -end $script:TEST_END_TIME +Write-Host("Build time:`t" + $buildTime) +Write-Host("Test time:`t" + $testTime) \ No newline at end of file diff --git a/ApiAsAService/odata.net/build.root b/ApiAsAService/odata.net/build.root new file mode 100644 index 0000000..a781e32 --- /dev/null +++ b/ApiAsAService/odata.net/build.root @@ -0,0 +1 @@ +Marker file indicating root of build system. \ No newline at end of file diff --git a/ApiAsAService/odata.net/images/PoliCheck/odata.net-master/exTermEdge_v58.xlsx b/ApiAsAService/odata.net/images/PoliCheck/odata.net-master/exTermEdge_v58.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2d38dc4cd45643d7d9a99bd14051c1cd73c1bdaa GIT binary patch literal 60029 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 dJ-&B&_zq=gDL$k%`pPHu%)!UA< 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

>~5c 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

  • H<+x%{{H^;C2xO?Vj^--w&HP1keT1 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 literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/images/ndependlogo.png b/ApiAsAService/odata.net/images/ndependlogo.png new file mode 100644 index 0000000000000000000000000000000000000000..0531e25eaca75d68ecf36830015e6bd5e42fe95b GIT binary patch literal 4353 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 literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/sln/.nuget/NuGet.Config b/ApiAsAService/odata.net/sln/.nuget/NuGet.Config new file mode 100644 index 0000000..fbd27b8 --- /dev/null +++ b/ApiAsAService/odata.net/sln/.nuget/NuGet.Config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/sln/.nuget/NuGet.exe b/ApiAsAService/odata.net/sln/.nuget/NuGet.exe new file mode 100644 index 0000000000000000000000000000000000000000..e42e6d8277d7b195a11ef130fe77d6f46e91c6d7 GIT binary patch literal 4266712 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 HpX;b=m8dOW15c8 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

    yYi08 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 M5xNA)U5?Z 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�GPv0~5J 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 z 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%0At`(u!nb=p&$6UeIIz_5Nc{ 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%0AbOD=l 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ⓈFDaHK_b!((^x^ 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

    ^cTf%#$Sh`@c}7`Ib8)zA>|C@_*#7A34`8q3|> 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π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

    4VV7v@=* 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 z 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≪2) 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 XcJ6O#NIKSAxqIi1lO=0sfg8;L?w_lE literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/sln/.nuget/NuGet.targets b/ApiAsAService/odata.net/sln/.nuget/NuGet.targets new file mode 100644 index 0000000..3f8c37b --- /dev/null +++ b/ApiAsAService/odata.net/sln/.nuget/NuGet.targets @@ -0,0 +1,144 @@ + + + + $(MSBuildProjectDirectory)\..\ + + + false + + + false + + + true + + + false + + + + + + + + + + + $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) + + + + + $(SolutionDir).nuget + + + + $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config + $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config + + + + $(MSBuildProjectDirectory)\packages.config + $(PackagesProjectConfig) + + + + + $(NuGetToolsPath)\NuGet.exe + @(PackageSource) + + "$(NuGetExePath)" + mono --runtime=v4.0.30319 "$(NuGetExePath)" + + $(TargetDir.Trim('\\')) + + -RequireConsent + -NonInteractive + + "$(SolutionDir) " + "$(SolutionDir)" + + + $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) + $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols + + + + RestorePackages; + $(BuildDependsOn); + + + + + $(BuildDependsOn); + BuildPackage; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/odata.net/sln/.nuget/packages.config b/ApiAsAService/odata.net/sln/.nuget/packages.config new file mode 100644 index 0000000..8afc066 --- /dev/null +++ b/ApiAsAService/odata.net/sln/.nuget/packages.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/sln/OData.CodeGen.sln b/ApiAsAService/odata.net/sln/OData.CodeGen.sln new file mode 100644 index 0000000..e72cc24 --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.CodeGen.sln @@ -0,0 +1,132 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ADCAE682-923D-4E58-A59A-39BE8B7CB9D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ODataT4ItemTemplate", "..\src\CodeGen\ODataT4ItemTemplate\ODataT4ItemTemplate.csproj", "{50899D87-AA94-4E95-B073-41740F6DB3BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial", "..\src\Microsoft.Spatial\Microsoft.Spatial.csproj", "{5D921888-FE03-4C3F-40FE-2F624505461D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm", "..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj", "{7D921888-FE03-4C3F-80FE-2F624505461C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core", "..\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj", "{989A83CC-B864-4A75-8BF3-5EDA99203A86}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libraries", "libraries", "{A2309D09-659A-4810-A2C0-B248470F0AED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client", "..\src\Microsoft.OData.Client\Microsoft.OData.Client.csproj", "{D1567C63-4A0D-4E18-A14E-79699B9BFFFF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Cover|Any CPU.Build.0 = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Cover|x64.ActiveCfg = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Cover|x64.Build.0 = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Cover|x86.ActiveCfg = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Cover|x86.Build.0 = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Debug|x64.Build.0 = Debug|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Debug|x86.Build.0 = Debug|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Release|Any CPU.Build.0 = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Release|x64.ActiveCfg = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Release|x64.Build.0 = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Release|x86.ActiveCfg = Release|Any CPU + {50899D87-AA94-4E95-B073-41740F6DB3BA}.Release|x86.Build.0 = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.Build.0 = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.ActiveCfg = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.Build.0 = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.Build.0 = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.ActiveCfg = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.Build.0 = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.ActiveCfg = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.Build.0 = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.ActiveCfg = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.Build.0 = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.Build.0 = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.Build.0 = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.ActiveCfg = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.Build.0 = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.Build.0 = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.ActiveCfg = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.Build.0 = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.ActiveCfg = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.Build.0 = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.ActiveCfg = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.Build.0 = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.Build.0 = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.Build.0 = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.ActiveCfg = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.Build.0 = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.Build.0 = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.ActiveCfg = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.Build.0 = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.ActiveCfg = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.Build.0 = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.ActiveCfg = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.Build.0 = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.Build.0 = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|Any CPU.Build.0 = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x64.ActiveCfg = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x64.Build.0 = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x86.ActiveCfg = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x86.Build.0 = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x64.ActiveCfg = Debug|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x64.Build.0 = Debug|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x86.ActiveCfg = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x86.Build.0 = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x64.ActiveCfg = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x64.Build.0 = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x86.ActiveCfg = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {50899D87-AA94-4E95-B073-41740F6DB3BA} = {ADCAE682-923D-4E58-A59A-39BE8B7CB9D9} + {5D921888-FE03-4C3F-40FE-2F624505461D} = {A2309D09-659A-4810-A2C0-B248470F0AED} + {7D921888-FE03-4C3F-80FE-2F624505461C} = {A2309D09-659A-4810-A2C0-B248470F0AED} + {989A83CC-B864-4A75-8BF3-5EDA99203A86} = {A2309D09-659A-4810-A2C0-B248470F0AED} + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF} = {A2309D09-659A-4810-A2C0-B248470F0AED} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9306B46E-F839-4A98-95CE-3A4A1A609BF9} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.Net35.sln b/ApiAsAService/odata.net/sln/OData.Net35.sln new file mode 100644 index 0000000..520aa23 --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.Net35.sln @@ -0,0 +1,89 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core.NetFX35", "..\src\Microsoft.OData.Core\Build.Net35\Microsoft.OData.Core.NetFX35.csproj", "{E7B15F0C-0442-458D-9D16-69F3272420BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm.NetFX35", "..\src\Microsoft.OData.Edm\Build.Net35\Microsoft.OData.Edm.NetFX35.csproj", "{69A15676-2469-4315-9617-F0FFC04D310D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial.NetFX35", "..\src\Microsoft.Spatial\Build.Net35\Microsoft.Spatial.NetFX35.csproj", "{1104C2E4-076F-47AA-8121-03015AE70630}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{026B0134-EBEC-44F7-88D7-EA18BF385826}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Cover|Any CPU.Build.0 = Cover|Any CPU + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Cover|x64.ActiveCfg = Cover|x64 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Cover|x64.Build.0 = Cover|x64 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Cover|x86.ActiveCfg = Cover|x86 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Cover|x86.Build.0 = Cover|x86 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Debug|x64.ActiveCfg = Debug|x64 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Debug|x64.Build.0 = Debug|x64 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Debug|x86.ActiveCfg = Debug|x86 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Debug|x86.Build.0 = Debug|x86 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Release|Any CPU.Build.0 = Release|Any CPU + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Release|x64.ActiveCfg = Release|x64 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Release|x64.Build.0 = Release|x64 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Release|x86.ActiveCfg = Release|x86 + {E7B15F0C-0442-458D-9D16-69F3272420BF}.Release|x86.Build.0 = Release|x86 + {69A15676-2469-4315-9617-F0FFC04D310D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {69A15676-2469-4315-9617-F0FFC04D310D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {69A15676-2469-4315-9617-F0FFC04D310D}.Cover|x64.ActiveCfg = Cover|x64 + {69A15676-2469-4315-9617-F0FFC04D310D}.Cover|x64.Build.0 = Cover|x64 + {69A15676-2469-4315-9617-F0FFC04D310D}.Cover|x86.ActiveCfg = Cover|x86 + {69A15676-2469-4315-9617-F0FFC04D310D}.Cover|x86.Build.0 = Cover|x86 + {69A15676-2469-4315-9617-F0FFC04D310D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69A15676-2469-4315-9617-F0FFC04D310D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69A15676-2469-4315-9617-F0FFC04D310D}.Debug|x64.ActiveCfg = Debug|x64 + {69A15676-2469-4315-9617-F0FFC04D310D}.Debug|x64.Build.0 = Debug|x64 + {69A15676-2469-4315-9617-F0FFC04D310D}.Debug|x86.ActiveCfg = Debug|x86 + {69A15676-2469-4315-9617-F0FFC04D310D}.Debug|x86.Build.0 = Debug|x86 + {69A15676-2469-4315-9617-F0FFC04D310D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69A15676-2469-4315-9617-F0FFC04D310D}.Release|Any CPU.Build.0 = Release|Any CPU + {69A15676-2469-4315-9617-F0FFC04D310D}.Release|x64.ActiveCfg = Release|x64 + {69A15676-2469-4315-9617-F0FFC04D310D}.Release|x64.Build.0 = Release|x64 + {69A15676-2469-4315-9617-F0FFC04D310D}.Release|x86.ActiveCfg = Release|x86 + {69A15676-2469-4315-9617-F0FFC04D310D}.Release|x86.Build.0 = Release|x86 + {1104C2E4-076F-47AA-8121-03015AE70630}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {1104C2E4-076F-47AA-8121-03015AE70630}.Cover|Any CPU.Build.0 = Cover|Any CPU + {1104C2E4-076F-47AA-8121-03015AE70630}.Cover|x64.ActiveCfg = Cover|x64 + {1104C2E4-076F-47AA-8121-03015AE70630}.Cover|x64.Build.0 = Cover|x64 + {1104C2E4-076F-47AA-8121-03015AE70630}.Cover|x86.ActiveCfg = Cover|x86 + {1104C2E4-076F-47AA-8121-03015AE70630}.Cover|x86.Build.0 = Cover|x86 + {1104C2E4-076F-47AA-8121-03015AE70630}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1104C2E4-076F-47AA-8121-03015AE70630}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1104C2E4-076F-47AA-8121-03015AE70630}.Debug|x64.ActiveCfg = Debug|x64 + {1104C2E4-076F-47AA-8121-03015AE70630}.Debug|x64.Build.0 = Debug|x64 + {1104C2E4-076F-47AA-8121-03015AE70630}.Debug|x86.ActiveCfg = Debug|x86 + {1104C2E4-076F-47AA-8121-03015AE70630}.Debug|x86.Build.0 = Debug|x86 + {1104C2E4-076F-47AA-8121-03015AE70630}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1104C2E4-076F-47AA-8121-03015AE70630}.Release|Any CPU.Build.0 = Release|Any CPU + {1104C2E4-076F-47AA-8121-03015AE70630}.Release|x64.ActiveCfg = Release|x64 + {1104C2E4-076F-47AA-8121-03015AE70630}.Release|x64.Build.0 = Release|x64 + {1104C2E4-076F-47AA-8121-03015AE70630}.Release|x86.ActiveCfg = Release|x86 + {1104C2E4-076F-47AA-8121-03015AE70630}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E7B15F0C-0442-458D-9D16-69F3272420BF} = {026B0134-EBEC-44F7-88D7-EA18BF385826} + {69A15676-2469-4315-9617-F0FFC04D310D} = {026B0134-EBEC-44F7-88D7-EA18BF385826} + {1104C2E4-076F-47AA-8121-03015AE70630} = {026B0134-EBEC-44F7-88D7-EA18BF385826} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.Net45.sln b/ApiAsAService/odata.net/sln/OData.Net45.sln new file mode 100644 index 0000000..5a699c2 --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.Net45.sln @@ -0,0 +1,199 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm", "..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj", "{7D921888-FE03-4C3F-80FE-2F624505461C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial", "..\src\Microsoft.Spatial\Microsoft.Spatial.csproj", "{5D921888-FE03-4C3F-40FE-2F624505461D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core", "..\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj", "{989A83CC-B864-4A75-8BF3-5EDA99203A86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client", "..\src\Microsoft.OData.Client\Microsoft.OData.Client.csproj", "{D1567C63-4A0D-4E18-A14E-79699B9BFFFF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.TestCommon", "..\test\FunctionalTests\Microsoft.OData.TestCommon\Build.NetFramework\Microsoft.OData.TestCommon.csproj", "{9037FF4A-4636-41AA-BFA2-0930EF1563EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm.Tests", "..\test\FunctionalTests\Microsoft.OData.Edm.Tests\Build.NetFramework\Microsoft.OData.Edm.Tests.csproj", "{419F42FA-3313-46A7-B519-B2ED042B0F5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core.Tests", "..\test\FunctionalTests\Microsoft.OData.Core.Tests\Build.NetFramework\Microsoft.OData.Core.Tests.csproj", "{C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial.Tests", "..\test\FunctionalTests\Microsoft.Spatial.Tests\Build.NetFramework\Microsoft.Spatial.Tests.csproj", "{68C25902-0FBD-4694-9EE9-47F2219CE039}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client.Tests", "..\test\FunctionalTests\Microsoft.OData.Client.Tests\Microsoft.OData.Client.Tests.csproj", "{9F0AB290-8164-4885-BFCA-A6F87AB81740}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.DependencyInjection", "..\test\Common\Microsoft.Test.OData.DependencyInjection\Build.NetFramework\Microsoft.Test.OData.DependencyInjection.csproj", "{50AA23B2-56EF-4C51-A270-8D5BD0C899B6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FDC11FD3-7DCD-4B75-8CC8-3BDC88107F01}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6D9CCEF3-6509-4381-B104-1A3FA576297F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unitTests", "unitTests", "{130A60C1-34E9-4F6C-90A6-CE646BF9C469}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "helper", "helper", "{B12AC8F3-9C8D-4343-A776-215D7C39DAED}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.ActiveCfg = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.Build.0 = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.ActiveCfg = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.Build.0 = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.Build.0 = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.Build.0 = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.ActiveCfg = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.Build.0 = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.ActiveCfg = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.Build.0 = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.Build.0 = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.Build.0 = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.Build.0 = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.ActiveCfg = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.Build.0 = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.ActiveCfg = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.Build.0 = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.Build.0 = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.Build.0 = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|Any CPU.Build.0 = Cover|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x64.ActiveCfg = Cover|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x86.ActiveCfg = Cover|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x86.Build.0 = Cover|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x86.ActiveCfg = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x86.Build.0 = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|Any CPU.Build.0 = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x64.ActiveCfg = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x86.ActiveCfg = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x86.Build.0 = Release|x86 + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|Any CPU.Build.0 = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|x64.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|x86.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|x64.ActiveCfg = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|Any CPU.Build.0 = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|x64.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|x86.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|Any CPU.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|x64.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|x86.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|Any CPU.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|x64.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|x86.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|Any CPU.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|x64.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|x86.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|Any CPU.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|x64.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|x86.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|Any CPU.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|x64.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|x86.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|x64.ActiveCfg = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|x86.ActiveCfg = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|Any CPU.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|x64.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|x86.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|Any CPU.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|x64.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|x86.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|Any CPU.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|x64.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|x86.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|Any CPU.Build.0 = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x64.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x86.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|Any CPU.Build.0 = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x64.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7D921888-FE03-4C3F-80FE-2F624505461C} = {FDC11FD3-7DCD-4B75-8CC8-3BDC88107F01} + {5D921888-FE03-4C3F-40FE-2F624505461D} = {FDC11FD3-7DCD-4B75-8CC8-3BDC88107F01} + {989A83CC-B864-4A75-8BF3-5EDA99203A86} = {FDC11FD3-7DCD-4B75-8CC8-3BDC88107F01} + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF} = {FDC11FD3-7DCD-4B75-8CC8-3BDC88107F01} + {9037FF4A-4636-41AA-BFA2-0930EF1563EE} = {B12AC8F3-9C8D-4343-A776-215D7C39DAED} + {419F42FA-3313-46A7-B519-B2ED042B0F5C} = {130A60C1-34E9-4F6C-90A6-CE646BF9C469} + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C} = {130A60C1-34E9-4F6C-90A6-CE646BF9C469} + {68C25902-0FBD-4694-9EE9-47F2219CE039} = {130A60C1-34E9-4F6C-90A6-CE646BF9C469} + {9F0AB290-8164-4885-BFCA-A6F87AB81740} = {130A60C1-34E9-4F6C-90A6-CE646BF9C469} + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6} = {B12AC8F3-9C8D-4343-A776-215D7C39DAED} + {130A60C1-34E9-4F6C-90A6-CE646BF9C469} = {6D9CCEF3-6509-4381-B104-1A3FA576297F} + {B12AC8F3-9C8D-4343-A776-215D7C39DAED} = {6D9CCEF3-6509-4381-B104-1A3FA576297F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C6A51F5A-B7DB-4D01-AEE4-C8904965B635} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.NetStandard.sln b/ApiAsAService/odata.net/sln/OData.NetStandard.sln new file mode 100644 index 0000000..c57013a --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.NetStandard.sln @@ -0,0 +1,112 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 14.0.25420.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial.NetStandard", "..\src\Microsoft.Spatial\Build.NetStandard\Microsoft.Spatial.NetStandard.csproj", "{48142D27-C862-4F4F-A781-1A9B72F6CDFA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm.NetStandard", "..\src\Microsoft.OData.Edm\Build.NetStandard\Microsoft.OData.Edm.NetStandard.csproj", "{DB301FA8-1CFA-487D-BA1E-803016E2A85C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core.NetStandard", "..\src\Microsoft.OData.Core\Build.NetStandard\Microsoft.OData.Core.NetStandard.csproj", "{1A263F63-1859-4310-957B-4F8875019896}" + ProjectSection(ProjectDependencies) = postProject + {48142D27-C862-4F4F-A781-1A9B72F6CDFA} = {48142D27-C862-4F4F-A781-1A9B72F6CDFA} + {DB301FA8-1CFA-487D-BA1E-803016E2A85C} = {DB301FA8-1CFA-487D-BA1E-803016E2A85C} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client.NetStandard", "..\src\Microsoft.OData.Client\Build.NetStandard\Microsoft.OData.Client.NetStandard.csproj", "{2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}" + ProjectSection(ProjectDependencies) = postProject + {48142D27-C862-4F4F-A781-1A9B72F6CDFA} = {48142D27-C862-4F4F-A781-1A9B72F6CDFA} + {DB301FA8-1CFA-487D-BA1E-803016E2A85C} = {DB301FA8-1CFA-487D-BA1E-803016E2A85C} + {1A263F63-1859-4310-957B-4F8875019896} = {1A263F63-1859-4310-957B-4F8875019896} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Cover|Any CPU.Build.0 = Cover|Any CPU + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Cover|x64.ActiveCfg = Cover|x64 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Cover|x64.Build.0 = Cover|x64 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Cover|x86.ActiveCfg = Cover|x86 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Cover|x86.Build.0 = Cover|x86 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Debug|x64.ActiveCfg = Debug|x64 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Debug|x64.Build.0 = Debug|x64 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Debug|x86.ActiveCfg = Debug|x86 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Debug|x86.Build.0 = Debug|x86 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Release|Any CPU.Build.0 = Release|Any CPU + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Release|x64.ActiveCfg = Release|x64 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Release|x64.Build.0 = Release|x64 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Release|x86.ActiveCfg = Release|x86 + {48142D27-C862-4F4F-A781-1A9B72F6CDFA}.Release|x86.Build.0 = Release|x86 + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Cover|Any CPU.Build.0 = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Cover|x64.ActiveCfg = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Cover|x64.Build.0 = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Cover|x86.ActiveCfg = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Cover|x86.Build.0 = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Debug|x64.Build.0 = Debug|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Debug|x86.Build.0 = Debug|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Release|Any CPU.Build.0 = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Release|x64.ActiveCfg = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Release|x64.Build.0 = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Release|x86.ActiveCfg = Release|Any CPU + {DB301FA8-1CFA-487D-BA1E-803016E2A85C}.Release|x86.Build.0 = Release|Any CPU + {1A263F63-1859-4310-957B-4F8875019896}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {1A263F63-1859-4310-957B-4F8875019896}.Cover|Any CPU.Build.0 = Cover|Any CPU + {1A263F63-1859-4310-957B-4F8875019896}.Cover|x64.ActiveCfg = Cover|x64 + {1A263F63-1859-4310-957B-4F8875019896}.Cover|x64.Build.0 = Cover|x64 + {1A263F63-1859-4310-957B-4F8875019896}.Cover|x86.ActiveCfg = Cover|x86 + {1A263F63-1859-4310-957B-4F8875019896}.Cover|x86.Build.0 = Cover|x86 + {1A263F63-1859-4310-957B-4F8875019896}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A263F63-1859-4310-957B-4F8875019896}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A263F63-1859-4310-957B-4F8875019896}.Debug|x64.ActiveCfg = Debug|x64 + {1A263F63-1859-4310-957B-4F8875019896}.Debug|x64.Build.0 = Debug|x64 + {1A263F63-1859-4310-957B-4F8875019896}.Debug|x86.ActiveCfg = Debug|x86 + {1A263F63-1859-4310-957B-4F8875019896}.Debug|x86.Build.0 = Debug|x86 + {1A263F63-1859-4310-957B-4F8875019896}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A263F63-1859-4310-957B-4F8875019896}.Release|Any CPU.Build.0 = Release|Any CPU + {1A263F63-1859-4310-957B-4F8875019896}.Release|x64.ActiveCfg = Release|x64 + {1A263F63-1859-4310-957B-4F8875019896}.Release|x64.Build.0 = Release|x64 + {1A263F63-1859-4310-957B-4F8875019896}.Release|x86.ActiveCfg = Release|x86 + {1A263F63-1859-4310-957B-4F8875019896}.Release|x86.Build.0 = Release|x86 + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Cover|Any CPU.Build.0 = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Cover|x64.ActiveCfg = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Cover|x64.Build.0 = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Cover|x86.ActiveCfg = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Cover|x86.Build.0 = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Debug|x64.ActiveCfg = Debug|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Debug|x64.Build.0 = Debug|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Debug|x86.Build.0 = Debug|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Release|Any CPU.Build.0 = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Release|x64.ActiveCfg = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Release|x64.Build.0 = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Release|x86.ActiveCfg = Release|Any CPU + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.Tests.E2E.NetCore.VS2017.sln b/ApiAsAService/odata.net/sln/OData.Tests.E2E.NetCore.VS2017.sln new file mode 100644 index 0000000..efe56fa --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.Tests.E2E.NetCore.VS2017.sln @@ -0,0 +1,317 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.16 +MinimumVisualStudioVersion = 14.0.25420.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AE28D570-28A6-45E1-A695-35C5B27CC4F8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F2442089-4BD1-4AEF-91FE-E7192432077E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unitTests", "unitTests", "{31EFC4F5-C88F-434C-BB94-4C6769997C88}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "helper", "helper", "{56DEF0B0-DE00-478C-8191-F7A5526AB6F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Spatial.Tests.NetCore", "..\test\FunctionalTests\Microsoft.Spatial.Tests\Microsoft.Spatial.Tests.NetCore.csproj", "{AF1E7103-7894-4DF5-A81F-60D7845F8720}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.TestCommon.NetCore", "..\test\FunctionalTests\Microsoft.OData.TestCommon\Microsoft.OData.TestCommon.NetCore.csproj", "{184697C2-99B5-4616-9D60-F2693293AD24}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Test.OData.DependencyInjection.NetCore", "..\test\Common\Microsoft.Test.OData.DependencyInjection\Microsoft.Test.OData.DependencyInjection.NetCore.csproj", "{206C8A3B-4384-4417-A819-5B263A4F4910}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Spatial.NetStandard.VS2017", "..\src\Microsoft.Spatial\Microsoft.Spatial.NetStandard.VS2017.csproj", "{E800572A-D59A-4F5E-AF39-6BD9DD6CC166}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Edm.NetStandard.VS2017", "..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.NetStandard.VS2017.csproj", "{BF32BBDE-EE48-47FA-B09C-47B786FFF31B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Edm.Tests.NetCore", "..\test\FunctionalTests\Microsoft.OData.Edm.Tests\Microsoft.OData.Edm.Tests.NetCore.csproj", "{4FA5E740-D3C9-4F00-9E20-4DE023E53335}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Core.NetStandard.VS2017", "..\src\Microsoft.OData.Core\Microsoft.OData.Core.NetStandard.VS2017.csproj", "{3DF47F20-F2DA-4285-870F-F65AF60F0487}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Core.Tests.NetCore", "..\test\FunctionalTests\Microsoft.OData.Core.Tests\Microsoft.OData.Core.Tests.NetCore.csproj", "{58A61116-2BF3-4803-B01E-A45AE89A4025}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Client.NetStandard.VS2017", "..\src\Microsoft.OData.Client\Microsoft.OData.Client.NetStandard.VS2017.csproj", "{08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Client.Tests.Netcore", "..\test\FunctionalTests\Microsoft.OData.Client.Tests\Microsoft.OData.Client.Tests.Netcore.csproj", "{ACE12729-DE71-4E82-B9AB-32D9A2E4E895}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "regressionTests", "regressionTests", "{0BDE0365-3053-45AF-A415-7888E1DBBC2C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Client.TDDUnitTests.NetCore", "..\test\FunctionalTests\Tests\DataServices\UnitTests\Client.TDD.Tests\Microsoft.OData.Client.TDDUnitTests.NetCore.csproj", "{67649063-ECB5-4A2F-BC62-A53B9BD03B22}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Service.Test.Common.NetCore", "..\test\FunctionalTests\Tests\CommonTestUtil\Microsoft.OData.Service.Test.Common.NetCore.csproj", "{32266D34-34F3-4EE1-A870-C332CDAD6620}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "e2eTests", "e2eTests", "{C8E49548-264D-49A9-B73A-CA72FEA98E2C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Test.OData.Tests.Client.NetCore", "..\test\EndToEndTests\Tests\Client\Build.Desktop\Microsoft.Test.OData.Tests.Client.NetCore.csproj", "{17F6E0B6-6249-4E7C-B78C-2BCCBC130217}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|Any CPU.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x64.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x64.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x86.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x86.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x64.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x86.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|Any CPU.Build.0 = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x64.ActiveCfg = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x64.Build.0 = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x86.ActiveCfg = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x86.Build.0 = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|Any CPU.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x64.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x64.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x86.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x86.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x64.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x64.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x86.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x86.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|Any CPU.Build.0 = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x64.ActiveCfg = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x64.Build.0 = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x86.ActiveCfg = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x86.Build.0 = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|Any CPU.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x64.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x64.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x86.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x86.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|Any CPU.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x64.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x64.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x86.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x86.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|Any CPU.ActiveCfg = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|Any CPU.Build.0 = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x64.ActiveCfg = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x64.Build.0 = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x86.ActiveCfg = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x86.Build.0 = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|Any CPU.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x64.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x64.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x86.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x86.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x64.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x64.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x86.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x86.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|Any CPU.Build.0 = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x64.ActiveCfg = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x64.Build.0 = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x86.ActiveCfg = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x86.Build.0 = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|Any CPU.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x64.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x64.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x86.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x86.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x64.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x64.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x86.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x86.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|Any CPU.Build.0 = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x64.ActiveCfg = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x64.Build.0 = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x86.ActiveCfg = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x86.Build.0 = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|Any CPU.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x64.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x64.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x86.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x86.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x64.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x86.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x64.ActiveCfg = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x64.Build.0 = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x86.ActiveCfg = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x86.Build.0 = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|Any CPU.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x64.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x64.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x86.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x86.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x64.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x64.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x86.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x86.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|Any CPU.Build.0 = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x64.ActiveCfg = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x64.Build.0 = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x86.ActiveCfg = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x86.Build.0 = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|Any CPU.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x64.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x64.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x86.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x86.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x64.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x64.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x86.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x86.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|Any CPU.Build.0 = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x64.ActiveCfg = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x64.Build.0 = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x86.ActiveCfg = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x86.Build.0 = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|Any CPU.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x64.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x64.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x86.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x86.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x64.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x64.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x86.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x86.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|Any CPU.Build.0 = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x64.ActiveCfg = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x64.Build.0 = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x86.ActiveCfg = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x86.Build.0 = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|Any CPU.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x64.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x64.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x86.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x86.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x64.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x64.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x86.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x86.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|Any CPU.Build.0 = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x64.ActiveCfg = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x64.Build.0 = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x86.ActiveCfg = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x86.Build.0 = Release|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Cover|Any CPU.Build.0 = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Cover|x64.ActiveCfg = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Cover|x64.Build.0 = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Cover|x86.ActiveCfg = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Cover|x86.Build.0 = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Debug|x64.ActiveCfg = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Debug|x64.Build.0 = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Debug|x86.ActiveCfg = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Debug|x86.Build.0 = Debug|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Release|Any CPU.Build.0 = Release|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Release|x64.ActiveCfg = Release|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Release|x64.Build.0 = Release|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Release|x86.ActiveCfg = Release|Any CPU + {67649063-ECB5-4A2F-BC62-A53B9BD03B22}.Release|x86.Build.0 = Release|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Cover|Any CPU.Build.0 = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Cover|x64.ActiveCfg = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Cover|x64.Build.0 = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Cover|x86.ActiveCfg = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Cover|x86.Build.0 = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Debug|x64.ActiveCfg = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Debug|x64.Build.0 = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Debug|x86.ActiveCfg = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Debug|x86.Build.0 = Debug|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Release|Any CPU.Build.0 = Release|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Release|x64.ActiveCfg = Release|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Release|x64.Build.0 = Release|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Release|x86.ActiveCfg = Release|Any CPU + {32266D34-34F3-4EE1-A870-C332CDAD6620}.Release|x86.Build.0 = Release|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Cover|Any CPU.Build.0 = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Cover|x64.ActiveCfg = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Cover|x64.Build.0 = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Cover|x86.ActiveCfg = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Cover|x86.Build.0 = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Debug|x64.ActiveCfg = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Debug|x64.Build.0 = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Debug|x86.ActiveCfg = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Debug|x86.Build.0 = Debug|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Release|Any CPU.Build.0 = Release|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Release|x64.ActiveCfg = Release|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Release|x64.Build.0 = Release|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Release|x86.ActiveCfg = Release|Any CPU + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {31EFC4F5-C88F-434C-BB94-4C6769997C88} = {F2442089-4BD1-4AEF-91FE-E7192432077E} + {56DEF0B0-DE00-478C-8191-F7A5526AB6F7} = {F2442089-4BD1-4AEF-91FE-E7192432077E} + {AF1E7103-7894-4DF5-A81F-60D7845F8720} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + {184697C2-99B5-4616-9D60-F2693293AD24} = {56DEF0B0-DE00-478C-8191-F7A5526AB6F7} + {206C8A3B-4384-4417-A819-5B263A4F4910} = {56DEF0B0-DE00-478C-8191-F7A5526AB6F7} + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {4FA5E740-D3C9-4F00-9E20-4DE023E53335} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + {3DF47F20-F2DA-4285-870F-F65AF60F0487} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {58A61116-2BF3-4803-B01E-A45AE89A4025} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + {0BDE0365-3053-45AF-A415-7888E1DBBC2C} = {F2442089-4BD1-4AEF-91FE-E7192432077E} + {67649063-ECB5-4A2F-BC62-A53B9BD03B22} = {0BDE0365-3053-45AF-A415-7888E1DBBC2C} + {32266D34-34F3-4EE1-A870-C332CDAD6620} = {56DEF0B0-DE00-478C-8191-F7A5526AB6F7} + {C8E49548-264D-49A9-B73A-CA72FEA98E2C} = {F2442089-4BD1-4AEF-91FE-E7192432077E} + {17F6E0B6-6249-4E7C-B78C-2BCCBC130217} = {C8E49548-264D-49A9-B73A-CA72FEA98E2C} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9A8C8EA9-487D-4990-BFA6-2D5A9462AEDC} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.Tests.E2E.sln b/ApiAsAService/odata.net/sln/OData.Tests.E2E.sln new file mode 100644 index 0000000..802dbbe --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.Tests.E2E.sln @@ -0,0 +1,1651 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.TDDUnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\TDDUnitTests\Microsoft.OData.Service.TDDUnitTests.csproj", "{E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.Test.Common", "..\test\FunctionalTests\Tests\CommonTestUtil\Microsoft.OData.Service.Test.Common.csproj", "{A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm", "..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj", "{7D921888-FE03-4C3F-80FE-2F624505461C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core", "..\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj", "{989A83CC-B864-4A75-8BF3-5EDA99203A86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial", "..\src\Microsoft.Spatial\Microsoft.Spatial.csproj", "{5D921888-FE03-4C3F-40FE-2F624505461D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client", "..\src\Microsoft.OData.Client\Microsoft.OData.Client.csproj", "{D1567C63-4A0D-4E18-A14E-79699B9BFFFF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service", "..\test\FunctionalTests\Service\Microsoft.OData.Service.csproj", "{D1567C63-4A0D-4E18-A14E-79699B9BA325}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client.TDDUnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\Client.TDD.Tests\Microsoft.OData.Client.TDDUnitTests.csproj", "{F7A080F5-2054-447F-B09C-4E7B9B1B1B00}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Utils", "..\test\FunctionalTests\Tests\TestUtils\Common\Microsoft.Test.OData.Utils\Microsoft.Test.OData.Utils.csproj", "{EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.OData", "..\test\FunctionalTests\Tests\DataOData\Common\OData\Microsoft.Test.Taupo.OData.csproj", "{1D54775E-E211-4B35-A002-E53416BAE743}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.SqlServer.TestShell.InputSpaceModeling", "..\test\FunctionalTests\Taupo\External\TestShell\InputSpaceModeling\Microsoft.SqlServer.TestShell.InputSpaceModeling.csproj", "{EDC237AA-6875-4034-BA39-7F2A83533BFC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.Spatial", "..\test\FunctionalTests\Taupo\Source\Taupo.Spatial\Microsoft.Test.Taupo.Spatial.csproj", "{7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo", "..\test\FunctionalTests\Taupo\Source\Taupo\Microsoft.Test.Taupo.csproj", "{34010F3A-20CC-479C-83CF-EC99B1C90CD1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.Query", "..\test\FunctionalTests\Taupo\Source\Taupo.Query\Microsoft.Test.Taupo.Query.csproj", "{29236BBA-852B-46B2-A39B-09DB47A7F6EB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.Astoria", "..\test\FunctionalTests\Taupo\Source\Taupo.Astoria\Microsoft.Test.Taupo.Astoria.csproj", "{854AF4E9-B78F-4994-B9C5-82B846604CBE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.Edmlib", "..\test\FunctionalTests\Taupo\Source\Taupo.EdmLib\Microsoft.Test.Taupo.Edmlib.csproj", "{65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLibTests", "..\test\FunctionalTests\Tests\DataEdmLib\EdmLibTests.csproj", "{834DEA16-4215-41D1-99E7-FBE41A3326DA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Framework", "..\test\EndToEndTests\Framework\Core\Microsoft.Test.OData.Framework.csproj", "{CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.ClientExtensions.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\ClientExtensions\Microsoft.Data.ClientExtensions.UnitTests.csproj", "{A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleCore", "..\tools\ModuleCore\src\ModuleCore.csproj", "{5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomData.ObjectContext", "..\test\FunctionalTests\Tools\CustomData.ObjectContext\CustomData.ObjectContext.csproj", "{6762001D-60F0-4E70-A816-C15897C39B7F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AstoriaTestFramework", "..\test\FunctionalTests\Framework\AstoriaTestFramework.csproj", "{F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AstoriaTestFramework.FullTrust", "..\test\FunctionalTests\Framework.FullTrust\AstoriaTestFramework.FullTrust.csproj", "{B01915F6-37E4-4E7E-9A45-8722829E49F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commander.Common", "..\test\FunctionalTests\Tools\Commander.Common\Commander.Common.csproj", "{39F660EC-A5D8-4753-B3F2-28117EB3224B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KoKoMo", "..\tools\KoKoMo\KoKoMo.csproj", "{EB4C9641-0452-4E7F-AA38-3EBD871914A3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.MetadataObjectModel.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\MetadataObjectModelTests\Microsoft.Data.MetadataObjectModel.UnitTests.csproj", "{69A5EB3B-5AE5-49AD-B4F3-025D087A4647}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Web.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\ServerUnitTests\Microsoft.Data.Web.UnitTests.csproj", "{BB7D22CB-7003-4AF6-9035-B369B00EA95E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Astoria.Northwind", "..\test\FunctionalTests\Tests\DataServices\Models\northwind\Astoria.Northwind.csproj", "{D93149FD-0D7F-41AB-AFCF-62270E7FD613}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Astoria.EFFKModel", "..\test\FunctionalTests\Tests\DataServices\Models\EFFK\Astoria.EFFKModel.csproj", "{3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.ServerUnitTests2.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\ServerUnitTests2\Microsoft.Data.ServerUnitTests2.UnitTests.csproj", "{406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.ServerUnitTests1.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\ServerUnitTests1\Microsoft.Data.ServerUnitTests1.UnitTests.csproj", "{99C025F4-3FA8-4078-BC9F-46774450B574}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.NamedStream.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\NamedStreamTests\Microsoft.Data.NamedStream.UnitTests.csproj", "{D94501EA-4606-4A82-A591-79473D9EE368}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.WebClientCSharp.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\ClientCSharpUnitTests\Microsoft.Data.WebClientCSharp.UnitTests.csproj", "{9B78C515-20E7-49FF-B8A1-90F3D585E88D}" +EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Microsoft.Data.WebClient.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\ClientUnitTests\Microsoft.Data.WebClient.UnitTests.vbproj", "{2560BD0F-D2A5-4DB1-B260-7C07DF506E11}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataWebRules", "..\tools\FxCopRules\DataWebRules.csproj", "{0854AF44-074A-41B4-909D-2AA4CAE82332}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.Design.T4.UnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\DesignT4UnitTests\Microsoft.OData.Service.Design.T4.UnitTests.csproj", "{4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Web.Design.T4", "..\src\CodeGen\Microsoft.Data.Web.Design.T4.csproj", "{E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Web.RegressionUnitTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\RegressionUnitTests\Microsoft.Data.Web.RegressionUnitTests.csproj", "{7BA26157-3062-4552-9C23-BB9B082C1ACE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Data.Services.DDBasics", "..\test\FunctionalTests\Tests\DataServices\ddbasics\Microsoft.Test.Data.Services.DDBasics.csproj", "{B9380603-5090-483C-ADF0-66D7FEE4DF7E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.OData.Reader.Tests", "..\test\FunctionalTests\Tests\DataOData\Tests\OData.Reader.Tests\Microsoft.Test.Taupo.OData.Reader.Tests.csproj", "{70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.OData.Common.Tests", "..\test\FunctionalTests\Tests\DataOData\Tests\OData.Common.Tests\Microsoft.Test.Taupo.OData.Common.Tests.csproj", "{1C872F31-6100-4C76-999E-ED948E2CD246}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.OData.Query.Tests", "..\test\FunctionalTests\Tests\DataOData\Tests\OData.Query.Tests\Microsoft.Test.Taupo.OData.Query.Tests.csproj", "{C0A623F5-7165-432C-A764-2E995FD9ADA9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.OData.Scenario.Tests", "..\test\FunctionalTests\Tests\DataOData\Tests\OData.Scenario.Tests\Microsoft.Test.Taupo.OData.Scenario.Tests.csproj", "{481C1E7B-1A96-4322-9479-2F9706656E71}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.OData.WCFService", "..\test\FunctionalTests\Tests\DataOData\Common\OData.WCFService\Microsoft.Test.Taupo.OData.WCFService.csproj", "{2A9B8663-D894-48B9-93C1-CD6B397ACAA0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.Taupo.OData.Writer.Tests", "..\test\FunctionalTests\Tests\DataOData\Tests\OData.Writer.Tests\Microsoft.Test.Taupo.OData.Writer.Tests.csproj", "{DB742DF2-9873-44CF-8439-DA5ACFF69A46}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.PluggableFormat", "..\test\FunctionalTests\Tests\DataOData\Tests\OData.PluggableFormat\Microsoft.Test.OData.PluggableFormat.csproj", "{1D872F31-6100-4C76-999E-ED948E2CD248}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.PluggableFormat.Tests", "..\test\FunctionalTests\Tests\DataOData\Tests\OData.PluggableFormat.Tests\Microsoft.Test.OData.PluggableFormat.Tests.csproj", "{1D872F31-6100-4C76-999E-ED948E2CD249}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.DependencyInjection", "..\test\Common\Microsoft.Test.OData.DependencyInjection\Build.NetFramework\Microsoft.Test.OData.DependencyInjection.csproj", "{50AA23B2-56EF-4C51-A270-8D5BD0C899B6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.TestCommon", "..\test\FunctionalTests\Microsoft.OData.TestCommon\Build.NetFramework\Microsoft.OData.TestCommon.csproj", "{9037FF4A-4636-41AA-BFA2-0930EF1563EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core.Tests", "..\test\FunctionalTests\Microsoft.OData.Core.Tests\Build.NetFramework\Microsoft.OData.Core.Tests.csproj", "{C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{818C5A92-D7B8-4704-89F3-32E422BFFB9D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9C93B744-F1C4-4E80-A705-8EF1FEF898CF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "helper", "helper", "{F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unitTests", "unitTests", "{8CCBB4F7-EBD1-48B6-8A77-BD733EB9CB6B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client.Tests", "..\test\FunctionalTests\Microsoft.OData.Client.Tests\Microsoft.OData.Client.Tests.csproj", "{9F0AB290-8164-4885-BFCA-A6F87AB81740}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm.Tests", "..\test\FunctionalTests\Microsoft.OData.Edm.Tests\Build.NetFramework\Microsoft.OData.Edm.Tests.csproj", "{419F42FA-3313-46A7-B519-B2ED042B0F5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial.Tests", "..\test\FunctionalTests\Microsoft.Spatial.Tests\Build.NetFramework\Microsoft.Spatial.Tests.csproj", "{68C25902-0FBD-4694-9EE9-47F2219CE039}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "e2eTests", "e2eTests", "{88BCAAF8-C1CD-41A0-81AA-BF4D80CADD8C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Tests.Client", "..\test\EndToEndTests\Tests\Client\Build.Desktop\Microsoft.Test.OData.Tests.Client.csproj", "{B57522A7-18B7-42F7-95FD-24835FC5AE46}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "taupo", "taupo", "{8A8C5DF6-B845-4044-8677-D877E6450CB4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Framework.TestProviders", "..\test\EndToEndTests\Framework\TestProviders\Microsoft.Test.OData.Framework.TestProviders.csproj", "{1211B700-008A-4646-8F5F-25BC96EBC138}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.Astoria", "..\test\EndToEndTests\Services\Astoria\Microsoft.Test.OData.Services.Astoria.csproj", "{ECF627BB-5156-488F-9F6B-CF6537F8D6FE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.CSDSCReferences", "..\test\EndToEndTests\Services\CSDSCReferences\Microsoft.Test.OData.Services.CSDSCReferences.csproj", "{AE503558-F7BC-4CE4-9A90-066B580BB1ED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataCamelCaseService", "..\test\EndToEndTests\Services\ODataCamelCaseService\Microsoft.Test.OData.Services.ODataCamelCaseService.csproj", "{5C59B211-002F-40AE-BED4-16470A8024E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataDefaultService", "..\test\EndToEndTests\Services\ODataDefaultService\Microsoft.Test.OData.Services.ODataDefaultService.csproj", "{9A2BEEC2-42C7-4EE8-B9D9-95746F197288}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataModelRefService", "..\test\EndToEndTests\Services\ODataModelRefService\Microsoft.Test.OData.Services.ODataModelRefService.csproj", "{D380441D-66B8-4728-B83C-F9EDAC7DEEC6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataOperationService", "..\test\EndToEndTests\Services\ODataOperationService\Microsoft.Test.OData.Services.ODataOperationService.csproj", "{6C3BBBBA-360B-40E3-A96E-DD0708DA6718}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataPluggableFormatService", "..\test\EndToEndTests\Services\ODataPluggableFormatService\Microsoft.Test.OData.Services.ODataPluggableFormatService.csproj", "{9A2BEEC2-42C7-4EE8-B9D9-95746F197291}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataSimplifiedService", "..\test\EndToEndTests\Services\ODataSimplifiedService\Microsoft.Test.OData.Services.ODataSimplifiedService.csproj", "{B34C9A66-22BE-4377-896D-009577D5E6FF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataTypeDefinitionService", "..\test\EndToEndTests\Services\ODataTypeDefinitionService\Microsoft.Test.OData.Services.ODataTypeDefinitionService.csproj", "{99FC4701-33CD-477D-913C-E39E957D1548}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.ODataWCFService", "..\test\EndToEndTests\Services\ODataWCFService\Microsoft.Test.OData.Services.ODataWCFService.csproj", "{7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.TestServices", "..\test\EndToEndTests\Services\TestServices\Microsoft.Test.OData.Services.TestServices.csproj", "{B8A072BE-5B90-4193-A289-6CC8750D0DAD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testServices", "testServices", "{A8A0976E-E1D8-4C0E-B425-B4F74798D478}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ODataSamples.Services.Core", "..\test\EndToEndTests\Services\ODataWCFLibrary\ODataSamples.Services.Core.csproj", "{DF028E55-CE75-4F32-822E-F9EC9C756AE2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ODataSamples.Services.TripPin", "..\test\EndToEndTests\Services\ODataTripPinService\ODataSamples.Services.TripPin.csproj", "{58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "astoria", "astoria", "{975D9E9C-F983-46E5-83E5-CEDEE845E2BF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "regressionTests", "regressionTests", "{15AE273F-373F-4870-9B73-8F3B9DB05F98}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "codeGen", "codeGen", "{AA695E63-EB7D-4A21-968C-0BAE14D8C435}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "codeGenTests", "codeGenTests", "{0035949E-4316-4146-AF73-84118CA8D758}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client.Portable", "..\src\Microsoft.OData.Client\Build.Portable\Microsoft.OData.Client.Portable.csproj", "{AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.E2E.Profile7", "..\test\EndToEndTests\Microsoft.OData.E2E.Portable\Microsoft.OData.E2E.Profile7.csproj", "{A5A3F799-3B12-4BE6-85C7-51AD75565DE8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.E2E.Profile111", "..\test\EndToEndTests\Microsoft.OData.E2E.Portable\Microsoft.OData.E2E.Profile111.csproj", "{8C9A1B86-4474-484C-9545-223F9C6185FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Framework.Portable", "..\test\EndToEndTests\Framework\Core\Build.PortableLibrary\Microsoft.Test.OData.Framework.Portable.csproj", "{74295872-F930-497B-A70E-ED4FAAB395C4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.TestServices.Portable", "..\test\EndToEndTests\Services\TestServices\Build.PortableLibrary\Microsoft.Test.OData.Services.TestServices.Portable.csproj", "{4382D649-1A86-48D0-9156-AC37C3D568C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceWrapperApp", "..\test\EndToEndTests\Services\ServiceWrapperApp\ServiceWrapperApp.csproj", "{333077F2-0DBA-42A1-A5E1-E86361D96074}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "T4CrossPlatformTests", "..\test\FunctionalTests\Tests\DataServices\UnitTests\DesignT4CrossPlatformTests\CSharp\T4CrossPlatformTests.csproj", "{65DCD663-02DB-4E45-B41E-C7B7D3B11871}" +EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "T4CrossPlatformTestsVB", "..\test\FunctionalTests\Tests\DataServices\UnitTests\DesignT4CrossPlatformTests\VBasic\T4CrossPlatformTestsVB.vbproj", "{BE6827D5-E301-47DD-8CAC-E0DA555AB64F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Cover|Any CPU.Build.0 = Cover|Any CPU + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Cover|x64.ActiveCfg = Cover|x64 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Cover|x64.Build.0 = Cover|x64 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Cover|x86.ActiveCfg = Cover|x86 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Cover|x86.Build.0 = Cover|x86 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Debug|x64.ActiveCfg = Debug|x64 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Debug|x64.Build.0 = Debug|x64 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Debug|x86.ActiveCfg = Debug|x86 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Debug|x86.Build.0 = Debug|x86 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Release|Any CPU.Build.0 = Release|Any CPU + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Release|x64.ActiveCfg = Release|x64 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Release|x64.Build.0 = Release|x64 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Release|x86.ActiveCfg = Release|x86 + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A}.Release|x86.Build.0 = Release|x86 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Cover|Any CPU.Build.0 = Cover|Any CPU + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Cover|x64.ActiveCfg = Cover|x64 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Cover|x64.Build.0 = Cover|x64 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Cover|x86.ActiveCfg = Cover|x86 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Cover|x86.Build.0 = Cover|x86 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Debug|x64.ActiveCfg = Debug|x64 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Debug|x64.Build.0 = Debug|x64 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Debug|x86.ActiveCfg = Debug|x86 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Debug|x86.Build.0 = Debug|x86 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Release|Any CPU.Build.0 = Release|Any CPU + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Release|x64.ActiveCfg = Release|x64 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Release|x64.Build.0 = Release|x64 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Release|x86.ActiveCfg = Release|x86 + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A}.Release|x86.Build.0 = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.ActiveCfg = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.Build.0 = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.ActiveCfg = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.Build.0 = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.ActiveCfg = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.Build.0 = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.ActiveCfg = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.Build.0 = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.Build.0 = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.ActiveCfg = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.Build.0 = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.Build.0 = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.Build.0 = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.ActiveCfg = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.Build.0 = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.ActiveCfg = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.Build.0 = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.ActiveCfg = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.Build.0 = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.ActiveCfg = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.Build.0 = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.Build.0 = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.ActiveCfg = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.Build.0 = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.Build.0 = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.ActiveCfg = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.Build.0 = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.ActiveCfg = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.Build.0 = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.ActiveCfg = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.Build.0 = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.ActiveCfg = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.Build.0 = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.Build.0 = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.ActiveCfg = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.Build.0 = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.Build.0 = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|Any CPU.Build.0 = Cover|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x64.ActiveCfg = Cover|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x64.Build.0 = Cover|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x86.ActiveCfg = Cover|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Cover|x86.Build.0 = Cover|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x64.ActiveCfg = Debug|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x64.Build.0 = Debug|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x86.ActiveCfg = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Debug|x86.Build.0 = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|Any CPU.Build.0 = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x64.ActiveCfg = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x64.Build.0 = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x86.ActiveCfg = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF}.Release|x86.Build.0 = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Cover|Any CPU.Build.0 = Cover|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Cover|x64.ActiveCfg = Cover|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Cover|x64.Build.0 = Cover|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Cover|x86.ActiveCfg = Cover|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Cover|x86.Build.0 = Cover|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Debug|x64.ActiveCfg = Debug|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Debug|x64.Build.0 = Debug|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Debug|x86.ActiveCfg = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Debug|x86.Build.0 = Debug|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Release|Any CPU.Build.0 = Release|Any CPU + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Release|x64.ActiveCfg = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Release|x64.Build.0 = Release|x64 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Release|x86.ActiveCfg = Release|x86 + {D1567C63-4A0D-4E18-A14E-79699B9BA325}.Release|x86.Build.0 = Release|x86 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Cover|Any CPU.Build.0 = Cover|Any CPU + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Cover|x64.ActiveCfg = Cover|x64 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Cover|x64.Build.0 = Cover|x64 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Cover|x86.ActiveCfg = Cover|x86 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Cover|x86.Build.0 = Cover|x86 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Debug|x64.ActiveCfg = Debug|x64 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Debug|x64.Build.0 = Debug|x64 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Debug|x86.ActiveCfg = Debug|x86 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Debug|x86.Build.0 = Debug|x86 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Release|Any CPU.Build.0 = Release|Any CPU + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Release|x64.ActiveCfg = Release|x64 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Release|x64.Build.0 = Release|x64 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Release|x86.ActiveCfg = Release|x86 + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00}.Release|x86.Build.0 = Release|x86 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Cover|Any CPU.Build.0 = Cover|Any CPU + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Cover|x64.ActiveCfg = Cover|x64 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Cover|x64.Build.0 = Cover|x64 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Cover|x86.ActiveCfg = Cover|x86 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Cover|x86.Build.0 = Cover|x86 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Debug|x64.ActiveCfg = Debug|x64 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Debug|x64.Build.0 = Debug|x64 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Debug|x86.ActiveCfg = Debug|x86 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Debug|x86.Build.0 = Debug|x86 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Release|Any CPU.Build.0 = Release|Any CPU + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Release|x64.ActiveCfg = Release|x64 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Release|x64.Build.0 = Release|x64 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Release|x86.ActiveCfg = Release|x86 + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270}.Release|x86.Build.0 = Release|x86 + {1D54775E-E211-4B35-A002-E53416BAE743}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {1D54775E-E211-4B35-A002-E53416BAE743}.Cover|Any CPU.Build.0 = Cover|Any CPU + {1D54775E-E211-4B35-A002-E53416BAE743}.Cover|x64.ActiveCfg = Cover|x64 + {1D54775E-E211-4B35-A002-E53416BAE743}.Cover|x64.Build.0 = Cover|x64 + {1D54775E-E211-4B35-A002-E53416BAE743}.Cover|x86.ActiveCfg = Cover|x86 + {1D54775E-E211-4B35-A002-E53416BAE743}.Cover|x86.Build.0 = Cover|x86 + {1D54775E-E211-4B35-A002-E53416BAE743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D54775E-E211-4B35-A002-E53416BAE743}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D54775E-E211-4B35-A002-E53416BAE743}.Debug|x64.ActiveCfg = Debug|x64 + {1D54775E-E211-4B35-A002-E53416BAE743}.Debug|x64.Build.0 = Debug|x64 + {1D54775E-E211-4B35-A002-E53416BAE743}.Debug|x86.ActiveCfg = Debug|x86 + {1D54775E-E211-4B35-A002-E53416BAE743}.Debug|x86.Build.0 = Debug|x86 + {1D54775E-E211-4B35-A002-E53416BAE743}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D54775E-E211-4B35-A002-E53416BAE743}.Release|Any CPU.Build.0 = Release|Any CPU + {1D54775E-E211-4B35-A002-E53416BAE743}.Release|x64.ActiveCfg = Release|x64 + {1D54775E-E211-4B35-A002-E53416BAE743}.Release|x64.Build.0 = Release|x64 + {1D54775E-E211-4B35-A002-E53416BAE743}.Release|x86.ActiveCfg = Release|x86 + {1D54775E-E211-4B35-A002-E53416BAE743}.Release|x86.Build.0 = Release|x86 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Cover|Any CPU.Build.0 = Cover|Any CPU + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Cover|x64.ActiveCfg = Cover|x64 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Cover|x64.Build.0 = Cover|x64 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Cover|x86.ActiveCfg = Cover|x86 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Cover|x86.Build.0 = Cover|x86 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Debug|x64.ActiveCfg = Debug|x64 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Debug|x64.Build.0 = Debug|x64 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Debug|x86.ActiveCfg = Debug|x86 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Debug|x86.Build.0 = Debug|x86 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Release|Any CPU.Build.0 = Release|Any CPU + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Release|x64.ActiveCfg = Release|x64 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Release|x64.Build.0 = Release|x64 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Release|x86.ActiveCfg = Release|x86 + {EDC237AA-6875-4034-BA39-7F2A83533BFC}.Release|x86.Build.0 = Release|x86 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Cover|x64.ActiveCfg = Cover|x64 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Cover|x64.Build.0 = Cover|x64 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Cover|x86.ActiveCfg = Cover|x86 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Cover|x86.Build.0 = Cover|x86 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Debug|x64.ActiveCfg = Debug|x64 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Debug|x64.Build.0 = Debug|x64 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Debug|x86.ActiveCfg = Debug|x86 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Debug|x86.Build.0 = Debug|x86 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Release|Any CPU.Build.0 = Release|Any CPU + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Release|x64.ActiveCfg = Release|x64 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Release|x64.Build.0 = Release|x64 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Release|x86.ActiveCfg = Release|x86 + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D}.Release|x86.Build.0 = Release|x86 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Cover|Any CPU.Build.0 = Cover|Any CPU + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Cover|x64.ActiveCfg = Cover|x64 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Cover|x64.Build.0 = Cover|x64 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Cover|x86.ActiveCfg = Cover|x86 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Cover|x86.Build.0 = Cover|x86 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Debug|x64.ActiveCfg = Debug|x64 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Debug|x64.Build.0 = Debug|x64 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Debug|x86.ActiveCfg = Debug|x86 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Debug|x86.Build.0 = Debug|x86 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Release|Any CPU.Build.0 = Release|Any CPU + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Release|x64.ActiveCfg = Release|x64 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Release|x64.Build.0 = Release|x64 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Release|x86.ActiveCfg = Release|x86 + {34010F3A-20CC-479C-83CF-EC99B1C90CD1}.Release|x86.Build.0 = Release|x86 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Cover|Any CPU.Build.0 = Cover|Any CPU + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Cover|x64.ActiveCfg = Cover|x64 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Cover|x64.Build.0 = Cover|x64 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Cover|x86.ActiveCfg = Cover|x86 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Cover|x86.Build.0 = Cover|x86 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Debug|x64.ActiveCfg = Debug|x64 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Debug|x64.Build.0 = Debug|x64 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Debug|x86.ActiveCfg = Debug|x86 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Debug|x86.Build.0 = Debug|x86 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Release|Any CPU.Build.0 = Release|Any CPU + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Release|x64.ActiveCfg = Release|x64 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Release|x64.Build.0 = Release|x64 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Release|x86.ActiveCfg = Release|x86 + {29236BBA-852B-46B2-A39B-09DB47A7F6EB}.Release|x86.Build.0 = Release|x86 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Cover|Any CPU.Build.0 = Cover|Any CPU + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Cover|x64.ActiveCfg = Cover|x64 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Cover|x64.Build.0 = Cover|x64 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Cover|x86.ActiveCfg = Cover|x86 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Cover|x86.Build.0 = Cover|x86 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Debug|x64.ActiveCfg = Debug|x64 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Debug|x64.Build.0 = Debug|x64 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Debug|x86.ActiveCfg = Debug|x86 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Debug|x86.Build.0 = Debug|x86 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Release|Any CPU.Build.0 = Release|Any CPU + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Release|x64.ActiveCfg = Release|x64 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Release|x64.Build.0 = Release|x64 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Release|x86.ActiveCfg = Release|x86 + {854AF4E9-B78F-4994-B9C5-82B846604CBE}.Release|x86.Build.0 = Release|x86 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Cover|Any CPU.Build.0 = Cover|Any CPU + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Cover|x64.ActiveCfg = Cover|x64 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Cover|x64.Build.0 = Cover|x64 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Cover|x86.ActiveCfg = Cover|x86 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Cover|x86.Build.0 = Cover|x86 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Debug|x64.ActiveCfg = Debug|x64 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Debug|x64.Build.0 = Debug|x64 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Debug|x86.ActiveCfg = Debug|x86 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Debug|x86.Build.0 = Debug|x86 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Release|Any CPU.Build.0 = Release|Any CPU + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Release|x64.ActiveCfg = Release|x64 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Release|x64.Build.0 = Release|x64 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Release|x86.ActiveCfg = Release|x86 + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC}.Release|x86.Build.0 = Release|x86 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Cover|Any CPU.Build.0 = Cover|Any CPU + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Cover|x64.ActiveCfg = Cover|x64 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Cover|x64.Build.0 = Cover|x64 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Cover|x86.ActiveCfg = Cover|x86 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Cover|x86.Build.0 = Cover|x86 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Debug|x64.ActiveCfg = Debug|x64 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Debug|x64.Build.0 = Debug|x64 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Debug|x86.ActiveCfg = Debug|x86 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Debug|x86.Build.0 = Debug|x86 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Release|Any CPU.Build.0 = Release|Any CPU + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Release|x64.ActiveCfg = Release|x64 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Release|x64.Build.0 = Release|x64 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Release|x86.ActiveCfg = Release|x86 + {834DEA16-4215-41D1-99E7-FBE41A3326DA}.Release|x86.Build.0 = Release|x86 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Cover|Any CPU.Build.0 = Cover|Any CPU + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Cover|x64.ActiveCfg = Cover|x64 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Cover|x64.Build.0 = Cover|x64 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Cover|x86.ActiveCfg = Cover|x86 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Cover|x86.Build.0 = Cover|x86 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Debug|x64.ActiveCfg = Debug|x64 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Debug|x64.Build.0 = Debug|x64 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Debug|x86.ActiveCfg = Debug|x86 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Debug|x86.Build.0 = Debug|x86 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Release|Any CPU.Build.0 = Release|Any CPU + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Release|x64.ActiveCfg = Release|x64 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Release|x64.Build.0 = Release|x64 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Release|x86.ActiveCfg = Release|x86 + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B}.Release|x86.Build.0 = Release|x86 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Cover|Any CPU.Build.0 = Cover|Any CPU + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Cover|x64.ActiveCfg = Cover|x64 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Cover|x64.Build.0 = Cover|x64 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Cover|x86.ActiveCfg = Cover|x86 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Cover|x86.Build.0 = Cover|x86 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Debug|x64.ActiveCfg = Debug|x64 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Debug|x64.Build.0 = Debug|x64 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Debug|x86.ActiveCfg = Debug|x86 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Debug|x86.Build.0 = Debug|x86 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Release|Any CPU.Build.0 = Release|Any CPU + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Release|x64.ActiveCfg = Release|x64 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Release|x64.Build.0 = Release|x64 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Release|x86.ActiveCfg = Release|x86 + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67}.Release|x86.Build.0 = Release|x86 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Cover|x64.ActiveCfg = Cover|x64 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Cover|x64.Build.0 = Cover|x64 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Cover|x86.ActiveCfg = Cover|x86 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Cover|x86.Build.0 = Cover|x86 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Debug|x64.ActiveCfg = Debug|x64 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Debug|x64.Build.0 = Debug|x64 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Debug|x86.ActiveCfg = Debug|x86 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Debug|x86.Build.0 = Debug|x86 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Release|Any CPU.Build.0 = Release|Any CPU + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Release|x64.ActiveCfg = Release|x64 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Release|x64.Build.0 = Release|x64 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Release|x86.ActiveCfg = Release|x86 + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D}.Release|x86.Build.0 = Release|x86 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {6762001D-60F0-4E70-A816-C15897C39B7F}.Cover|Any CPU.Build.0 = Cover|Any CPU + {6762001D-60F0-4E70-A816-C15897C39B7F}.Cover|x64.ActiveCfg = Cover|x64 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Cover|x64.Build.0 = Cover|x64 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Cover|x86.ActiveCfg = Cover|x86 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Cover|x86.Build.0 = Cover|x86 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6762001D-60F0-4E70-A816-C15897C39B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6762001D-60F0-4E70-A816-C15897C39B7F}.Debug|x64.ActiveCfg = Debug|x64 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Debug|x64.Build.0 = Debug|x64 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Debug|x86.ActiveCfg = Debug|x86 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Debug|x86.Build.0 = Debug|x86 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6762001D-60F0-4E70-A816-C15897C39B7F}.Release|Any CPU.Build.0 = Release|Any CPU + {6762001D-60F0-4E70-A816-C15897C39B7F}.Release|x64.ActiveCfg = Release|x64 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Release|x64.Build.0 = Release|x64 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Release|x86.ActiveCfg = Release|x86 + {6762001D-60F0-4E70-A816-C15897C39B7F}.Release|x86.Build.0 = Release|x86 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Cover|Any CPU.Build.0 = Cover|Any CPU + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Cover|x64.ActiveCfg = Cover|x64 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Cover|x64.Build.0 = Cover|x64 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Cover|x86.ActiveCfg = Cover|x86 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Cover|x86.Build.0 = Cover|x86 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Debug|x64.ActiveCfg = Debug|x64 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Debug|x64.Build.0 = Debug|x64 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Debug|x86.ActiveCfg = Debug|x86 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Debug|x86.Build.0 = Debug|x86 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Release|Any CPU.Build.0 = Release|Any CPU + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Release|x64.ActiveCfg = Release|x64 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Release|x64.Build.0 = Release|x64 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Release|x86.ActiveCfg = Release|x86 + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB}.Release|x86.Build.0 = Release|x86 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Cover|Any CPU.Build.0 = Cover|Any CPU + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Cover|x64.ActiveCfg = Cover|x64 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Cover|x64.Build.0 = Cover|x64 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Cover|x86.ActiveCfg = Cover|x86 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Cover|x86.Build.0 = Cover|x86 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Debug|x64.ActiveCfg = Debug|x64 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Debug|x64.Build.0 = Debug|x64 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Debug|x86.ActiveCfg = Debug|x86 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Debug|x86.Build.0 = Debug|x86 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Release|Any CPU.Build.0 = Release|Any CPU + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Release|x64.ActiveCfg = Release|x64 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Release|x64.Build.0 = Release|x64 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Release|x86.ActiveCfg = Release|x86 + {B01915F6-37E4-4E7E-9A45-8722829E49F1}.Release|x86.Build.0 = Release|x86 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Cover|Any CPU.Build.0 = Cover|Any CPU + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Cover|x64.ActiveCfg = Cover|x64 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Cover|x64.Build.0 = Cover|x64 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Cover|x86.ActiveCfg = Cover|x86 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Cover|x86.Build.0 = Cover|x86 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Debug|x64.ActiveCfg = Debug|x64 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Debug|x64.Build.0 = Debug|x64 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Debug|x86.ActiveCfg = Debug|x86 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Debug|x86.Build.0 = Debug|x86 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Release|Any CPU.Build.0 = Release|Any CPU + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Release|x64.ActiveCfg = Release|x64 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Release|x64.Build.0 = Release|x64 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Release|x86.ActiveCfg = Release|x86 + {39F660EC-A5D8-4753-B3F2-28117EB3224B}.Release|x86.Build.0 = Release|x86 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Cover|Any CPU.Build.0 = Cover|Any CPU + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Cover|x64.ActiveCfg = Cover|x64 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Cover|x64.Build.0 = Cover|x64 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Cover|x86.ActiveCfg = Cover|x86 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Cover|x86.Build.0 = Cover|x86 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Debug|x64.ActiveCfg = Debug|x64 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Debug|x64.Build.0 = Debug|x64 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Debug|x86.ActiveCfg = Debug|x86 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Debug|x86.Build.0 = Debug|x86 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Release|Any CPU.Build.0 = Release|Any CPU + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Release|x64.ActiveCfg = Release|x64 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Release|x64.Build.0 = Release|x64 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Release|x86.ActiveCfg = Release|x86 + {EB4C9641-0452-4E7F-AA38-3EBD871914A3}.Release|x86.Build.0 = Release|x86 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Cover|Any CPU.Build.0 = Cover|Any CPU + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Cover|x64.ActiveCfg = Cover|x64 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Cover|x64.Build.0 = Cover|x64 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Cover|x86.ActiveCfg = Cover|x86 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Cover|x86.Build.0 = Cover|x86 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Debug|x64.ActiveCfg = Debug|x64 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Debug|x64.Build.0 = Debug|x64 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Debug|x86.ActiveCfg = Debug|x86 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Debug|x86.Build.0 = Debug|x86 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Release|Any CPU.Build.0 = Release|Any CPU + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Release|x64.ActiveCfg = Release|x64 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Release|x64.Build.0 = Release|x64 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Release|x86.ActiveCfg = Release|x86 + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647}.Release|x86.Build.0 = Release|x86 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Cover|Any CPU.Build.0 = Cover|Any CPU + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Cover|x64.ActiveCfg = Cover|x64 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Cover|x64.Build.0 = Cover|x64 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Cover|x86.ActiveCfg = Cover|x86 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Cover|x86.Build.0 = Cover|x86 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Debug|x64.ActiveCfg = Debug|x64 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Debug|x64.Build.0 = Debug|x64 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Debug|x86.ActiveCfg = Debug|x86 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Debug|x86.Build.0 = Debug|x86 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Release|Any CPU.Build.0 = Release|Any CPU + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Release|x64.ActiveCfg = Release|x64 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Release|x64.Build.0 = Release|x64 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Release|x86.ActiveCfg = Release|x86 + {BB7D22CB-7003-4AF6-9035-B369B00EA95E}.Release|x86.Build.0 = Release|x86 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Cover|Any CPU.Build.0 = Cover|Any CPU + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Cover|x64.ActiveCfg = Cover|x64 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Cover|x64.Build.0 = Cover|x64 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Cover|x86.ActiveCfg = Cover|x86 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Cover|x86.Build.0 = Cover|x86 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Debug|x64.ActiveCfg = Debug|x64 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Debug|x64.Build.0 = Debug|x64 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Debug|x86.ActiveCfg = Debug|x86 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Debug|x86.Build.0 = Debug|x86 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Release|Any CPU.Build.0 = Release|Any CPU + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Release|x64.ActiveCfg = Release|x64 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Release|x64.Build.0 = Release|x64 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Release|x86.ActiveCfg = Release|x86 + {D93149FD-0D7F-41AB-AFCF-62270E7FD613}.Release|x86.Build.0 = Release|x86 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Cover|Any CPU.Build.0 = Cover|Any CPU + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Cover|x64.ActiveCfg = Cover|x64 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Cover|x64.Build.0 = Cover|x64 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Cover|x86.ActiveCfg = Cover|x86 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Cover|x86.Build.0 = Cover|x86 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Debug|x64.ActiveCfg = Debug|x64 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Debug|x64.Build.0 = Debug|x64 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Debug|x86.ActiveCfg = Debug|x86 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Debug|x86.Build.0 = Debug|x86 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Release|Any CPU.Build.0 = Release|Any CPU + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Release|x64.ActiveCfg = Release|x64 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Release|x64.Build.0 = Release|x64 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Release|x86.ActiveCfg = Release|x86 + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B}.Release|x86.Build.0 = Release|x86 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Cover|Any CPU.Build.0 = Cover|Any CPU + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Cover|x64.ActiveCfg = Cover|x64 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Cover|x64.Build.0 = Cover|x64 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Cover|x86.ActiveCfg = Cover|x86 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Cover|x86.Build.0 = Cover|x86 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Debug|x64.ActiveCfg = Debug|x64 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Debug|x64.Build.0 = Debug|x64 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Debug|x86.ActiveCfg = Debug|x86 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Debug|x86.Build.0 = Debug|x86 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Release|Any CPU.Build.0 = Release|Any CPU + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Release|x64.ActiveCfg = Release|x64 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Release|x64.Build.0 = Release|x64 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Release|x86.ActiveCfg = Release|x86 + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE}.Release|x86.Build.0 = Release|x86 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {99C025F4-3FA8-4078-BC9F-46774450B574}.Cover|Any CPU.Build.0 = Cover|Any CPU + {99C025F4-3FA8-4078-BC9F-46774450B574}.Cover|x64.ActiveCfg = Cover|x64 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Cover|x64.Build.0 = Cover|x64 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Cover|x86.ActiveCfg = Cover|x86 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Cover|x86.Build.0 = Cover|x86 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99C025F4-3FA8-4078-BC9F-46774450B574}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99C025F4-3FA8-4078-BC9F-46774450B574}.Debug|x64.ActiveCfg = Debug|x64 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Debug|x64.Build.0 = Debug|x64 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Debug|x86.ActiveCfg = Debug|x86 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Debug|x86.Build.0 = Debug|x86 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99C025F4-3FA8-4078-BC9F-46774450B574}.Release|Any CPU.Build.0 = Release|Any CPU + {99C025F4-3FA8-4078-BC9F-46774450B574}.Release|x64.ActiveCfg = Release|x64 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Release|x64.Build.0 = Release|x64 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Release|x86.ActiveCfg = Release|x86 + {99C025F4-3FA8-4078-BC9F-46774450B574}.Release|x86.Build.0 = Release|x86 + {D94501EA-4606-4A82-A591-79473D9EE368}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {D94501EA-4606-4A82-A591-79473D9EE368}.Cover|Any CPU.Build.0 = Cover|Any CPU + {D94501EA-4606-4A82-A591-79473D9EE368}.Cover|x64.ActiveCfg = Cover|x64 + {D94501EA-4606-4A82-A591-79473D9EE368}.Cover|x64.Build.0 = Cover|x64 + {D94501EA-4606-4A82-A591-79473D9EE368}.Cover|x86.ActiveCfg = Cover|x86 + {D94501EA-4606-4A82-A591-79473D9EE368}.Cover|x86.Build.0 = Cover|x86 + {D94501EA-4606-4A82-A591-79473D9EE368}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D94501EA-4606-4A82-A591-79473D9EE368}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D94501EA-4606-4A82-A591-79473D9EE368}.Debug|x64.ActiveCfg = Debug|x64 + {D94501EA-4606-4A82-A591-79473D9EE368}.Debug|x64.Build.0 = Debug|x64 + {D94501EA-4606-4A82-A591-79473D9EE368}.Debug|x86.ActiveCfg = Debug|x86 + {D94501EA-4606-4A82-A591-79473D9EE368}.Debug|x86.Build.0 = Debug|x86 + {D94501EA-4606-4A82-A591-79473D9EE368}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D94501EA-4606-4A82-A591-79473D9EE368}.Release|Any CPU.Build.0 = Release|Any CPU + {D94501EA-4606-4A82-A591-79473D9EE368}.Release|x64.ActiveCfg = Release|x64 + {D94501EA-4606-4A82-A591-79473D9EE368}.Release|x64.Build.0 = Release|x64 + {D94501EA-4606-4A82-A591-79473D9EE368}.Release|x86.ActiveCfg = Release|x86 + {D94501EA-4606-4A82-A591-79473D9EE368}.Release|x86.Build.0 = Release|x86 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Cover|x64.ActiveCfg = Cover|x64 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Cover|x64.Build.0 = Cover|x64 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Cover|x86.ActiveCfg = Cover|x86 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Cover|x86.Build.0 = Cover|x86 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Debug|x64.ActiveCfg = Debug|x64 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Debug|x64.Build.0 = Debug|x64 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Debug|x86.ActiveCfg = Debug|x86 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Debug|x86.Build.0 = Debug|x86 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Release|Any CPU.Build.0 = Release|Any CPU + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Release|x64.ActiveCfg = Release|x64 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Release|x64.Build.0 = Release|x64 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Release|x86.ActiveCfg = Release|x86 + {9B78C515-20E7-49FF-B8A1-90F3D585E88D}.Release|x86.Build.0 = Release|x86 + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Cover|Any CPU.Build.0 = Cover|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Cover|x64.ActiveCfg = Cover|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Cover|x86.ActiveCfg = Cover|x86 + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Cover|x86.Build.0 = Cover|x86 + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Debug|x64.ActiveCfg = Debug|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Debug|x86.ActiveCfg = Debug|x86 + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Debug|x86.Build.0 = Debug|x86 + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Release|Any CPU.Build.0 = Release|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Release|x64.ActiveCfg = Release|Any CPU + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Release|x86.ActiveCfg = Release|x86 + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11}.Release|x86.Build.0 = Release|x86 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Cover|Any CPU.Build.0 = Cover|Any CPU + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Cover|x64.ActiveCfg = Cover|x64 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Cover|x64.Build.0 = Cover|x64 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Cover|x86.ActiveCfg = Cover|x86 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Cover|x86.Build.0 = Cover|x86 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Debug|x64.ActiveCfg = Debug|x64 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Debug|x64.Build.0 = Debug|x64 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Debug|x86.ActiveCfg = Debug|x86 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Debug|x86.Build.0 = Debug|x86 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Release|Any CPU.Build.0 = Release|Any CPU + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Release|x64.ActiveCfg = Release|x64 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Release|x64.Build.0 = Release|x64 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Release|x86.ActiveCfg = Release|x86 + {0854AF44-074A-41B4-909D-2AA4CAE82332}.Release|x86.Build.0 = Release|x86 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Cover|Any CPU.Build.0 = Cover|Any CPU + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Cover|x64.ActiveCfg = Cover|x64 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Cover|x64.Build.0 = Cover|x64 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Cover|x86.ActiveCfg = Cover|x86 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Cover|x86.Build.0 = Cover|x86 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Debug|x64.ActiveCfg = Debug|x64 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Debug|x64.Build.0 = Debug|x64 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Debug|x86.ActiveCfg = Debug|x86 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Debug|x86.Build.0 = Debug|x86 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Release|Any CPU.Build.0 = Release|Any CPU + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Release|x64.ActiveCfg = Release|x64 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Release|x64.Build.0 = Release|x64 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Release|x86.ActiveCfg = Release|x86 + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B}.Release|x86.Build.0 = Release|x86 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Cover|x64.ActiveCfg = Cover|x64 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Cover|x64.Build.0 = Cover|x64 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Cover|x86.ActiveCfg = Cover|x86 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Cover|x86.Build.0 = Cover|x86 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Debug|x64.ActiveCfg = Debug|x64 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Debug|x64.Build.0 = Debug|x64 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Debug|x86.ActiveCfg = Debug|x86 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Debug|x86.Build.0 = Debug|x86 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Release|Any CPU.Build.0 = Release|Any CPU + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Release|x64.ActiveCfg = Release|x64 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Release|x64.Build.0 = Release|x64 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Release|x86.ActiveCfg = Release|x86 + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D}.Release|x86.Build.0 = Release|x86 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Cover|x64.ActiveCfg = Cover|x64 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Cover|x64.Build.0 = Cover|x64 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Cover|x86.ActiveCfg = Cover|x86 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Cover|x86.Build.0 = Cover|x86 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Debug|x64.ActiveCfg = Debug|x64 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Debug|x64.Build.0 = Debug|x64 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Debug|x86.ActiveCfg = Debug|x86 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Debug|x86.Build.0 = Debug|x86 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Release|Any CPU.Build.0 = Release|Any CPU + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Release|x64.ActiveCfg = Release|x64 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Release|x64.Build.0 = Release|x64 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Release|x86.ActiveCfg = Release|x86 + {7BA26157-3062-4552-9C23-BB9B082C1ACE}.Release|x86.Build.0 = Release|x86 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Cover|Any CPU.Build.0 = Cover|Any CPU + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Cover|x64.ActiveCfg = Cover|x64 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Cover|x64.Build.0 = Cover|x64 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Cover|x86.ActiveCfg = Cover|x86 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Cover|x86.Build.0 = Cover|x86 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Debug|x64.ActiveCfg = Debug|x64 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Debug|x64.Build.0 = Debug|x64 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Debug|x86.ActiveCfg = Debug|x86 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Debug|x86.Build.0 = Debug|x86 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Release|Any CPU.Build.0 = Release|Any CPU + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Release|x64.ActiveCfg = Release|x64 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Release|x64.Build.0 = Release|x64 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Release|x86.ActiveCfg = Release|x86 + {B9380603-5090-483C-ADF0-66D7FEE4DF7E}.Release|x86.Build.0 = Release|x86 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Cover|Any CPU.Build.0 = Cover|Any CPU + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Cover|x64.ActiveCfg = Cover|x64 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Cover|x64.Build.0 = Cover|x64 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Cover|x86.ActiveCfg = Cover|x86 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Cover|x86.Build.0 = Cover|x86 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Debug|x64.ActiveCfg = Debug|x64 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Debug|x64.Build.0 = Debug|x64 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Debug|x86.ActiveCfg = Debug|x86 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Debug|x86.Build.0 = Debug|x86 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Release|Any CPU.Build.0 = Release|Any CPU + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Release|x64.ActiveCfg = Release|x64 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Release|x64.Build.0 = Release|x64 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Release|x86.ActiveCfg = Release|x86 + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6}.Release|x86.Build.0 = Release|x86 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {1C872F31-6100-4C76-999E-ED948E2CD246}.Cover|Any CPU.Build.0 = Cover|Any CPU + {1C872F31-6100-4C76-999E-ED948E2CD246}.Cover|x64.ActiveCfg = Cover|x64 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Cover|x64.Build.0 = Cover|x64 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Cover|x86.ActiveCfg = Cover|x86 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Cover|x86.Build.0 = Cover|x86 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C872F31-6100-4C76-999E-ED948E2CD246}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C872F31-6100-4C76-999E-ED948E2CD246}.Debug|x64.ActiveCfg = Debug|x64 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Debug|x64.Build.0 = Debug|x64 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Debug|x86.ActiveCfg = Debug|x86 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Debug|x86.Build.0 = Debug|x86 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C872F31-6100-4C76-999E-ED948E2CD246}.Release|Any CPU.Build.0 = Release|Any CPU + {1C872F31-6100-4C76-999E-ED948E2CD246}.Release|x64.ActiveCfg = Release|x64 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Release|x64.Build.0 = Release|x64 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Release|x86.ActiveCfg = Release|x86 + {1C872F31-6100-4C76-999E-ED948E2CD246}.Release|x86.Build.0 = Release|x86 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Cover|Any CPU.Build.0 = Cover|Any CPU + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Cover|x64.ActiveCfg = Cover|x64 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Cover|x64.Build.0 = Cover|x64 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Cover|x86.ActiveCfg = Cover|x86 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Cover|x86.Build.0 = Cover|x86 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Debug|x64.ActiveCfg = Debug|x64 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Debug|x64.Build.0 = Debug|x64 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Debug|x86.ActiveCfg = Debug|x86 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Debug|x86.Build.0 = Debug|x86 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Release|Any CPU.Build.0 = Release|Any CPU + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Release|x64.ActiveCfg = Release|x64 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Release|x64.Build.0 = Release|x64 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Release|x86.ActiveCfg = Release|x86 + {C0A623F5-7165-432C-A764-2E995FD9ADA9}.Release|x86.Build.0 = Release|x86 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {481C1E7B-1A96-4322-9479-2F9706656E71}.Cover|Any CPU.Build.0 = Cover|Any CPU + {481C1E7B-1A96-4322-9479-2F9706656E71}.Cover|x64.ActiveCfg = Cover|x64 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Cover|x64.Build.0 = Cover|x64 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Cover|x86.ActiveCfg = Cover|x86 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Cover|x86.Build.0 = Cover|x86 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {481C1E7B-1A96-4322-9479-2F9706656E71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {481C1E7B-1A96-4322-9479-2F9706656E71}.Debug|x64.ActiveCfg = Debug|x64 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Debug|x64.Build.0 = Debug|x64 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Debug|x86.ActiveCfg = Debug|x86 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Debug|x86.Build.0 = Debug|x86 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {481C1E7B-1A96-4322-9479-2F9706656E71}.Release|Any CPU.Build.0 = Release|Any CPU + {481C1E7B-1A96-4322-9479-2F9706656E71}.Release|x64.ActiveCfg = Release|x64 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Release|x64.Build.0 = Release|x64 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Release|x86.ActiveCfg = Release|x86 + {481C1E7B-1A96-4322-9479-2F9706656E71}.Release|x86.Build.0 = Release|x86 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Cover|Any CPU.Build.0 = Cover|Any CPU + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Cover|x64.ActiveCfg = Cover|x64 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Cover|x64.Build.0 = Cover|x64 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Cover|x86.ActiveCfg = Cover|x86 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Cover|x86.Build.0 = Cover|x86 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Debug|x64.ActiveCfg = Debug|x64 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Debug|x64.Build.0 = Debug|x64 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Debug|x86.ActiveCfg = Debug|x86 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Debug|x86.Build.0 = Debug|x86 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Release|Any CPU.Build.0 = Release|Any CPU + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Release|x64.ActiveCfg = Release|x64 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Release|x64.Build.0 = Release|x64 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Release|x86.ActiveCfg = Release|x86 + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0}.Release|x86.Build.0 = Release|x86 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Cover|Any CPU.Build.0 = Cover|Any CPU + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Cover|x64.ActiveCfg = Cover|x64 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Cover|x64.Build.0 = Cover|x64 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Cover|x86.ActiveCfg = Cover|x86 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Cover|x86.Build.0 = Cover|x86 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Debug|x64.ActiveCfg = Debug|x64 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Debug|x64.Build.0 = Debug|x64 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Debug|x86.ActiveCfg = Debug|x86 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Debug|x86.Build.0 = Debug|x86 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Release|Any CPU.Build.0 = Release|Any CPU + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Release|x64.ActiveCfg = Release|x64 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Release|x64.Build.0 = Release|x64 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Release|x86.ActiveCfg = Release|x86 + {DB742DF2-9873-44CF-8439-DA5ACFF69A46}.Release|x86.Build.0 = Release|x86 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD248}.Cover|Any CPU.Build.0 = Cover|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD248}.Cover|x64.ActiveCfg = Cover|x64 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Cover|x64.Build.0 = Cover|x64 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Cover|x86.ActiveCfg = Cover|x86 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Cover|x86.Build.0 = Cover|x86 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD248}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD248}.Debug|x64.ActiveCfg = Debug|x64 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Debug|x64.Build.0 = Debug|x64 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Debug|x86.ActiveCfg = Debug|x86 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Debug|x86.Build.0 = Debug|x86 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD248}.Release|Any CPU.Build.0 = Release|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD248}.Release|x64.ActiveCfg = Release|x64 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Release|x64.Build.0 = Release|x64 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Release|x86.ActiveCfg = Release|x86 + {1D872F31-6100-4C76-999E-ED948E2CD248}.Release|x86.Build.0 = Release|x86 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD249}.Cover|Any CPU.Build.0 = Cover|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD249}.Cover|x64.ActiveCfg = Cover|x64 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Cover|x64.Build.0 = Cover|x64 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Cover|x86.ActiveCfg = Cover|x86 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Cover|x86.Build.0 = Cover|x86 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD249}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD249}.Debug|x64.ActiveCfg = Debug|x64 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Debug|x64.Build.0 = Debug|x64 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Debug|x86.ActiveCfg = Debug|x86 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Debug|x86.Build.0 = Debug|x86 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD249}.Release|Any CPU.Build.0 = Release|Any CPU + {1D872F31-6100-4C76-999E-ED948E2CD249}.Release|x64.ActiveCfg = Release|x64 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Release|x64.Build.0 = Release|x64 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Release|x86.ActiveCfg = Release|x86 + {1D872F31-6100-4C76-999E-ED948E2CD249}.Release|x86.Build.0 = Release|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|Any CPU.Build.0 = Cover|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x64.ActiveCfg = Cover|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x64.Build.0 = Cover|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x86.ActiveCfg = Cover|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x86.Build.0 = Cover|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x64.ActiveCfg = Debug|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x64.Build.0 = Debug|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x86.ActiveCfg = Debug|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x86.Build.0 = Debug|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|Any CPU.Build.0 = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x64.ActiveCfg = Release|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x64.Build.0 = Release|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x86.ActiveCfg = Release|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x86.Build.0 = Release|x86 + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|Any CPU.Build.0 = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|x64.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|x64.Build.0 = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|x86.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Cover|x86.Build.0 = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|x64.ActiveCfg = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|x64.Build.0 = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Debug|x86.Build.0 = Debug|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|Any CPU.Build.0 = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|x64.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|x64.Build.0 = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|x86.ActiveCfg = Release|Any CPU + {9037FF4A-4636-41AA-BFA2-0930EF1563EE}.Release|x86.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|Any CPU.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|x64.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|x64.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|x86.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Cover|x86.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|x64.Build.0 = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Debug|x86.Build.0 = Debug|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|Any CPU.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|x64.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|x64.Build.0 = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|x86.ActiveCfg = Release|Any CPU + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C}.Release|x86.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|Any CPU.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|x64.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|x64.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|x86.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Cover|x86.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|x64.Build.0 = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Debug|x86.Build.0 = Debug|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|Any CPU.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|x64.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|x64.Build.0 = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|x86.ActiveCfg = Release|Any CPU + {9F0AB290-8164-4885-BFCA-A6F87AB81740}.Release|x86.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|Any CPU.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|x64.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|x64.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|x86.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Cover|x86.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|x64.Build.0 = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Debug|x86.Build.0 = Debug|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|Any CPU.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|x64.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|x64.Build.0 = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|x86.ActiveCfg = Release|Any CPU + {419F42FA-3313-46A7-B519-B2ED042B0F5C}.Release|x86.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|Any CPU.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|x64.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|x64.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|x86.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Cover|x86.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|x64.ActiveCfg = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|x64.Build.0 = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|x86.ActiveCfg = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Debug|x86.Build.0 = Debug|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|Any CPU.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|x64.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|x64.Build.0 = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|x86.ActiveCfg = Release|Any CPU + {68C25902-0FBD-4694-9EE9-47F2219CE039}.Release|x86.Build.0 = Release|Any CPU + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Cover|Any CPU.Build.0 = Cover|Any CPU + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Cover|x64.ActiveCfg = Cover|x64 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Cover|x64.Build.0 = Cover|x64 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Cover|x86.ActiveCfg = Cover|x86 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Cover|x86.Build.0 = Cover|x86 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Debug|x64.ActiveCfg = Debug|x64 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Debug|x64.Build.0 = Debug|x64 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Debug|x86.ActiveCfg = Debug|x86 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Debug|x86.Build.0 = Debug|x86 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Release|Any CPU.Build.0 = Release|Any CPU + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Release|x64.ActiveCfg = Release|x64 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Release|x64.Build.0 = Release|x64 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Release|x86.ActiveCfg = Release|x86 + {B57522A7-18B7-42F7-95FD-24835FC5AE46}.Release|x86.Build.0 = Release|x86 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {1211B700-008A-4646-8F5F-25BC96EBC138}.Cover|Any CPU.Build.0 = Cover|Any CPU + {1211B700-008A-4646-8F5F-25BC96EBC138}.Cover|x64.ActiveCfg = Cover|x64 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Cover|x64.Build.0 = Cover|x64 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Cover|x86.ActiveCfg = Cover|x86 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Cover|x86.Build.0 = Cover|x86 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1211B700-008A-4646-8F5F-25BC96EBC138}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1211B700-008A-4646-8F5F-25BC96EBC138}.Debug|x64.ActiveCfg = Debug|x64 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Debug|x64.Build.0 = Debug|x64 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Debug|x86.ActiveCfg = Debug|x86 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Debug|x86.Build.0 = Debug|x86 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1211B700-008A-4646-8F5F-25BC96EBC138}.Release|Any CPU.Build.0 = Release|Any CPU + {1211B700-008A-4646-8F5F-25BC96EBC138}.Release|x64.ActiveCfg = Release|x64 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Release|x64.Build.0 = Release|x64 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Release|x86.ActiveCfg = Release|x86 + {1211B700-008A-4646-8F5F-25BC96EBC138}.Release|x86.Build.0 = Release|x86 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Cover|Any CPU.Build.0 = Cover|Any CPU + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Cover|x64.ActiveCfg = Cover|x64 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Cover|x64.Build.0 = Cover|x64 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Cover|x86.ActiveCfg = Cover|x86 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Cover|x86.Build.0 = Cover|x86 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Debug|x64.ActiveCfg = Debug|x64 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Debug|x64.Build.0 = Debug|x64 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Debug|x86.ActiveCfg = Debug|x86 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Debug|x86.Build.0 = Debug|x86 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Release|Any CPU.Build.0 = Release|Any CPU + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Release|x64.ActiveCfg = Release|x64 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Release|x64.Build.0 = Release|x64 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Release|x86.ActiveCfg = Release|x86 + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE}.Release|x86.Build.0 = Release|x86 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Cover|Any CPU.Build.0 = Cover|Any CPU + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Cover|x64.ActiveCfg = Cover|x64 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Cover|x64.Build.0 = Cover|x64 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Cover|x86.ActiveCfg = Cover|x86 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Cover|x86.Build.0 = Cover|x86 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Debug|x64.ActiveCfg = Debug|x64 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Debug|x64.Build.0 = Debug|x64 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Debug|x86.ActiveCfg = Debug|x86 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Debug|x86.Build.0 = Debug|x86 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Release|Any CPU.Build.0 = Release|Any CPU + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Release|x64.ActiveCfg = Release|x64 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Release|x64.Build.0 = Release|x64 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Release|x86.ActiveCfg = Release|x86 + {AE503558-F7BC-4CE4-9A90-066B580BB1ED}.Release|x86.Build.0 = Release|x86 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {5C59B211-002F-40AE-BED4-16470A8024E4}.Cover|Any CPU.Build.0 = Cover|Any CPU + {5C59B211-002F-40AE-BED4-16470A8024E4}.Cover|x64.ActiveCfg = Cover|x64 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Cover|x64.Build.0 = Cover|x64 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Cover|x86.ActiveCfg = Cover|x86 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Cover|x86.Build.0 = Cover|x86 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C59B211-002F-40AE-BED4-16470A8024E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C59B211-002F-40AE-BED4-16470A8024E4}.Debug|x64.ActiveCfg = Debug|x64 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Debug|x64.Build.0 = Debug|x64 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Debug|x86.ActiveCfg = Debug|x86 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Debug|x86.Build.0 = Debug|x86 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C59B211-002F-40AE-BED4-16470A8024E4}.Release|Any CPU.Build.0 = Release|Any CPU + {5C59B211-002F-40AE-BED4-16470A8024E4}.Release|x64.ActiveCfg = Release|x64 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Release|x64.Build.0 = Release|x64 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Release|x86.ActiveCfg = Release|x86 + {5C59B211-002F-40AE-BED4-16470A8024E4}.Release|x86.Build.0 = Release|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Cover|Any CPU.Build.0 = Cover|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Cover|x64.ActiveCfg = Cover|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Cover|x64.Build.0 = Cover|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Cover|x86.ActiveCfg = Cover|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Cover|x86.Build.0 = Cover|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Debug|x64.ActiveCfg = Debug|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Debug|x64.Build.0 = Debug|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Debug|x86.ActiveCfg = Debug|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Debug|x86.Build.0 = Debug|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Release|Any CPU.Build.0 = Release|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Release|x64.ActiveCfg = Release|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Release|x64.Build.0 = Release|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Release|x86.ActiveCfg = Release|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288}.Release|x86.Build.0 = Release|x86 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Cover|Any CPU.Build.0 = Cover|Any CPU + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Cover|x64.ActiveCfg = Cover|x64 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Cover|x64.Build.0 = Cover|x64 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Cover|x86.ActiveCfg = Cover|x86 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Cover|x86.Build.0 = Cover|x86 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Debug|x64.ActiveCfg = Debug|x64 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Debug|x64.Build.0 = Debug|x64 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Debug|x86.ActiveCfg = Debug|x86 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Debug|x86.Build.0 = Debug|x86 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Release|Any CPU.Build.0 = Release|Any CPU + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Release|x64.ActiveCfg = Release|x64 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Release|x64.Build.0 = Release|x64 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Release|x86.ActiveCfg = Release|x86 + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6}.Release|x86.Build.0 = Release|x86 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Cover|Any CPU.Build.0 = Cover|Any CPU + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Cover|x64.ActiveCfg = Cover|x64 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Cover|x64.Build.0 = Cover|x64 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Cover|x86.ActiveCfg = Cover|x86 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Cover|x86.Build.0 = Cover|x86 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Debug|x64.ActiveCfg = Debug|x64 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Debug|x64.Build.0 = Debug|x64 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Debug|x86.ActiveCfg = Debug|x86 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Debug|x86.Build.0 = Debug|x86 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Release|Any CPU.Build.0 = Release|Any CPU + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Release|x64.ActiveCfg = Release|x64 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Release|x64.Build.0 = Release|x64 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Release|x86.ActiveCfg = Release|x86 + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718}.Release|x86.Build.0 = Release|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Cover|Any CPU.Build.0 = Cover|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Cover|x64.ActiveCfg = Cover|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Cover|x64.Build.0 = Cover|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Cover|x86.ActiveCfg = Cover|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Cover|x86.Build.0 = Cover|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Debug|x64.ActiveCfg = Debug|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Debug|x64.Build.0 = Debug|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Debug|x86.ActiveCfg = Debug|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Debug|x86.Build.0 = Debug|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Release|Any CPU.Build.0 = Release|Any CPU + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Release|x64.ActiveCfg = Release|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Release|x64.Build.0 = Release|x64 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Release|x86.ActiveCfg = Release|x86 + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291}.Release|x86.Build.0 = Release|x86 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Cover|Any CPU.Build.0 = Cover|Any CPU + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Cover|x64.ActiveCfg = Cover|x64 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Cover|x64.Build.0 = Cover|x64 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Cover|x86.ActiveCfg = Cover|x86 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Cover|x86.Build.0 = Cover|x86 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Debug|x64.ActiveCfg = Debug|x64 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Debug|x64.Build.0 = Debug|x64 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Debug|x86.ActiveCfg = Debug|x86 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Debug|x86.Build.0 = Debug|x86 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Release|Any CPU.Build.0 = Release|Any CPU + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Release|x64.ActiveCfg = Release|x64 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Release|x64.Build.0 = Release|x64 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Release|x86.ActiveCfg = Release|x86 + {B34C9A66-22BE-4377-896D-009577D5E6FF}.Release|x86.Build.0 = Release|x86 + {99FC4701-33CD-477D-913C-E39E957D1548}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {99FC4701-33CD-477D-913C-E39E957D1548}.Cover|Any CPU.Build.0 = Cover|Any CPU + {99FC4701-33CD-477D-913C-E39E957D1548}.Cover|x64.ActiveCfg = Cover|x64 + {99FC4701-33CD-477D-913C-E39E957D1548}.Cover|x64.Build.0 = Cover|x64 + {99FC4701-33CD-477D-913C-E39E957D1548}.Cover|x86.ActiveCfg = Cover|x86 + {99FC4701-33CD-477D-913C-E39E957D1548}.Cover|x86.Build.0 = Cover|x86 + {99FC4701-33CD-477D-913C-E39E957D1548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99FC4701-33CD-477D-913C-E39E957D1548}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99FC4701-33CD-477D-913C-E39E957D1548}.Debug|x64.ActiveCfg = Debug|x64 + {99FC4701-33CD-477D-913C-E39E957D1548}.Debug|x64.Build.0 = Debug|x64 + {99FC4701-33CD-477D-913C-E39E957D1548}.Debug|x86.ActiveCfg = Debug|x86 + {99FC4701-33CD-477D-913C-E39E957D1548}.Debug|x86.Build.0 = Debug|x86 + {99FC4701-33CD-477D-913C-E39E957D1548}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99FC4701-33CD-477D-913C-E39E957D1548}.Release|Any CPU.Build.0 = Release|Any CPU + {99FC4701-33CD-477D-913C-E39E957D1548}.Release|x64.ActiveCfg = Release|x64 + {99FC4701-33CD-477D-913C-E39E957D1548}.Release|x64.Build.0 = Release|x64 + {99FC4701-33CD-477D-913C-E39E957D1548}.Release|x86.ActiveCfg = Release|x86 + {99FC4701-33CD-477D-913C-E39E957D1548}.Release|x86.Build.0 = Release|x86 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Cover|x64.ActiveCfg = Cover|x64 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Cover|x64.Build.0 = Cover|x64 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Cover|x86.ActiveCfg = Cover|x86 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Cover|x86.Build.0 = Cover|x86 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Debug|x64.ActiveCfg = Debug|x64 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Debug|x64.Build.0 = Debug|x64 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Debug|x86.ActiveCfg = Debug|x86 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Debug|x86.Build.0 = Debug|x86 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Release|Any CPU.Build.0 = Release|Any CPU + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Release|x64.ActiveCfg = Release|x64 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Release|x64.Build.0 = Release|x64 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Release|x86.ActiveCfg = Release|x86 + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B}.Release|x86.Build.0 = Release|x86 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Cover|Any CPU.Build.0 = Cover|Any CPU + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Cover|x64.ActiveCfg = Cover|x64 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Cover|x64.Build.0 = Cover|x64 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Cover|x86.ActiveCfg = Cover|x86 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Cover|x86.Build.0 = Cover|x86 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Debug|x64.ActiveCfg = Debug|x64 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Debug|x64.Build.0 = Debug|x64 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Debug|x86.ActiveCfg = Debug|x86 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Debug|x86.Build.0 = Debug|x86 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Release|Any CPU.Build.0 = Release|Any CPU + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Release|x64.ActiveCfg = Release|x64 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Release|x64.Build.0 = Release|x64 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Release|x86.ActiveCfg = Release|x86 + {B8A072BE-5B90-4193-A289-6CC8750D0DAD}.Release|x86.Build.0 = Release|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|Any CPU.Build.0 = Cover|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x64.ActiveCfg = Cover|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x64.Build.0 = Cover|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x86.ActiveCfg = Cover|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x86.Build.0 = Cover|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x64.ActiveCfg = Debug|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x64.Build.0 = Debug|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x86.ActiveCfg = Debug|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x86.Build.0 = Debug|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|Any CPU.Build.0 = Release|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x64.ActiveCfg = Release|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x64.Build.0 = Release|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x86.ActiveCfg = Release|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x86.Build.0 = Release|x86 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Cover|Any CPU.Build.0 = Cover|Any CPU + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Cover|x64.ActiveCfg = Cover|x64 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Cover|x64.Build.0 = Cover|x64 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Cover|x86.ActiveCfg = Cover|x86 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Cover|x86.Build.0 = Cover|x86 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Debug|x64.ActiveCfg = Debug|x64 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Debug|x64.Build.0 = Debug|x64 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Debug|x86.ActiveCfg = Debug|x86 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Debug|x86.Build.0 = Debug|x86 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Release|Any CPU.Build.0 = Release|Any CPU + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Release|x64.ActiveCfg = Release|x64 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Release|x64.Build.0 = Release|x64 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Release|x86.ActiveCfg = Release|x86 + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0}.Release|x86.Build.0 = Release|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|Any CPU.Build.0 = Cover|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x64.ActiveCfg = Cover|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x64.Build.0 = Cover|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x86.ActiveCfg = Cover|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x86.Build.0 = Cover|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x64.ActiveCfg = Debug|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x64.Build.0 = Debug|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x86.ActiveCfg = Debug|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x86.Build.0 = Debug|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|Any CPU.Build.0 = Release|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x64.ActiveCfg = Release|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x64.Build.0 = Release|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x86.ActiveCfg = Release|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x86.Build.0 = Release|x86 + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Cover|Any CPU.Build.0 = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Cover|x64.ActiveCfg = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Cover|x64.Build.0 = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Cover|x86.ActiveCfg = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Cover|x86.Build.0 = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Debug|x64.ActiveCfg = Debug|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Debug|x64.Build.0 = Debug|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Debug|x86.ActiveCfg = Debug|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Debug|x86.Build.0 = Debug|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Release|Any CPU.Build.0 = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Release|x64.ActiveCfg = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Release|x64.Build.0 = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Release|x86.ActiveCfg = Release|Any CPU + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8}.Release|x86.Build.0 = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Cover|Any CPU.Build.0 = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Cover|x64.ActiveCfg = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Cover|x64.Build.0 = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Cover|x86.ActiveCfg = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Cover|x86.Build.0 = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Debug|x64.ActiveCfg = Debug|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Debug|x64.Build.0 = Debug|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Debug|x86.ActiveCfg = Debug|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Debug|x86.Build.0 = Debug|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Release|Any CPU.Build.0 = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Release|x64.ActiveCfg = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Release|x64.Build.0 = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Release|x86.ActiveCfg = Release|Any CPU + {8C9A1B86-4474-484C-9545-223F9C6185FC}.Release|x86.Build.0 = Release|Any CPU + {74295872-F930-497B-A70E-ED4FAAB395C4}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {74295872-F930-497B-A70E-ED4FAAB395C4}.Cover|Any CPU.Build.0 = Cover|Any CPU + {74295872-F930-497B-A70E-ED4FAAB395C4}.Cover|x64.ActiveCfg = Cover|x64 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Cover|x64.Build.0 = Cover|x64 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Cover|x86.ActiveCfg = Cover|x86 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Cover|x86.Build.0 = Cover|x86 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74295872-F930-497B-A70E-ED4FAAB395C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74295872-F930-497B-A70E-ED4FAAB395C4}.Debug|x64.ActiveCfg = Debug|x64 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Debug|x64.Build.0 = Debug|x64 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Debug|x86.ActiveCfg = Debug|x86 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Debug|x86.Build.0 = Debug|x86 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74295872-F930-497B-A70E-ED4FAAB395C4}.Release|Any CPU.Build.0 = Release|Any CPU + {74295872-F930-497B-A70E-ED4FAAB395C4}.Release|x64.ActiveCfg = Release|x64 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Release|x64.Build.0 = Release|x64 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Release|x86.ActiveCfg = Release|x86 + {74295872-F930-497B-A70E-ED4FAAB395C4}.Release|x86.Build.0 = Release|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|Any CPU.Build.0 = Cover|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x64.ActiveCfg = Cover|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x64.Build.0 = Cover|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x86.ActiveCfg = Cover|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x86.Build.0 = Cover|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x64.ActiveCfg = Debug|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x64.Build.0 = Debug|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x86.ActiveCfg = Debug|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x86.Build.0 = Debug|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|Any CPU.Build.0 = Release|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x64.ActiveCfg = Release|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x64.Build.0 = Release|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x86.ActiveCfg = Release|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x86.Build.0 = Release|x86 + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Cover|Any CPU.Build.0 = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Cover|x64.ActiveCfg = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Cover|x64.Build.0 = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Cover|x86.ActiveCfg = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Cover|x86.Build.0 = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Debug|Any CPU.Build.0 = Debug|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Debug|x64.ActiveCfg = Debug|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Debug|x64.Build.0 = Debug|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Debug|x86.ActiveCfg = Debug|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Debug|x86.Build.0 = Debug|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Release|Any CPU.ActiveCfg = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Release|Any CPU.Build.0 = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Release|x64.ActiveCfg = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Release|x64.Build.0 = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Release|x86.ActiveCfg = Release|Any CPU + {333077F2-0DBA-42A1-A5E1-E86361D96074}.Release|x86.Build.0 = Release|Any CPU + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Cover|Any CPU.Build.0 = Cover|Any CPU + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Cover|x64.ActiveCfg = Cover|x64 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Cover|x64.Build.0 = Cover|x64 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Cover|x86.ActiveCfg = Cover|x86 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Cover|x86.Build.0 = Cover|x86 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Debug|x64.ActiveCfg = Debug|x64 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Debug|x64.Build.0 = Debug|x64 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Debug|x86.ActiveCfg = Debug|x86 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Debug|x86.Build.0 = Debug|x86 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Release|Any CPU.Build.0 = Release|Any CPU + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Release|x64.ActiveCfg = Release|x64 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Release|x64.Build.0 = Release|x64 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Release|x86.ActiveCfg = Release|x86 + {65DCD663-02DB-4E45-B41E-C7B7D3B11871}.Release|x86.Build.0 = Release|x86 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Cover|Any CPU.Build.0 = Cover|Any CPU + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Cover|x64.ActiveCfg = Cover|x64 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Cover|x64.Build.0 = Cover|x64 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Cover|x86.ActiveCfg = Cover|x86 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Cover|x86.Build.0 = Cover|x86 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Debug|x64.ActiveCfg = Debug|x64 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Debug|x64.Build.0 = Debug|x64 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Debug|x86.ActiveCfg = Debug|x86 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Debug|x86.Build.0 = Debug|x86 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Release|Any CPU.Build.0 = Release|Any CPU + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Release|x64.ActiveCfg = Release|x64 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Release|x64.Build.0 = Release|x64 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Release|x86.ActiveCfg = Release|x86 + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E7A080F5-2054-447F-B09C-4E7B9B1B1B6A} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {A7A080F5-2054-447F-B09C-4E7B8B1B1B4A} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {7D921888-FE03-4C3F-80FE-2F624505461C} = {818C5A92-D7B8-4704-89F3-32E422BFFB9D} + {989A83CC-B864-4A75-8BF3-5EDA99203A86} = {818C5A92-D7B8-4704-89F3-32E422BFFB9D} + {5D921888-FE03-4C3F-40FE-2F624505461D} = {818C5A92-D7B8-4704-89F3-32E422BFFB9D} + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF} = {818C5A92-D7B8-4704-89F3-32E422BFFB9D} + {D1567C63-4A0D-4E18-A14E-79699B9BA325} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {F7A080F5-2054-447F-B09C-4E7B9B1B1B00} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {EFA72CF8-A73A-4EB4-88D4-AE6D4CE1B270} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {1D54775E-E211-4B35-A002-E53416BAE743} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {EDC237AA-6875-4034-BA39-7F2A83533BFC} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {7FF7E0D4-B00C-4C08-80F1-C7EFF617597D} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {34010F3A-20CC-479C-83CF-EC99B1C90CD1} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {29236BBA-852B-46B2-A39B-09DB47A7F6EB} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {854AF4E9-B78F-4994-B9C5-82B846604CBE} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {65C0C702-8A6D-4CDE-A387-0D0C1893E4BC} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {834DEA16-4215-41D1-99E7-FBE41A3326DA} = {88BCAAF8-C1CD-41A0-81AA-BF4D80CADD8C} + {CEC8AF29-77BC-4C62-B79C-A9B22C311E3B} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {A5AB5A4F-3FDB-4BF0-BA8C-22C7EA438D67} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {6762001D-60F0-4E70-A816-C15897C39B7F} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {F3EE82F0-8DDD-45EE-BD9C-47AC3A3AF4BB} = {975D9E9C-F983-46E5-83E5-CEDEE845E2BF} + {B01915F6-37E4-4E7E-9A45-8722829E49F1} = {975D9E9C-F983-46E5-83E5-CEDEE845E2BF} + {39F660EC-A5D8-4753-B3F2-28117EB3224B} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {EB4C9641-0452-4E7F-AA38-3EBD871914A3} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {69A5EB3B-5AE5-49AD-B4F3-025D087A4647} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {BB7D22CB-7003-4AF6-9035-B369B00EA95E} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {D93149FD-0D7F-41AB-AFCF-62270E7FD613} = {975D9E9C-F983-46E5-83E5-CEDEE845E2BF} + {3C8CB60D-D2C0-495E-A8D2-C8B1B244F27B} = {975D9E9C-F983-46E5-83E5-CEDEE845E2BF} + {406BEAB6-7437-471E-B72D-3CE9AAC3B7AE} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {99C025F4-3FA8-4078-BC9F-46774450B574} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {D94501EA-4606-4A82-A591-79473D9EE368} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {9B78C515-20E7-49FF-B8A1-90F3D585E88D} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {2560BD0F-D2A5-4DB1-B260-7C07DF506E11} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {0854AF44-074A-41B4-909D-2AA4CAE82332} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {4A8E61C3-53BC-471C-82BC-3ADB6F9B2E2B} = {0035949E-4316-4146-AF73-84118CA8D758} + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D} = {AA695E63-EB7D-4A21-968C-0BAE14D8C435} + {7BA26157-3062-4552-9C23-BB9B082C1ACE} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {B9380603-5090-483C-ADF0-66D7FEE4DF7E} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {70CCA883-E9EC-41C8-9A42-F910C4A6FCB6} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {1C872F31-6100-4C76-999E-ED948E2CD246} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {C0A623F5-7165-432C-A764-2E995FD9ADA9} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {481C1E7B-1A96-4322-9479-2F9706656E71} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {2A9B8663-D894-48B9-93C1-CD6B397ACAA0} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {DB742DF2-9873-44CF-8439-DA5ACFF69A46} = {8A8C5DF6-B845-4044-8677-D877E6450CB4} + {1D872F31-6100-4C76-999E-ED948E2CD248} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {1D872F31-6100-4C76-999E-ED948E2CD249} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {9037FF4A-4636-41AA-BFA2-0930EF1563EE} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {C6547B28-DC0C-414B-A1D7-488FC8EA7B6C} = {8CCBB4F7-EBD1-48B6-8A77-BD733EB9CB6B} + {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} = {9C93B744-F1C4-4E80-A705-8EF1FEF898CF} + {8CCBB4F7-EBD1-48B6-8A77-BD733EB9CB6B} = {9C93B744-F1C4-4E80-A705-8EF1FEF898CF} + {9F0AB290-8164-4885-BFCA-A6F87AB81740} = {8CCBB4F7-EBD1-48B6-8A77-BD733EB9CB6B} + {419F42FA-3313-46A7-B519-B2ED042B0F5C} = {8CCBB4F7-EBD1-48B6-8A77-BD733EB9CB6B} + {68C25902-0FBD-4694-9EE9-47F2219CE039} = {8CCBB4F7-EBD1-48B6-8A77-BD733EB9CB6B} + {88BCAAF8-C1CD-41A0-81AA-BF4D80CADD8C} = {9C93B744-F1C4-4E80-A705-8EF1FEF898CF} + {B57522A7-18B7-42F7-95FD-24835FC5AE46} = {88BCAAF8-C1CD-41A0-81AA-BF4D80CADD8C} + {8A8C5DF6-B845-4044-8677-D877E6450CB4} = {15AE273F-373F-4870-9B73-8F3B9DB05F98} + {1211B700-008A-4646-8F5F-25BC96EBC138} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {ECF627BB-5156-488F-9F6B-CF6537F8D6FE} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {AE503558-F7BC-4CE4-9A90-066B580BB1ED} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {5C59B211-002F-40AE-BED4-16470A8024E4} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {9A2BEEC2-42C7-4EE8-B9D9-95746F197288} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {D380441D-66B8-4728-B83C-F9EDAC7DEEC6} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {6C3BBBBA-360B-40E3-A96E-DD0708DA6718} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {9A2BEEC2-42C7-4EE8-B9D9-95746F197291} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {B34C9A66-22BE-4377-896D-009577D5E6FF} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {99FC4701-33CD-477D-913C-E39E957D1548} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {7AAA5D95-309F-4E3B-A12F-9E2C41C0B36B} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {B8A072BE-5B90-4193-A289-6CC8750D0DAD} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {A8A0976E-E1D8-4C0E-B425-B4F74798D478} = {88BCAAF8-C1CD-41A0-81AA-BF4D80CADD8C} + {DF028E55-CE75-4F32-822E-F9EC9C756AE2} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {58E9BBCB-F264-49F2-B6EC-B291B9EAE2C0} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {975D9E9C-F983-46E5-83E5-CEDEE845E2BF} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {15AE273F-373F-4870-9B73-8F3B9DB05F98} = {9C93B744-F1C4-4E80-A705-8EF1FEF898CF} + {AA695E63-EB7D-4A21-968C-0BAE14D8C435} = {818C5A92-D7B8-4704-89F3-32E422BFFB9D} + {0035949E-4316-4146-AF73-84118CA8D758} = {9C93B744-F1C4-4E80-A705-8EF1FEF898CF} + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18} = {818C5A92-D7B8-4704-89F3-32E422BFFB9D} + {A5A3F799-3B12-4BE6-85C7-51AD75565DE8} = {88BCAAF8-C1CD-41A0-81AA-BF4D80CADD8C} + {8C9A1B86-4474-484C-9545-223F9C6185FC} = {88BCAAF8-C1CD-41A0-81AA-BF4D80CADD8C} + {74295872-F930-497B-A70E-ED4FAAB395C4} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {4382D649-1A86-48D0-9156-AC37C3D568C0} = {F1453B46-0B8B-4BD4-B7E4-69A8CA431A4A} + {333077F2-0DBA-42A1-A5E1-E86361D96074} = {A8A0976E-E1D8-4C0E-B425-B4F74798D478} + {65DCD663-02DB-4E45-B41E-C7B7D3B11871} = {0035949E-4316-4146-AF73-84118CA8D758} + {BE6827D5-E301-47DD-8CAC-E0DA555AB64F} = {0035949E-4316-4146-AF73-84118CA8D758} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3381754F-0117-444E-9D95-4845461632CB} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.Tests.NetStandard.VS2017.sln b/ApiAsAService/odata.net/sln/OData.Tests.NetStandard.VS2017.sln new file mode 100644 index 0000000..3a789e9 --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.Tests.NetStandard.VS2017.sln @@ -0,0 +1,245 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.16 +MinimumVisualStudioVersion = 14.0.25420.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AE28D570-28A6-45E1-A695-35C5B27CC4F8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F2442089-4BD1-4AEF-91FE-E7192432077E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unitTests", "unitTests", "{31EFC4F5-C88F-434C-BB94-4C6769997C88}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "helper", "helper", "{56DEF0B0-DE00-478C-8191-F7A5526AB6F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Spatial.Tests.NetCore", "..\test\FunctionalTests\Microsoft.Spatial.Tests\Microsoft.Spatial.Tests.NetCore.csproj", "{AF1E7103-7894-4DF5-A81F-60D7845F8720}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.TestCommon.NetCore", "..\test\FunctionalTests\Microsoft.OData.TestCommon\Microsoft.OData.TestCommon.NetCore.csproj", "{184697C2-99B5-4616-9D60-F2693293AD24}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Test.OData.DependencyInjection.NetCore", "..\test\Common\Microsoft.Test.OData.DependencyInjection\Microsoft.Test.OData.DependencyInjection.NetCore.csproj", "{206C8A3B-4384-4417-A819-5B263A4F4910}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Spatial.NetStandard.VS2017", "..\src\Microsoft.Spatial\Microsoft.Spatial.NetStandard.VS2017.csproj", "{E800572A-D59A-4F5E-AF39-6BD9DD6CC166}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Edm.NetStandard.VS2017", "..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.NetStandard.VS2017.csproj", "{BF32BBDE-EE48-47FA-B09C-47B786FFF31B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Edm.Tests.NetCore", "..\test\FunctionalTests\Microsoft.OData.Edm.Tests\Microsoft.OData.Edm.Tests.NetCore.csproj", "{4FA5E740-D3C9-4F00-9E20-4DE023E53335}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Core.NetStandard.VS2017", "..\src\Microsoft.OData.Core\Microsoft.OData.Core.NetStandard.VS2017.csproj", "{3DF47F20-F2DA-4285-870F-F65AF60F0487}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Core.Tests.NetCore", "..\test\FunctionalTests\Microsoft.OData.Core.Tests\Microsoft.OData.Core.Tests.NetCore.csproj", "{58A61116-2BF3-4803-B01E-A45AE89A4025}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Client.NetStandard.VS2017", "..\src\Microsoft.OData.Client\Microsoft.OData.Client.NetStandard.VS2017.csproj", "{08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OData.Client.Tests.Netcore", "..\test\FunctionalTests\Microsoft.OData.Client.Tests\Microsoft.OData.Client.Tests.Netcore.csproj", "{ACE12729-DE71-4E82-B9AB-32D9A2E4E895}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|Any CPU.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x64.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x64.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x86.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Cover|x86.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x64.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Debug|x86.Build.0 = Debug|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|Any CPU.Build.0 = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x64.ActiveCfg = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x64.Build.0 = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x86.ActiveCfg = Release|Any CPU + {AF1E7103-7894-4DF5-A81F-60D7845F8720}.Release|x86.Build.0 = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|Any CPU.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x64.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x64.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x86.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Cover|x86.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x64.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x64.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x86.ActiveCfg = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Debug|x86.Build.0 = Debug|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|Any CPU.Build.0 = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x64.ActiveCfg = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x64.Build.0 = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x86.ActiveCfg = Release|Any CPU + {184697C2-99B5-4616-9D60-F2693293AD24}.Release|x86.Build.0 = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|Any CPU.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x64.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x64.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x86.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Cover|x86.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|Any CPU.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x64.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x64.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x86.ActiveCfg = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Debug|x86.Build.0 = Debug|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|Any CPU.ActiveCfg = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|Any CPU.Build.0 = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x64.ActiveCfg = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x64.Build.0 = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x86.ActiveCfg = Release|Any CPU + {206C8A3B-4384-4417-A819-5B263A4F4910}.Release|x86.Build.0 = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|Any CPU.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x64.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x64.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x86.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Cover|x86.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x64.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x64.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x86.ActiveCfg = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Debug|x86.Build.0 = Debug|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|Any CPU.Build.0 = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x64.ActiveCfg = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x64.Build.0 = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x86.ActiveCfg = Release|Any CPU + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166}.Release|x86.Build.0 = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|Any CPU.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x64.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x64.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x86.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Cover|x86.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x64.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x64.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x86.ActiveCfg = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Debug|x86.Build.0 = Debug|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|Any CPU.Build.0 = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x64.ActiveCfg = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x64.Build.0 = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x86.ActiveCfg = Release|Any CPU + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B}.Release|x86.Build.0 = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|Any CPU.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x64.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x64.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x86.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Cover|x86.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x64.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Debug|x86.Build.0 = Debug|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x64.ActiveCfg = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x64.Build.0 = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x86.ActiveCfg = Release|Any CPU + {4FA5E740-D3C9-4F00-9E20-4DE023E53335}.Release|x86.Build.0 = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|Any CPU.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x64.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x64.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x86.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Cover|x86.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x64.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x64.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x86.ActiveCfg = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Debug|x86.Build.0 = Debug|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|Any CPU.Build.0 = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x64.ActiveCfg = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x64.Build.0 = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x86.ActiveCfg = Release|Any CPU + {3DF47F20-F2DA-4285-870F-F65AF60F0487}.Release|x86.Build.0 = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|Any CPU.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x64.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x64.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x86.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Cover|x86.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x64.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x64.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x86.ActiveCfg = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Debug|x86.Build.0 = Debug|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|Any CPU.Build.0 = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x64.ActiveCfg = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x64.Build.0 = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x86.ActiveCfg = Release|Any CPU + {58A61116-2BF3-4803-B01E-A45AE89A4025}.Release|x86.Build.0 = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|Any CPU.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x64.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x64.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x86.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Cover|x86.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x64.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x64.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x86.ActiveCfg = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Debug|x86.Build.0 = Debug|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|Any CPU.Build.0 = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x64.ActiveCfg = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x64.Build.0 = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x86.ActiveCfg = Release|Any CPU + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7}.Release|x86.Build.0 = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|Any CPU.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x64.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x64.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x86.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Cover|x86.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x64.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x64.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x86.ActiveCfg = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Debug|x86.Build.0 = Debug|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|Any CPU.Build.0 = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x64.ActiveCfg = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x64.Build.0 = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x86.ActiveCfg = Release|Any CPU + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {31EFC4F5-C88F-434C-BB94-4C6769997C88} = {F2442089-4BD1-4AEF-91FE-E7192432077E} + {56DEF0B0-DE00-478C-8191-F7A5526AB6F7} = {F2442089-4BD1-4AEF-91FE-E7192432077E} + {AF1E7103-7894-4DF5-A81F-60D7845F8720} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + {184697C2-99B5-4616-9D60-F2693293AD24} = {56DEF0B0-DE00-478C-8191-F7A5526AB6F7} + {206C8A3B-4384-4417-A819-5B263A4F4910} = {56DEF0B0-DE00-478C-8191-F7A5526AB6F7} + {E800572A-D59A-4F5E-AF39-6BD9DD6CC166} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {BF32BBDE-EE48-47FA-B09C-47B786FFF31B} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {4FA5E740-D3C9-4F00-9E20-4DE023E53335} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + {3DF47F20-F2DA-4285-870F-F65AF60F0487} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {58A61116-2BF3-4803-B01E-A45AE89A4025} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + {08AE0354-BA3B-4E57-A39B-20EF04BBDBA7} = {AE28D570-28A6-45E1-A695-35C5B27CC4F8} + {ACE12729-DE71-4E82-B9AB-32D9A2E4E895} = {31EFC4F5-C88F-434C-BB94-4C6769997C88} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.Tests.Performance.sln b/ApiAsAService/odata.net/sln/OData.Tests.Performance.sln new file mode 100644 index 0000000..b90b345 --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.Tests.Performance.sln @@ -0,0 +1,211 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Performance.ComponentTests", "..\test\PerformanceTests\ComponentTests\Microsoft.OData.Performance.ComponentTests.csproj", "{7D98AD3B-6685-498A-834C-F18F7FAE9138}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core", "..\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj", "{989A83CC-B864-4A75-8BF3-5EDA99203A86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm", "..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj", "{7D921888-FE03-4C3F-80FE-2F624505461C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial", "..\src\Microsoft.Spatial\Microsoft.Spatial.csproj", "{5D921888-FE03-4C3F-40FE-2F624505461D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{178B9566-29C6-4CB9-B761-C99386E32D4C}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ODataSamples.Services.Core", "..\test\EndToEndTests\Services\ODataWCFLibrary\ODataSamples.Services.Core.csproj", "{DF028E55-CE75-4F32-822E-F9EC9C756AE2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Performance.ServiceTests", "..\test\PerformanceTests\ServiceTests\Microsoft.OData.Performance.ServiceTests.csproj", "{1277D018-9DFD-45B4-B9AA-9A6D7229B02C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.PerfService", "..\test\PerformanceTests\Framework\PerfServiceCore\Microsoft.Test.OData.Services.PerfService.csproj", "{6DA1260D-FE35-424B-ADFA-158368AD02DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.TestService", "..\test\PerformanceTests\Framework\TestService\Microsoft.Test.OData.TestService.csproj", "{7FC34D8C-EB95-4C16-81BF-5CDC8E221536}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7FEDC028-14C8-4438-9EF0-9633057E4826}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2CCDC07F-FAFD-4D00-AA73-991CD2AB9DC7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "helper", "helper", "{6120E0DE-DE46-49C7-B517-15E449D17BAC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perfTests", "perfTests", "{90DC3112-A209-499B-8A43-FFFF83A216C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.DependencyInjection", "..\test\Common\Microsoft.Test.OData.DependencyInjection\Build.NetFramework\Microsoft.Test.OData.DependencyInjection.csproj", "{50AA23B2-56EF-4C51-A270-8D5BD0C899B6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Cover|x64.ActiveCfg = Cover|x64 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Cover|x64.Build.0 = Cover|x64 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Cover|x86.ActiveCfg = Cover|x86 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Cover|x86.Build.0 = Cover|x86 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Debug|x64.ActiveCfg = Debug|x64 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Debug|x64.Build.0 = Debug|x64 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Debug|x86.ActiveCfg = Debug|x86 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Debug|x86.Build.0 = Debug|x86 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Release|Any CPU.Build.0 = Release|Any CPU + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Release|x64.ActiveCfg = Release|x64 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Release|x64.Build.0 = Release|x64 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Release|x86.ActiveCfg = Release|x86 + {7D98AD3B-6685-498A-834C-F18F7FAE9138}.Release|x86.Build.0 = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.Build.0 = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.ActiveCfg = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.Build.0 = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.ActiveCfg = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.Build.0 = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.ActiveCfg = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.Build.0 = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.ActiveCfg = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.Build.0 = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.Build.0 = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.ActiveCfg = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.Build.0 = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.Build.0 = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.ActiveCfg = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.Build.0 = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.ActiveCfg = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.Build.0 = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.ActiveCfg = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.Build.0 = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.ActiveCfg = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.Build.0 = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.Build.0 = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.ActiveCfg = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.Build.0 = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.Build.0 = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.ActiveCfg = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.Build.0 = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.ActiveCfg = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.Build.0 = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.ActiveCfg = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.Build.0 = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.ActiveCfg = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.Build.0 = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.Build.0 = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.ActiveCfg = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.Build.0 = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.Build.0 = Release|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|Any CPU.Build.0 = Cover|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x64.ActiveCfg = Cover|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x64.Build.0 = Cover|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x86.ActiveCfg = Cover|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Cover|x86.Build.0 = Cover|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x64.ActiveCfg = Debug|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x64.Build.0 = Debug|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x86.ActiveCfg = Debug|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Debug|x86.Build.0 = Debug|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|Any CPU.Build.0 = Release|Any CPU + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x64.ActiveCfg = Release|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x64.Build.0 = Release|x64 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x86.ActiveCfg = Release|x86 + {DF028E55-CE75-4F32-822E-F9EC9C756AE2}.Release|x86.Build.0 = Release|x86 + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Cover|Any CPU.Build.0 = Release|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Cover|x64.ActiveCfg = Release|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Cover|x86.ActiveCfg = Release|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Debug|x64.ActiveCfg = Debug|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Debug|x86.ActiveCfg = Debug|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Release|Any CPU.Build.0 = Release|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Release|x64.ActiveCfg = Release|Any CPU + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C}.Release|x86.ActiveCfg = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Cover|Any CPU.Build.0 = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Cover|x64.ActiveCfg = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Cover|x86.ActiveCfg = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Debug|x64.ActiveCfg = Debug|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Release|Any CPU.Build.0 = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Release|x64.ActiveCfg = Release|Any CPU + {6DA1260D-FE35-424B-ADFA-158368AD02DC}.Release|x86.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Cover|Any CPU.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Cover|Any CPU.Build.0 = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Cover|x64.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Cover|x86.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Debug|Any CPU.Build.0 = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Debug|x64.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Debug|x86.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Release|Any CPU.Build.0 = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Release|x64.ActiveCfg = Release|Any CPU + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536}.Release|x86.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|Any CPU.Build.0 = Cover|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x64.ActiveCfg = Cover|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x64.Build.0 = Cover|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x86.ActiveCfg = Cover|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Cover|x86.Build.0 = Cover|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x64.ActiveCfg = Debug|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x64.Build.0 = Debug|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x86.ActiveCfg = Debug|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Debug|x86.Build.0 = Debug|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|Any CPU.Build.0 = Release|Any CPU + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x64.ActiveCfg = Release|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x64.Build.0 = Release|x64 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x86.ActiveCfg = Release|x86 + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7D98AD3B-6685-498A-834C-F18F7FAE9138} = {90DC3112-A209-499B-8A43-FFFF83A216C3} + {989A83CC-B864-4A75-8BF3-5EDA99203A86} = {7FEDC028-14C8-4438-9EF0-9633057E4826} + {7D921888-FE03-4C3F-80FE-2F624505461C} = {7FEDC028-14C8-4438-9EF0-9633057E4826} + {5D921888-FE03-4C3F-40FE-2F624505461D} = {7FEDC028-14C8-4438-9EF0-9633057E4826} + {DF028E55-CE75-4F32-822E-F9EC9C756AE2} = {6120E0DE-DE46-49C7-B517-15E449D17BAC} + {1277D018-9DFD-45B4-B9AA-9A6D7229B02C} = {90DC3112-A209-499B-8A43-FFFF83A216C3} + {6DA1260D-FE35-424B-ADFA-158368AD02DC} = {6120E0DE-DE46-49C7-B517-15E449D17BAC} + {7FC34D8C-EB95-4C16-81BF-5CDC8E221536} = {6120E0DE-DE46-49C7-B517-15E449D17BAC} + {6120E0DE-DE46-49C7-B517-15E449D17BAC} = {2CCDC07F-FAFD-4D00-AA73-991CD2AB9DC7} + {90DC3112-A209-499B-8A43-FFFF83A216C3} = {2CCDC07F-FAFD-4D00-AA73-991CD2AB9DC7} + {50AA23B2-56EF-4C51-A270-8D5BD0C899B6} = {6120E0DE-DE46-49C7-B517-15E449D17BAC} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/sln/OData.Tests.WindowsApps.sln b/ApiAsAService/odata.net/sln/OData.Tests.WindowsApps.sln new file mode 100644 index 0000000..c088b84 --- /dev/null +++ b/ApiAsAService/odata.net/sln/OData.Tests.WindowsApps.sln @@ -0,0 +1,283 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Framework.Portable", "..\test\EndToEndTests\Framework\Core\Build.PortableLibrary\Microsoft.Test.OData.Framework.Portable.csproj", "{134D2AD7-3C82-45C9-AC43-75F482081F8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Client.Portable", "..\src\Microsoft.OData.Client\Build.Portable\Microsoft.OData.Client.Portable.csproj", "{AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial", "..\src\Microsoft.Spatial\Microsoft.Spatial.csproj", "{5D921888-FE03-4C3F-40FE-2F624505461D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core", "..\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj", "{989A83CC-B864-4A75-8BF3-5EDA99203A86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm", "..\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj", "{7D921888-FE03-4C3F-80FE-2F624505461C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Services.TestServices.Portable", "..\test\EndToEndTests\Services\TestServices\Build.PortableLibrary\Microsoft.Test.OData.Services.TestServices.Portable.csproj", "{4382D649-1A86-48D0-9156-AC37C3D568C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Tests.Client.Portable.Phone", "..\test\EndToEndTests\Tests\Client\Build.PortableLibrary.Phone\Microsoft.Test.OData.Tests.Client.Portable.Phone.csproj", "{B2DA4083-71ED-464A-946E-CC9A30C1075A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Test.OData.Tests.Client.Portable.Desktop", "..\test\EndToEndTests\Tests\Client\Build.PortableLibrary.Desktop\Microsoft.Test.OData.Tests.Client.Portable.Desktop.csproj", "{D40D9FEB-F6B1-4C2E-88AE-853E825988BD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{09ED6894-AF82-4D59-975B-EB0744E7DB35}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3C18710D-6AA0-418A-A407-02FF2E0C0B52}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "e2eTests", "e2eTests", "{6DE5F1E9-C385-4F0E-8609-4C9071EB6E7B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "helper", "helper", "{45E741AB-6521-43CB-A954-1783677D7EBD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Cover|Any CPU = Cover|Any CPU + Cover|ARM = Cover|ARM + Cover|Mixed Platforms = Cover|Mixed Platforms + Cover|x64 = Cover|x64 + Cover|x86 = Cover|x86 + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|Mixed Platforms = Release|Mixed Platforms + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|ARM.ActiveCfg = Cover|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|Mixed Platforms.ActiveCfg = Cover|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|Mixed Platforms.Build.0 = Cover|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|x64.ActiveCfg = Cover|x64 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|x64.Build.0 = Cover|x64 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|x86.ActiveCfg = Cover|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Cover|x86.Build.0 = Cover|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|x64.ActiveCfg = Debug|x64 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|x64.Build.0 = Debug|x64 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|x86.ActiveCfg = Debug|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Debug|x86.Build.0 = Debug|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|Any CPU.Build.0 = Release|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|ARM.ActiveCfg = Release|Any CPU + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|Mixed Platforms.Build.0 = Release|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|x64.ActiveCfg = Release|x64 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|x64.Build.0 = Release|x64 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|x86.ActiveCfg = Release|x86 + {134D2AD7-3C82-45C9-AC43-75F482081F8D}.Release|x86.Build.0 = Release|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|Any CPU.Build.0 = Cover|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|ARM.ActiveCfg = Cover|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|Mixed Platforms.ActiveCfg = Cover|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|Mixed Platforms.Build.0 = Cover|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x64.ActiveCfg = Cover|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x64.Build.0 = Cover|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x86.ActiveCfg = Cover|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Cover|x86.Build.0 = Cover|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|ARM.ActiveCfg = Debug|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x64.ActiveCfg = Debug|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x64.Build.0 = Debug|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x86.ActiveCfg = Debug|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Debug|x86.Build.0 = Debug|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|Any CPU.Build.0 = Release|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|ARM.ActiveCfg = Release|Any CPU + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|Mixed Platforms.Build.0 = Release|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x64.ActiveCfg = Release|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x64.Build.0 = Release|x64 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x86.ActiveCfg = Release|x86 + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18}.Release|x86.Build.0 = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.Build.0 = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|ARM.ActiveCfg = Cover|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Mixed Platforms.ActiveCfg = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Mixed Platforms.Build.0 = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.ActiveCfg = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.Build.0 = Cover|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.ActiveCfg = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.Build.0 = Cover|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.ActiveCfg = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.Build.0 = Debug|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.ActiveCfg = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.Build.0 = Debug|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.Build.0 = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|ARM.ActiveCfg = Release|Any CPU + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Mixed Platforms.Build.0 = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.ActiveCfg = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.Build.0 = Release|x64 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.ActiveCfg = Release|x86 + {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.Build.0 = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.Build.0 = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|ARM.ActiveCfg = Cover|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Mixed Platforms.ActiveCfg = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Mixed Platforms.Build.0 = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.ActiveCfg = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.Build.0 = Cover|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.ActiveCfg = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.Build.0 = Cover|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|ARM.ActiveCfg = Debug|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.ActiveCfg = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.Build.0 = Debug|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.ActiveCfg = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.Build.0 = Debug|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.Build.0 = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|ARM.ActiveCfg = Release|Any CPU + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Mixed Platforms.Build.0 = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.ActiveCfg = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.Build.0 = Release|x64 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.ActiveCfg = Release|x86 + {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.Build.0 = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.Build.0 = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|ARM.ActiveCfg = Cover|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Mixed Platforms.ActiveCfg = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Mixed Platforms.Build.0 = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.ActiveCfg = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.Build.0 = Cover|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.ActiveCfg = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.Build.0 = Cover|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.ActiveCfg = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.Build.0 = Debug|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.ActiveCfg = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.Build.0 = Debug|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.Build.0 = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|ARM.ActiveCfg = Release|Any CPU + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Mixed Platforms.Build.0 = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.ActiveCfg = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.Build.0 = Release|x64 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.ActiveCfg = Release|x86 + {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.Build.0 = Release|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|Any CPU.Build.0 = Cover|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|ARM.ActiveCfg = Cover|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|Mixed Platforms.ActiveCfg = Cover|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|Mixed Platforms.Build.0 = Cover|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x64.ActiveCfg = Cover|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x64.Build.0 = Cover|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x86.ActiveCfg = Cover|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Cover|x86.Build.0 = Cover|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|ARM.ActiveCfg = Debug|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x64.ActiveCfg = Debug|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x64.Build.0 = Debug|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x86.ActiveCfg = Debug|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Debug|x86.Build.0 = Debug|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|Any CPU.Build.0 = Release|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|ARM.ActiveCfg = Release|Any CPU + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|Mixed Platforms.Build.0 = Release|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x64.ActiveCfg = Release|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x64.Build.0 = Release|x64 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x86.ActiveCfg = Release|x86 + {4382D649-1A86-48D0-9156-AC37C3D568C0}.Release|x86.Build.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|Any CPU.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|ARM.ActiveCfg = Cover|Any CPU + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|Mixed Platforms.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|Mixed Platforms.Build.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|Mixed Platforms.Deploy.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|x64.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|x86.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|x86.Build.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Cover|x86.Deploy.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|x64.ActiveCfg = Debug|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|x86.ActiveCfg = Debug|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|x86.Build.0 = Debug|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Debug|x86.Deploy.0 = Debug|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|Any CPU.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|ARM.ActiveCfg = Release|Any CPU + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|Mixed Platforms.Build.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|Mixed Platforms.Deploy.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|x64.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|x86.ActiveCfg = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|x86.Build.0 = Release|x86 + {B2DA4083-71ED-464A-946E-CC9A30C1075A}.Release|x86.Deploy.0 = Release|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|Any CPU.ActiveCfg = Cover|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|Any CPU.Build.0 = Cover|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|ARM.ActiveCfg = Cover|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|ARM.Build.0 = Cover|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|Mixed Platforms.ActiveCfg = Cover|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|Mixed Platforms.Build.0 = Cover|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|x64.ActiveCfg = Cover|x64 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|x64.Build.0 = Cover|x64 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|x86.ActiveCfg = Cover|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Cover|x86.Build.0 = Cover|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|ARM.ActiveCfg = Debug|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|ARM.Build.0 = Debug|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|x64.ActiveCfg = Debug|x64 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|x64.Build.0 = Debug|x64 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|x86.ActiveCfg = Debug|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Debug|x86.Build.0 = Debug|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|Any CPU.Build.0 = Release|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|ARM.ActiveCfg = Release|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|ARM.Build.0 = Release|Any CPU + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|Mixed Platforms.Build.0 = Release|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|x64.ActiveCfg = Release|x64 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|x64.Build.0 = Release|x64 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|x86.ActiveCfg = Release|x86 + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {134D2AD7-3C82-45C9-AC43-75F482081F8D} = {45E741AB-6521-43CB-A954-1783677D7EBD} + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18} = {09ED6894-AF82-4D59-975B-EB0744E7DB35} + {5D921888-FE03-4C3F-40FE-2F624505461D} = {09ED6894-AF82-4D59-975B-EB0744E7DB35} + {989A83CC-B864-4A75-8BF3-5EDA99203A86} = {09ED6894-AF82-4D59-975B-EB0744E7DB35} + {7D921888-FE03-4C3F-80FE-2F624505461C} = {09ED6894-AF82-4D59-975B-EB0744E7DB35} + {4382D649-1A86-48D0-9156-AC37C3D568C0} = {45E741AB-6521-43CB-A954-1783677D7EBD} + {B2DA4083-71ED-464A-946E-CC9A30C1075A} = {6DE5F1E9-C385-4F0E-8609-4C9071EB6E7B} + {D40D9FEB-F6B1-4C2E-88AE-853E825988BD} = {6DE5F1E9-C385-4F0E-8609-4C9071EB6E7B} + {6DE5F1E9-C385-4F0E-8609-4C9071EB6E7B} = {3C18710D-6AA0-418A-A407-02FF2E0C0B52} + {45E741AB-6521-43CB-A954-1783677D7EBD} = {3C18710D-6AA0-418A-A407-02FF2E0C0B52} + EndGlobalSection +EndGlobal diff --git a/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.cs b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.cs new file mode 100644 index 0000000..9a15ee8 --- /dev/null +++ b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Security; + +#if !SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE +#if PORTABLELIB +#if ODATA_CLIENT +[assembly: TargetFramework(".NETPortable,Version=v4.5,Profile=Profile259", FrameworkDisplayName = ".NET Portable Subset")] +#else +[assembly: TargetFramework(".NETPortable,Version=v4.0,Profile=Profile328", FrameworkDisplayName = ".NET Portable Subset")] +#endif +#endif +#endif + +[assembly: AssemblyCompany("Microsoft Corporation")] +// If you want to control this metadata globally but not with the VersionProductName property, hard-code the value below. +// If you want to control this metadata at the individual project level with AssemblyInfo.cs, comment-out the line below. +// If you leave the line below unchanged, make sure to set the property in the root build.props, e.g.: Your Product Name +// [assembly: AssemblyProduct("%VersionProductName%")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyTrademark("Microsoft and Windows are either registered trademarks or trademarks of Microsoft Corporation in the U.S. and/or other countries.")] +[assembly: AssemblyCulture("")] +#if (DEBUG || _DEBUG) +[assembly: AssemblyConfiguration("Debug")] +#endif + +#if ASSEMBLY_ATTRIBUTE_PRODUCT_VS +[assembly: AssemblyProduct("Microsoft (R) Visual Studio (R) 2010")] +#else +[assembly: AssemblyProduct("Microsoft® .NET Framework")] +#endif + +#if ASSEMBLY_ATTRIBUTE_CLS_COMPLIANT +[assembly: CLSCompliant(true)] +#else +[assembly: CLSCompliant(false)] +#endif + +#if ASSEMBLY_ATTRIBUTE_COM_VISIBLE +[assembly: ComVisible(true)] +#else +[assembly: ComVisible(false)] +#endif + +#if ASSEMBLY_ATTRIBUTE_COM_COMPATIBLE_SIDEBYSIDE +[assembly:ComCompatibleVersion(1,0,3300,0)] +#endif + +#if ASSEMBLY_ATTRIBUTE_ALLOW_PARTIALLY_TRUSTED_CALLERS +[assembly: AllowPartiallyTrustedCallers] +#else +#if ASSEMBLY_ATTRIBUTE_CONDITIONAL_APTCA_L2 +[assembly:AllowPartiallyTrustedCallers(PartialTrustVisibilityLevel=PartialTrustVisibilityLevel.NotVisibleByDefault)] +#endif +#endif + +#if ASSEMBLY_ATTRIBUTE_TRANSPARENT_ASSEMBLY +[assembly: SecurityTransparent] +#endif + +#if !SUPPRESS_SECURITY_RULES +#if SECURITY_MIGRATION && !ASSEMBLY_ATTRIBUTE_CONDITIONAL_APTCA_L2 +#if ASSEMBLY_ATTRIBUTE_SKIP_VERIFICATION_IN_FULLTRUST +[assembly: SecurityRules(SecurityRuleSet.Level1, SkipVerificationInFullTrust = true)] +#else +[assembly: SecurityRules(SecurityRuleSet.Level1)] +#endif +#else +#if ASSEMBLY_ATTRIBUTE_SKIP_VERIFICATION_IN_FULLTRUST +[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)] +#else +[assembly: SecurityRules(SecurityRuleSet.Level2)] +#endif +#endif +#endif + +[assembly:NeutralResourcesLanguageAttribute("en-US")] diff --git a/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.vb b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.vb new file mode 100644 index 0000000..a9652d8 --- /dev/null +++ b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyInfoCommon.vb @@ -0,0 +1,101 @@ +'--------------------------------------------------------------------- +' +' Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +' +'--------------------------------------------------------------------- + +Imports System +Imports System.Reflection +Imports System.Resources +Imports System.Runtime.CompilerServices +Imports System.Runtime.InteropServices +Imports System.Security + +' The following assembly information is common to all product assemblies. +' If you get compiler errors CS0579, "Duplicate '' attribute", check your +' Properties\AssemblyInfo.vb file and remove any lines duplicating the ones below. +' (See also AssemblyVersion.vb in this same directory.) + +' If you want to control this metadata globally but not with the VersionProductName property, hard-code the value below. +' If you want to control this metadata at the individual project level with AssemblyInfo.cs, comment-out the line below. +' If you leave the line below unchanged, make sure to set the property in the root build.props, e.g.: Your Product Name +' + + + +#If (DEBUG OrElse _DEBUG) Then + +#End If + +#If ASSEMBLY_ATTRIBUTE_PRODUCT_VS + +#Else + +#End If + +#If ASSEMBLY_ATTRIBUTE_CLS_COMPLIANT + +#Else + +#End If + +#If ASSEMBLY_ATTRIBUTE_COM_VISIBLE + +#Else + +#End If + +#If ASSEMBLY_ATTRIBUTE_COM_COMPATIBLE_SIDEBYSIDE + +#End If + +#If ASSEMBLY_ATTRIBUTE_ALLOW_PARTIALLY_TRUSTED_CALLERS + +#Else +#If ASSEMBLY_ATTRIBUTE_CONDITIONAL_APTCA_L2 + +#End If +#End If + +#If ASSEMBLY_ATTRIBUTE_TRANSPARENT_ASSEMBLY + +#End If + +#If NOT SUPPRESS_SECURITY_RULES +#If SECURITY_MIGRATION AND NOT ASSEMBLY_ATTRIBUTE_CONDITIONAL_APTCA_L2 +#If ASSEMBLY_ATTRIBUTE_SKIP_VERIFICATION_IN_FULLTRUST + +#Else + +#End If +#Else +#If ASSEMBLY_ATTRIBUTE_SKIP_VERIFICATION_IN_FULLTRUST + +#Else + +#End If +#End If +#End If + + + +'''

    +''' Sets public key string for friend assemblies. +''' +Friend Module AssemblyRef + +#If DelaySignKeys Then + Friend Const ProductPublicKey As String = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9" + Friend Const TestPublicKey As String = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100197c25d0a04f73cb271e8181dba1c0c713df8deebb25864541a66670500f34896d280484b45fe1ff6c29f2ee7aa175d8bcbd0c83cc23901a894a86996030f6292ce6eda6e6f3e6c74b3c5a3ded4903c951e6747e6102969503360f7781bf8bf015058eb89b7621798ccc85aaca036ff1bc1556bb7f62de15908484886aa8bbae" + Friend Const ProductPublicKeyToken As String = "b03f5f7f11d50a3a" +#ElseIf TestSignKeys Then + Friend Const TestPublicKey As String = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100197c25d0a04f73cb271e8181dba1c0c713df8deebb25864541a66670500f34896d280484b45fe1ff6c29f2ee7aa175d8bcbd0c83cc23901a894a86996030f6292ce6eda6e6f3e6c74b3c5a3ded4903c951e6747e6102969503360f7781bf8bf015058eb89b7621798ccc85aaca036ff1bc1556bb7f62de15908484886aa8bbae" + Friend Const ProductPublicKey As String = TestPublicKey + Friend Const ProductPublicKeyToken As String = "69c3241e6f0468ca" +#Else + '''No Signing + Friend Const ProductPublicKey As String = "" + Friend Const TestPublicKey As String = "" + Friend Const ProductPublicKeyToken As String = "" +#End If +End Module diff --git a/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyKeys.cs b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyKeys.cs new file mode 100644 index 0000000..5f8a549 --- /dev/null +++ b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyKeys.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +/// +/// Sets public key string for friend assemblies. +/// +internal static class AssemblyRef +{ +#if DelaySignKeys + /// ProductPublicKey is an official MS supported public key for external releases. + internal const string ProductPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9"; + + /// TestPublicKey is an unsupported strong key for testing purpose only. + internal const string TestPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100197c25d0a04f73cb271e8181dba1c0c713df8deebb25864541a66670500f34896d280484b45fe1ff6c29f2ee7aa175d8bcbd0c83cc23901a894a86996030f6292ce6eda6e6f3e6c74b3c5a3ded4903c951e6747e6102969503360f7781bf8bf015058eb89b7621798ccc85aaca036ff1bc1556bb7f62de15908484886aa8bbae"; + + /// Dont know what this is + internal const string ProductPublicKeyToken = "31bf3856ad364e35"; + +#elif TestSignKeys + /// Dont know what this is + internal const string TestPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100197c25d0a04f73cb271e8181dba1c0c713df8deebb25864541a66670500f34896d280484b45fe1ff6c29f2ee7aa175d8bcbd0c83cc23901a894a86996030f6292ce6eda6e6f3e6c74b3c5a3ded4903c951e6747e6102969503360f7781bf8bf015058eb89b7621798ccc85aaca036ff1bc1556bb7f62de15908484886aa8bbae"; + + /// Dont know what this is + internal const string ProductPublicKey = TestPublicKey; + + /// Dont know what this is + internal const string ProductPublicKeyToken = "69c3241e6f0468ca"; + +#else + /// No signing + internal const string ProductPublicKey = ""; + + /// No signing + internal const string TestPublicKey = ""; + + /// No signing + internal const string ProductPublicKeyToken = ""; + +#endif +} diff --git a/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyMetadataAttribute.cs b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyMetadataAttribute.cs new file mode 100644 index 0000000..a436c0d --- /dev/null +++ b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyMetadataAttribute.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace System.Reflection +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] + internal sealed class AssemblyMetadataAttribute : Attribute + { + public AssemblyMetadataAttribute(string key, string value) + { + Key = key; + Value = value; + } + + public string Key { get; set; } + public string Value { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.cs b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.cs new file mode 100644 index 0000000..8a0e1c3 --- /dev/null +++ b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.cs @@ -0,0 +1,201 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Runtime.InteropServices; + +/// Current assembly reference +/// DO NOT FORGET TO ALSO UPDATE AssemblyRefs.vb +internal static class DataFxAssemblyRef +{ + /// Current assembly names + internal static class Name + { + internal const string mscorlib = "mscorlib"; + internal const string System = "System"; + internal const string SystemCore = "System.Core"; + internal const string SystemData = "System.Data"; + internal const string SystemDataDataSetExtensions = "System.Data.DataSetExtensions"; + internal const string SystemDataEntity = "System.Data.Entity"; + internal const string SystemDataLinq = "System.Data.Linq"; + internal const string SystemDrawing = "System.Drawing"; + internal const string SystemNumerics = "System.Numerics"; + internal const string SystemRuntimeRemoting = "System.Runtime.Remoting"; + internal const string SystemRuntimeSerialization = "System.Runtime.Serialization"; + internal const string SystemServiceModel = "System.ServiceModel"; + internal const string SystemWeb = "System.Web"; + internal const string SystemWebEntity = "System.Web.Entity"; + internal const string SystemWebEntityDesign = "System.Web.Entity.Design"; + internal const string SystemXml = "System.Xml"; + internal const string SystemXmlLinq = "System.Xml.Linq"; + + internal const string DataEntity = "System.Data.Entity"; + internal const string EntityFramework = "EntityFramework"; + internal const string DataEntityDesign = "System.Data.Entity.Design"; + internal const string DataServices = "Microsoft.OData.Service"; + internal const string DataServicesClient = "Microsoft.OData.Client"; + internal const string DataSvcUtil = "DataSvcUtil"; + internal const string OData = "Microsoft.OData.Core"; + internal const string Spatial = "Microsoft.Spatial"; + internal const string EntityDataModel = "Microsoft.OData.Edm"; + } + + /// Current assembly file names + internal static class File + { + /// base version for data framework + internal const string DS_BaseVersion = VersionConstants.ReleaseVersion; + + /// where to find desktop reference client sku reference assemblies + internal const string DotNetFrameworkV4_ClientReferenceAssemblyPath = @"%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client"; + + /// where to find desktop reference extended sku reference assemblies + internal const string DotNetFrameworkV4_ReferenceAssemblyPath = @"%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"; + + /// where find executable binaries + internal const string DotNetFrameworkV4_InstallPath = @"%SystemRoot%\Microsoft.NET\Framework\v4.0.30319"; + + internal const string SilverlightV5_ReferenceAssemblyPath = @"%ProgramFiles%\Microsoft Silverlight\5.1.20913.0"; + internal const string SilverlightV5_SdkClientReferenceAssemblyPath = @"%ProgramFiles%\Microsoft SDKs\Silverlight\v5.0\Libraries\Client"; + internal const string SilverlightV5_SdkServerReferenceAssemblyPath = @"%ProgramFiles%\Microsoft SDKs\Silverlight\v5.0\Libraries\Server"; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string DE_InstallPath + { + get { return GetDE_InstallPath(); } + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string DS_InstallPath + { + get { return GetDS_InstallPath(); } + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string DS_PortableInstallPath + { + get { return GetDS_PortableInstallPath(); } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string DS_Tools_InstallPath + { + get { return GetDS_Tools_InstallPath(); } + } + + /// where to find desktop reference assemblies + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string DE_ReferenceAssemblyPath + { + get { return GetDE_InstallPath(); } + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string DS_ReferenceAssemblyPath + { + get { return GetDS_InstallPath(); } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private static string GetDE_InstallPath() + { + return RuntimeEnvironment.GetRuntimeDirectory(); + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private static string GetDS_InstallPath() + { + return Environment.ExpandEnvironmentVariables(Environment.Is64BitOperatingSystem + ? @"%ProgramFiles(x86)%\Microsoft WCF Data Services\" + DS_BaseVersion + @"\bin\.NetFramework" + : @"%ProgramFiles%\Microsoft WCF Data Services\" + DS_BaseVersion + @"\bin\.NetFramework"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private static string GetDS_PortableInstallPath() + { + return Environment.ExpandEnvironmentVariables(Environment.Is64BitOperatingSystem + ? @"%ProgramFiles(x86)%\Microsoft WCF Data Services\" + DS_BaseVersion + @"\bin\.NetPortable" + : @"%ProgramFiles%\Microsoft WCF Data Services\" + DS_BaseVersion + @"\bin\.NetPortable"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private static string GetDS_Tools_InstallPath() + { + return Environment.ExpandEnvironmentVariables(Environment.Is64BitOperatingSystem + ? @"%ProgramFiles(x86)%\Microsoft WCF Data Services\" + DS_BaseVersion + @"\bin\tools" + : @"%ProgramFiles%\Microsoft WCF Data Services\" + DS_BaseVersion + @"\bin\tools"); + } + + /// where to find silverlight reference assemblies + internal const string DS_SilverlightReferenceAssemblyPath = @"%ProgramFiles%\Microsoft WCF Data Services\" + DS_BaseVersion + @"\bin\Silverlight"; + + internal const string EntityDataModel = "Microsoft.OData.Edm.dll"; + internal const string DataEntity = "System.Data.Entity.dll"; + internal const string EntityFramework = "EntityFramework.dll"; + internal const string DataEntityDesign = "System.Data.Entity.Design.dll"; + + internal const string DataServices = "Microsoft.OData.Service.dll"; + internal const string DataServicesClient = "Microsoft.OData.Client.dll"; + internal const string DataServicesSilverlightClient = "Microsoft.OData.Client.SL.dll"; + internal const string DataSvcUtil = "DataSvcUtil.exe"; + internal const string ODataLib = "Microsoft.OData.Core.dll"; + internal const string SpatialCore = "Microsoft.Spatial.dll"; + + internal const string System = "System.dll"; + internal const string SystemCore = "System.Core.dll"; + internal const string SystemIO = "System.IO.dll"; + internal const string SystemRuntime = "System.Runtime.dll"; + internal const string SystemXml = "System.Xml.dll"; + internal const string SystemXmlReaderWriter = "System.Xml.ReaderWriter.dll"; + } + + internal const string DataFxAssemblyVersion = VersionConstants.AssemblyVersion; + internal const string FXAssemblyVersion = "4.0.0.0"; + + internal const string EcmaPublicKey = "b77a5c561934e089"; + internal const string EcmaPublicKeyToken = EcmaPublicKey; + internal const string EcmaPublicKeyFull = "00000000000000000400000000000000"; + + internal const string MicrosoftPublicKey = "b03f5f7f11d50a3a"; + internal const string MicrosoftPublicKeyToken = MicrosoftPublicKey; + internal const string MicrosoftPublicKeyFull = "002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293"; + + internal const string SharedLibPublicKey = "31bf3856ad364e35"; + internal const string SharedLibPublicKeyToken = SharedLibPublicKey; + internal const string SharedLibPublicKeyFull = "0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9"; + + internal const string SilverlightPublicKey = "31bf3856ad364e35"; + internal const string SilverlightPublicKeyToken = SilverlightPublicKey; + internal const string SilverlightPublicKeyFull = "0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9"; + + internal const string SilverlightPlatformPublicKey = "7cec85d7bea7798e"; + internal const string SilverlightPlatformPublicKeyToken = SilverlightPlatformPublicKey; + internal const string SilverlightPlatformPublicKeyFull = "00240000048000009400000006020000002400005253413100040000010001008D56C76F9E8649383049F383C44BE0EC204181822A6C31CF5EB7EF486944D032188EA1D3920763712CCB12D75FB77E9811149E6148E5D32FBAAB37611C1878DDC19E20EF135D0CB2CFF2BFEC3D115810C3D9069638FE4BE215DBF795861920E5AB6F7DB2E2CEEF136AC23D5DD2BF031700AEC232F6C6B1C785B4305C123B37AB"; + + private const string DataFxFramework = ", Version=" + DataFxAssemblyVersion + ", Culture=neutral, PublicKeyToken=" + SharedLibPublicKey; + private const string MicrosoftFramework = ", Version=" + FXAssemblyVersion + ", Culture=neutral, PublicKeyToken=" + MicrosoftPublicKey; + private const string NetFxFramework = ", Version=" + FXAssemblyVersion + ", Culture=neutral, PublicKeyToken=" + EcmaPublicKey; + + internal const string Mscorlib = Name.mscorlib + NetFxFramework; + internal const string System = Name.System + NetFxFramework; + internal const string SystemCore = Name.SystemCore + NetFxFramework; + internal const string SystemData = Name.SystemData + NetFxFramework; + internal const string SystemDataDataSetExtensions = Name.SystemDataDataSetExtensions + NetFxFramework; + internal const string SystemDataEntity = Name.SystemDataEntity + NetFxFramework; + internal const string SystemDataLinq = Name.SystemDataLinq + NetFxFramework; + internal const string SystemDrawing = Name.SystemDrawing + NetFxFramework; + internal const string SystemNumerics = Name.SystemNumerics + NetFxFramework; + internal const string SystemRuntimeRemoting = Name.SystemRuntimeRemoting + NetFxFramework; + internal const string SystemRuntimeSerialization = Name.SystemRuntimeSerialization + NetFxFramework; + internal const string SystemServiceModel = Name.SystemServiceModel + NetFxFramework; + internal const string SystemWeb = Name.SystemWeb + MicrosoftFramework; + internal const string SystemWebEntity = Name.SystemWebEntity + NetFxFramework; + internal const string SystemWebEntityDesign = Name.SystemWebEntityDesign + NetFxFramework; + internal const string SystemXml = Name.SystemXml + NetFxFramework; + internal const string SystemXmlLinq = Name.SystemXmlLinq + NetFxFramework; + + internal const string DataServices = Name.DataServices + DataFxFramework; + internal const string DataServicesClient = Name.DataServicesClient + DataFxFramework; + internal const string EntityDataModel = Name.EntityDataModel + DataFxFramework; + internal const string OData = Name.OData + DataFxFramework; + internal const string Spatial = Name.Spatial + DataFxFramework; +} diff --git a/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.vb b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.vb new file mode 100644 index 0000000..a4ea37c --- /dev/null +++ b/ApiAsAService/odata.net/src/AssemblyInfo/AssemblyRefs.vb @@ -0,0 +1,103 @@ +'--------------------------------------------------------------------- +' +' Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +' +'--------------------------------------------------------------------- + +Friend Class DataFxAssemblyRef + + Friend Class Name + + Friend Shared mscorlib As String = "mscorlib" + Friend Shared System As String = "System" + Friend Shared SystemCore As String = "System.Core" + Friend Shared SystemData As String = "System.Data" + Friend Shared SystemDataDataSetExtensions As String = "System.Data.DataSetExtensions" + Friend Shared SystemDataEntity As String = "System.Data.Entity" + Friend Shared SystemNumerics As String = "System.Numerics" + Friend Shared SystemRuntimeRemoting As String = "System.Runtime.Remoting" + Friend Shared SystemRuntimeSerialization As String = "System.Runtime.Serialization" + Friend Shared SystemServiceModel As String = "System.ServiceModel" + Friend Shared SystemXml As String = "System.Xml" + Friend Shared SystemXmlLinq As String = "System.Xml.Linq" + + Friend Shared DataEntity As String = "System.Data.Entity" + Friend Shared DataEntityDesign As String = "System.Data.Entity.Design" + Friend Shared DataServices As String = "Microsoft.OData.Service" + Friend Shared DataServicesClient As String = "Microsoft.OData.Client" + Friend Shared DataServicesDesign As String = "Microsoft.OData.Service.Design" + Friend Shared DataSvcUtil As String = "DataSvcUtil" + End Class + + Friend Class File + Friend Shared DotNetFrameworkV4_ClientReferenceAssemblyPath As String = "%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client" + Friend Shared DotNetFrameworkV4_ReferenceAssemblyPath As String = "%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0" + + Friend Shared SilverlightV5_ReferenceAssemblyPath As String = "%ProgramFiles%\Microsoft Silverlight\5.1.20913.0" + Friend Shared SilverlightV5_SdkClientReferenceAssemblyPath As String = "%ProgramFiles%\Microsoft SDKs\Silverlight\v5.0\Libraries\Client" + Friend Shared SilverlightV5_SdkServerReferenceAssemblyPath As String = "%ProgramFiles%\Microsoft SDKs\Silverlight\v5.0\Libraries\Server" + Friend Shared DS_BaseVersion As String = VersionConstants.ReleaseVersion + Friend Shared EF_ReferenceAssemblyPath As String = "%ProgramFiles%\Microsoft Entity Framework June 2011 CTP\bin\.NetFramework" + Friend Shared DS_ReferenceAssemblyPath As String = "%ProgramFiles%\Microsoft WCF Data Services\" + DS_BaseVersion + "\bin\.NetFramework" + Friend Shared DS_SilverlightReferenceAssemblyPath As String = "%ProgramFiles%\Microsoft WCF Data Services\" + DS_BaseVersion + "\bin\Silverlight" + Friend Shared DS_PortableInstallPath As String = "%ProgramFiles%\Microsoft WCF Data Services\" + DS_BaseVersion + "\bin\.NetPortable" + + Friend Shared DataEntity As String = "System.Data.Entity.dll" + Friend Shared DataEntityDesign As String = "System.Data.Entity.Design.dll" + + Friend Shared DataServices As String = "Microsoft.OData.Service.dll" + Friend Shared DataServicesClient As String = "Microsoft.OData.Client.dll" + Friend Shared ODataLib As String = "Microsoft.OData.Core.dll" + Friend Shared EdmLib As String = "Microsoft.OData.Edm.dll" + + Friend Shared DataServicesSilverlightClient As String = "Microsoft.OData.Client.SL.dll" + + Friend Shared DataServicesDesign As String = "Microsoft.OData.Service.Design.dll" + Friend Shared DataSvcUtil As String = "DataSvcUtil.exe" + + + End Class + + Friend Shared DataFxAssemblyVersion As String = VersionConstants.AssemblyVersion + Friend Shared FXAssemblyVersion As String = "4.0.0.0" + + Friend Shared EcmaPublicKey As String = "b77a5c561934e089" + Friend Shared EcmaPublicKeyToken As String = EcmaPublicKey + Friend Shared EcmaPublicKeyFull As String = "00000000000000000400000000000000" + + Friend Shared MicrosoftPublicKey As String = "b03f5f7f11d50a3a" + Friend Shared MicrosoftPublicKeyToken As String = MicrosoftPublicKey + Friend Shared MicrosoftPublicKeyFull As String = "002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293" + + Friend Shared SharedLibPublicKey As String = "31bf3856ad364e35" + Friend Shared SharedLibPublicKeyToken As String = SharedLibPublicKey + Friend Shared SharedLibPublicKeyFull As String = "0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9" + + Friend Shared SilverlightPublicKey As String = "31bf3856ad364e35" + Friend Shared SilverlightPublicKeyToken As String = SilverlightPublicKey + Friend Shared SilverlightPublicKeyFull As String = "0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9" + + Friend Shared SilverlightPlatformPublicKey As String = "7cec85d7bea7798e" + Friend Shared SilverlightPlatformPublicKeyToken As String = SilverlightPlatformPublicKey + Friend Shared SilverlightPlatformPublicKeyFull As String = "00240000048000009400000006020000002400005253413100040000010001008D56C76F9E8649383049F383C44BE0EC204181822A6C31CF5EB7EF486944D032188EA1D3920763712CCB12D75FB77E9811149E6148E5D32FBAAB37611C1878DDC19E20EF135D0CB2CFF2BFEC3D115810C3D9069638FE4BE215DBF795861920E5AB6F7DB2E2CEEF136AC23D5DD2BF031700AEC232F6C6B1C785B4305C123B37AB" + + Private Shared DataFxFramework As String = ", Version=" + DataFxAssemblyVersion + ", Culture=neutral, PublicKeyToken=" + SharedLibPublicKey + Private Shared NetFxFramework As String = ", Version=" + FXAssemblyVersion + ", Culture=neutral, PublicKeyToken=" + EcmaPublicKey + + Friend Shared Mscorlib As String = Name.mscorlib + NetFxFramework + Friend Shared System As String = Name.System + NetFxFramework + Friend Shared SystemCore As String = Name.SystemCore + NetFxFramework + Friend Shared SystemData As String = Name.SystemData + NetFxFramework + Friend Shared SystemDataDatasetExtensions As String = Name.SystemDataDataSetExtensions + NetFxFramework + Friend Shared SystemDataEntity As String = Name.SystemDataEntity + NetFxFramework + Friend Shared SystemNumerics As String = Name.SystemNumerics + NetFxFramework + Friend Shared SystemRuntimeRemoting As String = Name.SystemRuntimeRemoting + NetFxFramework + Friend Shared SystemRuntimeSerialization As String = Name.SystemRuntimeSerialization + NetFxFramework + Friend Shared SystemServiceModel As String = Name.SystemServiceModel + NetFxFramework + Friend Shared SystemXml As String = Name.SystemXml + NetFxFramework + Friend Shared SystemXmlLinq As String = Name.SystemXmlLinq + NetFxFramework + + Friend Shared DataServices As String = Name.DataServices + DataFxFramework + Friend Shared DataServicesClient As String = Name.DataServicesClient + DataFxFramework + Friend Shared DataServicesDesign As String = Name.DataServicesDesign + DataFxFramework +End Class \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Build.props b/ApiAsAService/odata.net/src/Build.props new file mode 100644 index 0000000..caccf0c --- /dev/null +++ b/ApiAsAService/odata.net/src/Build.props @@ -0,0 +1,43 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + true + + + + + + + Product\$(RelativeOutputPath) + false + $(MSBuildThisFileDirectory)Common.StyleCop + $(SigningScenarioForRelease) + Delay + Product + true + true + + + + + + + + + + + + + + + + + $(NugetPackOptions) -Symbols + $(NugetPackProperties);SourcesRoot=$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.root)) + + diff --git a/ApiAsAService/odata.net/src/CodeGen/GlobalSuppressions.cs b/ApiAsAService/odata.net/src/CodeGen/GlobalSuppressions.cs new file mode 100644 index 0000000..5408993 --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/GlobalSuppressions.cs @@ -0,0 +1,10 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +// APTCA +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA900")] \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/CodeGen/InternalsVisibleTo.cs b/ApiAsAService/odata.net/src/CodeGen/InternalsVisibleTo.cs new file mode 100644 index 0000000..e16cf07 --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/InternalsVisibleTo.cs @@ -0,0 +1,11 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma warning disable 436 +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Service.Design.UnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Client.Design.T4.UnitTests" + AssemblyRef.TestPublicKey)] +#pragma warning restore 436 \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/CodeGen/Microsoft.Data.Web.Design.T4.csproj b/ApiAsAService/odata.net/src/CodeGen/Microsoft.Data.Web.Design.T4.csproj new file mode 100644 index 0000000..c1bb086 --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/Microsoft.Data.Web.Design.T4.csproj @@ -0,0 +1,60 @@ + + + + Microsoft.OData.Service.Design.T4 + Library + false + true + $(AssemblyName).xml + System + {E4167281-C1AF-48C1-9BBD-0453BFB8CE2D} + v4.5.1 + false + true + + + + + + + + + + + + + {7d921888-fe03-4c3f-80fe-2f624505461c} + Microsoft.OData.Edm + + + + + + AssemblyRefs.cs + + + + True + True + ODataT4CodeGenerator.tt + + + + + + + + + TextTemplatingFilePreprocessor + Microsoft.OData.Client.Design.T4 + ODataT4CodeGenerator.cs + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.cs b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.cs new file mode 100644 index 0000000..ee524e6 --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.cs @@ -0,0 +1,7459 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 15.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Microsoft.OData.Client.Design.T4 +{ + using System; + using System.IO; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Xml; + using System.Xml.Linq; + using System.Collections.Generic; + using Microsoft.OData.Edm.Csdl; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + using Microsoft.OData.Edm.Vocabularies.V1; + using Microsoft.OData.Edm.Vocabularies.Community.V1; + using System.Text; + using System.Net; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "15.0.0.0")] + internal partial class ODataT4CodeGenerator : ODataT4CodeGeneratorBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + +/* +OData Client T4 Template ver. #VersionNumber# +Copyright (c) Microsoft Corporation +All rights reserved. +MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + + CodeGenerationContext context; + if (!string.IsNullOrWhiteSpace(this.Edmx)) + { + context = new CodeGenerationContext(this.Edmx, this.NamespacePrefix) + { + UseDataServiceCollection = this.UseDataServiceCollection, + TargetLanguage = this.TargetLanguage, + EnableNamingAlias = this.EnableNamingAlias, + TempFilePath = this.TempFilePath, + IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes + }; + } + else + { + this.ApplyParametersFromCommandLine(); + if (string.IsNullOrEmpty(metadataDocumentUri)) + { + this.ApplyParametersFromConfigurationClass(); + } + + context = new CodeGenerationContext(new Uri(this.MetadataDocumentUri, UriKind.Absolute), this.NamespacePrefix) + { + UseDataServiceCollection = this.UseDataServiceCollection, + TargetLanguage = this.TargetLanguage, + EnableNamingAlias = this.EnableNamingAlias, + TempFilePath = this.TempFilePath, + IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes + }; + } + + if (this.GetReferencedModelReaderFunc != null) + { + context.GetReferencedModelReaderFunc = this.GetReferencedModelReaderFunc; + } + + ODataClientTemplate template; + switch(this.TargetLanguage) + { + case LanguageOption.CSharp: + template = new ODataClientCSharpTemplate(context); + break; + case LanguageOption.VB: + template = new ODataClientVBTemplate(context); + break; + + default: + throw new NotSupportedException(string.Format("Code gen for the target language '{0}' is not supported.", this.TargetLanguage.ToString())); + } + + + this.Write(this.ToStringHelper.ToStringWithCulture(template.TransformText())); + + foreach (string warning in context.Warnings) + { + this.Warning(warning); + } + + return this.GenerationEnvironment.ToString(); + } + private global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost hostValue; + /// + /// The current host for the text templating engine + /// + public virtual global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost Host + { + get + { + return this.hostValue; + } + set + { + this.hostValue = value; + } + } + +public static class Configuration +{ + // The URI of the metadata document. The value must be set to a valid service document URI or a local file path + // eg : "http://services.odata.org/V4/OData/OData.svc/", "File:///C:/Odata.edmx", or @"C:\Odata.edmx" + // ### Notice ### If the OData service requires authentication for accessing the metadata document, the value of + // MetadataDocumentUri has to be set to a local file path, or the client code generation process will fail. + public const string MetadataDocumentUri = ""; + + // The use of DataServiceCollection enables entity and property tracking. The value must be set to true or false. + public const bool UseDataServiceCollection = true; + + // The namespace of the client code generated. It replaces the original namespace in the metadata document, + // unless the model has several namespaces. + public const string NamespacePrefix = "$rootnamespace$"; + + // The target language of the generated client code. The value must be set to "CSharp" or "VB". + public const string TargetLanguage = "OutputLanguage"; + + // The path for the temporary file where the metadata xml document can be stored. Use this if your metadata is too big to be stored in a string literal. Ensure that you have write permission for this path. + // For example - "C:\\temp\\Test.xml" + public const string TempFilePath = ""; + + // This flag indicates whether to enable naming alias. The value must be set to true or false. + public const bool EnableNamingAlias = true; + + // This flag indicates whether to ignore unexpected elements and attributes in the metadata document and generate + // the client code if any. The value must be set to true or false. + public const bool IgnoreUnexpectedElementsAndAttributes = true; +} + +public static class Customization +{ + /// + /// Changes the text to use upper camel case, which upper case for the first character. + /// + /// Text to convert. + /// The converted text in upper camel case + internal static string CustomizeNaming(string text) + { + if (string.IsNullOrEmpty(text)) + { + return text; + } + + if (text.Length == 1) + { + return Char.ToUpperInvariant(text[0]).ToString(CultureInfo.InvariantCulture); + } + + return Char.ToUpperInvariant(text[0]) + text.Substring(1); + } + + /// + /// Changes the namespace to use upper camel case, which upper case for the first character of all segments. + /// + /// Namespace to convert. + /// The converted namespace in upper camel case + internal static string CustomizeNamespace(string fullNamespace) + { + if (string.IsNullOrEmpty(fullNamespace)) + { + return fullNamespace; + } + + string[] segs = fullNamespace.Split('.'); + string upperNamespace = string.Empty; + int n = segs.Length; + for (int i = 0; i < n; ++i) + { + upperNamespace += Customization.CustomizeNaming(segs[i]); + upperNamespace += (i == n - 1 ? string.Empty : "."); + } + + return upperNamespace; + } +} + + +/// +/// The string for the edmx content. +/// +public string Edmx +{ + get; + set; +} + +/// +/// The Uri string to the metadata document. +/// +public string MetadataDocumentUri +{ + get + { + return this.metadataDocumentUri; + } + + set + { + value = Uri.UnescapeDataString(value); + Uri uri; + if (!Uri.TryCreate(value, UriKind.Absolute, out uri)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" is not a valid MetadataDocumentUri because is it not a valid absolute Uri. The MetadataDocumentUri must be set to an absolute Uri referencing the $metadata endpoint of an OData service.", value)); + } + + if (uri.Scheme == "http" || uri.Scheme == "https") + { + value = uri.Scheme + "://" + uri.Authority + uri.AbsolutePath; + value = value.TrimEnd('/'); + if (!value.EndsWith("$metadata")) + { + value += "/$metadata"; + } + } + + this.metadataDocumentUri = value; + } +} + +private string metadataDocumentUri; + +/// +/// The Func to get referenced model's XmlReader. Must have value when the this.Edmx xml or this.metadataDocumentUri's model has referneced model. +/// +public Func GetReferencedModelReaderFunc +{ + get; + set; +} + +/// +/// The NamespacePrefix is used as the only namespace for types in the same namespace as the default container, +/// and as a prefix for the namespace from the model for everything else. If this argument is null, the +/// namespaces from the model are used for all types. +/// +public string NamespacePrefix +{ + get + { + return this.namespacePrefix; + } + + set + { + if (string.IsNullOrWhiteSpace(value)) + { + this.namespacePrefix = null; + } + else + { + this.namespacePrefix = value; + } + } +} + +private string namespacePrefix; + +/// +/// true to use DataServiceCollection in the generated code, false otherwise. +/// +public bool UseDataServiceCollection +{ + get; + set; +} + +/// +/// Specifies which specific .Net Framework language the generated code will target. +/// +public LanguageOption TargetLanguage +{ + get; + set; +} + +/// +/// true to use Upper camel case for all class and property names, false otherwise. +/// +public bool EnableNamingAlias +{ + get; + set; +} + +/// +/// The path for the temporary file where the metadata xml document can be stored. +/// +public string TempFilePath +{ + get; + set; +} + +/// +/// true to ignore unknown elements or attributes in metadata, false otherwise. +/// +public bool IgnoreUnexpectedElementsAndAttributes +{ + get; + set; +} + +/// +/// Generate code targeting a specific .Net Framework language. +/// +public enum LanguageOption +{ + /// Generate code for C# language. + CSharp = 0, + + /// Generate code for Visual Basic language. + VB = 1, +} + +/// +/// Set the UseDataServiceCollection property with the given value. +/// +/// The value to set. +public void ValidateAndSetUseDataServiceCollectionFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the UseDataServiceCollection parameter because it is not a valid boolean value.", stringValue)); + } + + this.UseDataServiceCollection = boolValue; +} + +/// +/// Tries to set the TargetLanguage property with the given value. +/// +/// The value to set. +public void ValidateAndSetTargetLanguageFromString(string stringValue) +{ + LanguageOption option; + if (!Enum.TryParse(stringValue, true, out option)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the TargetLanguage parameter because it is not a valid LanguageOption. The supported LanguageOptions are \"CSharp\" and \"VB\".", stringValue)); + } + + this.TargetLanguage = option; +} + +/// +/// Set the EnableNamingAlias property with the given value. +/// +/// The value to set. +public void ValidateAndSetEnableNamingAliasFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the EnableNamingAlias parameter because it is not a valid boolean value.", stringValue)); + } + + this.EnableNamingAlias = boolValue; +} + +/// +/// Set the IgnoreUnexpectedElementsAndAttributes property with the given value. +/// +/// The value to set. +public void ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the IgnoreUnexpectedElementsAndAttributes parameter because it is not a valid boolean value.", stringValue)); + } + + this.IgnoreUnexpectedElementsAndAttributes = boolValue; +} + +/// +/// Reads the parameter values from the Configuration class and applies them. +/// +private void ApplyParametersFromConfigurationClass() +{ + this.MetadataDocumentUri = Configuration.MetadataDocumentUri; + this.NamespacePrefix = Configuration.NamespacePrefix; + this.UseDataServiceCollection = Configuration.UseDataServiceCollection; + this.ValidateAndSetTargetLanguageFromString(Configuration.TargetLanguage); + this.EnableNamingAlias = Configuration.EnableNamingAlias; + this.TempFilePath = Configuration.TempFilePath; + this.IgnoreUnexpectedElementsAndAttributes = Configuration.IgnoreUnexpectedElementsAndAttributes; +} + +/// +/// Reads the parameter values from the command line (TextTransform.exe) and applies them. +/// +private void ApplyParametersFromCommandLine() +{ + if (this.Host == null) + { + return; + } + + string metadataDocumentUri = this.Host.ResolveParameterValue("notempty", "notempty", "MetadataDocumentUri"); + if (!string.IsNullOrEmpty(metadataDocumentUri)) + { + this.MetadataDocumentUri = metadataDocumentUri; + } + + string namespacePrefix = this.Host.ResolveParameterValue("notempty", "notempty", "NamespacePrefix"); + if (!string.IsNullOrEmpty(namespacePrefix)) + { + this.NamespacePrefix = namespacePrefix; + } + + string useDataServiceCollection = this.Host.ResolveParameterValue("notempty", "notempty", "UseDataServiceCollection"); + if (!string.IsNullOrEmpty(useDataServiceCollection)) + { + this.ValidateAndSetUseDataServiceCollectionFromString(useDataServiceCollection); + } + + string targetLanguage = this.Host.ResolveParameterValue("notempty", "notempty", "TargetLanguage"); + if (!string.IsNullOrEmpty(targetLanguage)) + { + this.ValidateAndSetTargetLanguageFromString(targetLanguage); + } + + string enableNamingAlias = this.Host.ResolveParameterValue("notempty", "notempty", "EnableNamingAlias"); + if (!string.IsNullOrEmpty(enableNamingAlias)) + { + this.ValidateAndSetEnableNamingAliasFromString(enableNamingAlias); + } + + string ignoreUnexpectedElementsAndAttributes = this.Host.ResolveParameterValue("notempty", "notempty", "IgnoreUnexpectedElementsAndAttributes"); + if (!string.IsNullOrEmpty(ignoreUnexpectedElementsAndAttributes)) + { + this.ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(ignoreUnexpectedElementsAndAttributes); + } +} + +/// +/// Context object to provide the model and configuration info to the code generator. +/// +public class CodeGenerationContext +{ + /// + /// The namespace of the term to use when building annotations for indicating the conventions used. + /// + private const string ConventionTermNamespace = "Com.Microsoft.OData.Service.Conventions.V1"; + + /// + /// The name of the term to use when building annotations for indicating the conventions used. + /// + private const string ConventionTermName = "UrlConventions"; + + /// + /// The string value for indicating that the key-as-segment convention is being used in annotations and headers. + /// + private const string KeyAsSegmentConventionName = "KeyAsSegment"; + + /// + /// The XElement for the edmx + /// + private readonly XElement edmx; + + /// + /// The namespacePrefix is used as the only namespace in generated code when there's only one schema in edm model, + /// and as a prefix for the namespace from the model with multiple schemas. If this argument is null, the + /// namespaces from the model are used for all types. + /// + private readonly string namespacePrefix; + + /// + /// The EdmModel to generate code for. + /// + private IEdmModel edmModel; + + /// + /// The array of namespaces in the current edm model. + /// + private string[] namespacesInModel; + + /// + /// The array of warnings occured when parsing edm model. + /// + private string[] warnings; + + /// + /// true if the model contains any structural type with inheritance, false otherwise. + /// + private bool? modelHasInheritance; + + /// + /// If the namespacePrefix is not null, this contains the mapping of namespaces in the model to the corresponding prefixed namespaces. + /// Otherwise this is an empty dictionary. + /// + private Dictionary namespaceMap; + + /// + /// Maps the element type of a navigation source to the navigation source. + /// + private Dictionary> elementTypeToNavigationSourceMap; + + /// + /// HashSet contains the pair of Names and Namespaces of EntityContainers using KeyAsSegment url convention + /// + private HashSet keyAsSegmentContainers; + + /// + /// Constructs an instance of . + /// + /// The Uri to the metadata document. The supported scheme are File, http and https. + public CodeGenerationContext(Uri metadataUri, string namespacePrefix) + : this(GetEdmxStringFromMetadataPath(metadataUri), namespacePrefix) + { + } + + /// + /// Constructs an instance of . + /// + /// The string for the edmx. + /// The namespacePrefix is used as the only namespace in generated code + /// when there's only one schema in edm model, and as a prefix for the namespace from the model with multiple + /// schemas. If this argument is null, the namespaces from the model are used for all types. + public CodeGenerationContext(string edmx, string namespacePrefix) + { + this.edmx = XElement.Parse(edmx); + this.namespacePrefix = namespacePrefix; + } + + /// + /// The EdmModel to generate code for. + /// + public XElement Edmx + { + get { return this.edmx; } + } + + /// + /// The EdmModel to generate code for. + /// + public IEdmModel EdmModel + { + get + { + if (this.edmModel == null) + { + Debug.Assert(this.edmx != null, "this.edmx != null"); + + IEnumerable errors; + CsdlReaderSettings edmxReaderSettings = new CsdlReaderSettings() + { + GetReferencedModelReaderFunc = this.GetReferencedModelReaderFuncWrapper, + IgnoreUnexpectedAttributesAndElements = this.IgnoreUnexpectedElementsAndAttributes + }; + if (!CsdlReader.TryParse(this.edmx.CreateReader(ReaderOptions.None), Enumerable.Empty(), edmxReaderSettings, out this.edmModel, out errors)) + { + Debug.Assert(errors != null, "errors != null"); + throw new InvalidOperationException(errors.FirstOrDefault().ErrorMessage); + } + else if (this.IgnoreUnexpectedElementsAndAttributes) + { + if (errors != null && errors.Any()) + { + this.warnings = errors.Select(e => e.ErrorMessage).ToArray(); + } + } + } + + return this.edmModel; + } + } + + /// + /// The func for user code to overwrite and provide referenced model's XmlReader. + /// + public Func GetReferencedModelReaderFunc + { + get { return getReferencedModelReaderFunc; } + set { this.getReferencedModelReaderFunc = value; } + } + + /// + /// Basic setting for XmlReader. + /// + private static readonly XmlReaderSettings settings = new XmlReaderSettings() { IgnoreWhitespace = true }; + + /// + /// The func for user code to overwrite and provide referenced model's XmlReader. + /// + private Func getReferencedModelReaderFunc = uri => XmlReader.Create(GetEdmxStreamFromUri(uri), settings); + + /// + /// The Wrapper func for user code to overwrite and provide referenced model's stream. + /// + public Func GetReferencedModelReaderFuncWrapper + { + get + { + return (uri) => + { + using (XmlReader reader = GetReferencedModelReaderFunc(uri)) + { + if (reader == null) + { + return null; + } + + XElement element = XElement.Load(reader); + if (this.ReferencesMap == null) + { + this.ReferencesMap = new Dictionary(); + } + + this.ReferencesMap.Add(uri, element); + return element.CreateReader(ReaderOptions.None); + } + }; + } + } + + /// + /// Dictionary that stores uri and referenced xml mapping. + /// + public Dictionary ReferencesMap + { + get; + set; + } + + /// + /// The array of namespaces in the current edm model. + /// + public string[] NamespacesInModel + { + get + { + if (this.namespacesInModel == null) + { + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + this.namespacesInModel = GetElementsFromModelTree(this.EdmModel, (m) => m.SchemaElements.Select(e => e.Namespace)).Distinct().ToArray(); + } + + return this.namespacesInModel; + } + } + + /// + /// The array of warnings occured when parsing edm model. + /// + public string[] Warnings + { + get { return this.warnings ?? (this.warnings = new string[] {}); } + } + + /// + /// true if the model contains any structural type with inheritance, false otherwise. + /// + public bool ModelHasInheritance + { + get + { + if (!this.modelHasInheritance.HasValue) + { + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + this.modelHasInheritance = this.EdmModel.SchemaElementsAcrossModels() + .OfType().Any(t => !t.FullTypeName().StartsWith("Org.OData.Authorization.V1") && + !t.FullTypeName().StartsWith("Org.OData.Capabilities.V1") && + !t.FullTypeName().StartsWith("Org.OData.Core.V1") && t.BaseType != null); + } + + return this.modelHasInheritance.Value; + } + } + + /// + /// true if we need to generate the ResolveNameFromType method, false otherwise. + /// + public bool NeedResolveNameFromType + { + get { return this.ModelHasInheritance || this.NamespaceMap.Count > 0 || this.EnableNamingAlias; } + } + + /// + /// true if we need to generate the ResolveTypeFromName method, false otherwise. + /// + public bool NeedResolveTypeFromName + { + get { return this.NamespaceMap.Count > 0 || this.EnableNamingAlias; } + } + + /// + /// If the namespacePrefix is not null, this contains the mapping of namespaces in the model to the corresponding prefixed namespaces. + /// Otherwise this is an empty dictionary. + /// + public Dictionary NamespaceMap + { + get + { + if (this.namespaceMap == null) + { + if (!string.IsNullOrEmpty(this.namespacePrefix)) + { + if (this.NamespacesInModel.Count() == 1) + { + IEdmEntityContainer container = this.EdmModel.EntityContainer; + string containerNamespace = container == null ? null : container.Namespace; + this.namespaceMap = this.NamespacesInModel + .Distinct() + .ToDictionary( + ns => ns, + ns => ns == containerNamespace ? + this.namespacePrefix : + this.namespacePrefix + "." + (this.EnableNamingAlias ? Customization.CustomizeNamespace(ns) : ns)); + } + else + { + this.namespaceMap = this.NamespacesInModel + .Distinct() + .ToDictionary( + ns => ns, + ns => this.namespacePrefix + "." + (this.EnableNamingAlias ? Customization.CustomizeNamespace(ns) : ns)); + } + } + else if (this.EnableNamingAlias) + { + this.namespaceMap = this.NamespacesInModel + .Distinct() + .ToDictionary( + ns => ns, + ns => Customization.CustomizeNamespace(ns)); + } + else + { + this.namespaceMap = new Dictionary(); + } + } + + return this.namespaceMap; + } + } + + /// + /// true to use DataServiceCollection in the generated code, false otherwise. + /// + public bool UseDataServiceCollection + { + get; + set; + } + + /// + /// Specifies which specific .Net Framework language the generated code will target. + /// + public LanguageOption TargetLanguage + { + get; + set; + } + + /// + /// The path for the temporary file where the metadata xml document can be stored. + /// + public string TempFilePath + { + get; + set; + } + /// + /// true to use Upper camel case for all class and property names, false otherwise. + /// + public bool EnableNamingAlias + { + get; + set; + } + + /// + /// true to ignore unknown elements or attributes in metadata, false otherwise. + /// + public bool IgnoreUnexpectedElementsAndAttributes + { + get; + set; + } + + /// + /// Maps the element type of an entity set to the entity set. + /// + public Dictionary> ElementTypeToNavigationSourceMap + { + get + { + return this.elementTypeToNavigationSourceMap ?? (this.elementTypeToNavigationSourceMap = new Dictionary>(EqualityComparer.Default)); + } + } + + /// + /// true if this EntityContainer need to set the UrlConvention to KeyAsSegment, false otherwise. + /// + public bool UseKeyAsSegmentUrlConvention(IEdmEntityContainer currentContainer) + { + if (this.keyAsSegmentContainers == null) + { + this.keyAsSegmentContainers = new HashSet(); + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + IEnumerable annotations = this.EdmModel.VocabularyAnnotations; + foreach(IEdmVocabularyAnnotation valueAnnotation in annotations) + { + IEdmEntityContainer container = valueAnnotation.Target as IEdmEntityContainer; + IEdmTerm valueTerm = valueAnnotation.Term; + IEdmStringConstantExpression expression = valueAnnotation.Value as IEdmStringConstantExpression; + if (container != null && valueTerm != null && expression != null) + { + if (valueTerm.Namespace == ConventionTermNamespace && + valueTerm.Name == ConventionTermName && + expression.Value == KeyAsSegmentConventionName) + { + this.keyAsSegmentContainers.Add(container.FullName()); + } + } + } + } + + return this.keyAsSegmentContainers.Contains(currentContainer.FullName()); + } + + /// + /// Gets the enumeration of schema elements with the given namespace. + /// + /// The namespace of the schema elements to get. + /// The enumeration of schema elements with the given namespace. + public IEnumerable GetSchemaElements(string ns) + { + Debug.Assert(ns != null, "ns != null"); + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + return GetElementsFromModelTree(this.EdmModel, m => m.SchemaElements.Where(e => e.Namespace == ns)); + } + + /// + /// Gets the namespace qualified name for the given with the namespace prefix applied if this.NamespacePrefix is specified. + /// + /// The schema element to get the full name for. + /// The fixed name of this schemaElement. + /// The current code generate template. + /// The namespace qualified name for the given with the namespace prefix applied if this.NamespacePrefix is specified. + public string GetPrefixedFullName(IEdmSchemaElement schemaElement, string schemaElementFixedName, ODataClientTemplate template, bool needGlobalPrefix = true) + { + if (schemaElement == null) + { + return null; + } + + return this.GetPrefixedNamespace(schemaElement.Namespace, template, true, needGlobalPrefix) + "." + schemaElementFixedName; + } + + /// + /// Gets the prefixed namespace for the given . + /// + /// The namespace without the prefix. + /// The current code generate template. + /// The flag indicates whether the namespace need to be fixed now. + /// The flag indicates whether the namespace need to be added by gloabal prefix. + /// The prefixed namespace for the given . + public string GetPrefixedNamespace(string ns, ODataClientTemplate template, bool needFix, bool needGlobalPrefix) + { + if (ns == null) + { + return null; + } + + string prefixedNamespace; + if (!this.NamespaceMap.TryGetValue(ns, out prefixedNamespace)) + { + prefixedNamespace = ns; + } + + if (needFix) + { + string[] segments = prefixedNamespace.Split('.'); + prefixedNamespace = string.Empty; + int n = segments.Length; + for (int i = 0; i < n; ++i) + { + if (template.LanguageKeywords.Contains(segments[i])) + { + prefixedNamespace += string.Format(template.FixPattern, segments[i]); + } + else + { + prefixedNamespace += segments[i]; + } + + prefixedNamespace += (i == n - 1 ? string.Empty : "."); + } + } + + if (needGlobalPrefix) + { + prefixedNamespace = template.GlobalPrefix + prefixedNamespace; + } + + return prefixedNamespace; + } + + /// + /// Reads the edmx string from a file path or a http/https path. + /// + /// The Uri to the metadata document. The supported scheme are File, http and https. + private static string GetEdmxStringFromMetadataPath(Uri metadataUri) + { + string content = null; + using (StreamReader streamReader = new StreamReader(GetEdmxStreamFromUri(metadataUri))) + { + content = streamReader.ReadToEnd(); + } + + return content; + } + + /// + /// Get the metadata stream from a file path or a http/https path. + /// + /// The Uri to the stream. The supported scheme are File, http and https. + private static Stream GetEdmxStreamFromUri(Uri metadataUri) + { + Debug.Assert(metadataUri != null, "metadataUri != null"); + Stream metadataStream = null; + if (metadataUri.Scheme == "file") + { + metadataStream = new FileStream(Uri.UnescapeDataString(metadataUri.AbsolutePath), FileMode.Open, FileAccess.Read); + } + else if (metadataUri.Scheme == "http" || metadataUri.Scheme == "https") + { + try + { + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(metadataUri); + WebResponse webResponse = webRequest.GetResponse(); + metadataStream = webResponse.GetResponseStream(); + } + catch (WebException e) + { + HttpWebResponse webResponse = e.Response as HttpWebResponse; + if (webResponse != null && webResponse.StatusCode == HttpStatusCode.Unauthorized) + { + throw new WebException("Failed to access the metadata document. The OData service requires authentication for accessing it. Please download the metadata, store it into a local file, and set the value of “MetadataDocumentUri” in the .odata.config file to the file path. After that, run custom tool again to generate the OData Client code."); + } + else + { + throw e; + } + } + } + else + { + throw new ArgumentException("Only file, http, https schemes are supported for paths to metadata source locations."); + } + + return metadataStream; + } + + private static IEnumerable GetElementsFromModelTree(IEdmModel mainModel, Func> getElementFromOneModelFunc) + { + List ret = new List(); + if(mainModel is EdmCoreModel || mainModel.FindDeclaredTerm(CoreVocabularyConstants.OptimisticConcurrency) != null) + { + return ret; + } + + ret.AddRange(getElementFromOneModelFunc(mainModel)); + foreach (var tmp in mainModel.ReferencedModels) + { + if (tmp is EdmCoreModel || + tmp.FindDeclaredTerm(CoreVocabularyConstants.OptimisticConcurrency) != null || + tmp.FindDeclaredTerm(CapabilitiesVocabularyConstants.ChangeTracking) != null || + tmp.FindDeclaredTerm(AlternateKeysVocabularyConstants.AlternateKeys) != null || + tmp.FindDeclaredTerm("Org.OData.Authorization.V1.Authorizations") != null || + tmp.FindDeclaredTerm("Org.OData.Validation.V1.DerivedTypeConstraint") != null || + tmp.FindDeclaredTerm("Org.OData.Community.V1.UrlEscapeFunction") != null) + { + continue; + } + + ret.AddRange(getElementFromOneModelFunc(tmp)); + } + + return ret; + } +} + +/// +/// The template class to generate the OData client code. +/// +public abstract class ODataClientTemplate : TemplateBase +{ + protected const string T4Version = "#VersionNumber#"; + + /// + /// The code generation context. + /// + protected readonly CodeGenerationContext context; + + /// + /// The Dictionary to store identifier mappings when there are duplicate names between properties and Entity/Complex types + /// + protected Dictionary IdentifierMappings = new Dictionary(StringComparer.Ordinal); + + /// + /// Creates an instance of the ODataClientTemplate. + /// + /// The code generation context. + public ODataClientTemplate(CodeGenerationContext context) + { + this.context = context; + } + + internal string SingleSuffix + { + get { return "Single"; } + } + + #region Get Language specific keyword names. + internal abstract string GlobalPrefix { get; } + internal abstract string SystemTypeTypeName { get; } + internal abstract string AbstractModifier { get; } + internal abstract string DataServiceActionQueryTypeName { get; } + internal abstract string DataServiceActionQuerySingleOfTStructureTemplate { get; } + internal abstract string DataServiceActionQueryOfTStructureTemplate { get; } + internal abstract string NotifyPropertyChangedModifier { get; } + internal abstract string ClassInheritMarker { get; } + internal abstract string ParameterSeparator { get; } + internal abstract string KeyParameterSeparator { get; } + internal abstract string KeyDictionaryItemSeparator { get; } + internal abstract string SystemNullableStructureTemplate { get; } + internal abstract string ICollectionOfTStructureTemplate { get; } + internal abstract string DataServiceCollectionStructureTemplate { get; } + internal abstract string DataServiceQueryStructureTemplate { get; } + internal abstract string DataServiceQuerySingleStructureTemplate { get; } + internal abstract string ObservableCollectionStructureTemplate { get; } + internal abstract string ObjectModelCollectionStructureTemplate { get; } + internal abstract string DataServiceCollectionConstructorParameters { get; } + internal abstract string NewModifier { get; } + internal abstract string GeoTypeInitializePattern { get; } + internal abstract string Int32TypeName { get; } + internal abstract string StringTypeName { get; } + internal abstract string BinaryTypeName { get; } + internal abstract string DecimalTypeName { get; } + internal abstract string Int16TypeName { get; } + internal abstract string SingleTypeName { get; } + internal abstract string BooleanTypeName { get; } + internal abstract string DoubleTypeName { get; } + internal abstract string GuidTypeName { get; } + internal abstract string ByteTypeName { get; } + internal abstract string Int64TypeName { get; } + internal abstract string SByteTypeName { get; } + internal abstract string DataServiceStreamLinkTypeName { get; } + internal abstract string GeographyTypeName { get; } + internal abstract string GeographyPointTypeName { get; } + internal abstract string GeographyLineStringTypeName { get; } + internal abstract string GeographyPolygonTypeName { get; } + internal abstract string GeographyCollectionTypeName { get; } + internal abstract string GeographyMultiPolygonTypeName { get; } + internal abstract string GeographyMultiLineStringTypeName { get; } + internal abstract string GeographyMultiPointTypeName { get; } + internal abstract string GeometryTypeName { get; } + internal abstract string GeometryPointTypeName { get; } + internal abstract string GeometryLineStringTypeName { get; } + internal abstract string GeometryPolygonTypeName { get; } + internal abstract string GeometryCollectionTypeName { get; } + internal abstract string GeometryMultiPolygonTypeName { get; } + internal abstract string GeometryMultiLineStringTypeName { get; } + internal abstract string GeometryMultiPointTypeName { get; } + internal abstract string DateTypeName { get; } + internal abstract string DateTimeOffsetTypeName { get; } + internal abstract string DurationTypeName { get; } + internal abstract string TimeOfDayTypeName { get; } + internal abstract string XmlConvertClassName { get; } + internal abstract string EnumTypeName { get; } + internal abstract HashSet LanguageKeywords { get; } + internal abstract string FixPattern { get; } + internal abstract string EnumUnderlyingTypeMarker { get; } + internal abstract string ConstantExpressionConstructorWithType { get; } + internal abstract string TypeofFormatter { get; } + internal abstract string UriOperationParameterConstructor { get; } + internal abstract string UriEntityOperationParameterConstructor { get; } + internal abstract string BodyOperationParameterConstructor { get; } + internal abstract string BaseEntityType { get; } + internal abstract string OverloadsModifier { get; } + internal abstract string ODataVersion { get; } + internal abstract string ParameterDeclarationTemplate { get; } + internal abstract string DictionaryItemConstructor { get; } + #endregion Get Language specific keyword names. + + #region Language specific write methods. + internal abstract void WriteFileHeader(); + internal abstract void WriteNamespaceStart(string fullNamespace); + internal abstract void WriteClassStartForEntityContainer(string originalContainerName, string containerName, string fixedContainerName); + internal abstract void WriteMethodStartForEntityContainerConstructor(string containerName, string fixedContainerName); + internal abstract void WriteKeyAsSegmentUrlConvention(); + internal abstract void WriteInitializeResolveName(); + internal abstract void WriteInitializeResolveType(); + internal abstract void WriteClassEndForEntityContainerConstructor(); + internal abstract void WriteMethodStartForResolveTypeFromName(); + internal abstract void WriteResolveNamespace(string typeName, string fullNamespace, string languageDependentNamespace); + internal abstract void WriteMethodEndForResolveTypeFromName(); + internal abstract void WriteMethodStartForResolveNameFromType(string containerName, string fullNamespace); + internal abstract void WriteResolveType(string fullNamespace, string languageDependentNamespace); + internal abstract void WriteMethodEndForResolveNameFromType(bool modelHasInheritance); + internal abstract void WriteContextEntitySetProperty(string entitySetName, string entitySetFixedName, string originalEntitySetName, string entitySetElementTypeName, bool inContext = true); + internal abstract void WriteContextSingletonProperty(string singletonName, string singletonFixedName, string originalSingletonName, string singletonElementTypeName, bool inContext = true); + internal abstract void WriteContextAddToEntitySetMethod(string entitySetName, string originalEntitySetName, string typeName, string parameterName); + internal abstract void WriteGeneratedEdmModel(string escapedEdmxString); + internal abstract void WriteClassEndForEntityContainer(); + internal abstract void WriteSummaryCommentForStructuredType(string typeName); + internal abstract void WriteKeyPropertiesCommentAndAttribute(IEnumerable keyProperties, string keyString); + internal abstract void WriteEntityTypeAttribute(); + internal abstract void WriteEntitySetAttribute(string entitySetName); + internal abstract void WriteEntityHasStreamAttribute(); + internal abstract void WriteClassStartForStructuredType(string abstractModifier, string typeName, string originalTypeName, string baseTypeName); + internal abstract void WriteSummaryCommentForStaticCreateMethod(string typeName); + internal abstract void WriteParameterCommentForStaticCreateMethod(string parameterName, string propertyName); + internal abstract void WriteDeclarationStartForStaticCreateMethod(string typeName,string fixedTypeName ); + internal abstract void WriteParameterForStaticCreateMethod(string parameterTypeName, string parameterName, string parameterSeparater); + internal abstract void WriteDeclarationEndForStaticCreateMethod(string typeName, string instanceName); + internal abstract void WriteParameterNullCheckForStaticCreateMethod(string parameterName); + internal abstract void WritePropertyValueAssignmentForStaticCreateMethod(string instanceName, string propertyName, string parameterName); + internal abstract void WriteMethodEndForStaticCreateMethod(string instanceName); + internal abstract void WritePropertyForStructuredType(string propertyType, string originalPropertyName, string propertyName, string fixedPropertyName, string privatePropertyName, string propertyInitializationValue, bool writeOnPropertyChanged); + internal abstract void WriteINotifyPropertyChangedImplementation(); + internal abstract void WriteClassEndForStructuredType(); + internal abstract void WriteNamespaceEnd(); + internal abstract void WriteEnumFlags(); + internal abstract void WriteSummaryCommentForEnumType(string enumName); + internal abstract void WriteEnumDeclaration(string enumName, string originalEnumName, string underlyingType); + internal abstract void WriteMemberForEnumType(string member, string originalMemberName, bool last); + internal abstract void WriteEnumEnd(); + internal abstract void WritePropertyRootNamespace(string containerName, string fullNamespace); + internal abstract void WriteFunctionImportReturnCollectionResult(string functionName, string originalFunctionName, string returnTypeName, string parameters, string parameterValues, bool isComposable, bool useEntityReference); + internal abstract void WriteFunctionImportReturnSingleResult(string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference); + internal abstract void WriteBoundFunctionInEntityTypeReturnCollectionResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference); + internal abstract void WriteBoundFunctionInEntityTypeReturnSingleResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference); + internal abstract void WriteActionImport(string actionName, string originalActionName, string returnTypeName, string parameters, string parameterValues); + internal abstract void WriteBoundActionInEntityType(bool hideBaseMethod, string actionName, string originalActionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues); + internal abstract void WriteConstructorForSingleType(string singleTypeName, string baseTypeName); + internal abstract void WriteExtensionMethodsStart(); + internal abstract void WriteExtensionMethodsEnd(); + internal abstract void WriteByKeyMethods(string entityTypeName, string returnTypeName, IEnumerable keys, string keyParameters, string keyDictionaryItems); + internal abstract void WriteCastToMethods(string baseTypeName, string derivedTypeName, string derivedTypeFullName, string returnTypeName); + internal abstract void WriteBoundFunctionReturnSingleResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference); + internal abstract void WriteBoundFunctionReturnCollectionResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference); + internal abstract void WriteBoundActionAsExtension(string actionName, string originalActionName, string boundSourceType, string returnTypeName, string parameters, string fullNamespace, string parameterValues); + #endregion Language specific write methods. + + internal HashSet ClrReferenceTypes { get { + if (clrReferenceTypes == null) + { + clrReferenceTypes = new HashSet() + { + EdmPrimitiveTypeKind.String, EdmPrimitiveTypeKind.Binary, EdmPrimitiveTypeKind.Geography, EdmPrimitiveTypeKind.Stream, + EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeographyLineString, EdmPrimitiveTypeKind.GeographyPolygon, + EdmPrimitiveTypeKind.GeographyCollection, EdmPrimitiveTypeKind.GeographyMultiPolygon, EdmPrimitiveTypeKind.GeographyMultiLineString, + EdmPrimitiveTypeKind.GeographyMultiPoint, EdmPrimitiveTypeKind.Geometry, EdmPrimitiveTypeKind.GeometryPoint, + EdmPrimitiveTypeKind.GeometryLineString, EdmPrimitiveTypeKind.GeometryPolygon, EdmPrimitiveTypeKind.GeometryCollection, + EdmPrimitiveTypeKind.GeometryMultiPolygon, EdmPrimitiveTypeKind.GeometryMultiLineString, EdmPrimitiveTypeKind.GeometryMultiPoint + }; + } + return clrReferenceTypes; + } } + private HashSet clrReferenceTypes; + + /// + /// Generates code for the OData client. + /// + /// The generated code for the OData client. + public override string TransformText() + { + this.WriteFileHeader(); + this.WriteNamespaces(); + return this.GenerationEnvironment.ToString(); + } + + internal void WriteNamespaces() + { + foreach(string fullNamespace in context.NamespacesInModel) + { + this.WriteNamespace(fullNamespace); + } + } + + internal void WriteNamespace(string fullNamespace) + { + this.WriteNamespaceStart(this.context.GetPrefixedNamespace(fullNamespace, this, true, false)); + + IEdmSchemaElement[] schemaElements = this.context.GetSchemaElements(fullNamespace).ToArray(); + if (schemaElements.OfType().Any()) { + IEdmEntityContainer container = schemaElements.OfType().Single(); + this.WriteEntityContainer(container, fullNamespace); + } + + Dictionary> boundOperationsMap = new Dictionary>(); + foreach (IEdmOperation operation in schemaElements.OfType()) + { + if (operation.IsBound) + { + IEdmType edmType = operation.Parameters.First().Type.Definition; + IEdmStructuredType edmStructuredType = edmType as IEdmStructuredType; + if (edmStructuredType != null) + { + List operationList; + if (!boundOperationsMap.TryGetValue(edmStructuredType, out operationList)) + { + operationList = new List(); + } + + operationList.Add(operation); + boundOperationsMap[edmStructuredType] = operationList; + } + } + } + + Dictionary> structuredBaseTypeMap = new Dictionary>(); + foreach(IEdmSchemaType type in schemaElements.OfType()) + { + IEdmEnumType enumType = type as IEdmEnumType; + if (enumType != null) + { + this.WriteEnumType(enumType); + } + else + { + IEdmComplexType complexType = type as IEdmComplexType; + if (complexType != null) + { + this.WriteComplexType(complexType, boundOperationsMap); + } + else + { + IEdmEntityType entityType = type as IEdmEntityType; + this.WriteEntityType(entityType, boundOperationsMap); + } + + IEdmStructuredType structuredType = type as IEdmStructuredType; + if (structuredType.BaseType != null) + { + List derivedTypes; + if (!structuredBaseTypeMap.TryGetValue(structuredType.BaseType, out derivedTypes)) + { + structuredBaseTypeMap[structuredType.BaseType] = new List(); + } + + structuredBaseTypeMap[structuredType.BaseType].Add(structuredType); + } + } + } + + if (schemaElements.OfType().Any() || + schemaElements.OfType().Any(o => o.IsBound)) + { + this.WriteExtensionMethodsStart(); + foreach (IEdmEntityType type in schemaElements.OfType()) + { + string entityTypeName = type.Name; + entityTypeName = context.EnableNamingAlias ? Customization.CustomizeNaming(entityTypeName) : entityTypeName; + string entityTypeFullName = context.GetPrefixedFullName(type, GetFixedName(entityTypeName), this); + string returnTypeName = context.GetPrefixedFullName(type, GetFixedName(entityTypeName + this.SingleSuffix), this); + + var keyProperties = type.Key(); + if(keyProperties != null && keyProperties.Any()) + { + List keyParameters = new List(); + List keyDictionaryItems = new List(); + List keyNames = new List(); + foreach (IEdmProperty key in keyProperties) + { + string typeName = Utils.GetClrTypeName(key.Type, this.context.UseDataServiceCollection, this, this.context); + string keyName = Utils.CamelCase(key.Name); + keyNames.Add(keyName); + keyParameters.Add(string.Format(this.ParameterDeclarationTemplate, typeName, this.GetFixedName(keyName))); + keyDictionaryItems.Add(string.Format(this.DictionaryItemConstructor, "\"" + key.Name + "\"", this.GetFixedName(keyName))); + } + + string keyParametersString = string.Join(this.KeyParameterSeparator, keyParameters); + string keyDictionaryItemsString = string.Join(this.KeyDictionaryItemSeparator, keyDictionaryItems); + this.WriteByKeyMethods(entityTypeFullName, returnTypeName, keyNames, keyParametersString, keyDictionaryItemsString); + } + + IEdmEntityType current = (IEdmEntityType)type.BaseType; + while (current != null) + { + string baseTypeName = current.Name; + baseTypeName = context.EnableNamingAlias ? Customization.CustomizeNaming(baseTypeName) : baseTypeName; + baseTypeName = context.GetPrefixedFullName(current, GetFixedName(baseTypeName), this); + this.WriteCastToMethods(baseTypeName, entityTypeName, entityTypeFullName, returnTypeName); + current = (IEdmEntityType)current.BaseType; + } + } + + HashSet boundOperations = new HashSet(StringComparer.Ordinal); + foreach (IEdmFunction function in schemaElements.OfType()) + { + if (function.IsBound) + { + IEdmTypeReference edmTypeReference = function.Parameters.First().Type; + string functionName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(function.Name) : function.Name; + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + this.GetParameterStrings(function.IsBound, false, function.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string sourceTypeName = GetSourceOrReturnTypeName(edmTypeReference); + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, sourceTypeName); + string returnTypeName = GetSourceOrReturnTypeName(function.ReturnType); + string returnTypeNameWithSingleSuffix = GetSourceOrReturnTypeName(function.ReturnType, true); + string fixedFunctionName = GetFixedName(functionName); + string func = string.Format("{0}({1},{2})", fixedFunctionName, sourceTypeName, parameterTypes ); + + if (!boundOperations.Contains(func)) + { + boundOperations.Add(func); + + if (function.ReturnType.IsCollection()) + { + this.WriteBoundFunctionReturnCollectionResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, parameterString, function.Namespace, parameterValues, function.IsComposable, useEntityReference); + } + else + { + this.WriteBoundFunctionReturnSingleResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, function.Namespace, parameterValues, function.IsComposable, function.ReturnType.IsEntity(), useEntityReference); + } + } + + IEdmStructuredType structuredType; + if (edmTypeReference.IsCollection()) + { + IEdmCollectionType collectionType = edmTypeReference.Definition as IEdmCollectionType; + structuredType = (IEdmStructuredType)collectionType.ElementType.Definition; + } + else + { + structuredType = (IEdmStructuredType)edmTypeReference.Definition; + } + + List derivedTypes; + if (structuredBaseTypeMap.TryGetValue(structuredType, out derivedTypes)) + { + foreach (IEdmStructuredType type in derivedTypes) + { + IEdmTypeReference derivedTypeReference = new EdmEntityTypeReference((IEdmEntityType)type, true); + List currentParameters = function.Parameters.Select(p => p.Type).ToList(); + currentParameters[0] = derivedTypeReference; + + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, GetSourceOrReturnTypeName(derivedTypeReference)); + string currentFunc = string.Format("{0}({1},{2})", fixedFunctionName, sourceTypeName, parameterTypes ); + if (!boundOperations.Contains(currentFunc)) + { + boundOperations.Add(currentFunc); + + if (function.ReturnType.IsCollection()) + { + this.WriteBoundFunctionReturnCollectionResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, parameterString, function.Namespace, parameterValues, function.IsComposable, useEntityReference); + } + else + { + this.WriteBoundFunctionReturnSingleResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, function.Namespace, parameterValues, function.IsComposable, function.ReturnType.IsEntity(), useEntityReference); + } + } + } + } + } + } + + foreach (IEdmAction action in schemaElements.OfType()) + { + if (action.IsBound) + { + IEdmTypeReference edmTypeReference = action.Parameters.First().Type; + string actionName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(action.Name) : action.Name; + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + this.GetParameterStrings(action.IsBound, true, action.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string sourceTypeName = GetSourceOrReturnTypeName(edmTypeReference); + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, sourceTypeName); + string returnTypeName; + if (action.ReturnType != null) + { + returnTypeName = GetSourceOrReturnTypeName(action.ReturnType); + if (action.ReturnType.IsCollection()) + { + returnTypeName = string.Format(this.DataServiceActionQueryOfTStructureTemplate, returnTypeName); + } + else + { + returnTypeName = string.Format(this.DataServiceActionQuerySingleOfTStructureTemplate, returnTypeName); + } + } + else + { + returnTypeName = this.DataServiceActionQueryTypeName; + } + + string fixedActionName = GetFixedName(actionName); + string ac = string.Format("{0}({1},{2})", fixedActionName, sourceTypeName, parameterTypes ); + if (!boundOperations.Contains(ac)) + { + boundOperations.Add(ac); + this.WriteBoundActionAsExtension(fixedActionName, action.Name, sourceTypeName, returnTypeName, parameterString, action.Namespace, parameterValues); + } + + IEdmStructuredType structuredType; + if (edmTypeReference.IsCollection()) + { + IEdmCollectionType collectionType = edmTypeReference.Definition as IEdmCollectionType; + structuredType = (IEdmStructuredType)collectionType.ElementType.Definition; + } + else + { + structuredType = (IEdmStructuredType)edmTypeReference.Definition; + } + + List derivedTypes; + if (structuredBaseTypeMap.TryGetValue(structuredType, out derivedTypes)) + { + foreach (IEdmStructuredType type in derivedTypes) + { + IEdmTypeReference derivedTypeReference = new EdmEntityTypeReference((IEdmEntityType)type, true); + List currentParameters = action.Parameters.Select(p => p.Type).ToList(); + currentParameters[0] = derivedTypeReference; + + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, GetSourceOrReturnTypeName(derivedTypeReference)); + string currentAc = string.Format("{0}({1},{2})", fixedActionName, sourceTypeName, parameterTypes ); + if (!boundOperations.Contains(currentAc)) + { + boundOperations.Add(currentAc); + this.WriteBoundActionAsExtension(fixedActionName, action.Name, sourceTypeName, returnTypeName, parameterString, action.Namespace, parameterValues); + } + } + } + } + } + + this.WriteExtensionMethodsEnd(); + } + + this.WriteNamespaceEnd(); + } + + internal bool HasBoundOperations(IEnumerable operations) + { + foreach (IEdmOperation opeartion in operations) + { + if (opeartion.IsBound) + { + return true; + } + } + + return false; + } + + internal void WriteEntityContainer(IEdmEntityContainer container, string fullNamespace) + { + string camelCaseContainerName = container.Name; + if (this.context.EnableNamingAlias) + { + camelCaseContainerName = Customization.CustomizeNaming(camelCaseContainerName); + } + + this.WriteClassStartForEntityContainer(container.Name, camelCaseContainerName, GetFixedName(camelCaseContainerName)); + this.WriteEntityContainerConstructor(container); + + if (this.context.NeedResolveNameFromType) + { + this.WritePropertyRootNamespace(GetFixedName(camelCaseContainerName), this.context.GetPrefixedNamespace(fullNamespace, this, false, false)); + } + + this.WriteResolveTypeFromName(); + this.WriteResolveNameFromType(camelCaseContainerName, fullNamespace); + + foreach (IEdmEntitySet entitySet in container.EntitySets()) + { + IEdmEntityType entitySetElementType = entitySet.EntityType(); + string entitySetElementTypeName = GetElementTypeName(entitySetElementType, container); + + string camelCaseEntitySetName = entitySet.Name; + if (this.context.EnableNamingAlias) + { + camelCaseEntitySetName = Customization.CustomizeNaming(camelCaseEntitySetName); + } + + this.WriteContextEntitySetProperty(camelCaseEntitySetName, GetFixedName(camelCaseEntitySetName), entitySet.Name, GetFixedName(entitySetElementTypeName)); + List edmNavigationSourceList = null; + if (!this.context.ElementTypeToNavigationSourceMap.TryGetValue(entitySet.EntityType(), out edmNavigationSourceList)) + { + edmNavigationSourceList = new List(); + this.context.ElementTypeToNavigationSourceMap.Add(entitySet.EntityType(), edmNavigationSourceList); + } + + edmNavigationSourceList.Add(entitySet); + } + + foreach (IEdmEntitySet entitySet in container.EntitySets()) + { + IEdmEntityType entitySetElementType = entitySet.EntityType(); + + string entitySetElementTypeName = GetElementTypeName(entitySetElementType, container); + + UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService(/*IsLanguageCaseSensitive*/true); + string parameterName = GetFixedName(uniqueIdentifierService.GetUniqueParameterName(entitySetElementType.Name)); + + string camelCaseEntitySetName = entitySet.Name; + if (this.context.EnableNamingAlias) + { + camelCaseEntitySetName = Customization.CustomizeNaming(camelCaseEntitySetName); + } + + this.WriteContextAddToEntitySetMethod(camelCaseEntitySetName, entitySet.Name, GetFixedName(entitySetElementTypeName), parameterName); + } + + foreach (IEdmSingleton singleton in container.Singletons()) + { + IEdmEntityType singletonElementType = singleton.EntityType(); + string singletonElementTypeName = GetElementTypeName(singletonElementType, container); + string camelCaseSingletonName = singleton.Name; + if (this.context.EnableNamingAlias) + { + camelCaseSingletonName = Customization.CustomizeNaming(camelCaseSingletonName); + } + + this.WriteContextSingletonProperty(camelCaseSingletonName, GetFixedName(camelCaseSingletonName), singleton.Name, singletonElementTypeName + "Single"); + + List edmNavigationSourceList = null; + if (this.context.ElementTypeToNavigationSourceMap.TryGetValue(singleton.EntityType(), out edmNavigationSourceList)) + { + edmNavigationSourceList.Add(singleton); + } + } + + this.WriteGeneratedEdmModel(Utils.SerializeToString(this.context.Edmx).Replace("\"", "\"\"")); + + bool hasOperationImport = container.OperationImports().OfType().Any(); + foreach (IEdmFunctionImport functionImport in container.OperationImports().OfType()) + { + string parameterString, parameterTypes, parameterExpressionString, parameterValues; + bool useEntityReference; + this.GetParameterStrings(false, false, functionImport.Function.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName = GetSourceOrReturnTypeName(functionImport.Function.ReturnType); + string returnTypeNameWithSingleSuffix = GetSourceOrReturnTypeName(functionImport.Function.ReturnType, true); + string fixedContainerName = this.GetFixedName(functionImport.Container.Name); + bool isCollectionResult = functionImport.Function.ReturnType.IsCollection(); + string functionImportName = functionImport.Name; + if (this.context.EnableNamingAlias) + { + functionImportName = Customization.CustomizeNaming(functionImportName); + fixedContainerName = Customization.CustomizeNaming(fixedContainerName); + } + + if (functionImport.Function.ReturnType.IsCollection()) + { + this.WriteFunctionImportReturnCollectionResult(this.GetFixedName(functionImportName), functionImport.Name, returnTypeName, parameterString, parameterValues, functionImport.Function.IsComposable, useEntityReference); + } + else + { + this.WriteFunctionImportReturnSingleResult(this.GetFixedName(functionImportName), functionImport.Name, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, parameterValues, functionImport.Function.IsComposable, functionImport.Function.ReturnType.IsEntity(), useEntityReference); + } + } + + foreach (IEdmActionImport actionImport in container.OperationImports().OfType()) + { + string parameterString, parameterTypes, parameterExpressionString, parameterValues; + bool useEntityReference; + this.GetParameterStrings(false, true, actionImport.Action.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName = null; + string fixedContainerName = this.GetFixedName(actionImport.Container.Name); + + if (actionImport.Action.ReturnType != null) + { + returnTypeName = GetSourceOrReturnTypeName(actionImport.Action.ReturnType); + if (actionImport.Action.ReturnType.IsCollection()) + { + returnTypeName = string.Format(this.DataServiceActionQueryOfTStructureTemplate, returnTypeName); + } + else + { + returnTypeName = string.Format(this.DataServiceActionQuerySingleOfTStructureTemplate, returnTypeName); + } + } + else + { + returnTypeName = this.DataServiceActionQueryTypeName; + } + + string actionImportName = actionImport.Name; + if (this.context.EnableNamingAlias) + { + actionImportName = Customization.CustomizeNaming(actionImportName); + fixedContainerName = Customization.CustomizeNaming(fixedContainerName); + } + + this.WriteActionImport(this.GetFixedName(actionImportName), actionImport.Name, returnTypeName, parameterString, parameterValues); + } + + this.WriteClassEndForEntityContainer(); + } + + internal void WriteEntityContainerConstructor(IEdmEntityContainer container) + { + string camelCaseContainerName = container.Name; + if (this.context.EnableNamingAlias) + { + camelCaseContainerName = Customization.CustomizeNaming(camelCaseContainerName); + } + + this.WriteMethodStartForEntityContainerConstructor(camelCaseContainerName, GetFixedName(camelCaseContainerName)); + + if (this.context.UseKeyAsSegmentUrlConvention(container)) + { + this.WriteKeyAsSegmentUrlConvention(); + } + + if (this.context.NeedResolveNameFromType) + { + this.WriteInitializeResolveName(); + } + + if (this.context.NeedResolveTypeFromName) + { + this.WriteInitializeResolveType(); + } + + this.WriteClassEndForEntityContainerConstructor(); + } + + internal void WriteResolveTypeFromName() + { + if (!this.context.NeedResolveTypeFromName) + { + return; + } + + this.WriteMethodStartForResolveTypeFromName(); + + // NOTE: since multiple namespaces can have the same prefix and match the namespace + // prefix condition, it's important that the prefix check is done is prefix-length + // order, starting with the longest prefix. + IEnumerable> namespaceToPrefixedNamespacePairs = this.context.NamespaceMap.OrderByDescending(p => p.Key.Length).ThenBy(p => p.Key); + + string typeName = this.SystemTypeTypeName + " "; + foreach(KeyValuePair namespaceToPrefixedNamespacePair in namespaceToPrefixedNamespacePairs) + { + this.WriteResolveNamespace(typeName, namespaceToPrefixedNamespacePair.Key, namespaceToPrefixedNamespacePair.Value); + typeName = string.Empty; + } + + this.WriteMethodEndForResolveTypeFromName(); + } + + internal void WriteResolveNameFromType(string containerName, string fullNamespace) + { + if (!this.context.NeedResolveNameFromType) + { + return; + } + + this.WriteMethodStartForResolveNameFromType(GetFixedName(containerName), fullNamespace); + + // NOTE: in this case order also matters, but the length of the CLR + // namespace is what needs to be considered. + IEnumerable> namespaceToPrefixedNamespacePairs = this.context.NamespaceMap.OrderByDescending(p => p.Value.Length).ThenBy(p => p.Key); + + foreach(KeyValuePair namespaceToPrefixedNamespacePair in namespaceToPrefixedNamespacePairs) + { + this.WriteResolveType(namespaceToPrefixedNamespacePair.Key, namespaceToPrefixedNamespacePair.Value); + } + + this.WriteMethodEndForResolveNameFromType(this.context.ModelHasInheritance); + } + + internal void WritePropertiesForSingleType(IEnumerable properties) + { + foreach (IEdmProperty property in properties.Where(i => i.PropertyKind == EdmPropertyKind.Navigation)) + { + string propertyType; + string propertyName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(property.Name) : property.Name; + if (property.Type is Microsoft.OData.Edm.EdmCollectionTypeReference) + { + propertyType = GetSourceOrReturnTypeName(property.Type); + WriteContextEntitySetProperty(propertyName, GetFixedName(propertyName), property.Name, propertyType, false); + } + else + { + propertyType = Utils.GetClrTypeName(property.Type, true, this, this.context, true, isEntitySingleType : true); + WriteContextSingletonProperty(propertyName, GetFixedName(propertyName), property.Name, propertyType, false); + } + } + } + + internal void WriteEntityType(IEdmEntityType entityType, Dictionary> boundOperationsMap) + { + string entityTypeName = ((IEdmSchemaElement)entityType).Name; + entityTypeName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(entityTypeName) : entityTypeName; + this.WriteSummaryCommentForStructuredType(entityTypeName + this.SingleSuffix); + this.WriteStructurdTypeDeclaration(entityType, + this.ClassInheritMarker + string.Format(this.DataServiceQuerySingleStructureTemplate, GetFixedName(entityTypeName)), + this.SingleSuffix); + string singleTypeName = (this.context.EnableNamingAlias ? + Customization.CustomizeNaming(((IEdmSchemaElement)entityType).Name) : ((IEdmSchemaElement)entityType).Name) + this.SingleSuffix; + this.WriteConstructorForSingleType(GetFixedName(singleTypeName), string.Format(this.DataServiceQuerySingleStructureTemplate, GetFixedName(entityTypeName))); + IEdmEntityType current = entityType; + while (current != null) + { + this.WritePropertiesForSingleType(current.DeclaredProperties); + current = (IEdmEntityType)current.BaseType; + } + + this.WriteClassEndForStructuredType(); + + this.WriteSummaryCommentForStructuredType(this.context.EnableNamingAlias ? Customization.CustomizeNaming(entityType.Name) : entityType.Name); + + if (entityType.Key().Any()) + { + IEnumerable keyProperties = entityType.Key().Select(k => k.Name); + this.WriteKeyPropertiesCommentAndAttribute( + this.context.EnableNamingAlias ? keyProperties.Select(k => Customization.CustomizeNaming(k)) : keyProperties, + string.Join("\", \"", keyProperties)); + } + else + { + this.WriteEntityTypeAttribute(); + } + + if (this.context.UseDataServiceCollection) + { + List navigationSourceList; + if (this.context.ElementTypeToNavigationSourceMap.TryGetValue(entityType, out navigationSourceList)) + { + if(navigationSourceList.Count == 1) + { + this.WriteEntitySetAttribute(navigationSourceList[0].Name); + } + } + } + + if (entityType.HasStream) + { + this.WriteEntityHasStreamAttribute(); + } + + this.WriteStructurdTypeDeclaration(entityType, this.BaseEntityType); + this.SetPropertyIdentifierMappingsIfNameConflicts(entityType.Name, entityType); + this.WriteTypeStaticCreateMethod(entityType.Name, entityType); + this.WritePropertiesForStructuredType(entityType.DeclaredProperties); + + if (entityType.BaseType == null && this.context.UseDataServiceCollection) + { + this.WriteINotifyPropertyChangedImplementation(); + } + + this.WriteBoundOperations(entityType, boundOperationsMap); + + this.WriteClassEndForStructuredType(); + } + + internal void WriteComplexType(IEdmComplexType complexType, Dictionary> boundOperationsMap) + { + this.WriteSummaryCommentForStructuredType(this.context.EnableNamingAlias ? Customization.CustomizeNaming(complexType.Name) : complexType.Name); + this.WriteStructurdTypeDeclaration(complexType, string.Empty); + this.SetPropertyIdentifierMappingsIfNameConflicts(complexType.Name, complexType); + this.WriteTypeStaticCreateMethod(complexType.Name, complexType); + this.WritePropertiesForStructuredType(complexType.DeclaredProperties); + + if (complexType.BaseType == null && this.context.UseDataServiceCollection) + { + this.WriteINotifyPropertyChangedImplementation(); + } + + this.WriteClassEndForStructuredType(); + } + + internal void WriteBoundOperations(IEdmStructuredType structuredType, Dictionary> boundOperationsMap) + { + List operations; + if (boundOperationsMap.TryGetValue(structuredType, out operations)) + { + foreach (IEdmFunction function in operations.OfType()) + { + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + bool hideBaseMethod = this.CheckMethodsInBaseClass(structuredType.BaseType, function, boundOperationsMap); + this.GetParameterStrings(function.IsBound, false, function.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName = GetSourceOrReturnTypeName(function.ReturnType); + string returnTypeNameWithSingleSuffix = GetSourceOrReturnTypeName(function.ReturnType, true); + string functionName = function.Name; + if (this.context.EnableNamingAlias) + { + functionName = Customization.CustomizeNaming(functionName); + } + + if (function.ReturnType.IsCollection()) + { + this.WriteBoundFunctionInEntityTypeReturnCollectionResult(hideBaseMethod, GetFixedName(functionName), function.Name, returnTypeName, parameterString, function.Namespace, parameterValues, function.IsComposable, useEntityReference); + } + else + { + this.WriteBoundFunctionInEntityTypeReturnSingleResult(hideBaseMethod, GetFixedName(functionName), function.Name, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, function.Namespace, parameterValues, function.IsComposable, function.ReturnType.IsEntity(), useEntityReference); + } + } + + foreach (IEdmAction action in operations.OfType()) + { + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + bool hideBaseMethod = this.CheckMethodsInBaseClass(structuredType.BaseType, action, boundOperationsMap); + this.GetParameterStrings(action.IsBound, true, action.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName; + if (action.ReturnType != null) + { + returnTypeName = GetSourceOrReturnTypeName(action.ReturnType); + if (action.ReturnType.IsCollection()) + { + returnTypeName = string.Format(this.DataServiceActionQueryOfTStructureTemplate, returnTypeName); + } + else + { + returnTypeName = string.Format(this.DataServiceActionQuerySingleOfTStructureTemplate, returnTypeName); + } + } + else + { + returnTypeName = this.DataServiceActionQueryTypeName; + } + + string actionName = action.Name; + if (this.context.EnableNamingAlias) + { + actionName = Customization.CustomizeNaming(actionName); + } + + this.WriteBoundActionInEntityType(hideBaseMethod, GetFixedName(actionName), action.Name, returnTypeName, parameterString, action.Namespace, parameterValues); + } + } + } + + internal bool CheckMethodsInBaseClass(IEdmStructuredType structuredType, IEdmOperation operation, Dictionary> boundOperationsMap) + { + if (structuredType != null) + { + List operations; + if (boundOperationsMap.TryGetValue(structuredType, out operations)) + { + foreach (IEdmOperation op in operations) + { + if (this.context.TargetLanguage == LanguageOption.VB) + { + if (operation.Name == op.Name) + { + return true; + } + } + + List targetParameter = operation.Parameters.ToList(); + List checkParameter = op.Parameters.ToList(); + if (operation.Name == op.Name && targetParameter.Count == checkParameter.Count) + { + bool areSame = true; + for (int i = 1; i < targetParameter.Count; ++i) + { + var targetParameterType = targetParameter[i].Type; + var checkParameterType = checkParameter[i].Type; + if (!targetParameterType.Definition.Equals(checkParameterType.Definition) + || targetParameterType.IsNullable != checkParameterType.IsNullable) + { + areSame = false; + break; + } + } + + if (areSame) + { + return true; + } + } + } + } + + return CheckMethodsInBaseClass(structuredType.BaseType, operation, boundOperationsMap); + } + + return false; + } + + internal void WriteEnumType(IEdmEnumType enumType) + { + this.WriteSummaryCommentForEnumType(this.context.EnableNamingAlias ? Customization.CustomizeNaming(enumType.Name) : enumType.Name); + if (enumType.IsFlags) + { + this.WriteEnumFlags(); + } + + string underlyingType = string.Empty; + if (enumType.UnderlyingType != null && enumType.UnderlyingType.PrimitiveKind != EdmPrimitiveTypeKind.Int32) + { + underlyingType = Utils.GetClrTypeName(enumType.UnderlyingType, this); + underlyingType = this.EnumUnderlyingTypeMarker + underlyingType; + } + + this.WriteEnumDeclaration(this.context.EnableNamingAlias ? GetFixedName(Customization.CustomizeNaming(enumType.Name)) : GetFixedName(enumType.Name), enumType.Name, underlyingType); + this.WriteMembersForEnumType(enumType.Members); + this.WriteEnumEnd(); + } + + internal void WriteStructurdTypeDeclaration(IEdmStructuredType structuredType, string baseEntityType, string typeNameSuffix = null) + { + string abstractModifier = structuredType.IsAbstract && typeNameSuffix == null ? this.AbstractModifier : string.Empty; + string baseTypeName = baseEntityType; + + if (typeNameSuffix == null) + { + if (structuredType.BaseType == null) + { + if (this.context.UseDataServiceCollection) + { + if (this.context.TargetLanguage == LanguageOption.CSharp) + { + baseTypeName += string.IsNullOrEmpty(baseTypeName) ? this.ClassInheritMarker : ", "; + } + + baseTypeName += this.NotifyPropertyChangedModifier; + } + } + else + { + IEdmSchemaElement baseType = (IEdmSchemaElement)structuredType.BaseType; + string baseTypeFixedName = this.context.EnableNamingAlias ? GetFixedName(Customization.CustomizeNaming(baseType.Name)) : GetFixedName(baseType.Name); + baseTypeName = ((IEdmSchemaElement)structuredType).Namespace == baseType.Namespace ? baseTypeFixedName : this.context.GetPrefixedFullName(baseType, baseTypeFixedName, this); + baseTypeName = this.ClassInheritMarker + baseTypeName; + } + } + + string structuredTypeName = this.context.EnableNamingAlias ? + Customization.CustomizeNaming(((IEdmSchemaElement)structuredType).Name) : ((IEdmSchemaElement)structuredType).Name; + this.WriteClassStartForStructuredType(abstractModifier, GetFixedName(structuredTypeName + typeNameSuffix), ((IEdmSchemaElement)structuredType).Name + typeNameSuffix, baseTypeName); + } + + internal string GetSourceOrReturnTypeName(IEdmTypeReference typeReference, bool isEntitySingleType = false) + { + IEdmCollectionType edmCollectionType = typeReference.Definition as IEdmCollectionType; + bool addNullableTemplate = true; + if (edmCollectionType != null) + { + typeReference = edmCollectionType.ElementType; + addNullableTemplate = false; + } + + return Utils.GetClrTypeName(typeReference, this.context.UseDataServiceCollection, this, this.context, addNullableTemplate, isEntitySingleType:isEntitySingleType); + } + + internal void GetParameterStrings(bool isBound, bool isAction, IEdmOperationParameter[] parameters, out string parameterString, out string parameterTypes, out string parameterExpressionString, out string parameterValues, out bool useEntityReference) + { + parameterString = string.Empty; + parameterExpressionString = string.Empty; + parameterTypes = string.Empty; + parameterValues = string.Empty; + useEntityReference = false; + + int n = parameters.Count(); + for (int i = isBound ? 1 : 0; i < n; ++i) + { + IEdmOperationParameter param = parameters[i]; + if (i == (isBound ? 1 : 0)) + { + if (this.context.TargetLanguage == LanguageOption.CSharp) + { + parameterExpressionString += "\r\n "; + } + else + { + parameterExpressionString += "\r\n "; + } + } + + string typeName = Utils.GetClrTypeName(param.Type, this.context.UseDataServiceCollection, this, this.context, true, true, true); + if (this.context.TargetLanguage == LanguageOption.CSharp) + { + parameterString += typeName; + parameterString += (" " + GetFixedName(param.Name)); + } + else if (this.context.TargetLanguage == LanguageOption.VB) + { + parameterString += GetFixedName(param.Name); + parameterString += (this.EnumUnderlyingTypeMarker + typeName); + } + + parameterString += i == n - 1 ? string.Empty : ", "; + parameterTypes += string.Format(this.TypeofFormatter, typeName) + ", "; + parameterExpressionString += this.GetParameterExpressionString(param, typeName) + ", "; + + if (i != (isBound ? 1 : 0)) + { + parameterValues += ",\r\n "; + } + + if (isAction) + { + parameterValues += string.Format(this.BodyOperationParameterConstructor, param.Name, GetFixedName(param.Name)); + } + else if (param.Type.IsEntity() || (param.Type.IsCollection() && param.Type.AsCollection().ElementType().IsEntity())) + { + useEntityReference = true; + parameterValues += string.Format(this.UriEntityOperationParameterConstructor, param.Name, GetFixedName(param.Name),"useEntityReference"); + } + else + { + parameterValues += string.Format(this.UriOperationParameterConstructor, param.Name, GetFixedName(param.Name)); + } + } + } + + internal string GetParameterExpressionString(IEdmOperationParameter param, string typeName) + { + string clrTypeName; + IEdmType edmType = param.Type.Definition; + IEdmPrimitiveType edmPrimitiveType = edmType as IEdmPrimitiveType; + if (edmPrimitiveType != null) + { + clrTypeName = Utils.GetClrTypeName(edmPrimitiveType, this); + if (param.Type.IsNullable && !this.ClrReferenceTypes.Contains(edmPrimitiveType.PrimitiveKind)) + { + clrTypeName += "?"; + } + + return string.Format(this.ConstantExpressionConstructorWithType, GetFixedName(param.Name), clrTypeName); + } + + return string.Format(this.ConstantExpressionConstructorWithType, GetFixedName(param.Name), typeName); + } + + // This is to solve duplicate names between property and type + internal void SetPropertyIdentifierMappingsIfNameConflicts(string typeName, IEdmStructuredType structuredType) + { + if (this.context.EnableNamingAlias) + { + typeName = Customization.CustomizeNaming(typeName); + } + + // PropertyName in VB is case-insensitive. + bool isLanguageCaseSensitive = this.context.TargetLanguage == LanguageOption.CSharp; + + // In VB, it is allowed that a type has a property whose name is same with the type's name + bool allowPropertyNameSameWithTypeName = this.context.TargetLanguage == LanguageOption.VB; + + Func customizePropertyName = (name) => { return this.context.EnableNamingAlias ? Customization.CustomizeNaming(name) : name; }; + + var propertyGroups = structuredType.Properties() + .GroupBy(p => isLanguageCaseSensitive ? customizePropertyName(p.Name) : customizePropertyName(p.Name).ToUpperInvariant()); + + // If the group contains more than one property, or the property in the group has the same name with the type (only for C#), we need to rename the property + var propertyToBeRenamedGroups = propertyGroups.Where(g => g.Count() > 1 || !allowPropertyNameSameWithTypeName && g.Key == typeName); + + var knownIdentifiers = propertyGroups.Select(g => customizePropertyName(g.First().Name)).ToList(); + if(!allowPropertyNameSameWithTypeName && !knownIdentifiers.Contains(typeName)) + { + knownIdentifiers.Add(typeName); + } + UniqueIdentifierService uniqueIdentifierService = + new UniqueIdentifierService(knownIdentifiers, isLanguageCaseSensitive); + + IdentifierMappings.Clear(); + foreach (IGrouping g in propertyToBeRenamedGroups) + { + bool hasPropertyNameSameWithCustomizedPropertyName = false; + int itemCount = g.Count(); + for (int i = 0; i < itemCount; i++) + { + var property = g.ElementAt(i); + var customizedPropertyName = customizePropertyName(property.Name); + + if(this.context.EnableNamingAlias && customizedPropertyName == property.Name) + { + hasPropertyNameSameWithCustomizedPropertyName = true; + } + + if(isLanguageCaseSensitive) + { + // If a property name is same as its customized property name, then we don't rename it. + // Or we don't rename the last property in the group + if(customizedPropertyName != typeName + && (customizedPropertyName == property.Name + || (!hasPropertyNameSameWithCustomizedPropertyName && i == itemCount-1))) + { + continue; + } + } + else + { + // When EnableNamingAlias = true, If a property name is same as its customized property name, then we don't rename it. + // Or we don't rename the last property in the group. + if((this.context.EnableNamingAlias && customizedPropertyName == property.Name) + || (!hasPropertyNameSameWithCustomizedPropertyName && i == itemCount-1)) + { + continue; + } + } + var renamedPropertyName = uniqueIdentifierService.GetUniqueIdentifier(customizedPropertyName); + IdentifierMappings.Add(property.Name, renamedPropertyName); + } + } + } + + internal void WriteTypeStaticCreateMethod(string typeName, IEdmStructuredType structuredType) + { + Debug.Assert(structuredType != null, "structuredType != null"); + if (structuredType.IsAbstract) + { + return; + } + + Func hasDefault = p => p.PropertyKind == EdmPropertyKind.Structural && ((IEdmStructuralProperty)p).DefaultValueString != null; + + if (this.context.EnableNamingAlias) + { + typeName = Customization.CustomizeNaming(typeName); + } + + IEnumerable parameters = structuredType.Properties() + .Where(p => !p.Type.IsNullable && !p.Type.IsCollection() && !hasDefault(p)); + if (!parameters.Any()) + { + return; + } + + this.WriteSummaryCommentForStaticCreateMethod(typeName); + + UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService( /*IsLanguageCaseSensitive*/true); + string instanceName = GetFixedName(uniqueIdentifierService.GetUniqueParameterName(typeName)); + KeyValuePair[] propertyToParameterNamePairs = parameters + .Select(p => + new KeyValuePair(p, + uniqueIdentifierService.GetUniqueParameterName( + IdentifierMappings.ContainsKey(p.Name) ? IdentifierMappings[p.Name] : p.Name))) + .ToArray(); + + foreach (var propertyToParameterNamePair in propertyToParameterNamePairs) + { + string propertyName = propertyToParameterNamePair.Key.Name; + propertyName = IdentifierMappings.ContainsKey(propertyName) ? + IdentifierMappings[propertyName] : (this.context.EnableNamingAlias ? Customization.CustomizeNaming(propertyName) : propertyName); + this.WriteParameterCommentForStaticCreateMethod(propertyToParameterNamePair.Value, propertyName); + } + + propertyToParameterNamePairs = propertyToParameterNamePairs + .Select(p => p = new KeyValuePair(p.Key, GetFixedName(p.Value))) + .ToArray(); + + this.WriteDeclarationStartForStaticCreateMethod(typeName, GetFixedName(typeName)); + this.WriteStaticCreateMethodParameters(propertyToParameterNamePairs); + this.WriteDeclarationEndForStaticCreateMethod(GetFixedName(typeName), instanceName); + + foreach (var propertyToParameterNamePair in propertyToParameterNamePairs) + { + IEdmProperty property = propertyToParameterNamePair.Key; + string parameterName = propertyToParameterNamePair.Value; + + Debug.Assert(!property.Type.IsCollection(), "!property.Type.IsCollection()"); + Debug.Assert(!property.Type.IsNullable, "!property.Type.IsNullable"); + + // The static create method only sets non-nullable properties. We should add the null check if the type of the property is not a clr ValueType. + // For now we add the null check if the property type is non-primitive. We should add the null check for non-ValueType primitives in the future. + if (!property.Type.IsPrimitive() && !property.Type.IsEnum()) + { + this.WriteParameterNullCheckForStaticCreateMethod(parameterName); + } + + var uniqIdentifier = IdentifierMappings.ContainsKey(property.Name) ? + IdentifierMappings[property.Name] : (this.context.EnableNamingAlias ? Customization.CustomizeNaming(property.Name) : property.Name); + this.WritePropertyValueAssignmentForStaticCreateMethod(instanceName, + GetFixedName(uniqIdentifier), + parameterName); + } + + this.WriteMethodEndForStaticCreateMethod(instanceName); + } + + internal void WriteStaticCreateMethodParameters(KeyValuePair[] propertyToParameterPairs) + { + if (propertyToParameterPairs.Length == 0) + { + return; + } + + // If the number of parameters are greater than 5, we put them in separate lines. + string parameterSeparator = propertyToParameterPairs.Length > 5 ? this.ParameterSeparator : ", "; + for (int idx = 0; idx < propertyToParameterPairs.Length; idx++) + { + KeyValuePair propertyToParameterPair = propertyToParameterPairs[idx]; + + string parameterType = Utils.GetClrTypeName(propertyToParameterPair.Key.Type, this.context.UseDataServiceCollection, this, this.context); + string parameterName = propertyToParameterPair.Value; + if (idx == propertyToParameterPairs.Length - 1) + { + // No separator after the last parameter. + parameterSeparator = string.Empty; + } + + this.WriteParameterForStaticCreateMethod(parameterType, GetFixedName(parameterName), parameterSeparator); + } + } + + internal void WritePropertiesForStructuredType(IEnumerable properties) + { + bool useDataServiceCollection = this.context.UseDataServiceCollection; + + var propertyInfos = properties.Select(property => + { + string propertyName = IdentifierMappings.ContainsKey(property.Name) ? + IdentifierMappings[property.Name] : (this.context.EnableNamingAlias ? Customization.CustomizeNaming(property.Name) : property.Name); + + return new + { + PropertyType = Utils.GetClrTypeName(property.Type, useDataServiceCollection, this, this.context), + PropertyVanillaName = property.Name, + PropertyName = propertyName, + FixedPropertyName = GetFixedName(propertyName), + PrivatePropertyName = "_" + propertyName, + PropertyInitializationValue = Utils.GetPropertyInitializationValue(property, useDataServiceCollection, this, this.context) + }; + }).ToList(); + + // Private name should not confict with field name + UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService(propertyInfos.Select(_ => _.FixedPropertyName), + this.context.TargetLanguage == LanguageOption.CSharp); + + foreach (var propertyInfo in propertyInfos) + { + string privatePropertyName = uniqueIdentifierService.GetUniqueIdentifier("_" + propertyInfo.PropertyName); + + this.WritePropertyForStructuredType( + propertyInfo.PropertyType, + propertyInfo.PropertyVanillaName, + propertyInfo.PropertyName, + propertyInfo.FixedPropertyName, + privatePropertyName, + propertyInfo.PropertyInitializationValue, + useDataServiceCollection); + } + } + + internal void WriteMembersForEnumType(IEnumerable members) + { + int n = members.Count(); + for (int idx = 0; idx < n; ++idx) + { + IEdmEnumMember member = members.ElementAt(idx); + string value = string.Empty; + if (member.Value != null) + { + IEdmEnumMemberValue integerValue = member.Value as IEdmEnumMemberValue; + if (integerValue != null) + { + value = " = " + integerValue.Value.ToString(CultureInfo.InvariantCulture); + } + } + + string memberName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(member.Name) : member.Name; + this.WriteMemberForEnumType(GetFixedName(memberName) + value, member.Name, idx == n - 1); + } + } + + internal string GetFixedName(string originalName) + { + string fixedName = originalName; + + if (this.LanguageKeywords.Contains(fixedName)) + { + fixedName = string.Format(this.FixPattern, fixedName); + } + + return fixedName; + } + + internal string GetElementTypeName(IEdmEntityType elementType, IEdmEntityContainer container) + { + string elementTypeName = elementType.Name; + + if (this.context.EnableNamingAlias) + { + elementTypeName = Customization.CustomizeNaming(elementTypeName); + } + + if (elementType.Namespace != container.Namespace) + { + elementTypeName = this.context.GetPrefixedFullName(elementType, GetFixedName(elementTypeName), this); + } + + return elementTypeName; + } +} + +/// +/// Base class for text transformation +/// +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "11.0.0.0")] +public abstract class TemplateBase +{ + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + + /// + /// Create the template output + /// + public abstract string TransformText(); + + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion +} + +/// +/// Service making names within a scope unique. Initialize a new instance for every scope. +/// +internal sealed class UniqueIdentifierService +{ + // This is the list of keywords we check against when creating parameter names from propert. + // If a name matches this keyword we prefix it. + private static readonly string[] Keywords = new string[] {"class", "event"}; + + /// + /// Hash set to detect identifier collision. + /// + private readonly HashSet knownIdentifiers; + + /// + /// Constructs a . + /// + /// true if the language we are generating the code for is case sensitive, false otherwise. + internal UniqueIdentifierService(bool caseSensitive) + { + this.knownIdentifiers = new HashSet(caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + } + + /// + /// Constructs a . + /// + /// identifiers used to detect collision. + /// true if the language we are generating the code for is case sensitive, false otherwise. + internal UniqueIdentifierService(IEnumerable identifiers, bool caseSensitive) + { + this.knownIdentifiers = new HashSet(identifiers ?? Enumerable.Empty(), caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + } + + /// + /// Given an identifier, makes it unique within the scope by adding + /// a suffix (1, 2, 3, ...), and returns the adjusted identifier. + /// + /// Identifier. Must not be null or empty. + /// Identifier adjusted to be unique within the scope. + internal string GetUniqueIdentifier(string identifier) + { + Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier is null or empty"); + + // find a unique name by adding suffix as necessary + int numberOfConflicts = 0; + string uniqueIdentifier = identifier; + while (this.knownIdentifiers.Contains(uniqueIdentifier)) + { + ++numberOfConflicts; + uniqueIdentifier = identifier + numberOfConflicts.ToString(CultureInfo.InvariantCulture); + } + + // remember the identifier in this scope + Debug.Assert(!this.knownIdentifiers.Contains(uniqueIdentifier), "we just made it unique"); + this.knownIdentifiers.Add(uniqueIdentifier); + + return uniqueIdentifier; + } + + /// + /// Fix up the given parameter name and make it unique. + /// + /// Parameter name. + /// Fixed parameter name. + internal string GetUniqueParameterName(string name) + { + name = Utils.CamelCase(name); + + // FxCop consider 'iD' as violation, we will change any property that is 'id'(case insensitive) to 'ID' + if (StringComparer.OrdinalIgnoreCase.Equals(name, "id")) + { + name = "ID"; + } + + return this.GetUniqueIdentifier(name); + } +} + +/// +/// Utility class. +/// +internal static class Utils +{ + /// + /// Serializes the xml element to a string. + /// + /// The xml element to serialize. + /// The string representation of the xml. + internal static string SerializeToString(XElement xml) + { + // because comment nodes can contain special characters that are hard to embed in VisualBasic, remove them here + xml.DescendantNodes().OfType().Remove(); + + var stringBuilder = new StringBuilder(); + using (var writer = XmlWriter.Create( + stringBuilder, + new XmlWriterSettings + { + OmitXmlDeclaration = true, + NewLineHandling = NewLineHandling.Replace, + Indent = true, + })) + { + xml.WriteTo(writer); + } + + return stringBuilder.ToString(); + } + + /// + /// Changes the text to use camel case, which lower case for the first character. + /// + /// Text to convert. + /// The converted text in camel case + internal static string CamelCase(string text) + { + if (string.IsNullOrEmpty(text)) + { + return text; + } + + if (text.Length == 1) + { + return text[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant(); + } + + return text[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant() + text.Substring(1); + } + + /// + /// Changes the text to use pascal case, which upper case for the first character. + /// + /// Text to convert. + /// The converted text in pascal case + internal static string PascalCase(string text) + { + if (string.IsNullOrEmpty(text)) + { + return text; + } + + if (text.Length == 1) + { + return text[0].ToString(CultureInfo.InvariantCulture).ToUpperInvariant(); + } + + return text[0].ToString(CultureInfo.InvariantCulture).ToUpperInvariant() + text.Substring(1); + } + + /// + /// Gets the clr type name from the give type reference. + /// + /// The type reference in question. + /// true to use the DataServicCollection type for entity collections and the ObservableCollection type for non-entity collections, + /// false to use Collection for collections. + /// ODataClientTemplate instance that call this method. + /// CodeGenerationContext instance in the clientTemplate. + /// This flag indicates whether to return the type name in nullable format + /// The flag indicates whether the namespace need to be added by global prefix + /// This flag indicates whether the edmTypeReference is for an operation parameter + /// The clr type name of the type reference. + internal static string GetClrTypeName(IEdmTypeReference edmTypeReference, bool useDataServiceCollection, ODataClientTemplate clientTemplate, CodeGenerationContext context, bool addNullableTemplate = true, bool needGlobalPrefix = true, bool isOperationParameter = false, bool isEntitySingleType = false) + { + string clrTypeName; + IEdmType edmType = edmTypeReference.Definition; + IEdmPrimitiveType edmPrimitiveType = edmType as IEdmPrimitiveType; + if (edmPrimitiveType != null) + { + clrTypeName = Utils.GetClrTypeName(edmPrimitiveType, clientTemplate); + if (edmTypeReference.IsNullable && !clientTemplate.ClrReferenceTypes.Contains(edmPrimitiveType.PrimitiveKind) && addNullableTemplate) + { + clrTypeName = string.Format(clientTemplate.SystemNullableStructureTemplate, clrTypeName); + } + } + else + { + IEdmComplexType edmComplexType = edmType as IEdmComplexType; + if (edmComplexType != null) + { + clrTypeName = context.GetPrefixedFullName(edmComplexType, + context.EnableNamingAlias ? clientTemplate.GetFixedName(Customization.CustomizeNaming(edmComplexType.Name)) : clientTemplate.GetFixedName(edmComplexType.Name), clientTemplate); + } + else + { + IEdmEnumType edmEnumType = edmType as IEdmEnumType; + if (edmEnumType != null) + { + clrTypeName = context.GetPrefixedFullName(edmEnumType, + context.EnableNamingAlias ? clientTemplate.GetFixedName(Customization.CustomizeNaming(edmEnumType.Name)) : clientTemplate.GetFixedName(edmEnumType.Name), clientTemplate, needGlobalPrefix); + if (edmTypeReference.IsNullable && addNullableTemplate) + { + clrTypeName = string.Format(clientTemplate.SystemNullableStructureTemplate, clrTypeName); + } + } + else + { + IEdmEntityType edmEntityType = edmType as IEdmEntityType; + if (edmEntityType != null) + { + clrTypeName = context.GetPrefixedFullName(edmEntityType, + context.EnableNamingAlias + ? clientTemplate.GetFixedName(Customization.CustomizeNaming(edmEntityType.Name) + (isEntitySingleType ? clientTemplate.SingleSuffix : string.Empty)) + : clientTemplate.GetFixedName(edmEntityType.Name + (isEntitySingleType ? clientTemplate.SingleSuffix : string.Empty)), + clientTemplate); + } + else + { + IEdmCollectionType edmCollectionType = (IEdmCollectionType)edmType; + IEdmTypeReference elementTypeReference = edmCollectionType.ElementType; + IEdmPrimitiveType primitiveElementType = elementTypeReference.Definition as IEdmPrimitiveType; + if (primitiveElementType != null) + { + clrTypeName = Utils.GetClrTypeName(primitiveElementType, clientTemplate); + } + else + { + IEdmSchemaElement schemaElement = (IEdmSchemaElement)elementTypeReference.Definition; + clrTypeName = context.GetPrefixedFullName(schemaElement, + context.EnableNamingAlias ? clientTemplate.GetFixedName(Customization.CustomizeNaming(schemaElement.Name)) : clientTemplate.GetFixedName(schemaElement.Name), clientTemplate); + } + + string collectionTypeName = isOperationParameter + ? clientTemplate.ICollectionOfTStructureTemplate + : (useDataServiceCollection + ? (elementTypeReference.TypeKind() == EdmTypeKind.Entity + ? clientTemplate.DataServiceCollectionStructureTemplate + : clientTemplate.ObservableCollectionStructureTemplate) + : clientTemplate.ObjectModelCollectionStructureTemplate); + + clrTypeName = string.Format(collectionTypeName, clrTypeName); + } + } + } + } + + return clrTypeName; + } + + /// + /// Gets the value expression to initualize the property with. + /// + /// The property in question. + /// true to use the DataServicCollection type for entity collections and the ObservableCollection type for non-entity collections, + /// false to use Collection for collections. + /// ODataClientTemplate instance that call this method. + /// CodeGenerationContext instance in the clientTemplate. + /// The value expression to initualize the property with. + internal static string GetPropertyInitializationValue(IEdmProperty property, bool useDataServiceCollection, ODataClientTemplate clientTemplate, CodeGenerationContext context) + { + IEdmTypeReference edmTypeReference = property.Type; + IEdmCollectionTypeReference edmCollectionTypeReference = edmTypeReference as IEdmCollectionTypeReference; + if (edmCollectionTypeReference == null) + { + IEdmStructuralProperty structuredProperty = property as IEdmStructuralProperty; + if (structuredProperty != null) + { + if (!string.IsNullOrEmpty(structuredProperty.DefaultValueString)) + { + string valueClrType = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); + string defaultValue = structuredProperty.DefaultValueString; + bool isCSharpTemplate = clientTemplate is ODataClientCSharpTemplate; + if (edmTypeReference.Definition.TypeKind == EdmTypeKind.Enum) + { + var enumValues = defaultValue.Split(','); + string fullenumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); + string enumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context, false, false); + List customizedEnumValues = new List(); + foreach(var enumValue in enumValues) + { + string currentEnumValue = enumValue.Trim(); + int indexFirst = currentEnumValue.IndexOf('\'') + 1; + int indexLast = currentEnumValue.LastIndexOf('\''); + if (indexFirst > 0 && indexLast > indexFirst) + { + currentEnumValue = currentEnumValue.Substring(indexFirst, indexLast - indexFirst); + } + + var customizedEnumValue = context.EnableNamingAlias ? Customization.CustomizeNaming(currentEnumValue) : currentEnumValue; + if (isCSharpTemplate) + { + currentEnumValue = "(" + fullenumTypeName + ")" + clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + customizedEnumValue + "\")"; + } + else + { + currentEnumValue = clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + currentEnumValue + "\")"; + } + customizedEnumValues.Add(currentEnumValue); + } + if (isCSharpTemplate) + { + return string.Join(" | ", customizedEnumValues); + } + else + { + return string.Join(" Or ", customizedEnumValues); + } + } + + if (valueClrType.Equals(clientTemplate.StringTypeName)) + { + defaultValue = "\"" + defaultValue + "\""; + } + else if (valueClrType.Equals(clientTemplate.BinaryTypeName)) + { + defaultValue = "System.Text.Encoding.UTF8.GetBytes(\"" + defaultValue + "\")"; + } + else if (valueClrType.Equals(clientTemplate.SingleTypeName)) + { + if (isCSharpTemplate) + { + defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "f"; + } + else + { + defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "F"; + } + } + else if (valueClrType.Equals(clientTemplate.DecimalTypeName)) + { + if (isCSharpTemplate) + { + // decimal in C# must be initialized with 'm' at the end, like Decimal dec = 3.00m + defaultValue = defaultValue.EndsWith("m", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "m"; + } + else + { + // decimal in VB must be initialized with 'D' at the end, like Decimal dec = 3.00D + defaultValue = defaultValue.ToLower().Replace("m", "D"); + defaultValue = defaultValue.EndsWith("D", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "D"; + } + } + else if (valueClrType.Equals(clientTemplate.GuidTypeName) + | valueClrType.Equals(clientTemplate.DateTimeOffsetTypeName) + | valueClrType.Equals(clientTemplate.DateTypeName) + | valueClrType.Equals(clientTemplate.TimeOfDayTypeName)) + { + defaultValue = valueClrType + ".Parse(\"" + defaultValue + "\")"; + } + else if (valueClrType.Equals(clientTemplate.DurationTypeName)) + { + defaultValue = clientTemplate.XmlConvertClassName + ".ToTimeSpan(\"" + defaultValue + "\")"; + } + else if (valueClrType.Contains("Microsoft.Spatial")) + { + defaultValue = string.Format(clientTemplate.GeoTypeInitializePattern, valueClrType, defaultValue); + } + + return defaultValue; + } + else + { + // doesn't have a default value + return null; + } + } + else + { + // only structured property has default value + return null; + } + } + else + { + string constructorParameters; + if (edmCollectionTypeReference.ElementType().IsEntity() && useDataServiceCollection) + { + constructorParameters = clientTemplate.DataServiceCollectionConstructorParameters; + } + else + { + constructorParameters = "()"; + } + + string clrTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); + return clientTemplate.NewModifier + clrTypeName + constructorParameters; + } + } + + /// + /// Gets the clr type name from the give Edm primitive type. + /// + /// The Edm primitive type in question. + /// ODataClientTemplate instance that call this method. + /// The clr type name of the Edm primitive type. + internal static string GetClrTypeName(IEdmPrimitiveType edmPrimitiveType, ODataClientTemplate clientTemplate) + { + EdmPrimitiveTypeKind kind = edmPrimitiveType.PrimitiveKind; + + string type="UNKNOWN"; + if (kind==EdmPrimitiveTypeKind.Int32) + { + type= clientTemplate.Int32TypeName; + } + else if (kind== EdmPrimitiveTypeKind.String) + { + type= clientTemplate.StringTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Binary) + { + type= clientTemplate.BinaryTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Decimal) + { + type= clientTemplate.DecimalTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Int16) + { + type= clientTemplate.Int16TypeName; + } + else if(kind==EdmPrimitiveTypeKind.Single) + { + type= clientTemplate.SingleTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Boolean) + { + type= clientTemplate.BooleanTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Double) + { + type= clientTemplate.DoubleTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Guid) + { + type= clientTemplate.GuidTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Byte) + { + type= clientTemplate.ByteTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Int64) + { + type= clientTemplate.Int64TypeName; + } + else if (kind== EdmPrimitiveTypeKind.SByte) + { + type= clientTemplate.SByteTypeName; + } + else if (kind == EdmPrimitiveTypeKind.Stream) + { + type= clientTemplate.DataServiceStreamLinkTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Geography) + { + type= clientTemplate.GeographyTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyPoint) + { + type= clientTemplate.GeographyPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyLineString) + { + type= clientTemplate.GeographyLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyPolygon) + { + type= clientTemplate.GeographyPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyCollection) + { + type= clientTemplate.GeographyCollectionTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyMultiPolygon) + { + type= clientTemplate.GeographyMultiPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyMultiLineString) + { + type= clientTemplate.GeographyMultiLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyMultiPoint) + { + type= clientTemplate.GeographyMultiPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Geometry) + { + type= clientTemplate.GeometryTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryPoint) + { + type= clientTemplate.GeometryPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryLineString) + { + type= clientTemplate.GeometryLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryPolygon) + { + type= clientTemplate.GeometryPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryCollection) + { + type= clientTemplate.GeometryCollectionTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryMultiPolygon) + { + type= clientTemplate.GeometryMultiPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryMultiLineString) + { + type= clientTemplate.GeometryMultiLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryMultiPoint) + { + type= clientTemplate.GeometryMultiPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.DateTimeOffset) + { + type= clientTemplate.DateTimeOffsetTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Duration) + { + type= clientTemplate.DurationTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Date) + { + type= clientTemplate.DateTypeName; + } + else if (kind== EdmPrimitiveTypeKind.TimeOfDay) + { + type= clientTemplate.TimeOfDayTypeName; + } + else + { + throw new Exception("Type "+kind.ToString()+" is unrecognized"); + } + + return type; + } +} + +public sealed class ODataClientCSharpTemplate : ODataClientTemplate +{ + /// + /// Creates an instance of the ODataClientTemplate. + /// + /// The code generation context. + public ODataClientCSharpTemplate(CodeGenerationContext context) + : base(context) + { + } + + internal override string GlobalPrefix { get {return "global::"; } } + internal override string SystemTypeTypeName { get { return "global::System.Type"; } } + internal override string AbstractModifier { get { return " abstract"; } } + internal override string DataServiceActionQueryTypeName { get { return "global::Microsoft.OData.Client.DataServiceActionQuery"; } } + internal override string DataServiceActionQuerySingleOfTStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceActionQuerySingle<{0}>"; } } + internal override string DataServiceActionQueryOfTStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceActionQuery<{0}>"; } } + internal override string NotifyPropertyChangedModifier { get { return "global::System.ComponentModel.INotifyPropertyChanged"; } } + internal override string ClassInheritMarker { get { return " : "; } } + internal override string ParameterSeparator { get { return ", \r\n "; } } + internal override string KeyParameterSeparator { get { return ", \r\n "; } } + internal override string KeyDictionaryItemSeparator { get { return ", \r\n "; } } + internal override string SystemNullableStructureTemplate { get { return "global::System.Nullable<{0}>"; } } + internal override string ICollectionOfTStructureTemplate { get { return "global::System.Collections.Generic.ICollection<{0}>"; } } + internal override string DataServiceCollectionStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceCollection<{0}>"; } } + internal override string DataServiceQueryStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceQuery<{0}>"; } } + internal override string DataServiceQuerySingleStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceQuerySingle<{0}>"; } } + internal override string ObservableCollectionStructureTemplate { get { return "global::System.Collections.ObjectModel.ObservableCollection<{0}>"; } } + internal override string ObjectModelCollectionStructureTemplate { get { return "global::System.Collections.ObjectModel.Collection<{0}>"; } } + internal override string DataServiceCollectionConstructorParameters { get { return "(null, global::Microsoft.OData.Client.TrackingMode.None)"; } } + internal override string NewModifier { get { return "new "; } } + internal override string GeoTypeInitializePattern { get { return "global::Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(false).Read<{0}>(new global::System.IO.StringReader(\"{1}\"))"; } } + internal override string Int32TypeName { get { return "int"; } } + internal override string StringTypeName { get { return "string"; } } + internal override string BinaryTypeName { get { return "byte[]"; } } + internal override string DecimalTypeName { get { return "decimal"; } } + internal override string Int16TypeName { get { return "short"; } } + internal override string SingleTypeName { get { return "float"; } } + internal override string BooleanTypeName { get { return "bool"; } } + internal override string DoubleTypeName { get { return "double"; } } + internal override string GuidTypeName { get { return "global::System.Guid"; } } + internal override string ByteTypeName { get { return "byte"; } } + internal override string Int64TypeName { get { return "long"; } } + internal override string SByteTypeName { get { return "sbyte"; } } + internal override string DataServiceStreamLinkTypeName { get { return "global::Microsoft.OData.Client.DataServiceStreamLink"; } } + internal override string GeographyTypeName { get { return "global::Microsoft.Spatial.Geography"; } } + internal override string GeographyPointTypeName { get { return "global::Microsoft.Spatial.GeographyPoint"; } } + internal override string GeographyLineStringTypeName { get { return "global::Microsoft.Spatial.GeographyLineString"; } } + internal override string GeographyPolygonTypeName { get { return "global::Microsoft.Spatial.GeographyPolygon"; } } + internal override string GeographyCollectionTypeName { get { return "global::Microsoft.Spatial.GeographyCollection"; } } + internal override string GeographyMultiPolygonTypeName { get { return "global::Microsoft.Spatial.GeographyMultiPolygon"; } } + internal override string GeographyMultiLineStringTypeName { get { return "global::Microsoft.Spatial.GeographyMultiLineString"; } } + internal override string GeographyMultiPointTypeName { get { return "global::Microsoft.Spatial.GeographyMultiPoint"; } } + internal override string GeometryTypeName { get { return "global::Microsoft.Spatial.Geometry"; } } + internal override string GeometryPointTypeName { get { return "global::Microsoft.Spatial.GeometryPoint"; } } + internal override string GeometryLineStringTypeName { get { return "global::Microsoft.Spatial.GeometryLineString"; } } + internal override string GeometryPolygonTypeName { get { return "global::Microsoft.Spatial.GeometryPolygon"; } } + internal override string GeometryCollectionTypeName { get { return "global::Microsoft.Spatial.GeometryCollection"; } } + internal override string GeometryMultiPolygonTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPolygon"; } } + internal override string GeometryMultiLineStringTypeName { get { return "global::Microsoft.Spatial.GeometryMultiLineString"; } } + internal override string GeometryMultiPointTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPoint"; } } + internal override string DateTypeName { get { return "global::Microsoft.OData.Edm.Date"; } } + internal override string DateTimeOffsetTypeName { get { return "global::System.DateTimeOffset"; } } + internal override string DurationTypeName { get { return "global::System.TimeSpan"; } } + internal override string TimeOfDayTypeName { get { return "global::Microsoft.OData.Edm.TimeOfDay"; } } + internal override string XmlConvertClassName { get { return "global::System.Xml.XmlConvert"; } } + internal override string EnumTypeName { get { return "global::System.Enum"; } } + internal override string FixPattern { get { return "@{0}"; } } + internal override string EnumUnderlyingTypeMarker { get { return " : "; } } + internal override string ConstantExpressionConstructorWithType { get { return "global::System.Linq.Expressions.Expression.Constant({0}, typeof({1}))"; } } + internal override string TypeofFormatter { get { return "typeof({0})"; } } + internal override string UriOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; } } + internal override string UriEntityOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; } } + internal override string BodyOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; } } + internal override string BaseEntityType { get { return " : global::Microsoft.OData.Client.BaseEntityType"; } } + internal override string OverloadsModifier { get { return "new "; } } + internal override string ODataVersion { get { return "global::Microsoft.OData.ODataVersion.V4"; } } + internal override string ParameterDeclarationTemplate { get { return "{0} {1}"; } } + internal override string DictionaryItemConstructor { get { return "{{ {0}, {1} }}"; } } + internal override HashSet LanguageKeywords { get { + if (CSharpKeywords == null) + { + CSharpKeywords = new HashSet(StringComparer.Ordinal) + { + "abstract", "as", "base", "byte", "bool", "break", "case", "catch", "char", "checked", "class", "const", "continue", + "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "for", + "foreach", "finally", "fixed", "float", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", + "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", + "readonly", "ref", "return", "sbyte", "sealed", "string", "short", "sizeof", "stackalloc", "static", "struct", "switch", + "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "volatile", + "void", "while" + }; + } + return CSharpKeywords; + } } + private HashSet CSharpKeywords; + + internal override void WriteFileHeader() + { + +this.Write("//------------------------------------------------------------------------------\r" + + "\n// \r\n// This code was generated by a tool.\r\n// Runtime " + + "Version:"); + +this.Write(this.ToStringHelper.ToStringWithCulture(Environment.Version)); + +this.Write("\r\n//\r\n// Changes to this file may cause incorrect behavior and will be lost i" + + "f\r\n// the code is regenerated.\r\n// \r\n//--------------------" + + "----------------------------------------------------------\r\n\r\n// Generation date" + + ": "); + +this.Write(this.ToStringHelper.ToStringWithCulture(DateTime.Now.ToString(global::System.Globalization.CultureInfo.CurrentCulture))); + +this.Write("\r\n"); + + + } + + internal override void WriteNamespaceStart(string fullNamespace) + { + +this.Write("namespace "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("\r\n{\r\n"); + + + } + + internal override void WriteClassStartForEntityContainer(string originalContainerName, string containerName, string fixedContainerName) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(containerName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalContainerName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public partial class "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fixedContainerName)); + +this.Write(" : global::Microsoft.OData.Client.DataServiceContext\r\n {\r\n"); + + + } + + internal override void WriteMethodStartForEntityContainerConstructor(string containerName, string fixedContainerName) + { + +this.Write(" /// \r\n /// Initialize a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(containerName)); + +this.Write(" object.\r\n /// \r\n [global::System.CodeDom.Compiler.Genera" + + "tedCodeAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fixedContainerName)); + +this.Write("(global::System.Uri serviceRoot) : \r\n base(serviceRoot, global::Mi" + + "crosoft.OData.Client.ODataProtocolVersion.V4)\r\n {\r\n"); + + + } + + internal override void WriteKeyAsSegmentUrlConvention() + { + +this.Write(" this.UrlKeyDelimiter = global::Microsoft.OData.Client.DataServiceUrlK" + + "eyDelimiter.Slash;\r\n"); + + + } + + internal override void WriteInitializeResolveName() + { + +this.Write(" this.ResolveName = new global::System.Func(this.ResolveNameFromType);\r\n"); + + + } + + internal override void WriteInitializeResolveType() + { + +this.Write(" this.ResolveType = new global::System.Func(this.ResolveTypeFromName);\r\n"); + + + } + + internal override void WriteClassEndForEntityContainerConstructor() + { + +this.Write(" this.OnContextCreated();\r\n this.Format.LoadServiceModel = " + + "GeneratedEdmModel.GetInstance;\r\n this.Format.UseJson();\r\n }\r\n " + + " partial void OnContextCreated();\r\n"); + + + } + + internal override void WriteMethodStartForResolveTypeFromName() + { + +this.Write(@" /// + /// Since the namespace configured for this service reference + /// in Visual Studio is different from the one indicated in the + /// server schema, use type-mappers to map between the two. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Microsoft.OData.Client.Design.T4"", """); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n protected global::System.Type ResolveTypeFromName(string typeName)\r\n" + + " {\r\n"); + + + } + + internal override void WriteResolveNamespace(string typeName, string fullNamespace, string languageDependentNamespace) + { + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write("resolvedType = this.DefaultResolveType(typeName, \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(languageDependentNamespace)); + +this.Write("\");\r\n if ((resolvedType != null))\r\n {\r\n retu" + + "rn resolvedType;\r\n }\r\n"); + + + } + + internal override void WriteMethodEndForResolveTypeFromName() + { + +this.Write(" return null;\r\n }\r\n"); + + + } + + internal override void WritePropertyRootNamespace(string containerName, string fullNamespace) + { + + } + + internal override void WriteMethodStartForResolveNameFromType(string containerName, string fullNamespace) + { + +this.Write(@" /// + /// Since the namespace configured for this service reference + /// in Visual Studio is different from the one indicated in the + /// server schema, use type-mappers to map between the two. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Microsoft.OData.Client.Design.T4"", """); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n protected string ResolveNameFromType(global::System.Type clientType)" + + "\r\n {\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(@" global::Microsoft.OData.Client.OriginalNameAttribute originalNameAttribute = (global::Microsoft.OData.Client.OriginalNameAttribute)global::System.Linq.Enumerable.SingleOrDefault(global::Microsoft.OData.Client.Utility.GetCustomAttributes(clientType, typeof(global::Microsoft.OData.Client.OriginalNameAttribute), true)); +"); + + + } + } + + internal override void WriteResolveType(string fullNamespace, string languageDependentNamespace) + { + +this.Write(" if (clientType.Namespace.Equals(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(languageDependentNamespace)); + +this.Write("\", global::System.StringComparison.Ordinal))\r\n {\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" if (originalNameAttribute != null)\r\n {\r\n " + + " return string.Concat(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write(".\", originalNameAttribute.OriginalName);\r\n }\r\n"); + + + } + +this.Write(" return string.Concat(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write(".\", clientType.Name);\r\n }\r\n"); + + + } + + internal override void WriteMethodEndForResolveNameFromType(bool modelHasInheritance) + { + if (this.context.EnableNamingAlias && modelHasInheritance) + { + +this.Write(" if (originalNameAttribute != null)\r\n {\r\n re" + + "turn clientType.Namespace + \".\" + originalNameAttribute.OriginalName;\r\n " + + " }\r\n"); + + + } + +this.Write(" return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(modelHasInheritance ? "clientType.FullName" : "null")); + +this.Write(";\r\n }\r\n"); + + + } + + internal override void WriteConstructorForSingleType(string singleTypeName, string baseTypeName) + { + +this.Write(" /// \r\n /// Initialize a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write(" object.\r\n /// \r\n public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write("(global::Microsoft.OData.Client.DataServiceContext context, string path)\r\n " + + " : base(context, path) {}\r\n\r\n /// \r\n /// Initialize a" + + " new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write(" object.\r\n /// \r\n public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write("(global::Microsoft.OData.Client.DataServiceContext context, string path, bool isC" + + "omposable)\r\n : base(context, path, isComposable) {}\r\n\r\n /// \r\n /// Initialize a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write(" object.\r\n /// \r\n public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write(" query)\r\n : base(query) {}\r\n\r\n"); + + + } + + internal override void WriteContextEntitySetProperty(string entitySetName, string entitySetFixedName, string originalEntitySetName, string entitySetElementTypeName, bool inContext) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" in the schema.\r\n /// \r\n [global::System.CodeDom.Compiler" + + ".GeneratedCodeAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalEntitySetName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public global::Microsoft.OData.Client.DataServiceQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetElementTypeName)); + +this.Write("> "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetFixedName)); + +this.Write("\r\n {\r\n get\r\n {\r\n"); + + + if (!inContext) + { + +this.Write(" if (!this.IsComposable)\r\n {\r\n t" + + "hrow new global::System.NotSupportedException(\"The previous function is not comp" + + "osable.\");\r\n }\r\n"); + + + } + +this.Write(" if ((this._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" == null))\r\n {\r\n this._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" = "); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "base" : "Context")); + +this.Write(".CreateQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetElementTypeName)); + +this.Write(">("); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "\"" + originalEntitySetName + "\"" : "GetPath(\"" + originalEntitySetName + "\")")); + +this.Write(");\r\n }\r\n return this._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(";\r\n }\r\n }\r\n [global::System.CodeDom.Compiler.GeneratedCo" + + "deAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private global::Microsoft.OData.Client.DataServiceQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetElementTypeName)); + +this.Write("> _"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(";\r\n"); + + + } + + internal override void WriteContextSingletonProperty(string singletonName, string singletonFixedName, string originalSingletonName, string singletonElementTypeName, bool inContext) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(" in the schema.\r\n /// \r\n [global::System.CodeDom.Compiler" + + ".GeneratedCodeAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalSingletonName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonElementTypeName)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonFixedName)); + +this.Write("\r\n {\r\n get\r\n {\r\n"); + + + if (!inContext) + { + +this.Write(" if (!this.IsComposable)\r\n {\r\n t" + + "hrow new global::System.NotSupportedException(\"The previous function is not comp" + + "osable.\");\r\n }\r\n"); + + + } + +this.Write(" if ((this._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(" == null))\r\n {\r\n this._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(" = new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonElementTypeName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "this" : "this.Context")); + +this.Write(", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "\"" + originalSingletonName + "\"" : "GetPath(\"" + originalSingletonName + "\")")); + +this.Write(");\r\n }\r\n return this._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(";\r\n }\r\n }\r\n [global::System.CodeDom.Compiler.GeneratedCo" + + "deAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonElementTypeName)); + +this.Write(" _"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(";\r\n"); + + + } + + internal override void WriteContextAddToEntitySetMethod(string entitySetName, string originalEntitySetName, string typeName, string parameterName) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" in the schema.\r\n /// \r\n [global::System.CodeDom.Compiler" + + ".GeneratedCodeAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n public void AddTo"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(")\r\n {\r\n base.AddObject(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalEntitySetName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(");\r\n }\r\n"); + + + } + + internal override void WriteGeneratedEdmModel(string escapedEdmxString) + { + + string path = this.context.TempFilePath; + + if (!String.IsNullOrEmpty(path)) + { + using (StreamWriter writer = new StreamWriter(path, true)) + { + writer.WriteLine(escapedEdmxString); + } + } + + bool useTempFile = !String.IsNullOrEmpty(path) && System.IO.File.Exists(path); + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OData." + + "Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private abstract class GeneratedEdmModel\r\n {\r\n"); + + + if (this.context.ReferencesMap != null) + { + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OD" + + "ata.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private static global::System.Collections.Generic.Dictionary ReferencesMap = new global::System.Collections.Generic.Dictionary()\r\n {\r\n"); + + + foreach(var reference in this.context.ReferencesMap) + { + +this.Write(" {@\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(reference.Key.OriginalString.Replace("\"", "\"\""))); + +this.Write("\", @\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(Utils.SerializeToString(reference.Value).Replace("\"", "\"\""))); + +this.Write("\"},\r\n"); + + + } + +this.Write(" };\r\n"); + + + } + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OD" + + "ata.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private static global::Microsoft.OData.Edm.IEdmModel ParsedModel" + + " = LoadModelFromString();\r\n"); + + + if (useTempFile) + { + +this.Write(" \r\n [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsof" + + "t.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private const string filePath = @\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(path)); + +this.Write("\";\r\n"); + + + } + else + { + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OD" + + "ata.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private const string Edmx = @\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(escapedEdmxString)); + +this.Write("\";\r\n"); + + + } + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OD" + + "ata.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n public static global::Microsoft.OData.Edm.IEdmModel GetInstance(" + + ")\r\n {\r\n return ParsedModel;\r\n }\r\n"); + + + if (this.context.ReferencesMap != null) + { + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OD" + + "ata.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write(@""")] + private static global::System.Xml.XmlReader getReferencedModelFromMap(global::System.Uri uri) + { + string referencedEdmx; + if (ReferencesMap.TryGetValue(uri.OriginalString, out referencedEdmx)) + { + return CreateXmlReader(referencedEdmx); + } + + return null; + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Microsoft.OData.Client.Design.T4"", """); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private static global::Microsoft.OData.Edm.IEdmModel LoadModelFr" + + "omString()\r\n {\r\n"); + + + if (useTempFile) + { + +this.Write(" \r\n global::System.Xml.XmlReader reader = CreateXmlReader();\r\n"); + + + } + else + { + +this.Write(" global::System.Xml.XmlReader reader = CreateXmlReader(Edmx);\r\n"); + + + } + +this.Write(@" try + { + return global::Microsoft.OData.Edm.Csdl.CsdlReader.Parse(reader, getReferencedModelFromMap); + } + finally + { + ((global::System.IDisposable)(reader)).Dispose(); + } + } +"); + + + } + else + { + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OD" + + "ata.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private static global::Microsoft.OData.Edm.IEdmModel LoadModelFr" + + "omString()\r\n {\r\n"); + + + if (useTempFile) + { + +this.Write(" \r\n global::System.Xml.XmlReader reader = CreateXmlReader();\r\n"); + + + } + else + { + +this.Write(" global::System.Xml.XmlReader reader = CreateXmlReader(Edmx);\r\n"); + + + } + +this.Write(@" try + { + global::System.Collections.Generic.IEnumerable errors; + global::Microsoft.OData.Edm.IEdmModel edmModel; + + if (!global::Microsoft.OData.Edm.Csdl.CsdlReader.TryParse(reader, "); + +this.Write(this.ToStringHelper.ToStringWithCulture(this.context.IgnoreUnexpectedElementsAndAttributes ? "true" : "false")); + +this.Write(@", out edmModel, out errors)) + { + global::System.Text.StringBuilder errorMessages = new System.Text.StringBuilder(); + foreach (var error in errors) + { + errorMessages.Append(error.ErrorMessage); + errorMessages.Append(""; ""); + } + throw new global::System.InvalidOperationException(errorMessages.ToString()); + } + + return edmModel; + } + finally + { + ((global::System.IDisposable)(reader)).Dispose(); + } + } +"); + + + } + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OD" + + "ata.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private static global::System.Xml.XmlReader CreateXmlReader(stri" + + "ng edmxToParse)\r\n {\r\n return global::System.Xml.XmlRea" + + "der.Create(new global::System.IO.StringReader(edmxToParse));\r\n }\r\n"); + + + if (useTempFile) + { + +this.Write(" \r\n [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Mic" + + "rosoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private static global::System.Xml.XmlReader CreateXmlReader(" + + ")\r\n {\r\n return global::System.Xml.XmlReader.Cr" + + "eate(new global::System.IO.StreamReader(filePath));\r\n }\r\n"); + + + } + +this.Write(" }\r\n"); + + + } + + internal override void WriteClassEndForEntityContainer() + { + +this.Write(" }\r\n"); + + + } + + internal override void WriteSummaryCommentForStructuredType(string typeName) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + } + + internal override void WriteKeyPropertiesCommentAndAttribute(IEnumerable keyProperties, string keyString) + { + +this.Write(" /// \r\n"); + + + foreach (string key in keyProperties) + { + +this.Write(" /// "); + +this.Write(this.ToStringHelper.ToStringWithCulture(key)); + +this.Write("\r\n"); + + + } + +this.Write(" /// \r\n [global::Microsoft.OData.Client.Key(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(keyString)); + +this.Write("\")]\r\n"); + + + } + + internal override void WriteEntityTypeAttribute() + { + +this.Write(" [global::Microsoft.OData.Client.EntityType()]\r\n"); + + + } + + internal override void WriteEntitySetAttribute(string entitySetName) + { + +this.Write(" [global::Microsoft.OData.Client.EntitySet(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write("\")]\r\n"); + + + } + + internal override void WriteEntityHasStreamAttribute() + { + +this.Write(" [global::Microsoft.OData.Client.HasStream()]\r\n"); + + + } + + internal override void WriteClassStartForStructuredType(string abstractModifier, string typeName, string originalTypeName, string baseTypeName) + { + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalTypeName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public"); + +this.Write(this.ToStringHelper.ToStringWithCulture(abstractModifier)); + +this.Write(" partial class "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write("\r\n {\r\n"); + + + } + + internal override void WriteSummaryCommentForStaticCreateMethod(string typeName) + { + +this.Write(" /// \r\n /// Create a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(" object.\r\n /// \r\n"); + + + } + + internal override void WriteParameterCommentForStaticCreateMethod(string parameterName, string propertyName) + { + +this.Write(" /// Initial value of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write(".\r\n"); + + + } + + internal override void WriteDeclarationStartForStaticCreateMethod(string typeName, string fixedTypeName) + { + +this.Write(" [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.OData." + + "Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n public static "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fixedTypeName)); + +this.Write(" Create"); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write("("); + + + } + + internal override void WriteParameterForStaticCreateMethod(string parameterTypeName, string parameterName, string parameterSeparater) + { + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterTypeName)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterSeparater)); + + + } + + internal override void WriteDeclarationEndForStaticCreateMethod(string typeName, string instanceName) + { + +this.Write(")\r\n {\r\n "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(instanceName)); + +this.Write(" = new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write("();\r\n"); + + + } + + internal override void WriteParameterNullCheckForStaticCreateMethod(string parameterName) + { + +this.Write(" if (("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(" == null))\r\n {\r\n throw new global::System.ArgumentNullE" + + "xception(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write("\");\r\n }\r\n"); + + + } + + internal override void WritePropertyValueAssignmentForStaticCreateMethod(string instanceName, string propertyName, string parameterName) + { + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(instanceName)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write(" = "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(";\r\n"); + + + } + + internal override void WriteMethodEndForStaticCreateMethod(string instanceName) + { + +this.Write(" return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(instanceName)); + +this.Write(";\r\n }\r\n"); + + + } + + internal override void WritePropertyForStructuredType(string propertyType, string originalPropertyName, string propertyName, string fixedPropertyName, string privatePropertyName, string propertyInitializationValue, bool writeOnPropertyChanged) + { + +this.Write(" /// \r\n /// There are no comments for Property "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write(" in the schema.\r\n /// \r\n [global::System.CodeDom.Compiler" + + ".GeneratedCodeAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n"); + + + if (this.context.EnableNamingAlias || IdentifierMappings.ContainsKey(originalPropertyName)) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalPropertyName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyType)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fixedPropertyName)); + +this.Write("\r\n {\r\n get\r\n {\r\n return this."); + +this.Write(this.ToStringHelper.ToStringWithCulture(privatePropertyName)); + +this.Write(";\r\n }\r\n set\r\n {\r\n this.On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changing(value);\r\n this."); + +this.Write(this.ToStringHelper.ToStringWithCulture(privatePropertyName)); + +this.Write(" = value;\r\n this.On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changed();\r\n"); + + + if (writeOnPropertyChanged) + { + +this.Write(" this.OnPropertyChanged(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalPropertyName)); + +this.Write("\");\r\n"); + + + } + +this.Write(" }\r\n }\r\n [global::System.CodeDom.Compiler.GeneratedCodeA" + + "ttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write("\")]\r\n private "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyType)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(privatePropertyName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyInitializationValue != null ? " = " + propertyInitializationValue : string.Empty)); + +this.Write(";\r\n partial void On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changing("); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyType)); + +this.Write(" value);\r\n partial void On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changed();\r\n"); + + + } + + internal override void WriteINotifyPropertyChangedImplementation() + { + +this.Write(" /// \r\n /// This event is raised when the value of the pro" + + "perty is changed\r\n /// \r\n [global::System.CodeDom.Compil" + + "er.GeneratedCodeAttribute(\"Microsoft.OData.Client.Design.T4\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write(@""")] + public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + /// + /// The value of the property is changed + /// + /// property name + [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Microsoft.OData.Client.Design.T4"", """); + +this.Write(this.ToStringHelper.ToStringWithCulture(T4Version)); + +this.Write(@""")] + protected virtual void OnPropertyChanged(string property) + { + if ((this.PropertyChanged != null)) + { + this.PropertyChanged(this, new global::System.ComponentModel.PropertyChangedEventArgs(property)); + } + } +"); + + + } + + internal override void WriteClassEndForStructuredType() + { + +this.Write(" }\r\n"); + + + } + + internal override void WriteEnumFlags() + { + +this.Write(" [global::System.Flags]\r\n"); + + + } + + internal override void WriteSummaryCommentForEnumType(string enumName) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(enumName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + } + + internal override void WriteEnumDeclaration(string enumName, string originalEnumName, string underlyingType) + { + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalEnumName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public enum "); + +this.Write(this.ToStringHelper.ToStringWithCulture(enumName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(underlyingType)); + +this.Write("\r\n {\r\n"); + + + } + + internal override void WriteMemberForEnumType(string member, string originalMemberName, bool last) + { + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalMemberName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(member)); + +this.Write(this.ToStringHelper.ToStringWithCulture(last ? string.Empty : ",")); + +this.Write("\r\n"); + + + } + + internal override void WriteEnumEnd() + { + +this.Write(" }\r\n"); + + + } + + internal override void WriteFunctionImportReturnCollectionResult(string functionName, string originalFunctionName, string returnTypeName, string parameters, string parameterValues, bool isComposable, bool useEntityReference) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public global::Microsoft.OData.Client.DataServiceQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("> "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", bool useEntityReference = false" : string.Empty)); + +this.Write(")\r\n {\r\n return this.CreateFunctionQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(">(\"\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable.ToString().ToLower())); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(");\r\n }\r\n"); + + + } + + internal override void WriteFunctionImportReturnSingleResult(string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName))); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", bool useEntityReference = false" : string.Empty)); + +this.Write(")\r\n {\r\n return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? "new " + returnTypeNameWithSingleSuffix + "(" : string.Empty)); + +this.Write("this.CreateFunctionQuerySingle<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(">(\"\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable.ToString().ToLower())); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? ")" : string.Empty)); + +this.Write(";\r\n }\r\n"); + + + } + + internal override void WriteBoundFunctionInEntityTypeReturnCollectionResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(hideBaseMethod ? this.OverloadsModifier : string.Empty)); + +this.Write("global::Microsoft.OData.Client.DataServiceQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("> "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", bool useEntityReference = false" : string.Empty)); + +this.Write(")\r\n {\r\n global::System.Uri requestUri;\r\n Context.Try" + + "GetUri(this, out requestUri);\r\n return this.Context.CreateFunctionQue" + + "ry<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(">(string.Join(\"/\", global::System.Linq.Enumerable.Select(global::System.Linq.Enum" + + "erable.Skip(requestUri.Segments, this.Context.BaseUri.Segments.Length), s => s.T" + + "rim(\'/\'))), \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable.ToString().ToLower())); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", bool useEntityReference = false" : string.Empty)); + +this.Write(");\r\n }\r\n"); + + + } + + internal override void WriteBoundFunctionInEntityTypeReturnSingleResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(hideBaseMethod ? this.OverloadsModifier : string.Empty)); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName))); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", bool useEntityReference = false" : string.Empty)); + +this.Write(")\r\n {\r\n global::System.Uri requestUri;\r\n Context.Try" + + "GetUri(this, out requestUri);\r\n\r\n return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? "new " + returnTypeNameWithSingleSuffix + "(" : string.Empty)); + +this.Write("this.Context.CreateFunctionQuerySingle<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(">(string.Join(\"/\", global::System.Linq.Enumerable.Select(global::System.Linq.Enum" + + "erable.Skip(requestUri.Segments, this.Context.BaseUri.Segments.Length), s => s.T" + + "rim(\'/\'))), \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable.ToString().ToLower())); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? ")" : string.Empty)); + +this.Write(";\r\n }\r\n"); + + + } + + internal override void WriteActionImport(string actionName, string originalActionName, string returnTypeName, string parameters, string parameterValues) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(")\r\n {\r\n return new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(this, this.BaseUri.OriginalString.Trim(\'/\') + \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(");\r\n }\r\n"); + + + } + + internal override void WriteBoundActionInEntityType(bool hideBaseMethod, string actionName, string originalActionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(hideBaseMethod ? this.OverloadsModifier : string.Empty)); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(@") + { + global::Microsoft.OData.Client.EntityDescriptor resource = Context.EntityTracker.TryGetEntityDescriptor(this); + if (resource == null) + { + throw new global::System.Exception(""cannot find entity""); + } + + return new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(this.Context, resource.EditLink.OriginalString.Trim(\'/\') + \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(");\r\n }\r\n"); + + + } + + internal override void WriteExtensionMethodsStart() + { + +this.Write(" /// \r\n /// Class containing all extension methods\r\n /// \r\n public static class ExtensionMethods\r\n {\r\n"); + + + } + + internal override void WriteExtensionMethodsEnd() + { + +this.Write(" }\r\n"); + + + } + + internal override void WriteByKeyMethods(string entityTypeName, string returnTypeName, IEnumerable keys, string keyParameters, string keyDictionaryItems) + { + +this.Write(" /// \r\n /// Get an entity of type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write(" as "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" specified by key from an entity set\r\n /// \r\n /// source entity set\r\n /// dictiona" + + "ry with the names and values of keys\r\n public static "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" ByKey(this global::Microsoft.OData.Client.DataServiceQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write("> source, global::System.Collections.Generic.Dictionary keys)\r\n " + + " {\r\n return new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, source.GetKeyPath(global::Microsoft.OData.Client.Serializer.GetK" + + "eyString(source.Context, keys)));\r\n }\r\n /// \r\n ///" + + " Get an entity of type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write(" as "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" specified by key from an entity set\r\n /// \r\n /// source entity set\r\n"); + + + foreach (var key in keys) + { + +this.Write(" /// The value of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(key)); + +this.Write("\r\n"); + + + } + +this.Write(" public static "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" ByKey(this global::Microsoft.OData.Client.DataServiceQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write("> source,\r\n "); + +this.Write(this.ToStringHelper.ToStringWithCulture(keyParameters)); + +this.Write(")\r\n {\r\n global::System.Collections.Generic.Dictionary keys = new global::System.Collections.Generic.Dictionary\r" + + "\n {\r\n "); + +this.Write(this.ToStringHelper.ToStringWithCulture(keyDictionaryItems)); + +this.Write("\r\n };\r\n return new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, source.GetKeyPath(global::Microsoft.OData.Client.Serializer.GetK" + + "eyString(source.Context, keys)));\r\n }\r\n"); + + + } + + internal override void WriteCastToMethods(string baseTypeName, string derivedTypeName, string derivedTypeFullName, string returnTypeName) + { + +this.Write(" /// \r\n /// Cast an entity of type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write(" to its derived type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeFullName)); + +this.Write("\r\n /// \r\n /// source entity\r" + + "\n public static "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" CastTo"); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeName)); + +this.Write("(this global::Microsoft.OData.Client.DataServiceQuerySingle<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write("> source)\r\n {\r\n global::Microsoft.OData.Client.DataServiceQuery" + + "Single<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeFullName)); + +this.Write("> query = source.CastTo<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeFullName)); + +this.Write(">();\r\n return new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, query.GetPath(null));\r\n }\r\n"); + + + } + + internal override void WriteBoundFunctionReturnSingleResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public static "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName))); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("(this "); + +this.Write(this.ToStringHelper.ToStringWithCulture(boundTypeName)); + +this.Write(" source"); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", bool useEntityReference = false" : string.Empty)); + +this.Write(")\r\n {\r\n if (!source.IsComposable)\r\n {\r\n " + + " throw new global::System.NotSupportedException(\"The previous function is not " + + "composable.\");\r\n }\r\n\r\n return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? "new " + returnTypeNameWithSingleSuffix + "(" : string.Empty)); + +this.Write("source.CreateFunctionQuerySingle<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(">(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable.ToString().ToLower())); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? ")" : string.Empty)); + +this.Write(";\r\n }\r\n"); + + + } + + internal override void WriteBoundFunctionReturnCollectionResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public static global::Microsoft.OData.Client.DataServiceQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("> "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("(this "); + +this.Write(this.ToStringHelper.ToStringWithCulture(boundTypeName)); + +this.Write(" source"); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", bool useEntityReference = true" : string.Empty)); + +this.Write(")\r\n {\r\n if (!source.IsComposable)\r\n {\r\n " + + " throw new global::System.NotSupportedException(\"The previous function is not " + + "composable.\");\r\n }\r\n\r\n return source.CreateFunctionQuery<"); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(">(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable.ToString().ToLower())); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(");\r\n }\r\n"); + + + } + + internal override void WriteBoundActionAsExtension(string actionName, string originalActionName, string boundSourceType, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { + +this.Write(" /// \r\n /// There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write(" in the schema.\r\n /// \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" [global::Microsoft.OData.Client.OriginalNameAttribute(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\")]\r\n"); + + + } + +this.Write(" public static "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write("(this "); + +this.Write(this.ToStringHelper.ToStringWithCulture(boundSourceType)); + +this.Write(" source"); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters)); + +this.Write(")\r\n {\r\n if (!source.IsComposable)\r\n {\r\n " + + " throw new global::System.NotSupportedException(\"The previous function is not " + + "composable.\");\r\n }\r\n\r\n return new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, source.AppendRequestUri(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(");\r\n }\r\n"); + + + } + + internal override void WriteNamespaceEnd() + { + +this.Write("}\r\n"); + + + } +} + +public sealed class ODataClientVBTemplate : ODataClientTemplate +{ + /// + /// Creates an instance of the ODataClientTemplate. + /// + /// The cotion context. + public ODataClientVBTemplate(CodeGenerationContext context) + : base(context) + { + } + + internal override string GlobalPrefix { get { return string.Empty; } } + internal override string SystemTypeTypeName { get { return "Global.System.Type"; } } + internal override string AbstractModifier { get { return " MustInherit"; } } + internal override string DataServiceActionQueryTypeName { get { return "Global.Microsoft.OData.Client.DataServiceActionQuery"; } } + internal override string DataServiceActionQuerySingleOfTStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceActionQuerySingle(Of {0})"; } } + internal override string DataServiceActionQueryOfTStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceActionQuery(Of {0})"; } } + internal override string NotifyPropertyChangedModifier { get { return "\r\n Implements Global.System.ComponentModel.INotifyPropertyChanged"; } } + internal override string ClassInheritMarker { get { return "\r\n Inherits "; } } + internal override string ParameterSeparator { get { return ", _\r\n "; } } + internal override string KeyParameterSeparator { get { return ", _\r\n "; } } + internal override string KeyDictionaryItemSeparator { get { return ", _\r\n "; } } + internal override string SystemNullableStructureTemplate { get { return "Global.System.Nullable(Of {0})"; } } + internal override string ICollectionOfTStructureTemplate { get { return "Global.System.Collections.Generic.ICollection(Of {0})"; } } + internal override string DataServiceCollectionStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceCollection(Of {0})"; } } + internal override string DataServiceQueryStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceQuery(Of {0})"; } } + internal override string DataServiceQuerySingleStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceQuerySingle(Of {0})"; } } + internal override string ObservableCollectionStructureTemplate { get { return "Global.System.Collections.ObjectModel.ObservableCollection(Of {0})"; } } + internal override string ObjectModelCollectionStructureTemplate { get { return "Global.System.Collections.ObjectModel.Collection(Of {0})"; } } + internal override string DataServiceCollectionConstructorParameters { get { return "(Nothing, Global.Microsoft.OData.Client.TrackingMode.None)"; } } + internal override string NewModifier { get { return "New "; } } + internal override string GeoTypeInitializePattern { get { return "Global.Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(False).Read(Of {0})(New Global.System.IO.StringReader(\"{1}\"))"; } } + internal override string Int32TypeName { get { return "Integer"; } } + internal override string StringTypeName { get { return "String"; } } + internal override string BinaryTypeName { get { return "Byte()"; } } + internal override string DecimalTypeName { get { return "Decimal"; } } + internal override string Int16TypeName { get { return "Short"; } } + internal override string SingleTypeName { get { return "Single"; } } + internal override string BooleanTypeName { get { return "Boolean"; } } + internal override string DoubleTypeName { get { return "Double"; } } + internal override string GuidTypeName { get { return "Global.System.Guid"; } } + internal override string ByteTypeName { get { return "Byte"; } } + internal override string Int64TypeName { get { return "Long"; } } + internal override string SByteTypeName { get { return "SByte"; } } + internal override string DataServiceStreamLinkTypeName { get { return "Global.Microsoft.OData.Client.DataServiceStreamLink"; } } + internal override string GeographyTypeName { get { return "Global.Microsoft.Spatial.Geography"; } } + internal override string GeographyPointTypeName { get { return "Global.Microsoft.Spatial.GeographyPoint"; } } + internal override string GeographyLineStringTypeName { get { return "Global.Microsoft.Spatial.GeographyLineString"; } } + internal override string GeographyPolygonTypeName { get { return "Global.Microsoft.Spatial.GeographyPolygon"; } } + internal override string GeographyCollectionTypeName { get { return "Global.Microsoft.Spatial.GeographyCollection"; } } + internal override string GeographyMultiPolygonTypeName { get { return "Global.Microsoft.Spatial.GeographyMultiPolygon"; } } + internal override string GeographyMultiLineStringTypeName { get { return "Global.Microsoft.Spatial.GeographyMultiLineString"; } } + internal override string GeographyMultiPointTypeName { get { return "Global.Microsoft.Spatial.GeographyMultiPoint"; } } + internal override string GeometryTypeName { get { return "Global.Microsoft.Spatial.Geometry"; } } + internal override string GeometryPointTypeName { get { return "Global.Microsoft.Spatial.GeometryPoint"; } } + internal override string GeometryLineStringTypeName { get { return "Global.Microsoft.Spatial.GeometryLineString"; } } + internal override string GeometryPolygonTypeName { get { return "Global.Microsoft.Spatial.GeometryPolygon"; } } + internal override string GeometryCollectionTypeName { get { return "Global.Microsoft.Spatial.GeometryCollection"; } } + internal override string GeometryMultiPolygonTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPolygon"; } } + internal override string GeometryMultiLineStringTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiLineString"; } } + internal override string GeometryMultiPointTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPoint"; } } + internal override string DateTypeName { get { return "Global.Microsoft.OData.Edm.Date"; } } + internal override string DateTimeOffsetTypeName { get { return "Global.System.DateTimeOffset"; } } + internal override string DurationTypeName { get { return "Global.System.TimeSpan"; } } + internal override string TimeOfDayTypeName { get { return "Global.Microsoft.OData.Edm.TimeOfDay"; } } + internal override string XmlConvertClassName { get { return "Global.System.Xml.XmlConvert"; } } + internal override string EnumTypeName { get { return "Global.System.Enum"; } } + internal override string FixPattern { get { return "[{0}]"; } } + internal override string EnumUnderlyingTypeMarker { get { return " As "; } } + internal override string ConstantExpressionConstructorWithType { get { return "Global.System.Linq.Expressions.Expression.Constant({0}, GetType({1}))"; } } + internal override string TypeofFormatter { get { return "GetType({0})"; } } + internal override string UriOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; } } + internal override string UriEntityOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; } } + internal override string BodyOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; } } + internal override string BaseEntityType { get { return "\r\n Inherits Global.Microsoft.OData.Client.BaseEntityType"; } } + internal override string OverloadsModifier { get { return "Overloads "; } } + internal override string ODataVersion { get { return "Global.Microsoft.OData.ODataVersion.V4"; } } + internal override string ParameterDeclarationTemplate { get { return "{1} As {0}"; } } + internal override string DictionaryItemConstructor { get { return "{{ {0}, {1} }}"; } } + internal override HashSet LanguageKeywords { get { + if (VBKeywords == null) + { + VBKeywords = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "AddHandler", "AddressOf", "Alias", "And", "AndAlso", "As", "Boolean", "ByRef", "Byte", "ByVal", + "Call", "Case", "Catch", "CBool", "", "CByte", "CChar", "CDate", "CDbl", "CDec", "Char", + "CInt", "Class", "CLng", "CObj", "Const", "Continue", "CSByte", "CShort", "CSng", "CStr", + "CType", "CUInt", "CULng", "CUShort", "Date", "Decimal", "Declare", "Default", "Delegate", "Dim", + "DirectCast", "Do", "Double", "Each", "Else", "ElseIf", "End", "EndIf", "Enum", "Erase", + "Error", "Event", "Exit", "False", "Finally", "For", "Friend", "Function", "Get", "GetType", + "GetXMLNamespace", "Global", "GoSub", "GoTo", "Handles", "If", "Implements", "Imports", "In", "Inherits", + "Integer", "Interface", "Is", "IsNot", "Let", "Lib", "Like", "Long", "Loop", "Me", + "Mod", "Module", "MustInherit", "MustOverride", "MyBase", "MyClass", "Namespace", "Narrowing", "New", "Next", + "Not", "Nothing", "NotInheritable", "NotOverridable", "Object", "Of", "On", "Operator", "Option", "Optional", + "Or", "OrElse", "Out", "Overloads", "Overridable", "Overrides", "ParamArray", "Partial", "Private", "Property", + "Protected", "Public", "RaiseEvent", "ReadOnly", "ReDim", "REM", "RemoveHandler", "Resume", "Return", "SByte", + "Select", "Set", "Shadows", "Shared", "Short", "Single", "Static", "Step", "Stop", "String", + "Structure", "Sub", "SyncLock", "Then", "Throw", "To", "True", "Try", "TryCast", "TypeOf", + "UInteger", "ULong", "UShort", "Using", "Variant", "Wend", "When", "While", "Widening", "With", + "WithEvents", "WriteOnly", "Xor" + }; + } + return VBKeywords; + } } + private HashSet VBKeywords; + + internal override void WriteFileHeader() + { + +this.Write("\'------------------------------------------------------------------------------\r\n" + + "\' \r\n\' This code was generated by a tool.\r\n\' Runtime Vers" + + "ion:"); + +this.Write(this.ToStringHelper.ToStringWithCulture(Environment.Version)); + +this.Write(@" +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict Off +Option Explicit On + + +'Generation date: "); + +this.Write(this.ToStringHelper.ToStringWithCulture(DateTime.Now.ToString(System.Globalization.CultureInfo.CurrentCulture))); + +this.Write("\r\n"); + + + } + + internal override void WriteNamespaceStart(string fullNamespace) + { + +this.Write("Namespace "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("\r\n"); + + + } + + internal override void WriteClassStartForEntityContainer(string originalContainerName, string containerName, string fixedContainerName) + { + +this.Write(" \'\'\'\r\n \'\'\'There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(containerName)); + +this.Write(" in the schema.\r\n \'\'\'\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Partial Public Class "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fixedContainerName)); + +this.Write("\r\n Inherits Global.Microsoft.OData.Client.DataServiceContext\r\n"); + + + } + + internal override void WriteMethodStartForEntityContainerConstructor(string containerName, string fixedContainerName) + { + +this.Write(" \'\'\'\r\n \'\'\'Initialize a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(containerName)); + +this.Write(" object.\r\n \'\'\'\r\n _\r\n Public Sub New(ByVal serviceRoot As Global.System.Uri)\r\n " + + " MyBase.New(serviceRoot, Global.Microsoft.OData.Client.ODataProtocolVersion.V4" + + ")\r\n"); + + + } + + internal override void WriteKeyAsSegmentUrlConvention() + { + +this.Write(" Me.UrlKeyDelimiter = Global.Microsoft.OData.Client.DataServiceUrlKeyD" + + "elimiter.Slash\r\n"); + + + } + + internal override void WriteInitializeResolveName() + { + +this.Write(" Me.ResolveName = AddressOf Me.ResolveNameFromType\r\n"); + + + } + + internal override void WriteInitializeResolveType() + { + +this.Write(" Me.ResolveType = AddressOf Me.ResolveTypeFromName\r\n"); + + + } + + internal override void WriteClassEndForEntityContainerConstructor() + { + +this.Write(" Me.OnContextCreated\r\n Me.Format.LoadServiceModel = Address" + + "Of GeneratedEdmModel.GetInstance\r\n Me.Format.UseJson()\r\n End S" + + "ub\r\n Partial Private Sub OnContextCreated()\r\n End Sub\r\n"); + + + } + + internal override void WriteMethodStartForResolveTypeFromName() + { + +this.Write(@" ''' + '''Since the namespace configured for this service reference + '''in Visual Studio is different from the one indicated in the + '''server schema, use type-mappers to map between the two. + ''' + _\r\n Protected Function ResolveTypeFromName(ByVal typeName As String) " + + "As Global.System.Type\r\n"); + + + } + + internal override void WriteResolveNamespace(string typeName, string fullNamespace, string languageDependentNamespace) + { + if (!string.IsNullOrEmpty(typeName)) + { + +this.Write(" Dim resolvedType As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write("= Me.DefaultResolveType(typeName, \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("\", String.Concat(ROOTNAMESPACE, \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(languageDependentNamespace)); + +this.Write("\"))\r\n"); + + + } + else + { + +this.Write(" resolvedType = Me.DefaultResolveType(typeName, \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("\", String.Concat(ROOTNAMESPACE, \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(languageDependentNamespace)); + +this.Write("\"))\r\n"); + + + } + +this.Write(" If (Not (resolvedType) Is Nothing) Then\r\n Return resol" + + "vedType\r\n End If\r\n"); + + + } + + internal override void WriteMethodEndForResolveTypeFromName() + { + +this.Write(" Return Nothing\r\n End Function\r\n"); + + + } + + internal override void WritePropertyRootNamespace(string containerName, string fullNamespace) + { + +this.Write(" _\r\n Private Shared ROOTNAMESPACE As String = GetType("); + +this.Write(this.ToStringHelper.ToStringWithCulture(containerName)); + +this.Write(").Namespace.Remove(GetType("); + +this.Write(this.ToStringHelper.ToStringWithCulture(containerName)); + +this.Write(").Namespace.LastIndexOf(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("\"))\r\n"); + + + } + + internal override void WriteMethodStartForResolveNameFromType(string containerName, string fullNamespace) + { + +this.Write(@" ''' + '''Since the namespace configured for this service reference + '''in Visual Studio is different from the one indicated in the + '''server schema, use type-mappers to map between the two. + ''' + _\r\n Protected Function ResolveNameFromType(ByVal clientType As Global" + + ".System.Type) As String\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(@" Dim originalNameAttribute As Global.Microsoft.OData.Client.OriginalNameAttribute = + CType(Global.System.Linq.Enumerable.SingleOrDefault(Global.Microsoft.OData.Client.Utility.GetCustomAttributes(clientType, GetType(Global.Microsoft.OData.Client.OriginalNameAttribute), true)), Global.Microsoft.OData.Client.OriginalNameAttribute) +"); + + + } + } + + internal override void WriteResolveType(string fullNamespace, string languageDependentNamespace) + { + +this.Write(" If clientType.Namespace.Equals(String.Concat(ROOTNAMESPACE, \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(languageDependentNamespace)); + +this.Write("\"), Global.System.StringComparison.OrdinalIgnoreCase) Then\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" If (Not (originalNameAttribute) Is Nothing) Then\r\n " + + " Return String.Concat(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write(".\", originalNameAttribute.OriginalName)\r\n End If\r\n"); + + + } + +this.Write(" Return String.Concat(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write(".\", clientType.Name)\r\n End If\r\n"); + + + } + + internal override void WriteMethodEndForResolveNameFromType(bool modelHasInheritance) + { + if (this.context.EnableNamingAlias && modelHasInheritance) + { + +this.Write(@" If (Not (originalNameAttribute) Is Nothing) Then + Dim fullName As String = clientType.FullName.Substring(ROOTNAMESPACE.Length) + Return fullName.Remove(fullName.LastIndexOf(clientType.Name)) + originalNameAttribute.OriginalName + End If +"); + + + } + +this.Write(" Return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(modelHasInheritance ? "clientType.FullName.Substring(ROOTNAMESPACE.Length)" : "Nothing")); + +this.Write("\r\n End Function\r\n"); + + + } + + internal override void WriteConstructorForSingleType(string singleTypeName, string baseTypeName) + { + +this.Write(" \'\'\' \r\n \'\'\' Initialize a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write(@" object. + ''' + Public Sub New(ByVal context As Global.Microsoft.OData.Client.DataServiceContext, ByVal path As String) + MyBase.New(context, path) + End Sub + + ''' + ''' Initialize a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write(@" object. + ''' + Public Sub New(ByVal context As Global.Microsoft.OData.Client.DataServiceContext, ByVal path As String, ByVal isComposable As Boolean) + MyBase.New(context, path, isComposable) + End Sub + + ''' + ''' Initialize a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singleTypeName)); + +this.Write(" object.\r\n \'\'\' \r\n Public Sub New(ByVal query As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write(")\r\n MyBase.New(query)\r\n End Sub\r\n"); + + + } + + internal override void WriteContextEntitySetProperty(string entitySetName, string entitySetFixedName, string originalEntitySetName, string entitySetElementTypeName, bool inContext) + { + +this.Write(" \'\'\'\r\n \'\'\'There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" in the schema.\r\n \'\'\'\r\n _\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public ReadOnly Property "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetFixedName)); + +this.Write("() As Global.Microsoft.OData.Client.DataServiceQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetElementTypeName)); + +this.Write(")\r\n Get\r\n"); + + + if (!inContext) + { + +this.Write(" If Not Me.IsComposable Then\r\n Throw New Global" + + ".System.NotSupportedException(\"The previous function is not composable.\")\r\n " + + " End If\r\n"); + + + } + +this.Write(" If (Me._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" Is Nothing) Then\r\n Me._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" = "); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "MyBase" : "Context")); + +this.Write(".CreateQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetElementTypeName)); + +this.Write(")("); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "\"" + originalEntitySetName + "\"" : "GetPath(\"" + originalEntitySetName + "\")")); + +this.Write(")\r\n End If\r\n Return Me._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write("\r\n End Get\r\n End Property\r\n _\r\n Private _"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" As Global.Microsoft.OData.Client.DataServiceQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetElementTypeName)); + +this.Write(")\r\n"); + + + } + + internal override void WriteContextSingletonProperty(string singletonName, string singletonFixedName, string originalSingletonName, string singletonElementTypeName, bool inContext) + { + +this.Write(" \'\'\'\r\n \'\'\'There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(" in the schema.\r\n \'\'\'\r\n _\r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public ReadOnly Property "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonFixedName)); + +this.Write("() As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonElementTypeName)); + +this.Write("\r\n Get\r\n"); + + + if (!inContext) + { + +this.Write(" If Not Me.IsComposable Then\r\n Throw New Global" + + ".System.NotSupportedException(\"The previous function is not composable.\")\r\n " + + " End If\r\n"); + + + } + +this.Write(" If (Me._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(" Is Nothing) Then\r\n Me._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(" = New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonElementTypeName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "Me" : "Me.Context")); + +this.Write(", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(inContext ? "\"" + originalSingletonName + "\"" : "GetPath(\"" + originalSingletonName + "\")")); + +this.Write(")\r\n End If\r\n Return Me._"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write("\r\n End Get\r\n End Property\r\n _\r\n Private _"); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonName)); + +this.Write(" As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(singletonElementTypeName)); + +this.Write("\r\n"); + + + } + + internal override void WriteContextAddToEntitySetMethod(string entitySetName, string originalEntitySetName, string typeName, string parameterName) + { + +this.Write(" \'\'\'\r\n \'\'\'There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write(" in the schema.\r\n \'\'\'\r\n _\r\n Public Sub AddTo"); + +this.Write(this.ToStringHelper.ToStringWithCulture(entitySetName)); + +this.Write("(ByVal "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(" As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(")\r\n MyBase.AddObject(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalEntitySetName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(")\r\n End Sub\r\n"); + + + } + + internal override void WriteGeneratedEdmModel(string escapedEdmxString) + { + escapedEdmxString = escapedEdmxString.Replace("\r\n", "\" & _\r\n \""); + +this.Write(" _\r\n Private MustInherit Class GeneratedEdmModel\r\n"); + + + if (this.context.ReferencesMap != null) + { + +this.Write(" _\r\n Private Shared ReferencesMap As Global.System.Collections.Gen" + + "eric.Dictionary(Of String, String) = New Global.System.Collections.Generic.Dicti" + + "onary(Of String, String) From\r\n {\r\n"); + + + int count = this.context.ReferencesMap.Count(); + foreach(var reference in this.context.ReferencesMap) + { + +this.Write(" {\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(reference.Key.OriginalString.Replace("\"", "\"\""))); + +this.Write("\", \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(Utils.SerializeToString(reference.Value).Replace("\"", "\"\"").Replace("\r\n", "\" & _\r\n \""))); + +this.Write("\"}"); + +this.Write(this.ToStringHelper.ToStringWithCulture((--count>0?",":""))); + +this.Write("\r\n"); + + + } + +this.Write(" }\r\n"); + + + } + +this.Write(" _\r\n Private Shared ParsedModel As Global.Microsoft.OData.Edm.IEdm" + + "Model = LoadModelFromString\r\n _\r\n Private Const Edmx As String = \""); + +this.Write(this.ToStringHelper.ToStringWithCulture(escapedEdmxString)); + +this.Write("\"\r\n _\r\n Public Shared Function GetInstance() As Global.Microsoft.ODat" + + "a.Edm.IEdmModel\r\n Return ParsedModel\r\n End Function\r\n"); + + + if (this.context.ReferencesMap != null) + { + +this.Write(" _ + Private Shared Function getReferencedModelFromMap(ByVal uri As Global.System.Uri) As Global.System.Xml.XmlReader + Dim referencedEdmx As String = Nothing + If (ReferencesMap.TryGetValue(uri.OriginalString, referencedEdmx)) Then + Return CreateXmlReader(referencedEdmx) + End If + Return Nothing + End Function + _ + Private Shared Function LoadModelFromString() As Global.Microsoft.OData.Edm.IEdmModel + Dim reader As Global.System.Xml.XmlReader = CreateXmlReader(Edmx) + Try + Return Global.Microsoft.OData.Edm.Csdl.CsdlReader.Parse(reader, AddressOf getReferencedModelFromMap) + Finally + CType(reader,Global.System.IDisposable).Dispose + End Try + End Function +"); + + + } + else + { + +this.Write(" _ + Private Shared Function LoadModelFromString() As Global.Microsoft.OData.Edm.IEdmModel + Dim reader As Global.System.Xml.XmlReader = CreateXmlReader(Edmx) + Try + Return Global.Microsoft.OData.Edm.Csdl.CsdlReader.Parse(reader) + Finally + CType(reader,Global.System.IDisposable).Dispose + End Try + End Function +"); + + + } + +this.Write(" _ + Private Shared Function CreateXmlReader(ByVal edmxToParse As String) As Global.System.Xml.XmlReader + Return Global.System.Xml.XmlReader.Create(New Global.System.IO.StringReader(edmxToParse)) + End Function + End Class +"); + + + } + + internal override void WriteClassEndForEntityContainer() + { + +this.Write(" End Class\r\n"); + + + } + + internal override void WriteSummaryCommentForStructuredType(string typeName) + { + +this.Write(" \'\'\'\r\n \'\'\'There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(" in the schema.\r\n \'\'\'\r\n"); + + + } + + internal override void WriteKeyPropertiesCommentAndAttribute(IEnumerable keyProperties, string keyString) + { + +this.Write(" \'\'\'\r\n"); + + + foreach (string key in keyProperties) + { + +this.Write(" \'\'\'"); + +this.Write(this.ToStringHelper.ToStringWithCulture(key)); + +this.Write("\r\n"); + + + } + +this.Write(" \'\'\'\r\n _\r\n"); + + + } + + internal override void WriteEntityTypeAttribute() + { + +this.Write(" _\r\n"); + + + } + + internal override void WriteEntitySetAttribute(string entitySetName) + { + +this.Write(" _\r\n"); + + + } + + internal override void WriteEntityHasStreamAttribute() + { + +this.Write(" _\r\n"); + + + } + + internal override void WriteClassStartForStructuredType(string abstractModifier, string typeName, string originalTypeName, string baseTypeName) + { + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Partial Public"); + +this.Write(this.ToStringHelper.ToStringWithCulture(abstractModifier)); + +this.Write(" Class "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write("\r\n"); + + + } + + internal override void WriteSummaryCommentForStaticCreateMethod(string typeName) + { + +this.Write(" \'\'\'\r\n \'\'\'Create a new "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(" object.\r\n \'\'\'\r\n"); + + + } + + internal override void WriteParameterCommentForStaticCreateMethod(string parameterName, string propertyName) + { + +this.Write(" \'\'\'Initial value of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write(".\r\n"); + + + } + + internal override void WriteDeclarationStartForStaticCreateMethod(string typeName, string fixedTypeName) + { + +this.Write(" _\r\n Public Shared Function Create"); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write("("); + + + + } + + internal override void WriteParameterForStaticCreateMethod(string parameterTypeName, string parameterName, string parameterSeparater) + { + +this.Write("ByVal "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(" As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterTypeName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterSeparater)); + + + } + + internal override void WriteDeclarationEndForStaticCreateMethod(string typeName, string instanceName) + { + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write("\r\n Dim "); + +this.Write(this.ToStringHelper.ToStringWithCulture(instanceName)); + +this.Write(" As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write(" = New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(typeName)); + +this.Write("()\r\n"); + + + } + + internal override void WriteParameterNullCheckForStaticCreateMethod(string parameterName) + { + +this.Write(" If ("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write(" Is Nothing) Then\r\n Throw New Global.System.ArgumentNullException(" + + "\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write("\")\r\n End If\r\n"); + + + } + + internal override void WritePropertyValueAssignmentForStaticCreateMethod(string instanceName, string propertyName, string parameterName) + { + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(instanceName)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write(" = "); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameterName)); + +this.Write("\r\n"); + + + } + + internal override void WriteMethodEndForStaticCreateMethod(string instanceName) + { + +this.Write(" Return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(instanceName)); + +this.Write("\r\n End Function\r\n"); + + + } + + internal override void WritePropertyForStructuredType(string propertyType, string originalPropertyName, string propertyName, string fixedPropertyName, string privatePropertyName, string propertyInitializationValue, bool writeOnPropertyChanged) + { + +this.Write(" \'\'\'\r\n \'\'\'There are no comments for Property "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write(" in the schema.\r\n \'\'\'\r\n _\r\n"); + + + if (this.context.EnableNamingAlias || IdentifierMappings.ContainsKey(originalPropertyName)) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Property "); + +this.Write(this.ToStringHelper.ToStringWithCulture(fixedPropertyName)); + +this.Write("() As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyType)); + +this.Write("\r\n Get\r\n Return Me."); + +this.Write(this.ToStringHelper.ToStringWithCulture(privatePropertyName)); + +this.Write("\r\n End Get\r\n Set\r\n Me.On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changing(value)\r\n Me."); + +this.Write(this.ToStringHelper.ToStringWithCulture(privatePropertyName)); + +this.Write(" = value\r\n Me.On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changed\r\n"); + + + if (writeOnPropertyChanged) + { + +this.Write(" Me.OnPropertyChanged(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalPropertyName)); + +this.Write("\")\r\n"); + + + } + +this.Write(" End Set\r\n End Property\r\n _\r\n"); + + + string constructorString = string.Empty; + if (!string.IsNullOrEmpty(propertyInitializationValue)) + { + constructorString = " = " + propertyInitializationValue; + } + +this.Write(" Private "); + +this.Write(this.ToStringHelper.ToStringWithCulture(privatePropertyName)); + +this.Write(" As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyType)); + +this.Write(this.ToStringHelper.ToStringWithCulture(constructorString)); + +this.Write("\r\n Partial Private Sub On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changing(ByVal value As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyType)); + +this.Write(")\r\n End Sub\r\n Partial Private Sub On"); + +this.Write(this.ToStringHelper.ToStringWithCulture(propertyName)); + +this.Write("Changed()\r\n End Sub\r\n"); + + + } + + internal override void WriteINotifyPropertyChangedImplementation() + { + +this.Write(" \'\'\' \r\n \'\'\' This event is raised when the value of the pro" + + "perty is changed\r\n \'\'\' \r\n _ + Public Event PropertyChanged As Global.System.ComponentModel.PropertyChangedEventHandler Implements Global.System.ComponentModel.INotifyPropertyChanged.PropertyChanged + ''' + ''' The value of the property is changed + ''' + ''' property name + _ + Protected Overridable Sub OnPropertyChanged(ByVal [property] As String) + If (Not (Me.PropertyChangedEvent) Is Nothing) Then + RaiseEvent PropertyChanged(Me, New Global.System.ComponentModel.PropertyChangedEventArgs([property])) + End If + End Sub +"); + + + } + + internal override void WriteClassEndForStructuredType() + { + +this.Write(" End Class\r\n"); + + + } + + internal override void WriteEnumFlags() + { + +this.Write(" \r\n"); + + + } + + internal override void WriteSummaryCommentForEnumType(string enumName) + { + +this.Write(" \'\'\'\r\n \'\'\'There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(enumName)); + +this.Write(" in the schema.\r\n \'\'\'\r\n"); + + + } + + internal override void WriteEnumDeclaration(string enumName, string originalEnumName, string underlyingType) + { + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Enum "); + +this.Write(this.ToStringHelper.ToStringWithCulture(enumName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(underlyingType)); + +this.Write("\r\n"); + + + } + + internal override void WriteMemberForEnumType(string member, string originalMemberName, bool last) + { + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(member)); + +this.Write("\r\n"); + + + } + + internal override void WriteEnumEnd() + { + +this.Write(" End Enum\r\n"); + + + } + + internal override void WriteFunctionImportReturnCollectionResult(string functionName, string originalFunctionName, string returnTypeName, string parameters, string parameterValues, bool isComposable, bool useEntityReference) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty)); + +this.Write(") As Global.Microsoft.OData.Client.DataServiceQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(")\r\n Return Me.CreateFunctionQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(")(\"\", \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable)); + +this.Write(" "); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")\r\n End Function\r\n"); + + + } + + internal override void WriteFunctionImportReturnSingleResult(string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty)); + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName))); + +this.Write("\r\n Return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? "New " + returnTypeNameWithSingleSuffix + "(" : string.Empty)); + +this.Write("Me.CreateFunctionQuerySingle("); + +this.Write(this.ToStringHelper.ToStringWithCulture("Of " + returnTypeName)); + +this.Write(")(\"\", \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? ")" : string.Empty)); + +this.Write("\r\n End Function\r\n"); + + + } + + internal override void WriteBoundFunctionInEntityTypeReturnCollectionResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(hideBaseMethod ? this.OverloadsModifier : string.Empty)); + +this.Write("Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty)); + +this.Write(") As Global.Microsoft.OData.Client.DataServiceQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(")\r\n Dim requestUri As Global.System.Uri = Nothing\r\n Context" + + ".TryGetUri(Me, requestUri)\r\n Return Me.Context.CreateFunctionQuery(Of" + + " "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(")(\"\", String.Join(\"/\", Global.System.Linq.Enumerable.Select(Global.System.Linq.En" + + "umerable.Skip(requestUri.Segments, Me.Context.BaseUri.Segments.Length), Function" + + "(s) s.Trim(\"/\"C))) + \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")\r\n End Function\r\n"); + + + } + + internal override void WriteBoundFunctionInEntityTypeReturnSingleResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(hideBaseMethod ? this.OverloadsModifier : string.Empty)); + +this.Write("Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty)); + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName))); + +this.Write("\r\n Dim requestUri As Global.System.Uri = Nothing\r\n Context." + + "TryGetUri(Me, requestUri)\r\n Return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? "New " + returnTypeNameWithSingleSuffix + "(" : string.Empty)); + +this.Write("Me.Context.CreateFunctionQuerySingle("); + +this.Write(this.ToStringHelper.ToStringWithCulture("Of " + returnTypeName)); + +this.Write(")(String.Join(\"/\", Global.System.Linq.Enumerable.Select(Global.System.Linq.Enumer" + + "able.Skip(requestUri.Segments, Me.Context.BaseUri.Segments.Length), Function(s) " + + "s.Trim(\"/\"C))), \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? ")" : string.Empty)); + +this.Write("\r\n End Function\r\n"); + + + } + + internal override void WriteActionImport(string actionName, string originalActionName, string returnTypeName, string parameters, string parameterValues) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("\r\n Return New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(Me, Me.BaseUri.OriginalString.Trim(\"/\"C) + \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")\r\n End Function\r\n"); + + + } + + internal override void WriteBoundActionInEntityType(bool hideBaseMethod, string actionName, string originalActionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public "); + +this.Write(this.ToStringHelper.ToStringWithCulture(hideBaseMethod ? this.OverloadsModifier : string.Empty)); + +this.Write("Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write("("); + +this.Write(this.ToStringHelper.ToStringWithCulture(parameters)); + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(@" + Dim resource As Global.Microsoft.OData.Client.EntityDescriptor = Context.EntityTracker.TryGetEntityDescriptor(Me) + If resource Is Nothing Then + Throw New Global.System.Exception(""cannot find entity"") + End If + + Return New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(Me.Context, resource.EditLink.OriginalString.Trim(\"/\"C) + \"/"); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")\r\n End Function\r\n"); + + + } + + internal override void WriteExtensionMethodsStart() + { + +this.Write(" \'\'\' \r\n \'\'\' Class containing all extension methods\r\n \'\'\' \r\n Public Module ExtensionMethods\r\n"); + + + } + + internal override void WriteExtensionMethodsEnd() + { + +this.Write(" End Module\r\n"); + + + } + + internal override void WriteByKeyMethods(string entityTypeName, string returnTypeName, IEnumerable keys, string keyParameters, string keyDictionaryItems) + { + +this.Write(" \'\'\' \r\n \'\'\' Get an entity of type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write(" as "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(@" specified by key from an entity set + ''' + ''' source entity set + ''' dictionary with the names and values of keys + + Public Function ByKey(ByVal source As Global.Microsoft.OData.Client.DataServiceQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write("), ByVal keys As Global.System.Collections.Generic.Dictionary(Of String, Object))" + + " As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("\r\n Return New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, source.GetKeyPath(Global.Microsoft.OData.Client.Serializer.GetKe" + + "yString(source.Context, keys)))\r\n End Function\r\n \'\'\' \r\n " + + " \'\'\' Get an entity of type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write(" as "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(" specified by key from an entity set\r\n \'\'\' \r\n \'\'\' source entity set\r\n"); + + + foreach (var key in keys) + { + +this.Write(" \'\'\' The value of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(key)); + +this.Write("\r\n"); + + + } + +this.Write(" \r\n Public Func" + + "tion ByKey(ByVal source As Global.Microsoft.OData.Client.DataServiceQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(entityTypeName)); + +this.Write("),\r\n "); + +this.Write(this.ToStringHelper.ToStringWithCulture(keyParameters)); + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("\r\n Dim keys As Global.System.Collections.Generic.Dictionary(Of String," + + " Object) = New Global.System.Collections.Generic.Dictionary(Of String, Object)()" + + " From\r\n {\r\n "); + +this.Write(this.ToStringHelper.ToStringWithCulture(keyDictionaryItems)); + +this.Write("\r\n }\r\n Return New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, source.GetKeyPath(Global.Microsoft.OData.Client.Serializer.GetKe" + + "yString(source.Context, keys)))\r\n End Function\r\n"); + + + } + + internal override void WriteCastToMethods(string baseTypeName, string derivedTypeName, string derivedTypeFullName, string returnTypeName) + { + +this.Write(" \'\'\' \r\n \'\'\' Cast an entity of type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write(" to its derived type "); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeFullName)); + +this.Write("\r\n \'\'\' \r\n \'\'\' source entity\r" + + "\n \r\n Public Fu" + + "nction CastTo"); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeName)); + +this.Write("(ByVal source As Global.Microsoft.OData.Client.DataServiceQuerySingle(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(baseTypeName)); + +this.Write(")) As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("\r\n Dim query As Global.Microsoft.OData.Client.DataServiceQuerySingle(O" + + "f "); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeFullName)); + +this.Write(") = source.CastTo(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(derivedTypeFullName)); + +this.Write(")()\r\n Return New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, query.GetPath(Nothing))\r\n End Function\r\n"); + + + } + + internal override void WriteBoundFunctionReturnSingleResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("(ByVal source As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(boundTypeName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty)); + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName))); + +this.Write("\r\n If Not source.IsComposable Then\r\n Throw New Global.S" + + "ystem.NotSupportedException(\"The previous function is not composable.\")\r\n " + + " End If\r\n \r\n Return "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? "New " + returnTypeNameWithSingleSuffix + "(" : string.Empty)); + +this.Write("source.CreateFunctionQuerySingle("); + +this.Write(this.ToStringHelper.ToStringWithCulture("Of " + returnTypeName)); + +this.Write(")(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(isReturnEntity ? ")" : string.Empty)); + +this.Write("\r\n End Function\r\n"); + + + } + + internal override void WriteBoundFunctionReturnCollectionResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(functionName)); + +this.Write("(ByVal source As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(boundTypeName)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters)); + +this.Write(this.ToStringHelper.ToStringWithCulture(useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty)); + +this.Write(") As Global.Microsoft.OData.Client.DataServiceQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(")\r\n If Not source.IsComposable Then\r\n Throw New Global." + + "System.NotSupportedException(\"The previous function is not composable.\")\r\n " + + " End If\r\n \r\n Return source.CreateFunctionQuery(Of "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write(")(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalFunctionName)); + +this.Write("\", "); + +this.Write(this.ToStringHelper.ToStringWithCulture(isComposable)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")\r\n End Function\r\n"); + + + } + + internal override void WriteBoundActionAsExtension(string actionName, string originalActionName, string boundSourceType, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { + +this.Write(" \'\'\' \r\n \'\'\' There are no comments for "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write(" in the schema.\r\n \'\'\' \r\n \r\n"); + + + if (this.context.EnableNamingAlias) + { + +this.Write(" _\r\n"); + + + } + +this.Write(" Public Function "); + +this.Write(this.ToStringHelper.ToStringWithCulture(actionName)); + +this.Write("(ByVal source As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(boundSourceType)); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters)); + +this.Write(") As "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("\r\n If Not source.IsComposable Then\r\n Throw New Global.S" + + "ystem.NotSupportedException(\"The previous function is not composable.\")\r\n " + + " End If\r\n Return New "); + +this.Write(this.ToStringHelper.ToStringWithCulture(returnTypeName)); + +this.Write("(source.Context, source.AppendRequestUri(\""); + +this.Write(this.ToStringHelper.ToStringWithCulture(fullNamespace)); + +this.Write("."); + +this.Write(this.ToStringHelper.ToStringWithCulture(originalActionName)); + +this.Write("\")"); + +this.Write(this.ToStringHelper.ToStringWithCulture(string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues)); + +this.Write(")\r\n End Function\r\n"); + + + } + + internal override void WriteNamespaceEnd() + { + +this.Write("End Namespace\r\n"); + + + } +} + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "15.0.0.0")] + internal class ODataT4CodeGeneratorBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.tt b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.tt new file mode 100644 index 0000000..0ac2b1f --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.tt @@ -0,0 +1,79 @@ +<#@ include file="ODataT4CodeGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The URI of the metadata document. The value must be set to a valid service document URI or a local file path + // eg : "http://services.odata.org/V4/OData/OData.svc/", "File:///C:/Odata.edmx", or @"C:\Odata.edmx" + // ### Notice ### If the OData service requires authentication for accessing the metadata document, the value of + // MetadataDocumentUri has to be set to a local file path, or the client code generation process will fail. + public const string MetadataDocumentUri = ""; + + // The use of DataServiceCollection enables entity and property tracking. The value must be set to true or false. + public const bool UseDataServiceCollection = true; + + // The namespace of the client code generated. It replaces the original namespace in the metadata document, + // unless the model has several namespaces. + public const string NamespacePrefix = "$rootnamespace$"; + + // The target language of the generated client code. The value must be set to "CSharp" or "VB". + public const string TargetLanguage = "OutputLanguage"; + + // The path for the temporary file where the metadata xml document can be stored. Use this if your metadata is too big to be stored in a string literal. Ensure that you have write permission for this path. + // For example - "C:\\temp\\Test.xml" + public const string TempFilePath = ""; + + // This flag indicates whether to enable naming alias. The value must be set to true or false. + public const bool EnableNamingAlias = true; + + // This flag indicates whether to ignore unexpected elements and attributes in the metadata document and generate + // the client code if any. The value must be set to true or false. + public const bool IgnoreUnexpectedElementsAndAttributes = true; +} + +public static class Customization +{ + /// + /// Changes the text to use upper camel case, which upper case for the first character. + /// + /// Text to convert. + /// The converted text in upper camel case + internal static string CustomizeNaming(string text) + { + if (string.IsNullOrEmpty(text)) + { + return text; + } + + if (text.Length == 1) + { + return Char.ToUpperInvariant(text[0]).ToString(CultureInfo.InvariantCulture); + } + + return Char.ToUpperInvariant(text[0]) + text.Substring(1); + } + + /// + /// Changes the namespace to use upper camel case, which upper case for the first character of all segments. + /// + /// Namespace to convert. + /// The converted namespace in upper camel case + internal static string CustomizeNamespace(string fullNamespace) + { + if (string.IsNullOrEmpty(fullNamespace)) + { + return fullNamespace; + } + + string[] segs = fullNamespace.Split('.'); + string upperNamespace = string.Empty; + int n = segs.Length; + for (int i = 0; i < n; ++i) + { + upperNamespace += Customization.CustomizeNaming(segs[i]); + upperNamespace += (i == n - 1 ? string.Empty : "."); + } + + return upperNamespace; + } +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.ttinclude b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.ttinclude new file mode 100644 index 0000000..1d7f154 --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.ttinclude @@ -0,0 +1,5188 @@ +<# +/* +OData Client T4 Template ver. #VersionNumber# +Copyright (c) Microsoft Corporation +All rights reserved. +MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#> +<#@ template debug="true" hostSpecific="true" visibility="internal" linePragmas="false"#> +<#@ output extension=".cs" #> +<#@ Assembly Name="System.Core.dll" #> +<#@ Assembly Name="System.IO.dll" #> +<#@ Assembly Name="System.Runtime.dll" #> +<#@ Assembly Name="System.Xml.dll" #> +<#@ Assembly Name="System.Xml.Linq.dll" #> +<#@ Assembly Name="System.Xml.ReaderWriter.dll" #> +<#@ Assembly Name="System.Windows.Forms.dll" #> +<#@ Assembly Name="Microsoft.OData.Client.dll" #> +<#@ Assembly Name="Microsoft.OData.Core.dll" #> +<#@ Assembly Name="Microsoft.OData.Edm.dll" #> +<#@ Assembly Name="Microsoft.Spatial.dll" #> +<#@ Import Namespace="System" #> +<#@ Import Namespace="System.IO" #> +<#@ Import Namespace="System.Diagnostics" #> +<#@ Import Namespace="System.Globalization" #> +<#@ Import Namespace="System.Linq" #> +<#@ Import Namespace="System.Xml"#> +<#@ Import Namespace="System.Xml.Linq" #> +<#@ Import Namespace="System.Collections.Generic" #> +<#@ Import Namespace="Microsoft.OData.Edm.Csdl" #> +<#@ Import Namespace="Microsoft.OData.Edm" #> +<#@ Import Namespace="Microsoft.OData.Edm.Vocabularies" #> +<#@ Import Namespace="Microsoft.OData.Edm.Vocabularies.V1" #> +<#@ Import Namespace="Microsoft.OData.Edm.Vocabularies.Community.V1" #> +<#@ Import Namespace="System.Text"#> +<#@ Import Namespace="System.Net"#> +<# + CodeGenerationContext context; + if (!string.IsNullOrWhiteSpace(this.Edmx)) + { + context = new CodeGenerationContext(this.Edmx, this.NamespacePrefix) + { + UseDataServiceCollection = this.UseDataServiceCollection, + TargetLanguage = this.TargetLanguage, + EnableNamingAlias = this.EnableNamingAlias, + TempFilePath = this.TempFilePath, + IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes + }; + } + else + { + this.ApplyParametersFromCommandLine(); + if (string.IsNullOrEmpty(metadataDocumentUri)) + { + this.ApplyParametersFromConfigurationClass(); + } + + context = new CodeGenerationContext(new Uri(this.MetadataDocumentUri, UriKind.Absolute), this.NamespacePrefix) + { + UseDataServiceCollection = this.UseDataServiceCollection, + TargetLanguage = this.TargetLanguage, + EnableNamingAlias = this.EnableNamingAlias, + TempFilePath = this.TempFilePath, + IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes + }; + } + + if (this.GetReferencedModelReaderFunc != null) + { + context.GetReferencedModelReaderFunc = this.GetReferencedModelReaderFunc; + } + + ODataClientTemplate template; + switch(this.TargetLanguage) + { + case LanguageOption.CSharp: + template = new ODataClientCSharpTemplate(context); + break; + case LanguageOption.VB: + template = new ODataClientVBTemplate(context); + break; + + default: + throw new NotSupportedException(string.Format("Code gen for the target language '{0}' is not supported.", this.TargetLanguage.ToString())); + } + +#><#=template.TransformText()#><# + foreach (string warning in context.Warnings) + { + this.Warning(warning); + } +#><#+ +/// +/// The string for the edmx content. +/// +public string Edmx +{ + get; + set; +} + +/// +/// The Uri string to the metadata document. +/// +public string MetadataDocumentUri +{ + get + { + return this.metadataDocumentUri; + } + + set + { + value = Uri.UnescapeDataString(value); + Uri uri; + if (!Uri.TryCreate(value, UriKind.Absolute, out uri)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" is not a valid MetadataDocumentUri because is it not a valid absolute Uri. The MetadataDocumentUri must be set to an absolute Uri referencing the $metadata endpoint of an OData service.", value)); + } + + if (uri.Scheme == "http" || uri.Scheme == "https") + { + value = uri.Scheme + "://" + uri.Authority + uri.AbsolutePath; + value = value.TrimEnd('/'); + if (!value.EndsWith("$metadata")) + { + value += "/$metadata"; + } + } + + this.metadataDocumentUri = value; + } +} + +private string metadataDocumentUri; + +/// +/// The Func to get referenced model's XmlReader. Must have value when the this.Edmx xml or this.metadataDocumentUri's model has referneced model. +/// +public Func GetReferencedModelReaderFunc +{ + get; + set; +} + +/// +/// The NamespacePrefix is used as the only namespace for types in the same namespace as the default container, +/// and as a prefix for the namespace from the model for everything else. If this argument is null, the +/// namespaces from the model are used for all types. +/// +public string NamespacePrefix +{ + get + { + return this.namespacePrefix; + } + + set + { + if (string.IsNullOrWhiteSpace(value)) + { + this.namespacePrefix = null; + } + else + { + this.namespacePrefix = value; + } + } +} + +private string namespacePrefix; + +/// +/// true to use DataServiceCollection in the generated code, false otherwise. +/// +public bool UseDataServiceCollection +{ + get; + set; +} + +/// +/// Specifies which specific .Net Framework language the generated code will target. +/// +public LanguageOption TargetLanguage +{ + get; + set; +} + +/// +/// true to use Upper camel case for all class and property names, false otherwise. +/// +public bool EnableNamingAlias +{ + get; + set; +} + +/// +/// The path for the temporary file where the metadata xml document can be stored. +/// +public string TempFilePath +{ + get; + set; +} + +/// +/// true to ignore unknown elements or attributes in metadata, false otherwise. +/// +public bool IgnoreUnexpectedElementsAndAttributes +{ + get; + set; +} + +/// +/// Generate code targeting a specific .Net Framework language. +/// +public enum LanguageOption +{ + /// Generate code for C# language. + CSharp = 0, + + /// Generate code for Visual Basic language. + VB = 1, +} + +/// +/// Set the UseDataServiceCollection property with the given value. +/// +/// The value to set. +public void ValidateAndSetUseDataServiceCollectionFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the UseDataServiceCollection parameter because it is not a valid boolean value.", stringValue)); + } + + this.UseDataServiceCollection = boolValue; +} + +/// +/// Tries to set the TargetLanguage property with the given value. +/// +/// The value to set. +public void ValidateAndSetTargetLanguageFromString(string stringValue) +{ + LanguageOption option; + if (!Enum.TryParse(stringValue, true, out option)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the TargetLanguage parameter because it is not a valid LanguageOption. The supported LanguageOptions are \"CSharp\" and \"VB\".", stringValue)); + } + + this.TargetLanguage = option; +} + +/// +/// Set the EnableNamingAlias property with the given value. +/// +/// The value to set. +public void ValidateAndSetEnableNamingAliasFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the EnableNamingAlias parameter because it is not a valid boolean value.", stringValue)); + } + + this.EnableNamingAlias = boolValue; +} + +/// +/// Set the IgnoreUnexpectedElementsAndAttributes property with the given value. +/// +/// The value to set. +public void ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the IgnoreUnexpectedElementsAndAttributes parameter because it is not a valid boolean value.", stringValue)); + } + + this.IgnoreUnexpectedElementsAndAttributes = boolValue; +} + +/// +/// Reads the parameter values from the Configuration class and applies them. +/// +private void ApplyParametersFromConfigurationClass() +{ + this.MetadataDocumentUri = Configuration.MetadataDocumentUri; + this.NamespacePrefix = Configuration.NamespacePrefix; + this.UseDataServiceCollection = Configuration.UseDataServiceCollection; + this.ValidateAndSetTargetLanguageFromString(Configuration.TargetLanguage); + this.EnableNamingAlias = Configuration.EnableNamingAlias; + this.TempFilePath = Configuration.TempFilePath; + this.IgnoreUnexpectedElementsAndAttributes = Configuration.IgnoreUnexpectedElementsAndAttributes; +} + +/// +/// Reads the parameter values from the command line (TextTransform.exe) and applies them. +/// +private void ApplyParametersFromCommandLine() +{ + if (this.Host == null) + { + return; + } + + string metadataDocumentUri = this.Host.ResolveParameterValue("notempty", "notempty", "MetadataDocumentUri"); + if (!string.IsNullOrEmpty(metadataDocumentUri)) + { + this.MetadataDocumentUri = metadataDocumentUri; + } + + string namespacePrefix = this.Host.ResolveParameterValue("notempty", "notempty", "NamespacePrefix"); + if (!string.IsNullOrEmpty(namespacePrefix)) + { + this.NamespacePrefix = namespacePrefix; + } + + string useDataServiceCollection = this.Host.ResolveParameterValue("notempty", "notempty", "UseDataServiceCollection"); + if (!string.IsNullOrEmpty(useDataServiceCollection)) + { + this.ValidateAndSetUseDataServiceCollectionFromString(useDataServiceCollection); + } + + string targetLanguage = this.Host.ResolveParameterValue("notempty", "notempty", "TargetLanguage"); + if (!string.IsNullOrEmpty(targetLanguage)) + { + this.ValidateAndSetTargetLanguageFromString(targetLanguage); + } + + string enableNamingAlias = this.Host.ResolveParameterValue("notempty", "notempty", "EnableNamingAlias"); + if (!string.IsNullOrEmpty(enableNamingAlias)) + { + this.ValidateAndSetEnableNamingAliasFromString(enableNamingAlias); + } + + string ignoreUnexpectedElementsAndAttributes = this.Host.ResolveParameterValue("notempty", "notempty", "IgnoreUnexpectedElementsAndAttributes"); + if (!string.IsNullOrEmpty(ignoreUnexpectedElementsAndAttributes)) + { + this.ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(ignoreUnexpectedElementsAndAttributes); + } +} + +/// +/// Context object to provide the model and configuration info to the code generator. +/// +public class CodeGenerationContext +{ + /// + /// The namespace of the term to use when building annotations for indicating the conventions used. + /// + private const string ConventionTermNamespace = "Com.Microsoft.OData.Service.Conventions.V1"; + + /// + /// The name of the term to use when building annotations for indicating the conventions used. + /// + private const string ConventionTermName = "UrlConventions"; + + /// + /// The string value for indicating that the key-as-segment convention is being used in annotations and headers. + /// + private const string KeyAsSegmentConventionName = "KeyAsSegment"; + + /// + /// The XElement for the edmx + /// + private readonly XElement edmx; + + /// + /// The namespacePrefix is used as the only namespace in generated code when there's only one schema in edm model, + /// and as a prefix for the namespace from the model with multiple schemas. If this argument is null, the + /// namespaces from the model are used for all types. + /// + private readonly string namespacePrefix; + + /// + /// The EdmModel to generate code for. + /// + private IEdmModel edmModel; + + /// + /// The array of namespaces in the current edm model. + /// + private string[] namespacesInModel; + + /// + /// The array of warnings occured when parsing edm model. + /// + private string[] warnings; + + /// + /// true if the model contains any structural type with inheritance, false otherwise. + /// + private bool? modelHasInheritance; + + /// + /// If the namespacePrefix is not null, this contains the mapping of namespaces in the model to the corresponding prefixed namespaces. + /// Otherwise this is an empty dictionary. + /// + private Dictionary namespaceMap; + + /// + /// Maps the element type of a navigation source to the navigation source. + /// + private Dictionary> elementTypeToNavigationSourceMap; + + /// + /// HashSet contains the pair of Names and Namespaces of EntityContainers using KeyAsSegment url convention + /// + private HashSet keyAsSegmentContainers; + + /// + /// Constructs an instance of . + /// + /// The Uri to the metadata document. The supported scheme are File, http and https. + public CodeGenerationContext(Uri metadataUri, string namespacePrefix) + : this(GetEdmxStringFromMetadataPath(metadataUri), namespacePrefix) + { + } + + /// + /// Constructs an instance of . + /// + /// The string for the edmx. + /// The namespacePrefix is used as the only namespace in generated code + /// when there's only one schema in edm model, and as a prefix for the namespace from the model with multiple + /// schemas. If this argument is null, the namespaces from the model are used for all types. + public CodeGenerationContext(string edmx, string namespacePrefix) + { + this.edmx = XElement.Parse(edmx); + this.namespacePrefix = namespacePrefix; + } + + /// + /// The EdmModel to generate code for. + /// + public XElement Edmx + { + get { return this.edmx; } + } + + /// + /// The EdmModel to generate code for. + /// + public IEdmModel EdmModel + { + get + { + if (this.edmModel == null) + { + Debug.Assert(this.edmx != null, "this.edmx != null"); + + IEnumerable errors; + CsdlReaderSettings edmxReaderSettings = new CsdlReaderSettings() + { + GetReferencedModelReaderFunc = this.GetReferencedModelReaderFuncWrapper, + IgnoreUnexpectedAttributesAndElements = this.IgnoreUnexpectedElementsAndAttributes + }; + if (!CsdlReader.TryParse(this.edmx.CreateReader(ReaderOptions.None), Enumerable.Empty(), edmxReaderSettings, out this.edmModel, out errors)) + { + Debug.Assert(errors != null, "errors != null"); + throw new InvalidOperationException(errors.FirstOrDefault().ErrorMessage); + } + else if (this.IgnoreUnexpectedElementsAndAttributes) + { + if (errors != null && errors.Any()) + { + this.warnings = errors.Select(e => e.ErrorMessage).ToArray(); + } + } + } + + return this.edmModel; + } + } + + /// + /// The func for user code to overwrite and provide referenced model's XmlReader. + /// + public Func GetReferencedModelReaderFunc + { + get { return getReferencedModelReaderFunc; } + set { this.getReferencedModelReaderFunc = value; } + } + + /// + /// Basic setting for XmlReader. + /// + private static readonly XmlReaderSettings settings = new XmlReaderSettings() { IgnoreWhitespace = true }; + + /// + /// The func for user code to overwrite and provide referenced model's XmlReader. + /// + private Func getReferencedModelReaderFunc = uri => XmlReader.Create(GetEdmxStreamFromUri(uri), settings); + + /// + /// The Wrapper func for user code to overwrite and provide referenced model's stream. + /// + public Func GetReferencedModelReaderFuncWrapper + { + get + { + return (uri) => + { + using (XmlReader reader = GetReferencedModelReaderFunc(uri)) + { + if (reader == null) + { + return null; + } + + XElement element = XElement.Load(reader); + if (this.ReferencesMap == null) + { + this.ReferencesMap = new Dictionary(); + } + + this.ReferencesMap.Add(uri, element); + return element.CreateReader(ReaderOptions.None); + } + }; + } + } + + /// + /// Dictionary that stores uri and referenced xml mapping. + /// + public Dictionary ReferencesMap + { + get; + set; + } + + /// + /// The array of namespaces in the current edm model. + /// + public string[] NamespacesInModel + { + get + { + if (this.namespacesInModel == null) + { + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + this.namespacesInModel = GetElementsFromModelTree(this.EdmModel, (m) => m.SchemaElements.Select(e => e.Namespace)).Distinct().ToArray(); + } + + return this.namespacesInModel; + } + } + + /// + /// The array of warnings occured when parsing edm model. + /// + public string[] Warnings + { + get { return this.warnings ?? (this.warnings = new string[] {}); } + } + + /// + /// true if the model contains any structural type with inheritance, false otherwise. + /// + public bool ModelHasInheritance + { + get + { + if (!this.modelHasInheritance.HasValue) + { + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + this.modelHasInheritance = this.EdmModel.SchemaElementsAcrossModels() + .OfType().Any(t => !t.FullTypeName().StartsWith("Org.OData.Authorization.V1") && + !t.FullTypeName().StartsWith("Org.OData.Capabilities.V1") && + !t.FullTypeName().StartsWith("Org.OData.Core.V1") && t.BaseType != null); + } + + return this.modelHasInheritance.Value; + } + } + + /// + /// true if we need to generate the ResolveNameFromType method, false otherwise. + /// + public bool NeedResolveNameFromType + { + get { return this.ModelHasInheritance || this.NamespaceMap.Count > 0 || this.EnableNamingAlias; } + } + + /// + /// true if we need to generate the ResolveTypeFromName method, false otherwise. + /// + public bool NeedResolveTypeFromName + { + get { return this.NamespaceMap.Count > 0 || this.EnableNamingAlias; } + } + + /// + /// If the namespacePrefix is not null, this contains the mapping of namespaces in the model to the corresponding prefixed namespaces. + /// Otherwise this is an empty dictionary. + /// + public Dictionary NamespaceMap + { + get + { + if (this.namespaceMap == null) + { + if (!string.IsNullOrEmpty(this.namespacePrefix)) + { + if (this.NamespacesInModel.Count() == 1) + { + IEdmEntityContainer container = this.EdmModel.EntityContainer; + string containerNamespace = container == null ? null : container.Namespace; + this.namespaceMap = this.NamespacesInModel + .Distinct() + .ToDictionary( + ns => ns, + ns => ns == containerNamespace ? + this.namespacePrefix : + this.namespacePrefix + "." + (this.EnableNamingAlias ? Customization.CustomizeNamespace(ns) : ns)); + } + else + { + this.namespaceMap = this.NamespacesInModel + .Distinct() + .ToDictionary( + ns => ns, + ns => this.namespacePrefix + "." + (this.EnableNamingAlias ? Customization.CustomizeNamespace(ns) : ns)); + } + } + else if (this.EnableNamingAlias) + { + this.namespaceMap = this.NamespacesInModel + .Distinct() + .ToDictionary( + ns => ns, + ns => Customization.CustomizeNamespace(ns)); + } + else + { + this.namespaceMap = new Dictionary(); + } + } + + return this.namespaceMap; + } + } + + /// + /// true to use DataServiceCollection in the generated code, false otherwise. + /// + public bool UseDataServiceCollection + { + get; + set; + } + + /// + /// Specifies which specific .Net Framework language the generated code will target. + /// + public LanguageOption TargetLanguage + { + get; + set; + } + + /// + /// The path for the temporary file where the metadata xml document can be stored. + /// + public string TempFilePath + { + get; + set; + } + /// + /// true to use Upper camel case for all class and property names, false otherwise. + /// + public bool EnableNamingAlias + { + get; + set; + } + + /// + /// true to ignore unknown elements or attributes in metadata, false otherwise. + /// + public bool IgnoreUnexpectedElementsAndAttributes + { + get; + set; + } + + /// + /// Maps the element type of an entity set to the entity set. + /// + public Dictionary> ElementTypeToNavigationSourceMap + { + get + { + return this.elementTypeToNavigationSourceMap ?? (this.elementTypeToNavigationSourceMap = new Dictionary>(EqualityComparer.Default)); + } + } + + /// + /// true if this EntityContainer need to set the UrlConvention to KeyAsSegment, false otherwise. + /// + public bool UseKeyAsSegmentUrlConvention(IEdmEntityContainer currentContainer) + { + if (this.keyAsSegmentContainers == null) + { + this.keyAsSegmentContainers = new HashSet(); + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + IEnumerable annotations = this.EdmModel.VocabularyAnnotations; + foreach(IEdmVocabularyAnnotation valueAnnotation in annotations) + { + IEdmEntityContainer container = valueAnnotation.Target as IEdmEntityContainer; + IEdmTerm valueTerm = valueAnnotation.Term; + IEdmStringConstantExpression expression = valueAnnotation.Value as IEdmStringConstantExpression; + if (container != null && valueTerm != null && expression != null) + { + if (valueTerm.Namespace == ConventionTermNamespace && + valueTerm.Name == ConventionTermName && + expression.Value == KeyAsSegmentConventionName) + { + this.keyAsSegmentContainers.Add(container.FullName()); + } + } + } + } + + return this.keyAsSegmentContainers.Contains(currentContainer.FullName()); + } + + /// + /// Gets the enumeration of schema elements with the given namespace. + /// + /// The namespace of the schema elements to get. + /// The enumeration of schema elements with the given namespace. + public IEnumerable GetSchemaElements(string ns) + { + Debug.Assert(ns != null, "ns != null"); + Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); + return GetElementsFromModelTree(this.EdmModel, m => m.SchemaElements.Where(e => e.Namespace == ns)); + } + + /// + /// Gets the namespace qualified name for the given with the namespace prefix applied if this.NamespacePrefix is specified. + /// + /// The schema element to get the full name for. + /// The fixed name of this schemaElement. + /// The current code generate template. + /// The namespace qualified name for the given with the namespace prefix applied if this.NamespacePrefix is specified. + public string GetPrefixedFullName(IEdmSchemaElement schemaElement, string schemaElementFixedName, ODataClientTemplate template, bool needGlobalPrefix = true) + { + if (schemaElement == null) + { + return null; + } + + return this.GetPrefixedNamespace(schemaElement.Namespace, template, true, needGlobalPrefix) + "." + schemaElementFixedName; + } + + /// + /// Gets the prefixed namespace for the given . + /// + /// The namespace without the prefix. + /// The current code generate template. + /// The flag indicates whether the namespace need to be fixed now. + /// The flag indicates whether the namespace need to be added by gloabal prefix. + /// The prefixed namespace for the given . + public string GetPrefixedNamespace(string ns, ODataClientTemplate template, bool needFix, bool needGlobalPrefix) + { + if (ns == null) + { + return null; + } + + string prefixedNamespace; + if (!this.NamespaceMap.TryGetValue(ns, out prefixedNamespace)) + { + prefixedNamespace = ns; + } + + if (needFix) + { + string[] segments = prefixedNamespace.Split('.'); + prefixedNamespace = string.Empty; + int n = segments.Length; + for (int i = 0; i < n; ++i) + { + if (template.LanguageKeywords.Contains(segments[i])) + { + prefixedNamespace += string.Format(template.FixPattern, segments[i]); + } + else + { + prefixedNamespace += segments[i]; + } + + prefixedNamespace += (i == n - 1 ? string.Empty : "."); + } + } + + if (needGlobalPrefix) + { + prefixedNamespace = template.GlobalPrefix + prefixedNamespace; + } + + return prefixedNamespace; + } + + /// + /// Reads the edmx string from a file path or a http/https path. + /// + /// The Uri to the metadata document. The supported scheme are File, http and https. + private static string GetEdmxStringFromMetadataPath(Uri metadataUri) + { + string content = null; + using (StreamReader streamReader = new StreamReader(GetEdmxStreamFromUri(metadataUri))) + { + content = streamReader.ReadToEnd(); + } + + return content; + } + + /// + /// Get the metadata stream from a file path or a http/https path. + /// + /// The Uri to the stream. The supported scheme are File, http and https. + private static Stream GetEdmxStreamFromUri(Uri metadataUri) + { + Debug.Assert(metadataUri != null, "metadataUri != null"); + Stream metadataStream = null; + if (metadataUri.Scheme == "file") + { + metadataStream = new FileStream(Uri.UnescapeDataString(metadataUri.AbsolutePath), FileMode.Open, FileAccess.Read); + } + else if (metadataUri.Scheme == "http" || metadataUri.Scheme == "https") + { + try + { + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(metadataUri); + WebResponse webResponse = webRequest.GetResponse(); + metadataStream = webResponse.GetResponseStream(); + } + catch (WebException e) + { + HttpWebResponse webResponse = e.Response as HttpWebResponse; + if (webResponse != null && webResponse.StatusCode == HttpStatusCode.Unauthorized) + { + throw new WebException("Failed to access the metadata document. The OData service requires authentication for accessing it. Please download the metadata, store it into a local file, and set the value of “MetadataDocumentUri” in the .odata.config file to the file path. After that, run custom tool again to generate the OData Client code."); + } + else + { + throw e; + } + } + } + else + { + throw new ArgumentException("Only file, http, https schemes are supported for paths to metadata source locations."); + } + + return metadataStream; + } + + private static IEnumerable GetElementsFromModelTree(IEdmModel mainModel, Func> getElementFromOneModelFunc) + { + List ret = new List(); + if(mainModel is EdmCoreModel || mainModel.FindDeclaredTerm(CoreVocabularyConstants.OptimisticConcurrency) != null) + { + return ret; + } + + ret.AddRange(getElementFromOneModelFunc(mainModel)); + foreach (var tmp in mainModel.ReferencedModels) + { + if (tmp is EdmCoreModel || + tmp.FindDeclaredTerm(CoreVocabularyConstants.OptimisticConcurrency) != null || + tmp.FindDeclaredTerm(CapabilitiesVocabularyConstants.ChangeTracking) != null || + tmp.FindDeclaredTerm(AlternateKeysVocabularyConstants.AlternateKeys) != null || + tmp.FindDeclaredTerm("Org.OData.Authorization.V1.Authorizations") != null || + tmp.FindDeclaredTerm("Org.OData.Validation.V1.DerivedTypeConstraint") != null || + tmp.FindDeclaredTerm("Org.OData.Community.V1.UrlEscapeFunction") != null) + { + continue; + } + + ret.AddRange(getElementFromOneModelFunc(tmp)); + } + + return ret; + } +} + +/// +/// The template class to generate the OData client code. +/// +public abstract class ODataClientTemplate : TemplateBase +{ + protected const string T4Version = "#VersionNumber#"; + + /// + /// The code generation context. + /// + protected readonly CodeGenerationContext context; + + /// + /// The Dictionary to store identifier mappings when there are duplicate names between properties and Entity/Complex types + /// + protected Dictionary IdentifierMappings = new Dictionary(StringComparer.Ordinal); + + /// + /// Creates an instance of the ODataClientTemplate. + /// + /// The code generation context. + public ODataClientTemplate(CodeGenerationContext context) + { + this.context = context; + } + + internal string SingleSuffix + { + get { return "Single"; } + } + + #region Get Language specific keyword names. + internal abstract string GlobalPrefix { get; } + internal abstract string SystemTypeTypeName { get; } + internal abstract string AbstractModifier { get; } + internal abstract string DataServiceActionQueryTypeName { get; } + internal abstract string DataServiceActionQuerySingleOfTStructureTemplate { get; } + internal abstract string DataServiceActionQueryOfTStructureTemplate { get; } + internal abstract string NotifyPropertyChangedModifier { get; } + internal abstract string ClassInheritMarker { get; } + internal abstract string ParameterSeparator { get; } + internal abstract string KeyParameterSeparator { get; } + internal abstract string KeyDictionaryItemSeparator { get; } + internal abstract string SystemNullableStructureTemplate { get; } + internal abstract string ICollectionOfTStructureTemplate { get; } + internal abstract string DataServiceCollectionStructureTemplate { get; } + internal abstract string DataServiceQueryStructureTemplate { get; } + internal abstract string DataServiceQuerySingleStructureTemplate { get; } + internal abstract string ObservableCollectionStructureTemplate { get; } + internal abstract string ObjectModelCollectionStructureTemplate { get; } + internal abstract string DataServiceCollectionConstructorParameters { get; } + internal abstract string NewModifier { get; } + internal abstract string GeoTypeInitializePattern { get; } + internal abstract string Int32TypeName { get; } + internal abstract string StringTypeName { get; } + internal abstract string BinaryTypeName { get; } + internal abstract string DecimalTypeName { get; } + internal abstract string Int16TypeName { get; } + internal abstract string SingleTypeName { get; } + internal abstract string BooleanTypeName { get; } + internal abstract string DoubleTypeName { get; } + internal abstract string GuidTypeName { get; } + internal abstract string ByteTypeName { get; } + internal abstract string Int64TypeName { get; } + internal abstract string SByteTypeName { get; } + internal abstract string DataServiceStreamLinkTypeName { get; } + internal abstract string GeographyTypeName { get; } + internal abstract string GeographyPointTypeName { get; } + internal abstract string GeographyLineStringTypeName { get; } + internal abstract string GeographyPolygonTypeName { get; } + internal abstract string GeographyCollectionTypeName { get; } + internal abstract string GeographyMultiPolygonTypeName { get; } + internal abstract string GeographyMultiLineStringTypeName { get; } + internal abstract string GeographyMultiPointTypeName { get; } + internal abstract string GeometryTypeName { get; } + internal abstract string GeometryPointTypeName { get; } + internal abstract string GeometryLineStringTypeName { get; } + internal abstract string GeometryPolygonTypeName { get; } + internal abstract string GeometryCollectionTypeName { get; } + internal abstract string GeometryMultiPolygonTypeName { get; } + internal abstract string GeometryMultiLineStringTypeName { get; } + internal abstract string GeometryMultiPointTypeName { get; } + internal abstract string DateTypeName { get; } + internal abstract string DateTimeOffsetTypeName { get; } + internal abstract string DurationTypeName { get; } + internal abstract string TimeOfDayTypeName { get; } + internal abstract string XmlConvertClassName { get; } + internal abstract string EnumTypeName { get; } + internal abstract HashSet LanguageKeywords { get; } + internal abstract string FixPattern { get; } + internal abstract string EnumUnderlyingTypeMarker { get; } + internal abstract string ConstantExpressionConstructorWithType { get; } + internal abstract string TypeofFormatter { get; } + internal abstract string UriOperationParameterConstructor { get; } + internal abstract string UriEntityOperationParameterConstructor { get; } + internal abstract string BodyOperationParameterConstructor { get; } + internal abstract string BaseEntityType { get; } + internal abstract string OverloadsModifier { get; } + internal abstract string ODataVersion { get; } + internal abstract string ParameterDeclarationTemplate { get; } + internal abstract string DictionaryItemConstructor { get; } + #endregion Get Language specific keyword names. + + #region Language specific write methods. + internal abstract void WriteFileHeader(); + internal abstract void WriteNamespaceStart(string fullNamespace); + internal abstract void WriteClassStartForEntityContainer(string originalContainerName, string containerName, string fixedContainerName); + internal abstract void WriteMethodStartForEntityContainerConstructor(string containerName, string fixedContainerName); + internal abstract void WriteKeyAsSegmentUrlConvention(); + internal abstract void WriteInitializeResolveName(); + internal abstract void WriteInitializeResolveType(); + internal abstract void WriteClassEndForEntityContainerConstructor(); + internal abstract void WriteMethodStartForResolveTypeFromName(); + internal abstract void WriteResolveNamespace(string typeName, string fullNamespace, string languageDependentNamespace); + internal abstract void WriteMethodEndForResolveTypeFromName(); + internal abstract void WriteMethodStartForResolveNameFromType(string containerName, string fullNamespace); + internal abstract void WriteResolveType(string fullNamespace, string languageDependentNamespace); + internal abstract void WriteMethodEndForResolveNameFromType(bool modelHasInheritance); + internal abstract void WriteContextEntitySetProperty(string entitySetName, string entitySetFixedName, string originalEntitySetName, string entitySetElementTypeName, bool inContext = true); + internal abstract void WriteContextSingletonProperty(string singletonName, string singletonFixedName, string originalSingletonName, string singletonElementTypeName, bool inContext = true); + internal abstract void WriteContextAddToEntitySetMethod(string entitySetName, string originalEntitySetName, string typeName, string parameterName); + internal abstract void WriteGeneratedEdmModel(string escapedEdmxString); + internal abstract void WriteClassEndForEntityContainer(); + internal abstract void WriteSummaryCommentForStructuredType(string typeName); + internal abstract void WriteKeyPropertiesCommentAndAttribute(IEnumerable keyProperties, string keyString); + internal abstract void WriteEntityTypeAttribute(); + internal abstract void WriteEntitySetAttribute(string entitySetName); + internal abstract void WriteEntityHasStreamAttribute(); + internal abstract void WriteClassStartForStructuredType(string abstractModifier, string typeName, string originalTypeName, string baseTypeName); + internal abstract void WriteSummaryCommentForStaticCreateMethod(string typeName); + internal abstract void WriteParameterCommentForStaticCreateMethod(string parameterName, string propertyName); + internal abstract void WriteDeclarationStartForStaticCreateMethod(string typeName,string fixedTypeName ); + internal abstract void WriteParameterForStaticCreateMethod(string parameterTypeName, string parameterName, string parameterSeparater); + internal abstract void WriteDeclarationEndForStaticCreateMethod(string typeName, string instanceName); + internal abstract void WriteParameterNullCheckForStaticCreateMethod(string parameterName); + internal abstract void WritePropertyValueAssignmentForStaticCreateMethod(string instanceName, string propertyName, string parameterName); + internal abstract void WriteMethodEndForStaticCreateMethod(string instanceName); + internal abstract void WritePropertyForStructuredType(string propertyType, string originalPropertyName, string propertyName, string fixedPropertyName, string privatePropertyName, string propertyInitializationValue, bool writeOnPropertyChanged); + internal abstract void WriteINotifyPropertyChangedImplementation(); + internal abstract void WriteClassEndForStructuredType(); + internal abstract void WriteNamespaceEnd(); + internal abstract void WriteEnumFlags(); + internal abstract void WriteSummaryCommentForEnumType(string enumName); + internal abstract void WriteEnumDeclaration(string enumName, string originalEnumName, string underlyingType); + internal abstract void WriteMemberForEnumType(string member, string originalMemberName, bool last); + internal abstract void WriteEnumEnd(); + internal abstract void WritePropertyRootNamespace(string containerName, string fullNamespace); + internal abstract void WriteFunctionImportReturnCollectionResult(string functionName, string originalFunctionName, string returnTypeName, string parameters, string parameterValues, bool isComposable, bool useEntityReference); + internal abstract void WriteFunctionImportReturnSingleResult(string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference); + internal abstract void WriteBoundFunctionInEntityTypeReturnCollectionResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference); + internal abstract void WriteBoundFunctionInEntityTypeReturnSingleResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference); + internal abstract void WriteActionImport(string actionName, string originalActionName, string returnTypeName, string parameters, string parameterValues); + internal abstract void WriteBoundActionInEntityType(bool hideBaseMethod, string actionName, string originalActionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues); + internal abstract void WriteConstructorForSingleType(string singleTypeName, string baseTypeName); + internal abstract void WriteExtensionMethodsStart(); + internal abstract void WriteExtensionMethodsEnd(); + internal abstract void WriteByKeyMethods(string entityTypeName, string returnTypeName, IEnumerable keys, string keyParameters, string keyDictionaryItems); + internal abstract void WriteCastToMethods(string baseTypeName, string derivedTypeName, string derivedTypeFullName, string returnTypeName); + internal abstract void WriteBoundFunctionReturnSingleResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference); + internal abstract void WriteBoundFunctionReturnCollectionResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference); + internal abstract void WriteBoundActionAsExtension(string actionName, string originalActionName, string boundSourceType, string returnTypeName, string parameters, string fullNamespace, string parameterValues); + #endregion Language specific write methods. + + internal HashSet ClrReferenceTypes { get { + if (clrReferenceTypes == null) + { + clrReferenceTypes = new HashSet() + { + EdmPrimitiveTypeKind.String, EdmPrimitiveTypeKind.Binary, EdmPrimitiveTypeKind.Geography, EdmPrimitiveTypeKind.Stream, + EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeographyLineString, EdmPrimitiveTypeKind.GeographyPolygon, + EdmPrimitiveTypeKind.GeographyCollection, EdmPrimitiveTypeKind.GeographyMultiPolygon, EdmPrimitiveTypeKind.GeographyMultiLineString, + EdmPrimitiveTypeKind.GeographyMultiPoint, EdmPrimitiveTypeKind.Geometry, EdmPrimitiveTypeKind.GeometryPoint, + EdmPrimitiveTypeKind.GeometryLineString, EdmPrimitiveTypeKind.GeometryPolygon, EdmPrimitiveTypeKind.GeometryCollection, + EdmPrimitiveTypeKind.GeometryMultiPolygon, EdmPrimitiveTypeKind.GeometryMultiLineString, EdmPrimitiveTypeKind.GeometryMultiPoint + }; + } + return clrReferenceTypes; + } } + private HashSet clrReferenceTypes; + + /// + /// Generates code for the OData client. + /// + /// The generated code for the OData client. + public override string TransformText() + { + this.WriteFileHeader(); + this.WriteNamespaces(); + return this.GenerationEnvironment.ToString(); + } + + internal void WriteNamespaces() + { + foreach(string fullNamespace in context.NamespacesInModel) + { + this.WriteNamespace(fullNamespace); + } + } + + internal void WriteNamespace(string fullNamespace) + { + this.WriteNamespaceStart(this.context.GetPrefixedNamespace(fullNamespace, this, true, false)); + + IEdmSchemaElement[] schemaElements = this.context.GetSchemaElements(fullNamespace).ToArray(); + if (schemaElements.OfType().Any()) { + IEdmEntityContainer container = schemaElements.OfType().Single(); + this.WriteEntityContainer(container, fullNamespace); + } + + Dictionary> boundOperationsMap = new Dictionary>(); + foreach (IEdmOperation operation in schemaElements.OfType()) + { + if (operation.IsBound) + { + IEdmType edmType = operation.Parameters.First().Type.Definition; + IEdmStructuredType edmStructuredType = edmType as IEdmStructuredType; + if (edmStructuredType != null) + { + List operationList; + if (!boundOperationsMap.TryGetValue(edmStructuredType, out operationList)) + { + operationList = new List(); + } + + operationList.Add(operation); + boundOperationsMap[edmStructuredType] = operationList; + } + } + } + + Dictionary> structuredBaseTypeMap = new Dictionary>(); + foreach(IEdmSchemaType type in schemaElements.OfType()) + { + IEdmEnumType enumType = type as IEdmEnumType; + if (enumType != null) + { + this.WriteEnumType(enumType); + } + else + { + IEdmComplexType complexType = type as IEdmComplexType; + if (complexType != null) + { + this.WriteComplexType(complexType, boundOperationsMap); + } + else + { + IEdmEntityType entityType = type as IEdmEntityType; + this.WriteEntityType(entityType, boundOperationsMap); + } + + IEdmStructuredType structuredType = type as IEdmStructuredType; + if (structuredType.BaseType != null) + { + List derivedTypes; + if (!structuredBaseTypeMap.TryGetValue(structuredType.BaseType, out derivedTypes)) + { + structuredBaseTypeMap[structuredType.BaseType] = new List(); + } + + structuredBaseTypeMap[structuredType.BaseType].Add(structuredType); + } + } + } + + if (schemaElements.OfType().Any() || + schemaElements.OfType().Any(o => o.IsBound)) + { + this.WriteExtensionMethodsStart(); + foreach (IEdmEntityType type in schemaElements.OfType()) + { + string entityTypeName = type.Name; + entityTypeName = context.EnableNamingAlias ? Customization.CustomizeNaming(entityTypeName) : entityTypeName; + string entityTypeFullName = context.GetPrefixedFullName(type, GetFixedName(entityTypeName), this); + string returnTypeName = context.GetPrefixedFullName(type, GetFixedName(entityTypeName + this.SingleSuffix), this); + + var keyProperties = type.Key(); + if(keyProperties != null && keyProperties.Any()) + { + List keyParameters = new List(); + List keyDictionaryItems = new List(); + List keyNames = new List(); + foreach (IEdmProperty key in keyProperties) + { + string typeName = Utils.GetClrTypeName(key.Type, this.context.UseDataServiceCollection, this, this.context); + string keyName = Utils.CamelCase(key.Name); + keyNames.Add(keyName); + keyParameters.Add(string.Format(this.ParameterDeclarationTemplate, typeName, this.GetFixedName(keyName))); + keyDictionaryItems.Add(string.Format(this.DictionaryItemConstructor, "\"" + key.Name + "\"", this.GetFixedName(keyName))); + } + + string keyParametersString = string.Join(this.KeyParameterSeparator, keyParameters); + string keyDictionaryItemsString = string.Join(this.KeyDictionaryItemSeparator, keyDictionaryItems); + this.WriteByKeyMethods(entityTypeFullName, returnTypeName, keyNames, keyParametersString, keyDictionaryItemsString); + } + + IEdmEntityType current = (IEdmEntityType)type.BaseType; + while (current != null) + { + string baseTypeName = current.Name; + baseTypeName = context.EnableNamingAlias ? Customization.CustomizeNaming(baseTypeName) : baseTypeName; + baseTypeName = context.GetPrefixedFullName(current, GetFixedName(baseTypeName), this); + this.WriteCastToMethods(baseTypeName, entityTypeName, entityTypeFullName, returnTypeName); + current = (IEdmEntityType)current.BaseType; + } + } + + HashSet boundOperations = new HashSet(StringComparer.Ordinal); + foreach (IEdmFunction function in schemaElements.OfType()) + { + if (function.IsBound) + { + IEdmTypeReference edmTypeReference = function.Parameters.First().Type; + string functionName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(function.Name) : function.Name; + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + this.GetParameterStrings(function.IsBound, false, function.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string sourceTypeName = GetSourceOrReturnTypeName(edmTypeReference); + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, sourceTypeName); + string returnTypeName = GetSourceOrReturnTypeName(function.ReturnType); + string returnTypeNameWithSingleSuffix = GetSourceOrReturnTypeName(function.ReturnType, true); + string fixedFunctionName = GetFixedName(functionName); + string func = string.Format("{0}({1},{2})", fixedFunctionName, sourceTypeName, parameterTypes ); + + if (!boundOperations.Contains(func)) + { + boundOperations.Add(func); + + if (function.ReturnType.IsCollection()) + { + this.WriteBoundFunctionReturnCollectionResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, parameterString, function.Namespace, parameterValues, function.IsComposable, useEntityReference); + } + else + { + this.WriteBoundFunctionReturnSingleResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, function.Namespace, parameterValues, function.IsComposable, function.ReturnType.IsEntity(), useEntityReference); + } + } + + IEdmStructuredType structuredType; + if (edmTypeReference.IsCollection()) + { + IEdmCollectionType collectionType = edmTypeReference.Definition as IEdmCollectionType; + structuredType = (IEdmStructuredType)collectionType.ElementType.Definition; + } + else + { + structuredType = (IEdmStructuredType)edmTypeReference.Definition; + } + + List derivedTypes; + if (structuredBaseTypeMap.TryGetValue(structuredType, out derivedTypes)) + { + foreach (IEdmStructuredType type in derivedTypes) + { + IEdmTypeReference derivedTypeReference = new EdmEntityTypeReference((IEdmEntityType)type, true); + List currentParameters = function.Parameters.Select(p => p.Type).ToList(); + currentParameters[0] = derivedTypeReference; + + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, GetSourceOrReturnTypeName(derivedTypeReference)); + string currentFunc = string.Format("{0}({1},{2})", fixedFunctionName, sourceTypeName, parameterTypes ); + if (!boundOperations.Contains(currentFunc)) + { + boundOperations.Add(currentFunc); + + if (function.ReturnType.IsCollection()) + { + this.WriteBoundFunctionReturnCollectionResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, parameterString, function.Namespace, parameterValues, function.IsComposable, useEntityReference); + } + else + { + this.WriteBoundFunctionReturnSingleResultAsExtension(fixedFunctionName, function.Name, sourceTypeName, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, function.Namespace, parameterValues, function.IsComposable, function.ReturnType.IsEntity(), useEntityReference); + } + } + } + } + } + } + + foreach (IEdmAction action in schemaElements.OfType()) + { + if (action.IsBound) + { + IEdmTypeReference edmTypeReference = action.Parameters.First().Type; + string actionName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(action.Name) : action.Name; + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + this.GetParameterStrings(action.IsBound, true, action.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string sourceTypeName = GetSourceOrReturnTypeName(edmTypeReference); + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, sourceTypeName); + string returnTypeName; + if (action.ReturnType != null) + { + returnTypeName = GetSourceOrReturnTypeName(action.ReturnType); + if (action.ReturnType.IsCollection()) + { + returnTypeName = string.Format(this.DataServiceActionQueryOfTStructureTemplate, returnTypeName); + } + else + { + returnTypeName = string.Format(this.DataServiceActionQuerySingleOfTStructureTemplate, returnTypeName); + } + } + else + { + returnTypeName = this.DataServiceActionQueryTypeName; + } + + string fixedActionName = GetFixedName(actionName); + string ac = string.Format("{0}({1},{2})", fixedActionName, sourceTypeName, parameterTypes ); + if (!boundOperations.Contains(ac)) + { + boundOperations.Add(ac); + this.WriteBoundActionAsExtension(fixedActionName, action.Name, sourceTypeName, returnTypeName, parameterString, action.Namespace, parameterValues); + } + + IEdmStructuredType structuredType; + if (edmTypeReference.IsCollection()) + { + IEdmCollectionType collectionType = edmTypeReference.Definition as IEdmCollectionType; + structuredType = (IEdmStructuredType)collectionType.ElementType.Definition; + } + else + { + structuredType = (IEdmStructuredType)edmTypeReference.Definition; + } + + List derivedTypes; + if (structuredBaseTypeMap.TryGetValue(structuredType, out derivedTypes)) + { + foreach (IEdmStructuredType type in derivedTypes) + { + IEdmTypeReference derivedTypeReference = new EdmEntityTypeReference((IEdmEntityType)type, true); + List currentParameters = action.Parameters.Select(p => p.Type).ToList(); + currentParameters[0] = derivedTypeReference; + + sourceTypeName = string.Format(edmTypeReference.IsCollection() ? this.DataServiceQueryStructureTemplate : this.DataServiceQuerySingleStructureTemplate, GetSourceOrReturnTypeName(derivedTypeReference)); + string currentAc = string.Format("{0}({1},{2})", fixedActionName, sourceTypeName, parameterTypes ); + if (!boundOperations.Contains(currentAc)) + { + boundOperations.Add(currentAc); + this.WriteBoundActionAsExtension(fixedActionName, action.Name, sourceTypeName, returnTypeName, parameterString, action.Namespace, parameterValues); + } + } + } + } + } + + this.WriteExtensionMethodsEnd(); + } + + this.WriteNamespaceEnd(); + } + + internal bool HasBoundOperations(IEnumerable operations) + { + foreach (IEdmOperation opeartion in operations) + { + if (opeartion.IsBound) + { + return true; + } + } + + return false; + } + + internal void WriteEntityContainer(IEdmEntityContainer container, string fullNamespace) + { + string camelCaseContainerName = container.Name; + if (this.context.EnableNamingAlias) + { + camelCaseContainerName = Customization.CustomizeNaming(camelCaseContainerName); + } + + this.WriteClassStartForEntityContainer(container.Name, camelCaseContainerName, GetFixedName(camelCaseContainerName)); + this.WriteEntityContainerConstructor(container); + + if (this.context.NeedResolveNameFromType) + { + this.WritePropertyRootNamespace(GetFixedName(camelCaseContainerName), this.context.GetPrefixedNamespace(fullNamespace, this, false, false)); + } + + this.WriteResolveTypeFromName(); + this.WriteResolveNameFromType(camelCaseContainerName, fullNamespace); + + foreach (IEdmEntitySet entitySet in container.EntitySets()) + { + IEdmEntityType entitySetElementType = entitySet.EntityType(); + string entitySetElementTypeName = GetElementTypeName(entitySetElementType, container); + + string camelCaseEntitySetName = entitySet.Name; + if (this.context.EnableNamingAlias) + { + camelCaseEntitySetName = Customization.CustomizeNaming(camelCaseEntitySetName); + } + + this.WriteContextEntitySetProperty(camelCaseEntitySetName, GetFixedName(camelCaseEntitySetName), entitySet.Name, GetFixedName(entitySetElementTypeName)); + List edmNavigationSourceList = null; + if (!this.context.ElementTypeToNavigationSourceMap.TryGetValue(entitySet.EntityType(), out edmNavigationSourceList)) + { + edmNavigationSourceList = new List(); + this.context.ElementTypeToNavigationSourceMap.Add(entitySet.EntityType(), edmNavigationSourceList); + } + + edmNavigationSourceList.Add(entitySet); + } + + foreach (IEdmEntitySet entitySet in container.EntitySets()) + { + IEdmEntityType entitySetElementType = entitySet.EntityType(); + + string entitySetElementTypeName = GetElementTypeName(entitySetElementType, container); + + UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService(/*IsLanguageCaseSensitive*/true); + string parameterName = GetFixedName(uniqueIdentifierService.GetUniqueParameterName(entitySetElementType.Name)); + + string camelCaseEntitySetName = entitySet.Name; + if (this.context.EnableNamingAlias) + { + camelCaseEntitySetName = Customization.CustomizeNaming(camelCaseEntitySetName); + } + + this.WriteContextAddToEntitySetMethod(camelCaseEntitySetName, entitySet.Name, GetFixedName(entitySetElementTypeName), parameterName); + } + + foreach (IEdmSingleton singleton in container.Singletons()) + { + IEdmEntityType singletonElementType = singleton.EntityType(); + string singletonElementTypeName = GetElementTypeName(singletonElementType, container); + string camelCaseSingletonName = singleton.Name; + if (this.context.EnableNamingAlias) + { + camelCaseSingletonName = Customization.CustomizeNaming(camelCaseSingletonName); + } + + this.WriteContextSingletonProperty(camelCaseSingletonName, GetFixedName(camelCaseSingletonName), singleton.Name, singletonElementTypeName + "Single"); + + List edmNavigationSourceList = null; + if (this.context.ElementTypeToNavigationSourceMap.TryGetValue(singleton.EntityType(), out edmNavigationSourceList)) + { + edmNavigationSourceList.Add(singleton); + } + } + + this.WriteGeneratedEdmModel(Utils.SerializeToString(this.context.Edmx).Replace("\"", "\"\"")); + + bool hasOperationImport = container.OperationImports().OfType().Any(); + foreach (IEdmFunctionImport functionImport in container.OperationImports().OfType()) + { + string parameterString, parameterTypes, parameterExpressionString, parameterValues; + bool useEntityReference; + this.GetParameterStrings(false, false, functionImport.Function.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName = GetSourceOrReturnTypeName(functionImport.Function.ReturnType); + string returnTypeNameWithSingleSuffix = GetSourceOrReturnTypeName(functionImport.Function.ReturnType, true); + string fixedContainerName = this.GetFixedName(functionImport.Container.Name); + bool isCollectionResult = functionImport.Function.ReturnType.IsCollection(); + string functionImportName = functionImport.Name; + if (this.context.EnableNamingAlias) + { + functionImportName = Customization.CustomizeNaming(functionImportName); + fixedContainerName = Customization.CustomizeNaming(fixedContainerName); + } + + if (functionImport.Function.ReturnType.IsCollection()) + { + this.WriteFunctionImportReturnCollectionResult(this.GetFixedName(functionImportName), functionImport.Name, returnTypeName, parameterString, parameterValues, functionImport.Function.IsComposable, useEntityReference); + } + else + { + this.WriteFunctionImportReturnSingleResult(this.GetFixedName(functionImportName), functionImport.Name, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, parameterValues, functionImport.Function.IsComposable, functionImport.Function.ReturnType.IsEntity(), useEntityReference); + } + } + + foreach (IEdmActionImport actionImport in container.OperationImports().OfType()) + { + string parameterString, parameterTypes, parameterExpressionString, parameterValues; + bool useEntityReference; + this.GetParameterStrings(false, true, actionImport.Action.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName = null; + string fixedContainerName = this.GetFixedName(actionImport.Container.Name); + + if (actionImport.Action.ReturnType != null) + { + returnTypeName = GetSourceOrReturnTypeName(actionImport.Action.ReturnType); + if (actionImport.Action.ReturnType.IsCollection()) + { + returnTypeName = string.Format(this.DataServiceActionQueryOfTStructureTemplate, returnTypeName); + } + else + { + returnTypeName = string.Format(this.DataServiceActionQuerySingleOfTStructureTemplate, returnTypeName); + } + } + else + { + returnTypeName = this.DataServiceActionQueryTypeName; + } + + string actionImportName = actionImport.Name; + if (this.context.EnableNamingAlias) + { + actionImportName = Customization.CustomizeNaming(actionImportName); + fixedContainerName = Customization.CustomizeNaming(fixedContainerName); + } + + this.WriteActionImport(this.GetFixedName(actionImportName), actionImport.Name, returnTypeName, parameterString, parameterValues); + } + + this.WriteClassEndForEntityContainer(); + } + + internal void WriteEntityContainerConstructor(IEdmEntityContainer container) + { + string camelCaseContainerName = container.Name; + if (this.context.EnableNamingAlias) + { + camelCaseContainerName = Customization.CustomizeNaming(camelCaseContainerName); + } + + this.WriteMethodStartForEntityContainerConstructor(camelCaseContainerName, GetFixedName(camelCaseContainerName)); + + if (this.context.UseKeyAsSegmentUrlConvention(container)) + { + this.WriteKeyAsSegmentUrlConvention(); + } + + if (this.context.NeedResolveNameFromType) + { + this.WriteInitializeResolveName(); + } + + if (this.context.NeedResolveTypeFromName) + { + this.WriteInitializeResolveType(); + } + + this.WriteClassEndForEntityContainerConstructor(); + } + + internal void WriteResolveTypeFromName() + { + if (!this.context.NeedResolveTypeFromName) + { + return; + } + + this.WriteMethodStartForResolveTypeFromName(); + + // NOTE: since multiple namespaces can have the same prefix and match the namespace + // prefix condition, it's important that the prefix check is done is prefix-length + // order, starting with the longest prefix. + IEnumerable> namespaceToPrefixedNamespacePairs = this.context.NamespaceMap.OrderByDescending(p => p.Key.Length).ThenBy(p => p.Key); + + string typeName = this.SystemTypeTypeName + " "; + foreach(KeyValuePair namespaceToPrefixedNamespacePair in namespaceToPrefixedNamespacePairs) + { + this.WriteResolveNamespace(typeName, namespaceToPrefixedNamespacePair.Key, namespaceToPrefixedNamespacePair.Value); + typeName = string.Empty; + } + + this.WriteMethodEndForResolveTypeFromName(); + } + + internal void WriteResolveNameFromType(string containerName, string fullNamespace) + { + if (!this.context.NeedResolveNameFromType) + { + return; + } + + this.WriteMethodStartForResolveNameFromType(GetFixedName(containerName), fullNamespace); + + // NOTE: in this case order also matters, but the length of the CLR + // namespace is what needs to be considered. + IEnumerable> namespaceToPrefixedNamespacePairs = this.context.NamespaceMap.OrderByDescending(p => p.Value.Length).ThenBy(p => p.Key); + + foreach(KeyValuePair namespaceToPrefixedNamespacePair in namespaceToPrefixedNamespacePairs) + { + this.WriteResolveType(namespaceToPrefixedNamespacePair.Key, namespaceToPrefixedNamespacePair.Value); + } + + this.WriteMethodEndForResolveNameFromType(this.context.ModelHasInheritance); + } + + internal void WritePropertiesForSingleType(IEnumerable properties) + { + foreach (IEdmProperty property in properties.Where(i => i.PropertyKind == EdmPropertyKind.Navigation)) + { + string propertyType; + string propertyName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(property.Name) : property.Name; + if (property.Type is Microsoft.OData.Edm.EdmCollectionTypeReference) + { + propertyType = GetSourceOrReturnTypeName(property.Type); + WriteContextEntitySetProperty(propertyName, GetFixedName(propertyName), property.Name, propertyType, false); + } + else + { + propertyType = Utils.GetClrTypeName(property.Type, true, this, this.context, true, isEntitySingleType : true); + WriteContextSingletonProperty(propertyName, GetFixedName(propertyName), property.Name, propertyType, false); + } + } + } + + internal void WriteEntityType(IEdmEntityType entityType, Dictionary> boundOperationsMap) + { + string entityTypeName = ((IEdmSchemaElement)entityType).Name; + entityTypeName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(entityTypeName) : entityTypeName; + this.WriteSummaryCommentForStructuredType(entityTypeName + this.SingleSuffix); + this.WriteStructurdTypeDeclaration(entityType, + this.ClassInheritMarker + string.Format(this.DataServiceQuerySingleStructureTemplate, GetFixedName(entityTypeName)), + this.SingleSuffix); + string singleTypeName = (this.context.EnableNamingAlias ? + Customization.CustomizeNaming(((IEdmSchemaElement)entityType).Name) : ((IEdmSchemaElement)entityType).Name) + this.SingleSuffix; + this.WriteConstructorForSingleType(GetFixedName(singleTypeName), string.Format(this.DataServiceQuerySingleStructureTemplate, GetFixedName(entityTypeName))); + IEdmEntityType current = entityType; + while (current != null) + { + this.WritePropertiesForSingleType(current.DeclaredProperties); + current = (IEdmEntityType)current.BaseType; + } + + this.WriteClassEndForStructuredType(); + + this.WriteSummaryCommentForStructuredType(this.context.EnableNamingAlias ? Customization.CustomizeNaming(entityType.Name) : entityType.Name); + + if (entityType.Key().Any()) + { + IEnumerable keyProperties = entityType.Key().Select(k => k.Name); + this.WriteKeyPropertiesCommentAndAttribute( + this.context.EnableNamingAlias ? keyProperties.Select(k => Customization.CustomizeNaming(k)) : keyProperties, + string.Join("\", \"", keyProperties)); + } + else + { + this.WriteEntityTypeAttribute(); + } + + if (this.context.UseDataServiceCollection) + { + List navigationSourceList; + if (this.context.ElementTypeToNavigationSourceMap.TryGetValue(entityType, out navigationSourceList)) + { + if(navigationSourceList.Count == 1) + { + this.WriteEntitySetAttribute(navigationSourceList[0].Name); + } + } + } + + if (entityType.HasStream) + { + this.WriteEntityHasStreamAttribute(); + } + + this.WriteStructurdTypeDeclaration(entityType, this.BaseEntityType); + this.SetPropertyIdentifierMappingsIfNameConflicts(entityType.Name, entityType); + this.WriteTypeStaticCreateMethod(entityType.Name, entityType); + this.WritePropertiesForStructuredType(entityType.DeclaredProperties); + + if (entityType.BaseType == null && this.context.UseDataServiceCollection) + { + this.WriteINotifyPropertyChangedImplementation(); + } + + this.WriteBoundOperations(entityType, boundOperationsMap); + + this.WriteClassEndForStructuredType(); + } + + internal void WriteComplexType(IEdmComplexType complexType, Dictionary> boundOperationsMap) + { + this.WriteSummaryCommentForStructuredType(this.context.EnableNamingAlias ? Customization.CustomizeNaming(complexType.Name) : complexType.Name); + this.WriteStructurdTypeDeclaration(complexType, string.Empty); + this.SetPropertyIdentifierMappingsIfNameConflicts(complexType.Name, complexType); + this.WriteTypeStaticCreateMethod(complexType.Name, complexType); + this.WritePropertiesForStructuredType(complexType.DeclaredProperties); + + if (complexType.BaseType == null && this.context.UseDataServiceCollection) + { + this.WriteINotifyPropertyChangedImplementation(); + } + + this.WriteClassEndForStructuredType(); + } + + internal void WriteBoundOperations(IEdmStructuredType structuredType, Dictionary> boundOperationsMap) + { + List operations; + if (boundOperationsMap.TryGetValue(structuredType, out operations)) + { + foreach (IEdmFunction function in operations.OfType()) + { + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + bool hideBaseMethod = this.CheckMethodsInBaseClass(structuredType.BaseType, function, boundOperationsMap); + this.GetParameterStrings(function.IsBound, false, function.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName = GetSourceOrReturnTypeName(function.ReturnType); + string returnTypeNameWithSingleSuffix = GetSourceOrReturnTypeName(function.ReturnType, true); + string functionName = function.Name; + if (this.context.EnableNamingAlias) + { + functionName = Customization.CustomizeNaming(functionName); + } + + if (function.ReturnType.IsCollection()) + { + this.WriteBoundFunctionInEntityTypeReturnCollectionResult(hideBaseMethod, GetFixedName(functionName), function.Name, returnTypeName, parameterString, function.Namespace, parameterValues, function.IsComposable, useEntityReference); + } + else + { + this.WriteBoundFunctionInEntityTypeReturnSingleResult(hideBaseMethod, GetFixedName(functionName), function.Name, returnTypeName, returnTypeNameWithSingleSuffix, parameterString, function.Namespace, parameterValues, function.IsComposable, function.ReturnType.IsEntity(), useEntityReference); + } + } + + foreach (IEdmAction action in operations.OfType()) + { + string parameterString, parameterExpressionString, parameterTypes, parameterValues; + bool useEntityReference; + bool hideBaseMethod = this.CheckMethodsInBaseClass(structuredType.BaseType, action, boundOperationsMap); + this.GetParameterStrings(action.IsBound, true, action.Parameters.ToArray(), out parameterString, out parameterTypes, out parameterExpressionString, out parameterValues, out useEntityReference); + string returnTypeName; + if (action.ReturnType != null) + { + returnTypeName = GetSourceOrReturnTypeName(action.ReturnType); + if (action.ReturnType.IsCollection()) + { + returnTypeName = string.Format(this.DataServiceActionQueryOfTStructureTemplate, returnTypeName); + } + else + { + returnTypeName = string.Format(this.DataServiceActionQuerySingleOfTStructureTemplate, returnTypeName); + } + } + else + { + returnTypeName = this.DataServiceActionQueryTypeName; + } + + string actionName = action.Name; + if (this.context.EnableNamingAlias) + { + actionName = Customization.CustomizeNaming(actionName); + } + + this.WriteBoundActionInEntityType(hideBaseMethod, GetFixedName(actionName), action.Name, returnTypeName, parameterString, action.Namespace, parameterValues); + } + } + } + + internal bool CheckMethodsInBaseClass(IEdmStructuredType structuredType, IEdmOperation operation, Dictionary> boundOperationsMap) + { + if (structuredType != null) + { + List operations; + if (boundOperationsMap.TryGetValue(structuredType, out operations)) + { + foreach (IEdmOperation op in operations) + { + if (this.context.TargetLanguage == LanguageOption.VB) + { + if (operation.Name == op.Name) + { + return true; + } + } + + List targetParameter = operation.Parameters.ToList(); + List checkParameter = op.Parameters.ToList(); + if (operation.Name == op.Name && targetParameter.Count == checkParameter.Count) + { + bool areSame = true; + for (int i = 1; i < targetParameter.Count; ++i) + { + var targetParameterType = targetParameter[i].Type; + var checkParameterType = checkParameter[i].Type; + if (!targetParameterType.Definition.Equals(checkParameterType.Definition) + || targetParameterType.IsNullable != checkParameterType.IsNullable) + { + areSame = false; + break; + } + } + + if (areSame) + { + return true; + } + } + } + } + + return CheckMethodsInBaseClass(structuredType.BaseType, operation, boundOperationsMap); + } + + return false; + } + + internal void WriteEnumType(IEdmEnumType enumType) + { + this.WriteSummaryCommentForEnumType(this.context.EnableNamingAlias ? Customization.CustomizeNaming(enumType.Name) : enumType.Name); + if (enumType.IsFlags) + { + this.WriteEnumFlags(); + } + + string underlyingType = string.Empty; + if (enumType.UnderlyingType != null && enumType.UnderlyingType.PrimitiveKind != EdmPrimitiveTypeKind.Int32) + { + underlyingType = Utils.GetClrTypeName(enumType.UnderlyingType, this); + underlyingType = this.EnumUnderlyingTypeMarker + underlyingType; + } + + this.WriteEnumDeclaration(this.context.EnableNamingAlias ? GetFixedName(Customization.CustomizeNaming(enumType.Name)) : GetFixedName(enumType.Name), enumType.Name, underlyingType); + this.WriteMembersForEnumType(enumType.Members); + this.WriteEnumEnd(); + } + + internal void WriteStructurdTypeDeclaration(IEdmStructuredType structuredType, string baseEntityType, string typeNameSuffix = null) + { + string abstractModifier = structuredType.IsAbstract && typeNameSuffix == null ? this.AbstractModifier : string.Empty; + string baseTypeName = baseEntityType; + + if (typeNameSuffix == null) + { + if (structuredType.BaseType == null) + { + if (this.context.UseDataServiceCollection) + { + if (this.context.TargetLanguage == LanguageOption.CSharp) + { + baseTypeName += string.IsNullOrEmpty(baseTypeName) ? this.ClassInheritMarker : ", "; + } + + baseTypeName += this.NotifyPropertyChangedModifier; + } + } + else + { + IEdmSchemaElement baseType = (IEdmSchemaElement)structuredType.BaseType; + string baseTypeFixedName = this.context.EnableNamingAlias ? GetFixedName(Customization.CustomizeNaming(baseType.Name)) : GetFixedName(baseType.Name); + baseTypeName = ((IEdmSchemaElement)structuredType).Namespace == baseType.Namespace ? baseTypeFixedName : this.context.GetPrefixedFullName(baseType, baseTypeFixedName, this); + baseTypeName = this.ClassInheritMarker + baseTypeName; + } + } + + string structuredTypeName = this.context.EnableNamingAlias ? + Customization.CustomizeNaming(((IEdmSchemaElement)structuredType).Name) : ((IEdmSchemaElement)structuredType).Name; + this.WriteClassStartForStructuredType(abstractModifier, GetFixedName(structuredTypeName + typeNameSuffix), ((IEdmSchemaElement)structuredType).Name + typeNameSuffix, baseTypeName); + } + + internal string GetSourceOrReturnTypeName(IEdmTypeReference typeReference, bool isEntitySingleType = false) + { + IEdmCollectionType edmCollectionType = typeReference.Definition as IEdmCollectionType; + bool addNullableTemplate = true; + if (edmCollectionType != null) + { + typeReference = edmCollectionType.ElementType; + addNullableTemplate = false; + } + + return Utils.GetClrTypeName(typeReference, this.context.UseDataServiceCollection, this, this.context, addNullableTemplate, isEntitySingleType:isEntitySingleType); + } + + internal void GetParameterStrings(bool isBound, bool isAction, IEdmOperationParameter[] parameters, out string parameterString, out string parameterTypes, out string parameterExpressionString, out string parameterValues, out bool useEntityReference) + { + parameterString = string.Empty; + parameterExpressionString = string.Empty; + parameterTypes = string.Empty; + parameterValues = string.Empty; + useEntityReference = false; + + int n = parameters.Count(); + for (int i = isBound ? 1 : 0; i < n; ++i) + { + IEdmOperationParameter param = parameters[i]; + if (i == (isBound ? 1 : 0)) + { + if (this.context.TargetLanguage == LanguageOption.CSharp) + { + parameterExpressionString += "\r\n "; + } + else + { + parameterExpressionString += "\r\n "; + } + } + + string typeName = Utils.GetClrTypeName(param.Type, this.context.UseDataServiceCollection, this, this.context, true, true, true); + if (this.context.TargetLanguage == LanguageOption.CSharp) + { + parameterString += typeName; + parameterString += (" " + GetFixedName(param.Name)); + } + else if (this.context.TargetLanguage == LanguageOption.VB) + { + parameterString += GetFixedName(param.Name); + parameterString += (this.EnumUnderlyingTypeMarker + typeName); + } + + parameterString += i == n - 1 ? string.Empty : ", "; + parameterTypes += string.Format(this.TypeofFormatter, typeName) + ", "; + parameterExpressionString += this.GetParameterExpressionString(param, typeName) + ", "; + + if (i != (isBound ? 1 : 0)) + { + parameterValues += ",\r\n "; + } + + if (isAction) + { + parameterValues += string.Format(this.BodyOperationParameterConstructor, param.Name, GetFixedName(param.Name)); + } + else if (param.Type.IsEntity() || (param.Type.IsCollection() && param.Type.AsCollection().ElementType().IsEntity())) + { + useEntityReference = true; + parameterValues += string.Format(this.UriEntityOperationParameterConstructor, param.Name, GetFixedName(param.Name),"useEntityReference"); + } + else + { + parameterValues += string.Format(this.UriOperationParameterConstructor, param.Name, GetFixedName(param.Name)); + } + } + } + + internal string GetParameterExpressionString(IEdmOperationParameter param, string typeName) + { + string clrTypeName; + IEdmType edmType = param.Type.Definition; + IEdmPrimitiveType edmPrimitiveType = edmType as IEdmPrimitiveType; + if (edmPrimitiveType != null) + { + clrTypeName = Utils.GetClrTypeName(edmPrimitiveType, this); + if (param.Type.IsNullable && !this.ClrReferenceTypes.Contains(edmPrimitiveType.PrimitiveKind)) + { + clrTypeName += "?"; + } + + return string.Format(this.ConstantExpressionConstructorWithType, GetFixedName(param.Name), clrTypeName); + } + + return string.Format(this.ConstantExpressionConstructorWithType, GetFixedName(param.Name), typeName); + } + + // This is to solve duplicate names between property and type + internal void SetPropertyIdentifierMappingsIfNameConflicts(string typeName, IEdmStructuredType structuredType) + { + if (this.context.EnableNamingAlias) + { + typeName = Customization.CustomizeNaming(typeName); + } + + // PropertyName in VB is case-insensitive. + bool isLanguageCaseSensitive = this.context.TargetLanguage == LanguageOption.CSharp; + + // In VB, it is allowed that a type has a property whose name is same with the type's name + bool allowPropertyNameSameWithTypeName = this.context.TargetLanguage == LanguageOption.VB; + + Func customizePropertyName = (name) => { return this.context.EnableNamingAlias ? Customization.CustomizeNaming(name) : name; }; + + var propertyGroups = structuredType.Properties() + .GroupBy(p => isLanguageCaseSensitive ? customizePropertyName(p.Name) : customizePropertyName(p.Name).ToUpperInvariant()); + + // If the group contains more than one property, or the property in the group has the same name with the type (only for C#), we need to rename the property + var propertyToBeRenamedGroups = propertyGroups.Where(g => g.Count() > 1 || !allowPropertyNameSameWithTypeName && g.Key == typeName); + + var knownIdentifiers = propertyGroups.Select(g => customizePropertyName(g.First().Name)).ToList(); + if(!allowPropertyNameSameWithTypeName && !knownIdentifiers.Contains(typeName)) + { + knownIdentifiers.Add(typeName); + } + UniqueIdentifierService uniqueIdentifierService = + new UniqueIdentifierService(knownIdentifiers, isLanguageCaseSensitive); + + IdentifierMappings.Clear(); + foreach (IGrouping g in propertyToBeRenamedGroups) + { + bool hasPropertyNameSameWithCustomizedPropertyName = false; + int itemCount = g.Count(); + for (int i = 0; i < itemCount; i++) + { + var property = g.ElementAt(i); + var customizedPropertyName = customizePropertyName(property.Name); + + if(this.context.EnableNamingAlias && customizedPropertyName == property.Name) + { + hasPropertyNameSameWithCustomizedPropertyName = true; + } + + if(isLanguageCaseSensitive) + { + // If a property name is same as its customized property name, then we don't rename it. + // Or we don't rename the last property in the group + if(customizedPropertyName != typeName + && (customizedPropertyName == property.Name + || (!hasPropertyNameSameWithCustomizedPropertyName && i == itemCount-1))) + { + continue; + } + } + else + { + // When EnableNamingAlias = true, If a property name is same as its customized property name, then we don't rename it. + // Or we don't rename the last property in the group. + if((this.context.EnableNamingAlias && customizedPropertyName == property.Name) + || (!hasPropertyNameSameWithCustomizedPropertyName && i == itemCount-1)) + { + continue; + } + } + var renamedPropertyName = uniqueIdentifierService.GetUniqueIdentifier(customizedPropertyName); + IdentifierMappings.Add(property.Name, renamedPropertyName); + } + } + } + + internal void WriteTypeStaticCreateMethod(string typeName, IEdmStructuredType structuredType) + { + Debug.Assert(structuredType != null, "structuredType != null"); + if (structuredType.IsAbstract) + { + return; + } + + Func hasDefault = p => p.PropertyKind == EdmPropertyKind.Structural && ((IEdmStructuralProperty)p).DefaultValueString != null; + + if (this.context.EnableNamingAlias) + { + typeName = Customization.CustomizeNaming(typeName); + } + + IEnumerable parameters = structuredType.Properties() + .Where(p => !p.Type.IsNullable && !p.Type.IsCollection() && !hasDefault(p)); + if (!parameters.Any()) + { + return; + } + + this.WriteSummaryCommentForStaticCreateMethod(typeName); + + UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService( /*IsLanguageCaseSensitive*/true); + string instanceName = GetFixedName(uniqueIdentifierService.GetUniqueParameterName(typeName)); + KeyValuePair[] propertyToParameterNamePairs = parameters + .Select(p => + new KeyValuePair(p, + uniqueIdentifierService.GetUniqueParameterName( + IdentifierMappings.ContainsKey(p.Name) ? IdentifierMappings[p.Name] : p.Name))) + .ToArray(); + + foreach (var propertyToParameterNamePair in propertyToParameterNamePairs) + { + string propertyName = propertyToParameterNamePair.Key.Name; + propertyName = IdentifierMappings.ContainsKey(propertyName) ? + IdentifierMappings[propertyName] : (this.context.EnableNamingAlias ? Customization.CustomizeNaming(propertyName) : propertyName); + this.WriteParameterCommentForStaticCreateMethod(propertyToParameterNamePair.Value, propertyName); + } + + propertyToParameterNamePairs = propertyToParameterNamePairs + .Select(p => p = new KeyValuePair(p.Key, GetFixedName(p.Value))) + .ToArray(); + + this.WriteDeclarationStartForStaticCreateMethod(typeName, GetFixedName(typeName)); + this.WriteStaticCreateMethodParameters(propertyToParameterNamePairs); + this.WriteDeclarationEndForStaticCreateMethod(GetFixedName(typeName), instanceName); + + foreach (var propertyToParameterNamePair in propertyToParameterNamePairs) + { + IEdmProperty property = propertyToParameterNamePair.Key; + string parameterName = propertyToParameterNamePair.Value; + + Debug.Assert(!property.Type.IsCollection(), "!property.Type.IsCollection()"); + Debug.Assert(!property.Type.IsNullable, "!property.Type.IsNullable"); + + // The static create method only sets non-nullable properties. We should add the null check if the type of the property is not a clr ValueType. + // For now we add the null check if the property type is non-primitive. We should add the null check for non-ValueType primitives in the future. + if (!property.Type.IsPrimitive() && !property.Type.IsEnum()) + { + this.WriteParameterNullCheckForStaticCreateMethod(parameterName); + } + + var uniqIdentifier = IdentifierMappings.ContainsKey(property.Name) ? + IdentifierMappings[property.Name] : (this.context.EnableNamingAlias ? Customization.CustomizeNaming(property.Name) : property.Name); + this.WritePropertyValueAssignmentForStaticCreateMethod(instanceName, + GetFixedName(uniqIdentifier), + parameterName); + } + + this.WriteMethodEndForStaticCreateMethod(instanceName); + } + + internal void WriteStaticCreateMethodParameters(KeyValuePair[] propertyToParameterPairs) + { + if (propertyToParameterPairs.Length == 0) + { + return; + } + + // If the number of parameters are greater than 5, we put them in separate lines. + string parameterSeparator = propertyToParameterPairs.Length > 5 ? this.ParameterSeparator : ", "; + for (int idx = 0; idx < propertyToParameterPairs.Length; idx++) + { + KeyValuePair propertyToParameterPair = propertyToParameterPairs[idx]; + + string parameterType = Utils.GetClrTypeName(propertyToParameterPair.Key.Type, this.context.UseDataServiceCollection, this, this.context); + string parameterName = propertyToParameterPair.Value; + if (idx == propertyToParameterPairs.Length - 1) + { + // No separator after the last parameter. + parameterSeparator = string.Empty; + } + + this.WriteParameterForStaticCreateMethod(parameterType, GetFixedName(parameterName), parameterSeparator); + } + } + + internal void WritePropertiesForStructuredType(IEnumerable properties) + { + bool useDataServiceCollection = this.context.UseDataServiceCollection; + + var propertyInfos = properties.Select(property => + { + string propertyName = IdentifierMappings.ContainsKey(property.Name) ? + IdentifierMappings[property.Name] : (this.context.EnableNamingAlias ? Customization.CustomizeNaming(property.Name) : property.Name); + + return new + { + PropertyType = Utils.GetClrTypeName(property.Type, useDataServiceCollection, this, this.context), + PropertyVanillaName = property.Name, + PropertyName = propertyName, + FixedPropertyName = GetFixedName(propertyName), + PrivatePropertyName = "_" + propertyName, + PropertyInitializationValue = Utils.GetPropertyInitializationValue(property, useDataServiceCollection, this, this.context) + }; + }).ToList(); + + // Private name should not confict with field name + UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService(propertyInfos.Select(_ => _.FixedPropertyName), + this.context.TargetLanguage == LanguageOption.CSharp); + + foreach (var propertyInfo in propertyInfos) + { + string privatePropertyName = uniqueIdentifierService.GetUniqueIdentifier("_" + propertyInfo.PropertyName); + + this.WritePropertyForStructuredType( + propertyInfo.PropertyType, + propertyInfo.PropertyVanillaName, + propertyInfo.PropertyName, + propertyInfo.FixedPropertyName, + privatePropertyName, + propertyInfo.PropertyInitializationValue, + useDataServiceCollection); + } + } + + internal void WriteMembersForEnumType(IEnumerable members) + { + int n = members.Count(); + for (int idx = 0; idx < n; ++idx) + { + IEdmEnumMember member = members.ElementAt(idx); + string value = string.Empty; + if (member.Value != null) + { + IEdmEnumMemberValue integerValue = member.Value as IEdmEnumMemberValue; + if (integerValue != null) + { + value = " = " + integerValue.Value.ToString(CultureInfo.InvariantCulture); + } + } + + string memberName = this.context.EnableNamingAlias ? Customization.CustomizeNaming(member.Name) : member.Name; + this.WriteMemberForEnumType(GetFixedName(memberName) + value, member.Name, idx == n - 1); + } + } + + internal string GetFixedName(string originalName) + { + string fixedName = originalName; + + if (this.LanguageKeywords.Contains(fixedName)) + { + fixedName = string.Format(this.FixPattern, fixedName); + } + + return fixedName; + } + + internal string GetElementTypeName(IEdmEntityType elementType, IEdmEntityContainer container) + { + string elementTypeName = elementType.Name; + + if (this.context.EnableNamingAlias) + { + elementTypeName = Customization.CustomizeNaming(elementTypeName); + } + + if (elementType.Namespace != container.Namespace) + { + elementTypeName = this.context.GetPrefixedFullName(elementType, GetFixedName(elementTypeName), this); + } + + return elementTypeName; + } +} + +/// +/// Base class for text transformation +/// +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "11.0.0.0")] +public abstract class TemplateBase +{ + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + + /// + /// Create the template output + /// + public abstract string TransformText(); + + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion +} + +/// +/// Service making names within a scope unique. Initialize a new instance for every scope. +/// +internal sealed class UniqueIdentifierService +{ + // This is the list of keywords we check against when creating parameter names from propert. + // If a name matches this keyword we prefix it. + private static readonly string[] Keywords = new string[] {"class", "event"}; + + /// + /// Hash set to detect identifier collision. + /// + private readonly HashSet knownIdentifiers; + + /// + /// Constructs a . + /// + /// true if the language we are generating the code for is case sensitive, false otherwise. + internal UniqueIdentifierService(bool caseSensitive) + { + this.knownIdentifiers = new HashSet(caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + } + + /// + /// Constructs a . + /// + /// identifiers used to detect collision. + /// true if the language we are generating the code for is case sensitive, false otherwise. + internal UniqueIdentifierService(IEnumerable identifiers, bool caseSensitive) + { + this.knownIdentifiers = new HashSet(identifiers ?? Enumerable.Empty(), caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + } + + /// + /// Given an identifier, makes it unique within the scope by adding + /// a suffix (1, 2, 3, ...), and returns the adjusted identifier. + /// + /// Identifier. Must not be null or empty. + /// Identifier adjusted to be unique within the scope. + internal string GetUniqueIdentifier(string identifier) + { + Debug.Assert(!string.IsNullOrEmpty(identifier), "identifier is null or empty"); + + // find a unique name by adding suffix as necessary + int numberOfConflicts = 0; + string uniqueIdentifier = identifier; + while (this.knownIdentifiers.Contains(uniqueIdentifier)) + { + ++numberOfConflicts; + uniqueIdentifier = identifier + numberOfConflicts.ToString(CultureInfo.InvariantCulture); + } + + // remember the identifier in this scope + Debug.Assert(!this.knownIdentifiers.Contains(uniqueIdentifier), "we just made it unique"); + this.knownIdentifiers.Add(uniqueIdentifier); + + return uniqueIdentifier; + } + + /// + /// Fix up the given parameter name and make it unique. + /// + /// Parameter name. + /// Fixed parameter name. + internal string GetUniqueParameterName(string name) + { + name = Utils.CamelCase(name); + + // FxCop consider 'iD' as violation, we will change any property that is 'id'(case insensitive) to 'ID' + if (StringComparer.OrdinalIgnoreCase.Equals(name, "id")) + { + name = "ID"; + } + + return this.GetUniqueIdentifier(name); + } +} + +/// +/// Utility class. +/// +internal static class Utils +{ + /// + /// Serializes the xml element to a string. + /// + /// The xml element to serialize. + /// The string representation of the xml. + internal static string SerializeToString(XElement xml) + { + // because comment nodes can contain special characters that are hard to embed in VisualBasic, remove them here + xml.DescendantNodes().OfType().Remove(); + + var stringBuilder = new StringBuilder(); + using (var writer = XmlWriter.Create( + stringBuilder, + new XmlWriterSettings + { + OmitXmlDeclaration = true, + NewLineHandling = NewLineHandling.Replace, + Indent = true, + })) + { + xml.WriteTo(writer); + } + + return stringBuilder.ToString(); + } + + /// + /// Changes the text to use camel case, which lower case for the first character. + /// + /// Text to convert. + /// The converted text in camel case + internal static string CamelCase(string text) + { + if (string.IsNullOrEmpty(text)) + { + return text; + } + + if (text.Length == 1) + { + return text[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant(); + } + + return text[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant() + text.Substring(1); + } + + /// + /// Changes the text to use pascal case, which upper case for the first character. + /// + /// Text to convert. + /// The converted text in pascal case + internal static string PascalCase(string text) + { + if (string.IsNullOrEmpty(text)) + { + return text; + } + + if (text.Length == 1) + { + return text[0].ToString(CultureInfo.InvariantCulture).ToUpperInvariant(); + } + + return text[0].ToString(CultureInfo.InvariantCulture).ToUpperInvariant() + text.Substring(1); + } + + /// + /// Gets the clr type name from the give type reference. + /// + /// The type reference in question. + /// true to use the DataServicCollection type for entity collections and the ObservableCollection type for non-entity collections, + /// false to use Collection for collections. + /// ODataClientTemplate instance that call this method. + /// CodeGenerationContext instance in the clientTemplate. + /// This flag indicates whether to return the type name in nullable format + /// The flag indicates whether the namespace need to be added by global prefix + /// This flag indicates whether the edmTypeReference is for an operation parameter + /// The clr type name of the type reference. + internal static string GetClrTypeName(IEdmTypeReference edmTypeReference, bool useDataServiceCollection, ODataClientTemplate clientTemplate, CodeGenerationContext context, bool addNullableTemplate = true, bool needGlobalPrefix = true, bool isOperationParameter = false, bool isEntitySingleType = false) + { + string clrTypeName; + IEdmType edmType = edmTypeReference.Definition; + IEdmPrimitiveType edmPrimitiveType = edmType as IEdmPrimitiveType; + if (edmPrimitiveType != null) + { + clrTypeName = Utils.GetClrTypeName(edmPrimitiveType, clientTemplate); + if (edmTypeReference.IsNullable && !clientTemplate.ClrReferenceTypes.Contains(edmPrimitiveType.PrimitiveKind) && addNullableTemplate) + { + clrTypeName = string.Format(clientTemplate.SystemNullableStructureTemplate, clrTypeName); + } + } + else + { + IEdmComplexType edmComplexType = edmType as IEdmComplexType; + if (edmComplexType != null) + { + clrTypeName = context.GetPrefixedFullName(edmComplexType, + context.EnableNamingAlias ? clientTemplate.GetFixedName(Customization.CustomizeNaming(edmComplexType.Name)) : clientTemplate.GetFixedName(edmComplexType.Name), clientTemplate); + } + else + { + IEdmEnumType edmEnumType = edmType as IEdmEnumType; + if (edmEnumType != null) + { + clrTypeName = context.GetPrefixedFullName(edmEnumType, + context.EnableNamingAlias ? clientTemplate.GetFixedName(Customization.CustomizeNaming(edmEnumType.Name)) : clientTemplate.GetFixedName(edmEnumType.Name), clientTemplate, needGlobalPrefix); + if (edmTypeReference.IsNullable && addNullableTemplate) + { + clrTypeName = string.Format(clientTemplate.SystemNullableStructureTemplate, clrTypeName); + } + } + else + { + IEdmEntityType edmEntityType = edmType as IEdmEntityType; + if (edmEntityType != null) + { + clrTypeName = context.GetPrefixedFullName(edmEntityType, + context.EnableNamingAlias + ? clientTemplate.GetFixedName(Customization.CustomizeNaming(edmEntityType.Name) + (isEntitySingleType ? clientTemplate.SingleSuffix : string.Empty)) + : clientTemplate.GetFixedName(edmEntityType.Name + (isEntitySingleType ? clientTemplate.SingleSuffix : string.Empty)), + clientTemplate); + } + else + { + IEdmCollectionType edmCollectionType = (IEdmCollectionType)edmType; + IEdmTypeReference elementTypeReference = edmCollectionType.ElementType; + IEdmPrimitiveType primitiveElementType = elementTypeReference.Definition as IEdmPrimitiveType; + if (primitiveElementType != null) + { + clrTypeName = Utils.GetClrTypeName(primitiveElementType, clientTemplate); + } + else + { + IEdmSchemaElement schemaElement = (IEdmSchemaElement)elementTypeReference.Definition; + clrTypeName = context.GetPrefixedFullName(schemaElement, + context.EnableNamingAlias ? clientTemplate.GetFixedName(Customization.CustomizeNaming(schemaElement.Name)) : clientTemplate.GetFixedName(schemaElement.Name), clientTemplate); + } + + string collectionTypeName = isOperationParameter + ? clientTemplate.ICollectionOfTStructureTemplate + : (useDataServiceCollection + ? (elementTypeReference.TypeKind() == EdmTypeKind.Entity + ? clientTemplate.DataServiceCollectionStructureTemplate + : clientTemplate.ObservableCollectionStructureTemplate) + : clientTemplate.ObjectModelCollectionStructureTemplate); + + clrTypeName = string.Format(collectionTypeName, clrTypeName); + } + } + } + } + + return clrTypeName; + } + + /// + /// Gets the value expression to initualize the property with. + /// + /// The property in question. + /// true to use the DataServicCollection type for entity collections and the ObservableCollection type for non-entity collections, + /// false to use Collection for collections. + /// ODataClientTemplate instance that call this method. + /// CodeGenerationContext instance in the clientTemplate. + /// The value expression to initualize the property with. + internal static string GetPropertyInitializationValue(IEdmProperty property, bool useDataServiceCollection, ODataClientTemplate clientTemplate, CodeGenerationContext context) + { + IEdmTypeReference edmTypeReference = property.Type; + IEdmCollectionTypeReference edmCollectionTypeReference = edmTypeReference as IEdmCollectionTypeReference; + if (edmCollectionTypeReference == null) + { + IEdmStructuralProperty structuredProperty = property as IEdmStructuralProperty; + if (structuredProperty != null) + { + if (!string.IsNullOrEmpty(structuredProperty.DefaultValueString)) + { + string valueClrType = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); + string defaultValue = structuredProperty.DefaultValueString; + bool isCSharpTemplate = clientTemplate is ODataClientCSharpTemplate; + if (edmTypeReference.Definition.TypeKind == EdmTypeKind.Enum) + { + var enumValues = defaultValue.Split(','); + string fullenumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); + string enumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context, false, false); + List customizedEnumValues = new List(); + foreach(var enumValue in enumValues) + { + string currentEnumValue = enumValue.Trim(); + int indexFirst = currentEnumValue.IndexOf('\'') + 1; + int indexLast = currentEnumValue.LastIndexOf('\''); + if (indexFirst > 0 && indexLast > indexFirst) + { + currentEnumValue = currentEnumValue.Substring(indexFirst, indexLast - indexFirst); + } + + var customizedEnumValue = context.EnableNamingAlias ? Customization.CustomizeNaming(currentEnumValue) : currentEnumValue; + if (isCSharpTemplate) + { + currentEnumValue = "(" + fullenumTypeName + ")" + clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + customizedEnumValue + "\")"; + } + else + { + currentEnumValue = clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + currentEnumValue + "\")"; + } + customizedEnumValues.Add(currentEnumValue); + } + if (isCSharpTemplate) + { + return string.Join(" | ", customizedEnumValues); + } + else + { + return string.Join(" Or ", customizedEnumValues); + } + } + + if (valueClrType.Equals(clientTemplate.StringTypeName)) + { + defaultValue = "\"" + defaultValue + "\""; + } + else if (valueClrType.Equals(clientTemplate.BinaryTypeName)) + { + defaultValue = "System.Text.Encoding.UTF8.GetBytes(\"" + defaultValue + "\")"; + } + else if (valueClrType.Equals(clientTemplate.SingleTypeName)) + { + if (isCSharpTemplate) + { + defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "f"; + } + else + { + defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "F"; + } + } + else if (valueClrType.Equals(clientTemplate.DecimalTypeName)) + { + if (isCSharpTemplate) + { + // decimal in C# must be initialized with 'm' at the end, like Decimal dec = 3.00m + defaultValue = defaultValue.EndsWith("m", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "m"; + } + else + { + // decimal in VB must be initialized with 'D' at the end, like Decimal dec = 3.00D + defaultValue = defaultValue.ToLower().Replace("m", "D"); + defaultValue = defaultValue.EndsWith("D", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "D"; + } + } + else if (valueClrType.Equals(clientTemplate.GuidTypeName) + | valueClrType.Equals(clientTemplate.DateTimeOffsetTypeName) + | valueClrType.Equals(clientTemplate.DateTypeName) + | valueClrType.Equals(clientTemplate.TimeOfDayTypeName)) + { + defaultValue = valueClrType + ".Parse(\"" + defaultValue + "\")"; + } + else if (valueClrType.Equals(clientTemplate.DurationTypeName)) + { + defaultValue = clientTemplate.XmlConvertClassName + ".ToTimeSpan(\"" + defaultValue + "\")"; + } + else if (valueClrType.Contains("Microsoft.Spatial")) + { + defaultValue = string.Format(clientTemplate.GeoTypeInitializePattern, valueClrType, defaultValue); + } + + return defaultValue; + } + else + { + // doesn't have a default value + return null; + } + } + else + { + // only structured property has default value + return null; + } + } + else + { + string constructorParameters; + if (edmCollectionTypeReference.ElementType().IsEntity() && useDataServiceCollection) + { + constructorParameters = clientTemplate.DataServiceCollectionConstructorParameters; + } + else + { + constructorParameters = "()"; + } + + string clrTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); + return clientTemplate.NewModifier + clrTypeName + constructorParameters; + } + } + + /// + /// Gets the clr type name from the give Edm primitive type. + /// + /// The Edm primitive type in question. + /// ODataClientTemplate instance that call this method. + /// The clr type name of the Edm primitive type. + internal static string GetClrTypeName(IEdmPrimitiveType edmPrimitiveType, ODataClientTemplate clientTemplate) + { + EdmPrimitiveTypeKind kind = edmPrimitiveType.PrimitiveKind; + + string type="UNKNOWN"; + if (kind==EdmPrimitiveTypeKind.Int32) + { + type= clientTemplate.Int32TypeName; + } + else if (kind== EdmPrimitiveTypeKind.String) + { + type= clientTemplate.StringTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Binary) + { + type= clientTemplate.BinaryTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Decimal) + { + type= clientTemplate.DecimalTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Int16) + { + type= clientTemplate.Int16TypeName; + } + else if(kind==EdmPrimitiveTypeKind.Single) + { + type= clientTemplate.SingleTypeName; + } + else if (kind==EdmPrimitiveTypeKind.Boolean) + { + type= clientTemplate.BooleanTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Double) + { + type= clientTemplate.DoubleTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Guid) + { + type= clientTemplate.GuidTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Byte) + { + type= clientTemplate.ByteTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Int64) + { + type= clientTemplate.Int64TypeName; + } + else if (kind== EdmPrimitiveTypeKind.SByte) + { + type= clientTemplate.SByteTypeName; + } + else if (kind == EdmPrimitiveTypeKind.Stream) + { + type= clientTemplate.DataServiceStreamLinkTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Geography) + { + type= clientTemplate.GeographyTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyPoint) + { + type= clientTemplate.GeographyPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyLineString) + { + type= clientTemplate.GeographyLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyPolygon) + { + type= clientTemplate.GeographyPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyCollection) + { + type= clientTemplate.GeographyCollectionTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyMultiPolygon) + { + type= clientTemplate.GeographyMultiPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyMultiLineString) + { + type= clientTemplate.GeographyMultiLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeographyMultiPoint) + { + type= clientTemplate.GeographyMultiPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Geometry) + { + type= clientTemplate.GeometryTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryPoint) + { + type= clientTemplate.GeometryPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryLineString) + { + type= clientTemplate.GeometryLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryPolygon) + { + type= clientTemplate.GeometryPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryCollection) + { + type= clientTemplate.GeometryCollectionTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryMultiPolygon) + { + type= clientTemplate.GeometryMultiPolygonTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryMultiLineString) + { + type= clientTemplate.GeometryMultiLineStringTypeName; + } + else if (kind== EdmPrimitiveTypeKind.GeometryMultiPoint) + { + type= clientTemplate.GeometryMultiPointTypeName; + } + else if (kind== EdmPrimitiveTypeKind.DateTimeOffset) + { + type= clientTemplate.DateTimeOffsetTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Duration) + { + type= clientTemplate.DurationTypeName; + } + else if (kind== EdmPrimitiveTypeKind.Date) + { + type= clientTemplate.DateTypeName; + } + else if (kind== EdmPrimitiveTypeKind.TimeOfDay) + { + type= clientTemplate.TimeOfDayTypeName; + } + else + { + throw new Exception("Type "+kind.ToString()+" is unrecognized"); + } + + return type; + } +} + +public sealed class ODataClientCSharpTemplate : ODataClientTemplate +{ + /// + /// Creates an instance of the ODataClientTemplate. + /// + /// The code generation context. + public ODataClientCSharpTemplate(CodeGenerationContext context) + : base(context) + { + } + + internal override string GlobalPrefix { get {return "global::"; } } + internal override string SystemTypeTypeName { get { return "global::System.Type"; } } + internal override string AbstractModifier { get { return " abstract"; } } + internal override string DataServiceActionQueryTypeName { get { return "global::Microsoft.OData.Client.DataServiceActionQuery"; } } + internal override string DataServiceActionQuerySingleOfTStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceActionQuerySingle<{0}>"; } } + internal override string DataServiceActionQueryOfTStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceActionQuery<{0}>"; } } + internal override string NotifyPropertyChangedModifier { get { return "global::System.ComponentModel.INotifyPropertyChanged"; } } + internal override string ClassInheritMarker { get { return " : "; } } + internal override string ParameterSeparator { get { return ", \r\n "; } } + internal override string KeyParameterSeparator { get { return ", \r\n "; } } + internal override string KeyDictionaryItemSeparator { get { return ", \r\n "; } } + internal override string SystemNullableStructureTemplate { get { return "global::System.Nullable<{0}>"; } } + internal override string ICollectionOfTStructureTemplate { get { return "global::System.Collections.Generic.ICollection<{0}>"; } } + internal override string DataServiceCollectionStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceCollection<{0}>"; } } + internal override string DataServiceQueryStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceQuery<{0}>"; } } + internal override string DataServiceQuerySingleStructureTemplate { get { return "global::Microsoft.OData.Client.DataServiceQuerySingle<{0}>"; } } + internal override string ObservableCollectionStructureTemplate { get { return "global::System.Collections.ObjectModel.ObservableCollection<{0}>"; } } + internal override string ObjectModelCollectionStructureTemplate { get { return "global::System.Collections.ObjectModel.Collection<{0}>"; } } + internal override string DataServiceCollectionConstructorParameters { get { return "(null, global::Microsoft.OData.Client.TrackingMode.None)"; } } + internal override string NewModifier { get { return "new "; } } + internal override string GeoTypeInitializePattern { get { return "global::Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(false).Read<{0}>(new global::System.IO.StringReader(\"{1}\"))"; } } + internal override string Int32TypeName { get { return "int"; } } + internal override string StringTypeName { get { return "string"; } } + internal override string BinaryTypeName { get { return "byte[]"; } } + internal override string DecimalTypeName { get { return "decimal"; } } + internal override string Int16TypeName { get { return "short"; } } + internal override string SingleTypeName { get { return "float"; } } + internal override string BooleanTypeName { get { return "bool"; } } + internal override string DoubleTypeName { get { return "double"; } } + internal override string GuidTypeName { get { return "global::System.Guid"; } } + internal override string ByteTypeName { get { return "byte"; } } + internal override string Int64TypeName { get { return "long"; } } + internal override string SByteTypeName { get { return "sbyte"; } } + internal override string DataServiceStreamLinkTypeName { get { return "global::Microsoft.OData.Client.DataServiceStreamLink"; } } + internal override string GeographyTypeName { get { return "global::Microsoft.Spatial.Geography"; } } + internal override string GeographyPointTypeName { get { return "global::Microsoft.Spatial.GeographyPoint"; } } + internal override string GeographyLineStringTypeName { get { return "global::Microsoft.Spatial.GeographyLineString"; } } + internal override string GeographyPolygonTypeName { get { return "global::Microsoft.Spatial.GeographyPolygon"; } } + internal override string GeographyCollectionTypeName { get { return "global::Microsoft.Spatial.GeographyCollection"; } } + internal override string GeographyMultiPolygonTypeName { get { return "global::Microsoft.Spatial.GeographyMultiPolygon"; } } + internal override string GeographyMultiLineStringTypeName { get { return "global::Microsoft.Spatial.GeographyMultiLineString"; } } + internal override string GeographyMultiPointTypeName { get { return "global::Microsoft.Spatial.GeographyMultiPoint"; } } + internal override string GeometryTypeName { get { return "global::Microsoft.Spatial.Geometry"; } } + internal override string GeometryPointTypeName { get { return "global::Microsoft.Spatial.GeometryPoint"; } } + internal override string GeometryLineStringTypeName { get { return "global::Microsoft.Spatial.GeometryLineString"; } } + internal override string GeometryPolygonTypeName { get { return "global::Microsoft.Spatial.GeometryPolygon"; } } + internal override string GeometryCollectionTypeName { get { return "global::Microsoft.Spatial.GeometryCollection"; } } + internal override string GeometryMultiPolygonTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPolygon"; } } + internal override string GeometryMultiLineStringTypeName { get { return "global::Microsoft.Spatial.GeometryMultiLineString"; } } + internal override string GeometryMultiPointTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPoint"; } } + internal override string DateTypeName { get { return "global::Microsoft.OData.Edm.Date"; } } + internal override string DateTimeOffsetTypeName { get { return "global::System.DateTimeOffset"; } } + internal override string DurationTypeName { get { return "global::System.TimeSpan"; } } + internal override string TimeOfDayTypeName { get { return "global::Microsoft.OData.Edm.TimeOfDay"; } } + internal override string XmlConvertClassName { get { return "global::System.Xml.XmlConvert"; } } + internal override string EnumTypeName { get { return "global::System.Enum"; } } + internal override string FixPattern { get { return "@{0}"; } } + internal override string EnumUnderlyingTypeMarker { get { return " : "; } } + internal override string ConstantExpressionConstructorWithType { get { return "global::System.Linq.Expressions.Expression.Constant({0}, typeof({1}))"; } } + internal override string TypeofFormatter { get { return "typeof({0})"; } } + internal override string UriOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; } } + internal override string UriEntityOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; } } + internal override string BodyOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; } } + internal override string BaseEntityType { get { return " : global::Microsoft.OData.Client.BaseEntityType"; } } + internal override string OverloadsModifier { get { return "new "; } } + internal override string ODataVersion { get { return "global::Microsoft.OData.ODataVersion.V4"; } } + internal override string ParameterDeclarationTemplate { get { return "{0} {1}"; } } + internal override string DictionaryItemConstructor { get { return "{{ {0}, {1} }}"; } } + internal override HashSet LanguageKeywords { get { + if (CSharpKeywords == null) + { + CSharpKeywords = new HashSet(StringComparer.Ordinal) + { + "abstract", "as", "base", "byte", "bool", "break", "case", "catch", "char", "checked", "class", "const", "continue", + "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "for", + "foreach", "finally", "fixed", "float", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", + "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", + "readonly", "ref", "return", "sbyte", "sealed", "string", "short", "sizeof", "stackalloc", "static", "struct", "switch", + "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "volatile", + "void", "while" + }; + } + return CSharpKeywords; + } } + private HashSet CSharpKeywords; + + internal override void WriteFileHeader() + { +#>//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:<#= Environment.Version #> +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// Generation date: <#= DateTime.Now.ToString(global::System.Globalization.CultureInfo.CurrentCulture) #> +<#+ + } + + internal override void WriteNamespaceStart(string fullNamespace) + { +#> +namespace <#= fullNamespace #> +{ +<#+ + } + + internal override void WriteClassStartForEntityContainer(string originalContainerName, string containerName, string fixedContainerName) + { +#> + /// + /// There are no comments for <#= containerName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalContainerName #>")] +<#+ + } +#> + public partial class <#= fixedContainerName #> : global::Microsoft.OData.Client.DataServiceContext + { +<#+ + } + + internal override void WriteMethodStartForEntityContainerConstructor(string containerName, string fixedContainerName) + { +#> + /// + /// Initialize a new <#= containerName #> object. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + public <#= fixedContainerName #>(global::System.Uri serviceRoot) : + base(serviceRoot, global::Microsoft.OData.Client.ODataProtocolVersion.V4) + { +<#+ + } + + internal override void WriteKeyAsSegmentUrlConvention() + { +#> + this.UrlKeyDelimiter = global::Microsoft.OData.Client.DataServiceUrlKeyDelimiter.Slash; +<#+ + } + + internal override void WriteInitializeResolveName() + { +#> + this.ResolveName = new global::System.Func(this.ResolveNameFromType); +<#+ + } + + internal override void WriteInitializeResolveType() + { +#> + this.ResolveType = new global::System.Func(this.ResolveTypeFromName); +<#+ + } + + internal override void WriteClassEndForEntityContainerConstructor() + { +#> + this.OnContextCreated(); + this.Format.LoadServiceModel = GeneratedEdmModel.GetInstance; + this.Format.UseJson(); + } + partial void OnContextCreated(); +<#+ + } + + internal override void WriteMethodStartForResolveTypeFromName() + { +#> + /// + /// Since the namespace configured for this service reference + /// in Visual Studio is different from the one indicated in the + /// server schema, use type-mappers to map between the two. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + protected global::System.Type ResolveTypeFromName(string typeName) + { +<#+ + } + + internal override void WriteResolveNamespace(string typeName, string fullNamespace, string languageDependentNamespace) + { +#> + <#= typeName #>resolvedType = this.DefaultResolveType(typeName, "<#= fullNamespace #>", "<#= languageDependentNamespace #>"); + if ((resolvedType != null)) + { + return resolvedType; + } +<#+ + } + + internal override void WriteMethodEndForResolveTypeFromName() + { +#> + return null; + } +<#+ + } + + internal override void WritePropertyRootNamespace(string containerName, string fullNamespace) + { + + } + + internal override void WriteMethodStartForResolveNameFromType(string containerName, string fullNamespace) + { +#> + /// + /// Since the namespace configured for this service reference + /// in Visual Studio is different from the one indicated in the + /// server schema, use type-mappers to map between the two. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + protected string ResolveNameFromType(global::System.Type clientType) + { +<#+ + if (this.context.EnableNamingAlias) + { +#> + global::Microsoft.OData.Client.OriginalNameAttribute originalNameAttribute = (global::Microsoft.OData.Client.OriginalNameAttribute)global::System.Linq.Enumerable.SingleOrDefault(global::Microsoft.OData.Client.Utility.GetCustomAttributes(clientType, typeof(global::Microsoft.OData.Client.OriginalNameAttribute), true)); +<#+ + } + } + + internal override void WriteResolveType(string fullNamespace, string languageDependentNamespace) + { +#> + if (clientType.Namespace.Equals("<#= languageDependentNamespace #>", global::System.StringComparison.Ordinal)) + { +<#+ + if (this.context.EnableNamingAlias) + { +#> + if (originalNameAttribute != null) + { + return string.Concat("<#= fullNamespace #>.", originalNameAttribute.OriginalName); + } +<#+ + } +#> + return string.Concat("<#= fullNamespace #>.", clientType.Name); + } +<#+ + } + + internal override void WriteMethodEndForResolveNameFromType(bool modelHasInheritance) + { + if (this.context.EnableNamingAlias && modelHasInheritance) + { +#> + if (originalNameAttribute != null) + { + return clientType.Namespace + "." + originalNameAttribute.OriginalName; + } +<#+ + } +#> + return <#= modelHasInheritance ? "clientType.FullName" : "null" #>; + } +<#+ + } + + internal override void WriteConstructorForSingleType(string singleTypeName, string baseTypeName) + { +#> + /// + /// Initialize a new <#= singleTypeName #> object. + /// + public <#= singleTypeName #>(global::Microsoft.OData.Client.DataServiceContext context, string path) + : base(context, path) {} + + /// + /// Initialize a new <#= singleTypeName #> object. + /// + public <#= singleTypeName #>(global::Microsoft.OData.Client.DataServiceContext context, string path, bool isComposable) + : base(context, path, isComposable) {} + + /// + /// Initialize a new <#= singleTypeName #> object. + /// + public <#= singleTypeName #>(<#= baseTypeName #> query) + : base(query) {} + +<#+ + } + + internal override void WriteContextEntitySetProperty(string entitySetName, string entitySetFixedName, string originalEntitySetName, string entitySetElementTypeName, bool inContext) + { +#> + /// + /// There are no comments for <#= entitySetName #> in the schema. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalEntitySetName #>")] +<#+ + } +#> + public global::Microsoft.OData.Client.DataServiceQuery<<#= entitySetElementTypeName #>> <#= entitySetFixedName #> + { + get + { +<#+ + if (!inContext) + { +#> + if (!this.IsComposable) + { + throw new global::System.NotSupportedException("The previous function is not composable."); + } +<#+ + } +#> + if ((this._<#= entitySetName #> == null)) + { + this._<#= entitySetName #> = <#= inContext ? "base" : "Context" #>.CreateQuery<<#= entitySetElementTypeName #>>(<#= inContext ? "\"" + originalEntitySetName + "\"" : "GetPath(\"" + originalEntitySetName + "\")" #>); + } + return this._<#= entitySetName #>; + } + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private global::Microsoft.OData.Client.DataServiceQuery<<#= entitySetElementTypeName #>> _<#= entitySetName #>; +<#+ + } + + internal override void WriteContextSingletonProperty(string singletonName, string singletonFixedName, string originalSingletonName, string singletonElementTypeName, bool inContext) + { +#> + /// + /// There are no comments for <#= singletonName #> in the schema. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalSingletonName #>")] +<#+ + } +#> + public <#= singletonElementTypeName #> <#= singletonFixedName #> + { + get + { +<#+ + if (!inContext) + { +#> + if (!this.IsComposable) + { + throw new global::System.NotSupportedException("The previous function is not composable."); + } +<#+ + } +#> + if ((this._<#= singletonName #> == null)) + { + this._<#= singletonName #> = new <#= singletonElementTypeName #>(<#= inContext ? "this" : "this.Context" #>, <#= inContext ? "\"" + originalSingletonName + "\"" : "GetPath(\"" + originalSingletonName + "\")" #>); + } + return this._<#= singletonName #>; + } + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private <#= singletonElementTypeName #> _<#= singletonName #>; +<#+ + } + + internal override void WriteContextAddToEntitySetMethod(string entitySetName, string originalEntitySetName, string typeName, string parameterName) + { +#> + /// + /// There are no comments for <#= entitySetName #> in the schema. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + public void AddTo<#= entitySetName #>(<#= typeName #> <#= parameterName #>) + { + base.AddObject("<#= originalEntitySetName #>", <#= parameterName #>); + } +<#+ + } + + internal override void WriteGeneratedEdmModel(string escapedEdmxString) + { + + string path = this.context.TempFilePath; + + if (!String.IsNullOrEmpty(path)) + { + using (StreamWriter writer = new StreamWriter(path, true)) + { + writer.WriteLine(escapedEdmxString); + } + } + + bool useTempFile = !String.IsNullOrEmpty(path) && System.IO.File.Exists(path); +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private abstract class GeneratedEdmModel + { +<#+ + if (this.context.ReferencesMap != null) + { +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private static global::System.Collections.Generic.Dictionary ReferencesMap = new global::System.Collections.Generic.Dictionary() + { +<#+ + foreach(var reference in this.context.ReferencesMap) + { +#> + {@"<#= reference.Key.OriginalString.Replace("\"", "\"\"") #>", @"<#= Utils.SerializeToString(reference.Value).Replace("\"", "\"\"") #>"}, +<#+ + } +#> + }; +<#+ + } +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private static global::Microsoft.OData.Edm.IEdmModel ParsedModel = LoadModelFromString(); +<#+ + if (useTempFile) + { +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private const string filePath = @"<#= path #>"; +<#+ + } + else + { +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private const string Edmx = @"<#= escapedEdmxString #>"; +<#+ + } +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + public static global::Microsoft.OData.Edm.IEdmModel GetInstance() + { + return ParsedModel; + } +<#+ + if (this.context.ReferencesMap != null) + { +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private static global::System.Xml.XmlReader getReferencedModelFromMap(global::System.Uri uri) + { + string referencedEdmx; + if (ReferencesMap.TryGetValue(uri.OriginalString, out referencedEdmx)) + { + return CreateXmlReader(referencedEdmx); + } + + return null; + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private static global::Microsoft.OData.Edm.IEdmModel LoadModelFromString() + { +<#+ + if (useTempFile) + { +#> + global::System.Xml.XmlReader reader = CreateXmlReader(); +<#+ + } + else + { +#> + global::System.Xml.XmlReader reader = CreateXmlReader(Edmx); +<#+ + } +#> + try + { + return global::Microsoft.OData.Edm.Csdl.CsdlReader.Parse(reader, getReferencedModelFromMap); + } + finally + { + ((global::System.IDisposable)(reader)).Dispose(); + } + } +<#+ + } + else + { +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private static global::Microsoft.OData.Edm.IEdmModel LoadModelFromString() + { +<#+ + if (useTempFile) + { +#> + global::System.Xml.XmlReader reader = CreateXmlReader(); +<#+ + } + else + { +#> + global::System.Xml.XmlReader reader = CreateXmlReader(Edmx); +<#+ + } +#> + try + { + global::System.Collections.Generic.IEnumerable errors; + global::Microsoft.OData.Edm.IEdmModel edmModel; + + if (!global::Microsoft.OData.Edm.Csdl.CsdlReader.TryParse(reader, <#= this.context.IgnoreUnexpectedElementsAndAttributes ? "true" : "false" #>, out edmModel, out errors)) + { + global::System.Text.StringBuilder errorMessages = new System.Text.StringBuilder(); + foreach (var error in errors) + { + errorMessages.Append(error.ErrorMessage); + errorMessages.Append("; "); + } + throw new global::System.InvalidOperationException(errorMessages.ToString()); + } + + return edmModel; + } + finally + { + ((global::System.IDisposable)(reader)).Dispose(); + } + } +<#+ + } +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private static global::System.Xml.XmlReader CreateXmlReader(string edmxToParse) + { + return global::System.Xml.XmlReader.Create(new global::System.IO.StringReader(edmxToParse)); + } +<#+ + if (useTempFile) + { +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private static global::System.Xml.XmlReader CreateXmlReader() + { + return global::System.Xml.XmlReader.Create(new global::System.IO.StreamReader(filePath)); + } +<#+ + } +#> + } +<#+ + } + + internal override void WriteClassEndForEntityContainer() + { +#> + } +<#+ + } + + internal override void WriteSummaryCommentForStructuredType(string typeName) + { +#> + /// + /// There are no comments for <#= typeName #> in the schema. + /// +<#+ + } + + internal override void WriteKeyPropertiesCommentAndAttribute(IEnumerable keyProperties, string keyString) + { +#> + /// +<#+ + foreach (string key in keyProperties) + { +#> + /// <#= key #> +<#+ + } +#> + /// + [global::Microsoft.OData.Client.Key("<#= keyString #>")] +<#+ + } + + internal override void WriteEntityTypeAttribute() + { +#> + [global::Microsoft.OData.Client.EntityType()] +<#+ + } + + internal override void WriteEntitySetAttribute(string entitySetName) + { +#> + [global::Microsoft.OData.Client.EntitySet("<#= entitySetName #>")] +<#+ + } + + internal override void WriteEntityHasStreamAttribute() + { +#> + [global::Microsoft.OData.Client.HasStream()] +<#+ + } + + internal override void WriteClassStartForStructuredType(string abstractModifier, string typeName, string originalTypeName, string baseTypeName) + { + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalTypeName #>")] +<#+ + } +#> + public<#= abstractModifier #> partial class <#= typeName #><#= baseTypeName #> + { +<#+ + } + + internal override void WriteSummaryCommentForStaticCreateMethod(string typeName) + { +#> + /// + /// Create a new <#= typeName #> object. + /// +<#+ + } + + internal override void WriteParameterCommentForStaticCreateMethod(string parameterName, string propertyName) + { +#> + /// Initial value of <#= propertyName #>. +<#+ + } + + internal override void WriteDeclarationStartForStaticCreateMethod(string typeName, string fixedTypeName) + { +#> + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + public static <#= fixedTypeName #> Create<#= typeName #>(<#+ + } + + internal override void WriteParameterForStaticCreateMethod(string parameterTypeName, string parameterName, string parameterSeparater) + { +#><#= parameterTypeName #> <#= parameterName #><#= parameterSeparater #><#+ + } + + internal override void WriteDeclarationEndForStaticCreateMethod(string typeName, string instanceName) + { + #>) + { + <#= typeName #> <#= instanceName #> = new <#= typeName #>(); +<#+ + } + + internal override void WriteParameterNullCheckForStaticCreateMethod(string parameterName) + { +#> + if ((<#= parameterName #> == null)) + { + throw new global::System.ArgumentNullException("<#= parameterName #>"); + } +<#+ + } + + internal override void WritePropertyValueAssignmentForStaticCreateMethod(string instanceName, string propertyName, string parameterName) + { +#> + <#= instanceName #>.<#= propertyName #> = <#= parameterName #>; +<#+ + } + + internal override void WriteMethodEndForStaticCreateMethod(string instanceName) + { +#> + return <#= instanceName #>; + } +<#+ + } + + internal override void WritePropertyForStructuredType(string propertyType, string originalPropertyName, string propertyName, string fixedPropertyName, string privatePropertyName, string propertyInitializationValue, bool writeOnPropertyChanged) + { +#> + /// + /// There are no comments for Property <#= propertyName #> in the schema. + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] +<#+ + if (this.context.EnableNamingAlias || IdentifierMappings.ContainsKey(originalPropertyName)) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalPropertyName #>")] +<#+ + } +#> + public <#= propertyType #> <#= fixedPropertyName #> + { + get + { + return this.<#= privatePropertyName #>; + } + set + { + this.On<#= propertyName #>Changing(value); + this.<#= privatePropertyName #> = value; + this.On<#= propertyName #>Changed(); +<#+ + if (writeOnPropertyChanged) + { +#> + this.OnPropertyChanged("<#= originalPropertyName #>"); +<#+ + } +#> + } + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + private <#= propertyType #> <#= privatePropertyName #><#= propertyInitializationValue != null ? " = " + propertyInitializationValue : string.Empty #>; + partial void On<#= propertyName #>Changing(<#= propertyType #> value); + partial void On<#= propertyName #>Changed(); +<#+ + } + + internal override void WriteINotifyPropertyChangedImplementation() + { +#> + /// + /// This event is raised when the value of the property is changed + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + /// + /// The value of the property is changed + /// + /// property name + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.OData.Client.Design.T4", "<#=T4Version#>")] + protected virtual void OnPropertyChanged(string property) + { + if ((this.PropertyChanged != null)) + { + this.PropertyChanged(this, new global::System.ComponentModel.PropertyChangedEventArgs(property)); + } + } +<#+ + } + + internal override void WriteClassEndForStructuredType() + { +#> + } +<#+ + } + + internal override void WriteEnumFlags() + { +#> + [global::System.Flags] +<#+ + } + + internal override void WriteSummaryCommentForEnumType(string enumName) + { +#> + /// + /// There are no comments for <#= enumName #> in the schema. + /// +<#+ + } + + internal override void WriteEnumDeclaration(string enumName, string originalEnumName, string underlyingType) + { + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalEnumName #>")] +<#+ + } +#> + public enum <#= enumName #><#= underlyingType #> + { +<#+ + } + + internal override void WriteMemberForEnumType(string member, string originalMemberName, bool last) + { + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalMemberName #>")] +<#+ + } +#> + <#= member #><#= last ? string.Empty : "," #> +<#+ + } + + internal override void WriteEnumEnd() + { +#> + } +<#+ + } + + internal override void WriteFunctionImportReturnCollectionResult(string functionName, string originalFunctionName, string returnTypeName, string parameters, string parameterValues, bool isComposable, bool useEntityReference) + { +#> + /// + /// There are no comments for <#= functionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalFunctionName #>")] +<#+ + } +#> + public global::Microsoft.OData.Client.DataServiceQuery<<#= returnTypeName #>> <#= functionName #>(<#= parameters #><#= useEntityReference ? ", bool useEntityReference = false" : string.Empty #>) + { + return this.CreateFunctionQuery<<#= returnTypeName #>>("", "<#= originalFunctionName #>", <#= isComposable.ToString().ToLower() #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>); + } +<#+ + } + + internal override void WriteFunctionImportReturnSingleResult(string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { +#> + /// + /// There are no comments for <#= functionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalFunctionName #>")] +<#+ + } +#> + public <#= isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName) #> <#= functionName #>(<#= parameters #><#= useEntityReference ? ", bool useEntityReference = false" : string.Empty #>) + { + return <#= isReturnEntity ? "new " + returnTypeNameWithSingleSuffix + "(" : string.Empty #>this.CreateFunctionQuerySingle<<#= returnTypeName #>>("", "<#= originalFunctionName #>", <#= isComposable.ToString().ToLower() #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>)<#= isReturnEntity ? ")" : string.Empty #>; + } +<#+ + } + + internal override void WriteBoundFunctionInEntityTypeReturnCollectionResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { +#> + /// + /// There are no comments for <#= functionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalFunctionName #>")] +<#+ + } +#> + public <#= hideBaseMethod ? this.OverloadsModifier : string.Empty #>global::Microsoft.OData.Client.DataServiceQuery<<#= returnTypeName #>> <#= functionName #>(<#= parameters #><#= useEntityReference ? ", bool useEntityReference = false" : string.Empty #>) + { + global::System.Uri requestUri; + Context.TryGetUri(this, out requestUri); + return this.Context.CreateFunctionQuery<<#= returnTypeName #>>(string.Join("/", global::System.Linq.Enumerable.Select(global::System.Linq.Enumerable.Skip(requestUri.Segments, this.Context.BaseUri.Segments.Length), s => s.Trim('/'))), "<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable.ToString().ToLower() #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #><#= useEntityReference ? ", bool useEntityReference = false" : string.Empty #>); + } +<#+ + } + + internal override void WriteBoundFunctionInEntityTypeReturnSingleResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { +#> + /// + /// There are no comments for <#= functionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalFunctionName #>")] +<#+ + } +#> + public <#= hideBaseMethod ? this.OverloadsModifier : string.Empty #><#= isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName) #> <#= functionName #>(<#= parameters #><#= useEntityReference ? ", bool useEntityReference = false" : string.Empty #>) + { + global::System.Uri requestUri; + Context.TryGetUri(this, out requestUri); + + return <#= isReturnEntity ? "new " + returnTypeNameWithSingleSuffix + "(" : string.Empty #>this.Context.CreateFunctionQuerySingle<<#= returnTypeName #>>(string.Join("/", global::System.Linq.Enumerable.Select(global::System.Linq.Enumerable.Skip(requestUri.Segments, this.Context.BaseUri.Segments.Length), s => s.Trim('/'))), "<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable.ToString().ToLower() #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>)<#= isReturnEntity ? ")" : string.Empty #>; + } +<#+ + } + + internal override void WriteActionImport(string actionName, string originalActionName, string returnTypeName, string parameters, string parameterValues) + { +#> + /// + /// There are no comments for <#= actionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalActionName #>")] +<#+ + } +#> + public <#= returnTypeName #> <#= actionName #>(<#= parameters #>) + { + return new <#= returnTypeName #>(this, this.BaseUri.OriginalString.Trim('/') + "/<#= originalActionName #>"<#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>); + } +<#+ + } + + internal override void WriteBoundActionInEntityType(bool hideBaseMethod, string actionName, string originalActionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { +#> + /// + /// There are no comments for <#= actionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalActionName #>")] +<#+ + } +#> + public <#= hideBaseMethod ? this.OverloadsModifier : string.Empty #><#= returnTypeName #> <#= actionName #>(<#= parameters #>) + { + global::Microsoft.OData.Client.EntityDescriptor resource = Context.EntityTracker.TryGetEntityDescriptor(this); + if (resource == null) + { + throw new global::System.Exception("cannot find entity"); + } + + return new <#= returnTypeName #>(this.Context, resource.EditLink.OriginalString.Trim('/') + "/<#= fullNamespace #>.<#= originalActionName #>"<#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>); + } +<#+ + } + + internal override void WriteExtensionMethodsStart() + { +#> + /// + /// Class containing all extension methods + /// + public static class ExtensionMethods + { +<#+ + } + + internal override void WriteExtensionMethodsEnd() + { +#> + } +<#+ + } + + internal override void WriteByKeyMethods(string entityTypeName, string returnTypeName, IEnumerable keys, string keyParameters, string keyDictionaryItems) + { +#> + /// + /// Get an entity of type <#= entityTypeName #> as <#= returnTypeName #> specified by key from an entity set + /// + /// source entity set + /// dictionary with the names and values of keys + public static <#= returnTypeName #> ByKey(this global::Microsoft.OData.Client.DataServiceQuery<<#= entityTypeName #>> source, global::System.Collections.Generic.Dictionary keys) + { + return new <#= returnTypeName #>(source.Context, source.GetKeyPath(global::Microsoft.OData.Client.Serializer.GetKeyString(source.Context, keys))); + } + /// + /// Get an entity of type <#= entityTypeName #> as <#= returnTypeName #> specified by key from an entity set + /// + /// source entity set +<#+ + foreach (var key in keys) + { +#> + /// The value of <#= key #> +<#+ + } +#> + public static <#= returnTypeName #> ByKey(this global::Microsoft.OData.Client.DataServiceQuery<<#= entityTypeName #>> source, + <#= keyParameters #>) + { + global::System.Collections.Generic.Dictionary keys = new global::System.Collections.Generic.Dictionary + { + <#= keyDictionaryItems #> + }; + return new <#= returnTypeName #>(source.Context, source.GetKeyPath(global::Microsoft.OData.Client.Serializer.GetKeyString(source.Context, keys))); + } +<#+ + } + + internal override void WriteCastToMethods(string baseTypeName, string derivedTypeName, string derivedTypeFullName, string returnTypeName) + { +#> + /// + /// Cast an entity of type <#= baseTypeName #> to its derived type <#= derivedTypeFullName #> + /// + /// source entity + public static <#= returnTypeName #> CastTo<#= derivedTypeName #>(this global::Microsoft.OData.Client.DataServiceQuerySingle<<#= baseTypeName #>> source) + { + global::Microsoft.OData.Client.DataServiceQuerySingle<<#= derivedTypeFullName #>> query = source.CastTo<<#= derivedTypeFullName #>>(); + return new <#= returnTypeName #>(source.Context, query.GetPath(null)); + } +<#+ + } + + internal override void WriteBoundFunctionReturnSingleResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { +#> + /// + /// There are no comments for <#= functionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalFunctionName #>")] +<#+ + } +#> + public static <#= isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName) #> <#= functionName #>(this <#= boundTypeName #> source<#= string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters #><#= useEntityReference ? ", bool useEntityReference = false" : string.Empty #>) + { + if (!source.IsComposable) + { + throw new global::System.NotSupportedException("The previous function is not composable."); + } + + return <#= isReturnEntity ? "new " + returnTypeNameWithSingleSuffix + "(" : string.Empty #>source.CreateFunctionQuerySingle<<#= returnTypeName #>>("<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable.ToString().ToLower() #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>)<#= isReturnEntity ? ")" : string.Empty #>; + } +<#+ + } + + internal override void WriteBoundFunctionReturnCollectionResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { +#> + /// + /// There are no comments for <#= functionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalFunctionName #>")] +<#+ + } +#> + public static global::Microsoft.OData.Client.DataServiceQuery<<#= returnTypeName #>> <#= functionName #>(this <#= boundTypeName #> source<#= string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters #><#= useEntityReference ? ", bool useEntityReference = true" : string.Empty #>) + { + if (!source.IsComposable) + { + throw new global::System.NotSupportedException("The previous function is not composable."); + } + + return source.CreateFunctionQuery<<#= returnTypeName #>>("<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable.ToString().ToLower() #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>); + } +<#+ + } + + internal override void WriteBoundActionAsExtension(string actionName, string originalActionName, string boundSourceType, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { +#> + /// + /// There are no comments for <#= actionName #> in the schema. + /// +<#+ + if (this.context.EnableNamingAlias) + { +#> + [global::Microsoft.OData.Client.OriginalNameAttribute("<#= originalActionName #>")] +<#+ + } +#> + public static <#= returnTypeName #> <#= actionName #>(this <#= boundSourceType #> source<#= string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters #>) + { + if (!source.IsComposable) + { + throw new global::System.NotSupportedException("The previous function is not composable."); + } + + return new <#= returnTypeName #>(source.Context, source.AppendRequestUri("<#= fullNamespace #>.<#= originalActionName #>")<#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>); + } +<#+ + } + + internal override void WriteNamespaceEnd() + { +#> +} +<#+ + } +} + +public sealed class ODataClientVBTemplate : ODataClientTemplate +{ + /// + /// Creates an instance of the ODataClientTemplate. + /// + /// The cotion context. + public ODataClientVBTemplate(CodeGenerationContext context) + : base(context) + { + } + + internal override string GlobalPrefix { get { return string.Empty; } } + internal override string SystemTypeTypeName { get { return "Global.System.Type"; } } + internal override string AbstractModifier { get { return " MustInherit"; } } + internal override string DataServiceActionQueryTypeName { get { return "Global.Microsoft.OData.Client.DataServiceActionQuery"; } } + internal override string DataServiceActionQuerySingleOfTStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceActionQuerySingle(Of {0})"; } } + internal override string DataServiceActionQueryOfTStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceActionQuery(Of {0})"; } } + internal override string NotifyPropertyChangedModifier { get { return "\r\n Implements Global.System.ComponentModel.INotifyPropertyChanged"; } } + internal override string ClassInheritMarker { get { return "\r\n Inherits "; } } + internal override string ParameterSeparator { get { return ", _\r\n "; } } + internal override string KeyParameterSeparator { get { return ", _\r\n "; } } + internal override string KeyDictionaryItemSeparator { get { return ", _\r\n "; } } + internal override string SystemNullableStructureTemplate { get { return "Global.System.Nullable(Of {0})"; } } + internal override string ICollectionOfTStructureTemplate { get { return "Global.System.Collections.Generic.ICollection(Of {0})"; } } + internal override string DataServiceCollectionStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceCollection(Of {0})"; } } + internal override string DataServiceQueryStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceQuery(Of {0})"; } } + internal override string DataServiceQuerySingleStructureTemplate { get { return "Global.Microsoft.OData.Client.DataServiceQuerySingle(Of {0})"; } } + internal override string ObservableCollectionStructureTemplate { get { return "Global.System.Collections.ObjectModel.ObservableCollection(Of {0})"; } } + internal override string ObjectModelCollectionStructureTemplate { get { return "Global.System.Collections.ObjectModel.Collection(Of {0})"; } } + internal override string DataServiceCollectionConstructorParameters { get { return "(Nothing, Global.Microsoft.OData.Client.TrackingMode.None)"; } } + internal override string NewModifier { get { return "New "; } } + internal override string GeoTypeInitializePattern { get { return "Global.Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(False).Read(Of {0})(New Global.System.IO.StringReader(\"{1}\"))"; } } + internal override string Int32TypeName { get { return "Integer"; } } + internal override string StringTypeName { get { return "String"; } } + internal override string BinaryTypeName { get { return "Byte()"; } } + internal override string DecimalTypeName { get { return "Decimal"; } } + internal override string Int16TypeName { get { return "Short"; } } + internal override string SingleTypeName { get { return "Single"; } } + internal override string BooleanTypeName { get { return "Boolean"; } } + internal override string DoubleTypeName { get { return "Double"; } } + internal override string GuidTypeName { get { return "Global.System.Guid"; } } + internal override string ByteTypeName { get { return "Byte"; } } + internal override string Int64TypeName { get { return "Long"; } } + internal override string SByteTypeName { get { return "SByte"; } } + internal override string DataServiceStreamLinkTypeName { get { return "Global.Microsoft.OData.Client.DataServiceStreamLink"; } } + internal override string GeographyTypeName { get { return "Global.Microsoft.Spatial.Geography"; } } + internal override string GeographyPointTypeName { get { return "Global.Microsoft.Spatial.GeographyPoint"; } } + internal override string GeographyLineStringTypeName { get { return "Global.Microsoft.Spatial.GeographyLineString"; } } + internal override string GeographyPolygonTypeName { get { return "Global.Microsoft.Spatial.GeographyPolygon"; } } + internal override string GeographyCollectionTypeName { get { return "Global.Microsoft.Spatial.GeographyCollection"; } } + internal override string GeographyMultiPolygonTypeName { get { return "Global.Microsoft.Spatial.GeographyMultiPolygon"; } } + internal override string GeographyMultiLineStringTypeName { get { return "Global.Microsoft.Spatial.GeographyMultiLineString"; } } + internal override string GeographyMultiPointTypeName { get { return "Global.Microsoft.Spatial.GeographyMultiPoint"; } } + internal override string GeometryTypeName { get { return "Global.Microsoft.Spatial.Geometry"; } } + internal override string GeometryPointTypeName { get { return "Global.Microsoft.Spatial.GeometryPoint"; } } + internal override string GeometryLineStringTypeName { get { return "Global.Microsoft.Spatial.GeometryLineString"; } } + internal override string GeometryPolygonTypeName { get { return "Global.Microsoft.Spatial.GeometryPolygon"; } } + internal override string GeometryCollectionTypeName { get { return "Global.Microsoft.Spatial.GeometryCollection"; } } + internal override string GeometryMultiPolygonTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPolygon"; } } + internal override string GeometryMultiLineStringTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiLineString"; } } + internal override string GeometryMultiPointTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPoint"; } } + internal override string DateTypeName { get { return "Global.Microsoft.OData.Edm.Date"; } } + internal override string DateTimeOffsetTypeName { get { return "Global.System.DateTimeOffset"; } } + internal override string DurationTypeName { get { return "Global.System.TimeSpan"; } } + internal override string TimeOfDayTypeName { get { return "Global.Microsoft.OData.Edm.TimeOfDay"; } } + internal override string XmlConvertClassName { get { return "Global.System.Xml.XmlConvert"; } } + internal override string EnumTypeName { get { return "Global.System.Enum"; } } + internal override string FixPattern { get { return "[{0}]"; } } + internal override string EnumUnderlyingTypeMarker { get { return " As "; } } + internal override string ConstantExpressionConstructorWithType { get { return "Global.System.Linq.Expressions.Expression.Constant({0}, GetType({1}))"; } } + internal override string TypeofFormatter { get { return "GetType({0})"; } } + internal override string UriOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; } } + internal override string UriEntityOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; } } + internal override string BodyOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; } } + internal override string BaseEntityType { get { return "\r\n Inherits Global.Microsoft.OData.Client.BaseEntityType"; } } + internal override string OverloadsModifier { get { return "Overloads "; } } + internal override string ODataVersion { get { return "Global.Microsoft.OData.ODataVersion.V4"; } } + internal override string ParameterDeclarationTemplate { get { return "{1} As {0}"; } } + internal override string DictionaryItemConstructor { get { return "{{ {0}, {1} }}"; } } + internal override HashSet LanguageKeywords { get { + if (VBKeywords == null) + { + VBKeywords = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "AddHandler", "AddressOf", "Alias", "And", "AndAlso", "As", "Boolean", "ByRef", "Byte", "ByVal", + "Call", "Case", "Catch", "CBool", "", "CByte", "CChar", "CDate", "CDbl", "CDec", "Char", + "CInt", "Class", "CLng", "CObj", "Const", "Continue", "CSByte", "CShort", "CSng", "CStr", + "CType", "CUInt", "CULng", "CUShort", "Date", "Decimal", "Declare", "Default", "Delegate", "Dim", + "DirectCast", "Do", "Double", "Each", "Else", "ElseIf", "End", "EndIf", "Enum", "Erase", + "Error", "Event", "Exit", "False", "Finally", "For", "Friend", "Function", "Get", "GetType", + "GetXMLNamespace", "Global", "GoSub", "GoTo", "Handles", "If", "Implements", "Imports", "In", "Inherits", + "Integer", "Interface", "Is", "IsNot", "Let", "Lib", "Like", "Long", "Loop", "Me", + "Mod", "Module", "MustInherit", "MustOverride", "MyBase", "MyClass", "Namespace", "Narrowing", "New", "Next", + "Not", "Nothing", "NotInheritable", "NotOverridable", "Object", "Of", "On", "Operator", "Option", "Optional", + "Or", "OrElse", "Out", "Overloads", "Overridable", "Overrides", "ParamArray", "Partial", "Private", "Property", + "Protected", "Public", "RaiseEvent", "ReadOnly", "ReDim", "REM", "RemoveHandler", "Resume", "Return", "SByte", + "Select", "Set", "Shadows", "Shared", "Short", "Single", "Static", "Step", "Stop", "String", + "Structure", "Sub", "SyncLock", "Then", "Throw", "To", "True", "Try", "TryCast", "TypeOf", + "UInteger", "ULong", "UShort", "Using", "Variant", "Wend", "When", "While", "Widening", "With", + "WithEvents", "WriteOnly", "Xor" + }; + } + return VBKeywords; + } } + private HashSet VBKeywords; + + internal override void WriteFileHeader() + { +#>'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:<#= Environment.Version #> +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict Off +Option Explicit On + + +'Generation date: <#= DateTime.Now.ToString(System.Globalization.CultureInfo.CurrentCulture) #> +<#+ + } + + internal override void WriteNamespaceStart(string fullNamespace) + { +#> +Namespace <#= fullNamespace #> +<#+ + } + + internal override void WriteClassStartForEntityContainer(string originalContainerName, string containerName, string fixedContainerName) + { +#> + ''' + '''There are no comments for <#= containerName #> in the schema. + ''' +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Partial Public Class <#= fixedContainerName #> + Inherits Global.Microsoft.OData.Client.DataServiceContext +<#+ + } + + internal override void WriteMethodStartForEntityContainerConstructor(string containerName, string fixedContainerName) + { +#> + ''' + '''Initialize a new <#= containerName #> object. + ''' + ")> _ + Public Sub New(ByVal serviceRoot As Global.System.Uri) + MyBase.New(serviceRoot, Global.Microsoft.OData.Client.ODataProtocolVersion.V4) +<#+ + } + + internal override void WriteKeyAsSegmentUrlConvention() + { +#> + Me.UrlKeyDelimiter = Global.Microsoft.OData.Client.DataServiceUrlKeyDelimiter.Slash +<#+ + } + + internal override void WriteInitializeResolveName() + { +#> + Me.ResolveName = AddressOf Me.ResolveNameFromType +<#+ + } + + internal override void WriteInitializeResolveType() + { +#> + Me.ResolveType = AddressOf Me.ResolveTypeFromName +<#+ + } + + internal override void WriteClassEndForEntityContainerConstructor() + { +#> + Me.OnContextCreated + Me.Format.LoadServiceModel = AddressOf GeneratedEdmModel.GetInstance + Me.Format.UseJson() + End Sub + Partial Private Sub OnContextCreated() + End Sub +<#+ + } + + internal override void WriteMethodStartForResolveTypeFromName() + { +#> + ''' + '''Since the namespace configured for this service reference + '''in Visual Studio is different from the one indicated in the + '''server schema, use type-mappers to map between the two. + ''' + ")> _ + Protected Function ResolveTypeFromName(ByVal typeName As String) As Global.System.Type +<#+ + } + + internal override void WriteResolveNamespace(string typeName, string fullNamespace, string languageDependentNamespace) + { + if (!string.IsNullOrEmpty(typeName)) + { +#> + Dim resolvedType As <#= typeName #>= Me.DefaultResolveType(typeName, "<#= fullNamespace #>", String.Concat(ROOTNAMESPACE, "<#= languageDependentNamespace #>")) +<#+ + } + else + { +#> + resolvedType = Me.DefaultResolveType(typeName, "<#= fullNamespace #>", String.Concat(ROOTNAMESPACE, "<#= languageDependentNamespace #>")) +<#+ + } +#> + If (Not (resolvedType) Is Nothing) Then + Return resolvedType + End If +<#+ + } + + internal override void WriteMethodEndForResolveTypeFromName() + { +#> + Return Nothing + End Function +<#+ + } + + internal override void WritePropertyRootNamespace(string containerName, string fullNamespace) + { +#> + ")> _ + Private Shared ROOTNAMESPACE As String = GetType(<#= containerName #>).Namespace.Remove(GetType(<#= containerName #>).Namespace.LastIndexOf("<#= fullNamespace #>")) +<#+ + } + + internal override void WriteMethodStartForResolveNameFromType(string containerName, string fullNamespace) + { +#> + ''' + '''Since the namespace configured for this service reference + '''in Visual Studio is different from the one indicated in the + '''server schema, use type-mappers to map between the two. + ''' + ")> _ + Protected Function ResolveNameFromType(ByVal clientType As Global.System.Type) As String +<#+ + if (this.context.EnableNamingAlias) + { +#> + Dim originalNameAttribute As Global.Microsoft.OData.Client.OriginalNameAttribute = + CType(Global.System.Linq.Enumerable.SingleOrDefault(Global.Microsoft.OData.Client.Utility.GetCustomAttributes(clientType, GetType(Global.Microsoft.OData.Client.OriginalNameAttribute), true)), Global.Microsoft.OData.Client.OriginalNameAttribute) +<#+ + } + } + + internal override void WriteResolveType(string fullNamespace, string languageDependentNamespace) + { +#> + If clientType.Namespace.Equals(String.Concat(ROOTNAMESPACE, "<#= languageDependentNamespace #>"), Global.System.StringComparison.OrdinalIgnoreCase) Then +<#+ + if (this.context.EnableNamingAlias) + { +#> + If (Not (originalNameAttribute) Is Nothing) Then + Return String.Concat("<#= fullNamespace #>.", originalNameAttribute.OriginalName) + End If +<#+ + } +#> + Return String.Concat("<#= fullNamespace #>.", clientType.Name) + End If +<#+ + } + + internal override void WriteMethodEndForResolveNameFromType(bool modelHasInheritance) + { + if (this.context.EnableNamingAlias && modelHasInheritance) + { +#> + If (Not (originalNameAttribute) Is Nothing) Then + Dim fullName As String = clientType.FullName.Substring(ROOTNAMESPACE.Length) + Return fullName.Remove(fullName.LastIndexOf(clientType.Name)) + originalNameAttribute.OriginalName + End If +<#+ + } +#> + Return <#= modelHasInheritance ? "clientType.FullName.Substring(ROOTNAMESPACE.Length)" : "Nothing" #> + End Function +<#+ + } + + internal override void WriteConstructorForSingleType(string singleTypeName, string baseTypeName) + { +#> + ''' + ''' Initialize a new <#= singleTypeName #> object. + ''' + Public Sub New(ByVal context As Global.Microsoft.OData.Client.DataServiceContext, ByVal path As String) + MyBase.New(context, path) + End Sub + + ''' + ''' Initialize a new <#= singleTypeName #> object. + ''' + Public Sub New(ByVal context As Global.Microsoft.OData.Client.DataServiceContext, ByVal path As String, ByVal isComposable As Boolean) + MyBase.New(context, path, isComposable) + End Sub + + ''' + ''' Initialize a new <#= singleTypeName #> object. + ''' + Public Sub New(ByVal query As <#= baseTypeName #>) + MyBase.New(query) + End Sub +<#+ + } + + internal override void WriteContextEntitySetProperty(string entitySetName, string entitySetFixedName, string originalEntitySetName, string entitySetElementTypeName, bool inContext) + { +#> + ''' + '''There are no comments for <#= entitySetName #> in the schema. + ''' + ")> _ +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public ReadOnly Property <#= entitySetFixedName #>() As Global.Microsoft.OData.Client.DataServiceQuery(Of <#= entitySetElementTypeName #>) + Get +<#+ + if (!inContext) + { +#> + If Not Me.IsComposable Then + Throw New Global.System.NotSupportedException("The previous function is not composable.") + End If +<#+ + } +#> + If (Me._<#= entitySetName #> Is Nothing) Then + Me._<#= entitySetName #> = <#= inContext ? "MyBase" : "Context"#>.CreateQuery(Of <#= entitySetElementTypeName #>)(<#= inContext ? "\"" + originalEntitySetName + "\"" : "GetPath(\"" + originalEntitySetName + "\")" #>) + End If + Return Me._<#= entitySetName #> + End Get + End Property + ")> _ + Private _<#= entitySetName #> As Global.Microsoft.OData.Client.DataServiceQuery(Of <#= entitySetElementTypeName #>) +<#+ + } + + internal override void WriteContextSingletonProperty(string singletonName, string singletonFixedName, string originalSingletonName, string singletonElementTypeName, bool inContext) + { +#> + ''' + '''There are no comments for <#= singletonName #> in the schema. + ''' + ")> _ +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public ReadOnly Property <#= singletonFixedName #>() As <#= singletonElementTypeName #> + Get +<#+ + if (!inContext) + { +#> + If Not Me.IsComposable Then + Throw New Global.System.NotSupportedException("The previous function is not composable.") + End If +<#+ + } +#> + If (Me._<#= singletonName #> Is Nothing) Then + Me._<#= singletonName #> = New <#= singletonElementTypeName #>(<#= inContext ? "Me" : "Me.Context" #>, <#= inContext ? "\"" + originalSingletonName + "\"" : "GetPath(\"" + originalSingletonName + "\")" #>) + End If + Return Me._<#= singletonName #> + End Get + End Property + ")> _ + Private _<#= singletonName #> As <#= singletonElementTypeName #> +<#+ + } + + internal override void WriteContextAddToEntitySetMethod(string entitySetName, string originalEntitySetName, string typeName, string parameterName) + { +#> + ''' + '''There are no comments for <#= entitySetName #> in the schema. + ''' + ")> _ + Public Sub AddTo<#= entitySetName #>(ByVal <#= parameterName #> As <#= typeName #>) + MyBase.AddObject("<#= originalEntitySetName #>", <#= parameterName #>) + End Sub +<#+ + } + + internal override void WriteGeneratedEdmModel(string escapedEdmxString) + { + escapedEdmxString = escapedEdmxString.Replace("\r\n", "\" & _\r\n \""); +#> + ")> _ + Private MustInherit Class GeneratedEdmModel +<#+ + if (this.context.ReferencesMap != null) + { +#> + ")> _ + Private Shared ReferencesMap As Global.System.Collections.Generic.Dictionary(Of String, String) = New Global.System.Collections.Generic.Dictionary(Of String, String) From + { +<#+ + int count = this.context.ReferencesMap.Count(); + foreach(var reference in this.context.ReferencesMap) + { +#> + {"<#= reference.Key.OriginalString.Replace("\"", "\"\"") #>", "<#= Utils.SerializeToString(reference.Value).Replace("\"", "\"\"").Replace("\r\n", "\" & _\r\n \"") #>"}<#= (--count>0?",":"")#> +<#+ + } +#> + } +<#+ + } +#> + ")> _ + Private Shared ParsedModel As Global.Microsoft.OData.Edm.IEdmModel = LoadModelFromString + ")> _ + Private Const Edmx As String = "<#= escapedEdmxString #>" + ")> _ + Public Shared Function GetInstance() As Global.Microsoft.OData.Edm.IEdmModel + Return ParsedModel + End Function +<#+ + if (this.context.ReferencesMap != null) + { +#> + ")> _ + Private Shared Function getReferencedModelFromMap(ByVal uri As Global.System.Uri) As Global.System.Xml.XmlReader + Dim referencedEdmx As String = Nothing + If (ReferencesMap.TryGetValue(uri.OriginalString, referencedEdmx)) Then + Return CreateXmlReader(referencedEdmx) + End If + Return Nothing + End Function + ")> _ + Private Shared Function LoadModelFromString() As Global.Microsoft.OData.Edm.IEdmModel + Dim reader As Global.System.Xml.XmlReader = CreateXmlReader(Edmx) + Try + Return Global.Microsoft.OData.Edm.Csdl.CsdlReader.Parse(reader, AddressOf getReferencedModelFromMap) + Finally + CType(reader,Global.System.IDisposable).Dispose + End Try + End Function +<#+ + } + else + { +#> + ")> _ + Private Shared Function LoadModelFromString() As Global.Microsoft.OData.Edm.IEdmModel + Dim reader As Global.System.Xml.XmlReader = CreateXmlReader(Edmx) + Try + Return Global.Microsoft.OData.Edm.Csdl.CsdlReader.Parse(reader) + Finally + CType(reader,Global.System.IDisposable).Dispose + End Try + End Function +<#+ + } +#> + ")> _ + Private Shared Function CreateXmlReader(ByVal edmxToParse As String) As Global.System.Xml.XmlReader + Return Global.System.Xml.XmlReader.Create(New Global.System.IO.StringReader(edmxToParse)) + End Function + End Class +<#+ + } + + internal override void WriteClassEndForEntityContainer() + { +#> + End Class +<#+ + } + + internal override void WriteSummaryCommentForStructuredType(string typeName) + { +#> + ''' + '''There are no comments for <#= typeName #> in the schema. + ''' +<#+ + } + + internal override void WriteKeyPropertiesCommentAndAttribute(IEnumerable keyProperties, string keyString) + { +#> + ''' +<#+ + foreach (string key in keyProperties) + { +#> + '''<#= key #> +<#+ + } +#> + ''' + ")> _ +<#+ + } + + internal override void WriteEntityTypeAttribute() + { +#> + _ +<#+ + } + + internal override void WriteEntitySetAttribute(string entitySetName) + { +#> + ")> _ +<#+ + } + + internal override void WriteEntityHasStreamAttribute() + { +#> + _ +<#+ + } + + internal override void WriteClassStartForStructuredType(string abstractModifier, string typeName, string originalTypeName, string baseTypeName) + { + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Partial Public<#= abstractModifier #> Class <#= typeName #><#= baseTypeName #> +<#+ + } + + internal override void WriteSummaryCommentForStaticCreateMethod(string typeName) + { +#> + ''' + '''Create a new <#= typeName #> object. + ''' +<#+ + } + + internal override void WriteParameterCommentForStaticCreateMethod(string parameterName, string propertyName) + { +#> + '''Initial value of <#= propertyName #>. +<#+ + } + + internal override void WriteDeclarationStartForStaticCreateMethod(string typeName, string fixedTypeName) + { +#> + ")> _ + Public Shared Function Create<#= typeName #>(<#+ + + } + + internal override void WriteParameterForStaticCreateMethod(string parameterTypeName, string parameterName, string parameterSeparater) + { +#>ByVal <#= parameterName #> As <#= parameterTypeName #><#= parameterSeparater #><#+ + } + + internal override void WriteDeclarationEndForStaticCreateMethod(string typeName, string instanceName) + { + #>) As <#= typeName #> + Dim <#= instanceName #> As <#= typeName #> = New <#= typeName #>() +<#+ + } + + internal override void WriteParameterNullCheckForStaticCreateMethod(string parameterName) + { +#> + If (<#= parameterName #> Is Nothing) Then + Throw New Global.System.ArgumentNullException("<#= parameterName #>") + End If +<#+ + } + + internal override void WritePropertyValueAssignmentForStaticCreateMethod(string instanceName, string propertyName, string parameterName) + { +#> + <#= instanceName #>.<#= propertyName #> = <#= parameterName #> +<#+ + } + + internal override void WriteMethodEndForStaticCreateMethod(string instanceName) + { +#> + Return <#= instanceName #> + End Function +<#+ + } + + internal override void WritePropertyForStructuredType(string propertyType, string originalPropertyName, string propertyName, string fixedPropertyName, string privatePropertyName, string propertyInitializationValue, bool writeOnPropertyChanged) + { +#> + ''' + '''There are no comments for Property <#= propertyName #> in the schema. + ''' + ")> _ +<#+ + if (this.context.EnableNamingAlias || IdentifierMappings.ContainsKey(originalPropertyName)) + { +#> + ")> _ +<#+ + } +#> + Public Property <#= fixedPropertyName #>() As <#= propertyType #> + Get + Return Me.<#= privatePropertyName #> + End Get + Set + Me.On<#= propertyName #>Changing(value) + Me.<#= privatePropertyName #> = value + Me.On<#= propertyName #>Changed +<#+ + if (writeOnPropertyChanged) + { +#> + Me.OnPropertyChanged("<#= originalPropertyName #>") +<#+ + } +#> + End Set + End Property + ")> _ +<#+ + string constructorString = string.Empty; + if (!string.IsNullOrEmpty(propertyInitializationValue)) + { + constructorString = " = " + propertyInitializationValue; + } +#> + Private <#= privatePropertyName #> As <#= propertyType #><#= constructorString #> + Partial Private Sub On<#= propertyName #>Changing(ByVal value As <#= propertyType #>) + End Sub + Partial Private Sub On<#= propertyName #>Changed() + End Sub +<#+ + } + + internal override void WriteINotifyPropertyChangedImplementation() + { +#> + ''' + ''' This event is raised when the value of the property is changed + ''' + ")> _ + Public Event PropertyChanged As Global.System.ComponentModel.PropertyChangedEventHandler Implements Global.System.ComponentModel.INotifyPropertyChanged.PropertyChanged + ''' + ''' The value of the property is changed + ''' + ''' property name + ")> _ + Protected Overridable Sub OnPropertyChanged(ByVal [property] As String) + If (Not (Me.PropertyChangedEvent) Is Nothing) Then + RaiseEvent PropertyChanged(Me, New Global.System.ComponentModel.PropertyChangedEventArgs([property])) + End If + End Sub +<#+ + } + + internal override void WriteClassEndForStructuredType() + { +#> + End Class +<#+ + } + + internal override void WriteEnumFlags() + { +#> + +<#+ + } + + internal override void WriteSummaryCommentForEnumType(string enumName) + { +#> + ''' + '''There are no comments for <#= enumName #> in the schema. + ''' +<#+ + } + + internal override void WriteEnumDeclaration(string enumName, string originalEnumName, string underlyingType) + { + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public Enum <#= enumName #><#= underlyingType #> +<#+ + } + + internal override void WriteMemberForEnumType(string member, string originalMemberName, bool last) + { + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + <#= member #> +<#+ + } + + internal override void WriteEnumEnd() + { +#> + End Enum +<#+ + } + + internal override void WriteFunctionImportReturnCollectionResult(string functionName, string originalFunctionName, string returnTypeName, string parameters, string parameterValues, bool isComposable, bool useEntityReference) + { +#> + ''' + ''' There are no comments for <#= functionName #> in the schema. + ''' +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public Function <#= functionName #>(<#= parameters #><#= useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty #>) As Global.Microsoft.OData.Client.DataServiceQuery(Of <#= returnTypeName #>) + Return Me.CreateFunctionQuery(Of <#= returnTypeName #>)("", "/<#= originalFunctionName #>", <#= isComposable #> <#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>) + End Function +<#+ + } + + internal override void WriteFunctionImportReturnSingleResult(string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { +#> + ''' + ''' There are no comments for <#= functionName #> in the schema. + ''' +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public Function <#= functionName #>(<#= parameters #><#= useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty #>) As <#= isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName) #> + Return <#= isReturnEntity ? "New " + returnTypeNameWithSingleSuffix + "(" : string.Empty #>Me.CreateFunctionQuerySingle(<#= "Of " + returnTypeName #>)("", "/<#= originalFunctionName #>", <#= isComposable #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>)<#= isReturnEntity ? ")" : string.Empty #> + End Function +<#+ + } + + internal override void WriteBoundFunctionInEntityTypeReturnCollectionResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { +#> + ''' + ''' There are no comments for <#= functionName #> in the schema. + ''' +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public <#= hideBaseMethod ? this.OverloadsModifier : string.Empty #>Function <#= functionName #>(<#= parameters #><#= useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty #>) As Global.Microsoft.OData.Client.DataServiceQuery(Of <#= returnTypeName #>) + Dim requestUri As Global.System.Uri = Nothing + Context.TryGetUri(Me, requestUri) + Return Me.Context.CreateFunctionQuery(Of <#= returnTypeName #>)("", String.Join("/", Global.System.Linq.Enumerable.Select(Global.System.Linq.Enumerable.Skip(requestUri.Segments, Me.Context.BaseUri.Segments.Length), Function(s) s.Trim("/"C))) + "/<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>) + End Function +<#+ + } + + internal override void WriteBoundFunctionInEntityTypeReturnSingleResult(bool hideBaseMethod, string functionName, string originalFunctionName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { +#> + ''' + ''' There are no comments for <#= functionName #> in the schema. + ''' +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public <#= hideBaseMethod ? this.OverloadsModifier : string.Empty #>Function <#= functionName #>(<#= parameters #><#= useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty #>) As <#= isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName) #> + Dim requestUri As Global.System.Uri = Nothing + Context.TryGetUri(Me, requestUri) + Return <#= isReturnEntity ? "New " + returnTypeNameWithSingleSuffix + "(" : string.Empty #>Me.Context.CreateFunctionQuerySingle(<#= "Of " + returnTypeName #>)(String.Join("/", Global.System.Linq.Enumerable.Select(Global.System.Linq.Enumerable.Skip(requestUri.Segments, Me.Context.BaseUri.Segments.Length), Function(s) s.Trim("/"C))), "/<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>)<#= isReturnEntity ? ")" : string.Empty #> + End Function +<#+ + } + + internal override void WriteActionImport(string actionName, string originalActionName, string returnTypeName, string parameters, string parameterValues) + { +#> + ''' + ''' There are no comments for <#= actionName #> in the schema. + ''' +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public Function <#= actionName #>(<#= parameters #>) As <#= returnTypeName #> + Return New <#= returnTypeName #>(Me, Me.BaseUri.OriginalString.Trim("/"C) + "/<#= originalActionName #>"<#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>) + End Function +<#+ + } + + internal override void WriteBoundActionInEntityType(bool hideBaseMethod, string actionName, string originalActionName, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { +#> + ''' + ''' There are no comments for <#= actionName #> in the schema. + ''' +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public <#= hideBaseMethod ? this.OverloadsModifier : string.Empty #>Function <#= actionName #>(<#= parameters #>) As <#= returnTypeName #> + Dim resource As Global.Microsoft.OData.Client.EntityDescriptor = Context.EntityTracker.TryGetEntityDescriptor(Me) + If resource Is Nothing Then + Throw New Global.System.Exception("cannot find entity") + End If + + Return New <#= returnTypeName #>(Me.Context, resource.EditLink.OriginalString.Trim("/"C) + "/<#= fullNamespace #>.<#= originalActionName #>"<#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>) + End Function +<#+ + } + + internal override void WriteExtensionMethodsStart() + { +#> + ''' + ''' Class containing all extension methods + ''' + Public Module ExtensionMethods +<#+ + } + + internal override void WriteExtensionMethodsEnd() + { +#> + End Module +<#+ + } + + internal override void WriteByKeyMethods(string entityTypeName, string returnTypeName, IEnumerable keys, string keyParameters, string keyDictionaryItems) + { +#> + ''' + ''' Get an entity of type <#= entityTypeName #> as <#= returnTypeName #> specified by key from an entity set + ''' + ''' source entity set + ''' dictionary with the names and values of keys + + Public Function ByKey(ByVal source As Global.Microsoft.OData.Client.DataServiceQuery(Of <#= entityTypeName #>), ByVal keys As Global.System.Collections.Generic.Dictionary(Of String, Object)) As <#= returnTypeName #> + Return New <#= returnTypeName #>(source.Context, source.GetKeyPath(Global.Microsoft.OData.Client.Serializer.GetKeyString(source.Context, keys))) + End Function + ''' + ''' Get an entity of type <#= entityTypeName #> as <#= returnTypeName #> specified by key from an entity set + ''' + ''' source entity set +<#+ + foreach (var key in keys) + { +#> + ''' The value of <#= key #> +<#+ + } +#> + + Public Function ByKey(ByVal source As Global.Microsoft.OData.Client.DataServiceQuery(Of <#= entityTypeName #>), + <#= keyParameters #>) As <#= returnTypeName #> + Dim keys As Global.System.Collections.Generic.Dictionary(Of String, Object) = New Global.System.Collections.Generic.Dictionary(Of String, Object)() From + { + <#= keyDictionaryItems #> + } + Return New <#= returnTypeName #>(source.Context, source.GetKeyPath(Global.Microsoft.OData.Client.Serializer.GetKeyString(source.Context, keys))) + End Function +<#+ + } + + internal override void WriteCastToMethods(string baseTypeName, string derivedTypeName, string derivedTypeFullName, string returnTypeName) + { +#> + ''' + ''' Cast an entity of type <#= baseTypeName #> to its derived type <#= derivedTypeFullName #> + ''' + ''' source entity + + Public Function CastTo<#= derivedTypeName #>(ByVal source As Global.Microsoft.OData.Client.DataServiceQuerySingle(Of <#= baseTypeName #>)) As <#= returnTypeName #> + Dim query As Global.Microsoft.OData.Client.DataServiceQuerySingle(Of <#= derivedTypeFullName #>) = source.CastTo(Of <#= derivedTypeFullName #>)() + Return New <#= returnTypeName #>(source.Context, query.GetPath(Nothing)) + End Function +<#+ + } + + internal override void WriteBoundFunctionReturnSingleResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string returnTypeNameWithSingleSuffix, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool isReturnEntity, bool useEntityReference) + { +#> + ''' + ''' There are no comments for <#= functionName #> in the schema. + ''' + +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public Function <#= functionName #>(ByVal source As <#= boundTypeName #><#= string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters #><#= useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty #>) As <#= isReturnEntity ? returnTypeNameWithSingleSuffix : string.Format(this.DataServiceQuerySingleStructureTemplate, returnTypeName) #> + If Not source.IsComposable Then + Throw New Global.System.NotSupportedException("The previous function is not composable.") + End If + + Return <#= isReturnEntity ? "New " + returnTypeNameWithSingleSuffix + "(" : string.Empty #>source.CreateFunctionQuerySingle(<#= "Of " + returnTypeName #>)("<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>)<#= isReturnEntity ? ")" : string.Empty #> + End Function +<#+ + } + + internal override void WriteBoundFunctionReturnCollectionResultAsExtension(string functionName, string originalFunctionName, string boundTypeName, string returnTypeName, string parameters, string fullNamespace, string parameterValues, bool isComposable, bool useEntityReference) + { +#> + ''' + ''' There are no comments for <#= functionName #> in the schema. + ''' + +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public Function <#= functionName #>(ByVal source As <#= boundTypeName #><#= string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters #><#= useEntityReference ? ", Optional ByVal useEntityReference As Boolean = False" : string.Empty #>) As Global.Microsoft.OData.Client.DataServiceQuery(Of <#= returnTypeName #>) + If Not source.IsComposable Then + Throw New Global.System.NotSupportedException("The previous function is not composable.") + End If + + Return source.CreateFunctionQuery(Of <#= returnTypeName #>)("<#= fullNamespace #>.<#= originalFunctionName #>", <#= isComposable #><#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>) + End Function +<#+ + } + + internal override void WriteBoundActionAsExtension(string actionName, string originalActionName, string boundSourceType, string returnTypeName, string parameters, string fullNamespace, string parameterValues) + { +#> + ''' + ''' There are no comments for <#= actionName #> in the schema. + ''' + +<#+ + if (this.context.EnableNamingAlias) + { +#> + ")> _ +<#+ + } +#> + Public Function <#= actionName #>(ByVal source As <#= boundSourceType #><#= string.IsNullOrEmpty(parameters) ? string.Empty : ", " + parameters #>) As <#= returnTypeName #> + If Not source.IsComposable Then + Throw New Global.System.NotSupportedException("The previous function is not composable.") + End If + Return New <#= returnTypeName #>(source.Context, source.AppendRequestUri("<#= fullNamespace #>.<#= originalActionName #>")<#= string.IsNullOrEmpty(parameterValues) ? string.Empty : ", " + parameterValues #>) + End Function +<#+ + } + + internal override void WriteNamespaceEnd() + { +#> +End Namespace +<#+ + } +} +#> diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.vstemplate b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.vstemplate new file mode 100644 index 0000000..2b1642e --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/ODataT4CodeGenerator.vstemplate @@ -0,0 +1,27 @@ + + + {TemplateID} + ODataClient.tt + OData Client + Generates proxy classes from an OData v4 service + TargetLanguage + 10 + __TemplateIcon.ico + + + ODataT4Template.tt + ODataT4Template.ttinclude + + + NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + NuGet.VisualStudio.TemplateWizard + + + + + + + + + + diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ItemTemplates/ODataT4Template.CSharp.zip b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ItemTemplates/ODataT4Template.CSharp.zip new file mode 100644 index 0000000000000000000000000000000000000000..15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7 GIT binary patch literal 22 NcmWIWW@Tf*000g10H*)| literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ItemTemplates/ODataT4Template.VisualBasic.zip b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ItemTemplates/ODataT4Template.VisualBasic.zip new file mode 100644 index 0000000000000000000000000000000000000000..15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7 GIT binary patch literal 22 NcmWIWW@Tf*000g10H*)| literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ODataT4ItemTemplate.csproj b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ODataT4ItemTemplate.csproj new file mode 100644 index 0000000..eada1ec --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ODataT4ItemTemplate.csproj @@ -0,0 +1,95 @@ + + + + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + Release + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {50899D87-AA94-4E95-B073-41740F6DB3BA} + Library + Properties + ODataT4ItemTemplate + ODataT4ItemTemplate + v4.5.1 + false + false + false + false + false + false + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + Always + true + + + Always + true + + + Always + true + + + Always + true + + + Designer + + + + + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF} + Microsoft.OData.Client + + + {989A83CC-B864-4A75-8BF3-5EDA99203A86} + Microsoft.OData.Core + + + {7D921888-FE03-4C3F-80FE-2F624505461C} + Microsoft.OData.Edm + + + {5D921888-FE03-4C3F-40FE-2F624505461D} + Microsoft.Spatial + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ap-pl.txt b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ap-pl.txt new file mode 100644 index 0000000..95e6b7a --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/ap-pl.txt @@ -0,0 +1,8 @@ +Copyright (c) Microsoft Corporation +All rights reserved. + +Licensed under the Apache License, Version 2.0 (the ""License""); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/odata_logo.png b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/odata_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b33111e74bc4f1582d0720feb440ff6036469e6b GIT binary patch literal 7538 zcmeHLc~sL^76w@&i9o|5E&(b`1f)n9t%PBT+R#*JachK%1_B=JP=x?3AP~cDT#!oj zzz8%{M`b3c&{RMmkOY)zhsC7|86qTzrPL6DLI`A=_k!&SGyjgqGdmDDAJ{?M_Hm}^B;T*pc_8nq zWR6&Uu9~?UeaST8z}6Yhva+EIDekHE-UNk_&DtP%haoB zWi98pe~5kmz1V2a_YdJdCJo(hG9yuVpVdZx5izpyc)z^^I-!x`h6pYE%k}bqY(rBM zo?}yY5i5jQCoykuX}93Iibi9SCM60R zk4lS60T~mm4(2Bmkn4v8n|Bd?>&hC60vhn=3fwLgZrsM7UkHbNM zaGY5?&^A3sxS$)BZ;JMPqf23*vUt~3Q5KAVya5k~``Eg&Dc8={a$_qQ5AuKNQ1qNc z{1#i>y%!&Ng*ilu!cI{H^9a4TylH`6Iw!EAz~ah`dgM>$X_uJ3h`O?lDxT2GsCKVG z?f$=KjbzwQ)K);oc~sbk<^;rE0IJ;=@d=!+OB?F-dXj0s+bM{o;k+S|`q8AjwnN*G z$kf^O5qPcTav8RY!ISPsgMsl_`k3BKH}6G@Lh=hL)$bhEKd^=0K`7wt?NM9=s2jHU z%Xs6Z5}AARv{xvXVR3V(c!b3ldL?9&z~qTKo!u>uIs1)}h8+$%K_KJ>P#gWBC3!9z z_R9(2mFyIvk~e*5)?>x-hL+(#WK(GkeHkm~E+wl{GICZTb>Mir!v%lDiF+Y=*)eH8 z(0?2VH}p5p#}w$G97}}%?R~=?`A*#+hk-ByLo?cDn*y`gR zE{Pkd5|r$0V2FFuGPO+aC2Pp0QZaj~fhBH@0s0$h=w4GCU8;E8n+>eDHI~|$LT|$+ zXhwEuO%VPdPUEc{P#rjF=#q~vX6__^;wzS(|D~mn{6Z6-Iaj8)+_!@9pef^>zC3Qm z$jTQMK^uOHo`&=fxwBCZa5)DBEqmgh@P#otMEbUIf}jHvmrjNL`=dp);YyhkE&Y_R zhzWwf})smQ1DQd$*6InX1SkD>=~Js*)HbcT1hw5Tw@WI38~zJ){(vq|Lj)R@k%4P< z02X+30}zc2*Ej;7#XyY^?n?K-C)qT8xXP+sWl>cfm+l4F>mtw3BJj;8HD<8G1=0ul z2VXohiOD&wG4nkRR1ep>8E*M^3`XF8jYhLq1@;}!osz{WIIyab6aO@ASFThAQq)Ka z>$fk(Z?y*$6;uw6Wa^OG*^mT6B_Jf_Q3zuNNq0t50xo1!1qZg2ss`3qH?uG&A6>-> zgV8l~f-dPJ#O^x}=fxDL zEU6z#W)bL4{Tm$h%8z)Z!(;nZFOwyz?h3MA_dQsIVqZ%jS+`QP?rr#D|9%`h3?OPw z0vqYFmh~2YCILc_vSQp7$A(awZ^)eAQ<(#(<)8vi-QXDAI0I>|cE$+6q++>r@;_1t zgtjXRpl=-@T?L z092g4+QbN&)^wO#7(h+G4$Jv^2mXJX3th~FE4k^^M9Db#cZX>seK7EE(T#0P literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/source.extension.vsixmanifest b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/source.extension.vsixmanifest new file mode 100644 index 0000000..f7c242b --- /dev/null +++ b/ApiAsAService/odata.net/src/CodeGen/ODataT4ItemTemplate/source.extension.vsixmanifest @@ -0,0 +1,28 @@ + + + + + OData v4 Client Code Generator + An item template that simplifies the process of accessing OData v4 services by generating C# and VB.Net client-side proxy classes. + https://github.com/odata/odata.net + ap-pl.txt + odata_logo.png + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/odata.net/src/CodeGen/__TemplateIcon.ico b/ApiAsAService/odata.net/src/CodeGen/__TemplateIcon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7d638b1d6002bdeac28d672a833d2f55dc18a491 GIT binary patch literal 10134 zcmeHNy>8nu5I*@Y%1}7y0~7|jWa=w4$e!-mgCb8*catV(H1ZbNvj<-D78%;2K$cEP zGqK;1I*B@ww&K5}f$5x%$B%d4kzy%YE>S`Q8jTL1d-S_Q^p%JX4xD|M5Pj(qZ~~sv zRZ8@&$GEf4`$Qk!2|l7Lv`^j(KB9B<{q&LO5HS&gjv&lq3%J51C&C})xl_2J3dw0U zn*lKPkBs3O1Rril-T&Bi24Ee+>m2_C>{^#~p2H589E%zV*0oi*qS|_$|A8ytJ*|0$x!q)0_Bej@ z7g096aEoQ=7D-lUQ`!ZN`wH(YqhWDhht{m@4CErH`Xz^EYqQb=qBbGq9V1H#Gy;c_?j^`Vbp% zv6V=#G)-zH-4H$)IGR}e3fCFe(n~bj%%58-5xmQ|l*C8AqaM7WO$6H+d z`8e4-*zY7sryuHR@xIGF)WPEL=F(9Cn4j@I2RG7Lgmbug@k>0CpTzH_;qeKs(yp17 zzt&PW80R=KX@l1z)~(miA3Do%+x#*Eybd`;oa#JRY#znA1duzUc;}RplM^~SJF{n% z%gal9R=K^swda(lrzh*OyX3S`N)B7g z3~hIw@IE_&96=IWgAZ@Ks^I&cS%;{wt<80CbBxx6s-y7ZJF!_t#9xJNZHoUg-0UO% ztqD~}@qexScrRgCV);q_YFMpJ@sGuC%P(=3)iM4>?aT6MzePMTJQhdV#V=;-cIht~ z@2Cum*jkIfXu;dsx}W&7W?sQ#IS9V0_IWk8#17>k*#DwsoG+EH=9i5^=Yw~m2JcAA zb;osX)BRSpL;TDmd1}4x976p5xHUgxtBhIVNes=8chJ$XYhQ`+9Iaz)n_uUu`5E8j z{I}uX*W=#? zr_Z7JH+{c#{MW|s&&@XKdi>kq^f|)6?Z4Ff;E|nHQQ}C z3;)tr^5z)x{DmKVM%+*Qd_B)!uvPhG{CtM-$KUo$!~R^5&s1_^jo(J>_+^|i?Y!O9 h_itO*{xP0dyY_F(EB(s<8#Cv9D0Ae6%%JZ)`3E-S-Xj12 literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/src/Common.Stylecop b/ApiAsAService/odata.net/src/Common.Stylecop new file mode 100644 index 0000000..e4f74aa --- /dev/null +++ b/ApiAsAService/odata.net/src/Common.Stylecop @@ -0,0 +1,321 @@ + + + NoMerge + + + + + False + + TemporaryGeneratedFile_.*\.cs$ + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + True + True + + + + + + + False + + + + + + id + in + is + if + + + + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + False + + + + + + + + + False + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/CustomDictionary.xml b/ApiAsAService/odata.net/src/CustomDictionary.xml new file mode 100644 index 0000000..1bb06a2 --- /dev/null +++ b/ApiAsAService/odata.net/src/CustomDictionary.xml @@ -0,0 +1,49 @@ + + + + + IANA + describedby + Composable + noncomposable + Edm + Edmx + Csdl + Multi + Contrib + Changeset + odata + Api + Util + Dereferenceable + Etag + etag + deltalink + Srs + Pos + groupby + Untyped + Ieee + + + OData + CharSets + ETag + subType + DeltaLink + forceFully + TypeCast + NonEntity + UnBound + NonBinding + GroupBy + + + + + IDs + Un + URL + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ALinqExpressionVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ALinqExpressionVisitor.cs new file mode 100644 index 0000000..3921d12 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ALinqExpressionVisitor.cs @@ -0,0 +1,465 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#else +namespace Microsoft.OData.Client +#endif +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq.Expressions; + + /// + /// base visitor class for walking an expression tree bottom up. + /// + internal abstract class ALinqExpressionVisitor + { + /// + /// Main visit method for ALinqExpressionVisitor + /// + /// The expression to visit + /// The visited expression + internal virtual Expression Visit(Expression exp) + { + if (exp == null) + { + return exp; + } + + switch (exp.NodeType) + { + case ExpressionType.UnaryPlus: + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + return this.VisitUnary((UnaryExpression)exp); + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: +#if !ODATA_CLIENT + case ExpressionType.Power: +#endif + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + return this.VisitBinary((BinaryExpression)exp); + case ExpressionType.TypeIs: + return this.VisitTypeIs((TypeBinaryExpression)exp); + case ExpressionType.Conditional: + return this.VisitConditional((ConditionalExpression)exp); + case ExpressionType.Constant: + return this.VisitConstant((ConstantExpression)exp); + case ExpressionType.Parameter: + return this.VisitParameter((ParameterExpression)exp); + case ExpressionType.MemberAccess: + return this.VisitMemberAccess((MemberExpression)exp); + case ExpressionType.Call: + return this.VisitMethodCall((MethodCallExpression)exp); + case ExpressionType.Lambda: + return this.VisitLambda((LambdaExpression)exp); + case ExpressionType.New: + return this.VisitNew((NewExpression)exp); + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + return this.VisitNewArray((NewArrayExpression)exp); + case ExpressionType.Invoke: + return this.VisitInvocation((InvocationExpression)exp); + case ExpressionType.MemberInit: + return this.VisitMemberInit((MemberInitExpression)exp); + case ExpressionType.ListInit: + return this.VisitListInit((ListInitExpression)exp); + default: + throw new NotSupportedException(Strings.ALinq_UnsupportedExpression(exp.NodeType.ToString())); + } + } + + /// + /// MemberBinding visit method + /// + /// The MemberBinding expression to visit + /// The visited MemberBinding expression + internal virtual MemberBinding VisitBinding(MemberBinding binding) + { + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + return this.VisitMemberAssignment((MemberAssignment)binding); + case MemberBindingType.MemberBinding: + return this.VisitMemberMemberBinding((MemberMemberBinding)binding); + case MemberBindingType.ListBinding: + return this.VisitMemberListBinding((MemberListBinding)binding); + default: + throw new NotSupportedException(Strings.ALinq_UnsupportedExpression(binding.BindingType.ToString())); + } + } + + /// + /// ElementInit visit method + /// + /// The ElementInit expression to visit + /// The visited ElementInit expression + internal virtual ElementInit VisitElementInitializer(ElementInit initializer) + { + ReadOnlyCollection arguments = this.VisitExpressionList(initializer.Arguments); + return arguments != initializer.Arguments ? Expression.ElementInit(initializer.AddMethod, arguments) : initializer; + } + + /// + /// UnaryExpression visit method + /// + /// The UnaryExpression expression to visit + /// The visited UnaryExpression expression + internal virtual Expression VisitUnary(UnaryExpression u) + { + Expression operand = this.Visit(u.Operand); + return operand != u.Operand ? Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method) : u; + } + + /// + /// BinaryExpression visit method + /// + /// The BinaryExpression expression to visit + /// The visited BinaryExpression expression + internal virtual Expression VisitBinary(BinaryExpression b) + { + Expression left = this.Visit(b.Left); + Expression right = this.Visit(b.Right); + Expression conversion = this.Visit(b.Conversion); + if (left != b.Left || right != b.Right || conversion != b.Conversion) + { + if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null) + { + return Expression.Coalesce(left, right, conversion as LambdaExpression); + } + + return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method); + } + + return b; + } + + /// + /// TypeBinaryExpression visit method + /// + /// The TypeBinaryExpression expression to visit + /// The visited TypeBinaryExpression expression + internal virtual Expression VisitTypeIs(TypeBinaryExpression b) + { + Expression expr = this.Visit(b.Expression); + return expr != b.Expression ? Expression.TypeIs(expr, b.TypeOperand) : b; + } + + /// + /// ConstantExpression visit method + /// + /// The ConstantExpression expression to visit + /// The visited ConstantExpression expression + internal virtual Expression VisitConstant(ConstantExpression c) + { + return c; + } + + /// + /// ConditionalExpression visit method + /// + /// The ConditionalExpression expression to visit + /// The visited ConditionalExpression expression + internal virtual Expression VisitConditional(ConditionalExpression c) + { + Expression test = this.Visit(c.Test); + Expression iftrue = this.Visit(c.IfTrue); + Expression iffalse = this.Visit(c.IfFalse); + if (test != c.Test || iftrue != c.IfTrue || iffalse != c.IfFalse) + { + return Expression.Condition(test, iftrue, iffalse, iftrue.Type.IsAssignableFrom(iffalse.Type) ? iftrue.Type : iffalse.Type); + } + + return c; + } + + /// + /// ParameterExpression visit method + /// + /// The ParameterExpression expression to visit + /// The visited ParameterExpression expression + internal virtual Expression VisitParameter(ParameterExpression p) + { + return p; + } + + /// + /// MemberExpression visit method + /// + /// The MemberExpression expression to visit + /// The visited MemberExpression expression + internal virtual Expression VisitMemberAccess(MemberExpression m) + { + Expression exp = this.Visit(m.Expression); + return exp != m.Expression ? Expression.MakeMemberAccess(exp, m.Member) : m; + } + + /// + /// MethodCallExpression visit method + /// + /// The MethodCallExpression expression to visit + /// The visited MethodCallExpression expression + internal virtual Expression VisitMethodCall(MethodCallExpression m) + { + Expression obj = this.Visit(m.Object); + IEnumerable args = this.VisitExpressionList(m.Arguments); + return obj != m.Object || args != m.Arguments ? Expression.Call(obj, m.Method, args) : m; + } + + /// + /// Expression list visit method + /// + /// The expression list to visit + /// The visited expression list + internal virtual ReadOnlyCollection VisitExpressionList(ReadOnlyCollection original) + { + List list = null; + for (int i = 0, n = original.Count; i < n; i++) + { + Expression p = this.Visit(original[i]); + if (list != null) + { + list.Add(p); + } + else if (p != original[i]) + { + list = new List(n); + for (int j = 0; j < i; j++) + { + list.Add(original[j]); + } + + list.Add(p); + } + } + + if (list != null) + { + return new ReadOnlyCollection(list); + } + + return original; + } + + /// + /// MemberAssignment visit method + /// + /// The MemberAssignment to visit + /// The visited MemberAssignmentt + internal virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment) + { + Expression e = this.Visit(assignment.Expression); + return e != assignment.Expression ? Expression.Bind(assignment.Member, e) : assignment; + } + + /// + /// MemberMemberBinding visit method + /// + /// The MemberMemberBinding to visit + /// The visited MemberMemberBinding + internal virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) + { + IEnumerable bindings = this.VisitBindingList(binding.Bindings); + return bindings != binding.Bindings ? Expression.MemberBind(binding.Member, bindings) : binding; + } + + /// + /// MemberListBinding visit method + /// + /// The MemberListBinding to visit + /// The visited MemberListBinding + internal virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding) + { + IEnumerable initializers = this.VisitElementInitializerList(binding.Initializers); + return initializers != binding.Initializers ? Expression.ListBind(binding.Member, initializers) : binding; + } + + /// + /// Binding List visit method + /// + /// The Binding list to visit + /// The visited Binding list + internal virtual IEnumerable VisitBindingList(ReadOnlyCollection original) + { + List list = null; + for (int i = 0, n = original.Count; i < n; i++) + { + MemberBinding b = this.VisitBinding(original[i]); + if (list != null) + { + list.Add(b); + } + else if (b != original[i]) + { + list = new List(n); + for (int j = 0; j < i; j++) + { + list.Add(original[j]); + } + + list.Add(b); + } + } + + if (list != null) + { + return list; + } + + return original; + } + + /// + /// ElementInit expression list visit method + /// + /// The ElementInit expression list to visit + /// The visited ElementInit expression list + internal virtual IEnumerable VisitElementInitializerList(ReadOnlyCollection original) + { + List list = null; + for (int i = 0, n = original.Count; i < n; i++) + { + ElementInit init = this.VisitElementInitializer(original[i]); + if (list != null) + { + list.Add(init); + } + else if (init != original[i]) + { + list = new List(n); + for (int j = 0; j < i; j++) + { + list.Add(original[j]); + } + + list.Add(init); + } + } + + if (list != null) + { + return list; + } + + return original; + } + + /// + /// LambdaExpression visit method + /// + /// The LambdaExpression to visit + /// The visited LambdaExpression + internal virtual Expression VisitLambda(LambdaExpression lambda) + { + Expression body = this.Visit(lambda.Body); + if (body != lambda.Body) + { + return Expression.Lambda(lambda.Type, body, lambda.Parameters); + } + + return lambda; + } + + /// + /// NewExpression visit method + /// + /// The NewExpression to visit + /// The visited NewExpression + internal virtual NewExpression VisitNew(NewExpression nex) + { + IEnumerable args = this.VisitExpressionList(nex.Arguments); + if (args != nex.Arguments) + { + return nex.Members != null + ? Expression.New(nex.Constructor, args, nex.Members) + : Expression.New(nex.Constructor, args); + } + + return nex; + } + + /// + /// MemberInitExpression visit method + /// + /// The MemberInitExpression to visit + /// The visited MemberInitExpression + internal virtual Expression VisitMemberInit(MemberInitExpression init) + { + NewExpression n = this.VisitNew(init.NewExpression); + IEnumerable bindings = this.VisitBindingList(init.Bindings); + return n != init.NewExpression || bindings != init.Bindings ? Expression.MemberInit(n, bindings) : init; + } + + /// + /// ListInitExpression visit method + /// + /// The ListInitExpression to visit + /// The visited ListInitExpression + internal virtual Expression VisitListInit(ListInitExpression init) + { + NewExpression n = this.VisitNew(init.NewExpression); + IEnumerable initializers = this.VisitElementInitializerList(init.Initializers); + return n != init.NewExpression || initializers != init.Initializers ? Expression.ListInit(n, initializers) : init; + } + + /// + /// NewArrayExpression visit method + /// + /// The NewArrayExpression to visit + /// The visited NewArrayExpression + internal virtual Expression VisitNewArray(NewArrayExpression na) + { + IEnumerable exprs = this.VisitExpressionList(na.Expressions); + if (exprs != na.Expressions) + { + return na.NodeType == ExpressionType.NewArrayInit ? Expression.NewArrayInit(na.Type.GetElementType(), exprs) : Expression.NewArrayBounds(na.Type.GetElementType(), exprs); + } + + return na; + } + + /// + /// InvocationExpression visit method + /// + /// The InvocationExpression to visit + /// The visited InvocationExpression + internal virtual Expression VisitInvocation(InvocationExpression iv) + { + IEnumerable args = this.VisitExpressionList(iv.Arguments); + Expression expr = this.Visit(iv.Expression); + return args != iv.Arguments || expr != iv.Expression ? Expression.Invoke(expr, args) : iv; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/AddNewEndingTokenVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/AddNewEndingTokenVisitor.cs new file mode 100644 index 0000000..2406790 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/AddNewEndingTokenVisitor.cs @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using Microsoft.OData.Client.ALinq.UriParser; + + /// + /// Traverse the tree to the last token, then add a new token there if provided. + /// + internal class AddNewEndingTokenVisitor : IPathSegmentTokenVisitor + { + /// + /// The new token to add to the tree + /// + private readonly PathSegmentToken newTokenToAdd; + + /// + /// Create a new AddNewEndingTokenVisitor, with the new token to add at the end. + /// + /// a new token to add at the end of the path, can be null + public AddNewEndingTokenVisitor(PathSegmentToken newTokenToAdd) + { + this.newTokenToAdd = newTokenToAdd; + } + + /// + /// Traverse a SystemToken. Always throws because a SystemToken is illegal in this case. + /// + /// The system token to traverse + public void Visit(SystemToken tokenIn) + { + throw new NotSupportedException(Strings.ALinq_IllegalSystemQueryOption(tokenIn.Identifier)); + } + + /// + /// Traverse a NonSystemToken. + /// + /// The NonSystemToken to traverse. + public void Visit(NonSystemToken tokenIn) + { + if (tokenIn.NextToken == null) + { + if (newTokenToAdd != null) + { + tokenIn.SetNextToken(newTokenToAdd); + } + } + else + { + tokenIn.NextToken.Accept(this); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceExpressionVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceExpressionVisitor.cs new file mode 100644 index 0000000..1a211ed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceExpressionVisitor.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System.Diagnostics; + using System.Linq.Expressions; + + #endregion Namespaces + + /// + /// Specific Vistior base class for the DataServiceQueryProvider. + /// + internal abstract class DataServiceALinqExpressionVisitor : ALinqExpressionVisitor + { + /// + /// Main visit method. + /// + /// Expression to visit + /// Visited expression + internal override Expression Visit(Expression exp) + { + if (exp == null) + { + return null; + } + + switch ((ResourceExpressionType)exp.NodeType) + { + case ResourceExpressionType.RootResourceSet: + case ResourceExpressionType.ResourceNavigationProperty: + case ResourceExpressionType.RootSingleResource: + return this.VisitQueryableResourceExpression((QueryableResourceExpression)exp); + case ResourceExpressionType.ResourceNavigationPropertySingleton: + return this.VisitNavigationPropertySingletonExpression((NavigationPropertySingletonExpression)exp); + case ResourceExpressionType.InputReference: + return this.VisitInputReferenceExpression((InputReferenceExpression)exp); + default: + return base.Visit(exp); + } + } + + /// + /// QueryableResourceExpression visit method. + /// + /// QueryableResource expression to visit + /// Visited QueryableResourceExpression expression + internal virtual Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) + { + Expression source = this.Visit(rse.Source); + + if (source != rse.Source) + { + rse = QueryableResourceExpression.CreateNavigationResourceExpression(rse.NodeType, rse.Type, source, rse.MemberExpression, rse.ResourceType, rse.ExpandPaths, rse.CountOption, rse.CustomQueryOptions, rse.Projection, rse.ResourceTypeAs, rse.UriVersion, rse.OperationName, rse.OperationParameters); + } + + return rse; + } + + /// + /// NavigationPropertySingletonExpressionvisit method. + /// + /// NavigationPropertySingletonExpression expression to visit + /// Visited NavigationPropertySingletonExpression expression + internal virtual Expression VisitNavigationPropertySingletonExpression(NavigationPropertySingletonExpression npse) + { + Expression source = this.Visit(npse.Source); + + if (source != npse.Source) + { + npse = new NavigationPropertySingletonExpression(npse.Type, source, npse.MemberExpression, npse.MemberExpression.Type, npse.ExpandPaths, npse.CountOption, npse.CustomQueryOptions, npse.Projection, npse.ResourceTypeAs, npse.UriVersion); + } + + return npse; + } + + /// + /// Visit an , producing a new InputReferenceExpression + /// based on the visited form of the that is referenced by + /// the InputReferenceExpression argument, . + /// + /// InputReferenceExpression expression to visit + /// Visited InputReferenceExpression expression + internal virtual Expression VisitInputReferenceExpression(InputReferenceExpression ire) + { + Debug.Assert(ire != null, "ire != null -- otherwise caller never should have visited here"); + ResourceExpression re = (ResourceExpression)this.Visit(ire.Target); + return re.CreateReference(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceQueryProvider.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceQueryProvider.cs new file mode 100644 index 0000000..9e1c4cc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/DataServiceQueryProvider.cs @@ -0,0 +1,156 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + #endregion Namespaces + + /// + /// QueryProvider implementation + /// + public sealed class DataServiceQueryProvider : IQueryProvider + { + /// DataServiceContext for query provider + internal readonly DataServiceContext Context; + + /// Constructs a query provider based on the context passed in + /// The context for the query provider + internal DataServiceQueryProvider(DataServiceContext context) + { + this.Context = context; + } + + #region IQueryProvider implementation + + /// Factory method for creating DataServiceOrderedQuery based on expression + /// The expression for the new query + /// new DataServiceQuery + public IQueryable CreateQuery(Expression expression) + { + Util.CheckArgumentNull(expression, "expression"); + Type et = TypeSystem.GetElementType(expression.Type); + Type qt = typeof(DataServiceQuery<>.DataServiceOrderedQuery).MakeGenericType(et); + object[] args = new object[] { expression, this }; + + ConstructorInfo ci = qt.GetInstanceConstructor( + false /*isPublic*/, + new Type[] { typeof(Expression), typeof(DataServiceQueryProvider) }); + + return (IQueryable)Util.ConstructorInvoke(ci, args); + } + + /// Factory method for creating DataServiceOrderedQuery based on expression + /// generic type + /// The expression for the new query + /// new DataServiceQuery + public IQueryable CreateQuery(Expression expression) + { + Util.CheckArgumentNull(expression, "expression"); + return new DataServiceQuery.DataServiceOrderedQuery(expression, this); + } + + /// Creates and executes a DataServiceQuery based on the passed in expression + /// The expression for the new query + /// the results + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] + public object Execute(Expression expression) + { + Util.CheckArgumentNull(expression, "expression"); + + MethodInfo mi = typeof(DataServiceQueryProvider).GetMethod("ReturnSingleton", false /*isPublic*/, false /*isStatic*/); + return mi.MakeGenericMethod(expression.Type).Invoke(this, new object[] { expression }); + } + + /// Creates and executes a DataServiceQuery based on the passed in expression + /// generic type + /// The expression for the new query + /// the results + public TResult Execute(Expression expression) + { + Util.CheckArgumentNull(expression, "expression"); + return ReturnSingleton(expression); + } + + #endregion + + /// Creates and executes a DataServiceQuery based on the passed in expression which results a single value + /// generic type + /// The expression for the new query + /// single valued results + internal TElement ReturnSingleton(Expression expression) + { + IQueryable query = new DataServiceQuery.DataServiceOrderedQuery(expression, this); + + MethodCallExpression mce = expression as MethodCallExpression; + Debug.Assert(mce != null, "mce != null"); + + SequenceMethod sequenceMethod; + if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod)) + { + switch (sequenceMethod) + { + case SequenceMethod.Single: + return query.AsEnumerable().Single(); + case SequenceMethod.SingleOrDefault: + return query.AsEnumerable().SingleOrDefault(); + case SequenceMethod.First: + return query.AsEnumerable().First(); + case SequenceMethod.FirstOrDefault: + return query.AsEnumerable().FirstOrDefault(); +#if !PORTABLELIB + case SequenceMethod.LongCount: + case SequenceMethod.Count: + return (TElement)Convert.ChangeType(((DataServiceQuery)query).GetQuerySetCount(this.Context), typeof(TElement), System.Globalization.CultureInfo.InvariantCulture.NumberFormat); +#endif + default: + throw Error.MethodNotSupported(mce); + } + } + + // Should never get here - should be caught by expression compiler. + Debug.Assert(false, "Not supported singleton operator not caught by Resource Binder"); + throw Error.MethodNotSupported(mce); + } + + /// Builds the Uri for the expression passed in. + /// The expression to translate into a Uri + /// Query components + internal QueryComponents Translate(Expression e) + { + Uri uri; + Version version; + bool addTrailingParens = false; + Dictionary normalizerRewrites = null; + + // short cut analysis if just a resource set or singleton resource. + // note - to be backwards compatible with V1, will only append trailing () for queries + // that include more then just a resource set. + if (!(e is QueryableResourceExpression)) + { + normalizerRewrites = new Dictionary(ReferenceEqualityComparer.Instance); + e = Evaluator.PartialEval(e); + e = ExpressionNormalizer.Normalize(e, normalizerRewrites); + e = ResourceBinder.Bind(e, this.Context); + addTrailingParens = true; + } + + UriWriter.Translate(this.Context, addTrailingParens, e, out uri, out version); + ResourceExpression re = e as ResourceExpression; + Type lastSegmentType = re.Projection == null ? re.ResourceType : re.Projection.Selector.Parameters[0].Type; + LambdaExpression selector = re.Projection == null ? null : re.Projection.Selector; + return new QueryComponents(uri, version, lastSegmentType, selector, normalizerRewrites); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/Evaluator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/Evaluator.cs new file mode 100644 index 0000000..c95b622 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/Evaluator.cs @@ -0,0 +1,208 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq.Expressions; + + /// + /// performs funcletization on an expression tree + /// + internal static class Evaluator + { + /// + /// Performs evaluation and replacement of independent sub-trees + /// + /// The root of the expression tree. + /// A function that decides whether a given expression node can be part of the local function. + /// A new tree with sub-trees evaluated and replaced. + internal static Expression PartialEval(Expression expression, Func canBeEvaluated) + { + Nominator nominator = new Nominator(canBeEvaluated); + HashSet candidates = nominator.Nominate(expression); + return new SubtreeEvaluator(candidates).Eval(expression); + } + + /// + /// Performs evaluation and replacement of independent sub-trees + /// + /// The root of the expression tree. + /// A new tree with sub-trees evaluated and replaced. + internal static Expression PartialEval(Expression expression) + { + return PartialEval(expression, Evaluator.CanBeEvaluatedLocally); + } + + /// + /// Evaluates if an expression can be evaluated locally. + /// + /// the expression. + /// true/ false if can be evaluated locally + private static bool CanBeEvaluatedLocally(Expression expression) + { + return expression.NodeType != ExpressionType.Parameter && + expression.NodeType != ExpressionType.Lambda && + expression.NodeType != (ExpressionType)ResourceExpressionType.RootResourceSet && + expression.NodeType != (ExpressionType)ResourceExpressionType.RootSingleResource; + } + + /// + /// Evaluates and replaces sub-trees when first candidate is reached (top-down) + /// + internal class SubtreeEvaluator : DataServiceALinqExpressionVisitor + { + /// list of candidates + private HashSet candidates; + + /// + /// constructs an expression evaluator with a list of candidates + /// + /// List of expressions to evaluate + internal SubtreeEvaluator(HashSet candidates) + { + this.candidates = candidates; + } + + /// + /// Evaluates an expression sub-tree + /// + /// The expression to evaluate. + /// The evaluated expression. + internal Expression Eval(Expression exp) + { + return this.Visit(exp); + } + + /// + /// Visit method for visitor + /// + /// the expression to visit + /// visited expression + internal override Expression Visit(Expression exp) + { + if (exp == null) + { + return null; + } + + if (this.candidates.Contains(exp)) + { + return Evaluate(exp); + } + + return base.Visit(exp); + } + + /// + /// Evaluates expression + /// + /// the expression to evaluate + /// constant expression with return value of evaluation + private static Expression Evaluate(Expression e) + { + if (e.NodeType == ExpressionType.Constant) + { + return e; + } + + LambdaExpression lambda = Expression.Lambda(e); + Delegate fn = lambda.Compile(); + object constantValue = fn.DynamicInvoke(null); + Debug.Assert(!(constantValue is Expression), "!(constantValue is Expression)"); + + // Use the expression type unless it's an array type, + // where the actual type may be a vector array rather + // than an array with a dynamic lower bound. + Type constantType = e.Type; + if (constantValue != null && constantType.IsArray && constantType.GetElementType() == constantValue.GetType().GetElementType()) + { + constantType = constantValue.GetType(); + } + + return Expression.Constant(constantValue, constantType); + } + } + + /// + /// Performs bottom-up analysis to determine which nodes can possibly + /// be part of an evaluated sub-tree. + /// + internal class Nominator : DataServiceALinqExpressionVisitor + { + /// func to determine whether expression can be evaluated + private Func functionCanBeEvaluated; + + /// candidate expressions for evaluation + private HashSet candidates; + + /// flag for when sub tree cannot be evaluated + private bool cannotBeEvaluated; + + /// + /// Creates the Nominator based on the function passed. + /// + /// + /// A Func speficying whether an expression can be evaluated or not. + /// + /// visited expression + internal Nominator(Func functionCanBeEvaluated) + { + this.functionCanBeEvaluated = functionCanBeEvaluated; + } + + /// + /// Nominates an expression to see if it can be evaluated + /// + /// + /// Expression to check + /// + /// a list of expression sub trees that can be evaluated + internal HashSet Nominate(Expression expression) + { + this.candidates = new HashSet(EqualityComparer.Default); + this.Visit(expression); + return this.candidates; + } + + /// + /// Visit method for walking expression tree bottom up. + /// + /// + /// root expression to visit + /// + /// visited expression + internal override Expression Visit(Expression expression) + { + if (expression != null) + { + bool saveCannotBeEvaluated = this.cannotBeEvaluated; + this.cannotBeEvaluated = false; + + base.Visit(expression); + + if (!this.cannotBeEvaluated) + { + if (this.functionCanBeEvaluated(expression)) + { + this.candidates.Add(expression); + } + else + { + this.cannotBeEvaluated = true; + } + } + + this.cannotBeEvaluated |= saveCannotBeEvaluated; + } + + return expression; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpandOnlyPathToStringVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpandOnlyPathToStringVisitor.cs new file mode 100644 index 0000000..2a7147f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpandOnlyPathToStringVisitor.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Text; + using Microsoft.OData.Client.ALinq.UriParser; + + /// + /// Build a string based on a path that contains only expands. + /// + internal class ExpandOnlyPathToStringVisitor : IPathSegmentTokenVisitor + { + /// + /// Const to represent the beginning of a sub expand clause. + /// + //// ($expand= + private readonly string subExpandStartingText = + new StringBuilder().Append(UriHelper.LEFTPAREN) + .Append(UriHelper.DOLLARSIGN) + .Append(UriHelper.OPTIONEXPAND) + .Append(UriHelper.EQUALSSIGN).ToString(); + + /// + /// Visit a SystemToken + /// + /// the system token to visit + /// Always throws, since a system token is invalid in an expand path. + public string Visit(SystemToken tokenIn) + { + throw new NotSupportedException(Strings.ALinq_IllegalSystemQueryOption(tokenIn.Identifier)); + } + + /// + /// Visit a NonSystemToken + /// + /// the token to visit + /// A string containing the expand represented by the input token. + public string Visit(NonSystemToken tokenIn) + { + if (tokenIn.IsNamespaceOrContainerQualified()) + { + if (tokenIn.NextToken != null) + { + return tokenIn.Identifier + "/" + tokenIn.NextToken.Accept(this); + } + else + { + throw new NotSupportedException(Strings.ALinq_TypeTokenWithNoTrailingNavProp(tokenIn.Identifier)); + } + } + else + { + if (tokenIn.NextToken == null) + { + return tokenIn.Identifier; + } + else + { + return tokenIn.Identifier + subExpandStartingText + tokenIn.NextToken.Accept(this) + UriHelper.RIGHTPAREN; + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionNormalizer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionNormalizer.cs new file mode 100644 index 0000000..8952d7f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionNormalizer.cs @@ -0,0 +1,803 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using Microsoft.OData.Client.Metadata; + using System.Diagnostics; + using System.Linq.Expressions; + using System.Reflection; + + #endregion Namespaces + + /// + /// Replaces expression patterns produced by the compiler with approximations + /// used in query translation. For instance, the following VB code: + /// + /// x = y + /// + /// becomes the expression + /// + /// Equal(MethodCallExpression(Microsoft.VisualBasic.CompilerServices.Operators.CompareString(x, y, False), 0) + /// + /// which is normalized to + /// + /// Equal(x, y) + /// + /// Comment convention: + /// + /// CODE(Lang): _VB or C# coding pattern being simplified_ + /// ORIGINAL: _original LINQ expression_ + /// NORMALIZED: _normalized LINQ expression_ + /// + internal class ExpressionNormalizer : DataServiceALinqExpressionVisitor + { + #region Private fields + + /// + /// If we encounter a MethodCallExpression, we never need to lift to lift to null. This capability + /// exists to translate certain patterns in the language. In this case, the user (or compiler) + /// has explicitly asked for a method invocation (at which point, lifting can no longer occur). + /// + private const bool LiftToNull = false; + + /// + /// Gets a dictionary mapping from LINQ expressions to matched by those expressions. Used + /// to identify composite expression patterns. + /// + private readonly Dictionary _patterns = new Dictionary(ReferenceEqualityComparer.Instance); + + /// Records the generated-to-source rewrites created. + private readonly Dictionary normalizerRewrites; + + #endregion Private fields + + #region Constructors + + /// Initializes a new instance. + /// Dictionary in which to store rewrites. + private ExpressionNormalizer(Dictionary normalizerRewrites) + { + Debug.Assert(normalizerRewrites != null, "normalizerRewrites != null"); + this.normalizerRewrites = normalizerRewrites; + } + + #endregion Constructors + + #region Internal properties + + /// Records the generated-to-source rewrites created. + internal Dictionary NormalizerRewrites + { + get { return this.normalizerRewrites; } + } + + #endregion Internal properties + + /// + /// Applies normalization rewrites to the specified + /// , recording them in the + /// dictionary. + /// + /// Expression to normalize. + /// Dictionary in which to record rewrites. + /// The normalized expression. + internal static Expression Normalize(Expression expression, Dictionary rewrites) + { + Debug.Assert(expression != null, "expression != null"); + Debug.Assert(rewrites != null, "rewrites != null"); + + ExpressionNormalizer normalizer = new ExpressionNormalizer(rewrites); + Expression result = normalizer.Visit(expression); + return result; + } + + /// + /// Handle binary patterns: + /// + /// - VB 'Is' operator + /// - Compare patterns + /// + internal override Expression VisitBinary(BinaryExpression b) + { + BinaryExpression visited = (BinaryExpression)base.VisitBinary(b); + + // CODE(VB): x Is y + // ORIGINAL: Equal(Convert(x, typeof(object)), Convert(y, typeof(object)) + // NORMALIZED: Equal(x, y) + if (visited.NodeType == ExpressionType.Equal) + { + Expression normalizedLeft = UnwrapObjectConvert(visited.Left); + Expression normalizedRight = UnwrapObjectConvert(visited.Right); + if (normalizedLeft != visited.Left || normalizedRight != visited.Right) + { + visited = CreateRelationalOperator(ExpressionType.Equal, normalizedLeft, normalizedRight); + } + } + + // CODE(VB): x = y + // ORIGINAL: Equal(Microsoft.VisualBasic.CompilerServices.Operators.CompareString(x, y, False), 0) + // NORMALIZED: Equal(x, y) + Pattern pattern; + if (_patterns.TryGetValue(visited.Left, out pattern) && pattern.Kind == PatternKind.Compare && IsConstantZero(visited.Right)) + { + ComparePattern comparePattern = (ComparePattern)pattern; + // handle relational operators + BinaryExpression relationalExpression; + if (TryCreateRelationalOperator(visited.NodeType, comparePattern.Left, comparePattern.Right, out relationalExpression)) + { + visited = relationalExpression; + } + } + + this.RecordRewrite(b, visited); + + return visited; + } + + /// + /// CODE: x + /// ORIGINAL: Convert(x, t) where t is assignable from typeof(x) + /// ORIGINAL: x as t, where t is assignable from typeof(x) + /// ORIGINAL: and typeof(x) or t are not known primitives unless typeof(x) == t + /// ORIGINAL: and x is not a collection of entity types + /// ORIGINAL: and x is not a enum type + /// NORMALIZED: x + /// + internal override Expression VisitUnary(UnaryExpression u) + { + UnaryExpression visited = (UnaryExpression)base.VisitUnary(u); + Expression result = visited; + + // Note that typically we would record a potential rewrite + // after extracting the conversion, but we avoid doing this + // because it breaks undoing the rewrite by making the non-local + // change circular, ie: + // unary [operand = a] + // becomes + // a <- unary [operand = a] + // So the undoing visits a, then the original unary, then the + // operand and again the unary, the operand, etc. + this.RecordRewrite(u, result); + + // Convert(x, t) or x as t, where t is assignable from typeof(x) + if ((visited.NodeType == ExpressionType.Convert || visited.NodeType == ExpressionType.TypeAs) && visited.Type.IsAssignableFrom(visited.Operand.Type)) + { + // typeof(x) or t are not known primitives unless typeof(x) == t + if (!PrimitiveType.IsKnownNullableType(visited.Operand.Type) && !PrimitiveType.IsKnownNullableType(visited.Type) || visited.Operand.Type == visited.Type) + { + // x is not a collection of entity types + if(!(ClientTypeUtil.TypeOrElementTypeIsEntity(visited.Operand.Type) && ProjectionAnalyzer.IsCollectionProducingExpression(visited.Operand))) + { + // x is not an enum type + if (!visited.Operand.Type.IsEnum()) + { + result = visited.Operand; + } + } + } + } + + return result; + } + + /// + /// CODE: x + /// ORIGINAL: Convert(x, typeof(object)) + /// ORIGINAL(Funcletized): Constant(x, typeof(object)) + /// NORMALIZED: x + /// + private static Expression UnwrapObjectConvert(Expression input) + { + // recognize funcletized (already evaluated) Converts + if (input.NodeType == ExpressionType.Constant && input.Type == typeof(object)) + { + ConstantExpression constant = (ConstantExpression)input; + + // we will handle nulls later, so just bypass those + if (constant.Value != null && + constant.Value.GetType() != typeof(object)) + { + return Expression.Constant(constant.Value, constant.Value.GetType()); + } + } + + // unwrap object converts + while (ExpressionType.Convert == input.NodeType && typeof(object) == input.Type) + { + input = ((UnaryExpression)input).Operand; + } + + return input; + } + + /// + /// Returns true if the given expression is a constant '0'. + /// + private static bool IsConstantZero(Expression expression) + { + return expression.NodeType == ExpressionType.Constant && + ((ConstantExpression)expression).Value.Equals(0); + } + + /// + /// Handles MethodCall patterns: + /// + /// - Operator overloads + /// - VB operators + /// + internal override Expression VisitMethodCall(MethodCallExpression call) + { + Expression visited = this.VisitMethodCallNoRewrite(call); + this.RecordRewrite(call, visited); + return visited; + } + + /// + /// Handles MethodCall patterns (without recording rewrites): + /// + /// - Operator overloads + /// - VB operators + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Large switch is necessary")] + internal Expression VisitMethodCallNoRewrite(MethodCallExpression call) + { + MethodCallExpression visited = (MethodCallExpression)base.VisitMethodCall(call); + + // handle operator overloads + if (visited.Method.IsStatic && visited.Method.Name.StartsWith("op_", StringComparison.Ordinal)) + { + // handle binary operator overloads + if (visited.Arguments.Count == 2) + { + // CODE(C#): x == y + // ORIGINAL: MethodCallExpression(, x, y) + // NORMALIZED: Equal(x, y) + switch (visited.Method.Name) + { + case "op_Equality": + return Expression.Equal(visited.Arguments[0], visited.Arguments[1], LiftToNull, visited.Method); + + case "op_Inequality": + return Expression.NotEqual(visited.Arguments[0], visited.Arguments[1], LiftToNull, visited.Method); + + case "op_GreaterThan": + return Expression.GreaterThan(visited.Arguments[0], visited.Arguments[1], LiftToNull, visited.Method); + + case "op_GreaterThanOrEqual": + return Expression.GreaterThanOrEqual(visited.Arguments[0], visited.Arguments[1], LiftToNull, visited.Method); + + case "op_LessThan": + return Expression.LessThan(visited.Arguments[0], visited.Arguments[1], LiftToNull, visited.Method); + + case "op_LessThanOrEqual": + return Expression.LessThanOrEqual(visited.Arguments[0], visited.Arguments[1], LiftToNull, visited.Method); + + case "op_Multiply": + return Expression.Multiply(visited.Arguments[0], visited.Arguments[1], visited.Method); + + case "op_Subtraction": + return Expression.Subtract(visited.Arguments[0], visited.Arguments[1], visited.Method); + + case "op_Addition": + return Expression.Add(visited.Arguments[0], visited.Arguments[1], visited.Method); + + case "op_Division": + return Expression.Divide(visited.Arguments[0], visited.Arguments[1], visited.Method); + + case "op_Modulus": + return Expression.Modulo(visited.Arguments[0], visited.Arguments[1], visited.Method); + + case "op_BitwiseAnd": + return Expression.And(visited.Arguments[0], visited.Arguments[1], visited.Method); + + case "op_BitwiseOr": + return Expression.Or(visited.Arguments[0], visited.Arguments[1], visited.Method); + + case "op_ExclusiveOr": + return Expression.ExclusiveOr(visited.Arguments[0], visited.Arguments[1], visited.Method); + + default: + break; + } + } + + // handle unary operator overloads + if (visited.Arguments.Count == 1) + { + // CODE(C#): +x + // ORIGINAL: MethodCallExpression(, x) + // NORMALIZED: UnaryPlus(x) + switch (visited.Method.Name) + { + case "op_UnaryNegation": + return Expression.Negate(visited.Arguments[0], visited.Method); + + case "op_UnaryPlus": + return Expression.UnaryPlus(visited.Arguments[0], visited.Method); + + case "op_Explicit": + case "op_Implicit": + return Expression.Convert(visited.Arguments[0], visited.Type, visited.Method); + + case "op_OnesComplement": + case "op_False": + return Expression.Not(visited.Arguments[0], visited.Method); + + default: + break; + } + } + } + + // check for static Equals method + if (visited.Method.IsStatic && visited.Method.Name == "Equals" && visited.Arguments.Count > 1) + { + // CODE(C#): Object.Equals(x, y) + // ORIGINAL: MethodCallExpression(, x, y) + // NORMALIZED: Equal(x, y) + return Expression.Equal(visited.Arguments[0], visited.Arguments[1], false, visited.Method); + } + + // check for instance Equals method + if (!visited.Method.IsStatic && visited.Method.Name == "Equals" && visited.Arguments.Count > 0) + { + // CODE(C#): x.Equals(y) + // ORIGINAL: MethodCallExpression(x, , y) + // NORMALIZED: Equal(x, y) + return CreateRelationalOperator(ExpressionType.Equal, visited.Object, visited.Arguments[0]); + } + + // check for Microsoft.VisualBasic.CompilerServices.Operators.CompareString method + if (visited.Method.IsStatic && visited.Method.Name == "CompareString" && visited.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators") + { + // CODE(VB): x = y; where x and y are strings, a part of the expression looks like: + // ORIGINAL: MethodCallExpression(Microsoft.VisualBasic.CompilerServices.Operators.CompareString(x, y, False) + // NORMALIZED: see CreateCompareExpression method + return CreateCompareExpression(visited.Arguments[0], visited.Arguments[1]); + } + + // check for instance CompareTo method + if (!visited.Method.IsStatic && visited.Method.Name == "CompareTo" && visited.Arguments.Count == 1 && visited.Method.ReturnType == typeof(int)) + { + // CODE(C#): x.CompareTo(y) + // ORIGINAL: MethodCallExpression(x.CompareTo(y)) + // NORMALIZED: see CreateCompareExpression method + return CreateCompareExpression(visited.Object, visited.Arguments[0]); + } + + // check for static Compare method + if (visited.Method.IsStatic && visited.Method.Name == "Compare" && visited.Arguments.Count > 1 && visited.Method.ReturnType == typeof(int)) + { + // CODE(C#): Class.Compare(x, y) + // ORIGINAL: MethodCallExpression(, x, y) + // NORMALIZED: see CreateCompareExpression method + return CreateCompareExpression(visited.Arguments[0], visited.Arguments[1]); + } + + MethodCallExpression normalizedResult; + // check for coalesce operators added by the VB compiler to predicate arguments + normalizedResult = NormalizePredicateArgument(visited); + // check for type conversions in a Select that can be converted to Cast + normalizedResult = NormalizeSelectWithTypeCast(normalizedResult); + // check for type conversion for Any/All/OfType source + normalizedResult = NormalizeEnumerableSource(normalizedResult); + + return normalizedResult; + } + + /// + /// Remove extra Converts from the source of Any/All/OfType methods + /// + private static MethodCallExpression NormalizeEnumerableSource(MethodCallExpression callExpression) + { + SequenceMethod sequenceMethod; + MethodInfo method = callExpression.Method; + if (ReflectionUtil.TryIdentifySequenceMethod(callExpression.Method, out sequenceMethod) && + (ReflectionUtil.IsAnyAllMethod(sequenceMethod) || sequenceMethod == SequenceMethod.OfType)) + { + Expression source = callExpression.Arguments[0]; + + //strip converts + while (ExpressionType.Convert == source.NodeType) + { + source = ((UnaryExpression)source).Operand; + } + + if (source != callExpression.Arguments[0]) + { + if (sequenceMethod == SequenceMethod.Any || sequenceMethod == SequenceMethod.OfType) + { + // source.Any() or source.OfType + return Expression.Call(method, source); + } + else + { + //source.Any(predicate) or source.All(predicate) + return Expression.Call(method, source, callExpression.Arguments[1]); + } + } + } + + return callExpression; + } + + /// + /// Identifies and normalizes any predicate argument in the given call expression. If no changes + /// are needed, returns the existing expression. Otherwise, returns a new call expression + /// with a normalized predicate argument. + /// + private static MethodCallExpression NormalizePredicateArgument(MethodCallExpression callExpression) + { + MethodCallExpression result; + + int argumentOrdinal; + Expression normalizedArgument; + if (HasPredicateArgument(callExpression, out argumentOrdinal) && + TryMatchCoalescePattern(callExpression.Arguments[argumentOrdinal], out normalizedArgument)) + { + List normalizedArguments = new List(callExpression.Arguments); + + // replace the predicate argument with the normalized version + normalizedArguments[argumentOrdinal] = normalizedArgument; + + result = Expression.Call(callExpression.Object, callExpression.Method, normalizedArguments); + } + else + { + // nothing has changed + result = callExpression; + } + + return result; + } + + /// + /// Determines whether the given call expression has a 'predicate' argument (e.g. Where(source, predicate)) + /// and returns the ordinal for the predicate. + /// + /// + /// Obviously this method will need to be replaced if we ever encounter a method with multiple predicates. + /// + private static bool HasPredicateArgument(MethodCallExpression callExpression, out int argumentOrdinal) + { + argumentOrdinal = default(int); + bool result = false; + + // It turns out all supported methods taking a predicate argument have it as the second + // argument. As a result, we always set argumentOrdinal to 1 when there is a match and + // we can safely ignore all methods taking fewer than 2 arguments + SequenceMethod sequenceMethod; + if (2 <= callExpression.Arguments.Count && + ReflectionUtil.TryIdentifySequenceMethod(callExpression.Method, out sequenceMethod)) + { + switch (sequenceMethod) + { + case SequenceMethod.FirstPredicate: + case SequenceMethod.FirstOrDefaultPredicate: + case SequenceMethod.SinglePredicate: + case SequenceMethod.SingleOrDefaultPredicate: + case SequenceMethod.LastPredicate: + case SequenceMethod.LastOrDefaultPredicate: + case SequenceMethod.Where: + case SequenceMethod.WhereOrdinal: + case SequenceMethod.CountPredicate: + case SequenceMethod.LongCountPredicate: + case SequenceMethod.AnyPredicate: + case SequenceMethod.All: + case SequenceMethod.SkipWhile: + case SequenceMethod.SkipWhileOrdinal: + case SequenceMethod.TakeWhile: + case SequenceMethod.TakeWhileOrdinal: + argumentOrdinal = 1; // the second argument is always the one + result = true; + break; + } + } + + return result; + } + + /// + /// Determines whether the given expression of the form Lambda(Coalesce(left, Constant(false)), ...), a pattern + /// introduced by the VB compiler for predicate arguments. Returns the 'normalized' version of the expression + /// Lambda((bool)left, ...) + /// + private static bool TryMatchCoalescePattern(Expression expression, out Expression normalized) + { + normalized = null; + bool result = false; + + if (expression.NodeType == ExpressionType.Quote) + { + // try to normalize the quoted expression + UnaryExpression quote = (UnaryExpression)expression; + if (TryMatchCoalescePattern(quote.Operand, out normalized)) + { + result = true; + normalized = Expression.Quote(normalized); + } + } + else if (expression.NodeType == ExpressionType.Lambda) + { + LambdaExpression lambda = (LambdaExpression)expression; + + // collapse coalesce lambda expressions + // CODE(VB): where a.NullableInt = 1 + // ORIGINAL: Lambda(Coalesce(expr, Constant(false)), a) + // NORMALIZED: Lambda(expr, a) + if (lambda.Body.NodeType == ExpressionType.Coalesce && lambda.Body.Type == typeof(bool)) + { + BinaryExpression coalesce = (BinaryExpression)lambda.Body; + if (coalesce.Right.NodeType == ExpressionType.Constant && false.Equals(((ConstantExpression)coalesce.Right).Value)) + { + normalized = Expression.Lambda(lambda.Type, Expression.Convert(coalesce.Left, typeof(bool)), lambda.Parameters); + result = true; + } + } + } + + return result; + } + + /// + /// Identifies and normalizes a Select method call expression of the following form: + /// Select(x => Convert(x)) + /// If a match is found, it is translated into a Cast() call. + /// + /// This supports type casting in queries like the following: + /// from DerivedType entity in context.Entities + /// select entity + /// Where DerivedType is derived from the type of context.Entities. + /// The pattern also applies to SelectMany calls with the same structure as above. + /// + /// In C#, the type cast above is represented as a Cast call and the ResourceBinder knows how to handle that. + /// In VB, the same query is translated into Select(x => Convert(x)) instead of Cast, and the ResourceBinder + /// doesn't recognize that pattern. This normalization allows the two queries to be treated the same. + /// + /// MethodCallExpression to potentially normalize. + /// + /// If the query pattern was found, a Cast call is returned with the same source as the original Select and + /// a cast type that is the same as the original Convert expression. + /// If no normalization is required, the original MethodCallExpression is returned without changes. + /// + private static MethodCallExpression NormalizeSelectWithTypeCast(MethodCallExpression callExpression) + { + Type convertType; + if (TryMatchSelectWithConvert(callExpression, out convertType)) + { + // Find the Cast method on the same type where the Select method was declared + MethodInfo castMethodInfo = callExpression.Method.DeclaringType.GetMethod("Cast", true /*isPublic*/, true /*isStatic*/); + if (castMethodInfo != null && castMethodInfo.IsGenericMethodDefinition && ReflectionUtil.IsSequenceMethod(castMethodInfo, SequenceMethod.Cast)) + { + MethodInfo genericCastMethodInfo = castMethodInfo.MakeGenericMethod(convertType); + return Expression.Call(genericCastMethodInfo, callExpression.Arguments[0]); + } + } + + // nothing has changed + return callExpression; + } + + /// + /// Looks for a method call expression of the form + /// Select(entity => Convert(entity, DerivedType)) + /// If found, returns DerivedType. + /// + /// Expression to check for pattern match. + /// If the match was found, this is the type used in the Convert, otherwise null. + /// True if the expression matches the desired pattern, otherwise false. + private static bool TryMatchSelectWithConvert(MethodCallExpression callExpression, out Type convertType) + { + convertType = null; + return ReflectionUtil.IsSequenceMethod(callExpression.Method, SequenceMethod.Select) && + TryMatchConvertSingleArgument(callExpression.Arguments[1], out convertType); + } + + /// + /// Looks for a lambda expression of the form + /// related => Convert(related, DerivedType) + /// Returns DerivedType if a match was found. + /// + /// Expression to check for pattern match. + /// + /// If the matches the pattern, this is the type of the found Convert call, otherwise null. + /// + /// True if the expression matches the desired pattern, otherwise false. + private static bool TryMatchConvertSingleArgument(Expression expression, out Type convertType) + { + convertType = null; + + expression = expression.NodeType == ExpressionType.Quote ? ((UnaryExpression)expression).Operand : expression; + + if (expression.NodeType == ExpressionType.Lambda) + { + LambdaExpression lambda = (LambdaExpression)expression; + if (lambda.Parameters.Count == 1 && lambda.Body.NodeType == ExpressionType.Convert) + { + UnaryExpression convertExpression = (UnaryExpression)lambda.Body; + // Make sure the parameter being converted is the single lambda parameter + if (convertExpression.Operand == lambda.Parameters[0]) + { + convertType = convertExpression.Type; + return true; + } + } + } + + return false; + } + + private static readonly MethodInfo s_relationalOperatorPlaceholderMethod = typeof(ExpressionNormalizer).GetMethod("RelationalOperatorPlaceholder", false /*isPublic*/, true /*isStatic*/); + + /// + /// This method exists solely to support creation of valid relational operator LINQ expressions that are not natively supported + /// by the CLR (e.g. String > String). This method must not be invoked. + /// + private static bool RelationalOperatorPlaceholder(TLeft left, TRight right) + { + Debug.Assert(false, "This method should never be called. It exists merely to support creation of relational LINQ expressions."); + return object.ReferenceEquals(left, right); + } + + /// + /// Create an operator relating 'left' and 'right' given a relational operator. + /// + private static BinaryExpression CreateRelationalOperator(ExpressionType op, Expression left, Expression right) + { + BinaryExpression result; + if (!TryCreateRelationalOperator(op, left, right, out result)) + { + Debug.Assert(false, "CreateRelationalOperator has unknown op " + op); + } + + return result; + } + + /// + /// Try to create an operator relating 'left' and 'right' using the given operator. If the given operator + /// does not define a known relation, returns false. + /// + private static bool TryCreateRelationalOperator(ExpressionType op, Expression left, Expression right, out BinaryExpression result) + { + MethodInfo relationalOperatorPlaceholderMethod = s_relationalOperatorPlaceholderMethod.MakeGenericMethod(left.Type, right.Type); + + switch (op) + { + case ExpressionType.Equal: + result = Expression.Equal(left, right, LiftToNull, relationalOperatorPlaceholderMethod); + return true; + + case ExpressionType.NotEqual: + result = Expression.NotEqual(left, right, LiftToNull, relationalOperatorPlaceholderMethod); + return true; + + case ExpressionType.LessThan: + result = Expression.LessThan(left, right, LiftToNull, relationalOperatorPlaceholderMethod); + return true; + + case ExpressionType.LessThanOrEqual: + result = Expression.LessThanOrEqual(left, right, LiftToNull, relationalOperatorPlaceholderMethod); + return true; + + case ExpressionType.GreaterThan: + result = Expression.GreaterThan(left, right, LiftToNull, relationalOperatorPlaceholderMethod); + return true; + + case ExpressionType.GreaterThanOrEqual: + result = Expression.GreaterThanOrEqual(left, right, LiftToNull, relationalOperatorPlaceholderMethod); + return true; + + default: + result = null; + return false; + } + } + + /// + /// CODE(C#): Class.Compare(left, right) + /// ORIGINAL: MethodCallExpression(Compare, left, right) + /// NORMALIZED: Condition(Equal(left, right), 0, Condition(left > right, 1, -1)) + /// + /// Why is this an improvement? We know how to evaluate Condition in the store, but we don't + /// know how to evaluate MethodCallExpression... Where the CompareTo appears within a larger expression, + /// e.g. left.CompareTo(right) > 0, we can further simplify to left > right (we register the "ComparePattern" + /// to make this possible). + /// + private Expression CreateCompareExpression(Expression left, Expression right) + { + Expression result = Expression.Condition( + CreateRelationalOperator(ExpressionType.Equal, left, right), + Expression.Constant(0), + Expression.Condition( + CreateRelationalOperator(ExpressionType.GreaterThan, left, right), + Expression.Constant(1), + Expression.Constant(-1))); + + // Remember that this node matches the pattern + _patterns[result] = new ComparePattern(left, right); + + return result; + } + + /// Records a rewritten expression as necessary. + /// Original source expression. + /// Rewritten expression. + /// + /// IMPORTANT: if there are higher-level rewrites such as replacing parameter + /// references, the lower-level rewrites will become un-doable in other + /// contexts; we will have to change normalization/de-normalization strategy, + /// record additional mapping information and/or bubble up the rewrite + /// tracking. + /// + private void RecordRewrite(Expression source, Expression rewritten) + { + Debug.Assert(source != null, "source != null"); + Debug.Assert(rewritten != null, "rewritten != null"); + Debug.Assert(this.NormalizerRewrites != null, "this.NormalizerRewrites != null"); + + if (source != rewritten) + { + this.NormalizerRewrites.Add(rewritten, source); + } + } + + #region Inner types + + /// + /// Encapsulates an expression matching some pattern. + /// + private abstract class Pattern + { + /// + /// Gets pattern kind. + /// + internal abstract PatternKind Kind { get; } + } + + /// + /// Gets pattern kind. + /// + private enum PatternKind + { + Compare, + } + + /// + /// Matches expression of the form x.CompareTo(y) or Class.CompareTo(x, y) + /// + private sealed class ComparePattern : Pattern + { + internal ComparePattern(Expression left, Expression right) + { + this.Left = left; + this.Right = right; + } + + /// + /// Gets left-hand argument to Compare operation. + /// + internal readonly Expression Left; + + /// + /// Gets right-hand argument to Compare operation. + /// + internal readonly Expression Right; + + + internal override PatternKind Kind + { + get { return PatternKind.Compare; } + } + } + + #endregion Inner types + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionWriter.cs new file mode 100644 index 0000000..0aea68b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ExpressionWriter.cs @@ -0,0 +1,954 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Text; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Special visitor to serialize supported expression as query parameters + /// in the generated URI. + /// + internal class ExpressionWriter : DataServiceALinqExpressionVisitor + { + #region Private fields + + /// Internal buffer. + private readonly StringBuilder builder; + + /// Data context used to generate type names for types. + private readonly DataServiceContext context; + + /// Stack of expressions being visited. + private readonly Stack expressionStack; + + /// Whether or not the expression being written is part of the path of the URI. + private readonly bool inPath; + + /// set if can't translate expression + private bool cantTranslateExpression; + + /// Parent expression of the current expression (expression.Peek()); possibly null. + private Expression parent; + + /// the request data service version for the uri + private Version uriVersion; + + /// number of sub scopes (any/all calls) on stack + private int scopeCount; + + /// + /// currently writing functions in query options + /// + private bool writingFunctionsInQuery; + + #endregion Private fields + + /// + /// Creates an ExpressionWriter for the specified . + /// + /// Data context used to generate type names for types. + /// Whether or not the expression being written is part of the path of the URI. + private ExpressionWriter(DataServiceContext context, bool inPath) + { + Debug.Assert(context != null, "context != null"); + this.context = context; + this.inPath = inPath; + this.builder = new StringBuilder(); + this.expressionStack = new Stack(); + this.expressionStack.Push(null); + this.uriVersion = Util.ODataVersion4; + this.scopeCount = 0; + this.writingFunctionsInQuery = false; + } + + /// + /// An enumeration indicating the direction of a child operand + /// + private enum ChildDirection + { + /// The operand is the left child + Left, + + /// The operand is the right child + Right + } + + /// Whether inside any/all lambda or not + private bool InSubScope + { + get + { + return this.scopeCount > 0; + } + } + + /// + /// Serializes an expression to a string + /// + /// Data context used to generate type names for types. + /// Expression to serialize + /// Whether or not the expression being written is part of the path of the URI. + /// the request data service version for the uri + /// serialized expression + internal static string ExpressionToString(DataServiceContext context, Expression e, bool inPath, ref Version uriVersion) + { + ExpressionWriter ew = new ExpressionWriter(context, inPath); + string serialized = ew.Translate(e); + WebUtil.RaiseVersion(ref uriVersion, ew.uriVersion); + if (ew.cantTranslateExpression) + { + throw new NotSupportedException(Strings.ALinq_CantTranslateExpression(e.ToString())); + } + + return serialized; + } + + /// Main visit method. + /// Expression to visit + /// Visited expression + internal override Expression Visit(Expression exp) + { + this.parent = this.expressionStack.Peek(); + this.expressionStack.Push(exp); + Expression result = base.Visit(exp); + this.expressionStack.Pop(); + return result; + } + + /// + /// ConditionalExpression visit method + /// + /// The ConditionalExpression expression to visit + /// The visited ConditionalExpression expression + internal override Expression VisitConditional(ConditionalExpression c) + { + this.cantTranslateExpression = true; + return c; + } + + /// + /// LambdaExpression visit method + /// + /// The LambdaExpression to visit + /// The visited LambdaExpression + internal override Expression VisitLambda(LambdaExpression lambda) + { + this.cantTranslateExpression = true; + return lambda; + } + + /// + /// NewExpression visit method + /// + /// The NewExpression to visit + /// The visited NewExpression + internal override NewExpression VisitNew(NewExpression nex) + { + this.cantTranslateExpression = true; + return nex; + } + + /// + /// MemberInitExpression visit method + /// + /// The MemberInitExpression to visit + /// The visited MemberInitExpression + internal override Expression VisitMemberInit(MemberInitExpression init) + { + this.cantTranslateExpression = true; + return init; + } + + /// + /// ListInitExpression visit method + /// + /// The ListInitExpression to visit + /// The visited ListInitExpression + internal override Expression VisitListInit(ListInitExpression init) + { + this.cantTranslateExpression = true; + return init; + } + + /// + /// NewArrayExpression visit method + /// + /// The NewArrayExpression to visit + /// The visited NewArrayExpression + internal override Expression VisitNewArray(NewArrayExpression na) + { + this.cantTranslateExpression = true; + return na; + } + + /// + /// InvocationExpression visit method + /// + /// The InvocationExpression to visit + /// The visited InvocationExpression + internal override Expression VisitInvocation(InvocationExpression iv) + { + this.cantTranslateExpression = true; + return iv; + } + + /// + /// Input resource set references are intentionally omitted from the URL string for the top level + /// refences to input parameter (i.e. outside of any/all methods). + /// For parameter references to input (range variable for Where) inside any/all methods we write "$it". + /// + /// The input reference + /// The same input reference expression + internal override Expression VisitInputReferenceExpression(InputReferenceExpression ire) + { + // This method intentionally does not write anything to the URI for implicit references to the input parameter ($it). + // This is how 'Where(.Id == 5)' becomes '$filter=Id eq 5'. + Debug.Assert(ire != null, "ire != null"); + if (this.parent == null || (!this.InSubScope && this.parent.NodeType != ExpressionType.MemberAccess && this.parent.NodeType != ExpressionType.TypeAs && this.parent.NodeType != ExpressionType.Call)) + { + // Ideally we refer to the parent expression as the un-translatable one, + // because we cannot reference 'this' as a standalone expression; however + // if the parent is null for any reasonn, we fall back to the expression itself. + string expressionText = (this.parent != null) ? this.parent.ToString() : ire.ToString(); + throw new NotSupportedException(Strings.ALinq_CantTranslateExpression(expressionText)); + } + + // Write "$it" for input parameter reference inside any/all methods + if (this.InSubScope || this.parent.NodeType == ExpressionType.Call) + { + this.builder.Append(XmlConstants.ImplicitFilterParameter); + } + + return ire; + } + + /// + /// MethodCallExpression visit method + /// + /// The MethodCallExpression expression to visit + /// The visited MethodCallExpression expression + internal override Expression VisitMethodCall(MethodCallExpression m) + { + string methodName; + if (TypeSystem.TryGetQueryOptionMethod(m.Method, out methodName)) + { + this.builder.Append(methodName); + this.builder.Append(UriHelper.LEFTPAREN); + + // There is a single function, 'contains', which reorders its argument with + // respect to the CLR method. Thus handling it as a special case rather than + // using a more general argument reordering mechanism. + if (methodName == "contains") + { + Debug.Assert(m.Method.Name == "Contains", "m.Method.Name == 'Contains'"); + Debug.Assert(m.Object != null, "m.Object != null"); + Debug.Assert(m.Arguments.Count == 1, "m.Arguments.Count == 1"); + this.Visit(m.Object); + this.builder.Append(UriHelper.COMMA); + this.Visit(m.Arguments[0]); + } + else + { + if (m.Object != null) + { + this.Visit(m.Object); + } + + if (m.Arguments.Count > 0) + { + if (m.Object != null) + { + this.builder.Append(UriHelper.COMMA); + } + + for (int ii = 0; ii < m.Arguments.Count; ii++) + { + this.Visit(m.Arguments[ii]); + if (ii < m.Arguments.Count - 1) + { + this.builder.Append(UriHelper.COMMA); + } + } + } + } + + this.builder.Append(UriHelper.RIGHTPAREN); + } + else if (m.Method.Name == "HasFlag") + { + Debug.Assert(m.Method.Name == "HasFlag", "m.Method.Name == 'HasFlag'"); + Debug.Assert(m.Object != null, "m.Object != null"); + Debug.Assert(m.Arguments.Count == 1, "m.Arguments.Count == 1"); + this.Visit(m.Object); + this.builder.Append(UriHelper.SPACE); + this.builder.Append(UriHelper.HAS); + this.builder.Append(UriHelper.SPACE); + this.Visit(m.Arguments[0]); + } + else + { + SequenceMethod sequenceMethod; + if (ReflectionUtil.TryIdentifySequenceMethod(m.Method, out sequenceMethod)) + { + if (ReflectionUtil.IsAnyAllMethod(sequenceMethod)) + { + // Raise the uriVersion each time we write any or all methods to the uri. + WebUtil.RaiseVersion(ref this.uriVersion, Util.ODataVersion4); + + this.Visit(m.Arguments[0]); + this.builder.Append(UriHelper.FORWARDSLASH); + if (sequenceMethod == SequenceMethod.All) + { + this.builder.Append(XmlConstants.AllMethodName); + } + else + { + this.builder.Append(XmlConstants.AnyMethodName); + } + + this.builder.Append(UriHelper.LEFTPAREN); + if (sequenceMethod != SequenceMethod.Any) + { + // SequenceMethod.Any represents Enumerable.Any(), which has only source argument + // AnyPredicate and All has a second parameter which is the predicate lambda. + Debug.Assert(m.Arguments.Count() == 2, "m.Arguments.Count() == 2"); + LambdaExpression le = (LambdaExpression)m.Arguments[1]; + string rangeVariable = le.Parameters[0].Name; + this.builder.Append(rangeVariable); + this.builder.Append(UriHelper.COLON); + this.scopeCount++; + this.Visit(le.Body); + this.scopeCount--; + } + + this.builder.Append(UriHelper.RIGHTPAREN); + return m; + } + else if (sequenceMethod == SequenceMethod.OfType && this.parent != null) + { + // check to see if this is an OfType filter for Any or All. + // e.g. ctx.CreateQuery("Movies").Where(m=>m.Actors.OfType().Any()) + // which translates to /Movies()?$filter=Actors/MegaStar/any() + MethodCallExpression mce = this.parent as MethodCallExpression; + if (mce != null && ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod) && ReflectionUtil.IsAnyAllMethod(sequenceMethod)) + { + Type filteredType = mce.Method.GetGenericArguments().SingleOrDefault(); + if (ClientTypeUtil.TypeOrElementTypeIsEntity(filteredType)) + { + this.Visit(m.Arguments[0]); + this.builder.Append(UriHelper.FORWARDSLASH); + + UriHelper.AppendTypeSegment(this.builder, filteredType, this.context, this.inPath, ref this.uriVersion); + + return m; + } + } + } + else if (sequenceMethod == SequenceMethod.Count && this.parent != null) + { + if (m.Arguments.Any() && m.Arguments[0] != null) + { + this.Visit(m.Arguments[0]); + } + + this.builder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); + return m; + } + } + else + { + if (m.Object != null) + { + this.Visit(m.Object); + } + + if (m.Method.Name != "GetValue" && m.Method.Name != "GetValueAsync") + { + this.builder.Append(UriHelper.FORWARDSLASH); + + // writing functions in query options + writingFunctionsInQuery = true; + string declaringType = this.context.ResolveNameFromTypeInternal(m.Method.DeclaringType); + if (string.IsNullOrEmpty(declaringType)) + { + throw new NotSupportedException(Strings.ALinq_CantTranslateExpression(m.ToString())); + } + + int index = declaringType.LastIndexOf('.'); + string fullNamespace = declaringType.Remove(index + 1); + string serverMethodName = ClientTypeUtil.GetServerDefinedName(m.Method); + this.builder.Append(fullNamespace + serverMethodName); + this.builder.Append(UriHelper.LEFTPAREN); + string[] argumentNames = m.Method.GetParameters().Select(p => p.Name).ToArray(); + for (int i = 0; i < m.Arguments.Count; ++i) + { + this.builder.Append(argumentNames[i]); + this.builder.Append(UriHelper.EQUALSSIGN); + this.scopeCount++; + this.Visit(m.Arguments[i]); + this.scopeCount--; + this.builder.Append(UriHelper.COMMA); + } + + if (m.Arguments.Any()) + { + this.builder.Remove(this.builder.Length - 1, 1); + } + + this.builder.Append(UriHelper.RIGHTPAREN); + writingFunctionsInQuery = false; + } + + return m; + } + + this.cantTranslateExpression = true; + } + + return m; + } + + /// + /// Serializes an MemberExpression to a string + /// + /// Expression to serialize + /// MemberExpression + internal override Expression VisitMemberAccess(MemberExpression m) + { + if (m.Member is FieldInfo) + { + throw new NotSupportedException(Strings.ALinq_CantReferToPublicField(m.Member.Name)); + } + + Expression e = this.Visit(m.Expression); + + // if this is a Nullable instance, don't write out /Value since not supported by server + if (m.Member.Name == "Value" && m.Member.DeclaringType.IsGenericType() + && m.Member.DeclaringType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return m; + } + + // if this is a GetValueAsync().Result call in async scenario, don't write out /Result + MethodCallExpression methodCallExpression = m.Expression as MethodCallExpression; + if (methodCallExpression != null && methodCallExpression.Method.Name == "GetValueAsync" && m.Member.Name == "Result") + { + return m; + } + + if (!this.IsImplicitInputReference(e) || writingFunctionsInQuery) + { + this.builder.Append(UriHelper.FORWARDSLASH); + } + + // If the member is collection with count property, it will be parsed as $count segment + var parentType = m.Member.DeclaringType; + Type collectionType = ClientTypeUtil.GetImplementationType(parentType, typeof(ICollection<>)); + if (!PrimitiveType.IsKnownNullableType(parentType) && collectionType != null && + m.Member.Name.Equals(ReflectionUtil.COUNTPROPNAME)) + { + this.builder.Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); + } + else + { + this.builder.Append(ClientTypeUtil.GetServerDefinedName(m.Member)); + } + + return m; + } + + /// + /// ConstantExpression visit method + /// + /// The ConstantExpression expression to visit + /// The visited ConstantExpression expression + internal override Expression VisitConstant(ConstantExpression c) + { + if (c.Value == null) + { + this.builder.Append(UriHelper.NULL); + return c; + } + + // DEVNOTE: + // Rather than forcing every other codepath to have the 'Try...' pattern for formatting, + // we catch the InvalidOperationException here to change the exception type. + // This is exceedingly rare, and not a scenario where performance is meaningful, so the + // reduced complexity in all other call sites is worth the extra logic here. + string result; + BinaryExpression b = this.parent as BinaryExpression; + MethodCallExpression m = this.parent as MethodCallExpression; + if ((b != null && HasEnumInBinaryExpression(b)) || (m != null && m.Method.Name == "HasFlag")) + { + c = this.ConvertConstantExpressionForEnum(c); + ClientEdmModel model = this.context.Model; + IEdmType edmType = model.GetOrCreateEdmType(c.Type.IsEnum() ? c.Type : c.Type.GetGenericArguments()[0]); + ClientTypeAnnotation typeAnnotation = model.GetClientTypeAnnotation(edmType); + string typeNameInEdm = this.context.ResolveNameFromTypeInternal(typeAnnotation.ElementType); + MemberInfo member = typeAnnotation.ElementType.GetField(c.Value.ToString()); + string memberValue = ClientTypeUtil.GetServerDefinedName(member); + ODataEnumValue enumValue = new ODataEnumValue(memberValue, typeNameInEdm ?? typeAnnotation.ElementTypeName); + result = ODataUriUtils.ConvertToUriLiteral(enumValue, CommonUtil.ConvertToODataVersion(this.uriVersion), null); + } + else + { + try + { + result = LiteralFormatter.ForConstants.Format(c.Value); + } + catch (InvalidOperationException) + { + if (this.cantTranslateExpression) + { + // there's already a problem in the parents. + // we should just return here, because caller somewhere up the stack will throw a better exception + return c; + } + + throw new NotSupportedException(Strings.ALinq_CouldNotConvert(c.Value)); + } + } + + Debug.Assert(result != null, "result != null"); + this.builder.Append(result); + return c; + } + + /// + /// Serializes an UnaryExpression to a string + /// + /// Expression to serialize + /// UnaryExpression + internal override Expression VisitUnary(UnaryExpression u) + { + switch (u.NodeType) + { + case ExpressionType.Not: + this.builder.Append(UriHelper.NOT); + this.builder.Append(UriHelper.SPACE); + this.VisitOperand(u.Operand); + break; + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + this.builder.Append(UriHelper.SPACE); + this.builder.Append(UriHelper.NEGATE); + this.VisitOperand(u.Operand); + break; + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + if (u.Type != typeof(object)) + { + if (IsEnumTypeExpression(u)) + { + this.Visit(u.Operand); + } + else + { + this.builder.Append(UriHelper.CAST); + this.builder.Append(UriHelper.LEFTPAREN); + if (!this.IsImplicitInputReference(u.Operand)) + { + this.Visit(u.Operand); + this.builder.Append(UriHelper.COMMA); + } + + this.builder.Append(UriHelper.QUOTE); + this.builder.Append(UriHelper.GetTypeNameForUri(u.Type, this.context)); + this.builder.Append(UriHelper.QUOTE); + this.builder.Append(UriHelper.RIGHTPAREN); + } + } + else + { + if (!this.IsImplicitInputReference(u.Operand)) + { + this.Visit(u.Operand); + } + } + + break; + case ExpressionType.TypeAs: + if (u.Operand.NodeType == ExpressionType.TypeAs) + { + throw new NotSupportedException(Strings.ALinq_CannotUseTypeFiltersMultipleTimes); + } + + this.Visit(u.Operand); + + if (!this.IsImplicitInputReference(u.Operand)) + { + // InputReferenceExpressions aren't emitted, so no leading slash is required + this.builder.Append(UriHelper.FORWARDSLASH); + } + + UriHelper.AppendTypeSegment(this.builder, u.Type, this.context, this.inPath, ref this.uriVersion); + + break; + case ExpressionType.UnaryPlus: + // no-op always ignore. + break; + default: + this.cantTranslateExpression = true; + break; + } + + return u; + } + + /// + /// Serializes an BinaryExpression to a string + /// + /// BinaryExpression to serialize + /// serialized expression + internal override Expression VisitBinary(BinaryExpression b) + { + this.VisitOperand(b.Left, b.NodeType, ChildDirection.Left); + this.builder.Append(UriHelper.SPACE); + switch (b.NodeType) + { + case ExpressionType.AndAlso: + case ExpressionType.And: + this.builder.Append(UriHelper.AND); + break; + case ExpressionType.OrElse: + case ExpressionType.Or: + this.builder.Append(UriHelper.OR); + break; + case ExpressionType.Equal: + this.builder.Append(UriHelper.EQ); + break; + case ExpressionType.NotEqual: + this.builder.Append(UriHelper.NE); + break; + case ExpressionType.LessThan: + this.builder.Append(UriHelper.LT); + break; + case ExpressionType.LessThanOrEqual: + this.builder.Append(UriHelper.LE); + break; + case ExpressionType.GreaterThan: + this.builder.Append(UriHelper.GT); + break; + case ExpressionType.GreaterThanOrEqual: + this.builder.Append(UriHelper.GE); + break; + case ExpressionType.Add: + case ExpressionType.AddChecked: + this.builder.Append(UriHelper.ADD); + break; + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + this.builder.Append(UriHelper.SUB); + break; + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + this.builder.Append(UriHelper.MUL); + break; + case ExpressionType.Divide: + this.builder.Append(UriHelper.DIV); + break; + case ExpressionType.Modulo: + this.builder.Append(UriHelper.MOD); + break; + case ExpressionType.ArrayIndex: + case ExpressionType.Power: + case ExpressionType.Coalesce: + case ExpressionType.ExclusiveOr: + case ExpressionType.LeftShift: + case ExpressionType.RightShift: + default: + this.cantTranslateExpression = true; + break; + } + + this.builder.Append(UriHelper.SPACE); + this.VisitOperand(b.Right, b.NodeType, ChildDirection.Right); + return b; + } + + /// + /// Serializes an TypeBinaryExpression to a string + /// + /// TypeBinaryExpression to serialize + /// serialized expression + internal override Expression VisitTypeIs(TypeBinaryExpression b) + { + this.builder.Append(UriHelper.ISOF); + this.builder.Append(UriHelper.LEFTPAREN); + + if (!this.IsImplicitInputReference(b.Expression)) + { + this.Visit(b.Expression); + this.builder.Append(UriHelper.COMMA); + this.builder.Append(UriHelper.SPACE); + } + + this.builder.Append(UriHelper.QUOTE); + this.builder.Append(UriHelper.GetTypeNameForUri(b.TypeOperand, this.context)); + this.builder.Append(UriHelper.QUOTE); + this.builder.Append(UriHelper.RIGHTPAREN); + + return b; + } + + /// + /// ParameterExpression visit method. + /// + /// The ParameterExpression expression to visit + /// The visited ParameterExpression expression + internal override Expression VisitParameter(ParameterExpression p) + { + if (this.InSubScope) + { + this.builder.Append(p.Name); + } + + return p; + } + + /// + /// Indicates if two expression types are collapsible, e.g., ((a or b) or c) can be collapsed to (a or b or c). + /// + /// The expression type + /// The expression type of the parent expression + /// Indicates if the expression is to the left or the right of the parent expression + /// True if the two expression types are collapsible, false otherwise + private static bool AreExpressionTypesCollapsible(ExpressionType type, ExpressionType parentType, ChildDirection childDirection) + { + int precedence = BinaryPrecedence(type); + int parentPrecedence = BinaryPrecedence(parentType); + + // don't process if operators are not supported + if (precedence >= 0 && parentPrecedence >= 0) + { + if (childDirection == ChildDirection.Left) + { + // Left nodes do not need parentheses if the precedence is equal or higher than the parent, e.g., + // (1 + 2) + 3 => 1 + 2 + 3 + // (1 * 2) + 3 => 1 * 2 + 3 + if (precedence <= parentPrecedence) + { + return true; + } + } + else + { + // Right nodes do not need parentheses if the precedence is higher than the parent + if (precedence < parentPrecedence) + { + return true; + } + } + } + + return false; + } + + /// + /// Returns the precedence of a binary operator for comparison purposes, or -1 if not applicable. + /// + /// The ExpressionType representing the binary operator + /// The precedence of a binary operator for comparison purposes, or -1 if not applicable + private static int BinaryPrecedence(ExpressionType type) + { + switch (type) + { + case ExpressionType.OrElse: + case ExpressionType.Or: + return 4; + case ExpressionType.AndAlso: + case ExpressionType.And: + return 3; + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + return 2; + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + return 1; + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + return 0; + default: + return -1; + } + } + + /// + /// Visits operands for Binary and Unary expressions. + /// Will only output parens if operand is complex expression, + /// this is so don't have unecessary parens in URI. + /// + /// The operand expression to visit + private void VisitOperand(Expression e) + { + this.VisitOperand(e, null, null); + } + + /// + /// Visits operands for Binary and Unary expressions. + /// Will only output parens if operand is complex expression, + /// this is so don't have unecessary parens in URI. + /// + /// The operand expression to visit + /// The node type of the parent expression (if applicable) + /// Indicates if the expression is to the left or the right of the parent expression + private void VisitOperand(Expression e, ExpressionType? parentType, ChildDirection? childDirection) + { + Debug.Assert( + parentType.HasValue == childDirection.HasValue, + "If a parent type is specified, a child direction must also be specified, or both must be unspecified."); + if (e is BinaryExpression) + { + bool requiresParens = !parentType.HasValue || + !AreExpressionTypesCollapsible(e.NodeType, parentType.Value, childDirection.Value); + + if (requiresParens) + { + this.builder.Append(UriHelper.LEFTPAREN); + } + + this.Visit(e); + + if (requiresParens) + { + this.builder.Append(UriHelper.RIGHTPAREN); + } + } + else + { + this.Visit(e); + } + } + + /// + /// Serializes an expression to a string + /// + /// Expression to serialize + /// serialized expression + private string Translate(Expression e) + { + this.Visit(e); + return this.builder.ToString(); + } + + /// + /// The references to parameter for the main predicate (.Where()) is implicit outside any/all methods. + /// + /// The expression to test + /// true if the expression represents a reference to the current (resource set) input and it is not in any/all method; otherwise false. + private bool IsImplicitInputReference(Expression exp) + { + // in subscope (i.e. any/all method), references are explicit. + if (this.InSubScope) + { + return false; + } + + return (exp is InputReferenceExpression || exp is ParameterExpression); + } + + /// + /// Check whether this BinaryExpression has enum type in it + /// + /// The BinaryExpression to check + /// The checked result + private static bool HasEnumInBinaryExpression(BinaryExpression b) + { + return IsEnumTypeExpression(b.Left) || IsEnumTypeExpression(b.Right); + } + + /// + /// Check whether the type of this Expresion is enum + /// + /// The BinaryExpression to check + /// The checked result + private static bool IsEnumTypeExpression(Expression e) + { + UnaryExpression u = e as UnaryExpression; + if (u != null) + { + return u.Operand.Type.IsEnum() || (u.Operand.Type.IsGenericType() && u.Operand.Type.GetGenericArguments()[0].IsEnum()); + } + + return false; + } + + /// + /// Get the expected enum type from UnaryExpression + /// + /// The Expression to get enum type from + /// The extracted enum type + private static Type GetEnumType(Expression e) + { + UnaryExpression u = e as UnaryExpression; + if (u != null) + { + return u.Operand.Type.IsEnum() ? u.Operand.Type : u.Operand.Type.GetGenericArguments()[0]; + } + + Debug.Assert(e.Type.IsEnum() || e.Type.GetGenericArguments()[0].IsEnum(), "e.Type.IsEnum || e.Type.GetGenericArguments()[0].IsEnum"); + return e.Type.IsEnum() ? e.Type : e.Type.GetGenericArguments()[0]; + } + + /// + /// Convert a ConstantExpression into expected enum type + /// + /// The ConstantExpression to convert + /// The converted ConstantExpression + private ConstantExpression ConvertConstantExpressionForEnum(ConstantExpression constant) + { + Type enumType = null; + if (this.parent is BinaryExpression) + { + BinaryExpression b = this.parent as BinaryExpression; + if (constant == b.Left) + { + Debug.Assert(b.Right is UnaryExpression, "another binary operand should be UnaryExpression"); + enumType = GetEnumType(b.Right); + } + else + { + Debug.Assert(b.Left is UnaryExpression, "another binary operand should be UnaryExpression"); + enumType = GetEnumType(b.Left); + } + } + else + { + MethodCallExpression m = this.parent as MethodCallExpression; + if (m != null && m.Method.Name == "HasFlag") + { + enumType = GetEnumType(m.Object); + } + } + + Debug.Assert(enumType != null, "enumType != null"); + return Expression.Constant(Enum.Parse(enumType, constant.Value.ToString(), false)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/FilterQueryOptionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/FilterQueryOptionExpression.cs new file mode 100644 index 0000000..b894ded --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/FilterQueryOptionExpression.cs @@ -0,0 +1,89 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq.Expressions; + + /// + /// An resource specific expression representing a filter query option. + /// + internal class FilterQueryOptionExpression : QueryOptionExpression + { + /// + /// The individual expressions that makes the filter predicate + /// + private readonly List individualExpressions; + + /// + /// Creates a FilterQueryOptionExpression expression + /// + /// the return type of the expression + internal FilterQueryOptionExpression(Type type) + : base(type) + { + this.individualExpressions = new List(); + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.FilterQueryOption; } + } + + /// + /// Gets the list of individual conjucts which are separated by AND for the predicate + /// i.e. if the filter statement is id1=1 and id2="var1" and id3=datetimeoffset'31' + /// then this list will have 3 entries, id1=1, id2="var1" and id3=datetimeoffset'xxxxxxxxx' + /// + internal ReadOnlyCollection PredicateConjuncts + { + get + { + return new ReadOnlyCollection(this.individualExpressions); + } + } + + /// + /// Adds the conjuncts to individualExpressions + /// + /// The predicates. + public void AddPredicateConjuncts(IEnumerable predicates) + { + this.individualExpressions.AddRange(predicates); + } + + /// + /// Gets the query option value. + /// + /// A predicate with all Conjuncts AND'ed + public Expression GetPredicate() + { + Expression combinedPredicate = null; + bool isFirst = true; + + foreach (Expression individual in this.individualExpressions) + { + if (isFirst) + { + combinedPredicate = individual; + isFirst = false; + } + else + { + combinedPredicate = Expression.And(combinedPredicate, individual); + } + } + + return combinedPredicate; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputBinder.cs new file mode 100644 index 0000000..8920b94 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputBinder.cs @@ -0,0 +1,237 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq.Expressions; + using System.Reflection; + + #endregion Namespaces + + /// + /// Replaces references to resources - represented as either ParameterExpressions or one or more + /// MemberExpressions over a ParameterExpression - with an appropriate InputReferenceExpression that + /// indicates which resource is referenced; effective 'binds' the argument expression to the + /// resources that it references. + /// + internal sealed class InputBinder : DataServiceALinqExpressionVisitor + { + #region Private fields + + /// Tracks which resources are referenced by the argument expression + private readonly HashSet referencedInputs = new HashSet(EqualityComparer.Default); + + /// Resource from which valid references must start; if no resource with a transparent scope is present, only direct references to this resource will be rebound + private readonly ResourceExpression input; + + /// The input resource, as a queryable resource (may be null if the input is actually a NavigationPropertySingletonExpression) + private readonly QueryableResourceExpression inputResource; + + /// The ParameterExpression that, if encountered, indicates a reference to the input resource + private readonly ParameterExpression inputParameter; + + #endregion Private fields + + /// + /// Constructs a new InputBinder based on the specified input resources, which is represented by the specified ParameterExpression. + /// + /// The current input resource from which valid references must start + /// The parameter that must be referenced in order to refer to the specified input resources + private InputBinder(ResourceExpression resource, ParameterExpression setReferenceParam) + { + this.input = resource; + this.inputResource = resource as QueryableResourceExpression; + this.inputParameter = setReferenceParam; + } + + /// + /// Replaces Lambda parameter references or transparent scope property accesses over those Lambda + /// parameter references with s to the appropriate corresponding + /// s, based on the 'input' QueryableResourceExpression to which the + /// Lambda is logically applied and any enclosing transparent scope applied to that input resource. + /// + /// The expression to rebind + /// + /// The 'current input' resource - either the root resource or the + /// rightmost resource in the navigation chain. + /// The Lambda parameter that represents a reference to the 'input' + /// A list that will be populated with the resources that were referenced by the rebound expression + /// + /// The rebound version of where MemberExpression/ParameterExpressions that + /// represent resource references have been replaced with appropriate InputReferenceExpressions. + /// + internal static Expression Bind(Expression e, ResourceExpression currentInput, ParameterExpression inputParameter, List referencedInputs) + { + Debug.Assert(e != null, "Expression cannot be null"); + Debug.Assert(currentInput != null, "A current input resource is required"); + Debug.Assert(inputParameter != null, "The input lambda parameter is required"); + Debug.Assert(referencedInputs != null, "The referenced inputs list is required"); + + InputBinder binder = new InputBinder(currentInput, inputParameter); + Expression result = binder.Visit(e); + referencedInputs.AddRange(binder.referencedInputs); + return result; + } + + /// + /// Resolves member accesses that represent transparent scope property accesses to the corresponding resource, + /// iff the input resource is enclosed in a transparent scope and the specified MemberExpression represents + /// such a property access. + /// + /// MemberExpression expression to visit + /// + /// An InputReferenceExpression if the member access represents a transparent scope property + /// access that can be resolved to a resource in the path that produces the input resource; + /// otherwise the same MemberExpression is returned. + /// + internal override Expression VisitMemberAccess(MemberExpression m) + { + // If the current input resource is not enclosed in a transparent scope, then this + // MemberExpression cannot represent a valid transparent scope access based on the input parameter. + if (this.inputResource == null || + !this.inputResource.HasTransparentScope) + { + return base.VisitMemberAccess(m); + } + + ParameterExpression innerParamRef = null; + Stack nestedAccesses = new Stack(); + MemberExpression memberRef = m; + while (memberRef != null && + PlatformHelper.IsProperty(memberRef.Member) && + memberRef.Expression != null) + { + nestedAccesses.Push((PropertyInfo)memberRef.Member); + + if (memberRef.Expression.NodeType == ExpressionType.Parameter) + { + innerParamRef = (ParameterExpression)memberRef.Expression; + } + + memberRef = memberRef.Expression as MemberExpression; + } + + // Only continue if the inner non-MemberExpression is the input reference ParameterExpression and + // at least one property reference is present - otherwise this cannot be a transparent scope access. + if (innerParamRef != this.inputParameter || nestedAccesses.Count == 0) + { + return m; + } + + ResourceExpression target = this.input; + QueryableResourceExpression targetResource = this.inputResource; + bool transparentScopeTraversed = false; + + // Process all the traversals through transparent scopes. + while (nestedAccesses.Count > 0) + { + if (targetResource == null || !targetResource.HasTransparentScope) + { + break; + } + + // Peek the property; pop it once it's consumed + // (it could be a non-transparent-identifier access). + PropertyInfo currentProp = nestedAccesses.Peek(); + + // If this is the accessor for the target, then the member + // refers to the target itself. + if (currentProp.Name.Equals(targetResource.TransparentScope.Accessor, StringComparison.Ordinal)) + { + target = targetResource; + nestedAccesses.Pop(); + transparentScopeTraversed = true; + continue; + } + + // This member could also be one of the in-scope sources of the target. + Expression source; + if (!targetResource.TransparentScope.SourceAccessors.TryGetValue(currentProp.Name, out source)) + { + break; + } + + transparentScopeTraversed = true; + nestedAccesses.Pop(); + Debug.Assert(source != null, "source != null -- otherwise ResourceBinder created an accessor to nowhere"); + InputReferenceExpression sourceReference = source as InputReferenceExpression; + if (sourceReference == null) + { + targetResource = source as QueryableResourceExpression; + if (targetResource == null || !targetResource.HasTransparentScope) + { + target = (ResourceExpression)source; + } + } + else + { + targetResource = sourceReference.Target as QueryableResourceExpression; + target = targetResource; + } + } + + // If no traversals were made, the original expression is OK. + if (!transparentScopeTraversed) + { + return m; + } + + // Process traversals after the transparent scope. + Expression result = this.CreateReference(target); + while (nestedAccesses.Count > 0) + { + result = Expression.Property(result, nestedAccesses.Pop()); + } + + return result; + } + + /// + /// Converts a parameter reference to the input resource into an InputReferenceExpression, + /// iff the parameter reference is to the parameter expression that represents the input resource + /// and the input resource is not enclosed in a transparent scope. + /// + /// The parameter reference expression + /// + /// An InputReferenceExpression if the parameter reference is to the input parameter; + /// otherwise the same parameter reference expression + /// + internal override Expression VisitParameter(ParameterExpression p) + { + // If the input resource is not enclosed in a transparent scope, + // and the parameter reference is a reference to the Lambda parameter + // that represents the input resource, then return an InputReferenceExpression. + if ((this.inputResource == null || !this.inputResource.HasTransparentScope) && + p == this.inputParameter) + { + return this.CreateReference(this.input); + } + else + { + return base.VisitParameter(p); + } + } + + /// + /// Returns an that references the specified resource, + /// and also adds the the resource to the hashset of resources that were referenced by the + /// expression that is being rebound. + /// + /// The resource for which a reference was found + /// An InputReferenceExpression that represents a reference to the specified resource + private Expression CreateReference(ResourceExpression resource) + { + this.referencedInputs.Add(resource); + return resource.CreateReference(); + } + } +} + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputReferenceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputReferenceExpression.cs new file mode 100644 index 0000000..fd7274d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/InputReferenceExpression.cs @@ -0,0 +1,78 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Linq.Expressions; + + /// + /// Represents a reference to a bound resource set in the resource path. + /// The type of the input reference is the element type of the set. + /// + /// + /// Because the type of the input reference is the element type of the set, + /// it can be used to indicate what a range variable ranges over. + /// + /// For example, in input.Select(b => b.id), 'input' is an IQueryable of T, + /// and 'b' is a parameter of type T. 'b' can be rebound as an input reference + /// to 'input' by the InputBinder, which helps in query analysis and + /// translation. + /// + [DebuggerDisplay("InputReferenceExpression -> {Type}")] + internal sealed class InputReferenceExpression : Expression + { + /// The resource or set referred to by this input reference expression + private ResourceExpression target; + + /// + /// Constructs a new input reference expression that refers to the specified resource set + /// + /// The target resource set that the new expression will reference + internal InputReferenceExpression(ResourceExpression target) + { + Debug.Assert(target != null, "Target resource set cannot be null"); + this.target = target; + } + + /// + /// The of the value represented by this . + /// + public override Type Type + { + get { return this.target.ResourceType; } + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.InputReference; } + } + + /// + /// Retrieves the resource set referred to by this input reference expression + /// + internal ResourceExpression Target + { + get { return this.target; } + } + + /// + /// Retargets this input reference to point to the resource set specified by . + /// + /// The that this input reference should use as its target + internal void OverrideTarget(QueryableResourceExpression newTarget) + { + Debug.Assert(newTarget != null, "Resource set cannot be null"); + Debug.Assert(newTarget.ResourceType.Equals(this.Type), "Cannot reference a resource set with a different resource type"); + + this.target = newTarget; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NavigationPropertySingletonExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NavigationPropertySingletonExpression.cs new file mode 100644 index 0000000..4d37348 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NavigationPropertySingletonExpression.cs @@ -0,0 +1,132 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Private fields + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + + #endregion Private fields + + /// Expression for a navigation property into a single entity (eg: Customer.BestFriend). + internal class NavigationPropertySingletonExpression : ResourceExpression + { + #region Private fields + + /// property member name + private readonly Expression memberExpression; + + /// resource type + private readonly Type resourceType; + + #endregion Private fields + + /// + /// Creates a NavigationPropertySingletonExpression expression + /// + /// the return type of the expression + /// the source expression + /// property member name + /// resource type for expression + /// expand paths for resource set + /// count option for the resource set + /// custom query options for resourcse set + /// projection expression + /// target expression type for a TypeAs conversion + /// version of the Uri from the expand and projection paths + internal NavigationPropertySingletonExpression(Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion) + : base(source, type, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion) + { + Debug.Assert(memberExpression != null, "memberExpression != null"); + Debug.Assert(resourceType != null, "resourceType != null"); + + this.memberExpression = memberExpression; + this.resourceType = resourceType; + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.ResourceNavigationPropertySingleton; } + } + + /// + /// Gets the member expression. + /// + internal MemberExpression MemberExpression + { + get + { + return (MemberExpression)this.memberExpression; + } + } + + /// + /// The resource type of the singe instance produced by this singleton navigation. + /// + internal override Type ResourceType + { + get { return this.resourceType; } + } + + /// + /// Singleton navigation properties always produce at most 1 result + /// + internal override bool IsSingleton + { + get { return true; } + } + + /// + /// Does Singleton navigation have query options. + /// + internal override bool HasQueryOptions + { + get + { + return this.ExpandPaths.Count > 0 || + this.CountOption == CountOption.CountQuery || + this.CustomQueryOptions.Count > 0 || + this.Projection != null; + } + } + + /// + /// Whether this is a function invocation. + /// + internal override bool IsOperationInvocation + { + get { return false; } + } + + /// + /// Cast changes the type of the ResourceExpression + /// + /// new type + /// new NavigationPropertySingletonExpression + internal override ResourceExpression CreateCloneWithNewType(Type type) + { + return new NavigationPropertySingletonExpression( + type, + this.source, + this.MemberExpression, + TypeSystem.GetElementType(type), + this.ExpandPaths.ToList(), + this.CountOption, + this.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + this.Projection, + this.ResourceTypeAs, + this.UriVersion); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NewTreeBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NewTreeBuilder.cs new file mode 100644 index 0000000..636dc2a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/NewTreeBuilder.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using Microsoft.OData.Client.ALinq.UriParser; + + /// + /// Construct a new copy of an existing tree + /// + internal class NewTreeBuilder : IPathSegmentTokenVisitor + { + /// + /// Visit a SystemToken + /// + /// The SystemToken to visit + /// Always throws, since a SystemToken is illegal in a select or expand path. + public PathSegmentToken Visit(SystemToken tokenIn) + { + throw new NotSupportedException(Strings.ALinq_IllegalSystemQueryOption(tokenIn.Identifier)); + } + + /// + /// Visit a NonSystemToken + /// + /// The non system token to visit + /// A new copy of the input token. + public PathSegmentToken Visit(NonSystemToken tokenIn) + { + if (tokenIn == null) + { + return null; + } + else + { + PathSegmentToken subToken = tokenIn.NextToken != null ? tokenIn.NextToken.Accept(this) : null; + return new NonSystemToken(tokenIn.Identifier, tokenIn.NamedValues, subToken); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/OrderByQueryOptionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/OrderByQueryOptionExpression.cs new file mode 100644 index 0000000..25b0209 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/OrderByQueryOptionExpression.cs @@ -0,0 +1,78 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + /// + /// An resource specific expression representing an OrderBy query option. + /// + internal class OrderByQueryOptionExpression : QueryOptionExpression + { + /// selectors for OrderBy query option + private List selectors; + + /// + /// Creates an OrderByQueryOptionExpression expression + /// + /// the return type of the expression + /// selectors for orderby expression + internal OrderByQueryOptionExpression(Type type, List selectors) + : base(type) + { + this.selectors = selectors; + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.OrderByQueryOption; } + } + + /// + /// Selectors for OrderBy expression + /// + internal List Selectors + { + get + { + return this.selectors; + } + } + + /// + /// Structure for selectors. Holds lambda expression + flag indicating desc. + /// + internal struct Selector + { + /// + /// lambda expression for selector + /// + internal readonly Expression Expression; + + /// + /// flag indicating if descending + /// + internal readonly bool Descending; + + /// + /// Creates a Selector + /// + /// lambda expression for selector + /// flag indicating if descending + internal Selector(Expression e, bool descending) + { + this.Expression = e; + this.Descending = descending; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ParameterReplacerVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ParameterReplacerVisitor.cs new file mode 100644 index 0000000..1fcc829 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ParameterReplacerVisitor.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#else +namespace Microsoft.OData.Client +#endif +{ + using System.Diagnostics; + using System.Linq.Expressions; + + /// Provides an expression visitor that can replace a . + internal class ParameterReplacerVisitor : ALinqExpressionVisitor + { + /// Expression to replace with. + private readonly Expression newExpression; + + /// Parameter to replace. + private readonly ParameterExpression oldParameter; + + /// Initializes a new instance. + /// Parameter to replace. + /// Expression to replace with. + private ParameterReplacerVisitor(ParameterExpression oldParameter, Expression newExpression) + { + this.oldParameter = oldParameter; + this.newExpression = newExpression; + } + + /// + /// Replaces the occurences of for in + /// . + /// + /// Expression to perform replacement on. + /// Parameter to replace. + /// Expression to replace with. + /// A new expression with the replacement performed. + internal static Expression Replace(Expression expression, ParameterExpression oldParameter, Expression newExpression) + { + Debug.Assert(expression != null, "expression != null"); + Debug.Assert(oldParameter != null, "oldParameter != null"); + Debug.Assert(newExpression != null, "newExpression != null"); + return new ParameterReplacerVisitor(oldParameter, newExpression).Visit(expression); + } + + /// ParameterExpression visit method. + /// The ParameterExpression expression to visit + /// The visited ParameterExpression expression + internal override Expression VisitParameter(ParameterExpression p) + { + return p == this.oldParameter ? this.newExpression : p; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionAnalyzer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionAnalyzer.cs new file mode 100644 index 0000000..e500617 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionAnalyzer.cs @@ -0,0 +1,830 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using Microsoft.OData.Client.Metadata; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + #endregion Namespaces + + /// + /// Analyzes projection expressions to see if supported. + /// To be writable, must follow these rules: + /// 1) Must be known Entity Type + /// 2) Must be a true narrowing of the source type. Subset of properties + no transformations other then casts. + /// + /// To be materializable (read-only), must follow these rules + /// 1) No transient object creation. (Entity and non-Entity types) + /// 2) No referencing of other DataService queries or contexts. + /// + internal static class ProjectionAnalyzer + { + #region Internal methods. + + /// + /// Analyzes a lambda expression to check whether it can be satisfied with + /// $select and client-side materialization. + /// + /// Lambda expression. + /// Resource expression in scope. + /// Whether member accesses are matched as top-level projections. + /// Context of expression to analyze. + /// true if the lambda is a client-side projection; false otherwise. + internal static bool Analyze(LambdaExpression le, ResourceExpression re, bool matchMembers, DataServiceContext context) + { + Debug.Assert(le != null, "le != null"); + + if (le.Body.NodeType == ExpressionType.Constant) + { + if (ClientTypeUtil.TypeOrElementTypeIsEntity(le.Body.Type)) + { + throw new NotSupportedException(Strings.ALinq_CannotCreateConstantEntity); + } + + re.Projection = new ProjectionQueryOptionExpression(le.Body.Type, le, new List()); + return true; + } + + if (le.Body.NodeType == ExpressionType.MemberInit || le.Body.NodeType == ExpressionType.New) + { + AnalyzeResourceExpression(le, re, context); + return true; + } + + if (matchMembers) + { + // Members can be projected standalone or type-casted. + Expression withoutConverts = SkipConverts(le.Body); + if (withoutConverts.NodeType == ExpressionType.MemberAccess) + { + AnalyzeResourceExpression(le, re, context); + return true; + } + } + + return false; + } + + private static void Analyze(LambdaExpression e, SelectExpandPathBuilder pb, DataServiceContext context) + { + bool knownEntityType = ClientTypeUtil.TypeOrElementTypeIsEntity(e.Body.Type); + ParameterExpression pe = e.Parameters.Last(); + bool isEntityParameter = ClientTypeUtil.TypeOrElementTypeIsEntity(pe.Type); + if(isEntityParameter) + { + pb.PushParamExpression(pe); + } + + if (!knownEntityType) + { + NonEntityProjectionAnalyzer.Analyze(e.Body, pb, context); + } + else + { + switch (e.Body.NodeType) + { + case ExpressionType.MemberInit: + EntityProjectionAnalyzer.Analyze((MemberInitExpression)e.Body, pb, context); + break; + case ExpressionType.New: + throw new NotSupportedException(Strings.ALinq_CannotConstructKnownEntityTypes); + case ExpressionType.Constant: + throw new NotSupportedException(Strings.ALinq_CannotCreateConstantEntity); + default: + // ExpressionType.MemberAccess as a top-level expression is correctly + // processed here, as the lambda isn't being member-initialized. + NonEntityProjectionAnalyzer.Analyze(e.Body, pb, context); + break; + } + } + + if (isEntityParameter) + { + pb.PopParamExpression(); + } + } + + /// + /// Checks whether the specified refers + /// to a sequence method call allowed on entity types. + /// + /// Method call expression to check. + /// true if the method call is allowed; false otherwise. + /// The method won't check whether the call is made on actual entity types. + internal static bool IsMethodCallAllowedEntitySequence(MethodCallExpression call) + { + Debug.Assert(call != null, "call != null"); + return + ReflectionUtil.IsSequenceMethod(call.Method, SequenceMethod.ToList) || + ReflectionUtil.IsSequenceMethod(call.Method, SequenceMethod.Select); + } + + /// + /// Checks whether the specified refers + /// to a Select method call that works on the results of another Select call + /// + /// Method call expression to check. + /// Type of the projection + internal static void CheckChainedSequence(MethodCallExpression call, Type type) + { + if (ReflectionUtil.IsSequenceSelectMethod(call.Method)) + { + // Chained Selects are not allowed + // c.Orders.Select(...).Select(...) + MethodCallExpression insideCall = ResourceBinder.StripTo(call.Arguments[0]); + if (insideCall != null && (ReflectionUtil.IsSequenceSelectMethod(insideCall.Method))) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(type, call.ToString())); + } + } + } + + /// + /// Checks whether the specified expression creates a collection. + /// + /// Expression to check. + /// true if given expression is collection producing. + internal static bool IsCollectionProducingExpression(Expression e) + { + if (TypeSystem.FindIEnumerable(e.Type) != null) + { + Type elementType = TypeSystem.GetElementType(e.Type); + Debug.Assert(elementType != null, "elementType == null"); + Type dscType = WebUtil.GetDataServiceCollectionOfT(elementType); + if (typeof(List<>).MakeGenericType(elementType).IsAssignableFrom(e.Type) || + (dscType != null && dscType.IsAssignableFrom(e.Type))) + { + return true; + } + } + + return false; + } + + /// + /// Checks whether the specified expression is allowed in a MethodCall. Expressions that + /// produce collections are not allowed. The only exception is when collection property + /// belongs to an entity e.g. c.Orders.Select(o => o), where we allow c.Orders. + /// + /// Expression to check. + /// The client model used. + /// true if expression is disallowed, false otherwise. + internal static bool IsDisallowedExpressionForMethodCall(Expression e, ClientEdmModel model) + { + // If this is a collection attached to an Entity, then that is fine. + MemberExpression me = e as MemberExpression; + if (me != null && ClientTypeUtil.TypeIsEntity(me.Expression.Type, model)) + { + return false; + } + + // All collection producing expressions are disallowed. + return IsCollectionProducingExpression(e); + } + + #endregion Internal methods. + + #region Private methods. + + /// + /// Analyzes the specified expression with an entity-projection or + /// non-entity-projection analyzer. + /// + /// Expression to analyze. + /// Path box where select and expand paths are tracked. + /// Context of expression to analyze. + private static void Analyze(MemberInitExpression mie, SelectExpandPathBuilder pb, DataServiceContext context) + { + Debug.Assert(mie != null, "mie != null"); + Debug.Assert(pb != null, "pb != null"); + + bool knownEntityType = ClientTypeUtil.TypeOrElementTypeIsEntity(mie.Type); + if (knownEntityType) + { + EntityProjectionAnalyzer.Analyze(mie, pb, context); + } + else + { + NonEntityProjectionAnalyzer.Analyze(mie, pb, context); + } + } + + /// + /// Analyzes the specified for selection and updates + /// . + /// + /// Lambda expression to analyze. + /// Resource expression to update. + /// Context of expression to analyze. + private static void AnalyzeResourceExpression(LambdaExpression lambda, ResourceExpression resource, DataServiceContext context) + { + SelectExpandPathBuilder pb = new SelectExpandPathBuilder(); + ProjectionAnalyzer.Analyze(lambda, pb, context); + resource.Projection = new ProjectionQueryOptionExpression(lambda.Body.Type, lambda, pb.ProjectionPaths.ToList()); + resource.ExpandPaths = pb.ExpandPaths.Union(resource.ExpandPaths, StringComparer.Ordinal).ToList(); + resource.RaiseUriVersion(pb.UriVersion); + } + + /// Skips converts and returns the underlying expression. + /// Expression to dig into. + /// The original expression without converts. + /// + /// IMPORTANT: This is fine for checks on underlying expressions where we + /// want converts to be "mostly" transparent, but using the result in + /// place of the given loses information. + /// + private static Expression SkipConverts(Expression expression) + { + Expression result = expression; + while (result.NodeType == ExpressionType.Convert || result.NodeType == ExpressionType.ConvertChecked) + { + result = ((UnaryExpression)result).Operand; + } + + return result; + } + + #endregion Private methods. + + #region Inner types + + private class EntityProjectionAnalyzer : ALinqExpressionVisitor + { + #region Private fields + + /// Path-tracking object. + private readonly SelectExpandPathBuilder builder; + + /// Type being member-init'ed. + private readonly Type type; + + /// + /// This analyzer iterates through the list of member assignments in the MemberInitExpression + /// and visits each one. This field tracks if the currently visited member assignment is a + /// MemberAccessExpression, used for determining if a TryAs convert should be emitted when visited. + /// + private bool leafExpressionIsMemberAccess; + + /// + /// The associated DataServiceContext instance. DevNote(shank): this is used for determining + /// the fully-qualified name of types when TryAs converts are processed (C# "as", VB "TryCast"). + /// Ideally the FQN is only required during URI translation, not during analysis. However, + /// the current code constructs the $select and $expand parts of the URI during analysis. This + /// could be refactored in the future to defer the $select and $expand URI construction until + /// the URI translation phase. + /// + private readonly DataServiceContext context; + + #endregion Private fields + + /// Initializes a new instance. + /// Path-tracking object. + /// Type being member-init'ed. + /// Context of expression to analyze. + private EntityProjectionAnalyzer(SelectExpandPathBuilder pb, Type type, DataServiceContext context) + { + Debug.Assert(pb != null, "pb != null"); + Debug.Assert(type != null, "type != null"); + + this.builder = pb; + this.type = type; + this.context = context; + } + + /// Analyzes the specified member-init expression. + /// Expression to analyze. + /// Path-tracking object to store analysis in. + /// Context of expression to analyze. + internal static void Analyze(MemberInitExpression mie, SelectExpandPathBuilder pb, DataServiceContext context) + { + Debug.Assert(mie != null, "mie != null"); + + var epa = new EntityProjectionAnalyzer(pb, mie.Type, context); + + MemberAssignmentAnalysis targetEntityPath = null; + foreach (MemberBinding mb in mie.Bindings) + { + MemberAssignment ma = mb as MemberAssignment; + epa.Visit(ma.Expression); + if (ma != null) + { + var analysis = MemberAssignmentAnalysis.Analyze(pb.ParamExpressionInScope, ma.Expression); + if (analysis.IncompatibleAssignmentsException != null) + { + throw analysis.IncompatibleAssignmentsException; + } + + // Note that an "empty" assignment on the binding is not checked/handled, + // because the funcletizer would have turned that into a constant + // in the tree, the visit earlier in this method would have thrown + // an exception at finding a constant in an entity projection. + // + // We do account however for failing to find a reference off the + // parameter entry to detect errors like this: new ET() { Ref = e } + // Here it looks like the new ET should be the parent of 'e', but + // there is nothing in scope that represents that. + // + // This also explains while error messages might be a bit misleading + // in this case (because they reference a constant when the user + // hasn't included any). + Type targetType = ClientTypeUtil.GetMemberType(ma.Member); + Expression[] lastExpressions = analysis.GetExpressionsBeyondTargetEntity(); + if (lastExpressions.Length == 0) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(targetType, ma.Expression)); + } + + MemberExpression lastExpression = lastExpressions[lastExpressions.Length - 1] as MemberExpression; + Debug.Assert( + !analysis.MultiplePathsFound, + "!analysis.MultiplePathsFound -- the initilizer has been visited, and cannot be empty, and expressions that can combine paths should have thrown exception during initializer analysis"); +#if DEBUG + Debug.Assert( + lastExpression != null, + "lastExpression != null -- the initilizer has been visited, and cannot be empty, and the only expressions that are allowed can be formed off the parameter, so this is always correlatd"); +#endif + + analysis.CheckCompatibleAssignments(mie.Type, ref targetEntityPath); + + // For DataServiceStreamLink, the last expression will be a constant expression. Hence we won't be comparing name checks and entity checks for those type of bindings + if (lastExpression != null) + { + if (lastExpression.Member.Name != ma.Member.Name) + { + throw new NotSupportedException(Strings.ALinq_PropertyNamesMustMatchInProjections(lastExpression.Member.Name, ma.Member.Name)); + } + + // Unless we're initializing an entity, we should not traverse into the parameter in scope. + bool targetIsEntity = ClientTypeUtil.TypeOrElementTypeIsEntity(targetType); + bool sourceIsEntity = ClientTypeUtil.TypeOrElementTypeIsEntity(lastExpression.Type); + if (sourceIsEntity && !targetIsEntity) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(targetType, ma.Expression)); + } + } + } + } + } + + internal override Expression VisitUnary(UnaryExpression u) + { + Debug.Assert(u != null, "u != null"); + + // Perfectly assignable conversions are OK. VB.NET compilers + // inserts these to exactly match method signatures, for example. + if (ResourceBinder.PatternRules.MatchConvertToAssignable(u) || (u.NodeType == ExpressionType.TypeAs && this.leafExpressionIsMemberAccess)) + { + return base.VisitUnary(u); + } + + if ((u.NodeType == ExpressionType.Convert) || (u.NodeType == ExpressionType.ConvertChecked)) + { + Type sourceType = Nullable.GetUnderlyingType(u.Operand.Type) ?? u.Operand.Type; + Type targetType = Nullable.GetUnderlyingType(u.Type) ?? u.Type; + + // when projecting known entity types, will allow convert expressions of primitive types. + if (PrimitiveType.IsKnownType(sourceType) && PrimitiveType.IsKnownType(targetType)) + { + return base.Visit(u.Operand); + } + } + + // In V3 while we support TypeAs conversions, we only support TypeAs before a MemberAccess and not TypeAs as the last operation + // i.e. we support "Manager = (p as Employee).Manager" (see VisitMemberAccess for detail), but we dont support "Manager = (p as Manager)" + // Note that the server also doesn't support a property path which ends with a type identifier. + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, u.ToString())); + } + + internal override Expression VisitBinary(BinaryExpression b) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, b.ToString())); + } + + internal override Expression VisitTypeIs(TypeBinaryExpression b) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, b.ToString())); + } + + internal override Expression VisitConditional(ConditionalExpression c) + { + var nullCheck = ResourceBinder.PatternRules.MatchNullCheck(this.builder.ParamExpressionInScope, c); + if (nullCheck.Match) + { + this.Visit(nullCheck.AssignExpression); + return c; + } + + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, c.ToString())); + } + + internal override Expression VisitConstant(ConstantExpression c) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, c.ToString())); + } + + internal override Expression VisitMemberAccess(MemberExpression m) + { + Debug.Assert(m != null, "m != null"); + + this.leafExpressionIsMemberAccess = true; + + // Only allowed to project entities + if (!ClientTypeUtil.TypeOrElementTypeIsEntity(m.Expression.Type) || + IsCollectionProducingExpression(m.Expression)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, m.ToString())); + } + + PropertyInfo pi; + Expression boundTarget; + if (ResourceBinder.PatternRules.MatchNonPrivateReadableProperty(m, out pi, out boundTarget)) + { + Expression e = base.VisitMemberAccess(m); + Type convertedType; + ResourceBinder.StripTo(m.Expression, out convertedType); + this.builder.AppendPropertyToPath(pi, convertedType, this.context); + + this.leafExpressionIsMemberAccess = false; + return e; + } + + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, m.ToString())); + } + + internal override Expression VisitMethodCall(MethodCallExpression m) + { + // We throw NotSupportedException when IsDisallowedExceptionForMethodCall() is true + // or we have a method call on a non-entity type, for example c.MyCollectionComplexProperty.Select(...) + if ((m.Object != null && (IsDisallowedExpressionForMethodCall(m.Object, this.context.Model) || !ClientTypeUtil.TypeOrElementTypeIsEntity(m.Object.Type))) + || m.Arguments.Any(a => IsDisallowedExpressionForMethodCall(a, this.context.Model)) + || (m.Object == null && !ClientTypeUtil.TypeOrElementTypeIsEntity(m.Arguments[0].Type))) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, m.ToString())); + } + + if (ProjectionAnalyzer.IsMethodCallAllowedEntitySequence(m)) + { + CheckChainedSequence(m, this.type); + + // allow selects for following pattern: + // Orders = c.Orders.Select(o=> new NarrowOrder {...}).ToList(); + return base.VisitMethodCall(m); + } + + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, m.ToString())); + } + + internal override Expression VisitInvocation(InvocationExpression iv) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, iv.ToString())); + } + + internal override Expression VisitLambda(LambdaExpression lambda) + { + ProjectionAnalyzer.Analyze(lambda, this.builder, this.context); + return lambda; + } + + internal override Expression VisitListInit(ListInitExpression init) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, init.ToString())); + } + + internal override Expression VisitNewArray(NewArrayExpression na) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, na.ToString())); + } + + internal override Expression VisitMemberInit(MemberInitExpression init) + { + if (!ClientTypeUtil.TypeOrElementTypeIsEntity(init.Type)) + { + // MemberInit to a complex type is not supported on entity types. + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, init.ToString())); + } + + ProjectionAnalyzer.Analyze(init, this.builder, this.context); + return init; + } + + internal override NewExpression VisitNew(NewExpression nex) + { + // Allow creation of DataServiceCollection objects in projections + if (ResourceBinder.PatternRules.MatchNewDataServiceCollectionOfT(nex)) + { + // It doesn't matter if the DSC is being tracked or not, that has no direct effect on the projections + // But it does matter if the T in DSC is an entity or not. In here we only allow entity types to be used + // for creation of DSC. + if (ClientTypeUtil.TypeOrElementTypeIsEntity(nex.Type)) + { + foreach (Expression e in nex.Arguments) + { + // no need to check the constant values here (DataServiceContext, funcs, etc). + if (e.NodeType != ExpressionType.Constant) + { + base.Visit(e); + } + } + + return nex; + } + } + else if (ResourceBinder.PatternRules.MatchNewCollectionOfT(nex)) + { + if (!ClientTypeUtil.TypeOrElementTypeIsEntity(nex.Type)) + { + foreach (Expression e in nex.Arguments) + { + // no need to check the constant values here (DataServiceContext, funcs, etc). + if (e.NodeType != ExpressionType.Constant) + { + base.Visit(e); + } + } + + return nex; + } + } + + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjectionToEntity(this.type, nex.ToString())); + } + + internal override Expression VisitParameter(ParameterExpression p) + { + if (p != this.builder.ParamExpressionInScope) + { + throw new NotSupportedException(Strings.ALinq_CanOnlyProjectTheLeaf); + } + + this.builder.StartNewPath(); + return p; + } + } + + private class NonEntityProjectionAnalyzer : DataServiceALinqExpressionVisitor + { + private SelectExpandPathBuilder builder; + + private Type type; + + /// See corresponding comment in EntityProjectionAnalyzer + private bool leafExpressionIsMemberAccess; + + /// See corresponding comment in EntityProjectionAnalyzer + private readonly DataServiceContext context; + + private NonEntityProjectionAnalyzer(SelectExpandPathBuilder pb, Type type, DataServiceContext context) + { + this.builder = pb; + this.type = type; + this.context = context; + } + + internal static void Analyze(Expression e, SelectExpandPathBuilder pb, DataServiceContext context) + { + var nepa = new NonEntityProjectionAnalyzer(pb, e.Type, context); + + MemberInitExpression mie = e as MemberInitExpression; + + if (mie != null) + { + foreach (MemberBinding mb in mie.Bindings) + { + MemberAssignment ma = mb as MemberAssignment; + if (ma != null) + { + nepa.Visit(ma.Expression); + } + } + } + else + { + nepa.Visit(e); + } + } + + /// Visits a unary expression while initializing a non-entity type structure. + /// Expression to visit. + /// The visited expression. + internal override Expression VisitUnary(UnaryExpression u) + { + Debug.Assert(u != null, "u != null"); + + if (!ResourceBinder.PatternRules.MatchConvertToAssignable(u)) + { + // In V3 while we support TypeAs conversions, we only support TypeAs before a MemberAccess and not TypeAs as the last operation + // i.e. we support "Manager = (p as Employee).Manager" (see VisitMemberAccess for detail), but we dont support "Manager = (p as Manager)" + // Note that the server also doesn't support a property path which ends with a type identifier. + if (u.NodeType == ExpressionType.TypeAs && this.leafExpressionIsMemberAccess) + { + return base.VisitUnary(u); + } + + if (ClientTypeUtil.TypeOrElementTypeIsEntity(u.Operand.Type)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, u.ToString())); + } + } + + return base.VisitUnary(u); + } + + internal override Expression VisitBinary(BinaryExpression b) + { + if (ClientTypeUtil.TypeOrElementTypeIsEntity(b.Left.Type) || + ClientTypeUtil.TypeOrElementTypeIsEntity(b.Right.Type) || + IsCollectionProducingExpression(b.Left) || IsCollectionProducingExpression(b.Right)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, b.ToString())); + } + + return base.VisitBinary(b); + } + + internal override Expression VisitTypeIs(TypeBinaryExpression b) + { + if (ClientTypeUtil.TypeOrElementTypeIsEntity(b.Expression.Type ) || IsCollectionProducingExpression(b.Expression)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, b.ToString())); + } + + return base.VisitTypeIs(b); + } + + internal override Expression VisitConditional(ConditionalExpression c) + { + var nullCheck = ResourceBinder.PatternRules.MatchNullCheck(this.builder.ParamExpressionInScope, c); + if (nullCheck.Match) + { + this.Visit(nullCheck.AssignExpression); + return c; + } + + if (ClientTypeUtil.TypeOrElementTypeIsEntity(c.Test.Type) || + ClientTypeUtil.TypeOrElementTypeIsEntity(c.IfTrue.Type) || + ClientTypeUtil.TypeOrElementTypeIsEntity(c.IfFalse.Type) + || IsCollectionProducingExpression(c.Test) || IsCollectionProducingExpression(c.IfTrue) || IsCollectionProducingExpression(c.IfFalse)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, c.ToString())); + } + + return base.VisitConditional(c); + } + + /// + /// Visits a member access expression in non-entity projections, validating that + /// it's correct and recording the path visit to include in a projection if necessary. + /// + /// Expression to visit. + /// The same expression. + /// + /// The projection analyzer runs after funcletization, so a member expression + /// rather than a constant expression implies that this is correlated to + /// a parameter, by dotting through the argument in valid cases, and possibly + /// more complex cases in others like new DSC(p.Orders)*.Var1* <- .Var1 is invalid. + /// + internal override Expression VisitMemberAccess(MemberExpression m) + { + Debug.Assert(m != null, "m != null"); + Type expressionType = m.Expression.Type; + + this.leafExpressionIsMemberAccess = true; + + // if primitive or nullable primitive, allow member access... i.e. calling Value on nullable + if (PrimitiveType.IsKnownNullableType(expressionType)) + { + this.leafExpressionIsMemberAccess = false; + return base.VisitMemberAccess(m); + } + + // Only allowed to project entities, also it is ok to do client side projections on complex types. + // Details on the fix for "Inconsistency between Count() method call and Count property projection on clr type collections": + // Relax check to only throw if IsCollectionProducingExpression returns true. + // This enables client side projections (for example "Count") on Clr type collections, like ReadOnlyCollection (which is used in spatial types), ICollection, IList, etc. + // We already allow client side method calls (like Linq extension method "Count()") on clr type collections, so it makes client side projections consistent. + // Note: it will still throw for List (because IsCollectionProducingExpression returns true for List), + // however this is consistent with how we handle MethodCallExpression on clr type collections + // and changing IsCollectionProducingExpression seems risky at this point as it's used in a lot of places. + if (IsCollectionProducingExpression(m.Expression)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, m.ToString())); + } + + PropertyInfo pi; + Expression boundTarget; + if (ResourceBinder.PatternRules.MatchNonPrivateReadableProperty(m, out pi, out boundTarget)) + { + Expression e = base.VisitMemberAccess(m); + if (ClientTypeUtil.TypeOrElementTypeIsEntity(expressionType)) + { + Type convertedType; + ResourceBinder.StripTo(m.Expression, out convertedType); + this.builder.AppendPropertyToPath(pi, convertedType, this.context); + this.leafExpressionIsMemberAccess = false; + } + + return e; + } + + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, m.ToString())); + } + + internal override Expression VisitMethodCall(MethodCallExpression m) + { + if ((m.Object != null && IsDisallowedExpressionForMethodCall(m.Object, this.context.Model)) + || m.Arguments.Any(a => IsDisallowedExpressionForMethodCall(a, this.context.Model))) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, m.ToString())); + } + + CheckChainedSequence(m, this.type); + if (ProjectionAnalyzer.IsMethodCallAllowedEntitySequence(m)) + { + // allow IEnum.Select and IEnum.ToList even if entity type. + return base.VisitMethodCall(m); + } + + if ((m.Object != null ? ClientTypeUtil.TypeOrElementTypeIsEntity(m.Object.Type) : false) + || m.Arguments.Any(a => ClientTypeUtil.TypeOrElementTypeIsEntity(a.Type))) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, m.ToString())); + } + + return base.VisitMethodCall(m); + } + + internal override Expression VisitInvocation(InvocationExpression iv) + { + if (ClientTypeUtil.TypeOrElementTypeIsEntity(iv.Expression.Type) || + IsCollectionProducingExpression(iv.Expression) || + iv.Arguments.Any(a => ClientTypeUtil.TypeOrElementTypeIsEntity(a.Type) || IsCollectionProducingExpression(a))) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, iv.ToString())); + } + + return base.VisitInvocation(iv); + } + + internal override Expression VisitLambda(LambdaExpression lambda) + { + ProjectionAnalyzer.Analyze(lambda, this.builder, this.context); + return lambda; + } + + internal override Expression VisitMemberInit(MemberInitExpression init) + { + ProjectionAnalyzer.Analyze(init, this.builder, this.context); + return init; + } + + internal override NewExpression VisitNew(NewExpression nex) + { + // Allow creation of DataServiceCollection objects in projections, stop others that project entities + if (ClientTypeUtil.TypeOrElementTypeIsEntity(nex.Type) && + !ResourceBinder.PatternRules.MatchNewDataServiceCollectionOfT(nex)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, nex.ToString())); + } + + return base.VisitNew(nex); + } + + internal override Expression VisitParameter(ParameterExpression p) + { + if (ClientTypeUtil.TypeOrElementTypeIsEntity(p.Type)) + { + if (p != this.builder.ParamExpressionInScope) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, p.ToString())); + } + + this.builder.StartNewPath(); + } + return p; + } + + internal override Expression VisitConstant(ConstantExpression c) + { + if (ClientTypeUtil.TypeOrElementTypeIsEntity(c.Type)) + { + throw new NotSupportedException(Strings.ALinq_ExpressionNotSupportedInProjection(this.type, c.ToString())); + } + + return base.VisitConstant(c); + } + } + + #endregion Inner types + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionQueryOptionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionQueryOptionExpression.cs new file mode 100644 index 0000000..121739b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionQueryOptionExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq.Expressions; + + #endregion Namespaces + + /// + /// An resource specific expression representing a projection query option. + /// + internal class ProjectionQueryOptionExpression : QueryOptionExpression + { + #region Private fields + + /// projection expression to evaluate on client on results from server to materialize type + private readonly LambdaExpression lambda; + + /// projection paths to send to the server + private readonly List paths; + + #endregion Private fields + + /// + /// Creates a ProjectionQueryOption expression + /// + /// the return type of the expression + /// projection expression + /// Projection paths for the query option + internal ProjectionQueryOptionExpression(Type type, LambdaExpression lambda, List paths) + : base(type) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(lambda != null, "lambda != null"); + Debug.Assert(paths != null, "paths != null"); + + this.lambda = lambda; + this.paths = paths; + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.ProjectionQueryOption; } + } + + #region Internal properties + + /// + /// expression for the projection + /// + internal LambdaExpression Selector + { + get + { + return this.lambda; + } + } + + /// + /// expression for the projection + /// + internal List Paths + { + get + { + return this.paths; + } + } + + #endregion Internal properties + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionRewriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionRewriter.cs new file mode 100644 index 0000000..08af416 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ProjectionRewriter.cs @@ -0,0 +1,99 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using Microsoft.OData.Client.Metadata; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + + #endregion Namespaces + + internal class ProjectionRewriter : ALinqExpressionVisitor + { + #region Private fields + + private readonly ParameterExpression newLambdaParameter; + + private ParameterExpression oldLambdaParameter; + private ResourceExpression projectionSource; + + private bool successfulRebind; + + #endregion Private fields + + private ProjectionRewriter(Type proposedParameterType) + { + Debug.Assert(proposedParameterType != null, "proposedParameterType != null"); + this.newLambdaParameter = Expression.Parameter(proposedParameterType, "it"); + } + + #region Internal methods. + + internal static LambdaExpression TryToRewrite(LambdaExpression le, ResourceExpression source) + { + Type proposedParameterType = source.ResourceType; + LambdaExpression result; + if (!ResourceBinder.PatternRules.MatchSingleArgumentLambda(le, out le) || // can only rewrite single parameter Lambdas. + ClientTypeUtil.TypeOrElementTypeIsEntity(le.Parameters[0].Type) || // only attempt to rewrite if lambda parameter is not an entity type + !(le.Parameters[0].Type.GetProperties().Any(p => p.PropertyType == proposedParameterType))) // lambda parameter must have public property that is same as proposed type. + { + result = le; + } + else + { + ProjectionRewriter rewriter = new ProjectionRewriter(proposedParameterType); + result = rewriter.Rebind(le, source); + } + + return result; + } + + internal LambdaExpression Rebind(LambdaExpression lambda, ResourceExpression source) + { + this.successfulRebind = true; + this.oldLambdaParameter = lambda.Parameters[0]; + this.projectionSource = source; + + Expression body = this.Visit(lambda.Body); + if (this.successfulRebind) + { + Type delegateType = typeof(Func<,>).MakeGenericType(new Type[] { newLambdaParameter.Type, lambda.Body.Type }); + return Expression.Lambda(delegateType, body, new ParameterExpression[] { this.newLambdaParameter }); + } + else + { + throw new NotSupportedException(Strings.ALinq_CanOnlyProjectTheLeaf); + } + } + + internal override Expression VisitMemberAccess(MemberExpression m) + { + if (m.Expression == this.oldLambdaParameter) + { + // Member is only a valid projection target if it is the target of the current scope + QueryableResourceExpression resourceExpression = this.projectionSource as QueryableResourceExpression; + if (resourceExpression != null && resourceExpression.HasTransparentScope && resourceExpression.TransparentScope.Accessor == m.Member.Name) + { + Debug.Assert(m.Type == this.newLambdaParameter.Type, "Should not be rewriting a parameter with a different type than the original"); + return this.newLambdaParameter; + } + else + { + this.successfulRebind = false; + } + } + + return base.VisitMemberAccess(m); + } + + #endregion Internal methods. + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryComponents.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryComponents.cs new file mode 100644 index 0000000..68b5579 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryComponents.cs @@ -0,0 +1,243 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using Microsoft.OData; + + #endregion Namespaces + + /// Represents the components of query. + internal class QueryComponents + { + #region Private fields + + /// type + private readonly Type lastSegmentType; + + /// Records the generated-to-source rewrites created. + private readonly Dictionary normalizerRewrites; + + /// selector Lambda Expression + private readonly LambdaExpression projection; + + /// HttpMethod to use in the query. + private readonly string httpMethod; + + /// List of parameters for a service operation or a service function. + private readonly List uriOperationParameters; + + /// List of parameters for service action. + private readonly List bodyOperationParameters; + + /// + /// Optional field; not all codepaths set this. If true, then a single primitive or complex value is expected. + /// If false, then a collection of primitives or complex is expected. A null value makes no claim as to what + /// the return type should be. It follows that a single entry or a feed would always have this value as null. + /// + private readonly bool? singleResult; + + /// Query option used in projection queries. + private const string SelectQueryOption = "$select="; + + /// Select query option as it appears at the beginning of a query string. + private const string SelectQueryOptionWithQuestionMark = "?" + SelectQueryOption; + + /// Select query option as it appears in the middle of a query string. + private const string SelectQueryOptionWithAmpersand = "&" + SelectQueryOption; + + /// Select query option as it appears as the beginning of an embedded expand. + private const string SelectQueryOptionWithLeftParen = "(" + SelectQueryOption; + + /// Select query option as it appers as in the middle of an embedded expand. + private const string SelectQueryOptionWithSemi = ";" + SelectQueryOption; + + /// Version for query + private Version version; + + #endregion Private fields + + #region Constructors + + /// + /// Constructs a container for query components with HttpMethod GET. + /// + /// URI for the query + /// Version for the query + /// Element type for the query + /// selector Lambda Expression + /// Records the generated-to-source rewrites created (possibly null). + internal QueryComponents(Uri uri, Version version, Type lastSegmentType, LambdaExpression projection, Dictionary normalizerRewrites) + { + this.projection = projection; + this.normalizerRewrites = normalizerRewrites; + this.lastSegmentType = lastSegmentType; + this.Uri = uri; + this.version = version; + this.httpMethod = XmlConstants.HttpMethodGet; + } + + /// + /// Constructs a container for query components + /// + /// URI for the query + /// Version for the query + /// Element type for the query + /// selector Lambda Expression + /// Records the generated-to-source rewrites created (possibly null). + /// The HttpMethod to be used in the request. + /// If true, then a single primitive or complex value is expected. If false, then a collection of primitives or complex + /// is expected. Should be null when expecting a void response, a single entry, or a feed. + /// The body operation parameters associated with a service action. + /// The uri operation parameters associated with a service function or a service operation. + internal QueryComponents( + Uri uri, + Version version, + Type lastSegmentType, + LambdaExpression projection, + Dictionary normalizerRewrites, + string httpMethod, + bool? singleResult, + List bodyOperationParameters, + List uriOperationParameters) + { + Debug.Assert( + string.CompareOrdinal(XmlConstants.HttpMethodGet, httpMethod) == 0 || + string.CompareOrdinal(XmlConstants.HttpMethodPost, httpMethod) == 0 || + string.CompareOrdinal(XmlConstants.HttpMethodDelete, httpMethod) == 0, + "httpMethod should only be GET, POST or DELETE"); + + this.projection = projection; + this.normalizerRewrites = normalizerRewrites; + this.lastSegmentType = lastSegmentType; + this.Uri = uri; + this.version = version; + this.httpMethod = httpMethod; + this.uriOperationParameters = uriOperationParameters; + this.bodyOperationParameters = bodyOperationParameters; + this.singleResult = singleResult; + } + + #endregion + + /// Records the generated-to-source rewrites created. + internal Dictionary NormalizerRewrites + { + get + { + return this.normalizerRewrites; + } + } + + /// The projection expression for a query + internal LambdaExpression Projection + { + get + { + return this.projection; + } + } + + /// The last segment type for query + internal Type LastSegmentType + { + get + { + return this.lastSegmentType; + } + } + + /// The data service version associated with the uri + internal Version Version + { + get + { + return this.version; + } + } + + /// The HttpMethod to be used in the query. + internal string HttpMethod + { + get + { + return this.httpMethod; + } + } + + /// + /// List of operation parameters for service operation or a service function. + /// + internal List UriOperationParameters + { + get + { + return this.uriOperationParameters; + } + } + + /// + /// List of operation parameters for a service action. + /// + internal List BodyOperationParameters + { + get + { + return this.bodyOperationParameters; + } + } + + /// + /// Optional field; not all codepaths set this. If true, then a single primitive or complex value is expected. + /// If false, then a collection of primitives or complex is expected. A null value makes no claim as to what + /// the return type should be. It follows that a single entry or a feed would always have this value as null. + /// + internal bool? SingleResult + { + get + { + return this.singleResult; + } + } + + /// + /// Gets a value indicating whether the URI for this query has the select query option. + /// + internal bool HasSelectQueryOption + { + get { return this.Uri != null && ContainsSelectQueryOption(UriUtil.UriToString(this.Uri)); } + } + + /// Gets or sets the URI for a query, possibly with query options added to the cached URI. + /// URI with additional query options added if required. + internal Uri Uri { get; set; } + + /// + /// Determines whether or not the specified query string contains the $select query option. + /// + /// String that may contain $select. + /// True if the specified string contains the $select query option, otherwise false. + /// + /// This method is specifically looking for patterns that would indicate we really have the specific query option and not something like $selectNew. It also expects that + /// any data string being passed to this method has already been escaped, as the things we are looking for specifically contain equals signs that would be escaped if in a data value. + /// + private static bool ContainsSelectQueryOption(string queryString) + { + return queryString.Contains(SelectQueryOptionWithQuestionMark) + || queryString.Contains(SelectQueryOptionWithAmpersand) + || queryString.Contains(SelectQueryOptionWithLeftParen) + || queryString.Contains(SelectQueryOptionWithSemi); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryOptionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryOptionExpression.cs new file mode 100644 index 0000000..e7ddadf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryOptionExpression.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Linq.Expressions; + + /// + /// An resource specific expression representing a query option. + /// + internal abstract class QueryOptionExpression : Expression + { + /// The CLR type this node will evaluate into. + private Type type; + + /// + /// Creates a QueryOptionExpression expression + /// + /// the return type of the expression + internal QueryOptionExpression(Type type) + { + Debug.Assert(type != null, "type != null"); + this.type = type; + } + + /// + /// The of the value represented by this . + /// + public override Type Type + { + get { return this.type; } + } + + /// + /// Composes the expression with this one when it's specified multiple times. + /// + /// to compose. + /// + /// The expression that results from composing the expression with this one. + /// + internal virtual QueryOptionExpression ComposeMultipleSpecification(QueryOptionExpression previous) + { + Debug.Assert(previous != null, "other != null"); + Debug.Assert(previous.GetType() == this.GetType(), "other.GetType == this.GetType() -- otherwise it's not the same specification"); + return this; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryableResourceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryableResourceExpression.cs new file mode 100644 index 0000000..cf78ac8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/QueryableResourceExpression.cs @@ -0,0 +1,508 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + /// Queryable Resource Expression, the base class for ResourceSetExpression and SingletonExpression + [DebuggerDisplay("QueryableResourceExpression {Source}.{MemberExpression}")] + internal abstract class QueryableResourceExpression : ResourceExpression + { + /// Key Predicate conjuncts that will make a key predicate + private readonly List keyPredicateConjuncts; + + /// + /// The (static) type of the resources in this navigation resource. + /// The resource type can differ from this.Type if this expression represents a transparent scope. + /// For example, in TransparentScope{Category, Product}, the true element type is Product. + /// + private readonly Type resourceType; + + /// property member name + private readonly Expression member; + + /// key predicate + private Dictionary keyFilter; + + /// sequence query options + private List sequenceQueryOptions; + + /// enclosing transparent scope + private TransparentAccessors transparentScope; + + /// + /// Creates a navigation resource expression + /// + /// the return type of the expression + /// the source expression + /// property member name + /// the element type of the resource + /// expand paths for resource set + /// count query option for the resource + /// custom query options for resource + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + internal QueryableResourceExpression(Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion) + : this(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion, null, null, false) + { + } + + /// + /// Creates a navigation resource expression + /// + /// the return type of the expression + /// the source expression + /// property member name + /// the element type of the resource + /// expand paths for resource set + /// count query option for the resource + /// custom query options for resource + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + /// name of function + /// parameters' names and values of function + /// action flag + internal QueryableResourceExpression(Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion, string operationName, Dictionary operationParameters, bool isAction) + : base(source, type, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion, operationName, operationParameters, isAction) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(resourceType != null, "resourceType != null"); + Debug.Assert( + (source == null && memberExpression is ConstantExpression) || + (source != null && memberExpression is MemberExpression) || + (memberExpression == null), + "source is null with constant entity set name, or not null with member expression, or memberExpression is null for function import."); + + this.member = memberExpression; + this.resourceType = resourceType; + this.sequenceQueryOptions = new List(); + this.keyPredicateConjuncts = new List(); + } + + /// + /// Member for ResourceSet + /// + internal Expression MemberExpression + { + get { return this.member; } + } + + /// + /// Type of resources contained in this ResourceSet - it's element type. + /// + internal override Type ResourceType + { + get { return this.resourceType; } + } + + /// + /// Is this ResourceSet enclosed in an anonymously-typed transparent scope produced by a SelectMany operation? + /// Applies to navigation ResourceSets. + /// + internal bool HasTransparentScope + { + get { return this.transparentScope != null; } + } + + /// + /// The property accesses required to reference this ResourceSet and its source ResourceSet if a transparent scope is present. + /// May be null. Use to test for the presence of a value. + /// + internal TransparentAccessors TransparentScope + { + get { return this.transparentScope; } + set { this.transparentScope = value; } + } + + /// + /// The list of key expressions that comprise the key predicate (if any) applied to this ResourceSet. + /// + internal ReadOnlyCollection KeyPredicateConjuncts + { + get + { + return new ReadOnlyCollection(this.keyPredicateConjuncts); + } + } + + /// + /// Have sequence query options (filter, orderby, skip, take), expand paths, projection + /// or custom query options been applied to this resource set? + /// + internal override bool HasQueryOptions + { + get + { + return this.sequenceQueryOptions.Count > 0 || + this.ExpandPaths.Count > 0 || + this.CountOption == CountOption.CountQuery || // value only count is not an option + this.CustomQueryOptions.Count > 0 || + this.Projection != null; + } + } + + /// + /// If this expresssion contains at least one non-key predicate + /// This indicates that a filter should be used + /// + internal bool UseFilterAsPredicate { get; set; } + + /// + /// Filter query option for ResourceSet + /// + internal FilterQueryOptionExpression Filter + { + get + { + return this.sequenceQueryOptions.OfType().SingleOrDefault(); + } + } + + /// + /// OrderBy query option for ResourceSet + /// + internal OrderByQueryOptionExpression OrderBy + { + get { return this.sequenceQueryOptions.OfType().SingleOrDefault(); } + } + + /// + /// Skip query option for ResourceSet + /// + internal SkipQueryOptionExpression Skip + { + get { return this.sequenceQueryOptions.OfType().SingleOrDefault(); } + } + + /// + /// Take query option for ResourceSet + /// + internal TakeQueryOptionExpression Take + { + get { return this.sequenceQueryOptions.OfType().SingleOrDefault(); } + } + + /// + /// Gets sequence query options for ResourceSet + /// + internal IEnumerable SequenceQueryOptions + { + get { return this.sequenceQueryOptions.ToList(); } + } + + /// Whether there are any query options for the sequence. + internal bool HasSequenceQueryOptions + { + get { return this.sequenceQueryOptions.Count > 0; } + } + + /// + /// Create a clone with new type. + /// + /// The type. + /// The new clone. + internal override ResourceExpression CreateCloneWithNewType(Type type) + { + QueryableResourceExpression clone = this.CreateCloneWithNewTypes(type, TypeSystem.GetElementType(type)); + + if (this.keyPredicateConjuncts != null && this.keyPredicateConjuncts.Count > 0) + { + clone.SetKeyPredicate(this.keyPredicateConjuncts); + } + + clone.keyFilter = this.keyFilter; + clone.sequenceQueryOptions = this.sequenceQueryOptions; + clone.transparentScope = this.transparentScope; + return clone; + } + + /// + /// Creates a navigation resource expression + /// + /// The expression type. + /// the return type of the expression + /// the source expression + /// property member name + /// the element type of the resource + /// expand paths for resource set + /// count query option for the resource + /// custom query options for resource + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + /// The operation name. + /// The operation parameter names and parameters pair for Resource + /// The navigation resource expression. + internal static QueryableResourceExpression CreateNavigationResourceExpression(ExpressionType expressionType, Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion, string operationName, Dictionary operationParameters) + { + Debug.Assert( + expressionType == (ExpressionType)ResourceExpressionType.RootResourceSet || expressionType == (ExpressionType)ResourceExpressionType.ResourceNavigationProperty || expressionType == (ExpressionType)ResourceExpressionType.RootSingleResource, + "Expression type is not one of following: RootResourceSet, ResourceNavigationProperty, RootSingleResource."); + + QueryableResourceExpression expression = null; + + if (expressionType == (ExpressionType)ResourceExpressionType.RootResourceSet || expressionType == (ExpressionType)ResourceExpressionType.ResourceNavigationProperty) + { + expression = new ResourceSetExpression(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion); + } + + if (expressionType == (ExpressionType)ResourceExpressionType.RootSingleResource) + { + expression = new SingletonResourceExpression(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion); + } + + if (expression != null) + { + expression.OperationName = operationName; + expression.OperationParameters = operationParameters; + return expression; + } + + return null; + } + + /// + /// Cast QueryableResourceExpression to new type without affecting member type + /// + /// The new expression type + /// A copy of this with the new types + internal QueryableResourceExpression CreateCloneForTransparentScope(Type type) + { + // QueryableResourceExpression can always have order information, + // so return them as IOrderedQueryable<> always. Necessary to allow + // OrderBy results that get aliased to a previous expression work + // with ThenBy. + Type elementType = TypeSystem.GetElementType(type); + Debug.Assert(elementType != null, "elementType != null -- otherwise the set isn't going to act like a collection"); + Type newType = typeof(IOrderedQueryable<>).MakeGenericType(elementType); + + QueryableResourceExpression clone = this.CreateCloneWithNewTypes(newType, this.ResourceType); + + if (this.keyPredicateConjuncts != null && this.keyPredicateConjuncts.Count > 0) + { + clone.SetKeyPredicate(this.keyPredicateConjuncts); + } + + clone.keyFilter = this.keyFilter; + clone.sequenceQueryOptions = this.sequenceQueryOptions; + clone.transparentScope = this.transparentScope; + return clone; + } + + /// + /// Converts the key expression to filter expression + /// + internal void ConvertKeyToFilterExpression() + { + if (this.keyPredicateConjuncts.Count > 0) + { + this.AddFilter(this.keyPredicateConjuncts); + } + } + + /// + /// Adds a filter to this ResourceSetExpression. + /// If filter is already presents, adds the predicateConjunts to the + /// PredicateConjucts of the filter + /// + /// The predicate conjuncts. + internal void AddFilter(IEnumerable predicateConjuncts) + { + if (this.Skip != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "skip")); + } + else if (this.Take != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "top")); + } + else if (this.Projection != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "select")); + } + + if (this.Filter == null) + { + this.AddSequenceQueryOption(new FilterQueryOptionExpression(this.Type)); + } + + this.Filter.AddPredicateConjuncts(predicateConjuncts); + + this.keyPredicateConjuncts.Clear(); + } + + /// + /// Add query option to resource expression + /// + /// The query option expression. + internal void AddSequenceQueryOption(QueryOptionExpression qoe) + { + Debug.Assert(qoe != null, "qoe != null"); + QueryOptionExpression old = this.sequenceQueryOptions.Where(o => o.GetType() == qoe.GetType()).FirstOrDefault(); + if (old != null) + { + qoe = qoe.ComposeMultipleSpecification(old); + this.sequenceQueryOptions.Remove(old); + } + + this.sequenceQueryOptions.Add(qoe); + } + + /// + /// Removes the filter expression from current resource set. + /// This happens when a separate Where clause is specified in a LINQ + /// expression for every key property. + /// + internal void RemoveFilterExpression() + { + if (this.Filter != null) + { + this.sequenceQueryOptions.Remove(this.Filter); + } + } + + /// + /// Instructs this resource set expression to use the input reference expression from as it's + /// own input reference, and to retarget the input reference from to this resource set expression. + /// + /// The resource set expression from which to take the input reference. + /// Used exclusively by ResourceBinder.RemoveTransparentScope. + internal void OverrideInputReference(QueryableResourceExpression newInput) + { + Debug.Assert(newInput != null, "Original resource set cannot be null"); + Debug.Assert(this.inputRef == null, "OverrideInputReference cannot be called if the target has already been referenced"); + + InputReferenceExpression inputRef = newInput.inputRef; + if (inputRef != null) + { + this.inputRef = inputRef; + inputRef.OverrideTarget(this); + } + } + + /// + /// Sets key predicate from given values + /// + /// The key values + internal void SetKeyPredicate(IEnumerable keyValues) + { + Debug.Assert(keyValues != null, "keyValues != null"); + + this.keyPredicateConjuncts.Clear(); + + foreach (Expression ex in keyValues) + { + this.keyPredicateConjuncts.Add(ex); + } + } + + /// + /// Gets the key properties from KeyPredicateConjuncts + /// + /// The key properties. + internal Dictionary GetKeyProperties() + { + var keyValues = new Dictionary(EqualityComparer.Default); + if (this.keyPredicateConjuncts.Count > 0) + { + foreach (Expression predicate in this.keyPredicateConjuncts) + { + PropertyInfo property; + ConstantExpression constantValue; + if (ResourceBinder.PatternRules.MatchKeyComparison(predicate, out property, out constantValue)) + { + keyValues.Add(property, constantValue); + } + } + } + + return keyValues; + } + + /// + /// Creates a clone of the current QueryableResourceExpression with the specified expression and resource types + /// + /// The new expression type + /// The new resource type + /// A copy of this with the new types + protected abstract QueryableResourceExpression CreateCloneWithNewTypes(Type newType, Type newResourceType); + + /// + /// Represents the property accesses required to access both + /// this resource set and its source resource/set (for navigations). + /// + /// These accesses are required to reference resource sets enclosed + /// in transparent scopes introduced by use of SelectMany. + /// + /// + /// For example, this query: + /// from c in Custs where c.id == 1 + /// from o in c.Orders from od in o.OrderDetails select od + /// + /// Translates to: + /// c.Where(c => c.id == 1) + /// .SelectMany(c => o, (c, o) => new $(c=c, o=o)) + /// .SelectMany($ => $.o, ($, od) => od) + /// + /// PatternRules.MatchPropertyProjectionSet identifies Orders as the target of the collector. + /// PatternRules.MatchTransparentScopeSelector identifies the introduction of a transparent identifer. + /// + /// A transparent accessor is associated with Orders, with 'c' being the source accesor, + /// and 'o' being the (introduced) accessor. + /// + [DebuggerDisplay("{ToString()}")] + internal class TransparentAccessors + { + #region Internal fields. + + /// + /// The property reference that must be applied to reference this resource set + /// + internal readonly string Accessor; + + /// + /// The property reference that must be applied to reference the source resource set. + /// Note that this set's Accessor is NOT required to access the source set, but the + /// source set MAY impose it's own Transparent Accessors + /// + internal readonly Dictionary SourceAccessors; + + #endregion Internal fields. + + /// + /// Constructs a new transparent scope with the specified set and source set accessors + /// + /// The name of the property required to access the resource set + /// The names of the property required to access the resource set's sources. + internal TransparentAccessors(string acc, Dictionary sourceAccesors) + { + Debug.Assert(!string.IsNullOrEmpty(acc), "Set accessor cannot be null or empty"); + Debug.Assert(sourceAccesors != null, "sourceAccesors != null"); + + this.Accessor = acc; + this.SourceAccessors = sourceAccesors; + } + + /// Provides a string representation of this accessor. + /// The text represntation of this accessor. + public override string ToString() + { + string result = "SourceAccessors=[" + string.Join(",", this.SourceAccessors.Keys.ToArray()); + result += "] ->* Accessor=" + this.Accessor; + return result; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ReflectionUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ReflectionUtil.cs new file mode 100644 index 0000000..5a51fd8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ReflectionUtil.cs @@ -0,0 +1,733 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Text; + + #endregion Namespaces + + /// + /// Static utility class for identifying methods in Queryable, Sequence, and IEnumerable + /// and + /// + internal static class ReflectionUtil + { + /// The Collection Count property name + internal const string COUNTPROPNAME = "Count"; + + #region Static information on sequence methods + private static readonly Dictionary s_methodMap; + private static readonly Dictionary s_inverseMap; + + // Initialize method map + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "lots of lines of code are necessary to initialize the map properly")] + static ReflectionUtil() + { + // register known canonical method names + Dictionary map = new Dictionary(EqualityComparer.Default); + map.Add(@"Sum(IQueryable`1, Expression`1>)->Double", SequenceMethod.SumDoubleSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.SumNullableDoubleSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>)->Decimal", SequenceMethod.SumDecimalSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.SumNullableDecimalSelector); + map.Add(@"Average(IQueryable`1, Expression`1>)->Double", SequenceMethod.AverageIntSelector); + map.Add(@"Average(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.AverageNullableIntSelector); + map.Add(@"Average(IQueryable`1, Expression`1>)->Single", SequenceMethod.AverageSingleSelector); + map.Add(@"Average(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.AverageNullableSingleSelector); + map.Add(@"Average(IQueryable`1, Expression`1>)->Double", SequenceMethod.AverageLongSelector); + map.Add(@"Average(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.AverageNullableLongSelector); + map.Add(@"Average(IQueryable`1, Expression`1>)->Double", SequenceMethod.AverageDoubleSelector); + map.Add(@"Average(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.AverageNullableDoubleSelector); + map.Add(@"Average(IQueryable`1, Expression`1>)->Decimal", SequenceMethod.AverageDecimalSelector); + map.Add(@"Average(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.AverageNullableDecimalSelector); + map.Add(@"Aggregate(IQueryable`1, Expression`1>)->T0", SequenceMethod.Aggregate); + map.Add(@"Aggregate(IQueryable`1, T1, Expression`1>)->T1", SequenceMethod.AggregateSeed); + map.Add(@"Aggregate(IQueryable`1, T1, Expression`1>, Expression`1>)->T2", SequenceMethod.AggregateSeedSelector); + map.Add(@"AsQueryable(IEnumerable`1)->IQueryable`1", SequenceMethod.AsQueryableGeneric); + map.Add(@"Where(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.Where); + map.Add(@"Where(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.WhereOrdinal); + map.Add(@"OfType(IQueryable)->IQueryable`1", SequenceMethod.OfType); + map.Add(@"Cast(IQueryable)->IQueryable`1", SequenceMethod.Cast); + map.Add(@"Select(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.Select); + map.Add(@"Select(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.SelectOrdinal); + map.Add(@"SelectMany(IQueryable`1, Expression`1>>)->IQueryable`1", SequenceMethod.SelectMany); + map.Add(@"SelectMany(IQueryable`1, Expression`1>>)->IQueryable`1", SequenceMethod.SelectManyOrdinal); + map.Add(@"SelectMany(IQueryable`1, Expression`1>>, Expression`1>)->IQueryable`1", SequenceMethod.SelectManyOrdinalResultSelector); + map.Add(@"SelectMany(IQueryable`1, Expression`1>>, Expression`1>)->IQueryable`1", SequenceMethod.SelectManyResultSelector); + map.Add(@"Join(IQueryable`1, IEnumerable`1, Expression`1>, Expression`1>, Expression`1>)->IQueryable`1", SequenceMethod.Join); + map.Add(@"Join(IQueryable`1, IEnumerable`1, Expression`1>, Expression`1>, Expression`1>, IEqualityComparer`1)->IQueryable`1", SequenceMethod.JoinComparer); + map.Add(@"GroupJoin(IQueryable`1, IEnumerable`1, Expression`1>, Expression`1>, Expression`1, T3>>)->IQueryable`1", SequenceMethod.GroupJoin); + map.Add(@"GroupJoin(IQueryable`1, IEnumerable`1, Expression`1>, Expression`1>, Expression`1, T3>>, IEqualityComparer`1)->IQueryable`1", SequenceMethod.GroupJoinComparer); + map.Add(@"OrderBy(IQueryable`1, Expression`1>)->IOrderedQueryable`1", SequenceMethod.OrderBy); + map.Add(@"OrderBy(IQueryable`1, Expression`1>, IComparer`1)->IOrderedQueryable`1", SequenceMethod.OrderByComparer); + map.Add(@"OrderByDescending(IQueryable`1, Expression`1>)->IOrderedQueryable`1", SequenceMethod.OrderByDescending); + map.Add(@"OrderByDescending(IQueryable`1, Expression`1>, IComparer`1)->IOrderedQueryable`1", SequenceMethod.OrderByDescendingComparer); + map.Add(@"ThenBy(IOrderedQueryable`1, Expression`1>)->IOrderedQueryable`1", SequenceMethod.ThenBy); + map.Add(@"ThenBy(IOrderedQueryable`1, Expression`1>, IComparer`1)->IOrderedQueryable`1", SequenceMethod.ThenByComparer); + map.Add(@"ThenByDescending(IOrderedQueryable`1, Expression`1>)->IOrderedQueryable`1", SequenceMethod.ThenByDescending); + map.Add(@"ThenByDescending(IOrderedQueryable`1, Expression`1>, IComparer`1)->IOrderedQueryable`1", SequenceMethod.ThenByDescendingComparer); + map.Add(@"Take(IQueryable`1, Int32)->IQueryable`1", SequenceMethod.Take); + map.Add(@"TakeWhile(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.TakeWhile); + map.Add(@"TakeWhile(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.TakeWhileOrdinal); + map.Add(@"Skip(IQueryable`1, Int32)->IQueryable`1", SequenceMethod.Skip); + map.Add(@"SkipWhile(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.SkipWhile); + map.Add(@"SkipWhile(IQueryable`1, Expression`1>)->IQueryable`1", SequenceMethod.SkipWhileOrdinal); + map.Add(@"GroupBy(IQueryable`1, Expression`1>)->IQueryable`1>", SequenceMethod.GroupBy); + map.Add(@"GroupBy(IQueryable`1, Expression`1>, Expression`1>)->IQueryable`1>", SequenceMethod.GroupByElementSelector); + map.Add(@"GroupBy(IQueryable`1, Expression`1>, IEqualityComparer`1)->IQueryable`1>", SequenceMethod.GroupByComparer); + map.Add(@"GroupBy(IQueryable`1, Expression`1>, Expression`1>, IEqualityComparer`1)->IQueryable`1>", SequenceMethod.GroupByElementSelectorComparer); + map.Add(@"GroupBy(IQueryable`1, Expression`1>, Expression`1>, Expression`1, T3>>)->IQueryable`1", SequenceMethod.GroupByElementSelectorResultSelector); + map.Add(@"GroupBy(IQueryable`1, Expression`1>, Expression`1, T2>>)->IQueryable`1", SequenceMethod.GroupByResultSelector); + map.Add(@"GroupBy(IQueryable`1, Expression`1>, Expression`1, T2>>, IEqualityComparer`1)->IQueryable`1", SequenceMethod.GroupByResultSelectorComparer); + map.Add(@"GroupBy(IQueryable`1, Expression`1>, Expression`1>, Expression`1, T3>>, IEqualityComparer`1)->IQueryable`1", SequenceMethod.GroupByElementSelectorResultSelectorComparer); + map.Add(@"Distinct(IQueryable`1)->IQueryable`1", SequenceMethod.Distinct); + map.Add(@"Distinct(IQueryable`1, IEqualityComparer`1)->IQueryable`1", SequenceMethod.DistinctComparer); + map.Add(@"Concat(IQueryable`1, IEnumerable`1)->IQueryable`1", SequenceMethod.Concat); + map.Add(@"Union(IQueryable`1, IEnumerable`1)->IQueryable`1", SequenceMethod.Union); + map.Add(@"Union(IQueryable`1, IEnumerable`1, IEqualityComparer`1)->IQueryable`1", SequenceMethod.UnionComparer); + map.Add(@"Intersect(IQueryable`1, IEnumerable`1)->IQueryable`1", SequenceMethod.Intersect); + map.Add(@"Intersect(IQueryable`1, IEnumerable`1, IEqualityComparer`1)->IQueryable`1", SequenceMethod.IntersectComparer); + map.Add(@"Except(IQueryable`1, IEnumerable`1)->IQueryable`1", SequenceMethod.Except); + map.Add(@"Except(IQueryable`1, IEnumerable`1, IEqualityComparer`1)->IQueryable`1", SequenceMethod.ExceptComparer); + map.Add(@"First(IQueryable`1)->T0", SequenceMethod.First); + map.Add(@"First(IQueryable`1, Expression`1>)->T0", SequenceMethod.FirstPredicate); + map.Add(@"FirstOrDefault(IQueryable`1)->T0", SequenceMethod.FirstOrDefault); + map.Add(@"FirstOrDefault(IQueryable`1, Expression`1>)->T0", SequenceMethod.FirstOrDefaultPredicate); + map.Add(@"Last(IQueryable`1)->T0", SequenceMethod.Last); + map.Add(@"Last(IQueryable`1, Expression`1>)->T0", SequenceMethod.LastPredicate); + map.Add(@"LastOrDefault(IQueryable`1)->T0", SequenceMethod.LastOrDefault); + map.Add(@"LastOrDefault(IQueryable`1, Expression`1>)->T0", SequenceMethod.LastOrDefaultPredicate); + map.Add(@"Single(IQueryable`1)->T0", SequenceMethod.Single); + map.Add(@"Single(IQueryable`1, Expression`1>)->T0", SequenceMethod.SinglePredicate); + map.Add(@"SingleOrDefault(IQueryable`1)->T0", SequenceMethod.SingleOrDefault); + map.Add(@"SingleOrDefault(IQueryable`1, Expression`1>)->T0", SequenceMethod.SingleOrDefaultPredicate); + map.Add(@"ElementAt(IQueryable`1, Int32)->T0", SequenceMethod.ElementAt); + map.Add(@"ElementAtOrDefault(IQueryable`1, Int32)->T0", SequenceMethod.ElementAtOrDefault); + map.Add(@"DefaultIfEmpty(IQueryable`1)->IQueryable`1", SequenceMethod.DefaultIfEmpty); + map.Add(@"DefaultIfEmpty(IQueryable`1, T0)->IQueryable`1", SequenceMethod.DefaultIfEmptyValue); + map.Add(@"Contains(IQueryable`1, T0)->Boolean", SequenceMethod.Contains); + map.Add(@"Contains(IQueryable`1, T0, IEqualityComparer`1)->Boolean", SequenceMethod.ContainsComparer); + map.Add(@"Reverse(IQueryable`1)->IQueryable`1", SequenceMethod.Reverse); + map.Add(@"SequenceEqual(IQueryable`1, IEnumerable`1)->Boolean", SequenceMethod.SequenceEqual); + map.Add(@"SequenceEqual(IQueryable`1, IEnumerable`1, IEqualityComparer`1)->Boolean", SequenceMethod.SequenceEqualComparer); + map.Add(@"Any(IQueryable`1)->Boolean", SequenceMethod.Any); + map.Add(@"Any(IQueryable`1, Expression`1>)->Boolean", SequenceMethod.AnyPredicate); + map.Add(@"All(IQueryable`1, Expression`1>)->Boolean", SequenceMethod.All); + map.Add(@"Count(IQueryable`1)->Int32", SequenceMethod.Count); + map.Add(@"Count(IQueryable`1, Expression`1>)->Int32", SequenceMethod.CountPredicate); + map.Add(@"LongCount(IQueryable`1)->Int64", SequenceMethod.LongCount); + map.Add(@"LongCount(IQueryable`1, Expression`1>)->Int64", SequenceMethod.LongCountPredicate); + map.Add(@"Min(IQueryable`1)->T0", SequenceMethod.Min); + map.Add(@"Min(IQueryable`1, Expression`1>)->T1", SequenceMethod.MinSelector); + map.Add(@"Max(IQueryable`1)->T0", SequenceMethod.Max); + map.Add(@"Max(IQueryable`1, Expression`1>)->T1", SequenceMethod.MaxSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>)->Int32", SequenceMethod.SumIntSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.SumNullableIntSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>)->Int64", SequenceMethod.SumLongSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.SumNullableLongSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>)->Single", SequenceMethod.SumSingleSelector); + map.Add(@"Sum(IQueryable`1, Expression`1>>)->Nullable`1", SequenceMethod.SumNullableSingleSelector); + map.Add(@"AsQueryable(IEnumerable)->IQueryable", SequenceMethod.AsQueryable); + map.Add(@"Sum(IQueryable`1)->Int32", SequenceMethod.SumInt); + map.Add(@"Sum(IQueryable`1>)->Nullable`1", SequenceMethod.SumNullableInt); + map.Add(@"Sum(IQueryable`1)->Int64", SequenceMethod.SumLong); + map.Add(@"Sum(IQueryable`1>)->Nullable`1", SequenceMethod.SumNullableLong); + map.Add(@"Sum(IQueryable`1)->Single", SequenceMethod.SumSingle); + map.Add(@"Sum(IQueryable`1>)->Nullable`1", SequenceMethod.SumNullableSingle); + map.Add(@"Sum(IQueryable`1)->Double", SequenceMethod.SumDouble); + map.Add(@"Sum(IQueryable`1>)->Nullable`1", SequenceMethod.SumNullableDouble); + map.Add(@"Sum(IQueryable`1)->Decimal", SequenceMethod.SumDecimal); + map.Add(@"Sum(IQueryable`1>)->Nullable`1", SequenceMethod.SumNullableDecimal); + map.Add(@"Average(IQueryable`1)->Double", SequenceMethod.AverageInt); + map.Add(@"Average(IQueryable`1>)->Nullable`1", SequenceMethod.AverageNullableInt); + map.Add(@"Average(IQueryable`1)->Double", SequenceMethod.AverageLong); + map.Add(@"Average(IQueryable`1>)->Nullable`1", SequenceMethod.AverageNullableLong); + map.Add(@"Average(IQueryable`1)->Single", SequenceMethod.AverageSingle); + map.Add(@"Average(IQueryable`1>)->Nullable`1", SequenceMethod.AverageNullableSingle); + map.Add(@"Average(IQueryable`1)->Double", SequenceMethod.AverageDouble); + map.Add(@"Average(IQueryable`1>)->Nullable`1", SequenceMethod.AverageNullableDouble); + map.Add(@"Average(IQueryable`1)->Decimal", SequenceMethod.AverageDecimal); + map.Add(@"Average(IQueryable`1>)->Nullable`1", SequenceMethod.AverageNullableDecimal); + map.Add(@"First(IEnumerable`1)->T0", SequenceMethod.First); + map.Add(@"First(IEnumerable`1, Func`2)->T0", SequenceMethod.FirstPredicate); + map.Add(@"FirstOrDefault(IEnumerable`1)->T0", SequenceMethod.FirstOrDefault); + map.Add(@"FirstOrDefault(IEnumerable`1, Func`2)->T0", SequenceMethod.FirstOrDefaultPredicate); + map.Add(@"Last(IEnumerable`1)->T0", SequenceMethod.Last); + map.Add(@"Last(IEnumerable`1, Func`2)->T0", SequenceMethod.LastPredicate); + map.Add(@"LastOrDefault(IEnumerable`1)->T0", SequenceMethod.LastOrDefault); + map.Add(@"LastOrDefault(IEnumerable`1, Func`2)->T0", SequenceMethod.LastOrDefaultPredicate); + map.Add(@"Single(IEnumerable`1)->T0", SequenceMethod.Single); + map.Add(@"Single(IEnumerable`1, Func`2)->T0", SequenceMethod.SinglePredicate); + map.Add(@"SingleOrDefault(IEnumerable`1)->T0", SequenceMethod.SingleOrDefault); + map.Add(@"SingleOrDefault(IEnumerable`1, Func`2)->T0", SequenceMethod.SingleOrDefaultPredicate); + map.Add(@"ElementAt(IEnumerable`1, Int32)->T0", SequenceMethod.ElementAt); + map.Add(@"ElementAtOrDefault(IEnumerable`1, Int32)->T0", SequenceMethod.ElementAtOrDefault); + map.Add(@"Repeat(T0, Int32)->IEnumerable`1", SequenceMethod.NotSupported); + map.Add(@"Empty()->IEnumerable`1", SequenceMethod.Empty); + map.Add(@"Any(IEnumerable`1)->Boolean", SequenceMethod.Any); + map.Add(@"Any(IEnumerable`1, Func`2)->Boolean", SequenceMethod.AnyPredicate); + map.Add(@"All(IEnumerable`1, Func`2)->Boolean", SequenceMethod.All); + map.Add(@"Count(IEnumerable`1)->Int32", SequenceMethod.Count); + map.Add(@"Count(IEnumerable`1, Func`2)->Int32", SequenceMethod.CountPredicate); + map.Add(@"LongCount(IEnumerable`1)->Int64", SequenceMethod.LongCount); + map.Add(@"LongCount(IEnumerable`1, Func`2)->Int64", SequenceMethod.LongCountPredicate); + map.Add(@"Contains(IEnumerable`1, T0)->Boolean", SequenceMethod.Contains); + map.Add(@"Contains(IEnumerable`1, T0, IEqualityComparer`1)->Boolean", SequenceMethod.ContainsComparer); + map.Add(@"Aggregate(IEnumerable`1, Func`3)->T0", SequenceMethod.Aggregate); + map.Add(@"Aggregate(IEnumerable`1, T1, Func`3)->T1", SequenceMethod.AggregateSeed); + map.Add(@"Aggregate(IEnumerable`1, T1, Func`3, Func`2)->T2", SequenceMethod.AggregateSeedSelector); + map.Add(@"Sum(IEnumerable`1, Func`2)->Int32", SequenceMethod.SumIntSelector); + map.Add(@"Sum(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.SumNullableIntSelector); + map.Add(@"Sum(IEnumerable`1, Func`2)->Int64", SequenceMethod.SumLongSelector); + map.Add(@"Sum(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.SumNullableLongSelector); + map.Add(@"Sum(IEnumerable`1, Func`2)->Single", SequenceMethod.SumSingleSelector); + map.Add(@"Sum(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.SumNullableSingleSelector); + map.Add(@"Sum(IEnumerable`1, Func`2)->Double", SequenceMethod.SumDoubleSelector); + map.Add(@"Sum(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.SumNullableDoubleSelector); + map.Add(@"Sum(IEnumerable`1, Func`2)->Decimal", SequenceMethod.SumDecimalSelector); + map.Add(@"Sum(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.SumNullableDecimalSelector); + map.Add(@"Min(IEnumerable`1)->T0", SequenceMethod.Min); + map.Add(@"Min(IEnumerable`1, Func`2)->Int32", SequenceMethod.MinIntSelector); + map.Add(@"Min(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MinNullableIntSelector); + map.Add(@"Min(IEnumerable`1, Func`2)->Int64", SequenceMethod.MinLongSelector); + map.Add(@"Min(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MinNullableLongSelector); + map.Add(@"Min(IEnumerable`1, Func`2)->Single", SequenceMethod.MinSingleSelector); + map.Add(@"Min(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MinNullableSingleSelector); + map.Add(@"Min(IEnumerable`1, Func`2)->Double", SequenceMethod.MinDoubleSelector); + map.Add(@"Min(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MinNullableDoubleSelector); + map.Add(@"Min(IEnumerable`1, Func`2)->Decimal", SequenceMethod.MinDecimalSelector); + map.Add(@"Min(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MinNullableDecimalSelector); + map.Add(@"Min(IEnumerable`1, Func`2)->T1", SequenceMethod.MinSelector); + map.Add(@"Max(IEnumerable`1)->T0", SequenceMethod.Max); + map.Add(@"Max(IEnumerable`1, Func`2)->Int32", SequenceMethod.MaxIntSelector); + map.Add(@"Max(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MaxNullableIntSelector); + map.Add(@"Max(IEnumerable`1, Func`2)->Int64", SequenceMethod.MaxLongSelector); + map.Add(@"Max(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MaxNullableLongSelector); + map.Add(@"Max(IEnumerable`1, Func`2)->Single", SequenceMethod.MaxSingleSelector); + map.Add(@"Max(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MaxNullableSingleSelector); + map.Add(@"Max(IEnumerable`1, Func`2)->Double", SequenceMethod.MaxDoubleSelector); + map.Add(@"Max(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MaxNullableDoubleSelector); + map.Add(@"Max(IEnumerable`1, Func`2)->Decimal", SequenceMethod.MaxDecimalSelector); + map.Add(@"Max(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.MaxNullableDecimalSelector); + map.Add(@"Max(IEnumerable`1, Func`2)->T1", SequenceMethod.MaxSelector); + map.Add(@"Average(IEnumerable`1, Func`2)->Double", SequenceMethod.AverageIntSelector); + map.Add(@"Average(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.AverageNullableIntSelector); + map.Add(@"Average(IEnumerable`1, Func`2)->Double", SequenceMethod.AverageLongSelector); + map.Add(@"Average(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.AverageNullableLongSelector); + map.Add(@"Average(IEnumerable`1, Func`2)->Single", SequenceMethod.AverageSingleSelector); + map.Add(@"Average(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.AverageNullableSingleSelector); + map.Add(@"Average(IEnumerable`1, Func`2)->Double", SequenceMethod.AverageDoubleSelector); + map.Add(@"Average(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.AverageNullableDoubleSelector); + map.Add(@"Average(IEnumerable`1, Func`2)->Decimal", SequenceMethod.AverageDecimalSelector); + map.Add(@"Average(IEnumerable`1, Func`2>)->Nullable`1", SequenceMethod.AverageNullableDecimalSelector); + map.Add(@"Where(IEnumerable`1, Func`2)->IEnumerable`1", SequenceMethod.Where); + map.Add(@"Where(IEnumerable`1, Func`3)->IEnumerable`1", SequenceMethod.WhereOrdinal); + map.Add(@"Select(IEnumerable`1, Func`2)->IEnumerable`1", SequenceMethod.Select); + map.Add(@"Select(IEnumerable`1, Func`3)->IEnumerable`1", SequenceMethod.SelectOrdinal); + map.Add(@"SelectMany(IEnumerable`1, Func`2>)->IEnumerable`1", SequenceMethod.SelectMany); + map.Add(@"SelectMany(IEnumerable`1, Func`3>)->IEnumerable`1", SequenceMethod.SelectManyOrdinal); + map.Add(@"SelectMany(IEnumerable`1, Func`3>, Func`3)->IEnumerable`1", SequenceMethod.SelectManyOrdinalResultSelector); + map.Add(@"SelectMany(IEnumerable`1, Func`2>, Func`3)->IEnumerable`1", SequenceMethod.SelectManyResultSelector); + map.Add(@"Take(IEnumerable`1, Int32)->IEnumerable`1", SequenceMethod.Take); + map.Add(@"TakeWhile(IEnumerable`1, Func`2)->IEnumerable`1", SequenceMethod.TakeWhile); + map.Add(@"TakeWhile(IEnumerable`1, Func`3)->IEnumerable`1", SequenceMethod.TakeWhileOrdinal); + map.Add(@"Skip(IEnumerable`1, Int32)->IEnumerable`1", SequenceMethod.Skip); + map.Add(@"SkipWhile(IEnumerable`1, Func`2)->IEnumerable`1", SequenceMethod.SkipWhile); + map.Add(@"SkipWhile(IEnumerable`1, Func`3)->IEnumerable`1", SequenceMethod.SkipWhileOrdinal); + map.Add(@"Join(IEnumerable`1, IEnumerable`1, Func`2, Func`2, Func`3)->IEnumerable`1", SequenceMethod.Join); + map.Add(@"Join(IEnumerable`1, IEnumerable`1, Func`2, Func`2, Func`3, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.JoinComparer); + map.Add(@"GroupJoin(IEnumerable`1, IEnumerable`1, Func`2, Func`2, Func`3, T3>)->IEnumerable`1", SequenceMethod.GroupJoin); + map.Add(@"GroupJoin(IEnumerable`1, IEnumerable`1, Func`2, Func`2, Func`3, T3>, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.GroupJoinComparer); + map.Add(@"OrderBy(IEnumerable`1, Func`2)->IOrderedEnumerable`1", SequenceMethod.OrderBy); + map.Add(@"OrderBy(IEnumerable`1, Func`2, IComparer`1)->IOrderedEnumerable`1", SequenceMethod.OrderByComparer); + map.Add(@"OrderByDescending(IEnumerable`1, Func`2)->IOrderedEnumerable`1", SequenceMethod.OrderByDescending); + map.Add(@"OrderByDescending(IEnumerable`1, Func`2, IComparer`1)->IOrderedEnumerable`1", SequenceMethod.OrderByDescendingComparer); + map.Add(@"ThenBy(IOrderedEnumerable`1, Func`2)->IOrderedEnumerable`1", SequenceMethod.ThenBy); + map.Add(@"ThenBy(IOrderedEnumerable`1, Func`2, IComparer`1)->IOrderedEnumerable`1", SequenceMethod.ThenByComparer); + map.Add(@"ThenByDescending(IOrderedEnumerable`1, Func`2)->IOrderedEnumerable`1", SequenceMethod.ThenByDescending); + map.Add(@"ThenByDescending(IOrderedEnumerable`1, Func`2, IComparer`1)->IOrderedEnumerable`1", SequenceMethod.ThenByDescendingComparer); + map.Add(@"GroupBy(IEnumerable`1, Func`2)->IEnumerable`1>", SequenceMethod.GroupBy); + map.Add(@"GroupBy(IEnumerable`1, Func`2, IEqualityComparer`1)->IEnumerable`1>", SequenceMethod.GroupByComparer); + map.Add(@"GroupBy(IEnumerable`1, Func`2, Func`2)->IEnumerable`1>", SequenceMethod.GroupByElementSelector); + map.Add(@"GroupBy(IEnumerable`1, Func`2, Func`2, IEqualityComparer`1)->IEnumerable`1>", SequenceMethod.GroupByElementSelectorComparer); + map.Add(@"GroupBy(IEnumerable`1, Func`2, Func`3, T2>)->IEnumerable`1", SequenceMethod.GroupByResultSelector); + map.Add(@"GroupBy(IEnumerable`1, Func`2, Func`2, Func`3, T3>)->IEnumerable`1", SequenceMethod.GroupByElementSelectorResultSelector); + map.Add(@"GroupBy(IEnumerable`1, Func`2, Func`3, T2>, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.GroupByResultSelectorComparer); + map.Add(@"GroupBy(IEnumerable`1, Func`2, Func`2, Func`3, T3>, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.GroupByElementSelectorResultSelectorComparer); + map.Add(@"Concat(IEnumerable`1, IEnumerable`1)->IEnumerable`1", SequenceMethod.Concat); + map.Add(@"Distinct(IEnumerable`1)->IEnumerable`1", SequenceMethod.Distinct); + map.Add(@"Distinct(IEnumerable`1, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.DistinctComparer); + map.Add(@"Union(IEnumerable`1, IEnumerable`1)->IEnumerable`1", SequenceMethod.Union); + map.Add(@"Union(IEnumerable`1, IEnumerable`1, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.UnionComparer); + map.Add(@"Intersect(IEnumerable`1, IEnumerable`1)->IEnumerable`1", SequenceMethod.Intersect); + map.Add(@"Intersect(IEnumerable`1, IEnumerable`1, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.IntersectComparer); + map.Add(@"Except(IEnumerable`1, IEnumerable`1)->IEnumerable`1", SequenceMethod.Except); + map.Add(@"Except(IEnumerable`1, IEnumerable`1, IEqualityComparer`1)->IEnumerable`1", SequenceMethod.ExceptComparer); + map.Add(@"Reverse(IEnumerable`1)->IEnumerable`1", SequenceMethod.Reverse); + map.Add(@"SequenceEqual(IEnumerable`1, IEnumerable`1)->Boolean", SequenceMethod.SequenceEqual); + map.Add(@"SequenceEqual(IEnumerable`1, IEnumerable`1, IEqualityComparer`1)->Boolean", SequenceMethod.SequenceEqualComparer); + map.Add(@"AsEnumerable(IEnumerable`1)->IEnumerable`1", SequenceMethod.AsEnumerable); + map.Add(@"ToArray(IEnumerable`1)->TSource[]", SequenceMethod.NotSupported); + map.Add(@"ToList(IEnumerable`1)->List`1", SequenceMethod.ToList); + map.Add(@"ToDictionary(IEnumerable`1, Func`2)->Dictionary`2", SequenceMethod.NotSupported); + map.Add(@"ToDictionary(IEnumerable`1, Func`2, IEqualityComparer`1)->Dictionary`2", SequenceMethod.NotSupported); + map.Add(@"ToDictionary(IEnumerable`1, Func`2, Func`2)->Dictionary`2", SequenceMethod.NotSupported); + map.Add(@"ToDictionary(IEnumerable`1, Func`2, Func`2, IEqualityComparer`1)->Dictionary`2", SequenceMethod.NotSupported); + map.Add(@"ToLookup(IEnumerable`1, Func`2)->ILookup`2", SequenceMethod.NotSupported); + map.Add(@"ToLookup(IEnumerable`1, Func`2, IEqualityComparer`1)->ILookup`2", SequenceMethod.NotSupported); + map.Add(@"ToLookup(IEnumerable`1, Func`2, Func`2)->ILookup`2", SequenceMethod.NotSupported); + map.Add(@"ToLookup(IEnumerable`1, Func`2, Func`2, IEqualityComparer`1)->ILookup`2", SequenceMethod.NotSupported); + map.Add(@"DefaultIfEmpty(IEnumerable`1)->IEnumerable`1", SequenceMethod.DefaultIfEmpty); + map.Add(@"DefaultIfEmpty(IEnumerable`1, T0)->IEnumerable`1", SequenceMethod.DefaultIfEmptyValue); + map.Add(@"OfType(IEnumerable)->IEnumerable`1", SequenceMethod.OfType); + map.Add(@"Cast(IEnumerable)->IEnumerable`1", SequenceMethod.Cast); + map.Add(@"Range(Int32, Int32)->IEnumerable`1", SequenceMethod.NotSupported); + map.Add(@"Sum(IEnumerable`1)->Int32", SequenceMethod.SumInt); + map.Add(@"Sum(IEnumerable`1>)->Nullable`1", SequenceMethod.SumNullableInt); + map.Add(@"Sum(IEnumerable`1)->Int64", SequenceMethod.SumLong); + map.Add(@"Sum(IEnumerable`1>)->Nullable`1", SequenceMethod.SumNullableLong); + map.Add(@"Sum(IEnumerable`1)->Single", SequenceMethod.SumSingle); + map.Add(@"Sum(IEnumerable`1>)->Nullable`1", SequenceMethod.SumNullableSingle); + map.Add(@"Sum(IEnumerable`1)->Double", SequenceMethod.SumDouble); + map.Add(@"Sum(IEnumerable`1>)->Nullable`1", SequenceMethod.SumNullableDouble); + map.Add(@"Sum(IEnumerable`1)->Decimal", SequenceMethod.SumDecimal); + map.Add(@"Sum(IEnumerable`1>)->Nullable`1", SequenceMethod.SumNullableDecimal); + map.Add(@"Min(IEnumerable`1)->Int32", SequenceMethod.MinInt); + map.Add(@"Min(IEnumerable`1>)->Nullable`1", SequenceMethod.MinNullableInt); + map.Add(@"Min(IEnumerable`1)->Int64", SequenceMethod.MinLong); + map.Add(@"Min(IEnumerable`1>)->Nullable`1", SequenceMethod.MinNullableLong); + map.Add(@"Min(IEnumerable`1)->Single", SequenceMethod.MinSingle); + map.Add(@"Min(IEnumerable`1>)->Nullable`1", SequenceMethod.MinNullableSingle); + map.Add(@"Min(IEnumerable`1)->Double", SequenceMethod.MinDouble); + map.Add(@"Min(IEnumerable`1>)->Nullable`1", SequenceMethod.MinNullableDouble); + map.Add(@"Min(IEnumerable`1)->Decimal", SequenceMethod.MinDecimal); + map.Add(@"Min(IEnumerable`1>)->Nullable`1", SequenceMethod.MinNullableDecimal); + map.Add(@"Max(IEnumerable`1)->Int32", SequenceMethod.MaxInt); + map.Add(@"Max(IEnumerable`1>)->Nullable`1", SequenceMethod.MaxNullableInt); + map.Add(@"Max(IEnumerable`1)->Int64", SequenceMethod.MaxLong); + map.Add(@"Max(IEnumerable`1>)->Nullable`1", SequenceMethod.MaxNullableLong); + map.Add(@"Max(IEnumerable`1)->Double", SequenceMethod.MaxDouble); + map.Add(@"Max(IEnumerable`1>)->Nullable`1", SequenceMethod.MaxNullableDouble); + map.Add(@"Max(IEnumerable`1)->Single", SequenceMethod.MaxSingle); + map.Add(@"Max(IEnumerable`1>)->Nullable`1", SequenceMethod.MaxNullableSingle); + map.Add(@"Max(IEnumerable`1)->Decimal", SequenceMethod.MaxDecimal); + map.Add(@"Max(IEnumerable`1>)->Nullable`1", SequenceMethod.MaxNullableDecimal); + map.Add(@"Average(IEnumerable`1)->Double", SequenceMethod.AverageInt); + map.Add(@"Average(IEnumerable`1>)->Nullable`1", SequenceMethod.AverageNullableInt); + map.Add(@"Average(IEnumerable`1)->Double", SequenceMethod.AverageLong); + map.Add(@"Average(IEnumerable`1>)->Nullable`1", SequenceMethod.AverageNullableLong); + map.Add(@"Average(IEnumerable`1)->Single", SequenceMethod.AverageSingle); + map.Add(@"Average(IEnumerable`1>)->Nullable`1", SequenceMethod.AverageNullableSingle); + map.Add(@"Average(IEnumerable`1)->Double", SequenceMethod.AverageDouble); + map.Add(@"Average(IEnumerable`1>)->Nullable`1", SequenceMethod.AverageNullableDouble); + map.Add(@"Average(IEnumerable`1)->Decimal", SequenceMethod.AverageDecimal); + map.Add(@"Average(IEnumerable`1>)->Nullable`1", SequenceMethod.AverageNullableDecimal); + + // by redirection through canonical method names, determine sequence enum value + // for all know LINQ operators + s_methodMap = new Dictionary(EqualityComparer.Default); + s_inverseMap = new Dictionary(EqualityComparer.Default); + foreach (MethodInfo method in GetAllLinqOperators()) + { + SequenceMethod sequenceMethod; + string canonicalMethod = GetCanonicalMethodDescription(method); + if (map.TryGetValue(canonicalMethod, out sequenceMethod)) + { + s_methodMap.Add(method, sequenceMethod); + s_inverseMap[sequenceMethod] = method; + } + } + } + #endregion + + /// + /// Identifies methods as instances of known sequence operators. + /// + /// Method info to identify + /// Identified sequence operator + /// true if method is known; false otherwise + internal static bool TryIdentifySequenceMethod(MethodInfo method, out SequenceMethod sequenceMethod) + { + method = method.IsGenericMethod ? method.GetGenericMethodDefinition() : + method; + return s_methodMap.TryGetValue(method, out sequenceMethod); + } + + internal static bool IsSequenceMethod(MethodInfo method, SequenceMethod sequenceMethod) + { + bool result; + SequenceMethod foundSequenceMethod; + if (ReflectionUtil.TryIdentifySequenceMethod(method, out foundSequenceMethod)) + { + result = foundSequenceMethod == sequenceMethod; + } + else + { + result = false; + } + + return result; + } + + internal static bool IsSequenceSelectMethod(MethodInfo method) + { + SequenceMethod foundSequenceMethod; + if (ReflectionUtil.TryIdentifySequenceMethod(method, out foundSequenceMethod)) + { + switch (foundSequenceMethod) + { + case SequenceMethod.Select: + case SequenceMethod.SelectOrdinal: + case SequenceMethod.SelectMany: + case SequenceMethod.SelectManyOrdinal: + case SequenceMethod.SelectManyResultSelector: + case SequenceMethod.SelectManyOrdinalResultSelector: + return true; + } + } + + return false; + } + + /// + /// Check to see if this is an Any or an All method + /// + /// sequence method to check. + /// true if sequence method is Any or All, false otherwise. + internal static bool IsAnyAllMethod(SequenceMethod sequenceMethod) + { + return sequenceMethod == SequenceMethod.Any || + sequenceMethod == SequenceMethod.AnyPredicate || + sequenceMethod == SequenceMethod.All; + } + +#if false + /// + /// Identifies method call expressions as calls to known sequence operators. + /// + /// + /// Expression that may represent a call to a known sequence method + /// + /// + /// If true, and the argument is a LambdaExpression, + /// the Body of the LambdaExpression argument will be retrieved, and that expression will + /// then be examined for a sequence method call instead of the Lambda itself. + /// + /// + /// Identified sequence operator + /// + /// + /// true if is a + /// and its target method is known; false otherwise + /// + internal static bool TryIdentifySequenceMethod(Expression expression, bool unwrapLambda, out SequenceMethod sequenceMethod) + { + if (expression.NodeType == ExpressionType.Lambda && unwrapLambda) + { + expression = ((LambdaExpression)expression).Body; + } + + if (expression.NodeType == ExpressionType.Call) + { + MethodCallExpression methodCall = (MethodCallExpression)expression; + return ReflectionUtil.TryIdentifySequenceMethod(methodCall.Method, out sequenceMethod); + } + + sequenceMethod = default(SequenceMethod); + return false; + } + + /// + /// Looks up some implementation of a sequence method. + /// + /// Sequence method to find + /// Known method + /// true if some method is found; false otherwise + internal static bool TryLookupMethod(SequenceMethod sequenceMethod, out MethodInfo method) + { + return s_inverseMap.TryGetValue(sequenceMethod, out method); + } +#endif + + /// + /// Requires: + /// - no collisions on type names + /// - no output or reference method parameters + /// + /// + /// Produces a string description of a method consisting of the name and all parameters, + /// where all generic type parameters have been substituted with number identifiers. + /// + /// Method to identify. + /// Canonical description of method (suitable for lookup) + internal static string GetCanonicalMethodDescription(MethodInfo method) + { + // retrieve all generic type arguments and assign them numbers based on order + Dictionary genericArgumentOrdinals = null; + if (method.IsGenericMethodDefinition) + { + Predicate isGenericParameterPredicate = (type) => type.IsGenericParameter; + genericArgumentOrdinals = method.GetGenericArguments() + .Where(t => isGenericParameterPredicate(t)) + .Select((t, i) => new KeyValuePair(t, i)) + .ToDictionary(r => r.Key, r => r.Value); + } + + StringBuilder description = new StringBuilder(); + description.Append(method.Name).Append("("); + + // append types for all method parameters + bool first = true; + foreach (ParameterInfo parameter in method.GetParameters()) + { + if (first) { first = false; } + else { description.Append(", "); } + AppendCanonicalTypeDescription(parameter.ParameterType, genericArgumentOrdinals, description); + } + + description.Append(")"); + + // include return type + if (null != method.ReturnType) + { + description.Append("->"); + AppendCanonicalTypeDescription(method.ReturnType, genericArgumentOrdinals, description); + } + + return description.ToString(); + } + + private static void AppendCanonicalTypeDescription(Type type, Dictionary genericArgumentOrdinals, StringBuilder description) + { + int ordinal; + + // if this a type argument for the method, substitute + if (null != genericArgumentOrdinals && genericArgumentOrdinals.TryGetValue(type, out ordinal)) + { + description.Append("T").Append(ordinal.ToString(CultureInfo.InvariantCulture)); + + return; + } + + // always include the name (note: we omit the namespace/assembly; assuming type names do not collide) + description.Append(type.Name); + + if (type.IsGenericType()) + { + description.Append("<"); + bool first = true; + foreach (Type genericArgument in type.GetGenericArguments()) + { + if (first) { first = false; } + else { description.Append(", "); } + AppendCanonicalTypeDescription(genericArgument, genericArgumentOrdinals, description); + } + description.Append(">"); + } + } + + /// + /// Returns all static methods in the Queryable and Enumerable classes. + /// + internal static IEnumerable GetAllLinqOperators() + { + return typeof(Queryable).GetPublicStaticMethods().Concat( + typeof(Enumerable).GetPublicStaticMethods()); + } + } + + /// + /// Enumeration of known extension methods + /// + internal enum SequenceMethod + { + Where, + WhereOrdinal, + OfType, + Cast, + Select, + SelectOrdinal, + SelectMany, + SelectManyOrdinal, + SelectManyResultSelector, + SelectManyOrdinalResultSelector, + Join, + JoinComparer, + GroupJoin, + GroupJoinComparer, + OrderBy, + OrderByComparer, + OrderByDescending, + OrderByDescendingComparer, + ThenBy, + ThenByComparer, + ThenByDescending, + ThenByDescendingComparer, + Take, + TakeWhile, + TakeWhileOrdinal, + Skip, + SkipWhile, + SkipWhileOrdinal, + GroupBy, + GroupByComparer, + GroupByElementSelector, + GroupByElementSelectorComparer, + GroupByResultSelector, + GroupByResultSelectorComparer, + GroupByElementSelectorResultSelector, + GroupByElementSelectorResultSelectorComparer, + Distinct, + DistinctComparer, + Concat, + Union, + UnionComparer, + Intersect, + IntersectComparer, + Except, + ExceptComparer, + First, + FirstPredicate, + FirstOrDefault, + FirstOrDefaultPredicate, + Last, + LastPredicate, + LastOrDefault, + LastOrDefaultPredicate, + Single, + SinglePredicate, + SingleOrDefault, + SingleOrDefaultPredicate, + ElementAt, + ElementAtOrDefault, + DefaultIfEmpty, + DefaultIfEmptyValue, + Contains, + ContainsComparer, + Reverse, + Empty, + SequenceEqual, + SequenceEqualComparer, + + Any, + AnyPredicate, + All, + + Count, + CountPredicate, + LongCount, + LongCountPredicate, + + Min, + MinSelector, + Max, + MaxSelector, + + MinInt, + MinNullableInt, + MinLong, + MinNullableLong, + MinDouble, + MinNullableDouble, + MinDecimal, + MinNullableDecimal, + MinSingle, + MinNullableSingle, + MinIntSelector, + MinNullableIntSelector, + MinLongSelector, + MinNullableLongSelector, + MinDoubleSelector, + MinNullableDoubleSelector, + MinDecimalSelector, + MinNullableDecimalSelector, + MinSingleSelector, + MinNullableSingleSelector, + + MaxInt, + MaxNullableInt, + MaxLong, + MaxNullableLong, + MaxDouble, + MaxNullableDouble, + MaxDecimal, + MaxNullableDecimal, + MaxSingle, + MaxNullableSingle, + MaxIntSelector, + MaxNullableIntSelector, + MaxLongSelector, + MaxNullableLongSelector, + MaxDoubleSelector, + MaxNullableDoubleSelector, + MaxDecimalSelector, + MaxNullableDecimalSelector, + MaxSingleSelector, + MaxNullableSingleSelector, + + SumInt, + SumNullableInt, + SumLong, + SumNullableLong, + SumDouble, + SumNullableDouble, + SumDecimal, + SumNullableDecimal, + SumSingle, + SumNullableSingle, + SumIntSelector, + SumNullableIntSelector, + SumLongSelector, + SumNullableLongSelector, + SumDoubleSelector, + SumNullableDoubleSelector, + SumDecimalSelector, + SumNullableDecimalSelector, + SumSingleSelector, + SumNullableSingleSelector, + + AverageInt, + AverageNullableInt, + AverageLong, + AverageNullableLong, + AverageDouble, + AverageNullableDouble, + AverageDecimal, + AverageNullableDecimal, + AverageSingle, + AverageNullableSingle, + AverageIntSelector, + AverageNullableIntSelector, + AverageLongSelector, + AverageNullableLongSelector, + AverageDoubleSelector, + AverageNullableDoubleSelector, + AverageDecimalSelector, + AverageNullableDecimalSelector, + AverageSingleSelector, + AverageNullableSingleSelector, + + Aggregate, + AggregateSeed, + AggregateSeedSelector, + + AsQueryable, + AsQueryableGeneric, + AsEnumerable, + + ToList, + + NotSupported, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/RemoveWildcardVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/RemoveWildcardVisitor.cs new file mode 100644 index 0000000..5cf30b1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/RemoveWildcardVisitor.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using Microsoft.OData.Client.ALinq.UriParser; + + /// + /// Remove any wildcard characters from a given path. + /// + internal class RemoveWildcardVisitor : IPathSegmentTokenVisitor + { + /// + /// The previous token, used as a cursor so that we can cut off the + /// next pointer of the previous token if we find a wildcard. + /// + private PathSegmentToken previous = null; + + /// + /// Translate a SystemToken, this is illegal so this always throws. + /// + /// The SystemToken to translate. + public void Visit(SystemToken tokenIn) + { + throw new NotSupportedException(Strings.ALinq_IllegalSystemQueryOption(tokenIn.Identifier)); + } + + /// + /// Translate a NonSystemToken. + /// + /// The NonSystemToken to translate. + public void Visit(NonSystemToken tokenIn) + { + if (tokenIn.Identifier != UriHelper.ASTERISK.ToString()) + { + if (tokenIn.NextToken == null) + { + return; + } + + previous = tokenIn; + tokenIn.NextToken.Accept(this); + } + else + { + previous.SetNextToken(null); + return; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceBinder.cs new file mode 100644 index 0000000..0e74942 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceBinder.cs @@ -0,0 +1,3027 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.OData.Client.ALinq.UriParser; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + using PathSegmentToken = Microsoft.OData.Client.ALinq.UriParser.PathSegmentToken; + using NonSystemToken = Microsoft.OData.Client.ALinq.UriParser.NonSystemToken; + #endregion Namespaces + + /// + /// This class provides a Bind method that analyzes an input and returns a bound tree. + /// + internal class ResourceBinder : DataServiceALinqExpressionVisitor + { + private const string AddQueryOptionMethodName = "AddQueryOption"; + + private const string ExpandMethodName = "Expand"; + + private const string IncludeTotalCountMethodName = "IncludeTotalCount"; + + /// + /// The associated DataServiceContext instance. DevNote(shank): this is used for determining + /// the fully-qualified name of types when TypeAs converts are processed (C# "as", VB "TryCast"). + /// Ideally the FQN is only required during URI translation, not during analysis. However, + /// the current code constructs the $select and $expand parts of the URI during analysis. This + /// could be refactored in the future to defer the $select and $expand URI construction until + /// the URI translation phase. + /// + private readonly DataServiceContext context; + + /// Convenience property: model. + private ClientEdmModel Model + { + get + { + return this.context.Model; + } + } + + + private ResourceBinder(DataServiceContext context) + { + this.context = context; + } + + /// Analyzes and binds the specified expression. + /// Expression to bind. + /// The context of the expression used for bind. + /// + /// The expression with bound nodes (annotated expressions used by + /// the Expression-to-URI translator). + /// + internal static Expression Bind(Expression e, DataServiceContext context) + { + Debug.Assert(e != null, "e != null"); + + ResourceBinder rb = new ResourceBinder(context); + Expression boundExpression = rb.Visit(e); + VerifyKeyPredicates(boundExpression); + VerifyNotSelectManyProjection(boundExpression); + return boundExpression; + } + + /// + /// Checks whether the specified is + /// missing necessary key predicates. + /// + /// Expression to check. + /// + /// true if the expression is a navigation expression and doesn't + /// have the necessary key predicates on the associated resource + /// expression; false otherwise. + /// + internal static bool IsMissingKeyPredicates(Expression expression) + { + ResourceExpression re = expression as ResourceExpression; + if (re != null) + { + if (IsMissingKeyPredicates(re.Source)) + { + return true; + } + + if (re.Source != null && !re.IsOperationInvocation) + { + ResourceSetExpression rse = re.Source as ResourceSetExpression; + if ((rse != null) && !rse.HasKeyPredicate) + { + return true; + } + } + } + + return false; + } + + /// + /// Verifies that all key predicates are assigned to the specified expression. + /// + /// Expression to verify. + internal static void VerifyKeyPredicates(Expression e) + { + if (IsMissingKeyPredicates(e)) + { + throw new NotSupportedException(Strings.ALinq_CantNavigateWithoutKeyPredicate); + } + } + + /// Verifies that the specified is not a projection based on SelectMany. + /// Expression to check. + internal static void VerifyNotSelectManyProjection(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + + // Check that there isn't a SelectMany projection (or if there is one, + // that there isn't an associated transparent scope for the resource + // set reference). + QueryableResourceExpression resourceExpression = expression as QueryableResourceExpression; + if (resourceExpression != null) + { + ProjectionQueryOptionExpression projection = resourceExpression.Projection; + if (projection != null) + { + Debug.Assert(projection.Selector != null, "projection.Selector != null -- otherwise incorrectly constructed"); + MethodCallExpression call = StripTo(projection.Selector.Body); + if (call != null && call.Method.Name == "SelectMany") + { + throw new NotSupportedException(Strings.ALinq_UnsupportedExpression(call)); + } + } + else if (resourceExpression.HasTransparentScope) + { + throw new NotSupportedException(Strings.ALinq_UnsupportedExpression(resourceExpression)); + } + } + } + + /// Analyzes a predicate (Where clause). + /// for a Where call. + /// The model. + /// + /// An equivalent expression to , possibly a different one with additional annotations. + /// + private static Expression AnalyzePredicate(MethodCallExpression mce, ClientEdmModel model) + { + Debug.Assert(mce != null, "mce != null -- caller couldn't have know the expression kind otherwise"); + Debug.Assert(mce.Method.Name == "Where", "mce.Method.Name == 'Where' -- otherwise this isn't a predicate"); + + // Validate that the input is a resource set and retrieve the Lambda that defines the predicate + QueryableResourceExpression input; + LambdaExpression le; + if (!TryGetResourceSetMethodArguments(mce, out input, out le)) + { + // might have failed because of singleton, so throw better error if so. + ValidationRules.RequireNonSingleton(mce.Arguments[0]); + return mce; + } + + ValidationRules.CheckPredicate(le.Body, model); + + // + // Valid predicate patterns are as follows: + // 1. A URI-compatible filter applied to the input resource set + // 2. A key-predicate filter applied to the input resource set + // - Additionally, key-predicate filters may be applied to any resource path component + // that does not already have a key-predicate filter, regardless of any filter applied + // to the current input resource set, if transparent scopes are present. + // - It is not valid to apply a key-predicate or a filter to a resource set + // for which key-predicate is already present. + // - It is valid to apply a filter to a resource set for which a filter already exists; + // such filters are AND'd together. + // + // [Key-predicate that targets a path component AND]* [Key-predicate over input | URI-compatible filter over input]+ + + List conjuncts = new List(); + AddConjuncts(le.Body, conjuncts); + + Dictionary> predicatesByTarget = new Dictionary>(ReferenceEqualityComparer.Instance); + List referencedInputs = new List(); + foreach (Expression e in conjuncts) + { + ValidateFilter(e, mce.Method.Name); + Expression reboundPredicate = InputBinder.Bind(e, input, le.Parameters[0], referencedInputs); + if (referencedInputs.Count > 1) + { + // UNSUPPORTED: A single clause cannot refer to more than one resource set + return mce; + } + + QueryableResourceExpression boundTarget = (referencedInputs.Count == 0 ? input : referencedInputs[0] as QueryableResourceExpression); + if (boundTarget == null) + { + // UNSUPPORTED: Each clause must refer to a path component that is a resource set, not a singleton navigation property + return mce; + } + + List targetPredicates = null; + if (!predicatesByTarget.TryGetValue(boundTarget, out targetPredicates)) + { + targetPredicates = new List(); + predicatesByTarget[boundTarget] = targetPredicates; + } + + targetPredicates.Add(reboundPredicate); + referencedInputs.Clear(); + } + + conjuncts = null; + List inputPredicates; + if (predicatesByTarget.TryGetValue(input, out inputPredicates)) + { + predicatesByTarget.Remove(input); + } + else + { + inputPredicates = null; + } + + foreach (KeyValuePair> predicates in predicatesByTarget) + { + QueryableResourceExpression target = predicates.Key; + List clauses = predicates.Value; + List nonKeyPredicates; + List keyPredicates = ExtractKeyPredicate(target, clauses, model, out nonKeyPredicates); + if (keyPredicates == null || + (nonKeyPredicates != null && nonKeyPredicates.Count > 0)) + { + // UNSUPPORTED: Only key predicates may be applied to earlier path components + return mce; + } + + // Earlier path components must be navigation sources, and navigation sources cannot have query options. + Debug.Assert(!target.HasQueryOptions, "Navigation source had query options?"); + + target.SetKeyPredicate(keyPredicates); + } + + if (inputPredicates != null) + { + List keyPredicates = null; + List nonKeyPredicates = null; + + // Get the current key predicates + List currentPredicates = input.KeyPredicateConjuncts.ToList(); + + if (input.Filter != null && input.Filter.PredicateConjuncts.Count > 0) + { + // Get the filter predicates + currentPredicates = currentPredicates.Union(input.Filter.PredicateConjuncts.Union(inputPredicates)).ToList(); + } + else + { + currentPredicates = currentPredicates.Union(inputPredicates).ToList(); + } + + if (!input.UseFilterAsPredicate) + { + keyPredicates = ExtractKeyPredicate(input, currentPredicates, model, out nonKeyPredicates); + } + + if (keyPredicates != null) + { + input.SetKeyPredicate(keyPredicates); + input.RemoveFilterExpression(); + } + + // A key predicate cannot be applied if query options other than 'Expand' are present, + // so merge the key predicate into the filter query option instead. + if (keyPredicates != null && input.HasSequenceQueryOptions) + { + input.ConvertKeyToFilterExpression(); + } + + if (keyPredicates == null) + { + input.ConvertKeyToFilterExpression(); + input.AddFilter(inputPredicates); + } + } + + return input; // No need to adjust this.currentResource - filters are merged in all cases + } + + /// + /// Validates neither operands of the binary expression filter ends with TypeAs + /// + /// filter expression + /// method name where this filter is passed to + private static void ValidateFilter(Expression exp, string method) + { + Debug.Assert(exp != null && exp.Type == typeof(Boolean), "exp != null && exp.Type == typeof(Boolean)"); + BinaryExpression e = ResourceBinder.StripTo(exp) as BinaryExpression; + if (e != null) + { + ValidationRules.DisallowExpressionEndWithTypeAs(e.Left, method); + ValidationRules.DisallowExpressionEndWithTypeAs(e.Right, method); + } + } + + /// + /// Given a list of predicates, extracts key values for the specified . + /// + /// Target set. + /// Candidate predicates. + /// Returns list of non-key predicates. + /// List of key predicates if found, otherwise null + private static List ExtractKeyPredicate( + QueryableResourceExpression target, + List predicates, + ClientEdmModel edmModel, + out List nonKeyPredicates) + { + Debug.Assert(target != null, "target != null"); + Debug.Assert(predicates != null, "predicates != null"); + + Dictionary keyValuesFromPredicates = null; + nonKeyPredicates = null; + List keyPredicates = null; + + foreach (Expression predicate in predicates) + { + PropertyInfo property; + ConstantExpression constantValue; + if (PatternRules.MatchKeyComparison(predicate, out property, out constantValue)) + { + if (keyValuesFromPredicates == null) + { + keyValuesFromPredicates = new Dictionary(EqualityComparer.Default); + keyPredicates = new List(); + } + else if (keyValuesFromPredicates.ContainsKey(property)) + { + // UNSUPPORTED: = AND = are multiple key predicates and + // cannot be represented as a resource path. + throw Error.NotSupported(Strings.ALinq_CanOnlyApplyOneKeyPredicate); + } + + keyValuesFromPredicates.Add(property, constantValue); + keyPredicates.Add(predicate); + } + else + { + if (nonKeyPredicates == null) + { + nonKeyPredicates = new List(); + } + + nonKeyPredicates.Add(predicate); + } + } + + if (nonKeyPredicates != null && nonKeyPredicates.Count > 0) + { + // If there is any non-key predicate, everything must go in filter statement, + target.UseFilterAsPredicate = true; + keyPredicates = null; + } + else + { + Debug.Assert(keyValuesFromPredicates != null || nonKeyPredicates != null, "No key predicates or non-key predicates found?"); + if (keyValuesFromPredicates != null) + { + // Get the EDM schema type for target's Resource Type + var schemaType = (IEdmEntityType)edmModel.GetOrCreateEdmType(target.CreateReference().Type); + + Debug.Assert(schemaType != null, "SchemaType can not be null."); + + // Obtain the key properties from EdmEntityType, and compare them with keys obtained from predicates + // By this time we are sure that keyValuesFromPredicates has unique set (i.e. no duplicates), and has all the keys in it. + // So just comparing count with EDMModel's keys is enough for validation + bool allKeysPresent = schemaType.Key().Count() == keyValuesFromPredicates.Keys.Count; + + if (!allKeysPresent) + { + keyValuesFromPredicates = null; + keyPredicates = null; + } + } + } + + return keyPredicates; + } + + /// Adds all AND'ed expressions to the specified list. + /// Expression to recursively add conjuncts from. + /// Target list of conjucts. + private static void AddConjuncts(Expression e, List conjuncts) + { + Debug.Assert(conjuncts != null, "conjuncts != null"); + if (PatternRules.MatchAnd(e)) + { + BinaryExpression be = (BinaryExpression)e; + AddConjuncts(be.Left, conjuncts); + AddConjuncts(be.Right, conjuncts); + } + else + { + conjuncts.Add(e); + } + } + + /// + /// Analyzes the specified call to see whether it is recognized as a + /// projection that is satisfied with $select usage. + /// + /// Call expression to analyze. + /// Kind of sequence method to analyze. + /// Resulting expression. + /// true if is a projection that can be satisfied with $select; false otherwise. + internal bool AnalyzeProjection(MethodCallExpression mce, SequenceMethod sequenceMethod, out Expression e) + { + Debug.Assert(mce != null, "mce != null"); + Debug.Assert( + sequenceMethod == SequenceMethod.Select || sequenceMethod == SequenceMethod.SelectManyResultSelector, + "sequenceMethod == SequenceMethod.Select(ManyResultSelector)"); + + e = mce; + + bool matchMembers = sequenceMethod == SequenceMethod.SelectManyResultSelector; + ResourceExpression source = this.Visit(mce.Arguments[0]) as ResourceExpression; + if (source == null) + { + return false; + } + + if (sequenceMethod == SequenceMethod.SelectManyResultSelector) + { + // The processing for SelectMany for a projection is similar to that + // of a regular Select as a projection, however in the latter the + // signature is .Select(source, selector), whereas in the former + // the signature is .SelectMany(source, collector, selector), where + // the selector's source is the result of the collector. + // + // Only simple collectors (single member access) are supported. + Expression collectionSelector = mce.Arguments[1]; + if (!PatternRules.MatchParameterMemberAccess(collectionSelector)) + { + return false; + } + + Expression resultSelector = mce.Arguments[2]; + LambdaExpression resultLambda; + if (!PatternRules.MatchDoubleArgumentLambda(resultSelector, out resultLambda)) + { + return false; + } + + if (ExpressionPresenceVisitor.IsExpressionPresent(resultLambda.Parameters[0], resultLambda.Body)) + { + return false; + } + + // Build a version of the collection body that has transparent identifiers + // resolved and create a new resource reference for the navigation collection; + // this is the source for the selector. + List referencedExpressions = new List(); + LambdaExpression collectionLambda = StripTo(collectionSelector); + Expression collectorReference = InputBinder.Bind(collectionLambda.Body, source, collectionLambda.Parameters[0], referencedExpressions); + collectorReference = StripCastMethodCalls(collectorReference); + MemberExpression setNavigationMember; + if (!PatternRules.MatchPropertyProjectionRelatedSet(source, collectorReference, this.context, out setNavigationMember)) + { + return false; + } + + ResourceExpression resultSelectorSource = CreateResourceSetExpression(mce.Method.ReturnType, source, setNavigationMember, TypeSystem.GetElementType(setNavigationMember.Type)); + + if (!PatternRules.MatchMemberInitExpressionWithDefaultConstructor(resultSelectorSource, resultLambda) && + !PatternRules.MatchNewExpression(resultSelectorSource, resultLambda)) + { + return false; + } + + resultLambda = Expression.Lambda(resultLambda.Body, new ParameterExpression[] { resultLambda.Parameters[1] }); + + // Ideally, the projection analyzer would return true/false and an + // exception with the explanation of the results if it failed; + // however to minimize churn we will use a try/catch on + // NotSupportedException instead. + ResourceExpression resultWithProjection = resultSelectorSource.CreateCloneWithNewType(mce.Type); + bool isProjection; + try + { + isProjection = ProjectionAnalyzer.Analyze(resultLambda, resultWithProjection, false, this.context); + } + catch (NotSupportedException) + { + isProjection = false; + } + + if (!isProjection) + { + return false; + } + + e = resultWithProjection; + ValidationRules.RequireCanProject(resultSelectorSource); + } + else + { + LambdaExpression lambda; + if (!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out lambda)) + { + return false; + } + + // the projection might be over a transparent identifier, so first try to rewrite if that is the case + lambda = ProjectionRewriter.TryToRewrite(lambda, source); + + ResourceExpression re = source.CreateCloneWithNewType(mce.Type); + + // See whether the lambda matches a projection that is satisfied with $select usage. + if (!ProjectionAnalyzer.Analyze(lambda, re, matchMembers, this.context)) + { + return false; + } + + // Defer validating until after the projection has been analyzed since the lambda could represent a + // navigation that we do not want to turn into a projection. + ValidationRules.RequireCanProject(source); + e = re; + } + + return true; + } + + /// + /// Analyzes the specified method call as a WCF Data + /// Services navigation operation. + /// + /// Expression to analyze. + /// Data service context instance + /// An expression that represents the potential navigation. + internal static Expression AnalyzeNavigation(MethodCallExpression mce, DataServiceContext context) + { + Debug.Assert(mce != null, "mce != null"); + Expression input = mce.Arguments[0]; + LambdaExpression le; + ResourceExpression navSource; + Expression boundProjection; + MemberExpression navigationMember; + + if (!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le)) + { + return mce; + } + + ValidationRules.DisallowExpressionEndWithTypeAs(le.Body, mce.Method.Name); + if (PatternRules.MatchIdentitySelector(le)) + { + return input; + } + else if (PatternRules.MatchTransparentIdentitySelector(input, le, context)) + { + return RemoveTransparentScope(mce.Method.ReturnType, (QueryableResourceExpression)input); + } + else if (IsValidNavigationSource(input, out navSource) && + TryBindToInput(navSource, le, out boundProjection) && + PatternRules.MatchPropertyProjectionSingleton(navSource, boundProjection, context, out navigationMember)) + { + ValidationRules.DisallowMemberAccessInNavigation(navigationMember, context.Model); + boundProjection = navigationMember; + return CreateNavigationPropertySingletonExpression(mce.Method.ReturnType, navSource, boundProjection); + } + + return mce; + } + + private static bool IsValidNavigationSource(Expression input, out ResourceExpression sourceExpression) + { + ValidationRules.RequireCanNavigate(input); + sourceExpression = input as ResourceExpression; + return sourceExpression != null; + } + + /// + /// Analyzes a .Select or .SelectMany method call to determine + /// whether it's allowed, to identify transparent identifiers + /// in appropriate .SelectMany() cases, returning the method + /// call or a resource set expression. + /// + /// Expression to analyze. + /// Data service context instance + /// + /// , or a new resource set expression for + /// the target resource in the method call for navigation properties. + /// + internal static Expression AnalyzeSelectMany(MethodCallExpression mce, DataServiceContext context) + { + Debug.Assert(mce != null, "mce != null"); + + if (mce.Arguments.Count != 2 && mce.Arguments.Count != 3) + { + return mce; + } + + ResourceExpression input; + if (!IsValidNavigationSource(mce.Arguments[0], out input)) + { + return mce; + } + + LambdaExpression collectorSelector; + if (!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out collectorSelector)) + { + return mce; + } + + ValidationRules.DisallowExpressionEndWithTypeAs(collectorSelector.Body, mce.Method.Name); + List referencedInputs = new List(); + Expression navPropRef = InputBinder.Bind(collectorSelector.Body, input, collectorSelector.Parameters[0], referencedInputs); + + QueryableResourceExpression rse; + if (!TryAnalyzeSelectManyCollector(input, navPropRef, context, out rse)) + { + return mce; + } + else if (rse.Type != mce.Method.ReturnType) + { + rse = rse.CreateCloneForTransparentScope(mce.Method.ReturnType); + } + + if (mce.Arguments.Count == 3) + { + return AnalyzeSelectManySelector(mce, rse, context); + } + else + { + return rse; + } + } + + /// + /// Analyzes the nav prop reference for a SelectMany method call + /// building the appropriate ResourceSetExpression if one can be + /// created. + /// + /// Input resource expression to the collector + /// The navigation property reference to analyze + /// Data service context instance + /// The resource set expression + /// true if succesful, else false + private static bool TryAnalyzeSelectManyCollector(ResourceExpression input, Expression navPropRef, DataServiceContext context, out QueryableResourceExpression result) + { + MethodCallExpression call = StripTo(navPropRef); + + SequenceMethod sequenceMethod; + + if (call != null && ReflectionUtil.TryIdentifySequenceMethod(call.Method, out sequenceMethod) && + (sequenceMethod == SequenceMethod.Cast || sequenceMethod == SequenceMethod.OfType)) + { + // Recursively analyze Cast<> and OfType<> inputs + if (TryAnalyzeSelectManyCollector(input, call.Arguments[0], context, out result)) + { + call = Expression.Call(call.Method, result); + if (sequenceMethod == SequenceMethod.Cast) + { + result = AnalyzeCast(call) as QueryableResourceExpression; + } + else + { + result = AnalyzeOfType(call) as QueryableResourceExpression; + } + } + else + { + result = null; + } + } + else + { + MemberExpression setNavigationMember; + if (PatternRules.MatchPropertyProjectionRelatedSet(input, navPropRef, context, out setNavigationMember)) + { + // Ground case for success - we have a member access from the input resource set + Type memberType = TypeSystem.GetElementType(setNavigationMember.Type); + result = CreateResourceSetExpression(setNavigationMember.Type, input, setNavigationMember, memberType); + } + else + { + result = null; + } + } + return result != null; + } + + /// + /// Analyzes a SelectMany method call that ranges over a resource set and + /// returns the same method or the annotated resource set. + /// + /// SelectMany method to analyze. + /// Source resource set for SelectMany result. + /// Context of expression to analyze. + /// The visited expression. + /// + /// The expression represents the + /// navigation produced by the collector of the SelectMany() method call. + /// + private static Expression AnalyzeSelectManySelector(MethodCallExpression selectManyCall, QueryableResourceExpression sourceResource, DataServiceContext context) + { + Debug.Assert(selectManyCall != null, "selectManyCall != null"); + + LambdaExpression selector = StripTo(selectManyCall.Arguments[2]); + + // Check for transparent scope result - projects the input and the selector + Expression result; + QueryableResourceExpression.TransparentAccessors transparentScope; + if (PatternRules.MatchTransparentScopeSelector(sourceResource, selector, out transparentScope)) + { + sourceResource.TransparentScope = transparentScope; + result = sourceResource; + } + else if (PatternRules.MatchIdentityProjectionResultSelector(selector)) + { + result = sourceResource; + } + else if (PatternRules.MatchMemberInitExpressionWithDefaultConstructor(sourceResource, selector) || PatternRules.MatchNewExpression(sourceResource, selector)) + { + // Projection analyzer will throw if it selector references first ParamExpression, so this is safe to do here. + selector = Expression.Lambda(selector.Body, new ParameterExpression[] { selector.Parameters[1] }); + + if (!ProjectionAnalyzer.Analyze(selector, sourceResource, false, context)) + { + result = selectManyCall; + } + else + { + result = sourceResource; + } + } + else + { + result = selectManyCall; + } + + return result; + } + + internal static Expression ApplyOrdering(MethodCallExpression mce, bool descending, bool thenBy, ClientEdmModel model) + { + ValidationRules.CheckOrderBy(mce, model); + + QueryableResourceExpression input; + LambdaExpression le; + if (!TryGetResourceSetMethodArguments(mce, out input, out le)) + { + // UNSUPPORTED: Expected LambdaExpression as second argument to sequence method + return mce; + } + + ValidationRules.DisallowExpressionEndWithTypeAs(le.Body, mce.Method.Name); + Expression selector; + if (!TryBindToInput(input, le, out selector)) + { + // UNSUPPORTED: Lambda should reference the input, and only the input + return mce; + } + List selectors; + if (!thenBy) + { + selectors = new List(); + AddSequenceQueryOption(input, new OrderByQueryOptionExpression(mce.Type, selectors)); + } + else + { + Debug.Assert(input.OrderBy != null, "input.OrderBy != null"); + selectors = input.OrderBy.Selectors; + } + + selectors.Add(new OrderByQueryOptionExpression.Selector(selector, descending)); + + // The ResourceSetExpression 'input' can be of type IQueryable (for example after OfType() is called). Since OrderBy returns + // IOrderedQueryable, we need to change the ResourceSetExpression to IOrderedQueryable so that ThenBy can compose correctly after this. + if (input.Type != mce.Method.ReturnType) + { + return input.CreateCloneWithNewType(mce.Method.ReturnType); + } + + return input; + } + + /// Ensures that there's a limit on the cardinality of a query. + /// for the method to limit First/Single(OrDefault). + /// Maximum cardinality to allow. + /// + /// An expression that limits to no more than elements. + /// + /// This method is used by .First(OrDefault) and .Single(OrDefault) to limit cardinality. + private static Expression LimitCardinality(MethodCallExpression mce, int maxCardinality) + { + Debug.Assert(mce != null, "mce != null"); + Debug.Assert(maxCardinality > 0, "Cardinality must be at least 1"); + + if (mce.Arguments.Count != 1) + { + // Don't support methods with predicates. + return mce; + } + + ResourceSetExpression rse = mce.Arguments[0] as ResourceSetExpression; + if (rse != null) + { + if (!rse.HasKeyPredicate && // no-op, already returns a singleton + (ResourceExpressionType)rse.NodeType != ResourceExpressionType.ResourceNavigationProperty) + { + if (rse.Take == null || (int)rse.Take.TakeAmount.Value > maxCardinality) + { + AddSequenceQueryOption(rse, new TakeQueryOptionExpression(mce.Type, Expression.Constant(maxCardinality))); + } + } + return mce.Arguments[0]; + } + else if (mce.Arguments[0] is NavigationPropertySingletonExpression || mce.Arguments[0] is SingletonResourceExpression) + { + // no-op, already returns a singleton + return mce.Arguments[0]; + } + + return mce; + } + + private static Expression AnalyzeCast(MethodCallExpression mce) + { + ResourceExpression re = mce.Arguments[0] as ResourceExpression; + if (re != null) + { + return re.CreateCloneWithNewType(mce.Method.ReturnType); + } + + return mce; + } + + /// + /// Analyzes the OfType method call + /// + /// The OfType method call expression + /// , or a resource set expression with ResourceTypeAs set. + private static Expression AnalyzeOfType(MethodCallExpression mce) + { + QueryableResourceExpression rse = mce.Arguments[0] as QueryableResourceExpression; + if (rse != null) + { + Type filteredType; + filteredType = mce.Method.GetGenericArguments().SingleOrDefault(); + + if (filteredType == null) + { + throw new InvalidOperationException(Strings.ALinq_OfTypeArgumentNotAvailable); + } + + if (filteredType.IsAssignableFrom(rse.ResourceType)) + { + return rse; + } + else if (rse.ResourceType.IsAssignableFrom(filteredType)) + { + if (rse.ResourceTypeAs != null) + { + throw new NotSupportedException(Strings.ALinq_CannotUseTypeFiltersMultipleTimes); + } + + // only apply the OfType filter if it is not redundant + rse.ResourceTypeAs = filteredType; + return rse.CreateCloneWithNewType(mce.Method.ReturnType); + } + } + + return mce; + } + + private static bool IsTypeOfGenericBaseType(Type baseType, Type derivedType) + { + while (derivedType != null && derivedType != typeof(object)) + { + var currentType = derivedType.IsGenericType() ? derivedType.GetGenericTypeDefinition() : derivedType; + if (currentType == baseType) + { + return true; + } + + derivedType = derivedType.GetBaseType(); + } + + return false; + } + + private static Expression StripConvert(Expression e, Type selfDefinedConvertType) + { + UnaryExpression ue = e as UnaryExpression; + + // TODO: We are going to allow either of DataServiceQuery or DataServiceOrderedQuery + // to be the type of ResourceExpression in the cast parameter. Although this might be considered + // overly relaxed we want to avoid causing breaking changes by just having the Ordered version + if (ue != null && ue.NodeType == ExpressionType.Convert && ue.Type.GetBaseType() == typeof(DataServiceContext)) + { + e = ue.Operand; + } + else + { + if (ue != null && ue.NodeType == ExpressionType.Convert && + IsTypeOfGenericBaseType(typeof(DataServiceQuery<>), ue.Type)) + { + e = ue.Operand; + ResourceExpression re = e as ResourceExpression; + if (re != null && selfDefinedConvertType != null) + { + e = re.CreateCloneWithNewType(selfDefinedConvertType); + } + else if (re != null) + { + e = re.CreateCloneWithNewType(ue.Type); + } + } + } + + return e; + } + + private static Expression AnalyzeExpand(MethodCallExpression mce, DataServiceContext context) + { + Expression obj = StripConvert(mce.Object, null); + ResourceExpression re = obj as ResourceExpression; + if (re == null) + { + return mce; + } + + ValidationRules.RequireCanExpand(re); + Expression arg = StripTo(mce.Arguments[0]); + string path = null; + if (arg.NodeType == ExpressionType.Constant) + { + path = (string)((ConstantExpression)arg).Value; + } + else if (arg.NodeType == ExpressionType.Lambda) + { + Version uriVersion; + ValidationRules.ValidateExpandPath(arg, context, out path, out uriVersion); + re.RaiseUriVersion(uriVersion); + } + else + { + Debug.Assert(false, "Unexpected NodeType: " + arg.NodeType); + } + + if (!re.ExpandPaths.Contains(path)) + { + re.ExpandPaths.Add(path); + } + + return re; + } + + private static Expression AnalyzeFunc(MethodCallExpression mce, bool isExtensionMethod) + { + Expression obj; + if (isExtensionMethod) + { + obj = StripConvert(mce.Arguments[0], mce.Method.ReturnType); + } + else + { + obj = StripConvert(mce.Object, mce.Method.ReturnType); + } + + ResourceExpression re = obj as ResourceExpression; + if (re == null) + { + return mce; + } + + if (re.OperationName == null) + { + re.OperationName = mce.Method.Name; + } + + // for now there's just functionimport that use this function, so it's always false. + re.IsAction = false; + + string[] names = mce.Method.GetParameters().Select(p => p.Name).ToArray(); + for (int i = isExtensionMethod ? 1 : 0; i < mce.Arguments.Count - 1; ++i) + { + Expression arg = StripTo(mce.Arguments[i]); + string value = null; + if (arg.NodeType == ExpressionType.Constant) + { + ConstantExpression constantArgs = (ConstantExpression)arg; + value = ODataUriUtils.ConvertToUriLiteral(constantArgs.Value, ODataVersion.V4); + } + else + { + Debug.Assert(false, "Unexpected NodeType: " + arg.NodeType); + } + + KeyValuePair parameters = new KeyValuePair(names[i], value); + if (!re.OperationParameters.Contains(parameters)) + { + re.OperationParameters.Add(names[i], value); + } + } + + return re; + } + + private static Expression AnalyzeAddCustomQueryOption(MethodCallExpression mce) + { + Expression obj = StripConvert(mce.Object, null); + ResourceExpression re = obj as ResourceExpression; + if (re == null) + { + return mce; + } + + ValidationRules.RequireCanAddCustomQueryOption(re); + + ConstantExpression name = StripTo(mce.Arguments[0]); + ConstantExpression value = StripTo(mce.Arguments[1]); + + if (((string)name.Value).Trim() == UriHelper.DOLLARSIGN + UriHelper.OPTIONEXPAND) + { + // if the user is setting $expand option, need to merge with other existing expand paths that may have been alredy added + // check for allow expansion + ValidationRules.RequireCanExpand(re); + re.ExpandPaths = re.ExpandPaths.Union(new string[] { (string)value.Value }, StringComparer.Ordinal).ToList(); + } + else + { + ValidationRules.RequireLegalCustomQueryOption(mce.Arguments[0], re); + re.CustomQueryOptions.Add(name, value); + } + + return re; + } + + private static Expression AnalyzeAddCountOption(MethodCallExpression mce, CountOption countOption) + { + Expression obj = StripConvert(mce.Object, null); + QueryableResourceExpression rse = obj as QueryableResourceExpression; + if (rse == null) + { + return mce; + } + + ValidationRules.RequireCanAddCount(rse); + rse.ConvertKeyToFilterExpression(); + rse.CountOption = countOption; + + return rse; + } + + /// Creates a new resource set as produced by a navigation. + /// + /// The type of the expression as it appears in the tree (possibly + /// with transparent scopes). + /// + /// The source of the set. + /// The member access on that yields the set. + /// The resource type on the set. + /// A new instance. + private static QueryableResourceExpression CreateResourceSetExpression(Type type, ResourceExpression source, Expression memberExpression, Type resourceType) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(source != null, "source != null"); + Debug.Assert(memberExpression != null, "memberExpression != null"); + Debug.Assert(resourceType != null, "resourceType != null"); + + // ResourceSetExpressions can always have order information, + // so return them as IOrderedQueryable<> always. Necessary to allow + // OrderBy results that get aliased to a previous expression work + // with ThenBy. + Type elementType = TypeSystem.GetElementType(type); + Debug.Assert(elementType != null, "elementType != null -- otherwise the set isn't going to act like a collection"); + Type expressionType = typeof(IOrderedQueryable<>).MakeGenericType(elementType); + + // TODO: Should we create SingletonExpression here? + ResourceSetExpression newResource = new ResourceSetExpression(expressionType, source, memberExpression, resourceType, source.ExpandPaths.ToList(), source.CountOption, source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), null /*projection*/, null /*resourceTypeAs*/, source.UriVersion); + source.ExpandPaths.Clear(); + source.CountOption = CountOption.None; + source.CustomQueryOptions.Clear(); + return newResource; + } + + /// Creates a new resource singleton as produced by a navigation. + /// + /// The type of the expression as it appears in the tree (possibly + /// with transparent scopes). + /// + /// The source of the singleton. + /// The member access on that yields the singleton. + /// A new instance. + private static NavigationPropertySingletonExpression CreateNavigationPropertySingletonExpression(Type type, ResourceExpression source, Expression memberExpression) + { + NavigationPropertySingletonExpression newResource = new NavigationPropertySingletonExpression(type, source, memberExpression, memberExpression.Type, source.ExpandPaths.ToList(), source.CountOption, source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), null /*projection*/, null /*resourceTypeAs*/, source.UriVersion); + source.ExpandPaths.Clear(); + source.CountOption = CountOption.None; + source.CustomQueryOptions.Clear(); + return newResource; + } + + /// + /// Produces a new that is a clone of in all respects, + /// other than its result type - which will be - and its transparent scope, + /// which will be null. This is a shallow clone operation - sequence query options, key predicate, etc are + /// not cloned, but are reassigned to the new instance. The resource expression should be + /// discarded after being used with this method. + /// + /// The result type - - that the new resource set expression should have. + /// The resource set expression from which the transparent scope is being removed + /// A new resource set expression without an enclosing transparent scope and with the specified result type. + private static QueryableResourceExpression RemoveTransparentScope(Type expectedResultType, QueryableResourceExpression input) + { + // Create a new resource set expression based on the input + ResourceSetExpression newResource = new ResourceSetExpression(expectedResultType, input.Source, input.MemberExpression, input.ResourceType, input.ExpandPaths, input.CountOption, input.CustomQueryOptions, input.Projection, input.ResourceTypeAs, input.UriVersion); + + // Reassign state items that are not constructor arguments - query options + key predicate + newResource.SetKeyPredicate(input.KeyPredicateConjuncts); + foreach (QueryOptionExpression queryOption in input.SequenceQueryOptions) + { + newResource.AddSequenceQueryOption(queryOption); + } + + // Instruct the new resource set expression to use input's Input Reference instead of creating its own. + // This will also update the Input Reference to specify the new resource set expression as it's target, + // so that any usage of it in query option expressions is consistent with the new resource set expression. + newResource.OverrideInputReference(input); + + return newResource; + } + + /// Returns the specified expression, stripping redundant converts. + /// Expression to return. + /// e, or the underlying expression for redundant converts. + internal static Expression StripConvertToAssignable(Expression e) + { + Debug.Assert(e != null, "e != null"); + + Expression result; + UnaryExpression unary = e as UnaryExpression; + if (unary != null && PatternRules.MatchConvertToAssignable(unary)) + { + result = unary.Operand; + } + else + { + result = e; + } + + return result; + } + + /// + /// Strips the specifed of intermediate + /// expression (unnecessary converts and quotes) and returns + /// the underlying expression of type T (or null if it's not of that type). + /// + /// Type of expression to return. + /// Expression to consider. + /// The underlying expression for . + internal static T StripTo(Expression expression) where T : Expression + { + Debug.Assert(expression != null, "expression != null"); + + Expression result; + do + { + result = expression; + expression = expression.NodeType == ExpressionType.Quote ? ((UnaryExpression)expression).Operand : expression; + expression = StripConvertToAssignable(expression); + } + while (result != expression); + + return result as T; + } + + /// + /// Strips the specifed of intermediate unnecessary converts + /// and quotes, and returns the underlying expression of type T (or null if it's not of that type). + /// + /// Type of expression to return. + /// Expression to consider. + /// The converted type for typeAs expressions. + /// The underlying expression for . + internal static T StripTo(Expression expression, out Type convertedType) where T : Expression + { + Debug.Assert(expression != null, "expression != null"); + + Expression result; + convertedType = null; + do + { + result = expression; + expression = expression.NodeType == ExpressionType.Quote ? ((UnaryExpression)expression).Operand : expression; + + if (expression.NodeType == ExpressionType.Convert || + expression.NodeType == ExpressionType.ConvertChecked || + expression.NodeType == ExpressionType.TypeAs) + { + UnaryExpression ue = expression as UnaryExpression; + if (ue != null) + { + if (PatternRules.MatchConvertToAssignable(ue)) + { + expression = ue.Operand; // remove redundant casts (equivalent and upcasts) + } + else if (expression.NodeType == ExpressionType.TypeAs) + { + if (convertedType != null) + { + // We do not support more than one instance of a necessary TypeAs conversion, + // e.g., ((p as Employee) as Customer) + // Note 1: this also means that we do not support multiple downcasts as these are redundant, + // e.g., ((p as Employee) as Manager) + // from e in p.OfType() select e as Manager + throw new NotSupportedException(Strings.ALinq_CannotUseTypeFiltersMultipleTimes); + } + else + { + // Note 2: no check if this is a downcast (see note 1 above). + expression = ue.Operand; + convertedType = ue.Type; + } + } + } + } + } + while (result != expression); + + return result as T; + } + + internal override Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) + { + Debug.Assert(rse != null, "rse != null"); + + if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.RootResourceSet || (ResourceExpressionType)rse.NodeType == ResourceExpressionType.RootSingleResource) + { + // Actually, the user may already have composed an expansion, so + // we'll find a query option here. + // Debug.Assert(!rse.HasQueryOptions, "!rse.HasQueryOptions"); + + // since we could be adding query options to the root, create a new one which can be mutable. + return QueryableResourceExpression.CreateNavigationResourceExpression(rse.NodeType, rse.Type, rse.Source, rse.MemberExpression, rse.ResourceType, null /*expandPaths*/, CountOption.None, null /*customQueryOptions*/, null /*projection*/, rse.ResourceTypeAs, rse.UriVersion, rse.OperationName, rse.OperationParameters); + } + return rse; + } + + private static bool TryGetResourceSetMethodArguments(MethodCallExpression mce, out QueryableResourceExpression input, out LambdaExpression lambda) + { + input = null; + lambda = null; + + input = mce.Arguments[0] as QueryableResourceExpression; + if (input != null && + PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out lambda)) + { + return true; + } + + return false; + } + + private static bool TryBindToInput(ResourceExpression input, LambdaExpression le, out Expression bound) + { + List referencedInputs = new List(); + bound = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs); + if (referencedInputs.Count > 1 || (referencedInputs.Count == 1 && referencedInputs[0] != input)) + { + bound = null; + } + + return bound != null; + } + + private static Expression AnalyzeResourceSetConstantMethod(MethodCallExpression mce, Func constantMethodAnalyzer) + { + ResourceExpression input = (ResourceExpression)mce.Arguments[0]; + ConstantExpression constantArg = StripTo(mce.Arguments[1]); + if (null == constantArg) + { + // UNSUPPORTED: A ConstantExpression is expected + return mce; + } + + return constantMethodAnalyzer(mce, input, constantArg); + } + + private static Expression AnalyzeCountMethod(MethodCallExpression mce) + { + // [Res].LongCount() + // [Res].Count() + // It is either a ResourceExpression or a collection property. + QueryableResourceExpression rse = mce.Arguments[0] as QueryableResourceExpression; + if (rse == null) + { + return mce; + } + + ValidationRules.RequireCanAddCount(rse); + rse.ConvertKeyToFilterExpression(); + rse.CountOption = CountOption.CountSegment; + + return rse; + } + + private static void AddSequenceQueryOption(ResourceExpression target, QueryOptionExpression qoe) + { + QueryableResourceExpression rse = (QueryableResourceExpression)target; + rse.ConvertKeyToFilterExpression(); + + // Validation that can add option + switch (qoe.NodeType) + { + case (ExpressionType)ResourceExpressionType.FilterQueryOption: + if (rse.Skip != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "skip")); + } + else if (rse.Take != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "top")); + } + else if (rse.Projection != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "select")); + } + break; + case (ExpressionType)ResourceExpressionType.OrderByQueryOption: + if (rse.Skip != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "skip")); + } + else if (rse.Take != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "top")); + } + else if (rse.Projection != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "select")); + } + break; + case (ExpressionType)ResourceExpressionType.SkipQueryOption: + if (rse.Take != null) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("skip", "top")); + } + break; + default: + break; + } + + rse.AddSequenceQueryOption(qoe); + } + + internal override Expression VisitBinary(BinaryExpression b) + { + Expression e = base.VisitBinary(b); + if (PatternRules.MatchStringAddition(e)) + { + BinaryExpression be = StripTo(e); + MethodInfo mi = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }); + return Expression.Call(mi, new Expression[] { be.Left, be.Right }); + } + + return e; + } + + internal override Expression VisitMemberAccess(MemberExpression m) + { + Expression e = base.VisitMemberAccess(m); + MemberExpression me = StripTo(e); + PropertyInfo pi; + Expression boundTarget; + MethodInfo mi; + if (me != null && + PatternRules.MatchNonPrivateReadableProperty(me, out pi, out boundTarget) && + TypeSystem.TryGetPropertyAsMethod(pi, out mi)) + { + return Expression.Call(me.Expression, mi); + } + + return e; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Large switch is necessary")] + internal override Expression VisitMethodCall(MethodCallExpression mce) + { + Expression e; + + // check first to see if projection (not a navigation) so that func does recursively analyze selector + // Currently the patterns being looked for in the selector are mutually exclusive from naviagtion patterns looked at later. + SequenceMethod sequenceMethod; + if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod)) + { + // The leaf projection can be one of Select(source, selector) or + // SelectMany(source, collectionSelector, resultSelector). + if (sequenceMethod == SequenceMethod.Select || + sequenceMethod == SequenceMethod.SelectManyResultSelector) + { + if (this.AnalyzeProjection(mce, sequenceMethod, out e)) + { + return e; + } + } + } + + e = base.VisitMethodCall(mce); + mce = e as MethodCallExpression; + + if (mce != null) + { + if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod)) + { + switch (sequenceMethod) + { + case SequenceMethod.Where: + return AnalyzePredicate(mce, this.Model); + case SequenceMethod.Select: + return AnalyzeNavigation(mce, this.context); + case SequenceMethod.SelectMany: + case SequenceMethod.SelectManyResultSelector: + { + Expression result = AnalyzeSelectMany(mce, this.context); + return result; + } + + case SequenceMethod.Take: + return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, takeCount) => { AddSequenceQueryOption(resource, new TakeQueryOptionExpression(callExp.Type, takeCount)); return resource; }); + case SequenceMethod.Skip: + return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, skipCount) => { AddSequenceQueryOption(resource, new SkipQueryOptionExpression(callExp.Type, skipCount)); return resource; }); + case SequenceMethod.OrderBy: + return ApplyOrdering(mce, /*descending=*/false, /*thenBy=*/false, this.Model); + case SequenceMethod.ThenBy: + return ApplyOrdering(mce, /*descending=*/false, /*thenBy=*/true, this.Model); + case SequenceMethod.OrderByDescending: + return ApplyOrdering(mce, /*descending=*/true, /*thenBy=*/false, this.Model); + case SequenceMethod.ThenByDescending: + return ApplyOrdering(mce, /*descending=*/true, /*thenBy=*/true, this.Model); + case SequenceMethod.First: + case SequenceMethod.FirstOrDefault: + return LimitCardinality(mce, 1); + case SequenceMethod.Single: + case SequenceMethod.SingleOrDefault: + return LimitCardinality(mce, 2); + case SequenceMethod.Cast: + return AnalyzeCast(mce); + case SequenceMethod.OfType: + return AnalyzeOfType(mce); + case SequenceMethod.Any: + case SequenceMethod.All: + case SequenceMethod.AnyPredicate: + return mce; + case SequenceMethod.LongCount: + case SequenceMethod.Count: + return AnalyzeCountMethod(mce); + default: + throw Error.MethodNotSupported(mce); + } + } + else if ((mce.Method.DeclaringType.IsGenericType() && + IsTypeOfGenericBaseType(typeof(DataServiceQuery<>), mce.Method.DeclaringType)) + || (mce.Method.GetParameters().Any() && IsTypeOfGenericBaseType(typeof(DataServiceQuery<>), mce.Method.GetParameters()[0].ParameterType))) + { + Type t; + if (mce.Method.DeclaringType.IsGenericType() && IsTypeOfGenericBaseType(typeof(DataServiceQuery<>), mce.Method.DeclaringType)) + { + t = typeof(DataServiceQuery<>).MakeGenericType(mce.Method.DeclaringType.GetGenericArguments()[0]); + } + else + { + t = typeof(DataServiceQuery<>).MakeGenericType(mce.Method.GetParameters()[0].ParameterType.GetGenericArguments()[0]); + } + + if (mce.Method.Name == ExpandMethodName && mce.Method.DeclaringType == t) + { + return AnalyzeExpand(mce, this.context); + } + if (mce.Method.GetParameters().Any() && mce.Method.GetParameters()[0].ParameterType == t) + { + return AnalyzeFunc(mce, true); + } + if (mce.Method.Name == AddQueryOptionMethodName && mce.Method.DeclaringType == t) + { + return AnalyzeAddCustomQueryOption(mce); + } + if (mce.Method.Name == IncludeTotalCountMethodName && mce.Method.DeclaringType == t) + { + return AnalyzeAddCountOption(mce, CountOption.CountQuery); + } + else + { + throw Error.MethodNotSupported(mce); + } + } + else if (mce.Method.DeclaringType != null && mce.Method.DeclaringType.GetBaseType() == typeof(DataServiceContext)) + { + return AnalyzeFunc(mce, false); + } + + return mce; + } + + return e; + } + + /// Strips calls to .Cast() methods, returning the underlying expression. + /// Expression to strip calls from. + /// The underlying expression. + /// + /// Note that this method drops information about what the casts were, + /// and is only supported for collector selectors in SelectMany() calls, + /// to enable scenarios such as from t in ctx.Tables from Customer c in t.Items... + /// + private static Expression StripCastMethodCalls(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + + MethodCallExpression call = StripTo(expression); + while (call != null && ReflectionUtil.IsSequenceMethod(call.Method, SequenceMethod.Cast)) + { + expression = call.Arguments[0]; + call = StripTo(expression); + } + + return expression; + } + + /// Use this class to perform pattern-matching over expression trees. + /// + /// Following these guidelines simplifies usage: + /// + /// - Return true/false for matches, and interesting matched information in out parameters. + /// + /// - If one of the inputs to be matched undergoes "skipping" for unnecesary converts, + /// return the same member as an out parameter. This forces callers to be aware that + /// they should use the more precise representation for computation (without having + /// to rely on a normalization step). + /// + internal static class PatternRules + { + /// + /// Checks whether the is a convert that + /// always succeeds because it converts to the same target type or a + /// base type. + /// + /// Expression to match. + /// + /// true if is a convert to same or base type; false otherwise. + /// + internal static bool MatchConvertToAssignable(UnaryExpression expression) + { + Debug.Assert(expression != null, "expression != null"); + + if (expression.NodeType != ExpressionType.Convert && + expression.NodeType != ExpressionType.ConvertChecked && + expression.NodeType != ExpressionType.TypeAs) + { + return false; + } + + return expression.Type.IsAssignableFrom(expression.Operand.Type); + } + + /// + /// Checks whether is a lambda of the + /// form (p) => p.member[.member]* (possibly quoted). + /// + /// Expression to match. + /// true if the expression is a match; false otherwise. + /// + /// This method strip .Call methods because it's currently used only + /// to supporte .SelectMany() collector selectors. If this method + /// is reused for other purposes, this behavior should be made + /// conditional or factored out. + /// + internal static bool MatchParameterMemberAccess(Expression expression) + { + Debug.Assert(expression != null, "lambda != null"); + + LambdaExpression lambda = StripTo(expression); + if (lambda == null || lambda.Parameters.Count != 1) + { + return false; + } + + ParameterExpression parameter = lambda.Parameters[0]; + Expression body = StripCastMethodCalls(lambda.Body); + MemberExpression memberAccess = StripTo(body); + while (memberAccess != null) + { + if (memberAccess.Expression == parameter) + { + return true; + } + + memberAccess = StripTo(memberAccess.Expression); + } + + return false; + } + + /// + /// Checks whether the specified expression is a path of member + /// access expressions. + /// + /// Expression to match. + /// Data service context instance + /// Expression equivalent to , without unnecessary converts. + /// Expression from which the path starts. + /// Path of member names from . + /// Uri version. + /// true if there is at least one property in the path; false otherwise. + internal static bool MatchPropertyAccess(Expression e, DataServiceContext context, out MemberExpression member, out Expression instance, out PathSegmentToken propertyPath, out Version uriVersion) + { + instance = null; + propertyPath = null; + uriVersion = Util.ODataVersion4; + + MemberExpression me = StripTo(e); + member = me; + while (me != null) + { + PropertyInfo pi; + Expression boundTarget; + if (MatchNonPrivateReadableProperty(me, out pi, out boundTarget)) + { + string propertyName = ClientTypeUtil.GetServerDefinedName(pi); + + NonSystemToken newPropertyToAdd = new NonSystemToken(propertyName, null, null); + if (propertyPath == null) + { + propertyPath = newPropertyToAdd; + } + else + { + newPropertyToAdd.SetNextToken(propertyPath); + propertyPath = newPropertyToAdd; + } + e = me.Expression; + Type convertedType; + me = ResourceBinder.StripTo(e, out convertedType); + if (convertedType != null) + { + NonSystemToken subPropertyToAdd = new NonSystemToken(UriHelper.GetEntityTypeNameForUriAndValidateMaxProtocolVersion(convertedType, context, ref uriVersion), null, null); + subPropertyToAdd.SetNextToken(propertyPath); + propertyPath = subPropertyToAdd; + } + } + else + { + me = null; + } + } + + if (propertyPath != null) + { + instance = e; + return true; + } + + return false; + } + + /// + /// Checks whether the specified expression is a path of member + /// access expressions. + /// + /// Expression to match. + /// Data service context instance + /// Expression equivalent to , without unnecessary converts. + /// Expression from which the path starts. + /// Path of member names from . + /// Uri version. + /// true if there is at least one property in the path; false otherwise. + internal static bool MatchPropertyAccess(Expression e, DataServiceContext context, out MemberExpression member, out Expression instance, out List propertyPath, out Version uriVersion) + { + instance = null; + propertyPath = null; + uriVersion = Util.ODataVersion4; + + MemberExpression me = StripTo(e); + member = me; + while (me != null) + { + PropertyInfo pi; + Expression boundTarget; + if (MatchNonPrivateReadableProperty(me, out pi, out boundTarget)) + { + if (propertyPath == null) + { + propertyPath = new List(); + } + + propertyPath.Insert(0, pi.Name); + e = me.Expression; + Type convertedType; + me = ResourceBinder.StripTo(e, out convertedType); + if (convertedType != null) + { + propertyPath.Insert(0, UriHelper.GetTypeNameForUri(convertedType, context)); + } + } + else + { + me = null; + } + } + + if (propertyPath != null) + { + instance = e; + return true; + } + + return false; + } + + // is constant + internal static bool MatchConstant(Expression e, out ConstantExpression constExpr) + { + constExpr = e as ConstantExpression; + return constExpr != null; + } + + internal static bool MatchAnd(Expression e) + { + BinaryExpression be = e as BinaryExpression; + return (be != null && (be.NodeType == ExpressionType.And || be.NodeType == ExpressionType.AndAlso)); + } + + /// + /// Checks whether the specified member expression refers to a member + /// that is a non-private property (readable and with getter and/or setter). + /// + /// Expression to check. + /// Non-null property info when result is true. + /// The property access target. + /// Whether the member refers to a non-private, readable property. + internal static bool MatchNonPrivateReadableProperty(Expression e, out PropertyInfo propInfo, out Expression target) + { + Debug.Assert(e != null, "e != null"); + + propInfo = null; + target = null; + // must be member expression + MemberExpression me = e as MemberExpression; + if (me == null) + { + return false; + } + + // must be property access + if (PlatformHelper.IsProperty(me.Member)) + { + PropertyInfo pi = (PropertyInfo)me.Member; + // must be readable and non-private + if (pi.CanRead && !TypeSystem.IsPrivate(pi)) + { + propInfo = pi; + target = me.Expression; + return true; + } + } + + return false; + } + + /// + /// Checks whether the specified is a member access to a key. + /// + /// Expression to check. + /// If this is a key access, the property for the key. + /// true if is a member access to a key; false otherwise. + internal static bool MatchKeyProperty(Expression expression, out PropertyInfo property) + { + property = null; + + // make sure member is property, it is public and has a Getter. + PropertyInfo pi; + Expression boundTarget; + if (!PatternRules.MatchNonPrivateReadableProperty(expression, out pi, out boundTarget)) + { + return false; + } + + // DEVNOTE(pqian): + // we must check whether the property access is: + // 1. a Key property on the resource's type + // 2. bound to the input ref expression + // (binding to the root IRE means that the property access must be on the lambda's parameter + // i.e., e => e.ID, rather than e.OtherProperty.ID), note that "OtherProperty" may be the same type as e. + // Call Order: Do not short circuit the get key properties call + // in V1 we will always call this for memberaccess expr, and thus always create a client type. + // There are certain types that we don't support and this could be throwing. +#if PORTABLELIB + Type resourceType = pi.DeclaringType; +#else + Type resourceType = pi.ReflectedType; +#endif + if ((ClientTypeUtil.GetKeyPropertiesOnType(resourceType) ?? ClientTypeUtil.EmptyPropertyInfoArray).Contains(pi, PropertyInfoEqualityComparer.Instance) && boundTarget is InputReferenceExpression) + { + property = pi; + return true; + } + + return false; + } + + internal static bool MatchKeyComparison(Expression e, out PropertyInfo keyProperty, out ConstantExpression keyValue) + { + if (PatternRules.MatchBinaryEquality(e)) + { + BinaryExpression be = (BinaryExpression)e; + if ((PatternRules.MatchKeyProperty(be.Left, out keyProperty) && PatternRules.MatchConstant(be.Right, out keyValue)) || + (PatternRules.MatchKeyProperty(be.Right, out keyProperty) && PatternRules.MatchConstant(be.Left, out keyValue))) + { + // if property is compared to null, expression is not key predicate comparison + return keyValue.Value != null; + } + } + + keyProperty = null; + keyValue = null; + return false; + } + + /// + /// Checks whether the specified matches + /// a call to System.Object.ReferenceEquals. + /// + /// Expression to check. + /// true if the expression matches; false otherwise. + internal static bool MatchReferenceEquals(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + MethodCallExpression call = expression as MethodCallExpression; + if (call == null) + { + return false; + } + + return call.Method == typeof(object).GetMethod("ReferenceEquals"); + } + + /// + /// Checks whether the specifed refers to a resource. + /// + /// Expression to check. + /// Resource expression if successful. + /// true if the expression is a resource expression; false otherwise. + internal static bool MatchResource(Expression expression, out ResourceExpression resource) + { + resource = expression as ResourceExpression; + return resource != null; + } + + /// + /// Checks whether the specified expression is a lambda with a two parameters + /// (possibly quoted). + /// + /// Expression to match. + /// If the expression matches, the lambda with the two parameters. + /// true if the expression is a lambda with two parameters. + internal static bool MatchDoubleArgumentLambda(Expression expression, out LambdaExpression lambda) + { + return MatchNaryLambda(expression, 2, out lambda); + } + + /// + /// Checks whether the specified is a selector + /// of the form (p) => p. + /// + /// Expression to check. + /// true if the lambda is an identity selector; false otherwise. + internal static bool MatchIdentitySelector(LambdaExpression lambda) + { + Debug.Assert(lambda != null, "lambda != null"); + + ParameterExpression parameter = lambda.Parameters[0]; + return parameter == StripTo(lambda.Body); + } + + /// + /// Checks whether the specified expression is a lambda with a single parameter + /// (possibly quoted). + /// + /// Expression to match. + /// If the expression matches, the lambda with the single parameter. + /// true if the expression is a lambda with a single argument. + internal static bool MatchSingleArgumentLambda(Expression expression, out LambdaExpression lambda) + { + return MatchNaryLambda(expression, 1, out lambda); + } + + /// + /// Checked whether the specified has the + /// form [input's transparent scope].[accessor]. + /// + /// Input expression (source) for the selector. + /// Selector lambda. + /// Data service context + /// true if the selector's body looks like [input's transparent scope].[accesor]. + internal static bool MatchTransparentIdentitySelector(Expression input, LambdaExpression selector, DataServiceContext context) + { + if (selector.Parameters.Count != 1) + { + return false; + } + + QueryableResourceExpression rse = input as QueryableResourceExpression; + if (rse == null || rse.TransparentScope == null) + { + return false; + } + + Expression potentialRef = selector.Body; + ParameterExpression expectedTarget = selector.Parameters[0]; + + MemberExpression propertyMember; + Expression paramRef; + List refPath; + Version uriVersion; + if (!MatchPropertyAccess(potentialRef, context, out propertyMember, out paramRef, out refPath, out uriVersion)) + { + return false; + } + + Debug.Assert(refPath != null, "refPath != null -- otherwise MatchPropertyAccess should not have returned true"); + return paramRef == expectedTarget && refPath.Count == 1 && refPath[0] == rse.TransparentScope.Accessor; + } + + internal static bool MatchIdentityProjectionResultSelector(Expression e) + { + LambdaExpression le = (LambdaExpression)e; + return (le.Body == le.Parameters[1]); + } + + /// + /// Checks wheter the specified lambda matches a selector that yields + /// a transparent identifier. + /// + /// + /// The input expression for the lambda, used to set up the + /// references from the transparent scope if one is produced. + /// + /// Lambda expression to match. + /// + /// After invocation, information on the accessors if the result + /// is true; null otherwise. + /// + /// + /// true if is a selector for a transparent + /// identifier; false otherwise. + /// + /// + /// Note that C# and VB.NET have different patterns for accumulating + /// parameters. + /// + /// C# uses a two-member anonymous type with "everything so far" + /// plus the newly introduced range variable. + /// + /// VB.NET uses an n-member anonymous type by pulling range variables + /// from a previous anonymous type (or the first range variable), + /// plus the newly introduced range variable. + /// + /// For additional background, see: + /// Transparent Identifiers - http://blogs.msdn.com/wesdyer/archive/2006/12/22/transparent-identifiers.aspx + /// http://msdn.microsoft.com/en-us/library/bb308966.aspx + /// In particular: + /// - 26.7.1.4 From, let, where, join and orderby clauses + /// - 26.7.1.7 Transparent identifiers + /// + /// is the expression that represents the + /// navigation resulting from the collector selector in the + /// SelectMany() call under analysis. + /// + internal static bool MatchTransparentScopeSelector(QueryableResourceExpression input, LambdaExpression resultSelector, out QueryableResourceExpression.TransparentAccessors transparentScope) + { + transparentScope = null; + + // Constructing transparent identifiers must be a simple instantiation. + if (resultSelector.Body.NodeType != ExpressionType.New) + { + return false; + } + + // Less than two arguments implies there's no new range variable introduced. + NewExpression ne = (NewExpression)resultSelector.Body; + if (ne.Arguments.Count < 2) + { + return false; + } + + // Transparent identifier must not be part of hierarchies. + if (ne.Type.GetBaseType() != typeof(object)) + { + return false; + } + + // Transparent identifiers have a public property per constructor, + // matching the parameter name. + ParameterInfo[] constructorParams = ne.Constructor.GetParameters(); + if (ne.Members.Count != constructorParams.Length) + { + return false; + } + + // Every argument to the constructor should be a lambdaparameter or + // a member access off one. The introduced range variable is always + // a standalone parameter (note that for the first transparent + // identifier, both are; we pick it by convention in that case). + QueryableResourceExpression inputSource = input.Source as QueryableResourceExpression; + int introducedMemberIndex = -1; + ParameterExpression collectorSourceParameter = resultSelector.Parameters[0]; + ParameterExpression introducedRangeParameter = resultSelector.Parameters[1]; + MemberInfo[] memberProperties = new MemberInfo[ne.Members.Count]; + IEnumerable properties = ne.Type.GetPublicProperties(true /*instanceOnly*/); + Dictionary sourceAccessors = new Dictionary(constructorParams.Length - 1, StringComparer.Ordinal); + for (int i = 0; i < ne.Arguments.Count; i++) + { + Expression argument = ne.Arguments[i]; + MemberInfo member = ne.Members[i]; + + if (!ExpressionIsSimpleAccess(argument, resultSelector.Parameters)) + { + return false; + } + + // Transparent identifiers have a property that matches the parameter + // name. The Members collection contains the get_Foo methods. + if (PlatformHelper.IsMethod(member)) + { + member = properties.Where(property => PlatformHelper.AreMembersEqual(member, property.GetGetMethod())).FirstOrDefault(); + if (member == null) + { + return false; + } + } + + if (member.Name != constructorParams[i].Name) + { + return false; + } + + memberProperties[i] = member; + + ParameterExpression argumentAsParameter = StripTo(argument); + if (introducedRangeParameter == argumentAsParameter) + { + if (introducedMemberIndex != -1) + { + return false; + } + + introducedMemberIndex = i; + } + else if (collectorSourceParameter == argumentAsParameter) + { + sourceAccessors[member.Name] = inputSource.CreateReference(); + } + else + { + List referencedInputs = new List(); + InputBinder.Bind(argument, inputSource, resultSelector.Parameters[0], referencedInputs); + if (referencedInputs.Count != 1) + { + return false; + } + + sourceAccessors[member.Name] = referencedInputs[0].CreateReference(); + } + } + + // Transparent identifers should add at least one new range variable. + if (introducedMemberIndex == -1) + { + return false; + } + + string resultAccessor = memberProperties[introducedMemberIndex].Name; + transparentScope = new QueryableResourceExpression.TransparentAccessors(resultAccessor, sourceAccessors); + + return true; + } + + /// + /// Checks whether the specified is a member access + /// that references . + /// + /// Expression to check. + /// InputReferenceExpression to consider as source. + /// Data service context instance. + /// Navigation member, equivalent to without unnecessary casts. + /// + /// true if is a property collection that originates in + /// ; false otherwise. + /// + internal static bool MatchPropertyProjectionRelatedSet(ResourceExpression input, Expression potentialPropertyRef, DataServiceContext context, out MemberExpression setNavigationMember) + { + return MatchPropertyProjection(input, potentialPropertyRef, true, context, out setNavigationMember); + } + + /// + /// Checks whether the specified is a member access + /// that references . + /// + /// Expression to check. + /// InputReferenceExpression to consider as source. + /// Data service context instance + /// Member expression, equivalent to without unnecessary casts. + /// + /// true if is a scalar property or singleton navigation property that originates in + /// ; false otherwise. + /// + internal static bool MatchPropertyProjectionSingleton(ResourceExpression input, Expression potentialPropertyRef, DataServiceContext context, out MemberExpression propertyMember) + { + return MatchPropertyProjection(input, potentialPropertyRef, false, context, out propertyMember); + } + + /// + /// Checks whether the specified is a member access with the specified cardinality (per ) + /// that references . + /// + /// Expression to check. + /// InputReferenceExpression to consider as source. + /// + /// Whether the match should be for a set of related entities or a singleton property. + /// Singleton properties can be scalar types (including collection and other enumerable types like byte[]), complex types, or singleton navigation properties. + /// Related set properties are only navigation properties. + /// + /// Data service context instance. + /// Member expression for accessing the property, equivalent to without unnecessary casts. + /// + /// true if is a property that originates in + /// and is the expected cardinality (per ); false otherwise. + /// + private static bool MatchPropertyProjection(ResourceExpression input, Expression potentialPropertyRef, bool matchSetNavigationProperty, DataServiceContext context, out MemberExpression propertyMember) + { + Expression foundInstance; + List propertyPath; + Version uriVersion; + if (MatchPropertyAccess(potentialPropertyRef, context, out propertyMember, out foundInstance, out propertyPath, out uriVersion)) + { + UnaryExpression unaryInstance = foundInstance as UnaryExpression; + if (unaryInstance != null && unaryInstance.NodeType == ExpressionType.TypeAs) + { + foundInstance = unaryInstance.Operand; + } + + if (foundInstance == input.CreateReference()) + { + if (PatternRules.MatchSetNavigationProperty(propertyMember, context.Model) == matchSetNavigationProperty) + { + return true; + } + } + } + + propertyMember = null; + return false; + } + + internal static bool MatchMemberInitExpressionWithDefaultConstructor(Expression source, LambdaExpression e) + { + MemberInitExpression mie = StripTo(e.Body); + ResourceExpression resource; + return MatchResource(source, out resource) && (mie != null) && (mie.NewExpression.Arguments.Count == 0); + } + + internal static bool MatchNewExpression(Expression source, LambdaExpression e) + { + ResourceExpression resource; + return MatchResource(source, out resource) && (e.Body is NewExpression); + } + + /// + /// Checks whether is a logical negation + /// expression. + /// + /// Expression to check. + /// true if expression is a Not expression; false otherwise. + internal static bool MatchNot(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + return expression.NodeType == ExpressionType.Not; + } + + /// Checks whether the type of the specified expression could represent a set of related resources. + /// Expression to check. + /// The model that the client uses. + /// true if the type of the expression could represent a set of related resources; false otherwise. + internal static bool MatchSetNavigationProperty(Expression e, ClientEdmModel model) + { + // Even though collection, byte[], and char[] are enumerable types, these properties are treated as scalars + // for the purposes of projection and should not be considered as set navigation properties + return (TypeSystem.FindIEnumerable(e.Type) != null) && + e.Type != typeof(char[]) && + e.Type != typeof(byte[]) && + !WebUtil.IsCLRTypeCollection(e.Type, model); + } + + /// + /// Checks whether is a conditional expression + /// that checks whether a navigation property (reference or collection) is + /// null before proceeding. + /// + /// Entity in scope to be checked. + /// Expression to check. + /// Check results. + internal static MatchNullCheckResult MatchNullCheck(Expression entityInScope, ConditionalExpression conditional) + { + Debug.Assert(conditional != null, "conditional != null"); + + MatchNullCheckResult result = new MatchNullCheckResult(); + MatchEqualityCheckResult equalityCheck = MatchEquality(conditional.Test); + if (!equalityCheck.Match) + { + return result; + } + + Expression assignedCandidate; + if (equalityCheck.EqualityYieldsTrue) + { + // Pattern: memberCandidate EQ null ? null : memberCandidate.Something + if (!MatchNullConstant(conditional.IfTrue)) + { + return result; + } + + assignedCandidate = conditional.IfFalse; + } + else + { + // Pattern: memberCandidate NEQ null ? memberCandidate.Something : null + if (!MatchNullConstant(conditional.IfFalse)) + { + return result; + } + + assignedCandidate = conditional.IfTrue; + } + + // Pattern can be one memberCandidate OP null or null OP memberCandidate. + Expression memberCandidate; + if (MatchNullConstant(equalityCheck.TestLeft)) + { + memberCandidate = equalityCheck.TestRight; + } + else if (MatchNullConstant(equalityCheck.TestRight)) + { + memberCandidate = equalityCheck.TestLeft; + } + else + { + return result; + } + + Debug.Assert(assignedCandidate != null, "assignedCandidate != null"); + Debug.Assert(memberCandidate != null, "memberCandidate != null"); + + // Verify that the member expression is a prefix path of the assigned expressions. + MemberAssignmentAnalysis assignedAnalysis = MemberAssignmentAnalysis.Analyze(entityInScope, assignedCandidate); + if (assignedAnalysis.MultiplePathsFound) + { + return result; + } + + MemberAssignmentAnalysis memberAnalysis = MemberAssignmentAnalysis.Analyze(entityInScope, memberCandidate); + if (memberAnalysis.MultiplePathsFound) + { + return result; + } + + Expression[] assignedExpressions = assignedAnalysis.GetExpressionsToTargetEntity(); + Expression[] memberExpressions = memberAnalysis.GetExpressionsToTargetEntity(); + if (memberExpressions.Length > assignedExpressions.Length) + { + return result; + } + + // The access form we're interested in is [param].member0.member1... + for (int i = 0; i < memberExpressions.Length; i++) + { + Expression assigned = assignedExpressions[i]; + Expression member = memberExpressions[i]; + if (assigned == member) + { + continue; + } + + if (assigned.NodeType != member.NodeType || assigned.NodeType != ExpressionType.MemberAccess) + { + return result; + } + + if (((MemberExpression)assigned).Member != ((MemberExpression)member).Member) + { + return result; + } + } + + result.AssignExpression = assignedCandidate; + result.Match = true; + result.TestToNullExpression = memberCandidate; + return result; + } + + /// Checks whether the specified is a null constant. + /// Expression to check. + /// true if is a constant null value; false otherwise. + internal static bool MatchNullConstant(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + ConstantExpression constant = expression as ConstantExpression; + if (constant != null && constant.Value == null) + { + return true; + } + + return false; + } + + internal static bool MatchBinaryExpression(Expression e) + { + return (e is BinaryExpression); + } + + internal static bool MatchBinaryEquality(Expression e) + { + return (PatternRules.MatchBinaryExpression(e) && ((BinaryExpression)e).NodeType == ExpressionType.Equal); + } + + internal static bool MatchStringAddition(Expression e) + { + if (e.NodeType == ExpressionType.Add) + { + BinaryExpression be = e as BinaryExpression; + return be != null && + be.Left.Type == typeof(string) && + be.Right.Type == typeof(string); + } + return false; + } + + /// + /// Checks whether is a "new DataServiceCollection of T". + /// + /// The expression to match + /// true if the expression matches the "new DataServiceCollection of T" or false otherwise. + internal static bool MatchNewDataServiceCollectionOfT(NewExpression nex) + { + return nex.Type.IsGenericType() && WebUtil.IsDataServiceCollectionType(nex.Type.GetGenericTypeDefinition()); + } + + /// + /// Checks whether is a "new ICollection of T". + /// + /// The expression to match + /// + internal static bool MatchNewCollectionOfT(NewExpression nex) + { + Type type = nex.Type; + return type.GetInterfaces().Any(t => t.GetGenericTypeDefinition() == typeof(ICollection<>)); + } + + /// + /// Checks whether is a check for + /// equality on two expressions. + /// + /// Expression to match. + /// + /// A structure describing whether the expression is a match, + /// whether it yields true on equality (ie, '==' as opposed to '!='), + /// and the compared expressions. + /// + /// + /// This pattern recognizes the following: + /// - Calls to object.ReferenceEquals + /// - Equality checks (ExpressionNodeType.Equals and ExpressionNodeType.NotEquals) + /// - Negation (ExpressionNodeType.Not) + /// + internal static MatchEqualityCheckResult MatchEquality(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + + // Before starting the pattern match, assume that we will not + // find one, and if we do, that it's a simple '==' check. The + // implementation needs to update these values as it traverses + // down the tree for nesting in expression such as !(a==b). + MatchEqualityCheckResult result = new MatchEqualityCheckResult(); + result.Match = false; + result.EqualityYieldsTrue = true; + + while (true) + { + if (MatchReferenceEquals(expression)) + { + MethodCallExpression call = (MethodCallExpression)expression; + result.Match = true; + result.TestLeft = call.Arguments[0]; + result.TestRight = call.Arguments[1]; + break; + } + else if (MatchNot(expression)) + { + result.EqualityYieldsTrue = !result.EqualityYieldsTrue; + expression = ((UnaryExpression)expression).Operand; + } + else + { + BinaryExpression test = expression as BinaryExpression; + if (test == null) + { + break; + } + + if (test.NodeType == ExpressionType.NotEqual) + { + result.EqualityYieldsTrue = !result.EqualityYieldsTrue; + } + else if (test.NodeType != ExpressionType.Equal) + { + break; + } + + result.TestLeft = test.Left; + result.TestRight = test.Right; + result.Match = true; + break; + } + } + + return result; + } + + /// + /// Checks whether the expression is a + /// simple access (standalone or member-access'ed) on one of the + /// parameter . + /// + /// Argument to match. + /// Candidate parameters. + /// + /// true if the argument is a parmater or a member from a + /// parameter; false otherwise. + /// + private static bool ExpressionIsSimpleAccess(Expression argument, ReadOnlyCollection expressions) + { + Debug.Assert(argument != null, "argument != null"); + Debug.Assert(expressions != null, "expressions != null"); + + Expression source = argument; + MemberExpression member; + do + { + member = source as MemberExpression; + if (member != null) + { + source = member.Expression; + } + } + while (member != null); + + ParameterExpression parameter = source as ParameterExpression; + if (parameter == null) + { + return false; + } + + return expressions.Contains(parameter); + } + + /// + /// Checks whether the specified expression is a lambda with a parameterCount parameters + /// (possibly quoted). + /// + /// Expression to match. + /// Expected number of parametrs. + /// If the expression matches, the lambda with the two parameters. + /// true if the expression is a lambda with parameterCount parameters. + private static bool MatchNaryLambda(Expression expression, int parameterCount, out LambdaExpression lambda) + { + lambda = null; + + LambdaExpression le = StripTo(expression); + if (le != null && le.Parameters.Count == parameterCount) + { + lambda = le; + } + + return lambda != null; + } + + /// + /// Use this class to represent the results of a match on an expression + /// that does a null check before accessing a property. + /// + internal struct MatchNullCheckResult + { + /// Expression used to assign a value when the reference is not null. + internal Expression AssignExpression; + + /// Whether the expression analyzed matches a null check pattern. + internal bool Match; + + /// Expression being checked againt null. + internal Expression TestToNullExpression; + } + + /// + /// Use this class to represent the results of a match on an expression + /// that checks for equality . + /// + internal struct MatchEqualityCheckResult + { + /// Whether a positive equality yields 'true' (ie, is this '==' as opposed to '!='). + internal bool EqualityYieldsTrue; + + /// Whether the expression analyzed matches an equality check pattern. + internal bool Match; + + /// The left-hand side of the check. + internal Expression TestLeft; + + /// The right-hand side of the check. + internal Expression TestRight; + } + } + + internal static class ValidationRules + { + internal static void RequireCanNavigate(Expression e) + { + QueryableResourceExpression resourceExpression = e as QueryableResourceExpression; + if (resourceExpression != null && resourceExpression.HasSequenceQueryOptions) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnLeafNodes); + } + + ResourceExpression resource; + if (PatternRules.MatchResource(e, out resource) && resource.Projection != null) + { + throw new NotSupportedException(Strings.ALinq_ProjectionOnlyAllowedOnLeafNodes); + } + } + + internal static void RequireCanProject(Expression e) + { + ResourceExpression re = (ResourceExpression)e; + if (!PatternRules.MatchResource(e, out re)) + { + throw new NotSupportedException(Strings.ALinq_CanOnlyProjectTheLeaf); + } + + if (re.Projection != null) + { + throw new NotSupportedException(Strings.ALinq_ProjectionCanOnlyHaveOneProjection); + } + + if (re.ExpandPaths.Count > 0) + { + throw new NotSupportedException(Strings.ALinq_CannotProjectWithExplicitExpansion); + } + } + + internal static void RequireCanExpand(Expression e) + { + ResourceExpression re = (ResourceExpression)e; + if (!PatternRules.MatchResource(e, out re)) + { + throw new NotSupportedException(Strings.ALinq_CantExpand); + } + + if (re.Projection != null) + { + throw new NotSupportedException(Strings.ALinq_CannotProjectWithExplicitExpansion); + } + } + + internal static void RequireCanAddCount(Expression e) + { + ResourceExpression re = (ResourceExpression)e; + if (!PatternRules.MatchResource(e, out re)) + { + throw new NotSupportedException(Strings.ALinq_CannotAddCountOption); + } + + // do we already have a count option? + if (re.CountOption != CountOption.None) + { + throw new NotSupportedException(Strings.ALinq_CannotAddCountOptionConflict); + } + } + + internal static void RequireCanAddCustomQueryOption(Expression e) + { + ResourceExpression re = (ResourceExpression)e; + if (!PatternRules.MatchResource(e, out re)) + { + throw new NotSupportedException(Strings.ALinq_CantAddQueryOption); + } + } + + internal static void RequireNonSingleton(Expression e) + { + ResourceExpression re = e as ResourceExpression; + if (re != null && re.IsSingleton) + { + throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnSingletons); + } + } + + internal static void RequireLegalCustomQueryOption(Expression e, ResourceExpression target) + { + string name = ((string)(e as ConstantExpression).Value).Trim(); + + if (name[0] == UriHelper.DOLLARSIGN) + { + if (target.CustomQueryOptions.Any(c => (string)c.Key.Value == name)) + { + // don't allow dups in Astoria $ namespace. + throw new NotSupportedException(Strings.ALinq_CantAddDuplicateQueryOption(name)); + } + + QueryableResourceExpression rse = target as QueryableResourceExpression; + if (rse != null) + { + switch (name.Substring(1)) + { + case UriHelper.OPTIONFILTER: + if (rse.Filter != null) + { + throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); + } + break; + case UriHelper.OPTIONORDERBY: + if (rse.OrderBy != null) + throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); + break; + case UriHelper.OPTIONEXPAND: + // how did we get here? + Debug.Assert(false, "$expand should not walk this path"); + break; + case UriHelper.OPTIONSKIP: + if (rse.Skip != null) + throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); + break; + case UriHelper.OPTIONTOP: + if (rse.Take != null) + throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); + break; + case UriHelper.OPTIONCOUNT: + // cannot add count if any counting already exists + if (rse.CountOption != CountOption.None) + throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); + break; + case UriHelper.OPTIONSELECT: + if (rse.Projection != null) + { + throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); + } + // DEVNOTE(pqian): + // while the normal projection analyzer does not allow expansions, we must allow it here + // since we do not automatically include the expansions in the URI. Users who wishes to + // project into navigation properties must manually expand those that are been selected. + break; + case UriHelper.OPTIONFORMAT: + ThrowNotSupportedExceptionForTheFormatOption(); + break; + default: + throw new NotSupportedException(Strings.ALinq_CantAddQueryOptionStartingWithDollarSign(name)); + } + } + } + } + + /// + /// Throws the NotSupportedException for the $format query option. + /// + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "DataServiceContext", Justification = "The spelling is correct.")] + private static void ThrowNotSupportedExceptionForTheFormatOption() + { + throw new NotSupportedException(Strings.ALinq_FormatQueryOptionNotSupported); + } + + /// + /// Detect and disallow member access for certain known types, in 'where' and 'orderby' requests. + /// + internal class WhereAndOrderByChecker : DataServiceALinqExpressionVisitor + { + private readonly SequenceMethod checkedMethod; + private readonly ClientEdmModel model; + + internal WhereAndOrderByChecker(ClientEdmModel model, SequenceMethod checkedMethod) + { + Debug.Assert(checkedMethod == SequenceMethod.Where || checkedMethod == SequenceMethod.OrderBy); + this.model = model; + this.checkedMethod = checkedMethod; + } + + internal override Expression VisitMethodCall(MethodCallExpression mce) + { + SequenceMethod sequenceMethod; + if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod) && + ReflectionUtil.IsAnyAllMethod(sequenceMethod)) + { + if (this.checkedMethod == SequenceMethod.OrderBy) + { + throw new NotSupportedException(Strings.ALinq_AnyAllNotSupportedInOrderBy(mce.Method.Name)); + } + + Type filteredType = mce.Method.GetGenericArguments().SingleOrDefault(); + if (!ClientTypeUtil.TypeOrElementTypeIsEntity(filteredType)) + { + Expression source = mce.Arguments[0]; + MemberExpression me = StripTo(source); + PropertyInfo pi; + Expression boundTarget; + if (me == null || + !PatternRules.MatchNonPrivateReadableProperty(me, out pi, out boundTarget) || + !WebUtil.IsCLRTypeCollection(pi.PropertyType, this.model)) + { + throw new NotSupportedException(Strings.ALinq_InvalidSourceForAnyAll(mce.Method.Name)); + } + } + + // visit lambda for any/all + if (mce.Arguments.Count == 2) + { + base.Visit(mce.Arguments[1]); + } + + return mce; + } + + return base.VisitMethodCall(mce); + } + + internal override Expression VisitMemberAccess(MemberExpression m) + { + if (PlatformHelper.IsProperty(m.Member)) + { + PropertyInfo pi = (PropertyInfo)m.Member; + + // For member like "c.Trips.Count", when Count is visited, no future visit if declare type is a Collection + if (pi.Name.Equals(ReflectionUtil.COUNTPROPNAME)) + { + MemberExpression me = StripTo(m.Expression); + if (me != null && !PrimitiveType.IsKnownNullableType(me.Type)) + { + Type collectionType = ClientTypeUtil.GetImplementationType(me.Type, typeof(ICollection<>)); + if (collectionType != null) + { + return m; + } + } + } + + // Continue the check if it is not a count segment + if (WebUtil.IsCLRTypeCollection(pi.PropertyType, this.model)) + { + if (this.checkedMethod == SequenceMethod.Where) + { + throw new NotSupportedException(Strings.ALinq_CollectionPropertyNotSupportedInWhere(pi.Name)); + } + else + { + throw new NotSupportedException(Strings.ALinq_CollectionPropertyNotSupportedInOrderBy(pi.Name)); + } + } + + if (typeof(DataServiceStreamLink).IsAssignableFrom(pi.PropertyType)) + { + throw new NotSupportedException(Strings.ALinq_LinkPropertyNotSupportedInExpression(pi.Name)); + } + } + + return base.VisitMemberAccess(m); + } + } + + /// + /// Detect and disallow member access for certain known types, in 'where' requests. + /// + internal static void CheckPredicate(Expression e, ClientEdmModel model) + { + WhereAndOrderByChecker predicateVisitor = new WhereAndOrderByChecker(model, SequenceMethod.Where); + predicateVisitor.Visit(e); + } + + /// + /// Detect and disallow member access for certain known types, in 'orderby' requests. + /// + internal static void CheckOrderBy(Expression e, ClientEdmModel model) + { + WhereAndOrderByChecker orderByVisitor = new WhereAndOrderByChecker(model, SequenceMethod.OrderBy); + orderByVisitor.Visit(e); + } + + /// + /// Used to identify and block navigation to members of collection property in select statement. + /// e.g. from c in ctx.Customers select c.CollectionProperty.member + /// + internal static void DisallowMemberAccessInNavigation(Expression e, ClientEdmModel model) + { + MemberExpression me = StripTo(e); + + while (me != null) + { + if (WebUtil.IsCLRTypeCollection(me.Expression.Type, model)) + { + throw new NotSupportedException(Strings.ALinq_CollectionMemberAccessNotSupportedInNavigation(me.Member.Name)); + } + + me = StripTo(me.Expression); + } + } + + /// + /// We do not support type identifier at the end of a path. + /// For example, these would throw: + /// select p as Employee + /// select p.BestFriend as Employee + /// Expand(p => p.BestFriend as Employee) + /// + /// expression passed to validate + /// The context of where is applied + internal static void DisallowExpressionEndWithTypeAs(Expression exp, string method) + { + Expression e = ResourceBinder.StripTo(exp); + if (e != null && e.NodeType == ExpressionType.TypeAs) + { + throw new NotSupportedException(Strings.ALinq_ExpressionCannotEndWithTypeAs(exp.ToString(), method)); + } + } + + /// + /// Checks whether the specified is a valid expand path. + /// + /// The lambda expression for the expand path + /// Data service context instance. + /// Expand path + /// Uri version + internal static void ValidateExpandPath(Expression input, DataServiceContext context, out string expandPath, out Version uriVersion) + { + expandPath = null; + uriVersion = Util.ODataVersion4; + LambdaExpression le; + if (PatternRules.MatchSingleArgumentLambda(input, out le)) + { + Expression foundInstance; + PathSegmentToken propertyPath; + MemberExpression propertyMember; + MemberExpression pathExpression = ResourceBinder.StripTo(le.Body); + + if (pathExpression != null && PatternRules.MatchPropertyAccess(pathExpression, context, out propertyMember, out foundInstance, out propertyPath, out uriVersion)) + { + UnaryExpression unaryInstance = foundInstance as UnaryExpression; + if (unaryInstance != null && unaryInstance.NodeType == ExpressionType.TypeAs) + { + foundInstance = unaryInstance.Operand; + } +#if PORTABLELIB + Type resourceType = propertyMember.Member.DeclaringType; +#else + Type resourceType = propertyMember.Member.ReflectedType; +#endif + if (foundInstance == le.Parameters[0] && ClientTypeUtil.TypeOrElementTypeIsEntity(resourceType)) + { + Debug.Assert(propertyPath != null, "propertyPath != null"); + ExpandOnlyPathToStringVisitor expandPathVisitor = new ExpandOnlyPathToStringVisitor(); + expandPath = propertyPath.Accept(expandPathVisitor); + return; + } + } + + } + + throw new NotSupportedException(Strings.ALinq_InvalidExpressionInNavigationPath(input)); + } + } + + // TODO: By default, C#/Vb compilers uses declaring type for property expression when + // we pass property name while creating the property expression. But its totally fine to use + // property info reflected from any subtype while creating property expressions. + // The problem is when one creates the property expression from a property info reflected from one + // of the subtype, then we don't recognize the key properties and instead of generating a key predicate, we generate + // a filter predicate. This limits a bunch of scenarios, since we can't navigate further once + // we generate a filter predicate. + // To fix this issue, we use a PropertyInfoEqualityComparer, which checks for the name and DeclaringType + // of the property and if they are the same, then it considers them equal. + + /// + /// Equality and comparison implementation for PropertyInfo. + /// + private sealed class PropertyInfoEqualityComparer : IEqualityComparer + { + /// + /// private constructor for the singleton pattern + /// + private PropertyInfoEqualityComparer() { } + + /// + /// Static property which returns the single instance of the EqualityComparer + /// + internal static readonly PropertyInfoEqualityComparer Instance = new PropertyInfoEqualityComparer(); + + #region IEqualityComparer Members + + /// + /// Checks whether the given property info's refers to the same property or not. + /// + /// first property info + /// second property info + /// true if they refer to the same property, otherwise returns false. + public bool Equals(PropertyInfo left, PropertyInfo right) + { + // Short circuit the comparison if we know the other reference is equivalent + if (object.ReferenceEquals(left, right)) { return true; } + + // If either side is null, return false order (both can't be null because of + // the previous check) + if (null == left || null == right) { return false; } + + // If the declaring type and the name of the property are the same, + // both the property infos refer to the same property. + return object.ReferenceEquals(left.DeclaringType, right.DeclaringType) && left.Name.Equals(right.Name); + } + + /// + /// Computes the hashcode for the given property info + /// + /// property info whose hash code needs to be computed. + /// the hashcode for the given property info. + public int GetHashCode(PropertyInfo obj) + { + Debug.Assert(obj != null, "obj != null"); + return (null != obj) ? obj.GetHashCode() : 0; + } + + #endregion + } + + /// + /// Use this visitor to detect whether an Expression is found in an + /// Expression tree. + /// + private sealed class ExpressionPresenceVisitor : DataServiceALinqExpressionVisitor + { + #region Private fields + + /// Target expression being sought. + private readonly Expression target; + + /// Whether the target has been found. + private bool found; + + #endregion Private fields + + /// + /// Initializes a new that + /// searches for the given . + /// + /// Target expression to look for. + private ExpressionPresenceVisitor(Expression target) + { + Debug.Assert(target != null, "target != null"); + this.target = target; + } + + /// + /// Checks whether the specified can + /// be found in the given . + /// + /// Expression sought. + /// Expression tree to look into. + /// true if target is found at least once; false otherwise. + internal static bool IsExpressionPresent(Expression target, Expression tree) + { + Debug.Assert(target != null, "target != null"); + Debug.Assert(tree != null, "tree != null"); + + ExpressionPresenceVisitor visitor = new ExpressionPresenceVisitor(target); + visitor.Visit(tree); + return visitor.found; + } + + /// Visits the specified expression. + /// Expression to visit. + /// The visited expression (). + internal override Expression Visit(Expression exp) + { + Expression result; + + // After finding the node, there is no need to visit any further. + if (this.found || object.ReferenceEquals(this.target, exp)) + { + this.found = true; + result = exp; + } + else + { + result = base.Visit(exp); + } + + return result; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpression.cs new file mode 100644 index 0000000..2826628 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpression.cs @@ -0,0 +1,270 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq.Expressions; + + #endregion Namespaces + + /// + /// The counting option for the resource expression + /// + internal enum CountOption + { + /// No counting + None, + + /// + /// Translates to the $count segment. + /// Example: http://host/service/Categories(1)/Products/$count + /// + CountSegment, + + /// + /// Translates to the $count=true query option. + /// Example: http://host/service/Products?$count=true + /// + CountQuery + } + + /// + /// Abstract base class for expressions that support Query Options + /// + internal abstract class ResourceExpression : Expression + { + #region Fields + + /// Source expression. + protected readonly Expression source; + + /// Singleton InputReferenceExpression that should be used to indicate a reference to this element of the resource path + protected InputReferenceExpression inputRef; + + /// The CLR type this node will evaluate into. + private Type type; + + /// expand paths + private List expandPaths; + + /// The count query option for the resource set + private CountOption countOption; + + /// custom query options + private Dictionary customQueryOptions; + + /// projection expression + private ProjectionQueryOptionExpression projection; + + /// Uri version for the expression and also the expand and projection paths + private Version uriVersion; + + /// name of operation + private string operationName; + + /// names and values of parameters + private Dictionary operationParameters; + + /// false for function, true for action + private bool isAction; + + #endregion Fields + + /// + /// Creates a Resource expression + /// + /// the source expression + /// the return type of the expression + /// the expand paths + /// the count option + /// The custom query options + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + internal ResourceExpression(Expression source, Type type, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion) + : this(source, type, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion, null, null, false) + { + } + + /// + /// Creates a Resource expression + /// + /// the source expression + /// the return type of the expression + /// the expand paths + /// the count option + /// The custom query options + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + /// name of function + /// parameters' names and values of function + /// action flag + internal ResourceExpression(Expression source, Type type, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion, string operationName, Dictionary operationParameters, bool isAction) + { + this.source = source; + this.type = type; + this.expandPaths = expandPaths ?? new List(); + this.countOption = countOption; + this.customQueryOptions = customQueryOptions ?? new Dictionary(ReferenceEqualityComparer.Instance); + this.projection = projection; + this.ResourceTypeAs = resourceTypeAs; + this.uriVersion = uriVersion ?? Util.ODataVersion4; + this.operationName = operationName; + this.OperationParameters = operationParameters ?? new Dictionary(StringComparer.Ordinal); + this.isAction = isAction; + } + + /// + /// The of the value represented by this . + /// + public override Type Type + { + get { return this.type; } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "type", Justification = "It is the value being used to set the field")] + abstract internal ResourceExpression CreateCloneWithNewType(Type type); + + abstract internal bool HasQueryOptions { get; } + + abstract internal bool IsOperationInvocation { get; } + + /// + /// Resource type for this expression (for sets, this is the element type). + /// Never null. + /// + internal abstract Type ResourceType { get; } + + /// + /// The resource type that this expression is explicitly converted to by a TypeAs + /// expression (i.e., "as" operator in C#, "TryCast" in VB). Null if no conversion. + /// + internal Type ResourceTypeAs { get; set; } + + /// + /// Uri version from the expand and projection paths + /// + internal Version UriVersion + { + get + { + return this.uriVersion; + } + } + + /// + /// Does this expression produce at most 1 resource? + /// + abstract internal bool IsSingleton { get; } + + /// + /// Expand query option for ResourceSet + /// + internal virtual List ExpandPaths + { + get { return this.expandPaths; } + set { this.expandPaths = value; } + } + + /// + /// Count query option for ResourceSet + /// + internal virtual CountOption CountOption + { + get { return this.countOption; } + set { this.countOption = value; } + } + + /// + /// custom query options for ResourceSet + /// + internal virtual Dictionary CustomQueryOptions + { + get { return this.customQueryOptions; } + set { this.customQueryOptions = value; } + } + + /// Description of the projection on a resource. + /// + /// This property is set by the ProjectionAnalyzer component (so it + /// mutates this instance), or by the ResourceBinder when it clones + /// a ResourceExpression. + /// + internal ProjectionQueryOptionExpression Projection + { + get { return this.projection; } + set { this.projection = value; } + } + + /// + /// Gets the source expression. + /// + internal Expression Source + { + get + { + return this.source; + } + } + + /// + /// operation name + /// + internal string OperationName + { + get { return this.operationName; } + set { this.operationName = value; } + } + + /// + /// operation parameter names and parameters pair for Resource + /// + internal Dictionary OperationParameters + { + get { return this.operationParameters; } + set { this.operationParameters = value; } + } + + /// + /// false for function, true for action + /// + internal bool IsAction + { + get { return this.isAction; } + set { this.isAction = value; } + } + + /// + /// Creates an that refers to this component of the resource path. + /// The returned expression is guaranteed to be reference-equal (object.ReferenceEquals) + /// to any other InputReferenceExpression that also refers to this resource path component. + /// + /// The InputReferenceExpression that refers to this resource path component + internal InputReferenceExpression CreateReference() + { + if (this.inputRef == null) + { + this.inputRef = new InputReferenceExpression(this); + } + + return this.inputRef; + } + + /// Raise the UriVersion if it is lower than . + /// Uri version from the expand and projection paths + internal void RaiseUriVersion(Version newVersion) + { + Debug.Assert(newVersion != null, "newVersion != null"); + WebUtil.RaiseVersion(ref this.uriVersion, newVersion); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpressionType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpressionType.cs new file mode 100644 index 0000000..916555d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceExpressionType.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// Enum for resource expression types + internal enum ResourceExpressionType + { + /// ResourceSet Expression + RootResourceSet = 10000, + + /// Single resource expression, used to represent singleton. + RootSingleResource, + + /// Resource Navigation Expression + ResourceNavigationProperty, + + /// Resource Navigation Expression to Singleton + ResourceNavigationPropertySingleton, + + /// Take Query Option Expression + TakeQueryOption, + + /// Skip Query Option Expression + SkipQueryOption, + + /// OrderBy Query Option Expression + OrderByQueryOption, + + /// Filter Query Option Expression + FilterQueryOption, + + /// Reference to a bound component of the resource set path + InputReference, + + /// Projection Query Option Expression + ProjectionQueryOption, + + /// Expand Query Option Expression + ExpandQueryOption, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceSetExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceSetExpression.cs new file mode 100644 index 0000000..aa15882 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/ResourceSetExpression.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + + /// ResourceSet Expression. + [DebuggerDisplay("ResourceSetExpression {Source}.{MemberExpression}")] + internal class ResourceSetExpression : QueryableResourceExpression + { + /// + /// Creates a resource set expression + /// + /// the return type of the expression + /// the source expression + /// property member name + /// the element type of the resource + /// expand paths for resource set + /// count query option for the resource + /// custom query options for resource + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + internal ResourceSetExpression(Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion) + : base(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion) + { + } + + /// + /// Creates a resource set expression + /// + /// the return type of the expression + /// the source expression + /// property member name + /// the element type of the resource + /// expand paths for resource set + /// count query option for the resource + /// custom query options for resource + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + /// name of function + /// parameters' names and values of function + /// action flag + internal ResourceSetExpression(Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion, string operationName, Dictionary operationParameters, bool isAction) + : base(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion, operationName, operationParameters, isAction) + { + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return this.source != null ? (ExpressionType)ResourceExpressionType.ResourceNavigationProperty : (ExpressionType)ResourceExpressionType.RootResourceSet; } + } + + /// + /// A resource set produces at most 1 result if constrained by a key predicate + /// + internal override bool IsSingleton + { + get { return this.KeyPredicateConjuncts.Count > 0; } + } + + /// + /// Has a key predicate restriction been applied to this ResourceSet? + /// + internal bool HasKeyPredicate + { + get { return this.KeyPredicateConjuncts.Count > 0; } + } + + /// + /// A resource set invocates functions + /// + internal override bool IsOperationInvocation + { + get { return this.OperationName != null; } + } + + /// + /// Creates a clone of the current ResourceSetExpression with the specified expression and resource types + /// + /// The new expression type + /// The new resource type + /// A copy of this with the new types + protected override QueryableResourceExpression CreateCloneWithNewTypes(Type newType, Type newResourceType) + { + return new ResourceSetExpression( + newType, + this.source, + this.MemberExpression, + newResourceType, + this.ExpandPaths.ToList(), + this.CountOption, + this.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + this.Projection, + this.ResourceTypeAs, + this.UriVersion, + this.OperationName, + this.OperationParameters, + this.IsAction); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathBuilder.cs new file mode 100644 index 0000000..9eee901 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathBuilder.cs @@ -0,0 +1,284 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.OData.Client.ALinq.UriParser; + using Microsoft.OData.Client.Metadata; + + + #endregion Namespaces + + /// + /// Holds state (Path, lambda parameter stack, etc) for projection analysis. + /// + internal class SelectExpandPathBuilder + { + #region Private fields + + /// + /// This class is used as a marker for an entity projected in its entirety. + /// + private const char EntireEntityMarker = UriHelper.ASTERISK; + + /// + /// The list of projection paths as PathSegmentTokens. + /// + private readonly List projectionPaths = new List(); + + /// + /// The list of expansion paths as PathSegmentTokens. + /// + private readonly List expandPaths = new List(); + + /// + /// The stack of parameter expressions. + /// + private readonly Stack parameterExpressions = new Stack(); + + /// + /// The dictionary linking parameter expressions to their base paths. + /// + private readonly Dictionary basePaths = new Dictionary(ReferenceEqualityComparer.Instance); + + /// + /// The request data service version for the projection and expand paths + /// + private Version uriVersion; + + /// + /// Flag indicating whether we're processing the first segment in a new path. + /// + private bool firstSegmentInNewPath; + + /// + /// Summary to indicate whether we're building from an empty base path or not + /// + private bool basePathIsEmpty; + + #endregion Private fields + + /// + /// Initializes a new instance. + /// + public SelectExpandPathBuilder() + { + firstSegmentInNewPath = true; + this.uriVersion = Util.ODataVersion4; + } + + /// + /// Get a list of strings that represent the current list of ProjectionPaths + /// + public IEnumerable ProjectionPaths + { + get + { + return this.WriteProjectionPaths(); + } + } + + /// + /// Get a list of strings that represent the current list of ExpansionPaths. + /// + public IEnumerable ExpandPaths + { + get + { + return this.WriteExpansionPaths(); + } + } + + /// + /// The request data service version for the projection and expand paths + /// + public Version UriVersion + { + get + { + return this.uriVersion; + } + } + + /// + /// Get the parameter expression that is currently in scope. + /// + public ParameterExpression ParamExpressionInScope + { + get + { + Debug.Assert(parameterExpressions.Count > 0, "parameterExpressions.Count > 0"); + return parameterExpressions.Peek(); + } + } + + /// + /// Add a new ParameterExpression to the stack. + /// + /// The parameter expression to add. + public void PushParamExpression(ParameterExpression pe) + { + PathSegmentToken basePath = expandPaths.LastOrDefault(); + basePaths.Add(pe, basePath); + expandPaths.Remove(basePath); + parameterExpressions.Push(pe); + } + + /// + /// Pop the top parameter expression off the stack. + /// + public void PopParamExpression() + { + parameterExpressions.Pop(); + } + + /// + /// Starts a new path. + /// + public void StartNewPath() + { + Debug.Assert(this.ParamExpressionInScope != null, "this.ParamExpressionInScope != null -- should not be starting new path with no lambda parameter in scope."); + + PathSegmentToken basePath = basePaths[this.ParamExpressionInScope]; + PathSegmentToken newExpandPathToAdd; + if (basePath != null) + { + NewTreeBuilder newTreeBuilder = new NewTreeBuilder(); + newExpandPathToAdd = basePath.Accept(newTreeBuilder); + } + else + { + newExpandPathToAdd = null; + } + + expandPaths.Add(newExpandPathToAdd); + + firstSegmentInNewPath = true; + basePathIsEmpty = basePath == null; + } + + /// + /// Appends the given property and source TypeAs to the projection and expand paths. + /// + /// Navigation property + /// The TypeAs type if the source of the member access expression is a TypeAs operation. Null otherwise. + /// Data service context instance. + public void AppendPropertyToPath(PropertyInfo pi, Type convertedSourceType, DataServiceContext context) + { + Debug.Assert(pi != null, "pi != null"); + + bool propertyTypeisEntityType = ClientTypeUtil.TypeOrElementTypeIsEntity(pi.PropertyType); + + string convertedSourceTypeName = (convertedSourceType == null) ? + null : + UriHelper.GetEntityTypeNameForUriAndValidateMaxProtocolVersion(convertedSourceType, context, ref this.uriVersion); + + string propertyServerDefinedName = ClientTypeUtil.GetServerDefinedName(pi); + + string propertyName = convertedSourceType != null ? + String.Join(UriHelper.FORWARDSLASH.ToString(), new string[] { convertedSourceTypeName, propertyServerDefinedName }) : + propertyServerDefinedName; + + if (propertyTypeisEntityType) + { + // an entity, so need to append to expand path only + AppendToExpandPath(propertyName, false); + } + else + { + // if this is a non-entity, then it an either be + // 1) a top level property that we're selecting + // -or- + // 2) a lower level property being selected via a nav prop. + // + // if 1) Then we just add it to the projection path + // if 2) then we add it to the expand path instead. + + // we decide that based on whether this is the first property we're adding to + // the path, and whether the base path we're starting from is empty. + if (firstSegmentInNewPath && basePathIsEmpty) + { + AppendToProjectionPath(propertyName); + } + else + { + AppendToExpandPath(propertyName, true); + } + } + + // clear the firstSegmentInPath flag + firstSegmentInNewPath = false; + } + + /// + /// Write out the current list of expansion paths as a list of strings. + /// + /// The current list of expansion paths as a list of strings. + private IEnumerable WriteExpansionPaths() + { + SelectExpandPathToStringVisitor visitor = new SelectExpandPathToStringVisitor(); + return this.expandPaths.Where(path => path != null).Select(path => path.Accept(visitor)); + } + + /// + /// Write out the current list of projection paths as a list of strings. + /// + /// The current list of projection paths as a list of strings. + private IEnumerable WriteProjectionPaths() + { + return this.projectionPaths.Where(path => path != null).Select(path => path.Identifier); + } + + /// + /// Appends a name of a property/link/type to the current projection path. + /// + /// name of the property/link/type which needs to be added to the select path. + private void AppendToProjectionPath(string name) + { + // get rid of the * if its present + foreach (PathSegmentToken pathSegment in projectionPaths) + { + if (pathSegment != null && pathSegment.Identifier == EntireEntityMarker.ToString()) + { + projectionPaths.Remove(pathSegment); + } + } + + projectionPaths.Add(new NonSystemToken(name, /*namedValues*/ null, /*nextToken*/ null)); + } + + /// + /// Appends a name of a property/link/type to the current expand path. + /// + /// name of the property/link/type which needs to be added to the expand path. + /// is this a structural property. + private void AppendToExpandPath(string name, bool isStructural) + { + PathSegmentToken path = this.expandPaths.LastOrDefault(); + NonSystemToken newToken = new NonSystemToken(name, /*namedValues*/null, /*nextToken*/null); + newToken.IsStructuralProperty = isStructural; + if (path != null) + { + expandPaths.Remove(path); + AddNewEndingTokenVisitor addNewEndingTokenVisitor = new AddNewEndingTokenVisitor(newToken); + path.Accept(addNewEndingTokenVisitor); + expandPaths.Add(path); + } + else + { + expandPaths.Add(newToken); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathToStringVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathToStringVisitor.cs new file mode 100644 index 0000000..e442f29 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SelectExpandPathToStringVisitor.cs @@ -0,0 +1,112 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using Microsoft.OData.Client.ALinq.UriParser; + using Microsoft.OData; + + /// + /// Translate from an expand path token to a string. + /// + internal class SelectExpandPathToStringVisitor : IPathSegmentTokenVisitor + { + /// + /// The beginning of a sub-select clause + /// + public const string SelectClause = "($select="; + + /// + /// The beginning of a sub-expand clause + /// + public const string StartingExpandClause = "($expand="; + + /// + /// The beginning of an expand clause for the current level. + /// + public const string NonStartingExpandClause = "$expand="; + + /// + /// Translate a system token, always throws. + /// + /// the system token. + /// Always throws, because a system token is illegal in this case. + public string Visit(SystemToken tokenIn) + { + throw new NotSupportedException(Strings.ALinq_IllegalSystemQueryOption(tokenIn.Identifier)); + } + + /// + /// Translate a NonSystemToken into a select or expand string. + /// + /// the non sytem token to translate + /// The string representation of a given NonSystemToken + public string Visit(NonSystemToken tokenIn) + { + if (tokenIn.NextToken == null) + { + return tokenIn.Identifier; + } + else + { + if (tokenIn.NextToken.IsStructuralProperty) + { + PathSegmentToken firstNonStructuralProperty; + string selectClauses = WriteNextStructuralProperties(tokenIn.NextToken, out firstNonStructuralProperty); + if (firstNonStructuralProperty != null) + { + return tokenIn.Identifier + SelectClause + selectClauses + UriHelper.SEMICOLON + NonStartingExpandClause + firstNonStructuralProperty.Accept(this) + UriHelper.RIGHTPAREN; + } + else + { + return tokenIn.Identifier + SelectClause + selectClauses + UriHelper.RIGHTPAREN; + } + } + else + { + return tokenIn.Identifier + StartingExpandClause + tokenIn.NextToken.Accept(this) + UriHelper.RIGHTPAREN; + } + } + } + + /// + /// Follow a chain of structrual properties until we hit a non-structural property + /// + /// the first structural property we hit + /// the first non structural property we hit + /// a comma separated list of structural properties + private static string WriteNextStructuralProperties(PathSegmentToken firstStructuralProperty, out PathSegmentToken firstNonStructuralProperty) + { + firstNonStructuralProperty = firstStructuralProperty; + string stringToWrite = ""; + while (firstNonStructuralProperty.IsStructuralProperty) + { + if (firstNonStructuralProperty.NextToken != null) + { + if (firstNonStructuralProperty.NextToken.IsStructuralProperty) + { + stringToWrite += firstNonStructuralProperty.Identifier + ","; + } + else + { + stringToWrite += firstNonStructuralProperty.Identifier; + } + + firstNonStructuralProperty = firstNonStructuralProperty.NextToken; + } + else + { + stringToWrite += firstNonStructuralProperty.Identifier; + firstNonStructuralProperty = null; + return stringToWrite; + } + } + + return stringToWrite; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SingletonResourceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SingletonResourceExpression.cs new file mode 100644 index 0000000..f4ee6e6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SingletonResourceExpression.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + + /// Singleton Resource Expression, used to represent singleton, may extend to support FunctionImport + [DebuggerDisplay("SingletonResourceExpression {Source}.{MemberExpression}")] + internal class SingletonResourceExpression : QueryableResourceExpression + { + /// + /// Creates a singleton resource expression + /// + /// the return type of the expression + /// the source expression + /// property member name + /// the element type of the resource + /// expand paths for resource set + /// count query option for the resource + /// custom query options for resource + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + internal SingletonResourceExpression(Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion) : + base(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion) + { + UseFilterAsPredicate = true; + } + + /// + /// Creates a singleton resource expression + /// + /// the return type of the expression + /// the source expression + /// property member name + /// the element type of the resource + /// expand paths for resource set + /// count query option for the resource + /// custom query options for resource + /// the projection expression + /// TypeAs type + /// version of the Uri from the expand and projection paths + /// name of function + /// parameters' names and values of function + /// action flag + internal SingletonResourceExpression(Type type, Expression source, Expression memberExpression, Type resourceType, List expandPaths, CountOption countOption, Dictionary customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion, string functionName, Dictionary functionParameters, bool isAction) : + base(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion, functionName, functionParameters, isAction) + { + UseFilterAsPredicate = true; + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.RootSingleResource; } + } + + /// + /// Always be singleton. + /// + internal override bool IsSingleton + { + get { return true; } + } + + /// + /// Maybe function import invocation. + /// + internal override bool IsOperationInvocation + { + get { return this.OperationName != null; } + } + + /// + /// Creates a clone of the current SingletResourceExpression with the specified expression and resource types + /// + /// The new expression type + /// The new resource type + /// A copy of this with the new types + protected override QueryableResourceExpression CreateCloneWithNewTypes(Type newType, Type newResourceType) + { + return new SingletonResourceExpression( + newType, + this.source, + this.MemberExpression, + newResourceType, + this.ExpandPaths.ToList(), + this.CountOption, + this.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + this.Projection, + this.ResourceTypeAs, + this.UriVersion, + this.OperationName, + this.OperationParameters, + this.IsAction); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SkipQueryOptionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SkipQueryOptionExpression.cs new file mode 100644 index 0000000..d3e560a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/SkipQueryOptionExpression.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Linq.Expressions; + + /// + /// An resource specific expression representing a skip query option. + /// + [DebuggerDisplay("SkipQueryOptionExpression {SkipAmount}")] + internal class SkipQueryOptionExpression : QueryOptionExpression + { + /// amount to skip + private ConstantExpression skipAmount; + + /// + /// Creates a SkipQueryOption expression + /// + /// the return type of the expression + /// the query option value + internal SkipQueryOptionExpression(Type type, ConstantExpression skipAmount) + : base(type) + { + this.skipAmount = skipAmount; + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.SkipQueryOption; } + } + + /// + /// query option value + /// + internal ConstantExpression SkipAmount + { + get + { + return this.skipAmount; + } + } + + /// + /// Composes the expression with this one when it's specified multiple times. + /// + /// to compose. + /// + /// The expression that results from composing the expression with this one. + /// + internal override QueryOptionExpression ComposeMultipleSpecification(QueryOptionExpression previous) + { + Debug.Assert(previous != null, "other != null"); + Debug.Assert(previous.GetType() == this.GetType(), "other.GetType == this.GetType() -- otherwise it's not the same specification"); + Debug.Assert(this.skipAmount != null, "this.skipAmount != null"); + Debug.Assert( + this.skipAmount.Type == typeof(int), + "this.skipAmount.Type == typeof(int) -- otherwise it wouldn't have matched the Enumerable.Skip(source, int count) signature"); + int thisValue = (int)this.skipAmount.Value; + int previousValue = (int)((SkipQueryOptionExpression)previous).skipAmount.Value; + return new SkipQueryOptionExpression(this.Type, Expression.Constant(thisValue + previousValue, typeof(int))); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TakeQueryOptionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TakeQueryOptionExpression.cs new file mode 100644 index 0000000..a92e7cf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TakeQueryOptionExpression.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Linq.Expressions; + + /// + /// An resource specific expression representing a take query option. + /// + [DebuggerDisplay("TakeQueryOptionExpression {TakeAmount}")] + internal class TakeQueryOptionExpression : QueryOptionExpression + { + /// amount to skip + private ConstantExpression takeAmount; + + /// + /// Creates a TakeQueryOption expression + /// + /// the return type of the expression + /// the query option value + internal TakeQueryOptionExpression(Type type, ConstantExpression takeAmount) + : base(type) + { + this.takeAmount = takeAmount; + } + + /// + /// The of the . + /// + public override ExpressionType NodeType + { + get { return (ExpressionType)ResourceExpressionType.TakeQueryOption; } + } + + /// + /// query option value + /// + internal ConstantExpression TakeAmount + { + get + { + return this.takeAmount; + } + } + + /// + /// Composes the expression with this one when it's specified multiple times. + /// + /// to compose. + /// + /// The expression that results from composing the expression with this one. + /// + internal override QueryOptionExpression ComposeMultipleSpecification(QueryOptionExpression previous) + { + Debug.Assert(previous != null, "other != null"); + Debug.Assert(previous.GetType() == this.GetType(), "other.GetType == this.GetType() -- otherwise it's not the same specification"); + Debug.Assert(this.takeAmount != null, "this.takeAmount != null"); + Debug.Assert( + this.takeAmount.Type == typeof(int), + "this.takeAmount.Type == typeof(int) -- otherwise it wouldn't have matched the Enumerable.Take(source, int count) signature"); + int thisValue = (int)this.takeAmount.Value; + int previousValue = (int)((TakeQueryOptionExpression)previous).takeAmount.Value; + return (thisValue < previousValue) ? this : previous; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TypeSystem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TypeSystem.cs new file mode 100644 index 0000000..e22c7fb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/TypeSystem.cs @@ -0,0 +1,377 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Reflection; + using Microsoft.OData.Edm; + using Microsoft.Spatial; + + /// Utility functions for processing Expression trees + internal static class TypeSystem + { + /// Used for comparison with external assemblies for silverlight like Microsoft.VisualBasic. + private const string OfficialSilverLightPublicKeyToken = "31bf3856ad364e35"; + + /// Used for comparison with external assemblies for desktop like Microsoft.VisualBasic. + private const string OfficialDesktopPublicKeyToken = "b03f5f7f11d50a3a"; + + /// Method map for methods in URI query options + private static readonly Dictionary expressionMethodMap; + + /// VB Method map for methods in URI query options + private static readonly Dictionary expressionVBMethodMap; + + /// Properties that should be represented as methods + private static readonly Dictionary propertiesAsMethodsMap; + + /// + /// Cache used to store element type (TElement) for key Type if key Type implements IEnumerable{TElement} or + /// null if the key Type does not implement IEnumerable{T} e.g.: + /// List{Entity} - Entity + /// Entity - null + /// + private static readonly Dictionary ienumerableElementTypeCache = new Dictionary(EqualityComparer.Default); + + /// VB Assembly name + private const string VisualBasicAssemblyName = "Microsoft.VisualBasic,"; + + /// VB Assembly public key token +#pragma warning disable 436 + private const string VisualBasicAssemblyPublicKeyToken = "PublicKeyToken=" + OfficialDesktopPublicKeyToken; +#pragma warning restore 436 + /// + /// Initializes method map + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Cleaner code")] + static TypeSystem() + { +#if !PORTABLELIB + const int ExpectedCount = 43; +#endif + // string functions + expressionMethodMap = new Dictionary(EqualityComparer.Default); + expressionMethodMap.Add(typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), @"contains"); + expressionMethodMap.Add(typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }), @"endswith"); + expressionMethodMap.Add(typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), @"startswith"); + expressionMethodMap.Add(typeof(string).GetMethod("IndexOf", new Type[] { typeof(string) }), @"indexof"); + expressionMethodMap.Add(typeof(string).GetMethod("Replace", new Type[] { typeof(string), typeof(string) }), @"replace"); + expressionMethodMap.Add(typeof(string).GetMethod("Substring", new Type[] { typeof(int) }), @"substring"); + expressionMethodMap.Add(typeof(string).GetMethod("Substring", new Type[] { typeof(int), typeof(int) }), @"substring"); + expressionMethodMap.Add(typeof(string).GetMethod("ToLower", PlatformHelper.EmptyTypes), @"tolower"); + expressionMethodMap.Add(typeof(string).GetMethod("ToUpper", PlatformHelper.EmptyTypes), @"toupper"); + expressionMethodMap.Add(typeof(string).GetMethod("Trim", PlatformHelper.EmptyTypes), @"trim"); + expressionMethodMap.Add(typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }), @"concat"); + expressionMethodMap.Add(typeof(string).GetProperty("Length", typeof(int)).GetGetMethod(), @"length"); + + // date methods + expressionMethodMap.Add(typeof(Date).GetProperty("Day", typeof(int)).GetGetMethod(), @"day"); + expressionMethodMap.Add(typeof(Date).GetProperty("Month", typeof(int)).GetGetMethod(), @"month"); + expressionMethodMap.Add(typeof(Date).GetProperty("Year", typeof(int)).GetGetMethod(), @"year"); + + // timeOfDay methods + expressionMethodMap.Add(typeof(TimeOfDay).GetProperty("Hours", typeof(int)).GetGetMethod(), @"hour"); + expressionMethodMap.Add(typeof(TimeOfDay).GetProperty("Minutes", typeof(int)).GetGetMethod(), @"minute"); + expressionMethodMap.Add(typeof(TimeOfDay).GetProperty("Seconds", typeof(int)).GetGetMethod(), @"second"); + + // datetimeoffset methods + expressionMethodMap.Add(typeof(DateTimeOffset).GetProperty("Date", typeof(DateTime)).GetGetMethod(), @"date"); + expressionMethodMap.Add(typeof(DateTimeOffset).GetProperty("Day", typeof(int)).GetGetMethod(), @"day"); + expressionMethodMap.Add(typeof(DateTimeOffset).GetProperty("Hour", typeof(int)).GetGetMethod(), @"hour"); + expressionMethodMap.Add(typeof(DateTimeOffset).GetProperty("Month", typeof(int)).GetGetMethod(), @"month"); + expressionMethodMap.Add(typeof(DateTimeOffset).GetProperty("Minute", typeof(int)).GetGetMethod(), @"minute"); + expressionMethodMap.Add(typeof(DateTimeOffset).GetProperty("Second", typeof(int)).GetGetMethod(), @"second"); + expressionMethodMap.Add(typeof(DateTimeOffset).GetProperty("Year", typeof(int)).GetGetMethod(), @"year"); + + // datetime methods + expressionMethodMap.Add(typeof(DateTime).GetProperty("Date", typeof(DateTime)).GetGetMethod(), @"date"); + expressionMethodMap.Add(typeof(DateTime).GetProperty("Day", typeof(int)).GetGetMethod(), @"day"); + expressionMethodMap.Add(typeof(DateTime).GetProperty("Hour", typeof(int)).GetGetMethod(), @"hour"); + expressionMethodMap.Add(typeof(DateTime).GetProperty("Month", typeof(int)).GetGetMethod(), @"month"); + expressionMethodMap.Add(typeof(DateTime).GetProperty("Minute", typeof(int)).GetGetMethod(), @"minute"); + expressionMethodMap.Add(typeof(DateTime).GetProperty("Second", typeof(int)).GetGetMethod(), @"second"); + expressionMethodMap.Add(typeof(DateTime).GetProperty("Year", typeof(int)).GetGetMethod(), @"year"); + + // timespan methods + expressionMethodMap.Add(typeof(TimeSpan).GetProperty("Hours", typeof(int)).GetGetMethod(), @"hour"); + expressionMethodMap.Add(typeof(TimeSpan).GetProperty("Minutes", typeof(int)).GetGetMethod(), @"minute"); + expressionMethodMap.Add(typeof(TimeSpan).GetProperty("Seconds", typeof(int)).GetGetMethod(), @"second"); + + // math methods + expressionMethodMap.Add(typeof(Math).GetMethod("Round", new Type[] { typeof(double) }), @"round"); + expressionMethodMap.Add(typeof(Math).GetMethod("Round", new Type[] { typeof(decimal) }), @"round"); + expressionMethodMap.Add(typeof(Math).GetMethod("Floor", new Type[] { typeof(double) }), @"floor"); + + MethodInfo foundMethod = null; + if (typeof(Math).TryGetMethod("Floor", new Type[] { typeof(decimal) }, out foundMethod)) + { + expressionMethodMap.Add(foundMethod, @"floor"); + } + + expressionMethodMap.Add(typeof(Math).GetMethod("Ceiling", new Type[] { typeof(double) }), @"ceiling"); + + if (typeof(Math).TryGetMethod("Ceiling", new Type[] { typeof(decimal) }, out foundMethod)) + { + expressionMethodMap.Add(foundMethod, @"ceiling"); + } + + // Spatial methods + expressionMethodMap.Add(typeof(GeographyOperationsExtensions).GetMethod("Distance", new Type[] { typeof(GeographyPoint), typeof(GeographyPoint) }, true /*isPublic*/, true /*isStatic*/), @"geo.distance"); + expressionMethodMap.Add(typeof(GeometryOperationsExtensions).GetMethod("Distance", new Type[] { typeof(GeometryPoint), typeof(GeometryPoint) }, true /*isPublic*/, true /*isStatic*/), @"geo.distance"); + + // Portable Lib can be 35 or 33 depending on if its running on Silverlight or not, disabling in this case +#if !PORTABLELIB + Debug.Assert(expressionMethodMap.Count == ExpectedCount, "expressionMethodMap.Count == ExpectedCount"); +#endif + // vb methods + // lookup these by type name + method name + expressionVBMethodMap = new Dictionary(StringComparer.Ordinal); + + expressionVBMethodMap.Add("Microsoft.VisualBasic.Strings.Trim", @"trim"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.Strings.Len", @"length"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.Strings.Mid", @"substring"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.Strings.UCase", @"toupper"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.Strings.LCase", @"tolower"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.DateAndTime.Year", @"year"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.DateAndTime.Month", @"month"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.DateAndTime.Day", @"day"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.DateAndTime.Hour", @"hour"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.DateAndTime.Minute", @"minute"); + expressionVBMethodMap.Add("Microsoft.VisualBasic.DateAndTime.Second", @"second"); + + Debug.Assert(expressionVBMethodMap.Count == 11, "expressionVBMethodMap.Count == 11"); + + propertiesAsMethodsMap = new Dictionary(EqualityComparer.Default); + propertiesAsMethodsMap.Add( + typeof(string).GetProperty("Length", typeof(int)), + typeof(string).GetProperty("Length", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTimeOffset).GetProperty("Day", typeof(int)), + typeof(DateTimeOffset).GetProperty("Day", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTimeOffset).GetProperty("Date", typeof(DateTime)), + typeof(DateTimeOffset).GetProperty("Date", typeof(DateTime)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTimeOffset).GetProperty("Hour", typeof(int)), + typeof(DateTimeOffset).GetProperty("Hour", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTimeOffset).GetProperty("Minute", typeof(int)), + typeof(DateTimeOffset).GetProperty("Minute", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTimeOffset).GetProperty("Second", typeof(int)), + typeof(DateTimeOffset).GetProperty("Second", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTimeOffset).GetProperty("Month", typeof(int)), + typeof(DateTimeOffset).GetProperty("Month", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTimeOffset).GetProperty("Year", typeof(int)), + typeof(DateTimeOffset).GetProperty("Year", typeof(int)).GetGetMethod()); + + propertiesAsMethodsMap.Add( + typeof(DateTime).GetProperty("Day", typeof(int)), + typeof(DateTime).GetProperty("Day", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTime).GetProperty("Date", typeof(DateTime)), + typeof(DateTime).GetProperty("Date", typeof(DateTime)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTime).GetProperty("Hour", typeof(int)), + typeof(DateTime).GetProperty("Hour", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTime).GetProperty("Minute", typeof(int)), + typeof(DateTime).GetProperty("Minute", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTime).GetProperty("Second", typeof(int)), + typeof(DateTime).GetProperty("Second", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTime).GetProperty("Month", typeof(int)), + typeof(DateTime).GetProperty("Month", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(DateTime).GetProperty("Year", typeof(int)), + typeof(DateTime).GetProperty("Year", typeof(int)).GetGetMethod()); + + propertiesAsMethodsMap.Add( + typeof(TimeSpan).GetProperty("Hours", typeof(int)), + typeof(TimeSpan).GetProperty("Hours", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(TimeSpan).GetProperty("Minutes", typeof(int)), + typeof(TimeSpan).GetProperty("Minutes", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(TimeSpan).GetProperty("Seconds", typeof(int)), + typeof(TimeSpan).GetProperty("Seconds", typeof(int)).GetGetMethod()); + + propertiesAsMethodsMap.Add( + typeof(TimeOfDay).GetProperty("Hours", typeof(int)), + typeof(TimeOfDay).GetProperty("Hours", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(TimeOfDay).GetProperty("Minutes", typeof(int)), + typeof(TimeOfDay).GetProperty("Minutes", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(TimeOfDay).GetProperty("Seconds", typeof(int)), + typeof(TimeOfDay).GetProperty("Seconds", typeof(int)).GetGetMethod()); + + propertiesAsMethodsMap.Add( + typeof(Date).GetProperty("Year", typeof(int)), + typeof(Date).GetProperty("Year", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(Date).GetProperty("Month", typeof(int)), + typeof(Date).GetProperty("Month", typeof(int)).GetGetMethod()); + propertiesAsMethodsMap.Add( + typeof(Date).GetProperty("Day", typeof(int)), + typeof(Date).GetProperty("Day", typeof(int)).GetGetMethod()); + + Debug.Assert(propertiesAsMethodsMap.Count == 24, "propertiesAsMethodsMap.Count == 24"); + } + + /// + /// Sees if method has URI equivalent + /// + /// The method info + /// uri method name + /// true/ false + internal static bool TryGetQueryOptionMethod(MethodInfo mi, out string methodName) + { + return (expressionMethodMap.TryGetValue(mi, out methodName) || + (IsVisualBasicAssembly(mi.DeclaringType.GetAssembly()) && + expressionVBMethodMap.TryGetValue(mi.DeclaringType.FullName + "." + mi.Name, out methodName))); + } + + /// + /// Sees if property can be represented as method for translation to URI + /// + /// The property info + /// get method for property + /// true/ false + internal static bool TryGetPropertyAsMethod(PropertyInfo pi, out MethodInfo mi) + { + return propertiesAsMethodsMap.TryGetValue(pi, out mi); + } + + /// + /// Gets the elementtype for a sequence + /// + /// The sequence type + /// The element type + internal static Type GetElementType(Type seqType) + { + Type ienum = FindIEnumerable(seqType); + if (ienum == null) + { + return seqType; + } + + return ienum.GetGenericArguments()[0]; + } + + /// + /// Determines whether a property is private + /// + /// The PropertyInfo structure for the property + /// true/ false if property is private + internal static bool IsPrivate(PropertyInfo pi) + { + MethodInfo mi = pi.GetGetMethod() ?? pi.GetSetMethod(); + if (mi != null) + { + return mi.IsPrivate; + } + + return true; + } + + /// + /// Finds type that implements IEnumerable so can get element type + /// + /// The Type to check + /// returns the type which implements IEnumerable + internal static Type FindIEnumerable(Type seqType) + { + // shortcircuit types that never implement IEnumerable (including value types as the only value types recognized by the client + // are primitive types like decimal, GUID or DateTimeOffset) + if (seqType == null || seqType == typeof(string) || seqType.IsPrimitive() || seqType.IsValueType() || Nullable.GetUnderlyingType(seqType) != null) + { + return null; + } + + Type resultType; + lock (ienumerableElementTypeCache) + { + if (!ienumerableElementTypeCache.TryGetValue(seqType, out resultType)) + { + resultType = FindIEnumerableForNonPrimitiveType(seqType); + ienumerableElementTypeCache.Add(seqType, resultType); + } + } + + return resultType; + } + + /// Finds whether a non-primitive implements IEnumerable and returns element type if it does. + /// Type to check. + /// Type of the element if the implements IEnumerable{T}. Otherwise null. + private static Type FindIEnumerableForNonPrimitiveType(Type seqType) + { + Debug.Assert(seqType != null, "seqType != null"); + Debug.Assert(seqType != typeof(string) && !seqType.IsPrimitive() && !seqType.IsValueType() && Nullable.GetUnderlyingType(seqType) == null, "seqType must not be a primitive type (nullable or not)"); + + if (seqType.IsArray) + { + return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType()); + } + + if (seqType.IsGenericType()) + { + foreach (Type arg in seqType.GetGenericArguments()) + { + Type ienum = typeof(IEnumerable<>).MakeGenericType(arg); + if (ienum.IsAssignableFrom(seqType)) + { + return ienum; + } + } + } + + IEnumerable ifaces = seqType.GetInterfaces(); + if (ifaces != null) + { + foreach (Type iface in ifaces) + { + Type ienum = FindIEnumerable(iface); + if (ienum != null) + { + return ienum; + } + } + } + + if (seqType.GetBaseType() != null && seqType.GetBaseType() != typeof(object)) + { + return FindIEnumerable(seqType.GetBaseType()); + } + + return null; + } + + /// + /// Checks if the given assembly is the VisualBasic assembly. + /// + /// assembly to check. + /// true if the assembly is Microsoft.VisualBasic, otherwise returns false.root + /// + private static bool IsVisualBasicAssembly(Assembly assembly) + { + string assemblyFullName = assembly.FullName; + if (assemblyFullName.Contains(VisualBasicAssemblyName) && assembly.FullName.Contains(VisualBasicAssemblyPublicKeyToken)) + { + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriHelper.cs new file mode 100644 index 0000000..6b51d32 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriHelper.cs @@ -0,0 +1,245 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Text; + using Microsoft.OData.Client.Metadata; + + /// utility class for helping construct uris + internal static class UriHelper + { + /// forwardslash character + internal const char FORWARDSLASH = '/'; + + /// leftparan character + internal const char LEFTPAREN = '('; + + /// rightparan character + internal const char RIGHTPAREN = ')'; + + /// questionmark character + internal const char QUESTIONMARK = '?'; + + /// ampersand character + internal const char AMPERSAND = '&'; + + /// equals character + internal const char EQUALSSIGN = '='; + + /// at sign + internal const char ATSIGN = '@'; + + /// dollar sign character + internal const char DOLLARSIGN = '$'; + + /// space + internal const char SPACE = ' '; + + /// comma + internal const char COMMA = ','; + + /// colon + internal const char COLON = ':'; + + /// semicolon + internal const char SEMICOLON = ';'; + + /// single quote + internal const char QUOTE = '\''; + + /// asterisk + internal const char ASTERISK = '*'; + + /// top + internal const string OPTIONTOP = "top"; + + /// skip + internal const string OPTIONSKIP = "skip"; + + /// orderby + internal const string OPTIONORDERBY = "orderby"; + + /// where + internal const string OPTIONFILTER = "filter"; + + /// desc + internal const string OPTIONDESC = "desc"; + + /// expand + internal const string OPTIONEXPAND = "expand"; + + /// count + internal const string OPTIONCOUNT = "count"; + + /// select + internal const string OPTIONSELECT = "select"; + + /// The $format query option. + internal const string OPTIONFORMAT = "format"; + + /// true + internal const string COUNTTRUE = "true"; + + /// value + internal const string COUNT = "count"; + + /// and + internal const string AND = "and"; + + /// or + internal const string OR = "or"; + + /// eq + internal const string EQ = "eq"; + + /// ne + internal const string NE = "ne"; + + /// lt + internal const string LT = "lt"; + + /// le + internal const string LE = "le"; + + /// gt + internal const string GT = "gt"; + + /// ge + internal const string GE = "ge"; + + /// add + internal const string ADD = "add"; + + /// sub + internal const string SUB = "sub"; + + /// mul + internal const string MUL = "mul"; + + /// div + internal const string DIV = "div"; + + /// mod + internal const string MOD = "mod"; + + /// negate + internal const string NEGATE = "-"; + + /// not + internal const string NOT = "not"; + + /// null + internal const string NULL = "null"; + + /// isof + internal const string ISOF = "isof"; + + /// cast + internal const string CAST = "cast"; + + /// has + internal const string HAS = "has"; + + /// The encoded @ sign + internal const string ENCODEDATSIGN = "%40"; + + /// The encoded [ sign + internal const string ENCODEDSQUAREBRACKETSIGN = "%5B"; + + /// The encoded { sign + internal const string ENCODEDBRACESIGN = "%7B"; + + /// Gets the type name to be used in the URI for the given . + /// Type to get name for. + /// Data context used to generate type names for types. + /// The name for the , suitable for including in a URI. + internal static string GetTypeNameForUri(Type type, DataServiceContext context) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(context != null, "context != null"); + type = Nullable.GetUnderlyingType(type) ?? type; + + PrimitiveType pt; + if (PrimitiveType.TryGetPrimitiveType(type, out pt)) + { + if (pt.HasReverseMapping) + { + return pt.EdmTypeName; + } + else + { + // unsupported primitive type + throw new NotSupportedException(Strings.ALinq_CantCastToUnsupportedPrimitive(type.Name)); + } + } + else + { + return context.ResolveNameFromTypeInternal(type) ?? ClientTypeUtil.GetServerDefinedTypeFullName(type); + } + } + + /// Gets the type name to be used in the URI for the given . + /// Type to get name for. + /// Data context used to generate type names for types. + /// Data service version for the uri + /// The name for the , suitable for including in a URI. + internal static string GetEntityTypeNameForUriAndValidateMaxProtocolVersion(Type type, DataServiceContext context, ref Version uriVersion) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(context != null, "context != null"); + + if (context.MaxProtocolVersionAsVersion < Util.ODataVersion4) + { + throw new NotSupportedException(Strings.ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3); + } + + if (!ClientTypeUtil.TypeOrElementTypeIsEntity(type)) + { + throw new NotSupportedException(Strings.ALinq_TypeAsArgumentNotEntityType(type.FullName)); + } + + // Raise the uriVersion each time we write the type segment on the uri. + WebUtil.RaiseVersion(ref uriVersion, Util.ODataVersion4); + + return context.ResolveNameFromTypeInternal(type) ?? ClientTypeUtil.GetServerDefinedTypeFullName(type); + } + + /// + /// Appends a type segment to the which is building up a URI from a query. + /// + /// The string builder. + /// The type for the segment. + /// The data service context. + /// Whether or not the type segment is being appended within the path (as opposed to within a $filter or $orderby expression). + /// The current version. + internal static void AppendTypeSegment(StringBuilder stringBuilder, Type type, DataServiceContext dataServiceContext, bool inPath, ref Version version) + { + // The '$' segment is used to escape known metadata segments in the key-as-segments mode to avoid ambiguity. + // Because keys are not allowed inside filter or orderby expressions, we do not need to add it in those cases. + if (inPath && dataServiceContext.UrlKeyDelimiter == DataServiceUrlKeyDelimiter.Slash) + { + stringBuilder.Append('$'); + stringBuilder.Append(FORWARDSLASH); + } + + string typeName = GetTypeNameForUri(type, dataServiceContext); + stringBuilder.Append(typeName); + } + + /// + /// If the value represented by the string is primitive value or not. + /// + /// The string value represent the odata value. + /// True if the value is primitive value. + internal static bool IsPrimitiveValue(string value) + { + return !(value.StartsWith(UriHelper.ENCODEDBRACESIGN, StringComparison.Ordinal) || value.StartsWith(UriHelper.ENCODEDSQUAREBRACKETSIGN, StringComparison.Ordinal)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriWriter.cs new file mode 100644 index 0000000..91f1a85 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ALinq/UriWriter.cs @@ -0,0 +1,617 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Text; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Translates resource bound expression trees into URIs. + /// + internal class UriWriter : DataServiceALinqExpressionVisitor + { + /// Data context used to generate type names for types. + private readonly DataServiceContext context; + + /// stringbuilder for constructed URI. + private readonly StringBuilder uriBuilder = new StringBuilder(); + + /// The dictionary to store the alias. + private readonly Dictionary alias = new Dictionary(StringComparer.Ordinal); + + /// the request data service version for the uri. + private Version uriVersion; + + /// + /// For caching query options to be grouped + /// + private Dictionary> cachedQueryOptions = new Dictionary>(StringComparer.Ordinal); + + /// + /// Private constructor for creating UriWriter + /// + /// Data context used to generate type names for types. + private UriWriter(DataServiceContext context) + { + Debug.Assert(context != null, "context != null"); + this.context = context; + this.uriVersion = Util.ODataVersion4; + } + + /// + /// Translates resource bound expression tree to a URI. + /// + /// Data context used to generate type names for types. + /// flag to indicate whether generated URI should include () if leaf is ResourceSet + /// The expression to translate + /// uri + /// version for query + internal static void Translate(DataServiceContext context, bool addTrailingParens, Expression e, out Uri uri, out Version version) + { + var writer = new UriWriter(context); + writer.Visit(e); + string fullUri = writer.uriBuilder.ToString(); + + if (writer.alias.Any()) + { + if (fullUri.IndexOf(UriHelper.QUESTIONMARK) > -1) + { + fullUri += UriHelper.AMPERSAND; + } + else + { + fullUri += UriHelper.QUESTIONMARK; + } + + foreach (var kv in writer.alias) + { + fullUri += kv.Key; + fullUri += UriHelper.EQUALSSIGN; + fullUri += kv.Value; + fullUri += UriHelper.AMPERSAND; + } + + fullUri = fullUri.Substring(0, fullUri.Length - 1); + } + + + uri = UriUtil.CreateUri(fullUri, UriKind.Absolute); + version = writer.uriVersion; + } + + /// + /// MethodCallExpression visit method + /// + /// The MethodCallExpression expression to visit + /// The visited MethodCallExpression expression + internal override Expression VisitMethodCall(MethodCallExpression m) + { + throw Error.MethodNotSupported(m); + } + + /// + /// UnaryExpression visit method + /// + /// The UnaryExpression expression to visit + /// The visited UnaryExpression expression + internal override Expression VisitUnary(UnaryExpression u) + { + throw new NotSupportedException(Strings.ALinq_UnaryNotSupported(u.NodeType.ToString())); + } + + /// + /// BinaryExpression visit method + /// + /// The BinaryExpression expression to visit + /// The visited BinaryExpression expression + internal override Expression VisitBinary(BinaryExpression b) + { + throw new NotSupportedException(Strings.ALinq_BinaryNotSupported(b.NodeType.ToString())); + } + + /// + /// ConstantExpression visit method + /// + /// The ConstantExpression expression to visit + /// The visited ConstantExpression expression + internal override Expression VisitConstant(ConstantExpression c) + { + throw new NotSupportedException(Strings.ALinq_ConstantNotSupported(c.Value)); + } + + /// + /// TypeBinaryExpression visit method + /// + /// The TypeBinaryExpression expression to visit + /// The visited TypeBinaryExpression expression + internal override Expression VisitTypeIs(TypeBinaryExpression b) + { + throw new NotSupportedException(Strings.ALinq_TypeBinaryNotSupported); + } + + /// + /// ConditionalExpression visit method + /// + /// The ConditionalExpression expression to visit + /// The visited ConditionalExpression expression + internal override Expression VisitConditional(ConditionalExpression c) + { + throw new NotSupportedException(Strings.ALinq_ConditionalNotSupported); + } + + /// + /// ParameterExpression visit method + /// + /// The ParameterExpression expression to visit + /// The visited ParameterExpression expression + internal override Expression VisitParameter(ParameterExpression p) + { + throw new NotSupportedException(Strings.ALinq_ParameterNotSupported); + } + + /// + /// MemberExpression visit method + /// + /// The MemberExpression expression to visit + /// The visited MemberExpression expression + internal override Expression VisitMemberAccess(MemberExpression m) + { + throw new NotSupportedException(Strings.ALinq_MemberAccessNotSupported(m.Member.Name)); + } + + /// + /// LambdaExpression visit method + /// + /// The LambdaExpression to visit + /// The visited LambdaExpression + internal override Expression VisitLambda(LambdaExpression lambda) + { + throw new NotSupportedException(Strings.ALinq_LambdaNotSupported); + } + + /// + /// NewExpression visit method + /// + /// The NewExpression to visit + /// The visited NewExpression + internal override NewExpression VisitNew(NewExpression nex) + { + throw new NotSupportedException(Strings.ALinq_NewNotSupported); + } + + /// + /// MemberInitExpression visit method + /// + /// The MemberInitExpression to visit + /// The visited MemberInitExpression + internal override Expression VisitMemberInit(MemberInitExpression init) + { + throw new NotSupportedException(Strings.ALinq_MemberInitNotSupported); + } + + /// + /// ListInitExpression visit method + /// + /// The ListInitExpression to visit + /// The visited ListInitExpression + internal override Expression VisitListInit(ListInitExpression init) + { + throw new NotSupportedException(Strings.ALinq_ListInitNotSupported); + } + + /// + /// NewArrayExpression visit method + /// + /// The NewArrayExpression to visit + /// The visited NewArrayExpression + internal override Expression VisitNewArray(NewArrayExpression na) + { + throw new NotSupportedException(Strings.ALinq_NewArrayNotSupported); + } + + /// + /// InvocationExpression visit method + /// + /// The InvocationExpression to visit + /// The visited InvocationExpression + internal override Expression VisitInvocation(InvocationExpression iv) + { + throw new NotSupportedException(Strings.ALinq_InvocationNotSupported); + } + + /// + /// NavigationPropertySingletonExpression visit method. + /// + /// NavigationPropertySingletonExpression expression to visit + /// Visited NavigationPropertySingletonExpression expression + internal override Expression VisitNavigationPropertySingletonExpression(NavigationPropertySingletonExpression npse) + { + this.Visit(npse.Source); + this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(this.ExpressionToString(npse.MemberExpression, /*inPath*/ true)); + this.VisitQueryOptions(npse); + return npse; + } + + /// + /// QueryableResourceExpression visit method. + /// + /// QueryableResourceExpression expression to visit + /// Visited QueryableResourceExpression expression + internal override Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) + { + if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.ResourceNavigationProperty) + { + if (rse.IsOperationInvocation && !(rse.Source is QueryableResourceExpression)) + { + var normalizerRewrites = new Dictionary(ReferenceEqualityComparer.Instance); + var e = Evaluator.PartialEval(rse.Source); + e = ExpressionNormalizer.Normalize(e, normalizerRewrites); + e = ResourceBinder.Bind(e, this.context); + this.Visit(e); + } + else + { + this.Visit(rse.Source); + } + + this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(this.ExpressionToString(rse.MemberExpression, /*inPath*/ true)); + } + else if (rse.MemberExpression != null) + { + // this is a resource set expression + // we should be at the very begining of + // the URI + Debug.Assert(this.uriBuilder.Length == 0, "The builder is not empty while we are adding a resourset"); + string entitySetName = (String)((ConstantExpression)rse.MemberExpression).Value; + this.uriBuilder.Append(this.context.BaseUriResolver.GetEntitySetUri(entitySetName)); + } + else + { + this.uriBuilder.Append(this.context.BaseUriResolver.BaseUriOrNull); + } + + WebUtil.RaiseVersion(ref this.uriVersion, rse.UriVersion); + + if (rse.ResourceTypeAs != null) + { + this.uriBuilder.Append(UriHelper.FORWARDSLASH); + UriHelper.AppendTypeSegment(this.uriBuilder, rse.ResourceTypeAs, this.context, /*inPath*/ true, ref this.uriVersion); + } + + if (rse.KeyPredicateConjuncts.Count > 0) + { + this.context.UrlKeyDelimiter.AppendKeyExpression(rse.GetKeyProperties(), kvp => ClientTypeUtil.GetServerDefinedName(kvp.Key), kvp => kvp.Value.Value, this.uriBuilder); + } + + if (rse.IsOperationInvocation) + { + this.VisitOperationInvocation(rse); + } + + if (rse.CountOption == CountOption.CountSegment) + { + // append $count segment: /$count + this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); + } + + this.VisitQueryOptions(rse); + return rse; + } + + /// + /// Visit Function Invocation + /// + /// Resource Expression with function invocation + internal void VisitOperationInvocation(QueryableResourceExpression rse) + { + if (!this.uriBuilder.ToString().EndsWith(UriHelper.FORWARDSLASH.ToString(), StringComparison.Ordinal)) + { + this.uriBuilder.Append(UriHelper.FORWARDSLASH); + } + + if (rse.IsOperationInvocation) + { + this.uriBuilder.Append(rse.OperationName); + if (rse.IsAction) + { + return; + } + + this.uriBuilder.Append(UriHelper.LEFTPAREN); + bool needComma = false; + KeyValuePair[] parameters = rse.OperationParameters.ToArray(); + for (int i = 0; i < parameters.Length; ++i) + { + KeyValuePair param = parameters[i]; + if (needComma) + { + this.uriBuilder.Append(UriHelper.COMMA); + } + + this.uriBuilder.Append(param.Key); + this.uriBuilder.Append(UriHelper.EQUALSSIGN); + + // non-primitive value, use alias. + if (!UriHelper.IsPrimitiveValue(param.Value)) + { + string aliasName = UriHelper.ATSIGN + param.Key; + int count = 1; + while (this.alias.ContainsKey(aliasName)) + { + aliasName = UriHelper.ATSIGN + param.Key + count; + count++; + } + + this.uriBuilder.Append(aliasName); + + this.alias.Add(aliasName, param.Value); + } + else + { + // primitive value, do not use alias. + this.uriBuilder.Append(param.Value); + } + + needComma = true; + } + + this.uriBuilder.Append(UriHelper.RIGHTPAREN); + } + } + + /// + /// Visit Query options for Resource + /// + /// Resource Expression with query options + internal void VisitQueryOptions(ResourceExpression re) + { + if (re.HasQueryOptions) + { + this.uriBuilder.Append(UriHelper.QUESTIONMARK); + + QueryableResourceExpression rse = re as QueryableResourceExpression; + if (rse != null) + { + IEnumerator options = rse.SequenceQueryOptions.GetEnumerator(); + while (options.MoveNext()) + { + Expression e = ((Expression)options.Current); + ResourceExpressionType et = (ResourceExpressionType)e.NodeType; + switch (et) + { + case ResourceExpressionType.SkipQueryOption: + this.VisitQueryOptionExpression((SkipQueryOptionExpression)e); + break; + case ResourceExpressionType.TakeQueryOption: + this.VisitQueryOptionExpression((TakeQueryOptionExpression)e); + break; + case ResourceExpressionType.OrderByQueryOption: + this.VisitQueryOptionExpression((OrderByQueryOptionExpression)e); + break; + case ResourceExpressionType.FilterQueryOption: + this.VisitQueryOptionExpression((FilterQueryOptionExpression)e); + break; + default: + Debug.Assert(false, "Unexpected expression type " + (int)et); + break; + } + } + } + + if (re.ExpandPaths.Count > 0) + { + this.VisitExpandOptions(re.ExpandPaths); + } + + if (re.Projection != null && re.Projection.Paths.Count > 0) + { + this.VisitProjectionPaths(re.Projection.Paths); + } + + if (re.CountOption == CountOption.CountQuery) + { + this.VisitCountQueryOptions(); + } + + if (re.CustomQueryOptions.Count > 0) + { + this.VisitCustomQueryOptions(re.CustomQueryOptions); + } + + this.AppendCachedQueryOptionsToUriBuilder(); + } + } + + /// + /// SkipQueryOptionExpression visit method. + /// + /// SkipQueryOptionExpression expression to visit + internal void VisitQueryOptionExpression(SkipQueryOptionExpression sqoe) + { + this.AddAsCachedQueryOption(UriHelper.DOLLARSIGN + UriHelper.OPTIONSKIP, this.ExpressionToString(sqoe.SkipAmount, /*inPath*/ false)); + } + + /// + /// TakeQueryOptionExpression visit method. + /// + /// TakeQueryOptionExpression expression to visit + internal void VisitQueryOptionExpression(TakeQueryOptionExpression tqoe) + { + this.AddAsCachedQueryOption(UriHelper.DOLLARSIGN + UriHelper.OPTIONTOP, this.ExpressionToString(tqoe.TakeAmount, /*inPath*/ false)); + } + + /// + /// FilterQueryOptionExpression visit method. + /// + /// FilterQueryOptionExpression expression to visit + internal void VisitQueryOptionExpression(FilterQueryOptionExpression fqoe) + { + this.AddAsCachedQueryOption(UriHelper.DOLLARSIGN + UriHelper.OPTIONFILTER, this.ExpressionToString(fqoe.GetPredicate(), /*inPath*/ false)); + } + + /// + /// OrderByQueryOptionExpression visit method. + /// + /// OrderByQueryOptionExpression expression to visit + internal void VisitQueryOptionExpression(OrderByQueryOptionExpression oboe) + { + StringBuilder tmpBuilder = new StringBuilder(); + int ii = 0; + while (true) + { + var selector = oboe.Selectors[ii]; + + tmpBuilder.Append(this.ExpressionToString(selector.Expression, /*inPath*/ false)); + if (selector.Descending) + { + tmpBuilder.Append(UriHelper.SPACE); + tmpBuilder.Append(UriHelper.OPTIONDESC); + } + + if (++ii == oboe.Selectors.Count) + { + break; + } + + tmpBuilder.Append(UriHelper.COMMA); + } + + this.AddAsCachedQueryOption(UriHelper.DOLLARSIGN + UriHelper.OPTIONORDERBY, tmpBuilder.ToString()); + } + + /// + /// VisitExpandOptions visit method. + /// + /// Expand Paths + internal void VisitExpandOptions(List paths) + { + StringBuilder tmpBuilder = new StringBuilder(); + int ii = 0; + while (true) + { + tmpBuilder.Append(paths[ii]); + + if (++ii == paths.Count) + { + break; + } + + tmpBuilder.Append(UriHelper.COMMA); + } + + this.AddAsCachedQueryOption(UriHelper.DOLLARSIGN + UriHelper.OPTIONEXPAND, tmpBuilder.ToString()); + } + + /// + /// ProjectionPaths visit method. + /// + /// Projection Paths + internal void VisitProjectionPaths(List paths) + { + StringBuilder tmpBuilder = new StringBuilder(); + int ii = 0; + while (true) + { + string path = paths[ii]; + + tmpBuilder.Append(path); + + if (++ii == paths.Count) + { + break; + } + + tmpBuilder.Append(UriHelper.COMMA); + } + + this.AddAsCachedQueryOption(UriHelper.DOLLARSIGN + UriHelper.OPTIONSELECT, tmpBuilder.ToString()); + } + + /// + /// VisitCountQueryOptions visit method. + /// + internal void VisitCountQueryOptions() + { + this.AddAsCachedQueryOption(UriHelper.DOLLARSIGN + UriHelper.OPTIONCOUNT, UriHelper.COUNTTRUE); + } + + /// + /// VisitCustomQueryOptions visit method. + /// + /// Custom query options + internal void VisitCustomQueryOptions(Dictionary options) + { + List keys = options.Keys.ToList(); + List values = options.Values.ToList(); + for (int i = 0; i < keys.Count; i++) + { + string k = keys[i].Value + ""; + string v = values[i].Value + ""; + this.AddAsCachedQueryOption(k, v); + } + } + + /// + /// Caches query option to be grouped + /// + /// The key. + /// The value + private void AddAsCachedQueryOption(string optionKey, string optionValue) + { + List tmp = null; + if (!this.cachedQueryOptions.TryGetValue(optionKey, out tmp)) + { + tmp = new List(); + this.cachedQueryOptions.Add(optionKey, tmp); + } + + tmp.Add(optionValue); + } + + /// + /// Append all cached query options to uri. + /// + private void AppendCachedQueryOptionsToUriBuilder() + { + int i = 0; + foreach (var queryOption in this.cachedQueryOptions) + { + if (i++ != 0) + { + this.uriBuilder.Append(UriHelper.AMPERSAND); + } + + string keyStr = queryOption.Key; + string valueStr = string.Join(",", queryOption.Value); + this.uriBuilder.Append(keyStr); + this.uriBuilder.Append(UriHelper.EQUALSSIGN); + this.uriBuilder.Append(valueStr); + } + } + + /// Serializes an expression to a string. + /// Expression to serialize + /// Whether or not the expression being written is part of the path of the URI. + /// The serialized expression. + private string ExpressionToString(Expression expression, bool inPath) + { + return ExpressionWriter.ExpressionToString(this.context, expression, inPath, ref this.uriVersion); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ActionDescriptor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ActionDescriptor.cs new file mode 100644 index 0000000..fa5e5c4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ActionDescriptor.cs @@ -0,0 +1,13 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// Holds information about a ServiceAction. + public sealed class ActionDescriptor : OperationDescriptor + { + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/AnnotationHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/AnnotationHelper.cs new file mode 100644 index 0000000..ca25510 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/AnnotationHelper.cs @@ -0,0 +1,771 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Annotation +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Runtime.CompilerServices; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + + /// + /// Provides functions to get instance annotation or metadata annotation, or to build metadata annotation dictionary. + /// + internal static class AnnotationHelper + { + /// + /// Gets the CLR value of a term that has been applied to the specified object + /// + /// The CLR type of the annotation to be returned. + /// The data service context. + /// The specified annotated object instance. + /// The term name. + /// The qualifier name. + /// Value of the term evaluated. + /// True if the annotation value can be evaluated, else false. + internal static bool TryGetMetadataAnnotation(DataServiceContext context, object source, string term, string qualifier, out TResult annotationValue) + { + IEdmVocabularyAnnotation edmValueAnnotation = null; + ClientEdmStructuredValue clientEdmValue = null; + PropertyInfo propertyInfo = null; + MethodInfo methodInfo = null; + + var keyValue = source as Tuple; + if (keyValue != null) + { + // Get metadata annotation defined on property or Navigation property or Operation or OperationImport + var instance = keyValue.Item1; + var memberInfo = keyValue.Item2; + propertyInfo = memberInfo as PropertyInfo; + methodInfo = memberInfo as MethodInfo; + if (instance != null) + { + IEdmType edmType = context.Model.GetOrCreateEdmType(instance.GetType()); + if (edmType is IEdmStructuredType) + { + ClientTypeAnnotation clientTypeAnnotation = context.Model.GetClientTypeAnnotation(edmType); + clientEdmValue = new ClientEdmStructuredValue(instance, context.Model, clientTypeAnnotation); + } + } + } + else + { + if (propertyInfo == null) + { + propertyInfo = source as PropertyInfo; + } + + if (methodInfo == null) + { + methodInfo = source as MethodInfo; + } + } + + if (propertyInfo != null) + { + edmValueAnnotation = GetOrInsertCachedMetadataAnnotationForPropertyInfo(context, propertyInfo, term, qualifier); + return TryEvaluateMetadataAnnotation(context, edmValueAnnotation, clientEdmValue, out annotationValue); + } + + if (methodInfo != null) + { + edmValueAnnotation = GetOrInsertCachedMetadataAnnotationForMethodInfo(context, methodInfo, term, qualifier); + return TryEvaluateMetadataAnnotation(context, edmValueAnnotation, clientEdmValue, out annotationValue); + } + + var type = source as Type; + Type underlyingType = type; + if (type == null) + { + type = source.GetType(); + underlyingType = Nullable.GetUnderlyingType(type) ?? type; + + // For complex type or entity type instance, try to convert the instance to ClientEdmStructuredValue for further evaluation. + IEdmType edmType = context.Model.GetOrCreateEdmType(underlyingType); + if (edmType is IEdmStructuredType) + { + ClientTypeAnnotation clientTypeAnnotation = context.Model.GetClientTypeAnnotation(edmType); + clientEdmValue = new ClientEdmStructuredValue(source, context.Model, clientTypeAnnotation); + } + } + + edmValueAnnotation = GetOrInsertCachedMetadataAnnotationForType(context, underlyingType, term, qualifier); + return TryEvaluateMetadataAnnotation(context, edmValueAnnotation, clientEdmValue, out annotationValue); + } + + /// + /// Get Edm operation according to the MethodInfo from current data service context. + /// + /// The data service context. + /// The specified MethodInfo + /// The related will be returned if it is found, or return null. + internal static IEdmOperation GetEdmOperation(DataServiceContext context, MethodInfo methodInfo) + { + var serviceModel = context.Format.ServiceModel; + if (serviceModel == null) + { + return null; + } + + var parameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); + Type bindingType = null; + IEnumerable clientParameters; + if (methodInfo.IsDefined(typeof(ExtensionAttribute), false)) + { + bindingType = parameterTypes.First(); + clientParameters = parameterTypes.Skip(1); + } + else + { + bindingType = methodInfo.DeclaringType; + clientParameters = parameterTypes; + } + + var declaringType = methodInfo.DeclaringType; + + string methodInfoNameSpacePrefix = declaringType.Namespace + "."; + if (context.ResolveName != null) + { + string serverSideDeclaringTypeName = context.ResolveName(declaringType); + if (serverSideDeclaringTypeName != null) + { + int index = serverSideDeclaringTypeName.LastIndexOf('.'); + methodInfoNameSpacePrefix = index > 0 ? serverSideDeclaringTypeName.Substring(0, index + 1) : ""; + } + } + + var serverSideMethodName = ClientTypeUtil.GetServerDefinedName(methodInfo); + var operations = serviceModel.FindOperations(methodInfoNameSpacePrefix + serverSideMethodName).Where(o => o.IsBound); + + while (bindingType != null) + { + foreach (var operation in operations) + { + Type bindingTypeFromTypeReference; + + if (TryGetClrTypeFromEdmTypeReference( + context, + operation.Parameters.First().Type, + methodInfo.IsDefined(typeof(ExtensionAttribute), false), + out bindingTypeFromTypeReference) + && bindingTypeFromTypeReference == bindingType + && clientParameters.SequenceEqual(GetNonBindingParameterTypeArray(context, operation.Parameters, true))) + { + return operation; + } + } + + if (methodInfo.IsDefined(typeof(ExtensionAttribute), false) && bindingType.IsGenericType()) + { + var genericTypeDefinition = bindingType.GetGenericTypeDefinition(); + var genericArguments = bindingType.GetGenericArguments().ToList(); + if (genericArguments.Count == 1) + { + var genericArgumentBaseType = genericArguments[0].GetBaseType(); + if (genericArgumentBaseType != null) + { + bindingType = genericTypeDefinition.MakeGenericType(genericArgumentBaseType); + continue; + } + } + } + + return null; + } + + return null; + } + + /// + /// Get Edm operation import according to the MethodInfo from current data service context. + /// + /// The data service context. + /// The specified MethodInfo + /// The related will be returned if it is found, or return null. + internal static IEdmOperationImport GetEdmOperationImport(DataServiceContext context, MethodInfo methodInfo) + { + var serviceModel = context.Format.ServiceModel; + if (serviceModel == null) + { + return null; + } + + var serversideName = ClientTypeUtil.GetServerDefinedName(methodInfo); + var operationImports = serviceModel.FindDeclaredOperationImports(serversideName); + var clientParameters = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); + + foreach (var operationImport in operationImports) + { + var operationParameters = operationImport.Operation.Parameters; + var clientOperationParameters = GetNonBindingParameterTypeArray(context, operationParameters, false); + if (clientParameters.SequenceEqual(clientOperationParameters)) + { + return operationImport; + } + } + + return null; + } + + /// + /// Gets vocabulary annotation that binds to a term and a qualifier from the metadata annotation dictionary in current data service context for a specified type. + /// + /// The data service context. + /// The specified annotated type. + /// The term name. + /// The qualifier name. + /// The vocabulary annotation that binds to a term and a qualifier for the specified annotated type. + private static IEdmVocabularyAnnotation GetOrInsertCachedMetadataAnnotationForType(DataServiceContext context, Type type, string term, string qualifier) + { + var serviceModel = context.Format.ServiceModel; + if (serviceModel == null) + { + return null; + } + + IEdmVocabularyAnnotation edmValueAnnotation = GetCachedMetadataAnnotation(context, type, term, qualifier); + if (edmValueAnnotation != null) + { + return edmValueAnnotation; + } + + IEdmVocabularyAnnotatable edmVocabularyAnnotatable = null; + if (type.IsSubclassOf(typeof(DataServiceContext))) + { + edmVocabularyAnnotatable = serviceModel.EntityContainer; + } + else + { + var serversideName = context.ResolveName == null ? type.FullName : context.ResolveName(type); + if (!string.IsNullOrWhiteSpace(serversideName)) + { + edmVocabularyAnnotatable = serviceModel.FindDeclaredType(serversideName); + if (edmVocabularyAnnotatable == null) + { + return null; + } + } + } + + // Gets the annotations which exactly match the qualifier and target. + var edmValueAnnotations = serviceModel.FindVocabularyAnnotations(edmVocabularyAnnotatable, term, qualifier) + .Where(a => a.Qualifier == qualifier && a.Target == edmVocabularyAnnotatable); + + if (edmValueAnnotations.Count() == 0) + { + edmValueAnnotation = GetOrInsertCachedMetadataAnnotationForType(context, type.GetBaseType(), term, qualifier); + } + else if (edmValueAnnotations.Count() == 1) + { + edmValueAnnotation = edmValueAnnotations.Single(); + } + + InsertMetadataAnnotation(context, type, edmValueAnnotation); + return edmValueAnnotation; + } + + /// + /// Gets vocabulary annotation that binds to a term and a qualifier from the metadata annotation dictionary in current data service context for a specified propertyInfo. + /// + /// The data service context. + /// The specified annotated propertyInfo. + /// The term name. + /// The qualifier name. + /// The vocabulary annotation that binds to a term and a qualifier for the specified annotated propertyInfo. + private static IEdmVocabularyAnnotation GetOrInsertCachedMetadataAnnotationForPropertyInfo(DataServiceContext context, PropertyInfo propertyInfo, string term, string qualifier) + { + var serviceModel = context.Format.ServiceModel; + if (serviceModel == null) + { + return null; + } + + IEdmVocabularyAnnotation edmValueAnnotation = GetCachedMetadataAnnotation(context, propertyInfo, term, qualifier); + if (edmValueAnnotation != null) + { + return edmValueAnnotation; + } + + var severSidePropertyName = ClientTypeUtil.GetServerDefinedName(propertyInfo); + if (string.IsNullOrEmpty(severSidePropertyName)) + { + return null; + } + + var declaringType = propertyInfo.DeclaringType; + IEnumerable edmValueAnnotations = null; + if (declaringType.IsSubclassOf(typeof(DataServiceContext))) + { + var entityContainer = serviceModel.EntityContainer; + var edmEntityContainerElements = entityContainer.Elements.Where(e => e.Name == severSidePropertyName); + if (edmEntityContainerElements != null && edmEntityContainerElements.Count() == 1) + { + edmValueAnnotations = serviceModel.FindVocabularyAnnotations( + edmEntityContainerElements.Single(), term, qualifier).Where(a => a.Qualifier == qualifier); + } + } + else + { + var serversideTypeName = context.ResolveName == null ? declaringType.FullName : context.ResolveName(declaringType); + var edmType = serviceModel.FindDeclaredType(serversideTypeName); + if (edmType != null) + { + var edmStructuredType = edmType as IEdmStructuredType; + if (edmStructuredType != null) + { + var edmProperty = edmStructuredType.FindProperty(severSidePropertyName); + if (edmProperty != null) + { + edmValueAnnotations = serviceModel.FindVocabularyAnnotations( + edmProperty, term, qualifier).Where(a => a.Qualifier == qualifier); + } + } + } + } + + if (edmValueAnnotations != null && edmValueAnnotations.Count() == 1) + { + edmValueAnnotation = edmValueAnnotations.Single(); + InsertMetadataAnnotation(context, propertyInfo, edmValueAnnotation); + return edmValueAnnotation; + } + + return null; + } + + /// + /// Gets vocabulary annotation that binds to a term and a qualifier from the metadata annotation dictionary in current data service context for a specified methodInfo. + /// + /// The data service context. + /// The specified annotated methodInfo. + /// The term name. + /// The qualifier name. + /// The vocabulary annotation that binds to a term and a qualifier for the specified annotated methodInfo. + private static IEdmVocabularyAnnotation GetOrInsertCachedMetadataAnnotationForMethodInfo(DataServiceContext context, MethodInfo methodInfo, string term, string qualifier) + { + IEdmModel serviceModel = context.Format.ServiceModel; + if (serviceModel == null) + { + return null; + } + + IEdmVocabularyAnnotation edmValueAnnotation = GetCachedMetadataAnnotation(context, methodInfo, term, qualifier); + if (edmValueAnnotation != null) + { + return edmValueAnnotation; + } + + IEdmVocabularyAnnotatable edmVocabularyAnnotatable = context.GetEdmOperationOrOperationImport(methodInfo); + if (edmVocabularyAnnotatable == null) + { + return null; + } + + var edmOperationImport = edmVocabularyAnnotatable as IEdmOperationImport; + IEnumerable edmValueAnnotations = null; + if (edmOperationImport != null) + { + edmValueAnnotations = serviceModel.FindVocabularyAnnotations(edmOperationImport, term, qualifier) + .Where(a => a.Qualifier == qualifier); + if (!edmValueAnnotations.Any()) + { + edmVocabularyAnnotatable = edmOperationImport.Operation; + } + } + + if (edmValueAnnotations == null || !edmValueAnnotations.Any()) + { + edmValueAnnotations = serviceModel.FindVocabularyAnnotations(edmVocabularyAnnotatable, term, qualifier) + .Where(a => a.Qualifier == qualifier); + } + + if (edmValueAnnotations != null && edmValueAnnotations.Count() == 1) + { + edmValueAnnotation = edmValueAnnotations.Single(); + InsertMetadataAnnotation(context, methodInfo, edmValueAnnotation); + return edmValueAnnotation; + } + + return null; + } + + /// + /// Get a cached metadata annotation for a specified annotatable object from current context. + /// + /// The data service context. + /// The specified annotatable object. + /// The term name of the annotation. + /// The qualifier to be applied. + /// to be returned if it is found or return null. + private static IEdmVocabularyAnnotation GetCachedMetadataAnnotation(DataServiceContext context, object key, string term, string qualifier = null) + { + if (key != null && context.MetadataAnnotationsDictionary.ContainsKey(key)) + { + var annotations = context.MetadataAnnotationsDictionary[key] + .Where(a => a.Term.FullName().Equals(term) && a.Qualifier == qualifier); + + // If there are more than one annotation per term and qualifier returned, we will return null + if (annotations.Count() == 1) + { + return annotations.Single(); + } + } + + return null; + } + + /// + /// Insert an metadata annotation with the provided key to the metadata annotation dictionary. + /// + /// The data service context + /// The specified key + /// The metadata annotation to be inserted. + private static void InsertMetadataAnnotation(DataServiceContext context, object key, IEdmVocabularyAnnotation edmValueAnnotation) + { + if (edmValueAnnotation != null) + { + IList edmValueAnnotations; + if (!context.MetadataAnnotationsDictionary.TryGetValue(key, out edmValueAnnotations)) + { + edmValueAnnotations = new List(); + context.MetadataAnnotationsDictionary.Add(key, edmValueAnnotations); + } + + edmValueAnnotations.Add(edmValueAnnotation); + } + } + + /// + /// Evaluate IEdmVocabularyAnnotation to an CLR object + /// + /// The CLR type of the annotation to be returned. + /// The data service context. + /// IEdmVocabularyAnnotation to be evaluated. + /// Value to use as context in evaluating the expression. + /// Value of the term evaluated. + /// True if the annotation value can be evaluated, else false. + private static bool TryEvaluateMetadataAnnotation(DataServiceContext context, IEdmVocabularyAnnotation edmValueAnnotation, ClientEdmStructuredValue clientEdmValue, out TResult annotationValue) + { + if (edmValueAnnotation == null) + { + annotationValue = default(TResult); + return false; + } + + EdmToClrEvaluator evaluator = CreateEdmToClrEvaluator(context); + + try + { + annotationValue = evaluator.EvaluateToClrValue(edmValueAnnotation.Value, clientEdmValue); + } + catch (InvalidOperationException) + { + // When expression contains Path. if the clientEdmValue is null, or the related property is not valid property of the clientEdmValue. + // TheEvaluateToClrValue might throw InvalidOperationException; + annotationValue = default(TResult); + return false; + } + + return true; + } + + /// + /// Create an instance used to evaluate an edm value. + /// + /// The data service context. + /// The instance. + private static EdmToClrEvaluator CreateEdmToClrEvaluator(DataServiceContext context) + { + AnnotationMaterializeHelper helper = new AnnotationMaterializeHelper(context); + + EdmToClrEvaluator evaluator = new EdmToClrEvaluator( + null, + null, + helper.GetAnnnotationExpressionForType, + helper.GetAnnnotationExpressionForProperty, + context.Model); + + evaluator.EdmToClrConverter = new EdmToClrConverter( + helper.TryCreateObjectInstance, + helper.TryGetClientPropertyInfo, + helper.TryGetClrTypeName); + + return evaluator; + } + + /// + /// Gets CLR types for a collection of s. + /// + /// The data service context + /// The parameters to be converted + /// This flag indicates whether the operation that these parameters belongs to is bound operation + /// The CLR types for the + private static Type[] GetNonBindingParameterTypeArray(DataServiceContext context, IEnumerable parameters, bool isBound = false) + { + List parameterTypes = new List(); + + for (int i = isBound ? 1 : 0; i < parameters.Count(); ++i) + { + Type parameterType; + if (TryGetClrTypeFromEdmTypeReference(context, parameters.ElementAt(i).Type, false, out parameterType)) + { + parameterTypes.Add(parameterType); + } + } + + return parameterTypes.ToArray(); + } + + /// + /// Gets the CLR type based on the and the current data service context. + /// + /// The data service context. + /// The specified edm type reference. + /// This flag indicates whether the edm type reference is used for a binding parameter. + /// The output parameter to return the CLR type. + /// True if the CLR type is found, or false. + private static bool TryGetClrTypeFromEdmTypeReference(DataServiceContext context, IEdmTypeReference edmTypeReference, bool isBindingParameter, out Type clrType) + { + EdmTypeKind typeKind = edmTypeReference.Definition.TypeKind; + if (typeKind == EdmTypeKind.None) + { + clrType = null; + return false; + } + + if (typeKind == EdmTypeKind.Primitive) + { + PrimitiveType primitiveType = null; + if (PrimitiveType.TryGetPrimitiveType(edmTypeReference.Definition.FullName(), out primitiveType)) + { + clrType = primitiveType.ClrType; + + if (edmTypeReference.IsNullable && ClientTypeUtil.CanAssignNull(clrType)) + { + clrType = typeof(Nullable<>).MakeGenericType(clrType); + } + + return true; + } + } + + if (typeKind == EdmTypeKind.Collection) + { + Type elementType; + if (TryGetClrTypeFromEdmTypeReference(context, ((IEdmCollectionTypeReference)edmTypeReference).ElementType(), false, out elementType)) + { + if (isBindingParameter) + { + clrType = typeof(DataServiceQuery<>).MakeGenericType(elementType); + } + else + { + clrType = typeof(List<>).MakeGenericType(elementType); + } + + return true; + } + } + + if (typeKind == EdmTypeKind.Complex || typeKind == EdmTypeKind.Entity || typeKind == EdmTypeKind.Enum) + { + clrType = ResolveTypeFromName(context, edmTypeReference.FullName()); + if (clrType == null) + { + return false; + } + + if (isBindingParameter) + { + clrType = typeof(DataServiceQuerySingle<>).MakeGenericType(clrType); + } + + return true; + } + + clrType = null; + return false; + } + + /// + /// Get the client CLR type according to the qualified type name. + /// + /// The data service context. + /// The qualified type name. + /// The client CLR type. + private static Type ResolveTypeFromName(DataServiceContext context, string qualifiedTypeName) + { + var typeInClientModel = context.ResolveTypeFromName(qualifiedTypeName); + if (typeInClientModel == null) + { + var typeNamespaceIndex = qualifiedTypeName.LastIndexOf('.'); + if (typeNamespaceIndex > 0) + { + string typeNamespace = qualifiedTypeName.Substring(0, typeNamespaceIndex); + typeInClientModel = context.DefaultResolveType(qualifiedTypeName, typeNamespace, typeNamespace); + } + } + + return typeInClientModel; + } + + /// + /// Provides functions to to get client CLR information by using edm info. + /// + private class AnnotationMaterializeHelper + { + /// + /// The data service context. + /// + private DataServiceContext dataServiceContext; + + /// + /// Create an instance. + /// + /// Current data service context. + internal AnnotationMaterializeHelper(DataServiceContext context) + { + this.dataServiceContext = context; + } + + /// + /// Gets CLR type name based on the edm type name and . + /// + /// The edm model. + /// The edm type name. + /// The client clr type name. + /// True if the client clr type name can be found, else false. + internal bool TryGetClrTypeName(IEdmModel edmModel, string qualifiedEdmTypeName, out string typeNameInClientModel) + { + var typeInClientModel = ResolveTypeFromName(dataServiceContext, qualifiedEdmTypeName); + typeNameInClientModel = typeInClientModel == null ? null : typeInClientModel.FullName; + return typeNameInClientModel != null; + } + + /// + /// Gets client property info of a specified property name from a Type. + /// + /// The type that contains the property. + /// The name of the property. + /// The specified property, or null if the property is not found + /// True if the property is found, else false. + internal bool TryGetClientPropertyInfo(Type type, string propertyName, out PropertyInfo propertyInfo) + { + propertyInfo = ClientTypeUtil.GetClientPropertyInfo(type, propertyName, this.dataServiceContext.UndeclaredPropertyBehavior); + return propertyInfo != null; + } + + /// + /// Create an instance of a CLR type based on and . + /// + /// The for which the needs to be created. + /// The CLR type of the object instance. + /// The converter instance calling this method. + /// A CLR object instance created for the + /// True if all properties of the created are initialized. + /// False if properties of the created instance should be initialized using the default logic. + /// True if the is created, else false. + internal bool TryCreateObjectInstance(IEdmStructuredValue edmValue, Type clrType, EdmToClrConverter converter, out object objectInstance, out bool objectInstanceInitialized) + { + return TryCreateClientObjectInstance(this.dataServiceContext, edmValue, clrType, out objectInstance, out objectInstanceInitialized); + } + + /// + /// Gets the of an annotation targeting a specified . + /// + /// The edm model. + /// The specified edm type. + /// The term name. + /// Qualifier to apply + /// The of the annotation, or null if it is not found. + internal IEdmExpression GetAnnnotationExpressionForType(IEdmModel edmModel, IEdmType edmType, string termName, string qualifier) + { + if (termName != null) + { + var clientTypeAnnotation = edmModel.GetClientTypeAnnotation(edmType); + if (clientTypeAnnotation != null) + { + var annotation = GetOrInsertCachedMetadataAnnotationForType(this.dataServiceContext, clientTypeAnnotation.ElementType, termName, qualifier); + if (annotation != null) + { + return annotation.Value; + } + } + } + + return null; + } + + /// + /// Gets the of an annotation targeting a specified property or navigation property of an . + /// + /// The edm model. + /// The specified edm type. + /// The name of the specified property or navigation property. + /// The term name. + /// Qualifier to apply + /// The of the annotation, or null if it is not found. + internal IEdmExpression GetAnnnotationExpressionForProperty(IEdmModel edmModel, IEdmType edmType, string propertyName, string termName, string qualifier) + { + if (termName != null) + { + var clientTypeAnnotation = edmModel.GetClientTypeAnnotation(edmType); + if (clientTypeAnnotation != null) + { + var propertyInfo = ClientTypeUtil.GetClientPropertyInfo(clientTypeAnnotation.ElementType, propertyName, this.dataServiceContext.UndeclaredPropertyBehavior); + if (propertyInfo != null) + { + var annotation = GetOrInsertCachedMetadataAnnotationForPropertyInfo(this.dataServiceContext, propertyInfo, termName, qualifier); + if (annotation != null) + { + return annotation.Value; + } + } + } + } + + return null; + } + + /// + /// Create an instance of a CLR type based on and . + /// + /// The data service context. + /// /// The for which the needs to be created. + /// The CLR type of the object instance. + /// A CLR object instance created for the + /// True if all properties of the created are initialized. + /// False if properties of the created instance should be initialized using the default logic. + /// True if the is created, else false. + private static bool TryCreateClientObjectInstance(DataServiceContext context, IEdmStructuredValue edmValue, Type clrType, out object objectInstance, out bool objectInstanceInitialized) + { + var clientStructuredValue = edmValue as ClientEdmStructuredValue; + Type realClientType = clrType; + if (clientStructuredValue != null) + { + var clientTypeAnnotation = context.Model.GetClientTypeAnnotation(edmValue.Type.Definition.FullName()); + realClientType = clientTypeAnnotation.ElementType; + } + + if (realClientType.IsSubclassOf(clrType)) + { + objectInstance = Activator.CreateInstance(realClientType); + } + else + { + objectInstance = Activator.CreateInstance(clrType); + } + + objectInstanceInitialized = false; + return true; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/InstanceAnnotationDictWeakKeyComparer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/InstanceAnnotationDictWeakKeyComparer.cs new file mode 100644 index 0000000..5a73f18 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Annotation/InstanceAnnotationDictWeakKeyComparer.cs @@ -0,0 +1,136 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Annotation +{ + using System; + using System.Reflection; + + /// + /// Used to compare key in DataServiceContext.InstanceAnnotations. + /// + internal class InstanceAnnotationDictWeakKeyComparer : WeakKeyComparer + { + private static InstanceAnnotationDictWeakKeyComparer defaultInstance; + + /// + /// The constructor to create an InstanceAnnotationDictWeakKeyComparer + /// + private InstanceAnnotationDictWeakKeyComparer() + : base(null) + { + } + + /// + /// Get the default comparer. + /// + public static new InstanceAnnotationDictWeakKeyComparer Default + { + get + { + if (defaultInstance == null) + { + defaultInstance = new InstanceAnnotationDictWeakKeyComparer(); + } + + return defaultInstance; + } + } + + /// + /// Get the hash code for the specified object. + /// + /// The object of which a hash code is to be returned. + /// + /// A hash code for the specified object. + /// + public override int GetHashCode(object obj) + { + WeakKeyReference weakKey = obj as WeakKeyReference; + if (weakKey != null) + { + return weakKey.HashCode; + } + + Tuple tuple = obj as Tuple; + if (tuple != null) + { + return tuple.Item1.GetHashCode() ^ tuple.Item2.GetHashCode(); + } + + Tuple, MemberInfo> tuple2 = obj as Tuple, MemberInfo>; + if (tuple2 != null) + { + return tuple2.Item1.HashCode ^ tuple2.Item2.GetHashCode(); + } + + return this.comparer.GetHashCode(obj); + } + + /// + /// Create a dictionary key for the specified obj. + /// + /// The specified object used to create a key + /// Returns a Tuple<WeakKeyReference<object>, MemberInfo> if the input object is a tuple, + /// or returns a WeakKeyReference<object> + public object CreateKey(object obj) + { + Tuple tm = obj as Tuple; + if (tm != null) + { + return new Tuple, MemberInfo>(new WeakKeyReference(tm.Item1, this), tm.Item2); + } + else + { + return new WeakKeyReference(obj, this); + } + } + + /// + /// A rule to determine whether an entry with the key could be removed. + /// + /// The key of an entry to be checked. + /// Returns true if the target of a WeakKeyReference<object> in a tuple is dead, else false. + public bool RemoveRule(object key) + { + var tupleObj = key as Tuple, MemberInfo>; + if (tupleObj != null) + { + return !tupleObj.Item1.IsAlive; + } + + return false; + } + + /// + /// Gets the target of the input object if it is a , + /// else a new Tuple<object, MemberInfo> if it is a Tuple<WeakKeyReferece<object>, MemberInfo>. + /// + /// The input object from which to get the target. + /// Indicate whether the object is dead if it is a . + /// Or wehther the first item of a tuple is dead. + /// + /// The target of the input object. + protected override object GetTarget(object obj, out bool isDead) + { + WeakKeyReference wref = obj as WeakKeyReference; + if (wref != null) + { + isDead = !wref.IsAlive; + return wref.Target; + } + + Tuple, MemberInfo> tuple = obj as Tuple, MemberInfo>; + if (tuple != null) + { + return new Tuple(GetTarget(tuple.Item1, out isDead), tuple.Item2); + } + + isDead = false; + return obj; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ArraySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ArraySet.cs new file mode 100644 index 0000000..2eb9b76 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ArraySet.cs @@ -0,0 +1,235 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + /// a set, collection of unordered, distinct objects, implemented as an array + /// element type + [DebuggerDisplay("Count = {count}")] + internal struct ArraySet : IEnumerable where T : class + { + /// item array of T + private T[] items; + + /// count of elements in the items array + private int count; + + /// number of Add and RemoveAt operations + private int version; + + /// + /// array set with an initial capacity + /// + /// initial capacity + public ArraySet(int capacity) + { + this.items = new T[capacity]; + this.count = 0; + this.version = 0; + } + + /// count of elements in the set + public int Count + { + get { return this.count; } + } + + /// get an item from an index in the set + /// index to access + public T this[int index] + { + get + { + Debug.Assert(index < this.count); + return this.items[index]; + } + } + + /// add new element to the set + /// element to add + /// equality comparison function to avoid duplicates + /// true if actually added, false if a duplicate was discovered + public bool Add(T item, Func equalityComparer) + { + if ((null != equalityComparer) && this.Contains(item, equalityComparer)) + { + return false; + } + + int index = this.count++; + if ((null == this.items) || (index == this.items.Length)) + { // grow array in size, with minimum size being 32 + Array.Resize(ref this.items, Math.Min(Math.Max(index, 16), Int32.MaxValue / 2) * 2); + } + + this.items[index] = item; + unchecked + { + this.version++; + } + + return true; + } + + /// is the element contained within the set + /// item to find + /// comparer + /// true if the element is contained + public bool Contains(T item, Func equalityComparer) + { + return (0 <= this.IndexOf(item, equalityComparer)); + } + + /// + /// enumerator + /// + /// enumerator + public IEnumerator GetEnumerator() + { + for (int i = 0; i < this.count; ++i) + { + yield return this.items[i]; + } + } + + /// + /// enumerator + /// + /// enumerator + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// Find the current index of element within the set + /// item to find + /// comparison function + /// index of the item else (-1) + public int IndexOf(T item, Func comparer) + { + return this.IndexOf(item, IdentitySelect, comparer); + } + + /// Find the current index of element within the set + /// selected type + /// item to find + /// selector for item to compare + /// item to compare + /// index of the item else (-1) + public int IndexOf(K item, Func select, Func comparer) + { + T[] array = this.items; + if (null != array) + { + int length = this.count; + for (int i = 0; i < length; ++i) + { + if (comparer(item, select(array[i]))) + { + return i; + } + } + } + + return -1; + } + + /// Remove the matched item from within the set + /// item to find within the set + /// comparer to find item to remove + /// the item that was actually contained else its default + public T Remove(T item, Func equalityComparer) + { + int index = this.IndexOf(item, equalityComparer); + if (0 <= index) + { + item = this.items[index]; + this.RemoveAt(index); + return item; + } + + return default(T); + } + + /// Remove an item at a specific index from within the set + /// index of item to remove within the set + public void RemoveAt(int index) + { + Debug.Assert(unchecked((uint)index < (uint)this.count), "index out of range"); + T[] array = this.items; + int lastIndex = --this.count; + array[index] = array[lastIndex]; + array[lastIndex] = default(T); + + if ((0 == lastIndex) && (256 <= array.Length)) + { + this.items = null; + } + else if ((256 < array.Length) && (lastIndex < array.Length / 4)) + { // shrink to half size when count is a quarter + Array.Resize(ref this.items, array.Length / 2); + } + + unchecked + { + this.version++; + } + } + + /// Sort array based on selected value out of item being stored + /// selected type + /// selector + /// comparer + public void Sort(Func selector, Func comparer) + { + if (null != this.items) + { + SelectorComparer scomp; + scomp.Selector = selector; + scomp.Comparer = comparer; + Array.Sort(this.items, 0, this.count, scomp); + } + } + + /// Sets the capacity to the actual number of elements in the ArraySet. + public void TrimToSize() + { + Array.Resize(ref this.items, this.count); + } + + /// identity selector, returns self + /// input + /// output + private static T IdentitySelect(T arg) + { + return arg; + } + + /// Compare selected value out of t + /// comparison type + private struct SelectorComparer : IComparer + { + /// Select something out of T + internal Func Selector; + + /// Comparer of selected value + internal Func Comparer; + + /// Compare + /// x + /// y + /// int + int IComparer.Compare(T x, T y) + { + return this.Comparer(this.Selector(x), this.Selector(y)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomMaterializerLog.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomMaterializerLog.cs new file mode 100644 index 0000000..15bd298 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomMaterializerLog.cs @@ -0,0 +1,466 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData; + using Microsoft.OData.Client.Materialization; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Use this class to keep a log of changes done by the materializer. + /// + internal class AtomMaterializerLog + { + #region Private fields + + /// The merge option. + private readonly MergeOption mergeOption; + + /// The client edm model. + private readonly ClientEdmModel clientEdmModel; + + /// The entity tracker. + private readonly EntityTrackerBase entityTracker; + + /// Dictionary of identity URI to instances created during previous AppendOnly moves. + private readonly Dictionary appendOnlyEntries; + + /// Dictionary of identity URI to tracked entities. + private readonly Dictionary identityStack; + + /// List of link descriptors (data for links and state). + private readonly List links; + + /// Target instance to refresh. + private object insertRefreshObject; + + #endregion Private fields + + #region Constructors + + /// + /// Initializes a new instance. + /// + /// The merge option for the log. + /// The model for the log. + /// The entity tracker for the log. + /// + /// Note that the merge option can't be changed. + /// + internal AtomMaterializerLog(MergeOption mergeOption, ClientEdmModel model, EntityTrackerBase entityTracker) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(entityTracker != null, "entityTracker != null"); + + this.appendOnlyEntries = new Dictionary(EqualityComparer.Default); + this.mergeOption = mergeOption; + this.clientEdmModel = model; + this.entityTracker = entityTracker; + this.identityStack = new Dictionary(EqualityComparer.Default); + this.links = new List(); + } + + #endregion Constructors + + #region Internal properties + + /// Whether changes are being tracked. + internal bool Tracking + { + get + { + return this.mergeOption != MergeOption.NoTracking; + } + } + + #endregion Internal properties + + #region Internal methods. + + /// + /// This method is used to merge all the metadata that come in the response payload. + /// + /// entityDescriptor that is getting tracked by the client + /// entityDescriptor that is returned by the materializer + /// if true, we will need to merge all entity descriptor info, otherwise not. + /// merge option depending on which etag information needs to be merged. + internal static void MergeEntityDescriptorInfo(EntityDescriptor trackedEntityDescriptor, EntityDescriptor entityDescriptorFromMaterializer, bool mergeInfo, MergeOption mergeOption) + { + Debug.Assert(trackedEntityDescriptor != null, "trackedEntityDescriptor != null"); + Debug.Assert(entityDescriptorFromMaterializer != null, "entityDescriptorFromMaterializer != null"); + + // In this function, we need to handle 2 cases: + // 1> Either the 2 function entity descriptors are the same, in which case + // we just have to convert the relative uri's into absolute uri's if any + // 2> If the entity descriptor are not the same, then we need to update the etag + // If the merge option is not AppendOnly and merge all entity descriptor if specified + if (!Object.ReferenceEquals(trackedEntityDescriptor, entityDescriptorFromMaterializer)) + { + // Keeping the old behavior - merging etags only when the mergeOption is something other than AppendOnly + if (entityDescriptorFromMaterializer.ETag != null && mergeOption != MergeOption.AppendOnly) + { + trackedEntityDescriptor.ETag = entityDescriptorFromMaterializer.ETag; + } + + if (mergeInfo) + { + // We need to merge the rest of the metadata irrespective of the MergeOption + if (entityDescriptorFromMaterializer.SelfLink != null) + { + trackedEntityDescriptor.SelfLink = entityDescriptorFromMaterializer.SelfLink; + } + + if (entityDescriptorFromMaterializer.EditLink != null) + { + trackedEntityDescriptor.EditLink = entityDescriptorFromMaterializer.EditLink; + } + + foreach (LinkInfo linkInfo in entityDescriptorFromMaterializer.LinkInfos) + { + trackedEntityDescriptor.MergeLinkInfo(linkInfo); + } + + foreach (StreamDescriptor streamInfo in entityDescriptorFromMaterializer.StreamDescriptors) + { + trackedEntityDescriptor.MergeStreamDescriptor(streamInfo); + } + + trackedEntityDescriptor.ServerTypeName = entityDescriptorFromMaterializer.ServerTypeName; + } + + // An entity might show actions conditionally based on each request, so get whatever sent by server + if (entityDescriptorFromMaterializer.OperationDescriptors != null) + { + trackedEntityDescriptor.ClearOperationDescriptors(); + trackedEntityDescriptor.AppendOperationalDescriptors(entityDescriptorFromMaterializer.OperationDescriptors); + } + + // TODO: ideally, we should also merge this only based on the merge context, but since for MLE we do POST + // and PUT as part of the same request, we need to merge it always to make the insert case of MLE work. + // Once we fix that issue, this also should be moved within the above if statement + if (entityDescriptorFromMaterializer.ReadStreamUri != null) + { + trackedEntityDescriptor.ReadStreamUri = entityDescriptorFromMaterializer.ReadStreamUri; + } + + if (entityDescriptorFromMaterializer.EditStreamUri != null) + { + trackedEntityDescriptor.EditStreamUri = entityDescriptorFromMaterializer.EditStreamUri; + } + + if (entityDescriptorFromMaterializer.ReadStreamUri != null || entityDescriptorFromMaterializer.EditStreamUri != null) + { + trackedEntityDescriptor.StreamETag = entityDescriptorFromMaterializer.StreamETag; + } + } + } + + /// Applies all accumulated changes to the associated data context. + /// The log should be cleared after this method successfully executed. + internal void ApplyToContext() + { + if (!this.Tracking) + { + return; + } + + foreach (KeyValuePair entity in this.identityStack) + { + // Try to attach the entity descriptor got from materializer, if one already exists, get the existing reference instead. + MaterializerEntry entry = MaterializerEntry.GetEntry(entity.Value); + + bool mergeEntityDescriptorInfo = entry.CreatedByMaterializer || + entry.ResolvedObject == this.insertRefreshObject || + entry.ShouldUpdateFromPayload; + + // Whenever we merge the data, only at those times will be merge the links also + EntityDescriptor descriptor = this.entityTracker.InternalAttachEntityDescriptor(entry.EntityDescriptor, false /*failIfDuplicated*/); + AtomMaterializerLog.MergeEntityDescriptorInfo(descriptor, entry.EntityDescriptor, mergeEntityDescriptorInfo, this.mergeOption); + + if (mergeEntityDescriptorInfo) + { + // In AtomMaterializer.TryResolveFromContext, we set AtomEntry.ShouldUpdateFromPayload to true + // when even MergeOption is PreserveChanges and entityState is Deleted. But in that case, we cannot + // set the entity state to Unchanged, hence need to workaround that one scenario + if (this.mergeOption != MergeOption.PreserveChanges || descriptor.State != EntityStates.Deleted) + { + // we should always reset descriptor's state to Unchanged (old v1 behavior) + descriptor.State = EntityStates.Unchanged; + descriptor.PropertiesToSerialize.Clear(); + } + } + } + + foreach (LinkDescriptor link in this.links) + { + if (EntityStates.Added == link.State) + { + // Added implies collection + this.entityTracker.AttachLink(link.Source, link.SourceProperty, link.Target, this.mergeOption); + } + else if (EntityStates.Modified == link.State) + { + // Modified implies reference + object target = link.Target; + if (MergeOption.PreserveChanges == this.mergeOption) + { + // GetLinks looks up the existing link using just the SourceProperty, the declaring server type name is not significant here. + LinkDescriptor end = this.entityTracker.GetLinks(link.Source, link.SourceProperty).SingleOrDefault(); + if (null != end && null == end.Target) + { + // leave the SetLink(link.Source, link.SourceProperty, null) + continue; + } + + if ((null != target) && (EntityStates.Deleted == this.entityTracker.GetEntityDescriptor(target).State) || + (EntityStates.Deleted == this.entityTracker.GetEntityDescriptor(link.Source).State)) + { + target = null; + } + } + + this.entityTracker.AttachLink(link.Source, link.SourceProperty, target, this.mergeOption); + } + else + { + // detach link + Debug.Assert(EntityStates.Detached == link.State, "not detached link"); + this.entityTracker.DetachExistingLink(link, false); + } + } + } + + /// Clears all state in the log. + internal void Clear() + { + this.identityStack.Clear(); + this.links.Clear(); + this.insertRefreshObject = null; + } + + /// + /// Invoke this method to notify the log that an existing + /// instance was found while resolving an object. + /// + /// Entry for instance. + internal void FoundExistingInstance(MaterializerEntry entry) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(IsEntity(entry), "Existing entries should be entity"); + this.identityStack[entry.Id] = entry.Entry; + } + + /// + /// Invoke this method to notify the log that the + /// target instance of a "directed" update was found. + /// + /// Entry found. + /// + /// The target instance is typically the object that we + /// expect will get refreshed by the response from a POST + /// method. + /// + /// For example if a create a Customer and POST it to + /// a service, the response of the POST will return the + /// re-serialized instance, with (important!) server generated + /// values and URIs. + /// + internal void FoundTargetInstance(MaterializerEntry entry) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise this is not a target"); + + if (IsEntity(entry)) + { + Debug.Assert(entry.IsTracking, "entry.isTracking == true, otherwise we should not be tracking this entry with the context."); + + this.entityTracker.AttachIdentity(entry.EntityDescriptor, this.mergeOption); + this.identityStack.Add(entry.Id, entry.Entry); + this.insertRefreshObject = entry.ResolvedObject; + } + } + + /// Attempts to resolve an entry from those tracked in the log. + /// Entry to resolve. + /// + /// After invocation, an existing entry with the same identity as + /// ; possibly null. + /// + /// true if an existing entry was found; false otherwise. + internal bool TryResolve(MaterializerEntry entry, out MaterializerEntry existingEntry) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(entry.Id != null, "entry.Id != null"); + Debug.Assert(entry.IsTracking, "Should not be trying to resolve the entry if entry.isTracking is false."); + + ODataResource existingODataEntry; + + if (this.identityStack.TryGetValue(entry.Id, out existingODataEntry)) + { + existingEntry = MaterializerEntry.GetEntry(existingODataEntry); + return true; + } + + if (this.appendOnlyEntries.TryGetValue(entry.Id, out existingODataEntry)) + { + // The AppendOnly entries are valid only as long as they were not modified + // between calls to .MoveNext(). + EntityStates state; + this.entityTracker.TryGetEntity(entry.Id, out state); + if (state == EntityStates.Unchanged) + { + existingEntry = MaterializerEntry.GetEntry(existingODataEntry); + return true; + } + else + { + this.appendOnlyEntries.Remove(entry.Id); + } + } + + existingEntry = default(MaterializerEntry); + return false; + } + + /// + /// Invoke this method to notify the log that a new link was + /// added to a collection. + /// + /// + /// Instance with the collection to which + /// was added. + /// + /// Property name for collection. + /// Object which was added. + internal void AddedLink(MaterializerEntry source, string propertyName, object target) + { + Debug.Assert(source.Entry != null || source.ForLoadProperty, "source != null || source.ForLoadProperty"); + Debug.Assert(propertyName != null, "propertyName != null"); + + if (!this.Tracking) + { + return; + } + + if (IsEntity(source) && IsEntity(target, this.clientEdmModel)) + { + LinkDescriptor item = new LinkDescriptor(source.ResolvedObject, propertyName, target, EntityStates.Added); + this.links.Add(item); + } + } + + /// + /// Invoke this method to notify the log that a new instance + /// was created. + /// + /// Entry for the created instance. + internal void CreatedInstance(MaterializerEntry entry) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise, what did we create?"); + Debug.Assert(entry.CreatedByMaterializer, "entry.CreatedByMaterializer -- otherwise we shouldn't be calling this"); + + if (IsEntity(entry) && entry.IsTracking && !entry.Entry.IsTransient) + { + this.identityStack.Add(entry.Id, entry.Entry); + if (this.mergeOption == MergeOption.AppendOnly) + { + this.appendOnlyEntries.Add(entry.Id, entry.Entry); + } + } + } + + /// + /// Invoke this method to notify the log that a link was removed + /// from a collection. + /// + /// + /// Instance with the collection from which + /// was removed. + /// + /// Property name for collection. + /// Object which was removed. + internal void RemovedLink(MaterializerEntry source, string propertyName, object target) + { + Debug.Assert(source.Entry != null || source.ForLoadProperty, "source != null || source.ForLoadProperty"); + Debug.Assert(propertyName != null, "propertyName != null"); + + if (IsEntity(source) && IsEntity(target, this.clientEdmModel)) + { + Debug.Assert(this.Tracking, "this.Tracking -- otherwise there's an 'if' missing (it happens to be that the assert holds for all current callers"); + LinkDescriptor item = new LinkDescriptor(source.ResolvedObject, propertyName, target, EntityStates.Detached); + this.links.Add(item); + } + } + + /// + /// Invoke this method to notify the log that a link was set on + /// a property. + /// + /// Entry for source object. + /// Name of property set. + /// Target object. + internal void SetLink(MaterializerEntry source, string propertyName, object target) + { + Debug.Assert(source.Entry != null || source.ForLoadProperty, "source != null || source.ForLoadProperty"); + Debug.Assert(propertyName != null, "propertyName != null"); + + if (!this.Tracking) + { + return; + } + + if (IsEntity(source) && IsEntity(target, this.clientEdmModel)) + { + Debug.Assert(this.Tracking, "this.Tracking -- otherwise there's an 'if' missing (it happens to be that the assert holds for all current callers"); + LinkDescriptor item = new LinkDescriptor(source.ResolvedObject, propertyName, target, EntityStates.Modified); + this.links.Add(item); + } + } + + #endregion Internal methods. + + #region Private methods. + /// + /// Returns true the specified entry represents an entity. + /// + /// The materializer entry + /// True if the entry represents an entity. + private static bool IsEntity(MaterializerEntry entry) + { + Debug.Assert(entry.ActualType != null, "Entry with no type added to log"); + return entry.ActualType.IsEntityType; + } + + /// + /// Returns true the specified entry represents an entity. + /// + /// The resolved instance + /// The client model. + /// True if the entry represents an entity. + private static bool IsEntity(object entity, ClientEdmModel model) + { + if (entity == null) + { + // you can set link to null, we need to track these values + return true; + } + + return ClientTypeUtil.TypeIsEntity(entity.GetType(), model); + } + + #endregion Private methods. + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomParser.cs new file mode 100644 index 0000000..e23ea0d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/AtomParser.cs @@ -0,0 +1,1522 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Service.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using Microsoft.OData.Service.Client.Xml; + using Microsoft.OData.Service.Common; + using System.Diagnostics; + using System.Linq; + using System.Text; + using System.Xml; + using System.Xml.Linq; + + #endregion Namespaces + + /// Parser for DataService payloads (mostly ATOM). + /// + /// There are four types of documents parsed: + /// 1. A single non-entity value. + /// 2. A list of non-entity values. + /// 3. An entity. + /// 4. A feed. + /// + /// In case of (1), the parser will go through these states: + /// None -> Custom -> Finished + /// + /// In case of (2), the parser will go through these states: + /// None -> Custom -> Custom -> Finished + /// + /// In case of (3), the parser will go through these states: + /// None -> Entry -> Finished + /// + /// In case of (4), the parser will go through these states: + /// None -> (FeedData | Entry) -> (FeedData | Entry) -> Finished + /// + /// Note that the parser will always stop on 'top-level' packets; all recursive data + /// structures are handled internally. + /// + [DebuggerDisplay("AtomParser {kind} {reader}")] + internal class AtomParser : IDisposable + { + #region Private fields + + /// The maximum protocol version the client should support (send and receive). + internal readonly DataServiceProtocolVersion MaxProtocolVersion; + + /// Callback invoked each time an ATOM entry is found. + /// + /// This callback takes the current XmlReader and returns a + /// subtree XmlReader and an object that is assigned to the + /// entry's Tag property. + /// + private readonly Func> entryCallback; + + /// Row count value representing the initial state (-1) + private const long CountStateInitial = -1; + + /// Row count value representing the failure state (-2) + private const long CountStateFailure = -2; + + /// Stack of available XmlWrappingReader. + private readonly Stack readers; + + /// Scheme used to find type information on ATOM category elements. + private readonly string typeScheme; + + /// The data namespace + private readonly string currentDataNamespace; + + /// gets the appropriate uri. This is used to convert relative uri's in the response payload into absolute uri's. + private readonly UriResolver baseUriResolver; + + /// The count tag's value, if requested + private long countValue; + + /// ATOM entry being parsed. + private AtomEntry entry; + + /// ATOM feed being parsed. + private AtomFeed feed; + + /// Current data kind (nothing, entry, feed, custom-top-level-thingy, etc). + private AtomDataKind kind; + + /// Current . + private XmlWrappingReader reader; + + #endregion Private fields + + #region Constructors + + /// Initializes a new instance. + /// to parse content from. + /// + /// Callback invoked each time an ATOM entry is found; see the comments + /// on the entryCallback field. + /// + /// + /// Scheme used to find type information on ATOM category elements. + /// + /// The xml document's DataWeb Namespace + /// Interface to retrieve the baseUri to use for this EntitySetName - this will be used to convert relative uri's in the response payload to absolute uri's. + /// max protocol version that the client understands. + internal AtomParser(XmlReader reader, Func> entryCallback, string typeScheme, string currentDataNamespace, UriResolver baseUriResolver, DataServiceProtocolVersion maxProtocolVersion) + { + Debug.Assert(reader != null, "reader != null"); + Debug.Assert(typeScheme != null, "typeScheme != null"); + Debug.Assert(entryCallback != null, "entryCallback != null"); + Debug.Assert(!String.IsNullOrEmpty(currentDataNamespace), "currentDataNamespace is empty or null"); + Debug.Assert(baseUriResolver != null, "baseUriResolver != null"); + + if (reader.Settings.NameTable != null) + { + // NOTE: dataNamespace is used for reference equality, and while it looks like + // a variable, it appears that it will only get set to XmlConstants.DataWebNamespace + // at runtime. Therefore we remove string dataNamespace as a field here. + // this.dataNamespace = reader != null ? reader.Settings.NameTable.Add(context.DataNamespace) : null; + reader.Settings.NameTable.Add(currentDataNamespace); + } + + this.reader = new Microsoft.OData.Service.Client.Xml.XmlAtomErrorReader(reader); + this.readers = new Stack(); + this.entryCallback = entryCallback; + this.typeScheme = typeScheme; + this.currentDataNamespace = currentDataNamespace; + this.baseUriResolver = baseUriResolver; + this.countValue = CountStateInitial; + this.MaxProtocolVersion = maxProtocolVersion; + Debug.Assert(this.kind == AtomDataKind.None, "this.kind == AtomDataKind.None -- otherwise not initialized correctly"); + } + + /// + /// Private default ctor for ResultsWrapper + /// + private AtomParser() + { + } + + #endregion Constructors + + #region Internal properties + + /// Entry being materialized; possibly null. + internal AtomEntry CurrentEntry + { + get + { + return this.entry; + } + } + + /// Feed being materialized; possibly null. + internal AtomFeed CurrentFeed + { + get + { + return this.feed; + } + } + + /// Kind of ATOM data available on the parser. + internal AtomDataKind DataKind + { + get + { + return this.kind; + } + } + + /// + /// Returns true if the current element is in the data web namespace + /// + internal virtual bool IsDataWebElement + { + get { return this.reader.NamespaceURI == this.currentDataNamespace; } + } + + #endregion Internal properties + + #region Public Methods + + /// + /// Dispose of all the disposeable fields in this object + /// + public void Dispose() + { + if (null != this.reader) + { + ((IDisposable)this.reader).Dispose(); + } + } + + #endregion Public Methods + + #region Internal methods. + + /// + /// Creates a wrapper around already parsed AtomEntry(s) + /// + /// the entries to wrap + /// The wrapped parser + internal static AtomParser CreateWrapper(IEnumerable entries) + { + return new ResultsWrapper(entries); + } + + /// + /// Creates an instance for ATOM entries. + /// + /// Reader being used. + /// + /// A pair of an XmlReader instance and an object to be assigned + /// to the Tag on the entry (available for materialization callbacks + /// later in the pipeline). + /// + /// + /// A no-op implementation would do this instead: + /// + /// return new KeyValuePair<XmlReader, object>(reader.ReadSubtree(), null); + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "not required")] + internal static KeyValuePair XElementBuilderCallback(XmlWrappingReader reader) + { + Debug.Assert(reader != null, "reader != null"); + + XElement element = XElement.Load(reader.ReadSubtree(), LoadOptions.None); + + // The XElement created here will be passed to users through the DataServiceContext.ReadingEntity event. If xml:base is on the parent + // element, there is no way to get to the base uri from the event handler. Links in the atom payload are relative uris, we are storing the + // xml base in the tag and pass it to ReadingWritingEntityEventArgs.BaseUri so the full uris for links can be constructed. + // + // ReadingWritingEntityEventArgs.BaseUri contains the base uri from the current element. If the current XElement also contains a xml:base + // attribute, it will be part of the uri we are passing to ReadingWritingEntityEventArgs.BaseUri. + // + // [Client-ODataLib-Integration] ReadingEntityEvent in astoria client gives the incorrect base uri in the event args + // We will pass in the base uri of the parent element to the readingentity event. + AtomTag tag = new AtomTag(element, reader.ParentBaseURI); + + return new KeyValuePair(XmlWrappingReader.CreateReader(reader.ParentBaseURI, element.CreateReader()), tag); + } + + /// + /// This method is for parsing CUD operation payloads which are expected to contain one of the following: + /// + /// - Single AtomEntry + /// This is the typical response we expect in the non-error case. + /// - Feed with a single AtomEntry + /// This is not valid per OData protocol, but we allowed this in V1/V2 and will continue to accept it since we can still treat it like a single entry. + /// - Error + /// Parser handles this case as we read the payload, it's not explicitly handled here. + /// + /// Since we don't control the payload, it may contain something that doesn't fit these requirements, in which case we will throw. + /// + /// the reader for the payload + /// The current ResponseInfo object + /// the AtomEntry that was read + internal static AtomEntry ParseSingleEntityPayload(XmlReader reader, ResponseInfo responseInfo) + { + using (AtomParser parser = new AtomParser(reader, AtomParser.XElementBuilderCallback, CommonUtil.UriToString(responseInfo.TypeScheme), responseInfo.DataNamespace, responseInfo.BaseUriResolver, responseInfo.MaxProtocolVersion)) + { + Debug.Assert(parser.DataKind == AtomDataKind.None, "the parser didn't start in the right state"); + AtomEntry entry = null; + while (parser.Read()) + { + if (parser.DataKind != AtomDataKind.Feed && parser.DataKind != AtomDataKind.Entry) + { + throw new InvalidOperationException(Strings.AtomParser_SingleEntry_ExpectedFeedOrEntry); + } + + if (parser.DataKind == AtomDataKind.Entry) + { + if (entry != null) + { + throw new InvalidOperationException(Strings.AtomParser_SingleEntry_MultipleFound); + } + + entry = parser.CurrentEntry; + } + } + + if (entry == null) + { + throw new InvalidOperationException(Strings.AtomParser_SingleEntry_NoneFound); + } + + Debug.Assert(parser.DataKind == AtomDataKind.Finished, "the parser didn't end in the right state"); + return entry; + } + } + + #endregion Internal methods. + + #region Internal methods. + + /// Consumes the next chunk of content from the underlying XML reader. + /// + /// true if another piece of content is available, identified by DataKind. + /// false if there is no more content. + /// + internal virtual bool Read() + { + // When an external caller 'insists', we'll come all the way down (which is the 'most local' + // scope at which this is known), and unwind as a no-op. + if (this.DataKind == AtomDataKind.Finished) + { + return false; + } + + while (this.reader.Read()) + { + if (ShouldIgnoreNode(this.reader)) + { + continue; + } + + Debug.Assert( + this.reader.NodeType == XmlNodeType.Element || this.reader.NodeType == XmlNodeType.EndElement, + "this.reader.NodeType == XmlNodeType.Element || this.reader.NodeType == XmlNodeType.EndElement -- otherwise we should have ignored or thrown"); + + AtomDataKind readerData = ParseStateForReader(this.reader); + + if (this.reader.NodeType == XmlNodeType.EndElement) + { + // The only case in which we expect to see an end-element at the top level + // is for a feed. Custom elements and entries should be consumed by + // their own parsing methods. However we are tolerant of additional EndElements, + // which at this point mean we have nothing else to consume. + break; + } + + switch (readerData) + { + case AtomDataKind.Custom: + if (this.DataKind == AtomDataKind.None) + { + this.kind = AtomDataKind.Custom; + return true; + } + else + { + CommonUtil.SkipToEndAtDepth(this.reader, this.reader.Depth); + continue; + } + + case AtomDataKind.Entry: + this.kind = AtomDataKind.Entry; + this.ParseCurrentEntry(out this.entry); + return true; + + case AtomDataKind.Feed: + if (this.DataKind == AtomDataKind.None) + { + this.feed = new AtomFeed(); + this.kind = AtomDataKind.Feed; + return true; + } + + throw new InvalidOperationException(Strings.AtomParser_FeedUnexpected); + + case AtomDataKind.FeedCount: + this.ParseCurrentFeedCount(); + break; + + case AtomDataKind.PagingLinks: + if (this.feed == null) + { + // paging link outside of feed? + throw new InvalidOperationException(Strings.AtomParser_PagingLinkOutsideOfFeed); + } + + this.kind = AtomDataKind.PagingLinks; + this.ParseCurrentFeedPagingLinks(); + + // need to take care of both patterns - if the element is empty as + // well as when there is an end element specified. + CommonUtil.SkipToEndAtDepth(this.reader, this.reader.Depth); + return true; + + default: + Debug.Assert(false, "Atom Parser is in a wrong state...Did you add a new AtomDataKind?"); + break; + } + } + + this.kind = AtomDataKind.Finished; + this.entry = null; + return false; + } + + /// Reads the current property value from the reader. + /// A structured property instance. + /// + /// This method should only be called for top-level complex properties. + /// + /// For top-level primitive values, + /// should be used to preserve V1 behavior in which mixed-content + /// XML elements are allowed. + /// + internal AtomContentProperty ReadCurrentPropertyValue() + { + Debug.Assert( + this.kind == AtomDataKind.Custom, + "this.kind == AtomDataKind.Custom -- otherwise caller shouldn't invoke ReadCurrentPropertyValue"); + return this.ReadPropertyValue(); + } + + /// + /// The count tag's value, if requested + /// + /// The count value returned from the server + internal long CountValue() + { + if (this.countValue == CountStateInitial) + { + this.ReadCountValue(); + } + else if (this.countValue == CountStateFailure) + { + throw new InvalidOperationException(Strings.MaterializeFromAtom_CountNotPresent); + } + + return this.countValue; + } + + /// + /// Read value from the reader and convert it into a PrimitiveParserToken. + /// + /// PrimitiveType to use for the conversion. + /// PrimitiveParserToken for the value. + internal PrimitiveParserToken TokenizeFromXml(PrimitiveType type) + { + return type.TypeConverter.TokenizeFromXml(this.reader); + } + + #endregion Internal methods. + + #region Private methods. + + /// + /// Determines what the parse state should be for the specified + /// . + /// + /// Reader to check. + /// The data kind derived from the current element. + /// + /// Note that no previous state is considered, so state transitions + /// aren't handled by the method - instead, certain known elements + /// are mapped to parser states. + /// + private static AtomDataKind ParseStateForReader(XmlReader reader) + { + Debug.Assert(reader != null, "reader != null"); + Debug.Assert( + reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.EndElement, + "reader.NodeType == XmlNodeType.Element || EndElement -- otherwise can't determine"); + + AtomDataKind result = AtomDataKind.Custom; + string elementName = reader.LocalName; + string namespaceURI = reader.NamespaceURI; + if (Util.AreSame(XmlConstants.AtomNamespace, namespaceURI)) + { + if (Util.AreSame(XmlConstants.AtomEntryElementName, elementName)) + { + result = AtomDataKind.Entry; + } + else if (Util.AreSame(XmlConstants.AtomFeedElementName, elementName)) + { + result = AtomDataKind.Feed; + } + else if (Util.AreSame(XmlConstants.AtomLinkElementName, elementName) && + Util.AreSame(XmlConstants.AtomLinkNextAttributeString, reader.GetAttribute(XmlConstants.AtomLinkRelationAttributeName))) + { + result = AtomDataKind.PagingLinks; + } + } + else if (Util.AreSame(XmlConstants.DataWebMetadataNamespace, namespaceURI)) + { + if (Util.AreSame(XmlConstants.RowCountElement, elementName)) + { + result = AtomDataKind.FeedCount; + } + } + + return result; + } + + /// + /// Reads from the specified and moves to the + /// child element which should match the specified name. + /// + /// Reader to consume. + /// Expected local name of child element. + /// Expected namespace of child element. + /// + /// true if the is left position on a child + /// with the given name; false otherwise. + /// + private static bool ReadChildElement(XmlReader reader, string localName, string namespaceUri) + { + Debug.Assert(localName != null, "localName != null"); + Debug.Assert(namespaceUri != null, "namespaceUri != null"); + Debug.Assert(!reader.IsEmptyElement, "!reader.IsEmptyElement"); + Debug.Assert(reader.NodeType != XmlNodeType.EndElement, "reader.NodeType != XmlNodeType.EndElement"); + + return reader.Read() && reader.IsStartElement(localName, namespaceUri); + } + + /// + /// Reads the text inside the element on the . + /// + /// Reader to get text from. + /// The text inside the specified . + /// + /// This method was designed to be compatible with the results + /// of evaluating the text of an XElement. + /// + /// In short, this means that nulls are never returned, and + /// that all non-text nodes are ignored (but elements are + /// recursed into). + /// + private static string ReadElementStringForText(XmlReader reader) + { + Debug.Assert(reader != null, "reader != null"); + if (reader.IsEmptyElement) + { + return String.Empty; + } + + StringBuilder result = new StringBuilder(); + int depth = reader.Depth; + while (reader.Read()) + { + if (reader.Depth == depth) + { + Debug.Assert( + reader.NodeType == XmlNodeType.EndElement, + "reader.NodeType == XmlNodeType.EndElement -- otherwise XmlReader is acting odd"); + break; + } + + if (reader.NodeType == XmlNodeType.SignificantWhitespace || + reader.NodeType == XmlNodeType.Text) + { + result.Append(reader.Value); + } + } + + return result.ToString(); + } + + /// + /// Checks whether the current node on the specified + /// should be ignored. + /// + /// Reader to check. + /// true if the node should be ignored; false if it should be processed. + /// + /// This method will throw an exception on unexpected content (CDATA, entity references, + /// text); therefore it should not be used if mixed content is allowed. + /// + private static bool ShouldIgnoreNode(XmlReader reader) + { + Debug.Assert(reader != null, "reader != null"); + + switch (reader.NodeType) + { + case XmlNodeType.CDATA: + case XmlNodeType.EntityReference: + case XmlNodeType.EndEntity: + Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading); + break; + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + // With the ODataLib integration, ODataLib ignores Text and other elements + // when reading m:properties and other elements where mixed content is not allowed + // throw Error.InvalidOperation(Strings.Deserialize_MixedContent(currentType.ElementTypeName)); + // Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading); + break; + case XmlNodeType.Element: + case XmlNodeType.EndElement: + return false; + default: + break; + } + + return true; + } + + /// + /// Checks if the given content type string matches with 'application/xml' or + /// 'application/atom+xml' case insensitively. + /// + /// Input content type. + /// true if match found, false otherwise. + private static bool IsAllowedContentType(string contentType) + { + return (String.Equals(XmlConstants.MimeApplicationXml, contentType, StringComparison.OrdinalIgnoreCase) || + String.Equals(XmlConstants.MimeApplicationAtom, contentType, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Checks if the given link type matches 'application/xml' case insensitively. + /// + /// Input link type. + /// true if match found, false otherwise. + private static bool IsAllowedAssociationLinkType(string linkType) + { + return String.Equals(XmlConstants.MimeApplicationXml, linkType, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Checks if the given link type matches 'application/atom+xml;type=feed' or + /// 'application/atom+xml;type=entry' case insensitively. + /// + /// Input link type. + /// Output parameter indicating whether we are reading a feed or an entry inline. + /// true if match found, false otherwise. + private static bool IsAllowedNavigationLinkType(string linkType, out bool isFeed) + { + isFeed = String.Equals(XmlConstants.LinkMimeTypeFeed, linkType, StringComparison.OrdinalIgnoreCase); + return isFeed ? true : String.Equals(XmlConstants.LinkMimeTypeEntry, linkType, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Parses the content on the reader into the specified . + /// + /// Target to read values into. + private void ParseCurrentContent(AtomEntry targetEntry) + { + Debug.Assert(targetEntry != null, "targetEntry != null"); + Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element"); + + string propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomContentSrcAttributeName, XmlConstants.AtomNamespace); + if (propertyValue != null) + { + // This is a media link entry + if (!CommonUtil.ReadEmptyElement(this.reader)) + { + throw Error.InvalidOperation(Strings.Deserialize_ExpectedEmptyMediaLinkEntryContent); + } + + targetEntry.MediaLinkEntry = true; + + // We should not use Util.CreateUri method to convert relative uri's into absolute uris/ + // Look at ConvertHRefAttributeValueIntoURI method in this class - this makes it consistent + // with other href attributes that we read from the response payload. + // can we treat this as similar to an href attribute and use ConvertHRefAttributeValueIntoURI method + Debug.Assert(targetEntry.EntityDescriptor != null, "EntityDescriptor has not been created yet."); + targetEntry.EntityDescriptor.ReadStreamUri = this.ProcessUriFromPayload(propertyValue); + } + else + { + // This is a regular (non-media link) entry + if (targetEntry.MediaLinkEntry.HasValue && targetEntry.MediaLinkEntry.Value) + { + // This means we saw a element but now we have a Content element + // that's not just a media link entry pointer (src) + throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed); + } + + targetEntry.MediaLinkEntry = false; + + propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.AtomNamespace); + if (AtomParser.IsAllowedContentType(propertyValue)) + { + if (this.reader.IsEmptyElement) + { + return; + } + + if (ReadChildElement(this.reader, XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace)) + { + this.ReadCurrentProperties(targetEntry.DataValues); + } + else if (this.reader.NodeType != XmlNodeType.EndElement) + { + throw Error.InvalidOperation(Strings.Deserialize_NotApplicationXml); + } + } + } + } + + /// + /// read the m2:count tag in the feed + /// + private void ReadCountValue() + { + Debug.Assert(this.countValue == CountStateInitial, "Count value is not in the initial state"); + + if (this.CurrentFeed != null && + this.CurrentFeed.Count.HasValue) + { + this.countValue = this.CurrentFeed.Count.Value; + return; + } + + // find the first element tag + while (this.reader.NodeType != XmlNodeType.Element && this.reader.Read()) + { + } + + if (this.reader.EOF) + { + throw new InvalidOperationException(Strings.MaterializeFromAtom_CountNotPresent); + } + + // the tag Should only be or tag: + Debug.Assert( + (Util.AreSame(XmlConstants.AtomNamespace, this.reader.NamespaceURI) && + Util.AreSame(XmlConstants.AtomFeedElementName, this.reader.LocalName)) || + (Util.AreSame(XmlConstants.DataWebNamespace, this.reader.NamespaceURI) && + Util.AreSame(XmlConstants.LinkCollectionElementName, this.reader.LocalName)), + " or tag expected"); + + // Create the XElement for look-ahead + // DEVNOTE(pqian): + // This is not streaming friendly! + XElement element = XElement.Load(this.reader); + this.reader.Close(); + + // Read the count value from the xelement + XElement countNode = element.Descendants(XNamespace.Get(XmlConstants.DataWebMetadataNamespace) + XmlConstants.RowCountElement).FirstOrDefault(); + + if (countNode == null) + { + throw new InvalidOperationException(Strings.MaterializeFromAtom_CountNotPresent); + } + else + { + if (!long.TryParse(countNode.Value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out this.countValue)) + { + throw new FormatException(Strings.MaterializeFromAtom_CountFormatError); + } + } + + this.reader = new Microsoft.OData.Service.Client.Xml.XmlAtomErrorReader(element.CreateReader()); + } + + /// Parses a link for the specified . + /// Entry to update with link information. + private void ParseCurrentLink(AtomEntry targetEntry) + { + Debug.Assert(targetEntry != null, "targetEntry != null"); + Debug.Assert( + this.reader.NodeType == XmlNodeType.Element, + "this.reader.NodeType == XmlNodeType.Element -- otherwise we shouldn't try to parse a link"); + Debug.Assert( + this.reader.LocalName == "link", + "this.reader.LocalName == 'link' -- otherwise we shouldn't try to parse a link"); + + string propertyName = null; + + string relation = this.reader.GetAttribute(XmlConstants.AtomLinkRelationAttributeName); + if (relation == null) + { + return; + } + + if ((relation == XmlConstants.AtomEditRelationAttributeValue || + XmlConstants.AtomEditRelationAttributeValue == UriUtil.GetNameFromAtomLinkRelationAttribute(relation, XmlConstants.IanaLinkRelationsNamespace)) + && targetEntry.EditLink == null) + { + // Only process the first link that has @rel='edit'. + string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); + if (String.IsNullOrEmpty(href)) + { + throw Error.InvalidOperation(Strings.Context_MissingEditLinkInResponseBody); + } + + targetEntry.EditLink = this.ProcessUriFromPayload(href); + } + else if ((relation == XmlConstants.AtomSelfRelationAttributeValue || + XmlConstants.AtomSelfRelationAttributeValue == UriUtil.GetNameFromAtomLinkRelationAttribute(relation, XmlConstants.IanaLinkRelationsNamespace)) + && targetEntry.QueryLink == null) + { + // Only process the first link that has @rel='self'. + string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); + if (String.IsNullOrEmpty(href)) + { + throw Error.InvalidOperation(Strings.Context_MissingSelfLinkInResponseBody); + } + + targetEntry.QueryLink = this.ProcessUriFromPayload(href); + } + else if ((relation == XmlConstants.AtomEditMediaRelationAttributeValue || + XmlConstants.AtomEditMediaRelationAttributeValue == UriUtil.GetNameFromAtomLinkRelationAttribute(relation, XmlConstants.IanaLinkRelationsNamespace)) + && targetEntry.MediaEditUri == null) + { + string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); + if (String.IsNullOrEmpty(href)) + { + throw Error.InvalidOperation(Strings.Context_MissingEditMediaLinkInResponseBody); + } + + targetEntry.MediaEditUri = this.ProcessUriFromPayload(href); + targetEntry.EntityDescriptor.StreamETag = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace); + } + else if ((propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(relation, XmlConstants.DataWebRelatedNamespace)) != null) + { + #region Read the navigation link from the href attribute and handle expanded entities, if any + bool isFeed = false; + string propertyValueText = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName); + if (!IsAllowedNavigationLinkType(propertyValueText, out isFeed)) + { + return; + } + + // Get the link for the navigation property from the href attribute + string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); + if (String.IsNullOrEmpty(href)) + { + throw Error.InvalidOperation(Strings.Context_MissingRelationshipLinkInResponseBody(propertyName)); + } + + // Add the link to the target entry so that we do use the link while querying the relationship + Uri uri = this.ProcessUriFromPayload(href); + targetEntry.AddNavigationLink(propertyName, uri, isFeed); + + if (!this.reader.IsEmptyElement) + { + this.HandleExpandedNavigationProperties(targetEntry, propertyName, isFeed); + } + #endregion Read the navigation link from the href attribute and handle expanded entities, if any + } + else if ((propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(relation, XmlConstants.DataWebRelatedLinkNamespace)) != null) + { + string propertyValueText = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName); + + // check type="application/xml" + if (!IsAllowedAssociationLinkType(propertyValueText)) + { + return; + } + + // Get the related link for the navigation property from the href attribute + string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); + if (String.IsNullOrEmpty(href)) + { + throw Error.InvalidOperation(Strings.Context_MissingRelationshipLinkInResponseBody(propertyName)); + } + + // Add the link to the target entry so that we do use the link while querying the association + Uri uri = this.ProcessUriFromPayload(href); + targetEntry.AddAssociationLink(propertyName, uri); + } + else if ((propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(relation, XmlConstants.DataWebMediaResourceEditNamespace)) != null) + { + this.ReadNamedStreamInfoFromLinkElement(targetEntry, propertyName, true/*editLink*/); + } + else if ((propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(relation, XmlConstants.DataWebMediaResourceNamespace)) != null) + { + this.ReadNamedStreamInfoFromLinkElement(targetEntry, propertyName, false/*editLink*/); + } + } + + /// + /// Reads a property value and adds it as a text or a sub-property of + /// the specified . + /// + /// Property to read content into. + private void ReadPropertyValueIntoResult(AtomContentProperty property) + { + Debug.Assert(this.reader != null, "reader != null"); + Debug.Assert(property != null, "property != null"); + + switch (this.reader.NodeType) + { + case XmlNodeType.CDATA: + case XmlNodeType.SignificantWhitespace: + case XmlNodeType.Text: + if (property.PrimitiveToken != null) + { + throw Error.InvalidOperation(Strings.Deserialize_MixedTextWithComment); + } + + // this is a string token + property.PrimitiveToken = new TextPrimitiveParserToken(this.reader.Value); + break; + case XmlNodeType.Comment: + case XmlNodeType.Whitespace: + case XmlNodeType.ProcessingInstruction: + case XmlNodeType.EndElement: + // Do nothing. + // ProcessingInstruction, Whitespace would have thrown before + break; + case XmlNodeType.Element: + // We found an element while reading a property value. This should be + // a complex type. + if (property.PrimitiveToken != null) + { + throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue); + } + + // Complex types: + property.EnsureProperties(); + AtomContentProperty prop = this.ReadPropertyValue(); + + if (prop != null) + { + property.Properties.Add(prop); + } + + break; + + default: + throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue); + } + } + + /// This method will read a string or a complex type. + /// The property value read. + /// Always checks for null attribute. + private AtomContentProperty ReadPropertyValue() + { + Debug.Assert(this.reader != null, "reader != null"); + Debug.Assert( + this.reader.NodeType == XmlNodeType.Element, + "reader.NodeType == XmlNodeType.Element -- otherwise caller is confused as to where the reader is"); + + if (!this.IsDataWebElement) + { + // we expect ... only + CommonUtil.SkipToEndAtDepth(this.reader, this.reader.Depth); + return null; + } + + AtomContentProperty result = new AtomContentProperty(); + result.Name = this.reader.LocalName; + result.TypeName = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace); + result.IsNull = Util.DoesNullAttributeSayTrue(this.reader); + + // simple optimization for empty and obviously null properties + if (result.IsNull && this.reader.IsEmptyElement) + { + return result; + } + + PrimitiveType type; + if (result.TypeName != null && PrimitiveType.TryGetPrimitiveType(result.TypeName, out type)) + { + // primitive type - tokenize it + // DEVNOTE(pqian): + // 1. If the typeName is null, it can be either a complex type or edm.string + // We must drill into the element to find out + // 2. We should throw if the Edm type is unrecognized, but this was not in V1/V2 + // So we need to ignore unknown types + result.PrimitiveToken = this.TokenizeFromXml(type); + } + else + { + // complex or collection type - recursive parse and store into result + if (!this.reader.IsEmptyElement) + { + int depth = this.reader.Depth; + while (this.reader.Read()) + { + this.ReadPropertyValueIntoResult(result); + if (this.reader.Depth == depth) + { + break; + } + } + } + } + + if (result.PrimitiveToken == null && !result.IsNull) + { + // Empty String or actual null property can both cause PrimitiveToken to be null + // NOTE: Ideally we should leave this null when parsing complex types or collection + // But the V1/V2 behavior is to set them as Empty + result.PrimitiveToken = TextPrimitiveParserToken.Empty; + } + + return result; + } + + /// + /// Reads properties from the current reader into the + /// specified collection. + /// + /// Values to read into. + private void ReadCurrentProperties(List values) + { + Debug.Assert(values != null, "values != null"); + Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element"); + + while (this.reader.Read()) + { + if (ShouldIgnoreNode(this.reader)) + { + continue; + } + + if (this.reader.NodeType == XmlNodeType.EndElement) + { + return; + } + + if (this.reader.NodeType == XmlNodeType.Element) + { + AtomContentProperty prop = this.ReadPropertyValue(); + + if (prop != null) + { + values.Add(prop); + } + } + } + } + + /// + /// Parses the current reader into a new + /// instance. + /// + /// + /// After invocation, the target entry that was created as a result + /// of parsing the current reader. + /// + private void ParseCurrentEntry(out AtomEntry targetEntry) + { + Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element"); + + // Push reader. + var callbackResult = this.entryCallback(this.reader); + Debug.Assert(callbackResult.Key != null, "callbackResult.Key != null"); + this.readers.Push(this.reader); + this.reader = callbackResult.Key; + + this.reader.Read(); + Debug.Assert(this.reader.LocalName == "entry", "this.reader.LocalName == 'entry' - otherwise we're not reading the subtree"); + + targetEntry = new AtomEntry(this.MaxProtocolVersion); + targetEntry.DataValues = new List(); + targetEntry.Tag = callbackResult.Value; + targetEntry.EntityDescriptor.ETag = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace); + + while (this.reader.Read()) + { + if (ShouldIgnoreNode(this.reader)) + { + continue; + } + + if (this.reader.NodeType == XmlNodeType.Element) + { + int depth = this.reader.Depth; + string elementName = this.reader.LocalName; + string namespaceURI = this.reader.NamespaceURI; + if (namespaceURI == XmlConstants.AtomNamespace) + { + if (elementName == XmlConstants.AtomCategoryElementName && targetEntry.TypeName == null) + { + string text = this.reader.GetAttributeEx(XmlConstants.AtomCategorySchemeAttributeName, XmlConstants.AtomNamespace); + if (text == this.typeScheme && !targetEntry.TypeNameHasBeenSet) + { + targetEntry.TypeNameHasBeenSet = true; + targetEntry.TypeName = this.reader.GetAttributeEx(XmlConstants.AtomCategoryTermAttributeName, XmlConstants.AtomNamespace); + } + } + else if (elementName == XmlConstants.AtomContentElementName) + { + this.ParseCurrentContent(targetEntry); + } + else if (elementName == XmlConstants.AtomIdElementName && targetEntry.Identity == null) + { + // The .Identity == null check ensures that only the first id element is processed. + string idText = ReadElementStringForText(this.reader); + WebUtil.ValidateIdentityValue(idText); + targetEntry.Identity = idText; + } + else if (elementName == XmlConstants.AtomLinkElementName) + { + this.ParseCurrentLink(targetEntry); + } + } + else if (namespaceURI == XmlConstants.DataWebMetadataNamespace) + { + if (elementName == XmlConstants.AtomPropertiesElementName) + { + if (targetEntry.MediaLinkEntry.HasValue && !targetEntry.MediaLinkEntry.Value) + { + // This means we saw a non-empty element but now we have a Properties element + // that also carries properties + throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed); + } + + targetEntry.MediaLinkEntry = true; + + if (!this.reader.IsEmptyElement) + { + this.ReadCurrentProperties(targetEntry.DataValues); + } + } + else if (elementName == XmlConstants.ActionElementName || elementName == XmlConstants.FunctionElementName) + { + this.ReadOperationDescriptor(targetEntry); + } + } + + CommonUtil.SkipToEndAtDepth(this.reader, depth); + } + } + + if (targetEntry.Identity == null) + { + throw Error.InvalidOperation(Strings.Deserialize_MissingIdElement); + } + + this.reader = this.readers.Pop(); + } + + /// Parses the value for the current feed count. + /// This method will update the value on the current feed. + private void ParseCurrentFeedCount() + { + if (this.feed == null) + { + throw new InvalidOperationException(Strings.AtomParser_FeedCountNotUnderFeed); + } + + if (this.feed.Count.HasValue) + { + throw new InvalidOperationException(Strings.AtomParser_ManyFeedCounts); + } + + long count; + if (!long.TryParse(MaterializeAtom.ReadElementString(this.reader, true), System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out count)) + { + throw new FormatException(Strings.MaterializeFromAtom_CountFormatError); + } + + this.feed.Count = count; + } + + /// + /// Parsing paging links + /// + private void ParseCurrentFeedPagingLinks() + { + // feed should never be null here since there is an outer check + // just need to assert + Debug.Assert(this.feed != null, "Trying to parser paging links but feed is null."); + + if (this.feed.NextLink != null) + { + // we have set next link before, this is a duplicate + // atom spec does not allow duplicated next links + throw new InvalidOperationException(Strings.AtomMaterializer_DuplicatedNextLink); + } + + string nextLink = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); + + if (nextLink == null) + { + throw new InvalidOperationException(Strings.AtomMaterializer_LinksMissingHref); + } + else + { + this.feed.NextLink = this.ProcessUriFromPayload(nextLink); + } + } + + /// + /// creates a new uri instance which takes into account the base uri of the reader. + /// + /// the uri attribute value as a string. + /// the error message to throw if the newly created uri was not an absolute uri. + /// a new instance of uri as refered by the + private Uri ProcessUriFromPayload(string uriString, Func getErrorMessage) + { + Uri uri = Util.CreateUri(uriString, UriKind.RelativeOrAbsolute); + if (!uri.IsAbsoluteUri && !String.IsNullOrEmpty(this.reader.BaseURI)) + { + Uri xmlBaseUri = Util.CreateUri(this.reader.BaseURI, UriKind.RelativeOrAbsolute); + + // The reason why we can't use Util.CreateUri function here, is that the util method + // checks for trailing slashes in the baseuri and starting forward slashes in the request uri + // and does some tricks which is not consistent with the uri class behaviour. Hence using the + // uri class directly here. + uri = new Uri(xmlBaseUri, uri); + } + + if (!uri.IsAbsoluteUri) + { + // If the uri is not an absolute uri (after applying the xml:base), then we need to use the context + // base uri to convert into an absolute uri. + // Again the same comment as above applies : Util.CreateUri does a bunch of magic with '/' and doesn't + // follow the right uri behavior + uri = new Uri(this.baseUriResolver.GetBaseUriWithSlash(getErrorMessage), uri); + } + + return uri; + } + + /// + /// creates a new uri instance which takes into account the base uri of the reader. + /// + /// the uri attribute value as a string. + /// a new instance of uri as refered by the + private Uri ProcessUriFromPayload(string uriString) + { + return this.ProcessUriFromPayload(uriString, () => Strings.Context_BaseUriRequired); + } + + /// + /// Reads an OData operation and constructs an OperationDescriptor representing the OData operation. + /// + /// the entry containing the OData operation. The constructed operation descriptor is added to the list of operation descriptors of the entry. + private void ReadOperationDescriptor(AtomEntry targetEntry) + { + Debug.Assert(targetEntry != null, "targetEntry != null"); + Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element"); + Debug.Assert(this.reader.NamespaceURI == XmlConstants.DataWebMetadataNamespace, "The NamespaceURI was expected to be pointing to the metadata namespace."); + Debug.Assert(this.reader.LocalName == XmlConstants.ActionElementName || this.reader.LocalName == XmlConstants.FunctionElementName, "An xml element named 'action' or 'function' was expected."); + + OperationDescriptor operationDescriptor; + string operationTypeName = this.reader.LocalName; + + if (operationTypeName == XmlConstants.ActionElementName) + { + operationDescriptor = new ActionDescriptor(); + } + else + { + Debug.Assert(operationTypeName == XmlConstants.FunctionElementName, "An xml element named 'function' was expected."); + operationDescriptor = new FunctionDescriptor(); + } + + Debug.Assert(operationDescriptor != null, "operationDescriptor != null"); + + // read all the attributes. + while (this.reader.MoveToNextAttribute()) + { + if (object.ReferenceEquals(this.reader.NamespaceURI, XmlConstants.EmptyNamespace)) + { + string attributeValue = this.reader.Value; + string localName = this.reader.LocalName; + + if (localName == XmlConstants.ActionMetadataAttributeName) + { + // For metadata, if the URI is relative we don't attempt to make it absolute using the service + // base URI, because the ODataOperation metadata URI is relative to $metadata. + operationDescriptor.Metadata = Util.CreateUri(attributeValue, UriKind.RelativeOrAbsolute); + } + else if (localName == XmlConstants.ActionTargetAttributeName) + { + operationDescriptor.Target = this.ProcessUriFromPayload(attributeValue, () => Strings.AtomParser_OperationTargetUriIsNotAbsolute(operationTypeName, attributeValue)); + } + else if (localName == XmlConstants.ActionTitleAttributeName) + { + operationDescriptor.Title = attributeValue; + } + + // skip unknown attributes + } + } + + if (operationDescriptor.Metadata == null) + { + throw new InvalidOperationException(Strings.AtomParser_OperationMissingMetadataAttribute(operationTypeName)); + } + + if (operationDescriptor.Target == null) + { + throw new InvalidOperationException(Strings.AtomParser_OperationMissingTargetAttribute(operationTypeName)); + } + + targetEntry.EntityDescriptor.AddOperationDescriptor(operationDescriptor); + + // move back to the element node. + this.reader.MoveToElement(); + } + + /// + /// Handle the expanded navigation property entities for the given target entry + /// + /// parent entity containing the navigation property. + /// name of the navigation property. + /// whether the navigation property is a feed or entry. This information is obtained from the type attribute of the link element. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "not required to dispose of the nested XmlReader")] + private void HandleExpandedNavigationProperties(AtomEntry targetEntry, string propertyName, bool isFeed) + { + Debug.Assert(!this.reader.IsEmptyElement, "the current element has some child content"); + + if (!ReadChildElement(this.reader, XmlConstants.AtomInlineElementName, XmlConstants.DataWebMetadataNamespace)) + { + return; + } + + bool emptyInlineCollection = this.reader.IsEmptyElement; + object propertyValue = null; + + if (!emptyInlineCollection) + { + AtomFeed nestedFeed = null; + AtomEntry nestedEntry = null; + List feedEntries = null; + + Debug.Assert(this.reader is Xml.XmlWrappingReader, "reader must be a instance of XmlWrappingReader"); + Uri readerBaseUri = string.IsNullOrEmpty(this.reader.BaseURI) ? null : new Uri(this.reader.BaseURI, UriKind.Absolute); + XmlReader nestedReader = Xml.XmlWrappingReader.CreateReader(readerBaseUri, this.reader.ReadSubtree()); + nestedReader.Read(); + Debug.Assert(nestedReader.LocalName == "inline", "nestedReader.LocalName == 'inline'"); + + AtomParser nested = new AtomParser(nestedReader, this.entryCallback, this.typeScheme, this.currentDataNamespace, this.baseUriResolver, this.MaxProtocolVersion); + while (nested.Read()) + { + switch (nested.DataKind) + { + case AtomDataKind.Feed: + feedEntries = new List(); + nestedFeed = nested.CurrentFeed; + propertyValue = nestedFeed; + break; + case AtomDataKind.Entry: + nestedEntry = nested.CurrentEntry; + if (feedEntries != null) + { + feedEntries.Add(nestedEntry); + } + else + { + propertyValue = nestedEntry; + } + + break; + case AtomDataKind.PagingLinks: + // Here the inner feed parser found a paging link, and stored it on nestedFeed.NextPageLink + // we are going to add it into a link table and associate + // with the collection at AtomMaterializer::Materialize() + // Do nothing for now. + break; + default: + throw new InvalidOperationException(Strings.AtomParser_UnexpectedContentUnderExpandedLink); + } + } + + if (nestedFeed != null) + { + Debug.Assert( + nestedFeed.Entries == null, + "nestedFeed.Entries == null -- otherwise someone initialized this for us"); + nestedFeed.Entries = feedEntries; + } + } + + AtomContentProperty property = new AtomContentProperty(); + property.Name = propertyName; + + if (emptyInlineCollection || propertyValue == null) + { + property.IsNull = true; + if (isFeed) + { + property.Feed = new AtomFeed(); + property.Feed.Entries = Enumerable.Empty(); + } + else + { + property.Entry = new AtomEntry(this.MaxProtocolVersion); + property.Entry.IsNull = true; + } + } + else + { + property.Feed = propertyValue as AtomFeed; + property.Entry = propertyValue as AtomEntry; + } + + targetEntry.DataValues.Add(property); + } + + /// + /// Read the named stream info from the current link element. + /// + /// parent entity containing the named stream. + /// name of the stream. + /// whether the current link element represents the edit link of the stream. + private void ReadNamedStreamInfoFromLinkElement(AtomEntry targetEntry, string name, bool editLink) + { + StreamDescriptor streamInfo = targetEntry.EntityDescriptor.AddStreamInfoIfNotPresent(name); + + // In ParseCurrentLink method, we only read href from the first link element. If the link element is repeated + // with the same rel attribute value, the corresponding ones are ignored. + if ((editLink && streamInfo.EditLink == null) || (!editLink && streamInfo.SelfLink == null)) + { + // Get the related link for the navigation property from the href attribute + string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); + if (String.IsNullOrEmpty(href)) + { + throw Error.InvalidOperation(editLink ? Strings.Context_MissingHRefInNamedStreamEditLinkElement(name) : Strings.Context_MissingHRefInNamedStreamSelfLinkElement(name)); + } + + if (editLink) + { + streamInfo.EditLink = this.ProcessUriFromPayload(href); + streamInfo.ETag = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace); + } + else + { + streamInfo.SelfLink = this.ProcessUriFromPayload(href); + } + + string contentType = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName); + if (String.IsNullOrEmpty(streamInfo.ContentType)) + { + streamInfo.ContentType = contentType; + } + else + { + if (!streamInfo.ContentType.Equals(contentType, StringComparison.OrdinalIgnoreCase)) + { + throw Error.InvalidOperation(Strings.Context_NamedStreamDeclareDifferentContentType(name)); + } + } + } + else + { + // Duplicated edit and self link is now blocked + throw Error.InvalidOperation(editLink ? Strings.Context_StreamPropertyWithMultipleEditLinks(name) : Strings.Context_StreamPropertyWithMultipleSelfLinks(name)); + } + } + #endregion Private methods. + + /// + /// Responsible for pretending to be an AtomParser, but simply + /// handing out the pre parsed AtomEntry(s) + /// + private class ResultsWrapper : AtomParser + { + /// + /// results to wrap + /// + private IEnumerator resultEnumerator; + + /// + /// ctor for creating the wrapper around the results + /// + /// the results to be wraped + internal ResultsWrapper(IEnumerable results) + { + Debug.Assert(results != null, "send an empty collection instead of null"); + + this.resultEnumerator = results.GetEnumerator(); + } + + /// + /// Reads the next wrapped result + /// + /// true if more results are avialable, false otherwise + /// + /// Returns true if the current element is in the data web namespace + /// + internal override bool IsDataWebElement + { + get + { + // we won't hit this because we never have a AtomDataKind.Custom + return false; + } + } + + /// Consumes the next chunk of content from the underlying XML reader. + /// + /// true if another piece of content is available, identified by DataKind. + /// false if there is no more content. + /// + internal override bool Read() + { + if (this.resultEnumerator.MoveNext()) + { + this.kind = AtomDataKind.Entry; + this.entry = this.resultEnumerator.Current; + return true; + } + else + { + this.kind = AtomDataKind.Finished; + this.entry = null; + return false; + } + } + } + } + + /// Object to be assigned to the Tag on the AtomEntry object. + internal class AtomTag + { + /// Constructs a new AtomTag instance. + /// XML data of the ATOM entry. + /// The xml base of the feed or entry containing the current ATOM entry. + public AtomTag(XElement entry, Uri baseUri) + { + Debug.Assert(entry != null, "entry != null"); + Debug.Assert(baseUri == null || baseUri.IsAbsoluteUri, "baseUri == null || baseUri.IsAbsoluteUri"); + + this.Entry = entry; + this.BaseUri = baseUri; + } + + /// XML data of the ATOM entry. + public XElement Entry + { + get; + private set; + } + + /// The xml base of the feed or entry containing the current ATOM entry. + public Uri BaseUri + { + get; + private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntitySetAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntitySetAttribute.cs new file mode 100644 index 0000000..7c5a0f6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntitySetAttribute.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Indicates the entity set to which a client data service class belongs. + /// + /// This attribute is generated only when there is one entity set associated with the type. + /// When there are more than one entity set associated with the type, then the entity set + /// name can be passed in through the EntitySetNameResolver event. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class EntitySetAttribute : System.Attribute + { + /// + /// The entity set name. + /// + private readonly string entitySet; + + /// Creates a new instance of the . + /// The entity set to which the class belongs. + public EntitySetAttribute(string entitySet) + { + this.entitySet = entitySet; + } + + /// Gets the entity set to which the class belongs. + /// The entity set as string value. + public string EntitySet + { + get + { + return this.entitySet; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntityTypeAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntityTypeAttribute.cs new file mode 100644 index 0000000..f28e0ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/EntityTypeAttribute.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Marks a class as an entity type in WCF Data Services. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class EntityTypeAttribute : System.Attribute + { + /// Creates a new instance of the class. + public EntityTypeAttribute() + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/HasStreamAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/HasStreamAttribute.cs new file mode 100644 index 0000000..e5d2486 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/HasStreamAttribute.cs @@ -0,0 +1,16 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Indicates that a class that is an entity type has a default binary data stream. + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] + public sealed class HasStreamAttribute : Attribute + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/IgnoreClientPropertyAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/IgnoreClientPropertyAttribute.cs new file mode 100644 index 0000000..f80eef8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/IgnoreClientPropertyAttribute.cs @@ -0,0 +1,16 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Indicates that an attribute should not be serialized while inserting or updating an entity. + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public sealed class IgnoreClientPropertyAttribute : Attribute + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/KeyAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/KeyAttribute.cs new file mode 100644 index 0000000..3726860 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/KeyAttribute.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.ObjectModel; + using System.Linq; + + /// Denotes the key property or properties of an entity. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "Accessors are available for processed input.")] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class KeyAttribute : System.Attribute + { + /// Name of the properties that form the key. + private readonly ReadOnlyCollection keyNames; + + /// Initializes a new instance of the class. + /// The string that contains name of the key attribute. + public KeyAttribute(string keyName) + { + Util.CheckArgumentNull(keyName, "keyName"); + Util.CheckArgumentNullAndEmpty(keyName, "KeyName"); + this.keyNames = new ReadOnlyCollection(new string[1] { keyName }); + } + + /// Initializes a new instance of the class. + /// An array of string values that contain key attribute names. + public KeyAttribute(params string[] keyNames) + { + Util.CheckArgumentNull(keyNames, "keyNames"); + if (keyNames.Length == 0 || keyNames.Any(f => f == null || f.Length == 0)) + { + throw Error.Argument(Strings.DSKAttribute_MustSpecifyAtleastOnePropertyName, "keyNames"); + } + + this.keyNames = new ReadOnlyCollection(keyNames); + } + + /// Gets the names of key attributes. + /// String value that contains names of key attributes. + public ReadOnlyCollection KeyNames + { + get + { + return this.keyNames; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/NamedStreamAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/NamedStreamAttribute.cs new file mode 100644 index 0000000..cdb67ed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/NamedStreamAttribute.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Indicates that a class that is an entity type has a related named binary stream. + [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + public sealed class NamedStreamAttribute : Attribute + { + /// Creates a new instance of the class. + /// The name of a binary stream that belongs to the attributed entity. + public NamedStreamAttribute(string name) + { + this.Name = name; + } + + /// The name of a binary stream that belongs to the attributed entity. + /// The name of the binary stream. + public string Name + { + get; + private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/OriginalNameAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/OriginalNameAttribute.cs new file mode 100644 index 0000000..d33a204 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Attribute/OriginalNameAttribute.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Denotes the original name of a variable defined in metadata. + [AttributeUsage(AttributeTargets.All)] + public sealed class OriginalNameAttribute : Attribute + { + /// The original name. + private readonly string originalName; + + /// Initializes a new instance of the class. + /// The string that contains original name of the variable. + public OriginalNameAttribute(string originalName) + { + this.originalName = originalName; + } + + /// Gets the orginal names of the variable. + /// String value that contains the original name of the variable. + public string OriginalName + { + get { return this.originalName; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseAsyncResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseAsyncResult.cs new file mode 100644 index 0000000..f99af26 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseAsyncResult.cs @@ -0,0 +1,1157 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Threading; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData; + + /// + /// Implementation of IAsyncResult + /// + internal abstract class BaseAsyncResult : IAsyncResult + { + /// Originating object, used to validate End* + internal readonly object Source; + + /// Originating method on source, to differentiate between different methods from same source + internal readonly string Method; + + /// wrapped request + protected PerRequest perRequest; + + /// + /// The int equivalent for true. + /// + private const int True = 1; + + /// + /// The int equivalent for false. + /// + private const int False = 0; + + /// User callback passed to Begin* + private readonly AsyncCallback userCallback; + + /// User state passed to Begin* + private readonly object userState; + + /// wait handle for user to wait until done, we only use this within lock of asyncWaitDisposeLock. + private System.Threading.ManualResetEvent asyncWait; + + /// Holding exception to throw as a nested exception during to End* + private Exception failure; + + /// Abortable request + private ODataRequestMessageWrapper abortable; + + /// true unless something completes asynchronously + private int completedSynchronously = BaseAsyncResult.True; + + /// true when really completed for the user + private bool userCompleted; + + /// true when no more changes are pending, 0 false, 1 completed, 2 aborted + private int completed; + + /// verify we only invoke the user callback once, 0 false, 1 true + private int userNotified; + + /// non-zero after End*, 0 false, 1, true + private int done; + + /// true if the AsyncWaitHandle has already been disposed. + private bool asyncWaitDisposed; + + /// delay created object to lock to prevent using disposed asyncWait handle. + private object asyncWaitDisposeLock; + + /// + /// ctor + /// + /// source object of async request + /// async method name on source object + /// user callback to invoke when complete + /// user state + internal BaseAsyncResult(object source, string method, AsyncCallback callback, object state) + { + Debug.Assert(null != source, "null source"); + this.Source = source; + this.Method = method; + this.userCallback = callback; + this.userState = state; + } + + /// + /// This delegate exists to workaround limitations in the WP7 runtime. + /// When limitations on the number of parameters to Func<> are resolved, this can be subsumed by the following: + /// Func<byte[], int, int, AsyncCallback, object, IAsyncResult> + /// + /// buffer to transfer the data + /// byte offset in buffer + /// max number of bytes in the buffer + /// async callback to be called when the operation is complete + /// A user-provided object that distinguishes this particular asynchronous request from other requests. + /// An IAsyncResult that represents the asynchronous operation, which could still be pending + internal delegate IAsyncResult AsyncAction(byte[] buffer, int offset, int length, AsyncCallback asyncCallback, object state); + + #region IAsyncResult implementation - AsyncState, AsyncWaitHandle, CompletedSynchronously, IsCompleted + + /// user state object parameter + public object AsyncState + { + get { return this.userState; } + } + + /// wait handle for when waiting is required + /// if displayed by debugger, it undesirable to create the WaitHandle + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public System.Threading.WaitHandle AsyncWaitHandle + { + get + { + if (null == this.asyncWait) + { // delay create the wait handle since the user may never use it + // like asyncWait which will be GC'd, the losers in creating the asyncWait will also be GC'd + System.Threading.Interlocked.CompareExchange(ref this.asyncWait, new System.Threading.ManualResetEvent(this.IsCompleted), null); + + // multi-thread condition + // 1) thread 1 returned IAsyncResult and !IsCompleted so AsyncWaitHandle.WaitOne() + // 2) thread 2 signals complete, however thread 1 has retrieved this.completed but not assigned asyncWait + if (this.IsCompleted) + { // yes, Set may be called multiple times - but user would have to assume ManualResetEvent and call Reset + // + // There is a very small window for race condition between setting the wait handle here and disposing + // the wait handle inside EndExecute(). Say thread1 calls EndExecute() and IsCompleted is already true we won't + // create asyncWait from inside thread1, and thread2 wakes up right before thread1 tries to dispose the handle and + // thread2 calls AsyncWaitHandle which creates a new asyncWait handle, if thread1 wakes up right before the Set event + // here and disposes the asyncWait handle we just created here, Set() will throw ObjectDisposedException. + // SetAsyncWaitHandle() will protect this scenario with a critical section. + this.SetAsyncWaitHandle(); + } + } + + // Note that if the first time AsyncWaitHandle gets called is after EndExecute() is completed, we don't dispose the + // newly created handle, it'll just get GC'd. + return this.asyncWait; + } + } + + /// did the result complete synchronously? + public bool CompletedSynchronously + { + get + { + return this.completedSynchronously == BaseAsyncResult.True; + } + } + + /// is the result complete? + public bool IsCompleted + { + get { return this.userCompleted; } + } + + /// is the result complete? + internal bool IsCompletedInternally + { + get { return (0 != this.completed); } + } + + /// abort the result + internal bool IsAborted + { + get { return (2 == this.completed); } + } + + #endregion + + /// + /// WebRequest available for DataServiceContext.CancelRequest + /// + internal ODataRequestMessageWrapper Abortable + { + get + { + return this.abortable; + } + + set + { + this.abortable = value; + if ((null != value) && this.IsAborted) + { // if the value hadn't been set yet, but aborting then propagate the abort + value.Abort(); + } + } + } + + /// first exception that happened + internal Exception Failure + { + get { return this.failure; } + } + + /// + /// common handler for EndExecuteBatch & EndSaveChanges + /// + /// derived type of the AsyncResult + /// source object of async request + /// async method name on source object + /// the asyncResult being ended + /// data service response for batch + internal static T EndExecute(object source, string method, IAsyncResult asyncResult) where T : BaseAsyncResult + { + Util.CheckArgumentNull(asyncResult, "asyncResult"); + + T result = (asyncResult as T); + if ((null == result) || (source != result.Source) || (result.Method != method)) + { + throw Error.Argument(Strings.Context_DidNotOriginateAsync, "asyncResult"); + } + + Debug.Assert((result.CompletedSynchronously && result.IsCompleted) || !result.CompletedSynchronously, "CompletedSynchronously && !IsCompleted"); + + if (!result.IsCompleted) + { // if the user doesn't want to wait forever, they should explicitly wait on the handle with a timeout + result.AsyncWaitHandle.WaitOne(); + + Debug.Assert(result.IsCompleted, "not completed after waiting"); + } + + // Prevent EndExecute from being called more than once. + if (System.Threading.Interlocked.Exchange(ref result.done, 1) != 0) + { + throw Error.Argument(Strings.Context_AsyncAlreadyDone, "asyncResult"); + } + + // Dispose the wait handle. + if (null != result.asyncWait) + { + System.Threading.Interlocked.CompareExchange(ref result.asyncWaitDisposeLock, new object(), null); + lock (result.asyncWaitDisposeLock) + { + result.asyncWaitDisposed = true; + Util.Dispose(result.asyncWait); + } + } + + if (result.IsAborted) + { + throw Error.InvalidOperation(Strings.Context_OperationCanceled); + } + + if (null != result.Failure) + { + if (Util.IsKnownClientExcption(result.Failure)) + { + throw result.Failure; + } + + throw Error.InvalidOperation(Strings.DataServiceException_GeneralError, result.Failure); + } + + return result; + } + + /// + /// Due to the unexpected behaviors of IAsyncResult.CompletedSynchronously in the System.Net networking stack, we have to make + /// async calls to their APIs using the specific pattern they've prescribed. This method runs in the caller thread and invokes + /// the BeginXXX methods. It then checks IAsyncResult.CompletedSynchronously and if it is true, we invoke the callback in the + /// caller thread. + /// + /// + /// This is the action that invokes the BeginXXX method. Note we MUST use our special callback from GetDataServiceAsyncCallback() + /// when invoking the async call. + /// + /// async callback to be called when the operation is complete + /// A user-provided object that distinguishes this particular asynchronous request from other requests. + /// Returns the async result from the BeginXXX method. + /// + /// CompletedSynchronously (for System.Net networking stack) means "was the operation completed before the first time + /// that somebody asked if it was completed synchronously"? They do this because some of their asynchronous operations + /// (particularly those in the Socket class) will avoid the cost of capturing and transferring the ExecutionContext + /// to the callback thread by checking CompletedSynchronously, and calling the callback from within BeginXxx instead of + /// on the completion port thread if the native winsock call completes quickly. + /// + /// For other operations however (notably those in HttpWebRequest), they use the same underlying IAsyncResult implementation, + /// but do NOT check CompletedSynchronously before returning from BeginXxx. That means that CompletedSynchronously will + /// be false if and only if you checked it from the thread which called BeginXxx BEFORE the operation completed. It will + /// then continue to be false even after IsCompleted becomes true. + /// + /// Note that CompletedSynchronously == true does not guarantee anything about how much of your callback has executed. + /// + /// The usual pattern for handling synchronous completion is that both the caller and callback should check CompletedSynchronously. + /// If its true, the callback should do nothing and the caller should call EndRead and process the result. + /// This guarantees that the caller and callback are not accessing the stream or buffer concurrently without the need + /// for explicit synchronization between the two. + /// + internal static IAsyncResult InvokeAsync(Func asyncAction, AsyncCallback callback, object state) + { + IAsyncResult asyncResult = asyncAction(BaseAsyncResult.GetDataServiceAsyncCallback(callback), state); + return PostInvokeAsync(asyncResult, callback); + } + +#if PORTABLELIB + /// + /// This is the Win8 version of the InvokeAsync overload below. See comments on that method for more details. + /// + /// + /// Beta bits of the Win8 profile always return false for IAsyncResult.CompletedSynchronously, but that + /// is not guaranteed, so keeping the existing pattern here that we use on other platforms. + /// + /// + /// Func that invokes the async operation. We must use our special callback from GetDataServiceAsyncCallback(), see InvokeAsync comments below for details. + /// + /// buffer to transfer the data + /// byte offset in buffer + /// max number of bytes in the buffer + /// async callback to be called when the operation is complete + /// A user-provided object that distinguishes this particular asynchronous request from other requests. + /// An Task that represents the asynchronous operation, which could still be pending. + internal static Task InvokeTask(Func task, byte[] buffer, int offset, int length, Action callback, object state) + { + Action taskCallback = BaseAsyncResult.GetDataServiceTaskCallback(callback); + Task returnTask = task(buffer, offset, length).FollowOnSuccessWith(t => taskCallback(t, state)); + return PostInvokeTask(returnTask, callback, state); + } +#else + /// + /// Due to the unexpected behaviors of IAsyncResult.CompletedSynchronously in the System.Net networking stack, we have to make + /// async calls to their APIs using the specific pattern they've prescribed. This method runs in the caller thread and invokes + /// the BeginXXX methods. It then checks IAsyncResult.CompletedSynchronously and if it is true, we invoke the callback in the + /// caller thread. + /// + /// + /// This is the action that invokes the BeginXXX method. Note we MUST use our special callback from GetDataServiceAsyncCallback() + /// when invoking the async call. + /// + /// buffer to transfer the data + /// byte offset in buffer + /// max number of bytes in the buffer + /// async callback to be called when the operation is complete + /// A user-provided object that distinguishes this particular asynchronous request from other requests. + /// An IAsyncResult that represents the asynchronous operation, which could still be pending + /// + /// Please see remarks on the other InvokeAsync() overload. + /// Also note that the InvokeTask method above is a Win8 version of this method, so it should be kept in sync with any changes that occur here. + /// + internal static IAsyncResult InvokeAsync(AsyncAction asyncAction, byte[] buffer, int offset, int length, AsyncCallback callback, object state) + { + IAsyncResult asyncResult = asyncAction(buffer, offset, length, BaseAsyncResult.GetDataServiceAsyncCallback(callback), state); + return PostInvokeAsync(asyncResult, callback); + } +#endif + + /// + /// Sets the CompletedSynchronously property. + /// + /// true if the async operation was completed synchronously, false otherwise. + internal void SetCompletedSynchronously(bool isCompletedSynchronously) + { + // This is equivalent to "CompletedSynchronously &= completedSynchronously". The &= operator is a nonatomic operation involving a read and a write operations. + Interlocked.CompareExchange(ref this.completedSynchronously, isCompletedSynchronously ? BaseAsyncResult.True : BaseAsyncResult.False, BaseAsyncResult.True); + Debug.Assert(isCompletedSynchronously || this.completedSynchronously == BaseAsyncResult.False, "this.completedSynchronously must be false if the isCompletedSynchronously argument is false."); + } + + /// Set the AsyncWait and invoke the user callback. + /// + /// If the background thread gets a ThreadAbort, the userCallback will never be invoked. + /// This is why it's generally important to never wait forever, but to have more specific + /// time limit. Also then cancel the operation, to make sure its stopped, to avoid + /// multi-threading if your wait time limit was just too short. + /// + internal void HandleCompleted() + { + // TODO: even if background thread of async operation encounters + // an "uncatchable" exception, do the minimum to unblock the async result. + if (this.IsCompletedInternally && (System.Threading.Interlocked.Exchange(ref this.userNotified, 1) == 0)) + { + this.abortable = null; // reset abort via CancelRequest + try + { + // avoid additional work when aborting for exceptional reasons + if (CommonUtil.IsCatchableExceptionType(this.Failure)) + { + // the CompleteRequest may do additional work which is why + // it is important not to signal the user via either the + // IAsyncResult.IsCompleted, IAsyncResult.WaitHandle or the callback + this.CompletedRequest(); + } + } + catch (Exception ex) + { + if (this.HandleFailure(ex)) + { + throw; + } + } + finally + { + // 1. set IAsyncResult.IsCompleted, otherwise user was + // signalled on another thread, but the property may not be true. + this.userCompleted = true; + + // 2. signal the wait handle because it can't be first nor can it be last. + // + // There is a very small window for race condition between setting the wait handle here and disposing + // the wait handle inside EndExecute(). Say thread1 is the async thread that executes up till this point, i.e. right + // after userCompleted is set to true and before the asyncWait is signaled; thread2 wakes up and calls EndExecute() till + // right before we try to dispose the wait handle; thread3 wakes up and calls AsyncWaitHandle which creates a new instance + // for this.asyncWait; thread2 then resumes to dispose this.asyncWait and if at this point thread1 sets this.asyncWait, + // we'll get an ObjectDisposedException on thread1. SetAsyncWaitHandle() will protect this scenario with a critical section. + this.SetAsyncWaitHandle(); + + // 3. invoke the callback because user may throw an exception and stop any further processing +#if PORTABLELIB + if ((null != this.userCallback)) +#else + if ((null != this.userCallback) && !(this.Failure is System.Threading.ThreadAbortException) && !(this.Failure is System.StackOverflowException)) +#endif + { // any exception thrown by user should be "unhandled" + // it's possible callback will be invoked while another creates and sets the asyncWait + this.userCallback(this); + } + } + } + } + + /// Cache the exception that happened on the background thread for the caller of EndSaveChanges. + /// exception object from background thread + /// true if the exception (like StackOverflow or ThreadAbort) should be rethrown + internal bool HandleFailure(Exception e) + { + System.Threading.Interlocked.CompareExchange(ref this.failure, e, null); + this.SetCompleted(); + return !CommonUtil.IsCatchableExceptionType(e); + } + + /// Set the async result as completed and aborted. + internal void SetAborted() + { + System.Threading.Interlocked.Exchange(ref this.completed, 2); + } + + /// Set the async result as completed. + internal void SetCompleted() + { + System.Threading.Interlocked.CompareExchange(ref this.completed, 1, 0); + } + + /// verify they have the same reference + /// the actual thing + /// the expected thing + /// error code if they are not + protected static void EqualRefCheck(PerRequest actual, PerRequest expected, InternalError errorcode) + { + if (!Object.ReferenceEquals(actual, expected)) + { + Error.ThrowInternalError(errorcode); + } + } + + /// invoked for derived classes to cleanup before callback is invoked + protected abstract void CompletedRequest(); + + /// Disposes the request object if it is not null. Invokes the user callback + /// the request object + protected abstract void HandleCompleted(PerRequest pereq); + + /// handle request.BeginGetResponse with request.EndGetResponse and then copy response stream + /// async result + protected abstract void AsyncEndGetResponse(IAsyncResult asyncResult); + + /// verify non-null and not completed + /// the request in progress + /// error code if null or completed + protected virtual void CompleteCheck(PerRequest value, InternalError errorcode) + { + if ((null == value) || value.RequestCompleted) + { + // since PerRequest is nested, it won't get set true during Abort unlike BaseAsyncResult + // but like QueryAsyncResult, when the request is aborted it it lets the request throw on next operation + Error.ThrowInternalError(errorcode); + } + } + + /// Read and store response data for the current change, and try to start the next one + /// the completed per request object + protected virtual void FinishCurrentChange(PerRequest pereq) + { + if (!pereq.RequestCompleted) + { + Error.ThrowInternalError(InternalError.SaveNextChangeIncomplete); + } + + // Note that this.perRequest can be set to null by another thread executing concurrently in this.HandleCompleted(pereq). + // We need to cache the value for this.perRequest and only call EqualRefCheck() if it is not null. + PerRequest request = this.perRequest; + if (null != request) + { + EqualRefCheck(request, pereq, InternalError.InvalidSaveNextChange); + } + } + + /// Cache the exception that happened on the background thread for the caller of EndSaveChanges. + /// the request object + /// exception object from background thread + /// true if the exception should be rethrown + protected bool HandleFailure(PerRequest pereq, Exception e) + { + if (null != pereq) + { + if (this.IsAborted) + { + pereq.SetAborted(); + } + else + { + pereq.SetComplete(); + } + } + + return this.HandleFailure(e); + } + + /// handle request.BeginGetRequestStream with request.EndGetRequestStream and then write out request stream + /// async result + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + protected void AsyncEndGetRequestStream(IAsyncResult asyncResult) + { + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted"); + AsyncStateBag asyncState = asyncResult.AsyncState as AsyncStateBag; + PerRequest pereq = asyncState == null ? null : asyncState.PerRequest; + + try + { + this.CompleteCheck(pereq, InternalError.InvalidEndGetRequestCompleted); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginGetRequestStream + + EqualRefCheck(this.perRequest, pereq, InternalError.InvalidEndGetRequestStream); + + ODataRequestMessageWrapper requestMessage = Util.NullCheck(pereq.Request, InternalError.InvalidEndGetRequestStreamRequest); + + Stream httpRequestStream = Util.NullCheck(requestMessage.EndGetRequestStream(asyncResult), InternalError.InvalidEndGetRequestStreamStream); + pereq.RequestStream = httpRequestStream; + + ContentStream contentStream = pereq.RequestContentStream; + Util.NullCheck(contentStream, InternalError.InvalidEndGetRequestStreamContent); + Util.NullCheck(contentStream.Stream, InternalError.InvalidEndGetRequestStreamContent); + + if (contentStream.IsKnownMemoryStream) + { + MemoryStream memoryStream = contentStream.Stream as MemoryStream; +#if PORTABLELIB + byte[] buffer = memoryStream.ToArray(); +#else + byte[] buffer = memoryStream.GetBuffer(); +#endif + int bufferOffset = checked((int)memoryStream.Position); + int bufferLength = checked((int)memoryStream.Length) - bufferOffset; + if ((null == buffer) || (0 == bufferLength)) + { + Error.ThrowInternalError(InternalError.InvalidEndGetRequestStreamContentLength); + } + } + + // Start the Read on the request content stream. + // Note that we don't deal with synchronous results here. + // If the read finishes synchronously the AsyncRequestContentEndRead will be called from inside the BeginRead + // call below. In there we will call BeginWrite. If that completes synchronously we will loop + // and call BeginRead again. If that completes synchronously as well we will call BeginWrite and so on. + // AsyncEndWrite will return immediately if it finished synchronously (otherwise it calls BeginRead). + // So in the worst case we will have a stack like this: + // AsyncEndGetRequestStream + // AsyncRequestContentEndRead + // AsyncRequestContentEndRead or AsyncEndWrite + + // We just need to differentiate between the first AsyncRequestContentEndRead and the others (the first one + // must not return even if it completed synchronously, otherwise we would have to do the loop here as well). + // We'll use the RequestContentBufferValidLength as the notification. It will start with -1 which means + // we didn't read anything at all and thus it's the first read ending. + pereq.RequestContentBufferValidLength = -1; + + Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetRequestStream_BeforeBeginRead"); +#if PORTABLELIB + asyncResult = BaseAsyncResult.InvokeTask(contentStream.Stream.ReadAsync, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, asyncState); +#else + asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, asyncState); +#endif + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + } + catch (Exception e) + { + if (this.HandleFailure(pereq, e)) + { + throw; + } + } + finally + { + this.HandleCompleted(pereq); + } + } + + /// + /// Due to the unexpected behaviors of IAsyncResult.CompletedSynchronously in the System.Net networking stack, we have to make + /// async calls to their APIs using the specific pattern they've prescribed. This method runs in the caller thread after the + /// BeginXXX method returns. It checks IAsyncResult.CompletedSynchronously and if it is true, we invoke the callback in the + /// caller thread. + /// + /// The IAsyncResult that represents the asynchronous operation we just called, which could still be pending + /// Callback to be invoked when IAsyncResult.CompletedSynchronously is true. + /// Returns an IAsyncResult that represents the asynchronous operation we just called, which could still be pending + /// + /// Please see remarks on BaseAsyncResult.InvokeAsync(). + /// Also note that PostInvokeTask below is a Win8 version of this method, so it should be kept in sync with any changes that occur here. + /// + private static IAsyncResult PostInvokeAsync(IAsyncResult asyncResult, AsyncCallback callback) + { + Debug.Assert(asyncResult != null, "asyncResult != null"); + if (asyncResult.CompletedSynchronously) + { + Debug.Assert(asyncResult.IsCompleted, "asyncResult.IsCompleted"); + callback(asyncResult); + } + + return asyncResult; + } + +#if PORTABLELIB + /// + /// This is the Win8 version of the PostInvokeAsync method above. Note that method is still used where possible on Win8, but there are some + /// case where the Win8 API has been completely migrated to use Task, so this method supports that usage. + /// + /// + /// See PostInvokeAsync for more details. + /// + /// The Task that represents the asynchronous operation we just called, which could still be pending + /// Callback to be invoked when IAsyncResult.CompletedSynchronously is true. + /// A user-provided object that distinguishes this particular asynchronous request from other requests. + /// Returns a Task that represents the asynchronous operation we just called, which could still be pending. + private static Task PostInvokeTask(Task task, Action callback, object state) + { + Debug.Assert(task != null, "task != null"); + if (((IAsyncResult)task).CompletedSynchronously) + { + Debug.Assert(task.IsCompleted, "asyncResult.IsCompleted"); + callback(task, state); + } + + return task; + } +#endif + + /// + /// Due to the unexpected behaviors of IAsyncResult.CompletedSynchronously in the System.Net networking stack, we have to make + /// async calls to their APIs using the specific pattern they've prescribed. This method returns an AsyncCallback which we can pass + /// to the BeginXXX methods in the caller thread. The returned callback will only run the wrapped callback if + /// IAsyncResult.CompletedSynchronously is false, otherwise it returns immediately. + /// + /// callback to be wrapped + /// Returns a callback which will only run the wrapped callback if IAsyncResult.CompletedSynchronously is false, otherwise it returns immediately. + /// + /// Please see remarks on BaseAsyncResult.InvokeAsync(). + /// Also note that the GetDataServiceTaskCallback method below is a Win8 version of this method, so it should be kept in sync with any changes that occur here. + /// + private static AsyncCallback GetDataServiceAsyncCallback(AsyncCallback callback) + { + return (asyncResult) => + { + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult != null && asyncResult.IsCompleted"); + if (asyncResult.CompletedSynchronously) + { + return; + } + + callback(asyncResult); + }; + } + +#if PORTABLELIB + /// + /// This is the Win8 version of the GetDataServiceAsyncCallback overload above. See comments on that method for more details. + /// + /// + /// Beta bits of the Win8 .NETCore profile always return false for IAsyncResult.CompletedSynchronously, but that + /// is not guaranteed, so keeping the existing pattern here that we use on other platforms. + /// + /// callback to be wrapped + /// Returns a callback which will only run the wrapped callback if IAsyncResult.CompletedSynchronously is false, otherwise it returns immediately. + private static Action GetDataServiceTaskCallback(Action callback) + { + return (task, state) => + { + Debug.Assert(task != null && task.IsCompleted, "task != null && task.IsCompleted"); + if (((IAsyncResult)task).CompletedSynchronously) + { + return; + } + + callback(task, state); + }; + } +#endif + + /// + /// Sets the async wait handle + /// + private void SetAsyncWaitHandle() + { + if (null != this.asyncWait) + { + System.Threading.Interlocked.CompareExchange(ref this.asyncWaitDisposeLock, new object(), null); + lock (this.asyncWaitDisposeLock) + { + if (!this.asyncWaitDisposed) + { + this.asyncWait.Set(); + } + } + } + } + +#if PORTABLELIB + /// + /// Callback for Stream.ReadAsync on the request content input stream. Calls request content output stream WriteAsync + /// and in case of synchronous also the next ReadAsync. + /// + /// The task associated with the completed operation. + /// State associated with the task. + private void AsyncRequestContentEndRead(Task task, object asyncState) +#else + /// + /// Callback for Stream.BeginRead on the request content input stream. Calls request content output stream BeginWrite + /// and in case of synchronous also the next BeginRead. + /// + /// The asynchronous result associated with the completed operation. + private void AsyncRequestContentEndRead(IAsyncResult asyncResult) +#endif + { +#if PORTABLELIB + IAsyncResult asyncResult = (IAsyncResult)task; +#endif + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted"); +#if PORTABLELIB + AsyncStateBag asyncStateBag = asyncState as AsyncStateBag; +#else + AsyncStateBag asyncStateBag = asyncResult.AsyncState as AsyncStateBag; +#endif + PerRequest pereq = asyncStateBag == null ? null : asyncStateBag.PerRequest; + + try + { + this.CompleteCheck(pereq, InternalError.InvalidEndReadCompleted); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginRead + + EqualRefCheck(this.perRequest, pereq, InternalError.InvalidEndRead); + + ContentStream contentStream = pereq.RequestContentStream; + Util.NullCheck(contentStream, InternalError.InvalidEndReadStream); + Util.NullCheck(contentStream.Stream, InternalError.InvalidEndReadStream); + + Stream httpRequestStream = Util.NullCheck(pereq.RequestStream, InternalError.InvalidEndReadStream); + + Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeEndRead"); +#if PORTABLELIB + int count = ((Task)task).Result; +#else + int count = contentStream.Stream.EndRead(asyncResult); +#endif + if (0 < count) + { + bool firstEndRead = (pereq.RequestContentBufferValidLength == -1); + pereq.RequestContentBufferValidLength = count; + + // If we completed synchronously then just return. Our caller will take care of processing the results. + // First EndRead must not return even if completed synchronously. + if (!asyncResult.CompletedSynchronously || firstEndRead) + { + do + { + // Write the data we've read to the request stream + Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeBeginWrite"); +#if PORTABLELIB + asyncResult = BaseAsyncResult.InvokeTask(httpRequestStream.WriteAsync, pereq.RequestContentBuffer, 0, pereq.RequestContentBufferValidLength, this.AsyncEndWrite, asyncStateBag); +#else + asyncResult = BaseAsyncResult.InvokeAsync(httpRequestStream.BeginWrite, pereq.RequestContentBuffer, 0, pereq.RequestContentBufferValidLength, this.AsyncEndWrite, asyncStateBag); +#endif + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + + // If the write above completed synchronously + // immediately start the next read so that we loop instead of recursion. + // If it completed asynchronously we just return as we will deal with the results in the EndWrite + if (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally) + { + Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeBeginRead"); +#if PORTABLELIB + asyncResult = BaseAsyncResult.InvokeTask(contentStream.Stream.ReadAsync, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, asyncStateBag); +#else + asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, asyncStateBag); +#endif + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + } + + // If the read completed synchronously as well we loop to write the data to the request stream without recursion. + // Only loop if there's actually some data to be processed. If there's no more data then return. + // The request will continue inside the inner call to AsyncRequestContentEndRead (which will get 0 data + // and will end up in the else branch of the big if). + } + while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && + pereq.RequestContentBufferValidLength > 0); + } + } + else + { + // Done reading data (and writing them) + pereq.RequestContentBufferValidLength = 0; + pereq.RequestStream = null; +#if PORTABLELIB + httpRequestStream.Dispose(); +#else + httpRequestStream.Close(); +#endif + ODataRequestMessageWrapper requestMessage = Util.NullCheck(pereq.Request, InternalError.InvalidEndWriteRequest); + asyncResult = BaseAsyncResult.InvokeAsync(requestMessage.BeginGetResponse, this.AsyncEndGetResponse, asyncStateBag); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginGetResponse + } + } + catch (Exception e) + { + if (this.HandleFailure(pereq, e)) + { + throw; + } + } + finally + { + this.HandleCompleted(pereq); + } + } + +#if PORTABLELIB + /// Handle requestStream.WriteAsync and complete the write operation, then call BeginGetResponse. + /// The task associated with the completed operation. + /// State associated with the task. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + private void AsyncEndWrite(Task task, object asyncState) +#else + /// handle requestStream.BeginWrite with requestStream.EndWrite then BeginGetResponse. + /// async result + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + private void AsyncEndWrite(IAsyncResult asyncResult) +#endif + { +#if PORTABLELIB + IAsyncResult asyncResult = (IAsyncResult)task; +#endif + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted"); +#if PORTABLELIB + AsyncStateBag asyncStateBag = asyncState as AsyncStateBag; +#else + AsyncStateBag asyncStateBag = asyncResult.AsyncState as AsyncStateBag; +#endif + PerRequest pereq = asyncStateBag == null ? null : asyncStateBag.PerRequest; + + try + { + this.CompleteCheck(pereq, InternalError.InvalidEndWriteCompleted); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginWrite + + EqualRefCheck(this.perRequest, pereq, InternalError.InvalidEndWrite); + + ContentStream contentStream = pereq.RequestContentStream; + Util.NullCheck(contentStream, InternalError.InvalidEndWriteStream); + Util.NullCheck(contentStream.Stream, InternalError.InvalidEndWriteStream); + Stream httpRequestStream = Util.NullCheck(pereq.RequestStream, InternalError.InvalidEndWriteStream); + Util.DebugInjectFault("SaveAsyncResult::AsyncEndWrite_BeforeEndWrite"); +#if PORTABLELIB + // Ensure we surface any errors that may have occurred during the write operation + task.Wait(); +#else + httpRequestStream.EndWrite(asyncResult); +#endif + // If the write completed synchronously just return. The caller (AsyncRequestContentEndRead) + // will loop and initiate the next read. + // If the write completed asynchronously we need to start the next read here. Note that we start the read + // regardless if the stream has other data to offer or not. This is to avoid dealing with the end + // of the read/write loop in several places. We simply issue a read which (if the stream is at the end) + // will return 0 bytes and we will deal with that in the AsyncRequestContentEndRead method. + if (!asyncResult.CompletedSynchronously) + { + Util.DebugInjectFault("SaveAsyncResult::AsyncEndWrite_BeforeBeginRead"); +#if PORTABLELIB + asyncResult = BaseAsyncResult.InvokeTask(contentStream.Stream.ReadAsync, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, asyncStateBag); +#else + asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, asyncStateBag); +#endif + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + } + } + catch (Exception e) + { + if (this.HandleFailure(pereq, e)) + { + throw; + } + } + finally + { + this.HandleCompleted(pereq); + } + } + + /// + /// Wraps PerRequest and context reference together to save state information. + /// Used instead of KeyValuePair in order to avoid FxCop CA908. + /// + protected sealed class AsyncStateBag + { + /// the request wrapper. + internal readonly PerRequest PerRequest; + + /// + /// Constructor for the state object + /// + /// See PerRequest field. + internal AsyncStateBag(PerRequest pereq) + { + Debug.Assert(pereq != null, "pereq cannot be null"); + this.PerRequest = pereq; + } + } + + /// wrap the full request + protected sealed class PerRequest + { + /// + /// The int equivalent for true. + /// + private const int True = 1; + + /// + /// The int equivalent for false. + /// + private const int False = 0; + + /// + /// did the sequence (BeginGetRequest, EndGetRequest, ... complete. 0 = In Progress, 1 = Completed, 2 = Aborted + /// + private int requestStatus; + + /// + /// Buffer used when pumping data from the write stream to the request content stream + /// + private byte[] requestContentBuffer; + + /// True if Dispose is called. + private bool isDisposed; + + /// Synchronize the Dispose method calls. + private object disposeLock = new object(); + + /// Did the request complete all of its steps synchronously? 1 represents true and 0 represents false. + /// Note that there is no overload for Interlocked.CompareExchange that takes booleans, we workaround using the overload for int. + private int requestCompletedSynchronously; + + /// ctor + internal PerRequest() + { + this.requestCompletedSynchronously = PerRequest.True; + } + + /// active web request + internal ODataRequestMessageWrapper Request + { + get; + set; + } + + /// active web request stream + internal Stream RequestStream + { + get; + set; + } + + /// content to write to request stream + internal ContentStream RequestContentStream + { + get; + set; + } + + /// web response + internal IODataResponseMessage ResponseMessage + { + get; + set; + } + + /// async web response stream + internal Stream ResponseStream + { + get; + set; + } + + /// did the request complete all of its steps synchronously? + internal bool RequestCompletedSynchronously + { + get + { + return this.requestCompletedSynchronously == PerRequest.True; + } + } + + /// + /// Short cut for testing if request has finished (either completed or aborted) + /// + internal bool RequestCompleted + { + get { return this.requestStatus != 0; } + } + + /// + /// Short cut for testing request status is 2 (Aborted) + /// + internal bool RequestAborted + { + get { return this.requestStatus == 2; } + } + + /// + /// Buffer used when pumping data from the write stream to the request content stream + /// + internal byte[] RequestContentBuffer + { + get + { + if (this.requestContentBuffer == null) + { + this.requestContentBuffer = new byte[WebUtil.DefaultBufferSizeForStreamCopy]; + } + + return this.requestContentBuffer; + } + } + + /// + /// The length of the valid content in the RequestContentBuffer + /// Once the data is read from the request content stream into the RequestContent buffer + /// this length is set to the amount of data read. + /// When the data is written into the request stream it is set back to 0. + /// + internal int RequestContentBufferValidLength + { + get; + set; + } + + /// + /// Sets the RequestCompletedSynchronously property. + /// + /// true if the async operation was completed synchronously, false otherwise. + internal void SetRequestCompletedSynchronously(bool completedSynchronously) + { + // This is equivalent to "RequestCompletedSynchronously &= completedSynchronously". The &= operator is a nonatomic operation involving a read and a write operations. + Interlocked.CompareExchange(ref this.requestCompletedSynchronously, completedSynchronously ? PerRequest.True : PerRequest.False, PerRequest.True); + Debug.Assert(completedSynchronously || this.requestCompletedSynchronously == PerRequest.False, "this.requestCompletedSynchronously must be false if the completedSynchronously argument is false."); + } + + /// + /// Change the request status to completed + /// + internal void SetComplete() + { + System.Threading.Interlocked.CompareExchange(ref this.requestStatus, 1, 0); + } + + /// + /// Change the request status to aborted + /// + internal void SetAborted() + { + System.Threading.Interlocked.Exchange(ref this.requestStatus, 2); + } + + /// + /// dispose of the request object + /// + internal void Dispose() + { + if (this.isDisposed) + { + return; + } + + // The dispose method is called by BaseSaveResult.HandleCompleted() and SaveResult.FinishCurrentChange(). + // These methods can be run by multiple threads. We need to protect the Dispose method with a critical section + // or else we will get sporadic null ref exceptions. + lock (this.disposeLock) + { + if (!this.isDisposed) + { + this.isDisposed = true; + + if (null != this.ResponseStream) + { + this.ResponseStream.Dispose(); + this.ResponseStream = null; + } + + if (null != this.RequestContentStream) + { + if (this.RequestContentStream.Stream != null && this.RequestContentStream.IsKnownMemoryStream) + { + this.RequestContentStream.Stream.Dispose(); + } + + // We must not dispose the stream which came from outside + // the disposing/closing of that stream depends on parameter passed to us and is dealt with + // at the end of SaveChanges process. + this.RequestContentStream = null; + } + + if (null != this.RequestStream) + { + try + { + Util.DebugInjectFault("PerRequest::Dispose_BeforeRequestStreamDisposed"); + this.RequestStream.Dispose(); + this.RequestStream = null; + } + catch (Exception e) + { + // if the request is aborted, then the connect stream + // cannot be disposed - since not all bytes are written to it yet + // In this case, we eat the exception + // Otherwise, keep throwing, always throw if the exception is not catchable. + if (!this.RequestAborted || !CommonUtil.IsCatchableExceptionType(e)) + { + throw; + } + + // Call Injector to report the exception so test code can verify it is thrown + Util.DebugInjectFault("PerRequest::Dispose_WebExceptionThrown"); + } + } + + WebUtil.DisposeMessage(this.ResponseMessage); + this.Request = null; + this.SetComplete(); + } + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseEntityType.cs new file mode 100644 index 0000000..ca34e43 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseEntityType.cs @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// + /// Base type of entity type to include for function and action invocation + /// + public class BaseEntityType + { + /// DataServiceContext for query provider + protected internal DataServiceContext Context { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseSaveResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseSaveResult.cs new file mode 100644 index 0000000..aa3aac1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BaseSaveResult.cs @@ -0,0 +1,1481 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Threading; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Base class for building the request and handling the response + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Pending")] + internal abstract class BaseSaveResult : BaseAsyncResult + { + #region Private Fields + + /// where to pull the changes from + protected readonly RequestInfo RequestInfo; + + /// serializer to serialize the request data. + protected readonly Serializer SerializerInstance; + + /// sorted list of entries by change order + protected readonly List ChangedEntries; + + /// option in use for SaveChanges + protected readonly SaveChangesOptions Options; + + /// batch web response + protected IODataResponseMessage batchResponseMessage; + + /// The ResourceBox or RelatedEnd currently in flight + protected int entryIndex = -1; + + /// what kind of request are we processing - POST MR or PUT MR + protected StreamRequestKind streamRequestKind; + + /// + /// If the is set to anything but None, + /// this field holds a stream needs to be send in the request. + /// This can be null in the case where the content of MR is empty. (In which case + /// we will not try to open the request stream and thus avoid additional async call). + /// + protected Stream mediaResourceRequestStream; + + /// temporary buffer when cache results from CUD op in non-batching save changes + protected byte[] buildBatchBuffer; + + #endregion Private Fields + + /// + /// constructor for operations + /// + /// context + /// method + /// queries + /// options + /// user callback + /// user state object + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "IsBatch returns a constant value and hence safe to be invoked from the constructor")] + internal BaseSaveResult(DataServiceContext context, string method, DataServiceRequest[] queries, SaveChangesOptions options, AsyncCallback callback, object state) + : base(context, method, callback, state) + { + this.RequestInfo = new RequestInfo(context); + this.Options = options; + this.SerializerInstance = new Serializer(this.RequestInfo, options); + + if (null == queries) + { + #region changed entries + this.ChangedEntries = context.EntityTracker.Entities.Cast() + .Union(context.EntityTracker.Links.Cast()) + .Union(context.EntityTracker.Entities.SelectMany(e => e.StreamDescriptors).Cast()) + .Where(o => o.IsModified && o.ChangeOrder != UInt32.MaxValue) + .OrderBy(o => o.ChangeOrder) + .ToList(); + + foreach (Descriptor e in this.ChangedEntries) + { + e.ContentGeneratedForSave = false; + e.SaveResultWasProcessed = 0; + e.SaveError = null; + + if (e.DescriptorKind == DescriptorKind.Link) + { + object target = ((LinkDescriptor)e).Target; + if (null != target) + { + Descriptor f = context.EntityTracker.GetEntityDescriptor(target); + if (EntityStates.Unchanged == f.State) + { + f.ContentGeneratedForSave = false; + f.SaveResultWasProcessed = 0; + f.SaveError = null; + } + } + } + } + #endregion + } + else + { + this.ChangedEntries = new List(); + } + } + + /// + /// enum which says what kind of request we are currently processing + /// + protected enum StreamRequestKind + { + /// This request doesn't involve Media Resource or named stream. + None = 0, + + /// This request is a POST to a MLE and the body contains the content of the MR. + PostMediaResource, + + /// This request is a PUT to MR and the body contains the content of the MR. + PutMediaResource, + } + + /// returns true if its a batch, otherwise returns false. + internal abstract bool IsBatchRequest { get; } + + /// + /// In async case, this is a memory stream used to cache responses, as we are reading async from the underlying http web response stream. + /// In sync case, this is the actual response stream, as returned by the http request. + /// + protected abstract Stream ResponseStream + { + get; + } + + /// + /// returns true if the response payload needs to be processed. + /// + protected abstract bool ProcessResponsePayload + { + get; + } + + /// + /// factory method for SaveResult + /// + /// context + /// method + /// queries + /// options + /// user callback + /// user state object + /// a new instance of SaveResult or BatchSaveResult, depending on the options value. + internal static BaseSaveResult CreateSaveResult(DataServiceContext context, string method, DataServiceRequest[] queries, SaveChangesOptions options, AsyncCallback callback, object state) + { + if (!Util.IsBatch(options)) + { + Debug.Assert(queries == null, "In non-batch case, queries must be null"); + return new SaveResult(context, method, options, callback, state); + } + else + { + return new BatchSaveResult(context, method, queries, options, callback, state); + } + } + + /// + /// Handle response by looking at status and possibly throwing an exception. + /// + /// The request info. + /// response status code + /// Version string on the response header; possibly null. + /// delegate to get response stream + /// throw or return on failure + /// Parsed response version (null if no version was specified). + /// exception on failure + internal static InvalidOperationException HandleResponse( + RequestInfo requestInfo, + HttpStatusCode statusCode, + string responseVersion, + Func getResponseStream, + bool throwOnFailure, + out Version parsedResponseVersion) + { + InvalidOperationException failure = null; + if (!CanHandleResponseVersion(responseVersion, out parsedResponseVersion)) + { + string description = Strings.Context_VersionNotSupported(responseVersion, SerializeSupportedVersions()); + failure = Error.InvalidOperation(description); + } + + if (failure == null) + { + failure = requestInfo.ValidateResponseVersion(parsedResponseVersion); + } + + if (failure == null && !WebUtil.SuccessStatusCode(statusCode)) + { + failure = GetResponseText(getResponseStream, statusCode); + } + + if (failure != null && throwOnFailure) + { + throw failure; + } + + return failure; + } + + /// + /// get the response text into a string + /// + /// method to get response stream + /// status code + /// text + [SuppressMessage("Microsoft.Design", "CA1031", Justification = "Cache exception so user can examine it later")] + internal static DataServiceClientException GetResponseText(Func getResponseStream, HttpStatusCode statusCode) + { + string message = null; + using (Stream stream = getResponseStream()) + { + if ((null != stream) && stream.CanRead) + { + // this StreamReader can go out of scope without dispose because the underly stream is disposed of + message = new StreamReader(stream).ReadToEnd(); + } + } + + if (string.IsNullOrEmpty(message)) + { + message = statusCode.ToString(); + } + + return new DataServiceClientException(message, (int)statusCode); + } + + /// process the batch + /// data service response + internal DataServiceResponse EndRequest() + { + // Close all Save streams before we return (and do this before we throw for any errors below) + foreach (Descriptor descriptor in this.ChangedEntries) + { + descriptor.ClearChanges(); + } + + return this.HandleResponse(); + } + + /// Get the value of HttpMethod enum from link resource state + /// Instance of LinkDescriptor containing the link state and type of link. + /// HttpMethod enum value for the link descriptor state. + protected static string GetLinkHttpMethod(LinkDescriptor link) + { + if (!link.IsSourcePropertyCollection) + { + Debug.Assert(EntityStates.Modified == link.State, "not Modified state"); + if (null == link.Target) + { // REMOVE/DELETE a reference + return XmlConstants.HttpMethodDelete; + } + else + { // UPDATE/PUT a reference + return XmlConstants.HttpMethodPut; + } + } + else if (EntityStates.Deleted == link.State) + { // you call DELETE on $ref + return XmlConstants.HttpMethodDelete; + } + else + { // you INSERT/POST into a collection + Debug.Assert(EntityStates.Added == link.State, "not Added state"); + return XmlConstants.HttpMethodPost; + } + } + + /// + /// Apply the response preferences for the client. + /// + /// Headers to which preferences will be added. + /// HTTP method. + /// Response preference. + /// Request version so far for the request. The method may modify it. + protected static void ApplyPreferences(HeaderCollection headers, string method, DataServiceResponsePreference responsePreference, ref Version requestVersion) + { + // The AddAndUpdateResponsePreference only applies to POST/PUT/PATCH requests + if (string.CompareOrdinal(XmlConstants.HttpMethodPost, method) != 0 && + string.CompareOrdinal(XmlConstants.HttpMethodPut, method) != 0 && + string.CompareOrdinal(XmlConstants.HttpMethodPatch, method) != 0) + { + return; + } + + string preferHeaderValue = WebUtil.GetPreferHeaderAndRequestVersion(responsePreference, ref requestVersion); + if (preferHeaderValue != null) + { + headers.SetHeader(XmlConstants.HttpPrefer, preferHeaderValue); + } + } + + /// + /// Handle response. + /// + /// an instance of the DataServiceResponse. + protected abstract DataServiceResponse HandleResponse(); + + /// + /// Returns the request message to write the headers and payload into. + /// + /// Http method for the request. + /// Base Uri for the request. + /// Request headers. + /// HttpStack to use. + /// Descriptor for the request, if there is one. + /// Content-ID header that could be used in batch request. + /// an instance of IODataRequestMessage. + protected abstract ODataRequestMessageWrapper CreateRequestMessage(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor, string contentId); + + /// Get the value of the HttpMethod enum from entity resource state + /// resource state + /// The version of the request determined so far. The method may modify this if needed. + /// HttpMethod value from the entity resource state. + protected string GetHttpMethod(EntityStates state, ref Version requestVersion) + { + switch (state) + { + case EntityStates.Deleted: + return XmlConstants.HttpMethodDelete; + case EntityStates.Modified: + if (Util.IsFlagSet(this.Options, SaveChangesOptions.ReplaceOnUpdate)) + { + return XmlConstants.HttpMethodPut; + } + else + { + // Default update is PATCH not PUT + return XmlConstants.HttpMethodPatch; + } + + case EntityStates.Added: + return XmlConstants.HttpMethodPost; + default: + throw Error.InternalError(InternalError.UnvalidatedEntityState); + } + } + + /// + /// Create request message for the descriptor at the given index. + /// + /// Index into changed entries + /// IODataRequestMessage that needs to be used for writing the payload. + /// true, if any request payload was generated, else false. + protected bool CreateChangeData(int index, ODataRequestMessageWrapper requestMessage) + { + Descriptor descriptor = this.ChangedEntries[index]; + Debug.Assert(!descriptor.ContentGeneratedForSave, "already saved entity/link"); + + // Since batch payloads do not support MR and Named Stream, the code for handling that has been moved to + // SaveResult.CreateNonBatchChangeData. In this method, we only handle entity and link payload. + Debug.Assert(descriptor.DescriptorKind != DescriptorKind.NamedStream, "NamedStream payload is not supported in batch"); + + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; + Debug.Assert(this.streamRequestKind == StreamRequestKind.None, "Batch does not support stream payloads. Hence this code should not get called at all"); + + // either normal entity or second call for media link entity, generate content payload + // else first call of media link descriptor where we only send the default value + descriptor.ContentGeneratedForSave = true; + return this.CreateRequestData(entityDescriptor, requestMessage); + } + else + { + descriptor.ContentGeneratedForSave = true; + LinkDescriptor link = (LinkDescriptor)descriptor; + if ((EntityStates.Added == link.State) || + ((EntityStates.Modified == link.State) && (null != link.Target))) + { + this.CreateRequestData(link, requestMessage); + return true; + } + } + + return false; + } + + /// Set the AsyncWait and invoke the user callback. + /// the request object + protected override void HandleCompleted(PerRequest pereq) + { + if (null != pereq) + { + this.SetCompletedSynchronously(pereq.RequestCompletedSynchronously); + + if (pereq.RequestCompleted) + { + Interlocked.CompareExchange(ref this.perRequest, null, pereq); + if (this.IsBatchRequest) + { // all competing thread must complete this before user callback is invoked + Interlocked.CompareExchange(ref this.batchResponseMessage, pereq.ResponseMessage, null); + pereq.ResponseMessage = null; + } + + pereq.Dispose(); + } + } + + this.HandleCompleted(); + } + + /// handle request.BeginGetResponse with request.EndGetResponse and then copy response stream + /// async result + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + protected override void AsyncEndGetResponse(IAsyncResult asyncResult) + { + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted"); + AsyncStateBag asyncStateBag = asyncResult.AsyncState as AsyncStateBag; + + PerRequest pereq = asyncStateBag == null ? null : asyncStateBag.PerRequest; + + try + { + this.CompleteCheck(pereq, InternalError.InvalidEndGetResponseCompleted); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginGetResponse + + EqualRefCheck(this.perRequest, pereq, InternalError.InvalidEndGetResponse); + ODataRequestMessageWrapper requestMessage = Util.NullCheck(pereq.Request, InternalError.InvalidEndGetResponseRequest); + + // the httpWebResponse is kept for batching, discarded by non-batch + IODataResponseMessage responseMessage = null; + Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse::BeforeEndGetResponse"); + + responseMessage = this.RequestInfo.EndGetResponse(requestMessage, asyncResult); + pereq.ResponseMessage = Util.NullCheck(responseMessage, InternalError.InvalidEndGetResponseResponse); + + if (!this.IsBatchRequest) + { + this.HandleOperationResponse(responseMessage); + this.HandleOperationResponseHeaders((HttpStatusCode)responseMessage.StatusCode, new HeaderCollection(responseMessage)); + } + + Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse_BeforeGetStream"); + Stream httpResponseStream = responseMessage.GetStream(); + pereq.ResponseStream = httpResponseStream; + + if ((null != httpResponseStream) && httpResponseStream.CanRead) + { + if (null == this.buildBatchBuffer) + { + this.buildBatchBuffer = new byte[8000]; + } + + do + { + Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse_BeforeBeginRead"); +#if PORTABLELIB + asyncResult = BaseAsyncResult.InvokeTask(httpResponseStream.ReadAsync, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, new AsyncReadState(pereq)); +#else + asyncResult = InvokeAsync(httpResponseStream.BeginRead, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, new AsyncReadState(pereq)); +#endif + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginRead + } + while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && httpResponseStream.CanRead); + } + else + { + pereq.SetComplete(); + + // BeginGetResponse could fail and callback still invoked + // if pereq.RequestCompletedSynchronously is true, this.FinishCurrentChange() will be + // invoked by the current thread in SaveResult.BeginCreateNextChange(). + // if pereq.RequestCompletedSynchronously is false, we will call this.FinishCurrentChange() here and + // the parent thread will not call this.FinishCurrentChange() in SaveResult.BeginCreateNextChange(). + if (!this.IsCompletedInternally && !pereq.RequestCompletedSynchronously) + { + this.FinishCurrentChange(pereq); + } + } + } + catch (Exception e) + { + if (this.HandleFailure(pereq, e)) + { + throw; + } + } + finally + { + this.HandleCompleted(pereq); + } + } + + /// IODataResponseMessage contain response for the operation. + /// IODataResponseMessage instance. + protected abstract void HandleOperationResponse(IODataResponseMessage responseMessage); + + /// operation with HttpWebResponse + /// status code of the response. + /// response headers. + protected void HandleOperationResponseHeaders(HttpStatusCode statusCode, HeaderCollection headers) + { + Descriptor descriptor = this.ChangedEntries[this.entryIndex]; + + // in the first pass, the http response is packaged into a batch response (which is then processed in second pass). + // in this first pass, (all added entities and first call of modified media link entities) update their edit location + // added entities - so entities that have not sent content yet w/ reference links can inline those reference links in their payload + // media entities - because they can change edit location which is then necessary for second call that includes property content + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; + Debug.Assert(this.streamRequestKind != StreamRequestKind.PostMediaResource || descriptor.State == EntityStates.Modified, "For the POST MR, the entity state must be modified"); + + // For POST and PATCH scenarios + if (descriptor.State == EntityStates.Added || + this.streamRequestKind == StreamRequestKind.PostMediaResource || + !Util.IsFlagSet(this.Options, SaveChangesOptions.ReplaceOnUpdate)) + { + if (WebUtil.SuccessStatusCode(statusCode)) + { + string location; + string odataEntityId; + Uri editLink = null; + headers.TryGetHeader(XmlConstants.HttpResponseLocation, out location); + headers.TryGetHeader(XmlConstants.HttpODataEntityId, out odataEntityId); + + if (location != null) + { + // Verify the location header is an absolute uri + editLink = WebUtil.ValidateLocationHeader(location); + } + else if (descriptor.State == EntityStates.Added || + this.streamRequestKind == StreamRequestKind.PostMediaResource) + { + // For POST scenarios, location header must be specified. + throw Error.NotSupported(Strings.Deserialize_NoLocationHeader); + } + + // Verify the id value if present. Otherwise we should use the location header + // as identity. This was done to avoid breaking change, since in V1/V2, we used + // to do this. + Uri odataId = null; + if (odataEntityId != null) + { + odataId = WebUtil.ValidateIdentityValue(odataEntityId); + if (location == null) + { + throw Error.NotSupported(Strings.Context_BothLocationAndIdMustBeSpecified); + } + } + else + { + // we already verified that the location must be an absolute uri + odataId = UriUtil.CreateUri(location, UriKind.Absolute); + } + + if (null != editLink) + { + this.RequestInfo.EntityTracker.AttachLocation(entityDescriptor.Entity, odataId, editLink); + } + } + } + + if (this.streamRequestKind != StreamRequestKind.None) + { + if (!WebUtil.SuccessStatusCode(statusCode)) + { + // If the request failed and it was the MR request we should not try to send the PUT MLE after it + // for one we don't have the location to send it to (if it was POST MR) + if (this.streamRequestKind == StreamRequestKind.PostMediaResource) + { + // If this was the POST MR it means we tried to add the entity. Now its state is Modified but we need + // to revert back to Added so that user can retry by calling SaveChanges again. + Debug.Assert(descriptor.State == EntityStates.Modified, "Entity state should be set to Modified once we've sent the POST MR"); + descriptor.State = EntityStates.Added; + } + + // Just reset the streamRequestKind flag - that means that we will not try to PUT the MLE and instead skip over + // to the next change (if we are to ignore errors that is) + this.streamRequestKind = StreamRequestKind.None; + + // And we also need to mark it such that we generated the save content (which we did before the POST request in fact) + // to workaround the fact that we use the same descriptor object to track two requests. + descriptor.ContentGeneratedForSave = true; + } + else if (this.streamRequestKind == StreamRequestKind.PostMediaResource) + { + // We just finished a POST MR request and the PUT MLE coming immediately after it will + // need the new etag value from the server to succeed. + string etag; + if (headers.TryGetHeader(XmlConstants.HttpResponseETag, out etag)) + { + entityDescriptor.ETag = etag; + } + + // else is not interesting and we intentionally do nothing. + } + } + } + } + +#if DEBUG + /// + /// Handle operation response + /// + /// descriptor whose response is getting processed. + /// content headers as returned in the response. + /// status code. + protected void HandleOperationResponse(Descriptor descriptor, HeaderCollection contentHeaders, HttpStatusCode statusCode) +#else + /// + /// Handle operation response + /// + /// descriptor whose response is getting processed. + /// content headers as returned in the response. + protected void HandleOperationResponse(Descriptor descriptor, HeaderCollection contentHeaders) +#endif + { + EntityStates streamState = EntityStates.Unchanged; + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; + streamState = entityDescriptor.StreamState; +#if DEBUG + if (entityDescriptor.StreamState == EntityStates.Added) + { + // We do not depend anywhere for the status code to be Created (201). Hence changing the assert from checking for a specific status code + // to just checking for success status code. + Debug.Assert( + WebUtil.SuccessStatusCode(statusCode) && entityDescriptor.State == EntityStates.Modified && entityDescriptor.IsMediaLinkEntry, + "WebUtil.SuccessStatusCode(statusCode) && descriptor.State == EntityStates.Modified && descriptor.IsMediaLinkEntry -- Processing Post MR"); + } + else if (entityDescriptor.StreamState == EntityStates.Modified) + { + // We do not depend anywhere for the status code to be Created (201). Hence changing the assert from checking for a specific status code + // to just checking for success status code. + Debug.Assert( + WebUtil.SuccessStatusCode(statusCode) && entityDescriptor.IsMediaLinkEntry, + "WebUtil.SuccessStatusCode(statusCode) && descriptor.IsMediaLinkEntry -- Processing Put MR"); + } + + // if the entity is added state or modified state with patch requests + if (streamState == EntityStates.Added || descriptor.State == EntityStates.Added || + (descriptor.State == EntityStates.Modified && !Util.IsFlagSet(this.Options, SaveChangesOptions.ReplaceOnUpdate))) + { + string location; + string odataEntityId; + contentHeaders.TryGetHeader(XmlConstants.HttpResponseLocation, out location); + contentHeaders.TryGetHeader(XmlConstants.HttpODataEntityId, out odataEntityId); + + Debug.Assert(location == null || location == entityDescriptor.GetLatestEditLink().AbsoluteUri, "edit link must already be set to location header"); + Debug.Assert((location == null && odataEntityId == null) || (odataEntityId ?? location) == UriUtil.UriToString(entityDescriptor.GetLatestIdentity()), "Identity must already be set"); + } +#endif + } + + if (streamState == EntityStates.Added || descriptor.State == EntityStates.Added) + { + this.HandleResponsePost(descriptor, contentHeaders); + } + else if (streamState == EntityStates.Modified || descriptor.State == EntityStates.Modified) + { + this.HandleResponsePut(descriptor, contentHeaders); + } + else if (descriptor.State == EntityStates.Deleted) + { + this.HandleResponseDelete(descriptor); + } + + // else condition is not interesting here and we intentionally do nothing. + } + + /// + /// Get the materializer to process the response. + /// + /// entity descriptor whose response is getting materialized. + /// information about the response to be materialized. + /// an instance of MaterializeAtom, that can be used to materialize the response. + protected abstract MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo); + + /// cleanup work to do once the batch / savechanges is complete + protected override void CompletedRequest() + { + this.buildBatchBuffer = null; + } + + /// + /// Create the response info instance to be passed to the materializer. + /// + /// entity descriptor whose response is getting handled. + /// instance of the response info class. + protected ResponseInfo CreateResponseInfo(EntityDescriptor entityDescriptor) + { + MergeOption mergeOption = MergeOption.OverwriteChanges; + + // If we are processing a POST MR, we want to materialize the payload to get the metadata for the stream. + // However we must not modify the MLE properties with the server initialized properties. The next request + // will be a Put MLE operation and we will set the server properties with values from the client entity. + if (entityDescriptor.StreamState == EntityStates.Added) + { + mergeOption = MergeOption.PreserveChanges; + Debug.Assert(entityDescriptor.State == EntityStates.Modified, "The MLE state must be Modified."); + } + + return this.RequestInfo.GetDeserializationInfo(mergeOption); + } + + /// + /// enumerate the related Modified/Unchanged links for an added item + /// + /// entity + /// related links + /// + /// During a non-batch SaveChanges, an Added entity can become an Unchanged entity + /// and should be included in the set of related links for the second Added entity. + /// + protected IEnumerable RelatedLinks(EntityDescriptor entityDescriptor) + { + foreach (LinkDescriptor end in this.RequestInfo.EntityTracker.Links) + { + if (end.Source == entityDescriptor.Entity) + { + if (null != end.Target) + { // null TargetResource is equivalent to Deleted + EntityDescriptor target = this.RequestInfo.EntityTracker.GetEntityDescriptor(end.Target); + + // assumption: the source entity started in the Added state + // note: SaveChanges operates with two passes + // a) first send the request and then attach identity and append the result into a batch response (Example: BeginSaveChanges) + // b) process the batch response (shared code with SaveChanges(BatchWithSingleChangeset)) (Example: EndSaveChanges) + // note: SaveResultWasProcessed is set when to the pre-save state when the save result is successfully processed + + // scenario #1 when target entity started in modified or unchanged state + // 1) the link target entity was modified and now implicitly assumed to be unchanged (this is true in second pass) + // 2) or link target entity has not been saved is in the modified or unchanged state (this is true in first pass) + + // scenario #2 when target entity started in added state + // 1) target entity has an identity (true in first pass for non-batch) + // 2) target entity is processed before source to qualify (1) better during the second pass + // 3) the link target has not been saved and is in the added state + // 4) or the link target has been saved and was in the added state + if (Util.IncludeLinkState(target.SaveResultWasProcessed) || ((0 == target.SaveResultWasProcessed) && Util.IncludeLinkState(target.State)) || + ((null != target.Identity) && (target.ChangeOrder < entityDescriptor.ChangeOrder) && + ((0 == target.SaveResultWasProcessed && EntityStates.Added == target.State) || + (EntityStates.Added == target.SaveResultWasProcessed)))) + { + Debug.Assert(entityDescriptor.ChangeOrder < end.ChangeOrder, "saving is out of order"); + yield return end; + } + } + } + } + } + + /// flag results as being processed + /// result descriptor being processed + /// count of related links that were also processed + protected int SaveResultProcessed(Descriptor descriptor) + { + // media links will be processed twice + descriptor.SaveResultWasProcessed = descriptor.State; + + int count = 0; + if (descriptor.DescriptorKind == DescriptorKind.Entity && (EntityStates.Added == descriptor.State)) + { + foreach (LinkDescriptor end in this.RelatedLinks((EntityDescriptor)descriptor)) + { + if (end.ContentGeneratedForSave) + { + Debug.Assert(0 == end.SaveResultWasProcessed, "this link already had a result"); + end.SaveResultWasProcessed = end.State; + count++; + } + } + } + + return count; + } + + /// + /// Generate the link payload. + /// + /// binding + /// An instance of ODataRequestMessage for the link request. + protected ODataRequestMessageWrapper CreateRequest(LinkDescriptor binding) + { + Debug.Assert(null != binding, "null binding"); + if (binding.ContentGeneratedForSave) + { + return null; + } + + EntityDescriptor sourceEntityDescriptor = this.RequestInfo.EntityTracker.GetEntityDescriptor(binding.Source); + EntityDescriptor targetEntityDescriptor = (null != binding.Target) ? this.RequestInfo.EntityTracker.GetEntityDescriptor(binding.Target) : null; + + // We allow the source and target to be in Added state, i.e. without identities, for batch with single changeset. + if (!Util.IsBatchWithSingleChangeset(this.Options)) + { + ValidateLinkDescriptorSourceAndTargetHaveIdentities(binding, sourceEntityDescriptor, targetEntityDescriptor); + } + + Debug.Assert(this.IsBatchRequest || null != sourceEntityDescriptor.GetLatestIdentity(), "missing sourceResource.Identity in non-batch"); + + Uri requestUri = null; + LinkInfo linkInfo = null; + + if (sourceEntityDescriptor.TryGetLinkInfo(binding.SourceProperty, out linkInfo) + && linkInfo.AssociationLink != null) + { + Debug.Assert(null != sourceEntityDescriptor.GetLatestIdentity(), "Source must have an identity in order to have link info"); + + // If there is already an Association link from the payload, use that + requestUri = linkInfo.AssociationLink; + } + else + { + Uri sourceEntityUri; + if (null == sourceEntityDescriptor.GetLatestIdentity()) + { + Debug.Assert(this.IsBatchRequest && Util.IsBatchWithSingleChangeset(this.Options), "Source must have an identity outside of batch with single changeset"); + + // if the source hasn't yet been inserted (because its in batch), then create a uri based on its content-ID + sourceEntityUri = UriUtil.CreateUri("$" + sourceEntityDescriptor.ChangeOrder.ToString(CultureInfo.InvariantCulture), UriKind.Relative); + } + else + { + // otherwise use the edit link of the source + sourceEntityUri = sourceEntityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/); + } + + // get the source property Uri + string sourcePropertyUri = GetSourcePropertyUri(binding, sourceEntityDescriptor); + + // get the convention-based relative uri for the association + Uri conventionalRelativeUri = UriUtil.CreateUri(sourcePropertyUri, UriKind.Relative); + + // add $ref at the end + conventionalRelativeUri = UriUtil.CreateUri(UriUtil.UriToString(conventionalRelativeUri) + UriHelper.FORWARDSLASH + XmlConstants.UriLinkSegment, UriKind.Relative); + + // combine the association uri with the source entity uri + requestUri = UriUtil.CreateUri(sourceEntityUri, conventionalRelativeUri); + } + + // in the case of deleting a link from a collection, the key of the target must be appended + requestUri = AppendTargetEntityKeyIfNeeded(requestUri, binding, targetEntityDescriptor); + + string method = GetLinkHttpMethod(binding); + + HeaderCollection headers = new HeaderCollection(); + + headers.SetRequestVersion(Util.ODataVersion4, this.RequestInfo.MaxProtocolVersionAsVersion); + this.RequestInfo.Format.SetRequestAcceptHeader(headers); + + // if (EntityStates.Deleted || (EntityState.Modifed && null == TargetResource)) + // then the server will fail the batch section if content type exists + if ((EntityStates.Added == binding.State) || (EntityStates.Modified == binding.State && (null != binding.Target))) + { + this.RequestInfo.Format.SetRequestContentTypeForLinks(headers); + } + + return this.CreateRequestMessage(method, requestUri, headers, this.RequestInfo.HttpStack, binding, this.IsBatchRequest ? binding.ChangeOrder.ToString(CultureInfo.InvariantCulture) : null); + } + + /// + /// Create ODataRequestMessage for the given entity. + /// + /// resource + /// An instance of ODataRequestMessage for the given entity. + protected ODataRequestMessageWrapper CreateRequest(EntityDescriptor entityDescriptor) + { + Debug.Assert(null != entityDescriptor, "null entityDescriptor"); + Debug.Assert(entityDescriptor.State == EntityStates.Added || entityDescriptor.State == EntityStates.Deleted || entityDescriptor.State == EntityStates.Modified, "the entity must be in one of the 3 possible states"); + + EntityStates state = entityDescriptor.State; + Uri requestUri = entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/); + + Debug.Assert(null != requestUri, "request uri is null"); + Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri"); + + ClientEdmModel model = this.RequestInfo.Model; + ClientTypeAnnotation clientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); + Version requestVersion = DetermineRequestVersion(clientType); + string httpMethod = this.GetHttpMethod(state, ref requestVersion); + + HeaderCollection headers = new HeaderCollection(); + + // Set the content type + if (EntityStates.Deleted != entityDescriptor.State) + { + this.RequestInfo.Context.Format.SetRequestContentTypeForEntry(headers); + } + + // Set IfMatch (etag) header for update and delete requests + if ((EntityStates.Deleted == state) || (EntityStates.Modified == state)) + { + string etag = entityDescriptor.GetLatestETag(); + if (etag != null) + { + headers.SetHeader(XmlConstants.HttpRequestIfMatch, etag); + } + } + + // Set the prefer header if required + ApplyPreferences(headers, httpMethod, this.RequestInfo.AddAndUpdateResponsePreference, ref requestVersion); + + // Set the request DSV and request MDSV headers + headers.SetRequestVersion(requestVersion, this.RequestInfo.MaxProtocolVersionAsVersion); + + this.RequestInfo.Format.SetRequestAcceptHeader(headers); + return this.CreateRequestMessage(httpMethod, requestUri, headers, this.RequestInfo.HttpStack, entityDescriptor, this.IsBatchRequest ? entityDescriptor.ChangeOrder.ToString(CultureInfo.InvariantCulture) : null); + } + + /// + /// Returns the request message to write the headers and payload into. + /// + /// Http method for the request. + /// Base Uri for the request. + /// Request headers. + /// HttpStack to use. + /// Descriptor for the request, if there is one. + /// an instance of IODataRequestMessage. + protected ODataRequestMessageWrapper CreateTopLevelRequest(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor) + { + BuildingRequestEventArgs args = this.RequestInfo.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, httpStack, descriptor); + return this.RequestInfo.WriteHelper.CreateRequestMessage(args); + } + + /// + /// Figures out value to be written in OData-Version HTTP header for the given entity based on features used in this entity. + /// + /// Entity type for which data service version needs to be determined. + /// Data service version for the given entity and state. + private static Version DetermineRequestVersion(ClientTypeAnnotation clientType) + { + Debug.Assert(clientType != null, "clientType != null"); + Debug.Assert(clientType.IsEntityType, "This method should be called only for entities"); + + // Determine what the version is based on the client type. + Version requestVersion = Util.ODataVersion4; + + WebUtil.RaiseVersion(ref requestVersion, clientType.GetMetadataVersion()); + return requestVersion; + } + + /// Checks whether a WCF Data Service version string can be handled. + /// Version string on the response header; possibly null. + /// The response version parsed into a instance + /// if the version was valid and can be handled, otherwise null. + /// true if the version can be handled; false otherwise. + private static bool CanHandleResponseVersion(string responseVersion, out Version parsedResponseVersion) + { + parsedResponseVersion = null; + + if (!string.IsNullOrEmpty(responseVersion)) + { + KeyValuePair version; + if (!CommonUtil.TryReadVersion(responseVersion, out version)) + { + return false; + } + + if (!Util.SupportedResponseVersions.Contains(version.Key)) + { + return false; + } + + parsedResponseVersion = version.Key; + } + + return true; + } + + /// Handle changeset response. + /// headers of changeset response + private static void HandleResponsePost(LinkDescriptor linkDescriptor) + { + if (!((EntityStates.Added == linkDescriptor.State) || (EntityStates.Modified == linkDescriptor.State && null != linkDescriptor.Target))) + { + Error.ThrowBatchUnexpectedContent(InternalError.LinkNotAddedState); + } + + linkDescriptor.State = EntityStates.Unchanged; + } + + /// + /// Validates that the link descriptor source and target have identities. + /// + /// The binding. + /// The source resource. + /// The target resource. + private static void ValidateLinkDescriptorSourceAndTargetHaveIdentities(LinkDescriptor binding, EntityDescriptor sourceResource, EntityDescriptor targetResource) + { + Debug.Assert(!binding.ContentGeneratedForSave, "already saved link"); + + // In non-batch scenarios, the source should always have an identity + if (null == sourceResource.GetLatestIdentity()) + { + binding.ContentGeneratedForSave = true; + Debug.Assert(EntityStates.Added == sourceResource.State, "expected added state"); + throw Error.InvalidOperation(Strings.Context_LinkResourceInsertFailure, sourceResource.SaveError); + } + + if (null != targetResource && null == targetResource.GetLatestIdentity()) + { + binding.ContentGeneratedForSave = true; + Debug.Assert(EntityStates.Added == targetResource.State, "expected added state"); + throw Error.InvalidOperation(Strings.Context_LinkResourceInsertFailure, targetResource.SaveError); + } + } + + /// + /// Serialize supported data service versions to a string that will be used in the exception message. + /// The string contains versions in single quotes separated by comma followed by a single space (e.g. "'1.0', '2.0'"). + /// + /// Supported data service versions in single quotes separated by comma followed by a space. + private static string SerializeSupportedVersions() + { + Debug.Assert(Util.SupportedResponseVersions.Length > 0, "At least one supported version must exist."); + + StringBuilder supportedVersions = new StringBuilder("'").Append(Util.SupportedResponseVersions[0].ToString()); + for (int versionIdx = 1; versionIdx < Util.SupportedResponseVersions.Length; versionIdx++) + { + supportedVersions.Append("', '"); + supportedVersions.Append(Util.SupportedResponseVersions[versionIdx].ToString()); + } + + supportedVersions.Append("'"); + + return supportedVersions.ToString(); + } + + /// + /// Appends the target entity key to the uri if the binding is in the deleted state and the property is a collection. + /// + /// The link URI so far. + /// The binding. + /// The target's entity descriptor. + /// The original link uri or one with the target entity key appended. + private static Uri AppendTargetEntityKeyIfNeeded(Uri linkUri, LinkDescriptor binding, EntityDescriptor targetResource) + { + // To delete from a collection, we need to append the key. + // For example: if the navigation property name is "Purchases" and the resource type is Order with key '1', then this method will generate 'baseuri/Purchases(1)' + if (!binding.IsSourcePropertyCollection || EntityStates.Deleted != binding.State) + { + return linkUri; + } + + Debug.Assert(targetResource != null, "targetResource != null"); + StringBuilder builder = new StringBuilder(); + builder.Append(UriUtil.UriToString(linkUri)); + builder.Append(UriHelper.QUESTIONMARK + XmlConstants.HttpQueryStringId + UriHelper.EQUALSSIGN + targetResource.Identity); + return UriUtil.CreateUri(builder.ToString(), UriKind.RelativeOrAbsolute); + } + + /// + /// Generate a request for the given entity. + /// + /// Instance of EntityDescriptor. + /// Instance of IODataRequestMessage to be used to generate the payload. + /// True if the payload was generated, otherwise false. + private bool CreateRequestData(EntityDescriptor entityDescriptor, ODataRequestMessageWrapper requestMessage) + { + Debug.Assert(null != entityDescriptor, "null entityDescriptor"); + bool generateRequestPayload = false; + switch (entityDescriptor.State) + { + case EntityStates.Deleted: + break; + case EntityStates.Modified: + case EntityStates.Added: + generateRequestPayload = true; + break; + default: + Error.ThrowInternalError(InternalError.UnvalidatedEntityState); + break; + } + + if (generateRequestPayload) + { + Debug.Assert(this.SerializerInstance != null, "this.SerializerInstance != null"); + this.SerializerInstance.WriteEntry(entityDescriptor, this.RelatedLinks(entityDescriptor), requestMessage); + } + + return generateRequestPayload; + } + + /// + /// Generate a request for the given link. + /// + /// Instance of LinkDescriptor. + /// Instance of IODataRequestMessage to be used to generate the payload. + private void CreateRequestData(LinkDescriptor binding, ODataRequestMessageWrapper requestMessage) + { + Debug.Assert( + (binding.State == EntityStates.Added) || + (binding.State == EntityStates.Modified && null != binding.Target), + "This method must be called only when a binding is added or put"); +#if DEBUG + Debug.Assert(!Util.IsBatchWithSingleChangeset(this.Options) || this.IsBatchRequest, "If this.Options.IsBatchWithSingleChangeset() is true, this.IsBatchRequest must also be true."); + this.SerializerInstance.WriteEntityReferenceLink(binding, requestMessage, Util.IsBatchWithSingleChangeset(this.Options)); +#else + this.SerializerInstance.WriteEntityReferenceLink(binding, requestMessage); +#endif + } + + /// Handle changeset response. + /// descriptor whose response is getting handled. + /// response headers. + private void HandleResponsePost(Descriptor descriptor, HeaderCollection contentHeaders) + { + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + string etag; + contentHeaders.TryGetHeader(XmlConstants.HttpResponseETag, out etag); + this.HandleResponsePost((EntityDescriptor)descriptor, etag); + } + else + { + HandleResponsePost((LinkDescriptor)descriptor); + } + } + + /// Handle changeset response for the given entity descriptor. + /// entity descriptor whose response is getting handled. + /// ETag header value from the server response (or null if no etag or if there is an actual response) + private void HandleResponsePost(EntityDescriptor entityDescriptor, string etag) + { + try + { + if (EntityStates.Added != entityDescriptor.State && EntityStates.Added != entityDescriptor.StreamState) + { + Error.ThrowBatchUnexpectedContent(InternalError.EntityNotAddedState); + } + + if (this.ProcessResponsePayload) + { + this.MaterializeResponse(entityDescriptor, this.CreateResponseInfo(entityDescriptor), etag); + } + else + { + entityDescriptor.ETag = etag; + entityDescriptor.State = EntityStates.Unchanged; + entityDescriptor.PropertiesToSerialize.Clear(); + } + + if (entityDescriptor.StreamState != EntityStates.Added) + { + // For MR - entityDescriptor.State is merged, we don't need to do link folding since MR will never fold links. + foreach (LinkDescriptor end in this.RelatedLinks(entityDescriptor)) + { + Debug.Assert(0 != end.SaveResultWasProcessed, "link should have been saved with the entity"); + + // Since we allow link folding on collection properties also, we need to check if the link + // was in added state also, and make sure we put that link in unchanged state. + if (Util.IncludeLinkState(end.SaveResultWasProcessed) || end.SaveResultWasProcessed == EntityStates.Added) + { + HandleResponsePost(end); + } + } + } + } + finally + { + if (entityDescriptor.StreamState == EntityStates.Added) + { + // The materializer will always set the entity state to Unchanged. We just processed Post MR, we + // need to restore the entity state to Modified to process the Put MLE. + Debug.Assert(entityDescriptor.State == EntityStates.Unchanged, "The materializer should always set the entity state to Unchanged."); + entityDescriptor.State = EntityStates.Modified; + + // Need to clear the stream state so the next iteration we will always process the Put MLE operation. + entityDescriptor.StreamState = EntityStates.Unchanged; + } + } + } + + /// + /// Handle the PUT response sent by the server + /// + /// descriptor, whose response is getting handled. + /// response headers. + private void HandleResponsePut(Descriptor descriptor, HeaderCollection responseHeaders) + { + Debug.Assert(descriptor != null, "descriptor != null"); + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + string etag; + responseHeaders.TryGetHeader(XmlConstants.HttpResponseETag, out etag); + EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; + + // Only process the response if the resource is an entity resource and process update response is set to true + if (this.ProcessResponsePayload) + { + this.MaterializeResponse(entityDescriptor, this.CreateResponseInfo(entityDescriptor), etag); + } + else + { + if (EntityStates.Modified != entityDescriptor.State && EntityStates.Modified != entityDescriptor.StreamState) + { + Error.ThrowBatchUnexpectedContent(InternalError.EntryNotModified); + } + + // We MUST process the MR before the MLE since we always issue the requests in that order. + if (entityDescriptor.StreamState == EntityStates.Modified) + { + entityDescriptor.StreamETag = etag; + entityDescriptor.StreamState = EntityStates.Unchanged; + } + else + { + Debug.Assert(entityDescriptor.State == EntityStates.Modified, "descriptor.State == EntityStates.Modified"); + entityDescriptor.ETag = etag; + entityDescriptor.State = EntityStates.Unchanged; + entityDescriptor.PropertiesToSerialize.Clear(); + } + } + } + else if (descriptor.DescriptorKind == DescriptorKind.Link) + { + if ((EntityStates.Added == descriptor.State) || (EntityStates.Modified == descriptor.State)) + { + descriptor.State = EntityStates.Unchanged; + } + else if (EntityStates.Detached != descriptor.State) + { // this link may have been previously detached by a detaching entity + Error.ThrowBatchUnexpectedContent(InternalError.LinkBadState); + } + } + else + { + Debug.Assert(descriptor.DescriptorKind == DescriptorKind.NamedStream, "it must be named stream"); + Debug.Assert(descriptor.State == EntityStates.Modified, "named stream must only be in modified state"); + descriptor.State = EntityStates.Unchanged; + + StreamDescriptor streamDescriptor = (StreamDescriptor)descriptor; + + // The named stream has been updated, so the old ETag value is stale. Replace + // it with the new value or clear it if no value was specified. + string etag; + responseHeaders.TryGetHeader(XmlConstants.HttpResponseETag, out etag); + streamDescriptor.ETag = etag; + } + } + + /// Handle response to deleted entity. + /// deleted entity + private void HandleResponseDelete(Descriptor descriptor) + { + if (EntityStates.Deleted != descriptor.State) + { + Error.ThrowBatchUnexpectedContent(InternalError.EntityNotDeleted); + } + + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + EntityDescriptor resource = (EntityDescriptor)descriptor; + this.RequestInfo.EntityTracker.DetachResource(resource); + } + else + { + this.RequestInfo.EntityTracker.DetachExistingLink((LinkDescriptor)descriptor, false); + } + } + +#if PORTABLELIB + /// Handle responseStream.ReadAsync and complete the read operation. + /// Task that has completed. + /// State associated with the Task. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + private void AsyncEndRead(Task task, object asyncState) +#else + + /// handle responseStream.BeginRead with responseStream.EndRead + /// async result + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + private void AsyncEndRead(IAsyncResult asyncResult) +#endif + { +#if PORTABLELIB + IAsyncResult asyncResult = (IAsyncResult)task; +#endif + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted"); +#if PORTABLELIB + AsyncReadState state = (AsyncReadState)asyncState; +#else + AsyncReadState state = (AsyncReadState)asyncResult.AsyncState; +#endif + PerRequest pereq = state.Pereq; + int count = 0; + try + { + this.CompleteCheck(pereq, InternalError.InvalidEndReadCompleted); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginRead + + EqualRefCheck(this.perRequest, pereq, InternalError.InvalidEndRead); + Stream httpResponseStream = Util.NullCheck(pereq.ResponseStream, InternalError.InvalidEndReadStream); + + Util.DebugInjectFault("SaveAsyncResult::AsyncEndRead_BeforeEndRead"); +#if PORTABLELIB + count = ((Task)task).Result; +#else + count = httpResponseStream.EndRead(asyncResult); +#endif + if (0 < count) + { + Stream outputResponse = Util.NullCheck(this.ResponseStream, InternalError.InvalidEndReadCopy); + outputResponse.Write(this.buildBatchBuffer, 0, count); + state.TotalByteCopied += count; + + if (!asyncResult.CompletedSynchronously && httpResponseStream.CanRead) + { + // if CompletedSynchronously then caller will call and we reduce risk of stack overflow + do + { +#if PORTABLELIB + asyncResult = BaseAsyncResult.InvokeTask(httpResponseStream.ReadAsync, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, new AsyncReadState(pereq)); +#else + asyncResult = InvokeAsync(httpResponseStream.BeginRead, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, state); +#endif + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginRead + } + while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && httpResponseStream.CanRead); + } + } + else + { + pereq.SetComplete(); + + // BeginRead could fail and callback still invoked + // if pereq.RequestCompletedSynchronously is true, this.FinishCurrentChange() will be + // invoked by the current thread in SaveResult.BeginCreateNextChange(). + // if pereq.RequestCompletedSynchronously is false, we will call this.FinishCurrentChange() here and + // the parent thread will not call this.FinishCurrentChange() in SaveResult.BeginCreateNextChange(). + if (!this.IsCompletedInternally && !pereq.RequestCompletedSynchronously) + { + this.FinishCurrentChange(pereq); + } + } + } + catch (Exception e) + { + if (this.HandleFailure(pereq, e)) + { + throw; + } + } + finally + { + this.HandleCompleted(pereq); + } + } + + /// + /// Materialize the response payload. + /// + /// entity descriptor whose response is getting materialized. + /// information about the response to be materialized. + /// etag value, if specified in the response header. + private void MaterializeResponse(EntityDescriptor entityDescriptor, ResponseInfo responseInfo, string etag) + { + using (MaterializeAtom materializer = this.GetMaterializer(entityDescriptor, responseInfo)) + { + materializer.SetInsertingObject(entityDescriptor.Entity); + + object materializedEntity = null; + foreach (object x in materializer) + { + Debug.Assert(materializedEntity == null, "entity == null"); + if (materializedEntity != null) + { + Error.ThrowInternalError(InternalError.MaterializerReturningMoreThanOneEntity); + } + + materializedEntity = x; + } + + Debug.Assert(null != entityDescriptor.GetLatestIdentity(), "updated inserted should always gain an identity"); + Debug.Assert(materializedEntity == entityDescriptor.Entity, "x == entityDescriptor.Entity, should have same object generated by response"); + Debug.Assert(EntityStates.Unchanged == entityDescriptor.State, "should have moved out of insert"); + Debug.Assert(this.RequestInfo.EntityTracker.TryGetEntityDescriptor(entityDescriptor.GetLatestIdentity()) != null, "should have identity tracked"); + + // If there was no etag specified in the payload, then we need to set the etag from the header + if (entityDescriptor.GetLatestETag() == null) + { + entityDescriptor.ETag = etag; + } + } + } + + /// + /// Get the source property Uri for the link URL + /// + /// Link descriptor object of the binding + /// entity descriptor for source + /// source property Uri string + private string GetSourcePropertyUri(LinkDescriptor binding, EntityDescriptor sourceEntityDescriptor) + { + Debug.Assert(binding != null, "binding != null"); + Debug.Assert(sourceEntityDescriptor != null, "sourceEntityDescriptor != null"); + + if (string.IsNullOrEmpty(binding.SourceProperty)) + { + return null; + } + + string sourcePropertyUri = binding.SourceProperty; + + // Add type segment in the link URL for the derived entity type on which a navigation property is defined. + // e.g. cxt.Attachto("",) + // cxt.AddLink(, "" ) + // Get entity type name from model (here service model instead of client model should be used) + string entityTypeFullName = this.RequestInfo.TypeResolver.ResolveServiceEntityTypeFullName(binding.Source.GetType()); + if (string.IsNullOrEmpty(entityTypeFullName)) + { + return sourcePropertyUri; + } + + // Get the type of entityset from service model. + string sourceEntitySetTypeName = null; + if (!string.IsNullOrEmpty(sourceEntityDescriptor.EntitySetName) && this.RequestInfo.TypeResolver.TryResolveEntitySetBaseTypeName(sourceEntityDescriptor.EntitySetName, out sourceEntitySetTypeName)) + { + // Check whether the entity type and the entity set type are matched. if not matched, set the dervied entity type name as a key segment in the URL. + if (!string.IsNullOrEmpty(sourceEntitySetTypeName) && !string.Equals(entityTypeFullName, sourceEntitySetTypeName, StringComparison.OrdinalIgnoreCase)) + { + sourcePropertyUri = entityTypeFullName + UriHelper.FORWARDSLASH + sourcePropertyUri; + } + } + + return sourcePropertyUri; + } + + /// + /// Async read state + /// + private struct AsyncReadState + { + /// PerRequest class which tracks the request and response stream + internal readonly PerRequest Pereq; + + /// total number of byte copied. + private int totalByteCopied; + + /// + /// constructor + /// + /// Perrequest class + internal AsyncReadState(PerRequest pereq) + { + this.Pereq = pereq; + this.totalByteCopied = 0; + } + + /// + /// Returns the total number of byte copied till now. + /// + internal int TotalByteCopied + { + get { return this.totalByteCopied; } + set { this.totalByteCopied = value; } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/BatchSaveResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BatchSaveResult.cs new file mode 100644 index 0000000..a32a3fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BatchSaveResult.cs @@ -0,0 +1,972 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using Microsoft.OData; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Handles the batch requests and responses (both sync and async) + /// + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "The response stream is disposed by the message reader we create over it which we dispose inside the enumerator.")] + internal class BatchSaveResult : BaseSaveResult + { + #region Private Fields + + /// The size of the copy buffer to create. + private const int StreamCopyBufferSize = 4000; + + /// Array of queries being executed + private readonly DataServiceRequest[] Queries; + + /// Response stream containing the entire batch response. + private Stream responseStream; + + /// Instance of ODataBatchWriter used to write current batch request. + private ODataBatchWriter batchWriter; + + /// The message reader used to read the batch response. + private ODataMessageReader batchMessageReader; + + /// Object representing the current operation response. + private CurrentOperationResponse currentOperationResponse; + + /// Buffer used for caching operation response body streams. + private byte[] streamCopyBuffer; + + #endregion + + /// + /// constructor for BatchSaveResult + /// + /// context + /// method + /// queries + /// options + /// user callback + /// user state object + internal BatchSaveResult(DataServiceContext context, string method, DataServiceRequest[] queries, SaveChangesOptions options, AsyncCallback callback, object state) + : base(context, method, queries, options, callback, state) + { + Debug.Assert(Util.IsBatch(options), "the options must have batch flag set"); + this.Queries = queries; + this.streamCopyBuffer = new byte[StreamCopyBufferSize]; + } + + /// returns true since this class handles batch requests. + internal override bool IsBatchRequest + { + get { return true; } + } + + /// + /// In async case, this is a memory stream used to cache responses, as we are reading async from the underlying http web response stream. + /// In non-async case, this is the actual response stream, as returned by the http request. + /// + /// + /// This is the stream which holds the entire batch response, when we process any given part those streams are enumerated through + /// a different field (currentOperationResponseContentStream). + /// + protected override Stream ResponseStream + { + get { return this.responseStream; } + } + + /// + /// returns true if the response payload needs to be processed. + /// + protected override bool ProcessResponsePayload + { + get + { + Debug.Assert(this.currentOperationResponse != null, "There must be an active operation response for this property to work correctly."); + return !this.currentOperationResponse.HasEmptyContent; + } + } + + /// initial the async batch save changeset + internal void BatchBeginRequest() + { + PerRequest pereq = null; + try + { + ODataRequestMessageWrapper batchRequestMessage = this.GenerateBatchRequest(); + this.Abortable = batchRequestMessage; + + if (batchRequestMessage != null) + { + batchRequestMessage.SetContentLengthHeader(); + this.perRequest = pereq = new PerRequest(); + pereq.Request = batchRequestMessage; + pereq.RequestContentStream = batchRequestMessage.CachedRequestStream; + + AsyncStateBag asyncStateBag = new AsyncStateBag(pereq); + + this.responseStream = new MemoryStream(); + IAsyncResult asyncResult = BaseAsyncResult.InvokeAsync(batchRequestMessage.BeginGetRequestStream, this.AsyncEndGetRequestStream, asyncStateBag); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + } + else + { + Debug.Assert(this.CompletedSynchronously, "completedSynchronously"); + Debug.Assert(this.IsCompletedInternally, "completed"); + } + } + catch (Exception e) + { + this.HandleFailure(pereq, e); + throw; // to user on BeginSaveChangeSet, will still invoke Callback + } + finally + { + this.HandleCompleted(pereq); // will invoke user callback + } + + Debug.Assert((this.CompletedSynchronously && this.IsCompleted) || !this.CompletedSynchronously, "sync without complete"); + } + +#if !PORTABLELIB // Synchronous methods not available + /// + /// Synchronous batch request + /// + internal void BatchRequest() + { + ODataRequestMessageWrapper batchRequestMessage = this.GenerateBatchRequest(); + + if (batchRequestMessage != null) + { + batchRequestMessage.SetRequestStream(batchRequestMessage.CachedRequestStream); + + try + { + this.batchResponseMessage = this.RequestInfo.GetSyncronousResponse(batchRequestMessage, false); + } + catch (DataServiceTransportException ex) + { + InvalidOperationException exception = WebUtil.GetHttpWebResponse(ex, ref this.batchResponseMessage); + + // For non-async batch requests we rethrow the WebException. This is shipped behavior. + throw exception; + } + finally + { + if (this.batchResponseMessage != null) + { + // For non-async batch requests we call the test hook to get the response stream but we cannot consume it + // because we rethrow what we caught and the customer need to be able to read the response stream from the WebException. + // Note that on the async batch code path we do consume the response stream and throw a DataServiceRequestException. + this.responseStream = this.batchResponseMessage.GetStream(); + } + } + } + } +#endif + + /// Read and store response data for the current change + /// The completed per request object + /// This is called only from the async code paths, when the response to the batch has been read fully. + protected override void FinishCurrentChange(PerRequest pereq) + { + base.FinishCurrentChange(pereq); + + // This resets the position in the buffered response stream to the beginning + // so that we can start reading the response. + // In this case the ResponseStream is always a MemoryStream since we cache the async response. + this.ResponseStream.Position = 0; + this.perRequest = null; + this.SetCompleted(); + } + + /// IODataResponseMessage contain response for the operation. + /// IODataResponseMessage instance. + protected override void HandleOperationResponse(IODataResponseMessage responseMessage) + { + Debug.Assert(false, "This method should never be called for batch scenarios"); + Error.ThrowInternalError(InternalError.InvalidHandleOperationResponse); + } + + /// + /// Handle response. + /// + /// an instance of the DataServiceResponse, containing individual operation responses for this batch request. + protected override DataServiceResponse HandleResponse() + { + Debug.Assert(this.currentOperationResponse == null, "Batch response processing is already in-flight, we shouldn't get here now."); + + // This will process the responses and throw if failure was detected + if (this.ResponseStream != null) + { + return this.HandleBatchResponse(); + } + + return new DataServiceResponse(null, (int)WebExceptionStatus.Success, new List(0), true /*batchResponse*/); + } + + /// + /// Get the materializer to process the response. + /// + /// entity descriptor whose response is getting materialized. + /// information about the response to be materialized. + /// an instance of MaterializeAtom, that can be used to materialize the response. + /// + /// This can only be called from inside the HandleBatchResponse or during enumeration of the responses. + /// This is used when processing responses for update operations. + /// + protected override MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo) + { + // check if the batch stream is empty or not + Debug.Assert(this.currentOperationResponse != null, "There must be an active operation response for this method to work correctly."); + Debug.Assert(!this.currentOperationResponse.HasEmptyContent, "We should not get here if the response is empty."); + + // Since this is used for processing responses to update operations there are no projections to apply. + QueryComponents queryComponents = new QueryComponents( + /*uri*/ null, + Util.ODataVersionEmpty, + entityDescriptor.Entity.GetType(), + /*projection*/ null, + /*normalizerRewrites*/ null); + return new MaterializeAtom( + responseInfo, + queryComponents, + /*projectionPlan*/ null, + this.currentOperationResponse.CreateResponseMessage(), + ODataPayloadKind.Resource); + } + + /// + /// Returns the request message to write the headers and payload into. + /// + /// Http method for the request. + /// Base Uri for the request. + /// Request headers. + /// HttpStack to use. + /// Descriptor for the request, if there is one. + /// Content-ID header that could be used in batch request. + /// an instance of IODataRequestMessage. + protected override ODataRequestMessageWrapper CreateRequestMessage(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor, string contentId) + { + BuildingRequestEventArgs args = this.RequestInfo.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, this.RequestInfo.HttpStack, descriptor); + return ODataRequestMessageWrapper.CreateBatchPartRequestMessage(this.batchWriter, args, this.RequestInfo, contentId); + } + + /// + /// Creates the type of the multi part MIME content. + /// + /// A multipart mime header with a generated batch boundary + private static string CreateMultiPartMimeContentType() + { + return string.Format(CultureInfo.InvariantCulture, "{0}; {1}={2}_{3}", XmlConstants.MimeMultiPartMixed, XmlConstants.HttpMultipartBoundary, XmlConstants.HttpMultipartBoundaryBatch, Guid.NewGuid()); + } + + /// + /// Creates a ODataRequestMessage for batch request. + /// + /// Returns an instance of ODataRequestMessage for the batch request. + private ODataRequestMessageWrapper CreateBatchRequest() + { + Uri requestUri = UriUtil.CreateUri(this.RequestInfo.BaseUriResolver.GetBaseUriWithSlash(), UriUtil.CreateUri("$batch", UriKind.Relative)); + HeaderCollection headers = new HeaderCollection(); + headers.SetRequestVersion(Util.ODataVersion4, this.RequestInfo.MaxProtocolVersionAsVersion); + headers.SetHeader(XmlConstants.HttpContentType, CreateMultiPartMimeContentType()); + this.RequestInfo.Format.SetRequestAcceptHeaderForBatch(headers); + + return this.CreateTopLevelRequest(XmlConstants.HttpMethodPost, requestUri, headers, this.RequestInfo.HttpStack, null /*descriptor*/); + } + + /// + /// Generate the batch request for all changes to save. + /// + /// Returns the instance of ODataRequestMessage containing all the headers and payload for the batch request. + private ODataRequestMessageWrapper GenerateBatchRequest() + { + if (this.ChangedEntries.Count == 0 && this.Queries == null) + { + this.SetCompleted(); + return null; + } + + ODataRequestMessageWrapper batchRequestMessage = this.CreateBatchRequest(); + + // we need to fire request after the headers have been written, but before we write the payload + batchRequestMessage.FireSendingRequest2(null); + + using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(batchRequestMessage, this.RequestInfo, false /*isParameterPayload*/)) + { + this.batchWriter = messageWriter.CreateODataBatchWriter(); + this.batchWriter.WriteStartBatch(); + + if (this.Queries != null) + { + foreach (DataServiceRequest query in this.Queries) + { + QueryComponents queryComponents = query.QueryComponents(this.RequestInfo.Model); + Uri requestUri = this.RequestInfo.BaseUriResolver.GetOrCreateAbsoluteUri(queryComponents.Uri); + + Debug.Assert(requestUri != null, "request uri is null"); + Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri"); + + HeaderCollection headers = new HeaderCollection(); + + headers.SetRequestVersion(queryComponents.Version, this.RequestInfo.MaxProtocolVersionAsVersion); + + this.RequestInfo.Format.SetRequestAcceptHeaderForQuery(headers, queryComponents); + + ODataRequestMessageWrapper batchOperationRequestMessage = this.CreateRequestMessage(XmlConstants.HttpMethodGet, requestUri, headers, this.RequestInfo.HttpStack, null /*descriptor*/, null /*contentId*/); + + batchOperationRequestMessage.FireSendingEventHandlers(null /*descriptor*/); + } + } + else if (0 < this.ChangedEntries.Count) + { + if (Util.IsBatchWithSingleChangeset(this.Options)) + { + this.batchWriter.WriteStartChangeset(); + } + + var model = this.RequestInfo.Model; + + for (int i = 0; i < this.ChangedEntries.Count; ++i) + { + if (Util.IsBatchWithIndependentOperations(this.Options)) + { + this.batchWriter.WriteStartChangeset(); + } + + Descriptor descriptor = this.ChangedEntries[i]; + if (descriptor.ContentGeneratedForSave) + { + continue; + } + + EntityDescriptor entityDescriptor = descriptor as EntityDescriptor; + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + if (entityDescriptor.State == EntityStates.Added) + { + // We don't support adding MLE/MR in batch mode + ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); + if (type.IsMediaLinkEntry || entityDescriptor.IsMediaLinkEntry) + { + throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink); + } + } + else if (entityDescriptor.State == EntityStates.Unchanged || entityDescriptor.State == EntityStates.Modified) + { + // We don't support PUT for the MR in batch mode + // It's OK to PUT the MLE alone inside a batch mode though + if (entityDescriptor.SaveStream != null) + { + throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink); + } + } + } + else if (descriptor.DescriptorKind == DescriptorKind.NamedStream) + { + // Similar to MR, we do not support adding named streams in batch mode. + throw Error.NotSupported(Strings.Context_BatchNotSupportedForNamedStreams); + } + + ODataRequestMessageWrapper operationRequestMessage; + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + operationRequestMessage = this.CreateRequest(entityDescriptor); + } + else + { + operationRequestMessage = this.CreateRequest((LinkDescriptor)descriptor); + } + + // we need to fire request after the headers have been written, but before we write the payload + operationRequestMessage.FireSendingRequest2(descriptor); + + this.CreateChangeData(i, operationRequestMessage); + + if (Util.IsBatchWithIndependentOperations(this.Options)) + { + this.batchWriter.WriteEndChangeset(); + } + } + + if (Util.IsBatchWithSingleChangeset(this.Options)) + { + this.batchWriter.WriteEndChangeset(); + } + } + + this.batchWriter.WriteEndBatch(); + this.batchWriter.Flush(); + } + + Debug.Assert(this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generated content for all entities/links"); + return batchRequestMessage; + } + + /// + /// process the batch response + /// + /// an instance of the DataServiceResponse, containing individual operation responses for this batch request. + private DataServiceResponse HandleBatchResponse() + { + bool batchMessageReaderOwned = true; + + try + { + if ((this.batchResponseMessage == null) || (this.batchResponseMessage.StatusCode == (int)HttpStatusCode.NoContent)) + { // we always expect a response to our batch POST request + throw Error.InvalidOperation(Strings.Batch_ExpectedResponse(1)); + } + + Func getResponseStream = () => this.ResponseStream; + + // We are not going to use the responseVersion returned from this call, as the $batch request itself doesn't apply versioning + // of the responses on the root level. The responses are versioned on the part level. (Note that the version on the $batch level + // is actually used to version the batch itself, but we for now we only recognize a single version so to keep it backward compatible + // we don't check this here. Also note that the HandleResponse method will verify that we can support the version, that is it's + // lower than the highest version we understand). + Version responseVersion; + BaseSaveResult.HandleResponse( + this.RequestInfo, + (HttpStatusCode)this.batchResponseMessage.StatusCode, // statusCode + this.batchResponseMessage.GetHeader(XmlConstants.HttpODataVersion), // responseVersion + getResponseStream, // getResponseStream + true, // throwOnFailure + out responseVersion); + + if (this.ResponseStream == null) + { + Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream); + } + + // Create the message and the message reader. + this.batchResponseMessage = new HttpWebResponseMessage(new HeaderCollection(this.batchResponseMessage), this.batchResponseMessage.StatusCode, getResponseStream); + ODataMessageReaderSettings messageReaderSettings = this.RequestInfo.GetDeserializationInfo(/*mergeOption*/ null).ReadHelper.CreateSettings(); + + // No need to pass in any model to the batch reader. + this.batchMessageReader = new ODataMessageReader(this.batchResponseMessage, messageReaderSettings); + ODataBatchReader batchReader; + try + { + batchReader = this.batchMessageReader.CreateODataBatchReader(); + } + catch (ODataContentTypeException contentTypeException) + { + string mime; + Encoding encoding; + Exception inner = contentTypeException; + ContentTypeUtil.ReadContentType(this.batchResponseMessage.GetHeader(XmlConstants.HttpContentType), out mime, out encoding); + if (String.Equals(XmlConstants.MimeTextPlain, mime, StringComparison.Ordinal)) + { + inner = GetResponseText( + this.batchResponseMessage.GetStream, + (HttpStatusCode)this.batchResponseMessage.StatusCode); + } + + throw Error.InvalidOperation(Strings.Batch_ExpectedContentType(this.batchResponseMessage.GetHeader(XmlConstants.HttpContentType)), inner); + } + + DataServiceResponse response = this.HandleBatchResponseInternal(batchReader); + + // In case of successful processing of at least the beginning of the batch, the message reader is owned by the returned response + // (or rather by the IEnumerable of operation responses inside it). + // It will be disposed once the operation responses are enumerated (since the IEnumerator should be disposed once used). + // In that case we must NOT dispose it here, since that enumeration can exist long after we return from this method. + batchMessageReaderOwned = false; + + return response; + } + catch (DataServiceRequestException) + { + throw; + } + catch (InvalidOperationException ex) + { + HeaderCollection headers = new HeaderCollection(this.batchResponseMessage); + int statusCode = this.batchResponseMessage == null ? (int)HttpStatusCode.InternalServerError : (int)this.batchResponseMessage.StatusCode; + DataServiceResponse response = new DataServiceResponse(headers, statusCode, new OperationResponse[0], this.IsBatchRequest); + throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, ex, response); + } + finally + { + if (batchMessageReaderOwned) + { + Util.Dispose(ref this.batchMessageReader); + } + } + } + + /// + /// process the batch response + /// + /// The batch reader to use for reading the batch response. + /// an instance of the DataServiceResponse, containing individual operation responses for this batch request. + /// + /// The message reader for the entire batch response is stored in the this.batchMessageReader. + /// The message reader is disposable, but this method should not dispose it itself. It will be either disposed by the caller (in case of exception) + /// or the ownership will be passed to the returned response object (in case of success). + /// In could also be diposed indirectly by this method when it enumerates through the responses. + /// + private DataServiceResponse HandleBatchResponseInternal(ODataBatchReader batchReader) + { + Debug.Assert(this.batchMessageReader != null, "this.batchMessageReader != null"); + Debug.Assert(batchReader != null, "batchReader != null"); + + DataServiceResponse response; + HeaderCollection headers = new HeaderCollection(this.batchResponseMessage); + + IEnumerable responses = this.HandleBatchResponse(batchReader); + if (this.Queries != null) + { + // ExecuteBatch, EndExecuteBatch + response = new DataServiceResponse( + headers, + (int)this.batchResponseMessage.StatusCode, + responses, + true /*batchResponse*/); + } + else + { + List operationResponses = new List(); + response = new DataServiceResponse(headers, (int)this.batchResponseMessage.StatusCode, operationResponses, true /*batchResponse*/); + Exception exception = null; + + // SaveChanges, EndSaveChanges + // enumerate the entire response + foreach (ChangeOperationResponse changeOperationResponse in responses) + { + operationResponses.Add(changeOperationResponse); + if (Util.IsBatchWithSingleChangeset(this.Options) && exception == null && changeOperationResponse.Error != null) + { + exception = changeOperationResponse.Error; + } + + // Note that this will dispose the enumerator and this release the batch message reader which is owned + // by the enumerable of responses by now. + } + + // Note that if we encounter any error in a batch request with a single changeset, + // we throw here since all change operations in the changeset are rolled back on the server. + // If we encounter any error in a batch request with independent operations, we don't want to throw + // since some of the operations might succeed. + // Users need to inspect each OperationResponse to get the exception information from the failed operations. + if (exception != null) + { + throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, exception, response); + } + } + + return response; + } + + /// + /// process the batch response + /// + /// The batch reader to use for reading the batch response. + /// enumerable of QueryResponse or null + /// + /// The batch message reader for the entire batch response is stored in this.batchMessageReader. + /// Note that this method takes over the ownership of this reader and must Dispose it if it successfully returns. + /// + private IEnumerable HandleBatchResponse(ODataBatchReader batchReader) + { + try + { + if (this.batchMessageReader == null) + { + // The enumerable returned by this method can be enumerated multiple times. + // In that case it looks like if the method is called multiple times. + // This didn't fail in previous versions, it simply returned no results, so we need to do the same. + yield break; + } + + Debug.Assert(batchReader != null, "batchReader != null"); + + bool changesetFound = false; + bool insideChangeset = false; + int queryCount = 0; + int operationCount = 0; + this.entryIndex = 0; + while (batchReader.Read()) + { + switch (batchReader.State) + { + #region ChangesetStart + case ODataBatchReaderState.ChangesetStart: + if ((Util.IsBatchWithSingleChangeset(this.Options) && changesetFound) || (operationCount != 0)) + { + // Throw if we encounter multiple changesets when running in batch with single changeset mode + // or if we encounter operations outside of a changeset. + Error.ThrowBatchUnexpectedContent(InternalError.UnexpectedBeginChangeSet); + } + + insideChangeset = true; + break; + #endregion + + #region ChangesetEnd + case ODataBatchReaderState.ChangesetEnd: + changesetFound = true; + operationCount = 0; + insideChangeset = false; + break; + #endregion + + #region Operation + case ODataBatchReaderState.Operation: + Exception exception = this.ProcessCurrentOperationResponse(batchReader, insideChangeset); + if (!insideChangeset) + { + #region Get response + Debug.Assert(operationCount == 0, "missing an EndChangeSet 2"); + + QueryOperationResponse qresponse = null; + try + { + if (exception == null) + { + DataServiceRequest query = this.Queries[queryCount]; + ResponseInfo responseInfo = this.RequestInfo.GetDeserializationInfo(null /*mergeOption*/); + MaterializeAtom materializer = DataServiceRequest.Materialize( + responseInfo, + query.QueryComponents(this.RequestInfo.Model), + null, + this.currentOperationResponse.Headers.GetHeader(XmlConstants.HttpContentType), + this.currentOperationResponse.CreateResponseMessage(), + query.PayloadKind); + qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, materializer); + } + } + catch (ArgumentException e) + { + exception = e; + } + catch (FormatException e) + { + exception = e; + } + catch (InvalidOperationException e) + { + exception = e; + } + + if (qresponse == null) + { + if (this.Queries != null) + { + // this is the normal ExecuteBatch response + DataServiceRequest query = this.Queries[queryCount]; + + if (this.RequestInfo.IgnoreResourceNotFoundException && this.currentOperationResponse.StatusCode == HttpStatusCode.NotFound) + { + qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); + } + else + { + qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); + qresponse.Error = exception; + } + } + else + { + // This is top-level failure for SaveChanges(SaveChangesOptions.BatchWithSingleChangeset) or SaveChanges(SaveChangesOptions.BatchWithIndependentOperations) operations. + // example: server doesn't support batching or number of batch objects exceeded an allowed limit. + // ex could be null if the server responded to SaveChanges with an unexpected success with + // response of batched GETS that did not correspond the original POST/PATCH/PUT/DELETE requests. + // we expect non-null since server should have failed with a non-success code + // and HandleResponse(status, ...) should generate the exception object + throw exception; + } + } + + qresponse.StatusCode = (int)this.currentOperationResponse.StatusCode; + queryCount++; + yield return qresponse; + #endregion + } + else + { + #region Update response + try + { + Descriptor descriptor = this.ChangedEntries[this.entryIndex]; + operationCount += this.SaveResultProcessed(descriptor); + + if (exception != null) + { + throw exception; + } + + this.HandleOperationResponseHeaders(this.currentOperationResponse.StatusCode, this.currentOperationResponse.Headers); +#if DEBUG + this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers, this.currentOperationResponse.StatusCode); +#else + this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers); +#endif + } + catch (Exception e) + { + this.ChangedEntries[this.entryIndex].SaveError = e; + exception = e; + + if (!CommonUtil.IsCatchableExceptionType(e)) + { + throw; + } + } + + ChangeOperationResponse changeOperationResponse = + new ChangeOperationResponse(this.currentOperationResponse.Headers, this.ChangedEntries[this.entryIndex]); + changeOperationResponse.StatusCode = (int)this.currentOperationResponse.StatusCode; + if (exception != null) + { + changeOperationResponse.Error = exception; + } + + operationCount++; + this.entryIndex++; + yield return changeOperationResponse; + #endregion + } + + break; + #endregion + + default: + Error.ThrowBatchExpectedResponse(InternalError.UnexpectedBatchState); + break; + } + } + + Debug.Assert(batchReader.State == ODataBatchReaderState.Completed, "unexpected batch state"); + + // Check for a changeset without response (first line) or GET request without response (second line). + // either all saved entries must be processed or it was a batch and one of the entries has the error + if ((this.Queries == null && + (!changesetFound || + 0 < queryCount || + this.ChangedEntries.Any(o => o.ContentGeneratedForSave && o.SaveResultWasProcessed == 0) && + (!this.IsBatchRequest || this.ChangedEntries.FirstOrDefault(o => o.SaveError != null) == null))) || + (this.Queries != null && queryCount != this.Queries.Length)) + { + throw Error.InvalidOperation(Strings.Batch_IncompleteResponseCount); + } + } + finally + { + // Note that this will be called only once the enumeration of all responses is finished and the Dispose + // was called on the IEnumerator used for that enumeration. It is not called when the method returns, + // since the compiler change this method to return the compiler-generated IEnumerable. + Util.Dispose(ref this.batchMessageReader); + } + } + + /// + /// Processed the operation response reported by the batch reader. + /// This is a side-effecting method that is tied deeply to how it is used in the batch processing pipeline. + /// + /// The batch reader to get the operation response from. + /// True if the current operation is inside a changeset (implying CUD, not query) + /// An exception if the operation response is an error response, null for success response. + private Exception ProcessCurrentOperationResponse(ODataBatchReader batchReader, bool isChangesetOperation) + { + Debug.Assert(batchReader != null, "batchReader != null"); + Debug.Assert(batchReader.State == ODataBatchReaderState.Operation, "This method requires the batch reader to be on an operation."); + + ODataBatchOperationResponseMessage operationResponseMessage = batchReader.CreateOperationResponseMessage(); + Descriptor descriptor = null; + + if (isChangesetOperation) + { + // We need to peek at the content-Id before handing the response to the user, so we can expose the Descriptor them. + // We're OK with this exception to our general rule of not using them before ReceivingResponse event is fired. + this.entryIndex = this.ValidateContentID(operationResponseMessage.ContentId); + descriptor = this.ChangedEntries[entryIndex]; + } + + // If we hit en error inside a batch, we will never expose a descriptor since we don't know which one to return. + // The descriptor we fetched above based on the content-ID is bogus because the server returns an errounous content-id when + // it hits an error inside batch. + if (!WebUtil.SuccessStatusCode((HttpStatusCode)operationResponseMessage.StatusCode)) + { + descriptor = null; + } + + this.RequestInfo.Context.FireReceivingResponseEvent(new ReceivingResponseEventArgs(operationResponseMessage, descriptor, true)); + + // We need to know if the content of the operation response is empty or not. + // We also need to cache the entire content, since in case of GET response the response itself will be parsed + // lazily and so it can happen that we will move the batch reader after this operation before we actually read + // the content of the operation. + Stream originalOperationResponseContentStream = operationResponseMessage.GetStream(); + if (originalOperationResponseContentStream == null) + { + Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream); + } + + MemoryStream operationResponseContentStream; + try + { + operationResponseContentStream = new MemoryStream(); + WebUtil.CopyStream(originalOperationResponseContentStream, operationResponseContentStream, ref this.streamCopyBuffer); + operationResponseContentStream.Position = 0; + } + finally + { + originalOperationResponseContentStream.Dispose(); + } + + this.currentOperationResponse = new CurrentOperationResponse( + (HttpStatusCode)operationResponseMessage.StatusCode, + operationResponseMessage.Headers, + operationResponseContentStream); + + Version responseVersion; + string headerName = XmlConstants.HttpODataVersion; + return BaseSaveResult.HandleResponse( + this.RequestInfo, + this.currentOperationResponse.StatusCode, + this.currentOperationResponse.Headers.GetHeader(headerName), + () => this.currentOperationResponse.ContentStream, + false, + out responseVersion); + } + + /// + /// Validate the content-id. + /// + /// The contentId read from ChangeSetHead. + /// Returns the correct ChangedEntries index. + private int ValidateContentID(string contentIdStr) + { + int contentID = 0; + + if (string.IsNullOrEmpty(contentIdStr) || + !Int32.TryParse(contentIdStr, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out contentID)) + { + Error.ThrowBatchUnexpectedContent(InternalError.ChangeResponseMissingContentID); + } + + for (int i = 0; i < this.ChangedEntries.Count; ++i) + { + if (this.ChangedEntries[i].ChangeOrder == contentID) + { + return i; + } + } + + Error.ThrowBatchUnexpectedContent(InternalError.ChangeResponseUnknownContentID); + return -1; + } + + /// + /// Stores information about the currenly processed operation response. + /// + private sealed class CurrentOperationResponse + { + /// The HTTP response status code for the current operation response. + private readonly HttpStatusCode statusCode; + + /// The HTTP headers for the current operation response. + private readonly HeaderCollection headers; + + /// The content stream for the current operation response. + private readonly MemoryStream contentStream; + + /// + /// Constructor. + /// + /// The status code of the response. + /// The response headers. + /// An in-memory copy of the response stream. + public CurrentOperationResponse(HttpStatusCode statusCode, IEnumerable> headers, MemoryStream contentStream) + { + Debug.Assert(headers != null, "headers != null"); + Debug.Assert(contentStream != null, "contentStream != null"); + Debug.Assert(contentStream.Position == 0, "The stream should have been reset to the begining."); + + this.statusCode = statusCode; + this.contentStream = contentStream; + + this.headers = new HeaderCollection(); + foreach (KeyValuePair operationResponseHeader in headers) + { + this.headers.SetHeader(operationResponseHeader.Key, operationResponseHeader.Value); + } + } + + /// + /// The status code of the operation response. + /// + public HttpStatusCode StatusCode + { + get + { + return this.statusCode; + } + } + + /// + /// The content stream of the operation response. + /// + public Stream ContentStream + { + get + { + return this.contentStream; + } + } + + /// + /// true if the content stream is empty, false otherwise. + /// + public bool HasEmptyContent + { + get + { + return this.contentStream.Length == 0; + } + } + + /// + /// The response headers for the operation response. + /// + public HeaderCollection Headers + { + get + { + return this.headers; + } + } + + /// + /// Creates IODataResponseMessage for the operation response. + /// + /// + /// IODataResponseMessage for the operation response. + /// null if the operation response has empty content. + /// + public IODataResponseMessage CreateResponseMessage() + { + return this.HasEmptyContent + ? null + : new HttpWebResponseMessage(this.headers, (int)this.statusCode, () => this.contentStream); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingEntityInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingEntityInfo.cs new file mode 100644 index 0000000..b577065 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingEntityInfo.cs @@ -0,0 +1,486 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Linq; + using System.Threading; + using Microsoft.OData.Client.Metadata; + #endregion + + /// Type of property stored in BindingPropertyInfo. + internal enum BindingPropertyKind + { + /// Property type is a complex type. + BindingPropertyKindComplex, + + /// Property type is an entity type with keys. + BindingPropertyKindEntity, + + /// Property is a DataServiceCollection. + BindingPropertyKindDataServiceCollection, + + /// Property is a collection of primitives or complex types. + BindingPropertyKindPrimitiveOrComplexCollection + } + + /// Cache of information about entity types and their observable properties + internal class BindingEntityInfo + { + /// Object reference used as a 'False' flag. + private static readonly object FalseObject = new object(); + + /// Object reference used as a 'True' flag. + private static readonly object TrueObject = new object(); + + /// Lock on metadata caches. + private static readonly ReaderWriterLockSlim metadataCacheLock = new ReaderWriterLockSlim(); + + /// Types which are known not to be entity types. + private static readonly HashSet knownNonEntityTypes = new HashSet(EqualityComparer.Default); + + /// Types which are known to be (or not) collection types. + private static readonly Dictionary knownObservableCollectionTypes = new Dictionary(EqualityComparer.Default); + + /// Mapping between types and their corresponding entity information + private static readonly Dictionary bindingEntityInfos = new Dictionary(EqualityComparer.Default); + + /// Obtain binding info corresponding to a given type + /// Type for which to obtain information + /// the client model. + /// Info about the + internal static IList GetObservableProperties(Type entityType, ClientEdmModel model) + { + return GetBindingEntityInfoFor(entityType, model).ObservableProperties; + } + + /// Gets the ClientType corresponding to the given type + /// Input type + /// The client model. + /// Corresponding ClientType + internal static ClientTypeAnnotation GetClientType(Type entityType, ClientEdmModel model) + { + return GetBindingEntityInfoFor(entityType, model).ClientType; + } + + /// + /// Get the entity set name for the target entity object. + /// + /// An entity object. + /// The 'currently known' entity set name for the target object. + /// The client model. + /// The entity set name for the target object. + /// + /// Allow user code to provide the entity set name. If user code does not provide the entity set name, then + /// this method will get the entity set name from the value of the EntitySetAttribute. + /// The 'currently known' entity set name for top level collections can be provided through OEC constructor + /// + internal static string GetEntitySet( + object target, + string targetEntitySet, + ClientEdmModel model) + { + Debug.Assert(target != null, "Argument 'target' cannot be null."); + Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType(), model), "Argument 'target' must be an entity type."); + + // Here's the rules in order of priority for resolving entity set name + // 1. EntitySet name passed in the constructor or extension methods of DataServiceCollection + // 2. EntitySet name specified in the EntitySet attribute by the code gen. {Remember this attribute is + // not generated in case of MEST) + if (!String.IsNullOrEmpty(targetEntitySet)) + { + return targetEntitySet; + } + else + { + // If there is not a 'currently known' entity set name to validate against, then there must be + // EntitySet attribute on the entity type + return BindingEntityInfo.GetEntitySetAttribute(target.GetType(), model); + } + } + + /// + /// Determine if the specified type is an DataServiceCollection. + /// + /// + /// If there a generic class in the inheritance hierarchy of the type, that has a single + /// entity type paramenter T, and is assignable to DataServiceCollection(Of T), then + /// the type is an DataServiceCollection. + /// + /// An object type specifier. + /// The client model. + /// true if the type is an DataServiceCollection; otherwise false. + internal static bool IsDataServiceCollection(Type collectionType, ClientEdmModel model) + { + Debug.Assert(collectionType != null, "Argument 'collectionType' cannot be null."); + + metadataCacheLock.EnterReadLock(); + try + { + object resultAsObject; + if (knownObservableCollectionTypes.TryGetValue(collectionType, out resultAsObject)) + { + return resultAsObject == TrueObject; + } + } + finally + { + metadataCacheLock.ExitReadLock(); + } + + Type type = collectionType; + bool result = false; + + while (type != null) + { + if (type.IsGenericType()) + { + // Is there a generic class in the inheritance hierarchy, that has a single + // entity type paramenter T, and is assignable to DataServiceCollection + Type[] parms = type.GetGenericArguments(); + + if (parms != null && parms.Length == 1 && ClientTypeUtil.TypeOrElementTypeIsEntity(parms[0])) + { + // if ObservableCollection is not available dataServiceCollection will be null + Type dataServiceCollection = WebUtil.GetDataServiceCollectionOfT(parms); + if (dataServiceCollection != null && dataServiceCollection.IsAssignableFrom(type)) + { + result = true; + break; + } + } + } + + type = type.GetBaseType(); + } + + metadataCacheLock.EnterWriteLock(); + try + { + if (!knownObservableCollectionTypes.ContainsKey(collectionType)) + { + knownObservableCollectionTypes[collectionType] = result ? TrueObject : FalseObject; + } + } + finally + { + metadataCacheLock.ExitWriteLock(); + } + + return result; + } + + /// + /// Determine if the specified type is an entity type. + /// + /// An object type specifier. + /// The client model. + /// true if the type is an entity type; otherwise false. + internal static bool IsEntityType(Type type, ClientEdmModel model) + { + Debug.Assert(type != null, "Argument 'type' cannot be null."); + + metadataCacheLock.EnterReadLock(); + try + { + if (knownNonEntityTypes.Contains(type)) + { + return false; + } + } + finally + { + metadataCacheLock.ExitReadLock(); + } + + bool isEntityType; + try + { + if (BindingEntityInfo.IsDataServiceCollection(type, model)) + { + return false; + } + + isEntityType = ClientTypeUtil.TypeOrElementTypeIsEntity(type); + } + catch (InvalidOperationException) + { + isEntityType = false; + } + + if (!isEntityType) + { + metadataCacheLock.EnterWriteLock(); + try + { + if (!knownNonEntityTypes.Contains(type)) + { + knownNonEntityTypes.Add(type); + } + } + finally + { + metadataCacheLock.ExitWriteLock(); + } + } + + return isEntityType; + } + + /// + /// Tries to get the value of a property and corresponding BindingPropertyInfo or ClientPropertyAnnotation if the property exists + /// + /// Source object whose property needs to be read + /// Name of the source object property + /// The client model. + /// BindingPropertyInfo corresponding to + /// Instance of ClientProperty corresponding to + /// Value of the property + /// true if the property exists and the value was read; otherwise false. + internal static bool TryGetPropertyValue(object source, string sourceProperty, ClientEdmModel model, out BindingPropertyInfo bindingPropertyInfo, out ClientPropertyAnnotation clientProperty, out object propertyValue) + { + Type sourceType = source.GetType(); + + bindingPropertyInfo = BindingEntityInfo.GetObservableProperties(sourceType, model) + .SingleOrDefault(x => x.PropertyInfo.PropertyName == sourceProperty); + + bool propertyFound = bindingPropertyInfo != null; + + // bindingPropertyInfo is null for primitive properties. + if (!propertyFound) + { + clientProperty = BindingEntityInfo.GetClientType(sourceType, model) + .GetProperty(sourceProperty, UndeclaredPropertyBehavior.Support); + + propertyFound = clientProperty != null; + if (!propertyFound) + { + propertyValue = null; + } + else + { + propertyValue = clientProperty.GetValue(source); + } + } + else + { + clientProperty = null; + propertyValue = bindingPropertyInfo.PropertyInfo.GetValue(source); + } + + return propertyFound; + } + + /// Obtain binding info corresponding to a given type + /// Type for which to obtain information + /// The client model. + /// Info about the + private static BindingEntityInfoPerType GetBindingEntityInfoFor(Type entityType, ClientEdmModel model) + { + BindingEntityInfoPerType bindingEntityInfo; + + metadataCacheLock.EnterReadLock(); + try + { + if (bindingEntityInfos.TryGetValue(entityType, out bindingEntityInfo)) + { + return bindingEntityInfo; + } + } + finally + { + metadataCacheLock.ExitReadLock(); + } + + bindingEntityInfo = new BindingEntityInfoPerType(); + + // Try to get the entity set name from the EntitySetAttribute attributes. In order to make the + // inheritance work, we need to look at the attributes declared in the base types also. + // EntitySetAttribute does not allow multiples, so there can be at most 1 instance on the type. + EntitySetAttribute entitySetAttribute = (EntitySetAttribute)entityType.GetCustomAttributes(typeof(EntitySetAttribute), true).SingleOrDefault(); + + // There must be exactly one (unambiguous) EntitySetAttribute attribute. + bindingEntityInfo.EntitySet = entitySetAttribute != null ? entitySetAttribute.EntitySet : null; + bindingEntityInfo.ClientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityType)); + + foreach (ClientPropertyAnnotation p in bindingEntityInfo.ClientType.Properties()) + { + BindingPropertyInfo bpi = null; + Type propertyType = p.PropertyType; + + if (p.IsStreamLinkProperty) + { + // DataServiceStreamLink is not mutable externally + // It implements INPC to notify controls about our updates + // We should ignore its events since we are the only one updating it. + continue; + } + else if (p.IsPrimitiveOrEnumOrComplexCollection) + { + Debug.Assert(!BindingEntityInfo.IsDataServiceCollection(propertyType, model), "DataServiceCollection cannot be the type that backs collections of primitives or complex types."); + bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection }; + } + else if (p.IsEntityCollection) + { + if (BindingEntityInfo.IsDataServiceCollection(propertyType, model)) + { + bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindDataServiceCollection }; + } + } + else if (BindingEntityInfo.IsEntityType(propertyType, model)) + { + bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindEntity }; + } + else if (BindingEntityInfo.CanBeComplexType(propertyType)) + { + // Add complex types and nothing else. + Debug.Assert(!p.IsKnownType, "Known types do not implement INotifyPropertyChanged."); + bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindComplex }; + } + + if (bpi != null) + { + bpi.PropertyInfo = p; + + // For entity types, all of the above types of properties are interesting. + // For complex types, only observe collections and complex type properties. + if (bindingEntityInfo.ClientType.IsEntityType || + bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex || + bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection) + { + bindingEntityInfo.ObservableProperties.Add(bpi); + } + } + } + + metadataCacheLock.EnterWriteLock(); + try + { + if (!bindingEntityInfos.ContainsKey(entityType)) + { + bindingEntityInfos[entityType] = bindingEntityInfo; + } + } + finally + { + metadataCacheLock.ExitWriteLock(); + } + + return bindingEntityInfo; + } + + /// Checks whether a given type can be a complex type i.e. implements INotifyPropertyChanged. + /// Input type. + /// true if the type is complex type, false otherwise. + private static bool CanBeComplexType(Type type) + { + Debug.Assert(type != null, "type != null"); + return typeof(INotifyPropertyChanged).IsAssignableFrom(type); + } + + /// Gets entity set corresponding to a given type + /// Intput type + /// The client model. + /// Entity set name for the type + private static string GetEntitySetAttribute(Type entityType, ClientEdmModel model) + { + return GetBindingEntityInfoFor(entityType, model).EntitySet; + } + + /// Information about a property interesting for binding + internal class BindingPropertyInfo + { + /// Property information + public ClientPropertyAnnotation PropertyInfo + { + get; + set; + } + + /// Kind of the property i.e. complex, entity or collection. + public BindingPropertyKind PropertyKind + { + get; + set; + } + } + + /// Holder of information about entity properties for a type + private sealed class BindingEntityInfoPerType + { + /// Collection of properties interesting to the observer + private List observableProperties; + + /// Constructor + public BindingEntityInfoPerType() + { + this.observableProperties = new List(); + } + + /// Entity set of the entity + public String EntitySet + { + get; + set; + } + + /// Corresponding ClientTyp + public ClientTypeAnnotation ClientType + { + get; + set; + } + + /// Collection of properties interesting to the observer + public List ObservableProperties + { + get + { + return this.observableProperties; + } + } + } + +#if PORTABLELIB + /// Read-writer lock, implemented over a Monitor. + private sealed class ReaderWriterLockSlim + { + /// Single object on which to lock. + private object readerWriterLock = new object(); + + /// Enters a reader lock. Writers will also be blocked. + internal void EnterReadLock() + { + Monitor.Enter(this.readerWriterLock); + } + + /// Enters a writer lock. Readers will also be blocked. + internal void EnterWriteLock() + { + Monitor.Enter(this.readerWriterLock); + } + + /// Exits a reader lock. + internal void ExitReadLock() + { + Monitor.Exit(this.readerWriterLock); + } + + /// Exits a writer lock. + internal void ExitWriteLock() + { + Monitor.Exit(this.readerWriterLock); + } + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingGraph.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingGraph.cs new file mode 100644 index 0000000..f8d8534 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingGraph.cs @@ -0,0 +1,1144 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using Microsoft.OData.Client.Metadata; + #endregion + + /// + /// Color of each vertex to be used for Depth First Search + /// + internal enum VertexColor + { + /// White color means un-visited + White, + + /// Gray color means added to queue for DFS + Gray, + + /// Black color means already visited hence reachable from root + Black + } + + /// + /// The BindingGraph maps objects tracked by the DataServiceContext to vertices in a + /// graph used to manage the information needed for data binding. The objects tracked + /// by the BindingGraph are entity type objects and observable entity collections. + /// + internal sealed class BindingGraph + { + /// The observer of the graph + private BindingObserver observer; + + /// Graph containing entities, collections and their relationships + private Graph graph; + + /// Constructor + /// Observer of the graph + public BindingGraph(BindingObserver observer) + { + this.observer = observer; + this.graph = new Graph(); + } + + /// Adds a DataServiceCollection to the graph + /// Source object for the collection, this object has navigation property corresponding to collection + /// Property in that corresponds to the collection + /// Collection being added + /// Entity set of entities in the collection + /// true if a new vertex had to be created, false if it already exists + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] + public bool AddDataServiceCollection( + object source, + string sourceProperty, + object collection, + string collectionEntitySet) + { + Debug.Assert(collection != null, "'collection' can not be null"); + Debug.Assert( + BindingEntityInfo.IsDataServiceCollection(collection.GetType(), this.observer.Context.Model), + "Argument 'collection' must be an DataServiceCollection of entity type T"); + + if (this.graph.ExistsVertex(collection)) + { + return false; + } + + Vertex collectionVertex = this.graph.AddVertex(collection); + collectionVertex.IsDataServiceCollection = true; + collectionVertex.EntitySet = collectionEntitySet; + + ICollection collectionItf = collection as ICollection; + + if (source != null) + { + collectionVertex.Parent = this.graph.LookupVertex(source); + collectionVertex.ParentProperty = sourceProperty; + this.graph.AddEdge(source, collection, sourceProperty); + + // Update the observer on the child collection + Type entityType = BindingUtils.GetCollectionEntityType(collection.GetType()); + Debug.Assert(entityType != null, "Collection must at least be inherited from DataServiceCollection"); + + // Fail if the collection entity type does not implement INotifyPropertyChanged. + if (!typeof(INotifyPropertyChanged).IsAssignableFrom(entityType)) + { + throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(entityType)); + } + + typeof(BindingGraph) + .GetMethod("SetObserver", false /*isPublic*/, false /*isStatic*/) + .MakeGenericMethod(entityType) + .Invoke(this, new object[] { collectionItf }); + } + else + { + // When there is no source, then this vertex is the root vertex + this.graph.Root = collectionVertex; + } + + Debug.Assert( + collectionVertex.Parent != null || collectionVertex.IsRootDataServiceCollection, + "If parent is null, then collectionVertex should be a root collection"); + + // Register for collection notifications + this.AttachDataServiceCollectionNotification(collection); + + // Perform deep add, by recursively adding entities in the collection + foreach (var item in collectionItf) + { + this.AddEntity( + source, + sourceProperty, + item, + collectionEntitySet, + collection); + } + + return true; + } + + /// Adds a collection of primitives or complex types to the graph + /// Source object for the collection, this object has property corresponding to collection + /// Property in that corresponds to the collection + /// Collection being added + /// Type of item in the collection + public void AddPrimitiveOrComplexCollection( + object source, + string sourceProperty, + object collection, + Type collectionItemType) + { + Vertex parentVertex = this.graph.LookupVertex(source); + + Debug.Assert(parentVertex != null, "Must have a valid parent entity for collection properties."); + Debug.Assert(collection != null, "Must have non-null collection reference."); + Debug.Assert(ClientTypeUtil.GetImplementationType(collection.GetType(), typeof(ICollection<>)) != null, "Argument 'collection' must be an ICollection"); + + Vertex collectionVertex = this.graph.LookupVertex(collection); + + if (collectionVertex == null) + { + collectionVertex = this.graph.AddVertex(collection); + collectionVertex.Parent = parentVertex; + collectionVertex.ParentProperty = sourceProperty; + collectionVertex.IsPrimitiveOrEnumOrComplexCollection = true; + collectionVertex.PrimitiveOrComplexCollectionItemType = collectionItemType; + + this.graph.AddEdge(source, collection, sourceProperty); + + // Attach CollectionChanged event listener and fail if the collection type doesn't support notification + if (!this.AttachPrimitiveOrComplexCollectionNotification(collection)) + { + throw new InvalidOperationException(Strings.DataBinding_NotifyCollectionChangedNotImpl(collection.GetType())); + } + + // If the collection contains complex objects, bind to all of the objects in the collection + if (!PrimitiveType.IsKnownNullableType(collectionItemType) && !collectionItemType.IsEnum()) + { + // Fail if the collection contains a complex type that does not implement INotifyPropertyChanged. + if (!typeof(INotifyPropertyChanged).IsAssignableFrom(collectionItemType)) + { + throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(collectionItemType)); + } + + // Recursively bind to all of the complex objects in the collection and any nested complex objects or collections + this.AddComplexObjectsFromCollection(collection, (IEnumerable)collection); + } + } + else + { + throw new InvalidOperationException(Strings.DataBinding_CollectionAssociatedWithMultipleEntities(collection.GetType())); + } + } + + /// Adds an entity to the graph + /// Source object for the entity, this object has navigation property that links to entity + /// Property in that links to entity + /// Entity being added + /// Entity set of entity being added + /// Item from which the directed edge in the graph goes into . This can be a collection + /// true if a new vertex had to be created, false if it already exists + /// + /// This method processes the current 'target' entity and then recursively moves into the graph through + /// the navigation properties. The 'source' is a previously processed item - it is the 'parent' + /// of the target entity. + /// The code generated EntitySetAttribute is processed by this method. + /// A source entity can reference the target entity directly through an entity reference navigation property, + /// or indirectly through a collection navigation property. + /// + public bool AddEntity( + object source, + string sourceProperty, + object target, + string targetEntitySet, + object edgeSource) + { + Vertex sourceVertex = this.graph.LookupVertex(edgeSource); + Debug.Assert(sourceVertex != null, "Must have a valid edge source"); + + Vertex entityVertex = null; + bool addedNewEntity = false; + + if (target != null) + { + entityVertex = this.graph.LookupVertex(target); + + if (entityVertex == null) + { + entityVertex = this.graph.AddVertex(target); + + entityVertex.EntitySet = BindingEntityInfo.GetEntitySet(target, targetEntitySet, this.observer.Context.Model); + + // Register for entity notifications, fail if the entity does not implement INotifyPropertyChanged. + if (!this.AttachEntityOrComplexObjectNotification(target)) + { + throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(target.GetType())); + } + + addedNewEntity = true; + } + + // Add relationship. Connect the from end to the target. + if (this.graph.ExistsEdge(edgeSource, target, sourceVertex.IsDataServiceCollection ? null : sourceProperty)) + { + throw new InvalidOperationException(Strings.DataBinding_EntityAlreadyInCollection(target.GetType())); + } + + this.graph.AddEdge(edgeSource, target, sourceVertex.IsDataServiceCollection ? null : sourceProperty); + } + + if (!sourceVertex.IsDataServiceCollection) + { + this.observer.HandleUpdateEntityReference( + source, + sourceProperty, + sourceVertex.EntitySet, + target, + entityVertex == null ? null : entityVertex.EntitySet); + } + else + { + Debug.Assert(target != null, "Target must be non-null when adding to collections"); + this.observer.HandleAddEntity( + source, + sourceProperty, + sourceVertex.Parent != null ? sourceVertex.Parent.EntitySet : null, + edgeSource as ICollection, + target, + entityVertex.EntitySet); + } + + if (addedNewEntity) + { + // Perform recursive add operation through target's properties + this.AddFromProperties(target); + } + + return addedNewEntity; + } + + /// + /// Removes the from the binding graph + /// + /// Item to remove + /// Parent of the + /// Parent property that refers to + public void RemoveDataServiceCollectionItem(object item, object parent, string parentProperty) + { + Vertex vertexToRemove = this.graph.LookupVertex(item); + if (vertexToRemove == null) + { + return; + } + + Debug.Assert(!vertexToRemove.IsRootDataServiceCollection, "Root collections are never removed"); + + // Parent will always be non-null for deletes from collections, this will include + // both root and child collections. For root collections, parentProperty will be null. + Debug.Assert(parent != null, "Parent has to be present."); + + // When parentProperty is null, parent is itself a root collection + if (parentProperty != null) + { + BindingEntityInfo.BindingPropertyInfo bpi = BindingEntityInfo.GetObservableProperties(parent.GetType(), this.observer.Context.Model) + .Single(p => p.PropertyInfo.PropertyName == parentProperty); + Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindDataServiceCollection, "parentProperty must refer to an DataServiceCollection"); + + parent = bpi.PropertyInfo.GetValue(parent); + } + + object source = null; + string sourceProperty = null; + string sourceEntitySet = null; + string targetEntitySet = null; + + this.GetDataServiceCollectionInfo( + parent, + out source, + out sourceProperty, + out sourceEntitySet, + out targetEntitySet); + + targetEntitySet = BindingEntityInfo.GetEntitySet(item, targetEntitySet, this.observer.Context.Model); + + this.observer.HandleDeleteEntity( + source, + sourceProperty, + sourceEntitySet, + parent as ICollection, + item, + targetEntitySet); + + this.graph.RemoveEdge(parent, item, null); + } + + /// + /// Removes the from the binding graph + /// + /// Item to remove + /// Collection that contains the + public void RemoveComplexTypeCollectionItem(object item, object collection) + { + Debug.Assert(collection != null, "Cannot remove item from a null collection"); + + if (item == null) + { + // There is no binding to null items, so just return + return; + } + + Vertex vertexToRemove = this.graph.LookupVertex(item); + if (vertexToRemove == null) + { + // If we weren't already bound to the item, just return. + return; + } + + Debug.Assert(vertexToRemove.IsComplex, "Should only need to remove vertex for complex objects."); + + this.graph.RemoveEdge(collection, item, null); + } + + /// Removes all of a collection's items from the graph, but does not remove the collection. + /// Collection containing the items to remove. + /// This is used for both DataServiceCollection and collections of primitives or complex types. + public void RemoveCollection(object source) + { + Vertex sourceVertex = this.graph.LookupVertex(source); + Debug.Assert(sourceVertex != null, "Must be tracking the vertex for the collection"); + + foreach (Edge edge in sourceVertex.OutgoingEdges.ToList()) + { + this.graph.RemoveEdge(source, edge.Target.Item, null); + } + + // This is where actual removal from graph happens, detach notifications for removed object + this.RemoveUnreachableVertices(); + } + + /// Removes a relationship between two items based on source and relation label + /// Source item + /// Label for relation + public void RemoveRelation(object source, string relation) + { + Edge edge = this.graph + .LookupVertex(source) + .OutgoingEdges + .SingleOrDefault(e => e.Source.Item == source && e.Label == relation); + if (edge != null) + { + this.graph.RemoveEdge(edge.Source.Item, edge.Target.Item, edge.Label); + } + + // This is where actual removal from graph happens, detach notifications for removed object + this.RemoveUnreachableVertices(); + } + +#if DEBUG + /// Checks to see if an object is being tracked by the graph + /// Object being checked + /// true if the object exists in the graph, false otherwise + public bool IsTracking(object item) + { + return this.graph.ExistsVertex(item); + } +#endif + /// Remove all non-tracked entities from the graph + public void RemoveNonTrackedEntities() + { + // Cleanup all untracked entities + foreach (var entity in this.graph.Select(o => !this.observer.IsContextTrackingEntity(o) && BindingEntityInfo.IsEntityType(o.GetType(), this.observer.Context.Model))) + { + this.graph.ClearEdgesForVertex(this.graph.LookupVertex(entity)); + } + + this.RemoveUnreachableVertices(); + } + + /// + /// Returns a sequence of items belonging to a collection. Uses the children of a collection + /// vertex for this enumeration. + /// + /// Collection being enumerated. + /// Sequence of items belonging to the collection. + public IEnumerable GetDataServiceCollectionItems(object collection) + { + Vertex collectionVertex = this.graph.LookupVertex(collection); + Debug.Assert(collectionVertex != null, "Must be tracking the vertex for the collection"); + foreach (Edge collectionEdge in collectionVertex.OutgoingEdges.ToList()) + { + yield return collectionEdge.Target.Item; + } + } + + /// Reset the graph after detaching notifications for everything + public void Reset() + { + this.graph.Reset(this.DetachNotifications); + } + + /// Removes the un-reachable vertices from the graph and un-registers notification handlers + public void RemoveUnreachableVertices() + { + // This is where actual removal from graph happens, detach notifications for removed object + this.graph.RemoveUnreachableVertices(this.DetachNotifications); + } + + /// Get the binding information for a DataServiceCollection + /// Collection + /// The source object that reference the target object through a navigation property. + /// The navigation property in the source object that reference the target object. + /// The entity set of the source object. + /// The entity set name of the target object. + public void GetDataServiceCollectionInfo( + object collection, + out object source, + out string sourceProperty, + out string sourceEntitySet, + out string targetEntitySet) + { + Debug.Assert(collection != null, "Argument 'collection' cannot be null."); + Debug.Assert(this.graph.ExistsVertex(collection), "Vertex corresponding to 'collection' must exist in the graph."); + + this.graph + .LookupVertex(collection) + .GetDataServiceCollectionInfo( + out source, + out sourceProperty, + out sourceEntitySet, + out targetEntitySet); + } + + /// Get the binding information for a collection + /// Collection + /// The source object that reference the target object through a collection property. + /// The collection property in the source object that reference the target object. + /// Type of item in the collection + public void GetPrimitiveOrComplexCollectionInfo( + object collection, + out object source, + out string sourceProperty, + out Type collectionItemType) + { + Debug.Assert(collection != null, "Argument 'collection' cannot be null."); + Debug.Assert(this.graph.ExistsVertex(collection), "Vertex corresponding to 'collection' must exist in the graph."); + + this.graph + .LookupVertex(collection) + .GetPrimitiveOrComplexCollectionInfo( + out source, + out sourceProperty, + out collectionItemType); + } + + /// + /// Obtains the closest ancestor entity type in the graph corresponding to a complex object vertex. + /// + /// On input this is a complex object, on output it is the closest entity ancestor. + /// On input this is a complex object's member property name, on output it is the name of corresponding property of the ancestor entity. + /// On input this is a complex object's member property value, on output it is the value of the corresponding property of the ancestor entity. + public void GetAncestorEntityForComplexProperty( + ref object entity, + ref string propertyName, + ref object propertyValue) + { + Vertex childVertex = this.graph.LookupVertex(entity); + Debug.Assert(childVertex != null, "Must have a vertex in the graph corresponding to the entity."); + Debug.Assert(childVertex.IsComplex == true, "Vertex must correspond to a complex object."); + + // The complex object that contains the property could be contained in another complex object or collection, + // so continue to walk the graph until we find a parent that is neither of these types. + while (childVertex.IsComplex || childVertex.IsPrimitiveOrEnumOrComplexCollection) + { + propertyName = childVertex.IncomingEdges[0].Label; + propertyValue = childVertex.Item; + + Debug.Assert(childVertex.Parent != null, "Complex properties must always have parent vertices."); + entity = childVertex.Parent.Item; + + childVertex = childVertex.Parent; + } + } + + /// + /// Adds a complex typed object to the graph, also traverses all the child complex properties and adds them. + /// + /// Source object that contains the complex object, can be an entity, complex object, or a collection. + /// Source property of complex type, is null if complex type is in a collection, otherwise is the property that references the complex object on source. + /// Target complex object value. + public void AddComplexObject(object source, string sourceProperty, object target) + { + Debug.Assert(target != null, "Must have non-null complex object reference."); + Vertex complexVertex = this.graph.LookupVertex(target); + + if (complexVertex == null) + { + Vertex parentVertex = this.graph.LookupVertex(source); + Debug.Assert(parentVertex != null, "Must have a valid parent entity for complex properties."); + + complexVertex = this.graph.AddVertex(target); + complexVertex.Parent = parentVertex; + complexVertex.IsComplex = true; + + // Register for complex type notifications, fail if the complex type does not implement INotifyPropertyChanged. + if (!this.AttachEntityOrComplexObjectNotification(target)) + { + throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(target.GetType())); + } + + this.graph.AddEdge(source, target, sourceProperty); + + // Add nested properties for the complex object. + this.AddFromProperties(target); + } + else + { + throw new InvalidOperationException(Strings.DataBinding_ComplexObjectAssociatedWithMultipleEntities(target.GetType())); + } + } + + /// + /// Adds complex items to the graph from the specified collection. + /// + /// Collection that contains . + /// Items in to add to the binding graph. May be only a subset of the total items in . + public void AddComplexObjectsFromCollection(object collection, IEnumerable collectionItems) + { + foreach (object collectionItem in collectionItems) + { + if (collectionItem != null) + { + this.AddComplexObject(collection, null, collectionItem); + } + } + } + + /// Add items to the graph, from the object's properties + /// Object whose properties are to be explored + private void AddFromProperties(object entity) + { + // Once the entity is attached to the graph, we need to traverse all it's properties + // and add related entities and collections to this entity. + foreach (BindingEntityInfo.BindingPropertyInfo bpi in BindingEntityInfo.GetObservableProperties(entity.GetType(), this.observer.Context.Model)) + { + object propertyValue = bpi.PropertyInfo.GetValue(entity); + + if (propertyValue != null) + { + switch (bpi.PropertyKind) + { + case BindingPropertyKind.BindingPropertyKindDataServiceCollection: + this.AddDataServiceCollection( + entity, + bpi.PropertyInfo.PropertyName, + propertyValue, + null); + + break; + + case BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection: + this.AddPrimitiveOrComplexCollection( + entity, + bpi.PropertyInfo.PropertyName, + propertyValue, + bpi.PropertyInfo.PrimitiveOrComplexCollectionItemType); + break; + + case BindingPropertyKind.BindingPropertyKindEntity: + this.AddEntity( + entity, + bpi.PropertyInfo.PropertyName, + propertyValue, + null, + entity); + + break; + + default: + Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex, "Must be complex type if PropertyKind is not entity, DataServiceCollection, or collection."); + this.AddComplexObject( + entity, + bpi.PropertyInfo.PropertyName, + propertyValue); + break; + } + } + } + } + + /// Attach the CollectionChanged handler to an DataServiceCollection. + /// An DataServiceCollection. + private void AttachDataServiceCollectionNotification(object target) + { + Debug.Assert(target != null, "Argument 'target' cannot be null"); + + INotifyCollectionChanged notify = target as INotifyCollectionChanged; + Debug.Assert(notify != null, "DataServiceCollection must implement INotifyCollectionChanged"); + + notify.CollectionChanged -= this.observer.OnDataServiceCollectionChanged; + notify.CollectionChanged += this.observer.OnDataServiceCollectionChanged; + } + + /// Attach the CollectionChanged handler to a collection of primitives or complex types. + /// An ICollection of T, where T is the type of the item in the collection. + /// True if the collection is attached; otherwise false. + private bool AttachPrimitiveOrComplexCollectionNotification(object collection) + { + Debug.Assert(collection != null, "Argument 'Collection' cannot be null"); + + INotifyCollectionChanged notify = collection as INotifyCollectionChanged; + if (notify != null) + { + notify.CollectionChanged -= this.observer.OnPrimitiveOrComplexCollectionChanged; + notify.CollectionChanged += this.observer.OnPrimitiveOrComplexCollectionChanged; + return true; + } + + return false; + } + + /// Attach the PropertyChanged handler to an entity or complex object. + /// An entity or complex object. + /// True if the target is attached; otherwise false. + private bool AttachEntityOrComplexObjectNotification(object target) + { + Debug.Assert(target != null, "Argument 'target' cannot be null"); + + INotifyPropertyChanged notify = target as INotifyPropertyChanged; + if (notify != null) + { + notify.PropertyChanged -= this.observer.OnPropertyChanged; + notify.PropertyChanged += this.observer.OnPropertyChanged; + return true; + } + + return false; + } + + /// Detach CollectionChanged or PropertyChanged handlers from the target + /// An entity object or collection. + private void DetachNotifications(object target) + { + Debug.Assert(target != null, "Argument 'target' cannot be null"); + + this.DetachCollectionNotifications(target); + + INotifyPropertyChanged notifyPropertyChanged = target as INotifyPropertyChanged; + if (notifyPropertyChanged != null) + { + notifyPropertyChanged.PropertyChanged -= this.observer.OnPropertyChanged; + } + } + + /// Detach CollectionChanged handlers from the target + /// A collection object + private void DetachCollectionNotifications(object target) + { + Debug.Assert(target != null, "Argument 'target' cannot be null"); + + INotifyCollectionChanged notifyCollectionChanged = target as INotifyCollectionChanged; + if (notifyCollectionChanged != null) + { + notifyCollectionChanged.CollectionChanged -= this.observer.OnDataServiceCollectionChanged; + notifyCollectionChanged.CollectionChanged -= this.observer.OnPrimitiveOrComplexCollectionChanged; + } + } + + /// + /// Sets the observer for a child DataServiceCollection + /// + /// Entity type for the collection + /// Non-typed collection interface + private void SetObserver(ICollection collection) + { + DataServiceCollection oec = collection as DataServiceCollection; + oec.Observer = this.observer; + } + + /// Graph implementation for tracking entities, collections for binding + internal sealed class Graph + { + /// Vertices of the graph, which also hold edges + private Dictionary vertices; + + /// The root vertex for the graph, DFS traversals start from this vertex + private Vertex root; + + /// Constructor + public Graph() + { + this.vertices = new Dictionary(ReferenceEqualityComparer.Instance); + } + + /// Root vertex of the graph + public Vertex Root + { + get + { + Debug.Assert(this.root != null, "Must have a non-null root vertex when this call is made."); + return this.root; + } + + set + { + Debug.Assert(this.root == null, "Must only initialize root vertex once."); + Debug.Assert(this.ExistsVertex(value.Item), "Must already have the assigned vertex in the graph."); + this.root = value; + } + } + + /// Adds vertex to the graph + /// Item corresponding to vertex + /// Newly created vertex + public Vertex AddVertex(object item) + { + Vertex v = new Vertex(item); + this.vertices.Add(item, v); + return v; + } + + /// Removes all edges going out of and coming into the given vertex + /// Vertex whose edges are to be cleared + public void ClearEdgesForVertex(Vertex v) + { + foreach (Edge e in v.OutgoingEdges.Concat(v.IncomingEdges).ToList()) + { + this.RemoveEdge(e.Source.Item, e.Target.Item, e.Label); + } + } + + /// + /// Checks if a vertex exists corresponding to given + /// + /// Item to lookup + /// true if vertex found, false otherwise + public bool ExistsVertex(object item) + { + Vertex v; + return this.vertices.TryGetValue(item, out v); + } + + /// Looksup the vertex corresponding to + /// Item to lookup + /// Vertex corresponding to item + public Vertex LookupVertex(object item) + { + Vertex v; + this.vertices.TryGetValue(item, out v); + return v; + } + + /// + /// Adds edge between vertices corresponding to and + /// objects which will be labeled with + /// + /// Outgoing end of the edge + /// Incoming end of the edge + /// Label for the vertex + /// Newly created edge + public Edge AddEdge(object source, object target, string label) + { + Vertex s = this.vertices[source]; + Vertex t = this.vertices[target]; + Edge e = new Edge { Source = s, Target = t, Label = label }; + s.OutgoingEdges.Add(e); + t.IncomingEdges.Add(e); + return e; + } + + /// + /// Removes edge between vertices corresponding to and + /// objects which was labeled with + /// + /// Outgoing end of the edge + /// Incoming end of the edge + /// Label for the vertex + public void RemoveEdge(object source, object target, string label) + { + Vertex s = this.vertices[source]; + Vertex t = this.vertices[target]; + Edge e = new Edge { Source = s, Target = t, Label = label }; + s.OutgoingEdges.Remove(e); + t.IncomingEdges.Remove(e); + } + + /// + /// Checks if an edge exists between and labeled + /// with + /// + /// Outgoing end of the edge + /// Incoming end of the edge + /// Label for the vertex + /// true if an edge exists between source and target with given label, false otherwise + public bool ExistsEdge(object source, object target, string label) + { + Edge e = new Edge { Source = this.vertices[source], Target = this.vertices[target], Label = label }; + return this.vertices[source].OutgoingEdges.Any(r => r.Equals(e)); + } + + /// + /// Selects collection of objects tracked by the graph based on the given filter + /// + /// Filter for the objects + /// Filtered list of objects tracked by the graph + public IList Select(Func filter) + { + return this.vertices.Keys.Where(filter).ToList(); + } + + /// + /// Removes everything from the graph after applying + /// + /// Action to apply before removal of each node + public void Reset(Action action) + { + foreach (object obj in this.vertices.Keys) + { + action(obj); + } + + this.vertices.Clear(); + } + + /// Remove all vertices from graph that are unreachable from the root collection vertex + /// Action to perform for each removed vertex + public void RemoveUnreachableVertices(Action detachAction) + { + try + { + foreach (Vertex v in this.UnreachableVertices()) + { + this.ClearEdgesForVertex(v); + detachAction(v.Item); + this.vertices.Remove(v.Item); + } + } + finally + { + // Reset color for all vertices back to white. + foreach (Vertex v in this.vertices.Values) + { + v.Color = VertexColor.White; + } + } + } + + /// Collects all vertices unreachable from the root collection vertex + /// Sequence of vertices that are unreachable from the root collection vertex + /// + /// Performs a depth first traversal of the graph starting from the root collection + /// vertex and checks if some vertices were unreachable was reached while doing the traversal. + /// Alogrithm from Introduction to Algorithms 22.2 by Cormen et al. + /// + private IEnumerable UnreachableVertices() + { + if (this.vertices.Count == 0) + { + // If there is no vertex in the graph, there is no unreachable vertex as well. + return Enumerable.Empty(); + } + + Queue q = new Queue(); + + this.Root.Color = VertexColor.Gray; + q.Enqueue(this.Root); + + while (q.Count != 0) + { + Vertex current = q.Dequeue(); + + foreach (Edge e in current.OutgoingEdges) + { + if (e.Target.Color == VertexColor.White) + { + e.Target.Color = VertexColor.Gray; + q.Enqueue(e.Target); + } + } + + current.Color = VertexColor.Black; + } + + return this.vertices.Values.Where(v => v.Color == VertexColor.White).ToList(); + } + } + + /// Vertex of the + internal sealed class Vertex + { + /// Collection of incoming edges for the vertex + private List incomingEdges; + + /// Collection of outgoing edges for the vertex + private List outgoingEdges; + + /// Constructor + /// Item corresponding to vertex + public Vertex(object item) + { + Debug.Assert(item != null, "item must be non-null"); + this.Item = item; + this.Color = VertexColor.White; + } + + /// Item corresponding to the vertex + public object Item + { + get; + private set; + } + + /// Entity set of the item held by the vertex + public string EntitySet + { + get; + set; + } + + /// Is item a DataServiceCollection object + public bool IsDataServiceCollection + { + get; + set; + } + + /// Is item a complex type object + public bool IsComplex + { + get; + set; + } + + /// Is item a collection of primitives or enum or complex types + public bool IsPrimitiveOrEnumOrComplexCollection + { + get; + set; + } + + /// Type of items in the collection if this items represents a collection of primitives or complex types + public Type PrimitiveOrComplexCollectionItemType + { + get; + set; + } + + /// Parent vertex, only exists for non-top level collection vertices or complex objects + public Vertex Parent + { + get; + set; + } + + /// Property of the object that associates this vertex with it's parent + public string ParentProperty + { + get; + set; + } + + /// Is item a root collection object + public bool IsRootDataServiceCollection + { + get + { + return this.IsDataServiceCollection && this.Parent == null; + } + } + + /// Color of the vertex + public VertexColor Color + { + get; + set; + } + + /// Edges coming into this vertex + public IList IncomingEdges + { + get + { + if (this.incomingEdges == null) + { + this.incomingEdges = new List(); + } + + return this.incomingEdges; + } + } + + /// Edges going out of this vertex + public IList OutgoingEdges + { + get + { + if (this.outgoingEdges == null) + { + this.outgoingEdges = new List(); + } + + return this.outgoingEdges; + } + } + + /// Get the binding information for a collection vertex + /// The source object that reference the target object through a navigation property corresponding to current collection vertex. + /// The navigation property in the source object that reference the target object. + /// The entity set of the source object. + /// The entity set of the target object. + public void GetDataServiceCollectionInfo( + out object source, + out string sourceProperty, + out string sourceEntitySet, + out string targetEntitySet) + { + Debug.Assert(this.IsDataServiceCollection, "Must be a collection to be in this method"); + + if (!this.IsRootDataServiceCollection) + { + Debug.Assert(this.Parent != null, "Parent must be non-null for child collection"); + + source = this.Parent.Item; + Debug.Assert(source != null, "Source object must be present for child collection"); + + sourceProperty = this.ParentProperty; + Debug.Assert(sourceProperty != null, "Source entity property associated with a child collection must be non-null"); + +#if DEBUG + PropertyInfo propertyInfo = source.GetType().GetProperty(ClientTypeUtil.GetClientPropertyName(source.GetType(), sourceProperty, UndeclaredPropertyBehavior.ThrowException)); + Debug.Assert(propertyInfo != null, "Unable to get information for the source entity property associated with a child collection"); +#endif + sourceEntitySet = this.Parent.EntitySet; + } + else + { + Debug.Assert(this.Parent == null, "Parent must be null for top level collection"); + source = null; + sourceProperty = null; + sourceEntitySet = null; + } + + targetEntitySet = this.EntitySet; + } + + /// Get the binding information for a collection vertex + /// The source object that reference the target object through a collection property corresponding to current collection vertex. + /// The collection property in the source object that references the collection object. + /// Type of item in the collection. + public void GetPrimitiveOrComplexCollectionInfo( + out object source, + out string sourceProperty, + out Type collectionItemType) + { + Debug.Assert(this.Parent != null, "Parent must be non-null for collection"); + + source = this.Parent.Item; + Debug.Assert(source != null, "Source object must be present for collection"); + + sourceProperty = this.ParentProperty; + Debug.Assert(sourceProperty != null, "Source entity property associated with a collection must be non-null"); + + collectionItemType = this.PrimitiveOrComplexCollectionItemType; + Debug.Assert(collectionItemType != null, "Collection item type must be non-null for collection."); + +#if DEBUG + PropertyInfo propertyInfo = source.GetType().GetProperty(ClientTypeUtil.GetClientPropertyName(source.GetType(), sourceProperty, UndeclaredPropertyBehavior.ThrowException)); + Debug.Assert(propertyInfo != null, "Unable to get information for the source entity property associated with a collection"); +#endif + } + } + + /// + /// Edge between two vertices of graph, directed and labeled + /// + internal sealed class Edge : IEquatable + { + /// Source vertex + public Vertex Source + { + get; + set; + } + + /// Target vertex + public Vertex Target + { + get; + set; + } + + /// Label of the edge + public string Label + { + get; + set; + } + + /// IEquatable override + /// Comparand + /// true if equal, false otherwise + public bool Equals(Edge other) + { + return other != null && + Object.ReferenceEquals(this.Source, other.Source) && + Object.ReferenceEquals(this.Target, other.Target) && + this.Label == other.Label; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingObserver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingObserver.cs new file mode 100644 index 0000000..aaf853e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingObserver.cs @@ -0,0 +1,1131 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ +#region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using Microsoft.OData.Client.Metadata; +#endregion + + /// The BindingObserver class + internal sealed class BindingObserver + { + #region Fields + + /// + /// The BindingGraph maps objects tracked by the DataServiceContext to vertices in a + /// graph used to manage the information needed for data binding. The objects tracked + /// by the BindingGraph are entities, complex types and DataServiceCollections. + /// + private BindingGraph bindingGraph; + + #endregion + + #region Constructor + + /// Constructor + /// The DataServiceContext associated with the BindingObserver. + /// EntityChanged delegate. + /// EntityCollectionChanged delegate. + internal BindingObserver(DataServiceContext context, Func entityChanged, Func collectionChanged) + { + Debug.Assert(context != null, "Must have been validated during DataServiceCollection construction."); + this.Context = context; + this.Context.ChangesSaved += this.OnChangesSaved; + + this.EntityChanged = entityChanged; + this.CollectionChanged = collectionChanged; + + this.bindingGraph = new BindingGraph(this); + } + + #endregion + + #region Properties + + /// The DataServiceContext associated with the BindingObserver. + internal DataServiceContext Context + { + get; + private set; + } + + /// The behavior of add operations should be Attach or Add on the context. + internal bool AttachBehavior + { + get; + set; + } + + /// The behavior of remove operations should be Detach on the context. + internal bool DetachBehavior + { + get; + set; + } + + /// + /// Callback invoked when a property of an entity object tracked by the BindingObserver has changed. + /// + /// + /// Entity objects tracked by the BindingObserver implement INotifyPropertyChanged. Events of this type + /// flow throw the EntityChangedParams. If this callback is not implemented by user code, or the user code + /// implementation returns false, the BindingObserver executes a default implementation for the callback. + /// + internal Func EntityChanged + { + get; + private set; + } + + /// + /// Callback invoked when an DataServiceCollection tracked by the BindingObserver has changed. + /// + /// + /// DataServiceCollection objects tracked by the BindingObserver implement INotifyCollectionChanged. + /// Events of this type flow throw the EntityCollectionChanged callback. If this callback is not + /// implemented by user code, or the user code implementation returns false, the BindingObserver executes + /// a default implementation for the callback. + /// + internal Func CollectionChanged + { + get; + private set; + } + + #endregion + + #region Methods + + /// Start tracking the specified DataServiceCollection. + /// An entity type. + /// An DataServiceCollection. + /// The entity set of the elements in . + internal void StartTracking(DataServiceCollection collection, string collectionEntitySet) + { + Debug.Assert(collection != null, "Only constructed collections are tracked."); + Debug.Assert(BindingEntityInfo.IsEntityType(typeof(T), this.Context.Model), "DataServiceCollection type should already have been verified to be an entity type in DataServiceCollection.StartTracking."); + + try + { + this.AttachBehavior = true; + + // Recursively traverse the entire object graph under the root collection. + this.bindingGraph.AddDataServiceCollection(null, null, collection, collectionEntitySet); + } + finally + { + this.AttachBehavior = false; + } + } + + /// Stop tracking the root DataServiceCollection associated with the observer. + internal void StopTracking() + { + this.bindingGraph.Reset(); + + this.Context.ChangesSaved -= this.OnChangesSaved; + } + + /// + /// Looks up parent entity that references . + /// + /// Type of DataServiceCollection. + /// DataService collection + /// Parent entity that references . May return null if there is none. + /// Navigation property in the parentEntity that references . May return null if there is no parent entity. + /// True if parent entity was found, otherwise false. + internal bool LookupParent(DataServiceCollection collection, out object parentEntity, out string parentProperty) + { + string sourceEntitySet; + string targetEntitySet; + this.bindingGraph.GetDataServiceCollectionInfo(collection, out parentEntity, out parentProperty, out sourceEntitySet, out targetEntitySet); + + return parentEntity != null; + } + + /// Handle changes to tracked entity. + /// The entity that raised the event. + /// Information about the event such as changed property name. + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] + internal void OnPropertyChanged(object source, PropertyChangedEventArgs eventArgs) + { + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNull(eventArgs, "eventArgs"); + +#if DEBUG + Debug.Assert(this.bindingGraph.IsTracking(source), "Entity must be part of the graph if it has the event notification registered."); +#endif + string sourceProperty = eventArgs.PropertyName; + + // When sourceProperty is null, it is assumed that all properties for the object changed + // As a result, we should be performing an UpdateObject operation on the context. + if (String.IsNullOrEmpty(sourceProperty)) + { + this.HandleUpdateEntity( + source, + null, + null); + } + else + { + BindingEntityInfo.BindingPropertyInfo bpi; + ClientPropertyAnnotation property; + object sourcePropertyValue; + + // Try to get the new value for the changed property. Ignore this change event if the property doesn't exist. + if (BindingEntityInfo.TryGetPropertyValue(source, sourceProperty, this.Context.Model, out bpi, out property, out sourcePropertyValue)) + { + // Check if it is an interesting property e.g. collection of entities, entity reference, complex type, or collection of primitives or complex types. + if (bpi != null) + { + // Disconnect the edge between source and original source property value. + this.bindingGraph.RemoveRelation(source, sourceProperty); + + switch (bpi.PropertyKind) + { + case BindingPropertyKind.BindingPropertyKindDataServiceCollection: + // If collection is already being tracked by the graph we can not have > 1 links to it. + if (sourcePropertyValue != null) + { + // Make sure that there is no observer on the input collection property. + try + { + typeof(BindingUtils) + .GetMethod("VerifyObserverNotPresent", false /*isPublic*/, true /*isStatic*/) + .MakeGenericMethod(bpi.PropertyInfo.EntityCollectionItemType) +#if DEBUG +.Invoke(null, new object[] { sourcePropertyValue, sourceProperty, source.GetType(), this.Context.Model }); +#else + .Invoke(null, new object[] { sourcePropertyValue, sourceProperty, source.GetType() }); +#endif + } + catch (TargetInvocationException tie) + { + throw tie.InnerException; + } + + try + { + this.AttachBehavior = true; + this.bindingGraph.AddDataServiceCollection( + source, + sourceProperty, + sourcePropertyValue, + null); + } + finally + { + this.AttachBehavior = false; + } + } + + break; + + case BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection: + // Attach the newly assigned collection + if (sourcePropertyValue != null) + { + this.bindingGraph.AddPrimitiveOrComplexCollection( + source, + sourceProperty, + sourcePropertyValue, + bpi.PropertyInfo.PrimitiveOrComplexCollectionItemType); + } + + this.HandleUpdateEntity( + source, + sourceProperty, + sourcePropertyValue); + break; + + case BindingPropertyKind.BindingPropertyKindEntity: + // Add the newly added entity to the graph, or update entity reference. + this.bindingGraph.AddEntity( + source, + sourceProperty, + sourcePropertyValue, + null, + source); + break; + + default: + Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex, "Must be complex type if PropertyKind is not entity or collection."); + + // Attach the newly assigned complex type object and it's child complex typed objects. + if (sourcePropertyValue != null) + { + this.bindingGraph.AddComplexObject( + source, + sourceProperty, + sourcePropertyValue); + } + + this.HandleUpdateEntity( + source, + sourceProperty, + sourcePropertyValue); + break; + } + } + else + { + Debug.Assert(property != null, "property != null"); + + // If the property is DataServiceStreamLink property, we need to ignore any changes to it + if (!property.IsStreamLinkProperty) + { + // For non-interesting properties we simply call UpdateObject on the context. + // This applies to primitive properties on entities and complex types, and collections of entities that are not a DataServiceCollection. + this.HandleUpdateEntity( + source, + sourceProperty, + sourcePropertyValue); + } + } + } + } + } + + /// Handle changes to tracked DataServiceCollection. + /// The DataServiceCollection that raised the event. + /// Information about the event such as added/removed entities, operation. + internal void OnDataServiceCollectionChanged(object collection, NotifyCollectionChangedEventArgs eventArgs) + { + Util.CheckArgumentNull(collection, "collection"); + Util.CheckArgumentNull(eventArgs, "eventArgs"); + + Debug.Assert(BindingEntityInfo.IsDataServiceCollection(collection.GetType(), this.Context.Model), "We only register this event for DataServiceCollections."); +#if DEBUG + Debug.Assert(this.bindingGraph.IsTracking(collection), "Collection must be part of the graph if it has the event notification registered."); +#endif + object source; + string sourceProperty; + string sourceEntitySet; + string targetEntitySet; + + this.bindingGraph.GetDataServiceCollectionInfo( + collection, + out source, + out sourceProperty, + out sourceEntitySet, + out targetEntitySet); + + switch (eventArgs.Action) + { + case NotifyCollectionChangedAction.Add: + // This event is raised by ObservableCollection.InsertItem. + this.OnAddToDataServiceCollection( + eventArgs, + source, + sourceProperty, + targetEntitySet, + collection); + break; + + case NotifyCollectionChangedAction.Remove: + // This event is raised by ObservableCollection.RemoveItem. + this.OnRemoveFromDataServiceCollection( + eventArgs, + source, + sourceProperty, + collection); + break; + + case NotifyCollectionChangedAction.Replace: + // This event is raised by ObservableCollection.SetItem. + this.OnRemoveFromDataServiceCollection( + eventArgs, + source, + sourceProperty, + collection); + + this.OnAddToDataServiceCollection( + eventArgs, + source, + sourceProperty, + targetEntitySet, + collection); + break; + + case NotifyCollectionChangedAction.Reset: + // This event is raised by ObservableCollection.Clear. + if (this.DetachBehavior) + { + // Detach behavior requires going through each item and detaching it from context. + this.RemoveWithDetachDataServiceCollection(collection); + } + else + { + // Non-detach behavior requires only removing vertices of collection from graph. + this.bindingGraph.RemoveCollection(collection); + } + + break; + +#if !PORTABLELIB + case NotifyCollectionChangedAction.Move: + // Do Nothing. Added for completeness. + break; +#endif + + default: + throw new InvalidOperationException(Strings.DataBinding_DataServiceCollectionChangedUnknownActionCollection(eventArgs.Action)); + } + } + + /// Handle changes to collection properties. + /// The collection that raised the event. + /// Information about the event such as added/removed items, operation. + internal void OnPrimitiveOrComplexCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Util.CheckArgumentNull(sender, "sender"); + Util.CheckArgumentNull(e, "e"); + + object source; + string sourceProperty; + Type collectionItemType; + + this.bindingGraph.GetPrimitiveOrComplexCollectionInfo( + sender, + out source, + out sourceProperty, + out collectionItemType); + + // For complex types need to bind to any newly added items or unbind from removed items + if (!PrimitiveType.IsKnownNullableType(collectionItemType) && !collectionItemType.IsEnum()) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + this.OnAddToComplexTypeCollection(sender, e.NewItems); + break; + case NotifyCollectionChangedAction.Remove: + this.OnRemoveFromComplexTypeCollection(sender, e.OldItems); + break; + case NotifyCollectionChangedAction.Replace: + this.OnRemoveFromComplexTypeCollection(sender, e.OldItems); + this.OnAddToComplexTypeCollection(sender, e.NewItems); + break; +#if !PORTABLELIB + case NotifyCollectionChangedAction.Move: + // Do Nothing. Added for completeness. + break; +#endif + case NotifyCollectionChangedAction.Reset: + this.bindingGraph.RemoveCollection(sender); + break; + default: + throw new InvalidOperationException(Strings.DataBinding_CollectionChangedUnknownActionCollection(e.Action, sender.GetType())); + } + } + + this.HandleUpdateEntity( + source, + sourceProperty, + sender); + } + + /// Handle Adds to a tracked DataServiceCollection. Perform operations on context to reflect the changes. + /// The source object that reference the target object through a navigation property. + /// The navigation property in the source object that reference the target object. + /// The entity set of the source object. + /// The collection containing the target object. + /// The target entity to attach. + /// The entity set name of the target object. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Pending")] + internal void HandleAddEntity( + object source, + string sourceProperty, + string sourceEntitySet, + ICollection collection, + object target, + string targetEntitySet) + { + if (this.Context.ApplyingChanges) + { + return; + } + + Debug.Assert( + (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)), + "source and sourceProperty should either both be present or both be absent."); + + Debug.Assert(target != null, "target must be provided by the caller."); + Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType(), this.Context.Model), "target must be an entity type."); + + // Do not handle add for already detached and deleted entities. + if (source != null && this.IsDetachedOrDeletedFromContext(source)) + { + return; + } + + // Do we need an operation on context to handle the Add operation. + EntityDescriptor targetDescriptor = this.Context.GetEntityDescriptor(target); + + // Following are the conditions where context operation is required: + // 1. Not a call to Load or constructions i.e. we have Add behavior and not Attach behavior + // 2. Target entity is not being tracked + // 3. Target is being tracked but there is no link between the source and target entity and target is in non-deleted state + bool contextOperationRequired = !this.AttachBehavior && + (targetDescriptor == null || + (source != null && !this.IsContextTrackingLink(source, sourceProperty, target) && targetDescriptor.State != EntityStates.Deleted)); + + if (contextOperationRequired) + { + // First give the user code a chance to handle Add operation. + if (this.CollectionChanged != null) + { + EntityCollectionChangedParams args = new EntityCollectionChangedParams( + this.Context, + source, + sourceProperty, + sourceEntitySet, + collection, + target, + targetEntitySet, + NotifyCollectionChangedAction.Add); + + if (this.CollectionChanged(args)) + { + return; + } + } + } + + // The user callback code could detach the source. + if (source != null && this.IsDetachedOrDeletedFromContext(source)) + { + throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); + } + + // Default implementation. + targetDescriptor = this.Context.GetEntityDescriptor(target); + + if (source != null) + { + if (this.AttachBehavior) + { + // If the target entity is not being currently tracked, we attach both the + // entity and the link between source and target entity. + if (targetDescriptor == null) + { + BindingUtils.ValidateEntitySetName(targetEntitySet, target); + + this.Context.AttachTo(targetEntitySet, target); + this.Context.AttachLink(source, sourceProperty, target); + } + else + if (targetDescriptor.State != EntityStates.Deleted && !this.IsContextTrackingLink(source, sourceProperty, target)) + { + // If the target is already being tracked, then we attach the link if it + // does not already exist between the source and target entities and the + // target entity is not already in Deleted state. + this.Context.AttachLink(source, sourceProperty, target); + } + } + else + { + // The target will be added and link from source to target will get established in the code + // below. Note that if there is already target present then we just try to establish the link + // however, if the link is also already established then we don't do anything. + if (targetDescriptor == null) + { + // If the entity is not tracked, that means the entity needs to + // be added to the context. We need to call AddRelatedObject, + // which adds via the parent (for e.g. POST Customers(0)/Orders). + this.Context.AddRelatedObject(source, sourceProperty, target); + } + else + if (targetDescriptor.State != EntityStates.Deleted && !this.IsContextTrackingLink(source, sourceProperty, target)) + { + // If the entity is already tracked, then we just add the link. + // However, we would not do it if the target entity is already + // in a Deleted state. + this.Context.AddLink(source, sourceProperty, target); + } + } + } + else + if (targetDescriptor == null) + { + // The source is null when the DataServiceCollection is the root collection. + BindingUtils.ValidateEntitySetName(targetEntitySet, target); + + if (this.AttachBehavior) + { + // Attach the target entity. + this.Context.AttachTo(targetEntitySet, target); + } + else + { + // Add the target entity. + this.Context.AddObject(targetEntitySet, target); + } + } + } + + /// Handle Deletes from a tracked DataServiceCollection. Perform operations on context to reflect the changes. + /// The source object that reference the target object through a navigation property. + /// The navigation property in the source object that reference the target object. + /// The entity set of the source object. + /// The collection containing the target object. + /// The target entity. + /// The entity set name of the target object. + internal void HandleDeleteEntity( + object source, + string sourceProperty, + string sourceEntitySet, + ICollection collection, + object target, + string targetEntitySet) + { + if (this.Context.ApplyingChanges) + { + return; + } + + Debug.Assert( + (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)), + "source and sourceProperty should either both be present or both be absent."); + + Debug.Assert(target != null, "target must be provided by the caller."); + Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType(), this.Context.Model), "target must be an entity type."); + + Debug.Assert(!this.AttachBehavior, "AttachBehavior is only allowed during Construction and Load when this method should never be entered."); + + // Do not handle delete for already detached and deleted entities. + if (source != null && this.IsDetachedOrDeletedFromContext(source)) + { + return; + } + + // Do we need an operation on context to handle the Delete operation. + // Detach behavior is special because it is only applicable in Clear + // cases, where we don't callback users for detach nofications. + bool contextOperationRequired = this.IsContextTrackingEntity(target) && !this.DetachBehavior; + + if (contextOperationRequired) + { + // First give the user code a chance to handle Delete operation. + if (this.CollectionChanged != null) + { + EntityCollectionChangedParams args = new EntityCollectionChangedParams( + this.Context, + source, + sourceProperty, + sourceEntitySet, + collection, + target, + targetEntitySet, + NotifyCollectionChangedAction.Remove); + + if (this.CollectionChanged(args)) + { + return; + } + } + } + + // The user callback code could detach the source. + if (source != null && !this.IsContextTrackingEntity(source)) + { + throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); + } + + // Default implementation. + // Remove the entity from the context if it is currently being tracked. + if (this.IsContextTrackingEntity(target)) + { + if (this.DetachBehavior) + { + this.Context.Detach(target); + } + else + { + this.Context.DeleteObject(target); + } + } + } + + /// Handle changes to navigation properties of a tracked entity. Perform operations on context to reflect the changes. + /// The source object that reference the target object through a navigation property. + /// The navigation property in the source object that reference the target object. + /// The entity set of the source object. + /// The target entity. + /// The entity set name of the target object. + internal void HandleUpdateEntityReference( + object source, + string sourceProperty, + string sourceEntitySet, + object target, + string targetEntitySet) + { + if (this.Context.ApplyingChanges) + { + return; + } + + Debug.Assert(source != null, "source can not be null for update operations."); + Debug.Assert(BindingEntityInfo.IsEntityType(source.GetType(), this.Context.Model), "source must be an entity with keys."); + Debug.Assert(!String.IsNullOrEmpty(sourceProperty), "sourceProperty must be a non-empty string for update operations."); + + // Do not handle update for detached and deleted entities. + if (this.IsDetachedOrDeletedFromContext(source)) + { + return; + } + + // Do we need an operation on context to handle the Update operation. + EntityDescriptor targetDescriptor = target != null ? this.Context.GetEntityDescriptor(target) : null; + + // Following are the conditions where context operation is required: + // 1. Not a call to Load or constructions i.e. we have Add behavior and not Attach behavior + // 2. Target entity is not being tracked + // 3. Target is being tracked but there is no link between the source and target entity + bool contextOperationRequired = !this.AttachBehavior && + (targetDescriptor == null || + !this.IsContextTrackingLink(source, sourceProperty, target)); + + if (contextOperationRequired) + { + // First give the user code a chance to handle Update link operation. + if (this.EntityChanged != null) + { + EntityChangedParams args = new EntityChangedParams( + this.Context, + source, + sourceProperty, + target, + sourceEntitySet, + targetEntitySet); + + if (this.EntityChanged(args)) + { + return; + } + } + } + + // The user callback code could detach the source. + if (this.IsDetachedOrDeletedFromContext(source)) + { + throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); + } + + // Default implementation. + targetDescriptor = target != null ? this.Context.GetEntityDescriptor(target) : null; + + if (target != null) + { + bool needSetLink = true; + if (targetDescriptor == null) + { + // If the targetEntitySet is null, it indicates that the target is a contained navigation property. + if (targetEntitySet == null && !this.AttachBehavior) + { + this.Context.UpdateRelatedObject(source, sourceProperty, target); + needSetLink = false; + } + else + { + // If the entity set name is not known, then we must throw since we need to know the + // entity set in order to add/attach the referenced object to it's entity set. + BindingUtils.ValidateEntitySetName(targetEntitySet, target); + + if (this.AttachBehavior) + { + this.Context.AttachTo(targetEntitySet, target); + } + else + { + this.Context.AddObject(targetEntitySet, target); + } + } + + targetDescriptor = this.Context.GetEntityDescriptor(target); + } + + // if the entity is already tracked, then just set/attach the link. However, do + // not try to attach the link if the target is in Deleted state. + if (!this.IsContextTrackingLink(source, sourceProperty, target)) + { + if (this.AttachBehavior) + { + if (targetDescriptor.State != EntityStates.Deleted) + { + this.Context.AttachLink(source, sourceProperty, target); + } + } + else if (needSetLink) + { + this.Context.SetLink(source, sourceProperty, target); + } + } + } + else + { + Debug.Assert(!this.AttachBehavior, "During attach operations we must never perform operations for null values."); + + // The target could be null in which case we just need to set the link to null. + this.Context.SetLink(source, sourceProperty, null); + } + } + + /// Determine if the DataServiceContext is tracking the specified entity. + /// An entity object. + /// true if the entity is tracked; otherwise false. + internal bool IsContextTrackingEntity(object entity) + { + Debug.Assert(entity != null, "entity must be provided when checking for context tracking."); + return this.Context.GetEntityDescriptor(entity) != default(EntityDescriptor); + } + + /// + /// Handle changes to an entity object tracked by the BindingObserver + /// + /// The entity object that has changed. + /// The property of the target entity object that has changed. + /// The value of the changed property of the target object. + private void HandleUpdateEntity(object entity, string propertyName, object propertyValue) + { + Debug.Assert(!this.AttachBehavior || this.Context.ApplyingChanges, "Entity updates must not happen during Attach or construction phases, deserialization case is the exception."); + + if (this.Context.ApplyingChanges) + { + return; + } + + // For complex types, we will perform notification and update on the closest ancestor entity using the farthest ancestor complex property. + if (!BindingEntityInfo.IsEntityType(entity.GetType(), this.Context.Model)) + { + this.bindingGraph.GetAncestorEntityForComplexProperty(ref entity, ref propertyName, ref propertyValue); + } + + Debug.Assert(entity != null, "entity must be provided for update operations."); + Debug.Assert(BindingEntityInfo.IsEntityType(entity.GetType(), this.Context.Model), "entity must be an entity with keys."); + Debug.Assert(!String.IsNullOrEmpty(propertyName) || propertyValue == null, "When propertyName is null no propertyValue should be provided."); + + // Do not handle update for detached and deleted entities. + if (this.IsDetachedOrDeletedFromContext(entity)) + { + return; + } + + HashSet propertiesToSerialize = this.Context.GetEntityDescriptor(entity).PropertiesToSerialize; + if (propertyName != null) + { + propertiesToSerialize.Add(propertyName); + } + + // First give the user code a chance to handle Update operation. + if (this.EntityChanged != null) + { + EntityChangedParams args = new EntityChangedParams( + this.Context, + entity, + propertyName, + propertyValue, + null, + null); + + if (this.EntityChanged(args)) + { + return; + } + } + + // Default implementation. + // The user callback code could detach the entity. + if (this.IsContextTrackingEntity(entity)) + { + // Let UpdateObject check the state of the entity. + this.Context.UpdateObject(entity); + } + } + + /// Processes the INotifyCollectionChanged.Add event. + /// Event information such as added items. + /// Parent entity to which collection belongs. + /// Parent entity property referring to collection. + /// Entity set of the collection. + /// Collection that changed. + private void OnAddToDataServiceCollection( + NotifyCollectionChangedEventArgs eventArgs, + object source, + String sourceProperty, + String targetEntitySet, + object collection) + { + Debug.Assert(collection != null, "Must have a valid collection to which entities are added."); + + if (eventArgs.NewItems != null) + { + foreach (object target in eventArgs.NewItems) + { + if (target == null) + { + throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNull("Add")); + } + + if (!BindingEntityInfo.IsEntityType(target.GetType(), this.Context.Model)) + { + throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNotEntity("Add")); + } + + // Start tracking the target entity and synchronize the context with the Add operation. + this.bindingGraph.AddEntity( + source, + sourceProperty, + target, + targetEntitySet, + collection); + } + } + } + + /// Processes the INotifyCollectionChanged.Remove event. + /// Event information such as deleted items. + /// Parent entity to which collection belongs. + /// Parent entity property referring to collection. + /// Collection that changed. + private void OnRemoveFromDataServiceCollection( + NotifyCollectionChangedEventArgs eventArgs, + object source, + String sourceProperty, + object collection) + { + Debug.Assert(collection != null, "Must have a valid collection from which entities are removed."); + Debug.Assert( + (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)), + "source and sourceProperty must both be null or both be non-null."); + + if (eventArgs.OldItems != null) + { + this.DeepRemoveDataServiceCollection( + eventArgs.OldItems, + source ?? collection, + sourceProperty, + this.ValidateDataServiceCollectionItem); + } + } + + /// Removes a collection from the binding graph and detaches each item. + /// Collection whose elements are to be removed and detached. + private void RemoveWithDetachDataServiceCollection(object collection) + { + Debug.Assert(this.DetachBehavior, "Must be detaching each item in collection."); + + object source = null; + string sourceProperty = null; + string sourceEntitySet = null; + string targetEntitySet = null; + + this.bindingGraph.GetDataServiceCollectionInfo( + collection, + out source, + out sourceProperty, + out sourceEntitySet, + out targetEntitySet); + + this.DeepRemoveDataServiceCollection( + this.bindingGraph.GetDataServiceCollectionItems(collection), + source ?? collection, + sourceProperty, + null); + } + + /// Performs a Deep removal of all entities in a collection. + /// Collection whose items are removed from binding graph. + /// Parent item whose property refer to the being cleared. + /// Property of the that refers to . + /// Validation method if any that checks the individual item in for validity. + private void DeepRemoveDataServiceCollection(IEnumerable collection, object source, string sourceProperty, Action itemValidator) + { + foreach (object target in collection) + { + if (itemValidator != null) + { + itemValidator(target); + } + + // Accumulate the list of entities to untrack, this includes deep added entities under target. + List untrackingInfo = new List(); + + this.CollectUnTrackingInfo( + target, + source, + sourceProperty, + untrackingInfo); + + // Stop tracking the collection of entities found by CollectUnTrackingInfo from bottom up in object graph. + foreach (UnTrackingInfo info in untrackingInfo) + { + this.bindingGraph.RemoveDataServiceCollectionItem( + info.Entity, + info.Parent, + info.ParentProperty); + } + } + + this.bindingGraph.RemoveUnreachableVertices(); + } + + /// + /// Handles additions to collections of complex types. + /// + /// Collection that contains the new items. + /// Items that were added to the collection. + private void OnAddToComplexTypeCollection(object collection, IList newItems) + { + if (newItems != null) + { + this.bindingGraph.AddComplexObjectsFromCollection(collection, newItems); + } + } + + /// + /// Handles removals from collections of complex types. + /// + /// Collection that no longer contains the items. + /// Items that were removed from the collection. + private void OnRemoveFromComplexTypeCollection(object collection, IList items) + { + if (items != null) + { + foreach (object oldItem in items) + { + this.bindingGraph.RemoveComplexTypeCollectionItem(oldItem, collection); + } + + // Remove items from graph + this.bindingGraph.RemoveUnreachableVertices(); + } + } + + /// Handle the DataServiceContext.SaveChanges operation. + /// DataServiceContext for the observer. + /// Information about SaveChanges operation results. + private void OnChangesSaved(object sender, SaveChangesEventArgs eventArgs) + { + // Does the response status code have to be checked? SaveChanges throws on failure. + // DataServiceResponse response = eventArgs.Response; + this.bindingGraph.RemoveNonTrackedEntities(); + } + + /// Collects a list of entities that observer is supposed to stop tracking + /// Entity being delete along with it's children + /// Parent of the + /// Property by which refers to + /// List in which entities to be untracked are collected + private void CollectUnTrackingInfo( + object currentEntity, + object parentEntity, + string parentProperty, + IList entitiesToUnTrack) + { + // We need to delete the child objects first before we delete the parent + foreach (var ed in this.Context + .Entities + .Where(x => x.ParentEntity == currentEntity && x.State == EntityStates.Added)) + { + this.CollectUnTrackingInfo( + ed.Entity, + ed.ParentEntity, + ed.ParentPropertyForInsert, + entitiesToUnTrack); + } + + entitiesToUnTrack.Add(new UnTrackingInfo + { + Entity = currentEntity, + Parent = parentEntity, + ParentProperty = parentProperty + }); + } + + /// Determine if the DataServiceContext is tracking link between and . + /// The source object side of the link. + /// A property in the source side of the link that references the target. + /// The target object side of the link. + /// True if the link is tracked; otherwise false. + private bool IsContextTrackingLink(object source, string sourceProperty, object target) + { + Debug.Assert(source != null, "source entity must be provided."); + Debug.Assert(BindingEntityInfo.IsEntityType(source.GetType(), this.Context.Model), "source must be an entity with keys."); + + Debug.Assert(!String.IsNullOrEmpty(sourceProperty), "sourceProperty must be provided."); + + Debug.Assert(target != null, "target entity must be provided."); + Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType(), this.Context.Model), "target must be an entity with keys."); + + return this.Context.GetLinkDescriptor(source, sourceProperty, target) != default(LinkDescriptor); + } + + /// Checks whether the given entity is in detached or deleted state. + /// Entity being checked. + /// true if the entity is detached or deleted, otherwise returns false. + private bool IsDetachedOrDeletedFromContext(object entity) + { + Debug.Assert(entity != null, "entity must be provided."); + Debug.Assert(BindingEntityInfo.IsEntityType(entity.GetType(), this.Context.Model), "entity must be an entity with keys."); + + EntityDescriptor descriptor = this.Context.GetEntityDescriptor(entity); + return descriptor == null || descriptor.State == EntityStates.Deleted; + } + + /// Entity validator that checks if the is of entity type. + /// Entity being validated. + private void ValidateDataServiceCollectionItem(object target) + { + if (target == null) + { + throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNull("Remove")); + } + + if (!BindingEntityInfo.IsEntityType(target.GetType(), this.Context.Model)) + { + throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNotEntity("Remove")); + } + } + + #endregion + + /// Information regarding each entity to be untracked + private class UnTrackingInfo + { + /// Entity to untrack + public object Entity + { + get; + set; + } + + /// Parent object of + public object Parent + { + get; + set; + } + + /// Parent object property referring to + public string ParentProperty + { + get; + set; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingUtils.cs new file mode 100644 index 0000000..ca8fc27 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/BindingUtils.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ +#region Namespaces + + using System; + using System.Diagnostics; +#endregion + + /// Utilities for binding related operations + internal static class BindingUtils + { + /// + /// Throw if the entity set name is null or empty + /// + /// entity set name. + /// entity instance for which the entity set name is generated. + internal static void ValidateEntitySetName(string entitySetName, object entity) + { + if (String.IsNullOrEmpty(entitySetName)) + { + throw new InvalidOperationException(Strings.DataBinding_Util_UnknownEntitySetName(entity.GetType().FullName)); + } + } + + /// + /// Given a collection type, gets it's entity type + /// + /// Input collection type + /// Generic type argument for the collection + internal static Type GetCollectionEntityType(Type collectionType) + { + while (collectionType != null) + { + if (collectionType.IsGenericType() && WebUtil.IsDataServiceCollectionType(collectionType.GetGenericTypeDefinition())) + { + return collectionType.GetGenericArguments()[0]; + } + + collectionType = collectionType.GetBaseType(); + } + + return null; + } + +#if DEBUG + /// Verifies the absence of observer for an DataServiceCollection + /// Type of DataServiceCollection + /// Non-typed collection object + /// Collection property of the source object which is being assigned to + /// Type of the source object + /// The client model. + internal static void VerifyObserverNotPresent(object oec, string sourceProperty, Type sourceType, ClientEdmModel model) +#else + /// Verifies the absence of observer for an DataServiceCollection + /// Type of DataServiceCollection + /// Non-typed collection object + /// Collection property of the source object which is being assigned to + /// Type of the source object + internal static void VerifyObserverNotPresent(object oec, string sourceProperty, Type sourceType) +#endif + { +#if DEBUG + Debug.Assert(BindingEntityInfo.IsDataServiceCollection(oec.GetType(), model), "Must be an DataServiceCollection."); +#endif + DataServiceCollection typedCollection = oec as DataServiceCollection; + + if (typedCollection.Observer != null) + { + throw new InvalidOperationException(Strings.DataBinding_CollectionPropertySetterValueHasObserver(sourceProperty, sourceType)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceCollectionOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceCollectionOfT.cs new file mode 100644 index 0000000..a9edd1b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceCollectionOfT.cs @@ -0,0 +1,832 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics; + using System.Threading; + using Microsoft.OData.Client.Materialization; + + #endregion Namespaces + + /// Determines whether changes that are made to a are tracked. + public enum TrackingMode + { + /// The collection should not track changes. + None = 0, + + /// The collection should automatically track changes to the entities + /// in the collection. + AutoChangeTracking + } + + /// Represents a dynamic entity collection that provides notifications when items get added, removed, or when the list is refreshed. + /// An entity type. + public class DataServiceCollection : ObservableCollection + { + #region Private fields + /// The BindingObserver associated with the DataServiceCollection + private BindingObserver observer; + + /// Is this a root collection + private bool rootCollection; + + /// The continuation for partial collections. + private DataServiceQueryContinuation continuation; + + /// True if tracking setup was deferred to first Load() call. + private bool trackingOnLoad; + + /// Callback tracked until tracking is enabled. + private Func entityChangedCallback; + + /// Callback tracked until tracking is enabled. + private Func collectionChangedCallback; + + /// Entity set name tracked until tracking is enabled. + private string entitySetName; + + /// + /// The async handle for the current LoadAsync Operation + /// + private IAsyncResult ongoingAsyncOperation; + + #endregion Private fields + + /// Initializes a new instance of the class. + /// Creates a default data service collection, with auto-change tracking enabled as soon as data is loaded into it. + public DataServiceCollection() + : this(null, null, TrackingMode.AutoChangeTracking, null, null, null) + { + } + + /// Initializes a new instance of the class based on query execution. + /// A or LINQ query that returns an object that are used to initialize the collection. + public DataServiceCollection(DataServiceQuerySingle item) + : this(null, item.Query, TrackingMode.AutoChangeTracking, null, null, null) + { + } + + /// Initializes a new instance of the class based on query execution. + /// A or LINQ query that returns an collection of objects that are used to initialize the collection. + public DataServiceCollection(IEnumerable items) + : this(null, items, TrackingMode.AutoChangeTracking, null, null, null) + { + } + + /// Initializes a new instance of the class based on query execution and with the specified tracking mode. + /// A value that indicated whether or not changes made to items in the collection are automatically tracked. + /// A or LINQ query that returns an object that are used to initialize the collection. + public DataServiceCollection(TrackingMode trackingMode, DataServiceQuerySingle item) + : this(null, item.Query, trackingMode, null, null, null) + { + } + + /// Initializes a new instance of the class based on query execution and with the specified tracking mode. + /// A or LINQ query that returns an collection of objects that are used to initialize the collection. + /// A value that indicated whether or not changes made to items in the collection are automatically tracked. + public DataServiceCollection(IEnumerable items, TrackingMode trackingMode) + : this(null, items, trackingMode, null, null, null) + { + } + + /// Initializes a new instance of the class that uses the specified . + /// The used to track changes to objects in the collection. + public DataServiceCollection(DataServiceContext context) + : this(context, null, TrackingMode.AutoChangeTracking, null, null, null) + { + } + + /// Initializes a new instance of the class with the supplied change method delegates and that uses the specified . + /// The used to track items in the collection. + /// The entity set of the objects in the collection. + /// A delegate that encapsulates a method that is called when an entity changes. + /// A delegate that encapsulates a method that is called when the collection of entities changes. + public DataServiceCollection( + DataServiceContext context, + string entitySetName, + Func entityChangedCallback, + Func collectionChangedCallback) + : this(context, null, TrackingMode.AutoChangeTracking, entitySetName, entityChangedCallback, collectionChangedCallback) + { + } + + /// Initializes a new instance of the class based on query execution and with the supplied change method delegates. + /// A or LINQ query that returns an collection of objects that are used to initialize the collection. + /// A value that indicated whether or not changes made to items in the collection are automatically tracked. + /// The entity set of the objects in the collection. + /// A delegate that encapsulates a method that is called when an entity changes. + /// A delegate that encapsulates a method that is called when the collection of entities changes. + public DataServiceCollection( + IEnumerable items, + TrackingMode trackingMode, + string entitySetName, + Func entityChangedCallback, + Func collectionChangedCallback) + : this(null, items, trackingMode, entitySetName, entityChangedCallback, collectionChangedCallback) + { + } + + /// Initializes a new instance of the class based on query execution, with the supplied change method delegates, and that uses the supplied . + /// The used to track items in the collection. + /// A or LINQ query that returns an collection of objects that are used to initialize the collection. + /// A value that indicated whether or not changes made to items in the collection are automatically tracked. + /// The entity set of the objects in the collection. + /// A delegate that encapsulates a method that is called when an entity changes. + /// A delegate that encapsulates a method that is called when the collection of entities changes. + public DataServiceCollection( + DataServiceContext context, + IEnumerable items, + TrackingMode trackingMode, + string entitySetName, + Func entityChangedCallback, + Func collectionChangedCallback) + { + if (trackingMode == TrackingMode.AutoChangeTracking) + { + if (context == null) + { + if (items == null) + { + // Enable tracking on first Load/LoadAsync call, when we can obtain a context + this.trackingOnLoad = true; + + // Save off these for when we enable tracking later + this.entitySetName = entitySetName; + this.entityChangedCallback = entityChangedCallback; + this.collectionChangedCallback = collectionChangedCallback; + } + else + { + // This throws if no context can be obtained, no need to check here + context = DataServiceCollection.GetContextFromItems(items); + } + } + + if (!this.trackingOnLoad) + { + if (items != null) + { + DataServiceCollection.ValidateIteratorParameter(items); + } + + this.StartTracking(context, items, entitySetName, entityChangedCallback, collectionChangedCallback); + } + } + else if (items != null) + { + this.Load(items); + } + } + + /// Creates new DataServiceCollection. + /// The materializer + /// associated with the new collection. + /// Enumeration of items to initialize the new DataServiceCollection with. + /// The tracking mode for the new collection. + /// The name of the entity set the elements in the collection belong to. + /// Delegate that gets called when an entity changes. + /// Delegate that gets called when an entity collection changes. + /// This is the internal constructor called from materializer and used inside our projection queries. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800", Justification = "Constructor and debug-only code can't reuse cast.")] + internal DataServiceCollection( + object entityMaterializer, + DataServiceContext context, + IEnumerable items, + TrackingMode trackingMode, + string entitySetName, + Func entityChangedCallback, + Func collectionChangedCallback) + : this( + context != null ? context : ((ODataEntityMaterializer)entityMaterializer).EntityTrackingAdapter.Context, + items, + trackingMode, + entitySetName, + entityChangedCallback, + collectionChangedCallback) + { + Debug.Assert(entityMaterializer != null, "entityMaterializer != null"); + Debug.Assert(((ODataEntityMaterializer)entityMaterializer).EntityTrackingAdapter != null, "EntityTrackingAdapter != null"); + + if (items != null) + { + ((ODataEntityMaterializer)entityMaterializer).PropagateContinuation(items, this); + } + } + + #region Properties + /// A completion event for the , + /// and method. + /// This event is raised exactly once for each call to the , + /// or method. It is called both when the operation + /// succeeded and/or when it failed. + public event EventHandler LoadCompleted; + + /// Gets a continuation object that is used to return the next set of paged results. + /// A object that contains the URI to return the next set of paged results. + public DataServiceQueryContinuation Continuation + { + get { return this.continuation; } + set { this.continuation = value; } + } + + /// Observer for the collection. + /// The setter would get called only for child collections in the graph. + internal BindingObserver Observer + { + get + { + return this.observer; + } + + set + { + Debug.Assert(typeof(System.ComponentModel.INotifyPropertyChanged).IsAssignableFrom(typeof(T)), "The entity type must be trackable (by implementing INotifyPropertyChanged interface)"); + this.observer = value; + } + } + + /// + /// Whether this collection is actively tracking + /// + internal bool IsTracking + { + get { return this.observer != null; } + } + #endregion Properties + + /// Loads a collection of entity objects into the collection. + /// Collection of entity objects to be added to the . + /// + /// When tracking is enabled, the behavior of Load would be to attach all those entities that are not already tracked by the context + /// associated with the collection. The operation will go deep into the input entities so that all related + /// entities are attached to the context if not already present. All entities in + /// will be tracked after Load is done. + /// Load method checks for duplication. The collection will ignore any duplicated items been loaded. + /// For large amount of items, consider DataServiceContext.LoadProperty instead. + /// + public void Load(IEnumerable items) + { + DataServiceCollection.ValidateIteratorParameter(items); + + if (this.trackingOnLoad) + { + // This throws if no context can be obtained, no need to check here + DataServiceContext context = DataServiceCollection.GetContextFromItems(items); + + this.trackingOnLoad = false; + + this.StartTracking(context, items, this.entitySetName, this.entityChangedCallback, this.collectionChangedCallback); + } + else + { + this.StartLoading(); + try + { + this.InternalLoadCollection(items); + } + finally + { + this.FinishLoading(); + } + } + } + + /// Asynchronously loads the collection by executing a .Supported only by the WCF Data Services 5.0 client for Silverlight. + /// The that, when executed, returns the entities to load into the collection. + /// When query is null or not a . + /// When a previous call to is not yet complete. + /// This method uses the event-based async pattern. + /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the + /// event exactly once on the UI thread. The event will be raised regardless + /// if the query succeeded or not. + /// This class only support one asynchronous operation in flight. + public void LoadAsync(System.Linq.IQueryable query) + { + Util.CheckArgumentNull(query, "query"); + DataServiceQuery dsq = query as DataServiceQuery; + if (dsq == null) + { + throw new ArgumentException(Strings.DataServiceCollection_LoadAsyncRequiresDataServiceQuery, "query"); + } + + if (this.ongoingAsyncOperation != null) + { + throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); + } + + if (this.trackingOnLoad) + { + this.StartTracking( + ((DataServiceQueryProvider)dsq.Provider).Context, + null, + this.entitySetName, + this.entityChangedCallback, + this.collectionChangedCallback); + this.trackingOnLoad = false; + } + + this.BeginLoadAsyncOperation( + asyncCallback => dsq.BeginExecute(asyncCallback, null), + asyncResult => + { + QueryOperationResponse response = (QueryOperationResponse)dsq.EndExecute(asyncResult); + this.Load(response); + return response; + }); + } + + /// Loads the collection asynchronously by loading the results from the request Uri. + /// The request uri to download results from. + /// This method uses the event-based async pattern. + /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the + /// event exactly once on the UI thread. The event will be raised regradless + /// if the query succeeded or not. + /// This class only support one asynchronous operation in flight. + public void LoadAsync(Uri requestUri) + { + Util.CheckArgumentNull(requestUri, "requestUri"); + + if (!this.IsTracking) + { + throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly); + } + + if (this.ongoingAsyncOperation != null) + { + throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); + } + + DataServiceContext context = this.observer.Context; + requestUri = UriUtil.CreateUri(context.BaseUri, requestUri); + + this.BeginLoadAsyncOperation( + asyncCallback => context.BeginExecute(requestUri, asyncCallback, null), + asyncResult => + { + QueryOperationResponse response = (QueryOperationResponse)context.EndExecute(asyncResult); + this.Load(response); + return response; + }); + } + + /// Asynchronously loads items into the collection, when it represents the navigation property of an entity.Supported only by the WCF Data Services 5.0 client for Silverlight. + /// When the collection does not belong to a parent entity.-or-When the parent entity is not tracked by the .-or-When a previous call to is not yet complete. + /// This method loads the content of a property represented by this DataServiceCollection. + /// If this instance is not associated with any property and entity the method will fail. + /// This method uses the event-based async pattern. + /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the + /// event exactly once on the UI thread. The event will be raised regradless + /// if the query succeeded or not. + /// This class only support one asynchronous operation in flight. + public void LoadAsync() + { + if (!this.IsTracking) + { + throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly); + } + + object parent; + string property; + if (!this.observer.LookupParent(this, out parent, out property)) + { + throw new InvalidOperationException(Strings.DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity); + } + + if (this.ongoingAsyncOperation != null) + { + throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); + } + + this.BeginLoadAsyncOperation( + asyncCallback => this.observer.Context.BeginLoadProperty(parent, property, asyncCallback, null), + asyncResult => (QueryOperationResponse)this.observer.Context.EndLoadProperty(asyncResult)); + } + + /// Loads the next page of data into the collection.Supported only by the WCF Data Services 5.0 client for Silverlight. + /// A value that is true when the Microsoft.OData.Client.DataServiceCollection has a continuation token; otherwise false. + /// This method is the same as except that it runs the query as defined + /// by the continuation token of this collection. + /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the + /// event exactly once on the UI thread. The event will be raised regradless + /// if the query succeeded or not. Even if the method returns false, the event will be raised (immeditaly) + /// This class only support one asynchronous operation in flight. + /// If this collection doesn't have a continuation token (this.Continuation == null) then this method + /// returns false and does not issue any request. + /// If there is a continuation token the method will return true and will start a request to load + /// the next partial set based on that continuation token. + public bool LoadNextPartialSetAsync() + { + if (!this.IsTracking) + { + throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly); + } + + if (this.ongoingAsyncOperation != null) + { + throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); + } + + if (this.Continuation == null) + { + if (this.LoadCompleted != null) + { + this.LoadCompleted(this, new LoadCompletedEventArgs(null, null)); + } + + return false; + } + + this.BeginLoadAsyncOperation( + asyncCallback => this.observer.Context.BeginExecute(this.Continuation, asyncCallback, null), + asyncResult => + { + QueryOperationResponse response = (QueryOperationResponse)this.observer.Context.EndExecute(asyncResult); + this.Load(response); + return response; + }); + + return true; + } + + /// Cancels any running LoadAsync operations and calls the LoadCompleted event handler after cancellation. + public void CancelAsyncLoad() + { + if (this.ongoingAsyncOperation != null) + { + this.observer.Context.CancelRequest(this.ongoingAsyncOperation); + } + } + + /// Loads a single entity object into the collection. + /// Entity object to be added. + /// + /// When tracking is enabled, the behavior of Load would be to attach the entity if it is not already tracked by the context + /// associated with the collection. The operation will go deep into the input entity so that all related + /// entities are attached to the context if not already present. The will be + /// tracked after Load is done. + /// Load method checks for duplication. The collection will ignore any duplicated items been loaded. + /// + public void Load(T item) + { + // When loading a single item, + if (item == null) + { + throw Error.ArgumentNull("item"); + } + + this.StartLoading(); + try + { + if (!this.Contains(item)) + { + this.Add(item); + } + } + finally + { + this.FinishLoading(); + } + } + + /// Indicates whether all the items from the collection are removed. + /// true if all the items from the collection are removed; otherwise, false. + public void Clear(bool stopTracking) + { + if (!this.IsTracking) + { + throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly); + } + + if (!stopTracking) + { + // non-binding or just clear + this.Clear(); + } + else + { + Debug.Assert(this.observer.Context != null, "Must have valid context when the collection is being observed."); + try + { + this.observer.DetachBehavior = true; + this.Clear(); + } + finally + { + this.observer.DetachBehavior = false; + } + } + } + + /// Disables the tracking of all items in the collection. + /// + /// All the entitities in the root collection and all it's related objects will be untracked at the + /// end of this operation. + /// + public void Detach() + { + if (!this.IsTracking) + { + throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly); + } + + // Operation only allowed on root collections. + if (!this.rootCollection) + { + throw new InvalidOperationException(Strings.DataServiceCollection_CannotStopTrackingChildCollection); + } + + this.observer.StopTracking(); + this.observer = null; + + this.rootCollection = false; + } + + /// Adds a specified item to the collection at the specified index. + /// Index at which to add the item. + /// The item to add. + /// + /// Override to prevent additions to the collection in "deferred tracking" mode, and to verify that the item implements INotifyPropertyChanged. + /// Overridding this method will cover items that are added to the collection via Add and Insert. + /// + protected override void InsertItem(int index, T item) + { + if (this.trackingOnLoad) + { + throw new InvalidOperationException(Strings.DataServiceCollection_InsertIntoTrackedButNotLoadedCollection); + } + + if (this.IsTracking && item != null) + { + INotifyPropertyChanged notify = item as INotifyPropertyChanged; + if (notify == null) + { + throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(item.GetType())); + } + } + + base.InsertItem(index, item); + } + + /// + /// Verifies that input iterator parameter is not null and in case + /// of Silverlight, it is not of DataServiceQuery type. + /// + /// Input iterator parameter. + private static void ValidateIteratorParameter(IEnumerable items) + { + Util.CheckArgumentNull(items, "items"); +#if PORTABLELIB + DataServiceQuery dsq = items as DataServiceQuery; + if (dsq != null) + { + throw new ArgumentException(Strings.DataServiceCollection_DataServiceQueryCanNotBeEnumerated); + } +#endif + } + + /// + /// Obtain the DataServiceContext from the incoming enumerable + /// + /// An IEnumerable that may be a DataServiceQuery or QueryOperationResponse object + /// DataServiceContext instance associated with the input + private static DataServiceContext GetContextFromItems(IEnumerable items) + { + Debug.Assert(items != null, "items != null"); + + DataServiceQuery dataServiceQuery = items as DataServiceQuery; + if (dataServiceQuery != null) + { + DataServiceQueryProvider queryProvider = dataServiceQuery.Provider as DataServiceQueryProvider; + Debug.Assert(queryProvider != null, "Got DataServiceQuery with unknown query provider."); + DataServiceContext context = queryProvider.Context; + Debug.Assert(context != null, "Query provider must always have valid context."); + return context; + } + + QueryOperationResponse queryOperationResponse = items as QueryOperationResponse; + if (queryOperationResponse != null) + { + Debug.Assert(queryOperationResponse.Results != null, "Got QueryOperationResponse without valid results."); + DataServiceContext context = queryOperationResponse.Results.Context; + Debug.Assert(context != null, "Materializer must always have valid context."); + return context; + } + + throw new ArgumentException(Strings.DataServiceCollection_CannotDetermineContextFromItems); + } + + /// + /// Populate this collection with another collection of items + /// + /// The items to populate this collection with + private void InternalLoadCollection(IEnumerable items) + { + Debug.Assert(items != null, "items != null"); +#if !PORTABLELIB + // For SDP, we must execute the Query implicitly + DataServiceQuery query = items as DataServiceQuery; + if (query != null) + { + items = query.Execute() as QueryOperationResponse; + } +#else + Debug.Assert(!(items is DataServiceQuery), "SL Client using DSQ as items...should have been caught by ValidateIteratorParameter."); +#endif + + foreach (T item in items) + { + // if this is too slow, consider hashing the set + // or just use LoadProperties + if (!this.Contains(item)) + { + this.Add(item); + } + } + + QueryOperationResponse response = items as QueryOperationResponse; + if (response != null) + { + // this should never be throwing (since we've enumerated already)! + // Note: Inner collection's nextPartLinkUri is set by the materializer + this.continuation = response.GetContinuation(); + } + else + { + this.continuation = null; + } + } + + /// + /// Prepare the collection for loading. For tracked collections, we enter the attaching state + /// + private void StartLoading() + { + if (this.IsTracking) + { + // Observer must be present on the target collection which implies that the operation would fail on default constructed objects. + if (this.observer.Context == null) + { + throw new InvalidOperationException(Strings.DataServiceCollection_LoadRequiresTargetCollectionObserved); + } + + this.observer.AttachBehavior = true; + } + } + + /// + /// Reset the collection after loading. For tracked collections, we exit the attaching state. + /// + private void FinishLoading() + { + if (this.IsTracking) + { + this.observer.AttachBehavior = false; + } + } + + /// Initialize and start tracking an DataServiceCollection + /// The context + /// Collection to initialize with + /// The entity set of the elements in the collection. + /// delegate that needs to be called when an entity changes. + /// delegate that needs to be called when an entity collection is changed. + private void StartTracking( + DataServiceContext context, + IEnumerable items, + String entitySet, + Func entityChanged, + Func collectionChanged) + { + Debug.Assert(context != null, "Must have a valid context to initialize."); + Debug.Assert(this.observer == null, "Must have no observer which implies Initialize should only be called once."); + + context.UsingDataServiceCollection = true; + + // Verify that T corresponds to an entity type. + // Validate here before any items are added to the collection because if this fails we want to prevent the collection from being populated. + if (!BindingEntityInfo.IsEntityType(typeof(T), context.Model)) + { + throw new ArgumentException(Strings.DataBinding_DataServiceCollectionArgumentMustHaveEntityType(typeof(T))); + } + + // Set up the observer first because we want the collection to know it's supposed to be tracked while the items are being loaded. + // Items being added to the collection are not added to the binding graph until later when StartTracking is called on the observer. + this.observer = new BindingObserver(context, entityChanged, collectionChanged); + + // Add everything from the input collection. + if (items != null) + { + try + { + this.InternalLoadCollection(items); + } + catch + { + // If any failures occur, reset the observer since we couldn't successfully start tracking + this.observer = null; + throw; + } + } + + this.observer.StartTracking(this, entitySet); + + this.rootCollection = true; + } + + /// Helper method to start a LoadAsync operation. + /// Function which calls the Begin method for the load. It should take + /// parameter which should be used as the callback for the Begin call. It should return + /// of the started asynchronous operation (or throw). + /// Function which calls the End method for the load. It should take + /// which represents the asynchronous operation in flight. It should return + /// with the result of the operation (or throw). + /// The method takes care of error handling as well as maintaining the . + /// Note that it does not check the to disallow multiple operations in flight. + /// The method makes sure that the will be called from the UI thread. It makes no assumptions + /// about the calling thread of this method. + /// The method does not process the results of the , it just raises the + /// event as appropriate. If there's some processing to be done for the results it should all be done by the + /// method before it returns. + private void BeginLoadAsyncOperation( + Func beginCall, + Func endCall) + { + Debug.Assert(this.ongoingAsyncOperation == null, "Trying to start a new LoadAsync while another is still in progress. We should have thrown."); + + // NOTE: this is Silverlight-only, use BackgroundWorker instead of Deployment.Current.Dispatcher + // to do this in WinForms/WCF once we decide to add it there as well. + // Note that we must mark the operation as in progress before we actually call Begin + // as the async operation might end immediately inside the Begin call and we have no control + // over the ordering between the End callback thread, the thread Begin is called from + // and the UI thread on which we process the end event. + this.ongoingAsyncOperation = null; + try + { + AsyncCallback endLoadAsyncOperation; + + // If SynchronizationContext.Current is not null, use that to invoke the end call and event firing, otherwise + // just invoke those operations on the callback thread. If we can't automatically get the SynchronizationContext.Current, + // the user can still call SynchronizationContext.SetSynchronizationContext and set a specific context to use, + // or can invoke operations themselves in a specific context in the event handler. + // This is not the common scenario so we are not providing a way to directly set this in our API. + SynchronizationContext syncContext = SynchronizationContext.Current; + if (syncContext == null) + { + endLoadAsyncOperation = (ar) => this.EndLoadAsyncOperation(endCall, ar); + } + else + { + endLoadAsyncOperation = (ar) => syncContext.Post((unused) => this.EndLoadAsyncOperation(endCall, ar), null); + } + + this.ongoingAsyncOperation = beginCall(endLoadAsyncOperation); + } + catch (Exception) + { + this.ongoingAsyncOperation = null; + throw; + } + } + + /// + /// Calls the end method for the LoadAsync operation and fires the LoadCompleted event. + /// + /// End method to complete the asynchronous query execution. + /// IAsyncResult to be passed to . + private void EndLoadAsyncOperation(Func endCall, IAsyncResult asyncResult) + { + try + { + QueryOperationResponse result = endCall(asyncResult); + this.ongoingAsyncOperation = null; + if (this.LoadCompleted != null) + { + this.LoadCompleted(this, new LoadCompletedEventArgs(result, null)); + } + } + catch (Exception ex) + { + if (!CommonUtil.IsCatchableExceptionType(ex)) + { + throw; + } + + this.ongoingAsyncOperation = null; + if (this.LoadCompleted != null) + { + this.LoadCompleted(this, new LoadCompletedEventArgs(null, ex)); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceSaveChangesEventArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceSaveChangesEventArgs.cs new file mode 100644 index 0000000..9aaf488 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/DataServiceSaveChangesEventArgs.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// + /// Encapsulates the arguments for the DataServiceContext ChangesSaved event + /// + internal class SaveChangesEventArgs : EventArgs + { + /// + /// DataServiceContext SaveChanges response + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823", Justification = "No upstream callers.")] + private DataServiceResponse response; + + /// + /// Construct a DataServiceSaveChangesEventArgs object. + /// + /// DataServiceContext SaveChanges response + public SaveChangesEventArgs(DataServiceResponse response) + { + this.response = response; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityChangedParams.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityChangedParams.cs new file mode 100644 index 0000000..66ebeea --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityChangedParams.cs @@ -0,0 +1,110 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// Encapsulates the arguments of a delegate + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Name gets too long with Parameters")] + public sealed class EntityChangedParams + { + #region Fields + + /// Context associated with the BindingObserver. + private readonly DataServiceContext context; + + /// The entity object that has changed. + private readonly object entity; + + /// The property of the entity that has changed. + private readonly string propertyName; + + /// The current value of the target property. + private readonly object propertyValue; + + /// Entity set to which the entity object belongs + private readonly string sourceEntitySet; + + /// Entity set to which the target propertyValue entity belongs + private readonly string targetEntitySet; + + #endregion + + #region Constructor + + /// + /// Construct an EntityChangedParams object. + /// + /// Context to which the entity and propertyValue belong. + /// The entity object that has changed. + /// The property of the target entity object that has changed. + /// The current value of the entity property. + /// Entity set to which the entity object belongs + /// Entity set to which the target propertyValue entity belongs + internal EntityChangedParams( + DataServiceContext context, + object entity, + string propertyName, + object propertyValue, + string sourceEntitySet, + string targetEntitySet) + { + this.context = context; + this.entity = entity; + this.propertyName = propertyName; + this.propertyValue = propertyValue; + this.sourceEntitySet = sourceEntitySet; + this.targetEntitySet = targetEntitySet; + } + + #endregion + + #region Properties + + /// The context that is associated with the entity object that has changed. + /// The context that is tracking the changed object. + public DataServiceContext Context + { + get { return this.context; } + } + + /// The entity object that has changed. + /// The changed object. + public object Entity + { + get { return this.entity; } + } + + /// The name of the property on the entity object that references the target object. + /// The name of the changed property. + public string PropertyName + { + get { return this.propertyName; } + } + + /// The object that is currently referenced by the changed property on the entity object. + /// The current value that references a target entity. + public object PropertyValue + { + get { return this.propertyValue; } + } + + /// The entity set of the source object. + /// An entity set name. + public string SourceEntitySet + { + get { return this.sourceEntitySet; } + } + + /// The entity set to which the target entity object belongs + /// An entity set name. + public string TargetEntitySet + { + get { return this.targetEntitySet; } + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityCollectionChangedParams.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityCollectionChangedParams.cs new file mode 100644 index 0000000..a7fe665 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/EntityCollectionChangedParams.cs @@ -0,0 +1,145 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ +#region Namespaces + using System.Collections; + using System.Collections.Specialized; +#endregion + + /// Encapsulates the arguments of a delegate. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Name gets too long with Parameters")] + public sealed class EntityCollectionChangedParams + { + #region Fields + + /// Context associated with the BindingObserver. + private readonly DataServiceContext context; + + /// + /// The source object that references the target object through a collection navigation property. + /// + private readonly object sourceEntity; + + /// The property of the source object that references the collection that has changed. + private readonly string propertyName; + + /// The entity set of the source object. + private readonly string sourceEntitySet; + + /// The collection that has changed. + private readonly ICollection collection; + + /// The target entity object involved in the change. + private readonly object targetEntity; + + /// The entity set name of the target object. + private readonly string targetEntitySet; + + /// + /// The action that indicates how the collection was changed. The value will be Add or Remove. + /// + private readonly NotifyCollectionChangedAction action; + + #endregion + + #region Constructor + + /// + /// Construct an EntityCollectionChangedParams object. + /// + /// The DataServiceContext associated with the BindingObserver. + /// The source object that references the target object through a collection navigation property. + /// The property of the source object that references the collection that has changed. + /// The entity set of the source object. + /// The collection that has changed. + /// The target entity object involved in the change. + /// The entity set name of the target object. + /// The action that indicates how the collection was changed. The value will be Add or Remove. + internal EntityCollectionChangedParams( + DataServiceContext context, + object sourceEntity, + string propertyName, + string sourceEntitySet, + ICollection collection, + object targetEntity, + string targetEntitySet, + NotifyCollectionChangedAction action) + { + this.context = context; + this.sourceEntity = sourceEntity; + this.propertyName = propertyName; + this.sourceEntitySet = sourceEntitySet; + this.collection = collection; + this.targetEntity = targetEntity; + this.targetEntitySet = targetEntitySet; + this.action = action; + } + + #endregion + + #region Properties + + /// The associated with the that has changed. + /// The context associated with the collection that has changed + public DataServiceContext Context + { + get { return this.context; } + } + + /// The source object that references the target object in the collection by using a navigation property. + /// The source object. + public object SourceEntity + { + get { return this.sourceEntity; } + } + + /// The navigation property on the source object that references the collection that has changed. + /// The navigation property name. + public string PropertyName + { + get { return this.propertyName; } + } + + /// The entity set of the source object. + /// An entity set name. + public string SourceEntitySet + { + get { return this.sourceEntitySet; } + } + + /// The entity object in the collection that has changed. + /// The changed entity object in the collection. + public object TargetEntity + { + get { return this.targetEntity; } + } + + /// The entity set name of the object in the collection. + /// An entity set name. + public string TargetEntitySet + { + get { return this.targetEntitySet; } + } + + /// The that has changed. + /// A reference to the collection that has changed. + public ICollection Collection + { + get { return this.collection; } + } + + /// A value that indicates how the collection was changed. + /// A value that indicates how the collection was changed. + public NotifyCollectionChangedAction Action + { + get { return this.action; } + } + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/LoadCompletedEventArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/LoadCompletedEventArgs.cs new file mode 100644 index 0000000..1c067a6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Binding/LoadCompletedEventArgs.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces. + using System; + using System.ComponentModel; + #endregion Namespaces. + + /// Used as the class for the event.Supported only by the WCF Data Services 5.0 client for Silverlight. + public sealed class LoadCompletedEventArgs : AsyncCompletedEventArgs + { + /// The which represents + /// the response for the Load operation. + /// This field is non-null only when the Load operation was successfull. + /// Otherwise it's null. + private QueryOperationResponse queryOperationResponse; + + /// Constructor + /// The response for the Load operation. null when the operation didn't succeed. + /// which represents the error if the Load operation failed. null if the operation + /// didn't fail. + /// This constructor doesn't allow creation of canceled event args. + internal LoadCompletedEventArgs(QueryOperationResponse queryOperationResponse, Exception error) + : this(queryOperationResponse, error, false) + { + } + + /// Constructor + /// The response for the Load operation. null when the operation didn't succeed. + /// which represents the error if the Load operation failed. null if the operation + /// didn't fail. + /// True, if the LoadAsync operation was cancelled, False otherwise. + /// This constructor doesn't allow creation of canceled event args. + internal LoadCompletedEventArgs( + QueryOperationResponse queryOperationResponse, + Exception error, + bool cancelled) + : base(error, cancelled, null) + { + this.queryOperationResponse = queryOperationResponse; + } + + /// Gets the response to an asynchronous load operation.Supported only by the WCF Data Services 5.0 client for Silverlight. + /// A that represents the response to a load operation. + /// Accessing this property will throw exception if the Load operation failed + /// or it was canceled. + public QueryOperationResponse QueryOperationResponse + { + get + { + this.RaiseExceptionIfNecessary(); + return this.queryOperationResponse; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/BodyOperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BodyOperationParameter.cs new file mode 100644 index 0000000..979c50d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BodyOperationParameter.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Represents a parameter associated with a service action. + public sealed class BodyOperationParameter : OperationParameter + { + /// Instantiates a new BodyOperationParameter + /// The name of the body operation parameter. + /// The value of the body operation parameter. + public BodyOperationParameter(string name, Object value) + : base(name, value) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/Microsoft.OData.Client.NetStandard.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/Microsoft.OData.Client.NetStandard.csproj new file mode 100644 index 0000000..fa197d8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/Microsoft.OData.Client.NetStandard.csproj @@ -0,0 +1,951 @@ + + + + Microsoft.OData.Client + Library + + + .NETPortable + v5.0 + true + $(DefineConstants);ODATA_CLIENT;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + $(AssemblyName).xml + true + Microsoft.OData.Client + {2B869CE6-ECFB-4B14-A5B3-E4169DEAC0EB} + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + 14.0 + 14.0 + true + + + ..\..\..\bin\AnyCPU\Debug\Product\.NETPortable\v5.0\Microsoft.OData.Client.NetStandard\ + + + ..\..\..\bin\AnyCPU\Release\Product\.NETPortable\v5.0\Microsoft.OData.Client.NetStandard\ + + + ..\..\..\bin\AnyCPU\Cover\Product\.NETPortable\v5.0\Microsoft.OData.Client.NetStandard\ + + + + + + {48142D27-C862-4F4F-A781-1A9B72F6CDFA} + Microsoft.Spatial.NetStandard + + + {DB301FA8-1CFA-487D-BA1E-803016E2A85C} + Microsoft.OData.Edm.NetStandard + + + {1A263F63-1859-4310-957B-4F8875019896} + Microsoft.OData.Core.NetStandard + + + + + + Microsoft.OData.Client + true + true + internal + true + Microsoft.OData.Client.TextRes + skip + + + + + + + EdmValueParser.cs + + + EdmValueWriter.cs + + + EdmValueUtils.cs + + + ODataResourceMetadataBuilder.cs + + + ODataUriBuilder.cs + + + Serialization\JsonSharedUtils.cs + + + SimpleLazy.cs + + + Metadata\EdmLibraryExtensions.cs + + + InternalErrorCodesCommon.cs + + + ALinq\UriParser\SyntacticAst\AllToken.cs + + + ALinq\UriParser\SyntacticAst\AnyToken.cs + + + ALinq\UriParser\SyntacticAst\BinaryOperatorToken.cs + + + ALinq\UriParser\SyntacticAst\ComputeToken.cs + + + ALinq\UriParser\SyntacticAst\ComputeExpressionToken.cs + + + ALinq\UriParser\SyntacticAst\CustomQueryOptionToken.cs + + + ALinq\UriParser\SyntacticAst\DottedIdentifierToken.cs + + + ALinq\UriParser\SyntacticAst\EndPathToken.cs + + + ALinq\UriParser\ExceptionUtils.cs + + + ALinq\UriParser\SyntacticAst\ExpandTermToken.cs + + + ALinq\UriParser\SyntacticAst\ExpandToken.cs + + + ALinq\UriParser\SyntacticAst\AggregateToken.cs + + + ALinq\UriParser\SyntacticAst\GroupByToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionCallToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionParameterAliasToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionParameterToken.cs + + + ALinq\UriParser\SyntacticAst\InnerPathToken.cs + + + ALinq\UriParser\SyntacticAst\InToken.cs + + + ALinq\UriParser\Visitors\IPathSegmentTokenVisitor.cs + + + ALinq\UriParser\Visitors\ISyntacticTreeVisitor.cs + + + ALinq\UriParser\Visitors\SyntacticTreeVisitor.cs + + + ALinq\UriParser\SyntacticAst\LambdaToken.cs + + + ALinq\UriParser\SyntacticAst\LiteralToken.cs + + + ALinq\UriParser\NamedValue.cs + + + ALinq\UriParser\SyntacticAst\NonSystemToken.cs + + + ALinq\UriParser\SyntacticAst\OrderByToken.cs + + + ALinq\UriParser\SyntacticAst\PathSegmentToken.cs + + + ALinq\UriParser\SyntacticAst\PathToken.cs + + + ALinq\UriParser\SyntacticAst\QueryToken.cs + + + ALinq\UriParser\SyntacticAst\QueryTokenKind.cs + + + ALinq\UriParser\SyntacticAst\RangeVariableToken.cs + + + ALinq\UriParser\SyntacticAst\SelectExpandTermToken.cs + + + ALinq\UriParser\SyntacticAst\SelectTermToken.cs + + + ALinq\UriParser\ReadOnlyEnumerableForUriParser.cs + + + ALinq\UriParser\SyntacticAst\SelectToken.cs + + + ALinq\UriParser\SyntacticAst\StarToken.cs + + + ALinq\UriParser\SyntacticAst\SystemToken.cs + + + ALinq\UriParser\SyntacticAst\UnaryOperatorToken.cs + + + GlobalSuppressions.cs + + + DataServiceActionQueryOfT.cs + + + ActionDescriptor.cs + + + DataServiceActionQuerySingleOfT.cs + + + DataServiceActionQuery.cs + + + ALinq\QueryableResourceExpression.cs + + + ALinq\ResourceSetExpression.cs + + + ALinq\SingletonResourceExpression.cs + + + ALinq\ExpandOnlyPathToStringVisitor.cs + + + ALinq\SelectExpandPathToStringVisitor.cs + + + ALinq\AddNewEndingTokenVisitor.cs + + + ALinq\NewTreeBuilder.cs + + + ALinq\RemoveWildcardVisitor.cs + + + ALinq\SelectExpandPathBuilder.cs + + + Attribute\OriginalNameAttribute.cs + + + BaseEntityType.cs + + + BaseSaveResult.cs + + + BatchSaveResult.cs + + + BodyOperationParameter.cs + + + BuildingRequestEventArgs.cs + + + ClientEdmCollectionValue.cs + + + ClientEdmModel.cs + + + ClientEdmStructuredValue.cs + + + Common.cs + + + CommonUtil.cs + + + ContentStream.cs + + + ContentTypeUtil.cs + + + ConventionalODataEntityMetadataBuilder.cs + + + DataServiceQuerySingleOfT.cs + + + Materialization\EnumValueMaterializationPolicy.cs + + + Materialization\ODataLoadNavigationPropertyMaterializer.cs + + + DataServiceClientRequestMessageArgs.cs + + + HttpStack.cs + + + EntityParameterSendOption.cs + + + MessageReaderSettingsArgs.cs + + + MessageWriterSettingsArgs.cs + + + DataServiceClientConfigurations.cs + + + Materialization\CollectionValueMaterializationPolicy.cs + + + Materialization\EntityTrackingAdapter.cs + + + Materialization\EntryValueMaterializationPolicy.cs + + + Materialization\MaterializationPolicy.cs + + + Materialization\ODataReaderWrapper.cs + + + Materialization\PrimitivePropertyConverter.cs + + + Materialization\PrimitiveValueMaterializationPolicy.cs + + + Materialization\StructuralValueMaterializationPolicy.cs + + + MaterializedEntityArgs.cs + + + ODataAnnotatableExtensions.cs + + + Serialization\DataServiceClientRequestMessage.cs + + + Utility.cs + + + WritingEntityReferenceLinkArgs.cs + + + WritingEntryArgs.cs + + + WritingNestedResourceInfoArgs.cs + + + DataServiceClientRequestPipelineConfiguration.cs + + + ReadingNestedResourceInfoArgs.cs + + + ReadingFeedArgs.cs + + + EntityTrackerBase.cs + + + Materialization\ODataMaterializerContext.cs + + + Materialization\IODataMaterializerContext.cs + + + ReadingEntryArgs.cs + + + DataServiceClientResponsePipelineConfiguration.cs + + + Materialization\FeedAndEntryMaterializerAdapter.cs + + + DictionaryExtensions.cs + + + DataServiceClientFormat.cs + + + DataServiceQueryContinuation.cs + + + DataServiceResponsePreference.cs + + + DataServiceSaveStream.cs + + + DataServiceStreamLink.cs + + + DataServiceWebException.cs + + + DynamicProxyMethodGenerator.cs + + + EntityTracker.cs + + + FunctionDescriptor.cs + + + InternalODataRequestMessage.cs + + + InvokeResponse.cs + + + LinkInfo.cs + + + LoadPropertyResult.cs + + + Materialization\ODataCollectionMaterializer.cs + + + Materialization\ODataEntriesEntityMaterializer.cs + + + Materialization\ODataEntityMaterializer.cs + + + Materialization\ODataEntityMaterializerInvoker.cs + + + Materialization\ODataItemExtensions.cs + + + Materialization\ODataLinksMaterializer.cs + + + Materialization\ODataMaterializer.cs + + + Materialization\ODataMessageReaderMaterializer.cs + + + Materialization\ODataPropertyMaterializer.cs + + + Materialization\ODataReaderEntityMaterializer.cs + + + Materialization\ODataValueMaterializer.cs + + + MemberAssignmentAnalysis.cs + + + ODataMessageReadingHelper.cs + + + ODataMessageWritingHelper.cs + + + ODataRequestMessageWrapper.cs + + + OperationDescriptor.cs + + + OperationParameter.cs + + + ProjectionPath.cs + + + ProjectionPathBuilder.cs + + + ProjectionPathSegment.cs + + + ProjectionPlan.cs + + + ProjectionPlanCompiler.cs + + + ReceivingResponseEventArgs.cs + + + HeaderCollection.cs + + + RequestInfo.cs + + + ResponseInfo.cs + + + SendingRequest2EventArgs.cs + + + Serialization\DataStringEscapeBuilder.cs + + + Serialization\ODataWriterWrapper.cs + + + Serialization\HttpWebRequestMessage.cs + + + Materialization\HttpWebResponseMessage.cs + + + Serialization\ODataPropertyConverter.cs + + + Serialization\PrimitiveXmlConverter.cs + + + Serialization\PrimitiveType.cs + + + Serialization\PrimitiveParserToken.cs + + + ReferenceEqualityComparer.cs + + + true + ALinq\ProjectionRewriter.cs + + + GetReadStreamResult.cs + + + true + ALinq\ProjectionAnalyzer.cs + + + true + ALinq\ProjectionQueryOptionExpression.cs + + + QueryResult.cs + + + ALinq\QueryComponents.cs + + + true + ALinq\ReflectionUtil.cs + + + ALinq\InputReferenceExpression.cs + + + true + ALinq\ResourceExpression.cs + + + ALinq\Evaluator.cs + + + true + ALinq\ExpressionNormalizer.cs + + + ALinq\ALinqExpressionVisitor.cs + + + ALinq\FilterQueryOptionExpression.cs + + + ALinq\InputBinder.cs + + + ALinq\OrderByQueryOptionExpression.cs + + + ALinq\QueryOptionExpression.cs + + + true + ALinq\ResourceBinder.cs + + + ALinq\ResourceExpressionType.cs + + + ALinq\NavigationPropertySingletonExpression.cs + + + ALinq\SkipQueryOptionExpression.cs + + + ALinq\TakeQueryOptionExpression.cs + + + ALinq\TypeSystem.cs + + + ALinq\UriHelper.cs + + + ALinq\ExpressionWriter.cs + + + ALinq\UriWriter.cs + + + ALinq\DataServiceExpressionVisitor.cs + + + ALinq\DataServiceQueryProvider.cs + + + Annotation\AnnotationHelper.cs + + + true + ArraySet.cs + + + AtomMaterializerLog.cs + + + BaseAsyncResult.cs + + + Binding\BindingEntityInfo.cs + + + Binding\BindingGraph.cs + + + Binding\BindingObserver.cs + + + Binding\BindingUtils.cs + + + Binding\DataServiceSaveChangesEventArgs.cs + + + Binding\EntityChangedParams.cs + + + Binding\EntityCollectionChangedParams.cs + + + Binding\DataServiceCollectionOfT.cs + + + Binding\LoadCompletedEventArgs.cs + + + Attribute\EntitySetAttribute.cs + + + Attribute\EntityTypeAttribute.cs + + + Attribute\KeyAttribute.cs + + + ODataProtocolVersion.cs + + + Attribute\HasStreamAttribute.cs + + + Attribute\NamedStreamAttribute.cs + + + DataServiceQueryException.cs + + + DataServiceRequestArgs.cs + + + DataServiceRequestException.cs + + + DataServiceStreamResponse.cs + + + Descriptor.cs + + + LinkDescriptor.cs + + + ChangesetResponse.cs + + + ClientConvert.cs + + + Metadata\ClientTypeAnnotation.cs + + + DataServiceClientException.cs + + + DataServiceQuery.cs + + + DataServiceRequest.cs + + + DataServiceRequestOfT.cs + + + DataServiceResponse.cs + + + OperationResponse.cs + + + QueryOperationResponseOfT.cs + + + QueryOperationResponse.cs + + + true + Error.cs + + + KeySerializer.cs + + + LiteralFormatter.cs + + + true + MaterializeFromAtom.cs + + + Materialization\MaterializerEntry.cs + + + Materialization\MaterializerFeed.cs + + + Materialization\MaterializerNavigationLink.cs + + + Materialization\InstanceAnnotationMaterializationPolicy.cs + + + MediaEntryAttribute.cs + + + MergeOption.cs + + + MimeTypePropertyAttribute.cs + + + EntityDescriptor.cs + + + EntityStates.cs + + + SaveChangesOptions.cs + + + SaveResult.cs + + + SendingRequestEventArgs.cs + + + Serialization\Serializer.cs + + + StreamDescriptor.cs + + + TypeResolver.cs + + + UriEntityOperationParameter.cs + + + UriOperationParameter.cs + + + UriResolver.cs + + + UriUtil.cs + + + DataServiceUrlKeyDelimiter.cs + + + Util.cs + + + DataServiceContext.cs + + + DataServiceQueryOfT.cs + + + WebUtil.cs + + + Metadata\ClientPropertyAnnotation.cs + + + Metadata\ClientTypeUtil.cs + + + Metadata\ClientTypeCache.cs + + + Metadata\EdmComplexTypeWithDelayLoadedProperties.cs + + + Metadata\EdmEntityTypeWithDelayLoadedProperties.cs + + + Metadata\EdmEnumTypeWithDelayLoadedMembers.cs + + + XmlConstants.cs + + + PlatformHelper.cs + + + + + + Client\TaskUtils.cs + + + false + + + false + + + + + + + + + + + + TextTemplatingFileGenerator + Microsoft.OData.Client.Portable.cs + + + ALinq\UriParser\Aggregation\ApplyTransformationToken.cs + + + ALinq\UriParser\Aggregation\EntitySetAggregateExpression.cs + + + ALinq\UriParser\Aggregation\EntitySetAggregateToken.cs + + + ALinq\UriParser\Aggregation\AggregateTokenBase.cs + + + ALinq\UriParser\Aggregation\AggregateExpressionToken.cs + + + Annotation\InstanceAnnotationDictWeakKeyComparer.cs + + + Attribute\IgnoreClientPropertyAttribute.cs + + + WeakDictionary.cs + + + Wrappers\ODataItemWrapper.cs + + + Wrappers\ODataNestedResourceInfoWrapper.cs + + + Wrappers\ODataResourceSetWrapper.cs + + + Wrappers\ODataResourceWrapper.cs + + + Wrappers\ODataWriterHelper.cs + + + True + True + Microsoft.OData.Client.Portable.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Client.Portable.cs + + + True + True + Parameterized.Microsoft.OData.Client.Portable.tt + true + + + + + + + false + + + false + + + $(SuiteBinPath) + + + + + + + + BuildTextFile;$(GenerateTextStringResourcesDependsOn) + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/project.json b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/project.json new file mode 100644 index 0000000..f528972 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NetStandard/project.json @@ -0,0 +1,15 @@ +{ + "supports": {}, + "dependencies": { + "NETStandard.Library": "1.6.0", + "System.ComponentModel": "4.3.0", + "System.ComponentModel.EventBasedAsync": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Linq.Queryable": "4.3.0", + "System.Net.Requests": "4.3.0", + "System.Runtime.Serialization.Primitives": "4.3.0" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Nightly.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Nightly.Release.nuspec new file mode 100644 index 0000000..382fa08 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Nightly.Release.nuspec @@ -0,0 +1,82 @@ + + + + OData Client for .NET + Microsoft.OData.Client + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionFullSemantic$-Nightly$NightlyBuildVersion$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + LINQ-enabled client API for issuing OData queries and consuming OData JSON payloads. Supports OData v4. + LINQ-enabled client API for issuing OData queries and consuming OData JSON payloads. Supports OData v4. Targets .NET 4.5 and .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Release.nuspec new file mode 100644 index 0000000..85eaecf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.NuGet/Microsoft.OData.Client.Release.nuspec @@ -0,0 +1,87 @@ + + + + OData Client for .NET + Microsoft.OData.Client + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionNuGetSemantic$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + LINQ-enabled client API for issuing OData queries and consuming OData JSON payloads. Supports OData v4. + LINQ-enabled client API for issuing OData queries and consuming OData JSON payloads. Supports OData v4. Targets .NET 4.5 and .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + http://odata.github.io/odata.net/v7/#ODL-$VersionFullSemantic$ + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.cs new file mode 100644 index 0000000..eed52eb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.cs @@ -0,0 +1,344 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client { + using System; + using System.Globalization; + using System.Reflection; + using System.Resources; +#if !PORTABLELIB + using System.Security.Permissions; +#endif + using System.Text; + using System.Threading; + + /// + /// AutoGenerated resource class. Usage: + /// + /// string s = TextRes.GetString(TextRes.MyIdenfitier); + /// + internal sealed class TextRes { + internal const string Batch_ExpectedContentType = "Batch_ExpectedContentType"; + internal const string Batch_ExpectedResponse = "Batch_ExpectedResponse"; + internal const string Batch_IncompleteResponseCount = "Batch_IncompleteResponseCount"; + internal const string Batch_UnexpectedContent = "Batch_UnexpectedContent"; + internal const string Context_BaseUri = "Context_BaseUri"; + internal const string Context_BaseUriRequired = "Context_BaseUriRequired"; + internal const string Context_ResolveReturnedInvalidUri = "Context_ResolveReturnedInvalidUri"; + internal const string Context_RequestUriIsRelativeBaseUriRequired = "Context_RequestUriIsRelativeBaseUriRequired"; + internal const string Context_ResolveEntitySetOrBaseUriRequired = "Context_ResolveEntitySetOrBaseUriRequired"; + internal const string Context_CannotConvertKey = "Context_CannotConvertKey"; + internal const string Context_TrackingExpectsAbsoluteUri = "Context_TrackingExpectsAbsoluteUri"; + internal const string Context_LocationHeaderExpectsAbsoluteUri = "Context_LocationHeaderExpectsAbsoluteUri"; + internal const string Context_LinkResourceInsertFailure = "Context_LinkResourceInsertFailure"; + internal const string Context_InternalError = "Context_InternalError"; + internal const string Context_BatchExecuteError = "Context_BatchExecuteError"; + internal const string Context_EntitySetName = "Context_EntitySetName"; + internal const string Context_BatchNotSupportedForNamedStreams = "Context_BatchNotSupportedForNamedStreams"; + internal const string Context_SetSaveStreamWithoutNamedStreamEditLink = "Context_SetSaveStreamWithoutNamedStreamEditLink"; + internal const string Content_EntityWithoutKey = "Content_EntityWithoutKey"; + internal const string Content_EntityIsNotEntityType = "Content_EntityIsNotEntityType"; + internal const string Context_EntityNotContained = "Context_EntityNotContained"; + internal const string Context_EntityAlreadyContained = "Context_EntityAlreadyContained"; + internal const string Context_DifferentEntityAlreadyContained = "Context_DifferentEntityAlreadyContained"; + internal const string Context_DidNotOriginateAsync = "Context_DidNotOriginateAsync"; + internal const string Context_AsyncAlreadyDone = "Context_AsyncAlreadyDone"; + internal const string Context_OperationCanceled = "Context_OperationCanceled"; + internal const string Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX = "Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX"; + internal const string Context_NoLoadWithInsertEnd = "Context_NoLoadWithInsertEnd"; + internal const string Context_NoRelationWithInsertEnd = "Context_NoRelationWithInsertEnd"; + internal const string Context_NoRelationWithDeleteEnd = "Context_NoRelationWithDeleteEnd"; + internal const string Context_RelationAlreadyContained = "Context_RelationAlreadyContained"; + internal const string Context_RelationNotRefOrCollection = "Context_RelationNotRefOrCollection"; + internal const string Context_AddLinkCollectionOnly = "Context_AddLinkCollectionOnly"; + internal const string Context_AddRelatedObjectCollectionOnly = "Context_AddRelatedObjectCollectionOnly"; + internal const string Context_AddRelatedObjectSourceDeleted = "Context_AddRelatedObjectSourceDeleted"; + internal const string Context_UpdateRelatedObjectNonCollectionOnly = "Context_UpdateRelatedObjectNonCollectionOnly"; + internal const string Context_SetLinkReferenceOnly = "Context_SetLinkReferenceOnly"; + internal const string Context_NoContentTypeForMediaLink = "Context_NoContentTypeForMediaLink"; + internal const string Context_BatchNotSupportedForMediaLink = "Context_BatchNotSupportedForMediaLink"; + internal const string Context_UnexpectedZeroRawRead = "Context_UnexpectedZeroRawRead"; + internal const string Context_VersionNotSupported = "Context_VersionNotSupported"; + internal const string Context_ResponseVersionIsBiggerThanProtocolVersion = "Context_ResponseVersionIsBiggerThanProtocolVersion"; + internal const string Context_RequestVersionIsBiggerThanProtocolVersion = "Context_RequestVersionIsBiggerThanProtocolVersion"; + internal const string Context_ChildResourceExists = "Context_ChildResourceExists"; + internal const string Context_ContentTypeRequiredForNamedStream = "Context_ContentTypeRequiredForNamedStream"; + internal const string Context_EntityNotMediaLinkEntry = "Context_EntityNotMediaLinkEntry"; + internal const string Context_MLEWithoutSaveStream = "Context_MLEWithoutSaveStream"; + internal const string Context_SetSaveStreamOnMediaEntryProperty = "Context_SetSaveStreamOnMediaEntryProperty"; + internal const string Context_SetSaveStreamWithoutEditMediaLink = "Context_SetSaveStreamWithoutEditMediaLink"; + internal const string Context_SetSaveStreamOnInvalidEntityState = "Context_SetSaveStreamOnInvalidEntityState"; + internal const string Context_EntityDoesNotContainNamedStream = "Context_EntityDoesNotContainNamedStream"; + internal const string Context_MissingSelfAndEditLinkForNamedStream = "Context_MissingSelfAndEditLinkForNamedStream"; + internal const string Context_BothLocationAndIdMustBeSpecified = "Context_BothLocationAndIdMustBeSpecified"; + internal const string Context_BodyOperationParametersNotAllowedWithGet = "Context_BodyOperationParametersNotAllowedWithGet"; + internal const string Context_MissingOperationParameterName = "Context_MissingOperationParameterName"; + internal const string Context_DuplicateUriOperationParameterName = "Context_DuplicateUriOperationParameterName"; + internal const string Context_DuplicateBodyOperationParameterName = "Context_DuplicateBodyOperationParameterName"; + internal const string Context_NullKeysAreNotSupported = "Context_NullKeysAreNotSupported"; + internal const string Context_ExecuteExpectsGetOrPostOrDelete = "Context_ExecuteExpectsGetOrPostOrDelete"; + internal const string Context_EndExecuteExpectedVoidResponse = "Context_EndExecuteExpectedVoidResponse"; + internal const string Context_NullElementInOperationParameterArray = "Context_NullElementInOperationParameterArray"; + internal const string Context_EntityMetadataBuilderIsRequired = "Context_EntityMetadataBuilderIsRequired"; + internal const string Context_CannotChangeStateToAdded = "Context_CannotChangeStateToAdded"; + internal const string Context_CannotChangeStateToModifiedIfNotUnchanged = "Context_CannotChangeStateToModifiedIfNotUnchanged"; + internal const string Context_CannotChangeStateIfAdded = "Context_CannotChangeStateIfAdded"; + internal const string Context_OnMessageCreatingReturningNull = "Context_OnMessageCreatingReturningNull"; + internal const string Context_SendingRequest_InvalidWhenUsingOnMessageCreating = "Context_SendingRequest_InvalidWhenUsingOnMessageCreating"; + internal const string Context_MustBeUsedWith = "Context_MustBeUsedWith"; + internal const string DataServiceClientFormat_LoadServiceModelRequired = "DataServiceClientFormat_LoadServiceModelRequired"; + internal const string DataServiceClientFormat_ValidServiceModelRequiredForJson = "DataServiceClientFormat_ValidServiceModelRequiredForJson"; + internal const string Collection_NullCollectionReference = "Collection_NullCollectionReference"; + internal const string ClientType_MissingOpenProperty = "ClientType_MissingOpenProperty"; + internal const string Clienttype_MultipleOpenProperty = "Clienttype_MultipleOpenProperty"; + internal const string ClientType_MissingProperty = "ClientType_MissingProperty"; + internal const string ClientType_KeysMustBeSimpleTypes = "ClientType_KeysMustBeSimpleTypes"; + internal const string ClientType_KeysOnDifferentDeclaredType = "ClientType_KeysOnDifferentDeclaredType"; + internal const string ClientType_MissingMimeTypeProperty = "ClientType_MissingMimeTypeProperty"; + internal const string ClientType_MissingMimeTypeDataProperty = "ClientType_MissingMimeTypeDataProperty"; + internal const string ClientType_MissingMediaEntryProperty = "ClientType_MissingMediaEntryProperty"; + internal const string ClientType_NoSettableFields = "ClientType_NoSettableFields"; + internal const string ClientType_MultipleImplementationNotSupported = "ClientType_MultipleImplementationNotSupported"; + internal const string ClientType_NullOpenProperties = "ClientType_NullOpenProperties"; + internal const string ClientType_Ambiguous = "ClientType_Ambiguous"; + internal const string ClientType_UnsupportedType = "ClientType_UnsupportedType"; + internal const string ClientType_CollectionOfCollectionNotSupported = "ClientType_CollectionOfCollectionNotSupported"; + internal const string ClientType_MultipleTypesWithSameName = "ClientType_MultipleTypesWithSameName"; + internal const string WebUtil_TypeMismatchInCollection = "WebUtil_TypeMismatchInCollection"; + internal const string WebUtil_TypeMismatchInNonPropertyCollection = "WebUtil_TypeMismatchInNonPropertyCollection"; + internal const string ClientTypeCache_NonEntityTypeCannotContainEntityProperties = "ClientTypeCache_NonEntityTypeCannotContainEntityProperties"; + internal const string DataServiceException_GeneralError = "DataServiceException_GeneralError"; + internal const string Deserialize_GetEnumerator = "Deserialize_GetEnumerator"; + internal const string Deserialize_Current = "Deserialize_Current"; + internal const string Deserialize_MixedTextWithComment = "Deserialize_MixedTextWithComment"; + internal const string Deserialize_ExpectingSimpleValue = "Deserialize_ExpectingSimpleValue"; + internal const string Deserialize_MismatchAtomLinkLocalSimple = "Deserialize_MismatchAtomLinkLocalSimple"; + internal const string Deserialize_MismatchAtomLinkFeedPropertyNotCollection = "Deserialize_MismatchAtomLinkFeedPropertyNotCollection"; + internal const string Deserialize_MismatchAtomLinkEntryPropertyIsCollection = "Deserialize_MismatchAtomLinkEntryPropertyIsCollection"; + internal const string Deserialize_NoLocationHeader = "Deserialize_NoLocationHeader"; + internal const string Deserialize_ServerException = "Deserialize_ServerException"; + internal const string Deserialize_MissingIdElement = "Deserialize_MissingIdElement"; + internal const string Collection_NullCollectionNotSupported = "Collection_NullCollectionNotSupported"; + internal const string Collection_NullNonPropertyCollectionNotSupported = "Collection_NullNonPropertyCollectionNotSupported"; + internal const string Collection_NullCollectionItemsNotSupported = "Collection_NullCollectionItemsNotSupported"; + internal const string Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed = "Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed"; + internal const string Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed = "Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed"; + internal const string EntityDescriptor_MissingSelfEditLink = "EntityDescriptor_MissingSelfEditLink"; + internal const string HttpProcessUtility_ContentTypeMissing = "HttpProcessUtility_ContentTypeMissing"; + internal const string HttpProcessUtility_MediaTypeMissingValue = "HttpProcessUtility_MediaTypeMissingValue"; + internal const string HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter = "HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter"; + internal const string HttpProcessUtility_MediaTypeRequiresSlash = "HttpProcessUtility_MediaTypeRequiresSlash"; + internal const string HttpProcessUtility_MediaTypeRequiresSubType = "HttpProcessUtility_MediaTypeRequiresSubType"; + internal const string HttpProcessUtility_MediaTypeUnspecified = "HttpProcessUtility_MediaTypeUnspecified"; + internal const string HttpProcessUtility_EncodingNotSupported = "HttpProcessUtility_EncodingNotSupported"; + internal const string HttpProcessUtility_EscapeCharWithoutQuotes = "HttpProcessUtility_EscapeCharWithoutQuotes"; + internal const string HttpProcessUtility_EscapeCharAtEnd = "HttpProcessUtility_EscapeCharAtEnd"; + internal const string HttpProcessUtility_ClosingQuoteNotFound = "HttpProcessUtility_ClosingQuoteNotFound"; + internal const string MaterializeFromAtom_CountNotPresent = "MaterializeFromAtom_CountNotPresent"; + internal const string MaterializeFromAtom_TopLevelLinkNotAvailable = "MaterializeFromAtom_TopLevelLinkNotAvailable"; + internal const string MaterializeFromAtom_CollectionKeyNotPresentInLinkTable = "MaterializeFromAtom_CollectionKeyNotPresentInLinkTable"; + internal const string MaterializeFromAtom_GetNestLinkForFlatCollection = "MaterializeFromAtom_GetNestLinkForFlatCollection"; + internal const string ODataRequestMessage_GetStreamMethodNotSupported = "ODataRequestMessage_GetStreamMethodNotSupported"; + internal const string Util_EmptyString = "Util_EmptyString"; + internal const string Util_EmptyArray = "Util_EmptyArray"; + internal const string Util_NullArrayElement = "Util_NullArrayElement"; + internal const string ALinq_UnsupportedExpression = "ALinq_UnsupportedExpression"; + internal const string ALinq_CouldNotConvert = "ALinq_CouldNotConvert"; + internal const string ALinq_MethodNotSupported = "ALinq_MethodNotSupported"; + internal const string ALinq_UnaryNotSupported = "ALinq_UnaryNotSupported"; + internal const string ALinq_BinaryNotSupported = "ALinq_BinaryNotSupported"; + internal const string ALinq_ConstantNotSupported = "ALinq_ConstantNotSupported"; + internal const string ALinq_TypeBinaryNotSupported = "ALinq_TypeBinaryNotSupported"; + internal const string ALinq_ConditionalNotSupported = "ALinq_ConditionalNotSupported"; + internal const string ALinq_ParameterNotSupported = "ALinq_ParameterNotSupported"; + internal const string ALinq_MemberAccessNotSupported = "ALinq_MemberAccessNotSupported"; + internal const string ALinq_LambdaNotSupported = "ALinq_LambdaNotSupported"; + internal const string ALinq_NewNotSupported = "ALinq_NewNotSupported"; + internal const string ALinq_MemberInitNotSupported = "ALinq_MemberInitNotSupported"; + internal const string ALinq_ListInitNotSupported = "ALinq_ListInitNotSupported"; + internal const string ALinq_NewArrayNotSupported = "ALinq_NewArrayNotSupported"; + internal const string ALinq_InvocationNotSupported = "ALinq_InvocationNotSupported"; + internal const string ALinq_QueryOptionsOnlyAllowedOnLeafNodes = "ALinq_QueryOptionsOnlyAllowedOnLeafNodes"; + internal const string ALinq_CantExpand = "ALinq_CantExpand"; + internal const string ALinq_CantCastToUnsupportedPrimitive = "ALinq_CantCastToUnsupportedPrimitive"; + internal const string ALinq_CantNavigateWithoutKeyPredicate = "ALinq_CantNavigateWithoutKeyPredicate"; + internal const string ALinq_CanOnlyApplyOneKeyPredicate = "ALinq_CanOnlyApplyOneKeyPredicate"; + internal const string ALinq_CantTranslateExpression = "ALinq_CantTranslateExpression"; + internal const string ALinq_TranslationError = "ALinq_TranslationError"; + internal const string ALinq_CantAddQueryOption = "ALinq_CantAddQueryOption"; + internal const string ALinq_CantAddDuplicateQueryOption = "ALinq_CantAddDuplicateQueryOption"; + internal const string ALinq_CantAddAstoriaQueryOption = "ALinq_CantAddAstoriaQueryOption"; + internal const string ALinq_CantAddQueryOptionStartingWithDollarSign = "ALinq_CantAddQueryOptionStartingWithDollarSign"; + internal const string ALinq_CantReferToPublicField = "ALinq_CantReferToPublicField"; + internal const string ALinq_QueryOptionsOnlyAllowedOnSingletons = "ALinq_QueryOptionsOnlyAllowedOnSingletons"; + internal const string ALinq_QueryOptionOutOfOrder = "ALinq_QueryOptionOutOfOrder"; + internal const string ALinq_CannotAddCountOption = "ALinq_CannotAddCountOption"; + internal const string ALinq_CannotAddCountOptionConflict = "ALinq_CannotAddCountOptionConflict"; + internal const string ALinq_ProjectionOnlyAllowedOnLeafNodes = "ALinq_ProjectionOnlyAllowedOnLeafNodes"; + internal const string ALinq_ProjectionCanOnlyHaveOneProjection = "ALinq_ProjectionCanOnlyHaveOneProjection"; + internal const string ALinq_ProjectionMemberAssignmentMismatch = "ALinq_ProjectionMemberAssignmentMismatch"; + internal const string ALinq_InvalidExpressionInNavigationPath = "ALinq_InvalidExpressionInNavigationPath"; + internal const string ALinq_ExpressionNotSupportedInProjectionToEntity = "ALinq_ExpressionNotSupportedInProjectionToEntity"; + internal const string ALinq_ExpressionNotSupportedInProjection = "ALinq_ExpressionNotSupportedInProjection"; + internal const string ALinq_CannotConstructKnownEntityTypes = "ALinq_CannotConstructKnownEntityTypes"; + internal const string ALinq_CannotCreateConstantEntity = "ALinq_CannotCreateConstantEntity"; + internal const string ALinq_PropertyNamesMustMatchInProjections = "ALinq_PropertyNamesMustMatchInProjections"; + internal const string ALinq_CanOnlyProjectTheLeaf = "ALinq_CanOnlyProjectTheLeaf"; + internal const string ALinq_CannotProjectWithExplicitExpansion = "ALinq_CannotProjectWithExplicitExpansion"; + internal const string ALinq_CollectionPropertyNotSupportedInOrderBy = "ALinq_CollectionPropertyNotSupportedInOrderBy"; + internal const string ALinq_CollectionPropertyNotSupportedInWhere = "ALinq_CollectionPropertyNotSupportedInWhere"; + internal const string ALinq_CollectionMemberAccessNotSupportedInNavigation = "ALinq_CollectionMemberAccessNotSupportedInNavigation"; + internal const string ALinq_LinkPropertyNotSupportedInExpression = "ALinq_LinkPropertyNotSupportedInExpression"; + internal const string ALinq_OfTypeArgumentNotAvailable = "ALinq_OfTypeArgumentNotAvailable"; + internal const string ALinq_CannotUseTypeFiltersMultipleTimes = "ALinq_CannotUseTypeFiltersMultipleTimes"; + internal const string ALinq_ExpressionCannotEndWithTypeAs = "ALinq_ExpressionCannotEndWithTypeAs"; + internal const string ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3 = "ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3"; + internal const string ALinq_TypeAsArgumentNotEntityType = "ALinq_TypeAsArgumentNotEntityType"; + internal const string ALinq_InvalidSourceForAnyAll = "ALinq_InvalidSourceForAnyAll"; + internal const string ALinq_AnyAllNotSupportedInOrderBy = "ALinq_AnyAllNotSupportedInOrderBy"; + internal const string ALinq_FormatQueryOptionNotSupported = "ALinq_FormatQueryOptionNotSupported"; + internal const string ALinq_IllegalSystemQueryOption = "ALinq_IllegalSystemQueryOption"; + internal const string ALinq_IllegalPathStructure = "ALinq_IllegalPathStructure"; + internal const string ALinq_TypeTokenWithNoTrailingNavProp = "ALinq_TypeTokenWithNoTrailingNavProp"; + internal const string DSKAttribute_MustSpecifyAtleastOnePropertyName = "DSKAttribute_MustSpecifyAtleastOnePropertyName"; + internal const string DataServiceCollection_LoadRequiresTargetCollectionObserved = "DataServiceCollection_LoadRequiresTargetCollectionObserved"; + internal const string DataServiceCollection_CannotStopTrackingChildCollection = "DataServiceCollection_CannotStopTrackingChildCollection"; + internal const string DataServiceCollection_OperationForTrackedOnly = "DataServiceCollection_OperationForTrackedOnly"; + internal const string DataServiceCollection_CannotDetermineContextFromItems = "DataServiceCollection_CannotDetermineContextFromItems"; + internal const string DataServiceCollection_InsertIntoTrackedButNotLoadedCollection = "DataServiceCollection_InsertIntoTrackedButNotLoadedCollection"; + internal const string DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime = "DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime"; + internal const string DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity = "DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity"; + internal const string DataServiceCollection_LoadAsyncRequiresDataServiceQuery = "DataServiceCollection_LoadAsyncRequiresDataServiceQuery"; + internal const string DataBinding_DataServiceCollectionArgumentMustHaveEntityType = "DataBinding_DataServiceCollectionArgumentMustHaveEntityType"; + internal const string DataBinding_CollectionPropertySetterValueHasObserver = "DataBinding_CollectionPropertySetterValueHasObserver"; + internal const string DataBinding_DataServiceCollectionChangedUnknownActionCollection = "DataBinding_DataServiceCollectionChangedUnknownActionCollection"; + internal const string DataBinding_CollectionChangedUnknownActionCollection = "DataBinding_CollectionChangedUnknownActionCollection"; + internal const string DataBinding_BindingOperation_DetachedSource = "DataBinding_BindingOperation_DetachedSource"; + internal const string DataBinding_BindingOperation_ArrayItemNull = "DataBinding_BindingOperation_ArrayItemNull"; + internal const string DataBinding_BindingOperation_ArrayItemNotEntity = "DataBinding_BindingOperation_ArrayItemNotEntity"; + internal const string DataBinding_Util_UnknownEntitySetName = "DataBinding_Util_UnknownEntitySetName"; + internal const string DataBinding_EntityAlreadyInCollection = "DataBinding_EntityAlreadyInCollection"; + internal const string DataBinding_NotifyPropertyChangedNotImpl = "DataBinding_NotifyPropertyChangedNotImpl"; + internal const string DataBinding_NotifyCollectionChangedNotImpl = "DataBinding_NotifyCollectionChangedNotImpl"; + internal const string DataBinding_ComplexObjectAssociatedWithMultipleEntities = "DataBinding_ComplexObjectAssociatedWithMultipleEntities"; + internal const string DataBinding_CollectionAssociatedWithMultipleEntities = "DataBinding_CollectionAssociatedWithMultipleEntities"; + internal const string AtomParser_SingleEntry_NoneFound = "AtomParser_SingleEntry_NoneFound"; + internal const string AtomParser_SingleEntry_MultipleFound = "AtomParser_SingleEntry_MultipleFound"; + internal const string AtomParser_SingleEntry_ExpectedFeedOrEntry = "AtomParser_SingleEntry_ExpectedFeedOrEntry"; + internal const string AtomMaterializer_CannotAssignNull = "AtomMaterializer_CannotAssignNull"; + internal const string AtomMaterializer_EntryIntoCollectionMismatch = "AtomMaterializer_EntryIntoCollectionMismatch"; + internal const string AtomMaterializer_EntryToAccessIsNull = "AtomMaterializer_EntryToAccessIsNull"; + internal const string AtomMaterializer_EntryToInitializeIsNull = "AtomMaterializer_EntryToInitializeIsNull"; + internal const string AtomMaterializer_ProjectEntityTypeMismatch = "AtomMaterializer_ProjectEntityTypeMismatch"; + internal const string AtomMaterializer_PropertyMissing = "AtomMaterializer_PropertyMissing"; + internal const string AtomMaterializer_PropertyNotExpectedEntry = "AtomMaterializer_PropertyNotExpectedEntry"; + internal const string AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities = "AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities"; + internal const string AtomMaterializer_NoParameterlessCtorForCollectionProperty = "AtomMaterializer_NoParameterlessCtorForCollectionProperty"; + internal const string AtomMaterializer_InvalidCollectionItem = "AtomMaterializer_InvalidCollectionItem"; + internal const string AtomMaterializer_InvalidEntityType = "AtomMaterializer_InvalidEntityType"; + internal const string AtomMaterializer_InvalidNonEntityType = "AtomMaterializer_InvalidNonEntityType"; + internal const string AtomMaterializer_CollectionExpectedCollection = "AtomMaterializer_CollectionExpectedCollection"; + internal const string AtomMaterializer_InvalidResponsePayload = "AtomMaterializer_InvalidResponsePayload"; + internal const string AtomMaterializer_InvalidContentTypeEncountered = "AtomMaterializer_InvalidContentTypeEncountered"; + internal const string AtomMaterializer_MaterializationTypeError = "AtomMaterializer_MaterializationTypeError"; + internal const string AtomMaterializer_ResetAfterEnumeratorCreationError = "AtomMaterializer_ResetAfterEnumeratorCreationError"; + internal const string AtomMaterializer_TypeShouldBeCollectionError = "AtomMaterializer_TypeShouldBeCollectionError"; + internal const string Serializer_LoopsNotAllowedInComplexTypes = "Serializer_LoopsNotAllowedInComplexTypes"; + internal const string Serializer_LoopsNotAllowedInNonPropertyComplexTypes = "Serializer_LoopsNotAllowedInNonPropertyComplexTypes"; + internal const string Serializer_InvalidCollectionParamterItemType = "Serializer_InvalidCollectionParamterItemType"; + internal const string Serializer_NullCollectionParamterItemValue = "Serializer_NullCollectionParamterItemValue"; + internal const string Serializer_InvalidParameterType = "Serializer_InvalidParameterType"; + internal const string Serializer_UriDoesNotContainParameterAlias = "Serializer_UriDoesNotContainParameterAlias"; + internal const string Serializer_InvalidEnumMemberValue = "Serializer_InvalidEnumMemberValue"; + internal const string DataServiceQuery_EnumerationNotSupported = "DataServiceQuery_EnumerationNotSupported"; + internal const string Context_SendingRequestEventArgsNotHttp = "Context_SendingRequestEventArgsNotHttp"; + internal const string General_InternalError = "General_InternalError"; + internal const string ODataMetadataBuilder_MissingEntitySetUri = "ODataMetadataBuilder_MissingEntitySetUri"; + internal const string ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix = "ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix"; + internal const string ODataMetadataBuilder_MissingEntityInstanceUri = "ODataMetadataBuilder_MissingEntityInstanceUri"; + internal const string EdmValueUtils_UnsupportedPrimitiveType = "EdmValueUtils_UnsupportedPrimitiveType"; + internal const string EdmValueUtils_IncorrectPrimitiveTypeKind = "EdmValueUtils_IncorrectPrimitiveTypeKind"; + internal const string EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName = "EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName"; + internal const string EdmValueUtils_CannotConvertTypeToClrValue = "EdmValueUtils_CannotConvertTypeToClrValue"; + internal const string ValueParser_InvalidDuration = "ValueParser_InvalidDuration"; + internal const string PlatformHelper_DateTimeOffsetMustContainTimeZone = "PlatformHelper_DateTimeOffsetMustContainTimeZone"; + internal const string Silverlight_BrowserHttp_NotSupported = "Silverlight_BrowserHttp_NotSupported"; + internal const string DataServiceCollection_DataServiceQueryCanNotBeEnumerated = "DataServiceCollection_DataServiceQueryCanNotBeEnumerated"; + + static TextRes loader = null; + ResourceManager resources; + + internal TextRes() { +#if !PORTABLELIB + resources = new System.Resources.ResourceManager("Microsoft.OData.Client", this.GetType().Assembly); +#else + resources = new System.Resources.ResourceManager("Microsoft.OData.Client", this.GetType().GetTypeInfo().Assembly); +#endif + } + + private static TextRes GetLoader() { + if (loader == null) { + TextRes sr = new TextRes(); + Interlocked.CompareExchange(ref loader, sr, null); + } + return loader; + } + + private static CultureInfo Culture { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + public static ResourceManager Resources { + get { + return GetLoader().resources; + } + } + + public static string GetString(string name, params object[] args) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, TextRes.Culture); + + if (args != null && args.Length > 0) { + for (int i = 0; i < args.Length; i ++) { + String value = args[i] as String; + if (value != null && value.Length > 1024) { + args[i] = value.Substring(0, 1024 - 3) + "..."; + } + } + return String.Format(CultureInfo.CurrentCulture, res, args); + } + else { + return res; + } + } + + public static string GetString(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetString(name, TextRes.Culture); + } + + public static string GetString(string name, out bool usedFallback) { + // always false for this version of gensr + usedFallback = false; + return GetString(name); + } +#if !PORTABLELIB + public static object GetObject(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetObject(name, TextRes.Culture); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.csproj new file mode 100644 index 0000000..669bc9a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.csproj @@ -0,0 +1,939 @@ + + + + Microsoft.OData.Client + Library + Profile111 + .NETPortable + v4.5 + true + $(DefineConstants);ODATA_CLIENT;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + $(AssemblyName).xml + true + System + {AED0DC9D-76E5-4145-AF5E-9E2F856F4D18} + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 11.0 + true + + + + + + + {7D921888-FE03-4C3F-80FE-2F624505461C} + Microsoft.OData.Edm + + + {989a83cc-b864-4a75-8bf3-5eda99203a86} + Microsoft.OData.Core + + + {5D921888-FE03-4C3F-40FE-2F624505461D} + Microsoft.Spatial + + + + + + Microsoft.OData.Client + true + true + internal + true + Microsoft.OData.Client.TextRes + skip + + + + + + + EdmValueParser.cs + + + EdmValueWriter.cs + + + EdmValueUtils.cs + + + ODataResourceMetadataBuilder.cs + + + ODataUriBuilder.cs + + + Serialization\JsonSharedUtils.cs + + + SimpleLazy.cs + + + Metadata\EdmLibraryExtensions.cs + + + InternalErrorCodesCommon.cs + + + ALinq\UriParser\SyntacticAst\AllToken.cs + + + ALinq\UriParser\SyntacticAst\AnyToken.cs + + + ALinq\UriParser\SyntacticAst\BinaryOperatorToken.cs + + + ALinq\UriParser\SyntacticAst\CustomQueryOptionToken.cs + + + ALinq\UriParser\SyntacticAst\DottedIdentifierToken.cs + + + ALinq\UriParser\SyntacticAst\EndPathToken.cs + + + ALinq\UriParser\ExceptionUtils.cs + + + ALinq\UriParser\SyntacticAst\ComputeToken.cs + + + ALinq\UriParser\SyntacticAst\ComputeExpressionToken.cs + + + ALinq\UriParser\SyntacticAst\ExpandTermToken.cs + + + ALinq\UriParser\SyntacticAst\ExpandToken.cs + + + ALinq\UriParser\SyntacticAst\AggregateToken.cs + + + ALinq\UriParser\SyntacticAst\GroupByToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionCallToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionParameterAliasToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionParameterToken.cs + + + ALinq\UriParser\SyntacticAst\InnerPathToken.cs + + + ALinq\UriParser\SyntacticAst\InToken.cs + + + ALinq\UriParser\Visitors\IPathSegmentTokenVisitor.cs + + + ALinq\UriParser\Visitors\ISyntacticTreeVisitor.cs + + + ALinq\UriParser\Visitors\SyntacticTreeVisitor.cs + + + ALinq\UriParser\SyntacticAst\LambdaToken.cs + + + ALinq\UriParser\SyntacticAst\LiteralToken.cs + + + ALinq\UriParser\NamedValue.cs + + + ALinq\UriParser\SyntacticAst\NonSystemToken.cs + + + ALinq\UriParser\SyntacticAst\OrderByToken.cs + + + ALinq\UriParser\SyntacticAst\PathSegmentToken.cs + + + ALinq\UriParser\SyntacticAst\PathToken.cs + + + ALinq\UriParser\SyntacticAst\QueryToken.cs + + + ALinq\UriParser\SyntacticAst\QueryTokenKind.cs + + + ALinq\UriParser\SyntacticAst\RangeVariableToken.cs + + + ALinq\UriParser\SyntacticAst\SelectExpandTermToken.cs + + + ALinq\UriParser\SyntacticAst\SelectTermToken.cs + + + ALinq\UriParser\ReadOnlyEnumerableForUriParser.cs + + + ALinq\UriParser\SyntacticAst\SelectToken.cs + + + ALinq\UriParser\SyntacticAst\StarToken.cs + + + ALinq\UriParser\SyntacticAst\SystemToken.cs + + + ALinq\UriParser\SyntacticAst\UnaryOperatorToken.cs + + + GlobalSuppressions.cs + + + DataServiceActionQueryOfT.cs + + + ActionDescriptor.cs + + + DataServiceActionQuerySingleOfT.cs + + + DataServiceActionQuery.cs + + + ALinq\QueryableResourceExpression.cs + + + ALinq\ResourceSetExpression.cs + + + ALinq\SingletonResourceExpression.cs + + + ALinq\ExpandOnlyPathToStringVisitor.cs + + + ALinq\SelectExpandPathToStringVisitor.cs + + + ALinq\AddNewEndingTokenVisitor.cs + + + ALinq\NewTreeBuilder.cs + + + ALinq\RemoveWildcardVisitor.cs + + + ALinq\SelectExpandPathBuilder.cs + + + Attribute\OriginalNameAttribute.cs + + + BaseEntityType.cs + + + BaseSaveResult.cs + + + BatchSaveResult.cs + + + BodyOperationParameter.cs + + + BuildingRequestEventArgs.cs + + + ClientEdmCollectionValue.cs + + + ClientEdmModel.cs + + + ClientEdmStructuredValue.cs + + + Common.cs + + + CommonUtil.cs + + + ContentStream.cs + + + ContentTypeUtil.cs + + + ConventionalODataEntityMetadataBuilder.cs + + + DataServiceQuerySingleOfT.cs + + + Materialization\EnumValueMaterializationPolicy.cs + + + Materialization\ODataLoadNavigationPropertyMaterializer.cs + + + DataServiceClientRequestMessageArgs.cs + + + HttpStack.cs + + + EntityParameterSendOption.cs + + + MessageReaderSettingsArgs.cs + + + MessageWriterSettingsArgs.cs + + + DataServiceClientConfigurations.cs + + + Materialization\CollectionValueMaterializationPolicy.cs + + + Materialization\EntityTrackingAdapter.cs + + + Materialization\EntryValueMaterializationPolicy.cs + + + Materialization\MaterializationPolicy.cs + + + Materialization\ODataReaderWrapper.cs + + + Materialization\PrimitivePropertyConverter.cs + + + Materialization\PrimitiveValueMaterializationPolicy.cs + + + Materialization\StructuralValueMaterializationPolicy.cs + + + MaterializedEntityArgs.cs + + + ODataAnnotatableExtensions.cs + + + Serialization\DataServiceClientRequestMessage.cs + + + Utility.cs + + + WritingEntityReferenceLinkArgs.cs + + + WritingEntryArgs.cs + + + WritingNestedResourceInfoArgs.cs + + + DataServiceClientRequestPipelineConfiguration.cs + + + ReadingNestedResourceInfoArgs.cs + + + ReadingFeedArgs.cs + + + EntityTrackerBase.cs + + + Materialization\ODataMaterializerContext.cs + + + Materialization\IODataMaterializerContext.cs + + + ReadingEntryArgs.cs + + + DataServiceClientResponsePipelineConfiguration.cs + + + Materialization\FeedAndEntryMaterializerAdapter.cs + + + DictionaryExtensions.cs + + + DataServiceClientFormat.cs + + + DataServiceQueryContinuation.cs + + + DataServiceResponsePreference.cs + + + DataServiceSaveStream.cs + + + DataServiceStreamLink.cs + + + DataServiceWebException.cs + + + DynamicProxyMethodGenerator.cs + + + EntityTracker.cs + + + FunctionDescriptor.cs + + + InternalODataRequestMessage.cs + + + InvokeResponse.cs + + + LinkInfo.cs + + + LoadPropertyResult.cs + + + Materialization\ODataCollectionMaterializer.cs + + + Materialization\ODataEntriesEntityMaterializer.cs + + + Materialization\ODataEntityMaterializer.cs + + + Materialization\ODataEntityMaterializerInvoker.cs + + + Materialization\ODataItemExtensions.cs + + + Materialization\ODataLinksMaterializer.cs + + + Materialization\ODataMaterializer.cs + + + Materialization\ODataMessageReaderMaterializer.cs + + + Materialization\ODataPropertyMaterializer.cs + + + Materialization\ODataReaderEntityMaterializer.cs + + + Materialization\ODataValueMaterializer.cs + + + MemberAssignmentAnalysis.cs + + + ODataMessageReadingHelper.cs + + + ODataMessageWritingHelper.cs + + + ODataRequestMessageWrapper.cs + + + OperationDescriptor.cs + + + OperationParameter.cs + + + ProjectionPath.cs + + + ProjectionPathBuilder.cs + + + ProjectionPathSegment.cs + + + ProjectionPlan.cs + + + ProjectionPlanCompiler.cs + + + ReceivingResponseEventArgs.cs + + + HeaderCollection.cs + + + RequestInfo.cs + + + ResponseInfo.cs + + + SendingRequest2EventArgs.cs + + + Serialization\DataStringEscapeBuilder.cs + + + Serialization\ODataWriterWrapper.cs + + + Serialization\HttpWebRequestMessage.cs + + + Materialization\HttpWebResponseMessage.cs + + + Serialization\ODataPropertyConverter.cs + + + Serialization\PrimitiveXmlConverter.cs + + + Serialization\PrimitiveType.cs + + + Serialization\PrimitiveParserToken.cs + + + ReferenceEqualityComparer.cs + + + true + ALinq\ProjectionRewriter.cs + + + GetReadStreamResult.cs + + + true + ALinq\ProjectionAnalyzer.cs + + + true + ALinq\ProjectionQueryOptionExpression.cs + + + QueryResult.cs + + + ALinq\QueryComponents.cs + + + true + ALinq\ReflectionUtil.cs + + + ALinq\InputReferenceExpression.cs + + + true + ALinq\ResourceExpression.cs + + + ALinq\Evaluator.cs + + + true + ALinq\ExpressionNormalizer.cs + + + ALinq\ALinqExpressionVisitor.cs + + + ALinq\FilterQueryOptionExpression.cs + + + ALinq\InputBinder.cs + + + ALinq\OrderByQueryOptionExpression.cs + + + ALinq\QueryOptionExpression.cs + + + true + ALinq\ResourceBinder.cs + + + ALinq\ResourceExpressionType.cs + + + ALinq\NavigationPropertySingletonExpression.cs + + + ALinq\SkipQueryOptionExpression.cs + + + ALinq\TakeQueryOptionExpression.cs + + + ALinq\TypeSystem.cs + + + ALinq\UriHelper.cs + + + ALinq\ExpressionWriter.cs + + + ALinq\UriWriter.cs + + + ALinq\DataServiceExpressionVisitor.cs + + + ALinq\DataServiceQueryProvider.cs + + + Annotation\AnnotationHelper.cs + + + true + ArraySet.cs + + + AtomMaterializerLog.cs + + + BaseAsyncResult.cs + + + Binding\BindingEntityInfo.cs + + + Binding\BindingGraph.cs + + + Binding\BindingObserver.cs + + + Binding\BindingUtils.cs + + + Binding\DataServiceSaveChangesEventArgs.cs + + + Binding\EntityChangedParams.cs + + + Binding\EntityCollectionChangedParams.cs + + + Binding\DataServiceCollectionOfT.cs + + + Binding\LoadCompletedEventArgs.cs + + + Attribute\EntitySetAttribute.cs + + + Attribute\EntityTypeAttribute.cs + + + Attribute\KeyAttribute.cs + + + ODataProtocolVersion.cs + + + Attribute\HasStreamAttribute.cs + + + Attribute\NamedStreamAttribute.cs + + + DataServiceQueryException.cs + + + DataServiceRequestArgs.cs + + + DataServiceRequestException.cs + + + DataServiceStreamResponse.cs + + + Descriptor.cs + + + LinkDescriptor.cs + + + ChangesetResponse.cs + + + ClientConvert.cs + + + Metadata\ClientTypeAnnotation.cs + + + DataServiceClientException.cs + + + DataServiceQuery.cs + + + DataServiceRequest.cs + + + DataServiceRequestOfT.cs + + + DataServiceResponse.cs + + + OperationResponse.cs + + + QueryOperationResponseOfT.cs + + + QueryOperationResponse.cs + + + true + Error.cs + + + KeySerializer.cs + + + LiteralFormatter.cs + + + true + MaterializeFromAtom.cs + + + Materialization\MaterializerEntry.cs + + + Materialization\MaterializerFeed.cs + + + Materialization\MaterializerNavigationLink.cs + + + Materialization\InstanceAnnotationMaterializationPolicy.cs + + + MediaEntryAttribute.cs + + + MergeOption.cs + + + MimeTypePropertyAttribute.cs + + + EntityDescriptor.cs + + + EntityStates.cs + + + SaveChangesOptions.cs + + + SaveResult.cs + + + SendingRequestEventArgs.cs + + + Serialization\Serializer.cs + + + StreamDescriptor.cs + + + TypeResolver.cs + + + UriEntityOperationParameter.cs + + + UriOperationParameter.cs + + + UriResolver.cs + + + UriUtil.cs + + + DataServiceUrlKeyDelimiter.cs + + + Util.cs + + + DataServiceContext.cs + + + DataServiceQueryOfT.cs + + + WebUtil.cs + + + Metadata\ClientPropertyAnnotation.cs + + + Metadata\ClientTypeUtil.cs + + + Metadata\ClientTypeCache.cs + + + Metadata\EdmComplexTypeWithDelayLoadedProperties.cs + + + Metadata\EdmEntityTypeWithDelayLoadedProperties.cs + + + Metadata\EdmEnumTypeWithDelayLoadedMembers.cs + + + XmlConstants.cs + + + PlatformHelper.cs + + + + + + Client\TaskUtils.cs + + + false + + + false + + + + + + + + + + + + TextTemplatingFileGenerator + Microsoft.OData.Client.Portable.cs + + + ALinq\UriParser\SyntacticAst\ApplyTransformationToken.cs + + + ALinq\UriParser\SyntacticAst\EntitySetAggregateExpression.cs + + + ALinq\UriParser\SyntacticAst\EntitySetAggregateToken.cs + + + ALinq\UriParser\SyntacticAst\AggregateTokenBase.cs + + + ALinq\UriParser\SyntacticAst\AggregateExpressionToken.cs + + + Annotation\InstanceAnnotationDictWeakKeyComparer.cs + + + Attribute\IgnoreClientPropertyAttribute.cs + + + WeakDictionary.cs + + + Wrappers\ODataItemWrapper.cs + + + Wrappers\ODataNestedResourceInfoWrapper.cs + + + Wrappers\ODataResourceSetWrapper.cs + + + Wrappers\ODataResourceWrapper.cs + + + Wrappers\ODataWriterHelper.cs + + + True + True + Microsoft.OData.Client.Portable.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Client.Portable.cs + + + True + True + Parameterized.Microsoft.OData.Client.Portable.tt + true + + + + + + false + + + false + + + $(SuiteBinPath) + + + + + + + + BuildTextFile;$(GenerateTextStringResourcesDependsOn) + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.tt new file mode 100644 index 0000000..cc53f37 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.tt @@ -0,0 +1,23 @@ +<#@ include file="..\..\..\tools\StringResourceGenerator\ResourceClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData.Client"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Client"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // Indicates whether to skip generation of string resource attributes. + public const bool SkipSRAttributes = true; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "..\\Microsoft.OData.Client.Common.txt", + "Microsoft.OData.Client.Portable.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.txt b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.txt new file mode 100644 index 0000000..83a16e8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Microsoft.OData.Client.Portable.txt @@ -0,0 +1,3 @@ +Silverlight_BrowserHttp_NotSupported=Silverlight Browser Http Stack is not supported on the Portable Library, only Client Http is supported. + +DataServiceCollection_DataServiceQueryCanNotBeEnumerated=Parameters of type DataServiceQuery can not be used as the input enumerators for DataServiceCollection. Try using result of DataServiceQuery.EndExecute instead. diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.cs new file mode 100644 index 0000000..13d55c8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.cs @@ -0,0 +1,2019 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client { + using System; + using System.Resources; + + /// + /// Strongly-typed and parameterized string resources. + /// + internal static class Strings { + /// + /// A string like "The expected content type for a batch requests is "multipart/mixed;boundary=batch" not "{0}"." + /// + internal static string Batch_ExpectedContentType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_ExpectedContentType, p0); + } + + /// + /// A string like "The POST request expected a response with content. ID={0}" + /// + internal static string Batch_ExpectedResponse(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_ExpectedResponse, p0); + } + + /// + /// A string like "Not all requests in the batch had a response." + /// + internal static string Batch_IncompleteResponseCount { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_IncompleteResponseCount); + } + } + + /// + /// A string like "The web response contained unexpected sections. ID={0}" + /// + internal static string Batch_UnexpectedContent(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_UnexpectedContent, p0); + } + + /// + /// A string like "Expected an absolute, well formed http URL without a query or fragment." + /// + internal static string Context_BaseUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BaseUri); + } + } + + /// + /// A string like "You must set the BaseUri property before you perform this operation." + /// + internal static string Context_BaseUriRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BaseUriRequired); + } + } + + /// + /// A string like "The Uri that is returned by the ResolveEntitySet function must be an absolute, well-formed URL with an "http" or "https" scheme name and without any query strings or fragment identifiers." + /// + internal static string Context_ResolveReturnedInvalidUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ResolveReturnedInvalidUri); + } + } + + /// + /// A string like "Because the requestUri is a relative Uri, you must set the BaseUri property on the DataServiceContext." + /// + internal static string Context_RequestUriIsRelativeBaseUriRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RequestUriIsRelativeBaseUriRequired); + } + } + + /// + /// A string like "The ResolveEntitySet function must return a non-null Uri for the EntitySet '{0}', otherwise you must set the BaseUri property." + /// + internal static string Context_ResolveEntitySetOrBaseUriRequired(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ResolveEntitySetOrBaseUriRequired, p0); + } + + /// + /// A string like "Unable to convert value '{0}' into a key string for a URI." + /// + internal static string Context_CannotConvertKey(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotConvertKey, p0); + } + + /// + /// A string like "The identity value specified by either the Atom id element or the OData-EntityId header must be an absolute URI." + /// + internal static string Context_TrackingExpectsAbsoluteUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_TrackingExpectsAbsoluteUri); + } + } + + /// + /// A string like "The 'Location' header value specified in the response must be an absolute URI." + /// + internal static string Context_LocationHeaderExpectsAbsoluteUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_LocationHeaderExpectsAbsoluteUri); + } + } + + /// + /// A string like "One of the link's resources failed to insert." + /// + internal static string Context_LinkResourceInsertFailure { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_LinkResourceInsertFailure); + } + } + + /// + /// A string like "Microsoft.OData.Client internal error {0}." + /// + internal static string Context_InternalError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_InternalError, p0); + } + + /// + /// A string like "An error occurred for this query during batch execution. See the inner exception for details." + /// + internal static string Context_BatchExecuteError { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BatchExecuteError); + } + } + + /// + /// A string like "Expected a relative URL path without query or fragment." + /// + internal static string Context_EntitySetName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntitySetName); + } + } + + /// + /// A string like "Changes cannot be saved as a batch when an entity has one or more streams associated with it. Retry the SaveChanges operation without enabling the SaveChangesOptions.BatchWithSingleChangeset and the SaveChangesOptions.BatchWithIndependentOperations options." + /// + internal static string Context_BatchNotSupportedForNamedStreams { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BatchNotSupportedForNamedStreams); + } + } + + /// + /// A string like "The stream named '{0}' cannot be modified because it does not have an edit-media link. Make sure that the stream name is correct and that an edit-media link for this stream is included in the entry element in the response." + /// + internal static string Context_SetSaveStreamWithoutNamedStreamEditLink(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamWithoutNamedStreamEditLink, p0); + } + + /// + /// A string like "This operation requires the entity be of an Entity Type, and has at least one key property." + /// + internal static string Content_EntityWithoutKey { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Content_EntityWithoutKey); + } + } + + /// + /// A string like "This operation requires the entity to be of an Entity Type, either mark its key properties, or attribute the class with DataServiceEntityAttribute" + /// + internal static string Content_EntityIsNotEntityType { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Content_EntityIsNotEntityType); + } + } + + /// + /// A string like "The context is not currently tracking the entity." + /// + internal static string Context_EntityNotContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityNotContained); + } + } + + /// + /// A string like "The context is already tracking the entity." + /// + internal static string Context_EntityAlreadyContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityAlreadyContained); + } + } + + /// + /// A string like "The context is already tracking a different entity with the same resource Uri." + /// + internal static string Context_DifferentEntityAlreadyContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DifferentEntityAlreadyContained); + } + } + + /// + /// A string like "The current object did not originate the async result." + /// + internal static string Context_DidNotOriginateAsync { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DidNotOriginateAsync); + } + } + + /// + /// A string like "The asynchronous result has already been completed." + /// + internal static string Context_AsyncAlreadyDone { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AsyncAlreadyDone); + } + } + + /// + /// A string like "The operation has been canceled." + /// + internal static string Context_OperationCanceled { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_OperationCanceled); + } + } + + /// + /// A string like "The property '{0}' is not supported when MaxProtocolVersion is greater than '{1}'." + /// + internal static string Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX, p0, p1); + } + + /// + /// A string like "The context can not load the related collection or reference for objects in the added state." + /// + internal static string Context_NoLoadWithInsertEnd { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoLoadWithInsertEnd); + } + } + + /// + /// A string like "One or both of the ends of the relationship is in the added state." + /// + internal static string Context_NoRelationWithInsertEnd { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoRelationWithInsertEnd); + } + } + + /// + /// A string like "One or both of the ends of the relationship is in the deleted state." + /// + internal static string Context_NoRelationWithDeleteEnd { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoRelationWithDeleteEnd); + } + } + + /// + /// A string like "The context is already tracking the relationship." + /// + internal static string Context_RelationAlreadyContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RelationAlreadyContained); + } + } + + /// + /// A string like "The sourceProperty is not a reference or collection of the target's object type." + /// + internal static string Context_RelationNotRefOrCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RelationNotRefOrCollection); + } + } + + /// + /// A string like "AddLink and DeleteLink methods only work when the sourceProperty is a collection." + /// + internal static string Context_AddLinkCollectionOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AddLinkCollectionOnly); + } + } + + /// + /// A string like "AddRelatedObject method only works when the sourceProperty is a collection." + /// + internal static string Context_AddRelatedObjectCollectionOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AddRelatedObjectCollectionOnly); + } + } + + /// + /// A string like "AddRelatedObject method only works if the source entity is in a non-deleted state." + /// + internal static string Context_AddRelatedObjectSourceDeleted { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AddRelatedObjectSourceDeleted); + } + } + + /// + /// A string like "UpdateRelatedObject method only works when the sourceProperty is not collection." + /// + internal static string Context_UpdateRelatedObjectNonCollectionOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_UpdateRelatedObjectNonCollectionOnly); + } + } + + /// + /// A string like "SetLink method only works when the sourceProperty is not a collection." + /// + internal static string Context_SetLinkReferenceOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetLinkReferenceOnly); + } + } + + /// + /// A string like "Media link object of type '{0}' is configured to use the MIME type specified in the property '{1}'. However, that property's value is null or empty." + /// + internal static string Context_NoContentTypeForMediaLink(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoContentTypeForMediaLink, p0, p1); + } + + /// + /// A string like "Saving entities with the [MediaEntry] attribute is not currently supported in batch mode. Use non-batched mode instead." + /// + internal static string Context_BatchNotSupportedForMediaLink { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BatchNotSupportedForMediaLink); + } + } + + /// + /// A string like "Unexpected result (<= 0) from stream.Read() while reading raw data for this property." + /// + internal static string Context_UnexpectedZeroRawRead { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_UnexpectedZeroRawRead); + } + } + + /// + /// A string like "Response version '{0}' is not supported. The only supported versions are: {1}." + /// + internal static string Context_VersionNotSupported(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_VersionNotSupported, p0, p1); + } + + /// + /// A string like "The response version is {0}, but the MaxProtocolVersion of the data service context is set to {1}. Set the MaxProtocolVersion to the version required by the response, and then retry the request. If the client does not support the required protocol version, then upgrade the client." + /// + internal static string Context_ResponseVersionIsBiggerThanProtocolVersion(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ResponseVersionIsBiggerThanProtocolVersion, p0, p1); + } + + /// + /// A string like "The request requires that version {0} of the protocol be used, but the MaxProtocolVersion of the data service context is set to {1}. Set the MaxProtocolVersion to the higher version, and then retry the operation." + /// + internal static string Context_RequestVersionIsBiggerThanProtocolVersion(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RequestVersionIsBiggerThanProtocolVersion, p0, p1); + } + + /// + /// A string like "Attempt to delete a link between two objects failed because the identity of the target object of the link depends on the source object of the link." + /// + internal static string Context_ChildResourceExists { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ChildResourceExists); + } + } + + /// + /// A string like "The ContentType value for a named stream cannot be null or an empty string." + /// + internal static string Context_ContentTypeRequiredForNamedStream { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ContentTypeRequiredForNamedStream); + } + } + + /// + /// A string like "This operation requires that the specified entity be a Media Link Entry and that the ReadStreamUri be available. However, the specified entity either is not a Media Link Entry or does not have a valid ReadStreamUri value. If the entity is a Media Link Entry, re-query the data service for this entity to obtain a valid ReadStreamUri value." + /// + internal static string Context_EntityNotMediaLinkEntry { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityNotMediaLinkEntry); + } + } + + /// + /// A string like "The entity type {0} is marked with MediaEntry attribute but no save stream was set for the entity." + /// + internal static string Context_MLEWithoutSaveStream(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MLEWithoutSaveStream, p0); + } + + /// + /// A string like "Can't use SetSaveStream on entity with type {0} which has a media entry property defined." + /// + internal static string Context_SetSaveStreamOnMediaEntryProperty(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamOnMediaEntryProperty, p0); + } + + /// + /// A string like "There is no edit-media link for the entity's media stream. Make sure that the edit-media link is specified for this stream." + /// + internal static string Context_SetSaveStreamWithoutEditMediaLink { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamWithoutEditMediaLink); + } + } + + /// + /// A string like "Calling SetSaveStream on an entity with state '{0}' is not allowed." + /// + internal static string Context_SetSaveStreamOnInvalidEntityState(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamOnInvalidEntityState, p0); + } + + /// + /// A string like "The entity does not have a stream named '{0}'. Make sure that the name of the stream is correct." + /// + internal static string Context_EntityDoesNotContainNamedStream(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityDoesNotContainNamedStream, p0); + } + + /// + /// A string like "There is no self-link or edit-media link for the stream named '{0}'. Make sure that either the self-link or edit-media link is specified for this stream." + /// + internal static string Context_MissingSelfAndEditLinkForNamedStream(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MissingSelfAndEditLinkForNamedStream, p0); + } + + /// + /// A string like "The response should have both 'Location' and 'OData-EntityId' headers or the response should not have any of these headers." + /// + internal static string Context_BothLocationAndIdMustBeSpecified { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BothLocationAndIdMustBeSpecified); + } + } + + /// + /// A string like "OperationParameter of type BodyOperationParameter cannot be specified when the HttpMethod is set to GET." + /// + internal static string Context_BodyOperationParametersNotAllowedWithGet { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BodyOperationParametersNotAllowedWithGet); + } + } + + /// + /// A string like "The Name property of an OperationParameter must be set to a non-null, non-empty string." + /// + internal static string Context_MissingOperationParameterName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MissingOperationParameterName); + } + } + + /// + /// A string like "Multiple uri operation parameters were found with the same name. Uri operation parameter names must be unique." + /// + internal static string Context_DuplicateUriOperationParameterName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DuplicateUriOperationParameterName); + } + } + + /// + /// A string like "Multiple body operation parameters were found with the same name. Body operation parameter names must be unique." + /// + internal static string Context_DuplicateBodyOperationParameterName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DuplicateBodyOperationParameterName); + } + } + + /// + /// A string like "The serialized resource has a null value in key member '{0}'. Null values are not supported in key members." + /// + internal static string Context_NullKeysAreNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NullKeysAreNotSupported, p0); + } + + /// + /// A string like "The HttpMethod must be GET, POST or DELETE." + /// + internal static string Context_ExecuteExpectsGetOrPostOrDelete { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ExecuteExpectsGetOrPostOrDelete); + } + } + + /// + /// A string like "EndExecute overload for void service operations and actions received a non-void response from the server." + /// + internal static string Context_EndExecuteExpectedVoidResponse { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EndExecuteExpectedVoidResponse); + } + } + + /// + /// A string like "The operation parameters array contains a null element which is not allowed." + /// + internal static string Context_NullElementInOperationParameterArray { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NullElementInOperationParameterArray); + } + } + + /// + /// A string like "An implementation of ODataEntityMetadataBuilder is required, but a null value was returned from GetEntityMetadataBuilder." + /// + internal static string Context_EntityMetadataBuilderIsRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityMetadataBuilderIsRequired); + } + } + + /// + /// A string like "The ChangeState method does not support the 'Added' state because additional information is needed for inserts. Use either AddObject or AddRelatedObject instead." + /// + internal static string Context_CannotChangeStateToAdded { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotChangeStateToAdded); + } + } + + /// + /// A string like "The entity's state can only be changed to 'Modified' if it is currently 'Unchanged'." + /// + internal static string Context_CannotChangeStateToModifiedIfNotUnchanged { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotChangeStateToModifiedIfNotUnchanged); + } + } + + /// + /// A string like "An entity in the 'Added' state cannot be changed to '{0}', it can only be changed to 'Detached'." + /// + internal static string Context_CannotChangeStateIfAdded(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotChangeStateIfAdded, p0); + } + + /// + /// A string like "DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property must not return a null value. Please return a non-null value for this property." + /// + internal static string Context_OnMessageCreatingReturningNull { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_OnMessageCreatingReturningNull); + } + } + + /// + /// A string like "SendingRequest cannot be used in combination with the DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property. Please use SendingRequest2 with DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property instead." + /// + internal static string Context_SendingRequest_InvalidWhenUsingOnMessageCreating { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SendingRequest_InvalidWhenUsingOnMessageCreating); + } + } + + /// + /// A string like "'{0}' must be used with '{1}'." + /// + internal static string Context_MustBeUsedWith(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MustBeUsedWith, p0, p1); + } + + /// + /// A string like "When you call the UseJson method without a parameter, you must use the LoadServiceModel property to provide a valid IEdmModel instance." + /// + internal static string DataServiceClientFormat_LoadServiceModelRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceClientFormat_LoadServiceModelRequired); + } + } + + /// + /// A string like "To use the JSON format, you must first call DataServiceContext.Format.UseJson() and supply a valid service model." + /// + internal static string DataServiceClientFormat_ValidServiceModelRequiredForJson { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceClientFormat_ValidServiceModelRequiredForJson); + } + } + + /// + /// A string like "{0}.{1} must return a non-null open property collection." + /// + internal static string Collection_NullCollectionReference(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullCollectionReference, p0, p1); + } + + /// + /// A string like "The open object property '{0}:{1}' is not defined." + /// + internal static string ClientType_MissingOpenProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingOpenProperty, p0, p1); + } + + /// + /// A string like "{0} has multiple definitions for OpenObjectAttribute." + /// + internal static string Clienttype_MultipleOpenProperty(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Clienttype_MultipleOpenProperty, p0); + } + + /// + /// A string like "The closed type {0} does not have a corresponding {1} settable property." + /// + internal static string ClientType_MissingProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingProperty, p0, p1); + } + + /// + /// A string like "The key property '{0}' on for type '{1}' is of type '{2}', which is not a simple type. Only properties of simple type can be key properties." + /// + internal static string ClientType_KeysMustBeSimpleTypes(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_KeysMustBeSimpleTypes, p0, p1, p2); + } + + /// + /// A string like "{0} has key properties declared at different levels within its type hierarchy." + /// + internal static string ClientType_KeysOnDifferentDeclaredType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_KeysOnDifferentDeclaredType, p0); + } + + /// + /// A string like "Type '{0}' has a MimeTypeProperty attribute that references the MIME type property '{1}'. However, this type does not have a property '{1}'." + /// + internal static string ClientType_MissingMimeTypeProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingMimeTypeProperty, p0, p1); + } + + /// + /// A string like "Type '{0}' has a MimeTypeProperty attribute that references the data property '{1}'. However, this type does not have a property '{1}'." + /// + internal static string ClientType_MissingMimeTypeDataProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingMimeTypeDataProperty, p0, p1); + } + + /// + /// A string like "Type '{0}' has a MediaEntry attribute that references a property called '{1}'. However, this type does not have a property '{1}'." + /// + internal static string ClientType_MissingMediaEntryProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingMediaEntryProperty, p0, p1); + } + + /// + /// A string like "The complex type '{0}' has no settable properties." + /// + internal static string ClientType_NoSettableFields(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_NoSettableFields, p0); + } + + /// + /// A string like "Multiple implementations of ICollection<T> is not supported." + /// + internal static string ClientType_MultipleImplementationNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MultipleImplementationNotSupported); + } + } + + /// + /// A string like "The open type property '{0}' returned a null instance." + /// + internal static string ClientType_NullOpenProperties(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_NullOpenProperties, p0); + } + + /// + /// A string like "Resolving type from '{0}' that inherits from '{1}' is ambiguous." + /// + internal static string ClientType_Ambiguous(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_Ambiguous, p0, p1); + } + + /// + /// A string like "The type '{0}' is not supported by the client library." + /// + internal static string ClientType_UnsupportedType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_UnsupportedType, p0); + } + + /// + /// A string like "Collection properties of a collection type are not supported." + /// + internal static string ClientType_CollectionOfCollectionNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_CollectionOfCollectionNotSupported); + } + } + + /// + /// A string like "Multiple types were found with the same name '{0}'. Type names must be unique." + /// + internal static string ClientType_MultipleTypesWithSameName(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MultipleTypesWithSameName, p0); + } + + /// + /// A string like "An item in the collection property '{0}' is not of the correct type. All items in the collection property must be of the collection item type." + /// + internal static string WebUtil_TypeMismatchInCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.WebUtil_TypeMismatchInCollection, p0); + } + + /// + /// A string like "A collection of item type '{0}' has an item which is not of the correct type. All items in the collection must be of the collection item type." + /// + internal static string WebUtil_TypeMismatchInNonPropertyCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.WebUtil_TypeMismatchInNonPropertyCollection, p0); + } + + /// + /// A string like "The property '{0}' is of entity type and it cannot be a property of the type '{1}', which is not of entity type. Only entity types can contain navigation properties." + /// + internal static string ClientTypeCache_NonEntityTypeCannotContainEntityProperties(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientTypeCache_NonEntityTypeCannotContainEntityProperties, p0, p1); + } + + /// + /// A string like "An error occurred while processing this request." + /// + internal static string DataServiceException_GeneralError { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceException_GeneralError); + } + } + + /// + /// A string like "Only a single enumeration is supported by this IEnumerable." + /// + internal static string Deserialize_GetEnumerator { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_GetEnumerator); + } + } + + /// + /// A string like "The current value '{1}' type is not compatible with the expected '{0}' type." + /// + internal static string Deserialize_Current(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_Current, p0, p1); + } + + /// + /// A string like "Error processing response stream. Element value interspersed with a comment is not supported." + /// + internal static string Deserialize_MixedTextWithComment { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MixedTextWithComment); + } + } + + /// + /// A string like "Error processing response stream. The XML element contains mixed content." + /// + internal static string Deserialize_ExpectingSimpleValue { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_ExpectingSimpleValue); + } + } + + /// + /// A string like "Error processing response stream. Atom payload has a link, local object has a simple value." + /// + internal static string Deserialize_MismatchAtomLinkLocalSimple { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MismatchAtomLinkLocalSimple); + } + } + + /// + /// A string like "Error processing response stream. Atom payload has a feed and the property '{0}' is not a collection." + /// + internal static string Deserialize_MismatchAtomLinkFeedPropertyNotCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MismatchAtomLinkFeedPropertyNotCollection, p0); + } + + /// + /// A string like "Error processing response stream. Atom payload has an entry and the property '{0}' is a collection." + /// + internal static string Deserialize_MismatchAtomLinkEntryPropertyIsCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MismatchAtomLinkEntryPropertyIsCollection, p0); + } + + /// + /// A string like "The response to this POST request did not contain a 'location' header. That is not supported by this client." + /// + internal static string Deserialize_NoLocationHeader { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_NoLocationHeader); + } + } + + /// + /// A string like "Error processing response stream. Server failed with following message:\r\n{0}" + /// + internal static string Deserialize_ServerException(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_ServerException, p0); + } + + /// + /// A string like "Error processing response stream. Missing id element in the response." + /// + internal static string Deserialize_MissingIdElement { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MissingIdElement); + } + } + + /// + /// A string like "The value of the property '{0}' is null. Properties that are a collection type of primitive or complex types cannot be null." + /// + internal static string Collection_NullCollectionNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullCollectionNotSupported, p0); + } + + /// + /// A string like "The value of the collection of item type '{0}' is null. A collection cannot have a null value." + /// + internal static string Collection_NullNonPropertyCollectionNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullNonPropertyCollectionNotSupported, p0); + } + + /// + /// A string like "An item in the collection property has a null value. Collection properties that contain items with null values are not supported." + /// + internal static string Collection_NullCollectionItemsNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullCollectionItemsNotSupported); + } + } + + /// + /// A string like "A collection property of primitive types cannot contain an item of a collection type." + /// + internal static string Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed); + } + } + + /// + /// A string like "A collection property of complex types cannot contain an item of a primitive type." + /// + internal static string Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed); + } + } + + /// + /// A string like "The entity with identity '{0}' does not have a self-link or an edit-link associated with it. Please make sure that the entity has either a self-link or an edit-link associated with it." + /// + internal static string EntityDescriptor_MissingSelfEditLink(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EntityDescriptor_MissingSelfEditLink, p0); + } + + /// + /// A string like "Content-Type header value missing." + /// + internal static string HttpProcessUtility_ContentTypeMissing { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_ContentTypeMissing); + } + } + + /// + /// A string like "Media type is missing a parameter value." + /// + internal static string HttpProcessUtility_MediaTypeMissingValue { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeMissingValue); + } + } + + /// + /// A string like "Media type requires a ';' character before a parameter definition." + /// + internal static string HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter); + } + } + + /// + /// A string like "Media type requires a '/' character." + /// + internal static string HttpProcessUtility_MediaTypeRequiresSlash { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeRequiresSlash); + } + } + + /// + /// A string like "Media type requires a subtype definition." + /// + internal static string HttpProcessUtility_MediaTypeRequiresSubType { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeRequiresSubType); + } + } + + /// + /// A string like "Media type is unspecified." + /// + internal static string HttpProcessUtility_MediaTypeUnspecified { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeUnspecified); + } + } + + /// + /// A string like "Character set '{0}' is not supported." + /// + internal static string HttpProcessUtility_EncodingNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_EncodingNotSupported, p0); + } + + /// + /// A string like "Value for MIME type parameter '{0}' is incorrect because it contained escape characters even though it was not quoted." + /// + internal static string HttpProcessUtility_EscapeCharWithoutQuotes(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_EscapeCharWithoutQuotes, p0); + } + + /// + /// A string like "Value for MIME type parameter '{0}' is incorrect because it terminated with escape character. Escape characters must always be followed by a character in a parameter value." + /// + internal static string HttpProcessUtility_EscapeCharAtEnd(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_EscapeCharAtEnd, p0); + } + + /// + /// A string like "Value for MIME type parameter '{0}' is incorrect because the closing quote character could not be found while the parameter value started with a quote character." + /// + internal static string HttpProcessUtility_ClosingQuoteNotFound(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_ClosingQuoteNotFound, p0); + } + + /// + /// A string like "Count value is not part of the response stream." + /// + internal static string MaterializeFromAtom_CountNotPresent { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_CountNotPresent); + } + } + + /// + /// A string like "The top level link is only available after the response has been enumerated." + /// + internal static string MaterializeFromAtom_TopLevelLinkNotAvailable { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_TopLevelLinkNotAvailable); + } + } + + /// + /// A string like "The collection is not part of the current entry" + /// + internal static string MaterializeFromAtom_CollectionKeyNotPresentInLinkTable { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_CollectionKeyNotPresentInLinkTable); + } + } + + /// + /// A string like "This response does not contain any nested collections. Use null as Key instead." + /// + internal static string MaterializeFromAtom_GetNestLinkForFlatCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_GetNestLinkForFlatCollection); + } + } + + /// + /// A string like "GetStream method is not supported." + /// + internal static string ODataRequestMessage_GetStreamMethodNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataRequestMessage_GetStreamMethodNotSupported); + } + } + + /// + /// A string like "Empty string." + /// + internal static string Util_EmptyString { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Util_EmptyString); + } + } + + /// + /// A string like "Empty array." + /// + internal static string Util_EmptyArray { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Util_EmptyArray); + } + } + + /// + /// A string like "Array contains a null element." + /// + internal static string Util_NullArrayElement { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Util_NullArrayElement); + } + } + + /// + /// A string like "The expression type {0} is not supported." + /// + internal static string ALinq_UnsupportedExpression(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_UnsupportedExpression, p0); + } + + /// + /// A string like "Could not convert constant {0} expression to string." + /// + internal static string ALinq_CouldNotConvert(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CouldNotConvert, p0); + } + + /// + /// A string like "The method '{0}' is not supported." + /// + internal static string ALinq_MethodNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_MethodNotSupported, p0); + } + + /// + /// A string like "The unary operator '{0}' is not supported." + /// + internal static string ALinq_UnaryNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_UnaryNotSupported, p0); + } + + /// + /// A string like "The binary operator '{0}' is not supported." + /// + internal static string ALinq_BinaryNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_BinaryNotSupported, p0); + } + + /// + /// A string like "The constant for '{0}' is not supported." + /// + internal static string ALinq_ConstantNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ConstantNotSupported, p0); + } + + /// + /// A string like "An operation between an expression and a type is not supported." + /// + internal static string ALinq_TypeBinaryNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeBinaryNotSupported); + } + } + + /// + /// A string like "The conditional expression is not supported." + /// + internal static string ALinq_ConditionalNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ConditionalNotSupported); + } + } + + /// + /// A string like "The parameter expression is not supported." + /// + internal static string ALinq_ParameterNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ParameterNotSupported); + } + } + + /// + /// A string like "The member access of '{0}' is not supported." + /// + internal static string ALinq_MemberAccessNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_MemberAccessNotSupported, p0); + } + + /// + /// A string like "Lambda Expressions not supported." + /// + internal static string ALinq_LambdaNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_LambdaNotSupported); + } + } + + /// + /// A string like "New Expressions not supported." + /// + internal static string ALinq_NewNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_NewNotSupported); + } + } + + /// + /// A string like "Member Init Expressions not supported." + /// + internal static string ALinq_MemberInitNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_MemberInitNotSupported); + } + } + + /// + /// A string like "List Init Expressions not supported." + /// + internal static string ALinq_ListInitNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ListInitNotSupported); + } + } + + /// + /// A string like "New Array Expressions not supported." + /// + internal static string ALinq_NewArrayNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_NewArrayNotSupported); + } + } + + /// + /// A string like "Invocation Expressions not supported." + /// + internal static string ALinq_InvocationNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_InvocationNotSupported); + } + } + + /// + /// A string like "Can only specify query options (orderby, where, take, skip) after last navigation." + /// + internal static string ALinq_QueryOptionsOnlyAllowedOnLeafNodes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_QueryOptionsOnlyAllowedOnLeafNodes); + } + } + + /// + /// A string like "Expand query option not allowed." + /// + internal static string ALinq_CantExpand { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantExpand); + } + } + + /// + /// A string like "Can't cast to unsupported type '{0}'" + /// + internal static string ALinq_CantCastToUnsupportedPrimitive(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantCastToUnsupportedPrimitive, p0); + } + + /// + /// A string like "Individual properties can only be selected from a single resource or as part of a type. Specify a key predicate to restrict the entity set to a single instance or project the property into a named or anonymous type." + /// + internal static string ALinq_CantNavigateWithoutKeyPredicate { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantNavigateWithoutKeyPredicate); + } + } + + /// + /// A string like "Multiple key predicates cannot be specified for the same entity set." + /// + internal static string ALinq_CanOnlyApplyOneKeyPredicate { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CanOnlyApplyOneKeyPredicate); + } + } + + /// + /// A string like "The expression {0} is not supported." + /// + internal static string ALinq_CantTranslateExpression(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantTranslateExpression, p0); + } + + /// + /// A string like "Error translating Linq expression to URI: {0}" + /// + internal static string ALinq_TranslationError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TranslationError, p0); + } + + /// + /// A string like "Custom query option not allowed." + /// + internal static string ALinq_CantAddQueryOption { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddQueryOption); + } + } + + /// + /// A string like "Can't add duplicate query option '{0}'." + /// + internal static string ALinq_CantAddDuplicateQueryOption(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddDuplicateQueryOption, p0); + } + + /// + /// A string like "Can't add query option '{0}' because it would conflict with the query options from the translated Linq expression." + /// + internal static string ALinq_CantAddAstoriaQueryOption(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddAstoriaQueryOption, p0); + } + + /// + /// A string like "Can't add query option '{0}' because it begins with reserved character '$'." + /// + internal static string ALinq_CantAddQueryOptionStartingWithDollarSign(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddQueryOptionStartingWithDollarSign, p0); + } + + /// + /// A string like "Referencing public field '{0}' not supported in query option expression. Use public property instead." + /// + internal static string ALinq_CantReferToPublicField(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantReferToPublicField, p0); + } + + /// + /// A string like "Cannot specify query options (orderby, where, take, skip, count) on single resource." + /// + internal static string ALinq_QueryOptionsOnlyAllowedOnSingletons { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_QueryOptionsOnlyAllowedOnSingletons); + } + } + + /// + /// A string like "The {0} query option cannot be specified after the {1} query option." + /// + internal static string ALinq_QueryOptionOutOfOrder(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_QueryOptionOutOfOrder, p0, p1); + } + + /// + /// A string like "Cannot add count option to the resource set." + /// + internal static string ALinq_CannotAddCountOption { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotAddCountOption); + } + } + + /// + /// A string like "Cannot add count option to the resource set because it would conflict with existing count options." + /// + internal static string ALinq_CannotAddCountOptionConflict { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotAddCountOptionConflict); + } + } + + /// + /// A string like "Can only specify 'select' query option after last navigation." + /// + internal static string ALinq_ProjectionOnlyAllowedOnLeafNodes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ProjectionOnlyAllowedOnLeafNodes); + } + } + + /// + /// A string like "Cannot translate multiple Linq Select operations in a single 'select' query option." + /// + internal static string ALinq_ProjectionCanOnlyHaveOneProjection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ProjectionCanOnlyHaveOneProjection); + } + } + + /// + /// A string like "Cannot initialize an instance of entity type '{0}' because '{1}' and '{2}' do not refer to the same source entity." + /// + internal static string ALinq_ProjectionMemberAssignmentMismatch(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ProjectionMemberAssignmentMismatch, p0, p1, p2); + } + + /// + /// A string like "The expression '{0}' is not a valid expression for navigation path. The only supported operations inside the lambda expression body are MemberAccess and TypeAs. The expression must contain at least one MemberAccess and it cannot end with TypeAs." + /// + internal static string ALinq_InvalidExpressionInNavigationPath(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_InvalidExpressionInNavigationPath, p0); + } + + /// + /// A string like "Initializing instances of the entity type {0} with the expression {1} is not supported." + /// + internal static string ALinq_ExpressionNotSupportedInProjectionToEntity(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ExpressionNotSupportedInProjectionToEntity, p0, p1); + } + + /// + /// A string like "Constructing or initializing instances of the type {0} with the expression {1} is not supported." + /// + internal static string ALinq_ExpressionNotSupportedInProjection(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ExpressionNotSupportedInProjection, p0, p1); + } + + /// + /// A string like "Construction of entity type instances must use object initializer with default constructor." + /// + internal static string ALinq_CannotConstructKnownEntityTypes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotConstructKnownEntityTypes); + } + } + + /// + /// A string like "Referencing of local entity type instances not supported when projecting results." + /// + internal static string ALinq_CannotCreateConstantEntity { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotCreateConstantEntity); + } + } + + /// + /// A string like "Cannot assign the value from the {0} property to the {1} property. When projecting results into a entity type, the property names of the source type and the target type must match for the properties being projected." + /// + internal static string ALinq_PropertyNamesMustMatchInProjections(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_PropertyNamesMustMatchInProjections, p0, p1); + } + + /// + /// A string like "Can only project the last entity type in the query being translated." + /// + internal static string ALinq_CanOnlyProjectTheLeaf { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CanOnlyProjectTheLeaf); + } + } + + /// + /// A string like "Cannot create projection while there is an explicit expansion specified on the same query." + /// + internal static string ALinq_CannotProjectWithExplicitExpansion { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotProjectWithExplicitExpansion); + } + } + + /// + /// A string like "The collection property '{0}' cannot be used in an 'orderby' query expression. Collection properties are not supported by the 'orderby' query option." + /// + internal static string ALinq_CollectionPropertyNotSupportedInOrderBy(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CollectionPropertyNotSupportedInOrderBy, p0); + } + + /// + /// A string like "The collection property '{0}' cannot be used in a 'where' query expression. Collection properties are only supported as the source of 'any' or 'all' methods in a 'where' query option." + /// + internal static string ALinq_CollectionPropertyNotSupportedInWhere(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CollectionPropertyNotSupportedInWhere, p0); + } + + /// + /// A string like "Navigation to members of the collection property '{0}' in a 'select' query expression is not supported." + /// + internal static string ALinq_CollectionMemberAccessNotSupportedInNavigation(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CollectionMemberAccessNotSupportedInNavigation, p0); + } + + /// + /// A string like "The property '{0}' of type 'DataServiceStreamLink' cannot be used in 'where' or 'orderby' query expressions. Properties of type 'DataServiceStreamLink' are not supported by these query options." + /// + internal static string ALinq_LinkPropertyNotSupportedInExpression(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_LinkPropertyNotSupportedInExpression, p0); + } + + /// + /// A string like "The target type for an OfType filter could not be determined." + /// + internal static string ALinq_OfTypeArgumentNotAvailable { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_OfTypeArgumentNotAvailable); + } + } + + /// + /// A string like "Non-redundant type filters (OfType<T>, C# 'as' and VB 'TryCast') can only be used once per resource set." + /// + internal static string ALinq_CannotUseTypeFiltersMultipleTimes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotUseTypeFiltersMultipleTimes); + } + } + + /// + /// A string like "Unsupported expression '{0}' in '{1}' method. Expression cannot end with TypeAs." + /// + internal static string ALinq_ExpressionCannotEndWithTypeAs(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ExpressionCannotEndWithTypeAs, p0, p1); + } + + /// + /// A string like "The expression 'TypeAs' is not supported when MaxProtocolVersion is less than '3.0'." + /// + internal static string ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3 { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3); + } + } + + /// + /// A string like "The type '{0}' is not an entity type. The target type for a TypeAs operator must be an entity type." + /// + internal static string ALinq_TypeAsArgumentNotEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeAsArgumentNotEntityType, p0); + } + + /// + /// A string like "The source parameter for the '{0}' method has to be either a navigation or a collection property." + /// + internal static string ALinq_InvalidSourceForAnyAll(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_InvalidSourceForAnyAll, p0); + } + + /// + /// A string like "The method '{0}' is not supported by the 'orderby' query option." + /// + internal static string ALinq_AnyAllNotSupportedInOrderBy(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_AnyAllNotSupportedInOrderBy, p0); + } + + /// + /// A string like "The '$format' query option is not supported. Use the DataServiceContext.Format property to set the desired format." + /// + internal static string ALinq_FormatQueryOptionNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_FormatQueryOptionNotSupported); + } + } + + /// + /// A string like "Found the following illegal system token while building a projection or expansion path: '{0}'" + /// + internal static string ALinq_IllegalSystemQueryOption(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_IllegalSystemQueryOption, p0); + } + + /// + /// A string like "Found a projection as a non-leaf segment in an expand path. Please rephrase your query. The projected property was : '{0}'" + /// + internal static string ALinq_IllegalPathStructure(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_IllegalPathStructure, p0); + } + + /// + /// A string like "Found an illegal type token '{0}' without a trailing navigation property." + /// + internal static string ALinq_TypeTokenWithNoTrailingNavProp(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeTokenWithNoTrailingNavProp, p0); + } + + /// + /// A string like "DataServiceKey attribute must specify at least one property name." + /// + internal static string DSKAttribute_MustSpecifyAtleastOnePropertyName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DSKAttribute_MustSpecifyAtleastOnePropertyName); + } + } + + /// + /// A string like "Target collection for the Load operation must have an associated DataServiceContext." + /// + internal static string DataServiceCollection_LoadRequiresTargetCollectionObserved { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_LoadRequiresTargetCollectionObserved); + } + } + + /// + /// A string like "The tracking of DataServiceCollection can not be stopped for child collections." + /// + internal static string DataServiceCollection_CannotStopTrackingChildCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_CannotStopTrackingChildCollection); + } + } + + /// + /// A string like "This operation is only supported on collections that are being tracked." + /// + internal static string DataServiceCollection_OperationForTrackedOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_OperationForTrackedOnly); + } + } + + /// + /// A string like "The DataServiceContext to which the DataServiceCollection instance belongs could not be determined. The DataServiceContext must either be supplied in the DataServiceCollection constructor or be used to create the DataServiceQuery or QueryOperationResponse object that is the source of the items in the DataServiceCollection." + /// + internal static string DataServiceCollection_CannotDetermineContextFromItems { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_CannotDetermineContextFromItems); + } + } + + /// + /// A string like "An item could not be added to the collection. When items in a DataServiceCollection are tracked by the DataServiceContext, new items cannot be added before items have been loaded into the collection." + /// + internal static string DataServiceCollection_InsertIntoTrackedButNotLoadedCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_InsertIntoTrackedButNotLoadedCollection); + } + } + + /// + /// A string like "A previous LoadAsync operation has not yet completed. You cannot call the LoadAsync method on the DataServiceCollection again until the previous operation has completed." + /// + internal static string DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); + } + } + + /// + /// A string like "The LoadAsync method cannot be called when the DataServiceCollection is not a child collection of a related entity." + /// + internal static string DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity); + } + } + + /// + /// A string like "Only a typed DataServiceQuery object can be supplied when calling the LoadAsync method on DataServiceCollection." + /// + internal static string DataServiceCollection_LoadAsyncRequiresDataServiceQuery { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_LoadAsyncRequiresDataServiceQuery); + } + } + + /// + /// A string like "The DataServiceCollection to be tracked must contain entity typed elements with at least one key property. The element type '{0}' does not have any key property." + /// + internal static string DataBinding_DataServiceCollectionArgumentMustHaveEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_DataServiceCollectionArgumentMustHaveEntityType, p0); + } + + /// + /// A string like "Setting an instance of DataServiceCollection to an entity property is disallowed if the instance is already being tracked. Error occurred on property '{0}' for entity type '{1}'." + /// + internal static string DataBinding_CollectionPropertySetterValueHasObserver(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_CollectionPropertySetterValueHasObserver, p0, p1); + } + + /// + /// A string like "Unexpected action '{0}' on the OnCollectionChanged event raised by DataServiceCollection." + /// + internal static string DataBinding_DataServiceCollectionChangedUnknownActionCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_DataServiceCollectionChangedUnknownActionCollection, p0); + } + + /// + /// A string like "Unexpected action '{0}' on the OnCollectionChanged event raised by a collection object of type '{1}'." + /// + internal static string DataBinding_CollectionChangedUnknownActionCollection(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_CollectionChangedUnknownActionCollection, p0, p1); + } + + /// + /// A string like "Add/Update/Delete operation cannot be performed on a child entity, if the parent entity is already detached." + /// + internal static string DataBinding_BindingOperation_DetachedSource { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_BindingOperation_DetachedSource); + } + } + + /// + /// A string like "Null values are disallowed during '{0}' operations on DataServiceCollection." + /// + internal static string DataBinding_BindingOperation_ArrayItemNull(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_BindingOperation_ArrayItemNull, p0); + } + + /// + /// A string like "A value provided during '{0}' operation on DataServiceCollection is not of an entity type with key." + /// + internal static string DataBinding_BindingOperation_ArrayItemNotEntity(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_BindingOperation_ArrayItemNotEntity, p0); + } + + /// + /// A string like "Entity set name has not been provided for an entity of type '{0}'." + /// + internal static string DataBinding_Util_UnknownEntitySetName(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_Util_UnknownEntitySetName, p0); + } + + /// + /// A string like "An attempt was made to add entity of type '{0}' to a collection in which the same entity already exists." + /// + internal static string DataBinding_EntityAlreadyInCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_EntityAlreadyInCollection, p0); + } + + /// + /// A string like "An attempt to track an entity or complex type failed because the entity or complex type '{0}' does not implement the INotifyPropertyChanged interface." + /// + internal static string DataBinding_NotifyPropertyChangedNotImpl(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_NotifyPropertyChangedNotImpl, p0); + } + + /// + /// A string like "An attempt to track an entity or complex type failed because the entity or complex type contains a collection property of type '{0}' that does not implement the INotifyCollectionChanged interface." + /// + internal static string DataBinding_NotifyCollectionChangedNotImpl(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_NotifyCollectionChangedNotImpl, p0); + } + + /// + /// A string like "An attempt to track a complex object of type '{0}' failed because the complex object is already being tracked." + /// + internal static string DataBinding_ComplexObjectAssociatedWithMultipleEntities(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_ComplexObjectAssociatedWithMultipleEntities, p0); + } + + /// + /// A string like "An attempt to track a collection object of type '{0}' failed because the collection object is already being tracked." + /// + internal static string DataBinding_CollectionAssociatedWithMultipleEntities(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_CollectionAssociatedWithMultipleEntities, p0); + } + + /// + /// A string like "Expected exactly one Atom entry in the response from the server, but none was found." + /// + internal static string AtomParser_SingleEntry_NoneFound { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomParser_SingleEntry_NoneFound); + } + } + + /// + /// A string like "Expected exactly one Atom entry in the response from the server, but more than one was found." + /// + internal static string AtomParser_SingleEntry_MultipleFound { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomParser_SingleEntry_MultipleFound); + } + } + + /// + /// A string like "Expected an Atom feed or entry in the response from the server, but found an unexpected element instead." + /// + internal static string AtomParser_SingleEntry_ExpectedFeedOrEntry { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomParser_SingleEntry_ExpectedFeedOrEntry); + } + } + + /// + /// A string like "The null value from property '{0}' cannot be assigned to a type '{1}'." + /// + internal static string AtomMaterializer_CannotAssignNull(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_CannotAssignNull, p0, p1); + } + + /// + /// A string like "An entry of type '{0}' cannot be added to a collection that contains instances of type '{1}'. This may occur when an existing entry of a different type has the same identity value or when the same entity is projected into two different types in a single query." + /// + internal static string AtomMaterializer_EntryIntoCollectionMismatch(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_EntryIntoCollectionMismatch, p0, p1); + } + + /// + /// A string like "An entry returned by the navigation property '{0}' is null and cannot be initialized. You should check for a null value before accessing this property." + /// + internal static string AtomMaterializer_EntryToAccessIsNull(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_EntryToAccessIsNull, p0); + } + + /// + /// A string like "An entry that contains the data required to create an instance of type '{0}' is null and cannot be initialized. You should check for a null value before accessing this entry." + /// + internal static string AtomMaterializer_EntryToInitializeIsNull(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_EntryToInitializeIsNull, p0); + } + + /// + /// A string like "An entity of type '{0}' cannot be projected because there is already an instance of type '{1}' for '{2}'." + /// + internal static string AtomMaterializer_ProjectEntityTypeMismatch(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_ProjectEntityTypeMismatch, p0, p1, p2); + } + + /// + /// A string like "The expected property '{0}' could not be found while processing an entry. Check for null before accessing this property." + /// + internal static string AtomMaterializer_PropertyMissing(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_PropertyMissing, p0); + } + + /// + /// A string like "Property '{0}' is not an entity." + /// + internal static string AtomMaterializer_PropertyNotExpectedEntry(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_PropertyNotExpectedEntry, p0); + } + + /// + /// A string like "A DataServiceCollection can only contain entity types. Primitive and complex types cannot be contained by this kind of collection." + /// + internal static string AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities); + } + } + + /// + /// A string like "Collection property '{0}' cannot be created because the type '{1}' does not have a public parameterless constructor." + /// + internal static string AtomMaterializer_NoParameterlessCtorForCollectionProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_NoParameterlessCtorForCollectionProperty, p0, p1); + } + + /// + /// A string like "The element '{0}' is not a valid collection item. The name of the collection item element must be 'element' and must belong to the 'http://docs.oasis-open.org/odata/ns/data' namespace." + /// + internal static string AtomMaterializer_InvalidCollectionItem(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidCollectionItem, p0); + } + + /// + /// A string like "There is a type mismatch between the client and the service. Type '{0}' is an entity type, but the type in the response payload does not represent an entity type. Please ensure that types defined on the client match the data model of the service, or update the service reference on the client." + /// + internal static string AtomMaterializer_InvalidEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidEntityType, p0); + } + + /// + /// A string like "There is a type mismatch between the client and the service. Type '{0}' is not an entity type, but the type in the response payload represents an entity type. Please ensure that types defined on the client match the data model of the service, or update the service reference on the client." + /// + internal static string AtomMaterializer_InvalidNonEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidNonEntityType, p0); + } + + /// + /// A string like "Materialization of top level collection expected ICollection<>, but actual type was {0}." + /// + internal static string AtomMaterializer_CollectionExpectedCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_CollectionExpectedCollection, p0); + } + + /// + /// A string like "The response payload is a not a valid response payload. Please make sure that the top level element is a valid Atom or JSON element or belongs to '{0}' namespace." + /// + internal static string AtomMaterializer_InvalidResponsePayload(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidResponsePayload, p0); + } + + /// + /// A string like "The response content type '{0}' is not currently supported." + /// + internal static string AtomMaterializer_InvalidContentTypeEncountered(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidContentTypeEncountered, p0); + } + + /// + /// A string like "Cannot materialize the results into a collection type '{0}' because it does not have a parameterless constructor." + /// + internal static string AtomMaterializer_MaterializationTypeError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_MaterializationTypeError, p0); + } + + /// + /// A string like "Reset should never be called for collection reader in an internal enumerable." + /// + internal static string AtomMaterializer_ResetAfterEnumeratorCreationError { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_ResetAfterEnumeratorCreationError); + } + } + + /// + /// A string like "Cannot materialize a collection of a primitives or complex without the type '{0}' being a collection." + /// + internal static string AtomMaterializer_TypeShouldBeCollectionError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_TypeShouldBeCollectionError, p0); + } + + /// + /// A string like "A circular loop was detected while serializing the property '{0}'. You must make sure that loops are not present in properties that return a collection or complex type." + /// + internal static string Serializer_LoopsNotAllowedInComplexTypes(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_LoopsNotAllowedInComplexTypes, p0); + } + + /// + /// A string like "A circular loop was detected while serializing the complex type '{0}'. You must make sure that loops are not present in a collection or a complex type." + /// + internal static string Serializer_LoopsNotAllowedInNonPropertyComplexTypes(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_LoopsNotAllowedInNonPropertyComplexTypes, p0); + } + + /// + /// A string like "The operation parameter named '{0}' has a collection item of Edm type kind '{1}'. A collection item must be either a primitive type or a complex Edm type kind." + /// + internal static string Serializer_InvalidCollectionParamterItemType(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_InvalidCollectionParamterItemType, p0, p1); + } + + /// + /// A string like "The operation parameter named '{0}' has a null collection item. The items of a collection must not be null." + /// + internal static string Serializer_NullCollectionParamterItemValue(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_NullCollectionParamterItemValue, p0); + } + + /// + /// A string like "The operation parameter named '{0}' was of Edm type kind '{1}'. An operation parameter must be either a primitive type, a complex type or a collection of primitive or complex types." + /// + internal static string Serializer_InvalidParameterType(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_InvalidParameterType, p0, p1); + } + + /// + /// A string like "The parameter alias '{0}' was not present in the request URI. All parameters passed as alias must be present in the request URI." + /// + internal static string Serializer_UriDoesNotContainParameterAlias(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_UriDoesNotContainParameterAlias, p0); + } + + /// + /// A string like "The enum type '{0}' has no member named '{1}'." + /// + internal static string Serializer_InvalidEnumMemberValue(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_InvalidEnumMemberValue, p0, p1); + } + + /// + /// A string like "This target framework does not enable you to directly enumerate over a data service query. This is because enumeration automatically sends a synchronous request to the data service. Because this framework only supports asynchronous operations, you must instead call the BeginExecute and EndExecute methods to obtain a query result that supports enumeration." + /// + internal static string DataServiceQuery_EnumerationNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceQuery_EnumerationNotSupported); + } + } + + /// + /// A string like "Only instances of HttpWebRequest are currently allowed for this property. Other subtypes of WebRequest are not supported." + /// + internal static string Context_SendingRequestEventArgsNotHttp { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SendingRequestEventArgsNotHttp); + } + } + + /// + /// A string like "An internal error '{0}' occurred." + /// + internal static string General_InternalError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.General_InternalError, p0); + } + + /// + /// A string like "The entity set '{0}' doesn't have the 'OData.EntitySetUri' annotation. This annotation is required." + /// + internal static string ODataMetadataBuilder_MissingEntitySetUri(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataMetadataBuilder_MissingEntitySetUri, p0); + } + + /// + /// A string like "The entity set '{0}' has a URI '{1}' which has no path segments. An entity set URI suffix cannot be appended to a URI without path segments." + /// + internal static string ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix, p0, p1); + } + + /// + /// A string like "Neither the 'OData.EntityInstanceUri' nor the 'OData.EntitySetUriSuffix' annotation was found for entity set '{0}'. One of these annotations is required." + /// + internal static string ODataMetadataBuilder_MissingEntityInstanceUri(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataMetadataBuilder_MissingEntityInstanceUri, p0); + } + + /// + /// A string like "The type '{0}' was found for a primitive value. In OData, the type '{0}' is not a supported primitive type." + /// + internal static string EdmValueUtils_UnsupportedPrimitiveType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_UnsupportedPrimitiveType, p0); + } + + /// + /// A string like "Incompatible primitive type kinds were found. The type '{0}' was found to be of kind '{2}' instead of the expected kind '{1}'." + /// + internal static string EdmValueUtils_IncorrectPrimitiveTypeKind(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_IncorrectPrimitiveTypeKind, p0, p1, p2); + } + + /// + /// A string like "Incompatible primitive type kinds were found. Found type kind '{0}' instead of the expected kind '{1}'." + /// + internal static string EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName, p0, p1); + } + + /// + /// A string like "A value with primitive kind '{0}' cannot be converted into a primitive object value." + /// + internal static string EdmValueUtils_CannotConvertTypeToClrValue(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_CannotConvertTypeToClrValue, p0); + } + + /// + /// A string like "The value '{0}' is not a valid duration value." + /// + internal static string ValueParser_InvalidDuration(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ValueParser_InvalidDuration, p0); + } + + /// + /// A string like "The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information." + /// + internal static string PlatformHelper_DateTimeOffsetMustContainTimeZone(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.PlatformHelper_DateTimeOffsetMustContainTimeZone, p0); + } + + /// + /// A string like "Silverlight Browser Http Stack is not supported on the Portable Library, only Client Http is supported." + /// + internal static string Silverlight_BrowserHttp_NotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Silverlight_BrowserHttp_NotSupported); + } + } + + /// + /// A string like "Parameters of type DataServiceQuery<T> can not be used as the input enumerators for DataServiceCollection. Try using result of DataServiceQuery<T>.EndExecute instead." + /// + internal static string DataServiceCollection_DataServiceQueryCanNotBeEnumerated { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_DataServiceQueryCanNotBeEnumerated); + } + } + + } + + /// + /// Strongly-typed and parameterized exception factory. + /// + internal static partial class Error { + + /// + /// The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument. + /// + internal static Exception ArgumentNull(string paramName) { + return new ArgumentNullException(paramName); + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method. + /// + internal static Exception ArgumentOutOfRange(string paramName) { + return new ArgumentOutOfRangeException(paramName); + } + + /// + /// The exception that is thrown when the author has not yet implemented the logic at this point in the program. This can act as an exception based TODO tag. + /// + internal static Exception NotImplemented() { + return new NotImplementedException(); + } + + /// + /// The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality. + /// + internal static Exception NotSupported() { + return new NotSupportedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.tt new file mode 100644 index 0000000..925102e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.Portable/Parameterized.Microsoft.OData.Client.Portable.tt @@ -0,0 +1,20 @@ +<#@ include file="..\..\..\tools\StringResourceGenerator\StringsClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData.Client"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Client"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "..\\Microsoft.OData.Client.Common.txt", + "Microsoft.OData.Client.Portable.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.props b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.props new file mode 100644 index 0000000..25d815d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Build.props @@ -0,0 +1,15 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/BuildingRequestEventArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BuildingRequestEventArgs.cs new file mode 100644 index 0000000..13b6584 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/BuildingRequestEventArgs.cs @@ -0,0 +1,102 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + + /// + /// EventArgs for the BuildingRequest event. + /// + public class BuildingRequestEventArgs : EventArgs + { + /// + /// Uri of the outgoing request. + /// + private Uri requestUri; + + /// + /// Initializes a new instance of the class. + /// + /// The method. + /// The request URI. + /// The request headers. + /// Descriptor for this request; or null if there isn't one. + /// The http stack. + internal BuildingRequestEventArgs(string method, Uri requestUri, HeaderCollection headers, Descriptor descriptor, HttpStack httpStack) + { + this.Method = method; + this.RequestUri = requestUri; + this.HeaderCollection = headers ?? new HeaderCollection(); + this.ClientHttpStack = httpStack; + this.Descriptor = descriptor; + } + + /// + /// Gets the Request HTTP Method that the outgoing request will use. + /// + public string Method { get; set; } + + /// + /// The Uri of the outgoing request. The Uri may be altered. No error checking will be performed against any changes made. + /// + public Uri RequestUri + { + get + { + return this.requestUri; + } + + set + { + this.requestUri = value; + } + } + + /// + /// The headers for this request. Adding new custom headers is supported. Behavior is undefined for changing existing headers or adding + /// system headers. No error checking will be performed against any changes made. + /// + public IDictionary Headers + { + get + { + return this.HeaderCollection.UnderlyingDictionary; + } + } + + /// + /// Descriptor for this request if there is one; null otherwise. + /// + public Descriptor Descriptor { get; private set; } + + /// + /// Gets the http stack. + /// + /// + /// The reason for having this property is that this is request specific + /// and cannot be taken from the context. For e.g. In silverlight, irrespective + /// of the value of HttpStack property, for stream requests (get or update), we + /// use ClientHttp. + /// + internal HttpStack ClientHttpStack { get; private set; } + + /// + /// Returns the set of headers as HeaderCollection instance. + /// + internal HeaderCollection HeaderCollection { get; private set; } + + /// + /// Retrieves a new RequestMessageArgs with any custom query parameters added. + /// + /// A new RequestMessageArgs instance that takes new custom query options into account. + internal BuildingRequestEventArgs Clone() + { + return new BuildingRequestEventArgs(this.Method, this.RequestUri, this.HeaderCollection, this.Descriptor, this.ClientHttpStack); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ChangesetResponse.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ChangesetResponse.cs new file mode 100644 index 0000000..e4e1021 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ChangesetResponse.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Collections.Generic; + using System.Diagnostics; + + /// Response from SaveChanges. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710", Justification = "required for this feature")] + public sealed class ChangeOperationResponse : OperationResponse + { + /// Descriptor containing the response object. + private Descriptor descriptor; + + /// Initializes a new instance of the class. + /// HTTP headers + /// response object containing information about resources that got changed. + internal ChangeOperationResponse(HeaderCollection headers, Descriptor descriptor) + : base(headers) + { + Debug.Assert(descriptor != null, "descriptor != null"); + this.descriptor = descriptor; + } + + /// Gets the or modified by a change operation. + /// An or modified by a change operation. + public Descriptor Descriptor + { + get { return this.descriptor; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientConvert.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientConvert.cs new file mode 100644 index 0000000..2307cee --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientConvert.cs @@ -0,0 +1,177 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Reflection; + + #endregion Namespaces + + /// + /// static utility functions for conversions + /// + internal static class ClientConvert + { + /// + /// convert from string to the appropriate type + /// + /// incoming string value + /// type to convert to + /// converted value + internal static object ChangeType(string propertyValue, Type propertyType) + { + Debug.Assert(null != propertyValue, "should never be passed null"); + + PrimitiveType primitiveType; + if (PrimitiveType.TryGetPrimitiveType(propertyType, out primitiveType) && primitiveType.TypeConverter != null) + { + try + { + // functionality provided by type converter + return primitiveType.TypeConverter.Parse(propertyValue); + } + catch (FormatException ex) + { + propertyValue = (0 == propertyValue.Length ? "String.Empty" : "String"); + throw Error.InvalidOperation(Strings.Deserialize_Current(propertyType.ToString(), propertyValue), ex); + } + catch (OverflowException ex) + { + propertyValue = (0 == propertyValue.Length ? "String.Empty" : "String"); + throw Error.InvalidOperation(Strings.Deserialize_Current(propertyType.ToString(), propertyValue), ex); + } + } + else + { + Debug.Assert(false, "new StorageType without update to knownTypes"); + return propertyValue; + } + } + + /// + /// Tries to converts a binary value to a byte array. + /// + /// The binary value to convert. + /// The equivalent value converted to a byte array. + /// Whether the value was binary. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "binaryValue", + Justification = "Method is compiled into desktop and SL assemblies, and the parameter is used in the desktop version.")] + internal static bool TryConvertBinaryToByteArray(object binaryValue, out byte[] converted) + { + Debug.Assert(binaryValue != null, "binaryValue != null"); +#if !PORTABLELIB + Type valueType = binaryValue.GetType(); + PrimitiveType ptype; + if (PrimitiveType.TryGetPrimitiveType(valueType, out ptype) && valueType == BinaryTypeConverter.BinaryType) + { + const BindingFlags Flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod; + converted = (byte[])valueType.InvokeMember("ToArray", Flags, null, binaryValue, null, CultureInfo.InvariantCulture); + return true; + } +#endif + converted = null; + return false; + } + + /// + /// change primtive typeName into non-nullable type + /// + /// like Edm.String or Edm.Binary + /// the mapped output type + /// true if named + internal static bool ToNamedType(string typeName, out Type type) + { + type = typeof(string); + if (String.IsNullOrEmpty(typeName)) + { + return true; + } + else + { + PrimitiveType ptype; + if (PrimitiveType.TryGetPrimitiveType(typeName, out ptype)) + { + type = ptype.ClrType; + return true; + } + else + { + return false; + } + } + } + + /// + /// Convert from primitive value to an xml payload string. + /// + /// incoming object value + /// converted value + internal static string ToString(object propertyValue) + { + Debug.Assert(null != propertyValue, "null should be handled by caller"); + Debug.Assert(!(propertyValue is ODataUntypedValue), "!(propertyValue is ODataUntypedValue)"); + + PrimitiveType primitiveType; + if (PrimitiveType.TryGetPrimitiveType(propertyValue.GetType(), out primitiveType) && primitiveType.TypeConverter != null) + { + return primitiveType.TypeConverter.ToString(propertyValue); + } + + // If the type of a property is enum on server side, but it is System.String on client side, + // then propertyValue should be ODataEnumValue. We should return the enumValue.Value. + var enumValue = propertyValue as ODataEnumValue; + if (enumValue != null) + { + return enumValue.Value; + } + + Debug.Assert(false, "new StorageType without update to knownTypes"); + return propertyValue.ToString(); + } + + /// type edm type string for content + /// type to analyze + /// edm type string for payload, null for unknown + internal static string GetEdmType(Type propertyType) + { + PrimitiveType primitiveType; + if (PrimitiveType.TryGetPrimitiveType(propertyType, out primitiveType)) + { + // Map DateTime to DateTimeOffset + if (primitiveType.ClrType == typeof(DateTime)) + { + return XmlConstants.EdmDateTimeOffsetTypeName; + } + + if (primitiveType.EdmTypeName != null) + { + return primitiveType.EdmTypeName; + } + else + { + // case StorageType.UInt16: + // case StorageType.UInt32: + // case StorageType.UInt64: + // don't support reverse mappings for these types in this version + // allows us to add real server support in the future without a + // "breaking change" in the future client + throw new NotSupportedException(Strings.ALinq_CantCastToUnsupportedPrimitive(propertyType.Name)); + } + } + else + { + Debug.Assert(false, "knowntype without reverse mapping"); + return null; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmCollectionValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmCollectionValue.cs new file mode 100644 index 0000000..f5b21c1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmCollectionValue.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + + /// + /// Implementation of which wraps client-side objects. + /// + internal sealed class ClientEdmCollectionValue : IEdmCollectionValue + { + /// + /// Initializes a new instance of the class. + /// + /// The type of the collection. + /// The elements of the collection. + public ClientEdmCollectionValue(IEdmTypeReference type, IEnumerable elements) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(elements != null, "values != null"); + + this.Type = type; + this.Elements = elements.Select(v => (IEdmDelayedValue)new NullEdmDelayedValue(v)); + } + + /// + /// Gets the type of this value. + /// + public IEdmTypeReference Type { get; private set; } + + /// + /// Gets the kind of this value. + /// + public EdmValueKind ValueKind + { + get { return EdmValueKind.Collection; } + } + + /// + /// Gets the values stored in this collection. + /// + public IEnumerable Elements { get; private set; } + + /// + /// Non-delayed implementation of + /// + private class NullEdmDelayedValue : IEdmDelayedValue + { + /// + /// Initializes a new instance of the class. + /// + /// The value. + public NullEdmDelayedValue(IEdmValue value) + { + this.Value = value; + } + + /// + /// Gets the data stored in this value. + /// + public IEdmValue Value { get; private set; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmModel.cs new file mode 100644 index 0000000..3554b41 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmModel.cs @@ -0,0 +1,745 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces. + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Reflection; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Client.Providers; + using Microsoft.OData; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + using c = Microsoft.OData.Client; + +#if PORTABLELIB && WINDOWSPHONE + // Windows Phone 8.0 doesn't support ConcurrentDictionary + using ConcurrentEdmSchemaDictionary = System.Collections.Generic.Dictionary; +#else + using ConcurrentEdmSchemaDictionary = System.Collections.Concurrent.ConcurrentDictionary; +#endif + + #endregion Namespaces. + + /// + /// EdmModel describing the client metadata + /// + internal sealed class ClientEdmModel : EdmElement, IEdmModel + { + /// A cache that maps a client Clr type to it corresponding Edm type. + private readonly Dictionary clrToEdmTypeCache = new Dictionary(EqualityComparer.Default); + + /// A cache that maps a client type name to the corresponding client type annotation. + private readonly Dictionary typeNameToClientTypeAnnotationCache = + new Dictionary(StringComparer.Ordinal); + + /// The annotations manager. + private readonly EdmDirectValueAnnotationsManager directValueAnnotationsManager = new EdmDirectValueAnnotationsManager(); + + /// The max protocol version this Edm model is created for. + private readonly ODataProtocolVersion maxProtocolVersion; + + /// Referenced core model. + private readonly IEnumerable coreModel = new IEdmModel[] { EdmCoreModel.Instance }; + + /// + /// Constructor. + /// + /// The protocol version this Edm model is created for. + internal ClientEdmModel(ODataProtocolVersion maxProtocolVersion) + { + this.maxProtocolVersion = maxProtocolVersion; + this.EdmStructuredSchemaElements = new ConcurrentEdmSchemaDictionary(); + } + + /// + /// Returns all the vocabulary annotations defined in the model. + /// + public IEnumerable VocabularyAnnotations + { + get { return Enumerable.Empty(); } + } + + /// + /// Returns all the referenced models. + /// + public IEnumerable ReferencedModels + { + get { return this.coreModel; } + } + + /// + /// Returns all the schema elements. + /// + public IEnumerable SchemaElements + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets the collection of namespaces that schema elements use contained in this model. + /// + public IEnumerable DeclaredNamespaces + { + get { throw new NotImplementedException(); } + } + + /// + /// Returns the model's annotations manager. + /// + public IEdmDirectValueAnnotationsManager DirectValueAnnotationsManager + { + get + { + return this.directValueAnnotationsManager; + } + } + + /// + /// Gets the only one entity container of the model. + /// + public IEdmEntityContainer EntityContainer + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets the state of edm structured schema elements keyed by their Name. + /// + internal ConcurrentEdmSchemaDictionary EdmStructuredSchemaElements + { + get; + private set; + } + + /// + /// Gets the max protocol version of the model. + /// + internal ODataProtocolVersion MaxProtocolVersion + { + get { return this.maxProtocolVersion; } + } + + /// + /// Searches for any functionImport or actionImport by name and parameter names. + /// + /// The name of the operation imports to find. May be qualified with the namespace. + /// The parameter names of the parameters. + /// The operation imports that matches the search criteria or empty there was no match. + public IEnumerable FindOperationImportsByNameNonBindingParameterType(string operationImportName, IEnumerable parameterNames) + { + throw new NotImplementedException(); + } + + /// + /// Searches for a schema element with the given name in this model and returns null if no such schema element exists. + /// + /// The qualified name of the schema element being found. + /// The requested schema element, or null if no such schema element exists. + public IEdmSchemaType FindDeclaredType(string qualifiedName) + { + ClientTypeAnnotation clientTypeAnnotation = null; + if (this.typeNameToClientTypeAnnotationCache.TryGetValue(qualifiedName, out clientTypeAnnotation)) + { + return (IEdmSchemaType)clientTypeAnnotation.EdmType; + } + + return null; + } + + /// + /// Searches for operations with the given name in this model and returns null if no such operation exists. + /// + /// The qualified name of the operation being found. + /// A set operations sharing the specified qualified name, or an empty enumerable if no such operation exists. + public IEnumerable FindDeclaredOperations(string qualifiedName) + { + throw new NotImplementedException(); + } + + /// + /// Searches for bound operations based on the binding type, returns an empty enumerable if no operation exists. + /// + /// Type of the binding. + /// A set of operations that share the binding type or empty enumerable if no such operation exists. + public IEnumerable FindDeclaredBoundOperations(IEdmType bindingType) + { + throw new NotImplementedException(); + } + + /// + /// Searches for bound operations based on the qualified name and binding type, returns an empty enumerable if no operation exists. + /// + /// The qualified name of the operation. + /// Type of the binding. + /// + /// A set of operations that share the qualified name and binding type or empty enumerable if no such operation exists. + /// + public IEnumerable FindDeclaredBoundOperations(string qualifiedName, IEdmType bindingType) + { + throw new NotImplementedException(); + } + + /// + /// Searches for a term with the given name in this model and returns null if no such term exists. + /// + /// The qualified name of the term being found. + /// The requested term, or null if no such term exists. + public IEdmTerm FindDeclaredTerm(string qualifiedName) + { + return null; + } + + /// + /// Finds a list of types that derive directly from the supplied type. + /// + /// The base type that derived types are being searched for. + /// A list of types that derive directly from the type. + public IEnumerable FindDirectlyDerivedTypes(IEdmStructuredType type) + { + throw new NotImplementedException(); + } + + /// + /// Searches for vocabulary annotations specified by this model or a referenced model for a given element. + /// + /// The annotated element. + /// The vocabulary annotations for the element. + public IEnumerable FindDeclaredVocabularyAnnotations(IEdmVocabularyAnnotatable element) + { + return Enumerable.Empty(); + } + + /// + /// Get or create a client EDM type instance. + /// + /// type to wrap + /// client type + internal IEdmType GetOrCreateEdmType(Type type) + { + Debug.Assert(type != null, "type != null"); + + EdmTypeCacheValue cachedEdmType; + lock (this.clrToEdmTypeCache) + { + this.clrToEdmTypeCache.TryGetValue(type, out cachedEdmType); + } + + if (cachedEdmType == null) + { + if (PrimitiveType.IsKnownNullableType(type)) + { + cachedEdmType = this.GetOrCreateEdmTypeInternal(null /*baseType*/, type, ClientTypeUtil.EmptyPropertyInfoArray, false /*isEntity*/, false /*hasProperties*/); + } + else + { + PropertyInfo[] keyProperties; + bool hasProperties; + Type[] hierarchy = ClientEdmModel.GetTypeHierarchy(type, out keyProperties, out hasProperties); + + Debug.Assert(keyProperties == null || keyProperties.Length == 0 || keyProperties.All(p => p.DeclaringType == keyProperties[0].DeclaringType), "All key properties must be declared on the same type."); + + bool isEntity = keyProperties != null; + keyProperties = keyProperties ?? ClientTypeUtil.EmptyPropertyInfoArray; + foreach (Type t in hierarchy) + { + // Pass in the full list of key properties for the most base type to be added there. We only allow key properties to be + // declared on the same type. + IEdmStructuredType edmBaseType = cachedEdmType == null ? null : cachedEdmType.EdmType as IEdmStructuredType; + cachedEdmType = this.GetOrCreateEdmTypeInternal(edmBaseType, t, keyProperties, isEntity, t == type ? hasProperties : (bool?)null); + + // Pass in an empty PropertyInfo array on subsequent derived types. + keyProperties = ClientTypeUtil.EmptyPropertyInfoArray; + } + } + } + + Debug.Assert(cachedEdmType != null, "cachedEdmType != null"); + this.ValidateComplexType(type, cachedEdmType); + return cachedEdmType.EdmType; + } + + /// + /// Get the client type annotation for the given name. + /// + /// Name of the type. + /// An instance of ClientTypeAnnotation for the type with the given name. + internal ClientTypeAnnotation GetClientTypeAnnotation(string edmTypeName) + { + Debug.Assert(WebUtil.GetCollectionItemWireTypeName(edmTypeName) == null, "This method must not be called for collections"); + IEdmType result = this.clrToEdmTypeCache.Values.First(e => e.EdmType.FullName() == edmTypeName).EdmType; + Debug.Assert(result != null, "result != null"); + return this.GetClientTypeAnnotation(result); + } + + /// Returns and its base types, in the order of most base type first and last. + /// Type instance in question. + /// Returns the list of key properties if is an entity type; null otherwise. + /// true if has any (declared or inherited) properties; otherwise false. + /// Returns and its base types, in the order of most base type first and last. + private static Type[] GetTypeHierarchy(Type type, out PropertyInfo[] keyProperties, out bool hasProperties) + { + Debug.Assert(type != null, "type != null"); + + keyProperties = ClientTypeUtil.GetKeyPropertiesOnType(type, out hasProperties); + + List hierarchy = new List(); + if (keyProperties != null) + { + // type is an entity. Return all types between keyPropertyDeclaredType and type inclusive. + Type baseEntityType; + if (keyProperties.Length > 0) + { + baseEntityType = keyProperties[0].DeclaringType; + } + else + { + // Find the type where the DataServiceEntityAttribute is declared on. + baseEntityType = type; + Debug.Assert(type.GetCustomAttributes(true).OfType().Any(), "type.GetCustomAttributes(true).OfType().Any()"); + while (!baseEntityType.GetCustomAttributes(false).OfType().Any() && c.PlatformHelper.GetBaseType(baseEntityType) != null) + { + baseEntityType = c.PlatformHelper.GetBaseType(baseEntityType); + } + + Debug.Assert(baseEntityType != null, "keyPropertyDeclaringType != null"); + } + + do + { + hierarchy.Insert(0, type); + } + while (type != baseEntityType && (type = c.PlatformHelper.GetBaseType(type)) != null); + } + else + { + // type is a complex type. Return all types on the hierarchy where there are properties defined. + do + { + hierarchy.Insert(0, type); + } + while ((type = c.PlatformHelper.GetBaseType(type)) != null && ClientTypeUtil.GetPropertiesOnType(type, false /*declaredOnly*/).Any()); + } + + return hierarchy.ToArray(); + } + + /// + /// Throw if the given complex type has no properties. + /// + /// The type in question + /// The EdmTypeCacheValue of the type in question. + private void ValidateComplexType(Type type, EdmTypeCacheValue cachedEdmType) + { + Debug.Assert(cachedEdmType != null, "cachedEdmType != null"); + + if (cachedEdmType.EdmType.TypeKind == EdmTypeKind.Complex) + { + bool? hasProperties = cachedEdmType.HasProperties; + if (!hasProperties.HasValue) + { + hasProperties = ClientTypeUtil.GetPropertiesOnType(type, /*declaredOnly*/false).Any(); + + lock (this.clrToEdmTypeCache) + { + EdmTypeCacheValue existing = this.clrToEdmTypeCache[type]; + existing.HasProperties = hasProperties; + } + } + + if (hasProperties == false && (type == typeof(System.Object) || type.IsGenericType())) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_NoSettableFields(type.ToString())); + } + } + } + + /// + /// Find properties with dynamic MIME type related properties and + /// set the references from each ClientProperty to its related MIME type property + /// + /// Client edm type instance to wire up the mime type properties. + private void SetMimeTypeForProperties(IEdmStructuredType edmStructuredType) + { + MimeTypePropertyAttribute attribute = (MimeTypePropertyAttribute)this.GetClientTypeAnnotation(edmStructuredType).ElementType.GetCustomAttributes(typeof(MimeTypePropertyAttribute), true).SingleOrDefault(); + if (null != attribute) + { + IEdmProperty dataProperty = edmStructuredType.Properties().SingleOrDefault(p => p.Name == attribute.DataPropertyName); + if (dataProperty == null) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_MissingMimeTypeDataProperty(this.GetClientTypeAnnotation(edmStructuredType).ElementTypeName, attribute.DataPropertyName)); + } + + IEdmProperty mimeTypeProperty = edmStructuredType.Properties().SingleOrDefault(p => p.Name == attribute.MimeTypePropertyName); + if (mimeTypeProperty == null) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_MissingMimeTypeProperty(this.GetClientTypeAnnotation(edmStructuredType).ElementTypeName, attribute.MimeTypePropertyName)); + } + + this.GetClientPropertyAnnotation(dataProperty).MimeTypeProperty = this.GetClientPropertyAnnotation(mimeTypeProperty); + } + } + + /// + /// Get or create a client EDM type instance. + /// + /// The base type of this structured type. + /// type to wrap + /// List of key properties to add to if the type is an entity type; null otherwise. + /// true if is an entity type; false otherwise. + /// true if the is known to have properties; false if is known to have no properties; null if nothing is known about the properties. + /// client type + [SuppressMessage("Microsoft.Maintainability", "CA1502", Justification = "cyclomatic complexity")] + [SuppressMessage("Microsoft.Maintainability", "CA1506:MethodCoupledWithTooManyTypesFromDifferentNamespaces", Justification = "should refactor the method in the future.")] + private EdmTypeCacheValue GetOrCreateEdmTypeInternal(IEdmStructuredType edmBaseType, Type type, PropertyInfo[] keyProperties, bool isEntity, bool? hasProperties) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(keyProperties != null, "keyProperties != null"); + + EdmTypeCacheValue cachedEdmType; + lock (this.clrToEdmTypeCache) + { + this.clrToEdmTypeCache.TryGetValue(type, out cachedEdmType); + } + + if (cachedEdmType == null) + { + Type collectionType; + bool isOpen = false; + IEdmSchemaElement edmSchemaElement = null; + if (EdmStructuredSchemaElements.TryGetValue(ClientTypeUtil.GetServerDefinedTypeName(type), out edmSchemaElement)) + { + var edmStructuredType = edmSchemaElement as IEdmStructuredType; + if (edmStructuredType != null) + { + isOpen = edmStructuredType.IsOpen; + } + } + + if (PrimitiveType.IsKnownNullableType(type)) + { + PrimitiveType primitiveType; + PrimitiveType.TryGetPrimitiveType(type, out primitiveType); + Debug.Assert(primitiveType != null, "primitiveType != null"); + cachedEdmType = new EdmTypeCacheValue(primitiveType.CreateEdmPrimitiveType(), hasProperties); + } + else if ((collectionType = ClientTypeUtil.GetImplementationType(type, typeof(ICollection<>))) != null && ClientTypeUtil.GetImplementationType(type, typeof(IDictionary<,>)) == null) + { + // Collection Type + Type elementType = collectionType.GetGenericArguments()[0]; + IEdmType itemType = this.GetOrCreateEdmType(elementType); + + // Note that + // 1. throw here because collection of a collection is not allowed + // 2. will also throw during SaveChanges(), validated by unit test case 'SerializationOfCollection'in CollectionTests.cs. + if ((itemType.TypeKind == EdmTypeKind.Collection)) + { + throw new ODataException(Strings.ClientType_CollectionOfCollectionNotSupported); + } + + cachedEdmType = new EdmTypeCacheValue(new EdmCollectionType(itemType.ToEdmTypeReference(ClientTypeUtil.CanAssignNull(elementType))), hasProperties); + } + else + { + Type enumTypeTmp = null; + if (isEntity) + { + Action delayLoadEntityProperties = (entityType) => + { + // Create properties without modifying the entityType. + // This will leave entityType intact in case of an exception during loading. + List loadedProperties = new List(); + List loadedKeyProperties = new List(); + foreach (PropertyInfo property in ClientTypeUtil.GetPropertiesOnType(type, /*declaredOnly*/edmBaseType != null).OrderBy(p => p.Name)) + { + IEdmProperty edmProperty = this.CreateEdmProperty((EdmStructuredType)entityType, property); + loadedProperties.Add(edmProperty); + + if (edmBaseType == null && keyProperties.Any(k => k.DeclaringType == type && k.Name == property.Name)) + { + Debug.Assert(edmProperty.PropertyKind == EdmPropertyKind.Structural, "edmProperty.PropertyKind == EdmPropertyKind.Structural"); + Debug.Assert(edmProperty.Type.TypeKind() == EdmTypeKind.Primitive || edmProperty.Type.TypeKind() == EdmTypeKind.Enum, "edmProperty.Type.TypeKind() == EdmTypeKind.Primitive || edmProperty.Type.TypeKind() == EdmTypeKind.Enum"); + loadedKeyProperties.Add((IEdmStructuralProperty)edmProperty); + } + } + + // Now add properties to the entityType. + foreach (IEdmProperty property in loadedProperties) + { + entityType.AddProperty(property); + } + + entityType.AddKeys(loadedKeyProperties); + }; + + // Creating an entity type + Debug.Assert(edmBaseType == null || edmBaseType.TypeKind == EdmTypeKind.Entity, "baseType == null || baseType.TypeKind == EdmTypeKind.Entity"); + bool hasStream = GetHasStreamValue((IEdmEntityType)edmBaseType, type); + cachedEdmType = new EdmTypeCacheValue( + new EdmEntityTypeWithDelayLoadedProperties(CommonUtil.GetModelTypeNamespace(type), CommonUtil.GetModelTypeName(type), (IEdmEntityType)edmBaseType, c.PlatformHelper.IsAbstract(type), isOpen, hasStream, delayLoadEntityProperties), + hasProperties); + } + else if ((enumTypeTmp = Nullable.GetUnderlyingType(type) ?? type) != null + && enumTypeTmp.IsEnum()) + { + Action delayLoadEnumMembers = (enumType) => + { +#if PORTABLELIB + foreach (FieldInfo tmp in enumTypeTmp.GetFields().Where(fieldInfo => fieldInfo.IsStatic)) +#else + foreach (FieldInfo tmp in enumTypeTmp.GetFields(BindingFlags.Static | BindingFlags.Public)) +#endif + { + object memberValue = Enum.Parse(enumTypeTmp, tmp.Name, false); + enumType.AddMember(new EdmEnumMember(enumType, tmp.Name, new EdmEnumMemberValue((long)Convert.ChangeType(memberValue, typeof(long), CultureInfo.InvariantCulture.NumberFormat)))); + } + }; + + // underlying type may be Edm.Byte, Edm.SByte, Edm.Int16, Edm.Int32, or Edm.Int64. + Type underlyingType = Enum.GetUnderlyingType(enumTypeTmp); + IEdmPrimitiveType underlyingEdmType = (IEdmPrimitiveType)EdmCoreModel.Instance.FindDeclaredType("Edm." + underlyingType.Name); + Debug.Assert(underlyingEdmType != null, "underlyingEdmType != null"); + bool isFlags = enumTypeTmp.GetCustomAttributes(false).Any(s => s is FlagsAttribute); + cachedEdmType = new EdmTypeCacheValue( + new EdmEnumTypeWithDelayLoadedMembers(CommonUtil.GetModelTypeNamespace(enumTypeTmp), CommonUtil.GetModelTypeName(enumTypeTmp), underlyingEdmType, isFlags, delayLoadEnumMembers), + null); + } + else + { + Action delayLoadComplexProperties = (complexType) => + { + // Create properties without modifying the complexType. + // This will leave complexType intact in case of an exception during loading. + List loadedProperties = new List(); + foreach (PropertyInfo property in ClientTypeUtil.GetPropertiesOnType(type, /*declaredOnly*/edmBaseType != null).OrderBy(p => p.Name)) + { + IEdmProperty edmProperty = this.CreateEdmProperty(complexType, property); + loadedProperties.Add(edmProperty); + } + + // Now add properties to the complexType. + foreach (IEdmProperty property in loadedProperties) + { + complexType.AddProperty(property); + } + }; + + // Creating a complex type + Debug.Assert(edmBaseType == null || edmBaseType.TypeKind == EdmTypeKind.Complex, "baseType == null || baseType.TypeKind == EdmTypeKind.Complex"); + cachedEdmType = new EdmTypeCacheValue( + new EdmComplexTypeWithDelayLoadedProperties(CommonUtil.GetModelTypeNamespace(type), CommonUtil.GetModelTypeName(type), (IEdmComplexType)edmBaseType, c.PlatformHelper.IsAbstract(type), isOpen, delayLoadComplexProperties), + hasProperties); + } + } + + Debug.Assert(cachedEdmType != null, "cachedEdmType != null"); + + IEdmType edmType = cachedEdmType.EdmType; + ClientTypeAnnotation clientTypeAnnotation = this.GetOrCreateClientTypeAnnotation(edmType, type); + this.SetClientTypeAnnotation(edmType, clientTypeAnnotation); + + if (edmType.TypeKind == EdmTypeKind.Entity || edmType.TypeKind == EdmTypeKind.Complex) + { + IEdmStructuredType edmStructuredType = edmType as IEdmStructuredType; + Debug.Assert(edmStructuredType != null, "edmStructuredType != null"); + this.SetMimeTypeForProperties(edmStructuredType); + } + + // Need to cache the type before loading the properties so we don't stack overflow because + // loading the property can trigger calls to GetOrCreateEdmType on the same type. + lock (this.clrToEdmTypeCache) + { + EdmTypeCacheValue existing; + if (this.clrToEdmTypeCache.TryGetValue(type, out existing)) + { + cachedEdmType = existing; + } + else + { + this.clrToEdmTypeCache.Add(type, cachedEdmType); + } + } + } + + return cachedEdmType; + } + + /// + /// Creates an Edm property. + /// + /// Type declaring this property. + /// PropertyInfo instance for this property. + /// Returns a new instance of Edm property. + private IEdmProperty CreateEdmProperty(IEdmStructuredType declaringType, PropertyInfo propertyInfo) + { + IEdmType propertyEdmType = this.GetOrCreateEdmType(propertyInfo.PropertyType); + Debug.Assert( + propertyEdmType.TypeKind == EdmTypeKind.Entity || + propertyEdmType.TypeKind == EdmTypeKind.Complex || + propertyEdmType.TypeKind == EdmTypeKind.Enum || + propertyEdmType.TypeKind == EdmTypeKind.Primitive || + propertyEdmType.TypeKind == EdmTypeKind.Collection, + "Property kind should be Entity, Complex, Enum, Primitive or Collection."); + + IEdmProperty edmProperty; + bool isPropertyNullable = ClientTypeUtil.CanAssignNull(propertyInfo.PropertyType); + if (propertyEdmType.TypeKind == EdmTypeKind.Entity || (propertyEdmType.TypeKind == EdmTypeKind.Collection && ((IEdmCollectionType)propertyEdmType).ElementType.TypeKind() == EdmTypeKind.Entity)) + { + IEdmEntityType declaringEntityType = declaringType as IEdmEntityType; + if (declaringEntityType == null) + { + throw c.Error.InvalidOperation(c.Strings.ClientTypeCache_NonEntityTypeCannotContainEntityProperties(propertyInfo.Name, propertyInfo.DeclaringType.ToString())); + } + + // Create a navigation property representing one side of an association. + // The partner representing the other side exists only inside this property and is not added to the target entity type, + // so it should not cause any name collisions. + edmProperty = EdmNavigationProperty.CreateNavigationPropertyWithPartner( + ClientTypeUtil.GetServerDefinedName(propertyInfo), + propertyEdmType.ToEdmTypeReference(isPropertyNullable), + /*dependentProperties*/ null, + /*principalProperties*/ null, + /*containsTarget*/ false, + EdmOnDeleteAction.None, + "Partner", + declaringEntityType.ToEdmTypeReference(true), + /*partnerDependentProperties*/ null, + /*partnerPrincipalProperties*/ null, + /*partnerContainsTarget*/ false, + EdmOnDeleteAction.None); + } + else + { + edmProperty = new EdmStructuralProperty(declaringType, ClientTypeUtil.GetServerDefinedName(propertyInfo), propertyEdmType.ToEdmTypeReference(isPropertyNullable)); + } + + edmProperty.SetClientPropertyAnnotation(new ClientPropertyAnnotation(edmProperty, propertyInfo, this)); + return edmProperty; + } + + /// + /// Gets or creates client type annotation. + /// + /// The EdmType to use for creating client type annotation + /// The Clr type to create client type annotation for. + /// Client type annotation + private ClientTypeAnnotation GetOrCreateClientTypeAnnotation(IEdmType edmType, Type type) + { + ClientTypeAnnotation clientTypeAnnotation; + string qualifiedName = type.ToString(); + + // all that are not built-in types need to be cached: enum, complex, entity + if (edmType.TypeKind == EdmTypeKind.Enum || edmType.TypeKind == EdmTypeKind.Complex || edmType.TypeKind == EdmTypeKind.Entity) + { + lock (this.typeNameToClientTypeAnnotationCache) + { + if (this.typeNameToClientTypeAnnotationCache.TryGetValue(qualifiedName, out clientTypeAnnotation) && clientTypeAnnotation.ElementType != type) + { + qualifiedName = type.AssemblyQualifiedName; + if (this.typeNameToClientTypeAnnotationCache.TryGetValue(qualifiedName, out clientTypeAnnotation) && clientTypeAnnotation.ElementType != type) + { + throw c.Error.InvalidOperation(Strings.ClientType_MultipleTypesWithSameName(qualifiedName)); + } + } + + if (clientTypeAnnotation == null) + { + clientTypeAnnotation = new ClientTypeAnnotation(edmType, type, qualifiedName, this); + this.typeNameToClientTypeAnnotationCache.Add(qualifiedName, clientTypeAnnotation); + } + else + { + Debug.Assert(clientTypeAnnotation.ElementType == type, "existing clientTypeAnnotation.ElementType == type"); + } + } + } + else + { + clientTypeAnnotation = new ClientTypeAnnotation(edmType, type, qualifiedName, this); + } + + return clientTypeAnnotation; + } + + /// + /// Check based on edm base entity type and attribute on CLR type whether or not + /// the to-be-created type is a media entity. + /// + /// Base EDM Entity type + /// The CLR type to check on + /// HasStream value to set on the to-be-created EntityType + private static bool GetHasStreamValue(IEdmEntityType edmBaseType, Type type) + { + // MediaEntryAttribute does not allow multiples, so there can be at most 1 instance on the type. + MediaEntryAttribute mediaEntryAttribute = (MediaEntryAttribute)type.GetCustomAttributes(typeof(MediaEntryAttribute), true).SingleOrDefault(); + if (mediaEntryAttribute != null) + { + return true; + } + + // HasStreamAttribute does not allow multiples, so there can be at most 1 instance on the type. + bool hasStreamAttribute = type.GetCustomAttributes(typeof(HasStreamAttribute), true).Any(); + return hasStreamAttribute; + } + + /// + /// Cache value for the type cache. + /// + private sealed class EdmTypeCacheValue + { + /// The cached EDM type. + private readonly IEdmType edmType; + + /// true if the Clr type this EDM type is based on has settable properties; otherwise false. + private bool? hasProperties; + + /// + /// Creates a new instance of the EDM type cache value. + /// + /// The cached EDM type. + /// true if the Clr type this EDM type is based on has settable properties; otherwise false. + public EdmTypeCacheValue(IEdmType edmType, bool? hasProperties) + { + this.edmType = edmType; + this.hasProperties = hasProperties; + } + + /// + /// The cached EDM type. + /// + public IEdmType EdmType + { + get + { + return this.edmType; + } + } + + /// + /// true if the Clr type this EDM type is based on has settable properties; otherwise false. + /// + public bool? HasProperties + { + get + { + return this.hasProperties; + } + + set + { + this.hasProperties = value; + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmStructuredValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmStructuredValue.cs new file mode 100644 index 0000000..aae7151 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ClientEdmStructuredValue.cs @@ -0,0 +1,168 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + + /// + /// Implementation of which wraps client-side objects. + /// + internal sealed class ClientEdmStructuredValue : IEdmStructuredValue + { + /// The structured value this instance is wrapping. + private readonly object structuredValue; + + /// The client-side metadata about this value. + private readonly ClientTypeAnnotation typeAnnotation; + + /// The model. + private readonly ClientEdmModel model; + + /// + /// Initializes a new instance of the class. + /// + /// The structured value. + /// The model. + /// The client type annotation. + public ClientEdmStructuredValue(object structuredValue, ClientEdmModel model, ClientTypeAnnotation clientTypeAnnotation) + { + Debug.Assert(structuredValue != null, "entity != null"); + Debug.Assert(model != null, "model != null"); + Debug.Assert(clientTypeAnnotation != null, "clientTypeAnnotation != null"); + + if (clientTypeAnnotation.EdmType.TypeKind == EdmTypeKind.Complex) + { + // TODO: nullable? + this.Type = new EdmComplexTypeReference((IEdmComplexType)clientTypeAnnotation.EdmType, true); + } + else + { + Debug.Assert(clientTypeAnnotation.EdmType.TypeKind == EdmTypeKind.Entity, "Only complex and entity values supported"); + + // TODO: nullable? + this.Type = new EdmEntityTypeReference((IEdmEntityType)clientTypeAnnotation.EdmType, true); + } + + this.structuredValue = structuredValue; + this.typeAnnotation = clientTypeAnnotation; + this.model = model; + } + + /// + /// Gets the type of this value. + /// + public IEdmTypeReference Type + { + get; + private set; + } + + /// + /// Gets the kind of this value. + /// + public EdmValueKind ValueKind + { + get { return EdmValueKind.Structured; } + } + + /// + /// Gets the property values of this structured value. + /// + public IEnumerable PropertyValues + { + get { return this.typeAnnotation.Properties().Select(this.BuildEdmPropertyValue); } + } + + /// + /// Finds the value corresponding to the provided property name. + /// + /// Property to find the value of. + /// + /// The found property, or null if no property was found. + /// + public IEdmPropertyValue FindPropertyValue(string propertyName) + { + var propertyAnnotation = this.typeAnnotation.GetProperty(propertyName, UndeclaredPropertyBehavior.Support); + if (propertyAnnotation == null) + { + return null; + } + + return this.BuildEdmPropertyValue(propertyAnnotation); + } + + /// + /// Builds an edm property value from the given annotation. + /// + /// The property annotation. + /// The property value + private IEdmPropertyValue BuildEdmPropertyValue(ClientPropertyAnnotation propertyAnnotation) + { + var propertyValue = propertyAnnotation.GetValue(this.structuredValue); + var edmValue = this.ConvertToEdmValue(propertyValue, propertyAnnotation.EdmProperty.Type); + return new EdmPropertyValue(propertyAnnotation.EdmProperty.Name, edmValue); + } + + /// + /// Converts a clr value to an edm value. + /// + /// The property value. + /// Type of the property. + /// + /// The converted value + /// + private IEdmValue ConvertToEdmValue(object propertyValue, IEdmTypeReference edmPropertyType) + { + Debug.Assert(edmPropertyType != null, "edmPropertyType != null"); + + if (propertyValue == null) + { + return EdmNullExpression.Instance; + } + + if (edmPropertyType.IsStructured()) + { + var actualEdmTypeReference = this.model.GetClientTypeAnnotation(propertyValue.GetType()); + if (actualEdmTypeReference != null && actualEdmTypeReference.EdmTypeReference.Definition.IsOrInheritsFrom(edmPropertyType.Definition)) + { + return new ClientEdmStructuredValue(propertyValue, this.model, actualEdmTypeReference); + } + else + { + return new ClientEdmStructuredValue(propertyValue, this.model, this.model.GetClientTypeAnnotation(edmPropertyType.Definition)); + } + } + + if (edmPropertyType.IsCollection()) + { + var collectionType = edmPropertyType as IEdmCollectionTypeReference; + Debug.Assert(collectionType != null, "collectionType != null"); + var elements = ((IEnumerable)propertyValue).Cast().Select(v => this.ConvertToEdmValue(v, collectionType.ElementType())); + return new ClientEdmCollectionValue(collectionType, elements); + } + + if (edmPropertyType.IsEnum()) + { + // Need to handle underlying type(Int16, Int32, Int64) + return new EdmEnumValue(edmPropertyType as IEdmEnumTypeReference, new EdmEnumMemberValue(Convert.ToInt64(propertyValue, CultureInfo.InvariantCulture))); + } + + var primitiveType = edmPropertyType as IEdmPrimitiveTypeReference; + Debug.Assert(primitiveType != null, "Type was not structured, collection, or primitive"); + + return EdmValueUtils.ConvertPrimitiveValue(propertyValue, primitiveType).Value; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Common.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Common.cs new file mode 100644 index 0000000..2f1aca6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Common.cs @@ -0,0 +1,339 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Service +#endif +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Dynamic; + using System.Linq; + using System.Xml; + using System.Text; + using Microsoft.OData; +#if !ODATA_CLIENT + using Microsoft.OData.Client; +#endif + + /// + /// Common defintions and functions for the server and client lib + /// + internal static partial class CommonUtil + { + /// + /// List of types unsupported by the client + /// + private static readonly Type[] unsupportedTypes = new Type[] + { + typeof(IDynamicMetaObjectProvider), + typeof(Tuple<>), // 1-Tuple + typeof(Tuple<,>), // 2-Tuple + typeof(Tuple<,,>), // 3-Tuple + typeof(Tuple<,,,>), // 4-Tuple + typeof(Tuple<,,,,>), // 5-Tuple + typeof(Tuple<,,,,,>), // 6-Tuple + typeof(Tuple<,,,,,,>), // 7-Tuple + typeof(Tuple<,,,,,,,>) // 8-Tuple + }; + + /// + /// Test whether a type is unsupported by the client lib + /// + /// The type to test + /// Returns true if the type is not supported + internal static bool IsUnsupportedType(Type type) + { +#if ODATA_CLIENT + if (type.IsGenericType()) +#else + if (type.IsGenericType) +#endif + { + type = type.GetGenericTypeDefinition(); + } + + if (unsupportedTypes.Any(t => t.IsAssignableFrom(type))) + { + return true; + } + + Debug.Assert(!type.FullName.StartsWith("System.Tuple", StringComparison.Ordinal), "System.Tuple is not blocked by unsupported type check"); + return false; + } + + /// + /// Returns collection item type name or null if the provided type name is not a collection. + /// + /// Collection type name read from payload. + /// Whether it is a nested (recursive) call. + /// Collection element type name or null if not a collection. + /// + /// The following rules are used for collection type names: + /// - it has to start with "Collection(" and end with ")" - trailing and leading whitespaces make the type not to be recognized as collection. + /// - there is to be no characters (including whitespaces) between "Collection" and "(" - otherwise it won't be recognized as collection + /// - collection item type name has to be a non-empty string - i.e. "Collection()" won't be recognized as collection + /// - nested collection - e.g. "Collection(Collection(Edm.Int32))" - are not supported - we will throw + /// Note the following are examples of valid type names which are not collection: + /// - "Collection()" + /// - " Collection(Edm.Int32)" + /// - "Collection (Edm.Int32)" + /// - "Collection(" + /// If the type name is not recognized as a collection it will be eventually passed to type resolver if it is not a known primitive type. + /// + internal static string GetCollectionItemTypeName(string typeName, bool isNested) + { + // to be recognized as a collection wireTypeName must not be null, has to start with "Collection(" and end with ")" and must not be "Collection()" + if (typeName != null && typeName.StartsWith(XmlConstants.CollectionTypeQualifier + "(", StringComparison.Ordinal) && typeName[typeName.Length - 1] == ')' && typeName.Length != (XmlConstants.CollectionTypeQualifier + "()").Length) + { + if (isNested) + { +#if ODATA_CLIENT + throw Error.InvalidOperation(Strings.ClientType_CollectionOfCollectionNotSupported); +#else + throw DataServiceException.CreateBadRequestError(Strings.BadRequest_CollectionOfCollectionNotSupported); +#endif + } + + string innerTypeName = typeName.Substring((XmlConstants.CollectionTypeQualifier + "(").Length, typeName.Length - (XmlConstants.CollectionTypeQualifier + "()").Length); + + // Check if it is not a nested collection and throw if it is + GetCollectionItemTypeName(innerTypeName, true); + + return innerTypeName; + } + + return null; + } + +#if !ODATA_CLIENT + /// + /// checks whether the given xml reader element is empty or not. + /// This method reads over the start tag and if this returns false, + /// one needs to throw an appropriate exception + /// + /// reader instance. + /// true if the current element is empty. Otherwise false. + internal static bool ReadEmptyElement(XmlReader reader) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element, "IsEmptyElement method must be called for elements only"); + if (reader.IsEmptyElement) + { + return true; + } + + if (reader.Read() && reader.NodeType == XmlNodeType.EndElement) + { + return true; + } + + return false; + } +#endif + + /// + /// Convert the ODataProtocolVersion to ODataVersion. + /// + /// ODataProtocolVersion value to convert. + /// an ODataVersion value for the given ODataProtocolVersion value. + internal static ODataVersion ConvertToODataVersion(ODataProtocolVersion maxProtocolVersion) + { + switch (maxProtocolVersion) + { + case ODataProtocolVersion.V4: + return ODataVersion.V4; + + case ODataProtocolVersion.V401: + return ODataVersion.V401; + + default: + Debug.Assert(false, "Need to add a case for the new version that got added"); + return (ODataVersion)(-1); + } + } + + /// + /// Converts the given version instance to ODataVersion enum. + /// + /// Version instance containing the response payload. + /// ODataVersion enum value for the given version. + internal static ODataVersion ConvertToODataVersion(Version version) + { + Debug.Assert(version != null, "version != null"); + + Debug.Assert(version.Major == 4 && (version.Minor == 0 || version.Minor == 1), "Major version should be 4 and minor version should be 0 or 1"); + if (version.Major == 4 && version.Minor == 1) + { + return ODataVersion.V401; + } + else + { + return ODataVersion.V4; + } + } + + /// + /// Gets the type name (without namespace) of the specified , + /// appropriate as an externally-visible type name. + /// + /// Type to get name for. + /// The type name for . + internal static string GetModelTypeName(Type type) + { + Debug.Assert(type != null, "type != null"); +#if ODATA_CLIENT + if (type.IsGenericType()) +#else + if (type.IsGenericType) +#endif + { + Type[] genericArguments = type.GetGenericArguments(); + StringBuilder builder = new StringBuilder(type.Name.Length * 2 * (1 + genericArguments.Length)); + if (type.IsNested) + { + Debug.Assert(type.DeclaringType != null, "type.DeclaringType != null"); + builder.Append(GetModelTypeName(type.DeclaringType)); + builder.Append('_'); + } + + builder.Append(type.Name); + builder.Append('['); + for (int i = 0; i < genericArguments.Length; i++) + { + if (i > 0) + { + builder.Append(' '); + } + + if (genericArguments[i].IsGenericParameter) + { + builder.Append(genericArguments[i].Name); + } + else + { + string genericNamespace = GetModelTypeNamespace(genericArguments[i]); + if (!String.IsNullOrEmpty(genericNamespace)) + { + builder.Append(genericNamespace); + builder.Append('.'); + } + + builder.Append(GetModelTypeName(genericArguments[i])); + } + } + + builder.Append(']'); + return builder.ToString(); + } + else if (type.IsNested) + { + Debug.Assert(type.DeclaringType != null, "type.DeclaringType != null"); + return GetModelTypeName(type.DeclaringType) + "_" + type.Name; + } + else + { + return type.Name; + } + } + + /// + /// Gets the type namespace of the specified , + /// appropriate as an externally-visible type name. + /// + /// Type to get namespace for. + /// The namespace for . + internal static string GetModelTypeNamespace(Type type) + { + Debug.Assert(type != null, "type != null"); + return type.Namespace ?? String.Empty; + } + + /// Tries to read a WCF Data Service version string. + /// Text to read. + /// Parsed version and trailing text. + /// true if the version was read successfully; false otherwise. + internal static bool TryReadVersion(string text, out KeyValuePair result) + { + Debug.Assert(text != null, "text != null"); + + // Separate version number and extra string. + int separator = text.IndexOf(';'); + string versionText, libraryName; + if (separator >= 0) + { + versionText = text.Substring(0, separator); + libraryName = text.Substring(separator + 1).Trim(); + } + else + { + versionText = text; + libraryName = null; + } + + result = default(KeyValuePair); + versionText = versionText.Trim(); + + // The Version constructor allows for a more complex syntax, including + // build, revisions, and major/minor for revisions. We only take two + // number parts separated by a single dot. + bool dotFound = false; + for (int i = 0; i < versionText.Length; i++) + { + if (versionText[i] == '.') + { + if (dotFound) + { + return false; + } + + dotFound = true; + } + else if (versionText[i] < '0' || versionText[i] > '9') + { + return false; + } + } + + try + { + result = new KeyValuePair(new Version(versionText), libraryName); + return true; + } + catch (Exception e) + { + if (CommonUtil.IsCatchableExceptionType(e) && + (e is FormatException || e is OverflowException || e is ArgumentException)) + { + return false; + } + + throw; + } + } + + /// + /// Set the message quota limits for WCF Data services server. + /// + /// Instance of ODataMessageQuotas. + internal static void SetDefaultMessageQuotas(ODataMessageQuotas messageQuotas) + { + // NOTE: the size of the input message is only limited by the WCF message size in Astoria + // In WCF DS client, we never had a limit on any of these. Hence for client, it makes sense + // to set these values to some high limit. In WCF DS server, there are bunch of API's to + // cover some of these limits and if we pass the value to ODL, for batch requests, there is + // a breaking change, since WCF DS server cannot figure out why the exception was thrown and + // and hence fail way early. For now, the best way is to tell ODL to not impose any limits + // and WCF DS server imposes the limits in its own way. + messageQuotas.MaxReceivedMessageSize = long.MaxValue; + messageQuotas.MaxPartsPerBatch = int.MaxValue; + messageQuotas.MaxOperationsPerChangeset = int.MaxValue; + messageQuotas.MaxNestingDepth = int.MaxValue; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/CommonUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/CommonUtil.cs new file mode 100644 index 0000000..70f8798 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/CommonUtil.cs @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Service +#endif +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Threading; + + /// + /// Common defintions and functions for ALL product assemblies + /// + internal static partial class CommonUtil + { + // Only StackOverflowException & ThreadAbortException are sealed classes. + + /// Type of OutOfMemoryException. + private static readonly Type OutOfMemoryType = typeof(OutOfMemoryException); + +#if !PORTABLELIB + /// Type of StackOverflowException. + private static readonly Type StackOverflowType = typeof(StackOverflowException); + + /// Type of ThreadAbortException. + private static readonly Type ThreadAbortType = typeof(ThreadAbortException); +#endif + + public static object ParseJsonToPrimitiveValue(string rawValue) + { + Debug.Assert(rawValue != null && rawValue.Length > 0 && rawValue.IndexOf('{') != 0 && rawValue.IndexOf('[') != 0, + "rawValue != null && rawValue.Length > 0 && rawValue.IndexOf('{') != 0 && rawValue.IndexOf('[') != 0"); + ODataCollectionValue collectionValue = (ODataCollectionValue) + Microsoft.OData.ODataUriUtils.ConvertFromUriLiteral(string.Format(CultureInfo.InvariantCulture, "[{0}]", rawValue), ODataVersion.V4); + foreach (object item in collectionValue.Items) + { + return item; + } + + return null; + } + + /// + /// Determines whether the specified exception can be caught and + /// handled, or whether it should be allowed to continue unwinding. + /// + /// to test. + /// + /// true if the specified exception can be caught and handled; + /// false otherwise. + /// + internal static bool IsCatchableExceptionType(Exception e) + { + if (e == null) + { + return true; + } + + // a 'catchable' exception is defined by what it is not. + Type type = e.GetType(); + return ( +#if !PORTABLELIB +(type != ThreadAbortType) && + (type != StackOverflowType) && +#endif + (type != OutOfMemoryType)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentStream.cs new file mode 100644 index 0000000..c58523c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentStream.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.IO; + + /// + /// Helper class to wrap the stream with the content of the request. + /// We need to remember if the stream came from us (IsKnownMemoryStream is true) + /// or if it came from outside. For backward compatibility we set the Content-Length for our streams + /// since they are always MemoryStream and thus know their length. + /// For outside streams (content of the MR requests) we don't set Content-Length since the stream + /// might not be able to answer to the Length call. + /// + internal sealed class ContentStream + { + /// + /// The stream with the content of the request + /// + private readonly Stream stream; + + /// + /// Set to true if the stream is a MemoryStream and we produced it (so it does have the buffer accesible) + /// + private readonly bool isKnownMemoryStream; + + /// + /// Constructor + /// + /// The stream with the request content + /// The stream was create by us and it's a MemoryStream + public ContentStream(Stream stream, bool isKnownMemoryStream) + { + this.stream = stream; + this.isKnownMemoryStream = isKnownMemoryStream; + } + + /// + /// The stream with the content of the request + /// + public Stream Stream + { + get { return this.stream; } + } + + /// + /// Set to true if the stream is a MemoryStream and we produced it (so it does have the buffer accesible) + /// + public bool IsKnownMemoryStream + { + get { return this.isKnownMemoryStream; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentTypeUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentTypeUtil.cs new file mode 100644 index 0000000..e0c692a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ContentTypeUtil.cs @@ -0,0 +1,1274 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Service +#endif +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; +#if ODATA_CLIENT + using System.Net; +#endif + using Microsoft.OData; + + /// Provides helper methods for processing HTTP requests. + internal static class ContentTypeUtil + { + /// UTF-8 encoding, without the BOM preamble. + /// + /// While a BOM preamble on UTF8 is generally benign, it seems that some MIME handlers under IE6 will not + /// process the payload correctly when included. + /// + /// Because the data service should include the encoding as part of the Content-Type in the response, + /// there should be no ambiguity as to what encoding is being used. + /// + /// For further information, see http://www.unicode.org/faq/utf_bom.html#BOM. + /// + internal static readonly UTF8Encoding EncodingUtf8NoPreamble = new UTF8Encoding(false, true); + +#if !ODATA_CLIENT + /// + /// Allowable Media Types for an Entity or Feed in V2. + /// + private static readonly string[] MediaTypesForEntityOrFeedV2 = new string[] + { + XmlConstants.MimeApplicationJson, + XmlConstants.MimeApplicationAtom, + }; + + /// + /// Allowable Media Types for something besides an Entity or Feed in V2. + /// + private static readonly string[] MediaTypesForOtherV2 = new string[] + { + XmlConstants.MimeApplicationJson, + XmlConstants.MimeApplicationXml, + XmlConstants.MimeTextXml, + }; + + /// + /// Allowable Media Types for Entities or Feeds in V3. + /// + private static readonly string[] MediaTypesForEntityOrFeedV3 = new string[] + { + XmlConstants.MimeApplicationJson, + XmlConstants.MimeApplicationAtom, + XmlConstants.MimeApplicationJsonODataMinimalMetadata, + XmlConstants.MimeApplicationJsonODataFullMetadata, + XmlConstants.MimeApplicationJsonODataNoMetadata, + }; + + /// + /// Allowable Media Types for something other than Entities or Feeds in V3. + /// + private static readonly string[] MediaTypesForOtherV3 = new string[] + { + XmlConstants.MimeApplicationJson, + XmlConstants.MimeApplicationXml, + XmlConstants.MimeTextXml, + XmlConstants.MimeApplicationJsonODataMinimalMetadata, + XmlConstants.MimeApplicationJsonODataFullMetadata, + XmlConstants.MimeApplicationJsonODataNoMetadata, + }; +#endif + + /// Encoding to fall back to an appropriate encoding is not available. + internal static Encoding FallbackEncoding + { + get + { + return EncodingUtf8NoPreamble; + } + } + + /// Encoding implied by an unspecified encoding value. + /// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details. + private static Encoding MissingEncoding + { + get + { +#if PORTABLELIB // ISO-8859-1 not available + return Encoding.UTF8; +#else + return Encoding.GetEncoding("ISO-8859-1", new EncoderExceptionFallback(), new DecoderExceptionFallback()); +#endif + } + } + +#if !ODATA_CLIENT + + /// Selects an acceptable MIME type that satisfies the Accepts header. + /// Text for Accepts header. + /// + /// Types that the server is willing to return, in descending order + /// of preference. + /// + /// The best MIME type for the client + internal static string SelectMimeType(string acceptTypesText, string[] availableTypes) + { + Debug.Assert(availableTypes != null, "acceptableTypes != null"); + string selectedContentType = null; + int selectedMatchingParts = -1; + int selectedQualityValue = 0; + int selectedPreferenceIndex = Int32.MaxValue; + bool acceptable = false; + bool acceptTypesEmpty = true; + if (!String.IsNullOrEmpty(acceptTypesText)) + { + IEnumerable acceptTypes = MimeTypesFromAcceptHeader(acceptTypesText); + foreach (MediaType acceptType in acceptTypes) + { + acceptTypesEmpty = false; + for (int i = 0; i < availableTypes.Length; i++) + { + string availableType = availableTypes[i]; + int matchingParts = acceptType.GetMatchingParts(availableType); + if (matchingParts < 0) + { + continue; + } + + if (matchingParts > selectedMatchingParts) + { + // A more specific type wins. + selectedContentType = availableType; + selectedMatchingParts = matchingParts; + selectedQualityValue = acceptType.SelectQualityValue(); + selectedPreferenceIndex = i; + acceptable = selectedQualityValue != 0; + } + else if (matchingParts == selectedMatchingParts) + { + // A type with a higher q-value wins. + int candidateQualityValue = acceptType.SelectQualityValue(); + if (candidateQualityValue > selectedQualityValue) + { + selectedContentType = availableType; + selectedQualityValue = candidateQualityValue; + selectedPreferenceIndex = i; + acceptable = selectedQualityValue != 0; + } + else if (candidateQualityValue == selectedQualityValue) + { + // A type that is earlier in the availableTypes array wins. + if (i < selectedPreferenceIndex) + { + selectedContentType = availableType; + selectedPreferenceIndex = i; + } + } + } + } + } + } + + if (acceptTypesEmpty) + { + selectedContentType = availableTypes[0]; + } + else if (!acceptable) + { + selectedContentType = null; + } + + return selectedContentType; + } + + /// Gets the appropriate MIME type for the request, throwing if there is none. + /// Text as it appears in an HTTP Accepts header. + /// Preferred content type to match if an exact media type is given - this is in descending order of preference. + /// Preferred fallback content type for inexact matches. + /// One of exactContentType or inexactContentType. + internal static string SelectRequiredMimeType( + string acceptTypesText, + string[] exactContentType, + string inexactContentType) + { + Debug.Assert(exactContentType != null && exactContentType.Length != 0, "exactContentType != null && exactContentType.Length != 0"); + Debug.Assert(inexactContentType != null, "inexactContentType != null"); + + string selectedContentType = null; + int selectedMatchingParts = -1; + int selectedQualityValue = 0; + bool acceptable = false; + bool acceptTypesEmpty = true; + bool foundExactMatch = false; + + if (!String.IsNullOrEmpty(acceptTypesText)) + { + IEnumerable acceptTypes = MimeTypesFromAcceptHeader(acceptTypesText); + foreach (MediaType acceptType in acceptTypes) + { + acceptTypesEmpty = false; + for (int i = 0; i < exactContentType.Length; i++) + { + if (CompareMimeType(acceptType.MimeType, exactContentType[i])) + { + selectedContentType = exactContentType[i]; + selectedQualityValue = acceptType.SelectQualityValue(); + acceptable = selectedQualityValue != 0; + foundExactMatch = true; + break; + } + } + + if (foundExactMatch) + { + break; + } + + int matchingParts = acceptType.GetMatchingParts(inexactContentType); + if (matchingParts < 0) + { + continue; + } + + if (matchingParts > selectedMatchingParts) + { + // A more specific type wins. + selectedContentType = inexactContentType; + selectedMatchingParts = matchingParts; + selectedQualityValue = acceptType.SelectQualityValue(); + acceptable = selectedQualityValue != 0; + } + else if (matchingParts == selectedMatchingParts) + { + // A type with a higher q-value wins. + int candidateQualityValue = acceptType.SelectQualityValue(); + if (candidateQualityValue > selectedQualityValue) + { + selectedContentType = inexactContentType; + selectedQualityValue = candidateQualityValue; + acceptable = selectedQualityValue != 0; + } + } + } + } + + if (!acceptable && !acceptTypesEmpty) + { + throw Error.HttpHeaderFailure(415, Strings.DataServiceException_UnsupportedMediaType); + } + + if (acceptTypesEmpty) + { + Debug.Assert(selectedContentType == null, "selectedContentType == null - otherwise accept types were not empty"); + selectedContentType = inexactContentType; + } + + Debug.Assert(selectedContentType != null, "selectedContentType != null - otherwise no selection was made"); + return selectedContentType; + } + + /// Gets the best encoding available for the specified charset request. + /// + /// The Accept-Charset header value (eg: "iso-8859-5, unicode-1-1;q=0.8"). + /// + /// An Encoding object appropriate to the specifed charset request. + internal static Encoding EncodingFromAcceptCharset(string acceptCharset) + { + // Determines the appropriate encoding mapping according to + // RFC 2616.14.2 (http://tools.ietf.org/html/rfc2616#section-14.2). + Encoding result = null; + if (!String.IsNullOrEmpty(acceptCharset)) + { + // PERF: keep a cache of original strings to resolved Encoding. + List parts = new List(AcceptCharsetParts(acceptCharset)); + parts.Sort((x, y) => y.Quality - x.Quality); + + var encoderFallback = new EncoderExceptionFallback(); + var decoderFallback = new DecoderExceptionFallback(); + foreach (CharsetPart part in parts) + { + if (part.Quality > 0) + { + // When UTF-8 is specified, select the version that doesn't use the BOM. + if (String.Compare(XmlConstants.Utf8Encoding, part.Charset, StringComparison.OrdinalIgnoreCase) == 0) + { + result = FallbackEncoding; + break; + } + + try + { + result = Encoding.GetEncoding(part.Charset, encoderFallback, decoderFallback); + break; + } + catch (ArgumentException) + { + // This exception is thrown when the character + // set isn't supported - it is ignored so + // other possible charsets are evaluated. + } + } + } + } + + // No Charset was specifed, or if charsets were specified, no valid charset was found. + // Returning a different charset is also valid. + return result ?? FallbackEncoding; + } + + /// + /// Selects a response format for the requestMessage's request and sets the appropriate response header. + /// + /// A comma-delimited list of client-supported MIME accept types. + /// Whether the target is an entity. + /// The effective max response version. + /// The selected media type. + internal static string SelectResponseMediaType(string acceptTypesText, bool entityTarget, Version effectiveMaxResponseVersion) + { + string[] availableTypes = GetAvailableMediaTypes(effectiveMaxResponseVersion, entityTarget); + + string contentType = SelectMimeType(acceptTypesText, availableTypes); + + // never respond with just app/json + if (CompareMimeType(contentType, XmlConstants.MimeApplicationJson)) + { + contentType = XmlConstants.MimeApplicationJsonODataMinimalMetadata; + } + + return contentType; + } + + /// + /// Does a ordinal ignore case comparision of the given mime types. + /// + /// mime type1. + /// mime type2. + /// returns true if the mime type are the same. + internal static bool CompareMimeType(string mimeType1, string mimeType2) + { + return String.Equals(mimeType1, mimeType2, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether the response media type would be JSON light for the given accept-header text. + /// + /// The text from the request's accept header. + /// Whether the target is an entity. + /// The effective max response version. + /// True if the response type is Json Light. + internal static bool IsResponseMediaTypeJsonLight(string acceptTypesText, bool entityTarget, Version effectiveMaxResponseVersion) + { + string selectedMediaType; + try + { + selectedMediaType = SelectResponseMediaType(acceptTypesText, entityTarget, effectiveMaxResponseVersion); + } + catch (DataServiceException) + { + // The acceptTypesText does not contain a supported mime type. + selectedMediaType = null; + } + + return string.Equals(XmlConstants.MimeApplicationJsonODataMinimalMetadata, selectedMediaType, StringComparison.OrdinalIgnoreCase) + || string.Equals(XmlConstants.MimeApplicationJsonODataFullMetadata, selectedMediaType, StringComparison.OrdinalIgnoreCase) + || string.Equals(XmlConstants.MimeApplicationJsonODataNoMetadata, selectedMediaType, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether the response media type would be JSON light for the request. + /// + /// The data service instance to determine the response media type. + /// true if the target of the request is an entry or a feed, false otherwise. + /// true if the response type is Json Light; false otherwise + internal static bool IsResponseMediaTypeJsonLight(IDataService dataService, bool isEntryOrFeed) + { + AstoriaRequestMessage requestMessage = dataService.OperationContext.RequestMessage; + Version effectiveMaxResponseVersion = VersionUtil.GetEffectiveMaxResponseVersion(dataService.Configuration.DataServiceBehavior.MaxProtocolVersion.ToVersion(), requestMessage.RequestMaxVersion); + return IsResponseMediaTypeJsonLight(requestMessage.GetAcceptableContentTypes(), isEntryOrFeed, effectiveMaxResponseVersion); + } + + /// + /// Determines whether the response content type is a JSON-based format. + /// + /// The response content-type. + /// + /// true if the content-type is JSON; otherwise, false. + /// + internal static bool IsNotJson(string responseContentType) + { + return responseContentType == null || !responseContentType.StartsWith(XmlConstants.MimeApplicationJson, StringComparison.OrdinalIgnoreCase); + } +#endif + +#if ODATA_CLIENT + /// Reads a Content-Type header and extracts the MIME type/subtype. + /// The Content-Type header. + /// The MIME type in standard type/subtype form, without parameters. + /// parameters of content type + internal static MediaParameter[] ReadContentType(string contentType, out string mime) + { + if (String.IsNullOrEmpty(contentType)) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ContentTypeMissing); + } + + MediaType mediaType = ReadMediaType(contentType); + mime = mediaType.MimeType; + return mediaType.Parameters; + } + + /// Builds a Content-Type given the mime type and all the parameters. + /// The MIME type in standard type/subtype form, without parameters. + /// Parameters to be appended in the mime type. + /// content type containing the mime type and all the parameters. + internal static string WriteContentType(string mimeType, MediaParameter[] parameters) + { + Debug.Assert(!string.IsNullOrEmpty(mimeType), "!string.IsNullOrEmpty(mimeType)"); + Debug.Assert(parameters != null, "parameters != null"); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append(mimeType); + + foreach (var parameter in parameters) + { + stringBuilder.Append(';'); + stringBuilder.Append(parameter.Name); + stringBuilder.Append("="); + stringBuilder.Append(parameter.GetOriginalValue()); + } + + return stringBuilder.ToString(); + } + +#endif + + /// Reads a Content-Type header and extracts the MIME type/subtype and encoding. + /// The Content-Type header. + /// The MIME type in standard type/subtype form, without parameters. + /// Encoding (possibly null). + /// parameters of content type + internal static MediaParameter[] ReadContentType(string contentType, out string mime, out Encoding encoding) + { + if (String.IsNullOrEmpty(contentType)) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ContentTypeMissing); + } + + MediaType mediaType = ReadMediaType(contentType); + mime = mediaType.MimeType; + encoding = mediaType.SelectEncoding(); + return mediaType.Parameters; + } + + /// Gets the named encoding if specified. + /// Name (possibly null or empty). + /// + /// The named encoding if specified; the encoding for HTTP missing + /// charset specification otherwise. + /// + /// + /// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details. + /// + private static Encoding EncodingFromName(string name) + { + if (name == null) + { + return MissingEncoding; + } + + name = name.Trim(); + if (name.Length == 0) + { + return MissingEncoding; + } + + try + { + return Encoding.GetEncoding(name); + } + catch (ArgumentException) + { + // 400 - Bad Request + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EncodingNotSupported(name)); + } + } + +#if !ODATA_CLIENT + /// Creates a new exception for parsing errors. + /// Message for error. + /// A new exception that can be thrown for a parsing error. + private static DataServiceException CreateParsingException(string message) + { + // Status code "400" ; Section 10.4.1: Bad Request + return Error.HttpHeaderFailure(400, message); + } + + /// + /// Returns the list of available media types. + /// + /// The effective max response version of the request. + /// true if the response will contain an entity or feed. + /// A list of recognized media types. + private static string[] GetAvailableMediaTypes(Version effectiveMaxResponseVersion, bool isEntityOrFeed) + { + return isEntityOrFeed ? MediaTypesForEntityOrFeedV3 : MediaTypesForOtherV3; + } + + /// + /// Verfies whether the specified character is a valid separator in + /// an HTTP header list of element. + /// + /// Character to verify. + /// true if c is a valid character for separating elements; false otherwise. + private static bool IsHttpElementSeparator(char c) + { + return c == ',' || c == ' ' || c == '\t'; + } + + /// + /// "Reads" a literal from the specified string by verifying that + /// the exact text can be found at the specified position. + /// + /// Text within which a literal should be checked. + /// Index in text where the literal should be found. + /// Literal to check at the specified position. + /// true if the end of string is found; false otherwise. + private static bool ReadLiteral(string text, int textIndex, string literal) + { + if (String.Compare(text, textIndex, literal, 0, literal.Length, StringComparison.Ordinal) != 0) + { + // Failed to find expected literal. + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + + return textIndex + literal.Length == text.Length; + } + + /// + /// Converts the specified character from the ASCII range to a digit. + /// + /// Character to convert. + /// + /// The Int32 value for c, or -1 if it is an element separator. + /// + private static int DigitToInt32(char c) + { + if (c >= '0' && c <= '9') + { + return c - '0'; + } + + if (IsHttpElementSeparator(c)) + { + return -1; + } + + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + + /// Returns all MIME types from the specified (non-blank) . + /// Non-blank text, as it appears on an HTTP Accepts header. + /// An enumerable object with media type descriptions. + private static IEnumerable MimeTypesFromAcceptHeader(string text) + { + Debug.Assert(!String.IsNullOrEmpty(text), "!String.IsNullOrEmpty(text)"); + List mediaTypes = new List(); + int textIndex = 0; + while (!SkipWhitespace(text, ref textIndex)) + { + string type; + string subType; + ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType); + + MediaParameter[] parameters = null; + while (!SkipWhitespace(text, ref textIndex)) + { + if (text[textIndex] == ',') + { + textIndex++; + break; + } + + if (text[textIndex] != ';') + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter); + } + + textIndex++; + if (SkipWhitespace(text, ref textIndex)) + { + // ';' should be a leading separator, but we choose to be a + // bit permissive and allow it as a final delimiter as well. + break; + } + + ReadMediaTypeParameter(text, ref textIndex, ref parameters); + } + + mediaTypes.Add(new MediaType(type, subType, parameters)); + } + + return mediaTypes; + } + + /// + /// Reads the numeric part of a quality value substring, normalizing it to 0-1000 + /// rather than the standard 0.000-1.000 ranges. + /// + /// Text to read qvalue from. + /// Index into text where the qvalue starts. + /// After the method executes, the normalized qvalue. + /// + /// For more information, see RFC 2616.3.8. + /// + private static void ReadQualityValue(string text, ref int textIndex, out int qualityValue) + { + char digit = text[textIndex++]; + if (digit == '0') + { + qualityValue = 0; + } + else if (digit == '1') + { + qualityValue = 1; + } + else + { + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + + if (textIndex < text.Length && text[textIndex] == '.') + { + textIndex++; + + int adjustFactor = 1000; + while (adjustFactor > 1 && textIndex < text.Length) + { + char c = text[textIndex]; + int charValue = DigitToInt32(c); + if (charValue >= 0) + { + textIndex++; + adjustFactor /= 10; + qualityValue *= 10; + qualityValue += charValue; + } + else + { + break; + } + } + + qualityValue *= adjustFactor; + if (qualityValue > 1000) + { + // Too high of a value in qvalue. + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + } + else + { + qualityValue *= 1000; + } + } + + /// + /// Enumerates each charset part in the specified Accept-Charset header. + /// + /// Non-null and non-empty header value for Accept-Charset. + /// + /// A (non-sorted) enumeration of CharsetPart elements, which include + /// a charset name and a quality (preference) value, normalized to 0-1000. + /// + private static IEnumerable AcceptCharsetParts(string headerValue) + { + Debug.Assert(!String.IsNullOrEmpty(headerValue), "!String.IsNullOrEmpty(headerValuer)"); + + // PERF: optimize for common patterns. + bool commaRequired = false; // Whether a comma should be found + int headerIndex = 0; // Index of character being procesed on headerValue. + int headerStart; // Index into headerValue for the start of the charset name. + int headerNameEnd; // Index into headerValue for the end of the charset name (+1). + int headerEnd; // Index into headerValue for this charset part (+1). + int qualityValue; // Normalized qvalue for this charset. + + while (headerIndex < headerValue.Length) + { + if (SkipWhitespace(headerValue, ref headerIndex)) + { + yield break; + } + + if (headerValue[headerIndex] == ',') + { + commaRequired = false; + headerIndex++; + continue; + } + + if (commaRequired) + { + // Comma missing between charset elements. + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + + headerStart = headerIndex; + headerNameEnd = headerStart; + + bool endReached = ReadToken(headerValue, ref headerNameEnd); + if (headerNameEnd == headerIndex) + { + // Invalid charset name. + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + + if (endReached) + { + qualityValue = 1000; + headerEnd = headerNameEnd; + } + else + { + char afterNameChar = headerValue[headerNameEnd]; + if (IsHttpSeparator(afterNameChar)) + { + if (afterNameChar == ';') + { + if (ReadLiteral(headerValue, headerNameEnd, ";q=")) + { + // Unexpected end of qvalue. + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + + headerEnd = headerNameEnd + 3; + ReadQualityValue(headerValue, ref headerEnd, out qualityValue); + } + else + { + qualityValue = 1000; + headerEnd = headerNameEnd; + } + } + else + { + // Invalid separator character. + throw CreateParsingException(Strings.HttpContextServiceHost_MalformedHeaderValue); + } + } + + yield return new CharsetPart(headerValue.Substring(headerStart, headerNameEnd - headerStart), qualityValue); + + // Prepare for next charset; we require at least one comma before we process it. + commaRequired = true; + headerIndex = headerEnd; + } + } + +#endif + + /// Reads the type and subtype specifications for a MIME type. + /// Text in which specification exists. + /// Pointer into text. + /// Type of media found. + /// Subtype of media found. + private static void ReadMediaTypeAndSubtype(string text, ref int textIndex, out string type, out string subType) + { + Debug.Assert(text != null, "text != null"); + int textStart = textIndex; + if (ReadToken(text, ref textIndex)) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeUnspecified); + } + + if (text[textIndex] != '/') + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSlash); + } + + type = text.Substring(textStart, textIndex - textStart); + textIndex++; + + int subTypeStart = textIndex; + ReadToken(text, ref textIndex); + + if (textIndex == subTypeStart) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSubType); + } + + subType = text.Substring(subTypeStart, textIndex - subTypeStart); + } + + /// Reads a media type definition as used in a Content-Type header. + /// Text to read. + /// The defined by the specified + /// All syntactic errors will produce a 400 - Bad Request status code. + private static MediaType ReadMediaType(string text) + { + Debug.Assert(text != null, "text != null"); + + string type; + string subType; + int textIndex = 0; + ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType); + + MediaParameter[] parameters = null; + while (!SkipWhitespace(text, ref textIndex)) + { + if (text[textIndex] != ';') + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter); + } + + textIndex++; + if (SkipWhitespace(text, ref textIndex)) + { + // ';' should be a leading separator, but we choose to be a + // bit permissive and allow it as a final delimiter as well. + break; + } + + ReadMediaTypeParameter(text, ref textIndex, ref parameters); + } + + return new MediaType(type, subType, parameters); + } + + /// + /// Reads a token on the specified text by advancing an index on it. + /// + /// Text to read token from. + /// Index for the position being scanned on text. + /// true if the end of the text was reached; false otherwise. + private static bool ReadToken(string text, ref int textIndex) + { + while (textIndex < text.Length && IsHttpToken(text[textIndex])) + { + textIndex++; + } + + return (textIndex == text.Length); + } + + /// + /// Skips whitespace in the specified text by advancing an index to + /// the next non-whitespace character. + /// + /// Text to scan. + /// Index to begin scanning from. + /// true if the end of the string was reached, false otherwise. + private static bool SkipWhitespace(string text, ref int textIndex) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(text.Length >= 0, "text >= 0"); + Debug.Assert(textIndex <= text.Length, "text <= text.Length"); + + while (textIndex < text.Length && Char.IsWhiteSpace(text, textIndex)) + { + textIndex++; + } + + return (textIndex == text.Length); + } + + /// Read a parameter for a media type/range. + /// Text to read from. + /// Pointer in text. + /// Array with parameters to grow as necessary. + private static void ReadMediaTypeParameter(string text, ref int textIndex, ref MediaParameter[] parameters) + { + int startIndex = textIndex; + if (ReadToken(text, ref textIndex)) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue); + } + + string parameterName = text.Substring(startIndex, textIndex - startIndex); + if (text[textIndex] != '=') + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_MediaTypeMissingValue); + } + + textIndex++; + + MediaParameter parameter = ReadQuotedParameterValue(parameterName, text, ref textIndex); + + // Add the parameter name/value pair to the list. + if (parameters == null) + { + parameters = new MediaParameter[1]; + } + else + { + var grow = new MediaParameter[parameters.Length + 1]; + Array.Copy(parameters, grow, parameters.Length); + parameters = grow; + } + + parameters[parameters.Length - 1] = parameter; + } + + /// + /// Reads Mime type parameter value for a particular parameter in the Content-Type/Accept headers. + /// + /// Name of parameter. + /// Header text. + /// Parsing index in . + /// String representing the value of the parameter. + private static MediaParameter ReadQuotedParameterValue(string parameterName, string headerText, ref int textIndex) + { + StringBuilder parameterValue = new StringBuilder(); + bool isQuoted = false; + + // Check if the value is quoted. + bool valueIsQuoted = false; + if (textIndex < headerText.Length) + { + if (headerText[textIndex] == '\"') + { + textIndex++; + valueIsQuoted = true; + isQuoted = true; + } + } + + while (textIndex < headerText.Length) + { + char currentChar = headerText[textIndex]; + + if (currentChar == '\\' || currentChar == '\"') + { + if (!valueIsQuoted) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharWithoutQuotes(parameterName)); + } + + textIndex++; + + // End of quoted parameter value. + if (currentChar == '\"') + { + valueIsQuoted = false; + break; + } + + if (textIndex >= headerText.Length) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_EscapeCharAtEnd(parameterName)); + } + + currentChar = headerText[textIndex]; + } + else + if (!IsHttpToken(currentChar)) + { + // If the given character is special, we stop processing. + break; + } + + parameterValue.Append(currentChar); + textIndex++; + } + + if (valueIsQuoted) + { + throw Error.HttpHeaderFailure(400, Strings.HttpProcessUtility_ClosingQuoteNotFound(parameterName)); + } + + return new MediaParameter(parameterName, parameterValue.ToString(), isQuoted); + } + + /// + /// Determines whether the specified character is a valid HTTP separator. + /// + /// Character to verify. + /// true if c is a separator; false otherwise. + /// + /// See RFC 2616 2.2 for further information. + /// + private static bool IsHttpSeparator(char c) + { + return + c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || + c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' || + c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || + c == '{' || c == '}' || c == ' ' || c == '\x9'; + } + + /// + /// Determines whether the specified character is a valid HTTP header token character. + /// + /// Character to verify. + /// true if c is a valid HTTP header token character; false otherwise. + private static bool IsHttpToken(char c) + { + // A token character is any character (0-127) except control (0-31) or + // separators. 127 is DEL, a control character. + return c < '\x7F' && c > '\x1F' && !IsHttpSeparator(c); + } + +#if !ODATA_CLIENT + /// Provides a struct to encapsulate a charset name and its relative desirability. + private struct CharsetPart + { + /// Name of the charset. + internal readonly string Charset; + + /// Charset quality (desirability), normalized to 0-1000. + internal readonly int Quality; + + /// + /// Initializes a new CharsetPart with the specified values. + /// + /// Name of charset. + /// Charset quality (desirability), normalized to 0-1000. + internal CharsetPart(string charset, int quality) + { + Debug.Assert(charset != null, "charset != null"); + Debug.Assert(charset.Length > 0, "charset.Length > 0"); + Debug.Assert(0 <= quality && quality <= 1000, "0 <= quality && quality <= 1000"); + + this.Charset = charset; + this.Quality = quality; + } + } +#endif + + /// Class to store media parameter information. + internal class MediaParameter + { + /// + /// Creates a new instance of MediaParameter. + /// + /// Name of the parameter. + /// Value of the parameter. + /// True if the value of the parameter is quoted, otherwise false. + public MediaParameter(string name, string value, bool isQuoted) + { + this.Name = name; + this.Value = value; + this.IsQuoted = isQuoted; + } + + /// Gets the name of the parameter. + public string Name { get; private set; } + + /// Value of the parameter. + public string Value { get; private set; } + + /// true if the value is quoted, otherwise false. + private bool IsQuoted { get; set; } + + /// + /// Gets the original value of the parameter. + /// + /// the original value of the parameter. + public string GetOriginalValue() + { + return this.IsQuoted ? "\"" + this.Value + "\"" : this.Value; + } + } + + /// Use this class to represent a media type definition. + [DebuggerDisplay("MediaType [{type}/{subType}]")] + private sealed class MediaType + { + /// Parameters specified on the media type. + private readonly MediaParameter[] parameters; + + /// Sub-type specification (for example, 'plain'). + private readonly string subType; + + /// Type specification (for example, 'text'). + private readonly string type; + + /// + /// Initializes a new read-only instance. + /// + /// Type specification (for example, 'text'). + /// Sub-type specification (for example, 'plain'). + /// Parameters specified on the media type. + internal MediaType(string type, string subType, MediaParameter[] parameters) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(subType != null, "subType != null"); + + this.type = type; + this.subType = subType; + this.parameters = parameters; + } + + /// Returns the MIME type in standard type/subtype form, without parameters. + internal string MimeType + { + get { return this.type + "/" + this.subType; } + } + + /// media type parameters + internal MediaParameter[] Parameters + { + get { return this.parameters; } + } + +#if !ODATA_CLIENT + /// Gets a number of non-* matching types, or -1 if not matching at all. + /// Candidate MIME type to match. + /// The number of non-* matching types, or -1 if not matching at all. + internal int GetMatchingParts(string candidate) + { + Debug.Assert(candidate != null, "candidate must not be null."); + + return this.GetMatchingParts(MimeTypesFromAcceptHeader(candidate).Single()); + } + + /// Gets a number of non-* matching types, or -1 if not matching at all. + /// Candidate MIME type to match. + /// The number of non-* matching types, or -1 if not matching at all. + internal int GetMatchingParts(MediaType candidate) + { + Debug.Assert(candidate != null, "candidate must not be null."); + + int result = -1; + if (candidate != null) + { + if (this.type == "*") + { + result = 0; + } + else + { + if (candidate.subType != null) + { + string candidateType = candidate.type; + if (CompareMimeType(this.type, candidateType)) + { + if (this.subType == "*") + { + result = 1; + } + else + { + string candidateSubType = candidate.subType; + if (CompareMimeType(this.subType, candidateSubType)) + { + if (String.Equals(this.GetParameterValue(XmlConstants.MimeODataParameterName), candidate.GetParameterValue(XmlConstants.MimeODataParameterName), StringComparison.OrdinalIgnoreCase)) + { + result = 2; + } + } + } + } + } + } + } + + return result; + } + + /// + /// Searches for the parameter with the given name and returns its value. + /// + /// name of the parameter whose value needs to be returned. + /// returns the value of the parameter with the given name. Returns null, if the parameter is not found. + internal string GetParameterValue(string parameterName) + { + if (this.parameters == null) + { + return null; + } + + foreach (MediaParameter parameterInfo in this.parameters) + { + if (String.Equals(parameterInfo.Name, parameterName, StringComparison.OrdinalIgnoreCase)) + { + string parameterValue = parameterInfo.Value.Trim(); + if (parameterValue.Length > 0) + { + return parameterInfo.Value; + } + } + } + + return null; + } + + /// Selects a quality value for the specified type. + /// The quality value, in range from 0 through 1000. + /// See http://tools.ietf.org/html/rfc2616#section-14.1 for further details. + internal int SelectQualityValue() + { + string qvalueText = this.GetParameterValue(XmlConstants.HttpQValueParameter); + if (qvalueText == null) + { + return 1000; + } + + int result; + int textIndex = 0; + ReadQualityValue(qvalueText, ref textIndex, out result); + + return result; + } +#endif + + /// + /// Selects the encoding appropriate for this media type specification + /// (possibly null). + /// + /// + /// The encoding explicitly defined on the media type specification, or + /// the default encoding for well-known media types. + /// + /// + /// As per http://tools.ietf.org/html/rfc2616#section-3.7, the type, + /// subtype and parameter name attributes are case-insensitive. + /// + internal Encoding SelectEncoding() + { + if (this.parameters != null) + { + foreach (MediaParameter parameter in this.parameters) + { + if (String.Equals(parameter.Name, XmlConstants.HttpCharsetParameter, StringComparison.OrdinalIgnoreCase)) + { + string encodingName = parameter.Value.Trim(); + if (encodingName.Length > 0) + { + return EncodingFromName(parameter.Value); + } + } + } + } + + // Select the default encoding for this media type. + if (String.Equals(this.type, XmlConstants.MimeTextType, StringComparison.OrdinalIgnoreCase)) + { + // HTTP 3.7.1 Canonicalization and Text Defaults + // "text" subtypes default to ISO-8859-1 + // + // Unless the subtype is XML, in which case we should default + // to us-ascii. Instead we return null, to let the encoding + // in the PI win (http://tools.ietf.org/html/rfc3023#section-3.1) + if (String.Equals(this.subType, XmlConstants.MimeXmlSubType, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + return MissingEncoding; + } + + if (String.Equals(this.type, XmlConstants.MimeApplicationType, StringComparison.OrdinalIgnoreCase) && + String.Equals(this.subType, XmlConstants.MimeJsonSubType, StringComparison.OrdinalIgnoreCase)) + { + // http://tools.ietf.org/html/rfc4627#section-3 + // The default encoding is UTF-8. + return FallbackEncoding; + } + + return null; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ConventionalODataEntityMetadataBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ConventionalODataEntityMetadataBuilder.cs new file mode 100644 index 0000000..12a8981 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ConventionalODataEntityMetadataBuilder.cs @@ -0,0 +1,183 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Text; + using Microsoft.OData.Edm.Vocabularies; + + /// + /// Implementation of which uses OData conventions. + /// + internal sealed class ConventionalODataEntityMetadataBuilder : ODataResourceMetadataBuilder + { + /// The entity instance to build metadata for. + private readonly IEdmStructuredValue entityInstance; + + /// The name of the set the entity instance belongs to. + private readonly string entitySetName; + + /// The base uri of the service. + private readonly Uri baseUri; + + /// The convention-based uri builder to use. + private readonly ConventionalODataUriBuilder uriBuilder; + + /// + /// Initializes a new instance of the class. + /// + /// The base URI of the service. + /// Name of the entity set the entity belongs to. + /// The entity instance to build metadata for. + /// The user-specified delimiter to use. + internal ConventionalODataEntityMetadataBuilder(Uri baseUri, string entitySetName, IEdmStructuredValue entityInstance, DataServiceUrlKeyDelimiter keyDelimiter) + : this(UriResolver.CreateFromBaseUri(baseUri, "baseUri"), entitySetName, entityInstance, keyDelimiter) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The URI resolver to use. + /// Name of the entity set the entity belongs to. + /// The entity instance to build metadata for. + /// The user-specified conventions to use. + internal ConventionalODataEntityMetadataBuilder(UriResolver resolver, string entitySetName, IEdmStructuredValue entityInstance, DataServiceUrlKeyDelimiter keyDelimiter) + { + Util.CheckArgumentNullAndEmpty(entitySetName, "entitySetName"); + Util.CheckArgumentNull(entityInstance, "entityInstance"); + Util.CheckArgumentNull(keyDelimiter, "keyDelimiter"); + this.entitySetName = entitySetName; + this.entityInstance = entityInstance; + + this.uriBuilder = new ConventionalODataUriBuilder(resolver, keyDelimiter); + this.baseUri = resolver.BaseUriOrNull; + } + + /// + /// Gets the edit link of the entity. + /// + /// + /// The absolute URI of the edit link for the entity. + /// Or null if it is not possible to determine the edit link. + /// + internal override Uri GetEditLink() + { + Uri entitySetUri = this.uriBuilder.BuildEntitySetUri(this.baseUri, this.entitySetName); + Uri editLink = this.uriBuilder.BuildEntityInstanceUri(entitySetUri, this.entityInstance); + return editLink; + } + + /// + /// Gets the ID of the entity. + /// + /// + /// The ID for the entity. + /// Or null if it is not possible to determine the ID. + /// + internal override Uri GetId() + { + return this.GetEditLink(); + } + + /// + /// Gets the ETag of the entity. + /// + /// + /// The ETag for the entity. + /// Or null if it is not possible to determine the ETag. + /// + internal override string GetETag() + { + return null; + } + + /// + /// Gets the read link of the entity. + /// + /// + /// The absolute URI of the read link for the entity. + /// Or null if it is not possible to determine the read link. + /// + internal override Uri GetReadLink() + { + return null; + } + + + /// + /// Get the id that need to be written into wire + /// + /// The id return to the caller + /// + /// If writer should write odata.id property into wire + /// + internal override bool TryGetIdForSerialization(out Uri id) + { + id = null; + return false; + } + + /// + /// Implementation of that uses conventions. + /// + private class ConventionalODataUriBuilder : ODataUriBuilder + { + /// The uri resolver to use for entity sets. + private readonly UriResolver resolver; + + /// The key delimiter user specified. + private readonly DataServiceUrlKeyDelimiter urlKeyDelimiter; + + /// + /// Initializes a new instance of the class. + /// + /// The uri resolver to use. + /// The key delimiter user specified. + internal ConventionalODataUriBuilder(UriResolver resolver, DataServiceUrlKeyDelimiter urlKeyDelimiter) + { + Debug.Assert(resolver != null, "resolver != null"); + this.resolver = resolver; + this.urlKeyDelimiter = urlKeyDelimiter; + } + + /// + /// Appends to create the URI for an entity set. + /// + /// The URI to append to + /// The entity set name. + /// + /// The entity set URI. + /// + internal override Uri BuildEntitySetUri(Uri baseUri, string entitySetName) + { + return this.resolver.GetEntitySetUri(entitySetName); + } + + /// + /// Appends to create the entity instance URI for the specified . + /// + /// The URI to append to + /// The entity instance to use. + /// + /// The entity instance URI. + /// + internal override Uri BuildEntityInstanceUri(Uri baseUri, IEdmStructuredValue entityInstance) + { + var builder = new StringBuilder(); + if (baseUri != null) + { + builder.Append(UriUtil.UriToString(baseUri)); + } + + this.urlKeyDelimiter.AppendKeyExpression(entityInstance, builder); + return UriUtil.CreateUri(builder.ToString(), UriKind.RelativeOrAbsolute); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuery.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuery.cs new file mode 100644 index 0000000..81a7bc4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuery.cs @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Threading.Tasks; + + /// + /// Object of an action which returns nothing. + /// + public class DataServiceActionQuery + { + /// + /// Context associated with this query. + /// + private readonly DataServiceContext Context; + + /// + /// Parameters of this action. + /// + private readonly BodyOperationParameter[] Parameters; + + /// + /// Object of an action which returns nothing. + /// + /// Context associated with this query. + /// The URI string for this action. + /// Parameters of this action. + public DataServiceActionQuery(DataServiceContext context, string requestUriString, params BodyOperationParameter[] parameters) + { + this.Context = context; + this.RequestUri = new Uri(requestUriString); + this.Parameters = parameters; + } + + /// + /// The URI for this action. + /// + public Uri RequestUri { get; private set; } + +#if !PORTABLELIB // Synchronous methods not available + /// + /// Executes the action and returns the operation response. + /// + /// Operation result. + /// Problem materializing result of query into object. + public OperationResponse Execute() + { + return Context.Execute(this.RequestUri, XmlConstants.HttpMethodPost, Parameters); + } +#endif + + /// Asynchronously sends a request to the data service to execute a specific URI. + /// The result of the operation. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + public IAsyncResult BeginExecute(AsyncCallback callback, object state) + { + return Context.BeginExecute(this.RequestUri, callback, state, XmlConstants.HttpMethodPost, Parameters); + } + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// A task represents the result of the operation. + public Task ExecuteAsync() + { + return Context.ExecuteAsync(this.RequestUri, XmlConstants.HttpMethodPost, Parameters); + } + + /// Called to complete the . + /// The result of the operation. + /// An that represents the status of the asynchronous operation. + /// This method should be used in combination with the BeginExecute overload which + /// expects the request uri to end with an action that returns void. + public OperationResponse EndExecute(IAsyncResult asyncResult) + { + return Context.EndExecute(asyncResult); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQueryOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQueryOfT.cs new file mode 100644 index 0000000..5a47183 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQueryOfT.cs @@ -0,0 +1,102 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// Object of an action which returns a collection. + /// + /// Type of object to materialize. + public sealed class DataServiceActionQuery + { + /// + /// Context associated with this query. + /// + private readonly DataServiceContext Context; + + /// + /// Parameters of this action. + /// + private readonly BodyOperationParameter[] Parameters; + + /// + /// Object of an action which returns a collection. + /// + /// Context associated with this query. + /// The URI string for this action. + /// Parameters of this action. + public DataServiceActionQuery(DataServiceContext context, string requestUriString, params BodyOperationParameter[] parameters) + { + this.Context = context; + this.RequestUri = new Uri(requestUriString); + this.Parameters = parameters; + } + + /// + /// The URI for this action. + /// + public Uri RequestUri { get; private set; } + +#if !PORTABLELIB // Synchronous methods not available + /// + /// Executes the action and returns the results as a collection. + /// + /// Action results. + /// Problem materializing result of query into object. + public IEnumerable Execute() + { + return Context.Execute(this.RequestUri, XmlConstants.HttpMethodPost, false, Parameters); + } +#endif + + /// Asynchronously sends a request to the data service to execute a specific URI. + /// The result of the operation. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IAsyncResult BeginExecute(AsyncCallback callback, object state) + { + return Context.BeginExecute(this.RequestUri, callback, state, XmlConstants.HttpMethodPost, false, Parameters); + } + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// A task represents the result of the operation. + public Task> ExecuteAsync() + { + return Context.ExecuteAsync(this.RequestUri, XmlConstants.HttpMethodPost, false, Parameters); + } + + /// Called to complete the . + /// The results returned by the query operation. + /// + /// object. + /// When is null. + /// When did not originate from this instance. -or- When the method was previously called. + /// When an error is raised either during execution of the request or when it converts the contents of the response message into objects. + /// When the data service returns an HTTP 404: Resource Not Found error. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IEnumerable EndExecute(IAsyncResult asyncResult) + { + Util.CheckArgumentNull(asyncResult, "asyncResult"); + return Context.EndExecute(asyncResult); + } + +#if !PORTABLELIB // Synchronous methods not available + /// + /// Executes the query and returns the results as a collection. + /// + /// A typed enumerator over the results in which TElement represents the type of the query results. + public IEnumerator GetEnumerator() + { + return this.Execute().GetEnumerator(); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuerySingleOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuerySingleOfT.cs new file mode 100644 index 0000000..545daed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceActionQuerySingleOfT.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Object of an action which returns a single item. + /// + /// Type of object to materialize. + public sealed class DataServiceActionQuerySingle + { + /// + /// Context associated with this query. + /// + private readonly DataServiceContext context; + + /// + /// Parameters of this action. + /// + private readonly BodyOperationParameter[] parameters; + + /// + /// Object of an action which returns a single item. + /// + /// Context associated with this query. + /// The URI string for this action. + /// Parameters of this action. + public DataServiceActionQuerySingle(DataServiceContext context, string requestUriString, params BodyOperationParameter[] parameters) + { + this.context = context; + this.RequestUri = new Uri(requestUriString); + this.parameters = parameters; + } + + /// + /// The URI for this action. + /// + public Uri RequestUri { get; private set; } + +#if !PORTABLELIB // Synchronous methods not available + /// + /// Executes the action and returns the result. + /// + /// Action result. + /// Problem materializing result of query into object. + public T GetValue() + { + return context.Execute(this.RequestUri, XmlConstants.HttpMethodPost, true, parameters).Single(); + } +#endif + + /// Asynchronously sends a request to the data service to execute a specific URI. + /// The result of the operation. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IAsyncResult BeginGetValue(AsyncCallback callback, object state) + { + return context.BeginExecute(this.RequestUri, callback, state, XmlConstants.HttpMethodPost, true, parameters); + } + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// A task represents the result of the operation. + public Task GetValueAsync() + { + return Task.Factory.FromAsync(this.BeginGetValue, this.EndGetValue, null); + } + + /// Called to complete the . + /// The results returned by the query operation. + /// + /// object. + /// When is null. + /// When did not originate from this instance. -or- When the method was previously called. + /// When an error is raised either during execution of the request or when it converts the contents of the response message into objects. + /// When the data service returns an HTTP 404: Resource Not Found error. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public T EndGetValue(IAsyncResult asyncResult) + { + Util.CheckArgumentNull(asyncResult, "asyncResult"); + return context.EndExecute(asyncResult).Single(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientConfigurations.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientConfigurations.cs new file mode 100644 index 0000000..59f8a2e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientConfigurations.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Diagnostics; + + /// + /// Configurations on the behavior of the Client. + /// + public class DataServiceClientConfigurations + { + /// + /// Creates a data service client configurations class + /// + /// The sender for the Reading Atom event. + internal DataServiceClientConfigurations(object sender) + { + Debug.Assert(sender != null, "sender!= null"); + + this.ResponsePipeline = new DataServiceClientResponsePipelineConfiguration(sender); + this.RequestPipeline = new DataServiceClientRequestPipelineConfiguration(); + } + + /// + /// Gets the response configuration pipeline. + /// + public DataServiceClientResponsePipelineConfiguration ResponsePipeline { get; private set; } + + /// + /// Gets the request pipeline. + /// + public DataServiceClientRequestPipelineConfiguration RequestPipeline { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientException.cs new file mode 100644 index 0000000..7e4d14b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientException.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + + /// + /// The exception that is thrown when the server returns an error. + /// +#if !PORTABLELIB + [Serializable] +#endif + [System.Diagnostics.DebuggerDisplay("{Message}")] + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "No longer relevant after .NET 4 introduction of SerializeObjectState event and ISafeSerializationData interface.")] + public sealed class DataServiceClientException : InvalidOperationException + { + /// + /// Contains the state for this exception. + /// +#if !PORTABLELIB + [NonSerialized] +#endif + private DataServiceClientExceptionSerializationState state; + + #region Constructors + + /// Initializes a new instance of the class with a system-supplied message that describes the error. + public DataServiceClientException() + : this(Strings.DataServiceException_GeneralError) + { + } + + /// Initializes a new instance of the class with a specified message that describes the error. + /// The message that describes the exception. The caller of this constructor is required to ensure that this string has been localized for the current system culture. + public DataServiceClientException(string message) + : this(message, null) + { + } + + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// The message that describes the exception. The caller of this constructor is required to ensure that this string has been localized for the current system culture. + /// The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. + public DataServiceClientException(string message, Exception innerException) + : this(message, innerException, 500) + { + } + + /// Initializes a new instance of the class. + /// The string value that contains the error message. + /// The integer value that contains status code. + public DataServiceClientException(string message, int statusCode) + : this(message, null, statusCode) + { + } + + /// Initializes a new instance of the class. + /// The string value that contains the error message. + /// The System.Exception object that contains the inner exception. + /// The integer value that contains status code. + public DataServiceClientException(string message, Exception innerException, int statusCode) + : base(message, innerException) + { + this.state.StatusCode = statusCode; + +#if !PORTABLELIB + this.SerializeObjectState += (sender, e) => e.AddSerializedState(this.state); +#endif + } + + #endregion Constructors + + #region Public properties + + /// Gets the HTTP error status code returned after . + /// An integer value that represents the exception. + public int StatusCode + { + get { return this.state.StatusCode; } + } + + #endregion Public properties + + /// + /// Contains the state of the exception, used for serialization in security transparent code. + /// +#if !PORTABLELIB + [Serializable] +#endif + private struct DataServiceClientExceptionSerializationState +#if !PORTABLELIB + : ISafeSerializationData +#endif + { + /// + /// Gets or sets the status code as returned by the server. + /// + public int StatusCode { get; set; } + +#if !PORTABLELIB + /// + /// Called when deserialization of the exception is complete. + /// + /// The deserialized exception. + void ISafeSerializationData.CompleteDeserialization(object deserialized) + { + var exception = (DataServiceClientException)deserialized; + exception.state = this; + } +#endif + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientFormat.cs new file mode 100644 index 0000000..44889d0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientFormat.cs @@ -0,0 +1,278 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData; + using Microsoft.OData.Edm; + + /// + /// Tracks the user-preferred format which the client should use when making requests. + /// + public sealed class DataServiceClientFormat + { + /// MIME type for ATOM bodies (http://www.iana.org/assignments/media-types/application/). + private const string MimeApplicationAtom = "application/atom+xml"; + + /// MIME type for JSON bodies (implies light in V3, verbose otherwise) (http://www.iana.org/assignments/media-types/application/). + private const string MimeApplicationJson = "application/json"; + + /// MIME type for JSON bodies in light mode (http://www.iana.org/assignments/media-types/application/). + private const string MimeApplicationJsonODataLight = "application/json;odata.metadata=minimal"; + + /// MIME type for JSON bodies in light mode with all metadata. + private const string MimeApplicationJsonODataLightWithAllMetadata = "application/json;odata.metadata=full"; + + /// MIME type for changeset multipart/mixed + private const string MimeMultiPartMixed = "multipart/mixed"; + + /// MIME type for XML bodies. + private const string MimeApplicationXml = "application/xml"; + + /// Combined accept header value for either 'application/atom+xml' or 'application/xml'. + private const string MimeApplicationAtomOrXml = MimeApplicationAtom + "," + MimeApplicationXml; + + /// text for the utf8 encoding + private const string Utf8Encoding = "UTF-8"; + + /// The character set the client wants the response to be in. + private const string HttpAcceptCharset = "Accept-Charset"; + + /// The context this format instance is associated with. + private readonly DataServiceContext context; + + /// The service edm model. + private IEdmModel serviceModel; + + /// + /// Initializes a new instance of the class. + /// + /// DataServiceContext instance associated with this format. + internal DataServiceClientFormat(DataServiceContext context) + { + Debug.Assert(context != null, "Context cannot be null for DataServiceClientFormat"); + + // On V6.0.2, we change the default format to be json for the client + this.ODataFormat = ODataFormat.Json; + this.context = context; + } + + /// + /// Gets the current format. Defaults to Atom if nothing else has been specified. + /// + public ODataFormat ODataFormat { get; private set; } + + /// + /// Invoked when using the parameterless UseJson method in order to get the service model. + /// + public Func LoadServiceModel { get; set; } + + /// + /// Gets the service model. + /// + internal IEdmModel ServiceModel + { + get + { + if (serviceModel == null && LoadServiceModel != null) + { + serviceModel = LoadServiceModel(); + } + + return serviceModel; + } + } + + /// + /// Indicates that the client should use the efficient JSON format. + /// + /// The model of the service. + public void UseJson(IEdmModel serviceModel) + { + Util.CheckArgumentNull(serviceModel, "serviceModel"); + + this.ODataFormat = ODataFormat.Json; + this.serviceModel = serviceModel; + } + + /// + /// Indicates that the client should use the efficient JSON format. Will invoke the LoadServiceModel delegate property in order to get the required service model. + /// + public void UseJson() + { + if (this.ServiceModel == null) + { + throw new InvalidOperationException(Strings.DataServiceClientFormat_LoadServiceModelRequired); + } + + this.ODataFormat = ODataFormat.Json; + } + + /// + /// Sets the value of the Accept header to the appropriate value for the current format. + /// + /// The headers to modify. + internal void SetRequestAcceptHeader(HeaderCollection headers) + { + this.SetAcceptHeaderAndCharset(headers, ChooseMediaType(false)); + } + + /// + /// Sets the value of the Accept header for a query. + /// + /// The headers to modify. + /// The query components for the request. + internal void SetRequestAcceptHeaderForQuery(HeaderCollection headers, QueryComponents components) + { + this.SetAcceptHeaderAndCharset(headers, ChooseMediaType(components.HasSelectQueryOption)); + } + + /// + /// Sets the value of the Accept header for a stream request (will set it to '*/*'). + /// + /// The headers to modify. + internal void SetRequestAcceptHeaderForStream(HeaderCollection headers) + { + this.SetAcceptHeaderAndCharset(headers, XmlConstants.MimeAny); + } + + /// + /// Sets the value of the Accept header for a count request (will set it to 'text/plain'). + /// + /// The headers to modify. + internal void SetRequestAcceptHeaderForCount(HeaderCollection headers) + { + this.SetAcceptHeaderAndCharset(headers, XmlConstants.MimeTextPlain); + } + + /// + /// Sets the value of the Accept header for a count request (will set it to 'multipart/mixed'). + /// + /// The headers to modify. + internal void SetRequestAcceptHeaderForBatch(HeaderCollection headers) + { + this.SetAcceptHeaderAndCharset(headers, MimeMultiPartMixed); + } + + /// + /// Sets the value of the ContentType header on the specified entry request to the appropriate value for the current format. + /// + /// Dictionary of request headers. + internal void SetRequestContentTypeForEntry(HeaderCollection headers) + { + this.SetRequestContentTypeHeader(headers, ChooseMediaType(false)); + } + + /// + /// Sets the value of the Content-Type header a request with operation parameters to the appropriate value for the current format. + /// + /// Dictionary of request headers. + internal void SetRequestContentTypeForOperationParameters(HeaderCollection headers) + { + // Note: There has never been an atom or xml format for parameters. + this.SetRequestContentTypeHeader(headers, MimeApplicationJsonODataLight); + } + + /// + /// Sets the value of the ContentType header on the specified links request to the appropriate value for the current format. + /// + /// Dictionary of request headers. + internal void SetRequestContentTypeForLinks(HeaderCollection headers) + { + this.SetRequestContentTypeHeader(headers, ChooseMediaType(false)); + } + + /// + /// Validates that we can write the request format. + /// + /// The request message to get the format from. + internal static void ValidateCanWriteRequestFormat(IODataRequestMessage requestMessage) + { + string contentType = requestMessage.GetHeader(XmlConstants.HttpContentType); + ValidateContentType(contentType); + } + + /// + /// Validates that we can read the response format. + /// + /// The response message to get the format from. + internal static void ValidateCanReadResponseFormat(IODataResponseMessage responseMessage) + { + string contentType = responseMessage.GetHeader(XmlConstants.HttpContentType); + ValidateContentType(contentType); + } + + /// + /// Validates that we can read or write a message with the given content-type value. + /// + /// The content-type value in question. + private static void ValidateContentType(string contentType) + { + if (string.IsNullOrEmpty(contentType)) + { + return; + } + + // Ideally ODataLib should have a public API to get the ODataFormat from the content-type header. + // Unfortunately since that's not available, we will process the content-type value to determine if the format is JSON Light. + string mime; + ContentTypeUtil.ReadContentType(contentType, out mime); + } + + /// + /// Sets the request's content type header. + /// + /// Dictionary of request headers. + /// content type + private void SetRequestContentTypeHeader(HeaderCollection headers, string mediaType) + { + if (mediaType == MimeApplicationJsonODataLight) + { + // set the request version to 4.0 + headers.SetRequestVersion(Util.ODataVersion4, this.context.MaxProtocolVersionAsVersion); + } + + headers.SetHeaderIfUnset(XmlConstants.HttpContentType, mediaType); + } + + /// + /// Sets the accept header to the given value and the charset to UTF-8. + /// + /// The headers to update. + /// The media type for the accept header. + [SuppressMessage("Microsoft.Performance", "CA1822", Justification = "If this becomes static, then so do its more visible callers, and we do not want to provide a mix of instance and static methods on this class.")] + private void SetAcceptHeaderAndCharset(HeaderCollection headers, string mediaType) + { + // NOTE: we intentionally do NOT set the DSV header for 'accept' as content-negotiation + // is primarily about determining how to respond and not how to interpret the request. + // It is entirely valid to send a V1 request and get a V3 response. + // (We do set the DSV to 3 for Content-Type above) + headers.SetHeaderIfUnset(XmlConstants.HttpAccept, mediaType); + headers.SetHeaderIfUnset(HttpAcceptCharset, Utf8Encoding); + } + + /// + /// Chooses between using JSON-Light and the context-dependent media type for when Atom is selected based on the user-selected format. + /// + /// + /// Whether or not the select query option is present in the request URI. + /// If true, indicates that the client should ask the server to include all metadata in a JSON-Light response. + /// + /// The media type to use (either JSON-Light or the provided value) + private static string ChooseMediaType(bool hasSelectQueryOption) + { + if (hasSelectQueryOption) + { + return MimeApplicationJsonODataLightWithAllMetadata; + } + + return MimeApplicationJsonODataLight; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestMessageArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestMessageArgs.cs new file mode 100644 index 0000000..d6cda27 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestMessageArgs.cs @@ -0,0 +1,85 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// + /// Arguments for creating an instance of DataServiceClientRequestMessage. + /// + public class DataServiceClientRequestMessageArgs + { + /// The actual method. + private readonly string actualMethod; + + /// + /// Initializes a new instance of the class. + /// + /// Method of the request. + /// The Request Uri. + /// True if the default credentials need to be sent with the request. Otherwise false. + /// True if the request message must use POST verb for the request and pass the actual verb in X-HTTP-Method header, otherwise false. + /// The set of headers for the request. + public DataServiceClientRequestMessageArgs(string method, Uri requestUri, bool useDefaultCredentials, bool usePostTunneling, IDictionary headers) + { + Debug.Assert(method != null, "method cannot be null"); + Debug.Assert(requestUri != null, "requestUri cannot be null"); + Debug.Assert(headers != null, "headers cannot be null"); + + this.Headers = headers; + this.Method = method; + this.RequestUri = requestUri; + this.UsePostTunneling = usePostTunneling; + this.UseDefaultCredentials = useDefaultCredentials; + + this.actualMethod = this.Method; + if (this.UsePostTunneling && this.Headers.ContainsKey(XmlConstants.HttpXMethod)) + { + this.actualMethod = XmlConstants.HttpMethodPost; + } + } + + /// + /// Gets the method. + /// + public string Method { get; private set; } + + /// + /// Gets the request URI. + /// + public Uri RequestUri { get; private set; } + + /// + /// Returns whether the request message should use Post-Tunneling. + /// + public bool UsePostTunneling { get; private set; } + + /// + /// Gets the headers. + /// + public IDictionary Headers { get; private set; } + + /// + /// Gets the actual method. Indicates correct method to use in the post tunneling case. + /// + public string ActualMethod + { + get + { + return this.actualMethod; + } + } + + /// + /// Gets a System.Boolean value that controls whether default credentials are sent with requests. + /// + public bool UseDefaultCredentials { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestPipelineConfiguration.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestPipelineConfiguration.cs new file mode 100644 index 0000000..c9b8ecb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientRequestPipelineConfiguration.cs @@ -0,0 +1,285 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData; + using ClientStrings = Microsoft.OData.Client.Strings; + + /// + /// Class that holds a variety of events for writing the payload from the OData to the wire + /// + public class DataServiceClientRequestPipelineConfiguration + { + /// Actions to execute before start entry called. + private readonly List> writingStartResourceActions; + + /// Actions to execute before end entry called. + private readonly List> writingEndResourceActions; + + /// Actions to execute before entity reference link written. + private readonly List> writeEntityReferenceLinkActions; + + /// Actions to execute after before start navigation link called. + private readonly List> writingStartNestedResourceInfoActions; + + /// Actions to execute before end navigation link called. + private readonly List> writingEndNestedResourceInfoActions; + + /// The message writer setting configurations. + private readonly List> messageWriterSettingsConfigurationActions; + + /// The delegate that represents how a message is created. + private Func onmessageCreating; + + /// + /// Creates a request pipeline configuration class + /// + internal DataServiceClientRequestPipelineConfiguration() + { + this.writeEntityReferenceLinkActions = new List>(); + this.writingEndResourceActions = new List>(); + this.writingEndNestedResourceInfoActions = new List>(); + this.writingStartResourceActions = new List>(); + this.writingStartNestedResourceInfoActions = new List>(); + this.messageWriterSettingsConfigurationActions = new List>(); + } + + /// + /// Gets the request message to be used for sending the request. By providing a custom message, users + /// can replace the transport layer. + /// + public Func OnMessageCreating + { + get + { + return this.onmessageCreating; + } + + set + { + if (this.ContextUsingSendingRequest) + { + throw new DataServiceClientException(ClientStrings.Context_SendingRequest_InvalidWhenUsingOnMessageCreating); + } + + this.onmessageCreating = value; + } + } + + /// + /// Determines if OnMessageCreating is being used or not. + /// + internal bool HasOnMessageCreating + { + get { return this.OnMessageCreating != null; } + } + + /// + /// Gets or sets the a value indicating whether the context is using the sending request event or not. + /// + internal bool ContextUsingSendingRequest { get; set; } + + /// + /// Called when [message writer created]. + /// + /// The args. + /// The request pipeline configuration. + public DataServiceClientRequestPipelineConfiguration OnMessageWriterSettingsCreated(Action args) + { + WebUtil.CheckArgumentNull(args, "args"); + + this.messageWriterSettingsConfigurationActions.Add(args); + return this; + } + + /// + /// Called when [entry starting]. + /// + /// The action. + /// The request pipeline configuration. + public DataServiceClientRequestPipelineConfiguration OnEntryStarting(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.writingStartResourceActions.Add(action); + return this; + } + + /// + /// Called when [entry ending]. + /// + /// The action. + /// The request pipeline configuration. + public DataServiceClientRequestPipelineConfiguration OnEntryEnding(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.writingEndResourceActions.Add(action); + return this; + } + + /// + /// Called when [entity reference link]. + /// + /// The action. + /// The request pipeline configuration. + public DataServiceClientRequestPipelineConfiguration OnEntityReferenceLink(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.writeEntityReferenceLinkActions.Add(action); + return this; + } + + /// + /// Called when [navigation link starting]. + /// + /// The action. + /// The request pipeline configuration. + public DataServiceClientRequestPipelineConfiguration OnNestedResourceInfoStarting(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.writingStartNestedResourceInfoActions.Add(action); + return this; + } + + /// + /// Called when [navigation link end]. + /// + /// The action. + /// The request pipeline configuration. + public DataServiceClientRequestPipelineConfiguration OnNestedResourceInfoEnding(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.writingEndNestedResourceInfoActions.Add(action); + return this; + } + + /// + /// Called when [create message writer settings configurations]. + /// + /// The writer settings. + internal void ExecuteWriterSettingsConfiguration(ODataMessageWriterSettings writerSettings) + { + Debug.Assert(writerSettings != null, "writerSettings != null"); + + if (this.messageWriterSettingsConfigurationActions.Count > 0) + { + MessageWriterSettingsArgs args = new MessageWriterSettingsArgs(writerSettings); + foreach (Action configureWriterSettings in this.messageWriterSettingsConfigurationActions) + { + configureWriterSettings(args); + } + } + } + + /// + /// Fires before entry end. + /// + /// The entry. + /// The entity. + internal void ExecuteOnEntryEndActions(ODataResource entry, object entity) + { + Debug.Assert(entry != null, "entry != null"); + Debug.Assert(entity != null, "entity != entity"); + + if (this.writingEndResourceActions.Count > 0) + { + WritingEntryArgs args = new WritingEntryArgs(entry, entity); + foreach (Action entryArgsAction in this.writingEndResourceActions) + { + entryArgsAction(args); + } + } + } + + /// + /// Fires before entry start. + /// + /// The entry. + /// The entity. + internal void ExecuteOnEntryStartActions(ODataResource entry, object entity) + { + Debug.Assert(entry != null, "entry != null"); + Debug.Assert(entity != null, "entity != entity"); + + if (this.writingStartResourceActions.Count > 0) + { + WritingEntryArgs args = new WritingEntryArgs(entry, entity); + foreach (Action entryArgsAction in this.writingStartResourceActions) + { + entryArgsAction(args); + } + } + } + + /// + /// Fires before navigation end. + /// + /// The link. + /// The source. + /// The target. + internal void ExecuteOnNestedResourceInfoEndActions(ODataNestedResourceInfo link, object source, object target) + { + Debug.Assert(link != null, "link != null"); + + if (this.writingEndNestedResourceInfoActions.Count > 0) + { + WritingNestedResourceInfoArgs args = new WritingNestedResourceInfoArgs(link, source, target); + foreach (Action navArgsAction in this.writingEndNestedResourceInfoActions) + { + navArgsAction(args); + } + } + } + + /// + /// Fires before navigation start. + /// + /// The link. + /// The source. + /// The target. + internal void ExecuteOnNestedResourceInfoStartActions(ODataNestedResourceInfo link, object source, object target) + { + Debug.Assert(link != null, "link != null"); + + if (this.writingStartNestedResourceInfoActions.Count > 0) + { + WritingNestedResourceInfoArgs args = new WritingNestedResourceInfoArgs(link, source, target); + foreach (Action navArgsAction in this.writingStartNestedResourceInfoActions) + { + navArgsAction(args); + } + } + } + + /// + /// Fires before writing the on entity reference link. + /// + /// The entity reference link. + /// The source. + /// The target. + internal void ExecuteEntityReferenceLinkActions(ODataEntityReferenceLink entityReferenceLink, object source, object target) + { + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + if (this.writeEntityReferenceLinkActions.Count > 0) + { + WritingEntityReferenceLinkArgs args = new WritingEntityReferenceLinkArgs(entityReferenceLink, source, target); + foreach (Action navArgsAction in this.writeEntityReferenceLinkActions) + { + navArgsAction(args); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientResponsePipelineConfiguration.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientResponsePipelineConfiguration.cs new file mode 100644 index 0000000..ed6d38e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceClientResponsePipelineConfiguration.cs @@ -0,0 +1,364 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData; + using Microsoft.OData.Client.Materialization; + + /// + /// Class that is responsible for configuration of actions that are invoked from a response + /// + public class DataServiceClientResponsePipelineConfiguration + { + /// Actions to be run when reading start entry called + private readonly List> readingStartResourceActions; + + /// Actions to be run when reading end entry called + private readonly List> readingEndResourceActions; + + /// Actions to be run when reading start feed called + private readonly List> readingStartFeedActions; + + /// Actions to be run when reading end feed called + private readonly List> readingEndFeedActions; + + /// Actions to be run when reading start link called + private readonly List> readingStartNestedResourceInfoActions; + + /// Actions to be run when reading end link called + private readonly List> readingEndNestedResourceInfoActions; + + /// Actions to be run after an entry has been materialized + private readonly List> materializedEntityActions; + + /// The message reader setting configurations. + private readonly List> messageReaderSettingsConfigurationActions; + + /// The sender. + private readonly object sender; + + /// + /// Creates a Data service client response pipeline class + /// + /// The sender for the Reading Atom event. + internal DataServiceClientResponsePipelineConfiguration(object sender) + { + Debug.Assert(sender != null, "sender!= null"); + + this.sender = sender; + this.readingEndResourceActions = new List>(); + this.readingEndFeedActions = new List>(); + this.readingEndNestedResourceInfoActions = new List>(); + + this.readingStartResourceActions = new List>(); + this.readingStartFeedActions = new List>(); + this.readingStartNestedResourceInfoActions = new List>(); + + this.materializedEntityActions = new List>(); + + this.messageReaderSettingsConfigurationActions = new List>(); + } + + /// + /// Gets a value indicating whether this instance has handlers. + /// + /// + /// true if this instance has handlers; otherwise, false. + /// + internal bool HasConfigurations + { + get + { + return this.readingStartResourceActions.Count > 0 || + this.readingEndResourceActions.Count > 0 || + this.readingStartFeedActions.Count > 0 || + this.readingEndFeedActions.Count > 0 || + this.readingStartNestedResourceInfoActions.Count > 0 || + this.readingEndNestedResourceInfoActions.Count > 0; + } + } + + /// + /// Gets whether there is a reading entity handler + /// + internal bool HasReadingEntityHandlers + { + get + { + if (this.materializedEntityActions.Count > 0) + { + return true; + } + + return false; + } + } + + /// + /// Called when [reader settings created]. + /// + /// The reader message settings configuration. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnMessageReaderSettingsCreated(Action messageReaderSettingsAction) + { + WebUtil.CheckArgumentNull(messageReaderSettingsAction, "messageReaderSettingsAction"); + + this.messageReaderSettingsConfigurationActions.Add(messageReaderSettingsAction); + return this; + } + + /// + /// Called when [read start entry]. + /// + /// The action. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnEntryStarted(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.readingStartResourceActions.Add(action); + return this; + } + + /// + /// Called when [read end entry]. + /// + /// The action. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnEntryEnded(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.readingEndResourceActions.Add(action); + return this; + } + + /// + /// Called when [read start feed]. + /// + /// The action. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnFeedStarted(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.readingStartFeedActions.Add(action); + return this; + } + + /// + /// Called when [read end feed]. + /// + /// The action. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnFeedEnded(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.readingEndFeedActions.Add(action); + return this; + } + + /// + /// Called when [read start navigation link]. + /// + /// The action. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnNestedResourceInfoStarted(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.readingStartNestedResourceInfoActions.Add(action); + return this; + } + + /// + /// Called when [read end navigation link]. + /// + /// The action. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnNestedResourceInfoEnded(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.readingEndNestedResourceInfoActions.Add(action); + return this; + } + + /// + /// Called when [entity materialized]. + /// + /// The action. + /// The response pipeline configuration. + public DataServiceClientResponsePipelineConfiguration OnEntityMaterialized(Action action) + { + WebUtil.CheckArgumentNull(action, "action"); + + this.materializedEntityActions.Add(action); + return this; + } + + /// + /// Executes actions that configure reader settings. + /// + /// The reader settings. + internal void ExecuteReaderSettingsConfiguration(ODataMessageReaderSettings readerSettings) + { + Debug.Assert(readerSettings != null, "readerSettings != null"); + + if (this.messageReaderSettingsConfigurationActions.Count > 0) + { + MessageReaderSettingsArgs args = new MessageReaderSettingsArgs(readerSettings); + foreach (Action readerSettingsConfigurationAction in this.messageReaderSettingsConfigurationActions) + { + readerSettingsConfigurationAction(args); + } + } + } + + /// + /// Executes the on entry end actions. + /// + /// The entry. + internal void ExecuteOnEntryEndActions(ODataResource entry) + { + // Be noticed that the entry could be null in some case, like expand. + if (this.readingEndResourceActions.Count > 0) + { + ReadingEntryArgs args = new ReadingEntryArgs(entry); + foreach (Action entryAction in this.readingEndResourceActions) + { + entryAction(args); + } + } + } + + /// + /// Executes the on entry start actions. + /// + /// The entry. + internal void ExecuteOnEntryStartActions(ODataResource entry) + { + // Be noticed that the entry could be null in some case, like expand. + if (this.readingStartResourceActions.Count > 0) + { + ReadingEntryArgs args = new ReadingEntryArgs(entry); + foreach (Action entryAction in this.readingStartResourceActions) + { + entryAction(args); + } + } + } + + /// + /// Executes the on feed end actions. + /// + /// The feed. + internal void ExecuteOnFeedEndActions(ODataResourceSet feed) + { + Debug.Assert(feed != null, "entry != null"); + + if (this.readingEndFeedActions.Count > 0) + { + ReadingFeedArgs args = new ReadingFeedArgs(feed); + foreach (Action feedAction in this.readingEndFeedActions) + { + feedAction(args); + } + } + } + + /// + /// Executes the on feed start actions. + /// + /// The feed. + internal void ExecuteOnFeedStartActions(ODataResourceSet feed) + { + Debug.Assert(feed != null, "feed != null"); + if (this.readingStartFeedActions.Count > 0) + { + ReadingFeedArgs args = new ReadingFeedArgs(feed); + foreach (Action feedAction in this.readingStartFeedActions) + { + feedAction(args); + } + } + } + + /// + /// Executes the on navigation end actions. + /// + /// The link. + internal void ExecuteOnNavigationEndActions(ODataNestedResourceInfo link) + { + Debug.Assert(link != null, "link != null"); + if (this.readingEndNestedResourceInfoActions.Count > 0) + { + ReadingNestedResourceInfoArgs args = new ReadingNestedResourceInfoArgs(link); + foreach (Action navAction in this.readingEndNestedResourceInfoActions) + { + navAction(args); + } + } + } + + /// + /// Executes the on navigation start actions. + /// + /// The link. + internal void ExecuteOnNavigationStartActions(ODataNestedResourceInfo link) + { + Debug.Assert(link != null, "link != null"); + + if (this.readingStartNestedResourceInfoActions.Count > 0) + { + ReadingNestedResourceInfoArgs args = new ReadingNestedResourceInfoArgs(link); + foreach (Action navAction in this.readingStartNestedResourceInfoActions) + { + navAction(args); + } + } + } + + /// + /// Fires after the entry was materialized + /// + /// The entry. + /// The entity. + internal void ExecuteEntityMaterializedActions(ODataResource entry, object entity) + { + Debug.Assert(entry != null, "entry != null"); + Debug.Assert(entity != null, "entity != entity"); + + if (this.materializedEntityActions.Count > 0) + { + MaterializedEntityArgs args = new MaterializedEntityArgs(entry, entity); + foreach (Action materializedEntryArgsAction in this.materializedEntityActions) + { + materializedEntryArgsAction(args); + } + } + } + + /// + /// Fires the end entry events. + /// + /// The entry. + internal void FireEndEntryEvents(MaterializerEntry entry) + { + Debug.Assert(entry != null, "entry!=null"); + + if (this.HasReadingEntityHandlers) + { + this.ExecuteEntityMaterializedActions(entry.Entry, entry.ResolvedObject); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceContext.cs new file mode 100644 index 0000000..81d1060 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceContext.cs @@ -0,0 +1,3508 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +// #define TESTUNIXNEWLINE + + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using System.Linq.Expressions; + using System.Net; + using System.Reflection; + using System.Threading.Tasks; + using Microsoft.OData; + using Microsoft.OData.Client.Annotation; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + using ClientStrings = Microsoft.OData.Client.Strings; + + #endregion Namespaces + /// + /// Indicates DataServiceContext's behavior on undeclared property in entity/complex value. + /// + internal enum UndeclaredPropertyBehavior + { + /// + /// The default value. Supports undeclared property. + /// + Support = 0, + + /// + /// Throw on undeclared property. + /// + ThrowException = 1, + } + + /// + /// The represents the runtime context of the data service. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506", Justification = "Central class of the API, likely to have many cross-references")] + public class DataServiceContext + { + /// Same version as but stored as instance of for easy comparisons. + internal Version MaxProtocolVersionAsVersion; + + /// + /// string constant for the 'serviceRoot' parameter to UriResolver + /// + private const string ServiceRootParameterName = "serviceRoot"; + + /// The client model for the current context instance. + private readonly ClientEdmModel model; + + /// Internal instance annotations in current context + private readonly WeakDictionary> instanceAnnotations = new WeakDictionary>(InstanceAnnotationDictWeakKeyComparer.Default) + { + RemoveCollectedEntriesRules = new List> + { + InstanceAnnotationDictWeakKeyComparer.Default.RemoveRule + }, + CreateWeakKey = InstanceAnnotationDictWeakKeyComparer.Default.CreateKey + }; + + /// metadata annotations for current context + private readonly WeakDictionary> metadataAnnotationsDictionary = new WeakDictionary>(EqualityComparer.Default); + + /// The tracker for user-specified format information. + private DataServiceClientFormat formatTracker; + + /// The maximum protocol version the client should support (send and receive). + private ODataProtocolVersion maxProtocolVersion; + + /// + /// Class which tracks all the entities and links for the given context + /// + private EntityTracker entityTracker; + + /// + /// The response preference for add and update operations + /// + private DataServiceResponsePreference addAndUpdateResponsePreference; + + /// The resolver for baseUris + private UriResolver baseUriResolver; + + /// Authentication interface for retrieving credentials for Web client authentication. + private System.Net.ICredentials credentials; + + /// resolve type from a typename + private Func resolveName; + + /// resolve typename from a type + private Func resolveType; + +#if !PORTABLELIB // Timeout not available + /// time-out value in seconds, 0 for default + private int timeout; +#endif + /// whether to use post-tunneling for PUT/DELETE + private bool postTunneling; + + /// Used to specify a strategy to send entity parameter. + private EntityParameterSendOption entityParameterSendOption; + + /// Used to specify a value synchronization strategy. + private MergeOption mergeOption; + + /// Default options to be used while doing savechanges. + private SaveChangesOptions saveChangesDefaultOptions; + + /// Client will ignore 404 resource not found exception and return an empty set when this is set to true + private bool ignoreResourceNotFoundException; + + /// Options that can overwrite ignoreMissingProperties. + private UndeclaredPropertyBehavior undeclaredPropertyBehavior = UndeclaredPropertyBehavior.Support; + + /// The URL key delimiter to use. + private DataServiceUrlKeyDelimiter urlKeyDelimiter; + + /// The HTTP stack to use for requests. + private HttpStack httpStack; + + #region Test hooks for header and payload verification + +#pragma warning disable 0169, 0649 + /// + /// Test hook which gets called after the HttpWebRequest has been created and all headers have been set. + /// Never set by product code. + /// + private Action sendRequest; + + /// + /// Test hook which gets called after we call HttpWebRequest.GetRequestStream, so that the test code can wrap the stream and see what gets written to it. + /// Never set by product code. + /// + private Func getRequestWrappingStream; + + /// + /// Test hook which gets called after the HttpWebResponse is received. + /// Never set by product code. + /// + private Action sendResponse; + + /// + /// Test hook which gets called after we call HttpWebRequest.GetResponseStream, so that the test code can wrap the stream and see what gets read from it. + /// Never set by product code. + /// + private Func getResponseWrappingStream; +#pragma warning restore 0169, 0649 + + #endregion + + /// + /// A flag indicating if the data service context is applying changes + /// + private bool applyingChanges; + + #region ctor + + /// Initializes a new instance of the class. + /// It is expected that the BaseUri or ResolveEntitySet properties will be set before using the newly created context. + public DataServiceContext() + : this(null) + { + } + + /// Initializes a new instance of the class with the specified . + /// An absolute URI that identifies the root of a data service. + /// When the is null. + /// If the is not an absolute URI -or-If the is a well formed URI without a query or query fragment. + /// + /// The library expects the Uri to point to the root of a data service, + /// but does not issue a request to validate it does indeed identify the root of a service. + /// If the Uri does not identify the root of the service, the behavior of the client library is undefined. + /// A Uri provided with a trailing slash is equivalent to one without such a trailing character. + /// With Silverlight, the can be a relative Uri + /// that will be combined with System.Windows.Browser.HtmlPage.Document.DocumentUri. + /// + public DataServiceContext(Uri serviceRoot) + : this(serviceRoot, ODataProtocolVersion.V4) + { + } + + /// Initializes a new instance of the class with the specified and targeting the specific . + /// An absolute URI that identifies the root of a data service. + /// A value that is the maximum protocol version that the client understands. + /// + /// The library expects the Uri to point to the root of a data service, + /// but does not issue a request to validate it does indeed identify the root of a service. + /// If the Uri does not identify the root of the service, the behavior of the client library is undefined. + /// A Uri provided with a trailing slash is equivalent to one without such a trailing character. + /// With Silverlight, the can be a relative Uri + /// that will be combined with System.Windows.Browser.HtmlPage.Document.DocumentUri. + /// + public DataServiceContext(Uri serviceRoot, ODataProtocolVersion maxProtocolVersion) + : this(serviceRoot, maxProtocolVersion, ClientEdmModelCache.GetModel(maxProtocolVersion)) + { + } + + /// + /// Instantiates a new context with the specified Uri. + /// The library expects the Uri to point to the root of a data service, + /// but does not issue a request to validate it does indeed identify the root of a service. + /// If the Uri does not identify the root of the service, the behavior of the client library is undefined. + /// + /// + /// An absolute, well formed http or https URI without a query or fragment which identifies the root of a data service. + /// A Uri provided with a trailing slash is equivalent to one without such a trailing character + /// + /// max protocol version that the client understands. + /// The client edm model to use. Provided for testability. + /// If the is not a valid value. + /// + /// With Silverlight, the can be a relative Uri + /// that will be combined with System.Windows.Browser.HtmlPage.Document.DocumentUri. + /// + internal DataServiceContext(Uri serviceRoot, ODataProtocolVersion maxProtocolVersion, ClientEdmModel model) + { + Debug.Assert(model != null, "model != null"); + this.model = model; + + this.baseUriResolver = UriResolver.CreateFromBaseUri(serviceRoot, ServiceRootParameterName); + this.maxProtocolVersion = Util.CheckEnumerationValue(maxProtocolVersion, "maxProtocolVersion"); + this.entityParameterSendOption = EntityParameterSendOption.SendFullProperties; + this.mergeOption = MergeOption.AppendOnly; + this.entityTracker = new EntityTracker(model); + this.MaxProtocolVersionAsVersion = Util.GetVersionFromMaxProtocolVersion(maxProtocolVersion); + this.formatTracker = new DataServiceClientFormat(this); + this.urlKeyDelimiter = DataServiceUrlKeyDelimiter.Parentheses; + this.Configurations = new DataServiceClientConfigurations(this); + this.httpStack = HttpStack.Auto; + this.UsingDataServiceCollection = false; + this.UsePostTunneling = false; + + // Need to use the same defaults when running sl in portable lib as when running in SL normally. +#if PORTABLELIB + if (HttpWebRequestMessage.IsRunningOnSilverlight) + { + this.UsePostTunneling = true; + this.UseDefaultCredentials = true; + } +#endif + } + + #endregion + + /// + /// This event is fired before a request is sent to the server, giving + /// the handler the opportunity to inspect, adjust and/or replace the + /// WebRequest object used to perform the request. + /// + /// + /// When calling BeginSaveChanges and not using SaveChangesOptions.BatchWithSingleChangeset and SaveChangesOptions.BatchWithIndependentOperations, + /// this event may be raised from a different thread. + /// + public event EventHandler SendingRequest2; + + /// + /// This event is fired before a request message object is built, giving + /// the handler the opportunity to inspect, adjust and/or replace some + /// request information before the message is built. This event should be + /// used to modify the outgoing Url of the request or alter request headers. + /// After the request is built, other modifications on the WebRequest object can be made + /// in SendingRequest2. + /// + /// + /// When calling BeginSaveChanges and not using SaveChangesOptions.BatchWithSingleChangeset and SaveChangesOptions.BatchWithIndependentOperations, + /// this event may be raised from a different thread. + /// + public event EventHandler BuildingRequest + { + add + { + this.InnerBuildingRequest += value; + } + + remove + { + this.InnerBuildingRequest -= value; + } + } + + /// + /// This event fires when a response is received by the client. + /// It fires for both top level responses and each operation or query within a batch response. + /// + /// + /// On top level requests, the event is fired before any processing is done. + /// For inner batch operations, the event is also fired before any processing is done, with + /// the exception that the content-ID of a changeset operation will be read before the event is fired. + /// + public event EventHandler ReceivingResponse; + + /// + /// This event fires when SaveChanges or EndSaveChanges is called + /// + internal event EventHandler ChangesSaved; + + /// + /// Internal event instance used by the public SendingRequest event. + /// + private event EventHandler InnerSendingRequest; + + /// + /// Internal event instance used by the public BuildingRequest event. + /// + private event EventHandler InnerBuildingRequest; + + #region BaseUri, Credentials, MergeOption, Timeout, Links, Entities, Prefer header + + /// Gets or sets the delegate method that is used to resolve the entity set URI when the value cannot be determined from an edit-link or self-link URI. + /// A delegate that takes a and returns a value. + public Func ResolveEntitySet + { + get + { + return this.baseUriResolver.ResolveEntitySet; + } + + set + { + this.baseUriResolver = this.baseUriResolver.CloneWithOverrideValue(value); + } + } + + /// Gets the absolute URI identifying the root of the target data service. + /// An absolute URI that identifies the root of a T data service. + /// + /// A Uri provided with a trailing slash is equivalent to one without such a trailing character. + /// Example: http://server/host/myservice.svc + /// + public Uri BaseUri + { + get + { + return this.baseUriResolver.RawBaseUriValue; + } + + set + { + if (this.baseUriResolver == null) + { + this.baseUriResolver = UriResolver.CreateFromBaseUri(value, ServiceRootParameterName); + } + else + { + this.baseUriResolver = this.baseUriResolver.CloneWithOverrideValue(value, null /*parameterName*/); + } + } + } + + /// Gets or sets whether the client requests that the data service return entity data in the response message to a change request. + /// A object that determines whether to request a response form the data service. + /// Whether POST/PUT/PATCH requests will process response from the server. Corresponds to Prefer header in HTTP POST/PUT/PATCH request. + public DataServiceResponsePreference AddAndUpdateResponsePreference + { + get + { + return this.addAndUpdateResponsePreference; + } + + set + { + if (value != DataServiceResponsePreference.None) + { + this.EnsureMinimumProtocolVersionV3(); + } + + this.addAndUpdateResponsePreference = value; + } + } + + /// Gets the maximum version of the Open Data Protocol (OData) that the client is allowed to use. + /// The maximum version of OData that the client is allowed to use. + /// If the request or response would require higher version the client will fail. + public ODataProtocolVersion MaxProtocolVersion + { + get + { + return this.maxProtocolVersion; + } + + internal set + { + this.maxProtocolVersion = value; + this.MaxProtocolVersionAsVersion = Util.GetVersionFromMaxProtocolVersion(this.maxProtocolVersion); + } + } + + /// Gets or sets the authentication information that is used by each query created by using the object. + /// The base authentication interface for retrieving credentials for Web client authentication. + public System.Net.ICredentials Credentials + { + get { return this.credentials; } + set { this.credentials = value; } + } + + /// Gets or sets the option for sending entity parameters to service. + /// One of the members of the enumeration. + public EntityParameterSendOption EntityParameterSendOption + { + get { return this.entityParameterSendOption; } + set { this.entityParameterSendOption = Util.CheckEnumerationValue(value, "EntityParameterSendOption"); } + } + + /// Gets or sets the synchronization option for receiving entities from a data service. + /// One of the members of the enumeration. + /// + /// Used to specify a synchronization strategy when sending/receiving entities to/from a data service. + /// This value is read by the deserialization component of the client prior to materializing objects. + /// As such, it is recommended to set this property to the appropriate materialization strategy + /// before executing any queries/updates to the data service. + /// The default value is .AppendOnly. + /// + public MergeOption MergeOption + { + get { return this.mergeOption; } + set { this.mergeOption = Util.CheckEnumerationValue(value, "MergeOption"); } + } + + /// Gets a value that indicates whether the is currently applying changes to tracked objects. + /// Returns true when changes are currently being applied; otherwise returns false. + public bool ApplyingChanges + { + get { return this.applyingChanges; } + internal set { this.applyingChanges = value; } + } + + /// Gets or sets a function to override the default type resolution strategy used by the client library when you send entities to a data service. + /// Returns a string that contains the name of the . + /// + /// Enables one to override the default type resolution strategy used by the client library. + /// Set this property to a delegate which identifies a function that resolves + /// a type within the client application to a namespace-qualified type name. + /// This enables the client to perform custom mapping between the type name + /// provided in a response from the server and a type on the client. + /// This method enables one to override the entity name that is serialized + /// to the target representation (ATOM,JSON, etc) for the specified type. + /// + public Func ResolveName + { + get { return this.resolveName; } + set { this.resolveName = value; } + } + + /// Gets or sets a function that is used to override the default type resolution option that is used by the client library when receiving entities from a data service. + /// A function delegate that identifies an override function that is used to override the default type resolution option that is used by the client library. + /// + /// Enables one to override the default type resolution strategy used by the client library. + /// Set this property to a delegate which identifies a function that resolves a + /// namespace-qualified type name to type within the client application. + /// This enables the client to perform custom mapping between the type name + /// provided in a response from the server and a type on the client. + /// Overriding type resolution enables inserting a custom type name to type mapping strategy. + /// It does not enable one to affect how a response is materialized into the identified type. + /// + public Func ResolveType + { + get { return this.resolveType; } + set { this.resolveType = value; } + } + +#if !PORTABLELIB // Timeout not available + /// Gets or sets the time-out option (in seconds) that is used for the underlying HTTP request to the data service. + /// An integer that indicates the time interval (in seconds) before time-out of a service request. + /// + /// A value of 0 will use the default timeout of the underlying HTTP request. + /// This value must be set before executing any query or update operations against + /// the target data service for it to have effect on the on the request. + /// The value may be changed between requests to a data service and the new value + /// will be picked up by the next data service request. + /// + public int Timeout + { + get + { + return this.timeout; + } + + set + { + if (value < 0) + { + throw Error.ArgumentOutOfRange("Timeout"); + } + + this.timeout = value; + } + } +#endif + + /// Gets or sets a Boolean value that indicates whether to use post tunneling. + /// A Boolean value that indicates whether to use post tunneling. + public bool UsePostTunneling + { + get { return this.postTunneling; } + set { this.postTunneling = value; } + } + + /// Gets the collection of all associations or links currently being tracked by the object. + /// A collection of objects that represent all associations or links current being tracked by the current being tracked by the object. + /// If no links are being tracked, a collection with 0 elements is returned. + public ReadOnlyCollection Links + { + get + { + return new ReadOnlyCollection(this.entityTracker.Links.OrderBy(l => l.ChangeOrder).ToList()); + } + } + + /// Gets a list of all the resources currently being tracked by the . + /// A list of objects that represent all the resources currently being tracked by the . + /// If no resources are being tracked, a collection with 0 elements is returned. + public ReadOnlyCollection Entities + { + get + { +#if DEBUG + foreach (EntityDescriptor entityDescriptor in this.entityTracker.Entities) + { + foreach (StreamDescriptor streamDescriptor in entityDescriptor.StreamDescriptors) + { + Debug.Assert(Object.ReferenceEquals(streamDescriptor.EntityDescriptor, entityDescriptor), "All the StreamDescriptor instances should contain the current EntityDescriptor instance in its EntityDescriptor property"); + } + } +#endif + + return new ReadOnlyCollection(this.entityTracker.Entities.OrderBy(d => d.ChangeOrder).ToList()); + } + } + + /// Gets or sets the values that are used by the method. + /// The current options for the save changes operation. + public SaveChangesOptions SaveChangesDefaultOptions + { + get + { + return this.saveChangesDefaultOptions; + } + + set + { + this.ValidateSaveChangesOptions(value); + this.saveChangesDefaultOptions = value; + } + } + + #endregion + + /// Gets or sets whether an exception is raised when a 404 error (resource not found) is returned by the data service. + /// When set to true, the client library returns an empty set instead of raising a when the data service returns an HTTP 404: Resource Not Found error. + public bool IgnoreResourceNotFoundException + { + get { return this.ignoreResourceNotFoundException; } + set { this.ignoreResourceNotFoundException = value; } + } + + /// + /// Gets the configurations. + /// + public DataServiceClientConfigurations Configurations { get; private set; } + + /// + /// Gets an object which allows the user to customize the format the client will use for making requests. + /// + public DataServiceClientFormat Format + { + get { return this.formatTracker; } + } + + /// + /// Gets or sets the URL key delimiter the client should use. + /// + public DataServiceUrlKeyDelimiter UrlKeyDelimiter + { + get + { + return this.urlKeyDelimiter; + } + + set + { + Util.CheckArgumentNull(value, "value"); + this.urlKeyDelimiter = value; + } + } + + /// + /// Returns the instance of entity tracker which tracks all the entities and links tracked by the context. + /// + public EntityTracker EntityTracker + { + get + { + return this.entityTracker; + } + + set + { + this.entityTracker = value; + } + } + + /// + /// Disable instance annotation to be materialized. + /// + public bool DisableInstanceAnnotationMaterialization { get; set; } + + /// + /// Whether enable writing odata annotation without prefix. + /// + public bool EnableWritingODataAnnotationWithoutPrefix { get; set; } + + /// Gets or sets whether to support undeclared properties. + /// UndeclaredPropertyBehavior. + internal UndeclaredPropertyBehavior UndeclaredPropertyBehavior + { + get { return this.undeclaredPropertyBehavior; } + set { this.undeclaredPropertyBehavior = value; } + } + + /// + /// Gets or sets a System.Boolean value that controls whether default credentials are sent with requests. + /// + internal bool UseDefaultCredentials { get; set; } + + /// Gets a value that indicates the type of HTTP implementation to use when accessing the data service in Silverlight. + /// A value that indicates the HTTP implementation to use when accessing the data service. + /// Default value is HttpStack.Auto + internal HttpStack HttpStack + { + get { return this.httpStack; } + set { this.httpStack = Util.CheckEnumerationValue(value, "HttpStack"); } + } + + /// Indicates if there are subscribers for the SendingRequest2 event. + internal bool HasSendingRequest2EventHandlers + { + [DebuggerStepThrough] + get { return this.SendingRequest2 != null; } + } + + /// + /// INdicates if there are any subscribers for the BuildingRequestEvent. + /// + internal bool HasBuildingRequestEventHandlers + { + [DebuggerStepThrough] + get { return this.InnerBuildingRequest != null; } + } + + /// The tracker for user-specified format information. + internal DataServiceClientFormat FormatTracker + { + get + { + return this.formatTracker; + } + + set + { + this.formatTracker = value; + } + } + + /// Returns the instance of entity tracker which tracks all the entities and links tracked by the context. + internal UriResolver BaseUriResolver + { + get + { + return this.baseUriResolver; + } + } + + /// + /// Gets the client model. + /// + internal ClientEdmModel Model + { + get { return this.model; } + } + + /// + /// Indicates whether user is using to track changes. + /// + internal bool UsingDataServiceCollection { get; set; } + + /// The instance annotations in current context + internal WeakDictionary> InstanceAnnotations + { + get + { + return instanceAnnotations; + } + } + + /// + /// Gets the MetadataAnnotationsDictionary + /// + internal WeakDictionary> MetadataAnnotationsDictionary + { + get + { + return metadataAnnotationsDictionary; + } + } + + #region GetAnnotation + + /// + /// Try to get instance annotations or metadata annotation associated with the specified object. + /// + /// CLR type of the annotation + /// The annotated object + /// The term name of an annotation + /// Qualifier to apply + /// + /// When this method returns, contains the annotation associated with the specified object and term, if the annotation is found; + /// otherwise, the default value for the type of the annotation parameter. + /// + /// true if the annotation is found + public bool TryGetAnnotation(object source, string term, string qualifier, out TResult annotation) + { + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNull(term, "term"); + + if (qualifier != null) + { + return AnnotationHelper.TryGetMetadataAnnotation(this, source, term, qualifier, out annotation); + } + + object instanceAnnotation; + IDictionary instanceAnnotationInfo; + object key = source; + + // Try to get instance annotation + if (this.InstanceAnnotations.TryGetValue(key, out instanceAnnotationInfo) + && instanceAnnotationInfo.TryGetValue(term, out instanceAnnotation)) + { + if (instanceAnnotation is TResult) + { + annotation = (TResult)instanceAnnotation; + return true; + } + + if (ClientTypeUtil.CanAssignNull(typeof(TResult)) && instanceAnnotation == null) + { + annotation = default(TResult); + return true; + } + } + + return AnnotationHelper.TryGetMetadataAnnotation(this, key, term, null, out annotation); + } + + /// + /// Try to get instance annotations or metadata annotation associated with the specified object. + /// + /// CLR type of the annotation + /// The annotated object + /// The term name of an annotation + /// + /// When this method returns, contains the annotation associated with the specified object and term, if the annotation is found; + /// otherwise, the default value for the type of the annotation parameter. + /// + /// true if the annotation is found + public bool TryGetAnnotation(object source, string term, out TResult annotation) + { + return TryGetAnnotation(source, term, null, out annotation); + } + + /// + /// Try to get instance annotations or metadata annotation for property or navigation property. + /// Or try to get metadata annotation for property, navigation property, entitySet, singleton, operation or operation import. + /// + /// Type of the action or function the expression represents + /// CLR Type of annotation + /// The closure expression to access following items: + /// property + /// navigation property + /// entitySet + /// singleton + /// function + /// action + /// function import + /// action import + /// + /// The term name of the annotation + /// Qualifier to apply + /// + /// When this method returns, contains the annotation associated with the specified expression and term, if the annotation is found; + /// otherwise, the default value for the type of the annotation parameter. + /// + /// true if the annotation is found + public bool TryGetAnnotation(Expression expression, string term, string qualifier, out TResult annotation) + { + Util.CheckArgumentNull(expression, "expression"); + Util.CheckArgumentNull(term, "term"); + + object key = null; + Expression callerExpression = null; + MemberInfo memberInfo = null; + object caller = null; + + if (expression.Body.NodeType == ExpressionType.MemberAccess) + { + var memberExp = (MemberExpression)expression.Body; + callerExpression = memberExp.Expression; + memberInfo = memberExp.Member; + } + else if (expression.Body.NodeType == ExpressionType.Call) + { + var callExp = (MethodCallExpression)expression.Body; + memberInfo = callExp.Method; + callerExpression = callExp.Object; + } + + if (callerExpression != null) + { + try + { + caller = Expression.Lambda(callerExpression).Compile().DynamicInvoke(); + } + catch (InvalidCastException) + { + // If this expression is invalid to compile, the TryGetAnnotation operation will return false. + annotation = default(TResult); + return false; + } + } + + key = new Tuple(caller, memberInfo); + if (key != null) + { + return TryGetAnnotation(key, term, qualifier, out annotation); + } + + annotation = default(TResult); + return false; + } + + /// + /// Try to get instance annotations or metadata annotation for property or navigation property. + /// Or try to get metadata annotation for property, navigation property, entitySet, singleton, operation or operation import. + /// + /// Type of the action or function the expression represents + /// Type of annotation + /// The closure expression to access following items: + /// property + /// navigation property + /// entitySet + /// singleton + /// function + /// action + /// function import + /// action import + /// + /// The term name of an annotation + /// + /// When this method returns, contains the annotation associated with the specified expression and term, if the annotation is found; + /// otherwise, the default value for the type of the annotation parameter. + /// + /// true if the annotation is found + /// + public bool TryGetAnnotation(Expression expression, string term, out TResult annotation) + { + return TryGetAnnotation(expression, term, null, out annotation); + } + + #endregion GetAnnotation + + #region Entity and Link Tracking + + /// Gets the for the supplied entity object. + /// The instance for the , or null if an does not exist for the object. + /// The object for which to return the entity descriptor. + public EntityDescriptor GetEntityDescriptor(object entity) + { + Util.CheckArgumentNull(entity, "entity"); + return this.entityTracker.TryGetEntityDescriptor(entity); + } + + /// Gets the for a specific link that defines the relationship between two entities. + /// The instance for the specified relationship, or null if a does not exist for the relationship. + /// Source object in the link + /// The name of the navigation property on the object that returns the related object. + /// The related entity. + public LinkDescriptor GetLinkDescriptor(object source, string sourceProperty, object target) + { + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNullAndEmpty(sourceProperty, "sourceProperty"); + Util.CheckArgumentNull(target, "target"); + + return this.entityTracker.TryGetLinkDescriptor(source, sourceProperty, target); + } + + #endregion + + #region CancelRequest + /// Attempts to cancel the operation that is associated with the supplied object. + /// The object from the operation being canceled. + /// DataServiceContext is not safe to use until asyncResult.IsCompleted is true. + public void CancelRequest(IAsyncResult asyncResult) + { + Util.CheckArgumentNull(asyncResult, "asyncResult"); + BaseAsyncResult result = asyncResult as BaseAsyncResult; + + // verify this asyncResult orginated from this context or via query from this context + if ((null == result) || (this != result.Source)) + { + object context = null; + DataServiceQuery query = null; + if (null != result) + { + query = result.Source as DataServiceQuery; + + if (null != query) + { + DataServiceQueryProvider provider = query.Provider as DataServiceQueryProvider; + if (null != provider) + { + context = provider.Context; + } + } + } + + if (this != context) + { + throw Error.Argument(Strings.Context_DidNotOriginateAsync, "asyncResult"); + } + } + + // at this point the result originated from this context or a query from this context + if (!result.IsCompletedInternally) + { + result.SetAborted(); + + ODataRequestMessageWrapper request = result.Abortable; + if (null != request) + { + // with Silverlight we can't wait around to check if the request was aborted + // because that would block callbacks for the abort from actually running. + request.Abort(); + } + } + } + #endregion + + #region CreateQuery + /// Creates a data service query for data of a specified generic type. + /// A new instance that represents a data service query. + /// A string that resolves to a URI. + /// The type returned by the query + /// create a query based on (BaseUri + relativeUri) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "required for this feature")] + public DataServiceQuery CreateQuery(string entitySetName) + { + Util.CheckArgumentNullAndEmpty(entitySetName, "entitySetName"); + ValidateEntitySetName(ref entitySetName); + + ResourceSetExpression rse = new ResourceSetExpression(typeof(IOrderedQueryable), null, Expression.Constant(entitySetName), typeof(T), null, CountOption.None, null, null, null, null); + return new DataServiceQuery.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this)); + } + + /// Creates a data service query for a function with return type in a specified generic type. + /// A new instance that represents a data service query. + /// A string ends with function invocation that resolves to a URI. + /// Whether this function query is composable + /// The type returned by the query + /// create a query based on (BaseUri + relativeUri) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "required for this feature")] + public DataServiceQuery CreateQuery(string resourcePath, bool isComposable) + { + Util.CheckArgumentNullAndEmpty(resourcePath, "entitySetName"); + ValidateEntitySetName(ref resourcePath); + + ResourceSetExpression rse = new ResourceSetExpression(typeof(IOrderedQueryable), null, Expression.Constant(resourcePath), typeof(T), null, CountOption.None, null, null, null, null); + var query = new DataServiceQuery.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this), isComposable); + return query; + } + + /// Creates a data service query for a function invocation that returns a specified generic type. + /// A new instance that represents a data service query. + /// The type returned by the query + /// create a query based on (BaseUri + relativeUri) + public DataServiceQuery CreateFunctionQuery() + { + ResourceSetExpression rse = new ResourceSetExpression(typeof(IOrderedQueryable), null, null, typeof(T), null, CountOption.None, null, null, null, null); + return new DataServiceQuery.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this)); + } + + /// Creates a data service query for function which return collection of data. + /// The type returned by the query + /// The path before the function. + /// The function name. + /// Whether this query is composable. + /// The function parameters. + /// A new instance that represents the function call. + public DataServiceQuery CreateFunctionQuery(string path, string functionName, bool isComposable, params UriOperationParameter[] parameters) + { + Dictionary operationParameters = this.SerializeOperationParameters(parameters); + ResourceSetExpression rse = new ResourceSetExpression(typeof(IOrderedQueryable), null, Expression.Constant(path), typeof(T), null, CountOption.None, null, null, null, null, functionName, operationParameters, false); + return new DataServiceQuery.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this), isComposable); + } + + /// Creates a data service single query for function which return single data. + /// The type returned by the query + /// The path before the function. + /// The function name. + /// Whether this query is composable. + /// The function parameters. + /// A new instance that represents the function call. + public DataServiceQuerySingle CreateFunctionQuerySingle(string path, string functionName, bool isComposable, params UriOperationParameter[] parameters) + { + Dictionary operationParameters = this.SerializeOperationParameters(parameters); + SingletonResourceExpression rse = new SingletonResourceExpression(typeof(IOrderedQueryable), null, Expression.Constant(path), typeof(T), null, CountOption.None, null, null, null, null, functionName, operationParameters, false); + return new DataServiceQuerySingle(new DataServiceQuery.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this)), isComposable); + } + + /// Creates a data service query for singleton data of a specified generic type. + /// A new instance that represents a data service query. + /// A string that resolves to a URI. + /// The type returned by the query + /// create a query based on (BaseUri + relativeUri) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "required for this feature")] + public DataServiceQuery CreateSingletonQuery(string singletonName) + { + Util.CheckArgumentNullAndEmpty(singletonName, "singletonName"); + ValidateEntitySetName(ref singletonName); + + SingletonResourceExpression rse = new SingletonResourceExpression(typeof(IOrderedQueryable), null, Expression.Constant(singletonName), typeof(T), null, CountOption.None, null, null, null, null); + return new DataServiceQuery.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this)); + } + + #endregion + + #region GetMetadataUri + /// Gets a URI of the location of .edmx metadata. + /// A URI that identifies the location of the metadata description, in .edmx format, for the data service identified by the base URI that is passed to the constructor. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "required for this feature")] + public Uri GetMetadataUri() + { + // TODO: resolve the location of the metadata endpoint for the service by using an HTTP OPTIONS request + Uri metadataUri = UriUtil.CreateUri(UriUtil.UriToString(this.BaseUriResolver.GetBaseUriWithSlash()) + XmlConstants.UriMetadataSegment, UriKind.Absolute); + return metadataUri; + } + #endregion + + #region LoadProperty + + /// Asynchronously loads the value of the specified property from the data service. + /// An IAsyncResult that represents the status of the asynchronous operation. + /// The entity that contains the property to load. + /// The name of the property on the specified entity to load. + /// The delegate called when a response to the request is received. + /// The user-defined state object that is used to pass context data to the callback method. + /// actually doesn't modify the property until EndLoadProperty is called. + public IAsyncResult BeginLoadProperty(object entity, string propertyName, AsyncCallback callback, object state) + { + return this.BeginLoadProperty(entity, propertyName, (Uri)null /*nextLinkUri*/, callback, state); + } + + /// Asynchronously loads the value of the specified property from the data service. + /// A task that represents the response to the load operation. + /// The entity that contains the property to load. + /// The name of the property on the specified entity to load. + public Task LoadPropertyAsync(object entity, string propertyName) + { + return Task.Factory.FromAsync(this.BeginLoadProperty, this.EndLoadProperty, entity, propertyName, null); + } + + /// Asynchronously loads a page of related entities from the data service by using the supplied next link URI. + /// An object that is used to track the status of the asynchronous operation. + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// The URI used to load the next results page. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + public IAsyncResult BeginLoadProperty(object entity, string propertyName, Uri nextLinkUri, AsyncCallback callback, object state) + { + LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, callback, state, nextLinkUri, null); + result.BeginExecuteQuery(); + return result; + } + + /// Asynchronously loads a page of related entities from the data service by using the supplied next link URI. + /// A task that represents the response to the load operation. + /// The entity that contains the property to load. + /// The name of the property on the specified entity to load. + /// The URI used to load the next results page. + public Task LoadPropertyAsync(object entity, string propertyName, Uri nextLinkUri) + { + return Task.Factory.FromAsync(this.BeginLoadProperty, this.EndLoadProperty, entity, propertyName, nextLinkUri, null); + } + + /// Asynchronously loads the next page of related entities from the data service by using the supplied query continuation object. + /// An that represents the status of the operation. + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// A object that represents the next page of related entity data to return from the data service. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + public IAsyncResult BeginLoadProperty(object entity, string propertyName, DataServiceQueryContinuation continuation, AsyncCallback callback, object state) + { + Util.CheckArgumentNull(continuation, "continuation"); + LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, callback, state, null /*requestUri*/, continuation); + result.BeginExecuteQuery(); + return result; + } + + /// Asynchronously loads the next page of related entities from the data service by using the supplied query continuation object. + /// A Task that represents the response to the load operation. + /// The entity that contains the property to load. + /// The name of the property on the specified entity to load. + /// A object that represents the next page of related entity data to return from the data service. + public Task LoadPropertyAsync(object entity, string propertyName, DataServiceQueryContinuation continuation) + { + return Task.Factory.FromAsync(this.BeginLoadProperty, this.EndLoadProperty, entity, propertyName, continuation, null); + } + + /// Called to complete the operation. + /// The response to the load operation. + /// An that represents the status of the asynchronous operation. + public QueryOperationResponse EndLoadProperty(IAsyncResult asyncResult) + { + LoadPropertyResult response = BaseAsyncResult.EndExecute(this, Util.LoadPropertyMethodName, asyncResult); + return response.LoadProperty(); + } + +#if !PORTABLELIB // Synchronous methods not available + /// Loads deferred content for a specified property from the data service. + /// The response to the load operation. + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// + /// If is in in detached or added state, this method will throw an InvalidOperationException + /// since there is nothing it can load from the server. + /// + /// If is in unchanged or modified state, this method will load its collection or + /// reference elements as unchanged with unchanged bindings. + /// + /// If is in deleted state, this method will load the entities linked to by its collection or + /// reference property in the unchanged state with bindings in the deleted state. + /// + public QueryOperationResponse LoadProperty(object entity, string propertyName) + { + return this.LoadProperty(entity, propertyName, (Uri)null); + } + + /// Loads a page of related entities by using the supplied next link URI. + /// An instance of that contains the results of the request. + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// The URI that is used to load the next results page. + /// When is in a or state. + /// + /// If is in in detached or added state, this method will throw an InvalidOperationException + /// since there is nothing it can load from the server. + /// + /// If is in unchanged or modified state, this method will load its collection or + /// reference elements as unchanged with unchanged bindings. + /// + /// If is in deleted state, this method will load the entities linked to by its collection or + /// reference property in the unchanged state with bindings in the deleted state. + /// + public QueryOperationResponse LoadProperty(object entity, string propertyName, Uri nextLinkUri) + { + LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null /*callback*/, null /*state*/, nextLinkUri, null /*continuation*/); + result.ExecuteQuery(); + return result.LoadProperty(); + } + + /// Loads the next page of related entities from the data service by using the supplied query continuation object. + /// The response that contains the next page of related entity data. + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// A object that represents the next page of related entities to load from the data service. + /// When is in the or state. + /// + /// If is in in detached or added state, this method will throw an InvalidOperationException + /// since there is nothing it can load from the server. + /// + /// If is in unchanged or modified state, this method will load its collection or + /// reference elements as unchanged with unchanged bindings. + /// + /// If is in deleted state, this method will load the entities linked to by its collection or + /// reference property in the unchanged state with bindings in the deleted state. + /// + public QueryOperationResponse LoadProperty(object entity, string propertyName, DataServiceQueryContinuation continuation) + { + LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null /*callback*/, null /*state*/, null /*requestUri*/, continuation); + result.ExecuteQuery(); + return result.LoadProperty(); + } + + /// Loads the next page of related entities from the data service by using the supplied generic query continuation object. + /// The response that contains the next page of related entity data. + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// A object that represents the next page of related entities to load from the data service. + /// Element type of collection to load. + /// When is in the or state. + /// + /// If is in in detached or added state, this method will throw an InvalidOperationException + /// since there is nothing it can load from the server. + /// + /// If is in unchanged or modified state, this method will load its collection or + /// reference elements as unchanged with unchanged bindings. + /// + /// If is in deleted state, this method will load the entities linked to by its collection or + /// reference property in the unchanged state with bindings in the deleted state. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011", Justification = "allows compiler to infer 'T'")] + public QueryOperationResponse LoadProperty(object entity, string propertyName, DataServiceQueryContinuation continuation) + { + LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null /*callback*/, null /*state*/, null /*requestUri*/, continuation); + result.ExecuteQuery(); + return (QueryOperationResponse)result.LoadProperty(); + } + +#endif + + + #endregion + + #region GetReadStreamUri + /// Gets the URI that is used to return a binary data stream. + /// The read URI of the binary data stream. + /// The entity that has a related binary stream to retrieve. + /// If the entity specified is null. + /// The is not tracked by this . + /// If the specified entity is a Media Link Entry, this method will return an URI which can be used to access the content of the Media Resource. This URI should only be used to GET/Read the content of the MR. It may not respond to POST/PUT/DELETE requests. + public Uri GetReadStreamUri(object entity) + { + Util.CheckArgumentNull(entity, "entity"); + EntityDescriptor box = this.entityTracker.GetEntityDescriptor(entity); + return box.ReadStreamUri; + } + + /// Gets the URI that is used to return a named binary data stream. + /// The read URI of the binary data stream. + /// The entity that has the named binary data stream to retrieve. + /// The name of the stream to request. + /// If the specified entity has a stream with the given name, this method will return an URI which can be used to access the content of the stream. This URI should only be used to GET/Read the content of the stream. It may not respond to POST/PUT/DELETE requests. + /// If the entity specified is null. + /// If the name parameter is empty or the entity specified is not being tracked. + public Uri GetReadStreamUri(object entity, string name) + { + Util.CheckArgumentNull(entity, "entity"); + Util.CheckArgumentNullAndEmpty(name, "name"); + this.EnsureMinimumProtocolVersionV3(); + EntityDescriptor entityDescriptor = this.entityTracker.GetEntityDescriptor(entity); + StreamDescriptor namedStreamInfo; + if (entityDescriptor.TryGetNamedStreamInfo(name, out namedStreamInfo)) + { + return namedStreamInfo.SelfLink ?? namedStreamInfo.EditLink; + } + + return null; + } + + #endregion + + #region GetReadStream, BeginGetReadStream, EndGetReadStream + + /// Asynchronously gets the binary data stream that belongs to the specified entity, by using the specified message headers. + /// An object that is used to track the status of the asynchronous operation. + /// The entity that has a the binary data stream to retrieve. + /// Instance of the class that contains settings for the HTTP request message. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + /// Any of the parameters supplied to the method is null. + /// The is not tracked by this .-or-The is in the state.-or-The is not a Media Link Entry and does not have a related binary data stream. + public IAsyncResult BeginGetReadStream(object entity, DataServiceRequestArgs args, AsyncCallback callback, object state) + { + GetReadStreamResult result = this.CreateGetReadStreamResult(entity, args, callback, state, null /*name*/); + result.Begin(); + return result; + } + + /// Asynchronously gets the binary data stream that belongs to the specified entity, by using the specified message headers. + /// A Task that represents an instance of which contains the response stream along with its metadata. + /// The entity that has a the binary data stream to retrieve. + /// Instance of the class that contains settings for the HTTP request message. + /// Any of the parameters supplied to the method is null. + /// The is not tracked by this .-or-The is in the state.-or-The is not a Media Link Entry and does not have a related binary data stream. + public Task GetReadStreamAsync(object entity, DataServiceRequestArgs args) + { + return Task.Factory.FromAsync(this.BeginGetReadStream, this.EndGetReadStream, entity, args, null); + } + + /// Asynchronously gets a named binary data stream that belongs to the specified entity, by using the specified message headers. + /// An object that is used to track the status of the asynchronous operation. + /// The entity that has the binary data stream to retrieve. + /// The name of the binary stream to request. + /// Instance of the class that contains settings for the HTTP request message. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + public IAsyncResult BeginGetReadStream(object entity, string name, DataServiceRequestArgs args, AsyncCallback callback, object state) + { + Util.CheckArgumentNullAndEmpty(name, "name"); + this.EnsureMinimumProtocolVersionV3(); + GetReadStreamResult result = this.CreateGetReadStreamResult(entity, args, callback, state, name); + result.Begin(); + return result; + } + + /// Asynchronously gets the binary data stream that belongs to the specified entity, by using the specified message headers. + /// A task that represents an instance of which contains the response stream along with its metadata. + /// The entity that has a the binary data stream to retrieve. + /// The name of the binary stream to request. + /// Instance of the class that contains settings for the HTTP request message. + public Task GetReadStreamAsync(object entity, string name, DataServiceRequestArgs args) + { + return Task.Factory.FromAsync(this.BeginGetReadStream, this.EndGetReadStream, entity, name, args, null); + } + + /// Called to complete the asynchronous operation of retrieving a binary data stream. + /// An instance of which contains the response stream along with its metadata. + /// The result from the operation that contains the binary data stream. + /// The method will block if the request have not finished yet. + public DataServiceStreamResponse EndGetReadStream(IAsyncResult asyncResult) + { + GetReadStreamResult result = BaseAsyncResult.EndExecute(this, "GetReadStream", asyncResult); + return result.End(); + } + +#if !PORTABLELIB + /// Gets the binary data stream that belongs to the specified entity. + /// An instance of that represents the response. + /// The entity that has the binary stream to retrieve. + /// The is null. + /// The is not tracked by this .-or-The is in the state.-or-The is not a Media Link Entry and does not have a related binary stream. + public DataServiceStreamResponse GetReadStream(object entity) + { + DataServiceRequestArgs args = new DataServiceRequestArgs(); + return this.GetReadStream(entity, args); + } + + /// Gets the binary data stream that belongs to the specified entity, by using the specified Content-Type message header. + /// An instance of that represents the response. + /// The entity that has the binary data stream to retrieve. + /// The Content-Type of the binary data stream requested from the data service, specified in the Accept header. + /// + /// is null.-or- is null. + /// The is not tracked by this .-or-The is in the state.-or-The is not a Media Link Entry and does not have a related stream. + public DataServiceStreamResponse GetReadStream(object entity, string acceptContentType) + { + Util.CheckArgumentNullAndEmpty(acceptContentType, "acceptContentType"); + DataServiceRequestArgs args = new DataServiceRequestArgs(); + args.AcceptContentType = acceptContentType; + return this.GetReadStream(entity, args); + } + + /// Gets binary data stream for the specified entity by using the specified message headers. + /// An instance of that represents the response. + /// The entity that has the binary stream to retrieve. + /// Instance of class that contains settings for the HTTP request message. + /// + /// is null.-or- is null. + /// The is not tracked by this .-or-The is in the state.-or-The is not a Media Link Entry and does not have a related binary stream. + public DataServiceStreamResponse GetReadStream(object entity, DataServiceRequestArgs args) + { + GetReadStreamResult result = this.CreateGetReadStreamResult(entity, args, null, null, null); + return result.Execute(); + } + + /// Gets a named binary data stream that belongs to the specified entity, by using the specified Content-Type message header. + /// An instance of that represents the response. + /// The entity that has the binary data stream to retrieve. + /// The name of the binary stream to request. + /// Instance of class that contains settings for the HTTP request message. + /// Either entity or args parameters are null. + /// The specified entity is either not tracked, is in the added state. + public DataServiceStreamResponse GetReadStream(object entity, string name, DataServiceRequestArgs args) + { + Util.CheckArgumentNullAndEmpty(name, "name"); + this.EnsureMinimumProtocolVersionV3(); + GetReadStreamResult result = this.CreateGetReadStreamResult(entity, args, null, null, name); + return result.Execute(); + } + +#endif + #endregion + + #region SetSaveStream + + /// Sets a binary data stream that belongs to the specified entity, with the specified Content-Type and Slug headers in the request message. + /// The entity to which the data stream belongs. + /// The from which to read the binary data. + /// A value that determines whether the data stream is closed when the method is completed. + /// The Content-Type header value for the request message. + /// The Slug header value for the request message. + /// Any of the parameters supplied to the method are null. + /// The is not being tracked by this instance. -or-The entity has the applied. + /// Calling this method marks the entity as media link resource (MLE). It also marks the entity as modified + /// so that it will participate in the next call to SaveChanges. + public void SetSaveStream(object entity, Stream stream, bool closeStream, string contentType, string slug) + { + Util.CheckArgumentNull(contentType, "contentType"); + Util.CheckArgumentNull(slug, "slug"); + + DataServiceRequestArgs args = new DataServiceRequestArgs(); + args.ContentType = contentType; + args.Slug = slug; + this.SetSaveStream(entity, stream, closeStream, args); + } + + /// Sets a binary data stream for the specified entity, with the specified headers in the request message. + /// The entity to which the binary stream belongs. + /// The from which to read the binary data. + /// A value that determines whether the data stream is closed when the method is completed. + /// An instance of the class that contains settings for the HTTP request message. + /// Any of the parameters supplied to the method are null. + /// The is not being tracked by this instance. -or-The has the applied. + /// Calling this method marks the entity as media link resource (MLE). It also marks the entity as modified + /// so that it will participate in the next call to SaveChanges. + public void SetSaveStream(object entity, Stream stream, bool closeStream, DataServiceRequestArgs args) + { + Util.CheckArgumentNull(entity, "entity"); + Util.CheckArgumentNull(stream, "stream"); + Util.CheckArgumentNull(args, "args"); + + EntityDescriptor box = this.entityTracker.GetEntityDescriptor(entity); + ClientTypeAnnotation clientType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(entity.GetType())); + if (clientType.MediaDataMember != null) + { + throw new ArgumentException( + Strings.Context_SetSaveStreamOnMediaEntryProperty(clientType.ElementTypeName), + "entity"); + } + + box.SaveStream = new DataServiceSaveStream(stream, closeStream, args); + + Debug.Assert(box.State != EntityStates.Detached, "We should never have a detached entity in the entityDescriptor dictionary."); + switch (box.State) + { + case EntityStates.Added: + box.StreamState = EntityStates.Added; + break; + + case EntityStates.Modified: + case EntityStates.Unchanged: + box.StreamState = EntityStates.Modified; + break; + + default: + throw new DataServiceClientException(Strings.Context_SetSaveStreamOnInvalidEntityState(Enum.GetName(typeof(EntityStates), box.State))); + } + + // Note that there's no need to mark the entity as updated because we consider the presense + // of the save stream as the mark that the MR for this MLE has been updated. + + // TODO: why we don't increment the change order number in this case, when the entity is in unmodified state? + } + + /// Sets a binary data stream for the specified entity. + /// The entity to which the binary stream belongs. + /// The name of the binary stream to save. + /// The from which to read the binary data. + /// A value that determines whether the data stream is closed when the method is completed. + /// The Content-Type header value for the request message. + /// The entity is not being tracked or name is an empty string. + /// Any of the arguments is null. + public void SetSaveStream(object entity, string name, Stream stream, bool closeStream, string contentType) + { + Util.CheckArgumentNullAndEmpty(contentType, "contentType"); + DataServiceRequestArgs args = new DataServiceRequestArgs(); + args.ContentType = contentType; + this.SetSaveStream(entity, name, stream, closeStream, args); + } + + /// Sets a named binary data stream that belongs to the specified entity, with the specified headers in the request message. + /// The entity to which the binary stream belongs. + /// The name of the binary stream to save. + /// The from which to read the binary data. + /// A value that determines whether the data stream is closed when the method is completed. + /// An instance of the class that contains settings for the HTTP request message. + /// Calling this method marks the entity as media link resource (MLE). It also marks the entity as modified + /// so that it will participate in the next call to SaveChanges. + /// The entity is not being tracked. The entity has the MediaEntry attribute + /// marking it to use the older way of handling MRs. + /// Any of the arguments is null. + public void SetSaveStream(object entity, string name, Stream stream, bool closeStream, DataServiceRequestArgs args) + { + Util.CheckArgumentNull(entity, "entity"); + Util.CheckArgumentNullAndEmpty(name, "name"); + Util.CheckArgumentNull(stream, "stream"); + Util.CheckArgumentNull(args, "args"); + this.EnsureMinimumProtocolVersionV3(); + + if (string.IsNullOrEmpty(args.ContentType)) + { + throw Error.Argument(Strings.Context_ContentTypeRequiredForNamedStream, "args"); + } + + EntityDescriptor box = this.entityTracker.GetEntityDescriptor(entity); + Debug.Assert(box.State != EntityStates.Detached, "We should never have a detached entity in the entityDescriptor dictionary."); + if (box.State == EntityStates.Deleted) + { + throw new DataServiceClientException(Strings.Context_SetSaveStreamOnInvalidEntityState(Enum.GetName(typeof(EntityStates), box.State))); + } + + StreamDescriptor namedStream = box.AddStreamInfoIfNotPresent(name); + namedStream.SaveStream = new DataServiceSaveStream(stream, closeStream, args); + namedStream.State = EntityStates.Modified; + this.entityTracker.IncrementChange(namedStream); + } + + #endregion + + #region ExecuteBatch, BeginExecuteBatch, EndExecuteBatch + + /// Asynchronously submits a group of queries as a batch to the data service. + /// An object that is used to track the status of the asynchronous operation. + /// The delegate that is called when a response to the batch request is received. + /// User-defined state object that is used to pass context data to the callback method. + /// The array of query requests to include in the batch request. + public IAsyncResult BeginExecuteBatch(AsyncCallback callback, object state, params DataServiceRequest[] queries) + { + Util.CheckArgumentNotEmpty(queries, "queries"); + BatchSaveResult result = new BatchSaveResult(this, "ExecuteBatch", queries, SaveChangesOptions.BatchWithSingleChangeset, callback, state); + result.BatchBeginRequest(); + return result; + } + + /// Asynchronously submits a group of queries as a batch to the data service. + /// An Task that represents the DataServiceResult object that indicates the result of the batch operation. + /// The array of query requests to include in the batch request. + public Task ExecuteBatchAsync(params DataServiceRequest[] queries) + { + return Task.Factory.FromAsync((callback, state) => this.BeginExecuteBatch(callback, state, queries), this.EndExecuteBatch, null); + } + + /// Called to complete the . + /// The DataServiceResult object that indicates the result of the batch operation. + /// An that represents the status of the asynchronous operation. + public DataServiceResponse EndExecuteBatch(IAsyncResult asyncResult) + { + BatchSaveResult result = BaseAsyncResult.EndExecute(this, "ExecuteBatch", asyncResult); + return result.EndRequest(); + } + +#if !PORTABLELIB // Synchronous methods not available + /// Synchronously submits a group of queries as a batch to the data service. + /// The response to the batch operation. + /// Array of objects that make up the queries. + public DataServiceResponse ExecuteBatch(params DataServiceRequest[] queries) + { + Util.CheckArgumentNotEmpty(queries, "queries"); + + BatchSaveResult result = new BatchSaveResult(this, "ExecuteBatch", queries, SaveChangesOptions.BatchWithSingleChangeset, null, null); + result.BatchRequest(); + return result.EndRequest(); + } +#endif + + #endregion + + #region Execute, BeginExecute, EndExecute + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// An object that is used to track the status of the asynchronous operation. + /// The URI to which the query request will be sent. The URI may be any valid data service URI; it can contain $ query parameters. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + /// The type returned by the query. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IAsyncResult BeginExecute(Uri requestUri, AsyncCallback callback, object state) + { + return this.InnerBeginExecute(requestUri, callback, state, XmlConstants.HttpMethodGet, Util.ExecuteMethodName, null /*singleResult*/); + } + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// A task represents the result of the operation. + /// The URI to which the query request will be sent. The URI may be any valid data service URI; it can contain $ query parameters. + /// The type returned by the query. + public Task> ExecuteAsync(Uri requestUri) + { + return Task>.Factory.FromAsync(this.BeginExecute, this.EndExecute, requestUri, null); + } + + /// Asynchronously sends a request to the data service to execute a specific URI. + /// The result of the operation. + /// The URI to which the query request will be sent. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + /// The HTTP data transfer method used by the client. + /// The operation parameters used. + /// + /// This overload expects the to end with a ServiceOperation + /// or ServiceAction that returns void. + /// + public IAsyncResult BeginExecute(Uri requestUri, AsyncCallback callback, object state, string httpMethod, params OperationParameter[] operationParameters) + { + return this.InnerBeginExecute(requestUri, callback, state, httpMethod, Util.ExecuteMethodNameForVoidResults, false, operationParameters); + } + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// A task represents the result of the operation. + /// The URI to which the query request will be sent. The URI may be any valid data service URI; it can contain $ query parameters. + /// The HTTP data transfer method used by the client. + /// The operation parameters used. + public Task ExecuteAsync(Uri requestUri, string httpMethod, params OperationParameter[] operationParameters) + { + return Task.Factory.FromAsync((callback, state) => this.BeginExecute(requestUri, callback, state, httpMethod, operationParameters), this.EndExecute, null); + } + + /// Asynchronously sends a request to the data service to execute a specific URI. + /// The result of the operation. + /// The URI to which the query request will be sent. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + /// The HTTP data transfer method used by the client. + /// Attribute used on service operations to specify that they return a single instance of their return element. + /// The operation parameters used. + /// The type returned by the query. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IAsyncResult BeginExecute(Uri requestUri, AsyncCallback callback, object state, string httpMethod, bool singleResult, params OperationParameter[] operationParameters) + { + return this.InnerBeginExecute(requestUri, callback, state, httpMethod, Util.ExecuteMethodName, singleResult, operationParameters); + } + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// A task represents the result of the operation. + /// The URI to which the query request will be sent. The URI may be any valid data service URI; it can contain $ query parameters. + /// The HTTP data transfer method used by the client. + /// Attribute used on service operations to specify that they return a single instance of their return element. + /// The operation parameters used. + /// The type returned by the query. + public Task> ExecuteAsync(Uri requestUri, string httpMethod, bool singleResult, params OperationParameter[] operationParameters) + { + return Task>.Factory.FromAsync((callback, state) => this.BeginExecute(requestUri, callback, state, httpMethod, singleResult, operationParameters), this.EndExecute, null); + } + + /// Asynchronously sends a request to the data service to execute a specific URI. + /// The result of the operation. + /// The URI to which the query request will be sent. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + /// The HTTP data transfer method used by the client. + /// The operation parameters used. + /// The type returned by the query. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IAsyncResult BeginExecute(Uri requestUri, AsyncCallback callback, object state, string httpMethod, params OperationParameter[] operationParameters) + { + bool? singleResult = this.IsSingletonType(); + return this.InnerBeginExecute(requestUri, callback, state, httpMethod, Util.ExecuteMethodName, singleResult, operationParameters); + } + + /// Asynchronously sends the request so that this call does not block processing while waiting for the results from the service. + /// A task represents the result of the operation. + /// The URI to which the query request will be sent. The URI may be any valid data service URI; it can contain $ query parameters. + /// The HTTP data transfer method used by the client. + /// The operation parameters used. + /// The type returned by the query. + public Task> ExecuteAsync(Uri requestUri, string httpMethod, params OperationParameter[] operationParameters) + { + return Task>.Factory.FromAsync((callback, state) => this.BeginExecute(requestUri, callback, state, httpMethod, operationParameters), this.EndExecute, null); + } + + /// Asynchronously sends a request to the data service to retrieve the next page of data in a paged query result. + /// An that represents the status of the operation. + /// A object that represents the next page of data to return from the data service. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + /// The type returned by the query. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IAsyncResult BeginExecute(DataServiceQueryContinuation continuation, AsyncCallback callback, object state) + { + Util.CheckArgumentNull(continuation, "continuation"); + QueryComponents qc = continuation.CreateQueryComponents(); + Uri requestUri = qc.Uri; + return (new DataServiceRequest(requestUri, qc, continuation.Plan)).BeginExecute(this, this, callback, state, Util.ExecuteMethodName); + } + + /// Asynchronously sends a request to the data service to retrieve the next page of data in a paged query result. + /// A task that represents the results returned by the query operation. + /// A object that represents the next page of data to return from the data service. + /// The type returned by the query. + public Task> ExecuteAsync(DataServiceQueryContinuation continuation) + { + return Task>.Factory.FromAsync(this.BeginExecute, this.EndExecute, continuation, null); + } + + /// Called to complete the . + /// The results returned by the query operation. + /// + /// object. + /// The type returned by the query. + /// When is null. + /// When did not originate from this instance. -or- When the method was previously called. + /// When an error is raised either during execution of the request or when it converts the contents of the response message into objects. + /// When the data service returns an HTTP 404: Resource Not Found error. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IEnumerable EndExecute(IAsyncResult asyncResult) + { + Util.CheckArgumentNull(asyncResult, "asyncResult"); + return DataServiceRequest.EndExecute(this, this, Util.ExecuteMethodName, asyncResult); + } + + /// Called to complete the . + /// The result of the operation. + /// An that represents the status of the asynchronous operation. + /// This method should be used in combination with the BeginExecute overload which + /// expects the request uri to end with a service operation or service action that returns void. + public OperationResponse EndExecute(IAsyncResult asyncResult) + { + Util.CheckArgumentNull(asyncResult, "asyncResult"); + QueryOperationResponse result = (QueryOperationResponse)DataServiceRequest.EndExecute(this, this, Util.ExecuteMethodNameForVoidResults, asyncResult); + if (result.Any()) + { + throw new DataServiceClientException(Strings.Context_EndExecuteExpectedVoidResponse); + } + + return result; + } + +#if !PORTABLELIB // Synchronous methods not available + /// Sends a request to the data service to execute a specific URI. + /// The results of the query operation. + /// The URI to which the query request will be sent. The URI may be any valid data service URI. Can contain $ query parameters. + /// The type that the query returns. + /// When a response is not received from a request to the . + /// When is null. + /// When is not a valid URI for the data service. + /// When an error is raised either during execution of the request or when it converts the contents of the response message into objects. + /// When the data service returns an HTTP 404: Resource Not Found error. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IEnumerable Execute(Uri requestUri) + { + // We don't support operation parameters with "GET" yet. + + // This public API is for backwards compatability, which is why it always uses GET and sets singleResult to null + return InnerSynchExecute(requestUri, XmlConstants.HttpMethodGet, null); + } + + /// Sends a request to the data service to retrieve the next page of data in a paged query result. + /// The response that contains the next page of data in the query result. + /// A object that represents the next page of data to return from the data service. + /// The type returned by the query. + public QueryOperationResponse Execute(DataServiceQueryContinuation continuation) + { + Util.CheckArgumentNull(continuation, "continuation"); + QueryComponents qc = continuation.CreateQueryComponents(); + Uri requestUri = qc.Uri; + DataServiceRequest request = new DataServiceRequest(requestUri, qc, continuation.Plan); + return request.Execute(this, qc); + } + + /// Sends a request to the data service to execute a specific URI by using a specific HTTP method. + /// The response of the operation. + /// The URI to which the query request will be sent. The URI may be any valid data service URI. Can contain $ query parameters. + /// The HTTP data transfer method used by the client. + /// The operation parameters used. + /// + /// This overload expects the to end with a ServiceOperation + /// or ServiceAction that returns void. + /// + /// null requestUri + /// The is not GET, POST or DELETE. + /// problem materializing results of query into objects + /// failure to get response for requestUri + public OperationResponse Execute(Uri requestUri, string httpMethod, params OperationParameter[] operationParameters) + { + QueryOperationResponse result = (QueryOperationResponse)Execute(requestUri, httpMethod, false, operationParameters); + if (result.Any()) + { + throw new DataServiceClientException(Strings.Context_ExecuteExpectedVoidResponse); + } + + return result; + } + + /// Sends a request to the data service to execute a specific URI by using a specific HTTP method. + /// Returns . + /// The URI to which the query request will be sent. The URI may be any valid data service URI. Can contain $ query parameters. + /// The HTTP data transfer method used by the client. + /// Attribute used on service operations to specify that they return a single instance of their return element. + /// The operation parameters used. + /// The type returned by the query. + /// null requestUri + /// The is not GET nor POST. + /// problem materializing results of query into objects + /// failure to get response for requestUri + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "Just for CTP")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IEnumerable Execute(Uri requestUri, string httpMethod, bool singleResult, params OperationParameter[] operationParameters) + { + return InnerSynchExecute(requestUri, httpMethod, singleResult, operationParameters); + } + + /// Sends a request to the data service to execute a specific URI by using a specific HTTP method. + /// Returns . + /// The URI to which the query request will be sent. The URI may be any valid data service URI. Can contain $ query parameters. + /// The HTTP data transfer method used by the client. + /// The operation parameters used. + /// The type returned by the query. + /// null requestUri + /// The is not GET nor POST. + /// problem materializing results of query into objects + /// failure to get response for requestUri + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "Just for CTP")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + public IEnumerable Execute(Uri requestUri, string httpMethod, params OperationParameter[] operationParameters) + { + bool? singleResult = this.IsSingletonType(); + return InnerSynchExecute(requestUri, httpMethod, singleResult, operationParameters); + } + +#endif + #endregion + + #region SaveChanges, BeginSaveChanges, EndSaveChanges + + /// Asynchronously submits the pending changes to the data service collected by the since the last time changes were saved. + /// An IAsyncResult that represents the status of the asynchronous operation. + /// The delegate to call when the operation is completed. + /// The user-defined state object that is used to pass context data to the callback method. + public IAsyncResult BeginSaveChanges(AsyncCallback callback, object state) + { + return this.BeginSaveChanges(this.SaveChangesDefaultOptions, callback, state); + } + + /// Asynchronously submits the pending changes to the data service collected by the since the last time changes were saved. + /// A task that represents a object that indicates the result of the batch operation. + public Task SaveChangesAsync() + { + return SaveChangesAsync(this.SaveChangesDefaultOptions); + } + + /// Asynchronously submits the pending changes to the data service collected by the since the last time changes were saved. + /// A task that represents a object that indicates the result of the batch operation. + /// A member of the enumeration for how the client can save the pending set of changes. + /// The delegate to call when the operation is completed. + /// The user-defined state object that is used to pass context data to the callback method. + /// + /// BeginSaveChanges will asynchronously attach identity Uri returned by server to sucessfully added entites. + /// EndSaveChanges will apply updated values to entities, raise ReadingEntity events and change entity states. + /// + public IAsyncResult BeginSaveChanges(SaveChangesOptions options, AsyncCallback callback, object state) + { + this.ValidateSaveChangesOptions(options); + BaseSaveResult result = BaseSaveResult.CreateSaveResult(this, Util.SaveChangesMethodName, null, options, callback, state); + if (result.IsBatchRequest) + { + ((BatchSaveResult)result).BatchBeginRequest(); + } + else + { + ((SaveResult)result).BeginCreateNextChange(); // may invoke callback before returning + } + + return result; + } + + /// Asynchronously submits the pending changes to the data service collected by the since the last time changes were saved. + /// A task that represents a object that indicates the result of the batch operation. + /// A member of the enumeration for how the client can save the pending set of changes. + public Task SaveChangesAsync(SaveChangesOptions options) + { + return Task.Factory.FromAsync(this.BeginSaveChanges, this.EndSaveChanges, options, null); + } + + /// Called to complete the operation. + /// A object that indicates the result of the batch operation. + /// An that represents the status of the asynchronous operation. + public DataServiceResponse EndSaveChanges(IAsyncResult asyncResult) + { + BaseSaveResult result = BaseAsyncResult.EndExecute(this, Util.SaveChangesMethodName, asyncResult); + + DataServiceResponse errors = result.EndRequest(); + + if (this.ChangesSaved != null) + { + this.ChangesSaved(this, new SaveChangesEventArgs(errors)); + } + + return errors; + } + +#if !PORTABLELIB // Synchronous methods not available + /// Saves the changes that the is tracking to storage. + /// A that contains status, headers, and errors that result from the call to . + public DataServiceResponse SaveChanges() + { + return this.SaveChanges(this.SaveChangesDefaultOptions); + } + + /// Saves the changes that the is tracking to storage. + /// A that contains status, headers, and errors that result from the call to . + /// A member of the enumeration for how the client can save the pending set of changes. + public DataServiceResponse SaveChanges(SaveChangesOptions options) + { + DataServiceResponse errors = null; + this.ValidateSaveChangesOptions(options); + + BaseSaveResult result = BaseSaveResult.CreateSaveResult(this, Util.SaveChangesMethodName, null, options, null, null); + if (result.IsBatchRequest) + { + ((BatchSaveResult)result).BatchRequest(); + } + else + { + ((SaveResult)result).CreateNextChange(); + } + + errors = result.EndRequest(); + + Debug.Assert(null != errors, "null errors"); + + if (this.ChangesSaved != null) + { + this.ChangesSaved(this, new SaveChangesEventArgs(errors)); + } + + return errors; + } +#endif + #endregion + + #region Add, Attach, Delete, Detach, Update, TryGetEntity, TryGetUri + + /// Adds the specified link to the set of objects the is tracking. + /// The source object for the new link. + /// The name of the navigation property on the source object that returns the related object. + /// The object related to the source object by the new link. + /// When , , or are null. + /// If a link already exists.-or-If either the or objects are in a or state.-or-If is not a collection. + /// + /// Notifies the context that a new link exists between the and objects + /// and that the link is represented via the source. which is a collection. + /// The context adds this link to the set of newly created links to be sent to + /// the data service on the next call to SaveChanges(). + /// Links are one way relationships. If a back pointer exists (ie. two way association), + /// this method should be called a second time to notify the context object of the second link. + /// + public void AddLink(object source, string sourceProperty, object target) + { + this.EnsureRelatable(source, sourceProperty, target, EntityStates.Added); + + LinkDescriptor descriptor = new LinkDescriptor(source, sourceProperty, target, this.model); + this.entityTracker.AddLink(descriptor); + descriptor.State = EntityStates.Added; + this.entityTracker.IncrementChange(descriptor); + } + + /// Notifies the to start tracking the specified link that defines a relationship between entity objects. + /// The source object in the new link. + /// The name of the property on the source object that represents the link between the source and target object. + /// The target object in the link that is bound to the source object specified in this call. The target object must be of the type identified by the source property or a subtype. + /// When , , or is null. + /// When the link between the two entities already exists.-or-When or is in an or state. + public void AttachLink(object source, string sourceProperty, object target) + { + this.AttachLink(source, sourceProperty, target, MergeOption.NoTracking); + } + + /// Removes the specified link from the list of links being tracked by the . + /// Returns true if the specified entity was detached; otherwise false. + /// The source object participating in the link to be marked for deletion. + /// The name of the property on the source object that represents the source in the link between the source and the target. + /// The target object involved in the link that is bound to the source object. The target object must be of the type identified by the source property or a subtype. + /// When or are null. + /// When is an empty string. + /// Any link being tracked by the context, regardless of its current state, can be detached. + public bool DetachLink(object source, string sourceProperty, object target) + { + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNullAndEmpty(sourceProperty, "sourceProperty"); + + LinkDescriptor existing = this.entityTracker.TryGetLinkDescriptor(source, sourceProperty, target); + if (existing == null) + { + return false; + } + + this.entityTracker.DetachExistingLink(existing, false); + return true; + } + + /// Changes the state of the link to deleted in the list of links being tracked by the . + /// The source object in the link to be marked for deletion. + /// The name of the navigation property on the source object that is used to access the target object. + /// The target object involved in the link that is bound to the source object. The target object must be of the type identified by the source property or a subtype. + /// When , , or is null. + /// When or is in a or state.-or-When is not a collection. + /// + /// Notifies the context that a link exists between the and object + /// and that the link is represented via the source. which is a collection. + /// The context adds this link to the set of deleted links to be sent to + /// the data service on the next call to SaveChanges(). + /// If the specified link exists in the "Added" state, then the link is detached (see DetachLink method) instead. + /// + public void DeleteLink(object source, string sourceProperty, object target) + { + bool delay = this.EnsureRelatable(source, sourceProperty, target, EntityStates.Deleted); + + LinkDescriptor existing = this.entityTracker.TryGetLinkDescriptor(source, sourceProperty, target); + if (existing != null && (EntityStates.Added == existing.State)) + { // Added -> Detached + this.entityTracker.DetachExistingLink(existing, false); + } + else + { + if (delay) + { // can't have non-added relationship when source or target is in added state + throw Error.InvalidOperation(Strings.Context_NoRelationWithInsertEnd); + } + + if (null == existing) + { // detached -> deleted + LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target, this.model); + this.entityTracker.AddLink(relation); + existing = relation; + } + + if (EntityStates.Deleted != existing.State) + { + existing.State = EntityStates.Deleted; + + // It is the users responsibility to delete the link + // before deleting the entity when required. + this.entityTracker.IncrementChange(existing); + } + } + } + + /// Notifies the that a new link exists between the objects specified and that the link is represented by the property specified by the parameter. + /// The source object for the new link. + /// The property on the source object that identifies the target object of the new link. + /// The child object involved in the new link that is to be initialized by calling this method. The target object must be a subtype of the type identified by the parameter. If is set to null, the call represents a delete link operation. + /// When , or are null. + /// When the specified link already exists.-or-When the objects supplied as or are in the or state.-or-When is not a navigation property that defines a reference to a single related object. + /// + /// Notifies the context that a modified link exists between the and objects + /// and that the link is represented via the source. which is a reference. + /// The context adds this link to the set of modified created links to be sent to + /// the data service on the next call to SaveChanges(). + /// Links are one way relationships. If a back pointer exists (ie. two way association), + /// this method should be called a second time to notify the context object of the second link. + /// + public void SetLink(object source, string sourceProperty, object target) + { + this.EnsureRelatable(source, sourceProperty, target, EntityStates.Modified); + + LinkDescriptor relation = this.entityTracker.DetachReferenceLink(source, sourceProperty, target, MergeOption.NoTracking); + if (null == relation) + { + relation = new LinkDescriptor(source, sourceProperty, target, this.model); + this.entityTracker.AddLink(relation); + } + + Debug.Assert( + 0 == relation.State || + Util.IncludeLinkState(relation.State), + "set link entity state"); + + if (EntityStates.Modified != relation.State) + { + relation.State = EntityStates.Modified; + this.entityTracker.IncrementChange(relation); + } + } + + #endregion + + #region AddObject, AttachTo, DeleteObject, Detach, TryGetEntity, TryGetUri + /// Adds the specified object to the set of objects that the is tracking. + /// The name of the entity set to which the resource will be added. + /// The object to be tracked by the . + /// When or is null. + /// When is empty.-or-When does not have a key property defined. + /// When the entity is already being tracked by the context. + /// + /// It does not follow the object graph and add related objects. + /// Any leading or trailing forward slashes will automatically be trimmed from entitySetName. + /// + public void AddObject(string entitySetName, object entity) + { + ValidateEntitySetName(ref entitySetName); + ValidateEntityType(entity, this.Model); + + var resource = new EntityDescriptor(this.model) + { + Entity = entity, + State = EntityStates.Added, + EntitySetName = entitySetName, + }; + + resource.SetEntitySetUriForInsert(this.BaseUriResolver.GetEntitySetUri(entitySetName)); + + this.EntityTracker.AddEntityDescriptor(resource); + this.EntityTracker.IncrementChange(resource); + } + + /// Adds a related object to the context and creates the link that defines the relationship between the two objects in a single request. + /// The parent object that is being tracked by the context. + /// The name of the navigation property that returns the related object based on an association between the two entities. + /// The related object that is being added. + public void AddRelatedObject(object source, string sourceProperty, object target) + { + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNullAndEmpty(sourceProperty, "sourceProperty"); + Util.CheckArgumentNull(target, "target"); + + // Validate that the source is an entity and is already tracked by the context. + ValidateEntityType(source, this.Model); + + EntityDescriptor sourceResource = this.entityTracker.GetEntityDescriptor(source); + + // Check for deleted source entity + if (sourceResource.State == EntityStates.Deleted) + { + throw Error.InvalidOperation(Strings.Context_AddRelatedObjectSourceDeleted); + } + + // Validate that the property is valid and exists on the source + ClientTypeAnnotation parentType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(source.GetType())); + ClientPropertyAnnotation property = parentType.GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException); + if (property.IsKnownType || !property.IsEntityCollection) + { + throw Error.InvalidOperation(Strings.Context_AddRelatedObjectCollectionOnly); + } + + // Validate that the target is an entity + ClientTypeAnnotation childType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(target.GetType())); + ValidateEntityType(target, this.Model); + + // Validate that the property type matches with the target type + ClientTypeAnnotation propertyElementType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(property.EntityCollectionItemType)); + if (!propertyElementType.ElementType.IsAssignableFrom(childType.ElementType)) + { + // target is not of the correct type + throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target"); + } + + var targetResource = new EntityDescriptor(this.model) + { + Entity = target, + State = EntityStates.Added + }; + + targetResource.SetParentForInsert(sourceResource, sourceProperty); + + this.EntityTracker.AddEntityDescriptor(targetResource); + + // Add the link in the added state. + LinkDescriptor end = targetResource.GetRelatedEnd(); + end.State = EntityStates.Added; + this.entityTracker.AddLink(end); + + this.entityTracker.IncrementChange(targetResource); + } + + /// Notifies the to start tracking the specified resource and supplies the location of the resource within the specified resource set. + /// The name of the set that contains the resource. + /// The resource to be tracked by the . The resource is attached in the Unchanged state. + /// When or is null. + /// When is an empty string.-or-When the does not have a key property defined. + /// When the is already being tracked by the context. + /// It does not follow the object graph and attach related objects. + public void AttachTo(string entitySetName, object entity) + { + this.AttachTo(entitySetName, entity, null); + } + + /// Notifies the to start tracking the specified resource and supplies the location of the resource in the specified resource set. + /// The string value that contains the name of the entity set to which to the entity is attached. + /// The entity to add. + /// An etag value that represents the state of the entity the last time it was retrieved from the data service. This value is treated as an opaque string; no validation is performed on it by the client library. + /// When is null.-or-When is null. + /// When is an empty string.-or-When the supplied object does not have a key property. + /// When the supplied object is already being tracked by the context + /// It does not follow the object graph and attach related objects. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", MessageId = "etag", Justification = "represents ETag in request")] + public void AttachTo(string entitySetName, object entity, string etag) + { + ValidateEntitySetName(ref entitySetName); + Util.CheckArgumentNull(entity, "entity"); + + var descriptor = new EntityDescriptor(this.model) + { + Entity = entity, + ETag = etag, + State = EntityStates.Unchanged, + EntitySetName = entitySetName, + }; + + ODataResourceMetadataBuilder entityMetadataBuilder = this.GetEntityMetadataBuilderInternal(descriptor); + + descriptor.EditLink = entityMetadataBuilder.GetEditLink(); + descriptor.Identity = entityMetadataBuilder.GetId(); + + this.entityTracker.InternalAttachEntityDescriptor(descriptor, true /*failIfDuplicated*/); + } + + /// Changes the state of the specified object to be deleted in the . + /// The tracked entity to be changed to the deleted state. + /// When is null. + /// When the object is not being tracked by the . + /// + /// Existings objects in the Added state become detached. + /// + public void DeleteObject(object entity) + { + this.DeleteObjectInternal(entity, false /*failIfInAddedState*/); + } + + /// Removes the entity from the list of entities that the is tracking. + /// Returns true if the specified entity was detached; otherwise false. + /// The tracked entity to be detached from the . + /// When is null. + public bool Detach(object entity) + { + Util.CheckArgumentNull(entity, "entity"); + + EntityDescriptor resource = this.entityTracker.TryGetEntityDescriptor(entity); + if (resource != null) + { + return this.entityTracker.DetachResource(resource); + } + + return false; + } + + /// Changes the state of the specified object in the to . + /// The tracked entity to be assigned to the state. + /// When is null. + /// When is in the state. + public void UpdateObject(object entity) + { + this.UpdateObjectInternal(entity, false /*failIfNotUnchanged*/); + } + + /// Update a related object to the context. + /// The parent object that is being tracked by the context. + /// The name of the navigation property that returns the related object based on an association between the two entities. + /// The related object that is being updated. + public void UpdateRelatedObject(object source, string sourceProperty, object target) + { + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNullAndEmpty(sourceProperty, "sourceProperty"); + Util.CheckArgumentNull(target, "target"); + + // Validate that the source is an entity and is already tracked by the context. + ValidateEntityType(source, this.Model); + + EntityDescriptor sourceResource = this.entityTracker.GetEntityDescriptor(source); + + // Check for deleted source entity + if (sourceResource.State == EntityStates.Deleted) + { + throw Error.InvalidOperation(Strings.Context_AddRelatedObjectSourceDeleted); + } + + // Validate that the property is valid and exists on the source + ClientTypeAnnotation parentType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(source.GetType())); + ClientPropertyAnnotation property = parentType.GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException); + + if (property.IsKnownType || property.IsEntityCollection) + { + throw Error.InvalidOperation(Strings.Context_UpdateRelatedObjectNonCollectionOnly); + } + + // Validate that the target is an entity + ClientTypeAnnotation childType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(target.GetType())); + ValidateEntityType(target, this.Model); + + ClientTypeAnnotation propertyElementType = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(property.PropertyType)); + if (!propertyElementType.ElementType.IsAssignableFrom(childType.ElementType)) + { + // target is not of the correct type + throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target"); + } + + EntityDescriptor targetResource = this.entityTracker.TryGetEntityDescriptor(target); + if (targetResource != null) + { + this.UpdateObject(target); + } + else + { + targetResource = new EntityDescriptor(this.model) + { + Entity = target, + State = EntityStates.Modified, + EditLink = sourceResource.GetNestedResourceInfo(this.baseUriResolver, property) + }; + + targetResource.SetParentForUpdate(sourceResource, sourceProperty); + this.EntityTracker.AddEntityDescriptor(targetResource); + this.entityTracker.IncrementChange(targetResource); + } + } + + /// + /// Changes the state of the given entity. + /// Note that the 'Added' state is not supported by this method, and that AddObject or AddRelatedObject should be used instead. + /// If the state 'Modified' is given, calling this method is exactly equivalent to calling UpdateObject. + /// If the state 'Deleted' is given, calling this method is exactly equivalent to calling DeleteObject. + /// If the state 'Detached' is given, calling this method is exactly equivalent to calling Detach. + /// If the state 'Unchanged' is given, the state will be changed, but no other modifications will be made to the entity or entity descriptor associated with it. + /// + /// The entity whose state to change. + /// The new state of the entity. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ChangeState", Justification = "Method name, will be removed when string is added to resources.")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AddObject", Justification = "Method name, will be removed when string is added to resources.")] + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "AddRelatedObject", Justification = "Method name, will be removed when string is added to resources.")] + public void ChangeState(object entity, EntityStates state) + { + switch (state) + { + case EntityStates.Added: + throw Error.NotSupported(ClientStrings.Context_CannotChangeStateToAdded); + + case EntityStates.Modified: + this.UpdateObjectInternal(entity, true /*failIfNotUnchanged*/); + break; + + case EntityStates.Deleted: + this.DeleteObjectInternal(entity, true /*failIfInAddedState*/); + break; + + case EntityStates.Detached: + this.Detach(entity); + break; + + case EntityStates.Unchanged: + this.SetStateToUnchanged(entity); + break; + + default: + throw Error.InternalError(InternalError.UnvalidatedEntityState); + } + +#if DEBUG + if (state != EntityStates.Detached) + { + var descriptor = this.entityTracker.TryGetEntityDescriptor(entity); + Debug.Assert(descriptor != null, "Should have found entity descriptor."); + Debug.Assert(descriptor.State == state, "ChangeState should have changed state to " + state + " not " + descriptor.State); + } + else + { + Debug.Assert(this.entityTracker.TryGetEntityDescriptor(entity) == null, "Entity should have been detached."); + } +#endif + } + + /// Test retrieval of an entity being tracked by the by reference to the URI of the entity. + /// If an entity is found at , the entity is returned in the out parameter and true is returned. If no entity is found, false is returned. + /// The URI of the tracked entity to be retrieved. + /// The entity to be retrieved. + /// The type of the entity. + /// When is null. + /// entities in added state are not likely to have a identity + public bool TryGetEntity(Uri identity, out TEntity entity) where TEntity : class + { + entity = null; + Util.CheckArgumentNull(identity, "relativeUri"); + + EntityStates state; + + // ReferenceIdentity is a test hook to help verify we dont' use identity instead of editLink + entity = (TEntity)this.EntityTracker.TryGetEntity(identity, out state); + return (null != entity); + } + + /// Retrieves the canonical URI associated with the specified entity, if available. + /// Returns true if the canonical URI is returned in the out parameter. If the specified entity is not tracked by the or is in the added state, no URI is available and false is returned. + /// The entity identified by the . + /// The URI of the entity. + /// When is null. + /// Entities in added state are not likely to have an identity. Though the identity might use a dereferencable scheme, you MUST NOT assume it can be dereferenced. + public bool TryGetUri(object entity, out Uri identity) + { + identity = null; + Util.CheckArgumentNull(entity, "entity"); + + // if the entity's identity does not map back to the entity, don't return it + EntityDescriptor resource = this.entityTracker.TryGetEntityDescriptor(entity); + if (resource != null && + (null != resource.Identity) && + Object.ReferenceEquals(resource, this.entityTracker.TryGetEntityDescriptor(resource.Identity))) + { + // DereferenceIdentity is a test hook to help verify we dont' use identity instead of editLink + identity = resource.Identity; + } + + return (null != identity); + } + + #endregion + + /// + /// Get the bound or according to the client MethodInfo. + /// + /// The specified MethodInfo + /// return a bound or an if it is found, or return null. + internal virtual IEdmVocabularyAnnotatable GetEdmOperationOrOperationImport(MethodInfo methodInfo) + { + var declaringType = methodInfo.DeclaringType; + if (declaringType.IsSubclassOf(typeof(DataServiceContext))) + { + return AnnotationHelper.GetEdmOperationImport(this, methodInfo); + } + + return AnnotationHelper.GetEdmOperation(this, methodInfo); + } + + /// + /// Asynchronously loads all pages of related entities for a specified property from the data service. + /// + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// An instance of that contains the results of the last page request. + internal Task LoadPropertyAllPagesAsync(object entity, string propertyName) + { + var currentTask = Task.Factory.FromAsync(this.BeginLoadProperty, this.EndLoadProperty, entity, propertyName, null); + var nextTask = currentTask.ContinueWith(t => this.ContinuePage(t.Result, entity, propertyName)); + return nextTask; + } + +#if !PORTABLELIB + + /// + /// Loads all pages of related entities for a specified property from the data service. + /// + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// An instance of that contains the results of the last page request. + internal QueryOperationResponse LoadPropertyAllPages(object entity, string propertyName) + { + DataServiceQueryContinuation continuation = null; + QueryOperationResponse response; + do + { + if (continuation == null) + { + response = this.LoadProperty(entity, propertyName, (Uri)null); + } + else + { + response = this.LoadProperty(entity, propertyName, continuation); + } + + continuation = response.GetContinuation(); + } + while (continuation != null); + + return response; + } + + /// + /// Execute the using . + /// + /// Element type of the result. + /// Request URI to execute. + /// HttpMethod to use. Only GET or POST are supported. + /// If set to true, indicates that a single result is expected as a response. + /// False indicates that a collection of TElement is assumed. Should be null for void, entry, and feed cases. + /// This function will check if TElement is an entity type and set singleResult to null in this case. + /// The operation parameters associated with the service operation. + /// A QueryOperationResponse that is enumerable over the results and holds other response information. + /// null requestUri + /// The is not GET nor POST. + /// problem materializing results of query into objects + /// failure to get response for requestUri + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")] + internal QueryOperationResponse InnerSynchExecute(Uri requestUri, string httpMethod, bool? singleResult, params OperationParameter[] operationParameters) + { + List uriOperationParameters = null; + List bodyOperationParameters = null; + this.ValidateExecuteParameters(ref requestUri, httpMethod, ref singleResult, out bodyOperationParameters, out uriOperationParameters, operationParameters); + QueryComponents qc = new QueryComponents( + requestUri, + Util.ODataVersionEmpty, + typeof(TElement), + null /* projection */, + null /* normalizer rewrites */, + httpMethod, + singleResult, + bodyOperationParameters, + uriOperationParameters); + + requestUri = qc.Uri; + DataServiceRequest request = new DataServiceRequest(requestUri, qc, null); + return request.Execute(this, qc); + } +#endif + + /// Begins the execution of the request uri based on the http method. + /// element type of the result + /// request to execute + /// User callback when results from execution are available. + /// user state in IAsyncResult + /// HttpMethod to use. Only GET and POST are supported. + /// async method name at the source. + /// If set to true, indicates that a single result is expected as a response. + /// The operation parameters associated with the service operation. + /// async result object + internal IAsyncResult InnerBeginExecute(Uri requestUri, AsyncCallback callback, object state, string httpMethod, string method, bool? singleResult, params OperationParameter[] operationParameters) + { + List uriOperationParameters = null; + List bodyOperationParameters = null; + this.ValidateExecuteParameters(ref requestUri, httpMethod, ref singleResult, out bodyOperationParameters, out uriOperationParameters, operationParameters); + QueryComponents qc = new QueryComponents(requestUri, Util.ODataVersionEmpty, typeof(TElement), null, null, httpMethod, singleResult, bodyOperationParameters, uriOperationParameters); + requestUri = qc.Uri; + return (new DataServiceRequest(requestUri, qc, null)).BeginExecute(this, this, callback, state, method); + } + + /// + /// Track a binding. + /// + /// Source resource. + /// Property on the source resource that relates to the target resource. + /// Target resource. + /// merge operation + internal void AttachLink(object source, string sourceProperty, object target, MergeOption linkMerge) + { + this.EnsureRelatable(source, sourceProperty, target, EntityStates.Unchanged); + + // TODO: attach link should never result in any changes ?? why did we ever incrementchange number on attach link + this.entityTracker.AttachLink(source, sourceProperty, target, linkMerge); + } + + #region HttpWebRequest Creation Methods + + /// + /// Creates the OData request message to write the headers and payload into. + /// + /// The arguments for creating the message. + /// Descriptor to expose in SendingRequest2 + /// An instance of IODataRequestMessage with the given headers and version. + internal ODataRequestMessageWrapper CreateODataRequestMessage( + BuildingRequestEventArgs requestMessageArgs, + Descriptor descriptor) + { + ODataRequestMessageWrapper requestMessage = new RequestInfo(this).WriteHelper.CreateRequestMessage(requestMessageArgs); + requestMessage.FireSendingRequest2(descriptor); + return requestMessage; + } + + #endregion + + /// + /// user hook to resolve name into a type + /// + /// name to resolve + /// Null if no type resolver is registered, otherwise returns whatever is returned by the type resolver. + internal Type ResolveTypeFromName(string wireName) + { + Func resolve = this.ResolveType; + if (null != resolve) + { + // if the ResolveType property is set, call the provided type resultion method + return resolve(wireName); + } + + return null; + } + + /// + /// The reverse of ResolveType, use for complex types and LINQ query expression building + /// + /// client type + /// type for the server + internal string ResolveNameFromTypeInternal(Type type) + { + Debug.Assert(null != type, "null type"); + Func resolve = this.ResolveName; + return ((null != resolve) ? resolve(type) : (String)null); + } + + /// + /// Fires the SendingRequest event. + /// + /// SendingRequestEventArgs instance containing all information about the request. + internal void FireSendingRequest(SendingRequestEventArgs eventArgs) + { + Debug.Assert(this.InnerSendingRequest != null, "this.InnerSendingRequest != null"); + Debug.Assert(this.SendingRequest2 == null, "this.SendingRequest2 == null"); + this.InnerSendingRequest(this, eventArgs); + } + + /// + /// Fires the SendingRequest2 event. + /// + /// SendingRequest2EventArgs instance containing all information about the request. + internal void FireSendingRequest2(SendingRequest2EventArgs eventArgs) + { + Debug.Assert(this.SendingRequest2 != null, "this.SendingRequest2 != null"); + this.SendingRequest2(this, eventArgs); + } + + /// + /// Fires the ReceivingResponse event. + /// + /// Args instance containing information about the response. + internal void FireReceivingResponseEvent(ReceivingResponseEventArgs receivingResponseEventArgs) + { + if (this.ReceivingResponse != null) + { + this.ReceivingResponse(this, receivingResponseEventArgs); + } + } + + #region GetResponse + + /// + /// This method wraps the HttpWebRequest.GetSyncronousResponse method call. The reasons for doing this are to give us a place + /// to invoke internal test hook callbacks that can validate the response headers, and also so that we can do + /// debug validation to make sure that the headers have not changed since they were originally configured on the request. + /// + /// HttpWebRequest instance + /// If set to true, this method will only re-throw the WebException that was caught if + /// the response in the exception is null. If set to false, this method will always re-throw in case of a WebException. + /// + /// Returns the HttpWebResponse from the wrapped GetSyncronousResponse method. + /// + internal IODataResponseMessage GetSyncronousResponse(ODataRequestMessageWrapper request, bool handleWebException) + { + return this.GetResponseHelper(request, null, handleWebException); + } + + /// + /// This method wraps the HttpWebRequest.EndGetResponse method call. The reason for doing this is to give us a place + /// to invoke internal test hook callbacks that can validate the response headers. + /// + /// HttpWebRequest instance + /// Async result obtained from previous call to BeginGetResponse. + /// Returns the HttpWebResponse from the wrapped EndGetResponse method. + internal IODataResponseMessage EndGetResponse(ODataRequestMessageWrapper request, IAsyncResult asyncResult) + { + Debug.Assert(asyncResult != null, "Expected a non-null asyncResult for all scenarios calling EndGetResponse"); + return this.GetResponseHelper(request, asyncResult, true); + } + + #endregion + + #region Test hooks for header and payload verification + + /// + /// Invokes the sendRequest test hook callback with a reference to the HttpWebRequest + /// + /// HttpWebRequest to provide in the callback. + internal void InternalSendRequest(HttpWebRequest request) + { + if (this.sendRequest != null) + { + this.sendRequest(request); + } + } + + /// + /// Invokes the getRequestWrappingStream test hook callback, so that the test code can wrap the stream and see what gets written to it. + /// + /// Underlying HTTP stream to be wrapped + /// + /// If the test hook is being used, returns the stream provided by the callback, otherwise returns the original stream. + /// + internal Stream InternalGetRequestWrappingStream(Stream requestStream) + { + return this.getRequestWrappingStream != null ? this.getRequestWrappingStream(requestStream) : requestStream; + } + + /// + /// Invokes the sendResponse test hook callback with a reference to the HttpWebResponse + /// + /// HttpWebResponse to provide in the callback. + internal void InternalSendResponse(HttpWebResponse response) + { + if (this.sendResponse != null) + { + this.sendResponse(response); + } + } + + /// + /// Invokes the getResponseWrappingStream test hook callback, so that the test code can wrap the stream and see what gets read from it. + /// + /// Underlying HTTP stream to be wrapped + /// + /// If the test hook is being used, returns the stream provided by the callback, otherwise returns the original stream. + /// + internal Stream InternalGetResponseWrappingStream(Stream responseStream) + { + return this.getResponseWrappingStream != null ? this.getResponseWrappingStream(responseStream) : responseStream; + } + + #endregion + + /// + /// Gets an entity metadata builder to evaluate metadata which is not present in payloads, or for which the payload is not available. + /// + /// Name of the entity set to which the entity belongs. + /// The entity to build metadata for. + /// + /// A metadata builder for the entity tracked by the given entity instance. + /// + /// + /// This is used for example to determine the edit link for an entity if the payload didn't have one, or to determine the URL for a navigation when building a query through LINQ. + /// + internal virtual ODataResourceMetadataBuilder GetEntityMetadataBuilder(string entitySetName, IEdmStructuredValue entityInstance) + { + return new ConventionalODataEntityMetadataBuilder(this.baseUriResolver, entitySetName, entityInstance, this.UrlKeyDelimiter); + } + + /// + /// Fires the BuildingRequest event to get a new RequestMessageArgs object. + /// + /// Http method for the request. + /// Base Uri for the request. + /// Http stack to use for the request. + /// Http stack to use for the request. + /// Descriptor for the request, if there is one. + /// A new RequestMessageArgs object for building the request message. + internal BuildingRequestEventArgs CreateRequestArgsAndFireBuildingRequest(string method, Uri requestUri, HeaderCollection headers, HttpStack stack, Descriptor descriptor) + { + BuildingRequestEventArgs requestMessageArgs = new BuildingRequestEventArgs(method, requestUri, headers, descriptor, stack); + + // Set default headers before firing BudingRequest event + requestMessageArgs.HeaderCollection.SetDefaultHeaders(); + + return this.FireBuildingRequest(requestMessageArgs); + } + + /// + /// Determines the type that + /// + /// Name of the type to resolve. + /// Namespace of the type. + /// Namespace of the type, can be different in VB than the fullNamespace. + /// Type that the name resolved to or null if none found. + /// Function was added for Portable Lib support to handle the differences in accessing the assembly of the context. + protected internal Type DefaultResolveType(string typeName, string fullNamespace, string languageDependentNamespace) + { + if (typeName != null && typeName.StartsWith(fullNamespace, StringComparison.Ordinal)) + { + int namespaceLength = fullNamespace != null ? fullNamespace.Length : 0; + Type type = this.GetType().GetAssembly().GetType(string.Concat(languageDependentNamespace, typeName.Substring(namespaceLength)), false); + if (type == null) + { + return this.GetType().GetAssembly().GetTypes().ToList().Where(t => + { + string serverDefinedName = typeName.Substring(namespaceLength + 1); + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)t.GetCustomAttributes(typeof(OriginalNameAttribute), true).SingleOrDefault(); + return originalNameAttribute != null && originalNameAttribute.OriginalName == serverDefinedName && t.Namespace == languageDependentNamespace; + }).SingleOrDefault(); + } + else + { + return type; + } + } + + return null; + } + + /// + /// Determines if a type is a singleton type + /// + /// The type to be determined + /// True for primitive or complex types, + /// false for collection types of primitive and complex, + /// and null for anything else (like entity, feed, etc.) + private bool? IsSingletonType() + { + var clrType = typeof(TElement); + var edmType = this.Model.GetOrCreateEdmType(clrType).ToEdmTypeReference(ClientTypeUtil.CanAssignNull(clrType)); + + if (edmType.IsPrimitive() || edmType.IsComplex()) + { + return true; + } + + if (edmType.IsCollection()) + { + var elemType = edmType.AsCollection().ElementType(); + if (elemType.IsPrimitive() || elemType.IsComplex()) + { + return false; + } + } + + return null; + } + + /// + /// Continue to asynchronously loads the next page of related entities for a specified property from the data service. + /// + /// The response of previous page + /// The entity that contains the property to load. + /// The name of the property of the specified entity to load. + /// An instance of that contains the results of the request. + private QueryOperationResponse ContinuePage(QueryOperationResponse response, object entity, string propertyName) + { + var continuation = response.GetContinuation(); + if (continuation != null) + { + var currentTask = Task.Factory.FromAsync(this.BeginLoadProperty(entity, propertyName, continuation, null, null), this.EndLoadProperty); + var nextTask = currentTask.ContinueWith(t => this.ContinuePage(t.Result, entity, propertyName)); + Task.WaitAll(new Task[] { currentTask, nextTask }); + return nextTask.Result; + } + + return response; + } + + /// validate and trim leading and trailing forward slashes + /// + /// resource name to validate + /// if entitySetName was null + /// if entitySetName was empty or contained only forward slash + private static void ValidateEntitySetName(ref string entitySetName) + { + Util.CheckArgumentNullAndEmpty(entitySetName, "entitySetName"); + entitySetName = entitySetName.Trim(UriUtil.ForwardSlash); + + Util.CheckArgumentNullAndEmpty(entitySetName, "entitySetName"); + + Uri tmp = UriUtil.CreateUri(entitySetName, UriKind.RelativeOrAbsolute); + if (tmp.IsAbsoluteUri || + !String.IsNullOrEmpty(UriUtil.CreateUri(new Uri("http://ConstBaseUri/ConstService.svc/"), tmp) + .GetComponents(UriComponents.Query | UriComponents.Fragment, UriFormat.SafeUnescaped))) + { + throw Error.Argument(Strings.Context_EntitySetName, "entitySetName"); + } + } + + /// validate is entity type + /// entity to validate + /// The client model. + /// if entity was null + /// if entity does not have a key property + private static void ValidateEntityType(object entity, ClientEdmModel model) + { + Util.CheckArgumentNull(entity, "entity"); + + if (!ClientTypeUtil.TypeIsEntity(entity.GetType(), model)) + { + throw Error.Argument(Strings.Content_EntityIsNotEntityType, "entity"); + } + } + + /// + /// Validates a given list of operation parameter and returns two seperated list of body operation parameter + /// and uri operation parameter respectively. + /// + /// the http method used in the request. Only POST and GET http methods are supported with operation parameters. + /// The list of operation parameters to be validated. + /// The list of body operation parameters to be returned. + /// The list of uri operation parameters to be returned. + private static void ValidateOperationParameters( + string httpMethod, + OperationParameter[] parameters, + out List bodyOperationParameters, + out List uriOperationParameters) + { + Debug.Assert(parameters != null, "parameters != null"); + Debug.Assert( + string.CompareOrdinal(XmlConstants.HttpMethodPost, httpMethod) == 0 || + string.CompareOrdinal(XmlConstants.HttpMethodGet, httpMethod) == 0, + "HttpMethod was expected to be either GET or POST."); + + HashSet uriParamNames = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet bodyParamNames = new HashSet(StringComparer.OrdinalIgnoreCase); + + List uriParams = new List(); + List bodyParams = new List(); + + foreach (OperationParameter operationParameter in parameters) + { + if (operationParameter == null) + { + throw new ArgumentException(Strings.Context_NullElementInOperationParameterArray); + } + + if (String.IsNullOrEmpty(operationParameter.Name)) + { + throw new ArgumentException(Strings.Context_MissingOperationParameterName); + } + + String paramName = operationParameter.Name.Trim(); + + BodyOperationParameter bodyOperationParameter = operationParameter as BodyOperationParameter; + if (bodyOperationParameter != null) + { + if (string.CompareOrdinal(XmlConstants.HttpMethodGet, httpMethod) == 0) + { + throw new ArgumentException(Strings.Context_BodyOperationParametersNotAllowedWithGet); + } + + if (!bodyParamNames.Add(paramName)) + { + throw new ArgumentException(Strings.Context_DuplicateBodyOperationParameterName); + } + + bodyParams.Add(bodyOperationParameter); + } + else + { + UriOperationParameter uriOperationParameter = operationParameter as UriOperationParameter; + if (uriOperationParameter != null) + { + if (!uriParamNames.Add(paramName)) + { + throw new ArgumentException(Strings.Context_DuplicateUriOperationParameterName); + } + + uriParams.Add(uriOperationParameter); + } + } + } + + uriOperationParameters = uriParams.Any() ? uriParams : null; + bodyOperationParameters = bodyParams.Any() ? bodyParams : null; + } + + /// + /// Fires the BuildingRequest event so the user can add custom query parameters. + /// + /// Information about the request so they user can selectively add query parameters. + /// A new RequestMessageArgs object that contains any changes the user made to the query string. + private BuildingRequestEventArgs FireBuildingRequest(BuildingRequestEventArgs buildingRequestEventArgs) + { + if (this.HasBuildingRequestEventHandlers) + { + this.InnerBuildingRequest(this, buildingRequestEventArgs); + + // The reason to clone it is so that users can change the + // value after this event is fired. + return buildingRequestEventArgs.Clone(); + } + + return buildingRequestEventArgs; + } + + /// + /// Validate the SaveChanges Option + /// + /// options as specified by the user. + private void ValidateSaveChangesOptions(SaveChangesOptions options) + { + const SaveChangesOptions All = SaveChangesOptions.ContinueOnError | SaveChangesOptions.BatchWithSingleChangeset | SaveChangesOptions.BatchWithIndependentOperations | SaveChangesOptions.ReplaceOnUpdate | SaveChangesOptions.PostOnlySetProperties; + + // Make sure no higher order bits are set. + if ((options | All) != All) + { + throw Error.ArgumentOutOfRange("options"); + } + + // SaveChangesOptions.BatchWithSingleChangeset and SaveChangesOptions.BatchWithIndependentOperations can't be set together. + if (Util.IsFlagSet(options, SaveChangesOptions.BatchWithSingleChangeset | SaveChangesOptions.BatchWithIndependentOperations)) + { + throw Error.ArgumentOutOfRange("options"); + } + + // BatchWithSingleChangeset and continueOnError can't be set together + if (Util.IsFlagSet(options, SaveChangesOptions.BatchWithSingleChangeset | SaveChangesOptions.ContinueOnError)) + { + throw Error.ArgumentOutOfRange("options"); + } + + // BatchWithIndependentOperations and continueOnError can't be set together + if (Util.IsFlagSet(options, SaveChangesOptions.BatchWithIndependentOperations | SaveChangesOptions.ContinueOnError)) + { + throw Error.ArgumentOutOfRange("options"); + } + + // OnlyPostExplicitProperties cannot be used without DataServiceCollection to track properties change + if (Util.IsFlagSet(options, SaveChangesOptions.PostOnlySetProperties) && !this.UsingDataServiceCollection) + { + throw Error.InvalidOperation(Strings.Context_MustBeUsedWith("SaveChangesOptions.OnlyPostExplicitProperties", "DataServiceCollection")); + } + } + + /// + /// Validate and process the input parameters to all the execute methods. Also seperates and returns + /// the input operation parameters list into two seperate list - one of body operation parameters and the other + /// for uri operation parameters. + /// + /// element type. See Execute method for more details. + /// request to execute + /// HttpMethod to use. Only GET and POST are supported if operation parameters are not empty. + /// If set to true, indicates that a single result is expected as a response. + /// The list of body operation parameters to be returned. + /// The list of uri operation parameters to be returned. + /// The operation parameters associated with the service operation. + private void ValidateExecuteParameters( + ref Uri requestUri, + string httpMethod, + ref bool? singleResult, + out List bodyOperationParameters, + out List uriOperationParameters, + params OperationParameter[] operationParameters) + { + if (string.CompareOrdinal(XmlConstants.HttpMethodGet, httpMethod) != 0 && + string.CompareOrdinal(XmlConstants.HttpMethodPost, httpMethod) != 0 && + string.CompareOrdinal(XmlConstants.HttpMethodDelete, httpMethod) != 0) + { + throw new ArgumentException(Strings.Context_ExecuteExpectsGetOrPostOrDelete, "httpMethod"); + } + + if (ClientTypeUtil.TypeOrElementTypeIsStructured(typeof(TElement))) + { + singleResult = null; + } + + if (operationParameters != null && operationParameters.Length > 0) + { + DataServiceContext.ValidateOperationParameters(httpMethod, operationParameters, out bodyOperationParameters, out uriOperationParameters); + } + else + { + uriOperationParameters = null; + bodyOperationParameters = null; + } + + requestUri = this.BaseUriResolver.GetOrCreateAbsoluteUri(requestUri); + } + + /// + /// create the load property request + /// + /// entity + /// name of collection or reference property to load + /// The AsyncCallback delegate. + /// user state + /// The request uri, or null if one is to be constructed + /// Continuation, if one is available. + /// a aync result that you can get a response from + private LoadPropertyResult CreateLoadPropertyRequest(object entity, string propertyName, AsyncCallback callback, object state, Uri requestUri, DataServiceQueryContinuation continuation) + { + Debug.Assert(continuation == null || requestUri == null, "continuation == null || requestUri == null -- only one or the either (or neither) may be passed in"); + Util.CheckArgumentNull(entity, "entity"); + EntityDescriptor box = this.entityTracker.GetEntityDescriptor(entity); + Util.CheckArgumentNullAndEmpty(propertyName, "propertyName"); + + ClientTypeAnnotation type = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(entity.GetType())); + Debug.Assert(type.IsEntityType, "must be entity type to be contained"); + + if (EntityStates.Added == box.State) + { + throw Error.InvalidOperation(Strings.Context_NoLoadWithInsertEnd); + } + + ClientPropertyAnnotation property = type.GetProperty(propertyName, UndeclaredPropertyBehavior.ThrowException); + Debug.Assert(null != property, "should have thrown if propertyName didn't exist"); + + bool isContinuation = requestUri != null || continuation != null; + + ProjectionPlan plan; + if (continuation == null) + { + plan = null; + } + else + { + plan = continuation.Plan; + requestUri = continuation.NextLinkUri; + } + + bool mediaLink = (type.MediaDataMember != null && propertyName == type.MediaDataMember.PropertyName); + Version requestVersion; + if (requestUri == null) + { + if (mediaLink) + { + // special case for requesting the "media" value of an ATOM media link entry + Uri relativeUri = UriUtil.CreateUri(XmlConstants.UriValueSegment, UriKind.Relative); + requestUri = UriUtil.CreateUri(box.GetResourceUri(this.BaseUriResolver, true /*queryLink*/), relativeUri); + } + else + { + requestUri = box.GetNestedResourceInfo(this.baseUriResolver, property); + } + } + + requestVersion = Util.ODataVersion4; + + HeaderCollection headers = new HeaderCollection(); + + // Validate and set the request DSV header + headers.SetRequestVersion(requestVersion, this.MaxProtocolVersionAsVersion); + + if (mediaLink) + { + this.Format.SetRequestAcceptHeaderForStream(headers); + } + else + { + this.formatTracker.SetRequestAcceptHeader(headers); + } + + ODataRequestMessageWrapper request = this.CreateODataRequestMessage( + this.CreateRequestArgsAndFireBuildingRequest(XmlConstants.HttpMethodGet, requestUri, headers, this.HttpStack, null /*descriptor*/), + null /*descriptor*/); + + DataServiceRequest dataServiceRequest = DataServiceRequest.GetInstance(property.PropertyType, requestUri); + dataServiceRequest.PayloadKind = ODataPayloadKind.IndividualProperty; + return new LoadPropertyResult(entity, propertyName, this, request, callback, state, dataServiceRequest, plan, isContinuation); + } + + /// + /// verify the source and target are relatable + /// + /// source Resource + /// source Property + /// target Resource + /// destination state of relationship to evaluate for + /// true if DeletedState and one of the ends is in the added state + /// if source or target are null + /// if source or target are not contained + /// if source property is null + /// if source property empty + /// Can only relate ends with keys. + /// If target doesn't match property type. + /// If adding relationship where one of the ends is in the deleted state. + /// If attaching relationship where one of the ends is in the added or deleted state. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Pending")] + private bool EnsureRelatable(object source, string sourceProperty, object target, EntityStates state) + { + Util.CheckArgumentNull(source, "source"); + EntityDescriptor sourceResource = this.entityTracker.GetEntityDescriptor(source); + + EntityDescriptor targetResource = null; + if ((null != target) || ((EntityStates.Modified != state) && (EntityStates.Unchanged != state))) + { + Util.CheckArgumentNull(target, "target"); + targetResource = this.entityTracker.GetEntityDescriptor(target); + } + + Util.CheckArgumentNullAndEmpty(sourceProperty, "sourceProperty"); + + ClientTypeAnnotation type = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(source.GetType())); + Debug.Assert(type.IsEntityType, "should be enforced by just adding an object"); + + // will throw InvalidOperationException if property doesn't exist + ClientPropertyAnnotation property = type.GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException); + + if (property.IsKnownType) + { + throw Error.InvalidOperation(Strings.Context_RelationNotRefOrCollection); + } + + if (EntityStates.Unchanged == state && null == target && property.IsEntityCollection) + { + Util.CheckArgumentNull(target, "target"); + targetResource = this.entityTracker.GetEntityDescriptor(target); + } + + if (((EntityStates.Added == state) || (EntityStates.Deleted == state)) && !property.IsEntityCollection) + { + throw Error.InvalidOperation(Strings.Context_AddLinkCollectionOnly); + } + else if (EntityStates.Modified == state && property.IsEntityCollection) + { + throw Error.InvalidOperation(Strings.Context_SetLinkReferenceOnly); + } + + // if (property.IsEntityCollection) then property.PropertyType is the collection elementType + // either way you can only have a relation ship between keyed objects + type = this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(property.EntityCollectionItemType ?? property.PropertyType)); + Debug.Assert(type.IsEntityType, "should be enforced by just adding an object"); + + if ((null != target) && !type.ElementType.IsInstanceOfType(target)) + { + // target is not of the correct type + throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target"); + } + + if ((EntityStates.Added == state) || (EntityStates.Unchanged == state)) + { + if ((sourceResource.State == EntityStates.Deleted) || + ((targetResource != null) && (targetResource.State == EntityStates.Deleted))) + { + // can't add/attach new relationship when source or target in deleted state + throw Error.InvalidOperation(Strings.Context_NoRelationWithDeleteEnd); + } + } + + if ((EntityStates.Deleted == state) || (EntityStates.Unchanged == state)) + { + if ((sourceResource.State == EntityStates.Added) || + ((targetResource != null) && (targetResource.State == EntityStates.Added))) + { + // can't have non-added relationship when source or target is in added state + if (EntityStates.Deleted == state) + { + return true; + } + + throw Error.InvalidOperation(Strings.Context_NoRelationWithInsertEnd); + } + } + + return false; + } + + /// + /// This method creates an async result object around a request to get the read stream for a Media Resource + /// associated with the Media Link Entry represented by the entity object. + /// + /// The entity which is the Media Link Entry for the requested Media Resource. Thist must specify + /// a tracked entity in a non-added state. + /// Instance of class with additional metadata for the request. + /// Must not be null. + /// User defined callback to be called when results are available. Can be null. + /// User state in IAsyncResult. Can be null. + /// name of the stream. + /// The async result object for the request, the request hasn't been started yet. + /// Either entity or args parameters are null. + /// The specified entity is either not tracked, + /// is in the added state or it's not an MLE. + private GetReadStreamResult CreateGetReadStreamResult( + object entity, + DataServiceRequestArgs args, + AsyncCallback callback, + object state, + string name) + { + Util.CheckArgumentNull(entity, "entity"); + Util.CheckArgumentNull(args, "args"); + + EntityDescriptor entityDescriptor = this.entityTracker.GetEntityDescriptor(entity); + StreamDescriptor streamDescriptor; + Uri requestUri; + Version version; + if (name == null) + { + version = null; + requestUri = entityDescriptor.ReadStreamUri; + if (requestUri == null) + { + throw new ArgumentException(Strings.Context_EntityNotMediaLinkEntry, "entity"); + } + + streamDescriptor = entityDescriptor.DefaultStreamDescriptor; + } + else + { + version = Util.ODataVersion4; + if (!entityDescriptor.TryGetNamedStreamInfo(name, out streamDescriptor)) + { + throw new ArgumentException(Strings.Context_EntityDoesNotContainNamedStream(name), "name"); + } + + // use the edit link, if self link is not specified. + requestUri = streamDescriptor.SelfLink ?? streamDescriptor.EditLink; + if (requestUri == null) + { + throw new ArgumentException(Strings.Context_MissingSelfAndEditLinkForNamedStream(name), "name"); + } + } + + // Because the user could be re-using the args class, make a copy of the headers they have set so far before adding any more. + HeaderCollection headers = args.HeaderCollection.Copy(); + + // Validate and set the request DSV header + headers.SetRequestVersion(version, this.MaxProtocolVersionAsVersion); + + this.Format.SetRequestAcceptHeaderForStream(headers); + + BuildingRequestEventArgs requestMessageArgs = this.CreateRequestArgsAndFireBuildingRequest(XmlConstants.HttpMethodGet, requestUri, headers, HttpStack.Auto, streamDescriptor); + + ODataRequestMessageWrapper requestMessage = this.CreateODataRequestMessage(requestMessageArgs, streamDescriptor); + + return new GetReadStreamResult(this, "GetReadStream", requestMessage, callback, state, streamDescriptor); + } + + /// + /// Ensures that the required OData version is lesser than the maxprotocolversion on this instance. + /// + /// throws an invalidoperationexception if the max protocolversion is lesser than the required protocol version + private void EnsureMinimumProtocolVersionV3() + { + if (this.MaxProtocolVersionAsVersion < Util.ODataVersion4) + { + throw Error.InvalidOperation(Strings.Context_RequestVersionIsBiggerThanProtocolVersion(Util.ODataVersion4, this.MaxProtocolVersionAsVersion)); + } + } + + /// + /// Helper method for calling the overridable GetEntityMetadataBuilder API and performing common logic/verification. + /// + /// The entity descriptor tracking the entity. + /// A metadata builder for the entity tracked by the given descriptor. + private ODataResourceMetadataBuilder GetEntityMetadataBuilderInternal(EntityDescriptor descriptor) + { + Debug.Assert(descriptor != null, "descriptor != null"); + + // TODO: Should things with slashes still be passed down? We will need to make a decision one way or the other. + // For now we will pass them down. + ODataResourceMetadataBuilder entityMetadataBuilder = this.GetEntityMetadataBuilder(descriptor.EntitySetName, descriptor.EdmValue); + if (entityMetadataBuilder == null) + { + throw new InvalidOperationException(Strings.Context_EntityMetadataBuilderIsRequired); + } + + return entityMetadataBuilder; + } + + /// + /// This method wraps the HttpWebRequest.GetSyncronousResponse method call. It fires the ReceivingResponse event. + /// It also gives us a place to invoke internal test hook callbacks that can validate the response headers, and also so that we can do + /// debug validation to make sure that the headers have not changed since they were originally configured on the request. + /// + /// HttpWebRequest instance + /// IAsyncResult for EndGetResponse if this is an async call. + /// If set to true, this method will only re-throw the WebException that was caught if + /// the response in the exception is null. If set to false, this method will always re-throw in case of a WebException. + /// Returns the HttpWebResponse from the wrapped GetSyncronousResponse method. + private IODataResponseMessage GetResponseHelper(ODataRequestMessageWrapper request, IAsyncResult asyncResult, bool handleWebException) + { + Debug.Assert(request != null, "Expected a non-null request for all scenarios calling GetSyncronousResponse"); + + IODataResponseMessage response = null; + try + { +#if !PORTABLELIB + if (asyncResult == null) + { + response = request.GetResponse(); + } + else + { + response = request.EndGetResponse(asyncResult); + } +#else + response = request.EndGetResponse(asyncResult); +#endif + this.FireReceivingResponseEvent(new ReceivingResponseEventArgs(response, request.Descriptor)); + } + catch (DataServiceTransportException e) + { + response = e.Response; + + this.FireReceivingResponseEvent(new ReceivingResponseEventArgs(response, request.Descriptor)); + + if (!handleWebException || response == null) + { + throw; + } + } + + return response; + } + + /// + /// Mark an existing object for update in the context. + /// + /// entity to be mark for update + /// If true, then an exception should be thrown if the entity is in neither the unchanged nor modified states. + /// if entity is null + /// if entity is detached + /// if entity is not unchanged or modified and is true. + private void UpdateObjectInternal(object entity, bool failIfNotUnchanged) + { + Util.CheckArgumentNull(entity, "entity"); + + EntityDescriptor resource = this.entityTracker.TryGetEntityDescriptor(entity); + if (resource == null) + { + throw Error.Argument(Strings.Context_EntityNotContained, "entity"); + } + + if (resource.State == EntityStates.Modified) + { + return; + } + + if (resource.State != EntityStates.Unchanged) + { + if (failIfNotUnchanged) + { + throw Error.InvalidOperation(ClientStrings.Context_CannotChangeStateToModifiedIfNotUnchanged); + } + + return; + } + + resource.State = EntityStates.Modified; + this.entityTracker.IncrementChange(resource); + } + + /// + /// Mark an existing object being tracked by the context for deletion. + /// + /// entity to be mark deleted + /// If true, then an exception will be thrown if the entity is in the added state. + /// if entity is null + /// if entity is not being tracked by the context, or if the entity is in the added state and is true. + /// + /// Existings objects in the Added state become detached if is false. + /// + private void DeleteObjectInternal(object entity, bool failIfInAddedState) + { + Util.CheckArgumentNull(entity, "entity"); + + EntityDescriptor resource = this.entityTracker.GetEntityDescriptor(entity); + EntityStates state = resource.State; + if (EntityStates.Added == state) + { + if (failIfInAddedState) + { + throw Error.InvalidOperation(ClientStrings.Context_CannotChangeStateIfAdded(EntityStates.Deleted)); + } + + // added -> detach + this.entityTracker.DetachResource(resource); + } + else if (EntityStates.Deleted != state) + { + Debug.Assert( + Util.IncludeLinkState(state), + "bad state transition to deleted"); + + // Leave related links alone which means we can have a link in the Added + // or Modified state referencing a source/target entity in the Deleted state. + resource.State = EntityStates.Deleted; + this.entityTracker.IncrementChange(resource); + } + } + + /// + /// Sets the entity's state to unchanged. + /// + /// The entity to set back to unchanged. + private void SetStateToUnchanged(object entity) + { + Util.CheckArgumentNull(entity, "entity"); + EntityDescriptor descriptor = this.entityTracker.GetEntityDescriptor(entity); + if (descriptor.State == EntityStates.Added) + { + throw Error.InvalidOperation(ClientStrings.Context_CannotChangeStateIfAdded(EntityStates.Unchanged)); + } + + descriptor.State = EntityStates.Unchanged; + } + + /// + /// Cache for client edm models by version. + /// + private static class ClientEdmModelCache + { + /// A cache that maps a data service protocol version to its corresponding . + /// Note that it is initialized in a static ctor and must not be changed later to avoid threading issues. + private static readonly Dictionary modelCache = CreateClientEdmModelCache(); + + /// + /// Get the cached model for the specified max protocol version. + /// + /// The to get the cached model for. + /// The cached model for the . + internal static ClientEdmModel GetModel(ODataProtocolVersion maxProtocolVersion) + { + Util.CheckEnumerationValue(maxProtocolVersion, "maxProtocolVersion"); + Debug.Assert(modelCache.ContainsKey(maxProtocolVersion), "modelCache should be pre-initialized"); + + return modelCache[maxProtocolVersion]; + } + + /// + /// Initialize modelCache. + /// + /// The model cache built. + private static Dictionary CreateClientEdmModelCache() + { + Dictionary cache = + new Dictionary(EqualityComparer.Default); + + IEnumerable protocolVersions = +#if PORTABLELIB // Portable lib does not support Enum.GetValues() + typeof(ODataProtocolVersion).GetFields().Where(f => f.IsLiteral).Select(f => f.GetValue(typeof(ODataProtocolVersion))).Cast(); +#else + Enum.GetValues(typeof(ODataProtocolVersion)).Cast(); +#endif + + foreach (var protocolVersion in protocolVersions) + { + ClientEdmModel model = new ClientEdmModel(protocolVersion); + model.SetEdmVersion(protocolVersion.ToVersion()); + cache.Add(protocolVersion, model); + } + + return cache; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuery.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuery.cs new file mode 100644 index 0000000..5c9b782 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuery.cs @@ -0,0 +1,107 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + + /// non-generic placeholder for generic implementation + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710", Justification = "required for this feature")] + public abstract class DataServiceQuery : DataServiceRequest, IQueryable + { + /// internal constructor so that only our assembly can provide an implementation + internal DataServiceQuery() + { + } + + /// Represents an expression that contains the query to the data service. + /// An object that represents the query. + public abstract Expression Expression + { + get; + } + + /// Represents the query provider instance. + /// An representing the data source provider. + public abstract IQueryProvider Provider + { + get; + } + + /// Gets the object that can be used to iterate through the collection returned by the query. + /// An enumerator over the query results. + /// Expect derived class to override this with an explict interface implementation + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033", Justification = "required for this feature")] + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + throw Error.NotImplemented(); + } + +#if !PORTABLELIB + /// Executes the query against the data service. + /// An that contains the results of the query operation. + /// When the data service returns an HTTP 404: Resource Not Found error. + public IEnumerable Execute() + { + return this.ExecuteInternal(); + } +#endif + + /// Asynchronously sends a request to execute the data service query. + /// An object that is used to track the status of the asynchronous operation. + /// Delegate to invoke when results are available for client consumption. + /// User-defined state object passed to the callback. + public IAsyncResult BeginExecute(AsyncCallback callback, object state) + { + return this.BeginExecuteInternal(callback, state); + } + + /// Asynchronously sends a request to execute the data service query. + /// A task represents An that contains the results of the query operation. + public Task ExecuteAsync() + { + return Task.Factory.FromAsync(this.BeginExecute, this.EndExecute, null); + } + + /// Called to complete the asynchronous operation of executing a data service query. + /// An that contains the results of the query operation. + /// The result from the operation that contains the query results. + /// When the data service returns an HTTP 404: Resource Not Found error. + public IEnumerable EndExecute(IAsyncResult asyncResult) + { + return this.EndExecuteInternal(asyncResult); + } + +#if !PORTABLELIB + /// Synchronous methods not available + /// + /// Returns an IEnumerable from an Internet resource. + /// + /// An IEnumerable that contains the response from the Internet resource. + internal abstract IEnumerable ExecuteInternal(); +#endif + + /// + /// Begins an asynchronous request to an Internet resource. + /// + /// The AsyncCallback delegate. + /// The state object for this request. + /// An IAsyncResult that references the asynchronous request for a response. + internal abstract IAsyncResult BeginExecuteInternal(AsyncCallback callback, object state); + + /// + /// Ends an asynchronous request to an Internet resource. + /// + /// The pending request for a response. + /// An IEnumerable that contains the response from the Internet resource. + internal abstract IEnumerable EndExecuteInternal(IAsyncResult asyncResult); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryContinuation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryContinuation.cs new file mode 100644 index 0000000..e38865f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryContinuation.cs @@ -0,0 +1,169 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Reflection; + using System.Runtime.Serialization; + + #endregion Namespaces + + /// Encapsulates a URI that returns the next page of a paged WCF Data Services query result. + [DebuggerDisplay("{NextLinkUri}")] + public abstract class DataServiceQueryContinuation + { + #region Private fields + + /// URI to next page of data. + private Uri nextLinkUri; + + /// Projection plan for results of next page. + private ProjectionPlan plan; + +#if DEBUG + /// True,if this instance is being deserialized via DataContractSerialization, false otherwise + private bool deserializing; +#endif + #endregion Private fields + + #region Constructors + + /// Initializes a new instance. + /// URI to next page of data. + /// Projection plan for results of next page. + internal DataServiceQueryContinuation(Uri nextLinkUri, ProjectionPlan plan) + { + Debug.Assert(nextLinkUri != null, "nextLinkUri != null"); + Debug.Assert(plan != null, "plan != null"); + + this.nextLinkUri = nextLinkUri; + this.plan = plan; +#if DEBUG + this.deserializing = false; +#endif + } + + #endregion Contructors. + + #region Properties + + /// Gets the URI that is used to return the next page of data from a paged query result. + /// A URI that returns the next page of data. + public Uri NextLinkUri + { + get + { + return this.nextLinkUri; + } + + internal set + { +#if DEBUG + Debug.Assert(this.deserializing, "This property can only be set during deserialization"); +#endif + this.nextLinkUri = value; + } + } + + /// Type of element to be paged over. + internal abstract Type ElementType + { + get; + } + + /// Projection plan for the next page of data; null if not available. + internal ProjectionPlan Plan + { + get + { + return this.plan; + } + + set + { +#if DEBUG + Debug.Assert(this.deserializing, "This property can only be set during deserialization"); +#endif + this.plan = value; + } + } + + #endregion Properties + + #region Methods + + /// Returns the next link URI as a string. + /// A string representation of the next link URI. + public override string ToString() + { + return this.NextLinkUri.ToString(); + } + + /// Creates a new instance. + /// Link to next page of data (possibly null). + /// Plan to materialize the data (only null if nextLinkUri is null). + /// A new continuation object; null if nextLinkUri is null. + internal static DataServiceQueryContinuation Create(Uri nextLinkUri, ProjectionPlan plan) + { + Debug.Assert(plan != null || nextLinkUri == null, "plan != null || nextLinkUri == null"); + + if (nextLinkUri == null) + { + return null; + } + + var constructors = typeof(DataServiceQueryContinuation<>).MakeGenericType(plan.ProjectedType).GetInstanceConstructors(false /*isPublic*/); + object result = Util.ConstructorInvoke(constructors.Single(), new object[] { nextLinkUri, plan }); + return (DataServiceQueryContinuation)result; + } + + /// + /// Initializes a new instance that can + /// be used for this continuation. + /// + /// A new initializes . + internal QueryComponents CreateQueryComponents() + { + // DSV needs to be 2.0 since $skiptoken will be on the uri. + QueryComponents result = new QueryComponents(this.NextLinkUri, Util.ODataVersionEmpty, this.Plan.LastSegmentType, null, null); + return result; + } + #endregion Methods + } + + /// Encapsulates a URI that returns the next page of a paged WCF Data Services query result.  + /// The type of continuation token. + public sealed class DataServiceQueryContinuation : DataServiceQueryContinuation + { + #region Contructors. + + /// Initializes a new typed instance. + /// URI to next page of data. + /// Projection plan for results of next page. + internal DataServiceQueryContinuation(Uri nextLinkUri, ProjectionPlan plan) + : base(nextLinkUri, plan) + { + } + + #endregion Contructors. + + #region Properties + + /// Type of element to be paged over. + internal override Type ElementType + { + get { return typeof(T); } + } + + #endregion Properties + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryException.cs new file mode 100644 index 0000000..88b9a8c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryException.cs @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Exception that indicates an error occurred while querying the data service. +#if !PORTABLELIB + [Serializable] +#endif + [System.Diagnostics.DebuggerDisplay("{Message}")] + public sealed class DataServiceQueryException : InvalidOperationException + { + #region Private fields + + /// Actual response object. +#if !PORTABLELIB + [NonSerialized] +#endif + private readonly QueryOperationResponse response; + + #endregion Private fields + + #region Constructors + + /// Initializes a new instance of the class with a system-supplied message that describes the error. + public DataServiceQueryException() + : base(Strings.DataServiceException_GeneralError) + { + } + + /// Initializes a new instance of the class with a specified message that describes the error. + /// The message that describes the exception. The caller of this constructor is required to ensure that this string has been localized for the current system culture.The string value that the contains error message. + public DataServiceQueryException(string message) + : base(message) + { + } + + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// The message that describes the exception. The caller of this constructor is required to ensure that this string has been localized for the current system culture. The string value that contains the error message. + /// The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. The inner exception object. + public DataServiceQueryException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// Initializes a new instance of the class. + /// The string value that contains the error message. + /// The inner exception object. + /// The object. + public DataServiceQueryException(string message, Exception innerException, QueryOperationResponse response) + : base(message, innerException) + { + this.response = response; + } + +#if !PORTABLELIB +#pragma warning disable 0628 + /// + /// Initializes a new instance of the DataServiceQueryException class from the + /// specified SerializationInfo and StreamingContext instances. + /// + /// + /// A SerializationInfo containing the information required to serialize + /// the new DataServiceQueryException. + /// + /// A StreamingContext containing the source of the serialized stream + /// associated with the new DataServiceQueryException. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1047", Justification = "Follows serialization info pattern.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1032", Justification = "Follows serialization info pattern.")] + protected DataServiceQueryException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#pragma warning restore 0628 +#endif + + #endregion Constructors + + #region Public properties + + /// Gets the that indicates the exception results. + /// A object that indicates the exception results. + public QueryOperationResponse Response + { + get { return this.response; } + } + + #endregion Public properties + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryOfT.cs new file mode 100644 index 0000000..ed70bf9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQueryOfT.cs @@ -0,0 +1,517 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Threading.Tasks; + + /// + /// query object + /// + /// type of object to materialize + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "required for this feature")] + public class DataServiceQuery : DataServiceQuery, IQueryable + { + #region Private fields + + /// Method info for the v1 Expand method. + private static readonly MethodInfo expandMethodInfo = typeof(DataServiceQuery).GetMethod("Expand", new Type[] { typeof(string) }); + + /// Method info for the generic version of the Expand method +#if PORTABLELIB + private static readonly MethodInfo expandGenericMethodInfo = typeof(DataServiceQuery).GetMethodWithGenericArgs("Expand", true /*isPublic*/, false /*isStatic*/, 1 /*genericArgCount*/); +#else + private static readonly MethodInfo expandGenericMethodInfo = (MethodInfo)typeof(DataServiceQuery).GetMember("Expand*").Single(m => ((MethodInfo)m).GetGenericArguments().Count() == 1); +#endif + + /// Linq Expression + private readonly Expression queryExpression; + + /// Linq Query Provider + private readonly DataServiceQueryProvider queryProvider; + + /// Uri, Projection, Version for translated query + private QueryComponents queryComponents; + + #endregion Private fields + + /// + /// query object + /// + /// expression for query + /// query provider for query + public DataServiceQuery(Expression expression, DataServiceQueryProvider provider) + : this(expression, provider, true) + { + this.IsFunction = false; + } + + /// + /// query object of a function which returns a collection of items + /// + /// expression for query + /// query provider for query + /// whether this query is composable + public DataServiceQuery(Expression expression, DataServiceQueryProvider provider, bool isComposable) + { + Debug.Assert(null != provider.Context, "null context"); + Debug.Assert(expression != null, "null expression"); + Debug.Assert(provider is DataServiceQueryProvider, "Currently only support Web Query Provider"); + + this.queryExpression = expression; + this.queryProvider = provider; + this.IsComposable = isComposable; + this.IsFunction = true; + } + + #region IQueryable implementation + /// Returns the type of the object used in the template to create the instance. + /// Returns representing the type used in the template when the query is created. + public override Type ElementType + { + get { return typeof(TElement); } + } + + /// Represents an expression containing the query to the data service. + /// A object representing the query. + public override Expression Expression + { + get { return this.queryExpression; } + } + + /// Represents the query provider instance. + /// A representing the data source provider. + public override IQueryProvider Provider + { + get { return this.queryProvider; } + } + + #endregion + + /// Get the URI for the query. + /// The URI of the request. + public override Uri RequestUri + { + get + { + return this.Translate().Uri; + } + + internal set + { + this.Translate().Uri = value; + } + } + + /// Context associated with this query. + public DataServiceContext Context + { + get { return this.queryProvider.Context; } + } + + /// + /// Whether this query is composable + /// + public bool IsComposable { get; private set; } + + /// + /// The flag of whether this query is a function. + /// + internal bool IsFunction { get; private set; } + + /// The ProjectionPlan for the request (if precompiled in a previous page). + internal override ProjectionPlan Plan + { + get { return null; } + } + + /// + /// Gets a new URI string with keys. + /// + /// The string representing keys. + /// The new URI string with keys. + public string GetKeyPath(string keyString) + { + string resourcePath = UriUtil.UriToString(this.RequestUri).Substring(UriUtil.UriToString(this.Context.BaseUri).Length); + if (this.Context.UrlKeyDelimiter == DataServiceUrlKeyDelimiter.Slash) + { + return resourcePath + UriHelper.FORWARDSLASH + keyString; + } + else + { + return resourcePath + UriHelper.LEFTPAREN + keyString + UriHelper.RIGHTPAREN; + } + } + + /// Creates a data service query for function which return collection of data. + /// The type returned by the query + /// The function name. + /// Whether this query is composable. + /// The function parameters. + /// A new instance that represents the function call. + public DataServiceQuery CreateFunctionQuery(string functionName, bool isComposable, params UriOperationParameter[] parameters) + { + Dictionary operationParameters = this.Context.SerializeOperationParameters(parameters); + ResourceSetExpression rse = new ResourceSetExpression(typeof(IOrderedQueryable), this.Expression, null, typeof(T), null, CountOption.None, null, null, null, null, functionName, operationParameters, false); + return new DataServiceQuery.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this.Context), isComposable); + } + + /// Creates a data service query for function which return single data. + /// The type returned by the query + /// The function name. + /// Whether this query is composable. + /// The function parameters. + /// A new instance that represents the function call. + public DataServiceQuerySingle CreateFunctionQuerySingle(string functionName, bool isComposable, params UriOperationParameter[] parameters) + { + return new DataServiceQuerySingle(CreateFunctionQuery(functionName, isComposable, parameters), isComposable); + } + + /// + /// Get a new URI string by adding to the original one. + /// + /// Name of the action. + /// The new URI string. + public string AppendRequestUri(string nextSegment) + { + Uri requestUri = this.RequestUri; + return UriUtil.UriToString(requestUri).Replace(requestUri.AbsolutePath, requestUri.AbsolutePath + UriHelper.FORWARDSLASH + nextSegment); + } + + /// + /// Get a new URI path string by adding to the original one. + /// + /// The next segment to add to path. + /// The new URI path string. + public string GetPath(string nextSegment) + { + string resourcePath = UriUtil.UriToString(this.RequestUri).Substring(UriUtil.UriToString(this.Context.BaseUri).Length); + return resourcePath + UriHelper.FORWARDSLASH + nextSegment; + } + + /// Starts an asynchronous network operation that executes the query represented by this object instance. + /// An that represents the status of the asynchronous operation. + /// The delegate to invoke when the operation completes. + /// User defined object used to transfer state between the start of the operation and the callback defined by . + public new IAsyncResult BeginExecute(AsyncCallback callback, object state) + { + if (this.IsFunction) + { + return this.Context.BeginExecute(this.RequestUri, callback, state, XmlConstants.HttpMethodGet, false); + } + else + { + return base.BeginExecute(this, this.Context, callback, state, Util.ExecuteMethodName); + } + } + + /// Starts an asynchronous network operation that executes the query represented by this object instance. + /// A task that represents an that contains the results of the query operation. + public new Task> ExecuteAsync() + { + return Task>.Factory.FromAsync(this.BeginExecute, this.EndExecute, null); + } + + /// Ends an asynchronous query request to a data service. + /// Returns an that contains the results of the query operation. + /// The pending asynchronous query request. + /// When the data service returns an HTTP 404: Resource Not Found error. + public new IEnumerable EndExecute(IAsyncResult asyncResult) + { + if (this.IsFunction) + { + return this.Context.EndExecute(asyncResult); + } + else + { + return DataServiceRequest.EndExecute(this, this.Context, Util.ExecuteMethodName, asyncResult); + } + } + + /// + /// Asynchronously sends a request to get all items by auto iterating all pages + /// + /// A task that represents an that contains the results of the query operation. + public Task> GetAllPagesAsync() + { + var currentTask = Task>.Factory.FromAsync(this.BeginExecute, this.EndExecute, null); + var nextTask = currentTask.ContinueWith(t => this.ContinuePage(t.Result)); + return nextTask; + } + +#if !PORTABLELIB // Synchronous methods not available + /// Executes the query and returns the results as a collection that implements IEnumerable. + /// An in which TElement represents the type of the query results. + /// When the data service returns an HTTP 404: Resource Not Found error. + /// When during materialization an object is encountered in the input stream that cannot be deserialized to an instance of TElement. + public new IEnumerable Execute() + { + if (this.IsFunction) + { + return this.Context.Execute(this.RequestUri, XmlConstants.HttpMethodGet, false); + } + else + { + return this.Execute(this.Context, this.Translate()); + } + } + + /// + /// Get all items by auto iterating all pages, will send the request of first page as default, regardless if it's iterated. + /// + /// The items retrieved + public IEnumerable GetAllPages() + { + QueryOperationResponse response = this.Execute(this.Context, this.Translate()); + return this.GetRestPages(response); + } +#endif + + /// Expands a query to include entities from a related entity set in the query response. + /// A new query that includes the requested $expand query option appended to the URI of the supplied query. + /// The expand path in the format Orders/Order_Details. + public DataServiceQuery Expand(string path) + { + Util.CheckArgumentNullAndEmpty(path, "path"); + Debug.Assert(DataServiceQuery.expandMethodInfo != null, "DataServiceQuery.expandMethodInfo != null"); + + return (DataServiceQuery)this.Provider.CreateQuery( + Expression.Call( + Expression.Convert(this.Expression, typeof(DataServiceQuery.DataServiceOrderedQuery)), + DataServiceQuery.expandMethodInfo, + new Expression[] { Expression.Constant(path) })); + } + + /// Expands a query to include entities from a related entity set in the query response, where the related entity is of a specific type in a type hierarchy. + /// Returns a that with the expand option included. + /// A lambda expression that indicates the navigation property that returns the entity set to include in the expanded query. + /// Target type of the last property on the expand path. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design")] + public DataServiceQuery Expand(Expression> navigationPropertyAccessor) + { + Util.CheckArgumentNull(navigationPropertyAccessor, "navigationPropertyAccessor"); + Debug.Assert(DataServiceQuery.expandGenericMethodInfo != null, "DataServiceQuery.expandGenericMethodInfo != null"); + + MethodInfo mi = DataServiceQuery.expandGenericMethodInfo.MakeGenericMethod(typeof(TTarget)); + return (DataServiceQuery)this.Provider.CreateQuery( + Expression.Call( + Expression.Convert(this.Expression, typeof(DataServiceQuery.DataServiceOrderedQuery)), + mi, + new Expression[] { navigationPropertyAccessor })); + } + + /// Requests that the count of all entities in the entity set be returned inline with the query results. + /// A new object that has the inline count option set. + public DataServiceQuery IncludeTotalCount() + { + MethodInfo mi = typeof(DataServiceQuery).GetMethod("IncludeTotalCount"); + + return (DataServiceQuery)this.Provider.CreateQuery( + Expression.Call( + Expression.Convert(this.Expression, typeof(DataServiceQuery.DataServiceOrderedQuery)), + mi)); + } + + /// Creates a new with the query option set in the URI generated by the returned query. + /// A new query that includes the requested query option appended to the URI of the supplied query + /// The string value that contains the name of the query string option to add. + /// The object that contains the value of the query string option. + public DataServiceQuery AddQueryOption(string name, object value) + { + Util.CheckArgumentNull(name, "name"); + Util.CheckArgumentNull(value, "value"); + MethodInfo mi = typeof(DataServiceQuery).GetMethod("AddQueryOption"); + return (DataServiceQuery)this.Provider.CreateQuery( + Expression.Call( + Expression.Convert(this.Expression, typeof(DataServiceQuery.DataServiceOrderedQuery)), + mi, + new Expression[] { Expression.Constant(name), Expression.Constant(value, typeof(object)) })); + } + + /// Executes the query and returns the results as a collection. + /// A typed enumerator over the results in which TElement represents the type of the query results. +#if !PORTABLELIB // Synchronous methods not available + public IEnumerator GetEnumerator() + { + return this.Execute().GetEnumerator(); + } +#else + public IEnumerator GetEnumerator() + { + throw Error.NotSupported(Strings.DataServiceQuery_EnumerationNotSupported); + } +#endif + + /// Represents the URI of the query to the data service. + /// A URI as string that represents the query to the data service for this instance. + public override string ToString() + { + try + { + return this.QueryComponents(this.Context.Model).Uri.ToString(); + } + catch (NotSupportedException e) + { + return Strings.ALinq_TranslationError(e.Message); + } + } + + /// Executes the query and returns the results as a collection. + /// An enumerator over the query results. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { +#if !PORTABLELIB // Synchronous methods not available + return this.GetEnumerator(); +#else + throw Error.NotSupported(); +#endif + } + + /// + /// gets the UriTranslateResult for a the query + /// + /// The client model. + /// an instance of QueryComponents. + internal override QueryComponents QueryComponents(ClientEdmModel model) + { + return this.Translate(); + } + +#if !PORTABLELIB + /// Synchronous methods not available + /// + /// Returns an IEnumerable from an Internet resource. + /// + /// An IEnumerable that contains the response from the Internet resource. + internal override IEnumerable ExecuteInternal() + { + return this.Execute(); + } +#endif + + /// + /// Begins an asynchronous request to an Internet resource. + /// + /// The AsyncCallback delegate. + /// The state object for this request. + /// An IAsyncResult that references the asynchronous request for a response. + internal override IAsyncResult BeginExecuteInternal(AsyncCallback callback, object state) + { + return this.BeginExecute(callback, state); + } + + /// + /// Ends an asynchronous request to an Internet resource. + /// + /// The pending request for a response. + /// An IEnumerable that contains the response from the Internet resource. + internal override IEnumerable EndExecuteInternal(IAsyncResult asyncResult) + { + return this.EndExecute(asyncResult); + } + + /// + /// gets the query components for the query after translating + /// + /// QueryComponents for query + private QueryComponents Translate() + { + if (this.queryComponents == null) + { + this.queryComponents = this.queryProvider.Translate(this.queryExpression); + } + + return this.queryComponents; + } + + /// + /// Continues to asynchronously send a request to get items of the next page + /// + /// The response of the previous page + /// The items retrieved + private IEnumerable ContinuePage(IEnumerable response) + { + foreach (var element in response) + { + yield return element; + } + + var continuation = (response as QueryOperationResponse).GetContinuation() as DataServiceQueryContinuation; + if (continuation != null) + { + var currentTask = Task>.Factory.FromAsync(this.Context.BeginExecute(continuation, null, null), this.Context.EndExecute); + var nextTask = currentTask.ContinueWith(t => this.ContinuePage(t.Result)); + nextTask.Wait(); + foreach (var element in nextTask.Result) + { + yield return element; + } + } + } + +#if !PORTABLELIB + /// Synchronous methods not available + /// + /// Returns an IEnumerable from an Internet resource. + /// + /// The response of the previous page + /// An IEnumerable that contains the response from the Internet resource. + private IEnumerable GetRestPages(IEnumerable response) + { + foreach (var element in response) + { + yield return element; + } + + var continuation = (response as QueryOperationResponse).GetContinuation(); + while (continuation != null) + { + response = this.Context.Execute(continuation); + foreach (var element in response) + { + yield return element; + } + + continuation = (response as QueryOperationResponse).GetContinuation(); + } + } +#endif + + /// + /// Ordered DataServiceQuery which implements IOrderedQueryable. + /// + public class DataServiceOrderedQuery : DataServiceQuery, IOrderedQueryable, IOrderedQueryable + { + /// + /// constructor + /// + /// expression for query + /// query provider for query + internal DataServiceOrderedQuery(Expression expression, DataServiceQueryProvider provider) + : base(expression, provider) + { + } + + /// + /// constructor + /// + /// expression for query + /// query provider for query + /// whether this query is composable + internal DataServiceOrderedQuery(Expression expression, DataServiceQueryProvider provider, bool isComposable) + : base(expression, provider, isComposable) + { + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuerySingleOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuerySingleOfT.cs new file mode 100644 index 0000000..2585d0f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceQuerySingleOfT.cs @@ -0,0 +1,249 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + + /// + /// Query object of a single item. + /// + /// Type of object to materialize. + public class DataServiceQuerySingle + { + /// + /// Query object. + /// + internal DataServiceQuery Query; + + /// + /// The flag of whether this query is a function. + /// + private readonly bool isFunction; + + /// + /// Query object of a single item. + /// + /// Context associated with this query. + /// A string that resolves to a URI. + public DataServiceQuerySingle(DataServiceContext context, string path) + { + this.Context = context; + this.Query = context.CreateSingletonQuery(path); + this.IsComposable = true; + this.isFunction = false; + } + + /// + /// Query object of a single item. + /// + /// Context associated with this query. + /// A string that resolves to a URI. + /// Whether this query is composable. + public DataServiceQuerySingle(DataServiceContext context, string path, bool isComposable) + { + this.Context = context; + this.Query = context.CreateSingletonQuery(path); + this.IsComposable = isComposable; + this.isFunction = true; + } + + /// Create a query of a single item based on another one. + /// The query. + public DataServiceQuerySingle(DataServiceQuerySingle query) + { + this.Context = query.Context; + this.Query = query.Query; + this.IsComposable = query.IsComposable; + this.isFunction = query.isFunction; + } + + /// + /// Query object of a function which returns a single item. + /// + /// Query object. + /// Whether this query is composable. + internal DataServiceQuerySingle(DataServiceQuery query, bool isComposable) + { + this.Query = query; + this.Context = query.Context; + this.IsComposable = isComposable; + this.isFunction = query.IsFunction; + } + + /// + /// Context associated with this query. + /// + public DataServiceContext Context { get; private set; } + + /// + /// Whether this query is composable. + /// + public bool IsComposable { get; private set; } + + /// + /// Get the URI for the query. + /// + public Uri RequestUri + { + get + { + if (this.Query == null) + { + this.Query = Context.CreateSingletonQuery(GetPath(null)); + } + + return Query.RequestUri; + } + } + + /// Creates a data service query for function which return collection of data. + /// The type returned by the query + /// The function name. + /// Whether this query is composable. + /// The function parameters. + /// A new instance that represents the function call. + public DataServiceQuery CreateFunctionQuery(string functionName, bool isComposable, params UriOperationParameter[] parameters) + { + return this.Query.CreateFunctionQuery(functionName, isComposable, parameters); + } + + /// Creates a data service query for function which return single data. + /// The type returned by the query + /// The function name. + /// Whether this query is composable. + /// The function parameters. + /// A new instance that represents the function call. + public DataServiceQuerySingle CreateFunctionQuerySingle(string functionName, bool isComposable, params UriOperationParameter[] parameters) + { + return new DataServiceQuerySingle(this.CreateFunctionQuery(functionName, isComposable, parameters), isComposable); + } + +#if !PORTABLELIB // Synchronous methods not available + /// + /// Executes the query and returns the result. + /// + /// Query result. + /// Problem materializing result of query into object. + public TElement GetValue() + { + if (this.isFunction) + { + return this.Context.Execute(this.RequestUri, XmlConstants.HttpMethodGet, true).SingleOrDefault(); + } + + return this.Query.Execute().SingleOrDefault(); + } +#endif + + /// Starts an asynchronous network operation that executes the query represented by this object instance. + /// An that represents the status of the asynchronous operation. + /// The delegate to invoke when the operation completes. + /// User defined object used to transfer state between the start of the operation and the callback defined by . + public IAsyncResult BeginGetValue(AsyncCallback callback, object state) + { + if (this.isFunction) + { + return this.Context.BeginExecute(this.RequestUri, callback, state, XmlConstants.HttpMethodGet, true); + } + + return this.Query.BeginExecute(callback, state); + } + + /// Starts an asynchronous network operation that executes the query represented by this object instance. + /// A task that represents the result of the query operation. + public Task GetValueAsync() + { + return Task.Factory.FromAsync(this.BeginGetValue, this.EndGetValue, null); + } + + /// Ends an asynchronous query request to a data service. + /// Returns the results of the query operation. + /// The pending asynchronous query request. + /// When the data service returns an HTTP 404: Resource Not Found error. + public TElement EndGetValue(IAsyncResult asyncResult) + { + Util.CheckArgumentNull(asyncResult, "asyncResult"); + if (this.isFunction) + { + return this.Context.EndExecute(asyncResult).SingleOrDefault(); + } + else + { + return this.Query.EndExecute(asyncResult).SingleOrDefault(); + } + } + + /// + /// Get a new URI path string by adding to the original one. + /// + /// The next segment to add to path. + /// The new URI path string. + public string GetPath(string nextSegment) + { + string resourcePath = UriUtil.UriToString(this.RequestUri).Substring(UriUtil.UriToString(this.Context.BaseUri).Length); + return nextSegment == null ? resourcePath : resourcePath + UriHelper.FORWARDSLASH + nextSegment; + } + + /// + /// Get a new URI string by adding to the original one. + /// + /// Name of the action. + /// The new URI string. + public string AppendRequestUri(string nextSegment) + { + return UriUtil.UriToString(this.RequestUri).Replace(this.RequestUri.AbsolutePath, this.RequestUri.AbsolutePath + UriHelper.FORWARDSLASH + nextSegment); + } + + /// + /// Projects the element of this query into a new form. + /// + /// The type of the value returned by selector. + /// A lambda expression that indicates the property returns. + /// A whose element is the result of invoking the transform function on the element of source. + public DataServiceQuerySingle Select(Expression> selector) + { + if (this.Query == null) + { + this.Query = Context.CreateSingletonQuery(GetPath(null)); + } + + return new DataServiceQuerySingle((DataServiceQuery)Query.Select(selector), true); + } + + /// + /// Expands a query to include entity from a related entity set in the query response, where the related entity is of a specific type in a type hierarchy. + /// + /// Target type of the last property on the expand path. + /// A lambda expression that indicates the navigation property that returns the entity set to include in the expanded query. + /// Returns a that with the expand option included. + public DataServiceQuerySingle Expand(Expression> navigationPropertyAccessor) + { + return new DataServiceQuerySingle(this.Query.Expand(navigationPropertyAccessor), true); + } + + /// Expands a query to include entities from a related entity set in the query response. + /// A new query that includes the requested $expand query option appended to the URI of the supplied query. + /// The expand path in the format Orders/Order_Details. + public DataServiceQuerySingle Expand(string path) + { + return new DataServiceQuerySingle(this.Query.Expand(path), true); + } + + /// + /// Cast this query type into its derived type. + /// + /// Derived type of TElement to be casted to. + /// Returns a of TResult type. + public DataServiceQuerySingle CastTo() + { + return new DataServiceQuerySingle((DataServiceQuery)this.Query.OfType(), true); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequest.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequest.cs new file mode 100644 index 0000000..7ad73b9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequest.cs @@ -0,0 +1,348 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Net; + using System.Xml; + using Microsoft.OData; + + /// non-generic placeholder for generic implementation + public abstract class DataServiceRequest + { + /// internal constructor so that only our assembly can provide an implementation + internal DataServiceRequest() + { + this.PayloadKind = ODataPayloadKind.Unsupported; + } + + /// Gets the type of object submitted as a batch to the data service. + /// Type object. + public abstract Type ElementType + { + get; + } + + /// Gets the URI of the request object submitted to a data service. + /// URI of the request object. + public abstract Uri RequestUri + { + get; + internal set; + } + + /// The ProjectionPlan for the request, if precompiled in a previous page; null otherwise. + internal abstract ProjectionPlan Plan + { + get; + } + + /// Gets or sets the payload kind for this request. + internal ODataPayloadKind PayloadKind + { + get; + set; + } + + /// + /// get an enumerable materializes the objects the response + /// + /// context + /// query components + /// Projection plan (if compiled in an earlier query). + /// contentType + /// the message + /// expected payload kind. + /// atom materializer + internal static MaterializeAtom Materialize( + ResponseInfo responseInfo, + QueryComponents queryComponents, + ProjectionPlan plan, + string contentType, + IODataResponseMessage message, + ODataPayloadKind expectedPayloadKind) + { + Debug.Assert(null != queryComponents, "querycomponents"); + Debug.Assert(null != message, "message"); + + // If there is no content (For e.g. /Customers(1)/BestFriend is null), we need to return empty results. + if (message.StatusCode == (int)HttpStatusCode.NoContent || String.IsNullOrEmpty(contentType)) + { + return MaterializeAtom.EmptyResults; + } + + return new MaterializeAtom(responseInfo, queryComponents, plan, message, expectedPayloadKind); + } + + /// + /// Creates a instance of strongly typed DataServiceRequest with the given element type. + /// + /// element type for the DataServiceRequest. + /// constructor parameter. + /// returns the strongly typed DataServiceRequest instance. + internal static DataServiceRequest GetInstance(Type elementType, Uri requestUri) + { + Type genericType = typeof(DataServiceRequest<>).MakeGenericType(elementType); + return (DataServiceRequest)Activator.CreateInstance(genericType, new object[] { requestUri }); + } + + /// + /// Ends an asynchronous request to an Internet resource. + /// + /// Element type of the result. + /// Source object of async request. + /// The data service context. + /// async method name. + /// The asyncResult being ended. + /// The response - result of the request. + internal static IEnumerable EndExecute(object source, DataServiceContext context, string method, IAsyncResult asyncResult) + { + QueryResult result = null; + try + { + result = QueryResult.EndExecuteQuery(source, method, asyncResult); + return result.ProcessResult(result.ServiceRequest.Plan); + } + catch (DataServiceQueryException ex) + { + Exception inEx = ex; + while (inEx.InnerException != null) + { + inEx = inEx.InnerException; + } + + DataServiceClientException serviceEx = inEx as DataServiceClientException; + if (context.IgnoreResourceNotFoundException && serviceEx != null && serviceEx.StatusCode == (int)HttpStatusCode.NotFound) + { + QueryOperationResponse qor = new QueryOperationResponse(ex.Response.HeaderCollection, ex.Response.Query, MaterializeAtom.EmptyResults); + qor.StatusCode = (int)HttpStatusCode.NotFound; + return (IEnumerable)qor; + } + + throw; + } + } + + /// The QueryComponents associated with this request + /// The client model. + /// instance of query components + internal abstract QueryComponents QueryComponents(ClientEdmModel model); + +#if !PORTABLELIB// Synchronous methods not available + /// + /// execute uri and materialize result + /// + /// element type + /// context + /// query components for request to execute + /// enumerable of results + internal QueryOperationResponse Execute(DataServiceContext context, QueryComponents queryComponents) + { + QueryResult result = null; + try + { + Uri requestUri = queryComponents.Uri; + DataServiceRequest serviceRequest = new DataServiceRequest(requestUri, queryComponents, this.Plan); + result = serviceRequest.CreateExecuteResult(this, context, null, null, Util.ExecuteMethodName); + result.ExecuteQuery(); + return result.ProcessResult(this.Plan); + } + catch (InvalidOperationException ex) + { + if (result != null) + { + QueryOperationResponse operationResponse = result.GetResponse(MaterializeAtom.EmptyResults); + + if (null != operationResponse) + { + if (context.IgnoreResourceNotFoundException) + { + DataServiceClientException cex = ex as DataServiceClientException; + if (cex != null && cex.StatusCode == (int)HttpStatusCode.NotFound) + { + // don't throw + return (QueryOperationResponse)operationResponse; + } + } + + operationResponse.Error = ex; + throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, operationResponse); + } + } + + throw; + } + } + + /// + /// Synchronizely get the query set count from the server by executing the $count=value query + /// + /// The context + /// The server side count of the query set + internal long GetQuerySetCount(DataServiceContext context) + { + Debug.Assert(null != context, "context is null"); + Version requestVersion = this.QueryComponents(context.Model).Version; + if (requestVersion == null) + { + requestVersion = Util.ODataVersion4; + } + + QueryResult response = null; + QueryComponents qc = this.QueryComponents(context.Model); + Uri requestUri = qc.Uri; + DataServiceRequest serviceRequest = new DataServiceRequest(requestUri, qc, null); + + HeaderCollection headers = new HeaderCollection(); + + // Validate and set the request DSV header + headers.SetRequestVersion(requestVersion, context.MaxProtocolVersionAsVersion); + context.Format.SetRequestAcceptHeaderForCount(headers); + + string httpMethod = XmlConstants.HttpMethodGet; + ODataRequestMessageWrapper request = context.CreateODataRequestMessage( + context.CreateRequestArgsAndFireBuildingRequest(httpMethod, requestUri, headers, context.HttpStack, null /*descriptor*/), + null /*descriptor*/); + + response = new QueryResult(this, Util.ExecuteMethodName, serviceRequest, request, new RequestInfo(context), null, null); + + try + { + response.ExecuteQuery(); + + if (HttpStatusCode.NoContent != response.StatusCode) + { + StreamReader sr = new StreamReader(response.GetResponseStream()); + long r = -1; + try + { + r = XmlConvert.ToInt64(sr.ReadToEnd()); + } + finally + { + sr.Close(); + } + + return r; + } + else + { + throw new DataServiceQueryException(Strings.DataServiceRequest_FailGetCount, response.Failure); + } + } + catch (InvalidOperationException ex) + { + QueryOperationResponse operationResponse = null; + operationResponse = response.GetResponse(MaterializeAtom.EmptyResults); + if (null != operationResponse) + { + operationResponse.Error = ex; + throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, operationResponse); + } + + throw; + } + } +#endif + + /// + /// Begins an asynchronous request to an Internet resource. + /// + /// source of execute (DataServiceQuery or DataServiceContext + /// context + /// The AsyncCallback delegate. + /// The state object for this request. + /// async method name. + /// An IAsyncResult that references the asynchronous request for a response. + internal IAsyncResult BeginExecute(object source, DataServiceContext context, AsyncCallback callback, object state, string method) + { + QueryResult result = this.CreateExecuteResult(source, context, callback, state, method); + result.BeginExecuteQuery(); + return result; + } + + /// + /// Creates the result object for the specified query parameters. + /// + /// The source object for the request. + /// The data service context. + /// The AsyncCallback delegate. + /// The state object for the callback. + /// async method name at the source. + /// Result representing the create request. The request has not been initiated yet. + private QueryResult CreateExecuteResult(object source, DataServiceContext context, AsyncCallback callback, object state, string method) + { + Debug.Assert(null != context, "context is null"); + + QueryComponents qc = this.QueryComponents(context.Model); + RequestInfo requestInfo = new RequestInfo(context); + + Debug.Assert( + string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0 || + string.CompareOrdinal(XmlConstants.HttpMethodGet, qc.HttpMethod) == 0 || + string.CompareOrdinal(XmlConstants.HttpMethodDelete, qc.HttpMethod) == 0, + "Only get, post and delete are supported in the execute pipeline, which should have been caught earlier"); + + if (qc.UriOperationParameters != null) + { + Debug.Assert(qc.UriOperationParameters.Any(), "qc.UriOperationParameters.Any()"); + Serializer serializer = new Serializer(requestInfo); + this.RequestUri = serializer.WriteUriOperationParametersToUri(this.RequestUri, qc.UriOperationParameters); + } + + HeaderCollection headers = new HeaderCollection(); + + if (string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0) + { + if (qc.BodyOperationParameters == null) + { + // set the content length to be 0 if there are no operation parameters. + headers.SetHeader(XmlConstants.HttpContentLength, "0"); + } + else + { + context.Format.SetRequestContentTypeForOperationParameters(headers); + } + } + + // Validate and set the request DSV and MDSV header + headers.SetRequestVersion(qc.Version, requestInfo.MaxProtocolVersionAsVersion); + + requestInfo.Format.SetRequestAcceptHeaderForQuery(headers, qc); + + // We currently do not have a descriptor to expose to the user for invoking something through Execute. Ideally we could expose an OperationDescriptor. + ODataRequestMessageWrapper requestMessage = new RequestInfo(context).WriteHelper.CreateRequestMessage(context.CreateRequestArgsAndFireBuildingRequest(qc.HttpMethod, this.RequestUri, headers, context.HttpStack, null /*descriptor*/)); + + requestMessage.FireSendingRequest2(null /*descriptor*/); + + if (qc.BodyOperationParameters != null) + { + Debug.Assert(string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0, "qc.HttpMethod == XmlConstants.HttpMethodPost"); + Debug.Assert(qc.BodyOperationParameters.Any(), "unexpected body operation parameter count of zero."); + + Serializer serializer = new Serializer(requestInfo, context.EntityParameterSendOption); + serializer.WriteBodyOperationParameters(qc.BodyOperationParameters, requestMessage); + + // pass in the request stream so that request payload can be written to the http webrequest. + return new QueryResult(source, method, this, requestMessage, requestInfo, callback, state, requestMessage.CachedRequestStream); + } + +#if PORTABLELIB + // Empty Memorystream will be set when posting null operation parameters + return string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0 + ? new QueryResult(source, method, this, requestMessage, requestInfo, callback, state, new ContentStream(new MemoryStream(), false /*isKnownMemoryStream*/)) + : new QueryResult(source, method, this, requestMessage, requestInfo, callback, state); +#else + return new QueryResult(source, method, this, requestMessage, requestInfo, callback, state); +#endif + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestArgs.cs new file mode 100644 index 0000000..82bfe57 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestArgs.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.Serialization; + + /// Represents additional metadata that is included in a request message to WCF Data Services. + public class DataServiceRequestArgs + { + /// Creates a new instance of the class. + public DataServiceRequestArgs() + { + this.HeaderCollection = new HeaderCollection(); + } + + /// Gets or sets the Accept header of the request message. + /// The value of the Accept header. + /// + /// Sets the mime type (ex. image/png) to be used when retrieving the stream. + /// Note that no validation is done on the contents of this property. + /// It is the responsibility of the user to format it correctly to be used + /// as the value of an HTTP Accept header. + /// + public string AcceptContentType + { + get + { + return this.HeaderCollection.GetHeader(XmlConstants.HttpRequestAccept); + } + + set + { + this.HeaderCollection.SetHeader(XmlConstants.HttpRequestAccept, value); + } + } + + /// Gets or sets the Content-Type header of the request message. + /// The value of the Content-Type header. + /// + /// Sets the Content-Type header to be used when sending the stream to the server. + /// Note that no validation is done on the contents of this property. + /// It is the responsibility of the user to format it correctly to be used + /// as the value of an HTTP Content-Type header. + /// + public string ContentType + { + get + { + return this.HeaderCollection.GetHeader(XmlConstants.HttpContentType); + } + + set + { + this.HeaderCollection.SetHeader(XmlConstants.HttpContentType, value); + } + } + + /// Gets or sets the value of the Slug header of the request message. + /// A value that is the Slug header of the request. + /// + /// Sets the Slug header to be used when sending the stream to the server. + /// Note that no validation is done on the contents of this property. + /// It is the responsibility of the user to format it correctly to be used + /// as the value of an HTTP Slug header. + /// + public string Slug + { + get + { + return this.HeaderCollection.GetHeader(XmlConstants.HttpSlug); + } + + set + { + this.HeaderCollection.SetHeader(XmlConstants.HttpSlug, value); + } + } + + /// Gets the headers in the request message. + /// The headers in the request message. + /// + /// Dictionary containing all the request headers to be used when retrieving the stream. + /// The user should take care so as to not alter an HTTP header which will change + /// the meaning of the request. + /// No validation is performed on the header names or values. + /// This class will not attempt to fix up any of the headers specified and + /// will try to use them "as is". + /// + public Dictionary Headers + { + get + { + // by mistake in V2 we made some public API not expose the interface, but we don't + // want the rest of the codebase to use this type, so we only cast it when absolutely + // required by the public API. + return (Dictionary)this.HeaderCollection.UnderlyingDictionary; + } + + internal set + { + this.HeaderCollection = new HeaderCollection(value); + } + } + + /// Request header collection. + internal HeaderCollection HeaderCollection + { + get; + private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestException.cs new file mode 100644 index 0000000..e17992c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestException.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Represents the error thrown if the data service returns a response code less than 200 or greater than 299, or the top-level element in the response is <error>. This class cannot be inherited. +#if !PORTABLELIB + [Serializable] +#endif + [System.Diagnostics.DebuggerDisplay("{Message}")] + public sealed class DataServiceRequestException : InvalidOperationException + { + /// Actual response object. +#if !PORTABLELIB + [NonSerialized] +#endif + private readonly DataServiceResponse response; + + #region Constructors + + /// Initializes a new instance of the class with a system-supplied message that describes the error. + public DataServiceRequestException() + : base(Strings.DataServiceException_GeneralError) + { + } + + /// Initializes a new instance of the class with a specified message that describes the error. + /// The message that describes the exception. The caller of this constructor is required to ensure that this string has been localized for the current system culture.The error message text. + public DataServiceRequestException(string message) + : base(message) + { + } + + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// The message that describes the exception. The caller of this constructor is required to ensure that this string has been localized for the current system culture. + /// The exception that is the cause of the current exception. If the parameter is not null, the current exception is raised in a catch block that handles the inner exception. + public DataServiceRequestException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// Initializes a new instance of the class. + /// Error message text. + /// Exception object that contains the inner exception. + /// object. + public DataServiceRequestException(string message, Exception innerException, DataServiceResponse response) + : base(message, innerException) + { + this.response = response; + } + +#if !PORTABLELIB +#pragma warning disable 0628 + /// + /// Initializes a new instance of the DataServiceQueryException class from the + /// specified SerializationInfo and StreamingContext instances. + /// + /// + /// A SerializationInfo containing the information required to serialize + /// the new DataServiceException. + /// + /// A StreamingContext containing the source of the serialized stream + /// associated with the new DataServiceException. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1047", Justification = "Follows serialization info pattern.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1032", Justification = "Follows serialization info pattern.")] + protected DataServiceRequestException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#pragma warning restore 0628 +#endif + + #endregion Constructors + + #region Public properties + + /// Gets the response as a object. + /// A object. + public DataServiceResponse Response + { + get { return this.response; } + } + + #endregion Public properties + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestOfT.cs new file mode 100644 index 0000000..52fd066 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceRequestOfT.cs @@ -0,0 +1,117 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Holds a Uri and type for the request. + /// + /// The type to construct for the request results + public sealed class DataServiceRequest : DataServiceRequest + { + #region Private fields + + /// The ProjectionPlan for the request (if precompiled in a previous page). + private readonly ProjectionPlan plan; + + /// Request uri for the current request. + private Uri requestUri; + + /// The QueryComponents for the request + private QueryComponents queryComponents; + + #endregion Private fields + + #region Constructors + + /// Initializes a new instance of the class. + /// The URI object that contains the request string. + public DataServiceRequest(Uri requestUri) + { + Util.CheckArgumentNull(requestUri, "requestUri"); + this.requestUri = requestUri; + } + + /// Create a request for a specific Uri + /// The URI for the request. + /// The query components for the request + /// Projection plan to reuse (possibly null). + internal DataServiceRequest(Uri requestUri, QueryComponents queryComponents, ProjectionPlan plan) + : this(requestUri) + { + Debug.Assert(requestUri != null, "requestUri != null"); + Debug.Assert(queryComponents != null, "queryComponents != null"); + + this.queryComponents = queryComponents; + this.plan = plan; + } + + #endregion Constructors + + /// Gets the type of the object used to create the instance. + /// A value that indicates the type of data returned. + public override Type ElementType + { + get { return typeof(TElement); } + } + + /// Gets the URI object that contains the request string. + /// A object that contains the request string. + public override Uri RequestUri + { + get + { + return this.requestUri; + } + + internal set + { + Debug.Assert(value != null, "RequestUri should never be set to null"); + this.requestUri = value; + } + } + + /// The ProjectionPlan for the request, if precompiled in a previous page; null otherwise. + internal override ProjectionPlan Plan + { + get + { + return this.plan; + } + } + + /// Represents the URI of the query to the data service. + /// The requested URI as a value. + public override string ToString() + { + return this.requestUri.ToString(); + } + + /// The QueryComponents associated with this request + /// The client model. + /// an instance of QueryComponents. + internal override QueryComponents QueryComponents(ClientEdmModel model) + { + if (this.queryComponents == null) + { + Type elementType = typeof(TElement); + + // for 1..* navigation properties we need the type of the entity of the collection that is being navigated to. Otherwise we use TElement. + elementType = PrimitiveType.IsKnownType(elementType) || WebUtil.IsCLRTypeCollection(elementType, model) ? elementType : TypeSystem.GetElementType(elementType); + this.queryComponents = new QueryComponents(this.requestUri, Util.ODataVersionEmpty, elementType, null, null); + } + + return this.queryComponents; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponse.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponse.cs new file mode 100644 index 0000000..d59385f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponse.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Collections.Generic; + using System.Diagnostics; + + /// + /// Data service response to ExecuteBatch & SaveChanges + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710", Justification = "required for this feature")] + public sealed class DataServiceResponse : IEnumerable + { + /// Http headers of the response. + private readonly HeaderCollection headers; + + /// Http status code of the response. + private readonly int statusCode; + + /// responses + private readonly IEnumerable response; + + /// true if this is a batch response, otherwise false. + private readonly bool batchResponse; + + /// + /// constructor + /// + /// HTTP headers + /// HTTP status code + /// list of responses + /// true if this represents a batch response, otherwise false. + internal DataServiceResponse(HeaderCollection headers, int statusCode, IEnumerable response, bool batchResponse) + { + Debug.Assert(response != null, "response!=null"); + this.headers = headers ?? new HeaderCollection(); + this.statusCode = statusCode; + this.batchResponse = batchResponse; + this.response = response; + } + + /// The headers from an HTTP response associated with a batch request. + /// An object containing the name-value pairs of an HTTP response. + public IDictionary BatchHeaders + { + get { return this.headers.UnderlyingDictionary; } + } + + /// The status code from an HTTP response associated with a batch request. + /// An integer based on status codes defined in Hypertext Transfer Protocol. + public int BatchStatusCode + { + get { return this.statusCode; } + } + + /// Gets a Boolean value that indicates whether the response contains multiple results. + /// A Boolean value that indicates whether the response contains multiple results. + public bool IsBatchResponse + { + get { return this.batchResponse; } + } + + /// Gets an enumerator that enables retrieval of responses to operations being tracked by objects within the . + /// An enumerator over the response received from the service. + public IEnumerator GetEnumerator() + { + return this.response.GetEnumerator(); + } + + /// Gets an enumerator that enables retrieval of responses to operations being tracked by objects within the . + /// An enumerator over the response received from the service. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponsePreference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponsePreference.cs new file mode 100644 index 0000000..185480a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceResponsePreference.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// Determines whether the client requests that the data service return inserted or updated entity data as an entry in the response message. + public enum DataServiceResponsePreference + { + /// default option, no Prefer header is sent. + None = 0, + + /// Prefer header with value return=representation is sent with all PUT/PATCH/POST requests to entities. + IncludeContent, + + /// Prefer header with value return=minimal is sent with all PUT/PATCH/POST requests to entities. + NoContent, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceSaveStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceSaveStream.cs new file mode 100644 index 0000000..2a25146 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceSaveStream.cs @@ -0,0 +1,98 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System.Diagnostics; + using System.IO; + using System.Runtime.Serialization; + + #endregion Namespaces + + /// Stream wrapper for MR POST/PUT which also holds the information if the stream should be closed or not. + internal class DataServiceSaveStream + { + /// Arguments for the request when POST/PUT of the stream is issued. + private DataServiceRequestArgs args; + + /// The stream we are wrapping. + /// Can be null in which case we didn't open it yet. + private Stream stream; + + /// Set to true if the stream should be closed once we're done with it. + private bool close; + +#if DEBUG + /// True,if this instance is being deserialized via DataContractSerialization, false otherwise + private bool deserializing; +#endif + + /// + /// Constructor + /// + /// The stream to use. + /// Should the stream be closed before SaveChanges returns. + /// Additional arguments to apply to the request before sending it. + internal DataServiceSaveStream(Stream stream, bool close, DataServiceRequestArgs args) + { + Debug.Assert(stream != null, "stream must not be null."); + + this.stream = stream; + this.close = close; + this.args = args; +#if DEBUG + this.deserializing = false; +#endif + } + + /// The stream to use. + internal Stream Stream + { + get + { + return this.stream; + } + } + + /// + /// Arguments to be used for creation of the HTTP request when POST/PUT for the MR is issued. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811", Justification = "The setter is called during de-serialization")] + internal DataServiceRequestArgs Args + { + get + { + return this.args; + } + + set + { +#if DEBUG + Debug.Assert(this.deserializing, "Property can only be set when this instance is deserializing"); +#endif + this.args = value; + } + } + + /// + /// Close the stream if required. + /// This is so that callers can simply call this method and don't have to care about the settings. + /// + internal void Close() + { + if (this.stream != null && this.close) + { +#if PORTABLELIB + this.stream.Dispose(); +#else + this.stream.Close(); +#endif + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamLink.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamLink.cs new file mode 100644 index 0000000..e474bf2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamLink.cs @@ -0,0 +1,141 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.ComponentModel; + using System.Diagnostics; + + /// Represents the URL of a binary resource stream. + public sealed class DataServiceStreamLink : INotifyPropertyChanged + { + /// name of the stream whose link needs to be populated. + private readonly string name; + + /// self link for the stream. + /// This should always be an absolute uri, if specified. If the payload contains an relative uri, + /// we always use the context base uri to convert this into an absolute uri. + private Uri selfLink; + + /// edit link for the stream. + /// This should always be an absolute uri, if specified. If the payload contains an relative uri, + /// we always use the context base uri to convert this into an absolute uri. + private Uri editLink; + + /// content type of the stream. + private string contentType; + + /// etag for the stream. + private string etag; + + /// + /// Internal constructor to be used by the projection plan compiler, so that we capture the ri + /// + /// name of the stream. + internal DataServiceStreamLink(string name) + { + this.name = name; + } + + /// + /// PropertyChanged Event + /// + public event PropertyChangedEventHandler PropertyChanged; + + #region Public Properties + + /// The name of the binary resource stream. + /// The name of the binary resource stream. + public string Name + { + get + { + return this.name; + } + } + + /// The URI that returns the binary resource stream. + /// The URI of the stream. + public Uri SelfLink + { + get + { + return this.selfLink; + } + + internal set + { + Debug.Assert(value == null || value.IsAbsoluteUri, "self link must be an absolute uri"); + this.selfLink = value; + this.OnPropertyChanged("SelfLink"); + } + } + + /// Gets the URI used to edit the binary resource stream. + /// The URI used to edit the stream. + public Uri EditLink + { + get + { + return this.editLink; + } + + internal set + { + Debug.Assert(value.IsAbsoluteUri, "edit link must be an absolute uri"); + this.editLink = value; + this.OnPropertyChanged("EditLink"); + } + } + + /// Gets the MIME Content-Type of the binary resource stream. + /// The Content-Type value for the stream. + public string ContentType + { + get + { + return this.contentType; + } + + internal set + { + this.contentType = value; + this.OnPropertyChanged("ContentType"); + } + } + + /// The eTag value that is used to determine concurrency for a binary resource stream. + /// The value of the eTag header for the stream. + public string ETag + { + get + { + return this.etag; + } + + internal set + { + this.etag = value; + this.OnPropertyChanged("ETag"); + } + } + + #endregion + + /// + /// One of the properties changed its value + /// + /// property name + private void OnPropertyChanged(string propertyName) + { + if ((this.PropertyChanged != null)) + { + this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamResponse.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamResponse.cs new file mode 100644 index 0000000..01bad5e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceStreamResponse.cs @@ -0,0 +1,130 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using Microsoft.OData; + + /// Represents a response from WCF Data Services that contains binary data as a stream. + public sealed class DataServiceStreamResponse : IDisposable + { + /// IODataResponseMessage containing all the response information. + private IODataResponseMessage responseMessage; + + /// Lazy initialized cached response headers. + private Dictionary headers; + + /// + /// Response stream. Caching the response stream so that IODataResponseStream.GetStream is only called once. + /// This helps us to assert that no one internally calls the GetStream method more than once. + /// + private Stream responseStream; + + /// + /// Constructor for the response. This method is internal since we don't want users to create instances + /// of this class. + /// + /// The web response to wrap. + internal DataServiceStreamResponse(IODataResponseMessage response) + { + Debug.Assert(response != null, "Can't create a stream response object from a null response."); + this.responseMessage = response; + } + + /// Gets the content type of the response stream. + /// The content type of the response stream. + /// If the Content-Type header was not present in the response this property will return null. + public string ContentType + { + get + { + this.CheckDisposed(); + return this.responseMessage.GetHeader(XmlConstants.HttpContentType); + } + } + + /// Gets the Content-Disposition header field for the response stream. + /// The contents of the Content-Disposition header field. + /// /// If the Content-Disposition header was not present in the response this property will return null. + public string ContentDisposition + { + get + { + this.CheckDisposed(); + return this.responseMessage.GetHeader(XmlConstants.HttpContentDisposition); + } + } + + /// Gets the collection of headers from the response. + /// The headers collection from the response message as a object. + public Dictionary Headers + { + get + { + this.CheckDisposed(); + if (this.headers == null) + { + // by mistake in V2 we made this public API not expose the interface, but we don't + // want the rest of the codebase to use this type, so we only cast it when absolutely + // required by the public API. + this.headers = (Dictionary)new HeaderCollection(this.responseMessage).UnderlyingDictionary; + } + + return this.headers; + } + } + + /// Gets the binary property data from the data service as a binary stream. + /// The stream that contains the binary property data. + /// When the is already disposed. + /// + /// Returns the stream obtained from the data service. When reading from this stream + /// the operations may throw if a network error occurs. This stream is read-only. + /// + /// Caller must call Dispose/Close on either the returned stream or on the response + /// object itself. Otherwise the network connection will be left open and the caller + /// might run out of available connections. + /// + public Stream Stream + { + get + { + this.CheckDisposed(); + if (this.responseStream == null) + { + this.responseStream = this.responseMessage.GetStream(); + } + + return this.responseStream; + } + } + + #region IDisposable Members + + /// Releases all resources used by the current instance of the class. + public void Dispose() + { + WebUtil.DisposeMessage(this.responseMessage); + } + + #endregion + + /// Checks if the object has already been disposed. If so it throws the ObjectDisposedException. + /// If the object has already been disposed. + private void CheckDisposed() + { + if (this.responseMessage == null) + { + Error.ThrowObjectDisposed(this.GetType()); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceTransportInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceTransportInfo.cs new file mode 100644 index 0000000..3f62809 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceTransportInfo.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Service.Client +{ + using System.Diagnostics; + + /// + /// Information required for creating the request message. + /// + internal class DataServiceTransportInfo + { + /// + /// Creates an instance of DataServiceTransportInfo. + /// + /// DataServiceContext instance + internal DataServiceTransportInfo(DataServiceContext context) + { + Debug.Assert(context != null, "context != null"); + this.Context = context; + } + + /// + /// Gets the DataServiceContext instance. + /// + public DataServiceContext Context { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceUrlKeyDelimiter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceUrlKeyDelimiter.cs new file mode 100644 index 0000000..ce3b251 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceUrlKeyDelimiter.cs @@ -0,0 +1,130 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + using ErrorStrings = Microsoft.OData.Client.Strings; + + /// + /// Component for controlling what convention set is used for generating URLs. + /// + public sealed class DataServiceUrlKeyDelimiter + { + /// Singleton instance of the parentheses delimiter. + private static readonly DataServiceUrlKeyDelimiter parenthesesDelimiter = new DataServiceUrlKeyDelimiter(enableKeyAsSegment: false); + + /// Singleton instance of the slash delimiter. + private static readonly DataServiceUrlKeyDelimiter slashDelimiter = new DataServiceUrlKeyDelimiter(enableKeyAsSegment: true); + + /// The key serializer to use. + private readonly KeySerializer keySerializer; + + /// + /// Prevents a default instance of the class from being created. + /// + /// Whether enable key as segment + private DataServiceUrlKeyDelimiter(bool enableKeyAsSegment) + { + this.keySerializer = KeySerializer.Create(enableKeyAsSegment); + } + + /// + /// An instance of which uses arentheses delimiter. Specifically, this instance will produce keys that use parentheses like "Customers('ALFKI')". + /// + public static DataServiceUrlKeyDelimiter Parentheses + { + get { return parenthesesDelimiter; } + } + + /// + /// An instance of which uses slash delimiter. Specifically, this instance will produce keys that use segments like "Customers/ALFKI". + /// + public static DataServiceUrlKeyDelimiter Slash + { + get { return slashDelimiter; } + } + + /// + /// Appends the key expression for the given entity to the given + /// + /// The entity to build the key expression from. + /// The builder to append onto. + internal void AppendKeyExpression(IEdmStructuredValue entity, StringBuilder builder) + { + Debug.Assert(entity != null, "entity != null"); + Debug.Assert(builder != null, "builder != null"); + + IEdmEntityTypeReference edmEntityTypeReference = entity.Type as IEdmEntityTypeReference; + if (edmEntityTypeReference == null || !edmEntityTypeReference.Key().Any()) + { + throw Error.Argument(ErrorStrings.Content_EntityWithoutKey, "entity"); + } + + this.AppendKeyExpression(edmEntityTypeReference.Key().ToList(), p => p.Name, p => GetPropertyValue(entity.FindPropertyValue(p.Name), entity.Type), builder); + } + + /// + /// Appends the key expression for the given entity to the given + /// + /// The type of the properties. + /// The properties of the key. + /// Delegate to get the name of a property. + /// Delegate to get the value of a property. + /// The builder to append onto. + internal void AppendKeyExpression(ICollection keyProperties, Func getPropertyName, Func getValueForProperty, StringBuilder builder) + { + Func getValueForPropertyWithNullCheck = p => + { + Debug.Assert(getValueForProperty != null, "getValueForProperty != null"); + object propertyValue = getValueForProperty(p); + if (propertyValue == null) + { + throw Error.InvalidOperation(Microsoft.OData.Client.Strings.Context_NullKeysAreNotSupported(getPropertyName(p))); + } + + return propertyValue; + }; + this.keySerializer.AppendKeyExpression(builder, keyProperties, getPropertyName, getValueForPropertyWithNullCheck); + } + + /// + /// Gets the raw CLR value for the given . + /// + /// The property to get the value for. + /// The type which declared the property. + /// The raw CLR value of the property. + private static object GetPropertyValue(IEdmPropertyValue property, IEdmTypeReference type) + { + Debug.Assert(property != null, "property != null"); + IEdmValue propertyValue = property.Value; + + // DEVNOTE: though this check is not strictly necessary, and would be caught by later checks, + // it seems worthwhile to fail fast if we can. + if (propertyValue.ValueKind == EdmValueKind.Null) + { + throw Error.InvalidOperation(ErrorStrings.Context_NullKeysAreNotSupported(property.Name)); + } + + var primitiveValue = propertyValue as IEdmPrimitiveValue; + if (primitiveValue == null) + { + throw Error.InvalidOperation(ErrorStrings.ClientType_KeysMustBeSimpleTypes(property.Name, type.FullName(), propertyValue.Type.FullName())); + } + + // DEVNOTE: This can return null, and will be handled later. The reason for this is that the client + // and server have different ways of getting property values, but both will eventually hit the same + // codepath and that is where the null is handled. + return primitiveValue.ToClrValue(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceWebException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceWebException.cs new file mode 100644 index 0000000..a400926 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DataServiceWebException.cs @@ -0,0 +1,85 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + using Microsoft.OData; + + /// + /// Class to describe errors thrown by transport layer. + /// +#if !PORTABLELIB + [Serializable] +#endif + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "No longer relevant after .NET 4 introduction of SerializeObjectState event and ISafeSerializationData interface.")] + public class DataServiceTransportException : InvalidOperationException + { + /// + /// Contains the state for this exception. + /// +#if !PORTABLELIB + [NonSerialized] +#endif + private DataServiceWebExceptionSerializationState state; + + /// + /// Constructs a new instance of DataServiceTransportException. + /// + /// ResponseMessage from the exception so that the error payload can be read. + /// Actual exception that this exception is wrapping. + public DataServiceTransportException(IODataResponseMessage response, Exception innerException) + : base(innerException.Message, innerException) + { + Util.CheckArgumentNull(innerException, "innerException"); + + this.state.ResponseMessage = response; + +#if !PORTABLELIB + this.SerializeObjectState += (sender, e) => e.AddSerializedState(this.state); +#endif + } + + /// + /// Gets the response message for this exception. + /// + public IODataResponseMessage Response + { + get { return this.state.ResponseMessage; } + } + + /// + /// Contains the state of the exception, used for serialization in security transparent code. + /// +#if !PORTABLELIB + [Serializable] +#endif + private struct DataServiceWebExceptionSerializationState +#if !PORTABLELIB + : ISafeSerializationData +#endif + { + /// + /// Gets or sets the response message for this exception. + /// + public IODataResponseMessage ResponseMessage { get; set; } + +#if !PORTABLELIB + /// + /// Called when deserialization of the exception is complete. + /// + /// The deserialized exception. + void ISafeSerializationData.CompleteDeserialization(object deserialized) + { + var exception = (DataServiceTransportException)deserialized; + exception.state = this; + } +#endif + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Descriptor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Descriptor.cs new file mode 100644 index 0000000..5d91426 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Descriptor.cs @@ -0,0 +1,139 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + + /// + /// enum to describe the descriptor kind + /// + internal enum DescriptorKind + { + /// Entity Descriptor + Entity = 0, + + /// Link Descriptor + Link, + + /// Named stream descriptor + NamedStream, + + /// Service Operation descriptor + OperationDescriptor, + } + + /// Abstract class from which is derived. + public abstract class Descriptor + { + #region Fields + + /// change order + private uint changeOrder = UInt32.MaxValue; + + /// was content generated for the entity + private bool saveContentGenerated; + + /// was this entity save result processed + /// 0 - no processed, otherwise reflects the previous state + private EntityStates saveResultProcessed; + + /// last save exception per entry + private Exception saveError; + + /// State of the modified entity or link. + private EntityStates state; + + #endregion + + /// + /// constructor + /// + /// entity state + internal Descriptor(EntityStates state) + { + this.state = state; + } + + #region Public Properties + + /// When overridden in a derived class, gets the state of the object at the time this instance was constructed. + /// An of the object returned at the time this instance was constructed. + public EntityStates State + { + get { return this.state; } + internal set { this.state = value; } + } + + #endregion + + #region Internal Properties + + /// true if resource, false if link + internal abstract DescriptorKind DescriptorKind + { + get; + } + + /// changeOrder + internal uint ChangeOrder + { + get { return this.changeOrder; } + set { this.changeOrder = value; } + } + + /// was content generated for the entity + internal bool ContentGeneratedForSave + { + get { return this.saveContentGenerated; } + set { this.saveContentGenerated = value; } + } + + /// was this entity save result processed + internal EntityStates SaveResultWasProcessed + { + get { return this.saveResultProcessed; } + set { this.saveResultProcessed = value; } + } + + /// last save exception per entry + internal Exception SaveError + { + get { return this.saveError; } + set { this.saveError = value; } + } + + /// + /// Returns true if the entry has been modified (and thus should participate in SaveChanges). + /// + internal virtual bool IsModified + { + get + { + System.Diagnostics.Debug.Assert( + (EntityStates.Added == this.state) || + (EntityStates.Modified == this.state) || + (EntityStates.Unchanged == this.state) || + (EntityStates.Deleted == this.state), + "entity state is not valid"); + + return (EntityStates.Unchanged != this.state); + } + } + + #endregion + + #region Internal Methods + + /// + /// Clear all the changes associated with this descriptor + /// This method is called when the client is done with sending all the pending requests. + /// + internal abstract void ClearChanges(); + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cd b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cd new file mode 100644 index 0000000..04c6d1e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cd @@ -0,0 +1,145 @@ + + + + + + AAAAAAAAAgAAAAQAAAAAAECAAAAAAAAAAAAEAAAAAAA= + System\Data\Services\Client\ALinq\DataServiceQueryProvider.cs + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\Evaluator.cs + + + + + + AAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\FilterQueryOptionExpression.cs + + + + + + AAAAgAAgBhAQAAAAAAAAAAAAAAAAABAAAAAAAAgAAAA= + System\Data\Services\Client\ALinq\InputBinder.cs + + + + + + AAAAAAAAAAAAAABAIAAAACAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\InputReferenceExpression.cs + + + + + + AAAAAAAAIgAAACIAAAgAQAAAAAAAAAAIAAAAAAAAAAA= + System\Data\Services\Client\ALinq\NavigationPropertySingletonExpression.cs + + + + + + AIAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\OrderByQueryOptionExpression.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\QueryOptionExpression.cs + + + + + + AAAAAAAAAAAIAAIAAAAAAAAAAAAAAAAAAAABgAgAAAI= + System\Data\Services\Client\ALinq\ReflectionUtil.cs + + + + + + AAMCgAAAAACjAIACQBIwBAAAAAgAACEgABAAAMgABEQ= + System\Data\Services\Client\ALinq\ResourceBinder.cs + + + + + + IAAAAAAAAgAAAAAAAAgAQAAAAACAAAAIAAAAQCAAAAA= + System\Data\Services\Client\ALinq\ResourceExpression.cs + + + + + + QIAABkIAAgACICJAEAiAQAAAgBEAAAAICAEAAEAAEAQ= + System\Data\Services\Client\ALinq\ResourceSetExpression.cs + + + + + + AAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\SkipQueryOptionExpression.cs + + + + + + AAAAABAAQAAAAAAACAAAQAAQABAAAAAEAAAAAAAQIAA= + System\Data\Services\Client\ALinq\TypeSystem.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAIAAAACAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\TakeQueryOptionExpression.cs + + + + + + PEIABAQAAiUECoABAAAEhAwCAIlIAYAAAAAAEAMAAAA= + System\Data\Services\Client\ALinq\UriHelper.cs + + + + + + IAAAAAAAAAYwBIAAAAAAITSAAAAECFAAABAEAAgAQIA= + System\Data\Services\Client\ALinq\UriWriter.cs + + + + + + BAAAAGIQDBACCAgAAAAAAIEACAAAIBgQABUgAIAAAAg= + c:\dev\DP_DataSvcV1_1\src\ndp\fx\src\DataWeb\Server\System\Data\Services\Parsing\WebConvert.cs + + + + + + MtxAyFrP3TX5iX6KkYqMZmSJrHXPjFW3YelGTQTRclA= + System\Data\Services\Client\ALinq\ReflectionUtil.cs + + + + + + AIAAAAAAAABAAAAABAAAAAAAAAAAAECAAAAAAkBAAAA= + System\Data\Services\Client\ALinq\ResourceExpressionType.cs + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cs new file mode 100644 index 0000000..04c6d1e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/ALinq.cs @@ -0,0 +1,145 @@ + + + + + + AAAAAAAAAgAAAAQAAAAAAECAAAAAAAAAAAAEAAAAAAA= + System\Data\Services\Client\ALinq\DataServiceQueryProvider.cs + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\Evaluator.cs + + + + + + AAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\FilterQueryOptionExpression.cs + + + + + + AAAAgAAgBhAQAAAAAAAAAAAAAAAAABAAAAAAAAgAAAA= + System\Data\Services\Client\ALinq\InputBinder.cs + + + + + + AAAAAAAAAAAAAABAIAAAACAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\InputReferenceExpression.cs + + + + + + AAAAAAAAIgAAACIAAAgAQAAAAAAAAAAIAAAAAAAAAAA= + System\Data\Services\Client\ALinq\NavigationPropertySingletonExpression.cs + + + + + + AIAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\OrderByQueryOptionExpression.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\QueryOptionExpression.cs + + + + + + AAAAAAAAAAAIAAIAAAAAAAAAAAAAAAAAAAABgAgAAAI= + System\Data\Services\Client\ALinq\ReflectionUtil.cs + + + + + + AAMCgAAAAACjAIACQBIwBAAAAAgAACEgABAAAMgABEQ= + System\Data\Services\Client\ALinq\ResourceBinder.cs + + + + + + IAAAAAAAAgAAAAAAAAgAQAAAAACAAAAIAAAAQCAAAAA= + System\Data\Services\Client\ALinq\ResourceExpression.cs + + + + + + QIAABkIAAgACICJAEAiAQAAAgBEAAAAICAEAAEAAEAQ= + System\Data\Services\Client\ALinq\ResourceSetExpression.cs + + + + + + AAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\SkipQueryOptionExpression.cs + + + + + + AAAAABAAQAAAAAAACAAAQAAQABAAAAAEAAAAAAAQIAA= + System\Data\Services\Client\ALinq\TypeSystem.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAIAAAACAAAAAAAAAAAA= + System\Data\Services\Client\ALinq\TakeQueryOptionExpression.cs + + + + + + PEIABAQAAiUECoABAAAEhAwCAIlIAYAAAAAAEAMAAAA= + System\Data\Services\Client\ALinq\UriHelper.cs + + + + + + IAAAAAAAAAYwBIAAAAAAITSAAAAECFAAABAEAAgAQIA= + System\Data\Services\Client\ALinq\UriWriter.cs + + + + + + BAAAAGIQDBACCAgAAAAAAIEACAAAIBgQABUgAIAAAAg= + c:\dev\DP_DataSvcV1_1\src\ndp\fx\src\DataWeb\Server\System\Data\Services\Parsing\WebConvert.cs + + + + + + MtxAyFrP3TX5iX6KkYqMZmSJrHXPjFW3YelGTQTRclA= + System\Data\Services\Client\ALinq\ReflectionUtil.cs + + + + + + AIAAAAAAAABAAAAABAAAAAAAAAAAAECAAAAAAkBAAAA= + System\Data\Services\Client\ALinq\ResourceExpressionType.cs + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/Materialization.cd b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/Materialization.cd new file mode 100644 index 0000000..d1f7bc0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Diagrams/Materialization.cd @@ -0,0 +1,126 @@ + + + + + + + + + AAAQAgAIAAAQAAAAAAAAAAQAAAAAABAIAAAACAACAAA= + System\Data\Services\Client\AtomContentProperty.cs + + + + + + + + + + + + + BBhgGEAAAAgAAAgCQAAAgAAgACAAAYABAAgkAQEQEEI= + System\Data\Services\Client\AtomParser.cs + + + + + + + + + + + AACAAAAAgAQgBgABBAACIAABBAAAFBAAQEACAAAAAAU= + System\Data\Services\Client\AtomEntry.cs + + + + + + + + + AAAQAAAAAAAAACAAAAAAAAQAAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\AtomFeed.cs + + + + + + + + + + + + + QgACEAAAAAAREAAIAAAgSAIgQKsAAQAJIBgFAFIQrEA= + System\Data\Services\Client\AtomMaterializer.cs + + + + + + + + + + + + + + + + BIACAAIAAAAAABAQAAggAAQAAAAIAAAAAAQAoIBAAAA= + System\Data\Services\Client\AtomMaterializerLog.cs + + + + + + + + + + + + + System\Data\Services\Client\MaterializeFromAtom.cs + + + + + RQAGQEAIAiQAQJICCACgAIgBgAAAAhEAIQAJAAIwIAA= + System\Data\Services\Client\MaterializeFromAtom.cs + + + + + + + + + + AAAAAAEAAAAAAAIAAAAAAAAgAAAAAAAAAAAAAAAAAAA= + System\Data\Services\Client\Xml\XmlAtomErrorReader.cs + + + + + + AAAAQSACBCAICSEQAEAoQAQjBAoDAQKjgBAgAACQFAA= + System\Data\Services\Client\Xml\XmlWrappingReader.cs + + + + + + + AAAYAgAgAAAAAAAAAAQAAAAAAAAACAAAAAAAAAEAAAA= + System\Data\Services\Client\AtomDataKind.cs + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DictionaryExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DictionaryExtensions.cs new file mode 100644 index 0000000..f1a5bf1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DictionaryExtensions.cs @@ -0,0 +1,122 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Collections.Concurrent; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// + /// Useful extension methods for IDictionary and ConcurrentDictionary + /// + internal static class DictionaryExtensions + { +#if PORTABLELIB && WINDOWSPHONE + /// + /// Try to add a key/value pair to the dictionary and returns true if the key was added, false if it was already present. + /// The difference with Add is that it will not throw if the key is already present. + /// + /// + /// This provides a shim for ConcurrentDictionary.TryAdd that isn't supported in Windows Phone 8.0. + /// + /// The type of the key. + /// The type of the value. + /// The dictionary to try to add the value to. + /// The key to add if not already present. + /// The value to add. + /// True if the key was added or false if the key is already present. + internal static bool TryAdd(this IDictionary dict, TKey key, TValue value) + { + try + { + TValue val; + if (!dict.TryGetValue(key, out val)) + { + dict.Add(key, value); + return true; + } + } + catch (ArgumentException) + { + } + + return false; + } +#else + /// + /// Convenience function that wraps ConcurrentDictionary.TryAdd() to allow same signature as IDictionary. + /// + /// The type of the key. + /// The type of the value. + /// The ConcurrentDictionary to which to add the item. + /// The key to use in adding the item to the ConcurrentDictionary. + /// The value to add to the ConcurrentDictionary. + public static void Add(this ConcurrentDictionary self, TKey key, TValue value) + { + if (!self.TryAdd(key, value)) + { + throw new ArgumentException("Argument_AddingDuplicate"); + } + } + + /// + /// Convenience function for ConcurrentDictionary to allow same signature as IDictionary. + /// + /// The type of the key. + /// The type of the value. + /// The concurrent dictionary from which to remove the item. + /// The key of the item to be removed. + /// + /// True, if the item is removed, otherwise False. + /// + public static bool Remove(this ConcurrentDictionary self, TKey key) + { + return ((IDictionary)self).Remove(key); + } +#endif + + /// + /// If the key exists in the dictionary, returns it. Otherwise creates a new value, adds it to the dictionary, and returns it. + /// + /// The type of the key. + /// The type of the value. + /// The dictionary to look in. + /// The key to find/add. + /// A callback to create a new value if one is not found. + /// The new or found value. + internal static TValue FindOrAdd(this IDictionary dictionary, TKey key, Func createValue) + { + Debug.Assert(dictionary != null, "dictionary != null"); + Debug.Assert(createValue != null, "createValue != null"); + + TValue value; + if (!dictionary.TryGetValue(key, out value)) + { + dictionary[key] = value = createValue(); + } + + return value; + } + + /// + /// Sets a range of values in the dictionary. A set operation is performed on each value in + /// + /// The type of the key. + /// The type of the value. + /// The dictionary to set the values in. + /// Enumerable of key-value pairs to set in . + internal static void SetRange(this IDictionary dictionary, IEnumerable> valuesToCopy) + { + foreach (var item in valuesToCopy) + { + dictionary[item.Key] = item.Value; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/DynamicProxyMethodGenerator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DynamicProxyMethodGenerator.cs new file mode 100644 index 0000000..a088d9f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/DynamicProxyMethodGenerator.cs @@ -0,0 +1,194 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; +#if !PORTABLELIB + using System.Reflection.Emit; +#endif + using System.Security; +#if !PORTABLELIB + using System.Security.Permissions; +#endif + + /// + /// Generates proxy methods for external callers to call internal methods + /// All lambda_methods are considered external. When these methods need + /// to access internal resources, a proxy must be used. Otherwise the call + /// will fail for partial trust scenario. + /// + internal class DynamicProxyMethodGenerator + { +#if !PORTABLELIB + /// + /// Dynamically generated proxy methods for external callers (lambda_method are external callers) + /// + private static Dictionary dynamicProxyMethods = new Dictionary(EqualityComparer.Default); +#endif + + /// + /// Builds an expression to best call the specified . + /// + /// The original method or constructor + /// The arguments with which to call the method. + /// An expression to call the argument method or constructor + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "'this' parameter is required when compiling for the desktop.")] + internal Expression GetCallWrapper(MethodBase method, params Expression[] arguments) + { +#if PORTABLELIB + return WrapOriginalMethodWithExpression(method, arguments); +#else + if (!this.ThisAssemblyCanCreateHostedDynamicMethodsWithSkipVisibility()) + { + return WrapOriginalMethodWithExpression(method, arguments); + } + + return GetDynamicMethodCallWrapper(method, arguments); +#endif + } + +#if !PORTABLELIB + /// + /// Determines whether this assembly has enough permissions to create + /// s that can be hosted within this assembly + /// and also skip visibility checks (access modifier checks) in order to call potentially + /// internal user types (e.g., anonymous types). + /// + /// True if this assembly has enough permissions. Otherwise, false. + protected virtual bool ThisAssemblyCanCreateHostedDynamicMethodsWithSkipVisibility() + { + return typeof(DynamicProxyMethodGenerator).Assembly.IsFullyTrusted; + } + + /// + /// Build a externally visible to call the argument method. + /// + /// The original method or constructor + /// The arguments with which to call the method. + /// An expression to call the argument method or constructor + [SecuritySafeCritical] + private static Expression GetDynamicMethodCallWrapper(MethodBase method, params Expression[] arguments) + { + if (method.DeclaringType == null || method.DeclaringType.Assembly != typeof(DynamicProxyMethodGenerator).Assembly) + { + // Security filtering: we should only accept methods that are bound to our own assembly + return WrapOriginalMethodWithExpression(method, arguments); + } + + string internalMethodName = "_dynamic_" + method.ReflectedType.Name + "_" + method.Name; + + MethodInfo mi = null; + + lock (dynamicProxyMethods) + { + dynamicProxyMethods.TryGetValue(method, out mi); + } + + if (mi != null) + { + return Expression.Call(mi, arguments); + } + else + { + Type[] parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); + MethodInfo methodInfo = method as MethodInfo; + + // Dynamic Method Signature return type: + // if method is a constructor, the return type is the reflected type + // otherwise, the return type is the method's return type + DynamicMethod dm = CreateDynamicMethod(internalMethodName, methodInfo == null ? method.ReflectedType : methodInfo.ReturnType, parameterTypes); + + ILGenerator g = dm.GetILGenerator(); + for (int i = 0; i < parameterTypes.Length; ++i) + { + switch (i) + { + case 0: + g.Emit(OpCodes.Ldarg_0); + break; + case 1: + g.Emit(OpCodes.Ldarg_1); + break; + case 2: + g.Emit(OpCodes.Ldarg_2); + break; + case 3: + g.Emit(OpCodes.Ldarg_3); + break; + default: + g.Emit(OpCodes.Ldarg, i); + break; + } + } + + if (methodInfo == null) + { + // 'method' argument must be either ConstructorInfo or MethodInfo. + // since it is not methodInfo emit the constructor call + g.Emit(OpCodes.Newobj, (ConstructorInfo)method); + } + else + { + // method call + g.EmitCall(OpCodes.Call, methodInfo, null); + } + + g.Emit(OpCodes.Ret); + + lock (dynamicProxyMethods) + { + // DEVNOTE(pqian): + // we may waste some cycles creating the dynamic method + // on multi-thread scenario, but it's better than locking this method entirely. + if (!dynamicProxyMethods.ContainsKey(method)) + { + dynamicProxyMethods.Add(method, dm); + } + } + + return Expression.Call(dm, arguments); + } + } + + /// + /// Create a new dynamic method + /// + /// the name + /// the return type + /// the parameter types + /// a new instance of dynamic method + [SecurityCritical] + [PermissionSet(SecurityAction.Assert, Unrestricted = true)] + private static DynamicMethod CreateDynamicMethod(string name, Type returnType, Type[] parameterTypes) + { + return new DynamicMethod(name, returnType, parameterTypes, typeof(DynamicProxyMethodGenerator).Module, skipVisibility: true); + } +#endif + + /// + /// Wraps the specified in an expression that invokes it. + /// + /// The method to wrap in an expression. + /// The arguments with which to invoke the . + /// An expression which invokes the with the specified . + private static Expression WrapOriginalMethodWithExpression(MethodBase method, Expression[] arguments) + { + var methodInfo = method as MethodInfo; + if (methodInfo != null) + { + return Expression.Call(methodInfo, arguments); + } + + return Expression.New((ConstructorInfo)method, arguments); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityDescriptor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityDescriptor.cs new file mode 100644 index 0000000..c85c717 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityDescriptor.cs @@ -0,0 +1,968 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + + /// + /// represents the cached entity + /// + [DebuggerDisplay("State = {state}, Uri = {editLink}, Element = {entity.GetType().ToString()}")] + public sealed class EntityDescriptor : Descriptor + { + #region Fields + /// uri to identitfy the entity + /// <atom:id>identity</id> + private Uri identity; + + /// entity + private object entity; + + /// tracks information about the default stream, if any. + private StreamDescriptor defaultStreamDescriptor; + + /// uri of the resource set to add the entity to during save + private Uri addToUri; + + /// uri to query the entity + /// <atom:link rel="self" href="queryLink" /> + private Uri selfLink; + + /// uri to edit the entity. In case of deep add, this can also refer to the navigation property name. + /// <atom:link rel="edit" href="editLink" /> + private Uri editLink; + + /// + /// Contains the LinkInfo (navigation and relationship links) for navigation properties + /// + private Dictionary relatedEntityLinks; + + /// + /// entity descriptor instance which contains metadata from responses which haven't been fully processed/materialized yet. + /// This is used only in non-batch SaveChanges scenario. + /// + private EntityDescriptor transientEntityDescriptor; + + /// List of named streams for this entity + private Dictionary streamDescriptors; + + /// List of service operation descriptors for this entity. + private List operationDescriptors; + + #endregion + + /// + /// Create a new instance of Entity descriptor. + /// + /// The client model + internal EntityDescriptor(ClientEdmModel model) + : base(EntityStates.Unchanged) + { + this.Model = model; + this.PropertiesToSerialize = new HashSet(StringComparer.Ordinal); + } + + #region Properties + + /// Gets the URI that is the identity value of the entity. + /// The property corresponds to the identity element of the entry that represents the entity in the Atom response. + public Uri Identity + { + get + { + return this.identity; + } + + internal set + { + this.identity = value; + this.ParentForInsert = null; + this.ParentPropertyForInsert = null; + this.ParentForUpdate = null; + this.ParentPropertyForUpdate = null; + this.addToUri = null; + + this.identity = value; + } + } + + /// Gets the URI that is used to return the entity resource. + /// A URI that returns the entity. + public Uri SelfLink + { + get + { + Debug.Assert(this.selfLink == null || this.selfLink.IsAbsoluteUri, "this.selfLink == null || this.selfLink.IsAbsoluteUri"); + return this.selfLink; + } + + internal set + { + this.selfLink = value; + } + } + + /// Gets the URI that modifies the entity. + /// The edit link URI for the entity resource. + public Uri EditLink + { + get + { + Debug.Assert(this.editLink == null || this.editLink.IsAbsoluteUri, "this.editLink == null || this.editLink.IsAbsoluteUri"); + return this.editLink; + } + + internal set + { + this.editLink = value; + } + } + + /// Gets the URI that accesses the binary property data of the entity. + /// A URI that accesses the binary property as a stream. + /// + /// If the entity for the box is an MLE this property stores the content source URI of the MLE. + /// That is, it stores the read URI for the associated MR. + /// Setting it to non-null marks the entity as MLE. + /// + public Uri ReadStreamUri + { + get + { + return this.defaultStreamDescriptor != null ? this.defaultStreamDescriptor.SelfLink : null; + } + + internal set + { + if (value != null) + { + this.CreateDefaultStreamDescriptor().SelfLink = value; + } + } + } + + /// Gets the URI that modifies the binary property data of the entity. + /// The property contains the edit-media link URI for the Media Resource that is associated with the entity, which is a Media Link Entry. + /// + /// If the entity for the box is an MLE this property stores the edit-media link URI. + /// That is, it stores the URI to send PUTs for the associated MR. + /// Setting it to non-null marks the entity as MLE. + /// + public Uri EditStreamUri + { + get + { + return this.defaultStreamDescriptor != null ? this.defaultStreamDescriptor.EditLink : null; + } + + internal set + { + if (value != null) + { + this.CreateDefaultStreamDescriptor().EditLink = value; + } + } + } + + /// Gets the entity that contains the update data. + /// An object that contains update data. + public object Entity + { + get + { + return this.entity; + } + + internal set + { + Debug.Assert(this.entity == null, "The entity instance must be set only once"); + this.entity = value; + + if (value != null) + { + IEdmType edmType = this.Model.GetOrCreateEdmType(value.GetType()); + ClientTypeAnnotation clientTypeAnnotation = this.Model.GetClientTypeAnnotation(edmType); + + this.EdmValue = new ClientEdmStructuredValue(value, this.Model, clientTypeAnnotation); + + if (clientTypeAnnotation.IsMediaLinkEntry) + { + this.CreateDefaultStreamDescriptor(); + } + } + } + } + + /// Gets an eTag value that indicates the state of data targeted for update since the last call to . + /// The state of data. + public string ETag { get; set; } + + /// Gets the eTag for the media resource associated with an entity that is a media link entry. + /// A string value that indicates the state of data. + public string StreamETag + { + get + { + return this.defaultStreamDescriptor != null ? this.defaultStreamDescriptor.ETag : null; + } + + internal set + { + this.CreateDefaultStreamDescriptor().ETag = value; + } + } + + /// Gets the parent entity that is related to the entity. + /// An object that is the parent entity in the relationship link. + /// This is only set for entities added through AddRelateObject call + public EntityDescriptor ParentForInsert { get; internal set; } + + /// Gets the name of the property of the entity that is a navigation property and links to the parent entity. + /// The name of the parent property. + public string ParentPropertyForInsert { get; internal set; } + + /// Gets the parent entity that is related to the entity. + /// An object that is the parent entity in the relationship link. + /// This is only set for entities updated through UpdateRelateObject call + public EntityDescriptor ParentForUpdate { get; internal set; } + + /// Gets the name of the property of the entity that is a navigation property and links to the parent entity. + /// The name of the parent property. + public string ParentPropertyForUpdate { get; internal set; } + + /// Gets the name of the type in the data source to which the entity is mapped. + /// A string that is the name of the data type. + public String ServerTypeName { get; internal set; } + + /// Returns a collection of links that are the relationships in which the entity participates. + /// A of objects that represents links in which the entity participates. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "LinkInfoCollection is cumbersome and Links isn't expressive enough")] + public ReadOnlyCollection LinkInfos + { + get + { + return this.relatedEntityLinks == null ? new ReadOnlyCollection(new List(0)) : new ReadOnlyCollection(this.relatedEntityLinks.Values.ToList()); + } + } + + /// Returns a collection of named binary data streams that belong to the entity. + /// A of objects that are the named binary data streams that belong to the entity. + public ReadOnlyCollection StreamDescriptors + { + get + { + return this.streamDescriptors == null ? new ReadOnlyCollection(new List(0)) : new ReadOnlyCollection(this.streamDescriptors.Values.ToList()); + } + } + + /// Gets a collection of operation descriptors associated with the entity. + /// A collection of operation descriptors associated with the entity. + public ReadOnlyCollection OperationDescriptors + { + get + { + return this.operationDescriptors == null ? new ReadOnlyCollection(new List()) : new ReadOnlyCollection(this.operationDescriptors); + } + } + + #endregion + + #region Internal Properties + + /// + /// Gets the client model. + /// + internal ClientEdmModel Model { get; private set; } + + /// Parent entity + internal object ParentEntity + { + get + { + return this.ParentEntityDescriptor != null ? this.ParentEntityDescriptor.entity : null; + } + } + + /// Parent entity descriptor + internal EntityDescriptor ParentEntityDescriptor + { + get { return this.ParentForInsert ?? this.ParentForUpdate; } + } + + /// Gets the name of the property of the entity that is a navigation property and links to the parent entity. + internal string ParentProperty + { + get { return !string.IsNullOrEmpty(this.ParentPropertyForInsert) ? this.ParentPropertyForInsert : (!string.IsNullOrEmpty(this.ParentPropertyForUpdate) ? this.ParentPropertyForUpdate : null); } + } + + /// this is a entity + internal override DescriptorKind DescriptorKind + { + get { return DescriptorKind.Entity; } + } + + /// + /// Returns true if the resource was inserted via its parent. E.g. POST customer(0)/Orders + /// + internal bool IsDeepInsert + { + get { return this.ParentForInsert != null; } + } + + /// + /// The stream which contains the new content for the MR associated with this MLE. + /// This stream is used during SaveChanges to POST/PUT the MR. + /// Setting it to non-null marks the entity as MLE. + /// + internal DataServiceSaveStream SaveStream + { + get + { + return this.defaultStreamDescriptor != null ? this.defaultStreamDescriptor.SaveStream : null; + } + + set + { + Debug.Assert(value != null, "SaveStream should never be set to null"); + this.CreateDefaultStreamDescriptor().SaveStream = value; + } + } + + /// + /// Describes whether the SaveStream is for Insert or Update. + /// The value NoStream is for both non-MLEs and MLEs with unmodified stream. + /// + internal EntityStates StreamState + { + get + { + return this.defaultStreamDescriptor != null ? this.defaultStreamDescriptor.State : EntityStates.Unchanged; + } + + set + { + Debug.Assert(this.defaultStreamDescriptor != null, "this.defaultStreamDescriptor != null"); + this.defaultStreamDescriptor.State = value; + } + } + + /// + /// Returns true if we know that the entity is MLE. Note that this does not include the information + /// from the entity type. So if the entity was attributed with HasStream for example + /// this boolean might not be aware of it. + /// + internal bool IsMediaLinkEntry + { + get { return this.defaultStreamDescriptor != null; } + } + + /// + /// Returns true if the entry has been modified (and thus should participate in SaveChanges). + /// + internal override bool IsModified + { + get + { + if (base.IsModified) + { + return true; + } + else + { + // If the entity is not modified but it does have a save stream associated with it + // it means that the MR for the MLE should be updated and thus we need to consider + // the entity as modified (so that it shows up during SaveChanges) + return this.defaultStreamDescriptor != null && this.defaultStreamDescriptor.SaveStream != null; + } + } + } + + /// + /// entity descriptor instance containing metadata from responses, which hasn't been fully processed yet. + /// + internal EntityDescriptor TransientEntityDescriptor + { + get + { + return this.transientEntityDescriptor; + } + + set + { + Debug.Assert(value != null, "should never try to set null transient entity descriptors"); + Debug.Assert(value.Entity == null, "for transient entity descriptors, entity must be null."); + + if (this.transientEntityDescriptor == null) + { + this.transientEntityDescriptor = value; + } + else + { + // we should merge the data always, as we do in the query case. There might be servers, who might send partial data back. + AtomMaterializerLog.MergeEntityDescriptorInfo(this.transientEntityDescriptor, value, true /*mergeInfo*/, MergeOption.OverwriteChanges); + } + + // During save changes call, BaseSaveResult.ChangeEntries contains the list of descriptors which are changed. + // Since named streams changes show up in this list as StreamDescriptor, we need to update the StreamDescriptor + // instance to have their individual transient StreamDescriptor. + if (value.streamDescriptors != null && this.streamDescriptors != null) + { + foreach (StreamDescriptor transientStreamInfo in value.streamDescriptors.Values) + { + StreamDescriptor existingStreamInfo; + if (this.streamDescriptors.TryGetValue(transientStreamInfo.Name, out existingStreamInfo)) + { + existingStreamInfo.TransientNamedStreamInfo = transientStreamInfo; + } + } + } + } + } + + /// + /// Returns the stream descriptor for the default stream associated with this entity. + /// If this entity is not an MLE, then returns null; + /// + internal StreamDescriptor DefaultStreamDescriptor + { + get { return this.defaultStreamDescriptor; } + } + + /// + /// Gets the edm structured value associated with this entity. + /// + internal IEdmStructuredValue EdmValue { get; private set; } + + /// + /// The entity set name provided in either AttachTo or AddObject. + /// + internal string EntitySetName { get; set; } + + /// + /// The hash set contains names of changed properties in this entity. + /// + internal HashSet PropertiesToSerialize { get; set; } + + #endregion + + #region Internal Methods + + /// + /// returns the most recent identity of the entity + /// + /// the identity of the entity, as returned in the latest response. + internal Uri GetLatestIdentity() + { + if (this.TransientEntityDescriptor != null && this.TransientEntityDescriptor.Identity != null) + { + return this.TransientEntityDescriptor.Identity; + } + + return this.Identity; + } + + /// return the most recent edit link for the entity + /// the uri to edit the entity associated with the entity descriptor. + internal Uri GetLatestEditLink() + { + if (this.TransientEntityDescriptor != null && this.TransientEntityDescriptor.EditLink != null) + { + return this.TransientEntityDescriptor.EditLink; + } + + return this.EditLink; + } + + /// return the most recent edit link for the MR associated with the entity + /// the uri to edit the MR associated with the entity descriptor. + internal Uri GetLatestEditStreamUri() + { + if (this.TransientEntityDescriptor != null && this.TransientEntityDescriptor.EditStreamUri != null) + { + return this.TransientEntityDescriptor.EditStreamUri; + } + + return this.EditStreamUri; + } + + /// return the most recent etag for the entity + /// etag for the entity associated with the entity descriptor. + internal string GetLatestETag() + { + if (this.TransientEntityDescriptor != null && !String.IsNullOrEmpty(this.TransientEntityDescriptor.ETag)) + { + return this.TransientEntityDescriptor.ETag; + } + + return this.ETag; + } + + /// return the most return etag for the MR associated with the entity + /// etag for the MR associated with the entity descriptor. + internal string GetLatestStreamETag() + { + if (this.TransientEntityDescriptor != null && !String.IsNullOrEmpty(this.TransientEntityDescriptor.StreamETag)) + { + return this.TransientEntityDescriptor.StreamETag; + } + + return this.StreamETag; + } + + /// return the most recent type name of the entity as returned in the response payload. + /// the type name of the entity as returned in the response payload. + internal string GetLatestServerTypeName() + { + if (this.TransientEntityDescriptor != null && !String.IsNullOrEmpty(this.TransientEntityDescriptor.ServerTypeName)) + { + return this.TransientEntityDescriptor.ServerTypeName; + } + + return this.ServerTypeName; + } + + /// uri to edit the entity + /// retrieves the baseUri to use for a given entity set. + /// whether to return the query link or edit link + /// absolute uri which can be used to edit the entity + internal Uri GetResourceUri(UriResolver baseUriResolver, bool queryLink) + { + // If the entity was inserted using the AddRelatedObject API + if (this.ParentEntityDescriptor != null) + { + // This is the batch scenario, where the entity might not have been saved yet, and there is another operation + // (for e.g. PUT $1/links/BestFriend or something). Hence we need to generate a Uri with the changeorder number. + if (this.ParentEntityDescriptor.Identity == null) + { + Uri relativeReferenceUri = UriUtil.CreateUri("$" + this.ParentEntityDescriptor.ChangeOrder.ToString(CultureInfo.InvariantCulture), UriKind.Relative); + Uri absoluteReferenceUri = baseUriResolver.GetOrCreateAbsoluteUri(relativeReferenceUri); + Uri requestUri = UriUtil.CreateUri(this.ParentProperty, UriKind.Relative); + return UriUtil.CreateUri(absoluteReferenceUri, requestUri); + } + else + { + Debug.Assert(this.ParentEntityDescriptor.ParentEntityDescriptor == null, "This code assumes that parentChild relationships will only ever be one level deep"); + Debug.Assert(this.ParentProperty != null, "ParentProperty != null"); + LinkInfo linkInfo; + if (this.ParentEntityDescriptor.TryGetLinkInfo(this.ParentProperty, out linkInfo)) + { + if (linkInfo.NavigationLink != null) + { + return linkInfo.NavigationLink; + } + } + + return UriUtil.CreateUri(this.ParentEntityDescriptor.GetLink(queryLink), this.GetLink(queryLink)); + } + } + else + { + return this.GetLink(queryLink); + } + } + + /// is the entity the same as the source or target entity + /// related end + /// true if same as source or target entity + internal bool IsRelatedEntity(LinkDescriptor related) + { + return ((this.entity == related.Source) || (this.entity == related.Target)); + } + + /// + /// Return the related end for this resource. One should call this method, only if the resource is inserted via deep resource. + /// + /// returns the related end via which the resource was inserted. + internal LinkDescriptor GetRelatedEnd() + { + Debug.Assert(this.IsDeepInsert, "For related end, this must be a deep insert"); + Debug.Assert(this.Identity == null, "If the identity is set, it means that the edit link no longer has the property name"); + + return new LinkDescriptor(this.ParentForInsert.entity, this.ParentPropertyForInsert, this.entity, this.Model); + } + + /// + /// clears all the changes - like closes the save stream, clears the transient entity descriptor. + /// This method is called when the client is done with sending all the pending requests. + /// + internal override void ClearChanges() + { + this.transientEntityDescriptor = null; + this.CloseSaveStream(); + } + + /// + /// Closes the save stream if there's any and sets it to null + /// + internal void CloseSaveStream() + { + if (this.defaultStreamDescriptor != null) + { + this.defaultStreamDescriptor.CloseSaveStream(); + } + } + + /// + /// Add the given navigation link to the entity descriptor + /// + /// name of the navigation property via which this entity is related to the other end. + /// uri that can be used to navigate from this entity to the other end. + internal void AddNestedResourceInfo(string propertyName, Uri navigationUri) + { + LinkInfo linkInfo = this.GetLinkInfo(propertyName); + + // There are scenarios where we need to overwrite an existing link (when someone tries to refresh the object) + linkInfo.NavigationLink = navigationUri; + } + + /// + /// Add the given association link to the entity descriptor + /// + /// name of the navigation property via which this entity is related to the other end. + /// uri that can be used to navigate associations for this property. + internal void AddAssociationLink(string propertyName, Uri associationUri) + { + LinkInfo linkInfo = this.GetLinkInfo(propertyName); + + // There are scenarios where we need to overwrite an existing link (when someone tries to refresh the object) + linkInfo.AssociationLink = associationUri; + } + + /// + /// Merges the given linkInfo to the entity descriptor, + /// overwrites existing links with new ones (coming from the payload) + /// + /// linkInfo + internal void MergeLinkInfo(LinkInfo linkInfo) + { + if (this.relatedEntityLinks == null) + { + this.relatedEntityLinks = new Dictionary(StringComparer.Ordinal); + } + + LinkInfo existingLinkInfo = null; + if (!this.relatedEntityLinks.TryGetValue(linkInfo.Name, out existingLinkInfo)) + { + this.relatedEntityLinks[linkInfo.Name] = linkInfo; + } + else + { + // overwrite existing links with new ones (coming from the payload). + if (linkInfo.AssociationLink != null) + { + existingLinkInfo.AssociationLink = linkInfo.AssociationLink; + } + + if (linkInfo.NavigationLink != null) + { + existingLinkInfo.NavigationLink = linkInfo.NavigationLink; + } + } + } + + /// + /// Try and get the navigation link. If the navigation link is not specified, then its used the self link of the entity and + /// appends the property name. + /// + /// retrieves the appropriate baseUri for a given entitySet. + /// ClientProperty instance representing the navigation property. + /// returns the uri for the given link. If the link is not present, its uses the self link of the current entity and appends the navigation property name. + internal Uri GetNestedResourceInfo(UriResolver baseUriResolver, ClientPropertyAnnotation property) + { + LinkInfo linkInfo = null; + Uri uri = null; + if (this.TryGetLinkInfo(property.PropertyName, out linkInfo)) + { + uri = linkInfo.NavigationLink; + } + + if (uri == null) + { + Uri relativeUri = UriUtil.CreateUri(property.PropertyName, UriKind.Relative); + uri = UriUtil.CreateUri(this.GetResourceUri(baseUriResolver, true /*queryLink*/), relativeUri); + } + + return uri; + } + + /// + /// Returns the LinkInfo for the given navigation property. + /// + /// name of the navigation property + /// LinkInfo for the navigation propery + /// true if LinkInfo is found for the navigation property, false if not found + internal bool TryGetLinkInfo(string propertyName, out LinkInfo linkInfo) + { + Util.CheckArgumentNullAndEmpty(propertyName, "propertyName"); + Debug.Assert(propertyName.IndexOf('/') == -1, "propertyName.IndexOf('/') == -1"); + + linkInfo = null; + if (this.TransientEntityDescriptor != null && this.TransientEntityDescriptor.TryGetLinkInfo(propertyName, out linkInfo)) + { + return true; + } + else if (this.relatedEntityLinks != null) + { + return this.relatedEntityLinks.TryGetValue(propertyName, out linkInfo); + } + + return false; + } + + /// + /// Check if there is a stream with this name. If yes, returns the information about that stream, otherwise add a streams with the given name. + /// + /// name of the stream. + /// an existing or new namedstreaminfo instance with the given name. + internal StreamDescriptor AddStreamInfoIfNotPresent(string name) + { + StreamDescriptor namedStreamInfo; + if (this.streamDescriptors == null) + { + this.streamDescriptors = new Dictionary(StringComparer.Ordinal); + } + + if (!this.streamDescriptors.TryGetValue(name, out namedStreamInfo)) + { + namedStreamInfo = new StreamDescriptor(name, this); + this.streamDescriptors.Add(name, namedStreamInfo); + } + + return namedStreamInfo; + } + + /// + /// Adds an operation descriptor to the list of operation descriptors. + /// + /// the operation descriptor to add. + internal void AddOperationDescriptor(OperationDescriptor operationDescriptor) + { + Debug.Assert(operationDescriptor != null, "operationDescriptor != null"); + + if (this.operationDescriptors == null) + { + this.operationDescriptors = new List(); + } + + // The protocol allows multiple descriptors with the same rel, so we don't check for duplicate entries here. + this.operationDescriptors.Add(operationDescriptor); + } + + /// + /// Clears all operator descriptors + /// + internal void ClearOperationDescriptors() + { + if (this.operationDescriptors != null) + { + this.operationDescriptors.Clear(); + } + } + + /// + /// Appends OperationDescriptors to the existing list of OperationDescriptors + /// + /// List containing OperationDescriptors to add for this entityDescriptor + internal void AppendOperationalDescriptors(IEnumerable descriptors) + { + if (this.operationDescriptors == null) + { + this.operationDescriptors = new List(); + } + + this.operationDescriptors.AddRange(descriptors); + } + + /// + /// Gets the stream info with the given name. + /// + /// name of the stream. + /// information about the stream with the given name. + /// true if there is a stream with the given name, otherwise returns false. + internal bool TryGetNamedStreamInfo(string name, out StreamDescriptor namedStreamInfo) + { + namedStreamInfo = null; + + if (this.streamDescriptors != null) + { + return this.streamDescriptors.TryGetValue(name, out namedStreamInfo); + } + + return false; + } + + /// + /// Merges the given named stream info object. + /// If the stream descriptor is already present, then this method merges the info from the given stream descriptor + /// into the existing one, otherwise justs add this given stream descriptor to the list of stream descriptors for + /// this entity. + /// + /// namedStreamInfo instance containing information about the stream. + internal void MergeStreamDescriptor(StreamDescriptor materializedStreamDescriptor) + { + if (this.streamDescriptors == null) + { + this.streamDescriptors = new Dictionary(StringComparer.Ordinal); + } + + StreamDescriptor existingStreamDescriptor = null; + if (!this.streamDescriptors.TryGetValue(materializedStreamDescriptor.Name, out existingStreamDescriptor)) + { + this.streamDescriptors[materializedStreamDescriptor.Name] = materializedStreamDescriptor; + materializedStreamDescriptor.EntityDescriptor = this; + } + else + { + StreamDescriptor.MergeStreamDescriptor(existingStreamDescriptor, materializedStreamDescriptor); + Debug.Assert(ReferenceEquals(existingStreamDescriptor.EntityDescriptor, this), "All stream descriptors that are already tracked by the entity must point to the same entity descriptor instance"); + } + } + + /// + /// Sets up the descriptor's parent descriptor and parent property. Only valid if the descriptor is in the Added state. + /// If the property ParentForUpdate and ParentPropertyForUpdate of descriptor has already been set, this method will also set value for these two properties to null. + /// + /// The parent descriptor. + /// The property for insert. + internal void SetParentForInsert(EntityDescriptor parentDescriptor, string propertyForInsert) + { + Debug.Assert(parentDescriptor != null, "parentDescriptor != null"); + Debug.Assert(!String.IsNullOrEmpty(propertyForInsert), "!string.IsNullOrEmpty(propertyForInsert)"); + Debug.Assert(this.State == EntityStates.Added, "State == EntityStates.Added"); + + this.ParentForInsert = parentDescriptor; + this.ParentPropertyForInsert = propertyForInsert; + this.ParentForUpdate = null; + this.ParentPropertyForUpdate = null; + } + + /// + /// Sets up the descriptor's parent descriptor and parent property for update. Only valid if the descriptor is in the Modified state. + /// If the property ParentForInsert and ParentPropertyForInsert of descriptor has already been set, this method will also set value for these two properties to null. + /// + /// The parent descriptor. + /// The property for update. + internal void SetParentForUpdate(EntityDescriptor parentDescriptor, string propertyForUpdate) + { + Debug.Assert(parentDescriptor != null, "parentDescriptor != null"); + Debug.Assert(!String.IsNullOrEmpty(propertyForUpdate), "!string.IsNullOrEmpty(propertyForUpdate)"); + Debug.Assert(this.State == EntityStates.Modified, "State == EntityStates.Modified"); + + this.ParentForUpdate = parentDescriptor; + this.ParentPropertyForUpdate = propertyForUpdate; + this.ParentForInsert = null; + this.ParentPropertyForInsert = null; + } + + /// + /// Sets the entity set URI to use for inserting the entity tracked by this descriptor. Only valid if the descriptor is in the added state. + /// + /// The entity set insert URI. + internal void SetEntitySetUriForInsert(Uri entitySetInsertUri) + { + Debug.Assert(entitySetInsertUri != null, "entitySetInsertUri != null"); + Debug.Assert(this.State == EntityStates.Added, "State == EntityStates.Added"); + + this.addToUri = entitySetInsertUri; + } + + #endregion Internal Methods + + #region Private Methods + + /// + /// Returns LinkInfo for the given property, if it does not exists than a new one is created. + /// + /// name of the navigation property + /// LinkInfo for propertyName + private LinkInfo GetLinkInfo(String propertyName) + { + if (this.relatedEntityLinks == null) + { + this.relatedEntityLinks = new Dictionary(StringComparer.Ordinal); + } + + LinkInfo linkInfo = null; + if (!this.relatedEntityLinks.TryGetValue(propertyName, out linkInfo)) + { + linkInfo = new LinkInfo(propertyName); + this.relatedEntityLinks[propertyName] = linkInfo; + } + + return linkInfo; + } + + /// + /// In V1, we used to not support self links. Hence we used to use edit links as self links. + /// IN V2, we are adding support for self links. But if there are not specified, we need to + /// fall back on the edit link. + /// + /// whether to get query link or the edit link. + /// the query or the edit link, as specified in the parameter. + private Uri GetLink(bool queryLink) + { + // If asked for a self link and self-link is present, return self link + Uri link; + if (queryLink && this.SelfLink != null) + { + return this.SelfLink; + } + + // otherwise return edit link if present. + if ((link = this.GetLatestEditLink()) != null) + { + return link; + } + + if (this.State != EntityStates.Added) + { + throw new ArgumentNullException(Strings.EntityDescriptor_MissingSelfEditLink(this.identity)); + } + else + { + Debug.Assert(this.TransientEntityDescriptor == null, "The transient entity container must be null, when the entity is in added state"); + + // If the entity is in added state, and either the parent property or the addToUri must be non-null + Debug.Assert(this.addToUri != null || !String.IsNullOrEmpty(this.ParentPropertyForInsert), "For entities in added state, parentProperty or addToUri must be specified"); + if (this.addToUri != null) + { + return this.addToUri; + } + else + { + return UriUtil.CreateUri(this.ParentPropertyForInsert, UriKind.Relative); + } + } + } + + /// + /// Creates a default stream descriptor, if there is none yet, and returns it. + /// If there is one already present, then returns the current instance. + /// + /// stream descriptor representing the default stream. + private StreamDescriptor CreateDefaultStreamDescriptor() + { + if (this.defaultStreamDescriptor == null) + { + this.defaultStreamDescriptor = new StreamDescriptor(this); + } + + return this.defaultStreamDescriptor; + } + + #endregion Private Methods + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityParameterSendOption.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityParameterSendOption.cs new file mode 100644 index 0000000..26acbc5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityParameterSendOption.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// + /// Used to specify a strategy to send entity parameter. + /// + public enum EntityParameterSendOption + { + /// + /// Send full properties of an entity parameter to service. + /// + SendFullProperties = 0, + + /// + /// Send only set properties of an entity parameter to service. + /// + SendOnlySetProperties = 1, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityStates.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityStates.cs new file mode 100644 index 0000000..4f0b30e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityStates.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// + /// Describes the insert/update/delete state of an entity or link. + /// + /// + /// Deleting an inserted resource will detach it. + /// After SaveChanges, deleted resources will become detached and Added & Modified resources will become unchanged. + /// + [System.Flags()] + public enum EntityStates + { + /// + /// The resource is not tracked by the context. + /// + Detached = 0x00000001, + + /// + /// The resource is tracked by a context with no changes. + /// + Unchanged = 0x00000002, + + /// + /// The resource is tracked by a context for insert. + /// + Added = 0x00000004, + + /// + /// The resource is tracked by a context for deletion. + /// + Deleted = 0x00000008, + + /// + /// The resource is tracked by a context for update. + /// + Modified = 0x00000010 + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTracker.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTracker.cs new file mode 100644 index 0000000..88dfdfc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTracker.cs @@ -0,0 +1,580 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Collections.Concurrent; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Runtime.Serialization; + using Microsoft.OData.Client.Metadata; + #endregion Namespaces + + /// + /// context + /// + public class EntityTracker : EntityTrackerBase + { + /// Storage for the client model. + private readonly ClientEdmModel model; + + #region Resource state management + + #if PORTABLELIB && WINDOWSPHONE + /// Set of tracked resources + private Dictionary entityDescriptors = new Dictionary(EqualityComparer.Default); + #else + /// Set of tracked resources + private ConcurrentDictionary entityDescriptors = new ConcurrentDictionary(EqualityComparer.Default); + #endif + #if PORTABLELIB && WINDOWSPHONE + /// Set of tracked resources by Identity + private Dictionary identityToDescriptor; + #else + /// Set of tracked resources by Identity + private ConcurrentDictionary identityToDescriptor; + #endif + + #if PORTABLELIB && WINDOWSPHONE + /// Set of tracked bindings + private Dictionary bindings; + #else + /// Set of tracked bindings + private ConcurrentDictionary bindings; + #endif + + /// change order + private uint nextChange; + #endregion + + #region ctor + + /// + /// Creates a new instance of EntityTracker class which tracks all instances of entities and links tracked by the context. + /// + /// max protocol version that the client understands. + internal EntityTracker(ClientEdmModel maxProtocolVersion) + { + this.model = maxProtocolVersion; + } + + #endregion + + #region Properties + + /// + /// Returns a collection of all the links (ie. associations) currently being tracked by the context. + /// If no links are being tracked, a collection with 0 elements is returned. + /// + public IEnumerable Links + { + get + { + this.EnsureLinkBindings(); + return this.bindings.Values; + } + } + + /// + /// Returns a collection of all the resources currently being tracked by the context. + /// If no resources are being tracked, a collection with 0 elements is returned. + /// + public IEnumerable Entities + { + get + { + return this.entityDescriptors.Values; + } + } + #endregion + + #region Entity Methods + + /// Gets the entity descriptor corresponding to a particular entity + /// Entity for which to find the entity descriptor + /// EntityDescriptor for the or null if not found + public EntityDescriptor TryGetEntityDescriptor(object entity) + { + Debug.Assert(entity != null, "entity != null"); + EntityDescriptor entityDescriptor = null; + this.entityDescriptors.TryGetValue(entity, out entityDescriptor); + return entityDescriptor; + } + + /// + /// verify the resource being tracked by context + /// + /// resource + /// The given resource. + /// if resource is not contained + internal override EntityDescriptor GetEntityDescriptor(object resource) + { + EntityDescriptor entityDescriptor = this.TryGetEntityDescriptor(resource); + if (entityDescriptor == null) + { + throw Error.InvalidOperation(Strings.Context_EntityNotContained); + } + + return entityDescriptor; + } + + /// + /// checks whether there is a tracked entity with the given identity. + /// + /// identity of the entity. + /// returns the entity if the identity matches, otherwise returns null. + internal EntityDescriptor TryGetEntityDescriptor(Uri identity) + { + EntityDescriptor entityDescriptor; + if (this.identityToDescriptor != null && + this.identityToDescriptor.TryGetValue(identity, out entityDescriptor)) + { + return entityDescriptor; + } + + return null; + } + + /// + /// Adds the given entity descriptors to the list of the tracked entity descriptors. + /// + /// entity descriptor instance to be added. + internal void AddEntityDescriptor(EntityDescriptor descriptor) + { + try + { + this.entityDescriptors.Add(descriptor.Entity, descriptor); + } + catch (ArgumentException) + { + throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained); + } + } + + /// the work to detach a resource + /// resource to detach + /// true if detached + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "flag", Justification = "Local variable used in debug assertion.")] + internal bool DetachResource(EntityDescriptor resource) + { + this.EnsureLinkBindings(); + + // Since we are changing the list on the fly, we need to convert it into a list first + // so that enumeration won't get effected. + foreach (LinkDescriptor end in this.bindings.Values.Where(resource.IsRelatedEntity).ToList()) + { + this.DetachExistingLink( + end, + end.Target == resource.Entity && resource.State == EntityStates.Added); + } + + resource.ChangeOrder = UInt32.MaxValue; + resource.State = EntityStates.Detached; + bool flag = this.entityDescriptors.Remove(resource.Entity); + Debug.Assert(flag, "should have removed existing entity"); + this.DetachResourceIdentity(resource); + + return true; + } + + /// remove the identity attached to the resource + /// resource with an identity to detach to detach + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "removed", Justification = "Local variable used in debug assertion.")] + internal void DetachResourceIdentity(EntityDescriptor resource) + { + EntityDescriptor existing = null; + if ((null != resource.Identity) && + this.identityToDescriptor.TryGetValue(resource.Identity, out existing) && + Object.ReferenceEquals(existing, resource)) + { + bool removed = this.identityToDescriptor.Remove(resource.Identity); + Debug.Assert(removed, "should have removed existing identity"); + } + } + + #endregion Entity Methods + + #region Link Methods + /// + /// Gets the link descriptor corresponding to a particular link b/w source and target objects + /// + /// Source entity + /// Property of + /// Target entity + /// LinkDescriptor for the relationship b/w source and target entities or null if not found + internal LinkDescriptor TryGetLinkDescriptor(object source, string sourceProperty, object target) + { + Debug.Assert(source != null, "source != null"); + Debug.Assert(sourceProperty != null, "sourceProperty != null"); + + this.EnsureLinkBindings(); + + LinkDescriptor link; + this.bindings.TryGetValue(new LinkDescriptor(source, sourceProperty, target, this.model), out link); + return link; + } + + /// + /// attach the link with the given source, sourceProperty and target. + /// + /// source entity of the link. + /// name of the property on the source entity. + /// target entity of the link. + /// merge option to be used to merge the link if there is an existing link. + internal override void AttachLink(object source, string sourceProperty, object target, MergeOption linkMerge) + { + LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target, this.model); + LinkDescriptor existing = this.TryGetLinkDescriptor(source, sourceProperty, target); + if (existing != null) + { + switch (linkMerge) + { + case MergeOption.AppendOnly: + break; + + case MergeOption.OverwriteChanges: + relation = existing; + break; + + case MergeOption.PreserveChanges: + if ((EntityStates.Added == existing.State) || + (EntityStates.Unchanged == existing.State) || + (EntityStates.Modified == existing.State && null != existing.Target)) + { + relation = existing; + } + + break; + + case MergeOption.NoTracking: // public API point should throw if link exists + throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained); + } + } + else + { + if (this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(source.GetType())).GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException).IsEntityCollection || + (null == (existing = this.DetachReferenceLink(source, sourceProperty, target, linkMerge)))) + { + this.AddLink(relation); + this.IncrementChange(relation); + } + else if (!((MergeOption.AppendOnly == linkMerge) || + (MergeOption.PreserveChanges == linkMerge && EntityStates.Modified == existing.State))) + { + // AppendOnly doesn't change state or target + // OverWriteChanges changes target and state + // PreserveChanges changes target if unchanged, leaves modified target and state alone + relation = existing; + } + } + + relation.State = EntityStates.Unchanged; + } + + /// + /// find and detach link for reference property + /// + /// source entity + /// source entity property name for target entity + /// target entity + /// link merge option + /// true if found and not removed + internal LinkDescriptor DetachReferenceLink(object source, string sourceProperty, object target, MergeOption linkMerge) + { + Debug.Assert(sourceProperty.IndexOf('/') == -1, "sourceProperty.IndexOf('/') == -1"); + + LinkDescriptor existing = this.GetLinks(source, sourceProperty).FirstOrDefault(); + if (null != existing) + { + if ((target == existing.Target) || + (MergeOption.AppendOnly == linkMerge) || + (MergeOption.PreserveChanges == linkMerge && EntityStates.Modified == existing.State)) + { + return existing; + } + + // Since we don't support deep insert on reference property, no need to check for deep insert. + this.DetachExistingLink(existing, false); + Debug.Assert(!this.Links.Any(o => (o.Source == source) && (o.SourceProperty == sourceProperty)), "only expecting one"); + } + + return null; + } + + /// + /// Add the given link to the link descriptor collection + /// + /// link descriptor to add + /// throws argument exception if the link already exists + internal void AddLink(LinkDescriptor linkDescriptor) + { + Debug.Assert(linkDescriptor != null, "linkDescriptor != null"); + try + { + this.EnsureLinkBindings(); + this.bindings.Add(linkDescriptor, linkDescriptor); + } + catch (ArgumentException) + { + throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained); + } + } + + /// + /// Remove the link from the list of tracked link descriptors. + /// + /// link to be removed. + /// true if the link was tracked and now removed, otherwise returns false. + internal bool TryRemoveLinkDescriptor(LinkDescriptor linkDescriptor) + { + this.EnsureLinkBindings(); + return this.bindings.Remove(linkDescriptor); + } + + /// + /// get the related links ignoring target entity + /// + /// source entity + /// source entity's property + /// enumerable of related ends + internal override IEnumerable GetLinks(object source, string sourceProperty) + { + this.EnsureLinkBindings(); + Debug.Assert(sourceProperty.IndexOf('/') == -1, "sourceProperty.IndexOf('/') == -1"); + return this.bindings.Values.Where(o => (o.Source == source) && (o.SourceProperty == sourceProperty)); + } + + /// Detach existing link + /// link to detach + /// true if target is being deleted, false otherwise + internal override void DetachExistingLink(LinkDescriptor existingLink, bool targetDelete) + { + // The target can be null in which case we don't need this check + if (existingLink.Target != null) + { + // Identify the target resource for the link + EntityDescriptor targetResource = this.GetEntityDescriptor(existingLink.Target); + + // Check if there is a dependency relationship b/w the source and target objects i.e. target can not exist without source link + // Deep insert requires this check to be made but skip the check if the target object is being deleted + if (targetResource.IsDeepInsert && !targetDelete) + { + EntityDescriptor parentOfTarget = targetResource.ParentForInsert; + if (Object.ReferenceEquals(targetResource.ParentEntity, existingLink.Source) && + (parentOfTarget.State != EntityStates.Deleted || + parentOfTarget.State != EntityStates.Detached)) + { + throw new InvalidOperationException(Strings.Context_ChildResourceExists); + } + } + } + + if (this.TryRemoveLinkDescriptor(existingLink)) + { // this link may have been previously detached by a detaching entity + existingLink.State = EntityStates.Detached; + } + } + + #endregion // Link Methods + + /// response materialization has an identity to attach to the inserted object + /// entity descriptor containing all the information about the entity from the response. + /// mergeOption based on which EntityDescriptor will be merged. + internal override void AttachIdentity(EntityDescriptor entityDescriptorFromMaterializer, MergeOption metadataMergeOption) + { // insert->unchanged + Debug.Assert(entityDescriptorFromMaterializer != null, "entityDescriptorFromMaterializer != null"); + + this.EnsureIdentityToResource(); + + // resource.State == EntityState.Added or Unchanged for second pass of media link + EntityDescriptor trackedEntityDescriptor = this.entityDescriptors[entityDescriptorFromMaterializer.Entity]; + + // make sure we got the right one - server could override identity and we may be tracking another one already. + this.ValidateDuplicateIdentity(entityDescriptorFromMaterializer.Identity, trackedEntityDescriptor); + + this.DetachResourceIdentity(trackedEntityDescriptor); + + // While processing the response, we need to find out if the given resource was inserted deep + // If it was, then we need to change the link state from added to unchanged + if (trackedEntityDescriptor.IsDeepInsert) + { + LinkDescriptor end = this.bindings[trackedEntityDescriptor.GetRelatedEnd()]; + end.State = EntityStates.Unchanged; + } + + trackedEntityDescriptor.Identity = entityDescriptorFromMaterializer.Identity; // always attach the identity + AtomMaterializerLog.MergeEntityDescriptorInfo(trackedEntityDescriptor, entityDescriptorFromMaterializer, true /*mergeInfo*/, metadataMergeOption); + trackedEntityDescriptor.State = EntityStates.Unchanged; + trackedEntityDescriptor.PropertiesToSerialize.Clear(); + + // scenario: sucessfully (1) delete an existing entity and (2) add a new entity where the new entity has the same identity as deleted entity + // where the SaveChanges pass1 will now associate existing identity with new entity + // but pass2 for the deleted entity will not blindly remove the identity that is now associated with the new identity + this.identityToDescriptor[entityDescriptorFromMaterializer.Identity] = trackedEntityDescriptor; + } + + /// use location from header to generate initial edit and identity + /// entity in added state + /// identity as specified in the response header - location header or OData-EntityId header. + /// editlink as specified in the response header - location header. + internal void AttachLocation(object entity, Uri identity, Uri editLink) + { + Debug.Assert(null != entity, "null != entity"); + Debug.Assert(editLink != null, "editLink != null"); + + this.EnsureIdentityToResource(); + + // resource.State == EntityState.Added or Unchanged for second pass of media link + EntityDescriptor resource = this.entityDescriptors[entity]; + + // make sure we got the right one - server could override identity and we may be tracking another one already. + this.ValidateDuplicateIdentity(identity, resource); + + this.DetachResourceIdentity(resource); + + // While processing the response, we need to find out if the given resource was inserted deep + // If it was, then we need to change the link state from added to unchanged + if (resource.IsDeepInsert) + { + LinkDescriptor end = this.bindings[resource.GetRelatedEnd()]; + end.State = EntityStates.Unchanged; + } + + resource.Identity = identity; // always attach the identity + resource.EditLink = editLink; + + // scenario: sucessfully batch (1) add a new entity and (2) delete an existing entity where the new entity has the same identity as deleted entity + // where the SaveChanges pass1 will now associate existing identity with new entity + // but pass2 for the deleted entity will not blindly remove the identity that is now associated with the new identity + this.identityToDescriptor[identity] = resource; + } + + /// + /// Attach entity into the context in the Unchanged state. + /// + /// entity descriptor from the response + /// fail for public api else change existing relationship to unchanged + /// Caller should validate descriptor instance. + /// The attached descriptor, if one already exists in the context and failIfDuplicated is set to false, then the existing instance is returned + /// if entity is already being tracked by the context + /// if identity is pointing to another entity + internal override EntityDescriptor InternalAttachEntityDescriptor(EntityDescriptor entityDescriptorFromMaterializer, bool failIfDuplicated) + { + Debug.Assert((null != entityDescriptorFromMaterializer.Identity), "must have identity"); + Debug.Assert(null != entityDescriptorFromMaterializer.Entity && ClientTypeUtil.TypeIsEntity(entityDescriptorFromMaterializer.Entity.GetType(), this.model), "must be entity type to attach"); + + this.EnsureIdentityToResource(); + + EntityDescriptor trackedEntityDescriptor; + this.entityDescriptors.TryGetValue(entityDescriptorFromMaterializer.Entity, out trackedEntityDescriptor); + + EntityDescriptor existing; + this.identityToDescriptor.TryGetValue(entityDescriptorFromMaterializer.Identity, out existing); + + // identity existing & pointing to something else + if (failIfDuplicated && (null != trackedEntityDescriptor)) + { + throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained); + } + else if (trackedEntityDescriptor != existing) + { + throw Error.InvalidOperation(Strings.Context_DifferentEntityAlreadyContained); + } + else if (null == trackedEntityDescriptor) + { + trackedEntityDescriptor = entityDescriptorFromMaterializer; + + // if resource doesn't exist... + this.IncrementChange(entityDescriptorFromMaterializer); + this.entityDescriptors.Add(entityDescriptorFromMaterializer.Entity, entityDescriptorFromMaterializer); + this.identityToDescriptor.Add(entityDescriptorFromMaterializer.Identity, entityDescriptorFromMaterializer); + } + + // DEVNOTE(pqian): + // we used to mark the descriptor as Unchanged + // but it's now up to the caller to do that + return trackedEntityDescriptor; + } + + /// + /// Find tracked entity by its resourceUri and update its etag. + /// + /// resource id + /// state of entity + /// entity if found else null + internal override object TryGetEntity(Uri resourceUri, out EntityStates state) + { + Debug.Assert(null != resourceUri, "null uri"); + state = EntityStates.Detached; + + EntityDescriptor resource = null; + if ((null != this.identityToDescriptor) && + this.identityToDescriptor.TryGetValue(resourceUri, out resource)) + { + state = resource.State; + Debug.Assert(null != resource.Entity, "null entity"); + return resource.Entity; + } + + return null; + } + + /// + /// increment the resource change for sorting during submit changes + /// + /// the resource to update the change order + internal void IncrementChange(Descriptor descriptor) + { + descriptor.ChangeOrder = ++this.nextChange; + } + + /// create this.identityToResource when necessary + private void EnsureIdentityToResource() + { + if (null == this.identityToDescriptor) + { + #if PORTABLELIB && WINDOWSPHONE + System.Threading.Interlocked.CompareExchange(ref this.identityToDescriptor, new Dictionary(EqualityComparer.Default), null); + #else + System.Threading.Interlocked.CompareExchange(ref this.identityToDescriptor, new ConcurrentDictionary(EqualityComparer.Default), null); + #endif + } + } + + /// create this.bindings when necessary + private void EnsureLinkBindings() + { + if (null == this.bindings) + { + #if PORTABLELIB && WINDOWSPHONE + System.Threading.Interlocked.CompareExchange(ref this.bindings, new Dictionary(LinkDescriptor.EquivalenceComparer), null); + #else + System.Threading.Interlocked.CompareExchange(ref this.bindings, new ConcurrentDictionary(LinkDescriptor.EquivalenceComparer), null); + #endif + } + } + + /// + /// Ensure an identity is unique and does not point to another resource + /// + /// The identity + /// The entity descriptor + private void ValidateDuplicateIdentity(Uri identity, EntityDescriptor descriptor) + { + EntityDescriptor trackedIdentity; + if (this.identityToDescriptor.TryGetValue(identity, out trackedIdentity) && descriptor != trackedIdentity && trackedIdentity.State != EntityStates.Deleted && trackedIdentity.State != EntityStates.Detached) + { + // we checked the state because we do not remove the deleted/detached entity descriptor from the dictionary until we have finished processing all changes + // So for instance if you delete one entity and add back one with the same ID, they will be a temporary conflict in the dictionary. + throw Error.InvalidOperation(Strings.Context_DifferentEntityAlreadyContained); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTrackerBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTrackerBase.cs new file mode 100644 index 0000000..e1fa26d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/EntityTrackerBase.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + + + #endregion Namespaces + + /// + /// Entity Tracker base, allows more decoupling for testing. + /// + public abstract class EntityTrackerBase + { + /// + /// Find tracked entity by its resourceUri and update its etag. + /// + /// resource id + /// state of entity + /// entity if found else null + internal abstract object TryGetEntity(Uri resourceUri, out EntityStates state); + + /// + /// get the related links ignoring target entity + /// + /// source entity + /// source entity's property + /// enumerable of related ends + internal abstract IEnumerable GetLinks(object source, string sourceProperty); + + /// + /// Attach entity into the context in the Unchanged state. + /// + /// entity descriptor from the response + /// fail for public api else change existing relationship to unchanged + /// Caller should validate descriptor instance. + /// The attached descriptor, if one already exists in the context and failIfDuplicated is set to false, then the existing instance is returned + /// if entity is already being tracked by the context + /// if identity is pointing to another entity + internal abstract EntityDescriptor InternalAttachEntityDescriptor(EntityDescriptor entityDescriptorFromMaterializer, bool failIfDuplicated); + + /// + /// verify the resource being tracked by context + /// + /// resource + /// The given resource. + /// if resource is not contained + internal abstract EntityDescriptor GetEntityDescriptor(object resource); + + /// Detach existing link + /// link to detach + /// true if target is being deleted, false otherwise + internal abstract void DetachExistingLink(LinkDescriptor existingLink, bool targetDelete); + + /// + /// attach the link with the given source, sourceProperty and target. + /// + /// source entity of the link. + /// name of the property on the source entity. + /// target entity of the link. + /// merge option to be used to merge the link if there is an existing link. + internal abstract void AttachLink(object source, string sourceProperty, object target, MergeOption linkMerge); + + /// response materialization has an identity to attach to the inserted object + /// entity descriptor containing all the information about the entity from the response. + /// mergeOption based on which EntityDescriptor will be merged. + internal abstract void AttachIdentity(EntityDescriptor entityDescriptorFromMaterializer, MergeOption metadataMergeOption); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Error.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Error.cs new file mode 100644 index 0000000..237bbbb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Error.cs @@ -0,0 +1,180 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// + /// Strongly-typed and parameterized exception factory. + /// + internal static partial class Error + { + /// + /// create and trace new ArgumentException + /// + /// exception message + /// parameter name in error + /// ArgumentException + internal static ArgumentException Argument(string message, string parameterName) + { + return Trace(new ArgumentException(message, parameterName)); + } + + /// + /// create and trace new InvalidOperationException + /// + /// exception message + /// InvalidOperationException + internal static InvalidOperationException InvalidOperation(string message) + { + return Trace(new InvalidOperationException(message)); + } + + /// + /// create and trace new InvalidOperationException + /// + /// exception message + /// innerException + /// InvalidOperationException + internal static InvalidOperationException InvalidOperation(string message, Exception innerException) + { + return Trace(new InvalidOperationException(message, innerException)); + } + + /// + /// Create and trace a NotSupportedException with a message + /// + /// Message for the exception + /// NotSupportedException + internal static NotSupportedException NotSupported(string message) + { + return Trace(new NotSupportedException(message)); + } + + /// + /// create and throw a ThrowObjectDisposed with a type name + /// + /// type being thrown on + internal static void ThrowObjectDisposed(Type type) + { + throw Trace(new ObjectDisposedException(type.ToString())); + } + + /// + /// create and trace a + /// + /// errorCode + /// message + /// InvalidOperationException + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801", Justification = "errorCode ignored for code sharing")] + internal static InvalidOperationException HttpHeaderFailure(int errorCode, string message) + { + return Trace(new InvalidOperationException(message)); + } + + /// method not supported + /// method + /// exception to throw + internal static NotSupportedException MethodNotSupported(System.Linq.Expressions.MethodCallExpression m) + { + return Error.NotSupported(Strings.ALinq_MethodNotSupported(m.Method.Name)); + } + + /// throw an exception because unexpected batch content was encounted + /// internal error + internal static void ThrowBatchUnexpectedContent(InternalError value) + { + throw InvalidOperation(Strings.Batch_UnexpectedContent((int)value)); + } + + /// throw an exception because expected batch content was not encountered + /// internal error + internal static void ThrowBatchExpectedResponse(InternalError value) + { + throw InvalidOperation(Strings.Batch_ExpectedResponse((int)value)); + } + + /// unexpected xml when reading web responses + /// internal error + /// exception to throw + internal static InvalidOperationException InternalError(InternalError value) + { + return InvalidOperation(Strings.Context_InternalError((int)value)); + } + + /// throw exception for unexpected xml when reading web responses + /// internal error + internal static void ThrowInternalError(InternalError value) + { + throw InternalError(value); + } + + /// + /// Trace the exception + /// + /// type of the exception + /// exception object to trace + /// the exception parameter + private static T Trace(T exception) where T : Exception + { + return exception; + } + } + + /// unique numbers for repeated error messages for unlikely, unactionable exceptions + internal enum InternalError + { + UnexpectedReadState = 4, + UnvalidatedEntityState = 6, + NullResponseStream = 7, + EntityNotDeleted = 8, + EntityNotAddedState = 9, + LinkNotAddedState = 10, + EntryNotModified = 11, + LinkBadState = 12, + UnexpectedBeginChangeSet = 13, + UnexpectedBatchState = 14, + ChangeResponseMissingContentID = 15, + ChangeResponseUnknownContentID = 16, + InvalidHandleOperationResponse = 18, + + InvalidEndGetRequestStream = 20, + InvalidEndGetRequestCompleted = 21, + InvalidEndGetRequestStreamRequest = 22, + InvalidEndGetRequestStreamStream = 23, + InvalidEndGetRequestStreamContent = 24, + InvalidEndGetRequestStreamContentLength = 25, + + InvalidEndWrite = 30, + InvalidEndWriteCompleted = 31, + InvalidEndWriteRequest = 32, + InvalidEndWriteStream = 33, + + InvalidEndGetResponse = 40, + InvalidEndGetResponseCompleted = 41, + InvalidEndGetResponseRequest = 42, + InvalidEndGetResponseResponse = 43, + InvalidAsyncResponseStreamCopy = 44, + InvalidAsyncResponseStreamCopyBuffer = 45, + + InvalidEndRead = 50, + InvalidEndReadCompleted = 51, + InvalidEndReadStream = 52, + InvalidEndReadCopy = 53, + InvalidEndReadBuffer = 54, + + InvalidSaveNextChange = 60, + InvalidBeginNextChange = 61, + SaveNextChangeIncomplete = 62, + MaterializerReturningMoreThanOneEntity = 63, + + InvalidGetResponse = 71, + InvalidHandleCompleted = 72, + + InvalidMethodCallWhenNotReadingJsonLight = 73, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/FunctionDescriptor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/FunctionDescriptor.cs new file mode 100644 index 0000000..23d82dc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/FunctionDescriptor.cs @@ -0,0 +1,13 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// Holds information about a ServiceFunction. + public sealed class FunctionDescriptor : OperationDescriptor + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/GetReadStreamResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/GetReadStreamResult.cs new file mode 100644 index 0000000..364004e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/GetReadStreamResult.cs @@ -0,0 +1,203 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Net; + using Microsoft.OData; + + /// + /// Class which implements the for the GetReadStream operation. + /// Note that this effectively behaves as a simple wrapper around the IAsyncResult returned + /// by the underlying HttpWebRequest, although it's implemented fully on our own to get the same + /// behavior as other IAsyncResult objects returned by the client library. + /// + internal class GetReadStreamResult : BaseAsyncResult + { + /// The web request this class wraps (effectively) + private readonly ODataRequestMessageWrapper requestMessage; + + /// descriptor of the stream which is getting queried. + private readonly StreamDescriptor streamDescriptor; + + /// RequestInfo for this request. + private readonly RequestInfo requestInfo; + + /// IODataResponseMessage containing all the response information. + private IODataResponseMessage responseMessage; + + /// + /// Constructs a new async result object + /// + /// The source of the operation. + /// Name of the method which is invoked asynchronously. + /// The object which is wrapped by this async result. + /// User specified callback for the async operation. + /// User state for the async callback. + /// stream descriptor whose value is getting queried. + internal GetReadStreamResult( + DataServiceContext context, + string method, + ODataRequestMessageWrapper request, + AsyncCallback callback, + object state, + StreamDescriptor streamDescriptor) + : base(context, method, callback, state) + { + Debug.Assert(request != null, "Null request can't be wrapped to a result."); + Debug.Assert(streamDescriptor != null, "streamDescriptor != null"); + this.requestMessage = request; + this.Abortable = request; + this.streamDescriptor = streamDescriptor; + this.requestInfo = new RequestInfo(context); + } + + /// + /// Begins the async request + /// + internal void Begin() + { + try + { + IAsyncResult asyncResult = BaseAsyncResult.InvokeAsync(this.requestMessage.BeginGetResponse, this.AsyncEndGetResponse, null); + this.SetCompletedSynchronously(asyncResult.CompletedSynchronously); + } + catch (Exception e) + { + this.HandleFailure(e); + throw; + } + finally + { + this.HandleCompleted(); + } + + Debug.Assert(!this.CompletedSynchronously || this.IsCompleted, "if CompletedSynchronously then MUST IsCompleted"); + } + + /// + /// Ends the request and creates the response object. + /// + /// The response object for this request. + internal DataServiceStreamResponse End() + { + if (this.responseMessage != null) + { + // update the etag if the response contains the etag + this.streamDescriptor.ETag = this.responseMessage.GetHeader(XmlConstants.HttpResponseETag); + + // update the content type of the stream for named stream. + this.streamDescriptor.ContentType = this.responseMessage.GetHeader(XmlConstants.HttpContentType); + + DataServiceStreamResponse streamResponse = new DataServiceStreamResponse(this.responseMessage); + return streamResponse; + } + else + { + return null; + } + } + +#if !PORTABLELIB + /// + /// Executes the request synchronously. + /// + /// + /// The response object for this request. + /// + internal DataServiceStreamResponse Execute() + { + try + { + this.responseMessage = this.requestInfo.GetSyncronousResponse(this.requestMessage, true); + Debug.Assert(this.responseMessage != null, "Can't set a null response."); + } + catch (Exception e) + { + this.HandleFailure(e); + throw; + } + finally + { + this.SetCompleted(); + this.CompletedRequest(); + } + + if (null != this.Failure) + { + throw this.Failure; + } + + return this.End(); + } +#endif + + /// invoked for derived classes to cleanup before callback is invoked + protected override void CompletedRequest() + { + Debug.Assert(null != this.responseMessage || null != this.Failure, "should have response or exception"); + if (null != this.responseMessage) + { + // Can't use DataServiceContext.HandleResponse as this request didn't necessarily go to our server + // the MR could have been served by arbitrary server. + InvalidOperationException failure = null; + if (!WebUtil.SuccessStatusCode((HttpStatusCode)this.responseMessage.StatusCode)) + { + failure = SaveResult.GetResponseText( + this.responseMessage.GetStream, + (HttpStatusCode)this.responseMessage.StatusCode); + } + + if (failure != null) + { + // we've cached off what we need, headers still accessible after close + WebUtil.DisposeMessage(this.responseMessage); + this.HandleFailure(failure); + } + } + } + + /// Set the AsyncWait and invoke the user callback. + /// the request object + /// This method is not implemented for this class. + protected override void HandleCompleted(BaseAsyncResult.PerRequest pereq) + { + Debug.Assert(false, "This method should never be called from GetReadStreamResult."); + Error.ThrowInternalError(InternalError.InvalidHandleCompleted); + } + + /// + /// Async callback registered with the underlying HttpWebRequest object. + /// + /// The async result associated with the HttpWebRequest operation. + protected override void AsyncEndGetResponse(IAsyncResult asyncResult) + { + try + { + this.SetCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginGetResponse + ODataRequestMessageWrapper request = Util.NullCheck(this.requestMessage, InternalError.InvalidEndGetResponseRequest); + + this.responseMessage = this.requestInfo.EndGetResponse(request, asyncResult); + Debug.Assert(this.responseMessage != null, "Can't set a null response."); + + this.SetCompleted(); + } + catch (Exception e) + { + if (this.HandleFailure(e)) + { + throw; + } + } + finally + { + this.HandleCompleted(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/GlobalSuppressions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/GlobalSuppressions.cs new file mode 100644 index 0000000..cf76bca --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/GlobalSuppressions.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "orderby", Scope = "resource", Target = "Microsoft.OData.Client.resources")] +[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "dataservices", Scope = "resource", Target = "Microsoft.OData.Client.resources")] +[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "rel", Scope = "resource", Target = "Microsoft.OData.Client.resources")] +[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Rel", Scope = "resource", Target = "Microsoft.OData.Client.resources")] +[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "resource", Target = "Microsoft.OData.Client.resources")] + +[module: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.OData.Service.Common")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.Strings.#ClientType_MissingOpenProperty(System.Object,System.Object)")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.Strings.#Clienttype_MultipleOpenProperty(System.Object)")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.Strings.#ClientType_NullOpenProperties(System.Object)")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.Strings.#Collection_NullCollectionReference(System.Object,System.Object)")] + +// Violations in the generated Resource file; can't prevent these from being generated... +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.TextRes.#GetObject(System.String)")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.TextRes.#Resources")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.TextRes.#GetString(System.String,System.Boolean&)")] +[module: SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily", Scope = "member", Target = "Microsoft.OData.Client.TextRes..cctor()")] +[module: SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily", Scope = "member", Target = "Microsoft.OData.Client.TextResDescriptionAttribute..ctor(System.String)")] + +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.Strings.#ODataMetadataBuilder_MissingEntitySetUri(System.Object)")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.Strings.#ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix(System.Object,System.Object)")] +[module: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Client.Strings.#ODataMetadataBuilder_MissingEntityInstanceUri(System.Object)")] + +// Since DataServiceClientRequestMessage is already a public API, suppress this issue until we decide to make breaking changes. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1012:AbstractTypesShouldNotHaveConstructors", Scope = "type", Target = "Microsoft.OData.Client.DataServiceClientRequestMessage")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Scope = "type", Target = "Microsoft.OData.Client.DataServiceQuery`1+DataServiceOrderedQuery")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.OData.Client.DataServiceQuery`1+DataServiceOrderedQuery")] + +// The class actually has instances in a code piece just disabled by a macro. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Scope = "type", Target = "Microsoft.OData.Metadata.EdmLibraryExtensions+EdmTypeEqualityComparer")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.OData.Client.DataServiceClientResponsePipelineConfiguration.#sender")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Client.Utility.#GetCustomAttributes(System.Type,System.Type,System.Boolean)")] + +// Already public APIs and thus cannot be changed. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuery.#.ctor(Microsoft.OData.Client.DataServiceContext,System.String,Microsoft.OData.Client.BodyOperationParameter[])")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuery`1.#.ctor(Microsoft.OData.Client.DataServiceContext,System.String,Microsoft.OData.Client.BodyOperationParameter[])")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuerySingle`1.#.ctor(Microsoft.OData.Client.DataServiceContext,System.String,Microsoft.OData.Client.BodyOperationParameter[])")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Scope = "member", Target = "Microsoft.OData.Client.DataServiceQuery`1.#AppendRequestUri(System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Scope = "member", Target = "Microsoft.OData.Client.DataServiceQuerySingle`1.#AppendRequestUri(System.String)")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuerySingle`1.#GetValue()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuerySingle`1.#GetValueAsync()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Client.DataServiceQuery`1.#GetAllPagesAsync()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Client.DataServiceQuery`1.#GetAllPages()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Client.DataServiceQuerySingle`1.#GetValue()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Client.DataServiceQuerySingle`1.#GetValueAsync()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuery.#.ctor(Microsoft.OData.Client.DataServiceContext,System.String,Microsoft.OData.Client.BodyOperationParameter[])")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuery`1.#.ctor(Microsoft.OData.Client.DataServiceContext,System.String,Microsoft.OData.Client.BodyOperationParameter[])")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Scope = "member", Target = "Microsoft.OData.Client.DataServiceActionQuerySingle`1.#.ctor(Microsoft.OData.Client.DataServiceContext,System.String,Microsoft.OData.Client.BodyOperationParameter[])")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Scope = "member", Target = "Microsoft.OData.Client.DataServiceQuery`1.#GetKeyPath(System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.OData.Client.ClientEdmModel.#FindOperationImportsByNameNonBindingParameterType(System.String,System.Collections.Generic.IEnumerable`1)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.OData.Client.HttpWebRequestMessage.#.cctor()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "httpRequestStream", Scope = "member", Target = "Microsoft.OData.Client.BaseAsyncResult.#AsyncEndWrite(System.Threading.Tasks.Task,System.Object)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.OData.Client.RequestInfo.#IsUserSuppliedResolver", Justification = "Non-static is required for for non-PORTABLELIB")] + +// APTCA +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA900")] \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/HeaderCollection.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/HeaderCollection.cs new file mode 100644 index 0000000..c7e6461 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/HeaderCollection.cs @@ -0,0 +1,265 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Net; + using Microsoft.OData; + + /// + /// Collection for header name/value pairs which is known to be case insensitive. + /// + internal class HeaderCollection + { + /// + /// Case-insensitive dictionary for storing the header name/value pairs. + /// + private readonly IDictionary headers; + + /// + /// Current assembly version. + /// + private static Version assemblyVersion = typeof(HeaderCollection).GetAssembly().GetName().Version; + + /// + /// Initializes a new instance of . + /// + /// The initial set of headers for the collection. + internal HeaderCollection(IEnumerable> headers) + : this() + { + Debug.Assert(headers != null, "headers != null"); + this.SetHeaders(headers); + } + + /// + /// Initializes a new instance of . + /// + /// The response message to pull the headers from. + internal HeaderCollection(IODataResponseMessage responseMessage) + : this() + { + if (responseMessage != null) + { + this.SetHeaders(responseMessage.Headers); + } + } + + /// + /// Initializes a new instance of . + /// + /// The initial set of headers for the collection. + internal HeaderCollection(WebHeaderCollection headers) + : this() + { + Debug.Assert(headers != null, "headers != null"); + foreach (string name in headers.AllKeys) + { + this.SetHeader(name, headers[name]); + } + } + + /// + /// Initializes a new instance of which is empty. + /// + internal HeaderCollection() + { + this.headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Gets the underlying dictionary the headers are stored in. Should only be used when absolutely necessary for maintaining public API. + /// + internal IDictionary UnderlyingDictionary + { + get { return this.headers; } + } + + /// + /// Gets the names of all the headers in the collection. + /// + internal IEnumerable HeaderNames + { + get { return this.headers.Keys; } + } + +#if DEBUG + /// + /// Gets the number of headers in the collection. + /// + internal int Count + { + get { return this.headers.Count; } + } +#endif + /// + /// Adds default system headers + /// Currently it sets User-Agent header as default system header + /// + internal void SetDefaultHeaders() + { + this.SetUserAgent(); + } + + /// + /// Tries to get the value of the header with the given name, if it is in the collection. + /// + /// The header name to look for. + /// The header value, if it was in the collection. + /// Whether or not the header was in the collection. + internal bool TryGetHeader(string headerName, out string headerValue) + { + return this.headers.TryGetValue(headerName, out headerValue); + } + + /// + /// Gets the value of the header, or null if it is not in the collection. + /// + /// The header name to look for. + /// The header value or null. + internal string GetHeader(string headerName) + { + string headerValue; + return this.TryGetHeader(headerName, out headerValue) ? headerValue : null; + } + + /// + /// Sets a header value. Will remove the header from the underlying dictionary if the new value is null. + /// + /// The header name to set. + /// The new value of the header. + internal void SetHeader(string headerName, string headerValue) + { + if (headerValue == null) + { + this.headers.Remove(headerName); + } + else + { + this.headers[headerName] = headerValue; + } + } + +#if DEBUG + /// + /// Returns whether or not the collection contains the given header. + /// + /// The header name to look for. + /// Whether the collection contains the header. + internal bool HasHeader(string headerName) + { + return this.headers.ContainsKey(headerName); + } +#endif + + /// + /// Sets multiple header values at once. + /// + /// The headers to set. + internal void SetHeaders(IEnumerable> headersToSet) + { + this.headers.SetRange(headersToSet); + } + + /// + /// Gets an enumeration of the header values in the collection. + /// + /// An enumeration of the header values in the collection. + internal IEnumerable> AsEnumerable() + { + return this.headers.AsEnumerable(); + } + + /// + /// Sets the request OData-Version and OData-MaxVersion. + /// + /// DSV to set the request to. + /// Max protocol version, which MDSV will essentially be set to. + internal void SetRequestVersion(Version requestVersion, Version maxProtocolVersion) + { + if (requestVersion != null) + { + if (requestVersion > maxProtocolVersion) + { + string message = Strings.Context_RequestVersionIsBiggerThanProtocolVersion(requestVersion.ToString(), maxProtocolVersion.ToString()); + throw Error.InvalidOperation(message); + } + + // if request version is 0.x, then we don't put a DSV header + // in this case it's up to the server to decide what version this request is + // (happens for example if Execute was used) + if (requestVersion.Major > 0) + { + // never decrease the version. + // if DSV == null || requestVersion > DSV + // FIx this. need to parse it or something + Version oldVersion = this.GetODataVersion(); + if (oldVersion == null || requestVersion > oldVersion) + { + this.SetHeader(XmlConstants.HttpODataVersion, requestVersion.ToString(Util.ODataVersionFieldCount)); + } + } + } + + // for legacy reasons, we don't set this until we set a DSV + this.SetHeader(XmlConstants.HttpODataMaxVersion, maxProtocolVersion.ToString(Util.ODataVersionFieldCount)); + } + + /// + /// Sets the header if it was previously unset. + /// + /// The header to set. + /// The new header value. + internal void SetHeaderIfUnset(string headerToSet, string headerValue) + { + // Don't overwrite the header if it has already been set + if (this.GetHeader(headerToSet) == null) + { + this.SetHeader(headerToSet, headerValue); + } + } + + /// + /// Sets UserAgent header + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is an instance method and calls another instance method for non-silverlight scenearios.")] + internal void SetUserAgent() + { + // Add User-Agent header to every request - Since UserAgent field is not supported in silverlight, + // doing this non-silverlight stacks only + this.SetHeader(XmlConstants.HttpUserAgent, string.Format(CultureInfo.InvariantCulture, "Microsoft.OData.Client/{0}.{1}.{2}", assemblyVersion.Major, assemblyVersion.Minor, assemblyVersion.Build)); + } + + /// + /// Creates a copy of the current header collection which uses a different dictionary to store headers. + /// + /// A copy of the current headers. + internal HeaderCollection Copy() + { + return new HeaderCollection(this.headers); + } + + /// + /// Gets the OData-Version as a Version object if it is encoded as a proper version string with an optional Util.VersionSuffix ending. + /// + /// The OData-Version header as a Version object. + private Version GetODataVersion() + { + string rawVersion; + if (!this.TryGetHeader(XmlConstants.HttpODataVersion, out rawVersion)) + { + return null; + } + + return Version.Parse(rawVersion); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/HttpStack.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/HttpStack.cs new file mode 100644 index 0000000..7b1fef1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/HttpStack.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// Represents the type of HTTP implementation to use when accessing the data service.Supported only by the WCF Data Services 5.0 client for Silverlight. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Accurately describes what the enum represents, and also this class has already shipped as a public type.")] + internal enum HttpStack + { + /// + /// Automatically choose the HTTP stack + /// When possible XmlHttp stack will be used, otherwise the Client stack will be used + /// + Auto = 0, + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/InternalODataRequestMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/InternalODataRequestMessage.cs new file mode 100644 index 0000000..de15387 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/InternalODataRequestMessage.cs @@ -0,0 +1,252 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.IO; + using System.Collections.Generic; + using System.Net; + using Microsoft.OData; + + /// + /// This is a just a pass through implementation of IODataRequestMessage. This class is used + /// for wrapping the inner batch requests or in silverlight when we are using the + /// non-silverlight http stack, we need to fire IODataRequestMessage which throws + /// when GetStream is called. + /// + internal class InternalODataRequestMessage : DataServiceClientRequestMessage + { + /// + /// IODataRequestMessage implementation that this class wraps. + /// + private readonly IODataRequestMessage requestMessage; + + /// + /// Boolean flag to allow calls to GetStream() on this instance + /// We want to allow this because WritingRequest and ReadingResponse events on the Windows Phone platform + /// requires that we pass a readable stream to user code as arguments. + /// + private readonly bool allowGetStream; + + /// + /// request stream + /// + private Stream cachedRequestStream; + + /// + /// dictionary containing http headers. + /// + private HeaderCollection headers; + + /// + /// Creates a new instance of InternalODataRequestMessage. + /// + /// IODataRequestMessage that needs to be wrapped. + /// boolean flag to allow calls to GetStream() on this instance + internal InternalODataRequestMessage(IODataRequestMessage requestMessage, bool allowGetStream) : base(requestMessage.Method) + { + this.requestMessage = requestMessage; + this.allowGetStream = allowGetStream; + } + + /// + /// Returns the collection of request headers. + /// + public override IEnumerable> Headers + { + get + { + return this.HeaderCollection.AsEnumerable(); + } + } + + /// + /// Gets or Sets the request url. + /// + public override Uri Url + { + get + { + return this.requestMessage.Url; + } + + set + { + throw new NotImplementedException(); + } + } + + /// + /// Gets or Sets the http method for this request. + /// + public override string Method + { + get + { + return this.requestMessage.Method; + } + + set + { + throw new NotImplementedException(); + } + } + + /// + /// Gets or set the credentials for this request. + /// + public override ICredentials Credentials + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + +#if !PORTABLELIB + /// + /// Gets or sets the timeout (in seconds) for this request. + /// + public override int Timeout + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + /// + /// Gets or sets a value that indicates whether to send data in segments to the + /// Internet resource. + /// + public override bool SendChunked + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } +#endif + + /// + /// internal headers dictionary + /// + private HeaderCollection HeaderCollection + { + get + { + if (this.headers == null) + { + this.headers = new HeaderCollection(this.requestMessage.Headers); + } + + return this.headers; + } + } + + /// + /// Returns the value of the header with the given name. + /// + /// Name of the header. + /// Returns the value of the header with the given name. + public override string GetHeader(string headerName) + { + return this.HeaderCollection.GetHeader(headerName); + } + + /// + /// Sets the value of the header with the given name. + /// + /// Name of the header. + /// Value of the header. + public override void SetHeader(string headerName, string headerValue) + { + this.requestMessage.SetHeader(headerName, headerValue); + } + + /// + /// Gets the stream to be used to write the request payload. + /// + /// Stream to which the request payload needs to be written. + public override Stream GetStream() + { + if (!this.allowGetStream) + { + throw new NotImplementedException(); + } + + if (this.cachedRequestStream == null) + { + this.cachedRequestStream = this.requestMessage.GetStream(); + } + + return this.cachedRequestStream; + } + + /// + /// Abort the current request. + /// + public override void Abort() + { + throw new NotImplementedException(); + } + + /// + /// Begins an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request. + public override IAsyncResult BeginGetRequestStream(AsyncCallback callback, object state) + { + throw new NotImplementedException(); + } + + /// + /// Ends an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The pending request for a stream. + /// A System.IO.Stream to use to write request data. + public override Stream EndGetRequestStream(IAsyncResult asyncResult) + { + throw new NotImplementedException(); + } + + /// + /// Begins an asynchronous request to an Internet resource. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request for a response. + public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) + { + throw new NotImplementedException(); + } + + /// + /// Ends an asynchronous request to an Internet resource. + /// + /// The pending request for a response. + /// A System.Net.WebResponse that contains the response from the Internet resource. + public override IODataResponseMessage EndGetResponse(IAsyncResult asyncResult) + { + throw new NotImplementedException(); + } + +#if !PORTABLELIB + /// + /// Returns a response from an Internet resource. + /// + /// A System.Net.WebResponse that contains the response from the Internet resource. + public override IODataResponseMessage GetResponse() + { + throw new NotImplementedException(); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/InvokeResponse.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/InvokeResponse.cs new file mode 100644 index 0000000..2a4085a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/InvokeResponse.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.OData.Client; + + /// Response from an Invoke call. + public class InvokeResponse : OperationResponse + { + /// Consutrcts an InvokeResponse identical to an OperationResponse. + /// The HTTP headers. + public InvokeResponse(Dictionary headers) + : base(new HeaderCollection(headers)) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkDescriptor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkDescriptor.cs new file mode 100644 index 0000000..804be6f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkDescriptor.cs @@ -0,0 +1,175 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using Microsoft.OData.Client.Metadata; + + /// + /// represents the association between two entities + /// + [DebuggerDisplay("State = {state}")] + public sealed class LinkDescriptor : Descriptor + { + #region Fields + + /// equivalence comparer + internal static readonly System.Collections.Generic.IEqualityComparer EquivalenceComparer = new Equivalent(); + + /// source entity + private object source; + + /// name of property on source entity that references the target entity + private string sourceProperty; + + /// target entity + private object target; + #endregion + + /// + /// Constructor + /// + /// Source entity + /// Navigation property on the source entity + /// Target entity + /// The client model. + internal LinkDescriptor(object source, string sourceProperty, object target, ClientEdmModel model) + : this(source, sourceProperty, target, EntityStates.Unchanged) + { + this.IsSourcePropertyCollection = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(source.GetType())).GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException).IsEntityCollection; + } + + /// + /// Constructor + /// + /// Source entity + /// Navigation property on the source entity + /// Target entity + /// The link state + internal LinkDescriptor(object source, string sourceProperty, object target, EntityStates state) + : base(state) + { + Debug.Assert(source != null, "source != null"); + Debug.Assert(!String.IsNullOrEmpty(sourceProperty), "!String.IsNullOrEmpty(propertyName)"); + Debug.Assert(!sourceProperty.Contains("/"), "!sourceProperty.Contains('/')"); + + this.source = source; + this.sourceProperty = sourceProperty; + this.target = target; + } + + #region Public Properties + + /// The source entity in a link returned by a . + /// . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811", Justification = "The setter is called during de-serialization")] + public object Target + { + get + { + return this.target; + } + + internal set + { + this.target = value; + } + } + + /// A source entity in a link returned by a . + /// . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811", Justification = "The setter is called during de-serialization")] + public object Source + { + get + { + return this.source; + } + + internal set + { + this.source = value; + } + } + + /// The identifier property of the source entity in a link returned by a . + /// The string identifier of an identity property in a source entity. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811", Justification = "The setter is called during de-serialization")] + public string SourceProperty + { + get + { + return this.sourceProperty; + } + + internal set + { + this.sourceProperty = value; + } + } + + #endregion + + /// this is a link + internal override DescriptorKind DescriptorKind + { + get { return DescriptorKind.Link; } + } + + /// is this a collection property or not + internal bool IsSourcePropertyCollection + { + get; + set; + } + + /// + /// Clear all the changes associated with this descriptor + /// This method is called when the client is done with sending all the pending requests. + /// + internal override void ClearChanges() + { + // Do nothing + } + + /// + /// If the current instance of link descriptor is equivalent to the parameters supplied + /// + /// The source entity + /// The source property name + /// The target entity + /// true if equivalent + internal bool IsEquivalent(object src, string srcPropName, object targ) + { + return (this.source == src && + this.target == targ && + this.sourceProperty == srcPropName); + } + + /// equivalence comparer + private sealed class Equivalent : System.Collections.Generic.IEqualityComparer + { + /// are two LinkDescriptors equivalent, ignore state + /// link descriptor x + /// link descriptor y + /// true if equivalent + public bool Equals(LinkDescriptor x, LinkDescriptor y) + { + return (null != x) && (null != y) && x.IsEquivalent(y.source, y.sourceProperty, y.target); + } + + /// compute hashcode for LinkDescriptor + /// link descriptor + /// hashcode + public int GetHashCode(LinkDescriptor obj) + { + return (null != obj) ? (obj.Source.GetHashCode() ^ ((null != obj.Target) ? obj.Target.GetHashCode() : 0) ^ obj.SourceProperty.GetHashCode()) : 0; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkInfo.cs new file mode 100644 index 0000000..bb47a25 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/LinkInfo.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + + /// Encapsulates information about a link, or relationship, between entities. + public sealed class LinkInfo + { + /// navigation URI to the related entity. + private Uri navigationLink; + + /// association URI to the related entity. + private Uri associationLink; + + /// the navigation property name + private string name; + + /// + /// Creates a LinkInfo with a given properyName + /// + /// the name of the navigation property + internal LinkInfo(String propertyName) + { + Debug.Assert(!String.IsNullOrEmpty(propertyName), "!String.IsNullOrEmpty(propertyName)"); + this.name = propertyName; + } + + /// Gets the name of the link. + /// The name of the link. + public string Name + { + get + { + return this.name; + } + } + + /// Gets the URI that is the navigation property representation of the link. + /// The navigation link URI. + public Uri NavigationLink + { + get + { + return this.navigationLink; + } + + internal set + { + Debug.Assert(value == null || value.IsAbsoluteUri, "navigation link must be absolute uri"); + this.navigationLink = value; + } + } + + /// Gets the URI that is the association link. + /// The URI of the association link. + public Uri AssociationLink + { + get + { + return this.associationLink; + } + + internal set + { + Debug.Assert(value == null || value.IsAbsoluteUri, "association link must be absolute uri"); + this.associationLink = value; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/LoadPropertyResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/LoadPropertyResult.cs new file mode 100644 index 0000000..f4cf5f2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/LoadPropertyResult.cs @@ -0,0 +1,338 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// wrapper around loading a property from a response + internal class LoadPropertyResult : QueryResult + { + #region Private fields + + /// entity whose property is being loaded + private readonly object entity; + + /// Projection plan for loading results; possibly null. + private readonly ProjectionPlan plan; + + /// name of the property on the entity that is being loaded + private readonly string propertyName; + + #endregion Private fields + + /// constructor + /// entity + /// name of collection or reference property to load + /// Originating context + /// Originating WebRequest + /// user callback + /// user state + /// request object. + /// Projection plan for materialization; possibly null. + /// Whether this request is a continuation request. + internal LoadPropertyResult(object entity, string propertyName, DataServiceContext context, ODataRequestMessageWrapper request, AsyncCallback callback, object state, DataServiceRequest dataServiceRequest, ProjectionPlan plan, bool isContinuation) + : base(context, Util.LoadPropertyMethodName, dataServiceRequest, request, new RequestInfo(context, isContinuation), callback, state) + { + this.entity = entity; + this.propertyName = propertyName; + this.plan = plan; + } + + /// + /// loading a property from a response + /// + /// QueryOperationResponse instance containing information about the response. + internal QueryOperationResponse LoadProperty() + { + MaterializeAtom results = null; + + DataServiceContext context = (DataServiceContext)this.Source; + + ClientEdmModel model = context.Model; + ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(this.entity.GetType())); + Debug.Assert(type.IsEntityType, "must be entity type to be contained"); + + EntityDescriptor box = context.GetEntityDescriptor(this.entity); + + if (EntityStates.Added == box.State) + { + throw Error.InvalidOperation(Strings.Context_NoLoadWithInsertEnd); + } + + ClientPropertyAnnotation property = type.GetProperty(this.propertyName, UndeclaredPropertyBehavior.ThrowException); + Type elementType = property.EntityCollectionItemType ?? property.NullablePropertyType; + try + { + if (type.MediaDataMember == property) + { + results = this.ReadPropertyFromRawData(property); + } + else + { + results = this.ReadPropertyFromAtom(property); + } + + return this.GetResponseWithType(results, elementType); + } + catch (InvalidOperationException ex) + { + QueryOperationResponse response = this.GetResponseWithType(results, elementType); + if (response != null) + { + response.Error = ex; + throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, response); + } + + throw; + } + } + + /// + /// Creates the ResponseInfo object. + /// + /// ResponseInfo object. + protected override ResponseInfo CreateResponseInfo() + { + DataServiceContext context = (DataServiceContext)this.Source; + + ClientEdmModel model = context.Model; + + ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(this.entity.GetType())); + + Debug.Assert(type.IsEntityType, "Must be entity type to be contained."); + + return this.RequestInfo.GetDeserializationInfoForLoadProperty( + null, + context.GetEntityDescriptor(this.entity), + type.GetProperty(this.propertyName, UndeclaredPropertyBehavior.ThrowException)); + } + + /// + /// Reads the data from the response stream into a buffer using the content length. + /// + /// Response stream. + /// Length of data to read. + /// byte array containing read data. + private static byte[] ReadByteArrayWithContentLength(Stream responseStream, int totalLength) + { + byte[] buffer = new byte[totalLength]; + int read = 0; + while (read < totalLength) + { + int r = responseStream.Read(buffer, read, totalLength - read); + if (r <= 0) + { + throw Error.InvalidOperation(Strings.Context_UnexpectedZeroRawRead); + } + + read += r; + } + + return buffer; + } + + /// Reads the data from the response stream in chunks. + /// Response stream. + /// byte array containing read data. + private static byte[] ReadByteArrayChunked(Stream responseStream) + { + byte[] completeBuffer = null; + using (MemoryStream m = new MemoryStream()) + { + byte[] buffer = new byte[4096]; + int numRead = 0; + int totalRead = 0; + while (true) + { + numRead = responseStream.Read(buffer, 0, buffer.Length); + if (numRead <= 0) + { + break; + } + + m.Write(buffer, 0, numRead); + totalRead += numRead; + } + + completeBuffer = new byte[totalRead]; + m.Position = 0; + numRead = m.Read(completeBuffer, 0, completeBuffer.Length); + } + + return completeBuffer; + } + + /// + /// Load property data from an ATOM response + /// + /// The property being loaded + /// property values as IEnumerable. + private MaterializeAtom ReadPropertyFromAtom(ClientPropertyAnnotation property) + { + DataServiceContext context = (DataServiceContext)this.Source; + bool merging = context.ApplyingChanges; + + try + { + context.ApplyingChanges = true; + + // store the results so that they can be there in the response body. + Type elementType = property.IsEntityCollection ? property.EntityCollectionItemType : property.NullablePropertyType; + IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); + + DataServiceQueryContinuation continuation = null; + + // elementType.ElementType has Nullable stripped away, use nestedType for materializer + using (MaterializeAtom materializer = this.GetMaterializer(this.plan)) + { + Debug.Assert(materializer != null, "materializer != null -- otherwise GetMaterializer() returned null rather than empty"); + + // when SetLink to null, we cannot get materializer because have no-content response. + if (materializer.IsNoContentResponse() + && property.GetValue(entity) != null + && context.MergeOption != MergeOption.AppendOnly + && context.MergeOption != MergeOption.NoTracking) + { + property.SetValue(this.entity, null, propertyName, false); + } + else + { + foreach (object child in materializer) + { + if (property.IsEntityCollection) + { + results.Add(child); + } + else if (property.IsPrimitiveOrEnumOrComplexCollection) + { + Debug.Assert(property.PropertyType.IsAssignableFrom(child.GetType()), "Created instance for storing collection items has to be compatible with the actual one."); + + // Collection materialization rules requires to clear the collection if not null or set the property first and then add the collection items + object collectionInstance = property.GetValue(this.entity); + if (collectionInstance == null) + { + // type of child has been resolved as per rules for collections so it is the correct type to instantiate + collectionInstance = Activator.CreateInstance(child.GetType()); + + // allowAdd is false - we need to assign instance as the new property value + property.SetValue(this.entity, collectionInstance, this.propertyName, false /* allowAdd? */); + } + else + { + // Clear existing collection + property.ClearBackingICollectionInstance(collectionInstance); + } + + foreach (var collectionItem in (IEnumerable)child) + { + Debug.Assert(property.PrimitiveOrComplexCollectionItemType.IsAssignableFrom(collectionItem.GetType()), "Type of materialized collection items have to be compatible with the type of collection items in the actual collection property."); + property.AddValueToBackingICollectionInstance(collectionInstance, collectionItem); + } + + results.Add(collectionInstance); + } + else + { + // it is either primitive type, complex type or 1..1 navigation property so we just allow setting the value but not adding. + property.SetValue(this.entity, child, this.propertyName, false); + results.Add(child); + } + } + } + + continuation = materializer.GetContinuation(null); + } + + return MaterializeAtom.CreateWrapper(context, results, continuation); + } + finally + { + context.ApplyingChanges = merging; + } + } + + /// + /// Load property data form a raw response + /// + /// The property being loaded + /// property values as IEnumerable. + private MaterializeAtom ReadPropertyFromRawData(ClientPropertyAnnotation property) + { + DataServiceContext context = (DataServiceContext)this.Source; + + bool merging = context.ApplyingChanges; + + try + { + context.ApplyingChanges = true; + + // if this is the data property for a media entry, what comes back + // is the raw value (no markup) + string mimeType = null; + Encoding encoding = null; + Type elementType = property.EntityCollectionItemType ?? property.NullablePropertyType; + IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); + ContentTypeUtil.ReadContentType(this.ContentType, out mimeType, out encoding); + + using (Stream responseStream = this.GetResponseStream()) + { + // special case byte[], and for everything else let std conversion kick-in + if (property.PropertyType == typeof(byte[])) + { + int total = checked((int)this.ContentLength); + byte[] buffer = null; + if (total >= 0) + { + buffer = LoadPropertyResult.ReadByteArrayWithContentLength(responseStream, total); + } + else + { + buffer = LoadPropertyResult.ReadByteArrayChunked(responseStream); + } + + results.Add(buffer); + + property.SetValue(this.entity, buffer, this.propertyName, false); + } + else + { + // responseStream will disposed, StreamReader doesn't need to dispose of it. + StreamReader reader = new StreamReader(responseStream, encoding); + object convertedValue = property.PropertyType == typeof(string) ? + reader.ReadToEnd() : + ClientConvert.ChangeType(reader.ReadToEnd(), property.PropertyType); + results.Add(convertedValue); + + property.SetValue(this.entity, convertedValue, this.propertyName, false); + } + } + + if (property.MimeTypeProperty != null) + { + // an implication of this 3rd-arg-null is that mime type properties cannot be open props + property.MimeTypeProperty.SetValue(this.entity, mimeType, null, false); + } + + return MaterializeAtom.CreateWrapper(context, results); + } + finally + { + context.ApplyingChanges = merging; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/CollectionValueMaterializationPolicy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/CollectionValueMaterializationPolicy.cs new file mode 100644 index 0000000..7b2f118 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/CollectionValueMaterializationPolicy.cs @@ -0,0 +1,244 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using Microsoft.OData.Edm; + using DSClient = Microsoft.OData.Client; + + /// + /// Use this class to materialize objects provided from an . + /// + internal class CollectionValueMaterializationPolicy : MaterializationPolicy + { + /// The materializer context. + private readonly IODataMaterializerContext materializerContext; + + /// The primitive value materialization policy. + private PrimitiveValueMaterializationPolicy primitiveValueMaterializationPolicy; + + /// The instance annotation materialization policy. + private InstanceAnnotationMaterializationPolicy instanceAnnotationMaterializationPolicy; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The primitive policy. + internal CollectionValueMaterializationPolicy(IODataMaterializerContext context, PrimitiveValueMaterializationPolicy primitivePolicy) + { + this.materializerContext = context; + this.primitiveValueMaterializationPolicy = primitivePolicy; + } + + /// + /// Gets the instance annotation materialization policy. + /// + /// + /// The instance annotation materialization policy. + /// + internal InstanceAnnotationMaterializationPolicy InstanceAnnotationMaterializationPolicy + { + get + { + Debug.Assert(this.instanceAnnotationMaterializationPolicy != null, "instanceAnnotationMaterializationPolicy!= null"); + return this.instanceAnnotationMaterializationPolicy; + } + + set + { + this.instanceAnnotationMaterializationPolicy = value; + } + } + + /// + /// Creates Collection instance of store Collection items. + /// + /// ODataProperty instance representing the Collection as seen in the atom payload. + /// CLR type of the Collection as defined by the user. + /// Newly created Collection instance. Never null. + internal object CreateCollectionPropertyInstance(ODataProperty collectionProperty, Type userCollectionType) + { + Debug.Assert(collectionProperty != null, "collectionProperty != null"); + Debug.Assert(collectionProperty.Value != null, "Collection should have already been checked for nullness"); + Debug.Assert(userCollectionType != null, "userCollectionType != null"); + Debug.Assert(ClientTypeUtil.GetImplementationType(userCollectionType, typeof(ICollection<>)) != null, "Not a Collection - Collection types must implement ICollection<> interface."); + Debug.Assert( + !ClientTypeUtil.TypeIsEntity(ClientTypeUtil.GetImplementationType(userCollectionType, typeof(ICollection<>)).GetGenericArguments()[0], this.materializerContext.Model), + "Not a Collection - Collections cannot contain entities"); + Debug.Assert(!(collectionProperty.Value is ODataResourceSet) && !(collectionProperty.Value is ODataResource), "Collection properties should never materialized from entry or feed payload"); + + ODataCollectionValue collectionValue = collectionProperty.Value as ODataCollectionValue; + + // get a ClientType instance for the Collection property. This determines what type will be used later when creating the actual Collection instance + ClientTypeAnnotation collectionClientType = this.materializerContext.ResolveTypeForMaterialization(userCollectionType, collectionValue.TypeName); + + return this.CreateCollectionInstance(collectionClientType.EdmTypeReference as IEdmCollectionTypeReference, collectionClientType.ElementType, () => DSClient.Strings.AtomMaterializer_NoParameterlessCtorForCollectionProperty(collectionProperty.Name, collectionClientType.ElementTypeName)); + } + + /// + /// Creates the collection instance. + /// + /// The edm collection type reference. + /// Type of the client collection. + /// New Collection Instance. + internal object CreateCollectionInstance(IEdmCollectionTypeReference edmCollectionTypeReference, Type clientCollectionType) + { + Debug.Assert(edmCollectionTypeReference != null, "edmCollectionTypeReference!=null"); + Debug.Assert(clientCollectionType != null, "clientCollectionType!=null"); + return CreateCollectionInstance(edmCollectionTypeReference, clientCollectionType, () => DSClient.Strings.AtomMaterializer_MaterializationTypeError(clientCollectionType.FullName)); + } + + /// + /// Applies collectionValue item to the provided . + /// + /// Atom property containing materialized Collection items. + /// Collection instance. Must implement ICollection<T> where T is either primitive or complex type (not an entity). + /// Type of items in the Collection. Note: this could be calculated from collectionInstance but we already have it in upstream methods. + /// Action called actually add a Collection item to + /// If element type is nullable. + internal void ApplyCollectionDataValues( + ODataProperty collectionProperty, + object collectionInstance, + Type collectionItemType, + Action addValueToBackingICollectionInstance, + bool isElementNullable) + { + Debug.Assert(collectionProperty != null, "property != null"); + Debug.Assert(collectionProperty.Value != null, "Collection should have already been checked for nullness"); + Debug.Assert(collectionInstance != null, "collectionInstance != null"); + Debug.Assert(WebUtil.IsCLRTypeCollection(collectionInstance.GetType(), this.materializerContext.Model), "collectionInstance must be a CollectionValue"); + Debug.Assert(collectionItemType.IsAssignableFrom( + ClientTypeUtil.GetImplementationType(collectionInstance.GetType(), typeof(ICollection<>)).GetGenericArguments()[0]), + "collectionItemType has to match the collectionInstance generic type."); + Debug.Assert(!ClientTypeUtil.TypeIsEntity(collectionItemType, this.materializerContext.Model), "CollectionValues cannot contain entities"); + Debug.Assert(addValueToBackingICollectionInstance != null, "AddValueToBackingICollectionInstance != null"); + + ODataCollectionValue collectionValue = collectionProperty.Value as ODataCollectionValue; + this.ApplyCollectionDataValues( + collectionValue.Items, + collectionValue.TypeName, + collectionInstance, + collectionItemType, + addValueToBackingICollectionInstance, + isElementNullable); + + collectionProperty.SetMaterializedValue(collectionInstance); + } + + /// + /// Applies the collection data values to a collection instance. + /// + /// The items. + /// Name of the wire type. + /// The collection instance. + /// Type of the collection item. + /// The add value to backing I collection instance. + /// If element type is nullable. + internal void ApplyCollectionDataValues( + IEnumerable items, + string wireTypeName, + object collectionInstance, + Type collectionItemType, + Action addValueToBackingICollectionInstance, + bool isElementNullable) + { + Debug.Assert(collectionInstance != null, "collectionInstance != null"); + Debug.Assert(WebUtil.IsCLRTypeCollection(collectionInstance.GetType(), this.materializerContext.Model), "collectionInstance must be a CollectionValue"); + Debug.Assert(collectionItemType.IsAssignableFrom( + ClientTypeUtil.GetImplementationType(collectionInstance.GetType(), typeof(ICollection<>)).GetGenericArguments()[0]), + "collectionItemType has to match the collectionInstance generic type."); + Debug.Assert(!ClientTypeUtil.TypeIsEntity(collectionItemType, this.materializerContext.Model), "CollectionValues cannot contain entities"); + Debug.Assert(addValueToBackingICollectionInstance != null, "AddValueToBackingICollectionInstance != null"); + + // is the Collection not empty ? + if (items != null) + { + bool isCollectionItemTypePrimitive = PrimitiveType.IsKnownNullableType(collectionItemType); + + foreach (object item in items) + { + if (!isElementNullable && item == null) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Collection_NullCollectionItemsNotSupported); + } + + ODataEnumValue enumVal = null; + + // Is it a Collection of primitive types? + if (isCollectionItemTypePrimitive) + { + if (item is ODataCollectionValue) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed); + } + + object materializedValue = this.primitiveValueMaterializationPolicy.MaterializePrimitiveDataValueCollectionElement(collectionItemType, wireTypeName, item); + + addValueToBackingICollectionInstance(collectionInstance, materializedValue); + } + else if ((enumVal = item as ODataEnumValue) != null) + { + // TODO: use EnumValueMaterializationPolicy.MaterializeEnumDataValueCollectionElement() here + object tmpValue = EnumValueMaterializationPolicy.MaterializeODataEnumValue(collectionItemType, enumVal); + addValueToBackingICollectionInstance(collectionInstance, tmpValue); + } + else + { + if (item != null) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed); + } + + addValueToBackingICollectionInstance(collectionInstance, null); + } + } + } + } + + /// + /// Creates Collection instance of store Collection items. + /// + /// The edm collection type reference. + /// Type of the client collection. + /// Error to throw. + /// + /// Newly created Collection instance. Never null. + /// + private object CreateCollectionInstance(IEdmCollectionTypeReference edmCollectionTypeReference, Type clientCollectionType, Func error) + { + Debug.Assert(clientCollectionType != null, "clientCollectionType != null"); + Debug.Assert(edmCollectionTypeReference != null, "edmCollectionTypeReference != null"); + Debug.Assert(error != null, "error != null"); + + // DataServiceCollection cannot track non-entity types so it should not be used for storing primitive or complex types + if (ClientTypeUtil.IsDataServiceCollection(clientCollectionType)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities); + } + + try + { + return this.CreateNewInstance(edmCollectionTypeReference, clientCollectionType); + } +#if PORTABLELIB + catch (MissingMemberException ex) +#else + catch (MissingMethodException ex) +#endif + { + throw DSClient.Error.InvalidOperation(error(), ex); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntityTrackingAdapter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntityTrackingAdapter.cs new file mode 100644 index 0000000..a7cd7f1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntityTrackingAdapter.cs @@ -0,0 +1,232 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Diagnostics; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using DSClient = Microsoft.OData.Client; + + /// + /// Determines if there is an existing entity or whether a new one is created + /// + internal class EntityTrackingAdapter + { + /// Target instance that the materializer expects to update. + private object targetInstance; + + /// + /// Initializes a new instance of the class. + /// + /// The entity tracker. + /// The merge option. + /// The model. + /// The context. + internal EntityTrackingAdapter(EntityTrackerBase entityTracker, MergeOption mergeOption, ClientEdmModel model, DataServiceContext context) + { + this.MaterializationLog = new AtomMaterializerLog(mergeOption, model, entityTracker); + this.MergeOption = mergeOption; + this.EntityTracker = entityTracker; + this.Model = model; + this.Context = context; + } + + /// + /// Gets the value of the MergeOption + /// + internal MergeOption MergeOption { get; private set; } + + /// + /// Gets the Context + /// + /// Implementation Note, only used in when a new DataServiceCollection, + /// would like to remove this dependency but would need to change projection + /// plan, might happen in a subsequent refactor + /// + internal DataServiceContext Context { get; private set; } + + /// + /// Gets the materialization log. + /// + internal AtomMaterializerLog MaterializationLog { get; private set; } + + /// + /// Gets the entity tracker. + /// + internal EntityTrackerBase EntityTracker { get; private set; } + + /// + /// Gets the model. + /// + internal ClientEdmModel Model { get; private set; } + + /// + /// Target instance that the materializer expects to update. + /// + internal object TargetInstance + { + get + { + return this.targetInstance; + } + + set + { + Debug.Assert(value != null, "value != null -- otherwise we have no instance target."); + this.targetInstance = value; + } + } + + /// Resolved or creates an instance on the specified . + /// Entry on which to resolve or create an instance. + /// Expected type for the . + /// + /// After invocation, the ResolvedObject value of the + /// will be assigned, along with the ActualType value. + /// + /// True if an existing entity is found. + internal virtual bool TryResolveExistingEntity(MaterializerEntry entry, Type expectedEntryType) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(expectedEntryType != null, "expectedEntryType != null"); + Debug.Assert(entry.EntityHasBeenResolved == false, "entry.EntityHasBeenResolved == false"); + + // This will be the case when TargetInstance has been set. + if (this.TryResolveAsTarget(entry)) + { + return true; + } + + if (this.TryResolveAsExistingEntry(entry, expectedEntryType)) + { + return true; + } + + return false; + } + + /// + /// Tries to resolve the specified entry as an entry that has already been created in this materialization session or is already in the context. + /// + /// Entry to resolve. + /// Expected type of the entry. + /// True if the entry was resolved, otherwise False. + internal bool TryResolveAsExistingEntry(MaterializerEntry entry, Type expectedEntryType) + { + if (entry.Entry.IsTransient) + { + return false; + } + + // Resolution is based on the entry Id, so if we can't use that property, we don't even need to try to resolve it. + if (entry.IsTracking) + { + // The resolver methods below will both need access to Id, so first ensure it's not null + if (entry.Id == null) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MissingIdElement); + } + + return this.TryResolveAsCreated(entry) || this.TryResolveFromContext(entry, expectedEntryType); + } + + return false; + } + + /// Tries to resolve the object as the target one in a POST refresh. + /// Entry to resolve. + /// true if the entity was resolved; false otherwise. + private bool TryResolveAsTarget(MaterializerEntry entry) + { + if (entry.ResolvedObject == null) + { + return false; + } + + // The only case when the entity hasn't been resolved but + // it has already been set is when the target instance + // was set directly to refresh a POST. + Debug.Assert( + entry.ResolvedObject == this.TargetInstance, + "entry.ResolvedObject == this.TargetInstance -- otherwise there we ResolveOrCreateInstance more than once on the same entry"); + Debug.Assert( + this.MergeOption == MergeOption.OverwriteChanges || this.MergeOption == MergeOption.PreserveChanges, + "MergeOption.OverwriteChanges and MergeOption.PreserveChanges are the only expected values during SaveChanges"); + ClientEdmModel edmModel = this.Model; + entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(entry.ResolvedObject.GetType())); + this.MaterializationLog.FoundTargetInstance(entry); + entry.ShouldUpdateFromPayload = this.MergeOption != MergeOption.PreserveChanges; + entry.EntityHasBeenResolved = true; + return true; + } + + /// Tries to resolve the object as one from the context (only if tracking is enabled). + /// Entry to resolve. + /// Expected entry type for the specified . + /// true if the entity was resolved; false otherwise. + private bool TryResolveFromContext(MaterializerEntry entry, Type expectedEntryType) + { + Debug.Assert(entry.IsTracking, "Should not be trying to resolve the entry from the context if entry.isTracking is false."); + + // We should either create a new instance or grab one from the context. + if (this.MergeOption != MergeOption.NoTracking) + { + EntityStates state; + entry.ResolvedObject = this.EntityTracker.TryGetEntity(entry.Id, out state); + if (entry.ResolvedObject != null) + { + if (!expectedEntryType.IsInstanceOfType(entry.ResolvedObject)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_Current(expectedEntryType, entry.ResolvedObject.GetType())); + } + + ClientEdmModel edmModel = this.Model; + entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(entry.ResolvedObject.GetType())); + entry.EntityHasBeenResolved = true; + + // Note that deleted items will have their properties overwritten even + // if PreserveChanges is used as a merge option. + entry.ShouldUpdateFromPayload = + this.MergeOption == MergeOption.OverwriteChanges || + (this.MergeOption == MergeOption.PreserveChanges && state == EntityStates.Unchanged) || + (this.MergeOption == MergeOption.PreserveChanges && state == EntityStates.Deleted); + + this.MaterializationLog.FoundExistingInstance(entry); + + return true; + } + } + + return false; + } + + /// Tries to resolve the object from those created in this materialization session. + /// Entry to resolve. + /// true if the entity was resolved; false otherwise. + private bool TryResolveAsCreated(MaterializerEntry entry) + { + Debug.Assert(entry.IsTracking, "Should not be trying to resolve the entry from the current materialization session if entry.isTracking is false."); + + MaterializerEntry existingEntry; + if (!this.MaterializationLog.TryResolve(entry, out existingEntry)) + { + return false; + } + + Debug.Assert( + existingEntry.ResolvedObject != null, + "existingEntry.ResolvedObject != null -- how did it get there otherwise?"); + entry.ActualType = existingEntry.ActualType; + entry.ResolvedObject = existingEntry.ResolvedObject; + entry.CreatedByMaterializer = existingEntry.CreatedByMaterializer; + entry.ShouldUpdateFromPayload = existingEntry.ShouldUpdateFromPayload; + entry.EntityHasBeenResolved = true; + return true; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntryValueMaterializationPolicy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntryValueMaterializationPolicy.cs new file mode 100644 index 0000000..1e7b780 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EntryValueMaterializationPolicy.cs @@ -0,0 +1,679 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using DSClient = Microsoft.OData.Client; + + /// + /// Used to materialize entities from an to an object. + /// + internal class EntryValueMaterializationPolicy : StructuralValueMaterializationPolicy + { + /// Collection->Next Link Table for nested links + private readonly Dictionary nextLinkTable; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The entity tracking adapter. + /// The lazy primitive property converter. + /// The next link table. + internal EntryValueMaterializationPolicy( + IODataMaterializerContext context, + EntityTrackingAdapter entityTrackingAdapter, + DSClient.SimpleLazy lazyPrimitivePropertyConverter, + Dictionary nextLinkTable) + : base(context, lazyPrimitivePropertyConverter) + { + this.nextLinkTable = nextLinkTable; + this.EntityTrackingAdapter = entityTrackingAdapter; + } + + /// + /// Gets the Entity Materializer Context + /// + internal EntityTrackingAdapter EntityTrackingAdapter { get; private set; } + + /// + /// Validates the specified matches + /// the parsed . + /// + /// Property as understood by the type system. + /// Property as parsed. + internal static void ValidatePropertyMatch(ClientPropertyAnnotation property, ODataNestedResourceInfo link) + { + ValidatePropertyMatch(property, link, null, false /*performEntityCheck*/); + } + + /// + /// Validates the specified matches + /// the parsed . + /// + /// Property as understood by the type system. + /// Property as parsed. + internal static void ValidatePropertyMatch(ClientPropertyAnnotation property, ODataProperty atomProperty) + { + ValidatePropertyMatch(property, atomProperty, null, false /*performEntityCheck*/); + } + + /// + /// Validates the specified matches + /// the parsed . + /// + /// Property as understood by the type system. + /// Property as parsed. + /// Client Model. + /// whether to do the entity check or not. + /// The type + internal static Type ValidatePropertyMatch(ClientPropertyAnnotation property, ODataNestedResourceInfo link, ClientEdmModel model, bool performEntityCheck) + { + Debug.Assert(property != null, "property != null"); + Debug.Assert(link != null, "link != null"); + + Type propertyType = null; + if (link.IsCollection.HasValue) + { + if (link.IsCollection.Value) + { + // We need to fail if the payload states that the property is a navigation collection property or a complex collection property + // and in the client, the property is not a collection property. + if (!property.IsResourceSet) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(property.PropertyName)); + } + + propertyType = property.ResourceSetItemType; + } + else + { + if (property.IsResourceSet) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkEntryPropertyIsCollection(property.PropertyName)); + } + + propertyType = property.PropertyType; + } + } + + // If the server type and the client type does not match, we need to throw. + // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa + if (propertyType != null && performEntityCheck) + { + if (!ClientTypeUtil.TypeIsStructured(propertyType, model)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(propertyType.ToString())); + } + } + + return propertyType; + } + + /// + /// Validates the specified matches + /// the parsed . + /// + /// Property as understood by the type system. + /// Property as parsed. + /// Client model. + /// whether to do the entity check or not. + internal static void ValidatePropertyMatch(ClientPropertyAnnotation property, ODataProperty atomProperty, ClientEdmModel model, bool performEntityCheck) + { + Debug.Assert(property != null, "property != null"); + Debug.Assert(atomProperty != null, "atomProperty != null"); + + ODataResourceSet feed = atomProperty.Value as ODataResourceSet; + ODataResource entry = atomProperty.Value as ODataResource; + + if (property.IsKnownType && (feed != null || entry != null)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkLocalSimple); + } + + Type propertyType = null; + if (feed != null) + { + // We need to fail if the payload states that the property is a navigation collection property + // and in the client, the property is not a collection property. + if (!property.IsEntityCollection) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(property.PropertyName)); + } + + propertyType = property.EntityCollectionItemType; + } + + if (entry != null) + { + if (property.IsEntityCollection) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkEntryPropertyIsCollection(property.PropertyName)); + } + + propertyType = property.PropertyType; + } + + // If the server type and the client type does not match, we need to throw. + // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa + if (propertyType != null && performEntityCheck) + { + if (!ClientTypeUtil.TypeIsEntity(propertyType, model)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(propertyType.ToString())); + } + } + } + + /// Materializes the specified . + /// Entry with object to materialize. + /// Expected type for the entry. + /// Whether links that are expanded should be materialized. + /// This is a payload-driven materialization process. + internal void Materialize(MaterializerEntry entry, Type expectedEntryType, bool includeLinks) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert( + entry.ResolvedObject == null || entry.ResolvedObject == this.EntityTrackingAdapter.TargetInstance, + "entry.ResolvedObject == null || entry.ResolvedObject == this.targetInstance -- otherwise getting called twice"); + Debug.Assert(expectedEntryType != null, "expectedType != null"); + + // TODO : Need to handle complex type with no tracking and entity with tracking but no id. + // ResolvedObject will already be assigned when we have a TargetInstance. + if ((entry.IsTracking && entry.Id == null) + || !this.EntityTrackingAdapter.TryResolveExistingEntity(entry, expectedEntryType)) + { + // If the type is a derived one this call will resolve to derived type, cannot put code in ResolveByCreatingWithType as this is used by projection and in this case + // the type cannot be changed or updated + ClientTypeAnnotation actualType = this.MaterializerContext.ResolveTypeForMaterialization(expectedEntryType, entry.Entry.TypeName); + + this.ResolveByCreatingWithType(entry, actualType.ElementType); + } + + Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise ResolveOrCreateInstnace didn't do its job"); + + this.MaterializeResolvedEntry(entry, includeLinks); + } + + /// + /// Applies the values of the enumeration to the + /// of the specified . + /// + /// Entry with collection to be modified. + /// Collection property on the entry. + /// Values to apply onto the collection. + /// Next link for collection continuation. + /// Projection plan for collection continuation. + /// Whether this is a continuation request. + internal void ApplyItemsToCollection( + MaterializerEntry entry, + ClientPropertyAnnotation property, + IEnumerable items, + Uri nextLink, + ProjectionPlan continuationPlan, + bool isContinuation) + { + Debug.Assert(entry.Entry != null || entry.ForLoadProperty, "ODataResource should be non-null except for LoadProperty"); + Debug.Assert(property != null, "property != null"); + Debug.Assert(items != null, "items != null"); + + IEnumerable itemsEnumerable = ODataEntityMaterializer.EnumerateAsElementType(items); + + // Populate the collection property with items collection. + object collection = this.PopulateCollectionProperty(entry, property, itemsEnumerable, nextLink, continuationPlan); + + // Get collection of all non-linked elements in collection and remove them except for the ones that were obtained from the response. + if (this.EntityTrackingAdapter.MergeOption == MergeOption.OverwriteChanges || + this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges) + { + var linkedItemsInCollection = + this.EntityTrackingAdapter + .EntityTracker + .GetLinks(entry.ResolvedObject, property.PropertyName) + .Select(l => new { l.Target, l.IsModified }); + + if (collection != null && !property.IsDictionary) + { + var nonLinkedNonReceivedItemsInCollection = ODataEntityMaterializer.EnumerateAsElementType((IEnumerable)collection) + .Except(linkedItemsInCollection.Select(i => i.Target)) + .Except(itemsEnumerable).ToArray(); + + // Since no link exists, we just remove the item from the collection + foreach (var item in nonLinkedNonReceivedItemsInCollection) + { + property.RemoveValue(collection, item); + } + } + + // When the first time a property or collection is being loaded, we remove all items other than the ones that we receive. + if (!isContinuation) + { + IEnumerable itemsToRemove; + + if (this.EntityTrackingAdapter.MergeOption == MergeOption.OverwriteChanges) + { + itemsToRemove = linkedItemsInCollection.Select(i => i.Target); + } + else + { + Debug.Assert( + this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges, + "this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges"); + + itemsToRemove = linkedItemsInCollection + .Where(i => !i.IsModified) + .Select(i => i.Target); + } + + itemsToRemove = itemsToRemove.Except(itemsEnumerable); + + foreach (var item in itemsToRemove) + { + if (collection != null) + { + property.RemoveValue(collection, item); + } + + this.EntityTrackingAdapter.MaterializationLog.RemovedLink(entry, property.PropertyName, item); + } + } + } + } + + /// Records the fact that a rel='next' link was found for the specified . + /// Collection to add link to. + /// Link (possibly null). + /// Projection plan for the collection (null allowed only if link is null). + internal void FoundNextLinkForCollection(IEnumerable collection, Uri link, ProjectionPlan plan) + { + Debug.Assert(plan != null || link == null, "plan != null || link == null"); + + if (collection != null && !this.nextLinkTable.ContainsKey(collection)) + { + DataServiceQueryContinuation continuation = DataServiceQueryContinuation.Create(link, plan); + this.nextLinkTable.Add(collection, continuation); + Util.SetNextLinkForCollection(collection, continuation); + } + } + + /// Records the fact that a was found but won't be modified. + /// Collection to add link to. + internal void FoundNextLinkForUnmodifiedCollection(IEnumerable collection) + { + if (collection != null && !this.nextLinkTable.ContainsKey(collection)) + { + this.nextLinkTable.Add(collection, null); + } + } + + /// "Resolved" the entity in the by instantiating it. + /// Entry to resolve. + /// Type to create. + /// + /// After invocation, entry.ResolvedObject is exactly of type . + /// + internal void ResolveByCreatingWithType(MaterializerEntry entry, Type type) + { + // TODO: CreateNewInstance needs to do all of these operations otherwise an inadvertent call to CreateNewInstance + // will create a new entity instance that is not tracked in the context or materialization log. Will need to change this + // prior to shipping if public + Debug.Assert( + entry.ResolvedObject == null, + "entry.ResolvedObject == null -- otherwise we're about to overwrite - should never be called"); + ClientEdmModel edmModel = this.MaterializerContext.Model; + entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(type)); + entry.ResolvedObject = this.CreateNewInstance(entry.ActualType.EdmTypeReference, type); + entry.CreatedByMaterializer = true; + entry.ShouldUpdateFromPayload = true; + entry.EntityHasBeenResolved = true; + this.EntityTrackingAdapter.MaterializationLog.CreatedInstance(entry); + } + + /// + /// Matches the given item type with the corresponding collection element type. + /// + /// Item type. + /// Collection element type. + private static void ValidateCollectionElementTypeIsItemType(Type itemType, Type collectionElementType) + { + if (!collectionElementType.IsAssignableFrom(itemType)) + { + string message = DSClient.Strings.AtomMaterializer_EntryIntoCollectionMismatch( + itemType.FullName, + collectionElementType.FullName); + + throw new InvalidOperationException(message); + } + } + + /// + /// Materializes the link properties if any with the url in the response payload + /// + /// Actual client type that is getting materialized. + /// MaterializerEntry instance containing all the links that came in the response. + private static void ApplyLinkProperties( + ClientTypeAnnotation actualType, + MaterializerEntry entry) + { + Debug.Assert(actualType != null, "actualType != null"); + Debug.Assert(entry.Entry != null, "entry != null"); + + if (entry.ShouldUpdateFromPayload) + { + foreach (var linkProperty in actualType.Properties().Where(p => p.PropertyType == typeof(DataServiceStreamLink))) + { + string propertyName = linkProperty.PropertyName; + StreamDescriptor streamDescriptor; + if (entry.EntityDescriptor.TryGetNamedStreamInfo(propertyName, out streamDescriptor)) + { + // At this time we have materialized the stream link onto the stream descriptor object + // we'll always make sure the stream link is the same instance on the entity and the descriptor + linkProperty.SetValue(entry.ResolvedObject, streamDescriptor.StreamLink, propertyName, true /*allowAdd*/); + } + } + } + } + + /// + /// Populates the collection property on the entry's resolved object with the given items enumerator. + /// + /// Entry with collection to be modified. + /// Collection property on the entry. + /// Values to apply onto the collection. + /// Next link for collection continuation. + /// Projection plan for collection continuation. + /// Collection instance that was populated. + private object PopulateCollectionProperty( + MaterializerEntry entry, + ClientPropertyAnnotation property, + IEnumerable items, + Uri nextLink, + ProjectionPlan continuationPlan) + { + Debug.Assert(entry.Entry != null || entry.ForLoadProperty, "ODataResource should be non-null except for LoadProperty"); + Debug.Assert(property != null, "property != null"); + Debug.Assert(items != null, "items != null"); + + object collection = null; + + ClientEdmModel edmModel = this.MaterializerContext.Model; + ClientTypeAnnotation collectionType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(property.ResourceSetItemType)); + + if (entry.ShouldUpdateFromPayload) + { + collection = this.GetOrCreateCollectionProperty(entry.ResolvedObject, property, entry.ForLoadProperty); + + foreach (object item in items) + { + // Validate that item can be inserted into collection. + ValidateCollectionElementTypeIsItemType(item.GetType(), collectionType.ElementType); + + property.SetValue(collection, item, property.PropertyName, true /* allowAdd? */); + + this.EntityTrackingAdapter.MaterializationLog.AddedLink(entry, property.PropertyName, item); + } + + this.FoundNextLinkForCollection(collection as IEnumerable, nextLink, continuationPlan); + } + else + { + Debug.Assert(!entry.ForLoadProperty, "LoadProperty should always have ShouldUpateForPayload set to true."); + + foreach (object item in items) + { + // Validate that item can be inserted into collection. + ValidateCollectionElementTypeIsItemType(item.GetType(), collectionType.ElementType); + } + + this.FoundNextLinkForUnmodifiedCollection(property.GetValue(entry.ResolvedObject) as IEnumerable); + } + + return collection; + } + + /// + /// Gets or creates a collection property on the specified . + /// + /// Instance on which to get/create the collection. + /// Collection property on the . + /// Is this collection being created for LoadProperty scenario. + /// + /// The collection corresponding to the specified ; + /// never null. + /// + private object GetOrCreateCollectionProperty(object instance, ClientPropertyAnnotation property, bool forLoadProperty) + { + Debug.Assert(instance != null, "instance != null"); + Debug.Assert(property != null, "property != null"); + Debug.Assert(property.IsResourceSet, "property.IsEntityCollection has to be true - otherwise property isn't a collection"); + + // NOTE: in V1, we would have instantiated nested objects before setting them. + object result; + result = property.GetValue(instance); + + if (result == null) + { + Type collectionType = property.PropertyType; + + // For backward compatiblity we need to have different strategy of collection creation b/w + // LoadProperty scenario versus regular collection creation scenario. + if (forLoadProperty) + { + if (BindingEntityInfo.IsDataServiceCollection(collectionType, this.MaterializerContext.Model)) + { + Debug.Assert(WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType) != null, "DataServiceCollection<> must be available here."); + + // new DataServiceCollection(null, TrackingMode.None) + result = Activator.CreateInstance( + WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType), + null, + TrackingMode.None); + } + else + { + // Try List<> first because that's what we did in V1/V2, but use the actual property type if it doesn't support List<> + Type listCollectionType = typeof(List<>).MakeGenericType(property.EntityCollectionItemType); + if (collectionType.IsAssignableFrom(listCollectionType)) + { + collectionType = listCollectionType; + } + + result = Activator.CreateInstance(collectionType); + } + } + else + { + if (DSClient.PlatformHelper.IsInterface(collectionType)) + { + collectionType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(property.EntityCollectionItemType); + } + + result = this.CreateNewInstance(property.EdmProperty.Type, collectionType); + } + + // Update the property value on the instance. + property.SetValue(instance, result, property.PropertyName, false /* add */); + } + + Debug.Assert(result != null, "result != null -- otherwise GetOrCreateCollectionProperty didn't fall back to creation"); + return result; + } + + /// + /// Applies the values of a nested to the collection + /// of the specified . + /// + /// Entry with collection to be modified. + /// Collection property on the entry. + /// Values to apply onto the collection. + /// Whether links that are expanded should be materialized. + private void ApplyFeedToCollection( + MaterializerEntry entry, + ClientPropertyAnnotation property, + ODataResourceSet feed, + bool includeLinks) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(property != null, "property != null"); + Debug.Assert(feed != null, "feed != null"); + + ClientEdmModel edmModel = this.MaterializerContext.Model; + ClientTypeAnnotation collectionType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(property.ResourceSetItemType)); + + IEnumerable entries = MaterializerFeed.GetFeed(feed).Entries; + + foreach (ODataResource feedEntry in entries) + { + this.Materialize(MaterializerEntry.GetEntry(feedEntry), collectionType.ElementType, includeLinks); + } + + ProjectionPlan continuationPlan = includeLinks ? + ODataEntityMaterializer.CreatePlanForDirectMaterialization(property.ResourceSetItemType) : + ODataEntityMaterializer.CreatePlanForShallowMaterialization(property.ResourceSetItemType); + + this.ApplyItemsToCollection( + entry, + property, + entries.Select(e => MaterializerEntry.GetEntry(e).ResolvedObject), + feed.NextPageLink, + continuationPlan, + false); + } + + /// Materializes the specified . + /// Entry with object to materialize. + /// Whether links that are expanded for navigation property should be materialized. + /// This is a payload-driven materialization process. + private void MaterializeResolvedEntry(MaterializerEntry entry, bool includeLinks) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise not resolved/created!"); + + ClientTypeAnnotation actualType = entry.ActualType; + + // While materializing entities, we need to make sure the payload that came in the wire is also an entity. + // Otherwise we need to fail. + // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa + if (!actualType.IsStructuredType) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(actualType.ElementTypeName)); + } + + // Note that even if ShouldUpdateFromPayload is false, we will still be creating + // nested instances (but not their links), so they show up in the data context + // entries. This keeps this code compatible with V1 behavior. + this.MaterializeDataValues(actualType, entry.Properties, this.MaterializerContext.UndeclaredPropertyBehavior); + + if (entry.NestedResourceInfos != null) + { + foreach (ODataNestedResourceInfo link in entry.NestedResourceInfos) + { + var prop = actualType.GetProperty(link.Name, UndeclaredPropertyBehavior.Support); + if (prop != null) + { + ValidatePropertyMatch(prop, link, this.MaterializerContext.Model, true /*performEntityCheck*/); + } + } + + foreach (ODataNestedResourceInfo link in entry.NestedResourceInfos) + { + MaterializerNavigationLink linkState = MaterializerNavigationLink.GetLink(link); + + if (linkState == null) + { + continue; + } + + var prop = actualType.GetProperty(link.Name, this.MaterializerContext.UndeclaredPropertyBehavior); + + if (prop == null) + { + continue; + } + + // includeLinks is for Navigation property, so we should handle complex property when includeLinks equals false; + if (!includeLinks && (prop.IsEntityCollection || prop.EntityCollectionItemType != null)) + { + continue; + } + + if (linkState.Feed != null) + { + this.ApplyFeedToCollection(entry, prop, linkState.Feed, includeLinks); + } + else if (linkState.Entry != null) + { + MaterializerEntry linkEntry = linkState.Entry; + + if (linkEntry.Entry != null) + { + this.Materialize(linkEntry, prop.PropertyType, includeLinks); + } + + if (entry.ShouldUpdateFromPayload) + { + prop.SetValue(entry.ResolvedObject, linkEntry.ResolvedObject, link.Name, true /* allowAdd? */); + + if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization && linkEntry.ShouldUpdateFromPayload) + { + // Apply instance annotation for navigation property + this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations( + prop.PropertyName, linkEntry.Entry, entry.ActualType.ElementType, entry.ResolvedObject); + + // Apply instance annotation for entity of the navigation property + this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(linkEntry.Entry, linkEntry.ResolvedObject); + } + + this.EntityTrackingAdapter.MaterializationLog.SetLink(entry, prop.PropertyName, linkEntry.ResolvedObject); + } + } + } + } + + foreach (var e in entry.Properties) + { + if (e.Value is ODataStreamReferenceValue) + { + continue; + } + + var prop = actualType.GetProperty(e.Name, this.MaterializerContext.UndeclaredPropertyBehavior); + if (prop == null) + { + continue; + } + + if (entry.ShouldUpdateFromPayload) + { + ValidatePropertyMatch(prop, e, this.MaterializerContext.Model, true /*performEntityCheck*/); + + this.ApplyDataValue(actualType, e, entry.ResolvedObject); + } + } + + // apply link values if present + ApplyLinkProperties(actualType, entry); + + Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise we didn't do any useful work"); + + BaseEntityType entity = entry.ResolvedObject as BaseEntityType; + if (entity != null) + { + entity.Context = this.EntityTrackingAdapter.Context; + } + + this.MaterializerContext.ResponsePipeline.FireEndEntryEvents(entry); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EnumValueMaterializationPolicy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EnumValueMaterializationPolicy.cs new file mode 100644 index 0000000..398b3cf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/EnumValueMaterializationPolicy.cs @@ -0,0 +1,121 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Diagnostics; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using DSClient = Microsoft.OData.Client; + + /// + /// Creates a policy that is used for materializing Enum values + /// + internal class EnumValueMaterializationPolicy + { + /// MaterializerContext used to resolve types for materialization. + private readonly IODataMaterializerContext context; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + internal EnumValueMaterializationPolicy(IODataMaterializerContext context) + { + this.context = context; + } + + /// + /// Materializes the enum data value. + /// + /// Type of the collection item. + /// The ODataProperty. + /// Materialized enum data CLR value. + public object MaterializeEnumTypeProperty(Type valueType, ODataProperty property) + { + object materializedValue = null; + ODataEnumValue value = property.Value as ODataEnumValue; + this.MaterializeODataEnumValue(valueType, value.TypeName, value.Value, () => "TODO: Is this reachable?", out materializedValue); + if (!property.HasMaterializedValue()) + { + property.SetMaterializedValue(materializedValue); + } + + return materializedValue; + } + + /// + /// Materializes the enum data value collection element. + /// + /// The collection item type. + /// Name of the wire type. + /// The ODataEnumValue object. + /// Materialized enum collection element value + public object MaterializeEnumDataValueCollectionElement(Type collectionItemType, string wireTypeName, string item) + { + object materializedValue = null; + this.MaterializeODataEnumValue(collectionItemType, wireTypeName, item, () => DSClient.Strings.Collection_NullCollectionItemsNotSupported, out materializedValue); + + return materializedValue; + } + + /// Materializes an enum value. No op or non-primitive values. + /// The clr Type of value to set. + /// The value of enum. + /// The materialized value. + internal static object MaterializeODataEnumValue(Type enumType, ODataEnumValue enumValue) + { + object tmpValue; + if (enumValue == null) + { + tmpValue = null; + } + else + { + // TODO: Find better way to parse Enum + string enumValueStr = enumValue.Value.Trim(); + Type underlyingType = Nullable.GetUnderlyingType(enumType) ?? enumType; + if (!Enum.IsDefined(underlyingType, enumValueStr)) + { + tmpValue = Enum.Parse(underlyingType, ClientTypeUtil.GetClientFieldName(underlyingType, enumValueStr), false); + } + else + { + tmpValue = Enum.Parse(underlyingType, enumValueStr, false); + } + } + + return tmpValue; + } + + /// Materializes an enum value. No op or non-primitive values. + /// The clr Type of value to set. + /// The Type name from the payload. + /// The string of enum value. + /// The exception message if the value is null. + /// The materialized value. + private void MaterializeODataEnumValue(Type type, string wireTypeName, string enumValueStr, Func throwOnNullMessage, out object materializedValue) + { + Debug.Assert(type != null, "type != null"); + Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; + ClientTypeAnnotation elementTypeAnnotation = this.context.ResolveTypeForMaterialization(underlyingType, wireTypeName); + Debug.Assert(elementTypeAnnotation != null, "elementTypeAnnotation != null"); + + // TODO: Find better way to parse Enum + Type enumClrType = elementTypeAnnotation.ElementType; + enumValueStr = enumValueStr.Trim(); + if (!Enum.IsDefined(enumClrType, enumValueStr)) + { + materializedValue = Enum.Parse(enumClrType, ClientTypeUtil.GetClientFieldName(enumClrType, enumValueStr), false); + } + else + { + materializedValue = Enum.Parse(enumClrType, enumValueStr, false); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/FeedAndEntryMaterializerAdapter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/FeedAndEntryMaterializerAdapter.cs new file mode 100644 index 0000000..54f3b76 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/FeedAndEntryMaterializerAdapter.cs @@ -0,0 +1,468 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData; + using Microsoft.OData.Client; + using DSClient = Microsoft.OData.Client; + + /// + /// Class for reading top level feeds or entries and adapting it for the materializer + /// + internal class FeedAndEntryMaterializerAdapter + { + /// The odata format being read. + private readonly ODataFormat readODataFormat; + + /// The reader. + private readonly ODataReaderWrapper reader; + + /// The Client Edm Model used to determine type information. + private readonly ClientEdmModel clientEdmModel; + + /// MergeOption information to determine how to merge descriptors. + private readonly MergeOption mergeOption; + + /// An enumerator of values. + private IEnumerator feedEntries; + + /// The current feed. + private ODataResourceSet currentFeed; + + /// The current entry. + private ODataResource currentEntry; + + /// + /// Initializes a new instance of the class. + /// + /// The messageReader that is used to get the format of the reader. + /// The reader. + /// The model. + /// The mergeOption. + internal FeedAndEntryMaterializerAdapter(ODataMessageReader messageReader, ODataReaderWrapper reader, ClientEdmModel model, MergeOption mergeOption) + : this(ODataUtils.GetReadFormat(messageReader), reader, model, mergeOption) + { + } + + /// + /// Initializes a new instance of the class. Used for tests so no ODataMessageReader is required + /// + /// The format of the reader. + /// The reader. + /// The model. + /// The mergeOption. + internal FeedAndEntryMaterializerAdapter(ODataFormat odataFormat, ODataReaderWrapper reader, ClientEdmModel model, MergeOption mergeOption) + { + this.readODataFormat = odataFormat; + this.clientEdmModel = model; + this.mergeOption = mergeOption; + this.reader = reader; + this.currentEntry = null; + this.currentFeed = null; + this.feedEntries = null; + } + + /// + /// Gets the current feed. + /// + public ODataResourceSet CurrentFeed + { + get { return this.currentFeed; } + } + + /// + /// Gets the current entry. + /// + public ODataResource CurrentEntry + { + get { return this.currentEntry; } + } + + /// + /// Gets a value indicating whether this instance is end of stream. + /// + /// + /// true if this instance is end of stream; otherwise, false. + /// + public bool IsEndOfStream + { + get { return this.reader.State == ODataReaderState.Completed; } + } + + /// + /// The count tag's value, if requested + /// + /// Should read pull if no feed exists. + /// The count value returned from the server + public long GetCountValue(bool readIfNoFeed) + { + if (this.currentFeed == null && this.currentEntry == null && readIfNoFeed && this.TryReadFeed(true, out this.currentFeed)) + { + this.feedEntries = MaterializerFeed.GetFeed(this.currentFeed).Entries.GetEnumerator(); + } + + if (this.currentFeed != null && this.currentFeed.Count.HasValue) + { + return this.currentFeed.Count.Value; + } + + throw new InvalidOperationException(DSClient.Strings.MaterializeFromAtom_CountNotPresent); + } + + /// + /// Read a feed or entry, with the expected type. + /// + /// true if a value was read, otherwise false + public bool Read() + { + if (this.feedEntries != null) + { + // ISSUE: this might throw - refactor? + if (this.feedEntries.MoveNext()) + { + this.currentEntry = this.feedEntries.Current; + return true; + } + else + { + this.feedEntries = null; + this.currentEntry = null; + } + } + + switch (this.reader.State) + { + case ODataReaderState.Completed: + this.currentEntry = null; + return false; + case ODataReaderState.Start: + { + ODataResourceSet feed; + MaterializerEntry entryAndState; + if (this.TryReadFeedOrEntry(true, out feed, out entryAndState)) + { + this.currentEntry = entryAndState != null ? entryAndState.Entry : null; + this.currentFeed = feed; + if (this.currentFeed != null) + { + this.feedEntries = MaterializerFeed.GetFeed(this.currentFeed).Entries.GetEnumerator(); + + // Try to read the first entry. + if (!this.feedEntries.MoveNext()) + { + this.feedEntries = null; + this.currentEntry = null; + return false; + } + else + { + this.currentEntry = this.feedEntries.Current; + } + } + + return true; + } + else + { + throw new NotImplementedException(); + } + } + + case ODataReaderState.ResourceSetEnd: + case ODataReaderState.ResourceEnd: + if (this.TryRead() || this.reader.State != ODataReaderState.Completed) + { + throw DSClient.Error.InternalError(InternalError.UnexpectedReadState); + } + + this.currentEntry = null; + return false; + default: + throw DSClient.Error.InternalError(InternalError.UnexpectedReadState); + } + } + + /// + /// Disposes the reader + /// + public void Dispose() + { + if (this.feedEntries != null) + { + this.feedEntries.Dispose(); + this.feedEntries = null; + } + } + + /// + /// Tries to read a feed or entry. + /// + /// if set to true [lazy]. + /// The feed. + /// The entry. + /// true if a value was read, otherwise false + private bool TryReadFeedOrEntry(bool lazy, out ODataResourceSet feed, out MaterializerEntry entry) + { + if (this.TryStartReadFeedOrEntry()) + { + if (this.reader.State == ODataReaderState.ResourceStart) + { + entry = this.ReadEntryCore(); + feed = null; + } + else + { + entry = null; + feed = this.ReadFeedCore(lazy); + } + } + else + { + feed = null; + entry = null; + } + + Debug.Assert(feed == null || entry == null, "feed == null || entry == null"); + return feed != null || entry != null; + } + + /// + /// Tries to read the start of a feed or entry. + /// + /// true if a value was read, otherwise false + private bool TryStartReadFeedOrEntry() + { + return this.TryRead() && (this.reader.State == ODataReaderState.ResourceSetStart || this.reader.State == ODataReaderState.ResourceStart); + } + + /// + /// Tries to read a feed. + /// + /// if set to true [lazy]. + /// The feed. + /// true if a value was read, otherwise false + private bool TryReadFeed(bool lazy, out ODataResourceSet feed) + { + if (this.TryStartReadFeedOrEntry()) + { + this.ExpectState(ODataReaderState.ResourceSetStart); + feed = this.ReadFeedCore(lazy); + } + else + { + feed = null; + } + + return feed != null; + } + + /// + /// Reads the remainder of a feed. + /// + /// if set to true [lazy]. + /// A feed. + private ODataResourceSet ReadFeedCore(bool lazy) + { + this.ExpectState(ODataReaderState.ResourceSetStart); + + ODataResourceSet result = (ODataResourceSet)this.reader.Item; + + IEnumerable lazyEntries = this.LazyReadEntries(); + + if (lazy) + { + MaterializerFeed.CreateFeed(result, lazyEntries); + } + else + { + MaterializerFeed.CreateFeed(result, new List(lazyEntries)); + } + + return result; + } + + /// + /// Lazily reads entries. + /// + /// An enumerable that will lazily read entries when enumerated. + private IEnumerable LazyReadEntries() + { + MaterializerEntry entryAndState; + while (this.TryReadEntry(out entryAndState)) + { + yield return entryAndState.Entry; + } + } + + /// + /// Tries to read an entry. + /// + /// The entry. + /// true if a value was read, otherwise false + private bool TryReadEntry(out MaterializerEntry entry) + { + if (this.TryStartReadFeedOrEntry()) + { + this.ExpectState(ODataReaderState.ResourceStart); + entry = this.ReadEntryCore(); + return true; + } + else + { + entry = default(MaterializerEntry); + return false; + } + } + + /// + /// Reads the remainder of an entry. + /// + /// An entry. + private MaterializerEntry ReadEntryCore() + { + this.ExpectState(ODataReaderState.ResourceStart); + + ODataResource result = (ODataResource)this.reader.Item; + + MaterializerEntry entry; + List navigationLinks = new List(); + if (result != null) + { + entry = MaterializerEntry.CreateEntry( + result, + this.readODataFormat, + this.mergeOption != MergeOption.NoTracking, + this.clientEdmModel); + + do + { + this.AssertRead(); + + switch (this.reader.State) + { + case ODataReaderState.NestedResourceInfoStart: + // Cache the list of navigation links here but don't add them to the entry because all of the key properties may not be available yet. + navigationLinks.Add(this.ReadNestedResourceInfo()); + break; + case ODataReaderState.ResourceEnd: + break; + default: + throw DSClient.Error.InternalError(InternalError.UnexpectedReadState); + } + } + while (this.reader.State != ODataReaderState.ResourceEnd); + + if (!entry.Entry.IsTransient) + { + entry.UpdateEntityDescriptor(); + } + } + else + { + entry = MaterializerEntry.CreateEmpty(); + this.ReadAndExpectState(ODataReaderState.ResourceEnd); + } + + // Add the navigation links here now that all of the property values have been read and are available to build the links. + foreach (ODataNestedResourceInfo navigationLink in navigationLinks) + { + entry.AddNestedResourceInfo(navigationLink); + } + + return entry; + } + + /// + /// Reads a navigation link. + /// + /// A navigation link. + private ODataNestedResourceInfo ReadNestedResourceInfo() + { + Debug.Assert(this.reader.State == ODataReaderState.NestedResourceInfoStart, "this.reader.State == ODataReaderState.NestedResourceInfoStart"); + + ODataNestedResourceInfo link = (ODataNestedResourceInfo)this.reader.Item; + + MaterializerEntry entry; + ODataResourceSet feed; + if (this.TryReadFeedOrEntry(false, out feed, out entry)) + { + if (feed != null) + { + MaterializerNavigationLink.CreateLink(link, feed); + } + else + { + Debug.Assert(entry != null, "entry != null"); + MaterializerNavigationLink.CreateLink(link, entry); + } + + this.ReadAndExpectState(ODataReaderState.NestedResourceInfoEnd); + } + + this.ExpectState(ODataReaderState.NestedResourceInfoEnd); + + return link; + } + + /// + /// Tries to read from the ODataReader. + /// + /// True if a value is read, otherwise false + private bool TryRead() + { + try + { + return this.reader.Read(); + } + catch (ODataErrorException e) + { + throw new DataServiceClientException(DSClient.Strings.Deserialize_ServerException(e.Error.Message), e); + } + catch (ODataException o) + { + throw new InvalidOperationException(o.Message, o); + } + } + + /// + /// Reads from the reader and asserts the reader is in the expected state. + /// + /// The expected state. + private void ReadAndExpectState(ODataReaderState expectedState) + { + this.AssertRead(); + + this.ExpectState(expectedState); + } + + /// + /// Asserts that an item could be read. + /// + private void AssertRead() + { + if (!this.TryRead()) + { + throw DSClient.Error.InternalError(InternalError.UnexpectedReadState); + } + } + + /// + /// Asserts the reader is in the expected state. + /// + /// The expected state. + private void ExpectState(ODataReaderState expectedState) + { + if (this.reader.State != expectedState) + { + throw DSClient.Error.InternalError(InternalError.UnexpectedReadState); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/HttpWebResponseMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/HttpWebResponseMessage.cs new file mode 100644 index 0000000..7d2dbd8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/HttpWebResponseMessage.cs @@ -0,0 +1,193 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Net; + using Microsoft.OData; + + /// + /// IODataResponseMessage interface implementation + /// + public class HttpWebResponseMessage : IODataResponseMessage, IDisposable + { + /// Cached headers. + private readonly HeaderCollection headers; + + /// A func which returns the response stream. + private readonly Func getResponseStream; + + /// The response status code. + private readonly int statusCode; + + /// HttpWebResponse instance. + private HttpWebResponse httpWebResponse; + +#if DEBUG + /// set to true once the GetStream was called. + private bool streamReturned; +#endif + + /// + /// Constructor. + /// + /// The headers. + /// The status code. + /// A function returning the response stream. + public HttpWebResponseMessage(IDictionary headers, int statusCode, Func getResponseStream) + { + Debug.Assert(headers != null, "headers != null"); + Debug.Assert(getResponseStream != null, "getResponseStream != null"); + + this.headers = new HeaderCollection(headers); + this.statusCode = statusCode; + this.getResponseStream = getResponseStream; + } + + /// + /// Constructor. + /// + /// HttpWebResponse instance. + public HttpWebResponseMessage(HttpWebResponse httpResponse) + { + Util.CheckArgumentNull(httpResponse, "httpResponse"); + this.headers = new HeaderCollection(httpResponse.Headers); + this.statusCode = (int)httpResponse.StatusCode; + this.getResponseStream = httpResponse.GetResponseStream; + this.httpWebResponse = httpResponse; + } + + /// + /// Constructor. + /// + /// The headers. + /// The status code. + /// A function returning the response stream. + internal HttpWebResponseMessage(HeaderCollection headers, int statusCode, Func getResponseStream) + { + Debug.Assert(headers != null, "headers != null"); + Debug.Assert(getResponseStream != null, "getResponseStream != null"); + + this.headers = headers; + this.statusCode = statusCode; + this.getResponseStream = getResponseStream; + } + + /// + /// Returns the collection of response headers. + /// + public virtual IEnumerable> Headers + { + get + { + return this.headers.AsEnumerable(); + } + } + + /// + /// Gets the underlying . + /// + public System.Net.HttpWebResponse Response + { + get + { + return this.httpWebResponse; + } + } + + /// + /// The response status code. + /// + public virtual int StatusCode + { + get + { + return this.statusCode; + } + + set + { + throw new NotSupportedException(); + } + } + + /// + /// Returns the value of the header with the given name. + /// + /// Name of the header. + /// Returns the value of the header with the given name. + public virtual string GetHeader(string headerName) + { + Util.CheckArgumentNullAndEmpty(headerName, "headerName"); + string result; + if (this.headers.TryGetHeader(headerName, out result)) + { + return result; + } + + // Since the unintialized value of ContentLength header is -1, we need to return + // -1 if the content length header is not present + if (string.Equals(headerName, XmlConstants.HttpContentLength, StringComparison.Ordinal)) + { + return "-1"; + } + + return null; + } + + /// + /// Sets the value of the header with the given name. + /// + /// Name of the header. + /// Value of the header. + public virtual void SetHeader(string headerName, string headerValue) + { + throw new NotSupportedException(); + } + + /// + /// Gets the stream to be used to read the response payload. + /// + /// Stream from which the response payload can be read. + public virtual Stream GetStream() + { +#if DEBUG + Debug.Assert(!this.streamReturned, "The GetStream can only be called once."); + this.streamReturned = true; +#endif + + return this.getResponseStream(); + } + + /// + /// Close the underlying HttpWebResponse. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected virtual void Dispose(bool disposing) + { + HttpWebResponse response = this.httpWebResponse; + this.httpWebResponse = null; + if (response != null) + { + ((IDisposable)response).Dispose(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/IODataMaterializerContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/IODataMaterializerContext.cs new file mode 100644 index 0000000..31639b1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/IODataMaterializerContext.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + + /// + /// Context for materialization of OData values + /// + internal interface IODataMaterializerContext + { + /// + /// The DataServiceContext associated with this materializer context + /// + DataServiceContext Context { get; } + + /// + /// Gets a value indicating whether to support untyped properties is set or not + /// + UndeclaredPropertyBehavior UndeclaredPropertyBehavior { get; } + + /// + /// Gets a value indicated the Client Edm Model + /// + ClientEdmModel Model { get; } + + /// + /// Gets the materialization Events + /// + DataServiceClientResponsePipelineConfiguration ResponsePipeline { get; } + + /// + /// Resolves the client type that should be used for materialization. + /// + /// Expected client clr type based on the API called. + /// + /// The name surfaced by the ODataLib reader. + /// If we have a server model, this will be a server type name that needs to be resolved. + /// If not, then this will already be a client type name. + /// The resolved annotation for the client type to materialize into. + ClientTypeAnnotation ResolveTypeForMaterialization(Type expectedType, string readerTypeName); + + /// + /// Resolves the expected EDM type to give to the ODataLib reader based on a client CLR type. + /// + /// The client side CLR type. + /// The resolved EDM type to provide to ODataLib. + IEdmType ResolveExpectedTypeForReading(Type clientClrType); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/InstanceAnnotationMaterializationPolicy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/InstanceAnnotationMaterializationPolicy.cs new file mode 100644 index 0000000..c27a05e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/InstanceAnnotationMaterializationPolicy.cs @@ -0,0 +1,323 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using Microsoft.OData.Edm; + + /// + /// Use this class to materialize instance annnotations in an . + /// + internal class InstanceAnnotationMaterializationPolicy + { + /// + /// The collection value materialization policy. + /// + private CollectionValueMaterializationPolicy collectionValueMaterializationPolicy; + + /// + /// The enum value materialization policy + /// + private EnumValueMaterializationPolicy enumValueMaterializationPolicy; + + /// + /// Initializes a new instance of the class. + /// + /// The materializer context. + internal InstanceAnnotationMaterializationPolicy(IODataMaterializerContext materializerContext) + { + Debug.Assert(materializerContext != null, "materializer!=null"); + this.MaterializerContext = materializerContext; + } + + /// + /// The collection value materialization policy. + /// + internal CollectionValueMaterializationPolicy CollectionValueMaterializationPolicy + { + get + { + Debug.Assert(this.collectionValueMaterializationPolicy != null, "collectionValueMaterializationPolicy != null"); + return this.collectionValueMaterializationPolicy; + } + + set + { + this.collectionValueMaterializationPolicy = value; + } + } + + /// + /// The Enum value materialization policy. + /// + internal EnumValueMaterializationPolicy EnumValueMaterializationPolicy + { + get + { + Debug.Assert(this.EnumValueMaterializationPolicy != null, "enumValueMaterializationPolicy != null"); + return this.enumValueMaterializationPolicy; + } + + set + { + this.enumValueMaterializationPolicy = value; + } + } + + /// + /// The materializer context. + /// + internal IODataMaterializerContext MaterializerContext { get; private set; } + + /// + /// Materialize instance annotation for an OData entry + /// + /// Odata entry + /// Client clr object for the OData entry + internal void SetInstanceAnnotations(ODataResource entry, object entity) + { + if (entry != null) + { + IDictionary instanceAnnotations = this.ConvertToClrInstanceAnnotations(entry.InstanceAnnotations); + SetInstanceAnnotations(entity, instanceAnnotations); + } + } + + /// + /// Materialize instance annotation for an OData property + /// + /// OData property + /// the clr object of the property + internal void SetInstanceAnnotations(ODataProperty property, object instance) + { + if (property != null) + { + IDictionary instanceAnnotations = this.GetClrInstanceAnnotationsFromODataProperty(property); + SetInstanceAnnotations(instance, instanceAnnotations); + } + } + + /// + /// Materialize instance annotation for OData property + /// + /// OData property + /// The type of declaringInstance + /// the client object that the property belongs to + internal void SetInstanceAnnotations(ODataProperty property, Type type, object declaringInstance) + { + if (property != null) + { + IDictionary instanceAnnotations = this.GetClrInstanceAnnotationsFromODataProperty(property); + SetInstanceAnnotations(property.Name, instanceAnnotations, type, declaringInstance); + } + } + + /// + /// Materialize instance annotation for OData navigation property + /// + /// navigation property name + /// OData single navigation property + /// The type of the declaringInstance + /// the client object that the navigation property belongs to + internal void SetInstanceAnnotations(string navigationPropertyName, ODataResource navigationProperty, Type type, object declaringInstance) + { + if (navigationProperty != null) + { + IDictionary instanceAnnotations = this.ConvertToClrInstanceAnnotations(navigationProperty.InstanceAnnotations); + SetInstanceAnnotations(navigationPropertyName, instanceAnnotations, type, declaringInstance); + } + } + + /// + /// Convert a collection of instance annotations to clr objects. + /// + /// A collection of instance annotation to be converted + /// A dictionary of clr-typed instance annotation + internal IDictionary ConvertToClrInstanceAnnotations(ICollection instanceAnnotations) + { + var clientInstanceAnnotationValue = new Dictionary(StringComparer.Ordinal); + if (instanceAnnotations != null) + { + foreach (var instanceAnnotation in instanceAnnotations) + { + object clrInstanceAnnotation; + if (TryConvertToClrInstanceAnnotation(instanceAnnotation, out clrInstanceAnnotation)) + { + clientInstanceAnnotationValue.Add(instanceAnnotation.Name, clrInstanceAnnotation); + } + } + } + + return clientInstanceAnnotationValue; + } + + /// + /// Set instance annotations for an object + /// + /// Object instance + /// The instance annotations to be set + private void SetInstanceAnnotations(object instance, IDictionary instanceAnnotations) + { + if (instance != null) + { + this.MaterializerContext.Context.InstanceAnnotations.Remove(instance); + if (instanceAnnotations != null && instanceAnnotations.Count > 0) + { + this.MaterializerContext.Context.InstanceAnnotations.Add(instance, instanceAnnotations); + } + } + } + + /// + /// Set instance annotation for a property + /// + /// Property name + /// Intance annotations to be set + /// The type of the containing object + /// The containing object instance + private void SetInstanceAnnotations(string propertyName, IDictionary instanceAnnotations, Type type, object declaringInstance) + { + if (declaringInstance != null) + { + UndeclaredPropertyBehavior undeclaredPropertyBehavior = this.MaterializerContext.Context.UndeclaredPropertyBehavior; + + // Get the client property info + ClientEdmModel edmModel = this.MaterializerContext.Model; + ClientTypeAnnotation clientTypeAnnotation = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(type)); + ClientPropertyAnnotation clientPropertyAnnotation = clientTypeAnnotation.GetProperty(propertyName, undeclaredPropertyBehavior); + Tuple annotationKeyForProperty = new Tuple(declaringInstance, clientPropertyAnnotation.PropertyInfo); + SetInstanceAnnotations(annotationKeyForProperty, instanceAnnotations); + } + } + + /// + /// Convert instance annotations of the ODataProperty to clr objects. + /// + /// OData property + /// A dictionary of clr-typed instance annotation which is materialized from the instance annotations of the ODataProperty + private IDictionary GetClrInstanceAnnotationsFromODataProperty(ODataProperty property) + { + IDictionary clientInstanceAnnotationValue = null; + + if (clientInstanceAnnotationValue == null) + { + clientInstanceAnnotationValue = new Dictionary(StringComparer.Ordinal); + } + + if (property.InstanceAnnotations != null) + { + foreach (var instanceAnnotation in property.InstanceAnnotations) + { + object clrInstanceAnnotation; + if (TryConvertToClrInstanceAnnotation(instanceAnnotation, out clrInstanceAnnotation)) + { + clientInstanceAnnotationValue.Add(instanceAnnotation.Name, clrInstanceAnnotation); + } + } + } + + return clientInstanceAnnotationValue; + } + + /// + /// Convert an instance annotation to clr object. + /// + /// Instance annotation to be converted + /// The clr object + /// A dictionary of clr-typed instance annotation + private bool TryConvertToClrInstanceAnnotation(ODataInstanceAnnotation instanceAnnotation, out object clrInstanceAnnotation) + { + clrInstanceAnnotation = null; + + var primitiveValue = instanceAnnotation.Value as ODataPrimitiveValue; + if (primitiveValue != null) + { + clrInstanceAnnotation = primitiveValue.Value; + return true; + } + + var enumValue = instanceAnnotation.Value as ODataEnumValue; + if (enumValue != null) + { + var type = this.MaterializerContext.Context.ResolveTypeFromName(enumValue.TypeName); + if (type != null) + { + clrInstanceAnnotation = EnumValueMaterializationPolicy.MaterializeODataEnumValue(type, enumValue); + return true; + } + + return false; + } + + var collectionValue = instanceAnnotation.Value as ODataCollectionValue; + if (collectionValue != null) + { + var serverSideModel = this.MaterializerContext.Context.Format.LoadServiceModel(); + var valueTerm = serverSideModel.FindTerm(instanceAnnotation.Name); + + if (valueTerm != null && valueTerm.Type != null && valueTerm.Type.Definition != null) + { + var edmCollectionType = valueTerm.Type.Definition as IEdmCollectionType; + if (edmCollectionType != null) + { + Type collectionItemType = null; + var elementType = edmCollectionType.ElementType; + PrimitiveType primitiveType; + if (PrimitiveType.TryGetPrimitiveType(elementType.FullName(), out primitiveType)) + { + collectionItemType = primitiveType.ClrType; + } + else + { + collectionItemType = this.MaterializerContext.Context.ResolveTypeFromName(elementType.FullName()); + } + + if (collectionItemType != null) + { + Type collectionICollectionType = typeof(ICollection<>).MakeGenericType(new Type[] { collectionItemType }); + + ClientTypeAnnotation collectionClientTypeAnnotation = this.MaterializerContext.ResolveTypeForMaterialization( + collectionICollectionType, + collectionValue.TypeName); + bool isElementNullable = edmCollectionType.ElementType.IsNullable; + + var collectionInstance = this.CollectionValueMaterializationPolicy.CreateCollectionInstance( + collectionClientTypeAnnotation.EdmTypeReference as IEdmCollectionTypeReference, + collectionClientTypeAnnotation.ElementType); + this.CollectionValueMaterializationPolicy.ApplyCollectionDataValues( + collectionValue.Items, + collectionValue.TypeName, + collectionInstance, + collectionItemType, + ClientTypeUtil.GetAddToCollectionDelegate(collectionICollectionType), + isElementNullable); + clrInstanceAnnotation = collectionInstance; + return true; + } + } + } + + return false; + } + + var nullValue = instanceAnnotation.Value as ODataNullValue; + if (nullValue != null) + { + clrInstanceAnnotation = null; + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializationPolicy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializationPolicy.cs new file mode 100644 index 0000000..ce40aac --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializationPolicy.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using Microsoft.OData.Client; + using Microsoft.OData.Edm; + + /// + /// Class responsible for materializing from OData to Objects + /// + internal abstract class MaterializationPolicy + { + /// + /// Creates the specified edm type. + /// + /// Type of the edm. + /// The type. + /// In the future this class will have Materialize and Update will be adding this in upcoming changes + /// A created object + public virtual object CreateNewInstance(IEdmTypeReference edmTypeReference, Type type) + { + return Util.ActivatorCreateInstance(type); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerEntry.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerEntry.cs new file mode 100644 index 0000000..b0bdd76 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerEntry.cs @@ -0,0 +1,396 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData; + using Microsoft.OData.Client.Metadata; + using DSClient = Microsoft.OData.Client; + + /// + /// Materializer state for a given ODataResource + /// + internal class MaterializerEntry + { + /// The entry. + private readonly ODataResource entry; + + /// entity descriptor object which keeps track of the entity state and other entity specific information. + private readonly EntityDescriptor entityDescriptor; + + /// True if the context format is Atom or if the MergeOption is anything other than NoTracking. + private readonly bool isTracking; + + /// Entry flags. + private EntryFlags flags; + + /// List of navigation links for this entry. + private ICollection navigationLinks = ODataMaterializer.EmptyLinks; + + /// + /// Creates a new instance of MaterializerEntry. + /// + private MaterializerEntry() + { + } + + /// + /// Creates a new instance of MaterializerEntry. + /// + /// The entry. + /// The format the entry was read in. + /// True if the contents of the entry will be tracked in the context, otherwise False. + /// The client model. + private MaterializerEntry(ODataResource entry, ODataFormat format, bool isTracking, ClientEdmModel model) + { + Debug.Assert(entry != null, "entry != null"); + + this.entry = entry; + this.Format = format; + this.entityDescriptor = new EntityDescriptor(model); + this.isTracking = isTracking; + + string serverTypeName = this.Entry.TypeName; + if (entry.TypeAnnotation != null) + { + // If the annotation has a value use it. Otherwise, in JSON-Light, the types can be inferred from the + // context URI even if they are not present on the wire, so just use the type name from the entry. + if (entry.TypeAnnotation.TypeName != null || this.Format != ODataFormat.Json) + { + serverTypeName = entry.TypeAnnotation.TypeName; + } + } + + this.entityDescriptor.ServerTypeName = serverTypeName; + } + + /// + /// Creates a new instance of MaterializerEntry using the given entity descriptor for LoadProperty. + /// + /// Entity descriptor. + /// OData Format. + /// Whether this entity is being tracked. + /// Use this constructor only for LoadProperty scenario. + private MaterializerEntry(EntityDescriptor entityDescriptor, ODataFormat format, bool isTracking) + { + this.entityDescriptor = entityDescriptor; + this.Format = format; + this.isTracking = isTracking; + this.SetFlagValue(EntryFlags.ShouldUpdateFromPayload | EntryFlags.EntityHasBeenResolved | EntryFlags.ForLoadProperty, true); + } + + /// + /// Masks used get/set the status of the entry + /// + [Flags] + private enum EntryFlags + { + /// Bitmask for ShouldUpdateFromPayload flag. + ShouldUpdateFromPayload = 0x01, + + /// Bitmask for CreatedByMaterializer flag. + CreatedByMaterializer = 0x02, + + /// Bitmask for EntityHasBeenResolved flag. + EntityHasBeenResolved = 0x04, + + /// Bitmask for MediaLinkEntry flag (value). + EntityDescriptorUpdated = 0x08, + + /// Bitmask for LoadProperty scenario. + ForLoadProperty = 0x10, + } + + /// + /// Gets the entry. + /// + public ODataResource Entry + { + get { return this.entry; } + } + + /// + /// True if the context format is Atom or if the context's MergeOption is anything other than NoTracking. + /// This is used to avoid building URI metadata information that is not needed outside of the context, such + /// as odata.id and odata.editlink. Since this information is always available in the payload with Atom, for + /// backward compatibility we continue using it as we always have, even for NoTracking cases. + /// + public bool IsTracking + { + get { return this.isTracking; } + } + + /// + /// Entry ID. + /// + public Uri Id + { + get + { + Debug.Assert(this.IsTracking, "Id property should not be used when this.isTracking is false."); + return this.entry.Id; + } + } + + /// + /// Properties of the entry. + /// + /// + /// Non-property content goes to annotations. + /// + public IEnumerable Properties + { + get { return this.entry != null ? this.entry.Properties : null; } + } + + /// The entity descriptor. + public EntityDescriptor EntityDescriptor + { + get { return this.entityDescriptor; } + } + + /// Resolved object. + public object ResolvedObject + { + get { return this.entityDescriptor != null ? this.entityDescriptor.Entity : null; } + set { this.entityDescriptor.Entity = value; } + } + + /// Actual type of the ResolvedObject. + public ClientTypeAnnotation ActualType { get; set; } + + /// Whether values should be updated from payload. + public bool ShouldUpdateFromPayload + { + get { return this.GetFlagValue(EntryFlags.ShouldUpdateFromPayload); } + set { this.SetFlagValue(EntryFlags.ShouldUpdateFromPayload, value); } + } + + /// Whether the entity has been resolved / created. + public bool EntityHasBeenResolved + { + get { return this.GetFlagValue(EntryFlags.EntityHasBeenResolved); } + set { this.SetFlagValue(EntryFlags.EntityHasBeenResolved, value); } + } + + /// Whether the materializer has created the ResolvedObject instance. + public bool CreatedByMaterializer + { + get { return this.GetFlagValue(EntryFlags.CreatedByMaterializer); } + set { this.SetFlagValue(EntryFlags.CreatedByMaterializer, value); } + } + + /// Is this entry created for LoadProperty. + public bool ForLoadProperty + { + get { return this.GetFlagValue(EntryFlags.ForLoadProperty); } + } + + /// The navigation links. + public ICollection NestedResourceInfos + { + get { return this.navigationLinks; } + } + + /// Gets the format + internal ODataFormat Format { get; private set; } + + /// Whether the entity descriptor has been updated. + private bool EntityDescriptorUpdated + { + get { return this.GetFlagValue(EntryFlags.EntityDescriptorUpdated); } + set { this.SetFlagValue(EntryFlags.EntityDescriptorUpdated, value); } + } + + /// + /// Creates an empty entry. + /// + /// An empty entry. + public static MaterializerEntry CreateEmpty() + { + return new MaterializerEntry(); + } + + /// + /// Creates the materializer entry. + /// + /// The entry. + /// The format the entry was read in. + /// True if the contents of the entry will be tracked in the context, otherwise False. + /// The client model. + /// A new materializer entry. + public static MaterializerEntry CreateEntry(ODataResource entry, ODataFormat format, bool isTracking, ClientEdmModel model) + { + Debug.Assert(entry.GetAnnotation() == null, "MaterializerEntry has already been created."); + + MaterializerEntry materializerEntry = new MaterializerEntry(entry, format, isTracking, model); + entry.SetAnnotation(materializerEntry); + + return materializerEntry; + } + + /// + /// Creates the materializer entry for LoadProperty scenario. + /// + /// The entity descriptor. + /// The format the entry was read in. + /// True if the contents of the entry will be tracked in the context, otherwise False. + /// A new materializer entry. + public static MaterializerEntry CreateEntryForLoadProperty(EntityDescriptor descriptor, ODataFormat format, bool isTracking) + { + return new MaterializerEntry(descriptor, format, isTracking); + } + + /// + /// Gets an entry for a given ODataResource. + /// + /// The ODataResource. + /// The materializer entry + public static MaterializerEntry GetEntry(ODataResource entry) + { + return entry.GetAnnotation(); + } + + /// + /// Adds a navigation link. + /// + /// The link. + public void AddNestedResourceInfo(ODataNestedResourceInfo link) + { + if (this.IsTracking) + { + this.EntityDescriptor.AddNestedResourceInfo(link.Name, link.Url); + Uri associationLinkUrl = link.AssociationLinkUrl; + if (associationLinkUrl != null) + { + this.EntityDescriptor.AddAssociationLink(link.Name, associationLinkUrl); + } + } + + if (this.navigationLinks == ODataMaterializer.EmptyLinks) + { + this.navigationLinks = new List(); + } + + this.navigationLinks.Add(link); + } + + /// + /// Updates the entity descriptor. + /// + public void UpdateEntityDescriptor() + { + if (!this.EntityDescriptorUpdated) + { + // Named stream properties are represented on the result type as a DataServiceStreamLink, which contains the + // ReadLink and EditLink for the stream. We need to build this metadata information even with NoTracking, + // because it is exposed on the result instances directly, not just in the context. + foreach (ODataProperty property in this.Properties) + { + ODataStreamReferenceValue streamValue = property.Value as ODataStreamReferenceValue; + if (streamValue != null) + { + StreamDescriptor streamInfo = this.EntityDescriptor.AddStreamInfoIfNotPresent(property.Name); + + if (streamValue.ReadLink != null) + { + streamInfo.SelfLink = streamValue.ReadLink; + } + + if (streamValue.EditLink != null) + { + streamInfo.EditLink = streamValue.EditLink; + } + + streamInfo.ETag = streamValue.ETag; + + streamInfo.ContentType = streamValue.ContentType; + } + } + + if (this.IsTracking) + { + if (this.Id == null) + { + // TODO: Remove these lines since complex type doesn't have Id. + // throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MissingIdElement); + } + + this.EntityDescriptor.Identity = this.entry.Id; + this.EntityDescriptor.EditLink = this.entry.EditLink; + this.EntityDescriptor.SelfLink = this.entry.ReadLink; + this.EntityDescriptor.ETag = this.entry.ETag; + + if (this.entry.MediaResource != null) + { + if (this.entry.MediaResource.ReadLink != null) + { + this.EntityDescriptor.ReadStreamUri = this.entry.MediaResource.ReadLink; + } + + if (this.entry.MediaResource.EditLink != null) + { + this.EntityDescriptor.EditStreamUri = this.entry.MediaResource.EditLink; + } + + if (this.entry.MediaResource.ETag != null) + { + this.EntityDescriptor.StreamETag = this.entry.MediaResource.ETag; + } + } + + if (this.entry.Functions != null) + { + foreach (ODataFunction function in this.entry.Functions) + { + this.EntityDescriptor.AddOperationDescriptor(new FunctionDescriptor { Title = function.Title, Metadata = function.Metadata, Target = function.Target }); + } + } + + if (this.entry.Actions != null) + { + foreach (ODataAction action in this.entry.Actions) + { + this.EntityDescriptor.AddOperationDescriptor(new ActionDescriptor { Title = action.Title, Metadata = action.Metadata, Target = action.Target }); + } + } + } + + this.EntityDescriptorUpdated = true; + } + } + + #region Private methods. + + /// Gets the value for a masked item. + /// Mask value. + /// true if the flag is set; false otherwise. + private bool GetFlagValue(EntryFlags mask) + { + return (this.flags & mask) != 0; + } + + /// Sets the value for a masked item. + /// Mask value. + /// Value to set + private void SetFlagValue(EntryFlags mask, bool value) + { + if (value) + { + this.flags |= mask; + } + else + { + this.flags &= (~mask); + } + } + + #endregion Private methods. + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerFeed.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerFeed.cs new file mode 100644 index 0000000..d6d6c3c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerFeed.cs @@ -0,0 +1,97 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData; + + /// + /// Materializer state for a given ODataResourceSet + /// + internal struct MaterializerFeed + { + /// The feed. + private readonly ODataResourceSet feed; + + /// The entries. + private readonly IEnumerable entries; + + /// + /// Prevents a default instance of the struct from being created. + /// + /// The feed. + /// The entries. + private MaterializerFeed(ODataResourceSet feed, IEnumerable entries) + { + Debug.Assert(feed != null, "feed != null"); + Debug.Assert(entries != null, "entries != null"); + + this.feed = feed; + this.entries = entries; + } + + /// + /// Gets the feed. + /// + public ODataResourceSet Feed + { + get { return this.feed; } + } + + /// + /// Gets the entries. + /// + public IEnumerable Entries + { + get { return this.entries; } + } + + /// + /// URI representing the next page link. + /// + public Uri NextPageLink + { + get { return this.feed.NextPageLink; } + } + + /// + /// Creates the materializer feed. + /// + /// The feed. + /// The entries. + /// The materializer feed. + public static MaterializerFeed CreateFeed(ODataResourceSet feed, IEnumerable entries) + { + Debug.Assert(feed.GetAnnotation>() == null, "Feed state has already been created."); + if (entries == null) + { + entries = Enumerable.Empty(); + } + else + { + feed.SetAnnotation>(entries); + } + + return new MaterializerFeed(feed, entries); + } + + /// + /// Gets the materializer feed. + /// + /// The feed. + /// The materializer feed. + public static MaterializerFeed GetFeed(ODataResourceSet feed) + { + IEnumerable entries = feed.GetAnnotation>(); + return new MaterializerFeed(feed, entries); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerNavigationLink.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerNavigationLink.cs new file mode 100644 index 0000000..e8a62be --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/MaterializerNavigationLink.cs @@ -0,0 +1,99 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System.Diagnostics; + using Microsoft.OData; + + /// + /// Materializer state for a given ODataNestedResourceInfo + /// + internal class MaterializerNavigationLink + { + /// The navigation link. + private readonly ODataNestedResourceInfo link; + + /// An object field for the feed or enty. + private readonly object feedOrEntry; + + /// + /// Prevents a default instance of the struct from being created. + /// + /// The link. + /// Value of the link. + private MaterializerNavigationLink(ODataNestedResourceInfo link, object materializedFeedOrEntry) + { + Debug.Assert(link != null, "link != null"); + Debug.Assert(materializedFeedOrEntry != null, "materializedFeedOrEntry != null"); + Debug.Assert(materializedFeedOrEntry is MaterializerEntry || materializedFeedOrEntry is ODataResourceSet, "must be feed or entry"); + this.link = link; + this.feedOrEntry = materializedFeedOrEntry; + } + + /// + /// Gets the link. + /// + public ODataNestedResourceInfo Link + { + get { return this.link; } + } + + /// + /// Gets the entry. + /// + public MaterializerEntry Entry + { + get { return this.feedOrEntry as MaterializerEntry; } + } + + /// + /// Gets the feed. + /// + public ODataResourceSet Feed + { + get { return this.feedOrEntry as ODataResourceSet; } + } + + /// + /// Creates the materializer link with an entry. + /// + /// The link. + /// The entry. + /// The materializer link. + public static MaterializerNavigationLink CreateLink(ODataNestedResourceInfo link, MaterializerEntry entry) + { + Debug.Assert(link.GetAnnotation() == null, "there should be no MaterializerNestedResourceInfo annotation on the entry link yet"); + MaterializerNavigationLink materializedNestedResourceInfo = new MaterializerNavigationLink(link, entry); + link.SetAnnotation(materializedNestedResourceInfo); + return materializedNestedResourceInfo; + } + + /// + /// Creates the materializer link with a resource set. + /// + /// The link. + /// The resource set. + /// The materializer link. + public static MaterializerNavigationLink CreateLink(ODataNestedResourceInfo link, ODataResourceSet resourceSet) + { + Debug.Assert(link.GetAnnotation() == null, "there should be no MaterializerNestedResourceInfo annotation on the feed link yet"); + MaterializerNavigationLink materializedNestedResourceInfo = new MaterializerNavigationLink(link, resourceSet); + link.SetAnnotation(materializedNestedResourceInfo); + return materializedNestedResourceInfo; + } + + /// + /// Gets the materializer link. + /// + /// The link. + /// The materializer link. + public static MaterializerNavigationLink GetLink(ODataNestedResourceInfo link) + { + return link.GetAnnotation(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataCollectionMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataCollectionMaterializer.cs new file mode 100644 index 0000000..fede6d6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataCollectionMaterializer.cs @@ -0,0 +1,169 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using Microsoft.OData.Edm; + using DSClient = Microsoft.OData.Client; + + /// + /// Used to materialize a collection of primitive or complex values from an . + /// + internal sealed class ODataCollectionMaterializer : ODataMessageReaderMaterializer + { + /// Current value being materialized; possibly null. + private object currentValue; + + /// + /// Initializes a new instance of the class. + /// + /// The reader. + /// The materializer context. + /// The expected type. + /// The single result. + public ODataCollectionMaterializer(ODataMessageReader reader, IODataMaterializerContext materializerContext, Type expectedType, bool? singleResult) + : base(reader, materializerContext, expectedType, singleResult) + { + } + + /// + /// Current value being materialized; possibly null. + /// + internal override object CurrentValue + { + get { return this.currentValue; } + } + + /// + /// Reads a value from the message reader. + /// + /// The expected client type being materialized into. + /// The expected type for the underlying reader. + protected override void ReadWithExpectedType(IEdmTypeReference expectedClientType, IEdmTypeReference expectedReaderType) + { + if (!expectedClientType.IsCollection()) + { + throw new DataServiceClientException(DSClient.Strings.AtomMaterializer_TypeShouldBeCollectionError(expectedClientType.FullName())); + } + + Type underlyingExpectedType = Nullable.GetUnderlyingType(this.ExpectedType) ?? this.ExpectedType; + Debug.Assert(WebUtil.IsCLRTypeCollection(underlyingExpectedType, this.MaterializerContext.Model) + || (SingleResult.HasValue && !SingleResult.Value), "expected type must be collection or single result must be false"); + + // We are here for two cases: + // (1) Something like Execute>, in which case the underlyingExpectedType is ICollection + // (2) Execute with the bool singleValue = false, in which case underlyingExpectedType is T + Type collectionItemType = underlyingExpectedType; + Type collectionICollectionType = ClientTypeUtil.GetImplementationType(underlyingExpectedType, typeof(ICollection<>)); + + if (collectionICollectionType != null) + { + // Case 1 : Something like Execute>, in which case the underlyingExpectedType is ICollection + collectionItemType = collectionICollectionType.GetGenericArguments()[0]; + } + else + { + // Case 2 : Execute with the bool singleValue = false, in which case underlyingExpectedType is T + collectionICollectionType = typeof(ICollection<>).MakeGenericType(new Type[] { collectionItemType }); + } + + Type clrCollectionType = WebUtil.GetBackingTypeForCollectionProperty(collectionICollectionType); + object collectionInstance = this.CollectionValueMaterializationPolicy.CreateCollectionInstance((IEdmCollectionTypeReference)expectedClientType, clrCollectionType); + + // Enumerator over our collection reader was created, then ApplyDataCollections was refactored to + // take an enumerable instead of a ODataCollectionValue. Enumerator is being used as a bridge + ODataCollectionReader collectionReader = messageReader.CreateODataCollectionReader(); + NonEntityItemsEnumerable collectionEnumerable = new NonEntityItemsEnumerable(collectionReader); + + bool isElementNullable = expectedClientType.AsCollection().ElementType().IsNullable; + this.CollectionValueMaterializationPolicy.ApplyCollectionDataValues( + collectionEnumerable, + null /*wireTypeName*/, + collectionInstance, + collectionItemType, + ClientTypeUtil.GetAddToCollectionDelegate(collectionICollectionType), + isElementNullable); + + this.currentValue = collectionInstance; + } + + /// + /// Class that wraps the collection reader to get values from the collection reader + /// + private class NonEntityItemsEnumerable : IEnumerable, IEnumerator + { + /// + /// Collection Reader + /// + private readonly ODataCollectionReader collectionReader; + + /// + /// Initializes a new instance of the class. + /// + /// The collection reader. + internal NonEntityItemsEnumerable(ODataCollectionReader collectionReader) + { + this.collectionReader = collectionReader; + } + + /// + /// Gets the current element in the collection. + /// + /// The current element in the collection. + /// The enumerator is positioned before the first element of the collection or after the last element.-or- The collection was modified after the enumerator was created. + public object Current + { + get { return this.collectionReader.Item; } + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator() + { + return this; + } + + /// + /// Advances the enumerator to the next element of the collection. + /// + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// + /// The collection was modified after the enumerator was created. + public bool MoveNext() + { + // Move to the next value + bool read = this.collectionReader.Read(); + while (read && this.collectionReader.State != ODataCollectionReaderState.Value) + { + read = this.collectionReader.Read(); + } + + return read; + } + + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + /// The collection was modified after the enumerator was created. + public void Reset() + { + throw new InvalidOperationException(DSClient.Strings.AtomMaterializer_ResetAfterEnumeratorCreationError); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializer.cs new file mode 100644 index 0000000..e2fbf88 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializer.cs @@ -0,0 +1,1056 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.OData; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + using DSClient = Microsoft.OData.Client; + + /// + /// Used to materialize entities from a objects. + /// + internal abstract class ODataEntityMaterializer : ODataMaterializer + { + /// The value of the current materialized entity. + protected object currentValue; + + /// The materializer plan. + private readonly ProjectionPlan materializeEntryPlan; + + /// The entry value materializer policy. + private readonly EntryValueMaterializationPolicy entryValueMaterializationPolicy; + + /// + /// Initializes a new instance of the class. + /// + /// The materializer context. + /// The entity tracking adapter. + /// The query components. + /// The expected type. + /// The materialize entry plan. + public ODataEntityMaterializer( + IODataMaterializerContext materializerContext, + EntityTrackingAdapter entityTrackingAdapter, + QueryComponents queryComponents, + Type expectedType, + ProjectionPlan materializeEntryPlan) + : base(materializerContext, expectedType) + { + this.materializeEntryPlan = materializeEntryPlan ?? CreatePlan(queryComponents); + this.EntityTrackingAdapter = entityTrackingAdapter; + DSClient.SimpleLazy converter = new DSClient.SimpleLazy(() => new PrimitivePropertyConverter()); + + this.entryValueMaterializationPolicy = new EntryValueMaterializationPolicy(this.MaterializerContext, this.EntityTrackingAdapter, converter, nextLinkTable); + this.entryValueMaterializationPolicy.CollectionValueMaterializationPolicy = this.CollectionValueMaterializationPolicy; + this.entryValueMaterializationPolicy.InstanceAnnotationMaterializationPolicy = this.InstanceAnnotationMaterializationPolicy; + } + + /// + /// Gets the Entity Materializer Context + /// + internal EntityTrackingAdapter EntityTrackingAdapter { get; private set; } + + /// + /// Target instance that the materializer expects to update. + /// + internal object TargetInstance + { + get + { + return this.EntityTrackingAdapter.TargetInstance; + } + + set + { + Debug.Assert(value != null, "value != null -- otherwise we have no instance target."); + this.EntityTrackingAdapter.TargetInstance = value; + } + } + + /// + /// Current value being materialized; possibly null. + /// + internal sealed override object CurrentValue + { + get { return this.currentValue; } + } + + /// + /// Function to materialize an entry and produce a value. + /// + internal sealed override ProjectionPlan MaterializeEntryPlan + { + get { return this.materializeEntryPlan; } + } + + /// + /// Gets the entry value materialization policy. + /// + /// + /// The entry value materialization policy. + /// + protected EntryValueMaterializationPolicy EntryValueMaterializationPolicy + { + get { return this.entryValueMaterializationPolicy; } + } + + #region Projection support. + + /// Enumerates casting each element to a type. + /// Element type to enumerate over. + /// Element source. + /// + /// An IEnumerable<T> that iterates over the specified . + /// + /// + /// This method should be unnecessary with .NET 4.0 covariance support. + /// + internal static IEnumerable EnumerateAsElementType(IEnumerable source) + { + Debug.Assert(source != null, "source != null"); + + IEnumerable typedSource = source as IEnumerable; + if (typedSource != null) + { + return typedSource; + } + else + { + return EnumerateAsElementTypeInternal(source); + } + } + + /// Enumerates casting each element to a type. + /// Element type to enumerate over. + /// Element source. + /// + /// An IEnumerable<T> that iterates over the specified . + /// + /// + /// This method should be unnecessary with .NET 4.0 covariance support. + /// + internal static IEnumerable EnumerateAsElementTypeInternal(IEnumerable source) + { + Debug.Assert(source != null, "source != null"); + + foreach (object item in source) + { + yield return (T)item; + } + } + + /// Creates a list to a target element type. + /// Materializer used to flow link tracking. + /// Element type to enumerate over. + /// Element type for list. + /// Element source. + /// + /// An IEnumerable<T> that iterates over the specified . + /// + /// + /// This method should be unnecessary with .NET 4.0 covariance support. + /// + internal static List ListAsElementType(ODataEntityMaterializer materializer, IEnumerable source) where T : TTarget + { + Debug.Assert(materializer != null, "materializer != null"); + Debug.Assert(source != null, "source != null"); + + List typedSource = source as List; + if (typedSource != null) + { + return typedSource; + } + + List list; + IList sourceList = source as IList; + if (sourceList != null) + { + list = new List(sourceList.Count); + } + else + { + list = new List(); + } + + foreach (T item in source) + { + list.Add((TTarget)item); + } + + // We can flow the same continuation becaues they're immutable, and + // we don't need to set the continuation property because List doesn't + // have one. + DataServiceQueryContinuation continuation; + if (materializer.nextLinkTable.TryGetValue(source, out continuation)) + { + materializer.nextLinkTable[list] = continuation; + } + + return list; + } + + /// Creates an entry materialization plan that is payload-driven. + /// Segment type for the entry to materialize (typically last of URI in query). + /// A payload-driven materialization plan. + internal static ProjectionPlan CreatePlanForDirectMaterialization(Type lastSegmentType) + { + ProjectionPlan result = new ProjectionPlan(); + result.Plan = ODataEntityMaterializerInvoker.DirectMaterializePlan; + result.ProjectedType = lastSegmentType; + result.LastSegmentType = lastSegmentType; + return result; + } + + /// Creates an entry materialization plan that is payload-driven and does not traverse expanded links. + /// Segment type for the entry to materialize (typically last of URI in query). + /// A payload-driven materialization plan. + internal static ProjectionPlan CreatePlanForShallowMaterialization(Type lastSegmentType) + { + ProjectionPlan result = new ProjectionPlan(); + result.Plan = ODataEntityMaterializerInvoker.ShallowMaterializePlan; + result.ProjectedType = lastSegmentType; + result.LastSegmentType = lastSegmentType; + return result; + } + + /// Checks whether the entity on the specified is null. + /// Root entry for paths. + /// Expected type for . + /// Path to pull value for. + /// Whether the specified is null. + /// + /// This method will not instantiate entity types on the path. + /// Note that if the target is a collection, the result is always false, + /// as the model does not allow null feeds (but instead gets an empty + /// collection, possibly with continuation tokens and such). + /// + internal static bool ProjectionCheckValueForPathIsNull( + MaterializerEntry entry, + Type expectedType, + ProjectionPath path) + { + Debug.Assert(path != null, "path != null"); + + if (path.Count == 0 || path.Count == 1 && path[0].Member == null) + { + return entry.Entry == null; + } + + bool result = false; + MaterializerNavigationLink atomProperty = default(MaterializerNavigationLink); + IEnumerable properties = entry.NestedResourceInfos; + ClientEdmModel model = entry.EntityDescriptor.Model; + for (int i = 0; i < path.Count; i++) + { + var segment = path[i]; + if (segment.Member == null) + { + continue; + } + + bool segmentIsLeaf = i == path.Count - 1; + string propertyName = segment.Member; + + if (segment.SourceTypeAs != null) + { + // (p as Employee).Manager + // The property might not be defined on the expectedType but is always defined on the TypeAs type which is a more derived type. + expectedType = segment.SourceTypeAs; + + if (!properties.Any(p => p.Name == propertyName)) + { + // We are projecting a property defined on a derived type and the entry is of the base type. The property doesn't exist, return null. + return true; + } + } + + IEdmType expectedEdmType = model.GetOrCreateEdmType(expectedType); + ClientPropertyAnnotation property = model.GetClientTypeAnnotation(expectedEdmType).GetProperty(propertyName, UndeclaredPropertyBehavior.ThrowException); + atomProperty = ODataEntityMaterializer.GetPropertyOrThrow(properties, propertyName); + EntryValueMaterializationPolicy.ValidatePropertyMatch(property, atomProperty.Link); + if (atomProperty.Feed != null) + { + Debug.Assert(segmentIsLeaf, "segmentIsLeaf -- otherwise the path generated traverses a feed, which should be disallowed"); + result = false; + } + else if (atomProperty.Entry != null) + { + if (segmentIsLeaf) + { + result = atomProperty.Entry.Entry == null; + } + else + { + entry = atomProperty.Entry; + properties = entry.NestedResourceInfos; + } + } + else + { + return true; + } + + expectedType = property.PropertyType; + } + + return result; + } + + /// Provides support for Select invocations for projections. + /// Materializer under which projection is taking place. + /// Root entry for paths. + /// Expected type for . + /// Expected result type. + /// Path to traverse. + /// Selector callback. + /// An enumerable with the select results. + internal static IEnumerable ProjectionSelect( + ODataEntityMaterializer materializer, + MaterializerEntry entry, + Type expectedType, + Type resultType, + ProjectionPath path, + Func selector) + { + ClientEdmModel edmModel = materializer.MaterializerContext.Model; + ClientTypeAnnotation entryType = entry.ActualType ?? edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(expectedType)); + IEnumerable list = (IEnumerable)Util.ActivatorCreateInstance(typeof(List<>).MakeGenericType(resultType)); + MaterializerNavigationLink atomProperty = default(MaterializerNavigationLink); + ClientPropertyAnnotation property = null; + for (int i = 0; i < path.Count; i++) + { + var segment = path[i]; + if (segment.SourceTypeAs != null) + { + entryType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(segment.SourceTypeAs)); + } + + if (segment.Member == null) + { + continue; + } + + string propertyName = segment.Member; + property = entryType.GetProperty(propertyName, UndeclaredPropertyBehavior.ThrowException); + + // If we are projecting a property defined on a derived type and the entry is of the base type, get property would throw. The user need to check for null in the query. + // e.g. Select(p => new MyEmployee { ID = p.ID, Manager = (p as Employee).Manager == null ? null : new MyManager { ID = (p as Employee).Manager.ID } }) + atomProperty = ODataEntityMaterializer.GetPropertyOrThrow(entry.NestedResourceInfos, propertyName); + + if (atomProperty.Entry != null) + { + entry = atomProperty.Entry; + entryType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(property.PropertyType)); + } + } + + EntryValueMaterializationPolicy.ValidatePropertyMatch(property, atomProperty.Link); + MaterializerFeed sourceFeed = MaterializerFeed.GetFeed(atomProperty.Feed); + Debug.Assert( + sourceFeed.Feed != null, + "sourceFeed != null -- otherwise ValidatePropertyMatch should have thrown or property isn't a collection (and should be part of this plan)"); + + Action addMethod = ClientTypeUtil.GetAddToCollectionDelegate(list.GetType()); + foreach (var paramEntry in sourceFeed.Entries) + { + object projected = selector(materializer, paramEntry, property.EntityCollectionItemType /* perhaps nested? */); + addMethod(list, projected); + } + + ProjectionPlan plan = new ProjectionPlan(); + plan.LastSegmentType = property.EntityCollectionItemType; + plan.Plan = selector; + plan.ProjectedType = resultType; + + materializer.EntryValueMaterializationPolicy.FoundNextLinkForCollection(list, sourceFeed.NextPageLink, plan); + + return list; + } + + /// Provides support for getting payload entries during projections. + /// Entry to get sub-entry from. + /// Name of sub-entry. + /// The sub-entry (never null). + internal static ODataResource ProjectionGetEntry(MaterializerEntry entry, string name) + { + Debug.Assert(entry.Entry != null, "entry != null -- ProjectionGetEntry never returns a null entry, and top-level materialization shouldn't pass one in"); + + // If we are projecting a property defined on a derived type and the entry is of the base type, get property would throw. The user need to check for null in the query. + // e.g. Select(p => new MyEmployee { ID = p.ID, Manager = (p as Employee).Manager == null ? null : new MyManager { ID = (p as Employee).Manager.ID } }) + MaterializerNavigationLink property = ODataEntityMaterializer.GetPropertyOrThrow(entry.NestedResourceInfos, name); + MaterializerEntry result = property.Entry; + if (result == null) + { + throw new InvalidOperationException(DSClient.Strings.AtomMaterializer_PropertyNotExpectedEntry(name)); + } + + CheckEntryToAccessNotNull(result, name); + return result.Entry; + } + + /// Initializes a projection-driven entry (with a specific type and specific properties). + /// Materializer under which projection is taking place. + /// Root entry for paths. + /// Expected type for . + /// Expected result type. + /// Properties to materialize. + /// Functions to get values for functions. + /// The initialized entry. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes", Justification = "Need to throw the type that the expression would throw with other providers")] + internal static object ProjectionInitializeEntity( + ODataEntityMaterializer materializer, + MaterializerEntry entry, + Type expectedType, + Type resultType, + string[] properties, + Func[] propertyValues) + { + if (entry.Entry == null) + { + throw new NullReferenceException(DSClient.Strings.AtomMaterializer_EntryToInitializeIsNull(resultType.FullName)); + } + + if (!entry.EntityHasBeenResolved) + { + ODataEntityMaterializer.ProjectionEnsureEntryAvailableOfType(materializer, entry, resultType); + } + else if (!resultType.IsAssignableFrom(entry.ActualType.ElementType)) + { + string message = DSClient.Strings.AtomMaterializer_ProjectEntityTypeMismatch( + resultType.FullName, + entry.ActualType.ElementType.FullName, + entry.Id); + throw new InvalidOperationException(message); + } + + object result = entry.ResolvedObject; + + for (int i = 0; i < properties.Length; i++) + { + string propertyName = properties[i]; + + // We get here if we have an entity member init in the projection. For example Select(p => new MyEmployee { Manager = (p as Employee).Manager }). + // The entry.ActualType in the example would be MyEmployee and the Manager property always exist on it or else the linq statement would not compile. + var property = entry.ActualType.GetProperty(propertyName, materializer.MaterializerContext.UndeclaredPropertyBehavior); + Debug.Assert(property != null, "property != null"); + + // NOTE: + // 1. The delegate calls into methods such as ProjectionValueForPath or ProjectionCheckValueForPathIsNull where the projection path is given. Those methods + // will throw if property is missing from the Atom entry and there is no TypeAs before the property access. I.e. if the Manager property is missing in the + // Atom entry for (p as Employee).Manager, we return null. But we would throw for e.Manager because we are not accessing a derived property. + // 2. If Manager is missing in the Atom entry for (p as Employee).Manager.Name, the delegate would throw because we are accessing the Name property on Manager + // which is null. We require user to do a null check, e.g. "(p as Employee).Manager == null ? null (p as Employee).Manager.Name". + object value = propertyValues[i](materializer, entry.Entry, expectedType); + + // If the property is missing in the Atom entry, we are projecting a derived property and entry is of the base type which the property is not defined on. + // We don't want to set the property value, which is null, for the non-existing property on the base type. + // Take the example Select(p => new MyEmployee { Manager = (p as Employee).Manager }), if p is of Person type, the Manager navigation property would not + // be on its Atom payload from the server. Thus we don't need to set the MyEmployee.Manager link. + StreamDescriptor streamInfo; + var odataProperty = entry.Entry.Properties.Where(p => p.Name == propertyName).FirstOrDefault(); + var link = odataProperty == null && entry.NestedResourceInfos != null ? entry.NestedResourceInfos.Where(l => l.Name == propertyName).FirstOrDefault() : null; + + if (link == null && odataProperty == null && !entry.EntityDescriptor.TryGetNamedStreamInfo(propertyName, out streamInfo)) + { + continue; + } + + if (entry.ShouldUpdateFromPayload) + { + if (property.EdmProperty.Type.TypeKind() == EdmTypeKind.Entity) + { + materializer.EntityTrackingAdapter.MaterializationLog.SetLink(entry, property.PropertyName, value); + } + + if (!property.IsEntityCollection) + { + // Collection properties cannot be just set like primitive or complex properties. For collectionValue we have a special initialization logic in the + // ApplyDataValue (called from ProjectionValueForPath invoked above with: propertyValues[i](materializer, entry, expectedType)) method + // that ensures that we either re-use existing collectionValue or create an instance using the right type for the collectionValue. As a result at this + // point the value of the collectionValue must already be set to a non-null value. + if (!property.IsPrimitiveOrEnumOrComplexCollection) + { + property.SetValue(result, value, property.PropertyName, false); + } + else + { + Debug.Assert(property.GetValue(result) != null, "Collection should have already been initialized to a non null value"); + } + } + else + { + Debug.Assert(value != null, "value != null"); + IEnumerable valueAsEnumerable = (IEnumerable)value; + DataServiceQueryContinuation continuation = materializer.nextLinkTable[valueAsEnumerable]; + Uri nextLinkUri = continuation == null ? null : continuation.NextLinkUri; + ProjectionPlan plan = continuation == null ? null : continuation.Plan; + materializer.MergeLists(entry, property, valueAsEnumerable, nextLinkUri, plan); + } + } + else if (property.IsEntityCollection) + { + materializer.EntryValueMaterializationPolicy.FoundNextLinkForUnmodifiedCollection(property.GetValue(entry.ResolvedObject) as IEnumerable); + } + } + + return result; + } + + /// + /// Ensures that an entry of is + /// available on the specified . + /// + /// Materilizer used for logging. + /// Entry to ensure. + /// Required type. + /// + /// As the 'Projection' suffix suggests, this method should only + /// be used during projection operations; it purposefully avoid + /// "source tree" type usage and POST reply entry resolution. + /// + internal static void ProjectionEnsureEntryAvailableOfType(ODataEntityMaterializer materializer, MaterializerEntry entry, Type requiredType) + { + Debug.Assert(materializer != null, "materializer != null"); + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(!entry.EntityHasBeenResolved, "should never get here for resolved entities."); + Debug.Assert( + materializer.EntityTrackingAdapter.TargetInstance == null, + "materializer.targetInstance == null -- projection shouldn't have a target instance set; that's only used for POST replies"); + + // TODO : Need to handle complex type with no tracking and entity with tracking but no id. + if (entry.Id == null || !materializer.EntityTrackingAdapter.TryResolveAsExistingEntry(entry, requiredType)) + { + // The type is always required, so skip ResolveByCreating. + materializer.EntryValueMaterializationPolicy.ResolveByCreatingWithType(entry, requiredType); + } + else + { + if (!requiredType.IsAssignableFrom(entry.ResolvedObject.GetType())) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_Current(requiredType, entry.ResolvedObject.GetType())); + } + } + } + + /// Materializes an entry with no special selection. + /// Materializer under which materialization should take place. + /// Entry with object to materialize. + /// Expected type for the entry. + /// The materialized instance. + internal static object DirectMaterializePlan(ODataEntityMaterializer materializer, MaterializerEntry entry, Type expectedEntryType) + { + materializer.entryValueMaterializationPolicy.Materialize(entry, expectedEntryType, true); + return entry.ResolvedObject; + } + + /// Materializes an entry without including in-lined expanded links. + /// Materializer under which materialization should take place. + /// Entry with object to materialize. + /// Expected type for the entry. + /// The materialized instance. + internal static object ShallowMaterializePlan(ODataEntityMaterializer materializer, MaterializerEntry entry, Type expectedEntryType) + { + materializer.entryValueMaterializationPolicy.Materialize(entry, expectedEntryType, false); + return entry.ResolvedObject; + } + + /// Projects a simple value from the specified . + /// Root entry for paths. + /// Expected type for . + /// Path to pull value for. + /// The value for the specified . + /// + /// This method will not instantiate entity types, except to satisfy requests + /// for payload-driven feeds or leaf entities. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502", Justification = "cyclomatic complexity")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:MethodCoupledWithTooManyTypesFromDifferentNamespaces", Justification = "should refactor the method in the future.")] + internal object ProjectionValueForPath(MaterializerEntry entry, Type expectedType, ProjectionPath path) + { + Debug.Assert(this != null, "materializer != null"); + Debug.Assert(entry.Entry != null, "entry.Entry != null"); + Debug.Assert(path != null, "path != null"); + + // An empty path indicates that we do a regular materialization. + if (path.Count == 0 || path.Count == 1 && path[0].Member == null) + { + if (!entry.EntityHasBeenResolved) + { + this.EntryValueMaterializationPolicy.Materialize(entry, expectedType, /* includeLinks */ false); + } + + return entry.ResolvedObject; + } + + object result = null; + ODataNestedResourceInfo link = null; + ODataProperty odataProperty = null; + ICollection links = entry.NestedResourceInfos; + IEnumerable properties = entry.Entry.Properties; + ClientEdmModel edmModel = this.MaterializerContext.Model; + for (int i = 0; i < path.Count; i++) + { + var segment = path[i]; + if (segment.Member == null) + { + continue; + } + + bool segmentIsLeaf = i == path.Count - 1; + string propertyName = segment.Member; + + // (p as Employee).Manager + // The property might not be defined on the expectedType but is always defined on the TypeAs type which is a more derived type. + expectedType = segment.SourceTypeAs ?? expectedType; + ClientPropertyAnnotation property = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(expectedType)).GetProperty(propertyName, UndeclaredPropertyBehavior.ThrowException); + if (property.IsStreamLinkProperty) + { + // projecting a DataServiceStreamLink property + // the stream link does not come inside + // instead, it's materialized and attached to the entity descriptor on the MaterializerEntry struct + var streamDescriptor = entry.EntityDescriptor.StreamDescriptors.Where(sd => sd.Name == propertyName).SingleOrDefault(); + if (streamDescriptor == null) + { + // We are projecting a named stream on a derived type and entry is of a base type which the named stream is not defined on. Return null. + if (segment.SourceTypeAs != null) + { + result = WebUtil.GetDefaultValue(); + break; + } + else + { + // the named stream projected did not come back as part of the property + throw new InvalidOperationException(DSClient.Strings.AtomMaterializer_PropertyMissing(propertyName)); + } + } + + result = streamDescriptor.StreamLink; + } + else + { + // Note that we should only return the default value if the current segment is leaf. + // Take for example, select(new { M = (p as Employee).Manager }). If p is Person and Manager is null, we should return null here. + // On the other hand select(new { MID = (p as Employee).Manager.ID }) should throw if p is Person and Manager is null. + if (segment.SourceTypeAs != null && !links.Any(p => p.Name == propertyName) && !properties.Any(p => p.Name == propertyName) && segmentIsLeaf) + { + // We are projecting a derived property and entry is of a base type which the property is not defined on. Return null. + result = WebUtil.GetDefaultValue(property.PropertyType); + break; + } + + odataProperty = properties.Where(p => p.Name == propertyName).FirstOrDefault(); + link = odataProperty == null && links != null ? links.Where(p => p.Name == propertyName).FirstOrDefault() : null; + if (link == null && odataProperty == null) + { + throw new InvalidOperationException(DSClient.Strings.AtomMaterializer_PropertyMissing(propertyName)); + } + + if (link != null) + { + EntryValueMaterializationPolicy.ValidatePropertyMatch(property, link); + + MaterializerNavigationLink linkState = MaterializerNavigationLink.GetLink(link); + + if (linkState.Feed != null) + { + MaterializerFeed feedValue = MaterializerFeed.GetFeed(linkState.Feed); + + Debug.Assert(segmentIsLeaf, "segmentIsLeaf -- otherwise the path generated traverses a feed, which should be disallowed"); + + // When we're materializing a feed as a leaf, we actually project each element. + Type collectionType = ClientTypeUtil.GetImplementationType(segment.ProjectionType, typeof(ICollection<>)); + if (collectionType == null) + { + collectionType = ClientTypeUtil.GetImplementationType(segment.ProjectionType, typeof(IEnumerable<>)); + } + + Debug.Assert( + collectionType != null, + "collectionType != null -- otherwise the property should never have been recognized as a collection"); + + Type nestedExpectedType = collectionType.GetGenericArguments()[0]; + Type feedType = segment.ProjectionType; + if (DSClient.PlatformHelper.IsInterface(feedType) || ClientTypeUtil.IsDataServiceCollection(feedType)) + { + feedType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(nestedExpectedType); + } + + IEnumerable list = (IEnumerable)Util.ActivatorCreateInstance(feedType); + MaterializeToList(this, list, nestedExpectedType, feedValue.Entries); + + if (ClientTypeUtil.IsDataServiceCollection(segment.ProjectionType)) + { + Type dataServiceCollectionType = WebUtil.GetDataServiceCollectionOfT(nestedExpectedType); + list = (IEnumerable)Util.ActivatorCreateInstance( + dataServiceCollectionType, + list, // items + TrackingMode.None); // tracking mode + } + + ProjectionPlan plan = CreatePlanForShallowMaterialization(nestedExpectedType); + this.EntryValueMaterializationPolicy.FoundNextLinkForCollection(list, feedValue.Feed.NextPageLink, plan); + result = list; + } + else if (linkState.Entry != null) + { + MaterializerEntry linkEntry = linkState.Entry; + + // If this is a leaf, then we'll do a tracking, payload-driven + // materialization. If this isn't the leaf, then we'll + // simply traverse through its properties. + if (segmentIsLeaf) + { + if (linkEntry.Entry != null && !linkEntry.EntityHasBeenResolved) + { + this.EntryValueMaterializationPolicy.Materialize(linkEntry, property.PropertyType, /* includeLinks */ false); + if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization && linkEntry.ShouldUpdateFromPayload) + { + this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(linkEntry.Entry, linkEntry.ResolvedObject); + } + } + } + else + { + // if entry is null, no further property access can be done. + CheckEntryToAccessNotNull(linkEntry, propertyName); + } + + // apply instance annotation for navigation property + if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization && linkEntry.ShouldUpdateFromPayload) + { + this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(propertyName, linkEntry.Entry, expectedType, entry.ResolvedObject); + } + + properties = linkEntry.Properties; + links = linkEntry.NestedResourceInfos; + result = linkEntry.ResolvedObject; + entry = linkEntry; + } + } + else + { + if (odataProperty.Value is ODataStreamReferenceValue) + { + result = null; + links = ODataMaterializer.EmptyLinks; + properties = ODataMaterializer.EmptyProperties; + continue; + } + + EntryValueMaterializationPolicy.ValidatePropertyMatch(property, odataProperty); + + // So the payload is for non-entity types. If we encounter an entity in the client side, we should throw + // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa + if (ClientTypeUtil.TypeOrElementTypeIsEntity(property.PropertyType)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidEntityType(property.EntityCollectionItemType ?? property.PropertyType)); + } + + if (property.IsPrimitiveOrEnumOrComplexCollection) + { + object instance = result ?? entry.ResolvedObject ?? this.CollectionValueMaterializationPolicy.CreateNewInstance(property.EdmProperty.Type.Definition.ToEdmTypeReference(true), expectedType); + this.entryValueMaterializationPolicy.ApplyDataValue(edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(instance.GetType())), odataProperty, instance); + + links = ODataMaterializer.EmptyLinks; + properties = ODataMaterializer.EmptyProperties; + } + else if (odataProperty.Value is ODataEnumValue) + { + this.EnumValueMaterializationPolicy.MaterializeEnumTypeProperty(property.PropertyType, odataProperty); + links = ODataMaterializer.EmptyLinks; + properties = ODataMaterializer.EmptyProperties; + } + else + { + if (odataProperty.Value == null && !ClientTypeUtil.CanAssignNull(property.NullablePropertyType)) + { + throw new InvalidOperationException(DSClient.Strings.AtomMaterializer_CannotAssignNull(odataProperty.Name, property.NullablePropertyType)); + } + + this.entryValueMaterializationPolicy.MaterializePrimitiveDataValue(property.NullablePropertyType, odataProperty); + + links = ODataMaterializer.EmptyLinks; + properties = ODataMaterializer.EmptyProperties; + } + + result = odataProperty.GetMaterializedValue(); + + // TODO: projection with anonymous type is not supported now. + // apply instance annotation for property + if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization) + { + this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(odataProperty, expectedType, entry.ResolvedObject); + } + } + } + + expectedType = property.PropertyType; + } + + return result; + } + + #endregion Projection support. + + /// Clears the materialization log of activity. + internal sealed override void ClearLog() + { + this.EntityTrackingAdapter.MaterializationLog.Clear(); + } + + /// Applies the materialization log to the context. + internal sealed override void ApplyLogToContext() + { + this.EntityTrackingAdapter.MaterializationLog.ApplyToContext(); + } + + /// Helper method for constructor of DataServiceCollection. + /// Element type for collection. + /// The enumerable which has the continuation on it. + /// The DataServiceCollection to apply the continuation to. + internal void PropagateContinuation(IEnumerable from, DataServiceCollection to) + { + DataServiceQueryContinuation continuation; + if (this.nextLinkTable.TryGetValue(from, out continuation)) + { + this.nextLinkTable.Add(to, continuation); + Util.SetNextLinkForCollection(to, continuation); + } + } + + /// + /// Implementation of Read/>. + /// + /// + /// Return value of Read/> + /// + protected override bool ReadImplementation() + { + // links from last entry should be cleared + this.nextLinkTable.Clear(); + + bool setFeedInstanceAnnotation = this.CurrentFeed == null; + if (this.ReadNextFeedOrEntry()) + { + if (this.CurrentEntry == null) + { + this.currentValue = null; + return true; + } + + Debug.Assert(this.CurrentEntry != null, "Read successfully without finding an entry."); + + MaterializerEntry entryAndState = MaterializerEntry.GetEntry(this.CurrentEntry); + entryAndState.ResolvedObject = this.TargetInstance; + this.currentValue = this.materializeEntryPlan.Run(this, this.CurrentEntry, this.ExpectedType); + + if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization) + { + // apply instance annotations for feed + if (setFeedInstanceAnnotation && this.CurrentFeed != null && this.SetInstanceAnnotations != null) + { + this.SetInstanceAnnotations( + this.InstanceAnnotationMaterializationPolicy.ConvertToClrInstanceAnnotations(this.CurrentFeed.InstanceAnnotations)); + } + + // 1. When using projection with anonymous type, the resolved object is null, ShouldUpdateFromPayload is false. + // 2. When using projection with a specific type, or in other circumstances, + // the resolved object is not null; the ShouldUpdateFromPayload is true or false according to the merge option. + if (this.CurrentEntry != null && entryAndState.ResolvedObject == null || entryAndState.ShouldUpdateFromPayload) + { + this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(this.CurrentEntry, this.currentValue); + } + } + + return true; + } + + return false; + } + + /// + /// Reads the next feed or entry. + /// + /// True if an entry was read, otherwise false + protected abstract bool ReadNextFeedOrEntry(); + + #region Private methods. + + /// + /// Checks that the specified isn't null. + /// + /// Entry to check. + /// Name of entry being accessed. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes", Justification = "Need to throw the type that the expression would throw with other providers")] + private static void CheckEntryToAccessNotNull(MaterializerEntry entry, string name) + { + Debug.Assert(name != null, "name != null"); + + if (entry.Entry == null) + { + throw new NullReferenceException(DSClient.Strings.AtomMaterializer_EntryToAccessIsNull(name)); + } + } + + /// Creates an entry materialization plan for a given projection. + /// Query components for plan to materialize. + /// A materialization plan. + private static ProjectionPlan CreatePlan(QueryComponents queryComponents) + { + // Can we have a primitive property as well? + LambdaExpression projection = queryComponents.Projection; + ProjectionPlan result; + if (projection == null) + { + result = CreatePlanForDirectMaterialization(queryComponents.LastSegmentType); + } + else + { + result = ProjectionPlanCompiler.CompilePlan(projection, queryComponents.NormalizerRewrites); + result.LastSegmentType = queryComponents.LastSegmentType; + } + + return result; + } + + /// Materializes the result of a projection into a list. + /// Materializer to use for the operation. + /// Target list. + /// Expected type for nested object. + /// Entries to materialize from. + /// + /// This method supports projections and as such does shallow payload-driven + /// materialization of entities. + /// + private static void MaterializeToList( + ODataEntityMaterializer materializer, + IEnumerable list, + Type nestedExpectedType, + IEnumerable entries) + { + Debug.Assert(materializer != null, "materializer != null"); + Debug.Assert(list != null, "list != null"); + + Action addMethod = ClientTypeUtil.GetAddToCollectionDelegate(list.GetType()); + foreach (ODataResource feedEntry in entries) + { + MaterializerEntry feedEntryState = MaterializerEntry.GetEntry(feedEntry); + if (!feedEntryState.EntityHasBeenResolved) + { + materializer.EntryValueMaterializationPolicy.Materialize(feedEntryState, nestedExpectedType, /* includeLinks */ false); + } + + addMethod(list, feedEntryState.ResolvedObject); + } + } + + /// Gets a property from the specified list, throwing if not found. + /// List to get value from. + /// Property name to look up. + /// The specified property (never null). + private static MaterializerNavigationLink GetPropertyOrThrow(IEnumerable links, string propertyName) + { + ODataNestedResourceInfo link = null; + if (links != null) + { + link = links.Where(p => p.Name == propertyName).FirstOrDefault(); + } + + if (link == null) + { + throw new InvalidOperationException(DSClient.Strings.AtomMaterializer_PropertyMissing(propertyName)); + } + + return MaterializerNavigationLink.GetLink(link); + } + + /// Merges a list into the property of a given . + /// Entry to merge into. + /// Property on entry to merge into. + /// List of materialized values. + /// Next link for feed from which the materialized values come from. + /// Projection plan for the list. + /// + /// This method will handle entries that shouldn't be updated correctly. + /// + private void MergeLists(MaterializerEntry entry, ClientPropertyAnnotation property, IEnumerable list, Uri nextLink, ProjectionPlan plan) + { + Debug.Assert(entry.Entry != null, "entry != null"); + Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null"); + Debug.Assert(property != null, "property != null"); + Debug.Assert(plan != null || nextLink == null, "plan != null || nextLink == null"); + + object leftCollection = property.GetValue(entry.ResolvedObject); + + // Simple case: the list is of the target type, and the resolved entity + // has null; we can simply assign the collection. No merge required. + // Another case: the collection is not null but of zero elements and has + // not been tracked already; we simply assign the collection too. + if (entry.ShouldUpdateFromPayload && + property.NullablePropertyType == list.GetType() && + (leftCollection == null || NeedToAssignCollectionDirectly(leftCollection))) + { + property.SetValue(entry.ResolvedObject, list, property.PropertyName, false /* allowAdd */); + this.EntryValueMaterializationPolicy.FoundNextLinkForCollection(list, nextLink, plan); + + foreach (object item in list) + { + this.EntityTrackingAdapter.MaterializationLog.AddedLink(entry, property.PropertyName, item); + } + + return; + } + + this.EntryValueMaterializationPolicy.ApplyItemsToCollection( + entry, + property, + list, + nextLink, + plan, + false); + } + + /// + /// Returns if the left collection needs to be directly assigned from the right collection. + /// + /// The given collection. + /// If the left collection needs to be directly assigned from the right collection. + private static bool NeedToAssignCollectionDirectly(object collection) + { + Type type = collection.GetType(); + PropertyInfo countProp = type.GetPublicProperties(true).SingleOrDefault(property => property.Name == "Count"); + PropertyInfo isTrackingProp = type.GetNonPublicProperties(true, false /*declaredOnly*/).SingleOrDefault(property => property.Name == "IsTracking"); + + if (countProp == null) + { + return false; + } + + int count = (int)countProp.GetValue(collection, null); + + if (isTrackingProp == null) + { + return false; + } + + bool isTracking = (bool)isTrackingProp.GetValue(collection, null); + + return count == 0 && !isTracking; + } + + #endregion Private methods. + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializerInvoker.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializerInvoker.cs new file mode 100644 index 0000000..b4e1142 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntityMaterializerInvoker.cs @@ -0,0 +1,164 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData; + + /// + /// Use this class to invoke projection methods from . + /// + internal static class ODataEntityMaterializerInvoker + { + /// Enumerates casting each element to a type. + /// Element type to enumerate over. + /// Element source. + /// + /// An IEnumerable<T> that iterates over the specified . + /// + /// + /// This method should be unnecessary with .NET 4.0 covariance support. + /// + internal static IEnumerable EnumerateAsElementType(IEnumerable source) + { + return ODataEntityMaterializer.EnumerateAsElementType(source); + } + + /// Creates a list to a target element type. + /// Materializer used to flow link tracking. + /// Element type to enumerate over. + /// Element type for list. + /// Element source. + /// + /// An IEnumerable<T> that iterates over the specified . + /// + /// + /// This method should be unnecessary with .NET 4.0 covariance support. + /// + internal static List ListAsElementType(object materializer, IEnumerable source) where T : TTarget + { + Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); + return ODataEntityMaterializer.ListAsElementType((ODataEntityMaterializer)materializer, source); + } + + /// Checks whether the entity on the specified is null. + /// Root entry for paths. + /// Expected type for . + /// Path to pull value for. + /// Whether the specified is null. + /// + /// This method will not instantiate entity types on the path. + /// + internal static bool ProjectionCheckValueForPathIsNull( + object entry, + Type expectedType, + object path) + { + Debug.Assert(entry.GetType() == typeof(ODataResource), "entry.GetType() == typeof(ODataResource)"); + Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)"); + return ODataEntityMaterializer.ProjectionCheckValueForPathIsNull(MaterializerEntry.GetEntry((ODataResource)entry), expectedType, (ProjectionPath)path); + } + + /// Provides support for Select invocations for projections. + /// Materializer under which projection is taking place. + /// Root entry for paths. + /// Expected type for . + /// Expected result type. + /// Path to traverse. + /// Selector callback. + /// An enumerable with the select results. + internal static IEnumerable ProjectionSelect( + object materializer, + object entry, + Type expectedType, + Type resultType, + object path, + Func selector) + { + Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); + Debug.Assert(entry.GetType() == typeof(ODataResource), "entry.GetType() == typeof(ODataResource)"); + Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)"); + return ODataEntityMaterializer.ProjectionSelect((ODataEntityMaterializer)materializer, MaterializerEntry.GetEntry((ODataResource)entry), expectedType, resultType, (ProjectionPath)path, selector); + } + + /// Provides support for getting payload entries during projections. + /// Entry to get sub-entry from. + /// Name of sub-entry. + /// The sub-entry (never null). + internal static object ProjectionGetEntry(object entry, string name) + { + Debug.Assert(entry.GetType() == typeof(ODataResource), "entry.GetType() == typeof(ODataResource)"); + return ODataEntityMaterializer.ProjectionGetEntry(MaterializerEntry.GetEntry((ODataResource)entry), name); + } + + /// Initializes a projection-driven entry (with a specific type and specific properties). + /// Materializer under which projection is taking place. + /// Root entry for paths. + /// Expected type for . + /// Expected result type. + /// Properties to materialize. + /// Functions to get values for functions. + /// The initialized entry. + internal static object ProjectionInitializeEntity( + object materializer, + object entry, + Type expectedType, + Type resultType, + string[] properties, + Func[] propertyValues) + { + Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); + Debug.Assert(entry.GetType() == typeof(ODataResource), "entry.GetType() == typeof(ODataResource)"); + return ODataEntityMaterializer.ProjectionInitializeEntity((ODataEntityMaterializer)materializer, MaterializerEntry.GetEntry((ODataResource)entry), expectedType, resultType, properties, propertyValues); + } + + /// Projects a simple value from the specified . + /// Materializer under which projection is taking place. + /// Root entry for paths. + /// Expected type for . + /// Path to pull value for. + /// The value for the specified . + /// + /// This method will not instantiate entity types, except to satisfy requests + /// for payload-driven feeds or leaf entities. + /// + internal static object ProjectionValueForPath(object materializer, object entry, Type expectedType, object path) + { + Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); + Debug.Assert(entry.GetType() == typeof(ODataResource), "entry.GetType() == typeof(ODataResource)"); + Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)"); + return ((ODataEntityMaterializer)materializer).ProjectionValueForPath(MaterializerEntry.GetEntry((ODataResource)entry), expectedType, (ProjectionPath)path); + } + + /// Materializes an entry with no special selection. + /// Materializer under which materialization should take place. + /// Entry with object to materialize. + /// Expected type for the entry. + /// The materialized instance. + internal static object DirectMaterializePlan(object materializer, object entry, Type expectedEntryType) + { + Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); + Debug.Assert(entry.GetType() == typeof(ODataResource), "entry.GetType() == typeof(ODataResource)"); + return ODataEntityMaterializer.DirectMaterializePlan((ODataEntityMaterializer)materializer, MaterializerEntry.GetEntry((ODataResource)entry), expectedEntryType); + } + + /// Materializes an entry without including in-lined expanded links. + /// Materializer under which materialization should take place. + /// Entry with object to materialize. + /// Expected type for the entry. + /// The materialized instance. + internal static object ShallowMaterializePlan(object materializer, object entry, Type expectedEntryType) + { + Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); + Debug.Assert(entry.GetType() == typeof(ODataResource), "entry.GetType() == typeof(ODataResource)"); + return ODataEntityMaterializer.ShallowMaterializePlan((ODataEntityMaterializer)materializer, MaterializerEntry.GetEntry((ODataResource)entry), expectedEntryType); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntriesEntityMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntriesEntityMaterializer.cs new file mode 100644 index 0000000..7f0597b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataEntriesEntityMaterializer.cs @@ -0,0 +1,148 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections.Generic; + using Microsoft.OData.Client; + using Microsoft.OData; + using DSClient = Microsoft.OData.Client; + + /// + /// Materializes entities from a sequence of ODataResource objects + /// + internal sealed class ODataEntriesEntityMaterializer : ODataEntityMaterializer + { + /// The format of the response being materialized. + private readonly ODataFormat format; + + /// The entries enumerator + private IEnumerator feedEntries; + + /// Is the enumerator finished. + private bool isFinished; + + /// + /// Initializes a new instance of the class. + /// + /// The entries. + /// The materializer context. + /// The entity tracking adapter. + /// The query components. + /// The expected type. + /// The materialize entry plan. + /// The format. + public ODataEntriesEntityMaterializer( + IEnumerable entries, + IODataMaterializerContext materializerContext, + EntityTrackingAdapter entityTrackingAdapter, + QueryComponents queryComponents, + Type expectedType, + ProjectionPlan materializeEntryPlan, + ODataFormat format) + : base(materializerContext, entityTrackingAdapter, queryComponents, expectedType, materializeEntryPlan) + { + this.format = format; + this.feedEntries = entries.GetEnumerator(); + } + + /// + /// Feed being materialized; possibly null. + /// + internal override ODataResourceSet CurrentFeed + { + get { return null; } + } + + /// + /// Entry being materialized; possibly null. + /// + internal override ODataResource CurrentEntry + { + get + { + this.VerifyNotDisposed(); + return this.feedEntries.Current; + } + } + + /// + /// The count tag's value, if requested + /// + /// The count value returned from the server + internal override long CountValue + { + get + { + throw new InvalidOperationException(DSClient.Strings.MaterializeFromAtom_CountNotPresent); + } + } + + /// + /// Returns true if the underlying object used for counting is available + /// + internal override bool IsCountable + { + get { return false; } + } + + /// + /// Whether we have finished processing the current data stream. + /// + internal override bool IsEndOfStream + { + get { return this.isFinished; } + } + + /// + /// Returns true if the materializer has been disposed + /// + protected override bool IsDisposed + { + get { return this.feedEntries == null; } + } + + /// + /// The format of the response being materialized. + /// + protected override ODataFormat Format + { + get { return this.format; } + } + + /// + /// Reads the next feed or entry. + /// + /// + /// True if an entry was read, otherwise false + /// + protected override bool ReadNextFeedOrEntry() + { + if (!this.isFinished) + { + if (!this.feedEntries.MoveNext()) + { + this.isFinished = true; + } + } + + return !this.isFinished; + } + + /// + /// Called when IDisposable.Dispose is called. + /// + protected override void OnDispose() + { + if (this.feedEntries != null) + { + this.feedEntries.Dispose(); + this.feedEntries = null; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataItemExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataItemExtensions.cs new file mode 100644 index 0000000..07f83a3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataItemExtensions.cs @@ -0,0 +1,101 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System.Diagnostics; + using Microsoft.OData; + + /// + /// Extension methods for ODataItems + /// + internal static class ODataItemExtensions + { + /// + /// Gets the materialized value. + /// + /// The property. + /// The materialized value. + public static object GetMaterializedValue(this ODataProperty property) + { + ODataAnnotatable annotatableObject = property.Value as ODataAnnotatable ?? property; + return GetMaterializedValueCore(annotatableObject); + } + + /// + /// Determines whether a value has been materialized. + /// + /// The property. + /// true if the value has been materialized; otherwise, false. + public static bool HasMaterializedValue(this ODataProperty property) + { + ODataAnnotatable annotatableObject = property.Value as ODataAnnotatable ?? property; + return HasMaterializedValueCore(annotatableObject); + } + + /// + /// Sets the materialized value. + /// + /// The property. + /// The materialized value. + public static void SetMaterializedValue(this ODataProperty property, object materializedValue) + { + ODataAnnotatable annotatableObject = property.Value as ODataAnnotatable ?? property; + SetMaterializedValueCore(annotatableObject, materializedValue); + } + + /// + /// Gets the materialized value. + /// + /// The annotatable object. + /// The materialized value + private static object GetMaterializedValueCore(ODataAnnotatable annotatableObject) + { + MaterializerPropertyValue value = annotatableObject.GetAnnotation(); + Debug.Assert(value != null, "MaterializedValue not set"); + return value.Value; + } + + /// + /// Determines whether a value has been materialized. + /// + /// The annotatable object. + /// true if the value has been materialized; otherwise, false. + private static bool HasMaterializedValueCore(ODataAnnotatable annotatableObject) + { + return annotatableObject.GetAnnotation() != null; + } + + /// + /// Sets the materialized value. + /// + /// The annotatable object. + /// The materialized value. + private static void SetMaterializedValueCore(ODataAnnotatable annotatableObject, object materializedValue) + { + MaterializerPropertyValue materializerValue = new MaterializerPropertyValue { Value = materializedValue }; + annotatableObject.SetAnnotation(materializerValue); + } + + /// + /// Annotation class for the materialized value + /// + private class MaterializerPropertyValue + { + /// + /// Gets or sets the value. + /// + /// + /// The value. + /// + public object Value + { + get; + set; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLinksMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLinksMaterializer.cs new file mode 100644 index 0000000..0613cbe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLinksMaterializer.cs @@ -0,0 +1,124 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData; + using DSClient = Microsoft.OData.Client; + + /// + /// Materializes from $ref + /// + internal sealed class ODataLinksMaterializer : ODataMessageReaderMaterializer + { + /// The links value read from the message. + private ODataEntityReferenceLinks links; + + /// + /// Initializes a new instance of the class. + /// + /// The reader. + /// The materializer context. + /// The expected type. + /// The single result. + public ODataLinksMaterializer(ODataMessageReader reader, IODataMaterializerContext materializerContext, Type expectedType, bool? singleResult) + : base(reader, materializerContext, expectedType, singleResult) + { + } + + /// + /// Gets the count value. + /// + internal override long CountValue + { + get + { + if (this.links == null && !this.IsDisposed) + { + this.ReadLinks(); + } + + if (this.links != null && this.links.Count.HasValue) + { + return this.links.Count.Value; + } + + throw new InvalidOperationException(DSClient.Strings.MaterializeFromAtom_CountNotPresent); + } + } + + /// + /// Current value being materialized; possibly null. + /// + internal override object CurrentValue + { + get { return null; } + } + + /// + /// Returns true if the underlying object used for counting is available + /// + internal override bool IsCountable + { + get + { + return true; + } + } + + /// + /// Reads from message reader. + /// + /// The expected client type being materialized into. + /// The expected type for the underlying reader. + protected override void ReadWithExpectedType(IEdmTypeReference expectedClientType, IEdmTypeReference expectedReaderType) + { + this.ReadLinks(); + + Type underlyingExpectedType = Nullable.GetUnderlyingType(this.ExpectedType) ?? this.ExpectedType; + + ClientEdmModel edmModel = this.MaterializerContext.Model; + ClientTypeAnnotation targetType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(underlyingExpectedType)); + + // If the target type is an entity, then we should throw since the type on the wire was not an entity + // this is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa + if (targetType.IsEntityType) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidEntityType(targetType.ElementTypeName)); + } + else + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MixedTextWithComment); + } + } + + /// + /// Reads the links. + /// + private void ReadLinks() + { + try + { + if (this.links == null) + { + this.links = this.messageReader.ReadEntityReferenceLinks(); + } + } + catch (ODataErrorException e) + { + throw new DataServiceClientException(DSClient.Strings.Deserialize_ServerException(e.Error.Message), e); + } + catch (ODataException e) + { + throw new InvalidOperationException(e.Message, e); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLoadNavigationPropertyMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLoadNavigationPropertyMaterializer.cs new file mode 100644 index 0000000..a84231d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataLoadNavigationPropertyMaterializer.cs @@ -0,0 +1,135 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + #endregion + + /// + /// Materializer for LoadProperty API call for navigation properties. + /// + internal class ODataLoadNavigationPropertyMaterializer : ODataReaderEntityMaterializer + { + /// + /// Response Info object. + /// + private LoadPropertyResponseInfo responseInfo; + + /// + /// Items that have been read from the feed or entry. + /// + private List items; + + /// + /// Iteration of the entity collection reader. + /// + private int iteration; + + /// + /// Initializes a new instance of the class. + /// + /// The odata message reader. + /// The reader. + /// The materializer context. + /// The entity tracking adapter. + /// The query components. + /// The expected type. + /// The materialize entry plan. + /// LoadProperty Response Info object. + public ODataLoadNavigationPropertyMaterializer( + ODataMessageReader odataMessageReader, + ODataReaderWrapper reader, + IODataMaterializerContext materializerContext, + EntityTrackingAdapter entityTrackingAdapter, + QueryComponents queryComponents, + Type expectedType, + ProjectionPlan materializeEntryPlan, + LoadPropertyResponseInfo responseInfo) + : base(odataMessageReader, reader, materializerContext, entityTrackingAdapter, queryComponents, expectedType, materializeEntryPlan) + { + this.responseInfo = responseInfo; + this.items = new List(); + } + + /// + /// Implementation of Read/>. + /// + /// + /// Return value of Read/> + /// + protected override bool ReadImplementation() + { + // Eagerly materialize the entire collection of objects into the items cache in LoadProperty scenario. + if (this.iteration == 0) + { + while (base.ReadImplementation()) + { + this.items.Add(this.currentValue); + } + + ClientPropertyAnnotation property = this.responseInfo.Property; + EntityDescriptor entityDescriptor = this.responseInfo.EntityDescriptor; + object entity = entityDescriptor.Entity; + + MaterializerEntry entry = MaterializerEntry.CreateEntryForLoadProperty( + entityDescriptor, + this.Format, + this.responseInfo.MergeOption != MergeOption.NoTracking); + + entry.ActualType = this.responseInfo.Model.GetClientTypeAnnotation(this.responseInfo.Model.GetOrCreateEdmType(entity.GetType())); + + if (property.IsEntityCollection) + { + this.EntryValueMaterializationPolicy.ApplyItemsToCollection( + entry, + property, + this.items, + this.CurrentFeed != null ? this.CurrentFeed.NextPageLink : null, + this.MaterializeEntryPlan, + this.responseInfo.IsContinuation); + } + else + { + Debug.Assert(this.items.Count <= 1, "Expecting 0 or 1 element."); + object target = this.items.Count > 0 ? this.items[0] : null; + + Debug.Assert(property.EdmProperty.Type.TypeKind() == EdmTypeKind.Entity, "Must be entity typed property if not an entity collection property."); + this.EntityTrackingAdapter.MaterializationLog.SetLink(entry, property.PropertyName, target); + + // Singleton entity property + property.SetValue(entity, target, property.PropertyName, false); + } + + // Apply the materialization log. + this.ApplyLogToContext(); + + // Clear the log after applying it. + this.ClearLog(); + } + + // Read object from the already loaded items cache. + if (this.items.Count > this.iteration) + { + this.currentValue = this.items[this.iteration]; + this.iteration++; + return true; + } + else + { + this.currentValue = null; + return false; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializer.cs new file mode 100644 index 0000000..06f693d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializer.cs @@ -0,0 +1,405 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData; + using Microsoft.OData.Client; + using Microsoft.OData.Edm; + using DSClient = Microsoft.OData.Client; + + /// + /// Use this class to materialize objects provided from an . + /// + internal abstract class ODataMaterializer : IDisposable + { + /// Empty navigation links collection + internal static readonly ODataNestedResourceInfo[] EmptyLinks = new ODataNestedResourceInfo[0]; + + /// Empty property collection + protected static readonly ODataProperty[] EmptyProperties = new ODataProperty[0]; + + /// Collection->Next Link Table for nested links + protected Dictionary nextLinkTable; + + /// The collection value materialization policy. + private readonly CollectionValueMaterializationPolicy collectionValueMaterializationPolicy; + + /// The enum value materialization policy. + private readonly EnumValueMaterializationPolicy enumValueMaterializationPolicy; + + /// The instance annotation materialization policy. + private readonly InstanceAnnotationMaterializationPolicy instanceAnnotationMaterializationPolicy; + + /// The materialization policy used to materialize primitive values. + private readonly PrimitiveValueMaterializationPolicy primitiveValueMaterializationPolicy; + + /// The converter to use when assigning values of primitive properties. + private DSClient.SimpleLazy lazyPrimitivePropertyConverter; + + /// + /// Initializes a new instance of the class. + /// + /// The materializer context. + /// The expected type. + protected ODataMaterializer(IODataMaterializerContext materializerContext, Type expectedType) + { + this.ExpectedType = expectedType; + this.MaterializerContext = materializerContext; + this.nextLinkTable = new Dictionary(DSClient.ReferenceEqualityComparer.Instance); + + this.enumValueMaterializationPolicy = new EnumValueMaterializationPolicy(this.MaterializerContext); + this.lazyPrimitivePropertyConverter = new DSClient.SimpleLazy(() => new PrimitivePropertyConverter()); + this.primitiveValueMaterializationPolicy = new PrimitiveValueMaterializationPolicy(this.MaterializerContext, this.lazyPrimitivePropertyConverter); + this.collectionValueMaterializationPolicy = new CollectionValueMaterializationPolicy(this.MaterializerContext, this.primitiveValueMaterializationPolicy); + this.instanceAnnotationMaterializationPolicy = new InstanceAnnotationMaterializationPolicy(this.MaterializerContext); + this.collectionValueMaterializationPolicy.InstanceAnnotationMaterializationPolicy = this.instanceAnnotationMaterializationPolicy; + this.instanceAnnotationMaterializationPolicy.CollectionValueMaterializationPolicy = this.collectionValueMaterializationPolicy; + this.instanceAnnotationMaterializationPolicy.EnumValueMaterializationPolicy = this.enumValueMaterializationPolicy; + } + + /// Current value being materialized; possibly null. + /// + /// This will typically be an entity if + /// is assigned, but may contain a string for example if a top-level + /// primitive of type string is found. + /// + internal abstract object CurrentValue { get; } + + /// Feed being materialized; possibly null. + internal abstract ODataResourceSet CurrentFeed { get; } + + /// Entry being materialized; possibly null. + internal abstract ODataResource CurrentEntry { get; } + + /// Table storing the next links assoicated with the current payload + internal Dictionary NextLinkTable + { + get { return this.nextLinkTable; } + } + + /// Whether we have finished processing the current data stream. + internal abstract bool IsEndOfStream { get; } + + /// + /// Returns true if the underlying object used for counting is available + /// + internal virtual bool IsCountable + { + get { return false; } + } + + /// + /// The action to set instance annotation associated with current feed + /// + internal Action> SetInstanceAnnotations { get; set; } + + /// + /// The count tag's value, if requested + /// + /// The count value returned from the server + internal abstract long CountValue { get; } + + /// Function to materialize an entry and produce a value. + internal abstract ProjectionPlan MaterializeEntryPlan { get; } + + /// + /// Gets the materializer context + /// + protected internal IODataMaterializerContext MaterializerContext { get; private set; } + + /// + /// Returns true if the materializer has been disposed + /// + protected abstract bool IsDisposed { get; } + + /// + /// Gets the expected type. + /// + /// + /// The expected type. + /// + protected Type ExpectedType { get; private set; } + + /// + /// Gets the collection value materialization policy. + /// + protected CollectionValueMaterializationPolicy CollectionValueMaterializationPolicy + { + get { return this.collectionValueMaterializationPolicy; } + } + + /// + /// Gets the collection value materialization policy. + /// + protected EnumValueMaterializationPolicy EnumValueMaterializationPolicy + { + get { return this.enumValueMaterializationPolicy; } + } + + /// + /// Gets the instance annotation materialization policy. + /// + protected InstanceAnnotationMaterializationPolicy InstanceAnnotationMaterializationPolicy + { + get { return this.instanceAnnotationMaterializationPolicy; } + } + + /// + /// The converter to use when assigning values of primitive properties. + /// + protected PrimitivePropertyConverter PrimitivePropertyConverter + { + get { return this.lazyPrimitivePropertyConverter.Value; } + } + + /// + /// The policy used to materialize primitive values. + /// + protected PrimitiveValueMaterializationPolicy PrimitiveValueMaterializier + { + get { return this.primitiveValueMaterializationPolicy; } + } + + /// + /// The format of the response being materialized. + /// + protected abstract ODataFormat Format { get; } + + /// + /// Creates an for a response. + /// + /// The response message. + /// The response context. + /// The type to materialize. + /// The query components for the request. + /// The projection plan. + /// expected payload kind. + /// A materializer specialized for the given response. + public static ODataMaterializer CreateMaterializerForMessage( + IODataResponseMessage responseMessage, + ResponseInfo responseInfo, + Type materializerType, + QueryComponents queryComponents, + ProjectionPlan plan, + ODataPayloadKind payloadKind) + { + ODataMessageReader messageReader = CreateODataMessageReader(responseMessage, responseInfo, ref payloadKind); + + ODataMaterializer result; + IEdmType edmType = null; + + try + { + ODataMaterializerContext materializerContext = new ODataMaterializerContext(responseInfo); + + // Since in V1/V2, astoria client allowed Execute and depended on the typeresolver or the wire type name + // to get the clr type to materialize. Hence if we see the materializer type as object, we should set the edmtype + // to null, since there is no expected type. + if (materializerType != typeof(System.Object)) + { + edmType = responseInfo.TypeResolver.ResolveExpectedTypeForReading(materializerType); + } + + if (payloadKind == ODataPayloadKind.Property && edmType != null) + { + if (edmType.TypeKind.IsStructured()) + { + payloadKind = ODataPayloadKind.Resource; + } + else if (edmType.TypeKind == EdmTypeKind.Collection && (edmType as IEdmCollectionType).ElementType.IsStructured()) + { + payloadKind = ODataPayloadKind.ResourceSet; + } + } + + if (payloadKind == ODataPayloadKind.Resource || payloadKind == ODataPayloadKind.ResourceSet) + { + // In V1/V2, we allowed System.Object type to be allowed to pass to ExecuteQuery. + // Hence we need to explicitly check for System.Object to allow this + if (edmType != null && !edmType.TypeKind.IsStructured()) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(materializerType.FullName)); + } + + ODataReaderWrapper reader = ODataReaderWrapper.Create(messageReader, payloadKind, edmType, responseInfo.ResponsePipeline); + EntityTrackingAdapter entityTrackingAdapter = new EntityTrackingAdapter(responseInfo.EntityTracker, responseInfo.MergeOption, responseInfo.Model, responseInfo.Context); + LoadPropertyResponseInfo loadPropertyResponseInfo = responseInfo as LoadPropertyResponseInfo; + + if (loadPropertyResponseInfo != null) + { + result = new ODataLoadNavigationPropertyMaterializer( + messageReader, + reader, + materializerContext, + entityTrackingAdapter, + queryComponents, + materializerType, + plan, + loadPropertyResponseInfo); + } + else + { + result = new ODataReaderEntityMaterializer( + messageReader, + reader, + materializerContext, + entityTrackingAdapter, + queryComponents, + materializerType, + plan); + } + } + else + { + switch (payloadKind) + { + case ODataPayloadKind.Value: + result = new ODataValueMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); + break; + case ODataPayloadKind.Collection: + result = new ODataCollectionMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); + break; + case ODataPayloadKind.Property: + case ODataPayloadKind.IndividualProperty: + // Top level properties cannot be of entity type. + if (edmType != null && (edmType.TypeKind == EdmTypeKind.Entity || edmType.TypeKind == EdmTypeKind.Complex)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidEntityType(materializerType.FullName)); + } + + result = new ODataPropertyMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); + break; + case ODataPayloadKind.EntityReferenceLinks: + case ODataPayloadKind.EntityReferenceLink: + result = new ODataLinksMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); + break; + + case ODataPayloadKind.Error: + var odataError = messageReader.ReadError(); + throw new ODataErrorException(odataError.Message, odataError); + default: + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidResponsePayload(XmlConstants.DataWebNamespace)); + } + } + + return result; + } + catch (Exception ex) + { + if (CommonUtil.IsCatchableExceptionType(ex)) + { + // Dispose the message reader in all error scenarios. + messageReader.Dispose(); + } + + throw; + } + } + + /// Reads the next value from the input content. + /// true if another value is available after reading; false otherwise. + /// + /// After invocation, the currentValue field (and CurrentValue property) will + /// reflect the value materialized from the parser; possibly null if the + /// result is true (for null values); always null if the result is false. + /// + public bool Read() + { + this.VerifyNotDisposed(); + + return this.ReadImplementation(); + } + + /// + /// Disposes the materializer + /// + public void Dispose() + { + this.OnDispose(); + + Debug.Assert(this.IsDisposed, "this.IsDisposed"); + } + + /// Clears the materialization log of activity. + internal abstract void ClearLog(); + + /// Applies the materialization log to the context. + internal abstract void ApplyLogToContext(); + + /// + /// Creates an for a given message and context using + /// WCF DS client settings. + /// + /// The response message + /// The response context + /// Type of the message. + /// The message reader. + protected static ODataMessageReader CreateODataMessageReader(IODataResponseMessage responseMessage, ResponseInfo responseInfo, ref ODataPayloadKind payloadKind) + { + ODataMessageReaderSettings settings = responseInfo.ReadHelper.CreateSettings(); + + ODataMessageReader odataMessageReader = responseInfo.ReadHelper.CreateReader(responseMessage, settings); + + if (payloadKind == ODataPayloadKind.Unsupported) + { + var payloadKinds = odataMessageReader.DetectPayloadKind().ToList(); + + if (payloadKinds.Count == 0) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidResponsePayload(XmlConstants.DataWebNamespace)); + } + + // Pick the first payload kind detected by ODataLib and use that to parse the exception. + // The only exception being payload with entity reference link(s). If one of the payload kinds + // is reference links, then we need to give preference to reference link payloads. + ODataPayloadKindDetectionResult detectionResult = payloadKinds.FirstOrDefault(k => k.PayloadKind == ODataPayloadKind.EntityReferenceLink || k.PayloadKind == ODataPayloadKind.EntityReferenceLinks); + + if (detectionResult == null) + { + detectionResult = payloadKinds.First(); + } + + if (detectionResult.Format != ODataFormat.Json && detectionResult.Format != ODataFormat.RawValue) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidContentTypeEncountered(responseMessage.GetHeader(XmlConstants.HttpContentType))); + } + + payloadKind = detectionResult.PayloadKind; + } + + return odataMessageReader; + } + + /// + /// Verifies that the object is not disposed. + /// + protected void VerifyNotDisposed() + { + if (this.IsDisposed) + { + throw new ObjectDisposedException(typeof(ODataEntityMaterializer).FullName); + } + } + + /// + /// Implementation of . + /// + /// Return value of + protected abstract bool ReadImplementation(); + + /// + /// Called when IDisposable.Dispose is called. + /// + protected abstract void OnDispose(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializerContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializerContext.cs new file mode 100644 index 0000000..266e239 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMaterializerContext.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + + /// + /// Contains state and methods required to materialize odata collection, complex and primitive values + /// + internal class ODataMaterializerContext : IODataMaterializerContext + { + /// + /// Initializes a materializer context + /// + /// Response information used to initialize with the materializer + internal ODataMaterializerContext(ResponseInfo responseInfo) + { + this.ResponseInfo = responseInfo; + } + + /// + /// Data service context associated with this materializer context + /// + public DataServiceContext Context + { + get { return this.ResponseInfo.Context; } + } + + /// + /// Gets a value indicating whether to support missing properties when materializing values or throw exception. + /// + public UndeclaredPropertyBehavior UndeclaredPropertyBehavior + { + get + { + return this.ResponseInfo.Context.UndeclaredPropertyBehavior; + } + } + + /// + /// Gets a Client Edm model used to materialize values + /// + public ClientEdmModel Model + { + get { return this.ResponseInfo.Model; } + } + + /// + /// Gets the materialization Events + /// + public DataServiceClientResponsePipelineConfiguration ResponsePipeline + { + get { return this.ResponseInfo.ResponsePipeline; } + } + + /// + /// Gets the Response information that backs the information on the context + /// + protected ResponseInfo ResponseInfo { get; private set; } + + /// + /// Resolved the given edm type to clr type. + /// + /// Expected Clr type. + /// Edm name of the type returned by the resolver. + /// an instance of ClientTypeAnnotation with the given name. + public ClientTypeAnnotation ResolveTypeForMaterialization(Type expectedType, string wireTypeName) + { + return this.ResponseInfo.TypeResolver.ResolveTypeForMaterialization(expectedType, wireTypeName); + } + + /// + /// Resolves the EDM type for the given CLR type. + /// + /// The client side CLR type. + /// The resolved EDM type. + public IEdmType ResolveExpectedTypeForReading(Type expectedType) + { + return this.ResponseInfo.TypeResolver.ResolveExpectedTypeForReading(expectedType); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMessageReaderMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMessageReaderMaterializer.cs new file mode 100644 index 0000000..7f8d2cb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataMessageReaderMaterializer.cs @@ -0,0 +1,185 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections.Generic; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData; + using ClientStrings = Microsoft.OData.Client.Strings; + + /// + /// Used to materialize a value from an . + /// + internal abstract class ODataMessageReaderMaterializer : ODataMaterializer + { + /// Optional field that indicates if we should expect a single result to materialize, as opposed to a collection. + protected readonly bool? SingleResult; + + /// Reader for a message that contains a value or property. + protected ODataMessageReader messageReader; + + /// Has the value been read. + private bool hasReadValue; + + /// + /// Initializes a new instance of the class. + /// + /// The reader. + /// The materializer context. + /// The expected type. + /// The single result. + public ODataMessageReaderMaterializer(ODataMessageReader reader, IODataMaterializerContext context, Type expectedType, bool? singleResult) + : base(context, expectedType) + { + this.messageReader = reader; + this.SingleResult = singleResult; + } + + /// + /// Feed being materialized; possibly null. + /// + internal sealed override ODataResourceSet CurrentFeed + { + get { return null; } + } + + /// + /// Entry being materialized; possibly null. + /// + internal sealed override ODataResource CurrentEntry + { + get { return null; } + } + + /// + /// Whether we have finished processing the current data stream. + /// + internal sealed override bool IsEndOfStream + { + get { return this.hasReadValue; } + } + + /// + /// The count tag's value, if requested + /// + /// The count value returned from the server + internal override long CountValue + { + get { throw new InvalidOperationException(ClientStrings.MaterializeFromAtom_CountNotPresent); } + } + + /// + /// Function to materialize an entry and produce a value. + /// + internal sealed override ProjectionPlan MaterializeEntryPlan + { + get { throw new NotSupportedException(); } + } + + /// + /// Returns true if the materializer has been disposed + /// + protected sealed override bool IsDisposed + { + get { return this.messageReader == null; } + } + + /// + /// The format of the response being materialized. + /// + protected override ODataFormat Format + { + get { return ODataUtils.GetReadFormat(this.messageReader); } + } + + /// Clears the materialization log of activity. + internal sealed override void ClearLog() + { + // no log to clear + } + + /// Applies the materialization log to the context. + internal sealed override void ApplyLogToContext() + { + // no log to apply + } + + /// + /// Implementation of Read>. + /// + /// + /// Return value of Read/> + /// + protected sealed override bool ReadImplementation() + { + if (!this.hasReadValue) + { + try + { + ClientEdmModel model = this.MaterializerContext.Model; + Type expectedType = this.ExpectedType; + IEdmTypeReference expectedClientType = model.GetOrCreateEdmType(expectedType).ToEdmTypeReference(ClientTypeUtil.CanAssignNull(expectedType)); + if ((this.SingleResult.HasValue && !this.SingleResult.Value) && expectedClientType.Definition.TypeKind != EdmTypeKind.Collection) + { + expectedType = typeof(ICollection<>).MakeGenericType(expectedType); + + // we do not allow null values for collection + expectedClientType = model.GetOrCreateEdmType(expectedType).ToEdmTypeReference(false); + } + + IEdmTypeReference expectedReaderType = this.MaterializerContext.ResolveExpectedTypeForReading(expectedType).ToEdmTypeReference(expectedClientType.IsNullable); + + this.ReadWithExpectedType(expectedClientType, expectedReaderType); + } + catch (ODataErrorException e) + { + throw new DataServiceClientException(ClientStrings.Deserialize_ServerException(e.Error.Message), e); + } + catch (ODataException e) + { + throw new InvalidOperationException(e.Message, e); + } + catch (ArgumentException e) + { + throw new InvalidOperationException(e.Message, e); + } + finally + { + this.hasReadValue = true; + } + + return true; + } + else + { + return false; + } + } + + /// + /// Called when IDisposable.Dispose is called. + /// + protected sealed override void OnDispose() + { + if (this.messageReader != null) + { + this.messageReader.Dispose(); + this.messageReader = null; + } + } + + /// + /// Reads a value from the message reader. + /// + /// The expected client type being materialized into. + /// The expected type for the underlying reader. + protected abstract void ReadWithExpectedType(IEdmTypeReference expectedClientType, IEdmTypeReference expectedReaderType); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataPropertyMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataPropertyMaterializer.cs new file mode 100644 index 0000000..67a3c2d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataPropertyMaterializer.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using Microsoft.OData.Edm; + + /// + /// Used to materialize a property from an . + /// + internal sealed class ODataPropertyMaterializer : ODataMessageReaderMaterializer + { + /// Current value being materialized; possibly null. + private object currentValue; + + /// + /// Initializes a new instance of the class. + /// + /// The reader. + /// The materializer context. + /// The expected type. + /// The single result. + public ODataPropertyMaterializer(ODataMessageReader reader, IODataMaterializerContext materializerContext, Type expectedType, bool? singleResult) + : base(reader, materializerContext, expectedType, singleResult) + { + } + + /// + /// Current value being materialized; possibly null. + /// + internal override object CurrentValue + { + get { return this.currentValue; } + } + + /// + /// Reads a value from the message reader. + /// + /// The expected client type being materialized into. + /// The expected type for the underlying reader. + protected override void ReadWithExpectedType(IEdmTypeReference expectedClientType, IEdmTypeReference expectedReaderType) + { + ODataProperty property = this.messageReader.ReadProperty(expectedReaderType); + Type underlyingExpectedType = Nullable.GetUnderlyingType(this.ExpectedType) ?? this.ExpectedType; + + if (expectedClientType.IsCollection()) + { + Debug.Assert(WebUtil.IsCLRTypeCollection(underlyingExpectedType, this.MaterializerContext.Model) || (SingleResult.HasValue && !SingleResult.Value), "expected type must be collection or single result must be false"); + + // We are here for two cases: + // (1) Something like Execute>, in which case the underlyingExpectedType is ICollection + // (2) Execute with the bool singleValue = false, in which case underlyingExpectedType is T + Type collectionItemType = this.ExpectedType; + Type collectionICollectionType = ClientTypeUtil.GetImplementationType(underlyingExpectedType, typeof(ICollection<>)); + object collectionInstance; + + if (collectionICollectionType != null) + { + // Case 1 + collectionItemType = collectionICollectionType.GetGenericArguments()[0]; + collectionInstance = this.CollectionValueMaterializationPolicy.CreateCollectionPropertyInstance(property, underlyingExpectedType); + } + else + { + // Case 2 + collectionICollectionType = typeof(ICollection<>).MakeGenericType(new Type[] { collectionItemType }); + collectionInstance = this.CollectionValueMaterializationPolicy.CreateCollectionPropertyInstance(property, collectionICollectionType); + } + + bool isElementNullable = expectedClientType.AsCollection().ElementType().IsNullable; + this.CollectionValueMaterializationPolicy.ApplyCollectionDataValues( + property, + collectionInstance, + collectionItemType, + ClientTypeUtil.GetAddToCollectionDelegate(collectionICollectionType), + isElementNullable); + + this.currentValue = collectionInstance; + } + else if (expectedClientType.IsEnum()) + { + this.currentValue = this.EnumValueMaterializationPolicy.MaterializeEnumTypeProperty(underlyingExpectedType, property); + } + else + { + Debug.Assert(this.MaterializerContext.Model.GetOrCreateEdmType(underlyingExpectedType).ToEdmTypeReference(false).IsPrimitive(), "expectedType must be primitive type"); + object value = property.Value; + ODataUntypedValue untypedVal = value as ODataUntypedValue; + if ((untypedVal != null) + && this.MaterializerContext.UndeclaredPropertyBehavior == UndeclaredPropertyBehavior.Support) + { + value = CommonUtil.ParseJsonToPrimitiveValue(untypedVal.RawValue); + } + + this.currentValue = this.PrimitivePropertyConverter.ConvertPrimitiveValue(value, this.ExpectedType); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderEntityMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderEntityMaterializer.cs new file mode 100644 index 0000000..320c5e4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderEntityMaterializer.cs @@ -0,0 +1,186 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using Microsoft.OData; + using Microsoft.OData.Client; + using Microsoft.OData.Edm; + using DSClient = Microsoft.OData.Client; + + /// + /// Materializes feeds and entities from an ODataReader + /// + internal class ODataReaderEntityMaterializer : ODataEntityMaterializer + { + /// The enty or feed reader. + private FeedAndEntryMaterializerAdapter feedEntryAdapter; + + /// The message reader. + private ODataMessageReader messageReader; + + /// + /// Initializes a new instance of the class. + /// + /// The odata message reader. + /// The reader. + /// The materializer context. + /// The entity tracking adapter. + /// The query components. + /// The expected type. + /// The materialize entry plan. + public ODataReaderEntityMaterializer( + ODataMessageReader odataMessageReader, + ODataReaderWrapper reader, + IODataMaterializerContext materializerContext, + EntityTrackingAdapter entityTrackingAdapter, + QueryComponents queryComponents, + Type expectedType, + ProjectionPlan materializeEntryPlan) + : base(materializerContext, entityTrackingAdapter, queryComponents, expectedType, materializeEntryPlan) + { + this.messageReader = odataMessageReader; + this.feedEntryAdapter = new FeedAndEntryMaterializerAdapter(odataMessageReader, reader, materializerContext.Model, entityTrackingAdapter.MergeOption); + } + + /// + /// Feed being materialized; possibly null. + /// + internal override ODataResourceSet CurrentFeed + { + get { return this.feedEntryAdapter.CurrentFeed; } + } + + /// + /// Entry being materialized; possibly null. + /// + internal override ODataResource CurrentEntry + { + get { return this.feedEntryAdapter.CurrentEntry; } + } + + /// + /// Whether we have finished processing the current data stream. + /// + internal override bool IsEndOfStream + { + get { return this.IsDisposed || this.feedEntryAdapter.IsEndOfStream; } + } + + /// + /// The count tag's value, if requested + /// + /// The count value returned from the server + internal override long CountValue + { + get + { + return this.feedEntryAdapter.GetCountValue(!this.IsDisposed); + } + } + + /// + /// Returns true if the underlying object used for counting is available + /// + internal override bool IsCountable + { + get { return true; } + } + + /// + /// Returns true if the materializer has been disposed + /// + protected override bool IsDisposed + { + get { return this.messageReader == null; } + } + + /// + /// The format of the response being materialized. + /// + protected override ODataFormat Format + { + get { return ODataUtils.GetReadFormat(this.messageReader); } + } + + /// + /// This method is for parsing CUD operation payloads which should contain + /// 1 a single entry + /// 2 An Error + /// + /// the message for the payload + /// The current ResponseInfo object + /// The expected type + /// the MaterializerEntry that was read + internal static MaterializerEntry ParseSingleEntityPayload(IODataResponseMessage message, ResponseInfo responseInfo, Type expectedType) + { + ODataPayloadKind messageType = ODataPayloadKind.Resource; + using (ODataMessageReader messageReader = CreateODataMessageReader(message, responseInfo, ref messageType)) + { + IEdmType edmType = responseInfo.TypeResolver.ResolveExpectedTypeForReading(expectedType); + ODataReaderWrapper reader = ODataReaderWrapper.Create(messageReader, messageType, edmType, responseInfo.ResponsePipeline); + + FeedAndEntryMaterializerAdapter parser = new FeedAndEntryMaterializerAdapter(messageReader, reader, responseInfo.Model, responseInfo.MergeOption); + + ODataResource entry = null; + bool readFeed = false; + while (parser.Read()) + { + readFeed |= parser.CurrentFeed != null; + if (parser.CurrentEntry != null) + { + if (entry != null) + { + throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_MultipleFound); + } + + entry = parser.CurrentEntry; + } + } + + if (entry == null) + { + if (readFeed) + { + throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_NoneFound); + } + else + { + throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_ExpectedFeedOrEntry); + } + } + + return MaterializerEntry.GetEntry(entry); + } + } + + /// + /// Called when IDisposable.Dispose is called. + /// + protected override void OnDispose() + { + if (this.messageReader != null) + { + this.messageReader.Dispose(); + this.messageReader = null; + } + + this.feedEntryAdapter.Dispose(); + } + + /// + /// Reads the next feed or entry. + /// + /// + /// True if an entry was read, otherwise false + /// + protected override bool ReadNextFeedOrEntry() + { + return this.feedEntryAdapter.Read(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderWrapper.cs new file mode 100644 index 0000000..5984e0f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataReaderWrapper.cs @@ -0,0 +1,127 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + using Microsoft.OData; + + /// + /// Contains an odata reader that is wrapped + /// + internal class ODataReaderWrapper + { + /// The reader. + private readonly ODataReader reader; + + /// The payload reading events. + private readonly DataServiceClientResponsePipelineConfiguration responsePipeline; + + /// + /// Prevents a default instance of the class from being created. + /// + /// The reader. + /// The data service response pipeling configuration object. + private ODataReaderWrapper(ODataReader reader, DataServiceClientResponsePipelineConfiguration responsePipeline) + { + Debug.Assert(reader != null, "reader!=null"); + this.reader = reader; + this.responsePipeline = responsePipeline; + } + + /// + /// The current state of the reader. + /// + public ODataReaderState State + { + get + { + return this.reader.State; + } + } + + /// + /// The most recent that has been read. + /// + public ODataItem Item + { + get + { + return this.reader.Item; + } + } + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + public bool Read() + { + bool read = this.reader.Read(); + + if (read && this.responsePipeline.HasConfigurations) + { + // ODataReaderState.EntityReferenceLink is not in the switch as entity reference link + // is only in an update payload so it will never occur + switch (this.reader.State) + { + case ODataReaderState.ResourceStart: + this.responsePipeline.ExecuteOnEntryStartActions((ODataResource)this.reader.Item); + break; + case ODataReaderState.ResourceEnd: + this.responsePipeline.ExecuteOnEntryEndActions((ODataResource)this.reader.Item); + break; + case ODataReaderState.ResourceSetStart: + this.responsePipeline.ExecuteOnFeedStartActions((ODataResourceSet)this.reader.Item); + break; + case ODataReaderState.ResourceSetEnd: + this.responsePipeline.ExecuteOnFeedEndActions((ODataResourceSet)this.reader.Item); + break; + case ODataReaderState.NestedResourceInfoStart: + this.responsePipeline.ExecuteOnNavigationStartActions((ODataNestedResourceInfo)this.reader.Item); + break; + case ODataReaderState.NestedResourceInfoEnd: + this.responsePipeline.ExecuteOnNavigationEndActions((ODataNestedResourceInfo)this.reader.Item); + break; + } + } + + return read; + } + + /// + /// Creates and Wraps an ODataReader for feeds or entries. + /// + /// The message reader. + /// The message type. + /// The expected EDM type. + /// The data service response pipeling configuration object. + /// A reader. + internal static ODataReaderWrapper Create(ODataMessageReader messageReader, ODataPayloadKind messageType, IEdmType expectedType, DataServiceClientResponsePipelineConfiguration responsePipeline) + { + IEdmStructuredType entityType = expectedType as IEdmStructuredType; + if (messageType == ODataPayloadKind.Resource) + { + return new ODataReaderWrapper(messageReader.CreateODataResourceReader(entityType), responsePipeline); + } + + return new ODataReaderWrapper(messageReader.CreateODataResourceSetReader(entityType), responsePipeline); + } + + /// + /// Wraps an ODataReader + /// + /// The reader. + /// The data service response pipeling configuration object. + /// A reader. + internal static ODataReaderWrapper CreateForTest(ODataReader reader, DataServiceClientResponsePipelineConfiguration responsePipeline) + { + return new ODataReaderWrapper(reader, responsePipeline); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataValueMaterializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataValueMaterializer.cs new file mode 100644 index 0000000..5afe8e8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/ODataValueMaterializer.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using Microsoft.OData.Client; + using Microsoft.OData.Edm; + using Microsoft.OData; + + /// + /// Used to materialize a value from an . + /// + internal sealed class ODataValueMaterializer : ODataMessageReaderMaterializer + { + /// Current value being materialized; possibly null. + private object currentValue; + + /// + /// Initializes a new instance of the class. + /// + /// The reader. + /// The materializer context. + /// The expected type. + /// Is a single result expected. + public ODataValueMaterializer(ODataMessageReader reader, IODataMaterializerContext materializerContext, Type expectedType, bool? singleResult) + : base(reader, materializerContext, expectedType, singleResult) + { + } + + /// + /// Current value being materialized; possibly null. + /// + internal override object CurrentValue + { + get { return this.currentValue; } + } + + /// + /// Reads a value from the message reader. + /// + /// The expected client type being materialized into. + /// The expected type for the underlying reader. + protected override void ReadWithExpectedType(IEdmTypeReference expectedClientType, IEdmTypeReference expectedReaderType) + { + object value = this.messageReader.ReadValue(expectedReaderType); + this.currentValue = this.PrimitiveValueMaterializier.MaterializePrimitiveDataValue(this.ExpectedType, null, value); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitivePropertyConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitivePropertyConverter.cs new file mode 100644 index 0000000..2fffc95 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitivePropertyConverter.cs @@ -0,0 +1,206 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.Xml.Linq; + using Microsoft.Spatial; + + /// + /// Converter for primitive values which do not match the client property types. This can happen for two reasons: + /// 1) The client property types do not exist in the protocol (Uri, XElement, etc) + /// 2) The values were read using the service's model, and the client types are slightly different (ie float vs double, int vs long). + /// + internal class PrimitivePropertyConverter + { + /// Geo JSON formatter used for converting spatial values. Lazily created in case no spatial values are ever converted. + private readonly SimpleLazy lazyGeoJsonFormatter = new SimpleLazy(GeoJsonObjectFormatter.Create); + + /// + /// Converts a value to primitive value. + /// + /// The value. + /// Type of the property. + /// The converted value if the value can be converted + internal object ConvertPrimitiveValue(object value, Type propertyType) + { + Debug.Assert(!(value is ODataUntypedValue), "!(propertyValue is ODataUntypedValue)"); + + // System.Xml.Linq.XElement and System.Data.Linq.Binaries primitive types are not supported by ODataLib directly, + // so if the property is of one of those types, we need to convert the value to that type here. + if (propertyType != null && value != null) + { + if (!PrimitiveType.IsKnownNullableType(propertyType)) + { + throw new InvalidOperationException(Client.Strings.ClientType_UnsupportedType(propertyType)); + } + + // Fast path for the supported primitive types that have a type code and are supported by ODataLib. + Type nonNullablePropertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + if (IsSupportedODataPrimitiveType(nonNullablePropertyType)) + { + return this.ConvertValueIfNeeded(value, propertyType); + } + + // Do the conversion for types that are not supported by ODataLib e.g. char[], char, etc + // PropertyType might be nullable. Hence to avoid nullable checks, we currently check for + // primitiveType.ClrType + if (CanMapToODataPrimitiveType(nonNullablePropertyType)) + { + PrimitiveType primitiveType; + PrimitiveType.TryGetPrimitiveType(propertyType, out primitiveType); + Debug.Assert(primitiveType != null, "must be a known primitive type"); + + string stringValue = (string)this.ConvertValueIfNeeded(value, typeof(string)); + return primitiveType.TypeConverter.Parse(stringValue); + } + +#if !PORTABLELIB + if (propertyType == BinaryTypeConverter.BinaryType) + { + byte[] byteArray = (byte[])this.ConvertValueIfNeeded(value, typeof(byte[])); + Debug.Assert(byteArray != null, "If the property is of type System.Data.Linq.Binary then ODataLib should have read it as byte[]."); + return Activator.CreateInstance(BinaryTypeConverter.BinaryType, byteArray); + } +#endif + } + + return this.ConvertValueIfNeeded(value, propertyType); + } + + private static bool IsSupportedODataPrimitiveType(Type type) + { + return type == typeof(Boolean) || type == typeof(Byte) || + type == typeof(Decimal) || type == typeof(Double) || + type == typeof(Int16) || type == typeof(Int32) || + type == typeof(Int64) || type == typeof(SByte) || + type == typeof(Single) || type == typeof(String); + } + + private static bool CanMapToODataPrimitiveType(Type type) + { + return type == typeof(Char) || type == typeof(UInt16) || + type == typeof(UInt32) || type == typeof(UInt64) || + type == typeof(Char[]) || type == typeof(Type) || + type == typeof(Uri) || type == typeof(XDocument) || + type == typeof(XElement); + } + + /// + /// Converts a non-spatial primitive value to the target type. + /// + /// The value to convert. + /// The target type of the conversion. + /// The converted value. + private static object ConvertNonSpatialValue(object value, Type targetType) + { + Debug.Assert(value != null, "value != null"); + + // These types can be safely converted to directly, as there is no risk of precision being lost. + if (CanSafelyConvertTo(targetType)) + { + return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture); + } + + string stringValue = ClientConvert.ToString(value); + return ClientConvert.ChangeType(stringValue, targetType); + } + + private static bool CanSafelyConvertTo(Type targetType) + { + return targetType == typeof(Boolean) || targetType == typeof(Byte) || + targetType == typeof(SByte) || targetType == typeof(Int16) || + targetType == typeof(Int32) || targetType == typeof(Int64); + } + + /// + /// Converts the value to the target type if needed. + /// + /// The value to convert. + /// The target type. + /// The converted value. + private object ConvertValueIfNeeded(object value, Type targetType) + { + // if conversion is not needed, just short cut here. + if (value == null || targetType.IsInstanceOfType(value)) + { + return value; + } + + // spatial types require some extra work, as they cannot be easily converted to/from string. + if (typeof(ISpatial).IsAssignableFrom(targetType) || value is ISpatial) + { + return this.ConvertSpatialValue(value, targetType); + } + + var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType); + if (nullableUnderlyingType != null) + { + targetType = nullableUnderlyingType; + } + + // re-parse the primitive value. + return ConvertNonSpatialValue(value, targetType); + } + + /// + /// Converts a spatial value by from geometry to geography or vice versa. Will return the original instance if it is already of the appropriate hierarchy. + /// Will throw whatever parsing/format exceptions occur if the sub type is not the same. + /// + /// The value to convert. + /// The target type of the conversion. + /// The original or converted value. + private object ConvertSpatialValue(object value, Type targetType) + { + Debug.Assert(value != null, "value != null"); + Debug.Assert(value is ISpatial && typeof(ISpatial).IsAssignableFrom(targetType), "Arguments must be spatial values/types."); + + // because spatial values already encode their specific subtype (ie point vs polygon), then the only way this conversion can work + // is if the switch is from geometry to geography, but with the same subtype. So if we detect that the value is already in the same + // hierarchy as the target type, then simply return it. + if (typeof(Geometry).IsAssignableFrom(targetType)) + { + var geographyValue = value as Geography; + if (geographyValue == null) + { + return value; + } + + return this.ConvertSpatialValue(geographyValue); + } + + Debug.Assert(typeof(Geography).IsAssignableFrom(targetType), "Unrecognized spatial target type: " + targetType.FullName); + + // as above, if the hierarchy already matches, simply return it. + var geometryValue = value as Geometry; + if (geometryValue == null) + { + return value; + } + + return this.ConvertSpatialValue(geometryValue); + } + + /// + /// Converts a spatial value by from geometry to geography or vice versa. Will return the original instance if it is already of the appropriate hierarchy. + /// Will throw whatever parsing/format exceptions occur if the sub type is not the same. + /// + /// The type of the value being converted. + /// The target type of the conversion. + /// The value to convert. + /// The original or converted value. + private TOut ConvertSpatialValue(TIn valueToConvert) + where TIn : ISpatial + where TOut : class, ISpatial + { + var json = this.lazyGeoJsonFormatter.Value.Write(valueToConvert); + return this.lazyGeoJsonFormatter.Value.Read(json); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitiveValueMaterializationPolicy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitiveValueMaterializationPolicy.cs new file mode 100644 index 0000000..7ed21fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/PrimitiveValueMaterializationPolicy.cs @@ -0,0 +1,129 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Diagnostics; + using Microsoft.OData.Client.Metadata; + using DSClient = Microsoft.OData.Client; + + /// + /// Creates a policy that is used for materializing Primitive values + /// + internal class PrimitiveValueMaterializationPolicy + { + /// MaterializerContext used to resolve types for materialization. + private readonly IODataMaterializerContext context; + + /// + /// primitive property converter used to convert the property have the value has been materialized. + private readonly SimpleLazy lazyPrimitivePropertyConverter; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The lazy primitive property converter. + internal PrimitiveValueMaterializationPolicy(IODataMaterializerContext context, SimpleLazy lazyPrimitivePropertyConverter) + { + this.context = context; + this.lazyPrimitivePropertyConverter = lazyPrimitivePropertyConverter; + } + + /// + /// Gets the primitive property converter. + /// + /// + /// The primitive property converter. + /// + private PrimitivePropertyConverter PrimitivePropertyConverter + { + get { return this.lazyPrimitivePropertyConverter.Value; } + } + + /// + /// Materializes the primitive data value. + /// + /// Type of the collection item. + /// Name of the wire type. + /// The item. + /// Materialized primitive data value. + public object MaterializePrimitiveDataValue(Type collectionItemType, string wireTypeName, object item) + { + object materializedValue = null; + this.MaterializePrimitiveDataValue(collectionItemType, wireTypeName, item, () => "TODO: Is this reachable?", out materializedValue); + + return materializedValue; + } + + /// + /// Materializes the primitive data value collection element. + /// + /// The collection item type. + /// Name of the wire type. + /// The item. + /// Materialized primitive collection element value + public object MaterializePrimitiveDataValueCollectionElement(Type collectionItemType, string wireTypeName, object item) + { + object materializedValue = null; + this.MaterializePrimitiveDataValue(collectionItemType, wireTypeName, item, () => DSClient.Strings.Collection_NullCollectionItemsNotSupported, out materializedValue); + + return materializedValue; + } + + /// Materializes a primitive value. No op or non-primitive values. + /// Type of value to set. + /// Type name from the payload. + /// Value of primitive provided by ODL. + /// The exception message if the value is null. + /// The materialized value. + private void MaterializePrimitiveDataValue(Type type, string wireTypeName, object value, Func throwOnNullMessage, out object materializedValue) + { + Debug.Assert(type != null, "type != null"); + + ClientTypeAnnotation nestedElementType = null; + Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; + + PrimitiveType ptype; + bool knownType = PrimitiveType.TryGetPrimitiveType(underlyingType, out ptype); + if (!knownType) + { + nestedElementType = this.context.ResolveTypeForMaterialization(type, wireTypeName); + Debug.Assert(nestedElementType != null, "nestedElementType != null -- otherwise ReadTypeAttribute (or someone!) should throw"); + knownType = PrimitiveType.TryGetPrimitiveType(nestedElementType.ElementType, out ptype); + } + + if (knownType) + { + if (value == null) + { + if (!ClientTypeUtil.CanAssignNull(type)) + { + throw new InvalidOperationException(throwOnNullMessage()); + } + + materializedValue = null; + } + else + { + ODataUntypedValue untypedVal = value as ODataUntypedValue; + if ((untypedVal != null) + && this.context.UndeclaredPropertyBehavior == UndeclaredPropertyBehavior.Support) + { + value = CommonUtil.ParseJsonToPrimitiveValue(untypedVal.RawValue); + } + + materializedValue = this.PrimitivePropertyConverter.ConvertPrimitiveValue(value, underlyingType); + } + } + else + { + materializedValue = null; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/StructuralValueMaterializationPolicy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/StructuralValueMaterializationPolicy.cs new file mode 100644 index 0000000..c208a60 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Materialization/StructuralValueMaterializationPolicy.cs @@ -0,0 +1,279 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Materialization +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using Microsoft.OData.Client; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData; + using Microsoft.OData.Edm; + using DSClient = Microsoft.OData.Client; + + /// + /// Contains logic on how to materialize properties into an instance + /// + internal abstract class StructuralValueMaterializationPolicy : MaterializationPolicy + { + /// The collection value materialization policy. + private CollectionValueMaterializationPolicy collectionValueMaterializationPolicy; + + /// The instance annnotation materialization policy. + private InstanceAnnotationMaterializationPolicy instanceAnnotationMaterializationPolicy; + + /// The primitive property converter. + private DSClient.SimpleLazy lazyPrimitivePropertyConverter; + + /// + /// Initializes a new instance of the class. + /// + /// The materializer context. + /// The lazy primitive property converter. + protected StructuralValueMaterializationPolicy(IODataMaterializerContext materializerContext, DSClient.SimpleLazy lazyPrimitivePropertyConverter) + { + Debug.Assert(materializerContext != null, "materializer!=null"); + this.MaterializerContext = materializerContext; + this.lazyPrimitivePropertyConverter = lazyPrimitivePropertyConverter; + } + + /// + /// Gets the collection value materialization policy. + /// + /// + /// The collection value materialization policy. + /// + protected internal CollectionValueMaterializationPolicy CollectionValueMaterializationPolicy + { + get + { + Debug.Assert(this.collectionValueMaterializationPolicy != null, "collectionValueMaterializationPolicy != null"); + return this.collectionValueMaterializationPolicy; + } + + set + { + this.collectionValueMaterializationPolicy = value; + } + } + + /// + /// Gets the instance annnotation materialization policy. + /// + /// + /// The instance annnotation materialization policy. + /// + protected internal InstanceAnnotationMaterializationPolicy InstanceAnnotationMaterializationPolicy + { + get + { + Debug.Assert(this.instanceAnnotationMaterializationPolicy != null, "instanceAnnotationMaterializationPolicy != null"); + return this.instanceAnnotationMaterializationPolicy; + } + + set + { + this.instanceAnnotationMaterializationPolicy = value; + } + } + + /// + /// Gets the primitive property converter. + /// + /// + /// The primitive property converter. + /// + protected PrimitivePropertyConverter PrimitivePropertyConverter + { + get + { + Debug.Assert(this.lazyPrimitivePropertyConverter != null, "lazyPrimitivePropertyConverter != null"); + return this.lazyPrimitivePropertyConverter.Value; + } + } + + /// + /// Gets the materializer context. + /// + /// + /// The materializer context. + /// + protected IODataMaterializerContext MaterializerContext { get; private set; } + + /// Materializes a primitive value. No op for non-primitive values. + /// Type of value to set. + /// Property holding value. + internal void MaterializePrimitiveDataValue(Type type, ODataProperty property) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(property != null, "atomProperty != null"); + + if (!property.HasMaterializedValue()) + { + object value = property.Value; + ODataUntypedValue untypedVal = value as ODataUntypedValue; + if ((untypedVal != null) + && this.MaterializerContext.UndeclaredPropertyBehavior == UndeclaredPropertyBehavior.Support) + { + value = CommonUtil.ParseJsonToPrimitiveValue(untypedVal.RawValue); + } + + object materializedValue = this.PrimitivePropertyConverter.ConvertPrimitiveValue(value, type); + property.SetMaterializedValue(materializedValue); + } + } + + /// + /// Applies the values of the specified to a given . + /// + /// Type to which properties will be applied. + /// Properties to assign to the specified . + /// Instance on which values will be applied. + internal void ApplyDataValues(ClientTypeAnnotation type, IEnumerable properties, object instance) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(properties != null, "properties != null"); + Debug.Assert(instance != null, "instance != context"); + + foreach (var p in properties) + { + this.ApplyDataValue(type, p, instance); + } + } + + /// Applies a data value to the specified . + /// Type to which a property value will be applied. + /// Property with value to apply. + /// Instance on which value will be applied. + internal void ApplyDataValue(ClientTypeAnnotation type, ODataProperty property, object instance) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(property != null, "property != null"); + Debug.Assert(instance != null, "instance != null"); + + var prop = type.GetProperty(property.Name, this.MaterializerContext.UndeclaredPropertyBehavior); + if (prop == null) + { + return; + } + + // Is it a collection? (note: property.Properties will be null if the Collection is empty (contains no elements)) + Type enumTypeTmp = null; + if (prop.IsPrimitiveOrEnumOrComplexCollection) + { + // Collections must not be null + if (property.Value == null) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Collection_NullCollectionNotSupported(property.Name)); + } + + // This happens if the payload contain just primitive value for a Collection property + if (property.Value is string) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MixedTextWithComment); + } + + // ODataLib already parsed the data and materialized all the primitive types. There is nothing more to materialize + // anymore. Only complex type instance and collection instances need to be materialized, but those will be + // materialized later on. + // We need to materialize items before we change collectionInstance since this may throw. If we tried materializing + // after the Collection is wiped or created we would leave the object in half constructed state. + object collectionInstance = prop.GetValue(instance); + if (collectionInstance == null) + { + collectionInstance = this.CollectionValueMaterializationPolicy.CreateCollectionPropertyInstance(property, prop.PropertyType); + + // allowAdd is false - we need to assign instance as the new property value + prop.SetValue(instance, collectionInstance, property.Name, false /* allowAdd? */); + } + else + { + // Clear existing Collection + prop.ClearBackingICollectionInstance(collectionInstance); + } + + bool isElementNullable = prop.EdmProperty.Type.AsCollection().ElementType().IsNullable; + this.CollectionValueMaterializationPolicy.ApplyCollectionDataValues( + property, + collectionInstance, + prop.PrimitiveOrComplexCollectionItemType, + prop.AddValueToBackingICollectionInstance, + isElementNullable); + } + else if ((enumTypeTmp = Nullable.GetUnderlyingType(prop.NullablePropertyType) ?? prop.NullablePropertyType) != null + && enumTypeTmp.IsEnum()) + { + ODataEnumValue enumValue = property.Value as ODataEnumValue; + object tmpValue = EnumValueMaterializationPolicy.MaterializeODataEnumValue(enumTypeTmp, enumValue); + + // TODO: 1. use EnumValueMaterializationPolicy 2. handle nullable enum property + prop.SetValue(instance, tmpValue, property.Name, false /* allowAdd? */); + } + else + { + this.MaterializePrimitiveDataValue(prop.NullablePropertyType, property); + prop.SetValue(instance, property.GetMaterializedValue(), property.Name, true /* allowAdd? */); + } + + if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization) + { + // Apply instance annotation for Property + this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(property, type.ElementType, instance); + } + } + + /// + /// Materializes the primitive data values in the given list of . + /// + /// Actual type for properties being materialized. + /// List of values to materialize. + /// + /// Whether properties missing from the client types should be supported or throw exception. + /// + /// + /// Values are materialized in-place withi each + /// instance. + /// + internal void MaterializeDataValues(ClientTypeAnnotation actualType, IEnumerable values, UndeclaredPropertyBehavior undeclaredPropertyBehavior) + { + Debug.Assert(actualType != null, "actualType != null"); + Debug.Assert(values != null, "values != null"); + + foreach (var odataProperty in values) + { + if (odataProperty.Value is ODataStreamReferenceValue) + { + continue; + } + + string propertyName = odataProperty.Name; + + var property = actualType.GetProperty(propertyName, undeclaredPropertyBehavior); // may throw + if (property == null) + { + continue; + } + + // we should throw if the property type on the client does not match with the property type in the server + // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa + if (ClientTypeUtil.TypeOrElementTypeIsEntity(property.PropertyType)) + { + throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidEntityType(property.EntityCollectionItemType ?? property.PropertyType)); + } + + if (property.IsKnownType) + { + // Note: MaterializeDataValue materializes only properties of primitive types. Materialization specific + // to complex types and collections is done later. + this.MaterializePrimitiveDataValue(property.NullablePropertyType, odataProperty); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializeFromAtom.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializeFromAtom.cs new file mode 100644 index 0000000..7b43ccd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializeFromAtom.cs @@ -0,0 +1,595 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using Microsoft.OData.Client.Materialization; + using Microsoft.OData.Client.Metadata; + using System.Diagnostics; + using System.Xml; + using Microsoft.OData; + + #endregion Namespaces + + /// + /// Use this class to materialize objects from an application/atom+xml stream. + /// + internal class MaterializeAtom : IDisposable, IEnumerable, IEnumerator + { + #region Private fields + + /// Backreference to the context to allow type resolution. + private readonly ResponseInfo responseInfo; + + /// base type of the object to create from the stream. + private readonly Type elementType; + + /// when materializing a known type (like string) + /// <property> text-value </property> + private readonly bool expectingPrimitiveValue; + + /// Materializer from which instances are read. + /// + /// The log for the materializer stores all changes for the + /// associated data context. + /// + private readonly ODataMaterializer materializer; + + /// untyped current object + private object current; + + /// has GetEnumerator been called? + private bool calledGetEnumerator; + + /// Whether anything has been read. + private bool moved; + + /// + /// output writer, set using reflection + /// +#if DEBUG + private System.IO.TextWriter writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture); +#else +#pragma warning disable 649 + private System.IO.TextWriter writer; +#pragma warning restore 649 +#endif + + #endregion Private fields + + /// + /// constructor + /// + /// originating context + /// Query components (projection, expected type) + /// Projection plan (if compiled in an earlier query). + /// responseMessage + /// The kind of the payload to materialize. + internal MaterializeAtom( + ResponseInfo responseInfo, + QueryComponents queryComponents, + ProjectionPlan plan, + IODataResponseMessage responseMessage, + ODataPayloadKind payloadKind) + { + Debug.Assert(queryComponents != null, "queryComponents != null"); + + this.responseInfo = responseInfo; + this.elementType = queryComponents.LastSegmentType; + this.expectingPrimitiveValue = PrimitiveType.IsKnownNullableType(elementType); + + Debug.Assert(responseMessage != null, "Response message is null! Did you mean to use Materializer.ResultsWrapper/EmptyResults?"); + + Type implementationType; + Type materializerType = GetTypeForMaterializer(this.expectingPrimitiveValue, this.elementType, responseInfo.Model, out implementationType); + this.materializer = ODataMaterializer.CreateMaterializerForMessage(responseMessage, responseInfo, materializerType, queryComponents, plan, payloadKind); + } + + /// + /// constructor + /// + /// originating context + /// entries that needs to be materialized. + /// result type. + /// The format of the response being materialized from. + internal MaterializeAtom(ResponseInfo responseInfo, IEnumerable entries, Type elementType, ODataFormat format) + { + this.responseInfo = responseInfo; + this.elementType = elementType; + this.expectingPrimitiveValue = PrimitiveType.IsKnownNullableType(elementType); + + Type implementationType; + Type materializerType = GetTypeForMaterializer(this.expectingPrimitiveValue, this.elementType, responseInfo.Model, out implementationType); + QueryComponents qc = new QueryComponents(null, Util.ODataVersionEmpty, elementType, null, null); + ODataMaterializerContext context = new ODataMaterializerContext(responseInfo); + EntityTrackingAdapter entityTrackingAdapter = new EntityTrackingAdapter(responseInfo.EntityTracker, responseInfo.MergeOption, responseInfo.Model, responseInfo.Context); + this.materializer = new ODataEntriesEntityMaterializer(entries, context, entityTrackingAdapter, qc, materializerType, null, format); + } + + /// + /// Private internal constructor used for creating empty wrapper. + /// + private MaterializeAtom() + { + } + + #region Current + + /// Loosely typed current object property. + /// + /// For collection properties this property will be of AtomContentProperties to allow further materialization processing. + /// Otherwise the value should be of the right type, as the materializer takes the expected type into account. + /// + public object Current + { + get + { + object currentValue = this.current; + return currentValue; + } + } + + #endregion + + /// + /// A materializer for empty results + /// + internal static MaterializeAtom EmptyResults + { + get + { + return new ResultsWrapper(null, null, null); + } + } + + /// + /// if the response from server is null (no-content) + /// + /// return true if it no content. + internal bool IsNoContentResponse() + { + ResultsWrapper wrapper = this as ResultsWrapper; + return wrapper != null && wrapper.IsEmptyResult(); + } + + /// + /// Returns true if the underlying object used for counting is available + /// + internal bool IsCountable + { + get { return this.materializer != null && this.materializer.IsCountable; } + } + + /// + /// The data service context object this materializer belongs to + /// + internal virtual DataServiceContext Context + { + get { return this.responseInfo.Context; } + } + + /// + /// The action to set instance annotation for property or entry or feed + /// + internal Action> SetInstanceAnnotations + { + get + { + return this.materializer == null ? null : this.materializer.SetInstanceAnnotations; + } + set + { + if (this.materializer != null) + { + this.materializer.SetInstanceAnnotations = value; + } + } + } + + #region IDisposable + /// + /// dispose + /// + public void Dispose() + { + this.current = null; + + if (null != this.materializer) + { + this.materializer.Dispose(); + } + + if (null != this.writer) + { + this.writer.Dispose(); + } + + GC.SuppressFinalize(this); + } + + #endregion + + #region IEnumerable + /// + /// as IEnumerable + /// + /// this + public virtual IEnumerator GetEnumerator() + { + this.CheckGetEnumerator(); + return this; + } + #endregion + + /// + /// Gets the type that of the instances that will be returned by materializer. + /// + /// Whether the expected is a primitive type. + /// Actual type on the client. + /// The client model used. + /// The actual type that implements ICollection<> + /// Type of the instances that will be returned by materializer. + /// + /// For collection navigation properties (i.e. ICollection<T> where T is an entity the method returns T. Materializer will + /// return single entity each time MoveNext() is called. However for collection properties we need the whole property instead of just a + /// single collection item. + /// + private static Type GetTypeForMaterializer(bool expectingPrimitiveValue, Type elementType, ClientEdmModel model, out Type implementationType) + { + if (!expectingPrimitiveValue && typeof(IEnumerable).IsAssignableFrom(elementType)) + { + implementationType = ClientTypeUtil.GetImplementationType(elementType, typeof(ICollection<>)); + if (implementationType != null) + { + Type expectedType = implementationType.GetGenericArguments()[0]; // already know its ICollection<> + + // We should use the inner type only if this is a collection of entities (as opposed to a collection of primitive or complex types) + if (ClientTypeUtil.TypeIsStructured(expectedType, model)) + { + return expectedType; + } + } + } + + implementationType = null; + return elementType; + } + + /// + /// Creates the next object from the stream. + /// + /// false if stream is finished + public bool MoveNext() + { + bool applying = this.responseInfo.ApplyingChanges; + try + { + this.responseInfo.ApplyingChanges = true; + return this.MoveNextInternal(); + } + finally + { + this.responseInfo.ApplyingChanges = applying; + } + } + + /// + /// Creates the next object from the stream. + /// + /// false if stream is finished + private bool MoveNextInternal() + { + // For empty results using ResultsWrapper, just return false. + if (this.materializer == null) + { + Debug.Assert(this.current == null, "this.current == null -- otherwise this.materializer should have some value."); + return false; + } + + this.current = null; + this.materializer.ClearLog(); + + bool result = false; + Type implementationType; + GetTypeForMaterializer(this.expectingPrimitiveValue, this.elementType, this.responseInfo.Model, out implementationType); + + // if implementationType is not null this is a collection of entities + if (implementationType != null) + { + // this is for CreateQuery/Execute> + // We should only "move" once in here, in the first iteration we will load everything and return true + // Second iteration we will always return false. + + if (this.moved) + { + return false; + } + + Type expectedType = implementationType.GetGenericArguments()[0]; // already know its IList<> + implementationType = this.elementType; + if (implementationType.IsInterface()) + { + implementationType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(expectedType); + } + + IList list = (IList)Activator.CreateInstance(implementationType); + + while (this.materializer.Read()) + { + list.Add(this.materializer.CurrentValue); + } + + this.moved = true; + + this.current = list; + result = true; + } + + if (null == this.current) + { + if (this.expectingPrimitiveValue && this.moved) + { + result = false; + } + else + { + result = this.materializer.Read(); + if (result) + { + BaseEntityType entity = this.materializer.CurrentValue as BaseEntityType; + if (entity != null) + { + entity.Context = this.responseInfo.Context; + } + + this.current = this.materializer.CurrentValue; + } + + this.moved = true; + } + } + + this.materializer.ApplyLogToContext(); + + return result; + } + + /// + /// Not supported. + /// + /// Always thrown + void System.Collections.IEnumerator.Reset() + { + throw Error.NotSupported(); + } + + /// + /// Creates materializer for results + /// + /// Context of expression to analyze. + /// the results to wrap + /// a new materializer + internal static MaterializeAtom CreateWrapper(DataServiceContext context, IEnumerable results) + { + return new ResultsWrapper(context, results, null); + } + + /// Creates a materializer for partial result sets. + /// Context of expression to analyze. + /// The current page of results + /// The continuation for the results. + /// A new materializer. + internal static MaterializeAtom CreateWrapper(DataServiceContext context, IEnumerable results, DataServiceQueryContinuation continuation) + { + return new ResultsWrapper(context, results, continuation); + } + + /// set the inserted object expected in the response + /// object being inserted that the response is looking for + internal void SetInsertingObject(object addedObject) + { + Debug.Assert(this.materializer is ODataEntityMaterializer, "this.materializer is ODataEntityMaterializer"); + ((ODataEntityMaterializer)this.materializer).TargetInstance = addedObject; + } + + /// + /// The count tag's value, if requested + /// + /// The count value returned from the server + internal long CountValue() + { + return this.materializer.CountValue; + } + + /// + /// Returns the next link URI for the collection key + /// + /// The collection for which the Uri is returned, or null, if the top level link is to be returned + /// An Uri pointing to the next page for the collection + internal virtual DataServiceQueryContinuation GetContinuation(IEnumerable key) + { + Debug.Assert(this.materializer != null, "Materializer is null!"); + + DataServiceQueryContinuation result; + if (key == null) + { + if ((this.expectingPrimitiveValue && !this.moved) || (!this.expectingPrimitiveValue && !this.materializer.IsEndOfStream)) + { + // expectingSingleValue && !moved : haven't started parsing single value (single value should not have next link anyway) + // !expectingSingleValue && !IsEndOfStream : collection type feed did not finish parsing yet + throw new InvalidOperationException(Strings.MaterializeFromAtom_TopLevelLinkNotAvailable); + } + + // we have already moved to the end of stream + // are we singleton or just an entry? + if (this.expectingPrimitiveValue || this.materializer.CurrentFeed == null) + { + result = null; + } + else + { + // DEVNOTE(pqian): The next link uri should never be edited by the client, and therefore it must be absolute + result = DataServiceQueryContinuation.Create( + this.materializer.CurrentFeed.NextPageLink, + this.materializer.MaterializeEntryPlan); + } + } + else + { + if (!this.materializer.NextLinkTable.TryGetValue(key, out result)) + { + // someone has asked for a collection that's "out of scope" or doesn't exist + throw new ArgumentException(Strings.MaterializeFromAtom_CollectionKeyNotPresentInLinkTable); + } + } + + return result; + } + + /// verify the GetEnumerator can only be called once + private void CheckGetEnumerator() + { + if (this.calledGetEnumerator) + { + throw Error.NotSupported(Strings.Deserialize_GetEnumerator); + } + + this.calledGetEnumerator = true; + } + + internal static string ReadElementString(XmlReader reader, bool checkNullAttribute) + { + Debug.Assert(reader != null, "reader != null"); + Debug.Assert( + reader.NodeType == XmlNodeType.Element, + "reader.NodeType == XmlNodeType.Element -- otherwise caller is confused as to where the reader is"); + + string result = null; + bool empty = checkNullAttribute && !Util.DoesNullAttributeSayTrue(reader); + + if (reader.IsEmptyElement) + { + return (empty ? String.Empty : null); + } + + while (reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.EndElement: + return result ?? (empty ? String.Empty : null); + case XmlNodeType.CDATA: + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + if (null != result) + { + throw Error.InvalidOperation(Strings.Deserialize_MixedTextWithComment); + } + + result = reader.Value; + break; + case XmlNodeType.Comment: + case XmlNodeType.Whitespace: + break; + + #region XmlNodeType error + case XmlNodeType.Element: + goto default; + + default: + throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue); + #endregion + } + } + + // xml ended before EndElement? + throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue); + } + + /// + /// Private type to wrap partial (paged) results and make it look like standard materialized results. + /// + private class ResultsWrapper : MaterializeAtom + { + #region Private fields + + /// The results to wrap + private readonly IEnumerable results; + + /// A continuation to the next page of results. + private readonly DataServiceQueryContinuation continuation; + + /// The data service context this result belongs to + private readonly DataServiceContext context; + + #endregion Private fields + + /// + /// Creates a wrapper for raw results + /// + /// Context of expression to analyze. + /// the results to wrap + /// The continuation for this query. + internal ResultsWrapper(DataServiceContext context, IEnumerable results, DataServiceQueryContinuation continuation) + { + this.context = context; + this.results = results ?? new object[0]; + this.continuation = continuation; + } + + /// + /// The data service context this result belongs to + /// + internal override DataServiceContext Context + { + get + { + return this.context; + } + } + + /// + /// Get the next link to the result set + /// + /// When equals to null, returns the next link associated with this collection. Otherwise throws InvalidOperationException. + /// The continuation for this query. + internal override DataServiceQueryContinuation GetContinuation(IEnumerable key) + { + if (key == null) + { + return this.continuation; + } + else + { + throw new InvalidOperationException(Strings.MaterializeFromAtom_GetNestLinkForFlatCollection); + } + } + + /// + /// If the ResultWrapper is empty. When response code is No-Content, it is empty. + /// + /// Return true if it is empty result. + internal bool IsEmptyResult() + { + return this.context == null && this.continuation == null; + } + + /// + /// Gets Enumerator for wrapped results. + /// + /// IEnumerator for results + public override IEnumerator GetEnumerator() + { + return this.results.GetEnumerator(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializedEntityArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializedEntityArgs.cs new file mode 100644 index 0000000..a1e5b4e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MaterializedEntityArgs.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// Materialized Entity arguments + /// + public sealed class MaterializedEntityArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The entry. + /// The entity. + public MaterializedEntityArgs(ODataResource entry, object entity) + { + Util.CheckArgumentNull(entry, "entry"); + Util.CheckArgumentNull(entity, "entity"); + this.Entry = entry; + this.Entity = entity; + } + + /// + /// Gets the entry. + /// + /// + /// The entry. + /// + public ODataResource Entry { get; private set; } + + /// + /// Gets the entity. + /// + public object Entity { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MediaEntryAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MediaEntryAttribute.cs new file mode 100644 index 0000000..c2ab7a5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MediaEntryAttribute.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// + /// This class marks a type that represents an Astoria client entity + /// such that the Astoria client will treat it as a media entry + /// according to ATOM's "media link entry" concept. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class MediaEntryAttribute : Attribute + { + /// Name of the member that contains the data for the media entry + private readonly string mediaMemberName; + + /// Creates a new instance of . + /// A string value that identifies the property that holds media data. + /// + /// Creates a new MediaEntryAttribute attribute and sets the name + /// of the member that contains the actual data of the media entry + /// (e.g. a byte[] containing a picture, a string containing HTML, etc.) + /// + public MediaEntryAttribute(string mediaMemberName) + { + Util.CheckArgumentNull(mediaMemberName, "mediaMemberName"); + this.mediaMemberName = mediaMemberName; + } + + /// The name of the property on the class that holds the media, usually binary data. + /// A string value that identifies the property that holds media data. + public string MediaMemberName + { + get { return this.mediaMemberName; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MemberAssignmentAnalysis.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MemberAssignmentAnalysis.cs new file mode 100644 index 0000000..ebafcd7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MemberAssignmentAnalysis.cs @@ -0,0 +1,412 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + + #endregion Namespaces + + /// + /// Use this class to analyze a member assignment and figure out the + /// target path for a member-init on an entity type. + /// + /// + /// This class will also detect cases in which the assignment + /// expression refers to cases which we shouldn't handle during + /// materialization, such as references to multiple entity types + /// as sources (or refering to no source at all). + /// + internal class MemberAssignmentAnalysis : ALinqExpressionVisitor + { + #region Fields + + /// Empty expression array; immutable. + internal static readonly Expression[] EmptyExpressionArray = new Expression[0]; + + /// Entity in scope for the lambda that's providing the parameter. + private readonly Expression entity; + + /// A non-null value when incompatible paths were found for an entity initializer. + private Exception incompatibleAssignmentsException; + + /// Whether multiple paths were found for this analysis. + private bool multiplePathsFound; + + /// Path traversed from the entry field. + private List pathFromEntity; + + #endregion Fields + + #region Constructor. + + /// Initializes a new instance. + /// Entity in scope for the lambda that's providing the parameter. + private MemberAssignmentAnalysis(Expression entity) + { + Debug.Assert(entity != null, "entity != null"); + + this.entity = entity; + this.pathFromEntity = new List(); + } + + #endregion Constructor. + + #region Properties + + /// A non-null value when incompatible paths were found for an entity initializer. + internal Exception IncompatibleAssignmentsException + { + get { return this.incompatibleAssignmentsException; } + } + + /// Whether multiple paths were found during analysis. + internal bool MultiplePathsFound + { + get { return this.multiplePathsFound; } + } + + #endregion Properites. + + #region Methods + + /// Analyzes an assignment from a member-init expression. + /// Entity in scope for the lambda that's providing the parameter. + /// The expression to analyze. + /// The analysis results. + internal static MemberAssignmentAnalysis Analyze(Expression entityInScope, Expression assignmentExpression) + { + Debug.Assert(entityInScope != null, "entityInScope != null"); + Debug.Assert(assignmentExpression != null, "assignmentExpression != null"); + + MemberAssignmentAnalysis result = new MemberAssignmentAnalysis(entityInScope); + result.Visit(assignmentExpression); + return result; + } + + /// + /// Checks whether the this and a + /// paths for assignments are compatible. + /// + /// Type being initialized. + /// Previously seen member accesses (null if this is the first). + /// An exception to be thrown if assignments are not compatible; null otherwise. + /// + /// This method does not set the IncompatibleAssignmentsException property on either + /// analysis instance. + /// + internal Exception CheckCompatibleAssignments(Type targetType, ref MemberAssignmentAnalysis previous) + { + if (previous == null) + { + previous = this; + return null; + } + + Expression[] previousExpressions = previous.GetExpressionsToTargetEntity(); + Expression[] candidateExpressions = this.GetExpressionsToTargetEntity(); + return CheckCompatibleAssignments(targetType, previousExpressions, candidateExpressions); + } + + /// Visits the specified . + /// Expression to visit. + /// The visited expression. + /// This method is overriden to short-circuit analysis once an error is found. + internal override Expression Visit(Expression expression) + { + if (this.multiplePathsFound || this.incompatibleAssignmentsException != null) + { + return expression; + } + + return base.Visit(expression); + } + + /// Visits a conditional expression. + /// Expression to visit. + /// The same expression. + /// + /// There are three expressions of interest: the Test, the IfTrue + /// branch, and the IfFalse branch. If this is a NullCheck expression, + /// then we can traverse the non-null branch, which will be the + /// correct path of the resulting value. + /// + internal override Expression VisitConditional(ConditionalExpression c) + { + Expression result; + + var nullCheck = ResourceBinder.PatternRules.MatchNullCheck(this.entity, c); + if (nullCheck.Match) + { + this.Visit(nullCheck.AssignExpression); + result = c; + } + else + { + result = base.VisitConditional(c); + } + + return result; + } + + /// Parameter visit method. + /// Parameter to visit. + /// The same expression. + internal override Expression VisitParameter(ParameterExpression p) + { + if (p == this.entity) + { + if (this.pathFromEntity.Count != 0) + { + this.multiplePathsFound = true; + } + else + { + this.pathFromEntity.Add(p); + } + } + + return p; + } + + /// + /// NewExpression visit method + /// + /// The NewExpression to visit + /// The visited NewExpression + internal override NewExpression VisitNew(NewExpression nex) + { + if (nex.Members == null) + { + return base.VisitNew(nex); + } + else + { + // Member init for an anonymous type. + MemberAssignmentAnalysis previousNested = null; + foreach (var arg in nex.Arguments) + { + if (!this.CheckCompatibleAssigmentExpression(arg, nex.Type, ref previousNested)) + { + break; + } + } + + return nex; + } + } + + /// Visits a nested member init. + /// Expression to visit. + /// The same expression. + internal override Expression VisitMemberInit(MemberInitExpression init) + { + Expression result = init; + MemberAssignmentAnalysis previousNested = null; + foreach (var binding in init.Bindings) + { + MemberAssignment assignment = binding as MemberAssignment; + if (assignment == null) + { + continue; + } + + if (!this.CheckCompatibleAssigmentExpression(assignment.Expression, init.Type, ref previousNested)) + { + break; + } + } + + return result; + } + + /// Visits a member access expression. + /// Access to visit. + /// The same expression. + internal override Expression VisitMemberAccess(MemberExpression m) + { + Expression result = base.VisitMemberAccess(m); + + // Remove any unnecessary type conversions before checking to see if this expression is in the path. + // For this purpose we should consider an expression to be equal to that same expression with an unnecessary conversion. + // E.g. (p as Person) is the same as just p + Type convertedType; + Expression strippedExp = ResourceBinder.StripTo(m.Expression, out convertedType); + if (this.pathFromEntity.Contains(strippedExp)) + { + this.pathFromEntity.Add(m); + } + + return result; + } + + /// Visits a method call expression. + /// Method call to visit. + /// The same call. + internal override Expression VisitMethodCall(MethodCallExpression call) + { + // When we .Select(), the source of the enumeration is what we contribute + // eg: p => p.Cities.Select(c => c) ::= Select(p.Cities, c => c); + if (ReflectionUtil.IsSequenceMethod(call.Method, SequenceMethod.Select)) + { + this.Visit(call.Arguments[0]); + return call; + } + + return base.VisitMethodCall(call); + } + + /// Gets the expressions that go beyond the last entity. + /// An array of member expressions coming after the last entity. + /// Currently a single member access is supported. + internal Expression[] GetExpressionsBeyondTargetEntity() + { + Debug.Assert(!this.multiplePathsFound, "this.multiplePathsFound -- otherwise GetExpressionsToTargetEntity won't return reliable (consistent) results"); + + if (this.pathFromEntity.Count <= 1) + { + return EmptyExpressionArray; + } + + Expression[] result = new Expression[1]; + result[0] = this.pathFromEntity[this.pathFromEntity.Count - 1]; + return result; + } + + /// Gets the expressions that "walk down" to the last entity, ignoring the last expression. + /// An array of member expressions down to, but excluding, the last entity. + internal Expression[] GetExpressionsToTargetEntity() + { + return this.GetExpressionsToTargetEntity(true); + } + + /// Gets the expressions that "walk down" to the last entity. + /// whether to ignore the last expression or not. + /// An array of member expressions down to the last entity. + internal Expression[] GetExpressionsToTargetEntity(bool ignoreLastExpression) + { + Debug.Assert(!this.multiplePathsFound, "this.multiplePathsFound -- otherwise GetExpressionsToTargetEntity won't return reliable (consistent) results"); + + int numOfExpressionsToIgnore = ignoreLastExpression ? 1 : 0; + if (this.pathFromEntity.Count <= numOfExpressionsToIgnore) + { + return EmptyExpressionArray; + } + + Expression[] result = new Expression[this.pathFromEntity.Count - numOfExpressionsToIgnore]; + for (int i = 0; i < result.Length; i++) + { + result[i] = this.pathFromEntity[i]; + } + + return result; + } + + /// + /// Checks whether the and + /// paths for assignments are compatible. + /// + /// Type being initialized. + /// Previously seen member accesses. + /// Member assignments under evaluate. + /// An exception to be thrown if assignments are not compatible; null otherwise. + private static Exception CheckCompatibleAssignments(Type targetType, Expression[] previous, Expression[] candidate) + { + Debug.Assert(targetType != null, "targetType != null"); + Debug.Assert(previous != null, "previous != null"); + Debug.Assert(candidate != null, "candidate != null"); + + if (previous.Length != candidate.Length) + { + throw CheckCompatibleAssignmentsFail(targetType, previous, candidate); + } + + for (int i = 0; i < previous.Length; i++) + { + Expression p = previous[i]; + Expression c = candidate[i]; + if (p.NodeType != c.NodeType) + { + throw CheckCompatibleAssignmentsFail(targetType, previous, candidate); + } + + if (p == c) + { + continue; + } + + if (p.NodeType != ExpressionType.MemberAccess) + { + return CheckCompatibleAssignmentsFail(targetType, previous, candidate); + } + + if (((MemberExpression)p).Member.Name != ((MemberExpression)c).Member.Name) + { + return CheckCompatibleAssignmentsFail(targetType, previous, candidate); + } + } + + return null; + } + + /// Creates an exception to be used when CheckCompatibleAssignment fails. + /// Type being initialized. + /// Previously seen member accesses. + /// Member assignments under evaluate. + /// A new exception with diagnostic information. + private static Exception CheckCompatibleAssignmentsFail(Type targetType, Expression[] previous, Expression[] candidate) + { + Debug.Assert(targetType != null, "targetType != null"); + Debug.Assert(previous != null, "previous != null"); + Debug.Assert(candidate != null, "candidate != null"); + + string message = Strings.ALinq_ProjectionMemberAssignmentMismatch(targetType.FullName, previous.LastOrDefault(), candidate.LastOrDefault()); + return new NotSupportedException(message); + } + + /// + /// If there is a MemberInitExpression 'new Person { ID = p.ID, Friend = new Person { ID = p.Friend.ID }}' + /// or a NewExpression 'new { ID = p.ID, Friend = new { ID = p.Friend.ID }}', + /// this method validates against the RHS of the member assigment, the expression "p.ID" for example. + /// + /// The expression to validate. + /// Type of the MemberInit or the New expression. + /// The outter nested initializer of the current initializer we are checking. + /// true if the expression to assign is fine; false otherwise. + private bool CheckCompatibleAssigmentExpression(Expression expressionToAssign, Type initType, ref MemberAssignmentAnalysis previousNested) + { + MemberAssignmentAnalysis nested = MemberAssignmentAnalysis.Analyze(this.entity, expressionToAssign); + if (nested.MultiplePathsFound) + { + this.multiplePathsFound = true; + return false; + } + + // When we're visitng a nested entity initializer, we're exactly one level above that. + Exception incompatibleException = nested.CheckCompatibleAssignments(initType, ref previousNested); + if (incompatibleException != null) + { + this.incompatibleAssignmentsException = incompatibleException; + return false; + } + + if (this.pathFromEntity.Count == 0) + { + this.pathFromEntity.AddRange(nested.GetExpressionsToTargetEntity()); + } + + return true; + } + + #endregion Methods + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MergeOption.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MergeOption.cs new file mode 100644 index 0000000..c7ff415 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MergeOption.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// + /// Used to specify a value synchronization strategy. + /// + /// + /// Equivalent to System.Data.dll!System.Data.LoadOption + /// Equivalent to System.Data.Linq.dll!System.Data.Linq.RefreshMode + /// Equivalent to System.Data.Entity.dll!System.Data.Objects.MergeOption + /// + public enum MergeOption + { + /// + /// No current values are modified. + /// + /// + /// Equivalent to System.Data.Objects.MergeOption.AppendOnly + /// Equivalent to System.Data.Linq.RefreshMode.KeepCurrentValues + /// + AppendOnly = 0, + + /// + /// All current values are overwritten with current store values, + /// regardless of whether they have been changed. + /// + /// + /// Equivalent to System.Data.LoadOption.OverwriteChanges + /// Equivalent to System.Data.Objects.MergeOption.OverwriteChanges + /// Equivalent to System.Data.Linq.RefreshMode.OverwriteCurrentValues + /// + OverwriteChanges = 1, + + /// + /// Current values that have been changed are not modified, but + /// any unchanged values are updated with the current store + /// values. No changes are lost in this merge. + /// + /// + /// Equivalent to System.Data.LoadOption.PreserveChanges + /// Equivalent to System.Data.Objects.MergeOption.PreserveChanges + /// Equivalent to System.Data.Linq.RefreshMode.KeepChanges + /// + PreserveChanges = 2, + + /// + /// Equivalent to System.Data.Objects.MergeOption.NoTracking + /// + NoTracking = 3, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageReaderSettingsArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageReaderSettingsArgs.cs new file mode 100644 index 0000000..100146a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageReaderSettingsArgs.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// Arguments used to configure the odata message reader settings. + /// + public class MessageReaderSettingsArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The settings. + public MessageReaderSettingsArgs(ODataMessageReaderSettings settings) + { + WebUtil.CheckArgumentNull(settings, "settings"); + + this.Settings = settings; + } + + /// + /// Gets the settings. + /// + public ODataMessageReaderSettings Settings { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageWriterSettingsArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageWriterSettingsArgs.cs new file mode 100644 index 0000000..3c90638 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MessageWriterSettingsArgs.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// Arguments used to configure the odata message writer settings. + /// + public class MessageWriterSettingsArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The settings. + public MessageWriterSettingsArgs(ODataMessageWriterSettings settings) + { + WebUtil.CheckArgumentNull(settings, "settings"); + + this.Settings = settings; + } + + /// + /// Gets the settings. + /// + public ODataMessageWriterSettings Settings { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientPropertyAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientPropertyAnnotation.cs new file mode 100644 index 0000000..84a9d42 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientPropertyAnnotation.cs @@ -0,0 +1,481 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Metadata +{ + #region Namespaces. + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.OData.Edm; + using c = Microsoft.OData.Client; + + #endregion Namespaces. + + /// + /// Represents an EDM property for client types and caches methods for the propertyInfo. + /// + [DebuggerDisplay("{PropertyName}")] + internal sealed class ClientPropertyAnnotation + { + /// Back reference to the EdmProperty this annotation is part of. + internal readonly IEdmProperty EdmProperty; + + /// property name for debugging + internal readonly string PropertyName; + + /// PropertyInfo in CLR type. + internal readonly PropertyInfo PropertyInfo; + + /// Exact property type; possibly nullable but not necessarily so. + internal readonly Type NullablePropertyType; + + /// type of the property + internal readonly Type PropertyType; + + /// what is the dictionary value type + internal readonly Type DictionaryValueType; + + /// what type was this property declared on? + internal readonly Type DeclaringClrType; + + /// + /// Is this a known primitive/reference type or an entity/complex/collection type? + /// + internal readonly bool IsKnownType; + + /// property getter + private readonly Func propertyGetter; + + /// property setter + private readonly Action propertySetter; + + /// "set_Item" method supporting IDictionary properties + private readonly Action dictionarySetter; + + /// "Add" method supporting ICollection<> properties + private readonly Action collectionAdd; + + /// "Remove" method supporting ICollection<> properties + private readonly Func collectionRemove; + + /// "Contains" method supporting ICollection<> properties + private readonly Func collectionContains; + + /// "Clear" method supporting ICollection<> properties + private readonly Action collectionClear; + + /// ICollection<> generic type + private readonly Type collectionGenericType; + + /// cached value for IsPrimitiveOrEnumOrComplexCollection property + private bool? isPrimitiveOrEnumOrComplexCollection; + + /// cached value for IsComplex property + private bool? isComplex; + + /// cached value for IsComplex property + private bool? isComplexCollection; + + /// cached value for IsCollection property + private bool? isResourceSet; + + /// cached value of IsGeographyOrGeometry property + private bool? isSpatialType; + + /// The other property in this type that holds the MIME type for this one + private ClientPropertyAnnotation mimeTypeProperty; + + /// + /// constructor + /// + /// Back reference to the EdmProperty this annotation is part of. + /// propertyInfo instance. + /// The client model. + internal ClientPropertyAnnotation(IEdmProperty edmProperty, PropertyInfo propertyInfo, ClientEdmModel model) + { + Debug.Assert(edmProperty != null, "edmProperty != null"); + Debug.Assert(propertyInfo != null, "null propertyInfo"); + + // Property should always have DeclaringType + Debug.Assert(propertyInfo.DeclaringType != null, "Property without a declaring type"); + + this.EdmProperty = edmProperty; + this.PropertyName = ClientTypeUtil.GetServerDefinedName(propertyInfo); + this.PropertyInfo = propertyInfo; + this.NullablePropertyType = propertyInfo.PropertyType; + this.PropertyType = Nullable.GetUnderlyingType(this.NullablePropertyType) ?? this.NullablePropertyType; + this.DeclaringClrType = propertyInfo.DeclaringType; + + MethodInfo propertyGetMethod = propertyInfo.GetGetMethod(); + + // Add the parameter to make set method is returned even it is not public. Portable lib does not support this. +#if PORTABLELIB + MethodInfo propertySetMethod = propertyInfo.GetSetMethod(); +#else + MethodInfo propertySetMethod = propertyInfo.GetSetMethod(true); +#endif + + ParameterExpression instance = Expression.Parameter(typeof(Object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(Object), "value"); + + // instance => (Object)(((T)instance).get_PropertyName()); <-- we need to box the value back to object to return + this.propertyGetter = propertyGetMethod == null ? null : (Func)Expression.Lambda( + Expression.Convert( + Expression.Call( + Expression.Convert(instance, this.DeclaringClrType), + propertyGetMethod), + typeof(Object)), + instance).Compile(); + + // (instance, value) => ((T)instance).set_PropertyName((T1)value); + this.propertySetter = propertySetMethod == null ? null : (Action)Expression.Lambda( + Expression.Call( + Expression.Convert(instance, this.DeclaringClrType), + propertySetMethod, + Expression.Convert(value, this.NullablePropertyType)), + instance, + value).Compile(); + + this.Model = model; + + this.IsKnownType = PrimitiveType.IsKnownType(this.PropertyType); + + // non primitive types: dictionary/collections + if (!this.IsKnownType) + { + var setMethodInfo = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(IDictionary<,>), "set_Item", out this.DictionaryValueType); + + if (setMethodInfo != null) + { + ParameterExpression propertyNameParam = Expression.Parameter(typeof(String), "propertyName"); + + // (instance, propertyName, value) => ((IDictionary)instance)[propertyName] = (DictionaryValueType)value; + this.dictionarySetter = (Action)Expression.Lambda( + Expression.Call( + Expression.Convert(instance, typeof(IDictionary<,>).MakeGenericType(typeof(String), this.DictionaryValueType)), + setMethodInfo, + propertyNameParam, + Expression.Convert(value, this.DictionaryValueType)), + instance, + propertyNameParam, + value).Compile(); + } + else + { + var containsMethod = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(ICollection<>), "Contains", out this.collectionGenericType); + var addMethod = ClientTypeUtil.GetAddToCollectionMethod(this.PropertyType, out this.collectionGenericType); + var removeMethod = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(ICollection<>), "Remove", out this.collectionGenericType); + var clearMethod = ClientTypeUtil.GetMethodForGenericType(this.PropertyType, typeof(ICollection<>), "Clear", out this.collectionGenericType); + + // (instance, value) => ((PropertyType)instance).Contains((CollectionType)value); returns boolean + this.collectionContains = containsMethod == null ? null : (Func)Expression.Lambda( + Expression.Call( + Expression.Convert(instance, this.PropertyType), + containsMethod, + Expression.Convert(value, this.collectionGenericType)), + instance, + value).Compile(); + + // (instance, value) => ((PropertyType)instance).Add((CollectionType)value); + this.collectionAdd = addMethod == null ? null : (Action)Expression.Lambda( + Expression.Call( + Expression.Convert(instance, this.PropertyType), + addMethod, + Expression.Convert(value, this.collectionGenericType)), + instance, + value).Compile(); + + // (instance, value) => ((PropertyType)instance).Remove((CollectionType)value); returns boolean + this.collectionRemove = removeMethod == null ? null : (Func)Expression.Lambda( + Expression.Call( + Expression.Convert(instance, this.PropertyType), + removeMethod, + Expression.Convert(value, this.collectionGenericType)), + instance, + value).Compile(); + + // (instance) => ((PropertyType)instance).Clear(); + this.collectionClear = clearMethod == null ? null : (Action)Expression.Lambda( + Expression.Call( + Expression.Convert(instance, this.PropertyType), + clearMethod), + instance).Compile(); + } + } + + Debug.Assert(this.collectionGenericType == null || this.DictionaryValueType == null, "collectionGenericType and DictionaryItemType mutually exclusive. (They both can be null though)."); + } + + /// + /// Gets the client model. + /// + internal ClientEdmModel Model { get; private set; } + + /// The other property in this type that holds the MIME type for this one + internal ClientPropertyAnnotation MimeTypeProperty + { + get { return this.mimeTypeProperty; } + set { this.mimeTypeProperty = value; } + } + + /// what is the nested collection element + internal Type EntityCollectionItemType + { + get { return this.IsEntityCollection ? this.collectionGenericType : null; } + } + + /// Is this property a collection of entities? + internal bool IsEntityCollection + { + get { return this.collectionGenericType != null && !this.IsPrimitiveOrEnumOrComplexCollection; } + } + + /// The item type of the nested resource set + internal Type ResourceSetItemType + { + get { return this.IsResourceSet ? this.collectionGenericType : null; } + } + + /// Is this property a resource set? + internal bool IsResourceSet + { + get + { + if (!this.isResourceSet.HasValue) + { + if (this.collectionGenericType == null) + { + this.isResourceSet = false; + } + else + { + this.isResourceSet = + (this.EdmProperty.PropertyKind == EdmPropertyKind.Structural + || this.EdmProperty.PropertyKind == EdmPropertyKind.Navigation) + && this.EdmProperty.Type.AsCollection().ElementType().IsStructured(); + } + } + + return this.isResourceSet.Value; + } + } + + /// Type of items in the primitive or complex collection. + internal Type PrimitiveOrComplexCollectionItemType + { + get + { + if (this.IsPrimitiveOrEnumOrComplexCollection) + { + return this.collectionGenericType; + } + + return null; + } + } + + /// Returns if this property is enum type. + internal bool IsEnumType + { + get { return this.PropertyType.IsEnum(); } + } + + /// Returns if this property is complex type. + internal bool IsComplex + { + get + { + if (!this.isComplex.HasValue) + { + this.isComplex = this.EdmProperty.Type.TypeKind() == EdmTypeKind.Complex; + } + + return this.isComplex.Value; + } + } + + /// Returns if this property is complex type. + internal bool IsComplexCollection + { + get + { + if (!this.isComplexCollection.HasValue) + { + if (this.collectionGenericType == null) + { + this.isComplexCollection = false; + } + else + { + var type = this.EdmProperty.Type; + this.isComplexCollection = this.EdmProperty.PropertyKind == EdmPropertyKind.Structural + && type.IsCollection() + && (type as IEdmCollectionTypeReference).ElementType().IsComplex(); + } + } + + return this.isComplexCollection.Value; + } + } + + /// Is this property a collection of primitive or enum or complex types? + internal bool IsPrimitiveOrEnumOrComplexCollection + { + get + { + if (!this.isPrimitiveOrEnumOrComplexCollection.HasValue) + { + if (this.collectionGenericType == null) + { + this.isPrimitiveOrEnumOrComplexCollection = false; + } + else + { + bool collection = this.EdmProperty.PropertyKind == EdmPropertyKind.Structural && this.EdmProperty.Type.TypeKind() == EdmTypeKind.Collection; + + this.isPrimitiveOrEnumOrComplexCollection = collection; + } + } + + return this.isPrimitiveOrEnumOrComplexCollection.Value; + } + } + + /// Returns true if the type of property is a Geography or Geometry type, otherwise returns false. + internal bool IsSpatialType + { + get + { + if (!this.isSpatialType.HasValue) + { + if (typeof(Microsoft.Spatial.ISpatial).IsAssignableFrom(this.PropertyType)) + { + this.isSpatialType = true; + } + else + { + this.isSpatialType = false; + } + } + + return this.isSpatialType.Value; + } + } + + /// Is this property a dictionary? + internal bool IsDictionary + { + get { return this.DictionaryValueType != null; } + } + + /// Returns true if this property is a stream link property, otherwise false. + internal bool IsStreamLinkProperty + { + get { return this.PropertyType == typeof(DataServiceStreamLink); } + } + + /// + /// get property value from an object + /// + /// object to get the property value from + /// property value + internal object GetValue(object instance) + { + Debug.Assert(null != instance, "null instance"); + Debug.Assert(null != this.propertyGetter, "null propertyGetter"); + return this.propertyGetter.Invoke(instance); + } + + /// + /// remove a item from the collection instance + /// + /// collection + /// item to remove + internal void RemoveValue(object instance, object value) + { + Debug.Assert(null != instance, "null instance"); + Debug.Assert(null != this.collectionRemove, "missing removeMethod"); + + Debug.Assert(this.PropertyType.IsAssignableFrom(instance.GetType()), "unexpected collection instance"); + Debug.Assert((null == value) || this.ResourceSetItemType.IsAssignableFrom(value.GetType()) || this.PrimitiveOrComplexCollectionItemType.IsAssignableFrom(value.GetType()), "unexpected collection value to remove"); + this.collectionRemove.Invoke(instance, value); + } + + /// + /// set property value on an object + /// + /// object to set the property value on + /// property value + /// used for open type + /// allow add to a collection if available, else allow setting collection property + internal void SetValue(object instance, object value, string propertyName, bool allowAdd) + { + Debug.Assert(null != instance, "null instance"); + if (null != this.dictionarySetter) + { + { + Debug.Assert(this.PropertyType.IsAssignableFrom(instance.GetType()), "unexpected dictionary instance"); + Debug.Assert((null == value) || this.DictionaryValueType.IsAssignableFrom(value.GetType()), "unexpected dictionary value to set"); + + // ((IDictionary)instance)[propertyName] = (DictionaryValueType)value; + this.dictionarySetter.Invoke(instance, propertyName, value); + } + } + else if (allowAdd && (null != this.collectionAdd)) + { + Debug.Assert(this.PropertyType.IsAssignableFrom(instance.GetType()), "unexpected collection instance"); + Debug.Assert( + (null == value) || + (this.EntityCollectionItemType != null && this.EntityCollectionItemType.IsAssignableFrom(value.GetType())) || + (this.PrimitiveOrComplexCollectionItemType != null && this.PrimitiveOrComplexCollectionItemType.IsAssignableFrom(value.GetType())), + "unexpected collection value to add"); + + if (!this.collectionContains.Invoke(instance, value)) + { + this.AddValueToBackingICollectionInstance(instance, value); + } + } + else if (null != this.propertySetter) + { + // ((ElementType)instance).PropertyName = (PropertyType)value; + this.propertySetter.Invoke(instance, value); + } + else + { + throw c.Error.InvalidOperation(c.Strings.ClientType_MissingProperty(value.GetType().ToString(), propertyName)); + } + } + + /// + /// Clears . + /// + /// ICollection instance that needs to be cleared. + internal void ClearBackingICollectionInstance(object collectionInstance) + { + Debug.Assert(this.IsEntityCollection || this.IsPrimitiveOrEnumOrComplexCollection, "this.IsEntityCollection or this.IsPrimitiveOrComplexCollection has to be true otherwise it is not a collection"); + Debug.Assert(this.collectionClear != null, "For collections the clearMethod must not be null"); + + this.collectionClear.Invoke(collectionInstance); + } + + /// + /// Adds value to a collection. + /// + /// ICollection Instance to add to. + /// Value to be added to . + internal void AddValueToBackingICollectionInstance(object collectionInstance, object value) + { + Debug.Assert(this.IsEntityCollection || this.IsPrimitiveOrEnumOrComplexCollection, "this.IsEntityCollection or this.IsPrimitiveOrComplexCollection has to be true otherwise it is not a collection"); + Debug.Assert(this.collectionAdd != null, "For collections the addMethod must not be null"); + + this.collectionAdd.Invoke(collectionInstance, value); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeAnnotation.cs new file mode 100644 index 0000000..11a86d2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeAnnotation.cs @@ -0,0 +1,347 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Metadata +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Annotates a type on the client. + /// + [DebuggerDisplay("{ElementTypeName}")] + internal sealed class ClientTypeAnnotation + { + /// Back reference to the EdmType this annotation is part of. + internal readonly IEdmType EdmType; + + /// what is the clr full name using ToString for generic name expansion + internal readonly string ElementTypeName; + + /// what clr type does this represent + internal readonly Type ElementType; + + /// Storage for the client model. + private readonly ClientEdmModel model; + + /// Set to true if the type is marked as ATOM-style media link entry + private bool? isMediaLinkEntry; + + /// Property that holds data for ATOM-style media link entries + private ClientPropertyAnnotation mediaDataMember; + + /// Whether any property (including properties on descendant types) of this type is a collection of primitive or complex types. + private Version metadataVersion; + + /// Cached client properties. + private Dictionary clientPropertyCache; + + /// Cached Edm properties + private IEdmProperty[] edmPropertyCache; + + /// + /// discover and prepare properties for usage + /// + /// Back reference to the EdmType this annotation is part of. + /// type being processed + /// the qualified name of the type being processed + /// The client model. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Pending")] + internal ClientTypeAnnotation(IEdmType edmType, Type type, string qualifiedName, ClientEdmModel model) + { + Debug.Assert(edmType != null, "edmType != null"); + Debug.Assert(null != type, "null type"); + Debug.Assert(!string.IsNullOrEmpty(qualifiedName), "!string.IsNullOrEmpty(qualifiedName)"); + + this.EdmType = edmType; + this.EdmTypeReference = this.EdmType.ToEdmTypeReference(Util.IsNullableType(type)); + this.ElementTypeName = qualifiedName; + this.ElementType = Nullable.GetUnderlyingType(type) ?? type; + this.model = model; + } + + /// if it is an IEdmEntityType + internal bool IsEntityType + { + get { return this.EdmType.TypeKind == EdmTypeKind.Entity; } + } + + /// if it is an IEdmStructuredType + internal bool IsStructuredType + { + get + { + return this.EdmType.TypeKind == EdmTypeKind.Entity || this.EdmType.TypeKind == EdmTypeKind.Complex; + } + } + + /// Property that holds data for ATOM-style media link entries + internal ClientPropertyAnnotation MediaDataMember + { + get + { + if (!this.isMediaLinkEntry.HasValue) + { + this.CheckMediaLinkEntry(); + Debug.Assert(this.isMediaLinkEntry.HasValue, "this.isMediaLinkEntry.HasValue"); + } + + return this.mediaDataMember; + } + } + + /// Returns true if the type is marked as ATOM-style media link entry + internal bool IsMediaLinkEntry + { + get + { + if (!this.isMediaLinkEntry.HasValue) + { + this.CheckMediaLinkEntry(); + Debug.Assert(this.isMediaLinkEntry.HasValue, "this.isMediaLinkEntry.HasValue"); + } + + return this.isMediaLinkEntry.Value; + } + } + + /// Gets the EdmTypeReference for the client Type annotation. + internal IEdmTypeReference EdmTypeReference { get; private set; } + + /// + /// Returns the list of EdmProperties for this type. + /// + /// Returns the list of EdmProperties for this type. + internal IEnumerable EdmProperties() + { + if (this.edmPropertyCache == null) + { + this.edmPropertyCache = this.DiscoverEdmProperties().ToArray(); + } + + return this.edmPropertyCache; + } + + /// Returns the list of properties from this type. + /// Returns the list of properties from this type. + internal IEnumerable Properties() + { + if (this.clientPropertyCache == null) + { + this.BuildPropertyCache(); + } + + return this.clientPropertyCache.Values; + } + + /// + /// Gets the set of properties on this type that should be serialized into insert/update payloads. + /// + /// The properties to serialize. + internal IEnumerable PropertiesToSerialize() + { + return this.Properties().Where(p => ShouldSerializeProperty(this, p)).OrderBy(p => p.PropertyName); + } + + /// + /// get property wrapper for a property name, might be method around open types for otherwise unknown properties + /// + /// property name + /// UndeclaredPropertyBehavior + /// property wrapper + /// for unknown properties on closed types + internal ClientPropertyAnnotation GetProperty(string propertyName, UndeclaredPropertyBehavior undeclaredPropertyBehavior) + { + Debug.Assert(propertyName != null, "property name"); + if (this.clientPropertyCache == null) + { + this.BuildPropertyCache(); + } + + ClientPropertyAnnotation property; + + if (!this.clientPropertyCache.TryGetValue(propertyName, out property)) + { + string propertyClientName = ClientTypeUtil.GetClientPropertyName(this.ElementType, propertyName, undeclaredPropertyBehavior); + if ((string.IsNullOrEmpty(propertyClientName) || !this.clientPropertyCache.TryGetValue(propertyClientName, out property)) && (undeclaredPropertyBehavior == UndeclaredPropertyBehavior.ThrowException)) + { + throw Microsoft.OData.Client.Error.InvalidOperation(Microsoft.OData.Client.Strings.ClientType_MissingProperty(this.ElementTypeName, propertyName)); + } + } + + return property; + } + + /// + /// Checks if any of the properties (including properties of descendant types) is a collection of primitive or complex types. + /// + /// true if any if any of the properties (including properties of descendant types) is a collection of primitive or complex types. Otherwise false. + internal Version GetMetadataVersion() + { + if (this.metadataVersion == null) + { + Version clientTypeMetadataVersion = Util.ODataVersion4; + WebUtil.RaiseVersion(ref clientTypeMetadataVersion, this.ComputeVersionForPropertyCollection(this.EdmProperties(), null /*visitedComplexTypes*/)); + this.metadataVersion = clientTypeMetadataVersion; + } + + return this.metadataVersion; + } + + /// + /// Determines whether a given property should be serialized into an insert or update payload. + /// + /// The declaring type of the property. + /// The property. + /// Whether or not the property should be serialized. + private static bool ShouldSerializeProperty(ClientTypeAnnotation type, ClientPropertyAnnotation property) + { + // don't write property if it is a dictionary + // don't write mime data member or the mime type member for it + // link properties need to be ignored + // don't write property if it is tagged with IgnoreClientProperty attribute + return !property.IsDictionary + && property != type.MediaDataMember + && !property.IsStreamLinkProperty + && (type.MediaDataMember == null || type.MediaDataMember.MimeTypeProperty != property) + && property.PropertyInfo.GetCustomAttributes(typeof(IgnoreClientPropertyAttribute)).Count() == 0; + } + + /// + /// build the clientPropertyCache from EdmProperties + /// + private void BuildPropertyCache() + { + // this is function is lazy loading the property cache, should only be called once per type annotation + lock (this) + { + if (this.clientPropertyCache == null) + { + var propertyCache = new Dictionary(StringComparer.Ordinal); + foreach (var p in this.EdmProperties()) + { + propertyCache.Add(p.Name, this.model.GetClientPropertyAnnotation(p)); + } + + this.clientPropertyCache = propertyCache; + } + } + } + + /// + /// Check if this type represents an ATOM-style media link entry and + /// if so mark the ClientType as such + /// + private void CheckMediaLinkEntry() + { + this.isMediaLinkEntry = false; + + // MediaEntryAttribute does not allow multiples, so there can be at most 1 instance on the type. + MediaEntryAttribute mediaEntryAttribute = (MediaEntryAttribute)this.ElementType.GetCustomAttributes(typeof(MediaEntryAttribute), true).SingleOrDefault(); + if (mediaEntryAttribute != null) + { + this.isMediaLinkEntry = true; + + ClientPropertyAnnotation mediaProperty = this.Properties().SingleOrDefault(p => p.PropertyName == mediaEntryAttribute.MediaMemberName); + if (mediaProperty == null) + { + throw Microsoft.OData.Client.Error.InvalidOperation(Microsoft.OData.Client.Strings.ClientType_MissingMediaEntryProperty( + this.ElementTypeName, mediaEntryAttribute.MediaMemberName)); + } + + this.mediaDataMember = mediaProperty; + } + + // HasStreamAttribute does not allow multiples, so there can be at most 1 instance on the type. + bool hasStreamAttribute = this.ElementType.GetCustomAttributes(typeof(HasStreamAttribute), true).Any(); + if (hasStreamAttribute) + { + this.isMediaLinkEntry = true; + } + } + + /// + /// Computes the metadata version of the property. + /// + /// List of properties for which metadata version needs to be computed. + /// List of complex type already visited. + /// the metadata version of the property collection. + private Version ComputeVersionForPropertyCollection(IEnumerable propertyCollection, HashSet visitedComplexTypes) + { + Version propertyMetadataVersion = Util.ODataVersion4; + + foreach (IEdmProperty property in propertyCollection) + { + ClientPropertyAnnotation propertyAnnotation = this.model.GetClientPropertyAnnotation(property); + + if (property.Type.TypeKind() == EdmTypeKind.Complex && !propertyAnnotation.IsDictionary) + { + if (visitedComplexTypes == null) + { + visitedComplexTypes = new HashSet(EqualityComparer.Default); + } + else if (visitedComplexTypes.Contains(property.Type.Definition)) + { + continue; + } + + visitedComplexTypes.Add(property.Type.Definition); + + WebUtil.RaiseVersion(ref propertyMetadataVersion, this.ComputeVersionForPropertyCollection(this.model.GetClientTypeAnnotation(property).EdmProperties(), visitedComplexTypes)); + } + } + + return propertyMetadataVersion; + } + + /// + /// Discovers and returns edm properties for this type. + /// + /// Edm properties on this type. + private IEnumerable DiscoverEdmProperties() + { + IEdmStructuredType edmStructuredType = this.EdmType as IEdmStructuredType; + if (edmStructuredType != null) + { + //// NOTE: We need to deal with property hiding here + //// If a property in a derived type is hiding a property in a base type + //// by using the 'new' keyword we treat this as a new property and + //// keep the property in the base type (where it has to be) as well as in + //// derived type (where it also has to be since it can even have a different property type). + //// When returning the Edm properties we thus have to start at the most derived type + //// and not report hidden properties from the base types. + //// NOTE: EDM does not have a concept of property hiding so this an artifact of our mapping + //// from CLR types to EDM types. + + HashSet propertyNames = new HashSet(StringComparer.Ordinal); + + do + { + foreach (var property in edmStructuredType.DeclaredProperties) + { + string propertyName = property.Name; + if (!propertyNames.Contains(propertyName)) + { + propertyNames.Add(propertyName); + yield return property; + } + } + + edmStructuredType = edmStructuredType.BaseType; + } + while (edmStructuredType != null); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeCache.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeCache.cs new file mode 100644 index 0000000..d9f017a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeCache.cs @@ -0,0 +1,175 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Metadata +{ + #region Namespaces. + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using c = Microsoft.OData.Client; + + #endregion Namespaces. + + /// + /// Caches wire type names and their mapped client CLR types. + /// + [DebuggerDisplay("{PropertyName}")] + internal static class ClientTypeCache + { + /// cache <T> and wireName to mapped type + private static readonly Dictionary namedTypes = new Dictionary(new TypeNameEqualityComparer()); + +#if !PORTABLELIB + /// + /// resolve the wireName/userType pair to a CLR type + /// + /// type name sent by server + /// type passed by user or on propertyType from a class + /// mapped clr type + internal static Type ResolveFromName(string wireName, Type userType) +#else + /// + /// resolve the wireName/userType pair to a CLR type + /// + /// type name sent by server + /// type passed by user or on propertyType from a class + /// typeof context for strongly typed assembly + /// mapped clr type + internal static Type ResolveFromName(string wireName, Type userType, Type contextType) +#endif + { + Type foundType; + + TypeName typename; + typename.Type = userType; + typename.Name = wireName; + + // search the "wirename"-userType key in type cache + bool foundInCache; + lock (ClientTypeCache.namedTypes) + { + foundInCache = ClientTypeCache.namedTypes.TryGetValue(typename, out foundType); + } + + // at this point, if we have seen this type before, we either have the resolved type "foundType", + // or we have tried to resolve it before but did not success, in which case foundType will be null. + // Either way we should return what's in the cache since the result is unlikely to change. + // We only need to keep on searching if there isn't an entry in the cache. + if (!foundInCache) + { + string name = wireName; + int index = wireName.LastIndexOf('.'); + if ((0 <= index) && (index < wireName.Length - 1)) + { + name = wireName.Substring(index + 1); + } + + if (userType.Name == name) + { + foundType = userType; + } + else + { +#if !PORTABLELIB + // searching only loaded assemblies, not referenced assemblies + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) +#else + foreach (Assembly assembly in new Assembly[] { userType.GetAssembly(), contextType.GetAssembly() }.Distinct()) +#endif + { + Type found = assembly.GetType(wireName, false); + ResolveSubclass(name, userType, found, ref foundType); + + if (null == found) + { + IEnumerable types = null; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException) + { + } + + if (null != types) + { + foreach (Type t in types) + { + ResolveSubclass(name, userType, t, ref foundType); + } + } + } + } + } + + // The above search can all fail and leave "foundType" to be null + // we should cache this result too so we won't waste time searching again. + lock (ClientTypeCache.namedTypes) + { + ClientTypeCache.namedTypes[typename] = foundType; + } + } + + return foundType; + } + + /// + /// is the type a visible subclass with correct name + /// + /// type name from server + /// the type from user for materialization or property type + /// type being tested + /// the previously discovered matching type + /// if the mapping is ambiguous + private static void ResolveSubclass(string wireClassName, Type userType, Type type, ref Type existing) + { + if ((null != type) && c.PlatformHelper.IsVisible(type) && (wireClassName == type.Name) && userType.IsAssignableFrom(type)) + { + if (null != existing) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_Ambiguous(wireClassName, userType)); + } + + existing = type; + } + } + + /// type + wireName combination + private struct TypeName + { + /// type + internal Type Type; + + /// type name from server + internal string Name; + } + + /// equality comparer for TypeName + private sealed class TypeNameEqualityComparer : IEqualityComparer + { + /// equality comparer for TypeName + /// left type + /// right type + /// true if x and y are equal + public bool Equals(TypeName x, TypeName y) + { + return (x.Type == y.Type && x.Name == y.Name); + } + + /// compute hashcode for TypeName + /// object to compute hashcode for + /// computed hashcode + public int GetHashCode(TypeName obj) + { + return obj.Type.GetHashCode() ^ obj.Name.GetHashCode(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs new file mode 100644 index 0000000..8beaf0f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs @@ -0,0 +1,763 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client.Metadata +{ + #region Namespaces. + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using c = Microsoft.OData.Client; + + #endregion Namespaces. + + /// + /// Utility methods for client types. + /// + internal static class ClientTypeUtil + { + /// A static empty PropertyInfo array. + internal static readonly PropertyInfo[] EmptyPropertyInfoArray = new PropertyInfo[0]; + + /// + /// Enumeration for the kind of key + /// + private enum KeyKind + { + /// If this is not a key + NotKey = 0, + + /// If the key property name was equal to ID + Id = 1, + + /// If the key property name was equal to TypeName+ID + TypeNameId = 2, + + /// if the key property was attributed + AttributedKey = 3, + } + + /// + /// Sets the single instance of on the given instance of . + /// + /// The model the belongs to. + /// IEdmType instance to set the annotation on. + /// The annotation to set + internal static void SetClientTypeAnnotation(this IEdmModel model, IEdmType edmType, ClientTypeAnnotation annotation) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(edmType != null, "edmType != null"); + Debug.Assert(annotation != null, "annotation != null"); + model.SetAnnotationValue(edmType, annotation); + } + + /// + /// Gets the ClientTypeAnnotation for the given type. + /// + /// The model. + /// Type for which the annotation needs to be returned. + /// An instance of ClientTypeAnnotation containing metadata about the given type. + internal static ClientTypeAnnotation GetClientTypeAnnotation(this ClientEdmModel model, Type type) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(type != null, "type != null"); + + IEdmType edmType = model.GetOrCreateEdmType(type); + return model.GetClientTypeAnnotation(edmType); + } + + /// + /// Gets the single instance of from the given instance of . + /// + /// The model the belongs to. + /// IEdmType instance to get the annotation. + /// Returns the single instance of from the given instance of . + internal static ClientTypeAnnotation GetClientTypeAnnotation(this IEdmModel model, IEdmType edmType) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(edmType != null, "edmType != null"); + + return model.GetAnnotationValue(edmType); + } + + /// + /// Sets the given instance of to the given instance of . + /// + /// IEdmProperty instance to set the annotation. + /// Annotation instance to set. + internal static void SetClientPropertyAnnotation(this IEdmProperty edmProperty, ClientPropertyAnnotation annotation) + { + Debug.Assert(edmProperty != null, "edmProperty != null"); + Debug.Assert(annotation != null, "annotation != null"); + annotation.Model.SetAnnotationValue(edmProperty, annotation); + } + + /// + /// Gets the single instance of ClientPropertyAnnotation from the given instance of . + /// + /// The model. + /// IEdmProperty instance to get the annotation. + /// Returns the single instance of ClientPropertyAnnotation from the given instance of . + internal static ClientPropertyAnnotation GetClientPropertyAnnotation(this IEdmModel model, IEdmProperty edmProperty) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(edmProperty != null, "edmProperty != null"); + + return model.GetAnnotationValue(edmProperty); + } + + /// + /// Gets the instance of ClientTypeAnnotation from the given instance of . + /// + /// The model. + /// IEdmProperty instance to get the annotation. + /// Returns the instance of ClientTypeAnnotation from the given instance of . + internal static ClientTypeAnnotation GetClientTypeAnnotation(this IEdmModel model, IEdmProperty edmProperty) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(edmProperty != null, "edmProperty != null"); + + IEdmType edmType = edmProperty.Type.Definition; + Debug.Assert(edmType != null, "edmType != null"); + + return model.GetAnnotationValue(edmType); + } + + /// + /// Returns the corresponding edm type reference for the given edm type. + /// + /// EdmType instance. + /// A boolean value indicating whether the clr type of this edm type is nullable + /// Returns the corresponding edm type reference for the given edm type. + internal static IEdmTypeReference ToEdmTypeReference(this IEdmType edmType, bool isNullable) + { + return EdmLibraryExtensions.ToTypeReference(edmType, isNullable); + } + + /// + /// Returns the full name for the given edm type + /// + /// EdmType instance. + /// the full name of the edmType. + internal static string FullName(this IEdmType edmType) + { + IEdmSchemaElement schemaElement = edmType as IEdmSchemaElement; + if (schemaElement != null) + { + return schemaElement.FullName(); + } + + return null; + } + + /// + /// Returns MethodInfo instance for a generic type retrieved by using and gets + /// element type for the provided . + /// + /// starting type + /// the generic type definition to find + /// the method to search for + /// the element type for if found + /// element types + internal static MethodInfo GetMethodForGenericType(Type propertyType, Type genericTypeDefinition, string methodName, out Type type) + { + Debug.Assert(null != propertyType, "null propertyType"); + Debug.Assert(null != genericTypeDefinition, "null genericTypeDefinition"); + Debug.Assert(genericTypeDefinition.IsGenericTypeDefinition(), "!IsGenericTypeDefinition"); + + type = null; + + Type implementationType = ClientTypeUtil.GetImplementationType(propertyType, genericTypeDefinition); + if (null != implementationType) + { + Type[] genericArguments = implementationType.GetGenericArguments(); + MethodInfo methodInfo = implementationType.GetMethod(methodName); + Debug.Assert(null != methodInfo, "should have found the method"); + +#if DEBUG + Debug.Assert(null != genericArguments, "null genericArguments"); + ParameterInfo[] parameters = methodInfo.GetParameters(); + if (0 < parameters.Length) + { + // following assert was disabled for Contains which returns bool + //// Debug.Assert(typeof(void) == methodInfo.ReturnParameter.ParameterType, "method doesn't return void"); + + Debug.Assert(genericArguments.Length == parameters.Length, "genericArguments don't match parameters"); + for (int i = 0; i < genericArguments.Length; ++i) + { + Debug.Assert(genericArguments[i] == parameters[i].ParameterType, "parameter doesn't match generic argument"); + } + } +#endif + type = genericArguments[genericArguments.Length - 1]; + return methodInfo; + } + + return null; + } + + /// Gets a delegate that can be invoked to add an item to a collection of the specified type. + /// Type of list to use. + /// The delegate to invoke. + internal static Action GetAddToCollectionDelegate(Type listType) + { + Debug.Assert(listType != null, "listType != null"); + + Type listElementType; + MethodInfo addMethod = ClientTypeUtil.GetAddToCollectionMethod(listType, out listElementType); + ParameterExpression list = Expression.Parameter(typeof(object), "list"); + ParameterExpression item = Expression.Parameter(typeof(object), "element"); + Expression body = Expression.Call(Expression.Convert(list, listType), addMethod, Expression.Convert(item, listElementType)); + LambdaExpression lambda = Expression.Lambda(body, list, item); + + return (Action)lambda.Compile(); + } + + /// + /// Gets the Add method to add items to a collection of the specified type. + /// + /// Type for the collection. + /// The element type in the collection if found; null otherwise. + /// The method to invoke to add to a collection of the specified type. + internal static MethodInfo GetAddToCollectionMethod(Type collectionType, out Type type) + { + return ClientTypeUtil.GetMethodForGenericType(collectionType, typeof(ICollection<>), "Add", out type); + } + + /// + /// get concrete type that implements the genericTypeDefinition + /// + /// starting type + /// the generic type definition to find + /// concrete type that implementats the generic type + internal static Type GetImplementationType(Type type, Type genericTypeDefinition) + { + if (IsConstructedGeneric(type, genericTypeDefinition)) + { // propertyType is genericTypeDefinition (e.g. ICollection) + return type; + } + else + { + Type implementationType = null; + foreach (Type interfaceType in type.GetInterfaces()) + { + if (IsConstructedGeneric(interfaceType, genericTypeDefinition)) + { + if (null == implementationType) + { // found implmentation of genericTypeDefinition (e.g. ICollection) + implementationType = interfaceType; + } + else + { // Multiple implementations (e.g. ICollection and ICollection) + throw c.Error.NotSupported(c.Strings.ClientType_MultipleImplementationNotSupported); + } + } + } + + return implementationType; + } + } + + /// + /// Is the type an Entity Type? + /// + /// Type to examine + /// The client model. + /// bool indicating whether or not entity type + internal static bool TypeIsEntity(Type t, ClientEdmModel model) + { + return model.GetOrCreateEdmType(t).TypeKind == EdmTypeKind.Entity; + } + + /// + /// Is the type an structured type? + /// + /// Type to examine + /// The client model. + /// bool indicating whether or not structured type + internal static bool TypeIsStructured(Type t, ClientEdmModel model) + { + var typeKind = model.GetOrCreateEdmType(t).TypeKind; + return typeKind == EdmTypeKind.Entity || typeKind == EdmTypeKind.Complex; + } + + /// + /// Is the type or element type (in the case of nullableOfT or IEnumOfT) a Entity Type? + /// + /// Type to examine + /// bool indicating whether or not entity type + internal static bool TypeOrElementTypeIsEntity(Type type) + { + type = TypeSystem.GetElementType(type); + type = Nullable.GetUnderlyingType(type) ?? type; + return !PrimitiveType.IsKnownType(type) && ClientTypeUtil.GetKeyPropertiesOnType(type) != null; + } + + /// + /// Is the type or element type (in the case of nullableOfT or IEnumOfT) a structured type? + /// + /// Type to examine + /// bool indicating whether or not structured type + internal static bool TypeOrElementTypeIsStructured(Type type) + { + type = TypeSystem.GetElementType(type); + type = Nullable.GetUnderlyingType(type) ?? type; + return !PrimitiveType.IsKnownType(type) && !type.IsEnum(); + } + + /// Checks whether the specified type is a DataServiceCollection type (or inherits from one). + /// Type to check. + /// true if the type inherits from DataServiceCollection; false otherwise. + internal static bool IsDataServiceCollection(Type type) + { + while (type != null) + { + if (c.PlatformHelper.IsGenericType(type) && WebUtil.IsDataServiceCollectionType(type.GetGenericTypeDefinition())) + { + return true; + } + + type = c.PlatformHelper.GetBaseType(type); + } + + return false; + } + + /// Whether a variable of can be assigned null. + /// Type to check. + /// true if a variable of type can be assigned null; false otherwise. + internal static bool CanAssignNull(Type type) + { + Debug.Assert(type != null, "type != null"); + return !type.IsValueType() || (type.IsGenericType() && (type.GetGenericTypeDefinition() == typeof(Nullable<>))); + } + + /// Returns the list of properties defined on . + /// Type instance in question. + /// True to to get the properties declared on ; false to get all properties defined on . + /// Returns the list of properties defined on . + internal static IEnumerable GetPropertiesOnType(Type type, bool declaredOnly) + { + if (!PrimitiveType.IsKnownType(type)) + { + foreach (PropertyInfo propertyInfo in type.GetPublicProperties(true /*instanceOnly*/, declaredOnly)) + { + //// examples where class + + //// the normal examples + //// PropertyType Property { get; set } + //// Nullable Property { get; set; } + + //// if 'Property: struct' then we would be unable set the property during construction (and have them stick) + //// but when its a class, we can navigate if non-null and set the nested properties + //// PropertyType Property { get; } where PropertyType: class + + //// we do support adding elements to collections + //// ICollection { get; /*ignored set;*/ } + + //// indexed properties are not suported because + //// we don't have anything to use as the index + //// PropertyType Property[object x] { /*ignored get;*/ /*ignored set;*/ } + + //// also ignored + //// if PropertyType.IsPointer (like byte*) + //// if PropertyType.IsArray except for byte[] and char[] + //// if PropertyType == IntPtr or UIntPtr + + //// Properties overriding abstract or virtual properties on a base type + //// are also ignored (because they are part of the base type declaration + //// and not of the derived type). + + Type propertyType = propertyInfo.PropertyType; // class / interface / value + propertyType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + + if (propertyType.IsPointer || + (propertyType.IsArray && (typeof(byte[]) != propertyType) && typeof(char[]) != propertyType) || + (typeof(IntPtr) == propertyType) || + (typeof(UIntPtr) == propertyType)) + { + continue; + } + + // Ignore properties overriding abstract/virtual properties of a base type + // when only getting the declared properties (otherwise the property will + // only be included once in the property list anyways). + if (declaredOnly && IsOverride(type, propertyInfo)) + { + continue; + } + + Debug.Assert(!propertyType.ContainsGenericParameters(), "remove when test case is found that encounters this"); + + if (propertyInfo.CanRead && + (!propertyType.IsValueType() || propertyInfo.CanWrite) && + !propertyType.ContainsGenericParameters() && + (0 == propertyInfo.GetIndexParameters().Length)) + { + yield return propertyInfo; + } + } + } + } + + /// + /// Returns the list of key properties defined on ; null if is complex. + /// + /// Type in question. + /// Returns the list of key properties defined on ; null if is complex. + internal static PropertyInfo[] GetKeyPropertiesOnType(Type type) + { + bool hasProperties; + return GetKeyPropertiesOnType(type, out hasProperties); + } + + /// + /// Returns the list of key properties defined on ; null if is complex. + /// + /// Type in question. + /// true if has any (declared or inherited) properties; otherwise false. + /// Returns the list of key properties defined on ; null if is complex. + internal static PropertyInfo[] GetKeyPropertiesOnType(Type type, out bool hasProperties) + { + if (CommonUtil.IsUnsupportedType(type)) + { + throw new InvalidOperationException(c.Strings.ClientType_UnsupportedType(type)); + } + + string typeName = type.ToString(); + IEnumerable customAttributes = type.GetCustomAttributes(true); + bool isEntity = customAttributes.OfType().Any(); + KeyAttribute dataServiceKeyAttribute = customAttributes.OfType().FirstOrDefault(); + List keyProperties = new List(); + PropertyInfo[] properties = ClientTypeUtil.GetPropertiesOnType(type, false /*declaredOnly*/).ToArray(); + hasProperties = properties.Length > 0; + + KeyKind currentKeyKind = KeyKind.NotKey; + KeyKind newKeyKind = KeyKind.NotKey; + foreach (PropertyInfo propertyInfo in properties) + { + if ((newKeyKind = ClientTypeUtil.IsKeyProperty(propertyInfo, dataServiceKeyAttribute)) != KeyKind.NotKey) + { + if (newKeyKind > currentKeyKind) + { + keyProperties.Clear(); + currentKeyKind = newKeyKind; + keyProperties.Add(propertyInfo); + } + else if (newKeyKind == currentKeyKind) + { + keyProperties.Add(propertyInfo); + } + } + } + + Type keyPropertyDeclaringType = null; + foreach (PropertyInfo key in keyProperties) + { + if (null == keyPropertyDeclaringType) + { + keyPropertyDeclaringType = key.DeclaringType; + } + else if (keyPropertyDeclaringType != key.DeclaringType) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_KeysOnDifferentDeclaredType(typeName)); + } + + if (!PrimitiveType.IsKnownType(key.PropertyType) && !(key.PropertyType.GetGenericTypeDefinition() == typeof(System.Nullable<>) && key.PropertyType.GetGenericArguments().First().IsEnum())) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_KeysMustBeSimpleTypes(key.Name, typeName, key.PropertyType.FullName)); + } + } + + if (newKeyKind == KeyKind.AttributedKey && keyProperties.Count != dataServiceKeyAttribute.KeyNames.Count) + { + var m = (from string a in dataServiceKeyAttribute.KeyNames + where null == (from b in properties + where b.Name == a + select b).FirstOrDefault() + select a).First(); + throw c.Error.InvalidOperation(c.Strings.ClientType_MissingProperty(typeName, m)); + } + + return keyProperties.Count > 0 ? keyProperties.ToArray() : (isEntity ? ClientTypeUtil.EmptyPropertyInfoArray : null); + } + + /// Gets the type of the specified . + /// Member to get type of (typically PropertyInfo or FieldInfo). + /// The type of property or field type. + internal static Type GetMemberType(MemberInfo member) + { + Debug.Assert(member != null, "member != null"); + + PropertyInfo propertyInfo = member as PropertyInfo; + if (propertyInfo != null) + { + return propertyInfo.PropertyType; + } + + FieldInfo fieldInfo = member as FieldInfo; + Debug.Assert(fieldInfo != null, "fieldInfo != null -- otherwise Expression.Member factory should have thrown an argument exception"); + return fieldInfo.FieldType; + } + + /// Gets the server defined name in of the specified . + /// Member to get server defined name of. + /// Server defined name. + internal static string GetServerDefinedName(PropertyInfo propertyInfo) + { + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)propertyInfo.GetCustomAttributes(typeof(OriginalNameAttribute), false).SingleOrDefault(); + if (originalNameAttribute != null) + { + return originalNameAttribute.OriginalName; + } + + return propertyInfo.Name; + } + + /// Gets the server defined name in of the specified . + /// Member to get server defined name of. + /// The server defined name. + internal static string GetServerDefinedName(MemberInfo memberInfo) + { + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)memberInfo.GetCustomAttributes(typeof(OriginalNameAttribute), false).SingleOrDefault(); + if (originalNameAttribute != null) + { + return originalNameAttribute.OriginalName; + } + + return memberInfo.Name; + } + + /// Gets the server defined type name in of the specified . + /// Member to get server defined type name of. + /// The server defined type name. + internal static string GetServerDefinedTypeName(Type type) + { + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)type.GetCustomAttributes(typeof(OriginalNameAttribute), false).SingleOrDefault(); + if (originalNameAttribute != null) + { + return originalNameAttribute.OriginalName; + } + + return type.Name; + } + + /// Gets the full server defined type name in of the specified . + /// Member to get server defined name of. + /// The server defined type full name. + internal static string GetServerDefinedTypeFullName(Type type) + { + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)type.GetCustomAttributes(typeof(OriginalNameAttribute), false).SingleOrDefault(); + if (originalNameAttribute != null) + { + return type.Namespace + "." + originalNameAttribute.OriginalName; + } + + return type.FullName; + } + + /// + /// Gets the clr name according to server defined name in the specified . + /// + /// Member to get clr name for. + /// name from server. + /// Client clr name. + internal static string GetClientFieldName(Type t, string serverDefinedName) + { + List serverDefinedNames = serverDefinedName.Split(',').Select(name => name.Trim()).ToList(); + List clientMemberNames = new List(); + foreach (var serverSideName in serverDefinedNames) + { + FieldInfo memberInfo = t.GetField(serverSideName) ?? t.GetFields().ToList().Where(m => + { + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)m.GetCustomAttributes(typeof(OriginalNameAttribute), false).SingleOrDefault(); + return originalNameAttribute != null && originalNameAttribute.OriginalName == serverSideName; + }).SingleOrDefault(); + + if (memberInfo == null) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_MissingProperty(t.ToString(), serverSideName)); + } + + clientMemberNames.Add(memberInfo.Name); + } + + return string.Join(",", clientMemberNames); + } + + /// + /// Gets the clr name according to server defined name in the specified . + /// + /// The type used to get the client PropertyInfo. + /// Name from server. + /// Flag to support untyped properties. + /// Client PropertyInfo, or null if the method is not found. + internal static PropertyInfo GetClientPropertyInfo(Type t, string serverDefinedName, UndeclaredPropertyBehavior undeclaredPropertyBehavior) + { + PropertyInfo propertyInfo = t.GetProperty(serverDefinedName); + if (propertyInfo == null) + { + propertyInfo = t.GetProperties().Where( + m => + { + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)m.GetCustomAttributes(typeof(OriginalNameAttribute), false).SingleOrDefault(); + return originalNameAttribute != null && originalNameAttribute.OriginalName == serverDefinedName; + }).SingleOrDefault(); + } + + if (propertyInfo == null && (undeclaredPropertyBehavior == UndeclaredPropertyBehavior.ThrowException)) + { + throw c.Error.InvalidOperation(c.Strings.ClientType_MissingProperty(t.ToString(), serverDefinedName)); + } + + return propertyInfo; + } + + /// + /// Gets the clr name according to server defined name in the specified . + /// + /// The type used to get the client property name. + /// Name from server. + /// Flag to support untyped properties. + /// Client property name, or null if the property is not found. + internal static string GetClientPropertyName(Type t, string serverDefinedName, UndeclaredPropertyBehavior undeclaredPropertyBehavior) + { + PropertyInfo propertyInfo = GetClientPropertyInfo(t, serverDefinedName, undeclaredPropertyBehavior); + return propertyInfo == null ? serverDefinedName : propertyInfo.Name; + } + + /// + /// Get a client method with the specified server side name. + /// + /// Client type used to search method. + /// Method name on server side. + /// An array of System.Type objects of the parameters for the method to get + /// Client MethodInfo, or null if the method is not found. + internal static MethodInfo GetClientMethod(Type t, string serverDefinedName, Type[] parameters) + { + MethodInfo methodInfo = t.GetMethod(serverDefinedName, parameters); + if (methodInfo == null) + { + var clientNamedMethodInfo = t.GetMethods().Where( + m => + { + OriginalNameAttribute originalNameAttribute = (OriginalNameAttribute)m.GetCustomAttributes(typeof(OriginalNameAttribute), false).SingleOrDefault(); + return originalNameAttribute != null && originalNameAttribute.OriginalName == serverDefinedName; + }).FirstOrDefault(); + + if (clientNamedMethodInfo != null) + { + methodInfo = t.GetMethod(clientNamedMethodInfo.Name, parameters); + } + } + + return methodInfo; + } + + /// + /// Get the enum string split by "," with their server side names + /// + /// enum string with names in client side + /// the source enum type + /// The enum string split by "," with their server side names + internal static string GetEnumValuesString(string enumString, Type enumType) + { + string[] enums = enumString.Split(',').Select(v => v.Trim()).ToArray(); + List memberValues = new List(); + foreach (var enumValue in enums) + { + MemberInfo member = enumType.GetField(enumValue); + if (member == null) + { + throw new NotSupportedException(Strings.Serializer_InvalidEnumMemberValue(enumType.Name, enumValue)); + } + + memberValues.Add(ClientTypeUtil.GetServerDefinedName(member)); + } + + return string.Join(",", memberValues); + } + + /// + /// Returns the KeyKind if is declared as a key in or it follows the key naming convension. + /// + /// Property in question. + /// DataServiceKeyAttribute instance. + /// Returns the KeyKind if is declared as a key in or it follows the key naming convension. + private static KeyKind IsKeyProperty(PropertyInfo propertyInfo, KeyAttribute dataServiceKeyAttribute) + { + Debug.Assert(propertyInfo != null, "propertyInfo != null"); + + string propertyName = GetServerDefinedName(propertyInfo); + + KeyKind keyKind = KeyKind.NotKey; + if (dataServiceKeyAttribute != null && dataServiceKeyAttribute.KeyNames.Contains(propertyName)) + { + keyKind = KeyKind.AttributedKey; + } + else if (propertyName.EndsWith("ID", StringComparison.Ordinal)) + { + string declaringTypeName = propertyInfo.DeclaringType.Name; + if ((propertyName.Length == (declaringTypeName.Length + 2)) && propertyName.StartsWith(declaringTypeName, StringComparison.Ordinal)) + { + // matched "DeclaringType.Name+ID" pattern + keyKind = KeyKind.TypeNameId; + } + else if (2 == propertyName.Length) + { + // matched "ID" pattern + keyKind = KeyKind.Id; + } + } + + return keyKind; + } + + /// + /// Checks whether the specified is a + /// closed constructed type of the generic type. + /// + /// Type to check. + /// Generic type for checkin. + /// true if is a constructed type of . + /// The check is an immediate check; no inheritance rules are applied. + private static bool IsConstructedGeneric(Type type, Type genericTypeDefinition) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(genericTypeDefinition != null, "genericTypeDefinition != null"); + + return type.IsGenericType() && (type.GetGenericTypeDefinition() == genericTypeDefinition) && !type.ContainsGenericParameters(); + } + + /// + /// Determines whether the declared on + /// overrides a (virtual/abstract) property of a base type. + /// + /// The declaring type of the property. + /// The property to check. + /// true if overrides a property on a base types; otherwise false. + private static bool IsOverride(Type type, PropertyInfo propertyInfo) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(propertyInfo != null, "propertyInfo != null"); + + // We only check the getter method; if a property does not have a getter method we don't consider it + MethodInfo getMethod = propertyInfo.GetGetMethod(); + if (getMethod != null && getMethod.GetBaseDefinition().DeclaringType != type) + { + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmComplexTypeWithDelayLoadedProperties.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmComplexTypeWithDelayLoadedProperties.cs new file mode 100644 index 0000000..a0de5ba --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmComplexTypeWithDelayLoadedProperties.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.Providers +#else +namespace Microsoft.OData.Service.Providers +#endif +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// An implementation that supports delay-loading of properties. + /// + internal class EdmComplexTypeWithDelayLoadedProperties : EdmComplexType + { + /// The lock object for the delayed property loading. + private readonly object lockObject = new object(); + + /// An action that is used to create the properties for this type. + private Action propertyLoadAction; + + /// + /// Initializes a new instance of the EdmComplexTypeWithDelayLoadedProperties class. + /// + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + /// Denotes an entity that cannot be instantiated. + /// Denotes if the type is open. + /// An action that is used to create the properties for this type. + internal EdmComplexTypeWithDelayLoadedProperties( + string namespaceName, + string name, + IEdmComplexType baseType, + bool isAbstract, + bool isOpen, + Action propertyLoadAction) + : base(namespaceName, name, baseType, isAbstract, isOpen) + { + Debug.Assert(propertyLoadAction != null, "propertyLoadAction != null"); + this.propertyLoadAction = propertyLoadAction; + } + + /// + /// Gets the properties declared immediately within this type. + /// + public override IEnumerable DeclaredProperties + { + get + { + this.EnsurePropertyLoaded(); + return base.DeclaredProperties; + } + } + + /// + /// Ensures that the properties have been loaded and can be used. + /// + private void EnsurePropertyLoaded() + { + lock (this.lockObject) + { + if (this.propertyLoadAction != null) + { + this.propertyLoadAction(this); + this.propertyLoadAction = null; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntitySetFacade.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntitySetFacade.cs new file mode 100644 index 0000000..9213ef0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntitySetFacade.cs @@ -0,0 +1,121 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Service.Client.Metadata +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + + /// + /// Wraps an entity set from the server-side model and restricts which APIs can be called. + /// + internal class EdmEntitySetFacade : IEdmEntitySet + { + /// Storage for the entity set from the server model. + private readonly IEdmEntitySet serverEntitySet; + + /// Storage for the model facade. + private readonly EdmModelFacade modelFacade; + + /// + /// Initializes a new instance of the class. + /// + /// The entity set from the server model. + /// The entity container facade to which the set belongs. + /// The model facade. + internal EdmEntitySetFacade(IEdmEntitySet serverEntitySet, EdmEntityContainerFacade containerFacade, EdmModelFacade modelFacade) + { + Debug.Assert(serverEntitySet != null, "serverEntitySet != null"); + Debug.Assert(containerFacade != null, "container != null"); + Debug.Assert(modelFacade != null, "modelFacade != null"); + + this.serverEntitySet = serverEntitySet; + this.Container = containerFacade; + this.modelFacade = modelFacade; + + this.Name = this.serverEntitySet.Name; + } + + /// + /// Gets the name of this element. + /// + public string Name { get; private set; } + + /// + /// Gets the kind of element of this container element. + /// + public EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.EntitySet; } + } + + /// + /// Gets the container that contains this element. + /// + public IEdmEntityContainer Container { get; private set; } + + /// + /// Gets the entity type contained in this entity set. + /// + public IEdmEntityType ElementType + { + get { return this.modelFacade.GetOrCreateEntityTypeFacade(this.serverEntitySet.ElementType); } + } + + /// + /// Gets the navigation targets of this entity set. + /// + public IEnumerable NavigationTargets + { + get { throw CreateExceptionForUnsupportedPublicMethod("NavigationTargets"); } + } + + /// + /// Finds the entity set that a navigation property targets. + /// + /// The navigation property. + /// The entity set that the navigation propertion targets, or null if no such entity set exists. + public IEdmEntitySet FindNavigationTarget(IEdmNavigationProperty navigationProperty) + { + Util.CheckArgumentNull(navigationProperty, "navigationProperty"); + + var navigationFacade = navigationProperty as EdmNavigationPropertyFacade; + if (navigationFacade == null) + { + return null; + } + + return navigationFacade.FindNavigationTarget(this.serverEntitySet); + } + + /// + /// Unit test method for determining whether two facades are equivalent (ie: wrap the same server entity set). + /// + /// The other facade. + /// + /// true if the two facades wrap the same entity sets; otherwise, false. + /// + internal bool IsEquivalentTo(EdmEntitySetFacade other) + { + return other != null && ReferenceEquals(other.serverEntitySet, this.serverEntitySet); + } + + /// + /// Creates an exception for a intentionally-unsupported part of the API. + /// This is used to prevent new code from calling previously-unused API, which could be a breaking change + /// for user implementations of the interface. + /// + /// Name of the unsupported method. + /// The exception + private static Exception CreateExceptionForUnsupportedPublicMethod(string methodName) + { + return new NotSupportedException(Microsoft.OData.Service.Client.Strings.EdmModelFacade_UnsupportedMethod("IEdmEntitySet", methodName)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntityTypeWithDelayLoadedProperties.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntityTypeWithDelayLoadedProperties.cs new file mode 100644 index 0000000..ef2c379 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEntityTypeWithDelayLoadedProperties.cs @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.Providers +#else +namespace Microsoft.OData.Service.Providers +#endif +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// An implementation that supports delay-loading of properties. + /// + internal class EdmEntityTypeWithDelayLoadedProperties : EdmEntityType + { + /// The lock object for the delayed property loading. + private readonly object lockObject = new object(); + + /// An action that is used to create the properties for this type. + private Action propertyLoadAction; + + /// + /// Initializes a new instance of the EdmEntityTypeWithDelayLoadedProperties class. + /// + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + /// Denotes an entity that cannot be instantiated. + /// Denotes if the type is open. + /// Denotes if the type is a media entity. + /// An action that is used to create the properties for this type. + internal EdmEntityTypeWithDelayLoadedProperties( + string namespaceName, + string name, + IEdmEntityType baseType, + bool isAbstract, + bool isOpen, + bool hasStream, + Action propertyLoadAction) + : base(namespaceName, name, baseType, isAbstract, isOpen, hasStream) + { + Debug.Assert(propertyLoadAction != null, "propertyLoadAction != null"); + this.propertyLoadAction = propertyLoadAction; + } + + /// + /// Gets or sets the structural properties of the entity type that make up the entity key. + /// + public override IEnumerable DeclaredKey + { + get + { + this.EnsurePropertyLoaded(); + return base.DeclaredKey; + } + } + + /// + /// Gets the properties declared immediately within this type. + /// + public override IEnumerable DeclaredProperties + { + get + { + this.EnsurePropertyLoaded(); + return base.DeclaredProperties; + } + } + + /// + /// Ensures that the properties have been loaded and can be used. + /// + private void EnsurePropertyLoaded() + { + lock (this.lockObject) + { + if (this.propertyLoadAction != null) + { + this.propertyLoadAction(this); + this.propertyLoadAction = null; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEnumTypeWithDelayLoadedMembers.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEnumTypeWithDelayLoadedMembers.cs new file mode 100644 index 0000000..69b4531 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmEnumTypeWithDelayLoadedMembers.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.Providers +#else +namespace Microsoft.OData.Service.Providers +#endif +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// An implementation that supports delay-loading of enumeration members. + /// + internal class EdmEnumTypeWithDelayLoadedMembers : EdmEnumType + { + /// The lock object for the delayed property loading. + private readonly object lockObject = new object(); + + /// An action that is used to create the members for this type. + private Action memberLoadAction; + + /// + /// Initializes a new instance of the EdmEnumTypeWithDelayLoadedMembers class. + /// + /// Namespace the enumeration belongs to. + /// Name of the enumeration Type. + /// The enum's underlying type, one of Edm.Byte, Edm.SByte, Edm.Int16, Edm.Int32, or Edm.Int64. + /// The isFlags or not + /// An action that is used to create the members for this enum type. + internal EdmEnumTypeWithDelayLoadedMembers( + string namespaceName, + string name, + IEdmPrimitiveType underlyingType, + bool isFlags, + Action memberLoadAction) + : base(namespaceName, name, underlyingType, isFlags) + { + Debug.Assert(memberLoadAction != null, "memberLoadAction != null"); + this.memberLoadAction = memberLoadAction; + } + + /// + /// Gets the enum members immediately within this type. + /// + public override IEnumerable Members + { + get + { + this.EnsureMemberLoaded(); + return base.Members; + } + } + + /// + /// Ensures that the properties have been loaded and can be used. + /// + private void EnsureMemberLoaded() + { + lock (this.lockObject) + { + if (this.memberLoadAction != null) + { + this.memberLoadAction(this); + this.memberLoadAction = null; + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionImportFacade.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionImportFacade.cs new file mode 100644 index 0000000..4c7de01 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionImportFacade.cs @@ -0,0 +1,177 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Service.Client.Metadata +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + + /// + /// Wraps a function import from the server-side model. + /// + internal class EdmFunctionImportFacade : IEdmFunctionImport + { + /// + /// The Edm Container facade this function import belongs to. + /// + private readonly EdmEntityContainerFacade containerFacade; + + /// + /// Stores the function import from the server-side model which we are wrapping. + /// + private readonly IEdmFunctionImport serverFunctionImport; + + /// + /// The return type of this function. + /// + private readonly IEdmTypeReference returnType; + + /// + /// The collection of parameters for this function. + /// + private readonly IEdmFunctionParameter[] parameters; + + /// + /// Initializes a new instance of class. + /// + /// The function import from the server-side model which we are wrapping. + /// The edm container facade this function import belongs to. + /// The edm model facade this function import belongs to. + public EdmFunctionImportFacade(IEdmFunctionImport serverFunctionImport, EdmEntityContainerFacade containerFacade, EdmModelFacade modelFacade) + { + Debug.Assert(serverFunctionImport != null, "serverFunctionImport != null"); + Debug.Assert(containerFacade != null, "containerFacade != null"); + Debug.Assert(modelFacade != null, "modelFacade != null"); + this.serverFunctionImport = serverFunctionImport; + this.containerFacade = containerFacade; + + IEdmTypeReference serverReturnTypeReference = serverFunctionImport.ReturnType; + if (serverReturnTypeReference == null) + { + this.returnType = null; + } + else + { + IEdmType serverReturnType = modelFacade.GetOrCreateEntityTypeFacadeOrReturnNonEntityServerType(serverReturnTypeReference.Definition); + this.returnType = serverReturnType.ToEdmTypeReference(serverReturnTypeReference.IsNullable); + } + + this.parameters = serverFunctionImport.Parameters.Select(p => new EdmFunctionParameterFacade(p, this, modelFacade)).ToArray(); + } + + /// + /// Gets a value indicating whether this function import has side-effects. + /// cannot be set to true if is set to true. + /// + public bool IsSideEffecting + { + get { return this.serverFunctionImport.IsSideEffecting; } + } + + /// + /// Gets a value indicating whether this functon import can be composed inside expressions. + /// cannot be set to true if is set to true. + /// + public bool IsComposable + { + get { return this.serverFunctionImport.IsComposable; } + } + + /// + /// Gets a value indicating whether this function import can be used as an extension method for the type of the first parameter of this function import. + /// + public bool IsBindable + { + get { return this.serverFunctionImport.IsBindable; } + } + + /// + /// Gets the entity set containing entities returned by this function import. + /// + public IEdmExpression EntitySet + { + get { throw CreateExceptionForUnsupportedPublicMethod("get_EntitySet"); } + } + + /// + /// Gets the kind of this function, which is always FunctionImport. + /// + public EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.FunctionImport; } + } + + /// + /// Gets the container of this function. + /// + public IEdmEntityContainer Container + { + get { return this.containerFacade; } + } + + /// + /// Gets the name of this element. + /// + public string Name + { + get { return this.serverFunctionImport.Name; } + } + + /// + /// Gets the return type of this function. + /// + public IEdmTypeReference ReturnType + { + get { return this.returnType; } + } + + /// + /// Gets the collection of parameters for this function. + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Searches for a parameter with the given name, and returns null if no such parameter exists. + /// + /// The name of the parameter being found. + /// The requested parameter or null if no such parameter exists. + public IEdmFunctionParameter FindParameter(string name) + { + return this.parameters.SingleOrDefault(p => p.Name == name); + } + + /// + /// Retrieves an annotation value for function import from the server model. Returns null if no annotation with the given name exists. + /// + /// The annotation manager from the server model. + /// Namespace that the annotation belongs to. + /// Local name of the annotation. + /// Returns the annotation value that corresponds to the provided name. Returns null if no annotation with the given name exists. + internal object GetAnnotationValue(IEdmDirectValueAnnotationsManager serverManager, string namespaceName, string localName) + { + Debug.Assert(serverManager != null, "serverManager != null"); + return serverManager.GetAnnotationValue(this.serverFunctionImport, namespaceName, localName); + } + + /// + /// Creates an exception for a intentionally-unsupported part of the API. + /// This is used to prevent new code from calling previously-unused API, which could be a breaking change + /// for user implementations of the interface. + /// + /// Name of the unsupported method. + /// The exception + private static Exception CreateExceptionForUnsupportedPublicMethod(string methodName) + { + return new NotSupportedException(Microsoft.OData.Service.Client.Strings.EdmModelFacade_UnsupportedMethod("IEdmFunctionImport", methodName)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionParameterFacade.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionParameterFacade.cs new file mode 100644 index 0000000..e3e7a82 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmFunctionParameterFacade.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Service.Client.Metadata +{ + using System.Diagnostics; + using Microsoft.OData.Edm; + + /// + /// Wraps a function parameter from the server-side model. + /// + internal class EdmFunctionParameterFacade : IEdmFunctionParameter + { + /// + /// The function parameter from the server-side model to wrap. + /// + private readonly IEdmFunctionParameter serverFunctionParameter; + + /// + /// The type of this function parameter. + /// + private readonly IEdmTypeReference type; + + /// + /// The function or function import that declared this parameter. + /// + private readonly IEdmFunctionBase declaringFunction; + + /// + /// Initializes a new instance of class. + /// + /// The function parameter from the server-side model to wrap. + /// The function import facade which this parameter belongs to. + /// The edm model facade this function import belongs to. + public EdmFunctionParameterFacade(IEdmFunctionParameter serverFunctionParameter, EdmFunctionImportFacade declaringFunctionFacade, EdmModelFacade modelFacade) + { + Debug.Assert(serverFunctionParameter != null, "serverFunctionParameter != null"); + Debug.Assert(declaringFunctionFacade != null, "declaringFunctionFacade != null"); + Debug.Assert(modelFacade != null, "modelFacade != null"); + + this.serverFunctionParameter = serverFunctionParameter; + this.declaringFunction = declaringFunctionFacade; + this.type = modelFacade.GetOrCreateEntityTypeFacadeOrReturnNonEntityServerType(serverFunctionParameter.Type.Definition).ToEdmTypeReference(serverFunctionParameter.Type.IsNullable); + } + + /// + /// Gets the name of this element. + /// + public string Name + { + get { return this.serverFunctionParameter.Name; } + } + + /// + /// Gets the type of this function parameter. + /// + public IEdmTypeReference Type + { + get { return this.type; } + } + + /// + /// Gets the function or function import that declared this parameter. + /// + public IEdmFunctionBase DeclaringFunction + { + get { return this.declaringFunction; } + } + + /// + /// Gets the mode of this function parameter. + /// + public EdmFunctionParameterMode Mode + { + get { return this.serverFunctionParameter.Mode; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmNavigationPropertyFacade.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmNavigationPropertyFacade.cs new file mode 100644 index 0000000..86c022b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Metadata/EdmNavigationPropertyFacade.cs @@ -0,0 +1,262 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Service.Client.Metadata +{ + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using ErrorStrings = Microsoft.OData.Service.Client.Strings; + + /// + /// A navigation property implementation which combines information from the client and server models. + /// + internal class EdmNavigationPropertyFacade : IEdmNavigationProperty + { + /// The model facade this property belongs to. + private readonly EdmModelFacade modelFacade; + + /// The type facade this property is declared on. + private readonly EdmEntityTypeFacade declaringTypeFacade; + + /// The server navigation property this facade represents, if one exists. + private readonly IEdmNavigationProperty serverProperty; + + /// The client navigation property this facade represents. + private readonly IEdmNavigationProperty clientProperty; + + /// Storage for the combined type of the navigation once it has been computed. + private IEdmTypeReference combinedPropertyType; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the property. + /// The model facade. + /// The type facade. + /// The server property if one exists. + /// The client property. + internal EdmNavigationPropertyFacade(string name, EdmModelFacade modelFacade, EdmEntityTypeFacade declaringTypeFacade, IEdmNavigationProperty serverProperty, IEdmNavigationProperty clientProperty) + { + Debug.Assert(clientProperty != null, "clientProperty != null"); + Debug.Assert(serverProperty != null, "serverProperty != null"); + Debug.Assert(modelFacade != null, "modelFacade != null"); + Debug.Assert(declaringTypeFacade != null, "declaringTypeFacade != null"); + + this.Name = name; + this.modelFacade = modelFacade; + this.declaringTypeFacade = declaringTypeFacade; + this.serverProperty = serverProperty; + this.clientProperty = clientProperty; + } + + /// + /// Gets the name of this element. + /// + public string Name { get; private set; } + + /// + /// Gets the kind of this property. + /// + public EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.Navigation; } + } + + /// + /// Gets the type of this property. + /// + public IEdmTypeReference Type + { + get + { + if (this.combinedPropertyType == null) + { + this.combinedPropertyType = CombinePropertyTypes(this.Name, this.clientProperty, this.serverProperty, this.modelFacade); + } + + return this.combinedPropertyType; + } + } + + /// + /// Gets the type that this property belongs to. + /// + public IEdmStructuredType DeclaringType + { + get { return this.declaringTypeFacade; } + } + + /// + /// Gets the partner of this navigation property. + /// + public IEdmNavigationProperty Partner + { + get { throw CreateExceptionForUnsupportedPublicMethod("Partner"); } + } + + /// + /// Gets the action to execute on the deletion of this end of a bidirectional association. + /// + public EdmOnDeleteAction OnDelete + { + get { throw CreateExceptionForUnsupportedPublicMethod("OnDelete"); } + } + + /// + /// Gets whether this navigation property originates at the principal end of an association. + /// + public bool IsPrincipal + { + get { throw CreateExceptionForUnsupportedPublicMethod("IsPrincipal"); } + } + + /// + /// Gets the dependent properties of this navigation property, returning null if this is the principal end or if there is no referential constraint. + /// + public IEnumerable DependentProperties + { + get { throw CreateExceptionForUnsupportedPublicMethod("DependentProperties"); } + } + + /// + /// Gets a value indicating whether the navigation target is contained inside the navigation source. + /// + public bool ContainsTarget + { + get { throw CreateExceptionForUnsupportedPublicMethod("ContainsTarget"); } + } + + /// + /// Finds the navigation target of this navigation property given a server entity set. + /// + /// The source server entity set. + /// The navigation target or null if one couldn't be found. + internal EdmEntitySetFacade FindNavigationTarget(IEdmEntitySet sourceServerEntitySet) + { + Debug.Assert(sourceServerEntitySet != null, "sourceServerEntitySet != null"); + + // if no property could be found, then there is no way to get the target. + if (this.serverProperty == null) + { + return null; + } + + // find the target using the server property. + IEdmEntitySet serverTarget = sourceServerEntitySet.FindNavigationTarget(this.serverProperty); + if (serverTarget == null) + { + return null; + } + + // if a target was found, wrap it in a new facade and return it. + return this.modelFacade.GetOrCreateEntityContainerFacade(serverTarget.Container).GetOrCreateEntitySetFacade(serverTarget); + } + + /// + /// Unit test method for determining whether two facades are equivalent (ie: wrap the same server/client properties). + /// + /// The other facade. + /// + /// true if the two facades wrap the same properties; otherwise, false. + /// + internal bool IsEquivalentTo(EdmNavigationPropertyFacade other) + { + return other != null + && ReferenceEquals(other.modelFacade, this.modelFacade) + && ReferenceEquals(other.declaringTypeFacade, this.declaringTypeFacade) + && ReferenceEquals(other.clientProperty, this.clientProperty) + && ReferenceEquals(other.serverProperty, this.serverProperty); + } + + /// + /// Creates an exception for a intentionally-unsupported part of the API. + /// This is used to prevent new code from calling previously-unused API, which could be a breaking change + /// for user implementations of the interface. + /// + /// Name of the unsupported method. + /// The exception + private static Exception CreateExceptionForUnsupportedPublicMethod(string methodName) + { + return new NotSupportedException(ErrorStrings.EdmModelFacade_UnsupportedMethod("IEdmNavigationProperty", methodName)); + } + + /// + /// Combines the property types and returns a reference to the resulting facade. + /// + /// The name of the navigation properties being combined. Used only for error messages. + /// The client property. + /// The server property. + /// The model facade. + /// + /// A type reference to the combined type. + /// + private static IEdmTypeReference CombinePropertyTypes(string propertyName, IEdmNavigationProperty clientProperty, IEdmNavigationProperty serverProperty, EdmModelFacade modelFacade) + { + Debug.Assert(clientProperty != null, "clientProperty != null"); + Debug.Assert(serverProperty != null, "serverProperty != null"); + + IEdmTypeReference clientPropertyType = clientProperty.Type; + IEdmTypeReference serverPropertyType = serverProperty.Type; + + // ensure that either both sides are a collection or neither is. + IEdmCollectionTypeReference clientCollectionType = clientPropertyType as IEdmCollectionTypeReference; + IEdmCollectionTypeReference serverCollectionType = serverPropertyType as IEdmCollectionTypeReference; + bool isCollection = clientCollectionType != null; + if (isCollection != (serverCollectionType != null)) + { + throw new InvalidOperationException(ErrorStrings.EdmNavigationPropertyFacade_InconsistentMultiplicity(propertyName)); + } + + // For collection properties: extract the element types, combine them, then recreate the collection. + // For reference properties: get the entity types and combine them. + IEdmType combinedType; + if (isCollection) + { + // get the client element type and ensure it's an entity type. + IEdmEntityTypeReference clientElementTypeReference = clientCollectionType.ElementType() as IEdmEntityTypeReference; + if (clientElementTypeReference == null) + { + throw new InvalidOperationException(ErrorStrings.EdmNavigationPropertyFacade_NonEntityType(propertyName, clientProperty.DeclaringType.FullName())); + } + + // get the server element type and ensure it's an entity type. + IEdmEntityTypeReference serverElementTypeReference = serverCollectionType.ElementType() as IEdmEntityTypeReference; + if (serverElementTypeReference == null) + { + throw new InvalidOperationException(ErrorStrings.EdmNavigationPropertyFacade_NonEntityType(propertyName, serverProperty.DeclaringType.FullName())); + } + + // combine the element types. + combinedType = modelFacade.CombineWithServerType(clientElementTypeReference.EntityDefinition(), serverElementTypeReference.EntityDefinition()); + + // turn it back into a collection, maintaining nullability of the client's element type. + combinedType = new EdmCollectionType(combinedType.ToEdmTypeReference(clientElementTypeReference.IsNullable)); + } + else + { + // ensure the server property type is an entity type. + IEdmEntityTypeReference clientEntityTypeReference = clientPropertyType as IEdmEntityTypeReference; + if (clientEntityTypeReference == null) + { + throw new InvalidOperationException(ErrorStrings.EdmNavigationPropertyFacade_NonEntityType(propertyName, clientProperty.DeclaringType.FullName())); + } + + // ensure the server property type is an entity type. + IEdmEntityTypeReference serverEntityTypeReference = serverPropertyType as IEdmEntityTypeReference; + if (serverEntityTypeReference == null) + { + throw new InvalidOperationException(ErrorStrings.EdmNavigationPropertyFacade_NonEntityType(propertyName, serverProperty.DeclaringType.FullName())); + } + + combinedType = modelFacade.CombineWithServerType(clientEntityTypeReference.EntityDefinition(), serverEntityTypeReference.EntityDefinition()); + } + + // return a type reference, maintaining the original nullability from the client property type. + return combinedType.ToEdmTypeReference(clientPropertyType.IsNullable); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Common.txt b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Common.txt new file mode 100644 index 0000000..130c1aa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Common.txt @@ -0,0 +1,290 @@ + +; NOTE: don't use \", use ' instead +; NOTE: don't use #, use ; instead for comments +; NOTE: leave the [strings] alone + +; See ResourceManager documentation and the ResGen tool. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error Messages + +Batch_ExpectedContentType=The expected content type for a batch requests is "multipart/mixed;boundary=batch" not "{0}". +Batch_ExpectedResponse=The POST request expected a response with content. ID={0} +Batch_IncompleteResponseCount=Not all requests in the batch had a response. +Batch_UnexpectedContent=The web response contained unexpected sections. ID={0} + +Context_BaseUri=Expected an absolute, well formed http URL without a query or fragment. +Context_BaseUriRequired=You must set the BaseUri property before you perform this operation. +Context_ResolveReturnedInvalidUri=The Uri that is returned by the ResolveEntitySet function must be an absolute, well-formed URL with an "http" or "https" scheme name and without any query strings or fragment identifiers. +Context_RequestUriIsRelativeBaseUriRequired=Because the requestUri is a relative Uri, you must set the BaseUri property on the DataServiceContext. +Context_ResolveEntitySetOrBaseUriRequired=The ResolveEntitySet function must return a non-null Uri for the EntitySet '{0}', otherwise you must set the BaseUri property. +Context_CannotConvertKey=Unable to convert value '{0}' into a key string for a URI. +Context_TrackingExpectsAbsoluteUri=The identity value specified by either the Atom id element or the OData-EntityId header must be an absolute URI. +Context_LocationHeaderExpectsAbsoluteUri=The 'Location' header value specified in the response must be an absolute URI. +Context_LinkResourceInsertFailure=One of the link's resources failed to insert. +Context_InternalError=Microsoft.OData.Client internal error {0}. +Context_BatchExecuteError=An error occurred for this query during batch execution. See the inner exception for details. +Context_EntitySetName=Expected a relative URL path without query or fragment. +Context_BatchNotSupportedForNamedStreams=Changes cannot be saved as a batch when an entity has one or more streams associated with it. Retry the SaveChanges operation without enabling the SaveChangesOptions.BatchWithSingleChangeset and the SaveChangesOptions.BatchWithIndependentOperations options. +Context_SetSaveStreamWithoutNamedStreamEditLink=The stream named '{0}' cannot be modified because it does not have an edit-media link. Make sure that the stream name is correct and that an edit-media link for this stream is included in the entry element in the response. +Content_EntityWithoutKey=This operation requires the entity be of an Entity Type, and has at least one key property. +Content_EntityIsNotEntityType=This operation requires the entity to be of an Entity Type, either mark its key properties, or attribute the class with DataServiceEntityAttribute +Context_EntityNotContained=The context is not currently tracking the entity. +Context_EntityAlreadyContained=The context is already tracking the entity. +Context_DifferentEntityAlreadyContained=The context is already tracking a different entity with the same resource Uri. +Context_DidNotOriginateAsync=The current object did not originate the async result. +Context_AsyncAlreadyDone=The asynchronous result has already been completed. +Context_OperationCanceled=The operation has been canceled. +Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX=The property '{0}' is not supported when MaxProtocolVersion is greater than '{1}'. + +Context_NoLoadWithInsertEnd=The context can not load the related collection or reference for objects in the added state. +Context_NoRelationWithInsertEnd=One or both of the ends of the relationship is in the added state. +Context_NoRelationWithDeleteEnd=One or both of the ends of the relationship is in the deleted state. +Context_RelationAlreadyContained=The context is already tracking the relationship. +Context_RelationNotRefOrCollection=The sourceProperty is not a reference or collection of the target's object type. +Context_AddLinkCollectionOnly=AddLink and DeleteLink methods only work when the sourceProperty is a collection. +Context_AddRelatedObjectCollectionOnly=AddRelatedObject method only works when the sourceProperty is a collection. +Context_AddRelatedObjectSourceDeleted=AddRelatedObject method only works if the source entity is in a non-deleted state. +Context_UpdateRelatedObjectNonCollectionOnly=UpdateRelatedObject method only works when the sourceProperty is not collection. +Context_SetLinkReferenceOnly=SetLink method only works when the sourceProperty is not a collection. + +Context_NoContentTypeForMediaLink=Media link object of type '{0}' is configured to use the MIME type specified in the property '{1}'. However, that property's value is null or empty. +Context_BatchNotSupportedForMediaLink=Saving entities with the [MediaEntry] attribute is not currently supported in batch mode. Use non-batched mode instead. +Context_UnexpectedZeroRawRead=Unexpected result (<= 0) from stream.Read() while reading raw data for this property. +Context_VersionNotSupported=Response version '{0}' is not supported. The only supported versions are: {1}. +Context_ResponseVersionIsBiggerThanProtocolVersion=The response version is {0}, but the MaxProtocolVersion of the data service context is set to {1}. Set the MaxProtocolVersion to the version required by the response, and then retry the request. If the client does not support the required protocol version, then upgrade the client. +Context_RequestVersionIsBiggerThanProtocolVersion=The request requires that version {0} of the protocol be used, but the MaxProtocolVersion of the data service context is set to {1}. Set the MaxProtocolVersion to the higher version, and then retry the operation. + +Context_ChildResourceExists=Attempt to delete a link between two objects failed because the identity of the target object of the link depends on the source object of the link. + +Context_ContentTypeRequiredForNamedStream=The ContentType value for a named stream cannot be null or an empty string. +Context_EntityNotMediaLinkEntry=This operation requires that the specified entity be a Media Link Entry and that the ReadStreamUri be available. However, the specified entity either is not a Media Link Entry or does not have a valid ReadStreamUri value. If the entity is a Media Link Entry, re-query the data service for this entity to obtain a valid ReadStreamUri value. +Context_MLEWithoutSaveStream=The entity type {0} is marked with MediaEntry attribute but no save stream was set for the entity. +Context_SetSaveStreamOnMediaEntryProperty=Can't use SetSaveStream on entity with type {0} which has a media entry property defined. +Context_SetSaveStreamWithoutEditMediaLink=There is no edit-media link for the entity's media stream. Make sure that the edit-media link is specified for this stream. +Context_SetSaveStreamOnInvalidEntityState=Calling SetSaveStream on an entity with state '{0}' is not allowed. +Context_EntityDoesNotContainNamedStream=The entity does not have a stream named '{0}'. Make sure that the name of the stream is correct. +Context_MissingSelfAndEditLinkForNamedStream=There is no self-link or edit-media link for the stream named '{0}'. Make sure that either the self-link or edit-media link is specified for this stream. +Context_BothLocationAndIdMustBeSpecified=The response should have both 'Location' and 'OData-EntityId' headers or the response should not have any of these headers. +Context_BodyOperationParametersNotAllowedWithGet=OperationParameter of type BodyOperationParameter cannot be specified when the HttpMethod is set to GET. +Context_MissingOperationParameterName=The Name property of an OperationParameter must be set to a non-null, non-empty string. +Context_DuplicateUriOperationParameterName=Multiple uri operation parameters were found with the same name. Uri operation parameter names must be unique. +Context_DuplicateBodyOperationParameterName=Multiple body operation parameters were found with the same name. Body operation parameter names must be unique. +Context_NullKeysAreNotSupported=The serialized resource has a null value in key member '{0}'. Null values are not supported in key members. +Context_ExecuteExpectsGetOrPostOrDelete=The HttpMethod must be GET, POST or DELETE. +Context_EndExecuteExpectedVoidResponse=EndExecute overload for void service operations and actions received a non-void response from the server. +Context_NullElementInOperationParameterArray=The operation parameters array contains a null element which is not allowed. +Context_EntityMetadataBuilderIsRequired=An implementation of ODataEntityMetadataBuilder is required, but a null value was returned from GetEntityMetadataBuilder. +Context_CannotChangeStateToAdded=The ChangeState method does not support the 'Added' state because additional information is needed for inserts. Use either AddObject or AddRelatedObject instead. +Context_CannotChangeStateToModifiedIfNotUnchanged=The entity's state can only be changed to 'Modified' if it is currently 'Unchanged'. +Context_CannotChangeStateIfAdded=An entity in the 'Added' state cannot be changed to '{0}', it can only be changed to 'Detached'. +Context_OnMessageCreatingReturningNull=DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property must not return a null value. Please return a non-null value for this property. +Context_SendingRequest_InvalidWhenUsingOnMessageCreating=SendingRequest cannot be used in combination with the DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property. Please use SendingRequest2 with DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property instead. +Context_MustBeUsedWith='{0}' must be used with '{1}'. + +DataServiceClientFormat_LoadServiceModelRequired=When you call the UseJson method without a parameter, you must use the LoadServiceModel property to provide a valid IEdmModel instance. +DataServiceClientFormat_ValidServiceModelRequiredForJson=To use the JSON format, you must first call DataServiceContext.Format.UseJson() and supply a valid service model. + +Collection_NullCollectionReference={0}.{1} must return a non-null open property collection. + +ClientType_MissingOpenProperty=The open object property '{0}:{1}' is not defined. +Clienttype_MultipleOpenProperty={0} has multiple definitions for OpenObjectAttribute. +ClientType_MissingProperty=The closed type {0} does not have a corresponding {1} settable property. +ClientType_KeysMustBeSimpleTypes=The key property '{0}' on for type '{1}' is of type '{2}', which is not a simple type. Only properties of simple type can be key properties. +ClientType_KeysOnDifferentDeclaredType={0} has key properties declared at different levels within its type hierarchy. +ClientType_MissingMimeTypeProperty=Type '{0}' has a MimeTypeProperty attribute that references the MIME type property '{1}'. However, this type does not have a property '{1}'. +ClientType_MissingMimeTypeDataProperty=Type '{0}' has a MimeTypeProperty attribute that references the data property '{1}'. However, this type does not have a property '{1}'. +ClientType_MissingMediaEntryProperty=Type '{0}' has a MediaEntry attribute that references a property called '{1}'. However, this type does not have a property '{1}'. +ClientType_NoSettableFields=The complex type '{0}' has no settable properties. +ClientType_MultipleImplementationNotSupported=Multiple implementations of ICollection is not supported. +ClientType_NullOpenProperties=The open type property '{0}' returned a null instance. +ClientType_Ambiguous=Resolving type from '{0}' that inherits from '{1}' is ambiguous. +ClientType_UnsupportedType=The type '{0}' is not supported by the client library. +ClientType_CollectionOfCollectionNotSupported=Collection properties of a collection type are not supported. +ClientType_MultipleTypesWithSameName=Multiple types were found with the same name '{0}'. Type names must be unique. + +WebUtil_TypeMismatchInCollection=An item in the collection property '{0}' is not of the correct type. All items in the collection property must be of the collection item type. +WebUtil_TypeMismatchInNonPropertyCollection=A collection of item type '{0}' has an item which is not of the correct type. All items in the collection must be of the collection item type. +ClientTypeCache_NonEntityTypeCannotContainEntityProperties=The property '{0}' is of entity type and it cannot be a property of the type '{1}', which is not of entity type. Only entity types can contain navigation properties. +DataServiceException_GeneralError=An error occurred while processing this request. + +Deserialize_GetEnumerator=Only a single enumeration is supported by this IEnumerable. +Deserialize_Current=The current value '{1}' type is not compatible with the expected '{0}' type. +Deserialize_MixedTextWithComment=Error processing response stream. Element value interspersed with a comment is not supported. +Deserialize_ExpectingSimpleValue=Error processing response stream. The XML element contains mixed content. +Deserialize_MismatchAtomLinkLocalSimple=Error processing response stream. Atom payload has a link, local object has a simple value. +Deserialize_MismatchAtomLinkFeedPropertyNotCollection=Error processing response stream. Atom payload has a feed and the property '{0}' is not a collection. +Deserialize_MismatchAtomLinkEntryPropertyIsCollection=Error processing response stream. Atom payload has an entry and the property '{0}' is a collection. +Deserialize_NoLocationHeader=The response to this POST request did not contain a 'location' header. That is not supported by this client. +Deserialize_ServerException=Error processing response stream. Server failed with following message:\r\n{0} +Deserialize_MissingIdElement=Error processing response stream. Missing id element in the response. + +Collection_NullCollectionNotSupported=The value of the property '{0}' is null. Properties that are a collection type of primitive or complex types cannot be null. +Collection_NullNonPropertyCollectionNotSupported=The value of the collection of item type '{0}' is null. A collection cannot have a null value. +Collection_NullCollectionItemsNotSupported=An item in the collection property has a null value. Collection properties that contain items with null values are not supported. +Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed=A collection property of primitive types cannot contain an item of a collection type. +Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed=A collection property of complex types cannot contain an item of a primitive type. + +EntityDescriptor_MissingSelfEditLink=The entity with identity '{0}' does not have a self-link or an edit-link associated with it. Please make sure that the entity has either a self-link or an edit-link associated with it. + +HttpProcessUtility_ContentTypeMissing=Content-Type header value missing. +HttpProcessUtility_MediaTypeMissingValue=Media type is missing a parameter value. +HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter=Media type requires a ';' character before a parameter definition. +HttpProcessUtility_MediaTypeRequiresSlash=Media type requires a '/' character. +HttpProcessUtility_MediaTypeRequiresSubType=Media type requires a subtype definition. +HttpProcessUtility_MediaTypeUnspecified=Media type is unspecified. +HttpProcessUtility_EncodingNotSupported=Character set '{0}' is not supported. +HttpProcessUtility_EscapeCharWithoutQuotes=Value for MIME type parameter '{0}' is incorrect because it contained escape characters even though it was not quoted. +HttpProcessUtility_EscapeCharAtEnd=Value for MIME type parameter '{0}' is incorrect because it terminated with escape character. Escape characters must always be followed by a character in a parameter value. +HttpProcessUtility_ClosingQuoteNotFound=Value for MIME type parameter '{0}' is incorrect because the closing quote character could not be found while the parameter value started with a quote character. + +MaterializeFromAtom_CountNotPresent=Count value is not part of the response stream. +MaterializeFromAtom_TopLevelLinkNotAvailable=The top level link is only available after the response has been enumerated. +MaterializeFromAtom_CollectionKeyNotPresentInLinkTable=The collection is not part of the current entry +MaterializeFromAtom_GetNestLinkForFlatCollection=This response does not contain any nested collections. Use null as Key instead. + +ODataRequestMessage_GetStreamMethodNotSupported=GetStream method is not supported. + +Util_EmptyString=Empty string. +Util_EmptyArray=Empty array. +Util_NullArrayElement=Array contains a null element. + +ALinq_UnsupportedExpression=The expression type {0} is not supported. +ALinq_CouldNotConvert=Could not convert constant {0} expression to string. +ALinq_MethodNotSupported=The method '{0}' is not supported. +ALinq_UnaryNotSupported=The unary operator '{0}' is not supported. +ALinq_BinaryNotSupported=The binary operator '{0}' is not supported. +ALinq_ConstantNotSupported=The constant for '{0}' is not supported. +ALinq_TypeBinaryNotSupported=An operation between an expression and a type is not supported. +ALinq_ConditionalNotSupported=The conditional expression is not supported. +ALinq_ParameterNotSupported=The parameter expression is not supported. +ALinq_MemberAccessNotSupported=The member access of '{0}' is not supported. +ALinq_LambdaNotSupported=Lambda Expressions not supported. +ALinq_NewNotSupported=New Expressions not supported. +ALinq_MemberInitNotSupported=Member Init Expressions not supported. +ALinq_ListInitNotSupported=List Init Expressions not supported. +ALinq_NewArrayNotSupported=New Array Expressions not supported. +ALinq_InvocationNotSupported=Invocation Expressions not supported. +ALinq_QueryOptionsOnlyAllowedOnLeafNodes=Can only specify query options (orderby, where, take, skip) after last navigation. +ALinq_CantExpand=Expand query option not allowed. +ALinq_CantCastToUnsupportedPrimitive=Can't cast to unsupported type '{0}' +ALinq_CantNavigateWithoutKeyPredicate=Individual properties can only be selected from a single resource or as part of a type. Specify a key predicate to restrict the entity set to a single instance or project the property into a named or anonymous type. +ALinq_CanOnlyApplyOneKeyPredicate=Multiple key predicates cannot be specified for the same entity set. +ALinq_CantTranslateExpression=The expression {0} is not supported. +ALinq_TranslationError=Error translating Linq expression to URI: {0} +ALinq_CantAddQueryOption=Custom query option not allowed. +ALinq_CantAddDuplicateQueryOption=Can't add duplicate query option '{0}'. +ALinq_CantAddAstoriaQueryOption=Can't add query option '{0}' because it would conflict with the query options from the translated Linq expression. +ALinq_CantAddQueryOptionStartingWithDollarSign=Can't add query option '{0}' because it begins with reserved character '$'. +ALinq_CantReferToPublicField=Referencing public field '{0}' not supported in query option expression. Use public property instead. +ALinq_QueryOptionsOnlyAllowedOnSingletons=Cannot specify query options (orderby, where, take, skip, count) on single resource. +ALinq_QueryOptionOutOfOrder=The {0} query option cannot be specified after the {1} query option. +ALinq_CannotAddCountOption=Cannot add count option to the resource set. +ALinq_CannotAddCountOptionConflict=Cannot add count option to the resource set because it would conflict with existing count options. +ALinq_ProjectionOnlyAllowedOnLeafNodes=Can only specify 'select' query option after last navigation. +ALinq_ProjectionCanOnlyHaveOneProjection=Cannot translate multiple Linq Select operations in a single 'select' query option. +ALinq_ProjectionMemberAssignmentMismatch=Cannot initialize an instance of entity type '{0}' because '{1}' and '{2}' do not refer to the same source entity. +ALinq_InvalidExpressionInNavigationPath=The expression '{0}' is not a valid expression for navigation path. The only supported operations inside the lambda expression body are MemberAccess and TypeAs. The expression must contain at least one MemberAccess and it cannot end with TypeAs. +ALinq_ExpressionNotSupportedInProjectionToEntity=Initializing instances of the entity type {0} with the expression {1} is not supported. +ALinq_ExpressionNotSupportedInProjection=Constructing or initializing instances of the type {0} with the expression {1} is not supported. +ALinq_CannotConstructKnownEntityTypes=Construction of entity type instances must use object initializer with default constructor. +ALinq_CannotCreateConstantEntity=Referencing of local entity type instances not supported when projecting results. +ALinq_PropertyNamesMustMatchInProjections=Cannot assign the value from the {0} property to the {1} property. When projecting results into a entity type, the property names of the source type and the target type must match for the properties being projected. +ALinq_CanOnlyProjectTheLeaf=Can only project the last entity type in the query being translated. +ALinq_CannotProjectWithExplicitExpansion=Cannot create projection while there is an explicit expansion specified on the same query. +ALinq_CollectionPropertyNotSupportedInOrderBy=The collection property '{0}' cannot be used in an 'orderby' query expression. Collection properties are not supported by the 'orderby' query option. +ALinq_CollectionPropertyNotSupportedInWhere=The collection property '{0}' cannot be used in a 'where' query expression. Collection properties are only supported as the source of 'any' or 'all' methods in a 'where' query option. +ALinq_CollectionMemberAccessNotSupportedInNavigation=Navigation to members of the collection property '{0}' in a 'select' query expression is not supported. +ALinq_LinkPropertyNotSupportedInExpression=The property '{0}' of type 'DataServiceStreamLink' cannot be used in 'where' or 'orderby' query expressions. Properties of type 'DataServiceStreamLink' are not supported by these query options. +ALinq_OfTypeArgumentNotAvailable=The target type for an OfType filter could not be determined. +ALinq_CannotUseTypeFiltersMultipleTimes=Non-redundant type filters (OfType, C# 'as' and VB 'TryCast') can only be used once per resource set. +ALinq_ExpressionCannotEndWithTypeAs=Unsupported expression '{0}' in '{1}' method. Expression cannot end with TypeAs. +ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3=The expression 'TypeAs' is not supported when MaxProtocolVersion is less than '3.0'. +ALinq_TypeAsArgumentNotEntityType=The type '{0}' is not an entity type. The target type for a TypeAs operator must be an entity type. +ALinq_InvalidSourceForAnyAll=The source parameter for the '{0}' method has to be either a navigation or a collection property. +ALinq_AnyAllNotSupportedInOrderBy=The method '{0}' is not supported by the 'orderby' query option. +ALinq_FormatQueryOptionNotSupported=The '$format' query option is not supported. Use the DataServiceContext.Format property to set the desired format. +ALinq_IllegalSystemQueryOption=Found the following illegal system token while building a projection or expansion path: '{0}' +ALinq_IllegalPathStructure=Found a projection as a non-leaf segment in an expand path. Please rephrase your query. The projected property was : '{0}' +ALinq_TypeTokenWithNoTrailingNavProp=Found an illegal type token '{0}' without a trailing navigation property. + +DSKAttribute_MustSpecifyAtleastOnePropertyName=DataServiceKey attribute must specify at least one property name. + +DataServiceCollection_LoadRequiresTargetCollectionObserved=Target collection for the Load operation must have an associated DataServiceContext. +DataServiceCollection_CannotStopTrackingChildCollection=The tracking of DataServiceCollection can not be stopped for child collections. +DataServiceCollection_OperationForTrackedOnly=This operation is only supported on collections that are being tracked. +DataServiceCollection_CannotDetermineContextFromItems=The DataServiceContext to which the DataServiceCollection instance belongs could not be determined. The DataServiceContext must either be supplied in the DataServiceCollection constructor or be used to create the DataServiceQuery or QueryOperationResponse object that is the source of the items in the DataServiceCollection. +DataServiceCollection_InsertIntoTrackedButNotLoadedCollection=An item could not be added to the collection. When items in a DataServiceCollection are tracked by the DataServiceContext, new items cannot be added before items have been loaded into the collection. +DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime=A previous LoadAsync operation has not yet completed. You cannot call the LoadAsync method on the DataServiceCollection again until the previous operation has completed. +DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity=The LoadAsync method cannot be called when the DataServiceCollection is not a child collection of a related entity. +DataServiceCollection_LoadAsyncRequiresDataServiceQuery=Only a typed DataServiceQuery object can be supplied when calling the LoadAsync method on DataServiceCollection. + +DataBinding_DataServiceCollectionArgumentMustHaveEntityType=The DataServiceCollection to be tracked must contain entity typed elements with at least one key property. The element type '{0}' does not have any key property. +DataBinding_CollectionPropertySetterValueHasObserver=Setting an instance of DataServiceCollection to an entity property is disallowed if the instance is already being tracked. Error occurred on property '{0}' for entity type '{1}'. +DataBinding_DataServiceCollectionChangedUnknownActionCollection=Unexpected action '{0}' on the OnCollectionChanged event raised by DataServiceCollection. +DataBinding_CollectionChangedUnknownActionCollection=Unexpected action '{0}' on the OnCollectionChanged event raised by a collection object of type '{1}'. +DataBinding_BindingOperation_DetachedSource=Add/Update/Delete operation cannot be performed on a child entity, if the parent entity is already detached. +DataBinding_BindingOperation_ArrayItemNull=Null values are disallowed during '{0}' operations on DataServiceCollection. +DataBinding_BindingOperation_ArrayItemNotEntity=A value provided during '{0}' operation on DataServiceCollection is not of an entity type with key. +DataBinding_Util_UnknownEntitySetName=Entity set name has not been provided for an entity of type '{0}'. +DataBinding_EntityAlreadyInCollection=An attempt was made to add entity of type '{0}' to a collection in which the same entity already exists. +DataBinding_NotifyPropertyChangedNotImpl=An attempt to track an entity or complex type failed because the entity or complex type '{0}' does not implement the INotifyPropertyChanged interface. +DataBinding_NotifyCollectionChangedNotImpl=An attempt to track an entity or complex type failed because the entity or complex type contains a collection property of type '{0}' that does not implement the INotifyCollectionChanged interface. +DataBinding_ComplexObjectAssociatedWithMultipleEntities=An attempt to track a complex object of type '{0}' failed because the complex object is already being tracked. +DataBinding_CollectionAssociatedWithMultipleEntities=An attempt to track a collection object of type '{0}' failed because the collection object is already being tracked. + +AtomParser_SingleEntry_NoneFound=Expected exactly one Atom entry in the response from the server, but none was found. +AtomParser_SingleEntry_MultipleFound=Expected exactly one Atom entry in the response from the server, but more than one was found. +AtomParser_SingleEntry_ExpectedFeedOrEntry=Expected an Atom feed or entry in the response from the server, but found an unexpected element instead. + +AtomMaterializer_CannotAssignNull=The null value from property '{0}' cannot be assigned to a type '{1}'. +AtomMaterializer_EntryIntoCollectionMismatch=An entry of type '{0}' cannot be added to a collection that contains instances of type '{1}'. This may occur when an existing entry of a different type has the same identity value or when the same entity is projected into two different types in a single query. +AtomMaterializer_EntryToAccessIsNull=An entry returned by the navigation property '{0}' is null and cannot be initialized. You should check for a null value before accessing this property. +AtomMaterializer_EntryToInitializeIsNull=An entry that contains the data required to create an instance of type '{0}' is null and cannot be initialized. You should check for a null value before accessing this entry. +AtomMaterializer_ProjectEntityTypeMismatch=An entity of type '{0}' cannot be projected because there is already an instance of type '{1}' for '{2}'. +AtomMaterializer_PropertyMissing=The expected property '{0}' could not be found while processing an entry. Check for null before accessing this property. +AtomMaterializer_PropertyNotExpectedEntry=Property '{0}' is not an entity. +AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities=A DataServiceCollection can only contain entity types. Primitive and complex types cannot be contained by this kind of collection. +AtomMaterializer_NoParameterlessCtorForCollectionProperty=Collection property '{0}' cannot be created because the type '{1}' does not have a public parameterless constructor. +AtomMaterializer_InvalidCollectionItem=The element '{0}' is not a valid collection item. The name of the collection item element must be 'element' and must belong to the 'http://docs.oasis-open.org/odata/ns/data' namespace. +AtomMaterializer_InvalidEntityType=There is a type mismatch between the client and the service. Type '{0}' is an entity type, but the type in the response payload does not represent an entity type. Please ensure that types defined on the client match the data model of the service, or update the service reference on the client. +AtomMaterializer_InvalidNonEntityType=There is a type mismatch between the client and the service. Type '{0}' is not an entity type, but the type in the response payload represents an entity type. Please ensure that types defined on the client match the data model of the service, or update the service reference on the client. +AtomMaterializer_CollectionExpectedCollection=Materialization of top level collection expected ICollection<>, but actual type was {0}. +AtomMaterializer_InvalidResponsePayload=The response payload is a not a valid response payload. Please make sure that the top level element is a valid Atom or JSON element or belongs to '{0}' namespace. +AtomMaterializer_InvalidContentTypeEncountered=The response content type '{0}' is not currently supported. +AtomMaterializer_MaterializationTypeError=Cannot materialize the results into a collection type '{0}' because it does not have a parameterless constructor. +AtomMaterializer_ResetAfterEnumeratorCreationError=Reset should never be called for collection reader in an internal enumerable. +AtomMaterializer_TypeShouldBeCollectionError=Cannot materialize a collection of a primitives or complex without the type '{0}' being a collection. + +Serializer_LoopsNotAllowedInComplexTypes=A circular loop was detected while serializing the property '{0}'. You must make sure that loops are not present in properties that return a collection or complex type. +Serializer_LoopsNotAllowedInNonPropertyComplexTypes=A circular loop was detected while serializing the complex type '{0}'. You must make sure that loops are not present in a collection or a complex type. +Serializer_InvalidCollectionParamterItemType=The operation parameter named '{0}' has a collection item of Edm type kind '{1}'. A collection item must be either a primitive type or a complex Edm type kind. +Serializer_NullCollectionParamterItemValue=The operation parameter named '{0}' has a null collection item. The items of a collection must not be null. +Serializer_InvalidParameterType=The operation parameter named '{0}' was of Edm type kind '{1}'. An operation parameter must be either a primitive type, a complex type or a collection of primitive or complex types. +Serializer_UriDoesNotContainParameterAlias=The parameter alias '{0}' was not present in the request URI. All parameters passed as alias must be present in the request URI. +Serializer_InvalidEnumMemberValue=The enum type '{0}' has no member named '{1}'. + +; NOTE: error message copied from Silverlight because the portable library requires this +DataServiceQuery_EnumerationNotSupported=This target framework does not enable you to directly enumerate over a data service query. This is because enumeration automatically sends a synchronous request to the data service. Because this framework only supports asynchronous operations, you must instead call the BeginExecute and EndExecute methods to obtain a query result that supports enumeration. + +; NOTE: Moved over to common as this is used in the portable library +Context_SendingRequestEventArgsNotHttp=Only instances of HttpWebRequest are currently allowed for this property. Other subtypes of WebRequest are not supported. + +; NOTE: these error messages are copied from ODL because they appear in shared source files. +General_InternalError=An internal error '{0}' occurred. + +ODataMetadataBuilder_MissingEntitySetUri=The entity set '{0}' doesn't have the 'OData.EntitySetUri' annotation. This annotation is required. +ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix=The entity set '{0}' has a URI '{1}' which has no path segments. An entity set URI suffix cannot be appended to a URI without path segments. +ODataMetadataBuilder_MissingEntityInstanceUri=Neither the 'OData.EntityInstanceUri' nor the 'OData.EntitySetUriSuffix' annotation was found for entity set '{0}'. One of these annotations is required. + +EdmValueUtils_UnsupportedPrimitiveType=The type '{0}' was found for a primitive value. In OData, the type '{0}' is not a supported primitive type. +EdmValueUtils_IncorrectPrimitiveTypeKind=Incompatible primitive type kinds were found. The type '{0}' was found to be of kind '{2}' instead of the expected kind '{1}'. +EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName=Incompatible primitive type kinds were found. Found type kind '{0}' instead of the expected kind '{1}'. +EdmValueUtils_CannotConvertTypeToClrValue=A value with primitive kind '{0}' cannot be converted into a primitive object value. + +; NOTE: these error messages are copied from EdmLib because they appear in shared source files. +ValueParser_InvalidDuration=The value '{0}' is not a valid duration value. + +; Note: The below list of error messages are common to the ODataLib, EdmLib, Spatial, Server and Client projects. +PlatformHelper_DateTimeOffsetMustContainTimeZone=The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information. +;END diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Desktop.txt b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Desktop.txt new file mode 100644 index 0000000..44d2aa6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.Desktop.txt @@ -0,0 +1,14 @@ + +; NOTE: don't use \", use ' instead +; NOTE: don't use #, use ; instead for comments +; NOTE: leave the [strings] alone + +; See ResourceManager documentation and the ResGen tool. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error Messages + +DataServiceRequest_FailGetCount=Failed to get the count value from the server. + +Context_ExecuteExpectedVoidResponse=Execute overload for void service operations and actions received a non-void response from the server. +;END diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.NetStandard.VS2017.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.NetStandard.VS2017.csproj new file mode 100644 index 0000000..be30d0d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.NetStandard.VS2017.csproj @@ -0,0 +1,240 @@ + + + + Microsoft.OData.Client + Microsoft.OData.Client + netstandard1.1 + 1.6.0 + + false + ..\..\..\tools\StrongNamePublicKeys\35MSSharedLib1024.snk + True + True + $(AssemblyName).xml + true + $(DefineConstants);ODATA_CLIENT;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE;SUPPRESS_SECURITY_RULES + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + + + + + Microsoft.OData.Client + true + true + internal + true + Microsoft.OData.Client.TextRes + skip + + + + + + false + + + false + + + + + + TextTemplatingFileGenerator + ALinq.cs + + + + + + + + + + BuildTextFile;$(GenerateTextStringResourcesDependsOn) + + + + + + + + + + TextTemplatingFileGenerator + Microsoft.OData.Client.Portable.cs + + + True + True + Microsoft.OData.Client.Portable.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Client.Portable.cs + + + True + True + Parameterized.Microsoft.OData.Client.Portable.tt + true + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.cs new file mode 100644 index 0000000..272452d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.cs @@ -0,0 +1,344 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client { + using System; + using System.Globalization; + using System.Reflection; + using System.Resources; +#if !PORTABLELIB + using System.Security.Permissions; +#endif + using System.Text; + using System.Threading; + + /// + /// AutoGenerated resource class. Usage: + /// + /// string s = TextRes.GetString(TextRes.MyIdenfitier); + /// + internal sealed class TextRes { + internal const string Batch_ExpectedContentType = "Batch_ExpectedContentType"; + internal const string Batch_ExpectedResponse = "Batch_ExpectedResponse"; + internal const string Batch_IncompleteResponseCount = "Batch_IncompleteResponseCount"; + internal const string Batch_UnexpectedContent = "Batch_UnexpectedContent"; + internal const string Context_BaseUri = "Context_BaseUri"; + internal const string Context_BaseUriRequired = "Context_BaseUriRequired"; + internal const string Context_ResolveReturnedInvalidUri = "Context_ResolveReturnedInvalidUri"; + internal const string Context_RequestUriIsRelativeBaseUriRequired = "Context_RequestUriIsRelativeBaseUriRequired"; + internal const string Context_ResolveEntitySetOrBaseUriRequired = "Context_ResolveEntitySetOrBaseUriRequired"; + internal const string Context_CannotConvertKey = "Context_CannotConvertKey"; + internal const string Context_TrackingExpectsAbsoluteUri = "Context_TrackingExpectsAbsoluteUri"; + internal const string Context_LocationHeaderExpectsAbsoluteUri = "Context_LocationHeaderExpectsAbsoluteUri"; + internal const string Context_LinkResourceInsertFailure = "Context_LinkResourceInsertFailure"; + internal const string Context_InternalError = "Context_InternalError"; + internal const string Context_BatchExecuteError = "Context_BatchExecuteError"; + internal const string Context_EntitySetName = "Context_EntitySetName"; + internal const string Context_BatchNotSupportedForNamedStreams = "Context_BatchNotSupportedForNamedStreams"; + internal const string Context_SetSaveStreamWithoutNamedStreamEditLink = "Context_SetSaveStreamWithoutNamedStreamEditLink"; + internal const string Content_EntityWithoutKey = "Content_EntityWithoutKey"; + internal const string Content_EntityIsNotEntityType = "Content_EntityIsNotEntityType"; + internal const string Context_EntityNotContained = "Context_EntityNotContained"; + internal const string Context_EntityAlreadyContained = "Context_EntityAlreadyContained"; + internal const string Context_DifferentEntityAlreadyContained = "Context_DifferentEntityAlreadyContained"; + internal const string Context_DidNotOriginateAsync = "Context_DidNotOriginateAsync"; + internal const string Context_AsyncAlreadyDone = "Context_AsyncAlreadyDone"; + internal const string Context_OperationCanceled = "Context_OperationCanceled"; + internal const string Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX = "Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX"; + internal const string Context_NoLoadWithInsertEnd = "Context_NoLoadWithInsertEnd"; + internal const string Context_NoRelationWithInsertEnd = "Context_NoRelationWithInsertEnd"; + internal const string Context_NoRelationWithDeleteEnd = "Context_NoRelationWithDeleteEnd"; + internal const string Context_RelationAlreadyContained = "Context_RelationAlreadyContained"; + internal const string Context_RelationNotRefOrCollection = "Context_RelationNotRefOrCollection"; + internal const string Context_AddLinkCollectionOnly = "Context_AddLinkCollectionOnly"; + internal const string Context_AddRelatedObjectCollectionOnly = "Context_AddRelatedObjectCollectionOnly"; + internal const string Context_AddRelatedObjectSourceDeleted = "Context_AddRelatedObjectSourceDeleted"; + internal const string Context_UpdateRelatedObjectNonCollectionOnly = "Context_UpdateRelatedObjectNonCollectionOnly"; + internal const string Context_SetLinkReferenceOnly = "Context_SetLinkReferenceOnly"; + internal const string Context_NoContentTypeForMediaLink = "Context_NoContentTypeForMediaLink"; + internal const string Context_BatchNotSupportedForMediaLink = "Context_BatchNotSupportedForMediaLink"; + internal const string Context_UnexpectedZeroRawRead = "Context_UnexpectedZeroRawRead"; + internal const string Context_VersionNotSupported = "Context_VersionNotSupported"; + internal const string Context_ResponseVersionIsBiggerThanProtocolVersion = "Context_ResponseVersionIsBiggerThanProtocolVersion"; + internal const string Context_RequestVersionIsBiggerThanProtocolVersion = "Context_RequestVersionIsBiggerThanProtocolVersion"; + internal const string Context_ChildResourceExists = "Context_ChildResourceExists"; + internal const string Context_ContentTypeRequiredForNamedStream = "Context_ContentTypeRequiredForNamedStream"; + internal const string Context_EntityNotMediaLinkEntry = "Context_EntityNotMediaLinkEntry"; + internal const string Context_MLEWithoutSaveStream = "Context_MLEWithoutSaveStream"; + internal const string Context_SetSaveStreamOnMediaEntryProperty = "Context_SetSaveStreamOnMediaEntryProperty"; + internal const string Context_SetSaveStreamWithoutEditMediaLink = "Context_SetSaveStreamWithoutEditMediaLink"; + internal const string Context_SetSaveStreamOnInvalidEntityState = "Context_SetSaveStreamOnInvalidEntityState"; + internal const string Context_EntityDoesNotContainNamedStream = "Context_EntityDoesNotContainNamedStream"; + internal const string Context_MissingSelfAndEditLinkForNamedStream = "Context_MissingSelfAndEditLinkForNamedStream"; + internal const string Context_BothLocationAndIdMustBeSpecified = "Context_BothLocationAndIdMustBeSpecified"; + internal const string Context_BodyOperationParametersNotAllowedWithGet = "Context_BodyOperationParametersNotAllowedWithGet"; + internal const string Context_MissingOperationParameterName = "Context_MissingOperationParameterName"; + internal const string Context_DuplicateUriOperationParameterName = "Context_DuplicateUriOperationParameterName"; + internal const string Context_DuplicateBodyOperationParameterName = "Context_DuplicateBodyOperationParameterName"; + internal const string Context_NullKeysAreNotSupported = "Context_NullKeysAreNotSupported"; + internal const string Context_ExecuteExpectsGetOrPostOrDelete = "Context_ExecuteExpectsGetOrPostOrDelete"; + internal const string Context_EndExecuteExpectedVoidResponse = "Context_EndExecuteExpectedVoidResponse"; + internal const string Context_NullElementInOperationParameterArray = "Context_NullElementInOperationParameterArray"; + internal const string Context_EntityMetadataBuilderIsRequired = "Context_EntityMetadataBuilderIsRequired"; + internal const string Context_CannotChangeStateToAdded = "Context_CannotChangeStateToAdded"; + internal const string Context_CannotChangeStateToModifiedIfNotUnchanged = "Context_CannotChangeStateToModifiedIfNotUnchanged"; + internal const string Context_CannotChangeStateIfAdded = "Context_CannotChangeStateIfAdded"; + internal const string Context_OnMessageCreatingReturningNull = "Context_OnMessageCreatingReturningNull"; + internal const string Context_SendingRequest_InvalidWhenUsingOnMessageCreating = "Context_SendingRequest_InvalidWhenUsingOnMessageCreating"; + internal const string Context_MustBeUsedWith = "Context_MustBeUsedWith"; + internal const string DataServiceClientFormat_LoadServiceModelRequired = "DataServiceClientFormat_LoadServiceModelRequired"; + internal const string DataServiceClientFormat_ValidServiceModelRequiredForJson = "DataServiceClientFormat_ValidServiceModelRequiredForJson"; + internal const string Collection_NullCollectionReference = "Collection_NullCollectionReference"; + internal const string ClientType_MissingOpenProperty = "ClientType_MissingOpenProperty"; + internal const string Clienttype_MultipleOpenProperty = "Clienttype_MultipleOpenProperty"; + internal const string ClientType_MissingProperty = "ClientType_MissingProperty"; + internal const string ClientType_KeysMustBeSimpleTypes = "ClientType_KeysMustBeSimpleTypes"; + internal const string ClientType_KeysOnDifferentDeclaredType = "ClientType_KeysOnDifferentDeclaredType"; + internal const string ClientType_MissingMimeTypeProperty = "ClientType_MissingMimeTypeProperty"; + internal const string ClientType_MissingMimeTypeDataProperty = "ClientType_MissingMimeTypeDataProperty"; + internal const string ClientType_MissingMediaEntryProperty = "ClientType_MissingMediaEntryProperty"; + internal const string ClientType_NoSettableFields = "ClientType_NoSettableFields"; + internal const string ClientType_MultipleImplementationNotSupported = "ClientType_MultipleImplementationNotSupported"; + internal const string ClientType_NullOpenProperties = "ClientType_NullOpenProperties"; + internal const string ClientType_Ambiguous = "ClientType_Ambiguous"; + internal const string ClientType_UnsupportedType = "ClientType_UnsupportedType"; + internal const string ClientType_CollectionOfCollectionNotSupported = "ClientType_CollectionOfCollectionNotSupported"; + internal const string ClientType_MultipleTypesWithSameName = "ClientType_MultipleTypesWithSameName"; + internal const string WebUtil_TypeMismatchInCollection = "WebUtil_TypeMismatchInCollection"; + internal const string WebUtil_TypeMismatchInNonPropertyCollection = "WebUtil_TypeMismatchInNonPropertyCollection"; + internal const string ClientTypeCache_NonEntityTypeCannotContainEntityProperties = "ClientTypeCache_NonEntityTypeCannotContainEntityProperties"; + internal const string DataServiceException_GeneralError = "DataServiceException_GeneralError"; + internal const string Deserialize_GetEnumerator = "Deserialize_GetEnumerator"; + internal const string Deserialize_Current = "Deserialize_Current"; + internal const string Deserialize_MixedTextWithComment = "Deserialize_MixedTextWithComment"; + internal const string Deserialize_ExpectingSimpleValue = "Deserialize_ExpectingSimpleValue"; + internal const string Deserialize_MismatchAtomLinkLocalSimple = "Deserialize_MismatchAtomLinkLocalSimple"; + internal const string Deserialize_MismatchAtomLinkFeedPropertyNotCollection = "Deserialize_MismatchAtomLinkFeedPropertyNotCollection"; + internal const string Deserialize_MismatchAtomLinkEntryPropertyIsCollection = "Deserialize_MismatchAtomLinkEntryPropertyIsCollection"; + internal const string Deserialize_NoLocationHeader = "Deserialize_NoLocationHeader"; + internal const string Deserialize_ServerException = "Deserialize_ServerException"; + internal const string Deserialize_MissingIdElement = "Deserialize_MissingIdElement"; + internal const string Collection_NullCollectionNotSupported = "Collection_NullCollectionNotSupported"; + internal const string Collection_NullNonPropertyCollectionNotSupported = "Collection_NullNonPropertyCollectionNotSupported"; + internal const string Collection_NullCollectionItemsNotSupported = "Collection_NullCollectionItemsNotSupported"; + internal const string Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed = "Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed"; + internal const string Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed = "Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed"; + internal const string EntityDescriptor_MissingSelfEditLink = "EntityDescriptor_MissingSelfEditLink"; + internal const string HttpProcessUtility_ContentTypeMissing = "HttpProcessUtility_ContentTypeMissing"; + internal const string HttpProcessUtility_MediaTypeMissingValue = "HttpProcessUtility_MediaTypeMissingValue"; + internal const string HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter = "HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter"; + internal const string HttpProcessUtility_MediaTypeRequiresSlash = "HttpProcessUtility_MediaTypeRequiresSlash"; + internal const string HttpProcessUtility_MediaTypeRequiresSubType = "HttpProcessUtility_MediaTypeRequiresSubType"; + internal const string HttpProcessUtility_MediaTypeUnspecified = "HttpProcessUtility_MediaTypeUnspecified"; + internal const string HttpProcessUtility_EncodingNotSupported = "HttpProcessUtility_EncodingNotSupported"; + internal const string HttpProcessUtility_EscapeCharWithoutQuotes = "HttpProcessUtility_EscapeCharWithoutQuotes"; + internal const string HttpProcessUtility_EscapeCharAtEnd = "HttpProcessUtility_EscapeCharAtEnd"; + internal const string HttpProcessUtility_ClosingQuoteNotFound = "HttpProcessUtility_ClosingQuoteNotFound"; + internal const string MaterializeFromAtom_CountNotPresent = "MaterializeFromAtom_CountNotPresent"; + internal const string MaterializeFromAtom_TopLevelLinkNotAvailable = "MaterializeFromAtom_TopLevelLinkNotAvailable"; + internal const string MaterializeFromAtom_CollectionKeyNotPresentInLinkTable = "MaterializeFromAtom_CollectionKeyNotPresentInLinkTable"; + internal const string MaterializeFromAtom_GetNestLinkForFlatCollection = "MaterializeFromAtom_GetNestLinkForFlatCollection"; + internal const string ODataRequestMessage_GetStreamMethodNotSupported = "ODataRequestMessage_GetStreamMethodNotSupported"; + internal const string Util_EmptyString = "Util_EmptyString"; + internal const string Util_EmptyArray = "Util_EmptyArray"; + internal const string Util_NullArrayElement = "Util_NullArrayElement"; + internal const string ALinq_UnsupportedExpression = "ALinq_UnsupportedExpression"; + internal const string ALinq_CouldNotConvert = "ALinq_CouldNotConvert"; + internal const string ALinq_MethodNotSupported = "ALinq_MethodNotSupported"; + internal const string ALinq_UnaryNotSupported = "ALinq_UnaryNotSupported"; + internal const string ALinq_BinaryNotSupported = "ALinq_BinaryNotSupported"; + internal const string ALinq_ConstantNotSupported = "ALinq_ConstantNotSupported"; + internal const string ALinq_TypeBinaryNotSupported = "ALinq_TypeBinaryNotSupported"; + internal const string ALinq_ConditionalNotSupported = "ALinq_ConditionalNotSupported"; + internal const string ALinq_ParameterNotSupported = "ALinq_ParameterNotSupported"; + internal const string ALinq_MemberAccessNotSupported = "ALinq_MemberAccessNotSupported"; + internal const string ALinq_LambdaNotSupported = "ALinq_LambdaNotSupported"; + internal const string ALinq_NewNotSupported = "ALinq_NewNotSupported"; + internal const string ALinq_MemberInitNotSupported = "ALinq_MemberInitNotSupported"; + internal const string ALinq_ListInitNotSupported = "ALinq_ListInitNotSupported"; + internal const string ALinq_NewArrayNotSupported = "ALinq_NewArrayNotSupported"; + internal const string ALinq_InvocationNotSupported = "ALinq_InvocationNotSupported"; + internal const string ALinq_QueryOptionsOnlyAllowedOnLeafNodes = "ALinq_QueryOptionsOnlyAllowedOnLeafNodes"; + internal const string ALinq_CantExpand = "ALinq_CantExpand"; + internal const string ALinq_CantCastToUnsupportedPrimitive = "ALinq_CantCastToUnsupportedPrimitive"; + internal const string ALinq_CantNavigateWithoutKeyPredicate = "ALinq_CantNavigateWithoutKeyPredicate"; + internal const string ALinq_CanOnlyApplyOneKeyPredicate = "ALinq_CanOnlyApplyOneKeyPredicate"; + internal const string ALinq_CantTranslateExpression = "ALinq_CantTranslateExpression"; + internal const string ALinq_TranslationError = "ALinq_TranslationError"; + internal const string ALinq_CantAddQueryOption = "ALinq_CantAddQueryOption"; + internal const string ALinq_CantAddDuplicateQueryOption = "ALinq_CantAddDuplicateQueryOption"; + internal const string ALinq_CantAddAstoriaQueryOption = "ALinq_CantAddAstoriaQueryOption"; + internal const string ALinq_CantAddQueryOptionStartingWithDollarSign = "ALinq_CantAddQueryOptionStartingWithDollarSign"; + internal const string ALinq_CantReferToPublicField = "ALinq_CantReferToPublicField"; + internal const string ALinq_QueryOptionsOnlyAllowedOnSingletons = "ALinq_QueryOptionsOnlyAllowedOnSingletons"; + internal const string ALinq_QueryOptionOutOfOrder = "ALinq_QueryOptionOutOfOrder"; + internal const string ALinq_CannotAddCountOption = "ALinq_CannotAddCountOption"; + internal const string ALinq_CannotAddCountOptionConflict = "ALinq_CannotAddCountOptionConflict"; + internal const string ALinq_ProjectionOnlyAllowedOnLeafNodes = "ALinq_ProjectionOnlyAllowedOnLeafNodes"; + internal const string ALinq_ProjectionCanOnlyHaveOneProjection = "ALinq_ProjectionCanOnlyHaveOneProjection"; + internal const string ALinq_ProjectionMemberAssignmentMismatch = "ALinq_ProjectionMemberAssignmentMismatch"; + internal const string ALinq_InvalidExpressionInNavigationPath = "ALinq_InvalidExpressionInNavigationPath"; + internal const string ALinq_ExpressionNotSupportedInProjectionToEntity = "ALinq_ExpressionNotSupportedInProjectionToEntity"; + internal const string ALinq_ExpressionNotSupportedInProjection = "ALinq_ExpressionNotSupportedInProjection"; + internal const string ALinq_CannotConstructKnownEntityTypes = "ALinq_CannotConstructKnownEntityTypes"; + internal const string ALinq_CannotCreateConstantEntity = "ALinq_CannotCreateConstantEntity"; + internal const string ALinq_PropertyNamesMustMatchInProjections = "ALinq_PropertyNamesMustMatchInProjections"; + internal const string ALinq_CanOnlyProjectTheLeaf = "ALinq_CanOnlyProjectTheLeaf"; + internal const string ALinq_CannotProjectWithExplicitExpansion = "ALinq_CannotProjectWithExplicitExpansion"; + internal const string ALinq_CollectionPropertyNotSupportedInOrderBy = "ALinq_CollectionPropertyNotSupportedInOrderBy"; + internal const string ALinq_CollectionPropertyNotSupportedInWhere = "ALinq_CollectionPropertyNotSupportedInWhere"; + internal const string ALinq_CollectionMemberAccessNotSupportedInNavigation = "ALinq_CollectionMemberAccessNotSupportedInNavigation"; + internal const string ALinq_LinkPropertyNotSupportedInExpression = "ALinq_LinkPropertyNotSupportedInExpression"; + internal const string ALinq_OfTypeArgumentNotAvailable = "ALinq_OfTypeArgumentNotAvailable"; + internal const string ALinq_CannotUseTypeFiltersMultipleTimes = "ALinq_CannotUseTypeFiltersMultipleTimes"; + internal const string ALinq_ExpressionCannotEndWithTypeAs = "ALinq_ExpressionCannotEndWithTypeAs"; + internal const string ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3 = "ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3"; + internal const string ALinq_TypeAsArgumentNotEntityType = "ALinq_TypeAsArgumentNotEntityType"; + internal const string ALinq_InvalidSourceForAnyAll = "ALinq_InvalidSourceForAnyAll"; + internal const string ALinq_AnyAllNotSupportedInOrderBy = "ALinq_AnyAllNotSupportedInOrderBy"; + internal const string ALinq_FormatQueryOptionNotSupported = "ALinq_FormatQueryOptionNotSupported"; + internal const string ALinq_IllegalSystemQueryOption = "ALinq_IllegalSystemQueryOption"; + internal const string ALinq_IllegalPathStructure = "ALinq_IllegalPathStructure"; + internal const string ALinq_TypeTokenWithNoTrailingNavProp = "ALinq_TypeTokenWithNoTrailingNavProp"; + internal const string DSKAttribute_MustSpecifyAtleastOnePropertyName = "DSKAttribute_MustSpecifyAtleastOnePropertyName"; + internal const string DataServiceCollection_LoadRequiresTargetCollectionObserved = "DataServiceCollection_LoadRequiresTargetCollectionObserved"; + internal const string DataServiceCollection_CannotStopTrackingChildCollection = "DataServiceCollection_CannotStopTrackingChildCollection"; + internal const string DataServiceCollection_OperationForTrackedOnly = "DataServiceCollection_OperationForTrackedOnly"; + internal const string DataServiceCollection_CannotDetermineContextFromItems = "DataServiceCollection_CannotDetermineContextFromItems"; + internal const string DataServiceCollection_InsertIntoTrackedButNotLoadedCollection = "DataServiceCollection_InsertIntoTrackedButNotLoadedCollection"; + internal const string DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime = "DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime"; + internal const string DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity = "DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity"; + internal const string DataServiceCollection_LoadAsyncRequiresDataServiceQuery = "DataServiceCollection_LoadAsyncRequiresDataServiceQuery"; + internal const string DataBinding_DataServiceCollectionArgumentMustHaveEntityType = "DataBinding_DataServiceCollectionArgumentMustHaveEntityType"; + internal const string DataBinding_CollectionPropertySetterValueHasObserver = "DataBinding_CollectionPropertySetterValueHasObserver"; + internal const string DataBinding_DataServiceCollectionChangedUnknownActionCollection = "DataBinding_DataServiceCollectionChangedUnknownActionCollection"; + internal const string DataBinding_CollectionChangedUnknownActionCollection = "DataBinding_CollectionChangedUnknownActionCollection"; + internal const string DataBinding_BindingOperation_DetachedSource = "DataBinding_BindingOperation_DetachedSource"; + internal const string DataBinding_BindingOperation_ArrayItemNull = "DataBinding_BindingOperation_ArrayItemNull"; + internal const string DataBinding_BindingOperation_ArrayItemNotEntity = "DataBinding_BindingOperation_ArrayItemNotEntity"; + internal const string DataBinding_Util_UnknownEntitySetName = "DataBinding_Util_UnknownEntitySetName"; + internal const string DataBinding_EntityAlreadyInCollection = "DataBinding_EntityAlreadyInCollection"; + internal const string DataBinding_NotifyPropertyChangedNotImpl = "DataBinding_NotifyPropertyChangedNotImpl"; + internal const string DataBinding_NotifyCollectionChangedNotImpl = "DataBinding_NotifyCollectionChangedNotImpl"; + internal const string DataBinding_ComplexObjectAssociatedWithMultipleEntities = "DataBinding_ComplexObjectAssociatedWithMultipleEntities"; + internal const string DataBinding_CollectionAssociatedWithMultipleEntities = "DataBinding_CollectionAssociatedWithMultipleEntities"; + internal const string AtomParser_SingleEntry_NoneFound = "AtomParser_SingleEntry_NoneFound"; + internal const string AtomParser_SingleEntry_MultipleFound = "AtomParser_SingleEntry_MultipleFound"; + internal const string AtomParser_SingleEntry_ExpectedFeedOrEntry = "AtomParser_SingleEntry_ExpectedFeedOrEntry"; + internal const string AtomMaterializer_CannotAssignNull = "AtomMaterializer_CannotAssignNull"; + internal const string AtomMaterializer_EntryIntoCollectionMismatch = "AtomMaterializer_EntryIntoCollectionMismatch"; + internal const string AtomMaterializer_EntryToAccessIsNull = "AtomMaterializer_EntryToAccessIsNull"; + internal const string AtomMaterializer_EntryToInitializeIsNull = "AtomMaterializer_EntryToInitializeIsNull"; + internal const string AtomMaterializer_ProjectEntityTypeMismatch = "AtomMaterializer_ProjectEntityTypeMismatch"; + internal const string AtomMaterializer_PropertyMissing = "AtomMaterializer_PropertyMissing"; + internal const string AtomMaterializer_PropertyNotExpectedEntry = "AtomMaterializer_PropertyNotExpectedEntry"; + internal const string AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities = "AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities"; + internal const string AtomMaterializer_NoParameterlessCtorForCollectionProperty = "AtomMaterializer_NoParameterlessCtorForCollectionProperty"; + internal const string AtomMaterializer_InvalidCollectionItem = "AtomMaterializer_InvalidCollectionItem"; + internal const string AtomMaterializer_InvalidEntityType = "AtomMaterializer_InvalidEntityType"; + internal const string AtomMaterializer_InvalidNonEntityType = "AtomMaterializer_InvalidNonEntityType"; + internal const string AtomMaterializer_CollectionExpectedCollection = "AtomMaterializer_CollectionExpectedCollection"; + internal const string AtomMaterializer_InvalidResponsePayload = "AtomMaterializer_InvalidResponsePayload"; + internal const string AtomMaterializer_InvalidContentTypeEncountered = "AtomMaterializer_InvalidContentTypeEncountered"; + internal const string AtomMaterializer_MaterializationTypeError = "AtomMaterializer_MaterializationTypeError"; + internal const string AtomMaterializer_ResetAfterEnumeratorCreationError = "AtomMaterializer_ResetAfterEnumeratorCreationError"; + internal const string AtomMaterializer_TypeShouldBeCollectionError = "AtomMaterializer_TypeShouldBeCollectionError"; + internal const string Serializer_LoopsNotAllowedInComplexTypes = "Serializer_LoopsNotAllowedInComplexTypes"; + internal const string Serializer_LoopsNotAllowedInNonPropertyComplexTypes = "Serializer_LoopsNotAllowedInNonPropertyComplexTypes"; + internal const string Serializer_InvalidCollectionParamterItemType = "Serializer_InvalidCollectionParamterItemType"; + internal const string Serializer_NullCollectionParamterItemValue = "Serializer_NullCollectionParamterItemValue"; + internal const string Serializer_InvalidParameterType = "Serializer_InvalidParameterType"; + internal const string Serializer_UriDoesNotContainParameterAlias = "Serializer_UriDoesNotContainParameterAlias"; + internal const string Serializer_InvalidEnumMemberValue = "Serializer_InvalidEnumMemberValue"; + internal const string DataServiceQuery_EnumerationNotSupported = "DataServiceQuery_EnumerationNotSupported"; + internal const string Context_SendingRequestEventArgsNotHttp = "Context_SendingRequestEventArgsNotHttp"; + internal const string General_InternalError = "General_InternalError"; + internal const string ODataMetadataBuilder_MissingEntitySetUri = "ODataMetadataBuilder_MissingEntitySetUri"; + internal const string ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix = "ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix"; + internal const string ODataMetadataBuilder_MissingEntityInstanceUri = "ODataMetadataBuilder_MissingEntityInstanceUri"; + internal const string EdmValueUtils_UnsupportedPrimitiveType = "EdmValueUtils_UnsupportedPrimitiveType"; + internal const string EdmValueUtils_IncorrectPrimitiveTypeKind = "EdmValueUtils_IncorrectPrimitiveTypeKind"; + internal const string EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName = "EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName"; + internal const string EdmValueUtils_CannotConvertTypeToClrValue = "EdmValueUtils_CannotConvertTypeToClrValue"; + internal const string ValueParser_InvalidDuration = "ValueParser_InvalidDuration"; + internal const string PlatformHelper_DateTimeOffsetMustContainTimeZone = "PlatformHelper_DateTimeOffsetMustContainTimeZone"; + internal const string DataServiceRequest_FailGetCount = "DataServiceRequest_FailGetCount"; + internal const string Context_ExecuteExpectedVoidResponse = "Context_ExecuteExpectedVoidResponse"; + + static TextRes loader = null; + ResourceManager resources; + + internal TextRes() { +#if !PORTABLELIB + resources = new System.Resources.ResourceManager("Microsoft.OData.Client", this.GetType().Assembly); +#else + resources = new System.Resources.ResourceManager("Microsoft.OData.Client", this.GetType().GetTypeInfo().Assembly); +#endif + } + + private static TextRes GetLoader() { + if (loader == null) { + TextRes sr = new TextRes(); + Interlocked.CompareExchange(ref loader, sr, null); + } + return loader; + } + + private static CultureInfo Culture { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + public static ResourceManager Resources { + get { + return GetLoader().resources; + } + } + + public static string GetString(string name, params object[] args) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, TextRes.Culture); + + if (args != null && args.Length > 0) { + for (int i = 0; i < args.Length; i ++) { + String value = args[i] as String; + if (value != null && value.Length > 1024) { + args[i] = value.Substring(0, 1024 - 3) + "..."; + } + } + return String.Format(CultureInfo.CurrentCulture, res, args); + } + else { + return res; + } + } + + public static string GetString(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetString(name, TextRes.Culture); + } + + public static string GetString(string name, out bool usedFallback) { + // always false for this version of gensr + usedFallback = false; + return GetString(name); + } +#if !PORTABLELIB + public static object GetObject(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetObject(name, TextRes.Culture); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj new file mode 100644 index 0000000..4bf5e9c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj @@ -0,0 +1,503 @@ + + + + Microsoft.OData.Client + Library + + true + false + + $(DefineConstants);ODATA_CLIENT + $(AssemblyName).xml + + System + {D1567C63-4A0D-4E18-A14E-79699B9BFFFF} + true + v4.5 + + + + + + + + + + + + {7D921888-FE03-4C3F-80FE-2F624505461C} + Microsoft.OData.Edm + + + {5D921888-FE03-4C3F-40FE-2F624505461D} + Microsoft.Spatial + + + {989A83CC-B864-4A75-8BF3-5EDA99203A86} + Microsoft.OData.Core + + + + + + Microsoft.OData.Client + true + true + internal + true + Microsoft.OData.Client.TextRes + skip + + + + + + + EdmValueParser.cs + + + EdmValueWriter.cs + + + EdmValueUtils.cs + + + ODataResourceMetadataBuilder.cs + + + ODataUriBuilder.cs + + + Serialization\JsonSharedUtils.cs + + + SimpleLazy.cs + + + LiteralFormatter.cs + + + KeySerializer.cs + + + Metadata\EdmLibraryExtensions.cs + + + InternalErrorCodesCommon.cs + + + ALinq\UriParser\SyntacticAst\AggregateToken.cs + + + ALinq\UriParser\SyntacticAst\GroupByToken.cs + + + ALinq\UriParser\SyntacticAst\AllToken.cs + + + ALinq\UriParser\SyntacticAst\AnyToken.cs + + + ALinq\UriParser\SyntacticAst\BinaryOperatorToken.cs + + + ALinq\UriParser\SyntacticAst\ComputeToken.cs + + + ALinq\UriParser\SyntacticAst\ComputeExpressionToken.cs + + + ALinq\UriParser\SyntacticAst\CustomQueryOptionToken.cs + + + ALinq\UriParser\SyntacticAst\DottedIdentifierToken.cs + + + ALinq\UriParser\SyntacticAst\EndPathToken.cs + + + ALinq\UriParser\ExceptionUtils.cs + + + ALinq\UriParser\SyntacticAst\ExpandTermToken.cs + + + ALinq\UriParser\SyntacticAst\ExpandToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionCallToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionParameterAliasToken.cs + + + ALinq\UriParser\SyntacticAst\FunctionParameterToken.cs + + + ALinq\UriParser\SyntacticAst\InnerPathToken.cs + + + ALinq\UriParser\SyntacticAst\InToken.cs + + + ALinq\UriParser\Visitors\IPathSegmentTokenVisitor.cs + + + ALinq\UriParser\Visitors\ISyntacticTreeVisitor.cs + + + ALinq\UriParser\Visitors\SyntacticTreeVisitor.cs + + + ALinq\UriParser\SyntacticAst\LambdaToken.cs + + + ALinq\UriParser\SyntacticAst\LiteralToken.cs + + + ALinq\UriParser\NamedValue.cs + + + ALinq\UriParser\SyntacticAst\NonSystemToken.cs + + + ALinq\UriParser\SyntacticAst\OrderByToken.cs + + + ALinq\UriParser\SyntacticAst\PathSegmentToken.cs + + + ALinq\UriParser\SyntacticAst\PathToken.cs + + + ALinq\UriParser\SyntacticAst\QueryToken.cs + + + ALinq\UriParser\SyntacticAst\QueryTokenKind.cs + + + ALinq\UriParser\SyntacticAst\SelectExpandTermToken.cs + + + ALinq\UriParser\SyntacticAst\SelectTermToken.cs + + + ALinq\UriParser\SyntacticAst\RangeVariableToken.cs + + + ALinq\UriParser\ReadOnlyEnumerableForUriParser.cs + + + ALinq\UriParser\SyntacticAst\SelectToken.cs + + + ALinq\UriParser\SyntacticAst\StarToken.cs + + + ALinq\UriParser\SyntacticAst\SystemToken.cs + + + ALinq\UriParser\SyntacticAst\UnaryOperatorToken.cs + + + ALinq\UriParser\SyntacticAst\ApplyTransformationToken.cs + + + ALinq\UriParser\SyntacticAst\EntitySetAggregateToken.cs + + + ALinq\UriParser\SyntacticAst\AggregateTokenBase.cs + + + ALinq\UriParser\SyntacticAst\AggregateExpressionToken.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + true + + + true + + + + + true + + + + true + + + + true + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PlatformHelper.cs + + + + + false + + + false + + + + + + + + + + + + + + + + + + BuildTextFile;$(GenerateTextStringResourcesDependsOn) + + + + TextTemplatingFileGenerator + Microsoft.OData.Client.cs + + + True + True + Microsoft.OData.Client.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Client.cs + + + True + True + Parameterized.Microsoft.OData.Client.tt + true + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.tt new file mode 100644 index 0000000..2c42147 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Microsoft.OData.Client.tt @@ -0,0 +1,23 @@ +<#@ include file="..\..\tools\StringResourceGenerator\ResourceClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData.Client"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Client"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // Indicates whether to skip generation of string resource attributes. + public const bool SkipSRAttributes = true; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.OData.Client.Common.txt", + "Microsoft.OData.Client.Desktop.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/MimeTypePropertyAttribute.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MimeTypePropertyAttribute.cs new file mode 100644 index 0000000..22cb3c7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/MimeTypePropertyAttribute.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// + /// This attribute indicates another property in the same type that + /// contains the MIME type that should be used for the data contained + /// in the property this attribute is applied to. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class MimeTypePropertyAttribute : Attribute + { + /// The name of the property that contains the data + private readonly string dataPropertyName; + + /// The name of the property that contains the mime type + private readonly string mimeTypePropertyName; + + /// Creates a new instance of the MimeTypePropertyAttribute. + /// A string that contains the name of the new property attribute. + /// A string that contains the Mime type of the new property attribute. + public MimeTypePropertyAttribute(string dataPropertyName, string mimeTypePropertyName) + { + this.dataPropertyName = dataPropertyName; + this.mimeTypePropertyName = mimeTypePropertyName; + } + + /// Gets the name of the MimeTypePropertyAttribute. + /// A string that contains the name of the property attribute. + public string DataPropertyName + { + get { return this.dataPropertyName; } + } + + /// Gets the Mime type of the MimeTypePropertyAttribute + /// A string that contains the Mime type of the property attribute. + public string MimeTypePropertyName + { + get { return this.mimeTypePropertyName; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataAnnotatableExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataAnnotatableExtensions.cs new file mode 100644 index 0000000..b9be03b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataAnnotatableExtensions.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Runtime.CompilerServices; +using System.Diagnostics; + +namespace Microsoft.OData.Client +{ + internal static class ODataAnnotatableExtensions + { + public static void SetAnnotation(this ODataAnnotatable annotatable, T annotation) + where T : class + { + Debug.Assert(annotatable != null, "annotatable != null"); + Debug.Assert(annotation != null, "annotation != null"); + + InternalDictionary.SetAnnotation(annotatable, annotation); + } + + public static T GetAnnotation(this ODataAnnotatable annotatable) + where T : class + { + Debug.Assert(annotatable != null, "annotatable != null"); + + return InternalDictionary.GetAnnotation(annotatable); + } + + private static class InternalDictionary where T : class + { + private static readonly ConditionalWeakTable Dictionary = + new ConditionalWeakTable(); + + public static void SetAnnotation(ODataAnnotatable annotatable, T annotation) + { + Dictionary.Add(annotatable, annotation); + } + + public static T GetAnnotation(ODataAnnotatable annotatable) + { + T annotation; + if (Dictionary.TryGetValue(annotatable, out annotation)) + { + return annotation; + } + + return default(T); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageReadingHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageReadingHelper.cs new file mode 100644 index 0000000..f299a12 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageReadingHelper.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using Microsoft.OData; + using Microsoft.OData.Edm; + + /// + /// Helper class for creating ODataLib readers, settings, and other read-related classes based on an instance of . + /// + internal class ODataMessageReadingHelper + { + /// The current response info. + private readonly ResponseInfo responseInfo; + + /// + /// Initializes a new instance of the class. + /// + /// The response info. + internal ODataMessageReadingHelper(ResponseInfo responseInfo) + { + Debug.Assert(responseInfo != null, "responseInfo != null"); + this.responseInfo = responseInfo; + } + + /// + /// Create message reader settings for consuming responses. + /// + /// Newly created message reader settings. + internal ODataMessageReaderSettings CreateSettings() + { + ODataMessageReaderSettings settings = new ODataMessageReaderSettings(); + Func resolveWireTypeName = this.responseInfo.TypeResolver.ResolveWireTypeName; + if (this.responseInfo.Context.Format.ServiceModel != null) + { + resolveWireTypeName = null; + } + + settings.Validations &= ~(ValidationKinds.ThrowOnDuplicatePropertyNames | ValidationKinds.ThrowIfTypeConflictsWithMetadata); + settings.ClientCustomTypeResolver = resolveWireTypeName; + settings.BaseUri = this.responseInfo.BaseUriResolver.BaseUriOrNull; + settings.MaxProtocolVersion = CommonUtil.ConvertToODataVersion(this.responseInfo.MaxProtocolVersion); + + if (!this.responseInfo.ThrowOnUndeclaredPropertyForNonOpenType) + { + settings.Validations &= ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; + } + + // [#623] As client does not support DI currently, odata simplifiedoptions cannot be customize pre request. + // Now, we just change the global options. + // TODO: After finish the issue #623, need add the customize code of ODataAnnotationWithoutPrefix and KeyAsSegment for each request + CommonUtil.SetDefaultMessageQuotas(settings.MessageQuotas); + + this.responseInfo.ResponsePipeline.ExecuteReaderSettingsConfiguration(settings); + return settings; + } + + /// + /// Creates a new the reader for the given response message and settings. + /// + /// The response message. + /// The settings. + /// Newly created message reader. + internal ODataMessageReader CreateReader(IODataResponseMessage responseMessage, ODataMessageReaderSettings settings) + { + Debug.Assert(responseMessage != null, "responseMessage != null"); + Debug.Assert(settings != null, "settings != null"); + + DataServiceClientFormat.ValidateCanReadResponseFormat(responseMessage); + return new ODataMessageReader(responseMessage, settings, this.responseInfo.TypeResolver.ReaderModel); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageWritingHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageWritingHelper.cs new file mode 100644 index 0000000..b9a980c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataMessageWritingHelper.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Diagnostics; + using Microsoft.OData; + + /// + /// Helper class for creating ODataLib writers, settings, and other write-related classes based on an instance of . + /// + internal class ODataMessageWritingHelper + { + /// The current request info. + private readonly RequestInfo requestInfo; + + /// + /// Initializes a new instance of the class. + /// + /// The request info. + internal ODataMessageWritingHelper(RequestInfo requestInfo) + { + Debug.Assert(requestInfo != null, "requestInfo != null"); + this.requestInfo = requestInfo; + } + + /// + /// Create message writer settings for producing requests. + /// + /// if set to true indicates that this is a part of a batch request. + /// Whether to enable writing odata annotation without prefix. + /// Newly created message writer settings. + internal ODataMessageWriterSettings CreateSettings(bool isBatchPartRequest, bool enableWritingODataAnnotationWithoutPrefix) + { + ODataMessageWriterSettings writerSettings = new ODataMessageWriterSettings + { + EnableCharactersCheck = false, + + // For operations inside batch, we need to dispose the stream. For top level requests, + // we do not need to dispose the stream. Since for inner batch requests, the request + // message is an internal implementation of IODataRequestMessage in ODataLib, + // we can do this here. + EnableMessageStreamDisposal = isBatchPartRequest + }; + + // [#623] As client does not support DI currently, odata simplifiedoptions cannot be customize pre request. + // Now, we just change the global options. + // TODO: After finish the issue #623, need add the customize code of ODataAnnotationWithoutPrefix for each request + CommonUtil.SetDefaultMessageQuotas(writerSettings.MessageQuotas); + + this.requestInfo.Configurations.RequestPipeline.ExecuteWriterSettingsConfiguration(writerSettings); + + return writerSettings; + } + + /// + /// Creates a writer for the given request message and settings. + /// + /// The request message. + /// The writer settings. + /// true if the writer is intended to for a parameter payload, false otherwise. + /// Newly created writer. + internal ODataMessageWriter CreateWriter(IODataRequestMessage requestMessage, ODataMessageWriterSettings writerSettings, bool isParameterPayload) + { + Debug.Assert(requestMessage != null, "requestMessage != null"); + Debug.Assert(writerSettings != null, "writerSettings != null"); + + DataServiceClientFormat.ValidateCanWriteRequestFormat(requestMessage); + + // When calling Execute() to invoke an Action, the client doesn't support parsing the target url + // to determine which IEdmOperationImport to pass to the ODL writer. So the ODL writer is + // serializing the parameter payload without metadata. Setting the model to null so ODL doesn't + // do unecessary validations when writing without metadata. + var model = isParameterPayload ? null : this.requestInfo.Model; + return new ODataMessageWriter(requestMessage, writerSettings, model); + } + + /// + /// Creates a request message with the given arguments. + /// + /// The request message args. + /// Newly created request message. + internal ODataRequestMessageWrapper CreateRequestMessage(BuildingRequestEventArgs requestMessageArgs) + { + return ODataRequestMessageWrapper.CreateRequestMessageWrapper(requestMessageArgs, this.requestInfo); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataProtocolVersion.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataProtocolVersion.cs new file mode 100644 index 0000000..946f1a7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataProtocolVersion.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + /// Represents the versions of the Open Data Protocol (OData) that the data service may support. + public enum ODataProtocolVersion + { + /// Version 4 + V4, + + /// Version 4.01 + V401 + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataRequestMessageWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataRequestMessageWrapper.cs new file mode 100644 index 0000000..bc6a9a9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ODataRequestMessageWrapper.cs @@ -0,0 +1,683 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Net; + using Microsoft.OData; + + /// + /// IODataRequestMessage interface implementation. + /// + internal abstract class ODataRequestMessageWrapper + { + #region Private Fields + + /// Request Url. + private readonly DataServiceClientRequestMessage requestMessage; + + /// RequestInfo instance. + private readonly RequestInfo requestInfo; + +#if DEBUG + /// + /// Keeps track whether FireSendingRequest2 method has been called or not. In debug bits, + /// we need to make sure that no header is set after this method is called. + /// + private bool fireSendingRequest2MethodCalled; + + /// + /// List of headers that were set on the request message when SendingRequest event was fired. + /// This variable is used to track the headers before SendingRequest event is called, + /// and to verify no headers is added/modified/remove after sending request event is called. + /// + private HeaderCollection cachedRequestHeaders = null; +#endif + #endregion // Private Fields + + /// + /// Creates a new instance of ODataRequestMessage. This constructor is used for top level requests. + /// + /// RequestMessage that needs to be wrapped. + /// Request Info. + /// Descriptor for this request. + protected ODataRequestMessageWrapper(DataServiceClientRequestMessage requestMessage, RequestInfo requestInfo, Descriptor descriptor) + { + Debug.Assert(requestMessage != null, "requestMessage != null"); + Debug.Assert(requestInfo != null, "requestInfo != null"); + this.requestMessage = requestMessage; + this.requestInfo = requestInfo; + this.Descriptor = descriptor; + } + + #region Properties + + /// + /// Descriptor for this request; or null if there isn't one. + /// + internal Descriptor Descriptor { get; private set; } + + /// + /// Return the stream containing the request payload. + /// + internal abstract ContentStream CachedRequestStream + { + get; + } + + /// + /// Gets or sets a value that indicates whether to send request data in segments. + /// + internal bool SendChunked + { + set { this.requestMessage.SendChunked = value; } + } + + /// + /// Returns true if the message is part of the batch request, otherwise return false; + /// + internal abstract bool IsBatchPartRequest + { + get; + } + +#if DEBUG + /// + /// Returns the collection of request headers. + /// + private IEnumerable> Headers + { + get { return this.requestMessage.Headers; } + } +#endif + + #endregion + + /// + /// Create a request message for a batch part from the batch writer. This method copies request headers + /// from in addition to the method and Uri. + /// + /// ODataBatchWriter instance to build operation message from. + /// RequestMessageArgs for the request. + /// RequestInfo instance. + /// The Content-ID value to write in ChangeSet head. + /// an instance of ODataRequestMessageWrapper. + internal static ODataRequestMessageWrapper CreateBatchPartRequestMessage( + ODataBatchWriter batchWriter, + BuildingRequestEventArgs requestMessageArgs, + RequestInfo requestInfo, + string contentId) + { + IODataRequestMessage requestMessage = batchWriter.CreateOperationRequestMessage(requestMessageArgs.Method, requestMessageArgs.RequestUri, contentId); + + foreach (var h in requestMessageArgs.Headers) + { + requestMessage.SetHeader(h.Key, h.Value); + } + + var clientRequestMessage = new InternalODataRequestMessage(requestMessage, false /*allowGetStream*/); + ODataRequestMessageWrapper messageWrapper = new InnerBatchRequestMessageWrapper(clientRequestMessage, requestMessage, requestInfo, requestMessageArgs.Descriptor); + return messageWrapper; + } + + /// + /// Create a request message for a non-batch requests and outer $batch request. This method copies request headers + /// from in addition to the method and Uri. + /// + /// RequestMessageArgs for the request. + /// RequestInfo instance. + /// an instance of ODataRequestMessageWrapper. + internal static ODataRequestMessageWrapper CreateRequestMessageWrapper(BuildingRequestEventArgs requestMessageArgs, RequestInfo requestInfo) + { + Debug.Assert(requestMessageArgs != null, "requestMessageArgs != null"); + + var requestMessage = requestInfo.CreateRequestMessage(requestMessageArgs); + + if (null != requestInfo.Credentials) + { + requestMessage.Credentials = requestInfo.Credentials; + } + +#if !PORTABLELIB // Timeout not available + if (0 != requestInfo.Timeout) + { + requestMessage.Timeout = requestInfo.Timeout; + } +#endif + + return new TopLevelRequestMessageWrapper(requestMessage, requestInfo, requestMessageArgs.Descriptor); + } + +#if DEBUG + /// + /// Returns the value of the header with the given name. + /// + /// Name of the header. + /// Returns the value of the header with the given name. + internal string GetHeader(string headerName) + { + Debug.Assert(!string.IsNullOrEmpty(headerName), "!String.IsNullOrEmpty(headerName)"); + return this.requestMessage.GetHeader(headerName); + } +#endif + + /// + /// Create ODataMessageWriter given the writer settings. + /// + /// Writer settings. + /// true if the writer is intended to for a parameter payload, false otherwise. + /// An instance of ODataMessageWriter. + internal abstract ODataMessageWriter CreateWriter(ODataMessageWriterSettings writerSettings, bool isParameterPayload); + + /// + /// Abort the current request. + /// + internal void Abort() + { + this.requestMessage.Abort(); + } + + /// + /// Sets the value of an HTTP header. + /// + /// The name of the header to set. + /// The value of the HTTP header or 'null' if the header should be removed. + internal void SetHeader(string headerName, string headerValue) + { + this.requestMessage.SetHeader(headerName, headerValue); + } + + /// + /// Begins an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request. + internal IAsyncResult BeginGetRequestStream(AsyncCallback callback, object state) + { +#if DEBUG + this.ValidateHeaders(); +#endif + return this.requestMessage.BeginGetRequestStream(callback, state); + } + + /// + /// Ends an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The pending request for a stream. + /// A System.IO.Stream to use to write request data. + internal Stream EndGetRequestStream(IAsyncResult asyncResult) + { + return this.requestMessage.EndGetRequestStream(asyncResult); + } + +#if !PORTABLELIB + + /// + /// Sets the request stream. + /// + /// The content stream to copy into the request stream. + internal void SetRequestStream(ContentStream requestStreamContent) + { + if (requestStreamContent.IsKnownMemoryStream) + { + this.SetContentLengthHeader(); + } + +#if DEBUG + this.ValidateHeaders(); +#endif + using (Stream requestStream = this.requestMessage.GetStream()) + { + if (requestStreamContent.IsKnownMemoryStream) + { + MemoryStream bufferableStream = (MemoryStream)requestStreamContent.Stream; + Debug.Assert(bufferableStream.Position == 0, "Cached/buffered stream position should be 0"); + + byte[] buffer = bufferableStream.GetBuffer(); + int bufferOffset = checked((int)bufferableStream.Position); + int bufferLength = checked((int)bufferableStream.Length) - bufferOffset; + + // the following is useful in the debugging Immediate Window + // string x = System.Text.Encoding.UTF8.GetString(buffer, bufferOffset, bufferLength); + requestStream.Write(buffer, bufferOffset, bufferLength); + } + else + { + byte[] buffer = new byte[WebUtil.DefaultBufferSizeForStreamCopy]; + WebUtil.CopyStream(requestStreamContent.Stream, requestStream, ref buffer); + } + } + } + +#endif + + /// + /// Begins an asynchronous request to an Internet resource. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request for a response. + internal IAsyncResult BeginGetResponse(AsyncCallback callback, object state) + { +#if DEBUG + this.ValidateHeaders(); +#endif + return this.requestMessage.BeginGetResponse(callback, state); + } + + /// + /// Ends an asynchronous request to an Internet resource. + /// + /// The pending request for a response. + /// A System.Net.WebResponse that contains the response from the Internet resource. + internal IODataResponseMessage EndGetResponse(IAsyncResult asyncResult) + { + return this.requestMessage.EndGetResponse(asyncResult); + } + +#if !PORTABLELIB + /// + /// Returns a response from an Internet resource. + /// + /// A System.Net.WebResponse that contains the response from the Internet resource. + internal IODataResponseMessage GetResponse() + { +#if DEBUG + this.ValidateHeaders(); +#endif + return this.requestMessage.GetResponse(); + } +#endif + + /// + /// Sets the content length header for the given request message. + /// + internal void SetContentLengthHeader() + { + // Ideally we should set the content length header in data service client. We need to remove this long term. + // For now, we only set the content length when SendingRequest event is fired. + // Also make sure that content length header is only set after SendingRequest2 is fired, so that users + // cannot depend on this functionality. Then when we remove SendingRequest event from the code, we need to + // go longer set the content length. +#if DEBUG + Debug.Assert(this.fireSendingRequest2MethodCalled, "FireSendingRequest2 method must be called before setting content length"); +#endif + // Setting the Contentlength when SendingRequest or SendRequest2 is subscribed to + // because there are chances for the contentlength to be wrong and for a ProtocolViolation to occur + // in the HttpWebRequest. Previously this was only done for SendingRequest but because we are + // deprecating SendingRequest it makes sense to do this for SendingRequest2 as people will move their + // code to use the SendingRequest2. So each event is consistent and will work properly as expected. + if (this.requestInfo.HasSendingRequest2EventHandlers) + { + // Since in V2, we always use to set the content length header after firing the SendingRequest event. + // Now since we are always setting the content length before firing the SendingRequest event, we need + // to add the Content-Length header to the list of headers to reset. + long contentLength = this.CachedRequestStream.Stream.Length; + this.SetHeader(XmlConstants.HttpContentLength, contentLength.ToString(CultureInfo.InvariantCulture)); + } + } + + /// + /// Fires the following events, in order + /// 1. WritingRequest + /// 2. SendingRequest2 + /// + /// Descriptor for which this request is getting generated. + internal void FireSendingEventHandlers(Descriptor descriptor) + { + this.FireSendingRequest2(descriptor); + } + + /// + /// FireSendingRequest2 event. + /// + /// Descriptor for which this request is getting generated. + internal void FireSendingRequest2(Descriptor descriptor) + { +#if DEBUG + Debug.Assert(!this.fireSendingRequest2MethodCalled, "!this.fireSendingRequest2MethodCalled"); + Debug.Assert(this.cachedRequestHeaders == null, "this.cachedRequestHeaders == null"); + + // Currently for actions there are no descriptors and hence this assert needs to be disabled. + // Once the actions have descriptors, we can enable this assert. + // Debug.Assert( + // descriptor != null || this.requestMessage.Method == XmlConstants.HttpMethodGet || this.requestMessage.Url.AbsoluteUri.Contains("$batch"), + // "For CUD operations, decriptor must be specified in every SendingRequest2 event except top level batch request"); +#endif + + // Do we need to fire these events if someone has replaced the transport layer? Maybe no. + if (this.requestInfo.HasSendingRequest2EventHandlers) + { + // For now, we don't think this adds a lot of value exposing on the public DataServiceClientRequestMessage class + // In future, we can always add it if customers ask for this. Erring on the side of keeping the public + // class simple. + var httpWebRequestMessage = this.requestMessage as HttpWebRequestMessage; + if (httpWebRequestMessage != null) + { + // For now we are saying that anyone who implements the transport layer do not get a chance to fire + // SendingRequest yet at all. That does not seem that bad. + httpWebRequestMessage.BeforeSendingRequest2Event(); + } + + try + { + this.requestInfo.FireSendingRequest2(new SendingRequest2EventArgs(this.requestMessage, descriptor, this.IsBatchPartRequest)); + } + finally + { + if (httpWebRequestMessage != null) + { + httpWebRequestMessage.AfterSendingRequest2Event(); + } + } + } +#if DEBUG + else + { + // Cache the headers if there is no sending request 2 event subscribers. At the time of GetRequestStream + // or GetSyncronousResponse, we will validate that the headers are the same. + this.cachedRequestHeaders = new HeaderCollection(); + foreach (var header in this.requestMessage.Headers) + { + this.cachedRequestHeaders.SetHeader(header.Key, header.Value); + } + } + + this.fireSendingRequest2MethodCalled = true; +#endif + } + + +#if DEBUG + /// + /// The reason for adding this method is that we have seen a couple of asserts that we were not able to figure out why they are getting fired. + /// So added this method which returns the current headers as well as cached headers as string and we display that in the assert message. + /// + /// current header values. + /// cached header values. + /// returns a string which contains both current and cached header names. + private static string GetHeaderValues(IEnumerable> currentHeaders, HeaderCollection cachedHeaders) + { + StringBuilder sb = new StringBuilder(); + string separator = String.Empty; + sb.Append("Current Headers: "); + foreach (var header in currentHeaders) + { + sb.Append(separator); + sb.Append(header.Key); + separator = ", "; + } + + sb.Append(". Headers fired in SendingRequest: "); + separator = String.Empty; + foreach (string name in cachedHeaders.HeaderNames) + { + sb.Append(separator); + sb.Append(name); + separator = ", "; + } + + return sb.ToString(); + } + + /// + /// This method validates that headers values are identical to what they originally were when the request was configured. + /// DataServiceContext.CachedRequestHeaders is populated in the DataServiceContext.CreateGetRequest method. + /// + private void ValidateHeaders() + { + Debug.Assert(this.fireSendingRequest2MethodCalled, "In ValidateHeaders - FireSendingRequest2 method must have been called"); + + if (this.cachedRequestHeaders != null) + { + Debug.Assert(this.Headers.Count() == this.cachedRequestHeaders.Count, "The request headers count must match" + GetHeaderValues(this.Headers, this.cachedRequestHeaders)); + + foreach (KeyValuePair header in this.Headers) + { + if (!this.cachedRequestHeaders.HasHeader(header.Key)) + { + Debug.Assert(false, "Missing header: " + GetHeaderValues(this.Headers, this.cachedRequestHeaders)); + } + + Debug.Assert( + header.Value == this.cachedRequestHeaders.GetHeader(header.Key), + String.Format(CultureInfo.InvariantCulture, "The header '{0}' has a different value. Old Value: '{1}', Current Value: '{2}' Please make sure to set the header before SendingRequest event is fired", header.Key, header.Value, this.cachedRequestHeaders.GetHeader(header.Key))); + } + + this.cachedRequestHeaders = null; + } + } +#endif + + /// + /// This is a just a pass through implementation of IODataRequestMessage. + /// In order to keep the sync and non-async code the same, we write all requests into an cached stream and then copy + /// it to the underlying network stream in sync or async manner. + /// + private class RequestMessageWithCachedStream : IODataRequestMessage + { + /// + /// IODataRequestMessage implementation that this class wraps. + /// + private readonly DataServiceClientRequestMessage requestMessage; + + /// + /// The cached request stream. + /// + private ContentStream cachedRequestStream; + + /// + /// Creates a new instance of InternalODataRequestMessage. + /// + /// IODataRequestMessage that needs to be wrapped. + internal RequestMessageWithCachedStream(DataServiceClientRequestMessage requestMessage) + { + Debug.Assert(requestMessage != null, "requestMessage != null"); + this.requestMessage = requestMessage; + } + + /// + /// Returns the collection of request headers. + /// + public IEnumerable> Headers + { + get { return this.requestMessage.Headers; } + } + + /// + /// Gets or Sets the request url. + /// + public Uri Url + { + get { return this.requestMessage.Url; } + + set { throw new NotImplementedException(); } + } + + /// + /// Gets or Sets the http method for this request. + /// + public string Method + { + get { return this.requestMessage.Method; } + + set { throw new NotImplementedException(); } + } + + /// + /// Return the stream containing the request payload. + /// + internal ContentStream CachedRequestStream + { + get + { + this.cachedRequestStream.Stream.Position = 0; + return this.cachedRequestStream; + } + } + + /// + /// Returns the value of the header with the given name. + /// + /// Name of the header. + /// Returns the value of the header with the given name. + public string GetHeader(string headerName) + { + return this.requestMessage.GetHeader(headerName); + } + + /// + /// Sets the value of the header with the given name. + /// + /// Name of the header. + /// Value of the header. + public void SetHeader(string headerName, string headerValue) + { + this.requestMessage.SetHeader(headerName, headerValue); + } + + /// + /// Gets the stream to be used to write the request payload. + /// + /// Stream to which the request payload needs to be written. + public Stream GetStream() + { + if (this.cachedRequestStream == null) + { + this.cachedRequestStream = new ContentStream(new MemoryStream(), true /*isKnownMemoryStream*/); + } + + return this.cachedRequestStream.Stream; + } + } + + /// + /// This class wraps the request message for non-batch requests and $batch requests. + /// + private class TopLevelRequestMessageWrapper : ODataRequestMessageWrapper + { + /// + /// Wrapper for the top-level request messages which caches the request stream as it is written. In order to keep the sync and non-async + /// code the same, we write all requests into an cached stream and then copy it to the underlying network stream in sync or async manner. + /// + private readonly RequestMessageWithCachedStream messageWithCachedStream; + + /// + /// Creates a new instance of ODataOuterRequestMessage. + /// + /// DataServiceClientRequestMessage instance. + /// RequestInfo instance. + /// Descriptor for this request. + internal TopLevelRequestMessageWrapper(DataServiceClientRequestMessage requestMessage, RequestInfo requestInfo, Descriptor descriptor) + : base(requestMessage, requestInfo, descriptor) + { + // Wrapper for the top-level request messages which caches the request stream as it is written. In order to keep the sync and non-async + // code the same, we write all requests into an cached stream and then copy it to the underlying network stream in sync or async manner. + this.messageWithCachedStream = new RequestMessageWithCachedStream(this.requestMessage); + } + + /// + /// Returns true if the message is part of the batch request, otherwise return false; + /// + internal override bool IsBatchPartRequest + { + get { return false; } + } + + /// + /// Return the stream containing the request payload. + /// + internal override ContentStream CachedRequestStream + { + get + { + Debug.Assert(this.messageWithCachedStream != null, "Cannot access the cached request stream for non-top-level messages."); + return this.messageWithCachedStream.CachedRequestStream; + } + } + + /// + /// Create ODataMessageWriter given the writer settings. + /// + /// Writer settings. + /// true if the writer is intended to for a parameter payload, false otherwise. + /// An instance of ODataMessageWriter. + internal override ODataMessageWriter CreateWriter(ODataMessageWriterSettings writerSettings, bool isParameterPayload) + { + // Memory stream to write the request payloads. In order to keep the sync and non-async code same, + // we write all requests into an cached stream and then copy it to the underlying network stream in sync or async + // manner. + return this.requestInfo.WriteHelper.CreateWriter(this.messageWithCachedStream, writerSettings, isParameterPayload); + } + } + + /// + /// This class wraps the request message for inner batch operations. + /// + private class InnerBatchRequestMessageWrapper : ODataRequestMessageWrapper + { + /// + /// Inner batch request that ODataLib creates. + /// + private readonly IODataRequestMessage innerBatchRequestMessage; + + /// + /// Creates a new instance of InnerBatchRequestMessageWrapper; + /// + /// Instance of DataServiceClientRequestMessage that represents this request. + /// Instance of IODataRequestMessage created by ODataLib. + /// RequestInfo instance. + /// Descriptor for this request. + internal InnerBatchRequestMessageWrapper(DataServiceClientRequestMessage clientRequestMessage, IODataRequestMessage odataRequestMessage, RequestInfo requestInfo, Descriptor descriptor) + : base(clientRequestMessage, requestInfo, descriptor) + { + this.innerBatchRequestMessage = odataRequestMessage; + } + + /// + /// Returns true if the message is part of the batch request, otherwise return false; + /// + internal override bool IsBatchPartRequest + { + get { return true; } + } + + /// + /// Return the stream containing the request payload. + /// + internal override ContentStream CachedRequestStream + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Create ODataMessageWriter given the writer settings. + /// + /// Writer settings. + /// true if the writer is intended to for a parameter payload, false otherwise. + /// An instance of ODataMessageWriter. + internal override ODataMessageWriter CreateWriter(ODataMessageWriterSettings writerSettings, bool isParameterPayload) + { + // Memory stream to write the request payloads. In order to keep the sync and non-async code same, + // we write all requests into an cached stream and then copy it to the underlying network stream in sync or async + // manner. + return this.requestInfo.WriteHelper.CreateWriter(this.innerBatchRequestMessage, writerSettings, isParameterPayload); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationDescriptor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationDescriptor.cs new file mode 100644 index 0000000..1c18216 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationDescriptor.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region namespaces + + using System; + using System.Diagnostics; + #endregion + + /// Holds information about a service operation. + public abstract class OperationDescriptor : Descriptor + { + #region fields + + /// Maps to m:action\@title. Human-readable description of the service operation. + private string title; + + /// maps to m:action\@metadata. Identifies the service operation. + private Uri metadata; + + /// maps to m:action\@target. The URI to invoke the service operation. + private Uri target; + #endregion + + /// + /// Creates a new instance of the Operation descriptor. + /// + internal OperationDescriptor() + : base(EntityStates.Unchanged) + { + } + + #region public properties + + /// Human-readable description of the service operation. + public string Title + { + get + { + return this.title; + } + + internal set + { + this.title = value; + } + } + + /// Identifies the service operation. + public Uri Metadata + { + get { return this.metadata; } + internal set { this.metadata = value; } + } + + /// The URI to invoke the service operation. + public Uri Target + { + get { return this.target; } + internal set { this.target = value; } + } + + /// + /// this is an operation descriptor. + /// + internal override DescriptorKind DescriptorKind + { + get { return DescriptorKind.OperationDescriptor; } + } + + #endregion + + /// + /// Nothing to clear in case of operation descriptor. + /// + internal override void ClearChanges() + { + // Do Nothing. + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationParameter.cs new file mode 100644 index 0000000..118ce18 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationParameter.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// Represents a parameter passed to a service action, service function or a service operation when it is Executed. + public abstract class OperationParameter + { + /// The name of the operation parameter. + private readonly String parameterName; + + /// The value of the operation parameter. + private readonly Object parameterValue; + + /// Initializes a new instance of the class. + /// The name of the operation parameter. + /// The value of the operation parameter. + protected OperationParameter(string name, Object value) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(Strings.Context_MissingOperationParameterName); + } + + this.parameterName = name; + this.parameterValue = value; + } + + /// Gets the name of the operation parameter. + /// The name of the operation parameter. + public String Name + { + get + { + return this.parameterName; + } + } + + /// Gets the value of the operation parameter. + /// The value of the operation parameter. + public Object Value + { + get { return this.parameterValue; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationResponse.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationResponse.cs new file mode 100644 index 0000000..25fc209 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/OperationResponse.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + /// Operation response base class + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710", Justification = "required for this feature")] + public abstract class OperationResponse + { + /// Http headers of the response. + private readonly HeaderCollection headers; + + /// Http status code of the response. + private int statusCode; + + /// exception to throw during get results + private Exception innerException; + + /// + /// constructor + /// + /// HTTP headers + internal OperationResponse(HeaderCollection headers) + { + Debug.Assert(null != headers, "null headers"); + this.headers = headers; + } + + /// When overridden in a derived class, contains the HTTP response headers associated with a single operation. + /// object that contains name value pairs of headers and values. + public IDictionary Headers + { + get { return this.headers.UnderlyingDictionary; } + } + + /// When overridden in a derived class, gets or sets the HTTP response code associated with a single operation. + /// Integer value that contains response code. + public int StatusCode + { + get { return this.statusCode; } + internal set { this.statusCode = value; } + } + + /// Gets error thrown by the operation. + /// An object that contains the error. + public Exception Error + { + get + { + return this.innerException; + } + + set + { + this.innerException = value; + } + } + + /// Http headers of the response. + internal HeaderCollection HeaderCollection + { + get { return this.headers; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs new file mode 100644 index 0000000..fb40280 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.cs @@ -0,0 +1,2019 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client { + using System; + using System.Resources; + + /// + /// Strongly-typed and parameterized string resources. + /// + internal static class Strings { + /// + /// A string like "The expected content type for a batch requests is "multipart/mixed;boundary=batch" not "{0}"." + /// + internal static string Batch_ExpectedContentType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_ExpectedContentType, p0); + } + + /// + /// A string like "The POST request expected a response with content. ID={0}" + /// + internal static string Batch_ExpectedResponse(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_ExpectedResponse, p0); + } + + /// + /// A string like "Not all requests in the batch had a response." + /// + internal static string Batch_IncompleteResponseCount { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_IncompleteResponseCount); + } + } + + /// + /// A string like "The web response contained unexpected sections. ID={0}" + /// + internal static string Batch_UnexpectedContent(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Batch_UnexpectedContent, p0); + } + + /// + /// A string like "Expected an absolute, well formed http URL without a query or fragment." + /// + internal static string Context_BaseUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BaseUri); + } + } + + /// + /// A string like "You must set the BaseUri property before you perform this operation." + /// + internal static string Context_BaseUriRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BaseUriRequired); + } + } + + /// + /// A string like "The Uri that is returned by the ResolveEntitySet function must be an absolute, well-formed URL with an "http" or "https" scheme name and without any query strings or fragment identifiers." + /// + internal static string Context_ResolveReturnedInvalidUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ResolveReturnedInvalidUri); + } + } + + /// + /// A string like "Because the requestUri is a relative Uri, you must set the BaseUri property on the DataServiceContext." + /// + internal static string Context_RequestUriIsRelativeBaseUriRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RequestUriIsRelativeBaseUriRequired); + } + } + + /// + /// A string like "The ResolveEntitySet function must return a non-null Uri for the EntitySet '{0}', otherwise you must set the BaseUri property." + /// + internal static string Context_ResolveEntitySetOrBaseUriRequired(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ResolveEntitySetOrBaseUriRequired, p0); + } + + /// + /// A string like "Unable to convert value '{0}' into a key string for a URI." + /// + internal static string Context_CannotConvertKey(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotConvertKey, p0); + } + + /// + /// A string like "The identity value specified by either the Atom id element or the OData-EntityId header must be an absolute URI." + /// + internal static string Context_TrackingExpectsAbsoluteUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_TrackingExpectsAbsoluteUri); + } + } + + /// + /// A string like "The 'Location' header value specified in the response must be an absolute URI." + /// + internal static string Context_LocationHeaderExpectsAbsoluteUri { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_LocationHeaderExpectsAbsoluteUri); + } + } + + /// + /// A string like "One of the link's resources failed to insert." + /// + internal static string Context_LinkResourceInsertFailure { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_LinkResourceInsertFailure); + } + } + + /// + /// A string like "Microsoft.OData.Client internal error {0}." + /// + internal static string Context_InternalError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_InternalError, p0); + } + + /// + /// A string like "An error occurred for this query during batch execution. See the inner exception for details." + /// + internal static string Context_BatchExecuteError { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BatchExecuteError); + } + } + + /// + /// A string like "Expected a relative URL path without query or fragment." + /// + internal static string Context_EntitySetName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntitySetName); + } + } + + /// + /// A string like "Changes cannot be saved as a batch when an entity has one or more streams associated with it. Retry the SaveChanges operation without enabling the SaveChangesOptions.BatchWithSingleChangeset and the SaveChangesOptions.BatchWithIndependentOperations options." + /// + internal static string Context_BatchNotSupportedForNamedStreams { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BatchNotSupportedForNamedStreams); + } + } + + /// + /// A string like "The stream named '{0}' cannot be modified because it does not have an edit-media link. Make sure that the stream name is correct and that an edit-media link for this stream is included in the entry element in the response." + /// + internal static string Context_SetSaveStreamWithoutNamedStreamEditLink(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamWithoutNamedStreamEditLink, p0); + } + + /// + /// A string like "This operation requires the entity be of an Entity Type, and has at least one key property." + /// + internal static string Content_EntityWithoutKey { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Content_EntityWithoutKey); + } + } + + /// + /// A string like "This operation requires the entity to be of an Entity Type, either mark its key properties, or attribute the class with DataServiceEntityAttribute" + /// + internal static string Content_EntityIsNotEntityType { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Content_EntityIsNotEntityType); + } + } + + /// + /// A string like "The context is not currently tracking the entity." + /// + internal static string Context_EntityNotContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityNotContained); + } + } + + /// + /// A string like "The context is already tracking the entity." + /// + internal static string Context_EntityAlreadyContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityAlreadyContained); + } + } + + /// + /// A string like "The context is already tracking a different entity with the same resource Uri." + /// + internal static string Context_DifferentEntityAlreadyContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DifferentEntityAlreadyContained); + } + } + + /// + /// A string like "The current object did not originate the async result." + /// + internal static string Context_DidNotOriginateAsync { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DidNotOriginateAsync); + } + } + + /// + /// A string like "The asynchronous result has already been completed." + /// + internal static string Context_AsyncAlreadyDone { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AsyncAlreadyDone); + } + } + + /// + /// A string like "The operation has been canceled." + /// + internal static string Context_OperationCanceled { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_OperationCanceled); + } + } + + /// + /// A string like "The property '{0}' is not supported when MaxProtocolVersion is greater than '{1}'." + /// + internal static string Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_PropertyNotSupportedForMaxDataServiceVersionGreaterThanX, p0, p1); + } + + /// + /// A string like "The context can not load the related collection or reference for objects in the added state." + /// + internal static string Context_NoLoadWithInsertEnd { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoLoadWithInsertEnd); + } + } + + /// + /// A string like "One or both of the ends of the relationship is in the added state." + /// + internal static string Context_NoRelationWithInsertEnd { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoRelationWithInsertEnd); + } + } + + /// + /// A string like "One or both of the ends of the relationship is in the deleted state." + /// + internal static string Context_NoRelationWithDeleteEnd { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoRelationWithDeleteEnd); + } + } + + /// + /// A string like "The context is already tracking the relationship." + /// + internal static string Context_RelationAlreadyContained { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RelationAlreadyContained); + } + } + + /// + /// A string like "The sourceProperty is not a reference or collection of the target's object type." + /// + internal static string Context_RelationNotRefOrCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RelationNotRefOrCollection); + } + } + + /// + /// A string like "AddLink and DeleteLink methods only work when the sourceProperty is a collection." + /// + internal static string Context_AddLinkCollectionOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AddLinkCollectionOnly); + } + } + + /// + /// A string like "AddRelatedObject method only works when the sourceProperty is a collection." + /// + internal static string Context_AddRelatedObjectCollectionOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AddRelatedObjectCollectionOnly); + } + } + + /// + /// A string like "AddRelatedObject method only works if the source entity is in a non-deleted state." + /// + internal static string Context_AddRelatedObjectSourceDeleted { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_AddRelatedObjectSourceDeleted); + } + } + + /// + /// A string like "UpdateRelatedObject method only works when the sourceProperty is not collection." + /// + internal static string Context_UpdateRelatedObjectNonCollectionOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_UpdateRelatedObjectNonCollectionOnly); + } + } + + /// + /// A string like "SetLink method only works when the sourceProperty is not a collection." + /// + internal static string Context_SetLinkReferenceOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetLinkReferenceOnly); + } + } + + /// + /// A string like "Media link object of type '{0}' is configured to use the MIME type specified in the property '{1}'. However, that property's value is null or empty." + /// + internal static string Context_NoContentTypeForMediaLink(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NoContentTypeForMediaLink, p0, p1); + } + + /// + /// A string like "Saving entities with the [MediaEntry] attribute is not currently supported in batch mode. Use non-batched mode instead." + /// + internal static string Context_BatchNotSupportedForMediaLink { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BatchNotSupportedForMediaLink); + } + } + + /// + /// A string like "Unexpected result (<= 0) from stream.Read() while reading raw data for this property." + /// + internal static string Context_UnexpectedZeroRawRead { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_UnexpectedZeroRawRead); + } + } + + /// + /// A string like "Response version '{0}' is not supported. The only supported versions are: {1}." + /// + internal static string Context_VersionNotSupported(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_VersionNotSupported, p0, p1); + } + + /// + /// A string like "The response version is {0}, but the MaxProtocolVersion of the data service context is set to {1}. Set the MaxProtocolVersion to the version required by the response, and then retry the request. If the client does not support the required protocol version, then upgrade the client." + /// + internal static string Context_ResponseVersionIsBiggerThanProtocolVersion(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ResponseVersionIsBiggerThanProtocolVersion, p0, p1); + } + + /// + /// A string like "The request requires that version {0} of the protocol be used, but the MaxProtocolVersion of the data service context is set to {1}. Set the MaxProtocolVersion to the higher version, and then retry the operation." + /// + internal static string Context_RequestVersionIsBiggerThanProtocolVersion(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_RequestVersionIsBiggerThanProtocolVersion, p0, p1); + } + + /// + /// A string like "Attempt to delete a link between two objects failed because the identity of the target object of the link depends on the source object of the link." + /// + internal static string Context_ChildResourceExists { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ChildResourceExists); + } + } + + /// + /// A string like "The ContentType value for a named stream cannot be null or an empty string." + /// + internal static string Context_ContentTypeRequiredForNamedStream { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ContentTypeRequiredForNamedStream); + } + } + + /// + /// A string like "This operation requires that the specified entity be a Media Link Entry and that the ReadStreamUri be available. However, the specified entity either is not a Media Link Entry or does not have a valid ReadStreamUri value. If the entity is a Media Link Entry, re-query the data service for this entity to obtain a valid ReadStreamUri value." + /// + internal static string Context_EntityNotMediaLinkEntry { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityNotMediaLinkEntry); + } + } + + /// + /// A string like "The entity type {0} is marked with MediaEntry attribute but no save stream was set for the entity." + /// + internal static string Context_MLEWithoutSaveStream(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MLEWithoutSaveStream, p0); + } + + /// + /// A string like "Can't use SetSaveStream on entity with type {0} which has a media entry property defined." + /// + internal static string Context_SetSaveStreamOnMediaEntryProperty(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamOnMediaEntryProperty, p0); + } + + /// + /// A string like "There is no edit-media link for the entity's media stream. Make sure that the edit-media link is specified for this stream." + /// + internal static string Context_SetSaveStreamWithoutEditMediaLink { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamWithoutEditMediaLink); + } + } + + /// + /// A string like "Calling SetSaveStream on an entity with state '{0}' is not allowed." + /// + internal static string Context_SetSaveStreamOnInvalidEntityState(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SetSaveStreamOnInvalidEntityState, p0); + } + + /// + /// A string like "The entity does not have a stream named '{0}'. Make sure that the name of the stream is correct." + /// + internal static string Context_EntityDoesNotContainNamedStream(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityDoesNotContainNamedStream, p0); + } + + /// + /// A string like "There is no self-link or edit-media link for the stream named '{0}'. Make sure that either the self-link or edit-media link is specified for this stream." + /// + internal static string Context_MissingSelfAndEditLinkForNamedStream(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MissingSelfAndEditLinkForNamedStream, p0); + } + + /// + /// A string like "The response should have both 'Location' and 'OData-EntityId' headers or the response should not have any of these headers." + /// + internal static string Context_BothLocationAndIdMustBeSpecified { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BothLocationAndIdMustBeSpecified); + } + } + + /// + /// A string like "OperationParameter of type BodyOperationParameter cannot be specified when the HttpMethod is set to GET." + /// + internal static string Context_BodyOperationParametersNotAllowedWithGet { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_BodyOperationParametersNotAllowedWithGet); + } + } + + /// + /// A string like "The Name property of an OperationParameter must be set to a non-null, non-empty string." + /// + internal static string Context_MissingOperationParameterName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MissingOperationParameterName); + } + } + + /// + /// A string like "Multiple uri operation parameters were found with the same name. Uri operation parameter names must be unique." + /// + internal static string Context_DuplicateUriOperationParameterName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DuplicateUriOperationParameterName); + } + } + + /// + /// A string like "Multiple body operation parameters were found with the same name. Body operation parameter names must be unique." + /// + internal static string Context_DuplicateBodyOperationParameterName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_DuplicateBodyOperationParameterName); + } + } + + /// + /// A string like "The serialized resource has a null value in key member '{0}'. Null values are not supported in key members." + /// + internal static string Context_NullKeysAreNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NullKeysAreNotSupported, p0); + } + + /// + /// A string like "The HttpMethod must be GET, POST or DELETE." + /// + internal static string Context_ExecuteExpectsGetOrPostOrDelete { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ExecuteExpectsGetOrPostOrDelete); + } + } + + /// + /// A string like "EndExecute overload for void service operations and actions received a non-void response from the server." + /// + internal static string Context_EndExecuteExpectedVoidResponse { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EndExecuteExpectedVoidResponse); + } + } + + /// + /// A string like "The operation parameters array contains a null element which is not allowed." + /// + internal static string Context_NullElementInOperationParameterArray { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_NullElementInOperationParameterArray); + } + } + + /// + /// A string like "An implementation of ODataEntityMetadataBuilder is required, but a null value was returned from GetEntityMetadataBuilder." + /// + internal static string Context_EntityMetadataBuilderIsRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_EntityMetadataBuilderIsRequired); + } + } + + /// + /// A string like "The ChangeState method does not support the 'Added' state because additional information is needed for inserts. Use either AddObject or AddRelatedObject instead." + /// + internal static string Context_CannotChangeStateToAdded { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotChangeStateToAdded); + } + } + + /// + /// A string like "The entity's state can only be changed to 'Modified' if it is currently 'Unchanged'." + /// + internal static string Context_CannotChangeStateToModifiedIfNotUnchanged { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotChangeStateToModifiedIfNotUnchanged); + } + } + + /// + /// A string like "An entity in the 'Added' state cannot be changed to '{0}', it can only be changed to 'Detached'." + /// + internal static string Context_CannotChangeStateIfAdded(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_CannotChangeStateIfAdded, p0); + } + + /// + /// A string like "DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property must not return a null value. Please return a non-null value for this property." + /// + internal static string Context_OnMessageCreatingReturningNull { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_OnMessageCreatingReturningNull); + } + } + + /// + /// A string like "SendingRequest cannot be used in combination with the DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property. Please use SendingRequest2 with DataServiceContext.Configurations.RequestPipeline.OnMessageCreating property instead." + /// + internal static string Context_SendingRequest_InvalidWhenUsingOnMessageCreating { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SendingRequest_InvalidWhenUsingOnMessageCreating); + } + } + + /// + /// A string like "'{0}' must be used with '{1}'." + /// + internal static string Context_MustBeUsedWith(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_MustBeUsedWith, p0, p1); + } + + /// + /// A string like "When you call the UseJson method without a parameter, you must use the LoadServiceModel property to provide a valid IEdmModel instance." + /// + internal static string DataServiceClientFormat_LoadServiceModelRequired { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceClientFormat_LoadServiceModelRequired); + } + } + + /// + /// A string like "To use the JSON format, you must first call DataServiceContext.Format.UseJson() and supply a valid service model." + /// + internal static string DataServiceClientFormat_ValidServiceModelRequiredForJson { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceClientFormat_ValidServiceModelRequiredForJson); + } + } + + /// + /// A string like "{0}.{1} must return a non-null open property collection." + /// + internal static string Collection_NullCollectionReference(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullCollectionReference, p0, p1); + } + + /// + /// A string like "The open object property '{0}:{1}' is not defined." + /// + internal static string ClientType_MissingOpenProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingOpenProperty, p0, p1); + } + + /// + /// A string like "{0} has multiple definitions for OpenObjectAttribute." + /// + internal static string Clienttype_MultipleOpenProperty(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Clienttype_MultipleOpenProperty, p0); + } + + /// + /// A string like "The closed type {0} does not have a corresponding {1} settable property." + /// + internal static string ClientType_MissingProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingProperty, p0, p1); + } + + /// + /// A string like "The key property '{0}' on for type '{1}' is of type '{2}', which is not a simple type. Only properties of simple type can be key properties." + /// + internal static string ClientType_KeysMustBeSimpleTypes(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_KeysMustBeSimpleTypes, p0, p1, p2); + } + + /// + /// A string like "{0} has key properties declared at different levels within its type hierarchy." + /// + internal static string ClientType_KeysOnDifferentDeclaredType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_KeysOnDifferentDeclaredType, p0); + } + + /// + /// A string like "Type '{0}' has a MimeTypeProperty attribute that references the MIME type property '{1}'. However, this type does not have a property '{1}'." + /// + internal static string ClientType_MissingMimeTypeProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingMimeTypeProperty, p0, p1); + } + + /// + /// A string like "Type '{0}' has a MimeTypeProperty attribute that references the data property '{1}'. However, this type does not have a property '{1}'." + /// + internal static string ClientType_MissingMimeTypeDataProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingMimeTypeDataProperty, p0, p1); + } + + /// + /// A string like "Type '{0}' has a MediaEntry attribute that references a property called '{1}'. However, this type does not have a property '{1}'." + /// + internal static string ClientType_MissingMediaEntryProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MissingMediaEntryProperty, p0, p1); + } + + /// + /// A string like "The complex type '{0}' has no settable properties." + /// + internal static string ClientType_NoSettableFields(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_NoSettableFields, p0); + } + + /// + /// A string like "Multiple implementations of ICollection<T> is not supported." + /// + internal static string ClientType_MultipleImplementationNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MultipleImplementationNotSupported); + } + } + + /// + /// A string like "The open type property '{0}' returned a null instance." + /// + internal static string ClientType_NullOpenProperties(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_NullOpenProperties, p0); + } + + /// + /// A string like "Resolving type from '{0}' that inherits from '{1}' is ambiguous." + /// + internal static string ClientType_Ambiguous(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_Ambiguous, p0, p1); + } + + /// + /// A string like "The type '{0}' is not supported by the client library." + /// + internal static string ClientType_UnsupportedType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_UnsupportedType, p0); + } + + /// + /// A string like "Collection properties of a collection type are not supported." + /// + internal static string ClientType_CollectionOfCollectionNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_CollectionOfCollectionNotSupported); + } + } + + /// + /// A string like "Multiple types were found with the same name '{0}'. Type names must be unique." + /// + internal static string ClientType_MultipleTypesWithSameName(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientType_MultipleTypesWithSameName, p0); + } + + /// + /// A string like "An item in the collection property '{0}' is not of the correct type. All items in the collection property must be of the collection item type." + /// + internal static string WebUtil_TypeMismatchInCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.WebUtil_TypeMismatchInCollection, p0); + } + + /// + /// A string like "A collection of item type '{0}' has an item which is not of the correct type. All items in the collection must be of the collection item type." + /// + internal static string WebUtil_TypeMismatchInNonPropertyCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.WebUtil_TypeMismatchInNonPropertyCollection, p0); + } + + /// + /// A string like "The property '{0}' is of entity type and it cannot be a property of the type '{1}', which is not of entity type. Only entity types can contain navigation properties." + /// + internal static string ClientTypeCache_NonEntityTypeCannotContainEntityProperties(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ClientTypeCache_NonEntityTypeCannotContainEntityProperties, p0, p1); + } + + /// + /// A string like "An error occurred while processing this request." + /// + internal static string DataServiceException_GeneralError { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceException_GeneralError); + } + } + + /// + /// A string like "Only a single enumeration is supported by this IEnumerable." + /// + internal static string Deserialize_GetEnumerator { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_GetEnumerator); + } + } + + /// + /// A string like "The current value '{1}' type is not compatible with the expected '{0}' type." + /// + internal static string Deserialize_Current(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_Current, p0, p1); + } + + /// + /// A string like "Error processing response stream. Element value interspersed with a comment is not supported." + /// + internal static string Deserialize_MixedTextWithComment { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MixedTextWithComment); + } + } + + /// + /// A string like "Error processing response stream. The XML element contains mixed content." + /// + internal static string Deserialize_ExpectingSimpleValue { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_ExpectingSimpleValue); + } + } + + /// + /// A string like "Error processing response stream. Atom payload has a link, local object has a simple value." + /// + internal static string Deserialize_MismatchAtomLinkLocalSimple { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MismatchAtomLinkLocalSimple); + } + } + + /// + /// A string like "Error processing response stream. Atom payload has a feed and the property '{0}' is not a collection." + /// + internal static string Deserialize_MismatchAtomLinkFeedPropertyNotCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MismatchAtomLinkFeedPropertyNotCollection, p0); + } + + /// + /// A string like "Error processing response stream. Atom payload has an entry and the property '{0}' is a collection." + /// + internal static string Deserialize_MismatchAtomLinkEntryPropertyIsCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MismatchAtomLinkEntryPropertyIsCollection, p0); + } + + /// + /// A string like "The response to this POST request did not contain a 'location' header. That is not supported by this client." + /// + internal static string Deserialize_NoLocationHeader { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_NoLocationHeader); + } + } + + /// + /// A string like "Error processing response stream. Server failed with following message:\r\n{0}" + /// + internal static string Deserialize_ServerException(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_ServerException, p0); + } + + /// + /// A string like "Error processing response stream. Missing id element in the response." + /// + internal static string Deserialize_MissingIdElement { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Deserialize_MissingIdElement); + } + } + + /// + /// A string like "The value of the property '{0}' is null. Properties that are a collection type of primitive or complex types cannot be null." + /// + internal static string Collection_NullCollectionNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullCollectionNotSupported, p0); + } + + /// + /// A string like "The value of the collection of item type '{0}' is null. A collection cannot have a null value." + /// + internal static string Collection_NullNonPropertyCollectionNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullNonPropertyCollectionNotSupported, p0); + } + + /// + /// A string like "An item in the collection property has a null value. Collection properties that contain items with null values are not supported." + /// + internal static string Collection_NullCollectionItemsNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_NullCollectionItemsNotSupported); + } + } + + /// + /// A string like "A collection property of primitive types cannot contain an item of a collection type." + /// + internal static string Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed); + } + } + + /// + /// A string like "A collection property of complex types cannot contain an item of a primitive type." + /// + internal static string Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed); + } + } + + /// + /// A string like "The entity with identity '{0}' does not have a self-link or an edit-link associated with it. Please make sure that the entity has either a self-link or an edit-link associated with it." + /// + internal static string EntityDescriptor_MissingSelfEditLink(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EntityDescriptor_MissingSelfEditLink, p0); + } + + /// + /// A string like "Content-Type header value missing." + /// + internal static string HttpProcessUtility_ContentTypeMissing { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_ContentTypeMissing); + } + } + + /// + /// A string like "Media type is missing a parameter value." + /// + internal static string HttpProcessUtility_MediaTypeMissingValue { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeMissingValue); + } + } + + /// + /// A string like "Media type requires a ';' character before a parameter definition." + /// + internal static string HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeRequiresSemicolonBeforeParameter); + } + } + + /// + /// A string like "Media type requires a '/' character." + /// + internal static string HttpProcessUtility_MediaTypeRequiresSlash { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeRequiresSlash); + } + } + + /// + /// A string like "Media type requires a subtype definition." + /// + internal static string HttpProcessUtility_MediaTypeRequiresSubType { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeRequiresSubType); + } + } + + /// + /// A string like "Media type is unspecified." + /// + internal static string HttpProcessUtility_MediaTypeUnspecified { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_MediaTypeUnspecified); + } + } + + /// + /// A string like "Character set '{0}' is not supported." + /// + internal static string HttpProcessUtility_EncodingNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_EncodingNotSupported, p0); + } + + /// + /// A string like "Value for MIME type parameter '{0}' is incorrect because it contained escape characters even though it was not quoted." + /// + internal static string HttpProcessUtility_EscapeCharWithoutQuotes(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_EscapeCharWithoutQuotes, p0); + } + + /// + /// A string like "Value for MIME type parameter '{0}' is incorrect because it terminated with escape character. Escape characters must always be followed by a character in a parameter value." + /// + internal static string HttpProcessUtility_EscapeCharAtEnd(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_EscapeCharAtEnd, p0); + } + + /// + /// A string like "Value for MIME type parameter '{0}' is incorrect because the closing quote character could not be found while the parameter value started with a quote character." + /// + internal static string HttpProcessUtility_ClosingQuoteNotFound(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.HttpProcessUtility_ClosingQuoteNotFound, p0); + } + + /// + /// A string like "Count value is not part of the response stream." + /// + internal static string MaterializeFromAtom_CountNotPresent { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_CountNotPresent); + } + } + + /// + /// A string like "The top level link is only available after the response has been enumerated." + /// + internal static string MaterializeFromAtom_TopLevelLinkNotAvailable { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_TopLevelLinkNotAvailable); + } + } + + /// + /// A string like "The collection is not part of the current entry" + /// + internal static string MaterializeFromAtom_CollectionKeyNotPresentInLinkTable { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_CollectionKeyNotPresentInLinkTable); + } + } + + /// + /// A string like "This response does not contain any nested collections. Use null as Key instead." + /// + internal static string MaterializeFromAtom_GetNestLinkForFlatCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.MaterializeFromAtom_GetNestLinkForFlatCollection); + } + } + + /// + /// A string like "GetStream method is not supported." + /// + internal static string ODataRequestMessage_GetStreamMethodNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataRequestMessage_GetStreamMethodNotSupported); + } + } + + /// + /// A string like "Empty string." + /// + internal static string Util_EmptyString { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Util_EmptyString); + } + } + + /// + /// A string like "Empty array." + /// + internal static string Util_EmptyArray { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Util_EmptyArray); + } + } + + /// + /// A string like "Array contains a null element." + /// + internal static string Util_NullArrayElement { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Util_NullArrayElement); + } + } + + /// + /// A string like "The expression type {0} is not supported." + /// + internal static string ALinq_UnsupportedExpression(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_UnsupportedExpression, p0); + } + + /// + /// A string like "Could not convert constant {0} expression to string." + /// + internal static string ALinq_CouldNotConvert(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CouldNotConvert, p0); + } + + /// + /// A string like "The method '{0}' is not supported." + /// + internal static string ALinq_MethodNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_MethodNotSupported, p0); + } + + /// + /// A string like "The unary operator '{0}' is not supported." + /// + internal static string ALinq_UnaryNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_UnaryNotSupported, p0); + } + + /// + /// A string like "The binary operator '{0}' is not supported." + /// + internal static string ALinq_BinaryNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_BinaryNotSupported, p0); + } + + /// + /// A string like "The constant for '{0}' is not supported." + /// + internal static string ALinq_ConstantNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ConstantNotSupported, p0); + } + + /// + /// A string like "An operation between an expression and a type is not supported." + /// + internal static string ALinq_TypeBinaryNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeBinaryNotSupported); + } + } + + /// + /// A string like "The conditional expression is not supported." + /// + internal static string ALinq_ConditionalNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ConditionalNotSupported); + } + } + + /// + /// A string like "The parameter expression is not supported." + /// + internal static string ALinq_ParameterNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ParameterNotSupported); + } + } + + /// + /// A string like "The member access of '{0}' is not supported." + /// + internal static string ALinq_MemberAccessNotSupported(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_MemberAccessNotSupported, p0); + } + + /// + /// A string like "Lambda Expressions not supported." + /// + internal static string ALinq_LambdaNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_LambdaNotSupported); + } + } + + /// + /// A string like "New Expressions not supported." + /// + internal static string ALinq_NewNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_NewNotSupported); + } + } + + /// + /// A string like "Member Init Expressions not supported." + /// + internal static string ALinq_MemberInitNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_MemberInitNotSupported); + } + } + + /// + /// A string like "List Init Expressions not supported." + /// + internal static string ALinq_ListInitNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ListInitNotSupported); + } + } + + /// + /// A string like "New Array Expressions not supported." + /// + internal static string ALinq_NewArrayNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_NewArrayNotSupported); + } + } + + /// + /// A string like "Invocation Expressions not supported." + /// + internal static string ALinq_InvocationNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_InvocationNotSupported); + } + } + + /// + /// A string like "Can only specify query options (orderby, where, take, skip) after last navigation." + /// + internal static string ALinq_QueryOptionsOnlyAllowedOnLeafNodes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_QueryOptionsOnlyAllowedOnLeafNodes); + } + } + + /// + /// A string like "Expand query option not allowed." + /// + internal static string ALinq_CantExpand { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantExpand); + } + } + + /// + /// A string like "Can't cast to unsupported type '{0}'" + /// + internal static string ALinq_CantCastToUnsupportedPrimitive(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantCastToUnsupportedPrimitive, p0); + } + + /// + /// A string like "Individual properties can only be selected from a single resource or as part of a type. Specify a key predicate to restrict the entity set to a single instance or project the property into a named or anonymous type." + /// + internal static string ALinq_CantNavigateWithoutKeyPredicate { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantNavigateWithoutKeyPredicate); + } + } + + /// + /// A string like "Multiple key predicates cannot be specified for the same entity set." + /// + internal static string ALinq_CanOnlyApplyOneKeyPredicate { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CanOnlyApplyOneKeyPredicate); + } + } + + /// + /// A string like "The expression {0} is not supported." + /// + internal static string ALinq_CantTranslateExpression(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantTranslateExpression, p0); + } + + /// + /// A string like "Error translating Linq expression to URI: {0}" + /// + internal static string ALinq_TranslationError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TranslationError, p0); + } + + /// + /// A string like "Custom query option not allowed." + /// + internal static string ALinq_CantAddQueryOption { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddQueryOption); + } + } + + /// + /// A string like "Can't add duplicate query option '{0}'." + /// + internal static string ALinq_CantAddDuplicateQueryOption(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddDuplicateQueryOption, p0); + } + + /// + /// A string like "Can't add query option '{0}' because it would conflict with the query options from the translated Linq expression." + /// + internal static string ALinq_CantAddAstoriaQueryOption(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddAstoriaQueryOption, p0); + } + + /// + /// A string like "Can't add query option '{0}' because it begins with reserved character '$'." + /// + internal static string ALinq_CantAddQueryOptionStartingWithDollarSign(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantAddQueryOptionStartingWithDollarSign, p0); + } + + /// + /// A string like "Referencing public field '{0}' not supported in query option expression. Use public property instead." + /// + internal static string ALinq_CantReferToPublicField(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CantReferToPublicField, p0); + } + + /// + /// A string like "Cannot specify query options (orderby, where, take, skip, count) on single resource." + /// + internal static string ALinq_QueryOptionsOnlyAllowedOnSingletons { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_QueryOptionsOnlyAllowedOnSingletons); + } + } + + /// + /// A string like "The {0} query option cannot be specified after the {1} query option." + /// + internal static string ALinq_QueryOptionOutOfOrder(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_QueryOptionOutOfOrder, p0, p1); + } + + /// + /// A string like "Cannot add count option to the resource set." + /// + internal static string ALinq_CannotAddCountOption { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotAddCountOption); + } + } + + /// + /// A string like "Cannot add count option to the resource set because it would conflict with existing count options." + /// + internal static string ALinq_CannotAddCountOptionConflict { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotAddCountOptionConflict); + } + } + + /// + /// A string like "Can only specify 'select' query option after last navigation." + /// + internal static string ALinq_ProjectionOnlyAllowedOnLeafNodes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ProjectionOnlyAllowedOnLeafNodes); + } + } + + /// + /// A string like "Cannot translate multiple Linq Select operations in a single 'select' query option." + /// + internal static string ALinq_ProjectionCanOnlyHaveOneProjection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ProjectionCanOnlyHaveOneProjection); + } + } + + /// + /// A string like "Cannot initialize an instance of entity type '{0}' because '{1}' and '{2}' do not refer to the same source entity." + /// + internal static string ALinq_ProjectionMemberAssignmentMismatch(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ProjectionMemberAssignmentMismatch, p0, p1, p2); + } + + /// + /// A string like "The expression '{0}' is not a valid expression for navigation path. The only supported operations inside the lambda expression body are MemberAccess and TypeAs. The expression must contain at least one MemberAccess and it cannot end with TypeAs." + /// + internal static string ALinq_InvalidExpressionInNavigationPath(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_InvalidExpressionInNavigationPath, p0); + } + + /// + /// A string like "Initializing instances of the entity type {0} with the expression {1} is not supported." + /// + internal static string ALinq_ExpressionNotSupportedInProjectionToEntity(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ExpressionNotSupportedInProjectionToEntity, p0, p1); + } + + /// + /// A string like "Constructing or initializing instances of the type {0} with the expression {1} is not supported." + /// + internal static string ALinq_ExpressionNotSupportedInProjection(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ExpressionNotSupportedInProjection, p0, p1); + } + + /// + /// A string like "Construction of entity type instances must use object initializer with default constructor." + /// + internal static string ALinq_CannotConstructKnownEntityTypes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotConstructKnownEntityTypes); + } + } + + /// + /// A string like "Referencing of local entity type instances not supported when projecting results." + /// + internal static string ALinq_CannotCreateConstantEntity { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotCreateConstantEntity); + } + } + + /// + /// A string like "Cannot assign the value from the {0} property to the {1} property. When projecting results into a entity type, the property names of the source type and the target type must match for the properties being projected." + /// + internal static string ALinq_PropertyNamesMustMatchInProjections(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_PropertyNamesMustMatchInProjections, p0, p1); + } + + /// + /// A string like "Can only project the last entity type in the query being translated." + /// + internal static string ALinq_CanOnlyProjectTheLeaf { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CanOnlyProjectTheLeaf); + } + } + + /// + /// A string like "Cannot create projection while there is an explicit expansion specified on the same query." + /// + internal static string ALinq_CannotProjectWithExplicitExpansion { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotProjectWithExplicitExpansion); + } + } + + /// + /// A string like "The collection property '{0}' cannot be used in an 'orderby' query expression. Collection properties are not supported by the 'orderby' query option." + /// + internal static string ALinq_CollectionPropertyNotSupportedInOrderBy(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CollectionPropertyNotSupportedInOrderBy, p0); + } + + /// + /// A string like "The collection property '{0}' cannot be used in a 'where' query expression. Collection properties are only supported as the source of 'any' or 'all' methods in a 'where' query option." + /// + internal static string ALinq_CollectionPropertyNotSupportedInWhere(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CollectionPropertyNotSupportedInWhere, p0); + } + + /// + /// A string like "Navigation to members of the collection property '{0}' in a 'select' query expression is not supported." + /// + internal static string ALinq_CollectionMemberAccessNotSupportedInNavigation(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CollectionMemberAccessNotSupportedInNavigation, p0); + } + + /// + /// A string like "The property '{0}' of type 'DataServiceStreamLink' cannot be used in 'where' or 'orderby' query expressions. Properties of type 'DataServiceStreamLink' are not supported by these query options." + /// + internal static string ALinq_LinkPropertyNotSupportedInExpression(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_LinkPropertyNotSupportedInExpression, p0); + } + + /// + /// A string like "The target type for an OfType filter could not be determined." + /// + internal static string ALinq_OfTypeArgumentNotAvailable { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_OfTypeArgumentNotAvailable); + } + } + + /// + /// A string like "Non-redundant type filters (OfType<T>, C# 'as' and VB 'TryCast') can only be used once per resource set." + /// + internal static string ALinq_CannotUseTypeFiltersMultipleTimes { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_CannotUseTypeFiltersMultipleTimes); + } + } + + /// + /// A string like "Unsupported expression '{0}' in '{1}' method. Expression cannot end with TypeAs." + /// + internal static string ALinq_ExpressionCannotEndWithTypeAs(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_ExpressionCannotEndWithTypeAs, p0, p1); + } + + /// + /// A string like "The expression 'TypeAs' is not supported when MaxProtocolVersion is less than '3.0'." + /// + internal static string ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3 { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeAsNotSupportedForMaxDataServiceVersionLessThan3); + } + } + + /// + /// A string like "The type '{0}' is not an entity type. The target type for a TypeAs operator must be an entity type." + /// + internal static string ALinq_TypeAsArgumentNotEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeAsArgumentNotEntityType, p0); + } + + /// + /// A string like "The source parameter for the '{0}' method has to be either a navigation or a collection property." + /// + internal static string ALinq_InvalidSourceForAnyAll(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_InvalidSourceForAnyAll, p0); + } + + /// + /// A string like "The method '{0}' is not supported by the 'orderby' query option." + /// + internal static string ALinq_AnyAllNotSupportedInOrderBy(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_AnyAllNotSupportedInOrderBy, p0); + } + + /// + /// A string like "The '$format' query option is not supported. Use the DataServiceContext.Format property to set the desired format." + /// + internal static string ALinq_FormatQueryOptionNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_FormatQueryOptionNotSupported); + } + } + + /// + /// A string like "Found the following illegal system token while building a projection or expansion path: '{0}'" + /// + internal static string ALinq_IllegalSystemQueryOption(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_IllegalSystemQueryOption, p0); + } + + /// + /// A string like "Found a projection as a non-leaf segment in an expand path. Please rephrase your query. The projected property was : '{0}'" + /// + internal static string ALinq_IllegalPathStructure(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_IllegalPathStructure, p0); + } + + /// + /// A string like "Found an illegal type token '{0}' without a trailing navigation property." + /// + internal static string ALinq_TypeTokenWithNoTrailingNavProp(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ALinq_TypeTokenWithNoTrailingNavProp, p0); + } + + /// + /// A string like "DataServiceKey attribute must specify at least one property name." + /// + internal static string DSKAttribute_MustSpecifyAtleastOnePropertyName { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DSKAttribute_MustSpecifyAtleastOnePropertyName); + } + } + + /// + /// A string like "Target collection for the Load operation must have an associated DataServiceContext." + /// + internal static string DataServiceCollection_LoadRequiresTargetCollectionObserved { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_LoadRequiresTargetCollectionObserved); + } + } + + /// + /// A string like "The tracking of DataServiceCollection can not be stopped for child collections." + /// + internal static string DataServiceCollection_CannotStopTrackingChildCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_CannotStopTrackingChildCollection); + } + } + + /// + /// A string like "This operation is only supported on collections that are being tracked." + /// + internal static string DataServiceCollection_OperationForTrackedOnly { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_OperationForTrackedOnly); + } + } + + /// + /// A string like "The DataServiceContext to which the DataServiceCollection instance belongs could not be determined. The DataServiceContext must either be supplied in the DataServiceCollection constructor or be used to create the DataServiceQuery or QueryOperationResponse object that is the source of the items in the DataServiceCollection." + /// + internal static string DataServiceCollection_CannotDetermineContextFromItems { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_CannotDetermineContextFromItems); + } + } + + /// + /// A string like "An item could not be added to the collection. When items in a DataServiceCollection are tracked by the DataServiceContext, new items cannot be added before items have been loaded into the collection." + /// + internal static string DataServiceCollection_InsertIntoTrackedButNotLoadedCollection { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_InsertIntoTrackedButNotLoadedCollection); + } + } + + /// + /// A string like "A previous LoadAsync operation has not yet completed. You cannot call the LoadAsync method on the DataServiceCollection again until the previous operation has completed." + /// + internal static string DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); + } + } + + /// + /// A string like "The LoadAsync method cannot be called when the DataServiceCollection is not a child collection of a related entity." + /// + internal static string DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity); + } + } + + /// + /// A string like "Only a typed DataServiceQuery object can be supplied when calling the LoadAsync method on DataServiceCollection." + /// + internal static string DataServiceCollection_LoadAsyncRequiresDataServiceQuery { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceCollection_LoadAsyncRequiresDataServiceQuery); + } + } + + /// + /// A string like "The DataServiceCollection to be tracked must contain entity typed elements with at least one key property. The element type '{0}' does not have any key property." + /// + internal static string DataBinding_DataServiceCollectionArgumentMustHaveEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_DataServiceCollectionArgumentMustHaveEntityType, p0); + } + + /// + /// A string like "Setting an instance of DataServiceCollection to an entity property is disallowed if the instance is already being tracked. Error occurred on property '{0}' for entity type '{1}'." + /// + internal static string DataBinding_CollectionPropertySetterValueHasObserver(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_CollectionPropertySetterValueHasObserver, p0, p1); + } + + /// + /// A string like "Unexpected action '{0}' on the OnCollectionChanged event raised by DataServiceCollection." + /// + internal static string DataBinding_DataServiceCollectionChangedUnknownActionCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_DataServiceCollectionChangedUnknownActionCollection, p0); + } + + /// + /// A string like "Unexpected action '{0}' on the OnCollectionChanged event raised by a collection object of type '{1}'." + /// + internal static string DataBinding_CollectionChangedUnknownActionCollection(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_CollectionChangedUnknownActionCollection, p0, p1); + } + + /// + /// A string like "Add/Update/Delete operation cannot be performed on a child entity, if the parent entity is already detached." + /// + internal static string DataBinding_BindingOperation_DetachedSource { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_BindingOperation_DetachedSource); + } + } + + /// + /// A string like "Null values are disallowed during '{0}' operations on DataServiceCollection." + /// + internal static string DataBinding_BindingOperation_ArrayItemNull(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_BindingOperation_ArrayItemNull, p0); + } + + /// + /// A string like "A value provided during '{0}' operation on DataServiceCollection is not of an entity type with key." + /// + internal static string DataBinding_BindingOperation_ArrayItemNotEntity(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_BindingOperation_ArrayItemNotEntity, p0); + } + + /// + /// A string like "Entity set name has not been provided for an entity of type '{0}'." + /// + internal static string DataBinding_Util_UnknownEntitySetName(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_Util_UnknownEntitySetName, p0); + } + + /// + /// A string like "An attempt was made to add entity of type '{0}' to a collection in which the same entity already exists." + /// + internal static string DataBinding_EntityAlreadyInCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_EntityAlreadyInCollection, p0); + } + + /// + /// A string like "An attempt to track an entity or complex type failed because the entity or complex type '{0}' does not implement the INotifyPropertyChanged interface." + /// + internal static string DataBinding_NotifyPropertyChangedNotImpl(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_NotifyPropertyChangedNotImpl, p0); + } + + /// + /// A string like "An attempt to track an entity or complex type failed because the entity or complex type contains a collection property of type '{0}' that does not implement the INotifyCollectionChanged interface." + /// + internal static string DataBinding_NotifyCollectionChangedNotImpl(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_NotifyCollectionChangedNotImpl, p0); + } + + /// + /// A string like "An attempt to track a complex object of type '{0}' failed because the complex object is already being tracked." + /// + internal static string DataBinding_ComplexObjectAssociatedWithMultipleEntities(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_ComplexObjectAssociatedWithMultipleEntities, p0); + } + + /// + /// A string like "An attempt to track a collection object of type '{0}' failed because the collection object is already being tracked." + /// + internal static string DataBinding_CollectionAssociatedWithMultipleEntities(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataBinding_CollectionAssociatedWithMultipleEntities, p0); + } + + /// + /// A string like "Expected exactly one Atom entry in the response from the server, but none was found." + /// + internal static string AtomParser_SingleEntry_NoneFound { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomParser_SingleEntry_NoneFound); + } + } + + /// + /// A string like "Expected exactly one Atom entry in the response from the server, but more than one was found." + /// + internal static string AtomParser_SingleEntry_MultipleFound { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomParser_SingleEntry_MultipleFound); + } + } + + /// + /// A string like "Expected an Atom feed or entry in the response from the server, but found an unexpected element instead." + /// + internal static string AtomParser_SingleEntry_ExpectedFeedOrEntry { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomParser_SingleEntry_ExpectedFeedOrEntry); + } + } + + /// + /// A string like "The null value from property '{0}' cannot be assigned to a type '{1}'." + /// + internal static string AtomMaterializer_CannotAssignNull(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_CannotAssignNull, p0, p1); + } + + /// + /// A string like "An entry of type '{0}' cannot be added to a collection that contains instances of type '{1}'. This may occur when an existing entry of a different type has the same identity value or when the same entity is projected into two different types in a single query." + /// + internal static string AtomMaterializer_EntryIntoCollectionMismatch(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_EntryIntoCollectionMismatch, p0, p1); + } + + /// + /// A string like "An entry returned by the navigation property '{0}' is null and cannot be initialized. You should check for a null value before accessing this property." + /// + internal static string AtomMaterializer_EntryToAccessIsNull(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_EntryToAccessIsNull, p0); + } + + /// + /// A string like "An entry that contains the data required to create an instance of type '{0}' is null and cannot be initialized. You should check for a null value before accessing this entry." + /// + internal static string AtomMaterializer_EntryToInitializeIsNull(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_EntryToInitializeIsNull, p0); + } + + /// + /// A string like "An entity of type '{0}' cannot be projected because there is already an instance of type '{1}' for '{2}'." + /// + internal static string AtomMaterializer_ProjectEntityTypeMismatch(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_ProjectEntityTypeMismatch, p0, p1, p2); + } + + /// + /// A string like "The expected property '{0}' could not be found while processing an entry. Check for null before accessing this property." + /// + internal static string AtomMaterializer_PropertyMissing(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_PropertyMissing, p0); + } + + /// + /// A string like "Property '{0}' is not an entity." + /// + internal static string AtomMaterializer_PropertyNotExpectedEntry(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_PropertyNotExpectedEntry, p0); + } + + /// + /// A string like "A DataServiceCollection can only contain entity types. Primitive and complex types cannot be contained by this kind of collection." + /// + internal static string AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_DataServiceCollectionNotSupportedForNonEntities); + } + } + + /// + /// A string like "Collection property '{0}' cannot be created because the type '{1}' does not have a public parameterless constructor." + /// + internal static string AtomMaterializer_NoParameterlessCtorForCollectionProperty(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_NoParameterlessCtorForCollectionProperty, p0, p1); + } + + /// + /// A string like "The element '{0}' is not a valid collection item. The name of the collection item element must be 'element' and must belong to the 'http://docs.oasis-open.org/odata/ns/data' namespace." + /// + internal static string AtomMaterializer_InvalidCollectionItem(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidCollectionItem, p0); + } + + /// + /// A string like "There is a type mismatch between the client and the service. Type '{0}' is an entity type, but the type in the response payload does not represent an entity type. Please ensure that types defined on the client match the data model of the service, or update the service reference on the client." + /// + internal static string AtomMaterializer_InvalidEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidEntityType, p0); + } + + /// + /// A string like "There is a type mismatch between the client and the service. Type '{0}' is not an entity type, but the type in the response payload represents an entity type. Please ensure that types defined on the client match the data model of the service, or update the service reference on the client." + /// + internal static string AtomMaterializer_InvalidNonEntityType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidNonEntityType, p0); + } + + /// + /// A string like "Materialization of top level collection expected ICollection<>, but actual type was {0}." + /// + internal static string AtomMaterializer_CollectionExpectedCollection(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_CollectionExpectedCollection, p0); + } + + /// + /// A string like "The response payload is a not a valid response payload. Please make sure that the top level element is a valid Atom or JSON element or belongs to '{0}' namespace." + /// + internal static string AtomMaterializer_InvalidResponsePayload(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidResponsePayload, p0); + } + + /// + /// A string like "The response content type '{0}' is not currently supported." + /// + internal static string AtomMaterializer_InvalidContentTypeEncountered(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_InvalidContentTypeEncountered, p0); + } + + /// + /// A string like "Cannot materialize the results into a collection type '{0}' because it does not have a parameterless constructor." + /// + internal static string AtomMaterializer_MaterializationTypeError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_MaterializationTypeError, p0); + } + + /// + /// A string like "Reset should never be called for collection reader in an internal enumerable." + /// + internal static string AtomMaterializer_ResetAfterEnumeratorCreationError { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_ResetAfterEnumeratorCreationError); + } + } + + /// + /// A string like "Cannot materialize a collection of a primitives or complex without the type '{0}' being a collection." + /// + internal static string AtomMaterializer_TypeShouldBeCollectionError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.AtomMaterializer_TypeShouldBeCollectionError, p0); + } + + /// + /// A string like "A circular loop was detected while serializing the property '{0}'. You must make sure that loops are not present in properties that return a collection or complex type." + /// + internal static string Serializer_LoopsNotAllowedInComplexTypes(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_LoopsNotAllowedInComplexTypes, p0); + } + + /// + /// A string like "A circular loop was detected while serializing the complex type '{0}'. You must make sure that loops are not present in a collection or a complex type." + /// + internal static string Serializer_LoopsNotAllowedInNonPropertyComplexTypes(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_LoopsNotAllowedInNonPropertyComplexTypes, p0); + } + + /// + /// A string like "The operation parameter named '{0}' has a collection item of Edm type kind '{1}'. A collection item must be either a primitive type or a complex Edm type kind." + /// + internal static string Serializer_InvalidCollectionParamterItemType(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_InvalidCollectionParamterItemType, p0, p1); + } + + /// + /// A string like "The operation parameter named '{0}' has a null collection item. The items of a collection must not be null." + /// + internal static string Serializer_NullCollectionParamterItemValue(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_NullCollectionParamterItemValue, p0); + } + + /// + /// A string like "The operation parameter named '{0}' was of Edm type kind '{1}'. An operation parameter must be either a primitive type, a complex type or a collection of primitive or complex types." + /// + internal static string Serializer_InvalidParameterType(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_InvalidParameterType, p0, p1); + } + + /// + /// A string like "The parameter alias '{0}' was not present in the request URI. All parameters passed as alias must be present in the request URI." + /// + internal static string Serializer_UriDoesNotContainParameterAlias(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_UriDoesNotContainParameterAlias, p0); + } + + /// + /// A string like "The enum type '{0}' has no member named '{1}'." + /// + internal static string Serializer_InvalidEnumMemberValue(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Serializer_InvalidEnumMemberValue, p0, p1); + } + + /// + /// A string like "This target framework does not enable you to directly enumerate over a data service query. This is because enumeration automatically sends a synchronous request to the data service. Because this framework only supports asynchronous operations, you must instead call the BeginExecute and EndExecute methods to obtain a query result that supports enumeration." + /// + internal static string DataServiceQuery_EnumerationNotSupported { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceQuery_EnumerationNotSupported); + } + } + + /// + /// A string like "Only instances of HttpWebRequest are currently allowed for this property. Other subtypes of WebRequest are not supported." + /// + internal static string Context_SendingRequestEventArgsNotHttp { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_SendingRequestEventArgsNotHttp); + } + } + + /// + /// A string like "An internal error '{0}' occurred." + /// + internal static string General_InternalError(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.General_InternalError, p0); + } + + /// + /// A string like "The entity set '{0}' doesn't have the 'OData.EntitySetUri' annotation. This annotation is required." + /// + internal static string ODataMetadataBuilder_MissingEntitySetUri(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataMetadataBuilder_MissingEntitySetUri, p0); + } + + /// + /// A string like "The entity set '{0}' has a URI '{1}' which has no path segments. An entity set URI suffix cannot be appended to a URI without path segments." + /// + internal static string ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix, p0, p1); + } + + /// + /// A string like "Neither the 'OData.EntityInstanceUri' nor the 'OData.EntitySetUriSuffix' annotation was found for entity set '{0}'. One of these annotations is required." + /// + internal static string ODataMetadataBuilder_MissingEntityInstanceUri(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ODataMetadataBuilder_MissingEntityInstanceUri, p0); + } + + /// + /// A string like "The type '{0}' was found for a primitive value. In OData, the type '{0}' is not a supported primitive type." + /// + internal static string EdmValueUtils_UnsupportedPrimitiveType(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_UnsupportedPrimitiveType, p0); + } + + /// + /// A string like "Incompatible primitive type kinds were found. The type '{0}' was found to be of kind '{2}' instead of the expected kind '{1}'." + /// + internal static string EdmValueUtils_IncorrectPrimitiveTypeKind(object p0, object p1, object p2) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_IncorrectPrimitiveTypeKind, p0, p1, p2); + } + + /// + /// A string like "Incompatible primitive type kinds were found. Found type kind '{0}' instead of the expected kind '{1}'." + /// + internal static string EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName(object p0, object p1) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName, p0, p1); + } + + /// + /// A string like "A value with primitive kind '{0}' cannot be converted into a primitive object value." + /// + internal static string EdmValueUtils_CannotConvertTypeToClrValue(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.EdmValueUtils_CannotConvertTypeToClrValue, p0); + } + + /// + /// A string like "The value '{0}' is not a valid duration value." + /// + internal static string ValueParser_InvalidDuration(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.ValueParser_InvalidDuration, p0); + } + + /// + /// A string like "The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information." + /// + internal static string PlatformHelper_DateTimeOffsetMustContainTimeZone(object p0) { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.PlatformHelper_DateTimeOffsetMustContainTimeZone, p0); + } + + /// + /// A string like "Failed to get the count value from the server." + /// + internal static string DataServiceRequest_FailGetCount { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.DataServiceRequest_FailGetCount); + } + } + + /// + /// A string like "Execute overload for void service operations and actions received a non-void response from the server." + /// + internal static string Context_ExecuteExpectedVoidResponse { + get { + return Microsoft.OData.Client.TextRes.GetString(Microsoft.OData.Client.TextRes.Context_ExecuteExpectedVoidResponse); + } + } + + } + + /// + /// Strongly-typed and parameterized exception factory. + /// + internal static partial class Error { + + /// + /// The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument. + /// + internal static Exception ArgumentNull(string paramName) { + return new ArgumentNullException(paramName); + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method. + /// + internal static Exception ArgumentOutOfRange(string paramName) { + return new ArgumentOutOfRangeException(paramName); + } + + /// + /// The exception that is thrown when the author has not yet implemented the logic at this point in the program. This can act as an exception based TODO tag. + /// + internal static Exception NotImplemented() { + return new NotImplementedException(); + } + + /// + /// The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality. + /// + internal static Exception NotSupported() { + return new NotSupportedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.tt new file mode 100644 index 0000000..4dbd000 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Parameterized.Microsoft.OData.Client.tt @@ -0,0 +1,20 @@ +<#@ include file="..\..\tools\StringResourceGenerator\StringsClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData.Client"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Client"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.OData.Client.Common.txt", + "Microsoft.OData.Client.Desktop.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPath.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPath.cs new file mode 100644 index 0000000..89ceaf5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPath.cs @@ -0,0 +1,113 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq.Expressions; + using System.Text; + + #endregion Namespaces + + /// Use this class to represent an annotated list of path segments. + [DebuggerDisplay("{ToString()}")] + internal class ProjectionPath : List + { + #region Constructors + + /// Initializes a new instance. + internal ProjectionPath() + { + } + + /// Initializes a new instance. + /// Root parameter for this path. + /// Expression to get the expected root type in the target tree. + /// Expression for the root entry. + internal ProjectionPath(ParameterExpression root, Expression expectedRootType, Expression rootEntry) + { + this.Root = root; + this.RootEntry = rootEntry; + this.ExpectedRootType = expectedRootType; + } + + /// Initializes a new instance. + /// Root parameter for this path. + /// Expression to get the expected root type in the target tree. + /// Expression for the root entry. + /// Member to initialize the path with. + internal ProjectionPath(ParameterExpression root, Expression expectedRootType, Expression rootEntry, IEnumerable members) + : this(root, expectedRootType, rootEntry) + { + Debug.Assert(members != null, "members != null"); + + foreach (Expression member in members) + { + this.Add(new ProjectionPathSegment(this, (MemberExpression)member)); + } + } + + #endregion Constructors + + #region Internal properties + + /// Parameter expression in the source tree. + internal ParameterExpression Root + { + get; + private set; + } + + /// Expression to get the entry for in the target tree. + internal Expression RootEntry + { + get; + private set; + } + + /// Expression to get the expected root type in the target tree. + internal Expression ExpectedRootType + { + get; + private set; + } + + #endregion Internal properties + + #region Methods + + /// Provides a string representation of this object. + /// A string representation of this object, suitable for debugging. + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + builder.Append(this.Root.ToString()); + builder.Append("->"); + for (int i = 0; i < this.Count; i++) + { + if (this[i].SourceTypeAs != null) + { + builder.Insert(0, "("); + builder.Append(" as " + this[i].SourceTypeAs.Name + ")"); + } + + if (i > 0) + { + builder.Append('.'); + } + + builder.Append(this[i].Member == null ? "*" : this[i].Member); + } + + return builder.ToString(); + } + + #endregion Methods + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathBuilder.cs new file mode 100644 index 0000000..2149ea8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathBuilder.cs @@ -0,0 +1,321 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq.Expressions; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Use this class to help keep track of projection paths built + /// while compiling a projection-based materialization plan. + /// + internal class ProjectionPathBuilder + { + #region Private fields + + /// Stack of whether entities are in scope. + private readonly Stack entityInScope; + + /// Registers rewrites for member initialization blocks. + private readonly List rewrites; + + /// Stack of lambda expressions in scope. + private readonly Stack parameterExpressions; + + /// + /// Stack of expected type expression for . + /// + private readonly Stack parameterExpressionTypes; + + /// Stack of 'entry' parameter expressions. + private readonly Stack parameterEntries; + + /// Stack of projection (target-tree) types for parameters. + private readonly Stack parameterProjectionTypes; + + #endregion Private fields + + #region Constructors + + /// Initializes a new instance. + internal ProjectionPathBuilder() + { + this.entityInScope = new Stack(); + this.rewrites = new List(); + this.parameterExpressions = new Stack(); + this.parameterExpressionTypes = new Stack(); + this.parameterEntries = new Stack(); + this.parameterProjectionTypes = new Stack(); + } + + #endregion Constructors + + #region Internal properties + + /// Whether the current scope is acting on an entity. + internal bool CurrentIsEntity + { + get { return this.entityInScope.Peek(); } + } + + /// Expression for the expected type parameter. + internal Expression ExpectedParamTypeInScope + { + get + { + Debug.Assert(this.parameterExpressionTypes.Count > 0, "this.parameterExpressionTypes.Count > 0"); + return this.parameterExpressionTypes.Peek(); + } + } + + /// Whether any rewrites have been registered. + internal bool HasRewrites + { + get { return this.rewrites.Count > 0; } + } + + /// Expression for the entity parameter in the source tree lambda. + internal Expression LambdaParameterInScope + { + get + { + return this.parameterExpressions.Peek(); + } + } + + /// Expression for the entry parameter in the target tree. + internal Expression ParameterEntryInScope + { + get + { + return this.parameterEntries.Peek(); + } + } + + #endregion Internal properties + + #region Methods + + /// Provides a string representation of this object. + /// String representation of this object. + public override string ToString() + { + string result = "ProjectionPathBuilder: "; + if (this.parameterExpressions.Count == 0) + { + result += "(empty)"; + } + else + { + result += + "entity:" + this.CurrentIsEntity + + " param:" + this.ParameterEntryInScope; + } + + return result; + } + + /// Records that a lambda scope has been entered when visiting a projection. + /// Lambda being visited. + /// Expression to the entry parameter from the target tree. + /// Expression to the entry-expected-type from the target tree. + internal void EnterLambdaScope(LambdaExpression lambda, Expression entry, Expression expectedType) + { + Debug.Assert(lambda != null, "lambda != null"); + Debug.Assert(lambda.Parameters.Count == 1, "lambda.Parameters.Count == 1"); + + ParameterExpression param = lambda.Parameters[0]; + Type projectionType = lambda.Body.Type; + bool isEntityType = ClientTypeUtil.TypeOrElementTypeIsEntity(projectionType); + + this.entityInScope.Push(isEntityType); + this.parameterExpressions.Push(param); + this.parameterExpressionTypes.Push(expectedType); + this.parameterEntries.Push(entry); + this.parameterProjectionTypes.Push(projectionType); + } + + /// + /// Records that a member initialization expression has been entered + /// when visting a projection. + /// + /// Expression for initialization. + internal void EnterMemberInit(MemberInitExpression init) + { + bool isEntityType = ClientTypeUtil.TypeOrElementTypeIsEntity(init.Type); + this.entityInScope.Push(isEntityType); + } + + /// Gets a rewrite for the specified expression; null if none is found. + /// Expression to match. + /// A rewrite for the expression; possibly null. + internal Expression GetRewrite(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + + List names = new List(); + + // This could be a MemberAccess wrapped in a type conversion, so strip off unnecessary converions for each loop iteration. + // E.g. (p.BestFriend As Person) instead of just p.BestFriend + expression = ResourceBinder.StripTo(expression); + while (expression.NodeType == ExpressionType.MemberAccess || expression.NodeType == ExpressionType.TypeAs) + { + if (expression.NodeType == ExpressionType.MemberAccess) + { + MemberExpression member = (MemberExpression)expression; + names.Add(member.Member.Name); + expression = ResourceBinder.StripTo(member.Expression); + } + else + { + expression = ResourceBinder.StripTo(((UnaryExpression)expression).Operand); + } + } + + Expression result = null; + foreach (var rewrite in this.rewrites) + { + if (rewrite.Root != expression) + { + continue; + } + + if (names.Count != rewrite.MemberNames.Length) + { + continue; + } + + bool match = true; + for (int i = 0; i < names.Count && i < rewrite.MemberNames.Length; i++) + { + if (names[names.Count - i - 1] != rewrite.MemberNames[i]) + { + match = false; + break; + } + } + + if (match) + { + result = rewrite.RewriteExpression; + break; + } + } + + return result; + } + + /// Records that a lambda scope has been left when visting a projection. + internal void LeaveLambdaScope() + { + this.entityInScope.Pop(); + this.parameterExpressions.Pop(); + this.parameterExpressionTypes.Pop(); + this.parameterEntries.Pop(); + this.parameterProjectionTypes.Pop(); + } + + /// + /// Records that a member initialization expression has been left when + /// visting a projection. + /// + internal void LeaveMemberInit() + { + this.entityInScope.Pop(); + } + + /// Registers a member initialization rewrite. + /// Root of member access path, typically a source tree parameter of entity type. + /// Sequence of names to match. + /// Rewrite expression for names. + internal void RegisterRewrite(Expression root, string[] names, Expression rewriteExpression) + { + Debug.Assert(root != null, "root != null"); + Debug.Assert(names != null, "names != null"); + Debug.Assert(rewriteExpression != null, "rewriteExpression != null"); + + this.rewrites.Add(new MemberInitRewrite() { Root = root, MemberNames = names, RewriteExpression = rewriteExpression }); + this.parameterEntries.Push(rewriteExpression); + } + + /// Revokes the latest rewrite registered on the specified . + /// Root of rewrites to revoke. + /// Names to revoke. + internal void RevokeRewrite(Expression root, string[] names) + { + Debug.Assert(root != null, "root != null"); + + for (int i = 0; i < this.rewrites.Count; i++) + { + if (this.rewrites[i].Root != root) + { + continue; + } + + if (names.Length != this.rewrites[i].MemberNames.Length) + { + continue; + } + + bool match = true; + for (int j = 0; j < names.Length; j++) + { + if (names[j] != this.rewrites[i].MemberNames[j]) + { + match = false; + break; + } + } + + if (match) + { + this.rewrites.RemoveAt(i); + this.parameterEntries.Pop(); + return; + } + } + } + + #endregion Methods + + #region Inner types + + /// Use this class to record how rewrites should occur under nested member initializations. + internal class MemberInitRewrite + { + /// Sequence of member names to match. + internal string[] MemberNames + { + get; + set; + } + + /// Root of member access path, typically a source tree parameter of entity type. + internal Expression Root + { + get; + set; + } + + /// Rewrite expressions for the last member path. + internal Expression RewriteExpression + { + get; + set; + } + } + + #endregion Inner types + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathSegment.cs new file mode 100644 index 0000000..3a28d17 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPathSegment.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Diagnostics; + using System.Linq.Expressions; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Use this class to represent a step in a path of segments + /// over a parsed tree used during projection-driven materialization. + /// + [DebuggerDisplay("Segment {ProjectionType} {Member}")] + internal class ProjectionPathSegment + { + #region Constructors + + /// Initializes a new instance. + /// Path on which this segment is located. + /// Name of member to access when traversing a property; possibly null. + /// + /// Type that we expect to project out; typically the same as , but may be adjusted. + /// + internal ProjectionPathSegment(ProjectionPath startPath, string member, Type projectionType) + { + Debug.Assert(startPath != null, "startPath != null"); + + this.Member = member; + this.StartPath = startPath; + this.ProjectionType = projectionType; + } + + /// Initializes a new instance. + /// Path on which this segment is located. + /// Member expression for the projection path; possibly null. + internal ProjectionPathSegment(ProjectionPath startPath, MemberExpression memberExpression) + { + Debug.Assert(startPath != null, "startPath != null"); + Debug.Assert(memberExpression != null, "memberExpression != null"); + + this.StartPath = startPath; + + Expression source = ResourceBinder.StripTo(memberExpression.Expression); + this.Member = ClientTypeUtil.GetServerDefinedName(memberExpression.Member); + this.ProjectionType = memberExpression.Type; + this.SourceTypeAs = source.NodeType == ExpressionType.TypeAs ? source.Type : null; + } + + #endregion Constructors + + #region Internal properties + + /// Name of member to access when traversing a property; possibly null. + internal string Member + { + get; + private set; + } + + /// + /// Type that we expect to project out; typically the same as , but may be adjusted. + /// + /// + /// In particular, this type will be adjusted for nested narrowing entity types. + /// + /// For example: + /// from c in ctx.Customers select new NarrowCustomer() { + /// ID = c.ID, + /// BestFriend = new NarrowCustomer() { ID = c.BestFriend.ID } + /// } + /// + /// In this case, ID will match types on both sides, but BestFriend + /// will be of type Customer in the member access of the source tree + /// and we want to project out a member-initialized NarrowCustomer + /// in the target tree. + /// + internal Type ProjectionType + { + get; + set; + } + + /// + /// Contains the TypeAs at the source of the member access, null otherwise + /// e.g. (p as Employee).Manager + /// + internal Type SourceTypeAs + { + get; + set; + } + + /// Path on which this segment is located. + internal ProjectionPath StartPath + { + get; + private set; + } + + #endregion Internal properties + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlan.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlan.cs new file mode 100644 index 0000000..059bd59 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlan.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Diagnostics; + using Microsoft.OData; + using Microsoft.OData.Client.Materialization; + + #endregion Namespaces + + /// Use this class to store a materialization plan used with projections. + internal class ProjectionPlan + { +#if DEBUG + /// Source projection for this plan. + internal System.Linq.Expressions.Expression SourceProjection + { + get; + set; + } + + /// Target projection for this plan. + internal System.Linq.Expressions.Expression TargetProjection + { + get; + set; + } +#endif + + /// Last segment type for query. + /// This typically matches the expected element type at runtime. + internal Type LastSegmentType + { + get; + set; + } + + /// Provides a method to materialize a payload. + internal Func Plan + { + get; + set; + } + + /// Expected type to project. + internal Type ProjectedType + { + get; + set; + } + +#if DEBUG + /// Returns a string representation for this object. + /// A string representation for this object, suitable for debugging. + public override string ToString() + { + return "Plan - projection: " + this.SourceProjection + "\r\nBecomes: " + this.TargetProjection; + } +#endif + + /// Runs this plan. + /// Materializer under which materialization should happen. + /// Root entry to materialize. + /// Expected type for the . + /// The materialized object. + internal object Run(ODataEntityMaterializer materializer, ODataResource entry, Type expectedType) + { + Debug.Assert(materializer != null, "materializer != null"); + Debug.Assert(entry != null, "entry != null"); + + return this.Plan(materializer, entry, expectedType); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlanCompiler.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlanCompiler.cs new file mode 100644 index 0000000..dbb58a1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ProjectionPlanCompiler.cs @@ -0,0 +1,1231 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +//// Uncomment the following line to trace projection building activity. +////#define TRACE_CLIENT_PROJECTIONS + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.OData.Client.Materialization; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Use this class to create a for a given projection lambda. + /// + internal class ProjectionPlanCompiler : ALinqExpressionVisitor + { + #region Private fields + + /// Creates dynamic methods that wrap calls to internal methods. + private static readonly DynamicProxyMethodGenerator dynamicProxyMethodGenerator = new DynamicProxyMethodGenerator(); + + /// Annotations being tracked on this tree. + private readonly Dictionary annotations; + + /// Expression that refers to the materializer. + private readonly ParameterExpression materializerExpression; + + /// Tracks rewrite-to-source rewrites introduced by expression normalizer. + private readonly Dictionary normalizerRewrites; + + /// Number to suffix to identifiers to help with debugging. + private int identifierId; + + /// Path builder used to help with tracking state while compiling. + private ProjectionPathBuilder pathBuilder; + + /// Whether the top level projection has been found. + private bool topLevelProjectionFound; + + #endregion Private fields + + #region Constructors + + /// + /// Initializes a new instance. + /// + /// Rewrites introduces by normalizer. + private ProjectionPlanCompiler(Dictionary normalizerRewrites) + { + this.annotations = new Dictionary(ReferenceEqualityComparer.Instance); + this.materializerExpression = Expression.Parameter(typeof(object), "mat"); + this.normalizerRewrites = normalizerRewrites; + this.pathBuilder = new ProjectionPathBuilder(); + } + + #endregion Constructors + + #region Internal methods. + + /// Creates a projection plan from the specified . + /// Projection expression. + /// Tracks rewrite-to-source rewrites introduced by expression normalizer. + /// A new instance. + internal static ProjectionPlan CompilePlan(LambdaExpression projection, Dictionary normalizerRewrites) + { + Debug.Assert(projection != null, "projection != null"); + Debug.Assert(projection.Parameters.Count == 1, "projection.Parameters.Count == 1"); + Debug.Assert( + projection.Body.NodeType == ExpressionType.Constant || + projection.Body.NodeType == ExpressionType.MemberInit || + projection.Body.NodeType == ExpressionType.MemberAccess || + projection.Body.NodeType == ExpressionType.Convert || + projection.Body.NodeType == ExpressionType.ConvertChecked || + projection.Body.NodeType == ExpressionType.New, + "projection.Body.NodeType == Constant, MemberInit, MemberAccess, Convert(Checked) New"); + + ProjectionPlanCompiler rewriter = new ProjectionPlanCompiler(normalizerRewrites); +#if TRACE_CLIENT_PROJECTIONS + Trace.WriteLine("Projection: " + projection); +#endif + + Expression plan = rewriter.Visit(projection); +#if TRACE_CLIENT_PROJECTIONS + Trace.WriteLine("Becomes: " + plan); +#endif + + ProjectionPlan result = new ProjectionPlan(); + result.Plan = (Func)((LambdaExpression)plan).Compile(); + result.ProjectedType = projection.Body.Type; +#if DEBUG + result.SourceProjection = projection; + result.TargetProjection = plan; +#endif + return result; + } + + /// Binary visit method. + /// Binary expression to visit. + /// (Possibly rewritten) binary expression. + /// + /// This override is introduced because binary expressions are one of + /// the scopes at which normalization happens. + /// + internal override Expression VisitBinary(BinaryExpression b) + { + Expression original = this.GetExpressionBeforeNormalization(b); + if (original == b) + { + return base.VisitBinary(b); + } + else + { + return this.Visit(original); + } + } + + /// Visits the specified expression. + /// Expression to check. + /// The visited expression. + /// + /// This override allows us to check for rewrites created by + /// ExpressionNormalizer.CreateCompareExpression. + /// + internal override Expression VisitConditional(ConditionalExpression conditional) + { + Debug.Assert(conditional != null, "conditional != null"); + Expression original = this.GetExpressionBeforeNormalization(conditional); + if (original != conditional) + { + return this.Visit(original); + } + + var nullCheck = ResourceBinder.PatternRules.MatchNullCheck(this.pathBuilder.LambdaParameterInScope, conditional); + if (!nullCheck.Match || !ClientTypeUtil.TypeOrElementTypeIsEntity(ResourceBinder.StripConvertToAssignable(nullCheck.TestToNullExpression).Type)) + { + Expression test = null; + + if (nullCheck.Match) + { + // Suppose we are processing the following query and Vacations is a collectionComplexType property: + // c.CreateQuery("People").Select( + // e => new MyEmployeeEntity + // { + // ID = e.ID, + // Vacations = (e as Manager).Vacations == null ? null : ... + // + // The expression "(e as Manager).Vacations" will get translated to "Convert(ProjectionValueForPath((e as Manager)->.Vacations), ICollection)". + // + // During materialization for a collection property in ProjectionValueForPath(), we add elements of the collection to the + // target object (i.e. MyEmployeeEntity.Vacations) and return the target object instead of activating a new instance of Manager.Vacations. + // However if MyEmployeeEntity.Vacations is of type ICollection, ProjectionValueForPath((e as Manager)->.Vacations) actually returns + // an instance of ICollection and thus converting it to ICollection will result in an InvalidCastException. + // + // Since this is a null check and the type conversion serves no real purpose, we are removing the covert from testToNullExpression. + Expression testToNullExpression = this.Visit(nullCheck.TestToNullExpression); + if (testToNullExpression.NodeType == ExpressionType.Convert) + { + testToNullExpression = ((UnaryExpression)testToNullExpression).Operand; + } + + test = Expression.MakeBinary(ExpressionType.Equal, testToNullExpression, Expression.Constant(null)); + } + + if (test == null) + { + test = this.Visit(conditional.Test); + } + + Expression iftrue = this.Visit(conditional.IfTrue); + Expression iffalse = this.Visit(conditional.IfFalse); + if (test != conditional.Test || iftrue != conditional.IfTrue || iffalse != conditional.IfFalse) + { + return Expression.Condition(test, iftrue, iffalse, iftrue.Type.IsAssignableFrom(iffalse.Type) ? iftrue.Type : iffalse.Type); + } + } + + return this.RebindConditionalNullCheck(conditional, nullCheck); + } + + /// Unary visit method. + /// Unary expression to visit. + /// (Possibly rewritten) unary expression. + /// + /// This override is introduced because unary expressions are one of + /// the scopes at which normalization happens. + /// + internal override Expression VisitUnary(UnaryExpression u) + { + Expression original = this.GetExpressionBeforeNormalization(u); + Expression result; + if (original == u) + { + result = base.VisitUnary(u); + UnaryExpression unaryResult = result as UnaryExpression; + if (unaryResult != null) + { + ExpressionAnnotation annotation; + if (this.annotations.TryGetValue(unaryResult.Operand, out annotation)) + { + this.annotations[result] = annotation; + } + } + } + else + { + result = this.Visit(original); + } + + return result; + } + + /// + /// MemberExpression visit method + /// + /// The MemberExpression expression to visit + /// The visited MemberExpression expression + internal override Expression VisitMemberAccess(MemberExpression m) + { + Debug.Assert(m != null, "m != null"); + + Expression result; + Expression baseSourceExpression = m.Expression; + + // if primitive or nullable primitive, allow member access... i.e. calling Value on nullable + if (PrimitiveType.IsKnownNullableType(baseSourceExpression.Type)) + { + result = base.VisitMemberAccess(m); + } + else + { + Expression baseTargetExpression = this.Visit(baseSourceExpression); + ExpressionAnnotation annotation; + if (this.annotations.TryGetValue(baseTargetExpression, out annotation)) + { + result = this.RebindMemberAccess(m, annotation); + } + else + { + result = Expression.MakeMemberAccess(baseTargetExpression, m.Member); + } + } + + return result; + } + + /// Parameter visit method. + /// Parameter to visit. + /// Resulting expression. + /// + /// The parameter may get rewritten as a materializing projection if + /// it refers to an entity outside of member binding. In this case, + /// it becomes a standalone tracked entity. + /// + internal override Expression VisitParameter(ParameterExpression p) + { + Debug.Assert(p != null, "p != null"); + + // If this parameter isn't interesting, we're not doing any rewrites. + Expression result; + ExpressionAnnotation annotation; + if (this.annotations.TryGetValue(p, out annotation)) + { + result = this.RebindParameter(p, annotation); + } + else + { + result = base.VisitParameter(p); + } + + return result; + } + + /// + /// MemberInitExpression visit method + /// + /// The MemberInitExpression to visit + /// The visited MemberInitExpression + /// + /// A MemberInitExpression on a knownEntityType implies that we + /// want to materialize the thing. + /// + internal override Expression VisitMemberInit(MemberInitExpression init) + { + this.pathBuilder.EnterMemberInit(init); + + Expression result = null; + if (this.pathBuilder.CurrentIsEntity && init.Bindings.Count > 0) + { + result = this.RebindEntityMemberInit(init); + } + else + { + result = base.VisitMemberInit(init); + } + + this.pathBuilder.LeaveMemberInit(); + return result; + } + + /// Visits a method call expression. + /// Expression to visit. + /// A (possibly rewritten) expression for . + internal override Expression VisitMethodCall(MethodCallExpression m) + { + Debug.Assert(m != null, "m != null"); + + Expression original = this.GetExpressionBeforeNormalization(m); + if (original != m) + { + return this.Visit(original); + } + + Expression result; + if (this.pathBuilder.CurrentIsEntity) + { + Debug.Assert( + ProjectionAnalyzer.IsMethodCallAllowedEntitySequence(m) || ResourceBinder.PatternRules.MatchReferenceEquals(m), + "ProjectionAnalyzer.IsMethodCallAllowedEntitySequence(m) || ResourceBinder.PatternRules.MatchReferenceEquals(m) -- otherwise ProjectionAnalyzer should have blocked this for entities"); + if (m.Method.Name == "Select") + { + result = this.RebindMethodCallForMemberSelect(m); + } + else if (m.Method.Name == "ToList") + { + result = this.RebindMethodCallForMemberToList(m); + } + else + { + Debug.Assert(m.Method.Name == "ReferenceEquals", "We don't know how to handle this method, ProjectionAnalyzer updated?"); + result = base.VisitMethodCall(m); + } + } + else + { + if (ProjectionAnalyzer.IsMethodCallAllowedEntitySequence(m)) + { + result = this.RebindMethodCallForNewSequence(m); + } + else + { + result = base.VisitMethodCall(m); + } + } + + return result; + } + + /// + /// Visit + /// + /// Expression to visit + /// an expression + internal override Expression Visit(Expression exp) + { + if (exp == null) + { + // Parent expression may contain null children, in this case just return + return exp; + } + + if (exp.NodeType == ExpressionType.New) + { + NewExpression nex = (NewExpression)exp; + Debug.Assert(nex != null, "nex != null"); + + // Special case DataServiceCollection creation so context instance + // and paging (continuations) propperly flow through + if (ResourceBinder.PatternRules.MatchNewDataServiceCollectionOfT(nex)) + { + return this.RebindNewExpressionForDataServiceCollectionOfT(nex); + } + + return this.VisitNew(nex); + } + + return base.Visit(exp); + } + + /// LambdaExpression visit method. + /// The LambdaExpression to visit + /// The visited LambdaExpression + internal override Expression VisitLambda(LambdaExpression lambda) + { + Debug.Assert(lambda != null, "lambda != null"); + + Expression result; + if (!this.topLevelProjectionFound || lambda.Parameters.Count == 1 && ClientTypeUtil.TypeOrElementTypeIsEntity(lambda.Parameters[0].Type)) + { + this.topLevelProjectionFound = true; + + ParameterExpression expectedTypeParameter = Expression.Parameter(typeof(Type), "type" + this.identifierId); + ParameterExpression entryParameter = Expression.Parameter(typeof(object), "entry" + this.identifierId); + this.identifierId++; + + this.pathBuilder.EnterLambdaScope(lambda, entryParameter, expectedTypeParameter); + ProjectionPath parameterPath = new ProjectionPath(lambda.Parameters[0], expectedTypeParameter, entryParameter); + ProjectionPathSegment parameterSegment = new ProjectionPathSegment(parameterPath, null, null); + parameterPath.Add(parameterSegment); + this.annotations[lambda.Parameters[0]] = new ExpressionAnnotation() { Segment = parameterSegment }; + + Expression body = this.Visit(lambda.Body); + + // Value types must be boxed explicitly; the lambda initialization + // won't do it for us (type-compatible types still work, so all + // references will work fine with System.Object). + if (body.Type.IsValueType()) + { + body = Expression.Convert(body, typeof(object)); + } + + result = Expression.Lambda>( + body, + this.materializerExpression, + entryParameter, + expectedTypeParameter); + + this.pathBuilder.LeaveLambdaScope(); + } + else + { + result = base.VisitLambda(lambda); + } + + return result; + } + + #endregion Internal methods. + + #region Private methods. + + /// Generates a call to a static method on AtomMaterializer. + /// Name of method to invoke. + /// Arguments to pass to method. + /// The constructed expression. + /// + /// There is no support for overload resolution - method names in AtomMaterializer + /// must be unique. + /// + private static Expression CallMaterializer(string methodName, params Expression[] arguments) + { + return CallMaterializerWithType(methodName, null, arguments); + } + + /// Generates a call to a static method on AtomMaterializer. + /// Name of method to invoke. + /// Type arguments for method (possibly null). + /// Arguments to pass to method. + /// The constructed expression. + /// + /// There is no support for overload resolution - method names in AtomMaterializer + /// must be unique. + /// + private static Expression CallMaterializerWithType(string methodName, Type[] typeArguments, params Expression[] arguments) + { + Debug.Assert(methodName != null, "methodName != null"); + Debug.Assert(arguments != null, "arguments != null"); + + MethodInfo method = typeof(ODataEntityMaterializerInvoker).GetMethod(methodName, false /*isPublic*/, true /*isStatic*/); + Debug.Assert(method != null, "method != null - found " + methodName); + if (typeArguments != null) + { + method = method.MakeGenericMethod(typeArguments); + } + + return dynamicProxyMethodGenerator.GetCallWrapper(method, arguments); + } + + /// + /// Rebind a call to DataServiceCollection constructor + /// + /// the constructor info + /// arguments to the constructor + /// An expression that calls the DSC constructor + private static Expression RebindConstructor(ConstructorInfo info, params Expression[] arguments) + { + return dynamicProxyMethodGenerator.GetCallWrapper(info, arguments); + } + + /// Creates an expression that calls ProjectionCheckValueForPathIsNull. + /// Expression for root entry for paths. + /// Expression for expected type for entry. + /// Path to check null value for. + /// A new expression with the call instance. + private Expression CallCheckValueForPathIsNull(Expression entry, Expression entryType, ProjectionPath path) + { + Expression result = CallMaterializer("ProjectionCheckValueForPathIsNull", entry, entryType, Expression.Constant(path, typeof(object))); + this.annotations.Add(result, new ExpressionAnnotation() { Segment = path[path.Count - 1] }); + return result; + } + + /// Creates an expression that calls ProjectionValueForPath. + /// Expression for root entry for paths. + /// Expression for expected type for entry. + /// Path to pull value from. + /// A new expression with the call instance. + private Expression CallValueForPath(Expression entry, Expression entryType, ProjectionPath path) + { + Debug.Assert(entry != null, "entry != null"); + Debug.Assert(path != null, "path != null"); + + Expression result = CallMaterializer("ProjectionValueForPath", this.materializerExpression, entry, entryType, Expression.Constant(path, typeof(object))); + this.annotations.Add(result, new ExpressionAnnotation() { Segment = path[path.Count - 1] }); + return result; + } + + /// Creates an expression that calls ProjectionValueForPath. + /// Expression for root entry for paths. + /// Expression for expected type for entry. + /// Path to pull value from. + /// Path to convert result for. + /// A new expression with the call instance. + private Expression CallValueForPathWithType(Expression entry, Expression entryType, ProjectionPath path, Type type) + { + Debug.Assert(entry != null, "entry != null"); + Debug.Assert(path != null, "path != null"); + + Expression value = this.CallValueForPath(entry, entryType, path); + Expression result = Expression.Convert(value, type); + this.annotations.Add(result, new ExpressionAnnotation() { Segment = path[path.Count - 1] }); + return result; + } + + /// + /// Rebinds a conditional that performs a null check on an entity. + /// + /// Conditional expression. + /// Results of null check analysis. + /// The rebound expression. + /// + /// Do a rewrite to avoid creating a type in the null check: + /// a.b == null ? null : [a.b]-based expression + /// becomes + /// ProjectionIsNull(a.b) ? null : [a.b]-based expression + /// + private Expression RebindConditionalNullCheck(ConditionalExpression conditional, ResourceBinder.PatternRules.MatchNullCheckResult nullCheck) + { + Debug.Assert(conditional != null, "conditional != null"); + Debug.Assert(nullCheck.Match, "nullCheck.Match -- otherwise no reason to call this rebind method"); + + Expression testToNullForProjection = this.Visit(nullCheck.TestToNullExpression); + Expression assignForProjection = this.Visit(nullCheck.AssignExpression); + ExpressionAnnotation testToNullAnnotation; + if (!this.annotations.TryGetValue(testToNullForProjection, out testToNullAnnotation)) + { + return base.VisitConditional(conditional); + } + + ProjectionPathSegment testToNullSegment = testToNullAnnotation.Segment; + + Expression testToNullThroughMethod = this.CallCheckValueForPathIsNull( + testToNullSegment.StartPath.RootEntry, + testToNullSegment.StartPath.ExpectedRootType, + testToNullSegment.StartPath); + + Expression test = testToNullThroughMethod; + Expression iftrue = Expression.Constant(null, assignForProjection.Type); + Expression iffalse = assignForProjection; + Expression result = Expression.Condition(test, iftrue, iffalse); + return result; + } + + /// + /// Rebinds the specified expression by gathering + /// annotated paths and returning an expression that calls the + /// ProjectionGetEntity method. + /// + /// Member initialization expression. + /// A new expression suitable for materialization. + private Expression RebindEntityMemberInit(MemberInitExpression init) + { + Debug.Assert(init != null, "init != null"); + Debug.Assert(init.Bindings.Count > 0, "init.Bindings.Count > 0 -- otherwise this is just empty construction"); + + // We "jump" into entities only if we're not already materializing an entity. + Expression[] expressions; + if (!this.pathBuilder.HasRewrites) + { + MemberAssignmentAnalysis propertyAnalysis = MemberAssignmentAnalysis.Analyze( + this.pathBuilder.LambdaParameterInScope, + ((MemberAssignment)init.Bindings[0]).Expression); + expressions = propertyAnalysis.GetExpressionsToTargetEntity(); + Debug.Assert(expressions.Length != 0, "expressions.Length != 0 -- otherwise there is no correlation to parameter in entity member init"); + } + else + { + expressions = MemberAssignmentAnalysis.EmptyExpressionArray; + } + + Expression entryParameterAtMemberInit = this.pathBuilder.ParameterEntryInScope; + List propertyNames = new List(); + List> propertyFunctions = new List>(); + Type projectedType = init.NewExpression.Type; + Expression projectedTypeExpression = Expression.Constant(projectedType, typeof(Type)); + + // We may need to materialize from deeper in the entity tree for anonymous types. + // t => new { nested = new Nested() { nid = t.nested.nid } + // + // We do the same kind of rewriting we'd do for a nested entity + // but at the initializing scope (rather than at the member assignment scope). + // + // t=> new { nested = ProjInit(GetEntry(entry0, "Nested"), "nid", *->nid) } + Expression entryToInitValue; // Expression that yields value for entry in target tree. + Expression expectedParamValue; // Expression that yield expectedType in target tree. + ParameterExpression entryParameterForMembers; // Parameter expression members think of as "entry". + ParameterExpression expectedParameterForMembers; // Parameter expression members think of as "expectedType" for entry. + string[] expressionNames = expressions.Skip(1).Select(e => ((MemberExpression)e).Member.Name).ToArray(); + if (expressions.Length <= 1) + { + entryToInitValue = this.pathBuilder.ParameterEntryInScope; + expectedParamValue = this.pathBuilder.ExpectedParamTypeInScope; + entryParameterForMembers = (ParameterExpression)this.pathBuilder.ParameterEntryInScope; + expectedParameterForMembers = (ParameterExpression)this.pathBuilder.ExpectedParamTypeInScope; + } + else + { + entryToInitValue = this.GetDeepestEntry(expressions); + expectedParamValue = projectedTypeExpression; + entryParameterForMembers = Expression.Parameter(typeof(object), "subentry" + this.identifierId++); + expectedParameterForMembers = (ParameterExpression)this.pathBuilder.ExpectedParamTypeInScope; + + // Annotate the entry expression with 'how we get to it' information. + // The annotation on entryToInitiValue is picked up + // The annotation on entryParameterForMembers is picked up to build nested member-init on entities. + ProjectionPath entryPath = new ProjectionPath( + (ParameterExpression)this.pathBuilder.LambdaParameterInScope, + this.pathBuilder.ExpectedParamTypeInScope, + this.pathBuilder.ParameterEntryInScope, + expressions.Skip(1)); + + this.annotations.Add(entryToInitValue, new ExpressionAnnotation() { Segment = entryPath[entryPath.Count - 1] }); + this.annotations.Add(entryParameterForMembers, new ExpressionAnnotation() { Segment = entryPath[entryPath.Count - 1] }); + this.pathBuilder.RegisterRewrite(this.pathBuilder.LambdaParameterInScope, expressionNames, entryParameterForMembers); + } + + for (int i = 0; i < init.Bindings.Count; i++) + { + MemberAssignment assignment = (MemberAssignment)init.Bindings[i]; + string memberName = ClientTypeUtil.GetServerDefinedName(assignment.Member); + propertyNames.Add(memberName); + + LambdaExpression propertyLambda; + + // Here are the rewrites we do for member inits: + // new T { id = t.id } + // => ProjInit(pt, "id", f(t -> *.id)); + // + // new T { t2 = new T2 { id2 = *.t2.id2 } } + // => ProjInit(pt, "t2", f(ProjInit(pt->t2), "id2", *.id2))) + if ((ClientTypeUtil.TypeOrElementTypeIsEntity(ClientTypeUtil.GetMemberType(assignment.Member)) && + assignment.Expression.NodeType == ExpressionType.MemberInit)) + { + Expression nestedEntry = CallMaterializer( + "ProjectionGetEntry", + entryParameterAtMemberInit, + Expression.Constant(assignment.Member.Name, typeof(string))); + ParameterExpression nestedEntryParameter = Expression.Parameter( + typeof(object), + "subentry" + this.identifierId++); + + // Register the rewrite from the top to the entry if necessary. + ProjectionPath entryPath; + ExpressionAnnotation entryAnnotation; + if (this.annotations.TryGetValue(this.pathBuilder.ParameterEntryInScope, out entryAnnotation)) + { + entryPath = new ProjectionPath( + (ParameterExpression)this.pathBuilder.LambdaParameterInScope, + this.pathBuilder.ExpectedParamTypeInScope, + entryParameterAtMemberInit); + entryPath.AddRange(entryAnnotation.Segment.StartPath); + } + else + { + entryPath = new ProjectionPath( + (ParameterExpression)this.pathBuilder.LambdaParameterInScope, + this.pathBuilder.ExpectedParamTypeInScope, + entryParameterAtMemberInit, + expressions.Skip(1)); + } +#if PORTABLELIB + Type memberParentType = assignment.Member.DeclaringType; +#else + Type memberParentType = assignment.Member.ReflectedType; +#endif + ProjectionPathSegment nestedSegment = new ProjectionPathSegment( + entryPath, + assignment.Member.Name, + memberParentType); + + entryPath.Add(nestedSegment); + + string[] names = (entryPath.Where(m => m.Member != null).Select(m => m.Member)).ToArray(); + + this.annotations.Add(nestedEntryParameter, new ExpressionAnnotation() { Segment = nestedSegment }); + this.pathBuilder.RegisterRewrite(this.pathBuilder.LambdaParameterInScope, names, nestedEntryParameter); + Expression e = this.Visit(assignment.Expression); + this.pathBuilder.RevokeRewrite(this.pathBuilder.LambdaParameterInScope, names); + this.annotations.Remove(nestedEntryParameter); + + e = Expression.Convert(e, typeof(object)); + ParameterExpression[] parameters = + new ParameterExpression[] + { + this.materializerExpression, + nestedEntryParameter, + expectedParameterForMembers, + }; + propertyLambda = Expression.Lambda(e, parameters); + + Expression[] nestedParams = + new Expression[] + { + this.materializerExpression, + nestedEntry, + expectedParameterForMembers, + }; + var invokeParameters = + new ParameterExpression[] + { + this.materializerExpression, + (ParameterExpression)entryParameterAtMemberInit, + expectedParameterForMembers, + }; + propertyLambda = Expression.Lambda(Expression.Invoke(propertyLambda, nestedParams), invokeParameters); + } + else + { + // We need an expression of object, which might require boxing. + Expression e = this.Visit(assignment.Expression); + e = Expression.Convert(e, typeof(object)); + ParameterExpression[] parameters = + new ParameterExpression[] + { + this.materializerExpression, + entryParameterForMembers, + expectedParameterForMembers, + }; + propertyLambda = Expression.Lambda(e, parameters); + } + +#if TRACE_CLIENT_PROJECTIONS + Trace.WriteLine("Compiling lambda for " + assignment.Member.Name + ": " + propertyLambda); +#endif + propertyFunctions.Add((Func)propertyLambda.Compile()); + } + + // Revoke rewrites used for nested initialization. + for (int i = 1; i < expressions.Length; i++) + { + this.pathBuilder.RevokeRewrite(this.pathBuilder.LambdaParameterInScope, expressionNames); + this.annotations.Remove(entryToInitValue); + this.annotations.Remove(entryParameterForMembers); + } + + Expression reboundExpression = CallMaterializer( + "ProjectionInitializeEntity", + this.materializerExpression, + entryToInitValue, + expectedParamValue, + projectedTypeExpression, + Expression.Constant(propertyNames.ToArray()), + Expression.Constant(propertyFunctions.ToArray())); + + return Expression.Convert(reboundExpression, projectedType); + } + + /// + /// Creates an expression that gets the deepest entry that will be found on the + /// specified (for the target tree). + /// + /// Path of expressions to walk. + /// An expression that invokes ProjectionGetEntry on the target tree. + private Expression GetDeepestEntry(Expression[] path) + { + Debug.Assert(path.Length > 1, "path.Length > 1"); + + Expression result = null; + int pathIndex = 1; + do + { + result = CallMaterializer( + "ProjectionGetEntry", + result ?? this.pathBuilder.ParameterEntryInScope, + Expression.Constant(((MemberExpression)path[pathIndex]).Member.Name, typeof(string))); + pathIndex++; + } + while (pathIndex < path.Length); + + return result; + } + + /// Gets an expression before its rewrite. + /// Expression to check. + /// The expression before normalization. + private Expression GetExpressionBeforeNormalization(Expression expression) + { + Debug.Assert(expression != null, "expression != null"); + if (this.normalizerRewrites != null) + { + Expression original; + if (this.normalizerRewrites.TryGetValue(expression, out original)) + { + expression = original; + } + } + + return expression; + } + + /// Rebinds the specified parameter expression as a path-based access. + /// Expression to rebind. + /// Annotation for the expression to rebind. + /// The rebound expression. + private Expression RebindParameter(Expression expression, ExpressionAnnotation annotation) + { + Debug.Assert(expression != null, "expression != null"); + Debug.Assert(annotation != null, "annotation != null"); + + Expression result; + result = this.CallValueForPathWithType( + annotation.Segment.StartPath.RootEntry, + annotation.Segment.StartPath.ExpectedRootType, + annotation.Segment.StartPath, + expression.Type); + + // Refresh the annotation so the next one that comes along + // doesn't start off with an already-written path. + ProjectionPath parameterPath = new ProjectionPath( + annotation.Segment.StartPath.Root, + annotation.Segment.StartPath.ExpectedRootType, + annotation.Segment.StartPath.RootEntry); + ProjectionPathSegment parameterSegment = new ProjectionPathSegment(parameterPath, null, null); + parameterPath.Add(parameterSegment); + this.annotations[expression] = new ExpressionAnnotation() { Segment = parameterSegment }; + + return result; + } + + /// Rebinds the specified member access expression into a path-based value retrieval method call. + /// Member expression. + /// Annotation for the base portion of the expression. + /// A rebound expression. + private Expression RebindMemberAccess(MemberExpression m, ExpressionAnnotation baseAnnotation) + { + Debug.Assert(m != null, "m != null"); + Debug.Assert(baseAnnotation != null, "baseAnnotation != null"); + + ProjectionPathSegment memberSegment; + + // If we are in nested member-init, we rewrite the property + // accessors that are in the form of top.nested.id to + // nested.id. + Expression baseSourceExpression = m.Expression; + Expression result = this.pathBuilder.GetRewrite(baseSourceExpression); + if (result != null) + { + Expression baseTypeExpression = Expression.Constant(baseSourceExpression.Type, typeof(Type)); + ProjectionPath nestedPath = new ProjectionPath(result as ParameterExpression, baseTypeExpression, result); + ProjectionPathSegment nestedSegment = new ProjectionPathSegment(nestedPath, m); + nestedPath.Add(nestedSegment); + result = this.CallValueForPathWithType(result, baseTypeExpression, nestedPath, m.Type); + } + else + { + // This actually modifies the path for the underlying + // segments, but that shouldn't be a problem. Actually + // we should be able to remove it from the dictionary. + // There should be no aliasing problems, because + // annotations always come from target expression + // that are generated anew (except parameters, + // but those) + memberSegment = new ProjectionPathSegment(baseAnnotation.Segment.StartPath, m); + baseAnnotation.Segment.StartPath.Add(memberSegment); + result = this.CallValueForPathWithType( + baseAnnotation.Segment.StartPath.RootEntry, + baseAnnotation.Segment.StartPath.ExpectedRootType, + baseAnnotation.Segment.StartPath, + m.Type); + } + + return result; + } + + /// Rewrites NewExpression for DataServiceCollection to a constructor proxy method call. + /// The proxy is required for partially trusted appdomains. Paging information is preserved in the materializer. + /// NewExpression to create a collection + /// The rewritten expression. + private Expression RebindNewExpressionForDataServiceCollectionOfT(NewExpression nex) + { + Debug.Assert(nex != null, "nex != null"); + Debug.Assert( + ResourceBinder.PatternRules.MatchNewDataServiceCollectionOfT(nex), + "Called should have checked that the 'new' was for our collection type"); + + NewExpression visited = this.VisitNew(nex); + Expression rebound = null; + ExpressionAnnotation annotation = null; + + if (visited != null) + { + ConstructorInfo constructorInfo = + nex.Type.GetInstanceConstructors(false /*isPublic*/).First( + c => c.GetParameters().Length == 7 && c.GetParameters()[0].ParameterType == typeof(object)); + + Type enumerable = typeof(IEnumerable<>).MakeGenericType(nex.Type.GetGenericArguments()[0]); + + if (visited.Arguments.Count == 1 && visited.Constructor == nex.Type.GetInstanceConstructor(true /*isPublic*/, new[] { enumerable }) && + this.annotations.TryGetValue(visited.Arguments[0], out annotation)) + { + // DataServiceCollection(IEnumerable items) + // -> + // DataServiceCollection(materializer, null, items, TrackingMode.AutoChangeTracking, null, null, null) + rebound = RebindConstructor( + constructorInfo, + this.materializerExpression, + Expression.Constant(null, typeof(DataServiceContext)), + visited.Arguments[0], + Expression.Constant(TrackingMode.AutoChangeTracking, typeof(TrackingMode)), + Expression.Constant(null, typeof(string)), + Expression.Constant(null, typeof(Func)), + Expression.Constant(null, typeof(Func))); + } + else if (visited.Arguments.Count == 2 && + this.annotations.TryGetValue(visited.Arguments[0], out annotation)) + { + // DataServiceCollection(IEnumerable items, TrackingMode trackingMode) + // -> + // DataServiceCollection(materializer, null, items, trackingMode, null, null, null) + rebound = RebindConstructor( + constructorInfo, + this.materializerExpression, + Expression.Constant(null, typeof(DataServiceContext)), + visited.Arguments[0], // items + visited.Arguments[1], // TrackingMode + Expression.Constant(null, typeof(string)), + Expression.Constant(null, typeof(Func)), + Expression.Constant(null, typeof(Func))); + } + else if (visited.Arguments.Count == 5 && + this.annotations.TryGetValue(visited.Arguments[0], out annotation)) + { + // DataServiceCollection( + // IEnumerable items, + // TrackingMode trackingMode, + // string entitySet, + // Func<> entityChangedCallback, + // Func<> entityCollectionChangedCallback) + // -> + // DataServiceCollection(materializer, null, items, trackingMode, + // entitySet, entityChangedCallback, entityCollectionChangedCallback) + rebound = RebindConstructor( + constructorInfo, + this.materializerExpression, + Expression.Constant(null, typeof(DataServiceContext)), + visited.Arguments[0], // items + visited.Arguments[1], // TrackingMode + visited.Arguments[2], // entityset name + visited.Arguments[3], // entity changed cb + visited.Arguments[4]); // collection changed cb + } + else if (visited.Arguments.Count == 6 && + typeof(DataServiceContext).IsAssignableFrom(visited.Arguments[0].Type) && + this.annotations.TryGetValue(visited.Arguments[1], out annotation)) + { + // DataServiceCollection( + // DataServiceContext context, + // IEnumerable items, + // TrackingMode trackingMode, + // string entitySet, + // Func<> entityChangedCallback, + // Func<> entityCollectionChangedCallback) + // -> + // DataServiceCollection(materializer, context, items, trackingMode, + // entitySet, entityChangedCallback, entityCollectionChangedCallback) + rebound = RebindConstructor( + constructorInfo, + this.materializerExpression, + visited.Arguments[0], // context + visited.Arguments[1], // items + visited.Arguments[2], // trackingMode + visited.Arguments[3], // entityset name + visited.Arguments[4], // entity changed cb + visited.Arguments[5]); // collection changed cb + } + } + + if (annotation != null) + { + // Propagate the annotation of the "items" parameter (the enumerable) as the annotation + // of the DataServiceCollection, since it now represents the same thing. + this.annotations.Add(visited, annotation); + } + + // Note that we end up just falling through without changing anything for + // constructors of DataServiceCollection that aren't correlated (i.e. don't take + // an enumerable in the input). + return rebound; + } + + /// Rewrites a call to Select() by adding to the current paths to project out. + /// Call expression. + /// Expression with annotated path to include in member binding. + private Expression RebindMethodCallForMemberSelect(MethodCallExpression call) + { + Debug.Assert(call != null, "call != null"); + Debug.Assert(call.Method.Name == "Select", "call.Method.Name == 'Select'"); + Debug.Assert(call.Object == null, "call.Object == null -- otherwise this isn't a call to a static Select method"); + Debug.Assert(call.Arguments.Count == 2, "call.Arguments.Count == 2 -- otherwise this isn't the expected Select() call on IQueryable"); + + // Get the path for the parameter value that will be used in the Select lambda. + Expression result = null; + Expression parameterSource = this.Visit(call.Arguments[0]); + ExpressionAnnotation annotation; + this.annotations.TryGetValue(parameterSource, out annotation); + + // It's possibly that we haven't annotated this argument, in which + // case we don't care about this select, eg new { a = new int[] {1}.Select(i=>i+1).First() } + if (annotation != null) + { + LambdaExpression le = call.Arguments[1] as LambdaExpression; + ParameterExpression pe = le.Parameters.Last(); + Expression selectorExpression = this.Visit(call.Arguments[1]); + if (ClientTypeUtil.TypeOrElementTypeIsEntity(pe.Type)) + { + // With this information from an annotation: + // {t->*.Players} + // + // Call this lambda: + // {(mat, entry1, type1) => Convert(ProjectionValueForPath(mat, entry1, type1, p->*.FirstName))} + // + // Annotating the intermediate expressions so we mark them + // as rewrites and we know to route them through materializer + // helpers, eg to preserve paging information. + Type returnElementType = call.Method.ReturnType.GetGenericArguments()[0]; + result = CallMaterializer( + "ProjectionSelect", + this.materializerExpression, + this.pathBuilder.ParameterEntryInScope, + this.pathBuilder.ExpectedParamTypeInScope, + Expression.Constant(returnElementType, typeof(Type)), + Expression.Constant(annotation.Segment.StartPath, typeof(object)), + selectorExpression); + this.annotations.Add(result, annotation); + result = CallMaterializerWithType( + "EnumerateAsElementType", + new Type[] { returnElementType }, + result); + this.annotations.Add(result, annotation); + } + else + { + result = Expression.Call(call.Method, parameterSource, selectorExpression); + this.annotations.Add(result, annotation); + } + } + + if (result == null) + { + result = base.VisitMethodCall(call); + } + + return result; + } + + /// Rewrites a call to ToList in the specified method. + /// Call expression. + /// Expression with annotated path to include in member binding. + /// + /// All that is necessary here is to rewrite the call to Select() and indicate + /// that the target type is a given List<T>. + /// + /// TODO: we're not bubbling this all the way to the GetOrCreateCollection method - does it matter? + /// + private Expression RebindMethodCallForMemberToList(MethodCallExpression call) + { + Debug.Assert(call != null, "call != null"); + Debug.Assert(call.Object == null, "call.Object == null -- otherwise this isn't a call to a static ToList method"); + Debug.Assert(call.Method.Name == "ToList", "call.Method.Name == 'ToList'"); + + // Wrap the source to the .ToList() call if it's our rewrite. + Debug.Assert(call.Arguments.Count == 1, "call.Arguments.Count == 1 -- otherwise this isn't the expected ToList() call on IEnumerable"); + + Expression result = this.Visit(call.Arguments[0]); + ExpressionAnnotation annotation; + if (this.annotations.TryGetValue(result, out annotation)) + { + result = this.TypedEnumerableToList(result, call.Type); + this.annotations.Add(result, annotation); + } + + return result; + } + + /// Rewrites a method call used in a sequence method (possibly over entity types). + /// Call expression. + /// Expression that can be called directly to yield the expected value. + private Expression RebindMethodCallForNewSequence(MethodCallExpression call) + { + Debug.Assert(call != null, "call != null"); + Debug.Assert(ProjectionAnalyzer.IsMethodCallAllowedEntitySequence(call), "ProjectionAnalyzer.IsMethodCallAllowedEntitySequence(call)"); + Debug.Assert(call.Object == null, "call.Object == null -- otherwise this isn't the supported Select or ToList methods"); + + // The only expressions that require rewriting are: + // - [null].Select(entity-type-based-expression, lambda) + // - [null].ToList(entity-type-enumeration) + // + // All other expressions can be visited normally. + Expression result = null; + + if (call.Method.Name == "Select") + { + Debug.Assert(call.Arguments.Count == 2, "call.Arguments.Count == 2 -- otherwise this isn't the argument we expected"); + + // Get the path for the parameter value that will be used in the Select lambda. + Expression parameterSource = this.Visit(call.Arguments[0]); + ExpressionAnnotation annotation; + this.annotations.TryGetValue(parameterSource, out annotation); + + // It's possibly that we haven't annotated this argument, in which + // case we don't care about this select, eg new { a = new int[] {1}.Select(i=>i+1).First() } + if (annotation != null) + { + LambdaExpression le = call.Arguments[1] as LambdaExpression; + ParameterExpression pe = le.Parameters.Last(); + Expression selectorExpression = this.Visit(call.Arguments[1]); + if (ClientTypeUtil.TypeOrElementTypeIsEntity(pe.Type)) + { + // With this information from an annotation: + // {t->*.Players} + // + // Call this lambda: + // {(mat, entry1, type1) => Convert(ProjectionValueForPath(mat, entry1, type1, p->*.FirstName))} + Type returnElementType = call.Method.ReturnType.GetGenericArguments()[0]; + result = CallMaterializer( + "ProjectionSelect", + this.materializerExpression, + this.pathBuilder.ParameterEntryInScope, + this.pathBuilder.ExpectedParamTypeInScope, + Expression.Constant(returnElementType, typeof(Type)), + Expression.Constant(annotation.Segment.StartPath, typeof(object)), + selectorExpression); + this.annotations.Add(result, annotation); + result = CallMaterializerWithType( + "EnumerateAsElementType", + new Type[] { returnElementType }, + result); + this.annotations.Add(result, annotation); + } + else + { + result = Expression.Call(call.Method, parameterSource, selectorExpression); + this.annotations.Add(result, annotation); + } + } + } + else + { + Debug.Assert(call.Method.Name == "ToList", "call.Method.Name == 'ToList'"); + + // Annotate the source to the .ToList() call. + Expression source = this.Visit(call.Arguments[0]); + ExpressionAnnotation annotation; + if (this.annotations.TryGetValue(source, out annotation)) + { + result = this.TypedEnumerableToList(source, call.Type); + this.annotations.Add(result, annotation); + } + } + + if (result == null) + { + result = base.VisitMethodCall(call); + } + + return result; + } + + /// Returns a method call that returns a list from a typed enumerable. + /// Expression to convert. + /// Target type to return. + /// The new expression. + private Expression TypedEnumerableToList(Expression source, Type targetType) + { + Debug.Assert(source != null, "source != null"); + Debug.Assert(targetType != null, "targetType != null"); + + // CONSIDER: change to a generic thing to pull the IEnumerable than just assuming it's always + // generic (so a property can be of class ClientList : List). + Type enumeratedType = source.Type.GetGenericArguments()[0]; + Type listElementType = targetType.GetGenericArguments()[0]; + + // Return the annotated expression. + Expression result = CallMaterializerWithType( + "ListAsElementType", + new Type[] { enumeratedType, listElementType }, + this.materializerExpression, + source); + + return result; + } + + #endregion Private methods. + + #region Inner types + + /// Annotates an expression, typically from the target tree. + internal class ExpressionAnnotation + { + /// Segment that marks the path found to an expression. + internal ProjectionPathSegment Segment + { + get; + set; + } + } + + #endregion Inner types + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponse.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponse.cs new file mode 100644 index 0000000..c1fa090 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponse.cs @@ -0,0 +1,183 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Response to a batched query. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1010", Justification = "required for this feature")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710", Justification = "required for this feature")] + public class QueryOperationResponse : OperationResponse, System.Collections.IEnumerable + { + #region Private fields + + /// Original query + private readonly DataServiceRequest query; + + /// Enumerable of objects in query + private readonly MaterializeAtom results; + + #endregion Private fields + + /// + /// constructor + /// + /// HTTP headers + /// original query + /// retrieved objects + internal QueryOperationResponse(HeaderCollection headers, DataServiceRequest query, MaterializeAtom results) + : base(headers) + { + this.query = query; + this.results = results; + } + + /// Gets the that generates the items. + /// A object. + public DataServiceRequest Query + { + get { return this.query; } + } + + /// Gets the server result set count value from a query, if the query has requested the value. + /// The return value can be either a zero or positive value equal to the number of entities in the set on the server. + /// Thrown when the count tag is not found in the response stream. + public virtual long TotalCount + { + get + { + throw new NotSupportedException(); + } + } + + /// get a non-null enumerable of the result + internal MaterializeAtom Results + { + get + { + if (null != this.Error) + { + throw Microsoft.OData.Client.Error.InvalidOperation(Strings.Context_BatchExecuteError, this.Error); + } + + return this.results; + } + } + + /// Executes the and returns items. + /// The enumerator to a collection of items. + /// In the case of Collection(primitive) or Collection(complex), the entire collection is + /// materialized when this is called. + public IEnumerator GetEnumerator() + { + return this.GetEnumeratorHelper(() => this.Results.GetEnumerator()); + } + + /// Gets a object containing the URI that is used to retrieve the next results page. + /// An object containing the URI that is used to return the next results page. + public DataServiceQueryContinuation GetContinuation() + { + return this.results.GetContinuation(null); + } + + /// Gets a object containing the URI that is used to retrieve the next page of related entities in the specified collection. + /// A continuation object that points to the next page for the collection. + /// The collection of related objects being loaded. + public DataServiceQueryContinuation GetContinuation(IEnumerable collection) + { + return this.results.GetContinuation(collection); + } + + /// Gets a object that contains the URI that is used to retrieve the next page of related entities in the specified collection. + /// A continuation object that points to the next page for the collection. + /// The collection of related objects being loaded. + /// The type of the items in the collection. + public DataServiceQueryContinuation GetContinuation(IEnumerable collection) + { + return (DataServiceQueryContinuation)this.results.GetContinuation(collection); + } + + /// + /// Creates a generic instance of the QueryOperationResponse and return it + /// + /// generic type for the QueryOperationResponse. + /// constructor parameter1 + /// constructor parameter2 + /// constructor parameter3 + /// returns a new strongly typed instance of QueryOperationResponse. + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] + internal static QueryOperationResponse GetInstance(Type elementType, HeaderCollection headers, DataServiceRequest query, MaterializeAtom results) + { + Type genericType = typeof(QueryOperationResponse<>).MakeGenericType(elementType); +#if !PORTABLELIB + return (QueryOperationResponse)Activator.CreateInstance( + genericType, + BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Instance, + null, + new object[] { headers, query, results }, + System.Globalization.CultureInfo.InvariantCulture); +#else + ConstructorInfo info = genericType.GetInstanceConstructors(false /*isPublic*/).Single(); + return (QueryOperationResponse)Util.ConstructorInvoke(info, new object[] { headers, query, results }); +#endif + } + + /// Gets the enumeration helper for the . + /// The enumerator. + /// The generic type. + /// An enumerator to enumerator through the results. + protected T GetEnumeratorHelper(Func getEnumerator) where T : IEnumerator + { + if (getEnumerator == null) + { + throw new ArgumentNullException("getEnumerator"); + } + + if (this.Results.Context != null) + { + bool? singleResult = this.Query.QueryComponents(this.Results.Context.Model).SingleResult; + + if (singleResult.HasValue && !singleResult.Value) + { + // Case: collection of complex or primitive. + // We materialize the entire collection now, and give them the inner enumerator instead + IEnumerator enumerator = this.Results.GetEnumerator(); + if (enumerator.MoveNext()) + { + object innerObject = enumerator.Current; + ICollection materializedCollection = innerObject as ICollection; + + if (materializedCollection == null) + { + throw new DataServiceClientException(Strings.AtomMaterializer_CollectionExpectedCollection(innerObject.GetType().ToString())); + } + + Debug.Assert(!enumerator.MoveNext(), "MaterializationEvents of top level collection expected one result of ICollection<>, but found at least 2 results"); + return (T)materializedCollection.GetEnumerator(); + } + else + { + Debug.Assert(false, "MaterializationEvents of top level collection expected one result of ICollection<>, but found at least no results."); + } + } + } + + return getEnumerator(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponseOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponseOfT.cs new file mode 100644 index 0000000..c9d1f6c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryOperationResponseOfT.cs @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Response to a batched query or Execute call. + /// + /// The type to construct for the request results + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710", Justification = "required for this feature")] + public sealed class QueryOperationResponse : QueryOperationResponse, IEnumerable + { + #region Constructors + + /// + /// constructor + /// + /// HTTP headers + /// original query + /// retrieved objects + internal QueryOperationResponse(HeaderCollection headers, DataServiceRequest query, MaterializeAtom results) + : base(headers, query, results) + { + } + + #endregion Constructors + + #region Public properties + + /// The server result set count value from a query, if the query has requested the value. + /// The return value can be either zero or a positive value equal to the number of entities in the set on the server. + public override long TotalCount + { + get + { + if (this.Results != null && this.Results.IsCountable) + { + return this.Results.CountValue(); + } + else + { + throw new InvalidOperationException(Strings.MaterializeFromAtom_CountNotPresent); + } + } + } + + #endregion Public properties + + #region Public methods + + /// Gets a object that contains the URI that is used to retrieve the next results page. + /// An object that contains the URI that is used to return the next results page. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "required for this feature")] + public new DataServiceQueryContinuation GetContinuation() + { + return (DataServiceQueryContinuation)base.GetContinuation(); + } + + /// Executes the and gets items. + /// An enumerator to a collection of items. + /// In the case of Collection(primitive) or Collection(complex), the entire collection is + /// materialized when this is called. + public new IEnumerator GetEnumerator() + { + return this.GetEnumeratorHelper>(() => this.Results.Cast().GetEnumerator()); + } + + #endregion Public methods. + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryResult.cs new file mode 100644 index 0000000..ed928d0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/QueryResult.cs @@ -0,0 +1,741 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData; + + /// + /// Wrapper HttpWebRequest & HttWebResponse + /// + internal class QueryResult : BaseAsyncResult + { + /// Originating service request + internal readonly DataServiceRequest ServiceRequest; + + /// The request info. + internal readonly RequestInfo RequestInfo; + + /// Originating WebRequest + internal readonly ODataRequestMessageWrapper Request; + + /// reusuable async copy buffer + private static byte[] reusableAsyncCopyBuffer; + + /// content to write to request stream + private ContentStream requestContentStream; + + /// web response, closed when completed + private IODataResponseMessage responseMessage; + + /// Response info once it's available + private ResponseInfo responseInfo; + + /// buffer when copying async stream to response stream cache + private byte[] asyncStreamCopyBuffer; + + /// response stream, returned to other parts of the system + /// with async, the asyncResponseStream is copied into this stream + private Stream outputResponseStream; + + /// copy of HttpWebResponse.ContentType + private string contentType; + + /// copy of HttpWebResponse.ContentLength + private long contentLength; + + /// copy of HttpWebResponse.StatusCode + private HttpStatusCode statusCode; + + /// + /// does this own the response stream or does the container of this QueryAsyncResult? + /// + private bool responseStreamOwner; + + /// + /// if the BeginRead has been called with asyncStreamCopyBuffer, but EndRead has not. + /// do not return the buffer to general pool if any question of it being in use. + /// + private bool usingBuffer; + + /// constructor + /// source object of async request + /// async method name on source object + /// Originating serviceRequest + /// Originating WebRequest + /// The request info of the originating request. + /// user callback + /// user state + internal QueryResult(object source, string method, DataServiceRequest serviceRequest, ODataRequestMessageWrapper request, RequestInfo requestInfo, AsyncCallback callback, object state) + : base(source, method, callback, state) + { + Debug.Assert(null != request, "null request"); + this.ServiceRequest = serviceRequest; + this.Request = request; + this.RequestInfo = requestInfo; + this.Abortable = request; + } + + /// constructor + /// source object of async request + /// async method name on source object + /// Originating serviceRequest + /// Originating WebRequest + /// The request info of the originating request. + /// user callback + /// user state + /// the stream containing the request data. + internal QueryResult(object source, string method, DataServiceRequest serviceRequest, ODataRequestMessageWrapper request, RequestInfo requestInfo, AsyncCallback callback, object state, ContentStream requestContentStream) + : this(source, method, serviceRequest, request, requestInfo, callback, state) + { + Debug.Assert(null != requestContentStream, "null requestContentStream"); + this.requestContentStream = requestContentStream; + } + + #region HttpResponse wrapper - ContentLength, ContentType, StatusCode + + /// HttpWebResponse.ContentLength + internal long ContentLength + { + get { return this.contentLength; } + } + + /// HttpWebResponse.ContentType + internal string ContentType + { + get { return this.contentType; } + } + + /// HttpWebResponse.StatusCode + internal HttpStatusCode StatusCode + { + get { return this.statusCode; } + } + + #endregion + + /// + /// Ends the asynchronous query request. + /// + /// Element type of the result. + /// Source object of async request. + /// async method name. + /// The asyncResult being ended. + /// Data service response. + internal static QueryResult EndExecuteQuery(object source, string method, IAsyncResult asyncResult) + { + QueryResult response = null; + + try + { + response = BaseAsyncResult.EndExecute(source, method, asyncResult); + } + catch (InvalidOperationException ex) + { + response = asyncResult as QueryResult; + Debug.Assert(response != null, "response != null, BaseAsyncResult.EndExecute() would have thrown a different exception otherwise."); + + QueryOperationResponse operationResponse = response.GetResponse(MaterializeAtom.EmptyResults); + if (operationResponse != null) + { + operationResponse.Error = ex; + throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, operationResponse); + } + + throw; + } + + return response; + } + + /// wrapper for HttpWebResponse.GetResponseStream + /// stream + internal Stream GetResponseStream() + { + return this.outputResponseStream; + } + + /// start the asynchronous request + internal void BeginExecuteQuery() + { + IAsyncResult asyncResult = null; + + PerRequest pereq = new PerRequest(); + AsyncStateBag asyncStateBag = new AsyncStateBag(pereq); + pereq.Request = this.Request; + + this.perRequest = pereq; + + try + { + if (this.requestContentStream != null && this.requestContentStream.Stream != null) + { + if (this.requestContentStream.IsKnownMemoryStream) + { + this.Request.SetContentLengthHeader(); + } + + this.perRequest.RequestContentStream = this.requestContentStream; + asyncResult = BaseAsyncResult.InvokeAsync(this.Request.BeginGetRequestStream, this.AsyncEndGetRequestStream, asyncStateBag); + } + else + { + asyncResult = BaseAsyncResult.InvokeAsync(this.Request.BeginGetResponse, this.AsyncEndGetResponse, asyncStateBag); + } + + // TODO: Async execute methods for query (QueryResult.cs), should not need to maintain "CompletedSynchronously" information in two state variables. + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + this.SetCompletedSynchronously(asyncResult.CompletedSynchronously); + } + catch (Exception e) + { + this.HandleFailure(e); + throw; + } + finally + { + this.HandleCompleted(pereq); + } + + Debug.Assert((!this.CompletedSynchronously && !pereq.RequestCompletedSynchronously) || this.IsCompleted, "if CompletedSynchronously then MUST IsCompleted"); + } + +#if !PORTABLELIB + /// Synchronous web request + internal void ExecuteQuery() + { + try + { + if (this.requestContentStream != null && this.requestContentStream.Stream != null) + { + this.Request.SetRequestStream(this.requestContentStream); + } +#if false + if ((null != requestContent) && (0 < requestContent.Length)) + { + using (System.IO.Stream stream = Util.NullCheck(this.Request.GetRequestStream(), InternalError.InvalidGetRequestStream)) + { + byte[] buffer = requestContent.GetBuffer(); + int bufferOffset = checked((int)requestContent.Position); + int bufferLength = checked((int)requestContent.Length) - bufferOffset; + + // the following is useful in the debugging Immediate Window + // string x = System.Text.Encoding.UTF8.GetString(buffer, bufferOffset, bufferLength); + stream.Write(buffer, bufferOffset, bufferLength); + } + } +#endif + IODataResponseMessage response = null; + response = this.RequestInfo.GetSyncronousResponse(this.Request, true); + this.SetHttpWebResponse(Util.NullCheck(response, InternalError.InvalidGetResponse)); + + if (HttpStatusCode.NoContent != this.StatusCode) + { + using (Stream stream = this.responseMessage.GetStream()) + { + if (null != stream) + { + Stream copy = this.GetAsyncResponseStreamCopy(); + this.outputResponseStream = copy; + + Byte[] buffer = this.GetAsyncResponseStreamCopyBuffer(); + + long copied = WebUtil.CopyStream(stream, copy, ref buffer); + if (this.responseStreamOwner) + { + if (0 == copied) + { + this.outputResponseStream = null; + } + else if (copy.Position < copy.Length) + { // In Silverlight, generally 3 bytes less than advertised by ContentLength are read + ((MemoryStream)copy).SetLength(copy.Position); + } + } + + this.PutAsyncResponseStreamCopyBuffer(buffer); + } + } + } + } + catch (Exception e) + { + this.HandleFailure(e); + throw; + } + finally + { + this.SetCompleted(); + this.CompletedRequest(); + } + + if (null != this.Failure) + { + throw this.Failure; + } + } +#endif + + /// + /// Returns the response for the request. + /// + /// materialized results for the request. + /// element type of the results. + /// returns the instance of QueryOperationResponse containing the response. + internal QueryOperationResponse GetResponse(MaterializeAtom results) + { + if (this.responseMessage != null) + { + HeaderCollection headers = new HeaderCollection(this.responseMessage); + QueryOperationResponse response = new QueryOperationResponse(headers, this.ServiceRequest, results); + response.StatusCode = (int)this.responseMessage.StatusCode; + return response; + } + + return null; + } + + /// + /// Returns the response for the request. + /// + /// materialized results for the request. + /// element type of the results. + /// returns the instance of QueryOperationResponse containing the response. + internal QueryOperationResponse GetResponseWithType(MaterializeAtom results, Type elementType) + { + if (this.responseMessage != null) + { + HeaderCollection headers = new HeaderCollection(this.responseMessage); + QueryOperationResponse response = QueryOperationResponse.GetInstance(elementType, headers, this.ServiceRequest, results); + response.StatusCode = (int)this.responseMessage.StatusCode; + return response; + } + + return null; + } + + /// + /// Create materializer on top of response stream + /// + /// Precompiled projection plan (possibly null). + /// A materializer instance ready to deserialize ther result + internal MaterializeAtom GetMaterializer(ProjectionPlan plan) + { + Debug.Assert(this.IsCompletedInternally, "request hasn't completed yet"); + + MaterializeAtom materializer; + if (HttpStatusCode.NoContent != this.StatusCode) + { + Debug.Assert(this.responseInfo != null, "The request didn't complete yet, we don't have a response info for it."); + materializer = this.CreateMaterializer(plan, ODataPayloadKind.Unsupported); + } + else + { + materializer = MaterializeAtom.EmptyResults; + } + + return materializer; + } + + /// + /// Processes the result for successfull request and produces the actual result of the request. + /// + /// Element type of the result. + /// The plan to use for the projection, if available in precompiled form. + /// A instance of QueryResponseResult created on top of of the request. + internal QueryOperationResponse ProcessResult(ProjectionPlan plan) + { + Debug.Assert(this.responseInfo != null, "The request didn't complete yet, we don't have a response info for it."); + MaterializeAtom materializeAtom = this.CreateMaterializer(plan, this.ServiceRequest.PayloadKind); + var response = this.GetResponse(materializeAtom); + + // When query feed, the instance annotation can be materialized only when enumerating the feed. + // So we register this action which will be called when enumerating the feed. + materializeAtom.SetInstanceAnnotations = (instanceAnnotations) => + { + if (!this.responseInfo.Context.InstanceAnnotations.ContainsKey(response) + && instanceAnnotations != null && instanceAnnotations.Count > 0) + { + this.responseInfo.Context.InstanceAnnotations.Add(response, instanceAnnotations); + } + }; + return response; + } + + /// cleanup work to do once the request has completed + protected override void CompletedRequest() + { + byte[] buffer = this.asyncStreamCopyBuffer; + this.asyncStreamCopyBuffer = null; + + if ((null != buffer) && !this.usingBuffer) + { + this.PutAsyncResponseStreamCopyBuffer(buffer); + } + + if (this.responseStreamOwner) + { + if (null != this.outputResponseStream) + { + this.outputResponseStream.Position = 0; + } + } + + Debug.Assert(null != this.responseMessage || null != this.Failure || this.IsAborted, "should have response or exception"); + if (null != this.responseMessage) + { + // we've cached off what we need, headers still accessible after close + WebUtil.DisposeMessage(this.responseMessage); + + Version responseVersion; + Exception ex = SaveResult.HandleResponse( + this.RequestInfo, + this.StatusCode, + this.responseMessage.GetHeader(XmlConstants.HttpODataVersion), + this.GetResponseStream, + false, + out responseVersion); + if (null != ex) + { + this.HandleFailure(ex); + } + else + { + this.responseInfo = this.CreateResponseInfo(); + } + } + } + + /// + /// Create the ResponseInfo. + /// + /// ResponseInfo object. + protected virtual ResponseInfo CreateResponseInfo() + { + return this.RequestInfo.GetDeserializationInfo(null); + } + + /// get stream which of copy buffer (via response stream) will be copied into + /// writtable stream, happens before GetAsyncResponseStreamCopyBuffer + protected virtual Stream GetAsyncResponseStreamCopy() + { + this.responseStreamOwner = true; + + long length = this.contentLength; + if ((0 < length) && (length <= Int32.MaxValue)) + { + Debug.Assert(null == this.asyncStreamCopyBuffer, "not expecting buffer"); + + // If more content is returned than specified we want the memory + // stream to be expandable which doesn't happen if you preallocate a buffer + return new MemoryStream((int)length); + } + + return new MemoryStream(); + } + + /// get buffer which response stream will be copied into + /// writtable stream + protected virtual byte[] GetAsyncResponseStreamCopyBuffer() + { // consider having a cache of these buffers since they will be pinned + Debug.Assert(null == this.asyncStreamCopyBuffer, "non-null this.asyncStreamCopyBuffer"); + return System.Threading.Interlocked.Exchange(ref reusableAsyncCopyBuffer, null) ?? new byte[8000]; + } + + /// returning a buffer after being done with it + /// buffer to return + protected virtual void PutAsyncResponseStreamCopyBuffer(byte[] buffer) + { + reusableAsyncCopyBuffer = buffer; + } + + /// set the http web response + /// response object + protected virtual void SetHttpWebResponse(IODataResponseMessage response) + { + this.responseMessage = response; + this.statusCode = (HttpStatusCode)response.StatusCode; + string stringContentLength = response.GetHeader(XmlConstants.HttpContentLength); + if (stringContentLength != null) + { + this.contentLength = int.Parse(stringContentLength, CultureInfo.InvariantCulture); + } + else + { + // Since the unintialized value of ContentLength header is -1, we need to return + // -1 if the content length header is not present + this.contentLength = -1; + } + + this.contentType = response.GetHeader(XmlConstants.HttpContentType); + } + + /// Disposes the request object if it is not null. Invokes the user callback + /// the request object + protected override void HandleCompleted(PerRequest pereq) + { + if (null != pereq) + { + this.SetCompletedSynchronously(pereq.RequestCompletedSynchronously); + + if (pereq.RequestCompleted) + { + System.Threading.Interlocked.CompareExchange(ref this.perRequest, null, pereq); + pereq.Dispose(); + } + } + + this.HandleCompleted(); + } + + /// handle request.BeginGetResponse with request.EndGetResponse and then copy response stream + /// async result + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + protected override void AsyncEndGetResponse(IAsyncResult asyncResult) + { + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted"); + AsyncStateBag asyncStateBag = asyncResult.AsyncState as AsyncStateBag; + + PerRequest pereq = asyncStateBag == null ? null : asyncStateBag.PerRequest; + + try + { + if (this.IsAborted) + { + if (pereq != null) + { + pereq.SetComplete(); + } + + this.SetCompleted(); + } + else + { + this.CompleteCheck(pereq, InternalError.InvalidEndGetResponseCompleted); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + this.SetCompletedSynchronously(asyncResult.CompletedSynchronously); + + ODataRequestMessageWrapper requestMessage = Util.NullCheck(pereq.Request, InternalError.InvalidEndGetResponseRequest); + + // the httpWebResponse is kept for batching, discarded by non-batch + IODataResponseMessage response = this.RequestInfo.EndGetResponse(requestMessage, asyncResult); + pereq.ResponseMessage = Util.NullCheck(response, InternalError.InvalidEndGetResponseResponse); + + this.SetHttpWebResponse(pereq.ResponseMessage); + + Debug.Assert(null == pereq.ResponseStream, "non-null async ResponseStream"); + Stream httpResponseStream = null; + + if (HttpStatusCode.NoContent != (HttpStatusCode)response.StatusCode) + { + httpResponseStream = response.GetStream(); + pereq.ResponseStream = httpResponseStream; + } + + if ((null != httpResponseStream) && httpResponseStream.CanRead) + { + if (null == this.outputResponseStream) + { + // this is the stream we copy the reponse to + this.outputResponseStream = Util.NullCheck(this.GetAsyncResponseStreamCopy(), InternalError.InvalidAsyncResponseStreamCopy); + } + + if (null == this.asyncStreamCopyBuffer) + { + // this is the buffer we read into and copy out of + this.asyncStreamCopyBuffer = Util.NullCheck(this.GetAsyncResponseStreamCopyBuffer(), InternalError.InvalidAsyncResponseStreamCopyBuffer); + } + + // Make async calls to read the response stream + this.ReadResponseStream(asyncStateBag); + } + else + { + pereq.SetComplete(); + this.SetCompleted(); + } + } + } + catch (Exception e) + { + if (this.HandleFailure(e)) + { + throw; + } + } + finally + { + this.HandleCompleted(pereq); + } + } + + /// verify non-null and not completed + /// async result + /// error code if null or completed + protected override void CompleteCheck(PerRequest pereq, InternalError errorcode) + { + if ((null == pereq) || ((pereq.RequestCompleted || this.IsCompletedInternally) && !(this.IsAborted || pereq.RequestAborted))) + { + // if aborting, let the request throw its abort code + Error.ThrowInternalError(errorcode); + } + } + + /// + /// Make async calls to read the response stream. + /// + /// the state containing the information about the asynchronous operation. + private void ReadResponseStream(AsyncStateBag asyncStateBag) + { + Debug.Assert(asyncStateBag != null, "asyncStateBag != null"); + PerRequest pereq = asyncStateBag.PerRequest; + IAsyncResult asyncResult = null; + + byte[] buffer = this.asyncStreamCopyBuffer; + Stream httpResponseStream = pereq.ResponseStream; + + do + { + int bufferOffset = 0; + int bufferLength = buffer.Length; + + this.usingBuffer = true; +#if PORTABLELIB + asyncResult = BaseAsyncResult.InvokeTask(httpResponseStream.ReadAsync, buffer, bufferOffset, bufferLength, this.AsyncEndRead, asyncStateBag); +#else + asyncResult = BaseAsyncResult.InvokeAsync(httpResponseStream.BeginRead, buffer, bufferOffset, bufferLength, this.AsyncEndRead, asyncStateBag); +#endif + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + this.SetCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginRead + } + while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && httpResponseStream.CanRead); + + Debug.Assert((!this.CompletedSynchronously && !pereq.RequestCompletedSynchronously) || this.IsCompletedInternally || pereq.RequestCompleted, "AsyncEndGetResponse !IsCompleted"); + } + +#if PORTABLELIB + /// Handle responseStream.BeginRead and complete the read operation. + /// Task that has completed. + /// State associated with the Task. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + private void AsyncEndRead(Task task, object asyncState) +#else + /// handle responseStream.BeginRead with responseStream.EndRead + /// async result + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")] + private void AsyncEndRead(IAsyncResult asyncResult) +#endif + { +#if PORTABLELIB + IAsyncResult asyncResult = (IAsyncResult)task; +#endif + Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted"); +#if PORTABLELIB + AsyncStateBag asyncStateBag = asyncState as AsyncStateBag; +#else + AsyncStateBag asyncStateBag = asyncResult.AsyncState as AsyncStateBag; +#endif + PerRequest pereq = asyncStateBag == null ? null : asyncStateBag.PerRequest; + + int count = 0; + try + { + this.CompleteCheck(pereq, InternalError.InvalidEndReadCompleted); + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); // BeginRead + this.SetCompletedSynchronously(asyncResult.CompletedSynchronously); + + Stream httpResponseStream = Util.NullCheck(pereq.ResponseStream, InternalError.InvalidEndReadStream); // get the http response stream. + + Stream outResponseStream = Util.NullCheck(this.outputResponseStream, InternalError.InvalidEndReadCopy); + + byte[] buffer = Util.NullCheck(this.asyncStreamCopyBuffer, InternalError.InvalidEndReadBuffer); +#if PORTABLELIB + count = ((Task)task).Result; +#else + count = httpResponseStream.EndRead(asyncResult); +#endif + this.usingBuffer = false; + + if (0 < count) + { + outResponseStream.Write(buffer, 0, count); + } + + if (0 < count && 0 < buffer.Length && httpResponseStream.CanRead) + { + if (!asyncResult.CompletedSynchronously) + { + // if CompletedSynchronously then caller will call and we reduce risk of stack overflow + this.ReadResponseStream(asyncStateBag); + } + } + else + { + // Debug.Assert(this.ContentLength < 0 || outResponseStream.Length == this.ContentLength, "didn't read expected ContentLength"); + if (outResponseStream.Position < outResponseStream.Length) + { + // In Silverlight, generally 3 bytes less than advertised by ContentLength are read + ((MemoryStream)outResponseStream).SetLength(outResponseStream.Position); + } + + pereq.SetComplete(); + this.SetCompleted(); + } + } + catch (Exception e) + { + if (this.HandleFailure(e)) + { + throw; + } + } + finally + { + this.HandleCompleted(pereq); + } + } + + /// + /// Creates an instance of for the given plan. + /// + /// The projection plan. + /// expected payload kind. + /// A new materializer instance + private MaterializeAtom CreateMaterializer(ProjectionPlan plan, ODataPayloadKind payloadKind) + { + QueryComponents queryComponents = this.ServiceRequest.QueryComponents(this.responseInfo.Model); + + // In V2, in projection path, we did not check for assignability between the expected type and the type returned by the type resolver. + if (plan != null || queryComponents.Projection != null) + { + this.RequestInfo.TypeResolver.IsProjectionRequest(); + } + + var responseMessageWrapper = new HttpWebResponseMessage( + new HeaderCollection(this.responseMessage), + this.responseMessage.StatusCode, + this.GetResponseStream); + + return DataServiceRequest.Materialize( + this.responseInfo, + queryComponents, + plan, + this.ContentType, + responseMessageWrapper, + payloadKind); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingEntryArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingEntryArgs.cs new file mode 100644 index 0000000..c810b94 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingEntryArgs.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// The reading entry args + /// + public sealed class ReadingEntryArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The entry. + public ReadingEntryArgs(ODataResource entry) + { + this.Entry = entry; + } + + /// + /// Gets the entry. + /// + /// + /// The entry. + /// + public ODataResource Entry { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingFeedArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingFeedArgs.cs new file mode 100644 index 0000000..4c6da42 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingFeedArgs.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// The reading feed arguments + /// + public sealed class ReadingFeedArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The feed. + public ReadingFeedArgs(ODataResourceSet feed) + { + Util.CheckArgumentNull(feed, "feed"); + this.Feed = feed; + } + + /// + /// Gets the feed. + /// + /// + /// The feed. + /// + public ODataResourceSet Feed { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingNestedResourceInfoArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingNestedResourceInfoArgs.cs new file mode 100644 index 0000000..cb0409e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingNestedResourceInfoArgs.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// The reading navigation link arguments + /// + public sealed class ReadingNestedResourceInfoArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The link. + public ReadingNestedResourceInfoArgs(ODataNestedResourceInfo link) + { + Util.CheckArgumentNull(link, "link"); + this.Link = link; + } + + /// + /// Gets the link. + /// + /// + /// The link. + /// + public ODataNestedResourceInfo Link { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingWritingEntityEventArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingWritingEntityEventArgs.cs new file mode 100644 index 0000000..be6fb22 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReadingWritingEntityEventArgs.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Xml.Linq; + + /// + /// Event args for the event fired during reading or writing of + /// an entity serialization/deserialization + /// + public sealed class ReadingWritingEntityEventArgs : EventArgs + { + /// The entity being (de)serialized + private object entity; + + /// The ATOM entry data to/from the network + private XElement data; + + /// The xml base of the feed or entry containing the current ATOM entry + private Uri baseUri; + + /// + /// Constructor + /// + /// The entity being (de)serialized + /// The ATOM entry data to/from the network + /// The xml base of the feed or entry containing the current ATOM entry + internal ReadingWritingEntityEventArgs(object entity, XElement data, Uri baseUri) + { + Debug.Assert(entity != null, "entity != null"); + Debug.Assert(data != null, "data != null"); + Debug.Assert(baseUri == null || baseUri.IsAbsoluteUri, "baseUri == null || baseUri.IsAbsoluteUri"); + + this.entity = entity; + this.data = data; + this.baseUri = baseUri; + } + + /// Gets the object representation of data returned from the property. + /// representation of the property. + public object Entity + { + get { return this.entity; } + } + + /// Gets an entry or feed data represented as an . + /// + /// + /// + public XElement Data + { + [DebuggerStepThrough] + get { return this.data; } + } + + /// Gets the base URI base of the entry or feed. + /// Returns . + public Uri BaseUri + { + [DebuggerStepThrough] + get { return this.baseUri; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReceivingResponseEventArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReceivingResponseEventArgs.cs new file mode 100644 index 0000000..1e12392 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReceivingResponseEventArgs.cs @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using Microsoft.OData; + + /// + /// class for the event. + /// Exposes the ResponseMessage to the user. + /// + public class ReceivingResponseEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class for a + /// non-batch or top level $batch response. + /// + /// The response message the client is receiving. + /// Descriptor for the request that the client is receiving the response for. + public ReceivingResponseEventArgs(IODataResponseMessage responseMessage, Descriptor descriptor) + : this(responseMessage, descriptor, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The response message the client is receiving. + /// Descriptor for the request that the client is receiving the response for. + /// Indicates if this response is to an inner batch query or operation. + public ReceivingResponseEventArgs(IODataResponseMessage responseMessage, Descriptor descriptor, bool isBatchPart) + { + this.ResponseMessage = responseMessage; + this.Descriptor = descriptor; + this.IsBatchPart = isBatchPart; + } + + /// + /// Gets the response message that the client is receiving. + /// + public IODataResponseMessage ResponseMessage { get; private set; } + + /// + /// True if the response is an inner batch operation or query; false otherwise. + /// + public bool IsBatchPart { get; private set; } + + /// + /// Descriptor for the request that the client is receiving the response for. + /// The descriptor may be null for certain types of requests, like most GET requests + /// and the top level $batch request. + /// + public Descriptor Descriptor { get; private set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReferenceEqualityComparer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReferenceEqualityComparer.cs new file mode 100644 index 0000000..0d61131 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ReferenceEqualityComparer.cs @@ -0,0 +1,171 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +//// #define NON_GENERIC_AVAILABLE + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Service +#endif +{ + #region Namespaces + + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + + #endregion Namespaces + + /// Equality comparer implementation that uses reference equality. + internal class ReferenceEqualityComparer : IEqualityComparer + { + #region Private fields + +#if NON_GENERIC_AVAILABLE + /// Singleton instance (non-generic, as opposed to the one in ReferenceEqualityComparer<T>. + private static ReferenceEqualityComparer nonGenericInstance; +#endif + + #endregion Private fields + + #region Constructors + + /// Initializes a new instance. + protected ReferenceEqualityComparer() + { + } + + #endregion Constructors + + #region Properties + + /// Determines whether two objects are the same. + /// First object to compare. + /// Second object to compare. + /// true if both are the same; false otherwise. + bool IEqualityComparer.Equals(object x, object y) + { + return object.ReferenceEquals(x, y); + } + + /// Serves as hashing function for collections. + /// Object to hash. + /// + /// Hash code for the object; shouldn't change through the lifetime + /// of . + /// + int IEqualityComparer.GetHashCode(object obj) + { + if (obj == null) + { + return 0; + } + + return obj.GetHashCode(); + } + +#if NON_GENERIC_AVAILABLE + /// Singleton instance (non-generic, as opposed to the one in ReferenceEqualityComparer<T>. + internal ReferenceEqualityComparer NonGenericInstance + { + get + { + if (nonGenericInstance == null) + { + ReferenceEqualityComparer comparer = new ReferenceEqualityComparer(); + System.Threading.Interlocked.CompareExchange(ref nonGenericInstance, comparer, null); + } + + return nonGenericInstance; + } + } +#endif + + #endregion Properties + } + + /// + /// Use this class to compare objects by reference in collections such as + /// dictionary or hashsets. + /// + /// Type of objects to compare. + /// + /// Typically accesses statically as eg + /// ReferenceEqualityComparer<Expression>.Instance. + /// + internal sealed class ReferenceEqualityComparer : ReferenceEqualityComparer, IEqualityComparer + { + #region Private fields + + /// Single instance per 'T' for comparison. + private static ReferenceEqualityComparer instance; + + #endregion Private fields + + #region Constructors + + /// Initializes a new instance. + private ReferenceEqualityComparer() + { + } + + #endregion Constructors + + #region Properties + + /// Returns a singleton instance for this comparer type. + internal static ReferenceEqualityComparer Instance + { + get + { + if (instance == null) + { +#if ODATA_SERVICE + Debug.Assert(!typeof(T).IsValueType, "!typeof(T).IsValueType -- can't use reference equality in a meaningful way with value types"); +#else + Debug.Assert(!typeof(T).IsValueType(), "!typeof(T).IsValueType() -- can't use reference equality in a meaningful way with value types"); +#endif + ReferenceEqualityComparer newInstance = new ReferenceEqualityComparer(); + System.Threading.Interlocked.CompareExchange(ref instance, newInstance, null); + } + + return instance; + } + } + + #endregion Properties + + #region Methods + + /// Determines whether two objects are the same. + /// First object to compare. + /// Second object to compare. + /// true if both are the same; false otherwise. + public bool Equals(T x, T y) + { + return object.ReferenceEquals(x, y); + } + + /// Serves as hashing function for collections. + /// Object to hash. + /// + /// Hash code for the object; shouldn't change through the lifetime + /// of . + /// + public int GetHashCode(T obj) + { + if (obj == null) + { + return 0; + } + + return obj.GetHashCode(); + } + + #endregion Methods + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/RequestInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/RequestInfo.cs new file mode 100644 index 0000000..a95f925 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/RequestInfo.cs @@ -0,0 +1,497 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.CodeDom.Compiler; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Xml.Linq; + using System.Net; + using Microsoft.OData; + using Microsoft.OData.Client.Metadata; + + /// + /// Class which wraps the dataservicecontext and exposes information required for + /// generating request to send to the server + /// + internal class RequestInfo + { + /// The type resolver for the current request. + private readonly TypeResolver typeResolver; + + /// + /// Creates a new instance of RequestInfo class which is used to build the request to send to the server + /// + /// wrapping context instance. + /// Whether this is a continuation request. + internal RequestInfo(DataServiceContext context, bool isContinuation) + : this(context) + { + this.IsContinuation = isContinuation; + } + + /// + /// Creates a new instance of RequestInfo class which is used to build the request to send to the server + /// + /// wrapping context instance. + internal RequestInfo(DataServiceContext context) + { + Debug.Assert(context != null, "context != null"); + this.Context = context; + this.WriteHelper = new ODataMessageWritingHelper(this); + this.typeResolver = new TypeResolver(context.Model, context.ResolveTypeFromName, context.ResolveNameFromTypeInternal, context.Format.ServiceModel); + } + + #region Properties + + /// The writing helper to use. + internal ODataMessageWritingHelper WriteHelper { get; private set; } + + /// context instance. + internal DataServiceContext Context { get; private set; } + + /// + /// Whether this is a continuation request. + /// + internal bool IsContinuation { get; private set; } + + /// Gets the configurations. + internal DataServiceClientConfigurations Configurations + { + get { return this.Context.Configurations; } + } + + /// Returns the instance of entity tracker class which tracks all the entities and links for the context. + internal EntityTracker EntityTracker + { + get { return this.Context.EntityTracker; } + } + + /// Whether to ignore extra properties in the response payload. + internal bool IgnoreResourceNotFoundException + { + get { return this.Context.IgnoreResourceNotFoundException; } + } + + /// True if the context's ResolveName property has been set, otherwise false. + internal bool HasResolveName + { + get { return this.Context.ResolveName != null; } + } + + /// True if the context's ResolveName property can be determined to be a user-supplied value, instead of the one provided by codegen. + internal bool IsUserSuppliedResolver + { + get + { + Debug.Assert(this.Context.ResolveName != null, "this.context.ResolveName != null."); +#if PORTABLELIB + // Func<>.Method property does not exist on Win8 and there is no other way to access any MethodInfo that is behind the Func, + // so we have no way to determine if the Func was supplied by the user or if it's the one provided by codegen. + // In this case we will always assume it's the one provided by codegen, which means we'll try to resolve the name using the entity descriptor + // first. This is likely to be correct in more cases than using the codegen resolver, so it is safer to make this assumption than the reverse. + return false; +#else + MethodInfo resolveNameMethodInfo = this.Context.ResolveName.Method; + var codegenAttr = resolveNameMethodInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + return codegenAttr == null || codegenAttr.Tool != Util.CodeGeneratorToolName; +#endif + } + } + + /// Gets the BaseUriResolver + internal UriResolver BaseUriResolver + { + get { return this.Context.BaseUriResolver; } + } + + /// Gets the response preference for Add and Update operations. + internal DataServiceResponsePreference AddAndUpdateResponsePreference + { + get { return this.Context.AddAndUpdateResponsePreference; } + } + + /// The maximum protocol version the client should understand. + internal Version MaxProtocolVersionAsVersion + { + get { return this.Context.MaxProtocolVersionAsVersion; } + } + + /// + /// Returns true if there are subscribers to SendingRequest event. + /// + internal bool HasSendingRequest2EventHandlers + { + get { return this.Context.HasSendingRequest2EventHandlers; } + } + + /// + /// True if the user could have modified a part of the request. This lets us turn off assertions that normally + /// prevent us from making certain mistakes we don't mind the user intentionally ignoring. + /// + internal bool UserModifiedRequestInBuildingRequest + { + get { return this.Context.HasBuildingRequestEventHandlers; } + } + + /// + /// Gets the authentication information used by each query created using the context object. + /// + internal System.Net.ICredentials Credentials + { + get { return this.Context.Credentials; } + } + +#if !PORTABLELIB + /// + /// Get the timeout span in seconds to use for the underlying HTTP request to the data service. + /// + internal int Timeout + { + get { return this.Context.Timeout; } + } +#endif + + /// + /// Whether to use post-tunneling for PUT/DELETE. + /// + internal bool UsePostTunneling + { + get { return this.Context.UsePostTunneling; } + } + + /// + /// Gets the client model. + /// + internal ClientEdmModel Model + { + get { return this.Context.Model; } + } + + /// + /// Gets the tracker for the user-specified format to use for requests. + /// + internal DataServiceClientFormat Format + { + get { return this.Context.Format; } + } + + /// + /// Gets the type resolver. + /// + internal TypeResolver TypeResolver + { + get { return this.typeResolver; } + } + + /// + /// The HTTP stack to use in Silverlight. + /// + internal HttpStack HttpStack + { + get { return this.Context.HttpStack; } + } + + /// + /// Gets a System.Boolean value that controls whether default credentials are sent with requests. + /// + internal bool UseDefaultCredentials + { + get { return this.Context.UseDefaultCredentials; } + } + #endregion Properties + + #region Methods + +#if !PORTABLELIB + /// + /// This method wraps the HttpWebRequest.GetSyncronousResponse method call. The reasons for doing this are to give us a place + /// to invoke internal test hook callbacks that can validate the response headers, and also so that we can do + /// debug validation to make sure that the headers have not changed since they were originally configured on the request. + /// + /// ODataRequestMessageWrapper instance + /// If set to true, this method will only re-throw the WebException that was caught if + /// the response in the exception is null. If set to false, this method will always re-throw in case of a WebException. + /// + /// Returns the HttpWebResponse from the wrapped GetSyncronousResponse method. + /// + internal IODataResponseMessage GetSyncronousResponse(ODataRequestMessageWrapper request, bool handleWebException) + { + return this.Context.GetSyncronousResponse(request, handleWebException); + } +#endif + + /// + /// This method wraps the HttpWebRequest.EndGetResponse method call. The reason for doing this is to give us a place + /// to invoke internal test hook callbacks that can validate the response headers. + /// + /// HttpWebRequest instance + /// Async result obtained from previous call to BeginGetResponse. + /// Returns the HttpWebResponse from the wrapped EndGetResponse method. + internal IODataResponseMessage EndGetResponse(ODataRequestMessageWrapper request, IAsyncResult asyncResult) + { + return this.Context.EndGetResponse(request, asyncResult); + } + + /// + /// Get the server type name - either from the entity descriptor or using the type resolver. + /// + /// The entity descriptor. + /// The server type name for the entity. + internal string GetServerTypeName(EntityDescriptor descriptor) + { + Debug.Assert(descriptor != null && descriptor.Entity != null, "Null descriptor or no entity in descriptor"); + string serverTypeName = null; + + if (this.HasResolveName) + { + Type entityType = descriptor.Entity.GetType(); + if (this.IsUserSuppliedResolver) + { + // User-supplied resolver, must call first + serverTypeName = this.ResolveNameFromType(entityType) ?? descriptor.GetLatestServerTypeName(); + } + else + { + // V2+ codegen resolver, called last + serverTypeName = descriptor.GetLatestServerTypeName() ?? this.ResolveNameFromType(entityType); + } + } + else + { + serverTypeName = descriptor.GetLatestServerTypeName(); + } + + return serverTypeName; + } + + /// + /// Get the server type name - either from the entity descriptor or using the type resolver. + /// + /// Client type annotation. + /// The server type name for the entity. + internal string GetServerTypeName(ClientTypeAnnotation clientTypeAnnotation) + { + string serverTypeName = this.ResolveNameFromType(clientTypeAnnotation.ElementType); + + return serverTypeName; + } + + /// + /// Infers the server type name for the entity tracked in the given descriptor based on the server model. + /// + /// The descriptor containing the entity to get the type name for. + /// The type name or null if it could not be inferred. + internal string InferServerTypeNameFromServerModel(EntityDescriptor descriptor) + { + Debug.Assert(descriptor != null, "descriptor != null"); + Debug.Assert(descriptor.ServerTypeName == null, "Should not be called if the server type name is already known."); + + if (descriptor.EntitySetName != null) + { + string serverTypeName; + if (this.TypeResolver.TryResolveEntitySetBaseTypeName(descriptor.EntitySetName, out serverTypeName)) + { + return serverTypeName; + } + } + else if (descriptor.IsDeepInsert) + { + string parentServerTypeName = this.GetServerTypeName(descriptor.ParentForInsert); + if (parentServerTypeName == null) + { + parentServerTypeName = this.InferServerTypeNameFromServerModel(descriptor.ParentForInsert); + } + + string serverTypeName; + if (this.TypeResolver.TryResolveNavigationTargetTypeName(parentServerTypeName, descriptor.ParentPropertyForInsert, out serverTypeName)) + { + return serverTypeName; + } + } + + return null; + } + + /// + /// The reverse of ResolveType, use for complex types and LINQ query expression building + /// + /// client type + /// type for the server + internal string ResolveNameFromType(Type type) + { + return this.Context.ResolveNameFromTypeInternal(type); + } + + /// + /// Returns the instance of ResponseInfo class, which provides all the information for response handling. + /// + /// merge option to use for handling the response conflicts. + /// If this parameter is null the default MergeOption value from the context is used. + /// instance of response info class. + internal ResponseInfo GetDeserializationInfo(MergeOption? mergeOption) + { + return new ResponseInfo( + this, + mergeOption.HasValue ? mergeOption.Value : this.Context.MergeOption); + } + + /// + /// Returns the instance of LoadPropertyResponseInfo class, which provides information for LoadProperty response handling. + /// + /// Merge option to use for conflict handling. + /// Entity whose property is being loaded. + /// Property which is being loaded. + /// Instance of the LoadPropertyResponseInfo class. + internal ResponseInfo GetDeserializationInfoForLoadProperty(MergeOption? mergeOption, EntityDescriptor entityDescriptor, ClientPropertyAnnotation property) + { + return new LoadPropertyResponseInfo( + this, + mergeOption.HasValue ? mergeOption.Value : this.Context.MergeOption, + entityDescriptor, + property); + } + + /// + /// Validates that the response version can be accepted as a response for this request + /// + /// The version of the response (possibly null if none was specified) + /// Exception if the version can't be accepted, otherwise null. + internal InvalidOperationException ValidateResponseVersion(Version responseVersion) + { + if (responseVersion != null && responseVersion > this.Context.MaxProtocolVersionAsVersion) + { + string message = Strings.Context_ResponseVersionIsBiggerThanProtocolVersion( + responseVersion.ToString(), + this.Context.MaxProtocolVersion.ToString()); + return Error.InvalidOperation(message); + } + + return null; + } + + /// + /// Fires the SendingRequest event. + /// + /// SendingRequestEventArgs instance containing all information about the request. + internal void FireSendingRequest(SendingRequestEventArgs eventArgs) + { +#if DEBUG + Version requestVersion = GetRequestVersion(eventArgs.RequestHeaders); + Debug.Assert(this.UserModifiedRequestInBuildingRequest || requestVersion == null || requestVersion <= this.MaxProtocolVersionAsVersion, "requestVersion must not be greater than the maxProtocolVersion"); + Debug.Assert(this.UserModifiedRequestInBuildingRequest || eventArgs.RequestHeaders[XmlConstants.HttpODataMaxVersion] == this.MaxProtocolVersionAsVersion.ToString(Util.ODataVersionFieldCount), "requestMDSV must be set to the maxProtocolVersion"); +#endif + this.Context.FireSendingRequest(eventArgs); + } + + /// + /// Fires the SendingRequest2 event. + /// + /// SendingRequest2EventArgs instance containing all information about the request. + internal void FireSendingRequest2(SendingRequest2EventArgs eventArgs) + { + this.Context.FireSendingRequest2(eventArgs); + } + + /// + /// Returns an instance of the IODataRequestMessage + /// + /// Arguments for creating the request message. + /// an instance of the IODataRequestMessage + internal DataServiceClientRequestMessage CreateRequestMessage(BuildingRequestEventArgs requestMessageArgs) + { + var headersDictionary = requestMessageArgs.HeaderCollection.UnderlyingDictionary; + + // We are implementing the PostTunneling logic here. The reason for doing this is + // 1> In this public class, the Method property returns the actual method (PUT, PATCH, DELETE), + // and not the verb that goes in the wire. So this class needs to know about + // actual verb since it will be using this verb to send over http. + if (this.UsePostTunneling) + { + bool setXHttpMethodHeader = false; + if (string.CompareOrdinal(XmlConstants.HttpMethodGet, requestMessageArgs.Method) != 0 && + string.CompareOrdinal(XmlConstants.HttpMethodPost, requestMessageArgs.Method) != 0) + { + setXHttpMethodHeader = true; + } + + // Setting the actual method in the header + if (setXHttpMethodHeader) + { + headersDictionary[XmlConstants.HttpXMethod] = requestMessageArgs.Method; + } + + // Set the Content-length of a Delete to 0. Important for post tunneling when its a post, content-length must be zero in this case. + if (string.CompareOrdinal(XmlConstants.HttpMethodDelete, requestMessageArgs.Method) == 0) + { + headersDictionary[XmlConstants.HttpContentLength] = "0"; + +#if DEBUG + if (!this.UserModifiedRequestInBuildingRequest) + { + Debug.Assert(!requestMessageArgs.HeaderCollection.HasHeader(XmlConstants.HttpContentType), "Content-Type header must not be set for DELETE requests"); + } +#endif + } + } + + var clientRequestMessageArgs = new DataServiceClientRequestMessageArgs(requestMessageArgs.Method, requestMessageArgs.RequestUri, this.UseDefaultCredentials, this.UsePostTunneling, headersDictionary); + DataServiceClientRequestMessage clientRequestMessage; + if (this.Configurations.RequestPipeline.OnMessageCreating != null) + { + clientRequestMessage = this.Configurations.RequestPipeline.OnMessageCreating(clientRequestMessageArgs); + if (clientRequestMessage == null) + { + throw Error.InvalidOperation(Strings.Context_OnMessageCreatingReturningNull); + } + } + else + { + clientRequestMessage = new HttpWebRequestMessage(clientRequestMessageArgs); + } + + return clientRequestMessage; + } + + /// + /// Asks the context to Fire the BuildingRequest event and get RequestMessageArgs. + /// + /// Http method for the request. + /// Base Uri for the request. + /// Request headers. + /// HttpStack to use. + /// Descriptor for the request, if there is one. + /// A new RequestMessageArgs object for building the request message. + internal BuildingRequestEventArgs CreateRequestArgsAndFireBuildingRequest(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor) + { + return this.Context.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, httpStack, descriptor); + } + +#if DEBUG + /// + /// Return the request DSV header value for this request. + /// + /// Web headers. + /// The request DSV header value for this request as Version instance. + private static Version GetRequestVersion(WebHeaderCollection headers) + { + string requestDSVHeaderValue = headers[XmlConstants.HttpODataVersion]; + if (!String.IsNullOrEmpty(requestDSVHeaderValue)) + { + Debug.Assert(requestDSVHeaderValue.Contains(";"), "Unexpected request DSV header value"); + return Version.Parse(requestDSVHeaderValue.Substring(0, requestDSVHeaderValue.IndexOf(';'))); + } + + return null; + } +#endif + + #endregion Methods + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ResponseInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ResponseInfo.cs new file mode 100644 index 0000000..8da03ea --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ResponseInfo.cs @@ -0,0 +1,188 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Diagnostics; + using Microsoft.OData; + using Microsoft.OData.Client.Metadata; + + /// + /// Wrappers the context and only exposes information required for + /// processing the response from the server + /// + internal class ResponseInfo + { + #region Private Fields + /// The request that led to this response. + private readonly RequestInfo requestInfo; + + /// MergeOption to use to process the response. + private readonly MergeOption mergeOption; + #endregion Private Fields + + /// + /// Creates a new instance of the ResponseInfo class which exposes all the information from + /// the context required for processing the response from the server. + /// + /// The request info + /// mergeOption + internal ResponseInfo(RequestInfo requestInfo, MergeOption mergeOption) + { + this.requestInfo = requestInfo; + this.mergeOption = mergeOption; + this.ReadHelper = new ODataMessageReadingHelper(this); + } + + #region Properties + + /// The reading helper to use. + public ODataMessageReadingHelper ReadHelper { get; private set; } + + /// + /// Whether this is a continuation request. + /// + internal bool IsContinuation + { + get + { + Debug.Assert(this.requestInfo != null, "this.requestInfo != null"); + return this.requestInfo.IsContinuation; + } + } + + /// MergeOption to use to merge the entities from the response and one present in the client. + internal MergeOption MergeOption + { + get { return this.mergeOption; } + } + + /// + /// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled. + /// + internal bool ThrowOnUndeclaredPropertyForNonOpenType + { + get + { + if (this.Context.UndeclaredPropertyBehavior == UndeclaredPropertyBehavior.Support) + { + return false; + } + else + { + Debug.Assert(this.Context.UndeclaredPropertyBehavior == UndeclaredPropertyBehavior.ThrowException, + "this.Context.UndeclaredPropertyBehavior == UndeclaredPropertyBehavior.ThrowException"); + return true; + } + } + } + + /// Returns the instance of entity tracker class which tracks all the entities and links for the context. + internal EntityTracker EntityTracker + { + get { return this.Context.EntityTracker; } + } + + /// A flag indicating if the data service context is applying changes + internal bool ApplyingChanges + { + get { return this.Context.ApplyingChanges; } + set { this.Context.ApplyingChanges = value; } + } + + /// Gets the type resolver instance. + internal TypeResolver TypeResolver + { + get { return this.requestInfo.TypeResolver; } + } + + /// Gets the BaseUriResolver + internal UriResolver BaseUriResolver + { + get { return this.requestInfo.BaseUriResolver; } + } + + /// return the protocol version as specified in the client. + internal ODataProtocolVersion MaxProtocolVersion + { + get { return this.Context.MaxProtocolVersion; } + } + + /// + /// Gets the client model. + /// + internal ClientEdmModel Model + { + get { return this.requestInfo.Model; } + } + + /// + /// Returns the DataServiceContext + /// Should be only used in DataServiceCollection constructor, where + /// we need to infer the context from the results. + /// + /// context instance. + internal DataServiceContext Context + { + get + { + return this.requestInfo.Context; + } + } + + /// + /// Gets the reading pipeline configuration + /// + internal DataServiceClientResponsePipelineConfiguration ResponsePipeline + { + get { return this.requestInfo.Configurations.ResponsePipeline; } + } + + #endregion Properties + } + + /// + /// Information used for handling response to a LoadProperty request. + /// + internal class LoadPropertyResponseInfo : ResponseInfo + { + /// + /// Constructs a new instance. + /// + /// Information about the request. + /// Merge option. + /// Entity whose property is being loaded. + /// Property which is being loaded. + internal LoadPropertyResponseInfo( + RequestInfo requestInfo, + MergeOption mergeOption, + EntityDescriptor entityDescriptor, + ClientPropertyAnnotation property) + : base(requestInfo, mergeOption) + { + this.EntityDescriptor = entityDescriptor; + this.Property = property; + } + + /// + /// Entity whose property is being loaded. + /// + internal EntityDescriptor EntityDescriptor + { + get; + private set; + } + + /// + /// Property being loaded. + /// + internal ClientPropertyAnnotation Property + { + get; + private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveChangesOptions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveChangesOptions.cs new file mode 100644 index 0000000..deb1fb4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveChangesOptions.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// + /// options when saving changes + /// + [Flags] + public enum SaveChangesOptions + { + /// default option, using multiple requests to the server stopping on the first failure + None = 0, + + /// save the changes in a single changeset in a batch request. + BatchWithSingleChangeset = 1, + + /// save all the changes using multiple requests + ContinueOnError = 2, + + /// Use replace semantics when doing update. + ReplaceOnUpdate = 4, + + /// save each change independently in a batch request. + BatchWithIndependentOperations = 16, + + /// + /// Use partial payload when doing post. + /// Note it can only be used when using + /// + PostOnlySetProperties = 8 + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveResult.cs new file mode 100644 index 0000000..09cb85f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SaveResult.cs @@ -0,0 +1,997 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Collections.Generic; + using Microsoft.OData; + using Microsoft.OData.Client.Materialization; + using Microsoft.OData.Client.Metadata; + + #endregion Namespaces + + /// + /// Handle the request (both sync and async) for non batch scenarios + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Pending")] + internal class SaveResult : BaseSaveResult + { + /// keeps track of all the parsed responses. + private readonly List cachedResponses; + + /// + /// We cache the current response and then parse it. we need to do this for the async case only. + /// + private MemoryStream inMemoryResponseStream; + + /// http web response + private IODataResponseMessage responseMessage; + + /// remove it later + private CachedResponse cachedResponse; + + /// + /// constructor for SaveResult + /// + /// context + /// method + /// options + /// user callback + /// user state object + internal SaveResult(DataServiceContext context, string method, SaveChangesOptions options, AsyncCallback callback, object state) + : base(context, method, null, options, callback, state) + { + Debug.Assert(!Util.IsBatch(this.Options), "Util.IsBatch(this.Options) is not set"); + + this.cachedResponses = new List(); + } + + /// returns false since this class handles only non-batch scenarios + internal override bool IsBatchRequest + { + get { return false; } + } + + /// + /// returns true if the payload needs to be processed. + /// + protected override bool ProcessResponsePayload + { + get + { + Debug.Assert(this.cachedResponse.Exception == null, "no exception should have been encountered"); + return this.cachedResponse.MaterializerEntry != null; + } + } + + /// + /// In async case, this is a memory stream used to cache responses, as we are reading async from the underlying http web response stream. + /// In non-async case, this is the actual response stream, as returned by the http request. + /// + protected override Stream ResponseStream + { + get { return this.inMemoryResponseStream; } + } + + /// + /// This starts the next change + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Pending")] + internal void BeginCreateNextChange() + { + Debug.Assert(!this.IsCompletedInternally, "why being called if already completed?"); + + // create the memory stream required to cache the responses as we read async from the underlying http web response + this.inMemoryResponseStream = new MemoryStream(); + + // SaveCallback can't chain synchronously completed responses, caller will loop the to next change + PerRequest pereq = null; + IAsyncResult asyncResult = null; + do + { + IODataResponseMessage responseMsg = null; + ODataRequestMessageWrapper requestMessage = null; + try + { + if (null != this.perRequest) + { + this.SetCompleted(); + Error.ThrowInternalError(InternalError.InvalidBeginNextChange); + } + + requestMessage = this.CreateNextRequest(); + + // Keeping the old behavior (V1/V2) where the abortable was set to null, + // if CreateNextRequest returned null. + if (requestMessage == null) + { + this.Abortable = null; + } + + if ((null != requestMessage) || (this.entryIndex < this.ChangedEntries.Count)) + { + if (this.ChangedEntries[this.entryIndex].ContentGeneratedForSave) + { + Debug.Assert(this.ChangedEntries[this.entryIndex] is LinkDescriptor, "only expected RelatedEnd to presave"); + Debug.Assert( + this.ChangedEntries[this.entryIndex].State == EntityStates.Added || + this.ChangedEntries[this.entryIndex].State == EntityStates.Modified, + "only expected added to presave"); + continue; + } + + this.Abortable = requestMessage; + ContentStream contentStream = this.CreateNonBatchChangeData(this.entryIndex, requestMessage); + this.perRequest = pereq = new PerRequest(); + pereq.Request = requestMessage; + + AsyncStateBag asyncStateBag = new AsyncStateBag(pereq); + + if (null == contentStream || null == contentStream.Stream) + { + asyncResult = BaseAsyncResult.InvokeAsync(requestMessage.BeginGetResponse, this.AsyncEndGetResponse, asyncStateBag); + } + else + { + if (contentStream.IsKnownMemoryStream) + { + requestMessage.SetContentLengthHeader(); + } + + pereq.RequestContentStream = contentStream; + asyncResult = BaseAsyncResult.InvokeAsync(requestMessage.BeginGetRequestStream, this.AsyncEndGetRequestStream, asyncStateBag); + } + + pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); + this.SetCompletedSynchronously(pereq.RequestCompletedSynchronously); + } + else + { + this.SetCompleted(); + + if (this.CompletedSynchronously) + { + this.HandleCompleted(pereq); + } + } + } + catch (InvalidOperationException e) + { + e = WebUtil.GetHttpWebResponse(e, ref responseMsg); + this.HandleOperationException(e, responseMsg); + this.HandleCompleted(pereq); + } + finally + { + WebUtil.DisposeMessage(responseMsg); + } + + // If the current request is completed synchronously, we need to call FinishCurrentChange() to process the response payload. + // FinishCurrentChange() will not call BeginCreateNextChange() when the request is synchronous. + // If the current request is completed asynchronously, an async thread will call FinishCurrentChange() to process the response payload. + // FinishCurrentchange() will then call BeginCreateNextChange() from the async thread and we need to exit this loop. + // If requestMessage = this.CreateNextRequest() returns null, we would have called this.SetCompleted() above and this.IsCompletedInternally + // would be true. This means we are done processing all changed entries and we should not call this.FinishCurrentChange(). + if (null != pereq && pereq.RequestCompleted && pereq.RequestCompletedSynchronously && !this.IsCompletedInternally) + { + Debug.Assert(requestMessage != null, "httpWebRequest != null"); + this.FinishCurrentChange(pereq); + } + + // In the condition for the do-while loop we must test for pereq.RequestCompletedSynchronously and not asyncResult.CompletedSynchronously. + // pereq.RequestCompletedSynchronously is true only if all async calls completed synchronously. If we don't exit this loop when + // pereq.RequestCompletedSynchronously is false, the current thread and an async thread will both re-enter BeginCreateNextChange() + // and we will fail. We can only process one request at a given time. + } + while (((null == pereq) || (pereq.RequestCompleted && pereq.RequestCompletedSynchronously)) && !this.IsCompletedInternally); + Debug.Assert((this.CompletedSynchronously && this.IsCompleted) || !this.CompletedSynchronously, "sync without complete"); + Debug.Assert(this.entryIndex < this.ChangedEntries.Count || this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generate content for all entities/links"); + } + +#if !PORTABLELIB// Synchronous methods not available + /// + /// This starts the next change + /// + internal void CreateNextChange() + { + ODataRequestMessageWrapper requestMessage = null; + + do + { + IODataResponseMessage responseMsg = null; + + try + { + requestMessage = this.CreateNextRequest(); + if ((null != requestMessage) || (this.entryIndex < this.ChangedEntries.Count)) + { + if (this.ChangedEntries[this.entryIndex].ContentGeneratedForSave) + { + Debug.Assert(this.ChangedEntries[this.entryIndex] is LinkDescriptor, "only expected RelatedEnd to presave"); + Debug.Assert( + this.ChangedEntries[this.entryIndex].State == EntityStates.Added || + this.ChangedEntries[this.entryIndex].State == EntityStates.Modified, + "only expected added to presave"); + continue; + } + + ContentStream contentStream = this.CreateNonBatchChangeData(this.entryIndex, requestMessage); + if (null != contentStream && null != contentStream.Stream) + { + requestMessage.SetRequestStream(contentStream); + } + + responseMsg = this.RequestInfo.GetSyncronousResponse(requestMessage, false); + + this.HandleOperationResponse(responseMsg); + this.HandleOperationResponseHeaders((HttpStatusCode)responseMsg.StatusCode, new HeaderCollection(responseMsg)); + this.HandleOperationResponseData(responseMsg); + this.perRequest = null; + } + } + catch (InvalidOperationException e) + { + e = WebUtil.GetHttpWebResponse(e, ref responseMsg); + this.HandleOperationException(e, responseMsg); + } + finally + { + WebUtil.DisposeMessage(responseMsg); + } + + // we have no more pending requests or there has been an error in the previous request and we decided not to continue + // (When an error occurs and we are not going to continue on error, we call SetCompleted + } + while (this.entryIndex < this.ChangedEntries.Count && !this.IsCompletedInternally); + + Debug.Assert(this.entryIndex < this.ChangedEntries.Count || this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generate content for all entities/links"); + } +#endif + + /// Read and store response data for the current change, and try to start the next one + /// the completed per request object + protected override void FinishCurrentChange(PerRequest pereq) + { + base.FinishCurrentChange(pereq); + + Debug.Assert(this.ResponseStream != null, "this.HttpWebResponseStream != null"); + Debug.Assert((this.ResponseStream as MemoryStream) != null, "(this.HttpWebResponseStream as MemoryStream) != null"); + + if (this.ResponseStream.Position != 0) + { + // Set the stream to the start position and then parse the response and cache it + this.ResponseStream.Position = 0; + this.HandleOperationResponseData(this.responseMessage, this.ResponseStream); + } + else + { + this.HandleOperationResponseData(this.responseMessage, null); + } + + pereq.Dispose(); + this.perRequest = null; + + if (!pereq.RequestCompletedSynchronously) + { // you can't chain synchronously completed responses without risking StackOverflow, caller will loop to next + if (!this.IsCompletedInternally) + { + this.BeginCreateNextChange(); + } + } + } + + /// IODataResponseMessage contain response for the operation. + /// IODataResponseMessage instance. + protected override void HandleOperationResponse(IODataResponseMessage responseMsg) + { + this.responseMessage = responseMsg; + } + + /// + /// Handle the response. + /// + /// an instance of the DataServiceResponse, containing individual responses for all the requests made during this SaveChanges call. + protected override DataServiceResponse HandleResponse() + { + List responses = new List(this.cachedResponses != null ? this.cachedResponses.Count : 0); + DataServiceResponse service = new DataServiceResponse(null, -1, responses, false /*isBatch*/); + Exception ex = null; + + try + { + foreach (CachedResponse response in this.cachedResponses) + { + Descriptor descriptor = response.Descriptor; + this.SaveResultProcessed(descriptor); + OperationResponse operationResponse = new ChangeOperationResponse(response.Headers, descriptor); + operationResponse.StatusCode = (int)response.StatusCode; + if (response.Exception != null) + { + operationResponse.Error = response.Exception; + + if (ex == null) + { + ex = response.Exception; + } + } + else + { + this.cachedResponse = response; +#if DEBUG + this.HandleOperationResponse(descriptor, response.Headers, response.StatusCode); +#else + this.HandleOperationResponse(descriptor, response.Headers); +#endif + } + + responses.Add(operationResponse); + } + } + catch (InvalidOperationException e) + { + ex = e; + } + + if (ex != null) + { + throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, ex, service); + } + + return service; + } + + /// + /// Get the materializer to process the response. + /// + /// entity descriptor whose response is getting materialized. + /// information about the response to be materialized. + /// an instance of MaterializeAtom, that can be used to materialize the response. + protected override MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo) + { + Debug.Assert(this.cachedResponse.Exception == null && this.cachedResponse.MaterializerEntry != null, "this.cachedResponse.Exception == null && this.cachedResponse.Entry != null"); + ODataResource entry = this.cachedResponse.MaterializerEntry == null ? null : this.cachedResponse.MaterializerEntry.Entry; + return new MaterializeAtom(responseInfo, new[] { entry }, entityDescriptor.Entity.GetType(), this.cachedResponse.MaterializerEntry.Format); + } + + /// + /// Returns the request message to write the headers and payload into. + /// + /// Http method for the request. + /// Base Uri for the request. + /// Request headers. + /// HttpStack to use. + /// Descriptor for the request, if there is one. + /// Content-ID header that could be used in batch request. + /// an instance of IODataRequestMessage. + protected override ODataRequestMessageWrapper CreateRequestMessage(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor, string contentId) + { + return this.CreateTopLevelRequest(method, requestUri, headers, httpStack, descriptor); + } + + /// + /// Create memory stream for descriptor (entity or link or MR or named stream). + /// + /// Index into changed entries. + /// RequestMessage to be used to generate the payload. + /// Stream of data for descriptor. + protected ContentStream CreateNonBatchChangeData(int index, ODataRequestMessageWrapper requestMessage) + { + Descriptor descriptor = this.ChangedEntries[index]; + Debug.Assert(!descriptor.ContentGeneratedForSave, "already saved entity/link"); + Debug.Assert(!this.IsBatchRequest, "we do not support MR requests in batch"); + + if (descriptor.DescriptorKind == DescriptorKind.Entity && this.streamRequestKind != StreamRequestKind.None) + { + if (this.streamRequestKind != StreamRequestKind.None) + { + Debug.Assert( + this.streamRequestKind == StreamRequestKind.PutMediaResource || descriptor.State == EntityStates.Modified, + "We should have modified the MLE state to Modified when we've created the MR POST request."); + Debug.Assert( + this.streamRequestKind != StreamRequestKind.PutMediaResource || (descriptor.State == EntityStates.Unchanged || descriptor.State == EntityStates.Modified), + "If we're processing MR PUT the entity must be either in Unchanged or Modified state."); + + // media resource request - we already precreated the body of the request + // in the CheckAndProcessMediaEntryPost or CheckAndProcessMediaEntryPut method. + Debug.Assert(this.mediaResourceRequestStream != null, "We should have precreated the MR stream already."); + return new ContentStream(this.mediaResourceRequestStream, false); + } + } + else if (descriptor.DescriptorKind == DescriptorKind.NamedStream) + { + Debug.Assert(!this.IsBatchRequest, "we do not support named stream requests in batch"); + + // named stream request - we already precreated the body of the request in CreateNamedStreamRequest method + Debug.Assert(this.mediaResourceRequestStream != null, "We should have precreated the MR stream already."); + descriptor.ContentGeneratedForSave = true; + return new ContentStream(this.mediaResourceRequestStream, false); + } + else if (this.CreateChangeData(index, requestMessage)) + { + return requestMessage.CachedRequestStream; + } + + return null; + } + + /// + /// Create request message from the next change. + /// + /// An instance of ODataRequestMessage for the next change. + private ODataRequestMessageWrapper CreateNextRequest() + { + bool moveForward = this.streamRequestKind == StreamRequestKind.None; + if (unchecked((uint)this.entryIndex < (uint)this.ChangedEntries.Count)) + { + Descriptor previousDescriptor = this.ChangedEntries[this.entryIndex]; + if (previousDescriptor.DescriptorKind == DescriptorKind.Entity) + { + EntityDescriptor entityDescriptor = (EntityDescriptor)previousDescriptor; + + // In any case also close the save stream if there's any and forget about it + // for POST this is just a good practice to do so as soon as possible + // for PUT it's actually required for us to recognize that we already processed the MR part of the change + entityDescriptor.CloseSaveStream(); + + // If the previous request was an MR request the next one might be a PUT for the MLE + // but if the entity was not changed (just the MR changed) no PUT for MLE should be sent + if (this.streamRequestKind == StreamRequestKind.PutMediaResource && EntityStates.Unchanged == entityDescriptor.State) + { + // Only the MR changed. In this case we also need to mark the descriptor as processed to notify + // that the content for save has been generated as there's not going to be another request for it. + entityDescriptor.ContentGeneratedForSave = true; + moveForward = true; + } + } + else if (previousDescriptor.DescriptorKind == DescriptorKind.NamedStream) + { + ((StreamDescriptor)previousDescriptor).CloseSaveStream(); + } + } + + if (moveForward) + { + this.entryIndex++; + } + + ODataRequestMessageWrapper requestMessage = null; + if (unchecked((uint)this.entryIndex < (uint)this.ChangedEntries.Count)) + { + Descriptor descriptor = this.ChangedEntries[this.entryIndex]; + Descriptor descriptorForSendingRequest2 = descriptor; + if (descriptor.DescriptorKind == DescriptorKind.Entity) + { + EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; + + if (((EntityStates.Unchanged == descriptor.State) || (EntityStates.Modified == descriptor.State)) && + (null != (requestMessage = this.CheckAndProcessMediaEntryPut(entityDescriptor)))) + { + this.streamRequestKind = StreamRequestKind.PutMediaResource; + descriptorForSendingRequest2 = entityDescriptor.DefaultStreamDescriptor; // We want to give SendingRequest2 the StreamDecriptor + } + else if ((EntityStates.Added == descriptor.State) && (null != (requestMessage = this.CheckAndProcessMediaEntryPost(entityDescriptor)))) + { + this.streamRequestKind = StreamRequestKind.PostMediaResource; + + Debug.Assert(entityDescriptor.SaveStream == null || entityDescriptor.StreamState == EntityStates.Added, "Either this is a V1 MR or the stream must be in added state"); + + // Set the stream state to added + // For V1 scenarios, when SetSaveStream was not called, this might not be in Added state + entityDescriptor.StreamState = EntityStates.Added; + } + else + { + this.streamRequestKind = StreamRequestKind.None; + Debug.Assert(descriptor.State != EntityStates.Unchanged, "descriptor.State != EntityStates.Unchanged"); + requestMessage = this.CreateRequest(entityDescriptor); + } + } + else if (descriptor.DescriptorKind == DescriptorKind.NamedStream) + { + requestMessage = this.CreateNamedStreamRequest((StreamDescriptor)descriptor); + } + else + { + requestMessage = this.CreateRequest((LinkDescriptor)descriptor); + } + + if (requestMessage != null) + { + // we need to fire request after the headers have been written, but before we write the payload + // also in the prototype we are firing SendingRequest2 before SendingRequest. + requestMessage.FireSendingEventHandlers(descriptorForSendingRequest2); + } + } + + return requestMessage; + } + + /// + /// Check to see if the resource to be inserted is a media descriptor, and if so + /// setup a POST request for the media content first and turn the rest of + /// the operation into a PUT to update the rest of the properties. + /// + /// The resource to check/process + /// An instance of ODataRequestMessage to do POST to the media resource + private ODataRequestMessageWrapper CheckAndProcessMediaEntryPost(EntityDescriptor entityDescriptor) + { + // TODO: Revisit the design of how media link entries are handled during update + ClientEdmModel model = this.RequestInfo.Model; + ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); + + if (!type.IsMediaLinkEntry && !entityDescriptor.IsMediaLinkEntry) + { + // this is not a media link descriptor, process normally + return null; + } + + if (type.MediaDataMember == null && entityDescriptor.SaveStream == null) + { + // The entity is marked as MLE but we don't have the content property + // and the user didn't set the save stream. + throw Error.InvalidOperation(Strings.Context_MLEWithoutSaveStream(type.ElementTypeName)); + } + + Debug.Assert( + (type.MediaDataMember != null && entityDescriptor.SaveStream == null) || + (type.MediaDataMember == null && entityDescriptor.SaveStream != null), + "Only one way of specifying the MR content is allowed."); + + ODataRequestMessageWrapper mediaRequest = null; + if (type.MediaDataMember != null) + { + string contentType = null; + int contentLength = 0; + + if (type.MediaDataMember.MimeTypeProperty == null) + { + contentType = XmlConstants.MimeApplicationOctetStream; + } + else + { + object mimeTypeValue = type.MediaDataMember.MimeTypeProperty.GetValue(entityDescriptor.Entity); + String mimeType = mimeTypeValue != null ? mimeTypeValue.ToString() : null; + + if (String.IsNullOrEmpty(mimeType)) + { + throw Error.InvalidOperation( + Strings.Context_NoContentTypeForMediaLink( + type.ElementTypeName, + type.MediaDataMember.MimeTypeProperty.PropertyName)); + } + + contentType = mimeType; + } + + object value = type.MediaDataMember.GetValue(entityDescriptor.Entity); + if (value == null) + { + this.mediaResourceRequestStream = null; + } + else + { + byte[] buffer = value as byte[]; + if (buffer == null) + { + string mime; + Encoding encoding; + ContentTypeUtil.ReadContentType(contentType, out mime, out encoding); + + if (encoding == null) + { + encoding = Encoding.UTF8; + contentType += XmlConstants.MimeTypeUtf8Encoding; + } + + buffer = encoding.GetBytes(ClientConvert.ToString(value)); + } + + contentLength = buffer.Length; + +#if PORTABLELIB + // Win8 doesn't allow accessing the buffer, so the constructor we normally use doesn't exist + this.mediaResourceRequestStream = new MemoryStream(buffer, 0, buffer.Length, false); +#else + // Need to specify that the buffer is publicly visible as we need to access it later on + this.mediaResourceRequestStream = new MemoryStream(buffer, 0, buffer.Length, false, true); +#endif + } + + HeaderCollection headers = new HeaderCollection(); + headers.SetHeader(XmlConstants.HttpContentLength, contentLength.ToString(CultureInfo.InvariantCulture)); + headers.SetHeader(XmlConstants.HttpContentType, contentType); + + mediaRequest = this.CreateMediaResourceRequest( + entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/), + XmlConstants.HttpMethodPost, + Util.ODataVersion4, + type.MediaDataMember == null, // sendChunked + true, // applyResponsePreference + headers, + entityDescriptor); + } + else + { + HeaderCollection headers = new HeaderCollection(); + this.SetupMediaResourceRequest(headers, entityDescriptor.SaveStream, null /*etag*/); + + mediaRequest = this.CreateMediaResourceRequest( + entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/), + XmlConstants.HttpMethodPost, + Util.ODataVersion4, + type.MediaDataMember == null, // sendChunked + true, // applyResponsePreference + headers, + entityDescriptor); + } + + // Convert the insert into an update for the media link descriptor we just created + // (note that the identity still needs to be fixed up on the resbox once + // the response comes with the 'location' header; that happens during processing + // of the response in SavedResource()) + entityDescriptor.State = EntityStates.Modified; + + return mediaRequest; + } + + /// + /// Checks if the entity descriptor represents an MLE with modified MR and if so creates a PUT request + /// to update the MR. + /// + /// The entity descriptor for the entity to be checked. + /// An instance of ODataRequestMessage for the newly created MR PUT request or null if the entity is not MLE or its MR hasn't changed. + private ODataRequestMessageWrapper CheckAndProcessMediaEntryPut(EntityDescriptor entityDescriptor) + { + // If there's no save stream associated with the entity it's not MLE or its MR hasn't changed + // (which for purposes of PUT is the same anyway) + if (entityDescriptor.SaveStream == null) + { + return null; + } + + Uri requestUri = entityDescriptor.GetLatestEditStreamUri(); + if (requestUri == null) + { + throw Error.InvalidOperation( + Strings.Context_SetSaveStreamWithoutEditMediaLink); + } + + HeaderCollection headers = new HeaderCollection(); + this.SetupMediaResourceRequest(headers, entityDescriptor.SaveStream, entityDescriptor.GetLatestStreamETag()); + + return this.CreateMediaResourceRequest( + requestUri, + XmlConstants.HttpMethodPut, + Util.ODataVersion4, + true, // sendChunked + false, // applyResponsePreference + headers, + entityDescriptor.DefaultStreamDescriptor); + } + + /// + /// Creates HTTP request for the media resource (MR) + /// + /// The URI to request + /// The HTTP method to use (POST or PUT) + /// version to be sent in the DSV request header. + /// Send the request using chunked encoding to avoid buffering. + /// If the response preference setting should be applied to the request + /// (basically means if the response is expected to contain an entity or not). + /// Collection of request headers + /// Descriptor for this media resource request. + /// An instance of ODataRequestMessage. + private ODataRequestMessageWrapper CreateMediaResourceRequest(Uri requestUri, string method, Version version, bool sendChunked, bool applyResponsePreference, HeaderCollection headers, Descriptor descriptor) + { + headers.SetHeaderIfUnset(XmlConstants.HttpContentType, XmlConstants.MimeAny); + + if (applyResponsePreference) + { + ApplyPreferences(headers, method, this.RequestInfo.AddAndUpdateResponsePreference, ref version); + } + + // Set the request DSV and request MDSV headers + headers.SetRequestVersion(version, this.RequestInfo.MaxProtocolVersionAsVersion); + this.RequestInfo.Format.SetRequestAcceptHeader(headers); + + ODataRequestMessageWrapper requestMessage = this.CreateRequestMessage(method, requestUri, headers, this.RequestInfo.HttpStack, descriptor, null /*contentId*/); + + // TODO: since under the hood this is a header, we should put it in our dictionary of headers that the user gets in BuildingRequest + // and later on handle the setting of the strongly named property on the underlying request + requestMessage.SendChunked = sendChunked; + return requestMessage; + } + + /// + /// Sets the content and the headers of the media resource request + /// + /// The header collection to setup. + /// DataServiceSaveStream instance containing all information about the stream. + /// ETag header value to be set. If passed null, etag header is not set. + /// This only works with the V2 MR support (SetSaveStream), this will not setup + /// the request for V1 property based MRs. + private void SetupMediaResourceRequest(HeaderCollection headers, DataServiceSaveStream saveStream, string etag) + { + // Get the write stream for this MR + this.mediaResourceRequestStream = saveStream.Stream; + + // Copy over headers for the request, except the accept header + headers.SetHeaders(saveStream.Args.Headers.Where(h => !string.Equals(h.Key, XmlConstants.HttpRequestAccept, StringComparison.OrdinalIgnoreCase))); + + if (etag != null) + { + headers.SetHeader(XmlConstants.HttpRequestIfMatch, etag); + } + + // Do NOT set the ContentLength since we don't know if the stream even supports reporting its length + } + + #region generate batch response from non-batch + + /// operation with exception + /// exception object + /// response object + private void HandleOperationException(InvalidOperationException e, IODataResponseMessage response) + { + Debug.Assert(this.entryIndex >= 0 && this.entryIndex < this.ChangedEntries.Count(), string.Format(System.Globalization.CultureInfo.InvariantCulture, "this.entryIndex = '{0}', this.ChangedEntries.Count = '{1}'", this.entryIndex, this.ChangedEntries.Count())); + + Descriptor current = this.ChangedEntries[this.entryIndex]; + HeaderCollection headers = null; + HttpStatusCode statusCode = HttpStatusCode.InternalServerError; + + Version responseVersion = null; + if (null != response) + { + headers = new HeaderCollection(response); + statusCode = (HttpStatusCode)response.StatusCode; + + this.HandleOperationResponseHeaders(statusCode, headers); + e = BaseSaveResult.HandleResponse( + this.RequestInfo, + statusCode, + response.GetHeader(XmlConstants.HttpODataVersion), + response.GetStream, + false/*throwOnFailure*/, + out responseVersion); + } + else + { + headers = new HeaderCollection(); + headers.SetHeader(XmlConstants.HttpContentType, XmlConstants.MimeTextPlain); + + // In V2 we used to merge individual responses from a call to SaveChanges() into a single batch response payload and then process that. + // When we encounter an exception at this point in V2, we used to write the exception to the batch response payload and later on when we + // process through the batch response, we create a DataServiceClientException for each failed operation. + // For backcompat reason, we will always convert the exception type to DataServiceClientException here. + Debug.Assert(e != null, "e != null"); + if (e.GetType() != typeof(DataServiceClientException)) + { + e = new DataServiceClientException(e.Message, e); + } + } + + // For error scenarios, we never invoke the ReadingEntity event. + this.cachedResponses.Add(new CachedResponse(current, headers, statusCode, responseVersion, null, e)); + this.perRequest = null; + this.CheckContinueOnError(); + } + + /// + /// Decide whether we should continue when there is an error thrown + /// + private void CheckContinueOnError() + { + if (!Util.IsFlagSet(this.Options, SaveChangesOptions.ContinueOnError)) + { + this.SetCompleted(); + } + else + { + // if it was a media link descriptor don't even try to do a PUT if the POST didn't succeed + this.streamRequestKind = StreamRequestKind.None; + + // Need to set this to true since we check this even on error cases, but we're here + // because exception was thrown during preparation of the request, so we might not have a chance + // to generate the content for save yet. + this.ChangedEntries[this.entryIndex].ContentGeneratedForSave = true; + } + } + +#if !PORTABLELIB + /// + /// copy the response data + /// + /// response object + private void HandleOperationResponseData(IODataResponseMessage response) + { + Debug.Assert(response != null, "response != null"); + + using (Stream stream = response.GetStream()) + { + if (null != stream) + { + // we need to check for whether the incoming stream was data or not. Hence we need to copy it to a temporary memory stream + using (MemoryStream memoryStream = new MemoryStream()) + { + if (WebUtil.CopyStream(stream, memoryStream, ref this.buildBatchBuffer) != 0) + { + // set the memory stream position to zero again. + memoryStream.Position = 0; + this.HandleOperationResponseData(response, memoryStream); + } + else + { + this.HandleOperationResponseData(response, null); + } + } + } + } + } +#endif + + /// + /// Handle the response payload. + /// + /// httpwebresponse instance. + /// stream containing the response payload. + private void HandleOperationResponseData(IODataResponseMessage responseMsg, Stream responseStream) + { + Debug.Assert(this.entryIndex >= 0 && this.entryIndex < this.ChangedEntries.Count(), string.Format(System.Globalization.CultureInfo.InvariantCulture, "this.entryIndex = '{0}', this.ChangedEntries.Count() = '{1}'", this.entryIndex, this.ChangedEntries.Count())); + + // Parse the response + Descriptor current = this.ChangedEntries[this.entryIndex]; + MaterializerEntry entry = default(MaterializerEntry); + Version responseVersion; + Exception exception = BaseSaveResult.HandleResponse(this.RequestInfo, (HttpStatusCode)responseMsg.StatusCode, responseMsg.GetHeader(XmlConstants.HttpODataVersion), () => { return responseStream; }, false/*throwOnFailure*/, out responseVersion); + + var headers = new HeaderCollection(responseMsg); + if (responseStream != null && current.DescriptorKind == DescriptorKind.Entity && exception == null) + { + // Only process the response if the current resource is an entity and it's an insert or update scenario + EntityDescriptor entityDescriptor = (EntityDescriptor)current; + + // We were ignoring the payload for non-insert and non-update scenarios. We need to keep doing that. + if (entityDescriptor.State == EntityStates.Added || entityDescriptor.StreamState == EntityStates.Added || + entityDescriptor.State == EntityStates.Modified || entityDescriptor.StreamState == EntityStates.Modified) + { + try + { + ResponseInfo responseInfo = this.CreateResponseInfo(entityDescriptor); + var responseMessageWrapper = new HttpWebResponseMessage( + headers, + responseMsg.StatusCode, + () => responseStream); + + entry = ODataReaderEntityMaterializer.ParseSingleEntityPayload(responseMessageWrapper, responseInfo, entityDescriptor.Entity.GetType()); + entityDescriptor.TransientEntityDescriptor = entry.EntityDescriptor; + } + catch (Exception ex) + { + exception = ex; + + if (!CommonUtil.IsCatchableExceptionType(ex)) + { + throw; + } + } + } + } + + this.cachedResponses.Add(new CachedResponse( + current, + headers, + (HttpStatusCode)responseMsg.StatusCode, + responseVersion, + entry, + exception)); + + if (exception != null) + { + current.SaveError = exception; + + // DEVNOTE(pqian): + // There are two possible scenario here: + // 1. We are in the sync code path, and there's an in stream error on the server side, or there are bad xml thrown + // 2. We are in the async code path, there's a error thrown on the server side (any error) + // Ideally, we need to check whether we want to continue to the next changeset. (Call this.CheckContinueOnError) + // However, in V1/V2, we did not do this. Thus we will always continue on error on these scenarios + } + } + + #endregion + + /// + /// Creates a request for the given named stream. + /// + /// NamedStreamInfo instance containing information about the stream. + /// An instance of ODataRequestMessage for the given named stream. + private ODataRequestMessageWrapper CreateNamedStreamRequest(StreamDescriptor namedStreamInfo) + { + Debug.Assert(namedStreamInfo.SaveStream != null, "The named stream must have an associated stream"); + Debug.Assert(!string.IsNullOrEmpty(namedStreamInfo.SaveStream.Args.ContentType), "ContentType must not be null or empty"); + + Uri requestUri = namedStreamInfo.GetLatestEditLink(); + if (requestUri == null) + { + throw Error.InvalidOperation(Strings.Context_SetSaveStreamWithoutNamedStreamEditLink(namedStreamInfo.Name)); + } + + HeaderCollection headers = new HeaderCollection(); + this.SetupMediaResourceRequest(headers, namedStreamInfo.SaveStream, namedStreamInfo.GetLatestETag()); + + ODataRequestMessageWrapper mediaRequestMessage = this.CreateMediaResourceRequest( + requestUri, + XmlConstants.HttpMethodPut, + Util.ODataVersion4, + true, // sendChunked + false, // applyResponsePreference + headers, + namedStreamInfo); + +#if DEBUG + Debug.Assert(mediaRequestMessage.GetHeader("Content-Type") == namedStreamInfo.SaveStream.Args.ContentType, "ContentType not set correctly for the named stream"); +#endif + + return mediaRequestMessage; + } + + /// + /// cached response + /// + private struct CachedResponse + { + /// response headers + public readonly HeaderCollection Headers; + + /// response status code + public readonly HttpStatusCode StatusCode; + + /// Parsed response OData-Version header. + public readonly Version Version; + + /// entry containing the parsed response. + public readonly MaterializerEntry MaterializerEntry; + + /// Exception if encountered. + public readonly Exception Exception; + + /// descriptor for which the response is getting parsed. + public readonly Descriptor Descriptor; + + /// + /// Constructor + /// + /// descriptor whose response is getting processed. + /// headers + /// status code + /// Parsed response OData-Version header. + /// atom entry, if there is a non-error response payload. + /// exception, if the request threw an exception. + internal CachedResponse(Descriptor descriptor, HeaderCollection headers, HttpStatusCode statusCode, Version responseVersion, MaterializerEntry entry, Exception exception) + { + Debug.Assert(descriptor != null, "descriptor != null"); + Debug.Assert(headers != null, "headers != null"); + Debug.Assert(entry == null || (exception == null && descriptor.DescriptorKind == DescriptorKind.Entity), "if entry is specified, exception cannot be specified and entry must be a resource, since we expect responses only for entities"); + + this.Descriptor = descriptor; + this.MaterializerEntry = entry; + this.Exception = exception; + this.Headers = headers; + this.StatusCode = statusCode; + this.Version = responseVersion; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequest2EventArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequest2EventArgs.cs new file mode 100644 index 0000000..568ab6d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequest2EventArgs.cs @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using Microsoft.OData; + + /// Event args for the SendingRequest2 event. + public class SendingRequest2EventArgs : EventArgs + { + /// + /// Creates a new instance of SendingRequest2EventsArgs + /// + /// request message. + /// Descriptor that represents this change. + /// True if this args represents a request within a batch, otherwise false. + internal SendingRequest2EventArgs(IODataRequestMessage requestMessage, Descriptor descriptor, bool isBatchPart) + { + this.RequestMessage = requestMessage; + this.Descriptor = descriptor; + this.IsBatchPart = isBatchPart; + } + + /// The web request reported through this event. The handler may modify it. + public IODataRequestMessage RequestMessage + { + get; + private set; + } + + /// The request header collection. + public Descriptor Descriptor + { + get; + private set; + } + + /// Returns true if this event is fired for request within a batch, otherwise returns false. + public bool IsBatchPart + { + get; + private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequestEventArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequestEventArgs.cs new file mode 100644 index 0000000..71b3018 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/SendingRequestEventArgs.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + + /// + /// Event args for the event fired before executing a web request. Gives a + /// chance to customize or replace the request object to be used. + /// + public class SendingRequestEventArgs : EventArgs + { + /// The web request reported through this event + private System.Net.WebRequest request; + + /// The request header collection. + private System.Net.WebHeaderCollection requestHeaders; + + /// + /// Constructor + /// + /// The request reported through this event + /// The request header collection. + internal SendingRequestEventArgs(System.Net.WebRequest request, System.Net.WebHeaderCollection requestHeaders) + { + // In Silverlight the request object is not accesible + Debug.Assert(null != request, "null request"); + Debug.Assert(null != requestHeaders, "null requestHeaders"); + this.request = request; + this.requestHeaders = requestHeaders; + } + + /// Gets or sets the instance about to be sent by the client library to the data service. + /// . + public System.Net.WebRequest Request + { + get + { + return this.request; + } + + set + { + Util.CheckArgumentNull(value, "value"); + if (!(value is System.Net.HttpWebRequest)) + { + throw Error.Argument(Strings.Context_SendingRequestEventArgsNotHttp, "value"); + } + + this.request = value; + this.requestHeaders = value.Headers; + } + } + + /// Gets the collection protocol headers that are associated with the request to the data service. + /// A collection of protocol headers that are associated with the request. + public System.Net.WebHeaderCollection RequestHeaders + { + get { return this.requestHeaders; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataServiceClientRequestMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataServiceClientRequestMessage.cs new file mode 100644 index 0000000..93c166b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataServiceClientRequestMessage.cs @@ -0,0 +1,157 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Net; + using Microsoft.OData; + + /// + /// This class represents the contract WCF Data Services client with the request message. + /// + public abstract class DataServiceClientRequestMessage : IODataRequestMessage + { + /// Http method. + private readonly string actualHttpMethod; + + /// + /// Initializes a new instance of the class. + /// + /// The actual method. + public DataServiceClientRequestMessage(string actualMethod) + { + this.actualHttpMethod = actualMethod; + } + + /// + /// Returns the collection of request headers. + /// + public abstract IEnumerable> Headers + { + get; + } + + /// + /// Gets or sets the request url. + /// + public abstract Uri Url + { + get; + set; + } + + /// + /// Gets or sets the method for this request. + /// + public abstract string Method + { + get; + set; + } + + /// + /// Gets or set the credentials for this request. + /// + public abstract ICredentials Credentials { get; set; } + +#if !PORTABLELIB + /// + /// Gets or sets the timeout (in seconds) for this request. + /// + public abstract int Timeout { get; set; } +#endif + + /// + /// Gets or sets a value that indicates whether to send data in segments to the + /// Internet resource. + /// + [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Make code very confusing and cumbersome to write code for various platforms. Hence suppressing the message")] +#if !PORTABLELIB + public abstract bool SendChunked { get; set; } +#else + internal bool SendChunked { get; set; } +#endif + + /// + /// Gets or the actual method. In post tunneling situations method will be POST instead of the specified verb method. + /// + protected virtual string ActualMethod + { + get + { + return this.actualHttpMethod; + } + } + + /// + /// Returns the value of the header with the given name. + /// + /// Name of the header. + /// Returns the value of the header with the given name. + public abstract string GetHeader(string headerName); + + /// + /// Sets the value of the header with the given name. + /// + /// Name of the header. + /// Value of the header. + public abstract void SetHeader(string headerName, string headerValue); + + /// + /// Gets the stream to be used to write the request payload. + /// + /// Stream to which the request payload needs to be written. + public abstract Stream GetStream(); + + /// + /// Abort the current request. + /// + public abstract void Abort(); + + /// + /// Begins an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request. + public abstract IAsyncResult BeginGetRequestStream(AsyncCallback callback, object state); + + /// + /// Ends an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The pending request for a stream. + /// A System.IO.Stream to use to write request data. + public abstract Stream EndGetRequestStream(IAsyncResult asyncResult); + + /// + /// Begins an asynchronous request to an Internet resource. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request for a response. + public abstract IAsyncResult BeginGetResponse(AsyncCallback callback, object state); + + /// + /// Ends an asynchronous request to an Internet resource. + /// + /// The pending request for a response. + /// A System.Net.WebResponse that contains the response from the Internet resource. + public abstract IODataResponseMessage EndGetResponse(IAsyncResult asyncResult); + +#if !PORTABLELIB + /// + /// Returns a response from an Internet resource. + /// + /// A System.Net.WebResponse that contains the response from the Internet resource. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is intentionally a method.")] + public abstract IODataResponseMessage GetResponse(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataStringEscapeBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataStringEscapeBuilder.cs new file mode 100644 index 0000000..7a17b3a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/DataStringEscapeBuilder.cs @@ -0,0 +1,140 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_SERVICE +namespace Microsoft.OData.Service.Serializers +#else +namespace Microsoft.OData.Client +#endif +{ + using System; + using System.Diagnostics; + using System.Text; + + /// + /// Take a URI string and escape the data portion of it + /// + internal class DataStringEscapeBuilder + { + /// + /// Sensitive characters that we should always skip + /// This should be the set of Http control characters intersecting with + /// the set of characters OData literal format allows outside of strings + /// (In V3: only +, as used in double literals ex. 3E+8) + /// + private const String SensitiveCharacters = "+"; + + /// + /// input string + /// + private readonly string input; + + /// + /// output string + /// + private readonly StringBuilder output = new StringBuilder(); + + /// + /// the current index + /// + private int index; + + /// + /// current quoted data string + /// + private StringBuilder quotedDataBuilder; + + /// + /// constructor + /// + /// The string to be escaped. + private DataStringEscapeBuilder(string dataString) + { + this.input = dataString; + } + + /// + /// Escape a URI string's data string portion + /// + /// The input string + /// The escaped string + internal static string EscapeDataString(string input) + { + DataStringEscapeBuilder builder = new DataStringEscapeBuilder(input); + return builder.Build(); + } + + /// + /// Build a new escaped string + /// + /// The escaped string + private string Build() + { + Debug.Assert(this.index == 0, "Expected this.index to be 0, because Build can only be called once for an instance of DataStringEscapeBuilder."); + Debug.Assert(this.output.Length == 0, "Expected this.output.Length to be 0, because Build can only be called once for an instance of DataStringEscapeBuilder."); + + for (this.index = 0; this.index < this.input.Length; ++this.index) + { + char current = this.input[this.index]; + if (current == '\'' || current == '"') + { + this.ReadQuotedString(current); + } + else if (SensitiveCharacters.IndexOf(current) >= 0) + { + this.output.Append(Uri.EscapeDataString(current.ToString())); + } + else + { + this.output.Append(current); + } + } + + return this.output.ToString(); + } + + /// + /// Read quoted string + /// + /// The character that started the quote + private void ReadQuotedString(char quoteStart) + { + if (this.quotedDataBuilder == null) + { + this.quotedDataBuilder = new StringBuilder(); + } +#if DEBUG + else + { + Debug.Assert(this.quotedDataBuilder.Length == 0, "Expected quotedDataBuilder to have been cleared by previous call to ReadQuotedString"); + } +#endif + + this.output.Append(quoteStart); + while (++this.index < this.input.Length) + { + if (this.input[this.index] == quoteStart) + { + this.output.Append(Uri.EscapeDataString(this.quotedDataBuilder.ToString())); + this.output.Append(quoteStart); + this.quotedDataBuilder.Clear(); + + break; + } + + this.quotedDataBuilder.Append(this.input[this.index]); + } + + if (this.quotedDataBuilder.Length > 0) + { + // unterminated quote, should have validated before. We should not fail here. + Debug.Assert(false, "unterminated quote in uri."); + this.output.Append(Uri.EscapeDataString(this.quotedDataBuilder.ToString())); + this.quotedDataBuilder.Clear(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/HttpWebRequestMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/HttpWebRequestMessage.cs new file mode 100644 index 0000000..2004ef2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/HttpWebRequestMessage.cs @@ -0,0 +1,580 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Globalization; + using System.Net; +#if PORTABLELIB + using System.Reflection; + using System.Xml; +#endif + using Microsoft.OData; + + /// IODataRequestMessage interface implementation. + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Returning MemoryStream which doesn't require disposal")] + public class HttpWebRequestMessage : DataServiceClientRequestMessage + { + #region Private Fields + +#if PORTABLELIB + /// Running on silverlight + internal static bool IsRunningOnSilverlight = false; + + /// Cached method info that will be used to call to set the userAgent property if its not null. + private static MethodInfo UserAgentMethodSetter; + + /// Cached method info that will be used to call to set the ContentLength property if its not null. + private static MethodInfo ContentLengthMethodSetter; + + /// Indicates the current platform the portablelib is running on can't set the user agent property + private static bool SettingUserAgentPropertyValidOnCurrentPlatform = true; + + /// Indicates that the current platform doesn't support setting the header useragent property + private static bool SettingUserAgentOnHeadersCollectionValidOnCurrentPlatform = true; + + /// Indicates the current platform the portablelib is running on can't set the user agent property + private static bool SettingContentLengthPropertyValidOnCurrentPlatform = true; + + /// Indicates that the current platform doesn't support setting the header useragent property + private static bool SettingContentLengthOnHeadersCollectionValidOnCurrentPlatform = true; +#endif + /// Request Url. + private readonly Uri requestUrl; + + /// The effective HTTP method. + private readonly string effectiveHttpMethod; + + /// HttpWebRequest instance. + private readonly HttpWebRequest httpRequest; + + /// True if SendingRequest2Event is currently invoked, otherwise false. + private bool inSendingRequest2Event; +#if DEBUG + /// True if the sendingRequest2 event is already fired. + private bool sendingRequest2Fired; +#endif + + #endregion // Private Fields + +#if PORTABLELIB + /// + /// Initializes the class. + /// + static HttpWebRequestMessage() + { + IsRunningOnSilverlight = typeof(System.Windows.Input.ICommand).GetAssembly().GetType("System.Net.Browser.WebRequestCreator", false) != null; + + PropertyInfo pi = typeof(HttpWebRequest).GetProperty("ContentLength"); + if (pi != null) + { + ContentLengthMethodSetter = pi.GetSetMethod(); + } + + pi = typeof(HttpWebRequest).GetProperty("UserAgent"); + if (pi != null) + { + UserAgentMethodSetter = pi.GetSetMethod(); + } + } +#endif + + /// + /// Creates a new instance of HttpWebRequestMessage. + /// + /// Arguments for creating the request message. + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "SetHeader is a safe virtual method to be called from the constructor")] + public HttpWebRequestMessage(DataServiceClientRequestMessageArgs args) + : base(args.ActualMethod) + { + Util.CheckArgumentNull(args, "args"); + Debug.Assert(args.RequestUri.IsAbsoluteUri, "request uri is not absolute uri"); + Debug.Assert( + args.RequestUri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) || + args.RequestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase), + "request uri is not for HTTP"); + this.effectiveHttpMethod = args.Method; + this.requestUrl = args.RequestUri; + + this.httpRequest = HttpWebRequestMessage.CreateRequest(this.ActualMethod, this.Url, args); + + // Now set the headers. + foreach (var keyValue in args.Headers) + { + this.SetHeader(keyValue.Key, keyValue.Value); + } + } + + #region Properties + + /// Returns the request url. + public override Uri Url + { + get + { + return this.requestUrl; + } + + set + { + throw new NotSupportedException(); + } + } + + /// Returns the method for this request. + public override string Method + { + get + { + return this.effectiveHttpMethod; + } + + set + { + throw new NotSupportedException(); + } + } + + /// Returns the collection of request headers. + public override IEnumerable> Headers + { + get + { + List> headerValues = new List>(this.httpRequest.Headers.Count); + foreach (string headerName in this.httpRequest.Headers.AllKeys) + { + string headerValue = this.httpRequest.Headers[headerName]; + headerValues.Add(new KeyValuePair(headerName, headerValue)); + } + + return headerValues; + } + } + + /// Returns the underlying HttpWebRequest + public System.Net.HttpWebRequest HttpWebRequest + { + get + { + return this.httpRequest; + } + } + + /// + /// Gets or set the credentials for this request. + /// + public override System.Net.ICredentials Credentials + { + get { return this.httpRequest.Credentials; } + set { this.httpRequest.Credentials = value; } + } + +#if !PORTABLELIB + /// + /// Gets or sets the timeout (in seconds) for this request. + /// + public override int Timeout + { + get { return this.httpRequest.Timeout; } + set { this.httpRequest.Timeout = (int)Math.Min(Int32.MaxValue, new TimeSpan(0, 0, value).TotalMilliseconds); } + } + + /// + /// Gets or sets a value that indicates whether to send data in segments to the + /// Internet resource. + /// + public override bool SendChunked + { + get { return this.httpRequest.SendChunked; } + set { this.httpRequest.SendChunked = value; } + } +#endif + #endregion + + /// + /// Returns the value of the header with the given name. + /// + /// Name of the header. + /// Returns the value of the header with the given name. + public override string GetHeader(string headerName) + { + Util.CheckArgumentNullAndEmpty(headerName, "headerName"); + return HttpWebRequestMessage.GetHeaderValue(this.httpRequest, headerName); + } + + /// + /// Sets the value of the header with the given name. + /// + /// Name of the header. + /// Value of the header. + public override void SetHeader(string headerName, string headerValue) + { +#if DEBUG + // Only content length header is set after firing SendingRequest2 event + Debug.Assert(!this.sendingRequest2Fired || headerName == XmlConstants.HttpContentLength, "!this.sendingRequest2Fired || headerName == XmlConstants.HttpContentLength"); +#endif + Util.CheckArgumentNullAndEmpty(headerName, "headerName"); + HttpWebRequestMessage.SetHeaderValue(this.httpRequest, headerName, headerValue); + } + + /// + /// Gets the stream to be used to write the request payload. + /// + /// Stream to which the request payload needs to be written. + public override Stream GetStream() + { +#if PORTABLELIB + // not supported in async environments. Another IODataRequestMessage which buffers the request should be used. + throw new NotSupportedException(Strings.ODataRequestMessage_GetStreamMethodNotSupported); +#else + if (this.inSendingRequest2Event) + { + throw new NotSupportedException(Strings.ODataRequestMessage_GetStreamMethodNotSupported); + } + + return this.httpRequest.GetRequestStream(); +#endif + } + + /// + /// Abort the current request. + /// + public override void Abort() + { + Debug.Assert(this.httpRequest != null, "this.httpRequest != null"); + this.httpRequest.Abort(); + } + + /// + /// Begins an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request. + public override IAsyncResult BeginGetRequestStream(AsyncCallback callback, object state) + { + // Currently this method cannot get called from SendingRequest2 event - But if we expose these + // method in the future, this is a good check to have. + if (this.inSendingRequest2Event) + { + throw new NotSupportedException(Strings.ODataRequestMessage_GetStreamMethodNotSupported); + } + + return this.httpRequest.BeginGetRequestStream(callback, state); + } + + /// + /// Ends an asynchronous request for a System.IO.Stream object to use to write data. + /// + /// The pending request for a stream. + /// A System.IO.Stream to use to write request data. + public override Stream EndGetRequestStream(IAsyncResult asyncResult) + { + return this.httpRequest.EndGetRequestStream(asyncResult); + } + + /// + /// Begins an asynchronous request to an Internet resource. + /// + /// The System.AsyncCallback delegate. + /// The state object for this request. + /// An System.IAsyncResult that references the asynchronous request for a response. + public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) + { + return this.httpRequest.BeginGetResponse(callback, state); + } + + /// + /// Ends an asynchronous request to an Internet resource. + /// + /// The pending request for a response. + /// A System.Net.WebResponse that contains the response from the Internet resource. + public override IODataResponseMessage EndGetResponse(IAsyncResult asyncResult) + { + Debug.Assert(this.httpRequest != null, "this.httpRequest != null"); + try + { + HttpWebResponse httpResponse = (HttpWebResponse)this.httpRequest.EndGetResponse(asyncResult); +#if PORTABLELIB + if (!httpResponse.SupportsHeaders) + { + throw new NotSupportedException(Strings.Silverlight_BrowserHttp_NotSupported); + } +#endif + return new HttpWebResponseMessage(httpResponse); + } + catch (WebException webException) + { + throw ConvertToDataServiceWebException(webException); + } + } + +#if !PORTABLELIB + /// + /// Returns a response from an Internet resource. + /// + /// A System.Net.WebResponse that contains the response from the Internet resource. + public override IODataResponseMessage GetResponse() + { + try + { + HttpWebResponse httpResponse = (HttpWebResponse)this.httpRequest.GetResponse(); + return new HttpWebResponseMessage(httpResponse); + } + catch (WebException webException) + { + throw ConvertToDataServiceWebException(webException); + } + } +#endif + + /// + /// Sets the Content length of the Http web request + /// + /// Http web request to set the content length on + /// Length to set + internal static void SetHttpWebRequestContentLength(HttpWebRequest httpWebRequest, long contentLength) + { +#if PORTABLELIB + // TODO: May need to add a special case for when the content length is zero + bool headerSet = false; + if (ContentLengthMethodSetter != null && SettingContentLengthPropertyValidOnCurrentPlatform) + { + try + { + ContentLengthMethodSetter.Invoke(httpWebRequest, new object[] { contentLength }); + headerSet = true; + } + catch (InvalidOperationException) + { + SettingContentLengthPropertyValidOnCurrentPlatform = false; + } + } + + if (!headerSet && SettingContentLengthOnHeadersCollectionValidOnCurrentPlatform) + { + // Attempting to set the header but may just not work + try + { + // Need this code to set the user agent on silverlight when the property doesn't exist + httpWebRequest.Headers[XmlConstants.HttpContentLength] = XmlConvert.ToString(contentLength); + } + catch (ArgumentException) + { + SettingContentLengthOnHeadersCollectionValidOnCurrentPlatform = false; + } + } +#else + httpWebRequest.ContentLength = contentLength; +#endif + } + + /// + /// Sets the accept charset. + /// + /// The HTTP web request. + /// The header value. + internal static void SetAcceptCharset(HttpWebRequest httpWebRequest, string headerValue) + { +#if !PORTABLELIB + httpWebRequest.Headers[XmlConstants.HttpAcceptCharset] = headerValue; +#endif +#if PORTABLELIB + if (!IsRunningOnSilverlight) + { + httpWebRequest.Headers[XmlConstants.HttpAcceptCharset] = headerValue; + } +#endif + } + + /// + /// Attempts to set the UserAgent property if it exists other wise returns false + /// + /// The http web request. + /// The value of the user agent. + internal static void SetUserAgentHeader(HttpWebRequest httpWebRequest, string headerValue) + { +#if !PORTABLELIB + httpWebRequest.UserAgent = headerValue; +#endif +#if PORTABLELIB + if (IsRunningOnSilverlight || !SettingUserAgentPropertyValidOnCurrentPlatform) + { + return; + } + + bool headerSet = false; + + // Weird behavior, looks like this won't work in silverlight on the .net portable libaries. + // http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/7440fc9d-3490-4823-ac93-73706b535c6a/ + // http://connect.microsoft.com/VisualStudio/feedback/details/770104/cannot-set-useragent-of-a-httpwebrequest-in-portable-class-and-winrt-libraries + // On Windows 8 store throws InvalidOperationException when setting the UserAgent + if (UserAgentMethodSetter != null) + { + try + { + UserAgentMethodSetter.Invoke(httpWebRequest, new object[] { headerValue }); + headerSet = true; + } + catch (InvalidOperationException) + { + SettingUserAgentPropertyValidOnCurrentPlatform = false; + } + catch (TargetInvocationException) + { + SettingUserAgentPropertyValidOnCurrentPlatform = false; + } + } + + if (!headerSet || SettingUserAgentOnHeadersCollectionValidOnCurrentPlatform) + { + // Attempting to set the header but may just not work + try + { + // Need this code to set the user agent on silverlight when the property doesn't exist + httpWebRequest.Headers[XmlConstants.HttpUserAgent] = headerValue; + } + catch (ArgumentException) + { + SettingUserAgentOnHeadersCollectionValidOnCurrentPlatform = false; + } + } +#endif + } + + /// + /// This method is called just before firing SendingRequest2 event. + /// + internal void BeforeSendingRequest2Event() + { + this.inSendingRequest2Event = true; + } + + /// + /// This method is called immd. after SendingRequest2 event has been fired. + /// + internal void AfterSendingRequest2Event() + { + this.inSendingRequest2Event = false; +#if DEBUG + this.sendingRequest2Fired = true; +#endif + } + + /// + /// Create an instance of HttpWebRequest from the given IODataRequestMessage instance. This method + /// is also responsible for firing SendingRequest event. + /// + /// Http Method. + /// Request Url. + /// DataServiceClientRequestMessageArgs instance. + /// an instance of HttpWebRequest. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "args", Justification = "the parameter is used in the SL version.")] + private static HttpWebRequest CreateRequest(string method, Uri requestUrl, DataServiceClientRequestMessageArgs args) + { + HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestUrl); +#if PORTABLELIB + // Set same default as Silverlight when running silverlight in .Net Portable + if (IsRunningOnSilverlight) + { + httpRequest.UseDefaultCredentials = args.UseDefaultCredentials; + } +#else + // KeepAlive not available + httpRequest.KeepAlive = true; +#endif + httpRequest.Method = method; + return httpRequest; + } + + /// + /// Sets the value of the given header in the httpwebrequest instance. + /// This has a special case for some of the headers to set the properties on the request instead of using the Headers collection. + /// + /// The request to apply the header to. + /// Name of the header. + /// Value of the header. + private static void SetHeaderValue(HttpWebRequest request, string headerName, string headerValue) + { + if (string.Equals(headerName, XmlConstants.HttpRequestAccept, StringComparison.OrdinalIgnoreCase)) + { + request.Accept = headerValue; + } + else if (string.Equals(headerName, XmlConstants.HttpContentType, StringComparison.OrdinalIgnoreCase)) + { + request.ContentType = headerValue; + } + else if (string.Equals(headerName, XmlConstants.HttpContentLength, StringComparison.OrdinalIgnoreCase)) + { + // Keeping long.parse so that this will fail if the string is not a valid long value + SetHttpWebRequestContentLength(request, long.Parse(headerValue, CultureInfo.InvariantCulture)); + } + else if (string.Equals(headerName, XmlConstants.HttpUserAgent, StringComparison.OrdinalIgnoreCase)) + { + SetUserAgentHeader(request, headerValue); + } + else if (string.Equals(headerName, XmlConstants.HttpAcceptCharset, StringComparison.OrdinalIgnoreCase)) + { + SetAcceptCharset(request, headerValue); + } + else + { + request.Headers[headerName] = headerValue; + } + } + + /// + /// Get the value of the given header in the httpwebrequest instance. + /// This has a special case for some of the headers to set the properties on the request instead of using the Headers collection. + /// + /// The request to get the header value from. + /// Name of the header. + /// the value of the header with the given name. + private static string GetHeaderValue(HttpWebRequest request, string headerName) + { + if (string.Equals(headerName, XmlConstants.HttpRequestAccept, StringComparison.OrdinalIgnoreCase)) + { + return request.Accept; + } + else if (string.Equals(headerName, XmlConstants.HttpContentType, StringComparison.OrdinalIgnoreCase)) + { + return request.ContentType; + } +#if !PORTABLELIB + else if (string.Equals(headerName, XmlConstants.HttpContentLength, StringComparison.OrdinalIgnoreCase)) + { + return request.ContentLength.ToString(CultureInfo.InvariantCulture); + } +#endif + else + { + return request.Headers[headerName]; + } + } + + /// + /// Convert the WebException into DataServiceWebException. + /// + /// WebException instance. + /// an instance of DataServiceWebException that abstracts the WebException. + private static DataServiceTransportException ConvertToDataServiceWebException(WebException webException) + { + HttpWebResponseMessage errorResponseMessage = null; + if (webException.Response != null) + { + var httpResponse = (HttpWebResponse)webException.Response; + errorResponseMessage = new HttpWebResponseMessage(httpResponse); + } + + return new DataServiceTransportException(errorResponseMessage, webException); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataPropertyConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataPropertyConverter.cs new file mode 100644 index 0000000..787fb3e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataPropertyConverter.cs @@ -0,0 +1,692 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData; + using Microsoft.OData.Json; + using Microsoft.OData.Metadata; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + + /// + /// Component for converting properties on client types into instance of in order to serialize insert/update payloads. + /// + internal class ODataPropertyConverter + { + /// + /// The request info. + /// + private readonly RequestInfo requestInfo; + + /// + /// Initializes a new instance of the class. + /// + /// The request info. + internal ODataPropertyConverter(RequestInfo requestInfo) + { + Debug.Assert(requestInfo != null, "requestInfo != null"); + this.requestInfo = requestInfo; + } + + /// + /// Creates a list of ODataProperty instances for the given set of properties. + /// + /// Instance of the resource which is getting serialized. + /// The server type name of the entity whose properties are being populated. + /// The properties to populate into instance of ODataProperty. + /// Populated ODataProperty instances for the given properties. + internal IEnumerable PopulateProperties(object resource, string serverTypeName, IEnumerable properties) + { + Debug.Assert(properties != null, "properties != null"); + List odataProperties = new List(); + var populatedProperties = properties.Where(p => !p.IsComplex && !p.IsComplexCollection); + foreach (ClientPropertyAnnotation property in populatedProperties) + { + object propertyValue = property.GetValue(resource); + ODataValue odataValue; + if (this.TryConvertPropertyValue(property, propertyValue, serverTypeName, null, out odataValue)) + { + odataProperties.Add( + new ODataProperty + { + Name = property.PropertyName, + Value = odataValue + }); + + this.AddTypeAnnotationNotDeclaredOnServer(serverTypeName, property, odataValue); + } + } + + return odataProperties; + } + + /// + /// Creates and returns an ODataResourceWrapper from the given value. + /// + /// The value type. + /// The complex instance. + /// If the value is a property, then it represents the name of the property. Can be null, for non-property. + /// Set of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// An ODataResourceWrapper representing the given value. + internal ODataResourceWrapper CreateODataResourceWrapperForComplex(Type complexType, object instance, string propertyName, HashSet visitedComplexTypeObjects) + { + Debug.Assert(complexType != null, "complexType != null"); + + ClientEdmModel model = this.requestInfo.Model; + ClientTypeAnnotation complexTypeAnnotation = model.GetClientTypeAnnotation(complexType); + Debug.Assert(complexTypeAnnotation != null, "complexTypeAnnotation != null"); + Debug.Assert(!complexTypeAnnotation.IsEntityType, "Unexpected entity"); + + if (instance == null) + { + return new ODataResourceWrapper() { Resource = null }; + } + + if (visitedComplexTypeObjects == null) + { + visitedComplexTypeObjects = new HashSet(ReferenceEqualityComparer.Instance); + } + else if (visitedComplexTypeObjects.Contains(instance)) + { + if (propertyName != null) + { + throw Error.InvalidOperation(Strings.Serializer_LoopsNotAllowedInComplexTypes(propertyName)); + } + else + { + Debug.Assert(complexTypeAnnotation.ElementTypeName != null, "complexTypeAnnotation.ElementTypeName != null"); + throw Error.InvalidOperation(Strings.Serializer_LoopsNotAllowedInNonPropertyComplexTypes(complexTypeAnnotation.ElementTypeName)); + } + } + + visitedComplexTypeObjects.Add(instance); + + ODataResource resource = new ODataResource() { TypeName = complexTypeAnnotation.ElementTypeName }; + + string serverTypeName = this.requestInfo.GetServerTypeName(complexTypeAnnotation); + resource.TypeAnnotation = new ODataTypeAnnotation(serverTypeName); + + resource.Properties = this.PopulateProperties(instance, serverTypeName, complexTypeAnnotation.PropertiesToSerialize(), visitedComplexTypeObjects); + + var wrapper = new ODataResourceWrapper() { Resource = resource, Instance = instance }; + + wrapper.NestedResourceInfoWrappers = this.PopulateNestedComplexProperties(instance, serverTypeName, complexTypeAnnotation.PropertiesToSerialize(), visitedComplexTypeObjects); + + visitedComplexTypeObjects.Remove(instance); + + return wrapper; + } + + /// + /// Creates a list of ODataNestedResourceInfoWrapper instances for the given set of properties. + /// + /// Instance of the resource which is getting serialized. + /// The server type name of the entity whose properties are being populated. + /// The properties to populate into instance of ODataProperty. + /// Set of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// Populated ODataNestedResourceInfoWrapper instances for the given properties. + internal IEnumerable PopulateNestedComplexProperties(object resource, string serverTypeName, IEnumerable properties, HashSet visitedComplexTypeObjects) + { + Debug.Assert(properties != null, "properties != null"); + + List odataNestedResourceInfoWrappers = new List(); + var populatedProperties = properties.Where(p => p.IsComplex || p.IsComplexCollection); + + foreach (ClientPropertyAnnotation property in populatedProperties) + { + object propertyValue = property.GetValue(resource); + ODataItemWrapper odataItem; + if (this.TryConvertPropertyToResourceOrResourceSet(property, propertyValue, serverTypeName, visitedComplexTypeObjects, out odataItem)) + { + odataNestedResourceInfoWrappers.Add(new ODataNestedResourceInfoWrapper + { + NestedResourceInfo = new ODataNestedResourceInfo() + { + Name = property.PropertyName, + IsCollection = property.IsComplexCollection + }, + NestedResourceOrResourceSet = odataItem + }); + } + } + + return odataNestedResourceInfoWrappers; + } + + /// + /// Creates and returns an ODataResource from the given value. + /// + /// The value type. + /// The entry value. + /// The given properties to serialize. + /// An ODataResource representing the given value. + internal ODataResource CreateODataEntry(Type entityType, object value, params ClientPropertyAnnotation[] properties) + { + Debug.Assert(entityType != null, "entityType != null"); + + if (value == null) + { + return null; + } + + ClientEdmModel model = this.requestInfo.Model; + ClientTypeAnnotation entityTypeAnnotation = model.GetClientTypeAnnotation(value.GetType()); + Debug.Assert(entityTypeAnnotation != null, "entityTypeAnnotation != null"); + Debug.Assert(entityTypeAnnotation.IsStructuredType, "Unexpected type"); + + ODataResource odataEntityValue = new ODataResource() + { + TypeName = entityTypeAnnotation.ElementTypeName, + }; + + string serverTypeName = this.requestInfo.GetServerTypeName(entityTypeAnnotation); + odataEntityValue.TypeAnnotation = new ODataTypeAnnotation(serverTypeName); + + odataEntityValue.Properties = this.PopulateProperties(value, serverTypeName, properties.Any() ? properties : entityTypeAnnotation.PropertiesToSerialize(), null); + + return odataEntityValue; + } + + /// + /// Creates and returns an ODataResourceWrapper from the given value. + /// + /// The value type. + /// The resource value. + /// The given properties to serialize. + /// An ODataResourceWrapper representing the given value. + internal ODataResourceWrapper CreateODataResourceWrapper(Type entityType, object value, params ClientPropertyAnnotation[] properties) + { + Debug.Assert(entityType != null, "entityType != null"); + + if (value == null) + { + return null; + } + + ClientEdmModel model = this.requestInfo.Model; + ClientTypeAnnotation entityTypeAnnotation = model.GetClientTypeAnnotation(value.GetType()); + Debug.Assert(entityTypeAnnotation != null, "entityTypeAnnotation != null"); + Debug.Assert(entityTypeAnnotation.IsStructuredType, "Unexpected type"); + + ODataResource odataEntityValue = new ODataResource() + { + TypeName = entityTypeAnnotation.ElementTypeName, + }; + + string serverTypeName = this.requestInfo.GetServerTypeName(entityTypeAnnotation); + odataEntityValue.TypeAnnotation = new ODataTypeAnnotation(serverTypeName); + + odataEntityValue.Properties = this.PopulateProperties(value, serverTypeName, properties.Any() ? properties : entityTypeAnnotation.PropertiesToSerialize(), null); + + var wrapper = new ODataResourceWrapper() { Resource = odataEntityValue }; + wrapper.NestedResourceInfoWrappers = PopulateNestedComplexProperties(value, serverTypeName, properties.Any() ? properties : entityTypeAnnotation.PropertiesToSerialize(), null); + + return wrapper; + } + + /// + /// Creates and returns an ODataResource from the given value. + /// + /// The value type. + /// The entry value. + /// An ODataResource representing the given value. + internal IEnumerable CreateODataEntries(Type entityType, object value) + { + Debug.Assert(entityType != null, "entityType != null"); + Debug.Assert(value != null, "value != null"); + + var list = value as IEnumerable; + var entries = new List(); + if (list != null) + { + entries.AddRange(from object o in list select this.CreateODataEntry(entityType, o)); + } + + return entries; + } + + /// + /// Converts CLR value into ODataEnumValue. + /// + /// The CLR type. + /// The Enum value. + /// The bool isCollectionItem. + /// An ODataEnumValue instance. + internal ODataEnumValue CreateODataEnumValue(Type enumClrType, object value, bool isCollectionItem) + { + Debug.Assert(enumClrType != null && enumClrType.IsEnum(), "enumClrType != null && enumClrType.IsEnum"); + Debug.Assert(value != null || !isCollectionItem, "Collection items must not be null"); + + ClientEdmModel model = this.requestInfo.Model; + ClientTypeAnnotation enumTypeAnnotation = model.GetClientTypeAnnotation(enumClrType); + Debug.Assert(enumTypeAnnotation != null, "enumTypeAnnotation != null"); + Debug.Assert(!enumTypeAnnotation.IsEntityType, "Unexpected entity"); + + // Handle null value by putting m:null="true" + if (value == null) + { + Debug.Assert(!isCollectionItem, "Null collection items are not supported. Should have already been checked."); + return null; + } + + return new ODataEnumValue(ClientTypeUtil.GetEnumValuesString(value.ToString(), enumClrType), enumTypeAnnotation.ElementTypeName); + } + + /// + /// Creates and returns an ODataCollectionValue from the given value. + /// + /// The type of the value. + /// If the value is a property, then it represents the name of the property. Can be null, for non-property. + /// The value. + /// Set of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// Whether this collection property is a dynamic property + /// If true, set the type annotation on ODataValue. + /// An ODataCollectionValue representing the given value. + internal ODataCollectionValue CreateODataCollection(Type collectionItemType, string propertyName, object value, HashSet visitedComplexTypeObjects, bool isDynamicProperty, bool setTypeAnnotation = true) + { + Debug.Assert(collectionItemType != null, "collectionItemType != null"); + + WebUtil.ValidateCollection(collectionItemType, value, propertyName, isDynamicProperty); + + PrimitiveType ptype; + bool isCollectionOfPrimitiveTypes = PrimitiveType.TryGetPrimitiveType(collectionItemType, out ptype); + + ODataCollectionValue collection = new ODataCollectionValue(); + IEnumerable enumerablePropertyValue = (IEnumerable)value; + string collectionItemTypeName; + string collectionTypeName; + if (isCollectionOfPrimitiveTypes) + { + collectionItemTypeName = ClientConvert.GetEdmType(Nullable.GetUnderlyingType(collectionItemType) ?? collectionItemType); + + if (enumerablePropertyValue != null) + { + collection.Items = Util.GetEnumerable( + enumerablePropertyValue, + (val) => + { + if (val == null) + { + return null; + } + + WebUtil.ValidatePrimitiveCollectionItem(val, propertyName, collectionItemType); + return ConvertPrimitiveValueToRecognizedODataType(val, collectionItemType); + }); + } + + // TypeName for primitives should be the EDM name since that's what we will be able to look up in the model + collectionTypeName = collectionItemTypeName; + } + else + { + Type collectionItemTypeTmp = Nullable.GetUnderlyingType(collectionItemType) ?? collectionItemType; + + // Note that the collectionItemTypeName will be null if the context does not have the ResolveName func. + collectionItemTypeName = this.requestInfo.ResolveNameFromType(collectionItemType); + + if (enumerablePropertyValue != null) + { + collection.Items = Util.GetEnumerable( + enumerablePropertyValue, + (val) => + { + if (val == null) + { + return new ODataEnumValue(null, collectionItemType.FullName) as ODataValue; + } + + return new ODataEnumValue(ClientTypeUtil.GetEnumValuesString(val.ToString(), collectionItemTypeTmp), collectionItemType.FullName) as ODataValue; + }); + } + + // TypeName for complex types needs to be the client type name (not the one we resolved above) since it will be looked up in the client model + collectionTypeName = collectionItemType.FullName; + } + + // Set the type name to use for client type lookups and validation. + collection.TypeName = GetCollectionName(collectionTypeName); + + // Ideally, we should not set type annotation on collection value. + // To keep backward compatibility, we'll keep it in request body, but do not include it in url. + if (setTypeAnnotation) + { + string wireTypeName = GetCollectionName(collectionItemTypeName); + collection.TypeAnnotation = new ODataTypeAnnotation(wireTypeName); + } + + return collection; + } + + /// + /// Creates and returns an ODataResourceSetWrapper from the given value. + /// + /// The type of the value. + /// If the value is a property, then it represents the name of the property. Can be null, for non-property. + /// The value. + /// Set of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// Whether this collection property is a dynamic property + /// If true, set the type annotation on ODataValue. + /// An ODataResourceSetWrapper representing the given value. + internal ODataResourceSetWrapper CreateODataResourceSetWrapperForComplexCollection(Type collectionItemType, string propertyName, object value, HashSet visitedComplexTypeObjects, bool isDynamicProperty, bool setTypeAnnotation = true) + { + Debug.Assert(collectionItemType != null, "collectionItemType != null"); + + WebUtil.ValidateCollection(collectionItemType, value, propertyName, isDynamicProperty); + + ODataResourceSet resourceSet = new ODataResourceSet(); + ODataResourceSetWrapper wrapper = new ODataResourceSetWrapper() + { + ResourceSet = resourceSet + }; + + // Note that the collectionItemTypeName will be null if the context does not have the ResolveName func. + string collectionItemTypeName = this.requestInfo.ResolveNameFromType(collectionItemType); + + IEnumerable enumerablePropertyValue = (IEnumerable)value; + if (enumerablePropertyValue != null) + { + wrapper.Resources = Util.GetEnumerable( + enumerablePropertyValue, + (val) => + { + if (val == null) + { + return null; + } + + WebUtil.ValidateComplexCollectionItem(val, propertyName, collectionItemType); + var complexResource = this.CreateODataResourceWrapperForComplex(val.GetType(), val, propertyName, visitedComplexTypeObjects); + return complexResource; + }); + } + + // Ideally, we should not set type annotation on collection value. + // To keep backward compatibility, we'll keep it in request body, but do not include it in url. + if (setTypeAnnotation) + { + string wireTypeName = GetCollectionName(collectionItemTypeName); + resourceSet.TypeAnnotation = new ODataTypeAnnotation(wireTypeName); + } + + return wrapper; + } + + /// + /// Returns the primitive property value. + /// + /// Value of the property. + /// Type of the property. + /// Returns the value of the primitive property. + internal static object ConvertPrimitiveValueToRecognizedODataType(object propertyValue, Type propertyType) + { + Debug.Assert(PrimitiveType.IsKnownNullableType(propertyType), "GetPrimitiveValue must be called only for primitive types"); + Debug.Assert(propertyValue == null || PrimitiveType.IsKnownType(propertyValue.GetType()), "GetPrimitiveValue method must be called for primitive values only"); + + if (propertyValue == null) + { + return null; + } + + PrimitiveType primitiveType; + PrimitiveType.TryGetPrimitiveType(propertyType, out primitiveType); + Debug.Assert(primitiveType != null, "must be a known primitive type"); + + // Do the conversion for types that are not supported by ODataLib e.g. char[], char, etc + if (propertyType == typeof(Char) || + propertyType == typeof(Char[]) || + propertyType == typeof(Type) || + propertyType == typeof(Uri) || + propertyType == typeof(System.Xml.Linq.XDocument) || + propertyType == typeof(System.Xml.Linq.XElement)) + { + return primitiveType.TypeConverter.ToString(propertyValue); + } + else if (propertyType == typeof(DateTime)) + { + return PlatformHelper.ConvertDateTimeToDateTimeOffset((DateTime)propertyValue); + } +#if !PORTABLELIB + else if (propertyType.FullName == "System.Data.Linq.Binary") + { + // For System.Data.Linq.Binary, it is a delay loaded type. Hence checking it based on name. + // PrimitiveType.IsKnownType checks for binary type based on name and assembly. Hence just + // checking name here is sufficient, since any other type with the same name, but in different + // assembly will return false for PrimitiveType.IsKnownNullableType. + // Since ODataLib does not understand binary type, we need to convert the value to byte[]. + return ((BinaryTypeConverter)primitiveType.TypeConverter).ToArray(propertyValue); + } +#endif + else if (primitiveType.EdmTypeName == null) + { + // case StorageType.DateTimeOffset: + // case StorageType.TimeSpan: + // case StorageType.UInt16: + // case StorageType.UInt32: + // case StorageType.UInt64: + // don't support reverse mappings for these types in this version + // allows us to add real server support in the future without a + // "breaking change" in the future client + throw new NotSupportedException(Strings.ALinq_CantCastToUnsupportedPrimitive(propertyType.Name)); + } + + return propertyValue; + } + + /// + /// Gets the specified type name as an EDM Collection type, e.g. Collection(Edm.String) + /// + /// Type name of the items in the collection. + /// Collection type name for the specified item type name. + private static string GetCollectionName(string itemTypeName) + { + return itemTypeName == null ? null : EdmLibraryExtensions.GetCollectionTypeName(itemTypeName); + } + + /// + /// Creates and returns an for the given primitive value. + /// + /// The property being converted. + /// The property value to convert.. + /// An ODataValue representing the given value. + private static ODataValue CreateODataPrimitivePropertyValue(ClientPropertyAnnotation property, object propertyValue) + { + if (propertyValue == null) + { + return new ODataNullValue(); + } + + propertyValue = ConvertPrimitiveValueToRecognizedODataType(propertyValue, property.PropertyType); + + return new ODataPrimitiveValue(propertyValue); + } + + /// + /// Creates a list of ODataProperty instances for the given set of properties. + /// + /// Instance of the resource which is getting serialized. + /// The server type name of the entity whose properties are being populated. + /// The properties to populate into instance of ODataProperty. + /// Set of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// Populated ODataProperty instances for the given properties. + private IEnumerable PopulateProperties(object resource, string serverTypeName, IEnumerable properties, HashSet visitedComplexTypeObjects) + { + Debug.Assert(properties != null, "properties != null"); + List odataProperties = new List(); + var populatedProperties = properties.Where(p => !p.IsComplex && !p.IsComplexCollection); + foreach (ClientPropertyAnnotation property in populatedProperties) + { + object propertyValue = property.GetValue(resource); + ODataValue odataValue; + if (this.TryConvertPropertyValue(property, propertyValue, serverTypeName, visitedComplexTypeObjects, out odataValue)) + { + odataProperties.Add(new ODataProperty + { + Name = property.PropertyName, + Value = odataValue + }); + } + } + + return odataProperties; + } + + /// + /// Tries to convert the given value into an instance of . + /// + /// The property being converted. + /// The property value to convert.. + /// The server type name of the entity whose properties are being populated. + /// Set of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// The odata value if one was created. + /// Whether or not the value was converted. + private bool TryConvertPropertyValue(ClientPropertyAnnotation property, object propertyValue, string serverTypeName, HashSet visitedComplexTypeObjects, out ODataValue odataValue) + { + if (property.IsKnownType) + { + odataValue = CreateODataPrimitivePropertyValue(property, propertyValue); + return true; + } + + if (property.IsEnumType) + { + string enumValue; + if (propertyValue == null) + { + enumValue = null; + } + else + { + enumValue = ClientTypeUtil.GetEnumValuesString(propertyValue.ToString(), property.PropertyType); + } + + string typeNameInMetadata = this.requestInfo.ResolveNameFromType(property.PropertyType); + odataValue = new ODataEnumValue(enumValue, typeNameInMetadata); + return true; + } + + if (property.IsPrimitiveOrEnumOrComplexCollection) + { + odataValue = this.CreateODataCollectionPropertyValue(property, propertyValue, serverTypeName, visitedComplexTypeObjects); + return true; + } + + odataValue = null; + return false; + } + + /// + /// Tries to convert the given value into an instance of . + /// + /// The property being converted. + /// The property value to convert.. + /// The server type name of the entity whose properties are being populated. + /// Set of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// The odata resource or resource set if one was created. + /// Whether or not the value was converted. + private bool TryConvertPropertyToResourceOrResourceSet(ClientPropertyAnnotation property, object propertyValue, string serverTypeName, HashSet visitedComplexTypeObjects, out ODataItemWrapper odataItem) + { + if (property.IsComplexCollection) + { + odataItem = this.CreateODataComplexCollectionPropertyResourceSet(property, propertyValue, serverTypeName, visitedComplexTypeObjects); + return true; + } + + if (property.IsComplex) + { + odataItem = this.CreateODataComplexPropertyResource(property, propertyValue, visitedComplexTypeObjects); + return true; + } + + odataItem = null; + return false; + } + + /// + /// Returns the resource of the complex property. + /// + /// Property which contains name, type, is key (if false and null value, will throw). + /// property value + /// List of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// An instance of ODataResourceWrapper containing the resource of the properties of the given complex type. + private ODataResourceWrapper CreateODataComplexPropertyResource(ClientPropertyAnnotation property, object propertyValue, HashSet visitedComplexTypeObjects) + { + Type propertyType = property.IsComplexCollection ? property.PrimitiveOrComplexCollectionItemType : property.PropertyType; + if (propertyValue != null && propertyType != propertyValue.GetType()) + { + Debug.Assert(propertyType.IsAssignableFrom(propertyValue.GetType()), "Type from value should equals to or derived from property type from model."); + propertyType = propertyValue.GetType(); + } + + return this.CreateODataResourceWrapperForComplex(propertyType, propertyValue, property.PropertyName, visitedComplexTypeObjects); + } + + /// + /// Returns the value of the collection property. + /// + /// Collection property details. Must not be null. + /// Collection instance. + /// The server type name of the entity whose properties are being populated. + /// List of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// An instance of ODataCollectionValue representing the value of the property. + private ODataCollectionValue CreateODataCollectionPropertyValue(ClientPropertyAnnotation property, object propertyValue, string serverTypeName, HashSet visitedComplexTypeObjects) + { + Debug.Assert(property != null, "property != null"); + Debug.Assert(property.IsPrimitiveOrEnumOrComplexCollection, "This method is supposed to be used only for writing collections"); + Debug.Assert(property.PropertyName != null, "property.PropertyName != null"); + bool isDynamic = this.requestInfo.TypeResolver.ShouldWriteClientTypeForOpenServerProperty(property.EdmProperty, serverTypeName); + return this.CreateODataCollection(property.PrimitiveOrComplexCollectionItemType, property.PropertyName, propertyValue, visitedComplexTypeObjects, isDynamic); + } + + /// + /// Returns the resource set of the collection property. + /// + /// Collection property details. Must not be null. + /// Collection instance. + /// The server type name of the entity whose properties are being populated. + /// List of instances of complex types encountered in the hierarchy. Used to detect cycles. + /// An instance of ODataResourceSetWrapper representing the value of the property. + private ODataResourceSetWrapper CreateODataComplexCollectionPropertyResourceSet(ClientPropertyAnnotation property, object propertyValue, string serverTypeName, HashSet visitedComplexTypeObjects) + { + Debug.Assert(property != null, "property != null"); + Debug.Assert(property.IsComplexCollection, "This method is supposed to be used only for writing collections"); + Debug.Assert(property.PropertyName != null, "property.PropertyName != null"); + bool isDynamic = this.requestInfo.TypeResolver.ShouldWriteClientTypeForOpenServerProperty(property.EdmProperty, serverTypeName); + return this.CreateODataResourceSetWrapperForComplexCollection(property.PrimitiveOrComplexCollectionItemType, property.PropertyName, propertyValue, visitedComplexTypeObjects, isDynamic); + } + + /// + /// Adds a type annotation to the value if it is primitive and not defined on the server. + /// + /// The server type name of the entity whose properties are being populated. + /// The current property. + /// The already converted value of the property. + private void AddTypeAnnotationNotDeclaredOnServer(string serverTypeName, ClientPropertyAnnotation property, ODataValue odataValue) + { + Debug.Assert(property != null, "property != null"); + Debug.Assert(property.EdmProperty != null, "property.EdmProperty != null"); + var primitiveValue = odataValue as ODataPrimitiveValue; + if (primitiveValue == null) + { + return; + } + + if (this.requestInfo.TypeResolver.ShouldWriteClientTypeForOpenServerProperty(property.EdmProperty, serverTypeName) + && !JsonSharedUtils.ValueTypeMatchesJsonType(primitiveValue, property.EdmProperty.Type.AsPrimitive())) + { + // DEVNOTE: it is safe to use the property type name for primitive types because they do not generally support inheritance, + // and spatial values always contain their specific type inside the GeoJSON/GML representation. + primitiveValue.TypeAnnotation = new ODataTypeAnnotation(property.EdmProperty.Type.FullName()); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataWriterWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataWriterWrapper.cs new file mode 100644 index 0000000..bafa540 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/ODataWriterWrapper.cs @@ -0,0 +1,184 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData; + + /// + /// Forwards calls to an OData Writer + /// + internal class ODataWriterWrapper + { + /// The odataWriter. + private readonly ODataWriter odataWriter; + + /// The payload writing events. + private readonly DataServiceClientRequestPipelineConfiguration requestPipeline; + + /// + /// Prevents a default instance of the class from being created. + /// + /// The odata writer. + /// The request pipeline configuration. + private ODataWriterWrapper(ODataWriter odataWriter, DataServiceClientRequestPipelineConfiguration requestPipeline) + { + Debug.Assert(odataWriter != null, "odataWriter != null"); + Debug.Assert(requestPipeline != null, "DataServiceClientRequestPipelineConfiguration != null"); + + this.odataWriter = odataWriter; + this.requestPipeline = requestPipeline; + } + + /// + /// Creates the odata entry writer. + /// + /// We never create a feed writer as the client doesn't support deep insert, if we did this would need to change + /// The message writer. + /// The request pipeline configuration. + /// The odata Writer Wrapper + internal static ODataWriterWrapper CreateForEntry(ODataMessageWriter messageWriter, DataServiceClientRequestPipelineConfiguration requestPipeline) + { + return new ODataWriterWrapper(messageWriter.CreateODataResourceWriter(), requestPipeline); + } + + /// + /// Creates the odata entry writer for testing purposes only + /// + /// The odata writer. + /// The request pipeline configuration. + /// The odata Writer Wrapper + internal static ODataWriterWrapper CreateForEntryTest(ODataWriter writer, DataServiceClientRequestPipelineConfiguration requestPipeline) + { + return new ODataWriterWrapper(writer, requestPipeline); + } + + /// + /// Writes the start. + /// + /// The resource set. + internal void WriteStart(ODataResourceSet resourceSet) + { + this.odataWriter.WriteStart(resourceSet); + } + + /// + /// Writes the end. + /// + internal void WriteEnd() + { + this.odataWriter.WriteEnd(); + } + + /// + /// Writes the start. + /// + /// The resource. + internal void WriteStartResource(ODataResource resource) + { + this.odataWriter.WriteStart(resource); + } + + /// + /// Writes the start for the resource. + /// + /// The resource. + /// The entity. + internal void WriteStart(ODataResource resource, object entity) + { + this.requestPipeline.ExecuteOnEntryStartActions(resource, entity); + this.odataWriter.WriteStart(resource); + } + + /// + /// Writes the end. + /// + /// The entry. + /// The entity. + internal void WriteEnd(ODataResource entry, object entity) + { + this.requestPipeline.ExecuteOnEntryEndActions(entry, entity); + this.odataWriter.WriteEnd(); + } + + /// + /// Writes the end. + /// + /// The link. + /// The source. + /// The target. + internal void WriteEnd(ODataNestedResourceInfo navlink, object source, object target) + { + this.requestPipeline.ExecuteOnNestedResourceInfoEndActions(navlink, source, target); + this.odataWriter.WriteEnd(); + } + + /// + /// Writes the end Actions. + /// + /// The link. + /// The source. + /// The target. + internal void WriteNestedResourceInfoEnd(ODataNestedResourceInfo navlink, object source, object target) + { + this.requestPipeline.ExecuteOnNestedResourceInfoEndActions(navlink, source, target); + } + + /// + /// Writes the navigation link end. + /// + internal void WriteNestedResourceInfoEnd() + { + this.odataWriter.WriteEnd(); + } + + /// + /// Writes the start. + /// + /// The navigation link. + /// The source. + /// The target. + internal void WriteStart(ODataNestedResourceInfo navigationLink, object source, object target) + { + this.requestPipeline.ExecuteOnNestedResourceInfoStartActions(navigationLink, source, target); + this.odataWriter.WriteStart(navigationLink); + } + + /// + /// Writes the start for Navigation link. + /// + /// The navigation link. + /// The source. + /// The target. + internal void WriteNestedResourceInfoStart(ODataNestedResourceInfo navigationLink, object source, object target) + { + this.requestPipeline.ExecuteOnNestedResourceInfoStartActions(navigationLink, source, target); + } + + /// + /// Writes the start for a collection of navigation links. + /// + /// The navigation link. + internal void WriteNestedResourceInfoStart(ODataNestedResourceInfo navigationLink) + { + this.odataWriter.WriteStart(navigationLink); + } + + /// + /// Writes the entity reference link. + /// + /// The reference link. + /// The source. + /// The target. + internal void WriteEntityReferenceLink(ODataEntityReferenceLink referenceLink, object source, object target) + { + this.requestPipeline.ExecuteEntityReferenceLinkActions(referenceLink, source, target); + this.odataWriter.WriteEntityReferenceLink(referenceLink); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveParserToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveParserToken.cs new file mode 100644 index 0000000..9f947ab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveParserToken.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +/* DESIGN NOTES (pqian): + * a note on the primitive type parser/materializer on the client side + * Since the client type system allows multiple CLR types mapping to + * a single EDM type (i.e., String, Char, Char[], XDocument and XElement + * all maps to Edm.String). We cannot handle materialization based on the + * wire Edm type. The correct behavior would be to "tokenize" the wire data + * using the wire type, and them "materialize" the token using the CLR type + * declared on the entity type. However, there is a V1/V2 behavior where + * we DO NOT fail if the wire data type doesn't match the wire data (for example, + * you can have 3.2, as long as the CLR type + * can handle decimal numbers). Therefore, for all existing V1/V2 primitive types, + * the "tokenize" step should simply be storing the textual representation + * in an instance of TextPrimitiveParserToken, and use Parse() to materialize it. + * For new types, the TokenizeFromXml/TokenizeFromText on the PrimitiveTypeConverter + * should be overriden to give the correct behavior. + */ +namespace Microsoft.OData.Client +{ + using System; + + /// + /// A parser token + /// + internal abstract class PrimitiveParserToken + { + /// + /// Materialize this token using a PrimitiveTypeConverter + /// + /// The primitive type + /// A materialized instance + internal abstract object Materialize(Type clrType); + } + + /// + /// Textual based parser token + /// + internal class TextPrimitiveParserToken : PrimitiveParserToken + { + /// + /// Constructor + /// + /// Textual value + internal TextPrimitiveParserToken(string text) + { + this.Text = text; + } + + /// + /// The text property + /// + internal string Text + { + get; + private set; + } + + /// + /// Materialize by calling the Parse method on the converter + /// + /// clrType + /// A materialized instance + internal override object Materialize(Type clrType) + { + return ClientConvert.ChangeType(this.Text, clrType); + } + } + + /// + /// Instance based parser token, where the token is the materialized instance + /// + /// The instance type + internal class InstancePrimitiveParserToken : PrimitiveParserToken + { + /// + /// Constructor + /// + /// The instance + internal InstancePrimitiveParserToken(T instance) + { + this.Instance = instance; + } + + /// + /// The instance + /// + internal T Instance + { + get; + private set; + } + + /// + /// Materialize by returning the instance + /// + /// A primitive type converter + /// The instance + internal override object Materialize(Type clrType) + { + return this.Instance; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveType.cs new file mode 100644 index 0000000..420f1b3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveType.cs @@ -0,0 +1,548 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + using Microsoft.OData.Edm; + using Microsoft.Spatial; + + /// + /// Represent a Primitive Type on the client + /// + /// + /// 1) Performance + /// For performance reasons we use several dictionaries here: + /// - clrMapping contains well known primitive types. Initialized in the static constructor and never changed + /// after initialization. Therefore it is safe to read without locks. Also see comment about Binary type below + /// - derivedPrimitiveTypeMapping - a map for custom primitive types derived from well known primitive types - + /// especially spatial types. New items may be added at runtime so reads and writes must be locked + /// - knownNonPrimitiveTypes - a HashSet of types we have seen and determined they are not primitive. Used + /// to shortcircuit logic for finding derived primitive types for types we know are not primitive. + /// To get a primitive type one *MUST NOT* clrMapping since clrMapping will not contain custom primitive types + /// but call TryGetPrimitiveType method that knows how to handle multiple dictionaries. + /// 2) System.Data.Linq.Binary + /// We want to avoid static dependency on System.Data.Linq.dll. On the other hand System.Data.Linq.Binary is + /// a well known primitive type. For performance reasons and to avoid locking the clrMapping is only initialized + /// in the static ctor when we don't have System.Data.Linq.Binary type handy. Therefore we use the dummy BinaryTypeSub + /// type during initialization. As a result to get a well known primitive type one *MUST NOT* use the clrMapping + /// dictionary directly but call TryGetWellKnownPrimitiveType() method which knows how to handle BinaryType. + /// + internal sealed class PrimitiveType + { + /// + /// Clr Type - Primitive Type mapping for well known primitive types + /// + /// + /// It is being initialized in the static constructor and must not change + /// later. This way we can avoid locking it. + /// + private static readonly Dictionary clrMapping; + + /// + /// Clr Type - Primitive Type mapping for custom derived primitive type (e.g. spatial types) + /// + /// + /// This dictionary contains type mapping for custom derived primitive types (e.g. spatial) that + /// are types discovered at runtime and added as we go. Any access to this dictionary requires locking. + /// + private static readonly Dictionary derivedPrimitiveTypeMapping; + + /// + /// Edm Type - Primitive Type mapping + /// + private static readonly Dictionary edmMapping; + + /// + /// Cache containing known non-primitive types. Any access to this hashset requires locking. + /// + private static readonly HashSet knownNonPrimitiveTypes; + + /// + /// Static Constructor + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Required")] + static PrimitiveType() + { + clrMapping = new Dictionary(EqualityComparer.Default); + edmMapping = new Dictionary(StringComparer.Ordinal); + derivedPrimitiveTypeMapping = new Dictionary(EqualityComparer.Default); + knownNonPrimitiveTypes = new HashSet(EqualityComparer.Default); + + InitializeTypes(); + } + + /// + /// Constructor + /// + /// The Clr Type + /// The Edm Type Name + /// The Edm Primitive Type Kind + /// A PrimitiveXmlConverter that provides convertion between instances of this type to its Xml representation and back + /// Whether this primitive type can be mapped from the Edm type name + private PrimitiveType(Type clrType, string edmTypeName, EdmPrimitiveTypeKind primitiveKind, PrimitiveTypeConverter typeConverter, bool hasReverseMapping) + { + Debug.Assert(clrType != null, "clrType != null"); + Debug.Assert(primitiveKind != EdmPrimitiveTypeKind.None, "primitiveKind != EdmPrimitiveTypeKind.None"); + Debug.Assert(typeConverter != null, "typeConverter != null"); + + this.ClrType = clrType; + this.EdmTypeName = edmTypeName; + this.PrimitiveKind = primitiveKind; + this.TypeConverter = typeConverter; + this.HasReverseMapping = hasReverseMapping; + } + + /// + /// The Clr Type + /// + internal Type ClrType + { + get; + private set; + } + + /// + /// The Edm Type Name + /// + internal String EdmTypeName + { + get; + private set; + } + + /// + /// A PrimitiveXmlConverter that provides convertion between + /// instances of this type to its Xml representation and back + /// + internal PrimitiveTypeConverter TypeConverter + { + get; + private set; + } + + /// + /// This type has a reverse edm type mapping + /// + /// + /// Some known primitive types have shared edm type mapping + /// Only one of these shared type can contain a reverse mapping + /// + internal bool HasReverseMapping + { + get; + private set; + } + + /// + /// Gets the types EDM primitive type kind + /// + internal EdmPrimitiveTypeKind PrimitiveKind + { + get; + private set; + } + + /// + /// Try retrieve a primitive type metadata from a clr type + /// + /// The Clr Type + /// The returning primitive type + /// True if the type is found + /// + /// See remarks for the class. + /// + internal static bool TryGetPrimitiveType(Type clrType, out PrimitiveType ptype) + { + Type primitiveClrType = Nullable.GetUnderlyingType(clrType) ?? clrType; + + // Is it a well known primitive type? + if (!TryGetWellKnownPrimitiveType(primitiveClrType, out ptype)) + { + lock (knownNonPrimitiveTypes) + { + // Is it a type that we know that is not primitive? + if (knownNonPrimitiveTypes.Contains(clrType)) + { + ptype = null; + return false; + } + } + + KeyValuePair[] possibleMatches; + lock (derivedPrimitiveTypeMapping) + { + // is it a derived primitive type? + if (derivedPrimitiveTypeMapping.TryGetValue(clrType, out ptype)) + { + return true; + } + + // note that clrMapping contains a substitute type for Binary. We don't really care about this type since you cannot + // derive from System.Data.Linq.Binary type as it is sealed. Also to get all other applicable types we need to concatenate + // well known primitive types and custom primitive types. We can exclude primitive and sealed types as they cannot be + // derived from. + possibleMatches = clrMapping.Where(m => !m.Key.IsPrimitive() && !m.Key.IsSealed()).Concat(derivedPrimitiveTypeMapping).ToArray(); + } + + var bestMatch = new KeyValuePair(typeof(object), null); + foreach (var possibleMatch in possibleMatches) + { + // If the primitive type is a sub class of the one of the known primitive types + // then its a possible match + if (primitiveClrType.IsSubclassOf(possibleMatch.Key)) + { + // In order to find the most derived type for the best match, we need to check + // if the current match is a more derived type than the previous match + if (possibleMatch.Key.IsSubclassOf(bestMatch.Key)) + { + bestMatch = possibleMatch; + } + } + } + + if (bestMatch.Value == null) + { + // this is not a primitive type - update the hashset accordingly. + lock (knownNonPrimitiveTypes) + { + // Note that this is hashset so it is OK if we try adding the same type more than once. + knownNonPrimitiveTypes.Add(clrType); + } + + return false; + } + + ptype = bestMatch.Value; + lock (derivedPrimitiveTypeMapping) + { + // this is a derived primitive type - update the dictionary accordingly + derivedPrimitiveTypeMapping[primitiveClrType] = ptype; + } + } + + return true; + } + + /// + /// Try retrieve a primitive type metadata from a Edm Type Name + /// + /// Edm Type Name + /// The returning primitive type + /// True if the type is found + internal static bool TryGetPrimitiveType(String edmTypeName, out PrimitiveType ptype) + { + return edmMapping.TryGetValue(edmTypeName, out ptype); + } + + /// + /// Is this a known primitive type (including string,byte[],uri) + /// + /// type to analyze + /// true if known primitive type + internal static bool IsKnownType(Type type) + { + PrimitiveType primitiveType; + return TryGetPrimitiveType(type, out primitiveType); + } + + /// + /// Is this a known primitive type or a nullable based on a primitive type (including string,byte[],uri) + /// + /// type to analyze, possibly nullable + /// true if known primitive type or a nullable based on a primitive type + internal static bool IsKnownNullableType(Type type) + { + return IsKnownType(Nullable.GetUnderlyingType(type) ?? type); + } + + /// + /// Delete the type from known type table + /// + /// The clr type + /// The edm type name to remove, or null + /// This is a test clean up hook. MUST NOT BE CALLED FROM PRODUCT CODE. + internal static void DeleteKnownType(Type clrType, String edmTypeName) + { + // This is not thread safe. Product code should never modify clrMapping dictionary after initialization. + // This allows for reads without locks. + clrMapping.Remove(clrType); + + if (edmTypeName != null) + { + edmMapping.Remove(edmTypeName); + } + } + + /// + /// Register a known type as primitive type + /// + /// The Clr Type + /// The Edm Type Name + /// The Edm Primitive Type Kind + /// The Type Converter + /// Whether this mapping should have a reverse mapping from Edm + /// + /// This method is internal only for testing purposes. + /// IN PRODUCT MUST BE CALLED ONLY FROM THE STATIC CTOR OF THE PrimitiveType CLASS. + /// + internal static void RegisterKnownType(Type clrType, string edmTypeName, EdmPrimitiveTypeKind primitiveKind, PrimitiveTypeConverter converter, bool twoWay) + { + Debug.Assert(!clrMapping.ContainsKey(clrType), "Clr type already registered"); + Debug.Assert(clrType != null, "clrType != null"); + Debug.Assert(primitiveKind != EdmPrimitiveTypeKind.None, "primitiveKind != EdmPrimitiveTypeKind.None"); + Debug.Assert(converter != null, "converter != null"); + + PrimitiveType pt = new PrimitiveType(clrType, edmTypeName, primitiveKind, converter, twoWay); + clrMapping.Add(clrType, pt); + + if (twoWay) + { + Debug.Assert(!edmMapping.ContainsKey(edmTypeName), "Edm type name already registered"); + edmMapping.Add(edmTypeName, pt); + } + } + + /// + /// Creates a new instance of the corresponding IEdmPrimitiveType + /// + /// Returns a new instance of the corresponding IEdmPrimitiveType + internal IEdmPrimitiveType CreateEdmPrimitiveType() + { + // Note we always create a new instance of an IEdmPrimitiveType instead of returning the static instance by calling + // EdmCoreModel.Default.GetPrimitiveType(), for the following reasons: + // 1. We will annotate the IEdmPrimitiveType with a ClientTypeAnnotation, which has a MaxProtocolVersion property. We can't map + // an instance of IEdmPrimitiveType to a CLR type, instead it is mapped to the (CLR type, MaxProtocolVersion) pair. + // 2. We do have multiple CLR primitive types mapped to a single EDM primitive type (take Edm.String for example). In this case + // we need to create multiple instances of the Edm.String type and annotate them with the corresponding CLR type. + return ClientEdmPrimitiveType.CreateType(this.PrimitiveKind); + } + + /// + /// Populate the mapping table + /// + /// + /// MUST NOT BE CALLED FROM PRODUCT CODE OTHER THAN STATIC CTOR OF PrimitiveType class. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Coupling necessary for type table")] + private static void InitializeTypes() + { + // Following are types that are mapped directly to Edm type + RegisterKnownType(typeof(Boolean), XmlConstants.EdmBooleanTypeName, EdmPrimitiveTypeKind.Boolean, new BooleanTypeConverter(), true); + RegisterKnownType(typeof(Byte), XmlConstants.EdmByteTypeName, EdmPrimitiveTypeKind.Byte, new ByteTypeConverter(), true); + RegisterKnownType(typeof(Byte[]), XmlConstants.EdmBinaryTypeName, EdmPrimitiveTypeKind.Binary, new ByteArrayTypeConverter(), true); + RegisterKnownType(typeof(DateTimeOffset), XmlConstants.EdmDateTimeOffsetTypeName, EdmPrimitiveTypeKind.DateTimeOffset, new DateTimeOffsetTypeConverter(), true); + RegisterKnownType(typeof(Decimal), XmlConstants.EdmDecimalTypeName, EdmPrimitiveTypeKind.Decimal, new DecimalTypeConverter(), true); + RegisterKnownType(typeof(Double), XmlConstants.EdmDoubleTypeName, EdmPrimitiveTypeKind.Double, new DoubleTypeConverter(), true); + RegisterKnownType(typeof(Guid), XmlConstants.EdmGuidTypeName, EdmPrimitiveTypeKind.Guid, new GuidTypeConverter(), true); + RegisterKnownType(typeof(Int16), XmlConstants.EdmInt16TypeName, EdmPrimitiveTypeKind.Int16, new Int16TypeConverter(), true); + RegisterKnownType(typeof(Int32), XmlConstants.EdmInt32TypeName, EdmPrimitiveTypeKind.Int32, new Int32TypeConverter(), true); + RegisterKnownType(typeof(Int64), XmlConstants.EdmInt64TypeName, EdmPrimitiveTypeKind.Int64, new Int64TypeConverter(), true); + RegisterKnownType(typeof(Single), XmlConstants.EdmSingleTypeName, EdmPrimitiveTypeKind.Single, new SingleTypeConverter(), true); + RegisterKnownType(typeof(String), XmlConstants.EdmStringTypeName, EdmPrimitiveTypeKind.String, new StringTypeConverter(), true); + RegisterKnownType(typeof(SByte), XmlConstants.EdmSByteTypeName, EdmPrimitiveTypeKind.SByte, new SByteTypeConverter(), true); + RegisterKnownType(typeof(TimeSpan), XmlConstants.EdmDurationTypeName, EdmPrimitiveTypeKind.Duration, new TimeSpanTypeConverter(), true); + RegisterKnownType(typeof(Geography), XmlConstants.EdmGeographyTypeName, EdmPrimitiveTypeKind.Geography, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(GeographyPoint), XmlConstants.EdmPointTypeName, EdmPrimitiveTypeKind.GeographyPoint, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(GeographyLineString), XmlConstants.EdmLineStringTypeName, EdmPrimitiveTypeKind.GeographyLineString, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(GeographyPolygon), XmlConstants.EdmPolygonTypeName, EdmPrimitiveTypeKind.GeographyPolygon, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(GeographyCollection), XmlConstants.EdmGeographyCollectionTypeName, EdmPrimitiveTypeKind.GeographyCollection, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(GeographyMultiPoint), XmlConstants.EdmMultiPointTypeName, EdmPrimitiveTypeKind.GeographyMultiPoint, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(GeographyMultiLineString), XmlConstants.EdmMultiLineStringTypeName, EdmPrimitiveTypeKind.GeographyMultiLineString, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(GeographyMultiPolygon), XmlConstants.EdmMultiPolygonTypeName, EdmPrimitiveTypeKind.GeographyMultiPolygon, new GeographyTypeConverter(), true); + RegisterKnownType(typeof(Geometry), XmlConstants.EdmGeometryTypeName, EdmPrimitiveTypeKind.Geometry, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(GeometryPoint), XmlConstants.EdmGeometryPointTypeName, EdmPrimitiveTypeKind.GeometryPoint, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(GeometryLineString), XmlConstants.EdmGeometryLineStringTypeName, EdmPrimitiveTypeKind.GeometryLineString, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(GeometryPolygon), XmlConstants.EdmGeometryPolygonTypeName, EdmPrimitiveTypeKind.GeometryPolygon, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(GeometryCollection), XmlConstants.EdmGeometryCollectionTypeName, EdmPrimitiveTypeKind.GeometryCollection, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(GeometryMultiPoint), XmlConstants.EdmGeometryMultiPointTypeName, EdmPrimitiveTypeKind.GeometryMultiPoint, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(GeometryMultiLineString), XmlConstants.EdmGeometryMultiLineStringTypeName, EdmPrimitiveTypeKind.GeometryMultiLineString, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(GeometryMultiPolygon), XmlConstants.EdmGeometryMultiPolygonTypeName, EdmPrimitiveTypeKind.GeometryMultiPolygon, new GeometryTypeConverter(), true); + RegisterKnownType(typeof(DataServiceStreamLink), XmlConstants.EdmStreamTypeName, EdmPrimitiveTypeKind.Stream, new NamedStreamTypeConverter(), false); + RegisterKnownType(typeof(Date), XmlConstants.EdmDateTypeName, EdmPrimitiveTypeKind.Date, new DateTypeConverter(), true); + RegisterKnownType(typeof(TimeOfDay), XmlConstants.EdmTimeOfDayTypeName, EdmPrimitiveTypeKind.TimeOfDay, new TimeOfDayConvert(), true); + + // Following are known types are mapped to existing Edm type + RegisterKnownType(typeof(Char), XmlConstants.EdmStringTypeName, EdmPrimitiveTypeKind.String, new CharTypeConverter(), false); + RegisterKnownType(typeof(Char[]), XmlConstants.EdmStringTypeName, EdmPrimitiveTypeKind.String, new CharArrayTypeConverter(), false); + RegisterKnownType(typeof(Type), XmlConstants.EdmStringTypeName, EdmPrimitiveTypeKind.String, new ClrTypeConverter(), false); + RegisterKnownType(typeof(Uri), XmlConstants.EdmStringTypeName, EdmPrimitiveTypeKind.String, new UriTypeConverter(), false); + RegisterKnownType(typeof(System.Xml.Linq.XDocument), XmlConstants.EdmStringTypeName, EdmPrimitiveTypeKind.String, new XDocumentTypeConverter(), false); + RegisterKnownType(typeof(System.Xml.Linq.XElement), XmlConstants.EdmStringTypeName, EdmPrimitiveTypeKind.String, new XElementTypeConverter(), false); + + // Following are known types that are not mapped to Edm + RegisterKnownType(typeof(UInt16), null, EdmPrimitiveTypeKind.String, new UInt16TypeConverter(), false); + RegisterKnownType(typeof(UInt32), null, EdmPrimitiveTypeKind.String, new UInt32TypeConverter(), false); + RegisterKnownType(typeof(UInt64), null, EdmPrimitiveTypeKind.String, new UInt64TypeConverter(), false); + RegisterKnownType(typeof(DateTime), null, EdmPrimitiveTypeKind.DateTimeOffset, new DateTimeTypeConverter(), false); + +#if !PORTABLELIB + // There is no static dependency on System.Data.Linq so we use a substitute type for the Binary type + RegisterKnownType(typeof(BinaryTypeSub), XmlConstants.EdmBinaryTypeName, EdmPrimitiveTypeKind.Binary, new BinaryTypeConverter(), false); +#endif + } + + /// + /// Tries to get a well known PrimitiveType for a clr type. Contains logic to handle Binary type. + /// + /// The clr type to get well known PrimitiveType for. + /// PrimitiveType for the if exists. Otherwise null. + /// true if a PrimitiveType for the was found. Otherwise false. + private static bool TryGetWellKnownPrimitiveType(Type clrType, out PrimitiveType ptype) + { + Debug.Assert(clrType != null, "clrType != null"); + + ptype = null; + if (!clrMapping.TryGetValue(clrType, out ptype)) + { +#if !PORTABLELIB + if (IsBinaryType(clrType)) + { + Debug.Assert(clrMapping.ContainsKey(typeof(BinaryTypeSub)), "BinaryTypeSub missing from the dictionary"); + ptype = clrMapping[typeof(BinaryTypeSub)]; + } +#endif + } + + return ptype != null; + } + +#if !PORTABLELIB// System.Data.Linq not available + /// + /// Whether the is System.Data.Linq.Binary. + /// + /// Type to check. + /// true if is System.Data.Linq.Binary. Otherwise false. + private static bool IsBinaryType(Type type) + { + Debug.Assert(type != null, "clrType != null"); + + if (BinaryTypeConverter.BinaryType == null && type.Name == "Binary") + { + if ((type.Namespace == "System.Data.Linq") && + (System.Reflection.AssemblyName.ReferenceMatchesDefinition( + type.Assembly.GetName(), new System.Reflection.AssemblyName("System.Data.Linq")))) + { + BinaryTypeConverter.BinaryType = type; + } + } + + return type == BinaryTypeConverter.BinaryType; + } + + /// + /// There is no static dependency on System.Data.Linq where Binary type lives. We + /// will use this type to Substitute for the missing Binary type. + /// + private sealed class BinaryTypeSub + { + } +#endif + + /// + /// Represents a definition of an EDM primitive type. + /// + private class ClientEdmPrimitiveType : EdmType, IEdmPrimitiveType, IEdmFullNamedElement + { + /// + /// Namespace of the type. + /// + private readonly string namespaceName; + + /// + /// Name of the type. + /// + private readonly string name; + + /// + /// Full name of the type; + /// + private readonly string fullName; + + /// + /// The kind of primitive. + /// + private readonly EdmPrimitiveTypeKind primitiveKind; + + /// + /// Creates an instance of the client EDM primitive type. + /// + /// Namespace of the type. + /// Name of the type. + /// Kind fo the primitive type. + private ClientEdmPrimitiveType(string namespaceName, string name, EdmPrimitiveTypeKind primitiveKind) + { + this.namespaceName = namespaceName; + this.name = name; + this.primitiveKind = primitiveKind; + this.fullName = this.namespaceName + "." + this.name; + } + + /// + /// Name of the type. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Namespace of the type. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Full name of the type + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Kind of the primitive type. + /// + public EdmPrimitiveTypeKind PrimitiveKind + { + get { return this.primitiveKind; } + } + + /// + /// The kind of this schema element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Primitive; } + } + + /// + /// Creates a new instance of the IEdmPrimitiveType + /// + /// Kind of primitive type. + /// Returns a new instance of the IEdmPrimitiveType + public static IEdmPrimitiveType CreateType(EdmPrimitiveTypeKind primitiveKind) + { + Debug.Assert(primitiveKind != EdmPrimitiveTypeKind.None, "primitiveKiind != EdmPrimitiveTypeKind.None"); + return new ClientEdmPrimitiveType(XmlConstants.EdmNamespace, primitiveKind.ToString(), primitiveKind); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveXmlConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveXmlConverter.cs new file mode 100644 index 0000000..62dcb63 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/PrimitiveXmlConverter.cs @@ -0,0 +1,888 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +/* DESIGN NOTES (pqian): + * a note on the primitive type parser/materializer on the client side + * Since the client type system allows multiple CLR types mapping to + * a single EDM type (i.e., String, Char, Char[], XDocument and XElement + * all maps to Edm.String). We cannot handle materialization based on the + * wire Edm type. The correct behavior would be to "tokenize" the wire data + * using the wire type, and them "materialize" the token using the CLR type + * declared on the entity type. However, there is a V1/V2 behavior where + * we DO NOT fail if the wire data type doesn't match the wire data (for example, + * you can have 3.2, as long as the CLR type + * can handle decimal numbers). Therefore, for all existing V1/V2 primitive types, + * the "tokenize" step should simply be storing the textual representation + * in an instance of TextPrimitiveParserToken, and use Parse() to materialize it. + * For new types, the TokenizeFromXml/TokenizeFromText on the PrimitiveTypeConverter + * should be overriden to give the correct behavior. + */ +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + using System.Reflection; + using System.Xml; + using Microsoft.OData.Edm; + using Microsoft.Spatial; + + /// + /// Convert between primitive types to string and xml representation + /// + internal class PrimitiveTypeConverter + { + /// + /// Constructor + /// + protected PrimitiveTypeConverter() + { + } + + /// + /// Create a parser token from xml feed + /// + /// The xml reader + /// The reader is expected to be placed at the beginning of the element, and after this method call, the reader will be placed + /// at the EndElement, such that the next Element will be read in the next Read call. + /// token + internal virtual PrimitiveParserToken TokenizeFromXml(XmlReader reader) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element, "Reader at element"); + string elementString = MaterializeAtom.ReadElementString(reader, true); + if (elementString != null) + { + return new TextPrimitiveParserToken(elementString); + } + + return null; + } + + /// + /// Create a parser token from text representation ($value end points) + /// + /// The text form reprensentation + /// token + internal virtual PrimitiveParserToken TokenizeFromText(String text) + { + return new TextPrimitiveParserToken(text); + } + + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal virtual object Parse(String text) + { + return text; + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal virtual String ToString(object instance) + { + throw new NotImplementedException(); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class BooleanTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToBoolean(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Boolean)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class ByteTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToByte(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Byte)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class ByteArrayTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return Convert.FromBase64String(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return Convert.ToBase64String((byte[])instance); + } + } + +#if !PORTABLELIB + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class BinaryTypeConverter : PrimitiveTypeConverter + { + /// MethodInfo for the ToArray method on the Binary type. + private MethodInfo convertToByteArrayMethodInfo; + + /// + /// The delay loaded binary type + /// + internal static Type BinaryType + { + get; + set; + } + + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return Activator.CreateInstance(BinaryType, Convert.FromBase64String(text)); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return instance.ToString(); + } + + /// + /// Converts the System.Data.Linq.Binary to byte[] by calling the ToArray method on the Binary type. + /// + /// Instance of Binary type. + /// Byte[] instance containing the value of the Binary type. + internal byte[] ToArray(object instance) + { + if (this.convertToByteArrayMethodInfo == null) + { + this.convertToByteArrayMethodInfo = instance.GetType().GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance); + } + + return (byte[])this.convertToByteArrayMethodInfo.Invoke(instance, null); + } + } +#endif + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class DecimalTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToDecimal(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Decimal)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class DoubleTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToDouble(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Double)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class GuidTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return new Guid(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return instance.ToString(); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class Int16TypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToInt16(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Int16)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class Int32TypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToInt32(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Int32)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class Int64TypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToInt64(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Int64)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class SingleTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToSingle(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Single)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class StringTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return text; + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return (String)instance; + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class SByteTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToSByte(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((SByte)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class CharTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToChar(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((Char)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class CharArrayTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return text.ToCharArray(); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return new String((char[])instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class ClrTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return PlatformHelper.GetTypeOrThrow(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return ((Type)instance).AssemblyQualifiedName; + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class UriTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return UriUtil.CreateUri(text, UriKind.RelativeOrAbsolute); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return UriUtil.UriToString((Uri)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class XDocumentTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return text.Length > 0 ? System.Xml.Linq.XDocument.Parse(text) : new System.Xml.Linq.XDocument(); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return instance.ToString(); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class XElementTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return System.Xml.Linq.XElement.Parse(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return instance.ToString(); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class DateTimeOffsetTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return PlatformHelper.ConvertStringToDateTimeOffset(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((DateTimeOffset)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class DateTimeTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return PlatformHelper.ConvertStringToDateTime(text); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class TimeSpanTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return EdmValueParser.ParseDuration(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return EdmValueWriter.DurationAsXml((TimeSpan)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class UInt16TypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToUInt16(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((UInt16)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class UInt32TypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToUInt32(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((UInt32)instance); + } + } + + /// + /// Convert between primitive types to string and xml representation + /// + internal sealed class UInt64TypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + return XmlConvert.ToUInt64(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return XmlConvert.ToString((UInt64)instance); + } + } + + /// + /// Convert between an instance of geography and its Xml representation + /// + internal sealed class GeographyTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from the xml reader + /// + /// The xml reader + /// The reader is expected to be placed at the beginning of the element, and after this method call, the reader will be placed + /// at the EndElement, such that the next Element will be read in the next Read call. + /// An instance of primitive type + internal override PrimitiveParserToken TokenizeFromXml(XmlReader reader) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element, "reader at element"); + reader.ReadStartElement(); // + var g = new InstancePrimitiveParserToken(GmlFormatter.Create().Read(reader)); + Debug.Assert(reader.NodeType == XmlNodeType.EndElement, "reader at end of current element"); + + return g; + } + } + + /// + /// Convert between an instance of geometry and its Xml representation + /// + internal sealed class GeometryTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from the xml reader + /// + /// The xml reader + /// The reader is expected to be placed at the beginning of the element, and after this method call, the reader will be placed + /// at the EndElement, such that the next Element will be read in the next Read call. + /// An instance of primitive type + internal override PrimitiveParserToken TokenizeFromXml(XmlReader reader) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element, "reader at element"); + reader.ReadStartElement(); // + var g = new InstancePrimitiveParserToken(GmlFormatter.Create().Read(reader)); + Debug.Assert(reader.NodeType == XmlNodeType.EndElement, "reader at end of current element"); + + return g; + } + } + + /// + /// Convert between an instance of DataServiceStreamLink and its xml representation. + /// There is never a scenario in client which requires to do this, but since a converter + /// is required, adding one which does nothing for namedstream. + /// + internal sealed class NamedStreamTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(String text) + { + throw new NotImplementedException(); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override String ToString(object instance) + { + throw new NotImplementedException(); + } + } + + /// + /// Convert between primitive types Edm.Date and string + /// + internal sealed class DateTypeConverter : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(string text) + { + return PlatformHelper.ConvertStringToDate(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return ((Date)instance).ToString(); + } + } + + /// + /// Convert between primitive types Edm.TimeOfDay and string + /// + internal sealed class TimeOfDayConvert : PrimitiveTypeConverter + { + /// + /// Create an instance of primitive type from a string representation + /// + /// The string representation + /// An instance of primitive type + internal override object Parse(string text) + { + return PlatformHelper.ConvertStringToTimeOfDay(text); + } + + /// + /// Convert an instance of primitive type to string + /// + /// The instance + /// The string representation of the instance + internal override string ToString(object instance) + { + return ((TimeOfDay)instance).ToString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/Serializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/Serializer.cs new file mode 100644 index 0000000..6b56afa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Serialization/Serializer.cs @@ -0,0 +1,964 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + #region Namespaces + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Xml; + using System.Xml.Linq; + using Microsoft.OData; + using Microsoft.OData.UriParser; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Serializes the request data into the given format using the given message writer. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class needs refactoring.")] + public class Serializer + { + /// where to pull the changes from + private readonly RequestInfo requestInfo; + + /// The property converter to use for creating ODataProperty instances. + private readonly ODataPropertyConverter propertyConverter; + + /// The save changes option. + private readonly SaveChangesOptions options; + + /// The option to send entity parameters. + private readonly EntityParameterSendOption sendOption; + + /// + /// Creates a new instance of the Serializer. + /// + /// the request info. + internal Serializer(RequestInfo requestInfo) + { + Debug.Assert(requestInfo != null, "requestInfo != null"); + this.requestInfo = requestInfo; + this.propertyConverter = new ODataPropertyConverter(this.requestInfo); + } + + /// + /// Creates a new instance of the Serializer. + /// + /// the request info. + /// The option to send entity operation parameters. + internal Serializer(RequestInfo requestInfo, EntityParameterSendOption sendOption) + : this(requestInfo) + { + this.sendOption = sendOption; + } + + /// + /// Creates a new instance of the Serializer. + /// + /// the request info. + /// the save change options. + internal Serializer(RequestInfo requestInfo, SaveChangesOptions options) + : this(requestInfo) + { + this.options = options; + } + + /// + /// Gets the string of keys used in URI. + /// + /// Wrapping context instance. + /// The dictionary containing key pairs. + /// The string of keys. + public static string GetKeyString(DataServiceContext context, Dictionary keys) + { + var requestInfo = new RequestInfo(context); + var serializer = new Serializer(requestInfo); + if (keys.Count == 1) + { + return serializer.ConvertToEscapedUriValue(keys.First().Key, keys.First().Value); + } + else + { + StringBuilder stringBuilder = new StringBuilder(); + foreach (var keyPair in keys) + { + stringBuilder.Append(keyPair.Key); + stringBuilder.Append(UriHelper.EQUALSSIGN); + stringBuilder.Append(serializer.ConvertToEscapedUriValue(keyPair.Key, keyPair.Value)); + stringBuilder.Append(UriHelper.COMMA); + } + + stringBuilder.Remove(stringBuilder.Length - 1, 1); + return stringBuilder.ToString(); + } + } + + /// + /// Gets the string of parameters used in URI. + /// + /// Wrapping context instance. + /// Parameters of function. + /// The string of parameters. + public static string GetParameterString(DataServiceContext context, params OperationParameter[] parameters) + { + var requestInfo = new RequestInfo(context); + var serializer = new Serializer(requestInfo); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append(UriHelper.LEFTPAREN); + foreach (var key in parameters) + { + stringBuilder.Append(key.Name); + stringBuilder.Append(UriHelper.EQUALSSIGN); + stringBuilder.Append(serializer.ConvertToEscapedUriValue(key.Name, key.Value)); + stringBuilder.Append(UriHelper.COMMA); + } + + if (parameters.Any()) + { + stringBuilder.Remove(stringBuilder.Length - 1, 1); + } + + stringBuilder.Append(UriHelper.RIGHTPAREN); + return stringBuilder.ToString(); + } + + /// + /// Seriliaze the parameter value to string. + /// + /// The context. + /// The parameter. + /// Parameter value string. + internal static string GetParameterValue(DataServiceContext context, OperationParameter parameter) + { + var requestInfo = new RequestInfo(context); + var serializer = new Serializer(requestInfo); + UriEntityOperationParameter entityParameter = parameter as UriEntityOperationParameter; + return serializer.ConvertToEscapedUriValue(parameter.Name, parameter.Value, entityParameter != null && entityParameter.UseEntityReference); + } + + /// + /// Creates an instance of ODataMessageWriter. + /// + /// Instance of IODataRequestMessage. + /// RequestInfo containing information about the client settings. + /// true if the writer is intended to for a parameter payload, false otherwise. + /// An instance of ODataMessageWriter. + internal static ODataMessageWriter CreateMessageWriter(ODataRequestMessageWrapper requestMessage, RequestInfo requestInfo, bool isParameterPayload) + { + var writerSettings = requestInfo.WriteHelper.CreateSettings(requestMessage.IsBatchPartRequest, requestInfo.Context.EnableWritingODataAnnotationWithoutPrefix); + return requestMessage.CreateWriter(writerSettings, isParameterPayload); + } + + /// + /// Creates an ODataResource for the given EntityDescriptor and fills in its ODataLib metadata. + /// + /// The entity descriptor. + /// Name of the server type. + /// The client-side entity type. + /// The current client format. + /// An odata entry with its metadata filled in. + internal static ODataResource CreateODataEntry(EntityDescriptor entityDescriptor, string serverTypeName, ClientTypeAnnotation entityType, DataServiceClientFormat clientFormat) + { + ODataResource entry = new ODataResource(); + + // If the client type name is different from the server type name, then add SerializationTypeNameAnnotation + // which tells ODataLib to write the type name in the annotation in the payload. + if (entityType.ElementTypeName != serverTypeName) + { + entry.TypeAnnotation = new ODataTypeAnnotation(serverTypeName); + } + + // We always need to write the client type name, since this is the type name used by ODataLib + // to resolve the entity type using EdmModel.FindSchemaElement. + entry.TypeName = entityType.ElementTypeName; + + if (entityDescriptor.IsMediaLinkEntry || entityType.IsMediaLinkEntry) + { + // Since we are already enabled EnableWcfDataServicesClientBehavior in the writer settings, + // setting the MediaResource value will tell ODataLib to write MLE payload, irrespective of + // what the metadata says. + entry.MediaResource = new ODataStreamReferenceValue(); + } + + return entry; + } + + /// + /// Writes the body operation parameters associated with a ServiceAction. For each BodyOperationParameter: + /// 1. calls ODataPropertyConverter to convert CLR object into ODataValue/primitive values. + /// 2. then calls ODataParameterWriter to write the ODataValue/primitive values. + /// + /// The list of operation parameters to write. + /// The OData request message used to write the operation parameters. + internal void WriteBodyOperationParameters(List operationParameters, ODataRequestMessageWrapper requestMessage) + { + Debug.Assert(requestMessage != null, "requestMessage != null"); + Debug.Assert(operationParameters != null, "operationParameters != null"); + Debug.Assert(operationParameters.Any(), "operationParameters.Any()"); + + using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(requestMessage, this.requestInfo, true /*isParameterPayload*/)) + { + ODataParameterWriter parameterWriter = messageWriter.CreateODataParameterWriter(null); + parameterWriter.WriteStart(); + + foreach (BodyOperationParameter operationParameter in operationParameters) + { + if (operationParameter.Value == null) + { + parameterWriter.WriteValue(operationParameter.Name, operationParameter.Value); + } + else + { + ClientEdmModel model = this.requestInfo.Model; + IEdmType edmType = model.GetOrCreateEdmType(operationParameter.Value.GetType()); + Debug.Assert(edmType != null, "edmType != null"); + + switch (edmType.TypeKind) + { + case EdmTypeKind.Collection: + { + this.WriteCollectionValueInBodyOperationParameter(parameterWriter, operationParameter, (IEdmCollectionType)edmType); + break; + } + + case EdmTypeKind.Complex: + case EdmTypeKind.Entity: + { + Debug.Assert(model.GetClientTypeAnnotation(edmType).ElementType != null, "model.GetClientTypeAnnotation(edmType).ElementType != null"); + ODataResourceWrapper entry = this.CreateODataResourceFromEntityOperationParameter(model.GetClientTypeAnnotation(edmType), operationParameter.Value); + Debug.Assert(entry != null, "entry != null"); + var entryWriter = parameterWriter.CreateResourceWriter(operationParameter.Name); + ODataWriterHelper.WriteResource(entryWriter, entry); + break; + } + + case EdmTypeKind.Primitive: + object primitiveValue = ODataPropertyConverter.ConvertPrimitiveValueToRecognizedODataType(operationParameter.Value, operationParameter.Value.GetType()); + parameterWriter.WriteValue(operationParameter.Name, primitiveValue); + break; + + case EdmTypeKind.Enum: + ODataEnumValue tmp = this.propertyConverter.CreateODataEnumValue( + model.GetClientTypeAnnotation(edmType).ElementType, + operationParameter.Value, + false); + parameterWriter.WriteValue(operationParameter.Name, tmp); + + break; + default: + // EdmTypeKind.Row + // EdmTypeKind.EntityReference + throw new NotSupportedException(Strings.Serializer_InvalidParameterType(operationParameter.Name, edmType.TypeKind)); + } + } // else + } // foreach + + parameterWriter.WriteEnd(); + parameterWriter.Flush(); + } + } + + /// + /// Write the entry element. + /// + /// The entity. + /// Collection of links related to the entity. + /// The OData request message. + internal void WriteEntry(EntityDescriptor entityDescriptor, IEnumerable relatedLinks, ODataRequestMessageWrapper requestMessage) + { + ClientEdmModel model = this.requestInfo.Model; + ClientTypeAnnotation entityType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); + using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(requestMessage, this.requestInfo, false /*isParameterPayload*/)) + { + ODataWriterWrapper entryWriter = ODataWriterWrapper.CreateForEntry(messageWriter, this.requestInfo.Configurations.RequestPipeline); + + // Get the server type name using the type resolver or from the entity descriptor + string serverTypeName = this.requestInfo.GetServerTypeName(entityDescriptor); + + var entry = CreateODataEntry(entityDescriptor, serverTypeName, entityType, this.requestInfo.Format); + if (serverTypeName == null) + { + serverTypeName = this.requestInfo.InferServerTypeNameFromServerModel(entityDescriptor); + } + + IEnumerable properties; + if ((!Util.IsFlagSet(this.options, SaveChangesOptions.ReplaceOnUpdate) && + entityDescriptor.State == EntityStates.Modified && + entityDescriptor.PropertiesToSerialize.Any()) || + (Util.IsFlagSet(this.options, SaveChangesOptions.PostOnlySetProperties) && + entityDescriptor.State == EntityStates.Added)) + { + properties = entityType.PropertiesToSerialize().Where(prop => entityDescriptor.PropertiesToSerialize.Contains(prop.PropertyName)); + } + else + { + properties = entityType.PropertiesToSerialize(); + } + + entry.Properties = this.propertyConverter.PopulateProperties(entityDescriptor.Entity, serverTypeName, properties); + + entryWriter.WriteStart(entry, entityDescriptor.Entity); + + this.WriteNestedComplexProperties(entityDescriptor.Entity, serverTypeName, properties, entryWriter); + + if (EntityStates.Added == entityDescriptor.State) + { + this.WriteNestedResourceInfo(entityDescriptor, relatedLinks, entryWriter); + } + + entryWriter.WriteEnd(entry, entityDescriptor.Entity); + } + } + + /// + /// Write the instances for the given set of OData nested resource. + /// + /// Instance of the resource which is getting serialized. + /// The server type name of the entity whose properties are being populated. + /// The properties to write. + /// The writer used write the properties. + internal void WriteNestedComplexProperties(object entity, string serverTypeName, IEnumerable properties, ODataWriterWrapper odataWriter) + { + Debug.Assert(properties != null, "properties != null"); + var populatedProperties = properties.Where(p => p.IsComplex || p.IsComplexCollection); + + var nestedComplexProperties = this.propertyConverter.PopulateNestedComplexProperties(entity, serverTypeName, populatedProperties, null); + foreach (var property in nestedComplexProperties) + { + WriteNestedResourceInfo(odataWriter, property); + } + } + + /// + /// Writes a navigation link. + /// + /// The entity + /// The links related to the entity + /// The ODataWriter used to write the navigation link. + internal void WriteNestedResourceInfo(EntityDescriptor entityDescriptor, IEnumerable relatedLinks, ODataWriterWrapper odataWriter) + { + // TODO: create instance of odatawriter. + // TODO: send clientType once, so that we dont need entity descriptor + Debug.Assert(EntityStates.Added == entityDescriptor.State, "entity not added state"); + + Dictionary> groupRelatedLinks = new Dictionary>(EqualityComparer.Default); + foreach (LinkDescriptor end in relatedLinks) + { + List linkDescriptorsList = null; + if (!groupRelatedLinks.TryGetValue(end.SourceProperty, out linkDescriptorsList)) + { + linkDescriptorsList = new List(); + groupRelatedLinks.Add(end.SourceProperty, linkDescriptorsList); + } + + linkDescriptorsList.Add(end); + } + + ClientTypeAnnotation clientType = null; + foreach (var grlinks in groupRelatedLinks) + { + if (null == clientType) + { + ClientEdmModel model = this.requestInfo.Model; + clientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); + } + + bool isCollection = clientType.GetProperty(grlinks.Key, UndeclaredPropertyBehavior.ThrowException).IsEntityCollection; + bool started = false; + + foreach (LinkDescriptor end in grlinks.Value) + { + Debug.Assert(!end.ContentGeneratedForSave, "already saved link"); + end.ContentGeneratedForSave = true; + Debug.Assert(null != end.Target, "null is DELETE"); + + ODataNestedResourceInfo navigationLink = new ODataNestedResourceInfo(); + navigationLink.Url = this.requestInfo.EntityTracker.GetEntityDescriptor(end.Target).GetLatestEditLink(); + Debug.Assert(Uri.IsWellFormedUriString(UriUtil.UriToString(navigationLink.Url), UriKind.Absolute), "Uri.IsWellFormedUriString(targetEditLink, UriKind.Absolute)"); + + navigationLink.IsCollection = isCollection; + navigationLink.Name = grlinks.Key; + + if (!started) + { + odataWriter.WriteNestedResourceInfoStart(navigationLink); + started = true; + } + + odataWriter.WriteNestedResourceInfoStart(navigationLink, end.Source, end.Target); + odataWriter.WriteEntityReferenceLink(new ODataEntityReferenceLink() { Url = navigationLink.Url }, end.Source, end.Target); + odataWriter.WriteNestedResourceInfoEnd(navigationLink, end.Source, end.Target); + } + + odataWriter.WriteNestedResourceInfoEnd(); + } + } + +#if DEBUG + /// + /// Writes an entity reference link. + /// + /// The link descriptor. + /// The request message used for writing the payload. + /// True if batch, false otherwise. + internal void WriteEntityReferenceLink(LinkDescriptor binding, ODataRequestMessageWrapper requestMessage, bool isBatch) +#else + /// + /// Writes an entity reference link. + /// + /// The link descriptor. + /// The request message used for writing the payload. + internal void WriteEntityReferenceLink(LinkDescriptor binding, ODataRequestMessageWrapper requestMessage) +#endif + { + using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(requestMessage, this.requestInfo, false /*isParameterPayload*/)) + { + EntityDescriptor targetResource = this.requestInfo.EntityTracker.GetEntityDescriptor(binding.Target); + + Uri targetReferenceLink = targetResource.GetLatestIdentity(); + + if (targetReferenceLink == null) + { +#if DEBUG + Debug.Assert(isBatch, "we should be cross-referencing entities only in batch scenarios"); +#endif + targetReferenceLink = UriUtil.CreateUri("$" + targetResource.ChangeOrder.ToString(CultureInfo.InvariantCulture), UriKind.Relative); + } + + ODataEntityReferenceLink referenceLink = new ODataEntityReferenceLink(); + referenceLink.Url = targetReferenceLink; + messageWriter.WriteEntityReferenceLink(referenceLink); + } + } + + /// + /// Enumerates through the list of URI operation parameters and creates a new Uri with the uri operation parameters written as query string of the new Uri. + /// + /// The Uri used to construct the new Uri. + /// The non-empty list of uri parameters which will be converted to query string. + /// Uri containing the uri parameters as query string. + internal Uri WriteUriOperationParametersToUri(Uri requestUri, List operationParameters) + { + Debug.Assert(operationParameters != null && operationParameters.Any(), "OperationParameters was null or empty"); + Debug.Assert(requestUri != null, "request_uri != null"); + + UriBuilder uriBuilder = new UriBuilder(requestUri); + StringBuilder pathBuilder = new StringBuilder(); + pathBuilder.Append(uriBuilder.Path); + string lastSeg = uriBuilder.Path.Substring(uriBuilder.Path.LastIndexOf('/') + 1); + + StringBuilder queryBuilder = new StringBuilder(); + String uriString = UriUtil.UriToString(uriBuilder.Uri); + + if (!string.IsNullOrEmpty(uriBuilder.Query)) + { + Debug.Assert(uriBuilder.Query[0] == UriHelper.QUESTIONMARK, "uriBuilder.Query[0] == UriHelper.QUESTIONMARK"); + + // Don't append the '?', as later when we call setter on the Query, the '?' will be automatically added. + queryBuilder.Append(uriBuilder.Query.Substring(1)); + queryBuilder.Append(UriHelper.AMPERSAND); + } + + if (!lastSeg.Contains(Char.ToString(UriHelper.ATSIGN))) + { + pathBuilder.Append(UriHelper.LEFTPAREN); + } + else + { + if (pathBuilder.ToString().EndsWith(Char.ToString(UriHelper.RIGHTPAREN), StringComparison.OrdinalIgnoreCase)) + { + pathBuilder.Remove(pathBuilder.Length - 1, 1); + pathBuilder.Append(UriHelper.COMMA); + } + } + + foreach (UriOperationParameter op in operationParameters) + { + Debug.Assert(op != null, "op != null"); + Debug.Assert(!string.IsNullOrEmpty(op.Name), "!string.IsNullOrEmpty(op.ParameterName)"); + + string paramName = op.Name.Trim(); + + // if the parameter name is an alias, make sure that the URI contains it. + if (paramName.StartsWith(Char.ToString(UriHelper.ATSIGN), StringComparison.OrdinalIgnoreCase) && !uriString.Contains(paramName)) + { + throw new DataServiceRequestException(Strings.Serializer_UriDoesNotContainParameterAlias(op.Name)); + } + + if (paramName.StartsWith(Char.ToString(UriHelper.ATSIGN), StringComparison.OrdinalIgnoreCase)) + { + // name=value& + queryBuilder.Append(paramName); + queryBuilder.Append(UriHelper.EQUALSSIGN); + queryBuilder.Append(this.ConvertToEscapedUriValue(paramName, op.Value)); + queryBuilder.Append(UriHelper.AMPERSAND); + } + + string value = this.ConvertToEscapedUriValue(paramName, op.Value); + + // non-primitive value, use alias. + if (!UriHelper.IsPrimitiveValue(value)) + { + // name = @name + pathBuilder.Append(paramName); + pathBuilder.Append(UriHelper.EQUALSSIGN); + pathBuilder.Append(UriHelper.ENCODEDATSIGN); + pathBuilder.Append(paramName); + pathBuilder.Append(UriHelper.COMMA); + + // @name = value& + queryBuilder.Append(UriHelper.ENCODEDATSIGN); + queryBuilder.Append(paramName); + queryBuilder.Append(UriHelper.EQUALSSIGN); + queryBuilder.Append(value); + queryBuilder.Append(UriHelper.AMPERSAND); + } + else + { + // primitive value, do not use alias. + pathBuilder.Append(paramName); + pathBuilder.Append(UriHelper.EQUALSSIGN); + pathBuilder.Append(value); + pathBuilder.Append(UriHelper.COMMA); + } + } + + // remove the last extra comma. + if (pathBuilder.ToString().EndsWith(Char.ToString(UriHelper.COMMA), StringComparison.OrdinalIgnoreCase)) + { + Debug.Assert(pathBuilder.ToString().EndsWith(Char.ToString(UriHelper.COMMA), StringComparison.OrdinalIgnoreCase), "Uri was expected to end with an ampersand."); + pathBuilder.Remove(pathBuilder.Length - 1, 1); + } + + pathBuilder.Append(UriHelper.RIGHTPAREN); + + // remove the last extra ampersand. + if (queryBuilder.ToString().EndsWith(Char.ToString(UriHelper.AMPERSAND), StringComparison.OrdinalIgnoreCase)) + { + Debug.Assert(queryBuilder.ToString().EndsWith(Char.ToString(UriHelper.AMPERSAND), StringComparison.OrdinalIgnoreCase), "Uri was expected to end with an ampersand."); + queryBuilder.Remove(queryBuilder.Length - 1, 1); + } + + uriBuilder.Path = pathBuilder.ToString(); + uriBuilder.Query = queryBuilder.ToString(); + + return uriBuilder.Uri; + } + + /// + /// Writes collection value in body operation parameter. + /// + /// The odata parameter writer. + /// The operation parameter. + /// The edm collection type. + private void WriteCollectionValueInBodyOperationParameter(ODataParameterWriter parameterWriter, BodyOperationParameter operationParameter, IEdmCollectionType edmCollectionType) + { + ClientEdmModel model = this.requestInfo.Model; + + var elementTypeKind = edmCollectionType.ElementType.TypeKind(); + + if (elementTypeKind == EdmTypeKind.Entity || elementTypeKind == EdmTypeKind.Complex) + { + ODataWriter feedWriter = parameterWriter.CreateResourceSetWriter(operationParameter.Name); + feedWriter.WriteStart(new ODataResourceSet()); + + IEnumerator enumerator = ((ICollection)operationParameter.Value).GetEnumerator(); + + while (enumerator.MoveNext()) + { + Object collectionItem = enumerator.Current; + if (collectionItem == null) + { + if (elementTypeKind == EdmTypeKind.Complex) + { + feedWriter.WriteStart((ODataResource)null); + feedWriter.WriteEnd(); + continue; + } + else + { + throw new NotSupportedException(Strings.Serializer_NullCollectionParamterItemValue(operationParameter.Name)); + } + } + + IEdmType edmItemType = model.GetOrCreateEdmType(collectionItem.GetType()); + Debug.Assert(edmItemType != null, "edmItemType != null"); + + if (edmItemType.TypeKind != EdmTypeKind.Entity && edmItemType.TypeKind != EdmTypeKind.Complex) + { + throw new NotSupportedException(Strings.Serializer_InvalidCollectionParamterItemType(operationParameter.Name, edmItemType.TypeKind)); + } + + Debug.Assert(model.GetClientTypeAnnotation(edmItemType).ElementType != null, "edmItemType.GetClientTypeAnnotation().ElementType != null"); + ODataResourceWrapper entry = this.CreateODataResourceFromEntityOperationParameter(model.GetClientTypeAnnotation(edmItemType), collectionItem); + Debug.Assert(entry != null, "entry != null"); + ODataWriterHelper.WriteResource(feedWriter, entry); + } + + feedWriter.WriteEnd(); + feedWriter.Flush(); + } + else + { + ODataCollectionWriter collectionWriter = parameterWriter.CreateCollectionWriter(operationParameter.Name); + ODataCollectionStart odataCollectionStart = new ODataCollectionStart(); + collectionWriter.WriteStart(odataCollectionStart); + + IEnumerator enumerator = ((ICollection)operationParameter.Value).GetEnumerator(); + + while (enumerator.MoveNext()) + { + Object collectionItem = enumerator.Current; + if (collectionItem == null) + { + collectionWriter.WriteItem(null); + continue; + } + + IEdmType edmItemType = model.GetOrCreateEdmType(collectionItem.GetType()); + Debug.Assert(edmItemType != null, "edmItemType != null"); + + switch (edmItemType.TypeKind) + { + case EdmTypeKind.Primitive: + { + object primitiveItemValue = ODataPropertyConverter.ConvertPrimitiveValueToRecognizedODataType(collectionItem, collectionItem.GetType()); + collectionWriter.WriteItem(primitiveItemValue); + break; + } + + case EdmTypeKind.Enum: + { + ODataEnumValue enumTmp = this.propertyConverter.CreateODataEnumValue(model.GetClientTypeAnnotation(edmItemType).ElementType, collectionItem, false); + collectionWriter.WriteItem(enumTmp); + break; + } + + default: + // EdmTypeKind.Entity + // EdmTypeKind.Row + // EdmTypeKind.EntityReference + throw new NotSupportedException(Strings.Serializer_InvalidCollectionParamterItemType(operationParameter.Name, edmItemType.TypeKind)); + } + } + + collectionWriter.WriteEnd(); + collectionWriter.Flush(); + } + } + + private static void WriteResourceSet(ODataWriterWrapper writer, ODataResourceSetWrapper resourceSetWrapper) + { + writer.WriteStart(resourceSetWrapper.ResourceSet); + + if (resourceSetWrapper.Resources != null) + { + foreach (var resourceWrapper in resourceSetWrapper.Resources) + { + WriteResource(writer, resourceWrapper); + } + } + + writer.WriteEnd(); + } + + private static void WriteResource(ODataWriterWrapper writer, ODataResourceWrapper resourceWrapper) + { + if (resourceWrapper.Resource == null) + { + writer.WriteStartResource(resourceWrapper.Resource); + } + else + { + writer.WriteStart(resourceWrapper.Resource, resourceWrapper.Instance); + } + + if (resourceWrapper.NestedResourceInfoWrappers != null) + { + foreach (var nestedResourceInfoWrapper in resourceWrapper.NestedResourceInfoWrappers) + { + WriteNestedResourceInfo(writer, nestedResourceInfoWrapper); + } + } + + if (resourceWrapper.Resource == null) + { + writer.WriteEnd(); + } + else + { + writer.WriteEnd(resourceWrapper.Resource, resourceWrapper.Instance); + } + } + + private static void WriteNestedResourceInfo(ODataWriterWrapper writer, ODataNestedResourceInfoWrapper nestedResourceInfo) + { + writer.WriteNestedResourceInfoStart(nestedResourceInfo.NestedResourceInfo); + + if (nestedResourceInfo.NestedResourceOrResourceSet != null) + { + WriteItem(writer, nestedResourceInfo.NestedResourceOrResourceSet); + } + + writer.WriteNestedResourceInfoEnd(); + } + + private static void WriteItem(ODataWriterWrapper writer, ODataItemWrapper odataItemWrapper) + { + var odataResourceWrapper = odataItemWrapper as ODataResourceWrapper; + if (odataResourceWrapper != null) + { + WriteResource(writer, odataResourceWrapper); + return; + } + + var odataResourceSetWrapper = odataItemWrapper as ODataResourceSetWrapper; + if (odataResourceSetWrapper != null) + { + WriteResourceSet(writer, odataResourceSetWrapper); + } + } + + /// + /// Converts a value to an escaped string for use in a Uri. Wraps the call to ODL's ConvertToUriLiteral and escapes the results. + /// + /// The name of the . Used for error reporting. + /// The value of the . + /// If true, use entity reference, instead of entity to serialize the parameter. + /// A string representation of for use in a Url. + private string ConvertToEscapedUriValue(string paramName, object value, bool useEntityReference = false) + { + Debug.Assert(!string.IsNullOrEmpty(paramName), "!string.IsNullOrEmpty(paramName)"); + + // Literal values with single quotes need special escaping due to System.Uri changes in behavior between .NET 4.0 and 4.5. + // We need to ensure that our escaped values do not change between those versions, so we need to escape values differently when they could contain single quotes. + bool needsSpecialEscaping = false; + object valueInODataFormat = ConvertToODataValue(paramName, value, ref needsSpecialEscaping, useEntityReference); + + // When calling Execute() to invoke an Action, the client doesn't support parsing the target url + // to determine which IEdmOperationImport to pass to the ODL writer. So the ODL writer is + // serializing the parameter payload without metadata. Setting the model to null so ODL doesn't + // do unecessary validations when writing without metadata. + string literal = ODataUriUtils.ConvertToUriLiteral(valueInODataFormat, CommonUtil.ConvertToODataVersion(this.requestInfo.MaxProtocolVersionAsVersion), null /* edmModel */); + + // The value from ConvertToUriValue will not be escaped, but will already contain literal delimiters like single quotes, so we + // need to use our own escape method that will preserve those characters instead of directly calling Uri.EscapeDataString that may escape them. + // This is only necessary for primitives and nulls because the other structures are serialized using the JSON format and it uses double quotes + // which have always been escaped. + if (needsSpecialEscaping) + { + return DataStringEscapeBuilder.EscapeDataString(literal); + } + + return Uri.EscapeDataString(literal); + } + + /// + /// Converts the object to ODataValue, the result could be null, the original primitive object, ODataNullValue, + /// ODataEnumValue, ODataCollectionValue, ODataResource, ODataEntityReferenceLinks, ODataEntityReferenceLinks, or + /// a list of ODataResource. + /// + /// The name of the . Used for error reporting. + /// The value of the . + /// True if the result need special escaping. + /// If true, use entity reference, instead of entity to serialize the parameter. + /// The converted result. + private object ConvertToODataValue(string paramName, object value, ref bool needsSpecialEscaping, bool useEntityReference) + { + Object valueInODataFormat = null; + + if (value == null) + { + needsSpecialEscaping = true; + } + else if (value is ODataNullValue) + { + valueInODataFormat = value; + needsSpecialEscaping = true; + } + else + { + ClientEdmModel model = this.requestInfo.Model; + IEdmType edmType = model.GetOrCreateEdmType(value.GetType()); + Debug.Assert(edmType != null, "edmType != null"); + ClientTypeAnnotation typeAnnotation = model.GetClientTypeAnnotation(edmType); + Debug.Assert(typeAnnotation != null, "typeAnnotation != null"); + switch (edmType.TypeKind) + { + case EdmTypeKind.Primitive: + // Client lib internal conversion to support DateTime + if (value is DateTime) + { + valueInODataFormat = PlatformHelper.ConvertDateTimeToDateTimeOffset((DateTime)value); + } + else + { + valueInODataFormat = value; + } + + needsSpecialEscaping = true; + break; + + case EdmTypeKind.Enum: + string typeNameInEdm = this.requestInfo.GetServerTypeName(model.GetClientTypeAnnotation(edmType)); + valueInODataFormat = + new ODataEnumValue( + ClientTypeUtil.GetEnumValuesString(value.ToString(), typeAnnotation.ElementType), + typeNameInEdm ?? typeAnnotation.ElementTypeName); + needsSpecialEscaping = true; + + break; + + case EdmTypeKind.Collection: + IEdmCollectionType edmCollectionType = edmType as IEdmCollectionType; + Debug.Assert(edmCollectionType != null, "edmCollectionType != null"); + IEdmTypeReference itemTypeReference = edmCollectionType.ElementType; + Debug.Assert(itemTypeReference != null, "itemTypeReference != null"); + ClientTypeAnnotation itemTypeAnnotation = + model.GetClientTypeAnnotation(itemTypeReference.Definition); + Debug.Assert(itemTypeAnnotation != null, "itemTypeAnnotation != null"); + + valueInODataFormat = ConvertToCollectionValue(paramName, value, itemTypeAnnotation, useEntityReference); + break; + + case EdmTypeKind.Complex: + case EdmTypeKind.Entity: + Debug.Assert(edmType.TypeKind == EdmTypeKind.Complex || value != null, "edmType.TypeKind == EdmTypeKind.Complex || value != null"); + Debug.Assert(typeAnnotation != null, "typeAnnotation != null"); + valueInODataFormat = ConvertToEntityValue(value, typeAnnotation.ElementType, useEntityReference); + break; + + default: + // EdmTypeKind.Row + // EdmTypeKind.EntityReference + throw new NotSupportedException(Strings.Serializer_InvalidParameterType(paramName, edmType.TypeKind)); + } + + Debug.Assert(valueInODataFormat != null, "valueInODataFormat != null"); + } + + return valueInODataFormat; + } + + /// + /// Converts the object to ODataCollectionValue, ODataEntityReferenceLinks, or + /// a list of ODataResource. + /// + /// The name of the . Used for error reporting. + /// The value of the . + /// The client type annotation of the value. + /// If true, use entity reference, instead of entity to serialize the parameter. + /// The converted result. + private object ConvertToCollectionValue(string paramName, object value, ClientTypeAnnotation itemTypeAnnotation, bool useEntityReference) + { + object valueInODataFormat; + + switch (itemTypeAnnotation.EdmType.TypeKind) + { + case EdmTypeKind.Primitive: + case EdmTypeKind.Enum: + valueInODataFormat = this.propertyConverter.CreateODataCollection(itemTypeAnnotation.ElementType, null, value, null, false, false); + break; + case EdmTypeKind.Complex: + case EdmTypeKind.Entity: + if (useEntityReference) + { + var list = value as IEnumerable; + var links = (from object o in list + select new ODataEntityReferenceLink() + { + Url = this.requestInfo.EntityTracker.GetEntityDescriptor(o).GetLatestIdentity(), + }).ToList(); + + valueInODataFormat = new ODataEntityReferenceLinks() + { + Links = links, + }; + } + else + { + valueInODataFormat = this.propertyConverter.CreateODataEntries( + itemTypeAnnotation.ElementType, value); + } + + break; + + default: + throw new NotSupportedException(Strings.Serializer_InvalidCollectionParamterItemType(paramName, itemTypeAnnotation.EdmType.TypeKind)); + } + + return valueInODataFormat; + } + + /// + /// Converts the object to ODataResource or ODataEntityReferenceLink. + /// + /// The value of the . + /// The type of the value + /// If true, use entity reference, instead of entity to serialize the parameter. + /// The converted result. + private object ConvertToEntityValue(object value, Type elementType, bool useEntityReference) + { + object valueInODataFormat; + + if (!useEntityReference) + { + valueInODataFormat = this.propertyConverter.CreateODataEntry(elementType, value); + + ODataResource entry = (ODataResource)valueInODataFormat; + if (entry.TypeAnnotation == null || + string.IsNullOrEmpty(entry.TypeAnnotation.TypeName)) + { + throw Error.InvalidOperation(Strings.DataServiceException_GeneralError); + } + } + else + { + EntityDescriptor resource = this.requestInfo.EntityTracker.GetEntityDescriptor(value); + Uri link = resource.GetLatestIdentity(); + valueInODataFormat = new ODataEntityReferenceLink() + { + Url = link, + }; + } + + return valueInODataFormat; + } + + /// + /// Creates an ODataResource using some properties extracted from an entity operation parameter. + /// + /// The client type annotation of the entity. + /// The Clr value of the entity. + /// The ODataResource created. + private ODataResourceWrapper CreateODataResourceFromEntityOperationParameter(ClientTypeAnnotation clientTypeAnnotation, object parameterValue) + { + ClientPropertyAnnotation[] properties = new ClientPropertyAnnotation[0]; + if (sendOption == EntityParameterSendOption.SendOnlySetProperties) + { + try + { + var descripter = this.requestInfo.Context.EntityTracker.GetEntityDescriptor(parameterValue); + properties = clientTypeAnnotation.PropertiesToSerialize().Where(p => descripter.PropertiesToSerialize.Contains(p.PropertyName)).ToArray(); + } + catch (InvalidOperationException) + { + throw Error.InvalidOperation(Strings.Context_MustBeUsedWith("EntityParameterSendOption.SendOnlySetProperties", "DataServiceCollection")); + } + } + + return this.propertyConverter.CreateODataResourceWrapper(clientTypeAnnotation.ElementType, parameterValue, properties); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/ShippingAssemblyAttributes.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ShippingAssemblyAttributes.cs new file mode 100644 index 0000000..8263ea5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/ShippingAssemblyAttributes.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma warning disable 436 +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Service.Test.Common" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Service.Test.Client.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Client.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests.ClientExtensions" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Data.ServerUnitTests1.UnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Data.ServerUnitTests2.UnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("RegressionUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaClientUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.Data.Services.DDBasics" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests.ClientCSharp" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Client.Tests" + AssemblyRef.TestPublicKey)] +#pragma warning restore 436 + +#if PORTABLELIB +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DSClient.Net45.Delta.UnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DSClient.Win8Store.Delta.UnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DSClient.Win8Phone.Delta.UnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DSClient.SL.Delta.UnitTests" + AssemblyRef.TestPublicKey)] +#endif diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/StreamDescriptor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/StreamDescriptor.cs new file mode 100644 index 0000000..afec8e2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/StreamDescriptor.cs @@ -0,0 +1,286 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + + /// + /// Holds information about stream. + /// + public sealed class StreamDescriptor : Descriptor + { + #region Private Fields + + /// + /// The Data service stream link object + /// + private DataServiceStreamLink streamLink; + + /// entity descriptor refering the parent entity. + private EntityDescriptor entityDescriptor; + + /// + /// transient named stream info, which contains metadata from the response which has not been materialized yet. + /// + private StreamDescriptor transientNamedStreamInfo; + + #endregion + + /// + /// Creates a StreamDescriptor class with the given name and other information + /// + /// name of the stream. + /// instance of entity descriptor that contains this stream. + internal StreamDescriptor(string name, EntityDescriptor entityDescriptor) : base(EntityStates.Unchanged) + { + Debug.Assert(!String.IsNullOrEmpty(name), "!String.IsNullOrEmpty(name)"); + Debug.Assert(entityDescriptor != null, "entityDescriptor != null"); + this.streamLink = new DataServiceStreamLink(name); + this.entityDescriptor = entityDescriptor; + } + + /// + /// Creates a StreamDescriptor class for the default stream (MR) associated with an entity. + /// + /// instance of entity descriptor that contains this stream. + internal StreamDescriptor(EntityDescriptor entityDescriptor) + : base(EntityStates.Unchanged) + { + Debug.Assert(entityDescriptor != null, "entityDescriptor != null"); + this.streamLink = new DataServiceStreamLink(null); + this.entityDescriptor = entityDescriptor; + } + + /// The that represents the binary resource stream. + /// Returns . + public DataServiceStreamLink StreamLink + { + get { return this.streamLink; } + } + + /// The that represents the entity to which the named resource stream belongs. + /// The of the entity. + public EntityDescriptor EntityDescriptor + { + get + { + return this.entityDescriptor; + } + + set + { + this.entityDescriptor = value; + } + } + + #region Internal Properties + + /// + /// Returns the name of the stream. + /// + internal string Name + { + get + { + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + return this.streamLink.Name; + } + } + + /// + /// Returns the URI to get the named stream. + /// + internal Uri SelfLink + { + get + { + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + return this.streamLink.SelfLink; + } + + set + { + Debug.Assert(value.IsAbsoluteUri, "self link must be an absolute uri"); + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + this.streamLink.SelfLink = value; + } + } + + /// + /// Returns the URI to update the named stream. + /// + internal Uri EditLink + { + get + { + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + return this.streamLink.EditLink; + } + + set + { + Debug.Assert(value.IsAbsoluteUri, "edit link must be an absolute uri"); + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + this.streamLink.EditLink = value; + } + } + + /// + /// Returns the content type of the named stream. + /// + internal string ContentType + { + get + { + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + return this.streamLink.ContentType; + } + + set + { + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + this.streamLink.ContentType = value; + } + } + + /// + /// Returns the etag for the named stream. + /// + internal string ETag + { + get + { + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + return this.streamLink.ETag; + } + + set + { + Debug.Assert(this.streamLink != null, "Stream Link is not null"); + this.streamLink.ETag = value; + } + } + + /// + /// Returns the stream associated with this name. + /// + internal DataServiceSaveStream SaveStream + { + get; + set; + } + + /// return true, since this class represents entity descriptor. + internal override DescriptorKind DescriptorKind + { + get { return DescriptorKind.NamedStream; } + } + + /// + /// Transient named stream info, if there are responses which hasn't been fully processed yet. + /// + internal StreamDescriptor TransientNamedStreamInfo + { + set + { + Debug.Assert(value != null, "you can never set transient named stream to null"); + + if (this.transientNamedStreamInfo == null) + { + this.transientNamedStreamInfo = value; + } + else + { + StreamDescriptor.MergeStreamDescriptor(this.transientNamedStreamInfo, value); + } + } + } + + #endregion + + #region Internal Methods + + /// + /// Merge the information from the new stream info into the existing one. + /// + /// stream info into which the data needs to be merged. + /// stream info which contains the latest data. + internal static void MergeStreamDescriptor(StreamDescriptor existingStreamDescriptor, StreamDescriptor newStreamDescriptor) + { + // overwrite existing information with new ones (coming from the payload). + if (newStreamDescriptor.SelfLink != null) + { + existingStreamDescriptor.SelfLink = newStreamDescriptor.SelfLink; + } + + if (newStreamDescriptor.EditLink != null) + { + existingStreamDescriptor.EditLink = newStreamDescriptor.EditLink; + } + + if (newStreamDescriptor.ContentType != null) + { + existingStreamDescriptor.ContentType = newStreamDescriptor.ContentType; + } + + if (newStreamDescriptor.ETag != null) + { + existingStreamDescriptor.ETag = newStreamDescriptor.ETag; + } + } + + /// + /// clears all the changes - like closes the save stream, clears the transient entity descriptor. + /// This method is called when the client is done with sending all the pending requests. + /// + internal override void ClearChanges() + { + this.transientNamedStreamInfo = null; + this.CloseSaveStream(); + } + + /// return the most recent edit link for the named stream + /// the uri to edit the named stream. + internal Uri GetLatestEditLink() + { + if (this.transientNamedStreamInfo != null && this.transientNamedStreamInfo.EditLink != null) + { + return this.transientNamedStreamInfo.EditLink; + } + + return this.EditLink; + } + + /// return the most recent etag for the named stream + /// the etag for the named stream. + internal string GetLatestETag() + { + if (this.transientNamedStreamInfo != null && this.transientNamedStreamInfo.ETag != null) + { + return this.transientNamedStreamInfo.ETag; + } + + return this.ETag; + } + + /// + /// Closes the save stream if there's any and sets it to null + /// + internal void CloseSaveStream() + { + if (this.SaveStream != null) + { + DataServiceSaveStream stream = this.SaveStream; + this.SaveStream = null; + stream.Close(); + } + } + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/TypeResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/TypeResolver.cs new file mode 100644 index 0000000..90876f8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/TypeResolver.cs @@ -0,0 +1,447 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Client.Metadata; + using Microsoft.OData.Edm; + + /// + /// Class which contains all the logic for resolving the type from the wire name. + /// + internal class TypeResolver + { + /// + /// Dictionary instance to map the edm type name to the client type annotation. + /// In V1/V2, we never used to cache this and call this during materialization. Hence 2 different contexts can resolve the same wire type + /// differently and that would have been fine. Also, someone could have written a logic that might differ based on queries within the same context. + /// For e.g. before executing a projection query, the user can set a flag so that the resolver behaves differently. + /// Hence caching this locally for every request to simulate that behavior. + private readonly IDictionary edmTypeNameMap = new Dictionary(StringComparer.Ordinal); + + /// The callback for resolving client CLR types. + private readonly Func resolveTypeFromName; + + /// The callback for resolving server type names. + private readonly Func resolveNameFromType; + + /// The client model. + private readonly ClientEdmModel clientEdmModel; + + /// The service model, or null if one has not been provided. + private readonly IEdmModel serviceModel; + + /// Indicates whether or not to skip the type assignability check. + private bool skipTypeAssignabilityCheck; + + /// + /// Creates an instance of TypeResolver class. + /// + /// The client model. + /// The callback to resolve client CLR types. + /// The callback for resolving server type names. + /// The service model. + internal TypeResolver(ClientEdmModel model, Func resolveTypeFromName, Func resolveNameFromType, IEdmModel serviceModel) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(resolveTypeFromName != null, "resolveTypeFromName != null"); + Debug.Assert(resolveNameFromType != null, "resolveNameFromType != null"); + this.resolveTypeFromName = resolveTypeFromName; + this.resolveNameFromType = resolveNameFromType; + this.serviceModel = serviceModel; + this.clientEdmModel = model; + + if (serviceModel != null && clientEdmModel != null) + { + foreach (var element in serviceModel.SchemaElements.Where(se => se is IEdmStructuredType)) + { + clientEdmModel.EdmStructuredSchemaElements.TryAdd(element.Name, element); + } + } + } + + /// + /// Gets the reader model. + /// + internal IEdmModel ReaderModel + { + get + { + // DEVNOTE: because the two instances are different types, both internal, do NOT use ternary or + // coalesce operators here (?: and ??). + // This is a known C# compiler issue which produces unverifiable IL causing a runtime exception + // in partial trust environments. + if (this.serviceModel != null) + { + return this.serviceModel; + } + + return this.clientEdmModel; + } + } + + /// + /// In V2, in projection path, we do not use to check for assignability between the expected type + /// and the type returned by the type resolver. This variable is used to track that scenario. + /// If this is true, the current request is a projection request otherwise not. + /// + internal void IsProjectionRequest() + { + this.skipTypeAssignabilityCheck = true; + } + + /// + /// Resolves the client type that should be used for materialization. + /// + /// Expected client clr type based on the API called. + /// + /// The name surfaced by the ODataLib reader. + /// If we have a server model, this will be a server type name that needs to be resolved. + /// If not, then this will already be a client type name. + /// The resolved annotation for the client type to materialize into. + internal ClientTypeAnnotation ResolveTypeForMaterialization(Type expectedType, string readerTypeName) + { + // If its a collection, get the collection item name + string collectionItemTypeName = WebUtil.GetCollectionItemWireTypeName(readerTypeName); + if (collectionItemTypeName == null) + { + // Resolve the primitive type first + PrimitiveType primitiveType; + if (PrimitiveType.TryGetPrimitiveType(readerTypeName, out primitiveType)) + { + return this.clientEdmModel.GetClientTypeAnnotation(primitiveType.ClrType); + } + + ClientTypeAnnotation resultType; + if (this.edmTypeNameMap.TryGetValue(readerTypeName, out resultType)) + { + return resultType; + } + + if (this.serviceModel != null) + { + var resolvedType = this.ResolveTypeFromName(readerTypeName, expectedType); + return this.clientEdmModel.GetClientTypeAnnotation(resolvedType); + } + + // If there was no type name specified in the payload, then the type resolver won't be invoked + // and hence that edm type name might not be in the resolver cache. Hence look that up in the + // ClientEdmModel cache. This lookup is more expensive and is unique across the app domain for the + // given version. + return this.clientEdmModel.GetClientTypeAnnotation(readerTypeName); + } + + Type collectionImplementationType = ClientTypeUtil.GetImplementationType(expectedType, typeof(ICollection<>)); + Type collectionElementType = collectionImplementationType.GetGenericArguments()[0]; + + // In case of collection, the expectedType might be collection of nullable types (for e.g. ICollection). + // There is no way to know the nullability from the wireTypeName (For e.g. Collection(Edm.Int32)). + // Hence in case of collections of primitives, we need to look at the element type of the expected type + // and use that to create the instance otherwise we will not be able to assign the created ICollection<> + // instance to the property on the user's entity (ICollection cannot be assigned to ICollection). + // There is also no need to invoke the resolver for primitives, so we just use the element type. + if (!PrimitiveType.IsKnownType(collectionElementType)) + { + collectionElementType = this.ResolveTypeForMaterialization(collectionElementType, collectionItemTypeName).ElementType; + } + + Type clrCollectionType = WebUtil.GetBackingTypeForCollectionProperty(expectedType); + return this.clientEdmModel.GetClientTypeAnnotation(clrCollectionType); + } + + /// + /// ODL callback for client type resolution + /// + /// The expected type for the given wire name + /// The name of the type from the payload + /// An IEdmType + internal IEdmType ResolveWireTypeName(IEdmType expectedEdmType, string wireName) + { + // ODataLib should never pass an empty or null type name + Debug.Assert(!String.IsNullOrEmpty(wireName), "!String.IsNullOrEmpty(wireName)"); + + // For V3 and above, ODataLib will never call the type resolver if there is a collection + // type specified in the wire. However, in V1/V2, since there was no collection feature + // supported, it will call us with a collection wire name, but its okay to return null + // in that case, since there is no collection supported. If the user writes the type + // resolver in such a way to handle collections themselver, even then it will fail later + // in ODataLib stating collection types are not supported in V1/V2 versions. + if (expectedEdmType != null) + { + // In V1/V2, we never used to call the type resolver for primitives types. + // Instead, we just used to look at the expected property type and try to convert + // the value from the payload to the expected property type. In other words, we + // used to ignore the type name on the wire for primitive properties. + if (expectedEdmType.TypeKind == EdmTypeKind.Primitive) + { + return expectedEdmType; + } + } + + Type expectedType; + if (expectedEdmType != null) + { + ClientTypeAnnotation expectedAnnotation = this.clientEdmModel.GetClientTypeAnnotation(expectedEdmType); + Debug.Assert(expectedAnnotation != null, "expectedAnnotation != null"); + expectedType = expectedAnnotation.ElementType; + } + else + { + expectedType = typeof(object); + } + + // Breaking change: we decided to validate against the resolved type if the type are not assignable. + Type resolvedType = this.ResolveTypeFromName(wireName, expectedType); + + ClientTypeAnnotation resolvedTypeAnnotation = this.clientEdmModel.GetClientTypeAnnotation(this.clientEdmModel.GetOrCreateEdmType(resolvedType)); + Debug.Assert(resolvedTypeAnnotation != null, "result != null -- otherwise ClientType.Create returned null"); + + IEdmType clientEdmType = resolvedTypeAnnotation.EdmType; + EdmTypeKind typeKind = clientEdmType.TypeKind; + if (typeKind == EdmTypeKind.Entity || typeKind == EdmTypeKind.Complex) + { + // If the edm type name is not present in the dictionary, add it to the map + string edmTypeName = clientEdmType.FullName(); + if (!this.edmTypeNameMap.ContainsKey(edmTypeName)) + { + this.edmTypeNameMap.Add(edmTypeName, resolvedTypeAnnotation); + } + } + + return clientEdmType; + } + + /// + /// Resolves the expected EDM type full name to give to the ODataLib reader based on a client CLR type. + /// + /// The client side CLR type. + /// The resolved EDM type full name to provide to ODataLib. + internal string ResolveServiceEntityTypeFullName(Type clientClrType) + { + Debug.Assert(clientClrType != null, "materializerType != null"); + + IEdmType edmType = this.ResolveExpectedTypeForReading(clientClrType); + + return edmType != null ? edmType.FullName() : null; + } + + /// + /// Resolves the expected EDM type to give to the ODataLib reader based on a client CLR type. + /// + /// The client side CLR type. + /// The resolved EDM type to provide to ODataLib. + [SuppressMessage("Microsoft.Naming", "CA2204:LiteralsShouldBeSpelledCorrectly", Justification = "Names are correct. String can't be localized after string freeze.")] + internal IEdmType ResolveExpectedTypeForReading(Type clientClrType) + { + Debug.Assert(clientClrType != null, "materializerType != null"); + + ClientTypeAnnotation clientTypeAnnotation = this.clientEdmModel.GetClientTypeAnnotation(clientClrType); + + IEdmType clientType = clientTypeAnnotation.EdmType; + if (this.serviceModel == null) + { + return clientType; + } + + // for primitive types there is no need for the server's metadata + if (clientType.TypeKind == EdmTypeKind.Primitive) + { + return clientType; + } + + if (clientType.TypeKind == EdmTypeKind.Collection) + { + IEdmTypeReference clientEdmElementType = ((IEdmCollectionType)clientType).ElementType; + if (clientEdmElementType.IsPrimitive()) + { + return clientType; + } + + var elementType = clientClrType.GetGenericArguments()[0]; + var elementEdmType = this.ResolveExpectedTypeForReading(elementType); + + if (elementEdmType == null) + { + // If we can't determine an expected element type, for whatever reason, then we cannot construct an expected collection type. + return null; + } + + return new EdmCollectionType(elementEdmType.ToEdmTypeReference(clientEdmElementType.IsNullable)); + } + + IEdmStructuredType serverType; + if (!this.TryToResolveServerType(clientTypeAnnotation, out serverType)) + { + // if we don't know the server type, but do have a server model, then simply read without an expected type. + return null; + } + + return serverType; + } + + /// + /// Determines whether or not the client type should be written for a property that is not defined on the server. + /// DEVNOTE: If there is no server model, the declaring type is complex, or the server type cannot be + /// found then the server type will be assumed to match the client type. + /// This is done this way to prevent getting this wrong if the server property is defined, but we cannot find it for some reason. + /// So if the types do not match, or we aren't able to align them, we will not write the type name, allowing the server to interpret it as the correct type. + /// + /// The client-side property. + /// The server type name of the current entity. + /// True if the client property type should be written because the property definitely not defined on the server type. + internal bool ShouldWriteClientTypeForOpenServerProperty(IEdmProperty clientProperty, string serverTypeName) + { + if (serverTypeName == null) + { + // if the server side type cannot be found, then assume that its types match the client types. + return false; + } + + if (this.serviceModel == null) + { + // assume client model matches server model + return false; + } + + var serverType = this.serviceModel.FindType(serverTypeName) as IEdmStructuredType; + if (serverType == null) + { + // if the server side type cannot be found, then assume that its types match the client types. + return false; + } + + // if the property is not defined, then write the type name + return serverType.FindProperty(clientProperty.Name) == null; + } + + /// + /// Tries to resolve the name of the base type of the given entity set if a server model has been provided. + /// + /// The name of the entity set. + /// The server type name if one could be found. + /// Whether the type name could be found. + internal bool TryResolveEntitySetBaseTypeName(string entitySetName, out string serverTypeName) + { + serverTypeName = null; + if (this.serviceModel == null) + { + return false; + } + + var entitySet = this.serviceModel.FindDeclaredEntitySet(entitySetName); + if (entitySet != null) + { + serverTypeName = ExtensionMethods.FullName(entitySet.EntityType()); + return true; + } + + return false; + } + + /// + /// Tries to resolve the name of a navigation property's target if a server model has been provided. + /// + /// The name of the server side source type. + /// The name of the navigation property. + /// The server type name if one could be found. + /// Whether the type name could be found. + internal bool TryResolveNavigationTargetTypeName(string serverSourceTypeName, string navigationPropertyName, out string serverTypeName) + { + serverTypeName = null; + if (this.serviceModel == null || serverSourceTypeName == null) + { + return false; + } + + IEdmEntityType parentServerType = this.serviceModel.FindType(serverSourceTypeName) as IEdmEntityType; + if (parentServerType == null) + { + return false; + } + + IEdmNavigationProperty serverNavigation = parentServerType.FindProperty(navigationPropertyName) as IEdmNavigationProperty; + if (serverNavigation == null) + { + return false; + } + + IEdmTypeReference targetType = serverNavigation.Type; + if (targetType.IsCollection()) + { + targetType = targetType.AsCollection().ElementType(); + } + + serverTypeName = targetType.FullName(); + return true; + } + + /// + /// Tries to resolve the server type corresponding to the client type. + /// + /// The client type annotation. + /// The server type, if the server type could be resolved. + /// Whether or not the server type could be resolved. + private bool TryToResolveServerType(ClientTypeAnnotation clientTypeAnnotation, out IEdmStructuredType serverType) + { + Debug.Assert(this.serviceModel != null, "this.serviceModel != null"); + Debug.Assert(clientTypeAnnotation != null, "clientTypeAnnotation != null"); + + var serverTypeName = this.resolveNameFromType(clientTypeAnnotation.ElementType); + if (serverTypeName == null) + { + serverType = null; + return false; + } + + serverType = this.serviceModel.FindType(serverTypeName) as IEdmStructuredType; + return serverType != null; + } + + /// + /// User hook to resolve name into a type. + /// + /// Name to resolve. + /// Expected type for the name. + /// the type as returned by the resolver. If no resolver is registered or resolver returns null, expected type is returned. + /// if ResolveType function returns a type not assignable to the userType + private Type ResolveTypeFromName(string wireName, Type expectedType) + { + Debug.Assert(!String.IsNullOrEmpty(wireName), "!String.IsNullOrEmpty(wireName)"); + Debug.Assert(expectedType != null, "userType != null"); + + // If there is a mismatch between the wireName and expected type (For e.g. wireName is Edm.Int32 and expectedType is a complex type) + // we will return Edm.Int32 from this method and ODatalib fails stating the type kind do not match. + Type payloadType; + if (!ClientConvert.ToNamedType(wireName, out payloadType)) + { + payloadType = this.resolveTypeFromName(wireName); + + if (payloadType == null) + { + // if the type resolution method returns null or the ResolveType property was not set +#if !PORTABLELIB + payloadType = ClientTypeCache.ResolveFromName(wireName, expectedType); +#else + payloadType = ClientTypeCache.ResolveFromName(wireName, expectedType, this.GetType()); +#endif + } + + if (!this.skipTypeAssignabilityCheck && (payloadType != null) && (!expectedType.IsAssignableFrom(payloadType))) + { + // throw an exception if the type from the resolver is not assignable to the expected type + throw Error.InvalidOperation(Strings.Deserialize_Current(expectedType, payloadType)); + } + } + + return payloadType ?? expectedType; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriEntityOperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriEntityOperationParameter.cs new file mode 100644 index 0000000..8888bfc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriEntityOperationParameter.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// + /// Represents a function parameter that is either entity or collection of entities. + /// + public sealed class UriEntityOperationParameter : UriOperationParameter + { + /// + /// If true, use entity reference instead of entity as the operation parameter. + /// + private readonly bool useEntityReference; + + /// Instantiates a new UriEntityOperationParameter + /// The name of the uri operation parameter. + /// The value of the uri operation parameter. + public UriEntityOperationParameter(string name, Object value) + : base(name, value) + { + } + + /// Instantiates a new UriOperationParameter + /// The name of the uri operation parameter. + /// The value of the uri operation parameter. + /// If true, use entity reference, instead of entity to serialize the parameter. + public UriEntityOperationParameter(string name, Object value, bool useEntityReference) + : this(name, value) + { + this.useEntityReference = useEntityReference; + } + + + /// Use entity reference link. + /// True if it uses entity reference link. + internal bool UseEntityReference + { + get { return this.useEntityReference; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriOperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriOperationParameter.cs new file mode 100644 index 0000000..e8991c7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriOperationParameter.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + + /// + /// Represents a parameter associated with a service operation or a service function. + /// + public class UriOperationParameter : OperationParameter + { + /// Instantiates a new UriOperationParameter + /// The name of the uri operation parameter. + /// The value of the uri operation parameter. + public UriOperationParameter(string name, Object value) + : base(name, value) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriResolver.cs new file mode 100644 index 0000000..d96eccc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriResolver.cs @@ -0,0 +1,295 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Diagnostics; + + /// + /// This immutable class is responsible for knowing how to correctly resolve base uri requests + /// + internal class UriResolver + { + /// The baseUri provided by the user. can be null. + private readonly Uri baseUri; + + /// The function provided by the user to resolve the baseUri of entity sets. can be null + private readonly Func resolveEntitySet; + + /// base uri with guranteed trailing slash + private readonly Uri baseUriWithSlash; + + /// + /// Ctor for BaseUriResolver + /// + /// The baseUri provided by the user. + /// The function provider by the user to resolve the baseUri of the entity sets. + private UriResolver(Uri baseUri, Func resolveEntitySet) + { + this.baseUri = baseUri; + this.resolveEntitySet = resolveEntitySet; + if (this.baseUri != null) + { + this.baseUriWithSlash = ForceSlashTerminatedUri(this.baseUri); + } + } + + /// Gets the ResolveEntitySet value. + internal Func ResolveEntitySet + { + get { return this.resolveEntitySet; } + } + + /// + /// This property returns the baseUri value with no validation at all + /// + /// NOTE: this property should only be used to show the user what the BaseUri value is, all + /// other access should go through the GetBaseUriWithSlash() method + /// + /// the baseUri value + internal Uri RawBaseUriValue + { + get { return this.baseUri; } + } + + /// + /// Gets the base URI of the resolver regardless of whether or not it is null. + /// + internal Uri BaseUriOrNull + { + get { return this.baseUriWithSlash; } + } + + /// + /// Creates a UriResolver from a baseUri + /// + /// The baseUri to use in the UriResolver + /// The name of the paramter that the user passed the baseUri in from. + /// The new UriResolver using the passed in baseUri + internal static UriResolver CreateFromBaseUri(Uri baseUri, string parameterName) + { + ConvertToAbsoluteAndValidateBaseUri(ref baseUri, parameterName); + return new UriResolver(baseUri, null); + } + + /// + /// Creates a new BaseUriResolver with all the same values except a new BaseUri value + /// + /// The new BaseUri value + /// The name of the paramter that the user passed the baseUri in from. + /// A new BaseUriResolver with the BaseUri property set to the new value. + internal UriResolver CloneWithOverrideValue(Uri overrideBaseUriValue, string parameterName) + { + ConvertToAbsoluteAndValidateBaseUri(ref overrideBaseUriValue, parameterName); + return new UriResolver(overrideBaseUriValue, this.resolveEntitySet); + } + + /// + /// Creates a new BaseUriResolver with all the same values except a new ResolveEntitySet value + /// + /// The new BaseUri value + /// A new BaseUriResolver with the ResolveEntitySet property set to the new value. + internal UriResolver CloneWithOverrideValue(Func overrideResolveEntitySetValue) + { + return new UriResolver(this.baseUri, overrideResolveEntitySetValue); + } + + /// base uri with no trailing slash + /// the name of the entitSet whose Uri will be retrieved. + /// the baseUri ended with a slash for the entitySetName passed in. + internal Uri GetEntitySetUri(string entitySetName) + { + Uri resolved = this.GetEntitySetUriFromResolver(entitySetName); + if (resolved != null) + { + return ForceNonSlashTerminatedUri(resolved); + } + + if (this.baseUriWithSlash != null) + { + return UriUtil.CreateUri(this.baseUriWithSlash, UriUtil.CreateUri(entitySetName, UriKind.Relative)); + } + + throw Error.InvalidOperation(Strings.Context_ResolveEntitySetOrBaseUriRequired(entitySetName)); + } + + /// + /// returns the BaseUri property followed by a slash. + /// + /// if the BaseUri property is null, an InvalidOperationException is thrown + /// + /// The BaseUri property with a slash. + internal Uri GetBaseUriWithSlash() + { + return this.GetBaseUriWithSlash(() => Strings.Context_BaseUriRequired); + } + + /// + /// If necessary will create an absolute uri by combining the BaseUri and requestUri + /// + /// The uri specified by the user + /// An absolute Uri based on the requestUri and if nessesary the BaseUri + internal Uri GetOrCreateAbsoluteUri(Uri requestUri) + { + Util.CheckArgumentNull(requestUri, "requestUri"); + if (!requestUri.IsAbsoluteUri) + { + return UriUtil.CreateUri(this.GetBaseUriWithSlash(() => Strings.Context_RequestUriIsRelativeBaseUriRequired), requestUri); + } + + return requestUri; + } + + /// + /// Converts the baseUri passed in to an absolute Uri and then validates that it is + /// usable by the system. + /// + /// The user provided baseUri value. + /// The name of the paramter that the user passed the baseUri in from. + private static void ConvertToAbsoluteAndValidateBaseUri(ref Uri baseUri, string parameterName) + { + baseUri = ConvertToAbsoluteUri(baseUri); + if (!IsValidBaseUri(baseUri)) + { + if (parameterName != null) + { + throw Error.Argument(Strings.Context_BaseUri, parameterName); + } + + throw Error.InvalidOperation(Strings.Context_BaseUri); + } + } + + /// + /// Validates that the passed in BaseUri + /// + /// the baseUri that needs to be validated + /// True if the baseUri is valid, otherwise false + private static bool IsValidBaseUri(Uri baseUri) + { + if (baseUri == null) + { + return true; + } + + if (!baseUri.IsAbsoluteUri || + !Uri.IsWellFormedUriString(UriUtil.UriToString(baseUri), UriKind.Absolute) || + !String.IsNullOrEmpty(baseUri.Query) || + !String.IsNullOrEmpty(baseUri.Fragment)) + { + return false; + } + + return true; + } + + /// + /// Updates a relative silverlight uri to an absolute uri + /// + /// the uri passed by the client + /// the updated absolute uri + private static Uri ConvertToAbsoluteUri(Uri baseUri) + { + if (baseUri == null) + { + return null; + } + + return baseUri; + } + + /// + /// Returns a Uri that is not slash terminated + /// + /// Will be the passed in one if it is slash termination free, or a new one + /// if the passed in one is slash terminated. + /// + /// The Uri to be un slash terminated + /// A slash termination free version of the passed in Uri. + private static Uri ForceNonSlashTerminatedUri(Uri uri) + { + Debug.Assert(uri.IsAbsoluteUri, "the uri must be an absolute uri"); + Debug.Assert(String.IsNullOrEmpty(uri.Query), "the uri must not have any query"); + Debug.Assert(String.IsNullOrEmpty(uri.Fragment), "the uri must not have any fragment"); + + string uriString = UriUtil.UriToString(uri); + if (uriString[uriString.Length - 1] == '/') + { + return UriUtil.CreateUri(uriString.Substring(0, uriString.Length - 1), UriKind.Absolute); + } + + return uri; + } + + /// + /// Returns a slash terminated Uri. + /// + /// Will be the passed in one if it is already slash terminated, or a new one + /// if the passed in one is not slash terminated. + /// + /// The Uri to be slash terminated + /// A slash terminated version of the passed in Uri. + private static Uri ForceSlashTerminatedUri(Uri uri) + { + Debug.Assert(uri.IsAbsoluteUri, "the uri must be an absolute uri"); + Debug.Assert(String.IsNullOrEmpty(uri.Query), "the uri must not have any query"); + Debug.Assert(String.IsNullOrEmpty(uri.Fragment), "the uri must not have any fragment"); + + string uriString = UriUtil.UriToString(uri); + if (uriString[uriString.Length - 1] != '/') + { + return UriUtil.CreateUri(uriString + "/", UriKind.Absolute); + } + + return uri; + } + + /// + /// returns the BaseUri property followed by a slash. + /// + /// if the BaseUri property is null, an InvalidOperationException is thrown + /// + /// + /// Returns the error message to use if the BaseUri is not available. Using a function so we only have to + /// look up the resource if an error is actually thrown; + /// + /// The BaseUri property with a slash. + private Uri GetBaseUriWithSlash(Func getErrorMessage) + { + if (this.baseUriWithSlash == null) + { + throw Error.InvalidOperation(getErrorMessage()); + } + + return this.baseUriWithSlash; + } + + /// + /// Gets a Uri from the ResolveEntitySet property if available + /// + /// The name of the entity set to resolve to a URI + /// An absolute URI for the entitySet or null + private Uri GetEntitySetUriFromResolver(string entitySetName) + { + if (this.resolveEntitySet != null) + { + Uri resolved = this.resolveEntitySet(entitySetName); + if (resolved != null) + { + if (IsValidBaseUri(resolved)) + { + return resolved; + } + + throw Error.InvalidOperation(Strings.Context_ResolveReturnedInvalidUri); + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriUtil.cs new file mode 100644 index 0000000..c08a903 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/UriUtil.cs @@ -0,0 +1,179 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Service +#endif +{ + #region Namespaces + + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + #endregion Namespaces + + /// + /// static utility functions for uris + /// + internal static class UriUtil + { +#if ODATA_CLIENT + /// forward slash char array for triming uris + internal static readonly char[] ForwardSlash = new char[1] { '/' }; +#endif + + /// + /// Turn Uri instance into string representation + /// This is needed because Uri.ToString unescapes the string + /// + /// The uri instance + /// The string representation of the uri + internal static string UriToString(Uri uri) + { + Debug.Assert(uri != null, "uri != null"); + return uri.IsAbsoluteUri ? uri.AbsoluteUri : uri.OriginalString; + } + +#if ODATA_CLIENT + /// new Uri(string uriString, UriKind uriKind) + /// value + /// kind + /// new Uri(value, kind) + internal static Uri CreateUri(string value, UriKind kind) + { + return value == null ? null : new Uri(value, kind); + } + + /// new Uri(Uri baseUri, Uri requestUri) + /// baseUri + /// relativeUri + /// new Uri(baseUri, requestUri) + internal static Uri CreateUri(Uri baseUri, Uri requestUri) + { + Debug.Assert(baseUri != null, "baseUri != null"); + Util.CheckArgumentNull(requestUri, "requestUri"); + + if (!baseUri.IsAbsoluteUri) + { + return CreateUri(UriToString(baseUri) + UriHelper.FORWARDSLASH + UriToString(requestUri), UriKind.Relative); + } + + Debug.Assert(String.IsNullOrEmpty(baseUri.Query) && String.IsNullOrEmpty(baseUri.Fragment), "baseUri has query or fragment"); + + // there is a bug in (new Uri(Uri,Uri)) which corrupts the port of the result if out relativeUri is also absolute + if (requestUri.IsAbsoluteUri) + { + return requestUri; + } + + return AppendBaseUriAndRelativeUri(baseUri, requestUri); + } +#else + /// + /// Read the identifier from the uri segment value + /// + /// One of the segments as returned by Uri.Segments method. + /// The segment identifier after stripping the last '/' character and unescaping the identifier. + internal static string ReadSegmentValue(string segment) + { + if (segment.Length != 0 && segment != "/") + { + if (segment[segment.Length - 1] == '/') + { + segment = segment.Substring(0, segment.Length - 1); + } + + return Uri.UnescapeDataString(segment); + } + + return null; + } + + /// is the serviceRoot the base of the request uri + /// baseUriWithSlash + /// requestUri + /// true if the serviceRoot is the base of the request uri + internal static bool IsBaseOf(Uri baseUriWithSlash, Uri requestUri) + { + return baseUriWithSlash.IsBaseOf(requestUri); + } + + /// + /// Determines whether the Uri instance is a + /// base of the specified Uri instance. + /// + /// Candidate base URI. + /// The specified Uri instance to test. + /// true if the current Uri instance is a base of uri; otherwise, false. + internal static bool UriInvariantInsensitiveIsBaseOf(Uri current, Uri uri) + { + Debug.Assert(current != null, "current != null"); + Debug.Assert(uri != null, "uri != null"); + + Uri upperCurrent = CreateBaseComparableUri(current); + Uri upperUri = CreateBaseComparableUri(uri); + + return IsBaseOf(upperCurrent, upperUri); + } +#endif + +#if ODATA_CLIENT + /// + /// Appends the absolute baseUri with the relativeUri to create a new absolute uri + /// + /// An absolute Uri + /// A relative Uri + /// An absolute Uri that is the combination of the base and relative Uris passed in. + private static Uri AppendBaseUriAndRelativeUri(Uri baseUri, Uri relativeUri) + { + Debug.Assert(baseUri != null, "baseUri != null"); + Debug.Assert(baseUri.IsAbsoluteUri, "relativeUri is not relative"); + Debug.Assert(relativeUri != null, "relativeUri != null"); + Debug.Assert(!relativeUri.IsAbsoluteUri, "relativeUri is not relative"); + + string baseUriString = UriToString(baseUri); + string relativeUriString = UriToString(relativeUri); + + if (baseUriString.EndsWith("/", StringComparison.Ordinal)) + { + if (relativeUriString.StartsWith("/", StringComparison.Ordinal)) + { + relativeUri = new Uri(baseUri, CreateUri(relativeUriString.TrimStart(ForwardSlash), UriKind.Relative)); + } + else + { + relativeUri = new Uri(baseUri, relativeUri); + } + } + else + { + relativeUri = CreateUri(baseUriString + "/" + relativeUriString.TrimStart(ForwardSlash), UriKind.Absolute); + } + + return relativeUri; + } +#else + /// Creates a URI suitable for host-agnostic comparison purposes. + /// URI to compare. + /// URI suitable for comparison. + private static Uri CreateBaseComparableUri(Uri uri) + { + Debug.Assert(uri != null, "uri != null"); + + uri = new Uri(UriToString(uri).ToUpper(CultureInfo.InvariantCulture), UriKind.RelativeOrAbsolute); + + UriBuilder builder = new UriBuilder(uri); + builder.Host = "h"; + builder.Port = 80; + builder.Scheme = "http"; + return builder.Uri; + } +#endif + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Util.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Util.cs new file mode 100644 index 0000000..b6d9201 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Util.cs @@ -0,0 +1,610 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Xml; + + /// + /// static utility function + /// + internal static class Util + { + /// Tool name for the GeneratedCode attribute used by Astoria CodeGen + internal const string CodeGeneratorToolName = "Microsoft.OData.Service.Design"; + + /// Method name for the LoadProperty method. + internal const string LoadPropertyMethodName = "LoadProperty"; + + /// Method name for the Execute method. + internal const string ExecuteMethodName = "Execute"; + + /// Method name for the Async Execute method overload which expects void result. + internal const string ExecuteMethodNameForVoidResults = "ExecuteVoid"; + + /// Method name for the SaveChanges method. + internal const string SaveChangesMethodName = "SaveChanges"; + + /// + /// The number of components of version. + /// + internal const int ODataVersionFieldCount = 2; + + /// + /// Empty OData Version - represents a blank OData-Version header + /// + internal static readonly Version ODataVersionEmpty = new Version(0, 0); + + /// + /// OData Version 4 + /// + internal static readonly Version ODataVersion4 = new Version(4, 0); + + /// + /// OData Version 4.01 + /// + internal static readonly Version ODataVersion401 = new Version(4, 1); + + /// + /// Data service versions supported on the client + /// + internal static readonly Version[] SupportedResponseVersions = + { + ODataVersion4, + ODataVersion401 + }; + + /// + /// static char[] for indenting whitespace when tracing xml + /// + private static char[] whitespaceForTracing = new char[] { '\r', '\n', ' ', ' ', ' ', ' ', ' ' }; + +#if DEBUG + /// + /// DebugFaultInjector is a test hook to inject faults in specific locations. The string is the ID for the location + /// + private static Action DebugFaultInjector = new Action((s) => { }); +#endif + + /// + /// Converts the ODataProtocolVersion to a Version instance. + /// + /// The max protocol version value. + /// The same version expressed as Version instance. + internal static Version GetVersionFromMaxProtocolVersion(ODataProtocolVersion maxProtocolVersion) + { + switch (maxProtocolVersion) + { + case ODataProtocolVersion.V4: + return Util.ODataVersion4; + default: + Debug.Assert(false, "Unexpected max protocol version values."); + return Util.ODataVersion4; + } + } + + /// + /// DebugInjectFault is a test hook to inject faults in specific locations. The string is the ID for the location + /// + /// The injector state parameter + [Conditional("DEBUG")] + internal static void DebugInjectFault(string state) + { +#if DEBUG + DebugFaultInjector(state); +#endif + } + + /// + /// Checks the argument value for null and throw ArgumentNullException if it is null + /// + /// type of the argument to prevent accidental boxing of value types + /// argument whose value needs to be checked + /// name of the argument + /// if value is null + /// value + internal static T CheckArgumentNull([ValidatedNotNull] T value, string parameterName) where T : class + { + if (null == value) + { + throw Error.ArgumentNull(parameterName); + } + + return value; + } + + /// + /// Checks the string value is not empty + /// + /// value to check + /// parameterName of public function + /// if value is null + /// if value is empty + internal static void CheckArgumentNullAndEmpty([ValidatedNotNull] string value, string parameterName) + { + CheckArgumentNull(value, parameterName); + CheckArgumentNotEmpty(value, parameterName); + } + + /// + /// Checks the string value is not empty, but allows it to be null + /// + /// value to check + /// parameterName of public function + /// if value is empty + internal static void CheckArgumentNotEmpty(string value, string parameterName) + { + if (value != null && 0 == value.Length) + { + throw Error.Argument(Strings.Util_EmptyString, parameterName); + } + } + + /// + /// Checks the array value is not empty + /// + /// type of the argument to prevent accidental boxing of value types + /// value to check + /// parameterName of public function + /// if value is null + /// if value is empty or contains null elements + internal static void CheckArgumentNotEmpty(T[] value, string parameterName) where T : class + { + CheckArgumentNull(value, parameterName); + if (0 == value.Length) + { + throw Error.Argument(Strings.Util_EmptyArray, parameterName); + } + + for (int i = 0; i < value.Length; ++i) + { + if (Object.ReferenceEquals(value[i], null)) + { + throw Error.Argument(Strings.Util_NullArrayElement, parameterName); + } + } + } + + /// + /// Validate EntityParameterSendOption + /// + /// option to validate + /// name of the parameter being validated + /// if option is not valid + /// option + internal static EntityParameterSendOption CheckEnumerationValue(EntityParameterSendOption value, string parameterName) + { + switch (value) + { + case EntityParameterSendOption.SendFullProperties: + case EntityParameterSendOption.SendOnlySetProperties: + return value; + default: + throw Error.ArgumentOutOfRange(parameterName); + } + } + + /// + /// Validate MergeOption + /// + /// option to validate + /// name of the parameter being validated + /// if option is not valid + /// option + internal static MergeOption CheckEnumerationValue(MergeOption value, string parameterName) + { + switch (value) + { + case MergeOption.AppendOnly: + case MergeOption.OverwriteChanges: + case MergeOption.PreserveChanges: + case MergeOption.NoTracking: + return value; + default: + throw Error.ArgumentOutOfRange(parameterName); + } + } + + /// + /// Validate MaxProtocolVersion + /// + /// version to validate + /// name of the parameter being validated + /// if version is not valid + /// version + internal static ODataProtocolVersion CheckEnumerationValue(ODataProtocolVersion value, string parameterName) + { + switch (value) + { + case ODataProtocolVersion.V4: + return value; + default: + throw Error.ArgumentOutOfRange(parameterName); + } + } + + /// + /// Validate HttpStack + /// + /// option to validate + /// name of the parameter being validated + /// if option is not valid + /// option + internal static HttpStack CheckEnumerationValue(HttpStack value, string parameterName) + { + switch (value) + { + case HttpStack.Auto: + return value; + default: + throw Error.ArgumentOutOfRange(parameterName); + } + } + + /// + /// get char[] for indenting whitespace when tracing xml + /// + /// how many characters to trace + /// char[] + internal static char[] GetWhitespaceForTracing(int depth) + { + char[] whitespace = Util.whitespaceForTracing; + while (whitespace.Length <= depth) + { + char[] tmp = new char[2 * whitespace.Length]; + tmp[0] = '\r'; + tmp[1] = '\n'; + for (int i = 2; i < tmp.Length; ++i) + { + tmp[i] = ' '; + } + + System.Threading.Interlocked.CompareExchange(ref Util.whitespaceForTracing, tmp, whitespace); + whitespace = tmp; + } + + return whitespace; + } + + /// dispose of the object and set the reference to null + /// type that implements IDisposable + /// object to dispose + internal static void Dispose(ref T disposable) where T : class, IDisposable + { + Dispose(disposable); + disposable = null; + } + + /// dispose of the object + /// type that implements IDisposable + /// object to dispose + internal static void Dispose(T disposable) where T : class, IDisposable + { + if (null != disposable) + { + disposable.Dispose(); + } + } + + /// + /// Checks whether the exception type is one of the DataService*Exception + /// + /// exception to test + /// true if the exception type is one of the DataService*Exception + internal static bool IsKnownClientExcption(Exception ex) + { + return (ex is DataServiceClientException) || (ex is DataServiceQueryException) || (ex is DataServiceRequestException); + } + + /// validate value is non-null + /// type of value + /// value + /// error code to throw if null + /// the non-null value + internal static T NullCheck(T value, InternalError errorcode) where T : class + { + if (Object.ReferenceEquals(value, null)) + { + Error.ThrowInternalError(errorcode); + } + + return value; + } + + /// + /// check the atom:null="true" attribute + /// + /// XmlReader + /// true of null is true + internal static bool DoesNullAttributeSayTrue(XmlReader reader) + { + string attributeValue = reader.GetAttribute(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace); + return ((null != attributeValue) && XmlConvert.ToBoolean(attributeValue)); + } + + /// Set the continuation for the following results for a collection. + /// The collection to set the links to + /// The continuation for the collection. + internal static void SetNextLinkForCollection(object collection, DataServiceQueryContinuation continuation) + { + Debug.Assert(collection != null, "collection != null"); + + // We do a convention call for setting Continuation. We'll invoke this + // for all properties named 'Continuation' that is a DataServiceQueryContinuation + // (assigning to a single one would make it inconsistent if reflection + // order is changed). + foreach (var property in collection.GetType().GetPublicProperties(true /*instanceOnly*/)) + { + if (property.Name != "Continuation" || !property.CanWrite) + { + continue; + } + + if (typeof(DataServiceQueryContinuation).IsAssignableFrom(property.PropertyType)) + { + property.SetValue(collection, continuation, null); + } + } + } + + /// + /// Determines if the current type is nullable or not + /// + /// The type parameter. + /// true if its nullable false otherwise + internal static bool IsNullableType(Type t) + { + if (t.IsClass()) + { + return true; + } + + if (Nullable.GetUnderlyingType(t) != null) + { + return true; + } + + return false; + } + + /// + /// Similar to Activator.CreateInstance, but uses LCG to avoid + /// more stringent Reflection security constraints.in Silverlight + /// + /// Type to create. + /// Arguments. + /// The newly instantiated object. + internal static object ActivatorCreateInstance(Type type, params object[] arguments) + { + Debug.Assert(type != null, "type != null"); + + // Different error messages occur for each call to activator.CreateInstance, when no parameters specified error message is more meaningful + if (arguments.Length == 0) + { + return Activator.CreateInstance(type); + } + else + { + return Activator.CreateInstance(type, arguments); + } + } + + /// + /// Similar to ConstructorInfo.Invoke, but uses LCG to avoid + /// more stringent Reflection security constraints in Silverlight + /// + /// Constructor to invoke. + /// Arguments. + /// The newly instantiated object. + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] + internal static object ConstructorInvoke(ConstructorInfo constructor, object[] arguments) + { + if (constructor == null) + { +#if PORTABLELIB + throw new MissingMemberException(); +#else + throw new MissingMethodException(); +#endif + } + + return constructor.Invoke(arguments); + } + + /// + /// checks whether the given flag is set on the options + /// + /// options as specified by the user. + /// whether the given flag is set on the options + /// true if the given flag is set, otherwise false. + internal static bool IsFlagSet(SaveChangesOptions options, SaveChangesOptions flag) + { + return ((options & flag) == flag); + } + + /// + /// checks whether any batch flag is set on the options + /// + /// options as specified by the user. + /// true if the given flag is set, otherwise false. + internal static bool IsBatch(SaveChangesOptions options) + { + return IsBatchWithSingleChangeset(options) || IsBatchWithIndependentOperations(options); + } + + /// + /// checks whether the batch flag is set on the options for the single changeset + /// + /// options as specified by the user. + /// true if the given flag is set, otherwise false. + internal static bool IsBatchWithSingleChangeset(SaveChangesOptions options) + { + if (Util.IsFlagSet(options, SaveChangesOptions.BatchWithSingleChangeset)) + { + Debug.Assert(!Util.IsFlagSet(options, SaveChangesOptions.BatchWithIndependentOperations), "!Util.IsFlagSet(options, SaveChangesOptions.BatchWithIndependentOperations)"); + return true; + } + + return false; + } + + /// + /// checks whether the batch flag with independent Operation per change set is set + /// + /// options as specified by the user. + /// true if the given flag is set, otherwise false. + internal static bool IsBatchWithIndependentOperations(SaveChangesOptions options) + { + if (Util.IsFlagSet(options, SaveChangesOptions.BatchWithIndependentOperations)) + { + Debug.Assert(!Util.IsFlagSet(options, SaveChangesOptions.BatchWithSingleChangeset), "!Util.IsFlagSet(options, SaveChangesOptions.BatchWithSingleChangeset)"); + return true; + } + + return false; + } + + /// modified or unchanged + /// state to test + /// true if modified or unchanged + internal static bool IncludeLinkState(EntityStates x) + { + return ((EntityStates.Modified == x) || (EntityStates.Unchanged == x)); + } + + #region Tracing + + /// + /// trace Element node + /// + /// XmlReader + /// TextWriter + [Conditional("TRACE")] + internal static void TraceElement(XmlReader reader, System.IO.TextWriter writer) + { + Debug.Assert(XmlNodeType.Element == reader.NodeType, "not positioned on Element"); + + if (null != writer) + { + writer.Write(Util.GetWhitespaceForTracing(2 + reader.Depth), 0, 2 + reader.Depth); + writer.Write("<{0}", reader.Name); + + if (reader.MoveToFirstAttribute()) + { + do + { + writer.Write(" {0}=\"{1}\"", reader.Name, reader.Value); + } + while (reader.MoveToNextAttribute()); + + reader.MoveToElement(); + } + + writer.Write(reader.IsEmptyElement ? " />" : ">"); + } + } + + /// + /// trace EndElement node + /// + /// XmlReader + /// TextWriter + /// indent or not + [Conditional("TRACE")] + internal static void TraceEndElement(XmlReader reader, System.IO.TextWriter writer, bool indent) + { + if (null != writer) + { + if (indent) + { + writer.Write(Util.GetWhitespaceForTracing(2 + reader.Depth), 0, 2 + reader.Depth); + } + + writer.Write("", reader.Name); + } + } + + /// + /// trace string value + /// + /// TextWriter + /// value + [Conditional("TRACE")] + internal static void TraceText(System.IO.TextWriter writer, string value) + { + if (null != writer) + { + writer.Write(value); + } + } + + #endregion + + /// + /// Converts the given IEnumerable into IEnumerable + /// + /// Type parameter. + /// IEnumerable which contains the list of the objects that needs to be converted. + /// Delegate to use to convert the value. + /// An instance of IEnumerable which contains the converted values. + internal static IEnumerable GetEnumerable(IEnumerable enumerable, Func valueConverter) + { + List list = new List(); + + // Note that foreach will call Dispose on the used IEnumerator in a finally block + foreach (object value in enumerable) + { + list.Add(valueConverter(value)); + } + + return list; + } + + /// Given a enumeration returns the instance with the same version number. + /// The protocol version enum value to convert. + /// The version instance with the version number for the specified protocol version. + internal static Version ToVersion(this ODataProtocolVersion protocolVersion) + { + switch (protocolVersion) + { + case ODataProtocolVersion.V4: + return ODataVersion4; + case ODataProtocolVersion.V401: + return ODataVersion401; + default: + Debug.Assert(false, "Did you add a new version?"); + return null; + } + } + + /// + /// Serialize the operation parameters and put them in a dictionary. + /// + /// The context. + /// The parameters. + /// A dictionary where the key is the parameter name, and the value is the serialized parameter value. + internal static Dictionary SerializeOperationParameters(this DataServiceContext context, UriOperationParameter[] parameters) + { + return parameters.ToDictionary(parameter => parameter.Name, parameter => Serializer.GetParameterValue(context, parameter)); + } + + /// + /// A workaround to a problem with FxCop which does not recognize the CheckArgumentNotNull method + /// as the one which validates the argument is not null. + /// + /// This has been suggested as a workaround in msdn forums by the VS team. Note that even though this is production code + /// the attribute has no effect on anything else. + private sealed class ValidatedNotNullAttribute : Attribute + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Utility.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Utility.cs new file mode 100644 index 0000000..ca93a48 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Utility.cs @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Reflection; + + /// + /// Helper class for T4 Template, provide uniform API for different platforms + /// + public static class Utility + { + /// + /// When overridden in a derived class, returns an array of custom attributes + /// applied to this member and identified by System.Type. + /// + /// + /// The type to query custom attributes on. + /// + /// + /// The type of attribute to search for. Only attributes that are assignable + /// to this type are returned. + /// + /// + /// true to search this member's inheritance chain to find the attributes; otherwise, + /// false. This parameter is ignored for properties and events; see Remarks. + /// + /// + /// An of custom attributes applied to this member, or an array with zero + /// elements if no attributes assignable to attributeType have been applied. + /// + public static IEnumerable GetCustomAttributes(Type type, Type attributeType, bool inherit) + { +#if PORTABLELIB + return type.GetTypeInfo().GetCustomAttributes(attributeType, inherit); +#else + return type.GetCustomAttributes(attributeType, inherit); +#endif + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/WeakDictionary.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WeakDictionary.cs new file mode 100644 index 0000000..a94c28f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WeakDictionary.cs @@ -0,0 +1,410 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// A generic dictionary, which allows its keys to be garbage collected if there are no other references + /// to them than from the dictionary itself. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// If the key of a particular entry in the dictionary has been collected, then the key becomes + /// effectively unreachable. However, left-over WeakReference objects for the key will physically remain in + /// the dictionary until RemoveCollectedEntries is called. + /// + internal class WeakDictionary + where TKey : class + { + /// + /// Internal Dictionary which actually stores the key and value. + /// + private Dictionary dictionary; + + /// + /// The comparer used to determine equality of keys for the internal dictionary. + /// + private WeakKeyComparer comparer; + + /// + /// The interval for calling RemoveCollectedEntries to refresh the internal dictionary. + /// + private int intervalForRefresh; + + /// + /// The count limit for calling RemoveCollectedEntries to refresh the internal dictionary. + /// + private int countLimitForRefresh; + + /// + /// Count on how many entries has been added. + /// + private int countForRefresh = 0; + + /// + /// The constructor with a specified comparer. + /// + /// + /// The IEqualityComparer used to determine equals of object of . + /// + public WeakDictionary(IEqualityComparer comparer) + : this(comparer, 1000) + { + } + + /// + /// The constructor with a specified comparer and a referesh interval. + /// + /// + /// The IEqualityComparer used to determine equals of object of . + /// + /// + /// The interval for calling RemoveCollectedEntries to refresh the dictionary. + /// + public WeakDictionary(IEqualityComparer comparer, int refreshInterval) + { + this.comparer = comparer as WeakKeyComparer; + if (this.comparer == null) + { + this.comparer = new WeakKeyComparer(comparer); + } + + this.dictionary = new Dictionary(this.comparer); + this.intervalForRefresh = refreshInterval; + this.countLimitForRefresh = refreshInterval; + } + + /// + /// Defines how the keys are stored in this dictionary. + /// + public Func CreateWeakKey { get; set; } + + /// + /// Get the list of rules that define whether an entry can be removed. + /// + public List> RemoveCollectedEntriesRules { get; set; } + + /// + /// Get the count of entries in this dictionary. + /// + /// + /// The count returned here may include entries for which the key has already been garbage collected. + /// Call RemoveCollectedEntries to weed out collected entries and update the count accordingly. + /// + public int Count + { + get { return this.dictionary.Count; } + } + + /// + /// Gets or sets the value associated with the specified key. + /// + /// The key of the value to get or set. + /// The value associated with the specified key. + public TValue this[TKey key] + { + get + { + return this.dictionary[key]; + } + + set + { + this.dictionary[key] = value; + } + } + + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. + public void Add(TKey key, TValue value) + { + if (key == null) + { + throw new ArgumentNullException("key"); + } + + if (countForRefresh >= countLimitForRefresh) + { + RemoveCollectedEntries(); + } + + if (this.CreateWeakKey != null) + { + // Customize the key. + this.dictionary.Add(CreateWeakKey(key), value); + } + else + { + this.dictionary.Add(new WeakKeyReference(key, this.comparer), value); + } + + countForRefresh++; + } + + /// + /// Determines whether the dictionary contains the specified key. + /// + /// The key to locate in the dictionary. + /// true if the dictionary contains an element with the specified key; otherwise, false. + /// + public bool ContainsKey(TKey key) + { + return this.dictionary.ContainsKey(key); + } + + /// + /// Removes the value with the specified key from the dictionary. + /// + /// The key of the element to remove. + /// true if the element is successfully found and removed; + /// otherwise, false if key is not found in the dictionary. + public bool Remove(TKey key) + { + var removed = this.dictionary.Remove(key); + if (removed) + { + countForRefresh--; + } + + return removed; + } + + /// + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// When this method returns, contains the value associated with the specified + /// key, if the key is found; otherwise, the default value for the type of the value parameter. + /// + /// true if the dictionary contains an element with the specified key; otherwise, false. + public bool TryGetValue(TKey key, out TValue value) + { + return this.dictionary.TryGetValue(key, out value); + } + + /// + /// Removes the left-over weak references for entries in the dictionary whose key has already been + /// reclaimed by the garbage collector. This will reduce the dictionary's Count by the number of + /// dead key-value pairs that were eliminated. + /// + public void RemoveCollectedEntries() + { + List entriesToBeRemoved = new List(); + foreach (var key in this.dictionary.Keys) + { + WeakKeyReference weakKey = key as WeakKeyReference; + if (weakKey != null) + { + if (!weakKey.IsAlive) + { + entriesToBeRemoved.Add(key); + } + } + else if (this.RemoveCollectedEntriesRules.Any(f => f(key))) + { + entriesToBeRemoved.Add(key); + } + } + + foreach (var key in entriesToBeRemoved) + { + if (this.dictionary.Remove(key)) + { + this.countForRefresh--; + } + } + + // After a clean, we will let the dict to clean itself after adding the next intervalForRefresh(1000) items. + this.countLimitForRefresh = this.countForRefresh + intervalForRefresh; + } + } + + /// + /// A weak reference for an object of , which can be used as key in a dictionary. + /// + /// The type of the keys in the dictionary. + internal sealed class WeakKeyReference : WeakReference + where T : class + { + public WeakKeyReference(T target, WeakKeyComparer comparer) + : base(target, false) + { + if (target == null) + { + throw new ArgumentNullException("target"); + } + + // retain the object's hash code immediately so that even + // if the target is GC'ed we will be able to find and + // remove the dead weak reference. + this.HashCode = comparer.GetHashCode(target); + } + + /// + /// The hash code of the target. + /// + public int HashCode { get; private set; } + + /// + /// Gets the object (the target) referenced by the current weak reference object. + /// + public new T Target + { + get { return (T)base.Target; } + } + } + + /// + /// Defines methods to support the comparison of WeakKeyReferences for equality. + /// + /// The type of the object to be compared. + /// + /// Compares objects of the given type or WeakKeyReferences to them for equality based on the given comparer. Note that we can only + /// implement for T = object as there is no other common base between T and . We need a + /// single comparer to handle both types because we don't want to allocate a new weak reference for every lookup. + /// + internal class WeakKeyComparer : IEqualityComparer + where T : class + { + /// + /// Internal comparer used to determine the equal for objects. + /// + protected IEqualityComparer comparer; + + /// + /// The default comparer. + /// + private static WeakKeyComparer defaultInstance; + + /// + /// Constructor + /// + /// + /// Internal comparer used to determine the equal for objects. + /// + public WeakKeyComparer(IEqualityComparer comparer) + { + if (comparer == null) + { + comparer = EqualityComparer.Default; + } + + this.comparer = comparer; + } + + /// + /// Get the default comparer. + /// + public static WeakKeyComparer Default + { + get + { + if (defaultInstance == null) + { + defaultInstance = new WeakKeyComparer(null); + } + + return defaultInstance; + } + } + + /// + /// Get the hash code for the specified object. + /// + /// The object for which a hash code is to be returned. + /// + /// A hash code for the specified object. + /// + public virtual int GetHashCode(object obj) + { + WeakKeyReference weakKey = obj as WeakKeyReference; + if (weakKey != null) + { + return weakKey.HashCode; + } + + return this.comparer.GetHashCode((T)obj); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the specified objects are equal; otherwise, false. + /// + /// Note: There are actually 9 cases to handle here. + /// + /// Let Wa = Alive Weak Reference + /// Let Wd = Dead Weak Reference + /// Let S = Strong Reference + /// + /// x | y | Equals(x,y) + /// ------------------------------------------------- + /// Wa | Wa | comparer.Equals(x.Target, y.Target) + /// Wa | Wd | false + /// Wa | S | comparer.Equals(x.Target, y) + /// Wd | Wa | false + /// Wd | Wd | x == y + /// Wd | S | false + /// S | Wa | comparer.Equals(x, y.Target) + /// S | Wd | false + /// S | S | comparer.Equals(x, y) + /// ------------------------------------------------- + /// + public virtual new bool Equals(object obj1, object obj2) + { + bool obj1IsDead, obj2IsDead; + T first = GetTarget(obj1, out obj1IsDead); + T second = GetTarget(obj2, out obj2IsDead); + + if (obj1IsDead) + { + return obj2IsDead ? obj1 == obj2 : false; + } + + if (obj2IsDead) + { + return false; + } + + return this.comparer.Equals(first, second); + } + + /// + /// Gets the target of the input object if it is a , else return it self. + /// + /// The input object from which to get the target. + /// Indicate whether the object is dead if it is a . + /// The target of the input object. + protected virtual T GetTarget(object obj, out bool isDead) + { + T target; + + WeakKeyReference wref = obj as WeakKeyReference; + if (wref != null) + { + target = wref.Target; + isDead = !wref.IsAlive; + } + else + { + target = (T)obj; + isDead = false; + } + + return target; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/WebUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WebUtil.cs new file mode 100644 index 0000000..bfed569 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WebUtil.cs @@ -0,0 +1,486 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Runtime.CompilerServices; + using Microsoft.OData; + using Microsoft.OData.Client.Metadata; + + /// web utility functions + internal static partial class WebUtil + { + /// + /// Default buffer size used for stream copy. + /// + internal const int DefaultBufferSizeForStreamCopy = 64 * 1024; + + /// + /// Whether DataServiceCollection<> type is available. + /// + private static bool? dataServiceCollectionAvailable = null; + + /// Method info for GetDefaultValue<T>. +#if PORTABLELIB + private static MethodInfo getDefaultValueMethodInfo = typeof(WebUtil).GetMethodWithGenericArgs("GetDefaultValue", false /*isPublic*/, true /*isStatic*/, 1 /*genericArgCount*/); +#else + private static MethodInfo getDefaultValueMethodInfo = (MethodInfo)typeof(WebUtil).GetMember("GetDefaultValue", BindingFlags.NonPublic | BindingFlags.Static).Single(m => ((MethodInfo)m).GetGenericArguments().Count() == 1); +#endif + + /// + /// Returns true if DataServiceCollection<> type is available or false otherwise. + /// + private static bool DataServiceCollectionAvailable + { + get + { + if (dataServiceCollectionAvailable == null) + { + try + { + dataServiceCollectionAvailable = GetDataServiceCollectionOfTType() != null; + } + catch (FileNotFoundException) + { + // the assembly or one of its dependencies (read: WindowsBase.dll) was not found. DataServiceCollection is not available. + dataServiceCollectionAvailable = false; + } + } + + Debug.Assert(dataServiceCollectionAvailable != null, "observableCollectionOfTAvailable must not be null here."); + + return (bool)dataServiceCollectionAvailable; + } + } + + /// copy from one stream to another + /// input stream + /// output stream + /// reusable buffer + /// count of copied bytes + internal static long CopyStream(Stream input, Stream output, ref byte[] refBuffer) + { + Debug.Assert(null != input, "null input stream"); + Debug.Assert(null != output, "null output stream"); + + long total = 0; + byte[] buffer = refBuffer; + if (null == buffer) + { + refBuffer = buffer = new byte[1000]; + } + + int count = 0; + while (input.CanRead && (0 < (count = input.Read(buffer, 0, buffer.Length)))) + { + output.Write(buffer, 0, count); + total += count; + } + + return total; + } + + /// get response object from possible WebException + /// exception to probe + /// http web respose object from exception + /// an instance of InvalidOperationException. + internal static InvalidOperationException GetHttpWebResponse(InvalidOperationException exception, ref IODataResponseMessage response) + { + if (null == response) + { + DataServiceTransportException webexception = (exception as DataServiceTransportException); + if (null != webexception) + { + response = webexception.Response; + return (InvalidOperationException)webexception.InnerException; + } + } + + return exception; + } + + /// is this a success status code + /// status code + /// true if status is between 200-299 + internal static bool SuccessStatusCode(System.Net.HttpStatusCode status) + { + return (200 <= (int)status && (int)status < 300); + } + + /// + /// Checks if the provided type is a collection type (i.e. it implements ICollection and the collection item type is not an entity). + /// + /// Type being checked. + /// The client model. + /// True if the CLR type is a collection compatible type. False otherwise. + internal static bool IsCLRTypeCollection(Type type, ClientEdmModel model) + { + // char[] and byte[] implements ICollection<> but we should not threat them as collections since they are primitive types for us. + if (!PrimitiveType.IsKnownNullableType(type)) + { + Type collectionType = ClientTypeUtil.GetImplementationType(type, typeof(ICollection<>)); + if (collectionType != null) + { + // collectionType is ICollection so we know that the first generic parameter + // is the collection item type + if (!ClientTypeUtil.TypeIsEntity(collectionType.GetGenericArguments()[0], model)) + { + return true; + } + } + } + + return false; + } + + /// + /// Checks if the provided type name is a name of a collection type. + /// + /// Type name read from the payload. + /// true if is a name of a collection type otherwise false. + internal static bool IsWireTypeCollection(string wireTypeName) + { + return CommonUtil.GetCollectionItemTypeName(wireTypeName, false) != null; + } + + /// + /// Returns collection item type name or null if the provided type name is not a collection. + /// + /// Collection type name read from payload. + /// Collection item type name or null if not a collection. + internal static string GetCollectionItemWireTypeName(string wireTypeName) + { + return CommonUtil.GetCollectionItemTypeName(wireTypeName, false); + } + + /// + /// Resolves and creates if necessary a backing type for the . + /// + /// Type of a collection property as defined by the user - can be just an interface or generic type. + /// Resolved concrete type that can be instantiated and will back the collection property. Can be the type. + internal static Type GetBackingTypeForCollectionProperty(Type collectionPropertyType) + { + Debug.Assert(collectionPropertyType != null, "collectionPropertyType != null"); + Debug.Assert(ClientTypeUtil.GetImplementationType(collectionPropertyType, typeof(ICollection<>)) != null, "The type backing a collection has to implement ICollection<> interface."); + + Type collectionBackingType = null; + + // If the user's type is an interface we are using collectionItemType we have just resolved as the generic element type of the ICollection instance (i.e. T). + // Otherwise we can use directly the type defined by the user. + // Note that we don't check here if the type we created can be assigned to the user's type. This should be done by the caller (if requested) + if (collectionPropertyType.IsInterface()) + { + collectionBackingType = typeof(ObservableCollection<>).MakeGenericType(collectionPropertyType.GetGenericArguments()[0]); + } + else + { + collectionBackingType = collectionPropertyType; + } + + Debug.Assert(collectionBackingType != null, "The backing type for collection property must not be null at this point."); + + return collectionBackingType; + } + + /// + /// Checks the argument value for null and throw ArgumentNullException if it is null + /// + /// type of the argument + /// argument whose value needs to be checked + /// name of the argument + /// returns the argument back + internal static T CheckArgumentNull([ValidatedNotNull] T value, string parameterName) where T : class + { + if (null == value) + { + throw Error.ArgumentNull(parameterName); + } + + return value; + } + + /// + /// Checks if the collection is valid. Throws if the collection is not valid. + /// + /// The type of the collection item. Can not be null. + /// Collection instance to be validated. + /// The name of the property being serialized (for exception messages). Can be null if the type is not a property. + /// Whether this collection property is a dynamic property + internal static void ValidateCollection(Type collectionItemType, object propertyValue, string propertyName, bool isDynamicProperty) + { + Debug.Assert(collectionItemType != null, "collectionItemType != null"); + + // nested collections are not supported. Need to exclude primitve types - e.g. string implements IEnumerable + if (!PrimitiveType.IsKnownNullableType(collectionItemType) && + collectionItemType.GetInterfaces().SingleOrDefault(t => t == typeof(IEnumerable)) != null) + { + throw Error.InvalidOperation(Strings.ClientType_CollectionOfCollectionNotSupported); + } + + if (propertyValue == null) + { + if (propertyName != null) + { + if (!isDynamicProperty) + { + throw Error.InvalidOperation(Strings.Collection_NullCollectionNotSupported(propertyName)); + } + } + else + { + throw Error.InvalidOperation(Strings.Collection_NullNonPropertyCollectionNotSupported(collectionItemType)); + } + } + } + + /// + /// Checks if the value of a primitive collection item is valid. Throws if it finds the value invalid. + /// + /// The value of the collection item. + /// The name of the collection property being serialized. Can be null. + /// The type of the collection item as declared by the collection. + internal static void ValidatePrimitiveCollectionItem(object itemValue, string propertyName, Type collectionItemType) + { + Debug.Assert(itemValue != null, "itemValue != null. The ValidateDataServiceCollectionItem needs to be called first on each item."); + Debug.Assert(collectionItemType != null, "collectionItemType != null"); + Debug.Assert(PrimitiveType.IsKnownNullableType(collectionItemType), "This method should only be called for primitive types."); + + Type itemValueType = itemValue.GetType(); + + if (!PrimitiveType.IsKnownNullableType(itemValueType)) + { + throw Error.InvalidOperation(Strings.Collection_CollectionTypesInCollectionOfPrimitiveTypesNotAllowed); + } + + if (!collectionItemType.IsAssignableFrom(itemValueType)) + { + if (propertyName != null) + { + throw Error.InvalidOperation(Strings.WebUtil_TypeMismatchInCollection(propertyName)); + } + else + { + throw Error.InvalidOperation(Strings.WebUtil_TypeMismatchInNonPropertyCollection(collectionItemType)); + } + } + } + + /// + /// Checks if the value of a complex collection item is valid. Throws if it finds the value invalid. + /// + /// The value of the collection item. + /// The name of the collection property being serialized. Can be null if the type is not a property. + /// The type of the collection item as declared by the collection. + internal static void ValidateComplexCollectionItem(object itemValue, string propertyName, Type collectionItemType) + { + Debug.Assert(itemValue != null, "itemValue != null. The ValidateDataServiceCollectionItem needs to be called first on each item."); + Debug.Assert(collectionItemType != null, "collectionItemType != null"); + Debug.Assert(!PrimitiveType.IsKnownNullableType(collectionItemType), "This method should only be called for complex types."); + + Type itemValueType = itemValue.GetType(); + + if (PrimitiveType.IsKnownNullableType(itemValueType)) + { + throw Error.InvalidOperation(Strings.Collection_PrimitiveTypesInCollectionOfComplexTypesNotAllowed); + } + + if (!collectionItemType.IsAssignableFrom(itemValueType)) + { + if (propertyName != null) + { + throw Error.InvalidOperation(Strings.WebUtil_TypeMismatchInCollection(propertyName)); + } + else + { + throw Error.InvalidOperation(Strings.WebUtil_TypeMismatchInNonPropertyCollection(collectionItemType)); + } + } + } + + /// + /// Validates the value of the identity, the atom:id or OData-EntityId + /// + /// The value to validate + /// an absolute Uri + internal static Uri ValidateIdentityValue(string identityValue) + { + Uri identity; + + try + { + // here we could just assign idText to Identity + // however we used to check for AbsoluteUri, thus we need to + // convert string to Uri and check for absoluteness + identity = UriUtil.CreateUri(identityValue, UriKind.Absolute); + } + catch (FormatException) + { + throw Error.InvalidOperation(Strings.Context_TrackingExpectsAbsoluteUri); + } + + return identity; + } + + /// + /// Validates the value of the 'Location' response header. + /// + /// the value as seen on the wire. + /// an absolute Uri + internal static Uri ValidateLocationHeader(string location) + { + // We used to call the Uri constructor with the kind set to Absolute. + // Hence now checking for the absoluteness. + Uri locationUri = UriUtil.CreateUri(location, UriKind.RelativeOrAbsolute); + if (!locationUri.IsAbsoluteUri) + { + throw Error.InvalidOperation(Strings.Context_LocationHeaderExpectsAbsoluteUri); + } + + return locationUri; + } + + /// + /// Determines the value of the Prefer header based on the response preference settings. Also modifies the request version as necessary. + /// + /// The response preference setting for the request. + /// The request version so far, this might be modified (raised) if necessary due to response preference being applied. + /// The value of the Prefer header to apply to the request. + internal static string GetPreferHeaderAndRequestVersion(DataServiceResponsePreference responsePreference, ref Version requestVersion) + { + string preferHeaderValue = null; + + if (responsePreference != DataServiceResponsePreference.None) + { + if (responsePreference == DataServiceResponsePreference.IncludeContent) + { + preferHeaderValue = XmlConstants.HttpPreferReturnContent; + } + else + { + Debug.Assert(responsePreference == DataServiceResponsePreference.NoContent, "Invalid value for DataServiceResponsePreference."); + preferHeaderValue = XmlConstants.HttpPreferReturnNoContent; + } + + RaiseVersion(ref requestVersion, Util.ODataVersion4); + } + + return preferHeaderValue; + } + + /// + /// Raises the version specified to the new minimal version (if it's lower than the current one) + /// + /// The version to be raised. + /// The minimal version needed. + internal static void RaiseVersion(ref Version version, Version minimalVersion) + { + if (version == null || version < minimalVersion) + { + version = minimalVersion; + } + } + + /// + /// Checks if the given type is DataServiceCollection<> type. + /// + /// Type to be checked. + /// true if the provided type is DataServiceCollection<> or false otherwise. + internal static bool IsDataServiceCollectionType(Type t) + { + if (DataServiceCollectionAvailable) + { + return t == GetDataServiceCollectionOfTType(); + } + + return false; + } + + /// + /// Creates an instance of DataServiceCollection<> class using provided types. + /// + /// Types to be used for creating DataServiceCollection<> object. + /// + /// Instance of DataServiceCollection<> class created using provided types or null if DataServiceCollection<> + /// type is not avaiable. + /// + internal static Type GetDataServiceCollectionOfT(params Type[] typeArguments) + { + if (DataServiceCollectionAvailable) + { + Debug.Assert( + GetDataServiceCollectionOfTType() != null, + "DataServiceCollection is available so GetDataServiceCollectionOfTType() must not return null."); + + return GetDataServiceCollectionOfTType().MakeGenericType(typeArguments); + } + + return null; + } + + /// + /// Returns the default value for the given type + /// + /// type to get the default value + /// returns the default value for . + internal static object GetDefaultValue(Type type) + { + Debug.Assert(getDefaultValueMethodInfo != null, "WebUtil.getDefaultValueMethodInfo != null"); + return getDefaultValueMethodInfo.MakeGenericMethod(type).Invoke(null, null); + } + + /// + /// Returns the default value for the given type + /// + /// type to get the default value + /// returns the default value for . + internal static T GetDefaultValue() + { + return default(T); + } + + /// + /// Dispose the message if it implements IDisposable. + /// + /// IODataResponseMessage to dispose. + internal static void DisposeMessage(IODataResponseMessage responseMessage) + { + IDisposable disposableMessage = responseMessage as IDisposable; + if (disposableMessage != null) + { + disposableMessage.Dispose(); + } + } + + /// + /// Forces loading WindowsBase assembly. If WindowsBase assembly is not present JITter will throw an exception. + /// This method MUST NOT be inlined otherwise we won't be able to catch the exception by JITter in the caller. + /// + /// typeof(DataServiceCollection<>) + [MethodImpl(MethodImplOptions.NoInlining)] + private static Type GetDataServiceCollectionOfTType() + { + return typeof(DataServiceCollection<>); + } + + /// + /// A workaround to a problem with FxCop which does not recognize the CheckArgumentNotNull method + /// as the one which validates the argument is not null. + /// + /// This has been suggested as a workaround in msdn forums by the VS team. Note that even though this is production code + /// the attribute has no effect on anything else. + private sealed class ValidatedNotNullAttribute : Attribute + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataItemWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataItemWrapper.cs new file mode 100644 index 0000000..f66597d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataItemWrapper.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData; + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#elif ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.Test.OData +#endif +{ +#if ODATA_SERVICE + internal abstract class ODataItemWrapper +#elif ODATA_CLIENT + internal abstract class ODataItemWrapper +#else + public abstract class ODataItemWrapper +#endif + { + public abstract ODataItem Item { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataNestedResourceInfoWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataNestedResourceInfoWrapper.cs new file mode 100644 index 0000000..bb0a84d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataNestedResourceInfoWrapper.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData; + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#elif ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.Test.OData +#endif +{ +#if ODATA_SERVICE + internal class ODataNestedResourceInfoWrapper : ODataItemWrapper +#elif ODATA_CLIENT + internal class ODataNestedResourceInfoWrapper : ODataItemWrapper +#else + public class ODataNestedResourceInfoWrapper : ODataItemWrapper +#endif + { + public ODataNestedResourceInfo NestedResourceInfo { get; set; } + + public ODataItemWrapper NestedResourceOrResourceSet { get; set; } + + public override ODataItem Item + { + get { return this.NestedResourceInfo; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceSetWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceSetWrapper.cs new file mode 100644 index 0000000..34421d9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceSetWrapper.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData; + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#elif ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.Test.OData +#endif +{ +#if ODATA_SERVICE + internal class ODataResourceSetWrapper : ODataItemWrapper +#elif ODATA_CLIENT + internal class ODataResourceSetWrapper : ODataItemWrapper +#else + public class ODataResourceSetWrapper : ODataItemWrapper +#endif + { + public ODataResourceSet ResourceSet { get; set; } + + public IEnumerable Resources { get; set; } + + public override ODataItem Item + { + get { return this.ResourceSet; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceWrapper.cs new file mode 100644 index 0000000..ac74553 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataResourceWrapper.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData; + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#elif ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.Test.OData +#endif +{ +#if ODATA_SERVICE + internal class ODataResourceWrapper : ODataItemWrapper +#elif ODATA_CLIENT + internal class ODataResourceWrapper : ODataItemWrapper +#else + public class ODataResourceWrapper : ODataItemWrapper +#endif + { + public ODataResource Resource { get; set; } + + public IEnumerable NestedResourceInfoWrappers { get; set; } + + public override ODataItem Item + { + get { return Resource; } + } + + public object Instance { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataWriterHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataWriterHelper.cs new file mode 100644 index 0000000..9f79282 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/Wrappers/ODataWriterHelper.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData; + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#elif ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.Test.OData +#endif +{ +#if ODATA_SERVICE + internal static class ODataWriterHelper +#elif ODATA_CLIENT + internal static class ODataWriterHelper +#else + public static class ODataWriterHelper +#endif + { + public static void WriteResourceSet(ODataWriter writer, ODataResourceSetWrapper resourceSetWrapper) + { + writer.WriteStart(resourceSetWrapper.ResourceSet); + + if (resourceSetWrapper.Resources != null) + { + foreach (var resourceWrapper in resourceSetWrapper.Resources) + { + WriteResource(writer, resourceWrapper); + } + } + + writer.WriteEnd(); + } + + public static void WriteResource(ODataWriter writer, ODataResourceWrapper resourceWrapper) + { + writer.WriteStart(resourceWrapper.Resource); + + if (resourceWrapper.NestedResourceInfoWrappers != null) + { + foreach (var nestedResourceInfoWrapper in resourceWrapper.NestedResourceInfoWrappers) + { + WriteNestedResourceInfo(writer, nestedResourceInfoWrapper); + } + } + + writer.WriteEnd(); + } + + public static void WriteNestedResourceInfo(ODataWriter writer, ODataNestedResourceInfoWrapper nestedResourceInfo) + { + writer.WriteStart(nestedResourceInfo.NestedResourceInfo); + + if (nestedResourceInfo.NestedResourceOrResourceSet != null) + { + WriteItem(writer, nestedResourceInfo.NestedResourceOrResourceSet); + } + + writer.WriteEnd(); + } + + private static void WriteItem(ODataWriter writer, ODataItemWrapper odataItemWrapper) + { + var odataResourceWrapper = odataItemWrapper as ODataResourceWrapper; + if (odataResourceWrapper != null) + { + WriteResource(writer, odataResourceWrapper); + } + + var odataResourceSetWrapper = odataItemWrapper as ODataResourceSetWrapper; + if (odataResourceSetWrapper != null) + { + WriteResourceSet(writer, odataResourceSetWrapper); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntityReferenceLinkArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntityReferenceLinkArgs.cs new file mode 100644 index 0000000..2f8957f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntityReferenceLinkArgs.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// The writing entity reference link arguments + /// + public sealed class WritingEntityReferenceLinkArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The entity reference link. + /// The source. + /// The target. + public WritingEntityReferenceLinkArgs(ODataEntityReferenceLink entityReferenceLink, object source, object target) + { + Util.CheckArgumentNull(entityReferenceLink, "entityReferenceLink"); + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNull(target, "target"); + this.EntityReferenceLink = entityReferenceLink; + this.Source = source; + this.Target = target; + } + + /// + /// Gets the feed. + /// + /// + /// The feed. + /// + public ODataEntityReferenceLink EntityReferenceLink { get; private set; } + + /// + /// Gets the source. + /// + public object Source { get; private set; } + + /// + /// Gets the target. + /// + public object Target { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntryArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntryArgs.cs new file mode 100644 index 0000000..ab30fd9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingEntryArgs.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// Writing entry arguments + /// + public sealed class WritingEntryArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The entry. + /// The entity. + public WritingEntryArgs(ODataResource entry, object entity) + { + Util.CheckArgumentNull(entry, "entry"); + Util.CheckArgumentNull(entity, "entity"); + this.Entry = entry; + this.Entity = entity; + } + + /// + /// Gets the entry. + /// + /// + /// The entry. + /// + public ODataResource Entry { get; private set; } + + /// + /// Gets the entity. + /// + public object Entity { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingNestedResourceInfoArgs.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingNestedResourceInfoArgs.cs new file mode 100644 index 0000000..e1b4f0a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/WritingNestedResourceInfoArgs.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Client +{ + using Microsoft.OData; + + /// + /// Writing navigation link arguments + /// + public sealed class WritingNestedResourceInfoArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The link. + /// The source. + /// The target. + public WritingNestedResourceInfoArgs(ODataNestedResourceInfo link, object source, object target) + { + Util.CheckArgumentNull(link, "link"); + Util.CheckArgumentNull(source, "source"); + Util.CheckArgumentNull(target, "target"); + this.Link = link; + this.Source = source; + this.Target = target; + } + + /// + /// Gets the link. + /// + /// + /// The link. + /// + public ODataNestedResourceInfo Link { get; private set; } + + /// + /// Gets the source. + /// + public object Source { get; private set; } + + /// + /// Gets the target. + /// + public object Target { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Client/XmlConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Client/XmlConstants.cs new file mode 100644 index 0000000..ff10767 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Client/XmlConstants.cs @@ -0,0 +1,1081 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Service +#endif +{ + /// + /// Class that contains all the constants for various schemas. + /// + internal static class XmlConstants + { + #region CLR / Reflection constants. + + /// "InitializeService" method name for service initialize. + internal const string ClrServiceInitializationMethodName = "InitializeService"; + + #endregion CLR / Reflection constants. + + #region HTTP constants. + + /// id of the corresponding body + internal const string HttpContentID = "Content-ID"; + + /// byte-length of the corresponding body + internal const string HttpContentLength = "Content-Length"; + + /// mime-type of the corresponding body + internal const string HttpContentType = "Content-Type"; + + /// content disposition of the response (a hint how to handle the response) + internal const string HttpContentDisposition = "Content-Disposition"; + + /// 'OData-Version' - HTTP header name for OData version. + internal const string HttpODataVersion = "OData-Version"; + + /// 'OData-MaxVersion' - HTTP header name for maximum understood OData version. + internal const string HttpODataMaxVersion = "OData-MaxVersion"; + + /// + /// 'Prefer' - HTTP request header name for client preferences. + /// Refer to: http://tools.ietf.org/id/draft-snell-http-prefer-02.txt for details. + /// + internal const string HttpPrefer = "Prefer"; + +#if ODATA_CLIENT + /// Return no content Prefer header hint value. + internal const string HttpPreferReturnNoContent = "return=minimal"; + + /// Return content Prefer header hint value. + internal const string HttpPreferReturnContent = "return=representation"; +#endif + +#if !ODATA_CLIENT + /// + /// 'Preference-Applied' - HTTP response header name for client preference response. + /// + internal const string HttpPreferenceApplied = "Preference-Applied"; +#endif + + /// 'no-cache' - HTTP value for Cache-Control header. + internal const string HttpCacheControlNoCache = "no-cache"; + + /// 'charset' - HTTP parameter name. + internal const string HttpCharsetParameter = "charset"; + + /// HTTP method name for GET requests. + internal const string HttpMethodGet = "GET"; + + /// HTTP method name for POST requests. + internal const string HttpMethodPost = "POST"; + + /// Http Put Method name - basically used for updating resource. + internal const string HttpMethodPut = "PUT"; + + /// HTTP method name for delete requests. + internal const string HttpMethodDelete = "DELETE"; + + /// HTTP method name for PATCH requests. + internal const string HttpMethodPatch = "PATCH"; + + /// HTTP query string parameter value for expand. + internal const string HttpQueryStringExpand = "$expand"; + + /// HTTP query string parameter value for filtering. + internal const string HttpQueryStringFilter = "$filter"; + + /// HTTP query string parameter value for ordering. + internal const string HttpQueryStringOrderBy = "$orderby"; + + /// HTTP query string parameter value for skipping elements. + internal const string HttpQueryStringSkip = "$skip"; + + /// HTTP query string parameter value for limiting the number of elements. + internal const string HttpQueryStringTop = "$top"; + + /// HTTP query string parameter value for counting query result set, $count=true + internal const string HttpQueryStringQueryCount = "$count"; + + /// HTTP query string parameter value for skipping results based on paging. + internal const string HttpQueryStringSkipToken = "$skiptoken"; + + /// Property prefix for the skip token property in expanded results for a skip token + internal const string SkipTokenPropertyPrefix = "SkipTokenProperty"; + + /// HTTP query string parameter value for counting query result set + internal const string HttpQueryStringSegmentCount = "$count"; + + /// HTTP query string parameter value for projection. + internal const string HttpQueryStringSelect = "$select"; + + /// HTTP query string parameter for specifying the requested content-type of the response. + internal const string HttpQueryStringFormat = "$format"; + + /// HTTP query string parameter for specifying the a callback function name for JSONP (JSON padding). + internal const string HttpQueryStringCallback = "$callback"; + + /// HTTP query string parameter for specifying the a entity id. + internal const string HttpQueryStringId = "$id"; + + /// 'q' - HTTP q-value parameter name. + internal const string HttpQValueParameter = "q"; + + /// 'X-HTTP-Method' - HTTP header name for requests that want to tunnel a method through POST. + internal const string HttpXMethod = "X-HTTP-Method"; + + /// HTTP name for Accept header + internal const string HttpRequestAccept = "Accept"; + + /// HTTP name for Accept-Charset header + internal const string HttpRequestAcceptCharset = "Accept-Charset"; + + /// HTTP name for If-Match header + internal const string HttpRequestIfMatch = "If-Match"; + + /// HTTP name for If-None-Match header + internal const string HttpRequestIfNoneMatch = "If-None-Match"; + + /// HTTP name for User-Agent header + internal const string HttpUserAgent = "User-Agent"; + + /// multi-part keyword in content-type to identify batch separator + internal const string HttpMultipartBoundary = "boundary"; + + /// 'X-Content-Type-Options' - HTTP header for Internet Explorer 8 and 9 to specify options for content-type handling. + internal const string XContentTypeOptions = "X-Content-Type-Options"; + + /// An 'X-Content-Type-Options' HTTP header argument to instruct IE8/9 not to sniff the content and instead display it according to the content-type header. + internal const string XContentTypeOptionNoSniff = "nosniff"; +#if ODATA_CLIENT + /// multi-part mixed batch separator + internal const string HttpMultipartBoundaryBatch = "batch"; + + /// multi-part mixed changeset separator + internal const string HttpMultipartBoundaryChangeSet = "changeset"; +#endif + + /// 'Allow' - HTTP response header for allowed verbs. + internal const string HttpResponseAllow = "Allow"; + + /// HTTP name for Cache-Control header. + internal const string HttpResponseCacheControl = "Cache-Control"; + + /// HTTP name for ETag header + internal const string HttpResponseETag = "ETag"; + + /// HTTP name for location header + internal const string HttpResponseLocation = "Location"; + + /// HTTP name for OData-EntityId header + internal const string HttpODataEntityId = "OData-EntityId"; + + /// HTTP name for Status-Code header + internal const string HttpResponseStatusCode = "Status-Code"; + + /// multi-part mixed batch separator for response stream + internal const string HttpMultipartBoundaryBatchResponse = "batchresponse"; + + /// multi-part mixed changeset separator + internal const string HttpMultipartBoundaryChangesetResponse = "changesetresponse"; + + /// Content-Transfer-Encoding header for batch requests. + internal const string HttpContentTransferEncoding = "Content-Transfer-Encoding"; + + /// Http Version in batching requests and response. + internal const string HttpVersionInBatching = "HTTP/1.1"; + + /// To checks if the resource exists or not. + internal const string HttpAnyETag = "*"; + + /// Weak etags in HTTP must start with W/. + /// Look in http://www.ietf.org/rfc/rfc2616.txt?number=2616 section 14.19 for more information. + internal const string HttpWeakETagPrefix = "W/\""; + + /// The mime type that client wants the response to be in. + internal const string HttpAccept = "Accept"; + + /// The character set the client wants the response to be in. + internal const string HttpAcceptCharset = "Accept-Charset"; + + /// The name of the Cookie HTTP header + internal const string HttpCookie = "Cookie"; + + /// The Slug header name. Used by ATOM to hint the server on which MR is being POSTed. + internal const string HttpSlug = "Slug"; + + #endregion HTTP constants. + + #region MIME constants. + + /// MIME type for requesting any media type. + internal const string MimeAny = "*/*"; + + /// MIME type general binary bodies (http://www.iana.org/assignments/media-types/application/). + internal const string MimeApplicationOctetStream = "application/octet-stream"; +#if !ODATA_CLIENT + /// MIME type for ATOM bodies (http://www.iana.org/assignments/media-types/application/). + internal const string MimeApplicationAtom = "application/atom+xml"; + + /// MIME type for JSON bodies in light mode with minimal metadata. + internal const string MimeApplicationJsonODataMinimalMetadata = "application/json;odata.metadata=minimal"; + + /// MIME type for JSON bodies in light mode with full metadata. + internal const string MimeApplicationJsonODataFullMetadata = "application/json;odata.metadata=full"; + + /// MIME type for JSON bodies in light mode with no metadata. + internal const string MimeApplicationJsonODataNoMetadata = "application/json;odata.metadata=none"; + + /// MIME type for JSON bodies (implies light in V3, verbose otherwise) (http://www.iana.org/assignments/media-types/application/). + internal const string MimeApplicationJson = "application/json"; + + /// MIME type for batch requests - this mime type must be specified in CUD changesets or GET batch requests. + internal const string MimeApplicationHttp = "application/http"; + + /// MIME type for XML bodies. + internal const string MimeApplicationXml = "application/xml"; + + /// "application/xml", MIME type for metadata requests. + internal const string MimeMetadata = MimeApplicationXml; +#endif + /// 'application' - MIME type for application types. + internal const string MimeApplicationType = "application"; + + /// 'json' - constant for MIME JSON subtypes. + internal const string MimeJsonSubType = "json"; + + /// 'xml' - constant for MIME xml subtypes. + internal const string MimeXmlSubType = "xml"; + + /// 'odata' - parameter name for JSON MIME types. + internal const string MimeODataParameterName = "odata.metadata"; + + /// MIME type for changeset multipart/mixed + internal const string MimeMultiPartMixed = "multipart/mixed"; + + /// MIME type for plain text bodies. + internal const string MimeTextPlain = "text/plain"; + + /// 'text' - MIME type for text subtypes. + internal const string MimeTextType = "text"; + + /// MIME type for XML bodies (deprecated). + internal const string MimeTextXml = "text/xml"; + + /// Content-Transfer-Encoding value for batch requests. + internal const string BatchRequestContentTransferEncoding = "binary"; + +#if !ODATA_CLIENT || PORTABLELIB + /// text for the utf8 encoding + internal const string Utf8Encoding = "UTF-8"; +#endif +#if ODATA_CLIENT + /// Default encoding used for writing textual media link entries + internal const string MimeTypeUtf8Encoding = ";charset=UTF-8"; +#endif + #endregion MIME constants. + + #region URI constants. + + /// A prefix that turns an absolute-path URI into an absolute-URI. + internal const string UriHttpAbsolutePrefix = "http://host"; + + /// A segment name in a URI that indicates metadata is being requested. + internal const string UriMetadataSegment = "$metadata"; + + /// A segment name in a URI that indicates a plain primitive value is being requested. + internal const string UriValueSegment = "$value"; + + /// A segment name in a URI that indicates metadata is being requested. + internal const string UriBatchSegment = "$batch"; + + /// A segment name in a URI that indicates that this is a link operation. + internal const string UriLinkSegment = "$ref"; + + /// A segment name in a URI that indicates that this is a count operation. + internal const string UriCountSegment = "$count"; + + /// A const value for query parameter $count to set counting mode to true + internal const string UriCountTrueOption = "true"; + + /// A const value for query parameter $count to set counting mode to false + internal const string UriCountFalseOption = "false"; + + /// A segment name in a URI that indicates that this is a filter operation. + internal const string UriFilterSegment = "$filter"; + + /// Uri method name for Enumerable.Any(). + internal const string AnyMethodName = "any"; + + /// Uri method name for Enumerable.All(). + internal const string AllMethodName = "all"; + + /// Implicit parameter "it" used for Queryable.Where lambda. + internal const string ImplicitFilterParameter = "$it"; + + #endregion URI constants. + + #region WCF constants. + + /// "Binary" - WCF element name for binary content in XML-wrapping streams. + internal const string WcfBinaryElementName = "Binary"; + + #endregion WCF constants. + + #region ATOM constants + /// Schema Namespace prefix for atom. + internal const string AtomNamespacePrefix = "atom"; + + /// XML element name to mark content element in Atom. + internal const string AtomContentElementName = "content"; + + /// XML element name to mark entry element in Atom. + internal const string AtomEntryElementName = "entry"; + + /// XML element name to mark feed element in Atom. + internal const string AtomFeedElementName = "feed"; + + /// 'author' - XML element name for ATOM 'author' element for entries. + internal const string AtomAuthorElementName = "author"; + + /// 'contributor' - XML element name for ATOM 'author' element for entries. + internal const string AtomContributorElementName = "contributor"; + + /// 'category' - XML element name for ATOM 'category' element for entries. + internal const string AtomCategoryElementName = "category"; + + /// XML element name to mark link element in Atom. + internal const string AtomLinkElementName = "link"; + +#if ODATA_CLIENT + /// 'scheme' - XML attribute name for ATOM 'scheme' attribute for categories. + internal const string AtomCategorySchemeAttributeName = "scheme"; + + /// 'term' - XML attribute name for ATOM 'term' attribute for categories. + internal const string AtomCategoryTermAttributeName = "term"; + + /// XML element name to mark id element in Atom. + internal const string AtomIdElementName = "id"; + + /// XML element name to mark link relation attribute in Atom. + internal const string AtomLinkRelationAttributeName = "rel"; + + /// Atom attribute that indicates the actual location for an entry's content. + internal const string AtomContentSrcAttributeName = "src"; + + /// XML element string for "next" links: [atom:link rel="next"] + internal const string AtomLinkNextAttributeString = "next"; + +#endif + /// author/email + internal const string SyndAuthorEmail = "SyndicationAuthorEmail"; + + /// author/name + internal const string SyndAuthorName = "SyndicationAuthorName"; + + /// author/uri + internal const string SyndAuthorUri = "SyndicationAuthorUri"; + + /// published + internal const string SyndPublished = "SyndicationPublished"; + + /// rights + internal const string SyndRights = "SyndicationRights"; + + /// summary + internal const string SyndSummary = "SyndicationSummary"; + + /// title + internal const string SyndTitle = "SyndicationTitle"; + + /// 'updated' - XML element name for ATOM 'updated' element for entries. + internal const string AtomUpdatedElementName = "updated"; + + /// contributor/email + internal const string SyndContributorEmail = "SyndicationContributorEmail"; + + /// contributor/name + internal const string SyndContributorName = "SyndicationContributorName"; + + /// contributor/uri + internal const string SyndContributorUri = "SyndicationContributorUri"; + + /// updated + internal const string SyndUpdated = "SyndicationUpdated"; + + /// Plaintext + internal const string SyndContentKindPlaintext = "text"; + + /// HTML + internal const string SyndContentKindHtml = "html"; + + /// XHTML + internal const string SyndContentKindXHtml = "xhtml"; + + /// XML element name to mark href attribute element in Atom. + internal const string AtomHRefAttributeName = "href"; + + /// XML attribute name to mark the hreflang attribute in Atom. + internal const string AtomHRefLangAttributeName = "hreflang"; + + /// XML element name to mark summary element in Atom. + internal const string AtomSummaryElementName = "summary"; + + /// XML element name to mark author/name or contributor/name element in Atom. + internal const string AtomNameElementName = "name"; + + /// XML element name to mark author/email or contributor/email element in Atom. + internal const string AtomEmailElementName = "email"; + + /// XML element name to mark published element in Atom. + internal const string AtomPublishedElementName = "published"; + + /// XML element name to mark rights element in Atom. + internal const string AtomRightsElementName = "rights"; + + /// XML element name to mark 'collection' element in APP. + internal const string AtomPublishingCollectionElementName = "collection"; + + /// XML element name to mark 'service' element in APP. + internal const string AtomPublishingServiceElementName = "service"; + + /// XML value for a default workspace in APP. + internal const string AtomPublishingWorkspaceDefaultValue = "Default"; + + /// XML element name to mark 'workspace' element in APP. + internal const string AtomPublishingWorkspaceElementName = "workspace"; + + /// XML element name to mark title element in Atom. + internal const string AtomTitleElementName = "title"; + + /// XML attribute name to specify the type of the element. + internal const string AtomTypeAttributeName = "type"; + + /// Atom link relation attribute value for self links. + internal const string AtomSelfRelationAttributeValue = "self"; + + /// Atom link relation attribute value for edit links. + internal const string AtomEditRelationAttributeValue = "edit"; + + /// Atom link relation attribute value for edit-media links. + internal const string AtomEditMediaRelationAttributeValue = "edit-media"; + + /// Link relation: alternate - refers to a substitute for this context. + internal const string AtomAlternateRelationAttributeValue = "alternate"; + + /// Link relation: related - identifies a related resource. + internal const string AtomRelatedRelationAttributeValue = "related"; + + /// Link relation: enclosure - identifies a related resource that is potentially large and might require special handling. + internal const string AtomEnclosureRelationAttributeValue = "enclosure"; + + /// Link relation: via - identifies a resource that is the source of the information in the link's context. + internal const string AtomViaRelationAttributeValue = "via"; + + /// Link relation: describedby - refers to a resource providing information about the link's context. + internal const string AtomDescribedByRelationAttributeValue = "describedby"; + + /// Link relation: service - indicates a URI that can be used to retrieve a service document. + internal const string AtomServiceRelationAttributeValue = "service"; + + /// Atom attribute which indicates the null value for the element. + internal const string AtomNullAttributeName = "null"; + + /// Atom attribute which indicates the etag value for the declaring entry element. + internal const string AtomETagAttributeName = "etag"; + + /// 'Inline' - wrapping element for inlined entry/feed content. + internal const string AtomInlineElementName = "inline"; + + /// Element containing property values when 'content' is used for media link entries + internal const string AtomPropertiesElementName = "properties"; + + /// 'count' element + internal const string RowCountElement = "count"; + + #endregion ATOM constants + + #region XML constants. + + /// 'element', the XML element name for items in enumerations. + internal const string XmlCollectionItemElementName = "element"; + + /// XML element name for an error. + internal const string XmlErrorElementName = "error"; + + /// XML element name for an error code. + internal const string XmlErrorCodeElementName = "code"; + + /// XML element name for the inner error details. + internal const string XmlErrorInnerElementName = "innererror"; + + /// XML element name for an internal exception. + internal const string XmlErrorInternalExceptionElementName = "internalexception"; + + /// XML element name for an exception type. + internal const string XmlErrorTypeElementName = "type"; + + /// XML element name for an exception stack trace. + internal const string XmlErrorStackTraceElementName = "stacktrace"; + + /// XML element name for an error message. + internal const string XmlErrorMessageElementName = "message"; + + /// 'false' literal, as used in XML. + internal const string XmlFalseLiteral = "false"; + + /// 'true' literal, as used in XML. + internal const string XmlTrueLiteral = "true"; + + /// XML attribute value to indicate the base URI for a document or element. + internal const string XmlBaseAttributeName = "base"; + + /// XML attribute name for whitespace parsing control. + internal const string XmlSpaceAttributeName = "space"; + + /// XML attribute value to indicate whitespace should be preserved. + internal const string XmlSpacePreserveValue = "preserve"; + + /// XML attribute name to pass to the XMLReader.GetValue API to get the xml:base attribute value. + internal const string XmlBaseAttributeNameWithPrefix = "xml:base"; + + #endregion XML constants. + + #region XML namespaces. + + /// Schema Namespace For Edm Oasis. + internal const string EdmOasisNamespace = "http://docs.oasis-open.org/odata/ns/edm"; + + /// XML namespace for data services. + internal const string DataWebNamespace = "http://docs.oasis-open.org/odata/ns/data"; + + /// XML namespace for data service annotations. + internal const string DataWebMetadataNamespace = "http://docs.oasis-open.org/odata/ns/metadata"; + + /// XML namespace for data service links. + internal const string DataWebRelatedNamespace = "http://docs.oasis-open.org/odata/ns/related/"; + + /// XML namespace for data service related $ref. + internal const string DataWebRelatedLinkNamespace = "http://docs.oasis-open.org/odata/ns/relatedlinks/"; + + /// XML namespace for data service named media resources. + internal const string DataWebMediaResourceNamespace = "http://docs.oasis-open.org/odata/ns/mediaresource/"; + + /// XML namespace for data service edit-media link for named media resources. + internal const string DataWebMediaResourceEditNamespace = "http://docs.oasis-open.org/odata/ns/edit-media/"; + + /// ATOM Scheme Namespace For DataWeb. + internal const string DataWebSchemeNamespace = "http://docs.oasis-open.org/odata/ns/scheme"; + + /// Schema Namespace for Atom Publishing Protocol. + internal const string AppNamespace = "http://www.w3.org/2007/app"; + + /// Schema Namespace For Atom. + internal const string AtomNamespace = "http://www.w3.org/2005/Atom"; + + /// Schema Namespace prefix For xmlns. + internal const string XmlnsNamespacePrefix = "xmlns"; + + /// Schema Namespace prefix For xml. + internal const string XmlNamespacePrefix = "xml"; + + /// Schema Namespace Prefix For DataWeb. + internal const string DataWebNamespacePrefix = "d"; + + /// 'adsm' - namespace prefix for DataWebMetadataNamespace. + internal const string DataWebMetadataNamespacePrefix = "m"; + + /// 'http://www.w3.org/2000/xmlns/' - namespace for namespace declarations. + internal const string XmlNamespacesNamespace = "http://www.w3.org/2000/xmlns/"; + + /// Edmx namespace in metadata document. + internal const string EdmxNamespace = "http://docs.oasis-open.org/odata/ns/edmx"; + + /// Prefix for Edmx Namespace in metadata document. + internal const string EdmxNamespacePrefix = "edmx"; + + /// IANA link relations namespace. + internal const string IanaLinkRelationsNamespace = "http://www.iana.org/assignments/relation/"; + + /// The empty namespace. + internal const string EmptyNamespace = ""; + + #endregion XML namespaces. + + #region CDM Schema Xml NodeNames + + #region Constant node names in the CDM schema xml + + /// Association Element Name in csdl. + internal const string Association = "Association"; + + /// AssociationSet Element Name in csdl. + internal const string AssociationSet = "AssociationSet"; + + /// ComplexType Element Name in csdl. + internal const string ComplexType = "ComplexType"; + + /// Dependent Element Name in csdl. + internal const string Dependent = "Dependent"; + + /// The name of the EDM collection type. + internal const string EdmCollectionTypeName = "Collection"; + + /// + /// Attribute name used to indicate the real type of an EDM property or parameter, in cases where it needs to be different + /// from the Type attribute of the Property or Parameter element. This is used to support collection types and binary keys, + /// which are incompatible with EDM 1.1, which we are currently using for validation purposes. + /// This attribute is inserted into the CSDL in memory while codegen is processing properties that require special + /// type handling and should only be used in that scenario. This is not a real EDM or Data Services attribute. + /// + internal const string ActualEdmType = "ActualEdmType"; + + /// TypeRef element name in CSDL document. + internal const string EdmTypeRefElementName = "TypeRef"; + + /// EntitySet attribute name in CSDL documents. + internal const string EdmEntitySetAttributeName = "EntitySet"; + + /// EntitySetPath attribute name in CSDL documents. + internal const string EdmEntitySetPathAttributeName = "EntitySetPath"; + + /// ExtensionMethod attribute name in CSDL documents. + internal const string EdmBindableAttributeName = "Bindable"; + + /// Composable attribute name in CSDL documents. + internal const string EdmComposableAttributeName = "Composable"; + + /// SideEffecting attribute name in CSDL documents. + internal const string EdmSideEffectingAttributeName = "SideEffecting"; + + /// FunctionImport element name in CSDL documents. + internal const string EdmFunctionImportElementName = "FunctionImport"; + + /// Mode attribute name in CSDL documents. + internal const string EdmModeAttributeName = "Mode"; + + /// Mode attribute value for 'in' direction in CSDL documents. + internal const string EdmModeInValue = "In"; + + /// Parameter element name in CSDL documents. + internal const string EdmParameterElementName = "Parameter"; + + /// ReturnType attribute name in CSDL documents. + internal const string EdmReturnTypeAttributeName = "ReturnType"; + + /// + /// Attribute name used to indicate the real type of an EDM operation import return type, in cases where it needs to be different + /// from the ReturnType attribute of the operation import element. This is used to support special primitive types, + /// which are incompatible with EDM 1.1, which we are currently using for validation purposes. + /// This attribute is inserted into the CSDL in memory while codegen is processing operation imports that require special + /// type handling and should only be used in that scenario. This is not a real EDM or Data Services attribute. + /// + internal const string ActualReturnTypeAttributeName = "ActualReturnType"; + + /// End Element Name in csdl. + internal const string End = "End"; + + /// EntityType Element Name in csdl. + internal const string EntityType = "EntityType"; + + /// EntityContainer Element Name in csdl. + internal const string EntityContainer = "EntityContainer"; + + /// Key Element Name in csdl. + internal const string Key = "Key"; + + /// NavigationProperty Element Name in csdl. + internal const string NavigationProperty = "NavigationProperty"; + + /// OnDelete Element Name in csdl. + internal const string OnDelete = "OnDelete"; + + /// Principal Element Name in csdl. + internal const string Principal = "Principal"; + + /// Property Element Name in csdl. + internal const string Property = "Property"; + + /// PropetyRef Element Name in csdl. + internal const string PropertyRef = "PropertyRef"; + + /// ReferentialConstraint Element Name in csdl. + internal const string ReferentialConstraint = "ReferentialConstraint"; + + /// Role Element Name in csdl. + internal const string Role = "Role"; + + /// Schema Element Name in csdl. + internal const string Schema = "Schema"; + + /// Edmx Element Name in the metadata document. + internal const string EdmxElement = "Edmx"; + + /// Edmx DataServices Element Name in the metadata document. + internal const string EdmxDataServicesElement = "DataServices"; + + /// Version attribute for the root Edmx Element in the metadata document. + internal const string EdmxVersion = "Version"; + + /// Value of the version attribute in the root edmx element in metadata document. + internal const string EdmxVersionValue = "4.0"; + + #endregion //Constant node names in the CDM schema xml + + #region const attribute names in the CDM schema XML + + /// Element name for m:action. + internal const string ActionElementName = "action"; + + /// Element name for m:function + internal const string FunctionElementName = "function"; + + /// maps to m:action|m:function\@metadata + internal const string ActionMetadataAttributeName = "metadata"; + + /// maps to m:action|m:function\@target + internal const string ActionTargetAttributeName = "target"; + + /// maps to m:action|m:function\@title + internal const string ActionTitleAttributeName = "title"; + + /// BaseType attribute Name in csdl. + internal const string BaseType = "BaseType"; + + /// EntitySet attribute and Element Name in csdl. + internal const string EntitySet = "EntitySet"; + + /// EntitySetPath attribute and Element Name in csdl. + internal const string EntitySetPath = "EntitySetPath"; + + /// FromRole attribute Name in csdl. + internal const string FromRole = "FromRole"; + + /// Abstract attribute Name in csdl. + internal const string Abstract = "Abstract"; + + /// Multiplicity attribute Name in csdl. + internal const string Multiplicity = "Multiplicity"; + + /// Name attribute Name in csdl. + internal const string Name = "Name"; + + /// Namespace attribute Element Name in csdl. + internal const string Namespace = "Namespace"; + + /// ToRole attribute Name in csdl. + internal const string ToRole = "ToRole"; + + /// Type attribute Name in csdl. + internal const string Type = "Type"; + + /// Relationship attribute Name in csdl. + internal const string Relationship = "Relationship"; + + #endregion //const attribute names in the CDM schema XML + + #region values for multiplicity in Edm + + /// Value for Many multiplicity in csdl. + internal const string Many = "*"; + + /// Value for One multiplicity in csdl. + internal const string One = "1"; + + /// Value for ZeroOrOne multiplicity in csdl. + internal const string ZeroOrOne = "0..1"; + #endregion + + #region Edm Facets Names and Values + + /// Nullable facet name in CSDL. + internal const string CsdlNullableAttributeName = "Nullable"; + + /// The attribute name of the 'Precision' facet. + internal const string CsdlPrecisionAttributeName = "Precision"; + + /// The attribute name of the 'Scale' facet. + internal const string CsdlScaleAttributeName = "Scale"; + + /// The attribute name of the 'MaxLength' facet. + internal const string CsdlMaxLengthAttributeName = "MaxLength"; + + /// The attribute name of the 'FixedLength' facet. + internal const string CsdlFixedLengthAttributeName = "FixedLength"; + + /// The attribute name of the 'Unicode' facet. + internal const string CsdlUnicodeAttributeName = "Unicode"; + + /// The attribute name of the 'Collation' facet. + internal const string CsdlCollationAttributeName = "Collation"; + + /// The attribute name of the 'SRID' facet. + internal const string CsdlSridAttributeName = "SRID"; + + /// Name of the default value attribute. + internal const string CsdlDefaultValueAttributeName = "DefaultValue"; + + /// The special value for the 'MaxLength' facet to indicate that it has the max length. + internal const string CsdlMaxLengthAttributeMaxValue = "Max"; + + #endregion + + #endregion // CDM Schema Xml NodeNames + + #region DataWeb Elements and Attributes. + + /// 'MimeType' - attribute name for property MIME type attributes. + internal const string DataWebMimeTypeAttributeName = "MimeType"; + + /// 'OpenType' - attribute name to indicate a type is an OpenType property. + internal const string DataWebOpenTypeAttributeName = "OpenType"; + + /// 'HasStream' - attribute name to indicate a type has a default stream property. + internal const string DataWebAccessHasStreamAttribute = "HasStream"; + + /// 'true' - attribute value to indicate a type has a default stream property. + internal const string DataWebAccessDefaultStreamPropertyValue = "true"; + + /// Attribute name in the csdl to indicate whether the service operation must be called using POST or GET verb. + internal const string ServiceOperationHttpMethodName = "HttpMethod"; + + /// next element name for link paging + internal const string NextElementName = "next"; + + #endregion DataWeb Elements and Attributes. + + #region JSON Format constants + + /// JSON property name for an error. + internal const string JsonError = "error"; + + /// JSON property name for an error code. + internal const string JsonErrorCode = "code"; + + /// JSON property name for the inner error details. + internal const string JsonErrorInner = "innererror"; + + /// JSON property name for an internal exception. + internal const string JsonErrorInternalException = "internalexception"; + + /// JSON property name for an error message. + internal const string JsonErrorMessage = "message"; + + /// JSON property name for an exception stack trace. + internal const string JsonErrorStackTrace = "stacktrace"; + + /// JSON property name for an exception type. + internal const string JsonErrorType = "type"; + + /// JSON property name for an error message value. + internal const string JsonErrorValue = "value"; + #endregion //JSON Format constants + + #region Edm Primitive Type Names + Collection + /// namespace for edm primitive types. + internal const string EdmNamespace = "Edm"; + + /// edm binary primitive type name + internal const string EdmBinaryTypeName = "Edm.Binary"; + + /// edm boolean primitive type name + internal const string EdmBooleanTypeName = "Edm.Boolean"; + + /// edm byte primitive type name + internal const string EdmByteTypeName = "Edm.Byte"; + + /// edm decimal primitive type name + internal const string EdmDecimalTypeName = "Edm.Decimal"; + + /// edm date primitive type name + internal const string EdmDateTypeName = "Edm.Date"; + + /// edm double primitive type name + internal const string EdmDoubleTypeName = "Edm.Double"; + + /// edm guid primitive type name + internal const string EdmGuidTypeName = "Edm.Guid"; + + /// edm single primitive type name + internal const string EdmSingleTypeName = "Edm.Single"; + + /// edm sbyte primitive type name + internal const string EdmSByteTypeName = "Edm.SByte"; + + /// edm int16 primitive type name + internal const string EdmInt16TypeName = "Edm.Int16"; + + /// edm int32 primitive type name + internal const string EdmInt32TypeName = "Edm.Int32"; + + /// edm int64 primitive type name + internal const string EdmInt64TypeName = "Edm.Int64"; + + /// edm string primitive type name + internal const string EdmStringTypeName = "Edm.String"; + + /// edm stream primitive type name + internal const string EdmStreamTypeName = "Edm.Stream"; + + /// edm timeofday primitive type name + internal const string EdmTimeOfDayTypeName = "Edm.TimeOfDay"; + + /// edm string indicating that the value may be collection. + internal const string CollectionTypeQualifier = "Collection"; + + /// Edm Geography type name + internal const string EdmGeographyTypeName = "Edm.Geography"; + + /// Edm Geodetic point type name + internal const string EdmPointTypeName = "Edm.GeographyPoint"; + + /// Edm Geodetic linestring type name + internal const string EdmLineStringTypeName = "Edm.GeographyLineString"; + + /// Represents a geography Polygon type. + internal const string EdmPolygonTypeName = "Edm.GeographyPolygon"; + + /// Represents a geography GeomCollection type. + internal const string EdmGeographyCollectionTypeName = "Edm.GeographyCollection"; + + /// Represents a geography MultiPolygon type. + internal const string EdmMultiPolygonTypeName = "Edm.GeographyMultiPolygon"; + + /// Represents a geography MultiLineString type. + internal const string EdmMultiLineStringTypeName = "Edm.GeographyMultiLineString"; + + /// Represents a geography MultiPoint type. + internal const string EdmMultiPointTypeName = "Edm.GeographyMultiPoint"; + + /// Represents an arbitrary Geometry type. + internal const string EdmGeometryTypeName = "Edm.Geometry"; + + /// Represents a geometry Point type. + internal const string EdmGeometryPointTypeName = "Edm.GeometryPoint"; + + /// Represents a geometry LineString type. + internal const string EdmGeometryLineStringTypeName = "Edm.GeometryLineString"; + + /// Represents a geometry Polygon type. + internal const string EdmGeometryPolygonTypeName = "Edm.GeometryPolygon"; + + /// Represents a geometry GeomCollection type. + internal const string EdmGeometryCollectionTypeName = "Edm.GeometryCollection"; + + /// Represents a geometry MultiPolygon type. + internal const string EdmGeometryMultiPolygonTypeName = "Edm.GeometryMultiPolygon"; + + /// Represents a geometry MultiLineString type. + internal const string EdmGeometryMultiLineStringTypeName = "Edm.GeometryMultiLineString"; + + /// Represents a geometry MultiPoint type. + internal const string EdmGeometryMultiPointTypeName = "Edm.GeometryMultiPoint"; + + /// edm duration primitive type name + internal const string EdmDurationTypeName = "Edm.Duration"; + + /// edm string primitive type name + internal const string EdmDateTimeOffsetTypeName = "Edm.DateTimeOffset"; + + #endregion + + #region Astoria Constants + + /// '4.0' - the version 4.0 text for a data service. + internal const string ODataVersion4Dot0 = "4.0"; + + /// 'binary' constant prefixed to binary literals. + internal const string LiteralPrefixBinary = "binary"; + + /// 'geography' constant prefixed to geography literals. + internal const string LiteralPrefixGeography = "geography"; + + /// 'geometry' constant prefixed to geometry literals. + internal const string LiteralPrefixGeometry = "geometry"; + + /// 'duration' constant prefixed to duration literals. + internal const string LiteralPrefixDuration = "duration"; + + /// 'M': Suffix for decimal type's string representation + internal const string LiteralSuffixDecimal = "M"; + + /// 'L': Suffix for long (int64) type's string representation + internal const string LiteralSuffixInt64 = "L"; + + /// 'f': Suffix for float (single) type's string representation + internal const string LiteralSuffixSingle = "f"; + + /// 'D': Suffix for double (Real) type's string representation + internal const string LiteralSuffixDouble = "D"; + + /// null liternal that needs to be return in ETag value when the value is null + internal const string NullLiteralInETag = "null"; + + /// Incoming message property name for the original reqeust uri + internal const string MicrosoftDataServicesRequestUri = "MicrosoftDataServicesRequestUri"; + + /// Incoming message property name for the original root service uri + internal const string MicrosoftDataServicesRootUri = "MicrosoftDataServicesRootUri"; + + #endregion // Astoria Constants + + #region Spatial / GeoRss / GeoJson + + /// + /// GeoRss namespace + /// + internal const string GeoRssNamespace = "http://www.georss.org/georss"; + + /// + /// The "georss" prefix + /// + internal const string GeoRssPrefix = "georss"; + + /// + /// Gml Namespace + /// + internal const string GmlNamespace = "http://www.opengis.net/gml"; + + /// + /// Gml Prefix + /// + internal const string GmlPrefix = "gml"; + + /// + /// Embedded Gml tag inside Georss + /// + internal const string GeoRssWhere = "where"; + + /// + /// GeoRss representation of a point + /// + internal const string GeoRssPoint = "point"; + + /// + /// GeoRss representation of a line + /// + internal const string GeoRssLine = "line"; + + /// + /// Gml representation of a point + /// + internal const string GmlPosition = "pos"; + + /// + /// Gml representation of a point array + /// + internal const string GmlPositionList = "posList"; + + /// + /// Gml representation of a linestring + /// + internal const string GmlLineString = "LineString"; + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilter.cs new file mode 100644 index 0000000..b4ce7c9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilter.cs @@ -0,0 +1,164 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.OData +{ + /// + /// Filter class to determine whether or not to read an annotation. + /// + internal class AnnotationFilter + { + /// + /// Filter that maches all annotation names. + /// + private static readonly AnnotationFilter IncludeAll = new IncludeAllFilter(); + + /// + /// Filter than maches no annotation names. + /// + private static readonly AnnotationFilter ExcludeAll = new ExcludeAllFilter(); + + /// + /// Separator for annotation filter patterns. + /// + private static readonly char[] AnnotationFilterPatternSeparator = new[] { ',' }; + + /// + /// Patterns to match, sorted in the order of higher to lower priorities to match. + /// + private readonly AnnotationFilterPattern[] prioritizedPatternsToMatch; + + /// + /// Private constructor to create a filter from comma delimited patterns to match to include or exclude annotations. + /// + /// Patters to match to include or exclude annotations. + private AnnotationFilter(AnnotationFilterPattern[] prioritizedPatternsToMatch) + { +#if DEBUG + for (int idx = 1; idx < prioritizedPatternsToMatch.Length; idx++) + { + Debug.Assert( + prioritizedPatternsToMatch[idx - 1].CompareTo(prioritizedPatternsToMatch[idx]) < 1, + "The prioritizedPattersToMatch array should have been sorted in the order of higher to lower priorities to match."); + } +#endif + this.prioritizedPatternsToMatch = prioritizedPatternsToMatch; + } + + /// + /// Create a filter from comma delimited patterns to match to include or exclude annotations. + /// + /// Comma delimited patterns to match to include or exclude annotations. + /// The newly created filter. + internal static AnnotationFilter Create(string filter) + { + if (string.IsNullOrEmpty(filter)) + { + return ExcludeAll; + } + + AnnotationFilterPattern[] prioritizedPatternsToMatch = filter + .Split(AnnotationFilterPatternSeparator) + .Select(pattern => AnnotationFilterPattern.Create(pattern.Trim())) + .ToArray(); + + AnnotationFilterPattern.Sort(prioritizedPatternsToMatch); + + if (prioritizedPatternsToMatch[0] == AnnotationFilterPattern.IncludeAllPattern) + { + return IncludeAll; + } + + if (prioritizedPatternsToMatch[0] == AnnotationFilterPattern.ExcludeAllPattern) + { + return ExcludeAll; + } + + return new AnnotationFilter(prioritizedPatternsToMatch); + } + + /// + /// Create a filter that inlcude all. + /// + /// The include all filter. + internal static AnnotationFilter CreateInclueAllFilter() + { + return new IncludeAllFilter(); + } + + /// + /// Returns true to indicate that the annotation with the name should be read, false otherwise. + /// + /// The name of the annotation in question. + /// Returns true to indicate that the annotation with the name should be read, false otherwise. + internal virtual bool Matches(string annotationName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(annotationName, "annotationName"); + + // Find the highest priority pattern that maches and return true only if that pattern is not exclude. + foreach (AnnotationFilterPattern pattern in this.prioritizedPatternsToMatch) + { + if (pattern.Matches(annotationName)) + { + return !pattern.IsExclude; + } + } + + return false; + } + + /// + /// Filter to read all annotations. + /// + private sealed class IncludeAllFilter : AnnotationFilter + { + /// + /// Private default constructor. + /// + internal IncludeAllFilter() : base(new AnnotationFilterPattern[0]) + { + } + + /// + /// Returns true to indicate that the annotation with the name should be read, false otherwise. + /// + /// The name of the annotation in question. + /// Returns true to indicate that the annotation with the name should be read, false otherwise. + internal override bool Matches(string annotationName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(annotationName, "annotationName"); + return true; + } + } + + /// + /// Filter to read no annotation. + /// + private sealed class ExcludeAllFilter : AnnotationFilter + { + /// + /// Private default constructor. + /// + internal ExcludeAllFilter() : base(new AnnotationFilterPattern[0]) + { + } + + /// + /// Returns true to indicate that the annotation with the name should be read, false otherwise. + /// + /// The name of the annotation in question. + /// Returns true to indicate that the annotation with the name should be read, false otherwise. + internal override bool Matches(string annotationName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(annotationName, "annotationName"); + return false; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilterPattern.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilterPattern.cs new file mode 100644 index 0000000..8b974c8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/AnnotationFilterPattern.cs @@ -0,0 +1,350 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Diagnostics; + + /// + /// Filter pattern class to determine whether an annotation name matches the pattern. + /// + internal abstract class AnnotationFilterPattern : IComparable + { + /// + /// The "*" pattern that includes all annotations. + /// + internal static readonly AnnotationFilterPattern IncludeAllPattern = new WildCardPattern(/*isExclude*/false); + + /// + /// The "-*" pattern that excludes all annotations. + /// + internal static readonly AnnotationFilterPattern ExcludeAllPattern = new WildCardPattern(/*isExclude*/true); + + /// + /// The pattern to match. + /// + protected readonly string Pattern; + + /// + /// The '.' namespace separator. + /// + private const char NamespaceSeparator = '.'; + + /// + /// The '-' operator to indicate that the annotation should be excluded from read when it matches the pattern. + /// + private const char ExcludeOperator = '-'; + + /// + /// The wild card constant. + /// + private const string WildCard = "*"; + + /// + /// String constant for .* + /// + private const string DotStar = ".*"; + + /// + /// true if the annotation should be excluded from reading when its name matches this pattern; false otherwise. + /// + private readonly bool isExclude; + + /// + /// Constructs a pattern instance to determine whether an annotation name matches the pattern. + /// + /// The pattern to match. + /// true if the annotation should be excluded from reading when its name matches this pattern; false otherwise. + private AnnotationFilterPattern(string pattern, bool isExclude) + { + Debug.Assert(!string.IsNullOrEmpty(pattern), "!string.IsNullOrEmpty(pattern)"); + this.isExclude = isExclude; + this.Pattern = pattern; + } + + /// + /// true if the annotation should be excluded from reading when its name matches this pattern; false otherwise. + /// + internal virtual bool IsExclude + { + get + { + return this.isExclude; + } + } + + /// + /// Compares the priority of current pattern with the priority of . + /// + /// + /// A 32-bit signed integer that indicates the relative priority of the patterns being compared. The return value has the following meanings: + /// -1 means this pattern has higher priority than . + /// 0 means this pattern has the same priority as . + /// 1 means this pattern has lower priority than . + /// + /// A pattern to compare with this pattern. + public int CompareTo(AnnotationFilterPattern other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + + // If two pattern strings have the same priority, negation has higher priority. + int priority = ComparePatternPriority(this.Pattern, other.Pattern); + if (priority == 0) + { + return this.IsExclude == other.IsExclude ? 0 : (this.IsExclude ? -1 : 1); + } + + return priority; + } + + /// + /// Creates a pattern instance to determine whether an annotation name matches the pattern. + /// + /// The pattern for this instance. + /// The newly created instance. + internal static AnnotationFilterPattern Create(string pattern) + { + ValidatePattern(pattern); + + bool isExclude = RemoveExcludeOperator(ref pattern); + if (pattern == WildCard) + { + return isExclude ? ExcludeAllPattern : IncludeAllPattern; + } + + if (pattern.EndsWith(DotStar, StringComparison.Ordinal)) + { + Debug.Assert(pattern != DotStar, "pattern != DotStar"); + return new StartsWithPattern(pattern.Substring(0, pattern.Length - 1), isExclude); + } + + return new ExactMatchPattern(pattern, isExclude); + } + + /// + /// Sorts the patterns in the array from highest to lowest priorities. + /// + /// The source array to sort. When the method returns the items in this array instance will be rearragned. + internal static void Sort(AnnotationFilterPattern[] pattersToSort) + { + Array.Sort(pattersToSort); + } + + /// + /// Match the given annotation name against the pattern. + /// + /// Annotation name in question. + /// Returns true if the given annotation name matches the pattern, false otherwise. + internal abstract bool Matches(string annotationName); + + /// + /// Compares the priority of with . + /// + /// The left hand side pattern to compare. + /// The right hand side pattern to compare. + /// + /// A 32-bit signed integer that indicates the relative priority of the patterns being compared. The return value has the following meanings: + /// -1 means has higher priority than . + /// 0 means has same priority as . + /// 1 means has lower priority than . + /// + private static int ComparePatternPriority(string pattern1, string pattern2) + { + Debug.Assert(!string.IsNullOrEmpty(pattern1), "!string.IsNullOrEmpty(pattern1)"); + Debug.Assert(!string.IsNullOrEmpty(pattern2), "!string.IsNullOrEmpty(pattern2)"); + Debug.Assert(pattern1[0] != ExcludeOperator, "pattern1[0] != ExcludeOperator"); + Debug.Assert(pattern2[0] != ExcludeOperator, "pattern2[0] != ExcludeOperator"); + + //// The relative priority of the pattern is base on the relative specificity of the patterns being compared. If pattern1 is under the namespace pattern2, + //// pattern1 is more specific than pattern2 because pattern1 matches a subset of what pattern2 matches. We give higher priority to the pattern that is more specific. + //// For example: + //// "ns.*" has higher priority than "*" + //// "ns.name" has higher priority than "ns.*" + //// "ns1.name" has same priority as "ns2.*" + + // Identical patterns have the same priority. + if (pattern1 == pattern2) + { + return 0; + } + + // WildCard is the least specific pattern and thus has the lowest priority. + if (pattern1 == WildCard) + { + return 1; + } + + if (pattern2 == WildCard) + { + return -1; + } + + Debug.Assert(!pattern1.EndsWith(WildCard, StringComparison.Ordinal), "The trailing * should have already been stripped, e.g. ns.* => ns."); + Debug.Assert(!pattern2.EndsWith(WildCard, StringComparison.Ordinal), "The trailing * should have already been stripped, e.g. ns.* => ns."); + + // If pattern1 starts with pattern2, pattern1 is either more specific than pattern2 or they belong to different namespaces. + // Examples of pattern1 being more specific than pattern2: "ns.name" vs. "ns.", "ns.sub1.name" vs. "ns.sub1." + // If they belong to different namespaces, technically they have the same relative priority and we should return 0. But it's fine + // for our purpose to return -1 or 1 for simplicity sake since patterns of the same priority can be evaluated in any order. + // Examples of pattern1 starts with pattern2 but there's no intersection in the set they match: "ns.name1.name2" vs "ns.name", "ns.name1.*" vs "ns.name" + if (pattern1.StartsWith(pattern2, StringComparison.Ordinal)) + { + return -1; + } + + if (pattern2.StartsWith(pattern1, StringComparison.Ordinal)) + { + return 1; + } + + // Not under the same space. + return 0; + } + + /// + /// Removes the exclude operator from the given pattern string. + /// + /// The input pattern to the method and will return the pattern without the exclude operator if it's found. + /// Returns true if the exclude operator is found and removed from the input pattern; false otherwise. + private static bool RemoveExcludeOperator(ref string pattern) + { + Debug.Assert(!string.IsNullOrEmpty(pattern), "!string.IsNullOrEmpty(pattern)"); + if (pattern[0] == ExcludeOperator) + { + pattern = pattern.Substring(1); + return true; + } + + return false; + } + + /// + /// Validates the pattern. + /// + /// The pattern to validate. + private static void ValidatePattern(string pattern) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(pattern, "pattern"); + + string patternWithoutMinusSign = pattern; + RemoveExcludeOperator(ref patternWithoutMinusSign); + + if (patternWithoutMinusSign == WildCard) + { + return; + } + + string[] segments = patternWithoutMinusSign.Split(NamespaceSeparator); + int segmentCount = segments.Length; + + if (segmentCount == 1) + { + throw new ArgumentException(Strings.AnnotationFilterPattern_InvalidPatternMissingDot(pattern)); + } + + for (int idx = 0; idx < segmentCount; idx++) + { + string currentSegment = segments[idx]; + if (string.IsNullOrEmpty(currentSegment)) + { + throw new ArgumentException(Strings.AnnotationFilterPattern_InvalidPatternEmptySegment(pattern)); + } + + if (currentSegment != WildCard && currentSegment.Contains(WildCard)) + { + throw new ArgumentException(Strings.AnnotationFilterPattern_InvalidPatternWildCardInSegment(pattern)); + } + + bool isLastSegment = idx + 1 == segmentCount; + if (currentSegment == WildCard && !isLastSegment) + { + throw new ArgumentException(Strings.AnnotationFilterPattern_InvalidPatternWildCardMustBeInLastSegment(pattern)); + } + } + } + + /// + /// The wild card pattern that matches everything. + /// + private sealed class WildCardPattern : AnnotationFilterPattern + { + /// + /// Constructs the wild card pattern. + /// + /// true if the annotation should be excluded from reading when its name matches this pattern; false otherwise. + internal WildCardPattern(bool isExclude) : base(WildCard, isExclude) + { + } + + /// + /// Match the given annotation name against the pattern. + /// + /// Annotation name in question. + /// Returns true if the given annotation name matches the pattern, false otherwise. + internal override bool Matches(string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + return true; + } + } + + /// + /// Pattern class to match any annotation name that starts with this pattern. + /// + private sealed class StartsWithPattern : AnnotationFilterPattern + { + /// + /// Constructs the starts with pattern. + /// + /// The pattern to start with. + /// true if the annotation should be excluded from reading when its name matches this pattern; false otherwise. + internal StartsWithPattern(string pattern, bool isExclude) : base(pattern, isExclude) + { + Debug.Assert(!string.IsNullOrEmpty(pattern), "!string.IsNullOrEmpty(pattern)"); + Debug.Assert(pattern.EndsWith(".", StringComparison.Ordinal), "pattern.EndsWith(\".\", StringComparison.Ordinal)"); + } + + /// + /// Match the given annotation name against the pattern. + /// + /// Annotation name in question. + /// Returns true if the given annotation name matches the pattern, false otherwise. + internal override bool Matches(string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + return annotationName.StartsWith(this.Pattern, StringComparison.Ordinal); + } + } + + /// + /// Pattern class to match an annotation name that is exactly the same as this pattern. + /// + private sealed class ExactMatchPattern : AnnotationFilterPattern + { + /// + /// Constructs the exact match pattern. + /// + /// The exact pattern to match + /// true if the annotation should be excluded from reading when its name matches this pattern; false otherwise. + internal ExactMatchPattern(string pattern, bool isExclude) : base(pattern, isExclude) + { + Debug.Assert(!string.IsNullOrEmpty(pattern), "!string.IsNullOrEmpty(pattern)"); + } + + /// + /// Match the given annotation name against the pattern. + /// + /// Annotation name in question. + /// Returns true if the given annotation name matches the pattern, false otherwise. + internal override bool Matches(string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + return annotationName.Equals(this.Pattern, StringComparison.Ordinal); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/AsyncBufferedStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/AsyncBufferedStream.cs new file mode 100644 index 0000000..c2c79d6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/AsyncBufferedStream.cs @@ -0,0 +1,389 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Write-only stream which buffers all synchronous write operations until FlushAsync is called. + /// + internal sealed class AsyncBufferedStream : Stream + { + /// + /// The stream being wrapped. + /// + private readonly Stream innerStream; + + /// + /// Queue of buffers to write. + /// + private Queue bufferQueue; + + /// + /// The last buffer in the bufferQueue. This is the buffer we're writing into. + /// + private DataBuffer bufferToAppendTo; + + /// + /// Constructor + /// + /// The underlying async stream to wrap. Note that only asynchronous write operation will be invoked on this stream. + internal AsyncBufferedStream(Stream stream) + { + Debug.Assert(stream != null, "stream != null"); + + this.innerStream = stream; + this.bufferQueue = new Queue(); + } + + /// + /// Determines if the stream can read - this one cannot + /// + public override bool CanRead + { + get { return false; } + } + + /// + /// Determines if the stream can seek - this one cannot + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Determines if the stream can write - this one can + /// + public override bool CanWrite + { + get { return true; } + } + + /// + /// Returns the length of the stream, which this implementation doesn't support. + /// + public override long Length + { + get + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + } + + /// + /// Gets or sets the position in the stream, this stream doesn't support seeking, so position is also unsupported. + /// + public override long Position + { + get + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + set + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + } + + /// + /// Flush the stream to the underlying storage. + /// + public override void Flush() + { + // no-op + // This can be called from writers that are put on top of this stream when + // they are closed/disposed + } + + /// + /// Reads data from the stream. This operation is not supported by this stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Seeks the stream. This operation is not supported by this stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Sets the length of the stream. This operation is not supported by this stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Writes to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (count > 0) + { + if (this.bufferToAppendTo == null) + { + this.QueueNewBuffer(); + } + + while (count > 0) + { + int written = this.bufferToAppendTo.Write(buffer, offset, count); + if (written < count) + { + this.QueueNewBuffer(); + } + + count -= written; + offset += written; + } + } + } + + /// + /// Clears any internal buffers without writing them to the underlying stream. + /// + internal void Clear() + { + this.bufferQueue.Clear(); + this.bufferToAppendTo = null; + } + + /// + /// Synchronous flush operation. This will flush all buffered bytes to the underlying stream through synchronous writes. + /// + internal void FlushSync() + { + Queue buffers = this.PrepareFlushBuffers(); + if (buffers == null) + { + return; + } + + while (buffers.Count > 0) + { + DataBuffer buffer = buffers.Dequeue(); + buffer.WriteToStream(this.innerStream); + } + } + +#if PORTABLELIB + + /// + /// Asynchronous flush operation. This will flush all buffered bytes to the underlying stream through asynchronous writes. + /// + /// The task representing the asynchronous flush operation. +#if PORTABLELIB + internal new Task FlushAsync() +#else + internal Task FlushAsync() +#endif + { + return this.FlushAsyncInternal(); + } + + + /// + /// Asynchronous flush operation. This will flush all buffered bytes to the underlying stream through asynchronous writes. + /// + /// The task representing the asynchronous flush operation. + internal Task FlushAsyncInternal() + { + Queue buffers = this.PrepareFlushBuffers(); + if (buffers == null) + { + return TaskUtils.CompletedTask; + } + + // Note that this relies on lazy eval of the enumerator + return Task.Factory.Iterate(this.FlushBuffersAsync(buffers)); + } +#endif + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called from the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.bufferQueue.Count > 0) + { + throw new ODataException(Strings.AsyncBufferedStream_WriterDisposedWithoutFlush); + } + } + + // We do not dispose the innerStream so to allow writers on the current stream to be disposed + // without disposing the message stream. There should only be one place to dispose the message + // stream for the writers and that is ODataMessageWriter.Dispose(). + base.Dispose(disposing); + } + + /// + /// Queues a new buffer to the queue of buffers + /// + private void QueueNewBuffer() + { + this.bufferToAppendTo = new DataBuffer(); + this.bufferQueue.Enqueue(this.bufferToAppendTo); + } + + /// + /// Prepares all buffers for flushing and returns the queue of buffers to flush. + /// + /// The queue of buffer to flush. + private Queue PrepareFlushBuffers() + { + if (this.bufferQueue.Count == 0) + { + return null; + } + + this.bufferToAppendTo = null; + + // clear the buffer queue to leave the stream in a 'clean' state even if + // flushing fails + Queue buffers = this.bufferQueue; + this.bufferQueue = new Queue(); + return buffers; + } + +#if PORTABLELIB + /// + /// Returns enumeration of tasks to run to flush all pending buffers to the underlying stream. + /// + /// The queue of buffers that need to be flushed. + /// Enumeration of tasks to run to flush all buffers. + /// This method relies on lazy eval of the enumerator, never enumerate through it synchronously. + private IEnumerable FlushBuffersAsync(Queue buffers) + { + while (buffers.Count > 0) + { + DataBuffer buffer = buffers.Dequeue(); + yield return buffer.WriteToStreamAsync(this.innerStream); + } + } +#endif + + /// + /// Class to wrap a byte buffer used to store portion of the buffered data. + /// + private sealed class DataBuffer + { + /// + /// The size of a buffer to allocate (80 KB is the limit for large object heap, so use 79 to be sure to avoid LOB) + /// + private const int BufferSize = 79 * 1024; + + /// + /// The byte buffer used to store the data. + /// + private readonly byte[] buffer; + + /// + /// Number of bytes being stored. + /// + private int storedCount; + + /// + /// Constructor - creates a new buffer + /// + public DataBuffer() + { + this.buffer = new byte[BufferSize]; + this.storedCount = 0; + } + + /// + /// Writes data into the buffer. + /// + /// The buffer containing the data to write. + /// The index to start at. + /// Number of bytes to write. + /// How many bytes were written. + public int Write(byte[] data, int index, int count) + { + int countToCopy = count; + if (countToCopy > (this.buffer.Length - this.storedCount)) + { + countToCopy = this.buffer.Length - this.storedCount; + } + + if (countToCopy > 0) + { + Array.Copy(data, index, this.buffer, this.storedCount, countToCopy); + this.storedCount += countToCopy; + } + + return countToCopy; + } + + /// + /// Writes the buffer to the specified stream. + /// + /// The stream to write the data into. + public void WriteToStream(Stream stream) + { + Debug.Assert(stream != null, "stream != null"); + stream.Write(this.buffer, 0, this.storedCount); + } + +#if PORTABLELIB + /// + /// Creates a task which writes the buffer to the specified stream. + /// + /// The stream to write the data into. + /// The task which represent the asynchronous write operation. + public Task WriteToStreamAsync(Stream stream) + { + Debug.Assert(stream != null, "stream != null"); +#if PORTABLELIB + return stream.WriteAsync(this.buffer, 0, this.storedCount); +#else + return Task.Factory.FromAsync( + (callback, state) => stream.BeginWrite(this.buffer, 0, this.storedCount, callback, state), + stream.EndWrite, + null); +#endif + } +#endif + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/BindingPathHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/BindingPathHelper.cs new file mode 100644 index 0000000..e4c5640 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/BindingPathHelper.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.OData +{ + internal static class BindingPathHelper + { + /// + /// Determine if the path of current navigation property is matching the binding path. + /// The function used in FindNavigationTarget to resolve the navigation target for multi binding. + /// + /// The binding path. + /// The list of segments in Uri path. + /// True if the path of navigation property in current scope is matching the . + public static bool MatchBindingPath(IEdmPathExpression bindingPath, List parsedSegments) + { + List paths = bindingPath.PathSegments.ToList(); + + // If binding path only includes navigation property name, it matches. + if (paths.Count == 1) + { + return true; + } + + int pathIndex = paths.Count - 2; // Skip the last segment which is navigation property name. + + // Match from tail to head. + for (int segmentIndex = parsedSegments.Count - 1; segmentIndex >= 0; segmentIndex--) + { + ODataPathSegment segment = parsedSegments[segmentIndex]; + + // Cache the cast result to avoid CA1800:DoNotCastUnnecessarily. + bool segmentIsNavigationPropertySegment = segment is NavigationPropertySegment; + + // Containment navigation property or complex property in binding path. + if (segment is PropertySegment || (segmentIsNavigationPropertySegment && segment.TargetEdmNavigationSource is IEdmContainedEntitySet)) + { + if (pathIndex < 0 || string.CompareOrdinal(paths[pathIndex], segment.Identifier) != 0) + { + return false; + } + + pathIndex--; + } + else if (segment is TypeSegment) + { + // May need match type if the binding path contains type cast. + if (pathIndex >= 0 && paths[pathIndex].Contains(".")) + { + if (string.CompareOrdinal(paths[pathIndex], segment.EdmType.AsElementType().FullTypeName()) != 0) + { + return false; + } + + pathIndex--; + } + } + else if (segment is EntitySetSegment + || segment is SingletonSegment + || segmentIsNavigationPropertySegment) + { + // Containment navigation property in first if statement for NavigationPropertySegment. + break; + } + } + + // Return true if all the segments in binding path have been matched. + return pathIndex == -1 ? true : false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferedReadStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferedReadStream.cs new file mode 100644 index 0000000..e141963 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferedReadStream.cs @@ -0,0 +1,415 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ +#if PORTABLELIB + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Threading.Tasks; + #endregion Namespaces + + /// + /// Class which takes an input stream, buffers the entire content asynchronously and exposes it as a stream + /// which can be read synchronously. + /// + internal sealed class BufferedReadStream : Stream + { + /// + /// List of buffers which store the data. + /// + private readonly List buffers; + + /// + /// The input stream to read from. This is used only during the buffering and is set to null once we've buffered everything. + /// + private Stream inputStream; + + /// + /// Points to the buffer currently being processed. + /// When writing into the buffers this points to the last buffer to which the bytes should be written. + /// When reading from the buffers this points to the buffer from which we are currently reading. + /// + private int currentBufferIndex; + + /// + /// Number of bytes read from the current buffer. + /// + private int currentBufferReadCount; + + /// + /// Private constructor. + /// + /// The stream to read from. + private BufferedReadStream(Stream inputStream) + { + this.buffers = new List(); + this.inputStream = inputStream; + this.currentBufferIndex = -1; + } + + /// + /// Determines if the stream can read - this one can + /// + public override bool CanRead + { + get { return true; } + } + + /// + /// Determines if the stream can seek - this one cannot + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Determines if the stream can write - this one cannot + /// + public override bool CanWrite + { + get { return false; } + } + + /// + /// Returns the length of the stream, which this implementation doesn't support. + /// + public override long Length + { + get + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + } + + /// + /// Gets or sets the position in the stream, this stream doesn't support seeking, so position is also unsupported. + /// + public override long Position + { + get + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + set + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + } + + /// + /// Flush the stream to the underlying storage. This operation is not supported by this stream. + /// + public override void Flush() + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Reads data from the stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + // Note that it's OK to assert the inputs as this stream should never be exposed to the user + // it will only be used internally. + ExceptionUtils.CheckArgumentNotNull(buffer, "buffer"); + Debug.Assert(offset < buffer.Length, "offset < buffer.Length"); + Debug.Assert(count <= buffer.Length - offset, "count <= buffer.Length - offset"); + Debug.Assert(this.inputStream == null, "Can't start reading until the buffering is finished."); + Debug.Assert(this.currentBufferIndex >= -1 && this.currentBufferIndex < this.buffers.Count, "The currentBufferIndex is outside of the valid range."); + + if (this.currentBufferIndex == -1) + { + return 0; + } + + DataBuffer currentBuffer = this.buffers[this.currentBufferIndex]; + Debug.Assert(this.currentBufferReadCount <= currentBuffer.StoredCount, "this.currentBufferReadCount <= currentBuffer.StoredCount"); + while (this.currentBufferReadCount >= currentBuffer.StoredCount) + { + // We've read all the data in the current buffer - move to the next one + this.currentBufferIndex++; + if (this.currentBufferIndex >= this.buffers.Count) + { + this.currentBufferIndex = -1; + return 0; + } + + currentBuffer = this.buffers[this.currentBufferIndex]; + this.currentBufferReadCount = 0; + } + + int readCount = count; + if (count > (currentBuffer.StoredCount - this.currentBufferReadCount)) + { + readCount = currentBuffer.StoredCount - this.currentBufferReadCount; + } + + Array.Copy(currentBuffer.Buffer, this.currentBufferReadCount, buffer, offset, readCount); + this.currentBufferReadCount += readCount; + Debug.Assert(this.currentBufferReadCount <= currentBuffer.StoredCount, "this.currentBufferReadCount <= currentBuffer.StoredCount"); + + return readCount; + } + + /// + /// Seeks the stream. This operation is not supported by this stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Sets the length of the stream. This operation is not supported by this stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Writes to the stream. This operation is not supported by this stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Given the this method returns a task which will asynchronously + /// read the entire content of that stream and return a new synchronous stream from which the data can be read. + /// + /// The input stream to asynchronously buffer. + /// A task which returns the buffered stream. + internal static Task BufferStreamAsync(Stream inputStream) + { + Debug.Assert(inputStream != null, "inputStream != null"); + + BufferedReadStream bufferedReadStream = new BufferedReadStream(inputStream); + + // Note that this relies on lazy eval of the enumerator + return Task.Factory.Iterate(bufferedReadStream.BufferInputStream()) + .FollowAlwaysWith((task) => inputStream.Dispose()) + .FollowOnSuccessWith( + (task) => + { + bufferedReadStream.ResetForReading(); + return bufferedReadStream; + }); + } + + /// + /// Resets the stream to the begining and prepares it for reading. + /// + internal void ResetForReading() + { + Debug.Assert(this.inputStream == null, "Can't start reading until the buffering is finished."); + + this.currentBufferIndex = this.buffers.Count == 0 ? -1 : 0; + this.currentBufferReadCount = 0; + } + + /// + /// Returns enumeration of tasks to run to buffer the entire input stream. + /// + /// Enumeration of tasks to run to buffer the input stream. + /// This method relies on lazy eval of the enumerator, never enumerate through it synchronously. + private IEnumerable BufferInputStream() + { + while (this.inputStream != null) + { + Debug.Assert(this.currentBufferIndex >= -1 && this.currentBufferIndex < this.buffers.Count, "The currentBufferIndex is outside of the valid range."); + + DataBuffer currentBuffer = this.currentBufferIndex == -1 ? null : this.buffers[this.currentBufferIndex]; + + // Here we intentionally leave some memory unused (smaller than MinReadBufferSize) + // in order to issue big enough read requests. This is a perf optimization. + if (currentBuffer != null && currentBuffer.FreeBytes < DataBuffer.MinReadBufferSize) + { + currentBuffer = null; + } + + if (currentBuffer == null) + { + currentBuffer = this.AddNewBuffer(); + } + +#if PORTABLELIB + yield return inputStream.ReadAsync(currentBuffer.Buffer, currentBuffer.OffsetToWriteTo, currentBuffer.FreeBytes) + .ContinueWith(t => + { + try + { + int bytesRead = t.Result; + if (bytesRead == 0) + { + this.inputStream = null; + } + else + { + currentBuffer.MarkBytesAsWritten(bytesRead); + } + } + catch (Exception exception) + { + if (!ExceptionUtils.IsCatchableExceptionType(exception)) + { + throw; + } + + this.inputStream = null; + throw; + } + }); + +#else + yield return Task.Factory.FromAsync( + (asyncCallback, asyncState) => this.inputStream.BeginRead( + currentBuffer.Buffer, + currentBuffer.OffsetToWriteTo, + currentBuffer.FreeBytes, + asyncCallback, + asyncState), + (asyncResult) => + { + try + { + int bytesRead = this.inputStream.EndRead(asyncResult); + if (bytesRead == 0) + { + this.inputStream = null; + } + else + { + currentBuffer.MarkBytesAsWritten(bytesRead); + } + } + catch (Exception exception) + { + if (!ExceptionUtils.IsCatchableExceptionType(exception)) + { + throw; + } + + this.inputStream = null; + throw; + } + }, + null); +#endif + } + } + + /// + /// Adds a new buffer to the list and makes it the current buffer. + /// + /// The newly added buffer. + private DataBuffer AddNewBuffer() + { + Debug.Assert(this.currentBufferIndex == this.buffers.Count - 1, "We should only be adding new buffer if we're at the last buffer."); + DataBuffer buffer = new DataBuffer(); + this.buffers.Add(buffer); + this.currentBufferIndex = this.buffers.Count - 1; + return buffer; + } + + /// + /// Class to wrap a byte buffer used to store portion of the buffered data. + /// + private sealed class DataBuffer + { + /// + /// The minimum size to ask for when reading from underlying stream. + /// + internal const int MinReadBufferSize = 1024; + + /// + /// The size of a buffer to allocate - use 64KB to be aligned which makes it likely that the underlying levels + /// will be able to process the request in one go. + /// + private const int BufferSize = 64 * 1024; + + /// + /// The byte buffer which stored the data. + /// + private readonly byte[] buffer; + + /// + /// Constructor - creates a new buffer; + /// + public DataBuffer() + { + Debug.Assert(BufferSize >= MinReadBufferSize, "BufferSize >= MinReadBufferSize"); + this.buffer = new byte[BufferSize]; + this.StoredCount = 0; + } + + /// + /// The byte buffer. + /// + public byte[] Buffer + { + get { return this.buffer; } + } + + /// + /// The offset into the buffer to which more data can be written. + /// + public int OffsetToWriteTo + { + get { return this.StoredCount; } + } + + /// + /// The number of bytes stored in the buffer. + /// + public int StoredCount { get; private set; } + + /// + /// The number of bytes not yet used in the buffer. + /// + public int FreeBytes + { + get { return this.buffer.Length - this.StoredCount; } + } + + /// + /// Marks specified count of bytes as written starting at the OffsetToWriteTo. + /// + /// The number of bytes to mark as written. + public void MarkBytesAsWritten(int count) + { + Debug.Assert(this.StoredCount + count <= this.buffer.Length, "Trying to write more bytes than available."); + this.StoredCount += count; + } + } + } +#endif +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferingReadStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferingReadStream.cs new file mode 100644 index 0000000..4d1446b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/BufferingReadStream.cs @@ -0,0 +1,298 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + #endregion Namespaces + + /// + /// Read-only stream which initially buffers all read data in order to replay it later. + /// Once no more buffered data exists it reads from the underlying stream directly. + /// + internal sealed class BufferingReadStream : Stream + { + /// The list of buffered chunks of bytes as requested by callers. + private readonly LinkedList buffers; + + /// + /// The stream being wrapped.sdfasdf + /// + private Stream innerStream; + + /// The read position in the current buffer. + private int positionInCurrentBuffer; + + /// + /// true if the reader is not in buffering mode; otherwise false. + /// + private bool bufferingModeDisabled; + + /// + /// The current node in the buffer list to read from. + /// + private LinkedListNode currentReadNode; + + /// + /// Constructor + /// + /// The underlying stream to wrap. Note that only read operations will be invoked on this stream. + internal BufferingReadStream(Stream stream) + { + Debug.Assert(stream != null, "stream != null"); + + this.innerStream = stream; + this.buffers = new LinkedList(); + } + + /// + /// Determines if the stream can read - this one can. + /// + public override bool CanRead + { + get { return true; } + } + + /// + /// Determines if the stream can seek - this one cannot + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Determines if the stream can write - this one cannot + /// + public override bool CanWrite + { + get { return false; } + } + + /// + /// Returns the length of the stream, which this implementation doesn't support. + /// + public override long Length + { + get + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + } + + /// + /// Gets or sets the position in the stream, this stream doesn't support seeking, so position is also unsupported. + /// + public override long Position + { + get + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + set + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + } + + /// + /// true if the stream is in buffering mode; otherwise false. + /// + internal bool IsBuffering + { + get + { + return !this.bufferingModeDisabled; + } + } + + /// + /// Not supported since the stream only allows reading. + /// + public override void Flush() + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Reads data from the buffer or the underlying stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] userBuffer, int offset, int count) + { + ExceptionUtils.CheckArgumentNotNull(userBuffer, "userBuffer"); + ExceptionUtils.CheckIntegerNotNegative(offset, "offset"); + ExceptionUtils.CheckIntegerPositive(count, "count"); + + int bytesRead = 0; + + // See whether we still have buffered data and read from it if we have; + // NOTE When not reading from the buffer the currentReadNode must be null. + while (this.currentReadNode != null && count > 0) + { + byte[] currentBytes = this.currentReadNode.Value; + int bytesInCurrentBuffer = currentBytes.Length - this.positionInCurrentBuffer; + if (bytesInCurrentBuffer == count) + { + // Copy all the remaining bytes of the current buffer to the user buffer + // and move to the next buffer + Buffer.BlockCopy(currentBytes, this.positionInCurrentBuffer, userBuffer, offset, count); + bytesRead += count; + + this.MoveToNextBuffer(); + return bytesRead; + } + + if (bytesInCurrentBuffer > count) + { + // Copy the requested number of bytes to the user buffer + // and update the position in the current buffer + Buffer.BlockCopy(currentBytes, this.positionInCurrentBuffer, userBuffer, offset, count); + bytesRead += count; + this.positionInCurrentBuffer += count; + return bytesRead; + } + + // Copy the remaining bytes of the current buffer to the user buffer and + // move to the next buffer + Buffer.BlockCopy(currentBytes, this.positionInCurrentBuffer, userBuffer, offset, bytesInCurrentBuffer); + bytesRead += bytesInCurrentBuffer; + offset += bytesInCurrentBuffer; + count -= bytesInCurrentBuffer; + + this.MoveToNextBuffer(); + } + + // When we get here we either could not satisfy the requested number of bytes + // from the buffers or are in buffering mode. + Debug.Assert(this.currentReadNode == null, "No current read node should exist if we are not working off the buffers."); + int bytesReadFromInnerStream = this.innerStream.Read(userBuffer, offset, count); + + // If we are in buffering mode, store the read bytes in our buffer + if (!this.bufferingModeDisabled && bytesReadFromInnerStream > 0) + { + byte[] newDataBuffer = new byte[bytesReadFromInnerStream]; + Buffer.BlockCopy(userBuffer, offset, newDataBuffer, 0, bytesReadFromInnerStream); + this.buffers.AddLast(newDataBuffer); + } + + return bytesRead + bytesReadFromInnerStream; + } + + /// + /// Seeks the stream. This operation is not supported by this stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Sets the length of the stream. This operation is not supported by this stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Writes to the stream. This operation is not supported by this stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + Debug.Assert(false, "Should never get here."); + throw new NotSupportedException(); + } + + /// + /// Stops the buffering mode and turns the reader into normal read mode where first + /// the buffered data is re-read before the reads are performed on the underlying stream. + /// + internal void ResetStream() + { + Debug.Assert(!this.bufferingModeDisabled, "Cannot reset the stream again once buffering has been turned off!"); + + this.currentReadNode = this.buffers.First; + this.positionInCurrentBuffer = 0; + } + + /// + /// Stop buffering. + /// + internal void StopBuffering() + { + Debug.Assert(!this.bufferingModeDisabled, "Cannot stop buffering if the stream is not buffering!"); + + this.ResetStream(); + this.bufferingModeDisabled = true; + } + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called from the finalizer. + protected override void Dispose(bool disposing) + { + // Only honor dispose calls once buffering has been stopped. + if (this.bufferingModeDisabled) + { + if (disposing) + { + if (this.innerStream != null) + { + this.innerStream.Dispose(); + this.innerStream = null; + } + } + + base.Dispose(disposing); + } + } + + /// + /// Moves the reader to the next buffer and drops already consumed + /// data if not in buffering mode. + /// + private void MoveToNextBuffer() + { + // Drop the consumed data if not in buffering mode and continue + // reading from the buffer if more data is available; if in + // buffering mode, don't drop any data just move to the next buffer. + if (this.bufferingModeDisabled) + { + this.buffers.RemoveFirst(); + this.currentReadNode = this.buffers.First; + } + else + { + this.currentReadNode = this.currentReadNode.Next; + } + + this.positionInCurrentBuffer = 0; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/BufferUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/BufferUtils.cs new file mode 100644 index 0000000..62ee61e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/BufferUtils.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Buffers +{ + /// + /// Helpers to deal with buffers + /// + internal static class BufferUtils + { + /// + /// Buffer length + /// + private const int BufferLength = 128; + + /// + /// Checks if the buffer is not initialized and if initialized returns the same buffer or creates a new one. + /// + /// The buffer to verify + /// The initialized buffer + public static char[] InitializeBufferIfRequired(char[] buffer) + { + return InitializeBufferIfRequired(null, buffer); + } + + /// + /// Checks if the buffer is not initialized and if initialized returns the same buffer or creates a new one. + /// + /// The character pool. + /// The buffer to verify + /// The initialized buffer. + public static char[] InitializeBufferIfRequired(ICharArrayPool bufferPool, char[] buffer) + { + if (buffer != null) + { + return buffer; + } + + return RentFromBuffer(bufferPool, BufferLength); + } + + /// + /// Rents a character array from the pool. + /// + /// The character pool. + /// The min required size of the character array. + /// The character array from the pool. + public static char[] RentFromBuffer(ICharArrayPool bufferPool, int minSize) + { + if (bufferPool == null) + { + return new char[minSize]; + } + + char[] buffer = bufferPool.Rent(minSize); + if (buffer == null || buffer.Length < minSize) + { + throw new ODataException(Strings.BufferUtils_InvalidBufferOrSize(minSize)); + } + + return buffer; + } + + /// + /// Returns a character array to the pool. + /// + /// The character pool. + /// The character array should be returned. + public static void ReturnToBuffer(ICharArrayPool bufferPool, char[] buffer) + { + if (bufferPool != null) + { + bufferPool.Return(buffer); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/ICharArrayPool.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/ICharArrayPool.cs new file mode 100644 index 0000000..903d576 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Buffers/ICharArrayPool.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Buffers +{ + /// + /// Provides an interface for using character arrays. + /// + public interface ICharArrayPool + { + /// + /// Rents a character array from the pool. + /// This character array must be returned when it is no longer used. + /// + /// The min required size of the character array. + /// The character array from the pool. + char[] Rent(int minSize); + + /// + /// Returns a character array to the pool. + /// + /// The character array should be returned. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Return")] + void Return(char[] array); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj new file mode 100644 index 0000000..44266e0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj @@ -0,0 +1,1875 @@ + + + + true + v3.5 + Microsoft.OData.Core.NetFX35.V7 + Library + $(DefineConstants);ODATA_CORE + $(AssemblyName).xml + Microsoft.OData + {E7B15F0C-0442-458D-9D16-69F3272420BF} + ..\ + + + + + $(EnlistmentRoot)\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj + ODataCrossTargettingSourcePath + + + + + + + + + {69A15676-2469-4315-9617-F0FFC04D310D} + Microsoft.OData.Edm.NetFX35 + + + {1104C2E4-076F-47AA-8121-03015AE70630} + Microsoft.Spatial.NetFX35 + + + + + Microsoft.OData.Core.txt + Microsoft.OData.Core + true + true + internal + true + Microsoft.OData.Core.TextRes + + + + + + Microsoft\OData\Core\AnnotationFilter.cs + + + Microsoft\OData\Core\AnnotationFilterPattern.cs + + + Microsoft\OData\Core\AsyncBufferedStream.cs + + + Microsoft\OData\Core\BufferedReadStream.cs + + + Microsoft\OData\Core\BufferingReadStream.cs + + + Microsoft\OData\Core\CollectionWithoutExpectedTypeValidator.cs + + + Microsoft\OData\Core\ErrorUtils.cs + + + Microsoft\OData\Core\Evaluation\EdmValueUtils.cs + + + Microsoft\OData\Core\Evaluation\IODataResourceMetadataContext.cs + + + Microsoft\OData\Core\Evaluation\KeySerializer.cs + + + Microsoft\OData\Core\Evaluation\LiteralFormatter.cs + + + Microsoft\OData\Core\Evaluation\NoOpResourceMetadataBuilder.cs + + + Microsoft\OData\Core\Evaluation\ODataConventionalResourceMetadataBuilder.cs + + + Microsoft\OData\Core\Evaluation\ODataConventionalUriBuilder.cs + + + Microsoft\OData\Core\Evaluation\ODataResourceMetadataBuilder.cs + + + Microsoft\OData\Core\Evaluation\ODataResourceMetadataContext.cs + + + Microsoft\OData\Core\Evaluation\ODataMetadataContext.cs + + + Microsoft\OData\Core\Evaluation\ODataMissingOperationGenerator.cs + + + Microsoft\OData\Core\Evaluation\ODataUriBuilder.cs + + + Microsoft\OData\Core\ExceptionUtils.cs + + + Microsoft\OData\Core\ResourceSetWithoutExpectedTypeValidator.cs + + + Microsoft\OData\Core\GeographyTypeConverter.cs + + + Microsoft\OData\Core\GeometryTypeConverter.cs + + + Microsoft\OData\Core\HttpHeaderValue.cs + + + Microsoft\OData\Core\HttpHeaderValueElement.cs + + + Microsoft\OData\Core\HttpHeaderValueLexer.cs + + + Microsoft\OData\Core\HttpUtils.cs + + + Microsoft\OData\Core\IODataStreamListener.cs + + + Microsoft\OData\Core\IODataResourceTypeContext.cs + + + Microsoft\OData\Core\IODataOutputInStreamErrorListener.cs + + + Microsoft\OData\Core\IODataReaderWriterListener.cs + + + Microsoft\OData\Core\IODataRequestMessage.cs + + + Microsoft\OData\Core\IODataRequestMessageAsync.cs + + + Microsoft\OData\Core\IODataResponseMessage.cs + + + Microsoft\OData\Core\IODataResponseMessageAsync.cs + + + Microsoft\OData\Core\IODataPayloadUriConverter.cs + + + Microsoft\OData\Core\IPrimitiveTypeConverter.cs + + + Microsoft\OData\Core\InstanceAnnotationWriteTracker.cs + + + Microsoft\OData\Core\InternalErrorCodes.cs + + + Microsoft\OData\Core\InternalErrorCodesCommon.cs + + + Microsoft\OData\Core\JsonLight\IODataJsonLightReaderResourceState.cs + + + Microsoft\OData\Core\JsonLight\IODataJsonLightWriterResourceState.cs + + + Microsoft\OData\Core\JsonLight\JsonFullMetadataLevel.cs + + + Microsoft\OData\Core\JsonLight\JsonFullMetadataTypeNameOracle.cs + + + Microsoft\OData\Core\JsonLight\JsonLightConstants.cs + + + Microsoft\OData\Core\JsonLight\JsonLightMetadataLevel.cs + + + Microsoft\OData\Core\JsonLight\JsonLightODataAnnotationWriter.cs + + + Microsoft\OData\Core\JsonLight\JsonLightTypeNameOracle.cs + + + Microsoft\OData\Core\JsonLight\JsonMinimalMetadataLevel.cs + + + Microsoft\OData\Core\JsonLight\JsonMinimalMetadataTypeNameOracle.cs + + + Microsoft\OData\Core\JsonLight\JsonNoMetadataLevel.cs + + + Microsoft\OData\Core\JsonLight\JsonNoMetadataTypeNameOracle.cs + + + Microsoft\OData\Core\JsonLight\ODataAnnotationNames.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightCollectionDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightCollectionReader.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightCollectionSerializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightCollectionWriter.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightContextUriParseResult.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightContextUriParser.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightDeltaReader.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightDeltaWriter.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightEntityReferenceLinkDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightEntityReferenceLinkSerializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightResourceDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightResourceSerializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightErrorDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightInputContext.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightOutputContext.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightParameterDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightParameterReader.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightParameterWriter.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightPayloadKindDetectionDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightPropertyAndValueDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightPropertySerializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightReader.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightReaderNestedResourceInfo.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightReaderUtils.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightSerializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightServiceDocumentDeserializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightServiceDocumentSerializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightUtils.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightValidationUtils.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightValueSerializer.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightWriter.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightWriterUtils.cs + + + Microsoft\OData\Core\JsonLight\ReorderingJsonReader.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchBodyContentReaderStream.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchReader.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchWriter.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightReaderNestedPropertyInfo.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightReaderNestedInfo.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightReaderNestedStreamInfo.cs + + + Microsoft\OData\Core\Json\IJsonStreamReader.cs + + + Microsoft\OData\Core\Json\IJsonStreamWriter.cs + + + Microsoft\OData\Core\Json\ODataJsonTextWriter.cs + + + Microsoft\OData\Core\ODataTextStreamReader.cs + + + Microsoft\OData\Core\ODataNotificationStream.cs + + + Microsoft\OData\Core\ODataNotificationReader.cs + + + Microsoft\OData\Core\ODataNotificationWriter.cs + + + Microsoft\OData\Core\IODataStreamReferenceInfo.cs + + + Microsoft\OData\Core\ODataStreamItem.cs + + + Microsoft\OData\Core\ODataStreamPropertyInfo.cs + + + Microsoft\OData\Core\ODataPropertyInfo.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchPayloadItemPropertiesCache.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchReaderStream.cs + + + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchAtomicGroupCache.cs + + + Microsoft\OData\Core\Json\BufferingJsonReader.cs + + + Microsoft\OData\Core\Json\ODataJsonFormat.cs + + + Microsoft\OData\Core\Json\GeoJsonWriterAdapter.cs + + + Microsoft\OData\Core\Json\IJsonWriter.cs + + + Microsoft\OData\Core\Json\IODataJsonOperationsDeserializerContext.cs + + + Microsoft\OData\Core\Json\JsonConstants.cs + + + Microsoft\OData\Core\Json\JsonLightInstanceAnnotationWriter.cs + + + Microsoft\OData\Core\Json\JsonNodeType.cs + + + Microsoft\OData\Core\Json\JsonReader.cs + + + Microsoft\OData\Core\Json\JsonReaderExtensions.cs + + + Microsoft\OData\Core\Json\JsonSharedUtils.cs + + + Microsoft\OData\Core\Json\JsonValueUtils.cs + + + Microsoft\OData\Core\Json\JsonWriter.cs + + + Microsoft\OData\Core\Json\JsonWriterExtensions.cs + + + Microsoft\OData\Core\Json\NonIndentedTextWriter.cs + + + Microsoft\OData\Core\Json\ODataJsonReaderCoreUtils.cs + + + Microsoft\OData\Core\Json\ODataJsonWriterUtils.cs + + + Microsoft\OData\Core\Json\ODataStringEscapeOption.cs + + + Microsoft\OData\Core\Json\TextWriterWrapper.cs + + + Microsoft\OData\Core\ODataErrorDetail.cs + + + Microsoft\OData\Core\ODataMediaType.cs + + + Microsoft\OData\Core\ODataMediaTypeResolver.cs + + + Microsoft\OData\Core\ODataSimplifiedOptions.cs + + + Microsoft\OData\Core\MediaTypeUtils.cs + + + Microsoft\OData\Core\ODataMediaTypeFormat.cs + + + Microsoft\OData\Core\MessageStreamWrapper.cs + + + Microsoft\OData\Core\Metadata\BufferingXmlReader.cs + + + Microsoft\OData\Core\Metadata\CachedPrimitiveKeepInContentAnnotation.cs + + + Microsoft\OData\Core\Metadata\EdmConstants.cs + + + Microsoft\OData\Core\Metadata\EdmLibraryExtensions.cs + + + Microsoft\OData\Core\Metadata\EdmTypeReaderResolver.cs + + + Microsoft\OData\Core\Metadata\EdmTypeResolver.cs + + + Microsoft\OData\Core\Metadata\EdmTypeWriterResolver.cs + + + Microsoft\OData\Core\Metadata\MetadataUtils.cs + + + Microsoft\OData\Core\Metadata\MetadataUtilsCommon.cs + + + Microsoft\OData\Core\Metadata\ODataAtomErrorDeserializer.cs + + + Microsoft\OData\Core\Metadata\ODataMetadataConstants.cs + + + Microsoft\OData\Core\Metadata\ODataMetadataReaderUtils.cs + + + Microsoft\OData\Core\Metadata\ODataMetadataWriterUtils.cs + + + Microsoft\OData\Core\Metadata\XmlReaderExtensions.cs + + + Microsoft\OData\Core\MimeConstants.cs + + + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchFormat.cs + + + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchInputContext.cs + + + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchOutputContext.cs + + + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchReader.cs + + + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchReaderStream.cs + + + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchWriter.cs + + + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchWriterUtils.cs + + + Microsoft\OData\Core\MultipartMixed\DependsOnIdsTracker.cs + + + Microsoft\OData\Core\NonDisposingStream.cs + + + Microsoft\OData\Core\ODataAction.cs + + + Microsoft\OData\Core\ODataAnnotatable.cs + + + Microsoft\OData\Core\ODataAsynchronousReader.cs + + + Microsoft\OData\Core\ODataAsynchronousResponseMessage.cs + + + Microsoft\OData\Core\ODataAsynchronousWriter.cs + + + Microsoft\OData\Core\ODataBatchOperationHeaders.cs + + + Microsoft\OData\Core\ODataBatchOperationMessage.cs + + + Microsoft\OData\Core\ODataReadStream.cs + + + Microsoft\OData\Core\ODataBatchOperationRequestMessage.cs + + + Microsoft\OData\Core\ODataBatchOperationResponseMessage.cs + + + Microsoft\OData\Core\ODataStream.cs + + + Microsoft\OData\Core\ODataWriteStream.cs + + + Microsoft\OData\Core\ODataBatchReader.cs + + + Microsoft\OData\Core\ODataBatchReaderState.cs + + + Microsoft\OData\Core\ODataBatchReaderStream.cs + + + Microsoft\OData\Core\ODataBatchReaderStreamBuffer.cs + + + Microsoft\OData\Core\ODataBatchReaderStreamScanResult.cs + + + Microsoft\OData\Core\ODataBatchPayloadUriConverter.cs + + + Microsoft\OData\Core\ODataBatchUtils.cs + + + Microsoft\OData\Core\ODataBatchWriter.cs + + + Microsoft\OData\Core\ODataBinaryStreamWriter.cs + + + Microsoft\OData\Core\ODataBinaryStreamReader.cs + + + Microsoft\OData\Core\ODataCollectionReader.cs + + + Microsoft\OData\Core\ODataCollectionReaderCore.cs + + + Microsoft\OData\Core\ODataCollectionReaderCoreAsync.cs + + + Microsoft\OData\Core\ODataCollectionReaderState.cs + + + Microsoft\OData\Core\ODataCollectionStart.cs + + + Microsoft\OData\Core\ODataCollectionStartSerializationInfo.cs + + + Microsoft\OData\Core\ODataCollectionWriter.cs + + + Microsoft\OData\Core\ODataCollectionWriterCore.cs + + + Microsoft\OData\Core\ODataConstants.cs + + + Microsoft\OData\Core\ODataContentTypeException.cs + + + Microsoft\OData\Core\ODataContextUriBuilder.cs + + + Microsoft\OData\Core\ODataContextUrlInfo.cs + + + Microsoft\OData\Core\ODataContextUrlLevel.cs + + + Microsoft\OData\Core\ODataDeltaDeletedEntry.cs + + + Microsoft\OData\Core\ODataDeltaDeletedLink.cs + + + Microsoft\OData\Core\ODataDeltaResourceSet.cs + + + Microsoft\OData\Core\ODataDeltaResourceSetSerializationInfo.cs + + + Microsoft\OData\Core\ODataDeltaKind.cs + + + Microsoft\OData\Core\ODataDeltaLink.cs + + + Microsoft\OData\Core\ODataDeltaLinkBase.cs + + + Microsoft\OData\Core\ODataDeltaReader.cs + + + Microsoft\OData\Core\ODataDeltaReaderState.cs + + + Microsoft\OData\Core\ODataDeltaSerializationInfo.cs + + + Microsoft\OData\Core\ODataDeltaWriter.cs + + + Microsoft\OData\Core\ODataDeserializer.cs + + + Microsoft\OData\Core\ODataEntityReferenceLink.cs + + + Microsoft\OData\Core\ODataEntityReferenceLinks.cs + + + Microsoft\OData\Core\ODataEntitySetInfo.cs + + + Microsoft\OData\Core\ODataResource.cs + + + Microsoft\OData\Core\ODataError.cs + + + Microsoft\OData\Core\ODataErrorException.cs + + + Microsoft\OData\Core\ODataException.cs + + + Microsoft\OData\Core\ODataResourceSet.cs + + + Microsoft\OData\Core\ODataResourceSerializationInfo.cs + + + Microsoft\OData\Core\ODataResourceTypeContext.cs + + + Microsoft\OData\Core\ODataResourceSetBase.cs + + + Microsoft\OData\Core\ODataFormat.cs + + + Microsoft\OData\Core\ODataFunction.cs + + + Microsoft\OData\Core\ODataFunctionImportInfo.cs + + + Microsoft\OData\Core\ODataInnerError.cs + + + Microsoft\OData\Core\ODataInputContext.cs + + + Microsoft\OData\Core\ODataInstanceAnnotation.cs + + + Microsoft\OData\Core\ODataItem.cs + + + Microsoft\OData\Core\ODataJsonDateTimeFormat.cs + + + Microsoft\OData\Core\ODataMessage.cs + + + Microsoft\OData\Core\ODataMessageExtensions.cs + + + Microsoft\OData\Core\StreamExtensions.cs + + + Microsoft\OData\Core\ODataMessageInfo.cs + + + Microsoft\OData\Core\ODataMessageQuotas.cs + + + Microsoft\OData\Core\ODataMessageReader.cs + + + Microsoft\OData\Core\ODataMessageReaderSettings.cs + + + Microsoft\OData\Core\ODataMessageWriter.cs + + + Microsoft\OData\Core\ODataMessageWriterSettings.cs + + + Microsoft\OData\Core\ODataMetadataFormat.cs + + + Microsoft\OData\Core\ODataMetadataInputContext.cs + + + Microsoft\OData\Core\ODataMetadataOutputContext.cs + + + Microsoft\OData\Core\ODataNestedResourceInfo.cs + + + Microsoft\OData\Core\ODataObjectModelExtensions.cs + + + Microsoft\OData\Core\ODataOperation.cs + + + Microsoft\OData\Core\ODataOutputContext.cs + + + Microsoft\OData\Core\ODataParameterReader.cs + + + Microsoft\OData\Core\ODataParameterReaderCore.cs + + + Microsoft\OData\Core\ODataParameterReaderCoreAsync.cs + + + Microsoft\OData\Core\ODataParameterReaderState.cs + + + Microsoft\OData\Core\ODataParameterWriter.cs + + + Microsoft\OData\Core\ODataParameterWriterCore.cs + + + Microsoft\OData\Core\ODataPayloadKind.cs + + + Microsoft\OData\Core\ODataPayloadKindDetectionInfo.cs + + + Microsoft\OData\Core\ODataPayloadKindDetectionResult.cs + + + Microsoft\OData\Core\ODataPayloadValueConverter.cs + + + Microsoft\OData\Core\ODataPreferenceHeader.cs + + + Microsoft\OData\Core\ODataProperty.cs + + + Microsoft\OData\Core\ODataPropertyKind.cs + + + Microsoft\OData\Core\ODataPropertySerializationInfo.cs + + + Microsoft\OData\Core\ODataRawInputContext.cs + + + Microsoft\OData\Core\ODataRawOutputContext.cs + + + Microsoft\OData\Core\ODataRawValueConverter.cs + + + Microsoft\OData\Core\ODataRawValueFormat.cs + + + Microsoft\OData\Core\ODataRawValueUtils.cs + + + Microsoft\OData\Core\ODataReader.cs + + + Microsoft\OData\Core\ODataReaderCore.cs + + + Microsoft\OData\Core\ODataReaderCoreAsync.cs + + + Microsoft\OData\Core\ODataReaderState.cs + + + Microsoft\OData\Core\ODataRequestMessage.cs + + + Microsoft\OData\Core\ODataResponseMessage.cs + + + Microsoft\OData\Core\ODataSerializer.cs + + + Microsoft\OData\Core\ODataServiceDocument.cs + + + Microsoft\OData\Core\ODataServiceDocumentElement.cs + + + Microsoft\OData\Core\ODataSingletonInfo.cs + + + Microsoft\OData\Core\ODataTypeAnnotation.cs + + + Microsoft\OData\Core\ODataUtils.cs + + + Microsoft\OData\Core\ODataUtilsInternal.cs + + + Microsoft\OData\Core\ODataVersion.cs + + + Microsoft\OData\Core\ODataVersionCache.cs + + + Microsoft\OData\Core\ODataWriter.cs + + + Microsoft\OData\Core\ODataWriterCore.cs + + + Microsoft\OData\Core\PrimitiveConverter.cs + + + Microsoft\OData\Core\RawValueWriter.cs + + + Microsoft\OData\Core\ReadOnlyEnumerable.cs + + + Microsoft\OData\Core\ReadOnlyEnumerableExtensions.cs + + + Microsoft\OData\Core\ReadOnlyEnumerableOfT.cs + + + Microsoft\OData\Core\ReaderUtils.cs + + + Microsoft\OData\Core\ReaderValidationUtils.cs + + + Microsoft\OData\Core\ReferenceEqualityComparer.cs + + + Microsoft\OData\Core\SelectedPropertiesNode.cs + + + Microsoft\OData\Core\SimpleLazy.cs + + + Microsoft\OData\Core\TaskUtils.cs + + + Microsoft\OData\Core\TypeNameOracle.cs + + + Microsoft\OData\Core\TypeUtils.cs + + + Microsoft\OData\Core\UriParser\Binders\BinaryOperatorBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\BinderBase.cs + + + Microsoft\OData\Core\UriParser\Binders\BindingState.cs + + + Microsoft\OData\Core\UriParser\Binders\ComputeBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\InBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\DottedIdentifierBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\EndPathBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\EnumBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\ExpandTreeNormalizer.cs + + + Microsoft\OData\Core\UriParser\Binders\FilterBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\FunctionCallBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\InnerPathTokenBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\KeyBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\LambdaBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\LiteralBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\MetadataBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\MetadataBindingUtils.cs + + + Microsoft\OData\Core\UriParser\Binders\OrderByBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\ParameterAliasBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\RangeVariableBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\SearchBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectExpandBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectExpandClauseFinisher.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectExpandPathBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectExpandSemanticBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectExpandSyntacticUnifier.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectPathSegmentTokenBinder.cs + + + Microsoft\OData\Core\UriParser\Binders\SelectTreeNormalizer.cs + + + Microsoft\OData\Core\UriParser\Binders\UnaryOperatorBinder.cs + + + Microsoft\OData\Core\UriParser\ExceptionUtil.cs + + + Microsoft\OData\Core\UriParser\ExpressionLexer.cs + + + Microsoft\OData\Core\UriParser\ExpressionLexerLiteralExtensions.cs + + + Microsoft\OData\Core\UriParser\ExpressionLexerUtils.cs + + + Microsoft\OData\Core\UriParser\ExpressionToken.cs + + + Microsoft\OData\Core\UriParser\FunctionSignature.cs + + + Microsoft\OData\Core\UriParser\InternalErrorCodes.cs + + + Microsoft\OData\Core\UriParser\KeyPropertyValue.cs + + + Microsoft\OData\Core\UriParser\LiteralUtils.cs + + + Microsoft\OData\Core\UriParser\NamedValue.cs + + + Microsoft\OData\Core\UriParser\ODataQueryOptionParser.cs + + + Microsoft\OData\Core\UriParser\ODataUnrecognizedPathException.cs + + + Microsoft\OData\Core\UriParser\ODataUriParser.cs + + + Microsoft\OData\Core\UriParser\ODataUriParserConfiguration.cs + + + Microsoft\OData\Core\UriParser\ODataUriParserSettings.cs + + + Microsoft\OData\Core\UriParser\OrderByDirection.cs + + + Microsoft\OData\Core\UriParser\Parsers\ExpandDepthAndCountValidator.cs + + + Microsoft\OData\Core\UriParser\Parsers\SelectExpandOptionParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\FunctionCallParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\FunctionOverloadResolver.cs + + + Microsoft\OData\Core\UriParser\Parsers\FunctionParameterParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\IFunctionCallParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\IdentifierTokenizer.cs + + + Microsoft\OData\Core\UriParser\Parsers\KeyFinder.cs + + + Microsoft\OData\Core\UriParser\Parsers\LiteralParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\NodeFactory.cs + + + Microsoft\OData\Core\UriParser\Parsers\ODataPathFactory.cs + + + Microsoft\OData\Core\UriParser\Parsers\ODataPathParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\PathReverser.cs + + + Microsoft\OData\Core\UriParser\Parsers\SearchParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\SegmentArgumentParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\SegmentKeyHandler.cs + + + Microsoft\OData\Core\UriParser\Parsers\SelectExpandParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\SelectExpandSyntacticParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\SelectExpandTermParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\UriPathParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\UriQueryExpressionParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\UriTemplateParser.cs + + + Microsoft\OData\Core\UriParser\QueryNodeUtils.cs + + + Microsoft\OData\Core\UriParser\QueryOptionUtils.cs + + + Microsoft\OData\Core\UriParser\ReadOnlyEnumerableForUriParser.cs + + + Microsoft\OData\Core\UriParser\SearchLexer.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\AllNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\AnyNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\BatchReferenceSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\BatchSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\BinaryOperatorNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionFunctionCallNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionNavigationNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionOpenPropertyAccessNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionPropertyAccessNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ConstantNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ConvertNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CountNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CountSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\DetermineNavigationSourceTranslator.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\EntityIdSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\EntitySetSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ExpandedNavigationSelectItem.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ExpandedReferenceSelectItem.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\FilterClause.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\KeyLookupNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\KeySegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\LambdaNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\LevelsClause.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\MetadataSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\NamedFunctionParameterNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\NamespaceQualifiedWildcardSelectItem.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\NavigationPropertyLinkSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\NavigationPropertySegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ODataExpandPath.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ODataPath.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ODataPathExtensions.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ODataPathSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ODataSelectPath.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ODataUnresolvedFunctionParameterAlias.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\DynamicPathSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\OperationImportSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\OperationSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\OperationSegmentParameter.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\OrderByClause.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ParameterAliasNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ParameterAliasValueAccessor.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\PathSelectItem.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\PathTemplateSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\PropertySegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\QueryNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\RangeVariable.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\RangeVariableKind.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SearchClause.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SearchTermNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SelectExpandClause.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SelectExpandClauseExtensions.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SelectItem.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleEntityNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleNavigationNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleValueFunctionCallNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleValueNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleValueOpenPropertyAccessNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleValuePropertyAccessNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingletonSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\TypeSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\UnaryOperatorNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\UriTemplateExpression.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ValueSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\WildcardSelectItem.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\AllToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\AnyToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\BinaryOperatorToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\ComputeToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\ComputeExpressionToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\InToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\CustomQueryOptionToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\DottedIdentifierToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\EndPathToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\ExpandTermToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\ExpandToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\FunctionCallToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\FunctionParameterAliasToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\FunctionParameterToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\InnerPathToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\LambdaToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\LiteralToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\NonSystemToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\OrderByToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\PathSegmentToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\PathToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\QueryToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\QueryTokenKind.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\RangeVariableToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\SelectExpandTermToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\SelectTermToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\SelectToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\StarToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\StringLiteralToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\SystemToken.cs + + + Microsoft\OData\Core\UriParser\SyntacticAst\UnaryOperatorToken.cs + + + Microsoft\OData\Core\UriParser\TreeNodeKinds\BinaryOperatorKind.cs + + + Microsoft\OData\Core\UriParser\TreeNodeKinds\ExpressionTokenKind.cs + + + Microsoft\OData\Core\UriParser\TreeNodeKinds\QueryNodeKind.cs + + + Microsoft\OData\Core\UriParser\TreeNodeKinds\RequestTargetKind.cs + + + Microsoft\OData\Core\UriParser\TreeNodeKinds\UnaryOperatorKind.cs + + + Microsoft\OData\Core\UriParser\TypePromotionUtils.cs + + + Microsoft\OData\Core\UriParser\UriQueryConstants.cs + + + Microsoft\OData\Core\UriParser\Visitors\IPathSegmentTokenVisitor.cs + + + Microsoft\OData\Core\UriParser\Visitors\ISyntacticTreeVisitor.cs + + + Microsoft\OData\Core\UriParser\Visitors\IsCollectionTranslator.cs + + + Microsoft\OData\Core\UriParser\Visitors\PathSegmentHandler.cs + + + Microsoft\OData\Core\UriParser\Visitors\PathSegmentToContextUrlPathTranslator.cs + + + Microsoft\OData\Core\UriParser\Visitors\PathSegmentToStringTranslator.cs + + + Microsoft\OData\Core\UriParser\Visitors\PathSegmentTokenEqualityComparer.cs + + + Microsoft\OData\Core\UriParser\Visitors\PathSegmentTokenVisitor.cs + + + Microsoft\OData\Core\UriParser\Visitors\PathSegmentTranslator.cs + + + Microsoft\OData\Core\UriParser\Visitors\QueryNodeVisitor.cs + + + Microsoft\OData\Core\UriParser\Visitors\SelectItemHandler.cs + + + Microsoft\OData\Core\UriParser\Visitors\SelectItemTranslator.cs + + + Microsoft\OData\Core\UriParser\Visitors\SelectPropertyVisitor.cs + + + Microsoft\OData\Core\UriParser\Visitors\SplitEndingSegmentOfTypeHandler.cs + + + Microsoft\OData\Core\UriParser\Visitors\SyntacticTreeVisitor.cs + + + Microsoft\OData\Core\UriUtils.cs + + + Microsoft\OData\Core\Utils.cs + + + Microsoft\OData\Core\ValidationUtils.cs + + + Microsoft\OData\Core\WriterUtils.cs + + + Microsoft\OData\Core\WriterValidationUtils.cs + + + Microsoft\OData\Core\EdmValueParser.cs + + + Microsoft\OData\Core\EdmValueWriter.cs + + + Microsoft\OData\Core\PlatformHelper.cs + + + GlobalSuppressions.cs + + + + + Microsoft\OData\Core\UriParser\Aggregation\AggregationMethod.cs + + + Microsoft\OData\Core\UriParser\Aggregation\ApplyBinder.cs + + + Microsoft\OData\Core\UriParser\Aggregation\AggregateTransformationNode.cs + + + Microsoft\OData\Core\UriParser\Aggregation\AggregateExpression.cs + + + Microsoft\OData\Core\UriParser\Aggregation\ApplyClause.cs + + + Microsoft\OData\Core\UriParser\Aggregation\ComputeTransformationNode.cs + + + Microsoft\OData\Core\UriParser\Aggregation\ExpandTransformationNode.cs + + + Microsoft\OData\Core\UriParser\Aggregation\GroupByTransformationNode.cs + + + Microsoft\OData\Core\UriParser\Aggregation\FilterTransformationNode.cs + + + Microsoft\OData\Core\UriParser\Aggregation\TransformationNode.cs + + + Microsoft\OData\Core\UriParser\Aggregation\AggregateToken.cs + + + Microsoft\OData\Core\UriParser\Aggregation\GroupByToken.cs + + + Microsoft\OData\Core\UriParser\Aggregation\GroupByPropertyNode.cs + + + Microsoft\OData\Core\UriParser\Aggregation\TransformationNodeKind.cs + + + Microsoft\OData\Core\UriParser\Aggregation\ApplyTransformationToken.cs + + + Microsoft\OData\Core\UriParser\Aggregation\EntitySetAggregateExpression.cs + + + Microsoft\OData\Core\UriParser\Aggregation\EntitySetAggregateToken.cs + + + Microsoft\OData\Core\UriParser\Aggregation\AggregateExpressionBase.cs + + + Microsoft\OData\Core\UriParser\Aggregation\AggregateTokenBase.cs + + + Microsoft\OData\Core\UriParser\Aggregation\AggregateExpressionToken.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\AggregatedCollectionPropertyNode.cs + + + Microsoft\OData\Core\Value\ODataCollectionValue.cs + + + Microsoft\OData\Core\Value\ODataEnumValue.cs + + + Microsoft\OData\Core\Value\ODataNullValue.cs + + + Microsoft\OData\Core\Value\ODataPrimitiveValue.cs + + + Microsoft\OData\Core\Value\ODataResourceValue.cs + + + Microsoft\OData\Core\Value\ODataStreamReferenceValue.cs + + + Microsoft\OData\Core\Value\ODataBinaryStreamValue.cs + + + Microsoft\OData\Core\Value\ODataUntypedValue.cs + + + Microsoft\OData\Core\Value\ODataValue.cs + + + Microsoft\OData\Core\Value\ODataValueUtils.cs + + + Microsoft\OData\Core\BindingPathHelper.cs + + + Microsoft\OData\Core\Buffers\BufferUtils.cs + + + Microsoft\OData\Core\DerivedTypeValidator.cs + + + Microsoft\OData\Core\Buffers\ICharArrayPool.cs + + + Microsoft\OData\Core\DuplicatePropertyNameChecker.cs + + + Microsoft\OData\Core\EdmExtensionMethods.cs + + + Microsoft\OData\Core\ODataConventionalEntityMetadataBuilder.cs + + + Microsoft\OData\Core\IContainerProvider.cs + + + Microsoft\OData\Core\IDuplicatePropertyNameChecker.cs + + + Microsoft\OData\Core\IReaderValidator.cs + + + Microsoft\OData\Core\IWriterValidator.cs + + + Microsoft\OData\Core\Json\IJsonReader.cs + + + Microsoft\OData\Core\Json\IJsonReaderFactory.cs + + + Microsoft\OData\Core\Json\IJsonWriterFactory.cs + + + Microsoft\OData\Core\ODataBatchPayloadUriOptions.cs + + + Microsoft\OData\Core\ODataEdmPropertyAnnotation.cs + + + Microsoft\OData\Core\ODataLibraryCompatibility.cs + + + Microsoft\OData\Core\ODataNestedResourceInfoSerializationInfo.cs + + + Microsoft\OData\Core\ODataNullValueBehaviorKind.cs + + + Microsoft\OData\Core\PropertyAndAnnotationCollector.cs + + + PropertyCache.cs + + + Microsoft\OData\Core\PropertyCacheHandler.cs + + + PropertyMetadataTypeInfo.cs + + + Microsoft\OData\Core\PropertySerializationInfo.cs + + + PropertyValueTypeInfo.cs + + + Microsoft\OData\Core\ReaderValidator.cs + + + Microsoft\OData\Core\ServicePrototype.cs + + + Microsoft\OData\Core\ServiceProviderExtensions.cs + + + Microsoft\OData\Core\UnknownEntitySet.cs + + + Microsoft\OData\Core\UriParser\BuiltInUriFunctions.cs + + + Microsoft\OData\Core\UriParser\CustomUriFunctions.cs + + + Microsoft\OData\Core\UriParser\FunctionSignatureWithReturnType.cs + + + Microsoft\OData\Core\UriParser\ODataPathInfo.cs + + + Microsoft\OData\Core\UriParser\ParseDynamicPathSegment.cs + + + Microsoft\OData\Core\UriParser\Parsers\CustomUriLiteralParsers.cs + + + Microsoft\OData\Core\UriParser\Parsers\DefaultUriLiteralParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\IUriLiteralParser.cs + + + Microsoft\OData\Core\UriParser\Parsers\PathParserModelUtils.cs + + + Microsoft\OData\Core\UriParser\Parsers\UriLiteralParsingException.cs + + + Microsoft\OData\Core\UriParser\Parsers\UriPrimitiveTypeParser.cs + + + Microsoft\OData\Core\UriParser\Resolver\AlternateKeysODataUriResolver.cs + + + Microsoft\OData\Core\UriParser\Resolver\ODataUriResolver.cs + + + Microsoft\OData\Core\UriParser\Resolver\StringAsEnumResolver.cs + + + Microsoft\OData\Core\UriParser\Resolver\UnqualifiedODataUriResolver.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\AnnotationSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\InNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionComplexNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionConstantNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionResourceCastNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionResourceFunctionCallNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CollectionResourceNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ComputeClause.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ComputeExpression.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\CountVirtualPropertyNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\EachSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\FilterSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\NonResourceRangeVariable.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\NonResourceRangeVariableReferenceNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ReferenceSegment.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ResourceRangeVariable.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\ResourceRangeVariableReferenceNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleComplexNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleValueCastNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleResourceCastNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleResourceFunctionCallNode.cs + + + Microsoft\OData\Core\UriParser\SemanticAst\SingleResourceNode.cs + + + Microsoft\OData\Core\UriParser\TypeFacetsPromotionRules.cs + + + Microsoft\OData\Core\UriParser\UriEdmHelpers.cs + + + Microsoft\OData\Core\UriParser\Visitors\PathSegmentToResourcePathTranslator.cs + + + Microsoft\OData\Core\Uri\ExpressionConstants.cs + + + Microsoft\OData\Core\Uri\NodeToStringBuilder.cs + + + Microsoft\OData\Core\Uri\ODataUri.cs + + + Microsoft\OData\Core\Uri\ODataUriConversionUtils.cs + + + Microsoft\OData\Core\Uri\ODataUriExtensions.cs + + + Microsoft\OData\Core\Uri\ODataUriUtils.cs + + + Microsoft\OData\Core\Uri\ODataUrlKeyDelimiter.cs + + + Microsoft\OData\Core\Uri\SelectExpandClauseToStringBuilder.cs + + + Microsoft\OData\Core\Uri\ApplyClauseToStringBuilder.cs + + + Microsoft\OData\Core\UriParser\CustomUriLiteralPrefixes.cs + + + Microsoft\OData\Core\UriParser\Parsers\UriParserHelper.cs + + + Microsoft\OData\Core\UriParser\UriFunctionsHelper.cs + + + Microsoft\OData\Core\ValidationKinds.cs + + + Microsoft\OData\Core\WriterValidator.cs + + + + false + + + + + true + + + true + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/ShippingAssemblyAttributes.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/ShippingAssemblyAttributes.cs new file mode 100644 index 0000000..2c84538 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.Net35/ShippingAssemblyAttributes.cs @@ -0,0 +1,9 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma warning disable 436 +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Query.TDD.Tests.NetFX35" + AssemblyRef.TestPublicKey)] +#pragma warning restore 436 \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj new file mode 100644 index 0000000..bb18f41 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj @@ -0,0 +1,1925 @@ + + + + Microsoft.OData.Core + Library + v5.0 + + + .NETPortable + true + true + false + Microsoft.OData.Core + {1A263F63-1859-4310-957B-4F8875019896} + $(AssemblyName).xml + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + 14.0 + 14.0 + $(DefineConstants);ODATA_CORE;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + true + + + ..\..\..\bin\AnyCPU\Debug\Product\.NETPortable\v5.0\Microsoft.OData.Core\ + + + ..\..\..\bin\AnyCPU\Release\Product\.NETPortable\v5.0\Microsoft.OData.Core\ + + + ..\..\..\bin\AnyCPU\Cover\Product\.NETPortable\v5.0\Microsoft.OData.Core\ + + + + + + {DB301FA8-1CFA-487D-BA1E-803016E2A85C} + Microsoft.OData.Edm.NetStandard + + + {48142D27-C862-4F4F-A781-1A9B72F6CDFA} + Microsoft.Spatial.NetStandard + + + + + + Microsoft.OData.Core + true + true + internal + true + Microsoft.OData.Core.TextRes + + + + + EdmValueParser.cs + + + EdmValueWriter.cs + + + PlatformHelper.cs + + + AnnotationFilter.cs + + + AnnotationFilterPattern.cs + + + AsyncBufferedStream.cs + + + BindingPathHelper.cs + + + BufferedReadStream.cs + + + BufferingReadStream.cs + + + Buffers\BufferUtils.cs + + + Buffers\ICharArrayPool.cs + + + CollectionWithoutExpectedTypeValidator.cs + + + ContainerBuilderExtensions.cs + + + DerivedTypeValidator.cs + + + DuplicatePropertyNameChecker.cs + + + EdmExtensionMethods.cs + + + ErrorUtils.cs + + + Evaluation\EdmValueUtils.cs + + + Evaluation\IODataResourceMetadataContext.cs + + + Evaluation\KeySerializer.cs + + + Evaluation\LiteralFormatter.cs + + + Evaluation\NoOpResourceMetadataBuilder.cs + + + Evaluation\ODataConventionalEntityMetadataBuilder.cs + + + Evaluation\ODataConventionalResourceMetadataBuilder.cs + + + Evaluation\ODataConventionalUriBuilder.cs + + + Evaluation\ODataMetadataContext.cs + + + Evaluation\ODataMissingOperationGenerator.cs + + + Evaluation\ODataResourceMetadataBuilder.cs + + + Evaluation\ODataResourceMetadataContext.cs + + + Evaluation\ODataUriBuilder.cs + + + ExceptionUtils.cs + + + GeographyTypeConverter.cs + + + GeometryTypeConverter.cs + + + HttpHeaderValue.cs + + + HttpHeaderValueElement.cs + + + HttpHeaderValueLexer.cs + + + HttpUtils.cs + + + IContainerBuilder.cs + + + IContainerProvider.cs + + + IDuplicatePropertyNameChecker.cs + + + InstanceAnnotationWriteTracker.cs + + + InternalErrorCodes.cs + + + InternalErrorCodesCommon.cs + + + IODataStreamListener.cs + + + IODataOutputInStreamErrorListener.cs + + + IODataPayloadUriConverter.cs + + + IODataReaderWriterListener.cs + + + IODataRequestMessage.cs + + + IODataRequestMessageAsync.cs + + + IODataResourceTypeContext.cs + + + IODataResponseMessage.cs + + + IODataResponseMessageAsync.cs + + + IPrimitiveTypeConverter.cs + + + IReaderValidator.cs + + + IWriterValidator.cs + + + JsonLight\IODataJsonLightReaderResourceState.cs + + + JsonLight\IODataJsonLightWriterResourceState.cs + + + JsonLight\JsonFullMetadataLevel.cs + + + JsonLight\JsonFullMetadataTypeNameOracle.cs + + + JsonLight\JsonLightConstants.cs + + + JsonLight\JsonLightMetadataLevel.cs + + + JsonLight\JsonLightODataAnnotationWriter.cs + + + JsonLight\JsonLightTypeNameOracle.cs + + + JsonLight\JsonMinimalMetadataLevel.cs + + + JsonLight\JsonMinimalMetadataTypeNameOracle.cs + + + JsonLight\JsonNoMetadataLevel.cs + + + JsonLight\JsonNoMetadataTypeNameOracle.cs + + + JsonLight\ODataAnnotationNames.cs + + + JsonLight\ODataJsonLightCollectionDeserializer.cs + + + JsonLight\ODataJsonLightCollectionReader.cs + + + JsonLight\ODataJsonLightCollectionSerializer.cs + + + JsonLight\ODataJsonLightCollectionWriter.cs + + + JsonLight\ODataJsonLightContextUriParser.cs + + + JsonLight\ODataJsonLightContextUriParseResult.cs + + + JsonLight\ODataJsonLightDeltaReader.cs + + + JsonLight\ODataJsonLightDeltaWriter.cs + + + JsonLight\ODataJsonLightDeserializer.cs + + + JsonLight\ODataJsonLightEntityReferenceLinkDeserializer.cs + + + JsonLight\ODataJsonLightEntityReferenceLinkSerializer.cs + + + JsonLight\ODataJsonLightErrorDeserializer.cs + + + JsonLight\ODataJsonLightInputContext.cs + + + JsonLight\ODataJsonLightOutputContext.cs + + + JsonLight\ODataJsonLightBatchAtomicGroupCache.cs + + + JsonLight\ODataJsonLightBatchPayloadItemPropertiesCache.cs + + + JsonLight\ODataJsonLightBatchReader.cs + + + JsonLight\ODataJsonLightBatchWriter.cs + + + JsonLight\ODataJsonLightBatchReaderStream.cs + + + JsonLight\ODataJsonLightBatchBodyContentReaderStream.cs + + + JsonLight\ODataJsonLightParameterDeserializer.cs + + + JsonLight\ODataJsonLightParameterReader.cs + + + JsonLight\ODataJsonLightParameterWriter.cs + + + JsonLight\ODataJsonLightPayloadKindDetectionDeserializer.cs + + + JsonLight\ODataJsonLightPropertyAndValueDeserializer.cs + + + JsonLight\ODataJsonLightPropertySerializer.cs + + + JsonLight\ODataJsonLightReader.cs + + + JsonLight\ODataJsonLightReaderNestedResourceInfo.cs + + + JsonLight\ODataJsonLightReaderNestedPropertyInfo.cs + + + JsonLight\ODataJsonLightReaderNestedInfo.cs + + + JsonLight\ODataJsonLightReaderNestedStreamInfo.cs + + + JsonLight\ODataJsonLightReaderUtils.cs + + + JsonLight\ODataJsonLightResourceDeserializer.cs + + + JsonLight\ODataJsonLightResourceSerializer.cs + + + JsonLight\ODataJsonLightSerializer.cs + + + JsonLight\ODataJsonLightServiceDocumentDeserializer.cs + + + JsonLight\ODataJsonLightServiceDocumentSerializer.cs + + + JsonLight\ODataJsonLightUtils.cs + + + JsonLight\ODataJsonLightValidationUtils.cs + + + JsonLight\ODataJsonLightValueSerializer.cs + + + JsonLight\ODataJsonLightWriter.cs + + + JsonLight\ODataJsonLightWriterUtils.cs + + + JsonLight\ReorderingJsonReader.cs + + + Json\BufferingJsonReader.cs + + + Json\DefaultJsonReaderFactory.cs + + + Json\DefaultJsonWriterFactory.cs + + + Json\GeoJsonWriterAdapter.cs + + + Json\IJsonReader.cs + + + Json\IJsonStreamReader.cs + + + Json\IJsonStreamWriter.cs + + + Json\ODataJsonTextWriter.cs + + + Json\IJsonReaderFactory.cs + + + Json\IJsonWriter.cs + + + Json\IJsonWriterFactory.cs + + + Json\IODataJsonOperationsDeserializerContext.cs + + + Json\JsonConstants.cs + + + Json\JsonLightInstanceAnnotationWriter.cs + + + Json\JsonNodeType.cs + + + Json\JsonReader.cs + + + Json\JsonReaderExtensions.cs + + + Json\JsonSharedUtils.cs + + + Json\JsonValueUtils.cs + + + Json\JsonWriter.cs + + + Json\JsonWriterExtensions.cs + + + Json\NonIndentedTextWriter.cs + + + Json\ODataJsonFormat.cs + + + Json\ODataJsonReaderCoreUtils.cs + + + Json\ODataJsonWriterUtils.cs + + + Json\ODataStringEscapeOption.cs + + + Json\TextWriterWrapper.cs + + + MediaTypeUtils.cs + + + MessageStreamWrapper.cs + + + Metadata\BufferingXmlReader.cs + + + Metadata\CachedPrimitiveKeepInContentAnnotation.cs + + + Metadata\EdmConstants.cs + + + Metadata\EdmLibraryExtensions.cs + + + Metadata\EdmTypeReaderResolver.cs + + + Metadata\EdmTypeResolver.cs + + + Metadata\EdmTypeWriterResolver.cs + + + Metadata\MetadataUtils.cs + + + Metadata\MetadataUtilsCommon.cs + + + Metadata\ODataAtomErrorDeserializer.cs + + + Metadata\ODataMetadataConstants.cs + + + Metadata\ODataMetadataReaderUtils.cs + + + Metadata\ODataMetadataWriterUtils.cs + + + Metadata\XmlReaderExtensions.cs + + + MimeConstants.cs + + + MultipartMixed\ODataMultipartMixedBatchFormat.cs + + + MultipartMixed\ODataMultipartMixedBatchInputContext.cs + + + MultipartMixed\ODataMultipartMixedBatchOutputContext.cs + + + MultipartMixed\ODataMultipartMixedBatchReader.cs + + + MultipartMixed\ODataMultipartMixedBatchReaderStream.cs + + + MultipartMixed\ODataMultipartMixedBatchWriter.cs + + + MultipartMixed\ODataMultipartMixedBatchWriterUtils.cs + + + MultipartMixed\DependsOnIdsTracker.cs + + + NonDisposingStream.cs + + + ODataAction.cs + + + ODataAnnotatable.cs + + + ODataAsynchronousReader.cs + + + ODataAsynchronousResponseMessage.cs + + + ODataAsynchronousWriter.cs + + + ODataBatchOperationHeaders.cs + + + ODataBatchOperationMessage.cs + + + ODataReadStream.cs + + + ODataBatchOperationRequestMessage.cs + + + ODataBatchOperationResponseMessage.cs + + + ODataStream.cs + + + ODataWriteStream.cs + + + ODataBatchPayloadUriConverter.cs + + + ODataBatchPayloadUriOptions.cs + + + ODataBatchReader.cs + + + ODataBatchReaderState.cs + + + ODataBatchReaderStream.cs + + + ODataBatchReaderStreamBuffer.cs + + + ODataBatchReaderStreamScanResult.cs + + + ODataBatchUtils.cs + + + ODataBatchWriter.cs + + + ODataBinaryStreamReader.cs + + + ODataBinaryStreamWriter.cs + + + ODataTextStreamReader.cs + + + ODataNotificationStream.cs + + + ODataNotificationReader.cs + + + ODataNotificationWriter.cs + + + ODataCollectionReader.cs + + + ODataCollectionReaderCore.cs + + + ODataCollectionReaderCoreAsync.cs + + + ODataCollectionReaderState.cs + + + ODataCollectionStart.cs + + + ODataCollectionStartSerializationInfo.cs + + + ODataCollectionWriter.cs + + + ODataCollectionWriterCore.cs + + + ODataConstants.cs + + + ODataContentTypeException.cs + + + ODataContextUriBuilder.cs + + + ODataContextUrlInfo.cs + + + ODataContextUrlLevel.cs + + + ODataDeltaDeletedEntry.cs + + + ODataDeltaDeletedLink.cs + + + ODataDeltaKind.cs + + + ODataDeltaLink.cs + + + ODataDeltaLinkBase.cs + + + ODataDeltaReader.cs + + + ODataDeltaReaderState.cs + + + ODataDeltaResourceSet.cs + + + ODataDeltaResourceSetSerializationInfo.cs + + + ODataDeltaSerializationInfo.cs + + + ODataDeltaWriter.cs + + + ODataDeserializer.cs + + + ODataEdmPropertyAnnotation.cs + + + ODataEntityReferenceLink.cs + + + ODataEntityReferenceLinks.cs + + + ODataEntitySetInfo.cs + + + ODataError.cs + + + ODataErrorDetail.cs + + + ODataErrorException.cs + + + ODataException.cs + + + ODataFormat.cs + + + ODataFunction.cs + + + ODataFunctionImportInfo.cs + + + ODataInnerError.cs + + + ODataInputContext.cs + + + ODataInstanceAnnotation.cs + + + ODataItem.cs + + + ODataJsonDateTimeFormat.cs + + + ODataLibraryCompatibility.cs + + + ODataMediaType.cs + + + ODataMediaTypeFormat.cs + + + ODataMediaTypeResolver.cs + + + ODataMessage.cs + + + ODataMessageExtensions.cs + + + ODataMessageInfo.cs + + + ODataMessageQuotas.cs + + + ODataMessageReader.cs + + + ODataMessageReaderSettings.cs + + + ODataMessageWriter.cs + + + ODataMessageWriterSettings.cs + + + ODataMetadataFormat.cs + + + ODataMetadataInputContext.cs + + + ODataMetadataOutputContext.cs + + + ODataNestedResourceInfo.cs + + + ODataNestedResourceInfoSerializationInfo.cs + + + ODataNullValueBehaviorKind.cs + + + ODataObjectModelExtensions.cs + + + ODataOperation.cs + + + ODataOutputContext.cs + + + ODataParameterReader.cs + + + ODataParameterReaderCore.cs + + + ODataParameterReaderCoreAsync.cs + + + ODataParameterReaderState.cs + + + ODataParameterWriter.cs + + + ODataParameterWriterCore.cs + + + ODataPayloadKind.cs + + + ODataPayloadKindDetectionInfo.cs + + + ODataPayloadKindDetectionResult.cs + + + ODataPayloadValueConverter.cs + + + ODataPreferenceHeader.cs + + + IODataStreamReferenceInfo.cs + + + ODataStreamItem.cs + + + ODataPropertyInfo.cs + + + ODataStreamPropertyInfo.cs + + + ODataProperty.cs + + + ODataPropertyKind.cs + + + ODataPropertySerializationInfo.cs + + + ODataRawInputContext.cs + + + ODataRawOutputContext.cs + + + ODataRawValueConverter.cs + + + ODataRawValueFormat.cs + + + ODataRawValueUtils.cs + + + ODataReader.cs + + + ODataReaderCore.cs + + + ODataReaderCoreAsync.cs + + + ODataReaderState.cs + + + ODataRequestMessage.cs + + + ODataResource.cs + + + ODataResourceSerializationInfo.cs + + + ODataResourceSet.cs + + + ODataResourceSetBase.cs + + + ODataResourceTypeContext.cs + + + ODataResponseMessage.cs + + + ODataSerializer.cs + + + ODataServiceDocument.cs + + + ODataServiceDocumentElement.cs + + + ODataSimplifiedOptions.cs + + + ODataSingletonInfo.cs + + + ODataTypeAnnotation.cs + + + ODataUtils.cs + + + ODataUtilsInternal.cs + + + ODataVersion.cs + + + ODataVersionCache.cs + + + ODataWriter.cs + + + ODataWriterCore.cs + + + PrimitiveConverter.cs + + + PropertyAndAnnotationCollector.cs + + + PropertyCache.cs + + + PropertyCacheHandler.cs + + + PropertyMetadataTypeInfo.cs + + + PropertySerializationInfo.cs + + + PropertyValueTypeInfo.cs + + + RawValueWriter.cs + + + ReaderUtils.cs + + + ReaderValidationUtils.cs + + + ReaderValidator.cs + + + ReadOnlyEnumerable.cs + + + ReadOnlyEnumerableExtensions.cs + + + ReadOnlyEnumerableOfT.cs + + + ReferenceEqualityComparer.cs + + + ResourceSetWithoutExpectedTypeValidator.cs + + + SelectedPropertiesNode.cs + + + ServiceLifetime.cs + + + ServicePrototype.cs + + + ServiceProviderExtensions.cs + + + SimpleLazy.cs + + + TaskUtils.cs + + + TypeNameOracle.cs + + + TypeUtils.cs + + + UnknownEntitySet.cs + + + UriParser\Aggregation\AggregateExpression.cs + + + UriParser\Aggregation\AggregateToken.cs + + + UriParser\Aggregation\AggregationMethod.cs + + + UriParser\Aggregation\ApplyBinder.cs + + + UriParser\Aggregation\ApplyClause.cs + + + UriParser\Aggregation\ApplyTransformationToken.cs + + + UriParser\Aggregation\ComputeTransformationNode.cs + + + UriParser\Aggregation\ExpandTransformationNode.cs + + + UriParser\Aggregation\FilterTransformationNode.cs + + + UriParser\Aggregation\GroupByPropertyNode.cs + + + UriParser\Aggregation\GroupByToken.cs + + + UriParser\Aggregation\GroupByTransformationNode.cs + + + UriParser\Aggregation\TransformationNode.cs + + + UriParser\Aggregation\TransformationNodeKind.cs + + + UriParser\Aggregation\EntitySetAggregateExpression.cs + + + UriParser\Aggregation\EntitySetAggregateToken.cs + + + UriParser\Aggregation\AggregateExpressionBase.cs + + + UriParser\Aggregation\AggregateTokenBase.cs + + + UriParser\Aggregation\AggregateExpressionToken.cs + + + UriParser\Aggregation\AggregateTransformationNode.cs + + + UriParser\SemanticAst\AggregatedCollectionPropertyNode.cs + + + UriParser\Binders\BinaryOperatorBinder.cs + + + UriParser\Binders\BinderBase.cs + + + UriParser\Binders\BindingState.cs + + + UriParser\Binders\ComputeBinder.cs + + + UriParser\Binders\InBinder.cs + + + UriParser\Binders\DottedIdentifierBinder.cs + + + UriParser\Binders\EndPathBinder.cs + + + UriParser\Binders\EnumBinder.cs + + + UriParser\Binders\ExpandTreeNormalizer.cs + + + UriParser\Binders\FilterBinder.cs + + + UriParser\Binders\FunctionCallBinder.cs + + + UriParser\Binders\InnerPathTokenBinder.cs + + + UriParser\Binders\KeyBinder.cs + + + UriParser\Binders\LambdaBinder.cs + + + UriParser\Binders\LiteralBinder.cs + + + UriParser\Binders\MetadataBinder.cs + + + UriParser\Binders\MetadataBindingUtils.cs + + + UriParser\Binders\OrderByBinder.cs + + + UriParser\Binders\ParameterAliasBinder.cs + + + UriParser\Binders\RangeVariableBinder.cs + + + UriParser\Binders\SearchBinder.cs + + + UriParser\Binders\SelectBinder.cs + + + UriParser\Binders\SelectExpandBinder.cs + + + UriParser\Binders\SelectExpandClauseFinisher.cs + + + UriParser\Binders\SelectExpandPathBinder.cs + + + UriParser\Binders\SelectExpandSemanticBinder.cs + + + UriParser\Binders\SelectExpandSyntacticUnifier.cs + + + UriParser\Binders\SelectPathSegmentTokenBinder.cs + + + UriParser\Binders\SelectTreeNormalizer.cs + + + UriParser\Binders\UnaryOperatorBinder.cs + + + UriParser\BuiltInUriFunctions.cs + + + UriParser\CustomUriFunctions.cs + + + UriParser\CustomUriLiteralPrefixes.cs + + + UriParser\ExceptionUtil.cs + + + UriParser\ExpressionLexer.cs + + + UriParser\ExpressionLexerLiteralExtensions.cs + + + UriParser\ExpressionLexerUtils.cs + + + UriParser\ExpressionToken.cs + + + UriParser\FunctionSignature.cs + + + UriParser\FunctionSignatureWithReturnType.cs + + + UriParser\InternalErrorCodes.cs + + + UriParser\KeyPropertyValue.cs + + + UriParser\LiteralUtils.cs + + + UriParser\NamedValue.cs + + + UriParser\ODataPathInfo.cs + + + UriParser\ODataQueryOptionParser.cs + + + UriParser\ODataUnrecognizedPathException.cs + + + UriParser\ODataUriParser.cs + + + UriParser\ODataUriParserConfiguration.cs + + + UriParser\ODataUriParserSettings.cs + + + UriParser\OrderByDirection.cs + + + UriParser\ParseDynamicPathSegment.cs + + + UriParser\Parsers\CustomUriLiteralParsers.cs + + + UriParser\Parsers\DefaultUriLiteralParser.cs + + + UriParser\Parsers\ExpandDepthAndCountValidator.cs + + + UriParser\Parsers\SelectExpandOptionParser.cs + + + UriParser\Parsers\FunctionCallParser.cs + + + UriParser\Parsers\FunctionOverloadResolver.cs + + + UriParser\Parsers\FunctionParameterParser.cs + + + UriParser\Parsers\IdentifierTokenizer.cs + + + UriParser\Parsers\IFunctionCallParser.cs + + + UriParser\Parsers\IUriLiteralParser.cs + + + UriParser\Parsers\KeyFinder.cs + + + UriParser\Parsers\LiteralParser.cs + + + UriParser\Parsers\NodeFactory.cs + + + UriParser\Parsers\ODataPathFactory.cs + + + UriParser\Parsers\ODataPathParser.cs + + + UriParser\Parsers\PathParserModelUtils.cs + + + UriParser\Parsers\PathReverser.cs + + + UriParser\Parsers\SearchParser.cs + + + UriParser\Parsers\SegmentArgumentParser.cs + + + UriParser\Parsers\SegmentKeyHandler.cs + + + UriParser\Parsers\SelectExpandParser.cs + + + UriParser\Parsers\SelectExpandSyntacticParser.cs + + + UriParser\Parsers\SelectExpandTermParser.cs + + + UriParser\Parsers\UriLiteralParsingException.cs + + + UriParser\Parsers\UriParserHelper.cs + + + UriParser\Parsers\UriPathParser.cs + + + UriParser\Parsers\UriPrimitiveTypeParser.cs + + + UriParser\Parsers\UriQueryExpressionParser.cs + + + UriParser\Parsers\UriTemplateParser.cs + + + UriParser\QueryNodeUtils.cs + + + UriParser\QueryOptionUtils.cs + + + UriParser\ReadOnlyEnumerableForUriParser.cs + + + UriParser\Resolver\AlternateKeysODataUriResolver.cs + + + UriParser\Resolver\ODataUriResolver.cs + + + UriParser\Resolver\StringAsEnumResolver.cs + + + UriParser\Resolver\UnqualifiedODataUriResolver.cs + + + UriParser\SearchLexer.cs + + + UriParser\SemanticAst\AllNode.cs + + + UriParser\SemanticAst\AnyNode.cs + + + UriParser\SemanticAst\BatchReferenceSegment.cs + + + UriParser\SemanticAst\BatchSegment.cs + + + UriParser\SemanticAst\BinaryOperatorNode.cs + + + UriParser\SemanticAst\CollectionComplexNode.cs + + + UriParser\SemanticAst\CollectionConstantNode.cs + + + UriParser\SemanticAst\CollectionFunctionCallNode.cs + + + UriParser\SemanticAst\CollectionNavigationNode.cs + + + UriParser\SemanticAst\CollectionNode.cs + + + UriParser\SemanticAst\CollectionOpenPropertyAccessNode.cs + + + UriParser\SemanticAst\CollectionPropertyAccessNode.cs + + + UriParser\SemanticAst\CollectionResourceCastNode.cs + + + UriParser\SemanticAst\CollectionResourceFunctionCallNode.cs + + + UriParser\SemanticAst\CollectionResourceNode.cs + + + UriParser\SemanticAst\ComputeClause.cs + + + UriParser\SemanticAst\ComputeExpression.cs + + + UriParser\SemanticAst\ConstantNode.cs + + + UriParser\SemanticAst\ConvertNode.cs + + + UriParser\SemanticAst\CountNode.cs + + + UriParser\SemanticAst\CountSegment.cs + + + UriParser\SemanticAst\CountVirtualPropertyNode.cs + + + UriParser\SemanticAst\EachSegment.cs + + + UriParser\SemanticAst\FilterSegment.cs + + + UriParser\SemanticAst\DetermineNavigationSourceTranslator.cs + + + UriParser\SemanticAst\DynamicPathSegment.cs + + + UriParser\SemanticAst\EntityIdSegment.cs + + + UriParser\SemanticAst\EntitySetSegment.cs + + + UriParser\SemanticAst\ExpandedNavigationSelectItem.cs + + + UriParser\SemanticAst\ExpandedReferenceSelectItem.cs + + + UriParser\SemanticAst\FilterClause.cs + + + UriParser\SemanticAst\KeyLookupNode.cs + + + UriParser\SemanticAst\KeySegment.cs + + + UriParser\SemanticAst\LambdaNode.cs + + + UriParser\SemanticAst\LevelsClause.cs + + + UriParser\SemanticAst\AnnotationSegment.cs + + + UriParser\SemanticAst\InNode.cs + + + UriParser\SemanticAst\MetadataSegment.cs + + + UriParser\SemanticAst\NamedFunctionParameterNode.cs + + + UriParser\SemanticAst\NamespaceQualifiedWildcardSelectItem.cs + + + UriParser\SemanticAst\NavigationPropertyLinkSegment.cs + + + UriParser\SemanticAst\NavigationPropertySegment.cs + + + UriParser\SemanticAst\NonResourceRangeVariable.cs + + + UriParser\SemanticAst\NonResourceRangeVariableReferenceNode.cs + + + UriParser\SemanticAst\ODataExpandPath.cs + + + UriParser\SemanticAst\ODataPath.cs + + + UriParser\SemanticAst\ODataPathExtensions.cs + + + UriParser\SemanticAst\ODataPathSegment.cs + + + UriParser\SemanticAst\ODataSelectPath.cs + + + UriParser\SemanticAst\ODataUnresolvedFunctionParameterAlias.cs + + + UriParser\SemanticAst\OperationImportSegment.cs + + + UriParser\SemanticAst\OperationSegment.cs + + + UriParser\SemanticAst\OperationSegmentParameter.cs + + + UriParser\SemanticAst\OrderByClause.cs + + + UriParser\SemanticAst\ParameterAliasNode.cs + + + UriParser\SemanticAst\ParameterAliasValueAccessor.cs + + + UriParser\SemanticAst\PathSelectItem.cs + + + UriParser\SemanticAst\PathTemplateSegment.cs + + + UriParser\SemanticAst\PropertySegment.cs + + + UriParser\SemanticAst\QueryNode.cs + + + UriParser\SemanticAst\RangeVariable.cs + + + UriParser\SemanticAst\RangeVariableKind.cs + + + UriParser\SemanticAst\ReferenceSegment.cs + + + UriParser\SemanticAst\ResourceRangeVariable.cs + + + UriParser\SemanticAst\ResourceRangeVariableReferenceNode.cs + + + UriParser\SemanticAst\SearchClause.cs + + + UriParser\SemanticAst\SearchTermNode.cs + + + UriParser\SemanticAst\SelectExpandClause.cs + + + UriParser\SemanticAst\SelectExpandClauseExtensions.cs + + + UriParser\SemanticAst\SelectItem.cs + + + UriParser\SemanticAst\SingleComplexNode.cs + + + UriParser\SemanticAst\SingleEntityNode.cs + + + UriParser\SemanticAst\SingleNavigationNode.cs + + + UriParser\SemanticAst\SingleValueCastNode.cs + + + UriParser\SemanticAst\SingleResourceCastNode.cs + + + UriParser\SemanticAst\SingleResourceFunctionCallNode.cs + + + UriParser\SemanticAst\SingleResourceNode.cs + + + UriParser\SemanticAst\SingletonSegment.cs + + + UriParser\SemanticAst\SingleValueFunctionCallNode.cs + + + UriParser\SemanticAst\SingleValueNode.cs + + + UriParser\SemanticAst\SingleValueOpenPropertyAccessNode.cs + + + UriParser\SemanticAst\SingleValuePropertyAccessNode.cs + + + UriParser\SemanticAst\TypeSegment.cs + + + UriParser\SemanticAst\UnaryOperatorNode.cs + + + UriParser\SemanticAst\UriTemplateExpression.cs + + + UriParser\SemanticAst\ValueSegment.cs + + + UriParser\SemanticAst\WildcardSelectItem.cs + + + UriParser\SyntacticAst\AllToken.cs + + + UriParser\SyntacticAst\AnyToken.cs + + + UriParser\SyntacticAst\BinaryOperatorToken.cs + + + UriParser\SyntacticAst\ComputeToken.cs + + + UriParser\SyntacticAst\ComputeExpressionToken.cs + + + UriParser\SyntacticAst\InToken.cs + + + UriParser\SyntacticAst\CustomQueryOptionToken.cs + + + UriParser\SyntacticAst\DottedIdentifierToken.cs + + + UriParser\SyntacticAst\EndPathToken.cs + + + UriParser\SyntacticAst\ExpandTermToken.cs + + + UriParser\SyntacticAst\ExpandToken.cs + + + UriParser\SyntacticAst\FunctionCallToken.cs + + + UriParser\SyntacticAst\FunctionParameterAliasToken.cs + + + UriParser\SyntacticAst\FunctionParameterToken.cs + + + UriParser\SyntacticAst\InnerPathToken.cs + + + UriParser\SyntacticAst\LambdaToken.cs + + + UriParser\SyntacticAst\LiteralToken.cs + + + UriParser\SyntacticAst\NonSystemToken.cs + + + UriParser\SyntacticAst\OrderByToken.cs + + + UriParser\SyntacticAst\PathSegmentToken.cs + + + UriParser\SyntacticAst\PathToken.cs + + + UriParser\SyntacticAst\QueryToken.cs + + + UriParser\SyntacticAst\QueryTokenKind.cs + + + UriParser\SyntacticAst\RangeVariableToken.cs + + + UriParser\SyntacticAst\SelectExpandTermToken.cs + + + UriParser\SyntacticAst\SelectTermToken.cs + + + UriParser\SyntacticAst\SelectToken.cs + + + UriParser\SyntacticAst\StarToken.cs + + + UriParser\SyntacticAst\StringLiteralToken.cs + + + UriParser\SyntacticAst\SystemToken.cs + + + UriParser\SyntacticAst\UnaryOperatorToken.cs + + + UriParser\TreeNodeKinds\BinaryOperatorKind.cs + + + UriParser\TreeNodeKinds\ExpressionTokenKind.cs + + + UriParser\TreeNodeKinds\QueryNodeKind.cs + + + UriParser\TreeNodeKinds\RequestTargetKind.cs + + + UriParser\TreeNodeKinds\UnaryOperatorKind.cs + + + UriParser\TypeFacetsPromotionRules.cs + + + UriParser\TypePromotionUtils.cs + + + UriParser\UriEdmHelpers.cs + + + UriParser\UriFunctionsHelper.cs + + + UriParser\UriQueryConstants.cs + + + UriParser\Visitors\IPathSegmentTokenVisitor.cs + + + UriParser\Visitors\IsCollectionTranslator.cs + + + UriParser\Visitors\ISyntacticTreeVisitor.cs + + + UriParser\Visitors\PathSegmentHandler.cs + + + UriParser\Visitors\PathSegmentToContextUrlPathTranslator.cs + + + UriParser\Visitors\PathSegmentTokenEqualityComparer.cs + + + UriParser\Visitors\PathSegmentTokenVisitor.cs + + + UriParser\Visitors\PathSegmentToResourcePathTranslator.cs + + + UriParser\Visitors\PathSegmentToStringTranslator.cs + + + UriParser\Visitors\PathSegmentTranslator.cs + + + UriParser\Visitors\QueryNodeVisitor.cs + + + UriParser\Visitors\SelectItemHandler.cs + + + UriParser\Visitors\SelectItemTranslator.cs + + + UriParser\Visitors\SelectPropertyVisitor.cs + + + UriParser\Visitors\SplitEndingSegmentOfTypeHandler.cs + + + UriParser\Visitors\SyntacticTreeVisitor.cs + + + Value\ODataCollectionValue.cs + + + Value\ODataEnumValue.cs + + + Value\ODataNullValue.cs + + + Value\ODataPrimitiveValue.cs + + + Value\ODataResourceValue.cs + + + Value\ODataBinaryStreamValue.cs + + + Value\ODataStreamReferenceValue.cs + + + Value\ODataUntypedValue.cs + + + Value\ODataValue.cs + + + Value\ODataValueUtils.cs + + + UriUtils.cs + + + Uri\ApplyClauseToStringBuilder.cs + + + Uri\ExpressionConstants.cs + + + Uri\NodeToStringBuilder.cs + + + Uri\ODataUri.cs + + + Uri\ODataUriConversionUtils.cs + + + Uri\ODataUriExtensions.cs + + + Uri\ODataUriUtils.cs + + + Uri\ODataUrlKeyDelimiter.cs + + + Uri\SelectExpandClauseToStringBuilder.cs + + + Utils.cs + + + ValidationKinds.cs + + + ValidationUtils.cs + + + WriterUtils.cs + + + WriterValidationUtils.cs + + + + false + + + false + + + + + + + false + + + false + + + + + TextTemplatingFileGenerator + Microsoft.OData.Core.cs + + + True + True + Microsoft.OData.Core.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Core.cs + + + True + True + Parameterized.Microsoft.OData.Core.tt + true + + + + + + false + + + false + + + $(SuiteBinPath) + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/project.json b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/project.json new file mode 100644 index 0000000..933fc5c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NetStandard/project.json @@ -0,0 +1,10 @@ +{ + "supports": {}, + "dependencies": { + "NETStandard.Library": "1.6.0", + "System.ComponentModel": "4.3.0" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Net35.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Net35.Release.nuspec new file mode 100644 index 0000000..17215c2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Net35.Release.nuspec @@ -0,0 +1,32 @@ + + + + ODataLib + Microsoft.OData.Core.Net35 + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionNuGetSemantic$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Classes to serialize, deserialize and validate OData JSON payloads. Supports OData v4. + Classes to serialize, deserialize and validate OData JSON payloads. Supports OData v4. Enables construction of OData producers and consumers. Targets .NET Portable Lib with support for .NET 3.5. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Nightly.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Nightly.Release.nuspec new file mode 100644 index 0000000..e94dc41 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Nightly.Release.nuspec @@ -0,0 +1,35 @@ + + + + ODataLib + Microsoft.OData.Core + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionFullSemantic$-Nightly$NightlyBuildVersion$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Classes to serialize, deserialize and validate OData JSON payloads. Supports OData v4. + Classes to serialize, deserialize and validate OData JSON payloads. Supports OData v4. Enables construction of OData services and clients. Targets .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Release.nuspec new file mode 100644 index 0000000..67c03ec --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.NuGet/Microsoft.OData.Core.Release.nuspec @@ -0,0 +1,36 @@ + + + + ODataLib + Microsoft.OData.Core + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionNuGetSemantic$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Classes to serialize, deserialize and validate OData JSON payloads. Supports OData v4. + Classes to serialize, deserialize and validate OData JSON payloads. Supports OData v4. Enables construction of OData services and clients. Targets .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + http://odata.github.io/odata.net/v7/#ODL-$VersionFullSemantic$ + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.props b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.props new file mode 100644 index 0000000..25d815d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Build.props @@ -0,0 +1,15 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/CollectionWithoutExpectedTypeValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/CollectionWithoutExpectedTypeValidator.cs new file mode 100644 index 0000000..d07c6cf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/CollectionWithoutExpectedTypeValidator.cs @@ -0,0 +1,240 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Helper class to verify that all items of a collection are of the same kind and type. + /// + /// This class is only used if no expected item type is specified for the collection; + /// otherwise all items are already validated against the expected item type. + internal sealed class CollectionWithoutExpectedTypeValidator + { + /// true if the item type was derived from the collection value; otherwise false. + private readonly bool itemTypeDerivedFromCollectionValue; + + /// The item type name extracted from the first non-null item. + private string itemTypeName; + + /// + /// The primitive type denoted by the item type name or null if the type name is not a valid primitive type name. + /// + private IEdmPrimitiveType primitiveItemType; + + /// The item type kind from the first non-null item. + private EdmTypeKind itemTypeKind; + + /// + /// Constructor. + /// + /// The item type name extracted from the collection type name. + internal CollectionWithoutExpectedTypeValidator(string itemTypeNameFromCollection) + { + if (itemTypeNameFromCollection != null) + { + this.itemTypeName = GetItemTypeFullName(itemTypeNameFromCollection); + this.itemTypeKind = ComputeExpectedTypeKind(this.itemTypeName, out this.primitiveItemType); + this.itemTypeDerivedFromCollectionValue = true; + } + } + + /// + /// If specified on a collection, returns the item type name that all items are expected to be compatible with; otherwise null. + /// + internal string ItemTypeNameFromCollection + { + get + { + return this.itemTypeDerivedFromCollectionValue ? this.itemTypeName : null; + } + } + + /// + /// If specified on a collection, returns the item type kind that all items are expected to be compatible with; otherwise EdmTypeKind.None. + /// + internal EdmTypeKind ItemTypeKindFromCollection + { + get + { + return this.itemTypeDerivedFromCollectionValue ? this.itemTypeKind : EdmTypeKind.None; + } + } + + /// + /// Validates a collection item that was read to make sure it is valid (i.e., has the correct + /// type name and type kind) with respect to the other items in the collection. + /// + /// The type name of the item from the payload. + /// The type kind of the item from the payload. + internal void ValidateCollectionItem(string collectionItemTypeName, EdmTypeKind collectionItemTypeKind) + { + // Only primitive and complex values are allowed in collections + if (collectionItemTypeKind != EdmTypeKind.Primitive && collectionItemTypeKind != EdmTypeKind.Enum && collectionItemTypeKind != EdmTypeKind.Complex) + { + throw new ODataException(Strings.CollectionWithoutExpectedTypeValidator_InvalidItemTypeKind(collectionItemTypeKind)); + } + + if (this.itemTypeDerivedFromCollectionValue) + { + Debug.Assert(this.itemTypeName != null, "this.itemType != null"); + + // If the collection has a type name assign missing item type names from it. + collectionItemTypeName = collectionItemTypeName ?? this.itemTypeName; + + // If we have a type name from the collection, make sure the type names of all items match + this.ValidateCollectionItemTypeNameAndKind(collectionItemTypeName, collectionItemTypeKind); + } + else + { + // If we don't have a type name from the collection, store the type name and type kind of the first non-null item. + if (this.itemTypeKind == EdmTypeKind.None) + { + // Compute the kind from the specified type name if available. + this.itemTypeKind = collectionItemTypeName == null + ? collectionItemTypeKind + : ComputeExpectedTypeKind(collectionItemTypeName, out this.primitiveItemType); + + // If no payload type name is specified either default to Edm.String (for primitive type kinds) or leave the type name + // null (for complex items without type name) + if (collectionItemTypeName == null) + { + this.itemTypeKind = collectionItemTypeKind; + if (this.itemTypeKind == EdmTypeKind.Primitive) + { + this.itemTypeName = Metadata.EdmConstants.EdmStringTypeName; + this.primitiveItemType = EdmCoreModel.Instance.GetString(/*isNullable*/ false).PrimitiveDefinition(); + } + else + { + this.itemTypeName = null; + this.primitiveItemType = null; + } + } + else + { + this.itemTypeKind = ComputeExpectedTypeKind(collectionItemTypeName, out this.primitiveItemType); + this.itemTypeName = collectionItemTypeName; + } + } + + if (collectionItemTypeName == null && collectionItemTypeKind == EdmTypeKind.Primitive) + { + // Default to Edm.String if no payload type is specified and the type kind is 'Primitive' + collectionItemTypeName = Metadata.EdmConstants.EdmStringTypeName; + } + + // Validate the expected and actual type names and type kinds. + // Note that we compute the expected type kind from the expected type name and thus the payload + // type kind (passed to this method) might be different from the computed expected type kind. + this.ValidateCollectionItemTypeNameAndKind(collectionItemTypeName, collectionItemTypeKind); + } + } + + /// + /// Computes the expected type kind of an item from the type name read from the payload. + /// + /// The type name to compute the type kind from. + /// The primitive type for the specified type name or null if the type name is not a valid primitve type. + /// The of the type with the specified . + private static EdmTypeKind ComputeExpectedTypeKind(string typeName, out IEdmPrimitiveType primitiveItemType) + { + IEdmSchemaType knownType = EdmCoreModel.Instance.FindDeclaredType(typeName); + if (knownType != null) + { + Debug.Assert(knownType.TypeKind == EdmTypeKind.Primitive, "Only primitive types should be resolved by the core model."); + primitiveItemType = (IEdmPrimitiveType)knownType; + return EdmTypeKind.Primitive; + } + + primitiveItemType = null; + return EdmTypeKind.Complex; + } + + /// + /// Get the item type full name if it's the primitive type + /// + /// the original type name + /// the item type full name if it's the primitive type + private static string GetItemTypeFullName(string typeName) + { + IEdmSchemaType knownType = EdmCoreModel.Instance.FindDeclaredType(typeName); + if (knownType != null) + { + return knownType.FullName(); + } + + return typeName; + } + + /// + /// Validate that the expected and actual type names and type kinds are compatible. + /// + /// The actual type name. + /// The actual type kind. + private void ValidateCollectionItemTypeNameAndKind(string collectionItemTypeName, EdmTypeKind collectionItemTypeKind) + { + // Compare the item type kinds. + if (this.itemTypeKind != collectionItemTypeKind) + { + throw new ODataException(Strings.CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeKind(collectionItemTypeKind, this.itemTypeKind)); + } + + if (this.itemTypeKind == EdmTypeKind.Primitive) + { + Debug.Assert(this.primitiveItemType != null, "this.primitiveItemType != null"); + Debug.Assert(collectionItemTypeName != null, "collectionItemTypeName != null"); + + // NOTE: we do support type inheritance for spatial primitive types; otherwise the type names have to match. + if (!string.IsNullOrEmpty(this.itemTypeName) && this.itemTypeName.Equals(collectionItemTypeName, System.StringComparison.OrdinalIgnoreCase)) + { + return; + } + + if (this.primitiveItemType.IsSpatial()) + { + EdmPrimitiveTypeKind collectionItemPrimitiveKind = EdmCoreModel.Instance.GetPrimitiveTypeKind(collectionItemTypeName); + IEdmPrimitiveType collectionItemPrimitiveType = EdmCoreModel.Instance.GetPrimitiveType(collectionItemPrimitiveKind); + + if (this.itemTypeDerivedFromCollectionValue) + { + // If the collection defines an item type, the collection item type has to be assignable to it. + if (this.primitiveItemType.IsAssignableFrom(collectionItemPrimitiveType)) + { + return; + } + } + else + { + // If the collection does not define an item type, the collection items must have a common base type. + IEdmPrimitiveType commonBaseType = EdmLibraryExtensions.GetCommonBaseType(this.primitiveItemType, collectionItemPrimitiveType); + if (commonBaseType != null) + { + this.primitiveItemType = commonBaseType; + this.itemTypeName = commonBaseType.FullTypeName(); + return; + } + } + } + + throw new ODataException(Strings.CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeName(collectionItemTypeName, this.itemTypeName)); + } + else + { + // Since we do not support type inheritance for complex types, comparison of the type names is sufficient + if (string.CompareOrdinal(this.itemTypeName, collectionItemTypeName) != 0) + { + throw new ODataException(Strings.CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeName(collectionItemTypeName, this.itemTypeName)); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ContainerBuilderExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ContainerBuilderExtensions.cs new file mode 100644 index 0000000..6d09580 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ContainerBuilderExtensions.cs @@ -0,0 +1,153 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using Microsoft.OData.Edm; +using Microsoft.OData.Json; +using Microsoft.OData.UriParser; + +namespace Microsoft.OData +{ + /// + /// Extension methods for . + /// + public static class ContainerBuilderExtensions + { + #region Overloads for IContainerBuilder.AddService + + /// + /// Adds a service of with an . + /// + /// The type of the service to add. + /// The type of the implementation to use. + /// The to add the service to. + /// The lifetime of the service to register. + /// The instance itself. + public static IContainerBuilder AddService( + this IContainerBuilder builder, + ServiceLifetime lifetime) + where TService : class + where TImplementation : class, TService + { + Debug.Assert(builder != null, "builder != null"); + + return builder.AddService(lifetime, typeof(TService), typeof(TImplementation)); + } + + /// + /// Adds a service of . + /// + /// The to add the service to. + /// The lifetime of the service to register. + /// The type of the service to register and the implementation to use. + /// The instance itself. + public static IContainerBuilder AddService( + this IContainerBuilder builder, + ServiceLifetime lifetime, + Type serviceType) + { + Debug.Assert(builder != null, "builder != null"); + Debug.Assert(serviceType != null, "serviceType != null"); + + return builder.AddService(lifetime, serviceType, serviceType); + } + + /// + /// Adds a service of . + /// + /// The type of the service to add. + /// The to add the service to. + /// The lifetime of the service to register. + /// The instance itself. + public static IContainerBuilder AddService( + this IContainerBuilder builder, + ServiceLifetime lifetime) + where TService : class + { + Debug.Assert(builder != null, "builder != null"); + + return builder.AddService(lifetime, typeof(TService)); + } + + /// + /// Adds a service of with an . + /// + /// The type of the service to add. + /// The to add the service to. + /// The lifetime of the service to register. + /// The factory that creates the service. + /// The instance itself. + public static IContainerBuilder AddService( + this IContainerBuilder builder, + ServiceLifetime lifetime, + Func implementationFactory) + where TService : class + { + Debug.Assert(builder != null, "builder != null"); + Debug.Assert(implementationFactory != null, "implementationFactory != null"); + + return builder.AddService(lifetime, typeof(TService), implementationFactory); + } + + #endregion + + /// + /// Adds a service prototype of type . + /// + /// The type of the service prototype to add. + /// The to add the service to. + /// The service prototype to add. + /// The instance itself. + public static IContainerBuilder AddServicePrototype( + this IContainerBuilder builder, + TService instance) + { + Debug.Assert(builder != null, "builder != null"); + + return builder.AddService(ServiceLifetime.Singleton, sp => new ServicePrototype(instance)); + } + + /// + /// Adds the default OData services to the . + /// + /// The to add the services to. + /// The instance itself. + public static IContainerBuilder AddDefaultODataServices(this IContainerBuilder builder) + { + return builder.AddDefaultODataServices(ODataVersion.V4); + } + + /// + /// Adds the default OData services to the . + /// + /// The to add the services to. + /// ODataVersion for the default services. + /// The instance itself. + public static IContainerBuilder AddDefaultODataServices(this IContainerBuilder builder, ODataVersion odataVersion) + { + Debug.Assert(builder != null, "builder != null"); + + builder.AddService(ServiceLifetime.Singleton); + builder.AddService(ServiceLifetime.Singleton, sp => new DefaultJsonWriterFactory()); + builder.AddService(ServiceLifetime.Singleton, sp => ODataMediaTypeResolver.GetMediaTypeResolver(null)); + builder.AddService(ServiceLifetime.Scoped); + builder.AddServicePrototype(new ODataMessageReaderSettings()); + builder.AddService(ServiceLifetime.Scoped, sp => sp.GetServicePrototype().Clone()); + builder.AddServicePrototype(new ODataMessageWriterSettings()); + builder.AddService(ServiceLifetime.Scoped, sp => sp.GetServicePrototype().Clone()); + builder.AddService(ServiceLifetime.Singleton, sp => ODataPayloadValueConverter.GetPayloadValueConverter(null)); + builder.AddService(ServiceLifetime.Singleton, sp => EdmCoreModel.Instance); + builder.AddService(ServiceLifetime.Singleton, sp => ODataUriResolver.GetUriResolver(null)); + builder.AddService(ServiceLifetime.Scoped); + builder.AddService(ServiceLifetime.Scoped); + builder.AddServicePrototype(new ODataSimplifiedOptions(odataVersion)); + builder.AddService(ServiceLifetime.Scoped, sp => sp.GetServicePrototype().Clone()); + + return builder; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/DerivedTypeValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/DerivedTypeValidator.cs new file mode 100644 index 0000000..051d503 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/DerivedTypeValidator.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Edm; + + /// + /// Helper class to verify the resource type meets the drived type contraints. + /// + internal sealed class DerivedTypeValidator + { + private IEnumerable derivedTypeConstraints; + private IEdmType expectedType; + private string resourceKind; + private string resourceName; + + /// + /// Constructor. + /// + /// The expected type. + /// The derived type contraints. + /// The resource type, be used at error message. + /// The resource name, be used at error message. + public DerivedTypeValidator(IEdmType expectedType, IEnumerable derivedTypeConstraints, string resourceKind, string resourceName) + { + this.derivedTypeConstraints = derivedTypeConstraints; + this.expectedType = expectedType; + this.resourceKind = resourceKind; + this.resourceName = resourceName; + } + + /// + /// Validates the type of a resource. + /// + /// The type of the resource. + internal void ValidateResourceType(IEdmType resourceType) + { + if (resourceType == null) + { + return; + } + + ValidateResourceType(resourceType.FullTypeName()); + } + + /// + /// Validates the full type name of a resource. + /// + /// The full type name of the resource. + internal void ValidateResourceType(string resourceTypeName) + { + if (resourceTypeName == null) + { + return; + } + + if (this.expectedType != null && this.expectedType.FullTypeName() == resourceTypeName) + { + return; + } + + if (derivedTypeConstraints == null || derivedTypeConstraints.Any(c => c == resourceTypeName)) + { + return; + } + + throw new ODataException(Strings.ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint(resourceTypeName, resourceKind, resourceName)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/DuplicatePropertyNameChecker.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/DuplicatePropertyNameChecker.cs new file mode 100644 index 0000000..9b96b6e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/DuplicatePropertyNameChecker.cs @@ -0,0 +1,152 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData +{ + /// + /// Validates that + /// 1) No duplicate property. + /// 2) No duplicate "@odata.associationLink" on a property. + /// 3) "@odata.associationLink"s are put on navigation properties. + /// + internal class DuplicatePropertyNameChecker : IDuplicatePropertyNameChecker + { + /// + /// Caches property processing state. + /// + private IDictionary propertyState = new Dictionary(); + + /// + /// Property processing state. + /// + [Flags] + private enum State + { + /// + /// `ValidatePropertyUniqueness(ODataProperty)` has been called. + /// + NonNestedResource, + + /// + /// `ValidatePropertyUniqueness(ODataNestedResourceInfo)` has been called. + /// + NestedResource, + + /// + /// `ValidatePropertyOpenForAssociationLink` has been called. + /// + AssociationLink + } + + /// + /// Validates property uniqueness. + /// + /// The property. + public void ValidatePropertyUniqueness(ODataPropertyInfo property) + { + try + { + propertyState.Add(property.Name, State.NonNestedResource); + } + catch (ArgumentException) + { + throw new ODataException( + Strings.DuplicatePropertyNamesNotAllowed( + property.Name)); + } + } + + /// + /// Validates property uniqueness. + /// + /// The property. + public void ValidatePropertyUniqueness(ODataNestedResourceInfo property) + { + State state; + if (!propertyState.TryGetValue(property.Name, out state)) + { + propertyState[property.Name] = State.NestedResource; + } + else + { + if (state != State.AssociationLink) + { + throw new ODataException( + Strings.DuplicatePropertyNamesNotAllowed( + property.Name)); + } + else + { + propertyState[property.Name] = State.AssociationLink | State.NestedResource; + } + } + } + + /// + /// Validates that "@odata.associationLink" is put on a navigation property, + /// and that no duplicate exists. + /// + /// Name of the property. + public void ValidatePropertyOpenForAssociationLink(string propertyName) + { + State state; + if (!propertyState.TryGetValue(propertyName, out state)) + { + propertyState[propertyName] = State.AssociationLink; + } + else + { + if (state != State.NestedResource) + { + throw new ODataException( + Strings.DuplicatePropertyNamesNotAllowed( + propertyName)); + } + else + { + propertyState[propertyName] = State.NestedResource | State.AssociationLink; + } + } + } + + /// + /// Resets to initial state for reuse. + /// + public void Reset() + { + propertyState.Clear(); + } + } + + /// + /// Mock version of `DuplicatePropertyNameChecker`, which does nothing. + /// + internal class NullDuplicatePropertyNameChecker : IDuplicatePropertyNameChecker + { + public void ValidatePropertyUniqueness(ODataPropertyInfo property) + { + // nop + } + + public void ValidatePropertyUniqueness(ODataNestedResourceInfo property) + { + // nop + } + + public void ValidatePropertyOpenForAssociationLink(string propertyName) + { + // nop + } + + public void Reset() + { + // nop + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/EdmExtensionMethods.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/EdmExtensionMethods.cs new file mode 100644 index 0000000..47962d2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/EdmExtensionMethods.cs @@ -0,0 +1,119 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser; + +namespace Microsoft.OData +{ + internal static class EdmExtensionMethods + { + /// + /// Find the navigation target which is of current targets. + /// + /// The navigation source to find. + /// The navigation property + /// The function used to determine if the binding path matches. + /// The navigation target which matches the binding path. + public static IEdmNavigationSource FindNavigationTarget(this IEdmNavigationSource navigationSource, IEdmNavigationProperty navigationProperty, Func matchBindingPath) + { + Debug.Assert(navigationSource != null); + Debug.Assert(navigationProperty != null); + Debug.Assert(matchBindingPath != null); + + if (navigationProperty.ContainsTarget) + { + return navigationSource.FindNavigationTarget(navigationProperty); + } + + IEnumerable bindings = navigationSource.FindNavigationPropertyBindings(navigationProperty); + + if (bindings != null) + { + foreach (var binding in bindings) + { + if (matchBindingPath(binding.Path)) + { + return binding.Target; + } + } + } + + return new UnknownEntitySet(navigationSource, navigationProperty); + } + + /// + /// Find the navigation target which is of current targets. + /// The function is specifically used in Uri parser. + /// + /// The navigation source to find. + /// The navigation property + /// The function used to determine if the binding path matches. + /// The parsed segments in path, which is used to match binding path. + /// The output binding path of the navigation property which matches the + /// The navigation target which matches the binding path. + public static IEdmNavigationSource FindNavigationTarget(this IEdmNavigationSource navigationSource, IEdmNavigationProperty navigationProperty, Func, bool> matchBindingPath, List parsedSegments, out IEdmPathExpression bindingPath) + { + Debug.Assert(navigationSource != null); + Debug.Assert(navigationProperty != null); + Debug.Assert(matchBindingPath != null); + Debug.Assert(parsedSegments != null); + + bindingPath = null; + + if (navigationProperty.ContainsTarget) + { + return navigationSource.FindNavigationTarget(navigationProperty); + } + + IEnumerable bindings = navigationSource.FindNavigationPropertyBindings(navigationProperty); + + if (bindings != null) + { + foreach (var binding in bindings) + { + if (matchBindingPath(binding.Path, parsedSegments)) + { + bindingPath = binding.Path; + return binding.Target; + } + } + } + + return new UnknownEntitySet(navigationSource, navigationProperty); + } + + /// + /// Decide whether with type should have key. + /// + /// The navigation source to be evaluated. + /// The resource type to be evaluated. + /// True if the navigation source should have key. + public static bool HasKey(IEdmNavigationSource currentNavigationSource, IEdmStructuredType currentResourceType) + { + if (currentResourceType is IEdmComplexType) + { + return false; + } + + if (currentNavigationSource is IEdmEntitySet) + { + return true; + } + + var currentContainedEntitySet = currentNavigationSource as IEdmContainedEntitySet; + if (currentContainedEntitySet != null && currentContainedEntitySet.NavigationProperty.Type.TypeKind() == EdmTypeKind.Collection) + { + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ErrorUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ErrorUtils.cs new file mode 100644 index 0000000..0481827 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ErrorUtils.cs @@ -0,0 +1,146 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Xml; +using Microsoft.OData.Metadata; + +namespace Microsoft.OData +{ + /// + /// Utility methods serializing the xml error payload + /// + internal static class ErrorUtils + { + /// Default language for error messages if not specified. + /// + /// This constant is included here since this file is compiled into WCF DS Server as well + /// so we can't compile in the ODataConstants. + /// + internal const string ODataErrorMessageDefaultLanguage = "en-US"; + + /// + /// Extracts error details from an . + /// + /// The ODataError instance to extract the error details from. + /// A data service-defined string which serves as a substatus to the HTTP response code. + /// A human readable message describing the error. + internal static void GetErrorDetails(ODataError error, out string code, out string message) + { + Debug.Assert(error != null, "error != null"); + + code = error.ErrorCode ?? string.Empty; + message = error.Message ?? string.Empty; + } + + /// + /// Write an error message. + /// + /// The Xml writer to write to. + /// The error instance to write. + /// A flag indicating whether error details should be written (in debug mode only) or not. + /// The maximumum number of nested inner errors to allow. + internal static void WriteXmlError(XmlWriter writer, ODataError error, bool includeDebugInformation, int maxInnerErrorDepth) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(error != null, "error != null"); + + string code, message; + ErrorUtils.GetErrorDetails(error, out code, out message); + + ODataInnerError innerError = includeDebugInformation ? error.InnerError : null; + WriteXmlError(writer, code, message, innerError, maxInnerErrorDepth); + } + + /// + /// Write an error message. + /// + /// The Xml writer to write to. + /// The code of the error. + /// The message of the error. + /// Inner error details that will be included in debug mode (if present). + /// The maximumum number of nested inner errors to allow. + private static void WriteXmlError(XmlWriter writer, string code, string message, ODataInnerError innerError, int maxInnerErrorDepth) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(code != null, "code != null"); + Debug.Assert(message != null, "message != null"); + + // + writer.WriteStartElement(ODataMetadataConstants.ODataMetadataNamespacePrefix, ODataMetadataConstants.ODataErrorElementName, ODataMetadataConstants.ODataMetadataNamespace); + + // code + writer.WriteElementString(ODataMetadataConstants.ODataMetadataNamespacePrefix, ODataMetadataConstants.ODataErrorCodeElementName, ODataMetadataConstants.ODataMetadataNamespace, code); + + // error message + writer.WriteElementString(ODataMetadataConstants.ODataMetadataNamespacePrefix, ODataMetadataConstants.ODataErrorMessageElementName, ODataMetadataConstants.ODataMetadataNamespace, message); + + if (innerError != null) + { + WriteXmlInnerError(writer, innerError, ODataMetadataConstants.ODataInnerErrorElementName, /* recursionDepth */ 0, maxInnerErrorDepth); + } + + // + writer.WriteEndElement(); + } + + /// + /// Writes the inner exception information in debug mode. + /// + /// The Xml writer to write to. + /// The inner error to write. + /// The local name of the element representing the inner error. + /// The number of times this method has been called recursively. + /// The maximumum number of nested inner errors to allow. + private static void WriteXmlInnerError(XmlWriter writer, ODataInnerError innerError, string innerErrorElementName, int recursionDepth, int maxInnerErrorDepth) + { + Debug.Assert(writer != null, "writer != null"); + + recursionDepth++; + if (recursionDepth > maxInnerErrorDepth) + { +#if ODATA_CORE + throw new ODataException(Strings.ValidationUtils_RecursionDepthLimitReached(maxInnerErrorDepth)); +#else + throw new ODataException(Microsoft.OData.Service.Strings.BadRequest_DeepRecursion(maxInnerErrorDepth)); +#endif + } + + // or + writer.WriteStartElement(ODataMetadataConstants.ODataMetadataNamespacePrefix, innerErrorElementName, ODataMetadataConstants.ODataMetadataNamespace); + + //// NOTE: we add empty elements if no information is provided for the message, error type and stack trace + //// to stay compatible with Astoria. + + // ... + string errorMessage = innerError.Message ?? String.Empty; + writer.WriteStartElement(ODataMetadataConstants.ODataInnerErrorMessageElementName, ODataMetadataConstants.ODataMetadataNamespace); + writer.WriteString(errorMessage); + writer.WriteEndElement(); + + // ... + string errorType = innerError.TypeName ?? string.Empty; + writer.WriteStartElement(ODataMetadataConstants.ODataInnerErrorTypeElementName, ODataMetadataConstants.ODataMetadataNamespace); + writer.WriteString(errorType); + writer.WriteEndElement(); + + // ... + string errorStackTrace = innerError.StackTrace ?? String.Empty; + writer.WriteStartElement(ODataMetadataConstants.ODataInnerErrorStackTraceElementName, ODataMetadataConstants.ODataMetadataNamespace); + writer.WriteString(errorStackTrace); + writer.WriteEndElement(); + + if (innerError.InnerError != null) + { + WriteXmlInnerError(writer, innerError.InnerError, ODataMetadataConstants.ODataInnerErrorInnerErrorElementName, recursionDepth, maxInnerErrorDepth); + } + + // or + writer.WriteEndElement(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/EdmValueUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/EdmValueUtils.cs new file mode 100644 index 0000000..68dc97c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/EdmValueUtils.cs @@ -0,0 +1,395 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.Spatial; +#if ODATA_CLIENT + using Microsoft.OData; + using ErrorStrings = Microsoft.OData.Client.Strings; + using PlatformHelpers = Microsoft.OData.Client.PlatformHelper; +#else +using ErrorStrings = Microsoft.OData.Strings; +using PlatformHelpers = Microsoft.OData.PlatformHelper; +#endif + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Evaluation +#endif +{ + /// + /// Class with utility methods to deal with EDM values + /// + internal static class EdmValueUtils + { + /// + /// Converts a primitive OData value to the corresponding . + /// + /// The primitive OData value to convert. + /// The for the primitive value (if available). + /// An for the . + internal static IEdmDelayedValue ConvertPrimitiveValue(object primitiveValue, IEdmPrimitiveTypeReference type) + { + Debug.Assert(primitiveValue != null, "primitiveValue != null"); + + if (primitiveValue is Boolean) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Boolean); + return new EdmBooleanConstant(type, (bool)primitiveValue); + } + + if (primitiveValue is Byte) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Byte); + return new EdmIntegerConstant(type, (byte)primitiveValue); + } + + if (primitiveValue is SByte) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.SByte); + return new EdmIntegerConstant(type, (sbyte)primitiveValue); + } + + if (primitiveValue is Int16) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Int16); + return new EdmIntegerConstant(type, (Int16)primitiveValue); + } + + if (primitiveValue is Int32) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Int32); + return new EdmIntegerConstant(type, (Int32)primitiveValue); + } + + if (primitiveValue is Int64) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Int64); + return new EdmIntegerConstant(type, (Int64)primitiveValue); + } + + if (primitiveValue is Decimal) + { + var decimalType = + (IEdmDecimalTypeReference)EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Decimal); + return new EdmDecimalConstant(decimalType, (decimal)primitiveValue); + } + + if (primitiveValue is Single) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Single); + return new EdmFloatingConstant(type, (Single)primitiveValue); + } + + if (primitiveValue is Double) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Double); + return new EdmFloatingConstant(type, (Double)primitiveValue); + } + + var str = primitiveValue as string; + if (str != null) + { + var stringType = + (IEdmStringTypeReference)EnsurePrimitiveType(type, EdmPrimitiveTypeKind.String); + return new EdmStringConstant(stringType, str); + } + + return ConvertPrimitiveValueWithoutTypeCode(primitiveValue, type); + } + + /// + /// Gets the clr value of the edm value based on its type. + /// + /// The edm value. + /// The clr value + internal static object ToClrValue(this IEdmPrimitiveValue edmValue) + { + Debug.Assert(edmValue != null, "edmValue != null"); + EdmPrimitiveTypeKind primitiveKind = edmValue.Type.PrimitiveKind(); + switch (edmValue.ValueKind) + { + case EdmValueKind.Binary: + return ((IEdmBinaryValue)edmValue).Value; + + case EdmValueKind.Boolean: + return ((IEdmBooleanValue)edmValue).Value; + + case EdmValueKind.DateTimeOffset: + return ((IEdmDateTimeOffsetValue)edmValue).Value; + + case EdmValueKind.Decimal: + return ((IEdmDecimalValue)edmValue).Value; + + case EdmValueKind.Guid: + return ((IEdmGuidValue)edmValue).Value; + + case EdmValueKind.String: + return ((IEdmStringValue)edmValue).Value; + + case EdmValueKind.Duration: + return ((IEdmDurationValue)edmValue).Value; + + case EdmValueKind.Floating: + return ConvertFloatingValue((IEdmFloatingValue)edmValue, primitiveKind); + + case EdmValueKind.Integer: + return ConvertIntegerValue((IEdmIntegerValue)edmValue, primitiveKind); + + case EdmValueKind.Date: + return ((IEdmDateValue)edmValue).Value; + + case EdmValueKind.TimeOfDay: + return ((IEdmTimeOfDayValue)edmValue).Value; + } + + throw new ODataException(ErrorStrings.EdmValueUtils_CannotConvertTypeToClrValue(edmValue.ValueKind)); + } + +#if !ODATA_CLIENT + /// + /// Tries to get a stream property of the specified name. + /// + /// The instance of the entity to get the stream property for. + /// The stream property name to find. + /// The stream property found. + /// true if the stream property was found or if the stream property name was null (default stream). + /// false if the stream property doesn't exist. + internal static bool TryGetStreamProperty(IEdmStructuredValue entityInstance, string streamPropertyName, out IEdmProperty streamProperty) + { + Debug.Assert(entityInstance != null, "entityInstance != null"); + + streamProperty = null; + if (streamPropertyName != null) + { + streamProperty = entityInstance.Type.AsEntity().FindProperty(streamPropertyName); + if (streamProperty == null) + { + return false; + } + } + + return true; + } + + /// + /// Gets the the CLR value for a primitive property. + /// + /// The structured value. + /// Name of the property. + /// The clr value of the property. + internal static object GetPrimitivePropertyClrValue(this IEdmStructuredValue structuredValue, string propertyName) + { + Debug.Assert(structuredValue != null, "entityInstance != null"); + IEdmStructuredTypeReference valueType = structuredValue.Type.AsStructured(); + + IEdmPropertyValue propertyValue = structuredValue.FindPropertyValue(propertyName); + if (propertyValue == null) + { + throw new ODataException(ErrorStrings.EdmValueUtils_PropertyDoesntExist(valueType.FullName(), propertyName)); + } + + if (propertyValue.Value.ValueKind == EdmValueKind.Null) + { + return null; + } + + IEdmPrimitiveValue primitiveValue = propertyValue.Value as IEdmPrimitiveValue; + if (primitiveValue == null) + { + throw new ODataException(ErrorStrings.EdmValueUtils_NonPrimitiveValue(propertyValue.Name, valueType.FullName())); + } + + return primitiveValue.ToClrValue(); + } +#endif + + /// + /// Converts a floating-point edm value to a clr value + /// + /// The edm floating-point value. + /// Kind of the primitive. + /// The converted value + private static object ConvertFloatingValue(IEdmFloatingValue floatingValue, EdmPrimitiveTypeKind primitiveKind) + { + Debug.Assert(floatingValue != null, "floatingValue != null"); + double doubleValue = floatingValue.Value; + + if (primitiveKind == EdmPrimitiveTypeKind.Single) + { + return Convert.ToSingle(doubleValue); + } + + Debug.Assert(primitiveKind == EdmPrimitiveTypeKind.Double, "primitiveKind == EdmPrimitiveTypeKind.Double"); + return doubleValue; + } + + /// + /// Converts an integer edm value to a clr value. + /// + /// The integer value. + /// Kind of the primitive. + /// The converted value + private static object ConvertIntegerValue(IEdmIntegerValue integerValue, EdmPrimitiveTypeKind primitiveKind) + { + Debug.Assert(integerValue != null, "integerValue != null"); + long longValue = integerValue.Value; + + switch (primitiveKind) + { + case EdmPrimitiveTypeKind.Int16: + return Convert.ToInt16(longValue); + + case EdmPrimitiveTypeKind.Int32: + return Convert.ToInt32(longValue); + + case EdmPrimitiveTypeKind.Byte: + return Convert.ToByte(longValue); + + case EdmPrimitiveTypeKind.SByte: + return Convert.ToSByte(longValue); + + default: + Debug.Assert(primitiveKind == EdmPrimitiveTypeKind.Int64, "primitiveKind == EdmPrimitiveTypeKind.Int64"); + return longValue; + } + } + + /// + /// Convert a primitive value which didn't match any of the known values of the TypeCode enumeration. + /// + /// The value to convert. + /// The expected primitive type or null. + /// The converted value. + private static IEdmDelayedValue ConvertPrimitiveValueWithoutTypeCode(object primitiveValue, IEdmPrimitiveTypeReference type) + { + byte[] bytes = primitiveValue as byte[]; + if (bytes != null) + { + IEdmBinaryTypeReference binaryType = (IEdmBinaryTypeReference)EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Binary); + return new EdmBinaryConstant(binaryType, bytes); + } + + if (primitiveValue is Date) + { + IEdmPrimitiveTypeReference dateType = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Date); + return new EdmDateConstant(dateType, (Date)primitiveValue); + } + + if (primitiveValue is DateTimeOffset) + { + IEdmTemporalTypeReference dateTimeOffsetType = (IEdmTemporalTypeReference)EnsurePrimitiveType(type, EdmPrimitiveTypeKind.DateTimeOffset); + return new EdmDateTimeOffsetConstant(dateTimeOffsetType, (DateTimeOffset)primitiveValue); + } + + if (primitiveValue is Guid) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Guid); + return new EdmGuidConstant(type, (Guid)primitiveValue); + } + + if (primitiveValue is TimeOfDay) + { + IEdmTemporalTypeReference timeOfDayType = (IEdmTemporalTypeReference)EnsurePrimitiveType(type, EdmPrimitiveTypeKind.TimeOfDay); + return new EdmTimeOfDayConstant(timeOfDayType, (TimeOfDay)primitiveValue); + } + + if (primitiveValue is TimeSpan) + { + IEdmTemporalTypeReference timeType = (IEdmTemporalTypeReference)EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Duration); + return new EdmDurationConstant(timeType, (TimeSpan)primitiveValue); + } + + if (primitiveValue is ISpatial) + { + // TODO: [JsonLight] Add support for spatial values in ODataEdmStructuredValue + throw new NotImplementedException(); + } + +#if ODATA_CLIENT + IEdmDelayedValue convertPrimitiveValueWithoutTypeCode; + if (TryConvertClientSpecificPrimitiveValue(primitiveValue, type, out convertPrimitiveValueWithoutTypeCode)) + { + return convertPrimitiveValueWithoutTypeCode; + } +#endif + + throw new ODataException(ErrorStrings.EdmValueUtils_UnsupportedPrimitiveType(primitiveValue.GetType().FullName)); + } + +#if ODATA_CLIENT + /// + /// Tries to convert the given value if it is of a type specific to the client library but still able to be mapped to EDM. + /// + /// The value to convert. + /// The expected type of the value or null. + /// The converted value, if conversion was possible. + /// Whether or not conversion was possible. + private static bool TryConvertClientSpecificPrimitiveValue(object primitiveValue, IEdmPrimitiveTypeReference type, out IEdmDelayedValue convertedValue) + { + byte[] byteArray; + if (ClientConvert.TryConvertBinaryToByteArray(primitiveValue, out byteArray)) + { + type = EnsurePrimitiveType(type, EdmPrimitiveTypeKind.Binary); + convertedValue = new EdmBinaryConstant((IEdmBinaryTypeReference)type, byteArray); + return true; + } + + PrimitiveType clientPrimitiveType; + if (PrimitiveType.TryGetPrimitiveType(primitiveValue.GetType(), out clientPrimitiveType)) + { + type = EnsurePrimitiveType(type, clientPrimitiveType.PrimitiveKind); + if (clientPrimitiveType.PrimitiveKind == EdmPrimitiveTypeKind.String) + { + { + convertedValue = new EdmStringConstant((IEdmStringTypeReference)type, clientPrimitiveType.TypeConverter.ToString(primitiveValue)); + return true; + } + } + } + + convertedValue = null; + return false; + } +#endif + + /// + /// Ensures a primitive type reference for a given primitive type kind. + /// + /// The possibly null type reference. + /// The primitive type kind to ensure. + /// An instance created for the + /// if is null; if is not null, validates it and then returns it. + private static IEdmPrimitiveTypeReference EnsurePrimitiveType(IEdmPrimitiveTypeReference type, EdmPrimitiveTypeKind primitiveKindFromValue) + { + if (type == null) + { + type = EdmCoreModel.Instance.GetPrimitive(primitiveKindFromValue, /*isNullable*/ true); + } + else + { + EdmPrimitiveTypeKind primitiveKindFromType = type.PrimitiveDefinition().PrimitiveKind; + + if (primitiveKindFromType != primitiveKindFromValue) + { + string typeName = type.FullName(); + if (typeName == null) + { + throw new ODataException(ErrorStrings.EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName(primitiveKindFromType.ToString(), primitiveKindFromValue.ToString())); + } + + throw new ODataException(ErrorStrings.EdmValueUtils_IncorrectPrimitiveTypeKind(typeName, primitiveKindFromValue.ToString(), primitiveKindFromType.ToString())); + } + } + + return type; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/IODataResourceMetadataContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/IODataResourceMetadataContext.cs new file mode 100644 index 0000000..3b029d2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/IODataResourceMetadataContext.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Evaluation +{ + using System.Collections.Generic; + using Microsoft.OData.Edm; + + /// + /// Interface used for substitutability of the metadata-centric responsibilities of a resource. + /// Metadata may come from a user-provided model or from the SetSerializationInfo() method on a resource set or resource. The latter is considered the "no-model" case since only strings + /// are provided, and there is no interconnectedness. The goal of this interface is to provide a way to query the metadata information available on a resource without + /// needing to know where the metadata originated from. + /// + internal interface IODataResourceMetadataContext + { + /// + /// The resource instance. + /// + ODataResourceBase Resource { get; } + + /// + /// The context object to answer basic questions regarding the type of the resource. + /// + IODataResourceTypeContext TypeContext { get; } + + /// + /// The actual structured type of the resource, i.e. ODataResource.TypeName. + /// + string ActualResourceTypeName { get; } + + /// + /// The key property name and value pairs of the resource. + /// + ICollection> KeyProperties { get; } + + /// + /// The ETag property name and value pairs of the resource. + /// + IEnumerable> ETagProperties { get; } + + /// + /// The selected navigation properties. + /// + IEnumerable SelectedNavigationProperties { get; } + + /// + /// The selected stream properties. + /// + IDictionary SelectedStreamProperties { get; } + + /// + /// The selected bindable operations. + /// + IEnumerable SelectedBindableOperations { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/KeySerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/KeySerializer.cs new file mode 100644 index 0000000..4e7f100 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/KeySerializer.cs @@ -0,0 +1,199 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +#if ODATA_SERVICE +namespace Microsoft.OData.Service.Serializers +#else +namespace Microsoft.OData.Evaluation +#endif +#endif +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; +#if !ODATA_CORE + using Microsoft.OData; +#endif + + /// + /// Component for serializing entity key values for building identities, edit links, etc. + /// + internal abstract class KeySerializer + { + /// Singleton instance of the default serializer. + private static readonly DefaultKeySerializer DefaultInstance = new DefaultKeySerializer(); + + /// Singleton instance of the segment-based serializer. + private static readonly SegmentKeySerializer SegmentInstance = new SegmentKeySerializer(); + + /// + /// Creates a new key serializer. + /// + /// Whether enable key as segment. + /// A new key serializer. + internal static KeySerializer Create(bool enableKeyAsSegment) + { + if (enableKeyAsSegment) + { + return SegmentInstance; + } + + return DefaultInstance; + } + + /// + /// Appends the key expression for an entity to the given + /// + /// The type used to represent properties. + /// The builder to append onto. + /// The key properties. + /// The callback to get each property's name. + /// The callback to get each property's value. + internal abstract void AppendKeyExpression(StringBuilder builder, ICollection keyProperties, Func getPropertyName, Func getPropertyValue); + + /// + /// Gets the value of the key property and serializes it to a string. + /// + /// The type used to represent properties. + /// The callback to get the value for a property. + /// The key property. + /// The literal formatter to use. + /// The serialized key property value. + private static string GetKeyValueAsString(Func getPropertyValue, TProperty property, LiteralFormatter literalFormatter) + { + Debug.Assert(getPropertyValue != null, "getPropertyValue != null"); + object keyValue = getPropertyValue(property); + + Debug.Assert(keyValue != null, "keyValue != null"); + string keyValueText = literalFormatter.Format(keyValue); + + Debug.Assert(keyValueText != null, "keyValueText != null"); + return keyValueText; + } + + /// + /// Appends the key using the parentheses-based syntax (e.g. Customers(1)) onto the given . + /// + /// The builder to append onto. + /// The type used to represent properties. + /// The key properties. + /// The callback to get each property's name. + /// The callback to get each property's value. + private static void AppendKeyWithParentheses(StringBuilder builder, ICollection keyProperties, Func getPropertyName, Func getPropertyValue) + { + Debug.Assert(builder != null, "builder != null"); + Debug.Assert(keyProperties != null, "keyProperties != null"); + Debug.Assert(keyProperties.Count != 0, "every resource type must have a key"); + Debug.Assert(getPropertyValue != null, "getPropertyValue != null"); + + LiteralFormatter literalFormatter = LiteralFormatter.ForKeys(false); + + builder.Append('('); + + bool first = true; + foreach (TProperty property in keyProperties) + { + if (first) + { + first = false; + } + else + { + builder.Append(','); + } + + if (keyProperties.Count != 1) + { + builder.Append(getPropertyName(property)); + builder.Append('='); + } + + var keyValueText = GetKeyValueAsString(getPropertyValue, property, literalFormatter); + builder.Append(keyValueText); + } + + builder.Append(')'); + } + + /// + /// Default implementation of the key serializer which uses parentheses (e.g. Customers(1)). + /// + private sealed class DefaultKeySerializer : KeySerializer + { + /// + /// Appends the key expression for an entity to the given + /// + /// The builder to append onto. + /// The type used to represent properties. + /// The key properties. + /// The callback to get each property's name. + /// The callback to get each property's value. + internal override void AppendKeyExpression(StringBuilder builder, ICollection keyProperties, Func getPropertyName, Func getPropertyValue) + { + AppendKeyWithParentheses(builder, keyProperties, getPropertyName, getPropertyValue); + } + } + + /// + /// Implementation of the key serializer which uses segments (e.g. Customers/1). + /// + private sealed class SegmentKeySerializer : KeySerializer + { + /// + /// Creates a new instance of . + /// + internal SegmentKeySerializer() + { + } + + /// + /// Appends the key expression for an entity to the given + /// + /// The builder to append onto. + /// The type used to represent properties. + /// The key properties. + /// The callback to get each property's name. + /// The callback to get each property's value. + internal override void AppendKeyExpression(StringBuilder builder, ICollection keyProperties, Func getPropertyName, Func getPropertyValue) + { + Debug.Assert(builder != null, "builder != null"); + Debug.Assert(keyProperties != null, "keyProperties != null"); + + // Keys-as-segments mode is only supported for non-composite keys, so if there is more than 1 key property, + // then fall back to the default behavior for the edit link and identity. + if (keyProperties.Count > 1) + { + AppendKeyWithParentheses(builder, keyProperties, getPropertyName, getPropertyValue); + } + else + { + AppendKeyWithSegments(builder, keyProperties, getPropertyValue); + } + } + + /// + /// Appends the key for the current resource using segment-based syntax (e.g. Customers/1) onto the given . + /// + /// The builder to append onto. + /// The type used to represent properties. + /// The key properties. + /// The callback to get each property's value. + private static void AppendKeyWithSegments(StringBuilder builder, ICollection keyProperties, Func getPropertyValue) + { + Debug.Assert(keyProperties != null, "keyProperties != null"); + Debug.Assert(keyProperties.Count == 1, "Only supported for non-composite keys."); + + builder.Append('/'); + builder.Append(GetKeyValueAsString(getPropertyValue, keyProperties.Single(), LiteralFormatter.ForKeys(true))); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/LiteralFormatter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/LiteralFormatter.cs new file mode 100644 index 0000000..b08d6e3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/LiteralFormatter.cs @@ -0,0 +1,569 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#else +namespace Microsoft.OData.Evaluation +#endif +#endif +{ +#if ODATA_SERVICE + using System.Data.Linq; +#endif + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Text; + using System.Linq; + using System.Xml; +#if ODATA_CORE + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + using Microsoft.Spatial; +#else + using System.Xml.Linq; + using Microsoft.OData; + using Microsoft.OData.Edm; + using Microsoft.Spatial; + using ExpressionConstants = XmlConstants; +#endif + + + /// + /// Component for formatting literals for use in URIs, ETags, and skip-tokens. + /// + internal abstract class LiteralFormatter + { + /// Default singleton instance for parenthetical keys, etags, or skiptokens. + private static readonly LiteralFormatter DefaultInstance = new DefaultLiteralFormatter(); + +#if ODATA_CORE + /// Default singleton instance which does not URL-encode the resulting string. + private static readonly LiteralFormatter DefaultInstanceWithoutEncoding = new DefaultLiteralFormatter(/*disableUrlEncoding*/ true); +#endif + + /// Default singleton instance for keys formatted as segments. + private static readonly LiteralFormatter KeyAsSegmentInstance = new KeysAsSegmentsLiteralFormatter(); + +#if ODATA_SERVICE + /// + /// Gets the literal formatter for ETags. + /// + internal static LiteralFormatter ForETag + { + get { return DefaultInstance; } + } + + /// + /// Gets the literal formatter for skip-tokens. + /// + internal static LiteralFormatter ForSkipToken + { + get { return DefaultInstance; } + } +#else + /// + /// Gets the literal formatter for URL constants. + /// + internal static LiteralFormatter ForConstants + { + get + { + return DefaultInstance; + } + } +#endif + +#if ODATA_CORE + /// + /// Gets the literal formatter for URL constants which does not URL-encode the string. + /// + internal static LiteralFormatter ForConstantsWithoutEncoding + { + get + { + return DefaultInstanceWithoutEncoding; + } + } +#endif + + /// + /// Gets the literal formatter for keys. + /// + /// if set to true then the key is going to be written as a segment, rather than in parentheses. + /// The literal formatter for keys. + internal static LiteralFormatter ForKeys(bool keysAsSegment) + { + return keysAsSegment ? KeyAsSegmentInstance : DefaultInstance; + } + + /// Converts the specified value to an encoded, serializable string for URI key. + /// Non-null value to convert. + /// value converted to a serializable string for URI key. + internal abstract string Format(object value); + + /// + /// Escapes the result accoridng to URI escaping rules. + /// + /// The result to escape. + /// The escaped string. + protected virtual string EscapeResultForUri(string result) + { + // required for strings as data, DateTime for ':', numbers for '+' + // we specifically do not want to encode leading and trailing "'" wrapping strings/datetime/guid + return Uri.EscapeDataString(result); + } + + /// Converts the given byte[] into string. + /// byte[] that needs to be converted. + /// String containing hex values representing the byte[]. + private static string ConvertByteArrayToKeyString(byte[] byteArray) + { + Debug.Assert(byteArray != null, "byteArray != null"); + return Convert.ToBase64String(byteArray, 0, byteArray.Length); + } + + /// + /// Formats the literal without a type prefix, quotes, or escaping. + /// + /// The non-null value to format. + /// The formatted literal, without type marker or quotes. + private static string FormatRawLiteral(object value) + { + Debug.Assert(value != null, "value != null"); + + string stringValue = value as string; + if (stringValue != null) + { + return stringValue; + } + + if (value is bool) + { + return XmlConvert.ToString((bool)value); + } + + if (value is byte) + { + return XmlConvert.ToString((byte)value); + } + +#if ODATA_SERVICE || ODATA_CLIENT + if (value is DateTime) + { + // Since the server/client supports DateTime values, convert the DateTime value + // to DateTimeOffset and use XmlCOnvert to convert to String. + // If datetime kind is unspecified, then treat it as UTC. +#if ODATA_SERVICE + DateTimeOffset dto = WebUtil.ConvertDateTimeToDateTimeOffset((DateTime)value); +#elif ODATA_CLIENT + DateTimeOffset dto = PlatformHelper.ConvertDateTimeToDateTimeOffset((DateTime)value); +#endif + + return XmlConvert.ToString(dto); + } +#endif + + if (value is decimal) + { + return XmlConvert.ToString((decimal)value); + } + + if (value is double) + { + string formattedDouble = XmlConvert.ToString((double)value); + formattedDouble = SharedUtils.AppendDecimalMarkerToDouble(formattedDouble); + return formattedDouble; + } + + if (value is Guid) + { + return value.ToString(); + } + + if (value is short) + { + return XmlConvert.ToString((Int16)value); + } + + if (value is int) + { + return XmlConvert.ToString((Int32)value); + } + + if (value is long) + { + return XmlConvert.ToString((Int64)value); + } + + if (value is sbyte) + { + return XmlConvert.ToString((SByte)value); + } + + if (value is float) + { + return XmlConvert.ToString((Single)value); + } + + byte[] array = value as byte[]; + if (array != null) + { + return ConvertByteArrayToKeyString(array); + } + + if (value is Date) + { + return value.ToString(); + } + + if (value is DateTimeOffset) + { + return XmlConvert.ToString((DateTimeOffset)value); + } + + if (value is TimeOfDay) + { + return value.ToString(); + } + + if (value is TimeSpan) + { + return EdmValueWriter.DurationAsXml((TimeSpan)value); + } + + Geography geography = value as Geography; + if (geography != null) + { + return WellKnownTextSqlFormatter.Create(true).Write(geography); + } + + Geometry geometry = value as Geometry; + if (geometry != null) + { + return WellKnownTextSqlFormatter.Create(true).Write(geometry); + } + + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + return enumValue.Value; + } + + throw SharedUtils.CreateExceptionForUnconvertableType(value); + } + + /// + /// Formats the literal without a type prefix or quotes, but does escape it. + /// + /// The non-null value to format. + /// The formatted literal, without type marker or quotes. + private string FormatAndEscapeLiteral(object value) + { + Debug.Assert(value != null, "value != null"); + + string result = FormatRawLiteral(value); + Debug.Assert(result != null, "result != null"); + + if (value is string) + { + result = result.Replace("'", "''"); + } + + return this.EscapeResultForUri(result); + } + + /// + /// Helper utilities that capture any deltas between ODL, the WCF DS Client, and the WCF DS Server. + /// + private static class SharedUtils + { + /// + /// Creates a new exception instance to be thrown if the value is not a type that can be formatted as a literal. + /// DEVNOTE: Will return a different exception depending on whether this is ODataLib, the WCF DS Server, or the WCF DS client. + /// + /// The literal value that could not be converted. + /// The exception that should be thrown. + internal static InvalidOperationException CreateExceptionForUnconvertableType(object value) + { +#if ODATA_SERVICE + return new InvalidOperationException(Microsoft.OData.Service.Strings.Serializer_CannotConvertValue(value)); +#endif +#if ODATA_CLIENT + return Error.InvalidOperation(Client.Strings.Context_CannotConvertKey(value)); +#endif +#if ODATA_CORE + return new ODataException(Strings.ODataUriUtils_ConvertToUriLiteralUnsupportedType(value.GetType().ToString())); +#endif + } + + /// + /// Tries to convert the given value to one of the standard recognized types. Used specifically for handling XML and binary types. + /// + /// The original value. + /// The value converted to one of the standard types. + /// Whether or not the value was converted. + internal static bool TryConvertToStandardType(object value, out object converted) + { + byte[] array; + if (TryGetByteArrayFromBinary(value, out array)) + { + converted = array; + return true; + } + +#if !ODATA_CORE + XElement xml = value as XElement; + if (xml != null) + { + converted = xml.ToString(); + return true; + } +#endif + converted = null; + return false; + } + + /// + /// Appends the decimal marker to string form of double value if necessary. + /// DEVNOTE: Only used by the client and ODL, for legacy/back-compat reasons. + /// + /// Input string. + /// String with decimal marker optionally added. + internal static string AppendDecimalMarkerToDouble(string input) + { + // DEVNOTE: for some reason, the client adds .0 to doubles where the server does not. + // Unfortunately, it would be a breaking change to alter this behavior now. +#if ODATA_CLIENT || ODATA_CORE + IEnumerable characters = input.ToCharArray(); + +#if ODATA_CORE + // negative numbers can also be 'whole', but the client did not take that into account. + if (input[0] == '-') + { + characters = characters.Skip(1); + } +#endif + // a whole number should be all digits. + if (characters.All(char.IsDigit)) + { + return input + ".0"; + } + +#endif + // the server never appended anything, so it will fall through to here. + return input; + } + + /// + /// Tries to convert an instance of System.Data.Linq.Binary to a byte array. + /// + /// The original value which might be an instance of System.Data.Linq.Binary. + /// The converted byte array, if it was converted. + /// Whether or not the value was converted. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value", Justification = "Method is compiled into 3 assemblies, and the parameter is used in 2 of them.")] + private static bool TryGetByteArrayFromBinary(object value, out byte[] array) + { + // DEVNOTE: the client does not have a reference to System.Data.Linq, but the server does. + // So we need to interact with Binary differently. +#if ODATA_SERVICE + Binary binary = value as Binary; + if (binary != null) + { + array = binary.ToArray(); + return true; + } +#endif +#if ODATA_CLIENT + return ClientConvert.TryConvertBinaryToByteArray(value, out array); +#else + array = null; + return false; +#endif + } + } + + /// + /// Default literal formatter implementation. + /// + private sealed class DefaultLiteralFormatter : LiteralFormatter + { + /// If true, literals will not be URL encoded. + private readonly bool disableUrlEncoding; + + /// + /// Creates a new instance of . + /// + internal DefaultLiteralFormatter() + : this(false /*disableUrlEncoding*/) + { + } + +#if ODATA_CORE + /// + /// Creates a new instance of . + /// + /// If true, literals will not be URL encoded. + internal DefaultLiteralFormatter(bool disableUrlEncoding) +#else + /// + /// Creates a new instance of . + /// + /// If true, literals will not be URL encoded. + private DefaultLiteralFormatter(bool disableUrlEncoding) +#endif + { + this.disableUrlEncoding = disableUrlEncoding; + } + + /// Converts the specified value to an encoded, serializable string for URI key. + /// Non-null value to convert. + /// value converted to a serializable string for URI key. + internal override string Format(object value) + { + object converted; + if (SharedUtils.TryConvertToStandardType(value, out converted)) + { + value = converted; + } + + return this.FormatLiteralWithTypePrefix(value); + } + + /// + /// Escapes the result accoridng to URI escaping rules. + /// + /// The result to escape. + /// The escaped string. + protected override string EscapeResultForUri(string result) + { +#if !ODATA_CORE + Debug.Assert(!this.disableUrlEncoding, "Only supported for ODataLib for backwards compatibility reasons."); +#endif + if (!this.disableUrlEncoding) + { + result = base.EscapeResultForUri(result); + } + + return result; + } + + /// + /// Formats the literal with a type prefix and quotes (if the type requires it). + /// + /// The value to format. + /// The formatted literal, with type marker if needed. + private string FormatLiteralWithTypePrefix(object value) + { + Debug.Assert(value != null, "value != null. Null values need to be handled differently in some cases."); + + var enumValue = value as ODataEnumValue; + if (enumValue != null) + { + if (string.IsNullOrEmpty(enumValue.TypeName)) + { + // TODO: [Sizhong Du] Replace with error string #647. + throw new ODataException("Type name should not be null or empty when serializing an Enum value for URI key."); + } + + return enumValue.TypeName + "'" + this.FormatAndEscapeLiteral(enumValue.Value) + "'"; + } + + string result = this.FormatAndEscapeLiteral(value); + + if (value is byte[]) + { + return ExpressionConstants.LiteralPrefixBinary + "'" + result + "'"; + } + + if (value is Geography) + { + return ExpressionConstants.LiteralPrefixGeography + "'" + result + "'"; + } + + if (value is Geometry) + { + return ExpressionConstants.LiteralPrefixGeometry + "'" + result + "'"; + } + + if (value is TimeSpan) + { + return ExpressionConstants.LiteralPrefixDuration + "'" + result + "'"; + } + + if (value is string) + { + return "'" + result + "'"; + } + + // for int32,int64,float,double, decimal, Infinity/NaN, just output them without prefix or suffix such as L/M/D/F. + return result; + } + } + + /// + /// Literal formatter for keys which are written as URI segments. + /// Very similar to the default, but it never puts the type markers or single quotes around the value. + /// + private sealed class KeysAsSegmentsLiteralFormatter : LiteralFormatter + { + /// + /// Creates a new instance of . + /// + internal KeysAsSegmentsLiteralFormatter() + { + } + + /// Converts the specified value to an encoded, serializable string for URI key. + /// Non-null value to convert. + /// value converted to a serializable string for URI key. + internal override string Format(object value) + { + Debug.Assert(value != null, "value != null"); + + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + value = enumValue.Value; + } + + object converted; + if (SharedUtils.TryConvertToStandardType(value, out converted)) + { + value = converted; + } + + string stringValue = value as string; + if (stringValue != null) + { + value = EscapeLeadingDollarSign(stringValue); + } + + return FormatAndEscapeLiteral(value); + } + + /// + /// If the string starts with a '$', prepends another '$' to escape it. + /// + /// The string value. + /// The string value with a leading '$' escaped, if one was present. + private static string EscapeLeadingDollarSign(string stringValue) + { + if (stringValue.Length > 0 && stringValue[0] == '$') + { + stringValue = '$' + stringValue; + } + + return stringValue; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/NoOpResourceMetadataBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/NoOpResourceMetadataBuilder.cs new file mode 100644 index 0000000..337151e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/NoOpResourceMetadataBuilder.cs @@ -0,0 +1,172 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Evaluation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + /// + /// Implementation of the metadata builder which only returns values which were explicitly set (never computing or modifying them). + /// + internal sealed class NoOpResourceMetadataBuilder : ODataResourceMetadataBuilder + { + /// + /// The resource whose payload metadata is being queried. + /// + private readonly ODataResourceBase resource; + + /// + /// Creates a new no-op metadata builder. + /// + /// The resource whose payload metadata is being queried. + internal NoOpResourceMetadataBuilder(ODataResourceBase resource) + { + Debug.Assert(resource != null, "resource != null"); + + this.resource = resource; + } + + /// + /// Gets the edit link of the entity. + /// + /// + /// The absolute URI of the edit link for the entity. + /// + internal override Uri GetEditLink() + { + return this.resource.NonComputedEditLink; + } + + /// + /// Gets the read link of the entity. + /// + /// + /// The absolute URI of the read link for the entity. + /// + internal override Uri GetReadLink() + { + return this.resource.NonComputedReadLink; + } + + /// + /// Gets the ID of the entity. + /// + /// + /// The ID for the entity. + /// + internal override Uri GetId() + { + return this.resource.IsTransient ? null : this.resource.NonComputedId; + } + + /// + /// Gets the ETag of the entity. + /// + /// + /// The ETag for the entity. + /// + internal override string GetETag() + { + return this.resource.NonComputedETag; + } + + /// + /// Gets the default media resource of the entity. + /// + /// + /// The the default media resource of the entity. + /// Or null if the entity is not an MLE. + /// + internal override ODataStreamReferenceValue GetMediaResource() + { + return this.resource.NonComputedMediaResource; + } + + /// + /// Gets the entity properties. + /// + /// Non-computed properties from the entity. + /// The the computed and non-computed entity properties. + internal override IEnumerable GetProperties(IEnumerable nonComputedProperties) + { + return nonComputedProperties; + } + + + /// + /// Gets the list of computed and non-computed actions for the entity. + /// + /// The list of computed and non-computed actions for the entity. + internal override IEnumerable GetActions() + { + return this.resource.NonComputedActions; + } + + /// + /// Gets the list of computed and non-computed functions for the entity. + /// + /// The list of computed and non-computed functions for the entity. + internal override IEnumerable GetFunctions() + { + return this.resource.NonComputedFunctions; + } + + /// + /// Gets the navigation link URI for the specified navigation property. + /// + /// The name of the navigation property to get the navigation link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null navigation link url is an uninitialized value or a value that was set explicitly. + /// + /// The navigation link URI for the navigation property. + /// null if its not possible to determine the navigation link for the specified navigation property. + /// + internal override Uri GetNavigationLinkUri(string navigationPropertyName, Uri navigationLinkUrl, bool hasNestedResourceInfoUrl) + { + return navigationLinkUrl; + } + + /// + /// Gets the association link URI for the specified navigation property. + /// + /// The name of the navigation property to get the association link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null association link url is an uninitialized value or a value that was set explicitly. + /// + /// The association link URI for the navigation property. + /// null if its not possible to determine the association link for the specified navigation property. + /// + internal override Uri GetAssociationLinkUri(string navigationPropertyName, Uri associationLinkUrl, bool hasAssociationLinkUrl) + { + return associationLinkUrl; + } + + /// + /// Get the id that need to be written into wire + /// + /// The id return to the caller + /// + /// If writer should write odata.id property into wire + /// + internal override bool TryGetIdForSerialization(out Uri id) + { + if (this.resource.IsTransient) + { + id = null; + return true; + } + else + { + id = this.GetId(); + return id != null; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs new file mode 100644 index 0000000..5749322 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs @@ -0,0 +1,696 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Evaluation +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + using Microsoft.OData.JsonLight; + using Microsoft.OData.Metadata; + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + #endregion + + /// + /// Implementation of OData entity metadata builder based on OData protocol conventions. + /// + internal sealed class ODataConventionalEntityMetadataBuilder : ODataConventionalResourceMetadataBuilder + { + /// The edit link. + /// This is lazily evaluated. It may be retrieved from the resource or computed. + private Uri computedEditLink; + + /// The read link. + /// This is lazily evaluated. It may be retrieved from the resource or computed. + private Uri computedReadLink; + + /// The computed ETag. + private string computedETag; + + /// true if the etag value has been computed, false otherwise. + private bool etagComputed; + + /// The computed ID of this entity instance. + /// + /// This is always built from the key properties, and never comes from the resource. + /// + private Uri computedId; + + /// The computed MediaResource for MLEs. + private ODataStreamReferenceValue computedMediaResource; + + /// The list of computed stream properties. + private List computedStreamProperties; + + /// + /// Mark if we are at state of ResourceEnd, if it is true, GetProperties would concat computed stream properties. + /// + private bool isResourceEnd; + + /// The missing operation generator for the current resource. + private ODataMissingOperationGenerator missingOperationGenerator; + + /// The computed key property name and value pairs of the resource. + private ICollection> computedKeyProperties; + + /// + /// Constructor + /// + /// The context to answer basic metadata questions about the resource. + /// The metadata context. + /// The uri builder to use. + internal ODataConventionalEntityMetadataBuilder(IODataResourceMetadataContext resourceMetadataContext, IODataMetadataContext metadataContext, ODataUriBuilder uriBuilder) + : base(resourceMetadataContext, metadataContext, uriBuilder) + { + this.isResourceEnd = true; // Keep default behavior + } + + /// + /// Lazy evaluated computed entity Id. This is always a computed value and never comes from the resource. + /// + private Uri ComputedId + { + get + { + this.ComputeAndCacheId(); + return this.computedId; + } + } + + /// + /// The missing operation generator for the current resource. + /// + private ODataMissingOperationGenerator MissingOperationGenerator + { + get { return this.missingOperationGenerator ?? (this.missingOperationGenerator = new ODataMissingOperationGenerator(this.ResourceMetadataContext, this.MetadataContext)); } + } + + /// + /// The computed key property name and value pairs of the resource. + /// If a value is unsigned integer, it will be automatically converted to its underlying type. + /// + private ICollection> ComputedKeyProperties + { + get + { + if (computedKeyProperties == null) + { + computedKeyProperties = new List>(); + + foreach (var originalKeyProperty in this.ResourceMetadataContext.KeyProperties) + { + object newValue = this.MetadataContext.Model.ConvertToUnderlyingTypeIfUIntValue(originalKeyProperty.Value); + computedKeyProperties.Add(new KeyValuePair(originalKeyProperty.Key, newValue)); + } + } + + return computedKeyProperties; + } + } + + /// + /// Gets canonical url of current resource. + /// + /// The canonical url of current resource. + public override Uri GetCanonicalUrl() + { + return this.GetId(); + } + + /// + /// Gets the edit url of current entity. + /// + /// The edit url of current entity. + public override Uri GetEditUrl() + { + return this.GetEditLink(); + } + + /// + /// Gets the read url of current entity. + /// + /// The read url of current entity. + public override Uri GetReadUrl() + { + return this.GetReadLink(); + } + + /// + /// Gets the edit link of the entity. + /// + /// + /// The absolute URI of the edit link for the entity. + /// Or null if it is not possible to determine the edit link. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override Uri GetEditLink() + { + if (this.ResourceMetadataContext.Resource.HasNonComputedEditLink) + { + return this.ResourceMetadataContext.Resource.NonComputedEditLink; + } + else + { + // For readonly entity if ReadLink is there and EditLink is null, we should not calculate the EditLink in both serializer and deserializer. + if (this.ResourceMetadataContext.Resource.IsTransient || this.ResourceMetadataContext.Resource.HasNonComputedReadLink) + { + return null; + } + else + { + if (this.computedEditLink != null) + { + return this.computedEditLink; + } + else + { + return this.computedEditLink = this.ComputeEditLink(); + } + } + } + } + + /// + /// Gets the read link of the entity. + /// + /// + /// The absolute URI of the read link for the entity. + /// Or null if it is not possible to determine the read link. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override Uri GetReadLink() + { + if (this.ResourceMetadataContext.Resource.HasNonComputedReadLink) + { + return this.ResourceMetadataContext.Resource.NonComputedReadLink; + } + else + { + if (this.computedReadLink != null) + { + return this.computedReadLink; + } + else + { + return this.computedReadLink = this.GetEditLink(); + } + } + } + + /// + /// Gets the ID of the entity. + /// + /// + /// The ID for the entity. + /// Or null if it is not possible to determine the ID. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override Uri GetId() + { + // If the Id were on the wire, use that wire value for the Id. + // If the resource was transient resource, return null for Id + // Otherwise compute it based on the key values. + return this.ResourceMetadataContext.Resource.HasNonComputedId ? this.ResourceMetadataContext.Resource.NonComputedId : (this.ResourceMetadataContext.Resource.IsTransient ? null : this.ComputedId); + } + + /// + /// Gets the ETag of the entity. + /// + /// + /// The ETag for the entity. + /// Or null if it is not possible to determine the ETag. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override string GetETag() + { + if (this.ResourceMetadataContext.Resource.HasNonComputedETag) + { + return this.ResourceMetadataContext.Resource.NonComputedETag; + } + + if (!this.etagComputed) + { + StringBuilder resultBuilder = new StringBuilder(); + foreach (KeyValuePair etagProperty in this.ResourceMetadataContext.ETagProperties) + { + if (resultBuilder.Length > 0) + { + resultBuilder.Append(','); + } + else + { + resultBuilder.Append("W/\""); + } + + string keyValueText; + if (etagProperty.Value == null) + { + keyValueText = ExpressionConstants.KeywordNull; + } + else + { + keyValueText = LiteralFormatter.ForConstants.Format(etagProperty.Value); + } + + resultBuilder.Append(keyValueText); + } + + if (resultBuilder.Length > 0) + { + resultBuilder.Append('"'); + this.computedETag = resultBuilder.ToString(); + } + + this.etagComputed = true; + } + + return this.computedETag; + } + + /// + /// Gets the default media resource of the entity. + /// + /// + /// The the default media resource of the entity. + /// Or null if the entity is not an MLE. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override ODataStreamReferenceValue GetMediaResource() + { + if (this.ResourceMetadataContext.Resource.NonComputedMediaResource != null) + { + return this.ResourceMetadataContext.Resource.NonComputedMediaResource; + } + + if (this.computedMediaResource == null && this.ResourceMetadataContext.TypeContext.IsMediaLinkEntry) + { + this.computedMediaResource = new ODataStreamReferenceValue(); + this.computedMediaResource.SetMetadataBuilder(this, /*propertyName*/ null); + } + + return this.computedMediaResource; + } + + /// + /// Gets the entity properties. + /// + /// Non-computed properties from the entity. + /// The the computed and non-computed entity properties. + internal override IEnumerable GetProperties(IEnumerable nonComputedProperties) + { + if (!isResourceEnd) + { + return nonComputedProperties; + } + else + { + return ODataUtilsInternal.ConcatEnumerables(nonComputedProperties, this.GetComputedStreamProperties(nonComputedProperties)); + } + } + + /// + /// Mark the resource is just started to process. + /// + internal override void StartResource() + { + this.isResourceEnd = false; + } + + /// + /// Mark the resource has finished the processing. So GetProperties() need append ComputedStreamProperties for entity. + /// + internal override void EndResource() + { + this.isResourceEnd = true; + } + + /// + /// Gets the list of computed and non-computed actions for the entity. + /// + /// The list of computed and non-computed actions for the entity. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override IEnumerable GetActions() + { + return ODataUtilsInternal.ConcatEnumerables(this.ResourceMetadataContext.Resource.NonComputedActions, this.MissingOperationGenerator.GetComputedActions()); + } + + /// + /// Gets the list of computed and non-computed functions for the entity. + /// + /// The list of computed and non-computed functions for the entity. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override IEnumerable GetFunctions() + { + return ODataUtilsInternal.ConcatEnumerables(this.ResourceMetadataContext.Resource.NonComputedFunctions, this.MissingOperationGenerator.GetComputedFunctions()); + } + + /// + /// Gets the navigation link URI for the specified navigation property. + /// + /// The name of the navigation property to get the navigation link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null navigation link url is an uninitialized value or a value that was set explicitly. + /// + /// The navigation link URI for the navigation property. + /// null if its not possible to determine the navigation link for the specified navigation property. + /// + internal override Uri GetNavigationLinkUri(string navigationPropertyName, Uri navigationLinkUrl, bool hasNestedResourceInfoUrl) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); + + return hasNestedResourceInfoUrl ? navigationLinkUrl : this.UriBuilder.BuildNavigationLinkUri(this.GetReadLink(), navigationPropertyName); + } + + /// + /// Gets the association link URI for the specified navigation property. + /// + /// The name of the navigation property to get the association link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null association link url is an uninitialized value or a value that was set explicitly. + /// + /// The association link URI for the navigation property. + /// null if its not possible to determine the association link for the specified navigation property. + /// + internal override Uri GetAssociationLinkUri(string navigationPropertyName, Uri associationLinkUrl, bool hasAssociationLinkUrl) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); + + return hasAssociationLinkUrl ? associationLinkUrl : this.UriBuilder.BuildAssociationLinkUri(this.GetReadLink(), navigationPropertyName); + } + + /// + /// Get the operation target URI for the specified . + /// + /// The fully qualified name of the operation for which to get the target URI. + /// The binding parameter type name. + /// The parameter names to include in the target, or null/empty if there is none. + /// + /// The target URI for the operation. + /// null if it is not possible to determine the target URI for the specified operation. + /// + internal override Uri GetOperationTargetUri(string operationName, string bindingParameterTypeName, string parameterNames) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); + + Uri baseUri; + if (string.IsNullOrEmpty(bindingParameterTypeName) || this.ResourceMetadataContext.Resource.NonComputedEditLink != null) + { + // if there is no parameter type name to append, or the edit-link is an opaque non-computed value, then use the edit-link as normal. + baseUri = this.GetEditLink(); + } + else + { + // Otherwise, use the computed URI which has no type segment + baseUri = this.GetId(); + } + + return this.UriBuilder.BuildOperationTargetUri(baseUri, operationName, bindingParameterTypeName, parameterNames); + } + + /// + /// Get the operation title for the specified . + /// + /// The fully qualified name of the operation for which to get the target URI. + /// + /// The title for the operation. + /// null if it is not possible to determine the title for the specified operation. + /// + internal override string GetOperationTitle(string operationName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); + + // TODO: What is the convention for operation title? + return operationName; + } + + /// + /// Get the id that need to be written into wire + /// + /// The id return to the caller + /// + /// If writer should write odata.id property into wire + /// + internal override bool TryGetIdForSerialization(out Uri id) + { + id = this.ResourceMetadataContext.Resource.IsTransient ? null : this.GetId(); + return true; + } + + /// + /// Computes the edit link. + /// + /// Uri that was computed based on the computed Id and possible type segment. + private Uri ComputeEditLink() + { + Uri uri = this.ResourceMetadataContext.Resource.HasNonComputedId ? this.ResourceMetadataContext.Resource.NonComputedId : this.ComputedId; + + Debug.Assert(this.ResourceMetadataContext != null && this.ResourceMetadataContext.TypeContext != null, "this.resourceMetadataContext != null && this.resourceMetadataContext.TypeContext != null"); + if (this.ResourceMetadataContext.ActualResourceTypeName != this.ResourceMetadataContext.TypeContext.NavigationSourceEntityTypeName) + { + uri = this.UriBuilder.AppendTypeSegment(uri, this.ResourceMetadataContext.ActualResourceTypeName); + } + + return uri; + } + + /// + /// Computes and sets the field for the computed Id. + /// + private void ComputeAndCacheId() + { + if (this.computedId != null) + { + return; + } + + Uri uri; + switch (this.ResourceMetadataContext.TypeContext.NavigationSourceKind) + { + case EdmNavigationSourceKind.Singleton: + uri = this.ComputeIdForSingleton(); + break; + case EdmNavigationSourceKind.ContainedEntitySet: + uri = this.ComputeIdForContainment(); + break; + case EdmNavigationSourceKind.UnknownEntitySet: + throw new ODataException(Strings.ODataMetadataBuilder_UnknownEntitySet(this.ResourceMetadataContext.TypeContext.NavigationSourceName)); + default: + uri = this.ComputeId(); + break; + } + + this.computedId = uri; + } + + /// + /// Compute id for neither singleton, nor containment scenario. + /// + /// + /// The of @odata.id. + /// + private Uri ComputeId() + { + Uri uri = this.UriBuilder.BuildBaseUri(); + uri = this.UriBuilder.BuildEntitySetUri(uri, this.ResourceMetadataContext.TypeContext.NavigationSourceName); + uri = this.UriBuilder.BuildEntityInstanceUri(uri, this.ComputedKeyProperties, this.ResourceMetadataContext.ActualResourceTypeName); + return uri; + } + + /// + /// Compute id for containment scenario. + /// + /// + /// The of @odata.id. + /// + private Uri ComputeIdForContainment() + { + Uri uri; + + if (!TryComputeIdFromParent(out uri)) + { + // Compute ID from context URL rather than from parent. + uri = this.UriBuilder.BuildBaseUri(); + ODataUri odataUri = this.ODataUri ?? this.MetadataContext.ODataUri; + + if (odataUri == null || odataUri.Path == null || odataUri.Path.Count == 0) + { + throw new ODataException(Strings.ODataMetadataBuilder_MissingParentIdOrContextUrl); + } + + uri = this.GetContainingEntitySetUri(uri, odataUri); + } + + // A path segment for the containment navigation property + uri = this.UriBuilder.BuildEntitySetUri(uri, this.ResourceMetadataContext.TypeContext.NavigationSourceName); + + if (this.ResourceMetadataContext.TypeContext.IsFromCollection) + { + uri = this.UriBuilder.BuildEntityInstanceUri( + uri, + this.ComputedKeyProperties, + this.ResourceMetadataContext.ActualResourceTypeName); + } + + return uri; + } + + private bool TryComputeIdFromParent(out Uri uri) + { + try + { + ODataConventionalResourceMetadataBuilder parent = this.ParentMetadataBuilder as ODataConventionalResourceMetadataBuilder; + if (parent != null && parent != this) + { + // Get the parent canonical url + uri = parent.GetCanonicalUrl(); + + if (uri != null) + { + // And append cast (if needed). + // A cast segment if the navigation property is defined on a type derived from the type expected. + IODataResourceTypeContext typeContext = parent.ResourceMetadataContext.TypeContext; + + if (parent.ResourceMetadataContext.ActualResourceTypeName != typeContext.ExpectedResourceTypeName) + { + // Do not append type cast if we know that the navigation property is in base type, not in derived type. + ODataResourceTypeContext.ODataResourceTypeContextWithModel typeContextWithModel = + typeContext as ODataResourceTypeContext.ODataResourceTypeContextWithModel; + if (typeContextWithModel == null || + typeContextWithModel.ExpectedResourceType.FindProperty( + this.ResourceMetadataContext.TypeContext.NavigationSourceName) == null) + { + uri = new Uri(UriUtils.EnsureTaillingSlash(uri), + parent.ResourceMetadataContext.ActualResourceTypeName); + } + } + + return true; + } + } + } + catch (ODataException) + { + } + + uri = null; + return false; + } + + /// + /// Compute id for singleton scenario. + /// + /// + /// The of @odata.id. + /// + private Uri ComputeIdForSingleton() + { + // Do not append key for singleton. + Uri uri = this.UriBuilder.BuildBaseUri(); + uri = this.UriBuilder.BuildEntitySetUri(uri, this.ResourceMetadataContext.TypeContext.NavigationSourceName); + return uri; + } + + /// + /// Computes all projected or missing stream properties. + /// + /// Non-computed properties from the entity. + /// The the computed stream properties for the resource. + private IEnumerable GetComputedStreamProperties(IEnumerable nonComputedProperties) + { + if (this.computedStreamProperties == null) + { + // Remove all the projected properties that were already read from the payload + IDictionary projectedStreamProperties = this.ResourceMetadataContext.SelectedStreamProperties; + if (nonComputedProperties != null) + { + foreach (ODataProperty payloadProperty in nonComputedProperties) + { + projectedStreamProperties.Remove(payloadProperty.Name); + } + } + + this.computedStreamProperties = new List(); + if (projectedStreamProperties.Count > 0) + { + // Create all the missing stream properties and set the metadata builder + foreach (string missingStreamPropertyName in projectedStreamProperties.Keys) + { + ODataStreamReferenceValue streamPropertyValue = new ODataStreamReferenceValue(); + streamPropertyValue.SetMetadataBuilder(this, missingStreamPropertyName); + this.computedStreamProperties.Add(new ODataProperty { Name = missingStreamPropertyName, Value = streamPropertyValue }); + } + } + } + + return this.computedStreamProperties; + } + + /// + /// Gets resource path from service root, and request Uri. + /// + /// The service root Uri. + /// The request Uri. + /// The resource path. + private Uri GetContainingEntitySetUri(Uri baseUri, ODataUri odataUri) + { + ODataPath path = odataUri.Path; + List segments = path.ToList(); + ODataPathSegment lastSegment = segments.Last(); + while (!(lastSegment is NavigationPropertySegment) && !(lastSegment is OperationSegment)) + { + segments.Remove(lastSegment); + lastSegment = segments.Last(); + } + + // also removed the last navigation property segment + segments.Remove(lastSegment); + + // Remove the unnecessary type cast segment. + ODataPathSegment nextToLastSegment = segments.Last(); + while (nextToLastSegment is TypeSegment) + { + ODataPathSegment previousSegment = segments[segments.Count - 2]; + IEdmStructuredType owningType = previousSegment.TargetEdmType as IEdmStructuredType; + if (owningType != null && owningType.FindProperty(lastSegment.Identifier) != null) + { + segments.Remove(nextToLastSegment); + nextToLastSegment = segments.Last(); + } + else + { + break; + } + } + + // append each segment to base uri + Uri uri = baseUri; + foreach (ODataPathSegment segment in segments) + { + var keySegment = segment as KeySegment; + if (keySegment == null) + { + uri = this.UriBuilder.BuildEntitySetUri(uri, segment.Identifier); + } + else + { + uri = this.UriBuilder.BuildEntityInstanceUri( + uri, + keySegment.Keys.ToList(), + keySegment.EdmType.FullTypeName()); + } + } + + return uri; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalResourceMetadataBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalResourceMetadataBuilder.cs new file mode 100644 index 0000000..b672fb8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalResourceMetadataBuilder.cs @@ -0,0 +1,568 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Evaluation +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.JsonLight; + #endregion + + /// + /// Implementation of OData resource metadata builder based on OData protocol conventions. + /// This metadata builder will be created for complex or for the resource which is not explicitly known as entity. + /// + internal class ODataConventionalResourceMetadataBuilder : ODataResourceMetadataBuilder + { + /// The context to answer basic metadata questions about the resource. + public readonly IODataResourceMetadataContext ResourceMetadataContext; + + /// The URI builder to use. + protected readonly ODataUriBuilder UriBuilder; + + /// The metadata context. + protected readonly IODataMetadataContext MetadataContext; + + /// The list of nested info that have been processed. Here navigation property and complex will both be marked for convenience. + protected readonly HashSet ProcessedNestedResourceInfos; + + /// The list of stream properties that have been processed. + protected readonly HashSet ProcessedStreamProperties; + + /// The enumerator for unprocessed navigation links. + private IEnumerator unprocessedNavigationLinks; + + /// The enumerator for unprocessed streamProperties. + private IEnumerator unprocessedStreamProperties; + + /// The read url. + /// This is lazily evaluated. It may be retrieved from the resource or computed. + private Uri readUrl; + + /// The edit url. + /// This is lazily evaluated. It may be retrieved from the resource or computed. + private Uri editUrl; + + /// The canonical url. + /// This is lazily evaluated. It may be retrieved from the resource or computed. + private Uri canonicalUrl; + + /// + /// The resource whose payload metadata is being queried. + /// + private ODataResourceBase resource; + + /// + /// Constructor + /// + /// The context to answer basic metadata questions about the resource. + /// The metadata context. + /// The uri builder to use. + internal ODataConventionalResourceMetadataBuilder(IODataResourceMetadataContext resourceMetadataContext, IODataMetadataContext metadataContext, ODataUriBuilder uriBuilder) + { + Debug.Assert(resourceMetadataContext != null, "resourceMetadataContext != null"); + Debug.Assert(metadataContext != null, "metadataContext != null"); + Debug.Assert(uriBuilder != null, "uriBuilder != null"); + + this.ResourceMetadataContext = resourceMetadataContext; + this.UriBuilder = uriBuilder; + this.MetadataContext = metadataContext; + this.ProcessedNestedResourceInfos = new HashSet(StringComparer.Ordinal); + this.ProcessedStreamProperties = new HashSet(StringComparer.Ordinal); + this.resource = resourceMetadataContext.Resource; + } + + /// + /// OData uri that parsed based on the context url + /// + internal ODataUri ODataUri { get; set; } + + /// + /// Gets edit url of current resource. + /// Computes edit url of current resource from: + /// 1. NonComputedEditLink + /// 2. CanonicalUrl + /// 3. Parent edit url + /// + /// The edit url of current resource. + /// The method is used to compute edit Url for current resource, including complex. + public virtual Uri GetEditUrl() + { + if (this.editUrl != null) + { + return this.editUrl; + } + + // editUrl = NonComputedEditLink + if (this.resource.HasNonComputedEditLink) + { + return this.editUrl = this.resource.NonComputedEditLink; + } + + // Compute editUrl from canonicalUrl + var canonicalUrl = this.GetCanonicalUrl(); + if (canonicalUrl != null) + { + this.editUrl = canonicalUrl; + } + else + { + // compute edit url from parent + var parent = this.ParentMetadataBuilder as ODataConventionalResourceMetadataBuilder; + if (this.NameAsProperty != null + && parent != null + && parent.GetEditUrl() != null) + { + // If parent is collection of complex, the edit url for this resource should be null. + if (parent.IsFromCollection && !(parent is ODataConventionalEntityMetadataBuilder)) + { + return this.editUrl = null; + } + + // editUrl = parentEditUrl/propertyName + this.editUrl = new Uri(parent.GetEditUrl() + "/" + this.NameAsProperty, UriKind.RelativeOrAbsolute); + } + } + + // Append possible type cast + if (this.editUrl != null && this.ResourceMetadataContext.ActualResourceTypeName != + this.ResourceMetadataContext.TypeContext.ExpectedResourceTypeName) + { + this.editUrl = this.UriBuilder.AppendTypeSegment(editUrl, + this.ResourceMetadataContext.ActualResourceTypeName); + } + + return this.editUrl; + } + + /// + /// Gets read url of current resource. + /// Computes read url of current resource from: + /// 1. NonComputedReadLink + /// 2. Computed edit url + /// 3. Parent read url + /// + /// The read url of current resource. + /// The method is used to compute edit url for resource, including complex. + public virtual Uri GetReadUrl() + { + if (this.readUrl != null) + { + return this.readUrl; + } + + // readUrl = NonComputedReadLink + if (this.resource.HasNonComputedReadLink) + { + return this.readUrl = this.resource.NonComputedReadLink; + } + + // Compute readUrl from editUrl + var editLink = this.GetEditUrl(); + if (editLink != null) + { + return this.readUrl = editLink; + } + + // Compute readUrl from parent readUrl + var parent = this.ParentMetadataBuilder as ODataConventionalResourceMetadataBuilder; + if (this.NameAsProperty != null + && parent != null + && parent.GetReadUrl() != null) + { + // If parent is collection of complex, the read url for this resource should be null. + if (parent.IsFromCollection && !(parent is ODataConventionalEntityMetadataBuilder)) + { + return this.readUrl = null; + } + + // readUrl = parentReadUrl/propertyName + this.readUrl = new Uri(parent.GetReadUrl() + "/" + this.NameAsProperty, UriKind.RelativeOrAbsolute); + + // Append possible type cast + if (this.ResourceMetadataContext.ActualResourceTypeName != + this.ResourceMetadataContext.TypeContext.ExpectedResourceTypeName) + { + this.readUrl = this.UriBuilder.AppendTypeSegment(readUrl, + this.ResourceMetadataContext.ActualResourceTypeName); + } + } + + return this.readUrl; + } + + /// + /// Gets canonical url of current resource. + /// + /// The canonical url of current resource. + public virtual Uri GetCanonicalUrl() + { + if (this.canonicalUrl != null) + { + return this.canonicalUrl; + } + + // canonicalUrl = HasNonComputedId + if (this.resource.HasNonComputedId) + { + return this.canonicalUrl = this.resource.NonComputedId; + } + + // Compute canonicalUrl from parent canonicalUrl + var parent = this.ParentMetadataBuilder as ODataConventionalResourceMetadataBuilder; + if (this.NameAsProperty != null + && parent != null + && parent.GetCanonicalUrl() != null) + { + // If parent is collection of complex, the canonical url for this resource should be null. + if (parent.IsFromCollection && !(parent is ODataConventionalEntityMetadataBuilder)) + { + return this.canonicalUrl = null; + } + + // canonicalUrl = parentCanonicalUrl[/typeCast]/propertyName + // Different from edit url and read url, canonical url only needs type cast when the property is defined on derived type. + this.canonicalUrl = parent.GetCanonicalUrl(); + + IODataResourceTypeContext typeContext = parent.ResourceMetadataContext.TypeContext; + + if (parent.ResourceMetadataContext.ActualResourceTypeName != typeContext.ExpectedResourceTypeName) + { + // Do not append type cast if we know that the navigation property is in base type, not in derived type. + ODataResourceTypeContext.ODataResourceTypeContextWithModel typeContextWithModel = + typeContext as ODataResourceTypeContext.ODataResourceTypeContextWithModel; + if (typeContextWithModel == null || + typeContextWithModel.ExpectedResourceType.FindProperty( + this.NameAsProperty) == null) + { + this.canonicalUrl = this.UriBuilder.AppendTypeSegment(canonicalUrl, + parent.ResourceMetadataContext.ActualResourceTypeName); + } + } + + this.canonicalUrl = new Uri(this.canonicalUrl + "/" + this.NameAsProperty, UriKind.RelativeOrAbsolute); + } + else + { + if (this.ODataUri != null && this.ODataUri.Path.Count != 0) + { + this.canonicalUrl = this.ODataUri.BuildUri(ODataUrlKeyDelimiter.Parentheses); + } + } + + return this.canonicalUrl; + } + + /// + /// Mark the resource is just started to process. + /// + internal virtual void StartResource() + { + } + + /// + /// Mark the resource has finished the processing. + /// + internal virtual void EndResource() + { + } + + /// + /// Gets the edit link of the resource. + /// + /// The edit link of the resource. + internal override Uri GetEditLink() + { + return this.resource.NonComputedEditLink; + } + + /// + /// Gets the read link of the resource. + /// + /// The read link of the resource. + internal override Uri GetReadLink() + { + return this.resource.NonComputedReadLink; + } + + /// + /// Gets the id of the resource. + /// Only applies entity, so return null here, and will be overridden by entity. + /// + /// null + internal override Uri GetId() + { + return this.resource.IsTransient ? null : this.resource.NonComputedId; + } + + /// + /// Tries to get id which is needed to written into wire. + /// Only applies to entity. + /// + /// The id returns to caller, null here and will be overridden by entity. + /// false + internal override bool TryGetIdForSerialization(out Uri id) + { + if (this.resource.IsTransient) + { + id = null; + return true; + } + else + { + id = this.GetId(); + return id != null; + } + } + + /// + /// Gets the etag for current resource. + /// + /// The etag for current resource. + internal override string GetETag() + { + return this.resource.NonComputedETag; + } + + /// + /// Gets the entity properties. + /// + /// Non-computed properties from the entity. + /// The the computed and non-computed entity properties. + internal override IEnumerable GetProperties(IEnumerable nonComputedProperties) + { + return nonComputedProperties; + } + + /// + /// Gets the list of computed and non-computed actions for the entity. + /// + /// The list of computed and non-computed actions for the entity. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override IEnumerable GetActions() + { + return this.resource.NonComputedActions; + } + + /// + /// Gets the list of computed and non-computed functions for the entity. + /// + /// The list of computed and non-computed functions for the entity. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override IEnumerable GetFunctions() + { + return this.resource.NonComputedFunctions; + } + + /// + /// Marks the given nested resource info as processed. + /// + /// The nested resource info we've already processed. + internal override void MarkNestedResourceInfoProcessed(string navigationPropertyName) + { + Debug.Assert(!string.IsNullOrEmpty(navigationPropertyName), "!string.IsNullOrEmpty(navigationPropertyName)"); + Debug.Assert(this.ProcessedNestedResourceInfos != null, "this.processedNestedResourceInfos != null"); + this.ProcessedNestedResourceInfos.Add(navigationPropertyName); + } + + /// + /// Returns the next unprocessed navigation link or null if there's no more navigation links to process. + /// + /// Returns the next unprocessed navigation link or null if there's no more navigation links to process. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override ODataJsonLightReaderNestedResourceInfo GetNextUnprocessedNavigationLink() + { + if (this.unprocessedNavigationLinks == null) + { + Debug.Assert(this.ResourceMetadataContext != null, "this.resourceMetadataContext != null"); + this.unprocessedNavigationLinks = this.ResourceMetadataContext.SelectedNavigationProperties + .Where(p => !this.ProcessedNestedResourceInfos.Contains(p.Name)) + .Select(ODataJsonLightReaderNestedResourceInfo.CreateProjectedNestedResourceInfo) + .GetEnumerator(); + } + + if (this.unprocessedNavigationLinks.MoveNext()) + { + return this.unprocessedNavigationLinks.Current; + } + + return null; + } + + /// + /// Marks the given stream property as processed. + /// + /// The stream property we've already processed. + internal override void MarkStreamPropertyProcessed(string streamPropertyName) + { + Debug.Assert(!string.IsNullOrEmpty(streamPropertyName), "!string.IsNullOrEmpty(streamPropertyName)"); + Debug.Assert(this.ProcessedStreamProperties != null, "this.processedStreamProperties != null"); + this.ProcessedStreamProperties.Add(streamPropertyName); + } + + /// + /// Returns the next unprocessed stream property or null if there's no more stream properties to process. + /// + /// Returns the next unprocessed stream property or null if there's no more stream properties to process. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal override ODataProperty GetNextUnprocessedStreamProperty() + { + if (this.unprocessedStreamProperties == null) + { + Debug.Assert(this.ResourceMetadataContext != null, "this.resourceMetadataContext != null"); + this.unprocessedStreamProperties = this.ResourceMetadataContext.SelectedStreamProperties + .Where(p => !this.ProcessedStreamProperties.Contains(p.Key)) + .Select(p => p.Key) + .GetEnumerator(); + } + + if (this.unprocessedStreamProperties.MoveNext()) + { + string propertyName = unprocessedStreamProperties.Current; + ODataStreamReferenceValue streamPropertyValue = new ODataStreamReferenceValue(); + streamPropertyValue.SetMetadataBuilder(this, propertyName); + return new ODataProperty { Name = propertyName, Value = streamPropertyValue }; + } + + return null; + } + + //// Stream content type and ETag can't be computed from conventions. + + /// + /// Gets the navigation link URI for the specified navigation property. + /// + /// The name of the navigation property to get the navigation link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null navigation link url is an uninitialized value or a value that was set explicitly. + /// + /// The navigation link URI for the navigation property. + /// null if its not possible to determine the navigation link for the specified navigation property. + /// + internal override Uri GetNavigationLinkUri(string navigationPropertyName, Uri navigationLinkUrl, bool hasNestedResourceInfoUrl) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); + + var readLink = this.GetReadUrl(); + + if (readLink == null || this.IsFromCollection) + { + return null; + } + + return hasNestedResourceInfoUrl ? navigationLinkUrl : this.UriBuilder.BuildNavigationLinkUri(readLink, navigationPropertyName); + } + + /// + /// Gets the association link URI for the specified navigation property. + /// + /// The name of the navigation property to get the association link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null association link url is an uninitialized value or a value that was set explicitly. + /// + /// The association link URI for the navigation property. + /// null if its not possible to determine the association link for the specified navigation property. + /// + internal override Uri GetAssociationLinkUri(string navigationPropertyName, Uri associationLinkUrl, bool hasAssociationLinkUrl) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); + + var readLink = this.GetReadUrl(); + + if (readLink == null || this.IsFromCollection) + { + return null; + } + + return hasAssociationLinkUrl ? associationLinkUrl : this.UriBuilder.BuildAssociationLinkUri(readLink, navigationPropertyName); + } + + /// + /// Gets the edit link of a stream value. + /// + /// The name of the stream property the edit link is computed for; + /// or null for the default media resource. + /// + /// The absolute URI of the edit link for the specified stream property or the default media resource. + /// Or null if it is not possible to determine the stream edit link. + /// + internal override Uri GetStreamEditLink(string streamPropertyName) + { + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); + + return this.UriBuilder.BuildStreamEditLinkUri(this.GetEditUrl(), streamPropertyName); + } + + /// + /// Gets the read link of a stream value. + /// + /// The name of the stream property the read link is computed for; + /// or null for the default media resource. + /// + /// The absolute URI of the read link for the specified stream property or the default media resource. + /// Or null if it is not possible to determine the stream read link. + /// + internal override Uri GetStreamReadLink(string streamPropertyName) + { + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); + + return this.UriBuilder.BuildStreamReadLinkUri(this.GetReadUrl(), streamPropertyName); + } + + //// Stream content type and ETag can't be computed from conventions. + + /// + /// Get the operation target URI for the specified . + /// + /// The fully qualified name of the operation for which to get the target URI. + /// The binding parameter type name. + /// The parameter names to include in the target, or null/empty if there is none. + /// + /// The target URI for the operation. + /// null if it is not possible to determine the target URI for the specified operation. + /// + internal override Uri GetOperationTargetUri(string operationName, string bindingParameterTypeName, string parameterNames) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); + + Uri baseUri; + if (string.IsNullOrEmpty(bindingParameterTypeName) || this.ResourceMetadataContext.Resource.NonComputedEditLink != null) + { + // if there is no parameter type name to append, or the edit-link is an opaque non-computed value, then use the edit-link as normal. + baseUri = this.GetEditLink(); + } + else + { + // Otherwise, use the computed URI which has no type segment + baseUri = this.GetId(); + } + + return this.UriBuilder.BuildOperationTargetUri(baseUri, operationName, bindingParameterTypeName, parameterNames); + } + + /// + /// Get the operation title for the specified . + /// + /// The fully qualified name of the operation for which to get the target URI. + /// + /// The title for the operation. + /// null if it is not possible to determine the title for the specified operation. + /// + internal override string GetOperationTitle(string operationName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); + + // TODO: What is the convention for operation title? + return operationName; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalUriBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalUriBuilder.cs new file mode 100644 index 0000000..509f384 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataConventionalUriBuilder.cs @@ -0,0 +1,285 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Microsoft.OData.JsonLight; + +namespace Microsoft.OData.Evaluation +{ + /// + /// Implementation of OData URI builder based on OData protocol conventions. + /// + internal sealed class ODataConventionalUriBuilder : ODataUriBuilder + { + /// The base URI of the service. This will be used as the base URI for all entity containers. + private readonly Uri serviceBaseUri; + + /// The specific key-serializer to use based on the convention. + private readonly KeySerializer keySerializer; + + /// + /// Constructor. + /// + /// The base URI of the service. This will be used as the base URI for all entity containers. + /// Key delimiter used in url. + internal ODataConventionalUriBuilder(Uri serviceBaseUri, ODataUrlKeyDelimiter urlKeyDelimiter) + { + Debug.Assert(serviceBaseUri != null && serviceBaseUri.IsAbsoluteUri, "serviceBaseUri != null && serviceBaseUri.IsAbsoluteUri"); + Debug.Assert(urlKeyDelimiter != null, "urlKeyDelimiter != null"); + + this.serviceBaseUri = serviceBaseUri; + this.keySerializer = KeySerializer.Create(urlKeyDelimiter.EnableKeyAsSegment); + } + + /// + /// Builds the base URI for the entity container. + /// + /// + /// The base URI for the entity container. + /// This can be either an absolute URI, + /// or relative URI which will be combined with the URI of the metadata document for the service. + /// null if the model doesn't have the service base URI annotation. + /// + internal override Uri BuildBaseUri() + { + return this.serviceBaseUri; + } + + /// + /// Builds the URI for an entity set. + /// + /// The URI to append to. + /// The entity set name. + /// The entity set URI. + internal override Uri BuildEntitySetUri(Uri baseUri, string entitySetName) + { + ValidateBaseUri(baseUri); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(entitySetName, "entitySetName"); + + return AppendSegment(baseUri, entitySetName, true /*escapeSegment*/); + } + + /// + /// Builds the entity instance URI with the given key property values. + /// + /// The URI to append to. + /// The list of name value pair for key properties. + /// The full name of the entity type we are building the key expression for. + /// The entity instance URI. + internal override Uri BuildEntityInstanceUri(Uri baseUri, ICollection> keyProperties, string entityTypeName) + { + ValidateBaseUri(baseUri); + Debug.Assert(keyProperties != null, "keyProperties != null"); + Debug.Assert(!string.IsNullOrEmpty(entityTypeName), "!string.IsNullOrEmpty(entityTypeName)"); + + StringBuilder builder = new StringBuilder(UriUtils.UriToString(baseUri)); + + // TODO: What should be done about escaping the values. + // TODO: What should happen if the URL does end with a slash? + this.AppendKeyExpression(builder, keyProperties, entityTypeName); + return new Uri(builder.ToString(), UriKind.Absolute); + } + + /// + /// Builds the edit link for a stream property. + /// + /// The URI to append to. + /// + /// The name of the stream property the link is computed for; + /// or null for the default media resource. + /// + /// The edit link for the stream. + internal override Uri BuildStreamEditLinkUri(Uri baseUri, string streamPropertyName) + { + ValidateBaseUri(baseUri); + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); + + if (streamPropertyName == null) + { + return AppendSegment(baseUri, ODataConstants.DefaultStreamSegmentName, false /*escapeSegment*/); + } + else + { + return AppendSegment(baseUri, streamPropertyName, true /*escapeSegment*/); + } + } + + /// + /// Builds the read link for a stream property. + /// + /// The URI to append to. + /// + /// The name of the stream property the link is computed for; + /// or null for the default media resource. + /// + /// The read link for the stream. + internal override Uri BuildStreamReadLinkUri(Uri baseUri, string streamPropertyName) + { + ValidateBaseUri(baseUri); + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); + + if (streamPropertyName == null) + { + return AppendSegment(baseUri, ODataConstants.DefaultStreamSegmentName, false /*escapeSegment*/); + } + else + { + return AppendSegment(baseUri, streamPropertyName, true /*escapeSegment*/); + } + } + + /// + /// Builds the navigation link for the navigation property. + /// + /// The URI to append to. + /// The name of the navigation property to get the navigation link URI for. + /// The navigation link URI for the navigation property. + internal override Uri BuildNavigationLinkUri(Uri baseUri, string navigationPropertyName) + { + ValidateBaseUri(baseUri); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); + + return AppendSegment(baseUri, navigationPropertyName, true /*escapeSegment*/); + } + + /// + /// Builds the association link for the navigation property. + /// + /// The URI to append to. + /// The name of the navigation property to get the association link URI for. + /// The association link URI for the navigation property. + internal override Uri BuildAssociationLinkUri(Uri baseUri, string navigationPropertyName) + { + ValidateBaseUri(baseUri); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); + + Uri baseUriWithLinksSegment = AppendSegment(baseUri, navigationPropertyName, true /*escapeSegment*/); + + // We don't want the $ref segment to be escaped, so append that first without escaping, then append the property name with escaping + return AppendSegment(baseUriWithLinksSegment, ODataConstants.EntityReferenceSegmentName, false /*escapeSegment*/); + } + + /// + /// Builds the operation target URI for the specified . + /// + /// The URI to append to. + /// The fully qualified name of the operation for which to get the target URI. + /// The binding parameter type name to include in the target, or null/empty if there is none. + /// The parameter names to include in the target, or null/empty if there is none. + /// The target URI for the operation. + internal override Uri BuildOperationTargetUri(Uri baseUri, string operationName, string bindingParameterTypeName, string parameterNames) + { + ValidateBaseUri(baseUri); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); + + Uri targetUri = baseUri; + + if (!string.IsNullOrEmpty(bindingParameterTypeName)) + { + targetUri = AppendSegment(baseUri, bindingParameterTypeName, true /*escapeSegment*/); + } + + if (!string.IsNullOrEmpty(parameterNames)) + { + operationName += JsonLightConstants.FunctionParameterStart; + operationName += string.Join(JsonLightConstants.FunctionParameterSeparator, parameterNames.Split(JsonLightConstants.FunctionParameterSeparatorChar).Select(p => p + JsonLightConstants.FunctionParameterAssignment + p).ToArray()); + operationName += JsonLightConstants.FunctionParameterEnd; + } + + return AppendSegment(targetUri, operationName, false /*escapeSegment*/); + } + + /// + /// Builds a URI with the given type name appended as a new segment on the base URI. + /// + /// The URI to append to. + /// The fully qualified type name to append. + /// The URI with the type segment appended. + internal override Uri AppendTypeSegment(Uri baseUri, string typeName) + { + ValidateBaseUri(baseUri); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(typeName, "typeName"); + + return AppendSegment(baseUri, typeName, true /*escapeSegment*/); + } + + /// + /// Validates the base URI parameter to be a non-null absolute URI. + /// + /// The base URI parameter to validate. + [Conditional("DEBUG")] + private static void ValidateBaseUri(Uri baseUri) + { + Debug.Assert(baseUri != null, "baseUri != null"); + } + + /// + /// Appends a segment to the specified base URI. + /// + /// The base Uri to append the segment to. + /// The segment to append. + /// True if the new segment should be escaped, otherwise False. + /// New URI with the appended segment and no trailing slash added. + private static Uri AppendSegment(Uri baseUri, string segment, bool escapeSegment) + { + string baseUriString = UriUtils.UriToString(baseUri); + + if (escapeSegment) + { + segment = Uri.EscapeDataString(segment); + } + + if (baseUriString[baseUriString.Length - 1] != ODataConstants.UriSegmentSeparatorChar) + { + return new Uri(baseUriString + ODataConstants.UriSegmentSeparator + segment, UriKind.RelativeOrAbsolute); + } + else + { + return new Uri(baseUri, segment); + } + } + + /// + /// Gets the CLR value of a primitive key property. + /// + /// The key property name. + /// The key property value. + /// The entity type name we are validating the key value for. + /// The primitive value of the key property. + private static object ValidateKeyValue(string keyPropertyName, object keyPropertyValue, string entityTypeName) + { + if (keyPropertyValue == null) + { + throw new ODataException(Strings.ODataConventionalUriBuilder_NullKeyValue(keyPropertyName, entityTypeName)); + } + + return keyPropertyValue; + } + + /// + /// Appends the key expression for the given entity to the given + /// + /// The builder to append onto. + /// The list of name value pair for key properties. + /// The full name of the entity type we are building the key expression for. + private void AppendKeyExpression(StringBuilder builder, ICollection> keyProperties, string entityTypeName) + { + Debug.Assert(builder != null, "builder != null"); + Debug.Assert(keyProperties != null, "keyProperties != null"); + + if (!keyProperties.Any()) + { + throw new ODataException(Strings.ODataConventionalUriBuilder_EntityTypeWithNoKeyProperties(entityTypeName)); + } + + this.keySerializer.AppendKeyExpression(builder, keyProperties, p => p.Key, p => ValidateKeyValue(p.Key, p.Value, entityTypeName)); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMetadataContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMetadataContext.cs new file mode 100644 index 0000000..ee218d9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMetadataContext.cs @@ -0,0 +1,336 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Evaluation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.JsonLight; + using Microsoft.OData.Metadata; + + /// + /// Interface used for substitutability of the metadata-centric responsibilities of . + /// + internal interface IODataMetadataContext + { + /// + /// Gets the Edm Model. + /// + IEdmModel Model { get; } + + /// + /// Gets the service base Uri. + /// + Uri ServiceBaseUri { get; } + + /// + /// Gets the metadata document uri. + /// + Uri MetadataDocumentUri { get; } + + /// + /// Gets the OData uri. + /// + ODataUri ODataUri { get; } + + /// + /// Gets an entity metadata builder for the given resource. + /// + /// Resource state to use as reference for information needed by the builder. + /// true if keys should go in separate segments in auto-generated URIs, false if they should go in parentheses. + /// true if the payload being read is a delta payload. + /// An entity metadata builder. + ODataResourceMetadataBuilder GetResourceMetadataBuilderForReader(IODataJsonLightReaderResourceState resourceState, bool useKeyAsSegment, bool isDelta); + + /// + /// Gets the list of operations that are bindable to a type. + /// + /// The binding type in question. + /// The list of operations that are always bindable to a type. + IEnumerable GetBindableOperationsForType(IEdmType bindingType); + + /// + /// Determines whether operations bound to this type must be qualified with the operation they belong to when appearing in a $select clause. + /// + /// The structured type the operations are bound to. + /// True if the operations must be container qualified, otherwise false. + bool OperationsBoundToStructuredTypeMustBeContainerQualified(IEdmStructuredType structuredType); + } + + /// + /// Default implementation of . + /// + internal sealed class ODataMetadataContext : IODataMetadataContext + { + /// + /// The Edm Model. + /// + private readonly IEdmModel model; + + /// + /// EdmTypeResolver instance to resolve entity set base type. + /// + private readonly EdmTypeResolver edmTypeResolver; + + /// + /// Cache of operations that are bindable to entity types. + /// + private readonly Dictionary> bindableOperationsCache; + + /// + /// true if we are reading or writing a response payload, false otherwise. + /// + private readonly bool isResponse; + + /// + /// Callback to determine whether operations bound to this type must be qualified with the operation they belong to when appearing in a $select clause. + /// + private readonly Func operationsBoundToStructuredTypeMustBeContainerQualified; + + /// + /// The metadata document Uri. + /// + private readonly Uri metadataDocumentUri; + + /// + /// The OData Uri. + /// + private readonly ODataUri odataUri; + + /// + /// The service base Uri. + /// + private Uri serviceBaseUri; + + /// + /// The MetadataLevel. + /// + private JsonLightMetadataLevel metadataLevel; + + /// + /// Constructs an ODataMetadataContext. + /// + /// true if we are writing a response payload, false otherwise. + /// The Edm model. + /// The metadata document uri. + /// The request Uri. + /// This overload should only be used by the writer. + public ODataMetadataContext(bool isResponse, IEdmModel model, Uri metadataDocumentUri, ODataUri odataUri) + : this(isResponse, /*OperationsBoundToEntityTypeMustBeContainerQualified*/ null, EdmTypeWriterResolver.Instance, model, metadataDocumentUri, odataUri) + { + } + + /// + /// Constructs an ODataMetadataContext. + /// + /// true if we are reading a response payload, false otherwise. + /// Callback to determine whether operations bound to this type must be qualified with the operation they belong to when appearing in a $select clause. + /// EdmTypeResolver instance to resolve entity set base type. + /// The Edm model. + /// The metadata document Uri. + /// The request Uri. + /// This overload should only be used by the reader. + public ODataMetadataContext( + bool isResponse, + Func operationsBoundToStructuredTypeMustBeContainerQualified, + EdmTypeResolver edmTypeResolver, + IEdmModel model, + Uri metadataDocumentUri, + ODataUri odataUri) + { + Debug.Assert(edmTypeResolver != null, "edmTypeResolver != null"); + Debug.Assert(model != null, "model != null"); + + this.isResponse = isResponse; + this.operationsBoundToStructuredTypeMustBeContainerQualified = operationsBoundToStructuredTypeMustBeContainerQualified ?? EdmLibraryExtensions.OperationsBoundToStructuredTypeMustBeContainerQualified; + this.edmTypeResolver = edmTypeResolver; + this.model = model; + this.metadataDocumentUri = metadataDocumentUri; + this.bindableOperationsCache = new Dictionary>(ReferenceEqualityComparer.Instance); + this.odataUri = odataUri; + } + + /// + /// Constructs an ODataMetadataContext. + /// + /// true if we are reading a response payload, false otherwise. + /// Callback to determine whether operations bound to this type must be qualified with the operation they belong to when appearing in a $select clause. + /// EdmTypeResolver instance to resolve entity set base type. + /// The Edm model. + /// The metadata document Uri. + /// The request Uri. + /// Current Json Light MetadataLevel + /// This overload should only be used by the reader. + public ODataMetadataContext( + bool isResponse, + Func operationsBoundToEntityTypeMustBeContainerQualified, + EdmTypeResolver edmTypeResolver, + IEdmModel model, + Uri metadataDocumentUri, + ODataUri odataUri, + JsonLightMetadataLevel metadataLevel) + : this(isResponse, operationsBoundToEntityTypeMustBeContainerQualified, edmTypeResolver, model, metadataDocumentUri, odataUri) + { + Debug.Assert(metadataLevel != null, "MetadataLevel != null"); + + this.metadataLevel = metadataLevel; + } + + /// + /// Gets the Edm Model. + /// + public IEdmModel Model + { + get + { + Debug.Assert(this.model != null, "this.model != null"); + return this.model; + } + } + + /// + /// Gets the service base Uri. + /// + public Uri ServiceBaseUri + { + get { return this.serviceBaseUri ?? (this.serviceBaseUri = new Uri(this.MetadataDocumentUri, "./")); } + } + + /// + /// Gets the metadata document uri. + /// + public Uri MetadataDocumentUri + { + get + { + if (this.metadataDocumentUri == null) + { + throw new ODataException(Strings.ODataJsonLightResourceMetadataContext_MetadataAnnotationMustBeInPayload(ODataAnnotationNames.ODataContext)); + } + + Debug.Assert(this.metadataDocumentUri.IsAbsoluteUri, "this.metadataDocumentUri.IsAbsoluteUri"); + return this.metadataDocumentUri; + } + } + + /// + /// Gets the OData uri. + /// + public ODataUri ODataUri + { + get + { + return this.odataUri; + } + } + + /// + /// Gets a resource metadata builder for the given resource. + /// + /// Resource state to use as reference for information needed by the builder. + /// true if keys should go in separate segments in auto-generated URIs, false if they should go in parentheses. + /// true if the payload being read is a delta payload + /// A resource metadata builder. + public ODataResourceMetadataBuilder GetResourceMetadataBuilderForReader(IODataJsonLightReaderResourceState resourceState, bool useKeyAsSegment, bool isDelta = false) + { + Debug.Assert(resourceState != null, "resource != null"); + + // Only apply the conventional template builder on response. On a request we would only report what's on the wire. + if (resourceState.MetadataBuilder == null) + { + ODataResourceBase resource = resourceState.Resource; + if (this.isResponse && !isDelta) + { + ODataTypeAnnotation typeAnnotation = resource.TypeAnnotation; + + IEdmStructuredType structuredType = null; + if (typeAnnotation != null) + { + if (typeAnnotation.Type != null) + { + // First try ODataTypeAnnotation.Type (for perf improvement) + structuredType = typeAnnotation.Type as IEdmStructuredType; + } + else if (typeAnnotation.TypeName != null) + { + // Then try ODataTypeAnnotation.TypeName + structuredType = this.model.FindType(typeAnnotation.TypeName) as IEdmStructuredType; + } + } + + if (structuredType == null) + { + // No type name read from the payload. Use resource type from model. + structuredType = resourceState.ResourceType; + } + + IEdmNavigationSource navigationSource = resourceState.NavigationSource; + IEdmEntityType navigationSourceElementType = this.edmTypeResolver.GetElementType(navigationSource); + IODataResourceTypeContext typeContext = + ODataResourceTypeContext.Create( /*serializationInfo*/ + null, navigationSource, navigationSourceElementType, resourceState.ResourceTypeFromMetadata ?? resourceState.ResourceType, + /*throwIfMissingTypeInfo*/ true); + IODataResourceMetadataContext resourceMetadataContext = ODataResourceMetadataContext.Create(resource, typeContext, /*serializationInfo*/null, structuredType, this, resourceState.SelectedProperties); + + ODataConventionalUriBuilder uriBuilder = new ODataConventionalUriBuilder(this.ServiceBaseUri, + useKeyAsSegment ? ODataUrlKeyDelimiter.Slash : ODataUrlKeyDelimiter.Parentheses); + + if (structuredType.IsODataEntityTypeKind()) + { + resourceState.MetadataBuilder = new ODataConventionalEntityMetadataBuilder(resourceMetadataContext, this, uriBuilder); + } + else + { + resourceState.MetadataBuilder = new ODataConventionalResourceMetadataBuilder(resourceMetadataContext, this, uriBuilder); + } + } + else + { + resourceState.MetadataBuilder = new NoOpResourceMetadataBuilder(resource); + } + } + + return resourceState.MetadataBuilder; + } + + /// + /// Gets the list of operations that are always bindable to a type. + /// + /// The binding type in question. + /// The list of operations that are always bindable to a type. + public IEnumerable GetBindableOperationsForType(IEdmType bindingType) + { + Debug.Assert(bindingType != null, "bindingType != null"); + Debug.Assert(this.bindableOperationsCache != null, "this.bindableOperationsCache != null"); + Debug.Assert(this.isResponse, "this.readingResponse"); + + IList bindableOperations; + if (!this.bindableOperationsCache.TryGetValue(bindingType, out bindableOperations)) + { + bindableOperations = MetadataUtils.CalculateBindableOperationsForType(bindingType, this.model, this.edmTypeResolver); + this.bindableOperationsCache.Add(bindingType, bindableOperations); + } + + return bindableOperations; + } + + /// + /// Determines whether operations bound to this type must be qualified with the operation they belong to when appearing in a $select clause. + /// + /// The structured type the operations are bound to. + /// True if the operations must be container qualified, otherwise false. + public bool OperationsBoundToStructuredTypeMustBeContainerQualified(IEdmStructuredType structuredType) + { + Debug.Assert(structuredType != null, "entityType != null"); + Debug.Assert(this.isResponse, "this.isResponse"); + Debug.Assert(this.operationsBoundToStructuredTypeMustBeContainerQualified != null, "this.operationsBoundToStructuredTypeMustBeContainerQualified != null"); + + return this.operationsBoundToStructuredTypeMustBeContainerQualified(structuredType); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMissingOperationGenerator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMissingOperationGenerator.cs new file mode 100644 index 0000000..933743f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataMissingOperationGenerator.cs @@ -0,0 +1,157 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Evaluation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData.JsonLight; + + /// + /// Generates operations which were omitted by the service because they fully match conventions/templates and are always available. + /// + internal sealed class ODataMissingOperationGenerator + { + /// The current resource metadata context. + private readonly IODataMetadataContext metadataContext; + + /// The metadata context of the resource to generate the missing operations for. + private readonly IODataResourceMetadataContext resourceMetadataContext; + + /// The list of computed actions. + private List computedActions; + + /// The list of computed functions. + private List computedFunctions; + + /// + /// Initializes a new instance of the class. + /// + /// The metadata context of the resource to generate the missing operations for. + /// The current resource metadata context. + internal ODataMissingOperationGenerator(IODataResourceMetadataContext resourceMetadataContext, IODataMetadataContext metadataContext) + { + Debug.Assert(resourceMetadataContext != null, "resourceMetadataCotext != null"); + Debug.Assert(metadataContext != null, "metadataContext != null"); + + this.resourceMetadataContext = resourceMetadataContext; + this.metadataContext = metadataContext; + } + + /// + /// Gets the computed missing Actions from the generator. + /// + /// The computed missing Actions. + internal IEnumerable GetComputedActions() + { + this.ComputeMissingOperationsToResource(); + return this.computedActions; + } + + /// + /// Gets the computed missing Functions from the generator. + /// + /// The computed missing Functions. + internal IEnumerable GetComputedFunctions() + { + this.ComputeMissingOperationsToResource(); + return this.computedFunctions; + } + + /// + /// Returns a hash set of operation imports (actions and functions) in the given resource. + /// + /// The resource in question. + /// The edm model to resolve operation imports. + /// The metadata document uri. + /// The hash set of operation imports (actions and functions) in the given resource. + private static HashSet GetOperationsInEntry(ODataResourceBase resource, IEdmModel model, Uri metadataDocumentUri) + { + Debug.Assert(resource != null, "resource != null"); + Debug.Assert(model != null, "model != null"); + Debug.Assert(metadataDocumentUri != null && metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri != null && metadataDocumentUri.IsAbsoluteUri"); + + HashSet edmOperationImportsInEntry = new HashSet(EqualityComparer.Default); + IEnumerable operations = ODataUtilsInternal.ConcatEnumerables((IEnumerable)resource.NonComputedActions, (IEnumerable)resource.NonComputedFunctions); + if (operations != null) + { + foreach (ODataOperation operation in operations) + { + Debug.Assert(operation.Metadata != null, "operation.Metadata != null"); + string operationMetadataString = UriUtils.UriToString(operation.Metadata); + Debug.Assert( + ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString), + "ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString)"); + Debug.Assert( + operationMetadataString[0] == ODataConstants.ContextUriFragmentIndicator || metadataDocumentUri.IsBaseOf(operation.Metadata), + "operationMetadataString[0] == JsonLightConstants.ContextUriFragmentIndicator || metadataDocumentUri.IsBaseOf(operation.Metadata)"); + + string fullyQualifiedOperationName = ODataJsonLightUtils.GetUriFragmentFromMetadataReferencePropertyName(metadataDocumentUri, operationMetadataString); + IEnumerable edmOperations = model.ResolveOperations(fullyQualifiedOperationName); + if (edmOperations != null) + { + foreach (IEdmOperation edmOperation in edmOperations) + { + edmOperationImportsInEntry.Add(edmOperation); + } + } + } + } + + return edmOperationImportsInEntry; + } + + /// + /// Computes the operations that are missing from the payload but should be added by conventions onto the resource. + /// + private void ComputeMissingOperationsToResource() + { + Debug.Assert(this.resourceMetadataContext != null, "this.resourceMetadataContext != null"); + Debug.Assert(this.metadataContext != null, "this.metadataContext != null"); + + if (this.computedActions == null) + { + Debug.Assert(this.computedFunctions == null, "this.computedFunctions == null"); + + this.computedActions = new List(); + this.computedFunctions = new List(); + HashSet edmOperations = GetOperationsInEntry(this.resourceMetadataContext.Resource, this.metadataContext.Model, this.metadataContext.MetadataDocumentUri); + foreach (IEdmOperation bindableOperation in this.resourceMetadataContext.SelectedBindableOperations) + { + // if this operation appears in the payload, skip it. + if (edmOperations.Contains(bindableOperation)) + { + continue; + } + + string metadataReferencePropertyName = ODataConstants.ContextUriFragmentIndicator + ODataJsonLightUtils.GetMetadataReferenceName(this.metadataContext.Model, bindableOperation); + + bool isAction; + ODataOperation operation = ODataJsonLightUtils.CreateODataOperation(this.metadataContext.MetadataDocumentUri, metadataReferencePropertyName, bindableOperation, out isAction); + if (bindableOperation.Parameters.Any() && this.resourceMetadataContext.ActualResourceTypeName != bindableOperation.Parameters.First().Type.FullName()) + { + operation.BindingParameterTypeName = bindableOperation.Parameters.First().Type.FullName(); + } + + operation.SetMetadataBuilder(this.resourceMetadataContext.Resource.MetadataBuilder, this.metadataContext.MetadataDocumentUri); + if (isAction) + { + this.computedActions.Add((ODataAction)operation); + } + else + { + this.computedFunctions.Add((ODataFunction)operation); + } + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataBuilder.cs new file mode 100644 index 0000000..ea6cf60 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataBuilder.cs @@ -0,0 +1,407 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Evaluation +#endif +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.JsonLight; + #endregion + + /// + /// Extensibility point for customizing how OData entity metadata (edit-links, IDs, ETags, etc) is built. + /// + internal abstract class ODataResourceMetadataBuilder + { +#if !ODATA_CLIENT + /// + /// Gets an instance of the metadata builder which never returns anything other than nulls. + /// + internal static ODataResourceMetadataBuilder Null + { + get + { + return NullResourceMetadataBuilder.Instance; + } + } +#endif + + /// + /// Gets instance of the metadata builder which belongs to the parent odata resource + /// + /// + /// The metadata builder of the parent odata resource + /// Or null if there is no parent odata resource + /// + internal ODataResourceMetadataBuilder ParentMetadataBuilder { get; set; } + + /// + /// Whether the resource is from collection. + /// + internal bool IsFromCollection { get; set; } + + /// + /// The name of property that current resource represents. + /// + internal string NameAsProperty { get; set; } + + /// + /// Gets the edit link of the entity. + /// + /// + /// The absolute URI of the edit link for the entity. + /// Or null if it is not possible to determine the edit link. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal abstract Uri GetEditLink(); + + /// + /// Gets the read link of the entity. + /// + /// + /// The absolute URI of the read link for the entity. + /// Or null if it is not possible to determine the read link. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal abstract Uri GetReadLink(); + + /// + /// Gets the ID of the entity. + /// + /// + /// The ID for the entity. + /// Or null if it is not possible to determine the ID. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal abstract Uri GetId(); + + /// + /// Get the id that need to be written into wire + /// + /// The id return to the caller + /// + /// If writer should write odata.id property into wire + /// + internal abstract bool TryGetIdForSerialization(out Uri id); + + /// + /// Gets the ETag of the entity. + /// + /// + /// The ETag for the entity. + /// Or null if it is not possible to determine the ETag. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal abstract string GetETag(); + +#if !ODATA_CLIENT + /// + /// Gets the default media resource of the entity. + /// + /// + /// The the default media resource of the entity. + /// Or null if the entity is not an MLE. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal virtual ODataStreamReferenceValue GetMediaResource() + { + return null; + } + + /// + /// Gets the entity properties. + /// + /// Non-computed properties from the entity. + /// The the computed and non-computed entity properties. + internal virtual IEnumerable GetProperties(IEnumerable nonComputedProperties) + { + return nonComputedProperties == null ? null : nonComputedProperties.Where(p => + { + if (p.ODataValue is ODataStreamReferenceValue) + { + return false; + } + + if (p.ODataValue is ODataResourceValue) + { + throw new ODataException(Strings.ODataResource_PropertyValueCannotBeODataResourceValue(p.Name)); + } + + ODataCollectionValue collectionValue = p.ODataValue as ODataCollectionValue; + if (collectionValue != null && collectionValue.Items != null && collectionValue.Items.Any(t => t is ODataResourceValue)) + { + throw new ODataException(Strings.ODataResource_PropertyValueCannotBeODataResourceValue(p.Name)); + } + + return true; + }); + } + + /// + /// Gets the list of computed and non-computed actions for the entity. + /// + /// The list of computed and non-computed actions for the entity. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal virtual IEnumerable GetActions() + { + return null; + } + + /// + /// Gets the list of computed and non-computed functions for the entity. + /// + /// The list of computed and non-computed functions for the entity. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal virtual IEnumerable GetFunctions() + { + return null; + } + + /// + /// Marks the given nested resource info as processed. + /// + /// The nested resource info we've already processed. + internal virtual void MarkNestedResourceInfoProcessed(string navigationPropertyName) + { + } + + /// + /// Returns the next unprocessed stream property or null if there's no more stream properties to process. + /// + /// Returns the next unprocessed stream property or null if there's no more stream properties to process. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal virtual ODataProperty GetNextUnprocessedStreamProperty() + { + return null; + } + + /// + /// Marks the given stream property as processed. + /// + /// The stream property we've already processed. + internal virtual void MarkStreamPropertyProcessed(string streamPropertyName) + { + } + + /// + /// Returns the next unprocessed nested resource info or null if there's no more navigation links to process. + /// + /// Returns the next unprocessed nested resource info or null if there's no more navigation links to process. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + internal virtual ODataJsonLightReaderNestedResourceInfo GetNextUnprocessedNavigationLink() + { + return null; + } +#endif + + /// + /// Gets the edit link of a stream value. + /// + /// The name of the stream property the edit link is computed for; + /// or null for the default media resource. + /// + /// The absolute URI of the edit link for the specified stream property or the default media resource. + /// Or null if it is not possible to determine the stream edit link. + /// + internal virtual Uri GetStreamEditLink(string streamPropertyName) + { +#if ODATA_CLIENT + Util.CheckArgumentNotEmpty(streamPropertyName, "streamPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); +#endif + return null; + } + + /// + /// Gets the read link of a stream value. + /// + /// The name of the stream property the read link is computed for; + /// or null for the default media resource. + /// + /// The absolute URI of the read link for the specified stream property or the default media resource. + /// Or null if it is not possible to determine the stream read link. + /// + internal virtual Uri GetStreamReadLink(string streamPropertyName) + { +#if ODATA_CLIENT + Util.CheckArgumentNotEmpty(streamPropertyName, "streamPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); +#endif + + return null; + } + + /// + /// Gets the navigation link URI for the specified navigation property. + /// + /// The name of the navigation property to get the navigation link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null navigation link url is an uninitialized value or a value that was set explicitly. + /// + /// The navigation link URI for the navigation property. + /// null if its not possible to determine the navigation link for the specified navigation property. + /// + internal virtual Uri GetNavigationLinkUri(string navigationPropertyName, Uri navigationLinkUrl, bool hasNestedResourceInfoUrl) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(navigationPropertyName, "navigationPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); +#endif + return null; + } + + /// + /// Gets the association link URI for the specified navigation property. + /// + /// The name of the navigation property to get the association link URI for. + /// The value of the link URI as seen on the wire or provided explicitly by the user or previously returned by the metadata builder, which may be null. + /// true if the value of the was seen on the wire or provided explicitly by the user or previously returned by + /// the metadata builder, false otherwise. This flag allows the metadata builder to determine whether a null association link url is an uninitialized value or a value that was set explicitly. + /// + /// The association link URI for the navigation property. + /// null if its not possible to determine the association link for the specified navigation property. + /// + internal virtual Uri GetAssociationLinkUri(string navigationPropertyName, Uri associationLinkUrl, bool hasAssociationLinkUrl) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(navigationPropertyName, "navigationPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); +#endif + return null; + } + + /// + /// Get the operation target URI for the specified . + /// + /// The fully qualified name of the operation for which to get the target URI. + /// The binding parameter type name to include in the target, or null/empty if there is none. + /// The parameter names to include in the target, or null/empty if there is none. + /// + /// The target URI for the operation. + /// null if it is not possible to determine the target URI for the specified operation. + /// + internal virtual Uri GetOperationTargetUri(string operationName, string bindingParameterTypeName, string parameterNames) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(operationName, "operationName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); +#endif + return null; + } + + /// + /// Get the operation title for the specified . + /// + /// The fully qualified name of the operation for which to get the target URI. + /// + /// The title for the operation. + /// null if it is not possible to determine the title for the specified operation. + /// + internal virtual string GetOperationTitle(string operationName) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(operationName, "operationName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); +#endif + return null; + } + +#if !ODATA_CLIENT + /// + /// Implementation of the metadata builder which only returns nulls. + /// + private sealed class NullResourceMetadataBuilder : ODataResourceMetadataBuilder + { + /// + /// Singleton instance of the null metadata builder. + /// + internal static readonly NullResourceMetadataBuilder Instance = new NullResourceMetadataBuilder(); + + /// + /// Prevents a default instance of the class from being created. + /// + private NullResourceMetadataBuilder() + { + } + + /// + /// Gets the edit link of the entity. + /// + /// + /// The absolute URI of the edit link for the entity. + /// Or null if it is not possible to determine the edit link. + /// + internal override Uri GetEditLink() + { + return null; + } + + /// + /// Gets the read link of the entity. + /// + /// + /// The absolute URI of the read link for the entity. + /// Or null if it is not possible to determine the read link. + /// + internal override Uri GetReadLink() + { + return null; + } + + /// + /// Gets the ID of the entity. + /// + /// + /// The ID for the entity. + /// Or null if it is not possible to determine the ID. + /// + internal override Uri GetId() + { + return null; + } + + /// + /// Gets the ETag of the entity. + /// + /// + /// The ETag for the entity. + /// Or null if it is not possible to determine the ETag. + /// + internal override string GetETag() + { + return null; + } + + /// + /// Get the id that need to be written into wire + /// + /// The id return to the caller + /// + /// If writer should write odata.id property into wire + /// + internal override bool TryGetIdForSerialization(out Uri id) + { + id = null; + return false; + } + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs new file mode 100644 index 0000000..d04bef6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs @@ -0,0 +1,549 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Evaluation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + using Microsoft.OData.Edm.Vocabularies.V1; + + /// + /// Default implementation of + /// + internal abstract class ODataResourceMetadataContext : IODataResourceMetadataContext + { + /// + /// Empty array of properties. + /// + private static readonly KeyValuePair[] EmptyProperties = new KeyValuePair[0]; + + /// + /// The resource instance. + /// + private readonly ODataResourceBase resource; + + /// + /// The context object to answer basic questions regarding the type of the resource. + /// + private readonly IODataResourceTypeContext typeContext; + + /// + /// The key property name and value pairs of the resource. + /// + private KeyValuePair[] keyProperties; + + /// + /// The ETag property name and value pairs of the resource. + /// + private IEnumerable> etagProperties; + + /// + /// The selected navigation properties. + /// + private IEnumerable selectedNavigationProperties; + + /// + /// The selected stream properties. + /// + private IDictionary selectedStreamProperties; + + /// + /// The selected bindable operations. + /// + private IEnumerable selectedBindableOperations; + + /// + /// Constructs an instance of . + /// + /// The resource instance. + /// The context object to answer basic questions regarding the type of the resource. + protected ODataResourceMetadataContext(ODataResourceBase resource, IODataResourceTypeContext typeContext) + { + Debug.Assert(resource != null, "resource != null"); + Debug.Assert(typeContext != null, "typeContext != null"); + + this.resource = resource; + this.typeContext = typeContext; + } + + /// + /// The resource instance. + /// + public ODataResourceBase Resource + { + get { return this.resource; } + } + + /// + /// The context object to answer basic questions regarding the type of the resource. + /// + public IODataResourceTypeContext TypeContext + { + get { return this.typeContext; } + } + + /// + /// The actual structured type of the resource, i.e. ODataResource.TypeName. + /// + public abstract string ActualResourceTypeName { get; } + + /// + /// The key property name and value pairs of the resource. + /// + public abstract ICollection> KeyProperties { get; } + + /// + /// The ETag property name and value pairs of the resource. + /// + public abstract IEnumerable> ETagProperties { get; } + + /// + /// The selected navigation properties. + /// + public abstract IEnumerable SelectedNavigationProperties { get; } + + /// + /// The selected stream properties. + /// + public abstract IDictionary SelectedStreamProperties { get; } + + /// + /// The selected bindable operations. + /// + public abstract IEnumerable SelectedBindableOperations { get; } + + /// + /// Creates an instance of . + /// + /// The resource instance. + /// The context object to answer basic questions regarding the type of the resource. + /// The serialization info of the resource for writing without model. + /// The structured type of the resource. + /// The metadata context to use. + /// The selected properties. + /// A new instance of . + internal static ODataResourceMetadataContext Create( + ODataResourceBase resource, + IODataResourceTypeContext typeContext, + ODataResourceSerializationInfo serializationInfo, + IEdmStructuredType actualResourceType, + IODataMetadataContext metadataContext, + SelectedPropertiesNode selectedProperties) + { + if (serializationInfo != null) + { + return new ODataResourceMetadataContextWithoutModel(resource, typeContext, serializationInfo); + } + + return new ODataResourceMetadataContextWithModel(resource, typeContext, actualResourceType, metadataContext, selectedProperties); + } + + /// + /// Get key value pair array for specifc odata resource using specifc entity type + /// + /// The resource instance. + /// The serialization info of the resource for writing without model. + /// The edm entity type of the resource + /// Key value pair array + internal static KeyValuePair[] GetKeyProperties( + ODataResourceBase resource, + ODataResourceSerializationInfo serializationInfo, + IEdmEntityType actualEntityType) + { + KeyValuePair[] keyProperties = null; + string actualEntityTypeName = null; + + if (serializationInfo != null) + { + if (String.IsNullOrEmpty(resource.TypeName)) + { + throw new ODataException(Strings.ODataResourceTypeContext_ODataResourceTypeNameMissing); + } + + actualEntityTypeName = resource.TypeName; + keyProperties = ODataResourceMetadataContextWithoutModel.GetPropertiesBySerializationInfoPropertyKind(resource, ODataPropertyKind.Key, actualEntityTypeName); + } + else + { + actualEntityTypeName = actualEntityType.FullName(); + + IEnumerable edmKeyProperties = actualEntityType.Key(); + if (edmKeyProperties != null) + { + keyProperties = edmKeyProperties.Select(p => new KeyValuePair(p.Name, GetPrimitiveOrEnumPropertyValue(resource, p.Name, actualEntityTypeName, /*isKeyProperty*/false))).ToArray(); + } + } + + ValidateEntityTypeHasKeyProperties(keyProperties, actualEntityTypeName); + return keyProperties; + } + + /// + /// Gets the value for a primitive or enum property. + /// + /// The resource to get the property value. + /// Name of the property. + /// The name of the entity type to get the property value. + /// true if the property is a key property, false otherwise. + /// The value of the property. + private static object GetPrimitiveOrEnumPropertyValue(ODataResourceBase resource, string propertyName, string entityTypeName, bool isKeyProperty) + { + Debug.Assert(resource != null, "resource != null"); + + ODataProperty property = resource.NonComputedProperties == null ? null : resource.NonComputedProperties.SingleOrDefault(p => p.Name == propertyName); + if (property == null) + { + throw new ODataException(Strings.EdmValueUtils_PropertyDoesntExist(entityTypeName, propertyName)); + } + + return GetPrimitiveOrEnumPropertyValue(entityTypeName, property, isKeyProperty); + } + + /// + /// Gets the value for a primitive or enum property. For primitive type, returns its CLR value; for enum type, returns OData enum value. + /// + /// The name of the entity type to get the property value. + /// The ODataProperty to get the value from. + /// true if the property is a key property, false otherwise. + /// The value of the property. + private static object GetPrimitiveOrEnumPropertyValue(string entityTypeName, ODataProperty property, bool isKeyProperty) + { + object propertyValue = property.Value; + if (propertyValue == null && isKeyProperty) + { + throw new ODataException(Strings.ODataResourceMetadataContext_NullKeyValue(property.Name, entityTypeName)); + } + + if (propertyValue is ODataValue && !(propertyValue is ODataEnumValue)) + { + throw new ODataException(Strings.ODataResourceMetadataContext_KeyOrETagValuesMustBePrimitiveValues(property.Name, entityTypeName)); + } + + return propertyValue; + } + + /// + /// Validates that the resource has key properties. + /// + /// Key properties of the resource. + /// The entity type name of the resource. + private static void ValidateEntityTypeHasKeyProperties(KeyValuePair[] keyProperties, string actualEntityTypeName) + { + Debug.Assert(keyProperties != null, "keyProperties != null"); + if (keyProperties == null || keyProperties.Length == 0) + { + throw new ODataException(Strings.ODataResourceMetadataContext_EntityTypeWithNoKeyProperties(actualEntityTypeName)); + } + } + + /// + /// Gets the property name value pairs filtered by serialization property kind. + /// + /// The resource to get the properties from. + /// The serialization info property kind. + /// The entity type name of the resource. + /// The property name value pairs filtered by serialization property kind. + private static KeyValuePair[] GetPropertiesBySerializationInfoPropertyKind(ODataResourceBase resource, ODataPropertyKind propertyKind, string actualEntityTypeName) + { + Debug.Assert(resource != null, "resource != null"); + Debug.Assert(propertyKind == ODataPropertyKind.Key || propertyKind == ODataPropertyKind.ETag, "propertyKind == ODataPropertyKind.Key || propertyKind == ODataPropertyKind.ETag"); + + KeyValuePair[] properties = EmptyProperties; + if (resource.NonComputedProperties != null) + { + properties = resource.NonComputedProperties.Where(p => p.SerializationInfo != null && p.SerializationInfo.PropertyKind == propertyKind).Select(p => new KeyValuePair(p.Name, GetPrimitiveOrEnumPropertyValue(actualEntityTypeName, p, propertyKind == ODataPropertyKind.Key))).ToArray(); + } + + return properties; + } + + /// + /// Implementation of based on serialization info. + /// + private sealed class ODataResourceMetadataContextWithoutModel : ODataResourceMetadataContext + { + /// + /// Empty array of navigation properties. + /// + private static readonly IEdmNavigationProperty[] EmptyNavigationProperties = new IEdmNavigationProperty[0]; + + /// + /// Empty dictionary of stream properties. + /// + private static readonly Dictionary EmptyStreamProperties = new Dictionary(StringComparer.Ordinal); + + /// + /// Empty array of operations. + /// + private static readonly IEdmOperation[] EmptyOperations = new IEdmOperation[0]; + + /// + /// The serialization info of the resource for writing without model. + /// + private readonly ODataResourceSerializationInfo serializationInfo; + + /// + /// Constructs an instance of . + /// + /// The resource instance. + /// The context object to answer basic questions regarding the type of the resource. + /// The serialization info of the resource for writing without model. + internal ODataResourceMetadataContextWithoutModel(ODataResourceBase resource, IODataResourceTypeContext typeContext, ODataResourceSerializationInfo serializationInfo) + : base(resource, typeContext) + { + Debug.Assert(serializationInfo != null, "serializationInfo != null"); + this.serializationInfo = serializationInfo; + } + + /// + /// The key property name and value pairs of the resource. + /// + public override ICollection> KeyProperties + { + get + { + if (this.keyProperties == null) + { + this.keyProperties = GetPropertiesBySerializationInfoPropertyKind(this.resource, ODataPropertyKind.Key, this.ActualResourceTypeName); + ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName); + } + + return this.keyProperties; + } + } + + /// + /// The ETag property name and value pairs of the resource. + /// + public override IEnumerable> ETagProperties + { + get { return this.etagProperties ?? (this.etagProperties = GetPropertiesBySerializationInfoPropertyKind(this.resource, ODataPropertyKind.ETag, this.ActualResourceTypeName)); } + } + + /// + /// The actual structured type of the resource, i.e. ODataResource.TypeName. + /// + public override string ActualResourceTypeName + { + get + { + if (String.IsNullOrEmpty(this.Resource.TypeName)) + { + throw new ODataException(Strings.ODataResourceTypeContext_ODataResourceTypeNameMissing); + } + + return this.Resource.TypeName; + } + } + + /// + /// The selected navigation properties. + /// + public override IEnumerable SelectedNavigationProperties + { + get { return EmptyNavigationProperties; } + } + + /// + /// The selected stream properties. + /// + public override IDictionary SelectedStreamProperties + { + get { return EmptyStreamProperties; } + } + + /// + /// The selected bindable operations. + /// + public override IEnumerable SelectedBindableOperations + { + get { return EmptyOperations; } + } + } + + /// + /// Implementation of based on the given model. + /// + private sealed class ODataResourceMetadataContextWithModel : ODataResourceMetadataContext + { + /// + /// The structured type of the resource. + /// + private readonly IEdmStructuredType actualResourceType; + + /// + /// The metadata context to use. + /// + private readonly IODataMetadataContext metadataContext; + + /// + /// The selected properties. + /// + private readonly SelectedPropertiesNode selectedProperties; + + /// + /// Constructs an instance of . + /// + /// The resource instance. + /// The context object to answer basic questions regarding the type of the resource. + /// The structured type of the resource. + /// The metadata context to use. + /// The selected properties. + internal ODataResourceMetadataContextWithModel(ODataResourceBase resource, IODataResourceTypeContext typeContext, IEdmStructuredType actualResourceType, IODataMetadataContext metadataContext, SelectedPropertiesNode selectedProperties) + : base(resource, typeContext) + { + Debug.Assert(actualResourceType != null, "actualResourceType != null"); + Debug.Assert(metadataContext != null, "metadataContext != null"); + Debug.Assert(selectedProperties != null, "selectedProperties != null"); + + this.actualResourceType = actualResourceType; + this.metadataContext = metadataContext; + this.selectedProperties = selectedProperties; + } + + /// + /// The key property name and value pairs of the resource. + /// + public override ICollection> KeyProperties + { + get + { + if (this.keyProperties == null) + { + var entityType = this.actualResourceType as IEdmEntityType; + if (entityType != null) + { + IEnumerable edmKeyProperties = entityType.Key(); + if (edmKeyProperties != null) + { + this.keyProperties = edmKeyProperties.Select(p => new KeyValuePair(p.Name, GetPrimitiveOrEnumPropertyValue(this.resource, p.Name, this.ActualResourceTypeName, /*isKeyProperty*/true))).ToArray(); + } + + ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName); + } + else + { + this.keyProperties = Enumerable.Empty>().ToArray(); + } + } + + return this.keyProperties; + } + } + + /// + /// The ETag property name and value pairs of the resource. + /// + public override IEnumerable> ETagProperties + { + get + { + if (this.etagProperties == null) + { + IEnumerable properties = this.ComputeETagPropertiesFromAnnotation(); + this.etagProperties = properties.Any() + ? properties.Select(p => new KeyValuePair(p.Name, GetPrimitiveOrEnumPropertyValue(this.resource, p.Name, this.ActualResourceTypeName, /*isKeyProperty*/false))).ToArray() + : EmptyProperties; + } + + return this.etagProperties; + } + } + + /// + /// The actual structured type name of the resource. + /// + public override string ActualResourceTypeName + { + // Note that resource.TypeName can be null. When that happens, we use the expected structured type as the actual structured type. + get { return this.actualResourceType.FullTypeName(); } + } + + /// + /// The selected navigation properties. + /// + public override IEnumerable SelectedNavigationProperties + { + get + { + return this.selectedNavigationProperties + ?? (this.selectedNavigationProperties = this.selectedProperties.GetSelectedNavigationProperties(this.actualResourceType)); + } + } + + /// + /// The selected stream properties. + /// + public override IDictionary SelectedStreamProperties + { + get + { + return this.selectedStreamProperties + ?? (this.selectedStreamProperties = this.selectedProperties.GetSelectedStreamProperties(this.actualResourceType as IEdmEntityType)); + } + } + + /// + /// The selected bindable operations. + /// + public override IEnumerable SelectedBindableOperations + { + get + { + if (this.selectedBindableOperations == null) + { + bool mustBeContainerQualified = this.metadataContext.OperationsBoundToStructuredTypeMustBeContainerQualified(this.actualResourceType); + this.selectedBindableOperations = this.metadataContext.GetBindableOperationsForType(this.actualResourceType) + .Where(operation => this.selectedProperties.IsOperationSelected(this.actualResourceType, operation, mustBeContainerQualified)); + } + + return this.selectedBindableOperations; + } + } + + /// + /// Compute ETag from Annotation Org.OData.Core.V1.OptimisticConcurrency on EntitySet + /// + /// Enumerable of IEdmStructuralProperty + private IEnumerable ComputeETagPropertiesFromAnnotation() + { + IEdmModel model = this.metadataContext.Model; + IEdmEntitySet entitySet = model.FindDeclaredEntitySet(this.typeContext.NavigationSourceName); + + if (entitySet != null) + { + IEdmVocabularyAnnotation annotation = model.FindDeclaredVocabularyAnnotations(entitySet) + .SingleOrDefault(t => t.Term.FullName().Equals(CoreVocabularyConstants.OptimisticConcurrency, StringComparison.Ordinal)); + if (annotation != null) + { + IEdmExpression collectionExpression = annotation.Value; + if (collectionExpression is IEdmCollectionExpression) + { + IEnumerable pathExpressions = (collectionExpression as IEdmCollectionExpression).Elements.Where(p => p is IEdmPathExpression); + foreach (IEdmPathExpression pathExpression in pathExpressions) + { + // TODO: + // 1. Add support for Complex type + // 2. Add new exception when collectionExpression is not IEdmCollectionExpression: CoreOptimisticConcurrency must be followed by collection expression + IEdmStructuralProperty property = this.actualResourceType.StructuralProperties().FirstOrDefault(p => p.Name == pathExpression.PathSegments.LastOrDefault()); + if (property == null) + { + throw new ODataException(Strings.EdmValueUtils_PropertyDoesntExist(this.ActualResourceTypeName, pathExpression.PathSegments.LastOrDefault())); + } + + yield return property; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataUriBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataUriBuilder.cs new file mode 100644 index 0000000..e8f5b7e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Evaluation/ODataUriBuilder.cs @@ -0,0 +1,189 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +#if ODATA_CLIENT +using Microsoft.OData.Edm.Vocabularies; +#endif + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +namespace Microsoft.OData.Evaluation +#endif +{ + /// + /// Extensibility point for customizing how OData uri's are built. + /// + internal abstract class ODataUriBuilder + { + /// + /// Builds the base URI for the entity container. + /// + /// + /// The base URI for the entity container. + /// This can be either an absolute URI, + /// or relative URI which will be combined with the URI of the metadata document for the service. + /// null if the model doesn't have the service base URI annotation. + /// + internal virtual Uri BuildBaseUri() + { + return null; + } + + /// + /// Builds the URI for an entity set. + /// + /// The URI to append to. + /// The entity set name. + /// The entity set URI. + internal virtual Uri BuildEntitySetUri(Uri baseUri, string entitySetName) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(entitySetName, "entitySetName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(entitySetName, "entitySetName"); +#endif + return null; + } + +#if ODATA_CLIENT + /// + /// Appends to create the entity instance URI for the specified . + /// + /// The URI to append to + /// The entity instance to use. + /// + /// The entity instance URI. + /// + internal virtual Uri BuildEntityInstanceUri(Uri baseUri, IEdmStructuredValue entityInstance) +#else + /// + /// Builds the entity instance URI with the given key property values. + /// + /// The URI to append to. + /// The list of name value pair for key properties. + /// The full name of the entity type we are building the key expression for. + /// The entity instance URI. + internal virtual Uri BuildEntityInstanceUri(Uri baseUri, ICollection> keyProperties, string entityTypeName) +#endif + { +#if ODATA_CLIENT + Util.CheckArgumentNull(entityInstance, "entityInstance"); +#else + ExceptionUtils.CheckArgumentNotNull(keyProperties, "keyProperties"); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(entityTypeName, "entityTypeName"); +#endif + return null; + } + + /// + /// Builds the edit link for a stream property. + /// + /// The URI to append to. + /// + /// The name of the stream property the link is computed for; + /// or null for the default media resource. + /// + /// The edit link for the stream. + internal virtual Uri BuildStreamEditLinkUri(Uri baseUri, string streamPropertyName) + { +#if ODATA_CLIENT + Util.CheckArgumentNotEmpty(streamPropertyName, "streamPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); +#endif + return null; + } + + /// + /// Builds the read link for a stream property. + /// + /// The URI to append to. + /// + /// The name of the stream property the link is computed for; + /// or null for the default media resource. + /// + /// The read link for the stream. + internal virtual Uri BuildStreamReadLinkUri(Uri baseUri, string streamPropertyName) + { +#if ODATA_CLIENT + Util.CheckArgumentNotEmpty(streamPropertyName, "streamPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotEmpty(streamPropertyName, "streamPropertyName"); +#endif + return null; + } + + /// + /// Builds the navigation link for the navigation property. + /// + /// The URI to append to. + /// The name of the navigation property to get the navigation link URI for. + /// The navigation link URI for the navigation property. + internal virtual Uri BuildNavigationLinkUri(Uri baseUri, string navigationPropertyName) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(navigationPropertyName, "navigationPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); +#endif + return null; + } + + /// + /// Builds the association link for the navigation property. + /// + /// The URI to append to. + /// The name of the navigation property to get the association link URI for. + /// The association link URI for the navigation property. + internal virtual Uri BuildAssociationLinkUri(Uri baseUri, string navigationPropertyName) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(navigationPropertyName, "navigationPropertyName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(navigationPropertyName, "navigationPropertyName"); +#endif + return null; + } + + /// + /// Builds the operation target URI for the specified . + /// + /// The URI to append to. + /// The fully qualified name of the operation for which to get the target URI. + /// The binding parameter type name to include in the target, or null/empty if there is none. + /// The parameter names to include in the target, or null/empty if there is none. + /// The target URI for the operation. + internal virtual Uri BuildOperationTargetUri(Uri baseUri, string operationName, string bindingParameterTypeName, string parameterNames) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(operationName, "operationName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(operationName, "operationName"); +#endif + return null; + } + + /// + /// Builds a URI with the given type name appended as a new segment on the base URI. + /// + /// The URI to append to. + /// The fully qualified type name to append. + /// The URI with the type segment appended. + internal virtual Uri AppendTypeSegment(Uri baseUri, string typeName) + { +#if ODATA_CLIENT + Util.CheckArgumentNullAndEmpty(typeName, "typeName"); +#else + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(typeName, "typeName"); +#endif + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ExceptionUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ExceptionUtils.cs new file mode 100644 index 0000000..cd7500b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ExceptionUtils.cs @@ -0,0 +1,201 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData +#endif +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Class with utility methods to work with exceptions + /// + internal static class ExceptionUtils + { + /// Type of OutOfMemoryException. + private static readonly Type OutOfMemoryType = typeof(System.OutOfMemoryException); + +#if !PORTABLELIB + /// Type of StackOverflowException. + private static readonly Type StackOverflowType = typeof(System.StackOverflowException); + + /// Type of ThreadAbortException. + private static readonly Type ThreadAbortType = typeof(System.Threading.ThreadAbortException); +#endif + + /// + /// Determines whether the specified exception can be caught and + /// handled, or whether it should be allowed to continue unwinding. + /// + /// to test. + /// + /// true if the specified exception can be caught and handled; + /// false otherwise. + /// + internal static bool IsCatchableExceptionType(Exception e) + { + Debug.Assert(e != null, "Unexpected null exception!"); + + Type type = e.GetType(); + + // a 'catchable' exception is defined by what it is not. + return +#if !PORTABLELIB + type != ThreadAbortType && + type != StackOverflowType && +#endif + type != OutOfMemoryType; + } + + /// + /// Checks the argument value for null and throws if it is null. + /// + /// Type of the argument, used to force usage only for reference types. + /// Argument whose value needs to be checked. + /// Name of the argument, used for exception message. + /// The value + internal static T CheckArgumentNotNull([ValidatedNotNull] T value, string parameterName) where T : class + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + if (value == null) + { +#if !ODATA_CLIENT + throw Error.ArgumentNull(parameterName); +#endif + } + + return value; + } + + /// + /// Checks the argument string value empty string and throws if it is empty. The value can be null though. + /// + /// Argument whose value needs to be checked. + /// Name of the argument, used for exception message. + internal static void CheckArgumentStringNotEmpty(string value, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + if (value != null && value.Length == 0) + { +#if !ODATA_CLIENT + throw new ArgumentException(Strings.ExceptionUtils_ArgumentStringEmpty, parameterName); +#endif + } + } + + /// + /// Checks the argument string value for null or empty string and throws if it is null or empty. + /// + /// Argument whose value needs to be checked. + /// Name of the argument, used for exception message. + internal static void CheckArgumentStringNotNullOrEmpty([ValidatedNotNull] string value, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + if (string.IsNullOrEmpty(value)) + { +#if !ODATA_CLIENT + throw new ArgumentNullException(parameterName, Strings.ExceptionUtils_ArgumentStringNullOrEmpty); +#endif + } + } + + /// + /// Checks the for not being negative and throws if it is negative. + /// + /// Argument whose value needs to be checked. + /// Name of the argument, used for exception message. + internal static void CheckIntegerNotNegative(int value, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + if (value < 0) + { +#if !ODATA_CLIENT + throw new ArgumentOutOfRangeException(parameterName, Strings.ExceptionUtils_CheckIntegerNotNegative(value)); +#endif + } + } + + /// + /// Checks the for being greater than zero and throws if it is not positive. + /// + /// Argument whose value needs to be checked. + /// Name of the argument, used for exception message. + internal static void CheckIntegerPositive(int value, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + if (value <= 0) + { +#if !ODATA_CLIENT + throw new ArgumentOutOfRangeException(parameterName, Strings.ExceptionUtils_CheckIntegerPositive(value)); +#endif + } + } + + /// + /// Checks the for being greater than zero and throws if it is not positive. + /// + /// Argument whose value needs to be checked. + /// Name of the argument, used for exception message. + internal static void CheckLongPositive(long value, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + if (value <= 0) + { +#if !ODATA_CLIENT + throw new ArgumentOutOfRangeException(parameterName, Strings.ExceptionUtils_CheckLongPositive(value)); +#endif + } + } + + /// + /// Checks the for not being empty. + /// + /// Argument whose value needs to be checked. + /// Name of the argument, used for exception message. + /// Type of the collection. It does not matter. + internal static void CheckArgumentCollectionNotNullOrEmpty(ICollection value, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + if (value == null) + { +#if !ODATA_CLIENT + throw Error.ArgumentNull(parameterName); +#endif + } + else if (value.Count == 0) + { +#if !ODATA_CLIENT + // TODO: STRINGS The string is fine; just rename it to just ArgumentEmpty + throw new ArgumentException(Strings.ExceptionUtils_ArgumentStringEmpty, parameterName); +#endif + } + } + + /// + /// A workaround to a problem with FxCop which does not recognize the CheckArgumentNotNull method + /// as the one which validates the argument is not null. + /// + /// This has been suggested as a workaround in msdn forums by the VS team. Note that even though this is production code + /// the attribute has no effect on anything else. + private sealed class ValidatedNotNullAttribute : Attribute + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/GeographyTypeConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/GeographyTypeConverter.cs new file mode 100644 index 0000000..9e0da0c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/GeographyTypeConverter.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; +using System.IO; +using System.Xml; +using Microsoft.OData.Json; +using Microsoft.OData.Metadata; +using Microsoft.Spatial; + +namespace Microsoft.OData +{ + /// + /// Handles serialization and deserialization for types derived from Geography. + /// + internal sealed class GeographyTypeConverter : IPrimitiveTypeConverter + { + /// + /// Write the Atom representation of an instance of a primitive type to an XmlWriter. + /// + /// The instance to write. + /// The Xml writer to use to write the instance. + public void WriteAtom(object instance, XmlWriter writer) + { + ((Geography)instance).SendTo(GmlFormatter.Create().CreateWriter(writer)); + } + + /// + /// Write the Atom representation of an instance of a primitive type to an TextWriter. + /// + /// The instance to write. + /// The text writer to use to write the instance. + public void WriteAtom(object instance, TextWriter writer) + { + ((Geography)instance).SendTo(WellKnownTextSqlFormatter.Create().CreateWriter(writer)); + } + + /// + /// Write the Json Lite representation of an instance of a primitive type to a json writer. + /// + /// The instance to write. + /// Instance of JsonWriter. + public void WriteJsonLight(object instance, IJsonWriter jsonWriter) + { + IGeoJsonWriter adapter = new GeoJsonWriterAdapter(jsonWriter); + ((Geography)instance).SendTo(GeoJsonObjectFormatter.Create().CreateWriter(adapter)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/GeometryTypeConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/GeometryTypeConverter.cs new file mode 100644 index 0000000..498ef7d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/GeometryTypeConverter.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; +using System.IO; +using System.Xml; +using Microsoft.OData.Json; +using Microsoft.OData.Metadata; +using Microsoft.Spatial; + +namespace Microsoft.OData +{ + /// + /// Handles serialization and deserialization for types derived from Geometry. + /// This file is currently compiled by ODataLib and Astoria server, because it contains + /// functionality related to both serialization and deserialization, but deserialization + /// is not yet integrated into Astoria. Once that integration happens this functionality + /// should be fully contained within ODataLib only. + /// + internal sealed class GeometryTypeConverter : IPrimitiveTypeConverter + { + /// + /// Write the Atom representation of an instance of a primitive type to an XmlWriter. + /// + /// The instance to write. + /// The Xml writer to use to write the instance. + public void WriteAtom(object instance, XmlWriter writer) + { + ((Geometry)instance).SendTo(GmlFormatter.Create().CreateWriter(writer)); + } + + /// + /// Write the Atom representation of an instance of a primitive type to an TextWriter. + /// + /// The instance to write. + /// The text writer to use to write the instance. + public void WriteAtom(object instance, TextWriter writer) + { + ((Geometry)instance).SendTo(WellKnownTextSqlFormatter.Create().CreateWriter(writer)); + } + + /// + /// Write the Json Lite representation of an instance of a primitive type to a json object. + /// + /// The instance to write. + /// Instance of JsonWriter. + public void WriteJsonLight(object instance, IJsonWriter jsonWriter) + { + IGeoJsonWriter adapter = new GeoJsonWriterAdapter(jsonWriter); + ((Geometry)instance).SendTo(GeoJsonObjectFormatter.Create().CreateWriter(adapter)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/GlobalSuppressions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/GlobalSuppressions.cs new file mode 100644 index 0000000..5e2e900 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/GlobalSuppressions.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +// Violations triggered by using terms from our spec could not solve them by adding the word(s) to the FxCop custom dictionary +[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Utils", Scope = "resource", Target = "Microsoft.OData.Core.resources")] +[module: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Utils", Scope = "type", Target = "Microsoft.OData.ODataUtils")] +[module: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Utils", Scope = "type", Target = "Microsoft.OData.ODataUriUtils")] +[module: SuppressMessage("Microsoft.Naming", "CA1701:ResourceStringCompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", Scope = "resource", Target = "Microsoft.OData.Core.resources", Justification = "XmlNodeType.Whitespace is an existing API which we can't fix..")] + +// Normalize strings to uppercase +[module: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.ODataPreferenceHeader.#ReturnContent")] +[module: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.UriParser.SelectExpandOptionParser.#BuildExpandTermToken(Microsoft.OData.UriParser.PathSegmentToken,System.String)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.UriParser.FunctionCallBinder.#BindAsUriFunction(Microsoft.OData.UriParser.FunctionCallToken,System.Collections.Generic.List`1)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.UriParser.SelectExpandOptionParser.#BuildStarExpandTermToken(Microsoft.OData.UriParser.PathSegmentToken)")] +[module: SuppressMessage("Microsoft.Naming", "CA1701:ResourceStringCompoundWordsShouldBeCasedCorrectly", MessageId = "NonEntity", Scope = "resource", Target = "Microsoft.OData.Core.resources")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.OData.Evaluation.ODataResourceMetadataContext+ODataResourceMetadataContextWithoutModel.#serializationInfo")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.OData.Evaluation.ODataMetadataContext.#metadataLevel")] + +// Already public APIs thus cannot be changed. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Scope = "member", Target = "Microsoft.OData.ODataBatchOperationRequestMessage.#ContentId")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Scope = "member", Target = "Microsoft.OData.ODataBatchOperationResponseMessage.#ContentId")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Scope = "member", Target = "Microsoft.OData.ODataMediaType.#Type")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Scope = "member", Target = "Microsoft.OData.UriParser.ODataUnresolvedFunctionParameterAlias.#Type")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.OData.UriParser.SelectExpandSemanticBinder.#Bind(Microsoft.OData.UriParser.ODataPathInfo,Microsoft.OData.UriParser.ExpandToken,Microsoft.OData.UriParser.SelectToken,Microsoft.OData.UriParser.ODataUriParserConfiguration)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.OData.UriParser.SelectTreeNormalizer.#NormalizeSelectTree(Microsoft.OData.UriParser.SelectToken)")] + +// Current design actually can save cast effort. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.UriParser.FunctionCallBinder.#TryBindIdentifier(System.String,System.Collections.Generic.IEnumerable`1,Microsoft.OData.UriParser.QueryNode,Microsoft.OData.UriParser.BindingState,Microsoft.OData.UriParser.QueryNode&)")] + +// In lambda expression. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.UriParser.SelectPropertyVisitor.#ProcessTokenAsPath(Microsoft.OData.UriParser.NonSystemToken)")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.JsonLight.ODataJsonLightDeltaWriter.#StartDeltaLink(Microsoft.OData.ODataDeltaLinkBase)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.JsonLight.ODataJsonLightDeltaReader.#IsTopLevel", Justification = "Used in debug builds in assertions.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.JsonLight.ODataJsonLightDeltaWriter.#IsTopLevel", Justification = "Used in debug builds in assertions.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.OData.UriParser.FunctionSignatureWithReturnType.#ArgumentTypes")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.OData.UriParser.UriPrimitiveTypeParser.#TryUriStringToPrimitive(System.String,Microsoft.OData.Edm.IEdmTypeReference,System.Object&,Microsoft.OData.UriParser.UriLiteralParsingException&)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.OData.ContainerBuilderExtensions.#AddService`2(Microsoft.OData.IContainerBuilder,Microsoft.OData.ServiceLifetime)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.OData.ContainerBuilderExtensions.#AddService`1(Microsoft.OData.IContainerBuilder,Microsoft.OData.ServiceLifetime)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Scope = "member", Target = "Microsoft.OData.ContainerBuilderExtensions.#AddService`2(Microsoft.OData.IContainerBuilder,Microsoft.OData.ServiceLifetime,System.Func`2)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.UriParser.ODataUriParser.#IsODataQueryOption(System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.UriParser.InnerPathTokenBinder.#BindInnerPathSegment(Microsoft.OData.UriParser.InnerPathToken)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])", Scope = "member", Target = "Microsoft.OData.PropertyCacheHandler.#GetProperty(System.String,Microsoft.OData.Edm.IEdmStructuredType)")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Utils", Scope = "type", Target = "Microsoft.OData.Buffers.BufferUtils")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.OData.UriParser.SelectExpandOptionParser.#BuildSelectTermToken(Microsoft.OData.UriParser.PathSegmentToken,System.String)")] + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValue.cs new file mode 100644 index 0000000..d26ba91 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValue.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Extension methods for http header values. + /// + internal sealed class HttpHeaderValue : Dictionary + { + /// + /// Constructs a new instance of . + /// + internal HttpHeaderValue() + : base(StringComparer.OrdinalIgnoreCase /*Should be case-insensitive for tokens.*/) + { + } + + /// + /// Returns the HTTP header value string which can be used to set the header on the requst and response messages. + /// + /// Returns the HTTP header value string which can be used to set the header on the requst and response messages. + public override string ToString() + { + return this.Count == 0 ? null : String.Join(HttpHeaderValueLexer.ElementSeparator, this.Values.Select(element => element.ToString()).ToArray()); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueElement.cs new file mode 100644 index 0000000..72e86d2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueElement.cs @@ -0,0 +1,126 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; + + /// + /// Class to represent a HTTP header value element. + /// + public sealed class HttpHeaderValueElement + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the preference. + /// The value of the preference. + /// The enumeration of preference parameter key value pairs. + public HttpHeaderValueElement(string name, string value, IEnumerable> parameters) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(name, "name"); + ExceptionUtils.CheckArgumentNotNull(parameters, "parameters"); + + this.Name = name; + this.Value = value; + this.Parameters = parameters; +#if DEBUG + AssertToken(this.Name); + AssertTokenOrQuotedString(this.Value); + foreach (KeyValuePair kvp in this.Parameters) + { + AssertToken(kvp.Key); + AssertTokenOrQuotedString(kvp.Value); + } +#endif + } + + /// + /// The name of the preference. + /// + public string Name { get; private set; } + + /// + /// The value of the preference. + /// + public string Value { get; private set; } + + /// + /// The enumeration of preference parameter key value pairs. + /// + public IEnumerable> Parameters { get; private set; } + + /// + /// Converts the current to string. + /// + /// The string for . + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + AppendNameValuePair(stringBuilder, this.Name, this.Value); + foreach (KeyValuePair parameter in this.Parameters) + { + stringBuilder.Append(HttpHeaderValueLexer.ParameterSeparator); + AppendNameValuePair(stringBuilder, parameter.Key, parameter.Value); + } + + return stringBuilder.ToString(); + } + + /// + /// Appends the and to as name=value. + /// + /// The string builder to append to. + /// The name to append. + /// The value to append. + private static void AppendNameValuePair(StringBuilder stringBuilder, string name, string value) + { + Debug.Assert(!string.IsNullOrEmpty(name), "!string.IsNullOrEmpty(name)"); + + stringBuilder.Append(name); + if (value != null) + { + stringBuilder.Append(HttpHeaderValueLexer.ValueSeparator); + stringBuilder.Append(value); + } + } + +#if DEBUG + /// + /// Asserts the given value is a token. + /// + /// value in question. + private static void AssertToken(string value) + { + Debug.Assert(!string.IsNullOrEmpty(value), "!string.IsNullOrEmpty(value)"); + int index = 0; + bool isQuotedString; + HttpUtils.ReadTokenOrQuotedStringValue("header", value, ref index, out isQuotedString, message => new ODataException(message)); + Debug.Assert(!isQuotedString, "!isQuotedString"); + Debug.Assert(index == value.Length, "index == value.Length"); + } + + /// + /// Asserts the given value is a token or a quoted-string. + /// + /// value in question. + private static void AssertTokenOrQuotedString(string value) + { + if (value == null) + { + return; + } + + int index = 0; + bool isQuotedString; + HttpUtils.ReadTokenOrQuotedStringValue("header", value, ref index, out isQuotedString, message => new ODataException(message)); + Debug.Assert(index == value.Length, "index == value.Length"); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueLexer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueLexer.cs new file mode 100644 index 0000000..610c941 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpHeaderValueLexer.cs @@ -0,0 +1,615 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + /// + /// Lexer to parse HTTP header values. + /// + internal abstract class HttpHeaderValueLexer + { + /// + /// The ',' separator. + /// + internal const string ElementSeparator = ","; + + /// + /// The ';' separator. + /// + internal const string ParameterSeparator = ";"; + + /// + /// The '=' separator. + /// + internal const string ValueSeparator = "="; + + /// + /// The name of the HTTP header being parsed. + /// + private readonly string httpHeaderName; + + /// + /// The value of the HTTP header being parsed. + /// + private readonly string httpHeaderValue; + + /// + /// The starting index to the next item to be parsed. + /// + private readonly int startIndexOfNextItem; + + /// + /// The value of the current parsed item. If the item type is quoted-string, this returns the unescaped and unquoted string value. For other item types, + /// the value is the same as the original text from the header. + /// + private readonly string value; + + /// + /// The original text of the current parsed item. If the item type is quoted-string, this returns the escaped and quoted string value straight from the header. + /// For other item types, the original text is the same as the item value. + /// + private readonly string originalText; + + /// + /// Constructs a new instance of . + /// + /// The name of the HTTP header being parsed. + /// The value of the HTTP header being parsed. + /// The value of the current parsed item. If the item type is quoted-string, this returns the unescaped and unquoted string value. For other item types, + /// the value is the same as the original text from the header. + /// The original text of the current parsed item. If the item type is quoted-string, this returns the escaped and quoted string value straight from the header. + /// For other item types, the original text is the same as the item value. + /// The start index of the next item to be parsed. + private HttpHeaderValueLexer(string httpHeaderName, string httpHeaderValue, string value, string originalText, int startIndexOfNextItem) + { + this.httpHeaderName = httpHeaderName; + this.httpHeaderValue = httpHeaderValue; + this.value = value; + this.originalText = originalText; + + // Skip over white spaces. + if (this.httpHeaderValue != null) + { + HttpUtils.SkipWhitespace(this.httpHeaderValue, ref startIndexOfNextItem); + } + + this.startIndexOfNextItem = startIndexOfNextItem; + } + + /// + /// The item type enum. + /// + internal enum HttpHeaderValueItemType + { + /// Currently at the start of the header value. + Start = 0, + + /// The current item is a token. + Token, + + /// The current item is a quoted-string. + QuotedString, + + /// The current item is the header element separator ','. + ElementSeparator, + + /// The current item is the parameter separator ';'. + ParameterSeparator, + + /// The current item is the value separator '='. + ValueSeparator, + + /// At the end of the header value. + End + } + + /// + /// The value of the current parsed item. If the item type is quoted-string, this returns the unescaped and unquoted string value. For other item types, + /// the value is the same as the original text from the header. + /// + internal string Value + { + get + { + return this.value; + } + } + + /// + /// The original text of the current parsed item. If the item type is quoted-string, this returns the escaped and quoted string value straight from the header. + /// For other item types, the original text is the same as the item value. + /// + internal string OriginalText + { + get + { + return this.originalText; + } + } + + /// + /// The type of the current parsed item. + /// + internal abstract HttpHeaderValueItemType Type + { + get; + } + + /// + /// Constructs a new instance of the HTTP header value item. + /// + /// The name of the HTTP header being parsed. + /// The value of the HTTP header being parsed. + /// The newly created instance of . + internal static HttpHeaderValueLexer Create(string httpHeaderName, string httpHeaderValue) + { + Debug.Assert(!string.IsNullOrEmpty(httpHeaderName), "!string.IsNullOrEmpty(httpHeaderName)"); + + return new HttpHeaderStart(httpHeaderName, httpHeaderValue); + } + + /// + /// Reads the content of a HTTP header from this instance to a new instance. + /// + /// A new instance populated with the content from this instance. + internal HttpHeaderValue ToHttpHeaderValue() + { + HttpHeaderValueLexer lexer = this; + Debug.Assert( + lexer.Type == HttpHeaderValueItemType.Start, + "lexer.Type == HttpHeaderValueItemType.Start -- Should only call this method on a lexer that's not yet been read."); + + HttpHeaderValue headerValue = new HttpHeaderValue(); + + // header = "header-name" ":" 1#element + // element = token [ BWS "=" BWS (token | quoted-string) ] + // *( OWS ";" [ OWS parameter ] ) + // parameter = token [ BWS "=" BWS (token | quoted-string) ] + while (true) + { + if (lexer.Type == HttpHeaderValueItemType.End) + { + break; + } + + Debug.Assert( + lexer.Type == HttpHeaderValueItemType.Start || lexer.Type == HttpHeaderValueItemType.ElementSeparator, + "lexer.Type == HttpHeaderValueItemType.Start || lexer.Type == HttpHeaderValueItemType.ElementSeparator"); + + lexer = lexer.ReadNext(); + + Debug.Assert( + lexer.Type == HttpHeaderValueItemType.Token || lexer.Type == HttpHeaderValueItemType.End, + "lexer.Type == HttpHeaderValueItemType.Token || lexer.Type == HttpHeaderValueItemType.End"); + + if (lexer.Type == HttpHeaderValueItemType.Token) + { + var element = ReadHttpHeaderValueElement(ref lexer); + + // If multiple elements with the same name encountered, the first one wins. + if (!headerValue.ContainsKey(element.Name)) + { + headerValue.Add(element.Name, element); + } + } + } + + return headerValue; + } + + /// + /// Returns an instance of to parse the rest of the items on the header value. + /// Parsing is based on this grammar: + /// header = "header-name" ":" 1#element + /// element = token [ BWS "=" BWS (token | quoted-string) ] + /// *( OWS ";" [ OWS parameter ] ) + /// parameter = token [ BWS "=" BWS (token | quoted-string) ] + /// + /// Returns an instance of to parse the rest of the items on the header value. + internal abstract HttpHeaderValueLexer ReadNext(); + + /// + /// Reads a from and advances the forward. + /// + /// The lexer to read from. + /// The that was read. + private static HttpHeaderValueElement ReadHttpHeaderValueElement(ref HttpHeaderValueLexer lexer) + { + Debug.Assert(lexer.Type == HttpHeaderValueItemType.Token, "lexer.Type == HttpHeaderValueItemType.Token"); + + List> keyValuePairs = new List> { ReadKeyValuePair(ref lexer) }; + while (lexer.Type == HttpHeaderValueItemType.ParameterSeparator) + { + lexer = lexer.ReadNext(); + keyValuePairs.Add(ReadKeyValuePair(ref lexer)); + } + + Debug.Assert( + lexer.Type == HttpHeaderValueItemType.ElementSeparator || lexer.Type == HttpHeaderValueItemType.End, + "lexer.Type == HttpHeaderValueItemType.ElementSeparator || lexer.Type == HttpHeaderValueItemType.End"); + return new HttpHeaderValueElement(keyValuePairs[0].Key, keyValuePairs[0].Value, keyValuePairs.Skip(1).ToArray()); + } + + /// + /// Reads a token or token=(token|quoted-string) from the , convert it to a key value pair and advances the . + /// + /// The lexer to read from. + /// The converted key value pair. + private static KeyValuePair ReadKeyValuePair(ref HttpHeaderValueLexer lexer) + { + Debug.Assert(lexer.Type == HttpHeaderValueItemType.Token, "lexer.Type == HttpHeaderValueItemType.Token"); + + string name = lexer.OriginalText; + string value = null; + lexer = lexer.ReadNext(); + if (lexer.Type == HttpHeaderValueItemType.ValueSeparator) + { + lexer = lexer.ReadNext(); + Debug.Assert( + lexer.Type == HttpHeaderValueItemType.Token || lexer.Type == HttpHeaderValueItemType.QuotedString, + "lexer.Type == HttpHeaderValueItemType.Token || lexer.Type == HttpHeaderValueItemType.QuotedString"); + + value = lexer.OriginalText; + lexer = lexer.ReadNext(); + } + + Debug.Assert( + lexer.Type == HttpHeaderValueItemType.End || lexer.Type == HttpHeaderValueItemType.ElementSeparator || lexer.Type == HttpHeaderValueItemType.ParameterSeparator, + "lexer.Type == HttpHeaderValueItemType.End || lexer.Type == HttpHeaderValueItemType.ElementSeparator || lexer.Type == HttpHeaderValueItemType.ParameterSeparator"); + + return new KeyValuePair(name, value); + } + + /// + /// Returns true if we've parsed to the end of the header value, false otherwise. + /// + /// Returns true if we've parsed to the end of the header value, false otherwise. + private bool EndOfHeaderValue() + { + return this.startIndexOfNextItem == this.httpHeaderValue.Length; + } + + /// + /// Reads a token or quoted-string value from the header. + /// + /// The token or quoted-string value that was read from the header. + private HttpHeaderValueLexer ReadNextTokenOrQuotedString() + { + int index = this.startIndexOfNextItem; + bool isQuotedString; + string nextValue = HttpUtils.ReadTokenOrQuotedStringValue(this.httpHeaderName, this.httpHeaderValue, ref index, out isQuotedString, message => new ODataException(message)); + + // Instead of testing whether result is null or empty, we check to see if the index have moved forward because we can encounter the empty quoted string "". + if (index == this.startIndexOfNextItem) + { + throw new ODataException(Strings.HttpHeaderValueLexer_FailedToReadTokenOrQuotedString(this.httpHeaderName, this.httpHeaderValue, this.startIndexOfNextItem)); + } + + if (isQuotedString) + { + string original = this.httpHeaderValue.Substring(this.startIndexOfNextItem, index - this.startIndexOfNextItem); + return new HttpHeaderQuotedString(this.httpHeaderName, this.httpHeaderValue, nextValue, original, index); + } + + return new HttpHeaderToken(this.httpHeaderName, this.httpHeaderValue, nextValue, index); + } + + /// + /// Reads a token from the header. + /// + /// The token item that was read from the header. + private HttpHeaderToken ReadNextToken() + { + HttpHeaderValueLexer item = this.ReadNextTokenOrQuotedString(); + if (item.Type == HttpHeaderValueItemType.QuotedString) + { + throw new ODataException(Strings.HttpHeaderValueLexer_TokenExpectedButFoundQuotedString(this.httpHeaderName, this.httpHeaderValue, this.startIndexOfNextItem)); + } + + return (HttpHeaderToken)item; + } + + /// + /// Reads a separator from the header. + /// + /// The separator item that was read from the header. + private HttpHeaderSeparator ReadNextSeparator() + { + Debug.Assert(this.startIndexOfNextItem < this.httpHeaderValue.Length, "this.startIndexOfNextItem < this.httpHeaderValue.Length"); + string separator = this.httpHeaderValue.Substring(this.startIndexOfNextItem, 1); + if (separator != ElementSeparator && separator != ParameterSeparator && separator != ValueSeparator) + { + throw new ODataException(Strings.HttpHeaderValueLexer_UnrecognizedSeparator(this.httpHeaderName, this.httpHeaderValue, this.startIndexOfNextItem, separator)); + } + + return new HttpHeaderSeparator(this.httpHeaderName, this.httpHeaderValue, separator, this.startIndexOfNextItem + 1); + } + + /// + /// Represents the start of the http header value. + /// + private sealed class HttpHeaderStart : HttpHeaderValueLexer + { + /// + /// Constructs a new instance of . + /// + /// The name of the HTTP header being parsed. + /// The value of the HTTP header being parsed. + internal HttpHeaderStart(string httpHeaderName, string httpHeaderValue) + : base(httpHeaderName, httpHeaderValue, /*value*/ null, /*originalText*/ null, 0) + { + Debug.Assert(!string.IsNullOrEmpty(this.httpHeaderName), "!string.IsNullOrEmpty(this.httpHeaderName)"); + Debug.Assert( + this.httpHeaderValue == null || this.startIndexOfNextItem <= this.httpHeaderValue.Length, + "this.httpHeaderValue == null || this.startIndexOfNextItem <= this.httpHeaderValue.Length"); + } + + /// + /// The type of the current item. + /// + internal override HttpHeaderValueItemType Type + { + get + { + return HttpHeaderValueItemType.Start; + } + } + + /// + /// Returns an instance of to parse the rest of the items on the header value. + /// Parsing is based on this grammar: + /// header = "header-name" ":" 1#element + /// element = token [ BWS "=" BWS (token | quoted-string) ] + /// *( OWS ";" [ OWS parameter ] ) + /// parameter = token [ BWS "=" BWS (token | quoted-string) ] + /// + /// Returns an instance of to parse the rest of the items on the header value. + internal override HttpHeaderValueLexer ReadNext() + { + if (this.httpHeaderValue == null || this.EndOfHeaderValue()) + { + return HttpHeaderEnd.Instance; + } + + // The first item on the header must be a token. + return this.ReadNextToken(); + } + } + + /// + /// Represents a token in the HTTP header value. + /// + private sealed class HttpHeaderToken : HttpHeaderValueLexer + { + /// + /// Constructs a new instance of . + /// + /// The name of the HTTP header being parsed. + /// The value of the HTTP header being parsed. + /// The value of the token. + /// The start index of the next item. + internal HttpHeaderToken(string httpHeaderName, string httpHeaderValue, string value, int startIndexOfNextItem) + : base(httpHeaderName, httpHeaderValue, value, value, startIndexOfNextItem) + { + Debug.Assert(!string.IsNullOrEmpty(this.httpHeaderName), "!string.IsNullOrEmpty(this.httpHeaderName)"); + Debug.Assert(!string.IsNullOrEmpty(this.httpHeaderValue), "!string.IsNullOrEmpty(this.httpHeaderValue)"); + Debug.Assert(this.startIndexOfNextItem <= this.httpHeaderValue.Length, "this.startIndexOfNextItem <= this.httpHeaderValue.Length"); + Debug.Assert(!string.IsNullOrEmpty(this.value), "!string.IsNullOrEmpty(this.value)"); + } + + /// + /// The type of the current item. + /// + internal override HttpHeaderValueItemType Type + { + get + { + return HttpHeaderValueItemType.Token; + } + } + + /// + /// Returns an instance of to parse the rest of the items on the header value. + /// Parsing is based on this grammar: + /// header = "header-name" ":" 1#element + /// element = token [ BWS "=" BWS (token | quoted-string) ] + /// *( OWS ";" [ OWS parameter ] ) + /// parameter = token [ BWS "=" BWS (token | quoted-string) ] + /// + /// Returns an instance of to parse the rest of the items on the header value. + internal override HttpHeaderValueLexer ReadNext() + { + if (this.EndOfHeaderValue()) + { + return HttpHeaderEnd.Instance; + } + + // ',', ';' or '=' can come after a token. + return this.ReadNextSeparator(); + } + } + + /// + /// Represents a quoted-string in the HTTP header value. + /// + private sealed class HttpHeaderQuotedString : HttpHeaderValueLexer + { + /// + /// Constructs a new instance of . + /// + /// The name of the HTTP header being parsed. + /// The value of the HTTP header being parsed. + /// The value of the quoted string, unescaped and without quotes. + /// The original text of the quoted string, escaped and with quotes. + /// The start index of the next item. + internal HttpHeaderQuotedString(string httpHeaderName, string httpHeaderValue, string value, string originalText, int startIndexOfNextItem) + : base(httpHeaderName, httpHeaderValue, value, originalText, startIndexOfNextItem) + { + Debug.Assert(!string.IsNullOrEmpty(this.httpHeaderName), "!string.IsNullOrEmpty(this.httpHeaderName)"); + Debug.Assert(!string.IsNullOrEmpty(this.httpHeaderValue), "!string.IsNullOrEmpty(this.httpHeaderValue)"); + Debug.Assert(this.startIndexOfNextItem <= this.httpHeaderValue.Length, "this.startIndexOfNextItem <= this.httpHeaderValue.Length"); + Debug.Assert(!string.IsNullOrEmpty(this.originalText), "!string.IsNullOrEmpty(this.originalText)"); + } + + /// + /// The type of the current item. + /// + internal override HttpHeaderValueItemType Type + { + get + { + return HttpHeaderValueItemType.QuotedString; + } + } + + /// + /// Returns an instance of to parse the rest of the items on the header value. + /// Parsing is based on this grammar: + /// header = "header-name" ":" 1#element + /// element = token [ BWS "=" BWS (token | quoted-string) ] + /// *( OWS ";" [ OWS parameter ] ) + /// parameter = token [ BWS "=" BWS (token | quoted-string) ] + /// + /// Returns an instance of to parse the rest of the items on the header value. + internal override HttpHeaderValueLexer ReadNext() + { + if (this.EndOfHeaderValue()) + { + return HttpHeaderEnd.Instance; + } + + HttpHeaderSeparator separator = this.ReadNextSeparator(); + + // ',' and ';' can come after a quoted-string. + if (separator.Value == ElementSeparator || separator.Value == ParameterSeparator) + { + return separator; + } + + throw new ODataException(Strings.HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString(this.httpHeaderName, this.httpHeaderValue, this.startIndexOfNextItem, separator.Value)); + } + } + + /// + /// Represents a separator in the HTTP header value. + /// + private sealed class HttpHeaderSeparator : HttpHeaderValueLexer + { + /// + /// Constructs a new instance of . + /// + /// The name of the HTTP header being parsed. + /// The value of the HTTP header being parsed. + /// The value of the separator. + /// The start index of the next item. + internal HttpHeaderSeparator(string httpHeaderName, string httpHeaderValue, string value, int startIndexOfNextItem) + : base(httpHeaderName, httpHeaderValue, value, value, startIndexOfNextItem) + { + Debug.Assert(!string.IsNullOrEmpty(this.httpHeaderName), "!string.IsNullOrEmpty(this.httpHeaderName)"); + Debug.Assert(!string.IsNullOrEmpty(this.httpHeaderValue), "!string.IsNullOrEmpty(this.httpHeaderValue)"); + Debug.Assert(this.startIndexOfNextItem <= this.httpHeaderValue.Length, "this.startIndexOfNextItem <= this.httpHeaderValue.Length"); + Debug.Assert( + this.Value == ElementSeparator || this.Value == ParameterSeparator || this.Value == ValueSeparator, + "this.Value == CommaSeparator || this.Value == SemicolonSeparator || this.Value == EqualsSeparator"); + } + + /// + /// The type of the current item. + /// + internal override HttpHeaderValueItemType Type + { + get + { + switch (this.Value) + { + case ElementSeparator: + return HttpHeaderValueItemType.ElementSeparator; + case ParameterSeparator: + return HttpHeaderValueItemType.ParameterSeparator; + default: + Debug.Assert(this.Value == ValueSeparator, "this.Value == ValueSeparator"); + return HttpHeaderValueItemType.ValueSeparator; + } + } + } + + /// + /// Returns an instance of to parse the rest of the items on the header value. + /// Parsing is based on this grammar: + /// header = "header-name" ":" 1#element + /// element = token [ BWS "=" BWS (token | quoted-string) ] + /// *( OWS ";" [ OWS parameter ] ) + /// parameter = token [ BWS "=" BWS (token | quoted-string) ] + /// + /// Returns an instance of to parse the rest of the items on the header value. + internal override HttpHeaderValueLexer ReadNext() + { + if (this.EndOfHeaderValue()) + { + throw new ODataException(Strings.HttpHeaderValueLexer_EndOfFileAfterSeparator(this.httpHeaderName, this.httpHeaderValue, this.startIndexOfNextItem, this.originalText)); + } + + // Token or quoted-string can come after '='. i.e. token ['=' (token | quoted-string)] + if (this.Value == ValueSeparator) + { + return this.ReadNextTokenOrQuotedString(); + } + + // Only token can come after ',' and ';'. + return this.ReadNextToken(); + } + } + + /// + /// Represents the end of the http header value. + /// + private sealed class HttpHeaderEnd : HttpHeaderValueLexer + { + /// + /// Static instance of the end item. + /// + internal static readonly HttpHeaderEnd Instance = new HttpHeaderEnd(); + + /// + /// Constructs a new instance of . + /// + private HttpHeaderEnd() + : base(/*httpHeaderName*/ null, /*httpHeaderValue*/ null, /*value*/ null, /*originalText*/ null, /*startIndexOfNextItem*/ -1) + { + } + + /// + /// The type of the current item. + /// + internal override HttpHeaderValueItemType Type + { + get + { + return HttpHeaderValueItemType.End; + } + } + + /// + /// Returns an instance of to parse the rest of the items on the header value. + /// Parsing is based on this grammar: + /// header = "header-name" ":" 1#element + /// element = token [ BWS "=" BWS (token | quoted-string) ] + /// *( OWS ";" [ OWS parameter ] ) + /// parameter = token [ BWS "=" BWS (token | quoted-string) ] + /// + /// Returns an instance of to parse the rest of the items on the header value. + internal override HttpHeaderValueLexer ReadNext() + { + Debug.Assert(false, "Already reached EndOfHeaderValue, our code should never call HttpHeaderEnd.ReadNext()."); + return null; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpUtils.cs new file mode 100644 index 0000000..7681f9f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/HttpUtils.cs @@ -0,0 +1,885 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + #endregion Namespaces + + /// + /// Class with utility methods to work with HTTP concepts + /// + internal static class HttpUtils + { + /// Reads a Content-Type header and extracts the media type's name (type/subtype) and parameters. + /// The Content-Type header. + /// The media type in standard type/subtype form, without parameters. + /// The (optional) charset parameter of the media type. + /// The parameters of the media type not including the 'charset' parameter. + internal static IList> ReadMimeType(string contentType, out string mediaTypeName, out string mediaTypeCharset) + { + if (String.IsNullOrEmpty(contentType)) + { + throw new ODataContentTypeException(Strings.HttpUtils_ContentTypeMissing); + } + + IList> mediaTypes = ReadMediaTypes(contentType); + if (mediaTypes.Count != 1) + { + throw new ODataContentTypeException(Strings.HttpUtils_NoOrMoreThanOneContentTypeSpecified(contentType)); + } + + ODataMediaType mediaType = mediaTypes[0].Key; + MediaTypeUtils.CheckMediaTypeForWildCards(mediaType); + + mediaTypeName = mediaType.FullTypeName; + mediaTypeCharset = mediaTypes[0].Value; + + return mediaType.Parameters != null ? mediaType.Parameters.ToList() : null; + } + + /// Builds a Content-Type header which includes media type and encoding information. + /// Media type to be used. + /// Encoding to be used in response, possibly null. + /// The value for the Content-Type header. + internal static string BuildContentType(ODataMediaType mediaType, Encoding encoding) + { + Debug.Assert(mediaType != null, "mediaType != null"); + + return mediaType.ToText(encoding); + } + + /// Returns all media types from the specified (non-blank) . + /// Non-blank text, as it appears on an HTTP Accepts header. + /// An enumerable object with key/value pairs of media type descriptions with their (optional) charset parameter values. + internal static IList> MediaTypesFromString(string text) + { + if (string.IsNullOrEmpty(text)) + { + return null; + } + + return ReadMediaTypes(text); + } + + /// + /// Does an ordinal ignore case comparision of the given media type names. + /// + /// First media type name. + /// Second media type name. + /// returns true if the media type names are the same. + internal static bool CompareMediaTypeNames(string mediaTypeName1, string mediaTypeName2) + { + return string.Equals(mediaTypeName1, mediaTypeName2, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Does an ordinal ignore case comparision of the given MIME type parameter name. + /// + /// First parameter name. + /// Second parameter name. + /// returns true if the parameter names are the same. + internal static bool CompareMediaTypeParameterNames(string parameterName1, string parameterName2) + { + return string.Equals(parameterName1, parameterName2, StringComparison.OrdinalIgnoreCase) + || (IsMetadataParameter(parameterName1) && IsMetadataParameter(parameterName2)) + || (IsStreamingParameter(parameterName1) && IsStreamingParameter(parameterName2)); + } + + /// + /// Determines whether or not a parameter is the odata metadata parameter. + /// + /// The name of the parameter. + /// returns true if the parameter name is the odata metadata parameter. + internal static bool IsMetadataParameter(string parameterName) + { + return (String.Compare(parameterName, MimeConstants.MimeMetadataParameterName, StringComparison.OrdinalIgnoreCase) == 0 + || String.Compare(parameterName, MimeConstants.MimeShortMetadataParameterName, StringComparison.OrdinalIgnoreCase) == 0); + } + + + /// + /// Determines whether or not a parameter is the odata streaming parameter. + /// + /// The name of the parameter. + /// returns true if the parameter name is the odata streaming parameter. + internal static bool IsStreamingParameter(string parameterName) + { + return (String.Compare(parameterName, MimeConstants.MimeStreamingParameterName, StringComparison.OrdinalIgnoreCase) == 0 + || String.Compare(parameterName, MimeConstants.MimeShortStreamingParameterName, StringComparison.OrdinalIgnoreCase) == 0); + } + + /// Gets the best encoding available for the specified charset request. + /// + /// The Accept-Charset header value (eg: "iso-8859-5, unicode-1-1;q=0.8"). + /// + /// The media type used to compute the default encoding for the payload. + /// The encoding to use for UTF-8 charsets; we use the one without the BOM. + /// The encoding to use if no encoding could be computed from the or . + /// An Encoding object appropriate to the specifed charset request. + internal static Encoding EncodingFromAcceptableCharsets(string acceptableCharsets, ODataMediaType mediaType, Encoding utf8Encoding, Encoding defaultEncoding) + { + Debug.Assert(mediaType != null, "mediaType != null"); + + // Determines the appropriate encoding mapping according to + // RFC 2616.14.2 (http://tools.ietf.org/html/rfc2616#section-14.2). + Encoding result = null; + if (!string.IsNullOrEmpty(acceptableCharsets)) + { + // PERF: in the future if we find that computing the encoding from the accept charsets is + // too expensive we could introduce a cache of original strings to resolved encoding. + CharsetPart[] parts = new List(AcceptCharsetParts(acceptableCharsets)).ToArray(); + + // NOTE: List.Sort uses an unstable sort algorithm; if charsets have the same quality value + // we want to pick the first one specified so we need a stable sort. + KeyValuePair[] sortedParts = parts.StableSort(delegate(CharsetPart x, CharsetPart y) + { + return y.Quality - x.Quality; + }); + + foreach (KeyValuePair sortedPart in sortedParts) + { + CharsetPart part = sortedPart.Value; + if (part.Quality > 0) + { + // When UTF-8 is specified, select the version that doesn't use the BOM. + if (String.Compare("utf-8", part.Charset, StringComparison.OrdinalIgnoreCase) == 0) + { + result = utf8Encoding; + break; + } + else + { + result = GetEncodingFromCharsetName(part.Charset); + if (result != null) + { + break; + } + + // If the charset is not supported it is ignored so other possible charsets are evaluated. + } + } + } + } + + // No Charset was specifed, or if charsets were specified, no valid charset was found. + // Returning a different charset is also valid. Get the default encoding for the media type. + if (result == null) + { + result = mediaType.SelectEncoding(); + if (result == null) + { + return defaultEncoding; + } + } + + return result; + } + + /// + /// Reads the numeric part of a quality value substring, normalizing it to 0-1000 + /// rather than the standard 0.000-1.000 ranges. + /// + /// Text to read qvalue from. + /// Index into text where the qvalue starts. + /// After the method executes, the normalized qvalue. + /// + /// For more information, see RFC 2616.3.8. + /// + internal static void ReadQualityValue(string text, ref int textIndex, out int qualityValue) + { + char digit = text[textIndex++]; + switch (digit) + { + case '0': + qualityValue = 0; + break; + case '1': + qualityValue = 1; + break; + default: + throw new ODataContentTypeException(Strings.HttpUtils_InvalidQualityValueStartChar(text, digit)); + } + + if (textIndex < text.Length && text[textIndex] == '.') + { + textIndex++; + + int adjustFactor = 1000; + while (adjustFactor > 1 && textIndex < text.Length) + { + char c = text[textIndex]; + int charValue = DigitToInt32(c); + if (charValue >= 0) + { + textIndex++; + adjustFactor /= 10; + qualityValue *= 10; + qualityValue += charValue; + } + else + { + break; + } + } + + qualityValue *= adjustFactor; + if (qualityValue > 1000) + { + // Too high of a value in qvalue. + throw new ODataContentTypeException(Strings.HttpUtils_InvalidQualityValue(qualityValue / 1000, text)); + } + } + else + { + qualityValue *= 1000; + } + } + + /// + /// Validates that the HTTP method string matches one of the supported HTTP methods. + /// + /// The HTTP method string to validate. + internal static void ValidateHttpMethod(string httpMethodString) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(httpMethodString, "httpMethodString"); + + if (string.CompareOrdinal(httpMethodString, ODataConstants.MethodGet) != 0 + && string.CompareOrdinal(httpMethodString, ODataConstants.MethodDelete) != 0 + && string.CompareOrdinal(httpMethodString, ODataConstants.MethodPatch) != 0 + && string.CompareOrdinal(httpMethodString, ODataConstants.MethodPost) != 0 + && string.CompareOrdinal(httpMethodString, ODataConstants.MethodPut) != 0) + { + throw new ODataException(Strings.HttpUtils_InvalidHttpMethodString(httpMethodString)); + } + } + + /// + /// Determines whether the given HTTP method is one that is accepted for queries. GET is accepted for queries. + /// + /// The HTTP method to check. + /// True if the given httpMethod is GET. + internal static bool IsQueryMethod(string httpMethod) + { + return string.CompareOrdinal(httpMethod, ODataConstants.MethodGet) == 0; + } + + /// + /// Gets the string status message for a given Http response status code. + /// + /// The status code to get the status message for. + /// The string status message for the . + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This is a large switch on all the Http response codes; no complexity here.")] + internal static string GetStatusMessage(int statusCode) + { + // Non-localized messages for status codes. + // These are the recommended reason phrases as per HTTP RFC 2616, Section 6.1.1 + switch (statusCode) + { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Time-out"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Request Entity Too Large"; + case 414: + return "Request-URI Too Large"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Requested range not satisfiable"; + case 417: + return "Expectation Failed"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Time-out"; + case 505: + return "HTTP Version not supported"; + default: + return "Unknown Status Code"; + } + } + + /// + /// Returns the encoding object for the specified charset name. + /// + /// The of the charset to get the encoding for. + /// The encoding object or null if such encoding is not supported. + internal static Encoding GetEncodingFromCharsetName(string charsetName) + { + try + { +#if !ORCAS + // The default behavior without the fallbacks is to use either replacement or best-fit for unencodable characters. + // That would be the wrong behavior for us. On the other hand in Silverlight the only supported encodings + // are UTF-8 and UTF-16 (LE and BE), all of which can encode any character, so the fallbacks are never used. + // Thus it's OK to use the default behavior here. + return Encoding.GetEncoding(charsetName); +#else + return Encoding.GetEncoding(charsetName, new EncoderExceptionFallback(), new DecoderExceptionFallback()); +#endif + } + catch (ArgumentException) + { + // This exception is thrown when the character + // set isn't supported. + return null; + } + } + + /// + /// Reads a token or quoted-string value from the header. + /// + /// Name of the header. + /// Header text. + /// Parsing index in . + /// Returns true if the value is a quoted-string, false if the value is a token. + /// Func to create the appropriate exception to throw from the given error message. + /// The token or quoted-string value that was read from the header. + internal static string ReadTokenOrQuotedStringValue(string headerName, string headerText, ref int textIndex, out bool isQuotedString, Func createException) + { + //// NOTE: See RFC 2616, Sections 3.6 and 2.2 for the full grammar for HTTP parameter values + //// + //// parameter-value = token | quoted-string + //// token = 1* + //// CHAR = + //// CTL = + //// separators = "(" | ")" | "<" | ">" | "@" + //// | "," | ";" | ":" | "\" | <"> + //// | "/" | "[" | "]" | "?" | "=" + //// | "{" | "}" | SP | HT + //// quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + //// qdtext = > + //// TEXT = + //// quoted-pair = "\" CHAR + + StringBuilder parameterValue = new StringBuilder(); + + // Check if the value is quoted. + isQuotedString = false; + if (textIndex < headerText.Length) + { + if (headerText[textIndex] == '\"') + { + textIndex++; + isQuotedString = true; + } + } + + char currentChar = default(char); + while (textIndex < headerText.Length) + { + currentChar = headerText[textIndex]; + + if (currentChar == '\\' || currentChar == '\"') + { + if (!isQuotedString) + { + throw createException(Strings.HttpUtils_EscapeCharWithoutQuotes(headerName, headerText, textIndex, currentChar)); + } + + textIndex++; + + // End of quoted parameter value. + if (currentChar == '\"') + { + break; + } + + if (textIndex >= headerText.Length) + { + throw createException(Strings.HttpUtils_EscapeCharAtEnd(headerName, headerText, textIndex, currentChar)); + } + + currentChar = headerText[textIndex]; + } + else + { + if (!isQuotedString && !IsHttpToken(currentChar)) + { + // If the given character is special, we stop processing. + break; + } + + if (isQuotedString && !IsValidInQuotedHeaderValue(currentChar)) + { + throw createException(Strings.HttpUtils_InvalidCharacterInQuotedParameterValue(headerName, headerText, textIndex, currentChar)); + } + } + + parameterValue.Append(currentChar); + textIndex++; + } + + if (isQuotedString && currentChar != '\"') + { + throw createException(Strings.HttpUtils_ClosingQuoteNotFound(headerName, headerText, textIndex)); + } + + return parameterValue.ToString(); + } + + /// + /// Skips whitespace in the specified text by advancing an index to + /// the next non-whitespace character. + /// + /// Text to scan. + /// Index to begin scanning from. + /// true if the end of the string was reached, false otherwise. + internal static bool SkipWhitespace(string text, ref int textIndex) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(text.Length >= 0, "text >= 0"); + Debug.Assert(textIndex <= text.Length, "text <= text.Length"); + + while (textIndex < text.Length && Char.IsWhiteSpace(text, textIndex)) + { + textIndex++; + } + + return (textIndex == text.Length); + } + + /// + /// Enumerates each charset part in the specified Accept-Charset header. + /// + /// Non-null and non-empty header value for Accept-Charset. + /// + /// A (non-sorted) enumeration of CharsetPart elements, which include + /// a charset name and a quality (preference) value, normalized to 0-1000. + /// + private static IEnumerable AcceptCharsetParts(string headerValue) + { + Debug.Assert(!String.IsNullOrEmpty(headerValue), "!String.IsNullOrEmpty(headerValuer)"); + + // PERF: optimize for common patterns. + bool commaRequired = false; // Whether a comma should be found + int headerIndex = 0; // Index of character being procesed on headerValue. + int headerStart; // Index into headerValue for the start of the charset name. + int headerNameEnd; // Index into headerValue for the end of the charset name (+1). + int headerEnd; // Index into headerValue for this charset part (+1). + int qualityValue; // Normalized qvalue for this charset. + + while (headerIndex < headerValue.Length) + { + if (SkipWhitespace(headerValue, ref headerIndex)) + { + yield break; + } + + if (headerValue[headerIndex] == ',') + { + commaRequired = false; + headerIndex++; + continue; + } + + if (commaRequired) + { + // Comma missing between charset elements. + throw new ODataContentTypeException(Strings.HttpUtils_MissingSeparatorBetweenCharsets(headerValue)); + } + + headerStart = headerIndex; + headerNameEnd = headerStart; + + bool endReached = ReadToken(headerValue, ref headerNameEnd); + if (headerNameEnd == headerIndex) + { + // Invalid (empty) charset name. + throw new ODataContentTypeException(Strings.HttpUtils_InvalidCharsetName(headerValue)); + } + + if (endReached) + { + qualityValue = 1000; + headerEnd = headerNameEnd; + } + else + { + char afterNameChar = headerValue[headerNameEnd]; + if (IsHttpSeparator(afterNameChar)) + { + if (afterNameChar == ';') + { + if (ReadLiteral(headerValue, headerNameEnd, ";q=")) + { + // Unexpected end of qvalue. + throw new ODataContentTypeException(Strings.HttpUtils_UnexpectedEndOfQValue(headerValue)); + } + + headerEnd = headerNameEnd + 3; + ReadQualityValue(headerValue, ref headerEnd, out qualityValue); + } + else + { + qualityValue = 1000; + headerEnd = headerNameEnd; + } + } + else + { + // Invalid separator character. + throw new ODataContentTypeException(Strings.HttpUtils_InvalidSeparatorBetweenCharsets(headerValue)); + } + } + + yield return new CharsetPart(headerValue.Substring(headerStart, headerNameEnd - headerStart), qualityValue); + + // Prepare for next charset; we require at least one comma before we process it. + commaRequired = true; + headerIndex = headerEnd; + } + } + + /// Reads a media type definition as used in a Content-Type header. + /// Text to read. + /// A list of key/value pairs representing the s and their (optional) 'charset' parameters + /// parsed from the specified + private static IList> ReadMediaTypes(string text) + { + Debug.Assert(text != null, "text != null"); + + List> parameters = null; + List> mediaTypes = new List>(); + int textIndex = 0; + + while (!SkipWhitespace(text, ref textIndex)) + { + string type; + string subType; + ReadMediaTypeAndSubtype(text, ref textIndex, out type, out subType); + + string charset = null; + while (!SkipWhitespace(text, ref textIndex)) + { + if (text[textIndex] == ',') + { + textIndex++; + break; + } + + if (text[textIndex] != ';') + { + throw new ODataContentTypeException(Strings.HttpUtils_MediaTypeRequiresSemicolonBeforeParameter(text)); + } + + textIndex++; + if (SkipWhitespace(text, ref textIndex)) + { + // ';' should be a leading separator, but we choose to be a + // bit permissive and allow it as a final delimiter as well. + break; + } + + ReadMediaTypeParameter(text, ref textIndex, ref parameters, ref charset); + } + + mediaTypes.Add( + new KeyValuePair( + new ODataMediaType(type, subType, parameters), + charset)); + parameters = null; + } + + return mediaTypes; + } + + /// Read a parameter for a media type/range. + /// Text to read from. + /// Pointer in text. + /// Array with parameters to grow as necessary. + /// The (optional) charset parameter value. + private static void ReadMediaTypeParameter(string text, ref int textIndex, ref List> parameters, ref string charset) + { + int startIndex = textIndex; + bool eof = ReadToken(text, ref textIndex); + string parameterName = text.Substring(startIndex, textIndex - startIndex); + + if (parameterName.Length == 0) + { + throw new ODataContentTypeException(Strings.HttpUtils_MediaTypeMissingParameterName); + } + + if (eof) + { + throw new ODataContentTypeException(Strings.HttpUtils_MediaTypeMissingParameterValue(parameterName)); + } + + if (text[textIndex] != '=') + { + throw new ODataContentTypeException(Strings.HttpUtils_MediaTypeMissingParameterValue(parameterName)); + } + + textIndex++; + + bool isQuotedString; + string parameterValue = ReadTokenOrQuotedStringValue(ODataConstants.ContentTypeHeader, text, ref textIndex, out isQuotedString, message => new ODataContentTypeException(message)); + + if (CompareMediaTypeParameterNames(ODataConstants.Charset, parameterName)) + { + charset = parameterValue; + } + else + { + // Add the parameter name/value pair to the list. + if (parameters == null) + { + parameters = new List>(1); + } + + parameters.Add(new KeyValuePair(parameterName, parameterValue)); + } + } + + /// Reads the type and subtype specifications for a media type name. + /// Text in which specification exists. + /// Pointer into text. + /// Type of media found. + /// Subtype of media found. + private static void ReadMediaTypeAndSubtype(string mediaTypeName, ref int textIndex, out string type, out string subType) + { + Debug.Assert(mediaTypeName != null, "text != null"); + int textStart = textIndex; + if (ReadToken(mediaTypeName, ref textIndex)) + { + throw new ODataContentTypeException(Strings.HttpUtils_MediaTypeUnspecified(mediaTypeName)); + } + + if (mediaTypeName[textIndex] != '/') + { + throw new ODataContentTypeException(Strings.HttpUtils_MediaTypeRequiresSlash(mediaTypeName)); + } + + type = mediaTypeName.Substring(textStart, textIndex - textStart); + textIndex++; + + int subTypeStart = textIndex; + ReadToken(mediaTypeName, ref textIndex); + + if (textIndex == subTypeStart) + { + throw new ODataContentTypeException(Strings.HttpUtils_MediaTypeRequiresSubType(mediaTypeName)); + } + + subType = mediaTypeName.Substring(subTypeStart, textIndex - subTypeStart); + } + + /// + /// Determines whether the specified character is a valid HTTP header token character. + /// + /// Character to verify. + /// true if c is a valid HTTP header token character; false otherwise. + private static bool IsHttpToken(char c) + { + // A token character is any character (0-127) except control (0-31) or + // separators. 127 is DEL, a control character. + return c < '\x7F' && c > '\x1F' && !IsHttpSeparator(c); + } + + /// + /// Determines whether the specified character is valid in the quoted header values. + /// + /// Character to verify. + /// true if c is a valid in a quoted HTTP header value; false otherwise. + private static bool IsValidInQuotedHeaderValue(char c) + { + Debug.Assert(c != '\\' && c != '\"', "Control characters should have been handled already."); + + // Any non-control OCTET, including space and tab but excluding \". Control characters are 0 - 31 and 127. + // NOTE: we do not check for [CRLF] since we do not support multi-line HTTP headers. + // Batch reader handling of multiline headers + // ODataLib batch writer support for multiline headers in operations + int intValueOfCharacter = (int)c; + if (intValueOfCharacter < 32 && c != ' ' && c != '\t' || intValueOfCharacter == 127) + { + return false; + } + + return true; + } + + /// + /// Determines whether the specified character is a valid HTTP separator. + /// + /// Character to verify. + /// true if c is a separator; false otherwise. + /// + /// See RFC 2616 2.2 for further information. + /// + private static bool IsHttpSeparator(char c) + { + return + c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || + c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' || + c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || + c == '{' || c == '}' || c == ' ' || c == '\x9'; + } + + /// + /// Reads a token on the specified text by advancing an index on it. + /// + /// Text to read token from. + /// Index for the position being scanned on text. + /// true if the end of the text was reached; false otherwise. + private static bool ReadToken(string text, ref int textIndex) + { + while (textIndex < text.Length && IsHttpToken(text[textIndex])) + { + textIndex++; + } + + return (textIndex == text.Length); + } + + /// + /// Converts the specified character from the ASCII range to a digit. + /// + /// Character to convert. + /// + /// The Int32 value for c, or -1 if it is an element separator. + /// + private static int DigitToInt32(char c) + { + if (c >= '0' && c <= '9') + { + return c - '0'; + } + + if (IsHttpElementSeparator(c)) + { + return -1; + } + + throw new ODataException(Strings.HttpUtils_CannotConvertCharToInt(c)); + } + + /// + /// Verfies whether the specified character is a valid separator in + /// an HTTP header list of element. + /// + /// Character to verify. + /// true if c is a valid character for separating elements; false otherwise. + private static bool IsHttpElementSeparator(char c) + { + return c == ',' || c == ' ' || c == '\t'; + } + + /// + /// "Reads" a literal from the specified string by verifying that + /// the exact text can be found at the specified position. + /// + /// Text within which a literal should be checked. + /// Index in text where the literal should be found. + /// Literal to check at the specified position. + /// true if the end of string is found; false otherwise. + private static bool ReadLiteral(string text, int textIndex, string literal) + { + if (String.Compare(text, textIndex, literal, 0, literal.Length, StringComparison.Ordinal) != 0) + { + // Failed to find expected literal. + throw new ODataException(Strings.HttpUtils_ExpectedLiteralNotFoundInString(literal, textIndex, text)); + } + + return textIndex + literal.Length == text.Length; + } + + /// + /// Structure to represent a charset name with a quality value. + /// + private struct CharsetPart + { + /// Name of the charset. + internal readonly string Charset; + + /// Charset quality (desirability), normalized to 0-1000. + internal readonly int Quality; + + /// + /// Initializes a new CharsetPart with the specified values. + /// + /// Name of charset. + /// Charset quality (desirability), normalized to 0-1000. + internal CharsetPart(string charset, int quality) + { + Debug.Assert(charset != null, "charset != null"); + Debug.Assert(charset.Length > 0, "charset.Length > 0"); + Debug.Assert(0 <= quality && quality <= 1000, "0 <= quality && quality <= 1000"); + + this.Charset = charset; + this.Quality = quality; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerBuilder.cs new file mode 100644 index 0000000..91e0f58 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerBuilder.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData +{ + /// + /// An interface that decouples ODataLib from any implementation of dependency injection container. + /// + public interface IContainerBuilder + { + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The implementation type of the service. + /// The instance itself. + IContainerBuilder AddService( + ServiceLifetime lifetime, + Type serviceType, + Type implementationType); + + /// + /// Adds a service of with an . + /// + /// The lifetime of the service to register. + /// The type of the service to register. + /// The factory that creates the service. + /// The instance itself. + IContainerBuilder AddService( + ServiceLifetime lifetime, + Type serviceType, + Func implementationFactory); + + /// + /// Builds a container which implements and contains + /// all the services registered. + /// + /// The container built by this builder. + IServiceProvider BuildContainer(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerProvider.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerProvider.cs new file mode 100644 index 0000000..04e9a9b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IContainerProvider.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData +{ + /// + /// An interface that provides a dependency injection container. + /// +#if ORCAS + internal interface IContainerProvider +#else + public interface IContainerProvider +#endif + { + /// + /// Gets a container which implements and contains + /// all the services registered. + /// + IServiceProvider Container { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IDuplicatePropertyNameChecker.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IDuplicatePropertyNameChecker.cs new file mode 100644 index 0000000..fc70c1d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IDuplicatePropertyNameChecker.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Validates that + /// 1) No duplicate property. + /// 2) No duplicate "@odata.associationLink" on a property. + /// 3) "@odata.associationLink"s are put on navigation properties. + /// + internal interface IDuplicatePropertyNameChecker + { + /// + /// Validates property uniqueness. + /// + /// The property. + void ValidatePropertyUniqueness(ODataPropertyInfo property); + + /// + /// Validates property uniqueness. + /// + /// The property. + void ValidatePropertyUniqueness(ODataNestedResourceInfo property); + + /// + /// Validates that "@odata.associationLink" is put on a navigation property, + /// and that no duplicate exists. + /// + /// Name of the property. + void ValidatePropertyOpenForAssociationLink(string propertyName); + + /// + /// Resets to initial state for reuse. + /// + void Reset(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataOutputInStreamErrorListener.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataOutputInStreamErrorListener.cs new file mode 100644 index 0000000..be68d70 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataOutputInStreamErrorListener.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// An interface that allows the implementations of the writers to get notified if an in-stream error is to be written. + /// + internal interface IODataOutputInStreamErrorListener + { + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + void OnInStreamError(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataPayloadUriConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataPayloadUriConverter.cs new file mode 100644 index 0000000..04dc4f9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataPayloadUriConverter.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + #endregion + + /// Supports custom conversion of URLs found in the payload. + /// + /// This interface can be implemented on messages (see and + /// ). When a message implementing this interface is + /// passed to an or , the + /// message writer/reader will use this interface for custom URL conversion. + /// On writers this means that whenever a URI is written into the payload the conversion + /// method on this interface is called to convert a base URI and a payload URI to the + /// actual URI to be written to the payload. If the method returns null from a conversion + /// call the default conversion will be used. + /// On readers this means that a base URI (either from the payload or the reader settings) and + /// the URI read from the payload are passed to the method. The result is what is being reported + /// on the OData OM instances. Again if the conversion method returns null the default conversion + /// kicks in. + /// + public interface IODataPayloadUriConverter + { + /// Implements a custom URL conversion scheme. This method returns null if no custom conversion is desired. If the method returns a non-null URL that value will be used without further validation. + /// An instance that reflects the custom conversion of the method arguments into a URL or null if no custom conversion is desired; in that case the default conversion is used. + /// The (optional) base URI to use for the conversion. + /// The URI read from the payload. + Uri ConvertPayloadUri(Uri baseUri, Uri payloadUri); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataReaderWriterListener.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataReaderWriterListener.cs new file mode 100644 index 0000000..63890bf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataReaderWriterListener.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// An interface that allows the creator of a reader/writer to listen for status changes of the created reader/writer. + /// + internal interface IODataReaderWriterListener + { + /// + /// This method notifies the implementer of this interface that the created reader is in Exception state. + /// + void OnException(); + + /// + /// This method notifies the implementer of this interface that the created reader is in Completed state. + /// + void OnCompleted(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessage.cs new file mode 100644 index 0000000..1c30d2a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessage.cs @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + #endregion Namespaces + + /// + /// Interface for synchronous OData request messages. + /// + public interface IODataRequestMessage + { + /// Gets an enumerable over all the headers for this message. + /// An enumerable over all the headers for this message. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Users will never have to instantiate these; the rule does not apply.")] + IEnumerable> Headers + { + // TODO: do we want to impose a certain order of the headers? + get; + } + + /// Gets or sets the request URL for this request message. + /// The request URL for this request message. + Uri Url + { + get; + set; + } + + /// Gets or sets the HTTP method used for this request message. + /// The HTTP method used for this request message. + string Method + { + get; + set; + } + + /// Returns a value of an HTTP header. + /// The value of the HTTP header, or null if no such header was present on the message. + /// The name of the header to get. + string GetHeader(string headerName); + + /// Sets the value of an HTTP header. + /// The name of the header to set. + /// The value of the HTTP header or 'null' if the header should be removed. + void SetHeader(string headerName, string headerValue); + + /// Gets the stream backing for this message. + /// The stream backing for this message. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is intentionally a method.")] + Stream GetStream(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessageAsync.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessageAsync.cs new file mode 100644 index 0000000..1c45d38 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataRequestMessageAsync.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if PORTABLELIB +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Threading.Tasks; + #endregion Namespaces + + /// + /// Interface for asynchronous OData request messages. + /// + public interface IODataRequestMessageAsync : IODataRequestMessage + { + /// Asynchronously get the stream backing for this message. + /// The stream for this message. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is intentionally a method.")] + Task GetStreamAsync(); + } +} +#endif diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResourceTypeContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResourceTypeContext.cs new file mode 100644 index 0000000..f4d45ab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResourceTypeContext.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using Microsoft.OData.Edm; + + /// + /// Interface used for substitutability, to answer basic questions regarding the type of the resource or resource set. + /// Metadata may come from a user-provided model or from the SetSerializationInfo() method on a resource set or resource. The latter is considered the "no-model" case since only strings + /// are provided, and there is no interconnectedness. The goal of this interface is to provide a way to query the metadata information available on a resource or resource set without + /// needing to know where the metadata originated from. + /// + internal interface IODataResourceTypeContext + { + /// + /// The navigation source name of the resource set or resource. + /// + string NavigationSourceName { get; } + + /// + /// The entity type name of the navigation source of the resource set or resource. + /// + string NavigationSourceEntityTypeName { get; } + + /// + /// The full type name of the navigation source of the resource set or resource. + /// + string NavigationSourceFullTypeName { get; } + + /// + /// The kind of the navigation source of the resource set or resource. + /// + EdmNavigationSourceKind NavigationSourceKind { get; } + + /// + /// The expected entity type name of the resource. + /// For example, in the request URI 'http://example.com/Service.svc/People/Namespace.VIP_Person', the expected entity type is Namespace.VIP_Person. + /// (The entity set element type name in this example may be Person, and the actual entity type of a particular entity might be a type more derived than VIP_Person) + /// + string ExpectedResourceTypeName { get; } + + /// + /// true if the resource is an MLE, false otherwise. + /// + bool IsMediaLinkEntry { get; } + + /// + /// The flag we use to identify if the current resource is from a navigation property with collection type or not. + /// + bool IsFromCollection { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessage.cs new file mode 100644 index 0000000..2867e04 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessage.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + #endregion Namespaces + + /// + /// Interface for synchronous OData response messages. + /// + public interface IODataResponseMessage + { + /// Gets an enumerable over all the headers for this message. + /// An enumerable over all the headers for this message. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", + Justification = "Users will never have to instantiate these; the rule does not apply.")] + IEnumerable> Headers + { + // TODO: do we want to impose a certain order of the headers? + get; + } + + /// Gets or sets the result status code of the response message. + /// The result status code of the response message. + int StatusCode + { + get; + set; + } + + /// Returns a value of an HTTP header. + /// The value of the HTTP header, or null if no such header was present on the message. + /// The name of the header to get. + string GetHeader(string headerName); + + /// Sets the value of an HTTP header. + /// The name of the header to set. + /// The value of the HTTP header or 'null' if the header should be removed. + void SetHeader(string headerName, string headerValue); + + /// Gets the stream backing for this message. + /// The stream backing for this message. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is intentionally a method.")] + Stream GetStream(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessageAsync.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessageAsync.cs new file mode 100644 index 0000000..95a2727 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataResponseMessageAsync.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if PORTABLELIB +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Threading.Tasks; + #endregion Namespaces + + /// + /// Interface for asynchronous OData response messages. + /// + public interface IODataResponseMessageAsync : IODataResponseMessage + { + /// Asynchronously get the stream backing for this message. + /// The stream backing for this message. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is intentionally a method.")] + Task GetStreamAsync(); + } +} +#endif diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamListener.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamListener.cs new file mode 100644 index 0000000..14c9251 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamListener.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion + + /// + /// An interface that allows creators of a stream to listen for status changes + /// of the operation stream. + /// + internal interface IODataStreamListener + { + /// + /// This method notifies the implementer of this interface that the content stream for a batch operation has been requested. + /// + void StreamRequested(); + +#if PORTABLELIB + /// + /// This method notifies the implementer of this interface that the content stream for a batch operation has been requested. + /// + /// + /// A task representing any async operation that is running in reaction to the + /// status change (or null if no such action is required). + /// + Task StreamRequestedAsync(); +#endif + + /// + /// This method notifies the implementer of this interface that the content stream of a batch operation has been disposed. + /// + void StreamDisposed(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamReferenceInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamReferenceInfo.cs new file mode 100644 index 0000000..5510c30 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IODataStreamReferenceInfo.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + internal interface IODataStreamReferenceInfo + { + Uri EditLink { get; set; } + + Uri ReadLink { get; set; } + + string ContentType { get; set; } + + string ETag { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IPrimitiveTypeConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IPrimitiveTypeConverter.cs new file mode 100644 index 0000000..04d786e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IPrimitiveTypeConverter.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System.IO; + using System.Xml; + using Microsoft.OData.Json; + #endregion + + /// + /// Interface used for serialization and deserialization of primitive types. + /// + internal interface IPrimitiveTypeConverter + { + /// + /// Write the Atom representation of an instance of a primitive type to an XmlWriter. + /// + /// The instance to write. + /// The Xml writer to use to write the instance. + void WriteAtom(object instance, XmlWriter writer); + + /// + /// Write the Atom representation of an instance of a primitive type to an TextWriter. + /// + /// The instance to write. + /// The text writer to use to write the instance. + void WriteAtom(object instance, TextWriter writer); + + /// + /// Write the Json Lite representation of an instance of a primitive type to a json writer. + /// + /// The instance to write. + /// Instance of JsonWriter. + void WriteJsonLight(object instance, IJsonWriter jsonWriter); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IReaderValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IReaderValidator.cs new file mode 100644 index 0000000..94947ea --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IReaderValidator.cs @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using Microsoft.OData.Edm; + using Microsoft.OData.JsonLight; + + /// + /// Reader validator interface. + /// + internal interface IReaderValidator + { + /// + /// Validates that the specified is a valid resource as per the specified type. + /// + /// The resource to validate. + /// Optional entity type to validate the resource against. + /// If the is available only resource-level tests are performed, + /// properties and such are not validated. + void ValidateMediaResource(ODataResourceBase resource, IEdmEntityType resourceType); + + /// + /// Creates a PropertyAndAnnotationCollector instance. + /// + /// The created instance. + PropertyAndAnnotationCollector CreatePropertyAndAnnotationCollector(); + + /// + /// Validate a null value. + /// + /// The expected type of the null value. + /// true to validate the null value; false to only check whether the type is + /// supported. + /// The name of the property whose value is being read, if applicable + /// (used for error reporting). + /// Indicates whether the property is dynamic or unknown. + void ValidateNullValue(IEdmTypeReference expectedTypeReference, + bool validateNullValue, string propertyName, + bool? isDynamicProperty); + + /// + /// Resolves and validates the payload type against the expected type and returns the target type. + /// + /// The expected type kind for the value. + /// This value indicates if a structured type is expected to be return. + /// True for structured type, false for non-structured type, null for indetermination. + /// The default payload type if none is specified in the payload; + /// for ATOM this is Edm.String, for JSON it is null since there is no payload type name for primitive types in the payload. + /// The expected type reference, or null if no expected type is available. + /// The payload type name, or null if no payload type was specified. + /// The model to use. + /// A func to compute the type kind from the payload shape if it could not be determined from the expected type or the payload type. + /// The target type kind to be used to read the payload. + /// Potentially non-null instance of an annotation to put on the value reported from the reader. + /// + /// The target type reference to use for parsing the value. + /// If there is no user specified model, this will return null. + /// If there is a user specified model, this method never returns null. + /// + /// + /// This method cannot be used for primitive type resolution. Primitive type resolution is format dependent and format specific methods should be used instead. + /// + IEdmTypeReference ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind expectedTypeKind, + bool? expectStructuredType, + IEdmType defaultPrimitivePayloadType, + IEdmTypeReference expectedTypeReference, + string payloadTypeName, + IEdmModel model, + Func typeKindFromPayloadFunc, + out EdmTypeKind targetTypeKind, + out ODataTypeAnnotation typeAnnotation); + + /// + /// Validates that a property with the specified name exists on a given structured type. + /// The structured type can be null if no metadata is available. + /// + /// The name of the property to validate. + /// The owning type of the property with name + /// or null if no metadata is available. + /// The instance representing the property with name + /// or null if no metadata is available. + IEdmProperty ValidatePropertyDefined(string propertyName, + IEdmStructuredType owningStructuredType); + + /// + /// Validates a stream reference property. + /// + /// The stream property to check. + /// The name of the property being validated. + /// The owning type of the stream property or null if no metadata is available. + /// The stream property defined by the model. + void ValidateStreamReferenceProperty(IODataStreamReferenceInfo streamInfo, + string propertyName, + IEdmStructuredType structuredType, + IEdmProperty streamEdmProperty); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/IWriterValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IWriterValidator.cs new file mode 100644 index 0000000..a9afba3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/IWriterValidator.cs @@ -0,0 +1,139 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using Microsoft.OData.Edm; + + /// + /// Writer validator interface. + /// + internal interface IWriterValidator + { + /// + /// Creates a DuplicatePropertyNameChecker instance. + /// + /// The created instance. + IDuplicatePropertyNameChecker CreateDuplicatePropertyNameChecker(); + + /// + /// Validates a resource in an expanded link to make sure that the types match. + /// + /// The of the resource. + /// The type of the parent navigation property or + /// complex property or complex collection property. + void ValidateResourceInNestedResourceInfo(IEdmStructuredType resourceType, + IEdmStructuredType parentNavigationPropertyType); + + /// + /// Validates that the specified nested resource info has cardinality set, i.e., it has the + /// IsCollection value set. + /// + /// The nested resource info to validate. + void ValidateNestedResourceInfoHasCardinality(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Validates that a given primitive value is of the expected (primitive) type. + /// + /// The value to check. + /// The primitive type reference for the value. + /// So some callers have this already, and we save the lookup here. + /// The expected type for the value. + /// + /// Some callers have the primitive type reference already resolved (from the value type), + /// and the method will not lookup the primitive type reference again. + /// + void ValidateIsExpectedPrimitiveType(object value, + IEdmPrimitiveTypeReference valuePrimitiveTypeReference, + IEdmTypeReference expectedTypeReference); + + /// + /// Validates the value type reference against metadata. + /// + /// The metadata type reference. + /// The value type reference. + void ValidateTypeReference(IEdmTypeReference typeReferenceFromMetadata, + IEdmTypeReference typeReferenceFromValue); + + /// + /// Validates that the observed type kind is the expected type kind. + /// + /// The actual type kind. + /// The expected type kind. + /// This value indicates if the is expected to be complex or entity. + /// True for complex or entity, false for non-structured type kind, null for indetermination. + /// The edm type to use in the error. + void ValidateTypeKind(EdmTypeKind actualTypeKind, + EdmTypeKind expectedTypeKind, + bool? expectStructuredType, + IEdmType edmType); + + /// + /// Validates that the specified is a valid resource as per the + /// specified type. + /// + /// The resource to validate. + /// Optional entity type to validate the resource against. + /// + /// If the is available, only resource-level tests are + /// performed; properties and such are not validated. + /// + void ValidateMetadataResource(ODataResourceBase resource, IEdmEntityType resourceType); + + /// + /// Validates that the expected property allows null value. + /// + /// The expected property type or null if we + /// don't have any. + /// The name of the property. + /// true if the property is top-level. + /// The model used to get the OData version. + void ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, + string propertyName, bool isTopLevel, IEdmModel model); + + /// + /// Validates a null collection item against the expected type. + /// + /// The expected item type or null if none. + void ValidateNullCollectionItem(IEdmTypeReference expectedItemType); + + /// + /// Validates that a property with the specified name exists on a given structured type. + /// The structured type can be null if no metadata is available. + /// + /// Name of the property. + /// Hosting type of the property or null if no metadata is + /// available. + /// An instance representing the specified property or + /// null if no metadata is available. + IEdmProperty ValidatePropertyDefined(string propertyName, + IEdmStructuredType owningStructuredType); + + /// + /// Validates an to ensure all required information is specified and valid. + /// + /// The nested resource info to validate. + /// The declaring the structural property or navigation property; or null if metadata is not available. + /// The of the expanded content of this nested resource info or null for deferred links. + /// The type of the navigation property for this nested resource info; or null if no was specified. + IEdmNavigationProperty ValidateNestedResourceInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmStructuredType declaringStructuredType, + ODataPayloadKind? expandedPayloadKind); + + /// + /// Validates the input meets the derived type constaints on the . + /// + /// The input resource type. + /// The type from metadata. + /// The derived type constraints on the nested resource. + /// The item kind. + /// The item name. + void ValidateDerivedTypeConstraint(IEdmStructuredType resourceType, + IEdmStructuredType metadataType, IEnumerable derivedTypeConstraints, string itemKind, string itemName); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/InstanceAnnotationWriteTracker.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/InstanceAnnotationWriteTracker.cs new file mode 100644 index 0000000..940ceeb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/InstanceAnnotationWriteTracker.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Collections.Generic; + + /// + /// Helper class to track if an annotation has been written. + /// + internal sealed class InstanceAnnotationWriteTracker + { + /// + /// Maintains the write status for each annotation using its key. + /// If a key exists in the list then it is considered written. + /// + private readonly HashSet writeStatus; + + /// + /// Creates a new to hold write status for instance annotations. + /// + public InstanceAnnotationWriteTracker() + { + this.writeStatus = new HashSet(StringComparer.Ordinal); + } + + /// + /// Check if an annotation is already written. + /// + /// true if the annotation is written; otherwise false. + /// The key of the element to check if its written. + public bool IsAnnotationWritten(string key) + { + return this.writeStatus.Contains(key); + } + + /// + /// Mark an annotation as written. + /// + /// true if the annotation was unmarked before; otherwise false. + /// The key of the element to mark as written. + public bool MarkAnnotationWritten(string key) + { + return this.writeStatus.Add(key); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodes.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodes.cs new file mode 100644 index 0000000..92a03c8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodes.cs @@ -0,0 +1,215 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// An enumeration that lists the internal errors. + /// + internal enum InternalErrorCodes + { + /// Unreachable codepath in ODataWriterCore.WriteEnd + ODataWriterCore_WriteEnd_UnreachableCodePath, + + /// Unreachable codepath in ODataWriterCore.ValidateTransition + ODataWriterCore_ValidateTransition_UnreachableCodePath, + + /// Unreachable codepath in ODataWriterCore.Scope.Create + ODataWriterCore_Scope_Create_UnreachableCodePath, + + /// Unreachable codepath in ODataWriterCore.PropertyAndAnnotationCollector. + ODataWriterCore_PropertyAndAnnotationCollector, + + /// Unreachable codepath in ODataWriterCore.ParentNestedResourceInfoScope. + ODataWriterCore_ParentNestedResourceInfoScope, + + /// Unreachable codepath in ODataUtils.VersionString + ODataUtils_VersionString_UnreachableCodePath, + + /// Unreachable codepath in ODataUtilsInternal.IsPayloadKindSupported + ODataUtilsInternal_IsPayloadKindSupported_UnreachableCodePath, + + /// Unreachable codepath in ODataUtils.GetDefaultEncoding + ODataUtils_GetDefaultEncoding_UnreachableCodePath, + + /// Unreachable codepath in ODataMessageWriter.WriteProperty + ODataMessageWriter_WriteProperty, + + /// Unreachable codepath in ODataMessageWriter.WriteEntityReferenceLink + ODataMessageWriter_WriteEntityReferenceLink, + + /// Unreachable codepath in ODataMessageWriter.WriteEntityReferenceLinks + ODataMessageWriter_WriteEntityReferenceLinks, + + /// Unreachable codepath in ODataMessageWriter.WriteError + ODataMessageWriter_WriteError, + + /// Unreachable codepath in ODataMessageWriter.WriteServiceDocument + ODataMessageWriter_WriteServiceDocument, + + /// Unreachable codepath in ODataMessageWriter.WriteMetadataDocument + ODataMessageWriter_WriteMetadataDocument, + + /// Unreachable codepath in ODataCollectionWriter.CreateCollectionWriter + ODataCollectionWriter_CreateCollectionWriter_UnreachableCodePath, + + /// Unreachable codepath in ODataCollectionWriterCore.ValidateTransition + ODataCollectionWriterCore_ValidateTransition_UnreachableCodePath, + + /// Unreachable codepath in ODataCollectionWriterCore.WriteEnd + ODataCollectionWriterCore_WriteEnd_UnreachableCodePath, + + /// Unreachable codepath in ODataParameterWriter.CreateParameterWriter + ODataParameterWriter_CannotCreateParameterWriterForFormat, + + /// Unreachable codepath in ODataParameterWriter.ValidateTransition + ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromStart, + + /// Unreachable codepath in ODataParameterWriter.ValidateTransition + ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromCanWriteParameter, + + /// Unreachable codepath in ODataParameterWriter.ValidateTransition + ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromActiveSubWriter, + + /// Unreachable codepath in ODataParameterWriter.ValidateTransition + ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromCompleted, + + /// Unreachable codepath in ODataParameterWriter.ValidateTransition + ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromError, + + /// Unreachable codepath in ODataParameterWriter.ValidateTransition + ODataParameterWriterCore_ValidateTransition_UnreachableCodePath, + + /// Unreachable codepath in ODataParameterWriter.WriteEndImplementation + ODataParameterWriterCore_WriteEndImplementation_UnreachableCodePath, + + /// Unreachable codepath in ODataPathValidator.ValidateSegment root branch + QueryPathValidator_ValidateSegment_Root, + + /// Unreachable codepath in ODataPathValidator.ValidateSegment non-root branch + QueryPathValidator_ValidateSegment_NonRoot, + + /// Unreachable codepath in ODataBatchWriter.ValidateTransition + ODataBatchWriter_ValidateTransition_UnreachableCodePath, + + /// Unreachable codepath in ODataBatchWriter.ToText(this HttpMethod). + ODataBatchWriterUtils_HttpMethod_ToText_UnreachableCodePath, + + /// Unreachable codepath in ODataBatchReader.ReadImplementation. + ODataBatchReader_ReadImplementation, + + /// Unreachable codepath in ODataBatchReader.GetEndBoundary in state Completed. + ODataBatchReader_GetEndBoundary_Completed, + + /// Unreachable codepath in ODataBatchReader.GetEndBoundary in state Exception. + ODataBatchReader_GetEndBoundary_Exception, + + /// Unreachable codepath in ODataBatchReader.GetEndBoundary because of invalid enum value. + ODataBatchReader_GetEndBoundary_UnknownValue, + + /// Unreachable codepath in ODataBatchReaderStream.SkipToBoundary. + ODataBatchReaderStream_SkipToBoundary, + + /// Unreachable codepath in ODataBatchReaderStream.ReadLine. + ODataBatchReaderStream_ReadLine, + + /// Unreachable codepath in ODataBatchReaderStream.ReadWithDelimiter. + ODataBatchReaderStream_ReadWithDelimiter, + + /// Unreachable codepath in ODataBatchReaderStreamBuffer.ScanForBoundary. + ODataBatchReaderStreamBuffer_ScanForBoundary, + + /// Unreachable codepath in ODataBatchReaderStreamBuffer.ReadWithLength. + ODataBatchReaderStreamBuffer_ReadWithLength, + + /// Unreachable codepath in JsonReader.Read. + JsonReader_Read, + + /// Unreachable codepath in ODataReader.CreateReader. + ODataReader_CreateReader_UnreachableCodePath, + + /// Unreachable codepath in ODataReaderCore.ReadImplementation. + ODataReaderCore_ReadImplementation, + + /// Unreachable codepath in ODataReaderCoreAsync.ReadAsynchronously. + ODataReaderCoreAsync_ReadAsynchronously, + + /// Unreachable codepath in ODataCollectionReader.CreateReader. + ODataCollectionReader_CreateReader_UnreachableCodePath, + + /// Unreachable codepath in ODataCollectionReaderCore.ReadImplementation. + ODataCollectionReaderCore_ReadImplementation, + + /// Unreachable codepath in ODataCollectionReaderCoreAsync.ReadAsynchronously. + ODataCollectionReaderCoreAsync_ReadAsynchronously, + + /// Unreachable codepath in ODataParameterReaderCore.ReadImplementation. + ODataParameterReaderCore_ReadImplementation, + + /// Unreachable codepath in ODataParameterReaderCoreAsync.ReadAsynchronously. + ODataParameterReaderCoreAsync_ReadAsynchronously, + + /// The value from the parameter reader must be a primitive value, or null + ODataParameterReaderCore_ValueMustBePrimitiveOrNull, + + /// Unreachable codepath in ODataRawValueUtils.ConvertStringToPrimitive. + ODataRawValueUtils_ConvertStringToPrimitive, + + /// Unreachable codepath in EdmCoreModel.PrimitiveType (unsupported type). + EdmCoreModel_PrimitiveType, + + /// Unreachable codepath in ReaderValidationUtils.ResolveAndValidateTypeName in the strict branch, unexpected type kind. + ReaderValidationUtils_ResolveAndValidateTypeName_Strict_TypeKind, + + /// Unreachable codepath in ReaderValidationUtils.ResolveAndValidateTypeName in the lax branch, unexpected type kind. + ReaderValidationUtils_ResolveAndValidateTypeName_Lax_TypeKind, + + /// The ODataMetadataFormat.CreateOutputContextAsync was called, but this method is not yet supported. + ODataMetadataFormat_CreateOutputContextAsync, + + /// The ODataMetadataFormat.CreateInputContextAsync was called, but this method is not yet supported. + ODataMetadataFormat_CreateInputContextAsync, + + /// An unsupported method or property has been called on the IDictionary implementation of the ODataModelFunctions. + ODataModelFunctions_UnsupportedMethodOrProperty, + + /// Unreachable codepath in ODataJsonLightPropertyAndValueDeserializer.ReadPropertyValue. + ODataJsonLightPropertyAndValueDeserializer_ReadPropertyValue, + + /// Unreachable codepath in ODataJsonLightPropertyAndValueDeserializer.GetNonEntityValueKind. + ODataJsonLightPropertyAndValueDeserializer_GetNonEntityValueKind, + + /// Unreachable codepath in ODataJsonLightReader.ReadResourceStart. + ODataJsonLightReader_ReadResourceStart, + + /// Unreachable codepath in ODataJsonLightResourceDeserializer.ReadTopLevelResourceSetAnnotations. + ODataJsonLightResourceDeserializer_ReadTopLevelResourceSetAnnotations, + + /// Unreachable codepath in ODataJsonLightCollectionDeserializer.ReadCollectionStart. + ODataJsonLightCollectionDeserializer_ReadCollectionStart, + + /// Unreachable codepath in ODataJsonLightCollectionDeserializer.ReadCollectionStart.TypeKindFromPayloadFunc. + ODataJsonLightCollectionDeserializer_ReadCollectionStart_TypeKindFromPayloadFunc, + + /// Unreachable codepath in ODataJsonLightCollectionDeserializer.ReadCollectionEnd. + ODataJsonLightCollectionDeserializer_ReadCollectionEnd, + + /// Unreachable codepath in ODataJsonLightEntityReferenceLinkDeserializer.ReadSingleEntityReferenceLink. + ODataJsonLightEntityReferenceLinkDeserializer_ReadSingleEntityReferenceLink, + + /// Unreachable codepath in ODataJsonLightEntityReferenceLinkDeserializer.ReadEntityReferenceLinksAnnotations. + ODataJsonLightEntityReferenceLinkDeserializer_ReadEntityReferenceLinksAnnotations, + + /// Unreachable codepath in ODataJsonLightParameterDeserializer.ReadNextParameter. + ODataJsonLightParameterDeserializer_ReadNextParameter, + + /// Unreachable codepath in EdmTypeWriterResolver.GetReturnType for operation import group. + EdmTypeWriterResolver_GetReturnTypeForOperationImportGroup, + + /// Unreachable codepath in the indexer of ODataVersionCache for unknown versions. + ODataVersionCache_UnknownVersion, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodesCommon.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodesCommon.cs new file mode 100644 index 0000000..c077dc9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/InternalErrorCodesCommon.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// An enumeration that lists the internal errors that are shared between the OData library and the query library. + /// + internal enum InternalErrorCodesCommon + { + /// Unreachable codepath in EdmLibraryExtensions.ToTypeReference (unsupported type kind). + EdmLibraryExtensions_ToTypeReference, + + /// Unreachable codepath in EdmLibraryExtensions.ToClrType (unsupported type kind). + EdmLibraryExtensions_ToClrType, + + /// Unreachable codepath in EdmLibraryExtensions.PrimitiveTypeReference (unsupported primitive type kind). + EdmLibraryExtensions_PrimitiveTypeReference, + + /// Unreachable codepath in EdmLibraryExtensions.IsAssignableFrom(IEdmPrimitiveType, IEdmPrimitiveType). + EdmLibraryExtensions_IsAssignableFrom_Primitive, + + /// Unreachable codepath in EdmLibraryExtensions.IsAssignableFrom(IEdmType, IEdmType). + EdmLibraryExtensions_IsAssignableFrom_Type, + + /// Unreachable codepath in EdmLibraryExtensions.BaseType. + EdmLibraryExtensions_BaseType, + + /// Unreachable codepath in EdmLibraryExtensions.Clone for unexpected type kind. + EdmLibraryExtensions_Clone_TypeKind, + + /// Unreachable codepath in EdmLibraryExtensions.Clone for unexpected primitive type kind. + EdmLibraryExtensions_Clone_PrimitiveTypeKind, + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs new file mode 100644 index 0000000..e1e55d4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/BufferingJsonReader.cs @@ -0,0 +1,1084 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + using System; + #region Namespaces + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; + using Microsoft.OData.JsonLight; + + #endregion Namespaces + + /// + /// Reader for the JSON format (http://www.json.org) that supports look-ahead. + /// + internal class BufferingJsonReader : IJsonStreamReader + { + /// The (possibly empty) list of buffered nodes. + /// This is a circular linked list where this field points to the first item of the list. + protected BufferedNode bufferedNodesHead; + + /// + /// A pointer into the bufferedNodes list to track the most recent position of the current buffered node. + /// + protected BufferedNode currentBufferedNode; + + /// + /// The inner JSON reader. + /// + private readonly IJsonReader innerReader; + + /// + /// The maximum number of recursive internalexception objects to allow when reading in-stream errors. + /// + private readonly int maxInnerErrorDepth; + + /// The name of the property that denotes an in-stream error. + private readonly string inStreamErrorPropertyName; + + /// A flag indicating whether the reader is in buffering mode or not. + private bool isBuffering; + + /// + /// A flag indicating that the last node for non-buffering read was taken from the buffer; we leave the + /// node in the buffer until the next Read call. + /// + private bool removeOnNextRead; + + /// + /// Debug flag to ensure we do not re-enter the instance while reading ahead and trying to parse an in-stream error. + /// + private bool parsingInStreamError; + + /// + /// true if the parser should check for in-stream errors whenever a start-object node is encountered; otherwise false. + /// This is set to false for parsing of top-level errors where we don't want the in-stream error detection code to kick in. + /// + private bool disableInStreamErrorDetection; + + /// + /// Constructor. + /// + /// The inner JSON reader. + /// The name of the property that denotes an in-stream error. + /// The maximum number of recursive internalexception objects to allow when reading in-stream errors. + internal BufferingJsonReader(IJsonReader innerReader, string inStreamErrorPropertyName, int maxInnerErrorDepth) + { + Debug.Assert(innerReader != null, "innerReader != null"); + + this.innerReader = innerReader; + this.inStreamErrorPropertyName = inStreamErrorPropertyName; + this.maxInnerErrorDepth = maxInnerErrorDepth; + this.bufferedNodesHead = null; + this.currentBufferedNode = null; + } + + /// + /// The type of the last node read. + /// + /// + /// Depending on whether buffering is on or off this will return the node type of the last + /// buffered read or the node type of the last unbuffered read. + /// + public JsonNodeType NodeType + { + get + { + if (this.bufferedNodesHead != null) + { + if (this.isBuffering) + { + Debug.Assert(this.currentBufferedNode != null, "this.currentBufferedNode != null"); + return this.currentBufferedNode.NodeType; + } + + // in non-buffering mode if we have buffered nodes satisfy the request from the first node there + return this.bufferedNodesHead.NodeType; + } + + return this.innerReader.NodeType; + } + } + + /// + /// The value of the last reported node. + /// + /// + /// Depending on whether buffering is on or off this will return the node type of the last + /// buffered read or the node type of the last unbuffered read. + /// + public object Value + { + get + { + if (this.bufferedNodesHead != null) + { + if (this.isBuffering) + { + Debug.Assert(this.currentBufferedNode != null, "this.currentBufferedNode != null"); + return this.currentBufferedNode.Value; + } + + // in non-buffering mode if we have buffered nodes satisfy the request from the first node there + return this.bufferedNodesHead.Value; + } + + return this.innerReader.Value; + } + } + + /// + /// if it is IEEE754 compatible + /// + public bool IsIeee754Compatible + { + get + { + return this.innerReader.IsIeee754Compatible; + } + } + + /// + /// true if the parser should check for in-stream errors whenever a start-object node is encountered; otherwise false. + /// This is set to false for parsing of top-level errors where we don't want the in-stream error detection code to kick in. + /// + internal bool DisableInStreamErrorDetection + { + get + { + return this.disableInStreamErrorDetection; + } + + set + { + this.disableInStreamErrorDetection = value; + } + } + + /// + /// Flag indicating whether buffering is on or off; debug-only for use in asserts. + /// + internal bool IsBuffering + { + get + { + return this.isBuffering; + } + } + + /// + /// Creates a stream for reading a stream value + /// + /// A Stream used to read a stream value + public virtual Stream CreateReadStream() + { + IJsonStreamReader streamReader = this.innerReader as IJsonStreamReader; + if (!this.isBuffering && streamReader != null) + { + return streamReader.CreateReadStream(); + } + + Stream result = new MemoryStream(this.Value == null ? new byte[0] : + Convert.FromBase64String((string)this.Value)); + this.innerReader.Read(); + return result; + } + + /// + /// Creates a TextReader for reading a text value. + /// + /// A TextReader for reading the text value. + public virtual TextReader CreateTextReader() + { + IJsonStreamReader streamReader = this.innerReader as IJsonStreamReader; + if (!this.isBuffering && streamReader != null) + { + return streamReader.CreateTextReader(); + } + + TextReader result = new StringReader(this.Value == null ? "" : (string)this.Value); + this.innerReader.Read(); + return result; + } + + /// + /// Whether or not the current value can be streamed + /// + /// True if the current value can be streamed, otherwise false + public virtual bool CanStream() + { + IJsonStreamReader streamReader = this.innerReader as IJsonStreamReader; + if (!this.isBuffering && streamReader != null) + { + return streamReader.CanStream(); + } + + return (this.Value is string || this.Value == null || this.NodeType == JsonNodeType.StartArray || this.NodeType == JsonNodeType.StartObject); + } + + /// + /// Reads the next node from the input. + /// + /// true if a new node was found, or false if end of input was reached. + public bool Read() + { + Debug.Assert(!this.parsingInStreamError, "!this.parsingInStreamError"); + + // read the next node and check whether it is an in-stream error + return this.ReadInternal(); + } + + /// + /// Puts the reader into the state where it buffers read nodes. + /// + internal void StartBuffering() + { + Debug.Assert(!this.isBuffering, "Buffering is already turned on. Must not call StartBuffering again."); + + if (this.bufferedNodesHead == null) + { + // capture the current state of the reader as the first item in the buffer (if there are none) + this.bufferedNodesHead = new BufferedNode(this.innerReader.NodeType, this.innerReader.Value); + } + else + { + this.removeOnNextRead = false; + } + + Debug.Assert(this.bufferedNodesHead != null, "Expected at least the current node in the buffer when starting buffering."); + + // Set the currentBufferedNode to the first node in the list; this means every time we start buffering we reset the + // position of the current buffered node since in general we don't know how far ahead we have read before and thus don't + // want to blindly continuing to read. The model is that with every call to StartBuffering you reset the position of the + // current node in the list and start reading through the buffer again. + if (this.currentBufferedNode == null) + { + this.currentBufferedNode = this.bufferedNodesHead; + } + + this.isBuffering = true; + } + + /// + /// Creates a bookmark at the current position of the reader. + /// + /// The bookmark object, it should be treated as a black box by the caller. + internal object BookmarkCurrentPosition() + { + Debug.Assert(this.isBuffering, "Bookmarks can only be create when in buffering mode."); + + return this.currentBufferedNode; + } + + /// + /// Moves the reader to the bookmarked position. + /// + /// The bookmark object to move to. + internal void MoveToBookmark(object bookmark) + { + Debug.Assert(this.isBuffering, "Bookmarks can only be used when in buffering mode."); + Debug.Assert(bookmark != null, "bookmark != null"); + + BufferedNode bookmarkNode = bookmark as BufferedNode; + Debug.Assert(bookmarkNode != null, "Invalid bookmark."); +#if DEBUG + Debug.Assert(this.NodeExistsInCurrentBuffer(bookmarkNode), "Tried to move to a bookmark which is already out of scope, bookmarks are only valid inside a single buffering session."); +#endif + + this.currentBufferedNode = bookmarkNode; + } + + /// + /// Puts the reader into the state where no buffering happen on read. + /// Either buffered nodes are consumed or new nodes are read (and not buffered). + /// + internal void StopBuffering() + { + Debug.Assert(this.isBuffering, "Buffering is not turned on. Must not call StopBuffering in this state."); + + // NOTE: by turning off buffering the reader is set to the first node in the buffer (if any) and will continue + // to read from there. removeOnNextRead is set to true since we captured the original state of the reader + // (before starting to buffer) as the first node in the buffer and that node has to be removed on the next read. + this.isBuffering = false; + this.removeOnNextRead = true; + + // We set the currentBufferedNode to null here to indicate that we want to reset the position of the current + // buffered node when we turn on buffering the next time. So far this (i.e., resetting the position of the buffered + // node) is the only mode the BufferingJsonReader supports. We can make resetting the current node position more explicit + // if needed. + this.currentBufferedNode = null; + } + + /// + /// A method to detect whether the current property value represents an in-stream error. + /// + /// The read from the payload. + /// true if the current value is an in-stream error value; otherwise false. + internal bool StartBufferingAndTryToReadInStreamErrorPropertyValue(out ODataError error) + { + this.AssertNotBuffering(); + + error = null; + + this.StartBuffering(); + this.parsingInStreamError = true; + + try + { + return this.TryReadInStreamErrorPropertyValue(out error); + } + finally + { + this.StopBuffering(); + this.parsingInStreamError = false; + } + } + + /// + /// Reads the next node from the input. If we have still nodes in the buffer, takes the node + /// from there. Otherwise reads a new node from the underlying reader and buffers it (depending on the current mode). + /// + /// true if a new node was found, or false if end of input was reached. + /// + /// If the parsingInStreamError field is false, the method will read ahead for every StartObject node read from the input to check whether the JSON object + /// represents an in-stream error. If so, it throws an . If false, this check will not happen. + /// This parsingInStremError field is set to true when trying to parse an in-stream error; in normal operation it is false. + /// + protected bool ReadInternal() + { + if (this.removeOnNextRead) + { + Debug.Assert(this.bufferedNodesHead != null, "If removeOnNextRead is true we must have at least one node in the buffer."); + + this.RemoveFirstNodeInBuffer(); + + this.removeOnNextRead = false; + } + + bool result; + if (this.isBuffering) + { + Debug.Assert(this.currentBufferedNode != null, "this.currentBufferedNode != null"); + + if (this.currentBufferedNode.Next != this.bufferedNodesHead) + { + this.currentBufferedNode = this.currentBufferedNode.Next; + result = true; + } + else + { + if (this.parsingInStreamError) + { + // read more from the input stream and buffer it + result = this.innerReader.Read(); + + // Add the new node to the end + BufferedNode newNode = new BufferedNode(this.innerReader.NodeType, this.innerReader.Value); + newNode.Previous = this.bufferedNodesHead.Previous; + newNode.Next = this.bufferedNodesHead; + this.bufferedNodesHead.Previous.Next = newNode; + this.bufferedNodesHead.Previous = newNode; + this.currentBufferedNode = newNode; + } + else + { + // read the next node from the input stream and check + // whether it is an in-stream error + result = this.ReadNextAndCheckForInStreamError(); + } + } + } + else + { + if (this.bufferedNodesHead == null) + { + // if parsingInStreamError nothing in the buffer; read from the base, + // else read the next node from the input stream and check + // whether it is an in-stream error + result = this.parsingInStreamError ? this.innerReader.Read() : this.ReadNextAndCheckForInStreamError(); + } + else + { + Debug.Assert(!this.parsingInStreamError, "!this.parsingInStreamError"); + + // non-buffering read from the buffer + result = this.bufferedNodesHead.NodeType != JsonNodeType.EndOfInput; + this.removeOnNextRead = true; + } + } + + return result; + } + + /// + /// Called whenever we find a new object value in the payload. + /// The base class implementation reads ahead and tries to parse it as an in-stream error payload. If it finds one it will throw it. + /// + /// + /// This method is called when the reader is in the buffering mode and can read ahead (buffering) as much as it needs to + /// once it returns the reader will be returned to the position before the method was called. + /// The reader is always positioned on a start object when this method is called. + /// + protected virtual void ProcessObjectValue() + { + Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.StartObject, "this.currentBufferedNode.NodeType == JsonNodeType.StartObject"); + Debug.Assert(this.parsingInStreamError, "this.parsingInStreamError"); + this.AssertBuffering(); + + ODataError error = null; + + // only check for in-stream errors if the buffering reader is told to do so + if (!this.DisableInStreamErrorDetection) + { + // move to the first property of the potential error object (or the end-object node if no properties exist) + this.ReadInternal(); + + // we only consider this to be an in-stream error if the object has a single 'error' property + bool errorPropertyFound = false; + while (this.currentBufferedNode.NodeType == JsonNodeType.Property) + { + // NOTE: the JSON reader already ensures that the value of a property node (which is the name of the property) is a string + string propertyName = (string)this.currentBufferedNode.Value; + + // if we found any other property than the expected in-stream error property, we don't treat it as an in-stream error + if (string.CompareOrdinal(this.inStreamErrorPropertyName, propertyName) != 0 || errorPropertyFound) + { + return; + } + + errorPropertyFound = true; + + // position the reader over the property value + this.ReadInternal(); + + if (!this.TryReadInStreamErrorPropertyValue(out error)) + { + // This means we thought we saw an in-stream error, but then + // we didn't see an intelligible error object, so we give up an reading the in-stream error + // and return. We will fail later in some other way. This payload is totally messed up. + return; + } + } + + if (errorPropertyFound) + { + throw new ODataErrorException(error); + } + } + } + + /// + /// Reads the next node from the JSON reader and if a start-object node is detected starts reading ahead and + /// tries to parse an in-stream error. + /// + /// true if a new node was found, or false if end of input was reached. + private bool ReadNextAndCheckForInStreamError() + { + Debug.Assert(!this.parsingInStreamError, "!this.parsingInStreamError"); + + this.parsingInStreamError = true; + + try + { + // read the next node in the current reader mode (buffering or non-buffering) + bool result = this.ReadInternal(); + + if (this.innerReader.NodeType == JsonNodeType.StartObject) + { + // If we find a StartObject node we have to read ahead and check whether this + // JSON object represents an in-stream error. If we are currently in buffering + // mode remember the current position in the buffer; otherwise start buffering. + bool wasBuffering = this.isBuffering; + BufferedNode storedPosition = null; + if (this.isBuffering) + { + storedPosition = this.currentBufferedNode; + } + else + { + this.StartBuffering(); + } + + this.ProcessObjectValue(); + + // Reset the reader state to non-buffering or to the previously + // backed up position in the buffer. + if (wasBuffering) + { + this.currentBufferedNode = storedPosition; + } + else + { + this.StopBuffering(); + } + } + + return result; + } + finally + { + this.parsingInStreamError = false; + } + } + + /// + /// Try to read an error structure from the stream. Return null if no error structure can be read. + /// + /// An instance that was read from the reader or null if none could be read. + /// true if an instance that was read; otherwise false. + private bool TryReadInStreamErrorPropertyValue(out ODataError error) + { + Debug.Assert(this.parsingInStreamError, "this.parsingInStreamError"); + this.AssertBuffering(); + + error = null; + + // we expect a start-object node here + if (this.currentBufferedNode.NodeType != JsonNodeType.StartObject) + { + return false; + } + + // read the start-object node + this.ReadInternal(); + + error = new ODataError(); + + // we expect one of the supported properties for the value (or end-object) + ODataJsonLightReaderUtils.ErrorPropertyBitMask propertiesFoundBitmask = ODataJsonLightReaderUtils.ErrorPropertyBitMask.None; + while (this.currentBufferedNode.NodeType == JsonNodeType.Property) + { + // NOTE the Json reader already ensures that the value of a property node is a string + string propertyName = (string)this.currentBufferedNode.Value; + switch (propertyName) + { + case JsonConstants.ODataErrorCodeName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound(ref propertiesFoundBitmask, ODataJsonLightReaderUtils.ErrorPropertyBitMask.Code)) + { + return false; + } + + string errorCode; + if (this.TryReadErrorStringPropertyValue(out errorCode)) + { + error.ErrorCode = errorCode; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorMessageName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound(ref propertiesFoundBitmask, ODataJsonLightReaderUtils.ErrorPropertyBitMask.Message)) + { + return false; + } + + string errorMessage; + if (this.TryReadErrorStringPropertyValue(out errorMessage)) + { + error.Message = errorMessage; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorTargetName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound( + ref propertiesFoundBitmask, + ODataJsonLightReaderUtils.ErrorPropertyBitMask.Target)) + { + return false; + } + + string errorTarget; + if (this.TryReadErrorStringPropertyValue(out errorTarget)) + { + error.Target = errorTarget; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorDetailsName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound( + ref propertiesFoundBitmask, + ODataJsonLightReaderUtils.ErrorPropertyBitMask.Details)) + { + return false; + } + + ICollection details; + if (!this.TryReadErrorDetailsPropertyValue(out details)) + { + return false; + } + + error.Details = details; + break; + + case JsonConstants.ODataErrorInnerErrorName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound(ref propertiesFoundBitmask, ODataJsonLightReaderUtils.ErrorPropertyBitMask.InnerError)) + { + return false; + } + + ODataInnerError innerError; + if (!this.TryReadInnerErrorPropertyValue(out innerError, 0 /*recursionDepth */)) + { + return false; + } + + error.InnerError = innerError; + break; + + default: + // if we find a non-supported property we don't treat this as an error + return false; + } + + this.ReadInternal(); + } + + // read the end object + Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.EndObject, "this.currentBufferedNode.NodeType == JsonNodeType.EndObject"); + this.ReadInternal(); + + // if we don't find any properties it is not a valid error object + return propertiesFoundBitmask != ODataJsonLightReaderUtils.ErrorPropertyBitMask.None; + } + + private bool TryReadErrorDetailsPropertyValue(out ICollection details) + { + Debug.Assert( + this.currentBufferedNode.NodeType == JsonNodeType.Property, + "this.currentBufferedNode.NodeType == JsonNodeType.Property"); + Debug.Assert(this.parsingInStreamError, "this.parsingInStreamError"); + this.AssertBuffering(); + + // move the reader onto the property value + this.ReadInternal(); + + // we expect a start-array node here + if (this.currentBufferedNode.NodeType != JsonNodeType.StartArray) + { + details = null; + return false; + } + + // [ + ReadInternal(); + + details = new List(); + ODataErrorDetail detail; + if (TryReadErrorDetail(out detail)) + { + details.Add(detail); + } + + // ] + ReadInternal(); + + return true; + } + + private bool TryReadErrorDetail(out ODataErrorDetail detail) + { + Debug.Assert( + this.currentBufferedNode.NodeType == JsonNodeType.StartObject, + "this.currentBufferedNode.NodeType == JsonNodeType.StartObject"); + Debug.Assert(this.parsingInStreamError, "this.parsingInStreamError"); + this.AssertBuffering(); + + if (this.currentBufferedNode.NodeType != JsonNodeType.StartObject) + { + detail = null; + return false; + } + + // { + ReadInternal(); + + detail = new ODataErrorDetail(); + + // we expect one of the supported properties for the value (or end-object) + var propertiesFoundBitmask = ODataJsonLightReaderUtils.ErrorPropertyBitMask.None; + while (this.currentBufferedNode.NodeType == JsonNodeType.Property) + { + var propertyName = (string)this.currentBufferedNode.Value; + + switch (propertyName) + { + case JsonConstants.ODataErrorCodeName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound( + ref propertiesFoundBitmask, + ODataJsonLightReaderUtils.ErrorPropertyBitMask.Code)) + { + return false; + } + + string code; + if (this.TryReadErrorStringPropertyValue(out code)) + { + detail.ErrorCode = code; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorTargetName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound( + ref propertiesFoundBitmask, + ODataJsonLightReaderUtils.ErrorPropertyBitMask.Target)) + { + return false; + } + + string target; + if (this.TryReadErrorStringPropertyValue(out target)) + { + detail.Target = target; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorMessageName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound( + ref propertiesFoundBitmask, + ODataJsonLightReaderUtils.ErrorPropertyBitMask.MessageValue)) + { + return false; + } + + string message; + if (this.TryReadErrorStringPropertyValue(out message)) + { + detail.Message = message; + } + else + { + return false; + } + + break; + + default: + // if we find a non-supported property in an inner error, we skip it + this.SkipValueInternal(); + break; + } + + this.ReadInternal(); + } + + Debug.Assert( + this.currentBufferedNode.NodeType == JsonNodeType.EndObject, + "this.currentBufferedNode.NodeType == JsonNodeType.EndObject"); + + return true; + } + + /// + /// Try to read an inner error property value. + /// + /// An instance that was read from the reader or null if none could be read. + /// The number of times this method has been called recursively. + /// true if an instance that was read; otherwise false. + private bool TryReadInnerErrorPropertyValue(out ODataInnerError innerError, int recursionDepth) + { + Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.Property, "this.currentBufferedNode.NodeType == JsonNodeType.Property"); + Debug.Assert(this.parsingInStreamError, "this.parsingInStreamError"); + this.AssertBuffering(); + + ValidationUtils.IncreaseAndValidateRecursionDepth(ref recursionDepth, this.maxInnerErrorDepth); + + // move the reader onto the property value + this.ReadInternal(); + + // we expect a start-object node here + if (this.currentBufferedNode.NodeType != JsonNodeType.StartObject) + { + innerError = null; + return false; + } + + // read the start-object node + this.ReadInternal(); + + innerError = new ODataInnerError(); + + // we expect one of the supported properties for the value (or end-object) + ODataJsonLightReaderUtils.ErrorPropertyBitMask propertiesFoundBitmask = ODataJsonLightReaderUtils.ErrorPropertyBitMask.None; + while (this.currentBufferedNode.NodeType == JsonNodeType.Property) + { + // NOTE the Json reader already ensures that the value of a property node is a string + string propertyName = (string)this.currentBufferedNode.Value; + + switch (propertyName) + { + case JsonConstants.ODataErrorInnerErrorMessageName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound(ref propertiesFoundBitmask, ODataJsonLightReaderUtils.ErrorPropertyBitMask.MessageValue)) + { + return false; + } + + string message; + if (this.TryReadErrorStringPropertyValue(out message)) + { + innerError.Message = message; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorInnerErrorTypeNameName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound(ref propertiesFoundBitmask, ODataJsonLightReaderUtils.ErrorPropertyBitMask.TypeName)) + { + return false; + } + + string typeName; + if (this.TryReadErrorStringPropertyValue(out typeName)) + { + innerError.TypeName = typeName; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorInnerErrorStackTraceName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound(ref propertiesFoundBitmask, ODataJsonLightReaderUtils.ErrorPropertyBitMask.StackTrace)) + { + return false; + } + + string stackTrace; + if (this.TryReadErrorStringPropertyValue(out stackTrace)) + { + innerError.StackTrace = stackTrace; + } + else + { + return false; + } + + break; + + case JsonConstants.ODataErrorInnerErrorInnerErrorName: + if (!ODataJsonLightReaderUtils.ErrorPropertyNotFound(ref propertiesFoundBitmask, ODataJsonLightReaderUtils.ErrorPropertyBitMask.InnerError)) + { + return false; + } + + ODataInnerError nestedInnerError; + if (this.TryReadInnerErrorPropertyValue(out nestedInnerError, recursionDepth)) + { + innerError.InnerError = nestedInnerError; + } + else + { + return false; + } + + break; + + default: + // if we find a non-supported property in an inner error, we skip it + this.SkipValueInternal(); + break; + } + + this.ReadInternal(); + } + + Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.EndObject, "this.currentBufferedNode.NodeType == JsonNodeType.EndObject"); + + return true; + } + + /// + /// Reads the string value of a property. + /// + /// The string value read if the method returns true; otherwise null. + /// true if a string value (or null) was read as property value of the current property; otherwise false. + private bool TryReadErrorStringPropertyValue(out string stringValue) + { + Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.Property, "this.currentBufferedNode.NodeType == JsonNodeType.Property"); + Debug.Assert(this.parsingInStreamError, "this.parsingInStreamError"); + this.AssertBuffering(); + + this.ReadInternal(); + + // we expect a string value + stringValue = this.currentBufferedNode.Value as string; + return this.currentBufferedNode.NodeType == JsonNodeType.PrimitiveValue && (this.currentBufferedNode.Value == null || stringValue != null); + } + + /// + /// Skips over a JSON value (primitive, object or array) while parsing in-stream errors. + /// Note that the SkipValue extension method can not be used in this case as this method has to + /// access the base instance's NodeType and call ReadInternal. + /// + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue, JsonNodeType.StartArray or JsonNodeType.StartObject + /// Post-Condition: JsonNodeType.PrimitiveValue, JsonNodeType.EndArray or JsonNodeType.EndObject + /// + private void SkipValueInternal() + { + int depth = 0; + do + { + switch (this.currentBufferedNode.NodeType) + { + case JsonNodeType.PrimitiveValue: + break; + case JsonNodeType.StartArray: + case JsonNodeType.StartObject: + depth++; + break; + case JsonNodeType.EndArray: + case JsonNodeType.EndObject: + Debug.Assert(depth > 0, "Seen too many scope ends."); + depth--; + break; + default: + Debug.Assert( + this.currentBufferedNode.NodeType != JsonNodeType.EndOfInput, + "We should not have reached end of input, since the scopes should be well formed. Otherwise JsonReader should have failed by now."); + break; + } + + this.ReadInternal(); + } + while (depth > 0); + } + + /// + /// Removes the head node from the buffer. + /// + private void RemoveFirstNodeInBuffer() + { + if (this.bufferedNodesHead.Next == this.bufferedNodesHead) + { + Debug.Assert(this.bufferedNodesHead.Previous == this.bufferedNodesHead, "The linked list is corrupted."); + this.bufferedNodesHead = null; + } + else + { + this.bufferedNodesHead.Previous.Next = this.bufferedNodesHead.Next; + this.bufferedNodesHead.Next.Previous = this.bufferedNodesHead.Previous; + this.bufferedNodesHead = this.bufferedNodesHead.Next; + } + } + +#if DEBUG + /// + /// Determines whether the given node exists in the current buffer. + /// + /// The node to test. + /// true if the given node was found in the buffer; false otherwise. + private bool NodeExistsInCurrentBuffer(BufferedNode nodeToCheck) + { + BufferedNode currentNode = this.bufferedNodesHead; + + do + { + if (currentNode == nodeToCheck) + { + return true; + } + + currentNode = currentNode.Next; + } + while (currentNode != this.bufferedNodesHead); + + return false; + } + +#endif + /// + /// Private class used to buffer nodes when reading in buffering mode. + /// + protected internal sealed class BufferedNode + { + /// The type of the node read. + private readonly JsonNodeType nodeType; + + /// The value of the node. + private readonly object nodeValue; + + /// + /// Constructor. + /// + /// The type of the node read. + /// The value of the node. + internal BufferedNode(JsonNodeType nodeType, object value) + { + this.nodeType = nodeType; + this.nodeValue = value; + this.Previous = this; + this.Next = this; + } + + /// + /// The type of the node read. + /// + internal JsonNodeType NodeType + { + get + { + return this.nodeType; + } + } + + /// + /// The value of the node. + /// + internal object Value + { + get + { + return this.nodeValue; + } + } + + /// + /// The previous node in the list of nodes. + /// + internal BufferedNode Previous { get; set; } + + /// + /// The next node in the list of nodes. + /// + internal BufferedNode Next { get; set; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonReaderFactory.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonReaderFactory.cs new file mode 100644 index 0000000..a3ee116 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonReaderFactory.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.IO; + +namespace Microsoft.OData.Json +{ + /// + /// Default factory for creating a JsonReader + /// + internal sealed class DefaultJsonReaderFactory : IJsonReaderFactory + { + /// + /// Method to create a . + /// + /// The underlying text reader + /// Whether the reader returns large integers as strings + /// A new instance. + public IJsonReader CreateJsonReader(TextReader textReader, bool isIeee754Compatible) + { + return new JsonReader(textReader, isIeee754Compatible); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonWriterFactory.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonWriterFactory.cs new file mode 100644 index 0000000..519a6ff --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/DefaultJsonWriterFactory.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.IO; + +namespace Microsoft.OData.Json +{ + /// + /// Default JSON writer factory + /// + public sealed class DefaultJsonWriterFactory : IJsonWriterFactory + { + private ODataStringEscapeOption stringEscapeOption; + + /// + /// Initializes a new instance of the . + /// + public DefaultJsonWriterFactory() + : this(ODataStringEscapeOption.EscapeNonAscii) + { + } + + /// + /// Initializes a new instance of the . + /// + /// The string escape option. + public DefaultJsonWriterFactory(ODataStringEscapeOption stringEscapeOption) + { + this.stringEscapeOption = stringEscapeOption; + } + + /// + /// Creates a new JSON writer of . + /// + /// Writer to which text needs to be written. + /// True if it is IEEE754Compatible. + /// The JSON writer created. + [CLSCompliant(false)] + public IJsonWriter CreateJsonWriter(TextWriter textWriter, bool isIeee754Compatible) + { + return new JsonWriter(textWriter, isIeee754Compatible, this.stringEscapeOption); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/GeoJsonWriterAdapter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/GeoJsonWriterAdapter.cs new file mode 100644 index 0000000..1c0f238 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/GeoJsonWriterAdapter.cs @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + using Microsoft.Spatial; + + /// + /// Convert writer interface from IJsonWriter to IGeoJsonWriter. + /// This enables writer instance to be passed to Spatial to boost writing performance. + /// + internal sealed class GeoJsonWriterAdapter : IGeoJsonWriter + { + /// + /// Inner writer of interface IJsonWriter. + /// + private readonly IJsonWriter writer; + + /// + /// Constructor (only accessible from OData.Core + /// + /// Inner writer of interface IJsonWriter. + internal GeoJsonWriterAdapter(IJsonWriter writer) + { + this.writer = writer; + } + + /// + /// Start the object scope. + /// + void IGeoJsonWriter.StartObjectScope() + { + this.writer.StartObjectScope(); + } + + /// + /// End the current object scope. + /// + void IGeoJsonWriter.EndObjectScope() + { + this.writer.EndObjectScope(); + } + + /// + /// Start the array scope. + /// + void IGeoJsonWriter.StartArrayScope() + { + this.writer.StartArrayScope(); + } + + /// + /// End the current array scope. + /// + void IGeoJsonWriter.EndArrayScope() + { + this.writer.EndArrayScope(); + } + + /// + /// Add a property name to the current json object. + /// + /// The name to add. + void IGeoJsonWriter.AddPropertyName(string name) + { + this.writer.WriteName(name); + } + + /// + /// Add a value to the current json scope. + /// + /// The value to add. + void IGeoJsonWriter.AddValue(double value) + { + this.writer.WriteValue(value); + } + + /// + /// Add a value to the current json scope. + /// + /// The value to add. + void IGeoJsonWriter.AddValue(string value) + { + this.writer.WriteValue(value); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReader.cs new file mode 100644 index 0000000..6a12702 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReader.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + /// + /// Interface for a class that can read arbitrary JSON. + /// + public interface IJsonReader + { + /// + /// The value of the last reported node. + /// + /// This is non-null only if the last node was a PrimitiveValue or Property. + /// If the last node is a PrimitiveValue this property returns the value: + /// - null if the null token was found. + /// - boolean if the true or false token was found. + /// - string if a string token was found. + /// - DateTime if a string token formatted as DateTime was found. + /// - Int32 if a number which fits into the Int32 was found. + /// - Double if a number which doesn't fit into Int32 was found. + /// If the last node is a Property this property returns a string which is the name of the property. + /// + object Value { get; } + + /// + /// The type of the last node read. + /// + JsonNodeType NodeType { get; } + + /// + /// if it is IEEE754 compatible + /// + bool IsIeee754Compatible { get; } + + /// + /// Reads the next node from the input. + /// + /// true if a new node was found, or false if end of input was reached. + bool Read(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReaderFactory.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReaderFactory.cs new file mode 100644 index 0000000..6ccc7da --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonReaderFactory.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.IO; + +namespace Microsoft.OData.Json +{ + /// + /// Interface of the factory to create JSON readers. + /// + public interface IJsonReaderFactory + { + /// + /// Creates a new JSON reader of . + /// + /// The text reader to read input characters from. + /// True if it is IEEE754Compatible. + /// The JSON reader created. + IJsonReader CreateJsonReader(TextReader textReader, bool isIeee754Compatible); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamReader.cs new file mode 100644 index 0000000..7dc3b5b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamReader.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + + +namespace Microsoft.OData.Json +{ + using System.IO; + + /// + /// Interface for a class that can read arbitrary JSON and stream binary properties. + /// + public interface IJsonStreamReader : IJsonReader + { + /// + /// Creates a Stream for reading a binary value. + /// + /// A Stream for reading the binary value. + Stream CreateReadStream(); + + /// + /// Creates a TextReader for reading a string value. + /// + /// A TextReader for reading the string value. + TextReader CreateTextReader(); + + /// + /// Whether or not the current value can be streamed + /// + /// True if the current value can be streamed, otherwise false + bool CanStream(); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamWriter.cs new file mode 100644 index 0000000..68d424d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonStreamWriter.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + using System; + using System.IO; + + /// + /// Interface for writing JSON including streaming binary values. + /// + [CLSCompliant(false)] + public interface IJsonStreamWriter : IJsonWriter + { + /// + /// Start the stream property valuescope. + /// + /// + /// A Stream to write to + /// + Stream StartStreamValueScope(); + + /// + /// Start the TextWriter value valuescope. + /// + /// ContentType of the string being written. + /// + /// A Text writer to write to the stream. + /// + TextWriter StartTextWriterValueScope(string contentType); + + /// + /// End the current stream property value scope. + /// + void EndStreamValueScope(); + + /// + /// End the current TextWriter value valuescope. + /// + void EndTextWriterValueScope(); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriter.cs new file mode 100644 index 0000000..229173c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriter.cs @@ -0,0 +1,167 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + using System; + using Microsoft.OData.Edm; + + /// + /// Interface for a class that can write arbitrary JSON. + /// + [CLSCompliant(false)] + public interface IJsonWriter + { + /// + /// Start the padding function scope. + /// + void StartPaddingFunctionScope(); + + /// + /// End the padding function scope. + /// + void EndPaddingFunctionScope(); + + /// + /// Start the object scope. + /// + void StartObjectScope(); + + /// + /// End the current object scope. + /// + void EndObjectScope(); + + /// + /// Start the array scope. + /// + void StartArrayScope(); + + /// + /// End the current array scope. + /// + void EndArrayScope(); + + /// + /// Write the name for the object property. + /// + /// Name of the object property. + void WriteName(string name); + + /// + /// Writes a function name for JSON padding. + /// + /// Name of the padding function to write. + void WritePaddingFunctionName(string functionName); + + /// + /// Write a boolean value. + /// + /// Boolean value to be written. + void WriteValue(bool value); + + /// + /// Write an integer value. + /// + /// Integer value to be written. + void WriteValue(int value); + + /// + /// Write a float value. + /// + /// Float value to be written. + void WriteValue(float value); + + /// + /// Write a short value. + /// + /// Short value to be written. + void WriteValue(short value); + + /// + /// Write a long value. + /// + /// Long value to be written. + void WriteValue(long value); + + /// + /// Write a double value. + /// + /// Double value to be written. + void WriteValue(double value); + + /// + /// Write a Guid value. + /// + /// Guid value to be written. + void WriteValue(Guid value); + + /// + /// Write a decimal value + /// + /// Decimal value to be written. + void WriteValue(decimal value); + + /// + /// Writes a DateTimeOffset value + /// + /// DateTimeOffset value to be written. + void WriteValue(DateTimeOffset value); + + /// + /// Writes a TimeSpan value + /// + /// TimeSpan value to be written. + void WriteValue(TimeSpan value); + + /// + /// Write a byte value. + /// + /// Byte value to be written. + void WriteValue(byte value); + + /// + /// Write an sbyte value. + /// + /// SByte value to be written. + void WriteValue(sbyte value); + + /// + /// Write a string value. + /// + /// String value to be written. + void WriteValue(string value); + + /// + /// Write a byte array. + /// + /// Byte array to be written. + void WriteValue(byte[] value); + + /// + /// Write a Date value + /// + /// Date value to be written. + void WriteValue(Date value); + + /// + /// Write a TimeOfDay value + /// + /// TimeOfDay value to be written. + void WriteValue(TimeOfDay value); + + /// + /// Write a raw value. + /// + /// Raw value to be written. + void WriteRawValue(string rawValue); + + /// + /// Clears all buffers for the current writer. + /// + void Flush(); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriterFactory.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriterFactory.cs new file mode 100644 index 0000000..a9fe623 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IJsonWriterFactory.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.IO; + +namespace Microsoft.OData.Json +{ + /// + /// Interface of the factory to create JSON writers. + /// + [CLSCompliant(false)] + public interface IJsonWriterFactory + { + /// + /// Creates a new JSON writer of . + /// + /// Writer to which text needs to be written. + /// True if it is IEEE754Compatible. + /// The JSON writer created. + IJsonWriter CreateJsonWriter(TextWriter textWriter, bool isIeee754Compatible); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IODataJsonOperationsDeserializerContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IODataJsonOperationsDeserializerContext.cs new file mode 100644 index 0000000..6578a2a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/IODataJsonOperationsDeserializerContext.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + #endregion Namespaces + + /// + /// Interface representing a context necessary for reading JSON operations values. + /// + internal interface IODataJsonOperationsDeserializerContext + { + /// + /// The JSON reader to read the operations value from. + /// + IJsonReader JsonReader { get; } + + /// + /// Given a URI from the payload, this method will try to make it absolute, or fail otherwise. + /// + /// The URI string from the payload to process. + /// An absolute URI to report. + Uri ProcessUriFromPayload(string uriFromPayload); + + /// + /// Adds the specified action to the current resource. + /// + /// The action which is fully populated with the data from the payload. + void AddActionToResource(ODataAction action); + + /// + /// Adds the specified function to the current resource. + /// + /// The function which is fully populated with the data from the payload. + void AddFunctionToResource(ODataFunction function); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonConstants.cs new file mode 100644 index 0000000..9c42ff3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonConstants.cs @@ -0,0 +1,168 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + + #endregion Namespaces + + /// + /// Constants for the JSON format. + /// + internal static class JsonConstants + { + /// "actions" header for resource metadata. + internal const string ODataActionsMetadataName = "actions"; + + /// "functions" header for resource metadata. + internal const string ODataFunctionsMetadataName = "functions"; + + /// "title" header for "actions" and "functions" metadata. + internal const string ODataOperationTitleName = "title"; + + /// "metadata" header for "actions" and "functions" metadata. + internal const string ODataOperationMetadataName = "metadata"; + + /// "target" header for "actions" and "functions" metadata. + internal const string ODataOperationTargetName = "target"; + + /// + /// "error" header for the error payload + /// + internal const string ODataErrorName = "error"; + + /// + /// "code" header for the error code property + /// + internal const string ODataErrorCodeName = "code"; + + /// + /// "message" header for the error message property + /// + internal const string ODataErrorMessageName = "message"; + + /// + /// "target" header for the error message property + /// + internal const string ODataErrorTargetName = "target"; + + /// + /// "details" header for the inner error property + /// + internal const string ODataErrorDetailsName = "details"; + + /// + /// "innererror" header for the inner error property + /// + internal const string ODataErrorInnerErrorName = "innererror"; + + /// + /// "message" header for an inner error (for Astoria compatibility) + /// + internal const string ODataErrorInnerErrorMessageName = "message"; + + /// + /// "typename" header for an inner error (for Astoria compatibility) + /// + internal const string ODataErrorInnerErrorTypeNameName = "type"; + + /// + /// "stacktrace" header for an inner error (for Astoria compatibility) + /// + internal const string ODataErrorInnerErrorStackTraceName = "stacktrace"; + + /// + /// "internalexception" header for an inner, inner error property (for Astoria compatibility) + /// + internal const string ODataErrorInnerErrorInnerErrorName = "internalexception"; + + /// + /// JSON datetime format. + /// + internal const string ODataDateTimeFormat = @"\/Date({0})\/"; + + /// + /// JSON datetime offset format. + /// + internal const string ODataDateTimeOffsetFormat = @"\/Date({0}{1}{2:D4})\/"; + + /// + /// A plus sign for the date time offset format. + /// + internal const string ODataDateTimeOffsetPlusSign = "+"; + + /// + /// The fixed property name for the entity sets array in a service document payload. + /// + internal const string ODataServiceDocumentEntitySetsName = "EntitySets"; + + /// + /// The true value literal. + /// + internal const string JsonTrueLiteral = "true"; + + /// + /// The false value literal. + /// + internal const string JsonFalseLiteral = "false"; + + /// + /// The null value literal. + /// + internal const string JsonNullLiteral = "null"; + + /// + /// Character which starts the object scope. + /// + internal const string StartObjectScope = "{"; + + /// + /// Character which ends the object scope. + /// + internal const string EndObjectScope = "}"; + + /// + /// Character which starts the array scope. + /// + internal const string StartArrayScope = "["; + + /// + /// Character which ends the array scope. + /// + internal const string EndArrayScope = "]"; + + /// + /// "(" Json Padding Function scope open parens. + /// + internal const string StartPaddingFunctionScope = "("; + + /// + /// ")" Json Padding Function scope close parens. + /// + internal const string EndPaddingFunctionScope = ")"; + + /// + /// The separator between object members. + /// + internal const string ObjectMemberSeparator = ","; + + /// + /// The separator between array elements. + /// + internal const string ArrayElementSeparator = ","; + + /// + /// The separator between the name and the value. + /// + internal const string NameValueSeparator = ":"; + + /// + /// The quote character. + /// + internal const char QuoteCharacter = '"'; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonLightInstanceAnnotationWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonLightInstanceAnnotationWriter.cs new file mode 100644 index 0000000..614c15b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonLightInstanceAnnotationWriter.cs @@ -0,0 +1,247 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + using Microsoft.OData.JsonLight; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion + + /// + /// Class responsible for writing a collection of . + /// + internal sealed class JsonLightInstanceAnnotationWriter + { + /// + /// Value serializer, responsible for serializing the annotation values. + /// + private readonly ODataJsonLightValueSerializer valueSerializer; + + /// + /// The oracle to use to determine the type name to write for entries and values. + /// + private readonly JsonLightTypeNameOracle typeNameOracle; + + /// + /// JsonWriter instance to use for writing term names. + /// + private readonly IJsonWriter jsonWriter; + + /// + /// OData annotation writer. + /// + private readonly JsonLightODataAnnotationWriter odataAnnotationWriter; + + /// + /// The writer validator used during writing. + /// + private readonly IWriterValidator writerValidator; + + /// + /// Constructs a that can write a collection of . + /// + /// The to use for writing values of instance annotations. + /// The that is also used internally will be acquired from the this instance. + /// The oracle to use to determine the type name to write for entries and values. + internal JsonLightInstanceAnnotationWriter(ODataJsonLightValueSerializer valueSerializer, JsonLightTypeNameOracle typeNameOracle) + { + Debug.Assert(valueSerializer != null, "valueSerializer should not be null"); + this.valueSerializer = valueSerializer; + this.typeNameOracle = typeNameOracle; + this.jsonWriter = this.valueSerializer.JsonWriter; + this.odataAnnotationWriter = new JsonLightODataAnnotationWriter(this.jsonWriter, + valueSerializer.JsonLightOutputContext.OmitODataPrefix, this.valueSerializer.MessageWriterSettings.Version); + this.writerValidator = this.valueSerializer.MessageWriterSettings.Validator; + } + + /// + /// Writes all the instance annotations specified in . + /// + /// Collection of instance annotations to write. + /// The tracker to track if instance annotations are written. + /// Whether to ignore the filter in settings. + /// The name of the property this instance annotation applies to + internal void WriteInstanceAnnotations(IEnumerable instanceAnnotations, + InstanceAnnotationWriteTracker tracker, + bool ignoreFilter = false, string propertyName = null) + { + Debug.Assert(instanceAnnotations != null, "instanceAnnotations should not be null if we called this"); + Debug.Assert(tracker != null, "tracker should not be null if we called this"); + + HashSet instanceAnnotationNames = new HashSet(StringComparer.Ordinal); + foreach (var annotation in instanceAnnotations) + { + if (!instanceAnnotationNames.Add(annotation.Name)) + { + throw new ODataException(ODataErrorStrings.JsonLightInstanceAnnotationWriter_DuplicateAnnotationNameInCollection(annotation.Name)); + } + + if (!tracker.IsAnnotationWritten(annotation.Name) + && (!ODataAnnotationNames.IsODataAnnotationName(annotation.Name) || ODataAnnotationNames.IsUnknownODataAnnotationName(annotation.Name))) + { + this.WriteInstanceAnnotation(annotation, ignoreFilter, propertyName); + tracker.MarkAnnotationWritten(annotation.Name); + } + } + } + + /// + /// Writes all the instance annotations specified in . + /// + /// Collection of instance annotations to write. + /// The name of the property this instance annotation applies to + /// If writing an undeclared property. + internal void WriteInstanceAnnotations(IEnumerable instanceAnnotations, string propertyName = null, bool isUndeclaredProperty = false) + { + Debug.Assert(instanceAnnotations != null, "instanceAnnotations should not be null if we called this"); + if (isUndeclaredProperty) + { + // write undeclared property's all annotations + foreach (var annotation in instanceAnnotations) + { + this.WriteInstanceAnnotation(annotation, true, propertyName); + } + } + else + { + this.WriteInstanceAnnotations(instanceAnnotations, new InstanceAnnotationWriteTracker(), false, propertyName); + } + } + + /// + /// Writes all the instance annotations specified in of error. + /// + /// Collection of instance annotations to write. + internal void WriteInstanceAnnotationsForError(IEnumerable instanceAnnotations) + { + Debug.Assert(instanceAnnotations != null, "instanceAnnotations should not be null if we called this"); + this.WriteInstanceAnnotations(instanceAnnotations, new InstanceAnnotationWriteTracker(), true); + } + + /// + /// Writes an instance annotation. + /// + /// The instance annotation to write. + /// Whether to ignore the filter in settings. + /// The name of the property this instance annotation applies to + internal void WriteInstanceAnnotation(ODataInstanceAnnotation instanceAnnotation, bool ignoreFilter = false, string propertyName = null) + { + string name = instanceAnnotation.Name; + ODataValue value = instanceAnnotation.Value; + Debug.Assert(!string.IsNullOrEmpty(name), "name should not be null or empty"); + Debug.Assert(value != null, "value should not be null because we use ODataNullValue for null instead"); + Debug.Assert(!(value is ODataStreamReferenceValue), "!(value is ODataStreamReferenceValue) -- ODataInstanceAnnotation and InstanceAnnotationCollection will throw if the value is a stream value."); + Debug.Assert(this.valueSerializer.Model != null, "this.valueSerializer.Model != null"); + + if (!ignoreFilter && this.valueSerializer.MessageWriterSettings.ShouldSkipAnnotation(name)) + { + return; + } + + IEdmTypeReference expectedType = MetadataUtils.LookupTypeOfTerm(name, this.valueSerializer.Model); + + if (value is ODataNullValue) + { + if (expectedType != null && !expectedType.IsNullable) + { + throw new ODataException( + ODataErrorStrings.JsonLightInstanceAnnotationWriter_NullValueNotAllowedForInstanceAnnotation( + instanceAnnotation.Name, expectedType.FullName())); + } + + this.WriteInstanceAnnotationName(propertyName, name); + this.valueSerializer.WriteNullValue(); + return; + } + + // If we didn't find an expected type from looking up the term in the model, treat this value the same way we would for open property values. + // That is, write the type name (unless its a primitive value with a JSON-native type). If we did find an expected type, treat the annotation value like a + // declared property with an expected type. This will still write out the type if the value type is more derived than the declared type, for example. + bool treatLikeOpenProperty = expectedType == null; + + ODataResourceValue resourceValue = value as ODataResourceValue; + if (resourceValue != null) + { + this.WriteInstanceAnnotationName(propertyName, name); + this.valueSerializer.WriteResourceValue(resourceValue, + expectedType, + treatLikeOpenProperty, + this.valueSerializer.CreateDuplicatePropertyNameChecker()); + return; + } + + ODataCollectionValue collectionValue = value as ODataCollectionValue; + if (collectionValue != null) + { + IEdmTypeReference typeFromCollectionValue = (IEdmCollectionTypeReference)TypeNameOracle.ResolveAndValidateTypeForCollectionValue( + this.valueSerializer.Model, expectedType, collectionValue, treatLikeOpenProperty, this.writerValidator); + string collectionTypeNameToWrite = this.typeNameOracle.GetValueTypeNameForWriting(collectionValue, expectedType, typeFromCollectionValue, treatLikeOpenProperty); + if (collectionTypeNameToWrite != null) + { + this.odataAnnotationWriter.WriteODataTypePropertyAnnotation(name, collectionTypeNameToWrite); + } + + this.WriteInstanceAnnotationName(propertyName, name); + this.valueSerializer.WriteCollectionValue(collectionValue, expectedType, typeFromCollectionValue, false /*isTopLevelProperty*/, false /*isInUri*/, treatLikeOpenProperty); + return; + } + + ODataUntypedValue untypedValue = value as ODataUntypedValue; + if (untypedValue != null) + { + this.WriteInstanceAnnotationName(propertyName, name); + this.valueSerializer.WriteUntypedValue(untypedValue); + return; + } + + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + this.WriteInstanceAnnotationName(propertyName, name); + this.valueSerializer.WriteEnumValue(enumValue, expectedType); + return; + } + + ODataPrimitiveValue primitiveValue = value as ODataPrimitiveValue; + Debug.Assert(primitiveValue != null, "Did we add a new subclass of ODataValue?"); + + IEdmTypeReference typeFromPrimitiveValue = TypeNameOracle.ResolveAndValidateTypeForPrimitiveValue(primitiveValue); + + string primitiveTypeNameToWrite = this.typeNameOracle.GetValueTypeNameForWriting(primitiveValue, expectedType, typeFromPrimitiveValue, treatLikeOpenProperty); + if (primitiveTypeNameToWrite != null) + { + this.odataAnnotationWriter.WriteODataTypePropertyAnnotation(name, primitiveTypeNameToWrite); + } + + this.WriteInstanceAnnotationName(propertyName, name); + this.valueSerializer.WritePrimitiveValue(primitiveValue.Value, typeFromPrimitiveValue, expectedType); + } + + /// + /// Write the name of the instance annotation + /// + /// The name of the property this instance annotation applied to + /// The name of the instance annotation + private void WriteInstanceAnnotationName(string propertyName, string annotationName) + { + if (propertyName != null) + { + this.jsonWriter.WritePropertyAnnotationName(propertyName, annotationName); + } + else + { + this.jsonWriter.WriteInstanceAnnotationName(annotationName); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonNodeType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonNodeType.cs new file mode 100644 index 0000000..af0ac53 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonNodeType.cs @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + /// + /// Enumeration of all JSON node type. + /// + public enum JsonNodeType + { + /// + /// No node - invalid value. + /// + None, + + /// + /// Start of JSON object record, the { character. + /// + StartObject, + + /// + /// End of JSON object record, the } character. + /// + EndObject, + + /// + /// Start of JSON array, the [ character. + /// + StartArray, + + /// + /// End of JSON array, the ] character. + /// + EndArray, + + /// + /// Property, the name of the property (the value will be reported as a separate node or nodes) + /// + Property, + + /// + /// Primitive value, that is either null, true, false, number or string. + /// + PrimitiveValue, + + /// + /// End of input reached. + /// + EndOfInput + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReader.cs new file mode 100644 index 0000000..2342389 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReader.cs @@ -0,0 +1,1362 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + using Microsoft.OData.Buffers; + #endregion Namespaces + + /// + /// Reader for the JSON format. http://www.json.org + /// + [DebuggerDisplay("{NodeType}: {Value}")] + internal class JsonReader : IJsonStreamReader, IDisposable + { + /// + /// The initial size of the buffer of characters. + /// + /// + /// 4K (page size) divided by the size of a single character 2 and a little less + /// so that array structures also fit into that page. + /// The goal is for the entire buffer to fit into one page so that we don't cause + /// too many L1 cache misses. + /// + private const int InitialCharacterBufferSize = ((4 * 1024) / 2) - 8; + + /// + /// The text reader to read input characters from. + /// + private readonly TextReader reader; + + /// + /// If it is IEEE754Compatible, read quoted string for INT64 and decimal; + /// otherwise read number directly. + /// + private readonly bool isIeee754Compatible; + + /// + /// Stack of scopes. + /// + /// + /// At the beginning the Root scope is pushed to the stack and stays there for the entire parsing + /// (so that we don't have to check for empty stack and also to track the number of root-level values) + /// Each time a new object or array is started the Object or Array scope is pushed to the stack. + /// If a property inside an Object is found, the Property scope is pushed to the stack. + /// The Property is popped once we find the value for the property. + /// The Object and Array scopes are popped when their end is found. + /// + private readonly Stack scopes; + + /// true if annotations are allowed and thus the reader has to + /// accept more characters in property names than we do normally; otherwise false. + private readonly bool allowAnnotations; + + /// + /// End of input from the reader was already reached. + /// + /// This is used to avoid calling Read on the text reader multiple times + /// even though it already reported the end of input. + private bool endOfInputReached; + + /// + /// Whether the user is currently reading a string property as a stream. + /// + /// This is used to avoid calling Read on the text reader multiple times + /// even though it already reported the end of input. + private bool readingStream = false; + + /// + /// Whether or not the current value can be streamed + /// + /// True if we are positioned on a string or null value, otherwise false + private bool canStream = false; + + /// + /// The opening character read when reading a stream value. + /// + private char streamOpeningQuoteCharacter = '"'; + + /// + /// Buffer of characters from the input. + /// + private char[] characterBuffer; + + /// + /// Number of characters available in the input buffer. + /// + /// This can have value of 0 to characterBuffer.Length. + private int storedCharacterCount; + + /// + /// Index into the characterBuffer which points to the first character + /// of the token being currently processed (while in the Read method) + /// or of the next token to be processed (while in the caller code). + /// + /// This can have value from 0 to storedCharacterCount. + private int tokenStartIndex; + + /// + /// Number of times an opening character (for example, '{') has been read + /// greater than the number of times the corresponding closing character + /// (for example '}') has been read. + /// + private int balancedQuoteCount; + + /// + /// The last reported node type. + /// + private JsonNodeType nodeType; + + /// + /// The value of the last reported node. + /// + private object nodeValue; + + /// + /// Cached string builder to be used when constructing string values (needed to resolve escape sequences). + /// + /// The string builder instance is cached to avoid excessive allocation when many string values with escape sequences + /// are found in the payload. + private StringBuilder stringValueBuilder; + + /// + /// Constructor. + /// + /// The text reader to read input characters from. + /// If it is isIeee754Compatible + public JsonReader(TextReader reader, bool isIeee754Compatible) + { + Debug.Assert(reader != null, "reader != null"); + + this.nodeType = JsonNodeType.None; + this.nodeValue = null; + this.reader = reader; + this.storedCharacterCount = 0; + this.tokenStartIndex = 0; + this.endOfInputReached = false; + this.isIeee754Compatible = isIeee754Compatible; + this.allowAnnotations = true; + this.scopes = new Stack(); + this.scopes.Push(new Scope(ScopeType.Root)); + } + + /// + /// Various scope types for Json writer. + /// + private enum ScopeType + { + /// + /// Root scope - the top-level of the JSON content. + /// + /// This scope is only once on the stack and that is at the bottom, always. + /// It's used to track the fact that only one top-level value is allowed. + Root, + + /// + /// Array scope - inside an array. + /// + /// This scope is pushed when [ is found and is active before the first and between the elements in the array. + /// Between the elements it's active when the parser is in front of the comma, the parser is never after comma as then + /// it always immediately processed the next token. + Array, + + /// + /// Object scope - inside the object (but not in a property value). + /// + /// This scope is pushed when { is found and is active before the first and between the properties in the object. + /// Between the properties it's active when the parser is in front of the comma, the parser is never after comma as then + /// it always immediately processed the next token. + Object, + + /// + /// Property scope - after the property name and colon and throughout the value. + /// + /// This scope is pushed when a property name and colon is found. + /// The scope remains on the stack while the property value is parsed, but once the property value ends, it's immediately removed + /// so that it doesn't appear on the stack after the value (ever). + Property, + } + + /// + /// Get/sets the character buffer pool. + /// + public ICharArrayPool ArrayPool { get; set; } + + /// + /// The value of the last reported node. + /// + /// This is non-null only if the last node was a PrimitiveValue or Property. + /// If the last node is a PrimitiveValue this property returns the value: + /// - null if the null token was found. + /// - boolean if the true or false token was found. + /// - string if a string token was found. + /// - DateTime if a string token formatted as DateTime was found. + /// - Int32 if a number which fits into the Int32 was found. + /// - Double if a number which doesn't fit into Int32 was found. + /// If the last node is a Property this property returns a string which is the name of the property. + /// + public virtual object Value + { + get + { + if (this.readingStream) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_CannotAccessValueInStreamState); + } + + if (this.canStream) + { + if (this.nodeType != JsonNodeType.Property) + { + this.canStream = false; + } + + if (this.nodeType == JsonNodeType.PrimitiveValue) + { + if (this.characterBuffer[this.tokenStartIndex] == 'n') + { + this.nodeValue = this.ParseNullPrimitiveValue(); + } + else + { + bool hasLeadingBackslash; + this.nodeValue = this.ParseStringPrimitiveValue(out hasLeadingBackslash); + } + } + } + + return this.nodeValue; + } + } + + /// + /// The type of the last node read. + /// + public virtual JsonNodeType NodeType + { + get + { + return this.nodeType; + } + } + + /// + /// if it is IEEE754 compatible + /// + public virtual bool IsIeee754Compatible + { + get + { + return this.isIeee754Compatible; + } + } + + /// + /// Whether the reader can stream the current value. + /// + /// + /// True if the current value can be streamed, otherwise false + /// If the property is a string (or null) it can be streamed + public bool CanStream() + { + return this.canStream; + } + + /// + /// Reads the next node from the input. + /// + /// true if a new node was found, or false if end of input was reached. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Not really feasible to extract code to methods without introducing unnecessary complexity.")] + public virtual bool Read() + { + if (this.readingStream) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_CannotCallReadInStreamState); + } + + if (this.canStream) + { + this.canStream = false; + if (this.nodeType == JsonNodeType.PrimitiveValue) + { + // caller is positioned on a string value that they haven't read, so skip it + if (this.characterBuffer[this.tokenStartIndex] == 'n') + { + this.ParseNullPrimitiveValue(); + } + else + { + this.ParseStringPrimitiveValue(); + } + } + } + + // Reset the node value. + this.nodeValue = null; + +#if DEBUG + // Reset the node type to None - so that we can verify that the Read method actually sets it. + this.nodeType = JsonNodeType.None; +#endif + + // Skip any whitespace characters. + // This also makes sure that we have at least one non-whitespace character available. + if (!this.SkipWhitespaces()) + { + return this.EndOfInput(); + } + + Debug.Assert( + this.tokenStartIndex < this.storedCharacterCount && !IsWhitespaceCharacter(this.characterBuffer[this.tokenStartIndex]), + "The SkipWhitespaces didn't correctly skip all whitespace characters from the input."); + + Scope currentScope = this.scopes.Peek(); + + bool commaFound = false; + if (this.characterBuffer[this.tokenStartIndex] == ',') + { + commaFound = true; + this.tokenStartIndex++; + + // Note that validity of the comma is verified below depending on the current scope. + // Skip all whitespaces after comma. + // Note that this causes "Unexpected EOF" error if the comma is the last thing in the input. + // It might not be the best error message in certain cases, but it's still correct (a JSON payload can never end in comma). + if (!this.SkipWhitespaces()) + { + return this.EndOfInput(); + } + + Debug.Assert( + this.tokenStartIndex < this.storedCharacterCount && !IsWhitespaceCharacter(this.characterBuffer[this.tokenStartIndex]), + "The SkipWhitespaces didn't correctly skip all whitespace characters from the input."); + } + + switch (currentScope.Type) + { + case ScopeType.Root: + if (commaFound) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Root)); + } + + if (currentScope.ValueCount > 0) + { + // We already found the top-level value, so fail + throw JsonReaderExtensions.CreateException(Strings.JsonReader_MultipleTopLevelValues); + } + + // We expect a "value" - start array, start object or primitive value + this.nodeType = this.ParseValue(); + break; + + case ScopeType.Array: + if (commaFound && currentScope.ValueCount == 0) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Array)); + } + + // We might see end of array here + if (this.characterBuffer[this.tokenStartIndex] == ']') + { + this.tokenStartIndex++; + + // End of array is only valid when there was no comma before it. + if (commaFound) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Array)); + } + + this.PopScope(); + this.nodeType = JsonNodeType.EndArray; + break; + } + + if (!commaFound && currentScope.ValueCount > 0) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_MissingComma(ScopeType.Array)); + } + + // We expect element which is a "value" - start array, start object or primitive value + this.nodeType = this.ParseValue(); + break; + + case ScopeType.Object: + if (commaFound && currentScope.ValueCount == 0) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Object)); + } + + // We might see end of object here + if (this.characterBuffer[this.tokenStartIndex] == '}') + { + this.tokenStartIndex++; + + // End of object is only valid when there was no comma before it. + if (commaFound) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Object)); + } + + this.PopScope(); + this.nodeType = JsonNodeType.EndObject; + break; + } + else + { + if (!commaFound && currentScope.ValueCount > 0) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_MissingComma(ScopeType.Object)); + } + + // We expect a property here + this.nodeType = this.ParseProperty(); + break; + } + + case ScopeType.Property: + if (commaFound) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedComma(ScopeType.Property)); + } + + // We expect the property value, which is a "value" - start array, start object or primitive value + this.nodeType = this.ParseValue(); + break; + + default: + throw JsonReaderExtensions.CreateException(Strings.General_InternalError(InternalErrorCodes.JsonReader_Read)); + } + + Debug.Assert( + this.nodeType != JsonNodeType.None && this.nodeType != JsonNodeType.EndOfInput, + "Read should never go back to None and EndOfInput should be reported by directly returning."); + + return true; + } + + /// + /// Creates a stream for reading a base64 binary value. + /// + /// A stream for reading a base64 URL encoded binary value. + public Stream CreateReadStream() + { + if (!this.canStream) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_CannotCreateReadStream); + } + + this.canStream = false; + if ((this.streamOpeningQuoteCharacter = characterBuffer[this.tokenStartIndex]) == 'n') + { + this.ParseNullPrimitiveValue(); + this.scopes.Peek().ValueCount++; + this.Read(); + return new ODataBinaryStreamReader((a, b, c) => { return 0; }); + } + + this.tokenStartIndex++; + this.readingStream = true; + return new ODataBinaryStreamReader(this.ReadChars); + } + + /// + /// Creates a TextReader for reading text values. + /// + /// A TextReader for reading a text value. + public TextReader CreateTextReader() + { + if (!this.canStream) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_CannotCreateTextReader); + } + + this.canStream = false; + this.SkipWhitespaces(); + + Debug.Assert(this.NodeType == JsonNodeType.PrimitiveValue || this.NodeType == JsonNodeType.Property, "Streaming in an unknown state"); + if ((this.streamOpeningQuoteCharacter = characterBuffer[this.tokenStartIndex]) == 'n') + { + // value is null + this.ParseNullPrimitiveValue(); + this.scopes.Peek().ValueCount++; + this.Read(); + return new ODataTextStreamReader((a, b, c) => { return 0; }); + } + + // skip over the opening quote character for a string value + if (this.NodeType == JsonNodeType.PrimitiveValue) + { + this.tokenStartIndex++; + } + + this.readingStream = true; + return new ODataTextStreamReader(this.ReadChars); + } + + /// + /// Dispose the reader + /// + public void Dispose() + { + if (this.ArrayPool != null && this.characterBuffer != null) + { + BufferUtils.ReturnToBuffer(this.ArrayPool, this.characterBuffer); + this.characterBuffer = null; + } + } + + /// + /// Determines if a given character is a whitespace character. + /// + /// The character to test. + /// true if the is a whitespace; false otherwise. + /// Note that the behavior of this method is different from Char.IsWhitespace, since that method + /// returns true for all characters defined as whitespace by the Unicode spec (which is a lot of characters), + /// this one on the other hand recognizes just the whitespaces as defined by the JSON spec. + private static bool IsWhitespaceCharacter(char character) + { + // The whitespace characters are 0x20 (space), 0x09 (tab), 0x0A (new line), 0x0D (carriage return) + // Anything above 0x20 is a non-whitespace character. + if (character > (char)0x20 || character != (char)0x20 && character != (char)0x09 && character != (char)0x0A && character != (char)0x0D) + { + return false; + } + else + { + return true; + } + } + + /// + /// Parses a "value", that is an array, object or primitive value. + /// + /// The node type to report to the user. + private JsonNodeType ParseValue() + { + Debug.Assert( + this.tokenStartIndex < this.storedCharacterCount && !IsWhitespaceCharacter(this.characterBuffer[this.tokenStartIndex]), + "The SkipWhitespaces wasn't called or it didn't correctly skip all whitespace characters from the input."); + Debug.Assert(this.scopes.Count >= 1 && this.scopes.Peek().Type != ScopeType.Object, "Value can only occure at the root, in array or as a property value."); + + // Increase the count of values under the current scope. + this.scopes.Peek().ValueCount++; + + char currentCharacter = this.characterBuffer[this.tokenStartIndex]; + switch (currentCharacter) + { + case '{': + // Start of object + this.PushScope(ScopeType.Object); + this.tokenStartIndex++; + return JsonNodeType.StartObject; + + case '[': + // Start of array + this.PushScope(ScopeType.Array); + this.tokenStartIndex++; + this.SkipWhitespaces(); + this.canStream = + this.characterBuffer[this.tokenStartIndex] == '"' || + this.characterBuffer[this.tokenStartIndex] == '\'' || + this.characterBuffer[this.tokenStartIndex] == 'n'; + return JsonNodeType.StartArray; + + case '"': + case '\'': + // String primitive value + // Don't parse yet, as it may be a stream. Defer parsing until .Value is called. + this.canStream = true; + break; + + case 'n': + // Null value + // Don't parse yet, as user may be streaming a stream. Defer parsing until .Value is called. + this.canStream = true; + break; + + case 't': + case 'f': + this.nodeValue = this.ParseBooleanPrimitiveValue(); + break; + + default: + // COMPAT 47: JSON number can start with dot. + // The JSON spec doesn't allow numbers to start with ., but WCF DS does. We will follow the WCF DS behavior for compatibility. + if (Char.IsDigit(currentCharacter) || (currentCharacter == '-') || (currentCharacter == '.')) + { + this.nodeValue = this.ParseNumberPrimitiveValue(); + break; + } + else + { + // Unknown token - fail. + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedToken); + } + } + + this.TryPopPropertyScope(); + return JsonNodeType.PrimitiveValue; + } + + /// + /// Parses a property name and the colon after it. + /// + /// The node type to report to the user. + private JsonNodeType ParseProperty() + { + // Increase the count of values under the object (the number of properties). + Debug.Assert(this.scopes.Count >= 1 && this.scopes.Peek().Type == ScopeType.Object, "Property can only occur in an object."); + this.scopes.Peek().ValueCount++; + + this.PushScope(ScopeType.Property); + + // Parse the name of the property + this.nodeValue = this.ParseName(); + + if (string.IsNullOrEmpty((string)this.nodeValue)) + { + // The name can't be empty. + throw JsonReaderExtensions.CreateException(Strings.JsonReader_InvalidPropertyNameOrUnexpectedComma((string)this.nodeValue)); + } + + if (!this.SkipWhitespaces() || this.characterBuffer[this.tokenStartIndex] != ':') + { + // We need the colon character after the property name + throw JsonReaderExtensions.CreateException(Strings.JsonReader_MissingColon((string)this.nodeValue)); + } + + // Consume the colon. + Debug.Assert(this.characterBuffer[this.tokenStartIndex] == ':', "The above should verify that there's a colon."); + this.tokenStartIndex++; + this.SkipWhitespaces(); + + // if the content is nested json, we can stream + this.canStream = this.characterBuffer[this.tokenStartIndex] == '{' || this.characterBuffer[this.tokenStartIndex] == '['; + return JsonNodeType.Property; + } + + /// + /// Parses a primitive string value. + /// + /// The value of the string primitive value. + /// + /// Assumes that the current token position points to the opening quote. + /// Note that the string parsing can never end with EndOfInput, since we're already seen the quote. + /// So it can either return a string succesfully or fail. + private string ParseStringPrimitiveValue() + { + bool hasLeadingBackslash; + return this.ParseStringPrimitiveValue(out hasLeadingBackslash); + } + + /// + /// Parses a primitive string value. + /// + /// Set to true if the first character in the string was a backslash. This is used when parsing DateTime values + /// since they must start with an escaped slash character (\/). + /// The value of the string primitive value. + /// + /// Assumes that the current token position points to the opening quote. + /// Note that the string parsing can never end with EndOfInput, since we're already seen the quote. + /// So it can either return a string succesfully or fail. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Splitting the function would make it hard to understand.")] + private string ParseStringPrimitiveValue(out bool hasLeadingBackslash) + { + Debug.Assert(this.tokenStartIndex < this.storedCharacterCount, "At least the quote must be present."); + + hasLeadingBackslash = false; + + // COMPAT 45: We allow both double and single quotes around a primitive string + // Even though JSON spec only allows double quotes. + char openingQuoteCharacter = this.characterBuffer[this.tokenStartIndex]; + Debug.Assert(openingQuoteCharacter == '"' || openingQuoteCharacter == '\'', "The quote character must be the current character when this method is called."); + + // Consume the quote character + this.tokenStartIndex++; + + // String builder to be used if we need to resolve escape sequences. + StringBuilder valueBuilder = null; + + int currentCharacterTokenRelativeIndex = 0; + while ((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount || this.ReadInput()) + { + Debug.Assert((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount, "ReadInput didn't read more data but returned true."); + + char character = this.characterBuffer[this.tokenStartIndex + currentCharacterTokenRelativeIndex]; + if (character == '\\') + { + // If we're at the begining of the string + // (means that relative token index must be 0 and we must not have consumed anything into our value builder yet) + if (currentCharacterTokenRelativeIndex == 0 && valueBuilder == null) + { + hasLeadingBackslash = true; + } + + // We will need the stringbuilder to resolve the escape sequences. + if (valueBuilder == null) + { + if (this.stringValueBuilder == null) + { + this.stringValueBuilder = new StringBuilder(); + } + else + { + this.stringValueBuilder.Length = 0; + } + + valueBuilder = this.stringValueBuilder; + } + + // Append everything up to the \ character to the value. + valueBuilder.Append(this.ConsumeTokenToString(currentCharacterTokenRelativeIndex)); + currentCharacterTokenRelativeIndex = 0; + Debug.Assert(this.characterBuffer[this.tokenStartIndex] == '\\', "We should have consumed everything up to the escape character."); + + // Escape sequence - we need at least two characters, the backslash and the one character after it. + if (!this.EnsureAvailableCharacters(2)) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\")); + } + + // To simplify the code, consume the character after the \ as well, since that is the start of the escape sequence. + character = this.characterBuffer[this.tokenStartIndex + 1]; + this.tokenStartIndex += 2; + + switch (character) + { + case 'b': + valueBuilder.Append('\b'); + break; + case 'f': + valueBuilder.Append('\f'); + break; + case 'n': + valueBuilder.Append('\n'); + break; + case 'r': + valueBuilder.Append('\r'); + break; + case 't': + valueBuilder.Append('\t'); + break; + case '\\': + case '\"': + case '\'': + case '/': + valueBuilder.Append(character); + break; + case 'u': + Debug.Assert(currentCharacterTokenRelativeIndex == 0, "The token should be starting at the first character after the \\u"); + + // We need 4 hex characters + if (!this.EnsureAvailableCharacters(4)) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\uXXXX")); + } + + string unicodeHexValue = this.ConsumeTokenToString(4); + int characterValue; + if (!Int32.TryParse(unicodeHexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out characterValue)) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\u" + unicodeHexValue)); + } + + valueBuilder.Append((char)characterValue); + break; + default: + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\" + character)); + } + } + else if (character == openingQuoteCharacter) + { + // Consume everything up to the quote character + string result = this.ConsumeTokenToString(currentCharacterTokenRelativeIndex); + Debug.Assert(this.characterBuffer[this.tokenStartIndex] == openingQuoteCharacter, "We should have consumed everything up to the quote character."); + + // Consume the quote character as well. + this.tokenStartIndex++; + + if (valueBuilder != null) + { + valueBuilder.Append(result); + result = valueBuilder.ToString(); + } + + return result; + } + else + { + // Normal character, just skip over it - it will become part of the value as is. + currentCharacterTokenRelativeIndex++; + } + } + + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedEndOfString); + } + + /// + /// Parses the null primitive value. + /// + /// Always returns null if successful. Otherwise throws. + /// Assumes that the current token position points to the 'n' character. + private object ParseNullPrimitiveValue() + { + Debug.Assert( + this.tokenStartIndex < this.storedCharacterCount && this.characterBuffer[this.tokenStartIndex] == 'n', + "The method should only be called when the 'n' character is the start of the token."); + + // We can call ParseName since we know the first character is 'n' and thus it won't be quoted. + string token = this.ParseName(); + + if (!string.Equals(token, JsonConstants.JsonNullLiteral, StringComparison.Ordinal)) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedToken(token)); + } + + return null; + } + + /// + /// Parses the true or false primitive values. + /// + /// true of false boolean value if successful. Otherwise throws. + /// Assumes that the current token position points to the 't' or 'f' character. + private object ParseBooleanPrimitiveValue() + { + Debug.Assert( + this.tokenStartIndex < this.storedCharacterCount && (this.characterBuffer[this.tokenStartIndex] == 't' || this.characterBuffer[this.tokenStartIndex] == 'f'), + "The method should only be called when the 't' or 'f' character is the start of the token."); + + // We can call ParseName since we know the first character is 't' or 'f' and thus it won't be quoted. + string token = this.ParseName(); + + if (string.Equals(token, JsonConstants.JsonFalseLiteral, StringComparison.Ordinal)) + { + return false; + } + + if (string.Equals(token, JsonConstants.JsonTrueLiteral, StringComparison.Ordinal)) + { + return true; + } + + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedToken(token)); + } + + /// + /// Parses the number primitive values. + /// + /// Parse value to Int32, Decimal or Double. Otherwise throws. + /// Assumes that the current token position points to the first character of the number, so either digit, dot or dash. + private object ParseNumberPrimitiveValue() + { + Debug.Assert( + this.tokenStartIndex < this.storedCharacterCount && (this.characterBuffer[this.tokenStartIndex] == '.' || this.characterBuffer[this.tokenStartIndex] == '-' || Char.IsDigit(this.characterBuffer[this.tokenStartIndex])), + "The method should only be called when a digit, dash or dot character is the start of the token."); + + // Walk over all characters which might belong to the number + // Skip the first one since we already verified it belongs to the number. + int currentCharacterTokenRelativeIndex = 1; + while ((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount || this.ReadInput()) + { + char character = this.characterBuffer[this.tokenStartIndex + currentCharacterTokenRelativeIndex]; + if (Char.IsDigit(character) || + (character == '.') || + (character == 'E') || + (character == 'e') || + (character == '-') || + (character == '+')) + { + currentCharacterTokenRelativeIndex++; + } + else + { + break; + } + } + + // We now have all the characters which belong to the number, consume it into a string. + string numberString = this.ConsumeTokenToString(currentCharacterTokenRelativeIndex); + double doubleValue; + int intValue; + decimal decimalValue; + + // We will first try and convert the value to Int32. If it succeeds, use that. + // And then, we will try Decimal, since it will lose precision while expected type is specified. + // Otherwise, we will try and convert the value into a double. + if (Int32.TryParse(numberString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out intValue)) + { + return intValue; + } + + // if it is not Ieee754Compatible, decimal will be parsed before double to keep precision + if (!isIeee754Compatible && Decimal.TryParse(numberString, NumberStyles.Number, NumberFormatInfo.InvariantInfo, out decimalValue)) + { + return decimalValue; + } + + if (Double.TryParse(numberString, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out doubleValue)) + { + return doubleValue; + } + + throw JsonReaderExtensions.CreateException(Strings.JsonReader_InvalidNumberFormat(numberString)); + } + + /// + /// Parses a name token. + /// + /// The value of the name token. + /// Name tokens are (for backward compat reasons) either + /// - string value quoted with double quotes. + /// - string value quoted with single quotes. + /// - sequence of letters, digits, underscores and dollar signs (without quoted and in any order). + private string ParseName() + { + Debug.Assert(this.tokenStartIndex < this.storedCharacterCount, "Must have at least one character available."); + + char firstCharacter = this.characterBuffer[this.tokenStartIndex]; + if ((firstCharacter == '"') || (firstCharacter == '\'')) + { + return this.ParseStringPrimitiveValue(); + } + + int currentCharacterTokenRelativeIndex = 0; + do + { + Debug.Assert(this.tokenStartIndex < this.storedCharacterCount, "Must have at least one character available."); + + char character = this.characterBuffer[this.tokenStartIndex + currentCharacterTokenRelativeIndex]; + + // COMPAT 46: JSON property names don't require quotes and they allow any letter, digit, underscore or dollar sign in them. + if (character == '_' || + Char.IsLetterOrDigit(character) || + character == '$' || + (this.allowAnnotations && (character == '.' || character == '@'))) + { + currentCharacterTokenRelativeIndex++; + } + else + { + break; + } + } + while ((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount || this.ReadInput()); + + return this.ConsumeTokenToString(currentCharacterTokenRelativeIndex); + } + + /// + /// Reads bytes from the current string value. + /// + /// The character buffer to populate + /// The number of characters offset into the buffer to read + /// The maximum number of characters to read into the buffer + /// The number of characters read. + /// + /// Assumes that the current token position points to the opening quote. + /// Note that the string parsing can never end with EndOfInput, since we're already seen the quote. + /// So it can either return a string succesfully or fail. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Splitting the function would make it hard to understand.")] + private int ReadChars(char[] chars, int offset, int maxLength) + { + if (!readingStream) + { + return 0; + } + + int charsRead = 0; + + while (charsRead < maxLength && (this.tokenStartIndex < this.storedCharacterCount || this.ReadInput())) + { + char character = this.characterBuffer[this.tokenStartIndex]; + bool advance = true; + + if (character == GetClosingQuoteCharacter(this.streamOpeningQuoteCharacter) && --this.balancedQuoteCount < 1) + { + if (character != '"') + { + // The character is part of the JSON stream; copy it to the output buffer + chars[charsRead + offset] = character; + charsRead++; + this.scopes.Pop(); + } + + // Consume the closing quote character. + this.tokenStartIndex++; + readingStream = false; + this.scopes.Peek().ValueCount++; + + // move to next node + this.Read(); + return charsRead; + } + + if (character == this.streamOpeningQuoteCharacter) + { + this.balancedQuoteCount++; + } + + if (character == '\\') + { + // Consume the escape + this.tokenStartIndex++; + if (!this.EnsureAvailableCharacters(1)) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\uXXXX")); + } + + character = this.characterBuffer[this.tokenStartIndex]; + + switch (character) + { + case 'b': + character = '\b'; + break; + case 'f': + character = '\f'; + break; + case 'n': + character = '\n'; + break; + case 'r': + character = '\r'; + break; + case 't': + character = '\t'; + break; + case '\\': + case '\"': + case '\'': + case '/': + // Use the (now unescaped) character from reader; + break; + case 'u': + + // Consume the "u" + tokenStartIndex++; + + // We need 4 hex characters + if (!this.EnsureAvailableCharacters(4)) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\uXXXX")); + } + + string unicodeHexValue = this.ConsumeTokenToString(4); + int characterValue; + if (!Int32.TryParse(unicodeHexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out characterValue)) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\u" + unicodeHexValue)); + } + + character = (char)characterValue; + + // We are already positioned on the next character, so don't advance at the end + advance = false; + break; + default: + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnrecognizedEscapeSequence("\\" + character)); + } + } + + chars[charsRead + offset] = character; + charsRead++; + + if (advance) + { + this.tokenStartIndex++; + } + } + + // we reached the end of the file without finding a closing quote character + if (charsRead < maxLength) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_UnexpectedEndOfString); + } + + return charsRead; + } + + private static char GetClosingQuoteCharacter(char openingCharacter) + { + switch (openingCharacter) + { + case '{': + return '}'; + case '[': + return ']'; + default: + return openingCharacter; + } + } + + /// + /// Called when end of input is reached. + /// + /// Always returns false, used for easy readability of the callers. + private bool EndOfInput() + { + // We should be ending the input only with Root in the scope. + if (this.scopes.Count > 1) + { + // Not all open scopes were closed. + throw JsonReaderExtensions.CreateException(Strings.JsonReader_EndOfInputWithOpenScope); + } + + Debug.Assert( + this.scopes.Count > 0 && this.scopes.Peek().Type == ScopeType.Root && this.scopes.Peek().ValueCount <= 1, + "The end of input should only occure with root at the top of the stack with zero or one value."); + Debug.Assert(this.nodeValue == null, "The node value should have been reset to null."); + + this.nodeType = JsonNodeType.EndOfInput; + + if (this.ArrayPool != null) + { + BufferUtils.ReturnToBuffer(this.ArrayPool, this.characterBuffer); + this.characterBuffer = null; + } + + return false; + } + + /// + /// Creates a new scope of type and pushes the stack. + /// + /// The scope type to push. + private void PushScope(ScopeType newScopeType) + { + Debug.Assert(this.scopes.Count >= 1, "The root must always be on the stack."); + Debug.Assert(newScopeType != ScopeType.Root, "We should never try to push root scope."); + Debug.Assert(newScopeType != ScopeType.Property || this.scopes.Peek().Type == ScopeType.Object, "We should only try to push property onto an object."); + Debug.Assert(newScopeType == ScopeType.Property || this.scopes.Peek().Type != ScopeType.Object, "We should only try to push property onto an object."); + + this.scopes.Push(new Scope(newScopeType)); + } + + /// + /// Pops a scope from the stack. + /// + private void PopScope() + { + Debug.Assert(this.scopes.Count > 1, "We can never pop the root."); + Debug.Assert(this.scopes.Peek().Type != ScopeType.Property, "We should never try to pop property alone."); + + this.scopes.Pop(); + this.TryPopPropertyScope(); + } + + /// + /// Pops a property scope if it's present on the stack. + /// + private void TryPopPropertyScope() + { + Debug.Assert(this.scopes.Count > 0, "There should always be at least root on the stack."); + if (this.scopes.Peek().Type == ScopeType.Property) + { + Debug.Assert(this.scopes.Count > 2, "If the property is at the top of the stack there must be an object after it and then root."); + this.scopes.Pop(); + Debug.Assert(this.scopes.Peek().Type == ScopeType.Object, "The parent of a property must be an object."); + } + } + + /// + /// Skips all whitespace characters in the input. + /// + /// true if a non-whitespace character was found in which case the tokenStartIndex is pointing at that character. + /// false if there are no non-whitespace characters left in the input. + private bool SkipWhitespaces() + { + do + { + for (; this.tokenStartIndex < this.storedCharacterCount; this.tokenStartIndex++) + { + if (!IsWhitespaceCharacter(this.characterBuffer[this.tokenStartIndex])) + { + return true; + } + } + } + while (this.ReadInput()); + + return false; + } + + /// + /// Ensures that a specified number of characters after the token start is available in the buffer. + /// + /// The number of character after the token to make available. + /// true if at least the required number of characters is available; false if end of input was reached. + private bool EnsureAvailableCharacters(int characterCountAfterTokenStart) + { + while (this.tokenStartIndex + characterCountAfterTokenStart > this.storedCharacterCount) + { + if (!this.ReadInput()) + { + return false; + } + } + + return true; + } + + /// + /// Consumes the characters starting at the start of the token + /// and returns them as a string. + /// + /// The number of characters after the token start to consume. + /// The string value of the consumed token. + private string ConsumeTokenToString(int characterCount) + { + Debug.Assert(characterCount >= 0, "characterCount >= 0"); + Debug.Assert(this.tokenStartIndex + characterCount <= this.storedCharacterCount, "characterCount specified characters outside of the available range."); + + string result = new string(this.characterBuffer, this.tokenStartIndex, characterCount); + this.tokenStartIndex += characterCount; + + return result; + } + + /// + /// Reads more characters from the input. + /// + /// true if more characters are available; false if end of input was reached. + /// This may move characters in the characterBuffer, so after this is called + /// all indeces to the characterBuffer are invalid except for tokenStartIndex. + private bool ReadInput() + { + Debug.Assert(this.tokenStartIndex >= 0 && this.tokenStartIndex <= this.storedCharacterCount, "The token start is out of stored characters range."); + + if (this.endOfInputReached) + { + return false; + } + + // initialze the buffer + if (this.characterBuffer == null) + { + this.characterBuffer = BufferUtils.RentFromBuffer(ArrayPool, InitialCharacterBufferSize); + } + + Debug.Assert(this.storedCharacterCount <= this.characterBuffer.Length, "We can only store as many characters as fit into our buffer."); + + // If the buffer is empty (all characters were consumed from it), just start over. + if (this.tokenStartIndex == this.storedCharacterCount) + { + this.tokenStartIndex = 0; + this.storedCharacterCount = 0; + } + else if (this.storedCharacterCount == this.characterBuffer.Length) + { + // No more room in the buffer, move or grow the buffer. + if (this.tokenStartIndex < this.characterBuffer.Length / 4) + { + // The entire buffer is full of unconsumed characters + // We need to grow the buffer. Double the size of the buffer. + if (this.characterBuffer.Length == int.MaxValue) + { + throw JsonReaderExtensions.CreateException(Strings.JsonReader_MaxBufferReached); + } + + int newBufferSize = this.characterBuffer.Length * 2; + newBufferSize = newBufferSize < 0 ? int.MaxValue : newBufferSize; // maybe overflow + + char[] newCharacterBuffer = BufferUtils.RentFromBuffer(ArrayPool, newBufferSize); + + // Copy the existing characters to the new buffer. + Array.Copy(this.characterBuffer, this.tokenStartIndex, newCharacterBuffer, 0, + this.storedCharacterCount - this.tokenStartIndex); + this.storedCharacterCount = this.storedCharacterCount - this.tokenStartIndex; + this.tokenStartIndex = 0; + + // And switch the buffers + BufferUtils.ReturnToBuffer(ArrayPool, this.characterBuffer); + this.characterBuffer = newCharacterBuffer; + } + else + { + // Some characters were consumed, we can just move them in the buffer + // to get more room without allocating. + Array.Copy(this.characterBuffer, this.tokenStartIndex, this.characterBuffer, 0, + this.storedCharacterCount - this.tokenStartIndex); + this.storedCharacterCount -= this.tokenStartIndex; + this.tokenStartIndex = 0; + } + } + + Debug.Assert( + this.storedCharacterCount < this.characterBuffer.Length, + "We should have more room in the buffer by now."); + + // Read more characters from the input. + // Use the Read method which returns any character as soon as it's available + // we don't want to wait for the entire buffer to fill if the input doesn't have + // the characters ready. + int readCount = this.reader.Read( + this.characterBuffer, + this.storedCharacterCount, + this.characterBuffer.Length - this.storedCharacterCount); + + if (readCount == 0) + { + // No more characters available, end of input. + this.endOfInputReached = true; + return false; + } + + this.storedCharacterCount += readCount; + return true; + } + + /// + /// Class representing scope information. + /// + private sealed class Scope + { + /// + /// The type of the scope. + /// + private readonly ScopeType type; + + /// + /// Constructor. + /// + /// The type of the scope. + public Scope(ScopeType type) + { + this.type = type; + } + + /// + /// Get/Set the number of values found under the current scope. + /// + public int ValueCount + { + get; + set; + } + + /// + /// Gets the scope type for this scope. + /// + public ScopeType Type + { + get + { + return this.type; + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs new file mode 100644 index 0000000..b962c1d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonReaderExtensions.cs @@ -0,0 +1,412 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + /// + /// Extension methods for the JSON reader. + /// + internal static class JsonReaderExtensions + { + /// + /// Reads the next node from the and verifies that it is a StartObject node. + /// + /// The to read from. + internal static void ReadStartObject(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + ReadNext(jsonReader, JsonNodeType.StartObject); + } + + /// + /// Reads the next node from the and verifies that it is an EndObject node. + /// + /// The to read from. + internal static void ReadEndObject(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + ReadNext(jsonReader, JsonNodeType.EndObject); + } + + /// + /// Reads the next node from the and verifies that it is an StartArray node. + /// + /// The to read from. + internal static void ReadStartArray(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + ReadNext(jsonReader, JsonNodeType.StartArray); + } + + /// + /// Reads the next node from the and verifies that it is an EndArray node. + /// + /// The to read from. + internal static void ReadEndArray(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + ReadNext(jsonReader, JsonNodeType.EndArray); + } + + /// + /// Verifies that the current node is a property node and returns the property name. + /// + /// The to read from. + /// The property name of the current property node. + internal static string GetPropertyName(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + Debug.Assert(jsonReader.NodeType == JsonNodeType.Property, "jsonReader.NodeType == JsonNodeType.Property"); + + // NOTE: the JSON reader already verifies that property names are strings and not null/empty + string propertyName = (string)jsonReader.Value; + + return propertyName; + } + + /// + /// Reads the next node from the , verifies that it is a Property node and returns the property name. + /// + /// The to read from. + /// The property name of the property node read. + internal static string ReadPropertyName(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + jsonReader.ValidateNodeType(JsonNodeType.Property); + string propertyName = jsonReader.GetPropertyName(); + jsonReader.ReadNext(); + return propertyName; + } + + /// + /// Reads the next node from the and verifies that it is a PrimitiveValue node. + /// + /// The to read from. + /// The primitive value read from the reader. + internal static object ReadPrimitiveValue(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + object value = jsonReader.Value; + ReadNext(jsonReader, JsonNodeType.PrimitiveValue); + return value; + } + + /// + /// Reads the next node from the and verifies that it is a PrimitiveValue node of type string. + /// + /// The to read from. + /// The string value read from the reader; throws an exception if no string value could be read. + internal static string ReadStringValue(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + object value = jsonReader.ReadPrimitiveValue(); + string stringValue = value as string; + if (value == null || stringValue != null) + { + return stringValue; + } + + throw CreateException(Strings.JsonReaderExtensions_CannotReadValueAsString(value)); + } + + /// + /// Reads the next node from the as a URI and verifies that it is a PrimitiveValue node of type string. + /// + /// The to read from. + /// The string value read from the reader as a URI; throws an exception if no string value could be read. + internal static Uri ReadUriValue(this IJsonReader jsonReader) + { + return UriUtils.StringToUri(ReadStringValue(jsonReader)); + } + + /// + /// Reads the next node from the and verifies that it is a PrimitiveValue node of type string. + /// + /// The to read from. + /// The name of the property for which to read the string; used in error messages only. + /// The string value read from the reader; throws an exception if no string value could be read. + internal static string ReadStringValue(this IJsonReader jsonReader, string propertyName) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + object value = jsonReader.ReadPrimitiveValue(); + string stringValue = value as string; + if (value == null || stringValue != null) + { + return stringValue; + } + + throw CreateException(Strings.JsonReaderExtensions_CannotReadPropertyValueAsString(value, propertyName)); + } + + /// + /// Reads the next node from the and verifies that it is a PrimitiveValue node of type double. + /// + /// The to read from. + /// The double value read from the reader; throws an exception if no double value could be read. + internal static double? ReadDoubleValue(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + + object value = jsonReader.ReadPrimitiveValue(); + double? doubleValue = value as double?; + if (value == null || doubleValue != null) + { + return doubleValue; + } + + int? intValue = value as int?; + if (intValue != null) + { + return (double)intValue; + } + + decimal? decimalValue = value as decimal?; + if (decimalValue != null) + { + return (double)decimalValue; + } + + throw CreateException(Strings.JsonReaderExtensions_CannotReadValueAsDouble(value)); + } + + /// + /// Skips over a JSON value (primitive, object or array). + /// + /// The to read from. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue, JsonNodeType.StartArray or JsonNodeType.StartObject + /// Post-Condition: JsonNodeType.PrimitiveValue, JsonNodeType.EndArray or JsonNodeType.EndObject + /// + internal static void SkipValue(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + int depth = 0; + do + { + switch (jsonReader.NodeType) + { + case JsonNodeType.StartArray: + case JsonNodeType.StartObject: + depth++; + break; + + case JsonNodeType.EndArray: + case JsonNodeType.EndObject: + Debug.Assert(depth > 0, "Seen too many scope ends."); + depth--; + break; + + default: + Debug.Assert( + jsonReader.NodeType != JsonNodeType.EndOfInput, + "We should not have reached end of input, since the scopes should be well formed. Otherwise JsonReader should have failed by now."); + break; + } + } + while (jsonReader.Read() && depth > 0); + + if (depth > 0) + { + // Not all open scopes were closed: + // "Invalid JSON. Unexpected end of input was found in JSON content. Not all object and array scopes were closed." + throw JsonReaderExtensions.CreateException(Strings.JsonReader_EndOfInputWithOpenScope); + } + } + + /// + /// Skips over a JSON value (primitive, object or array), and append raw string to StringBuilder. + /// + /// The to read from. + /// The StringBuilder to receive JSON raw string. + internal static void SkipValue(this IJsonReader jsonReader, StringBuilder jsonRawValueStringBuilder) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + using (StringWriter stringWriter = new StringWriter(jsonRawValueStringBuilder, CultureInfo.InvariantCulture)) + { + JsonWriter jsonWriter = new JsonWriter(stringWriter, isIeee754Compatible: false); + int depth = 0; + do + { + switch (jsonReader.NodeType) + { + case JsonNodeType.PrimitiveValue: + if (jsonReader.Value == null) + { + jsonWriter.WriteValue((string)null); + } + else + { + jsonWriter.WritePrimitiveValue(jsonReader.Value); + } + + break; + + case JsonNodeType.StartArray: + jsonWriter.StartArrayScope(); + depth++; + break; + + case JsonNodeType.StartObject: + jsonWriter.StartObjectScope(); + depth++; + break; + + case JsonNodeType.EndArray: + jsonWriter.EndArrayScope(); + Debug.Assert(depth > 0, "Seen too many scope ends."); + depth--; + break; + + case JsonNodeType.EndObject: + jsonWriter.EndObjectScope(); + Debug.Assert(depth > 0, "Seen too many scope ends."); + depth--; + break; + + case JsonNodeType.Property: + jsonWriter.WriteName(jsonReader.GetPropertyName()); + break; + + default: + Debug.Assert( + jsonReader.NodeType != JsonNodeType.EndOfInput, + "We should not have reached end of input, since the scopes should be well formed. Otherwise JsonReader should have failed by now."); + break; + } + } + while (jsonReader.Read() && depth > 0); + + if (depth > 0) + { + // Not all open scopes were closed: + // "Invalid JSON. Unexpected end of input was found in JSON content. Not all object and array scopes were closed." + throw JsonReaderExtensions.CreateException(Strings.JsonReader_EndOfInputWithOpenScope); + } + + jsonWriter.Flush(); + } + } + + internal static ODataValue ReadAsUntypedOrNullValue(this IJsonReader jsonReader) + { + StringBuilder builder = new StringBuilder(); + jsonReader.SkipValue(builder); + Debug.Assert(builder.Length > 0, "builder.Length > 0"); + return new ODataUntypedValue() + { + RawValue = builder.ToString(), + }; + } + + /// + /// Reads the next node. Use this instead of the direct call to Read since this asserts that there actually is a next node. + /// + /// The to read from. + /// The node type of the node that reader is positioned on after reading. + internal static JsonNodeType ReadNext(this IJsonReader jsonReader) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + +#if DEBUG + bool result = jsonReader.Read(); + Debug.Assert(result, "JsonReader.Read returned false in an unexpected place."); +#else + jsonReader.Read(); +#endif + return jsonReader.NodeType; + } + + /// + /// Determines if the reader is on a value node. + /// + /// The reader to inspect. + /// true if the reader is on PrimitiveValue, StartObject or StartArray node, false otherwise. + internal static bool IsOnValueNode(this IJsonReader jsonReader) + { + JsonNodeType nodeType = jsonReader.NodeType; + return nodeType == JsonNodeType.PrimitiveValue || nodeType == JsonNodeType.StartObject || nodeType == JsonNodeType.StartArray; + } + + /// + /// Asserts that the reader is not buffer. + /// + /// The to read from. + [Conditional("DEBUG")] + internal static void AssertNotBuffering(this BufferingJsonReader bufferedJsonReader) + { +#if DEBUG + Debug.Assert(!bufferedJsonReader.IsBuffering, "!bufferedJsonReader.IsBuffering"); +#endif + } + + /// + /// Asserts that the reader is buffer. + /// + /// The to read from. + [Conditional("DEBUG")] + internal static void AssertBuffering(this BufferingJsonReader bufferedJsonReader) + { +#if DEBUG + Debug.Assert(bufferedJsonReader.IsBuffering, "bufferedJsonReader.IsBuffering"); +#endif + } + + /// + /// Creates an exception instance that is appropriate for the current library being built. + /// Allows the code in this class to be shared between ODataLib and the common spatial library. + /// + /// String to use for the exception message. + /// Exception to be thrown. + internal static ODataException CreateException(string exceptionMessage) + { + return new ODataException(exceptionMessage); + } + + /// + /// Reads the next node from the and verifies that it is of the expected node type. + /// + /// The to read from. + /// The expected of the read node. + private static void ReadNext(this IJsonReader jsonReader, JsonNodeType expectedNodeType) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + Debug.Assert(expectedNodeType != JsonNodeType.None, "expectedNodeType != JsonNodeType.None"); + + jsonReader.ValidateNodeType(expectedNodeType); + jsonReader.Read(); + } + + /// + /// Validates that the reader is positioned on the specified node type. + /// + /// The to use. + /// The expected node type. + private static void ValidateNodeType(this IJsonReader jsonReader, JsonNodeType expectedNodeType) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + Debug.Assert(expectedNodeType != JsonNodeType.None, "expectedNodeType != JsonNodeType.None"); + + if (jsonReader.NodeType != expectedNodeType) + { + throw CreateException(Strings.JsonReaderExtensions_UnexpectedNodeDetected(expectedNodeType, jsonReader.NodeType)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonSharedUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonSharedUtils.cs new file mode 100644 index 0000000..b159d71 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonSharedUtils.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using Microsoft.OData.Edm; + #endregion + + /// + /// Shared JSON util code for ODataLib and Server. + /// + internal static class JsonSharedUtils + { + /// + /// Determines if the given double is serialized as a string in JSON. + /// + /// The value to check. + /// true if the value should be written as a string, false if should be written as a JSON number. + internal static bool IsDoubleValueSerializedAsString(double value) + { + return Double.IsInfinity(value) || Double.IsNaN(value); + } + + /// + /// Determines if the given primitive value is of a basic type where we can rely on just the JSON representation to convey type information. + /// If so, we don't have to write the type name. + /// + /// The primitive value in question. + /// The type of the primitive value. + /// true if the given primitive value is of a basic JSON type, false otherwise. + internal static bool ValueTypeMatchesJsonType(ODataPrimitiveValue primitiveValue, IEdmPrimitiveTypeReference valueTypeReference) + { + return ValueTypeMatchesJsonType(primitiveValue, valueTypeReference.PrimitiveKind()); + } + + internal static bool ValueTypeMatchesJsonType(ODataPrimitiveValue primitiveValue, EdmPrimitiveTypeKind primitiveTypeKind) + { + switch (primitiveTypeKind) + { + // If the value being serialized is of a basic type where we can rely on just the JSON representation to convey type information, then never write the type name. + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.String: + case EdmPrimitiveTypeKind.Boolean: + return true; + + case EdmPrimitiveTypeKind.Double: + double doubleValue = (double)primitiveValue.Value; + + // If a double value is positive infinity, negative infinity, or NaN, we serialize the double as a string. + // Thus the reader can't infer the type from the JSON representation, and we must write the type name explicitly + // (i.e., if the property is open or the property type is assumed to be unknown, as is the case when writing in full metadata mode). + return !IsDoubleValueSerializedAsString(doubleValue); + + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonValueUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonValueUtils.cs new file mode 100644 index 0000000..2b2beb7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonValueUtils.cs @@ -0,0 +1,726 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + using Microsoft.OData.Buffers; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Provides helper method for converting data values to and from the OData JSON format. + /// + internal static class JsonValueUtils + { + /// + /// PositiveInfinitySymbol used in OData Json format + /// + internal static readonly string ODataJsonPositiveInfinitySymbol = "INF"; + + /// + /// NegativeInfinitySymbol used in OData Json format + /// + internal static readonly string ODataJsonNegativeInfinitySymbol = "-INF"; + + /// + /// The NumberFormatInfo used in OData Json format. + /// + internal static readonly NumberFormatInfo ODataNumberFormatInfo = JsonValueUtils.InitializeODataNumberFormatInfo(); + + /// + /// Const tick value for calculating tick values. + /// + private static readonly long JsonDateTimeMinTimeTicks = (new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Ticks; + + /// + /// Characters which, if found inside a number, indicate that the number is a double when no other type information is available. + /// + private static readonly char[] DoubleIndicatingCharacters = new char[] { '.', 'e', 'E' }; + + /// + /// Map of special characters to strings. + /// + private static readonly string[] SpecialCharToEscapedStringMap = JsonValueUtils.CreateSpecialCharToEscapedStringMap(); + + /// + /// Write a char value. + /// + /// The text writer to write the output to. + /// The char value to write. + /// The ODataStringEscapeOption to use in escaping the string. + internal static void WriteValue(TextWriter writer, char value, ODataStringEscapeOption stringEscapeOption) + { + Debug.Assert(writer != null, "writer != null"); + + if (stringEscapeOption == ODataStringEscapeOption.EscapeNonAscii || value <= 0x7F) + { + string escapedString = JsonValueUtils.SpecialCharToEscapedStringMap[value]; + if (escapedString != null) + { + writer.Write(escapedString); + return; + } + } + + writer.Write(value); + } + + /// + /// Write a boolean value. + /// + /// The text writer to write the output to. + /// The boolean value to write. + internal static void WriteValue(TextWriter writer, bool value) + { + Debug.Assert(writer != null, "writer != null"); + + writer.Write(value ? JsonConstants.JsonTrueLiteral : JsonConstants.JsonFalseLiteral); + } + + /// + /// Write an integer value. + /// + /// The text writer to write the output to. + /// Integer value to be written. + internal static void WriteValue(TextWriter writer, int value) + { + Debug.Assert(writer != null, "writer != null"); + + writer.Write(value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Write a float value. + /// + /// The text writer to write the output to. + /// Float value to be written. + internal static void WriteValue(TextWriter writer, float value) + { + Debug.Assert(writer != null, "writer != null"); + + if (float.IsInfinity(value) || float.IsNaN(value)) + { + JsonValueUtils.WriteQuoted(writer, value.ToString(JsonValueUtils.ODataNumberFormatInfo)); + } + else + { + // float.ToString() supports a max scale of six, + // whereas float.MinValue and float.MaxValue have 8 digits scale. Hence we need + // to use XmlConvert in all other cases, except infinity + writer.Write(XmlConvert.ToString(value)); + } + } + + /// + /// Write a short value. + /// + /// The text writer to write the output to. + /// Short value to be written. + internal static void WriteValue(TextWriter writer, short value) + { + Debug.Assert(writer != null, "writer != null"); + + writer.Write(value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Write a long value. + /// + /// The text writer to write the output to. + /// Long value to be written. + internal static void WriteValue(TextWriter writer, long value) + { + Debug.Assert(writer != null, "writer != null"); + + writer.Write(value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Write a double value. + /// + /// The text writer to write the output to. + /// Double value to be written. + internal static void WriteValue(TextWriter writer, double value) + { + Debug.Assert(writer != null, "writer != null"); + + if (JsonSharedUtils.IsDoubleValueSerializedAsString(value)) + { + JsonValueUtils.WriteQuoted(writer, value.ToString(JsonValueUtils.ODataNumberFormatInfo)); + } + else + { + // double.ToString() supports a max scale of 14, + // whereas double.MinValue and double.MaxValue have 16 digits scale. Hence we need + // to use XmlConvert in all other cases, except infinity + string valueToWrite = XmlConvert.ToString(value); + + writer.Write(valueToWrite); + if (valueToWrite.IndexOfAny(JsonValueUtils.DoubleIndicatingCharacters) < 0) + { + writer.Write(".0"); + } + } + } + + /// + /// Write a Guid value. + /// + /// The text writer to write the output to. + /// Guid value to be written. + internal static void WriteValue(TextWriter writer, Guid value) + { + Debug.Assert(writer != null, "writer != null"); + + JsonValueUtils.WriteQuoted(writer, value.ToString()); + } + + /// + /// Write a decimal value + /// + /// The text writer to write the output to. + /// Decimal value to be written. + internal static void WriteValue(TextWriter writer, decimal value) + { + Debug.Assert(writer != null, "writer != null"); + + writer.Write(value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Write a DateTimeOffset value. + /// + /// The text writer to write the output to. + /// DateTimeOffset value to be written. + /// The format to write out the DateTime value in. + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.OData.Json.JsonValueUtils.WriteQuoted(System.IO.TextWriter,System.String)", Justification = "Constant defined by the JSON spec.")] + internal static void WriteValue(TextWriter writer, DateTimeOffset value, ODataJsonDateTimeFormat dateTimeFormat) + { + Debug.Assert(writer != null, "writer != null"); + + Int32 offsetMinutes = (Int32)value.Offset.TotalMinutes; + + switch (dateTimeFormat) + { + case ODataJsonDateTimeFormat.ISO8601DateTime: + { + // Uses the same format as DateTime but with offset: + // jsonDateTime= quotation-mark + // YYYY-MM-DDThh:mm:ss.sTZD + // [("+" / "-") offset] + // quotation-mark + // + // offset = 4DIGIT + string textValue = XmlConvert.ToString(value); + JsonValueUtils.WriteQuoted(writer, textValue); + } + + break; + + case ODataJsonDateTimeFormat.ODataDateTime: + { + // Uses the same format as DateTime but with offset: + // jsonDateTime= quotation-mark + // "\/Date(" + // ticks + // [("+" / "-") offset] + // ")\/" + // quotation-mark + // + // ticks = *DIGIT + // offset = 4DIGIT + string textValue = String.Format( + CultureInfo.InvariantCulture, + JsonConstants.ODataDateTimeOffsetFormat, + JsonValueUtils.DateTimeTicksToJsonTicks(value.Ticks), + offsetMinutes >= 0 ? JsonConstants.ODataDateTimeOffsetPlusSign : string.Empty, + offsetMinutes); + JsonValueUtils.WriteQuoted(writer, textValue); + } + + break; + } + } + + /// + /// Write a TimeSpan value. + /// + /// The text writer to write the output to. + /// TimeSpan value to be written. + internal static void WriteValue(TextWriter writer, TimeSpan value) + { + Debug.Assert(writer != null, "writer != null"); + + JsonValueUtils.WriteQuoted(writer, EdmValueWriter.DurationAsXml(value)); + } + + /// + /// Write a TimeOfDay value + /// + /// The text writer to write the output to. + /// TimeOfDay value to be written. + internal static void WriteValue(TextWriter writer, TimeOfDay value) + { + Debug.Assert(writer != null, "writer != null"); + + JsonValueUtils.WriteQuoted(writer, value.ToString()); + } + + /// + /// Write a Date value + /// + /// The text writer to write the output to. + /// Date value to be written. + internal static void WriteValue(TextWriter writer, Date value) + { + Debug.Assert(writer != null, "writer != null"); + + JsonValueUtils.WriteQuoted(writer, value.ToString()); + } + + /// + /// Write a byte value. + /// + /// The text writer to write the output to. + /// Byte value to be written. + internal static void WriteValue(TextWriter writer, byte value) + { + Debug.Assert(writer != null, "writer != null"); + + writer.Write(value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Write an sbyte value. + /// + /// The text writer to write the output to. + /// SByte value to be written. + internal static void WriteValue(TextWriter writer, sbyte value) + { + Debug.Assert(writer != null, "writer != null"); + + writer.Write(value.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Write a string value. + /// + /// The text writer to write the output to. + /// String value to be written. + /// The string escape option. + /// Char buffer to use for streaming data. + /// Array pool for renting a buffer. + internal static void WriteValue(TextWriter writer, string value, ODataStringEscapeOption stringEscapeOption, ref char[] buffer, ICharArrayPool arrayPool = null) + { + Debug.Assert(writer != null, "writer != null"); + + if (value == null) + { + writer.Write(JsonConstants.JsonNullLiteral); + } + else + { + JsonValueUtils.WriteEscapedJsonString(writer, value, stringEscapeOption, ref buffer, arrayPool); + } + } + + /// + /// Write a byte array. + /// + /// The text writer to write the output to. + /// Byte array to be written. + /// Char buffer to use for streaming data. + /// Array pool for renting a buffer. + internal static void WriteValue(TextWriter writer, byte[] value, ref char[] buffer, ICharArrayPool arrayPool = null) + { + Debug.Assert(writer != null, "writer != null"); + + if (value == null) + { + writer.Write(JsonConstants.JsonNullLiteral); + } + else + { + writer.Write(JsonConstants.QuoteCharacter); + WriteBinaryString(writer, value, ref buffer, arrayPool); + writer.Write(JsonConstants.QuoteCharacter); + } + } + + /// + /// Write a byte array. + /// + /// The text writer to write the output to. + /// Byte array to be written. + /// Char buffer to use for streaming data. + /// Array pool for renting a buffer. + internal static void WriteBinaryString(TextWriter writer, byte[] value, ref char[] buffer, ICharArrayPool arrayPool) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(value != null, "The value must not be null."); + + buffer = BufferUtils.InitializeBufferIfRequired(arrayPool, buffer); + Debug.Assert(buffer != null); + + int bufferLength = buffer.Length; + + // Try to hold base64 string as much as possible in one converting. + int bufferByteSize = bufferLength * 3 / 4; + + for (int offsetIn = 0; offsetIn < value.Length; offsetIn += bufferByteSize) + { + int length = bufferByteSize; + if (offsetIn + length > value.Length) + { + length = value.Length - offsetIn; + } + + int output = Convert.ToBase64CharArray(value, offsetIn, length, buffer, 0); + writer.Write(buffer, 0, output); + } + } + + /// + /// Returns the string value with special characters escaped. + /// + /// The text writer to write the output to. + /// Input string value. + /// The string escape option. + /// Char buffer to use for streaming data + /// Array pool for renting a buffer. + internal static void WriteEscapedJsonString(TextWriter writer, string inputString, + ODataStringEscapeOption stringEscapeOption, ref char[] buffer, ICharArrayPool bufferPool = null) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(inputString != null, "The string value must not be null."); + + writer.Write(JsonConstants.QuoteCharacter); + WriteEscapedJsonStringValue(writer, inputString, stringEscapeOption, ref buffer, bufferPool); + writer.Write(JsonConstants.QuoteCharacter); + } + + /// + /// Writes the string value with special characters escaped. + /// + /// The text writer to write the output to. + /// Input string value. + /// The string escape option. + /// Char buffer to use for streaming data. + /// Array pool for renting a buffer. + internal static void WriteEscapedJsonStringValue(TextWriter writer, string inputString, + ODataStringEscapeOption stringEscapeOption, ref char[] buffer, ICharArrayPool bufferPool) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(inputString != null, "The string value must not be null."); + + int firstIndex; + if (!JsonValueUtils.CheckIfStringHasSpecialChars(inputString, stringEscapeOption, out firstIndex)) + { + writer.Write(inputString); + } + else + { + int inputStringLength = inputString.Length; + + Debug.Assert(firstIndex < inputStringLength, "First index of the special character should be within the string"); + buffer = BufferUtils.InitializeBufferIfRequired(bufferPool, buffer); + int bufferLength = buffer.Length; + int bufferIndex = 0; + int currentIndex = 0; + + // Let's copy and flush strings up to the first index of the special char + while (currentIndex < firstIndex) + { + int subStrLength = firstIndex - currentIndex; + + Debug.Assert(subStrLength > 0, "SubStrLength should be greater than 0 always"); + + // If the first index of the special character is larger than the buffer length, + // flush everything to the buffer first and reset the buffer to the next chunk. + // Otherwise copy to the buffer and go on from there. + if (subStrLength >= bufferLength) + { + inputString.CopyTo(currentIndex, buffer, 0, bufferLength); + writer.Write(buffer, 0, bufferLength); + currentIndex += bufferLength; + } + else + { + inputString.CopyTo(currentIndex, buffer, 0, subStrLength); + bufferIndex = subStrLength; + currentIndex += subStrLength; + } + } + + // start writing escaped strings + for (; currentIndex < inputStringLength; currentIndex++) + { + bufferIndex = EscapeAndWriteCharToBuffer(writer, inputString[currentIndex], buffer, bufferIndex, stringEscapeOption); + } + + // write any remaining chars to the writer + if (bufferIndex > 0) + { + writer.Write(buffer, 0, bufferIndex); + } + } + } + + /// + /// Escapes and writes a character array to a writer. + /// + /// The text writer to write the output to. + /// Character array to write. + /// How many characters to skip in the input array. + /// How many characters to write from the input array. + /// The string escape option. + /// Char buffer to use for streaming data. + /// Character buffer pool. + internal static void WriteEscapedCharArray(TextWriter writer, char[] inputArray, int inputArrayOffset, int inputArrayCount, ODataStringEscapeOption stringEscapeOption, ref char[] buffer, ICharArrayPool bufferPool) + { + int bufferIndex = 0; + buffer = BufferUtils.InitializeBufferIfRequired(bufferPool, buffer); + + for (; inputArrayOffset < inputArrayCount; inputArrayOffset++) + { + bufferIndex = EscapeAndWriteCharToBuffer(writer, inputArray[inputArrayOffset], buffer, bufferIndex, stringEscapeOption); + } + + // write remaining bytes in buffer + if (bufferIndex > 0) + { + writer.Write(buffer, 0, bufferIndex); + } + } + + /// + /// Converts the number of ticks from the JSON date time format to the one used in .NET DateTime or DateTimeOffset structure. + /// + /// The ticks to from the JSON date time format. + /// The ticks to use in the .NET DateTime of DateTimeOffset structure. + internal static long JsonTicksToDateTimeTicks(long ticks) + { + // Ticks in .NET are in 100-nanoseconds and start at 1.1.0001. + // Ticks in the JSON date time format are in milliseconds and start at 1.1.1970. + return (ticks * 10000) + JsonValueUtils.JsonDateTimeMinTimeTicks; + } + + /// + /// Convert string to Json-formated string with proper escaped special characters. + /// Note that the return value is not enclosed by the top level double-quotes. + /// + /// string that might contain special characters. + /// A string with special characters escaped properly. + internal static string GetEscapedJsonString(string inputString) + { + Debug.Assert(inputString != null, "The string value must not be null."); + + StringBuilder builder = new StringBuilder(); + int startIndex = 0; + int inputStringLength = inputString.Length; + int subStrLength; + for (int currentIndex = 0; currentIndex < inputStringLength; currentIndex++) + { + char c = inputString[currentIndex]; + + // Append the un-handled characters (that do not require special treatment) + // to the string builder when special characters are detected. + if (JsonValueUtils.SpecialCharToEscapedStringMap[c] == null) + { + continue; + } + + // Flush out the un-escaped characters we've built so far. + subStrLength = currentIndex - startIndex; + if (subStrLength > 0) + { + builder.Append(inputString.Substring(startIndex, subStrLength)); + } + + builder.Append(JsonValueUtils.SpecialCharToEscapedStringMap[c]); + startIndex = currentIndex + 1; + } + + subStrLength = inputStringLength - startIndex; + if (subStrLength > 0) + { + builder.Append(inputString.Substring(startIndex, subStrLength)); + } + + return builder.ToString(); + } + + /// + /// Escapes and writes a character buffer, flushing to the writer as the buffer fills. + /// + /// The text writer to write the output to. + /// The character to write to the buffer. + /// Char buffer to use for streaming data. + /// The index into the bufffer in which to write the character. + /// The string escape option. + /// Current position in the buffer after the character has been written. + /// + /// IMPORTANT: After all characters have been written, + /// caller is responsible for writing the final buffer contents to the writer. + /// + private static int EscapeAndWriteCharToBuffer(TextWriter writer, char character, char[] buffer, int bufferIndex, ODataStringEscapeOption stringEscapeOption) + { + int bufferLength = buffer.Length; + string escapedString = null; + + if (stringEscapeOption == ODataStringEscapeOption.EscapeNonAscii || character <= 0x7F) + { + escapedString = JsonValueUtils.SpecialCharToEscapedStringMap[character]; + } + + // Append the unhandled characters (that do not require special treament) + // to the buffer. + if (escapedString == null) + { + buffer[bufferIndex] = character; + bufferIndex++; + } + else + { + // Okay, an unhandled character was deteced. + // First lets check if we can fit it in the existing buffer, if not, + // flush the current buffer and reset. Add the escaped string to the buffer + // and continue. + int escapedStringLength = escapedString.Length; + Debug.Assert(escapedStringLength <= bufferLength, "Buffer should be larger than the escaped string"); + + if ((bufferIndex + escapedStringLength) > bufferLength) + { + writer.Write(buffer, 0, bufferIndex); + bufferIndex = 0; + } + + escapedString.CopyTo(0, buffer, bufferIndex, escapedStringLength); + bufferIndex += escapedStringLength; + } + + if (bufferIndex >= bufferLength) + { + Debug.Assert(bufferIndex == bufferLength, + "We should never encounter a situation where the buffer index is greater than the buffer length"); + writer.Write(buffer, 0, bufferIndex); + bufferIndex = 0; + } + + return bufferIndex; + } + + /// + /// Checks if the string contains special char and returns the first index + /// of special char if present. + /// + /// string that might contain special characters. + /// The string escape option. + /// first index of the special char + /// A value indicating whether the string contains special character + private static bool CheckIfStringHasSpecialChars(string inputString, ODataStringEscapeOption stringEscapeOption, out int firstIndex) + { + Debug.Assert(inputString != null, "The string value must not be null."); + + firstIndex = -1; + int inputStringLength = inputString.Length; + for (int currentIndex = 0; currentIndex < inputStringLength; currentIndex++) + { + char c = inputString[currentIndex]; + + if (stringEscapeOption == ODataStringEscapeOption.EscapeOnlyControls && c >= 0x7F) + { + continue; + } + + // Append the un-handled characters (that do not require special treatment) + // to the string builder when special characters are detected. + if (JsonValueUtils.SpecialCharToEscapedStringMap[c] != null) + { + firstIndex = currentIndex; + return true; + } + } + + return false; + } + + /// + /// Initialize static property ODataNumberFormatInfo. + /// + /// The object. + private static NumberFormatInfo InitializeODataNumberFormatInfo() + { + NumberFormatInfo odataNumberFormatInfo = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone(); + odataNumberFormatInfo.PositiveInfinitySymbol = JsonValueUtils.ODataJsonPositiveInfinitySymbol; + odataNumberFormatInfo.NegativeInfinitySymbol = JsonValueUtils.ODataJsonNegativeInfinitySymbol; + return odataNumberFormatInfo; + } + + /// + /// Write the string value with quotes. + /// + /// The text writer to write the output to. + /// String value to be written. + private static void WriteQuoted(TextWriter writer, string text) + { + writer.Write(JsonConstants.QuoteCharacter); + writer.Write(text); + writer.Write(JsonConstants.QuoteCharacter); + } + + /// + /// Converts the number of ticks from the .NET DateTime or DateTimeOffset structure to the ticks use in the JSON date time format. + /// + /// The ticks from the .NET DateTime of DateTimeOffset structure. + /// The ticks to use in the JSON date time format. + private static long DateTimeTicksToJsonTicks(long ticks) + { + // Ticks in .NET are in 100-nanoseconds and start at 1.1.0001. + // Ticks in the JSON date time format are in milliseconds and start at 1.1.1970. + return (ticks - JsonValueUtils.JsonDateTimeMinTimeTicks) / 10000; + } + + /// + /// Creates the special character to escaped string map. + /// + /// The map of special characters to the corresponding escaped strings. + private static string[] CreateSpecialCharToEscapedStringMap() + { + string[] specialCharToEscapedStringMap = new string[char.MaxValue + 1]; + for (int c = char.MinValue; c <= char.MaxValue; ++c) + { + if ((c < ' ') || (c > 0x7F)) + { + // We only need to populate for characters < ' ' and > 0x7F. + specialCharToEscapedStringMap[c] = string.Format(CultureInfo.InvariantCulture, "\\u{0:x4}", c); + } + else + { + specialCharToEscapedStringMap[c] = null; + } + } + + specialCharToEscapedStringMap['\r'] = "\\r"; + specialCharToEscapedStringMap['\t'] = "\\t"; + specialCharToEscapedStringMap['\"'] = "\\\""; + specialCharToEscapedStringMap['\\'] = "\\\\"; + specialCharToEscapedStringMap['\n'] = "\\n"; + specialCharToEscapedStringMap['\b'] = "\\b"; + specialCharToEscapedStringMap['\f'] = "\\f"; + + return specialCharToEscapedStringMap; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriter.cs new file mode 100644 index 0000000..841af93 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriter.cs @@ -0,0 +1,634 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + using Microsoft.OData.Buffers; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Writer for the JSON format. http://www.json.org + /// + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "This class does not own the underlying stream/writer and thus should never dispose it.")] + internal sealed class JsonWriter : IJsonStreamWriter, IDisposable + { + /// + /// Writer to write text into. + /// + private readonly TextWriterWrapper writer; + + /// + /// Scope of the json text - object, array. + /// + private readonly Stack scopes; + + /// + /// If it is IEEE754Compatible, write quoted string for INT64 and decimal to prevent data loss; + /// otherwise keep number without quotes. + /// + private readonly bool isIeee754Compatible; + + /// + /// Gets or sets a value indicating how to escape the string when writing JSON string. + /// + private readonly ODataStringEscapeOption stringEscapeOption; + + /// + /// The buffer to help with streaming responses. + /// + private char[] buffer; + + /// + /// Current stream for writing a binary property. + /// + private Stream binaryValueStream = null; + + /// + /// Content type of a value being written using TextWriter + /// + private string currentContentType; + + /// + /// Creates a new instance of Json writer. + /// + /// Writer to which text needs to be written. + /// if it is IEEE754Compatible. + internal JsonWriter(TextWriter writer, bool isIeee754Compatible) + : this(writer, isIeee754Compatible, ODataStringEscapeOption.EscapeNonAscii) + { + } + + /// + /// Creates a new instance of Json writer. + /// + /// Writer to which text needs to be written. + /// if it is IEEE754Compatible. + /// Specifies how to escape string. + internal JsonWriter(TextWriter writer, bool isIeee754Compatible, ODataStringEscapeOption stringEscapeOption) + { + this.writer = new NonIndentedTextWriter(writer); + this.scopes = new Stack(); + this.isIeee754Compatible = isIeee754Compatible; + this.stringEscapeOption = stringEscapeOption; + } + + /// + /// Various scope types for Json writer. + /// + internal enum ScopeType + { + /// + /// Array scope. + /// + Array = 0, + + /// + /// Object scope. + /// + Object = 1, + + /// + /// JSON padding function scope. + /// + Padding = 2, + } + + /// + /// Get/sets the character buffer pool. + /// + public ICharArrayPool ArrayPool { get; set; } + + /// + /// Whether the current TextWriter is writing JSON + /// + /// + private bool IsWritingJson + { + get + { + return String.IsNullOrEmpty(this.currentContentType) || this.currentContentType.StartsWith(MimeConstants.MimeApplicationJson, StringComparison.Ordinal); + } + } + + /// + /// Start the padding function scope. + /// + public void StartPaddingFunctionScope() + { + Debug.Assert(this.scopes.Count == 0, "Padding scope can only be the outer most scope."); + this.StartScope(ScopeType.Padding); + } + + /// + /// End the padding function scope. + /// + public void EndPaddingFunctionScope() + { + Debug.Assert(this.scopes.Count > 0, "No scope to end."); + + this.writer.WriteLine(); + this.writer.DecreaseIndentation(); + Scope scope = this.scopes.Pop(); + + Debug.Assert(scope.Type == ScopeType.Padding, "Ending scope does not match."); + + this.writer.Write(scope.EndString); + } + + /// + /// Start the object scope. + /// + public void StartObjectScope() + { + this.StartScope(ScopeType.Object); + } + + /// + /// End the current object scope. + /// + public void EndObjectScope() + { + Debug.Assert(this.scopes.Count > 0, "No scope to end."); + + this.writer.WriteLine(); + this.writer.DecreaseIndentation(); + Scope scope = this.scopes.Pop(); + + Debug.Assert(scope.Type == ScopeType.Object, "Ending scope does not match."); + + this.writer.Write(scope.EndString); + } + + /// + /// Start the array scope. + /// + public void StartArrayScope() + { + this.StartScope(ScopeType.Array); + } + + /// + /// End the current array scope. + /// + public void EndArrayScope() + { + Debug.Assert(this.scopes.Count > 0, "No scope to end."); + + this.writer.WriteLine(); + this.writer.DecreaseIndentation(); + Scope scope = this.scopes.Pop(); + + Debug.Assert(scope.Type == ScopeType.Array, "Ending scope does not match."); + + this.writer.Write(scope.EndString); + } + + /// + /// Write the name for the object property. + /// + /// Name of the object property. + public void WriteName(string name) + { + Debug.Assert(!string.IsNullOrEmpty(name), "The name must be specified."); + Debug.Assert(this.scopes.Count > 0, "There must be an active scope for name to be written."); + Debug.Assert(this.scopes.Peek().Type == ScopeType.Object, "The active scope must be an object scope for name to be written."); + + Scope currentScope = this.scopes.Peek(); + if (currentScope.ObjectCount != 0) + { + this.writer.Write(JsonConstants.ObjectMemberSeparator); + } + + currentScope.ObjectCount++; + + JsonValueUtils.WriteEscapedJsonString(this.writer, name, this.stringEscapeOption, ref this.buffer); + this.writer.Write(JsonConstants.NameValueSeparator); + } + + /// + /// Writes a function name for JSON padding. + /// + /// Name of the padding function to write. + public void WritePaddingFunctionName(string functionName) + { + this.writer.Write(functionName); + } + + /// + /// Write a boolean value. + /// + /// Boolean value to be written. + public void WriteValue(bool value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write an integer value. + /// + /// Integer value to be written. + public void WriteValue(int value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a float value. + /// + /// Float value to be written. + public void WriteValue(float value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a short value. + /// + /// Short value to be written. + public void WriteValue(short value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a long value. + /// + /// Long value to be written. + public void WriteValue(long value) + { + this.WriteValueSeparator(); + + // if it is IEEE754Compatible, write numbers with quotes; otherwise, write numbers directly. + if (isIeee754Compatible) + { + JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture), + this.stringEscapeOption, ref this.buffer); + } + else + { + JsonValueUtils.WriteValue(this.writer, value); + } + } + + /// + /// Write a double value. + /// + /// Double value to be written. + public void WriteValue(double value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a Guid value. + /// + /// Guid value to be written. + public void WriteValue(Guid value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a decimal value + /// + /// Decimal value to be written. + public void WriteValue(decimal value) + { + this.WriteValueSeparator(); + + // if it is not IEEE754Compatible, write numbers directly without quotes; + if (isIeee754Compatible) + { + JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture), + this.stringEscapeOption, ref this.buffer); + } + else + { + JsonValueUtils.WriteValue(this.writer, value); + } + } + + /// + /// Writes a DateTimeOffset value + /// + /// DateTimeOffset value to be written. + public void WriteValue(DateTimeOffset value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value, ODataJsonDateTimeFormat.ISO8601DateTime); + } + + /// + /// Writes a TimeSpan value + /// + /// TimeSpan value to be written. + public void WriteValue(TimeSpan value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a Date value + /// + /// Date value to be written. + public void WriteValue(TimeOfDay value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a Date value + /// + /// Date value to be written. + public void WriteValue(Date value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a byte value. + /// + /// Byte value to be written. + public void WriteValue(byte value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write an sbyte value. + /// + /// SByte value to be written. + public void WriteValue(sbyte value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value); + } + + /// + /// Write a string value. + /// + /// String value to be written. + public void WriteValue(string value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value, this.stringEscapeOption, ref this.buffer); + } + + /// + /// Write a byte array. + /// + /// Byte array to be written. + public void WriteValue(byte[] value) + { + this.WriteValueSeparator(); + JsonValueUtils.WriteValue(this.writer, value, ref this.buffer, ArrayPool); + } + + /// + /// Write a raw value. + /// + /// Raw value to be written. + public void WriteRawValue(string rawValue) + { + this.WriteValueSeparator(); + this.writer.Write(rawValue); + } + + /// + /// Clears all buffers for the current writer. + /// + public void Flush() + { + this.writer.Flush(); + } + + /// + /// Start the stream property valuescope. + /// + /// The stream to write the property value to + public Stream StartStreamValueScope() + { + this.WriteValueSeparator(); + this.writer.Write(JsonConstants.QuoteCharacter); + this.writer.Flush(); + + this.binaryValueStream = new ODataBinaryStreamWriter(writer, ref buffer, ArrayPool); + return this.binaryValueStream; + } + + /// + /// End the current stream property value scope. + /// + public void EndStreamValueScope() + { + this.binaryValueStream.Flush(); + this.binaryValueStream.Dispose(); + this.binaryValueStream = null; + this.writer.Flush(); + this.writer.Write(JsonConstants.QuoteCharacter); + } + + /// + /// Start the TextWriter valuescope. + /// + /// ContentType of the string being written. + /// The textwriter to write the text property value to + public TextWriter StartTextWriterValueScope(string contentType) + { + this.WriteValueSeparator(); + this.currentContentType = contentType; + if (!IsWritingJson) + { + this.writer.Write(JsonConstants.QuoteCharacter); + this.writer.Flush(); + return new ODataJsonTextWriter(writer, ref buffer, this.ArrayPool); + } + + this.writer.Flush(); + + return this.writer; + } + + /// + /// End the TextWriter valuescope. + /// + public void EndTextWriterValueScope() + { + if (!IsWritingJson) + { + this.writer.Write(JsonConstants.QuoteCharacter); + } + } + + void IDisposable.Dispose() + { + if (this.binaryValueStream != null) + { + try + { + this.binaryValueStream.Dispose(); + } + finally + { + this.binaryValueStream = null; + } + } + } + + /// + /// Dispose the writer + /// + public void Dispose() + { + if (this.ArrayPool != null && this.buffer != null) + { + BufferUtils.ReturnToBuffer(this.ArrayPool, this.buffer); + this.ArrayPool = null; + this.buffer = null; + } + } + + /// + /// Writes a separator of a value if it's needed for the next value to be written. + /// + private void WriteValueSeparator() + { + if (this.scopes.Count == 0) + { + return; + } + + Scope currentScope = this.scopes.Peek(); + if (currentScope.Type == ScopeType.Array) + { + if (currentScope.ObjectCount != 0) + { + this.writer.Write(JsonConstants.ArrayElementSeparator); + } + + currentScope.ObjectCount++; + } + } + + /// + /// Start the scope given the scope type. + /// + /// The scope type to start. + private void StartScope(ScopeType type) + { + if (this.scopes.Count != 0 && this.scopes.Peek().Type != ScopeType.Padding) + { + Scope currentScope = this.scopes.Peek(); + if ((currentScope.Type == ScopeType.Array) && + (currentScope.ObjectCount != 0)) + { + this.writer.Write(JsonConstants.ArrayElementSeparator); + } + + currentScope.ObjectCount++; + } + + Scope scope = new Scope(type); + this.scopes.Push(scope); + + this.writer.Write(scope.StartString); + this.writer.IncreaseIndentation(); + this.writer.WriteLine(); + } + + /// + /// Class representing scope information. + /// + private sealed class Scope + { + /// + /// The type of the scope. + /// + private readonly ScopeType type; + + /// + /// Constructor. + /// + /// The type of the scope. + public Scope(ScopeType type) + { + this.type = type; + switch (type) + { + case ScopeType.Array: + this.StartString = JsonConstants.StartArrayScope; + this.EndString = JsonConstants.EndArrayScope; + break; + case ScopeType.Object: + this.StartString = JsonConstants.StartObjectScope; + this.EndString = JsonConstants.EndObjectScope; + break; + case ScopeType.Padding: + this.StartString = JsonConstants.StartPaddingFunctionScope; + this.EndString = JsonConstants.EndPaddingFunctionScope; + break; + } + } + + /// + /// What to write at the beginning of this scope. + /// + public string StartString + { + get; + private set; + } + + /// + /// What to write at the end of this scope. + /// + public string EndString + { + get; + private set; + } + + /// + /// Get/Set the object count for this scope. + /// + public int ObjectCount + { + get; + set; + } + + /// + /// Gets the scope type for this scope. + /// + public ScopeType Type + { + get + { + return this.type; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriterExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriterExtensions.cs new file mode 100644 index 0000000..95a07dd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/JsonWriterExtensions.cs @@ -0,0 +1,210 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Collections.Generic; + using System.Collections; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + using ODataPlatformHelper = Microsoft.OData.PlatformHelper; + #endregion Namespaces + + /// + /// Extension methods for the JSON writer. + /// + internal static class JsonWriterExtensions + { + /// + /// Writes the json object value to the . + /// + /// The to write to. + /// Writes the given json object value to the underlying json writer. + /// Called when the top-level object is started to possibly inject first property into the object. + internal static void WriteJsonObjectValue(this IJsonWriter jsonWriter, IDictionary jsonObjectValue, Action injectPropertyAction) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + Debug.Assert(jsonObjectValue != null, "jsonObjectValue != null"); + + jsonWriter.StartObjectScope(); + + if (injectPropertyAction != null) + { + injectPropertyAction(jsonWriter); + } + + foreach (KeyValuePair property in jsonObjectValue) + { + jsonWriter.WriteName(property.Key); + jsonWriter.WriteJsonValue(property.Value); + } + + jsonWriter.EndObjectScope(); + } + + /// + /// Writes a primitive value. + /// + /// The to write to. + /// The value to write. + internal static void WritePrimitiveValue(this IJsonWriter jsonWriter, object value) + { + if (value is bool) + { + jsonWriter.WriteValue((bool)value); + return; + } + + if (value is byte) + { + jsonWriter.WriteValue((byte)value); + return; + } + + if (value is decimal) + { + jsonWriter.WriteValue((decimal)value); + return; + } + + if (value is double) + { + jsonWriter.WriteValue((double)value); + return; + } + + if (value is Int16) + { + jsonWriter.WriteValue((Int16)value); + return; + } + + if (value is Int32) + { + jsonWriter.WriteValue((Int32)value); + return; + } + + if (value is Int64) + { + jsonWriter.WriteValue((Int64)value); + return; + } + + if (value is sbyte) + { + jsonWriter.WriteValue((sbyte)value); + return; + } + + if (value is Single) + { + jsonWriter.WriteValue((Single)value); + return; + } + + var str = value as string; + if (str != null) + { + jsonWriter.WriteValue(str); + return; + } + + byte[] valueAsByteArray = value as byte[]; + if (valueAsByteArray != null) + { + jsonWriter.WriteValue(valueAsByteArray); + return; + } + + if (value is DateTimeOffset) + { + jsonWriter.WriteValue((DateTimeOffset)value); + return; + } + + if (value is Guid) + { + jsonWriter.WriteValue((Guid)value); + return; + } + + if (value is TimeSpan) + { + jsonWriter.WriteValue((TimeSpan)value); + return; + } + + if (value is Date) + { + jsonWriter.WriteValue((Date)value); + return; + } + + if (value is TimeOfDay) + { + jsonWriter.WriteValue((TimeOfDay)value); + return; + } + + throw new ODataException(ODataErrorStrings.ODataJsonWriter_UnsupportedValueType(value.GetType().FullName)); + } + + /// + /// Writes the json array value. + /// + /// The to write to. + /// Writes the json array value to the underlying json writer. + private static void WriteJsonArrayValue(this IJsonWriter jsonWriter, IEnumerable arrayValue) + { + Debug.Assert(arrayValue != null, "arrayValue != null"); + + jsonWriter.StartArrayScope(); + + foreach (object element in arrayValue) + { + jsonWriter.WriteJsonValue(element); + } + + jsonWriter.EndArrayScope(); + } + + /// + /// Writes the json value (primitive, IDictionary or IEnumerable) to the underlying json writer. + /// + /// The to write to. + /// value to write. + private static void WriteJsonValue(this IJsonWriter jsonWriter, object propertyValue) + { + if (propertyValue == null) + { + jsonWriter.WriteValue((string)null); + } + else if (EdmLibraryExtensions.IsPrimitiveType(propertyValue.GetType())) + { + jsonWriter.WritePrimitiveValue(propertyValue); + } + else + { + IDictionary objectValue = propertyValue as IDictionary; + if (objectValue != null) + { + jsonWriter.WriteJsonObjectValue(objectValue, null /*typeName */); + } + else + { + IEnumerable arrayValue = propertyValue as IEnumerable; + Debug.Assert(arrayValue != null, "arrayValue != null"); + jsonWriter.WriteJsonArrayValue(arrayValue); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs new file mode 100644 index 0000000..d801e53 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/NonIndentedTextWriter.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.IO; + +namespace Microsoft.OData.Json +{ + /// + /// Writes text indented as per the indentation level setting + /// + internal sealed class NonIndentedTextWriter : TextWriterWrapper + { + /// + /// Constructor + /// + /// The underlying writer to wrap. + public NonIndentedTextWriter(TextWriter writer) + : base(writer.FormatProvider) + { + this.writer = writer; + } + + /// + /// Writes the given string value to the underlying writer. + /// + /// String value to be written. + public override void Write(string s) + { + this.writer.Write(s); + } + + /// + /// Writes the given char value to the underlying writer. + /// + /// Char value to be written. + public override void Write(char value) + { + this.writer.Write(value); + } + + /// + /// Writes a new line. + /// + public override void WriteLine() + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonFormat.cs new file mode 100644 index 0000000..d5ada18 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonFormat.cs @@ -0,0 +1,170 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System.Collections.Generic; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.JsonLight; + + #endregion Namespaces + + /// + /// The JsonLight OData format. + /// + internal sealed class ODataJsonFormat : ODataFormat + { + /// + /// The text representation - the name of the format. + /// + /// The name of the format. + public override string ToString() + { + return "JsonLight"; + } + + /// + /// Detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The set of s that are supported with the specified payload. + public override IEnumerable DetectPayloadKind( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + return DetectPayloadKindImplementation(messageInfo, settings); + } + + /// + /// Creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The newly created input context. + public override ODataInputContext CreateInputContext( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + return new ODataJsonLightInputContext(messageInfo, messageReaderSettings); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// The newly created output context. + public override ODataOutputContext CreateOutputContext( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + return new ODataJsonLightOutputContext(messageInfo, messageWriterSettings); + } + +#if PORTABLELIB + /// + /// Asynchronously detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// A task that when completed returns the set of s + /// that are supported with the specified payload. + public override Task> DetectPayloadKindAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + return DetectPayloadKindImplementationAsync(messageInfo, settings); + } + + /// + /// Asynchronously creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// Task which when completed returned the newly created input context. + public override Task CreateInputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + return Task.FromResult( + new ODataJsonLightInputContext(messageInfo, messageReaderSettings)); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// Task which represents the pending create operation. + public override Task CreateOutputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + return Task.FromResult( + new ODataJsonLightOutputContext(messageInfo, messageWriterSettings)); + } +#endif + + /// + /// Detects the payload kind(s) from the message stream. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + private static IEnumerable DetectPayloadKindImplementation( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + var detectionInfo = new ODataPayloadKindDetectionInfo(messageInfo, settings); + messageInfo.Encoding = detectionInfo.GetEncoding(); + using (var jsonLightInputContext = new ODataJsonLightInputContext(messageInfo, settings)) + { + return jsonLightInputContext.DetectPayloadKind(detectionInfo); + } + } + +#if PORTABLELIB + /// + /// Detects the payload kind(s) from the message stream. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + private static Task> DetectPayloadKindImplementationAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + var detectionInfo = new ODataPayloadKindDetectionInfo(messageInfo, settings); + messageInfo.Encoding = detectionInfo.GetEncoding(); + var jsonLightInputContext = new ODataJsonLightInputContext(messageInfo, settings); + return jsonLightInputContext.DetectPayloadKindAsync(detectionInfo) + .FollowAlwaysWith(t => + { + jsonLightInputContext.Dispose(); + }); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonReaderCoreUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonReaderCoreUtils.cs new file mode 100644 index 0000000..fb1a9ea --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonReaderCoreUtils.cs @@ -0,0 +1,224 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.JsonLight; + using Microsoft.Spatial; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Helper methods used by the OData reader for the Verbose JSON and JSON Light formats. + /// + internal static class ODataJsonReaderCoreUtils + { + /// + /// Try and parse spatial type from the json payload. + /// + /// The JSON reader to read from. + /// true if the reader is positioned on the first property of the value which is a JSON Object + /// (or the second property if the first one was odata.type). + /// The input context with all the settings. + /// Expected edm property type. + /// true to validate null values; otherwise false. + /// The recursion depth to start with. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// An instance of the spatial type. + internal static ISpatial ReadSpatialValue( + IJsonReader jsonReader, + bool insideJsonObjectValue, + ODataInputContext inputContext, + IEdmPrimitiveTypeReference expectedValueTypeReference, + bool validateNullValue, + int recursionDepth, + string propertyName) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + Debug.Assert(inputContext != null, "inputContext != null"); + Debug.Assert(expectedValueTypeReference != null, "expectedValueTypeReference != null"); + Debug.Assert(expectedValueTypeReference.IsSpatial(), "TryParseSpatialType must be called only with spatial types"); + + // Spatial value can be either null constant or a JSON object + // If it's a null primitive value, report a null value. + if (!insideJsonObjectValue && TryReadNullValue(jsonReader, inputContext, expectedValueTypeReference, validateNullValue, propertyName)) + { + return null; + } + + Microsoft.Spatial.ISpatial spatialValue = null; + if (insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.StartObject) + { + IDictionary jsonObject = ReadObjectValue(jsonReader, insideJsonObjectValue, inputContext, recursionDepth); + Microsoft.Spatial.GeoJsonObjectFormatter jsonObjectFormatter = + Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateGeoJsonObjectFormatter(); + if (expectedValueTypeReference.IsGeography()) + { + spatialValue = jsonObjectFormatter.Read(jsonObject); + } + else + { + spatialValue = jsonObjectFormatter.Read(jsonObject); + } + } + + if (spatialValue == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue); + } + + return spatialValue; + } + + /// + /// Tries to read a null value from the JSON reader. + /// + /// The JSON reader to read from. + /// The input context with all the settings. + /// The expected type reference of the value. + /// true to validate null values; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// Indicates whether the property is dynamic or unknown. + /// true if a null value could be read from the JSON reader; otherwise false. + /// If the method detects a null value it will read it (position the reader after the null value); + /// otherwise the reader does not move. + internal static bool TryReadNullValue( + IJsonReader jsonReader, + ODataInputContext inputContext, + IEdmTypeReference expectedTypeReference, + bool validateNullValue, + string propertyName, + bool? isDynamicProperty = null) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + Debug.Assert(inputContext != null, "inputContext != null"); + + if (jsonReader.NodeType == JsonNodeType.PrimitiveValue && jsonReader.Value == null) + { + jsonReader.ReadNext(); + + // NOTE: when reading a null value we will never ask the type resolver (if present) to resolve the + // type; we always fall back to the expected type. + inputContext.MessageReaderSettings.Validator.ValidateNullValue( + expectedTypeReference, + validateNullValue, + propertyName, + isDynamicProperty); + + return true; + } + + return false; + } + + /// + /// Reads the json object value from the jsonReader + /// + /// Json reader to read payload from the wire. + /// true if the reader is positioned on the first property of the value which is a JSON Object + /// (or the second property if the first one was odata.type). + /// The input context with all the settings. + /// The recursion depth to start with. + /// an instance of IDictionary containing the spatial value. + private static IDictionary ReadObjectValue(IJsonReader jsonReader, bool insideJsonObjectValue, ODataInputContext inputContext, int recursionDepth) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + Debug.Assert(insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.StartObject, "insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.StartObject"); + Debug.Assert( + !insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.Property || jsonReader.NodeType == JsonNodeType.EndObject, + "!insideJsonObjectValue || jsonReader.NodeType == JsonNodeType.Property || jsonReader.NodeType == JsonNodeType.EndObject"); + Debug.Assert(inputContext != null, "inputContext != null"); + + ValidationUtils.IncreaseAndValidateRecursionDepth(ref recursionDepth, inputContext.MessageReaderSettings.MessageQuotas.MaxNestingDepth); + + IDictionary jsonValue = new Dictionary(StringComparer.Ordinal); + if (!insideJsonObjectValue) + { + // Note that if the insideJsonObjectValue is true we will ignore the odata.type instance annotation + // which might have been there. This is OK since for spatial we only need the normal properties anyway. + jsonReader.ReadNext(); + } + + while (jsonReader.NodeType != JsonNodeType.EndObject) + { + // read the property name + string propertyName = jsonReader.ReadPropertyName(); + + // read the property value + object propertyValue = null; + switch (jsonReader.NodeType) + { + case JsonNodeType.PrimitiveValue: + propertyValue = jsonReader.ReadPrimitiveValue(); + break; + case JsonNodeType.StartArray: + propertyValue = ReadArrayValue(jsonReader, inputContext, recursionDepth); + break; + case JsonNodeType.StartObject: + propertyValue = ReadObjectValue(jsonReader, /*insideJsonObjectValue*/ false, inputContext, recursionDepth); + break; + default: + Debug.Assert(false, "We should never reach here - There should be matching end element"); + return null; + } + + jsonValue.Add(ODataAnnotationNames.RemoveAnnotationPrefix(propertyName), propertyValue); + } + + jsonReader.ReadEndObject(); + + return jsonValue; + } + + /// + /// Read the json array from the reader. + /// + /// JsonReader instance. + /// The input context with all the settings. + /// The recursion depth to start with. + /// a list of json objects. + private static IEnumerable ReadArrayValue(IJsonReader jsonReader, ODataInputContext inputContext, int recursionDepth) + { + Debug.Assert(jsonReader != null, "jsonReader != null"); + Debug.Assert(jsonReader.NodeType == JsonNodeType.StartArray, "jsonReader.NodeType == JsonNodeType.StartArray"); + Debug.Assert(inputContext != null, "inputContext != null"); + + ValidationUtils.IncreaseAndValidateRecursionDepth(ref recursionDepth, inputContext.MessageReaderSettings.MessageQuotas.MaxNestingDepth); + + List array = new List(); + jsonReader.ReadNext(); + while (jsonReader.NodeType != JsonNodeType.EndArray) + { + switch (jsonReader.NodeType) + { + case JsonNodeType.PrimitiveValue: + array.Add(jsonReader.ReadPrimitiveValue()); + break; + case JsonNodeType.StartObject: + array.Add(ReadObjectValue(jsonReader, /*insideJsonObjectValue*/ false, inputContext, recursionDepth)); + break; + case JsonNodeType.StartArray: + array.Add(ReadArrayValue(jsonReader, inputContext, recursionDepth)); + break; + default: + Debug.Assert(false, "We should never have got here - the valid states in array are primitive value or object"); + return null; + } + } + + jsonReader.ReadEndArray(); + + return array; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonTextWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonTextWriter.cs new file mode 100644 index 0000000..50f6d27 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonTextWriter.cs @@ -0,0 +1,384 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.IO; + using System.Linq; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Buffers; + using Microsoft.OData.Json; + + /// + /// Wrapper for TextWriter to escape special characters. + /// + internal sealed class ODataJsonTextWriter : TextWriter + { + /// + /// The underlying textWriter for writing the JSON. + /// + private readonly TextWriter textWriter; + + /// + /// The escape option to use in writing text. + /// + private readonly ODataStringEscapeOption escapeOption = ODataStringEscapeOption.EscapeOnlyControls; + + /// + /// The buffer to help when converting binary values. + /// + private char[] streamingBuffer; + + /// + /// Get/sets the character buffer pool for streaming. + /// + private ICharArrayPool bufferPool; + + /// + /// A TextWriter for writing properly escaped JSON text. + /// + /// The underlying TextWriter used when writing JSON + /// Buffer used when converting binary values. + /// Buffer pool used for renting a buffer. + internal ODataJsonTextWriter(TextWriter textWriter, ref char[] buffer, ICharArrayPool bufferPool) + : base(System.Globalization.CultureInfo.InvariantCulture) + { + ExceptionUtils.CheckArgumentNotNull(textWriter, "textWriter"); + + this.textWriter = textWriter; + this.streamingBuffer = buffer; + this.bufferPool = bufferPool; + } + + /// + public override Encoding Encoding + { + get + { + return this.textWriter.Encoding; + } + } + + /// + public override string NewLine + { + get + { + return this.textWriter.NewLine; + } + + set + { + this.textWriter.NewLine = value; + } + } + + /// + public override IFormatProvider FormatProvider + { + get + { + return this.textWriter.FormatProvider; + } + } + + /// + public override void Flush() + { + this.textWriter.Flush(); + } + + /// + public override int GetHashCode() + { + return this.textWriter.GetHashCode(); + } + + /// + public override string ToString() + { + return this.textWriter.ToString(); + } + + #region Write methods + /// + public override void Write(char value) + { + this.WriteEscapedCharValue(value); + } + + /// + public override void Write(bool value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(string value) + { + this.WriteEscapedStringValue(value); + } + + /// + public override void Write(char[] buffer) + { + this.WriteEscapedCharArray(buffer, 0, buffer.Length); + } + + /// + public override void Write(char[] buffer, int index, int count) + { + this.WriteEscapedCharArray(buffer, index, count); + } + + /// + public override void Write(string format, params object[] arg) + { + this.WriteEscapedStringValue(string.Format(this.FormatProvider, format, arg)); + } + + /// + public override void Write(decimal value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(object value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(double value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(float value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(int value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(long value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(uint value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(ulong value) + { + this.textWriter.Write(value); + } + + #endregion + + #region WriteLine methods + /// + public override void WriteLine() + { + this.textWriter.WriteLine(); + } + + /// + public override void WriteLine(bool value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(char value) + { + this.Write(value); + this.textWriter.WriteLine(); + } + + /// + public override void WriteLine(char[] buffer) + { + this.Write(buffer); + this.textWriter.WriteLine(); + } + + /// + public override void WriteLine(char[] buffer, int index, int count) + { + this.Write(buffer, index, count); + this.textWriter.WriteLine(); + } + + /// + public override void WriteLine(decimal value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(double value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(float value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(int value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(long value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(object value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(string format, params object[] arg) + { + this.Write(format, arg); + this.textWriter.WriteLine(); + } + + /// + public override void WriteLine(string value) + { + this.Write(value); + this.textWriter.WriteLine(); + } + + /// + public override void WriteLine(uint value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(ulong value) + { + this.textWriter.WriteLine(value); + } + + #endregion + + #region async methods + +#if PORTABLELIB + + /// + public override Task FlushAsync() + { + return this.textWriter.FlushAsync(); + } + + /// + public override Task WriteAsync(char value) + { + return TaskUtils.GetTaskForSynchronousOperation(() => + this.Write(value)); + } + + /// + public override Task WriteAsync(char[] buffer, int index, int count) + { + return TaskUtils.GetTaskForSynchronousOperation(() => + this.Write(buffer, index, count)); + } + + /// + public override Task WriteAsync(string value) + { + return TaskUtils.GetTaskForSynchronousOperation(() => + this.Write(value)); + } + + /// + public override Task WriteLineAsync() + { + return this.textWriter.WriteLineAsync(); + } + + /// + public override Task WriteLineAsync(char value) + { + return TaskUtils.GetTaskForSynchronousOperation(() => + this.WriteLine(value)); + } + + /// + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + return TaskUtils.GetTaskForSynchronousOperation(() => + this.WriteLine(buffer, index, count)); + } + + /// + public override Task WriteLineAsync(string value) + { + return TaskUtils.GetTaskForSynchronousOperation(() => + this.WriteLine(value)); + } + +#endif + + #endregion + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called from the finalizer. + protected override void Dispose(bool disposing) + { + this.textWriter.Dispose(); + base.Dispose(disposing); + } + + #region private methods + private void WriteEscapedCharValue(char value) + { + JsonValueUtils.WriteValue(this.textWriter, value, escapeOption); + } + + private void WriteEscapedStringValue(string value) + { + JsonValueUtils.WriteEscapedJsonStringValue(this.textWriter, value, escapeOption, ref streamingBuffer, this.bufferPool); + } + + private void WriteEscapedCharArray(char[] value, int offset, int count) + { + JsonValueUtils.WriteEscapedCharArray(this.textWriter, value, offset, count, escapeOption, ref streamingBuffer, this.bufferPool); + } + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonWriterUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonWriterUtils.cs new file mode 100644 index 0000000..5b2512b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataJsonWriterUtils.cs @@ -0,0 +1,252 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.JsonLight; + #endregion Namespaces + + /// + /// Helper methods used by the OData writer for the JSON format. + /// + internal static class ODataJsonWriterUtils + { + /// + /// Write an error message. + /// + /// The JSON writer to write the error. + /// Action to write the instance annotations. + /// The error instance to write. + /// A flag indicating whether error details should be written (in debug mode only) or not. + /// The maximum number of nested inner errors to allow. + /// true if we're writing JSON lite, false if we're writing verbose JSON. + internal static void WriteError(IJsonWriter jsonWriter, + Action> writeInstanceAnnotationsDelegate, ODataError error, + bool includeDebugInformation, int maxInnerErrorDepth, bool writingJsonLight) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + Debug.Assert(error != null, "error != null"); + + string code, message; + ErrorUtils.GetErrorDetails(error, out code, out message); + + ODataInnerError innerError = includeDebugInformation ? error.InnerError : null; + + WriteError( + jsonWriter, + code, + message, + error.Target, + error.Details, + innerError, + error.GetInstanceAnnotations(), + writeInstanceAnnotationsDelegate, + maxInnerErrorDepth, + writingJsonLight); + } + + /// + /// Will write the function's name and start the JSONP scope if we are writing a response and the + /// JSONP function name is not null or empty. + /// + /// JsonWriter to write to. + /// Writer settings. + internal static void StartJsonPaddingIfRequired(IJsonWriter jsonWriter, ODataMessageWriterSettings settings) + { + Debug.Assert(jsonWriter != null, "jsonWriter should not be null"); + + if (settings.HasJsonPaddingFunction()) + { + jsonWriter.WritePaddingFunctionName(settings.JsonPCallback); + jsonWriter.StartPaddingFunctionScope(); + } + } + + /// + /// If we are writing a response and the given Json Padding function name is not null or empty + /// this function will close the JSONP scope. + /// + /// JsonWriter to write to. + /// Writer settings. + internal static void EndJsonPaddingIfRequired(IJsonWriter jsonWriter, ODataMessageWriterSettings settings) + { + Debug.Assert(jsonWriter != null, "jsonWriter should not be null"); + + if (settings.HasJsonPaddingFunction()) + { + jsonWriter.EndPaddingFunctionScope(); + } + } + + /// + /// Write an error message. + /// + /// JSON writer. + /// The code of the error. + /// The message of the error. + /// The target of the error. + /// The details of the error. + /// Inner error details that will be included in debug mode (if present). + /// Instance annotations for this error. + /// Action to write the instance annotations. + /// The maximum number of nested inner errors to allow. + /// true if we're writing JSON lite, false if we're writing verbose JSON. + private static void WriteError(IJsonWriter jsonWriter, string code, string message, string target, + IEnumerable details, + ODataInnerError innerError, + IEnumerable instanceAnnotations, + Action> writeInstanceAnnotationsDelegate, int maxInnerErrorDepth, + bool writingJsonLight) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + Debug.Assert(code != null, "code != null"); + Debug.Assert(message != null, "message != null"); + Debug.Assert(instanceAnnotations != null, "instanceAnnotations != null"); + + // "error": { + jsonWriter.StartObjectScope(); + if (writingJsonLight) + { + jsonWriter.WriteName(JsonLightConstants.ODataErrorPropertyName); + } + else + { + jsonWriter.WriteName(JsonConstants.ODataErrorName); + } + + jsonWriter.StartObjectScope(); + + // "code": "" + jsonWriter.WriteName(JsonConstants.ODataErrorCodeName); + jsonWriter.WriteValue(code); + + // "message": "" + jsonWriter.WriteName(JsonConstants.ODataErrorMessageName); + jsonWriter.WriteValue(message); + + // For example, "target": "query", + if (target != null) + { + jsonWriter.WriteName(JsonConstants.ODataErrorTargetName); + jsonWriter.WriteValue(target); + } + + // Such as, "details": [ + // { + // "code": "301", + // "target": "$search", + // "message": "$search query option not supported" + // }] + if (details != null) + { + WriteErrorDetails(jsonWriter, details, JsonConstants.ODataErrorDetailsName); + } + + if (innerError != null) + { + WriteInnerError(jsonWriter, innerError, JsonConstants.ODataErrorInnerErrorName, /* recursionDepth */ 0, maxInnerErrorDepth); + } + + if (writingJsonLight) + { + Debug.Assert(writeInstanceAnnotationsDelegate != null, "writeInstanceAnnotations != null"); + writeInstanceAnnotationsDelegate(instanceAnnotations); + } + + // } } + jsonWriter.EndObjectScope(); + jsonWriter.EndObjectScope(); + } + + private static void WriteErrorDetails(IJsonWriter jsonWriter, IEnumerable details, + string odataErrorDetailsName) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + Debug.Assert(details != null, "details != null"); + Debug.Assert(odataErrorDetailsName != null, "odataErrorDetailsName != null"); + + // "details": [ + jsonWriter.WriteName(odataErrorDetailsName); + jsonWriter.StartArrayScope(); + + foreach (var detail in details.Where(d => d != null)) + { + // { + jsonWriter.StartObjectScope(); + + // "code": "301", + jsonWriter.WriteName(JsonConstants.ODataErrorCodeName); + jsonWriter.WriteValue(detail.ErrorCode ?? string.Empty); + + if (detail.Target != null) + { + // "target": "$search" + jsonWriter.WriteName(JsonConstants.ODataErrorTargetName); + jsonWriter.WriteValue(detail.Target); + } + + // "message": "$search query option not supported", + jsonWriter.WriteName(JsonConstants.ODataErrorMessageName); + jsonWriter.WriteValue(detail.Message ?? string.Empty); + + // } + jsonWriter.EndObjectScope(); + } + + // ] + jsonWriter.EndArrayScope(); + } + + /// + /// Write an inner error property and message. + /// + /// The JSON writer to write the error to. + /// Inner error details. + /// The property name for the inner error property. + /// The number of times this method has been called recursively. + /// The maximum number of nested inner errors to allow. + private static void WriteInnerError(IJsonWriter jsonWriter, ODataInnerError innerError, string innerErrorPropertyName, int recursionDepth, int maxInnerErrorDepth) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + Debug.Assert(innerErrorPropertyName != null, "innerErrorPropertyName != null"); + + ValidationUtils.IncreaseAndValidateRecursionDepth(ref recursionDepth, maxInnerErrorDepth); + + // "innererror": { + jsonWriter.WriteName(innerErrorPropertyName); + jsonWriter.StartObjectScope(); + + //// NOTE: we add empty elements if no information is provided for the message, error type and stack trace + //// to stay compatible with Astoria. + + // "message": "" + jsonWriter.WriteName(JsonConstants.ODataErrorInnerErrorMessageName); + jsonWriter.WriteValue(innerError.Message ?? string.Empty); + + // "type": " + jsonWriter.WriteName(JsonConstants.ODataErrorInnerErrorTypeNameName); + jsonWriter.WriteValue(innerError.TypeName ?? string.Empty); + + // "stacktrace": "" + jsonWriter.WriteName(JsonConstants.ODataErrorInnerErrorStackTraceName); + jsonWriter.WriteValue(innerError.StackTrace ?? string.Empty); + + if (innerError.InnerError != null) + { + // "internalexception": { } + WriteInnerError(jsonWriter, innerError.InnerError, JsonConstants.ODataErrorInnerErrorInnerErrorName, recursionDepth, maxInnerErrorDepth); + } + + // } + jsonWriter.EndObjectScope(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataStringEscapeOption.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataStringEscapeOption.cs new file mode 100644 index 0000000..192b881 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/ODataStringEscapeOption.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + /// + /// Enum type to specify how to escape string value when writing JSON text. + /// + public enum ODataStringEscapeOption + { + /// + /// All non-ASCII and control characters (e.g. newline) are escaped. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Ascii is meaningful")] + EscapeNonAscii, + + /// + /// Only control characters (e.g. newline) are escaped. + /// + EscapeOnlyControls + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/TextWriterWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/TextWriterWrapper.cs new file mode 100644 index 0000000..30d0549 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Json/TextWriterWrapper.cs @@ -0,0 +1,93 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Json +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.IO; + using System.Text; + #endregion Namespaces + + /// + /// Writes text non indented + /// + internal abstract class TextWriterWrapper : TextWriter + { + /// + /// The underlying writer to write to. + /// + protected TextWriter writer; + + /// + /// Constructor + /// + /// An System.IFormatProvider object that controls formatting. + protected TextWriterWrapper(IFormatProvider formatProvider) + : base(formatProvider) + { + } + + /// + /// Returns the Encoding for the given writer. + /// + public override Encoding Encoding + { + get + { + return this.writer.Encoding; + } + } + + /// + /// Returns the new line character. + /// + public override string NewLine + { + get + { + return this.writer.NewLine; + } + } + + /// + /// Increases the level of indentation applied to the output. + /// + public virtual void IncreaseIndentation() + { + } + + /// + /// Decreases the level of indentation applied to the output. + /// + public virtual void DecreaseIndentation() + { + } + + /// + /// Clears the buffer of the current writer. + /// + public override void Flush() + { + this.writer.Flush(); + } + + /// + /// Closes or disposes the underlying writer. + /// + protected static void InternalCloseOrDispose() + { + Debug.Assert(false, "Should never call Close or Dispose on the TextWriter."); + + // This is done to make sure we don't accidently close or dispose the underlying stream. + // Since we don't own the stream, we should never close or dispose it. + throw new NotImplementedException(); + } + } +} + + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightReaderResourceState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightReaderResourceState.cs new file mode 100644 index 0000000..5acdd38 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightReaderResourceState.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Collections.Generic; + using Microsoft.OData.Edm; + using Microsoft.OData.Evaluation; + #endregion Namespaces + + /// + /// Interface representing a state of the JSON reader for resource. + /// + internal interface IODataJsonLightReaderResourceState + { + /// + /// The resource being read. + /// + ODataResourceBase Resource { get; } + + /// + /// The structured type for the resource (if available) + /// + IEdmStructuredType ResourceType { get; } + + /// + /// The expected type defined in the model for the resource (if available) + /// + IEdmStructuredType ResourceTypeFromMetadata { get; set; } + + /// + /// The navigation source for the resource (if available) + /// + IEdmNavigationSource NavigationSource { get; } + + /// + /// The metadata builder instance for the resource. + /// + ODataResourceMetadataBuilder MetadataBuilder { get; set; } + + /// + /// Flag which indicates that during parsing of the resource represented by this state, + /// any property which is not an instance annotation was found. This includes property annotations + /// for property which is not present in the payload. + /// + /// + /// This is used to detect incorrect ordering of the payload (for example odata.id must not come after the first property). + /// + bool AnyPropertyFound { get; set; } + + /// + /// If the reader finds a nested resource info to report, but it must first report the parent resource + /// it will store the nested resource info in this property. So this will only ever store the first nested resource info of a resource. + /// + ODataJsonLightReaderNestedInfo FirstNestedInfo { get; set; } + + /// + /// The duplicate property names checker for the resource represented by the current state. May be null. + /// + PropertyAndAnnotationCollector PropertyAndAnnotationCollector { get; } + + /// + /// The selected properties that should be expanded during template evaluation. + /// + SelectedPropertiesNode SelectedProperties { get; } + + /// + /// The set of names of the navigation properties we have read so far while reading the resource. + /// + List NavigationPropertiesRead { get; } + + /// + /// true if we have started processing missing projected navigation links, false otherwise. + /// + bool ProcessingMissingProjectedNestedResourceInfos { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightWriterResourceState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightWriterResourceState.cs new file mode 100644 index 0000000..f652e5b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/IODataJsonLightWriterResourceState.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Interface representing a state of the JSON writer for resource. + /// + internal interface IODataJsonLightWriterResourceState + { + /// + /// The resource being written. + /// + ODataResourceBase Resource { get; } + + /// + /// The structured type for the resource (if available) + /// + IEdmStructuredType ResourceType { get; } + + /// + /// The structured type which was derived from the model (may be either the same as structured type or its base type). + /// + IEdmStructuredType ResourceTypeFromMetadata { get; } + + /// + /// The serialization info for the current resource. + /// + ODataResourceSerializationInfo SerializationInfo { get; } + + /// + /// The current resource is for undeclared property or not. + /// + bool IsUndeclared { get; } + + /// + /// Flag which indicates that the odata.editLink metadata property has been written. + /// + bool EditLinkWritten { get; set; } + + /// + /// Flag which indicates that the odata.readLink metadata property has been written. + /// + bool ReadLinkWritten { get; set; } + + /// + /// Flag which indicates that the odata.mediaEditLink metadata property has been written. + /// + bool MediaEditLinkWritten { get; set; } + + /// + /// Flag which indicates that the odata.mediaReadLink metadata property has been written. + /// + bool MediaReadLinkWritten { get; set; } + + /// + /// Flag which indicates that the odata.mediaContentType metadata property has been written. + /// + bool MediaContentTypeWritten { get; set; } + + /// + /// Flag which indicates that the odata.mediaEtag metadata property has been written. + /// + bool MediaETagWritten { get; set; } + + /// + /// Gets or creates the type context to answer basic questions regarding the type info of the resource. + /// + /// True if writing a response payload, false otherwise. + /// The type context to answer basic questions regarding the type info of the resource. + ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataLevel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataLevel.cs new file mode 100644 index 0000000..dd8c198 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataLevel.cs @@ -0,0 +1,166 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Evaluation; + + #endregion Namespaces + + /// + /// Class responsible for logic specific to the JSON Light full metadata level (indicated by "odata.metadata=full" in the media type). + /// + /// + /// The general rule-of-thumb for full-metadata payloads is that they include all "odata.*" annotations that would be included in minimal metadata mode, + /// plus any "odata.*" annotations that could be computed client-side if we the client had a model. + /// + internal sealed class JsonFullMetadataLevel : JsonLightMetadataLevel + { + /// + /// The Edm model. + /// + private readonly IEdmModel model; + + /// + /// The metadata document uri from the writer settings. + /// + private readonly Uri metadataDocumentUri; + + /// + /// Constructs a new . + /// + /// The metadata document uri from the writer settings. + /// The Edm model. + internal JsonFullMetadataLevel(Uri metadataDocumentUri, IEdmModel model) + { + Debug.Assert(model != null, "model != null"); + + this.metadataDocumentUri = metadataDocumentUri; + this.model = model; + } + + /// + /// Returns the metadata document URI which has been validated to be non-null. + /// + private Uri NonNullMetadataDocumentUri + { + get + { + if (this.metadataDocumentUri == null) + { + throw new ODataException(Strings.ODataOutputContext_MetadataDocumentUriMissing); + } + + return this.metadataDocumentUri; + } + } + + /// + /// Returns the oracle to use when determing the type name to write for entries and values. + /// + /// An oracle that can be queried to determine the type name to write. + internal override JsonLightTypeNameOracle GetTypeNameOracle() + { + return new JsonFullMetadataTypeNameOracle(); + } + + /// + /// Creates the metadata builder for the given resource. If such a builder is set, asking for payload + /// metadata properties (like EditLink) of the resource may return a value computed by convention, + /// depending on the metadata level and whether the user manually set an edit link or not. + /// + /// The resource to create the metadata builder for. + /// The context object to answer basic questions regarding the type of the resource or resource set. + /// The serialization info for the resource. + /// The structured type of the resource. + /// The selected properties of this scope. + /// true if the resource metadata builder to create should be for a response payload; false for a request. + /// true if keys should go in separate segments in auto-generated URIs, false if they should go in parentheses. + /// The OData Uri. + /// The created metadata builder. + internal override ODataResourceMetadataBuilder CreateResourceMetadataBuilder( + ODataResourceBase resource, + IODataResourceTypeContext typeContext, + ODataResourceSerializationInfo serializationInfo, + IEdmStructuredType actualResourceType, + SelectedPropertiesNode selectedProperties, + bool isResponse, + bool keyAsSegment, + ODataUri odataUri) + { + Debug.Assert(resource != null, "resource != null"); + Debug.Assert(typeContext != null, "typeContext != null"); + Debug.Assert(selectedProperties != null, "selectedProperties != null"); + + IODataMetadataContext metadataContext = new ODataMetadataContext( + isResponse, + this.model, + this.NonNullMetadataDocumentUri, + odataUri); + + ODataConventionalUriBuilder uriBuilder = new ODataConventionalUriBuilder(metadataContext.ServiceBaseUri, + keyAsSegment ? ODataUrlKeyDelimiter.Slash : ODataUrlKeyDelimiter.Parentheses); + + IODataResourceMetadataContext resourceMetadataContext = ODataResourceMetadataContext.Create(resource, typeContext, serializationInfo, actualResourceType, metadataContext, selectedProperties); + + // Create ODataConventionalEntityMetadataBuilder if actualResourceType is entity type or typeContext.NavigationSourceKind is not none (complex type would be none) for no model scenario. + if (actualResourceType != null && actualResourceType.TypeKind == EdmTypeKind.Entity || + actualResourceType == null && typeContext.NavigationSourceKind != EdmNavigationSourceKind.None) + { + return new ODataConventionalEntityMetadataBuilder(resourceMetadataContext, metadataContext, uriBuilder); + } + else + { + return new ODataConventionalResourceMetadataBuilder(resourceMetadataContext, metadataContext, uriBuilder); + } + } + + /// + /// Injects the appropriate metadata builder based on the metadata level. + /// + /// The resource to inject the builder. + /// The metadata builder to inject. + internal override void InjectMetadataBuilder(ODataResourceBase resource, ODataResourceMetadataBuilder builder) + { + base.InjectMetadataBuilder(resource, builder); + + // Inject to the Media Resource. + var mediaResource = resource.NonComputedMediaResource; + if (mediaResource != null) + { + mediaResource.SetMetadataBuilder(builder, /*propertyName*/null); + } + + // Inject to named stream property values + if (resource.NonComputedProperties != null) + { + foreach (ODataProperty property in resource.NonComputedProperties) + { + var streamReferenceValue = property.ODataValue as ODataStreamReferenceValue; + if (streamReferenceValue != null) + { + streamReferenceValue.SetMetadataBuilder(builder, property.Name); + } + } + } + + // Inject to operations + IEnumerable operations = ODataUtilsInternal.ConcatEnumerables((IEnumerable)resource.NonComputedActions, (IEnumerable)resource.NonComputedFunctions); + if (operations != null) + { + foreach (ODataOperation operation in operations) + { + operation.SetMetadataBuilder(builder, this.NonNullMetadataDocumentUri); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataTypeNameOracle.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataTypeNameOracle.cs new file mode 100644 index 0000000..5314f56 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonFullMetadataTypeNameOracle.cs @@ -0,0 +1,123 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Class responsible for determining the type name that should be written on the wire for entries and values in JSON full metadata mode. + /// + internal sealed class JsonFullMetadataTypeNameOracle : JsonLightTypeNameOracle + { + /// + /// Determines the resource set type name to write to the payload. + /// + /// The expected resource type name of the items in the resource set. + /// The ODataResourceSet whose type is to be written. + /// true if the resource set is for some undeclared property + /// Type name to write to the payload, or null if no type name should be written. + internal override string GetResourceSetTypeNameForWriting(string expectedResourceTypeName, ODataResourceSet resourceSet, bool isUndeclared) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + if (resourceSet.TypeAnnotation != null) + { + return resourceSet.TypeAnnotation.TypeName; + } + + return resourceSet.TypeName; + } + + /// + /// Determines the resource type name to write to the payload. + /// + /// The expected type name, e.g. the base type of the an entity set or a collection of complex or the nav prop or a complex property. + /// The ODataResource whose type is to be written. + /// true if the ODataResource is for some undeclared property + /// Type name to write to the payload, or null if no type name should be written. + internal override string GetResourceTypeNameForWriting(string expectedTypeName, ODataResourceBase resource, bool isUndeclared = false) + { + Debug.Assert(resource != null, "resource != null"); + + if (resource.TypeAnnotation != null) + { + return resource.TypeAnnotation.TypeName; + } + + return resource.TypeName; + } + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The type as expected by the model. + /// The type resolved from the value. + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal override string GetValueTypeNameForWriting( + ODataValue value, + IEdmTypeReference typeReferenceFromMetadata, + IEdmTypeReference typeReferenceFromValue, + bool isOpenProperty) + { + string typeNameToWrite; + if (TypeNameOracle.TryGetTypeNameFromAnnotation(value, out typeNameToWrite)) + { + return typeNameToWrite; + } + + // Do not write type name when the type is native json type. + if (typeReferenceFromValue != null + && typeReferenceFromValue.IsPrimitive() + && JsonSharedUtils.ValueTypeMatchesJsonType((ODataPrimitiveValue)value, typeReferenceFromValue.AsPrimitive())) + { + return null; + } + + return GetTypeNameFromValue(value); + } + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The serialization info of current property + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal override string GetValueTypeNameForWriting( + ODataValue value, + PropertySerializationInfo propertyInfo, + bool isOpenProperty) + { + PropertyValueTypeInfo valueType = propertyInfo.ValueType; + + string typeNameToWrite; + if (TypeNameOracle.TryGetTypeNameFromAnnotation(value, out typeNameToWrite)) + { + return typeNameToWrite; + } + + if (valueType.TypeReference != null) + { + // Do not write type name when the type is native json type. + if (valueType.IsPrimitive && JsonSharedUtils.ValueTypeMatchesJsonType((ODataPrimitiveValue)value, valueType.PrimitiveTypeKind)) + { + return null; + } + } + + return GetTypeNameFromValue(value); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs new file mode 100644 index 0000000..2f16c6f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs @@ -0,0 +1,120 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + #endregion Namespaces + + /// + /// Constants for the JSON Lite format. + /// + internal static class JsonLightConstants + { + /// The prefix for OData annotation names. + internal const string ODataAnnotationNamespacePrefix = "odata."; + + /// The separator of property annotations. + internal const char ODataPropertyAnnotationSeparatorChar = '@'; + + /// + /// The 'null' property name for the Json Light value property. + /// This is an OData 3.0 protocol element used for compatibility + /// with 6.x library version. + /// + internal const string ODataNullPropertyName = "null"; + + /// The value 'true' for the OData null annotation. + internal const string ODataNullAnnotationTrueValue = "true"; + + /// The 'value' property name for the Json Light value property. + internal const string ODataValuePropertyName = "value"; + + /// The 'error' property name for the Json Light value property. + internal const string ODataErrorPropertyName = "error"; + + /// The 'source' property name for the Json Light value property. + internal const string ODataSourcePropertyName = "source"; + + /// The 'target' property name for the Json Light value property. + internal const string ODataTargetPropertyName = "target"; + + /// The 'relationship' property name for the Json Light value property. + internal const string ODataRelationshipPropertyName = "relationship"; + + /// The 'id' property name for the Json Light value property. + internal const string ODataIdPropertyName = "id"; + + /// The 'delta' property name for the Json Light value property. + internal const string ODataDeltaPropertyName = "delta"; + + /// The 'reason' property name for the Json Light value property. + internal const string ODataReasonPropertyName = "reason"; + + /// The value 'changed' for the Json Light 'reason' property. + internal const string ODataReasonChangedValue = "changed"; + + /// The value 'deleted' for the Json Light 'reason' property. + internal const string ODataReasonDeletedValue = "deleted"; + + /// The name of the property returned for a URL of a service document element. + internal const string ODataServiceDocumentElementUrlName = "url"; + + /// The string used for the title attribute for the service document element. + internal const string ODataServiceDocumentElementTitle = "title"; + + /// The string used for the kind attribute for the service document element. + internal const string ODataServiceDocumentElementKind = "kind"; + + /// The name of the property returned for a name of a service document element. + internal const string ODataServiceDocumentElementName = "name"; + + /// The name of the $select query option. + internal const string ContextUriSelectQueryOptionName = "$select"; + + /// The '=' character used to separate a query option name from its value. + internal const char ContextUriQueryOptionValueSeparator = '='; + + /// The '&' separator character between query options. + internal const char ContextUriQueryOptionSeparator = '&'; + + /// The '(' used to mark the start of function parameters in the fragment of a context URI. + internal const char FunctionParameterStart = '('; + + /// The ')' used to mark the end of function parameters in the fragment of a context URI. + internal const char FunctionParameterEnd = ')'; + + /// The "," to use as the separator for the function parameters in the fragment of a context URI. + internal const string FunctionParameterSeparator = ","; + + /// The ',' to use as the separator for the function parameters. + internal const char FunctionParameterSeparatorChar = ','; + + /// The "=@" to use as the separator for the function parameter in target. + internal const string FunctionParameterAssignment = "=@"; + + /// The kind name of the service document singleton element. + internal const string ServiceDocumentSingletonKindName = "Singleton"; + + /// The kind name of the service document function import element. + internal const string ServiceDocumentFunctionImportKindName = "FunctionImport"; + + /// The kind name of the service document entity set element. + internal const string ServiceDocumentEntitySetKindName = "EntitySet"; + + /// The simplified OData Context property name. + internal const string SimplifiedODataContextPropertyName = "@context"; + + /// The simplified OData Id property name. + internal const string SimplifiedODataIdPropertyName = "@id"; + + /// The simplified OData Type property name. + internal const string SimplifiedODataTypePropertyName = "@type"; + + /// The simplified Removed property name. + internal const string SimplifiedODataRemovedPropertyName = "@removed"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightMetadataLevel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightMetadataLevel.cs new file mode 100644 index 0000000..677b75a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightMetadataLevel.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Evaluation; + #endregion Namespaces + + /// + /// Class responsible for logic that varies based on the JSON Light metadata level. + /// + internal abstract class JsonLightMetadataLevel + { + /// + /// Creates the appropriate metadata level based on the media type being written. + /// + /// The full media type being written. This media type must have a type/subtype of "application/json". + /// The metadata document uri from the writer settings. + /// The edm model. + /// true if we are writing a response, false otherwise. + /// The JSON Light metadata level being written. + internal static JsonLightMetadataLevel Create(ODataMediaType mediaType, Uri metadataDocumentUri, IEdmModel model, bool writingResponse) + { + Debug.Assert(mediaType != null, "mediaType != null"); + + Debug.Assert( + string.Compare(mediaType.FullTypeName, MimeConstants.MimeApplicationJson, StringComparison.OrdinalIgnoreCase) == 0, + "media type should be application/json at this point"); + + if (writingResponse && mediaType.Parameters != null) + { + foreach (KeyValuePair parameter in mediaType.Parameters) + { + if (!HttpUtils.IsMetadataParameter(parameter.Key)) + { + // Only look at the "odata.metadata" parameter. + continue; + } + + if (string.Compare(parameter.Value, MimeConstants.MimeMetadataParameterValueMinimal, StringComparison.OrdinalIgnoreCase) == 0) + { + return new JsonMinimalMetadataLevel(); + } + + if (string.Compare(parameter.Value, MimeConstants.MimeMetadataParameterValueFull, StringComparison.OrdinalIgnoreCase) == 0) + { + return new JsonFullMetadataLevel(metadataDocumentUri, model); + } + + if (string.Compare(parameter.Value, MimeConstants.MimeMetadataParameterValueNone, StringComparison.OrdinalIgnoreCase) == 0) + { + return new JsonNoMetadataLevel(); + } + + Debug.Assert( + string.Compare(parameter.Value, MimeConstants.MimeMetadataParameterValueVerbose, StringComparison.OrdinalIgnoreCase) != 0, + "media type should not indicate verbose json at this point."); + } + } + + // No "odata.metadata" media type parameter implies minimal metadata. + return new JsonMinimalMetadataLevel(); + } + + /// + /// Returns the oracle to use when determing the type name to write for entries and values. + /// + /// An oracle that can be queried to determine the type name to write. + internal abstract JsonLightTypeNameOracle GetTypeNameOracle(); + + /// + /// Creates the metadata builder for the given resource. If such a builder is set, asking for payload + /// metadata properties (like EditLink) of the resource may return a value computed by convention, + /// depending on the metadata level and whether the user manually set an edit link or not. + /// + /// The resource to create the metadata builder for. + /// The context object to answer basic questions regarding the type of the resource or resource set. + /// The serialization info for the resource. + /// The structured type of the resource. + /// The selected properties of this scope. + /// true if the resource metadata builder to create should be for a response payload; false for a request. + /// true if keys should go in separate segments in auto-generated URIs, false if they should go in parentheses. + /// A null value means the user hasn't specified a preference and we should look for an annotation in the entity container, if available. + /// The OData Uri. + /// The created metadata builder. + internal abstract ODataResourceMetadataBuilder CreateResourceMetadataBuilder( + ODataResourceBase resource, + IODataResourceTypeContext typeContext, + ODataResourceSerializationInfo serializationInfo, + IEdmStructuredType actualResourceType, + SelectedPropertiesNode selectedProperties, + bool isResponse, + bool keyAsSegment, + ODataUri odataUri); + + /// + /// Injects the appropriate metadata builder based on the metadata level. + /// + /// The resource to inject the builder. + /// The metadata builder to inject. + internal virtual void InjectMetadataBuilder(ODataResourceBase resource, ODataResourceMetadataBuilder builder) + { + resource.MetadataBuilder = builder; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightODataAnnotationWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightODataAnnotationWriter.cs new file mode 100644 index 0000000..4564c9a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightODataAnnotationWriter.cs @@ -0,0 +1,128 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// JsonLight writer for OData annotations, i.e., odata.* + /// + internal sealed class JsonLightODataAnnotationWriter + { + /// + /// Length of "odata.". + /// + private static readonly int ODataAnnotationPrefixLength = + JsonLightConstants.ODataAnnotationNamespacePrefix.Length; + + /// + /// The underlying JSON writer. + /// + private readonly IJsonWriter jsonWriter; + + /// + /// Whether write odata annotation without "odata." prefix in name. + /// + private readonly bool enableWritingODataAnnotationWithoutPrefix; + + /// + /// OData Version to use when writing OData annotations. + /// + private readonly ODataVersion odataVersion; + + /// + /// Constructor. + /// + /// The underlying JSON writer. + /// Whether write odata annotation without "odata." prefix in name. + /// OData Version used when writing the annotations. + public JsonLightODataAnnotationWriter(IJsonWriter jsonWriter, bool enableWritingODataAnnotationWithoutPrefix, ODataVersion? odataVersion) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + + this.jsonWriter = jsonWriter; + this.enableWritingODataAnnotationWithoutPrefix = enableWritingODataAnnotationWithoutPrefix; + this.odataVersion = odataVersion ?? ODataVersion.V4; + } + + /// + /// Writes the odata.type instance annotation with the specified type name. + /// + /// The type name to write. + /// Whether to write the raw typeName without removing/adding prefix 'Edm.'/'#'. + public void WriteODataTypeInstanceAnnotation(string typeName, bool writeRawValue = false) + { + Debug.Assert(typeName != null, "typeName != null"); + + // "@odata.type": "#typename" + WriteInstanceAnnotationName(ODataAnnotationNames.ODataType); + if (writeRawValue) + { + jsonWriter.WriteValue(typeName); + } + else + { + jsonWriter.WriteValue(WriterUtils.PrefixTypeNameForWriting(typeName, odataVersion)); + } + } + + /// + /// Writes the odata.type property annotation for the specified property with the specified type name. + /// + /// The name of the property for which to write the odata.type annotation. + /// The type name to write. + public void WriteODataTypePropertyAnnotation(string propertyName, string typeName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(typeName != null, "typeName != null"); + + // "@odata.type": #"typename" + WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataType); + jsonWriter.WriteValue(WriterUtils.PrefixTypeNameForWriting(typeName, odataVersion)); + } + + /// + /// Write a JSON property name which represents a property annotation. + /// + /// The name of the property to annotate. + /// The name of the annotation to write. + public void WritePropertyAnnotationName(string propertyName, string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(annotationName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, + StringComparison.Ordinal), "annotationName.StartsWith(\"odata.\")"); + + jsonWriter.WritePropertyAnnotationName(propertyName, SimplifyODataAnnotationName(annotationName)); + } + + /// + /// Write a JSON instance annotation name which represents a instance annotation. + /// + /// The name of the instance annotation to write. + public void WriteInstanceAnnotationName(string annotationName) + { + Debug.Assert(annotationName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, + StringComparison.Ordinal), "annotationName.StartsWith(\"odata.\")"); + + jsonWriter.WriteInstanceAnnotationName(SimplifyODataAnnotationName(annotationName)); + } + + /// + /// Simplify OData annotation name if necessary. + /// + /// The annotation name to be simplified. + /// The simplified annotation name. + private string SimplifyODataAnnotationName(string annotationName) + { + return enableWritingODataAnnotationWithoutPrefix ? annotationName.Substring(ODataAnnotationPrefixLength) : annotationName; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightTypeNameOracle.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightTypeNameOracle.cs new file mode 100644 index 0000000..dadc151 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonLightTypeNameOracle.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Class responsible for determining the type name that should be written on the wire for entries and values in JSON Light. + /// + internal abstract class JsonLightTypeNameOracle : TypeNameOracle + { + /// + /// Determines the resource set type name to write to the payload. + /// + /// The expected resource type name of the items in the resource set. + /// The ODataResourceSet whose type is to be written. + /// true if the resource set is for some undeclared property. + /// Type name to write to the payload, or null if no type name should be written. + internal abstract string GetResourceSetTypeNameForWriting(string expectedResourceTypeName, ODataResourceSet resourceSet, bool isUndeclared); + + /// + /// Determines the entity type name to write to the payload. + /// + /// The expected type name, e.g. the base type of the set or the nav prop. + /// The ODataResource whose type is to be written. + /// true if the resource is for some undeclared property. + /// Type name to write to the payload, or null if no type name should be written. + internal abstract string GetResourceTypeNameForWriting(string expectedTypeName, ODataResourceBase resource, bool isUndeclared); + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The type as expected by the model. + /// The type resolved from the value. + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal abstract string GetValueTypeNameForWriting( + ODataValue value, + IEdmTypeReference typeReferenceFromMetadata, + IEdmTypeReference typeReferenceFromValue, + bool isOpenProperty); + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The serialization info of current property + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal abstract string GetValueTypeNameForWriting( + ODataValue value, + PropertySerializationInfo propertyInfo, + bool isOpenProperty); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataLevel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataLevel.cs new file mode 100644 index 0000000..abb7ec2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataLevel.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + using Microsoft.OData.Edm; + using Microsoft.OData.Evaluation; + + /// + /// Class responsible for logic specific to the JSON Light minimal metadata level (indicated by "odata.metadata=minimal" in the media type, or lack of an "odata" parameter in a v3 and above request). + /// + /// + /// The general rule-of-thumb for minimal-metadata payloads is that they include all "odata.*" annotations that can't be computed client-side, assuming the client has the server model available + /// as well as the ability to compute missing payload metadata based on the standard conventions. + /// + internal sealed class JsonMinimalMetadataLevel : JsonLightMetadataLevel + { + /// + /// Returns the oracle to use when determing the type name to write for entries and values. + /// + /// An oracle that can be queried to determine the type name to write. + internal override JsonLightTypeNameOracle GetTypeNameOracle() + { + return new JsonMinimalMetadataTypeNameOracle(); + } + + /// + /// Creates the metadata builder for the given resource. If such a builder is set, asking for payload + /// metadata properties (like EditLink) of the resource may return a value computed by convention, + /// depending on the metadata level and whether the user manually set an edit link or not. + /// + /// The resource to create the metadata builder for. + /// The context object to answer basic questions regarding the type of the resource or resource set. + /// The serialization info for the resource. + /// The structured type of the resource. + /// The selected properties of this scope. + /// true if the resource metadata builder to create should be for a response payload; false for a request. + /// true if keys should go in separate segments in auto-generated URIs, false if they should go in parentheses. + /// The OData Uri. + /// The created metadata builder. + internal override ODataResourceMetadataBuilder CreateResourceMetadataBuilder( + ODataResourceBase resource, + IODataResourceTypeContext typeContext, + ODataResourceSerializationInfo serializationInfo, + IEdmStructuredType actualResourceType, + SelectedPropertiesNode selectedProperties, + bool isResponse, + bool keyAsSegment, + ODataUri odataUri) + { + // For minimal metadata we don't want to change the metadata builder that's currently on the resource because the resource might come from a JSON light + // reader and it would contain the metadata builder from the reader. Until we give the user the ability to choose whether to write what was reported + // by the reader versus what was on the wire, we no-op here so the writer will just write what's on the OM for now. + return null; + } + + /// + /// Injects the appropriate metadata builder based on the metadata level. + /// + /// The resource to inject the builder. + /// The metadata builder to inject. + internal override void InjectMetadataBuilder(ODataResourceBase resource, ODataResourceMetadataBuilder builder) + { + // For minimal metadata we don't want to change the metadata builder that's currently on the resource because the resource might come from a JSON light + // reader and it would contain the metadata builder from the reader. Until we give the user the ability to choose whether to write what was reported + // by the reader versus what was on the wire, we no-op here so the writer will just write what's on the OM for now. + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataTypeNameOracle.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataTypeNameOracle.cs new file mode 100644 index 0000000..87f55d9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonMinimalMetadataTypeNameOracle.cs @@ -0,0 +1,191 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Class responsible for determining the type name that should be written on the wire for entries and values in JSON minimal metadata mode, + /// + internal sealed class JsonMinimalMetadataTypeNameOracle : JsonLightTypeNameOracle + { + /// + /// Determines the resource set type name to write to the payload. + /// + /// The expected resource type name of the items in the resource set. + /// The ODataResourceSet whose type is to be written. + /// true if the resource set is for some undeclared property + /// Type name to write to the payload, or null if no type name should be written. + internal override string GetResourceSetTypeNameForWriting(string expectedResourceTypeName, ODataResourceSet resourceSet, bool isUndeclared) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + if (resourceSet.TypeAnnotation != null) + { + return resourceSet.TypeAnnotation.TypeName; + } + + var expectedResourceSetTypeName = expectedResourceTypeName == null ? null : EdmLibraryExtensions.GetCollectionTypeName(expectedResourceTypeName); + + if (expectedResourceSetTypeName != resourceSet.TypeName || isUndeclared) + { + return resourceSet.TypeName; + } + + return null; + } + + /// + /// Determines the entity type name to write to the payload. + /// + /// The expected type name, e.g. the base type of the set or the nav prop. + /// The ODataResource whose type is to be written. + /// true if the ODataResource is for some undeclared property + /// Type name to write to the payload, or null if no type name should be written. + internal override string GetResourceTypeNameForWriting(string expectedTypeName, ODataResourceBase resource, bool isUndeclared = false) + { + Debug.Assert(resource != null, "resource != null"); + + if (resource.TypeAnnotation != null) + { + return resource.TypeAnnotation.TypeName; + } + + // We only write entity type names in Json Light if it's more derived (different) from the expected type name. + string resourceTypeName = resource.TypeName; + if (expectedTypeName != resourceTypeName || isUndeclared) + { + return resourceTypeName; + } + + return null; + } + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The type as expected by the model. + /// The type resolved from the value. + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal override string GetValueTypeNameForWriting( + ODataValue value, + IEdmTypeReference typeReferenceFromMetadata, + IEdmTypeReference typeReferenceFromValue, + bool isOpenProperty) + { + string fullTypeNameFromValue = null; + + string typeNameToWrite; + if (TypeNameOracle.TryGetTypeNameFromAnnotation(value, out typeNameToWrite)) + { + return typeNameToWrite; + } + + if (typeReferenceFromValue != null) + { + fullTypeNameFromValue = typeReferenceFromValue.FullName(); + + // Write type name when the type in the payload is more derived than the type from metadata. + if (typeReferenceFromMetadata != null && typeReferenceFromMetadata.Definition.AsActualType().FullTypeName() != fullTypeNameFromValue) + { + return fullTypeNameFromValue; + } + + // Do not write type name when the type is native json type. + if (typeReferenceFromValue.IsPrimitive() && JsonSharedUtils.ValueTypeMatchesJsonType((ODataPrimitiveValue)value, typeReferenceFromValue.AsPrimitive())) + { + return null; + } + + // Note: When writing derived structured value in a payload, we don't have the expected type. + // So always write @odata.type for top-level derived structured type. + if (typeReferenceFromMetadata == null && typeReferenceFromValue.IsStructured()) + { + if ((typeReferenceFromValue as IEdmStructuredTypeReference).StructuredDefinition().BaseType != null) + { + return fullTypeNameFromValue; + } + } + } + + if (!isOpenProperty) + { + // Do not write type name for non-open properties since we expect the reader to have an expected type (via API or context URI) and thus not need it. + return null; + } + + return fullTypeNameFromValue != null ? fullTypeNameFromValue : GetTypeNameFromValue(value); + } + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The serialization info of current property + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal override string GetValueTypeNameForWriting( + ODataValue value, + PropertySerializationInfo propertyInfo, + bool isOpenProperty) + { + string fullTypeNameFromValue = null; + PropertyValueTypeInfo valueType = propertyInfo.ValueType; + PropertyMetadataTypeInfo modelType = propertyInfo.MetadataType; + + string typeNameToWrite; + if (TypeNameOracle.TryGetTypeNameFromAnnotation(value, out typeNameToWrite)) + { + return typeNameToWrite; + } + + if (valueType.TypeReference != null) + { + fullTypeNameFromValue = valueType.FullName; + + // Write type name when the type in the payload is more derived than the type from metadata. + if (modelType.TypeReference != null && modelType.FullName != fullTypeNameFromValue) + { + return fullTypeNameFromValue; + } + + // Do not write type name when the type is native json type. + if (valueType.IsPrimitive && JsonSharedUtils.ValueTypeMatchesJsonType((ODataPrimitiveValue)value, valueType.PrimitiveTypeKind)) + { + return null; + } + + // Note: When writing derived complexType value in a payload, we don't have the expected type. + // So always write @odata.type for top-level derived complex type. + if (modelType.TypeReference == null && valueType.IsComplex) + { + if ((valueType.TypeReference as IEdmComplexTypeReference).ComplexDefinition().BaseType != null) + { + return fullTypeNameFromValue; + } + } + } + + if (!isOpenProperty) + { + // Do not write type name for non-open properties since we expect the reader to have an expected type (via API or context URI) and thus not need it. + return null; + } + + return fullTypeNameFromValue != null ? fullTypeNameFromValue : GetTypeNameFromValue(value); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataLevel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataLevel.cs new file mode 100644 index 0000000..9f2a282 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataLevel.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + using Microsoft.OData.Evaluation; + using Microsoft.OData.Edm; + + /// + /// Class responsible for logic specific to the JSON Light no metadata level (indicated by "odata.metadata=none" in the media type). + /// + /// + /// The general rule-of-thumb for no-metadata payloads is that they omit any "odata.*" annotations, + /// except for odata.nextlink and odata.count, since the client would get a inaccurate representation of the data available if they were left out. + /// + internal sealed class JsonNoMetadataLevel : JsonLightMetadataLevel + { + /// + /// Returns the oracle to use when determing the type name to write for entries and values. + /// + /// An oracle that can be queried to determine the type name to write. + internal override JsonLightTypeNameOracle GetTypeNameOracle() + { + return new JsonNoMetadataTypeNameOracle(); + } + + /// + /// Creates the metadata builder for the given resource. If such a builder is set, asking for payload + /// metadata properties (like EditLink) of the resource may return a value computed by convention, + /// depending on the metadata level and whether the user manually set an edit link or not. + /// + /// The resource to create the metadata builder for. + /// The context object to answer basic questions regarding the type of the resource or resource set. + /// The serialization info for the resource. + /// The structured type of the resource. + /// The selected properties of this scope. + /// true if the resource metadata builder to create should be for a response payload; false for a request. + /// true if keys should go in separate segments in auto-generated URIs, false if they should go in parentheses. + /// The OData Uri. + /// The created metadata builder. + internal override ODataResourceMetadataBuilder CreateResourceMetadataBuilder( + ODataResourceBase resource, + IODataResourceTypeContext typeContext, + ODataResourceSerializationInfo serializationInfo, + IEdmStructuredType actualResourceType, + SelectedPropertiesNode selectedProperties, + bool isResponse, + bool keyAsSegment, + ODataUri odataUri) + { + return ODataResourceMetadataBuilder.Null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataTypeNameOracle.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataTypeNameOracle.cs new file mode 100644 index 0000000..2720059 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/JsonNoMetadataTypeNameOracle.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Class responsible for determining the type name that should be written on the wire for entries and values in JSON no metadata mode. + /// + internal sealed class JsonNoMetadataTypeNameOracle : JsonLightTypeNameOracle + { + /// + /// Determines the resource set type name to write to the payload. + /// + /// The expected resource type name of the items in the resource set. + /// The ODataResourceSet whose type is to be written. + /// true if the resource set is for some undeclared property + /// Type name to write to the payload, or null if no type name should be written. + internal override string GetResourceSetTypeNameForWriting(string expectedResourceTypeName, ODataResourceSet resourceSet, bool isUndeclared) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + return null; + } + + /// + /// Determines the entity type name to write to the payload. + /// + /// The expected type name, e.g. the base type of the set or the nav prop. + /// The ODataResource whose type is to be written. + /// true if the ODataResource is for some undeclared property + /// Type name to write to the payload, or null if no type name should be written. + internal override string GetResourceTypeNameForWriting(string expectedTypeName, ODataResourceBase resource, bool isUndeclared = false) + { + Debug.Assert(resource != null, "resource != null"); + + return null; + } + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The type as expected by the model. + /// The type resolved from the value. + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal override string GetValueTypeNameForWriting( + ODataValue value, + IEdmTypeReference typeReferenceFromMetadata, + IEdmTypeReference typeReferenceFromValue, + bool isOpenProperty) + { + return null; + } + + /// + /// Determines the type name to write to the payload. Json Light type names are only written into the payload for open properties + /// or if the payload type name is more derived than the model type name. + /// + /// The ODataValue whose type name is to be written. + /// The serialization info of current property + /// true if the type name belongs to an open property, false otherwise. + /// Type name to write to the payload, or null if no type should be written. + internal override string GetValueTypeNameForWriting( + ODataValue value, + PropertySerializationInfo propertyInfo, + bool isOpenProperty) + { + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs new file mode 100644 index 0000000..5b33e08 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs @@ -0,0 +1,176 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// Well known OData annotation names reserved for OData Lib. + /// + internal static class ODataAnnotationNames + { + /// + /// Hash set of known odata annotation names that have special meanings to OData Lib. + /// + internal static readonly HashSet KnownODataAnnotationNames = + new HashSet( + new[] + { + ODataContext, + ODataType, + ODataId, + ODataETag, + ODataEditLink, + ODataReadLink, + ODataMediaEditLink, + ODataMediaReadLink, + ODataMediaContentType, + ODataMediaETag, + ODataCount, + ODataNextLink, + ODataBind, + ODataAssociationLinkUrl, + ODataNavigationLinkUrl, + ODataDeltaLink, + ODataRemoved, + ODataDelta, + ODataNull, + }, + StringComparer.Ordinal); + + /// The OData Context annotation name. + internal const string ODataContext = "odata.context"; + + /// The OData Type annotation name. + internal const string ODataType = "odata.type"; + + /// The OData ID annotation name. + internal const string ODataId = "odata.id"; + + /// The OData etag annotation name. + internal const string ODataETag = "odata.etag"; + + /// The OData edit link annotation name. + internal const string ODataEditLink = "odata.editLink"; + + /// The OData read link annotation name. + internal const string ODataReadLink = "odata.readLink"; + + /// The OData media edit link annotation name. + internal const string ODataMediaEditLink = "odata.mediaEditLink"; + + /// The OData media read link annotation name. + internal const string ODataMediaReadLink = "odata.mediaReadLink"; + + /// The OData media content type annotation name. + internal const string ODataMediaContentType = "odata.mediaContentType"; + + /// The OData media etag annotation name. + internal const string ODataMediaETag = "odata.mediaEtag"; + + /// The 'odata.count' annotation name. + internal const string ODataCount = "odata.count"; + + /// The 'odata.nextLink' annotation name. + internal const string ODataNextLink = "odata.nextLink"; + + /// The 'odata.navigationLink' annotation name. + internal const string ODataNavigationLinkUrl = "odata.navigationLink"; + + /// The 'odata.bind' annotation name. + internal const string ODataBind = "odata.bind"; + + /// The 'odata.associationLink' annotation name. + internal const string ODataAssociationLinkUrl = "odata.associationLink"; + + /// The 'odata.deltaLink' annotation name. + internal const string ODataDeltaLink = "odata.deltaLink"; + + /// The 'odata.removed' annotation name. + internal const string ODataRemoved = "odata.removed"; + + /// The 'odata.delta' annotation name. + internal const string ODataDelta = "odata.delta"; + + /// + /// The OData Null annotation name. This is an OData 3.0 protocol element + /// used for compatibility with 6.x library version. + /// + internal const string ODataNull = "odata.null"; + + /// + /// Returns true if the starts with "odata.", false otherwise. + /// + /// The name of the annotation in question. + /// Returns true if the starts with "odata.", false otherwise. + internal static bool IsODataAnnotationName(string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + if (annotationName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal)) + { + return true; + } + + return false; + } + + /// + /// Returns true if the starts with "odata." and is not one of the reserved odata annotation names; returns false otherwise. + /// + /// The annotation name in question. + /// Returns true if the starts with "odata." and is not one of the reserved odata annotation names; returns false otherwise. + internal static bool IsUnknownODataAnnotationName(string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + if (IsODataAnnotationName(annotationName) && !KnownODataAnnotationNames.Contains(annotationName)) + { + return true; + } + + return false; + } + + /// + /// Validates that the is not a reserved OData instance annotation. + /// + /// The instance annotation name to check. + internal static void ValidateIsCustomAnnotationName(string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + // All other reserved OData instance annotations should fail. + if (KnownODataAnnotationNames.Contains(annotationName)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(annotationName)); + } + + Debug.Assert(!IsODataAnnotationName(annotationName), "Unknown names under the odata. namespace should be skipped by ODataJsonLightDeserializer.ParseProperty()."); + } + + /// + /// Get the string without the instance annotation prefix @ + /// + /// the origin annotation name from reader + /// the annotation name without prefix @ + internal static string RemoveAnnotationPrefix(string annotationName) + { + if (!String.IsNullOrEmpty(annotationName) && annotationName[0] == JsonLightConstants.ODataPropertyAnnotationSeparatorChar) + { + return annotationName.Substring(1); + } + + return annotationName; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchAtomicGroupCache.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchAtomicGroupCache.cs new file mode 100644 index 0000000..e8a9e34 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchAtomicGroupCache.cs @@ -0,0 +1,176 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + + #endregion Namespaces + /// + /// Cache for atomic groups along with the stably-ordered message Ids in each group. + /// It also keeps track of atomic group start and atomic group end status during the reading + /// of batch message. + /// + internal sealed class ODataJsonLightBatchAtomicGroupCache + { + /// + /// Lookup table for atomicitGroup. + /// + private readonly Dictionary> groupToMessageIds = new Dictionary>(); + + /// + /// Group Id of the preceding message. Could be null. + /// + private string precedingMessageGroupId = null; + + /// + /// Latest status of whether the processing is within scope of an atomic group. + /// The scope is ended by a top-level message, the starting of another atomic group, or + /// end of the batch messages array. + /// + private bool isWithinAtomicGroup = false; + + /// + /// Whether the processing is within scope of an atomic group. + /// + internal bool IsWithinAtomicGroup + { + get { return this.isWithinAtomicGroup; } + set { this.isWithinAtomicGroup = value; } + } + + /// + /// Given the group Id from reader's current state, determine whether atomic group end is detected. + /// + /// The group Id from the reader's current state. + /// True if atomic group end is detected; false otherwise. + internal bool IsChangesetEnd(string groupId) + { + // Preceding group Id cannot null when we are within an atomic group. + Debug.Assert(!(isWithinAtomicGroup && precedingMessageGroupId == null), + "!(isWithinAtomicGroup && precedingMessageGroupId == null)"); + + if (!isWithinAtomicGroup + || ((precedingMessageGroupId != null) && precedingMessageGroupId.Equals(groupId)) + /*groupId is member of existing atomic group scope*/) + { + return false; + } + + // This groupId ends the preceding atomic group. + this.isWithinAtomicGroup = false; + this.precedingMessageGroupId = null; + return true; + } + + /// + /// Construct the list of Ids for the group, and determine whether this + /// is the start of an atomic group. + /// + /// Message Id to add. + /// Id of the group to add the message Id. Cannot be null. + /// + /// True if changeset start is detected; false otherwise. + /// Ensure all message Ids of the same groups are adjacent, otherwise throw an error. + /// + internal bool AddMessageIdAndGroupId(string messageId, string groupId) + { + Debug.Assert(messageId != null, "messageId != null"); + Debug.Assert(groupId != null, "groupId != null"); + + bool isChangesetStart = false; + if (groupId.Equals(this.precedingMessageGroupId, StringComparison.Ordinal)) + { + // Adjacent groupIds, add messageId to the existing group + Debug.Assert(groupToMessageIds[groupId] != null, "groupToMessageIds[groupId] != null"); + groupToMessageIds[groupId].Add(messageId); + } + else if (!groupToMessageIds.ContainsKey(groupId)) + { + // We get a new groupId. + groupToMessageIds.Add(groupId, new List { messageId }); + + this.precedingMessageGroupId = groupId; + + // Mark the atomic group status. + this.isWithinAtomicGroup = true; + + isChangesetStart = true; + } + else + { + throw new ODataException(Strings.ODataBatchReader_MessageIdPositionedIncorrectly(messageId, groupId)); + } + + return isChangesetStart; + } + + /// + /// Get the atomic group Id of the message; null if the message does not belong to any groups. + /// + /// Id of the message from the json property. + /// The group Id if found; null otherwise. + internal string GetGroupId(string targetMessageId) + { + foreach (KeyValuePair> pair in this.groupToMessageIds) + { + IList messageIds = pair.Value; + if (messageIds != null && messageIds.Contains(targetMessageId)) + { + return pair.Key; + } + } + + return null; + } + + /// + /// Testing whether the given Id is a group Id. + /// + /// The id under test. + /// True if it is group Id of the batch; false otherwise. + internal bool IsGroupId(string id) + { + return this.groupToMessageIds.Keys.Contains(id); + } + + /// + /// Flatten a given list of groupIds and messageIds into a string containing comma-separated message Ids. + /// + /// List of ids to be flattened. + /// The list containing comma-separated message Ids. + internal IList GetFlattenedMessageIds(IList ids) + { + List result = new List(); + if (ids.Count == 0) + { + return result; + } + + foreach (string id in ids) + { + IList reqIds; + if (this.groupToMessageIds.TryGetValue(id, out reqIds)) + { + result.AddRange(reqIds); + } + else + { + result.Add(id); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchBodyContentReaderStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchBodyContentReaderStream.cs new file mode 100644 index 0000000..51cc22c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchBodyContentReaderStream.cs @@ -0,0 +1,368 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + + using Microsoft.OData.Json; + + #endregion Namespaces + + /// + /// Wrapper stream backed by memory stream containing body content of request or response in Json batch. + /// + internal sealed class ODataJsonLightBatchBodyContentReaderStream : MemoryStream + { + /// Listener interface to be notified of operation changes. + private IODataStreamListener listener; + + /// + /// Cached body content which needs to be processed later when we have information + /// about the content-type value. + /// + private string cachedBodyContent = null; + + /// + /// Constructor using default encoding (Base64Url without the BOM preamble). + /// + /// The batch operation listener. + internal ODataJsonLightBatchBodyContentReaderStream(IODataStreamListener listener) + { + this.listener = listener; + } + + /// + /// Enum type for data type of body content. + /// + private enum BatchPayloadBodyContentType + { + // The body content contains Json data. + Json, + + // The body content contains textual data. + Textual, + + // The body content contains binary data, e.g. JPEG image raw data. + Binary + } + + /// + /// Populates the body content the Json reader is referencing. + /// Since the content-type header might not be available at this point (when "headers" attribute + /// is read after the "body" attribute), if the content-type is not json the body content is + /// first stored into a string which will be used to populate the stream when the content-type + /// header is read later. + /// + /// The Json reader providing access to the data. + /// The request's content-type header value. + /// True if body content is written to stream; false otherwise. + internal bool PopulateBodyContent(IJsonReader jsonReader, string contentTypeHeader) + { + bool isStreamPopulated = false; + + BatchPayloadBodyContentType? contentType = DetectBatchPayloadBodyContentType(jsonReader, contentTypeHeader); + + if (contentType == null) + { + // We don't have deterministic content-type, cached the string content. + Debug.Assert(jsonReader.NodeType == JsonNodeType.PrimitiveValue, "jsonReader.NodeType == JsonNodeType.PrimitiveValue"); + this.cachedBodyContent = jsonReader.ReadStringValue(); + Debug.Assert(isStreamPopulated == false, "isStreamPopulated == false"); + } + else + { + // We have content-type figured out and should be able to populate the stream. + switch (contentType) + { + case BatchPayloadBodyContentType.Json: + { + WriteJsonContent(jsonReader); + } + + break; + + case BatchPayloadBodyContentType.Textual: + { + string bodyContent = string.Format(CultureInfo.InvariantCulture, + "\"{0}\"", jsonReader.ReadStringValue()); + WriteBytes(Encoding.UTF8.GetBytes(bodyContent)); + } + + break; + + case BatchPayloadBodyContentType.Binary: + { + // Body content is a base64url encoded string. We could have used HttpServerUtility.UrlTokenDecode(string) + // directly but it would introduce new dependency of System.Web.dll. + string encoded = jsonReader.ReadStringValue(); + WriteBinaryContent(encoded); + } + + break; + + default: + throw new NotSupportedException(string.Format( + CultureInfo.InvariantCulture, + "unknown / undefined type, new type that needs to be supported: {0}? ", + contentType)); + } + + isStreamPopulated = true; + } + + return isStreamPopulated; + } + + /// + /// Populates the stream with the cached body content according to the content-type specified. + /// + /// The content-type header value. + internal void PopulateCachedBodyContent(string contentTypeHeader) + { + Debug.Assert(this.cachedBodyContent != null); + + ODataMediaType mediaType = GetMediaType(contentTypeHeader); + + // Content-Type can be either textual or binary, since json content is not cached. + if (mediaType != null && mediaType.Type.Equals(MimeConstants.MimeTextType)) + { + // Explicit check for matching of textual content-type. + string bodyContent = string.Format(CultureInfo.InvariantCulture, + "\"{0}\"", this.cachedBodyContent); + WriteBytes(Encoding.UTF8.GetBytes(bodyContent)); + } + else + { + // Anything else, treated as binary content-type. + WriteBinaryContent(cachedBodyContent); + } + } + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called form the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.listener != null) + { + // Tell the listener that the stream is being disposed; we expect + // that no asynchronous action is triggered by doing so. + this.listener.StreamDisposed(); + this.listener = null; + } + } + + base.Dispose(disposing); + } + + /// + /// Detects batch item (request or response) body content's data type. + /// The content of the "body" property can be either Json type or binary type. + /// + /// The json reader that provides access to the json object. + /// The request's content-type header value. + /// The detected batch operation payload type. Can be null if content-type header information + /// is not yet available and json reader is reading primitive type. + private static BatchPayloadBodyContentType? DetectBatchPayloadBodyContentType( + IJsonReader jsonReader, string contentTypeHeader) + { + BatchPayloadBodyContentType? result = null; + ODataMediaType mediaType = GetMediaType(contentTypeHeader); + + if (jsonReader.NodeType == JsonNodeType.StartObject) + { + result = BatchPayloadBodyContentType.Json; + } + else if (jsonReader.NodeType == JsonNodeType.PrimitiveValue + && (mediaType != null || contentTypeHeader != null)) + { + // We have processed the content-type header and should determine batch operation request + // content type now. + if (mediaType != null && mediaType.Type.Equals(MimeConstants.MimeTextType)) + { + // Explicit check for matching of textual content-type. + result = BatchPayloadBodyContentType.Textual; + } + else + { + // Anything else, treated as binary content-type. + result = BatchPayloadBodyContentType.Binary; + } + } + + return result; + } + + /// + /// Parses the content-type header to get the media type without parameters. + /// + /// The content-type header value. + /// The media type object without parameters, or null if value is null or empty. + private static ODataMediaType GetMediaType(string contentTypeHeader) + { + if (String.IsNullOrEmpty(contentTypeHeader)) + { + return null; + } + + contentTypeHeader = contentTypeHeader.Trim(); + int idx = contentTypeHeader.IndexOf(';'); + string fullType = idx != -1 + ? contentTypeHeader.Substring(0, idx) + : contentTypeHeader; + + int idxSlash = fullType.IndexOf('/'); + string type = null; + string subType = null; + if (idxSlash != -1) + { + type = fullType.Substring(0, idxSlash); + subType = fullType.Substring(idxSlash + 1); + } + else + { + type = fullType; + } + + return new ODataMediaType(type, subType); + } + + /// + /// Reads off the data of the starting Json object from the Json reader, + /// and populate the data into the memory stream. + /// + /// The json reader pointing at the json structure whose data needs to + /// be populated into an memory stream. + /// + private void WriteJsonContent(IJsonReader reader) + { + // Reader is on the value node after the "body" property name node. + IJsonWriter jsonWriter = new JsonWriter( + new StreamWriter(this), + true /*isIeee754Compatible*/); + + WriteCurrentJsonObject(reader, jsonWriter); + this.Flush(); + this.Position = 0; + } + + /// + /// Decodes the base64url-encoded string and writes the binary bytes to the underlying memory stream. + /// + /// The base64url-encoded content. + private void WriteBinaryContent(string encodedContent) + { + byte[] bytes = Convert.FromBase64String(encodedContent.Replace('-', '+').Replace('_', '/')); + WriteBytes(bytes); + } + + /// + /// Writes the binary bytes to the underlying memory stream. + /// + /// The raw bytes to be written into the stream. + private void WriteBytes(byte[] bytes) + { + this.Write(bytes, 0, bytes.Length); + this.Flush(); + this.Position = 0; + } + + /// + /// Writes the current Json object. + /// + /// The Json reader providing the data. + /// The Json writer writes data into memory stream. + private static void WriteCurrentJsonObject(IJsonReader reader, IJsonWriter jsonWriter) + { + Stack nodeTypes = new Stack(); + + do + { + switch (reader.NodeType) + { + case JsonNodeType.PrimitiveValue: + { + if (reader.Value != null) + { + jsonWriter.WritePrimitiveValue(reader.Value); + } + else + { + jsonWriter.WriteValue((string)null); + } + } + + break; + + case JsonNodeType.Property: + { + jsonWriter.WriteName(reader.Value.ToString()); + } + + break; + + case JsonNodeType.StartObject: + { + nodeTypes.Push(reader.NodeType); + jsonWriter.StartObjectScope(); + } + + break; + + case JsonNodeType.StartArray: + { + nodeTypes.Push(reader.NodeType); + jsonWriter.StartArrayScope(); + } + + break; + + case JsonNodeType.EndObject: + { + Debug.Assert(nodeTypes.Peek() == JsonNodeType.StartObject); + nodeTypes.Pop(); + jsonWriter.EndObjectScope(); + } + + break; + + case JsonNodeType.EndArray: + { + Debug.Assert(nodeTypes.Peek() == JsonNodeType.StartArray); + nodeTypes.Pop(); + jsonWriter.EndArrayScope(); + } + + break; + + default: + { + throw new ODataException(String.Format( + CultureInfo.InvariantCulture, + "Unexpected reader.NodeType: {0}.", + reader.NodeType)); + } + } + + reader.ReadNext(); // This can be EndOfInput, where nodeTypes should be empty. + } + while (nodeTypes.Count != 0); + + jsonWriter.Flush(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs new file mode 100644 index 0000000..a1be099 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -0,0 +1,279 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using Microsoft.OData.Json; + + #endregion Namespaces + + /// + /// Class for cache properties of a json object. + /// + internal class ODataJsonLightBatchPayloadItemPropertiesCache + { + /// + /// Property name for message Id in Json batch payload's message object. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameId = "ID"; + + /// + /// Property name for message atomicityGroup association in Json batch payload's message object. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameAtomicityGroup = "ATOMICITYGROUP"; + + /// + /// Property name for response headers in Json batch response. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameHeaders = "HEADERS"; + + /// + /// Property name for message body in Json batch payload's message object. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameBody = "BODY"; + + // The followings are request specific properties. + + /// + /// Property name for request execution dependency in Json batch request. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameDependsOn = "DEPENDSON"; + + /// + /// Property name for request HTTP method in Json batch request. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameMethod = "METHOD"; + + /// + /// Property name for request URL in Json batch request. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameUrl = "URL"; + + // The followings are response specific properties. + + /// + /// Property name for response status in Json batch response. + /// Property names definitions here are all in upper case to support case insensitiveness. + /// + internal const string PropertyNameStatus = "STATUS"; + + /// + /// The Json reader for reading payload item in Json format. + /// + private IJsonReader jsonReader; + + /// + /// The Json batch reader for batch processing. + /// + private IODataStreamListener listener; + + /// + /// Cache for json properties. + /// + private Dictionary jsonProperties = null; + + /// + /// Whether the stream has been populated with body content from the operation request message. + /// + private bool isStreamPopulated = false; + + /// + /// Constructor. + /// + /// The Json batch reader. + internal ODataJsonLightBatchPayloadItemPropertiesCache(ODataJsonLightBatchReader jsonBatchReader) + { + Debug.Assert(jsonBatchReader != null, "jsonBatchReader != null"); + + this.jsonReader = jsonBatchReader.JsonLightInputContext.JsonReader; + this.listener = jsonBatchReader; + + ScanJsonProperties(); + } + + /// + /// Retrieves the value for the cached property. + /// + /// Name of the property. + /// Property value. Null if not found. + internal object GetPropertyValue(string propertyName) + { + if (this.jsonProperties != null) + { + string canonicalPropertyName = Normalize(propertyName); + object propertyValue; + if (this.jsonProperties.TryGetValue(canonicalPropertyName, out propertyValue)) + { + return propertyValue; + } + } + + return null; + } + + /// + /// Creates a batch reader stream backed by memory stream containing data the current + /// Json object the reader is pointing at. + /// Current supported data types are Json and binary types. + /// + /// The content-type header value of the request. + /// The memory stream. + private ODataJsonLightBatchBodyContentReaderStream CreateJsonPayloadBodyContentStream(string contentTypeHeader) + { + // Serialization of json object to batch buffer. + ODataJsonLightBatchBodyContentReaderStream stream = + new ODataJsonLightBatchBodyContentReaderStream(listener); + + this.isStreamPopulated = stream.PopulateBodyContent(this.jsonReader, contentTypeHeader); + + return stream; + } + + /// + /// Normalization method for property name. Upper case conversion is used. + /// + /// Name to be normalized. + /// The normalized name. + private static string Normalize(string propertyName) + { + return propertyName.ToUpperInvariant(); + } + + /// + /// Wrapper method with validation to scan the Json object for known properties. + /// + private void ScanJsonProperties() + { + Debug.Assert(this.jsonReader != null, "this.jsonReaders != null"); + Debug.Assert(this.jsonProperties == null, "this.jsonProperties == null"); + + this.jsonProperties = new Dictionary(); + string contentTypeHeader = null; + ODataJsonLightBatchBodyContentReaderStream bodyContentStream = null; + + try + { + // Request object start. + this.jsonReader.ReadStartObject(); + + while (this.jsonReader.NodeType != JsonNodeType.EndObject) + { + // Convert to upper case to support case-insensitive request property names + string propertyName = Normalize(this.jsonReader.ReadPropertyName()); + + switch (propertyName) + { + case PropertyNameId: + case PropertyNameAtomicityGroup: + case PropertyNameMethod: + case PropertyNameUrl: + { + jsonProperties.Add(propertyName, this.jsonReader.ReadStringValue()); + } + + break; + + case PropertyNameStatus: + { + jsonProperties.Add(propertyName, this.jsonReader.ReadPrimitiveValue()); + } + + break; + + case PropertyNameDependsOn: + { + IList dependsOnIds = new List(); + this.jsonReader.ReadStartArray(); + while (this.jsonReader.NodeType != JsonNodeType.EndArray) + { + dependsOnIds.Add(this.jsonReader.ReadStringValue()); + } + + this.jsonReader.ReadEndArray(); + + jsonProperties.Add(propertyName, dependsOnIds); + } + + break; + + case PropertyNameHeaders: + { + ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); + + // Use empty string (non-null value) to indicate that content-type header has been processed. + contentTypeHeader = ""; + + this.jsonReader.ReadStartObject(); + + while (this.jsonReader.NodeType != JsonNodeType.EndObject) + { + string headerName = this.jsonReader.ReadPropertyName(); + string headerValue = this.jsonReader.ReadPrimitiveValue().ToString(); + + // Remember the Content-Type header value. + if (headerName.Equals(ODataConstants.ContentTypeHeader, StringComparison.CurrentCultureIgnoreCase)) + { + contentTypeHeader = headerValue; + } + + headers.Add(headerName, headerValue); + } + + this.jsonReader.ReadEndObject(); + + jsonProperties.Add(propertyName, headers); + + if (!this.isStreamPopulated && bodyContentStream != null) + { + // Populate the stream now since the body content has been cached and we now have content-type. + bodyContentStream.PopulateCachedBodyContent(contentTypeHeader); + } + } + + break; + + case PropertyNameBody: + { + bodyContentStream = CreateJsonPayloadBodyContentStream(contentTypeHeader); + jsonProperties.Add(propertyName, bodyContentStream); + } + + break; + + default: + { + throw new ODataException(string.Format( + CultureInfo.InvariantCulture, + "Unknown property name '{0}' for message in batch", + propertyName)); + } + } + } + + // Request object end. + this.jsonReader.ReadEndObject(); + } + finally + { + // We don't need to use the Json reader anymore. + this.jsonReader = null; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReader.cs new file mode 100644 index 0000000..c96595f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReader.cs @@ -0,0 +1,508 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; + + using Microsoft.OData.Json; + + #endregion Namespaces + + /// + /// Class for reading OData batch messages in json format. + /// Also verifies the proper sequence of read calls on the reader. + /// + internal sealed class ODataJsonLightBatchReader : ODataBatchReader + { + /// The batch stream used by the batch reader to divide a batch payload into parts. + private readonly ODataJsonLightBatchReaderStream batchStream; + + /// + /// The cache to keep track of atomicity group information during json batch message reading. + /// + private readonly ODataJsonLightBatchAtomicGroupCache atomicGroups + = new ODataJsonLightBatchAtomicGroupCache(); + + /// + /// Top-level attribute name for request arrays in Json batch format. + /// + private static string PropertyNameRequests = "requests"; + + /// + /// Top-level attribute name for response arrays in Json batch format. + /// + private static string PropertyNameResponses = "responses"; + + /// + /// The reader's mode. + /// + private ReaderMode mode = ReaderMode.NotDetected; + + /// + /// The cache for json property-value pairs of the current request or response message. + /// + private ODataJsonLightBatchPayloadItemPropertiesCache messagePropertiesCache = null; + + /// + /// Constructor. + /// + /// The input context to read the content from. + /// true if the reader is created for synchronous operation; false for asynchronous. + internal ODataJsonLightBatchReader(ODataJsonLightInputContext inputContext, bool synchronous) + : base(inputContext, synchronous) + { + this.batchStream = new ODataJsonLightBatchReaderStream(inputContext); + } + + /// + /// Definition of modes for Json reader. + /// + private enum ReaderMode + { + // Initial mode, not operational in this mode. + NotDetected, + + // Reading batch requests. + Requests, + + // Reading batch responses. + Responses + } + + /// + /// Gets the reader's input context as real runtime type. + /// + internal ODataJsonLightInputContext JsonLightInputContext + { + get + { + return this.InputContext as ODataJsonLightInputContext; + } + } + + /// + /// Gets the atomic group id for the current request. + /// + /// The group id for the current request. Null if current request is not in an atomic group. + protected override string GetCurrentGroupIdImplementation() + { + string result = null; + if (this.messagePropertiesCache != null) + { + result = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); + } + + return result; + } + + /// + /// Returns the cached for reading the content of an operation + /// in a batch request. + /// + /// The message that can be used to read the content of the batch request operation from. + protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation() + { + Debug.Assert(this.mode == ReaderMode.Requests, "this.mode == ReaderMode.Requests"); + Debug.Assert(this.messagePropertiesCache != null, "this.messagePropertiesCache != null"); + + // id + string id = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); + + // atomicityGroup + string atomicityGroupId = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); + + // dependsOn + // Flatten the dependsOn list by converting every groupId into request Ids, so that the caller + // can decide, at the earliest opportunity, whether the depending request can be invoked. + // Note that the forward reference of dependsOn id is not allowed, so the atomicGroups should have accurate + // information of atomicGroup that needs to be flattened. + IList dependsOnReqIds = null; + List dependsOn = (List)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameDependsOn); + if (dependsOn != null && dependsOn.Count != 0) + { + ValidateDependsOnId(dependsOn, atomicityGroupId, id); + dependsOnReqIds = atomicGroups.GetFlattenedMessageIds(dependsOn); + } + + // header + ODataBatchOperationHeaders headers = + (ODataBatchOperationHeaders)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameHeaders); + + // Add the atomicityGroup request header. + if (atomicityGroupId != null) + { + headers.Add(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup, atomicityGroupId); + } + + // body. Use empty stream when request body is not present. + Stream bodyContentStream = + (Stream)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameBody) + ?? new ODataJsonLightBatchBodyContentReaderStream(this); + + // method. Support case-insensitive value of HTTP methods. + string httpMethod = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameMethod); + + ValidateRequiredProperty(httpMethod, ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameMethod); + + httpMethod = httpMethod.ToUpperInvariant(); + + // url + string url = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameUrl); + ValidateRequiredProperty(url, ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameUrl); + + // escape any colons in the query string portion of the url + int queryOptionSeparator = url.IndexOf('?'); + int firstColon = url.IndexOf(':'); + if (queryOptionSeparator > 0 && firstColon > 0 && queryOptionSeparator < firstColon) + { + url = url.Substring(0, queryOptionSeparator) + url.Substring(queryOptionSeparator).Replace(":", "%3A"); + } + + Uri requestUri = new Uri(url, UriKind.RelativeOrAbsolute); + + // Reset the request property cache since all data in cache has been processed. + // So that new instance can be created during subsequent read in operation state. + this.messagePropertiesCache = null; + + ODataBatchOperationRequestMessage requestMessage = BuildOperationRequestMessage( + () => bodyContentStream, + httpMethod, + requestUri, + headers, + id, + atomicityGroupId, + dependsOnReqIds, + /*dependsOnIdsValidationRequired*/true); + + return requestMessage; + } + + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// The batch reader state after the read. + protected override ODataBatchReaderState ReadAtStartImplementation() + { + Debug.Assert(this.State == ODataBatchReaderState.Initial, "this.State == ODataBatchReaderState.Initial"); + if (mode == ReaderMode.NotDetected) + { + // The stream should be positioned at the beginning of the batch envelope. + // Need to detect whether we are reading request or response. Stay in Initial state upon return. + DetectReaderMode(); + return ODataBatchReaderState.Initial; + } + else + { + // The stream should be positioned at the beginning of requests array. + this.StartReadingBatchArray(); + + Debug.Assert(this.messagePropertiesCache == null, "this.messagePropertiesCache == null"); + this.messagePropertiesCache = + new ODataJsonLightBatchPayloadItemPropertiesCache(this); + + string currentGroup = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); + + if (currentGroup == null) + { + return ODataBatchReaderState.Operation; + } + else + { + HandleNewAtomicGroupStart( + (string) + this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId), + currentGroup); + return ODataBatchReaderState.ChangesetStart; + } + } + } + + protected override ODataBatchReaderState ReadAtChangesetStartImplementation() + { + Debug.Assert(this.messagePropertiesCache != null, + "request properties cache must have been set by now."); + return ODataBatchReaderState.Operation; + } + + /// + /// Implementation of the reader logic when in state 'ChangesetEnd'. + /// + /// The batch reader state after the read. + protected override ODataBatchReaderState ReadAtChangesetEndImplementation() + { + if (messagePropertiesCache == null + && this.JsonLightInputContext.JsonReader.NodeType != JsonNodeType.StartObject) + { + // The changeset is the last item of the batch. + return ODataBatchReaderState.Completed; + } + else + { + Debug.Assert(this.messagePropertiesCache != null); + return DetectChangesetStates(this.messagePropertiesCache); + } + } + + /// + /// Implementation of the reader logic when in state 'Operation'. + /// + /// The batch reader state after the read. + protected override ODataBatchReaderState ReadAtOperationImplementation() + { + if (this.JsonLightInputContext.JsonReader.NodeType != JsonNodeType.StartObject) + { + // No more requests in the batch. + return HandleMessagesEnd(); + } + + // Load the message properties if there is no cached item for processing. + if (this.messagePropertiesCache == null) + { + // Load the message details since operation is detected. + this.messagePropertiesCache = + new ODataJsonLightBatchPayloadItemPropertiesCache(this); + } + + // Calculate and return next state with changeset state detection. + return DetectChangesetStates(this.messagePropertiesCache); + } + + /// + /// Returns the cached for reading the content of a + /// batch response. + /// + /// The message that can be used to read the content of the batch response from. + protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation() + { + Debug.Assert(this.mode == ReaderMode.Responses, "this.mode == ReaderMode.Responses"); + Debug.Assert(this.messagePropertiesCache != null, "this.responsePropertiesCache != null"); + + // body. Use empty stream when request body is not present. + Stream bodyContentStream = + (Stream)this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameBody) + ?? new ODataJsonLightBatchBodyContentReaderStream(this); + + int statusCode = (int) + this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameStatus); + + string contentId = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); + + string groupId = (string)this.messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); + + ODataBatchOperationHeaders headers = (ODataBatchOperationHeaders) + this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameHeaders); + + // Reset the response property cache since all data in cache has been processed. + // So that new instance can be created during subsequent read in operation state. + this.messagePropertiesCache = null; + + // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs + // so use the URL resolver from the batch message instead. + ODataBatchOperationResponseMessage responseMessage = BuildOperationResponseMessage( + () => bodyContentStream, + statusCode, + headers, + contentId, + groupId); + + //// NOTE: Content-IDs for cross referencing are only supported in request messages; in responses + //// we allow a Content-ID header but don't process it (i.e., don't add the content ID to the URL resolver). + return responseMessage; + } + + /// + /// Validate that the property value is not null. + /// + /// Value of the property. + /// Name of the property. + private static void ValidateRequiredProperty(string propertyValue, string propertyName) + { + if (propertyValue == null) + { + throw new ODataException(Strings.ODataBatchReader_RequestPropertyMissing(propertyName)); + } + } + + /// + /// Validate the dependsOn Ids contains the proper values. + /// + /// Enumeration of dependsOn ids from the request property. + /// The atomicityGroup id of the request. Its value cannot be part of the dependsOnIds. + /// The id of the request. This value cannot be part of the dependsOnIds. + private void ValidateDependsOnId(IEnumerable dependsOnIds, string atomicityGroupId, string requestId) + { + foreach (string dependsOnId in dependsOnIds) + { + Debug.Assert(dependsOnId != null, "dependsOnId != null"); + + // Self reference to atomicityGroup is not allowed. + if (dependsOnId.Equals(atomicityGroupId)) + { + throw new ODataException(Strings.ODataBatchReader_SameRequestIdAsAtomicityGroupIdNotAllowed( + dependsOnId, + atomicityGroupId)); + } + + // Self reference is not allowed. + if (dependsOnId.Equals(requestId)) + { + throw new ODataException(Strings.ODataBatchReader_SelfReferenceDependsOnRequestIdNotAllowed( + dependsOnId, + requestId)); + } + + // For request Id referred to by dependsOn attribute, check that it is not part of any atomic group + // other than the dependent request's atomic group (if dependent request belongs to an atomic group). + string groupId = this.atomicGroups.GetGroupId(dependsOnId); + if (groupId != null && !groupId.Equals(this.atomicGroups.GetGroupId(requestId))) + { + throw new ODataException(Strings.ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed( + dependsOnId, + groupId)); + } + } + } + + /// + /// Verify the first Json property of the batch payload to detect the reader's mode. + /// + private void DetectReaderMode() + { + this.batchStream.JsonReader.ReadNext(); + this.batchStream.JsonReader.ReadStartObject(); + + string propertyName = this.batchStream.JsonReader.ReadPropertyName(); + if (PropertyNameRequests.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + { + this.mode = ReaderMode.Requests; + } + else if (PropertyNameResponses.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + { + this.mode = ReaderMode.Responses; + } + else + { + throw new ODataException(Strings.ODataBatchReader_JsonBatchTopLevelPropertyMissing); + } + } + + /// + /// Verify the json array of the batch payload. + /// + /// The batch reader's Operation state. + private ODataBatchReaderState StartReadingBatchArray() + { + this.batchStream.JsonReader.ReadStartArray(); + + ODataBatchReaderState nextState = ODataBatchReaderState.Operation; + return nextState; + } + + /// + /// Process atomic group start. + /// + /// Id of the first message (request or response) in the group. + /// Group Id for the new atomic group. + private void HandleNewAtomicGroupStart(string messageId, string groupId) + { + if (this.atomicGroups.IsGroupId(groupId)) + { + throw new ODataException(Strings.ODataBatchReader_DuplicateAtomicityGroupIDsNotAllowed(groupId)); + } + + // Add the request Id to the new group. + this.atomicGroups.AddMessageIdAndGroupId(messageId, groupId); + } + + /// + /// Setup the reader's states at the end of the messages. + /// If atomicGroup is under processing, it needs to be closed first. + /// + /// The reader's next state. + private ODataBatchReaderState HandleMessagesEnd() + { + ODataBatchReaderState nextReaderState; + + if (this.atomicGroups.IsWithinAtomicGroup) + { + // We need to close pending changeset and update the atomic group status first. + this.atomicGroups.IsWithinAtomicGroup = false; + nextReaderState = ODataBatchReaderState.ChangesetEnd; + } + else + { + // Set the completion state. + this.JsonLightInputContext.JsonReader.ReadEndArray(); + this.JsonLightInputContext.JsonReader.ReadEndObject(); + nextReaderState = ODataBatchReaderState.Completed; + } + + return nextReaderState; + } + + /// + /// Examine changeset states for the current message and setup reader state accordingly if + /// changeset related state transition is detected. + /// + /// Current message properties. + /// The next state for the reader. + private ODataBatchReaderState DetectChangesetStates(ODataJsonLightBatchPayloadItemPropertiesCache messagePropertiesCache) + { + // Validate message Id. + string valueId = (string)messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); + + string currentGroup = (string)messagePropertiesCache.GetPropertyValue( + ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); + + // ChangesetEnd check first; If not, check for changesetStart. + bool changesetEnd = this.atomicGroups.IsChangesetEnd(currentGroup); + bool changesetStart = false; + + if (!changesetEnd) + { + if (currentGroup != null) + { + // Add message Id to atomic group (create new group if needed). + // Also detect changeset start. + changesetStart = this.atomicGroups.AddMessageIdAndGroupId(valueId, currentGroup); + } + } + + // If we have changeset state change detected, set the state here. + ODataBatchReaderState nextState = ODataBatchReaderState.Operation; + if (changesetEnd) + { + nextState = ODataBatchReaderState.ChangesetEnd; + } + else if (changesetStart) + { + nextState = ODataBatchReaderState.ChangesetStart; + } + + return nextState; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReaderStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReaderStream.cs new file mode 100644 index 0000000..c04e0be --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchReaderStream.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Text; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Class used by the to read the various pieces of a batch payload + /// in application/json format. + /// + internal sealed class ODataJsonLightBatchReaderStream : ODataBatchReaderStream + { + /// + /// The input context used by the JsonLight reader. + /// + private readonly ODataJsonLightInputContext inputContext; + + /// + /// Constructor. + /// + /// The JsonLight input context. + internal ODataJsonLightBatchReaderStream(ODataJsonLightInputContext inputContext) + { + Debug.Assert(inputContext != null, "inputContext != null"); + this.inputContext = inputContext; + } + + /// + /// The reader providing access to payload in Json format. + /// + internal BufferingJsonReader JsonReader + { + get + { + return this.inputContext.JsonReader; + } + } + + /// + /// This method is not applicable for application/json format, and throws an exception. + /// + /// The byte array to read bytes into. + /// The offset in the buffer where to start reading bytes into. + /// The number of bytes to read. + /// The number of bytes actually read. + internal override int ReadWithDelimiter(byte[] userBuffer, int userBufferOffset, int count) + { + throw new NotImplementedException(); + } + + /// + /// Reads from the batch stream without checking for a boundary delimiter since we + /// know the length of the stream. + /// + /// The byte array to read bytes into. + /// The offset in the buffer where to start reading bytes into. + /// The number of bytes to read. + /// The number of bytes actually read. + internal override int ReadWithLength(byte[] userBuffer, int userBufferOffset, int count) + { + Debug.Assert(userBuffer != null, "userBuffer != null"); + Debug.Assert(userBufferOffset >= 0, "userBufferOffset >= 0"); + Debug.Assert(count >= 0, "count >= 0"); + + //// NOTE: if we have a stream with length we don't even check for boundaries but rely solely on the content length + int remainingNumberOfBytesToRead = count; + while (remainingNumberOfBytesToRead > 0) + { + // check whether we can satisfy the full read request from the buffer + // or whether we have to split the request and read more data into the buffer. + if (this.BatchBuffer.NumberOfBytesInBuffer >= remainingNumberOfBytesToRead) + { + // we can satisfy the full read request from the buffer + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, remainingNumberOfBytesToRead); + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + remainingNumberOfBytesToRead); + remainingNumberOfBytesToRead = 0; + } + else + { + // we can only partially satisfy the read request + int availableBytesToRead = this.BatchBuffer.NumberOfBytesInBuffer; + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, availableBytesToRead); + remainingNumberOfBytesToRead -= availableBytesToRead; + userBufferOffset += availableBytesToRead; + + // we exhausted the buffer; if the underlying stream is not exhausted, refill the buffer + if (this.underlyingStreamExhausted) + { + // We cannot fully satisfy the read request since there are not enough bytes in the stream. + // This means that the content length of the stream was incorrect; this should never happen + // since the caller should already have checked this. + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStreamBuffer_ReadWithLength)); + } + else + { + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.inputContext.Stream, /*preserveFrom*/ ODataBatchReaderStreamBuffer.BufferLength); + } + } + } + + // return the number of bytes that were read + return count - remainingNumberOfBytesToRead; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs new file mode 100644 index 0000000..a0528fd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs @@ -0,0 +1,651 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Class for writing OData batch messages of MIME application/json type. + /// + internal sealed class ODataJsonLightBatchWriter : ODataBatchWriter + { + #region JsonPropertyNames + /// + /// Camel-case property name for request Id in Json batch. + /// + private const string PropertyId = "id"; + + /// + /// Property name for request atomic group association in Json batch. + /// + private const string PropertyAtomicityGroup = "atomicityGroup"; + + /// + /// Property name for request HTTP headers in Json batch. + /// + private const string PropertyHeaders = "headers"; + + /// + /// Property name for request body in Json batch. + /// + private const string PropertyBody = "body"; + #endregion JsonPropertyNames + + #region RequestJsonPropertyNames + /// + /// Property name for top-level requests array in Json batch request. + /// + private const string PropertyRequests = "requests"; + + /// + /// Property name for preceding request Ids in Json batch request. + /// + private const string PropertyDependsOn = "dependsOn"; + + /// + /// Property name for request HTTP method in Json batch request. + /// + private const string PropertyMethod = "method"; + + /// + /// Property name for request URL in Json batch request. + /// + private const string PropertyUrl = "url"; + #endregion RequestJsonPropertyNames + + #region ResponseJsonPropertyNames + /// + /// Property name for top-level responses array in Json batch response. + /// + private const string PropertyResponses = "responses"; + + /// + /// Property name for response status in Json batch response. + /// + private const string PropertyStatus = "status"; + #endregion ResponseJsonPropertyNames + + /// + /// The underlying JSON writer. + /// + private readonly IJsonWriter jsonWriter; + + /// + /// The auto-generated GUID for AtomicityGroup of the Json item. Should be null for Json item + /// that doesn't belong to atomic group. + /// + private string atomicityGroupId = null; + + /// + /// Dictionary for keeping track of each request's associated atomic group id, which is null + /// for request that does not belong to atomic group. + /// + private Dictionary requestIdToAtomicGroupId = new Dictionary(); + + /// + /// Dictionary for keeping track of each atomic group's member request id. This is optimization + /// for reversed lookup. + /// + private Dictionary> atomicityGroupIdToRequestId = new Dictionary>(); + + /// + /// Constructor. + /// + /// The output context to write to. + internal ODataJsonLightBatchWriter(ODataJsonLightOutputContext jsonLightOutputContext) + : base(jsonLightOutputContext) + { + this.jsonWriter = this.JsonLightOutputContext.JsonWriter; + } + + /// + /// Gets the writer's output context as the real runtime type. + /// + private ODataJsonLightOutputContext JsonLightOutputContext + { + get { return this.OutputContext as ODataJsonLightOutputContext; } + } + + /// + /// The message for the operation that is currently written; or null if no operation is written right now. + /// + private ODataBatchOperationMessage CurrentOperationMessage + { + get + { + Debug.Assert(this.CurrentOperationRequestMessage == null || this.CurrentOperationResponseMessage == null, + "Only request or response message can be set, not both."); + if (this.CurrentOperationRequestMessage != null) + { + Debug.Assert(!this.JsonLightOutputContext.WritingResponse, "Request message can only be set when writing request."); + return this.CurrentOperationRequestMessage.OperationMessage; + } + else if (this.CurrentOperationResponseMessage != null) + { + Debug.Assert(this.JsonLightOutputContext.WritingResponse, "Response message can only be set when writing response."); + return this.CurrentOperationResponseMessage.OperationMessage; + } + else + { + return null; + } + } + } + + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + public override void StreamRequested() + { + // Write any pending data and flush the batch writer to the async buffered stream + this.StartBatchOperationContent(); + + // Flush the async buffered stream to the underlying message stream (if there's any) + this.JsonLightOutputContext.FlushBuffers(); + + // Set the corresponding state but we need to keep the Json batch writer around. + this.SetState(BatchWriterState.OperationStreamRequested); + } + +#if PORTABLELIB + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + /// + /// A task representing any action that is running as part of the status change of the operation; + /// null if no such action exists. + /// + public override Task StreamRequestedAsync() + { + // Write any pending data and flush the batch writer to the async buffered stream + this.StartBatchOperationContent(); + + // Asynchronously flush the async buffered stream to the underlying message stream (if there's any); + // then dispose the batch writer (since we are now writing the operation content) and set the corresponding state. + return this.JsonLightOutputContext.FlushBuffersAsync() + .FollowOnSuccessWith(task => this.SetState(BatchWriterState.OperationStreamRequested)); + } +#endif + + /// + /// This method is called to notify that the content stream of a batch operation has been disposed. + /// + public override void StreamDisposed() + { + Debug.Assert(this.CurrentOperationMessage != null, "Expected non-null operation message!"); + + this.SetState(BatchWriterState.OperationStreamDisposed); + this.CurrentOperationRequestMessage = null; + this.CurrentOperationResponseMessage = null; + + EnsurePreceedingMessageIsClosed(); + } + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + public override void OnInStreamError() + { + this.JsonLightOutputContext.VerifyNotDisposed(); + this.SetState(BatchWriterState.Error); + this.JsonLightOutputContext.JsonWriter.Flush(); + + // The OData protocol spec did not defined the behavior when an exception is encountered outside of a batch operation. The batch writer + // should not allow WriteError in this case. Note that WCF DS Server does serialize the error in XML format when it encounters one outside of a + // batch operation. + throw new ODataException(Strings.ODataBatchWriter_CannotWriteInStreamErrorForBatch); + } + + /// + /// Flush the output. + /// + protected override void FlushSynchronously() + { + this.JsonLightOutputContext.Flush(); + } + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected override Task FlushAsynchronously() + { + return this.JsonLightOutputContext.FlushAsync(); + } +#endif + + /// + /// Starts a new batch - implementation of the actual functionality. + /// + protected override void WriteStartBatchImplementation() + { + WriteBatchEnvelope(); + this.SetState(BatchWriterState.BatchStarted); + } + + /// + /// Given an enumerable of dependsOn ids containing request ids and group ids, return an enumeration of + /// equivalent request ids by converting the group ids into associated request ids. + /// + /// The dependsOn ids specifying current request's prerequisites. + /// An enumerable consists of request ids. + protected override IEnumerable GetDependsOnRequestIds(IEnumerable dependsOnIds) + { + List dependsOnRequestIds = new List(); + foreach (string id in dependsOnIds) + { + if (this.atomicityGroupIdToRequestId.ContainsKey(id)) + { + dependsOnRequestIds.AddRange(this.atomicityGroupIdToRequestId[id]); + } + else + { + dependsOnRequestIds.Add(id); + } + } + + return dependsOnRequestIds; + } + + /// + /// Ends a batch - implementation of the actual functionality. + /// + protected override void WriteEndBatchImplementation() + { + // write pending message data (headers, response line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + this.SetState(BatchWriterState.BatchCompleted); + + // Close the messages array + jsonWriter.EndArrayScope(); + + // Close the top level scope + jsonWriter.EndObjectScope(); + } + + /// + /// Starts a new changeset - implementation of the actual functionality. + /// + /// + /// The atomic group id of the changeset to start. + /// If it is null for Json batch, an GUID will be generated and used as the atomic group id. + protected override void WriteStartChangesetImplementation(string groupId) + { + Debug.Assert(groupId != null, "groupId != null"); + + // write pending message data (headers, response line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + // important to do this first since it will set up the change set boundary. + this.SetState(BatchWriterState.ChangesetStarted); + + this.atomicityGroupId = groupId; + } + + /// + /// Ends an active changeset - implementation of the actual functionality. + /// + protected override void WriteEndChangesetImplementation() + { + // write pending message data (headers, response line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + // change the state first so we validate the change set boundary before attempting to write it. + this.SetState(BatchWriterState.ChangesetCompleted); + this.atomicityGroupId = null; + } + + /// + /// Creates an for writing an operation of a + /// batch request - implementation of the actual functionality. + /// + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The Content-ID value to write in ChangeSet head. + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// The prerequisite request ids of this request. + /// The message that can be used to write the request operation. + protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation(string method, + Uri uri, string contentId, BatchPayloadUriOption payloadUriOption, IEnumerable dependsOnIds) + { + // write pending message data (headers, request line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + // For json batch request, content Id is required for single request or request within atomicityGroup. + if (contentId == null) + { + contentId = Guid.NewGuid().ToString(); + } + + AddGroupIdLookup(contentId); + + // Create the new request operation with a non-null dependsOnIds. + this.CurrentOperationRequestMessage = BuildOperationRequestMessage( + this.JsonLightOutputContext.GetOutputStream(), method, uri, contentId, + this.atomicityGroupId, + dependsOnIds ?? Enumerable.Empty()); + + this.SetState(BatchWriterState.OperationCreated); + + // write the operation's start boundary string + this.WriteStartBoundaryForOperation(); + + this.jsonWriter.WriteName(PropertyId); + this.jsonWriter.WriteValue(contentId); + + if (this.atomicityGroupId != null) + { + this.jsonWriter.WriteName(PropertyAtomicityGroup); + this.jsonWriter.WriteValue(this.atomicityGroupId); + } + + if (this.CurrentOperationRequestMessage.DependsOnIds != null + && this.CurrentOperationRequestMessage.DependsOnIds.Any()) + { + this.jsonWriter.WriteName(PropertyDependsOn); + this.jsonWriter.StartArrayScope(); + + foreach (string dependsOnId in this.CurrentOperationRequestMessage.DependsOnIds) + { + ValidateDependsOnId(contentId, dependsOnId); + this.jsonWriter.WriteValue(dependsOnId); + } + + this.jsonWriter.EndArrayScope(); + } + + this.jsonWriter.WriteName(PropertyMethod); + this.jsonWriter.WriteValue(method); + + this.jsonWriter.WriteName(PropertyUrl); + this.jsonWriter.WriteValue(UriUtils.UriToString(uri)); + + return this.CurrentOperationRequestMessage; + } + + /// + /// Creates an for writing an operation of a batch + /// response - implementation of the actual functionality. + /// + /// The Content-ID value to write for the response id. + /// The message that can be used to rite the response operation. + protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation(string contentId) + { + this.WritePendingMessageData(true); + + // Url resolver: In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs + // so use the URL resolver from the batch message instead. + // + // ContentId: could be null from public API common for both formats, so we don't enforce non-null value for Json format + // for sake of backward compatibility. For Json Batch response message, normally caller should use the same value + // from the request. + this.CurrentOperationResponseMessage = BuildOperationResponseMessage( + this.JsonLightOutputContext.GetOutputStream(), + contentId, this.atomicityGroupId); + this.SetState(BatchWriterState.OperationCreated); + + // Start the Json object for the response + this.WriteStartBoundaryForOperation(); + + return this.CurrentOperationResponseMessage; + } + + /// + /// Verifies that the writer is not disposed. + /// + protected override void VerifyNotDisposed() + { + this.JsonLightOutputContext.VerifyNotDisposed(); + } + + /// + /// Validates the dependsOnId. It needs to be a valid id, and cannot be inside another atomic group. + /// + /// Current request's id. + /// Prerequisite request id or atomic group id that current request depends on. + private void ValidateDependsOnId(string requestId, string dependsOnId) + { + // Validate the dependsOn id and ensure it is not part of atomic group. + if (this.atomicityGroupIdToRequestId.ContainsKey(dependsOnId)) + { + // The dependsOn id is a group id, throw if depondsOn id is the current group. + string currentGroupId; + this.requestIdToAtomicGroupId.TryGetValue(requestId, out currentGroupId); + + if (dependsOnId.Equals(currentGroupId)) + { + throw new ODataException( + Strings.ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed(requestId, dependsOnId)); + } + } + else + { + // The dependsOn id is a request Id. + // Throw if it doesn't exist, or if it belongs to a different group. + string groupId = null; + + if (!this.requestIdToAtomicGroupId.TryGetValue(dependsOnId, out groupId)) + { + throw new ODataException(Strings.ODataBatchReader_DependsOnIdNotFound(dependsOnId, requestId)); + } + else if (groupId != null) + { + // Throw if these two ids are not belonged to the same group. + string currentGroupId; + this.requestIdToAtomicGroupId.TryGetValue(requestId, out currentGroupId); + + if (!groupId.Equals(currentGroupId)) + { + throw new ODataException( + Strings.ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed(requestId, groupId)); + } + } + } + } + + /// + /// Adds group id lookup and reverse lookup. + /// + /// Add content Id to group Id lookup and reverse lookup. + private void AddGroupIdLookup(string contentId) + { + // For request that is not part of an atomic group, the corresponding atomic group id is null. + try + { + this.requestIdToAtomicGroupId.Add(contentId, this.atomicityGroupId); + } + catch (ArgumentException ae) + { + // The Dictionary class will throw an exception if the key + // already exists. Convert and throw ODataException. + throw new ODataException(Strings.ODataBatchWriter_DuplicateContentIDsNotAllowed(contentId), ae); + } + + // Add reverse lookup when current request is part of atomic group. + if (this.atomicityGroupId != null) + { + if (!this.atomicityGroupIdToRequestId.ContainsKey(this.atomicityGroupId)) + { + this.atomicityGroupIdToRequestId.Add(this.atomicityGroupId, new List()); + } + + this.atomicityGroupIdToRequestId[this.atomicityGroupId].Add(contentId); + } + } + + /// + /// Writes the start boundary for an operation. This is Json start object. + /// + private void WriteStartBoundaryForOperation() + { + // Start the individual message object + this.jsonWriter.StartObjectScope(); + } + + /// + /// Writes all the pending headers and prepares the writer to write a content of the operation. + /// + private void StartBatchOperationContent() + { + Debug.Assert(this.CurrentOperationMessage != null, "Expected non-null operation message!"); + Debug.Assert(this.JsonLightOutputContext.JsonWriter != null, "Must have a Json writer!"); + + // write the pending headers (if any) + this.WritePendingMessageData(false); + + this.jsonWriter.WriteRawValue(string.Format(CultureInfo.InvariantCulture, + "{0} \"{1}\" {2}", + JsonConstants.ArrayElementSeparator, + PropertyBody, + JsonConstants.NameValueSeparator)); + + // flush the text writer to make sure all buffers of the text writer + // are flushed to the underlying async stream + this.JsonLightOutputContext.JsonWriter.Flush(); + } + + /// + /// Writes any pending data for the current operation message (if any). + /// + /// + /// A flag to control whether after writing the pending data we report writing the message to be completed or not. + /// + private void WritePendingMessageData(bool reportMessageCompleted) + { + if (this.CurrentOperationMessage != null) + { + Debug.Assert(this.JsonLightOutputContext.JsonWriter != null, + "Must have a batch writer if pending data exists."); + + if (this.CurrentOperationRequestMessage != null) + { + WritePendingRequestMessageData(); + } + else + { + WritePendingResponseMessageData(); + } + + if (reportMessageCompleted) + { + this.CurrentOperationMessage.PartHeaderProcessingCompleted(); + this.CurrentOperationRequestMessage = null; + this.CurrentOperationResponseMessage = null; + + EnsurePreceedingMessageIsClosed(); + } + } + } + + /// + /// Closes preceding message Json object if any. + /// + private void EnsurePreceedingMessageIsClosed() + { + // There shouldn't be any pending message object. + Debug.Assert(this.CurrentOperationMessage == null, "this.CurrentOperationMessage == null"); + this.jsonWriter.EndObjectScope(); + } + + /// + /// Writes the json format batch envelope. + /// Always sets the isBatchEvelopeWritten flag to true before return. + /// + private void WriteBatchEnvelope() + { + // Start the top level scope + this.jsonWriter.StartObjectScope(); + + // Start the requests / responses property + this.jsonWriter.WriteName(this.JsonLightOutputContext.WritingResponse ? PropertyResponses : PropertyRequests); + this.jsonWriter.StartArrayScope(); + } + + /// + /// Writing pending data for the current request message. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "need to use lower characters for header key")] + private void WritePendingRequestMessageData() + { + Debug.Assert(this.CurrentOperationRequestMessage != null, "this.CurrentOperationRequestMessage != null"); + + // headers property. + this.jsonWriter.WriteName(PropertyHeaders); + this.jsonWriter.StartObjectScope(); + IEnumerable> headers = this.CurrentOperationRequestMessage.Headers; + if (headers != null) + { + foreach (KeyValuePair headerPair in headers) + { + this.jsonWriter.WriteName(headerPair.Key.ToLowerInvariant()); + this.jsonWriter.WriteValue(headerPair.Value); + } + } + + this.jsonWriter.EndObjectScope(); + } + + /// + /// Writing pending data for the current response message. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "need to use lower characters for header key")] + private void WritePendingResponseMessageData() + { + Debug.Assert(this.JsonLightOutputContext.WritingResponse, "If the response message is available we must be writing response."); + Debug.Assert(this.CurrentOperationResponseMessage != null, "this.CurrentOperationResponseMessage != null"); + + // id property. + this.jsonWriter.WriteName(PropertyId); + this.jsonWriter.WriteValue(this.CurrentOperationResponseMessage.ContentId); + + // atomicityGroup property. + if (this.atomicityGroupId != null) + { + this.jsonWriter.WriteName(PropertyAtomicityGroup); + this.jsonWriter.WriteValue(this.atomicityGroupId); + } + + // response status property. + this.jsonWriter.WriteName(PropertyStatus); + this.jsonWriter.WriteValue(this.CurrentOperationResponseMessage.StatusCode); + + // headers property. + this.jsonWriter.WriteName(PropertyHeaders); + this.jsonWriter.StartObjectScope(); + IEnumerable> headers = this.CurrentOperationMessage.Headers; + if (headers != null) + { + foreach (KeyValuePair headerPair in headers) + { + this.jsonWriter.WriteName(headerPair.Key.ToLowerInvariant()); + this.jsonWriter.WriteValue(headerPair.Value); + } + } + + this.jsonWriter.EndObjectScope(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionDeserializer.cs new file mode 100644 index 0000000..c263f55 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionDeserializer.cs @@ -0,0 +1,292 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// OData JsonLight deserializer for collections. + /// + internal sealed class ODataJsonLightCollectionDeserializer : ODataJsonLightPropertyAndValueDeserializer + { + /// Cached duplicate property names checker to use if the items are complex values. + private readonly PropertyAndAnnotationCollector propertyAndAnnotationCollector; + + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + internal ODataJsonLightCollectionDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + this.propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + } + + /// + /// Reads the start of a collection; this includes collection-level properties (e.g., the 'results' property) if the version permits it. + /// + /// The duplicate property names checker used to keep track of the properties and annotations + /// in the collection wrapper object. + /// true if we are reading a nested collection inside a paramter payload; otherwise false. + /// The expected item type reference or null if none is expected. + /// The validated actual item type reference (if specified in the payload) or the expected item type reference. + /// An representing the collection-level information. Currently this is only the name of the collection in ATOM. + /// + /// Pre-Condition: Any: the start of a nested collection value; if this is not a 'StartArray' node this method will fail. + /// JsonNodeType.Property: the first property of the collection wrapper object after the context URI. + /// JsonNodeType.EndObject: when the collection wrapper object has no properties (other than the context URI). + /// Post-Condition: JsonNodeType.StartArray: the start of the array of the collection items. + /// + internal ODataCollectionStart ReadCollectionStart( + PropertyAndAnnotationCollector collectionStartPropertyAndAnnotationCollector, + bool isReadingNestedPayload, + IEdmTypeReference expectedItemTypeReference, + out IEdmTypeReference actualItemTypeReference) + { + this.JsonReader.AssertNotBuffering(); + + actualItemTypeReference = expectedItemTypeReference; + + ODataCollectionStart collectionStart = null; + if (isReadingNestedPayload) + { + Debug.Assert(!this.JsonLightInputContext.ReadingResponse, "Nested collections are only supported in parameter payloads in requests."); + collectionStart = new ODataCollectionStart + { + Name = null + }; + } + else + { + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + IEdmTypeReference actualItemTypeRef = expectedItemTypeReference; + this.ProcessProperty( + collectionStartPropertyAndAnnotationCollector, + this.ReadTypePropertyAnnotationValue, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + if (!IsValidODataAnnotationOfCollection(propertyName)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); + } + + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.CustomInstanceAnnotation: + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) != 0) + { + throw new ODataException( + ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName(propertyName, JsonLightConstants.ODataValuePropertyName)); + } + + string payloadTypeName = ValidateDataPropertyTypeNameAnnotation(collectionStartPropertyAndAnnotationCollector, propertyName); + if (payloadTypeName != null) + { + string itemTypeName = EdmLibraryExtensions.GetCollectionItemTypeName(payloadTypeName); + if (itemTypeName == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightCollectionDeserializer_InvalidCollectionTypeName(payloadTypeName)); + } + + EdmTypeKind targetTypeKind; + ODataTypeAnnotation typeAnnotation; + Func typeKindFromPayloadFunc = () => { throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightCollectionDeserializer_ReadCollectionStart_TypeKindFromPayloadFunc)); }; + actualItemTypeRef = this.ReaderValidator.ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind.None, + /*expectStructuredType*/ null, + /*defaultPrimitivePayloadType*/ null, + expectedItemTypeReference, + itemTypeName, + this.Model, + typeKindFromPayloadFunc, + out targetTypeKind, + out typeAnnotation); + } + + collectionStart = new ODataCollectionStart + { + Name = null + }; + + break; + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightCollectionDeserializer_ReadCollectionStart)); + } + }); + + actualItemTypeReference = actualItemTypeRef; + } + + if (collectionStart == null) + { + // No collection property found; there should be exactly one property in the collection wrapper that does not have a reserved name. + throw new ODataException(ODataErrorStrings.ODataJsonLightCollectionDeserializer_ExpectedCollectionPropertyNotFound(JsonLightConstants.ODataValuePropertyName)); + } + } + + // at this point the reader is positioned on the start array node for the collection contents + if (this.JsonReader.NodeType != JsonNodeType.StartArray) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightCollectionDeserializer_CannotReadCollectionContentStart(this.JsonReader.NodeType)); + } + + this.JsonReader.AssertNotBuffering(); + + return collectionStart; + } + + /// + /// Reads an item in the collection. + /// + /// The expected type of the item to read. + /// The collection validator instance if no expected item type has been specified; otherwise null. + /// The value of the collection item that was read; this can be a primitive value or 'null'. + /// + /// Pre-Condition: The first node of the item in the collection + /// NOTE: this method will throw if the node is not + /// JsonNodeType.PrimitiveValue: for a primitive item + /// Post-Condition: The reader is positioned on the first node of the next item or an EndArray node if there are no more items in the collection + /// + internal object ReadCollectionItem(IEdmTypeReference expectedItemTypeReference, CollectionWithoutExpectedTypeValidator collectionValidator) + { + Debug.Assert( + expectedItemTypeReference == null || + expectedItemTypeReference.IsODataPrimitiveTypeKind() || + expectedItemTypeReference.IsODataEnumTypeKind() || + expectedItemTypeReference.IsODataTypeDefinitionTypeKind(), + "If an expected type is specified, it must be a primitive, enum type or type definition."); + this.JsonReader.AssertNotBuffering(); + + object item = this.ReadNonEntityValue( + /*payloadTypeName*/ null, + expectedItemTypeReference, + this.propertyAndAnnotationCollector, + collectionValidator, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ false, + /*insideResourceValue*/ false, + /*propertyName*/ null); + + this.JsonReader.AssertNotBuffering(); + + return item; + } + + /// + /// Reads the end of a collection; this includes collection-level instance annotations. + /// + /// true if we are reading a nested collection inside a paramter payload; otherwise false. + /// + /// Pre-Condition: EndArray node: End of the collection content array + /// Post-Condition: EndOfInput: All of the collection payload has been consumed. + /// + internal void ReadCollectionEnd(bool isReadingNestedPayload) + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndArray, "Pre-condition: JsonNodeType.EndArray"); + this.JsonReader.AssertNotBuffering(); + + this.JsonReader.ReadEndArray(); + + if (!isReadingNestedPayload) + { + // Create a new duplicate property names checker object here; we don't have to use the one from reading the + // collection start since we don't allow any annotations/properties after the collection property. + PropertyAndAnnotationCollector collectionEndPropertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + // Fail on anything after the collection that is not a custom instance annotation + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ProcessProperty( + collectionEndPropertyAndAnnotationCollector, + this.ReadTypePropertyAnnotationValue, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + // This method will allow and skip over any custom annotations, but will not report them as enum values, so any result we get other than EndOfObject indicates a malformed payload. + switch (propertyParsingResult) + { + case PropertyParsingResult.CustomInstanceAnnotation: + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.ODataInstanceAnnotation: + if (!IsValidODataAnnotationOfCollection(propertyName)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightCollectionDeserializer_CannotReadCollectionEnd(propertyName)); + } + + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.PropertyWithoutValue: // fall through + case PropertyParsingResult.PropertyWithValue: // fall through + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightCollectionDeserializer_CannotReadCollectionEnd(propertyName)); + + case PropertyParsingResult.EndOfObject: + break; + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightCollectionDeserializer_ReadCollectionEnd)); + } + }); + } + + // read the end-object node of the value containing the 'value' property + this.JsonReader.ReadEndObject(); + } + } + + /// + /// Returns if a property is a valid OData annotation of a collection. + /// + /// The name of the property. + /// If the property is a valid OData annotation of a collection. + private static bool IsValidODataAnnotationOfCollection(string propertyName) + { + return string.CompareOrdinal(ODataAnnotationNames.ODataCount, propertyName) == 0 + || string.CompareOrdinal(ODataAnnotationNames.ODataNextLink, propertyName) == 0; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionReader.cs new file mode 100644 index 0000000..e8df854 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionReader.cs @@ -0,0 +1,328 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + + #endregion Namespaces + + /// + /// OData collection reader for the JsonLight format. + /// + internal sealed class ODataJsonLightCollectionReader : ODataCollectionReaderCoreAsync + { + /// The input to read the payload from. + private readonly ODataJsonLightInputContext jsonLightInputContext; + + /// The collection deserializer to use to read from the input. + private readonly ODataJsonLightCollectionDeserializer jsonLightCollectionDeserializer; + + /// + /// Constructor. + /// + /// The input to read the payload from. + /// The expected type for the items in the collection. + /// If not null, the reader will notify the implementer of the interface of relevant state changes in the reader. + internal ODataJsonLightCollectionReader( + ODataJsonLightInputContext jsonLightInputContext, + IEdmTypeReference expectedItemTypeReference, + IODataReaderWriterListener listener) + : base(jsonLightInputContext, expectedItemTypeReference, listener) + { + Debug.Assert(jsonLightInputContext != null, "jsonLightInputContext != null"); + + this.jsonLightInputContext = jsonLightInputContext; + this.jsonLightCollectionDeserializer = new ODataJsonLightCollectionDeserializer(jsonLightInputContext); + } + + /// + /// Implementation of the collection reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: The reader is positioned on the first node of the first item or the EndArray node of an empty item array + /// + protected override bool ReadAtStartImplementation() + { + Debug.Assert(this.State == ODataCollectionReaderState.Start, "this.State == ODataCollectionReaderState.Start"); + Debug.Assert(this.IsReadingNestedPayload || this.jsonLightCollectionDeserializer.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None when not reading a nested payload."); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(); + + // Position the reader on the first node depending on whether we are reading a nested payload or not + this.jsonLightCollectionDeserializer.ReadPayloadStart( + ODataPayloadKind.Collection, + propertyAndAnnotationCollector, + this.IsReadingNestedPayload, + /*allowEmptyPayload*/false); + + return this.ReadAtStartImplementationSynchronously(propertyAndAnnotationCollector); + } + +#if PORTABLELIB + /// + /// Implementation of the collection reader logic when in state 'Start'. + /// + /// Task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: The reader is positioned on the first node of the first item or the EndArray node of an empty item array + /// + protected override Task ReadAtStartImplementationAsync() + { + Debug.Assert(this.State == ODataCollectionReaderState.Start, "this.State == ODataCollectionReaderState.Start"); + Debug.Assert(this.IsReadingNestedPayload || this.jsonLightCollectionDeserializer.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None when not reading a nested payload."); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(); + + // Position the reader on the first node depending on whether we are reading a nested payload or not + return this.jsonLightCollectionDeserializer.ReadPayloadStartAsync( + ODataPayloadKind.Collection, + propertyAndAnnotationCollector, + this.IsReadingNestedPayload, + /*allowEmptyPayload*/false) + + .FollowOnSuccessWith(t => + this.ReadAtStartImplementationSynchronously(propertyAndAnnotationCollector)); + } +#endif + + /// + /// Implementation of the reader logic when in state 'CollectionStart'. + /// + /// true if more nodes can be read from the reader; otherwise false. + /// + /// Pre-Condition: The first node of the first item in the collection or the EndArray node of the (empty) item array + /// NOTE: this method will throw if the node is not + /// JsonNodeType.EndArray: for an empty item array of the collection + /// JsonNodeType.StartObject: for a complex value as first item + /// JsonNodeType.PrimitiveValue: for a primitive value as first item + /// Post-Condition: The reader is positioned on the first node of the second item or an EndArray node if there are no items in the collection + /// + protected override bool ReadAtCollectionStartImplementation() + { + return this.ReadAtCollectionStartImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'CollectionStart'. + /// + /// Task which returns true if more nodes can be read from the reader; otherwise false. + /// + /// Pre-Condition: The first node of the first item in the collection or the EndArray node of the (empty) item array + /// NOTE: this method will throw if the node is not + /// JsonNodeType.EndArray: for an empty item array of the collection + /// JsonNodeType.StartObject: for a complex value as first item + /// JsonNodeType.PrimitiveValue: for a primitive value as first item + /// Post-Condition: The reader is positioned on the first node of the second item or an EndArray node if there are no items in the collection + /// + protected override Task ReadAtCollectionStartImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtCollectionStartImplementationSynchronously); + } +#endif + + /// + /// Implementation of the reader logic when in state 'Value'. + /// + /// true if more nodes can be read from the reader; otherwise false. + /// + /// Pre-Condition: The first node of the next item in the collection or the EndArray node of the item array + /// NOTE: this method will throw if the node is not + /// JsonNodeType.EndArray: for the end of the item array of the collection + /// JsonNodeType.StartObject: for a complex item + /// JsonNodeType.PrimitiveValue: for a primitive item + /// Post-Condition: The reader is positioned on the first node of the next item or an EndArray node if there are no items in the collection + /// + protected override bool ReadAtValueImplementation() + { + return this.ReadAtValueImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'Value'. + /// + /// Task which returns true if more nodes can be read from the reader; otherwise false. + /// + /// Pre-Condition: The first node of the next item in the collection or the EndArray node of the item array + /// NOTE: this method will throw if the node is not + /// JsonNodeType.EndArray: for the end of the item array of the collection + /// JsonNodeType.StartObject: for a complex item + /// JsonNodeType.PrimitiveValue: for a primitive item + /// Post-Condition: The reader is positioned on the first node of the next item or an EndArray node if there are no items in the collection + /// + protected override Task ReadAtValueImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtValueImplementationSynchronously); + } +#endif + + /// + /// Implementation of the reader logic when in state 'CollectionEnd'. + /// + /// false since no more nodes can be read from the reader after the collection ended. + /// + /// Pre-Condition: JsonNodeType.EndArray the end of the item array of the collection + /// Post-Condition: JsonNodeType.EndOfInput nothing else to read when not reading a nested payload + /// + protected override bool ReadAtCollectionEndImplementation() + { + return this.ReadAtCollectionEndImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'CollectionEnd'. + /// + /// Task which should return false since no more nodes can be read from the reader after the collection ends. + /// + /// Pre-Condition: JsonNodeType.EndArray the end of the item array of the collection + /// Post-Condition: JsonNodeType.EndOfInput nothing else to read when not reading a nested payload + /// + protected override Task ReadAtCollectionEndImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtCollectionEndImplementationSynchronously); + } +#endif + + /// + /// Implementation of the collection reader logic when in state 'Start'. + /// + /// The duplicate property names checker for the top-level scope. + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: The reader is positioned on the first node of the first item or the EndArray node of an empty item array + /// + private bool ReadAtStartImplementationSynchronously(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + IEdmTypeReference actualItemTypeReference; + this.ExpectedItemTypeReference = ReaderValidationUtils.ValidateCollectionContextUriAndGetPayloadItemTypeReference( + this.jsonLightCollectionDeserializer.ContextUriParseResult, + this.ExpectedItemTypeReference); + + // read the start of the collection until we find the content array for top-level collections + ODataCollectionStart collectionStart = this.jsonLightCollectionDeserializer.ReadCollectionStart( + propertyAndAnnotationCollector, + this.IsReadingNestedPayload, + this.ExpectedItemTypeReference, + out actualItemTypeReference); + + if (actualItemTypeReference != null) + { + this.ExpectedItemTypeReference = actualItemTypeReference; + } + + this.jsonLightCollectionDeserializer.JsonReader.ReadStartArray(); + + this.EnterScope(ODataCollectionReaderState.CollectionStart, collectionStart); + + return true; + } + + /// + /// Implementation of the reader logic when in state 'CollectionStart'. + /// + /// true if more nodes can be read from the reader; otherwise false. + /// + /// Pre-Condition: The first node of the first item in the collection or the EndArray node of the (empty) item array + /// NOTE: this method will throw if the node is not + /// JsonNodeType.EndArray: for an empty item array of the collection + /// JsonNodeType.StartObject: for a complex value as first item + /// JsonNodeType.PrimitiveValue: for a primitive value as first item + /// Post-Condition: The reader is positioned on the first node of the second item or an EndArray node if there are no items in the collection + /// + private bool ReadAtCollectionStartImplementationSynchronously() + { + Debug.Assert(this.State == ODataCollectionReaderState.CollectionStart, "this.State == ODataCollectionReaderState.CollectionStart"); + + if (this.jsonLightCollectionDeserializer.JsonReader.NodeType == JsonNodeType.EndArray) + { + // empty collection + this.ReplaceScope(ODataCollectionReaderState.CollectionEnd, this.Item); + } + else + { + object item = this.jsonLightCollectionDeserializer.ReadCollectionItem(this.ExpectedItemTypeReference, this.CollectionValidator); + this.EnterScope(ODataCollectionReaderState.Value, item); + } + + return true; + } + + /// + /// Implementation of the reader logic when in state 'Value'. + /// + /// true if more nodes can be read from the reader; otherwise false. + /// + /// Pre-Condition: The first node of the next item in the collection or the EndArray node of the item array + /// NOTE: this method will throw if the node is not + /// JsonNodeType.EndArray: for the end of the item array of the collection + /// JsonNodeType.StartObject: for a complex item + /// JsonNodeType.PrimitiveValue: for a primitive item + /// Post-Condition: The reader is positioned on the first node of the next item or an EndArray node if there are no items in the collection + /// + private bool ReadAtValueImplementationSynchronously() + { + Debug.Assert(this.State == ODataCollectionReaderState.Value, "this.State == ODataCollectionReaderState.Value"); + + if (this.jsonLightCollectionDeserializer.JsonReader.NodeType == JsonNodeType.EndArray) + { + // end of collection reached + this.PopScope(ODataCollectionReaderState.Value); + this.ReplaceScope(ODataCollectionReaderState.CollectionEnd, this.Item); + } + else + { + object item = this.jsonLightCollectionDeserializer.ReadCollectionItem(this.ExpectedItemTypeReference, this.CollectionValidator); + this.ReplaceScope(ODataCollectionReaderState.Value, item); + } + + return true; + } + + /// + /// Implementation of the reader logic when in state 'CollectionEnd'. + /// + /// false since no more nodes can be read from the reader after the collection ended. + /// + /// Pre-Condition: JsonNodeType.EndArray the end of the item array of the collection + /// Post-Condition: JsonNodeType.EndOfInput nothing else to read when not reading a nested payload + /// + private bool ReadAtCollectionEndImplementationSynchronously() + { + Debug.Assert(this.State == ODataCollectionReaderState.CollectionEnd, "this.State == ODataCollectionReaderState.CollectionEnd"); + Debug.Assert(this.jsonLightCollectionDeserializer.JsonReader.NodeType == JsonNodeType.EndArray, "Pre-Condition: expected JsonNodeType.EndArray"); + + this.PopScope(ODataCollectionReaderState.CollectionEnd); + Debug.Assert(this.State == ODataCollectionReaderState.Start, "this.State == ODataCollectionReaderState.Start"); + + this.jsonLightCollectionDeserializer.ReadCollectionEnd(this.IsReadingNestedPayload); + this.jsonLightCollectionDeserializer.ReadPayloadEnd(this.IsReadingNestedPayload); + + // replace the 'Start' scope with the 'Completed' scope + this.ReplaceScope(ODataCollectionReaderState.Completed, null); + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionSerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionSerializer.cs new file mode 100644 index 0000000..8714ea2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionSerializer.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System.Diagnostics; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// OData JsonLight serializer for collections. + /// + internal sealed class ODataJsonLightCollectionSerializer : ODataJsonLightValueSerializer + { + /// true when writing a top-level collection that requires the 'value' wrapper object; otherwise false. + private readonly bool writingTopLevelCollection; + + /// + /// Constructor. + /// + /// The output context to write to. + /// true when writing a top-level collection that requires the 'value' wrapper object; otherwise false. + internal ODataJsonLightCollectionSerializer(ODataJsonLightOutputContext jsonLightOutputContext, bool writingTopLevelCollection) + : base(jsonLightOutputContext, /*initContextUriBuilder*/true) + { + this.writingTopLevelCollection = writingTopLevelCollection; + } + + /// + /// Writes the start of a collection. + /// + /// The collection start to write. + /// The item type of the collection or null if no metadata is available. + internal void WriteCollectionStart(ODataCollectionStart collectionStart, IEdmTypeReference itemTypeReference) + { + Debug.Assert(collectionStart != null, "collectionStart != null"); + + if (this.writingTopLevelCollection) + { + // "{" + this.JsonWriter.StartObjectScope(); + + // "@odata.context":... + this.WriteContextUriProperty(ODataPayloadKind.Collection, () => ODataContextUrlInfo.Create(collectionStart.SerializationInfo, itemTypeReference)); + + // "@odata.count":... + if (collectionStart.Count.HasValue) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataCount); + this.JsonWriter.WriteValue(collectionStart.Count.Value); + } + + // "@odata.nextlink":... + if (collectionStart.NextPageLink != null) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataNextLink); + this.JsonWriter.WriteValue(this.UriToString(collectionStart.NextPageLink)); + } + + // "value": + this.JsonWriter.WriteValuePropertyName(); + } + + // Write the start of the array for the collection items + // "[" + this.JsonWriter.StartArrayScope(); + } + + /// + /// Writes the end of a collection. + /// + internal void WriteCollectionEnd() + { + // Write the end of the array for the collection items + // "]" + this.JsonWriter.EndArrayScope(); + + if (this.writingTopLevelCollection) + { + // "}" + this.JsonWriter.EndObjectScope(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionWriter.cs new file mode 100644 index 0000000..4b22b2c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightCollectionWriter.cs @@ -0,0 +1,172 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// ODataCollectionWriter for the JsonLight format. + /// + internal sealed class ODataJsonLightCollectionWriter : ODataCollectionWriterCore + { + /// + /// The output context to write to. + /// + private readonly ODataJsonLightOutputContext jsonLightOutputContext; + + /// + /// The JsonLight collection serializer to use. + /// + private readonly ODataJsonLightCollectionSerializer jsonLightCollectionSerializer; + + /// + /// Constructor for creating a collection writer to use when writing operation result payloads. + /// + /// The output context to write to. + /// The item type of the collection being written or null if no metadata is available. + internal ODataJsonLightCollectionWriter(ODataJsonLightOutputContext jsonLightOutputContext, IEdmTypeReference itemTypeReference) + : base(jsonLightOutputContext, itemTypeReference) + { + Debug.Assert(jsonLightOutputContext != null, "jsonLightOutputContext != null"); + + this.jsonLightOutputContext = jsonLightOutputContext; + this.jsonLightCollectionSerializer = new ODataJsonLightCollectionSerializer(this.jsonLightOutputContext, /*writingTopLevelCollection*/true); + } + + /// + /// Constructor for creating a collection writer to use when writing parameter payloads. + /// + /// The output context to write to. + /// The type reference of the expected item type or null if no expected item type exists. + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + internal ODataJsonLightCollectionWriter(ODataJsonLightOutputContext jsonLightOutputContext, IEdmTypeReference expectedItemType, IODataReaderWriterListener listener) + : base(jsonLightOutputContext, expectedItemType, listener) + { + Debug.Assert(jsonLightOutputContext != null, "jsonLightOutputContext != null"); + Debug.Assert(!jsonLightOutputContext.WritingResponse, "The collection writer constructor for parameter payloads must only be used for writing requests."); + + this.jsonLightOutputContext = jsonLightOutputContext; + this.jsonLightCollectionSerializer = new ODataJsonLightCollectionSerializer(this.jsonLightOutputContext, /*writingTopLevelCollection*/false); + } + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + protected override void VerifyNotDisposed() + { + this.jsonLightOutputContext.VerifyNotDisposed(); + } + + /// + /// Flush the output. + /// + protected override void FlushSynchronously() + { + this.jsonLightOutputContext.Flush(); + } + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected override Task FlushAsynchronously() + { + return this.jsonLightOutputContext.FlushAsync(); + } +#endif + + /// + /// Start writing an OData payload. + /// + protected override void StartPayload() + { + this.jsonLightCollectionSerializer.WritePayloadStart(); + } + + /// + /// Finish writing an OData payload. + /// + protected override void EndPayload() + { + this.jsonLightCollectionSerializer.WritePayloadEnd(); + } + + /// + /// Start writing a collection. + /// + /// The representing the collection. + protected override void StartCollection(ODataCollectionStart collectionStart) + { + this.jsonLightCollectionSerializer.WriteCollectionStart(collectionStart, this.ItemTypeReference); + } + + /// + /// Finish writing a collection. + /// + protected override void EndCollection() + { + this.jsonLightCollectionSerializer.WriteCollectionEnd(); + } + + /// + /// Writes a collection item (either primitive, enum or resource) + /// + /// The collection item to write. + /// The expected type of the collection item or null if no expected item type exists. + protected override void WriteCollectionItem(object item, IEdmTypeReference expectedItemType) + { + if (item == null) + { + this.jsonLightOutputContext.WriterValidator.ValidateNullCollectionItem(expectedItemType); + this.jsonLightOutputContext.JsonWriter.WriteValue((string)null); + } + else + { + ODataResourceValue resourceValue = item as ODataResourceValue; + ODataEnumValue enumVal = null; + if (resourceValue != null) + { + this.jsonLightCollectionSerializer.AssertRecursionDepthIsZero(); + this.jsonLightCollectionSerializer.WriteResourceValue( + resourceValue, + expectedItemType, + false /*isOpenPropertyType*/, + this.DuplicatePropertyNameChecker); + this.jsonLightCollectionSerializer.AssertRecursionDepthIsZero(); + this.DuplicatePropertyNameChecker.Reset(); + } + else if ((enumVal = item as ODataEnumValue) != null) + { + if (enumVal.Value == null) + { + this.jsonLightCollectionSerializer.WriteNullValue(); + } + else + { + // write ODataEnumValue.Value as string value + this.jsonLightCollectionSerializer.WritePrimitiveValue(enumVal.Value, EdmCoreModel.Instance.GetString(true)); + } + } + else + { + Debug.Assert(!(item is ODataCollectionValue), "!(item is ODataCollectionValue)"); + Debug.Assert(!(item is ODataStreamReferenceValue), "!(item is ODataStreamReferenceValue)"); + this.jsonLightCollectionSerializer.WritePrimitiveValue(item, expectedItemType); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParseResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParseResult.cs new file mode 100644 index 0000000..8c3a2ab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParseResult.cs @@ -0,0 +1,85 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// The result of parsing an OData context URI in JSON Lite. + /// + internal sealed class ODataJsonLightContextUriParseResult + { + /// The context URI read from the payload in its unparsed form. + private readonly Uri contextUriFromPayload; + + /// + /// Initializes a new instance of the class. + /// + /// The context URI read from the payload in its unparsed form. + internal ODataJsonLightContextUriParseResult(Uri contextUriFromPayload) + { + this.contextUriFromPayload = contextUriFromPayload; + } + + /// + /// The context URI read from the payload in its unparsed form. + /// + internal Uri ContextUri + { + get + { + return this.contextUriFromPayload; + } + } + + /// + /// The metadata document URI as read from the payload. + /// + /// This is the metadata document URI as read from the payload without the fragment. + internal Uri MetadataDocumentUri { get; set; } + + /// + /// The fragment portion of the context URI. + /// + internal string Fragment { get; set; } + + /// + /// The $select query option. + /// + internal string SelectQueryOption { get; set; } + + /// + /// The resolved navigation source as specified in the context URI. + /// + internal IEdmNavigationSource NavigationSource { get; set; } + + /// + /// The resolved structured type as specified in the context URI. + /// + internal IEdmType EdmType { get; set; } + + /// + /// The detected payload kinds from parsing the context URI. + /// + internal IEnumerable DetectedPayloadKinds { get; set; } + + /// + /// ODataPath parsed from context Url + /// + internal ODataPath Path { get; set; } + + /// + /// DeltaKind from context Url, only applicable when payload kind is Delta + /// + internal ODataDeltaKind DeltaKind { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs new file mode 100644 index 0000000..1c37df9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs @@ -0,0 +1,500 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Text.RegularExpressions; + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Parser for odata context URIs used in JSON Lite. + /// + internal sealed class ODataJsonLightContextUriParser + { + /// + /// Pattern for key segments, Examples: + /// Customer(1), Customer('foo'), + /// Customer(baf04077-a3c0-454b-ac6f-9fec00b8e170), Message(FromUsername='1',MessageId=-10) + /// Message(geography'SRID=0;Collection(LineString(142.1 64.1,3.14 2.78))'),Message(duration'P6DT23H59M59.9999S') + /// + private static readonly Regex KeyPattern = new Regex(@"^(?:-{0,1}\d+?|\w*'.+?'|[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}|.+?=.+?)$", RegexOptions.IgnoreCase); + + /// The model to use when resolving the target of the URI. + private readonly IEdmModel model; + + /// The result of parsing the context URI. + private readonly ODataJsonLightContextUriParseResult parseResult; + + /// + /// Initializes a new instance of the class. + /// + /// The model to use when resolving the target of the URI. + /// The context URI read from the payload. + private ODataJsonLightContextUriParser(IEdmModel model, Uri contextUriFromPayload) + { + Debug.Assert(model != null, "model != null"); + + if (!model.IsUserModel()) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_NoModel); + } + + this.model = model; + this.parseResult = new ODataJsonLightContextUriParseResult(contextUriFromPayload); + } + + /// + /// Creates a context URI parser and parses the context URI read from the payload. + /// + /// The model to use when resolving the target of the URI. + /// The string value of the odata.metadata annotation read from the payload. + /// The payload kind we expect the context URI to conform to. + /// The function of client cuetom type resolver. + /// Whether the fragment after $metadata should be parsed, if set to false, only MetadataDocumentUri is parsed. + /// Whether to throw if a type specified in the ContextUri is not found in metadata. + /// The result from parsing the context URI. + internal static ODataJsonLightContextUriParseResult Parse( + IEdmModel model, + string contextUriFromPayload, + ODataPayloadKind payloadKind, + Func clientCustomTypeResolver, + bool needParseFragment, + bool throwIfMetadataConflict = true) + { + if (contextUriFromPayload == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_NullMetadataDocumentUri); + } + + // Create an absolute URI from the payload string + // TODO: Support relative context uri and resolving other relative uris + Uri contextUri; + if (!Uri.TryCreate(contextUriFromPayload, UriKind.Absolute, out contextUri)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute(contextUriFromPayload)); + } + + ODataJsonLightContextUriParser parser = new ODataJsonLightContextUriParser(model, contextUri); + + parser.TokenizeContextUri(); + if (needParseFragment) + { + parser.ParseContextUri(payloadKind, clientCustomTypeResolver, throwIfMetadataConflict); + } + + return parser.parseResult; + } + + /// + /// Extracts the value of the $select query option from the specified fragment. + /// + /// The fragment to extract the $select query option from. + /// The value of the $select query option or null if none exists. + private static string ExtractSelectQueryOption(string fragment) + { + return fragment; + } + + /// + /// Parses a context URI read from the payload into its parts. + /// + private void TokenizeContextUri() + { + Uri contextUriFromPayload = this.parseResult.ContextUri; + Debug.Assert(contextUriFromPayload != null && contextUriFromPayload.IsAbsoluteUri, "contextUriFromPayload != null && contextUriFromPayload.IsAbsoluteUri"); + + // Remove the fragment from the URI read from the payload + UriBuilder uriBuilderWithoutFragment = new UriBuilder(contextUriFromPayload) + { + Fragment = null, + }; + + // Make sure the metadata document URI from the settings matches the context URI in the payload. + this.parseResult.MetadataDocumentUri = uriBuilderWithoutFragment.Uri; + + // Get the fragment of the context URI + this.parseResult.Fragment = contextUriFromPayload.GetComponents(UriComponents.Fragment, UriFormat.SafeUnescaped); + } + + /// + /// Applies the model and validates the context URI against it. + /// + /// The payload kind we expect the context URI to conform to. + /// The function of client custom type resolver. + /// Whether to throw if a type specified in the ContextUri is not found in metadata. + private void ParseContextUri(ODataPayloadKind expectedPayloadKind, Func clientCustomTypeResolver, bool throwIfMetadataConflict) + { + bool isUndeclared; + ODataPayloadKind detectedPayloadKind = this.ParseContextUriFragment(this.parseResult.Fragment, clientCustomTypeResolver, throwIfMetadataConflict, out isUndeclared); + + // unsupported payload kind indicates that this is during payload kind detection, so we should not fail. + bool detectedPayloadKindMatchesExpectation = detectedPayloadKind == expectedPayloadKind || expectedPayloadKind == ODataPayloadKind.Unsupported; + IEdmType parseType = this.parseResult.EdmType; + if (parseType != null && parseType.TypeKind == EdmTypeKind.Untyped) + { + if (string.Equals(parseType.FullTypeName(), ODataConstants.ContextUriFragmentUntyped, StringComparison.Ordinal)) + { + // Anything matches the built-in Edm.Untyped + this.parseResult.DetectedPayloadKinds = new[] { ODataPayloadKind.ResourceSet, ODataPayloadKind.Property, ODataPayloadKind.Collection, ODataPayloadKind.Resource }; + detectedPayloadKindMatchesExpectation = true; + } + else if (expectedPayloadKind == ODataPayloadKind.Property || expectedPayloadKind == ODataPayloadKind.Resource) + { + // If we created an untyped type because the name was not resolved it can match any single value + this.parseResult.DetectedPayloadKinds = new[] { ODataPayloadKind.Property, ODataPayloadKind.Resource }; + detectedPayloadKindMatchesExpectation = true; + } + } + else if (parseType != null && parseType.TypeKind == EdmTypeKind.Collection && ((IEdmCollectionType)parseType).ElementType.TypeKind() == EdmTypeKind.Untyped) + { + this.parseResult.DetectedPayloadKinds = new[] { ODataPayloadKind.ResourceSet, ODataPayloadKind.Property, ODataPayloadKind.Collection }; + if (expectedPayloadKind == ODataPayloadKind.ResourceSet || expectedPayloadKind == ODataPayloadKind.Property || expectedPayloadKind == ODataPayloadKind.Collection) + { + detectedPayloadKindMatchesExpectation = true; + } + } + else if (detectedPayloadKind == ODataPayloadKind.ResourceSet && parseType.IsODataComplexTypeKind()) + { + this.parseResult.DetectedPayloadKinds = new[] { ODataPayloadKind.ResourceSet, ODataPayloadKind.Property, ODataPayloadKind.Collection }; + + if (expectedPayloadKind == ODataPayloadKind.Property || expectedPayloadKind == ODataPayloadKind.Collection) + { + detectedPayloadKindMatchesExpectation = true; + } + } + else if (detectedPayloadKind == ODataPayloadKind.Resource && parseType.IsODataComplexTypeKind()) + { + this.parseResult.DetectedPayloadKinds = new[] { ODataPayloadKind.Resource, ODataPayloadKind.Property }; + if (expectedPayloadKind == ODataPayloadKind.Property) + { + detectedPayloadKindMatchesExpectation = true; + } + } + else if (detectedPayloadKind == ODataPayloadKind.Collection) + { + // If the detected payload kind is 'collection' it can always also be treated as a property. + this.parseResult.DetectedPayloadKinds = new[] { ODataPayloadKind.Collection, ODataPayloadKind.Property }; + if (expectedPayloadKind == ODataPayloadKind.Property) + { + detectedPayloadKindMatchesExpectation = true; + } + } + else if (detectedPayloadKind == ODataPayloadKind.Resource) + { + this.parseResult.DetectedPayloadKinds = new[] { ODataPayloadKind.Resource, ODataPayloadKind.Delta }; + if (expectedPayloadKind == ODataPayloadKind.Delta) + { + this.parseResult.DeltaKind = ODataDeltaKind.Resource; + detectedPayloadKindMatchesExpectation = true; + } + } + else if (detectedPayloadKind == ODataPayloadKind.Property && isUndeclared + && (expectedPayloadKind == ODataPayloadKind.Resource || expectedPayloadKind == ODataPayloadKind.ResourceSet)) + { + // for undeclared, we don't know whether it is a resource/resource set or not. + this.parseResult.DetectedPayloadKinds = new[] { expectedPayloadKind, ODataPayloadKind.Property }; + detectedPayloadKindMatchesExpectation = true; + } + else + { + this.parseResult.DetectedPayloadKinds = new[] { detectedPayloadKind }; + } + + // If the expected and detected payload kinds don't match and we are not running payload kind detection + // right now (payloadKind == ODataPayloadKind.Unsupported) and we did not detect a collection kind for + // an expected property kind (which is allowed), fail. + if (!detectedPayloadKindMatchesExpectation) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_ContextUriDoesNotMatchExpectedPayloadKind(UriUtils.UriToString(this.parseResult.ContextUri), expectedPayloadKind.ToString())); + } + + // NOTE: we interpret an empty select query option to mean that nothing should be projected + // (whereas a missing select query option means everything should be projected). + string selectQueryOption = this.parseResult.SelectQueryOption; + if (selectQueryOption != null) + { + if (detectedPayloadKind != ODataPayloadKind.ResourceSet && detectedPayloadKind != ODataPayloadKind.Resource && detectedPayloadKind != ODataPayloadKind.Delta) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidPayloadKindWithSelectQueryOption(expectedPayloadKind.ToString())); + } + } + } + + /// + /// Parses the fragment of a context URI. + /// + /// The fragment to parse + /// The function of client cuetom type resolver. + /// Whether to throw if a type specified in the ContextUri is not found in metadata. + /// Indicates if the fragment is for an unknown path segment + /// The detected payload kind based on parsing the fragment. + [SuppressMessage("Microsoft.Maintainability", "CA1502", Justification = "Will be moving to non case statements later, no point in investing in reducing this now")] + private ODataPayloadKind ParseContextUriFragment(string fragment, Func clientCustomTypeResolver, bool throwIfMetadataConflict, out bool isUndeclared) + { + bool hasItemSelector = false; + ODataDeltaKind kind = ODataDeltaKind.None; + isUndeclared = false; + + // Deal with /$entity + if (fragment.EndsWith(ODataConstants.ContextUriFragmentItemSelector, StringComparison.Ordinal)) + { + hasItemSelector = true; + fragment = fragment.Substring(0, fragment.Length - ODataConstants.ContextUriFragmentItemSelector.Length); + } + else if (fragment.EndsWith(ODataConstants.ContextUriDeltaResourceSet, StringComparison.Ordinal)) + { + kind = ODataDeltaKind.ResourceSet; + fragment = fragment.Substring(0, fragment.Length - ODataConstants.ContextUriDeltaResourceSet.Length); + } + else if (fragment.EndsWith(ODataConstants.ContextUriDeletedEntry, StringComparison.Ordinal)) + { + kind = ODataDeltaKind.DeletedEntry; + fragment = fragment.Substring(0, fragment.Length - ODataConstants.ContextUriDeletedEntry.Length); + } + else if (fragment.EndsWith(ODataConstants.ContextUriDeltaLink, StringComparison.Ordinal)) + { + kind = ODataDeltaKind.Link; + fragment = fragment.Substring(0, fragment.Length - ODataConstants.ContextUriDeltaLink.Length); + } + else if (fragment.EndsWith(ODataConstants.ContextUriDeletedLink, StringComparison.Ordinal)) + { + kind = ODataDeltaKind.DeletedLink; + fragment = fragment.Substring(0, fragment.Length - ODataConstants.ContextUriDeletedLink.Length); + } + + this.parseResult.DeltaKind = kind; + + // Deal with query option + if (fragment.EndsWith(")", StringComparison.Ordinal)) + { + int index = fragment.Length - 2; + for (int rcount = 1; rcount > 0 && index > 0; --index) + { + switch (fragment[index]) + { + case '(': + rcount--; + break; + case ')': + rcount++; + break; + } + } + + if (index == 0) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(UriUtils.UriToString(this.parseResult.ContextUri))); + } + + string previous = fragment.Substring(0, index + 1); + + // Don't treat Collection(Edm.Type) as SelectExpand segment + if (!previous.Equals("Collection")) + { + string selectExpandStr = fragment.Substring(index + 2); + selectExpandStr = selectExpandStr.Substring(0, selectExpandStr.Length - 1); + + // Do not treat Key as SelectExpand segment + if (KeyPattern.IsMatch(selectExpandStr)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_LastSegmentIsKeySegment(UriUtils.UriToString(this.parseResult.ContextUri))); + } + + this.parseResult.SelectQueryOption = ExtractSelectQueryOption(selectExpandStr); + fragment = previous; + } + } + + ODataPayloadKind detectedPayloadKind = ODataPayloadKind.Unsupported; + EdmTypeResolver edmTypeResolver = new EdmTypeReaderResolver(this.model, clientCustomTypeResolver); + + if (!fragment.Contains(ODataConstants.UriSegmentSeparator) && !hasItemSelector && kind == ODataDeltaKind.None) + { + // Service document: no fragment + if (fragment.Length == 0) + { + detectedPayloadKind = ODataPayloadKind.ServiceDocument; + } + else if (fragment.Equals(ODataConstants.CollectionPrefix + "(" + ODataConstants.EntityReferenceSegmentName + ")")) + { + detectedPayloadKind = ODataPayloadKind.EntityReferenceLinks; + } + else if (fragment.Equals(ODataConstants.EntityReferenceSegmentName)) + { + detectedPayloadKind = ODataPayloadKind.EntityReferenceLink; + } + else + { + var foundNavigationSource = this.model.FindDeclaredNavigationSource(fragment); + + if (foundNavigationSource != null) + { + // Resource Set: {schema.entity-container.entity-set} or Singleton: {schema.entity-container.singleton} + this.parseResult.NavigationSource = foundNavigationSource; + this.parseResult.EdmType = edmTypeResolver.GetElementType(foundNavigationSource); + detectedPayloadKind = foundNavigationSource is IEdmSingleton ? ODataPayloadKind.Resource : ODataPayloadKind.ResourceSet; + } + else + { + // Property: {schema.type} or Collection({schema.type}) where schema.type is primitive or complex. + detectedPayloadKind = this.ResolveType(fragment, clientCustomTypeResolver, throwIfMetadataConflict); + Debug.Assert( + this.parseResult.EdmType.TypeKind == EdmTypeKind.Primitive || this.parseResult.EdmType.TypeKind == EdmTypeKind.Enum || this.parseResult.EdmType.TypeKind == EdmTypeKind.TypeDefinition || this.parseResult.EdmType.TypeKind == EdmTypeKind.Complex || this.parseResult.EdmType.TypeKind == EdmTypeKind.Collection || this.parseResult.EdmType.TypeKind == EdmTypeKind.Entity || this.parseResult.EdmType.TypeKind == EdmTypeKind.Untyped, + "The first context URI segment must be a set or a non-entity type."); + } + } + } + else + { + Debug.Assert(this.parseResult.MetadataDocumentUri.IsAbsoluteUri, "this.parseResult.MetadataDocumentUri.IsAbsoluteUri"); + + string metadataDocumentStr = UriUtils.UriToString(this.parseResult.MetadataDocumentUri); + + if (!metadataDocumentStr.EndsWith(ODataConstants.UriMetadataSegment, StringComparison.Ordinal)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(UriUtils.UriToString(this.parseResult.ContextUri))); + } + + Uri serviceRoot = new Uri(metadataDocumentStr.Substring(0, metadataDocumentStr.Length - ODataConstants.UriMetadataSegment.Length)); + + ODataUriParser odataUriParser = new ODataUriParser(this.model, serviceRoot, new Uri(serviceRoot, fragment)); + + ODataPath path; + try + { + path = odataUriParser.ParsePath(); + } + catch (ODataException) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(UriUtils.UriToString(this.parseResult.ContextUri))); + } + + if (path.Count == 0) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(UriUtils.UriToString(this.parseResult.ContextUri))); + } + + this.parseResult.Path = path; + + parseResult.NavigationSource = path.NavigationSource(); + parseResult.EdmType = path.LastSegment.EdmType; + + ODataPathSegment lastSegment = path.TrimEndingTypeSegment().LastSegment; + if (lastSegment is EntitySetSegment || lastSegment is NavigationPropertySegment) + { + if (kind != ODataDeltaKind.None) + { + detectedPayloadKind = ODataPayloadKind.Delta; + } + else + { + detectedPayloadKind = hasItemSelector ? ODataPayloadKind.Resource : ODataPayloadKind.ResourceSet; + } + + if (this.parseResult.EdmType is IEdmCollectionType) + { + var collectionTypeReference = this.parseResult.EdmType.ToTypeReference().AsCollection(); + if (collectionTypeReference != null) + { + this.parseResult.EdmType = collectionTypeReference.ElementType().Definition; + } + } + } + else if (lastSegment is SingletonSegment) + { + detectedPayloadKind = ODataPayloadKind.Resource; + } + else if (path.IsIndividualProperty()) + { + isUndeclared = path.IsUndeclared(); + detectedPayloadKind = ODataPayloadKind.Property; + IEdmComplexType complexType = parseResult.EdmType as IEdmComplexType; + if (complexType != null) + { + detectedPayloadKind = ODataPayloadKind.Resource; + } + else + { + IEdmCollectionType collectionType = parseResult.EdmType as IEdmCollectionType; + + if (collectionType != null) + { + if (collectionType.ElementType.IsComplex()) + { + this.parseResult.EdmType = collectionType.ElementType.Definition; + detectedPayloadKind = ODataPayloadKind.ResourceSet; + } + else + { + detectedPayloadKind = ODataPayloadKind.Collection; + } + } + } + } + else + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(UriUtils.UriToString(this.parseResult.ContextUri))); + } + } + + return detectedPayloadKind; + } + + /// + /// Resolves a type. + /// + /// The type name. + /// The function of client cuetom type resolver. + /// Whether to throw if a type specified in the ContextUri is not found in metadata. + /// The resolved Edm type. + private ODataPayloadKind ResolveType(string typeName, Func clientCustomTypeResolver, bool throwIfMetadataConflict) + { + string typeNameToResolve = EdmLibraryExtensions.GetCollectionItemTypeName(typeName) ?? typeName; + bool isCollection = typeNameToResolve != typeName; + + EdmTypeKind typeKind; + IEdmType resolvedType = MetadataUtils.ResolveTypeNameForRead(this.model, /*expectedType*/ null, typeNameToResolve, clientCustomTypeResolver, out typeKind); + if (resolvedType == null && !throwIfMetadataConflict) + { + string namespaceName; + string name; + TypeUtils.ParseQualifiedTypeName(typeName, out namespaceName, out name, out isCollection); + resolvedType = new EdmUntypedStructuredType(namespaceName, name); + } + + if (resolvedType == null || + resolvedType.TypeKind != EdmTypeKind.Primitive + && resolvedType.TypeKind != EdmTypeKind.Enum + && resolvedType.TypeKind != EdmTypeKind.Complex + && resolvedType.TypeKind != EdmTypeKind.Entity + && resolvedType.TypeKind != EdmTypeKind.TypeDefinition + && resolvedType.TypeKind != EdmTypeKind.Untyped) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidEntitySetNameOrTypeName(UriUtils.UriToString(this.parseResult.ContextUri), typeName)); + } + + if (resolvedType.TypeKind == EdmTypeKind.Entity || resolvedType.TypeKind == EdmTypeKind.Complex) + { + this.parseResult.EdmType = resolvedType; + return isCollection ? ODataPayloadKind.ResourceSet : ODataPayloadKind.Resource; + } + + // For structured collection ,the EdmType is element type. for primitive collection, it is collection type + resolvedType = isCollection ? EdmLibraryExtensions.GetCollectionType(resolvedType.ToTypeReference(true /*nullable*/)) : resolvedType; + this.parseResult.EdmType = resolvedType; + return isCollection ? ODataPayloadKind.Collection : ODataPayloadKind.Property; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaReader.cs new file mode 100644 index 0000000..2ad94e6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaReader.cs @@ -0,0 +1,220 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + #endregion Namespaces + + /// + /// OData delta reader for the JsonLight format. + /// + internal sealed class ODataJsonLightDeltaReader : ODataDeltaReader + { + #region Private Fields + + /// Underlying JSON Light Reader + private readonly ODataJsonLightReader underlyingReader; + + /// Whether the reader is reading nested content + private int nestedLevel; + + #endregion + + #region Constructor + + /// + /// Constructor. + /// + /// The input to read the payload from. + /// The navigation source we are going to read entities for. + /// The expected entity type for the resource to be read (in case of resource reader) or entries in the resource set to be read (in case of resource set reader). + public ODataJsonLightDeltaReader( + ODataJsonLightInputContext jsonLightInputContext, + IEdmNavigationSource navigationSource, + IEdmEntityType expectedEntityType) + { + this.underlyingReader = new ODataJsonLightReader(jsonLightInputContext, navigationSource, expectedEntityType, /*readingResourceSet*/ true, /*readingParameter*/ false, /*readingDelta*/ true, /*listener*/ null); + } + + #endregion + + #region Public Properties + + /// Gets the current state of the reader. + /// The current state of the reader. + public override ODataDeltaReaderState State + { + get + { + if (this.nestedLevel > 0 || this.underlyingReader.State == ODataReaderState.NestedResourceInfoEnd) + { + return ODataDeltaReaderState.NestedResource; + } + + switch (this.underlyingReader.State) + { + case ODataReaderState.Start: + return ODataDeltaReaderState.Start; + case ODataReaderState.DeltaResourceSetStart: + return ODataDeltaReaderState.DeltaResourceSetStart; + case ODataReaderState.DeltaResourceSetEnd: + return ODataDeltaReaderState.DeltaResourceSetEnd; + case ODataReaderState.ResourceStart: + return ODataDeltaReaderState.DeltaResourceStart; + case ODataReaderState.ResourceEnd: + return ODataDeltaReaderState.DeltaResourceEnd; + case ODataReaderState.DeletedResourceEnd: + return ODataDeltaReaderState.DeltaDeletedEntry; + case ODataReaderState.DeltaDeletedLink: + return ODataDeltaReaderState.DeltaDeletedLink; + case ODataReaderState.DeltaLink: + return ODataDeltaReaderState.DeltaLink; + case ODataReaderState.Completed: + return ODataDeltaReaderState.Completed; + + // These states are for reading nested resources + case ODataReaderState.EntityReferenceLink: + case ODataReaderState.Primitive: + case ODataReaderState.DeletedResourceStart: + case ODataReaderState.NestedResourceInfoStart: + case ODataReaderState.ResourceSetStart: + case ODataReaderState.ResourceSetEnd: + default: + Debug.Assert(true, "unexpected ReaderState for legacy reader"); + return ODataDeltaReaderState.NestedResource; + } + } + } + + /// Gets the current sub state of the reader. + /// The current sub state of the reader. + /// + /// The sub state is a complement to the current state if the current state itself is not enough to determine + /// the real state of the reader. The sub state is only meaningful in NestedResourceInfo state. + /// + public override ODataReaderState SubState + { + get + { + // Match legacy behavior + if (this.nestedLevel == 1 && this.underlyingReader.State == ODataReaderState.NestedResourceInfoStart) + { + return ODataReaderState.Start; + } + + if (this.nestedLevel == 0 && this.underlyingReader.State == ODataReaderState.NestedResourceInfoEnd) + { + return ODataReaderState.Completed; + } + + return this.nestedLevel > 0 ? + this.underlyingReader.State : ODataReaderState.Start; + } + } + + /// Gets the most recent that has been read. + /// The most recent that has been read. + public override ODataItem Item + { + get + { + ODataDeletedResource deletedResource = this.underlyingReader.Item as ODataDeletedResource; + if (deletedResource != null) + { + return ODataDeltaDeletedEntry.GetDeltaDeletedEntry(deletedResource); + } + else + { + return this.underlyingReader.Item; + } + } + } + + #endregion + + #region Public Methods + + /// Reads the next from the message payload. + /// true if more items were read; otherwise false. + public override bool Read() + { + // Translate state transitions to legacy states + bool response = this.underlyingReader.Read(); + if (this.underlyingReader.State == ODataReaderState.DeletedResourceStart) + { + while ((response = this.underlyingReader.Read()) && this.underlyingReader.State != ODataReaderState.DeletedResourceEnd) + { + // skip to end of a deleted resource + this.SetNestedLevel(); + } + } + + this.SetNestedLevel(); + + return response; + } + +#if PORTABLELIB + /// Asynchronously reads the next from the message payload. + /// A task that when completed indicates whether more items were read. + public override Task ReadAsync() + { + return this.underlyingReader.ReadAsync().FollowOnSuccessWith(t => + { + if (this.underlyingReader.State == ODataReaderState.DeletedResourceStart) + { + this.SkipToDeletedResourceEnd(); + } + + this.SetNestedLevel(); + return t.Result; + }); + } +#endif +#endregion + +#region Private Methods + +#if PORTABLELIB + /// Sets nested level following a successful read. + private async void SkipToDeletedResourceEnd() + { + if (this.underlyingReader.State != ODataReaderState.DeletedResourceEnd) + { + await this.underlyingReader.ReadAsync().FollowOnSuccessWith(t => + { + this.SkipToDeletedResourceEnd(); + }); + } + } +#endif + + /// Sets nested level following a successful read. + private void SetNestedLevel() + { + if (this.underlyingReader.State == ODataReaderState.NestedResourceInfoStart) + { + this.nestedLevel++; + } + else if (this.underlyingReader.State == ODataReaderState.NestedResourceInfoEnd) + { + this.nestedLevel--; + } + } + +#endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaWriter.cs new file mode 100644 index 0000000..3dcf231 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeltaWriter.cs @@ -0,0 +1,312 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Implementation of the ODataDeltaWriter for the JsonLight format. + /// + internal sealed class ODataJsonLightDeltaWriter : ODataDeltaWriter, IODataOutputInStreamErrorListener + { + #region Private Fields + + /// + /// The output context to write to. + /// + private readonly ODataJsonLightOutputContext jsonLightOutputContext; + + /// + /// JsonLightWriter + /// + private readonly ODataJsonLightWriter resourceWriter; + + /// + /// NavigationSource + /// + private IEdmNavigationSource navigationSource; + + /// + /// EntityType + /// + private IEdmEntityType entityType; + + /// An in-stream error listener to notify when in-stream error is to be written. Or null if we don't need to notify anybody. + private IODataOutputInStreamErrorListener inStreamErrorListener; + + #endregion + + #region Constructor + + /// + /// Constructor. + /// + /// The output context to write to. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + public ODataJsonLightDeltaWriter(ODataJsonLightOutputContext jsonLightOutputContext, IEdmNavigationSource navigationSource, IEdmEntityType entityType) + { + Debug.Assert(jsonLightOutputContext != null, "jsonLightOutputContext != null"); + + this.navigationSource = navigationSource; + this.entityType = entityType; + this.jsonLightOutputContext = jsonLightOutputContext; + this.resourceWriter = new ODataJsonLightWriter(jsonLightOutputContext, navigationSource, entityType, true, writingDelta: true); + this.inStreamErrorListener = resourceWriter; + } + + #endregion + + #region Public Properties + + /// + /// The navigation source we are going to write entities for. + /// + public IEdmNavigationSource NavigationSource + { + get + { + return this.navigationSource; + } + + set + { + Debug.Assert(true, "NavigationSource can be set but is never used"); + this.navigationSource = value; + } + } + + /// + /// The entity type we are going to write entities for. + /// + public IEdmEntityType EntityType + { + get + { + return this.entityType; + } + + set + { + Debug.Assert(true, "EntityType can be set but is never used"); + this.entityType = value; + } + } + + #endregion + + #region Public API + + /// + /// Start writing a delta resource set. + /// + /// Delta resource set/collection to write. + public override void WriteStart(ODataDeltaResourceSet deltaResourceSet) + { + this.resourceWriter.WriteStart(deltaResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a delta resource set. + /// + /// Delta resource set/collection to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteStart(deltaResourceSet)); + } +#endif + + /// + /// Finish writing a delta resource set. + /// + public override void WriteEnd() + { + this.resourceWriter.WriteEnd(); + } + +#if PORTABLELIB + /// + /// Asynchronously finish writing a delta resource set. + /// + /// A task instance that represents the asynchronous write operation. + public override Task WriteEndAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteEnd()); + } +#endif + + /// + /// Start writing a nested resource info. + /// + /// The nested resource info to write. + public override void WriteStart(ODataNestedResourceInfo nestedResourceInfo) + { + this.resourceWriter.WriteStart(nestedResourceInfo); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a nested resource info. + /// + /// The nested resource info to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteStart(nestedResourceInfo)); + } +#endif + + /// + /// Start writing an expanded resource set. + /// + /// The expanded resource set to write. + public override void WriteStart(ODataResourceSet expandedResourceSet) + { + this.resourceWriter.WriteStart(expandedResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing an expanded resource set. + /// + /// The expanded resource set to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteStartAsync(ODataResourceSet expandedResourceSet) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteStart(expandedResourceSet)); + } +#endif + + /// + /// Start writing a delta resource. + /// + /// The delta resource to write. + public override void WriteStart(ODataResource deltaResource) + { + this.resourceWriter.WriteStart(deltaResource); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a delta resource. + /// + /// The delta resource to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteStartAsync(ODataResource deltaResource) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteStart(deltaResource)); + } +#endif + + /// + /// Writing a delta deleted resource. + /// + /// The delta deleted resource to write. + public override void WriteDeltaDeletedEntry(ODataDeltaDeletedEntry deltaDeletedEntry) + { + this.resourceWriter.WriteStart(ODataDeltaDeletedEntry.GetDeletedResource(deltaDeletedEntry)); + this.resourceWriter.WriteEnd(); + } + +#if PORTABLELIB + /// + /// Asynchronously writing a delta deleted resource. + /// + /// The delta deleted resource to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteDeltaDeletedEntryAsync(ODataDeltaDeletedEntry deltaDeletedEntry) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteStart(ODataDeltaDeletedEntry.GetDeletedResource(deltaDeletedEntry))); + } +#endif + + /// + /// Writes a delta link. + /// + /// The delta link to write. + public override void WriteDeltaLink(ODataDeltaLink deltaLink) + { + this.resourceWriter.WriteDeltaLink(deltaLink); + } + +#if PORTABLELIB + /// + /// Asynchronously writes a delta link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteDeltaLink(deltaLink)); + } +#endif + + /// + /// Writing a delta deleted link. + /// + /// The delta deleted link to write. + public override void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaDeletedLink) + { + this.resourceWriter.WriteDeltaDeletedLink(deltaDeletedLink); + } + +#if PORTABLELIB + /// + /// Asynchronously writing a delta deleted link. + /// + /// The delta deleted link to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaDeletedLink) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.resourceWriter.WriteDeltaDeletedLink(deltaDeletedLink)); + } +#endif + + /// + /// Flushes the write buffer to the underlying stream. + /// + public override void Flush() + { + this.jsonLightOutputContext.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously flushes the write buffer to the underlying stream. + /// + /// A task instance that represents the asynchronous operation. + public override Task FlushAsync() + { + return this.jsonLightOutputContext.FlushAsync(); + } +#endif + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + void IODataOutputInStreamErrorListener.OnInStreamError() + { + this.inStreamErrorListener.OnInStreamError(); + } + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs new file mode 100644 index 0000000..25f05ed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs @@ -0,0 +1,917 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData; + using Microsoft.OData.Evaluation; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Base class for all OData JsonLight deserializers. + /// + internal abstract class ODataJsonLightDeserializer : ODataDeserializer + { + /// The JsonLight input context to use for reading. + private readonly ODataJsonLightInputContext jsonLightInputContext; + + /// Context for resource metadata centric responsibilities. + private IODataMetadataContext metadataContext; + + /// Result of parsing the context URI for the payload (or null if none are available). + /// This field is only available after the ReadPayloadStart was called. + private ODataJsonLightContextUriParseResult contextUriParseResult; + + /// The OData Uri. + private ODataUri odataUri; + + /// True if the odata uri is calculated, false otherwise. + private bool isODataUriRead; + +#if DEBUG + /// + /// Set to true when ReadPayloadStart has been called and we would have read odata.context if it's on the payload. + /// This is a debug check to make sure we don't access contextUriParseResult until after ReadPayloadStart is called. + /// + private bool contextUriParseResultReady; +#endif + + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + protected ODataJsonLightDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + Debug.Assert(jsonLightInputContext != null, "jsonLightInputContext != null"); + + this.jsonLightInputContext = jsonLightInputContext; + } + + /// Possible results of parsing JSON object property. + internal enum PropertyParsingResult + { + /// An end of object was reached without any property to be reported. + EndOfObject, + + /// A property with value was found. + PropertyWithValue, + + /// A property without value was found. + PropertyWithoutValue, + + /// A 'odata' instance annotation was found. + ODataInstanceAnnotation, + + /// A custom instance annotation was found. + CustomInstanceAnnotation, + + /// A metadata reference property was found. + MetadataReferenceProperty, + + /// A property representing a nested delta resoruce set was found. + NestedDeltaResourceSet, + } + + /// + /// Gets the OData uri. + /// + internal ODataUri ODataUri + { + get + { + if (this.isODataUriRead) + { + return this.odataUri; + } + + if (this.ContextUriParseResult == null || + this.ContextUriParseResult.NavigationSource == null || + this.ContextUriParseResult.NavigationSource is IEdmContainedEntitySet == false || + this.contextUriParseResult.Path == null) + { + this.isODataUriRead = true; + return this.odataUri = null; + } + + this.odataUri = new ODataUri { Path = this.ContextUriParseResult.Path }; + + this.isODataUriRead = true; + return this.odataUri; + } + } + + /// + /// Context for resource metadata centric responsibilities. + /// + internal IODataMetadataContext MetadataContext + { + get + { + // Under the client knob, the model we're given is really a facade, which doesn't flow open-type information into the combined types. + // However, we need to answer this question for materializing operations which were left out of the payload. + // To get around this, the client sets a callback in the ReaderBehavior to answer the question. + return this.metadataContext ?? (this.metadataContext = new ODataMetadataContext( + this.ReadingResponse, + null, + this.JsonLightInputContext.EdmTypeResolver, + this.Model, + this.MetadataDocumentUri, + this.ODataUri, + this.JsonLightInputContext.MetadataLevel)); + } + } + + /// + /// Returns the which is to be used to read the content of the message. + /// + internal BufferingJsonReader JsonReader + { + get + { + return this.jsonLightInputContext.JsonReader; + } + } + + /// Result of parsing the context URI for the payload (or null if none are available). + /// This property is only available after the ReadPayloadStart was called. + internal ODataJsonLightContextUriParseResult ContextUriParseResult + { + get + { +#if DEBUG + Debug.Assert(this.contextUriParseResultReady, "The contextUriParseResult property should not be accessed before ReadPayloadStart() is called."); +#endif + return this.contextUriParseResult; + } + } + + /// + /// The Json lite input context to use for reading. + /// + internal ODataJsonLightInputContext JsonLightInputContext + { + get { return this.jsonLightInputContext; } + } + + /// + /// Function called to read property custom annotation value. + /// + protected Func ReadPropertyCustomAnnotationValue + { + get; + set; + } + + /// + /// Gets the metadata document Uri from the contextUriParseResult. + /// + private Uri MetadataDocumentUri + { + get + { + Uri metadataDocumentUri = this.ContextUriParseResult != null && this.ContextUriParseResult.MetadataDocumentUri != null ? this.ContextUriParseResult.MetadataDocumentUri : null; + Debug.Assert(metadataDocumentUri == null || metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri == null || metadataDocumentUri.IsAbsoluteUri"); + return metadataDocumentUri; + } + } + + /// + /// Parses the name of a property and returns the property name and annotation name if the property is a property annotation. + /// + /// The property name to parse. + /// The name of the annotated property, or null if the property is not a property annotation. + /// The annotation name, or null if the property is not a property annotation. + /// true if the is a property annotation, false otherwise. + internal static bool TryParsePropertyAnnotation(string propertyAnnotationName, out string propertyName, out string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyAnnotationName), "!string.IsNullOrEmpty(propertyAnnotationName)"); + + int propertyAnnotationSeparatorIndex = propertyAnnotationName.IndexOf(JsonLightConstants.ODataPropertyAnnotationSeparatorChar); + if (propertyAnnotationSeparatorIndex <= 0 || propertyAnnotationSeparatorIndex == propertyAnnotationName.Length - 1) + { + propertyName = null; + annotationName = null; + return false; + } + + propertyName = propertyAnnotationName.Substring(0, propertyAnnotationSeparatorIndex); + annotationName = propertyAnnotationName.Substring(propertyAnnotationSeparatorIndex + 1); + return true; + } + + /// + /// Read the start of the top-level data wrapper in JSON responses. + /// + /// The kind of payload we are reading; this guides the parsing of the context URI. + /// The duplicate property names checker. + /// true if we are deserializing a nested payload, e.g. a resource, a resource set or a collection within a parameters payload. + /// true if we allow a completely empty payload; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: The reader is positioned on the first property of the payload after having read (or skipped) the context URI property. + /// Or the reader is positioned on an end-object node if there are no properties (other than the context URI which is required in responses and optional in requests). + /// + internal void ReadPayloadStart( + ODataPayloadKind payloadKind, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + bool isReadingNestedPayload, + bool allowEmptyPayload) + { + this.JsonReader.AssertNotBuffering(); + Debug.Assert(isReadingNestedPayload || this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: JSON reader must not have been used yet when not reading a nested payload."); + + string contextUriAnnotationValue = this.ReadPayloadStartImplementation( + payloadKind, + propertyAndAnnotationCollector, + isReadingNestedPayload, + allowEmptyPayload); + + ODataJsonLightContextUriParseResult parseResult = null; + + // The context URI is only recognized in non-error response top-level payloads. + // If the payload is nested (for example when we read URL literals) we don't recognize the context URI. + // Top-level error payloads don't need and use the context URI. + if (!isReadingNestedPayload && payloadKind != ODataPayloadKind.Error && contextUriAnnotationValue != null) + { + parseResult = ODataJsonLightContextUriParser.Parse( + this.Model, + contextUriAnnotationValue, + payloadKind, + this.MessageReaderSettings.ClientCustomTypeResolver, + this.JsonLightInputContext.ReadingResponse || payloadKind == ODataPayloadKind.Delta, + this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata); + } + + this.contextUriParseResult = parseResult; +#if DEBUG + this.contextUriParseResultReady = true; +#endif + } + +#if PORTABLELIB + + /// + /// Read the start of the top-level data wrapper in JSON responses. + /// + /// The kind of payload we are reading; this guides the parsing of the context URI. + /// The duplicate property names checker. + /// true if we are deserializing a nested payload, e.g. a resource, a resource set or a collection within a parameters payload. + /// true if we allow a completely empty payload; otherwise false. + /// The parsed context URI. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: The reader is positioned on the first property of the payload after having read (or skipped) the context URI property. + /// Or the reader is positioned on an end-object node if there are no properties (other than the context URI which is required in responses and optional in requests). + /// + internal Task ReadPayloadStartAsync( + ODataPayloadKind payloadKind, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + bool isReadingNestedPayload, + bool allowEmptyPayload) + { + this.JsonReader.AssertNotBuffering(); + Debug.Assert(isReadingNestedPayload || this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: JSON reader must not have been used yet when not reading a nested payload."); + + return TaskUtils.GetTaskForSynchronousOperation(() => + { + string contextUriAnnotationValue = this.ReadPayloadStartImplementation( + payloadKind, + propertyAndAnnotationCollector, + isReadingNestedPayload, + allowEmptyPayload); + + // The context URI is only recognized in non-error response top-level payloads. + // If the payload is nested (for example when we read URL literals) we don't recognize the context URI. + // Top-level error payloads don't need and use the context URI. + if (!isReadingNestedPayload && payloadKind != ODataPayloadKind.Error && contextUriAnnotationValue != null) + { + this.contextUriParseResult = ODataJsonLightContextUriParser.Parse( + this.Model, + contextUriAnnotationValue, + payloadKind, + this.MessageReaderSettings.ClientCustomTypeResolver, + this.JsonLightInputContext.ReadingResponse); + } + +#if DEBUG + this.contextUriParseResultReady = true; +#endif + }); + } +#endif + + /// + /// Reads the end of the top-level data wrapper in JSON responses. + /// + /// true if we are deserializing a nested payload, e.g. a resource, a resource set or a collection within a parameters payload. + /// + /// Pre-Condition: any node: when reading response or a nested payload, will fail if find anything else then EndObject. + /// JsonNodeType.EndOfInput: otherwise + /// Post-Condition: JsonNodeType.EndOfInput + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "isReadingNestedPayload", Justification = "The parameter is used in debug builds.")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Needs to access this in debug only.")] + internal void ReadPayloadEnd(bool isReadingNestedPayload) + { + Debug.Assert( + isReadingNestedPayload || this.JsonReader.NodeType == JsonNodeType.EndOfInput, + "Pre-Condition: JsonNodeType.EndOfInput if not reading a nested payload."); + this.JsonReader.AssertNotBuffering(); + } + + /// + /// Reads and validates a string value from the json reader. + /// + /// The name of the annotation being read (used for error reporting). + /// The string that was read. + internal string ReadAndValidateAnnotationStringValue(string annotationName) + { + string valueRead = this.JsonReader.ReadStringValue(annotationName); + ODataJsonLightReaderUtils.ValidateAnnotationValue(valueRead, annotationName); + return valueRead; + } + + /// + /// Reads a string value from the json reader. + /// + /// The name of the annotation being read (used for error reporting). + /// The string that was read. + internal string ReadAnnotationStringValue(string annotationName) + { + return this.JsonReader.ReadStringValue(annotationName); + } + + /// + /// Reads a string value from the json reader and processes it as a Uri. + /// + /// The name of the annotation being read (used for error reporting). + /// The Uri that was read. + internal Uri ReadAnnotationStringValueAsUri(string annotationName) + { + string stringValue = this.JsonReader.ReadStringValue(annotationName); + + if (stringValue == null) + { + return null; + } + + return this.ReadingResponse ? this.ProcessUriFromPayload(stringValue) : new Uri(stringValue, UriKind.RelativeOrAbsolute); + } + + /// + /// Reads and validates a string value from the json reader and processes it as a Uri. + /// + /// The name of the annotation being read (used for error reporting). + /// The Uri that was read. + internal Uri ReadAndValidateAnnotationStringValueAsUri(string annotationName) + { + string stringValue = this.ReadAndValidateAnnotationStringValue(annotationName); + return this.ReadingResponse ? this.ProcessUriFromPayload(stringValue) : new Uri(stringValue, UriKind.RelativeOrAbsolute); + } + + /// + /// Reads and validates a value from the json reader and processes it as a long. + /// The input value could be string or number + /// + /// The name of the annotation being read. + /// The long that is read. + internal long ReadAndValidateAnnotationAsLongForIeee754Compatible(string annotationName) + { + object value = this.JsonReader.ReadPrimitiveValue(); + + ODataJsonLightReaderUtils.ValidateAnnotationValue(value, annotationName); + + if ((value is string) ^ this.JsonReader.IsIeee754Compatible) + { + throw new ODataException(Strings.ODataJsonReaderUtils_ConflictBetweenInputFormatAndParameter(Metadata.EdmConstants.EdmInt64TypeName)); + } + + return (long)ODataJsonLightReaderUtils.ConvertValue( + value, + EdmCoreModel.Instance.GetInt64(false), + this.MessageReaderSettings, + /*validateNullValue*/ true, + annotationName, + this.JsonLightInputContext.PayloadValueConverter); + } + + /// + /// Given a URI from the payload, this method will try to make it absolute, or fail otherwise. + /// + /// The URI string from the payload to process. + /// An absolute URI to report. + internal Uri ProcessUriFromPayload(string uriFromPayload) + { + Debug.Assert(uriFromPayload != null, "uriFromPayload != null"); + + Uri uri = new Uri(uriFromPayload, UriKind.RelativeOrAbsolute); + + // Try to resolve the URI using a custom URL resolver first. + Uri metadataDocumentUri = this.MetadataDocumentUri; + Uri resolvedUri = this.JsonLightInputContext.ResolveUri(metadataDocumentUri, uri); + if (resolvedUri != null) + { + return resolvedUri; + } + + if (!uri.IsAbsoluteUri) + { + if (metadataDocumentUri == null) + { + throw new ODataException(Strings.ODataJsonLightDeserializer_RelativeUriUsedWithouODataMetadataAnnotation(uriFromPayload, ODataAnnotationNames.ODataContext)); + } + + uri = UriUtils.UriToAbsoluteUri(metadataDocumentUri, uri); + } + + Debug.Assert(uri.IsAbsoluteUri, "By now we should have an absolute URI."); + return uri; + } + + /// + /// Parses JSON object property starting with the current position of the JSON reader. + /// + /// The duplicate property names checker to use, it will also store the property annotations found. + /// Function called to read property annotation value. + /// Function callback to handle the result of parsing property. + internal void ProcessProperty( + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + Func readPropertyAnnotationValue, + Action handleProperty) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + Debug.Assert(readPropertyAnnotationValue != null, "readPropertyAnnotationValue != null"); + Debug.Assert(handleProperty != null, "handleProperty != null"); + this.AssertJsonCondition(JsonNodeType.Property); + + string propertyName; + PropertyParsingResult propertyParsingResult = this.ParseProperty( + propertyAndAnnotationCollector, readPropertyAnnotationValue, out propertyName); + + while (propertyParsingResult == PropertyParsingResult.CustomInstanceAnnotation && this.ShouldSkipCustomInstanceAnnotation(propertyName)) + { + // Read over the property name + this.JsonReader.Read(); + + // Skip over the instance annotation value and don't report it to the OM. + this.JsonReader.SkipValue(); + propertyParsingResult = this.ParseProperty( + propertyAndAnnotationCollector, readPropertyAnnotationValue, out propertyName); + } + + handleProperty(propertyParsingResult, propertyName); + if (propertyParsingResult != PropertyParsingResult.EndOfObject + && propertyParsingResult != PropertyParsingResult.CustomInstanceAnnotation) + { + propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName); + } + } + + /// + /// Asserts that the JSON reader is positioned on one of the specified node types. + /// + /// The node types which should appear at this point. + [Conditional("DEBUG")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Needs access to this in Debug only.")] + internal void AssertJsonCondition(params JsonNodeType[] allowedNodeTypes) + { +#if DEBUG + if (allowedNodeTypes.Contains(this.JsonReader.NodeType)) + { + return; + } + + string message = string.Format( + CultureInfo.InvariantCulture, + "JSON condition failed: the JsonReader is on node {0} (Value: {1}) but it was expected be on {2}.", + this.JsonReader.NodeType.ToString(), + this.JsonReader.Value, + string.Join(",", allowedNodeTypes.Select(n => n.ToString()).ToArray())); + Debug.Assert(false, message); +#endif + } + + /// + /// Reads the odata.context annotation. + /// + /// The payload kind for which to read the context URI. + /// The duplicate property names checker. + /// true if the method should fail if the context URI annotation is missing, false if that can be ignored. + /// The value of the context URI annotation. + internal string ReadContextUriAnnotation( + ODataPayloadKind payloadKind, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + bool failOnMissingContextUriAnnotation) + { + if (this.JsonReader.NodeType != JsonNodeType.Property) + { + if (!failOnMissingContextUriAnnotation || payloadKind == ODataPayloadKind.Unsupported) + { + // Do not fail during payload kind detection + return null; + } + + throw new ODataException(Strings.ODataJsonLightDeserializer_ContextLinkNotFoundAsFirstProperty); + } + + // Must make sure the input odata.context has a '@' prefix + string propertyName = this.JsonReader.GetPropertyName(); + if (string.CompareOrdinal(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + ODataAnnotationNames.ODataContext, propertyName) != 0 + && !this.CompareSimplifiedODataAnnotation(JsonLightConstants.SimplifiedODataContextPropertyName, propertyName)) + { + if (!failOnMissingContextUriAnnotation || payloadKind == ODataPayloadKind.Unsupported) + { + // Do not fail during payload kind detection + return null; + } + + throw new ODataException(Strings.ODataJsonLightDeserializer_ContextLinkNotFoundAsFirstProperty); + } + + if (propertyAndAnnotationCollector != null) + { + propertyAndAnnotationCollector.MarkPropertyAsProcessed(propertyName); + } + + // Read over the property name + this.JsonReader.ReadNext(); + return this.JsonReader.ReadStringValue(); + } + + /// + /// Compares the JSON property name with the simplified OData annotation property name. + /// + /// The simplified OData annotation property name. + /// The JSON property name read from the payload. + /// If the JSON property name equals the simplified OData annotation property name. + protected bool CompareSimplifiedODataAnnotation(string simplifiedPropertyName, string propertyName) + { + Debug.Assert(simplifiedPropertyName.IndexOf('@') == 0, "simplifiedPropertyName must start with '@'."); + Debug.Assert(simplifiedPropertyName.IndexOf('.') == -1, "simplifiedPropertyName must not be namespace-qualified."); + + return this.JsonLightInputContext.OptionalODataPrefix && + string.CompareOrdinal(simplifiedPropertyName, propertyName) == 0; + } + + /// + /// Completes the simplified OData annotation name with "odata.". + /// + /// The annotation name to be completed. + /// The complete OData annotation name. + protected string CompleteSimplifiedODataAnnotation(string annotationName) + { + if (this.JsonLightInputContext.OptionalODataPrefix && + annotationName.IndexOf('.') == -1) + { + annotationName = JsonLightConstants.ODataAnnotationNamespacePrefix + annotationName; + } + + return annotationName; + } + + /// + /// Returns true if should be skipped by the reader; false otherwise. + /// + /// The custom instance annotation name in question. + /// Returns true if should be skipped by the reader; false otherwise. + private bool ShouldSkipCustomInstanceAnnotation(string annotationName) + { + // By default we always reading custom instance annotation on error payloads. + if (this is ODataJsonLightErrorDeserializer && this.MessageReaderSettings.ShouldIncludeAnnotation == null) + { + return false; + } + + // If readerSettings.AnnotationFilter is not set, readerSettings.ShouldSkipCustomInstanceAnnotation() will always return true, which is + // the default we want for non-error payloads. + // If the readerSettings.AnnotationFilter is set, readerSettings.ShouldSkipCustomInstanceAnnotation() will evaluate the annotationName based + // of the filter. This should override the default behavior for both error and non-error payloads. + return this.MessageReaderSettings.ShouldSkipAnnotation(annotationName); + } + + /// + /// Test the instance annotation is start with @ prefix + /// + /// the origin annotation name from reader + /// true is the instance annotation, false is not + private static bool IsInstanceAnnotation(string annotationName) + { + if (!String.IsNullOrEmpty(annotationName) && annotationName[0] == JsonLightConstants.ODataPropertyAnnotationSeparatorChar) + { + return true; + } + + return false; + } + + /// + /// If is under the odata namespace but is not known to ODataLib, move the JSON reader forward to skip the + /// annotation name and value then return true; return false otherwise. + /// + /// + /// The unknown odata annotation is skipped so that when this version of the reader reads a resource set produced by a future version of ODataLib + /// that contains an odata annotation that is not recognized on this version, we would simply ignore the annotation rather than failing. + /// Note that when we add new odata annotations that cannot be skipped, we would bump the protocol version. + /// + /// The annotation name in question. + /// Outputs the . + /// Returns true if the annotation name and value is skipped; returns false otherwise. + private bool SkippedOverUnknownODataAnnotation(string annotationName, out object annotationValue) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + this.AssertJsonCondition(JsonNodeType.Property); + + if (ODataAnnotationNames.IsUnknownODataAnnotationName(annotationName)) + { + annotationValue = ReadODataOrCustomInstanceAnnotationValue(annotationName); + return true; + } + + annotationValue = null; + return false; + } + + /// + /// Reads "odata." or custom instance annotation's value. + /// + /// The annotation name. + /// The annotation value. + private object ReadODataOrCustomInstanceAnnotationValue(string annotationName) + { + // Read over the name. + this.JsonReader.Read(); + object annotationValue; + if (this.JsonReader.NodeType != JsonNodeType.PrimitiveValue) + { + annotationValue = this.JsonReader.ReadAsUntypedOrNullValue(); + } + else + { + annotationValue = this.JsonReader.Value; + this.JsonReader.SkipValue(); + } + + return annotationValue; + } + + /// + /// Parses JSON object property starting with the current position of the JSON reader. + /// + /// The duplicate property names checker to use, it will also store the property annotations found. + /// Function called to read property annotation value. + /// The name of the property or instance annotation found. + /// + /// PropertyWithValue - a property with value was found. The contains the name of the property. + /// The reader is positioned on the property value. + /// PropertyWithoutValue - a property without a value was found. The contains the name of the property. + /// The reader is positioned on the node after property annotations (so either a property or end of object). + /// ODataInstanceAnnotation - an odata instance annotation was found. The contains the name of the annotation. + /// The reader is positioned on the value of the annotation. + /// CustomInstanceAnnotation - a custom instance annotation was found. The contains the name of the annotation. + /// The reader is positioned on the value of the annotation. + /// MetadataReferenceProperty - a property which is a reference into the metadata was found. + /// The reader is positioned on the value of the property. + /// EndOfObject - end of the object scope was reached and no properties are to be reported. The is null. + /// This can only happen if there's a property annotation which is ignored (for example custom one) at the end of the object. + /// + private PropertyParsingResult ParseProperty( + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + Func readPropertyAnnotationValue, + out string parsedPropertyName) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + Debug.Assert(readPropertyAnnotationValue != null, "readPropertyAnnotationValue != null"); + string lastPropertyAnnotationNameFound = null; + parsedPropertyName = null; + + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + string nameFromReader = this.JsonReader.GetPropertyName(); + + string propertyNameFromReader, annotationNameFromReader; + bool isPropertyAnnotation = TryParsePropertyAnnotation(nameFromReader, out propertyNameFromReader, out annotationNameFromReader); + + // reading a nested delta resource set + if (isPropertyAnnotation && String.CompareOrdinal(this.CompleteSimplifiedODataAnnotation(annotationNameFromReader), ODataAnnotationNames.ODataDelta) == 0) + { + // Read over the property name. + this.JsonReader.Read(); + parsedPropertyName = propertyNameFromReader; + return PropertyParsingResult.NestedDeltaResourceSet; + } + + bool isInstanceAnnotation = false; + if (!isPropertyAnnotation) + { + isInstanceAnnotation = IsInstanceAnnotation(nameFromReader); + propertyNameFromReader = isInstanceAnnotation ? this.CompleteSimplifiedODataAnnotation(nameFromReader.Substring(1)) : nameFromReader; + } + + // If parsedPropertyName is set and is different from the property name the reader is currently on, + // we have parsed a property annotation for a different property than the one at the current position. + if (parsedPropertyName != null && string.CompareOrdinal(parsedPropertyName, propertyNameFromReader) != 0) + { + if (ODataJsonLightReaderUtils.IsAnnotationProperty(parsedPropertyName)) + { + throw new ODataException(Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName)); + } + + return PropertyParsingResult.PropertyWithoutValue; + } + + object annotationValue = null; + if (isPropertyAnnotation) + { + annotationNameFromReader = this.CompleteSimplifiedODataAnnotation(annotationNameFromReader); + + // If this is a unknown odata annotation targeting a property, we skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explaination. + // Note that we don't skip over unknown odata annotations targeting another annotation. We don't allow annotations (except odata.type) targeting other annotations, + // so this.ProcessPropertyAnnotation() will test and fail for that case. + if (!ODataJsonLightReaderUtils.IsAnnotationProperty(propertyNameFromReader) && this.SkippedOverUnknownODataAnnotation(annotationNameFromReader, out annotationValue)) + { + propertyAndAnnotationCollector.AddODataPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, annotationValue); + continue; + } + + // We have another property annotation for the same property we parsed. + parsedPropertyName = propertyNameFromReader; + lastPropertyAnnotationNameFound = annotationNameFromReader; + + this.ProcessPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, propertyAndAnnotationCollector, readPropertyAnnotationValue); + continue; + } + + // If this is a unknown odata annotation, skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explaination. + if (isInstanceAnnotation && this.SkippedOverUnknownODataAnnotation(propertyNameFromReader, out annotationValue)) + { + // collect 'odata.' annotation: + // here we know the original property name contains no '@', but '.' dot + Debug.Assert(annotationNameFromReader == null, "annotationNameFromReader == null"); + propertyAndAnnotationCollector.AddODataScopeAnnotation(propertyNameFromReader, annotationValue); + continue; + } + + // We are encountering the property name for the first time. + // Don't read over property name, as that would cause the buffering reader to read ahead in the StartObject + // state, which would break our ability to stream inline json. Instead, callers of ParseProperty will have to + // call this.JsonReader.Read() as appropriate to read past the property name. + parsedPropertyName = propertyNameFromReader; + + if (!isInstanceAnnotation && ODataJsonLightUtils.IsMetadataReferenceProperty(propertyNameFromReader)) + { + return PropertyParsingResult.MetadataReferenceProperty; + } + + if (!isInstanceAnnotation && !ODataJsonLightReaderUtils.IsAnnotationProperty(propertyNameFromReader)) + { + // Normal property + return PropertyParsingResult.PropertyWithValue; + } + + // collect 'xxx.yyyy' annotation: + // here we know the original property name contains no '@', but '.' dot + Debug.Assert(annotationNameFromReader == null, "annotationNameFromReader == null"); + + // Handle 'odata.XXXXX' annotations + if (isInstanceAnnotation && ODataJsonLightReaderUtils.IsODataAnnotationName(propertyNameFromReader)) + { + return PropertyParsingResult.ODataInstanceAnnotation; + } + + // Handle custom annotations + return PropertyParsingResult.CustomInstanceAnnotation; + } + + this.AssertJsonCondition(JsonNodeType.EndObject); + if (parsedPropertyName != null) + { + if (ODataJsonLightReaderUtils.IsAnnotationProperty(parsedPropertyName)) + { + throw new ODataException(Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName)); + } + + return PropertyParsingResult.PropertyWithoutValue; + } + + return PropertyParsingResult.EndOfObject; + } + + /// + /// Process the current property annotation. + /// + /// The name being annotated. Can be a property or an instance annotation. + /// The annotation targeting the . + /// The duplicate property names checker. + /// Callback to read the property annotation value. + private void ProcessPropertyAnnotation(string annotatedPropertyName, string annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func readPropertyAnnotationValue) + { + // We don't currently support annotation targeting an instance annotation except for the @odata.type property annotation. + if (ODataJsonLightReaderUtils.IsAnnotationProperty(annotatedPropertyName) && string.CompareOrdinal(annotationName, ODataAnnotationNames.ODataType) != 0) + { + throw new ODataException(Strings.ODataJsonLightDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation(annotationName, annotatedPropertyName, ODataAnnotationNames.ODataType)); + } + + ReadODataOrCustomInstanceAnnotationValue(annotatedPropertyName, annotationName, propertyAndAnnotationCollector, readPropertyAnnotationValue); + } + + /// + /// Reads built-in "odata." or custom instance annotation's value. + /// + /// The property name. + /// The annotation name + /// The PropertyAndAnnotationCollector. + /// Callback to read the property annotation value. + private void ReadODataOrCustomInstanceAnnotationValue(string annotatedPropertyName, string annotationName, PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func readPropertyAnnotationValue) + { + // Read over the property name. + this.JsonReader.Read(); + if (ODataJsonLightReaderUtils.IsODataAnnotationName(annotationName)) + { + // OData annotations are read + propertyAndAnnotationCollector.AddODataPropertyAnnotation(annotatedPropertyName, annotationName, readPropertyAnnotationValue(annotationName)); + } + else + { + if (this.ShouldSkipCustomInstanceAnnotation(annotationName) || (this is ODataJsonLightErrorDeserializer && this.MessageReaderSettings.ShouldIncludeAnnotation == null)) + { + propertyAndAnnotationCollector.CheckIfPropertyOpenForAnnotations(annotatedPropertyName, annotationName); + this.JsonReader.SkipValue(); + } + else + { + Debug.Assert(ReadPropertyCustomAnnotationValue != null, "readPropertyCustomAnnotationValue != null"); + propertyAndAnnotationCollector.AddCustomPropertyAnnotation(annotatedPropertyName, annotationName, ReadPropertyCustomAnnotationValue(propertyAndAnnotationCollector, annotationName)); + } + } + } + + /// + /// Read the start of the top-level data wrapper in JSON responses. + /// + /// The kind of payload we are reading; this guides the parsing of the context URI. + /// The duplicate property names checker. + /// true if we are deserializing a nested payload, e.g. a resource, a resource set or a collection within a parameters payload. + /// true if we allow a completely empty payload; otherwise false. + /// The value of the context URI annotation (or null if it was not found). + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: The reader is positioned on the first property of the payload after having read (or skipped) the context URI property. + /// Or the reader is positioned on an end-object node if there are no properties (other than the context URI which is required in responses and optional in requests). + /// + private string ReadPayloadStartImplementation( + ODataPayloadKind payloadKind, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + bool isReadingNestedPayload, + bool allowEmptyPayload) + { + this.JsonReader.AssertNotBuffering(); + Debug.Assert(isReadingNestedPayload || this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: JSON reader must not have been used yet when not reading a nested payload."); + + if (!isReadingNestedPayload) + { + // Position the reader on the first node inside the outermost object. + this.JsonReader.Read(); + + if (allowEmptyPayload && this.JsonReader.NodeType == JsonNodeType.EndOfInput) + { + return null; + } + + // Read the start object node and position the reader on the first property + // (or the end object node). + this.JsonReader.ReadStartObject(); + + if (payloadKind != ODataPayloadKind.Error) + { + // Skip over the context URI annotation in request payloads or when we've already read it + // during payload kind detection. + bool failOnMissingContextUriAnnotation = this.jsonLightInputContext.ReadingResponse; + + // In responses we expect the context URI to be the first thing in the payload + // (except for error payloads). In requests we ignore the context URI. + return this.ReadContextUriAnnotation(payloadKind, propertyAndAnnotationCollector, failOnMissingContextUriAnnotation); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkDeserializer.cs new file mode 100644 index 0000000..cdd288b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkDeserializer.cs @@ -0,0 +1,475 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Json; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// OData JsonLight deserializer for entity reference links. + /// + internal sealed class ODataJsonLightEntityReferenceLinkDeserializer : ODataJsonLightPropertyAndValueDeserializer + { + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + internal ODataJsonLightEntityReferenceLinkDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + } + + /// + /// Read a set of top-level entity reference links. + /// + /// An representing the read links. + internal ODataEntityReferenceLinks ReadEntityReferenceLinks() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + this.ReadPayloadStart( + ODataPayloadKind.EntityReferenceLinks, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false); + + ODataEntityReferenceLinks entityReferenceLinks = this.ReadEntityReferenceLinksImplementation(propertyAndAnnotationCollector); + + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return entityReferenceLinks; + } + +#if PORTABLELIB + /// + /// Read a set of top-level entity reference links. + /// + /// A task which returns an representing the read links. + internal Task ReadEntityReferenceLinksAsync() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + return this.ReadPayloadStartAsync( + ODataPayloadKind.EntityReferenceLinks, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false) + + .FollowOnSuccessWith(t => + { + ODataEntityReferenceLinks entityReferenceLinks = this.ReadEntityReferenceLinksImplementation(propertyAndAnnotationCollector); + + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return entityReferenceLinks; + }); + } +#endif + + /// + /// Reads a top-level entity reference link - implementation of the actual functionality. + /// + /// An representing the read entity reference link. + internal ODataEntityReferenceLink ReadEntityReferenceLink() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + this.ReadPayloadStart( + ODataPayloadKind.EntityReferenceLink, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false); + + ODataEntityReferenceLink entityReferenceLink = this.ReadEntityReferenceLinkImplementation(propertyAndAnnotationCollector); + + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return entityReferenceLink; + } + +#if PORTABLELIB + /// + /// Reads a top-level entity reference link - implementation of the actual functionality. + /// + /// A task which returns an representing the read entity reference link. + internal Task ReadEntityReferenceLinkAsync() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + return this.ReadPayloadStartAsync( + ODataPayloadKind.EntityReferenceLink, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false) + + .FollowOnSuccessWith(t => + { + ODataEntityReferenceLink entityReferenceLink = this.ReadEntityReferenceLinkImplementation(propertyAndAnnotationCollector); + + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return entityReferenceLink; + }); + } +#endif + + /// + /// Read a set of top-level entity reference links. + /// + /// The duplicate property names checker to use for the top-level scope. + /// An representing the read links. + private ODataEntityReferenceLinks ReadEntityReferenceLinksImplementation(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + ODataEntityReferenceLinks entityReferenceLinks = new ODataEntityReferenceLinks(); + + this.ReadEntityReferenceLinksAnnotations(entityReferenceLinks, propertyAndAnnotationCollector, /*forLinksStart*/true); + + // Read the start of the content array of the links + this.JsonReader.ReadStartArray(); + + List links = new List(); + PropertyAndAnnotationCollector linkPropertyAndAnnotationCollector = this.JsonLightInputContext.CreatePropertyAndAnnotationCollector(); + + while (this.JsonReader.NodeType != JsonNodeType.EndArray) + { + // read another link + ODataEntityReferenceLink entityReferenceLink = this.ReadSingleEntityReferenceLink(linkPropertyAndAnnotationCollector, /*topLevel*/false); + links.Add(entityReferenceLink); + linkPropertyAndAnnotationCollector.Reset(); + } + + this.JsonReader.ReadEndArray(); + + this.ReadEntityReferenceLinksAnnotations(entityReferenceLinks, propertyAndAnnotationCollector, /*forLinksStart*/false); + + this.JsonReader.ReadEndObject(); + + entityReferenceLinks.Links = new ReadOnlyEnumerable(links); + return entityReferenceLinks; + } + + /// + /// Reads a top-level entity reference link - implementation of the actual functionality. + /// + /// The duplicate property names checker to use for the top-level scope. + /// An representing the read entity reference link. + private ODataEntityReferenceLink ReadEntityReferenceLinkImplementation(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + return this.ReadSingleEntityReferenceLink(propertyAndAnnotationCollector, /*topLevel*/true); + } + + /// + /// Reads the entity reference link instance annotations. + /// + /// The to read the annotations for. + /// The duplicate property names checker for the entity reference links scope. + /// true when parsing the instance annotations before the 'value' property; + /// false when parsing the instance annotations after the 'value' property. + /// + /// Pre-Condition: JsonNodeType.Property The first property in the payload (or the first property after the context URI in responses) + /// JsonNodeType.EndObject The end of the entity reference links object + /// Post-Condition: JsonNodeType.EndObject When the end of the entity reference links object is reached + /// Any The first node of the value of the 'url' property (if found) + /// + private void ReadEntityReferenceLinksAnnotations(ODataEntityReferenceLinks links, PropertyAndAnnotationCollector propertyAndAnnotationCollector, bool forLinksStart) + { + Debug.Assert(links != null, "links != null"); + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + this.JsonReader.AssertNotBuffering(); + + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + // OData property annotations are not supported on entity reference links. + Func propertyAnnotationValueReader = + annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLinks); }; + + bool foundValueProperty = false; + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationValueReader, + (propertyParseResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParseResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + if (string.CompareOrdinal(ODataAnnotationNames.ODataNextLink, propertyName) == 0) + { + this.ReadEntityReferenceLinksNextLinkAnnotationValue(links); + } + else if (string.CompareOrdinal(ODataAnnotationNames.ODataCount, propertyName) == 0) + { + this.ReadEntityReferenceCountAnnotationValue(links); + } + else + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); + } + + break; + + case PropertyParsingResult.CustomInstanceAnnotation: + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.StartArray); + ODataAnnotationNames.ValidateIsCustomAnnotationName(propertyName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(propertyName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(propertyName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + object annotationValue = this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, propertyName); + links.InstanceAnnotations.Add(new ODataInstanceAnnotation(propertyName, annotationValue.ToODataValue())); + break; + + case PropertyParsingResult.PropertyWithValue: + if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) != 0) + { + // We did not find a supported link collection property; fail. + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_InvalidEntityReferenceLinksPropertyFound(propertyName, JsonLightConstants.ODataValuePropertyName)); + } + + // We found the link collection property and are done parsing property annotations; + foundValueProperty = true; + break; + + case PropertyParsingResult.PropertyWithoutValue: + // If we find a property without a value it means that we did not find the entity reference links property (yet) + // but an invalid property annotation + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyAnnotationInEntityReferenceLinks(propertyName)); + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightEntityReferenceLinkDeserializer_ReadEntityReferenceLinksAnnotations)); + } + }); + + if (foundValueProperty) + { + return; + } + } + + if (forLinksStart) + { + // We did not find the 'value' property. + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_ExpectedEntityReferenceLinksPropertyNotFound(JsonLightConstants.ODataValuePropertyName)); + } + + this.AssertJsonCondition(JsonNodeType.EndObject); + } + + /// + /// Reads the odata.nextlink value of an entity reference links nextlink annotation. + /// + /// The entity reference links to read the next link value for; the value of the nextlink will be assigned to this instance. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the instance annotation + /// Post-Condition: JsonNodeType.EndObject The end of the entity reference links object + /// JsonNodeType.Property The next property after the instance annotation + /// + private void ReadEntityReferenceLinksNextLinkAnnotationValue(ODataEntityReferenceLinks links) + { + Debug.Assert(links != null, "links != null"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue); + + Uri nextPageUri = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataNextLink); + Debug.Assert(links.NextPageLink == null, "We should have checked for duplicates already."); + links.NextPageLink = nextPageUri; + } + + /// + /// Reads the value of an entity reference links count annotation. + /// + /// The entity reference links to read the count value for; the value of the count will be assigned to this instance. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the instance annotation + /// Post-Condition: JsonNodeType.EndObject The end of the entity reference links object + /// JsonNodeType.Property The next property after the instance annotation + /// + private void ReadEntityReferenceCountAnnotationValue(ODataEntityReferenceLinks links) + { + Debug.Assert(links != null, "links != null"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue); + Debug.Assert(!links.Count.HasValue, "We should have checked for duplicates already."); + links.Count = this.ReadAndValidateAnnotationAsLongForIeee754Compatible(ODataAnnotationNames.ODataCount); + } + + /// + /// Read an entity reference link. + /// + /// The duplicate property names checker to check for duplicate properties and + /// duplicate annotations; this is a separate instance per entity reference link. + /// true if we are reading a singleton entity reference link at the top level; false if we are reading + /// an entity reference link as part of a collection of entity reference links. + /// An instance of which was read. + /// + /// Pre-Condition: StartObject when the entity reference link is part of a collection + /// Property the first property in the entity reference link (for a top-level link) + /// EndObject the end object node of an entity reference link (for a top-level link) + /// Post-Condition: EndInput for a top-level object + /// EndArray for the last link in a collection of links + /// Any for the first node of the next link in a collection of links + /// + private ODataEntityReferenceLink ReadSingleEntityReferenceLink(PropertyAndAnnotationCollector propertyAndAnnotationCollector, bool topLevel) + { + this.JsonReader.AssertNotBuffering(); + + if (!topLevel) + { + if (this.JsonReader.NodeType != JsonNodeType.StartObject) + { + // entity reference link has to be an object + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkMustBeObjectValue(this.JsonReader.NodeType)); + } + + this.JsonReader.ReadStartObject(); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + ODataEntityReferenceLink[] entityReferenceLink = { null }; + + // Entity reference links use instance annotations. Fail if we find a property annotation. + Func propertyAnnotationValueReader = + annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLink(annotationName)); }; + + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationValueReader, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + if (string.CompareOrdinal(ODataAnnotationNames.ODataId, propertyName) != 0) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyInEntityReferenceLink(propertyName, ODataAnnotationNames.ODataId)); + } + else if (entityReferenceLink[0] != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_MultipleUriPropertiesInEntityReferenceLink(ODataAnnotationNames.ODataId)); + } + + // read the value of the 'odata.id' annotation + string urlString = this.JsonReader.ReadStringValue(ODataAnnotationNames.ODataId); + if (urlString == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkUrlCannotBeNull(ODataAnnotationNames.ODataId)); + } + + entityReferenceLink[0] = new ODataEntityReferenceLink + { + Url = this.ProcessUriFromPayload(urlString) + }; + + ReaderValidationUtils.ValidateEntityReferenceLink(entityReferenceLink[0]); + + break; + + case PropertyParsingResult.CustomInstanceAnnotation: + if (entityReferenceLink[0] == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_MissingEntityReferenceLinkProperty(ODataAnnotationNames.ODataId)); + } + + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.StartArray); + ODataAnnotationNames.ValidateIsCustomAnnotationName(propertyName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(propertyName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(propertyName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + object annotationValue = this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, propertyName); + entityReferenceLink[0].InstanceAnnotations.Add(new ODataInstanceAnnotation(propertyName, annotationValue.ToODataValue())); + break; + + case PropertyParsingResult.PropertyWithValue: + case PropertyParsingResult.PropertyWithoutValue: + // entity reference link is denoted by odata.id annotation + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_InvalidAnnotationInEntityReferenceLink(propertyName)); + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightEntityReferenceLinkDeserializer_ReadSingleEntityReferenceLink)); + } + }); + } + + if (entityReferenceLink[0] == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightEntityReferenceLinkDeserializer_MissingEntityReferenceLinkProperty(ODataAnnotationNames.ODataId)); + } + + // end of the entity reference link object + this.JsonReader.ReadEndObject(); + + this.JsonReader.AssertNotBuffering(); + return entityReferenceLink[0]; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkSerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkSerializer.cs new file mode 100644 index 0000000..3bb4e8d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightEntityReferenceLinkSerializer.cs @@ -0,0 +1,164 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// OData JsonLight serializer for entity reference links. + /// + internal sealed class ODataJsonLightEntityReferenceLinkSerializer : ODataJsonLightSerializer + { + /// + /// Constructor. + /// + /// The output context to write to. + internal ODataJsonLightEntityReferenceLinkSerializer(ODataJsonLightOutputContext jsonLightOutputContext) + : base(jsonLightOutputContext, /*initContextUriBuilder*/ true) + { + } + + /// + /// Writes a single top-level Uri in response to a $ref query. + /// + /// The entity reference link to write out. + internal void WriteEntityReferenceLink(ODataEntityReferenceLink link) + { + Debug.Assert(link != null, "link != null"); + + this.WriteTopLevelPayload( + () => this.WriteEntityReferenceLinkImplementation(link, /* isTopLevel */ true)); + } + + /// + /// Writes a set of links (Uris) in response to a $ref query; includes optional count and next-page-link information. + /// + /// The set of entity reference links to write out. + internal void WriteEntityReferenceLinks(ODataEntityReferenceLinks entityReferenceLinks) + { + Debug.Assert(entityReferenceLinks != null, "entityReferenceLinks != null"); + + this.WriteTopLevelPayload( + () => this.WriteEntityReferenceLinksImplementation(entityReferenceLinks)); + } + + /// + /// Writes a single Uri in response to a $ref query. + /// + /// The entity reference link to write out. + /// true if the entity reference link being written is at the top level of the payload. + private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink entityReferenceLink, bool isTopLevel) + { + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLink); + + this.JsonWriter.StartObjectScope(); + + if (isTopLevel) + { + this.WriteContextUriProperty(ODataPayloadKind.EntityReferenceLink); + } + + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataId); + this.JsonWriter.WriteValue(this.UriToString(entityReferenceLink.Url)); + + this.InstanceAnnotationWriter.WriteInstanceAnnotations(entityReferenceLink.InstanceAnnotations); + this.JsonWriter.EndObjectScope(); + } + + /// + /// Writes a set of links (Uris) in response to a $ref query; includes optional count and next-page-link information. + /// + /// The set of entity reference links to write out. + private void WriteEntityReferenceLinksImplementation(ODataEntityReferenceLinks entityReferenceLinks) + { + Debug.Assert(entityReferenceLinks != null, "entityReferenceLinks != null"); + + bool wroteNextLink = false; + + // { + this.JsonWriter.StartObjectScope(); + + // "@odata.context": ... + this.WriteContextUriProperty(ODataPayloadKind.EntityReferenceLinks); + + if (entityReferenceLinks.Count.HasValue) + { + // We try to write the count property at the top of the payload if one is available. + // "@odata.count": ... + this.WriteCountAnnotation(entityReferenceLinks.Count.Value); + } + + if (entityReferenceLinks.NextPageLink != null) + { + // We try to write the next link at the top of the payload if one is available. If not, we try again at the end. + wroteNextLink = true; + + // "@odata.next": ... + this.WriteNextLinkAnnotation(entityReferenceLinks.NextPageLink); + } + + this.InstanceAnnotationWriter.WriteInstanceAnnotations(entityReferenceLinks.InstanceAnnotations); + + // "value": + this.JsonWriter.WriteValuePropertyName(); + + // "[": + this.JsonWriter.StartArrayScope(); + + IEnumerable entityReferenceLinksEnumerable = entityReferenceLinks.Links; + if (entityReferenceLinksEnumerable != null) + { + foreach (ODataEntityReferenceLink entityReferenceLink in entityReferenceLinksEnumerable) + { + WriterValidationUtils.ValidateEntityReferenceLinkNotNull(entityReferenceLink); + this.WriteEntityReferenceLinkImplementation(entityReferenceLink, /* isTopLevel */ false); + } + } + + // "]" + this.JsonWriter.EndArrayScope(); + + if (!wroteNextLink && entityReferenceLinks.NextPageLink != null) + { + // "@odata.next": ... + this.WriteNextLinkAnnotation(entityReferenceLinks.NextPageLink); + } + + // "}" + this.JsonWriter.EndObjectScope(); + } + + /// + /// Writes the next link property, which consists of the property name and value. + /// + /// The non-null value of the next link to write. + private void WriteNextLinkAnnotation(Uri nextPageLink) + { + Debug.Assert(nextPageLink != null, "Expected non-null next link."); + + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataNextLink); + this.JsonWriter.WriteValue(this.UriToString(nextPageLink)); + } + + /// + /// Writes the odata.count property, which consists of the property name and value. + /// + /// The value of the count property to write. + private void WriteCountAnnotation(long countValue) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataCount); + this.JsonWriter.WriteValue(countValue); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightErrorDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightErrorDeserializer.cs new file mode 100644 index 0000000..96b8e7c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightErrorDeserializer.cs @@ -0,0 +1,464 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// OData JsonLight deserializer for errors. + /// + internal sealed class ODataJsonLightErrorDeserializer : ODataJsonLightDeserializer + { + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + internal ODataJsonLightErrorDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + } + + /// + /// Read a top-level error. + /// + /// An representing the read error. + /// + /// Pre-Condition: JsonNodeType.None - The reader must not have been used yet. + /// Post-Condition: JsonNodeType.EndOfInput + /// + internal ODataError ReadTopLevelError() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + Debug.Assert(!this.JsonReader.DisableInStreamErrorDetection, "!JsonReader.DisableInStreamErrorDetection"); + this.JsonReader.AssertNotBuffering(); + + // prevent the buffering JSON reader from detecting in-stream errors - we read the error ourselves + // to throw proper exceptions + this.JsonReader.DisableInStreamErrorDetection = true; + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + try + { + // Position the reader on the first node + this.ReadPayloadStart( + ODataPayloadKind.Error, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false); + + ODataError result = this.ReadTopLevelErrorImplementation(); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return result; + } + finally + { + this.JsonReader.DisableInStreamErrorDetection = false; + } + } + +#if PORTABLELIB + + /// + /// Read a top-level error. + /// + /// A task which returns an representing the read error. + /// + /// Pre-Condition: JsonNodeType.None - The reader must not have been used yet. + /// Post-Condition: JsonNodeType.EndOfInput + /// + internal Task ReadTopLevelErrorAsync() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + Debug.Assert(!this.JsonReader.DisableInStreamErrorDetection, "!JsonReader.DisableInStreamErrorDetection"); + this.JsonReader.AssertNotBuffering(); + + // prevent the buffering JSON reader from detecting in-stream errors - we read the error ourselves + // to throw proper exceptions + this.JsonReader.DisableInStreamErrorDetection = true; + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + // Position the reader on the first node + return this.ReadPayloadStartAsync( + ODataPayloadKind.Error, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false) + + .FollowOnSuccessWith(t => + { + ODataError result = this.ReadTopLevelErrorImplementation(); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return result; + }) + + .FollowAlwaysWith(t => + { + this.JsonReader.DisableInStreamErrorDetection = false; + }); + } +#endif + + /// + /// Read a top-level error. + /// + /// An representing the read error. + /// + /// Pre-Condition: JsonNodeType.Property - The first property of the top level object. + /// JsonNodeType.EndObject - If there are no properties in the top level object. + /// any - Will throw if anything else. + /// Post-Condition: JsonNodeType.EndOfInput + /// + private ODataError ReadTopLevelErrorImplementation() + { + ODataError error = null; + + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + string propertyName = this.JsonReader.ReadPropertyName(); + if (string.CompareOrdinal(JsonLightConstants.ODataErrorPropertyName, propertyName) != 0) + { + // we only allow a single 'error' property for a top-level error object + throw new ODataException(Strings.ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty(propertyName)); + } + + if (error != null) + { + throw new ODataException(Strings.ODataJsonReaderUtils_MultipleErrorPropertiesWithSameName(JsonLightConstants.ODataErrorPropertyName)); + } + + error = new ODataError(); + + this.ReadODataErrorObject(error); + } + + // Read the end of the error object + this.JsonReader.ReadEndObject(); + + // Read the end of the response. + this.ReadPayloadEnd(false /*isReadingNestedPayload*/); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: JsonNodeType.EndOfInput"); + + return error; + } + + /// + /// Reads all the properties in a single JSON object scope, calling for each non-annotation property encountered. + /// + /// + /// An action which takes the name of the current property and processes the property value as necessary. + /// At the start of this action, the reader is positioned at the property value node. + /// The action should leave the reader positioned on the node after the property value. + /// + /// + /// + /// This method should only be used for scopes where we allow (and ignore) annotations in a custom namespace, i.e. scopes which directly correspond to a class in the OM. + /// + /// Pre-Condition: JsonNodeType.StartObject - The start of the JSON object being processed. + /// any - Will throw if not StartObject. + /// Post-Condition: any - The node after the EndObject node for the JSON object being processed. + /// + private void ReadJsonObjectInErrorPayload(Action readPropertyWithValue) + { + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + this.JsonReader.ReadStartObject(); + + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ProcessProperty( + propertyAndAnnotationCollector, + this.ReadErrorPropertyAnnotationValue, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + throw new ODataException(Strings.ODataJsonLightErrorDeserializer_InstanceAnnotationNotAllowedInErrorPayload(propertyName)); + case PropertyParsingResult.CustomInstanceAnnotation: + readPropertyWithValue(propertyName, propertyAndAnnotationCollector); + break; + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(Strings.ODataJsonLightErrorDeserializer_PropertyAnnotationWithoutPropertyForError(propertyName)); + case PropertyParsingResult.PropertyWithValue: + readPropertyWithValue(propertyName, propertyAndAnnotationCollector); + break; + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(Strings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + }); + } + + this.JsonReader.ReadEndObject(); + } + + /// + /// Reads a value of property annotation on an error payload. + /// + /// The name of the property annotation to read. + /// The value of the property annotation. + /// + /// This method should read the property annotation value and return a representation of the value which will be later + /// consumed by the resource reading code, or throw if ther is something unexpected. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the property annotation property + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// Post-Condition: JsonNodeType.EndObject The end of the error object + /// JsonNodeType.Property The next property after the property annotation + /// + private object ReadErrorPropertyAnnotationValue(string propertyAnnotationName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyAnnotationName), "!string.IsNullOrEmpty(propertyAnnotationName)"); + Debug.Assert( + propertyAnnotationName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal), + "The method should only be called with OData. annotations"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.StartArray); + + if (string.CompareOrdinal(propertyAnnotationName, ODataAnnotationNames.ODataType) == 0) + { + string typeName = ReaderUtils.AddEdmPrefixOfTypeName(ReaderUtils.RemovePrefixOfTypeName(this.JsonReader.ReadStringValue())); + if (typeName == null) + { + throw new ODataException(Strings.ODataJsonLightPropertyAndValueDeserializer_InvalidTypeName(propertyAnnotationName)); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return typeName; + } + + throw new ODataException(Strings.ODataJsonLightErrorDeserializer_PropertyAnnotationNotAllowedInErrorPayload(propertyAnnotationName)); + } + + /// + /// Reads the JSON object which is the value of the "error" property. + /// + /// The object to update with data from the payload. + /// + /// Pre-Condition: JsonNodeType.StartObject - The start of the "error" object. + /// any - Will throw if not StartObject. + /// Post-Condition: any - The node after the "error" object's EndNode. + /// + private void ReadODataErrorObject(ODataError error) + { + this.ReadJsonObjectInErrorPayload((propertyName, duplicationPropertyNameChecker) => this.ReadPropertyValueInODataErrorObject(error, propertyName, duplicationPropertyNameChecker)); + } + + /// + /// Reads an inner error payload. + /// + /// The number of times this method has been called recursively. + /// An representing the read inner error. + /// + /// Pre-Condition: JsonNodeType.StartObject - The start of the "innererror" object. + /// any - will throw if not StartObject. + /// Post-Condition: any - The node after the "innererror" object's EndNode. + /// + private ODataInnerError ReadInnerError(int recursionDepth) + { + Debug.Assert(this.JsonReader.DisableInStreamErrorDetection, "JsonReader.DisableInStreamErrorDetection"); + this.JsonReader.AssertNotBuffering(); + + ValidationUtils.IncreaseAndValidateRecursionDepth(ref recursionDepth, this.MessageReaderSettings.MessageQuotas.MaxNestingDepth); + + ODataInnerError innerError = new ODataInnerError(); + + this.ReadJsonObjectInErrorPayload((propertyName, propertyAndAnnotationCollector) => this.ReadPropertyValueInInnerError(recursionDepth, innerError, propertyName)); + + this.JsonReader.AssertNotBuffering(); + return innerError; + } + + /// + /// Reads a property value which occurs in the "innererror" object scope. + /// + /// The number of parent inner errors for this inner error. + /// The object to update with the data from this property value. + /// The name of the property whose value is to be read. + /// + /// Pre-Condition: any - The value of the property being read. + /// Post-Condition: JsonNodeType.Property - The property after the one being read. + /// JsonNodeType.EndObject - The end of the "innererror" object. + /// any - Anything else after the property value is an invalid payload (but won't fail in this method). + /// + private void ReadPropertyValueInInnerError(int recursionDepth, ODataInnerError innerError, string propertyName) + { + switch (propertyName) + { + case JsonConstants.ODataErrorInnerErrorMessageName: + innerError.Message = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorInnerErrorMessageName); + break; + + case JsonConstants.ODataErrorInnerErrorTypeNameName: + innerError.TypeName = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorInnerErrorTypeNameName); + break; + + case JsonConstants.ODataErrorInnerErrorStackTraceName: + innerError.StackTrace = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorInnerErrorStackTraceName); + break; + + case JsonConstants.ODataErrorInnerErrorInnerErrorName: + innerError.InnerError = this.ReadInnerError(recursionDepth); + break; + + default: + // skip any unsupported properties in the inner error + this.JsonReader.SkipValue(); + break; + } + } + + /// + /// Reads a property value which occurs in the "error" object scope. + /// + /// The object to update with the data from this property value. + /// The name of the property whose value is to be read. + /// PropertyAndAnnotationCollector to use for extracting property annotations + /// targetting any custom instance annotations on the error. + /// + /// Pre-Condition: any - The value of the property being read. + /// Post-Condition: JsonNodeType.Property - The property after the one being read. + /// JsonNodeType.EndObject - The end of the "error" object. + /// any - Anything else after the property value is an invalid payload (but won't fail in this method). + /// + private void ReadPropertyValueInODataErrorObject(ODataError error, string propertyName, PropertyAndAnnotationCollector duplicationPropertyNameChecker) + { + switch (propertyName) + { + case JsonConstants.ODataErrorCodeName: + error.ErrorCode = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorCodeName); + break; + + case JsonConstants.ODataErrorMessageName: + error.Message = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorMessageName); + break; + + case JsonConstants.ODataErrorTargetName: + error.Target = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorTargetName); + break; + + case JsonConstants.ODataErrorDetailsName: + error.Details = this.ReadDetails(); + break; + + case JsonConstants.ODataErrorInnerErrorName: + error.InnerError = this.ReadInnerError(0 /* recursionDepth */); + break; + + default: + // See if it's an instance annotation + if (ODataJsonLightReaderUtils.IsAnnotationProperty(propertyName)) + { + ODataJsonLightPropertyAndValueDeserializer valueDeserializer = new ODataJsonLightPropertyAndValueDeserializer(this.JsonLightInputContext); + object typeName = null; + + duplicationPropertyNameChecker.GetODataPropertyAnnotations(propertyName) + .TryGetValue(ODataAnnotationNames.ODataType, out typeName); + + var value = valueDeserializer.ReadNonEntityValue( + typeName as string, + null /*expectedValueTypeReference*/, + null /*duplicatePropertiesNamesChecker*/, + null /*collectionValidator*/, + false /*validateNullValue*/, + false /*isTopLevelPropertyValue*/, + false /*insideResourceValue*/, + propertyName); + + error.GetInstanceAnnotations().Add(new ODataInstanceAnnotation(propertyName, value.ToODataValue())); + } + else + { + // we only allow a 'code', 'message', 'target', 'details, and 'innererror' properties + // in the value of the 'error' property or custom instance annotations + throw new ODataException(Strings.ODataJsonLightErrorDeserializer_TopLevelErrorValueWithInvalidProperty(propertyName)); + } + + break; + } + } + + private ICollection ReadDetails() + { + var details = new List(); + + // [ + this.JsonReader.ReadStartArray(); + + while (JsonReader.NodeType == JsonNodeType.StartObject) + { + var detail = ReadDetail(); + details.Add(detail); + } + + // ] + this.JsonReader.ReadEndArray(); + + return details; + } + + private ODataErrorDetail ReadDetail() + { + var detail = new ODataErrorDetail(); + + ReadJsonObjectInErrorPayload( + (propertyName, duplicationPropertyNameChecker) => + ReadPropertyValueInODataErrorDetailObject(detail, propertyName)); + + return detail; + } + + private void ReadPropertyValueInODataErrorDetailObject(ODataErrorDetail detail, string propertyName) + { + switch (propertyName) + { + case JsonConstants.ODataErrorCodeName: + detail.ErrorCode = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorCodeName); + break; + + case JsonConstants.ODataErrorMessageName: + detail.Message = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorMessageName); + break; + + case JsonConstants.ODataErrorTargetName: + detail.Target = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorTargetName); + break; + + default: + this.JsonReader.SkipValue(); + break; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightInputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightInputContext.cs new file mode 100644 index 0000000..fd8827a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightInputContext.cs @@ -0,0 +1,849 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Metadata; + using Microsoft.OData.Buffers; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + // ReSharper disable RedundantUsingDirective + using ODataErrorStrings = Microsoft.OData.Strings; + // ReSharper restore RedundantUsingDirective + #endregion Namespaces + + /// + /// Implementation of the OData input for JsonLight OData format. + /// + internal sealed class ODataJsonLightInputContext : ODataInputContext + { + /// + /// The json metadata level (i.e., full, none, minimal) being written. + /// + private readonly JsonLightMetadataLevel metadataLevel; + + /// The text reader created for the input stream. + /// + /// The ODataJsonLightInputContext instance owns the textReader instance and thus disposes it. + /// We further set this field to null when the input is disposed and use it for checks whether the instance has already been disposed. + /// + private TextReader textReader; + + /// The JSON reader to read from. + private BufferingJsonReader jsonReader; + + /// + /// The JsonLight message stream. + /// + private Stream stream; + + /// Constructor. + /// The context information for the message. + /// Configuration settings of the OData reader. + public ODataJsonLightInputContext( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + : this(CreateTextReader(messageInfo.MessageStream, messageInfo.Encoding), messageInfo, messageReaderSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + this.stream = messageInfo.MessageStream; + } + + /// Constructor. + /// The text reader to use. + /// The context information for the message. + /// Configuration settings of the OData reader. + internal ODataJsonLightInputContext( + TextReader textReader, + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + : base(ODataFormat.Json, messageInfo, messageReaderSettings) + { + Debug.Assert(messageInfo.MediaType != null, "messageInfo.MediaType != null"); + + try + { + this.textReader = textReader; + var innerReader = CreateJsonReader(this.Container, this.textReader, messageInfo.MediaType.HasIeee754CompatibleSetToTrue()); + if (messageReaderSettings.ArrayPool != null) + { + // make sure customer also can use reading setting if without DI. + JsonReader jsonReader = innerReader as JsonReader; + if (jsonReader != null && jsonReader.ArrayPool == null) + { + jsonReader.ArrayPool = messageReaderSettings.ArrayPool; + } + } + + if (messageInfo.MediaType.HasStreamingSetToTrue()) + { + this.jsonReader = new BufferingJsonReader( + innerReader, + JsonLightConstants.ODataErrorPropertyName, + messageReaderSettings.MessageQuotas.MaxNestingDepth); + } + else + { + // If we have a non-streaming Json Light content type we need to use the re-ordering Json reader + this.jsonReader = new ReorderingJsonReader(innerReader, messageReaderSettings.MessageQuotas.MaxNestingDepth); + } + } + catch (Exception e) + { + // Dispose the message stream if we failed to create the input context. + if (ExceptionUtils.IsCatchableExceptionType(e) && this.textReader != null) + { + this.textReader.Dispose(); + } + + throw; + } + + // dont know how to get MetadataDocumentUri uri here, messageReaderSettings do not have one + // Uri metadataDocumentUri = messageReaderSettings..MetadataDocumentUri == null ? null : messageReaderSettings.MetadataDocumentUri.BaseUri; + // the uri here is used here to create the FullMetadataLevel can pass null in + this.metadataLevel = JsonLightMetadataLevel.Create(messageInfo.MediaType, null, this.Model, this.ReadingResponse); + } + + /// + /// The json metadata level (i.e., full, none, minimal) being written. + /// + public JsonLightMetadataLevel MetadataLevel + { + get + { + return this.metadataLevel; + } + } + + /// + /// Returns the which is to be used to read the content of the message. + /// + public BufferingJsonReader JsonReader + { + get + { + Debug.Assert(this.jsonReader != null, "Trying to get JsonReader while none is available."); + return this.jsonReader; + } + } + + /// + /// The stream of the JsonLight input context. + /// + internal Stream Stream + { + get + { + return this.stream; + } + } + + /// + /// Returns whether to read odata control information without the odata prefix. + /// True for OData 4.01 and greater. Settable for OData 4.0 + /// + internal bool OptionalODataPrefix + { + get + { + if (this.MessageReaderSettings.Version == ODataVersion.V4) + { + return this.ODataSimplifiedOptions.EnableReadingODataAnnotationWithoutPrefix; + } + + return true; + } + } + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// The newly created . + public override ODataReader CreateResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.AssertSynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedResourceType); + + return this.CreateResourceSetReaderImplementation(entitySet, expectedResourceType, /*readingParameter*/ false, /*readingDelta*/ false); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// Task which when completed returns the newly created . + public override Task CreateResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.AssertAsynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedResourceType); + + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceSetReaderImplementation(entitySet, expectedResourceType, /*readingParameter*/ false, /*readingDelta*/ false)); + } +#endif + + /// + /// Creates an to read a delta resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// The newly created . + public override ODataReader CreateDeltaResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.AssertSynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedResourceType); + + return this.CreateResourceSetReaderImplementation(entitySet, expectedResourceType, /*readingParameter*/ false, /*readingDelta*/ true); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a delta resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// Task which when completed returns the newly created . + public override Task CreateDeltaResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.AssertAsynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedResourceType); + + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceSetReaderImplementation(entitySet, expectedResourceType, /*readingParameter*/ false, /*readingDelta*/ true)); + } +#endif + + /// + /// Creates an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected resource type for the resource to be read. + /// The newly created . + public override ODataReader CreateResourceReader(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + this.AssertSynchronous(); + this.VerifyCanCreateODataReader(navigationSource, expectedResourceType); + + return this.CreateResourceReaderImplementation(navigationSource, expectedResourceType); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// Task which when completed returns the newly created . + public override Task CreateResourceReaderAsync(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + this.AssertAsynchronous(); + this.VerifyCanCreateODataReader(navigationSource, expectedResourceType); + + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceReaderImplementation(navigationSource, expectedResourceType)); + } +#endif + + /// + /// Create a . + /// + /// The expected type reference for the items in the collection. + /// Newly create . + public override ODataCollectionReader CreateCollectionReader(IEdmTypeReference expectedItemTypeReference) + { + this.AssertSynchronous(); + this.VerifyCanCreateCollectionReader(expectedItemTypeReference); + + return this.CreateCollectionReaderImplementation(expectedItemTypeReference); + } + +#if PORTABLELIB + /// + /// Asynchronously create a . + /// + /// The expected type reference for the items in the collection. + /// Task which when completed returns the newly create . + public override Task CreateCollectionReaderAsync(IEdmTypeReference expectedItemTypeReference) + { + this.AssertAsynchronous(); + this.VerifyCanCreateCollectionReader(expectedItemTypeReference); + + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateCollectionReaderImplementation(expectedItemTypeReference)); + } +#endif + + /// + /// This method creates an reads the property from the input and + /// returns an representing the read property. + /// + /// The producing the property to be read. + /// The expected type reference of the property to read. + /// An representing the read property. + public override ODataProperty ReadProperty(IEdmStructuralProperty property, IEdmTypeReference expectedPropertyTypeReference) + { + this.AssertSynchronous(); + this.VerifyCanReadProperty(); + + ODataJsonLightPropertyAndValueDeserializer jsonLightPropertyAndValueDeserializer = new ODataJsonLightPropertyAndValueDeserializer(this); + return jsonLightPropertyAndValueDeserializer.ReadTopLevelProperty(expectedPropertyTypeReference); + } + +#if PORTABLELIB + /// + /// Asynchronously read the property from the input and + /// return an representing the read property. + /// + /// The producing the property to be read. + /// The expected type reference of the property to read. + /// Task which when completed returns an representing the read property. + public override Task ReadPropertyAsync(IEdmStructuralProperty property, IEdmTypeReference expectedPropertyTypeReference) + { + this.AssertAsynchronous(); + this.VerifyCanReadProperty(); + + ODataJsonLightPropertyAndValueDeserializer jsonLightPropertyAndValueDeserializer = new ODataJsonLightPropertyAndValueDeserializer(this); + return jsonLightPropertyAndValueDeserializer.ReadTopLevelPropertyAsync(expectedPropertyTypeReference); + } +#endif + + /// + /// Read a top-level error. + /// + /// An representing the read error. + public override ODataError ReadError() + { + this.AssertSynchronous(); + + ODataJsonLightErrorDeserializer jsonLightErrorDeserializer = new ODataJsonLightErrorDeserializer(this); + return jsonLightErrorDeserializer.ReadTopLevelError(); + } + +#if PORTABLELIB + /// + /// Asynchronously read a top-level error. + /// + /// Task which when completed returns an representing the read error. + public override Task ReadErrorAsync() + { + this.AssertAsynchronous(); + + ODataJsonLightErrorDeserializer jsonLightErrorDeserializer = new ODataJsonLightErrorDeserializer(this); + return jsonLightErrorDeserializer.ReadTopLevelErrorAsync(); + } +#endif + + /// + /// Creates an to read a resource set in a Uri operation parameter. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// The newly created . + public override ODataReader CreateUriParameterResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.AssertSynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedResourceType); + + return this.CreateResourceSetReaderImplementation(entitySet, expectedResourceType, /*readingParameter*/ true, /*readingDelta*/ false); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set in a Uri operation parameter. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// Task which when completed returns the newly created . + public override Task CreateUriParameterResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.AssertAsynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedResourceType); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceSetReaderImplementation(entitySet, expectedResourceType, /*readingParameter*/ true, /*readingDelta*/ false)); + } +#endif + + /// + /// Creates an to read a resource in a Uri operation parameter. + /// + /// The navigation source we are going to read resources for. + /// The expected resource type for the resource to be read. + /// The newly created . + public override ODataReader CreateUriParameterResourceReader(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + return this.CreateResourceReader(navigationSource, expectedResourceType); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource in a Uri operation parameter. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// Task which when completed returns the newly created . + public override Task CreateUriParameterResourceReaderAsync(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + return this.CreateResourceReaderAsync(navigationSource, expectedResourceType); + } +#endif + + /// + /// Create a . + /// + /// The operation whose parameters are being read. + /// The newly created . + public override ODataParameterReader CreateParameterReader(IEdmOperation operation) + { + this.AssertSynchronous(); + this.VerifyCanCreateParameterReader(operation); + + return this.CreateParameterReaderImplementation(operation); + } + +#if PORTABLELIB + /// + /// Asynchronously create a . + /// + /// The operation whose parameters are being read. + /// Task which when completed returns the newly created . + public override Task CreateParameterReaderAsync(IEdmOperation operation) + { + this.AssertAsynchronous(); + this.VerifyCanCreateParameterReader(operation); + + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateParameterReaderImplementation(operation)); + } +#endif + + /// + /// Detects the payload kind(s) from the message stream. + /// + /// Additional information available for the payload kind detection. + /// An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + public IEnumerable DetectPayloadKind(ODataPayloadKindDetectionInfo detectionInfo) + { + Debug.Assert(detectionInfo != null, "detectionInfo != null"); + this.VerifyCanDetectPayloadKind(); + + ODataJsonLightPayloadKindDetectionDeserializer payloadKindDetectionDeserializer = new ODataJsonLightPayloadKindDetectionDeserializer(this); + return payloadKindDetectionDeserializer.DetectPayloadKind(detectionInfo); + } + +#if PORTABLELIB + /// + /// Detects the payload kind(s) from the message stream. + /// + /// Additional information available for the payload kind detection. + /// A task which returns an enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + public Task> DetectPayloadKindAsync(ODataPayloadKindDetectionInfo detectionInfo) + { + Debug.Assert(detectionInfo != null, "detectionInfo != null"); + this.VerifyCanDetectPayloadKind(); + + ODataJsonLightPayloadKindDetectionDeserializer payloadKindDetectionDeserializer = new ODataJsonLightPayloadKindDetectionDeserializer(this); + return payloadKindDetectionDeserializer.DetectPayloadKindAsync(detectionInfo); + } +#endif + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base entity type for the entries in the delta response. + /// The newly created . + internal override ODataDeltaReader CreateDeltaReader(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + this.AssertSynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedBaseEntityType); + + return this.CreateDeltaReaderImplementation(entitySet, expectedBaseEntityType); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base entity type for the entries in the delta response. + /// Task which when completed returns the newly created . + internal override Task CreateDeltaReaderAsync(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + this.AssertAsynchronous(); + this.VerifyCanCreateODataReader(entitySet, expectedBaseEntityType); + + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateDeltaReaderImplementation(entitySet, expectedBaseEntityType)); + } +#endif + + /// + /// Create a . + /// + /// The newly created . + internal override ODataBatchReader CreateBatchReader() + { + return this.CreateBatchReaderImplementation(/*synchronous*/ true); + } + +#if PORTABLELIB + /// + /// Asynchronously create a . + /// + /// Task which when completed returns the newly created . + internal override Task CreateBatchReaderAsync() + { + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateBatchReaderImplementation(/*synchronous*/ false)); + } +#endif + + /// + /// Read a service document. + /// This method reads the service document from the input and returns + /// an that represents the read service document. + /// + /// An representing the read service document. + internal override ODataServiceDocument ReadServiceDocument() + { + this.AssertSynchronous(); + + ODataJsonLightServiceDocumentDeserializer jsonLightServiceDocumentDeserializer = new ODataJsonLightServiceDocumentDeserializer(this); + return jsonLightServiceDocumentDeserializer.ReadServiceDocument(); + } + +#if PORTABLELIB + /// + /// Asynchronously read a service document. + /// This method reads the service document from the input and returns + /// an that represents the read service document. + /// + /// Task which when completed returns an representing the read service document. + internal override Task ReadServiceDocumentAsync() + { + this.AssertAsynchronous(); + + ODataJsonLightServiceDocumentDeserializer jsonLightServiceDocumentDeserializer = new ODataJsonLightServiceDocumentDeserializer(this); + return jsonLightServiceDocumentDeserializer.ReadServiceDocumentAsync(); + } +#endif + + /// + /// Read a set of top-level entity reference links. + /// + /// An representing the read links. + internal override ODataEntityReferenceLinks ReadEntityReferenceLinks() + { + this.AssertSynchronous(); + + ODataJsonLightEntityReferenceLinkDeserializer jsonLightEntityReferenceLinkDeserializer = new ODataJsonLightEntityReferenceLinkDeserializer(this); + return jsonLightEntityReferenceLinkDeserializer.ReadEntityReferenceLinks(); + } + +#if PORTABLELIB + /// + /// Asynchronously read a set of top-level entity reference links. + /// + /// Task which when completed returns an representing the read links. + internal override Task ReadEntityReferenceLinksAsync() + { + this.AssertAsynchronous(); + + ODataJsonLightEntityReferenceLinkDeserializer jsonLightEntityReferenceLinkDeserializer = new ODataJsonLightEntityReferenceLinkDeserializer(this); + return jsonLightEntityReferenceLinkDeserializer.ReadEntityReferenceLinksAsync(); + } +#endif + + /// + /// Reads a top-level entity reference link. + /// + /// An representing the read entity reference link. + internal override ODataEntityReferenceLink ReadEntityReferenceLink() + { + this.AssertSynchronous(); + this.VerifyCanReadEntityReferenceLink(); + + ODataJsonLightEntityReferenceLinkDeserializer jsonLightEntityReferenceLinkDeserializer = new ODataJsonLightEntityReferenceLinkDeserializer(this); + return jsonLightEntityReferenceLinkDeserializer.ReadEntityReferenceLink(); + } + +#if PORTABLELIB + /// + /// Asynchronously read a top-level entity reference link. + /// + /// Task which when completed returns an representing the read entity reference link. + internal override Task ReadEntityReferenceLinkAsync() + { + this.AssertAsynchronous(); + this.VerifyCanReadEntityReferenceLink(); + + ODataJsonLightEntityReferenceLinkDeserializer jsonLightEntityReferenceLinkDeserializer = new ODataJsonLightEntityReferenceLinkDeserializer(this); + return jsonLightEntityReferenceLinkDeserializer.ReadEntityReferenceLinkAsync(); + } +#endif + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected override void Dispose(bool disposing) + { + if (disposing) + { + try + { + this.stream = null; + + if (this.textReader != null) + { + this.textReader.Dispose(); + } + } + finally + { + this.textReader = null; + this.jsonReader = null; + } + } + + base.Dispose(disposing); + } + + /// + /// Helper method to create a TextReader over the message stream. This is needed by the constructor to dispose the message stream if the creation fails + /// since this is called from the constructor in place where exception handling is not possible. + /// + /// The stream to read data from. + /// The encoding to use to read the input. + /// The newly created text reader. + private static TextReader CreateTextReader(Stream messageStream, Encoding encoding) + { + Debug.Assert(messageStream != null, "messageStream != null"); + + try + { + return new StreamReader(messageStream, encoding); + } + catch (Exception e) + { + // Dispose the message stream if we failed to create the input context. + if (ExceptionUtils.IsCatchableExceptionType(e) && messageStream != null) + { + messageStream.Dispose(); + } + + throw; + } + } + + private static IJsonReader CreateJsonReader(IServiceProvider container, TextReader textReader, bool isIeee754Compatible) + { + if (container == null) + { + return new JsonReader(textReader, isIeee754Compatible); + } + + var jsonReaderFactory = container.GetRequiredService(); + var jsonReader = jsonReaderFactory.CreateJsonReader(textReader, isIeee754Compatible); + Debug.Assert(jsonReader != null, "jsonWriter != null"); + + return jsonReader; + } + + /// + /// Verifies that CreateParameterReader can be called. + /// + /// The operation whose parameters are being read. + private void VerifyCanCreateParameterReader(IEdmOperation operation) + { + this.VerifyUserModel(); + + if (operation == null) + { + throw new ArgumentNullException("operation", ODataErrorStrings.ODataJsonLightInputContext_OperationCannotBeNullForCreateParameterReader("operation")); + } + } + + /// + /// Verifies that CreateResourceReader, CreateResourceSetReader, CreateDeltaResourceSetReader or CreateDeltaReader can be called. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource/resource set to be read. + private void VerifyCanCreateODataReader(IEdmNavigationSource navigationSource, IEdmStructuredType structuredType) + { + Debug.Assert(navigationSource == null || structuredType != null, "If an navigation source is specified, the structured type must be specified as well."); + + // We require metadata information for reading requests. + if (!this.ReadingResponse) + { + this.VerifyUserModel(); + + // TODO: check for entity only + if (navigationSource == null && (structuredType != null && structuredType.IsODataEntityTypeKind())) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_NoEntitySetForRequest); + } + } + + // We only check that the base type of the entity set is assignable from the specified entity type. + // If no resource set/resource type is specified in the API, we will read it from the context URI. + IEdmEntityType entitySetElementType = this.EdmTypeResolver.GetElementType(navigationSource); + if (navigationSource != null && structuredType != null && !structuredType.IsOrInheritsFrom(entitySetElementType)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType(structuredType.FullTypeName(), entitySetElementType.FullName(), navigationSource.FullNavigationSourceName())); + } + } + + /// + /// Verifies that CreateCollectionReader can be called. + /// + /// The expected type reference for the items in the collection. + private void VerifyCanCreateCollectionReader(IEdmTypeReference expectedItemTypeReference) + { + // We require metadata information for reading requests. + if (!this.ReadingResponse) + { + this.VerifyUserModel(); + + if (expectedItemTypeReference == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_ItemTypeRequiredForCollectionReaderInRequests); + } + } + } + + /// + /// Verifies that ReadEntityReferenceLink can be called. + /// + private void VerifyCanReadEntityReferenceLink() + { + // We require metadata information for reading requests. + if (!this.ReadingResponse) + { + this.VerifyUserModel(); + } + } + + /// + /// Verifies that ReadProperty can be called. + /// + private void VerifyCanReadProperty() + { + // We require metadata information for reading requests. + if (!this.ReadingResponse) + { + this.VerifyUserModel(); + } + } + + /// + /// Verifies that DetectPayloadKind can be called. + /// + private void VerifyCanDetectPayloadKind() + { + if (!this.ReadingResponse) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_PayloadKindDetectionForRequest); + } + } + + /// + /// Verifies that a user model is available for reading. + /// + private void VerifyUserModel() + { + if (!this.Model.IsUserModel()) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightInputContext_ModelRequiredForReading); + } + } + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// true means reading a resource set in uri operation parameter, false reading a resource set in other payloads. + /// true if reading a delta resource set. + /// The newly created . + private ODataReader CreateResourceSetReaderImplementation(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType, bool readingParameter, bool readingDelta) + { + return new ODataJsonLightReader(this, entitySet, expectedResourceType, /*readingResourceSet*/ true, readingParameter, readingDelta); + } + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base entity type for the entries in the delta response. + /// The newly created . + private ODataDeltaReader CreateDeltaReaderImplementation(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + return new ODataJsonLightDeltaReader(this, entitySet, expectedBaseEntityType); + } + + /// + /// Creates an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// The newly created . + private ODataReader CreateResourceReaderImplementation(IEdmNavigationSource navigationSource, IEdmStructuredType expectedBaseResourceType) + { + return new ODataJsonLightReader(this, navigationSource, expectedBaseResourceType, false); + } + + /// + /// Create a . + /// + /// The expected type reference for the items in the collection. + /// Newly create . + private ODataCollectionReader CreateCollectionReaderImplementation(IEdmTypeReference expectedItemTypeReference) + { + return new ODataJsonLightCollectionReader(this, expectedItemTypeReference, null /*listener*/); + } + + /// + /// Create a . + /// + /// The operation import whose parameters are being read. + /// The newly created . + private ODataParameterReader CreateParameterReaderImplementation(IEdmOperation operation) + { + return new ODataJsonLightParameterReader(this, operation); + } + + + /// + /// Create a concrete instance. + /// + /// true if the input should be read synchronously; false if it should be read asynchronously. + /// Newly created + private ODataBatchReader CreateBatchReaderImplementation(bool synchronous) + { + Debug.Assert(this.textReader != null); + Debug.Assert(this.textReader is StreamReader); + + return new ODataJsonLightBatchReader( + this, + synchronous); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightOutputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightOutputContext.cs new file mode 100644 index 0000000..8b68bfe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightOutputContext.cs @@ -0,0 +1,1008 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; + using Microsoft.OData.Buffers; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// JsonLight format output context. + /// + internal sealed class ODataJsonLightOutputContext : ODataOutputContext + { + /// + /// An in-memory stream for writing stream properties to non-streaming json writer. + /// + internal MemoryStream BinaryValueStream = null; + + /// + /// An in-memory StringWriter for writing string properties to non-streaming json writer. + /// + internal StringWriter StringWriter = null; + + /// + /// The json metadata level (i.e., full, none, minimal) being written. + /// + private readonly JsonLightMetadataLevel metadataLevel; + + /// An in-stream error listener to notify when in-stream error is to be written. Or null if we don't need to notify anybody. + private IODataOutputInStreamErrorListener outputInStreamErrorListener; + + /// The message output stream. + private Stream messageOutputStream; + + /// The asynchronous output stream if we're writing asynchronously. + private AsyncBufferedStream asynchronousOutputStream; + + /// The text writer created for the output stream. + private TextWriter textWriter; + + /// The JSON writer to write to. + /// This field is also used to determine if the output context has been disposed already. + private IJsonWriter jsonWriter; + + /// + /// The oracle to use to determine the type name to write for entries and values. + /// + private JsonLightTypeNameOracle typeNameOracle; + + /// + /// The handler to manage property cache. + /// + private PropertyCacheHandler propertyCacheHandler; + + /// + /// Constructor. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + public ODataJsonLightOutputContext( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + : base(ODataFormat.Json, messageInfo, messageWriterSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + Debug.Assert(messageInfo.MediaType != null, "messageInfo.MediaType != null"); + + try + { + this.messageOutputStream = messageInfo.MessageStream; + + Stream outputStream; + if (this.Synchronous) + { + outputStream = this.messageOutputStream; + } + else + { + this.asynchronousOutputStream = new AsyncBufferedStream(this.messageOutputStream); + outputStream = this.asynchronousOutputStream; + } + + this.textWriter = new StreamWriter(outputStream, messageInfo.Encoding); + + // COMPAT 2: JSON indentation - WCFDS indents only partially, it inserts newlines but doesn't actually insert spaces for indentation + // in here we allow the user to specify if true indentation should be used or if the limited functionality is enough. + this.jsonWriter = CreateJsonWriter(this.Container, this.textWriter, messageInfo.MediaType.HasIeee754CompatibleSetToTrue(), messageWriterSettings); + } + catch (Exception e) + { + // Dispose the message stream if we failed to create the input context. + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + this.messageOutputStream.Dispose(); + } + + throw; + } + + Uri metadataDocumentUri = messageWriterSettings.MetadataDocumentUri; + this.metadataLevel = JsonLightMetadataLevel.Create(messageInfo.MediaType, metadataDocumentUri, this.Model, this.WritingResponse); + this.propertyCacheHandler = new PropertyCacheHandler(); + } + + /// + /// Constructor. + /// + /// The text writer to write to. + /// The context information for the message. + /// Configuration settings of the OData writer. + internal ODataJsonLightOutputContext( + TextWriter textWriter, + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + : base(ODataFormat.Json, messageInfo, messageWriterSettings) + { + Debug.Assert(!this.WritingResponse, "Expecting WritingResponse to always be false for this constructor, so no need to validate the MetadataDocumentUri on the writer settings."); + Debug.Assert(textWriter != null, "textWriter != null"); + Debug.Assert(messageWriterSettings != null, "messageWriterSettings != null"); + + this.textWriter = textWriter; + this.jsonWriter = CreateJsonWriter(messageInfo.Container, textWriter, true /*isIeee754Compatible*/, messageWriterSettings); + this.metadataLevel = new JsonMinimalMetadataLevel(); + this.propertyCacheHandler = new PropertyCacheHandler(); + } + + /// + /// Returns the which is to be used to write the content of the message. + /// + public IJsonWriter JsonWriter + { + get + { + Debug.Assert(this.jsonWriter != null, "Trying to get JsonWriter while none is available."); + return this.jsonWriter; + } + } + + /// + /// Returns the oracle to use when determining the type name to write for entries and values. + /// + public JsonLightTypeNameOracle TypeNameOracle + { + get + { + if (this.typeNameOracle == null) + { + this.typeNameOracle = this.MetadataLevel.GetTypeNameOracle(); + } + + return this.typeNameOracle; + } + } + + /// + /// The json metadata level (i.e., full, none, minimal) being written. + /// + public JsonLightMetadataLevel MetadataLevel + { + get + { + return this.metadataLevel; + } + } + + /// + /// The handler to manage property cache. + /// + public PropertyCacheHandler PropertyCacheHandler + { + get { return propertyCacheHandler; } + } + + /// + /// Returns whether to write control information without the odata prefix. + /// + internal bool OmitODataPrefix + { + get + { + return this.ODataSimplifiedOptions.GetOmitODataPrefix(this.MessageWriterSettings.Version ?? ODataVersion.V4); + } + } + + /// + /// Creates an to write a resource set. + /// + /// The created writer. + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The write must flush the output when it's finished (inside the last Write call). + public override ODataWriter CreateODataResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.AssertSynchronous(); + + return this.CreateODataResourceSetWriterImplementation(entitySet, resourceType, false, false); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a resource set. + /// + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override Task CreateODataResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataResourceSetWriterImplementation(entitySet, resourceType, false, false)); + } +#endif + + /// + /// Creates an to write a delta resource set. + /// + /// The created writer. + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The write must flush the output when it's finished (inside the last Write call). + public override ODataWriter CreateODataDeltaResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.AssertSynchronous(); + + return this.CreateODataResourceSetWriterImplementation(entitySet, resourceType, false, true); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a delta resource set. + /// + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override Task CreateODataDeltaResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataResourceSetWriterImplementation(entitySet, resourceType, false, true)); + } +#endif + + /// + /// Creates an to write a resource. + /// + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override ODataWriter CreateODataResourceWriter(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.AssertSynchronous(); + + return this.CreateODataResourceWriterImplementation(navigationSource, resourceType); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a resource. + /// + /// The navigation source we are going to write entities for. + /// The structured type for the resources in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override Task CreateODataResourceWriterAsync(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataResourceWriterImplementation(navigationSource, resourceType)); + } +#endif + + /// + /// Creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The item type of the collection being written or null if no metadata is available. + /// The created collection writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override ODataCollectionWriter CreateODataCollectionWriter(IEdmTypeReference itemTypeReference) + { + this.AssertSynchronous(); + + return this.CreateODataCollectionWriterImplementation(itemTypeReference); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The item type of the collection being written or null if no metadata is available. + /// A running task for the created collection writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override Task CreateODataCollectionWriterAsync(IEdmTypeReference itemTypeReference) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataCollectionWriterImplementation(itemTypeReference)); + } +#endif + + /// + /// Creates an to write a resource into a Uri operation parameter. + /// + /// The navigation source we are going to write resource for. + /// The structured type for the resources in the resource set to be written. + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override ODataWriter CreateODataUriParameterResourceWriter(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + return this.CreateODataResourceWriter(navigationSource, resourceType); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a resource into a Uri operation parameter. + /// + /// The navigation source we are going to write resource for. + /// The structured type for the resources in the resource set to be written. + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override Task CreateODataUriParameterResourceWriterAsync(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + return this.CreateODataResourceWriterAsync(navigationSource, resourceType); + } +#endif + + /// + /// Creates an to write a resource set into a Uri operation parameter. + /// + /// The created writer. + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The write must flush the output when it's finished (inside the last Write call). + public override ODataWriter CreateODataUriParameterResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.AssertSynchronous(); + + return this.CreateODataResourceSetWriterImplementation(entitySet, resourceType, true, false); + } + +#if PORTABLELIB + + /// + /// Asynchronously Creates an to write a resource set into a Uri operation parameter. + /// + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override Task CreateODataUriParameterResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataResourceSetWriterImplementation(entitySet, resourceType, true, false)); + } +#endif + + /// + /// Creates an to write a parameter payload. + /// + /// The operation whose parameters will be written. + /// The created parameter writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override ODataParameterWriter CreateODataParameterWriter(IEdmOperation operation) + { + this.AssertSynchronous(); + + return this.CreateODataParameterWriterImplementation(operation); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a parameter payload. + /// + /// The operation import whose parameters will be written. + /// A running task for the created parameter writer. + /// The write must flush the output when it's finished (inside the last Write call). + public override Task CreateODataParameterWriterAsync(IEdmOperation operation) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataParameterWriterImplementation(operation)); + } +#endif + + /// + /// Writes an as message payload. + /// + /// The property to write. + /// It is the responsibility of this method to flush the output before the method returns. + public override void WriteProperty(ODataProperty property) + { + this.AssertSynchronous(); + + this.WritePropertyImplementation(property); + this.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously writes an as message payload. + /// + /// The property to write + /// A task representing the asynchronous operation of writing the property. + /// It is the responsibility of this method to flush the output before the task finishes. + public override Task WritePropertyAsync(ODataProperty property) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + this.WritePropertyImplementation(property); + return this.FlushAsync(); + }); + } +#endif + + /// + /// Writes an as the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// It is the responsibility of this method to flush the output before the method returns. + public override void WriteError(ODataError error, bool includeDebugInformation) + { + this.AssertSynchronous(); + + this.WriteErrorImplementation(error, includeDebugInformation); + this.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously writes an as the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// A task representing the asynchronous operation of writing the error. + /// It is the responsibility of this method to flush the output before the task finishes. + public override Task WriteErrorAsync(ODataError error, bool includeDebugInformation) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + this.WriteErrorImplementation(error, includeDebugInformation); + return this.FlushAsync(); + }); + } +#endif + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + public void VerifyNotDisposed() + { + if (this.messageOutputStream == null) + { + throw new ObjectDisposedException(this.GetType().FullName); + } + } + + /// + /// Synchronously flush the writer. + /// + public void Flush() + { + this.AssertSynchronous(); + + // JsonWriter.Flush will call the underlying TextWriter.Flush. + // The TextWriter.Flush (which is in fact StreamWriter.Flush) will call the underlying Stream.Flush. + // In the synchronous case the underlying stream is the message stream itself, which will then Flush as well. + this.jsonWriter.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously flush the writer. + /// + /// Task which represents the pending flush operation. + /// The method should not throw directly if the flush operation itself fails, it should instead return a faulted task. + public Task FlushAsync() + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + // JsonWriter.Flush will call the underlying TextWriter.Flush. + // The TextWriter.Flush (Which is in fact StreamWriter.Flush) will call the underlying Stream.Flush. + // In the async case the underlying stream is the async buffered stream, which ignores Flush call. + this.jsonWriter.Flush(); + + Debug.Assert(this.asynchronousOutputStream != null, "In async writing we must have the async buffered stream."); + return this.asynchronousOutputStream.FlushAsync(); + }) + .FollowOnSuccessWithTask((asyncBufferedStreamFlushTask) => this.messageOutputStream.FlushAsync()); + } +#endif + + /// + /// Flushes all buffered data to the underlying stream synchronously. + /// + internal void FlushBuffers() + { + if (this.asynchronousOutputStream != null) + { + this.asynchronousOutputStream.FlushSync(); + } + } + +#if PORTABLELIB + /// + /// Flushes all buffered data to the underlying stream asynchronously. + /// + /// Task which represents the pending operation. + internal Task FlushBuffersAsync() + { + if (this.asynchronousOutputStream != null) + { + return this.asynchronousOutputStream.FlushAsync(); + } + else + { + return TaskUtils.CompletedTask; + } + } +#endif + + /// + /// The output stream to write the payload to. + /// + /// The output stream. + internal Stream GetOutputStream() + { + return this.Synchronous + ? this.messageOutputStream + : this.asynchronousOutputStream; + } + + /// + /// Creates an to write a batch of requests or responses in Json. + /// + /// The created batch writer. + /// We don't plan to make this public! + /// The write must flush the output when it's finished (inside the last Write call). + internal override ODataBatchWriter CreateODataBatchWriter() + { + this.AssertSynchronous(); + + return this.CreateODataBatchWriterImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a batch of requests or responses. + /// + /// A running task for the created batch writer. + /// We don't plan to make this public! + /// The write must flush the output when it's finished (inside the last Write call). + internal override Task CreateODataBatchWriterAsync() + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataBatchWriterImplementation()); + } +#endif + + /// + /// Writes an into the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// + /// This method is called if the ODataMessageWriter.WriteError is called once some other + /// write operation has already started. + /// The method should write the in-stream error representation for the specific format into the current payload. + /// Before the method is called no flush is performed on the output context or any active writer. + /// It is the responsibility of this method to flush the output before the method returns. + /// + internal override void WriteInStreamError(ODataError error, bool includeDebugInformation) + { + this.AssertSynchronous(); + + this.WriteInStreamErrorImplementation(error, includeDebugInformation); + this.Flush(); + } + +#if PORTABLELIB + /// + /// Writes an into the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// Task which represents the pending write operation. + /// + /// This method is called if the ODataMessageWriter.WriteError is called once some other + /// write operation has already started. + /// The method should write the in-stream error representation for the specific format into the current payload. + /// Before the method is called no flush is performed on the output context or any active writer. + /// It is the responsibility of this method to make sure that all the data up to this point are written before + /// the in-stream error is written. + /// It is the responsibility of this method to flush the output before the task finishes. + /// + internal override Task WriteInStreamErrorAsync(ODataError error, bool includeDebugInformation) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + this.WriteInStreamErrorImplementation(error, includeDebugInformation); + return this.FlushAsync(); + }); + } +#endif + + /// + /// Creates an to write a delta response. + /// + /// The created writer. + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The write must flush the output when it's finished (inside the last Write call). + internal override ODataDeltaWriter CreateODataDeltaWriter(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + this.AssertSynchronous(); + return this.CreateODataDeltaWriterImplementation(entitySet, entityType); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a delta response. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + internal override Task CreateODataDeltaWriterAsync(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataDeltaWriterImplementation(entitySet, entityType)); + } +#endif + + /// + /// Writes a service document with the specified + /// as message payload. + /// + /// The service document to write. + /// It is the responsibility of this method to flush the output before the method returns. + internal override void WriteServiceDocument(ODataServiceDocument serviceDocument) + { + this.AssertSynchronous(); + + this.WriteServiceDocumentImplementation(serviceDocument); + this.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously writes a service document with the specified + /// as message payload. + /// + /// The service document to write. + /// A task representing the asynchronous operation of writing the service document. + /// It is the responsibility of this method to flush the output before the task finishes. + internal override Task WriteServiceDocumentAsync(ODataServiceDocument serviceDocument) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + this.WriteServiceDocumentImplementation(serviceDocument); + return this.FlushAsync(); + }); + } +#endif + + /// + /// Writes the result of a $ref query as the message payload. + /// + /// The entity reference links to write as message payload. + /// It is the responsibility of this method to flush the output before the method returns. + internal override void WriteEntityReferenceLinks(ODataEntityReferenceLinks links) + { + this.AssertSynchronous(); + + this.WriteEntityReferenceLinksImplementation(links); + this.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously writes the result of a $ref query as the message payload. + /// + /// The entity reference links to write as message payload. + /// A task representing the asynchronous writing of the entity reference links. + /// It is the responsibility of this method to flush the output before the task finishes. + internal override Task WriteEntityReferenceLinksAsync(ODataEntityReferenceLinks links) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + this.WriteEntityReferenceLinksImplementation(links); + return this.FlushAsync(); + }); + } +#endif + + /// + /// Writes a singleton result of a $ref query as the message payload. + /// + /// The entity reference link to write as message payload. + /// It is the responsibility of this method to flush the output before the method returns. + internal override void WriteEntityReferenceLink(ODataEntityReferenceLink link) + { + this.AssertSynchronous(); + + this.WriteEntityReferenceLinkImplementation(link); + this.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously writes a singleton result of a $ref query as the message payload. + /// + /// The link result to write as message payload. + /// A running task representing the writing of the link. + /// It is the responsibility of this method to flush the output before the task finishes. + internal override Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink link) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + this.WriteEntityReferenceLinkImplementation(link); + return this.FlushAsync(); + }); + } +#endif + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "textWriter", Justification = "We don't dispose the jsonWriter or textWriter, instead we dispose the underlying stream directly.")] + protected override void Dispose(bool disposing) + { + try + { + if (this.messageOutputStream != null) + { + // JsonWriter.Flush will call the underlying TextWriter.Flush. + // The TextWriter.Flush (Which is in fact StreamWriter.Flush) will call the underlying Stream.Flush. + this.jsonWriter.Flush(); + + JsonWriter writer = this.jsonWriter as JsonWriter; + if (writer != null) + { + writer.Dispose(); + } + + // In the async case the underlying stream is the async buffered stream, so we have to flush that explicitly. + if (this.asynchronousOutputStream != null) + { + this.asynchronousOutputStream.FlushSync(); + this.asynchronousOutputStream.Dispose(); + } + + // Dispose the message stream (note that we OWN this stream, so we always dispose it). + this.messageOutputStream.Dispose(); + } + + if (this.BinaryValueStream != null) + { + this.BinaryValueStream.Flush(); + this.BinaryValueStream.Dispose(); + } + + if (this.StringWriter != null) + { + this.StringWriter.Flush(); + this.StringWriter.Dispose(); + } + } + finally + { + this.messageOutputStream = null; + this.asynchronousOutputStream = null; + this.BinaryValueStream = null; + this.textWriter = null; + this.jsonWriter = null; + this.StringWriter = null; + } + + base.Dispose(disposing); + } + + private static IJsonWriter CreateJsonWriter(IServiceProvider container, TextWriter textWriter, bool isIeee754Compatible, ODataMessageWriterSettings writerSettings) + { + IJsonWriter jsonWriter; + if (container == null) + { + jsonWriter = new JsonWriter(textWriter, isIeee754Compatible); + } + else + { + IJsonWriterFactory jsonWriterFactory = container.GetRequiredService(); + jsonWriter = jsonWriterFactory.CreateJsonWriter(textWriter, isIeee754Compatible); + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + } + + JsonWriter writer = jsonWriter as JsonWriter; + if (writer != null && writerSettings.ArrayPool != null) + { + writer.ArrayPool = writerSettings.ArrayPool; + } + + return jsonWriter; + } + + /// + /// Creates an to write a resource set. + /// + /// The entity set we are going to write entities for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true means writing a resource set into a uri operation parameter, false writing a resource set in other payloads. + /// true means writing a delta resource set. + /// The created writer. + private ODataWriter CreateODataResourceSetWriterImplementation(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType, bool writingParameter, bool writingDelta) + { + ODataJsonLightWriter odataJsonWriter = new ODataJsonLightWriter(this, entitySet, resourceType, /*writingResourceSet*/true, writingParameter, writingDelta); + this.outputInStreamErrorListener = odataJsonWriter; + return odataJsonWriter; + } + + /// + /// Creates an to write a delta response. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + private ODataDeltaWriter CreateODataDeltaWriterImplementation(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + ODataJsonLightDeltaWriter odataJsonDeltaWriter = new ODataJsonLightDeltaWriter(this, entitySet, entityType); + this.outputInStreamErrorListener = odataJsonDeltaWriter; + return odataJsonDeltaWriter; + } + + /// + /// Creates an to write a resource. + /// + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + private ODataWriter CreateODataResourceWriterImplementation(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + ODataJsonLightWriter odataJsonWriter = new ODataJsonLightWriter(this, navigationSource, resourceType, /*writingResourceSet*/false); + this.outputInStreamErrorListener = odataJsonWriter; + return odataJsonWriter; + } + + /// + /// Creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The item type of the collection being written or null if no metadata is available. + /// The created collection writer. + private ODataCollectionWriter CreateODataCollectionWriterImplementation(IEdmTypeReference itemTypeReference) + { + ODataJsonLightCollectionWriter jsonLightCollectionWriter = new ODataJsonLightCollectionWriter(this, itemTypeReference); + this.outputInStreamErrorListener = jsonLightCollectionWriter; + return jsonLightCollectionWriter; + } + + /// + /// Creates an to write a parameter payload. + /// + /// The operation whose parameters will be written. + /// The created parameter writer. + private ODataParameterWriter CreateODataParameterWriterImplementation(IEdmOperation operation) + { + ODataJsonLightParameterWriter jsonLightParameterWriter = new ODataJsonLightParameterWriter(this, operation); + this.outputInStreamErrorListener = jsonLightParameterWriter; + return jsonLightParameterWriter; + } + + /// + /// Creates a concrete instance. + /// + /// The newly created batch writer. + private ODataBatchWriter CreateODataBatchWriterImplementation() + { + ODataBatchWriter batchWriter = new ODataJsonLightBatchWriter(this); + this.outputInStreamErrorListener = batchWriter; + return batchWriter; + } + + /// + /// Writes an in-stream error. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + private void WriteInStreamErrorImplementation(ODataError error, bool includeDebugInformation) + { + if (this.outputInStreamErrorListener != null) + { + this.outputInStreamErrorListener.OnInStreamError(); + } + + JsonLightInstanceAnnotationWriter instanceAnnotationWriter = new JsonLightInstanceAnnotationWriter(new ODataJsonLightValueSerializer(this), this.TypeNameOracle); + ODataJsonWriterUtils.WriteError(this.JsonWriter, instanceAnnotationWriter.WriteInstanceAnnotationsForError, error, includeDebugInformation, this.MessageWriterSettings.MessageQuotas.MaxNestingDepth, /*writingJsonLight*/ true); + } + + /// + /// Writes an as message payload. + /// + /// The property to write. + private void WritePropertyImplementation(ODataProperty property) + { + ODataJsonLightPropertySerializer jsonLightPropertySerializer = new ODataJsonLightPropertySerializer(this, /*initContextUriBuilder*/ true); + jsonLightPropertySerializer.WriteTopLevelProperty(property); + } + + /// + /// Writes a service document with the specified + /// as message payload. + /// + /// The service document to write. + private void WriteServiceDocumentImplementation(ODataServiceDocument serviceDocument) + { + ODataJsonLightServiceDocumentSerializer jsonLightServiceDocumentSerializer = new ODataJsonLightServiceDocumentSerializer(this); + jsonLightServiceDocumentSerializer.WriteServiceDocument(serviceDocument); + } + + /// + /// Writes an as the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + private void WriteErrorImplementation(ODataError error, bool includeDebugInformation) + { + ODataJsonLightSerializer jsonLightSerializer = new ODataJsonLightSerializer(this, false); + jsonLightSerializer.WriteTopLevelError(error, includeDebugInformation); + } + + /// + /// Writes the result of a $ref query as the message payload. + /// + /// The entity reference links to write as message payload. + private void WriteEntityReferenceLinksImplementation(ODataEntityReferenceLinks links) + { + ODataJsonLightEntityReferenceLinkSerializer jsonLightEntityReferenceLinkSerializer = new ODataJsonLightEntityReferenceLinkSerializer(this); + jsonLightEntityReferenceLinkSerializer.WriteEntityReferenceLinks(links); + } + + /// + /// Writes a singleton result of a $ref query as the message payload. + /// + /// The entity reference link to write as message payload. + private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink link) + { + ODataJsonLightEntityReferenceLinkSerializer jsonLightEntityReferenceLinkSerializer = new ODataJsonLightEntityReferenceLinkSerializer(this); + jsonLightEntityReferenceLinkSerializer.WriteEntityReferenceLink(link); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterDeserializer.cs new file mode 100644 index 0000000..8ddd2a2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterDeserializer.cs @@ -0,0 +1,220 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// OData JsonLight deserializer for parameter payloads. + /// + internal sealed class ODataJsonLightParameterDeserializer : ODataJsonLightPropertyAndValueDeserializer + { + /// OData property annotation reader for parameter payloads. + /// OData property annotations are not supported in parameter payloads. + private static readonly Func propertyAnnotationValueReader = + annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightParameterDeserializer_PropertyAnnotationForParameters); }; + + /// The JSON Light parameter reader. + private readonly ODataJsonLightParameterReader parameterReader; + + /// + /// Constructor. + /// + /// The JSON Light parameter reader. + /// The JsonLight input context to read from. + internal ODataJsonLightParameterDeserializer(ODataJsonLightParameterReader parameterReader, ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + this.parameterReader = parameterReader; + } + + /// + /// Reads the next parameter from the parameters payload. + /// + /// The duplicate property names checker used to read a parameter payload. + /// true if a parameter was read from the payload; otherwise false. + /// + /// Pre-Condition: Property or EndObject the property node of the parameter to read or the end object node if there are not parameters + /// Post-Condition: Property or EndObject the node after the property value of a primitive, complex or null collection parameter + /// Any the start of the value representing a non-null collection parameter (the collection reader will fail if this is not a StartArray node) + /// + internal bool ReadNextParameter(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + bool parameterRead = false; + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + bool foundCustomInstanceAnnotation = false; + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationValueReader, + (propertyParsingResult, parameterName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + // OData instance annotations are not supported in parameter payloads. + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(parameterName)); + + case PropertyParsingResult.CustomInstanceAnnotation: + this.JsonReader.SkipValue(); + foundCustomInstanceAnnotation = true; + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightParameterDeserializer_PropertyAnnotationWithoutPropertyForParameters(parameterName)); + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(parameterName)); + + case PropertyParsingResult.PropertyWithValue: + IEdmTypeReference parameterTypeReference = this.parameterReader.GetParameterTypeReference(parameterName); + Debug.Assert(parameterTypeReference != null, "parameterTypeReference != null"); + + ODataParameterReaderState state; + object parameterValue; + EdmTypeKind parameterTypeKind = parameterTypeReference.TypeKind(); + switch (parameterTypeKind) + { + case EdmTypeKind.Primitive: + IEdmPrimitiveTypeReference primitiveTypeReference = parameterTypeReference.AsPrimitive(); + if (primitiveTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.Stream) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightParameterDeserializer_UnsupportedPrimitiveParameterType(parameterName, primitiveTypeReference.PrimitiveKind())); + } + + parameterValue = this.ReadNonEntityValue( + /*payloadTypeName*/ null, + primitiveTypeReference, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ false, + /*insideComplexValue*/ false, + parameterName); + state = ODataParameterReaderState.Value; + break; + + case EdmTypeKind.Enum: + IEdmEnumTypeReference enumTypeReference = parameterTypeReference.AsEnum(); + parameterValue = this.ReadNonEntityValue( + /*payloadTypeName*/ null, + enumTypeReference, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ false, + /*insideComplexValue*/ false, + parameterName); + state = ODataParameterReaderState.Value; + break; + + case EdmTypeKind.TypeDefinition: + IEdmTypeDefinitionReference typeDefinitionReference = parameterTypeReference.AsTypeDefinition(); + parameterValue = this.ReadNonEntityValue( + /*payloadTypeName*/ null, + typeDefinitionReference, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ false, + /*insideComplexValue*/ false, + parameterName); + state = ODataParameterReaderState.Value; + break; + + case EdmTypeKind.Complex: + case EdmTypeKind.Entity: + parameterValue = null; + state = ODataParameterReaderState.Resource; + break; + + case EdmTypeKind.Collection: + parameterValue = null; + if (this.JsonReader.NodeType == JsonNodeType.PrimitiveValue) + { + // NOTE: we support null collections in parameter payloads but nowhere else. + parameterValue = this.JsonReader.ReadPrimitiveValue(); + if (parameterValue != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightParameterDeserializer_NullCollectionExpected(JsonNodeType.PrimitiveValue, parameterValue)); + } + + state = ODataParameterReaderState.Value; + } + else if (((IEdmCollectionType)parameterTypeReference.Definition).ElementType.IsStructured()) + { + state = ODataParameterReaderState.ResourceSet; + } + else + { + state = ODataParameterReaderState.Collection; + } + + break; + default: + + throw new ODataException(ODataErrorStrings.ODataJsonLightParameterDeserializer_UnsupportedParameterTypeKind(parameterName, parameterTypeReference.TypeKind())); + } + + parameterRead = true; + this.parameterReader.EnterScope(state, parameterName, parameterValue); + Debug.Assert( + state == ODataParameterReaderState.Collection || state == ODataParameterReaderState.Resource || state == ODataParameterReaderState.ResourceSet || this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, + "Expected any node for a collection; 'Property' or 'EndObject' if it is a primitive or complex value."); + break; + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightParameterDeserializer_ReadNextParameter)); + } + }); + + if (foundCustomInstanceAnnotation) + { + return this.ReadNextParameter(propertyAndAnnotationCollector); + } + } + + if (!parameterRead && this.JsonReader.NodeType == JsonNodeType.EndObject) + { + this.JsonReader.ReadEndObject(); + this.ReadPayloadEnd(/*isReadingNestedPayload*/false); + + // Pop the scope for the previous parameter if there is any + if (this.parameterReader.State != ODataParameterReaderState.Start) + { + this.parameterReader.PopScope(this.parameterReader.State); + } + + // Pop the 'Start' scope and enter the 'Completed' scope + this.parameterReader.PopScope(ODataParameterReaderState.Start); + this.parameterReader.EnterScope(ODataParameterReaderState.Completed, /*parameterName*/null, /*parameterValue*/null); + this.AssertJsonCondition(JsonNodeType.EndOfInput); + } + + return parameterRead; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs new file mode 100644 index 0000000..d945edc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterReader.cs @@ -0,0 +1,304 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + + #endregion Namespaces + + /// + /// OData parameter reader for the Json Light format. + /// + internal sealed class ODataJsonLightParameterReader : ODataParameterReaderCoreAsync + { + /// The input to read the payload from. + private readonly ODataJsonLightInputContext jsonLightInputContext; + + /// The parameter deserializer to read the parameter input with. + private readonly ODataJsonLightParameterDeserializer jsonLightParameterDeserializer; + + /// The duplicate property names checker to use for the parameter payload. + private PropertyAndAnnotationCollector propertyAndAnnotationCollector; + + /// + /// Constructor. + /// + /// The input to read the payload from. + /// The operation import whose parameters are being read. + internal ODataJsonLightParameterReader(ODataJsonLightInputContext jsonLightInputContext, IEdmOperation operation) + : base(jsonLightInputContext, operation) + { + Debug.Assert(jsonLightInputContext != null, "jsonLightInputContext != null"); + Debug.Assert(jsonLightInputContext.ReadingResponse == false, "jsonLightInputContext.ReadingResponse == false"); + Debug.Assert(operation != null, "operationImport != null"); + + this.jsonLightInputContext = jsonLightInputContext; + this.jsonLightParameterDeserializer = new ODataJsonLightParameterDeserializer(this, jsonLightInputContext); + Debug.Assert(this.jsonLightInputContext.Model.IsUserModel(), "this.jsonLightInputContext.Model.IsUserModel()"); + } + + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet. + /// Post-Condition: When the new state is Value, the reader is positioned at the closing '}' or at the name of the next parameter. + /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. + /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. + /// + protected override bool ReadAtStartImplementation() + { + Debug.Assert(this.State == ODataParameterReaderState.Start, "this.State == ODataParameterReaderState.Start"); + Debug.Assert(this.jsonLightParameterDeserializer.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None"); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + this.propertyAndAnnotationCollector = this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(); + + // The parameter payload looks like "{ param1 : value1, ..., paramN : valueN }", where each value can be primitive, complex, collection, entity, resource set or collection. + // Position the reader on the first node + this.jsonLightParameterDeserializer.ReadPayloadStart( + ODataPayloadKind.Parameter, + this.propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/true); + + return this.ReadAtStartImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the parameter reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet. + /// Post-Condition: When the new state is Value, the reader is positioned at the closing '}' or at the name of the next parameter. + /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. + /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. + /// + protected override Task ReadAtStartImplementationAsync() + { + Debug.Assert(this.State == ODataParameterReaderState.Start, "this.State == ODataParameterReaderState.Start"); + Debug.Assert(this.jsonLightParameterDeserializer.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None"); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + this.propertyAndAnnotationCollector = this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(); + + // The parameter payload looks like "{ param1 : value1, ..., paramN : valueN }", where each value can be primitive, complex, collection, entity, resource set or collection. + // Position the reader on the first node + return this.jsonLightParameterDeserializer.ReadPayloadStartAsync( + ODataPayloadKind.Parameter, + this.propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/true) + + .FollowOnSuccessWith(t => + this.ReadAtStartImplementationSynchronously()); + } +#endif + + /// + /// Implementation of the reader logic on the subsequent reads after the first parameter is read. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property or JsonNodeType.EndObject: assumes the last read puts the reader at the begining of the next parameter or at the end of the payload. + /// Post-Condition: When the new state is Value, the reader is positioned at the closing '}' or at the name of the next parameter. + /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. + /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. + /// + protected override bool ReadNextParameterImplementation() + { + return this.ReadNextParameterImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state Value, Resource, Resource Set or Collection state. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property or JsonNodeType.EndObject: assumes the last read puts the reader at the begining of the next parameter or at the end of the payload. + /// Post-Condition: When the new state is Value, the reader is positioned at the closing '}' or at the name of the next parameter. + /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. + /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. + /// + protected override Task ReadNextParameterImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadNextParameterImplementationSynchronously); + } +#endif + + /// + /// Creates an to read the resource value of type . + /// + /// Expected resource type to read. + /// An to read the resource value of type . + protected override ODataReader CreateResourceReader(IEdmStructuredType expectedResourceType) + { + return this.CreateResourceReaderSynchronously(expectedResourceType); + } + +#if PORTABLELIB + /// + /// Creates an to read the resource value of type . + /// + /// Expected entity type to read. + /// An to read the resource value of type . + protected override Task CreateResourceReaderAsync(IEdmStructuredType expectedResourceType) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceReaderSynchronously(expectedResourceType)); + } +#endif + + /// + /// Creates an to read the resource set value of type . + /// + /// Expected resource set element type to read. + /// An to read the resource set value of type . + protected override ODataReader CreateResourceSetReader(IEdmStructuredType expectedResourceType) + { + return this.CreateResourceSetReaderSynchronously(expectedResourceType); + } + +#if PORTABLELIB + /// + /// Cretes an to read the resource set value of type . + /// + /// Expected resource set element type to read. + /// An to read the resource set value of type . + protected override Task CreateResourceSetReaderAsync(IEdmStructuredType expectedResourceType) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResourceSetReaderSynchronously(expectedResourceType)); + } +#endif + + /// + /// Creates an to read the collection with type . + /// + /// Expected item type reference of the collection to read. + /// An to read the collection with type . + /// + /// Pre-Condition: Any: the reader should be on the start array node of the collection value; if it is not we let the collection reader fail. + /// Post-Condition: Any: the reader should be on the start array node of the collection value; if it is not we let the collection reader fail. + /// NOTE: this method does not move the reader. + /// + protected override ODataCollectionReader CreateCollectionReader(IEdmTypeReference expectedItemTypeReference) + { + return this.CreateCollectionReaderSynchronously(expectedItemTypeReference); + } + +#if PORTABLELIB + /// + /// Creates an to read the collection with type . + /// + /// Expected item type reference of the collection to read. + /// An to read the collection with type . + /// + /// Pre-Condition: Any: the reader should be on the start array node of the collection value; if it is not we let the collection reader fail. + /// Post-Condition: Any: the reader should be on the start array node of the collection value; if it is not we let the collection reader fail. + /// NOTE: this method does not move the reader. + /// + protected override Task CreateCollectionReaderAsync(IEdmTypeReference expectedItemTypeReference) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateCollectionReaderSynchronously(expectedItemTypeReference)); + } +#endif + + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet. + /// Post-Condition: When the new state is Value, the reader is positioned at the closing '}' or at the name of the next parameter. + /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. + /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. + /// + private bool ReadAtStartImplementationSynchronously() + { + if (this.jsonLightInputContext.JsonReader.NodeType == JsonNodeType.EndOfInput) + { + this.PopScope(ODataParameterReaderState.Start); + this.EnterScope(ODataParameterReaderState.Completed, null, null); + return false; + } + + return this.jsonLightParameterDeserializer.ReadNextParameter(this.propertyAndAnnotationCollector); + } + + /// + /// Implementation of the reader logic on the subsequent reads after the first parameter is read. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property or JsonNodeType.EndObject: assumes the last read puts the reader at the begining of the next parameter or at the end of the payload. + /// Post-Condition: When the new state is Value, the reader is positioned at the closing '}' or at the name of the next parameter. + /// When the new state is Resource, the reader is positioned at the starting '{' of the resource payload. + /// When the new state is Resource Set or Collection, the reader is positioned at the starting '[' of the resource set or collection payload. + /// + private bool ReadNextParameterImplementationSynchronously() + { + Debug.Assert( + this.State != ODataParameterReaderState.Start && + this.State != ODataParameterReaderState.Exception && + this.State != ODataParameterReaderState.Completed, + "The current state must not be Start, Exception or Completed."); + + this.PopScope(this.State); + return this.jsonLightParameterDeserializer.ReadNextParameter(this.propertyAndAnnotationCollector); + } + + /// + /// Creates an to read the resource value of type . + /// + /// Expected entity type to read. + /// An to read the resource value of type . + private ODataReader CreateResourceReaderSynchronously(IEdmStructuredType expectedResourceType) + { + Debug.Assert(expectedResourceType != null, "expectedResourceType != null"); + return new ODataJsonLightReader(this.jsonLightInputContext, null, expectedResourceType, false /*readingResourceSet*/, true /*readingParameter*/, false /*readingDelta*/, this /*IODataReaderListener*/); + } + + /// + /// Creates an to read the resource set value of type . + /// + /// Expected resource set element type to read. + /// An to read the resource set value of type . + private ODataReader CreateResourceSetReaderSynchronously(IEdmStructuredType expectedResourceType) + { + Debug.Assert(expectedResourceType != null, "expectedResourceType != null"); + return new ODataJsonLightReader(this.jsonLightInputContext, null, expectedResourceType, true /*readingResourceSet*/, true /*readingParameter*/, false /*readingDelta*/, this /*IODataReaderListener*/); + } + + /// + /// Creates an to read the collection with type . + /// + /// Expected item type reference of the collection to read. + /// An to read the collection with type . + /// + /// Pre-Condition: Any: the reader should be on the start array node of the collection value; if it is not we let the collection reader fail. + /// Post-Condition: Any: the reader should be on the start array node of the collection value; if it is not we let the collection reader fail. + /// NOTE: this method does not move the reader. + /// + private ODataCollectionReader CreateCollectionReaderSynchronously(IEdmTypeReference expectedItemTypeReference) + { + Debug.Assert(this.jsonLightInputContext.Model.IsUserModel(), "Should have verified that we created the parameter reader with a user model."); + Debug.Assert(expectedItemTypeReference != null, "expectedItemTypeReference != null"); + return new ODataJsonLightCollectionReader(this.jsonLightInputContext, expectedItemTypeReference, this /*IODataReaderListener*/); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterWriter.cs new file mode 100644 index 0000000..5290ca0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightParameterWriter.cs @@ -0,0 +1,162 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// ODataParameterWriter for the JsonLight format. + /// + internal sealed class ODataJsonLightParameterWriter : ODataParameterWriterCore + { + /// + /// The output context to write to. + /// + private readonly ODataJsonLightOutputContext jsonLightOutputContext; + + /// + /// The JsonLight property and value serializer to use. + /// + private readonly ODataJsonLightValueSerializer jsonLightValueSerializer; + + /// + /// Constructor. + /// + /// The output context to write to. + /// The operation import whose parameters will be written. + internal ODataJsonLightParameterWriter(ODataJsonLightOutputContext jsonLightOutputContext, IEdmOperation operation) + : base(jsonLightOutputContext, operation) + { + Debug.Assert(jsonLightOutputContext != null, "jsonLightOutputContext != null"); + + this.jsonLightOutputContext = jsonLightOutputContext; + this.jsonLightValueSerializer = new ODataJsonLightValueSerializer(this.jsonLightOutputContext); + } + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + protected override void VerifyNotDisposed() + { + this.jsonLightOutputContext.VerifyNotDisposed(); + } + + /// + /// Flush the output. + /// + protected override void FlushSynchronously() + { + this.jsonLightOutputContext.Flush(); + } + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected override Task FlushAsynchronously() + { + return this.jsonLightOutputContext.FlushAsync(); + } +#endif + + /// + /// Start writing an OData payload. + /// + protected override void StartPayload() + { + // NOTE: we are always writing a request payload here. + this.jsonLightValueSerializer.WritePayloadStart(); + this.jsonLightOutputContext.JsonWriter.StartObjectScope(); + } + + /// + /// Finish writing an OData payload. + /// + protected override void EndPayload() + { + // NOTE: we are always writing a request payload here. + this.jsonLightOutputContext.JsonWriter.EndObjectScope(); + this.jsonLightValueSerializer.WritePayloadEnd(); + } + + /// + /// Writes a value parameter (either primitive or enum) + /// + /// The name of the parameter to write. + /// The value of the parameter to write. + /// The expected type reference of the parameter value. + protected override void WriteValueParameter(string parameterName, object parameterValue, IEdmTypeReference expectedTypeReference) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + this.jsonLightOutputContext.JsonWriter.WriteName(parameterName); + if (parameterValue == null) + { + this.jsonLightOutputContext.JsonWriter.WriteValue((string)null); + } + else + { + ODataEnumValue enumVal = parameterValue as ODataEnumValue; + if (enumVal != null) + { + this.jsonLightValueSerializer.WriteEnumValue(enumVal, expectedTypeReference); + } + else + { + Debug.Assert(!(parameterValue is ODataCollectionValue), "!(parameterValue is ODataCollectionValue)"); + Debug.Assert(!(parameterValue is ODataStreamReferenceValue), "!(parameterValue is ODataStreamReferenceValue)"); + Debug.Assert(!(parameterValue is Stream), "!(parameterValue is Stream)"); + this.jsonLightValueSerializer.WritePrimitiveValue(parameterValue, expectedTypeReference); + } + } + } + + /// + /// Creates a format specific to write the value of a collection parameter. + /// + /// The name of the collection parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + protected override ODataCollectionWriter CreateFormatCollectionWriter(string parameterName, IEdmTypeReference expectedItemType) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + this.jsonLightOutputContext.JsonWriter.WriteName(parameterName); + return new ODataJsonLightCollectionWriter(this.jsonLightOutputContext, expectedItemType, /*listener*/this); + } + + /// Creates a format specific to write a resource. + /// The name of the parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + protected override ODataWriter CreateFormatResourceWriter(string parameterName, IEdmTypeReference expectedItemType) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + this.jsonLightOutputContext.JsonWriter.WriteName(parameterName); + return new ODataJsonLightWriter(this.jsonLightOutputContext, null, null, /*writingResourceSet*/false, /*writingParameter*/true, /*writingDelta*/false, /*listener*/this); + } + + /// Creates a format specific to write a resource set. + /// The name of the parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + protected override ODataWriter CreateFormatResourceSetWriter(string parameterName, IEdmTypeReference expectedItemType) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + this.jsonLightOutputContext.JsonWriter.WriteName(parameterName); + return new ODataJsonLightWriter(this.jsonLightOutputContext, null, null, /*writingResourceSet*/true, /*writingParameter*/true, /*writingDelta*/false, /*listener*/this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPayloadKindDetectionDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPayloadKindDetectionDeserializer.cs new file mode 100644 index 0000000..585a24e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPayloadKindDetectionDeserializer.cs @@ -0,0 +1,183 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// OData JsonLight deserializer for detecting the payload kind of a JsonLight payload. + /// + internal sealed class ODataJsonLightPayloadKindDetectionDeserializer : ODataJsonLightPropertyAndValueDeserializer + { + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + internal ODataJsonLightPayloadKindDetectionDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + } + + /// + /// Detects the payload kind(s). + /// + /// Additional information available for the payload kind detection. + /// An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + internal IEnumerable DetectPayloadKind(ODataPayloadKindDetectionInfo detectionInfo) + { + Debug.Assert(detectionInfo != null, "detectionInfo != null"); + Debug.Assert(this.ReadingResponse, "Payload kind detection is only supported in responses."); + + // prevent the buffering JSON reader from detecting in-stream errors - we read the error ourselves + this.JsonReader.DisableInStreamErrorDetection = true; + + try + { + this.ReadPayloadStart( + ODataPayloadKind.Unsupported, + /*propertyAndAnnotationCollector*/null, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false); + return this.DetectPayloadKindImplementation(detectionInfo); + } + catch (ODataException) + { + // If we are not able to read the payload in the expected JSON format/structure + // return no detected payload kind below. + return Enumerable.Empty(); + } + finally + { + this.JsonReader.DisableInStreamErrorDetection = false; + } + } + +#if PORTABLELIB + /// + /// Detects the payload kind(s). + /// + /// Additional information available for the payload kind detection. + /// A task which returns an enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + internal Task> DetectPayloadKindAsync(ODataPayloadKindDetectionInfo detectionInfo) + { + Debug.Assert(detectionInfo != null, "detectionInfo != null"); + Debug.Assert(this.ReadingResponse, "Payload kind detection is only supported in responses."); + + // prevent the buffering JSON reader from detecting in-stream errors - we read the error ourselves + this.JsonReader.DisableInStreamErrorDetection = true; + + return this.ReadPayloadStartAsync( + ODataPayloadKind.Unsupported, + /*propertyAndAnnotationCollector*/null, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false) + + .FollowOnSuccessWith(t => + { + return this.DetectPayloadKindImplementation(detectionInfo); + }) + + .FollowOnFaultAndCatchExceptionWith, ODataException>(t => + { + // If we are not able to read the payload in the expected JSON format/structure + // return no detected payload kind below. + return Enumerable.Empty(); + }) + + .FollowAlwaysWith(t => + { + this.JsonReader.DisableInStreamErrorDetection = false; + }); + } +#endif + + /// + /// Detects the payload kind(s). + /// + /// Additional information available for the payload kind detection. + /// An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + private IEnumerable DetectPayloadKindImplementation(ODataPayloadKindDetectionInfo detectionInfo) + { + Debug.Assert(detectionInfo != null, "detectionInfo != null"); + Debug.Assert(this.JsonReader.DisableInStreamErrorDetection, "The in-stream error detection should be disabled for payload kind detection."); + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // If we found a context URI and parsed it, look at the detected payload kind and return it. + if (this.ContextUriParseResult != null) + { + return this.ContextUriParseResult.DetectedPayloadKinds; + } + + // Otherwise this is a payload without context URI and we have to start sniffing; only error payloads + // don't have a context URI so check for a single 'error' property (ignoring custom annotations). + ODataError error = null; + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + string propertyName = this.JsonReader.ReadPropertyName(); + string annotatedPropertyName, annotationName; + if (!ODataJsonLightDeserializer.TryParsePropertyAnnotation(propertyName, out annotatedPropertyName, out annotationName)) + { + if (ODataJsonLightReaderUtils.IsAnnotationProperty(propertyName)) + { + if (propertyName != null && propertyName.StartsWith(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + JsonLightConstants.ODataAnnotationNamespacePrefix, System.StringComparison.Ordinal)) + { + // Any @odata.* instance annotations are not allowed for errors. + return Enumerable.Empty(); + } + else + { + // Skip custom instance annotations + this.JsonReader.SkipValue(); + } + } + else + { + if (string.CompareOrdinal(JsonLightConstants.ODataErrorPropertyName, propertyName) == 0) + { + // If we find multiple errors or an invalid error value, this is not an error payload. + if (error != null || !this.JsonReader.StartBufferingAndTryToReadInStreamErrorPropertyValue(out error)) + { + return Enumerable.Empty(); + } + + // At this point we successfully read the first error property. + // Skip the error value and check whether there are more properties. + this.JsonReader.SkipValue(); + } + else + { + // if it contains non-annotation property, it is not an error payload. + return Enumerable.Empty(); + } + } + } + else + { + // Property annotation + return Enumerable.Empty(); + } + } + + // If we got here without finding a context URI or an error payload, we don't know what this is. + if (error == null) + { + return Enumerable.Empty(); + } + + return new ODataPayloadKind[] { ODataPayloadKind.Error }; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs new file mode 100644 index 0000000..6a38c34 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs @@ -0,0 +1,2112 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// OData JsonLight deserializer for properties and value types. + /// + internal class ODataJsonLightPropertyAndValueDeserializer : ODataJsonLightDeserializer + { + /// A sentinel value indicating a missing property value. + private static readonly object missingPropertyValue = new object(); + + /// + /// The current recursion depth of values read by this deserializer, measured by the number of resource, collection, JSON object and JSON array values read so far. + /// + private int recursionDepth; + + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + internal ODataJsonLightPropertyAndValueDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + } + + /// + /// This method creates an reads the property from the input and + /// returns an representing the read property. + /// + /// The expected type reference of the property to read. + /// An representing the read property. + internal ODataProperty ReadTopLevelProperty(IEdmTypeReference expectedPropertyTypeReference) + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + this.ReadPayloadStart( + ODataPayloadKind.Property, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false); + + ODataProperty resultProperty = this.ReadTopLevelPropertyImplementation(expectedPropertyTypeReference, propertyAndAnnotationCollector); + + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return resultProperty; + } + +#if PORTABLELIB + /// + /// This method creates an reads the property from the input and + /// returns an representing the read property. + /// + /// The expected type reference of the property to read. + /// A task which returns an representing the read property. + internal Task ReadTopLevelPropertyAsync(IEdmTypeReference expectedPropertyTypeReference) + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + return this.ReadPayloadStartAsync( + ODataPayloadKind.Property, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false) + + .FollowOnSuccessWith(t => + { + ODataProperty resultProperty = this.ReadTopLevelPropertyImplementation(expectedPropertyTypeReference, propertyAndAnnotationCollector); + + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return resultProperty; + }); + } +#endif + + /// + /// Reads a primitive value, enum, resource (complex or entity) value or collection. + /// + /// The type name read from the payload as a property annotation, or null if none is available. + /// The expected type reference of the property value. + /// The duplicate property names checker to use - if null the method should create a new one if necessary. + /// The collection validator instance if no expected item type has been specified; otherwise null. + /// true to validate null values; otherwise false. + /// true if we are reading a top-level property value; otherwise false. + /// true if we are reading a resource value and the reader is already positioned inside the resource value; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// Indicates whether the property is dynamic or unknown. + /// The value of the property read. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue - the value of the property is a primitive value + /// JsonNodeType.StartObject - the value of the property is an object + /// JsonNodeType.StartArray - the value of the property is an array - method will fail in this case. + /// Post-Condition: almost anything - the node after the property value. + /// + /// Returns the value of the property read, which can be one of: + /// - null + /// - primitive value + /// - + /// - + /// - + /// + internal object ReadNonEntityValue( + string payloadTypeName, + IEdmTypeReference expectedValueTypeReference, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + CollectionWithoutExpectedTypeValidator collectionValidator, + bool validateNullValue, + bool isTopLevelPropertyValue, + bool insideResourceValue, + string propertyName, + bool? isDynamicProperty = null) + { + this.AssertRecursionDepthIsZero(); + object nonEntityValue = this.ReadNonEntityValueImplementation( + payloadTypeName, + expectedValueTypeReference, + propertyAndAnnotationCollector, + collectionValidator, + validateNullValue, + isTopLevelPropertyValue, + insideResourceValue, + propertyName, + isDynamicProperty); + this.AssertRecursionDepthIsZero(); + + return nonEntityValue; + } + + /// + /// Reads the value of the instance annotation. + /// + /// The duplicate property names checker instance. + /// The name of the instance annotation. + /// Returns the value of the instance annotation. + internal object ReadCustomInstanceAnnotationValue(PropertyAndAnnotationCollector propertyAndAnnotationCollector, string name) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + Debug.Assert(!string.IsNullOrEmpty(name), "!string.IsNullOrEmpty(name)"); + + object propertyAnnotation; + string odataType = null; + if (propertyAndAnnotationCollector.GetODataPropertyAnnotations(name) + .TryGetValue(ODataAnnotationNames.ODataType, out propertyAnnotation)) + { + odataType = ReaderUtils.AddEdmPrefixOfTypeName(ReaderUtils.RemovePrefixOfTypeName((string)propertyAnnotation)); + } + + return ReadODataOrCustomInstanceAnnotationValue(name, odataType); + } + + /// + /// Reads bulit-in "odata." or custom instance annotation's value. + /// + /// The annotation name. + /// the odata.type value if exists. + /// The annotation value. + internal object ReadODataOrCustomInstanceAnnotationValue(string annotationName, string odataType) + { + // If this term is defined in the model, look up its type. If the term is not in the model, this will be null. + IEdmTypeReference expectedTypeFromTerm = MetadataUtils.LookupTypeOfTerm(annotationName, this.Model); + object customInstanceAnnotationValue = this.ReadNonEntityValueImplementation( + odataType, + expectedTypeFromTerm, + null, /*propertyAndAnnotationCollector*/ + null, /*collectionValidator*/ + false, /*validateNullValue*/ + false, /*isTopLevelPropertyValue*/ + false, /*insideResourceValue Always pass false here to try read @odata.type annotation in custom instance annotations*/ + annotationName); + return customInstanceAnnotationValue; + } + + /// + /// Resolve the IEdmTypeReference of the current untyped value to be read. + /// + /// The current JsonReader NodeType. + /// The current JsonReader Value + /// The 'odata.type' annotation in payload. + /// The payloadTypeReference of 'odata.type'. + /// Function that takes a primitive value and returns an . + /// Whether unknown properties should be read as a raw string value. + /// Whether to generate a type if not already part of the model. + /// The of the current value to be read. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Each code path casts to bool at most one time, and only if needed.")] + internal static IEdmTypeReference ResolveUntypedType( + JsonNodeType jsonReaderNodeType, + object jsonReaderValue, + string payloadTypeName, + IEdmTypeReference payloadTypeReference, + Func primitiveTypeResolver, + bool readUntypedAsString, + bool generateTypeIfMissing) + { + if (payloadTypeReference != null && (payloadTypeReference.TypeKind() != EdmTypeKind.Untyped || readUntypedAsString)) + { + return payloadTypeReference; + } + + if (readUntypedAsString) + { + if (jsonReaderNodeType == JsonNodeType.PrimitiveValue && jsonReaderValue is bool) + { + return EdmCoreModel.Instance.GetBoolean(true); + } + + return EdmCoreModel.Instance.GetUntyped(); + } + + string namespaceName; + string name; + bool isCollection; + IEdmTypeReference typeReference; + switch (jsonReaderNodeType) + { + case JsonNodeType.PrimitiveValue: + if (primitiveTypeResolver != null) + { + typeReference = primitiveTypeResolver(jsonReaderValue, payloadTypeName); + if (typeReference != null) + { + return typeReference; + } + } + + if (jsonReaderValue == null) + { + if (payloadTypeName != null) + { + TypeUtils.ParseQualifiedTypeName(payloadTypeName, out namespaceName, out name, out isCollection); + Debug.Assert(namespaceName != Metadata.EdmConstants.EdmNamespace, "If type was in the edm namespace it should already have been resolved"); + + typeReference = new EdmUntypedStructuredType(namespaceName, name).ToTypeReference(/*isNullable*/ true); + return isCollection ? new EdmCollectionType(typeReference).ToTypeReference(/*isNullable*/ true) : typeReference; + } + + typeReference = EdmCoreModel.Instance.GetString(/*isNullable*/ true); + } + else if (jsonReaderValue is bool) + { + typeReference = EdmCoreModel.Instance.GetBoolean(/*isNullable*/ true); + } + else if (jsonReaderValue is string) + { + typeReference = EdmCoreModel.Instance.GetString(/*isNullable*/ true); + } + else + { + typeReference = EdmCoreModel.Instance.GetDecimal(/*isNullable*/ true); + } + + if (payloadTypeName != null) + { + TypeUtils.ParseQualifiedTypeName(payloadTypeName, out namespaceName, out name, out isCollection); + if (isCollection) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected(payloadTypeName)); + } + + typeReference = new EdmTypeDefinition(namespaceName, name, typeReference.PrimitiveKind()).ToTypeReference(/*isNullable*/ true); + } + + return typeReference; + + case JsonNodeType.StartObject: + if (payloadTypeName != null && generateTypeIfMissing) + { + TypeUtils.ParseQualifiedTypeName(payloadTypeName, out namespaceName, out name, out isCollection); + if (isCollection) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected(payloadTypeName)); + } + + return new EdmUntypedStructuredType(namespaceName, name).ToTypeReference(/*isNullable*/ true); + } + + return new EdmUntypedStructuredType().ToTypeReference(/*isNullable*/ true); + + case JsonNodeType.StartArray: + if (payloadTypeName != null && generateTypeIfMissing) + { + TypeUtils.ParseQualifiedTypeName(payloadTypeName, out namespaceName, out name, out isCollection); + if (!isCollection) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected(payloadTypeName)); + } + + return new EdmCollectionType(new EdmUntypedStructuredType(namespaceName, name).ToTypeReference(/*isNullable*/ true)).ToTypeReference(/*isNullable*/true); + } + + return new EdmCollectionType(new EdmUntypedStructuredType().ToTypeReference(/*isNullable*/ true)).ToTypeReference(/*isNullable*/true); + + default: + return EdmCoreModel.Instance.GetUntyped(); + } + } + + /// + /// Try to read or peek the odata.type annotation. + /// + /// The current level's PropertyAndAnnotationCollector. + /// The property name. + /// If inside complex value. + /// The odata.type value or null. + protected string TryReadOrPeekPayloadType(PropertyAndAnnotationCollector propertyAndAnnotationCollector, string propertyName, bool insideResourceValue) + { + string payloadTypeName = ValidateDataPropertyTypeNameAnnotation(propertyAndAnnotationCollector, propertyName); + bool valueIsJsonObject = this.JsonReader.NodeType == JsonNodeType.StartObject; + if (string.IsNullOrEmpty(payloadTypeName) && valueIsJsonObject) + { + try + { + this.JsonReader.StartBuffering(); + + // If we have an object value initialize the duplicate property names checker + propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + // Read the payload type name + string typeName; + bool typeNameFoundInPayload = this.TryReadPayloadTypeFromObject( + propertyAndAnnotationCollector, + insideResourceValue, + out typeName); + if (typeNameFoundInPayload) + { + payloadTypeName = typeName; + } + } + finally + { + this.JsonReader.StopBuffering(); + } + } + + return payloadTypeName; + } + + /// + /// Reads an entity or complex type's undeclared property. + /// + /// The IODataJsonLightReaderResourceState. + /// Now this name can't be found in model. + /// bool + /// The read result. + protected ODataJsonLightReaderNestedResourceInfo InnerReadUndeclaredProperty(IODataJsonLightReaderResourceState resourceState, string propertyName, bool isTopLevelPropertyValue) + { + PropertyAndAnnotationCollector propertyAndAnnotationCollector = resourceState.PropertyAndAnnotationCollector; + bool insideResourceValue = false; + string outerPayloadTypeName = ValidateDataPropertyTypeNameAnnotation(propertyAndAnnotationCollector, propertyName); + string payloadTypeName = this.TryReadOrPeekPayloadType(propertyAndAnnotationCollector, propertyName, insideResourceValue); + EdmTypeKind payloadTypeKind; + IEdmType payloadType = ReaderValidationUtils.ResolvePayloadTypeName( + this.Model, + null, // expectedTypeReference + payloadTypeName, + EdmTypeKind.Complex, + this.MessageReaderSettings.ClientCustomTypeResolver, + out payloadTypeKind); + IEdmTypeReference payloadTypeReference = null; + if (!string.IsNullOrEmpty(payloadTypeName) && payloadType != null) + { + // only try resolving for known type (the below will throw on unknown type name) : + ODataTypeAnnotation typeAnnotation; + EdmTypeKind targetTypeKind; + payloadTypeReference = this.ReaderValidator.ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind.None, + /*expectStructuredType*/ null, + /*defaultPrimitivePayloadType*/ null, + null, // expectedTypeReference + payloadTypeName, + this.Model, + this.GetNonEntityValueKind, + out targetTypeKind, + out typeAnnotation); + } + + object propertyValue = null; + payloadTypeReference = ResolveUntypedType( + this.JsonReader.NodeType, + this.JsonReader.Value, + payloadTypeName, + payloadTypeReference, + this.MessageReaderSettings.PrimitiveTypeResolver, + this.MessageReaderSettings.ReadUntypedAsString, + !this.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata); + + if (payloadTypeReference.ToStructuredType() != null) + { + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = null; + + // Complex property or collection of complex property. + bool isCollection = payloadTypeReference.IsCollection(); + ValidateExpandedNestedResourceInfoPropertyValue(this.JsonReader, isCollection, propertyName); + if (isCollection) + { + readerNestedResourceInfo = this.ReadingResponse + ? ReadExpandedResourceSetNestedResourceInfo(resourceState, null, payloadTypeReference.ToStructuredType(), propertyName, /*isDeltaResourceSet*/ false) + : ReadEntityReferenceLinksForCollectionNavigationLinkInRequest(resourceState, null, propertyName, /*isExpanded*/ true); + } + else + { + readerNestedResourceInfo = this.ReadingResponse + ? ReadExpandedResourceNestedResourceInfo(resourceState, null, propertyName, payloadTypeReference.ToStructuredType(), this.MessageReaderSettings) + : ReadEntityReferenceLinkForSingletonNavigationLinkInRequest(resourceState, null, propertyName, /*isExpanded*/ true); + } + + resourceState.PropertyAndAnnotationCollector.ValidatePropertyUniquenessOnNestedResourceInfoStart(readerNestedResourceInfo.NestedResourceInfo); + + return readerNestedResourceInfo; + } + + if (!(payloadTypeReference is IEdmUntypedTypeReference)) + { + this.JsonReader.AssertNotBuffering(); + propertyValue = this.ReadNonEntityValueImplementation( + outerPayloadTypeName, + payloadTypeReference, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + false, // validateNullValue + isTopLevelPropertyValue, + insideResourceValue, + propertyName); + } + else + { + propertyValue = this.JsonReader.ReadAsUntypedOrNullValue(); + } + + this.JsonReader.AssertNotBuffering(); + Debug.Assert( + this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, + "Post-Condition: expected JsonNodeType.Property or JsonNodeType.EndObject"); + AddResourceProperty(resourceState, propertyName, propertyValue); + return null; + } + + /// + /// Validates that the value of a JSON property can represent expanded nested resource info. + /// + /// The IJsonReader. + /// true if the property is entity set reference property; false for a resource reference property, null if unknown. + /// Name for the navigation property, used in error message only. + protected static void ValidateExpandedNestedResourceInfoPropertyValue(IJsonReader jsonReader, bool? isCollection, string propertyName) + { + // an expanded link with resource requires a StartObject node here; + // an expanded link with resource set requires a StartArray node here; + // an expanded link with null resource requires a primitive null node here; + JsonNodeType nodeType = jsonReader.NodeType; + if (nodeType == JsonNodeType.StartArray) + { + if (isCollection == false) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_CannotReadSingletonNestedResource(nodeType, propertyName)); + } + } + else if ((nodeType == JsonNodeType.PrimitiveValue && jsonReader.Value == null) || nodeType == JsonNodeType.StartObject) + { + // Expanded resource (null or non-null) + if (isCollection == true) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_CannotReadCollectionNestedResource(nodeType, propertyName)); + } + } + else + { + Debug.Assert(nodeType == JsonNodeType.PrimitiveValue, "nodeType == JsonNodeType.PrimitiveValue"); + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_CannotReadNestedResource(propertyName)); + } + } + + /// + /// Reads non-expanded nested resource set. + /// + /// The state of the reader for resource to read. + /// The collection of complex property for which to read the nested resource info. null for undeclared property. + /// The item type of the resource set, which should be provided when the collectionProperty is undeclared. + /// The property name. + /// The nested resource info for the expanded link read. + /// + /// This method doesn't move the reader. + /// + protected static ODataJsonLightReaderNestedResourceInfo ReadNonExpandedResourceSetNestedResourceInfo(IODataJsonLightReaderResourceState resourceState, IEdmStructuralProperty collectionProperty, IEdmStructuredType nestedResourceType, string propertyName) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert((propertyName != null) || (collectionProperty != null && collectionProperty.Type.ToStructuredType().IsODataComplexTypeKind()) || (nestedResourceType != null && nestedResourceType.IsODataComplexTypeKind()), + "The property name shouldn't be null or the item in the collection property should be complex instance"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = propertyName, + IsCollection = true, + IsComplex = true + }; + + ODataResourceSet expandedResourceSet = CreateCollectionResourceSet(resourceState, propertyName); + return ODataJsonLightReaderNestedResourceInfo.CreateResourceSetReaderNestedResourceInfo(nestedResourceInfo, collectionProperty, nestedResourceType, expandedResourceSet); + } + + /// + /// Reads non-expanded resource nested resource info. + /// + /// The state of the reader for resource to read. + /// The complex property for which to read the nested resource info. null for undeclared property. + /// The nested resource type which should be provided for undeclared property. + /// The property name. + /// The nested resource info for the complex property to read. + /// + /// This method doesn't move the reader. + /// + protected static ODataJsonLightReaderNestedResourceInfo ReadNonExpandedResourceNestedResourceInfo(IODataJsonLightReaderResourceState resourceState, IEdmStructuralProperty complexProperty, IEdmStructuredType nestedResourceType, string propertyName) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(complexProperty != null || nestedResourceType != null || propertyName != null, "complexProperty != null || nestedResourceType != null || propertyName != null"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = propertyName, + IsCollection = false, + IsComplex = true + }; + + // Check the odata.type annotation for the complex property, it should show inside the complex object. + if (ValidateDataPropertyTypeNameAnnotation(resourceState.PropertyAndAnnotationCollector, nestedResourceInfo.Name) != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_ComplexValueWithPropertyTypeAnnotation(ODataAnnotationNames.ODataType)); + } + + return ODataJsonLightReaderNestedResourceInfo.CreateResourceReaderNestedResourceInfo(nestedResourceInfo, complexProperty, nestedResourceType); + } + + /// + /// Reads expanded resource nested resource info. + /// + /// The state of the reader for resource to read. + /// The navigation property for which to read the expanded link. null for undeclared property. + /// The property name. + /// The type of the property. + /// The ODataMessageReaderSettings. + /// The nested resource info for the expanded link read. + /// + /// This method doesn't move the reader. + /// + protected static ODataJsonLightReaderNestedResourceInfo ReadExpandedResourceNestedResourceInfo(IODataJsonLightReaderResourceState resourceState, IEdmNavigationProperty navigationProperty, string propertyName, IEdmStructuredType propertyType, ODataMessageReaderSettings messageReaderSettings) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(navigationProperty != null || propertyName != null, "navigationProperty != null || propertyName != null"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = propertyName, + IsCollection = false + }; + + foreach (var propertyAnnotation + in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(nestedResourceInfo.Name)) + { + switch (propertyAnnotation.Key) + { + case ODataAnnotationNames.ODataNavigationLinkUrl: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.navigationLinkUrl annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.Url = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataAssociationLinkUrl: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.associationLinkUrl annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.AssociationLinkUrl = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataContext: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.context annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.ContextUrl = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataType: + Debug.Assert(propertyAnnotation.Value is String && propertyAnnotation.Value != null, "The odata.type annotation should have been parsed as a non-null string."); + nestedResourceInfo.TypeAnnotation = new ODataTypeAnnotation((string)propertyAnnotation.Value); + break; + + default: + if (messageReaderSettings.ThrowOnUndeclaredPropertyForNonOpenType) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedExpandedSingletonNavigationLinkPropertyAnnotation(nestedResourceInfo.Name, propertyAnnotation.Key)); + } + + break; + } + } + + return ODataJsonLightReaderNestedResourceInfo.CreateResourceReaderNestedResourceInfo(nestedResourceInfo, navigationProperty, propertyType); + } + + /// + /// Reads expanded resource set nested resource info. + /// + /// The state of the reader for resource to read. + /// The navigation property for which to read the expanded link. null for undeclared property. + /// The type of the collection. + /// The property name. + /// The property being read represents a nested delta resource set. + /// The nested resource info for the expanded link read. + /// + /// This method doesn't move the reader. + /// + protected static ODataJsonLightReaderNestedResourceInfo ReadExpandedResourceSetNestedResourceInfo(IODataJsonLightReaderResourceState resourceState, IEdmNavigationProperty navigationProperty, IEdmStructuredType propertyType, string propertyName, bool isDeltaResourceSet) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(navigationProperty != null || propertyName != null, "navigationProperty != null || propertyName != null"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = propertyName, + IsCollection = true + }; + + ODataResourceSetBase expandedResourceSet; + if (isDeltaResourceSet) + { + expandedResourceSet = new ODataDeltaResourceSet(); + } + else + { + expandedResourceSet = new ODataResourceSet(); + } + + foreach (var propertyAnnotation + in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(nestedResourceInfo.Name)) + { + switch (propertyAnnotation.Key) + { + case ODataAnnotationNames.ODataNavigationLinkUrl: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.navigationLinkUrl annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.Url = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataAssociationLinkUrl: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.associationLinkUrl annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.AssociationLinkUrl = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataNextLink: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.nextLink annotation should have been parsed as a non-null Uri."); + expandedResourceSet.NextPageLink = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataCount: + Debug.Assert(propertyAnnotation.Value is long && propertyAnnotation.Value != null, "The odata.count annotation should have been parsed as a non-null long."); + expandedResourceSet.Count = (long?)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataContext: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.context annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.ContextUrl = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataType: + Debug.Assert(propertyAnnotation.Value != null, "The odata.type annotation should have been parsed as a string."); + expandedResourceSet.TypeName = (string)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataDeltaLink: // Delta links are not supported on expanded resource sets. + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedExpandedCollectionNavigationLinkPropertyAnnotation(nestedResourceInfo.Name, propertyAnnotation.Key)); + } + } + + return ODataJsonLightReaderNestedResourceInfo.CreateResourceSetReaderNestedResourceInfo(nestedResourceInfo, navigationProperty, propertyType, expandedResourceSet); + } + + /// + /// Reads a nested stream collection as nested resource set info. + /// + /// The state of the reader for resource to read. + /// The collection of stream property for which to read the nested resource info. null for undeclared property. + /// The property name. + /// They primitive type of the collection element + /// The nested resource info for the stream collection. + /// + /// This method doesn't move the reader. + /// + protected static ODataJsonLightReaderNestedResourceInfo ReadStreamCollectionNestedResourceInfo(IODataJsonLightReaderResourceState resourceState, IEdmStructuralProperty collectionProperty, string propertyName, IEdmType elementType) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert((propertyName != null) || (collectionProperty != null), + "The collection property and property name shouldn't both be null"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = propertyName, + IsCollection = true, + IsComplex = false + }; + + ODataResourceSet expandedResourceSet = CreateCollectionResourceSet(resourceState, propertyName); + + ODataJsonLightReaderNestedResourceInfo nestedInfo = ODataJsonLightReaderNestedResourceInfo.CreateResourceSetReaderNestedResourceInfo(nestedResourceInfo, collectionProperty, elementType, expandedResourceSet); + return nestedInfo; + } + + /// + /// Reads entity reference link for a singleton navigation link in request. + /// + /// The state of the reader for resource to read. + /// The navigation property for which to read the entity reference link. + /// The property name. + /// true if the navigation link is expanded. + /// The navigation link info for the entity reference link read. + /// + /// This method doesn't move the reader. + /// + protected static ODataJsonLightReaderNestedResourceInfo ReadEntityReferenceLinkForSingletonNavigationLinkInRequest( + IODataJsonLightReaderResourceState resourceState, + IEdmNavigationProperty navigationProperty, + string propertyName, + bool isExpanded) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(navigationProperty != null || propertyName != null, "navigationProperty != null || propertyName != null"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = propertyName, + IsCollection = false + }; + + ODataEntityReferenceLink entityReferenceLink = null; + foreach (var propertyAnnotation + in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(nestedResourceInfo.Name)) + { + switch (propertyAnnotation.Key) + { + case ODataAnnotationNames.ODataBind: + LinkedList entityReferenceLinksList = propertyAnnotation.Value as LinkedList; + if (entityReferenceLinksList != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_ArrayValueForSingletonBindPropertyAnnotation(nestedResourceInfo.Name, ODataAnnotationNames.ODataBind)); + } + + if (isExpanded) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_SingletonNavigationPropertyWithBindingAndValue(nestedResourceInfo.Name, ODataAnnotationNames.ODataBind)); + } + + Debug.Assert( + propertyAnnotation.Value is ODataEntityReferenceLink && propertyAnnotation.Value != null, + "The value of odata.bind property annotation must be either ODataEntityReferenceLink or List"); + entityReferenceLink = (ODataEntityReferenceLink)propertyAnnotation.Value; + break; + + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedNavigationLinkInRequestPropertyAnnotation( + nestedResourceInfo.Name, + propertyAnnotation.Key, + ODataAnnotationNames.ODataBind)); + } + } + + return ODataJsonLightReaderNestedResourceInfo.CreateSingletonEntityReferenceLinkInfo(nestedResourceInfo, navigationProperty, entityReferenceLink, isExpanded); + } + + /// + /// Reads entity reference links for a collection navigation link in request. + /// + /// The state of the reader for resource to read. + /// The navigation property for which to read the entity reference links. null for undeclared property. + /// The property name. + /// true if the navigation link is expanded. + /// The navigation link info for the entity reference links read. + /// + /// This method doesn't move the reader. + /// + protected static ODataJsonLightReaderNestedResourceInfo ReadEntityReferenceLinksForCollectionNavigationLinkInRequest( + IODataJsonLightReaderResourceState resourceState, + IEdmNavigationProperty navigationProperty, + string propertyName, + bool isExpanded) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(propertyName != null, "propertyName != null"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = propertyName, + IsCollection = true + }; + + LinkedList entityReferenceLinksList = null; + foreach (var propertyAnnotation + in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(nestedResourceInfo.Name)) + { + switch (propertyAnnotation.Key) + { + case ODataAnnotationNames.ODataBind: + ODataEntityReferenceLink entityReferenceLink = propertyAnnotation.Value as ODataEntityReferenceLink; + if (entityReferenceLink != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_StringValueForCollectionBindPropertyAnnotation(nestedResourceInfo.Name, ODataAnnotationNames.ODataBind)); + } + + Debug.Assert( + propertyAnnotation.Value is LinkedList && propertyAnnotation.Value != null, + "The value of odata.bind property annotation must be either ODataEntityReferenceLink or List"); + entityReferenceLinksList = (LinkedList)propertyAnnotation.Value; + break; + + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedNavigationLinkInRequestPropertyAnnotation( + nestedResourceInfo.Name, + propertyAnnotation.Key, + ODataAnnotationNames.ODataBind)); + } + } + + return ODataJsonLightReaderNestedResourceInfo.CreateCollectionEntityReferenceLinksInfo(nestedResourceInfo, navigationProperty, entityReferenceLinksList, isExpanded); + } + + /// + /// Gets and validates the type name annotation for the specified property. + /// + /// The duplicate property names checker in use for the resource content. + /// The name of the property to get the type name for. + /// The type name for the property or null if no type name was found. + protected static string ValidateDataPropertyTypeNameAnnotation(PropertyAndAnnotationCollector propertyAndAnnotationCollector, string propertyName) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + string propertyTypeName = null; + foreach (var propertyAnnotation + in propertyAndAnnotationCollector.GetODataPropertyAnnotations(propertyName)) + { + if (string.CompareOrdinal(propertyAnnotation.Key, ODataAnnotationNames.ODataType) != 0) + { + // here allow other annotation name than odata.type, instead of throwing: + // ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedDataPropertyAnnotation + continue; + } + + Debug.Assert(propertyAnnotation.Value is string && propertyAnnotation.Value != null, "The odata.type annotation should have been parsed as a non-null string."); + propertyTypeName = (string)propertyAnnotation.Value; + } + + if (propertyTypeName != null) + { + var validator = propertyAndAnnotationCollector.GetDerivedTypeValidator(propertyName); + if (validator != null) + { + validator.ValidateResourceType(propertyTypeName); + } + } + + return propertyTypeName; + } + + /// + /// Adds a new property to a resource. + /// + /// The resource state for the resource to add the property to. + /// The name of the property to add. + /// The value of the property to add. + /// The added ODataProperty. + protected static ODataProperty AddResourceProperty(IODataJsonLightReaderResourceState resourceState, string propertyName, object propertyValue) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + ODataProperty property = new ODataProperty { Name = propertyName, Value = propertyValue }; + AttachODataAnnotations(resourceState, propertyName, property); + foreach (var annotation + in resourceState.PropertyAndAnnotationCollector.GetCustomPropertyAnnotations(propertyName)) + { + if (annotation.Value != null) + { + // annotation.Value == null indicates that this annotation should be skipped. + property.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotation.Key, annotation.Value.ToODataValue())); + } + } + + // Debug.Assert(property.InstanceAnnotations.GroupBy(s => s.Name).Where(s => s.Count() > 1).Count() <= 0, + // "No annotation name should have been added into the InstanceAnnotations collection twice."); + resourceState.PropertyAndAnnotationCollector.CheckForDuplicatePropertyNames(property); + ODataResourceBase resource = resourceState.Resource; + Debug.Assert(resource != null, "resource != null"); + resource.Properties = resource.Properties.ConcatToReadOnlyEnumerable("Properties", property); + return property; + } + + protected static void AttachODataAnnotations(IODataJsonLightReaderResourceState resourceState, string propertyName, ODataProperty property) + { + foreach (var annotation + in propertyName.Length == 0 + ? resourceState.PropertyAndAnnotationCollector.GetODataScopeAnnotation() + : resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(propertyName)) + { + Debug.Assert(annotation.Value != null); + if (String.Equals(annotation.Key, ODataAnnotationNames.ODataType, StringComparison.Ordinal) + || String.Equals(annotation.Key, JsonLightConstants.SimplifiedODataTypePropertyName, StringComparison.Ordinal)) + { + property.TypeAnnotation = new ODataTypeAnnotation( + ReaderUtils.AddEdmPrefixOfTypeName(ReaderUtils.RemovePrefixOfTypeName((string)annotation.Value))); + } + else + { + Uri uri; + ODataValue val = (uri = annotation.Value as Uri) != null + ? new ODataPrimitiveValue(uri.OriginalString) + : annotation.Value.ToODataValue(); + property.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotation.Key, val, true)); + } + } + } + + /// + /// Tries to read an annotation as OData type name annotation. + /// + /// The annotation name on which value the reader is positioned on. + /// The read value of the annotation (string). + /// true if the annotation is an OData type name annotation, false otherwise. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue - the value of the annotation + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// Post-Condition: JsonNodeType.Property - the next property after the annotation + /// JsonNodeType.EndObject - end of the parent object + /// JsonNodeType.PrimitiveValue - the reader didn't move + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// + /// If the method returns true, it consumed the value of the annotation from the reader. + /// If it returns false, it didn't move the reader. + /// + protected bool TryReadODataTypeAnnotationValue(string annotationName, out string value) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + if (string.CompareOrdinal(annotationName, ODataAnnotationNames.ODataType) == 0) + { + value = this.ReadODataTypeAnnotationValue(); + return true; + } + + value = null; + return false; + } + + /// + /// Reads the value of the odata.type annotation. + /// + /// The type name read from the annotation. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue - the value of the annotation, will fail if it's not PrimitiveValue + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// Post-Condition: JsonNodeType.Property - the next property after the annotation + /// JsonNodeType.EndObject - end of the parent object + /// + protected string ReadODataTypeAnnotationValue() + { + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.StartArray); + + string typeName = ReaderUtils.AddEdmPrefixOfTypeName(ReaderUtils.RemovePrefixOfTypeName(this.JsonReader.ReadStringValue())); + if (typeName == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTypeName(typeName)); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return typeName; + } + + /// + /// Reads top-level property payload property annotation value. + /// + /// The name of the property annotation. + /// The value of the annotation read. + protected object ReadTypePropertyAnnotationValue(string propertyAnnotationName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyAnnotationName), "!string.IsNullOrEmpty(propertyAnnotationName)"); + Debug.Assert( + propertyAnnotationName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal), + "The method should only be called with OData. annotations"); + + string typeName; + if (this.TryReadODataTypeAnnotationValue(propertyAnnotationName, out typeName)) + { + return typeName; + } + + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyAnnotationName)); + } + + /// + /// Determines the value kind for a non-entity value (that is top-level property value, property value on a complex type, item in a collection) + /// + /// The type kind of the property value. + /// + /// Doesn't move the JSON reader. + /// + protected EdmTypeKind GetNonEntityValueKind() + { + // If we get here, we did not find a type name in the payload and don't have an expected type. + // This can only happen for error cases when using open properties (for declared properties we always + // have an expected type and for open properties we always require a type). We then decide based on + // the node type of the reader. + // PrimitiveValue - we know that it is a primitive value; + // StartArray - we know that we have a collection; + // Other - for a JSON object value (and if we did not already find a payload type name) + // we have already started reading the object to find a type name (and have failed) + // and might thus be on a Property or EndObject node. + // Also note that in this case we can't distinguish whether what we are looking at is + // a complex value or a spatial value (both are JSON objects). We will report + // 'Complex' in that case which will fail we an appropriate error message + // also for spatial ('value without type name found'). + switch (this.JsonReader.NodeType) + { + case JsonNodeType.PrimitiveValue: return EdmTypeKind.Primitive; + case JsonNodeType.StartArray: return EdmTypeKind.Collection; + default: return EdmTypeKind.Complex; + } + } + + /// + /// Creates an instance of an ODataResourceSet that represents a nested collection + /// + /// The current resource state + /// The name of the collection property being read + /// An ODataResourceSet with properties set from any instance annotations + private static ODataResourceSet CreateCollectionResourceSet(IODataJsonLightReaderResourceState resourceState, string propertyName) + { + ODataResourceSet collectionResourceSet = new ODataResourceSet(); + + foreach (var propertyAnnotation + in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(propertyName)) + { + switch (propertyAnnotation.Key) + { + case ODataAnnotationNames.ODataNextLink: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.nextLink annotation should have been parsed as a non-null Uri."); + collectionResourceSet.NextPageLink = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataCount: + Debug.Assert(propertyAnnotation.Value is long && propertyAnnotation.Value != null, "The odata.count annotation should have been parsed as a non-null long."); + collectionResourceSet.Count = (long?)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataType: + collectionResourceSet.TypeName = (string)propertyAnnotation.Value; + Debug.Assert(propertyAnnotation.Value is string && propertyAnnotation.Value != null, "The odata.type annotation should have been parsed as a non-null string."); + break; + + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedComplexCollectionPropertyAnnotation(propertyName, propertyAnnotation.Key)); + } + } + + return collectionResourceSet; + } + + /// + /// Tries to read an annotation as OData type name annotation. + /// + /// The read value of the annotation (string). + /// true if the annotation is an OData type name annotation, false otherwise. + /// + /// Pre-Condition: JsonNodeType.Property - the property that possibly is an odata.type instance annotation + /// Post-Condition: JsonNodeType.Property - the next property after the annotation or if the reader did not move + /// JsonNodeType.EndObject - end of the parent object + /// If the method returns true, it consumed the value of the annotation from the reader. + /// If it returns false, it didn't move the reader. + /// + private bool TryReadODataTypeAnnotation(out string payloadTypeName) + { + this.AssertJsonCondition(JsonNodeType.Property); + payloadTypeName = null; + + bool result = false; + string propertyName = this.JsonReader.GetPropertyName(); + if (string.CompareOrdinal(propertyName, JsonLightConstants.ODataPropertyAnnotationSeparatorChar + ODataAnnotationNames.ODataType) == 0 + || this.CompareSimplifiedODataAnnotation(JsonLightConstants.SimplifiedODataTypePropertyName, propertyName)) + { + // Read over the property name + this.JsonReader.ReadNext(); + payloadTypeName = this.ReadODataTypeAnnotationValue(); + result = true; + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return result; + } + + /// + /// This method creates an reads the property from the input and + /// returns an representing the read property. + /// + /// The expected type reference of the property to read. + /// The duplicate property names checker to use. + /// An representing the read property. + /// + /// The method assumes that the ReadPayloadStart has already been called and it will not call ReadPayloadEnd. + /// + private ODataProperty ReadTopLevelPropertyImplementation(IEdmTypeReference expectedPropertyTypeReference, PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert( + expectedPropertyTypeReference == null || !expectedPropertyTypeReference.IsODataEntityTypeKind(), + "If the expected type is specified it must not be an entity type."); + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + expectedPropertyTypeReference = this.UpdateExpectedTypeBasedOnContextUri(expectedPropertyTypeReference); + + object propertyValue = missingPropertyValue; + var customInstanceAnnotations = new Collection(); + + // Check for the special top-level OData 3.0 null marker in order to accommodate + // the responses written by 6.x version of this library. + if (this.IsTopLevel6xNullValue()) + { + // NOTE: when reading a null value we will never ask the type resolver (if present) to resolve the + // type; we always fall back to the expected type. + this.ReaderValidator.ValidateNullValue( + expectedPropertyTypeReference, + /*validateNullValue*/ true, + /*propertyName*/ null, + null); + + // We don't allow properties or non-custom annotations in the null payload. + this.ValidateNoPropertyInNullPayload(propertyAndAnnotationCollector); + + propertyValue = null; + } + else + { + string payloadTypeName = null; + if (this.ReadingResourceProperty(propertyAndAnnotationCollector, expectedPropertyTypeReference, out payloadTypeName)) + { + // Figure out whether we are reading a resource property or not; resource properties are not wrapped while all others are. + // Since we don't have metadata in all cases (open properties), we have to detect the type in some cases. + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // Now read the property value + propertyValue = this.ReadNonEntityValue( + payloadTypeName, + expectedPropertyTypeReference, + propertyAndAnnotationCollector, + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ true, + /*insideResourceValue*/ true, + /*propertyName*/ null); + } + else + { + bool isReordering = this.JsonReader is ReorderingJsonReader; + + Func propertyAnnotationReaderForTopLevelProperty = + annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation(annotationName)); }; + + // Read through all top-level properties, ignore the ones with reserved names (i.e., reserved + // characters in their name) and throw if we find none or more than one properties without reserved name. + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationReaderForTopLevelProperty, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + if (string.CompareOrdinal(ODataAnnotationNames.ODataType, propertyName) == 0) + { + // When we are not using the reordering reader we have to ensure that the 'odata.type' property appears before + // the 'value' property; otherwise we already scanned ahead and read the type name and have to now + // ignore it (even if it is after the 'value' property). + if (isReordering) + { + this.JsonReader.SkipValue(); + } + else + { + if (!object.ReferenceEquals(missingPropertyValue, propertyValue)) + { + throw new ODataException( + ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty(ODataAnnotationNames.ODataType, JsonLightConstants.ODataValuePropertyName)); + } + + payloadTypeName = this.ReadODataTypeAnnotationValue(); + } + } + else + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); + } + + break; + case PropertyParsingResult.CustomInstanceAnnotation: + ODataAnnotationNames.ValidateIsCustomAnnotationName(propertyName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(propertyName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + var customInstanceAnnotationValue = this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, propertyName); + customInstanceAnnotations.Add(new ODataInstanceAnnotation(propertyName, customInstanceAnnotationValue.ToODataValue())); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) == 0) + { + // Now read the property value + propertyValue = this.ReadNonEntityValue( + payloadTypeName, + expectedPropertyTypeReference, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ true, + /*insideResourceValue*/ false, + /*propertyName*/ propertyName); + } + else + { + throw new ODataException( + ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName(propertyName, JsonLightConstants.ODataValuePropertyName)); + } + + break; + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + }); + } + + if (object.ReferenceEquals(missingPropertyValue, propertyValue)) + { + // No property found; there should be exactly one property in the top-level property wrapper that does not have a reserved name. + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload); + } + } + } + + Debug.Assert(!object.ReferenceEquals(missingPropertyValue, propertyValue), "!object.ReferenceEquals(missingPropertyValue, propertyValue)"); + ODataProperty resultProperty = new ODataProperty() + { + // The property name is not on the context URI or the payload, we report null. + Name = null, + Value = propertyValue, + InstanceAnnotations = customInstanceAnnotations + }; + + // Read over the end object - note that this might be the last node in the input (in case there's no response wrapper) + this.JsonReader.Read(); + return resultProperty; + } + + /// + /// Updates the expected type based on the context URI if there is one. + /// + /// The expected property type reference provided by the user through public APIs, or null if one was not provided. + /// The expected type reference updated based on the context uri, if there is one. + private IEdmTypeReference UpdateExpectedTypeBasedOnContextUri(IEdmTypeReference expectedPropertyTypeReference) + { + Debug.Assert(!this.JsonLightInputContext.ReadingResponse || this.ContextUriParseResult != null, "Responses should always have a context uri, and that should already have been validated."); + if (this.ContextUriParseResult == null || this.ContextUriParseResult.EdmType == null) + { + return expectedPropertyTypeReference; + } + + IEdmType typeFromContextUri = this.ContextUriParseResult.EdmType; + if (expectedPropertyTypeReference != null && !expectedPropertyTypeReference.Definition.IsAssignableFrom(typeFromContextUri)) + { + throw new ODataException(ODataErrorStrings.ReaderValidationUtils_TypeInContextUriDoesNotMatchExpectedType( + UriUtils.UriToString(this.ContextUriParseResult.ContextUri), + typeFromContextUri.FullTypeName(), + expectedPropertyTypeReference.FullName())); + } + + // Assume the value is nullable as its the looser option and the value may come from an open property. + bool isNullable = true; + if (expectedPropertyTypeReference != null) + { + // if there is a user-provided expected type, then flow nullability information from it. + isNullable = expectedPropertyTypeReference.IsNullable; + } + + return typeFromContextUri.ToTypeReference(isNullable); + } + + /// + /// Reads a collection value. + /// + /// The collection type reference of the value. + /// The type name read from the payload. + /// The serialization type name for the collection value (possibly null). + /// The value of the collection. + /// + /// Pre-Condition: Fails if the current node is not a JsonNodeType.StartArray + /// Post-Condition: almost anything - the node after the collection value (after the EndArray) + /// + private ODataCollectionValue ReadCollectionValue( + IEdmCollectionTypeReference collectionValueTypeReference, + string payloadTypeName, + ODataTypeAnnotation typeAnnotation) + { + Debug.Assert( + collectionValueTypeReference == null || collectionValueTypeReference.IsNonEntityCollectionType(), + "If the metadata is specified it must denote a Collection for this method to work."); + + this.IncreaseRecursionDepth(); + + // Read over the start array + this.JsonReader.ReadStartArray(); + + ODataCollectionValue collectionValue = new ODataCollectionValue(); + collectionValue.TypeName = collectionValueTypeReference != null ? collectionValueTypeReference.FullName() : payloadTypeName; + if (typeAnnotation != null) + { + collectionValue.TypeAnnotation = typeAnnotation; + } + + List items = new List(); + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + IEdmTypeReference itemType = null; + if (collectionValueTypeReference != null) + { + itemType = collectionValueTypeReference.CollectionDefinition().ElementType; + } + + // NOTE: we do not support reading JSON Light without metadata right now so we always have an expected item type; + // The collection validator is always null. + CollectionWithoutExpectedTypeValidator collectionValidator = null; + + while (this.JsonReader.NodeType != JsonNodeType.EndArray) + { + object itemValue = this.ReadNonEntityValueImplementation( + /*payloadTypeName*/ null, + itemType, + propertyAndAnnotationCollector, + collectionValidator, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ false, + /*insideResourceValue*/ false, + /*propertyName*/ null); + + // Validate the item (for example that it's not null) + ValidationUtils.ValidateCollectionItem(itemValue, itemType.IsNullable()); + + // Note that the ReadNonEntityValue already validated that the actual type of the value matches + // the expected type (the itemType). + items.Add(itemValue); + } + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndArray, "The results value must end with an end array."); + this.JsonReader.ReadEndArray(); + + collectionValue.Items = new ReadOnlyEnumerable(items); + + this.DecreaseRecursionDepth(); + + return collectionValue; + } + + /// + /// Reads a type definition value. + /// + /// true if the reader is positioned on the first property of the value which is a JSON Object + /// (or the second property if the first one was odata.type). + /// The expected type definition reference of the value, or null if none is available. + /// true to validate null values; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// The value of the primitive value. + private object ReadTypeDefinitionValue(bool insideJsonObjectValue, IEdmTypeDefinitionReference expectedValueTypeReference, bool validateNullValue, string propertyName) + { + object result = this.ReadPrimitiveValue(insideJsonObjectValue, expectedValueTypeReference.AsPrimitive(), validateNullValue, propertyName); + + // Try convert to the expected CLR types from their underlying CLR types. + try + { + return this.Model.GetPrimitiveValueConverter(expectedValueTypeReference).ConvertFromUnderlyingType(result); + } + catch (OverflowException) + { + throw new ODataException(ODataErrorStrings.EdmLibraryExtensions_ValueOverflowForUnderlyingType(result, expectedValueTypeReference.FullName())); + } + } + + /// + /// Reads a primitive value. + /// + /// true if the reader is positioned on the first property of the value which is a JSON Object + /// (or the second property if the first one was odata.type). + /// The expected type reference of the value, or null if none is available. + /// true to validate null values; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// The value of the primitive value. + /// + /// Pre-Condition: insideJsonObjectValue == false -> none - Fails if the current node is not a JsonNodeType.PrimitiveValue + /// insideJsonObjectValue == true -> JsonNodeType.Property or JsonNodeType.EndObject - the first property of the value object, + /// or the second property if first was odata.type, or the end-object. + /// Post-Condition: almost anything - the node after the primitive value. + /// + private object ReadPrimitiveValue(bool insideJsonObjectValue, IEdmPrimitiveTypeReference expectedValueTypeReference, bool validateNullValue, string propertyName) + { + object result; + + if (expectedValueTypeReference != null && expectedValueTypeReference.IsSpatial()) + { + result = ODataJsonReaderCoreUtils.ReadSpatialValue( + this.JsonReader, + insideJsonObjectValue, + this.JsonLightInputContext, + expectedValueTypeReference, + validateNullValue, + this.recursionDepth, + propertyName); + } + else + { + if (insideJsonObjectValue) + { + // We manually throw JSON exception here to get a nicer error message (we expect primitive value and got object). + // Otherwise the ReadPrimitiveValue would fail with something like "expected primitive value but found property/end object" which is rather confusing. + throw new ODataException(ODataErrorStrings.JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, propertyName)); + } + + result = this.JsonReader.ReadPrimitiveValue(); + + if (expectedValueTypeReference != null) + { + if ((expectedValueTypeReference.IsDecimal() || expectedValueTypeReference.IsInt64()) + && result != null) + { + if ((result is string) ^ this.JsonReader.IsIeee754Compatible) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderUtils_ConflictBetweenInputFormatAndParameter(expectedValueTypeReference.FullName())); + } + } + + result = ODataJsonLightReaderUtils.ConvertValue( + result, + expectedValueTypeReference, + this.MessageReaderSettings, + validateNullValue, + propertyName, + this.JsonLightInputContext.PayloadValueConverter); + } + else + { + if (result is Decimal) + { + // convert decimal back to double to follow legacy logic when target type is not specified and IEEE754Compatible=false. + // we may lose precision for some range of int64 and decimal. + return Convert.ToDouble((Decimal)result); + } + } + } + + return result; + } + + /// + /// Reads a primitive string as enum value. + /// + /// true if the reader is positioned on the first property of the value which is a JSON Object + /// (or the second property if the first one was odata.type). + /// The expected type reference of the value, or null if none is available. + /// true to validate null values; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// The Enum value from the primitive string. + private object ReadEnumValue(bool insideJsonObjectValue, IEdmEnumTypeReference expectedValueTypeReference, bool validateNullValue, string propertyName) + { + if (insideJsonObjectValue) + { + // We manually throw JSON exception here to get a nicer error message (we expect primitive value and got object). + // Otherwise the ReadPrimitiveValue would fail with something like "expected primitive value but found property/end object" which is rather confusing. + throw new ODataException(ODataErrorStrings.JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, propertyName)); + } + + string enumStr = this.JsonReader.ReadStringValue(); + return new ODataEnumValue(enumStr, expectedValueTypeReference.FullName()); + } + + /// + /// Reads a resource value, + /// + /// true if the reader is positioned on the first property of the value which is a JSON Object + /// (or the second property if the first one was odata.type). + /// true if we are reading a resource value and the reader is already positioned inside the resource value; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// The expected type reference of the value. + /// The type name read from the payload. + /// The duplicate property names checker. + /// The value of the resource value. + private ODataResourceValue ReadResourceValue( + bool insideJsonObjectValue, + bool insideResourceValue, + string propertyName, + IEdmStructuredTypeReference structuredTypeReference, + string payloadTypeName, + PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + if (!insideJsonObjectValue && !insideResourceValue) + { + if (this.JsonReader.NodeType != JsonNodeType.StartObject) + { + string typeName = structuredTypeReference != null ? structuredTypeReference.FullName() : payloadTypeName; + throw new ODataException( + string.Format(CultureInfo.InvariantCulture, + "The property with name '{0}' was found with a value node of type '{1}'; however, a resource value of type '{2}' was expected.", + propertyName, this.JsonReader.NodeType, typeName)); + } + + this.JsonReader.Read(); + } + + return this.ReadResourceValue(structuredTypeReference, payloadTypeName, propertyAndAnnotationCollector); + } + + /// + /// Reads a resource value. + /// + /// The expected type reference of the value. + /// The type name read from the payload. + /// The duplicate property names checker. + /// The value of the resource value. + /// + /// Pre-Condition: JsonNodeType.Property - the first property of the resource value object, or the second one if the first one was odata.type. + /// JsonNodeType.EndObject - the end object of the resource value object. + /// Post-Condition: almost anything - the node after the resource value (after the EndObject) + /// + private ODataResourceValue ReadResourceValue( + IEdmStructuredTypeReference structuredTypeReference, + string payloadTypeName, + PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + this.IncreaseRecursionDepth(); + + ODataResourceValue resourceValue = new ODataResourceValue(); + resourceValue.TypeName = structuredTypeReference != null ? structuredTypeReference.FullName() : payloadTypeName; + + if (structuredTypeReference != null) + { + resourceValue.TypeAnnotation = new ODataTypeAnnotation(resourceValue.TypeName); + } + + List properties = new List(); + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ReadPropertyCustomAnnotationValue = this.ReadCustomInstanceAnnotationValue; + this.ProcessProperty( + propertyAndAnnotationCollector, + this.ReadTypePropertyAnnotationValue, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: // odata.* + if (string.CompareOrdinal(ODataAnnotationNames.ODataType, propertyName) == 0) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_ResourceTypeAnnotationNotFirst); + } + else + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); + } + + case PropertyParsingResult.CustomInstanceAnnotation: // custom instance annotation + ODataAnnotationNames.ValidateIsCustomAnnotationName(propertyName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(propertyName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + var customInstanceAnnotationValue = this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, propertyName); + resourceValue.InstanceAnnotations.Add(new ODataInstanceAnnotation(propertyName, customInstanceAnnotationValue.ToODataValue())); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_ResourceValuePropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + // Any other property is data + ODataProperty property = new ODataProperty(); + property.Name = propertyName; + + // Lookup the property in metadata + IEdmProperty edmProperty = null; + if (structuredTypeReference != null) + { + edmProperty = ReaderValidationUtils.ValidatePropertyDefined(propertyName, + structuredTypeReference.StructuredDefinition(), this.MessageReaderSettings.ThrowOnUndeclaredPropertyForNonOpenType); + } + + // EdmLib bridge marks all key properties as non-nullable, but Astoria allows them to be nullable. + // If the property has an annotation to ignore null values, we need to omit the property in requests. + ODataNullValueBehaviorKind nullValueReadBehaviorKind = this.ReadingResponse || edmProperty == null + ? ODataNullValueBehaviorKind.Default + : this.Model.NullValueReadBehaviorKind(edmProperty); + + // Read the property value + object propertyValue = this.ReadNonEntityValueImplementation( + ValidateDataPropertyTypeNameAnnotation(propertyAndAnnotationCollector, propertyName), + edmProperty == null ? null : edmProperty.Type, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + nullValueReadBehaviorKind == ODataNullValueBehaviorKind.Default, + /*isTopLevelPropertyValue*/ false, + /*insideResourceValue*/ false, + propertyName, + edmProperty == null); + + if (nullValueReadBehaviorKind != ODataNullValueBehaviorKind.IgnoreValue || propertyValue != null) + { + propertyAndAnnotationCollector.CheckForDuplicatePropertyNames(property); + property.Value = propertyValue; + var propertyAnnotations = propertyAndAnnotationCollector.GetCustomPropertyAnnotations(propertyName); + if (propertyAnnotations != null) + { + foreach (var annotation in propertyAnnotations) + { + if (annotation.Value != null) + { + // annotation.Value == null indicates that this annotation should be skipped. + property.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotation.Key, annotation.Value.ToODataValue())); + } + } + } + + properties.Add(property); + } + + break; + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + }); + } + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndObject, "After all the properties of a resource value are read the EndObject node is expected."); + this.JsonReader.ReadEndObject(); + + resourceValue.Properties = new ReadOnlyEnumerable(properties); + + this.DecreaseRecursionDepth(); + + return resourceValue; + } + + /// + /// Reads a primitive, enum, resource (complex or entity) or collection value. + /// + /// The type name read from the payload as a property annotation, or null if none is available. + /// The expected type reference of the property value. + /// The duplicate property names checker to use - if null the method should create a new one if necessary. + /// The collection validator instance if no expected item type has been specified; otherwise null. + /// true to validate null values; otherwise false. + /// true if we are reading a top-level property value; otherwise false. + /// true if we are reading a complex value and the reader is already positioned inside the complex value; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// Indicates whether the property is dynamic or unknown. + /// The value of the property read. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue - the value of the property is a primitive value + /// JsonNodeType.StartArray - the value of the property is an array + /// Post-Condition: almost anything - the node after the property value. + /// + /// Returns the value of the property read, which can be one of: + /// - null + /// - primitive value + /// - + /// + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "No easy way to refactor.")] + private object ReadNonEntityValueImplementation( + string payloadTypeName, + IEdmTypeReference expectedTypeReference, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + CollectionWithoutExpectedTypeValidator collectionValidator, + bool validateNullValue, + bool isTopLevelPropertyValue, + bool insideResourceValue, + string propertyName, + bool? isDynamicProperty = null) + { + Debug.Assert( + this.JsonReader.NodeType == JsonNodeType.PrimitiveValue || this.JsonReader.NodeType == JsonNodeType.StartObject || + this.JsonReader.NodeType == JsonNodeType.StartArray || (insideResourceValue && (this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject)), + "Pre-Condition: expected JsonNodeType.PrimitiveValue or JsonNodeType.StartObject or JsonNodeType.StartArray or JsonNodeTypeProperty (when inside complex value)."); + Debug.Assert( + expectedTypeReference == null || collectionValidator == null, + "If an expected value type reference is specified, no collection validator must be provided."); + + bool valueIsJsonObject = this.JsonReader.NodeType == JsonNodeType.StartObject; + + bool typeNameFoundInPayload = false; + if (valueIsJsonObject || insideResourceValue) + { + // If we have an object value initialize the duplicate property names checker + if (propertyAndAnnotationCollector == null) + { + propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + } + else + { + propertyAndAnnotationCollector.Reset(); + } + + // Read the payload type name + if (!insideResourceValue) + { + string typeName; + typeNameFoundInPayload = this.TryReadPayloadTypeFromObject( + propertyAndAnnotationCollector, + insideResourceValue, + out typeName); + if (typeNameFoundInPayload) + { + payloadTypeName = typeName; + } + } + } + + ODataTypeAnnotation typeAnnotation; + EdmTypeKind targetTypeKind; + IEdmTypeReference targetTypeReference = this.ReaderValidator.ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind.None, + /*expectStructuredType*/ null, + /*defaultPrimitivePayloadType*/ null, + expectedTypeReference, + payloadTypeName, + this.Model, + this.GetNonEntityValueKind, + out targetTypeKind, + out typeAnnotation); + + object result; + + if (targetTypeKind == EdmTypeKind.Untyped || targetTypeKind == EdmTypeKind.None) + { + targetTypeReference = ResolveUntypedType( + this.JsonReader.NodeType, + this.JsonReader.Value, + payloadTypeName, + expectedTypeReference, + this.MessageReaderSettings.PrimitiveTypeResolver, + this.MessageReaderSettings.ReadUntypedAsString, + !this.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata); + + targetTypeKind = targetTypeReference.TypeKind(); + } + + // Try to read a null value + if (ODataJsonReaderCoreUtils.TryReadNullValue(this.JsonReader, this.JsonLightInputContext, targetTypeReference, validateNullValue, propertyName, isDynamicProperty)) + { + if (this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata && validateNullValue && targetTypeReference != null && !targetTypeReference.IsNullable) + { + // For dynamic collection property, we should allow null value to be assigned to it. + if (targetTypeKind != EdmTypeKind.Collection || isDynamicProperty != true) + { + // A null value was found for the property named '{0}', which has the expected type '{1}[Nullable=False]'. The expected type '{1}[Nullable=False]' does not allow null values. + throw new ODataException(ODataErrorStrings.ReaderValidationUtils_NullNamedValueForNonNullableType(propertyName, targetTypeReference.FullName())); + } + } + + result = null; + } + else + { + Debug.Assert( + !valueIsJsonObject || this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, + "If the value was an object the reader must be on either property or end object."); + switch (targetTypeKind) + { + case EdmTypeKind.Primitive: + Debug.Assert(targetTypeReference == null || targetTypeReference.IsODataPrimitiveTypeKind(), "Expected an OData primitive type."); + IEdmPrimitiveTypeReference primitiveTargetTypeReference = targetTypeReference == null ? null : targetTypeReference.AsPrimitive(); + + // If we found an odata.type annotation inside a primitive value, we have to fail; type annotations + // for primitive values are property annotations, not instance annotations inside the value. + if (typeNameFoundInPayload) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_ODataTypeAnnotationInPrimitiveValue(ODataAnnotationNames.ODataType)); + } + + result = this.ReadPrimitiveValue( + valueIsJsonObject, + primitiveTargetTypeReference, + validateNullValue, + propertyName); + break; + + case EdmTypeKind.Enum: + Debug.Assert(targetTypeReference == null || targetTypeReference.IsODataEnumTypeKind(), "Expected an OData enum type."); + IEdmEnumTypeReference enumTargetTypeReference = targetTypeReference == null ? null : targetTypeReference.AsEnum(); + result = this.ReadEnumValue( + valueIsJsonObject, + enumTargetTypeReference, + validateNullValue, + propertyName); + break; + + case EdmTypeKind.Complex: // nested complex + case EdmTypeKind.Entity: // nested entity + Debug.Assert(targetTypeReference == null || targetTypeReference.IsODataComplexTypeKind() || targetTypeReference.IsODataEntityTypeKind(), "Expected an OData complex or entity type."); + IEdmStructuredTypeReference structuredTypeTypeReference = targetTypeReference == null ? null : targetTypeReference.AsStructured(); + result = ReadResourceValue(valueIsJsonObject, + insideResourceValue, + propertyName, + structuredTypeTypeReference, + payloadTypeName, + propertyAndAnnotationCollector); + + break; + + case EdmTypeKind.Collection: + IEdmCollectionTypeReference collectionTypeReference = ValidationUtils.ValidateCollectionType(targetTypeReference); + if (valueIsJsonObject) + { + // We manually throw JSON exception here to get a nicer error message (we expect array value and got object). + // Otherwise the ReadCollectionValue would fail with something like "expected array value but found property/end object" which is rather confusing. + throw new ODataException(ODataErrorStrings.JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName(JsonNodeType.StartArray, JsonNodeType.StartObject, propertyName)); + } + + result = this.ReadCollectionValue( + collectionTypeReference, + payloadTypeName, + typeAnnotation); + break; + + case EdmTypeKind.TypeDefinition: + result = this.ReadTypeDefinitionValue( + valueIsJsonObject, + expectedTypeReference.AsTypeDefinition(), + validateNullValue, + propertyName); + break; + + case EdmTypeKind.Untyped: + result = this.JsonReader.ReadAsUntypedOrNullValue(); + break; + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightPropertyAndValueDeserializer_ReadPropertyValue)); + } + + // If we have no expected type make sure the collection items are of the same kind and specify the same name. + if (collectionValidator != null) + { + string payloadTypeNameFromResult = ODataJsonLightReaderUtils.GetPayloadTypeName(result); + Debug.Assert(expectedTypeReference == null, "If a collection validator is specified there must not be an expected value type reference."); + collectionValidator.ValidateCollectionItem(payloadTypeNameFromResult, targetTypeKind); + } + } + + return result; + } + + /// + /// Reads the payload type name from a JSON object (if it exists). + /// + /// The duplicate property names checker to track the detected 'odata.type' annotation (if any). + /// true if we are reading a resource value and the reader is already positioned inside the resource value; otherwise false. + /// The value of the odata.type annotation or null if no such annotation exists. + /// true if a type name was read from the payload; otherwise false. + /// + /// Precondition: StartObject the start of a JSON object + /// Postcondition: Property the first property of the object if no 'odata.type' annotation exists as first property + /// or the first property after the 'odata.type' annotation. + /// EndObject for an empty JSON object or an object with only the 'odata.type' annotation + /// + private bool TryReadPayloadTypeFromObject(PropertyAndAnnotationCollector propertyAndAnnotationCollector, bool insideResourceValue, out string payloadTypeName) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + Debug.Assert( + (this.JsonReader.NodeType == JsonNodeType.StartObject && !insideResourceValue) || + ((this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject) && insideResourceValue), + "Pre-Condition: JsonNodeType.StartObject when not inside complex value; JsonNodeType.Property or JsonNodeType.EndObject otherwise."); + bool readTypeName = false; + payloadTypeName = null; + + // If not already positioned inside the JSON object, read over the object start + if (!insideResourceValue) + { + this.JsonReader.ReadStartObject(); + } + + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + readTypeName = this.TryReadODataTypeAnnotation(out payloadTypeName); + if (readTypeName) + { + // Register the odata.type annotation we just found with the duplicate property names checker. + propertyAndAnnotationCollector.MarkPropertyAsProcessed(ODataAnnotationNames.ODataType); + } + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return readTypeName; + } + + /// + /// Detects whether we are currently reading a resource property or not. This can be determined from metadata (if we have it) + /// or from the presence of the odata.type instance annotation in the payload. + /// + /// The duplicate property names checker in use for the resource content. + /// The expected type reference of the property to read. + /// The type name of the resource value if found in the payload; otherwise null. + /// true if we are reading a resource property; otherwise false. + /// + /// This method does not move the reader. + /// + private bool ReadingResourceProperty(PropertyAndAnnotationCollector propertyAndAnnotationCollector, IEdmTypeReference expectedPropertyTypeReference, out string payloadTypeName) + { + payloadTypeName = null; + bool readingResourceProperty = false; + + // First try to use the metadata if is available + if (expectedPropertyTypeReference != null) + { + readingResourceProperty = expectedPropertyTypeReference.IsStructured(); + } + + // Then check whether the first property in the JSON object is the 'odata.type' + // annotation; if we don't have an expected property type reference, the 'odata.type' + // annotation has to exist for resource properties. (This can happen for top-level open + // properties). + if (this.JsonReader.NodeType == JsonNodeType.Property && this.TryReadODataTypeAnnotation(out payloadTypeName)) + { + // Register the odata.type annotation we just found with the duplicate property names checker. + propertyAndAnnotationCollector.MarkPropertyAsProcessed(ODataAnnotationNames.ODataType); + + IEdmType expectedPropertyType = null; + if (expectedPropertyTypeReference != null) + { + expectedPropertyType = expectedPropertyTypeReference.Definition; + } + + EdmTypeKind typeKind = EdmTypeKind.None; + IEdmType actualWirePropertyTypeReference = MetadataUtils.ResolveTypeNameForRead( + this.Model, + expectedPropertyType, + payloadTypeName, + this.MessageReaderSettings.ClientCustomTypeResolver, + out typeKind); + + if (actualWirePropertyTypeReference != null) + { + readingResourceProperty = actualWirePropertyTypeReference.IsODataComplexTypeKind() || actualWirePropertyTypeReference.IsODataEntityTypeKind(); + } + } + + return readingResourceProperty; + } + + /// + /// Tries to read a top-level null value from the JSON reader. + /// + /// true if a null value could be read from the JSON reader; otherwise false. + /// If the method detects the odata.null annotation, it will read it; otherwise the reader does not move. + private bool IsTopLevel6xNullValue() + { + bool odataNullAnnotationInPayload = this.JsonReader.NodeType == JsonNodeType.Property && string.CompareOrdinal(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + ODataAnnotationNames.ODataNull, JsonReader.GetPropertyName()) == 0; + if (odataNullAnnotationInPayload) + { + // If we found the expected annotation read over the property name + this.JsonReader.ReadNext(); + + // Now check the value of the annotation + object nullAnnotationValue = this.JsonReader.ReadPrimitiveValue(); + if (!(nullAnnotationValue is bool) || (bool)nullAnnotationValue == false) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation(ODataAnnotationNames.ODataNull, JsonLightConstants.ODataNullAnnotationTrueValue)); + } + } + + return odataNullAnnotationInPayload; + } + + /// + /// Make sure that we don't find any other odata.* annotations or properties after reading a payload with the odata.null annotation. + /// + /// The duplicate property names checker to use. + private void ValidateNoPropertyInNullPayload(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + // we use the ParseProperty method to ignore custom annotations. + Func propertyAnnotationReaderForTopLevelNull = annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation(annotationName)); }; + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationReaderForTopLevelNull, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); + + case PropertyParsingResult.CustomInstanceAnnotation: + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_NoPropertyAndAnnotationAllowedInNullPayload(propertyName)); + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + }); + } + } + + /// + /// Increases the recursion depth of values by 1. This will throw if the recursion depth exceeds the current limit. + /// + private void IncreaseRecursionDepth() + { + ValidationUtils.IncreaseAndValidateRecursionDepth(ref this.recursionDepth, this.MessageReaderSettings.MessageQuotas.MaxNestingDepth); + } + + /// + /// Decreases the recursion depth of values by 1. + /// + private void DecreaseRecursionDepth() + { + Debug.Assert(this.recursionDepth > 0, "Can't decrease recursion depth below 0."); + + this.recursionDepth--; + } + + /// + /// Asserts that the current recursion depth of values is zero. This should be true on all calls into this class from outside of this class. + /// + [Conditional("DEBUG")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This is needed in DEBUG build.")] + private void AssertRecursionDepthIsZero() + { + Debug.Assert(this.recursionDepth == 0, "The current recursion depth must be 0."); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs new file mode 100644 index 0000000..c7ef9db --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs @@ -0,0 +1,634 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Globalization; + using Microsoft.OData.Edm; + using Microsoft.OData.Evaluation; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// OData JsonLight serializer for properties. + /// + internal class ODataJsonLightPropertySerializer : ODataJsonLightSerializer + { + /// + /// Serializer to use to write property values. + /// + private readonly ODataJsonLightValueSerializer jsonLightValueSerializer; + + /// + /// Serialization info for current property. + /// + private PropertySerializationInfo currentPropertyInfo; + + /// + /// Constructor. + /// + /// The output context to write to. + /// Whether contextUriBuilder should be initialized. + internal ODataJsonLightPropertySerializer(ODataJsonLightOutputContext jsonLightOutputContext, bool initContextUriBuilder = false) + : base(jsonLightOutputContext, initContextUriBuilder) + { + this.jsonLightValueSerializer = new ODataJsonLightValueSerializer(this, initContextUriBuilder); + } + + /// + /// Gets the json light value writer. + /// + internal ODataJsonLightValueSerializer JsonLightValueSerializer + { + get + { + return this.jsonLightValueSerializer; + } + } + + /// + /// Write an to the given stream. This method creates an + /// async buffered stream and writes the property to it. + /// + /// The property to write. + internal void WriteTopLevelProperty(ODataProperty property) + { + Debug.Assert(property != null, "property != null"); + Debug.Assert(!(property.Value is ODataStreamReferenceValue), "!(property.Value is ODataStreamReferenceValue)"); + + this.WriteTopLevelPayload( + () => + { + this.JsonWriter.StartObjectScope(); + ODataPayloadKind kind = this.JsonLightOutputContext.MessageWriterSettings.IsIndividualProperty ? ODataPayloadKind.IndividualProperty : ODataPayloadKind.Property; + + if (!(this.JsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel)) + { + ODataContextUrlInfo contextInfo = ODataContextUrlInfo.Create(property.ODataValue, this.MessageWriterSettings.Version ?? ODataVersion.V4, this.JsonLightOutputContext.MessageWriterSettings.ODataUri, this.Model); + this.WriteContextUriProperty(kind, () => contextInfo); + } + + // Note we do not allow named stream properties to be written as top level property. + this.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + this.WriteProperty( + property, + null /*owningType*/, + true /* isTopLevel */, + this.CreateDuplicatePropertyNameChecker(), + null /* metadataBuilder */); + this.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + + this.JsonWriter.EndObjectScope(); + }); + } + + /// + /// Writes property names and value pairs. + /// + /// The of the resource (or null if not metadata is available). + /// The enumeration of properties to write out. + /// + /// Whether the properties are being written for complex value. Also used for detecting whether stream properties + /// are allowed as named stream properties should only be defined on ODataResource instances + /// + /// The DuplicatePropertyNameChecker to use. + /// The metadatabuilder for writing the property. + internal void WriteProperties( + IEdmStructuredType owningType, + IEnumerable properties, + bool isComplexValue, + IDuplicatePropertyNameChecker duplicatePropertyNameChecker, + ODataResourceMetadataBuilder metadataBuilder) + { + if (properties == null) + { + return; + } + + foreach (ODataProperty property in properties) + { + this.WriteProperty( + property, + owningType, + false /* isTopLevel */, + duplicatePropertyNameChecker, + metadataBuilder); + } + } + + /// + /// Writes a name/value pair for a property. + /// + /// The property to write out. + /// The owning type for the or null if no metadata is available. + /// true when writing a top-level property; false for nested properties. + /// The DuplicatePropertyNameChecker to use. + /// The metadatabuilder for the resource + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Splitting the code would make the logic harder to understand; class coupling is only slightly above threshold.")] + internal void WriteProperty( + ODataProperty property, + IEdmStructuredType owningType, + bool isTopLevel, + IDuplicatePropertyNameChecker duplicatePropertyNameChecker, + ODataResourceMetadataBuilder metadataBuilder) + { + this.WritePropertyInfo(property, owningType, isTopLevel, duplicatePropertyNameChecker, metadataBuilder); + + ODataValue value = property.ODataValue; + + // handle ODataUntypedValue + ODataUntypedValue untypedValue = value as ODataUntypedValue; + if (untypedValue != null) + { + WriteUntypedValue(untypedValue); + return; + } + + ODataStreamReferenceValue streamReferenceValue = value as ODataStreamReferenceValue; + if (streamReferenceValue != null && !(this.JsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel)) + { + Debug.Assert(!isTopLevel, "Stream properties are not allowed at the top level."); + WriteStreamValue(streamReferenceValue, property.Name, metadataBuilder); + return; + } + + if (value is ODataNullValue || value == null) + { + this.WriteNullProperty(property); + return; + } + + bool isOpenPropertyType = this.IsOpenProperty(property); + + ODataPrimitiveValue primitiveValue = value as ODataPrimitiveValue; + if (primitiveValue != null) + { + this.WritePrimitiveProperty(primitiveValue, isOpenPropertyType); + return; + } + + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + this.WriteEnumProperty(enumValue, isOpenPropertyType); + return; + } + + ODataResourceValue resourceValue = value as ODataResourceValue; + if (resourceValue != null) + { + if (isTopLevel) + { + throw new ODataException(Strings.ODataMessageWriter_NotAllowedWriteTopLevelPropertyWithResourceValue(property.Name)); + } + + this.WriteResourceProperty(property, resourceValue, isOpenPropertyType); + return; + } + + ODataCollectionValue collectionValue = value as ODataCollectionValue; + if (collectionValue != null) + { + if (isTopLevel) + { + if (collectionValue.Items != null && collectionValue.Items.Any(i => i is ODataResourceValue)) + { + throw new ODataException(Strings.ODataMessageWriter_NotAllowedWriteTopLevelPropertyWithResourceValue(property.Name)); + } + } + + this.WriteCollectionProperty(collectionValue, isOpenPropertyType); + return; + } + + ODataBinaryStreamValue streamValue = value as ODataBinaryStreamValue; + if (streamValue != null) + { + this.WriteStreamProperty(streamValue, isOpenPropertyType); + return; + } + } + + + /// + /// Writes the property information for a property. + /// + /// The property info to write out. + /// The owning type for the or null if no metadata is available. + /// true when writing a top-level property; false for nested properties. + /// The DuplicatePropertyNameChecker to use. + /// The metadatabuilder for the resource + internal void WritePropertyInfo( + ODataPropertyInfo propertyInfo, + IEdmStructuredType owningType, + bool isTopLevel, + IDuplicatePropertyNameChecker duplicatePropertyNameChecker, + ODataResourceMetadataBuilder metadataBuilder) + { + WriterValidationUtils.ValidatePropertyNotNull(propertyInfo); + + string propertyName = propertyInfo.Name; + + if (this.JsonLightOutputContext.MessageWriterSettings.Validations != ValidationKinds.None) + { + WriterValidationUtils.ValidatePropertyName(propertyName); + } + + if (!this.JsonLightOutputContext.PropertyCacheHandler.InResourceSetScope()) + { + this.currentPropertyInfo = new PropertySerializationInfo(this.JsonLightOutputContext.Model, propertyName, owningType) { IsTopLevel = isTopLevel }; + } + else + { + this.currentPropertyInfo = this.JsonLightOutputContext.PropertyCacheHandler.GetProperty(this.JsonLightOutputContext.Model, propertyName, owningType); + } + + WriterValidationUtils.ValidatePropertyDefined(this.currentPropertyInfo, this.MessageWriterSettings.ThrowOnUndeclaredPropertyForNonOpenType); + + duplicatePropertyNameChecker.ValidatePropertyUniqueness(propertyInfo); + + if (currentPropertyInfo.MetadataType.IsUndeclaredProperty) + { + WriteODataTypeAnnotation(propertyInfo, isTopLevel); + } + + WriteInstanceAnnotation(propertyInfo, isTopLevel, currentPropertyInfo.MetadataType.IsUndeclaredProperty); + + ODataStreamPropertyInfo streamInfo = propertyInfo as ODataStreamPropertyInfo; + if (streamInfo != null && !(this.JsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel)) + { + Debug.Assert(!isTopLevel, "Stream properties are not allowed at the top level."); + WriteStreamValue(streamInfo, propertyInfo.Name, metadataBuilder); + } + } + + /// + /// Test to see if is an open property or not. + /// + /// The property in question. + /// true if the property is an open property; false if it is not, or if openness cannot be determined + private bool IsOpenProperty(ODataPropertyInfo property) + { + Debug.Assert(property != null, "property != null"); + + bool isOpenProperty; + + if (property.SerializationInfo != null) + { + isOpenProperty = property.SerializationInfo.PropertyKind == ODataPropertyKind.Open; + } + else + { + // TODO: (issue #888) this logic results in type annotations not being written for dynamic properties on types that are not + // marked as open. Type annotations should always be written for dynamic properties whose type cannot be hueristically + // determined. Need to change this.currentPropertyInfo.MetadataType.IsOpenProperty to this.currentPropertyInfo.MetadataType.IsDynamic, + // and fix related tests and other logic (this change alone results in writing type even if it's already implied by context). + isOpenProperty = (!this.WritingResponse && this.currentPropertyInfo.MetadataType.OwningType == null) // Treat property as dynamic property when writing request and owning type is null + || this.currentPropertyInfo.MetadataType.IsOpenProperty; + } + + return isOpenProperty; + } + + private void WriteUntypedValue(ODataUntypedValue untypedValue) + { + this.JsonWriter.WriteName(this.currentPropertyInfo.WireName); + this.jsonLightValueSerializer.WriteUntypedValue(untypedValue); + return; + } + + private void WriteStreamValue(IODataStreamReferenceInfo streamInfo, string propertyName, ODataResourceMetadataBuilder metadataBuilder) + { + WriterValidationUtils.ValidateStreamPropertyInfo(streamInfo, currentPropertyInfo.MetadataType.EdmProperty, propertyName, this.WritingResponse); + this.WriteStreamInfo(propertyName, streamInfo); + if (metadataBuilder != null) + { + metadataBuilder.MarkStreamPropertyProcessed(propertyName); + } + } + + /// + /// Writes instance annotation for property + /// + /// The property to handle. + /// If writing top level property. + /// If writing an undeclared property. + private void WriteInstanceAnnotation(ODataPropertyInfo property, bool isTopLevel, bool isUndeclaredProperty) + { + if (property.InstanceAnnotations.Count != 0) + { + if (isTopLevel) + { + this.InstanceAnnotationWriter.WriteInstanceAnnotations(property.InstanceAnnotations); + } + else + { + this.InstanceAnnotationWriter.WriteInstanceAnnotations(property.InstanceAnnotations, property.Name, isUndeclaredProperty); + } + } + } + + /// + /// Writes odata type annotation for property + /// + /// The property to handle. + /// If writing top level property. + private void WriteODataTypeAnnotation(ODataPropertyInfo property, bool isTopLevel) + { + if (property.TypeAnnotation != null && property.TypeAnnotation.TypeName != null) + { + string typeName = property.TypeAnnotation.TypeName; + IEdmPrimitiveType primitiveType = EdmCoreModel.Instance.FindType(typeName) as IEdmPrimitiveType; + if (primitiveType == null || + (primitiveType.PrimitiveKind != EdmPrimitiveTypeKind.String && + primitiveType.PrimitiveKind != EdmPrimitiveTypeKind.Decimal && + primitiveType.PrimitiveKind != EdmPrimitiveTypeKind.Boolean)) + { + if (isTopLevel) + { + this.ODataAnnotationWriter.WriteODataTypeInstanceAnnotation(typeName); + } + else + { + this.ODataAnnotationWriter.WriteODataTypePropertyAnnotation(property.Name, typeName); + } + } + } + } + + /// + /// Writes stream property information. + /// + /// The name of the stream property to write. + /// The stream reference value to be written + private void WriteStreamInfo(string propertyName, IODataStreamReferenceInfo streamInfo) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(streamInfo != null, "streamReferenceValue != null"); + + Uri mediaEditLink = streamInfo.EditLink; + if (mediaEditLink != null) + { + this.ODataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataMediaEditLink); + this.JsonWriter.WriteValue(this.UriToString(mediaEditLink)); + } + + Uri mediaReadLink = streamInfo.ReadLink; + if (mediaReadLink != null) + { + this.ODataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataMediaReadLink); + this.JsonWriter.WriteValue(this.UriToString(mediaReadLink)); + } + + string mediaContentType = streamInfo.ContentType; + if (mediaContentType != null) + { + this.ODataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataMediaContentType); + this.JsonWriter.WriteValue(mediaContentType); + } + + string mediaETag = streamInfo.ETag; + if (mediaETag != null) + { + this.ODataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataMediaETag); + this.JsonWriter.WriteValue(mediaETag); + } + } + + /// + /// Writes a Null property. + /// + /// The property to write out. + private void WriteNullProperty( + ODataPropertyInfo property) + { + this.WriterValidator.ValidateNullPropertyValue( + this.currentPropertyInfo.MetadataType.TypeReference, property.Name, + this.currentPropertyInfo.IsTopLevel, this.Model); + + if (this.currentPropertyInfo.IsTopLevel) + { + if (this.JsonLightOutputContext.MessageWriterSettings.LibraryCompatibility < + ODataLibraryCompatibility.Version7 && this.JsonLightOutputContext.MessageWriterSettings.Version < ODataVersion.V401) + { + // The 6.x library used an OData 3.0 protocol element in this case: @odata.null=true + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataNull); + this.JsonWriter.WriteValue(true); + } + else + { + // From the spec: + // 11.2.3 Requesting Individual Properties + // ... + // If the property is single-valued and has the null value, the service responds with 204 No Content. + // ... + throw new ODataException(Strings.ODataMessageWriter_CannotWriteTopLevelNull); + } + } + else + { + this.JsonWriter.WriteName(property.Name); + this.JsonLightValueSerializer.WriteNullValue(); + } + } + + /// + /// Writes a resource property. + /// + /// The property to write out. + /// The resource value to be written + /// If the property is open. + private void WriteResourceProperty( + ODataProperty property, + ODataResourceValue resourceValue, + bool isOpenPropertyType) + { + Debug.Assert(!this.currentPropertyInfo.IsTopLevel, "Resource property should not be top level"); + this.JsonWriter.WriteName(property.Name); + + this.JsonLightValueSerializer.WriteResourceValue( + resourceValue, + this.currentPropertyInfo.MetadataType.TypeReference, + isOpenPropertyType, + this.CreateDuplicatePropertyNameChecker()); + } + + /// + /// Writes a enum property. + /// + /// The enum value to be written. + /// If the property is open. + private void WriteEnumProperty( + ODataEnumValue enumValue, + bool isOpenPropertyType) + { + ResolveEnumValueTypeName(enumValue, isOpenPropertyType); + + this.WritePropertyTypeName(); + this.JsonWriter.WriteName(this.currentPropertyInfo.WireName); + this.JsonLightValueSerializer.WriteEnumValue(enumValue, this.currentPropertyInfo.MetadataType.TypeReference); + } + + private void ResolveEnumValueTypeName(ODataEnumValue enumValue, bool isOpenPropertyType) + { + if (this.currentPropertyInfo.ValueType == null || this.currentPropertyInfo.ValueType.TypeName != enumValue.TypeName) + { + IEdmTypeReference typeFromValue = TypeNameOracle.ResolveAndValidateTypeForEnumValue( + this.Model, + enumValue, + isOpenPropertyType); + + // This is a work around, needTypeOnWire always = true for client side: + // ClientEdmModel's reflection can't know a property is open type even if it is, so here + // make client side always write 'odata.type' for enum. + bool needTypeOnWire = string.Equals(this.JsonLightOutputContext.Model.GetType().Name, "ClientEdmModel", + StringComparison.OrdinalIgnoreCase); + string typeNameToWrite = this.JsonLightOutputContext.TypeNameOracle.GetValueTypeNameForWriting( + enumValue, this.currentPropertyInfo.MetadataType.TypeReference, typeFromValue, needTypeOnWire || isOpenPropertyType); + + this.currentPropertyInfo.ValueType = new PropertyValueTypeInfo(enumValue.TypeName, typeFromValue); + this.currentPropertyInfo.TypeNameToWrite = typeNameToWrite; + } + else + { + string typeNameToWrite; + if (TypeNameOracle.TryGetTypeNameFromAnnotation(enumValue, out typeNameToWrite)) + { + this.currentPropertyInfo.TypeNameToWrite = typeNameToWrite; + } + } + } + + /// + /// Writes a collection property. + /// + /// The collection value to be written + /// If the property is open. + private void WriteCollectionProperty( + ODataCollectionValue collectionValue, + bool isOpenPropertyType) + { + ResolveCollectionValueTypeName(collectionValue, isOpenPropertyType); + + this.WritePropertyTypeName(); + this.JsonWriter.WriteName(this.currentPropertyInfo.WireName); + + // passing false for 'isTopLevel' because the outer wrapping object has already been written. + this.JsonLightValueSerializer.WriteCollectionValue( + collectionValue, + this.currentPropertyInfo.MetadataType.TypeReference, + this.currentPropertyInfo.ValueType.TypeReference, + this.currentPropertyInfo.IsTopLevel, + false /*isInUri*/, + isOpenPropertyType); + } + + private void ResolveCollectionValueTypeName(ODataCollectionValue collectionValue, bool isOpenPropertyType) + { + if (this.currentPropertyInfo.ValueType == null || this.currentPropertyInfo.ValueType.TypeName != collectionValue.TypeName) + { + IEdmTypeReference typeFromValue = TypeNameOracle.ResolveAndValidateTypeForCollectionValue( + this.Model, + this.currentPropertyInfo.MetadataType.TypeReference, + collectionValue, + isOpenPropertyType, + this.WriterValidator); + + this.currentPropertyInfo.ValueType = new PropertyValueTypeInfo(collectionValue.TypeName, typeFromValue); + this.currentPropertyInfo.TypeNameToWrite = + this.JsonLightOutputContext.TypeNameOracle.GetValueTypeNameForWriting(collectionValue, + this.currentPropertyInfo, isOpenPropertyType); + } + else + { + string typeNameToWrite; + if (TypeNameOracle.TryGetTypeNameFromAnnotation(collectionValue, out typeNameToWrite)) + { + this.currentPropertyInfo.TypeNameToWrite = typeNameToWrite; + } + } + } + + /// + /// Writes a stream property. + /// + /// The stream value to be written + /// If the property is open. + private void WriteStreamProperty(ODataBinaryStreamValue streamValue, bool isOpenPropertyType) + { + this.JsonWriter.WriteName(this.currentPropertyInfo.WireName); + this.JsonLightValueSerializer.WriteStreamValue(streamValue); + } + + /// + /// Writes a primitive property. + /// + /// The primitive value to be written + /// If the property is open. + private void WritePrimitiveProperty( + ODataPrimitiveValue primitiveValue, + bool isOpenPropertyType) + { + ResolvePrimitiveValueTypeName(primitiveValue, isOpenPropertyType); + + WriterValidationUtils.ValidatePropertyDerivedTypeConstraint(this.currentPropertyInfo); + + this.WritePropertyTypeName(); + this.JsonWriter.WriteName(this.currentPropertyInfo.WireName); + this.JsonLightValueSerializer.WritePrimitiveValue(primitiveValue.Value, this.currentPropertyInfo.ValueType.TypeReference, this.currentPropertyInfo.MetadataType.TypeReference); + } + + private void ResolvePrimitiveValueTypeName( + ODataPrimitiveValue primitiveValue, + bool isOpenPropertyType) + { + string typeName = primitiveValue.Value.GetType().Name; + if (this.currentPropertyInfo.ValueType == null || this.currentPropertyInfo.ValueType.TypeName != typeName) + { + IEdmTypeReference typeFromValue = TypeNameOracle.ResolveAndValidateTypeForPrimitiveValue(primitiveValue); + + this.currentPropertyInfo.ValueType = new PropertyValueTypeInfo(typeName, typeFromValue); + this.currentPropertyInfo.TypeNameToWrite = this.JsonLightOutputContext.TypeNameOracle.GetValueTypeNameForWriting(primitiveValue, + this.currentPropertyInfo, isOpenPropertyType); + } + else + { + string typeNameToWrite; + if (TypeNameOracle.TryGetTypeNameFromAnnotation(primitiveValue, out typeNameToWrite)) + { + this.currentPropertyInfo.TypeNameToWrite = typeNameToWrite; + } + } + } + + /// + /// Writes the type name on the wire. + /// + private void WritePropertyTypeName() + { + string typeNameToWrite = this.currentPropertyInfo.TypeNameToWrite; + if (typeNameToWrite != null) + { + // We write the type name as an instance annotation (named "odata.type") for top-level properties, but as a property annotation (e.g., "...@odata.type") if not top level. + if (this.currentPropertyInfo.IsTopLevel) + { + this.ODataAnnotationWriter.WriteODataTypeInstanceAnnotation(typeNameToWrite); + } + else + { + this.ODataAnnotationWriter.WriteODataTypePropertyAnnotation(this.currentPropertyInfo.PropertyName, typeNameToWrite); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs new file mode 100644 index 0000000..228f198 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs @@ -0,0 +1,2985 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB +using System.Threading.Tasks; +#endif +using Microsoft.OData.Evaluation; +using Microsoft.OData.Json; +using Microsoft.OData.Metadata; +using Microsoft.OData.UriParser; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.JsonLight +{ + /// + /// OData reader for the JsonLight format. + /// + internal sealed class ODataJsonLightReader : ODataReaderCoreAsync + { + #region private fields + /// The input to read the payload from. + private readonly ODataJsonLightInputContext jsonLightInputContext; + + /// The resource and resource set deserializer to read input with. + private readonly ODataJsonLightResourceDeserializer jsonLightResourceDeserializer; + + /// The scope associated with the top level of this payload. + private readonly JsonLightTopLevelScope topLevelScope; + + /// true if the reader is created for reading parameter; false otherwise. + private readonly bool readingParameter; + #endregion private fields + + #region Constructors + /// + /// Constructor. + /// + /// The input to read the payload from. + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read (in case of resource reader) or entries in the resource set to be read (in case of resource set reader). + /// true if the reader is created for reading a resource set; false when it is created for reading a resource. + /// true if the reader is created for reading a parameter; false otherwise. + /// true if the reader is created for reading expanded navigation property in delta response; false otherwise. + /// If not null, the Json reader will notify the implementer of the interface of relevant state changes in the Json reader. + internal ODataJsonLightReader( + ODataJsonLightInputContext jsonLightInputContext, + IEdmNavigationSource navigationSource, + IEdmStructuredType expectedResourceType, + bool readingResourceSet, + bool readingParameter = false, + bool readingDelta = false, + IODataReaderWriterListener listener = null) + : base(jsonLightInputContext, readingResourceSet, readingDelta, listener) + { + Debug.Assert(jsonLightInputContext != null, "jsonLightInputContext != null"); + Debug.Assert( + expectedResourceType == null || jsonLightInputContext.Model.IsUserModel(), + "If the expected type is specified we need model as well. We should have verified that by now."); + + this.jsonLightInputContext = jsonLightInputContext; + this.jsonLightResourceDeserializer = new ODataJsonLightResourceDeserializer(jsonLightInputContext); + this.readingParameter = readingParameter; + this.topLevelScope = new JsonLightTopLevelScope(navigationSource, expectedResourceType, new ODataUri()); + this.EnterScope(this.topLevelScope); + } + + #endregion + + #region private properties + + /// + /// Returns the current resource state. + /// + private IODataJsonLightReaderResourceState CurrentResourceState + { + get + { + Debug.Assert( + this.State == ODataReaderState.ResourceStart || this.State == ODataReaderState.ResourceEnd || + this.State == ODataReaderState.DeletedResourceStart || this.State == ODataReaderState.DeletedResourceEnd, + "This property can only be accessed in the EntryStart or EntryEnd scope."); + return (IODataJsonLightReaderResourceState)this.CurrentScope; + } + } + + /// + /// Returns current scope cast to JsonLightResourceSetScope + /// + private JsonLightResourceSetScope CurrentJsonLightResourceSetScope + { + get + { + return ((JsonLightResourceSetScope)this.CurrentScope); + } + } + + /// + /// Returns current scope cast to JsonLightNestedResourceInfoScope + /// + private JsonLightNestedResourceInfoScope CurrentJsonLightNestedResourceInfoScope + { + get + { + return ((JsonLightNestedResourceInfoScope)this.CurrentScope); + } + } + + /// + /// Returns nest info of current resource. + /// + private ODataNestedResourceInfo ParentNestedInfo + { + get + { + // NestInfo/Resource or NestInfo/ResourceSet/Resource + Scope scope = SeekScope(maxDepth: 3); + + return scope != null ? (ODataNestedResourceInfo)scope.Item : null; + } + } + #endregion private properties + + #region ReadAt<>Implementation Methods + #region ReadAtStartImplementation + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: when reading a resource set: the reader is positioned on the first item in the resource set or the end array node of an empty resource set + /// when reading a resource: the first node of the first nested resource info value, null for a null expanded link or an end object + /// node if there are no navigation links. + /// + protected override bool ReadAtStartImplementation() + { + Debug.Assert(this.State == ODataReaderState.Start, "this.State == ODataReaderState.Start"); + Debug.Assert( + this.IsReadingNestedPayload || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.None, + "Pre-Condition: expected JsonNodeType.None when not reading a nested payload."); + + PropertyAndAnnotationCollector propertyAndAnnotationCollector = + this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(); + + // Position the reader on the first node depending on whether we are reading a nested payload or a Uri Operation Parameter or not. + ODataPayloadKind payloadKind = this.ReadingDelta + ? ODataPayloadKind.Delta + : this.ReadingResourceSet ? + ODataPayloadKind.ResourceSet + : ODataPayloadKind.Resource; + + // Following parameter "this.IsReadingNestedPayload || this.readingParameter" indicates whether to read + // { "value" : + // or + // { "parameterName" : + this.jsonLightResourceDeserializer.ReadPayloadStart( + payloadKind, + propertyAndAnnotationCollector, + this.IsReadingNestedPayload || this.readingParameter, + /*allowEmptyPayload*/false); + + ResolveScopeInfoFromContextUrl(); + + Scope currentScope = this.CurrentScope; + if (this.jsonLightInputContext.Model.IsUserModel()) + { + var derivedTypeConstraints = this.jsonLightInputContext.Model.GetDerivedTypeConstraints(currentScope.NavigationSource); + if (derivedTypeConstraints != null) + { + currentScope.DerivedTypeValidator = new DerivedTypeValidator(currentScope.ResourceType, derivedTypeConstraints, "navigation source", currentScope.NavigationSource.Name); + } + } + + return this.ReadAtStartImplementationSynchronously(propertyAndAnnotationCollector); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: when reading a resource set: the reader is positioned on the first item in the resource set or the end array node of an empty resource set + /// when reading a resource: the first node of the first nested resource info value, null for a null expanded link or an end object + /// node if there are no navigation links. + /// + protected override Task ReadAtStartImplementationAsync() + { + Debug.Assert(this.State == ODataReaderState.Start, "this.State == ODataReaderState.Start"); + Debug.Assert( + this.IsReadingNestedPayload || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.None, + "Pre-Condition: expected JsonNodeType.None when not reading a nested payload."); + + PropertyAndAnnotationCollector propertyAndAnnotationCollector = + this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(); + + // Position the reader on the first node depending on whether we are reading a nested payload or not. + ODataPayloadKind payloadKind = this.ReadingDelta + ? ODataPayloadKind.Delta + : this.ReadingResourceSet ? + ODataPayloadKind.ResourceSet + : ODataPayloadKind.Resource; + + return this.jsonLightResourceDeserializer.ReadPayloadStartAsync( + payloadKind, + propertyAndAnnotationCollector, + this.IsReadingNestedPayload, + /*allowEmptyPayload*/false) + .FollowOnSuccessWith(t => ResolveScopeInfoFromContextUrl()) + .FollowOnSuccessWith(t => + this.ReadAtStartImplementationSynchronously(propertyAndAnnotationCollector)); + } +#endif + #endregion ReadAtStartImplementation + + #region ResourceSet + /// + /// Implementation of the reader logic when in state 'ResourceSetStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: Any start node - The first resource in the resource set + /// JsonNodeType.EndArray - The end of the resource set + /// Post-Condition: The reader is positioned over the StartObject node of the first resource in the resource set or + /// on the node following the resource set end in case of an empty resource set + /// + protected override bool ReadAtResourceSetStartImplementation() + { + return this.ReadAtResourceSetStartImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'ResourceSetStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: Any start node - The first resource in the resource set + /// JsonNodeType.EndArray - The end of the resource set + /// Post-Condition: The reader is positioned over the StartObject node of the first resource in the resource set or + /// on the node following the resource set end in case of an empty resource set + /// + protected override Task ReadAtResourceSetStartImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtResourceSetStartImplementationSynchronously); + } +#endif + + /// + /// Implementation of the reader logic when in state 'ResourceSetEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property if the resource set has further instance or property annotations after the resource set property + /// JsonNodeType.EndObject if the resource set has no further instance or property annotations after the resource set property + /// Post-Condition: JsonNodeType.EndOfInput for a top-level resource set when not reading a nested payload + /// JsonNodeType.Property more properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndObject no further properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndArray end of expanded link in request, in this case the resource set doesn't actually own the array object and it won't read it. + /// Any in case of expanded resource set in request, this might be the next item in the expanded array, which is not a resource + /// + protected override bool ReadAtResourceSetEndImplementation() + { + return this.ReadAtResourceSetEndImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'ResourceSetEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property if the resource set has further instance or property annotations after the resource set property + /// JsonNodeType.EndObject if the resource set has no further instance or property annotations after the resource set property + /// Post-Condition: JsonNodeType.EndOfInput for a top-level resource set when not reading a nested payload + /// JsonNodeType.Property more properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndObject no further properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndArray end of expanded link in request, in this case the resource set doesn't actually own the array object and it won't read it. + /// Any in case of expanded resource set in request, this might be the next item in the expanded array, which is not a resource + /// + protected override Task ReadAtResourceSetEndImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtResourceSetEndImplementationSynchronously); + } +#endif + #endregion ResourceSet + + #region Resource + /// + /// Implementation of the reader logic when in state 'EntryStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + protected override bool ReadAtResourceStartImplementation() + { + return this.ReadAtResourceStartImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'EntryStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + protected override Task ReadAtResourceStartImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtResourceStartImplementationSynchronously); + } +#endif + + /// + /// Implementation of the reader logic when in state 'EntryEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject end of object of the resource + /// JsonNodeType.PrimitiveValue end of primitive value in a collection + /// Post-Condition: The reader is positioned on the first node after the resource's end-object node + /// + protected override bool ReadAtResourceEndImplementation() + { + return this.ReadAtResourceEndImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'EntryEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject end of object of the resource + /// JsonNodeType.PrimitiveValue (null) end of null expanded resource + /// Post-Condition: The reader is positioned on the first node after the resource's end-object node + /// + protected override Task ReadAtResourceEndImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtResourceEndImplementationSynchronously); + } +#endif + + #endregion Resource + + #region Primitive + /// + /// Implementation of the reader logic when in state 'Primitive'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue end of primitive value in a collection + /// Post-Condition: The reader is positioned on the first node after the primitive value + /// + protected override bool ReadAtPrimitiveImplementation() + { + return this.ReadAtPrimitiveSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'Primitive'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue end of primitive value in a collection + /// Post-Condition: The reader is positioned on the first node after the primitive value + /// + protected override Task ReadAtPrimitiveImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtPrimitiveSynchronously); + } +#endif + #endregion Primitive + + #region Property + + /// + /// Implementation of the reader logic when in state 'PropertyInfo'. + /// + /// true if more items can be read from the reader; otherwise false. + protected override bool ReadAtNestedPropertyInfoImplementation() + { + return this.ReadAtNestedPropertyInfoSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'PropertyInfo'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected override Task ReadAtNestedPropertyInfoImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtNestedPropertyInfoSynchronously); + } +#endif + #endregion + + #region Stream + + /// + /// Implementation of the reader logic when in state 'Stream'. + /// + /// true if more items can be read from the reader; otherwise false. + protected override bool ReadAtStreamImplementation() + { + return this.ReadAtStreamSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'Stream'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected override Task ReadAtStreamImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtStreamSynchronously); + } +#endif + + /// + /// Creates a stream for reading an inline stream property. + /// + /// A stream for reading the stream property. + protected override Stream CreateReadStreamImplementation() + { + Stream stream; + IJsonStreamReader streamReader = this.jsonLightInputContext.JsonReader as IJsonStreamReader; + if (streamReader != null) + { + stream = streamReader.CreateReadStream(); + } + else + { + // JSONReader doesn't support streaming; read as a string and convert + // Skip over property or start array + this.jsonLightInputContext.JsonReader.Read(); + string valueAsString = this.jsonLightInputContext.JsonReader.ReadStringValue(); + stream = new MemoryStream(Convert.FromBase64String(valueAsString.Replace('_', '/').Replace('-', '+'))); + } + + return stream; + } + + protected override TextReader CreateTextReaderImplementation() + { + TextReader reader; + IJsonStreamReader jsonStreamReader = this.jsonLightInputContext.JsonReader as IJsonStreamReader; + if (jsonStreamReader != null) + { + reader = jsonStreamReader.CreateTextReader(); + } + else + { + // JSONReader doesn't support streaming; read as a string and convert + // Skip over property or start array + this.jsonLightInputContext.JsonReader.Read(); + string valueAsString = this.jsonLightInputContext.JsonReader.ReadStringValue(); + reader = new StringReader(valueAsString); + } + + return reader; + } + + #endregion + + #region NestedResourceInfo + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject start of an expanded resource + /// JsonNodeType.StartArray start of an expanded resource set + /// JsonNodeType.PrimitiveValue (null) expanded null resource + /// JsonNodeType.Property deferred link with more properties in owning resource + /// JsonNodeType.EndObject deferred link as last property of the owning resource + /// Post-Condition: JsonNodeType.StartArray: start of expanded resource + /// JsonNodeType.StartObject start of expanded resource set + /// JsonNodeType.PrimitiveValue (null) expanded null resource + /// JsonNodeType.Property deferred link with more properties in owning resource + /// JsonNodeType.EndObject deferred link as last property of the owning resource + /// + protected override bool ReadAtNestedResourceInfoStartImplementation() + { + return this.ReadAtNestedResourceInfoStartImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject start of an expanded resource + /// JsonNodeType.StartArray start of an expanded resource set + /// JsonNodeType.PrimitiveValue (null) expanded null resource + /// JsonNodeType.Property deferred link with more properties in owning resource + /// JsonNodeType.EndObject deferred link as last property of the owning resource + /// Post-Condition: JsonNodeType.StartArray: start of expanded resource + /// JsonNodeType.StartObject start of expanded resource set + /// JsonNodeType.PrimitiveValue (null) expanded null resource + /// JsonNodeType.Property deferred link with more properties in owning resource + /// JsonNodeType.EndObject deferred link as last property of the owning resource + /// + protected override Task ReadAtNestedResourceInfoStartImplementationAsync() + { + return + TaskUtils.GetTaskForSynchronousOperation( + this.ReadAtNestedResourceInfoStartImplementationSynchronously); + } +#endif + + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject: nested resource info is last property in owning resource + /// JsonNodeType.Property: there are more properties after the nested resource info in the owning resource + /// Post-Condition: JsonNodeType.StartObject start of the expanded resource nested resource info to read next + /// JsonNodeType.StartArray start of the expanded resource set nested resource info to read next + /// JsonNoteType.Primitive (null) expanded null resource nested resource info to read next + /// JsonNoteType.Property property after deferred link or entity reference link + /// JsonNodeType.EndObject end of the parent resource + /// + protected override bool ReadAtNestedResourceInfoEndImplementation() + { + return this.ReadAtNestedResourceInfoEndImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject: nested resource info is last property in owning resource + /// JsonNodeType.Property: there are more properties after the nested resource info in the owning resource + /// Post-Condition: JsonNodeType.StartObject start of the expanded resource nested resource info to read next + /// JsonNodeType.StartArray start of the expanded resource set nested resource info to read next + /// JsonNoteType.Primitive (null) expanded null resource nested resource info to read next + /// JsonNoteType.Property property after deferred link or entity reference link + /// JsonNodeType.EndObject end of the parent resource + /// + protected override Task ReadAtNestedResourceInfoEndImplementationAsync() + { + return + TaskUtils.GetTaskForSynchronousOperation( + this.ReadAtNestedResourceInfoEndImplementationSynchronously); + } +#endif + + #endregion NestedResourceInfo + + #region EntityReferenceLink + /// + /// Implementation of the reader logic when in state 'EntityReferenceLink'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// This method doesn't move the reader + /// Pre-Condition: JsonNodeType.EndObject: expanded link property is last property in owning resource + /// JsonNodeType.Property: there are more properties after the expanded link property in the owning resource + /// Any: expanded collection link - the node after the entity reference link. + /// Post-Condition: JsonNodeType.EndObject: expanded link property is last property in owning resource + /// JsonNodeType.Property: there are more properties after the expanded link property in the owning resource + /// Any: expanded collection link - the node after the entity reference link. + /// + protected override bool ReadAtEntityReferenceLink() + { + return this.ReadAtEntityReferenceLinkSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'EntityReferenceLink'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// This method doesn't move the reader + /// Pre-Condition: JsonNodeType.EndObject: expanded link property is last property in owning resource + /// JsonNodeType.Property: there are more properties after the expanded link property in the owning resource + /// Any: expanded collection link - the node after the entity reference link. + /// Post-Condition: JsonNodeType.EndObject: expanded link property is last property in owning resource + /// JsonNodeType.Property: there are more properties after the expanded link property in the owning resource + /// Any: expanded collection link - the node after the entity reference link. + /// + protected override Task ReadAtEntityReferenceLinkAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtEntityReferenceLinkSynchronously); + } +#endif + #endregion EntityReferenceLink + + #region DeltaResourceSet + + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: Any start node - The first resource in the resource set + /// JsonNodeType.EndArray - The end of the resource set + /// Post-Condition: The reader is positioned over the StartObject node of the first resource in the resource set or + /// on the node following the resource set end in case of an empty resource set + /// + protected override bool ReadAtDeltaResourceSetStartImplementation() + { + return this.ReadAtResourceSetStartImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: Any start node - The first resource in the resource set + /// JsonNodeType.EndArray - The end of the resource set + /// Post-Condition: The reader is positioned over the StartObject node of the first resource in the resource set or + /// on the node following the resource set end in case of an empty resource set + /// + protected override Task ReadAtDeltaResourceSetStartImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtResourceSetStartImplementationSynchronously); + } +#endif + + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property if the resource set has further instance or property annotations after the resource set property + /// JsonNodeType.EndObject if the resource set has no further instance or property annotations after the resource set property + /// Post-Condition: JsonNodeType.EndOfInput for a top-level resource set when not reading a nested payload + /// JsonNodeType.Property more properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndObject no further properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndArray end of expanded link in request, in this case the resource set doesn't actually own the array object and it won't read it. + /// Any in case of expanded resource set in request, this might be the next item in the expanded array, which is not a resource + /// + protected override bool ReadAtDeltaResourceSetEndImplementation() + { + // Logic is same as for ResourceSet + return this.ReadAtResourceSetEndImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property if the resource set has further instance or property annotations after the resource set property + /// JsonNodeType.EndObject if the resource set has no further instance or property annotations after the resource set property + /// Post-Condition: JsonNodeType.EndOfInput for a top-level resource set when not reading a nested payload + /// JsonNodeType.Property more properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndObject no further properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndArray end of expanded link in request, in this case the resource set doesn't actually own the array object and it won't read it. + /// Any in case of expanded resource set in request, this might be the next item in the expanded array, which is not a resource + /// + protected override Task ReadAtDeltaResourceSetEndImplementationAsync() + { + // Logic is same as for ResourceSet + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtResourceSetEndImplementationSynchronously); + } +#endif + #endregion DeltaResourceSet + + #region DeltaDeletedEntry + + /// + /// Implementation of the reader logic when in state 'DeltaDeletedEntryStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + protected override bool ReadAtDeletedResourceStartImplementation() + { + return this.ReadAtDeletedResourceStartImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'DeltaDeletedResourceStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + protected override Task ReadAtDeletedResourceStartImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeletedResourceStartImplementationSynchronously); + } +#endif + + /// + /// Implementation of the reader logic when in state 'DeletedResourceEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject end of object of the resource + /// JsonNodeType.PrimitiveValue end of primitive value in a collection + /// Post-Condition: The reader is positioned on the first node after the deleted resource's end-object node + /// + protected override bool ReadAtDeletedResourceEndImplementation() + { + // Same logic as ReadAtResourceEndImplementation + return this.ReadAtResourceEndImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'DeletedResourceEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject end of object of the resource + /// JsonNodeType.PrimitiveValue (null) end of null expanded resource + /// Post-Condition: The reader is positioned on the first node after the deleted resource's end-object node + /// + protected override Task ReadAtDeletedResourceEndImplementationAsync() + { + // Same logic as ReadAtResourceEndImplementationAsync + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtResourceEndImplementationSynchronously); + } +#endif + + #endregion DeltaDeletedEntry + + #region DeltaLink + + /// + /// Implementation of the reader logic when in state 'DeltaLinkImplementation'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndArry If no (more) properties exist in the resource's content + /// + protected override bool ReadAtDeltaLinkImplementation() + { + return this.ReadAtDeltaLinkImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'DeltaLinkImplementation'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + protected override Task ReadAtDeltaLinkImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeltaLinkImplementationSynchronously); + } +#endif + + #endregion DeltaLink + + #region DeltaDeletedLink + + /// + /// Implementation of the reader logic when in state 'DeltaLinkImplementation'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndArry If no (more) properties exist in the resource's content + /// + protected override bool ReadAtDeltaDeletedLinkImplementation() + { + return this.ReadAtDeltaDeletedLinkImplementationSynchronously(); + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'DeltaDeletedLinkImplementation'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + protected override Task ReadAtDeltaDeletedLinkImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeltaDeletedLinkImplementationSynchronously); + } +#endif + + #endregion DeltaDeletedLink + + #endregion ReadAt<>Implementation methods + + #region ReadAt<>ImplementationSynchronously methods + + #region Start + + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// The duplicate property names checker to use for the top-level scope. + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet when not reading a nested payload. + /// Post-Condition: when reading a resource set: the reader is positioned on the first item in the resource set or the end array node of an empty resource set + /// when reading a resource: the first node of the first nested resource info value, null for a null expanded link or an end object + /// node if there are no navigation links. + /// + private bool ReadAtStartImplementationSynchronously( + PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + // For nested payload (e.g., expanded resource set or resource in delta $entity payload), + // we usually don't have a context URL for the resource set or resource: + // { + // "@odata.context":"...", <--- this context URL is for delta entity only + // "value": [ + // { + // ... + // "NavigationProperty": <--- usually we don't have a context URL for this + // [ <--- nested payload start + // {...} + // ] <--- nested payload end + // } + // ] + // } + // + // The consequence is that the resource we read out from a nested payload doesn't + // have an entity metadata builder thus you cannot compute read link, edit link, + // etc. from the resource object. + if (this.jsonLightInputContext.ReadingResponse && !this.IsReadingNestedPayload) + { + Debug.Assert(this.jsonLightResourceDeserializer.ContextUriParseResult != null, + "We should have failed by now if we don't have parse results for context URI."); + + // Validate the context URI parsed from the payload against the entity set and entity type passed in through the API. + ReaderValidationUtils.ValidateResourceSetOrResourceContextUri( + this.jsonLightResourceDeserializer.ContextUriParseResult, this.CurrentScope, true); + } + + // Get the $select query option from the metadata link, if we have one. + string selectQueryOption = this.jsonLightResourceDeserializer.ContextUriParseResult == null + ? null + : this.jsonLightResourceDeserializer.ContextUriParseResult.SelectQueryOption; + + SelectedPropertiesNode selectedProperties = SelectedPropertiesNode.Create(selectQueryOption, (this.CurrentResourceTypeReference != null) ? this.CurrentResourceTypeReference.AsStructured().StructuredDefinition() : null, this.jsonLightInputContext.Model); + + if (this.ReadingResourceSet) + { + // Store the duplicate property names checker to use it later when reading the resource set end + // (since we allow resourceSet-related annotations to appear after the resource set's data). + this.topLevelScope.PropertyAndAnnotationCollector = propertyAndAnnotationCollector; + + bool isReordering = this.jsonLightInputContext.JsonReader is ReorderingJsonReader; + + if (this.ReadingDelta) + { + ODataDeltaResourceSet resourceSet = new ODataDeltaResourceSet(); + + // Read top-level resource set annotations for delta resource sets. + this.jsonLightResourceDeserializer.ReadTopLevelResourceSetAnnotations( + resourceSet, propertyAndAnnotationCollector, /*forResourceSetStart*/true, + /*readAllFeedProperties*/isReordering); + this.ReadDeltaResourceSetStart(resourceSet, selectedProperties); + + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.EndArray, JsonNodeType.StartObject); + } + else + { + ODataResourceSet resourceSet = new ODataResourceSet(); + if (!this.IsReadingNestedPayload) + { + if (!this.readingParameter) + { + // Skip top-level resource set annotations for nested resource sets. + this.jsonLightResourceDeserializer.ReadTopLevelResourceSetAnnotations( + resourceSet, propertyAndAnnotationCollector, /*forResourceSetStart*/true, + /*readAllFeedProperties*/isReordering); + } + else + { + // This line will be used to read the first node of a resource set in Uri operation parameter, The first node is : '[' + // Node is in following format: + // [ + // {...}, <------------ complex object. + // {...}, <------------ complex object. + // ] + this.jsonLightResourceDeserializer.JsonReader.Read(); + } + } + + this.ReadResourceSetStart(resourceSet, selectedProperties); + } + + return true; + } + + this.ReadResourceSetItemStart(propertyAndAnnotationCollector, selectedProperties); + return true; + } + + #endregion Start + + #region ResourceSet + + /// + /// Implementation of the reader logic when in state 'ResourceSetStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: Any start node - The first resource in the resource set + /// JsonNodeType.EndArray - The end of the resource set + /// Post-Condition: The reader is positioned over the StartObject node of the first resource in the resource set or + /// on the node following the resource set end in case of an empty resource set + /// + private bool ReadAtResourceSetStartImplementationSynchronously() + { + this.ReadNextResourceSetItem(); + return true; + } + + /// + /// Implementation of the reader logic when in state 'ResourceSetEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property if the resource set has further instance or property annotations after the resource set property + /// JsonNodeType.EndObject if the resource set has no further instance or property annotations after the resource set property + /// JsonNodeType.EndOfInput if the resource set is in a Uri operation parameter. + /// JsonNodeType.StartArray if the resource set is a member of an untyped collection followed by a collection + /// JsonNodeType.PrimitiveValue if the resource set is a member of an untyped collection followed by a primitive value + /// JsonNodeType.StartObject if the resource set is a member of an untyped collection followed by a resource + /// JsonNodeType.EndArray if the resource set is the last member of an untyped collection + /// Post-Condition: JsonNodeType.EndOfInput for a top-level resource set when not reading a nested payload + /// JsonNodeType.Property more properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndObject no further properties exist on the owning resource after the expanded link containing the resource set + /// JsonNodeType.EndArray end of expanded link in request, in this case the resource set doesn't actually own the array object and it won't read it. + /// Any in case of expanded resource set in request, this might be the next item in the expanded array, which is not a resource + /// + private bool ReadAtResourceSetEndImplementationSynchronously() + { + Debug.Assert(this.State == ODataReaderState.ResourceSetEnd || this.State == ODataReaderState.DeltaResourceSetEnd, "Not in (delta) resource set end state."); + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.Property || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndOfInput || + (this.ParentScope != null && (this.ParentScope.ResourceType == null || this.ParentScope.ResourceType.TypeKind == EdmTypeKind.Untyped) && + (this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartArray || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartObject || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndArray)) || + !this.IsTopLevel && !this.jsonLightInputContext.ReadingResponse, + "Pre-Condition: expected JsonNodeType.EndObject or JsonNodeType.Property, or JsonNodeType.StartArray, JsonNodeTypeStart.Object, or JsonNodeType.EndArray with an untyped collection"); + + bool isTopLevelResourceSet = this.IsTopLevel; + bool isExpandedLinkContent = this.IsExpandedLinkContent; + + this.PopScope(this.State == ODataReaderState.ResourceSetEnd ? ODataReaderState.ResourceSetEnd : ODataReaderState.DeltaResourceSetEnd); + + // When we finish a top-level resource set in a nested payload (inside parameter or delta payload), + // we can directly turn the reader into Completed state because we don't have any JSON token + // (e.g., EndObject in a normal resource set payload) left in the stream. + // + // Nested resource set payload: + // [ + // {...}, + // ... + // ] + // EOF <--- current reader position + // + // Normal resource set payload: + // { + // "@odata.context":"...", + // ..., + // "value": [ + // {...}, + // ... + // ], + // "@odata.nextLink":"..." + // } <--- current reader position + // EOF + // + // Normal resource set payload as uri operation parameter + // [ + // {...}, + // ... + // ] + // EOF <--- current reader position + if ((this.IsReadingNestedPayload || this.readingParameter) && isTopLevelResourceSet) + { + // replace the 'Start' scope with the 'Completed' scope + this.ReplaceScope(ODataReaderState.Completed); + return false; + } + + if (isTopLevelResourceSet) + { + Debug.Assert(this.State == ODataReaderState.Start, "this.State == ODataReaderState.Start"); + + // Read the end-object node of the resource set object and position the reader on the next input node + // This can hit the end of the input. + this.jsonLightResourceDeserializer.JsonReader.Read(); + + // read the end-of-payload + this.jsonLightResourceDeserializer.ReadPayloadEnd(this.IsReadingNestedPayload); + + // replace the 'Start' scope with the 'Completed' scope + this.ReplaceScope(ODataReaderState.Completed); + return false; + } + else if (isExpandedLinkContent) + { + // finish reading the expanded link + this.ReadExpandedNestedResourceInfoEnd(true); + return true; + } + + // read the next item in an untyped collection + this.ReadNextResourceSetItem(); + return true; + } + + #endregion ResourceSet + + #region Resource + /// + /// Implementation of the reader logic when in state 'ResourceStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + private bool ReadAtResourceStartImplementationSynchronously() + { + ODataResourceBase currentResource = this.Item as ODataResourceBase; + if (currentResource != null && !this.IsReadingNestedPayload) + { + this.CurrentResourceState.ResourceTypeFromMetadata = this.ParentScope.ResourceType as IEdmStructuredType; + ODataResourceMetadataBuilder builder = + this.jsonLightResourceDeserializer.MetadataContext.GetResourceMetadataBuilderForReader( + this.CurrentResourceState, + this.jsonLightInputContext.ODataSimplifiedOptions.EnableReadingKeyAsSegment, + this.ReadingDelta); + if (builder != currentResource.MetadataBuilder) + { + ODataNestedResourceInfo parentNestInfo = this.ParentNestedInfo; + ODataConventionalResourceMetadataBuilder conventionalResourceMetadataBuilder = + builder as ODataConventionalResourceMetadataBuilder; + + // If it's ODataConventionalResourceMetadataBuilder, then it means we need to build nested relation ship for it in containment case + if (conventionalResourceMetadataBuilder != null) + { + if (parentNestInfo != null) + { + conventionalResourceMetadataBuilder.NameAsProperty = parentNestInfo.Name; + conventionalResourceMetadataBuilder.IsFromCollection = parentNestInfo.IsCollection == true; + conventionalResourceMetadataBuilder.ODataUri = ResolveODataUriFromContextUrl(parentNestInfo) ?? CurrentScope.ODataUri; + } + + conventionalResourceMetadataBuilder.StartResource(); + } + + // Set the metadata builder and parent metadata builder for the resource itself + currentResource.MetadataBuilder = builder; + if (parentNestInfo != null && parentNestInfo.MetadataBuilder != null) + { + currentResource.MetadataBuilder.ParentMetadataBuilder = parentNestInfo.MetadataBuilder; + } + } + } + + if (currentResource == null) + { + // Debug.Assert(this.IsExpandedLinkContent || this.CurrentResourceType.IsODataComplexTypeKind() || this.CurrentResourceType.TypeKind == EdmTypeKind.Untyped, + // "null or untyped resource can only be reported in an expanded link or in collection of complex instance."); + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.PrimitiveValue); + + // There's nothing to read, so move to the end resource state + this.EndEntry(); + } + else if (this.CurrentResourceState.FirstNestedInfo != null) + { + this.ReadNestedInfo(this.CurrentResourceState.FirstNestedInfo); + } + else + { + // End of resource + // All the properties have already been read before we acually entered the EntryStart state (since we read as far as we can in any given state). + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.EndObject); + this.EndEntry(); + } + + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartObject || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartArray || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.Property || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject, + "Post-Condition: expected JsonNodeType.StartObject or JsonNodeType.StartArray or JsonNodeType.PrimitiveValue or JsonNodeType.Property or JsonNodeType.EndObject"); + + return true; + } + + /// + /// If the nested info has context url, resolve it to ODataUri. + /// + /// The nestedInfo to be evaluated. + /// The odata uri resolved from context url. + private ODataUri ResolveODataUriFromContextUrl(ODataNestedResourceInfo nestedInfo) + { + if (nestedInfo != null && nestedInfo.ContextUrl != null) + { + var payloadKind = nestedInfo.IsCollection.GetValueOrDefault() + ? ODataPayloadKind.ResourceSet + : ODataPayloadKind.Resource; + var odataPath = ODataJsonLightContextUriParser.Parse( + this.jsonLightResourceDeserializer.Model, + UriUtils.UriToString(nestedInfo.ContextUrl), + payloadKind, + this.jsonLightResourceDeserializer.MessageReaderSettings.ClientCustomTypeResolver, + this.jsonLightResourceDeserializer.JsonLightInputContext.ReadingResponse).Path; + + return new ODataUri() { Path = odataPath }; + } + + return null; + } + + /// + /// Implementation of the reader logic when in state 'EntryEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject end of object of the resource + /// JsonNodeType.PrimitiveValue (null) end of null expanded resource + /// Post-Condition: The reader is positioned on the first node after the resource's end-object node + /// + private bool ReadAtResourceEndImplementationSynchronously() + { + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject || + (this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue && + this.jsonLightResourceDeserializer.JsonReader.Value == null), + "Pre-Condition: JsonNodeType.EndObject or JsonNodeType.PrimitiveValue (null)"); + + // We have to cache these values here, since the PopScope below will destroy them. + bool isTopLevel = this.IsTopLevel; + bool isExpandedLinkContent = this.IsExpandedLinkContent; + + this.PopScope(this.State == ODataReaderState.ResourceEnd ? ODataReaderState.ResourceEnd : ODataReaderState.DeletedResourceEnd); + + // Read over the end object node (or null value) and position the reader on the next node in the input. + // This can hit the end of the input. + this.jsonLightResourceDeserializer.JsonReader.Read(); + + // Analyze the next Json token to determine whether it is start object (next resource), end array (resource set end) or eof (top-level resource end) + bool result = true; + if (isTopLevel) + { + // NOTE: we rely on the underlying JSON reader to fail if there is more than one value at the root level. + Debug.Assert( + this.IsReadingNestedPayload || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndOfInput, + "Expected JSON reader to have reached the end of input when not reading a nested payload."); + + // read the end-of-payload + Debug.Assert(this.State == ODataReaderState.Start, "this.State == ODataReaderState.Start"); + this.jsonLightResourceDeserializer.ReadPayloadEnd(this.IsReadingNestedPayload); + Debug.Assert( + this.IsReadingNestedPayload || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndOfInput, + "Expected JSON reader to have reached the end of input when not reading a nested payload."); + + // replace the 'Start' scope with the 'Completed' scope + this.ReplaceScope(ODataReaderState.Completed); + result = false; + } + else if (isExpandedLinkContent) + { + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject || // expanded link resource as last property of the owning resource + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.Property, // expanded link resource with more properties on the resource + "Invalid JSON reader state for reading end of resource in expanded link."); + + // finish reading the expanded link + this.ReadExpandedNestedResourceInfoEnd(false); + } + else + { + this.ReadNextResourceSetItem(); + } + + return result; + } + + #endregion Resource + + #region Primitive + + /// + /// Implementation of the reader logic when in state 'Primitive'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue primitive value + /// Post-Condition: The reader is positioned on the first node after the primitive value + /// + private bool ReadAtPrimitiveSynchronously() + { + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue, + "Pre-Condition: JsonNodeType.PrimitiveValue (null or untyped)"); + + this.PopScope(ODataReaderState.Primitive); + + // Read over the end object node (or null value) and position the reader on the next node in the input. + // This should never hit the end of the input. + this.jsonLightResourceDeserializer.JsonReader.Read(); + this.ReadNextResourceSetItem(); + return true; + } + + #endregion Primitive + + #region DeletedEntry + + /// + /// Implementation of the reader logic when in state 'DeletedEntryStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.StartObject Start of the expanded resource of the nested resource info to read next. + /// JsonNodeType.StartArray Start of the expanded resource set of the nested resource info to read next. + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next. + /// JsonNodeType.Property The next property after a deferred link or entity reference link + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + private bool ReadAtDeletedResourceStartImplementationSynchronously() + { + Debug.Assert(this.CurrentScope is JsonLightDeletedResourceScope); + + if (((JsonLightDeletedResourceScope)(this.CurrentScope)).Is40DeletedResource) + { + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.EndObject); + this.EndEntry(); + return true; + } + + return this.ReadAtResourceStartImplementationSynchronously(); + } + + #endregion DeletedEntry + + #region (Deleted)Link + + /// + /// Implementation of the reader logic when in state 'DeltaLink'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property The next annotation. + /// JsonNodeType.EndObject No more other annotation or property in the link. + /// Post-Condition: The reader is positioned on the first node after the link's end-object node. + /// + private bool ReadAtDeltaLinkImplementationSynchronously() + { + return this.EndDeltaLink(ODataReaderState.DeltaLink); + } + + /// + /// Implementation of the reader logic when in state 'DeltaDeletedLink'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property The next annotation. + /// JsonNodeType.EndObject No more other annotation or property in the link. + /// Post-Condition: The reader is positioned on the first node after the link's end-object node. + /// + private bool ReadAtDeltaDeletedLinkImplementationSynchronously() + { + return this.EndDeltaLink(ODataReaderState.DeltaDeletedLink); + } + + /// + /// Reads the end of the delta(deleted)link. + /// + /// The state of the link or deleted link being completed. + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property The next annotation. + /// JsonNodeType.EndObject No more other annotation or property in the link. + /// Post-Condition: The reader is positioned on the first node after the link's end-object node. + /// + private bool EndDeltaLink(ODataReaderState readerState) + { + Debug.Assert(readerState == ODataReaderState.DeltaLink || readerState == ODataReaderState.DeltaDeletedLink, "ReadAtDeltaLinkImplementation called when not on delta(deleted)link"); + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject, + "Not positioned at end of object after reading delta link"); + + this.PopScope(readerState); + + // Read over the end object node (or null value) and position the reader on the next node in the input. + // This should never hit the end of the input. + this.jsonLightResourceDeserializer.JsonReader.Read(); + this.ReadNextResourceSetItem(); + return true; + } + + #endregion (Deleted)Link + + #region NestedResourceInfo + + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoStart'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.StartObject start of an expanded resource + /// JsonNodeType.StartArray start of an expanded resource set + /// JsonNodeType.PrimitiveValue (null) expanded null resource + /// JsonNodeType.Property deferred link with more properties in owning resource + /// JsonNodeType.EndObject deferred link as last property of the owning resource or + /// reporting projected navigation links missing in the payload + /// Post-Condition: JsonNodeType.StartArray: start of expanded resource + /// JsonNodeType.StartObject start of expanded resource set + /// JsonNodeType.PrimitiveValue (null) expanded null resource + /// JsonNodeType.Property deferred link with more properties in owning resource + /// JsonNodeType.EndObject deferred link as last property of the owning resource or + /// reporting projected navigation links missing in the payload + /// + private bool ReadAtNestedResourceInfoStartImplementationSynchronously() + { + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.Property || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartObject || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartArray || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue && + this.jsonLightResourceDeserializer.JsonReader.Value == null, + "Pre-Condition: expected JsonNodeType.Property, JsonNodeType.EndObject, JsonNodeType.StartObject, JsonNodeType.StartArray or JsonNodeType.Primitive (null)"); + + ODataNestedResourceInfo currentLink = this.CurrentNestedResourceInfo; + + IODataJsonLightReaderResourceState parentResourceState = (IODataJsonLightReaderResourceState)this.ParentScope; + + if (this.jsonLightInputContext.ReadingResponse) + { + // If we are reporting a nested resource info that was projected but not included in the payload, + // simply change state to NestedResourceInfoEnd. + if (parentResourceState.ProcessingMissingProjectedNestedResourceInfos) + { + this.ReplaceScope(ODataReaderState.NestedResourceInfoEnd); + } + else if (!this.jsonLightResourceDeserializer.JsonReader.IsOnValueNode()) + { + // Deferred link (nested resource info which doesn't have a value and is in the response) + ReaderUtils.CheckForDuplicateNestedResourceInfoNameAndSetAssociationLink( + parentResourceState.PropertyAndAnnotationCollector, currentLink); + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.EndObject, JsonNodeType.Property); + + // Record that we read the link on the parent resource's scope. + parentResourceState.NavigationPropertiesRead.Add(currentLink.Name); + + this.ReplaceScope(ODataReaderState.NestedResourceInfoEnd); + } + else if (!currentLink.IsCollection.Value) + { + // We should get here only for declared or undeclared navigation properties. + Debug.Assert(this.CurrentResourceType != null || this.CurrentNestedResourceInfo.Name != null, + "We must have a declared navigation property to read expanded links."); + + // Expanded resource + ReaderUtils.CheckForDuplicateNestedResourceInfoNameAndSetAssociationLink( + parentResourceState.PropertyAndAnnotationCollector, currentLink); + this.ReadExpandedNestedResourceInfoStart(currentLink); + } + else + { + // Expanded resource set + ReaderUtils.CheckForDuplicateNestedResourceInfoNameAndSetAssociationLink( + parentResourceState.PropertyAndAnnotationCollector, currentLink); + + // We store the precreated expanded resource set in the nested resource info since it carries the annotations for it. + ODataJsonLightReaderNestedResourceInfo nestedResourceInfo = + this.CurrentJsonLightNestedResourceInfoScope.ReaderNestedResourceInfo; + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(nestedResourceInfo.NestedResourceSet != null, + "We must have a precreated expanded resource set already."); + JsonLightResourceBaseScope parentScope = (JsonLightResourceBaseScope)this.ParentScope; + SelectedPropertiesNode parentSelectedProperties = parentScope.SelectedProperties; + Debug.Assert(parentSelectedProperties != null, "parentProjectedProperties != null"); + + ODataResourceSet resourceSet = nestedResourceInfo.NestedResourceSet as ODataResourceSet; + if (resourceSet != null) + { + this.ReadResourceSetStart(resourceSet, parentSelectedProperties.GetSelectedPropertiesForNavigationProperty(parentScope.ResourceType, currentLink.Name)); + } + else + { + ODataDeltaResourceSet deltaResourceSet = nestedResourceInfo.NestedResourceSet as ODataDeltaResourceSet; + Debug.Assert(deltaResourceSet != null, "Nested recource collection is not a resource set or a delta resource set"); + this.ReadDeltaResourceSetStart(deltaResourceSet, parentSelectedProperties.GetSelectedPropertiesForNavigationProperty(parentScope.ResourceType, currentLink.Name)); + } + } + } + else + { + // Navigation link in request - report entity reference links and then possible expanded value. + ReaderUtils.CheckForDuplicateNestedResourceInfoNameAndSetAssociationLink( + parentResourceState.PropertyAndAnnotationCollector, + currentLink); + + this.ReadNextNestedResourceInfoContentItemInRequest(); + } + + return true; + } + + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject: nested resource info is last property in owning resource or + /// reporting projected navigation links missing in the payload + /// JsonNodeType.Property: there are more properties after the nested resource info in the owning resource + /// Post-Condition: JsonNodeType.StartObject start of the expanded resource nested resource info to read next + /// JsonNodeType.StartArray start of the expanded resource set nested resource info to read next + /// JsonNoteType.Primitive (null) expanded null resource nested resource info to read next + /// JsonNoteType.Property property after deferred link or entity reference link + /// JsonNodeType.EndObject end of the parent resource + /// + private bool ReadAtNestedResourceInfoEndImplementationSynchronously() + { + this.PopScope(ODataReaderState.NestedResourceInfoEnd); + return this.ReadNextNestedInfo(); + } + + + /// + /// Implementation of the reader logic when in state 'PropertyInfo'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.Property: there are more properties after the nested resource info in the owning resource + /// Post-Condition: JsonNodeType.StartObject start of the expanded resource nested resource info to read next + /// JsonNodeType.StartArray start of the expanded resource set nested resource info to read next + /// JsonNoteType.Primitive (null) expanded null resource nested resource info to read next + /// JsonNoteType.Property property after deferred link or entity reference link + /// JsonNodeType.EndObject end of the parent resource + /// + private bool ReadAtNestedPropertyInfoSynchronously() + { + ODataPropertyInfo propertyInfo = this.CurrentScope.Item as ODataPropertyInfo; + Debug.Assert(propertyInfo != null, "Reading Nested Property Without an ODataPropertyInfo"); + + ODataStreamPropertyInfo streamPropertyInfo = propertyInfo as ODataStreamPropertyInfo; + if (streamPropertyInfo != null && !String.IsNullOrEmpty(streamPropertyInfo.ContentType)) + { + this.StartNestedStreamInfo(new ODataJsonLightReaderStreamInfo(streamPropertyInfo.PrimitiveTypeKind, streamPropertyInfo.ContentType)); + } + else + { + this.StartNestedStreamInfo( + new ODataJsonLightReaderStreamInfo(propertyInfo.PrimitiveTypeKind)); + } + + return true; + } + + /// + /// Implementation of the reader logic when in state 'Stream'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// Pre-Condition: JsonNodeType.EndObject: nested resource info is last property in owning resource or + /// reporting projected navigation links missing in the payload + /// JsonNodeType.Property: there are more properties after the nested resource info in the owning resource + /// Post-Condition: JsonNodeType.StartObject start of the expanded resource nested resource info to read next + /// JsonNodeType.StartArray start of the expanded resource set nested resource info to read next + /// JsonNoteType.Primitive (null) expanded null resource nested resource info to read next + /// JsonNoteType.Property property after deferred link or entity reference link + /// JsonNodeType.EndObject end of the parent resource + /// + private bool ReadAtStreamSynchronously() + { + this.PopScope(ODataReaderState.Stream); + if (this.State == ODataReaderState.ResourceSetStart || + this.State == ODataReaderState.DeltaResourceSetStart) + { + // We are reading a stream within a collection + this.ReadNextResourceSetItem(); + return true; + } + + if (this.State == ODataReaderState.NestedProperty) + { + this.PopScope(ODataReaderState.NestedProperty); + } + + // We are reading a stream value + return this.ReadNextNestedInfo(); + } + + private bool ReadNextNestedInfo() + { + this.jsonLightResourceDeserializer.AssertJsonCondition( + JsonNodeType.EndObject, + JsonNodeType.Property); + Debug.Assert(this.State == ODataReaderState.ResourceStart || this.State == ODataReaderState.DeletedResourceStart, "Should be in (deleted) resource start state after reading stream."); + + ODataJsonLightReaderNestedInfo readerNestedInfo = null; + IODataJsonLightReaderResourceState resourceState = this.CurrentResourceState; + + if (this.jsonLightInputContext.ReadingResponse && + resourceState.ProcessingMissingProjectedNestedResourceInfos) + { + // We are reporting navigation links that were projected but missing from the payload + readerNestedInfo = resourceState.Resource.MetadataBuilder.GetNextUnprocessedNavigationLink(); + } + else + { + readerNestedInfo = this.jsonLightResourceDeserializer.ReadResourceContent(resourceState); + } + + if (readerNestedInfo == null) + { + // End of the resource + this.EndEntry(); + } + else + { + this.ReadNestedInfo(readerNestedInfo); + } + + return true; + } + + private void ReadNestedInfo(ODataJsonLightReaderNestedInfo nestedInfo) + { + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = nestedInfo as ODataJsonLightReaderNestedResourceInfo; + if (readerNestedResourceInfo != null) + { + // Next nested resource info on the resource + this.StartNestedResourceInfo(readerNestedResourceInfo); + } + else + { + ODataJsonLightReaderNestedPropertyInfo readerNestedStreamInfo = nestedInfo as ODataJsonLightReaderNestedPropertyInfo; + Debug.Assert(readerNestedStreamInfo != null, "NestedInfo is not a resource, stream, string"); + if (readerNestedStreamInfo != null) + { + this.StartNestedPropertyInfo(readerNestedStreamInfo); + } + } + } + + #endregion NestedResourceInfo + + #region EntityReferenceLink + + /// + /// Implementation of the reader logic when in state 'EntityReferenceLink'. + /// + /// true if more items can be read from the reader; otherwise false. + /// + /// This method doesn't move the reader + /// Pre-Condition: JsonNodeType.EndObject: expanded link property is last property in owning resource + /// JsonNodeType.Property: there are more properties after the expanded link property in the owning resource + /// Any: expanded collection link - the node after the entity reference link. + /// Post-Condition: JsonNodeType.EndObject: expanded link property is last property in owning resource + /// JsonNodeType.Property: there are more properties after the expanded link property in the owning resource + /// Any: expanded collection link - the node after the entity reference link. + /// + private bool ReadAtEntityReferenceLinkSynchronously() + { + this.PopScope(ODataReaderState.EntityReferenceLink); + Debug.Assert(this.State == ODataReaderState.NestedResourceInfoStart, + "this.State == ODataReaderState.NestedResourceInfoStart"); + + this.ReadNextNestedResourceInfoContentItemInRequest(); + return true; + } + + #endregion EntityReferenceLink + + #endregion ReadAt<>Synchronously methods + + #region Read<> methods + + #region ResourceSet + + /// + /// Reads the start of the JSON array for the content of the resource set and sets up the reader state correctly. + /// + /// The resource set to read the contents for. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// + /// Pre-Condition: The first node of the resource set property value; this method will throw if the node is not + /// JsonNodeType.StartArray + /// Post-Condition: The reader is positioned on the first item in the resource set, or on the end array of the resource set. + /// + private void ReadResourceSetStart(ODataResourceSet resourceSet, SelectedPropertiesNode selectedProperties) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + this.jsonLightResourceDeserializer.ReadResourceSetContentStart(); + IJsonReader jsonReader = this.jsonLightResourceDeserializer.JsonReader; + if (jsonReader.NodeType != JsonNodeType.EndArray + && jsonReader.NodeType != JsonNodeType.StartObject + && jsonReader.NodeType != JsonNodeType.PrimitiveValue + && jsonReader.NodeType != JsonNodeType.StartArray) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_InvalidNodeTypeForItemsInResourceSet(jsonReader.NodeType)); + } + + this.EnterScope(new JsonLightResourceSetScope(resourceSet, this.CurrentNavigationSource, + this.CurrentScope.ResourceTypeReference, selectedProperties, this.CurrentScope.ODataUri, /*isDelta*/ false)); + } + + /// + /// Reads the end of the current resource set. + /// + private void ReadResourceSetEnd() + { + Debug.Assert(this.State == ODataReaderState.ResourceSetStart || this.State == ODataReaderState.DeltaResourceSetStart, + "Not in ResourceSetStart or DeltaResourceSetStart state when reading end of (delta) resource set."); + Debug.Assert(this.Item is ODataResourceSetBase, "Current Item is not ResourceSetBase"); + + this.jsonLightResourceDeserializer.ReadResourceSetContentEnd(); + + ODataJsonLightReaderNestedResourceInfo expandedNestedResourceInfo = null; + JsonLightNestedResourceInfoScope parentNestedResourceInfoScope = (JsonLightNestedResourceInfoScope)this.ExpandedLinkContentParentScope; + if (parentNestedResourceInfoScope != null) + { + expandedNestedResourceInfo = parentNestedResourceInfoScope.ReaderNestedResourceInfo; + } + + if (!this.IsReadingNestedPayload && (this.IsExpandedLinkContent || this.IsTopLevel)) + { + // Temp ban reading the instance annotation after the resource set in parameter payload. (!this.IsReadingNestedPayload => !this.readingParameter) + // Nested resource set payload won't have a NextLink annotation after the resource set itself since the payload is NOT pageable. + this.jsonLightResourceDeserializer.ReadNextLinkAnnotationAtResourceSetEnd(this.Item as ODataResourceSetBase, + expandedNestedResourceInfo, this.topLevelScope.PropertyAndAnnotationCollector); + } + + this.ReplaceScope(this.State == ODataReaderState.ResourceSetStart ? ODataReaderState.ResourceSetEnd : ODataReaderState.DeltaResourceSetEnd); + } + + #endregion ResourceSet + + #region NestedResourceInfo + + /// + /// Reads the start of an expanded resource (null or non-null). + /// + /// The nested resource info that is being expanded. + /// + /// Pre-Condition: JsonNodeType.StartObject The start of the resource object + /// JsonNodeType.PrimitiveValue (null) The null resource value + /// Post-Condition: JsonNodeType.StartObject Start of expanded resource of the nested resource info to read next + /// JsonNodeType.StartArray Start of expanded resource set of the nested resource info to read next + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next, or the null value of the current null resource + /// JsonNodeType.Property Property after deferred link or expanded entity reference + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + private void ReadExpandedNestedResourceInfoStart(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + + if (this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue) + { + Debug.Assert(this.jsonLightResourceDeserializer.JsonReader.Value == null, + "If a primitive value is representing an expanded resource its value must be null."); + + var structuralProperty = + this.CurrentJsonLightNestedResourceInfoScope.ReaderNestedResourceInfo.StructuralProperty; + if (structuralProperty != null && !structuralProperty.Type.IsNullable) + { + ODataNullValueBehaviorKind nullValueReadBehaviorKind = + this.jsonLightResourceDeserializer.ReadingResponse + ? ODataNullValueBehaviorKind.Default + : this.jsonLightResourceDeserializer.Model.NullValueReadBehaviorKind(structuralProperty); + + if (nullValueReadBehaviorKind == ODataNullValueBehaviorKind.Default) + { + throw new ODataException( + Strings.ReaderValidationUtils_NullNamedValueForNonNullableType(nestedResourceInfo.Name, + structuralProperty.Type.FullName())); + } + } + + // Expanded null resource + // The expected type and expected navigation source for an expanded resource are the same as for the nested resource info around it. + this.EnterScope(new JsonLightResourceScope(ODataReaderState.ResourceStart, /*resource*/ null, + this.CurrentNavigationSource, this.CurrentResourceTypeReference, /*propertyAndAnnotationCollector*/null, + /*projectedProperties*/null, this.CurrentScope.ODataUri)); + } + else + { + // Expanded resource + // The expected type for an expanded resource is the same as for the nested resource info around it. + JsonLightResourceBaseScope parentScope = (JsonLightResourceBaseScope)this.ParentScope; + SelectedPropertiesNode parentSelectedProperties = parentScope.SelectedProperties; + Debug.Assert(parentSelectedProperties != null, "parentProjectedProperties != null"); + this.ReadResourceSetItemStart(/*propertyAndAnnotationCollector*/ null, parentSelectedProperties.GetSelectedPropertiesForNavigationProperty(parentScope.ResourceType, nestedResourceInfo.Name)); + } + } + + /// + /// Verifies that the current item is an instance, + /// sets the cardinality of the link (IsCollection property) and moves the reader + /// into state 'NestedResourceInfoEnd'. + /// + /// A flag indicating whether the link represents a collection or not. + private void ReadExpandedNestedResourceInfoEnd(bool isCollection) + { + Debug.Assert(this.State == ODataReaderState.NestedResourceInfoStart, + "this.State == ODataReaderState.NestedResourceInfoStart"); + this.CurrentNestedResourceInfo.IsCollection = isCollection; + + // Record that we read the link on the parent resource's scope. + IODataJsonLightReaderResourceState parentResourceState = (IODataJsonLightReaderResourceState)this.ParentScope; + parentResourceState.NavigationPropertiesRead.Add(this.CurrentNestedResourceInfo.Name); + + // replace the 'NestedResourceInfoStart' scope with the 'NestedResourceInfoEnd' scope + this.ReplaceScope(ODataReaderState.NestedResourceInfoEnd); + } + + #endregion NestedResourceInfo + + #region Resource + + /// + /// Reads the start of a (standard, delta, primitive, or null) resource and sets up the reader state correctly + /// + /// The duplicate property names checker to use for the resource; + /// or null if a new one should be created. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// + /// Pre-Condition: JsonNodeType.StartObject If the resource is in a resource set - the start of the resource object + /// JsonNodeType.Property If the resource is a top-level resource and has at least one property + /// JsonNodeType.EndObject If the resource is a top-level resource and has no properties + /// Post-Condition: JsonNodeType.StartObject Start of expanded resource of the nested resource info to read next + /// JsonNodeType.StartArray Start of expanded resource set of the nested resource info to read next + /// JsonNodeType.PrimitiveValue (null) Expanded null resource of the nested resource info to read next + /// JsonNodeType.Property Property after deferred link or expanded entity reference + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// + private void ReadResourceSetItemStart(PropertyAndAnnotationCollector propertyAndAnnotationCollector, + SelectedPropertiesNode selectedProperties) + { + IEdmNavigationSource source = this.CurrentNavigationSource; + IEdmTypeReference resourceTypeReference = this.CurrentResourceTypeReference; + + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.StartObject, JsonNodeType.Property, + JsonNodeType.EndObject, JsonNodeType.PrimitiveValue); + + if (this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue) + { + object primitiveValue = this.jsonLightResourceDeserializer.JsonReader.Value; + if (primitiveValue != null) + { + // primitive value in an untyped collection + if (this.CurrentResourceType.TypeKind == EdmTypeKind.Untyped) + { + this.EnterScope(new JsonLightPrimitiveScope(new ODataPrimitiveValue(primitiveValue), + this.CurrentNavigationSource, this.CurrentResourceTypeReference, this.CurrentScope.ODataUri)); + } + else + { + throw new ODataException(Strings.ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource); + } + } + else + { + // null resource + if (resourceTypeReference.IsComplex() || resourceTypeReference.IsUntyped()) + { + this.jsonLightResourceDeserializer.MessageReaderSettings.Validator.ValidateNullValue(this.CurrentResourceTypeReference, true, "", null); + } + + this.EnterScope(new JsonLightResourceScope(ODataReaderState.ResourceStart, /*resource*/ null, + this.CurrentNavigationSource, this.CurrentResourceTypeReference, /*propertyAndAnnotationCollector*/null, + /*projectedProperties*/null, this.CurrentScope.ODataUri)); + } + + return; + } + + // If the reader is on StartObject then read over it. This happens for entries in resource set. + // For top-level entries the reader will be positioned on the first resource property (after odata.context if it was present). + if (this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartObject) + { + this.jsonLightResourceDeserializer.JsonReader.Read(); + } + + ODataDeltaKind resourceKind = ODataDeltaKind.Resource; + if (this.ReadingResourceSet || this.IsExpandedLinkContent || this.ReadingDelta) + { + string contextUriStr = + this.jsonLightResourceDeserializer.ReadContextUriAnnotation(ODataPayloadKind.Resource, + propertyAndAnnotationCollector, false); + if (contextUriStr != null) + { + contextUriStr = + UriUtils.UriToString(this.jsonLightResourceDeserializer.ProcessUriFromPayload(contextUriStr)); + var parseResult = ODataJsonLightContextUriParser.Parse( + this.jsonLightResourceDeserializer.Model, + contextUriStr, + this.ReadingDelta ? ODataPayloadKind.Delta : ODataPayloadKind.Resource, + this.jsonLightResourceDeserializer.MessageReaderSettings.ClientCustomTypeResolver, + this.jsonLightInputContext.ReadingResponse || this.ReadingDelta); + if (parseResult != null) + { + // a top-level (deleted) resource in a delta response can come from any entity set + resourceKind = parseResult.DeltaKind; + if (this.ReadingDelta && this.IsTopLevel && (resourceKind == ODataDeltaKind.Resource || resourceKind == ODataDeltaKind.DeletedEntry)) + { + IEdmStructuredType parsedType = parseResult.EdmType as IEdmStructuredType; + if (parsedType != null) + { + resourceTypeReference = parsedType.ToTypeReference(true); + source = parseResult.NavigationSource; + } + } + else + { + ReaderValidationUtils.ValidateResourceSetOrResourceContextUri(parseResult, this.CurrentScope, + false); + } + } + } + } + + // If this is a resource in a delta resource set, check to see if it's a 4.01 deleted resource + ODataDeletedResource deletedResource = null; + if (this.ReadingDelta && (resourceKind == ODataDeltaKind.Resource || resourceKind == ODataDeltaKind.DeletedEntry)) + { + deletedResource = this.jsonLightResourceDeserializer.IsDeletedResource(); + if (deletedResource != null) + { + resourceKind = ODataDeltaKind.DeletedEntry; + } + } + + switch (resourceKind) + { + case ODataDeltaKind.None: + case ODataDeltaKind.Resource: + // Setup the new resource state + this.StartResource(source, resourceTypeReference, propertyAndAnnotationCollector, selectedProperties); + + // Start reading the resource up to the first nested resource info + this.StartReadingResource(); + + break; + + case ODataDeltaKind.ResourceSet: + this.ReadAtResourceSetStartImplementation(); + break; + + case ODataDeltaKind.DeletedEntry: + // OData 4.0 deleted entry + if (deletedResource == null) + { + deletedResource = this.jsonLightResourceDeserializer.ReadDeletedEntry(); + this.StartDeletedResource( + deletedResource, + source, + resourceTypeReference, + propertyAndAnnotationCollector, + selectedProperties, + true /*is 4.0 Deleted Resource*/); + } + else // OData 4.01 deleted entry + { + this.StartDeletedResource( + deletedResource, + source, + resourceTypeReference, + propertyAndAnnotationCollector, + selectedProperties); + + // Start reading the resource up to the first nested resource info + this.StartReadingResource(); + } + + break; + + case ODataDeltaKind.DeletedLink: + this.StartDeltaLink(ODataReaderState.DeltaDeletedLink); + break; + + case ODataDeltaKind.Link: + this.StartDeltaLink(ODataReaderState.DeltaLink); + break; + + default: + Debug.Assert(true, "Uknown ODataDeltaKind " + resourceKind.ToString()); + break; + } + } + + #endregion Resource + + #region DeltaResourceSet + /// + /// Reads the start of the JSON array for the content of the delta resource set and sets up the reader state correctly. + /// + /// The delta resource set to read the contents for. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// + /// Pre-Condition: The first node of the resource set property value; this method will throw if the node is not + /// JsonNodeType.StartArray + /// Post-Condition: The reader is positioned on the first item in the resource set, or on the end array of the resource set. + /// + private void ReadDeltaResourceSetStart(ODataDeltaResourceSet deltaResourceSet, SelectedPropertiesNode selectedProperties) + { + Debug.Assert(deltaResourceSet != null, "resourceSet != null"); + + this.jsonLightResourceDeserializer.ReadResourceSetContentStart(); + IJsonReader jsonReader = this.jsonLightResourceDeserializer.JsonReader; + if (jsonReader.NodeType != JsonNodeType.EndArray && jsonReader.NodeType != JsonNodeType.StartObject) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_InvalidNodeTypeForItemsInResourceSet(jsonReader.NodeType)); + } + + Debug.Assert(this.CurrentResourceType is IEdmEntityType, "Delta resource type is not an entity"); + + this.EnterScope(new JsonLightResourceSetScope( + deltaResourceSet, + this.CurrentNavigationSource, + this.CurrentResourceTypeReference as IEdmEntityTypeReference, + selectedProperties, + this.CurrentScope.ODataUri, + /*isDelta*/ true)); + } + + #endregion DeltaResourceSet + + #endregion Read<> methods + + #region private methods + + /// + /// Read the resource up to the first nested resource info. + /// + private void StartReadingResource() + { + ODataResourceBase currentResource = this.Item as ODataResourceBase; + + // Read the odata.type annotation. + this.jsonLightResourceDeserializer.ReadResourceTypeName(this.CurrentResourceState); + + // Resolve the type name + this.ApplyResourceTypeNameFromPayload(currentResource.TypeName); + + // Validate type with derived type validator if available + if (this.CurrentDerivedTypeValidator != null) + { + this.CurrentDerivedTypeValidator.ValidateResourceType(this.CurrentResourceType); + } + + // Validate type with resource set validator if available and not reading top-level delta resource set + if (this.CurrentResourceSetValidator != null && !(this.ReadingDelta && this.CurrentResourceDepth == 0)) + { + this.CurrentResourceSetValidator.ValidateResource(this.CurrentResourceType); + } + + this.CurrentResourceState.FirstNestedInfo = + this.jsonLightResourceDeserializer.ReadResourceContent(this.CurrentResourceState); + + this.jsonLightResourceDeserializer.AssertJsonCondition( + JsonNodeType.Property, + JsonNodeType.StartObject, + JsonNodeType.StartArray, + JsonNodeType.EndObject, + JsonNodeType.PrimitiveValue); + } + + /// + /// Reads the next entity or complex value (or primitive or collection value for an untyped collection) in a resource set. + /// + private void ReadNextResourceSetItem() + { + Debug.Assert(this.State == ODataReaderState.ResourceSetStart || + this.State == ODataReaderState.DeltaResourceSetStart, + "Reading a resource set item while not in a ResourceSetStart or DeltaResourceSetStart state."); + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.EndArray, JsonNodeType.PrimitiveValue, + JsonNodeType.StartObject, JsonNodeType.StartArray); + IEdmType resourceType = this.CurrentScope.ResourceType; + + // End of item in a resource set + switch (this.jsonLightResourceDeserializer.JsonReader.NodeType) + { + case JsonNodeType.StartObject: + // another resource in a resource set + this.ReadResourceSetItemStart( /*propertyAndAnnotationCollector*/ + null, this.CurrentJsonLightResourceSetScope.SelectedProperties); + break; + case JsonNodeType.StartArray: + // we are at the start of a nested resource set + this.ReadResourceSetStart(new ODataResourceSet(), new SelectedPropertiesNode(SelectedPropertiesNode.SelectionType.EntireSubtree)); + break; + case JsonNodeType.EndArray: + // we are at the end of a resource set + this.ReadResourceSetEnd(); + break; + case JsonNodeType.PrimitiveValue: + // Is this a stream, or a binary or string value with a collection that the client wants to read as a stream + if (!TryReadPrimitiveAsStream(resourceType)) + { + // we are at a null value, or a non-null primitive value within an untyped collection + object primitiveValue = this.jsonLightResourceDeserializer.JsonReader.Value; + if (primitiveValue != null) + { + this.EnterScope(new JsonLightPrimitiveScope(new ODataPrimitiveValue(primitiveValue), + this.CurrentNavigationSource, this.CurrentResourceTypeReference, this.CurrentScope.ODataUri)); + } + else + { + if (resourceType.TypeKind == EdmTypeKind.Primitive || resourceType.TypeKind == EdmTypeKind.Enum) + { + // null primitive + this.EnterScope(new JsonLightPrimitiveScope(new ODataNullValue(), + this.CurrentNavigationSource, this.CurrentResourceTypeReference, this.CurrentScope.ODataUri)); + } + else + { + // null resource (ReadResourceStart will raise the appropriate error for a non-null primitive value) + this.ReadResourceSetItemStart( /*propertyAndAnnotationCollector*/ + null, this.CurrentJsonLightResourceSetScope.SelectedProperties); + } + } + } + + break; + default: + throw new ODataException( + ODataErrorStrings.ODataJsonReader_CannotReadResourcesOfResourceSet( + this.jsonLightResourceDeserializer.JsonReader.NodeType)); + } + } + + private bool TryReadPrimitiveAsStream(IEdmType resourceType) + { + Func readAsStream = this.jsonLightInputContext.MessageReaderSettings.ReadAsStreamFunc; + + // Should stream primitive if + // 1. Primitive is a stream value + // 2. Primitive is a string or binary value (within an untyped or streamed collection) that the reader wants to read as a stream + if ( + (resourceType != null && resourceType.IsStream()) || + (resourceType != null + && readAsStream != null + && (resourceType.IsBinary() || resourceType.IsString()) + && readAsStream(resourceType as IEdmPrimitiveType, false, null, null))) + { + if (resourceType == null || resourceType.IsUntyped()) + { + this.StartNestedStreamInfo(new ODataJsonLightReaderStreamInfo( + EdmPrimitiveTypeKind.None)); + } + else if (resourceType.IsString()) + { + this.StartNestedStreamInfo(new ODataJsonLightReaderStreamInfo( + EdmPrimitiveTypeKind.String)); + } + else if (resourceType.IsStream() || resourceType.IsBinary()) + { + this.StartNestedStreamInfo(new ODataJsonLightReaderStreamInfo(EdmPrimitiveTypeKind.Binary)); + } + else + { + Debug.Assert(false, "We thought we could read as stream, but ran out of options"); + return false; + } + + return true; + } + + return false; + } + + /// + /// Reads the next item in a nested resource info content in a request payload. + /// + private void ReadNextNestedResourceInfoContentItemInRequest() + { + Debug.Assert(this.CurrentScope.State == ODataReaderState.NestedResourceInfoStart, + "Must be on 'NestedResourceInfoStart' scope."); + + ODataJsonLightReaderNestedResourceInfo nestedResourceInfo = + this.CurrentJsonLightNestedResourceInfoScope.ReaderNestedResourceInfo; + if (nestedResourceInfo.HasEntityReferenceLink) + { + this.EnterScope(new Scope(ODataReaderState.EntityReferenceLink, nestedResourceInfo.ReportEntityReferenceLink(), this.CurrentScope.ODataUri)); + } + else if (nestedResourceInfo.HasValue) + { + if (nestedResourceInfo.NestedResourceInfo.IsCollection == true) + { + // because this is a request, there is no $select query option. + SelectedPropertiesNode selectedProperties = new SelectedPropertiesNode(SelectedPropertiesNode.SelectionType.EntireSubtree); + ODataDeltaResourceSet deltaResourceSet = nestedResourceInfo.NestedResourceSet as ODataDeltaResourceSet; + if (deltaResourceSet != null) + { + this.ReadDeltaResourceSetStart(deltaResourceSet, selectedProperties); + } + else + { + ODataResourceSet resourceSet = nestedResourceInfo.NestedResourceSet as ODataResourceSet; + this.ReadResourceSetStart(resourceSet ?? new ODataResourceSet(), selectedProperties); + } + } + else + { + this.ReadExpandedNestedResourceInfoStart(nestedResourceInfo.NestedResourceInfo); + } + } + else + { + // replace the 'NestedResourceInfoStart' scope with the 'NestedResourceInfoEnd' scope + this.ReplaceScope(ODataReaderState.NestedResourceInfoEnd); + } + } + + /// + /// Starts the resource, initializing the scopes and such. This method starts a non-null resource only. + /// + /// The source for the resource + /// The entity type of the resource + /// The duplicate property names checker to use for the resource; + /// or null if a new one should be created. + /// The selected properties node capturing what properties should be expanded during template evaluation. + private void StartResource(IEdmNavigationSource source, IEdmTypeReference resourceType, PropertyAndAnnotationCollector propertyAndAnnotationCollector, + SelectedPropertiesNode selectedProperties) + { + this.EnterScope(new JsonLightResourceScope( + ODataReaderState.ResourceStart, + ReaderUtils.CreateNewResource(), + source, + resourceType, + propertyAndAnnotationCollector ?? this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(), + selectedProperties, + this.CurrentScope.ODataUri)); + } + + /// + /// Starts the deleted resource, initializing the scopes and such. This method starts a non-null resource only. + /// + /// The deletedResource to be created. + /// The navigation source of the deleted resource. + /// The entity type of the deleted resource. + /// The duplicate property names checker to use for the resource; + /// or null if a new one should be created. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// Whether the current resource being read is a 4.0-style deleted resource. + private void StartDeletedResource(ODataDeletedResource deletedResource, IEdmNavigationSource source, IEdmTypeReference resourceType, PropertyAndAnnotationCollector propertyAndAnnotationCollector, + SelectedPropertiesNode selectedProperties, bool is40DeletedResource = false) + { + this.EnterScope(new JsonLightDeletedResourceScope( + ODataReaderState.DeletedResourceStart, + deletedResource, + source, + resourceType, + propertyAndAnnotationCollector ?? this.jsonLightInputContext.CreatePropertyAndAnnotationCollector(), + selectedProperties, + this.CurrentScope.ODataUri, + is40DeletedResource)); + } + + /// + /// Starts the (deleted) link, initializing the scopes and such. This method starts a non-null resource only. + /// + /// The reader state to switch to. + private void StartDeltaLink(ODataReaderState state) + { + Debug.Assert( + state == ODataReaderState.DeltaLink || state == ODataReaderState.DeltaDeletedLink, + "state must be either DeltaResource or DeltaDeletedEntry or DeltaLink or DeltaDeletedLink."); + Debug.Assert(this.CurrentResourceType is IEdmEntityType, "DeltaLink is not from an entity type"); + + ODataDeltaLinkBase link; + if (state == ODataReaderState.DeltaLink) + { + link = new ODataDeltaLink(null, null, null); + } + else + { + link = new ODataDeltaDeletedLink(null, null, null); + } + + this.EnterScope(new JsonLightDeltaLinkScope( + state, + link, + this.CurrentNavigationSource, + this.CurrentResourceType as IEdmEntityType, + this.CurrentScope.ODataUri)); + + this.jsonLightResourceDeserializer.AssertJsonCondition(JsonNodeType.EndObject, JsonNodeType.Property); + + // Read source property. + this.jsonLightResourceDeserializer.ReadDeltaLinkSource(link); + + // Read relationship property. + this.jsonLightResourceDeserializer.ReadDeltaLinkRelationship(link); + + // Read target property. + this.jsonLightResourceDeserializer.ReadDeltaLinkTarget(link); + + Debug.Assert(this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject, "Unexpected content in a delta (deleted) link"); + } + + /// + /// Starts the nested resource info. + /// Does metadata validation of the nested resource info and sets up the reader to report it. + /// + /// The nested resource info for the nested resource info to start. + private void StartNestedResourceInfo(ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo) + { + Debug.Assert(readerNestedResourceInfo != null, "readerNestedResourceInfo != null"); + ODataNestedResourceInfo nestedResourceInfo = readerNestedResourceInfo.NestedResourceInfo; + IEdmProperty nestedProperty = readerNestedResourceInfo.NestedProperty; + IEdmTypeReference targetResourceTypeReference = readerNestedResourceInfo.NestedResourceTypeReference; + + Debug.Assert( + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.Property || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.EndObject || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartObject || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.StartArray || + this.jsonLightResourceDeserializer.JsonReader.NodeType == JsonNodeType.PrimitiveValue && + this.jsonLightResourceDeserializer.JsonReader.Value == null, + "Post-Condition: expected JsonNodeType.StartObject or JsonNodeType.StartArray or JsonNodeType.Primitive (null), or JsonNodeType.Property, JsonNodeType.EndObject"); + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(!string.IsNullOrEmpty(nestedResourceInfo.Name), "Navigation links must have a name."); + Debug.Assert(nestedProperty == null || nestedResourceInfo.Name == nestedProperty.Name, + "The navigation property must match the nested resource info."); + + // we are at the beginning of a link + if (targetResourceTypeReference == null && nestedProperty != null) + { + IEdmTypeReference nestedPropertyType = nestedProperty.Type; + targetResourceTypeReference = nestedPropertyType.IsCollection() + ? nestedPropertyType.AsCollection().ElementType().AsStructured() + : nestedPropertyType.AsStructured(); + } + + // Since we don't have the entity metadata builder for the resource read out from a nested payload + // as stated in ReadAtResourceSetEndImplementationSynchronously(), we cannot access it here which otherwise + // would lead to an exception. + if (this.jsonLightInputContext.ReadingResponse && !this.IsReadingNestedPayload + && (targetResourceTypeReference == null || targetResourceTypeReference.Definition.IsStructuredOrStructuredCollectionType())) + { + // Hookup the metadata builder to the nested resource info. + // Note that we set the metadata builder even when navigationProperty is null, which is the case when the link is undeclared. + // For undeclared links, we will apply conventional metadata evaluation just as declared links. + this.CurrentResourceState.ResourceTypeFromMetadata = this.ParentScope.ResourceType as IEdmStructuredType; + ODataResourceMetadataBuilder resourceMetadataBuilder = + this.jsonLightResourceDeserializer.MetadataContext.GetResourceMetadataBuilderForReader( + this.CurrentResourceState, + this.jsonLightInputContext.ODataSimplifiedOptions.EnableReadingKeyAsSegment, + this.ReadingDelta); + nestedResourceInfo.MetadataBuilder = resourceMetadataBuilder; + } + + Debug.Assert( + this.CurrentNavigationSource != null || this.readingParameter || + this.CurrentNavigationSource == null && this.CurrentScope.ResourceType.IsODataComplexTypeKind(), + "Json requires an navigation source when not reading parameter."); + + IEdmNavigationProperty navigationProperty = readerNestedResourceInfo.NavigationProperty; + + IEdmNavigationSource navigationSource; + + // Since we are entering a nested info scope, check whether the current resource is derived type in order to correctly further property or navigation property. + JsonLightResourceBaseScope currentScope = this.CurrentScope as JsonLightResourceBaseScope; + ODataUri odataUri = this.CurrentScope.ODataUri.Clone(); + ODataPath odataPath = odataUri.Path ?? new ODataPath(); + + if (currentScope != null && currentScope.ResourceTypeFromMetadata != currentScope.ResourceType) + { + odataPath.Add(new TypeSegment(currentScope.ResourceType, null)); + } + + if (navigationProperty == null) + { + navigationSource = this.CurrentNavigationSource; + } + else + { + IEdmPathExpression bindingPath; + navigationSource = this.CurrentNavigationSource == null + ? null + : this.CurrentNavigationSource.FindNavigationTarget(navigationProperty, + BindingPathHelper.MatchBindingPath, odataPath.ToList(), out bindingPath); + } + + if (navigationProperty != null) + { + if (navigationSource is IEdmContainedEntitySet) + { + if (TryAppendEntitySetKeySegment(ref odataPath)) + { + odataPath = odataPath.AppendNavigationPropertySegment(navigationProperty, navigationSource); + } + } + else if (navigationSource != null && !(navigationSource is IEdmUnknownEntitySet)) + { + var entitySet = navigationSource as IEdmEntitySet; + odataPath = entitySet != null + ? new ODataPath(new EntitySetSegment(entitySet)) + : new ODataPath(new SingletonSegment(navigationSource as IEdmSingleton)); + } + else + { + odataPath = new ODataPath(); + } + } + else if (nestedProperty != null) + { + odataPath = odataPath.AppendPropertySegment(nestedProperty as IEdmStructuralProperty); + } + + odataUri.Path = odataPath; + + JsonLightNestedResourceInfoScope newScope = new JsonLightNestedResourceInfoScope(readerNestedResourceInfo, navigationSource, + targetResourceTypeReference, odataUri); + + var derivedTypeConstraints = this.jsonLightInputContext.Model.GetDerivedTypeConstraints(nestedProperty); + if (derivedTypeConstraints != null) + { + newScope.DerivedTypeValidator = new DerivedTypeValidator(nestedProperty.Type.ToStructuredType(), derivedTypeConstraints, "nested resource", nestedProperty.Name); + } + + this.EnterScope(newScope); + } + + /// + /// Starts the nested property info. + /// + /// The nested resource info for the nested resource info to start. + private void StartNestedPropertyInfo(ODataJsonLightReaderNestedPropertyInfo readerNestedPropertyInfo) + { + Debug.Assert(readerNestedPropertyInfo != null, "readerNestedResourceInfo != null"); + Debug.Assert(this.jsonLightResourceDeserializer.JsonReader.CanStream() || this.CurrentScope is JsonLightResourceSetScope, + "Starting stream while not positioned on a primitive value or within an array"); + + this.EnterScope(new JsonLightNestedPropertyInfoScope(readerNestedPropertyInfo, this.CurrentNavigationSource, this.CurrentScope.ODataUri)); + } + + /// + /// Starts the nested stream info. + /// + /// The nested resource info for the nested resource info to start. + private void StartNestedStreamInfo(ODataJsonLightReaderStreamInfo readerStreamInfo) + { + Debug.Assert(readerStreamInfo != null, "readerNestedResourceInfo != null"); + Debug.Assert(this.jsonLightResourceDeserializer.JsonReader.CanStream() || this.CurrentScope is JsonLightResourceSetScope, + "Starting stream while not positioned on a primitive value or within an array"); + + this.EnterScope(new JsonLightStreamScope(readerStreamInfo, this.CurrentNavigationSource, this.CurrentScope.ODataUri)); + } + + /// + /// Try to append key segment. + /// + /// The ODataPath to be evaluated. + /// True if successfully append key segment. + private bool TryAppendEntitySetKeySegment(ref ODataPath odataPath) + { + try + { + if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType as IEdmStructuredType)) + { + IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType; + ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase; + KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, null, currentEntityType); + odataPath = odataPath.AppendKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource); + } + } + catch (ODataException) + { + odataPath = null; + return false; + } + + return true; + } + + /// + /// Replaces the current scope with a new scope with the specified and + /// the item of the current scope. + /// + /// The to use for the new scope. + private void ReplaceScope(ODataReaderState state) + { + this.ReplaceScope(new Scope(state, this.Item, this.CurrentNavigationSource, this.CurrentResourceTypeReference, + this.CurrentScope.ODataUri)); + } + + /// + /// Called to transition into the EntryEnd state. + /// + private void EndEntry() + { + IODataJsonLightReaderResourceState resourceState = this.CurrentResourceState; + ODataResourceBase currentResource = this.Item as ODataResourceBase; + + if (currentResource != null && !this.IsReadingNestedPayload) + { + // Builder should not be used outside the odataresource, lazy builder logic does not work here + // We should refactor this + foreach (string navigationPropertyName in this.CurrentResourceState.NavigationPropertiesRead) + { + currentResource.MetadataBuilder.MarkNestedResourceInfoProcessed(navigationPropertyName); + } + + ODataConventionalEntityMetadataBuilder builder = + currentResource.MetadataBuilder as ODataConventionalEntityMetadataBuilder; + if (builder != null) + { + builder.EndResource(); + } + } + + this.jsonLightResourceDeserializer.ValidateMediaEntity(resourceState); + + // In non-delta responses, ensure that all projected properties get created. + // Also ignore cases where the resource is 'null' which happens for expanded null entries. + if (this.jsonLightInputContext.ReadingResponse && !this.ReadingDelta && currentResource != null) + { + // If we have a projected nested resource info that was missing from the payload, report it now. + ODataJsonLightReaderNestedResourceInfo unprocessedNestedResourceInfo = + currentResource.MetadataBuilder.GetNextUnprocessedNavigationLink(); + if (unprocessedNestedResourceInfo != null) + { + this.CurrentResourceState.ProcessingMissingProjectedNestedResourceInfos = true; + this.StartNestedResourceInfo(unprocessedNestedResourceInfo); + return; + } + } + + if (this.State == ODataReaderState.ResourceStart) + { + this.EndEntry( + new JsonLightResourceScope( + ODataReaderState.ResourceEnd, + (ODataResource)this.Item, + this.CurrentNavigationSource, + this.CurrentResourceTypeReference, + this.CurrentResourceState.PropertyAndAnnotationCollector, + this.CurrentResourceState.SelectedProperties, + this.CurrentScope.ODataUri)); + } + else + { + this.EndEntry( + new JsonLightDeletedResourceScope( + ODataReaderState.DeletedResourceEnd, + (ODataDeletedResource)this.Item, + this.CurrentNavigationSource, + this.CurrentResourceTypeReference, + this.CurrentResourceState.PropertyAndAnnotationCollector, + this.CurrentResourceState.SelectedProperties, + this.CurrentScope.ODataUri)); + } + } + + /// + /// Add info resolved from context url to current scope. + /// + private void ResolveScopeInfoFromContextUrl() + { + if (this.jsonLightResourceDeserializer.ContextUriParseResult != null) + { + this.CurrentScope.ODataUri.Path = this.jsonLightResourceDeserializer.ContextUriParseResult.Path; + + if (this.CurrentScope.NavigationSource == null) + { + this.CurrentScope.NavigationSource = + this.jsonLightResourceDeserializer.ContextUriParseResult.NavigationSource; + } + + if (this.CurrentScope.ResourceType == null) + { + IEdmType typeFromContext = this.jsonLightResourceDeserializer.ContextUriParseResult.EdmType; + if (typeFromContext != null) + { + if (typeFromContext.TypeKind == EdmTypeKind.Collection) + { + typeFromContext = ((IEdmCollectionType)typeFromContext).ElementType.Definition; + if (!(typeFromContext is IEdmStructuredType)) + { + typeFromContext = new EdmUntypedStructuredType(); + this.jsonLightResourceDeserializer.ContextUriParseResult.EdmType = new EdmCollectionType(typeFromContext.ToTypeReference()); + } + } + + IEdmStructuredType resourceType = typeFromContext as IEdmStructuredType; + if (resourceType == null) + { + resourceType = new EdmUntypedStructuredType(); + this.jsonLightResourceDeserializer.ContextUriParseResult.EdmType = resourceType; + } + + this.CurrentScope.ResourceTypeReference = resourceType.ToTypeReference(true).AsStructured(); + } + } + } + } + +#endregion private methods + + #region scopes + + /// + /// A reader top-level scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightTopLevelScope : Scope + { + /// + /// Constructor creating a new reader scope. + /// + /// The navigation source we are going to read resources for. + /// The expected type for the scope. + /// The odataUri parsed based on the context uri attached to this scope. + /// The has the following meaning + /// it's the expected base type of the top-level resource or resource set in the top-level resource set. + /// In all cases the specified type must be a structured type. + internal JsonLightTopLevelScope(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType, ODataUri odataUri) + : base(ODataReaderState.Start, /*item*/ null, navigationSource, expectedResourceType.ToTypeReference(true), odataUri) + { + } + + /// + /// The duplicate property names checker for the top level scope represented by the current state. + /// + public PropertyAndAnnotationCollector PropertyAndAnnotationCollector { get; set; } + } + + /// + /// A reader primitive scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightPrimitiveScope : Scope + { + /// + /// Constructor creating a new reader scope. + /// + /// The item attached to this scope. + /// The navigation source we are going to read resources for. + /// The expected type reference for the scope. + /// The odataUri parsed based on the context uri for current scope + internal JsonLightPrimitiveScope( + ODataValue primitiveValue, + IEdmNavigationSource navigationSource, + IEdmTypeReference expectedTypeReference, + ODataUri odataUri) + : base(ODataReaderState.Primitive, primitiveValue, navigationSource, expectedTypeReference, odataUri) + { + Debug.Assert(primitiveValue is ODataPrimitiveValue || primitiveValue is ODataNullValue, "Primitive value scope created with non-primitive value"); + } + } + + /// + /// A reader resource scope; keeping track of the current reader state and an item associated with this state. + /// + private abstract class JsonLightResourceBaseScope : Scope, IODataJsonLightReaderResourceState + { + /// The set of names of the navigation properties we have read so far while reading the resource. + private List navigationPropertiesRead; + + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of the new scope that is being created. + /// The item attached to this scope. + /// The navigation source we are going to read resources for. + /// The expected type reference for the scope. + /// The duplicate property names checker for this resource scope. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// The odataUri parsed based on the context uri for current scope + /// The has the following meaning + /// it's the expected base type of the resource. If the resource has no type name specified + /// this type will be assumed. Otherwise the specified type name must be + /// the expected type or a more derived type. + /// In all cases the specified type must be an entity type. + protected JsonLightResourceBaseScope( + ODataReaderState readerState, + ODataResourceBase resource, + IEdmNavigationSource navigationSource, + IEdmTypeReference expectedResourceTypeReference, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri) + : base(readerState, resource, navigationSource, expectedResourceTypeReference, odataUri) + { + Debug.Assert( + readerState == ODataReaderState.ResourceStart || readerState == ODataReaderState.ResourceEnd || + readerState == ODataReaderState.DeletedResourceStart || readerState == ODataReaderState.DeletedResourceEnd, + "Resource scope created for invalid reader state: " + readerState); + + this.PropertyAndAnnotationCollector = propertyAndAnnotationCollector; + this.SelectedProperties = selectedProperties; + } + + /// + /// The metadata builder instance for the resource. + /// + public ODataResourceMetadataBuilder MetadataBuilder { get; set; } + + /// + /// Flag which indicates that during parsing of the resource represented by this state, + /// any property which is not an instance annotation was found. This includes property annotations + /// for property which is not present in the payload. + /// + /// + /// This is used to detect incorrect ordering of the payload (for example odata.id must not come after the first property). + /// + public bool AnyPropertyFound { get; set; } + + /// + /// If the reader finds a nested resource info to report, but it must first report the parent resource + /// it will store the nested resource info in this property. So this will only ever store the first nested resource info of a resource. + /// + public ODataJsonLightReaderNestedInfo FirstNestedInfo { get; set; } + + /// + /// The duplicate property names checker for the resource represented by the current state. + /// + public PropertyAndAnnotationCollector PropertyAndAnnotationCollector { get; private set; } + + /// + /// The selected properties that should be expanded during template evaluation. + /// + public SelectedPropertiesNode SelectedProperties { get; private set; } + + /// + /// The set of names of the navigation properties we have read so far while reading the resource. + /// true if we have started processing missing projected navigation links, false otherwise. + /// + public List NavigationPropertiesRead + { + get { return this.navigationPropertiesRead ?? (this.navigationPropertiesRead = new List()); } + } + + /// + /// true if we have started processing missing projected navigation links, false otherwise. + /// + public bool ProcessingMissingProjectedNestedResourceInfos { get; set; } + + /// + /// The expected type defined in the model for the resource. + /// + public IEdmStructuredType ResourceTypeFromMetadata { get; set; } + + /// + /// The resource type for this resource. + /// + public new IEdmStructuredType ResourceType + { + get + { + return base.ResourceType as IEdmStructuredType; + } + } + + /// + /// The resource being read. + /// + ODataResourceBase IODataJsonLightReaderResourceState.Resource + { + get + { + Debug.Assert( + this.State == ODataReaderState.ResourceStart || this.State == ODataReaderState.ResourceEnd || + this.State == ODataReaderState.DeletedResourceStart || this.State == ODataReaderState.DeletedResourceEnd, + "The IODataJsonLightReaderResourceState is only supported on ResourceStart or ResourceEnd scope."); + return (ODataResourceBase)this.Item; + } + } + + /// + /// The structured type for the resource (if available). + /// + IEdmStructuredType IODataJsonLightReaderResourceState.ResourceType + { + get + { + Debug.Assert( + this.State == ODataReaderState.ResourceStart || this.State == ODataReaderState.ResourceEnd | this.State == ODataReaderState.DeletedResourceStart || this.State == ODataReaderState.DeletedResourceEnd, + "The IODataJsonLightReaderResourceState is only supported on (Deleted)ResourceStart or (Deleted)ResourceEnd scope."); + return this.ResourceType; + } + } + + /// + /// The navigation source for the resource (if available) + /// + IEdmNavigationSource IODataJsonLightReaderResourceState.NavigationSource + { + get { return this.NavigationSource; } + } + } + + /// + /// Base class for a reader resource scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightResourceScope : JsonLightResourceBaseScope + { + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of the new scope that is being created. + /// The item attached to this scope. + /// The navigation source we are going to read resources for. + /// The expected type for the scope. + /// The duplicate property names checker for this resource scope. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// The odataUri parsed based on the context uri for current scope + /// The has the following meaning + /// it's the expected base type of the resource. If the resource has no type name specified + /// this type will be assumed. Otherwise the specified type name must be + /// the expected type or a more derived type. + /// In all cases the specified type must be an entity type. + internal JsonLightResourceScope( + ODataReaderState readerState, + ODataResourceBase resource, + IEdmNavigationSource navigationSource, + IEdmTypeReference expectedResourceTypeReference, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri) + : base(readerState, resource, navigationSource, expectedResourceTypeReference, propertyAndAnnotationCollector, selectedProperties, odataUri) + { + } + } + + /// + /// A reader deleted resource scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightDeletedResourceScope : JsonLightResourceBaseScope + { + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of the new scope that is being created. + /// The item attached to this scope. + /// The navigation source we are going to read resources for. + /// The expected type for the scope. + /// The duplicate property names checker for this resource scope. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// The odataUri parsed based on the context uri for current scope + /// Whether the deleted resource being read is an OData 4.0 Deleted Resource + /// The has the following meaning + /// it's the expected base type of the resource. If the resource has no type name specified + /// this type will be assumed. Otherwise the specified type name must be + /// the expected type or a more derived type. + /// In all cases the specified type must be an entity type. + internal JsonLightDeletedResourceScope( + ODataReaderState readerState, + ODataDeletedResource resource, + IEdmNavigationSource navigationSource, + IEdmTypeReference expectedResourceType, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri, + bool is40DeletedResource = false) + : base(readerState, resource, navigationSource, expectedResourceType, propertyAndAnnotationCollector, selectedProperties, odataUri) + { + this.Is40DeletedResource = is40DeletedResource; + } + + /// Whether the payload is an OData 4.0 deleted resource. + internal bool Is40DeletedResource { get; } + } + + /// + /// A reader resource set scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightResourceSetScope : Scope + { + /// + /// Constructor creating a new reader scope. + /// + /// The item attached to this scope. + /// The navigation source we are going to read entities for. + /// The expected type reference for the scope. + /// The selected properties node capturing what properties should be expanded during template evaluation. + /// The odataUri parsed based on the context uri for current scope + /// True of the ResourceSetScope is for a delta resource set + /// The has the following meaning + /// it's the expected base type of the entries in the resource set. + /// note that it might be a more derived type than the base type of the entity set for the resource set. + /// In all cases the specified type must be an entity type. + internal JsonLightResourceSetScope(ODataResourceSetBase resourceSet, IEdmNavigationSource navigationSource, IEdmTypeReference expectedResourceTypeReference, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isDelta) + : base(isDelta ? ODataReaderState.DeltaResourceSetStart : ODataReaderState.ResourceSetStart, resourceSet, navigationSource, expectedResourceTypeReference, odataUri) + { + this.SelectedProperties = selectedProperties; + } + + /// + /// The selected properties that should be expanded during template evaluation. + /// + public SelectedPropertiesNode SelectedProperties { get; private set; } + } + + /// + /// A reader scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightNestedResourceInfoScope : Scope + { + /// + /// Constructor creating a new reader scope. + /// + /// The nested resource info attached to this scope. + /// The navigation source we are going to read entities for. + /// The expected type reference for the scope. + /// The odataUri parsed based on the context uri for current scope + /// The is the expected base type reference the items in the nested resource info. + internal JsonLightNestedResourceInfoScope(ODataJsonLightReaderNestedResourceInfo nestedResourceInfo, IEdmNavigationSource navigationSource, IEdmTypeReference expectedTypeReference, ODataUri odataUri) + : base(ODataReaderState.NestedResourceInfoStart, nestedResourceInfo.NestedResourceInfo, navigationSource, expectedTypeReference, odataUri) + { + this.ReaderNestedResourceInfo = nestedResourceInfo; + } + + /// + /// The nested resource info for the nested resource info to report. + /// This is only used on a StartNestedResourceInfo scope in responses. + /// + public ODataJsonLightReaderNestedResourceInfo ReaderNestedResourceInfo { get; private set; } + } + + /// + /// A reader scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightNestedPropertyInfoScope : Scope + { + /// + /// Constructor creating a new nested property info scope. + /// + /// The nested property info attached to this scope. + /// The navigation source we are going to read entities for. + /// The odataUri parsed based on the context uri for current scope + internal JsonLightNestedPropertyInfoScope(ODataJsonLightReaderNestedPropertyInfo nestedPropertyInfo, IEdmNavigationSource navigationSource, ODataUri odataUri) + : base(ODataReaderState.NestedProperty, nestedPropertyInfo.NestedPropertyInfo, + navigationSource, EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Stream, true), odataUri) + { + Debug.Assert(nestedPropertyInfo != null, "JsonLightNestedInfoScope created with a null nestedPropertyInfo"); + } + } + + /// + /// A reader scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightStreamScope : StreamScope + { + /// + /// Constructor creating a new nested property info scope. + /// + /// The stream info attached to this scope. + /// The navigation source we are going to read entities for. + /// The odataUri parsed based on the context uri for current scope + internal JsonLightStreamScope(ODataJsonLightReaderStreamInfo streamInfo, IEdmNavigationSource navigationSource, ODataUri odataUri) + : base(ODataReaderState.Stream, new ODataStreamItem(streamInfo.PrimitiveTypeKind, streamInfo.ContentType), + navigationSource, EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Stream, true), odataUri) + { + Debug.Assert(streamInfo != null, "JsonLightNestedStreamScope created with a null streamInfo"); + } + } + + /// + /// A reader scope; keeping track of the current reader state and an item associated with this state. + /// + private sealed class JsonLightDeltaLinkScope : Scope + { + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of the new scope that is being created. + /// The link info attached to this scope. + /// The navigation source we are going to read entities for. + /// The expected type for the scope. + /// The odataUri parsed based on the context uri for current scope + /// The has the following meaning + /// it's the expected base type the entries in the expanded link (either the single resource + /// or entries in the expanded resource set). + /// In all cases the specified type must be an entity type. + public JsonLightDeltaLinkScope(ODataReaderState state, ODataDeltaLinkBase link, IEdmNavigationSource navigationSource, IEdmEntityType expectedEntityType, ODataUri odataUri) + : base(state, link, navigationSource, expectedEntityType.ToTypeReference(true), odataUri) + { + Debug.Assert( + state == ODataReaderState.DeltaLink && link is ODataDeltaLink || + state == ODataReaderState.DeltaDeletedLink && link is ODataDeltaDeletedLink, + "link must be either DeltaLink or DeltaDeletedLink."); + } + } + + #endregion Scopes + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedInfo.cs new file mode 100644 index 0000000..022f48e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedInfo.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + internal abstract class ODataJsonLightReaderNestedInfo + { + /// + /// Constructor. + /// + /// The nested property for which the nested resource info will be reported. + internal ODataJsonLightReaderNestedInfo(IEdmProperty nestedProperty) + { + this.NestedProperty = nestedProperty; + } + + /// + /// The Edm property for which the nested resource info will be reported. + /// + internal IEdmProperty NestedProperty { get; private set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedPropertyInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedPropertyInfo.cs new file mode 100644 index 0000000..d8b7441 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedPropertyInfo.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + internal class ODataJsonLightReaderNestedPropertyInfo : ODataJsonLightReaderNestedInfo + { + internal ODataJsonLightReaderNestedPropertyInfo(ODataPropertyInfo nestedPropertyInfo, IEdmProperty nestedProperty) : base(nestedProperty) + { + this.NestedPropertyInfo = nestedPropertyInfo; + } + + internal ODataPropertyInfo NestedPropertyInfo { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedResourceInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedResourceInfo.cs new file mode 100644 index 0000000..40044c7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedResourceInfo.cs @@ -0,0 +1,307 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Class which holds information about nested resource info to be reported by the reader. + /// + internal sealed class ODataJsonLightReaderNestedResourceInfo : ODataJsonLightReaderNestedInfo + { + /// + /// The nested resource info to report. + /// + private readonly ODataNestedResourceInfo nestedResourceInfo; + + /// + /// true if the nested resource info has a value. + /// + private readonly bool hasValue; + + /// + /// The nested resource set for nested resource info to be reported. + /// + private ODataResourceSetBase resourceSet; + + /// + /// List of entity reference links to be reported to the navigation link. + /// + /// + /// If the navigation link is a singleton this will hold up to 1 item. + /// If the navigation link is a collection this will hold any number of items. + /// When the entity reference link is reported it is removed from this list. + /// + private LinkedList entityReferenceLinks; + + /// + /// Constructor. + /// + /// The nested resource info to report. + /// The nested property for which the nested resource info will be reported. + /// true if the nested resource info is expanded. + private ODataJsonLightReaderNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo, IEdmProperty nestedProperty, bool isExpanded) + : base(nestedProperty) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(nestedProperty == null || nestedProperty.Name == nestedResourceInfo.Name, "The name of the nested resource info doesn't match the name of the property."); + + this.nestedResourceInfo = nestedResourceInfo; + + // ToStructuredType method needs to be called to resolve element type from the collection which AsStructured does not do. + NestedResourceTypeReference = (nestedProperty != null) ? nestedProperty.Type.Definition.AsElementType().ToTypeReference(nestedProperty.Type.IsNullable) : null; + this.hasValue = isExpanded; + } + + /// + /// Constructor which used when the nested resource info is a undeclared property in model. + /// + /// The nested resource info to report. + /// The nested property for which the nested resource info will be reported. + /// The resource type of the nested resource info. + /// true if the nested resource info is expanded. + private ODataJsonLightReaderNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo, IEdmProperty nestedProperty, IEdmType nestedResourceType, bool isExpanded) + : base(nestedProperty) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(nestedResourceType != null || nestedProperty != null, "nestedResourceType and nestedProperty are both null"); + + this.nestedResourceInfo = nestedResourceInfo; + + // We get the nullablity from the nestedProperty if it exists otherwise we consider the resource to be nullable if only the nestedResourceType is specified. + bool resourceTypeNullable = true; + if (nestedProperty != null && nestedProperty.Type != null) + { + resourceTypeNullable = nestedProperty.Type.IsNullable; + } + + IEdmType nestedType = nestedResourceType; + + // We get the resource type from the nestedProperty if not specified. + if (nestedProperty != null && nestedType == null) + { + nestedType = nestedProperty.Type.Definition; + } + + NestedResourceTypeReference = nestedType.ToTypeReference(resourceTypeNullable); + this.hasValue = isExpanded; + } + + /// + /// The nested resource info to report. + /// + internal ODataNestedResourceInfo NestedResourceInfo + { + get + { + return this.nestedResourceInfo; + } + } + + /// + /// The navigation property for which the navigation link will be reported. + /// + internal IEdmNavigationProperty NavigationProperty + { + get + { + return this.NestedProperty as IEdmNavigationProperty; + } + } + + /// + /// The structural property for which the nested resource info will be reported. + /// + internal IEdmStructuralProperty StructuralProperty + { + get + { + return this.NestedProperty as IEdmStructuralProperty; + } + } + + /// + /// true if the nested resource info has a value. + /// + internal bool HasValue + { + get + { + return this.hasValue; + } + } + + /// + /// The nested resource set for nested resource info to be reported. + /// + internal ODataResourceSetBase NestedResourceSet + { + get + { + return this.resourceSet; + } + } + + /// + /// The resource type reference of the nested resource info which will be reported. + /// + internal IEdmTypeReference NestedResourceTypeReference { get; private set; } + + /// + /// true if the link info has entity reference link which was not yet reported, false otherwise. + /// + internal bool HasEntityReferenceLink + { + get + { + return this.entityReferenceLinks != null && this.entityReferenceLinks.First != null; + } + } + + /// + /// Creates a nested resource info for a deferred link. + /// + /// The nested resource info to report. + /// The navigation property for which the link will be reported. + /// The nested resource info created. + internal static ODataJsonLightReaderNestedResourceInfo CreateDeferredLinkInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmNavigationProperty navigationProperty) + { + return new ODataJsonLightReaderNestedResourceInfo(nestedResourceInfo, navigationProperty, /*isExpanded*/ false); + } + + /// + /// Creates a nested resource info for an expanded resource link. + /// + /// The nested resource info to report. + /// The navigation property for which the link will be reported. + /// The type of the nested resource. + /// The nested resource info created. + internal static ODataJsonLightReaderNestedResourceInfo CreateResourceReaderNestedResourceInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmProperty nestedProperty, + IEdmStructuredType nestedResourceType) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(nestedResourceInfo.IsCollection == false, "Resource can only be reported for a singleton nested resource info."); + + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = new ODataJsonLightReaderNestedResourceInfo(nestedResourceInfo, nestedProperty, nestedResourceType, /*isExpanded*/ true); + return readerNestedResourceInfo; + } + + /// + /// Creates a nested resource info for nested resources. + /// + /// The nested resource info to report. + /// The nested property for which the nested resource info will be reported. + /// The type of the nested resource. + /// The nested resource set for the nested resource info to report. + /// The nested resource info created. + internal static ODataJsonLightReaderNestedResourceInfo CreateResourceSetReaderNestedResourceInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmProperty nestedProperty, + IEdmType nestedResourceType, + ODataResourceSetBase resourceSet) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(nestedResourceInfo.IsCollection == true, "Resource sets can only be reported for collection nested resource info."); + Debug.Assert(resourceSet != null, "resourceSet != null"); + + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = new ODataJsonLightReaderNestedResourceInfo(nestedResourceInfo, nestedProperty, nestedResourceType, /*isExpanded*/ true); + readerNestedResourceInfo.resourceSet = resourceSet; + return readerNestedResourceInfo; + } + + /// + /// Creates a nested resource info for a singleton entity reference link. + /// + /// The nested resource info to report. + /// The navigation property for which the link will be reported. + /// The entity reference link for the nested resource info to report. + /// true if the nested resource info is expanded. + /// The nested resource info created. + internal static ODataJsonLightReaderNestedResourceInfo CreateSingletonEntityReferenceLinkInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmNavigationProperty navigationProperty, + ODataEntityReferenceLink entityReferenceLink, + bool isExpanded) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(nestedResourceInfo.IsCollection == false, "Singleton entity reference can only be reported for a singleton navigation links."); + Debug.Assert(navigationProperty != null, "navigationProperty != null"); + + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = new ODataJsonLightReaderNestedResourceInfo(nestedResourceInfo, navigationProperty, isExpanded); + if (entityReferenceLink != null) + { + readerNestedResourceInfo.entityReferenceLinks = new LinkedList(); + readerNestedResourceInfo.entityReferenceLinks.AddFirst(entityReferenceLink); + } + + return readerNestedResourceInfo; + } + + /// + /// Creates a nested resource info for a collection of entity reference links. + /// + /// The nested resource info to report. + /// The navigation property for which the link will be reported. + /// The entity reference links for the nested resource info to report. + /// true if the nested resource info is expanded. + /// The nested resource info created. + internal static ODataJsonLightReaderNestedResourceInfo CreateCollectionEntityReferenceLinksInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmNavigationProperty navigationProperty, + LinkedList entityReferenceLinks, + bool isExpanded) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(nestedResourceInfo.IsCollection == true, "Collection entity reference can only be reported for a collection navigation links."); + Debug.Assert(navigationProperty != null, "navigationProperty != null"); + Debug.Assert(entityReferenceLinks == null || entityReferenceLinks.Count > 0, "entityReferenceLinks == null || entityReferenceLinks.Count > 0"); + + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = new ODataJsonLightReaderNestedResourceInfo(nestedResourceInfo, navigationProperty, isExpanded); + readerNestedResourceInfo.entityReferenceLinks = entityReferenceLinks; + return readerNestedResourceInfo; + } + + /// + /// Creates a nested resource info for a projected nested resource info that is missing from the payload. + /// + /// The navigation property for which the link will be reported. + /// The nested resource info created. + internal static ODataJsonLightReaderNestedResourceInfo CreateProjectedNestedResourceInfo(IEdmNavigationProperty navigationProperty) + { + Debug.Assert(navigationProperty != null, "navigationProperty != null"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo { Name = navigationProperty.Name, IsCollection = navigationProperty.Type.IsCollection() }; + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = new ODataJsonLightReaderNestedResourceInfo(nestedResourceInfo, navigationProperty, /*isExpanded*/ false); + return readerNestedResourceInfo; + } + + /// + /// Gets the next entity reference link to report and removes it from the internal storage. + /// + /// The entity reference link to report or null. + internal ODataEntityReferenceLink ReportEntityReferenceLink() + { + if (this.entityReferenceLinks != null && this.entityReferenceLinks.First != null) + { + ODataEntityReferenceLink entityRefernceLink = this.entityReferenceLinks.First.Value; + this.entityReferenceLinks.RemoveFirst(); + return entityRefernceLink; + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedStreamInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedStreamInfo.cs new file mode 100644 index 0000000..c350e91 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderNestedStreamInfo.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + internal sealed class ODataJsonLightReaderStreamInfo + { + /// The primitiveTypeKind of the stream, if known. + private EdmPrimitiveTypeKind primitiveTypeKind; + + /// + /// Constructor. + /// + /// The primitiveType being streamed. + internal ODataJsonLightReaderStreamInfo(EdmPrimitiveTypeKind primitiveTypeKind) + { + this.PrimitiveTypeKind = primitiveTypeKind; + } + + /// + /// Constructor. + /// + /// The primitiveType being streamed. + /// The mime type being streamed. + internal ODataJsonLightReaderStreamInfo(EdmPrimitiveTypeKind primitiveTypeKind, string contentType) + { + this.PrimitiveTypeKind = primitiveTypeKind; + this.ContentType = contentType; + if (contentType.Contains(MimeConstants.MimeApplicationJson)) + { + // Json should always be read/written as a string + this.PrimitiveTypeKind = EdmPrimitiveTypeKind.String; + } + } + + /// + /// The primitiveTypeKind of the stream being read + /// + internal EdmPrimitiveTypeKind PrimitiveTypeKind + { + get + { + return this.primitiveTypeKind; + } + + set + { + this.primitiveTypeKind = + value == EdmPrimitiveTypeKind.Stream ? EdmPrimitiveTypeKind.Binary : value; + } + } + + /// + /// The Mime content type of the stream being read, if known + /// + internal string ContentType { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderUtils.cs new file mode 100644 index 0000000..f6d331b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReaderUtils.cs @@ -0,0 +1,233 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + using ODataPlatformHelper = Microsoft.OData.PlatformHelper; + #endregion Namespaces + + /// + /// Helper methods used by the OData reader for the JsonLight format. + /// + internal static class ODataJsonLightReaderUtils + { + /// + /// Enumeration of all properties in error payloads, the value of the enum is the bitmask which identifies + /// a bit per property. + /// + /// + /// We only use a single enumeration for both top-level as well as inner errors. + /// This means that some bits are never set for top-level (or inner errors). + /// + [Flags] + internal enum ErrorPropertyBitMask + { + /// No property found yet. + None = 0, + + /// The "error" of the top-level object. + Error = 1, + + /// The "code" property. + Code = 2, + + /// The "message" property of either the error object or the inner error object. + Message = 4, + + /// The "value" property of the message object. + MessageValue = 16, + + /// The "innererror" or "internalexception" property of the error object or an inner error object. + InnerError = 32, + + /// The "type" property of an inner error object. + TypeName = 64, + + /// The "stacktrace" property of an inner error object. + StackTrace = 128, + + /// The "target" property. + Target = 256, + + /// The "details" property. + Details = 512 + } + + /// + /// Checks whether the specified property has already been found before. + /// + /// + /// The bit field which stores which properties of an error or inner error were found so far. + /// + /// The bit mask for the property to check. + /// true if the property has not been read before; otherwise false. + internal static bool ErrorPropertyNotFound( + ref ODataJsonLightReaderUtils.ErrorPropertyBitMask propertiesFoundBitField, + ODataJsonLightReaderUtils.ErrorPropertyBitMask propertyFoundBitMask) + { + Debug.Assert(((int)propertyFoundBitMask & (((int)propertyFoundBitMask) - 1)) == 0, "propertyFoundBitMask is not a power of 2."); + + if ((propertiesFoundBitField & propertyFoundBitMask) == propertyFoundBitMask) + { + return false; + } + + propertiesFoundBitField |= propertyFoundBitMask; + return true; + } + + + + /// + /// Converts the given JSON value to the expected type as per OData conversion rules for JSON values. + /// + /// Value to the converted. + /// Type reference to which the value needs to be converted. + /// The message reader settings used for reading. + /// true to validate null values; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// The payload value converter to convert this value. + /// Object which is in sync with the property type (modulo the V1 exception of converting numbers to non-compatible target types). + internal static object ConvertValue( + object value, + IEdmPrimitiveTypeReference primitiveTypeReference, + ODataMessageReaderSettings messageReaderSettings, + bool validateNullValue, + string propertyName, + ODataPayloadValueConverter converter) + { + Debug.Assert(primitiveTypeReference != null, "primitiveTypeReference != null"); + + if (value == null) + { + // Only primitive type references are validated. Core model is sufficient. + messageReaderSettings.Validator.ValidateNullValue( + primitiveTypeReference, + validateNullValue, + propertyName, + null); + return null; + } + + value = converter.ConvertFromPayloadValue(value, primitiveTypeReference); + + // otherwise just return the value without doing any conversion + return value; + } + + /// + /// Ensure that the is not null; if so create a new instance. + /// + /// The type of the instance to check. + /// The instance to check for null. + internal static void EnsureInstance(ref T instance) + where T : class, new() + { + if (instance == null) + { + instance = new T(); + } + } + + /// + /// Determines if the specified is an OData annotation property name. + /// + /// The property name to test. + /// true if the property name is an OData annotation property name, false otherwise. + internal static bool IsODataAnnotationName(string propertyName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + return propertyName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal); + } + + /// + /// Determines if the specified property name is a name of an annotation property. + /// + /// The name of the property. + /// true if is a name of an annotation property, false otherwise. + /// + /// This method returns true both for normal annotation as well as property annotations. + /// + internal static bool IsAnnotationProperty(string propertyName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + return propertyName.IndexOf('.') >= 0; + } + + /// + /// Validates that the annotation value is valid. + /// + /// The value of the annotation. + /// The name of the (instance or property) annotation (used for error reporting). + internal static void ValidateAnnotationValue(object propertyValue, string annotationName) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + if (propertyValue == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightReaderUtils_AnnotationWithNullValue(annotationName)); + } + } + + /// + /// Gets the payload type name for an OData OM instance for JsonLight. + /// + /// The payload item to get the type name for. + /// The type name as read from the payload item (or constructed for primitive items). + internal static string GetPayloadTypeName(object payloadItem) + { + if (payloadItem == null) + { + return null; + } + + // In JSON only boolean, String, Int32 and Double are recognized as primitive types + // (without additional type conversion). So only check for those; if not one of these primitive + // types it must be a complex, entity or collection value. + if (payloadItem is Boolean) + { + return Metadata.EdmConstants.EdmBooleanTypeName; + } + + if (payloadItem is String) + { + return Metadata.EdmConstants.EdmStringTypeName; + } + + if (payloadItem is Int32) + { + return Metadata.EdmConstants.EdmInt32TypeName; + } + + if (payloadItem is Double) + { + return Metadata.EdmConstants.EdmDoubleTypeName; + } + + ODataCollectionValue collectionValue = payloadItem as ODataCollectionValue; + if (collectionValue != null) + { + return EdmLibraryExtensions.GetCollectionTypeFullName(collectionValue.TypeName); + } + + ODataResourceBase resource = payloadItem as ODataResourceBase; + if (resource != null) + { + return resource.TypeName; + } + + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightReader_ReadResourceStart)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs new file mode 100644 index 0000000..436184f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs @@ -0,0 +1,2241 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using System.Text; + using Microsoft.OData.Evaluation; + using Microsoft.OData.Json; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + using Microsoft.OData.Edm.Vocabularies.V1; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// OData JsonLight deserializer for entries and resource sets. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Need to keep the logic together for better readability.")] + internal sealed class ODataJsonLightResourceDeserializer : ODataJsonLightPropertyAndValueDeserializer + { + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + internal ODataJsonLightResourceDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + } + + /// + /// Reads the start of the JSON array for the content of the resource set. + /// + /// + /// Pre-Condition: JsonNodeType.StartArray: The start of the resource set property array; this method will fail if the node is anything else. + /// Post-Condition: JsonNodeType.StartObject: The first item in the resource set + /// JsonNodeType.PrimitiveValue: A null resource, or a primitive value within an untyped collection + /// JsonNodeType.StartArray: A nested collection within an untyped collection + /// JsonNodeType.EndArray: The end of the resource set + /// + internal void ReadResourceSetContentStart() + { + this.JsonReader.AssertNotBuffering(); + + if (this.JsonReader.NodeType != JsonNodeType.StartArray) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_CannotReadResourceSetContentStart(this.JsonReader.NodeType)); + } + + this.JsonReader.ReadStartArray(); + this.JsonReader.AssertNotBuffering(); + } + + /// + /// Reads the end of the array containing the resource set content. + /// + /// + /// Pre-Condition: JsonNodeType.EndArray + /// Post-Condition: JsonNodeType.Property if the resource set is part of an expanded nested resource info and there are more properties in the object + /// JsonNodeType.EndObject if the resource set is a top-level resource set or the expanded nested resource info is the last property of the payload + /// JsonNodeType.EndOfInput if the resource set is in a Uri operation parameter + /// JsonNodeType.StartArray if the resource set is a member of an untyped collection followed by a collection + /// JsonNodeType.PrimitiveValue if the resource set is a member of an untyped collection followed by a primitive value + /// JsonNodeType.StartObject if the resource set is a member of an untyped collection followed by a resource + /// JsonNodeType.EndArray if the resource set is the last member of an untyped collection + /// + internal void ReadResourceSetContentEnd() + { + this.AssertJsonCondition(JsonNodeType.EndArray); + this.JsonReader.AssertNotBuffering(); + + this.JsonReader.ReadEndArray(); + + this.AssertJsonCondition(JsonNodeType.EndOfInput, JsonNodeType.EndObject, JsonNodeType.Property, JsonNodeType.StartArray, JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.EndArray); + } + + /// + /// Reads the resource type name annotation (odata.type) + /// + /// The state of the reader for resource to read. + /// + /// Pre-Condition: JsonNodeType.Property The first property after the odata.context in the resource object. + /// JsonNodeType.EndObject End of the resource object. + /// Post-Condition: JsonNodeType.Property The property after the odata.type (if there was any), or the property on which the method was called. + /// JsonNodeType.EndObject End of the resource object. + /// + /// This method fills the ODataResource.TypeName property if the type name is found in the payload. + /// + internal void ReadResourceTypeName(IODataJsonLightReaderResourceState resourceState) + { + Debug.Assert(resourceState != null, "resourceState != null"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // If the current node is the odata.type property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + string propertyName = this.JsonReader.GetPropertyName(); + if (string.CompareOrdinal(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + ODataAnnotationNames.ODataType, propertyName) == 0 + || this.CompareSimplifiedODataAnnotation(JsonLightConstants.SimplifiedODataTypePropertyName, propertyName)) + { + Debug.Assert(resourceState.Resource.TypeName == null, "type name should not have already been set"); + + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the annotation value. + resourceState.Resource.TypeName = this.ReadODataTypeAnnotationValue(); + } + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } + + /// + /// Reads the OData 4.01 deleted resource annotation (odata.removed) + /// + /// Returns True if the resource is a deleted resource, otherwise returns false + /// + /// Pre-Condition: JsonNodeType.Property The first property after the odata.context in the resource object. + /// JsonNodeType.EndObject End of the resource object. + /// Post-Condition: JsonNodeType.Property The property after the odata.type (if there was any), or the property on which the method was called. + /// JsonNodeType.EndObject End of the resource object. + /// + /// This method Creates an ODataDeltaDeletedEntry and fills in the Id and Reason properties, if specified in the payload. + /// + internal ODataDeletedResource IsDeletedResource() + { + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + ODataDeletedResource deletedResource = null; + + // If the current node is the deleted property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + string propertyName = this.JsonReader.GetPropertyName(); + if (string.CompareOrdinal(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + ODataAnnotationNames.ODataRemoved, propertyName) == 0 + || this.CompareSimplifiedODataAnnotation(JsonLightConstants.SimplifiedODataRemovedPropertyName, propertyName)) + { + DeltaDeletedEntryReason reason = DeltaDeletedEntryReason.Changed; + Uri id = null; + + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the removed object and extract the reason, if present + this.AssertJsonCondition(JsonNodeType.StartObject, JsonNodeType.PrimitiveValue /*null*/); + if (this.JsonReader.NodeType != JsonNodeType.PrimitiveValue) + { + while (this.JsonReader.NodeType != JsonNodeType.EndObject && this.JsonReader.Read()) + { + // If the current node is the reason property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property && + string.CompareOrdinal(JsonLightConstants.ODataReasonPropertyName, this.JsonReader.GetPropertyName()) == 0) + { + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the reason value. + if (string.CompareOrdinal(JsonLightConstants.ODataReasonDeletedValue, this.JsonReader.ReadStringValue()) == 0) + { + reason = DeltaDeletedEntryReason.Deleted; + } + } + } + } + else if (this.JsonReader.Value != null) + { + throw new ODataException(Strings.ODataJsonLightResourceDeserializer_DeltaRemovedAnnotationMustBeObject(this.JsonReader.Value)); + } + + // read over end object or null value + this.JsonReader.Read(); + + // A deleted object must have at least either the odata id annotation or the key values + if (this.JsonReader.NodeType != JsonNodeType.Property) + { + throw new ODataException(Strings.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties); + } + + // If the next property is the id property - read it. + propertyName = this.JsonReader.GetPropertyName(); + if (string.CompareOrdinal(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + ODataAnnotationNames.ODataId, propertyName) == 0 + || this.CompareSimplifiedODataAnnotation(JsonLightConstants.SimplifiedODataIdPropertyName, propertyName)) + { + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the id value. + id = UriUtils.StringToUri(this.JsonReader.ReadStringValue()); + } + + deletedResource = ReaderUtils.CreateDeletedResource(id, reason); + } + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + return deletedResource; + } + + /// + /// Reads an OData 4.0 delete entry + /// + /// Pre-Condition: JsonNodeType.Property The first property after the odata.context in the link object. + /// JsonNodeType.EndObject End of the link object. + /// Post-Condition: JsonNodeType.Property The properties. + /// JsonNodeType.EndObject End of the link object. + /// The read. + /// + /// This method Creates an ODataDeltaDeletedEntry and fills in the Id and Reason properties, if specified in the payload. + /// + internal ODataDeletedResource ReadDeletedEntry() + { + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + Uri id = null; + DeltaDeletedEntryReason reason = DeltaDeletedEntryReason.Changed; + + // If the current node is the id property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property && + string.CompareOrdinal(JsonLightConstants.ODataIdPropertyName, this.JsonReader.GetPropertyName()) == 0) + { + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the Id value. + id = this.JsonReader.ReadUriValue(); + Debug.Assert(id != null, "value for Id must be provided"); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // If the current node is the reason property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property && + string.CompareOrdinal(JsonLightConstants.ODataReasonPropertyName, this.JsonReader.GetPropertyName()) == 0) + { + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the reason value. + if (string.CompareOrdinal(JsonLightConstants.ODataReasonDeletedValue, this.JsonReader.ReadStringValue()) == 0) + { + reason = DeltaDeletedEntryReason.Deleted; + } + } + + // Ignore unknown primitive properties in a 4.0 deleted entry + while (this.JsonReader.NodeType != JsonNodeType.EndObject && this.JsonReader.Read()) + { + if (this.JsonReader.NodeType == JsonNodeType.StartObject || this.JsonReader.NodeType == JsonNodeType.StartArray) + { + throw new ODataException(Strings.ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry); + } + } + + return ReaderUtils.CreateDeletedResource(id, reason); + } + + /// + /// Reads the delta (deleted) link source. + /// + /// The delta (deleted) link being read. + /// + /// Pre-Condition: JsonNodeType.Property The first property after the odata.context in the link object. + /// JsonNodeType.EndObject End of the link object. + /// Post-Condition: JsonNodeType.Property The properties. + /// JsonNodeType.EndObject End of the link object. + /// + /// This method fills the ODataDelta(Deleted)Link.Source property if the id is found in the payload. + /// + internal void ReadDeltaLinkSource(ODataDeltaLinkBase link) + { + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // If the current node is the source property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property && + string.CompareOrdinal(JsonLightConstants.ODataSourcePropertyName, this.JsonReader.GetPropertyName()) == 0) + { + Debug.Assert(link.Source == null, "source should not have already been set"); + + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the source value. + link.Source = this.JsonReader.ReadUriValue(); + Debug.Assert(link.Source != null, "value for source must be provided"); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } + + /// + /// Reads the delta (deleted) link relationship. + /// + /// The delta (deleted) link being read. + /// + /// Pre-Condition: JsonNodeType.Property The first property after the odata.context in the link object. + /// JsonNodeType.EndObject End of the link object. + /// Post-Condition: JsonNodeType.Property The properties. + /// JsonNodeType.EndObject End of the link object. + /// + /// This method fills the ODataDelta(Deleted)Link.Relationship property if the id is found in the payload. + /// + internal void ReadDeltaLinkRelationship(ODataDeltaLinkBase link) + { + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // If the current node is the relationship property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property && + string.CompareOrdinal(JsonLightConstants.ODataRelationshipPropertyName, this.JsonReader.GetPropertyName()) == 0) + { + Debug.Assert(link.Relationship == null, "relationship should not have already been set"); + + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the relationship value. + link.Relationship = this.JsonReader.ReadStringValue(); + Debug.Assert(link.Relationship != null, "value for relationship must be provided"); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } + + /// + /// Reads the delta (deleted) link target. + /// + /// The delta (deleted) link being read. + /// + /// Pre-Condition: JsonNodeType.Property The first property after the odata.context in the link object. + /// JsonNodeType.EndObject End of the link object. + /// Post-Condition: JsonNodeType.Property The properties. + /// JsonNodeType.EndObject End of the link object. + /// + /// This method fills the ODataDelta(Deleted)Link.Target property if the id is found in the payload. + /// + internal void ReadDeltaLinkTarget(ODataDeltaLinkBase link) + { + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // If the current node is the target property - read it. + if (this.JsonReader.NodeType == JsonNodeType.Property && + string.CompareOrdinal(JsonLightConstants.ODataTargetPropertyName, this.JsonReader.GetPropertyName()) == 0) + { + Debug.Assert(link.Target == null, "target should not have already been set"); + + // Read over the property to move to its value. + this.JsonReader.Read(); + + // Read the source value. + link.Target = this.JsonReader.ReadUriValue(); + Debug.Assert(link.Target != null, "value for target must be provided"); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } + + /// + /// Reads the content of a resource until a nested resource info is detected. + /// + /// The state of the reader for resource to read. + /// A reader nested resource info representing the nested resource info detected while reading the resource contents; null if no nested resource info was detected. + /// + /// Pre-Condition: JsonNodeType.Property The property to read + /// JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// Post-Condition: JsonNodeType.EndObject If no (more) properties exist in the resource's content + /// JsonNodeType.Property If we've read a deferred link (this is the property after the deferred link) + /// JsonNodeType.StartObject Expanded resource + /// JsonNodeType.StartArray Expanded resource set + /// JsonNodeType.PrimitiveValue (null) Expanded null + /// + internal ODataJsonLightReaderNestedInfo ReadResourceContent(IODataJsonLightReaderResourceState resourceState) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(resourceState.ResourceType != null && this.Model.IsUserModel(), "A non-null resource type and non-null model are required."); + Debug.Assert( + this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, + "Pre-Condition: JsonNodeType.Property or JsonNodeType.EndObject"); + this.JsonReader.AssertNotBuffering(); + + ODataJsonLightReaderNestedInfo readerNestedResourceInfo = null; + Debug.Assert(resourceState.ResourceType != null, "In JSON we must always have an structured type when reading resource."); + + // Figure out whether we have more properties for this resource + // read all the properties until we hit a link + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ReadPropertyCustomAnnotationValue = this.ReadCustomInstanceAnnotationValue; + this.ProcessProperty( + resourceState.PropertyAndAnnotationCollector, + this.ReadEntryPropertyAnnotationValue, + (propertyParsingResult, propertyName) => + { + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + case PropertyParsingResult.CustomInstanceAnnotation: + this.ReadOverPropertyName(); + object value = ReadODataOrCustomInstanceAnnotationValue(resourceState, propertyParsingResult, propertyName); + this.ApplyEntryInstanceAnnotation(resourceState, propertyName, value); + break; + + case PropertyParsingResult.PropertyWithoutValue: + resourceState.AnyPropertyFound = true; + readerNestedResourceInfo = this.ReadPropertyWithoutValue(resourceState, propertyName); + break; + + case PropertyParsingResult.NestedDeltaResourceSet: + // Will read over property name in ReadPropertyWithValue + resourceState.AnyPropertyFound = true; + readerNestedResourceInfo = this.ReadPropertyWithValue(resourceState, propertyName, /*isDeltaResourceSet*/ true); + break; + + case PropertyParsingResult.PropertyWithValue: + // Will read over property name in ReadPropertyWithValue + resourceState.AnyPropertyFound = true; + readerNestedResourceInfo = this.ReadPropertyWithValue(resourceState, propertyName, /*isDeltaResourceSet*/ false); + break; + + case PropertyParsingResult.MetadataReferenceProperty: + this.ReadOverPropertyName(); + this.ReadMetadataReferencePropertyValue(resourceState, propertyName); + break; + + case PropertyParsingResult.EndOfObject: + this.ReadOverPropertyName(); + break; + } + }); + + if (readerNestedResourceInfo != null) + { + // we found a nested resource info + // stop parsing the resource content and return to the caller + break; + } + + Debug.Assert( + this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, + "After reading a property the reader should be positioned on another property or have hit the end of the object."); + } + + this.JsonReader.AssertNotBuffering(); + + // The reader can be either on + // - StartObject - if it's an expanded resource + // - StartArray - if it's an expanded resource set + // - Property - if it's a deferred link + // - PrimitiveValue- if it's a stream or an expanded null resource + // - EndObject - end of the resource + Debug.Assert( + readerNestedResourceInfo != null && this.JsonReader.NodeType == JsonNodeType.StartObject || + readerNestedResourceInfo != null && this.JsonReader.NodeType == JsonNodeType.StartArray || + readerNestedResourceInfo != null && this.JsonReader.NodeType == JsonNodeType.Property || + readerNestedResourceInfo != null && this.JsonReader.NodeType == JsonNodeType.PrimitiveValue || + readerNestedResourceInfo is ODataJsonLightReaderNestedInfo || + this.JsonReader.NodeType == JsonNodeType.EndObject, + "Post-Condition: expected JsonNodeType.StartObject or JsonNodeType.StartArray or JsonNodeType.Property or JsonNodeType.EndObject or JsonNodeType.Primitive (with null value)"); + + return readerNestedResourceInfo; + } + + /// + /// Reads built-in "odata." or custom instance annotation's value. + /// + /// The IODataJsonLightReaderResourceState. + /// The PropertyParsingResult. + /// The annotation name + /// The annotation value. + internal object ReadODataOrCustomInstanceAnnotationValue(IODataJsonLightReaderResourceState resourceState, PropertyParsingResult propertyParsingResult, string annotationName) + { + object value = this.ReadEntryInstanceAnnotation(annotationName, resourceState.AnyPropertyFound, /*typeAnnotationFound*/ true, resourceState.PropertyAndAnnotationCollector); + if (propertyParsingResult == PropertyParsingResult.ODataInstanceAnnotation) + { + resourceState.PropertyAndAnnotationCollector.AddODataScopeAnnotation(annotationName, value); + } + else + { + resourceState.PropertyAndAnnotationCollector.AddCustomScopeAnnotation(annotationName, value); + } + + return value; + } + + /// + /// Validates resource metadata. + /// + /// The resource state to use. + internal void ValidateMediaEntity(IODataJsonLightReaderResourceState resourceState) + { + ODataResourceBase resource = resourceState.Resource; + if (resource != null) + { + IEdmEntityType entityType = resourceState.ResourceType as IEdmEntityType; + if (entityType != null) + { + // If the entity in the model has a default stream and if no MR related metadata exists in the resource payload, create an empty MediaResource. + // Note that for responses the metadata builder will compute the default stream. For requests we really don't need to add the default stream since the service knows its metadata. + // We leave this here for now so we don't introduce a breaking change. + if (!this.ReadingResponse && entityType.HasStream && resource.MediaResource == null) + { + ODataStreamReferenceValue mediaResource = resource.MediaResource; + ODataJsonLightReaderUtils.EnsureInstance(ref mediaResource); + this.SetEntryMediaResource(resourceState, mediaResource); + } + + this.ReaderValidator.ValidateMediaResource(resource, entityType); + } + } + } + + /// + /// Reads the resource set instance annotations for a top-level resource set. + /// + /// The to read the instance annotations for. + /// The duplicate property names checker for the top-level scope. + /// true when parsing the instance annotations before the resource set property; + /// false when parsing the instance annotations after the resource set property. + /// true if we should scan ahead for the annotations and ignore the actual data properties (used with + /// the reordering reader); otherwise false. + internal void ReadTopLevelResourceSetAnnotations(ODataResourceSetBase resourceSet, PropertyAndAnnotationCollector propertyAndAnnotationCollector, bool forResourceSetStart, bool readAllResourceSetProperties) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + this.JsonReader.AssertNotBuffering(); + + bool buffering = false; + try + { + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + bool foundValueProperty = false; + + if (!forResourceSetStart && readAllResourceSetProperties) + { + // If this is not called for reading ResourceSetStart and we already scanned ahead and processed all resource set properties, we already checked for duplicate property names. + // Use an empty duplicate property name checker since this.ParseProperty() read through the same property annotation of instance annotations again. + propertyAndAnnotationCollector = new PropertyAndAnnotationCollector(false); + } + + this.ProcessProperty( + propertyAndAnnotationCollector, + this.ReadTypePropertyAnnotationValue, + (propertyParseResult, propertyName) => + { + this.ReadOverPropertyName(); + switch (propertyParseResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + case PropertyParsingResult.CustomInstanceAnnotation: + ReadODataOrCustomInstanceAnnotationValue(resourceSet, propertyAndAnnotationCollector, + forResourceSetStart, readAllResourceSetProperties, propertyParseResult, propertyName); + break; + + case PropertyParsingResult.PropertyWithValue: + if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) == 0) + { + // We found the resource set property and are done parsing property annotations; + // When we are in the mode where we scan ahead and read all resource set properties + // (for the reordering scenario), we have to start buffering and continue + // reading. Otherwise we found the resourceSet's data property and are done. + if (readAllResourceSetProperties) + { + this.JsonReader.StartBuffering(); + buffering = true; + + this.JsonReader.SkipValue(); + } + else + { + foundValueProperty = true; + } + } + else + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_InvalidPropertyInTopLevelResourceSet(propertyName, JsonLightConstants.ODataValuePropertyName)); + } + + break; + case PropertyParsingResult.PropertyWithoutValue: + // If we find a property without a value it means that we did not find the resource set property (yet) + // but an invalid property annotation + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_InvalidPropertyAnnotationInTopLevelResourceSet(propertyName)); + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + if (!(resourceSet is ODataResourceSet)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + + this.ReadMetadataReferencePropertyValue((ODataResourceSet)resourceSet, propertyName); + break; + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightResourceDeserializer_ReadTopLevelResourceSetAnnotations)); + } + }); + + if (foundValueProperty) + { + return; + } + } + } + finally + { + if (buffering) + { + Debug.Assert(readAllResourceSetProperties, "Expect the reader to be in buffering mode only when scanning to the end."); + this.JsonReader.StopBuffering(); + } + } + + if (forResourceSetStart && !readAllResourceSetProperties) + { + // We did not find any properties or only instance annotations. + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_ExpectedResourceSetPropertyNotFound(JsonLightConstants.ODataValuePropertyName)); + } + } + + /// + /// Reads built-in "odata." or custom instance annotation's value. + /// + /// The ODataResourceSetBase. + /// The PropertyAndAnnotationCollector. + /// true when parsing the instance annotations before the resource set property; + /// false when parsing the instance annotations after the resource set property. + /// true if we should scan ahead for the annotations and ignore the actual data properties (used with + /// the reordering reader); otherwise false. + /// The PropertyParsingResult. + /// The annotation name. + internal void ReadODataOrCustomInstanceAnnotationValue(ODataResourceSetBase resourceSet, + PropertyAndAnnotationCollector propertyAndAnnotationCollector, bool forResourceSetStart, + bool readAllResourceSetProperties, PropertyParsingResult propertyParseResult, string annotationName) + { + if (propertyParseResult == PropertyParsingResult.ODataInstanceAnnotation) + { + // #### annotation 1 #### + // built-in "odata." annotation value is added to propertyAndAnnotationCollector then later to resourceSet.InstanceAnnotations. + propertyAndAnnotationCollector.AddODataScopeAnnotation(annotationName, this.JsonReader.Value); + } + + // When we are reading the start of a resource set (in scan-ahead mode or not) or when + // we read the end of a resource set and not in scan-ahead mode, read the value; + // otherwise skip it. + if (forResourceSetStart || !readAllResourceSetProperties) + { + // #### annotation 2 #### + // custom annotation value will be directly added to resourceSet.InstanceAnnotations. + this.ReadAndApplyResourceSetInstanceAnnotationValue(annotationName, resourceSet, propertyAndAnnotationCollector); + } + else + { + this.JsonReader.SkipValue(); + } + } + + /// + /// Reads a value of property annotation on the resource level. + /// + /// The name of the property annotation to read. + /// The value of the property annotation. + /// + /// This method should read the property annotation value and return a representation of the value which will be later + /// consumed by the resource reading code. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the property annotation property + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// Post-Condition: JsonNodeType.EndObject The end of the resource object + /// JsonNodeType.Property The next property after the property annotation + /// + internal object ReadEntryPropertyAnnotationValue(string propertyAnnotationName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyAnnotationName), "!string.IsNullOrEmpty(propertyAnnotationName)"); + Debug.Assert( + propertyAnnotationName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal), + "The method should only be called with OData. annotations"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.StartArray); + + string typeName; + if (this.TryReadODataTypeAnnotationValue(propertyAnnotationName, out typeName)) + { + return typeName; + } + + switch (propertyAnnotationName) + { + case ODataAnnotationNames.ODataNavigationLinkUrl: // odata.navigationLinkUrl + case ODataAnnotationNames.ODataAssociationLinkUrl: // odata.associationLinkUrl + case ODataAnnotationNames.ODataNextLink: // odata.nextLink + case ODataAnnotationNames.ODataMediaEditLink: // odata.mediaEditLink + case ODataAnnotationNames.ODataMediaReadLink: // odata.mediaReadLink + case ODataAnnotationNames.ODataContext: // odata.context + return this.ReadAndValidateAnnotationStringValueAsUri(propertyAnnotationName); + + case ODataAnnotationNames.ODataCount: // odata.count + return this.ReadAndValidateAnnotationAsLongForIeee754Compatible(propertyAnnotationName); + + case ODataAnnotationNames.ODataMediaETag: // odata.mediaEtag + case ODataAnnotationNames.ODataMediaContentType: // odata.mediaContentType + return this.ReadAndValidateAnnotationStringValue(propertyAnnotationName); + + // odata.bind + case ODataAnnotationNames.ODataBind: + // The value of the odata.bind annotation can be either an array of strings or a string (collection or singleton nested resource info). + // Note that we don't validate that the cardinality of the navigation property matches the payload here, since we don't want to lookup the property twice. + // We will validate that later when we consume the value of the property annotation. + if (this.JsonReader.NodeType != JsonNodeType.StartArray) + { + return new ODataEntityReferenceLink + { + Url = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataBind) + }; + } + + LinkedList entityReferenceLinks = new LinkedList(); + + // Read over the start array + this.JsonReader.Read(); + while (this.JsonReader.NodeType != JsonNodeType.EndArray) + { + entityReferenceLinks.AddLast( + new ODataEntityReferenceLink + { + Url = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataBind) + }); + } + + // Read over the end array + this.JsonReader.Read(); + if (entityReferenceLinks.Count == 0) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_EmptyBindArray(ODataAnnotationNames.ODataBind)); + } + + return entityReferenceLinks; + + case ODataAnnotationNames.ODataDeltaLink: // Delta links are not supported on expanded resource sets. + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyAnnotationName)); + } + } + + /// + /// Reads instance annotation in the resource object. + /// + /// The name of the instance annotation found. + /// true if a non-annotation property has already been encountered. + /// true if the 'odata.type' annotation has already been encountered, or should have been by now. + /// The duplicate property names checker for the resource being read. + /// The value of the annotation. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the instance annotation property + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// Post-Condition: JsonNodeType.EndObject The end of the resource object + /// JsonNodeType.Property The next property after the instance annotation + /// + internal object ReadEntryInstanceAnnotation(string annotationName, bool anyPropertyFound, bool typeAnnotationFound, PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.StartObject, JsonNodeType.StartArray); + + switch (annotationName) + { + case ODataAnnotationNames.ODataType: // 'odata.type' + if (!typeAnnotationFound) + { + return this.ReadODataTypeAnnotationValue(); + } + + // We already read the odata.type if it was the first property in ReadResourceStart, so any other occurrence means + // that it was not the first property. + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_ResourceTypeAnnotationNotFirst); + + case ODataAnnotationNames.ODataId: // 'odata.id' + if (anyPropertyFound) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_ResourceInstanceAnnotationPrecededByProperty(annotationName)); + } + + return this.ReadAnnotationStringValueAsUri(annotationName); + + case ODataAnnotationNames.ODataETag: // 'odata.etag' + if (anyPropertyFound) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_ResourceInstanceAnnotationPrecededByProperty(annotationName)); + } + + return this.ReadAndValidateAnnotationStringValue(annotationName); + + case ODataAnnotationNames.ODataEditLink: // 'odata.editLink' + case ODataAnnotationNames.ODataReadLink: // 'odata.readLink' + case ODataAnnotationNames.ODataMediaEditLink: // 'odata.mediaEditLink' + case ODataAnnotationNames.ODataMediaReadLink: // 'odata.mediaReadLink' + return this.ReadAndValidateAnnotationStringValueAsUri(annotationName); + + case ODataAnnotationNames.ODataMediaContentType: // 'odata.mediaContentType' + case ODataAnnotationNames.ODataMediaETag: // 'odata.mediaEtag' + return this.ReadAndValidateAnnotationStringValue(annotationName); + + default: + ODataAnnotationNames.ValidateIsCustomAnnotationName(annotationName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(annotationName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + return this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, annotationName); + } + } + + /// + /// Reads instance annotation in the resource object. + /// + /// The state of the reader for resource to read. + /// The name of the instance annotation found. + /// The value of the annotation. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the instance annotation property + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// Post-Condition: JsonNodeType.EndObject The end of the resource object + /// JsonNodeType.Property The next property after the instance annotation + /// + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "The casts aren't actually being done multiple times, since they occur in different cases of the switch statement.")] + internal void ApplyEntryInstanceAnnotation(IODataJsonLightReaderResourceState resourceState, string annotationName, object annotationValue) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + ODataResourceBase resource = resourceState.Resource; + ODataStreamReferenceValue mediaResource = resource.MediaResource; + switch (annotationName) + { + case ODataAnnotationNames.ODataType: // 'odata.type' + resource.TypeName = ReaderUtils.AddEdmPrefixOfTypeName(ReaderUtils.RemovePrefixOfTypeName((string)annotationValue)); + break; + + case ODataAnnotationNames.ODataId: // 'odata.id' + if (annotationValue == null) + { + resource.IsTransient = true; + } + else + { + resource.Id = (Uri)annotationValue; + } + + break; + + case ODataAnnotationNames.ODataETag: // 'odata.etag' + resource.ETag = (string)annotationValue; + break; + + case ODataAnnotationNames.ODataEditLink: // 'odata.editLink' + resource.EditLink = (Uri)annotationValue; + break; + + case ODataAnnotationNames.ODataReadLink: // 'odata.readLink' + resource.ReadLink = (Uri)annotationValue; + break; + + case ODataAnnotationNames.ODataMediaEditLink: // 'odata.mediaEditLink' + ODataJsonLightReaderUtils.EnsureInstance(ref mediaResource); + mediaResource.EditLink = (Uri)annotationValue; + break; + + case ODataAnnotationNames.ODataMediaReadLink: // 'odata.mediaReadLink' + ODataJsonLightReaderUtils.EnsureInstance(ref mediaResource); + mediaResource.ReadLink = (Uri)annotationValue; + break; + + case ODataAnnotationNames.ODataMediaContentType: // 'odata.mediaContentType' + ODataJsonLightReaderUtils.EnsureInstance(ref mediaResource); + mediaResource.ContentType = (string)annotationValue; + break; + + case ODataAnnotationNames.ODataMediaETag: // 'odata.mediaEtag' + ODataJsonLightReaderUtils.EnsureInstance(ref mediaResource); + mediaResource.ETag = (string)annotationValue; + break; + + default: + ODataAnnotationNames.ValidateIsCustomAnnotationName(annotationName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(annotationName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + resource.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotationName, annotationValue.ToODataValue())); + break; + } + + if (mediaResource != null && resource.MediaResource == null) + { + this.SetEntryMediaResource(resourceState, mediaResource); + } + } + + /// + /// Reads the value of a resource set annotation (count or next link). + /// + /// The name of the annotation found. + /// The resource set to read the annotation for; if non-null, the annotation value will be assigned to the resource set. + /// The duplicate property names checker instance. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the annotation + /// Post-Condition: JsonNodeType.EndObject The end of the resource set object + /// JsonNodeType.Property The next annotation after the current annotation + /// + internal void ReadAndApplyResourceSetInstanceAnnotationValue(string annotationName, ODataResourceSetBase resourceSet, PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + Debug.Assert(resourceSet != null, "resourceSet != null"); + + switch (annotationName) + { + case ODataAnnotationNames.ODataCount: + resourceSet.Count = this.ReadAndValidateAnnotationAsLongForIeee754Compatible(ODataAnnotationNames.ODataCount); + break; + + case ODataAnnotationNames.ODataNextLink: + resourceSet.NextPageLink = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataNextLink); + break; + + case ODataAnnotationNames.ODataDeltaLink: + resourceSet.DeltaLink = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataDeltaLink); + break; + case ODataAnnotationNames.ODataType: + + // TODO: skip the odata.type; + this.ReadAndValidateAnnotationStringValue(ODataAnnotationNames.ODataType); + break; + default: + ODataAnnotationNames.ValidateIsCustomAnnotationName(annotationName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(annotationName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + object instanceAnnotationValue = this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, annotationName); + resourceSet.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotationName, instanceAnnotationValue.ToODataValue())); + break; + } + } + + /// + /// Reads resource property which doesn't have value, just annotations. + /// + /// The state of the reader for resource to read. + /// The name of the property read. + /// A reader nested resource info representing the nested resource info detected while reading the resource contents; null if no nested resource info was detected. + /// + /// Pre-Condition: JsonNodeType.EndObject The end of the resource object. + /// JsonNodeType.Property The property after the one we're to read. + /// Post-Condition: JsonNodeType.EndObject This method doesn't move the reader. + /// JsonNodeType.Property + /// + internal ODataJsonLightReaderNestedInfo ReadPropertyWithoutValue(IODataJsonLightReaderResourceState resourceState, string propertyName) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + ODataJsonLightReaderNestedInfo readerNestedInfo = null; + IEdmStructuredType resourceType = resourceState.ResourceType; + IEdmProperty edmProperty = this.ReaderValidator.ValidatePropertyDefined(propertyName, resourceType); + if (edmProperty != null && !edmProperty.Type.IsUntyped()) + { + // Declared property - read it. + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo; + IEdmNavigationProperty navigationProperty = edmProperty as IEdmNavigationProperty; + if (navigationProperty != null) + { + if (this.ReadingResponse) + { + // Deferred link + readerNestedResourceInfo = ReadDeferredNestedResourceInfo(resourceState, propertyName, navigationProperty); + } + else + { + // Entity reference link or links + readerNestedResourceInfo = navigationProperty.Type.IsCollection() + ? ReadEntityReferenceLinksForCollectionNavigationLinkInRequest(resourceState, navigationProperty, propertyName, /*isExpanded*/ false) + : ReadEntityReferenceLinkForSingletonNavigationLinkInRequest(resourceState, navigationProperty, propertyName, /*isExpanded*/ false); + + if (!readerNestedResourceInfo.HasEntityReferenceLink) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink(propertyName, ODataAnnotationNames.ODataBind)); + } + } + + resourceState.PropertyAndAnnotationCollector.ValidatePropertyUniquenessOnNestedResourceInfoStart(readerNestedResourceInfo.NestedResourceInfo); + readerNestedInfo = readerNestedResourceInfo; + } + else + { + IEdmTypeReference propertyTypeReference = edmProperty.Type; + if (propertyTypeReference.IsStream()) + { + Debug.Assert(propertyName == edmProperty.Name, "propertyName == edmProperty.Name"); + ODataStreamReferenceValue streamPropertyValue = this.ReadStreamPropertyValue(resourceState, propertyName); + AddResourceProperty(resourceState, edmProperty.Name, streamPropertyValue); + } + else + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_PropertyWithoutValueWithWrongType(propertyName, propertyTypeReference.FullName())); + } + } + } + else + { + // Undeclared property - we need to run detection algorithm here. + readerNestedInfo = this.ReadUndeclaredProperty(resourceState, propertyName, /*propertyWithValue*/ false); + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + return readerNestedInfo; + } + + /// + /// Reads any next link annotation immediately after the end of a resource set. + /// + /// The resource set being read. + /// The information about the expanded link. This must be non-null if we're reading an expanded resource set, and must be null if we're reading a top-level resource set. + /// The top-level duplicate property names checker, if we're reading a top-level resource set. + internal void ReadNextLinkAnnotationAtResourceSetEnd( + ODataResourceSetBase resourceSet, + ODataJsonLightReaderNestedResourceInfo expandedNestedResourceInfo, + PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + // Check for annotations on the resource set that occur after the resource set itself. (Note: the only allowed one is odata.nextLink, and we fail for anything else.) + // We do this slightly differently depending on whether the resource set was an expanded navigation or a top-level resource set. + if (expandedNestedResourceInfo != null) + { + this.ReadExpandedResourceSetAnnotationsAtResourceSetEnd(resourceSet, expandedNestedResourceInfo); + } + else + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + // Check for resource set instance annotations that appear after the resource set. + bool isReordering = this.JsonReader is ReorderingJsonReader; + this.ReadTopLevelResourceSetAnnotations(resourceSet, propertyAndAnnotationCollector, /*forResourceSetStart*/false, /*readAllResourceSetProperties*/isReordering); + } + } + + /// + /// Reads the information of a deferred link. + /// + /// The state of the reader for resource to read. + /// The name of the navigation property for which to read the deferred link. + /// The navigation property for which to read the deferred link. This can be null. + /// Returns the nested resource info for the deferred nested resource info read. + /// + /// This method doesn't move the reader. + /// + private static ODataJsonLightReaderNestedResourceInfo ReadDeferredNestedResourceInfo(IODataJsonLightReaderResourceState resourceState, string navigationPropertyName, IEdmNavigationProperty navigationProperty) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(navigationPropertyName), "!string.IsNullOrEmpty(navigationPropertyName)"); + Debug.Assert(navigationProperty == null || navigationPropertyName == navigationProperty.Name, "navigationProperty == null || navigationPropertyName == navigationProperty.Name"); + + ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo() + { + Name = navigationPropertyName, + IsCollection = navigationProperty == null ? null : (bool?)navigationProperty.Type.IsCollection() + }; + + foreach (var propertyAnnotation + in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(nestedResourceInfo.Name)) + { + switch (propertyAnnotation.Key) + { + case ODataAnnotationNames.ODataNavigationLinkUrl: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.navigationLinkUrl annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.Url = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataAssociationLinkUrl: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.associationLinkUrl annotation should have been parsed as a non-null Uri."); + nestedResourceInfo.AssociationLinkUrl = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataType: + Debug.Assert(propertyAnnotation.Value is String && propertyAnnotation.Value != null, "The odata.type annotation should have been parsed as a non-null string."); + nestedResourceInfo.TypeAnnotation = new ODataTypeAnnotation((string)propertyAnnotation.Value); + break; + + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedDeferredLinkPropertyAnnotation(nestedResourceInfo.Name, propertyAnnotation.Key)); + } + } + + return ODataJsonLightReaderNestedResourceInfo.CreateDeferredLinkInfo(nestedResourceInfo, navigationProperty); + } + + /// + /// We fail here if we encounter any other property annotation for the expanded navigation (since these should come before the property itself). + /// + /// The resource set that was just read. + /// The information for the current expanded nested resource info being read. + private void ReadExpandedResourceSetAnnotationsAtResourceSetEnd(ODataResourceSetBase resourceSet, ODataJsonLightReaderNestedResourceInfo expandedNestedResourceInfo) + { + Debug.Assert(expandedNestedResourceInfo != null, "expandedNestedResourceInfo != null"); + Debug.Assert(expandedNestedResourceInfo.NestedResourceInfo.IsCollection == true, "Only collection navigation properties can have resourceSet content."); + + // Look at the next property in the owning resource, if it's a property annotation for the expanded nested resource info property, read it. + string propertyName, annotationName; + while (this.JsonReader.NodeType == JsonNodeType.Property && + TryParsePropertyAnnotation(this.JsonReader.GetPropertyName(), out propertyName, out annotationName) && + string.CompareOrdinal(propertyName, expandedNestedResourceInfo.NestedResourceInfo.Name) == 0) + { + if (!this.ReadingResponse) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedPropertyAnnotation(propertyName, annotationName)); + } + + // Read over the property name. + this.JsonReader.Read(); + + switch (this.CompleteSimplifiedODataAnnotation(annotationName)) + { + case ODataAnnotationNames.ODataNextLink: + if (resourceSet.NextPageLink != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_DuplicateNestedResourceSetAnnotation(ODataAnnotationNames.ODataNextLink, expandedNestedResourceInfo.NestedResourceInfo.Name)); + } + + // Read the property value. + resourceSet.NextPageLink = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataNextLink); + break; + + case ODataAnnotationNames.ODataCount: + if (resourceSet.Count != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_DuplicateNestedResourceSetAnnotation(ODataAnnotationNames.ODataCount, expandedNestedResourceInfo.NestedResourceInfo.Name)); + } + + // Read the property value. + resourceSet.Count = this.ReadAndValidateAnnotationAsLongForIeee754Compatible(ODataAnnotationNames.ODataCount); + break; + + case ODataAnnotationNames.ODataDeltaLink: // Delta links are not supported on expanded resource sets. + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedPropertyAnnotationAfterExpandedResourceSet(annotationName, expandedNestedResourceInfo.NestedResourceInfo.Name)); + } + } + } + + /// + /// Sets specified media resource on a resource and hooks up metadata builder. + /// + /// The resource state to use. + /// The media resource to set. + private void SetEntryMediaResource(IODataJsonLightReaderResourceState resourceState, ODataStreamReferenceValue mediaResource) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(mediaResource != null, "mediaResource != null"); + ODataResourceBase resource = resourceState.Resource; + Debug.Assert(resource != null, "resource != null"); + + ODataResourceMetadataBuilder builder = + this.MetadataContext.GetResourceMetadataBuilderForReader(resourceState, + this.JsonLightInputContext.ODataSimplifiedOptions.EnableReadingKeyAsSegment, + /*isDelta*/ false); + mediaResource.SetMetadataBuilder(builder, /*propertyName*/ null); + resource.MediaResource = mediaResource; + } + + /// + /// Reads resource property (which is neither instance nor property annotation) which has a value. + /// + /// The state of the reader for resource to read. + /// The name of the property read. + /// The property being read represents a nested delta resource set. + /// A reader nested resource info representing the nested resource info detected while reading the resource contents; null if no nested resource info was detected. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the property + /// JsonNodeType.StartObject + /// JsonNodeType.StartArray + /// Post-Condition: JsonNodeType.EndObject The end of the resource object + /// JsonNodeType.Property The next property after the property + /// JsonNodeType.StartObject Expanded resource + /// JsonNodeType.StartArray Expanded resource set + /// JsonNodeType.PrimitiveValue (null) Expanded null resource + /// + private ODataJsonLightReaderNestedInfo ReadPropertyWithValue(IODataJsonLightReaderResourceState resourceState, string propertyName, bool isDeltaResourceSet) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.Property, JsonNodeType.StartObject, JsonNodeType.StartArray); + + ODataJsonLightReaderNestedInfo readerNestedInfo = null; + IEdmStructuredType resourceType = resourceState.ResourceType; + IEdmProperty edmProperty = this.ReaderValidator.ValidatePropertyDefined(propertyName, resourceType); + bool isCollection = edmProperty == null ? false : edmProperty.Type.IsCollection(); + IEdmStructuralProperty structuralProperty = edmProperty as IEdmStructuralProperty; + + if (structuralProperty != null) + { + ODataJsonLightReaderNestedInfo nestedInfo = TryReadAsStream(resourceState, structuralProperty, structuralProperty.Type, structuralProperty.Name); + if (nestedInfo != null) + { + return nestedInfo; + } + } + + if (edmProperty != null && !edmProperty.Type.IsUntyped()) + { + this.ReadOverPropertyName(); + IEdmStructuredType structuredPropertyTypeOrItemType = structuralProperty == null ? null : structuralProperty.Type.ToStructuredType(); + IEdmNavigationProperty navigationProperty = edmProperty as IEdmNavigationProperty; + if (structuredPropertyTypeOrItemType != null) + { + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = null; + + // Complex property or collection of complex property. + ValidateExpandedNestedResourceInfoPropertyValue(this.JsonReader, isCollection, propertyName); + + if (isCollection) + { + readerNestedResourceInfo = ReadNonExpandedResourceSetNestedResourceInfo(resourceState, structuralProperty, structuredPropertyTypeOrItemType, structuralProperty.Name); + } + else + { + readerNestedResourceInfo = ReadNonExpandedResourceNestedResourceInfo(resourceState, structuralProperty, structuredPropertyTypeOrItemType, structuralProperty.Name); + } + + resourceState.PropertyAndAnnotationCollector.ValidatePropertyUniquenessOnNestedResourceInfoStart(readerNestedResourceInfo.NestedResourceInfo); + readerNestedInfo = readerNestedResourceInfo; + } + else if (navigationProperty != null) + { + ODataJsonLightReaderNestedResourceInfo readerNestedResourceInfo = null; + + // Expanded link + ValidateExpandedNestedResourceInfoPropertyValue(this.JsonReader, isCollection, propertyName); + if (isCollection) + { + readerNestedResourceInfo = this.ReadingResponse + ? ReadExpandedResourceSetNestedResourceInfo(resourceState, navigationProperty, navigationProperty.Type.ToStructuredType(), propertyName, /*isDeltaResourceSet*/ isDeltaResourceSet) + : ReadEntityReferenceLinksForCollectionNavigationLinkInRequest(resourceState, navigationProperty, propertyName, /*isExpanded*/ true); + } + else + { + readerNestedResourceInfo = this.ReadingResponse + ? ReadExpandedResourceNestedResourceInfo(resourceState, navigationProperty, propertyName, navigationProperty.Type.ToStructuredType(), this.MessageReaderSettings) + : ReadEntityReferenceLinkForSingletonNavigationLinkInRequest(resourceState, navigationProperty, propertyName, /*isExpanded*/ true); + } + + resourceState.PropertyAndAnnotationCollector.ValidatePropertyUniquenessOnNestedResourceInfoStart(readerNestedResourceInfo.NestedResourceInfo); + readerNestedInfo = readerNestedResourceInfo; + } + else + { + var derivedTypeConstrants = this.JsonLightInputContext.Model.GetDerivedTypeConstraints(edmProperty); + if (derivedTypeConstrants != null) + { + resourceState.PropertyAndAnnotationCollector.SetDerivedTypeValidator(propertyName, new DerivedTypeValidator(edmProperty.Type.Definition, derivedTypeConstrants, "property", propertyName)); + } + + // NOTE: we currently do not check whether the property should be skipped + // here because this can only happen for navigation properties and open properties. + this.ReadEntryDataProperty(resourceState, edmProperty, ValidateDataPropertyTypeNameAnnotation(resourceState.PropertyAndAnnotationCollector, propertyName)); + } + } + else + { + // Undeclared property - we need to run detection alogorithm here. + readerNestedInfo = this.ReadUndeclaredProperty(resourceState, propertyName, /*propertyWithValue*/ true); + + // Note that if nested resource info is returned it's already validated, so we just report it here. + } + + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject, JsonNodeType.StartObject, JsonNodeType.StartArray, JsonNodeType.PrimitiveValue); + return readerNestedInfo; + } + + /// + /// Checks to see if the current property should be read as a stream and, if so reads it + /// + /// current ResourceState + /// The property being serialized (null for a dynamic property) + /// The type of the property being serialized + /// The name of the property being serialized + /// The ODataJsonLightReaderNestedInfo for a nested stream property, or null if the property shouldn't be streamed + private ODataJsonLightReaderNestedInfo TryReadAsStream(IODataJsonLightReaderResourceState resourceState, IEdmStructuralProperty property, IEdmTypeReference propertyType, string propertyName) + { + Debug.Assert(propertyName != null, "Property name must not be null"); + + bool isCollection = false; + IEdmPrimitiveType primitiveType = null; + if (propertyType != null) + { + primitiveType = propertyType.Definition.AsElementType() as IEdmPrimitiveType; + isCollection = propertyType.IsCollection(); + } + else + { + isCollection = this.JsonReader.NodeType != JsonNodeType.PrimitiveValue; + } + + Func readAsStream = this.MessageReaderSettings.ReadAsStreamFunc; + + // is the property a stream or a stream collection, + // or a binary or binary collection the client wants to read as a stream... + if ( + (primitiveType != null && + (primitiveType.IsStream() || + (readAsStream != null + && (property == null || !property.IsKey()) // don't stream key properties + && (primitiveType.IsBinary() || primitiveType.IsString() || isCollection)) + && readAsStream(primitiveType, isCollection, propertyName, property))) || + ((propertyType == null || propertyType.Definition.AsElementType().IsUntyped()) + && (isCollection || this.JsonReader.CanStream()) + && readAsStream != null + && readAsStream(null, isCollection, propertyName, property))) + { + if (isCollection) + { + this.ReadOverPropertyName(); + IEdmType elementType = propertyType == null ? EdmCoreModel.Instance.GetUntypedType() : propertyType.Definition.AsElementType(); + + // Collection of streams, or binary/string values to read as streams + return ReadStreamCollectionNestedResourceInfo(resourceState, property, propertyName, elementType); + } + else + { + ODataPropertyInfo propertyInfo; + if (primitiveType != null && primitiveType.PrimitiveKind == EdmPrimitiveTypeKind.Stream) + { + ODataStreamPropertyInfo streamPropertyInfo = this.ReadStreamPropertyInfo(resourceState, propertyName); + + // If it has an instance annotation saying that the content type is JSON, don't read propertyName + // otherwise, if we are on start object, BufferingJsonReader will read ahead to try and determine + // if we are reading an instream error, which destroys our ability to stream json stream values. + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + bool isJson = false; + if (streamPropertyInfo.ContentType != null) + { + if (streamPropertyInfo.ContentType.Contains(MimeConstants.MimeApplicationJson)) + { + isJson = true; + } + } + else if (property != null) + { + IEdmVocabularyAnnotation mediaTypeAnnotation = property.VocabularyAnnotations(this.Model).FirstOrDefault(a => a.Term == CoreVocabularyModel.MediaTypeTerm); + if (mediaTypeAnnotation != null) + { + // If the property does not have a mediaType annotation specifying application/json, then read over the property name + IEdmStringConstantExpression stringExpression = mediaTypeAnnotation.Value as IEdmStringConstantExpression; + if (stringExpression != null && stringExpression.Value.Contains(MimeConstants.MimeApplicationJson)) + { + isJson = true; + } + } + } + + if (!isJson) + { + // Not reading JSON stream, so read over property name + this.ReadOverPropertyName(); + } + } + + // Add the stream reference property + ODataStreamReferenceValue streamReferenceValue = this.ReadStreamPropertyValue(resourceState, propertyName); + AddResourceProperty(resourceState, propertyName, streamReferenceValue); + + propertyInfo = streamPropertyInfo; + } + else + { + this.ReadOverPropertyName(); + + propertyInfo = new ODataPropertyInfo + { + PrimitiveTypeKind = primitiveType == null ? EdmPrimitiveTypeKind.None : primitiveType.PrimitiveKind, + Name = propertyName, + }; + } + + // return without reading over the property node; we will create a stream over the value + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.Property); + return new ODataJsonLightReaderNestedPropertyInfo(propertyInfo, property); + } + } + + return null; + } + + /// + /// Reads over the current property name if positioned on a property + /// + private void ReadOverPropertyName() + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.JsonReader.Read(); + } + } + + /// + /// Returns whether or not a StreamPropertyInfo value specifies a content-type of application/json. + /// + /// The StreamPropertyInfo that may specify application/json. + /// True, if the StreamPropertyInfo specifies application/json, otherwise false. + private static bool IsJsonStream(ODataStreamPropertyInfo streamPropertyInfo) + { + return streamPropertyInfo.ContentType != null && streamPropertyInfo.ContentType.Contains(MimeConstants.MimeApplicationJson); + } + + /// + /// Read a resource-level data property and check its version compliance. + /// + /// The state of the reader for resource to read. + /// The EDM property of the property being read, or null if the property is an open property. + /// The type name specified for the property in property annotation, or null if no such type name is available. + /// + /// Pre-Condition: The reader is positioned on the first node of the property value + /// Post-Condition: JsonNodeType.Property: the next property of the resource + /// JsonNodeType.EndObject: the end-object node of the resource + /// + private void ReadEntryDataProperty(IODataJsonLightReaderResourceState resourceState, IEdmProperty edmProperty, string propertyTypeName) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(edmProperty != null, "edmProperty != null"); + this.JsonReader.AssertNotBuffering(); + + // EdmLib bridge marks all key properties as non-nullable, but Astoria allows them to be nullable. + // If the property has an annotation to ignore null values, we need to omit the property in requests. + ODataNullValueBehaviorKind nullValueReadBehaviorKind = this.ReadingResponse + ? ODataNullValueBehaviorKind.Default + : this.Model.NullValueReadBehaviorKind(edmProperty); + object propertyValue = this.ReadNonEntityValue( + propertyTypeName, + edmProperty.Type, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + nullValueReadBehaviorKind == ODataNullValueBehaviorKind.Default, + /*isTopLevelPropertyValue*/ false, + /*insideResourceValue*/ false, + edmProperty.Name); + + if (nullValueReadBehaviorKind != ODataNullValueBehaviorKind.IgnoreValue || propertyValue != null) + { + AddResourceProperty(resourceState, edmProperty.Name, propertyValue); + } + + this.JsonReader.AssertNotBuffering(); + Debug.Assert( + this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, + "Post-Condition: expected JsonNodeType.Property or JsonNodeType.EndObject"); + } + + /// + /// Read an open property. + /// + /// The state of the reader for resource to read. + /// The owning type of the property with name + /// or null if no metadata is available. + /// The name of the open property to read. + /// true if the property has a value, false if it doesn't. + /// + /// Pre-Condition: The reader is positioned on the first node of the property value + /// Post-Condition: JsonNodeType.Property: the next property of the resource + /// JsonNodeType.EndObject: the end-object node of the resource + /// + /// The NestedResourceInfo or null. + private ODataJsonLightReaderNestedInfo InnerReadUndeclaredProperty(IODataJsonLightReaderResourceState resourceState, IEdmStructuredType owningStructuredType, string propertyName, bool propertyWithValue) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + this.JsonReader.AssertNotBuffering(); + + // Property without a value can't be ignored if we don't know what it is. + if (!propertyWithValue) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_OpenPropertyWithoutValue(propertyName)); + } + + object propertyValue = null; + bool insideResourceValue = false; + string outerPayloadTypeName = ValidateDataPropertyTypeNameAnnotation(resourceState.PropertyAndAnnotationCollector, propertyName); + string payloadTypeName = TryReadOrPeekPayloadType(resourceState.PropertyAndAnnotationCollector, propertyName, insideResourceValue); + EdmTypeKind payloadTypeKind; + IEdmType payloadType = ReaderValidationUtils.ResolvePayloadTypeName( + this.Model, + null, // expectedTypeReference + payloadTypeName, + EdmTypeKind.Complex, + this.MessageReaderSettings.ClientCustomTypeResolver, + out payloadTypeKind); + IEdmTypeReference payloadTypeReference = null; + if (!string.IsNullOrEmpty(payloadTypeName) && payloadType != null) + { + // only try resolving for known type (the below will throw on unknown type name) : + ODataTypeAnnotation typeAnnotation; + EdmTypeKind targetTypeKind; + payloadTypeReference = this.ReaderValidator.ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind.None, + /*expectStructuredType*/ null, + /*defaultPrimitivePayloadType*/ null, + null, // expectedTypeReference + payloadTypeName, + this.Model, + this.GetNonEntityValueKind, + out targetTypeKind, + out typeAnnotation); + } + + ODataJsonLightReaderNestedInfo nestedResourceInfo = TryReadAsStream(resourceState, null, payloadTypeReference, propertyName); + if (nestedResourceInfo != null) + { + return nestedResourceInfo; + } + + payloadTypeReference = ResolveUntypedType( + this.JsonReader.NodeType, + this.JsonReader.Value, + payloadTypeName, + payloadTypeReference, + this.MessageReaderSettings.PrimitiveTypeResolver, + this.MessageReaderSettings.ReadUntypedAsString, + !this.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata); + + bool isCollection = payloadTypeReference.IsCollection(); + IEdmStructuredType payloadTypeOrItemType = payloadTypeReference.ToStructuredType(); + if (payloadTypeOrItemType != null) + { + // Complex property or collection of complex property. + ValidateExpandedNestedResourceInfoPropertyValue(this.JsonReader, isCollection, propertyName); + if (isCollection) + { + return ReadNonExpandedResourceSetNestedResourceInfo(resourceState, null, payloadTypeOrItemType, propertyName); + } + else + { + return ReadNonExpandedResourceNestedResourceInfo(resourceState, null, payloadTypeOrItemType, propertyName); + } + } + + if (!(payloadTypeReference is IEdmUntypedTypeReference)) + { + this.JsonReader.AssertNotBuffering(); + propertyValue = this.ReadNonEntityValue( + outerPayloadTypeName, + payloadTypeReference, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ false, + /*insideResourceValue*/ false, + propertyName, + /*isDynamicProperty*/true); + } + else + { + propertyValue = this.JsonReader.ReadAsUntypedOrNullValue(); + } + + ValidationUtils.ValidateOpenPropertyValue(propertyName, propertyValue); + AddResourceProperty(resourceState, propertyName, propertyValue); + this.JsonReader.AssertNotBuffering(); + Debug.Assert( + this.JsonReader.NodeType == JsonNodeType.Property || this.JsonReader.NodeType == JsonNodeType.EndObject, + "Post-Condition: expected JsonNodeType.Property or JsonNodeType.EndObject"); + + return null; + } + + /// + /// Read an undeclared property. That is a property which is not declared by the model, but the owning type is not an open type. + /// + /// The state of the reader for resource to read. + /// The name of the open property to read. + /// true if the property has a value, false if it doesn't. + /// + /// Pre-Condition: JsonNodeType.PrimitiveValue: propertyWithValue is true and the reader is positioned on the first node of the property value. + /// JsonNodeType.StartObject: + /// JsonNodeType.StartArray: + /// JsonNodeType.Property: propertyWithValue is false and the reader is positioned on the node after the property. + /// JsonNodeType.EndObject: + /// Post-Condition: JsonNodeType.Property: the next property of the resource + /// JsonNodeType.EndObject: the end-object node of the resource + /// + /// A nested resource info instance if the property read is a nested resource info which should be reported to the caller. + /// Otherwise null if the property was either ignored or read and added to the list of properties on the resource. + private ODataJsonLightReaderNestedInfo ReadUndeclaredProperty(IODataJsonLightReaderResourceState resourceState, string propertyName, bool propertyWithValue) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); +#if DEBUG + if (propertyWithValue) + { + this.AssertJsonCondition(JsonNodeType.PrimitiveValue, JsonNodeType.Property, JsonNodeType.StartObject, JsonNodeType.StartArray); + } + else + { + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } +#endif + // Undeclared property + // Detect whether it's a link property or value property. + // Link properties are stream properties and deferred links. + var odataPropertyAnnotations = resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(propertyName); + object propertyAnnotationValue; + + // If the property has 'odata.mediaEditLink', 'odata.mediaReadLink', 'odata.mediaContentType' or 'odata.mediaEtag' annotation, read it as a stream property + if (odataPropertyAnnotations.TryGetValue(ODataAnnotationNames.ODataMediaEditLink, out propertyAnnotationValue) || + odataPropertyAnnotations.TryGetValue(ODataAnnotationNames.ODataMediaReadLink, out propertyAnnotationValue) || + odataPropertyAnnotations.TryGetValue(ODataAnnotationNames.ODataMediaContentType, out propertyAnnotationValue) || + odataPropertyAnnotations.TryGetValue(ODataAnnotationNames.ODataMediaETag, out propertyAnnotationValue)) + { + // Add the stream reference property + ODataStreamReferenceValue streamReferenceValue = this.ReadStreamPropertyValue(resourceState, propertyName); + AddResourceProperty(resourceState, propertyName, streamReferenceValue); + + if (propertyWithValue) + { + ODataStreamPropertyInfo propertyInfo = this.ReadStreamPropertyInfo(resourceState, propertyName); + if (!IsJsonStream(propertyInfo)) + { + // Not a JSON Stream, so skip over property name in JSON reader + this.JsonReader.Read(); + } + + return new ODataJsonLightReaderNestedPropertyInfo(propertyInfo, null); + } + + return null; + } + + // It's not a JSON stream, so skip the property name. + // If the property does not have a value we will have already skipped the name + if (propertyWithValue) + { + this.JsonReader.Read(); + } + + // If the property has 'odata.navigationLink' or 'odata.associationLink' annotation, read it as a navigation property + if (odataPropertyAnnotations.TryGetValue(ODataAnnotationNames.ODataNavigationLinkUrl, out propertyAnnotationValue) || + odataPropertyAnnotations.TryGetValue(ODataAnnotationNames.ODataAssociationLinkUrl, out propertyAnnotationValue)) + { + // Read it as a deferred link - we never read the expanded content. + ODataJsonLightReaderNestedResourceInfo navigationLinkInfo = ReadDeferredNestedResourceInfo(resourceState, propertyName, /*navigationProperty*/ null); + resourceState.PropertyAndAnnotationCollector.ValidatePropertyUniquenessOnNestedResourceInfoStart(navigationLinkInfo.NestedResourceInfo); + + // If the property is expanded, ignore the content if we're asked to do so. + if (propertyWithValue) + { + ValidateExpandedNestedResourceInfoPropertyValue(this.JsonReader, null, propertyName); + + // Since we marked the nested resource info as deferred the reader will not try to read its content + // instead it will behave as if it was a real deferred link (without a property value). + // So skip the value here to move to the next property in the payload, which will look exactly the same + // as if the nested resource info was deferred. + this.JsonReader.SkipValue(); + } + + return navigationLinkInfo; + } + + + if (resourceState.ResourceType.IsOpen) + { + // Open property - read it as such. + ODataJsonLightReaderNestedInfo nestedResourceInfo = + this.InnerReadUndeclaredProperty(resourceState, resourceState.ResourceType, propertyName, propertyWithValue); + return nestedResourceInfo; + } + + // Property without a value can't be ignored if we don't know what it is. + if (!propertyWithValue) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_PropertyWithoutValueWithUnknownType(propertyName)); + } + + // Validate that the property doesn't have unrecognized annotations + // We ignore the type name since we might not have the full model and thus might not be able to resolve it correctly. + ValidateDataPropertyTypeNameAnnotation(resourceState.PropertyAndAnnotationCollector, propertyName); + + if (!this.MessageReaderSettings.ThrowOnUndeclaredPropertyForNonOpenType) + { + bool isTopLevelPropertyValue = false; + ODataJsonLightReaderNestedResourceInfo nestedResourceInfo = + this.InnerReadUndeclaredProperty(resourceState, propertyName, isTopLevelPropertyValue); + return nestedResourceInfo; + } + else + { + Debug.Assert( + this.MessageReaderSettings.ThrowOnUndeclaredPropertyForNonOpenType, + "this.MessageReaderSettings.ThrowOnUndeclaredPropertyForNonOpenType"); + } + + return null; + } + + /// + /// Reads a stream property value from the property annotations. + /// + /// The state of the reader for resource to read. + /// The name of the stream property to read the value for. + /// The newly created stream reference value. + private ODataStreamReferenceValue ReadStreamPropertyValue(IODataJsonLightReaderResourceState resourceState, string streamPropertyName) + { + ODataStreamReferenceValue streamReferenceValue = new ODataStreamReferenceValue(); + ReadStreamInfo(streamReferenceValue, resourceState, streamPropertyName); + ODataResourceMetadataBuilder builder = + this.MetadataContext.GetResourceMetadataBuilderForReader(resourceState, + this.JsonLightInputContext.ODataSimplifiedOptions.EnableReadingKeyAsSegment, + /*isDelta*/ false); + + // Note that we set the metadata builder even when streamProperty is null, which is the case when the stream property is undeclared. + // For undeclared stream properties, we will apply conventional metadata evaluation just as declared stream properties. + streamReferenceValue.SetMetadataBuilder(builder, streamPropertyName); + + return streamReferenceValue; + } + + /// + /// Reads a stream property info from the property annotations. + /// + /// The state of the reader for resource to read. + /// The name of the stream property to read the value for. + /// The newly created stream reference value. + private ODataStreamPropertyInfo ReadStreamPropertyInfo(IODataJsonLightReaderResourceState resourceState, string streamPropertyName) + { + ODataStreamPropertyInfo streamInfo = new ODataStreamPropertyInfo + { + Name = streamPropertyName, + }; + + ReadStreamInfo(streamInfo, resourceState, streamPropertyName); + ODataResourceMetadataBuilder builder = + this.MetadataContext.GetResourceMetadataBuilderForReader(resourceState, + this.JsonLightInputContext.ODataSimplifiedOptions.EnableReadingKeyAsSegment, + /*isDelta*/ false); + + // Note that we set the metadata builder even when streamProperty is null, which is the case when the stream property is undeclared. + // For undeclared stream properties, we will apply conventional metadata evaluation just as declared stream properties. + streamInfo.SetMetadataBuilder(builder, streamPropertyName); + + return streamInfo; + } + + /// + /// Populates StreamInfo from the property annotations. + /// + /// The stream info to populate. + /// The state of the reader for resource to read. + /// The name of the stream property to read the value for. + private void ReadStreamInfo(IODataStreamReferenceInfo streamInfo, IODataJsonLightReaderResourceState resourceState, string streamPropertyName) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(!string.IsNullOrEmpty(streamPropertyName), "!string.IsNullOrEmpty(streamPropertyName)"); + + foreach (var propertyAnnotation + in resourceState.PropertyAndAnnotationCollector.GetODataPropertyAnnotations(streamPropertyName)) + { + switch (propertyAnnotation.Key) + { + case ODataAnnotationNames.ODataMediaEditLink: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.mediaEditLink annotation should have been parsed as a non-null Uri."); + streamInfo.EditLink = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataMediaReadLink: + Debug.Assert(propertyAnnotation.Value is Uri && propertyAnnotation.Value != null, "The odata.mediaReadLink annotation should have been parsed as a non-null Uri."); + streamInfo.ReadLink = (Uri)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataMediaETag: + Debug.Assert(propertyAnnotation.Value is string && propertyAnnotation.Value != null, "The odata.mediaEtag annotation should have been parsed as a non-null string."); + streamInfo.ETag = (string)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataMediaContentType: + Debug.Assert(propertyAnnotation.Value is string && propertyAnnotation.Value != null, "The odata.mediaContentType annotation should have been parsed as a non-null string."); + streamInfo.ContentType = (string)propertyAnnotation.Value; + break; + + case ODataAnnotationNames.ODataType: + Debug.Assert(((string)propertyAnnotation.Value).Contains("Stream"), "Non-stream type annotation on stream property"); + break; + + default: + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_UnexpectedStreamPropertyAnnotation(streamPropertyName, propertyAnnotation.Key)); + } + } + + // Streams in requests cannot contain links or etags + if (!this.ReadingResponse) + { + if (streamInfo.ETag != null || streamInfo.EditLink != null || streamInfo.ReadLink != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_StreamPropertyInRequest(streamPropertyName)); + } + } + } + + /// + /// Reads one operation for the resource being read. + /// + /// The Json operation deserializer context. + /// The state of the reader for resource to read. + /// The name of the metadata reference property being read. + /// true if the operation value is inside an array, i.e. multiple targets for the operation; false otherwise. + /// + /// Pre-Condition: JsonNodeType.StartObject: first node of the operation value. + /// Post-Condition: JsonNodeType.Property: the property after the current operation being read when there is one target for the operation. + /// JsonNodeType.StartObject: the first node of the next operation value when there are multiple targets for the operation. + /// JsonNodeType.EndArray: the end-array of the operation values when there are multiple target for the operation. + /// + private void ReadSingleOperationValue(IODataJsonOperationsDeserializerContext readerContext, IODataJsonLightReaderResourceState resourceState, string metadataReferencePropertyName, bool insideArray) + { + Debug.Assert(readerContext != null, "readerContext != null"); + Debug.Assert(!string.IsNullOrEmpty(metadataReferencePropertyName), "!string.IsNullOrEmpty(metadataReferencePropertyName)"); + Debug.Assert(ODataJsonLightUtils.IsMetadataReferenceProperty(metadataReferencePropertyName), "ODataJsonLightReaderUtils.IsMetadataReferenceProperty(metadataReferencePropertyName)"); + + if (readerContext.JsonReader.NodeType != JsonNodeType.StartObject) + { + throw new ODataException(ODataErrorStrings.ODataJsonOperationsDeserializerUtils_OperationsPropertyMustHaveObjectValue(metadataReferencePropertyName, readerContext.JsonReader.NodeType)); + } + + // read over the start-object node of the metadata object for the operations + readerContext.JsonReader.ReadStartObject(); + + var operation = this.CreateODataOperationAndAddToEntry(readerContext, metadataReferencePropertyName); + + // Ignore the unrecognized operation. + if (operation == null) + { + while (readerContext.JsonReader.NodeType == JsonNodeType.Property) + { + readerContext.JsonReader.ReadPropertyName(); + readerContext.JsonReader.SkipValue(); + } + + readerContext.JsonReader.ReadEndObject(); + return; + } + + Debug.Assert(operation.Metadata != null, "operation.Metadata != null"); + + while (readerContext.JsonReader.NodeType == JsonNodeType.Property) + { + string operationPropertyName = ODataAnnotationNames.RemoveAnnotationPrefix(readerContext.JsonReader.ReadPropertyName()); + switch (operationPropertyName) + { + case JsonConstants.ODataOperationTitleName: + if (operation.Title != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation(operationPropertyName, metadataReferencePropertyName)); + } + + string titleString = readerContext.JsonReader.ReadStringValue(JsonConstants.ODataOperationTitleName); + ODataJsonLightValidationUtils.ValidateOperationPropertyValueIsNotNull(titleString, operationPropertyName, metadataReferencePropertyName); + operation.Title = titleString; + break; + + case JsonConstants.ODataOperationTargetName: + if (operation.Target != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation(operationPropertyName, metadataReferencePropertyName)); + } + + string targetString = readerContext.JsonReader.ReadStringValue(JsonConstants.ODataOperationTargetName); + ODataJsonLightValidationUtils.ValidateOperationPropertyValueIsNotNull(targetString, operationPropertyName, metadataReferencePropertyName); + operation.Target = readerContext.ProcessUriFromPayload(targetString); + break; + + default: + // skip over all unknown properties and read the next property or + // the end of the metadata for the current propertyName + readerContext.JsonReader.SkipValue(); + break; + } + } + + if (operation.Target == null && insideArray) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_OperationMissingTargetProperty(metadataReferencePropertyName)); + } + + // read the end-object node of the target / title pair + readerContext.JsonReader.ReadEndObject(); + + // Sets the metadata builder to evaluate by convention any operation property that's not on the wire. + // Note we must only set this after the operation is read from the wire since we lose the ability to tell + // what was on the wire and what is being dynamically computed. + this.SetMetadataBuilder(resourceState, operation); + } + + /// + /// Reads one operation for the resource set being read. + /// + /// The resource set to read. + /// The name of the metadata reference property being read. + /// true if the operation value is inside an array, i.e. multiple targets for the operation; false otherwise. + private void ReadSingleOperationValue(ODataResourceSet resourceSet, string metadataReferencePropertyName, bool insideArray) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + Debug.Assert(!string.IsNullOrEmpty(metadataReferencePropertyName), "!string.IsNullOrEmpty(metadataReferencePropertyName)"); + Debug.Assert(ODataJsonLightUtils.IsMetadataReferenceProperty(metadataReferencePropertyName), "ODataJsonLightReaderUtils.IsMetadataReferenceProperty(metadataReferencePropertyName)"); + + if (this.JsonReader.NodeType != JsonNodeType.StartObject) + { + throw new ODataException(ODataErrorStrings.ODataJsonOperationsDeserializerUtils_OperationsPropertyMustHaveObjectValue(metadataReferencePropertyName, this.JsonReader.NodeType)); + } + + // read over the start-object node of the metadata object for the operations + this.JsonReader.ReadStartObject(); + + var operation = this.CreateODataOperationAndAddToResourceSet(resourceSet, metadataReferencePropertyName); + + // Ignore the unrecognized operation. + if (operation == null) + { + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.JsonReader.ReadPropertyName(); + this.JsonReader.SkipValue(); + } + + this.JsonReader.ReadEndObject(); + return; + } + + Debug.Assert(operation.Metadata != null, "operation.Metadata != null"); + + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + string operationPropertyName = ODataAnnotationNames.RemoveAnnotationPrefix(this.JsonReader.ReadPropertyName()); + switch (operationPropertyName) + { + case JsonConstants.ODataOperationTitleName: + if (operation.Title != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation(operationPropertyName, metadataReferencePropertyName)); + } + + string titleString = this.JsonReader.ReadStringValue(JsonConstants.ODataOperationTitleName); + ODataJsonLightValidationUtils.ValidateOperationPropertyValueIsNotNull(titleString, operationPropertyName, metadataReferencePropertyName); + operation.Title = titleString; + break; + + case JsonConstants.ODataOperationTargetName: + if (operation.Target != null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation(operationPropertyName, metadataReferencePropertyName)); + } + + string targetString = this.JsonReader.ReadStringValue(JsonConstants.ODataOperationTargetName); + ODataJsonLightValidationUtils.ValidateOperationPropertyValueIsNotNull(targetString, operationPropertyName, metadataReferencePropertyName); + operation.Target = this.ProcessUriFromPayload(targetString); + break; + + default: + // skip over all unknown properties and read the next property or + // the end of the metadata for the current propertyName + this.JsonReader.SkipValue(); + break; + } + } + + if (operation.Target == null && insideArray) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_OperationMissingTargetProperty(metadataReferencePropertyName)); + } + + // read the end-object node of the target / title pair + this.JsonReader.ReadEndObject(); + } + + /// + /// Sets the metadata builder for the operation. + /// + /// The state of the reader for resource to read. + /// The operation to set the metadata builder on. + private void SetMetadataBuilder(IODataJsonLightReaderResourceState resourceState, ODataOperation operation) + { + ODataResourceMetadataBuilder builder = + this.MetadataContext.GetResourceMetadataBuilderForReader(resourceState, + this.JsonLightInputContext.ODataSimplifiedOptions.EnableReadingKeyAsSegment, + /*isDelta*/ false); + operation.SetMetadataBuilder(builder, this.ContextUriParseResult.MetadataDocumentUri); + } + + /// + /// Creates a new instance of ODataAction or ODataFunction for the . + /// + /// The Json operation deserializer context. + /// The name of the metadata reference property being read. + /// A new instance of ODataAction or ODataFunction for the . + private ODataOperation CreateODataOperationAndAddToEntry(IODataJsonOperationsDeserializerContext readerContext, string metadataReferencePropertyName) + { + string fullyQualifiedOperationName = ODataJsonLightUtils.GetUriFragmentFromMetadataReferencePropertyName(this.ContextUriParseResult.MetadataDocumentUri, metadataReferencePropertyName); + IEdmOperation firstActionOrFunction = this.JsonLightInputContext.Model.ResolveOperations(fullyQualifiedOperationName).FirstOrDefault(); + + bool isAction; + + if (firstActionOrFunction == null) + { + // Ignore the unknown function/action. + return null; + } + + var operation = ODataJsonLightUtils.CreateODataOperation(this.ContextUriParseResult.MetadataDocumentUri, metadataReferencePropertyName, firstActionOrFunction, out isAction); + + if (isAction) + { + readerContext.AddActionToResource((ODataAction)operation); + } + else + { + readerContext.AddFunctionToResource((ODataFunction)operation); + } + + return operation; + } + + /// + /// Creates a new instance of ODataAction or ODataFunction for the . + /// + /// The resource set to add the action or function . + /// The name of the metadata reference property being read. + /// A new instance of ODataAction or ODataFunction for the . + private ODataOperation CreateODataOperationAndAddToResourceSet(ODataResourceSet resourceSet, string metadataReferencePropertyName) + { + string fullyQualifiedOperationName = ODataJsonLightUtils.GetUriFragmentFromMetadataReferencePropertyName(this.ContextUriParseResult.MetadataDocumentUri, metadataReferencePropertyName); + IEdmOperation firstActionOrFunction = this.JsonLightInputContext.Model.ResolveOperations(fullyQualifiedOperationName).FirstOrDefault(); + + bool isAction; + + if (firstActionOrFunction == null) + { + // Ignore the unknown function/action. + return null; + } + + var operation = ODataJsonLightUtils.CreateODataOperation(this.ContextUriParseResult.MetadataDocumentUri, metadataReferencePropertyName, firstActionOrFunction, out isAction); + + if (isAction) + { + resourceSet.AddAction((ODataAction)operation); + } + else + { + resourceSet.AddFunction((ODataFunction)operation); + } + + return operation; + } + + /// + /// Read the metadata reference property value for the resource being read. + /// + /// The state of the reader for resource to read. + /// The name of the metadata reference property being read. + /// + /// Pre-Condition: JsonNodeType.Property: first node of the metadata reference property's value. Currently + /// actions and functions are the only supported metadata reference property, + /// we will throw if this is not a start object or start array node. + /// Post-Condition: JsonNodeType.Property: the property after the annotation value + /// JsonNodeType.EndObject: the end-object of the resource + /// + private void ReadMetadataReferencePropertyValue(IODataJsonLightReaderResourceState resourceState, string metadataReferencePropertyName) + { + Debug.Assert(resourceState != null, "resourceState != null"); + Debug.Assert(resourceState.Resource != null, "resourceState.Resource != null"); + Debug.Assert(!string.IsNullOrEmpty(metadataReferencePropertyName), "!string.IsNullOrEmpty(metadataReferencePropertyName)"); + Debug.Assert(metadataReferencePropertyName.IndexOf(ODataConstants.ContextUriFragmentIndicator) > -1, "metadataReferencePropertyName.IndexOf(JsonLightConstants.ContextUriFragmentIndicator) > -1"); + this.JsonReader.AssertNotBuffering(); + + this.ValidateCanReadMetadataReferenceProperty(); + + // Validate that the property name is a valid absolute URI or a valid URI fragment. + ODataJsonLightValidationUtils.ValidateMetadataReferencePropertyName(this.ContextUriParseResult.MetadataDocumentUri, metadataReferencePropertyName); + + IODataJsonOperationsDeserializerContext readerContext = new OperationsDeserializerContext(resourceState.Resource, this); + + bool insideArray = false; + if (readerContext.JsonReader.NodeType == JsonNodeType.StartArray) + { + readerContext.JsonReader.ReadStartArray(); + insideArray = true; + } + + do + { + this.ReadSingleOperationValue(readerContext, resourceState, metadataReferencePropertyName, insideArray); + } + while (insideArray && readerContext.JsonReader.NodeType != JsonNodeType.EndArray); + + if (insideArray) + { + readerContext.JsonReader.ReadEndArray(); + } + + this.JsonReader.AssertNotBuffering(); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } + + /// + /// Read the metadata reference property value for the resource set being read. + /// + /// The resource set to read. + /// The name of the metadata reference property being read. + private void ReadMetadataReferencePropertyValue(ODataResourceSet resourceSet, string metadataReferencePropertyName) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + Debug.Assert(!string.IsNullOrEmpty(metadataReferencePropertyName), "!string.IsNullOrEmpty(metadataReferencePropertyName)"); + Debug.Assert(metadataReferencePropertyName.IndexOf(ODataConstants.ContextUriFragmentIndicator) > -1, "metadataReferencePropertyName.IndexOf(JsonLightConstants.ContextUriFragmentIndicator) > -1"); + this.JsonReader.AssertNotBuffering(); + + this.ValidateCanReadMetadataReferenceProperty(); + + // Validate that the property name is a valid absolute URI or a valid URI fragment. + ODataJsonLightValidationUtils.ValidateMetadataReferencePropertyName(this.ContextUriParseResult.MetadataDocumentUri, metadataReferencePropertyName); + + bool insideArray = false; + if (this.JsonReader.NodeType == JsonNodeType.StartArray) + { + this.JsonReader.ReadStartArray(); + insideArray = true; + } + + do + { + this.ReadSingleOperationValue(resourceSet, metadataReferencePropertyName, insideArray); + } + while (insideArray && this.JsonReader.NodeType != JsonNodeType.EndArray); + + if (insideArray) + { + this.JsonReader.ReadEndArray(); + } + + this.JsonReader.AssertNotBuffering(); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + } + + /// + /// Validates that we can read metadata reference property. + /// + private void ValidateCanReadMetadataReferenceProperty() + { + if (!this.ReadingResponse) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightResourceDeserializer_MetadataReferencePropertyInRequest); + } + } + + /// + /// Operations deserializer context to pass to JSON operations reader. + /// + private sealed class OperationsDeserializerContext : IODataJsonOperationsDeserializerContext + { + /// + /// The resource to add operations to. + /// + private ODataResourceBase resource; + + /// + /// The deserializer to use. + /// + private ODataJsonLightResourceDeserializer jsonLightResourceDeserializer; + + /// + /// Constructor. + /// + /// The resource to add operations to. + /// The deserializer to use. + public OperationsDeserializerContext(ODataResourceBase resource, ODataJsonLightResourceDeserializer jsonLightResourceDeserializer) + { + Debug.Assert(resource != null, "resource != null"); + Debug.Assert(jsonLightResourceDeserializer != null, "jsonLightResourceDeserializer != null"); + + this.resource = resource; + this.jsonLightResourceDeserializer = jsonLightResourceDeserializer; + } + + /// + /// The JSON reader to read the operations value from. + /// + public IJsonReader JsonReader + { + get + { + return this.jsonLightResourceDeserializer.JsonReader; + } + } + + /// + /// Given a URI from the payload, this method will try to make it absolute, or fail otherwise. + /// + /// The URI string from the payload to process. + /// An absolute URI to report. + public Uri ProcessUriFromPayload(string uriFromPayload) + { + return this.jsonLightResourceDeserializer.ProcessUriFromPayload(uriFromPayload); + } + + /// + /// Adds the specified action to the current resource. + /// + /// The action whcih is fully populated with the data from the payload. + public void AddActionToResource(ODataAction action) + { + Debug.Assert(action != null, "action != null"); + this.resource.AddAction(action); + } + + /// + /// Adds the specified function to the current resource. + /// + /// The function whcih is fully populated with the data from the payload. + public void AddFunctionToResource(ODataFunction function) + { + Debug.Assert(function != null, "function != null"); + this.resource.AddFunction(function); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceSerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceSerializer.cs new file mode 100644 index 0000000..2a2c440 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceSerializer.cs @@ -0,0 +1,509 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Json; + +namespace Microsoft.OData.JsonLight +{ + /// + /// OData JsonLight serializer for resources and resource sets. + /// + internal sealed class ODataJsonLightResourceSerializer : ODataJsonLightPropertySerializer + { + /// + /// Constructor. + /// + /// The output context to write to. + internal ODataJsonLightResourceSerializer(ODataJsonLightOutputContext jsonLightOutputContext) + : base(jsonLightOutputContext, /*initContextUriBuilder*/ true) + { + } + + /// + /// Gets the base Uri of the metadata document uri, if it has been set. + /// + private Uri MetadataDocumentBaseUri + { + get + { + // Note: If we are in no-metadata mode or serializing a request, we don't require the MetadataDocumentUri to be set. + return this.JsonLightOutputContext.MessageWriterSettings.MetadataDocumentUri; + } + } + + /// + /// Writes the metadata properties for a resource set which can only occur at the start. + /// + /// The resource set for which to write the metadata properties. + /// The name of the property for which to write the resource set. + /// The expected resource type name of the items in the resource set. + /// true if the resource set is for an undeclared property + internal void WriteResourceSetStartMetadataProperties(ODataResourceSet resourceSet, string propertyName, string expectedResourceTypeName, bool isUndeclared) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + // Write the "@odata.type": "#typename" + string typeName = this.JsonLightOutputContext.TypeNameOracle.GetResourceSetTypeNameForWriting( + expectedResourceTypeName, + resourceSet, + isUndeclared); + + if (typeName != null && !typeName.Contains(ODataConstants.ContextUriFragmentUntyped)) + { + if (propertyName == null) + { + this.ODataAnnotationWriter.WriteODataTypeInstanceAnnotation(typeName); + } + else + { + this.ODataAnnotationWriter.WriteODataTypePropertyAnnotation(propertyName, typeName); + } + } + } + + /// + /// Writes the metadata properties for a resource which can only occur at the start. + /// + /// The resource state for which to write the metadata properties. + internal void WriteResourceStartMetadataProperties(IODataJsonLightWriterResourceState resourceState) + { + Debug.Assert(resourceState != null, "resourceState != null"); + + ODataResourceBase resource = resourceState.Resource; + + // expectedResourceTypeName : if expected is of base type. but the resource real type is derived, + // we need to set the resource type name. + // Writing response: expected type info can be identical with context uri info. + // Writing request: contextUri may not be provided, we always use the type from metadata info. + // From model: if the resource can be found in model. + // From serializationInfo: if user set the serializationInfo for the resource. + string expectedResourceTypeName = null; + if (this.WritingResponse) + { + expectedResourceTypeName = resourceState.GetOrCreateTypeContext(this.WritingResponse).ExpectedResourceTypeName; + } + else + { + if (resourceState.ResourceTypeFromMetadata == null) + { + expectedResourceTypeName = resourceState.SerializationInfo == null + ? null + : resourceState.SerializationInfo.ExpectedTypeName; + } + else + { + expectedResourceTypeName = resourceState.ResourceTypeFromMetadata.FullTypeName(); + } + } + + // Write the "@odata.type": "typename" + string typeName = this.JsonLightOutputContext.TypeNameOracle.GetResourceTypeNameForWriting( + expectedResourceTypeName, + resource, + resourceState.IsUndeclared); + + if (typeName != null && !string.Equals(typeName, ODataConstants.ContextUriFragmentUntyped, StringComparison.Ordinal)) + { + this.ODataAnnotationWriter.WriteODataTypeInstanceAnnotation(typeName); + } + + // Write the "@odata.id": "Entity Id" + Uri id; + if (resource.MetadataBuilder.TryGetIdForSerialization(out id)) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataId); + if (id != null && !resource.HasNonComputedId) + { + id = this.MetadataDocumentBaseUri.MakeRelativeUri(id); + } + + this.JsonWriter.WriteValue(id == null ? null : this.UriToString(id)); + } + + // Write the "@odata.etag": "ETag value" + string etag = resource.ETag; + if (etag != null) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataETag); + this.JsonWriter.WriteValue(etag); + } + } + + /// + /// Writes the metadata properties for a resource which can occur both at the start or at the end. + /// + /// The resource state for which to write the metadata properties. + /// + /// This method will only write properties which were not written yet. + /// + internal void WriteResourceMetadataProperties(IODataJsonLightWriterResourceState resourceState) + { + Debug.Assert(resourceState != null, "resourceState != null"); + + ODataResourceBase resource = resourceState.Resource; + + // Write the "@odata.editLink": "edit-link-uri" + Uri editLinkUriValue = resource.EditLink; + if (editLinkUriValue != null && !resourceState.EditLinkWritten) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataEditLink); + this.JsonWriter.WriteValue(this.UriToString( + resource.HasNonComputedEditLink || !editLinkUriValue.IsAbsoluteUri + ? editLinkUriValue + : this.MetadataDocumentBaseUri.MakeRelativeUri(editLinkUriValue))); + resourceState.EditLinkWritten = true; + } + + // Write the "@odata.readLink": "read-link-uri". + // If readlink is identical to editlink, don't write readlink. + Uri readLinkUriValue = resource.ReadLink; + if (readLinkUriValue != null && readLinkUriValue != editLinkUriValue && !resourceState.ReadLinkWritten) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataReadLink); + this.JsonWriter.WriteValue(this.UriToString( + resource.HasNonComputedReadLink + ? readLinkUriValue + : this.MetadataDocumentBaseUri.MakeRelativeUri(readLinkUriValue))); + resourceState.ReadLinkWritten = true; + } + + // Write MLE metadata + ODataStreamReferenceValue mediaResource = resource.MediaResource; + if (mediaResource != null) + { + // Write the "@odata.mediaEditLink": "edit-link-uri" + Uri mediaEditLinkUriValue = mediaResource.EditLink; + if (mediaEditLinkUriValue != null && !resourceState.MediaEditLinkWritten) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataMediaEditLink); + this.JsonWriter.WriteValue(this.UriToString( + mediaResource.HasNonComputedEditLink + ? mediaEditLinkUriValue + : this.MetadataDocumentBaseUri.MakeRelativeUri(mediaEditLinkUriValue))); + resourceState.MediaEditLinkWritten = true; + } + + // Write the "@odata.mediaReadLink": "read-link-uri" + // If mediaReadLink is identical to mediaEditLink, don't write readlink. + Uri mediaReadLinkUriValue = mediaResource.ReadLink; + if (mediaReadLinkUriValue != null && mediaReadLinkUriValue != mediaEditLinkUriValue && !resourceState.MediaReadLinkWritten) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataMediaReadLink); + this.JsonWriter.WriteValue(this.UriToString( + mediaResource.HasNonComputedReadLink + ? mediaReadLinkUriValue + : this.MetadataDocumentBaseUri.MakeRelativeUri(mediaReadLinkUriValue))); + resourceState.MediaReadLinkWritten = true; + } + + // Write the "@odata.mediaContentType": "content/type" + string mediaContentType = mediaResource.ContentType; + if (mediaContentType != null && !resourceState.MediaContentTypeWritten) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataMediaContentType); + this.JsonWriter.WriteValue(mediaContentType); + resourceState.MediaContentTypeWritten = true; + } + + // Write the "@odata.mediaEtag": "ETAG" + string mediaETag = mediaResource.ETag; + if (mediaETag != null && !resourceState.MediaETagWritten) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataMediaETag); + this.JsonWriter.WriteValue(mediaETag); + resourceState.MediaETagWritten = true; + } + } + + // TODO: actions + // TODO: functions + // TODO: association links + } + + /// + /// Writes the metadata properties for a resource which can only occur at the end. + /// + /// The resource state for which to write the metadata properties. + /// The DuplicatePropertyNameChecker to use. + internal void WriteResourceEndMetadataProperties(IODataJsonLightWriterResourceState resourceState, IDuplicatePropertyNameChecker duplicatePropertyNameChecker) + { + Debug.Assert(resourceState != null, "resourceState != null"); + + ODataResourceBase resource = resourceState.Resource; + + // write computed navigation properties + var navigationLinkInfo = resource.MetadataBuilder.GetNextUnprocessedNavigationLink(); + while (navigationLinkInfo != null) + { + Debug.Assert(resource.MetadataBuilder != null, "resource.MetadataBuilder != null"); + navigationLinkInfo.NestedResourceInfo.MetadataBuilder = resource.MetadataBuilder; + + this.WriteNavigationLinkMetadata(navigationLinkInfo.NestedResourceInfo, duplicatePropertyNameChecker); + navigationLinkInfo = resource.MetadataBuilder.GetNextUnprocessedNavigationLink(); + } + + // write computed stream properties + ODataProperty streamProperty = resource.MetadataBuilder.GetNextUnprocessedStreamProperty(); + while (streamProperty != null) + { + this.WriteProperty(streamProperty, resourceState.ResourceType, /*isTopLevel*/ false, duplicatePropertyNameChecker, null /*metadataBuilder*/); + streamProperty = resource.MetadataBuilder.GetNextUnprocessedStreamProperty(); + } + + // write "odata.actions" metadata + IEnumerable actions = resource.Actions; + if (actions != null && actions.Any()) + { + this.WriteOperations(actions.Cast(), /*isAction*/ true); + } + + // write "odata.functions" metadata + IEnumerable functions = resource.Functions; + if (functions != null && functions.Any()) + { + this.WriteOperations(functions.Cast(), /*isAction*/ false); + } + } + + /// + /// Writes the navigation link metadata. + /// + /// The navigation link to write the metadata for. + /// The DuplicatePropertyNameChecker to use. + internal void WriteNavigationLinkMetadata(ODataNestedResourceInfo nestedResourceInfo, IDuplicatePropertyNameChecker duplicatePropertyNameChecker) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(!string.IsNullOrEmpty(nestedResourceInfo.Name), "The nested resource info Name should have been validated by now."); + Debug.Assert(duplicatePropertyNameChecker != null); + + Uri navigationLinkUrl = nestedResourceInfo.Url; + string navigationLinkName = nestedResourceInfo.Name; + Uri associationLinkUrl = nestedResourceInfo.AssociationLinkUrl; + if (associationLinkUrl != null) + { + duplicatePropertyNameChecker.ValidatePropertyOpenForAssociationLink(navigationLinkName); + this.WriteAssociationLink(nestedResourceInfo.Name, associationLinkUrl); + } + + if (navigationLinkUrl != null) + { + // The navigation link URL is a property annotation "NestedResourceInfoName@odata.navigationLinkUrl: 'url'" + this.ODataAnnotationWriter.WritePropertyAnnotationName(navigationLinkName, ODataAnnotationNames.ODataNavigationLinkUrl); + this.JsonWriter.WriteValue(this.UriToString(navigationLinkUrl)); + } + } + + /// + /// Writes the navigation link metadata. + /// + /// The navigation link to write the metadata for. + /// The contextUrl information for current element. + internal void WriteNestedResourceInfoContextUrl(ODataNestedResourceInfo nestedResourceInfo, ODataContextUrlInfo contextUrlInfo) + { + this.WriteContextUriProperty(ODataPayloadKind.Resource, () => contextUrlInfo, /* parentContextUrlInfo*/ null, nestedResourceInfo.Name); + } + + /// + /// Writes "actions" or "functions" metadata. + /// + /// The operations to write. + /// true when writing the resource's actions; false when writing the resource's functions. + internal void WriteOperations(IEnumerable operations, bool isAction) + { + // We cannot compare two URI's directly because the 'Equals' method on the 'Uri' class compares two 'Uri' instances without regard to the + // fragment part of the URI. (E.G: For 'http://someuri/index.htm#EC.action1' and http://someuri/index.htm#EC.action2', the 'Equals' method + // will return true. + IEnumerable> metadataGroups = operations.GroupBy(o => + { + // We need to validate here to ensure that the metadata is not null, otherwise call to the method 'UriToString' will throw. + ValidationUtils.ValidateOperationNotNull(o, isAction); + WriterValidationUtils.ValidateCanWriteOperation(o, this.JsonLightOutputContext.WritingResponse); + ODataJsonLightValidationUtils.ValidateOperation(this.MetadataDocumentBaseUri, o); + return this.GetOperationMetadataString(o); + }); + + foreach (IGrouping metadataGroup in metadataGroups) + { + this.WriteOperationMetadataGroup(metadataGroup); + } + } + + /// + /// Tries to writes the context URI property for delta resource/resource set/link into the payload if one is available. + /// + /// The context object to answer basic questions regarding the type of the resource. + /// The delta kind to write. + /// The parent contextUrlInfo. + /// The created context uri info. + internal ODataContextUrlInfo WriteDeltaContextUri(ODataResourceTypeContext typeContext, ODataDeltaKind kind, ODataContextUrlInfo parentContextUrlInfo = null) + { + ODataUri odataUri = this.JsonLightOutputContext.MessageWriterSettings.ODataUri; + return this.WriteContextUriProperty(ODataPayloadKind.Delta, () => ODataContextUrlInfo.Create(typeContext, this.MessageWriterSettings.Version ?? ODataVersion.V4, kind, odataUri), parentContextUrlInfo); + } + + /// + /// Tries to writes the context URI property for a resource into the payload if one is available. + /// + /// The context object to answer basic questions regarding the type of the resource. + /// The parent contextUrlInfo. + /// The created context uri info. + internal ODataContextUrlInfo WriteResourceContextUri(ODataResourceTypeContext typeContext, ODataContextUrlInfo parentContextUrlInfo = null) + { + ODataUri odataUri = this.JsonLightOutputContext.MessageWriterSettings.ODataUri; + return this.WriteContextUriProperty(ODataPayloadKind.Resource, () => ODataContextUrlInfo.Create(typeContext, this.MessageWriterSettings.Version ?? ODataVersion.V4, /* isSingle */ true, odataUri), parentContextUrlInfo); + } + + /// + /// Tries to writes the context URI property for a resource set into the payload if one is available. + /// + /// The context object to answer basic questions regarding the type of the resource set. + /// The contextUrlInfo, if the context URI was successfully written. + internal ODataContextUrlInfo WriteResourceSetContextUri(ODataResourceTypeContext typeContext) + { + ODataUri odataUri = this.JsonLightOutputContext.MessageWriterSettings.ODataUri; + return this.WriteContextUriProperty(ODataPayloadKind.ResourceSet, () => ODataContextUrlInfo.Create(typeContext, this.MessageWriterSettings.Version ?? ODataVersion.V4, /* isSingle */ false, odataUri)); + } + + /// + /// Writes an association link property annotation. + /// + /// The name of the navigation property for which to write the association link. + /// The association link URL to write. + private void WriteAssociationLink(string propertyName, Uri associationLinkUrl) + { + Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(associationLinkUrl != null, "associationLinkUrl != null"); + + // The association link URL is a property annotation "NestedResourceInfoName@odata.associationLinkUrl: 'url'" + this.ODataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataAssociationLinkUrl); + this.JsonWriter.WriteValue(this.UriToString(associationLinkUrl)); + } + + /// + /// Gets the metadata reference fragment from the operation context uri. + /// i.e. if the operation context uri is {absolute metadata document uri}#{container-qualified-operation-name}, + /// this method will return #{container-qualified-operation-name}. + /// + /// Operation in question. + /// The metadata reference fragment from the operation context uri. + private string GetOperationMetadataString(ODataOperation operation) + { + Debug.Assert(operation != null && operation.Metadata != null, "operation != null && operation.Metadata != null"); + + string operationMetadataString = UriUtils.UriToString(operation.Metadata); + Debug.Assert(ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString), "ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString)"); + + // If we don't have a metadata document URI (which is the case with nometadata mode), just write the string form of the Uri we were given. + if (this.MetadataDocumentBaseUri == null) + { + return operation.Metadata.Fragment; + } + + Debug.Assert( + !ODataJsonLightValidationUtils.IsOpenMetadataReferencePropertyName(this.MetadataDocumentBaseUri, operationMetadataString), + "Open metadata reference property is not supported, we should have thrown before this point."); + + return ODataConstants.ContextUriFragmentIndicator + ODataJsonLightUtils.GetUriFragmentFromMetadataReferencePropertyName(this.MetadataDocumentBaseUri, operationMetadataString); + } + + /// + /// Returns the target uri string from the given operation. + /// + /// Operation in question. + /// Returns the target uri string from the given operation. + private string GetOperationTargetUriString(ODataOperation operation) + { + return operation.Target == null ? null : this.UriToString(operation.Target); + } + + /// + /// Validates a group of operations with the same context Uri. + /// + /// Operations to validate. + private void ValidateOperationMetadataGroup(IGrouping operations) + { + Debug.Assert(operations != null, "operations must not be null."); + Debug.Assert(operations.Any(), "operations.Any()"); + Debug.Assert(operations.All(o => this.GetOperationMetadataString(o) == operations.Key), "The operations should be grouped by their metadata."); + + if (operations.Count() > 1 && operations.Any(o => o.Target == null)) + { + throw new ODataException(Strings.ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustSpecifyTarget(operations.Key)); + } + + foreach (IGrouping operationsByTarget in operations.GroupBy(this.GetOperationTargetUriString)) + { + if (operationsByTarget.Count() > 1) + { + throw new ODataException(Strings.ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustNotHaveDuplicateTarget(operations.Key, operationsByTarget.Key)); + } + } + } + + /// + /// Writes a group of operation (all actions or all functions) that have the same "metadata". + /// + /// + /// Expects the actions or functions scope to already be open. + /// + /// A grouping of operations that are all actions or all functions and share the same "metadata". + private void WriteOperationMetadataGroup(IGrouping operations) + { + this.ValidateOperationMetadataGroup(operations); + this.JsonLightOutputContext.JsonWriter.WriteName(operations.Key); + bool useArray = operations.Count() > 1; + if (useArray) + { + this.JsonLightOutputContext.JsonWriter.StartArrayScope(); + } + + foreach (ODataOperation operation in operations) + { + this.WriteOperation(operation); + } + + if (useArray) + { + this.JsonLightOutputContext.JsonWriter.EndArrayScope(); + } + } + + /// + /// Writes an operation (an action or a function). + /// + /// + /// Expects the write to already have written the "rel value" and opened an array. + /// + /// The operation to write. + private void WriteOperation(ODataOperation operation) + { + Debug.Assert(operation != null, "operation must not be null."); + Debug.Assert(operation.Metadata != null, "operation.Metadata != null"); + + this.JsonLightOutputContext.JsonWriter.StartObjectScope(); + + if (operation.Title != null) + { + this.JsonLightOutputContext.JsonWriter.WriteName(JsonConstants.ODataOperationTitleName); + this.JsonLightOutputContext.JsonWriter.WriteValue(operation.Title); + } + + if (operation.Target != null) + { + string targetUrlString = this.GetOperationTargetUriString(operation); + this.JsonLightOutputContext.JsonWriter.WriteName(JsonConstants.ODataOperationTargetName); + this.JsonLightOutputContext.JsonWriter.WriteValue(targetUrlString); + } + + this.JsonLightOutputContext.JsonWriter.EndObjectScope(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightSerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightSerializer.cs new file mode 100644 index 0000000..594a9b9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightSerializer.cs @@ -0,0 +1,255 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Base class for all OData JsonLight serializers. + /// + internal class ODataJsonLightSerializer : ODataSerializer + { + /// The context uri builder to use. + protected readonly ODataContextUriBuilder ContextUriBuilder; + + /// + /// The JsonLight output context to write to. + /// + private readonly ODataJsonLightOutputContext jsonLightOutputContext; + + /// + /// Instance annotation writer. + /// + private readonly SimpleLazy instanceAnnotationWriter; + + /// + /// OData annotation writer. + /// + private readonly SimpleLazy odataAnnotationWriter; + + /// + /// Set to true when odata.context is writen; set to false otherwise. + /// When value is false, all URIs writen to the payload must be absolute. + /// + private bool allowRelativeUri; + + /// + /// Constructor. + /// + /// The output context to write to. + /// Whether contextUriBuilder should be initialized. + internal ODataJsonLightSerializer(ODataJsonLightOutputContext jsonLightOutputContext, bool initContextUriBuilder = false) + : base(jsonLightOutputContext) + { + Debug.Assert(jsonLightOutputContext != null, "jsonLightOutputContext != null"); + + this.jsonLightOutputContext = jsonLightOutputContext; + this.instanceAnnotationWriter = new SimpleLazy(() => + new JsonLightInstanceAnnotationWriter(new ODataJsonLightValueSerializer(jsonLightOutputContext), jsonLightOutputContext.TypeNameOracle)); + this.odataAnnotationWriter = new SimpleLazy(() => + new JsonLightODataAnnotationWriter(jsonLightOutputContext.JsonWriter, + this.JsonLightOutputContext.OmitODataPrefix, this.MessageWriterSettings.Version)); + + if (initContextUriBuilder) + { + // DEVNOTE: grab this early so that any validation errors are thrown at creation time rather than when Write___ is called. + this.ContextUriBuilder = ODataContextUriBuilder.Create( + this.jsonLightOutputContext.MessageWriterSettings.MetadataDocumentUri, + this.jsonLightOutputContext.WritingResponse && !(this.jsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel)); + } + } + + /// + /// Returns the which is to be used to write the content of the message. + /// + internal ODataJsonLightOutputContext JsonLightOutputContext + { + get + { + return this.jsonLightOutputContext; + } + } + + /// + /// Returns the which is to be used to write the content of the message. + /// + internal IJsonWriter JsonWriter + { + get + { + return this.jsonLightOutputContext.JsonWriter; + } + } + + /// + /// Instance annotation writer. + /// + internal JsonLightInstanceAnnotationWriter InstanceAnnotationWriter + { + get + { + return this.instanceAnnotationWriter.Value; + } + } + + /// + /// OData annotation writer. + /// + internal JsonLightODataAnnotationWriter ODataAnnotationWriter + { + get + { + return this.odataAnnotationWriter.Value; + } + } + + /// + /// Writes the start of the entire JSON payload. + /// + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is an instance method for consistency with other formats.")] + internal void WritePayloadStart() + { + ODataJsonWriterUtils.StartJsonPaddingIfRequired(this.JsonWriter, this.MessageWriterSettings); + } + + /// + /// Writes the end of the entire JSON payload. + /// + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is an instance method for consistency with other formats.")] + internal void WritePayloadEnd() + { + ODataJsonWriterUtils.EndJsonPaddingIfRequired(this.JsonWriter, this.MessageWriterSettings); + } + + /// + /// Writes the context URI property and the specified value into the payload. + /// + /// The ODataPayloadKind for the context URI. + /// Function to generate contextUrlInfo. + /// The parent contextUrlInfo. + /// Property name to write contextUri on. + /// The contextUrlInfo, if the context URI was successfully written. + internal ODataContextUrlInfo WriteContextUriProperty(ODataPayloadKind payloadKind, Func contextUrlInfoGen = null, ODataContextUrlInfo parentContextUrlInfo = null, string propertyName = null) + { + if (this.jsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel) + { + return null; + } + + Uri contextUri = null; + ODataContextUrlInfo contextUrlInfo = null; + + if (contextUrlInfoGen != null) + { + contextUrlInfo = contextUrlInfoGen(); + } + + if (contextUrlInfo != null && contextUrlInfo.IsHiddenBy(parentContextUrlInfo)) + { + return null; + } + + contextUri = this.ContextUriBuilder.BuildContextUri(payloadKind, contextUrlInfo); + + if (contextUri != null) + { + if (string.IsNullOrEmpty(propertyName)) + { + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataContext); + } + else + { + this.ODataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataContext); + } + + this.JsonWriter.WritePrimitiveValue(contextUri.IsAbsoluteUri ? contextUri.AbsoluteUri : contextUri.OriginalString); + this.allowRelativeUri = true; + return contextUrlInfo; + } + + return null; + } + + /// + /// Helper method to write the data wrapper around a JSON payload. + /// + /// The action that writes the actual JSON payload that is being wrapped. + internal void WriteTopLevelPayload(Action payloadWriterAction) + { + Debug.Assert(payloadWriterAction != null, "payloadWriterAction != null"); + + this.WritePayloadStart(); + + payloadWriterAction(); + + this.WritePayloadEnd(); + } + + /// + /// Write a top-level error message. + /// + /// The error instance to write. + /// A flag indicating whether error details should be written (in debug mode only) or not. + internal void WriteTopLevelError(ODataError error, bool includeDebugInformation) + { + Debug.Assert(error != null, "error != null"); + + this.WriteTopLevelPayload(() => ODataJsonWriterUtils.WriteError(this.JsonLightOutputContext.JsonWriter, this.InstanceAnnotationWriter.WriteInstanceAnnotationsForError, error, includeDebugInformation, this.MessageWriterSettings.MessageQuotas.MaxNestingDepth, /*writingJsonLight*/ true)); + } + + /// + /// Returns the string representation of the URI + /// + /// The uri to process. + /// Returns the string representation of the URI. + internal string UriToString(Uri uri) + { + Debug.Assert(uri != null, "uri != null"); + + // Get the metadataDocumentUri directly from MessageWriterSettings and not using ContextUriBuilder because in the case of getting the service document with nometadata + // ContextUriBuilder returns null, but the metadataDocumentUri is needed to calculate Absolute Uris in the service document. In any other case jsonLightOutputContext.CreateContextUriBuilder() should be used. + Uri metadataDocumentUri = this.jsonLightOutputContext.MessageWriterSettings.MetadataDocumentUri; + + Uri resultUri; + if (this.jsonLightOutputContext.PayloadUriConverter != null) + { + // The resolver returns 'null' if no custom resolution is desired. + resultUri = this.jsonLightOutputContext.PayloadUriConverter.ConvertPayloadUri(metadataDocumentUri, uri); + if (resultUri != null) + { + return UriUtils.UriToString(resultUri); + } + } + + resultUri = uri; + if (!resultUri.IsAbsoluteUri) + { + if (!this.allowRelativeUri) + { + // TODO: Check if it is dead code to be removed. + if (metadataDocumentUri == null) + { + throw new ODataException(Strings.ODataJsonLightSerializer_RelativeUriUsedWithoutMetadataDocumentUriOrMetadata(UriUtils.UriToString(resultUri))); + } + + resultUri = UriUtils.UriToAbsoluteUri(metadataDocumentUri, uri); + } + else + { + resultUri = UriUtils.EnsureEscapedRelativeUri(resultUri); + } + } + + return UriUtils.UriToString(resultUri); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentDeserializer.cs new file mode 100644 index 0000000..53edd55 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentDeserializer.cs @@ -0,0 +1,363 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Linq; + using System.Collections.Generic; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// OData JsonLight deserializer for service documents. + /// + internal sealed class ODataJsonLightServiceDocumentDeserializer : ODataJsonLightDeserializer + { + /// + /// Constructor. + /// + /// The JsonLight input context to read from. + internal ODataJsonLightServiceDocumentDeserializer(ODataJsonLightInputContext jsonLightInputContext) + : base(jsonLightInputContext) + { + } + + /// + /// Read a service document. + /// This method reads the service document from the input and returns + /// an that represents the read service document. + /// + /// An representing the read service document. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet. + /// Post-Condition: JsonNodeType.EndOfInput + /// + internal ODataServiceDocument ReadServiceDocument() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + // Position the reader on the first node + this.ReadPayloadStart( + ODataPayloadKind.ServiceDocument, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false); + + ODataServiceDocument serviceDocument = this.ReadServiceDocumentImplementation(propertyAndAnnotationCollector); + + // Read the end of the response. + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return serviceDocument; + } + +#if PORTABLELIB + /// + /// Read a service document. + /// This method reads the service document from the input and returns + /// an that represents the read service document. + /// + /// A task which returns an representing the read service document. + /// + /// Pre-Condition: JsonNodeType.None: assumes that the JSON reader has not been used yet. + /// Post-Condition: JsonNodeType.EndOfInput + /// + internal Task ReadServiceDocumentAsync() + { + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: expected JsonNodeType.None, the reader must not have been used yet."); + this.JsonReader.AssertNotBuffering(); + + // We use this to store annotations and check for duplicate annotation names, but we don't really store properties in it. + PropertyAndAnnotationCollector propertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + // Position the reader on the first node + return this.ReadPayloadStartAsync( + ODataPayloadKind.ServiceDocument, + propertyAndAnnotationCollector, + /*isReadingNestedPayload*/false, + /*allowEmptyPayload*/false) + + .FollowOnSuccessWith(t => + { + ODataServiceDocument serviceDocument = this.ReadServiceDocumentImplementation(propertyAndAnnotationCollector); + + // Read the end of the response. + this.ReadPayloadEnd(/*isReadingNestedPayload*/ false); + + Debug.Assert(this.JsonReader.NodeType == JsonNodeType.EndOfInput, "Post-Condition: expected JsonNodeType.EndOfInput"); + this.JsonReader.AssertNotBuffering(); + + return serviceDocument; + }); + } +#endif + + /// + /// Read a service document. + /// This method reads the service document from the input and returns + /// an that represents the read service document. + /// + /// The duplicate property names checker to use for the top-level scope. + /// An representing the read service document. + /// + /// Pre-Condition: JsonNodeType.Property The property right after the context URI property. + /// JsonNodeType.EndObject The EndObject of the service document. + /// Post-Condition: Any The node after the EndObject of the service document. + /// + private ODataServiceDocument ReadServiceDocumentImplementation(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + List[] serviceDocumentElements = { null }; + + // Read all the properties in the service document object; we ignore all except 'value'. + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Property annotations are not allowed on the 'value' property, so fail if we see one. + Func readPropertyAnnotationInServiceDoc = annotationName => { throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocument(annotationName, JsonLightConstants.ODataValuePropertyName)); }; + + this.ProcessProperty( + propertyAndAnnotationCollector, + readPropertyAnnotationInServiceDoc, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocument(propertyName, JsonLightConstants.ODataValuePropertyName)); + + case PropertyParsingResult.CustomInstanceAnnotation: + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) == 0) + { + // Fail if we've already processed a 'value' property. + if (serviceDocumentElements[0] != null) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocument(JsonLightConstants.ODataValuePropertyName)); + } + + serviceDocumentElements[0] = new List(); + + // Read the value of the 'value' property. + this.JsonReader.ReadStartArray(); + PropertyAndAnnotationCollector resourceCollectionPropertyAndAnnotationCollector = this.CreatePropertyAndAnnotationCollector(); + + while (this.JsonReader.NodeType != JsonNodeType.EndArray) + { + ODataServiceDocumentElement serviceDocumentElement = this.ReadServiceDocumentElement(resourceCollectionPropertyAndAnnotationCollector); + + if (serviceDocumentElement != null) + { + serviceDocumentElements[0].Add(serviceDocumentElement); + } + + resourceCollectionPropertyAndAnnotationCollector.Reset(); + } + + this.JsonReader.ReadEndArray(); + } + else + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocument(propertyName, JsonLightConstants.ODataValuePropertyName)); + } + + break; + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(Strings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + }); + } + + if (serviceDocumentElements[0] == null) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_MissingValuePropertyInServiceDocument(JsonLightConstants.ODataValuePropertyName)); + } + + // Read over the end object (nothing else can happen after all properties have been read) + this.JsonReader.ReadEndObject(); + + return new ODataServiceDocument + { + EntitySets = new ReadOnlyEnumerable(serviceDocumentElements[0].OfType().ToList()), + FunctionImports = new ReadOnlyEnumerable(serviceDocumentElements[0].OfType().ToList()), + Singletons = new ReadOnlyEnumerable(serviceDocumentElements[0].OfType().ToList()) + }; + } + + /// + /// Reads a resource collection within a service document. + /// + /// The to use for parsing annotations within the service document element object. + /// A representing the read resource collection. + /// + /// Pre-Condition: JsonNodeType.StartObject: The beginning of the JSON object representing the service document element. + /// other: Will throw with an appropriate message on any other node type encountered. + /// Post-Condition: JsonNodeType.StartObject: The beginning of the next resource collection in the array. + /// JsonNodeType.EndArray: The end of the array. + /// other: Any other node type occuring after the end object of the current service document element. (Would be invalid). + /// + private ODataServiceDocumentElement ReadServiceDocumentElement(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + this.JsonReader.ReadStartObject(); + string[] name = { null }; + string[] url = { null }; + string[] kind = { null }; + string[] title = { null }; + + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + // OData property annotations are not supported in service document element objects. + Func propertyAnnotationValueReader = annotationName => { throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocumentElement(annotationName)); }; + + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationValueReader, + (propertyParsingResult, propertyName) => + { + if (this.JsonReader.NodeType == JsonNodeType.Property) + { + // Read over property name + this.JsonReader.Read(); + } + + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocumentElement(propertyName)); + + case PropertyParsingResult.CustomInstanceAnnotation: + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(Strings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + if (string.CompareOrdinal(JsonLightConstants.ODataServiceDocumentElementName, propertyName) == 0) + { + if (name[0] != null) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement(JsonLightConstants.ODataServiceDocumentElementName)); + } + + name[0] = this.JsonReader.ReadStringValue(); + } + else if (string.CompareOrdinal(JsonLightConstants.ODataServiceDocumentElementUrlName, propertyName) == 0) + { + if (url[0] != null) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement(JsonLightConstants.ODataServiceDocumentElementUrlName)); + } + + url[0] = this.JsonReader.ReadStringValue(); + ValidationUtils.ValidateServiceDocumentElementUrl(url[0]); + } + else if (string.CompareOrdinal(JsonLightConstants.ODataServiceDocumentElementKind, propertyName) == 0) + { + if (kind[0] != null) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement(JsonLightConstants.ODataServiceDocumentElementKind)); + } + + kind[0] = this.JsonReader.ReadStringValue(); + } + else if (string.CompareOrdinal(JsonLightConstants.ODataServiceDocumentElementTitle, propertyName) == 0) + { + if (title[0] != null) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement(JsonLightConstants.ODataServiceDocumentElementTitle)); + } + + title[0] = this.JsonReader.ReadStringValue(); + } + else + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocumentElement(propertyName, JsonLightConstants.ODataServiceDocumentElementName, JsonLightConstants.ODataServiceDocumentElementUrlName)); + } + + break; + } + }); + } + + // URL and Name are mandatory + if (string.IsNullOrEmpty(name[0])) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_MissingRequiredPropertyInServiceDocumentElement(JsonLightConstants.ODataServiceDocumentElementName)); + } + + if (string.IsNullOrEmpty(url[0])) + { + throw new ODataException(Strings.ODataJsonLightServiceDocumentDeserializer_MissingRequiredPropertyInServiceDocumentElement(JsonLightConstants.ODataServiceDocumentElementUrlName)); + } + + ODataServiceDocumentElement serviceDocumentElement = null; + if (kind[0] != null) + { + if (kind[0].Equals(JsonLightConstants.ServiceDocumentEntitySetKindName, StringComparison.Ordinal)) + { + serviceDocumentElement = new ODataEntitySetInfo(); + } + else if (kind[0].Equals(JsonLightConstants.ServiceDocumentFunctionImportKindName, StringComparison.Ordinal)) + { + serviceDocumentElement = new ODataFunctionImportInfo(); + } + else if (kind[0].Equals(JsonLightConstants.ServiceDocumentSingletonKindName, StringComparison.Ordinal)) + { + serviceDocumentElement = new ODataSingletonInfo(); + } + } + else + { + // if not specified its an entity set. + serviceDocumentElement = new ODataEntitySetInfo(); + } + + if (serviceDocumentElement != null) + { + serviceDocumentElement.Url = this.ProcessUriFromPayload(url[0]); + serviceDocumentElement.Name = name[0]; + serviceDocumentElement.Title = title[0]; + } + + this.JsonReader.ReadEndObject(); + + return serviceDocumentElement; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentSerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentSerializer.cs new file mode 100644 index 0000000..90c2119 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightServiceDocumentSerializer.cs @@ -0,0 +1,136 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + #endregion Namespaces + + /// + /// OData JsonLight serializer for service documents. + /// + internal sealed class ODataJsonLightServiceDocumentSerializer : ODataJsonLightSerializer + { + /// + /// Constructor. + /// + /// The output context to write to. + internal ODataJsonLightServiceDocumentSerializer(ODataJsonLightOutputContext jsonLightOutputContext) + : base(jsonLightOutputContext, /*initContextUriBuilder*/ true) + { + } + + /// + /// Writes a service document in JsonLight format. + /// + /// The service document to write. + internal void WriteServiceDocument(ODataServiceDocument serviceDocument) + { + Debug.Assert(serviceDocument != null, "serviceDocument != null"); + + this.WriteTopLevelPayload( + () => + { + // "{" + this.JsonWriter.StartObjectScope(); + + // "@odata.context":... + this.WriteContextUriProperty(ODataPayloadKind.ServiceDocument); + + // "value": + this.JsonWriter.WriteValuePropertyName(); + + // "[" + this.JsonWriter.StartArrayScope(); + + if (serviceDocument.EntitySets != null) + { + foreach (ODataEntitySetInfo collectionInfo in serviceDocument.EntitySets) + { + this.WriteServiceDocumentElement(collectionInfo, JsonLightConstants.ServiceDocumentEntitySetKindName); + } + } + + if (serviceDocument.Singletons != null) + { + foreach (ODataSingletonInfo singletonInfo in serviceDocument.Singletons) + { + this.WriteServiceDocumentElement(singletonInfo, JsonLightConstants.ServiceDocumentSingletonKindName); + } + } + + HashSet functionImportsWritten = new HashSet(StringComparer.Ordinal); + + if (serviceDocument.FunctionImports != null) + { + foreach (ODataFunctionImportInfo functionImportInfo in serviceDocument.FunctionImports) + { + if (functionImportInfo == null) + { + throw new ODataException(Strings.ValidationUtils_WorkspaceResourceMustNotContainNullItem); + } + + if (!functionImportsWritten.Contains(functionImportInfo.Name)) + { + functionImportsWritten.Add(functionImportInfo.Name); + this.WriteServiceDocumentElement(functionImportInfo, JsonLightConstants.ServiceDocumentFunctionImportKindName); + } + } + } + + // "]" + this.JsonWriter.EndArrayScope(); + + // "}" + this.JsonWriter.EndObjectScope(); + }); + } + + /// + /// Writes a element (EntitySet, Singleton or FunctionImport) in service document. + /// + /// The element in service document to write. + /// Kind of the service document element, optional for entityset's must for FunctionImport and Singleton. + private void WriteServiceDocumentElement(ODataServiceDocumentElement serviceDocumentElement, string kind) + { + // validate that the resource has a non-null url. + ValidationUtils.ValidateServiceDocumentElement(serviceDocumentElement, ODataFormat.Json); + + // "{" + this.JsonWriter.StartObjectScope(); + + // "name": ... + this.JsonWriter.WriteName(JsonLightConstants.ODataServiceDocumentElementName); + this.JsonWriter.WriteValue(serviceDocumentElement.Name); + + // Do not write title if it is null or empty, or if title is the same as name. + if (!string.IsNullOrEmpty(serviceDocumentElement.Title) && !serviceDocumentElement.Title.Equals(serviceDocumentElement.Name, StringComparison.Ordinal)) + { + // "title": ... + this.JsonWriter.WriteName(JsonLightConstants.ODataServiceDocumentElementTitle); + this.JsonWriter.WriteValue(serviceDocumentElement.Title); + } + + // Not always writing because it can be null if an ODataEntitySetInfo, not necessary to write this. Required for the others though. + if (kind != null) + { + // "kind": ... + this.JsonWriter.WriteName(JsonLightConstants.ODataServiceDocumentElementKind); + this.JsonWriter.WriteValue(kind); + } + + // "url": ... + this.JsonWriter.WriteName(JsonLightConstants.ODataServiceDocumentElementUrlName); + this.JsonWriter.WriteValue(this.UriToString(serviceDocumentElement.Url)); + + // "}" + this.JsonWriter.EndObjectScope(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightUtils.cs new file mode 100644 index 0000000..791b376 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightUtils.cs @@ -0,0 +1,156 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + + /// + /// Helper methods used by the OData reader for the JsonLight format. + /// + internal static class ODataJsonLightUtils + { + /// + /// The set of characters to trim from the parameters of an operation. Contains '(' and ')'. + /// + private static readonly char[] CharactersToTrimFromParameters = new[] { JsonLightConstants.FunctionParameterStart, JsonLightConstants.FunctionParameterEnd }; + + /// + /// Determines if the specified property name is a name of a metadata reference property. + /// + /// The name of the property. + /// true if is a name of a metadata reference property, false otherwise. + internal static bool IsMetadataReferenceProperty(string propertyName) + { + Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + return propertyName.IndexOf(ODataConstants.ContextUriFragmentIndicator) >= 0; + } + + /// + /// Gets the fully qualified operation import name from the metadata reference property name. + /// + /// The metadata document Uri. + /// The metadata reference property name. + /// The parameter names, if any are present in the given string. + /// The fully qualified operation import name. + internal static string GetFullyQualifiedOperationName(Uri metadataDocumentUri, string metadataReferencePropertyName, out string parameterNames) + { + Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); + Debug.Assert(!String.IsNullOrEmpty(metadataReferencePropertyName), "!string.IsNullOrEmpty(metadataReferencePropertyName)"); + + string fullyQualifiedFunctionImportName = GetUriFragmentFromMetadataReferencePropertyName(metadataDocumentUri, metadataReferencePropertyName); + parameterNames = null; + + int indexOfLeftParenthesis = fullyQualifiedFunctionImportName.IndexOf(JsonLightConstants.FunctionParameterStart); + if (indexOfLeftParenthesis > -1) + { + string parameters = fullyQualifiedFunctionImportName.Substring(indexOfLeftParenthesis + 1); + fullyQualifiedFunctionImportName = fullyQualifiedFunctionImportName.Substring(0, indexOfLeftParenthesis); + + parameterNames = parameters.Trim(CharactersToTrimFromParameters); + } + + return fullyQualifiedFunctionImportName; + } + + /// + /// Gets the Uri fragment from the metadata reference property name. + /// + /// The metadata document Uri. + /// The metadata reference property name. + /// The Uri fragment which corresponds to action/function names, etc. + internal static string GetUriFragmentFromMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName) + { + Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); + Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + string fragment = GetAbsoluteUriFromMetadataReferencePropertyName(metadataDocumentUri, propertyName).GetComponents(UriComponents.Fragment, UriFormat.Unescaped); + Debug.Assert(!String.IsNullOrEmpty(fragment), "!string.IsNullOrEmpty(fragment)"); + return fragment; + } + + /// + /// Converts the metadata reference property name to an absolute Uri. + /// + /// The metadata document uri. + /// The metadata reference property name. + /// The absolute Uri for the metadata reference property name. + internal static Uri GetAbsoluteUriFromMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName) + { + Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); + Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + if (propertyName[0] == ODataConstants.ContextUriFragmentIndicator) + { + propertyName = UriUtils.EnsureEscapedFragment(propertyName); + return new Uri(metadataDocumentUri, propertyName); + } + + Debug.Assert(Uri.IsWellFormedUriString(propertyName, UriKind.Absolute), "The propertyName should be an absolute Uri."); + return new Uri(propertyName, UriKind.Absolute); + } + + /// + /// Calculates the metadata reference name for the given operation. When there is no overload to the function, this method will + /// return the namespace qualified operation name. When there is overload to the operation this method will + /// return FQFN([comma separated parameter type names]) to disambiguate between different overloads. + /// + /// The model of the operations. + /// The operation in question. + /// The metadata reference name for the given operation. + internal static string GetMetadataReferenceName(IEdmModel model, IEdmOperation operation) + { + Debug.Assert(operation != null, "operation != null"); + + string metadataReferenceName = operation.FullName(); + bool hasOverload = model.FindDeclaredOperations(operation.FullName()).Take(2).Count() > 1; + + if (hasOverload) + { + if (operation is IEdmFunction) + { + metadataReferenceName = operation.FullNameWithNonBindingParameters(); + } + } + + return metadataReferenceName; + } + + /// + /// Creates an ODataAction or ODataFunction from a operation import. + /// + /// The metadata document uri. + /// The metadata reference property name. + /// The operation to create the ODataOperation for. + /// true if the created ODataOperation is an ODataAction, false otherwise. + /// The created ODataAction or ODataFunction. + internal static ODataOperation CreateODataOperation(Uri metadataDocumentUri, string metadataReferencePropertyName, IEdmOperation edmOperation, out bool isAction) + { + Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); + Debug.Assert(!string.IsNullOrEmpty(metadataReferencePropertyName), "!string.IsNullOrEmpty(metadataReferencePropertyName)"); + Debug.Assert(edmOperation != null, "edmOperation != null"); + + isAction = edmOperation.IsAction(); + ODataOperation operation = isAction ? (ODataOperation)new ODataAction() : new ODataFunction(); + + // Note that the property name can be '#name' which is not a valid Uri. We need to prepend the metadata document uri in that case. + int parameterStartIndex = 0; + if (isAction && (parameterStartIndex = metadataReferencePropertyName.IndexOf(JsonLightConstants.FunctionParameterStart)) > 0) + { + metadataReferencePropertyName = metadataReferencePropertyName.Substring(0, parameterStartIndex); + } + + operation.Metadata = GetAbsoluteUriFromMetadataReferencePropertyName(metadataDocumentUri, metadataReferencePropertyName); + return operation; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValidationUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValidationUtils.cs new file mode 100644 index 0000000..701124f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValidationUtils.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Diagnostics; + #endregion Namespaces + + /// + /// Helper methods used by the OData reader for the JsonLight format. + /// + internal static class ODataJsonLightValidationUtils + { + /// + /// Validates that a string is either a valid absolute URI, or (if it begins with '#') it is a valid URI fragment. + /// + /// The metadata document uri. + /// The property name to validate. + internal static void ValidateMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName) + { + Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); + Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri"); + Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + string uriStringToValidate = propertyName; + + // If it starts with a '#', validate that the rest of the string is a valid Uri fragment. + if (propertyName[0] == ODataConstants.ContextUriFragmentIndicator) + { + // In order to use System.Uri to validate a fragement, we first prepend the metadataDocumentUri + // so that it becomes an absolute URI which we can validate with Uri.IsWellFormedUriString. + uriStringToValidate = UriUtils.UriToString(metadataDocumentUri) + UriUtils.EnsureEscapedFragment(propertyName); + } + + if (!Uri.IsWellFormedUriString(uriStringToValidate, UriKind.Absolute) || + !ODataJsonLightUtils.IsMetadataReferenceProperty(propertyName) || + propertyName[propertyName.Length - 1] == ODataConstants.ContextUriFragmentIndicator) + { + throw new ODataException(Strings.ValidationUtils_InvalidMetadataReferenceProperty(propertyName)); + } + + if (IsOpenMetadataReferencePropertyName(metadataDocumentUri, propertyName)) + { + throw new ODataException(Strings.ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported(propertyName, UriUtils.UriToString(metadataDocumentUri))); + } + } + + /// + /// Validates an operation is valid. + /// + /// The metadata document uri. + /// The operation to validate. + internal static void ValidateOperation(Uri metadataDocumentUri, ODataOperation operation) + { + Debug.Assert(operation != null, "operation != null"); + + ValidationUtils.ValidateOperationMetadataNotNull(operation); + string name = UriUtils.UriToString(operation.Metadata); + + if (metadataDocumentUri != null) + { + Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri"); + ValidateMetadataReferencePropertyName(metadataDocumentUri, name); + Debug.Assert(!IsOpenMetadataReferencePropertyName(metadataDocumentUri, name), "!IsOpenMetadataReferencePropertyName(metadataDocumentUri, name)"); + } + } + + /// + /// Determines if the specified property name is a name of an open metadata reference property. + /// + /// The metadata document uri. + /// The property name in question. + /// true if the specified property name is a name of an open metadata reference property; false otherwise. + internal static bool IsOpenMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName) + { + Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); + Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri"); + Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + // If a metadata reference property isn't based off of the known metadata document URI (for example, it points to a model on another server), + // then it must be open. It is based off the known metadata document URI if it either is a fragment (i.e., starts with a hash) or starts with the known absolute URI. + return ODataJsonLightUtils.IsMetadataReferenceProperty(propertyName) + && propertyName[0] != ODataConstants.ContextUriFragmentIndicator + && !propertyName.StartsWith(UriUtils.UriToString(metadataDocumentUri), StringComparison.OrdinalIgnoreCase); + } + + /// + /// Validates that the property in an operation (an action or a function) is valid. + /// + /// The value of the property. + /// The name of the property (used for error reporting). + /// The metadata value for the operation (used for error reporting). + internal static void ValidateOperationPropertyValueIsNotNull(object propertyValue, string propertyName, string metadata) + { + Debug.Assert(!String.IsNullOrEmpty(metadata), "!string.IsNullOrEmpty(metadata)"); + + if (propertyValue == null) + { + throw new ODataException(Strings.ODataJsonLightValidationUtils_OperationPropertyCannotBeNull(propertyName, metadata)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValueSerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValueSerializer.cs new file mode 100644 index 0000000..1c819b6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightValueSerializer.cs @@ -0,0 +1,413 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + + using System.Collections; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using Microsoft.OData.Json; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// OData JsonLight serializer for value types. + /// + internal class ODataJsonLightValueSerializer : ODataJsonLightSerializer + { + /// + /// The current recursion depth of values written by this serializer. + /// + private int recursionDepth; + + /// + /// Property serializer. + /// + private ODataJsonLightPropertySerializer propertySerializer; + + /// + /// Initializes a new instance of the class. + /// + /// The property serializer to use when writing complex values. + /// Whether contextUriBuilder should be initialized. + internal ODataJsonLightValueSerializer(ODataJsonLightPropertySerializer propertySerializer, bool initContextUriBuilder = false) + : base(propertySerializer.JsonLightOutputContext, initContextUriBuilder) + { + this.propertySerializer = propertySerializer; + } + + /// + /// Initializes a new instance of the class. + /// + /// The output context to use. + /// Whether contextUriBuilder should be initialized. + internal ODataJsonLightValueSerializer(ODataJsonLightOutputContext outputContext, bool initContextUriBuilder = false) + : base(outputContext, initContextUriBuilder) + { + } + + /// + /// Gets the property serializer. + /// + private ODataJsonLightPropertySerializer PropertySerializer + { + get + { + if (this.propertySerializer == null) + { + this.propertySerializer = new ODataJsonLightPropertySerializer(this.JsonLightOutputContext); + } + + return this.propertySerializer; + } + } + + /// + /// Writes a null value to the writer. + /// + public virtual void WriteNullValue() + { + this.JsonWriter.WriteValue((string)null); + } + + /// + /// Write enum value + /// + /// enum value + /// expected type reference + public virtual void WriteEnumValue( + ODataEnumValue value, + IEdmTypeReference expectedTypeReference) + { + if (value.Value == null) + { + this.WriteNullValue(); + } + else + { + this.JsonWriter.WritePrimitiveValue(value.Value); + } + } + + /// + /// Writes out the value of a resource (complex or entity). + /// + /// The resource (complex or entity) value to write. + /// The metadata type for the resource value. + /// true if the type name belongs to an open (dynamic) property. + /// The checker instance for duplicate property names. + /// The current recursion depth should be a value, measured by the number of resource and collection values between + /// this resource value and the top-level payload, not including this one. + [SuppressMessage("Microsoft.Naming", "CA2204:LiteralsShouldBeSpelledCorrectly", Justification = "Names are correct. String can't be localized after string freeze.")] + public virtual void WriteResourceValue( + ODataResourceValue resourceValue, + IEdmTypeReference metadataTypeReference, + bool isOpenPropertyType, + IDuplicatePropertyNameChecker duplicatePropertyNamesChecker) + { + Debug.Assert(resourceValue != null, "resourceValue != null"); + + this.IncreaseRecursionDepth(); + + // Start the object scope which will represent the entire resource instance; + this.JsonWriter.StartObjectScope(); + + string typeName = resourceValue.TypeName; + + // In requests, we allow the property type reference to be null if the type name is specified in the OM + if (metadataTypeReference == null && !this.WritingResponse && typeName == null && this.Model.IsUserModel()) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForResourceValueRequest); + } + + // Resolve the type name to the type; if no type name is specified we will use the type inferred from metadata. + IEdmStructuredTypeReference resourceValueTypeReference = + (IEdmStructuredTypeReference)TypeNameOracle.ResolveAndValidateTypeForResourceValue(this.Model, metadataTypeReference, resourceValue, isOpenPropertyType, this.WriterValidator); + Debug.Assert( + metadataTypeReference == null || resourceValueTypeReference == null || EdmLibraryExtensions.IsAssignableFrom(metadataTypeReference, resourceValueTypeReference), + "Complex property types must be the same as or inherit from the ones from metadata (unless open)."); + + typeName = this.JsonLightOutputContext.TypeNameOracle.GetValueTypeNameForWriting(resourceValue, metadataTypeReference, resourceValueTypeReference, isOpenPropertyType); + if (typeName != null) + { + this.ODataAnnotationWriter.WriteODataTypeInstanceAnnotation(typeName); + } + + // Write custom instance annotations + this.InstanceAnnotationWriter.WriteInstanceAnnotations(resourceValue.InstanceAnnotations); + + // Write the properties of the resource value as usual. Note we do not allow resource types to contain named stream properties. + this.PropertySerializer.WriteProperties( + resourceValueTypeReference == null ? null : resourceValueTypeReference.StructuredDefinition(), + resourceValue.Properties, + true /* isComplexValue */, + duplicatePropertyNamesChecker, + null); + + // End the object scope which represents the resource instance; + this.JsonWriter.EndObjectScope(); + + this.DecreaseRecursionDepth(); + } + + /// + /// Writes out the value of a collection property. + /// + /// The collection value to write. + /// The metadata type reference for the collection. + /// The value type reference for the collection. + /// Whether or not a top-level property is being written. + /// Whether or not the value is being written for a URI. + /// True if the type name belongs to an open property. + /// The current recursion depth is measured by the number of resource and collection values between + /// this one and the top-level payload, not including this one. + [SuppressMessage("Microsoft.Naming", "CA2204:LiteralsShouldBeSpelledCorrectly", Justification = "Names are correct. String can't be localized after string freeze.")] + public virtual void WriteCollectionValue(ODataCollectionValue collectionValue, IEdmTypeReference metadataTypeReference, IEdmTypeReference valueTypeReference, bool isTopLevelProperty, bool isInUri, bool isOpenPropertyType) + { + Debug.Assert(collectionValue != null, "collectionValue != null"); + Debug.Assert(!isTopLevelProperty || !isInUri, "Cannot be a top level property and in a uri"); + + this.IncreaseRecursionDepth(); + + // If the CollectionValue has type information write out the metadata and the type in it. + string typeName = collectionValue.TypeName; + + if (isTopLevelProperty) + { + Debug.Assert(metadataTypeReference == null, "Never expect a metadata type for top-level properties."); + if (typeName == null) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightValueSerializer_MissingTypeNameOnCollection); + } + } + else + { + // In requests, we allow the metadata type reference to be null if the type name is specified in the OM + if (metadataTypeReference == null && !this.WritingResponse && typeName == null && this.Model.IsUserModel()) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForCollectionValueInRequest); + } + } + + if (valueTypeReference == null) + { + valueTypeReference = TypeNameOracle.ResolveAndValidateTypeForCollectionValue(this.Model, metadataTypeReference, collectionValue, isOpenPropertyType, this.WriterValidator); + } + + bool useValueProperty = false; + if (isInUri) + { + // resolve the type name to the type; if no type name is specified we will use the + // type inferred from metadata + typeName = this.JsonLightOutputContext.TypeNameOracle.GetValueTypeNameForWriting(collectionValue, metadataTypeReference, valueTypeReference, isOpenPropertyType); + if (!string.IsNullOrEmpty(typeName)) + { + useValueProperty = true; + + // "{" + this.JsonWriter.StartObjectScope(); + this.ODataAnnotationWriter.WriteODataTypeInstanceAnnotation(typeName); + this.JsonWriter.WriteValuePropertyName(); + } + } + + // [ + // This represents the array of items in the CollectionValue + this.JsonWriter.StartArrayScope(); + + // Iterate through the CollectionValue items and write them out (treat null Items as an empty enumeration) + IEnumerable items = collectionValue.Items; + if (items != null) + { + IEdmTypeReference expectedItemTypeReference = valueTypeReference == null ? null : ((IEdmCollectionTypeReference)valueTypeReference).ElementType(); + + IDuplicatePropertyNameChecker duplicatePropertyNamesChecker = null; + foreach (object item in items) + { + ValidationUtils.ValidateCollectionItem(item, expectedItemTypeReference.IsNullable()); + + ODataResourceValue itemAsResourceValue = item as ODataResourceValue; + if (itemAsResourceValue != null) + { + if (duplicatePropertyNamesChecker == null) + { + duplicatePropertyNamesChecker = this.CreateDuplicatePropertyNameChecker(); + } + + this.WriteResourceValue( + itemAsResourceValue, + expectedItemTypeReference, + false /*isOpenPropertyType*/, + duplicatePropertyNamesChecker); + + duplicatePropertyNamesChecker.Reset(); + } + else + { + Debug.Assert(!(item is ODataCollectionValue), "!(item is ODataCollectionValue)"); + Debug.Assert(!(item is ODataStreamReferenceValue), "!(item is ODataStreamReferenceValue)"); + + // by design: collection element's type name is not written for enum or non-spatial primitive value even in case of full metadata. + // because enum and non-spatial primitive types don't have inheritance, the type of each element is the same as the item type of the collection, whose type name for spatial types in full metadata mode. + ODataEnumValue enumValue = item as ODataEnumValue; + if (enumValue != null) + { + this.WriteEnumValue(enumValue, expectedItemTypeReference); + } + else + { + ODataUntypedValue untypedValue = item as ODataUntypedValue; + if (untypedValue != null) + { + this.WriteUntypedValue(untypedValue); + } + else if (item != null) + { + this.WritePrimitiveValue(item, expectedItemTypeReference); + } + else + { + this.WriteNullValue(); + } + } + } + } + } + + // End the array scope which holds the items + this.JsonWriter.EndArrayScope(); + + if (useValueProperty) + { + this.JsonWriter.EndObjectScope(); + } + + this.DecreaseRecursionDepth(); + } + + /// + /// Writes a primitive value. + /// Uses a registered primitive type converter to write the value if one is registered for the type, otherwise directly writes the value. + /// + /// The value to write. + /// The expected type reference of the primitive value. + public virtual void WritePrimitiveValue( + object value, + IEdmTypeReference expectedTypeReference) + { + this.WritePrimitiveValue(value, null, expectedTypeReference); + } + + /// + /// Writes a primitive value. + /// + /// The value to write. + /// The actual type reference of the primitive value. + /// The expected type reference of the primitive value. + public virtual void WritePrimitiveValue( + object value, + IEdmTypeReference actualTypeReference, + IEdmTypeReference expectedTypeReference) + { + Debug.Assert(value != null, "value != null"); + + if (actualTypeReference == null) + { + // Try convert primitive values from their actual CLR types to their underlying CLR types. + value = this.Model.ConvertToUnderlyingTypeIfUIntValue(value, expectedTypeReference); + actualTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(value.GetType()); + } + + ODataPayloadValueConverter converter = this.JsonLightOutputContext.PayloadValueConverter; + + // Skip validation if user has set custom PayloadValueConverter + if (expectedTypeReference != null && converter.GetType() == typeof(ODataPayloadValueConverter)) + { + this.WriterValidator.ValidateIsExpectedPrimitiveType(value, (IEdmPrimitiveTypeReference)actualTypeReference, expectedTypeReference); + } + + value = converter.ConvertToPayloadValue(value, expectedTypeReference); + + if (actualTypeReference != null && actualTypeReference.IsSpatial()) + { + PrimitiveConverter.Instance.WriteJsonLight(value, this.JsonWriter); + } + else + { + this.JsonWriter.WritePrimitiveValue(value); + } + } + + /// + /// Writes an untyped value. + /// + /// The untyped value to write. + public virtual void WriteUntypedValue( + ODataUntypedValue value) + { + Debug.Assert(value != null, "value != null"); + + if (string.IsNullOrEmpty(value.RawValue)) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightValueSerializer_MissingRawValueOnUntyped); + } + + this.JsonWriter.WriteRawValue(value.RawValue); + } + + public virtual void WriteStreamValue(ODataBinaryStreamValue streamValue) + { + IJsonStreamWriter streamWriter = this.JsonWriter as IJsonStreamWriter; + if (streamWriter == null) + { + // write as a string + this.JsonWriter.WritePrimitiveValue(new StreamReader(streamValue.Stream).ReadToEnd()); + } + else + { + Stream stream = streamWriter.StartStreamValueScope(); + streamValue.Stream.CopyTo(stream); + stream.Flush(); + stream.Dispose(); + streamWriter.EndStreamValueScope(); + } + } + + /// + /// Asserts that the current recursion depth of values is zero. This should be true on all calls into this class from outside of this class. + /// + [Conditional("DEBUG")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "The this is needed in DEBUG build.")] + internal void AssertRecursionDepthIsZero() + { + Debug.Assert(this.recursionDepth == 0, "The current recursion depth must be 0."); + } + + /// + /// Increases the recursion depth of values by 1. This will throw if the recursion depth exceeds the current limit. + /// + private void IncreaseRecursionDepth() + { + ValidationUtils.IncreaseAndValidateRecursionDepth(ref this.recursionDepth, this.MessageWriterSettings.MessageQuotas.MaxNestingDepth); + } + + /// + /// Decreases the recursion depth of values by 1. + /// + private void DecreaseRecursionDepth() + { + Debug.Assert(this.recursionDepth > 0, "Can't decrease recursion depth below 0."); + + this.recursionDepth--; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs new file mode 100644 index 0000000..cb8e135 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs @@ -0,0 +1,2224 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Text; +#if PORTABLELIB +using System.Threading.Tasks; +#endif +using Microsoft.OData.Edm; +using Microsoft.OData.Evaluation; +using Microsoft.OData.Metadata; +using Microsoft.OData.Json; + +namespace Microsoft.OData.JsonLight +{ + /// + /// Implementation of the ODataWriter for the JsonLight format. + /// + internal sealed class ODataJsonLightWriter : ODataWriterCore + { + /// + /// The output context to write to. + /// + private readonly ODataJsonLightOutputContext jsonLightOutputContext; + + /// + /// The JsonLight resource and resource set serializer to use. + /// + private readonly ODataJsonLightResourceSerializer jsonLightResourceSerializer; + + /// + /// The JsonLight value serializer to use for primitive values in an untyped collection. + /// + private readonly ODataJsonLightValueSerializer jsonLightValueSerializer; + + /// + /// The JsonLight property serializer. + /// + private readonly ODataJsonLightPropertySerializer jsonLightPropertySerializer; + + /// + /// True if the writer was created for writing a parameter; false otherwise. + /// + private readonly bool writingParameter; + + /// + /// The underlying JSON writer. + /// + private readonly IJsonWriter jsonWriter; + + /// + /// The underlying JSON writer. + /// + private readonly IJsonStreamWriter jsonStreamWriter; + + /// + /// OData annotation writer. + /// + private readonly JsonLightODataAnnotationWriter odataAnnotationWriter; + + /// + /// Constructor. + /// + /// The output context to write to. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the writer is created for writing a resource set; false when it is created for writing a resource. + /// true if the writer is created for writing a parameter; false otherwise. + /// True if the writer is created for writing delta response; false otherwise. + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + internal ODataJsonLightWriter( + ODataJsonLightOutputContext jsonLightOutputContext, + IEdmNavigationSource navigationSource, + IEdmStructuredType resourceType, + bool writingResourceSet, + bool writingParameter = false, + bool writingDelta = false, + IODataReaderWriterListener listener = null) + : base(jsonLightOutputContext, navigationSource, resourceType, writingResourceSet, writingDelta, listener) + { + Debug.Assert(jsonLightOutputContext != null, "jsonLightOutputContext != null"); + + this.jsonLightOutputContext = jsonLightOutputContext; + this.jsonLightResourceSerializer = new ODataJsonLightResourceSerializer(this.jsonLightOutputContext); + this.jsonLightValueSerializer = new ODataJsonLightValueSerializer(this.jsonLightOutputContext); + this.jsonLightPropertySerializer = new ODataJsonLightPropertySerializer(this.jsonLightOutputContext); + + this.writingParameter = writingParameter; + this.jsonWriter = this.jsonLightOutputContext.JsonWriter; + this.jsonStreamWriter = this.jsonWriter as IJsonStreamWriter; + this.odataAnnotationWriter = new JsonLightODataAnnotationWriter(this.jsonWriter, + this.jsonLightOutputContext.OmitODataPrefix, this.jsonLightOutputContext.MessageWriterSettings.Version); + } + + /// + /// Returns the current JsonLightResourceScope. + /// + private JsonLightResourceScope CurrentResourceScope + { + get + { + JsonLightResourceScope currentJsonLightResourceScope = this.CurrentScope as JsonLightResourceScope; + Debug.Assert(currentJsonLightResourceScope != null, "Asking for JsonLightResourceScope when the current scope is not an JsonLightResourceScope."); + return currentJsonLightResourceScope; + } + } + + /// + /// Returns the current JsonLightDeletedResourceScope. + /// + private JsonLightDeletedResourceScope CurrentDeletedResourceScope + { + get + { + JsonLightDeletedResourceScope currentJsonLightDeletedResourceScope = this.CurrentScope as JsonLightDeletedResourceScope; + Debug.Assert(currentJsonLightDeletedResourceScope != null, "Asking for JsonLightResourceScope when the current scope is not an JsonLightResourceScope."); + return currentJsonLightDeletedResourceScope; + } + } + + /// + /// Returns the current JsonLightDeltaLinkScope. + /// + private JsonLightDeltaLinkScope CurrentDeltaLinkScope + { + get + { + JsonLightDeltaLinkScope jsonLightDeltaLinkScope = this.CurrentScope as JsonLightDeltaLinkScope; + Debug.Assert(jsonLightDeltaLinkScope != null, "Asking for JsonLightDeltaLinkScope when the current scope is not an JsonLightDeltaLinkScope."); + return jsonLightDeltaLinkScope; + } + } + + /// + /// Returns the current JsonLightResourceSetScope. + /// + private JsonLightResourceSetScope CurrentResourceSetScope + { + get + { + JsonLightResourceSetScope currentJsonLightResourceSetScope = this.CurrentScope as JsonLightResourceSetScope; + Debug.Assert(currentJsonLightResourceSetScope != null, "Asking for JsonResourceSetScope when the current scope is not a JsonResourceSetScope."); + return currentJsonLightResourceSetScope; + } + } + + /// + /// Returns the current JsonLightDeltaResourceSetScope. + /// + private JsonLightDeltaResourceSetScope CurrentDeltaResourceSetScope + { + get + { + JsonLightDeltaResourceSetScope currentJsonLightDeltaResourceSetScope = this.CurrentScope as JsonLightDeltaResourceSetScope; + Debug.Assert(currentJsonLightDeltaResourceSetScope != null, "Asking for JsonDeltaResourceSetScope when the current scope is not a JsonDeltaResourceSetScope."); + return currentJsonLightDeltaResourceSetScope; + } + } + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + protected override void VerifyNotDisposed() + { + this.jsonLightOutputContext.VerifyNotDisposed(); + } + + /// + /// Flush the output. + /// + protected override void FlushSynchronously() + { + this.jsonLightOutputContext.Flush(); + } + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected override Task FlushAsynchronously() + { + return this.jsonLightOutputContext.FlushAsync(); + } +#endif + + /// + /// Starts writing a payload (called exactly once before anything else) + /// + protected override void StartPayload() + { + this.jsonLightResourceSerializer.WritePayloadStart(); + } + + /// + /// Ends writing a payload (called exactly once after everything else in case of success) + /// + protected override void EndPayload() + { + this.jsonLightResourceSerializer.WritePayloadEnd(); + } + + /// + /// Place where derived writers can perform custom steps before the resource is written, at the beginning of WriteStartEntryImplementation. + /// + /// The ResourceScope. + /// Resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected override void PrepareResourceForWriteStart(ResourceScope resourceScope, ODataResource resource, bool writingResponse, SelectedPropertiesNode selectedProperties) + { + var typeContext = resourceScope.GetOrCreateTypeContext(writingResponse); + if (this.jsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel) + { + // 1. NoMetadata level: always enable its NullResourceMetadataBuilder + InnerPrepareResourceForWriteStart(resource, typeContext, selectedProperties); + } + else + { + // 2. Minimal/Full Metadata level: Use ODataConventionalEntityMetadataBuilder for entity, ODataConventionalResourceMetadataBuilder for other cases. + if (this.jsonLightOutputContext.Model.IsUserModel() || resourceScope.SerializationInfo != null) + { + InnerPrepareResourceForWriteStart(resource, typeContext, selectedProperties); + } + + // 3. Here fallback to the default NoOpResourceMetadataBuilder, when model and serializationInfo are both null. + } + } + + /// + /// Place where derived writers can perform custom steps before the deleted resource is written, at the beginning of WriteStartEntryImplementation. + /// + /// The ResourceScope. + /// Deleted resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected override void PrepareDeletedResourceForWriteStart(DeletedResourceScope resourceScope, ODataDeletedResource deletedResource, bool writingResponse, SelectedPropertiesNode selectedProperties) + { + if (this.jsonLightOutputContext.MessageWriterSettings.Version > ODataVersion.V4) + { + var typeContext = resourceScope.GetOrCreateTypeContext(writingResponse); + if (this.jsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel) + { + // 1. NoMetadata level: always enable its NullResourceMetadataBuilder + InnerPrepareResourceForWriteStart(deletedResource, typeContext, selectedProperties); + } + else + { + // 2. Minimal/Full Metadata level: Use ODataConventionalEntityMetadataBuilder for entity, ODataConventionalResourceMetadataBuilder for other cases. + if (this.jsonLightOutputContext.Model.IsUserModel() || resourceScope.SerializationInfo != null) + { + InnerPrepareResourceForWriteStart(deletedResource, typeContext, selectedProperties); + } + + // 3. Here fallback to the default NoOpResourceMetadataBuilder, when model and serializationInfo are both null. + } + } + } + + /// + /// Start writing a property. + /// + /// The property info to write. + protected override void StartProperty(ODataPropertyInfo property) + { + ResourceBaseScope scope = this.ParentScope as ResourceBaseScope; + Debug.Assert(scope != null, "Writing a property and the parent scope is not a resource"); + ODataResource resource = scope.Item as ODataResource; + Debug.Assert(resource != null && resource.MetadataBuilder != null, "Writing a property with no parent resource MetadataBuilder"); + + ODataProperty propertyWithValue = property as ODataProperty; + if (propertyWithValue != null) + { + this.jsonLightPropertySerializer.WriteProperty( + propertyWithValue, + scope.ResourceType, + false /*isTopLevel*/, + this.DuplicatePropertyNameChecker, + resource.MetadataBuilder); + } + else + { + this.jsonLightPropertySerializer.WritePropertyInfo( + property, + scope.ResourceType, + false /*isTopLevel*/, + this.DuplicatePropertyNameChecker, + resource.MetadataBuilder); + } + } + + /// + /// End writing a property. + /// + /// The property to write. + protected override void EndProperty(ODataPropertyInfo property) + { + // nothing to do + } + + /// + /// Start writing a resource. + /// + /// The resource to write. + protected override void StartResource(ODataResource resource) + { + ODataNestedResourceInfo parentNavLink = this.ParentNestedResourceInfo; + if (parentNavLink != null) + { + // For a null value, write the type as a property annotation + if (resource == null) + { + if (parentNavLink.TypeAnnotation != null && parentNavLink.TypeAnnotation.TypeName != null) + { + this.jsonLightResourceSerializer.ODataAnnotationWriter.WriteODataTypePropertyAnnotation(parentNavLink.Name, parentNavLink.TypeAnnotation.TypeName); + } + + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(parentNavLink.GetInstanceAnnotations(), parentNavLink.Name); + } + + // Write the property name of an expanded navigation property to start the value. + this.jsonWriter.WriteName(parentNavLink.Name); + } + + if (resource == null) + { + this.jsonWriter.WriteValue((string)null); + return; + } + + // Write just the object start, nothing else, since we might not have complete information yet + this.jsonWriter.StartObjectScope(); + + JsonLightResourceScope resourceScope = this.CurrentResourceScope; + + if (this.IsTopLevel && !(this.jsonLightOutputContext.MetadataLevel is JsonNoMetadataLevel)) + { + var contextUriInfo = this.jsonLightResourceSerializer.WriteResourceContextUri( + resourceScope.GetOrCreateTypeContext(this.jsonLightOutputContext.WritingResponse)); + + // Is writing an undeclared resource. + if (contextUriInfo != null) + { + resourceScope.IsUndeclared = contextUriInfo.IsUndeclared.HasValue && contextUriInfo.IsUndeclared.Value; + } + } + else if (this.ParentScope.State == WriterState.DeltaResourceSet && this.ScopeLevel == 3) + { + DeltaResourceSetScope deltaResourceSetScope = this.ParentScope as DeltaResourceSetScope; + Debug.Assert(deltaResourceSetScope != null, "Writing child of delta set and parent scope is not DeltaResourceSetScope"); + + string expectedNavigationSource = + deltaResourceSetScope.NavigationSource == null ? null : deltaResourceSetScope.NavigationSource.Name; + string currentNavigationSource = + resource.SerializationInfo != null ? resource.SerializationInfo.NavigationSourceName : + resourceScope.NavigationSource == null ? null : resourceScope.NavigationSource.Name; + + if (String.IsNullOrEmpty(currentNavigationSource) || currentNavigationSource != expectedNavigationSource) + { + this.jsonLightResourceSerializer.WriteDeltaContextUri( + this.CurrentResourceScope.GetOrCreateTypeContext(true), ODataDeltaKind.Resource, + deltaResourceSetScope.ContextUriInfo); + } + } + + // Write the metadata + this.jsonLightResourceSerializer.WriteResourceStartMetadataProperties(resourceScope); + this.jsonLightResourceSerializer.WriteResourceMetadataProperties(resourceScope); + + this.jsonLightOutputContext.PropertyCacheHandler.SetCurrentResourceScopeLevel(this.ScopeLevel); + + // Write custom instance annotations + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(resource.InstanceAnnotations, resourceScope.InstanceAnnotationWriteTracker); + + this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + if (resource.NonComputedProperties != null) + { + this.jsonLightResourceSerializer.WriteProperties( + this.ResourceType, + resource.NonComputedProperties, + false /* isComplexValue */, + this.DuplicatePropertyNameChecker, + resource.MetadataBuilder); + this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + } + + // COMPAT 48: Position of navigation properties/links in JSON differs. + } + + /// + /// Finish writing a resource. + /// + /// The resource to write. + protected override void EndResource(ODataResource resource) + { + if (resource == null) + { + return; + } + + // Get the projected properties + JsonLightResourceScope resourceScope = this.CurrentResourceScope; + + this.jsonLightResourceSerializer.WriteResourceMetadataProperties(resourceScope); + + // Write custom instance annotations + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(resource.InstanceAnnotations, resourceScope.InstanceAnnotationWriteTracker); + + this.jsonLightResourceSerializer.WriteResourceEndMetadataProperties(resourceScope, resourceScope.DuplicatePropertyNameChecker); + + // Close the object scope + this.jsonWriter.EndObjectScope(); + } + + /// + /// Finish writing a deleted resource. + /// + /// The resource to write. + protected override void EndDeletedResource(ODataDeletedResource deletedResource) + { + if (deletedResource == null) + { + return; + } + + // Get the projected properties + // JsonLightResourceScope resourceScope = this.CurrentResourceScope; + + // this.jsonLightResourceSerializer.WriteResourceMetadataProperties(resourceScope); + + // Write custom instance annotations + // this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(deletedResource.InstanceAnnotations, resourceScope.InstanceAnnotationWriteTracker); + + // this.jsonLightResourceSerializer.WriteResourceEndMetadataProperties(resourceScope, resourceScope.DuplicatePropertyNameChecker); + + // Close the object scope + this.jsonWriter.EndObjectScope(); + } + + /// + /// Start writing a resource set. + /// + /// The resource set to write. + protected override void StartResourceSet(ODataResourceSet resourceSet) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + if (this.ParentNestedResourceInfo == null && (this.writingParameter || this.ParentScope.State == WriterState.ResourceSet)) + { + // Start array which will hold the entries in the resource set. + this.jsonWriter.StartArrayScope(); + } + else if (this.ParentNestedResourceInfo == null) + { + // Top-level resource set. + // "{" + this.jsonWriter.StartObjectScope(); + + // @odata.context + this.jsonLightResourceSerializer.WriteResourceSetContextUri(this.CurrentResourceSetScope.GetOrCreateTypeContext(this.jsonLightOutputContext.WritingResponse)); + + if (this.jsonLightOutputContext.WritingResponse) + { + // write "odata.actions" metadata + IEnumerable actions = resourceSet.Actions; + if (actions != null && actions.Any()) + { + this.jsonLightResourceSerializer.WriteOperations(actions.Cast(), /*isAction*/ true); + } + + // write "odata.functions" metadata + IEnumerable functions = resourceSet.Functions; + if (functions != null && functions.Any()) + { + this.jsonLightResourceSerializer.WriteOperations(functions.Cast(), /*isAction*/ false); + } + + // Write the inline count if it's available. + this.WriteResourceSetCount(resourceSet.Count, /*propertyName*/null); + + // Write the next link if it's available. + this.WriteResourceSetNextLink(resourceSet.NextPageLink, /*propertyName*/null); + + // Write the delta link if it's available. + this.WriteResourceSetDeltaLink(resourceSet.DeltaLink); + } + + // Write custom instance annotations + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(resourceSet.InstanceAnnotations, this.CurrentResourceSetScope.InstanceAnnotationWriteTracker); + + // "value": + this.jsonWriter.WriteValuePropertyName(); + + // Start array which will hold the entries in the resource set. + this.jsonWriter.StartArrayScope(); + } + else + { + // Expanded resource set. + Debug.Assert( + this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value), + "We should have verified that resource sets can only be written into IsCollection = true links in requests."); + + this.ValidateNoDeltaLinkForExpandedResourceSet(resourceSet); + this.ValidateNoCustomInstanceAnnotationsForExpandedResourceSet(resourceSet); + + string propertyName = this.ParentNestedResourceInfo.Name; + bool isUndeclared = (this.CurrentScope as JsonLightResourceSetScope).IsUndeclared; + var expectedResourceTypeName = + this.CurrentResourceSetScope.GetOrCreateTypeContext(this.jsonLightOutputContext.WritingResponse) + .ExpectedResourceTypeName; + + if (this.jsonLightOutputContext.WritingResponse) + { + // Write the inline count if it's available. + this.WriteResourceSetCount(resourceSet.Count, propertyName); + + // Write the next link if it's available. + this.WriteResourceSetNextLink(resourceSet.NextPageLink, propertyName); + + // Write the odata type. + this.jsonLightResourceSerializer.WriteResourceSetStartMetadataProperties(resourceSet, propertyName, expectedResourceTypeName, isUndeclared); + + // And then write the property name to start the value. + this.jsonWriter.WriteName(propertyName); + + // Start array which will hold the entries in the resource set. + this.jsonWriter.StartArrayScope(); + } + else + { + JsonLightNestedResourceInfoScope navigationLinkScope = (JsonLightNestedResourceInfoScope)this.ParentNestedResourceInfoScope; + if (!navigationLinkScope.ResourceSetWritten) + { + // Close the entity reference link array (if written) + if (navigationLinkScope.EntityReferenceLinkWritten) + { + this.jsonWriter.EndArrayScope(); + } + + // Write the odata type. + this.jsonLightResourceSerializer.WriteResourceSetStartMetadataProperties(resourceSet, propertyName, expectedResourceTypeName, isUndeclared); + + // And then write the property name to start the value. + this.jsonWriter.WriteName(propertyName); + + // Start array which will hold the entries in the resource set. + this.jsonWriter.StartArrayScope(); + + navigationLinkScope.ResourceSetWritten = true; + } + } + } + + this.jsonLightOutputContext.PropertyCacheHandler.EnterResourceSetScope(this.CurrentResourceSetScope.ResourceType, this.ScopeLevel); + } + + /// + /// Finish writing a resource set. + /// + /// The resource set to write. + protected override void EndResourceSet(ODataResourceSet resourceSet) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + if (this.ParentNestedResourceInfo == null && (this.writingParameter || this.ParentScope.State == WriterState.ResourceSet)) + { + // End the array which holds the entries in the resource set. + this.jsonWriter.EndArrayScope(); + } + else if (this.ParentNestedResourceInfo == null) + { + // End the array which holds the entries in the resource set. + this.jsonWriter.EndArrayScope(); + + // Write custom instance annotations + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(resourceSet.InstanceAnnotations, this.CurrentResourceSetScope.InstanceAnnotationWriteTracker); + + if (this.jsonLightOutputContext.WritingResponse) + { + // Write the next link if it's available. + this.WriteResourceSetNextLink(resourceSet.NextPageLink, /*propertyName*/null); + + // Write the delta link if it's available. + this.WriteResourceSetDeltaLink(resourceSet.DeltaLink); + } + + // Close the object wrapper. + this.jsonWriter.EndObjectScope(); + } + else + { + Debug.Assert( + this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value), + "We should have verified that resource sets can only be written into IsCollection = true links in requests."); + string propertyName = this.ParentNestedResourceInfo.Name; + + this.ValidateNoDeltaLinkForExpandedResourceSet(resourceSet); + this.ValidateNoCustomInstanceAnnotationsForExpandedResourceSet(resourceSet); + + if (this.jsonLightOutputContext.WritingResponse) + { + // End the array which holds the entries in the resource set. + // NOTE: in requests we will only write the EndArray of a resource set + // when we hit the nested resource info end since a nested resource info + // can contain multiple resource sets that get collapesed into a single array value. + this.jsonWriter.EndArrayScope(); + + // Write the next link if it's available. + this.WriteResourceSetNextLink(resourceSet.NextPageLink, propertyName); + } + } + + this.jsonLightOutputContext.PropertyCacheHandler.LeaveResourceSetScope(); + } + + /// + /// Start writing a delta resource set. + /// + /// The delta resource set to write. + protected override void StartDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) + { + Debug.Assert(deltaResourceSet != null, "resourceSet != null"); + + if (this.ParentNestedResourceInfo == null) + { + this.jsonWriter.StartObjectScope(); + + // Write ContextUrl + this.CurrentDeltaResourceSetScope.ContextUriInfo = this.jsonLightResourceSerializer.WriteDeltaContextUri( + this.CurrentDeltaResourceSetScope.GetOrCreateTypeContext(this.jsonLightOutputContext.WritingResponse), + ODataDeltaKind.ResourceSet); + + // Write Count, if available + this.WriteResourceSetCount(deltaResourceSet.Count, /*propertyname*/ null); + + // Write NextLink, if available + this.WriteResourceSetNextLink(deltaResourceSet.NextPageLink, /*propertyname*/ null); + + // If we haven't written the delta link yet and it's available, write it. + this.WriteResourceSetDeltaLink(deltaResourceSet.DeltaLink); + + // Write annotations + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(deltaResourceSet.InstanceAnnotations, this.CurrentDeltaResourceSetScope.InstanceAnnotationWriteTracker); + + // Write Value Start + this.jsonWriter.WriteValuePropertyName(); + this.jsonWriter.StartArrayScope(); + } + else + { + // Nested Delta + Debug.Assert( + this.ParentNestedResourceInfo != null && this.ParentNestedResourceInfo.IsCollection.HasValue && this.ParentNestedResourceInfo.IsCollection.Value, + "We should have verified that resource sets can only be written into IsCollection = true links in requests."); + + string propertyName = this.ParentNestedResourceInfo.Name; + + // Write the inline count if it's available. + this.WriteResourceSetCount(deltaResourceSet.Count, propertyName); + + // Write the next link if it's available. + this.WriteResourceSetNextLink(deltaResourceSet.NextPageLink, propertyName); + + //// Write the name for the nested delta payload + this.jsonWriter.WritePropertyAnnotationName(propertyName, JsonLightConstants.ODataDeltaPropertyName); + + // Start array which will hold the entries in the nested delta resource set. + this.jsonWriter.StartArrayScope(); + } + } + + /// + /// Finish writing a delta resource set. + /// + /// The resource set to write. + protected override void EndDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) + { + Debug.Assert(deltaResourceSet != null, "deltaResourceSet != null"); + + if (this.ParentNestedResourceInfo == null) + { + // End the array which holds the entries in the resource set. + this.jsonWriter.EndArrayScope(); + + // Write custom instance annotations + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(deltaResourceSet.InstanceAnnotations, this.CurrentDeltaResourceSetScope.InstanceAnnotationWriteTracker); + + // Write the next link if it's available. + this.WriteResourceSetNextLink(deltaResourceSet.NextPageLink, /*propertynamne*/ null); + + // Write the delta link if it's available. + this.WriteResourceSetDeltaLink(deltaResourceSet.DeltaLink); + + // Close the object wrapper. + this.jsonWriter.EndObjectScope(); + } + else + { + // End the array which holds the entries in the resource set. + this.jsonWriter.EndArrayScope(); + } + } + + /// + /// Start writing a delta deleted resource. + /// + /// The resource to write. + protected override void StartDeletedResource(ODataDeletedResource resource) + { + Debug.Assert(resource != null, "resource != null"); + Debug.Assert(!this.IsTopLevel, "Delta resource cannot be on top level."); + DeletedResourceScope resourceScope = this.CurrentDeletedResourceScope; + Debug.Assert(resourceScope != null, "Writing deleted entry and scope is not DeltaResourceScope"); + ODataNestedResourceInfo parentNavLink = this.ParentNestedResourceInfo; + if (parentNavLink != null) + { + // Writing a nested deleted resource + if (this.Version == null || this.Version < ODataVersion.V401) + { + throw new ODataException(Strings.ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry); + } + else + { + // Write the property name of an expanded navigation property to start the value. + this.jsonWriter.WriteName(parentNavLink.Name); + this.jsonWriter.StartObjectScope(); + this.WriteDeletedEntryContents(resource); + } + } + else + { + // Writing a deleted resource within an entity set + DeltaResourceSetScope deltaResourceSetScope = this.ParentScope as DeltaResourceSetScope; + Debug.Assert(deltaResourceSetScope != null, "Writing child of delta set and parent scope is not DeltaResourceSetScope"); + + this.jsonWriter.StartObjectScope(); + + if (this.Version == null || this.Version < ODataVersion.V401) + { + // Write ContextUrl + this.jsonLightResourceSerializer.WriteDeltaContextUri( + this.CurrentDeletedResourceScope.GetOrCreateTypeContext(this.jsonLightOutputContext.WritingResponse), + ODataDeltaKind.DeletedEntry, deltaResourceSetScope.ContextUriInfo); + this.WriteV4DeletedEntryContents(resource); + } + else + { + // Only write ContextUrl for V4.01 deleted resource if it is in a top level delta + // resource set and comes from a different entity set + string expectedNavigationSource = + deltaResourceSetScope.NavigationSource == null ? null : deltaResourceSetScope.NavigationSource.Name; + string currentNavigationSource = + resource.SerializationInfo != null ? resource.SerializationInfo.NavigationSourceName : + resourceScope.NavigationSource == null ? null : resourceScope.NavigationSource.Name; + + if (String.IsNullOrEmpty(currentNavigationSource) || currentNavigationSource != expectedNavigationSource) + { + Debug.Assert(this.ScopeLevel == 3, "Writing a nested deleted resource of the wrong type should already have been caught."); + + // We are writing a deleted resource in a top level delta resource set + // from a different entity set, so include the context Url + this.jsonLightResourceSerializer.WriteDeltaContextUri( + this.CurrentDeletedResourceScope.GetOrCreateTypeContext(this.jsonLightOutputContext.WritingResponse), + ODataDeltaKind.DeletedEntry, deltaResourceSetScope.ContextUriInfo); + } + + this.WriteDeletedEntryContents(resource); + } + } + } + + /// + /// Start writing a delta (deleted) link. + /// + /// The link to write. + protected override void StartDeltaLink(ODataDeltaLinkBase link) + { + Debug.Assert(link != null, "link != null"); + + this.jsonWriter.StartObjectScope(); + + if (link is ODataDeltaLink) + { + this.WriteDeltaLinkContextUri(ODataDeltaKind.Link); + } + else + { + Debug.Assert(link is ODataDeltaDeletedLink, "link must be either DeltaLink or DeltaDeletedLink."); + this.WriteDeltaLinkContextUri(ODataDeltaKind.DeletedLink); + } + + this.WriteDeltaLinkSource(link); + this.WriteDeltaLinkRelationship(link); + this.WriteDeltaLinkTarget(link); + this.jsonWriter.EndObjectScope(); + } + + /// + /// Write a primitive type inside an untyped collection. + /// + /// The primitive value to write. + protected override void WritePrimitiveValue(ODataPrimitiveValue primitiveValue) + { + ODataPropertyInfo property; + if (this.ParentScope != null && (property = this.ParentScope.Item as ODataPropertyInfo) != null) + { + this.jsonWriter.WriteName(property.Name); + } + + if (primitiveValue == null) + { + this.jsonLightValueSerializer.WriteNullValue(); + } + else + { + this.jsonLightValueSerializer.WritePrimitiveValue(primitiveValue.Value, /*expectedType*/ null); + } + } + + /// + /// Create a stream for writing a binary value. + /// + /// Stream for writing a binary value. + protected override Stream StartBinaryStream() + { + ODataPropertyInfo property; + if (this.ParentScope != null && (property = this.ParentScope.Item as ODataPropertyInfo) != null) + { + // writing a stream property - write the property name + this.jsonWriter.WriteName(property.Name); + this.jsonWriter.Flush(); + } + + Stream stream; + if (this.jsonStreamWriter == null) + { + this.jsonLightOutputContext.BinaryValueStream = new MemoryStream(); + stream = this.jsonLightOutputContext.BinaryValueStream; + } + else + { + stream = this.jsonStreamWriter.StartStreamValueScope(); + } + + return stream; + } + + /// + /// Finish writing a stream value. + /// + protected sealed override void EndBinaryStream() + { + if (this.jsonStreamWriter == null) + { + this.jsonWriter.WriteValue(this.jsonLightOutputContext.BinaryValueStream.ToArray()); + this.jsonLightOutputContext.BinaryValueStream.Flush(); + this.jsonLightOutputContext.BinaryValueStream.Dispose(); + this.jsonLightOutputContext.BinaryValueStream = null; + } + else + { + this.jsonStreamWriter.EndStreamValueScope(); + } + } + + /// + /// Create a TextWriter for writing a string value. + /// + /// TextWriter for writing a string value. + protected override TextWriter StartTextWriter() + { + ODataPropertyInfo property = null; + if (this.ParentScope != null && (property = this.ParentScope.Item as ODataPropertyInfo) != null) + { + // writing a text property - write the property name + this.jsonWriter.WriteName(property.Name); + this.jsonWriter.Flush(); + } + + TextWriter writer; + if (this.jsonStreamWriter == null) + { + this.jsonLightOutputContext.StringWriter = new StringWriter(System.Globalization.CultureInfo.InvariantCulture); + writer = this.jsonLightOutputContext.StringWriter; + } + else + { + string contentType = "text/plain"; + ODataStreamPropertyInfo streamInfo = property as ODataStreamPropertyInfo; + if (streamInfo != null && streamInfo.ContentType != null) + { + contentType = streamInfo.ContentType; + } + + writer = this.jsonStreamWriter.StartTextWriterValueScope(contentType); + } + + return writer; + } + + /// + /// Finish writing a text value. + /// + protected sealed override void EndTextWriter() + { + if (this.jsonStreamWriter == null) + { + Debug.Assert(this.jsonLightOutputContext.StringWriter != null, "Calling EndTextWriter with a non-streaming JsonWriter and a null StringWriter"); + this.jsonLightOutputContext.StringWriter.Flush(); + this.jsonWriter.WriteValue(this.jsonLightOutputContext.StringWriter.GetStringBuilder().ToString()); + this.jsonLightOutputContext.StringWriter.Dispose(); + this.jsonLightOutputContext.StringWriter = null; + } + else + { + this.jsonStreamWriter.EndTextWriterValueScope(); + } + } + + /// + /// Start writing a deferred (non-expanded) nested resource info. + /// + /// The nested resource info to write. + protected override void WriteDeferredNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(this.jsonLightOutputContext.WritingResponse, "Deferred links are only supported in response, we should have verified this already."); + + // A deferred nested resource info is just the link metadata, no value. + this.jsonLightResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker); + } + + /// + /// Start writing the nested resource info with content. + /// + /// The nested resource info to write. + protected override void StartNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert(!string.IsNullOrEmpty(nestedResourceInfo.Name), "The nested resource info name should have been verified by now."); + + if (this.jsonLightOutputContext.WritingResponse) + { + // Write @odata.context annotation for navigation property + var containedEntitySet = this.CurrentScope.NavigationSource as IEdmContainedEntitySet; + if (containedEntitySet != null + && this.jsonLightOutputContext.MessageWriterSettings.LibraryCompatibility < ODataLibraryCompatibility.Version7 + && this.jsonLightOutputContext.MessageWriterSettings.Version < ODataVersion.V401) + { + ODataContextUrlInfo info = ODataContextUrlInfo.Create( + this.CurrentScope.NavigationSource, + this.CurrentScope.ResourceType.FullTypeName(), + containedEntitySet.NavigationProperty.Type.TypeKind() != EdmTypeKind.Collection, + this.CurrentScope.ODataUri, + this.jsonLightOutputContext.MessageWriterSettings.Version ?? ODataVersion.V4); + + this.jsonLightResourceSerializer.WriteNestedResourceInfoContextUrl(nestedResourceInfo, info); + } + + // Write the nested resource info metadata first. The rest is written by the content resource or resource set. + this.jsonLightResourceSerializer.WriteNavigationLinkMetadata(nestedResourceInfo, this.DuplicatePropertyNameChecker); + } + else + { + this.WriterValidator.ValidateNestedResourceInfoHasCardinality(nestedResourceInfo); + } + } + + /// + /// Finish writing nested resource info with content. + /// + /// The nested resource info to write. + protected override void EndNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + + JsonLightNestedResourceInfoScope navigationLinkScope = (JsonLightNestedResourceInfoScope)this.CurrentScope; + + // If we wrote entity reference links for a collection navigation property but no + // resource set afterwards, we have to now close the array of links. + if (!this.jsonLightOutputContext.WritingResponse) + { + if (navigationLinkScope.EntityReferenceLinkWritten && !navigationLinkScope.ResourceSetWritten && nestedResourceInfo.IsCollection.Value) + { + this.jsonWriter.EndArrayScope(); + } + + // In requests, the nested resource info may have multiple entries in multiple resource sets in it; if we + // wrote at least one resource set, close the resulting array here. + if (navigationLinkScope.ResourceSetWritten) + { + Debug.Assert(nestedResourceInfo.IsCollection == null || nestedResourceInfo.IsCollection.Value, "nestedResourceInfo.IsCollection.Value"); + this.jsonWriter.EndArrayScope(); + } + } + } + + /// + /// Write an entity reference link. + /// + /// The parent navigation link which is being written around the entity reference link. + /// The entity reference link to write. + protected override void WriteEntityReferenceInNavigationLinkContent(ODataNestedResourceInfo parentNestedResourceInfo, ODataEntityReferenceLink entityReferenceLink) + { + Debug.Assert(parentNestedResourceInfo != null, "parentNestedResourceInfo != null"); + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + // In JSON Light, we can only write entity reference links at the beginning of a navigation link in requests; + // once we wrote a resource set, entity reference links are not allowed anymore (we require all the entity reference + // link to come first because of the grouping in the JSON Light wire format). + JsonLightNestedResourceInfoScope nestedResourceScope = this.CurrentScope as JsonLightNestedResourceInfoScope; + if (nestedResourceScope == null) + { + nestedResourceScope = this.ParentNestedResourceInfoScope as JsonLightNestedResourceInfoScope; + } + + if (nestedResourceScope.ResourceSetWritten) + { + throw new ODataException(Strings.ODataJsonLightWriter_EntityReferenceLinkAfterResourceSetInRequest); + } + + if (!nestedResourceScope.EntityReferenceLinkWritten) + { + // In request + if (!this.jsonLightOutputContext.WritingResponse) + { + if (this.Version == null || this.Version < ODataVersion.V401) + { + // Write the property annotation for the entity reference link(s) + this.odataAnnotationWriter.WritePropertyAnnotationName(parentNestedResourceInfo.Name, ODataAnnotationNames.ODataBind); + } + else + { + this.jsonWriter.WriteName(parentNestedResourceInfo.Name); + } + + Debug.Assert(parentNestedResourceInfo.IsCollection.HasValue, "parentNestedResourceInfo.IsCollection.HasValue"); + if (parentNestedResourceInfo.IsCollection.Value) + { + // write [ for the collection + this.jsonWriter.StartArrayScope(); + } + } + else + { + // In response + Debug.Assert(parentNestedResourceInfo.IsCollection.HasValue, "parentNestedResourceInfo.IsCollection.HasValue"); + if (!parentNestedResourceInfo.IsCollection.Value) + { + // Write the property name for single-nested resource, + // for the collection nested resource, it's write at top level when writing ODataResourceSet + this.jsonWriter.WriteName(parentNestedResourceInfo.Name); + } + } + + nestedResourceScope.EntityReferenceLinkWritten = true; + } + + if (!this.jsonLightOutputContext.WritingResponse && + (this.Version == null || this.Version < ODataVersion.V401)) + { + Debug.Assert(entityReferenceLink.Url != null, "The entity reference link Url should have been validated by now."); + this.jsonWriter.WriteValue(this.jsonLightResourceSerializer.UriToString(entityReferenceLink.Url)); + } + else + { + WriteEntityReferenceLinkImplementation(entityReferenceLink); + } + } + + /// + /// Create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resources for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource set is for an undeclared property + /// The newly create scope. + protected override ResourceSetScope CreateResourceSetScope( + ODataResourceSet resourceSet, + IEdmNavigationSource navigationSource, + IEdmType itemType, + bool skipWriting, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri, + bool isUndeclared) + { + return new JsonLightResourceSetScope(resourceSet, navigationSource, itemType, skipWriting, selectedProperties, odataUri, isUndeclared); + } + + /// + /// Create a new delta resource set scope. + /// + /// The delta resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource set is for an undeclared property + /// The newly create scope. + protected override DeltaResourceSetScope CreateDeltaResourceSetScope( + ODataDeltaResourceSet deltaResourceSet, + IEdmNavigationSource navigationSource, + IEdmStructuredType resourceType, + bool skipWriting, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri, + bool isUndeclared) + { + return new JsonLightDeltaResourceSetScope(deltaResourceSet, navigationSource, resourceType, selectedProperties, odataUri); + } + + /// + /// Create a new resource scope. + /// + /// The resource for the new scope. + /// The navigation source we are going to write resources for. + /// The entity type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + /// The newly create scope. + protected override DeletedResourceScope CreateDeletedResourceScope(ODataDeletedResource deltaResource, IEdmNavigationSource navigationSource, IEdmEntityType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) + { + return new JsonLightDeletedResourceScope( + deltaResource, + this.GetResourceSerializationInfo(deltaResource), + navigationSource, + resourceType, + skipWriting, + this.jsonLightOutputContext.MessageWriterSettings, + selectedProperties, + odataUri, + isUndeclared); + } + + /// + /// Create a new property scope. + /// + /// The property for the new scope. + /// The navigation source. + /// The structured type for the resource containing the property to be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly created property scope. + protected override PropertyInfoScope CreatePropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + return new JsonLightPropertyScope(property, navigationSource, resourceType, selectedProperties, odataUri); + } + + /// + /// Create a new delta link scope. + /// + /// The link for the new scope. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly create scope. + protected override DeltaLinkScope CreateDeltaLinkScope(ODataDeltaLinkBase link, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + return new JsonLightDeltaLinkScope( + link is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, + link, + this.GetLinkSerializationInfo(link), + navigationSource, + entityType, + selectedProperties, + odataUri); + } + + /// + /// Create a new resource scope. + /// + /// The resource for the new scope. + /// The navigation source we are going to write resources for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + /// The newly create scope. + protected override ResourceScope CreateResourceScope(ODataResource resource, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) + { + return new JsonLightResourceScope( + resource, + this.GetResourceSerializationInfo(resource), + navigationSource, + resourceType, + skipWriting, + this.jsonLightOutputContext.MessageWriterSettings, + selectedProperties, + odataUri, + isUndeclared); + } + + /// + /// Creates a new JSON Light nested resource info scope. + /// + /// The writer state for the new scope. + /// The nested resource info for the new scope. + /// The navigation source we are going to write entities for. + /// The type for the items in the resource set to be written (or null if the navigationSource base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly created JSON Light nested resource info scope. + protected override NestedResourceInfoScope CreateNestedResourceInfoScope(WriterState writerState, ODataNestedResourceInfo navLink, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + return new JsonLightNestedResourceInfoScope(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri); + } + + /// + /// Write the entity reference link. + /// + /// The OData entity reference link. + private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink entityReferenceLink) + { + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLink); + + this.jsonWriter.StartObjectScope(); + + this.odataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataId); + + Uri id = this.jsonLightOutputContext.MessageWriterSettings.MetadataDocumentUri.MakeRelativeUri(entityReferenceLink.Url); + + this.jsonWriter.WriteValue(id == null ? null : this.jsonLightResourceSerializer.UriToString(id)); + + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(entityReferenceLink.InstanceAnnotations); + + this.jsonWriter.EndObjectScope(); + } + + /// + /// Sets resource's metadata builder based on current metadata level. + /// + /// Resource to write. + /// The context object to answer basic questions regarding the type of the resource or resource set. + /// The selected properties of this scope. + private void InnerPrepareResourceForWriteStart(ODataResourceBase resource, ODataResourceTypeContext typeContext, SelectedPropertiesNode selectedProperties) + { + ODataResourceSerializationInfo serializationInfo; + IEdmStructuredType resourceType; + ODataUri uri; + + if (resource is ODataResource) + { + ResourceScope resourceScope = (ResourceScope)this.CurrentScope; + Debug.Assert(resourceScope != null, "resourceScope != null"); + serializationInfo = resourceScope.SerializationInfo; + resourceType = resourceScope.ResourceType; + uri = resourceScope.ODataUri; + } + else + { + DeletedResourceScope resourceScope = (DeletedResourceScope)this.CurrentScope; + Debug.Assert(resourceScope != null, "resourceScope != null"); + serializationInfo = resourceScope.SerializationInfo; + resourceType = resourceScope.ResourceType; + uri = resourceScope.ODataUri; + } + + ODataResourceMetadataBuilder builder = this.jsonLightOutputContext.MetadataLevel.CreateResourceMetadataBuilder( + resource, + typeContext, + serializationInfo, + resourceType, + selectedProperties, + this.jsonLightOutputContext.WritingResponse, + this.jsonLightOutputContext.ODataSimplifiedOptions.EnableWritingKeyAsSegment, + uri); + + if (builder != null) + { + builder.NameAsProperty = this.BelongingNestedResourceInfo != null ? this.BelongingNestedResourceInfo.Name : null; + builder.IsFromCollection = this.BelongingNestedResourceInfo != null && this.BelongingNestedResourceInfo.IsCollection == true; + + if (builder is ODataConventionalResourceMetadataBuilder) + { + builder.ParentMetadataBuilder = this.FindParentResourceMetadataBuilder(); + } + + this.jsonLightOutputContext.MetadataLevel.InjectMetadataBuilder(resource, builder); + } + } + + /// + /// Find instance of the metadata builder which belong to the parent odata resource + /// + /// + /// The metadata builder of the parent odata resource + /// Or null if there is no parent odata resource + /// + private ODataResourceMetadataBuilder FindParentResourceMetadataBuilder() + { + ResourceScope parentResourceScope = this.GetParentResourceScope(); + + if (parentResourceScope != null) + { + ODataResourceBase resource = parentResourceScope.Item as ODataResourceBase; + if (resource != null) + { + return resource.MetadataBuilder; + } + } + + return null; + } + + /// + /// Writes the odata.count annotation for a resource set if it has not been written yet (and the count is specified on the resource set). + /// + /// The count to write for the resource set. + /// The name of the expanded nav property or null for a top-level resource set. + private void WriteResourceSetCount(long? count, string propertyName) + { + if (count.HasValue) + { + if (propertyName == null) + { + this.odataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataCount); + } + else + { + this.odataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataCount); + } + + this.jsonWriter.WriteValue(count.Value); + } + } + + /// + /// Writes the odata.nextLink annotation for a resource set if it has not been written yet (and the next link is specified on the resource set). + /// + /// The nextLink to write, if available. + /// The name of the expanded nav property or null for a top-level resource set. + private void WriteResourceSetNextLink(Uri nextPageLink, string propertyName) + { + bool nextPageWritten = this.State == WriterState.ResourceSet ? this.CurrentResourceSetScope.NextPageLinkWritten : this.CurrentDeltaResourceSetScope.NextPageLinkWritten; + if (nextPageLink != null && !nextPageWritten) + { + if (propertyName == null) + { + this.odataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataNextLink); + } + else + { + this.odataAnnotationWriter.WritePropertyAnnotationName(propertyName, ODataAnnotationNames.ODataNextLink); + } + + this.jsonWriter.WriteValue(this.jsonLightResourceSerializer.UriToString(nextPageLink)); + + if (this.State == WriterState.ResourceSet) + { + this.CurrentResourceSetScope.NextPageLinkWritten = true; + } + else + { + this.CurrentDeltaResourceSetScope.NextPageLinkWritten = true; + } + } + } + + /// + /// Writes the odata.deltaLink annotation for a resource set if it has not been written yet (and the delta link is specified on the resource set). + /// + /// The delta link to write. + private void WriteResourceSetDeltaLink(Uri deltaLink) + { + if (deltaLink == null) + { + return; + } + + Debug.Assert(this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "Write ResourceSet Delta Link called when not in ResourceSet or DeltaResourceSet state"); + + bool deltaLinkWritten = this.State == WriterState.ResourceSet ? this.CurrentResourceSetScope.DeltaLinkWritten : this.CurrentDeltaResourceSetScope.DeltaLinkWritten; + if (!deltaLinkWritten) + { + this.odataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataDeltaLink); + this.jsonWriter.WriteValue(this.jsonLightResourceSerializer.UriToString(deltaLink)); + if (this.State == WriterState.ResourceSet) + { + this.CurrentResourceSetScope.DeltaLinkWritten = true; + } + else + { + this.CurrentDeltaResourceSetScope.DeltaLinkWritten = true; + } + } + } + + private void WriteV4DeletedEntryContents(ODataDeletedResource resource) + { + this.WriteDeletedResourceId(resource); + this.WriteDeltaResourceReason(resource); + } + + private void WriteDeletedEntryContents(ODataDeletedResource resource) + { + this.odataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataRemoved); + this.jsonWriter.StartObjectScope(); + this.WriteDeltaResourceReason(resource); + this.jsonWriter.EndObjectScope(); + + JsonLightDeletedResourceScope resourceScope = this.CurrentDeletedResourceScope; + + // Write the metadata + this.jsonLightResourceSerializer.WriteResourceStartMetadataProperties(resourceScope); + this.jsonLightResourceSerializer.WriteResourceMetadataProperties(resourceScope); + + this.jsonLightOutputContext.PropertyCacheHandler.SetCurrentResourceScopeLevel(this.ScopeLevel); + + // Write custom instance annotations + this.jsonLightResourceSerializer.InstanceAnnotationWriter.WriteInstanceAnnotations(resource.InstanceAnnotations, resourceScope.InstanceAnnotationWriteTracker); + + this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + this.WriteDeltaResourceProperties(resource); + this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + } + + /// + /// Writes the id property for a delta deleted resource. + /// + /// The resource to write the id for. + private void WriteDeletedResourceId(ODataDeletedResource resource) + { + Debug.Assert(resource != null, "resource != null"); + if (this.Version == null || this.Version < ODataVersion.V401) + { + this.jsonWriter.WriteName(JsonLightConstants.ODataIdPropertyName); + this.jsonWriter.WriteValue(resource.Id.OriginalString); + } + else + { + Uri id; + if (resource.MetadataBuilder.TryGetIdForSerialization(out id)) + { + this.jsonWriter.WriteInstanceAnnotationName(JsonLightConstants.ODataIdPropertyName); + this.jsonWriter.WriteValue(id.OriginalString); + } + } + } + + /// + /// Writes the properties for a delta resource. + /// + /// The resource whose properties to write. + private void WriteDeltaResourceProperties(ODataResourceBase resource) + { + this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + if (resource.NonComputedProperties != null) + { + this.jsonLightResourceSerializer.WriteProperties( + this.ResourceType, + resource.NonComputedProperties, + false /* isComplexValue */, + this.DuplicatePropertyNameChecker, + resource.MetadataBuilder); + this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero(); + } + } + + /// + /// Writes the reason annotation for a delta deleted resource. + /// + /// The resource to write the reason for. + private void WriteDeltaResourceReason(ODataDeletedResource resource) + { + Debug.Assert(resource != null, "resource != null"); + + if (!resource.Reason.HasValue) + { + return; + } + + this.jsonWriter.WriteName(JsonLightConstants.ODataReasonPropertyName); + + switch (resource.Reason.Value) + { + case DeltaDeletedEntryReason.Deleted: + this.jsonWriter.WriteValue(JsonLightConstants.ODataReasonDeletedValue); + break; + case DeltaDeletedEntryReason.Changed: + this.jsonWriter.WriteValue(JsonLightConstants.ODataReasonChangedValue); + break; + default: + Debug.Assert(false, "Unknown reason."); + break; + } + } + + /// + /// Writes the context uri for a delta (deleted) link. + /// + /// The delta kind of link. + private void WriteDeltaLinkContextUri(ODataDeltaKind kind) + { + Debug.Assert(kind == ODataDeltaKind.Link || kind == ODataDeltaKind.DeletedLink, "kind must be either DeltaLink or DeltaDeletedLink."); + this.jsonLightResourceSerializer.WriteDeltaContextUri(this.CurrentDeltaLinkScope.GetOrCreateTypeContext(), kind); + } + + /// + /// Writes the source for a delta (deleted) link. + /// + /// The link to write source for. + private void WriteDeltaLinkSource(ODataDeltaLinkBase link) + { + Debug.Assert(link != null, "link != null"); + Debug.Assert(link is ODataDeltaLink || link is ODataDeltaDeletedLink, "link must be either DeltaLink or DeltaDeletedLink."); + + this.jsonWriter.WriteName(JsonLightConstants.ODataSourcePropertyName); + this.jsonWriter.WriteValue(UriUtils.UriToString(link.Source)); + } + + /// + /// Writes the relationship for a delta (deleted) link. + /// + /// The link to write relationship for. + private void WriteDeltaLinkRelationship(ODataDeltaLinkBase link) + { + Debug.Assert(link != null, "link != null"); + Debug.Assert(link is ODataDeltaLink || link is ODataDeltaDeletedLink, "link must be either DeltaLink or DeltaDeletedLink."); + + this.jsonWriter.WriteName(JsonLightConstants.ODataRelationshipPropertyName); + this.jsonWriter.WriteValue(link.Relationship); + } + + /// + /// Writes the target for a delta (deleted) link. + /// + /// The link to write target for. + private void WriteDeltaLinkTarget(ODataDeltaLinkBase link) + { + Debug.Assert(link != null, "link != null"); + Debug.Assert(link is ODataDeltaLink || link is ODataDeltaDeletedLink, "link must be either DeltaLink or DeltaDeletedLink."); + + this.jsonWriter.WriteName(JsonLightConstants.ODataTargetPropertyName); + this.jsonWriter.WriteValue(UriUtils.UriToString(link.Target)); + } + + /// + /// Validates that the ODataResourceSet.InstanceAnnotations collection is empty for the given expanded resource set. + /// + /// The expanded resource set in question. + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "An instance field is used in a debug assert.")] + private void ValidateNoCustomInstanceAnnotationsForExpandedResourceSet(ODataResourceSet resourceSet) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + Debug.Assert( + this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value == true), + "This should only be called when writing an expanded resource set."); + + if (resourceSet.InstanceAnnotations.Count > 0) + { + throw new ODataException(Strings.ODataJsonLightWriter_InstanceAnnotationNotSupportedOnExpandedResourceSet); + } + } + + /// + /// A scope for a JSON lite resource set. + /// + private sealed class JsonLightResourceSetScope : ResourceSetScope + { + /// true if the odata.nextLink was already written, false otherwise. + private bool nextLinkWritten; + + /// true if the odata.deltaLink was already written, false otherwise. + private bool deltaLinkWritten; + + /// true if the resource set represents a collection of complex property or a collection navigation property that is undeclared, false otherwise. + private bool isUndeclared; + + /// + /// Constructor to create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resources for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource set is for an undeclared property + internal JsonLightResourceSetScope(ODataResourceSet resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) + : base(resourceSet, navigationSource, itemType, skipWriting, selectedProperties, odataUri) + { + this.isUndeclared = isUndeclared; + } + + /// + /// true if the odata.nextLink annotation was already written, false otherwise. + /// + internal bool NextPageLinkWritten + { + get + { + return this.nextLinkWritten; + } + + set + { + this.nextLinkWritten = value; + } + } + + /// + /// true if the odata.deltaLink annotation was already written, false otherwise. + /// + internal bool DeltaLinkWritten + { + get + { + return this.deltaLinkWritten; + } + + set + { + this.deltaLinkWritten = value; + } + } + + /// + /// true if the resource set represents a collection of complex property or a collection navigation property that is undeclared, false otherwise. + /// + internal bool IsUndeclared + { + get { return isUndeclared; } + } + } + + /// + /// A scope for a deleted resource in JSON Light writer. + /// + private sealed class JsonLightDeletedResourceScope : DeletedResourceScope, IODataJsonLightWriterResourceState + { + /// Bit field of the JSON Light metadata properties written so far. + private int alreadyWrittenMetadataProperties; + + /// true if the resource set represents a complex property or a singleton navigation property that is undeclared, false otherwise. + private bool isUndeclared; + + /// + /// Constructor to create a new resource scope. + /// + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write resources for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + internal JsonLightDeletedResourceScope( + ODataDeletedResource resource, + ODataResourceSerializationInfo serializationInfo, + IEdmNavigationSource navigationSource, + IEdmEntityType resourceType, + bool skipWriting, + ODataMessageWriterSettings writerSettings, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri, + bool isUndeclared) + : base(resource, serializationInfo, navigationSource, resourceType, writerSettings, selectedProperties, odataUri) + { + this.isUndeclared = isUndeclared; + } + + /// + /// Enumeration of JSON Light metadata property flags, used to keep track of which properties were already written. + /// + [Flags] + private enum JsonLightEntryMetadataProperty + { + /// The odata.editLink property. + EditLink = 0x1, + + /// The odata.readLink property. + ReadLink = 0x2, + + /// The odata.mediaEditLink property. + MediaEditLink = 0x4, + + /// The odata.mediaReadLink property. + MediaReadLink = 0x8, + + /// The odata.mediaContentType property. + MediaContentType = 0x10, + + /// The odata.mediaEtag property. + MediaETag = 0x20, + } + + /// + /// The resource being written. + /// + ODataResourceBase IODataJsonLightWriterResourceState.Resource + { + get { return (ODataResourceBase)this.Item; } + } + + /// + /// true if the resource set represents a complex property or a singleton navigation property that is undeclared, false otherwise. + /// + public bool IsUndeclared + { + get { return isUndeclared; } + } + + /// + /// Flag which indicates that the odata.editLink metadata property has been written. + /// + public bool EditLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.EditLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.EditLink); + } + } + + /// + /// Flag which indicates that the odata.readLink metadata property has been written. + /// + public bool ReadLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.ReadLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.ReadLink); + } + } + + /// + /// Flag which indicates that the odata.mediaEditLink metadata property has been written. + /// + public bool MediaEditLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaEditLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaEditLink); + } + } + + /// + /// Flag which indicates that the odata.mediaReadLink metadata property has been written. + /// + public bool MediaReadLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaReadLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaReadLink); + } + } + + /// + /// Flag which indicates that the odata.mediaContentType metadata property has been written. + /// + public bool MediaContentTypeWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaContentType); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaContentType); + } + } + + /// + /// Flag which indicates that the odata.mediaEtag metadata property has been written. + /// + public bool MediaETagWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaETag); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaETag); + } + } + + /// + /// Marks the as written in this resource scope. + /// + /// The metadta property which was written. + private void SetWrittenMetadataProperty(JsonLightEntryMetadataProperty jsonLightMetadataProperty) + { + Debug.Assert(!this.IsMetadataPropertyWritten(jsonLightMetadataProperty), "Can't write the same metadata property twice."); + this.alreadyWrittenMetadataProperties |= (int)jsonLightMetadataProperty; + } + + /// + /// Determines if the was already written for this resource scope. + /// + /// The metadata property to test for. + /// true if the was already written for this resource scope; false otherwise. + private bool IsMetadataPropertyWritten(JsonLightEntryMetadataProperty jsonLightMetadataProperty) + { + return (this.alreadyWrittenMetadataProperties & (int)jsonLightMetadataProperty) == (int)jsonLightMetadataProperty; + } + } + + /// + /// A scope for a property in JSON Light writer. + /// + private sealed class JsonLightPropertyScope : PropertyInfoScope + { + /// + /// Constructor to create a new property scope. + /// + /// The property for the new scope. + /// The navigation source. + /// The structured type for the resource containing the property to be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal JsonLightPropertyScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(property, navigationSource, resourceType, selectedProperties, odataUri) + { + } + } + + /// + /// A scope for a delta link in JSON Light writer. + /// + private sealed class JsonLightDeltaLinkScope : DeltaLinkScope + { + /// + /// Constructor to create a new delta link scope. + /// + /// The writer state of this scope. + /// The link for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + public JsonLightDeltaLinkScope(WriterState state, ODataItem link, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(state, link, serializationInfo, navigationSource, entityType, selectedProperties, odataUri) + { + } + } + + /// + /// A scope for a JSON lite resource set. + /// + private sealed class JsonLightDeltaResourceSetScope : DeltaResourceSetScope + { + /// true if the odata.nextLink was already written, false otherwise. + private bool nextLinkWritten; + + /// true if the odata.deltaLink was already written, false otherwise. + private bool deltaLinkWritten; + + /// + /// Constructor to create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + public JsonLightDeltaResourceSetScope(ODataDeltaResourceSet resourceSet, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(resourceSet, navigationSource, resourceType, selectedProperties, odataUri) + { + } + + /// + /// true if the odata.nextLink annotation was already written, false otherwise. + /// + internal bool NextPageLinkWritten + { + get + { + return this.nextLinkWritten; + } + + set + { + this.nextLinkWritten = value; + } + } + + /// + /// true if the odata.deltaLink annotation was already written, false otherwise. + /// + internal bool DeltaLinkWritten + { + get + { + return this.deltaLinkWritten; + } + + set + { + this.deltaLinkWritten = value; + } + } + } + + /// + /// A scope for a resource in JSON Light writer. + /// + private sealed class JsonLightResourceScope : ResourceScope, IODataJsonLightWriterResourceState + { + /// Bit field of the JSON Light metadata properties written so far. + private int alreadyWrittenMetadataProperties; + + /// true if the resource set represents a complex property or a singleton navigation property that is undeclared, false otherwise. + private bool isUndeclared; + + /// + /// Constructor to create a new resource scope. + /// + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write resources for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + internal JsonLightResourceScope( + ODataResource resource, + ODataResourceSerializationInfo serializationInfo, + IEdmNavigationSource navigationSource, + IEdmStructuredType resourceType, + bool skipWriting, + ODataMessageWriterSettings writerSettings, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri, + bool isUndeclared) + : base(resource, serializationInfo, navigationSource, resourceType, skipWriting, writerSettings, selectedProperties, odataUri) + { + this.isUndeclared = isUndeclared; + } + + /// + /// Enumeration of JSON Light metadata property flags, used to keep track of which properties were already written. + /// + [Flags] + private enum JsonLightEntryMetadataProperty + { + /// The odata.editLink property. + EditLink = 0x1, + + /// The odata.readLink property. + ReadLink = 0x2, + + /// The odata.mediaEditLink property. + MediaEditLink = 0x4, + + /// The odata.mediaReadLink property. + MediaReadLink = 0x8, + + /// The odata.mediaContentType property. + MediaContentType = 0x10, + + /// The odata.mediaEtag property. + MediaETag = 0x20, + } + + /// + /// The resource being written. + /// + ODataResourceBase IODataJsonLightWriterResourceState.Resource + { + get { return (ODataResourceBase)this.Item; } + } + + /// + /// true if the resource set represents a complex property or a singleton navigation property that is undeclared, false otherwise. + /// + public bool IsUndeclared + { + get { return isUndeclared; } + set { isUndeclared = value; } + } + + /// + /// Flag which indicates that the odata.editLink metadata property has been written. + /// + public bool EditLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.EditLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.EditLink); + } + } + + /// + /// Flag which indicates that the odata.readLink metadata property has been written. + /// + public bool ReadLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.ReadLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.ReadLink); + } + } + + /// + /// Flag which indicates that the odata.mediaEditLink metadata property has been written. + /// + public bool MediaEditLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaEditLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaEditLink); + } + } + + /// + /// Flag which indicates that the odata.mediaReadLink metadata property has been written. + /// + public bool MediaReadLinkWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaReadLink); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaReadLink); + } + } + + /// + /// Flag which indicates that the odata.mediaContentType metadata property has been written. + /// + public bool MediaContentTypeWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaContentType); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaContentType); + } + } + + /// + /// Flag which indicates that the odata.mediaEtag metadata property has been written. + /// + public bool MediaETagWritten + { + get + { + return this.IsMetadataPropertyWritten(JsonLightEntryMetadataProperty.MediaETag); + } + + set + { + Debug.Assert(value == true, "The flag that a metadata property has been written should only ever be set from false to true."); + this.SetWrittenMetadataProperty(JsonLightEntryMetadataProperty.MediaETag); + } + } + + /// + /// Marks the as written in this resource scope. + /// + /// The metadta property which was written. + private void SetWrittenMetadataProperty(JsonLightEntryMetadataProperty jsonLightMetadataProperty) + { + Debug.Assert(!this.IsMetadataPropertyWritten(jsonLightMetadataProperty), "Can't write the same metadata property twice."); + this.alreadyWrittenMetadataProperties |= (int)jsonLightMetadataProperty; + } + + /// + /// Determines if the was already written for this resource scope. + /// + /// The metadata property to test for. + /// true if the was already written for this resource scope; false otherwise. + private bool IsMetadataPropertyWritten(JsonLightEntryMetadataProperty jsonLightMetadataProperty) + { + return (this.alreadyWrittenMetadataProperties & (int)jsonLightMetadataProperty) == (int)jsonLightMetadataProperty; + } + } + + /// + /// A scope for a JSON Light nested resource info. + /// + private sealed class JsonLightNestedResourceInfoScope : NestedResourceInfoScope + { + /// true if we have already written an entity reference link for this nested resource info in requests; otherwise false. + private bool entityReferenceLinkWritten; + + /// true if we have written at least one resource set for this nested resource info in requests; otherwise false. + private bool resourceSetWritten; + + /// + /// Constructor to create a new JSON Light nested resource info scope. + /// + /// The writer state for the new scope. + /// The nested resource info for the new scope. + /// The navigation source we are going to write entities for. + /// The type for the items in the resource set to be written (or null if the navigationSource base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal JsonLightNestedResourceInfoScope(WriterState writerState, ODataNestedResourceInfo navLink, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri) + { + } + + /// + /// true if we have already written an entity reference link for this navigation link in requests; otherwise false. + /// + internal bool EntityReferenceLinkWritten + { + get + { + return this.entityReferenceLinkWritten; + } + + set + { + this.entityReferenceLinkWritten = value; + } + } + + /// + /// true if we have written at least one resource set for this nested resource info in requests; otherwise false. + /// + internal bool ResourceSetWritten + { + get + { + return this.resourceSetWritten; + } + + set + { + this.resourceSetWritten = value; + } + } + + /// + /// Clones this JSON Light nested resource info scope and sets a new writer state. + /// + /// The writer state to set. + /// The cloned nested resource info scope with the specified writer state. + internal override NestedResourceInfoScope Clone(WriterState newWriterState) + { + return new JsonLightNestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ItemType, this.SkipWriting, this.SelectedProperties, this.ODataUri) + { + EntityReferenceLinkWritten = this.entityReferenceLinkWritten, + ResourceSetWritten = this.resourceSetWritten, + DerivedTypeConstraints = this.DerivedTypeConstraints + }; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriterUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriterUtils.cs new file mode 100644 index 0000000..c4da955 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriterUtils.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Helper methods used by the OData writer for the JsonLight format. + /// + internal static class ODataJsonLightWriterUtils + { + /// + /// Writes the 'value' property name. + /// + /// The JSON writer to write to. + internal static void WriteValuePropertyName(this IJsonWriter jsonWriter) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + + jsonWriter.WriteName(JsonLightConstants.ODataValuePropertyName); + } + + /// + /// Write a JSON property name which represents a property annotation. + /// + /// The JSON writer to write to. + /// The name of the property to annotate. + /// The name of the annotation to write. + internal static void WritePropertyAnnotationName(this IJsonWriter jsonWriter, string propertyName, string annotationName) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + jsonWriter.WriteName(propertyName + JsonLightConstants.ODataPropertyAnnotationSeparatorChar + annotationName); + } + + /// + /// Write a JSON instance annotation name which represents a instance annotation. + /// + /// The JSON writer to write to. + /// The name of the instance annotation to write. + internal static void WriteInstanceAnnotationName(this IJsonWriter jsonWriter, string annotationName) + { + Debug.Assert(jsonWriter != null, "jsonWriter != null"); + Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); + + jsonWriter.WriteName(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + annotationName); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ReorderingJsonReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ReorderingJsonReader.cs new file mode 100644 index 0000000..b4461b1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/JsonLight/ReorderingJsonReader.cs @@ -0,0 +1,681 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.JsonLight +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Reader for the JSON Lite format that supports look-ahead and re-ordering of payloads. + /// + /// TODO: not sure if this class could be implemented as a decorator as well. + internal sealed class ReorderingJsonReader : BufferingJsonReader + { + /// + /// Constructor. + /// + /// The inner JSON reader. + /// The maximum number of recursive internalexception objects to allow when reading in-stream errors. + internal ReorderingJsonReader(IJsonReader innerReader, int maxInnerErrorDepth) + : base(innerReader, JsonLightConstants.ODataErrorPropertyName, maxInnerErrorDepth) + { + Debug.Assert(innerReader != null, "innerReader != null"); + } + + /// + /// Creates a stream for reading a stream value. + /// + /// A Stream used to read a stream value. + public override Stream CreateReadStream() + { + Stream result; + try + { + result = new MemoryStream(this.Value == null ? new byte[0] : + Convert.FromBase64String((string)this.Value)); + } + catch (FormatException) + { + throw new ODataException(Strings.JsonReader_InvalidBinaryFormat(this.Value)); + } + + this.Read(); + + return result; + } + + /// + /// Creates a TextReader for reading a text value. + /// + /// A TextReader for reading the text value. + public override TextReader CreateTextReader() + { + if (this.NodeType == JsonNodeType.Property) + { + // reading JSON + throw new ODataException("Reading JSON Streams not supported for beta."); + } + + TextReader result = new StringReader(this.Value == null ? "" : (string)this.Value); + this.Read(); + + return result; + } + + /// + /// Whether or not the current value can be streamed. + /// + /// True if the current value can be streamed, otherwise false. + public override bool CanStream() + { + return (this.Value is string || this.Value == null); + } + + /// + /// Called whenever we find a new object value in the payload. + /// Buffers and re-orders an object value for later consumption by the JsonLight reader. + /// + /// + /// This method is called when the reader is in the buffering mode and can read ahead (buffering) as much as it needs to + /// once it returns the reader will be returned to the position before the method was called. + /// The reader is always positioned on a start object when this method is called. + /// + protected override void ProcessObjectValue() + { + Debug.Assert(this.currentBufferedNode.NodeType == JsonNodeType.StartObject, "this.currentBufferedNode.NodeType == JsonNodeType.StartObject"); + this.AssertBuffering(); + + Stack bufferedObjectStack = new Stack(); + while (true) + { + switch (this.currentBufferedNode.NodeType) + { + case JsonNodeType.StartObject: + { + // New object record - add the node to our stack + BufferedObject bufferedObject = new BufferedObject { ObjectStart = this.currentBufferedNode }; + bufferedObjectStack.Push(bufferedObject); + + // See if it's an in-stream error + base.ProcessObjectValue(); + this.currentBufferedNode = bufferedObject.ObjectStart; + + this.ReadInternal(); + } + + break; + case JsonNodeType.EndObject: + { + // End of object record + // Pop the node from our stack + BufferedObject bufferedObject = bufferedObjectStack.Pop(); + + // If there is a previous property record, mark its last value node. + if (bufferedObject.CurrentProperty != null) + { + bufferedObject.CurrentProperty.EndOfPropertyValueNode = this.currentBufferedNode.Previous; + } + + // Now perform the re-ordering on the buffered nodes + bufferedObject.Reorder(); + + if (bufferedObjectStack.Count == 0) + { + // No more objects to process - we're done. + return; + } + + this.ReadInternal(); + } + + break; + case JsonNodeType.Property: + { + BufferedObject bufferedObject = bufferedObjectStack.Peek(); + + // If there is a current property, mark its last value node. + if (bufferedObject.CurrentProperty != null) + { + bufferedObject.CurrentProperty.EndOfPropertyValueNode = this.currentBufferedNode.Previous; + } + + BufferedProperty bufferedProperty = new BufferedProperty(); + bufferedProperty.PropertyNameNode = this.currentBufferedNode; + + string propertyName; + string annotationName; + this.ReadPropertyName(out propertyName, out annotationName); + + bufferedProperty.PropertyAnnotationName = annotationName; + bufferedObject.AddBufferedProperty(propertyName, annotationName, bufferedProperty); + + if (annotationName != null) + { + // Instance-level property annotation - no reordering in its value + // or instance-level annotation - no reordering in its value either + // So skip its value while buffering. + this.BufferValue(); + } + } + + break; + + default: + // Read over (buffer) everything else + this.ReadInternal(); + break; + } + } + } + + /// + /// Reads a property name from the JSON reader and determines if it's a regular property, an instance annotation or a property annotation. + /// + /// The name of the regular property which the reader is positioned on or which a property annotation belongs to. + /// The name of the instance or property annotation, or null if the reader is on a regular property. + private void ReadPropertyName(out string propertyName, out string annotationName) + { + string jsonPropertyName = this.GetPropertyName(); + Debug.Assert(!string.IsNullOrEmpty(jsonPropertyName), "The JSON reader guarantees that property names are not null or empty."); + + this.ReadInternal(); + + if (jsonPropertyName.StartsWith("@", StringComparison.Ordinal)) + { + // Instance-level annotation for the instance itself; not property name. + propertyName = null; + annotationName = jsonPropertyName.Substring(1); + if (annotationName.IndexOf('.') == -1) + { + annotationName = JsonLightConstants.ODataAnnotationNamespacePrefix + annotationName; + } + } + else + { + int separatorIndex = jsonPropertyName.IndexOf(JsonLightConstants.ODataPropertyAnnotationSeparatorChar); + if (separatorIndex > 0) + { + // This is a property annotation; compute the property and annotation names + propertyName = jsonPropertyName.Substring(0, separatorIndex); + annotationName = jsonPropertyName.Substring(separatorIndex + 1); + } + else + { + // This is either a regular data property or an instance-level annotation + int dotIndex = jsonPropertyName.IndexOf('.'); + if (dotIndex < 0) + { + // Regular property + propertyName = jsonPropertyName; + annotationName = null; + } + else + { + if (ODataJsonLightUtils.IsMetadataReferenceProperty(jsonPropertyName)) + { + // Metadata reference property + propertyName = null; + annotationName = jsonPropertyName; + } + else + { + // unexpected instance annotation name + throw new ODataException(Microsoft.OData.Strings.JsonReaderExtensions_UnexpectedInstanceAnnotationName(jsonPropertyName)); + } + } + } + } + } + + /// + /// Reads over a value buffering it. + /// + private void BufferValue() + { + this.AssertBuffering(); + + // Skip the value bufferring it in the process. + int depth = 0; + do + { + switch (this.NodeType) + { + case JsonNodeType.PrimitiveValue: + break; + case JsonNodeType.StartArray: + case JsonNodeType.StartObject: + depth++; + break; + case JsonNodeType.EndArray: + case JsonNodeType.EndObject: + Debug.Assert(depth > 0, "Seen too many scope ends."); + depth--; + break; + default: + break; + } + + this.ReadInternal(); + } + while (depth > 0); + } + + /// + /// A data structure to represent the buffered object with information about its properties, + /// their order and annotations. + /// + private sealed class BufferedObject + { + /// The cache for properties. + /// The key is the property or instance annotation name, + /// the value are the buffered properties grouped by property name (incl. annotation properties). + private readonly Dictionary propertyCache; + + /// The set of data property names. + /// Data properties are the properties that are neither an instance annotation property nor a property annotation. + private readonly HashSet dataProperties; + + /// A list of property names with their annotation name. + /// This is needed to properly maintain the relative order of annotation properties if no data + /// property for the annotation property exists in the object. + private readonly List> propertyNamesWithAnnotations; + + /// + /// Constructor. + /// + internal BufferedObject() + { + this.propertyNamesWithAnnotations = new List>(); + this.dataProperties = new HashSet(StringComparer.Ordinal); + this.propertyCache = new Dictionary(StringComparer.Ordinal); + } + + /// + /// The node in the linked list of buffered nodes where this object starts. + /// + internal BufferedNode ObjectStart { get; set; } + + /// + /// The current buffered property being processed. + /// + internal BufferedProperty CurrentProperty { get; private set; } + + /// + /// Adds a new buffered property to the list of buffered properties for this object. + /// + /// The name of the data property (null for instance annotations). + /// The name of the annotation (null for data properties). + /// The buffered property to add. + internal void AddBufferedProperty(string propertyName, string annotationName, BufferedProperty bufferedProperty) + { + this.CurrentProperty = bufferedProperty; + string lookupName = propertyName ?? annotationName; + + // We have to record the relative positions of all properties so we can later on propertly sort them. + // Note that we have to also capture the positions of the property annotations as long as we have not + // seen a data property for that annotation since the data property might be missing. + if (propertyName == null) + { + this.propertyNamesWithAnnotations.Add(new KeyValuePair(annotationName, null)); + } + else if (!this.dataProperties.Contains(propertyName)) + { + if (annotationName == null) + { + this.dataProperties.Add(propertyName); + } + + this.propertyNamesWithAnnotations.Add(new KeyValuePair(propertyName, annotationName)); + } + + object storedValue; + if (this.propertyCache.TryGetValue(lookupName, out storedValue)) + { + Debug.Assert(storedValue != null, "storedValue != null"); + List storedProperties; + + BufferedProperty storedProperty = storedValue as BufferedProperty; + if (storedProperty != null) + { + storedProperties = new List(4); + storedProperties.Add(storedProperty); + this.propertyCache[lookupName] = storedProperties; + } + else + { + storedProperties = (List)storedValue; + } + + storedProperties.Add(bufferedProperty); + } + else + { + this.propertyCache.Add(lookupName, bufferedProperty); + } + } + + /// + /// Reorders the buffered properties to conform to the required payload order. + /// + /// + /// The required order is: odata.context comes first, odata.removed comes next (for deleted resources), + /// then comes odata.type, then all odata.* property annotations + /// and finally, we preserve the relative order of custom annotations and data properties. + internal void Reorder() + { + BufferedNode currentNode = this.ObjectStart; + + // Sort the property names preserving the relative order of the properties except for the OData instance annotations. + IEnumerable sortedPropertyNames = this.SortPropertyNames(); + foreach (string propertyName in sortedPropertyNames) + { + Debug.Assert(this.propertyCache.ContainsKey(propertyName), "Property must be in the cache for its name to be in the list of property names."); + object storedValue = this.propertyCache[propertyName]; + BufferedProperty storedProperty = storedValue as BufferedProperty; + if (storedProperty != null) + { + storedProperty.InsertAfter(currentNode); + currentNode = storedProperty.EndOfPropertyValueNode; + } + else + { + IEnumerable sortedProperties = SortBufferedProperties((IList)storedValue); + foreach (BufferedProperty sortedProperty in sortedProperties) + { + sortedProperty.InsertAfter(currentNode); + currentNode = sortedProperty.EndOfPropertyValueNode; + } + } + } + } + + /// + /// Sort the data properties and property annotations stored for a particular property name. + /// + /// The list of buffered properties to sort. + /// The sorted enumerable of buffered properties. + /// The sort order is for all odata.* property annotations to come before the data property + /// but otherwise preserve the relative order of custom property annotations with regard to the position of the data property. + private static IEnumerable SortBufferedProperties(IList bufferedProperties) + { + Debug.Assert(bufferedProperties != null && bufferedProperties.Count > 1, "bufferedProperties != null && bufferedProperties.Count > 1"); + + // NOTE: we re-order the property annotations so that ... + // 1) all odata.* property annotations come before the data property. + // 2) we preserve the relative order of custom property annotations with regard to the data property. + List delayedProperties = null; + for (int i = 0; i < bufferedProperties.Count; ++i) + { + BufferedProperty bufferedProperty = bufferedProperties[i]; + + string annotationName = bufferedProperty.PropertyAnnotationName; + if (annotationName == null || !IsODataInstanceAnnotation(annotationName)) + { + // This is either the data property or a custom annotation; we have to delay reporting it until we reported all OData.* annotations + if (delayedProperties == null) + { + delayedProperties = new List(); + } + + delayedProperties.Add(bufferedProperty); + } + else + { + yield return bufferedProperty; + } + } + + // If we delayed reporting of any property annotations, report them now preserving their relative order. + if (delayedProperties != null) + { + for (int i = 0; i < delayedProperties.Count; ++i) + { + yield return delayedProperties[i]; + } + } + } + + /// + /// Checks whether an annotation name is an odata.* annotation. + /// + /// The annotation name to check. + /// true if the annotation name represents an odata.* annotation; otherwise false. + private static bool IsODataInstanceAnnotation(string annotationName) + { + Debug.Assert(annotationName != null, "annotationName != null"); + return annotationName.StartsWith(JsonLightConstants.ODataAnnotationNamespacePrefix, StringComparison.Ordinal); + } + + /// + /// Checks whether an annotation name is a odata.context annotation. + /// + /// The annotation name to check. + /// true if the annotation name represents an odata.context annotation; otherwise false. + private static bool IsODataContextAnnotation(string annotationName) + { + Debug.Assert(annotationName != null, "annotationName != null"); + return string.CompareOrdinal(ODataAnnotationNames.ODataContext, annotationName) == 0; + } + + /// + /// Checks whether an annotation name is a odata.removed annotation. + /// + /// The annotation name to check. + /// true if the annotation name represents an odata.removed annotation; otherwise false. + private static bool IsODataRemovedAnnotation(string annotationName) + { + Debug.Assert(annotationName != null, "annotationName != null"); + return string.CompareOrdinal(ODataAnnotationNames.ODataRemoved, annotationName) == 0; + } + + /// + /// Checks whether an annotation name is a odata.type annotation. + /// + /// The annotation name to check. + /// true if the annotation name represents an odata.type annotation; otherwise false. + private static bool IsODataTypeAnnotation(string annotationName) + { + Debug.Assert(annotationName != null, "annotationName != null"); + return string.CompareOrdinal(ODataAnnotationNames.ODataType, annotationName) == 0; + } + + /// + /// Checks whether an annotation name is a odata.id annotation. + /// + /// The annotation name to check. + /// true if the annotation name represents an odata.id annotation; otherwise false. + private static bool IsODataIdAnnotation(string annotationName) + { + Debug.Assert(annotationName != null, "annotationName != null"); + return string.CompareOrdinal(ODataAnnotationNames.ODataId, annotationName) == 0; + } + + /// + /// Checks whether an annotation name is a odata.etag annotation. + /// + /// The annotation name to check. + /// true if the annotation name represents an odata.etag annotation; otherwise false. + private static bool IsODataETagAnnotation(string annotationName) + { + Debug.Assert(annotationName != null, "annotationName != null"); + return string.CompareOrdinal(ODataAnnotationNames.ODataETag, annotationName) == 0; + } + + /// + /// Sorts the property names for an object. + /// + /// The sorted enumerable of property names. + /// The sort order is to put odata.context first, then odata.type, odata.id, and odata.etag, followed by all other odata.* instance annotations. + /// For the rest, we preserve the relative order of custom annotations with regard to the data property. + /// Note that we choose the position of the first property annotation in cases where no data property for a set of + /// property annotations exists. + private IEnumerable SortPropertyNames() + { + string contextAnnotationName = null; + string removedAnnotationName = null; + string typeAnnotationName = null; + string idAnnotationName = null; + string etagAnnotationName = null; + List odataAnnotationNames = null; + List otherNames = null; + foreach (KeyValuePair propertyNameWithAnnotation in this.propertyNamesWithAnnotations) + { + string propertyName = propertyNameWithAnnotation.Key; + + // First ignore a property annotation if we found a data property for it (since we will use the + // position of the data property). To keep the property annotations is important for cases + // where no data property exists for a set of property annotations. + if (propertyNameWithAnnotation.Value != null && this.dataProperties.Contains(propertyName)) + { + continue; + } + else + { + // Add the property name to the 'dataProperties' to make sure we process the annotations for + // a property that is not in the payload at the position of the first such annotation. + this.dataProperties.Add(propertyName); + } + + // Then find the special properties 'odata.context', 'odata.type', 'odata.id', and 'odata.etag' before separating + // the rest into odata.* annotations and regular properties (and custom annotations). + if (IsODataContextAnnotation(propertyName)) + { + contextAnnotationName = propertyName; + } + else if (IsODataRemovedAnnotation(propertyName)) + { + removedAnnotationName = propertyName; + } + else if (IsODataTypeAnnotation(propertyName)) + { + typeAnnotationName = propertyName; + } + else if (IsODataIdAnnotation(propertyName)) + { + idAnnotationName = propertyName; + } + else if (IsODataETagAnnotation(propertyName)) + { + etagAnnotationName = propertyName; + } + else if (IsODataInstanceAnnotation(propertyName)) + { + if (odataAnnotationNames == null) + { + odataAnnotationNames = new List(); + } + + odataAnnotationNames.Add(propertyName); + } + else + { + if (otherNames == null) + { + otherNames = new List(); + } + + otherNames.Add(propertyName); + } + } + + if (contextAnnotationName != null) + { + yield return contextAnnotationName; + } + + if (removedAnnotationName != null) + { + yield return removedAnnotationName; + } + + if (typeAnnotationName != null) + { + yield return typeAnnotationName; + } + + if (idAnnotationName != null) + { + yield return idAnnotationName; + } + + if (etagAnnotationName != null) + { + yield return etagAnnotationName; + } + + if (odataAnnotationNames != null) + { + foreach (string propertyName in odataAnnotationNames) + { + yield return propertyName; + } + } + + if (otherNames != null) + { + foreach (string propertyName in otherNames) + { + yield return propertyName; + } + } + } + } + + /// + /// A data structure to represent a buffered property. + /// + private sealed class BufferedProperty + { + /// + /// The annotation name for this buffered property (either instance annotation or property annotation). + /// + internal string PropertyAnnotationName { get; set; } + + /// + /// The node in the linked list of buffered nodes that represents the property name of the buffered property. + /// + internal BufferedNode PropertyNameNode { get; set; } + + /// + /// The node in the linked list of buffered nodes that represents the end of the property value of the buffered property. + /// + internal BufferedNode EndOfPropertyValueNode { get; set; } + + /// + /// Reorders the buffered property to be positioned after the node. + /// + /// The node after which to insert this buffered property. + internal void InsertAfter(BufferedNode node) + { + Debug.Assert(node != null, "node != null"); + + // First take out the nodes representing the property from the linked list + BufferedNode predecessor = this.PropertyNameNode.Previous; + Debug.Assert(predecessor != null, "There must always be a predecessor for a property node."); + BufferedNode successor = this.EndOfPropertyValueNode.Next; + Debug.Assert(successor != null, "There should always be at least the end-object node after the property value."); + predecessor.Next = successor; + successor.Previous = predecessor; + + // Then insert the nodes representing the property after the 'insertAfter' node + successor = node.Next; + node.Next = this.PropertyNameNode; + this.PropertyNameNode.Previous = node; + this.EndOfPropertyValueNode.Next = successor; + if (successor != null) + { + successor.Previous = this.EndOfPropertyValueNode; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MediaTypeUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MediaTypeUtils.cs new file mode 100644 index 0000000..43abb02 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MediaTypeUtils.cs @@ -0,0 +1,1087 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; +#if PORTABLELIB + using System.Collections.Concurrent; +#endif + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + #endregion Namespaces + + /// + /// Class with utility methods to work with media types. + /// + internal static class MediaTypeUtils + { + /// An array of all the supported payload kinds. + private static readonly ODataPayloadKind[] allSupportedPayloadKinds = new ODataPayloadKind[] + { + ODataPayloadKind.ResourceSet, + ODataPayloadKind.Resource, + ODataPayloadKind.Property, + ODataPayloadKind.MetadataDocument, + ODataPayloadKind.ServiceDocument, + ODataPayloadKind.Value, + ODataPayloadKind.BinaryValue, + ODataPayloadKind.Collection, + ODataPayloadKind.EntityReferenceLinks, + ODataPayloadKind.EntityReferenceLink, + ODataPayloadKind.Batch, + ODataPayloadKind.Error, + ODataPayloadKind.Parameter, + ODataPayloadKind.IndividualProperty, + ODataPayloadKind.Delta, + ODataPayloadKind.Asynchronous + }; + + /// UTF-8 encoding, without the BOM preamble. + /// + /// While a BOM preamble on UTF8 is generally benign, it seems that some MIME handlers under IE6 will not + /// process the payload correctly when included. + /// + /// Because the data service should include the encoding as part of the Content-Type in the response, + /// there should be no ambiguity as to what encoding is being used. + /// + /// For further information, see http://www.unicode.org/faq/utf_bom.html#BOM. + /// + private static readonly UTF8Encoding encodingUtf8NoPreamble = new UTF8Encoding(false, true); + + /// + /// Max size to cache match info. + /// + private const int MatchInfoCacheMaxSize = 256; + + /// + /// Concurrent cache to cache match info. + /// + private static MatchInfoConcurrentCache MatchInfoCache = new MatchInfoConcurrentCache(MatchInfoCacheMaxSize); + + /// UTF-8 encoding, without the BOM preamble. + /// + /// While a BOM preamble on UTF8 is generally benign, it seems that some MIME handlers under IE6 will not + /// process the payload correctly when included. + /// + /// Because the data service should include the encoding as part of the Content-Type in the response, + /// there should be no ambiguity as to what encoding is being used. + /// + /// For further information, see http://www.unicode.org/faq/utf_bom.html#BOM. + /// + internal static UTF8Encoding EncodingUtf8NoPreamble + { + get + { + return encodingUtf8NoPreamble; + } + } + + /// Encoding to fall back to an appropriate encoding is not available. + internal static Encoding FallbackEncoding + { + get + { + return MediaTypeUtils.EncodingUtf8NoPreamble; + } + } + + /// Encoding implied by an unspecified encoding value. + /// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details. + internal static Encoding MissingEncoding + { + get + { +#if !ORCAS + return Encoding.UTF8; +#else + return Encoding.GetEncoding("ISO-8859-1", new EncoderExceptionFallback(), new DecoderExceptionFallback()); +#endif + } + } + + /// + /// Given the Accept and the Accept-Charset headers of the request message computes the media type, encoding and + /// to be used for the response message. + /// + /// The message writer settings to use for serializing the response payload. + /// The kind of payload to be serialized as part of the response message. + /// The media type resolver to use when interpreting the content type. + /// The media type to be used in the response message. + /// The encoding to be used in the response message. + /// The used when serializing the response. + internal static ODataFormat GetContentTypeFromSettings( + ODataMessageWriterSettings settings, + ODataPayloadKind payloadKind, + ODataMediaTypeResolver mediaTypeResolver, + out ODataMediaType mediaType, + out Encoding encoding) + { + Debug.Assert(settings != null, "settings != null"); + + // compute format, media type and encoding + ODataFormat format; + + // get the supported and default media types for the specified payload kind + IList supportedMediaTypes = mediaTypeResolver.GetMediaTypeFormats(payloadKind).ToList(); + if (supportedMediaTypes == null || supportedMediaTypes.Count == 0) + { + throw new ODataContentTypeException(Strings.MediaTypeUtils_DidNotFindMatchingMediaType(null, settings.AcceptableMediaTypes)); + } + + if (settings.UseFormat == true) + { + Debug.Assert(settings.AcceptableMediaTypes == null, "settings.AcceptableMediaTypes == null"); + Debug.Assert(settings.AcceptableCharsets == null, "settings.AcceptableCharsets == null"); + + mediaType = GetDefaultMediaType(supportedMediaTypes, settings.Format, out format); + + // NOTE the default media types don't have any parameters (in particular no 'charset' parameters) + encoding = mediaType.SelectEncoding(); + } + else + { + // parse the accept header into its parts + IList> specifiedTypes = HttpUtils.MediaTypesFromString(settings.AcceptableMediaTypes); + + // Starting in V3 we replace all occurrences of application/json with application/json;odata.metadata=minimal + // before handing the acceptable media types to the conneg code. This is necessary because for an accept + // header 'application/json, we want to the result to be 'application/json;odata.metadata=minimal' + ConvertApplicationJsonInAcceptableMediaTypes(specifiedTypes, settings.Version ?? ODataVersion.V4); + + ODataMediaTypeFormat selectedMediaTypeWithFormat; + string specifiedCharset = null; + if (specifiedTypes == null || specifiedTypes.Count == 0) + { + selectedMediaTypeWithFormat = supportedMediaTypes[0]; + } + else + { + // match the specified media types against the supported/default ones and get the format + MediaTypeMatchInfo matchInfo = MatchMediaTypes(specifiedTypes.Select(kvp => kvp.Key), supportedMediaTypes.Select(smt => smt.MediaType).ToArray()); + if (matchInfo == null) + { + // We're calling the ToArray here since not all platforms support the string.Join which takes IEnumerable. + string supportedTypesAsString = String.Join(", ", supportedMediaTypes.Select(mt => mt.MediaType.ToText()).ToArray()); + throw new ODataContentTypeException(Strings.MediaTypeUtils_DidNotFindMatchingMediaType(supportedTypesAsString, settings.AcceptableMediaTypes)); + } + + selectedMediaTypeWithFormat = supportedMediaTypes[matchInfo.TargetTypeIndex]; + specifiedCharset = specifiedTypes[matchInfo.SourceTypeIndex].Value; + } + + format = selectedMediaTypeWithFormat.Format; + mediaType = selectedMediaTypeWithFormat.MediaType; + + // If any accept types in request used non-prefixed odata metadata or streaming type parameter, + // use the non-prefixed version in the response + if (specifiedTypes != null && mediaType.Parameters != null) + { + if (specifiedTypes.Any(t => t.Key.Parameters != null && t.Key.Parameters.Any(p => String.Compare(p.Key, MimeConstants.MimeShortMetadataParameterName, StringComparison.OrdinalIgnoreCase) == 0))) + { + mediaType = new ODataMediaType(mediaType.Type, mediaType.SubType, mediaType.Parameters.Select(p => new KeyValuePair(String.Compare(p.Key, MimeConstants.MimeMetadataParameterName, StringComparison.OrdinalIgnoreCase) == 0 ? MimeConstants.MimeShortMetadataParameterName : p.Key, p.Value))); + } + + if (specifiedTypes.Any(t => t.Key.Parameters != null && t.Key.Parameters.Any(p => String.Compare(p.Key, MimeConstants.MimeShortStreamingParameterName, StringComparison.OrdinalIgnoreCase) == 0))) + { + mediaType = new ODataMediaType(mediaType.Type, mediaType.SubType, mediaType.Parameters.Select(p => new KeyValuePair(String.Compare(p.Key, MimeConstants.MimeStreamingParameterName, StringComparison.OrdinalIgnoreCase) == 0 ? MimeConstants.MimeShortStreamingParameterName : p.Key, p.Value))); + } + } + + // If a charset was specified with the accept header, consider it for the encoding + string acceptableCharsets = settings.AcceptableCharsets; + if (specifiedCharset != null) + { + acceptableCharsets = acceptableCharsets == null ? specifiedCharset : specifiedCharset + "," + acceptableCharsets; + } + + encoding = GetEncoding(acceptableCharsets, payloadKind, mediaType, /*useDefaultEncoding*/ true); + } + + return format; + } + + /// + /// Gets all payload kinds and their corresponding formats that match the specified content type header. + /// + /// The content type header to get the payload kinds for. + /// The media type resolver to use when interpreting the content type. + /// The parsed content type as . + /// The encoding from the content type or the default encoding from . + /// The list of payload kinds and formats supported for the specified . + internal static IList GetPayloadKindsForContentType(string contentTypeHeader, ODataMediaTypeResolver mediaTypeResolver, out ODataMediaType contentType, out Encoding encoding) + { + Debug.Assert(!String.IsNullOrEmpty(contentTypeHeader), "Content-Type header must not be null or empty."); + + string charset; + encoding = null; + contentType = ParseContentType(contentTypeHeader, out charset); + ODataMediaType[] targetTypes = new ODataMediaType[] { contentType }; + + List payloadKinds = new List(); + + IList mediaTypesForKind = null; + for (int i = 0; i < allSupportedPayloadKinds.Length; ++i) + { + // get the supported and default media types for the current payload kind + ODataPayloadKind payloadKind = allSupportedPayloadKinds[i]; + mediaTypesForKind = mediaTypeResolver.GetMediaTypeFormats(payloadKind).ToList(); + + // match the specified media types against the supported/default ones + // and get the format + MediaTypeMatchInfo matchInfo = MatchMediaTypes(mediaTypesForKind.Select(smt => smt.MediaType), targetTypes); + if (matchInfo != null) + { + Debug.Assert(matchInfo.TargetTypeIndex == 0, "Invalid target type index detected."); + payloadKinds.Add(new ODataPayloadKindDetectionResult(payloadKind, mediaTypesForKind[matchInfo.SourceTypeIndex].Format)); + } + } + + if (!String.IsNullOrEmpty(charset)) + { + encoding = HttpUtils.GetEncodingFromCharsetName(charset); + } + + return payloadKinds; + } + + /// + /// Checks whether two media types with subtypes (but without parameters) are equal. + /// + /// The first media type and subtype. + /// The second media type and subtype. + /// true if the is equal to ; otherwise false. + internal static bool MediaTypeAndSubtypeAreEqual(string firstTypeAndSubtype, string secondTypeAndSubtype) + { + ExceptionUtils.CheckArgumentNotNull(firstTypeAndSubtype, "firstTypeAndSubtype"); + ExceptionUtils.CheckArgumentNotNull(secondTypeAndSubtype, "secondTypeAndSubtype"); + + return HttpUtils.CompareMediaTypeNames(firstTypeAndSubtype, secondTypeAndSubtype); + } + + /// + /// Checks whether a media type starts with the expected type and subtype. + /// + /// The media type to check. + /// The type and subtype the should start with. + /// true if the starts with ; otherwise false. + internal static bool MediaTypeStartsWithTypeAndSubtype(string mediaType, string typeAndSubtype) + { + ExceptionUtils.CheckArgumentNotNull(mediaType, "mediaType"); + ExceptionUtils.CheckArgumentNotNull(typeAndSubtype, "typeAndSubtype"); + + return mediaType.StartsWith(typeAndSubtype, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Checks whether the specified media type has a parameter with the expected value. + /// + /// The media type to check the parameters for. + /// The name of the expected parameter. + /// The value of the expected parameter. + /// true if the has a parameter called + /// with value ; otherwise false. + internal static bool MediaTypeHasParameterWithValue(this ODataMediaType mediaType, string parameterName, string parameterValue) + { + Debug.Assert(mediaType != null, "mediaType != null"); + Debug.Assert(parameterName != null, "parameterName != null"); + + if (mediaType.Parameters == null) + { + return false; + } + + return mediaType.Parameters.Any(p => + HttpUtils.CompareMediaTypeParameterNames(p.Key, parameterName) && + String.Compare(p.Value, parameterValue, StringComparison.OrdinalIgnoreCase) == 0); + } + + /// + /// Determines whether the media type has a 'streaming' parameter with the value 'true'. + /// + /// The media type to check. + /// + /// true if the media type has a 'streaming' parameter with the value 'true'; otherwise, false. + /// + internal static bool HasStreamingSetToTrue(this ODataMediaType mediaType) + { + return mediaType.MediaTypeHasParameterWithValue(MimeConstants.MimeStreamingParameterName, MimeConstants.MimeParameterValueTrue); + } + + /// + /// Determines whether the media type has a 'IEEE754Compatible' parameter with the value 'true'. + /// + /// The media type to check + /// + /// true if the media type has a 'IEEE754Compatible' parameter with the value 'true'; otherwise, false. + /// + internal static bool HasIeee754CompatibleSetToTrue(this ODataMediaType mediaType) + { + return mediaType.MediaTypeHasParameterWithValue(MimeConstants.MimeIeee754CompatibleParameterName, MimeConstants.MimeParameterValueTrue); + } + + /// + /// Checks for wildcard characters in the . + /// + /// The to check. + internal static void CheckMediaTypeForWildCards(ODataMediaType mediaType) + { + Debug.Assert(mediaType != null, "mediaType != null"); + + if (HttpUtils.CompareMediaTypeNames(MimeConstants.MimeStar, mediaType.Type) || + HttpUtils.CompareMediaTypeNames(MimeConstants.MimeStar, mediaType.SubType)) + { + throw new ODataContentTypeException(Strings.ODataMessageReader_WildcardInContentType(mediaType.FullTypeName)); + } + } + + /// + /// JSONP - instead of writing 'application/json', we write 'text/javascript'. In all other ways we pretend it is JSON + /// + /// Original content-type value string. + /// New content-type value string. + internal static string AlterContentTypeForJsonPadding(string contentType) + { + if (contentType.StartsWith(MimeConstants.MimeApplicationJson, StringComparison.OrdinalIgnoreCase)) + { + return contentType.Remove(0, MimeConstants.MimeApplicationJson.Length).Insert(0, MimeConstants.TextJavaScript); + } + + if (contentType.StartsWith(MimeConstants.MimeTextPlain, StringComparison.OrdinalIgnoreCase)) + { + return contentType.Remove(0, MimeConstants.MimeTextPlain.Length).Insert(0, MimeConstants.TextJavaScript); + } + + throw new ODataException(Strings.ODataMessageWriter_JsonPaddingOnInvalidContentType(contentType)); + } + + /// + /// Determine the to use for the given . If no supported content type + /// is found an exception is thrown. + /// + /// The name of the content type to be checked. + /// All possible kinds of payload that can be read with this content type. + /// The media type resolver to use when interpreting the content type. + /// The media type parsed from the . + /// The encoding from the content type or the default encoding for the . + /// + /// The payload kind that was selected form the list of for the + /// specified . + /// + /// The for the . + internal static ODataFormat GetFormatFromContentType(string contentTypeName, ODataPayloadKind[] supportedPayloadKinds, ODataMediaTypeResolver mediaTypeResolver, out ODataMediaType mediaType, out Encoding encoding, out ODataPayloadKind selectedPayloadKind) + { + Debug.Assert(!supportedPayloadKinds.Contains(ODataPayloadKind.Unsupported), "!supportedPayloadKinds.Contains(ODataPayloadKind.Unsupported)"); + + string charset; + mediaType = ParseContentType(contentTypeName, out charset); + + IList supportedMediaTypes = null; + for (int i = 0; i < supportedPayloadKinds.Length; ++i) + { + // get the supported and default media types for the current payload kind + ODataPayloadKind supportedPayloadKind = supportedPayloadKinds[i]; + supportedMediaTypes = mediaTypeResolver.GetMediaTypeFormats(supportedPayloadKind).ToList(); + + // match the specified media types against the supported/default ones + // and get the format + var cacheKey = new MatchInfoCacheKey(mediaTypeResolver, supportedPayloadKind, contentTypeName); + MediaTypeMatchInfo matchInfo; + + if (!MatchInfoCache.TryGetValue(cacheKey, out matchInfo)) + { + matchInfo = MatchMediaTypes(supportedMediaTypes.Select(smt => smt.MediaType), new[] { mediaType }); + MatchInfoCache.Add(cacheKey, matchInfo); + } + + if (matchInfo != null) + { + Debug.Assert(matchInfo.TargetTypeIndex == 0, "Invalid target type index detected."); + selectedPayloadKind = supportedPayloadKind; + encoding = GetEncoding(charset, selectedPayloadKind, mediaType, /*useDefaultEncoding*/ false); + return supportedMediaTypes[matchInfo.SourceTypeIndex].Format; + } + } + + // We're calling the ToArray here since not all platforms support the string.Join which takes IEnumerable. + string supportedTypesAsString = String.Join(", ", supportedPayloadKinds.SelectMany(pk => mediaTypeResolver.GetMediaTypeFormats(pk).Select(mt => mt.MediaType.ToText())).ToArray()); + throw new ODataContentTypeException(Strings.MediaTypeUtils_CannotDetermineFormatFromContentType(supportedTypesAsString, contentTypeName)); + } + + /// + /// Parses the specified content type header into a media type instance. + /// + /// The content type header to parse. + /// The optional charset specified with the content type. + /// The of the parsed . + private static ODataMediaType ParseContentType(string contentTypeHeader, out string charset) + { + Debug.Assert(!String.IsNullOrEmpty(contentTypeHeader), "!string.IsNullOrEmpty(contentTypeHeader)"); + + // parse the content type header into its parts, make sure we only allow one content type to be specified. + IList> specifiedTypes = HttpUtils.MediaTypesFromString(contentTypeHeader); + if (specifiedTypes.Count != 1) + { + throw new ODataContentTypeException(Strings.MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified(contentTypeHeader)); + } + + ODataMediaType contentType = specifiedTypes[0].Key; + CheckMediaTypeForWildCards(contentType); + charset = specifiedTypes[0].Value; + return contentType; + } + + /// + /// Gets the default media type for a given payload kind in a given format. + /// + /// A list of supported media types and formats. + /// The user-specified format in which to write the payload (can be null). + /// The default format for the specified payload kind + /// The default media type for the given payload kind and format. + private static ODataMediaType GetDefaultMediaType( + IList supportedMediaTypes, + ODataFormat specifiedFormat, + out ODataFormat actualFormat) + { + for (int i = 0; i < supportedMediaTypes.Count; ++i) + { + // NOTE: the supportedMediaTypes are sorted (DESC) by format and media type; so the + // default format and media type is the first resource in the list + ODataMediaTypeFormat supportedMediaType = supportedMediaTypes[i]; + if (specifiedFormat == null || supportedMediaType.Format == specifiedFormat) + { + actualFormat = supportedMediaType.Format; + return supportedMediaType.MediaType; + } + } + + throw new ODataException(Strings.ODataUtils_DidNotFindDefaultMediaType(specifiedFormat)); + } + + /// + /// Parses the accepted char sets and matches them against the supported encodings for the given . + /// + /// The Accept-Charset header of the request. + /// The for which to compute the encoding. + /// The media type used to compute the default encoding for the payload. + /// true if the default encoding should be returned if no acceptable charset is found; otherwise false. + /// The encoding to be used for the response. + private static Encoding GetEncoding(string acceptCharsetHeader, ODataPayloadKind payloadKind, ODataMediaType mediaType, bool useDefaultEncoding) + { + if (payloadKind == ODataPayloadKind.BinaryValue) + { + return null; + } + + return HttpUtils.EncodingFromAcceptableCharsets( + acceptCharsetHeader, + mediaType, + /*utf8Encoding*/ encodingUtf8NoPreamble, + /*useDefaultEncoding*/ useDefaultEncoding ? encodingUtf8NoPreamble : null); + } + + /// + /// Matches the supported media types against the list of media types specified in the Accept header or ContentType header of the message. Matching follows the + /// rules for media type matching as described in RFC 2616. + /// + /// The set of media types to be matched against the . + /// The set of media types the will be matched against. + /// The best found during the matching process or null if no match was found. + private static MediaTypeMatchInfo MatchMediaTypes(IEnumerable sourceTypes, ODataMediaType[] targetTypes) + { + Debug.Assert(sourceTypes != null, "sourceTypes != null"); + Debug.Assert(targetTypes != null, "targetTypes != null"); + + MediaTypeMatchInfo selectedMatchInfo = null; + + int sourceIndex = 0; + if (sourceTypes != null) + { + foreach (ODataMediaType sourceType in sourceTypes) + { + int targetIndex = 0; + foreach (ODataMediaType targetType in targetTypes) + { + // match the type name parts and parameters of the media type + MediaTypeMatchInfo currentMatchInfo = new MediaTypeMatchInfo(sourceType, targetType, sourceIndex, targetIndex); + if (!currentMatchInfo.IsMatch) + { + targetIndex++; + continue; + } + + if (selectedMatchInfo == null) + { + selectedMatchInfo = currentMatchInfo; + } + else + { + int comparisonResult = selectedMatchInfo.CompareTo(currentMatchInfo); + if (comparisonResult < 0) + { + // If the selected match is less specific than the current match, use the current match. + selectedMatchInfo = currentMatchInfo; + } + } + + targetIndex++; + } + + sourceIndex++; + } + } + + if (selectedMatchInfo == null) + { + return null; + } + + return selectedMatchInfo; + } + + /// + /// Converts all occurrences of the 'application/json' media type to 'application/json;odata.metadata=minimal'. + /// This is necessary because for an accept header 'application/json + /// we want the result to be 'application/json;odata.metadata=minimal' + /// + /// The parsed acceptable media types. + /// The ODataVersion for which to convert the 'application/json' media type + private static void ConvertApplicationJsonInAcceptableMediaTypes(IList> specifiedTypes, ODataVersion version) + { + if (specifiedTypes == null) + { + return; + } + + for (int i = 0; i < specifiedTypes.Count; ++i) + { + ODataMediaType mediaType = specifiedTypes[i].Key; + if (HttpUtils.CompareMediaTypeNames(mediaType.SubType, MimeConstants.MimeJsonSubType) && + HttpUtils.CompareMediaTypeNames(mediaType.Type, MimeConstants.MimeApplicationType)) + { + if (mediaType.Parameters == null || !mediaType.Parameters.Any(p => HttpUtils.IsMetadataParameter(p.Key))) + { + // application/json detected; convert it to Json Light + IList> existingParams = mediaType.Parameters != null ? mediaType.Parameters.ToList() : null; + int newCount = existingParams == null ? 1 : existingParams.Count + 1; + List> newParams = new List>(newCount); + newParams.Add(new KeyValuePair( + version < ODataVersion.V401 ? MimeConstants.MimeMetadataParameterName : MimeConstants.MimeShortMetadataParameterName, + MimeConstants.MimeMetadataParameterValueMinimal)); + if (existingParams != null) + { + newParams.AddRange(existingParams); + } + + specifiedTypes[i] = new KeyValuePair(new ODataMediaType(mediaType.Type, mediaType.SubType, newParams), specifiedTypes[i].Value); + } + } + } + } + + /// + /// Class representing the result of matching two instances. + /// + private sealed class MediaTypeMatchInfo : IComparable + { + /// The default quality value (in the normalized range from 0 .. 1000). + private const int DefaultQualityValue = 1000; + + /// Index of the source type in the list of all source types. + private readonly int sourceIndex; + + /// Index of the target type in the list of all target types. + private readonly int targetIndex; + + /// + /// Constructor. + /// + /// The source to match against the target type. + /// The target to match against the source type. + /// Index of the source type in the list of all source types. + /// Index of the target type in the list of all target types. + public MediaTypeMatchInfo(ODataMediaType sourceType, ODataMediaType targetType, int sourceIndex, int targetIndex) + { + Debug.Assert(sourceType != null, "sourceType != null"); + Debug.Assert(targetType != null, "targetType != null"); + + this.sourceIndex = sourceIndex; + this.targetIndex = targetIndex; + + this.MatchTypes(sourceType, targetType); + } + + /// + /// Index of the source type in the list of all source types. + /// + public int SourceTypeIndex + { + get + { + return this.sourceIndex; + } + } + + /// + /// Index of the target type in the list of all target types. + /// + public int TargetTypeIndex + { + get + { + return this.targetIndex; + } + } + + /// + /// Represents the number of non-* matching type name parts or -1 if not matching at all. + /// + public int MatchingTypeNamePartCount + { + get; + private set; + } + + /// + /// Represents the number of matching parameters or -1 if neither the source type nor the target type have parameters. + /// + public int MatchingParameterCount + { + get; + private set; + } + + /// The quality value of the target type (or -1 if none is specified). + public int QualityValue + { + get; + private set; + } + + /// + /// The number of parameters of the source type that are used for comparison. All accept-parameters are ignored. + /// + public int SourceTypeParameterCountForMatching + { + get; + private set; + } + + /// + /// true if this represents a valid match (i.e., the source and target types match/are compatible); otherwise false. + /// + /// + /// Two types are considered compatible if at least one type name part matches (or we are dealing with a wildcard) + /// and all the parameters in the source type have been matched. + /// + public bool IsMatch + { + get + { + if (this.QualityValue == 0) + { + return false; + } + + if (this.MatchingTypeNamePartCount < 0) + { + // if none of the type name parts match, the types are not compatible; continue with next acceptable type. + return false; + } + + if (this.MatchingTypeNamePartCount > 1) + { + // make sure we matched all the parameters in the source type + if (this.MatchingParameterCount != -1 && this.MatchingParameterCount < this.SourceTypeParameterCountForMatching) + { + return false; + } + } + + return true; + } + } + + /// + /// Implementation of . + /// + /// The to compare against. + /// + /// -1 if this instance is a worse match than . + /// 0 if both matches are the same. + /// 1 if is a better match than this instance. + /// + public int CompareTo(MediaTypeMatchInfo other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + + if (this.MatchingTypeNamePartCount > other.MatchingTypeNamePartCount) + { + // the current match info matches more type name parts: choose it. + return 1; + } + + if (this.MatchingTypeNamePartCount == other.MatchingTypeNamePartCount) + { + // Now check the parameters and see whether we find a more specific match. + if (this.MatchingParameterCount > other.MatchingParameterCount) + { + return 1; + } + + if (this.MatchingParameterCount == other.MatchingParameterCount) + { + // check the quality value + int qualityComparison = this.QualityValue.CompareTo(other.QualityValue); + if (qualityComparison == 0) + { + // if they also have the same quality value, we choose the one + // that appears earlier in the conneg tables; that's the one with the smaller target type index. + return other.TargetTypeIndex < this.TargetTypeIndex ? -1 : 1; + } + + return qualityComparison; + } + } + + return -1; + } + + /// Selects a quality value for the specified type. + /// The text representation of the quality value. + /// The quality value, in range from 0 through 1000. + /// See http://tools.ietf.org/html/rfc2616#section-14.1 for further details. + private static int ParseQualityValue(string qualityValueText) + { + int qualityValue = DefaultQualityValue; + if (qualityValueText.Length > 0) + { + int textIndex = 0; + HttpUtils.ReadQualityValue(qualityValueText, ref textIndex, out qualityValue); + } + + return qualityValue; + } + + /// + /// Tries to find a parameter with the specified in the given list of parameters. + /// Does not include accept extensions (i.e., parameters after the q quality value parameter) + /// + /// The list of parameters to search. + /// The name of the parameter to find. + /// The parameter value of the parameter with the specified . + /// True if a parameter with the specified was found; otherwise false. + private static bool TryFindMediaTypeParameter(IList> parameters, string parameterName, out string parameterValue) + { + Debug.Assert(!String.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + + parameterValue = null; + + if (parameters != null) + { + for (int i = 0; i < parameters.Count; ++i) + { + string candidateParameterName = parameters[i].Key; + + if (HttpUtils.CompareMediaTypeParameterNames(parameterName, candidateParameterName)) + { + parameterValue = parameters[i].Value; + return true; + } + } + } + + return false; + } + + /// + /// Returns a flag indicating whether a given media type parameter name is the Http quality value parameter. + /// + /// The parameter name to check. + /// True if the parameter name is for the quality value; otherwise false. + private static bool IsQualityValueParameter(string parameterName) + { + return HttpUtils.CompareMediaTypeParameterNames(ODataConstants.HttpQValueParameter, parameterName); + } + + /// + /// Matches the source type against the media type. + /// + /// The source to match against the target type. + /// The target to match against the source type. + private void MatchTypes(ODataMediaType sourceType, ODataMediaType targetType) + { + this.MatchingTypeNamePartCount = -1; + + if (sourceType.Type == "*") + { + this.MatchingTypeNamePartCount = 0; + } + else + { + if (HttpUtils.CompareMediaTypeNames(sourceType.Type, targetType.Type)) + { + if (sourceType.SubType == "*") + { + // only type matches + this.MatchingTypeNamePartCount = 1; + } + else if (HttpUtils.CompareMediaTypeNames(sourceType.SubType, targetType.SubType)) + { + // both type and subtype match + this.MatchingTypeNamePartCount = 2; + } + } + } + + this.QualityValue = DefaultQualityValue; + this.SourceTypeParameterCountForMatching = 0; + this.MatchingParameterCount = 0; + + IList> sourceParameters = sourceType.Parameters != null ? sourceType.Parameters.ToList() : null; + IList> targetParameters = targetType.Parameters != null ? targetType.Parameters.ToList() : null; + bool targetHasParams = targetParameters != null && targetParameters.Count > 0; + bool sourceHasParams = sourceParameters != null && sourceParameters.Count > 0; + + if (sourceHasParams) + { + for (int i = 0; i < sourceParameters.Count; ++i) + { + string parameterName = sourceParameters[i].Key; + if (IsQualityValueParameter(parameterName)) + { + // once we hit the q-value in the parameters we know that only accept-params will follow + // that don't contribute to the matching. Parse the quality value but don't continue processing + // parameters. + this.QualityValue = ParseQualityValue(sourceParameters[i].Value.Trim()); + break; + } + + this.SourceTypeParameterCountForMatching = i + 1; + + if (targetHasParams) + { + // find the current parameter name in the set of parameters of the candidate and compare the value; + // if they match increase the result count + // NOTE: according to RFC 2045, Section 2, parameter values are case sensitive per default (while + // type values, subtype values and parameter names are case-insensitive); however, we + // are more relaxed in ODL and allow case insensitive values. + string parameterValue; + if (TryFindMediaTypeParameter(targetParameters, parameterName, out parameterValue) && + String.Compare(sourceParameters[i].Value.Trim(), parameterValue.Trim(), StringComparison.OrdinalIgnoreCase) == 0) + { + this.MatchingParameterCount++; + } + } + } + } + + // if the source does not have parameters or it only has accept extensions + // (parameters after the q value) or we match all the parameters we + // have a perfect parameter match. + if (!sourceHasParams || + this.SourceTypeParameterCountForMatching == 0 || + this.MatchingParameterCount == this.SourceTypeParameterCountForMatching) + { + this.MatchingParameterCount = -1; + } + } + } + + /// + /// Class representing key of match info cache. + /// + private sealed class MatchInfoCacheKey : IEquatable + { + /// + /// Constructor. + /// + /// The media type resolver to use. + /// Kind of the payload. + /// Name of content type. + public MatchInfoCacheKey(ODataMediaTypeResolver resolver, ODataPayloadKind payloadKind, string contentTypeName) + { + this.MediaTypeResolver = resolver; + this.PayloadKind = payloadKind; + this.ContentTypeName = contentTypeName; + } + + /// + /// Type of the mediaTypeResolver. + /// + private ODataMediaTypeResolver MediaTypeResolver { get; set; } + + /// + /// Kind of the payload. + /// + private ODataPayloadKind PayloadKind { get; set; } + + /// + /// Name of content type. + /// + private string ContentTypeName { get; set; } + + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// An object to compare with this instance. + /// true if obj is equal to this instance; otherwise, false. + public override bool Equals(object obj) + { + return this.Equals(obj as MatchInfoCacheKey); + } + + /// + /// Returns a value indicating whether this instance is equal to a specified . + /// + /// An object to compare with this instance. + /// true if obj is equal to this instance; otherwise, false. + public bool Equals(MatchInfoCacheKey other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return this.MediaTypeResolver == other.MediaTypeResolver && + this.PayloadKind == other.PayloadKind && + this.ContentTypeName == other.ContentTypeName; + } + + /// + /// Returns the hash code for the value of this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + int result = this.MediaTypeResolver.GetHashCode() ^ this.PayloadKind.GetHashCode(); + return this.ContentTypeName != null ? result ^ this.ContentTypeName.GetHashCode() : result; + } + } + +#if PORTABLELIB + /// + /// Class representing the concurrent cache for match info. + /// + private sealed class MatchInfoConcurrentCache + { + /// + /// The dictionary to save elements. + /// + private readonly ConcurrentDictionary dict; + + /// + /// Constructor. + /// + /// Max size of the elements that the cache can contain. + public MatchInfoConcurrentCache(int maxSize) + { + this.dict = new ConcurrentDictionary(4, maxSize); + } + + /// + /// Gets the value associated with the specified key. + /// + /// The key whose value to get. + /// The value associated with the specified key, if the key is found; otherwise, null. + /// true if the cache contains an element with the specified key; otherwise, false. + public bool TryGetValue(MatchInfoCacheKey key, out MediaTypeMatchInfo value) + { + return this.dict.TryGetValue(key, out value); + } + + /// + /// Adds an element with the provided key and value to the cache. + /// + /// The key of the element to add. + /// The value of the element to add. + public void Add(MatchInfoCacheKey key, MediaTypeMatchInfo value) + { + try + { + // Try to add the key to the dictionary. If we are overflowing + // clear the dictionary and attempt to add the entry again. + this.dict.TryAdd(key, value); + } + catch (OverflowException) + { + this.dict.Clear(); + this.dict.TryAdd(key, value); + } + } + } +#else + /// + /// Class representing the concurrent cache for match info. + /// + private sealed class MatchInfoConcurrentCache + { + /// + /// Max size of the elements that the cache can contain. + /// + private readonly int maxSize; + + /// + /// The dictionary to save elements. + /// + private IDictionary dict; + + /// + /// Constructor. + /// + /// Max size of the elements that the cache can contain. + public MatchInfoConcurrentCache(int maxSize) + { + this.maxSize = maxSize; + this.dict = new Dictionary(maxSize + 1); + } + + /// + /// Gets the value associated with the specified key. + /// + /// The key whose value to get. + /// The value associated with the specified key, if the key is found; otherwise, null. + /// true if the cache contains an element with the specified key; otherwise, false. + public bool TryGetValue(MatchInfoCacheKey key, out MediaTypeMatchInfo value) + { + lock (this.dict) + { + return dict.TryGetValue(key, out value); + } + } + + /// + /// Adds an element with the provided key and value to the cache. + /// + /// The key of the element to add. + /// The value of the element to add. + public void Add(MatchInfoCacheKey key, MediaTypeMatchInfo value) + { + lock (this.dict) + { + if (!dict.ContainsKey(key)) + { + if (dict.Count == maxSize) + { + dict.Clear(); + } + + dict.Add(key, value); + } + } + } + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MessageStreamWrapper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MessageStreamWrapper.cs new file mode 100644 index 0000000..72b1272 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MessageStreamWrapper.cs @@ -0,0 +1,324 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading; + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Factory class for the various wrapper streams around the actual message stream. + /// + internal static class MessageStreamWrapper + { + /// + /// Creates a non-disposing stream. + /// + /// The inner to wrap. + /// A stream wrapping the that ignores calls to Dispose. + internal static Stream CreateNonDisposingStream(Stream innerStream) + { + Debug.Assert(innerStream != null, "innerStream != null"); + + return new MessageStreamWrappingStream(innerStream, /*ignoreDispose*/ true, /*maxBytesToBeRead*/ -1); + } + + /// + /// Creates a stream with a given maximum size. + /// + /// The inner to wrap. + /// The maximum number of bytes to be read from the . + /// A stream wrapping the that + /// enforces the maximum number of bytes to be read from the stream. + internal static Stream CreateStreamWithMaxSize(Stream innerStream, long maxBytesToBeRead) + { + Debug.Assert(innerStream != null, "innerStream != null"); + + return new MessageStreamWrappingStream(innerStream, /*ignoreDispose*/ false, maxBytesToBeRead); + } + + /// + /// Creates a non-disposing stream with a given maximum size. + /// + /// The inner to wrap. + /// The maximum number of bytes to be read from the . + /// A stream wrapping the that ignores calls to Dispose and + /// enforces the maximum number of bytes to be read from the stream. + internal static Stream CreateNonDisposingStreamWithMaxSize(Stream innerStream, long maxBytesToBeRead) + { + Debug.Assert(innerStream != null, "innerStream != null"); + + return new MessageStreamWrappingStream(innerStream, /*ignoreDispose*/ true, maxBytesToBeRead); + } + + /// + /// Checks whether the provided stream already ignores calls to Dispose. + /// + /// The to check. + /// true if the ignores calls to Dispose; otherwise false. + internal static bool IsNonDisposingStream(Stream stream) + { + Debug.Assert(stream != null, "stream != null"); + + MessageStreamWrappingStream wrappingStream = stream as MessageStreamWrappingStream; + return wrappingStream != null && wrappingStream.IgnoreDispose; + } + + /// + /// Stream wrapper that supports counting the total number of bytes read from the stream and ensures + /// that they don't exceed a specified maximum (used for security purposes) and ignoring calls + /// to Dispose if the underlying stream should not be disposed. + /// + private sealed class MessageStreamWrappingStream : Stream + { + /// The maximum number of bytes to be read from the stream before reporting an error. + private readonly long maxBytesToBeRead; + + /// true to not dispose the inner stream when Dispose is called; otherwise false. + private readonly bool ignoreDispose; + + /// Stream that is being wrapped. + private Stream innerStream; + + /// The total number of bytes read from the stream so far. + private long totalBytesRead; + + /// + /// Constructs an instance of the byte counting stream wrapper class. + /// + /// Stream that is being wrapped. + /// true if calls to Dispose should be ignored; otherwise false. + /// The maximum number of bytes to be read from the stream before reporting an error. + internal MessageStreamWrappingStream(Stream innerStream, bool ignoreDispose, long maxBytesToBeRead) + { + Debug.Assert(innerStream != null, "innerStream != null"); + + this.innerStream = innerStream; + this.ignoreDispose = ignoreDispose; + this.maxBytesToBeRead = maxBytesToBeRead; + } + + /// + /// Determines if the stream can read. + /// + public override bool CanRead + { + get { return this.innerStream.CanRead; } + } + + /// + /// Determines if the stream can seek. + /// + public override bool CanSeek + { + get { return this.innerStream.CanSeek; } + } + + /// + /// Determines if the stream can write. + /// + public override bool CanWrite + { + get { return this.innerStream.CanWrite; } + } + + /// + /// Returns the length of the stream. + /// + public override long Length + { + get { return this.innerStream.Length; } + } + + /// + /// Gets or sets the position in the stream. + /// + public override long Position + { + get { return this.innerStream.Position; } + set { this.innerStream.Position = value; } + } + + /// true if the wrapping stream ignores calls to Dispose; otherwise false. + internal bool IgnoreDispose + { + get + { + return this.ignoreDispose; + } + } + + /// + /// Flush the stream to the underlying storage. + /// + public override void Flush() + { + this.innerStream.Flush(); + } + + /// + /// Reads data from the stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + int bytesRead = this.innerStream.Read(buffer, offset, count); + this.IncreaseTotalBytesRead(bytesRead); + return bytesRead; + } + +#if PORTABLELIB + /// + public async override Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken) + { + int bytesRead = await this.innerStream.ReadAsync(buffer, offset, count, cancellationToken); + this.IncreaseTotalBytesRead(bytesRead); + return bytesRead; + } +#else + /// + /// Begins a read operation from the stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The async callback. + /// The async state. + /// Async result representing the asynchornous operation. + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.innerStream.BeginRead(buffer, offset, count, callback, state); + } + + /// + /// Ends a read operation from the stream. + /// + /// The async result representing the read operation. + /// The number of bytes actually read. + public override int EndRead(IAsyncResult asyncResult) + { + int bytesRead = this.innerStream.EndRead(asyncResult); + this.IncreaseTotalBytesRead(bytesRead); + return bytesRead; + } +#endif + + /// + /// Seeks the stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + return this.innerStream.Seek(offset, origin); + } + + /// + /// Sets the length of the stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + this.innerStream.SetLength(value); + } + + /// + /// Writes to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + this.innerStream.Write(buffer, offset, count); + } + +#if PORTABLELIB + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.innerStream.WriteAsync(buffer, offset, count, cancellationToken); + } +#else + /// + /// Begins an asynchronous write operation to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + /// The async callback. + /// The async state. + /// Async result representing the write operation. + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.innerStream.BeginWrite(buffer, offset, count, callback, state); + } + + /// + /// Ends the asynchronous write operation. + /// + /// Async result representing the write operation. + public override void EndWrite(IAsyncResult asyncResult) + { + this.innerStream.EndWrite(asyncResult); + } +#endif + + /// + /// Dispose this wrapping stream and the underlying stream. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!this.ignoreDispose && this.innerStream != null) + { + this.innerStream.Dispose(); + this.innerStream = null; + } + } + + base.Dispose(disposing); + } + + /// + /// Increases the number of total bytes read from the stream. + /// + /// The number of bytes read from the stream during the last read operation. + /// Since we don't own the underlying stream we also have to prepare for streams returning < 0 bytes read. + private void IncreaseTotalBytesRead(int bytesRead) + { + // Only count the bytes if we have a maximum specified. + if (this.maxBytesToBeRead <= 0) + { + return; + } + + this.totalBytesRead += bytesRead < 0 ? 0 : bytesRead; + if (this.totalBytesRead > this.maxBytesToBeRead) + { + throw new ODataException(Strings.MessageStreamWrappingStream_ByteLimitExceeded(this.totalBytesRead, this.maxBytesToBeRead)); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/BufferingXmlReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/BufferingXmlReader.cs new file mode 100644 index 0000000..c4dd153 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/BufferingXmlReader.cs @@ -0,0 +1,1397 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Xml; + #endregion Namespaces + + /// + /// XML reader which supports look-ahead. + /// + internal sealed class BufferingXmlReader : XmlReader, IXmlLineInfo + { + #region Atomized strings + /// The "http://www.w3.org/XML/1998/namespace" namespace for the "xml" prefix. + internal readonly string XmlNamespace; + + /// The "base" name for the XML base attribute. + internal readonly string XmlBaseAttributeName; + + /// XML namespace for data service annotations. + internal readonly string ODataMetadataNamespace; + + /// XML namespace for data services. This is to provide compatibility with WCF DS client which accepts custom data namespace value. + internal readonly string ODataNamespace; + + /// The 'error' local name of the error element. + internal readonly string ODataErrorElementName; + #endregion Atomized strings + + /// Object converted from this.reader to provide line number and position (if presented). + private readonly IXmlLineInfo lineInfo; + + /// The underlying XML reader this buffering reader is wrapping. + private readonly XmlReader reader; + + /// The (possibly empty) list of buffered nodes. + /// This list stores only non-attribute nodes, attributes are stored in a separate list on an element node. + private readonly LinkedList bufferedNodes; + + /// + /// A special buffered node instance which represents the end of input. + /// We always have just one instance and compare references. + /// + private readonly BufferedNode endOfInputBufferedNode; + + /// Flag to control if the xml:base attributes should be processed when reading. + private readonly bool disableXmlBase; + + /// The maximumum number of recursive internalexception elements to allow when reading in-stream errors. + private readonly int maxInnerErrorDepth; + + /// The base URI for the document. + private readonly Uri documentBaseUri; + + /// A pointer into the bufferedNodes list to track the most recent position of the current buffered node. + private LinkedListNode currentBufferedNode; + + /// + /// A pointer into the linked list of attribute nodes which is only used if the currentBufferedNodeToReport is the attribute value node (not the attribute itself). + /// In that case it points to the current attribute node. + /// In all other cases this node is null. + private LinkedListNode currentAttributeNode; + + /// + /// A pointer either into the bufferedNodes list or into the list of attributes on a buffered element node + /// which points to the node which should be reported to the user. + /// + private LinkedListNode currentBufferedNodeToReport; + + /// A flag indicating whether the reader is in buffering mode or not. + private bool isBuffering; + + /// + /// A flag indicating that the last node for non-buffering read was taken from the buffer; we leave the + /// node in the buffer until the next Read call. + /// + private bool removeOnNextRead; + + /// Flag to control whether in-stream errors should be detected when reading. + private bool disableInStreamErrorDetection; + + /// The stack of XML base URI definitions. + private Stack xmlBaseStack; + + /// The XML base stack state when the buffering started. This is only used when in buffering mode. + private Stack bufferStartXmlBaseStack; + + /// Constructor + /// The reader to wrap. + /// If this reader is wrapping an inner reader of some kind, this parameter should pass the xml:base effective value of the parent. + /// The base URI for the document. + /// Flag to control if the xml:base attributes should be processed when reading. + /// The maximum number of recursive internalexception elements to allow when reading in-stream errors. + internal BufferingXmlReader(XmlReader reader, Uri parentXmlBaseUri, Uri documentBaseUri, bool disableXmlBase, int maxInnerErrorDepth) + { + Debug.Assert(reader != null, "reader != null"); + Debug.Assert(documentBaseUri == null || documentBaseUri.IsAbsoluteUri, "The document Base URI must be absolute if it's specified."); + + this.reader = reader; + this.lineInfo = reader as IXmlLineInfo; + this.documentBaseUri = documentBaseUri; + this.disableXmlBase = disableXmlBase; + this.maxInnerErrorDepth = maxInnerErrorDepth; + + XmlNameTable nameTable = this.reader.NameTable; + this.XmlNamespace = nameTable.Add(ODataMetadataConstants.XmlNamespace); + this.XmlBaseAttributeName = nameTable.Add(ODataMetadataConstants.XmlBaseAttributeName); + this.ODataMetadataNamespace = nameTable.Add(ODataMetadataConstants.ODataMetadataNamespace); + this.ODataNamespace = nameTable.Add(ODataMetadataConstants.ODataNamespace); + this.ODataErrorElementName = nameTable.Add(ODataMetadataConstants.ODataErrorElementName); + + this.bufferedNodes = new LinkedList(); + this.currentBufferedNode = null; + this.endOfInputBufferedNode = BufferedNode.CreateEndOfInput(this.reader.NameTable); + this.xmlBaseStack = new Stack(); + + // If there's a parent xml:base we need to push it onto our stack so that it's correctly propagated + // Note that it's not the same as using it as documentBaseUri, since that one is only used to resolve relative xml:base values + // not to report if there's no xml:base. + if (parentXmlBaseUri != null) + { + this.xmlBaseStack.Push(new XmlBaseDefinition(parentXmlBaseUri, 0)); + } + } + + /// + /// Returns the type of the current node. + /// + public override XmlNodeType NodeType + { + get + { + return this.currentBufferedNodeToReport != null ? this.currentBufferedNodeToReport.Value.NodeType : this.reader.NodeType; + } + } + + /// + /// Returns true if the reader is positioned on an empty element. + /// + public override bool IsEmptyElement + { + get + { + return this.currentBufferedNodeToReport != null ? this.currentBufferedNodeToReport.Value.IsEmptyElement : this.reader.IsEmptyElement; + } + } + + /// + /// Returns the local name of the current node. + /// + public override string LocalName + { + get + { + return this.currentBufferedNodeToReport != null ? this.currentBufferedNodeToReport.Value.LocalName : this.reader.LocalName; + } + } + + /// + /// Returns the prefix of the current node. + /// + public override string Prefix + { + get + { + return this.currentBufferedNodeToReport != null ? this.currentBufferedNodeToReport.Value.Prefix : this.reader.Prefix; + } + } + + /// + /// Returns the namespace URI of the current node. + /// + public override string NamespaceURI + { + get + { + return this.currentBufferedNodeToReport != null ? this.currentBufferedNodeToReport.Value.NamespaceUri : this.reader.NamespaceURI; + } + } + + /// + /// Returns the value of the current node. + /// + public override string Value + { + get + { + return this.currentBufferedNodeToReport != null ? this.currentBufferedNodeToReport.Value.Value : this.reader.Value; + } + } + + /// + /// Returns the depth of the current node. + /// + public override int Depth + { + get + { + return this.currentBufferedNodeToReport != null ? this.currentBufferedNodeToReport.Value.Depth : this.reader.Depth; + } + } + + /// + /// Returns true if the end of input was reached. + /// + public override bool EOF + { + get + { + return this.currentBufferedNodeToReport != null ? this.IsEndOfInputNode(this.currentBufferedNodeToReport.Value) : this.reader.EOF; + } + } + + /// + /// Returns the current state of the reader. + /// + /// We need to support ReadState in order for Skip to work without us implementing it again. + public override ReadState ReadState + { + get + { + if (this.currentBufferedNodeToReport != null) + { + if (this.IsEndOfInputNode(this.currentBufferedNodeToReport.Value)) + { + return ReadState.EndOfFile; + } + + return this.currentBufferedNodeToReport.Value.NodeType == XmlNodeType.None ? ReadState.Initial : ReadState.Interactive; + } + + return this.reader.ReadState; + } + } + + /// + /// Returns the nametable used by the reader. + /// + public override XmlNameTable NameTable + { + get + { + return this.reader.NameTable; + } + } + + /// + /// Returns the number of attributes on the node. + /// + public override int AttributeCount + { + get + { + if (this.currentBufferedNodeToReport != null) + { + return this.currentBufferedNodeToReport.Value.AttributeNodes != null + ? this.currentBufferedNodeToReport.Value.AttributeNodes.Count + : 0; + } + + return this.reader.AttributeCount; + } + } + + /// + /// Returns the base URI of the node - note that this is not based on the xml:base attribute, just the input streams. + /// + public override string BaseURI + { + get + { + // In ODataLib this reader is always created over a stream and in addition we don't allow DTD, so the base URI can never change. + // We also don't have a scenario where the BaseURI as reported by the XmlReader, that is the URI of the input stream + // would be useful. + // So return null for now, so that nobody can rely on the value even if the underlying reader has some. + return null; + } + } + + /// + /// Returns true if the current node has a value. + /// + public override bool HasValue + { + get + { + if (this.currentBufferedNodeToReport != null) + { + switch (this.NodeType) + { + case XmlNodeType.Attribute: + case XmlNodeType.Text: + case XmlNodeType.CDATA: + case XmlNodeType.ProcessingInstruction: + case XmlNodeType.Comment: + case XmlNodeType.DocumentType: + case XmlNodeType.Whitespace: + case XmlNodeType.SignificantWhitespace: + case XmlNodeType.XmlDeclaration: + return true; + default: + return false; + } + } + + return this.reader.HasValue; + } + } + + /// + /// Explicit IXmlLineInfo.LineNumber implementation. + /// This property provides current line number of this reader. + /// + int IXmlLineInfo.LineNumber + { + get { return this.HasLineInfo() ? lineInfo.LineNumber : 0; } + } + + /// + /// Explicit IXmlLineInfo.LinePosition implementation. + /// This property provides current line position of this reader. + /// + int IXmlLineInfo.LinePosition + { + get { return this.HasLineInfo() ? lineInfo.LinePosition : 0; } + } + + /// + /// The active XML base URI for the current node. + /// + internal Uri XmlBaseUri + { + get + { + return this.xmlBaseStack.Count > 0 ? this.xmlBaseStack.Peek().BaseUri : null; + } + } + + /// + /// The active XML base URI for the parent node (parent element) of the current node. + /// + internal Uri ParentXmlBaseUri + { + get + { + if (this.xmlBaseStack.Count == 0) + { + return null; + } + + XmlBaseDefinition xmlBaseDefinition = this.xmlBaseStack.Peek(); + + // The xml:base stack only keeps record for when the value of the active xml:base changes. + // So we only need to skip the top one if it's of the same depth as the current node since we want the parent one. + if (xmlBaseDefinition.Depth == this.Depth) + { + // If there's just one record on the stack, it means we don't have a record for the parent node + // so return null then. + if (this.xmlBaseStack.Count == 1) + { + return null; + } + + xmlBaseDefinition = this.xmlBaseStack.Skip(1).First(); + } + + return xmlBaseDefinition.BaseUri; + } + } + + /// + /// Flag to control whether in-stream errors should be detected when reading. + /// + internal bool DisableInStreamErrorDetection + { + get + { + return this.disableInStreamErrorDetection; + } + + set + { + this.disableInStreamErrorDetection = value; + } + } + +#if DEBUG + /// + /// Flag indicating whether buffering is on or off; debug-only for use in asserts. + /// + internal bool IsBuffering + { + get + { + return this.isBuffering; + } + } +#endif + + /// + /// Reads the next node from the input. + /// + /// true if another node is available and the reader has moved to it; false if end of input was reached. + public override bool Read() + { + // If we were on end element or empty element pop the XML base definition for this level. + if (!this.disableXmlBase && this.xmlBaseStack.Count > 0) + { + XmlNodeType nodeType = this.NodeType; + if (nodeType == XmlNodeType.Attribute) + { + this.MoveToElement(); + nodeType = XmlNodeType.Element; + } + + if (this.xmlBaseStack.Peek().Depth == this.Depth) + { + if (nodeType == XmlNodeType.EndElement || (nodeType == XmlNodeType.Element && this.IsEmptyElement)) + { + this.xmlBaseStack.Pop(); + } + } + } + + bool result = this.ReadInternal(this.disableInStreamErrorDetection); + + // Push a new XML base definition for this level if there's an xml:base attribute. + if (result && !this.disableXmlBase) + { + if (this.NodeType == XmlNodeType.Element) + { + string xmlBaseAttributeValue = this.GetAttributeWithAtomizedName(this.XmlBaseAttributeName, this.XmlNamespace); + + // We need to treat empty uri as a valid relative URI + if (xmlBaseAttributeValue != null) + { + Uri newBaseUri = new Uri(xmlBaseAttributeValue, UriKind.RelativeOrAbsolute); + if (!newBaseUri.IsAbsoluteUri) + { + // If the top-most xml:base is relative, use the document base URI (The one from the settings) + // to make it absolute. + if (this.xmlBaseStack.Count == 0) + { + if (this.documentBaseUri == null) + { + // If there's no document base URI we need to fail since there's no way to create an absolute URI now. + throw new ODataException(Strings.ODataAtomDeserializer_RelativeUriUsedWithoutBaseUriSpecified(xmlBaseAttributeValue)); + } + + newBaseUri = UriUtils.UriToAbsoluteUri(this.documentBaseUri, newBaseUri); + } + else + { + // If xml base of the current element is an absolute Uri, it overrides that of the parent node. + // If xml base of the current element is a relative Uri, it must be relative to the parent xml base. + // For more information, look into section 3 of the following RFC + // http://www.w3.org/TR/xmlbase/ + newBaseUri = UriUtils.UriToAbsoluteUri(this.xmlBaseStack.Peek().BaseUri, newBaseUri); + } + } + + this.xmlBaseStack.Push(new XmlBaseDefinition(newBaseUri, this.Depth)); + } + } + } + + return result; + } + + /// + /// Moves the reader to the element which owns the current attribute. + /// + /// true if the reader has moved (that is the current node was an attribute); + /// false if the reader didn't move (the reader was already positioned on an element or other node). + public override bool MoveToElement() + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + this.MoveFromAttributeValueNode(); + + if (this.isBuffering) + { + Debug.Assert(this.currentBufferedNode != null, "this.currentBufferedNode != null"); + Debug.Assert(this.currentBufferedNodeToReport != null, "this.currentBufferedNodeToReport != null"); + + if (this.currentBufferedNodeToReport.Value.NodeType == XmlNodeType.Attribute) + { + Debug.Assert( + this.currentBufferedNode.Value.NodeType == XmlNodeType.Element, + "If the current node to report is attribute, the current node must be an element."); + this.currentBufferedNodeToReport = this.currentBufferedNode; + this.ValidateInternalState(); + return true; + } + + return false; + } + + // in non-buffering mode if we have buffered nodes satisfy the request from the first node there + Debug.Assert( + this.currentBufferedNodeToReport != null, + "Even if we're not buffering, if we are reporting from the first node in the buffer, the current node to report must no be null."); + + if (this.currentBufferedNodeToReport.Value.NodeType == XmlNodeType.Attribute) + { + Debug.Assert( + this.bufferedNodes.First.Value.NodeType == XmlNodeType.Element, + "If the current node to report is attribute, the current node must be an element."); + this.currentBufferedNodeToReport = this.bufferedNodes.First; + this.ValidateInternalState(); + return true; + } + + return false; + } + + return this.reader.MoveToElement(); + } + + /// + /// Moves the reader to the first attribute of the current element. + /// + /// true if the reader moved to the first attribute; false if there are no attribute for the current node (the reader didn't move). + public override bool MoveToFirstAttribute() + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + BufferedNode elementNode = this.GetCurrentElementNode(); + if (elementNode.NodeType == XmlNodeType.Element && elementNode.AttributeNodes.Count > 0) + { + this.currentAttributeNode = null; + this.currentBufferedNodeToReport = elementNode.AttributeNodes.First; + this.ValidateInternalState(); + return true; + } + + return false; + } + + return this.reader.MoveToFirstAttribute(); + } + + /// + /// Moves the reader to the next attribute on the current element. + /// + /// true if the reader moved to the next attribute (if the node was an element it moves to the first attribute); + /// false if the reader didn't move (no attributes for the current node). + public override bool MoveToNextAttribute() + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + Debug.Assert( + this.currentBufferedNodeToReport != null, + "If we are reporting nodes from buffer (doesn't matter if buffering or not), we must have a current node to report."); + + LinkedListNode attributeNode = this.currentAttributeNode; + if (attributeNode == null) + { + attributeNode = this.currentBufferedNodeToReport; + } + + if (attributeNode.Value.NodeType == XmlNodeType.Attribute) + { + if (attributeNode.Next != null) + { + this.currentAttributeNode = null; + this.currentBufferedNodeToReport = attributeNode.Next; + this.ValidateInternalState(); + return true; + } + + return false; + } + + if (this.currentBufferedNodeToReport.Value.NodeType == XmlNodeType.Element) + { + Debug.Assert( + (this.isBuffering && this.currentBufferedNodeToReport == this.currentBufferedNode) || (!this.isBuffering && this.currentBufferedNodeToReport == this.bufferedNodes.First), + "If the node to report is element: if buffer it must be the current buffered node, otherwise it must be the first node in the buffer."); + + if (this.currentBufferedNodeToReport.Value.AttributeNodes.Count > 0) + { + this.currentBufferedNodeToReport = this.currentBufferedNodeToReport.Value.AttributeNodes.First; + this.ValidateInternalState(); + return true; + } + + return false; + } + + return false; + } + + return this.reader.MoveToNextAttribute(); + } + + /// + /// Reads the next node from the value of an attribute. + /// + /// true if next node was available; false if end of the attribute value was reached. + public override bool ReadAttributeValue() + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + if (this.currentBufferedNodeToReport.Value.NodeType != XmlNodeType.Attribute) + { + return false; + } + + // If we're already on an attribute value node, return false, since we always report the entire attribute value as one node. + if (this.currentAttributeNode != null) + { + return false; + } + + // Otherwise create the new "fake" node for the attribute value + BufferedNode attributeValueBufferedNode = new BufferedNode(this.currentBufferedNodeToReport.Value.Value, this.currentBufferedNodeToReport.Value.Depth, this.NameTable); + LinkedListNode attributeValueNode = new LinkedListNode(attributeValueBufferedNode); + this.currentAttributeNode = this.currentBufferedNodeToReport; + this.currentBufferedNodeToReport = attributeValueNode; + + this.ValidateInternalState(); + return true; + } + + return this.reader.ReadAttributeValue(); + } + +#if !PORTABLELIB + /// + /// Closes the reader and the underlying input. + /// + public override void Close() + { + throw new NotSupportedException(); + } +#endif + + /// + /// Returns the value of an attribute based on its index. + /// + /// The index of the attribute, starts at 0. + /// The value of the attribute at index . + public override string GetAttribute(int i) + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + if (i < 0 || i >= this.AttributeCount) + { + throw new ArgumentOutOfRangeException("i"); + } + + LinkedListNode attributeNode = this.FindAttributeBufferedNode(i); + return attributeNode == null ? null : attributeNode.Value.Value; + } + + return this.reader.GetAttribute(i); + } + + /// + /// Returns the value of an attribute based on its fully qualified name. + /// + /// The local name of the attribute. + /// The namespace URI of the attribute. + /// The value of the attribute with specified and . + public override string GetAttribute(string name, string namespaceURI) + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + LinkedListNode attributeNode = this.FindAttributeBufferedNode(name, namespaceURI); + return attributeNode == null ? null : attributeNode.Value.Value; + } + + return this.reader.GetAttribute(name, namespaceURI); + } + + /// + /// Returns the value of an attribute based on its name. + /// + /// The name of the attribute. (prefix:localname) + /// The value of the attribute with specified . + public override string GetAttribute(string name) + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + LinkedListNode attributeNode = this.FindAttributeBufferedNode(name); + return attributeNode == null ? null : attributeNode.Value.Value; + } + + return this.reader.GetAttribute(name); + } + + /// + /// Looks up a namespace URI based on the prefix. + /// + /// The prefix to search for. + /// The namespace URI for the specified . + public override string LookupNamespace(string prefix) + { + throw new NotSupportedException(); + } + + /// + /// Moves the reader to the attribute specified by fully qualified name. + /// + /// The local name of the attribute. + /// The namespace URI of the attribute. + /// true if the attribute specified by and was found and the reader is positioned on it; + /// false otherwise. + public override bool MoveToAttribute(string name, string ns) + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + LinkedListNode attributeNode = this.FindAttributeBufferedNode(name, ns); + if (attributeNode != null) + { + this.currentAttributeNode = null; + this.currentBufferedNodeToReport = attributeNode; + this.ValidateInternalState(); + return true; + } + + return false; + } + + return this.reader.MoveToAttribute(name, ns); + } + + /// + /// Moves the reader to the attribute specified by name. + /// + /// The name of the attribute (prefix:localname). + /// true if the attribute specified by was found and the reader is positioned on it; + /// false otherwise. + public override bool MoveToAttribute(string name) + { + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + LinkedListNode attributeNode = this.FindAttributeBufferedNode(name); + if (attributeNode != null) + { + this.currentAttributeNode = null; + this.currentBufferedNodeToReport = attributeNode; + this.ValidateInternalState(); + return true; + } + + return false; + } + + return this.reader.MoveToAttribute(name); + } + + /// + /// Resolves the current entity node. + /// + public override void ResolveEntity() + { + // We don't support entity references, and we should never get a reader which does. + throw new InvalidOperationException(Strings.ODataException_GeneralError); + } + + /// + /// Explicit IXmlLineInfo.HasLineInfo() implementation. + /// Check if this reader has line info. + /// + /// If line info is presented. + bool IXmlLineInfo.HasLineInfo() + { + return this.HasLineInfo(); + } + + /// + /// Puts the reader into the state where it buffers read nodes. + /// + internal void StartBuffering() + { + Debug.Assert(!this.isBuffering, "Buffering is already turned on. Must not call StartBuffering again."); + Debug.Assert(this.NodeType != XmlNodeType.Attribute, "Buffering cannot start on an attribute."); + this.ValidateInternalState(); + + if (this.bufferedNodes.Count == 0) + { + // capture the current state of the reader as the first item in the buffer (if there are none) + this.bufferedNodes.AddFirst(this.BufferCurrentReaderNode()); + } + else + { + this.removeOnNextRead = false; + } + + Debug.Assert(this.bufferedNodes.Count > 0, "Expected at least the current node in the buffer when starting buffering."); + + // Set the currentBufferedNode to the first node in the list; this means every time we start buffering we reset the + // position of the current buffered node since in general we don't know how far ahead we have read before and thus don't + // want to blindly continuing to read. The model is that with every call to StartBuffering you reset the position of the + // current node in the list and start reading through the buffer again. + Debug.Assert(this.currentBufferedNode == null, "When starting to buffer, the currentBufferedNode must be null, since we always reset to the begining of the buffer."); + this.currentBufferedNode = this.bufferedNodes.First; + + this.currentBufferedNodeToReport = this.currentBufferedNode; + + // Copy the existing XML base stack to a stored copy, so that we can restart it when we stop buffering. + int xmlBaseStackCount = this.xmlBaseStack.Count; + switch (xmlBaseStackCount) + { + case 0: + this.bufferStartXmlBaseStack = new Stack(); + break; + case 1: + this.bufferStartXmlBaseStack = new Stack(); + this.bufferStartXmlBaseStack.Push(this.xmlBaseStack.Peek()); + break; + default: + XmlBaseDefinition[] array = this.xmlBaseStack.ToArray(); + this.bufferStartXmlBaseStack = new Stack(xmlBaseStackCount); + for (int i = xmlBaseStackCount - 1; i >= 0; i--) + { + this.bufferStartXmlBaseStack.Push(array[i]); + } + + break; + } + + this.isBuffering = true; + this.ValidateInternalState(); + } + + /// + /// Puts the reader into the state where no buffering happen on read. + /// Either buffered nodes are consumed or new nodes are read (and not buffered). + /// + internal void StopBuffering() + { + Debug.Assert(this.isBuffering, "Buffering is not turned on. Must not call StopBuffering in this state."); + this.ValidateInternalState(); + + // NOTE: by turning off buffering the reader is set to the first node in the buffer (if any) and will continue + // to read from there. removeOnNextRead is set to true since we captured the original state of the reader + // (before starting to buffer) as the first node in the buffer and that node has to be removed on the next read. + this.isBuffering = false; + this.removeOnNextRead = true; + + // We set the currentBufferedNode to null here to indicate that we want to reset the position of the current + // buffered node when we turn on buffering the next time. So far this (i.e., resetting the position of the buffered + // node) is the only mode the BufferingJsonReader supports. We can make resetting the current node position more explicit + // if needed. + this.currentBufferedNode = null; + if (this.bufferedNodes.Count > 0) + { + this.currentBufferedNodeToReport = this.bufferedNodes.First; + } + + // Restore the XML base stack as it was when we started buffering + this.xmlBaseStack = this.bufferStartXmlBaseStack; + this.bufferStartXmlBaseStack = null; + + this.ValidateInternalState(); + } + + /// + /// The actual implementatin of the Read method. Moves the reader to the next node. + /// + /// true if the reader should not check for in-stream errors; otherwise false. + /// true if next node is available and the reader has moved; false if end-of-input was reached. + private bool ReadInternal(bool ignoreInStreamErrors) + { + this.ValidateInternalState(); + + if (this.removeOnNextRead) + { + Debug.Assert(this.bufferedNodes.Count > 0, "If removeOnNextRead is true we must have at least one node in the buffer."); + this.currentBufferedNodeToReport = this.currentBufferedNodeToReport.Next; + this.bufferedNodes.RemoveFirst(); + this.removeOnNextRead = false; + } + + bool result; + if (this.isBuffering) + { + Debug.Assert(this.currentBufferedNode != null, "this.currentBufferedNode != null"); + + this.MoveFromAttributeValueNode(); + + if (this.currentBufferedNode.Next != null) + { + Debug.Assert(!ignoreInStreamErrors, "!ignoreInStreamErrors"); + this.currentBufferedNode = this.currentBufferedNode.Next; + this.currentBufferedNodeToReport = this.currentBufferedNode; + result = true; + } + else + { + if (ignoreInStreamErrors) + { + // read more from the input stream and buffer it + result = this.reader.Read(); + this.bufferedNodes.AddLast(this.BufferCurrentReaderNode()); + this.currentBufferedNode = this.bufferedNodes.Last; + this.currentBufferedNodeToReport = this.currentBufferedNode; + } + else + { + // read the next node from the input stream and check + // whether it is an in-stream error + result = this.ReadNextAndCheckForInStreamError(); + } + } + + Debug.Assert( + this.currentBufferedNode == this.currentBufferedNodeToReport, + "After buffered Read() the node is not an attribute and thus the current buffered node and the one to report are the same."); + } + else + { + if (this.bufferedNodes.Count == 0) + { + // read the next node from the input stream and check + // whether it is an in-stream error + result = ignoreInStreamErrors ? this.reader.Read() : this.ReadNextAndCheckForInStreamError(); + } + else + { + Debug.Assert(!ignoreInStreamErrors, "!ignoreInStreamErrors"); + + // non-buffering read from the buffer + this.currentBufferedNodeToReport = this.bufferedNodes.First; + BufferedNode bufferedNode = this.currentBufferedNodeToReport.Value; + result = !this.IsEndOfInputNode(bufferedNode); + this.removeOnNextRead = true; + } + } + + this.ValidateInternalState(); + return result; + } + + /// + /// Reads the next node from the XML reader and if m:error element node is detected starts reading ahead and + /// tries to parse an in-stream error. + /// + /// true if a new node was found, or false if end of input was reached. + private bool ReadNextAndCheckForInStreamError() + { + // read the next node in the current reader mode (buffering or non-buffering) + bool result = this.ReadInternal(/*ignoreInStreamErrors*/true); + + if (!this.disableInStreamErrorDetection && + this.NodeType == XmlNodeType.Element && + this.LocalNameEquals(this.ODataErrorElementName) && + this.NamespaceEquals(this.ODataMetadataNamespace)) + { + // If we find an element m:error node we detected an in-stream error. + // We read the in-stream error and report it. + ODataError inStreamError = ODataAtomErrorDeserializer.ReadErrorElement(this, this.maxInnerErrorDepth); + Debug.Assert(inStreamError != null, "inStreamError != null"); + throw new ODataErrorException(inStreamError); + } + + return result; + } + + /// + /// Determines if the specified node is the end of input node. + /// + /// The buffered node to test. + /// true if the node is the special end of input node, false otherwise. + private bool IsEndOfInputNode(BufferedNode node) + { + return object.ReferenceEquals(node, this.endOfInputBufferedNode); + } + + /// + /// Buffers the current reader state into a node. + /// + /// The newly created buffered node. + private BufferedNode BufferCurrentReaderNode() + { + if (this.reader.EOF) + { + return this.endOfInputBufferedNode; + } + + BufferedNode resultNode = new BufferedNode(this.reader); + + // If the new node is an element, read all its attributes and buffer those as well + if (this.reader.NodeType == XmlNodeType.Element) + { + while (this.reader.MoveToNextAttribute()) + { + resultNode.AttributeNodes.AddLast(new BufferedNode(this.reader)); + } + + this.reader.MoveToElement(); + } + + return resultNode; + } + + /// + /// Returns the current element node (or node which acts like an element, it doesn't have to be of type Element). + /// + /// The current element node. + private BufferedNode GetCurrentElementNode() + { + Debug.Assert(this.bufferedNodes.Count > 0, "This method can only be called if we have buffered nodes."); + + if (this.isBuffering) + { + Debug.Assert(this.currentBufferedNode != null, "this.currentBufferedNode != null"); + return this.currentBufferedNode.Value; + } + else + { + // in non-buffering mode if we have buffered nodes satisfy the request from the first node there + return this.bufferedNodes.First.Value; + } + } + + /// + /// Finds the buffered node for the attribute specified by its index. + /// + /// The index of the attribute. + /// The linked list node of the found attribute, or null if no such attribute could be found. + private LinkedListNode FindAttributeBufferedNode(int index) + { + Debug.Assert(this.bufferedNodes.Count > 0, "This method can only be called if we have buffered nodes."); + + BufferedNode elementNode = this.GetCurrentElementNode(); + if (elementNode.NodeType == XmlNodeType.Element && elementNode.AttributeNodes.Count > 0) + { + LinkedListNode attributeNode = elementNode.AttributeNodes.First; + int attributeIndex = 0; + while (attributeNode != null) + { + if (attributeIndex == index) + { + return attributeNode; + } + + attributeIndex++; + attributeNode = attributeNode.Next; + } + } + + return null; + } + + /// + /// Finds the buffered node for the attribute specified by its local name and namespace URI. + /// + /// The local name of the attribute. + /// The namespace URI of the attribute. + /// The linked list node of the found attribute, or null if no such attribute could be found. + private LinkedListNode FindAttributeBufferedNode(string localName, string namespaceUri) + { + Debug.Assert(this.bufferedNodes.Count > 0, "This method can only be called if we have buffered nodes."); + + BufferedNode elementNode = this.GetCurrentElementNode(); + if (elementNode.NodeType == XmlNodeType.Element && elementNode.AttributeNodes.Count > 0) + { + LinkedListNode attributeNode = elementNode.AttributeNodes.First; + while (attributeNode != null) + { + BufferedNode bufferedAttribute = attributeNode.Value; + if (string.CompareOrdinal(bufferedAttribute.NamespaceUri, namespaceUri) == 0 && string.CompareOrdinal(bufferedAttribute.LocalName, localName) == 0) + { + return attributeNode; + } + + attributeNode = attributeNode.Next; + } + } + + return null; + } + + /// + /// Finds the buffered node for the attribute specified by its qualified name. + /// + /// The qualified name of the attribute to find, that is prefix:localName. + /// The linked list node of the found attribute, or null if no such attribute could be found. + private LinkedListNode FindAttributeBufferedNode(string qualifiedName) + { + Debug.Assert(this.bufferedNodes.Count > 0, "This method can only be called if we have buffered nodes."); + + BufferedNode elementNode = this.GetCurrentElementNode(); + if (elementNode.NodeType == XmlNodeType.Element && elementNode.AttributeNodes.Count > 0) + { + LinkedListNode attributeNode = elementNode.AttributeNodes.First; + while (attributeNode != null) + { + BufferedNode bufferedAttribute = attributeNode.Value; + bool hasPrefix = !string.IsNullOrEmpty(bufferedAttribute.Prefix); + if ((!hasPrefix && string.CompareOrdinal(bufferedAttribute.LocalName, qualifiedName) == 0) || + (hasPrefix && string.CompareOrdinal(bufferedAttribute.Prefix + ":" + bufferedAttribute.LocalName, qualifiedName) == 0)) + { + return attributeNode; + } + + attributeNode = attributeNode.Next; + } + } + + return null; + } + + /// + /// If the reader is positioned on the attribute value node, this moves it to the owning attribute node. + /// + private void MoveFromAttributeValueNode() + { + Debug.Assert(this.bufferedNodes.Count > 0, "This method can only be called if we have buffered nodes."); + + if (this.currentAttributeNode != null) + { + this.currentBufferedNodeToReport = this.currentAttributeNode; + this.currentAttributeNode = null; + } + } + + /// + /// Returns the value of an attribute based on its fully qualified name. + /// + /// The local name of the attribute. This string must already be atomized against the reader's nametable. + /// The namespace URI of the attribute. This string must already be atomized against the reader's nametable. + /// The value of the attribute with specified and . + /// + /// Behaves the same as GetAttribute, but it assumes that the parameters are already atomized against our nametable. + /// This allows the method to be much faster. + /// + private string GetAttributeWithAtomizedName(string name, string namespaceURI) + { + Debug.Assert(this.NodeType == XmlNodeType.Element, "We must be on an element to use this method."); + Debug.Assert(object.ReferenceEquals(name, this.NameTable.Get(name)), "The name parameter must be already atomized."); + Debug.Assert(object.ReferenceEquals(namespaceURI, this.NameTable.Get(namespaceURI)), "The namespaceURI parameter must be already atomized."); + this.ValidateInternalState(); + + if (this.bufferedNodes.Count > 0) + { + LinkedListNode attributeNode = this.GetCurrentElementNode().AttributeNodes.First; + while (attributeNode != null) + { + BufferedNode bufferedAttribute = attributeNode.Value; + if (object.ReferenceEquals(namespaceURI, bufferedAttribute.NamespaceUri) && object.ReferenceEquals(name, bufferedAttribute.LocalName)) + { + return attributeNode.Value.Value; + } + + attributeNode = attributeNode.Next; + } + + return null; + } + + // Instead of calling GetAttribute which is quite slow since it always atomizes the parameters (it doesn't know they're already atomized) + // perform the search ourselves. + string valueToReturn = null; + while (this.reader.MoveToNextAttribute()) + { + if (object.ReferenceEquals(name, this.reader.LocalName) && object.ReferenceEquals(namespaceURI, this.reader.NamespaceURI)) + { + valueToReturn = this.reader.Value; + break; + } + } + + this.reader.MoveToElement(); + return valueToReturn; + } + + /// + /// Validates internal state of the reader - debug only. + /// + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This is a DEBUG only method.")] + [Conditional("DEBUG")] + private void ValidateInternalState() + { +#if DEBUG + if (this.bufferedNodes.Count > 0) + { + Debug.Assert(this.currentBufferedNodeToReport != null, "If we have something in the buffer we must have the current node to report."); + + BufferedNode elementNode; + if (this.isBuffering) + { + Debug.Assert(this.currentBufferedNode != null, "If buffering we must have current buffered node."); + elementNode = this.currentBufferedNode.Value; + } + else + { + Debug.Assert(this.currentBufferedNode == null, "If we are not buffer, we must not have any current buffered node."); + elementNode = this.bufferedNodes.First.Value; + } + + Debug.Assert(elementNode.NodeType != XmlNodeType.Attribute, "The current buffered node must never be an attribute."); + + if (this.currentAttributeNode != null) + { + Debug.Assert( + elementNode.NodeType == XmlNodeType.Element, + "If the current reported node is attribute, the current buffered node must be element."); + Debug.Assert(this.currentAttributeNode.Value.NodeType == XmlNodeType.Attribute, "The currentAttributeNode must be an attribute."); + Debug.Assert(this.currentBufferedNodeToReport.Value.NodeType == XmlNodeType.Text, "The attribute value node must be a text node."); + } + else if (this.currentBufferedNodeToReport.Value.NodeType == XmlNodeType.Attribute) + { + Debug.Assert( + elementNode.NodeType == XmlNodeType.Element, + "If the current reported node is attribute, the current buffered node must be element."); + } + } + else + { + Debug.Assert(this.currentBufferedNode == null, "If we don't have anything in the buffer, no buffered node is available."); + Debug.Assert(this.currentBufferedNodeToReport == null, "If we don't have anything in the buffer, no buffered node is available."); + } + + Debug.Assert(this.bufferStartXmlBaseStack == null || this.isBuffering, "The buffered XML base stack should only be used when buffering."); +#endif + } + + /// + /// Check if this reader has line info. + /// + /// If line info is presented. + private bool HasLineInfo() + { + return lineInfo != null && lineInfo.HasLineInfo(); + } + + /// + /// Class representing one buffered XML node + /// + private sealed class BufferedNode + { + /// + /// The list of attribute nodes, if this node is an element node. + /// + private LinkedList attributeNodes; + + /// + /// Constructor. + /// + /// The XML reader to get all the interesting values from. The reader + /// is positioned on the node which the new buffered node should buffer. + internal BufferedNode(XmlReader reader) + { + Debug.Assert(reader != null, "reader != null"); + + this.NodeType = reader.NodeType; + this.NamespaceUri = reader.NamespaceURI; + this.LocalName = reader.LocalName; + this.Prefix = reader.Prefix; + this.Value = reader.Value; + this.Depth = reader.Depth; + this.IsEmptyElement = reader.IsEmptyElement; + } + + /// + /// Constructor for an attribute value node + /// + /// The value of the attribute value node to create. + /// The parent attribute depth. + /// The nametable to use. + internal BufferedNode(string value, int depth, XmlNameTable nametable) + { + Debug.Assert(value != null, "reader != null"); + + string emptyString = nametable.Add(string.Empty); + this.NodeType = XmlNodeType.Text; + this.NamespaceUri = emptyString; + this.LocalName = emptyString; + this.Prefix = emptyString; + this.Value = value; + this.Depth = depth + 1; + this.IsEmptyElement = false; + } + + /// + /// Constructor for end of input node. + /// + /// The atomized instance of an empty string. + private BufferedNode(string emptyString) + { + this.NodeType = XmlNodeType.None; + this.NamespaceUri = emptyString; + this.LocalName = emptyString; + this.Prefix = emptyString; + this.Value = emptyString; + + // Depth is 0 by default + // IsEmptyElement is false by default + } + + /// The type of the buffered node. + internal XmlNodeType NodeType { get; private set; } + + /// The namespace URI of the buffered node. + internal string NamespaceUri { get; private set; } + + /// The local name of the buffered node. + internal string LocalName { get; private set; } + + /// The prefix of the buffered node. + internal string Prefix { get; private set; } + + /// The value of the buffered node. + internal string Value { get; private set; } + + /// The depth of the buffered node. + internal int Depth { get; private set; } + + /// Denotes if the buffered node is an empty element. + internal bool IsEmptyElement { get; private set; } + + /// List of attributes. If the node is not element, this will be null. + internal LinkedList AttributeNodes + { + get + { + if (this.NodeType == XmlNodeType.Element && this.attributeNodes == null) + { + this.attributeNodes = new LinkedList(); + } + + return this.attributeNodes; + } + } + + /// + /// Creates a special node which represents the end of input. + /// + /// The nametable of the underlying reader. + /// The newly created node. + internal static BufferedNode CreateEndOfInput(XmlNameTable nametable) + { + string emptyString = nametable.Add(string.Empty); + return new BufferedNode(emptyString); + } + } + + /// + /// Helper class to store XML base URI definition for a specific depth of the reader. + /// + private sealed class XmlBaseDefinition + { + /// + /// Constructor. + /// + /// The XML base URI for the definition. + /// The depth of the XML reader for the definition. + internal XmlBaseDefinition(Uri baseUri, int depth) + { + Debug.Assert(baseUri != null, "baseUri != null"); + Debug.Assert(baseUri.IsAbsoluteUri, "Only absolute URIs can be used as base URIs."); + + this.BaseUri = baseUri; + this.Depth = depth; + } + + /// The base URI for this definition. + internal Uri BaseUri { get; private set; } + + /// The depth of the XmlReader on which this XML base is defined. + internal int Depth { get; private set; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/CachedPrimitiveKeepInContentAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/CachedPrimitiveKeepInContentAnnotation.cs new file mode 100644 index 0000000..6406675 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/CachedPrimitiveKeepInContentAnnotation.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + #endregion Namespaces + + /// + /// Annotation which stores a hashset of property names of a complex type that returned KeepInContent == true + /// when written the first time. See the comments on ODataWriterBehavior.UseV1ProviderBehavior for more details. + /// + internal sealed class CachedPrimitiveKeepInContentAnnotation + { + /// + /// A hash set with the property names of properties that are kept in the content. + /// + private readonly HashSet keptInContentPropertyNames; + + /// + /// Constructor. + /// + /// Enumeration of property names that are kept in content. + internal CachedPrimitiveKeepInContentAnnotation(IEnumerable keptInContentPropertyNames) + { + this.keptInContentPropertyNames = keptInContentPropertyNames == null + ? null + : new HashSet(keptInContentPropertyNames, StringComparer.Ordinal); + } + + /// + /// Determines if a property is in a list of properties that are kept in the content. + /// + /// The name of the property to lookup. + /// true if the property is kept in the content; false otherwise. + internal bool IsKeptInContent(string propertyName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + return this.keptInContentPropertyNames == null + ? false + : this.keptInContentPropertyNames.Contains(propertyName); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmConstants.cs new file mode 100644 index 0000000..52bf305 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmConstants.cs @@ -0,0 +1,136 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + /// + /// Constant values used in the EDM. + /// + internal static class EdmConstants + { + #region Edm Primitive Type Names -----------------------------------------------------------------------------/ + + /// namespace for edm primitive types. + internal const string EdmNamespace = "Edm"; + + /// edm binary primitive type name + internal const string EdmBinaryTypeName = "Edm.Binary"; + + /// edm boolean primitive type name + internal const string EdmBooleanTypeName = "Edm.Boolean"; + + /// edm byte primitive type name + internal const string EdmByteTypeName = "Edm.Byte"; + + /// edm date primitive type name + internal const string EdmDateTypeName = "Edm.Date"; + + /// Represents a Time instance as an interval measured in milliseconds from an instance of DateTime. + internal const string EdmDateTimeOffsetTypeName = "Edm.DateTimeOffset"; + + /// edm decimal primitive type name + internal const string EdmDecimalTypeName = "Edm.Decimal"; + + /// edm double primitive type name + internal const string EdmDoubleTypeName = "Edm.Double"; + + /// edm guid primitive type name + internal const string EdmGuidTypeName = "Edm.Guid"; + + /// edm single primitive type name + internal const string EdmSingleTypeName = "Edm.Single"; + + /// edm sbyte primitive type name + internal const string EdmSByteTypeName = "Edm.SByte"; + + /// edm int16 primitive type name + internal const string EdmInt16TypeName = "Edm.Int16"; + + /// edm int32 primitive type name + internal const string EdmInt32TypeName = "Edm.Int32"; + + /// edm int64 primitive type name + internal const string EdmInt64TypeName = "Edm.Int64"; + + /// edm string primitive type name + internal const string EdmStringTypeName = "Edm.String"; + + /// Represents an interval measured in milliseconds. + internal const string EdmDurationTypeName = "Edm.Duration"; + + /// edm stream primitive type name + internal const string EdmStreamTypeName = "Edm.Stream"; + + /// edm timeOfDay primitive type name + internal const string EdmTimeOfDayTypeName = "Edm.TimeOfDay"; + + /// edm geography primitive type name + internal const string EdmGeographyTypeName = "Edm.Geography"; + + /// Represents a geography Point type. + internal const string EdmPointTypeName = "Edm.GeographyPoint"; + + /// Represents a geography LineString type. + internal const string EdmLineStringTypeName = "Edm.GeographyLineString"; + + /// Represents a geography Polygon type. + internal const string EdmPolygonTypeName = "Edm.GeographyPolygon"; + + /// Represents a geography GeomCollection type. + internal const string EdmGeographyCollectionTypeName = "Edm.GeographyCollection"; + + /// Represents a geography MultiPolygon type. + internal const string EdmMultiPolygonTypeName = "Edm.GeographyMultiPolygon"; + + /// Represents a geography MultiLineString type. + internal const string EdmMultiLineStringTypeName = "Edm.GeographyMultiLineString"; + + /// Represents a geography MultiPoint type. + internal const string EdmMultiPointTypeName = "Edm.GeographyMultiPoint"; + + /// Represents an arbitrary Geometry type. + internal const string EdmGeometryTypeName = "Edm.Geometry"; + + /// Represents a geometry Point type. + internal const string EdmGeometryPointTypeName = "Edm.GeometryPoint"; + + /// Represents a geometry LineString type. + internal const string EdmGeometryLineStringTypeName = "Edm.GeometryLineString"; + + /// Represents a geometry Polygon type. + internal const string EdmGeometryPolygonTypeName = "Edm.GeometryPolygon"; + + /// Represents a geometry GeomCollection type. + internal const string EdmGeometryCollectionTypeName = "Edm.GeometryCollection"; + + /// Represents a geometry MultiPolygon type. + internal const string EdmGeometryMultiPolygonTypeName = "Edm.GeometryMultiPolygon"; + + /// Represents a geometry MultiLineString type. + internal const string EdmGeometryMultiLineStringTypeName = "Edm.GeometryMultiLineString"; + + /// Represents a geometry MultiPoint type. + internal const string EdmGeometryMultiPointTypeName = "Edm.GeometryMultiPoint"; + #endregion Edm Primitive Type Names + + #region CSDL serialization constants + + /// + /// The namespace for Oasis verion of Edmx + /// + internal const string EdmxOasisNamespace = "http://docs.oasis-open.org/odata/ns/edmx"; + + /// The element name of the top-level <Edmx> metadata envelope. + internal const string EdmxName = "Edmx"; + + /// + /// The URI of annotations that are internal and will not be serialized. + /// + internal const string InternalUri = "http://schemas.microsoft.com/ado/2011/04/edm/internal"; + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs new file mode 100644 index 0000000..9779377 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmLibraryExtensions.cs @@ -0,0 +1,1965 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + using Microsoft.Spatial; + using Microsoft.OData.Edm; +#if ODATA_SERVICE + using Microsoft.OData.Service; + using ErrorStrings = Microsoft.OData.Service.Strings; +#endif +#if ODATA_CLIENT + using ErrorStrings = Microsoft.OData.Client.Strings; +#endif +#if !ODATA_SERVICE && !ODATA_CLIENT + using Microsoft.OData.JsonLight; + using ErrorStrings = Microsoft.OData.Strings; + using PlatformHelper = Microsoft.OData.PlatformHelper; +#endif + #endregion Namespaces + + /// + /// Class with code that will eventually live in EdmLib. + /// + /// This class should go away completely when the EdmLib integration is fully done. + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "The class coupling is due to mapping primitive types, lot of different types there.")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm", Justification = "Following EdmLib standards.")] + internal static class EdmLibraryExtensions + { + /// + /// Map of CLR primitive type to EDM primitive type reference. Doesn't include spatial types since they need assignability and not equality. + /// + private static readonly Dictionary PrimitiveTypeReferenceMap = new Dictionary(EqualityComparer.Default); + + /// Type reference for Edm.Boolean. + private static readonly EdmPrimitiveTypeReference BooleanTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean), false); + + /// Type reference for Edm.Byte. + private static readonly EdmPrimitiveTypeReference ByteTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Byte), false); + + /// Type reference for Edm.Decimal. + private static readonly EdmPrimitiveTypeReference DecimalTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Decimal), false); + + /// Type reference for Edm.Double. + private static readonly EdmPrimitiveTypeReference DoubleTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Double), false); + + /// Type reference for Edm.Int16. + private static readonly EdmPrimitiveTypeReference Int16TypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int16), false); + + /// Type reference for Edm.Int32. + private static readonly EdmPrimitiveTypeReference Int32TypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int32), false); + + /// Type reference for Edm.Int64. + private static readonly EdmPrimitiveTypeReference Int64TypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int64), false); + + /// Type reference for Edm.SByte. + private static readonly EdmPrimitiveTypeReference SByteTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.SByte), false); + + /// Type reference for Edm.String. + private static readonly EdmPrimitiveTypeReference StringTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.String), true); + + /// Type reference for Edm.Float. + private static readonly EdmPrimitiveTypeReference SingleTypeReference = ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Single), false); + + #region Edm Collection constants + + /// The qualifier to turn a type name into a Collection type name. + private const string CollectionTypeQualifier = "Collection"; + + /// Format string to describe a Collection of a given type. + private const string CollectionTypeFormat = CollectionTypeQualifier + "({0})"; + + #endregion Edm Collection constants + + /// + /// Constructor. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to use the static constructor for the phone platform.")] + static EdmLibraryExtensions() + { + PrimitiveTypeReferenceMap.Add(typeof(Boolean), BooleanTypeReference); + PrimitiveTypeReferenceMap.Add(typeof(Byte), ByteTypeReference); + PrimitiveTypeReferenceMap.Add(typeof(Decimal), DecimalTypeReference); + PrimitiveTypeReferenceMap.Add(typeof(Double), DoubleTypeReference); + PrimitiveTypeReferenceMap.Add(typeof(Int16), Int16TypeReference); + PrimitiveTypeReferenceMap.Add(typeof(Int32), Int32TypeReference); + PrimitiveTypeReferenceMap.Add(typeof(Int64), Int64TypeReference); + PrimitiveTypeReferenceMap.Add(typeof(SByte), SByteTypeReference); + PrimitiveTypeReferenceMap.Add(typeof(String), StringTypeReference); + PrimitiveTypeReferenceMap.Add(typeof(Single), SingleTypeReference); + +#if ODATA_SERVICE + PrimitiveTypeReferenceMap.Add(typeof(DateTime), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset), false)); +#endif + PrimitiveTypeReferenceMap.Add(typeof(DateTimeOffset), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset), false)); + PrimitiveTypeReferenceMap.Add(typeof(Guid), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Guid), false)); + PrimitiveTypeReferenceMap.Add(typeof(TimeSpan), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Duration), false)); + PrimitiveTypeReferenceMap.Add(typeof(byte[]), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Binary), true)); + PrimitiveTypeReferenceMap.Add(typeof(Stream), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Stream), false)); + + PrimitiveTypeReferenceMap.Add(typeof(Boolean?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean), true)); + PrimitiveTypeReferenceMap.Add(typeof(Byte?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Byte), true)); +#if ODATA_SERVICE + PrimitiveTypeReferenceMap.Add(typeof(DateTime?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset), true)); +#endif + PrimitiveTypeReferenceMap.Add(typeof(DateTimeOffset?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset), true)); + + PrimitiveTypeReferenceMap.Add(typeof(Decimal?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Decimal), true)); + PrimitiveTypeReferenceMap.Add(typeof(Double?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Double), true)); + PrimitiveTypeReferenceMap.Add(typeof(Int16?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int16), true)); + PrimitiveTypeReferenceMap.Add(typeof(Int32?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int32), true)); + PrimitiveTypeReferenceMap.Add(typeof(Int64?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int64), true)); + PrimitiveTypeReferenceMap.Add(typeof(SByte?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.SByte), true)); + PrimitiveTypeReferenceMap.Add(typeof(Single?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Single), true)); + PrimitiveTypeReferenceMap.Add(typeof(Guid?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Guid), true)); + PrimitiveTypeReferenceMap.Add(typeof(TimeSpan?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Duration), true)); + PrimitiveTypeReferenceMap.Add(typeof(Date), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Date), false)); + PrimitiveTypeReferenceMap.Add(typeof(Date?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Date), true)); + PrimitiveTypeReferenceMap.Add(typeof(TimeOfDay), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.TimeOfDay), false)); + PrimitiveTypeReferenceMap.Add(typeof(TimeOfDay?), ToTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.TimeOfDay), true)); + } + + #region Internal methods + #region ODataLib only +#if !ODATA_SERVICE && !ODATA_CLIENT + + /// + /// Filters operations by the parameter names. + /// + /// The operation imports. + /// The parameter names. + /// Whether to support case insensitive. + /// Return the operation imports that match the parameter names. + internal static IEnumerable FilterOperationsByParameterNames(this IEnumerable operationImports, IEnumerable parameterNames, bool caseInsensitive) + { + Debug.Assert(operationImports != null, "operationImports"); + Debug.Assert(parameterNames != null, "parameterNames"); + + IList parameterNameList = parameterNames.ToList(); + + foreach (IEdmOperationImport operationImport in operationImports) + { + if (!ParametersSatisfyFunction(operationImport.Operation, parameterNameList, caseInsensitive)) + { + continue; + } + + yield return operationImport; + } + } + + /// + /// Filters the operations by parameter names. + /// + /// The operations. + /// The list of non-binding parameter names to match. + /// Whether case insensitive. + /// The best matching operations based on parameters. + internal static IEnumerable FindBestOverloadBasedOnParameters(this IEnumerable functions, IEnumerable parameters, bool caseInsensitive = false) + { + // The best match out of a list of candidates is the one that has the same number of (non-binding) parameters as specified. + IEnumerable exactMatches = functions.Where(f => f.Operation.Parameters.Count() == parameters.Count()); + return exactMatches.Count() > 0 ? exactMatches : functions; + } + + /// + /// Filters the type of the bound operations in by the bindingtype inheritance hierarchy to type closest to bindingtype. + /// + /// The operations. + /// Type of the binding. + /// The closest Bound Operations to the specified binding type. + internal static IEnumerable FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(this IEnumerable operations, IEdmType bindingType) + { + Debug.Assert(operations != null, "operations"); + Debug.Assert(bindingType != null, "bindingType"); + + IEdmStructuredType nonCollectionBindingType = bindingType as IEdmStructuredType; + if (bindingType.TypeKind == EdmTypeKind.Collection) + { + nonCollectionBindingType = ((IEdmCollectionType)bindingType).ElementType.Definition as IEdmStructuredType; + } + + // If its a type that can't have inheritance then no filtering is necessary + if (nonCollectionBindingType == null) + { + return operations; + } + + Dictionary> sortedOperations = new Dictionary>(new EdmTypeEqualityComparer()); + IEdmType currentClosestType = null; + int currentInheritanceLevelsFromBase = int.MaxValue; + foreach (IEdmOperation operation in operations) + { + if (!operation.IsBound) + { + continue; + } + + IEdmOperationParameter parameter = operation.Parameters.FirstOrDefault(); + if (parameter == null) + { + continue; + } + + IEdmType operationBindingType = parameter.Type.Definition; + IEdmStructuredType operationBindingStructuralType = operationBindingType as IEdmStructuredType; + + if (operationBindingType.TypeKind == EdmTypeKind.Collection) + { + IEdmCollectionType operationBindingCollectionType = operationBindingType as IEdmCollectionType; + operationBindingStructuralType = operationBindingCollectionType.ElementType.Definition as IEdmStructuredType; + } + + if (operationBindingStructuralType == null || !nonCollectionBindingType.IsOrInheritsFrom(operationBindingStructuralType)) + { + continue; + } + + int inheritanceLevelsFromBase = nonCollectionBindingType.InheritanceLevelFromSpecifiedInheritedType(operationBindingStructuralType); + + if (currentInheritanceLevelsFromBase > inheritanceLevelsFromBase) + { + currentInheritanceLevelsFromBase = inheritanceLevelsFromBase; + currentClosestType = operationBindingType; + } + + if (!sortedOperations.ContainsKey(operationBindingType)) + { + sortedOperations[operationBindingType] = new List(); + } + + sortedOperations[operationBindingType].Add(operation); + } + + if (currentClosestType != null) + { + return sortedOperations[currentClosestType]; + } + + return Enumerable.Empty(); + } + + /// + /// Filters the operations by parameter names. + /// + /// The operations. + /// The list of non-binding parameter names to match. + /// Whether case insensitive. + /// The best matching operations based on parameters. + internal static IEnumerable FindBestOverloadBasedOnParameters(this IEnumerable functions, IEnumerable parameters, bool caseInsensitive = false) + { + // The best match out of a list of candidates is the one that has the same number of (non-binding) parameters as specified. + IEnumerable exactMatches = functions.Where(f => f.Parameters.Count() == parameters.Count() + (f.IsBound ? 1 : 0)); + return exactMatches.Count() > 0 ? exactMatches : functions; + } + + /// + /// Filters the operations by parameter names. + /// + /// The operations. + /// The parameters. + /// Whether case insensitive. + /// Operations filtered by parameter. + internal static IEnumerable FilterOperationsByParameterNames(this IEnumerable operations, IEnumerable parameters, bool caseInsensitive) + { + Debug.Assert(operations != null, "operations"); + Debug.Assert(parameters != null, "parameters"); + + IList parameterNameList = parameters.ToList(); + + // TODO: update code that is duplicate between operation and operation import, add more tests. + foreach (IEdmOperation operation in operations) + { + if (!ParametersSatisfyFunction(operation, parameterNameList, caseInsensitive)) + { + continue; + } + + yield return operation; + } + } + + /// + /// Ensures that operations are bound and have a binding parameter, other wise throws an exception. + /// + /// The operations. + internal static void EnsureOperationsBoundWithBindingParameter(this IEnumerable operations) + { + foreach (IEdmOperation operation in operations) + { + if (!operation.IsBound) + { + throw new ODataException(ErrorStrings.EdmLibraryExtensions_UnBoundOperationsFoundFromIEdmModelFindMethodIsInvalid(operation.Name)); + } + + if (operation.Parameters.FirstOrDefault() == null) + { + throw new ODataException(ErrorStrings.EdmLibraryExtensions_NoParameterBoundOperationsFoundFromIEdmModelFindMethodIsInvalid(operation.Name)); + } + } + } + + /// + /// Finds the operation group with the specified namespace and name. If the name contains the function parameters, this + /// method will return the operation with matching parameters. + /// + /// The model to find the operation in. + /// The namespace qualified name of the operation. + /// The Operation group with the specified name or null if no such operation exists. + internal static IEnumerable ResolveOperations(this IEdmModel model, string namespaceQualifiedOperationName) + { + return model.ResolveOperations(namespaceQualifiedOperationName, true /*allowParameterNames*/); + } + + /// + /// Resolves an operation or operation group. + /// + /// The model. + /// The operation name to resolve. The name may be namespace qualified and it may contain parameter type names, e.g. Function1(P1Type,P2Type) + /// Whether parameter type names are allowed to appear in the operation name to resolve. + /// The resolved operation or operation group. + internal static IEnumerable ResolveOperations(this IEdmModel model, string operationName, bool allowParameterTypeNames) + { + // TODO: Resolve duplication of operationImport and operation + if (string.IsNullOrEmpty(operationName)) + { + return Enumerable.Empty(); + } + + int indexOfParameterStart = operationName.IndexOf(JsonLightConstants.FunctionParameterStart); + string operationNameWithoutParameterTypes; + if (indexOfParameterStart > 0) + { + if (!allowParameterTypeNames) + { + return Enumerable.Empty(); + } + + operationNameWithoutParameterTypes = operationName.Substring(0, indexOfParameterStart); + } + else + { + operationNameWithoutParameterTypes = operationName; + } + + IEnumerable operations = model.FindDeclaredOperations(operationNameWithoutParameterTypes); + + if (operations == null) + { + return Enumerable.Empty(); + } + + if (indexOfParameterStart > 0) + { + return operations.Where(f => (f.IsFunction() && f.FullNameWithNonBindingParameters().Equals(operationName, StringComparison.Ordinal)) || f.IsAction()); + } + + return ValidateOperationGroupReturnsOnlyOnKind(operations, operationNameWithoutParameterTypes); + } + + /// + /// Name of the operation with parameters. + /// + /// Operation in question. + /// Name of the operation import with parameters. + internal static string NameWithParameters(this IEdmOperation operation) + { + Debug.Assert(operation != null, "operation != null"); + + return operation.Name + operation.ParameterTypesToString(); + } + + /// + /// Full name of the operation with parameters. + /// + /// Operation in question. + /// Full name of the operation with parameters. + internal static string FullNameWithParameters(this IEdmOperation operation) + { + Debug.Assert(operation != null, "operation != null"); + + return operation.FullName() + operation.ParameterTypesToString(); + } + + /// + /// Full name of the operation with non-binding parameters. + /// + /// Operation in question. + /// Full name of the operation with parameters. + internal static string FullNameWithNonBindingParameters(this IEdmOperation operation) + { + Debug.Assert(operation != null, "operation != null"); + + return operation.FullName() + operation.NonBindingParameterNamesToString(); + } + + /// + /// Name of the operation import with parameters. + /// + /// Operation import in question. + /// Name of the operation import with parameters. + internal static string NameWithParameters(this IEdmOperationImport operationImport) + { + Debug.Assert(operationImport != null, "operationImport != null"); + + return operationImport.Name + operationImport.ParameterTypesToString(); + } + + /// + /// Full name of the operation import with parameters. + /// + /// Operation import in question. + /// Full name of the operation import with parameters. + internal static string FullNameWithParameters(this IEdmOperationImport operationImport) + { + Debug.Assert(operationImport != null, "operationImport != null"); + + return operationImport.FullName() + operationImport.ParameterTypesToString(); + } + + /// + /// Removes the actions. + /// + /// The source. + /// The action items. + /// Only the functions from the operation sequence. + internal static IEnumerable RemoveActions(this IEnumerable source, out IList actionItems) + { + List functions = new List(); + + actionItems = new List(); + foreach (var item in source) + { + if (item.IsAction()) + { + actionItems.Add(item); + } + else + { + functions.Add(item); + } + } + + return functions; + } + + /// + /// Removes the action imports. + /// + /// The source. + /// The action import items. + /// Only the function imports from the operation Import sequence. + internal static IEnumerable RemoveActionImports(this IEnumerable source, out IList actionImportItems) + { + List functions = new List(); + + actionImportItems = new List(); + foreach (var item in source) + { + if (item.IsActionImport()) + { + actionImportItems.Add(item); + } + else + { + functions.Add(item); + } + } + + return functions; + } + + /// + /// A method that determines whether a given model is a user model or one of the built-in core models + /// that can only used for primitive type resolution. + /// + /// The model to check. + /// true if the is a user model; otherwise false. + internal static bool IsUserModel(this IEdmModel model) + { + Debug.Assert(model != null, "model != null"); + + return !(model is EdmCoreModel); + } + + /// + /// Checks whether the provided is a supported primitive type. + /// + /// The CLR type to check. + /// true if the is a supported primitive type; otherwise false. + internal static bool IsPrimitiveType(Type clrType) + { + Debug.Assert(clrType != null, "clrType != null"); + + if (clrType == typeof(UInt16) || clrType == typeof(UInt32) || clrType == typeof(UInt64)) + { + // Since UInt types are not in the core model, they cannot be found in the map below. + return true; + } + + return PrimitiveTypeReferenceMap.ContainsKey(clrType) || typeof(ISpatial).IsAssignableFrom(clrType); + } + + /// + /// Creates a collection value type for the specified . + /// + /// The for the item type. + /// The created . + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Extension method for primitive type references only.")] + internal static IEdmCollectionTypeReference ToCollectionTypeReference(this IEdmPrimitiveTypeReference itemTypeReference) + { + IEdmCollectionType collectionType = new EdmCollectionType(itemTypeReference); + return (IEdmCollectionTypeReference)ToTypeReference(collectionType); + } + + /// + /// Creates a collection type for the specified . + /// + /// The for the item type. + /// The created . + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Extension method for complex type references only.")] + internal static IEdmCollectionTypeReference ToCollectionTypeReference(this IEdmComplexTypeReference itemTypeReference) + { + IEdmCollectionType collectionType = new EdmCollectionType(itemTypeReference); + return (IEdmCollectionTypeReference)ToTypeReference(collectionType); + } + + /// + /// Checks if the type reference is assignable from the type reference. + /// In other words, if is a subtype of or not. + /// + /// Type of the base type. + /// Type of the sub type. + /// true, if the is assignable to . Otherwise returns false. + /// Note that this method only checks the type definition for assignability; it does not consider nullability + /// or any other facets of the type reference. + internal static bool IsAssignableFrom(this IEdmTypeReference baseType, IEdmTypeReference subtype) + { + Debug.Assert(baseType != null, "baseType != null"); + Debug.Assert(subtype != null, "subtype != null"); + + // We only consider the type definition but no facets (incl. nullability) here. + return baseType.Definition.IsAssignableFrom(subtype.Definition); + } + + /// + /// Checks if the type is assignable from the type. + /// In other words, if is a subtype of or not. + /// + /// Type of the base type. + /// Type of the sub type. + /// true, if the is assignable to . Otherwise returns false. + internal static bool IsAssignableFrom(this IEdmType baseType, IEdmType subtype) + { + Debug.Assert(baseType != null, "baseType != null"); + Debug.Assert(subtype != null, "subtype != null"); + + baseType = baseType.AsActualType(); + subtype = subtype.AsActualType(); + + EdmTypeKind baseTypeKind = baseType.TypeKind; + EdmTypeKind subTypeKind = subtype.TypeKind; + + // If the type kinds don't match, the types are not assignable. + if (baseTypeKind != subTypeKind) + { + return false; + } + + switch (baseTypeKind) + { + case EdmTypeKind.Primitive: + return ((IEdmPrimitiveType)baseType).IsAssignableFrom((IEdmPrimitiveType)subtype); + + case EdmTypeKind.Untyped: // fall through + case EdmTypeKind.Entity: // fall through + case EdmTypeKind.Complex: + return ((IEdmStructuredType)baseType).IsAssignableFrom((IEdmStructuredType)subtype); + + case EdmTypeKind.Collection: + // NOTE: we do allow inheritance (or co-/contra-variance) in collection types. + return ((IEdmCollectionType)baseType).ElementType.Definition.IsAssignableFrom(((IEdmCollectionType)subtype).ElementType.Definition); + + case EdmTypeKind.Enum: + return baseType.IsEquivalentTo(subtype); + + default: + throw new ODataException(ErrorStrings.General_InternalError(InternalErrorCodesCommon.EdmLibraryExtensions_IsAssignableFrom_Type)); + } + } + + /// + /// Checks if the structured type and the structured type + /// have a common base type. + /// In other words, if is a subtype of or not. + /// + /// Type of the base type. + /// Type of the sub type. + /// The common base type or null if no common base type exists. + internal static IEdmStructuredType GetCommonBaseType(this IEdmStructuredType firstType, IEdmStructuredType secondType) + { + Debug.Assert(firstType != null, "firstType != null"); + Debug.Assert(secondType != null, "secondType != null"); + + if (firstType.IsEquivalentTo(secondType)) + { + return firstType; + } + + IEdmStructuredType commonBaseType = firstType; + while (commonBaseType != null) + { + if (commonBaseType.IsAssignableFrom(secondType)) + { + return commonBaseType; + } + + commonBaseType = commonBaseType.BaseType; + } + + commonBaseType = secondType; + while (commonBaseType != null) + { + if (commonBaseType.IsAssignableFrom(firstType)) + { + return commonBaseType; + } + + commonBaseType = commonBaseType.BaseType; + } + + return null; + } + + /// + /// Checks if the primitive type and the primitive type + /// have a common base type. + /// In other words, if is a subtype of or not. + /// + /// Type of the base type. + /// Type of the sub type. + /// The common base type or null if no common base type exists. + internal static IEdmPrimitiveType GetCommonBaseType(this IEdmPrimitiveType firstType, IEdmPrimitiveType secondType) + { + Debug.Assert(firstType != null, "firstType != null"); + Debug.Assert(secondType != null, "secondType != null"); + + if (firstType.IsEquivalentTo(secondType)) + { + return firstType; + } + + IEdmPrimitiveType commonBaseType = firstType; + while (commonBaseType != null) + { + if (commonBaseType.IsAssignableFrom(secondType)) + { + return commonBaseType; + } + + commonBaseType = commonBaseType.BaseType(); + } + + commonBaseType = secondType; + while (commonBaseType != null) + { + if (commonBaseType.IsAssignableFrom(firstType)) + { + return commonBaseType; + } + + commonBaseType = commonBaseType.BaseType(); + } + + return null; + } + + /// + /// Returns the base type of a primitive type. + /// + /// The to get the base type for. + /// The base type of the or null if no base type exists. + internal static IEdmPrimitiveType BaseType(this IEdmPrimitiveType type) + { + Debug.Assert(type != null, "type != null"); + + switch (type.PrimitiveKind) + { + case EdmPrimitiveTypeKind.None: + case EdmPrimitiveTypeKind.Binary: + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Decimal: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.String: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.Geometry: + return null; + + case EdmPrimitiveTypeKind.GeographyPoint: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geography); + + case EdmPrimitiveTypeKind.GeographyLineString: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geography); + + case EdmPrimitiveTypeKind.GeographyPolygon: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geography); + + case EdmPrimitiveTypeKind.GeographyCollection: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geography); + + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyCollection); + + case EdmPrimitiveTypeKind.GeographyMultiLineString: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyCollection); + + case EdmPrimitiveTypeKind.GeographyMultiPoint: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyCollection); + + case EdmPrimitiveTypeKind.GeometryPoint: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geometry); + + case EdmPrimitiveTypeKind.GeometryLineString: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geometry); + + case EdmPrimitiveTypeKind.GeometryPolygon: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geometry); + + case EdmPrimitiveTypeKind.GeometryCollection: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geometry); + + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryCollection); + + case EdmPrimitiveTypeKind.GeometryMultiLineString: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryCollection); + + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryCollection); + + default: + throw new ODataException(ErrorStrings.General_InternalError(InternalErrorCodesCommon.EdmLibraryExtensions_BaseType)); + } + } + + /// + /// Casts an to a or returns null if this is not supported. + /// + /// The type reference to convert. + /// An instance or null if the cannot be converted. + internal static IEdmCollectionTypeReference AsCollectionOrNull(this IEdmTypeReference typeReference) + { + if (typeReference == null) + { + return null; + } + + if (typeReference.TypeKind() != EdmTypeKind.Collection) + { + return null; + } + + IEdmCollectionTypeReference collectionTypeReference = typeReference.AsCollection(); + if (!collectionTypeReference.IsNonEntityCollectionType()) + { + return null; + } + + return collectionTypeReference; + } + + /// + /// Compare if the two collection types are equal on the element type regardless of nullable, facets. + /// + /// The left operand. + /// The right operand. + /// If the two collection types are equal on the element type regardless of nullable, facets. + internal static bool IsElementTypeEquivalentTo(this IEdmType type, IEdmType other) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(other != null, "other != null"); + + if (type.TypeKind != EdmTypeKind.Collection || other.TypeKind != EdmTypeKind.Collection) + { + return false; + } + + return ((IEdmCollectionType)type).ElementType.Definition.IsEquivalentTo(((IEdmCollectionType)other).ElementType.Definition); + } + + /// + /// Convert the value to underlying type according to model if the value is uint; + /// otherwise return the original value directly. + /// + /// The given model. + /// The value to convert. + /// The expected type reference of the value (null by default). + /// The converted value. + internal static object ConvertToUnderlyingTypeIfUIntValue(this IEdmModel model, object value, IEdmTypeReference expectedTypeReference = null) + { + if (model == null) + { + return value; + } + + try + { + if (expectedTypeReference == null) + { + expectedTypeReference = model.ResolveUIntTypeDefinition(value); + } + + if (expectedTypeReference != null) + { + return model.GetPrimitiveValueConverter(expectedTypeReference).ConvertToUnderlyingType(value); + } + + return value; + } + catch (OverflowException) + { + throw new ODataException(ErrorStrings.EdmLibraryExtensions_ValueOverflowForUnderlyingType(value, expectedTypeReference.FullName())); + } + } + + /// + /// Try to resolve the type definition from the model if value is unsigned int. + /// + /// The given model. + /// The given value. + /// The type reference to type definition corresponding to the value. + internal static IEdmTypeDefinitionReference ResolveUIntTypeDefinition(this IEdmModel model, object value) + { + if (model == null) + { + // type cannot be determined without model. + return null; + } + + if (value == null) + { + // null is not unsigned int. + return null; + } + + if (!(value is UInt16 || value is UInt32 || value is UInt64)) + { + // Not unsigned ints. + return null; + } + + IEdmTypeDefinition typeDefinition = model.SchemaElements.SingleOrDefault(e => string.CompareOrdinal(e.Name, value.GetType().Name) == 0) as IEdmTypeDefinition; + return typeDefinition == null ? null : new EdmTypeDefinitionReference(typeDefinition, true); + } + + /// + /// Resolves the name of a primitive type. + /// + /// The name of the type to resolve. + /// The representing the type specified by the ; + /// or null if no such type could be found. + internal static IEdmSchemaType ResolvePrimitiveTypeName(string typeName) + { + return EdmCoreModel.Instance.FindDeclaredType(typeName); + } + + /// + /// Get the of the item type of the . + /// + /// The collection type to get the item type for. + /// The item type of the . + internal static IEdmTypeReference GetCollectionItemType(this IEdmTypeReference typeReference) + { + IEdmCollectionTypeReference collectionType = typeReference.AsCollectionOrNull(); + return collectionType == null ? null : collectionType.ElementType(); + } + + /// + /// Returns the IEdmCollectionType implementation with the given IEdmType as nullable element type. + /// + /// IEdmType instance which is the element type. + /// An instance using the as Collection item type. + internal static IEdmCollectionType GetCollectionType(IEdmType itemType) + { + Debug.Assert(itemType != null, "itemType != null"); + + IEdmTypeReference itemTypeReference = EdmLibraryExtensions.ToTypeReference(itemType, true); + return GetCollectionType(itemTypeReference); + } + + /// + /// Returns the IEdmCollectionType implementation with the given IEdmTypeReference as element type. + /// + /// IEdmTypeReference instance which is the element type. + /// An instance using the as Collection item type. + internal static IEdmCollectionType GetCollectionType(IEdmTypeReference itemTypeReference) + { + Debug.Assert(itemTypeReference != null, "itemTypeReference != null"); + + return new EdmCollectionType(itemTypeReference); + } + + /// + /// Returns CollectionValue item type name or null if the provided type name is not a collectionValue. + /// + /// CollectionValue type name read from payload. + /// CollectionValue element type name or null if not a collectionValue. + internal static string GetCollectionItemTypeName(string typeName) + { + return GetCollectionItemTypeName(typeName, false); + } + + /// + /// Gets the collection full type name from the given type string. + /// + /// The original collection type string. + /// The full type name for the given origin type name/>. + internal static string GetCollectionTypeFullName(string typeName) + { + if (typeName != null) + { + string innerTypeName = GetCollectionItemTypeName(typeName); + if (innerTypeName != null) + { + IEdmSchemaType primitiveType = EdmCoreModel.Instance.FindDeclaredType(innerTypeName); + if (primitiveType != null) + { + return GetCollectionTypeName(primitiveType.FullName()); + } + } + } + + return typeName; + } + + /// + /// Determines whether operations bound to this type must be qualified with the operation they belong to when appearing in a $select clause. + /// + /// The structured type the operations are bound to. + /// True if the operations must be container qualified, otherwise false. + internal static bool OperationsBoundToStructuredTypeMustBeContainerQualified(this IEdmStructuredType structuredType) + { + Debug.Assert(structuredType != null, "structuredType != null"); + return structuredType.IsOpen; + } +#endif + #endregion + + #region ODataLib and WCF DS Server +#if !ODATA_CLIENT + /// + /// Gets the Partail name of the definition referred to by the type reference. + /// + /// The type reference to get the partial name for. + /// The partial name of this . + /// + /// Note that this method is different from the EdmLib PartialName extension method in that it also returns + /// names for collection types. For EdmLib, collection types are functions and thus don't have a Partial name. + /// The name/string they use in CSDL is just shorthand for them. + /// + internal static string ODataShortQualifiedName(this IEdmTypeReference typeReference) + { + Debug.Assert(typeReference != null, "typeReference != null"); + Debug.Assert(typeReference.Definition != null, "typeReference.Definition != null"); + return typeReference.Definition.ODataShortQualifiedName(); + } + + /// + /// Gets the Partial name of the type. + /// + /// The type to get the partial name for. + /// The partial name of the . + /// + /// Note that this method is different from the EdmLib PartialName extension method in that it also returns + /// names for collection types. For EdmLib, collection types are functions and thus don't have a full name. + /// The name/string they use in CSDL is just shorthand for them. + /// + internal static string ODataShortQualifiedName(this IEdmType type) + { + Debug.Assert(type != null, "type != null"); + + // Handle collection type names here since for EdmLib collection values are functions + // that do not have a full name + IEdmCollectionType collectionType = type as IEdmCollectionType; + if (collectionType != null) + { + string elementTypeName = collectionType.ElementType.ODataShortQualifiedName(); + if (elementTypeName == null) + { + return null; + } + + return GetCollectionTypeName(elementTypeName); + } + + var namedDefinition = type as IEdmSchemaElement; + return namedDefinition != null ? namedDefinition.ShortQualifiedName() : null; + } + + /// + /// Clones the specified type reference. + /// + /// The type reference to clone. + /// true to make the cloned type reference nullable; false to make it non-nullable. + /// The cloned instance. + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "The clone logic should stay in one place.")] + internal static IEdmTypeReference Clone(this IEdmTypeReference typeReference, bool nullable) + { + if (typeReference == null) + { + return null; + } + + EdmTypeKind typeKind = typeReference.TypeKind(); + switch (typeKind) + { + case EdmTypeKind.Primitive: + EdmPrimitiveTypeKind kind = typeReference.PrimitiveKind(); + IEdmPrimitiveType primitiveType = (IEdmPrimitiveType)typeReference.Definition; + switch (kind) + { + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.PrimitiveType: + return new EdmPrimitiveTypeReference(primitiveType, nullable); + case EdmPrimitiveTypeKind.Binary: + IEdmBinaryTypeReference binaryTypeReference = (IEdmBinaryTypeReference)typeReference; + return new EdmBinaryTypeReference( + primitiveType, + nullable, + binaryTypeReference.IsUnbounded, + binaryTypeReference.MaxLength); + + case EdmPrimitiveTypeKind.String: + IEdmStringTypeReference stringTypeReference = (IEdmStringTypeReference)typeReference; + return new EdmStringTypeReference( + primitiveType, + nullable, + stringTypeReference.IsUnbounded, + stringTypeReference.MaxLength, + stringTypeReference.IsUnicode); + + case EdmPrimitiveTypeKind.Decimal: + IEdmDecimalTypeReference decimalTypeReference = (IEdmDecimalTypeReference)typeReference; + return new EdmDecimalTypeReference(primitiveType, nullable, decimalTypeReference.Precision, decimalTypeReference.Scale); + + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + IEdmTemporalTypeReference temporalTypeReference = (IEdmTemporalTypeReference)typeReference; + return new EdmTemporalTypeReference(primitiveType, nullable, temporalTypeReference.Precision); + + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + IEdmSpatialTypeReference spatialTypeReference = (IEdmSpatialTypeReference)typeReference; + return new EdmSpatialTypeReference(primitiveType, nullable, spatialTypeReference.SpatialReferenceIdentifier); + + default: + throw new ODataException(ErrorStrings.General_InternalError(InternalErrorCodesCommon.EdmLibraryExtensions_Clone_PrimitiveTypeKind)); + } + + case EdmTypeKind.Entity: + return new EdmEntityTypeReference((IEdmEntityType)typeReference.Definition, nullable); + + case EdmTypeKind.Complex: + return new EdmComplexTypeReference((IEdmComplexType)typeReference.Definition, nullable); + + case EdmTypeKind.Collection: + return new EdmCollectionTypeReference((IEdmCollectionType)typeReference.Definition); + + case EdmTypeKind.EntityReference: + return new EdmEntityReferenceTypeReference((IEdmEntityReferenceType)typeReference.Definition, nullable); + + case EdmTypeKind.Enum: + return new EdmEnumTypeReference((IEdmEnumType)typeReference.Definition, nullable); + + case EdmTypeKind.None: // fall through + default: + throw new ODataException(ErrorStrings.General_InternalError(InternalErrorCodesCommon.EdmLibraryExtensions_Clone_TypeKind)); + } + } + + /// + /// Gets the full name of a operation group. + /// + /// The operation group in question. + /// The full name of the operation group. + internal static string OperationGroupFullName(this IEnumerable operationGroup) + { + // TODO: Resolve duplication of operationImport and operation + Debug.Assert(operationGroup != null && operationGroup.Any(), "operationGroup != null && operationGroup.Any()"); + + string fullName = operationGroup.First().FullName(); + Debug.Assert(operationGroup.All(f => f.FullName() == fullName), "operationGroup.All(f => f.FullName() == fullName)"); + return fullName; + } + + /// + /// Gets the full name of a operation import group. + /// + /// The operation import group in question. + /// The full name of the operation import group. + internal static string OperationImportGroupFullName(this IEnumerable operationImportGroup) + { + // TODO: Resolve duplication of operationImport and operation + Debug.Assert(operationImportGroup != null && operationImportGroup.Any(), "operationImportGroup != null && operationImportGroup.Any()"); + + string fullName = operationImportGroup.First().FullName(); + Debug.Assert(operationImportGroup.All(f => f.FullName() == fullName), "operationImportGroup.All(f => f.FullName() == fullName)"); + return fullName; + } +#endif + #endregion + + #region ODataLib and Query project +#if !ODATA_SERVICE && !ODATA_CLIENT + /// + /// Checks if the is assignable to . + /// In other words, if is a subtype of or not. + /// + /// Type of the base type. + /// Type of the sub type. + /// true, if the is assignable to . Otherwise returns false. + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Extension method for structured types only.")] + internal static bool IsAssignableFrom(this IEdmStructuredType baseType, IEdmStructuredType subtype) + { + Debug.Assert(baseType != null, "baseType != null"); + Debug.Assert(subtype != null, "subtype != null"); + + if (baseType.TypeKind == EdmTypeKind.Untyped) + { + return true; + } + + if (baseType.TypeKind != subtype.TypeKind) + { + return false; + } + + if (subtype.IsEquivalentTo(baseType)) + { + return true; + } + + // If the base type is "Edm.ComplexType" or "Edm.EntityType", any other structure type can assign to it. + if (baseType == EdmCoreModel.Instance.GetComplexType() || + baseType == EdmCoreModel.Instance.GetEntityType()) + { + return true; + } + + if (!baseType.IsODataEntityTypeKind() && !baseType.IsODataComplexTypeKind()) + { + // we only support complex and entity type inheritance. + return false; + } + + IEdmStructuredType structuredSubType = subtype; + while (structuredSubType != null) + { + if (structuredSubType.IsEquivalentTo(baseType)) + { + return true; + } + + structuredSubType = structuredSubType.BaseType; + } + + return false; + } + + /// + /// Checks if the primitive type is assignable to primitive type. + /// In other words, if is a subtype of or not. + /// + /// Type of the base type. + /// Type of the sub type. + /// true, if the is assignable to . Otherwise returns false. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Need to keep code together.")] + internal static bool IsAssignableFrom(this IEdmPrimitiveType baseType, IEdmPrimitiveType subtype) + { + Debug.Assert(baseType != null, "baseType != null"); + Debug.Assert(subtype != null, "subtype != null"); + + if (baseType.IsEquivalentTo(subtype)) + { + return true; + } + + // If the base type is "Edm.Primitive", any other primitive type can assign to it. + if (baseType.PrimitiveKind == EdmPrimitiveTypeKind.PrimitiveType) + { + return true; + } + + // Only spatial types are assignable + if (!baseType.IsSpatial() || !subtype.IsSpatial()) + { + return false; + } + + // For two spatial types, test for assignability + EdmPrimitiveTypeKind baseTypeKind = baseType.PrimitiveKind; + EdmPrimitiveTypeKind subTypeKind = subtype.PrimitiveKind; + + switch (baseTypeKind) + { + case EdmPrimitiveTypeKind.Geography: + return subTypeKind == EdmPrimitiveTypeKind.Geography || + subTypeKind == EdmPrimitiveTypeKind.GeographyCollection || + subTypeKind == EdmPrimitiveTypeKind.GeographyLineString || + subTypeKind == EdmPrimitiveTypeKind.GeographyMultiLineString || + subTypeKind == EdmPrimitiveTypeKind.GeographyMultiPoint || + subTypeKind == EdmPrimitiveTypeKind.GeographyMultiPolygon || + subTypeKind == EdmPrimitiveTypeKind.GeographyPoint || + subTypeKind == EdmPrimitiveTypeKind.GeographyPolygon; + + case EdmPrimitiveTypeKind.GeographyPoint: + return subTypeKind == EdmPrimitiveTypeKind.GeographyPoint; + + case EdmPrimitiveTypeKind.GeographyLineString: + return subTypeKind == EdmPrimitiveTypeKind.GeographyLineString; + + case EdmPrimitiveTypeKind.GeographyPolygon: + return subTypeKind == EdmPrimitiveTypeKind.GeographyPolygon; + + case EdmPrimitiveTypeKind.GeographyCollection: + return subTypeKind == EdmPrimitiveTypeKind.GeographyCollection || + subTypeKind == EdmPrimitiveTypeKind.GeographyMultiLineString || + subTypeKind == EdmPrimitiveTypeKind.GeographyMultiPoint || + subTypeKind == EdmPrimitiveTypeKind.GeographyMultiPolygon; + + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + return subTypeKind == EdmPrimitiveTypeKind.GeographyMultiPolygon; + + case EdmPrimitiveTypeKind.GeographyMultiLineString: + return subTypeKind == EdmPrimitiveTypeKind.GeographyMultiLineString; + + case EdmPrimitiveTypeKind.GeographyMultiPoint: + return subTypeKind == EdmPrimitiveTypeKind.GeographyMultiPoint; + + case EdmPrimitiveTypeKind.Geometry: + return subTypeKind == EdmPrimitiveTypeKind.Geometry || + subTypeKind == EdmPrimitiveTypeKind.GeometryCollection || + subTypeKind == EdmPrimitiveTypeKind.GeometryLineString || + subTypeKind == EdmPrimitiveTypeKind.GeometryMultiLineString || + subTypeKind == EdmPrimitiveTypeKind.GeometryMultiPoint || + subTypeKind == EdmPrimitiveTypeKind.GeometryMultiPolygon || + subTypeKind == EdmPrimitiveTypeKind.GeometryPoint || + subTypeKind == EdmPrimitiveTypeKind.GeometryPolygon; + + case EdmPrimitiveTypeKind.GeometryPoint: + return subTypeKind == EdmPrimitiveTypeKind.GeometryPoint; + + case EdmPrimitiveTypeKind.GeometryLineString: + return subTypeKind == EdmPrimitiveTypeKind.GeometryLineString; + + case EdmPrimitiveTypeKind.GeometryPolygon: + return subTypeKind == EdmPrimitiveTypeKind.GeometryPolygon; + + case EdmPrimitiveTypeKind.GeometryCollection: + return subTypeKind == EdmPrimitiveTypeKind.GeometryCollection || + subTypeKind == EdmPrimitiveTypeKind.GeometryMultiLineString || + subTypeKind == EdmPrimitiveTypeKind.GeometryMultiPoint || + subTypeKind == EdmPrimitiveTypeKind.GeometryMultiPolygon; + + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + return subTypeKind == EdmPrimitiveTypeKind.GeometryMultiPolygon; + + case EdmPrimitiveTypeKind.GeometryMultiLineString: + return subTypeKind == EdmPrimitiveTypeKind.GeometryMultiLineString; + + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return subTypeKind == EdmPrimitiveTypeKind.GeometryMultiPoint; + + default: + throw new ODataException(ErrorStrings.General_InternalError(InternalErrorCodesCommon.EdmLibraryExtensions_IsAssignableFrom_Primitive)); + } + } + + /// + /// Returns the primitive CLR type for the specified primitive type reference. + /// + /// The primitive type to resolve. + /// The CLR type for the primitive type reference. + internal static Type GetPrimitiveClrType(IEdmPrimitiveTypeReference primitiveTypeReference) + { + Debug.Assert(primitiveTypeReference != null, "primitiveTypeReference != null"); + + return GetPrimitiveClrType(primitiveTypeReference.PrimitiveDefinition(), primitiveTypeReference.IsNullable); + } + + /// + /// Turns a into the corresponding non-nullable . + /// + /// The type to convert. + /// A non-nullable type reference for the . + internal static IEdmTypeReference ToTypeReference(this IEdmType type) + { + return ToTypeReference(type, false /*nullable*/); + } + +#endif + #endregion + + #region Everyone + /// + /// Returns the fully qualified name of an entity container element. + /// + /// The container element to get the full name for. + /// The full name of the owning entity container, slash, name of the container element. + internal static string FullName(this IEdmEntityContainerElement containerElement) + { + Debug.Assert(containerElement != null, "containerElement != null"); + + return containerElement.Container.Name + "." + containerElement.Name; + } + +#if !ODATA_CLIENT + /// + /// Returns the primitive type reference for the given Clr type. + /// + /// The Clr type to resolve. + /// The primitive type reference for the given Clr type. + [SuppressMessage("Microsoft.Maintainability", "CA1502", Justification = "cyclomatic complexity")] + internal static IEdmPrimitiveTypeReference GetPrimitiveTypeReference(Type clrType) + { + Debug.Assert(clrType != null, "clrType != null"); + + // Try to lookup the type in our map. + IEdmPrimitiveTypeReference primitiveTypeReference; + if (PrimitiveTypeReferenceMap.TryGetValue(clrType, out primitiveTypeReference)) + { + return primitiveTypeReference; + } + + // If it didn't work, try spatial types which need assignability. + IEdmPrimitiveType primitiveType = null; + if (typeof(GeographyPoint).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyPoint); + } + else if (typeof(GeographyLineString).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyLineString); + } + else if (typeof(GeographyPolygon).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyPolygon); + } + else if (typeof(GeographyMultiPoint).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiPoint); + } + else if (typeof(GeographyMultiLineString).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiLineString); + } + else if (typeof(GeographyMultiPolygon).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiPolygon); + } + else if (typeof(GeographyCollection).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeographyCollection); + } + else if (typeof(Geography).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geography); + } + else if (typeof(GeometryPoint).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryPoint); + } + else if (typeof(GeometryLineString).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryLineString); + } + else if (typeof(GeometryPolygon).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryPolygon); + } + else if (typeof(GeometryMultiPoint).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiPoint); + } + else if (typeof(GeometryMultiLineString).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiLineString); + } + else if (typeof(GeometryMultiPolygon).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiPolygon); + } + else if (typeof(GeometryCollection).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.GeometryCollection); + } + else if (typeof(Geometry).IsAssignableFrom(clrType)) + { + primitiveType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Geometry); + } + + if (primitiveType == null) + { + return null; + } + + // All spatial CLR types are inherently nullable + return ToTypeReference(primitiveType, true); + } +#endif + + /// + /// Turns a into the corresponding . + /// + /// The type to convert. + /// true if the returned type reference should be nullable; otherwise false. + /// A type reference for the . + internal static IEdmTypeReference ToTypeReference(this IEdmType type, bool nullable) + { + if (type == null) + { + return null; + } + + switch (type.TypeKind) + { + case EdmTypeKind.Primitive: + return ToTypeReference((IEdmPrimitiveType)type, nullable); + case EdmTypeKind.Enum: + return new EdmEnumTypeReference((IEdmEnumType)type, nullable); + case EdmTypeKind.Untyped: + IEdmStructuredType untypedType = type as IEdmStructuredType; + if (untypedType != null) + { + return new EdmUntypedStructuredTypeReference(untypedType); + } + + return new EdmUntypedTypeReference((IEdmUntypedType)type); + case EdmTypeKind.Complex: + return new EdmComplexTypeReference((IEdmComplexType)type, nullable); + case EdmTypeKind.Entity: + return new EdmEntityTypeReference((IEdmEntityType)type, nullable); + case EdmTypeKind.Collection: + return new EdmCollectionTypeReference((IEdmCollectionType)type); + case EdmTypeKind.EntityReference: + return new EdmEntityReferenceTypeReference((IEdmEntityReferenceType)type, nullable); + case EdmTypeKind.TypeDefinition: + return new EdmTypeDefinitionReference((IEdmTypeDefinition)type, nullable); + case EdmTypeKind.None: + default: + throw new ODataException(ErrorStrings.General_InternalError(InternalErrorCodesCommon.EdmLibraryExtensions_ToTypeReference)); + } + } + + /// + /// Creates the EDM type name for a collection of the specified item type name. E.g. Collection(Edm.String) + /// + /// Type name of the items in the collection. + /// Type name for a collection of the specified item type name. + internal static string GetCollectionTypeName(string itemTypeName) + { + return string.Format(CultureInfo.InvariantCulture, CollectionTypeFormat, itemTypeName); + } + +#if !ODATA_CLIENT + /// + /// Resolves a operation import or operation import group. + /// + /// The entity container. + /// The operation import name to resolve. May contain parameter type names, e.g. Function1(P1Type,P2Type) + /// The resolved operation import or operation import group. + internal static IEnumerable ResolveOperationImports(this IEdmEntityContainer container, string operationImportName) + { + return ResolveOperationImports(container, operationImportName, true); + } + + /// + /// Resolves an operation import or operation import group. + /// + /// The entity container. + /// The operation import name to resolve. May contain parameter type names, e.g. Function1(P1Type,P2Type) + /// Whether parameter type names are allowed to appear in the operation import name to resolve. + /// The resolved operation import or operation import group. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "allowParameterTypeNames", Justification = "Used in the ODL version of the method.")] + internal static IEnumerable ResolveOperationImports(this IEdmEntityContainer container, string operationImportName, bool allowParameterTypeNames) + { + // TODO: Resolve duplication of operationImport and operation + Debug.Assert(container != null, "container != null"); + + if (string.IsNullOrEmpty(operationImportName)) + { + return Enumerable.Empty(); + } + +#if !ODATA_SERVICE && !ODATA_CLIENT + int indexOfParameterStart = operationImportName.IndexOf(JsonLightConstants.FunctionParameterStart); + string functionImportNameWithoutParameterTypes = operationImportName; + if (indexOfParameterStart > 0) + { + if (!allowParameterTypeNames) + { + return Enumerable.Empty(); + } + + functionImportNameWithoutParameterTypes = operationImportName.Substring(0, indexOfParameterStart); + } +#else + Debug.Assert(!allowParameterTypeNames, "Parameter type names not supported in the server version of this method."); + string functionImportNameWithoutParameterTypes = operationImportName; +#endif + string containerName = null; + string operationNameWithoutContainerOrNamespace = functionImportNameWithoutParameterTypes; + int lastPeriodPos = functionImportNameWithoutParameterTypes.LastIndexOf('.'); + if (lastPeriodPos > -1) + { + operationNameWithoutContainerOrNamespace = functionImportNameWithoutParameterTypes.Substring(lastPeriodPos, functionImportNameWithoutParameterTypes.Length - lastPeriodPos).TrimStart('.'); + containerName = functionImportNameWithoutParameterTypes.Substring(0, lastPeriodPos); + } + + // if the container name of the operationImport doesn't equal the current container, don't search just return empty as there are no matches. + if (containerName != null && !(container.Name.Equals(containerName) || container.FullName().Equals(containerName))) + { + return Enumerable.Empty(); + } + + IEnumerable operationImports = container.FindOperationImports(operationNameWithoutContainerOrNamespace); + Debug.Assert(operationImports != null, "operationImports != null"); +#if !ODATA_SERVICE && !ODATA_CLIENT + if (indexOfParameterStart > 0) + { + return FilterByOperationParameterTypes(operationImports, functionImportNameWithoutParameterTypes, operationImportName); + } +#endif + return operationImports; + } + + /// + /// Returns the primitive CLR type for the specified primitive type reference. + /// + /// The primitive type to resolve. + /// Whether the returned type should be a nullable variant or not. + /// The CLR type for the primitive type reference. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Not too complex for what this method does.")] + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling is with all the primitive Clr types only.")] + internal static Type GetPrimitiveClrType(IEdmPrimitiveType primitiveType, bool isNullable) + { + Debug.Assert(primitiveType != null, "primitiveType != null"); + + switch (primitiveType.PrimitiveKind) + { + case EdmPrimitiveTypeKind.Binary: + return typeof(byte[]); + case EdmPrimitiveTypeKind.Boolean: + return isNullable ? typeof(Boolean?) : typeof(Boolean); + case EdmPrimitiveTypeKind.Byte: + return isNullable ? typeof(Byte?) : typeof(Byte); + case EdmPrimitiveTypeKind.Date: + return isNullable ? typeof(Date?) : typeof(Date); + case EdmPrimitiveTypeKind.DateTimeOffset: + return isNullable ? typeof(DateTimeOffset?) : typeof(DateTimeOffset); + case EdmPrimitiveTypeKind.Decimal: + return isNullable ? typeof(Decimal?) : typeof(Decimal); + case EdmPrimitiveTypeKind.Double: + return isNullable ? typeof(Double?) : typeof(Double); + case EdmPrimitiveTypeKind.Geography: + return typeof(Geography); + case EdmPrimitiveTypeKind.GeographyCollection: + return typeof(GeographyCollection); + case EdmPrimitiveTypeKind.GeographyLineString: + return typeof(GeographyLineString); + case EdmPrimitiveTypeKind.GeographyMultiLineString: + return typeof(GeographyMultiLineString); + case EdmPrimitiveTypeKind.GeographyMultiPoint: + return typeof(GeographyMultiPoint); + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + return typeof(GeographyMultiPolygon); + case EdmPrimitiveTypeKind.GeographyPoint: + return typeof(GeographyPoint); + case EdmPrimitiveTypeKind.GeographyPolygon: + return typeof(GeographyPolygon); + case EdmPrimitiveTypeKind.Geometry: + return typeof(Geometry); + case EdmPrimitiveTypeKind.GeometryCollection: + return typeof(GeometryCollection); + case EdmPrimitiveTypeKind.GeometryLineString: + return typeof(GeometryLineString); + case EdmPrimitiveTypeKind.GeometryMultiLineString: + return typeof(GeometryMultiLineString); + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return typeof(GeometryMultiPoint); + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + return typeof(GeometryMultiPolygon); + case EdmPrimitiveTypeKind.GeometryPoint: + return typeof(GeometryPoint); + case EdmPrimitiveTypeKind.GeometryPolygon: + return typeof(GeometryPolygon); + case EdmPrimitiveTypeKind.Guid: + return isNullable ? typeof(Guid?) : typeof(Guid); + case EdmPrimitiveTypeKind.Int16: + return isNullable ? typeof(Int16?) : typeof(Int16); + case EdmPrimitiveTypeKind.Int32: + return isNullable ? typeof(Int32?) : typeof(Int32); + case EdmPrimitiveTypeKind.Int64: + return isNullable ? typeof(Int64?) : typeof(Int64); + case EdmPrimitiveTypeKind.SByte: + return isNullable ? typeof(SByte?) : typeof(SByte); + case EdmPrimitiveTypeKind.Single: + return isNullable ? typeof(Single?) : typeof(Single); + case EdmPrimitiveTypeKind.Stream: + return typeof(Stream); + case EdmPrimitiveTypeKind.String: + return typeof(String); + case EdmPrimitiveTypeKind.Duration: + return isNullable ? typeof(TimeSpan?) : typeof(TimeSpan); + case EdmPrimitiveTypeKind.TimeOfDay: + return isNullable ? typeof(TimeOfDay?) : typeof(TimeOfDay); + default: + return null; + } + } + +#endif + #endregion + #endregion + + #region Private methods + #region ODataLib only +#if ODATA_CORE + + /// + /// Validates the kind of the operation group returns only on. + /// + /// The operations. + /// The operation name without parameter types. + /// Enumerable list of operations. + /// If there is an action and function in the sequence. + private static IEnumerable ValidateOperationGroupReturnsOnlyOnKind(IEnumerable operations, string operationNameWithoutParameterTypes) + { + EdmSchemaElementKind? operationKind = null; + foreach (IEdmOperation operation in operations) + { + if (operationKind == null) + { + operationKind = operation.SchemaElementKind; + } + else + { + if (operation.SchemaElementKind != operationKind) + { + throw new ODataException(ErrorStrings.EdmLibraryExtensions_OperationGroupReturningActionsAndFunctionsModelInvalid(operationNameWithoutParameterTypes)); + } + } + + yield return operation; + } + } + + /// + /// Gets the operation parameter types in string. + /// + /// Operation in question. + /// Comma separated operation parameter types enclosed in parantheses. + private static string ParameterTypesToString(this IEdmOperation operation) + { + // TODO: Resolve duplication of operationImport and operation + return JsonLightConstants.FunctionParameterStart + + string.Join(JsonLightConstants.FunctionParameterSeparator, operation.Parameters.Select(p => p.Type.FullName()).ToArray()) + + JsonLightConstants.FunctionParameterEnd; + } + + /// + /// Gets the non binding operation parameter names in string. + /// + /// Operation in question. + /// Comma separated operation parameter names enclosed in parantheses. + private static string NonBindingParameterNamesToString(this IEdmOperation operation) + { + IEnumerable nonBindingParameters = operation.IsBound ? operation.Parameters.Skip(1) : operation.Parameters; + return JsonLightConstants.FunctionParameterStart + string.Join(JsonLightConstants.FunctionParameterSeparator, nonBindingParameters.Select(p => p.Name).ToArray()) + JsonLightConstants.FunctionParameterEnd; + } + + /// + /// Returns Collection item type name or null if the provided type name is not a collection. + /// + /// Collection type name. + /// Whether it is a nested (recursive) call. + /// Collection element type name or null if not a collection. + /// + /// The following rules are used for collection type names: + /// - it has to start with "Collection(" and end with ")" - trailing and leading whitespaces make the type not to be recognized as collection. + /// - there is to be no characters (including whitespaces) between "Collection" and "(" - otherwise it won't berecognized as collection + /// - collection item type name has to be a non-empty string - i.e. "Collection()" won't be recognized as collection + /// - nested collection - e.g. "Collection(Collection(Edm.Int32))" - are not supported - we will throw + /// Note the following are examples of valid type names which are not collection: + /// - "Collection()" + /// - " Collection(Edm.Int32)" + /// - "Collection (Edm.Int32)" + /// - "Collection(" + /// + private static string GetCollectionItemTypeName(string typeName, bool isNested) + { + int collectionTypeQualifierLength = CollectionTypeQualifier.Length; + + // to be recognized as a collection wireTypeName must not be null, has to start with "Collection(" and end with ")" and must not be "Collection()" + if (typeName != null && + typeName.StartsWith(CollectionTypeQualifier + "(", StringComparison.Ordinal) && + typeName[typeName.Length - 1] == ')' && + typeName.Length != collectionTypeQualifierLength + 2) + { + if (isNested) + { + throw new ODataException(ErrorStrings.ValidationUtils_NestedCollectionsAreNotSupported); + } + + string innerTypeName = typeName.Substring(collectionTypeQualifierLength + 1, typeName.Length - (collectionTypeQualifierLength + 2)); + + // Check if it is not a nested collection and throw if it is + GetCollectionItemTypeName(innerTypeName, true); + + return innerTypeName; + } + + return null; + } + + /// + /// Gets the operation import parameter types in string. + /// + /// Function import in question. + /// Comma separated operation import parameter types enclosed in parantheses. + private static string ParameterTypesToString(this IEdmOperationImport operationImport) + { + // TODO: Resolve duplication of operationImport and operation + return JsonLightConstants.FunctionParameterStart + + string.Join(JsonLightConstants.FunctionParameterSeparator, operationImport.Operation.Parameters.Select(p => p.Type.FullName()).ToArray()) + + JsonLightConstants.FunctionParameterEnd; + } + + /// + /// Filters the by operation parameter types. + /// + /// The operation imports. + /// The operation name without parameter types. + /// Name of the original full operation import. + /// A list of EdmOperations that filters out operations that don't match. + private static IEnumerable FilterByOperationParameterTypes(this IEnumerable operationImports, string operationNameWithoutParameterTypes, string originalFullOperationImportName) + { + Debug.Assert(operationImports != null, "operationImports != null"); + + foreach (IEdmOperationImport operationImport in operationImports) + { + // If the operation name is not a full name then filter accordingly by parameter name. + if (operationNameWithoutParameterTypes.IndexOf(".", StringComparison.Ordinal) > -1) + { + if ((operationImport.FullNameWithParameters().Equals(originalFullOperationImportName, StringComparison.Ordinal)) || + ((operationImport.Container.Name + "." + operationImport.NameWithParameters()).Equals(originalFullOperationImportName, StringComparison.Ordinal))) + { + yield return operationImport; + } + } + else + { + if (operationImport.NameWithParameters().Equals(originalFullOperationImportName, StringComparison.Ordinal)) + { + yield return operationImport; + } + } + } + } + + private static bool ParametersSatisfyFunction(IEdmOperation operation, IList parameterNameList, bool caseInsensitive) + { + IEnumerable parametersToMatch = operation.Parameters; + + // bindable functions don't require the first parameter be specified, since its already implied in the path. + if (operation.IsBound) + { + parametersToMatch = parametersToMatch.Skip(1); + } + + List functionParameters = parametersToMatch.ToList(); + + // if any required parameters are missing, don't consider it a match. + if (functionParameters.Where( + p => !(p is IEdmOptionalParameter)).Any( + p => parameterNameList.All( + k => !string.Equals(k, p.Name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)))) + { + return false; + } + + // if any specified parameters don't match, don't consider it a match. + if (parameterNameList.Any( + k => functionParameters.All( + p => !string.Equals(k, p.Name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)))) + { + return false; + } + + return true; + } + + /// + /// Inheritances the type of the level from specified inherited. + /// + /// Type of the structured. + /// Type of the root. + /// Get the inheritance level. + private static int InheritanceLevelFromSpecifiedInheritedType(this IEdmStructuredType structuredType, IEdmStructuredType rootType) + { + Debug.Assert(structuredType.IsOrInheritsFrom(rootType), "Roottype should be a base of the structuredtype."); + + IEdmStructuredType currentType = structuredType; + int inheritanceLevelsFromBase = 0; + while (currentType.InheritsFrom(rootType)) + { + currentType = currentType.BaseType; + inheritanceLevelsFromBase++; + } + + return inheritanceLevelsFromBase; + } +#endif + #endregion + + #region Everyone + /// + /// Gets a reference to a primitive kind definition of the appropriate kind. + /// + /// Primitive type to create a reference for. + /// Flag specifying if the referenced type should be nullable per default. + /// A new primitive type reference. + private static EdmPrimitiveTypeReference ToTypeReference(IEdmPrimitiveType primitiveType, bool nullable) + { + EdmPrimitiveTypeKind kind = primitiveType.PrimitiveKind; + switch (kind) + { + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.PrimitiveType: + return new EdmPrimitiveTypeReference(primitiveType, nullable); + case EdmPrimitiveTypeKind.Binary: + return new EdmBinaryTypeReference(primitiveType, nullable); + case EdmPrimitiveTypeKind.String: + return new EdmStringTypeReference(primitiveType, nullable); + case EdmPrimitiveTypeKind.Decimal: + return new EdmDecimalTypeReference(primitiveType, nullable); + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + return new EdmTemporalTypeReference(primitiveType, nullable); + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + case EdmPrimitiveTypeKind.GeometryCollection: + return new EdmSpatialTypeReference(primitiveType, nullable); + default: + throw new ODataException(ErrorStrings.General_InternalError(InternalErrorCodesCommon.EdmLibraryExtensions_PrimitiveTypeReference)); + } + } + + /// + /// Equality comparer for an IEdmType. + /// + private sealed class EdmTypeEqualityComparer : IEqualityComparer + { + /// + /// Determines whether the specified objects are equal. + /// + /// The first object of type to compare. + /// The second object of type to compare. + /// + /// true if the specified objects are equal; otherwise, false. + /// + public bool Equals(IEdmType x, IEdmType y) + { + return x.IsEquivalentTo(y); + } + + /// + /// Returns a hash code for this instance. + /// + /// The obj. + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public int GetHashCode(IEdmType obj) + { + return obj.GetHashCode(); + } + } + + #endregion + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeReaderResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeReaderResolver.cs new file mode 100644 index 0000000..3a6b6cf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeReaderResolver.cs @@ -0,0 +1,149 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Responsible for resolving the element type of an entity set with reader (i.e., looser) semantics. + /// + internal sealed class EdmTypeReaderResolver : EdmTypeResolver + { + /// The model to use or null if no model is available. + private readonly IEdmModel model; + + /// Reader behavior if the caller is a reader, null if no reader behavior is available. + private readonly Func clientCustomTypeResolver; + + /// Creates a new entity set element type resolver with all the information needed when resolving for reading scenarios. + /// The model to use or null if no model is available. + /// Reader behavior if the caller is a reader, null if no reader behavior is available. + public EdmTypeReaderResolver(IEdmModel model, Func clientCustomTypeResolver) + { + this.model = model; + this.clientCustomTypeResolver = clientCustomTypeResolver; + } + + /// Returns the entity type of the given navigation source. + /// The navigation source to get the element type of. + /// The representing the entity type of the . + internal override IEdmEntityType GetElementType(IEdmNavigationSource navigationSource) + { + IEdmEntityType entityType = navigationSource.EntityType(); + + if (entityType == null) + { + return null; + } + + return (IEdmEntityType)this.ResolveType(entityType); + } + + /// + /// Returns the return type of the given operation import. + /// + /// The operation import to get the return type from. + /// The representing the return type fo the . + internal override IEdmTypeReference GetReturnType(IEdmOperationImport operationImport) + { + if (operationImport != null && operationImport.Operation.ReturnType != null) + { + return this.ResolveTypeReference(operationImport.Operation.ReturnType); + } + + return null; + } + + /// + /// Returns the return type of the given operation import group. + /// + /// The operation import group to get the return type from. + /// The representing the return type fo the . + internal override IEdmTypeReference GetReturnType(IEnumerable functionImportGroup) + { + Debug.Assert(functionImportGroup != null, "functionImportGroup != null"); + + IEdmOperationImport firstFunctionImport = functionImportGroup.FirstOrDefault(); + Debug.Assert(firstFunctionImport != null, "firstFunctionImport != null"); + Debug.Assert( + functionImportGroup.All(f => + { + IEdmTypeReference returnType = this.GetReturnType(f); + IEdmTypeReference actual = this.GetReturnType(firstFunctionImport); + return returnType == null && actual == null || returnType.IsEquivalentTo(actual); + }), + "In a valid model, the return type of operation imports from the same operation import group should be the same."); + return this.GetReturnType(firstFunctionImport); + } + + /// + /// Gets the operation parameter type for read and calls the client type resolver to resolve type when it is specified. + /// + /// The operation parameter to resolve the type for. + /// The representing the type on the operation parameter; or null if no such type could be found. + internal override IEdmTypeReference GetParameterType(IEdmOperationParameter operationParameter) + { + return operationParameter == null ? null : this.ResolveTypeReference(operationParameter.Type); + } + + /// + /// Resolves the given type reference if a client type resolver is available. + /// + /// Type reference to resolve. + /// The resolved type reference. + private IEdmTypeReference ResolveTypeReference(IEdmTypeReference typeReferenceToResolve) + { + Debug.Assert(typeReferenceToResolve != null, "typeReferenceToResolve != null"); + + if (clientCustomTypeResolver == null) + { + return typeReferenceToResolve; + } + + return this.ResolveType(typeReferenceToResolve.Definition).ToTypeReference(typeReferenceToResolve.IsNullable); + } + + /// + /// Resolves the given type if a client type resolver is available. + /// + /// Type to resolve. + /// The resolved type. + private IEdmType ResolveType(IEdmType typeToResolve) + { + Debug.Assert(typeToResolve != null, "typeToResolve != null"); + Debug.Assert(this.model != null, "model != null"); + + if (clientCustomTypeResolver == null) + { + return typeToResolve; + } + + EdmTypeKind typeKind; + + // MetadataUtils.ResolveTypeName() does not allow entity collection types however both operationImport.ReturnType and operationParameter.Type can be of entity collection types. + // We don't want to relax MetadataUtils.ResolveTypeName() since the rest of ODL only allows primitive and complex collection types and is currently relying on the method to + // enforce this. So if typeToResolve is an entity collection type, we will resolve the item type and reconstruct the collection type from the resolved item type. + IEdmCollectionType collectionTypeToResolve = typeToResolve as IEdmCollectionType; + if (collectionTypeToResolve != null && collectionTypeToResolve.ElementType.IsEntity()) + { + IEdmTypeReference itemTypeReferenceToResolve = collectionTypeToResolve.ElementType; + IEdmType resolvedItemType = MetadataUtils.ResolveTypeName(this.model, null /*expectedType*/, itemTypeReferenceToResolve.FullName(), clientCustomTypeResolver, out typeKind); + return new EdmCollectionType(resolvedItemType.ToTypeReference(itemTypeReferenceToResolve.IsNullable)); + } + + return MetadataUtils.ResolveTypeName(this.model, null /*expectedType*/, typeToResolve.FullTypeName(), clientCustomTypeResolver, out typeKind); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeResolver.cs new file mode 100644 index 0000000..943d48b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeResolver.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + + using System.Collections.Generic; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Class responsible for determining the entity type of an entity set. + /// + internal abstract class EdmTypeResolver + { + /// + /// Returns the entity type of the given navigation source. + /// + /// The navigation source to get the entity type of. + /// The representing the entity type of the . + internal abstract IEdmEntityType GetElementType(IEdmNavigationSource navigationSource); + + /// + /// Returns the return type of the given operation import. + /// + /// The operation import to get the return type from. + /// The representing the return type fo the . + internal abstract IEdmTypeReference GetReturnType(IEdmOperationImport operationImport); + + /// + /// Returns the return type of the given operation import group. + /// + /// The operation import group to get the return type from. + /// The representing the return type fo the . + internal abstract IEdmTypeReference GetReturnType(IEnumerable functionImportGroup); + + /// + /// Gets the function parameter type. + /// + /// The function parameter to get the type for. + /// The representing the type on the function parameter; or null if no such type could be found. + internal abstract IEdmTypeReference GetParameterType(IEdmOperationParameter operationParameter); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeWriterResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeWriterResolver.cs new file mode 100644 index 0000000..5cbe4e7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/EdmTypeWriterResolver.cs @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Responsible for resolving the element type of an entity set with writer semantics. + /// + internal sealed class EdmTypeWriterResolver : EdmTypeResolver + { + /// + /// Singleton instance of the resolver. + /// + internal static EdmTypeWriterResolver Instance = new EdmTypeWriterResolver(); + + /// + /// Private constructor to ensure all access goes through the singleton Instance. + /// + private EdmTypeWriterResolver() + { + } + + /// Returns the entity type of the given navigation source. + /// The navigation source to get the entity type of. + /// The representing the entity type of the . + internal override IEdmEntityType GetElementType(IEdmNavigationSource navigationSource) + { + return navigationSource.EntityType(); + } + + /// + /// Returns the return type of the given operation import. + /// + /// The operation import to get the return type from. + /// The representing the return type fo the . + internal override IEdmTypeReference GetReturnType(IEdmOperationImport operationImport) + { + return operationImport == null ? null : operationImport.Operation.ReturnType; + } + + /// + /// Returns the return type of the given operation import group. + /// + /// The operation import group to get the return type from. + /// The representing the return type fo the . + internal override IEdmTypeReference GetReturnType(IEnumerable functionImportGroup) + { + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EdmTypeWriterResolver_GetReturnTypeForOperationImportGroup)); + } + + /// + /// Gets the operation parameter type for write. + /// + /// The operation parameter to resolve the type for. + /// The representing the type on the operation parameter; or null if no such type could be found. + internal override IEdmTypeReference GetParameterType(IEdmOperationParameter operationParameter) + { + return operationParameter == null ? null : operationParameter.Type; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtils.cs new file mode 100644 index 0000000..6ea4d7a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtils.cs @@ -0,0 +1,228 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Metadata +{ + /// + /// Class with utility methods for dealing with OData metadata. + /// + internal static class MetadataUtils + { + /// + /// Gets all the serializable annotations in the OData metadata namespace on the . + /// + /// The containing the annotations."/> + /// The to get the annotations from. + /// All annotations in the OData metadata namespace; or null if no annotations are found. + internal static IEnumerable GetODataAnnotations(this IEdmModel model, IEdmElement annotatable) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(annotatable != null, "annotatable != null"); + + IEnumerable annotations = model.DirectValueAnnotations(annotatable); + if (annotations == null) + { + return null; + } + + return annotations.Where(a => a.NamespaceUri == ODataMetadataConstants.ODataMetadataNamespace); + } + + /// + /// Resolves the name of a primitive, complex, entity or collection type to the respective type. Uses the semantics used by writers. + /// Thus it implements the strict speced behavior. + /// + /// The model to use. + /// The name of the type to resolve. + /// The representing the type specified by the ; + /// or null if no such type could be found. + internal static IEdmType ResolveTypeNameForWrite(IEdmModel model, string typeName) + { + EdmTypeKind typeKind; + + // Writers should use the highest recognized version for type resolution since they need to verify + // that the type being used is allowed in the given version. So pass the max here + // so that we recognize all types and writers can fail later on if the type doesn't fit into the payload. + return ResolveTypeName(model, /*expectedType*/ null, typeName, /*customTypeResolved*/ null, out typeKind); + } + + /// + /// Resolves the name of a primitive, complex, entity or collection type to the respective type. Uses the semantics used be readers. + /// Thus it can be a bit looser. + /// + /// The model to use. + /// The expected type for the type name being resolved, or null if none is available. + /// The name of the type to resolve. + /// The function of client cuetom type resolver. + /// The type kind of the type, if it could be determined. This will be None if we couldn't tell. It might be filled + /// even if the method returns null, for example for Collection types with item types which are not recognized. + /// The representing the type specified by the ; + /// or null if no such type could be found. + internal static IEdmType ResolveTypeNameForRead( + IEdmModel model, + IEdmType expectedType, + string typeName, + Func clientCustomTypeResolver, + out EdmTypeKind typeKind) + { + return ResolveTypeName(model, expectedType, typeName, clientCustomTypeResolver, out typeKind); + } + + /// + /// Resolves the name of a primitive, complex, entity or collection type to the respective type. + /// + /// The model to use. + /// The expected type for the type name being resolved, or null if none is available. + /// The name of the type to resolve. + /// Custom type resolver to use, if null the model is used directly. + /// The type kind of the type, if it could be determined. This will be None if we couldn't tell. It might be filled + /// even if the method returns null, for example for Collection types with item types which are not recognized. + /// The representing the type specified by the ; + /// or null if no such type could be found. + internal static IEdmType ResolveTypeName( + IEdmModel model, + IEdmType expectedType, + string typeName, + Func customTypeResolver, + out EdmTypeKind typeKind) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(typeName != null, "typeName != null"); + IEdmType resolvedType = null; + + // Collection types should only be recognized in V3 and higher. + string itemTypeName = EdmLibraryExtensions.GetCollectionItemTypeName(typeName); + if (itemTypeName == null) + { + // Note: we require the type resolver or the model to also resolve + // primitive types. + if (customTypeResolver != null && model.IsUserModel()) + { + resolvedType = customTypeResolver(expectedType, typeName); + if (resolvedType == null) + { + // If a type resolver is specified it must never return null. + throw new ODataException(Strings.MetadataUtils_ResolveTypeName(typeName)); + } + } + else + { + resolvedType = model.FindType(typeName); + } + + typeKind = resolvedType == null ? EdmTypeKind.None : resolvedType.TypeKind; + } + else + { + // Collection + typeKind = EdmTypeKind.Collection; + EdmTypeKind itemTypeKind; + + IEdmType expectedItemType = null; + if (customTypeResolver != null && expectedType != null && expectedType.TypeKind == EdmTypeKind.Collection) + { + expectedItemType = ((IEdmCollectionType)expectedType).ElementType.Definition; + } + + IEdmType itemType = ResolveTypeName(model, expectedItemType, itemTypeName, customTypeResolver, out itemTypeKind); + if (itemType != null) + { + resolvedType = EdmLibraryExtensions.GetCollectionType(itemType); + } + } + + return resolvedType; + } + + /// + /// Calculates the operations that are bindable to the given type. + /// + /// The binding type in question. + /// The model to search for operations. + /// The edm type resolver to get the parameter type. + /// An enumeration of operations that are always bindable to the given type. + internal static IList CalculateBindableOperationsForType(IEdmType bindingType, IEdmModel model, EdmTypeResolver edmTypeResolver) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(edmTypeResolver != null, "edmTypeResolver != null"); + + IEnumerable operations = null; + try + { + operations = model.FindBoundOperations(bindingType); + } + catch (Exception exc) + { + if (!ExceptionUtils.IsCatchableExceptionType(exc)) + { + throw; + } + + throw new ODataException(Strings.MetadataUtils_CalculateBindableOperationsForType(bindingType.FullTypeName()), exc); + } + + List operationsFound = new List(); + foreach (IEdmOperation operation in operations) + { + if (!operation.IsBound) + { + throw new ODataException(Strings.EdmLibraryExtensions_UnBoundOperationsFoundFromIEdmModelFindMethodIsInvalid(operation.Name)); + } + + if (operation.Parameters.FirstOrDefault() == null) + { + throw new ODataException(Strings.EdmLibraryExtensions_NoParameterBoundOperationsFoundFromIEdmModelFindMethodIsInvalid(operation.Name)); + } + + IEdmOperationParameter bindingParameter = operation.Parameters.FirstOrDefault(); + IEdmType resolvedBindingType = edmTypeResolver.GetParameterType(bindingParameter).Definition; + if (resolvedBindingType.IsAssignableFrom(bindingType)) + { + operationsFound.Add(operation); + } + } + + return operationsFound; + } + + /// + /// Looks up the given term name in the given model, and returns the term's type if a matching term was found. + /// + /// The name of the term to lookup, including the namespace. + /// The model to look in. + /// The type of the term in the model, or null if no matching term was found. + internal static IEdmTypeReference LookupTypeOfTerm(string qualifiedTermName, IEdmModel model) + { + Debug.Assert(model != null, "model != null"); + + IEdmTypeReference typeFromModel = null; + IEdmTerm termFromModel = model.FindTerm(qualifiedTermName); + if (termFromModel != null) + { + typeFromModel = termFromModel.Type; + } + + return typeFromModel; + } + + /// + /// Gets the nullable type reference for a payload type; if the payload type is null, uses Edm.String. + /// + /// The payload type to get the type reference for. + /// The nullable for the . + internal static IEdmTypeReference GetNullablePayloadTypeReference(IEdmType payloadType) + { + return payloadType == null ? null : payloadType.ToTypeReference(true); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtilsCommon.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtilsCommon.cs new file mode 100644 index 0000000..2f5b5ce --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/MetadataUtilsCommon.cs @@ -0,0 +1,579 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.UriParser; + #endregion Namespaces + + /// + /// Class with utility methods for dealing with OData metadata that are shared with the OData.Query project. + /// + internal static class MetadataUtilsCommon + { + /// + /// Checks whether a type reference refers to an OData primitive type (i.e., a primitive, non-stream type). + /// + /// The (non-null) to check. + /// true if the is an OData primitive type reference; otherwise false. + internal static bool IsODataPrimitiveTypeKind(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsODataPrimitiveTypeKind(); + } + + /// + /// Checks whether a type refers to an OData primitive type (i.e., a primitive, non-stream type). + /// + /// The (non-null) to check. + /// true if the is an OData primitive type; otherwise false. + internal static bool IsODataPrimitiveTypeKind(this IEdmType type) + { + ExceptionUtils.CheckArgumentNotNull(type, "type"); + + EdmTypeKind typeKind = type.TypeKind; + if (typeKind != EdmTypeKind.Primitive) + { + return false; + } + + // also make sure it is not a stream + return !type.IsStream(); + } + + /// + /// Checks whether a type reference refers to an OData complex type. + /// + /// The (non-null) to check. + /// true if the is an OData complex type reference; otherwise false. + internal static bool IsODataComplexTypeKind(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsODataComplexTypeKind(); + } + + /// + /// Checks whether a type refers to an OData complex type. + /// + /// The (non-null) to check. + /// true if the is an OData complex type; otherwise false. + internal static bool IsODataComplexTypeKind(this IEdmType type) + { + ExceptionUtils.CheckArgumentNotNull(type, "type"); + + return type.TypeKind == EdmTypeKind.Complex; + } + + /// + /// Checks whether a type reference refers to an OData enumeration type. + /// + /// The (non-null) to check. + /// true if the is an OData enumeration type reference; otherwise false. + internal static bool IsODataEnumTypeKind(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsODataEnumTypeKind(); + } + + /// + /// Checks whether a type refers to an OData Enumeration type + /// + /// The (non-null) to check. + /// true if the is an OData enumeration type; otherwise false. + internal static bool IsODataEnumTypeKind(this IEdmType type) + { + ExceptionUtils.CheckArgumentNotNull(type, "type"); + + EdmTypeKind typeKind = type.TypeKind; + return typeKind == EdmTypeKind.Enum; + } + + /// + /// Checks whether a type reference refers to an OData type definition. + /// + /// The (non-null) to check. + /// true if the is an OData type definition reference; otherwise false. + internal static bool IsODataTypeDefinitionTypeKind(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsODataTypeDefinitionTypeKind(); + } + + /// + /// Checks whether a type refers to an OData type definition. + /// + /// The (non-null) to check. + /// true if the is an OData type definition; otherwise false. + internal static bool IsODataTypeDefinitionTypeKind(this IEdmType type) + { + ExceptionUtils.CheckArgumentNotNull(type, "type"); + + return type.TypeKind == EdmTypeKind.TypeDefinition; + } + + /// + /// Checks whether a type reference refers to an OData entity type. + /// + /// The (non-null) to check. + /// true if the is an OData entity type reference; otherwise false. + internal static bool IsODataEntityTypeKind(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsODataEntityTypeKind(); + } + + /// + /// Checks whether a type refers to an OData entity type. + /// + /// The (non-null) to check. + /// true if the is an OData entity type; otherwise false. + internal static bool IsODataEntityTypeKind(this IEdmType type) + { + ExceptionUtils.CheckArgumentNotNull(type, "type"); + + return type.TypeKind == EdmTypeKind.Entity; + } + + /// + /// Checks whether a type reference is considered a value type in OData. + /// + /// The to check. + /// true if the is considered a value type; otherwise false. + /// + /// The notion of value type in the OData space is driven by the IDSMP requirements where + /// Clr types denote the primitive types. + /// + internal static bool IsODataValueType(this IEdmTypeReference typeReference) + { + IEdmPrimitiveTypeReference primitiveTypeReference = typeReference.AsPrimitiveOrNull(); + if (primitiveTypeReference == null) + { + return false; + } + + switch (primitiveTypeReference.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Decimal: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Duration: + return true; + default: + return false; + } + } + + /// + /// Checks whether a type reference refers to a OData collection value type of non-entity elements. + /// + /// The (non-null) to check. + /// true if the is a non-entity OData collection value type; otherwise false. + internal static bool IsNonEntityCollectionType(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsNonEntityCollectionType(); + } + + /// + /// Checks whether a type reference refers to a OData collection value type of entity elements. + /// + /// The (non-null) to check. + /// true if the is an entity OData collection value type; otherwise false. + internal static bool IsEntityCollectionType(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsEntityCollectionType(); + } + + /// + /// Checks whether a type reference refers to a OData collection value type of structured elements. + /// + /// The (non-null) to check. + /// true if the is an OData collection value type of structured type; otherwise false. + internal static bool IsStructuredCollectionType(this IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + ExceptionUtils.CheckArgumentNotNull(typeReference.Definition, "typeReference.Definition"); + + return typeReference.Definition.IsStructuredCollectionType(); + } + + /// + /// Checks whether a type refers to a OData collection value type of non-entity elements. + /// + /// The (non-null) to check. + /// true if the is a non-entity OData collection value type; otherwise false. + internal static bool IsNonEntityCollectionType(this IEdmType type) + { + Debug.Assert(type != null, "type != null"); + + IEdmCollectionType collectionType = type as IEdmCollectionType; + + // Return false if this is not a collection type, or if it's a collection of entity types (i.e., a navigation property) + if (collectionType == null || (collectionType.ElementType != null && collectionType.ElementType.TypeKind() == EdmTypeKind.Entity)) + { + return false; + } + + Debug.Assert(collectionType.TypeKind == EdmTypeKind.Collection, "Expected collection type kind."); + return true; + } + + /// + /// Checks whether a type refers to a OData collection value type of entity elements. + /// + /// The (non-null) to check. + /// true if the is an entity OData collection value type; otherwise false. + internal static bool IsEntityCollectionType(this IEdmType type) + { + Debug.Assert(type != null, "type != null"); + + IEdmCollectionType collectionType = type as IEdmCollectionType; + + // Return false if this is not a collection type, or if it's not a collection of entity types (i.e., a navigation property) + if (collectionType == null || (collectionType.ElementType != null && collectionType.ElementType.TypeKind() != EdmTypeKind.Entity)) + { + return false; + } + + Debug.Assert(collectionType.TypeKind == EdmTypeKind.Collection, "Expected collection type kind."); + return true; + } + + /// + /// Checks whether a type refers to a OData collection value type of structured elements. + /// + /// The (non-null) to check. + /// true if the is an OData collection value type of structured type; otherwise false. + internal static bool IsStructuredCollectionType(this IEdmType type) + { + Debug.Assert(type != null, "type != null"); + + IEdmCollectionType collectionType = type as IEdmCollectionType; + + if (collectionType == null + || (collectionType.ElementType != null + && (collectionType.ElementType.TypeKind() != EdmTypeKind.Entity && collectionType.ElementType.TypeKind() != EdmTypeKind.Complex))) + { + return false; + } + + Debug.Assert(collectionType.TypeKind == EdmTypeKind.Collection, "Expected collection type kind."); + return true; + } + + /// + /// Returns whether or not the type is an entity or entity collection type. + /// + /// The type to check. + /// Whether or not the type is an entity or entity collection type. + internal static bool IsEntityOrEntityCollectionType(this IEdmType edmType) + { + IEdmEntityType entityType; + return edmType.IsEntityOrEntityCollectionType(out entityType); + } + + /// + /// Returns whether or not the type is an entity or entity collection type. + /// + /// The type to check. + /// The entity type. If the given type was a collection, this will be the element type. + /// Whether or not the type is an entity or entity collection type. + internal static bool IsEntityOrEntityCollectionType(this IEdmType edmType, out IEdmEntityType entityType) + { + Debug.Assert(edmType != null, "edmType != null"); + if (edmType.TypeKind == EdmTypeKind.Entity) + { + entityType = (IEdmEntityType)edmType; + return true; + } + + if (edmType.TypeKind != EdmTypeKind.Collection) + { + entityType = null; + return false; + } + + entityType = ((IEdmCollectionType)edmType).ElementType.Definition as IEdmEntityType; + return entityType != null; + } + + /// + /// Returns whether or not the type is a structured or structured collection type. + /// + /// The type to check. + /// Whether or not the type is a structured or structured collection type. + internal static bool IsStructuredOrStructuredCollectionType(this IEdmType edmType) + { + IEdmStructuredType structuredType; + return edmType.IsStructuredOrStructuredCollectionType(out structuredType); + } + + /// + /// Returns whether or not the type is a structured or structured collection type. + /// + /// The type to check. + /// The structured type. If the given type was a collection, this will be the element type. + /// Whether or not the type is a structured or structured collection type. + internal static bool IsStructuredOrStructuredCollectionType(this IEdmType edmType, out IEdmStructuredType structuredType) + { + Debug.Assert(edmType != null, "edmType != null"); + if (edmType.TypeKind.IsStructured()) + { + structuredType = (IEdmStructuredType)edmType; + return true; + } + + if (edmType.TypeKind != EdmTypeKind.Collection) + { + structuredType = null; + return false; + } + + structuredType = ((IEdmCollectionType)edmType).ElementType.Definition as IEdmStructuredType; + return structuredType != null; + } + + /// + /// Casts an to a or returns null if this is not supported. + /// + /// The type reference to convert. + /// An instance or null if the cannot be converted. + internal static IEdmPrimitiveTypeReference AsPrimitiveOrNull(this IEdmTypeReference typeReference) + { + if (typeReference == null) + { + return null; + } + + return typeReference.TypeKind() == EdmTypeKind.Primitive || typeReference.TypeKind() == EdmTypeKind.TypeDefinition ? typeReference.AsPrimitive() : null; + } + + /// + /// Casts an to a or returns null if this is not supported. + /// + /// The type reference to convert. + /// An instance or null if the cannot be converted. + internal static IEdmEntityTypeReference AsEntityOrNull(this IEdmTypeReference typeReference) + { + if (typeReference == null) + { + return null; + } + + return typeReference.TypeKind() == EdmTypeKind.Entity ? typeReference.AsEntity() : null; + } + + /// + /// Casts an to a or returns null if this is not supported. + /// + /// The type reference to convert. + /// An instance or null if the cannot be converted. + internal static IEdmStructuredTypeReference AsStructuredOrNull(this IEdmTypeReference typeReference) + { + if (typeReference == null) + { + return null; + } + + return typeReference.IsStructured() ? typeReference.AsStructured() : null; + } + + /// + /// Determines if a is convertibale according to OData rules to the + /// . + /// + /// The node which is to be converted. + /// The type which is to be converted. + /// The type to which we want to convert. + /// true if the source type is convertible to the target type; otherwise false. + internal static bool CanConvertPrimitiveTypeTo( + SingleValueNode sourceNodeOrNull, + IEdmPrimitiveType sourcePrimitiveType, + IEdmPrimitiveType targetPrimitiveType) + { + Debug.Assert(sourcePrimitiveType != null, "sourcePrimitiveType != null"); + Debug.Assert(targetPrimitiveType != null, "targetPrimitiveType != null"); + + EdmPrimitiveTypeKind sourcePrimitiveKind = sourcePrimitiveType.PrimitiveKind; + EdmPrimitiveTypeKind targetPrimitiveKind = targetPrimitiveType.PrimitiveKind; + + if (targetPrimitiveKind == EdmPrimitiveTypeKind.PrimitiveType) + { + return true; + } + + switch (sourcePrimitiveKind) + { + case EdmPrimitiveTypeKind.SByte: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Decimal: + return true; + } + + break; + case EdmPrimitiveTypeKind.Byte: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Decimal: + return true; + } + + break; + case EdmPrimitiveTypeKind.Int16: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Decimal: + return true; + } + + break; + case EdmPrimitiveTypeKind.Int32: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Decimal: + return true; + } + + break; + case EdmPrimitiveTypeKind.Int64: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Decimal: + return true; + } + + break; + case EdmPrimitiveTypeKind.Single: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + return true; + + // allow single constant->decimal in order to made L,M,D,F optional + case EdmPrimitiveTypeKind.Decimal: + object tmp; + return TryGetConstantNodePrimitiveLDMF(sourceNodeOrNull, out tmp); + } + + break; + case EdmPrimitiveTypeKind.Double: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Double: + return true; + + // allow double constant->decimal in order to made L,M,D,F optional + // (if the double value actually has overflowed decimal.MaxValue, exception will occur later. more details in ExpressionLexer.cs) + case EdmPrimitiveTypeKind.Decimal: + object tmp; + return TryGetConstantNodePrimitiveLDMF(sourceNodeOrNull, out tmp); + } + + break; + case EdmPrimitiveTypeKind.Date: + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.DateTimeOffset: + return true; + } + + break; + + default: + return sourcePrimitiveKind == targetPrimitiveKind || targetPrimitiveType.IsAssignableFrom(sourcePrimitiveType); + } + + return false; + } + + /// + /// Tries getting the constant node's primitive L M D F value (which can be converted to other primitive type while primitive AccessPropertyNode can't). + /// + /// The Node + /// THe out parameter if succeeds + /// true if the constant node is for long, float, double or decimal type + internal static bool TryGetConstantNodePrimitiveLDMF(SingleValueNode sourceNodeOrNull, out object primitiveValue) + { + primitiveValue = null; + + ConstantNode tmp = sourceNodeOrNull as ConstantNode; + if (tmp != null) + { + IEdmPrimitiveType primitiveType = tmp.TypeReference.AsPrimitiveOrNull().Definition as IEdmPrimitiveType; + if (primitiveType != null) + { + switch (primitiveType.PrimitiveKind) + { + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Decimal: + primitiveValue = tmp.Value; + return true; + default: + return false; + } + } + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataAtomErrorDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataAtomErrorDeserializer.cs new file mode 100644 index 0000000..4ce579b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataAtomErrorDeserializer.cs @@ -0,0 +1,295 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Xml; + #endregion Namespaces + + /// + /// OData ATOM deserializer for error payloads. + /// + internal sealed class ODataAtomErrorDeserializer + { + /// + /// An enumeration of the various kinds of elements in an m:error element. + /// + [Flags] + private enum DuplicateErrorElementPropertyBitMask + { + /// No duplicates. + None = 0, + + /// The 'code' element of the error element. + Code = 1, + + /// The 'message' element of the error element. + Message = 2, + + /// The 'innererror' element of the error element. + InnerError = 4, + } + + /// + /// An enumeration of the various kinds of elements in an internal error element. + /// + [Flags] + private enum DuplicateInnerErrorElementPropertyBitMask + { + /// No duplicates. + None = 0, + + /// The 'message' element of the inner error element. + Message = 1, + + /// The 'type' element of the inner error element. + TypeName = 2, + + /// The 'stacktrace' element of the inner error element. + StackTrace = 4, + + /// The 'internalexception' element of the inner error element. + InternalException = 8, + } + + /// + /// Reads the content of an error element. + /// + /// The Xml reader to read the error payload from. + /// The maximumum number of recursive internalexception elements to allow. + /// The representing the error. + /// + /// This method is used to read top-level errors as well as in-stream errors (from inside the buffering Xml reader). + /// Pre-Condition: XmlNodeType.Element - The m:error start element. + /// Post-Condition: XmlNodeType.EndElement - The m:error end-element. + /// XmlNodeType.Element - The empty m:error start element. + /// + internal static ODataError ReadErrorElement(BufferingXmlReader xmlReader, int maxInnerErrorDepth) + { + Debug.Assert(xmlReader != null, "this.XmlReader != null"); + Debug.Assert(xmlReader.NodeType == XmlNodeType.Element, "xmlReader.NodeType == XmlNodeType.Element"); + Debug.Assert(xmlReader.LocalName == ODataMetadataConstants.ODataErrorElementName, "Expected reader to be positioned on element."); + Debug.Assert(xmlReader.NamespaceEquals(xmlReader.ODataMetadataNamespace), "this.XmlReader.NamespaceEquals(atomizedMetadataNamespace)"); + + ODataError error = new ODataError(); + DuplicateErrorElementPropertyBitMask elementsReadBitmask = DuplicateErrorElementPropertyBitMask.None; + + if (!xmlReader.IsEmptyElement) + { + // Move to the first child node of the element. + xmlReader.Read(); + + do + { + switch (xmlReader.NodeType) + { + case XmlNodeType.EndElement: + // end of the element + continue; + + case XmlNodeType.Element: + if (xmlReader.NamespaceEquals(xmlReader.ODataMetadataNamespace)) + { + switch (xmlReader.LocalName) + { + // + case ODataMetadataConstants.ODataErrorCodeElementName: + VerifyErrorElementNotFound( + ref elementsReadBitmask, + DuplicateErrorElementPropertyBitMask.Code, + ODataMetadataConstants.ODataErrorCodeElementName); + error.ErrorCode = xmlReader.ReadElementValue(); + continue; + + // + case ODataMetadataConstants.ODataErrorMessageElementName: + VerifyErrorElementNotFound( + ref elementsReadBitmask, + DuplicateErrorElementPropertyBitMask.Message, + ODataMetadataConstants.ODataErrorMessageElementName); + error.Message = xmlReader.ReadElementValue(); + continue; + + // + case ODataMetadataConstants.ODataInnerErrorElementName: + VerifyErrorElementNotFound( + ref elementsReadBitmask, + DuplicateErrorElementPropertyBitMask.InnerError, + ODataMetadataConstants.ODataInnerErrorElementName); + error.InnerError = ReadInnerErrorElement(xmlReader, 0 /* recursionDepth */, maxInnerErrorDepth); + continue; + + default: + break; + } + } + + break; + default: + break; + } + + xmlReader.Skip(); + } + while (xmlReader.NodeType != XmlNodeType.EndElement); + } + + return error; + } + + /// + /// Verifies that the specified element was not yet found in a top-level error element. + /// + /// + /// The bit field which stores which elements of an error were found so far. + /// + /// The bit mask for the element to check. + /// The name of the element to check (used for error reporting). + private static void VerifyErrorElementNotFound( + ref DuplicateErrorElementPropertyBitMask elementsFoundBitField, + DuplicateErrorElementPropertyBitMask elementFoundBitMask, + string elementName) + { + Debug.Assert(((int)elementFoundBitMask & (((int)elementFoundBitMask) - 1)) == 0, "elementFoundBitMask is not a power of 2."); + Debug.Assert(!string.IsNullOrEmpty(elementName), "!string.IsNullOrEmpty(elementName)"); + + if ((elementsFoundBitField & elementFoundBitMask) == elementFoundBitMask) + { + throw new ODataException(Strings.ODataAtomErrorDeserializer_MultipleErrorElementsWithSameName(elementName)); + } + + elementsFoundBitField |= elementFoundBitMask; + } + + /// + /// Verifies that the specified element was not yet found in an inner error element. + /// + /// + /// The bit field which stores which elements of an inner error were found so far. + /// + /// The bit mask for the element to check. + /// The name of the element to check (used for error reporting). + private static void VerifyInnerErrorElementNotFound( + ref DuplicateInnerErrorElementPropertyBitMask elementsFoundBitField, + DuplicateInnerErrorElementPropertyBitMask elementFoundBitMask, + string elementName) + { + Debug.Assert(((int)elementFoundBitMask & (((int)elementFoundBitMask) - 1)) == 0, "elementFoundBitMask is not a power of 2."); + Debug.Assert(!string.IsNullOrEmpty(elementName), "!string.IsNullOrEmpty(elementName)"); + + if ((elementsFoundBitField & elementFoundBitMask) == elementFoundBitMask) + { + throw new ODataException(Strings.ODataAtomErrorDeserializer_MultipleInnerErrorElementsWithSameName(elementName)); + } + + elementsFoundBitField |= elementFoundBitMask; + } + + /// + /// Reads the content of an inner error element. + /// + /// The (buffering) Xml reader to read the error payload from. + /// The number of times this function has been called recursively. + /// The maximumum number of recursive internalexception elements to allow. + /// The representing the inner error. + /// + /// Pre-Condition: XmlNodeType.Element - the m:innererror or m:internalexception element + /// Post-Condition: Any - the node after the m:innererror/m:internalexception end element or the node after the empty m:innererror/m:internalexception element node. + /// + private static ODataInnerError ReadInnerErrorElement(BufferingXmlReader xmlReader, int recursionDepth, int maxInnerErrorDepth) + { + Debug.Assert(xmlReader != null, "this.XmlReader != null"); + Debug.Assert(xmlReader.NodeType == XmlNodeType.Element, "xmlReader.NodeType == XmlNodeType.Element"); + Debug.Assert( + xmlReader.LocalName == ODataMetadataConstants.ODataInnerErrorElementName || + xmlReader.LocalName == ODataMetadataConstants.ODataInnerErrorInnerErrorElementName, + "Expected reader to be positioned on 'm:innererror' or 'm:internalexception' element."); + Debug.Assert(xmlReader.NamespaceEquals(xmlReader.ODataMetadataNamespace), "this.XmlReader.NamespaceEquals(this.ODataMetadataNamespace)"); + + ValidationUtils.IncreaseAndValidateRecursionDepth(ref recursionDepth, maxInnerErrorDepth); + + ODataInnerError innerError = new ODataInnerError(); + DuplicateInnerErrorElementPropertyBitMask elementsReadBitmask = DuplicateInnerErrorElementPropertyBitMask.None; + + if (!xmlReader.IsEmptyElement) + { + // Move to the first child node of the element. + xmlReader.Read(); + + do + { + switch (xmlReader.NodeType) + { + case XmlNodeType.EndElement: + // end of the or element + continue; + + case XmlNodeType.Element: + if (xmlReader.NamespaceEquals(xmlReader.ODataMetadataNamespace)) + { + switch (xmlReader.LocalName) + { + // + case ODataMetadataConstants.ODataInnerErrorMessageElementName: + VerifyInnerErrorElementNotFound( + ref elementsReadBitmask, + DuplicateInnerErrorElementPropertyBitMask.Message, + ODataMetadataConstants.ODataInnerErrorMessageElementName); + innerError.Message = xmlReader.ReadElementValue(); + continue; + + // + case ODataMetadataConstants.ODataInnerErrorTypeElementName: + VerifyInnerErrorElementNotFound( + ref elementsReadBitmask, + DuplicateInnerErrorElementPropertyBitMask.TypeName, + ODataMetadataConstants.ODataInnerErrorTypeElementName); + innerError.TypeName = xmlReader.ReadElementValue(); + continue; + + // + case ODataMetadataConstants.ODataInnerErrorStackTraceElementName: + VerifyInnerErrorElementNotFound( + ref elementsReadBitmask, + DuplicateInnerErrorElementPropertyBitMask.StackTrace, + ODataMetadataConstants.ODataInnerErrorStackTraceElementName); + innerError.StackTrace = xmlReader.ReadElementValue(); + continue; + + // + case ODataMetadataConstants.ODataInnerErrorInnerErrorElementName: + VerifyInnerErrorElementNotFound( + ref elementsReadBitmask, + DuplicateInnerErrorElementPropertyBitMask.InternalException, + ODataMetadataConstants.ODataInnerErrorInnerErrorElementName); + innerError.InnerError = ReadInnerErrorElement(xmlReader, recursionDepth, maxInnerErrorDepth); + continue; + + default: + break; + } + } + + break; + default: + break; + } + + xmlReader.Skip(); + } + while (xmlReader.NodeType != XmlNodeType.EndElement); + } + + // Read over the end element, or empty start element. + xmlReader.Read(); + + return innerError; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataConstants.cs new file mode 100644 index 0000000..affb9c1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataConstants.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + /// + /// Constant values related to the Metadata format. + /// + internal static class ODataMetadataConstants + { + #region Xml constants ----------------------------------------------------------------------------------------- + /// Attribute use to add xml: namespaces specific attributes. + public const string XmlNamespace = "http://www.w3.org/XML/1998/namespace"; + + /// XML attribute value to indicate the base URI for a document or element. + public const string XmlBaseAttributeName = "base"; + #endregion Xml constants + + #region OData constants --------------------------------------------------------------------------------------- + + /// XML namespace for data service annotations. + public const string ODataMetadataNamespace = "http://docs.oasis-open.org/odata/ns/metadata"; + + /// XML namespace prefix for data service annotations. + public const string ODataMetadataNamespacePrefix = "m"; + + /// XML namespace for data services. + public const string ODataNamespace = "http://docs.oasis-open.org/odata/ns/data"; + + /// OData element name for the 'value' element + public const string ODataValueElementName = "value"; + + /// Name of the error element for Xml error responses. + public const string ODataErrorElementName = "error"; + + /// Name of the error code element for Xml error responses. + public const string ODataErrorCodeElementName = "code"; + + /// Name of the error message element for Xml error responses. + public const string ODataErrorMessageElementName = "message"; + + /// Name of the inner error message element for Xml error responses. + public const string ODataInnerErrorElementName = "innererror"; + + /// Name of the message element in inner errors for Xml error responses. + public const string ODataInnerErrorMessageElementName = "message"; + + /// Name of the type element in inner errors for Xml error responses. + public const string ODataInnerErrorTypeElementName = "type"; + + /// Name of the stack trace element in inner errors for Xml error responses. + public const string ODataInnerErrorStackTraceElementName = "stacktrace"; + + /// Name of the inner error element nested in inner errors for Xml error responses. + public const string ODataInnerErrorInnerErrorElementName = "internalexception"; + #endregion OData constants + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataReaderUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataReaderUtils.cs new file mode 100644 index 0000000..5e54702 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataReaderUtils.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System.Diagnostics; + using System.IO; + using System.Text; + using System.Xml; + #endregion Namespaces + + /// + /// Helper methods used by the OData reader for the Metadata format. + /// + internal static class ODataMetadataReaderUtils + { + /// + /// Creates an Xml reader over the specified stream with the provided settings. + /// + /// The stream to create the XmlReader over. + /// The encoding to use to read the input. + /// The OData message reader settings used to control the settings of the Xml reader. + /// An instance configured with the provided settings. + internal static XmlReader CreateXmlReader(Stream stream, Encoding encoding, ODataMessageReaderSettings messageReaderSettings) + { + Debug.Assert(stream != null, "stream != null"); + Debug.Assert(messageReaderSettings != null, "messageReaderSettings != null"); + + XmlReaderSettings xmlReaderSettings = CreateXmlReaderSettings(messageReaderSettings); + + if (encoding != null) + { + // Use the encoding from the content type if specified. + // NOTE: The XmlReader will scan ahead and determine the encoding from the Xml declaration + // and or the payload. Only if no encoding is specified in the Xml declaration and + // the Xml reader cannot figure out the encoding from the payload, can it happen + // that we need to specify the encoding explicitly (and that wrapping the stream with + // a stream reader makes a difference in the first place). + return XmlReader.Create(new StreamReader(stream, encoding), xmlReaderSettings); + } + + return XmlReader.Create(stream, xmlReaderSettings); + } + + /// + /// Creates a new XmlReaderSettings instance using the encoding. + /// + /// Configuration settings of the OData reader. + /// The Xml reader settings to use for this reader. + private static XmlReaderSettings CreateXmlReaderSettings(ODataMessageReaderSettings messageReaderSettings) + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.CheckCharacters = messageReaderSettings.EnableCharactersCheck; + settings.ConformanceLevel = ConformanceLevel.Document; + settings.CloseInput = true; + + // We do not allow DTDs - this is the default +#if ORCAS + settings.ProhibitDtd = true; +#else + settings.DtdProcessing = DtdProcessing.Prohibit; +#endif + + return settings; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataWriterUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataWriterUtils.cs new file mode 100644 index 0000000..e788c35 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/ODataMetadataWriterUtils.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System.Diagnostics; + using System.IO; + using System.Text; + using System.Xml; + #endregion Namespaces + + /// + /// Helper methods used by the OData writer for the Metadata format. + /// + internal static class ODataMetadataWriterUtils + { + /// + /// Creates an Xml writer over the specified stream, with the provided settings and encoding. + /// + /// The stream to create the XmlWriter over. + /// The OData message writer settings used to control the settings of the Xml writer. + /// The encoding used for writing. + /// An instance configured with the provided settings and encoding. + internal static XmlWriter CreateXmlWriter(Stream stream, ODataMessageWriterSettings messageWriterSettings, Encoding encoding) + { + Debug.Assert(stream != null, "stream != null"); + Debug.Assert(messageWriterSettings != null, "messageWriterSettings != null"); + + XmlWriterSettings xmlWriterSettings = CreateXmlWriterSettings(messageWriterSettings, encoding); + + XmlWriter writer = XmlWriter.Create(stream, xmlWriterSettings); + + return writer; + } + + /// + /// Write an error message. + /// + /// The Xml writer to write to. + /// The error instance to write. + /// A flag indicating whether error details should be written (in debug mode only) or not. + /// The maximumum number of nested inner errors to allow. + internal static void WriteError(XmlWriter writer, ODataError error, bool includeDebugInformation, int maxInnerErrorDepth) + { + ErrorUtils.WriteXmlError(writer, error, includeDebugInformation, maxInnerErrorDepth); + } + + /// + /// Creates a new XmlWriterSettings instance using the encoding. + /// + /// Configuration settings of the OData writer. + /// Encoding to use in the writer settings. + /// The Xml writer settings to use for this writer. + private static XmlWriterSettings CreateXmlWriterSettings(ODataMessageWriterSettings messageWriterSettings, Encoding encoding) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.CheckCharacters = messageWriterSettings.EnableCharactersCheck; + settings.ConformanceLevel = ConformanceLevel.Document; + settings.OmitXmlDeclaration = false; + settings.Encoding = encoding ?? MediaTypeUtils.EncodingUtf8NoPreamble; + settings.NewLineHandling = NewLineHandling.Entitize; + + // we do not want to close the underlying stream when the OData writer is closed since we don't own the stream. + settings.CloseOutput = false; + + return settings; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/XmlReaderExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/XmlReaderExtensions.cs new file mode 100644 index 0000000..b5d7759 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Metadata/XmlReaderExtensions.cs @@ -0,0 +1,188 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Metadata +{ + #region Namespaces + using System.Diagnostics; + using System.Text; + using System.Xml; + #endregion Namespaces + + /// + /// Extension methods for the XML reader. + /// + internal static class XmlReaderExtensions + { + /// + /// Reads the value of the element as a string. + /// + /// The reader to read from. + /// The string value of the element. + /// + /// Pre-Condition: XmlNodeType.Element - the element to read the value for. + /// XmlNodeType.Attribute - an attribute on the element to read the value for. + /// Post-Condition: Any - the node after the element. + /// + /// This method is similar to ReadElementContentAsString with one difference: + /// - It ignores Whitespace nodes - this is needed for compatiblity, WCF DS ignores insignificant whitespaces + /// it does that by setting the IgnoreWhitespace option on reader settings, ODataLib can't do that + /// cause it doesn't always control the creation of the XmlReader, so it has to explicitely ignore + /// insignificant whitespaces. + /// + internal static string ReadElementValue(this XmlReader reader) + { + string result = reader.ReadElementContentValue(); + reader.Read(); + return result; + } + + /// + /// Reads the value of the element as a string. + /// + /// The reader to read from. + /// The string value of the element. + /// + /// Pre-Condition: XmlNodeType.Element - the element to read the value for. + /// XmlNodeType.Attribute - an attribute on the element to read the value for. + /// Post-Condition: XmlNodeType.Element - the element was empty. + /// XmlNodeType.EndElement - the element had some value. + /// + /// This method is similar to ReadElementContentAsString with two differences: + /// - It ignores Whitespace nodes - this is needed for compatiblity, WCF DS ignores insignificant whitespaces + /// it does that by setting the IgnoreWhitespace option on reader settings, ODataLib can't do that + /// cause it doesn't always control the creation of the XmlReader, so it has to explicitely ignore + /// insignificant whitespaces. + /// - It leaves the reader positioned on the EndElement node (or the start node if it was empty). + /// + internal static string ReadElementContentValue(this XmlReader reader) + { + Debug.Assert(reader != null, "reader != null"); + Debug.Assert( + reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.Attribute, + "Pre-Condition: XML reader must be on Element or Attribute node."); + + reader.MoveToElement(); + + string result = null; + if (reader.IsEmptyElement) + { + result = string.Empty; + } + else + { + StringBuilder builder = null; + bool endElementFound = false; + while (!endElementFound && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.EndElement: + endElementFound = true; + break; + case XmlNodeType.CDATA: + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + if (result == null) + { + result = reader.Value; + } + else if (builder == null) + { + builder = new StringBuilder(); + builder.Append(result); + builder.Append(reader.Value); + } + else + { + builder.Append(reader.Value); + } + + break; + + // Ignore comments, whitespaces and processing instructions. + case XmlNodeType.Comment: + case XmlNodeType.ProcessingInstruction: + case XmlNodeType.Whitespace: + break; + + default: + throw new ODataException(Strings.XmlReaderExtension_InvalidNodeInStringValue(reader.NodeType)); + } + } + + if (builder != null) + { + result = builder.ToString(); + } + else if (result == null) + { + result = string.Empty; + } + } + + Debug.Assert( + reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.EndElement, + "Post-Condition: XML reader must be on Element or EndElement node."); + Debug.Assert(result != null, "The method should never return null since it doesn't handle null values."); + return result; + } + + /// + /// Determines if the current node's namespace equals to the specified + /// + /// The XML reader to get the current node from. + /// The namespace URI to compare, this must be a string already atomized in the name table. + /// true if the current node is in the specified namespace; false otherwise. + internal static bool NamespaceEquals(this XmlReader reader, string namespaceUri) + { + Debug.Assert(reader != null, "reader != null"); + Debug.Assert( + reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.EndElement || reader.NodeType == XmlNodeType.Attribute, + "The namespace of the node should only be tested on Element or Attribute nodes."); + Debug.Assert(object.ReferenceEquals(reader.NameTable.Get(namespaceUri), namespaceUri), "The namespaceUri was not atomized on this reader."); + + return object.ReferenceEquals(reader.NamespaceURI, namespaceUri); + } + + /// + /// Determines if the current node's local name equals to the specified + /// + /// The XML reader to get the current node from. + /// The local name to compare, this must be a string already atomized in the name table. + /// true if the current node has the specified local name; false otherwise. + internal static bool LocalNameEquals(this XmlReader reader, string localName) + { + Debug.Assert(reader != null, "reader != null"); + Debug.Assert( + reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.EndElement || reader.NodeType == XmlNodeType.Attribute, + "The namespace of the node should only be tested on Element or Attribute nodes."); + Debug.Assert(object.ReferenceEquals(reader.NameTable.Get(localName), localName), "The localName was not atomized on this reader."); + + return object.ReferenceEquals(reader.LocalName, localName); + } + + /// + /// Reads to the next element encountered in an Xml payload. + /// + /// The to read from. + /// true if the method reached the next element; otherwise false. + internal static bool TryReadToNextElement(this XmlReader reader) + { + Debug.Assert(reader != null, "reader != null"); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.NetStandard.VS2017.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.NetStandard.VS2017.csproj new file mode 100644 index 0000000..eb966da --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.NetStandard.VS2017.csproj @@ -0,0 +1,109 @@ + + + + Microsoft.OData.Core + Microsoft.OData.Core + netstandard1.1 + 1.6.0 + + false + ..\..\..\tools\StrongNamePublicKeys\35MSSharedLib1024.snk + true + true + false + True + True + $(AssemblyName).xml + $(DefineConstants);ODATA_CORE;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE;SUPPRESS_SECURITY_RULES + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft.OData.Core + true + true + internal + true + Microsoft.OData.Core.TextRes + + + + + + false + + + false + + + + + + TextTemplatingFileGenerator + Microsoft.OData.Core.cs + + + True + True + Microsoft.OData.Core.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Core.cs + + + True + True + Parameterized.Microsoft.OData.Core.tt + true + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.cs new file mode 100644 index 0000000..1a98260 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -0,0 +1,944 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData { + using System; + using System.Globalization; + using System.Reflection; + using System.Resources; +#if !PORTABLELIB + using System.Security.Permissions; +#endif + using System.Text; + using System.Threading; + + using System.ComponentModel; +#if !PORTABLELIB + [AttributeUsage(AttributeTargets.All)] + internal sealed class TextResDescriptionAttribute : DescriptionAttribute { + + private bool replaced = false; + + /// + /// Constructs a new sys description. + /// + /// + /// description text. + /// + public TextResDescriptionAttribute(string description) : base(description) { + } + + /// + /// Retrieves the description text. + /// + /// + /// description + /// + public override string Description { + get { + if (!replaced) { + replaced = true; + DescriptionValue = TextRes.GetString(base.Description); + } + return base.Description; + } + } + } + + [AttributeUsage(AttributeTargets.All)] + internal sealed class TextResCategoryAttribute : CategoryAttribute { + + public TextResCategoryAttribute(string category) : base(category) { + } + + protected override string GetLocalizedString(string value) { + return TextRes.GetString(value); + } + } +#endif + + /// + /// AutoGenerated resource class. Usage: + /// + /// string s = TextRes.GetString(TextRes.MyIdenfitier); + /// + internal sealed class TextRes { + internal const string ExceptionUtils_ArgumentStringEmpty = "ExceptionUtils_ArgumentStringEmpty"; + internal const string ODataRequestMessage_AsyncNotAvailable = "ODataRequestMessage_AsyncNotAvailable"; + internal const string ODataRequestMessage_StreamTaskIsNull = "ODataRequestMessage_StreamTaskIsNull"; + internal const string ODataRequestMessage_MessageStreamIsNull = "ODataRequestMessage_MessageStreamIsNull"; + internal const string ODataResponseMessage_AsyncNotAvailable = "ODataResponseMessage_AsyncNotAvailable"; + internal const string ODataResponseMessage_StreamTaskIsNull = "ODataResponseMessage_StreamTaskIsNull"; + internal const string ODataResponseMessage_MessageStreamIsNull = "ODataResponseMessage_MessageStreamIsNull"; + internal const string AsyncBufferedStream_WriterDisposedWithoutFlush = "AsyncBufferedStream_WriterDisposedWithoutFlush"; + internal const string ODataFormat_AtomFormatObsoleted = "ODataFormat_AtomFormatObsoleted"; + internal const string ODataOutputContext_UnsupportedPayloadKindForFormat = "ODataOutputContext_UnsupportedPayloadKindForFormat"; + internal const string ODataInputContext_UnsupportedPayloadKindForFormat = "ODataInputContext_UnsupportedPayloadKindForFormat"; + internal const string ODataOutputContext_MetadataDocumentUriMissing = "ODataOutputContext_MetadataDocumentUriMissing"; + internal const string ODataJsonLightSerializer_RelativeUriUsedWithoutMetadataDocumentUriOrMetadata = "ODataJsonLightSerializer_RelativeUriUsedWithoutMetadataDocumentUriOrMetadata"; + internal const string ODataWriter_RelativeUriUsedWithoutBaseUriSpecified = "ODataWriter_RelativeUriUsedWithoutBaseUriSpecified"; + internal const string ODataWriter_StreamPropertiesMustBePropertiesOfODataResource = "ODataWriter_StreamPropertiesMustBePropertiesOfODataResource"; + internal const string ODataWriterCore_InvalidStateTransition = "ODataWriterCore_InvalidStateTransition"; + internal const string ODataWriterCore_InvalidTransitionFromStart = "ODataWriterCore_InvalidTransitionFromStart"; + internal const string ODataWriterCore_InvalidTransitionFromResource = "ODataWriterCore_InvalidTransitionFromResource"; + internal const string ODataWriterCore_InvalidTransitionFrom40DeletedResource = "ODataWriterCore_InvalidTransitionFrom40DeletedResource"; + internal const string ODataWriterCore_InvalidTransitionFromNullResource = "ODataWriterCore_InvalidTransitionFromNullResource"; + internal const string ODataWriterCore_InvalidTransitionFromResourceSet = "ODataWriterCore_InvalidTransitionFromResourceSet"; + internal const string ODataWriterCore_InvalidTransitionFromExpandedLink = "ODataWriterCore_InvalidTransitionFromExpandedLink"; + internal const string ODataWriterCore_InvalidTransitionFromCompleted = "ODataWriterCore_InvalidTransitionFromCompleted"; + internal const string ODataWriterCore_InvalidTransitionFromError = "ODataWriterCore_InvalidTransitionFromError"; + internal const string ODataJsonLightDeltaWriter_InvalidTransitionFromNestedResource = "ODataJsonLightDeltaWriter_InvalidTransitionFromNestedResource"; + internal const string ODataJsonLightDeltaWriter_InvalidTransitionToNestedResource = "ODataJsonLightDeltaWriter_InvalidTransitionToNestedResource"; + internal const string ODataJsonLightDeltaWriter_WriteStartExpandedResourceSetCalledInInvalidState = "ODataJsonLightDeltaWriter_WriteStartExpandedResourceSetCalledInInvalidState"; + internal const string ODataWriterCore_WriteEndCalledInInvalidState = "ODataWriterCore_WriteEndCalledInInvalidState"; + internal const string ODataWriterCore_StreamNotDisposed = "ODataWriterCore_StreamNotDisposed"; + internal const string ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties = "ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties"; + internal const string ODataWriterCore_QueryCountInRequest = "ODataWriterCore_QueryCountInRequest"; + internal const string ODataWriterCore_QueryNextLinkInRequest = "ODataWriterCore_QueryNextLinkInRequest"; + internal const string ODataWriterCore_QueryDeltaLinkInRequest = "ODataWriterCore_QueryDeltaLinkInRequest"; + internal const string ODataWriterCore_CannotWriteDeltaWithResourceSetWriter = "ODataWriterCore_CannotWriteDeltaWithResourceSetWriter"; + internal const string ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry = "ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry"; + internal const string ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter = "ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter"; + internal const string ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter = "ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter"; + internal const string ODataWriterCore_SyncCallOnAsyncWriter = "ODataWriterCore_SyncCallOnAsyncWriter"; + internal const string ODataWriterCore_AsyncCallOnSyncWriter = "ODataWriterCore_AsyncCallOnSyncWriter"; + internal const string ODataWriterCore_EntityReferenceLinkWithoutNavigationLink = "ODataWriterCore_EntityReferenceLinkWithoutNavigationLink"; + internal const string ODataWriterCore_DeferredLinkInRequest = "ODataWriterCore_DeferredLinkInRequest"; + internal const string ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent = "ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent"; + internal const string ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet = "ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet"; + internal const string ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement = "ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement"; + internal const string DuplicatePropertyNamesNotAllowed = "DuplicatePropertyNamesNotAllowed"; + internal const string DuplicateAnnotationNotAllowed = "DuplicateAnnotationNotAllowed"; + internal const string DuplicateAnnotationForPropertyNotAllowed = "DuplicateAnnotationForPropertyNotAllowed"; + internal const string DuplicateAnnotationForInstanceAnnotationNotAllowed = "DuplicateAnnotationForInstanceAnnotationNotAllowed"; + internal const string PropertyAnnotationAfterTheProperty = "PropertyAnnotationAfterTheProperty"; + internal const string AtomValueUtils_CannotConvertValueToAtomPrimitive = "AtomValueUtils_CannotConvertValueToAtomPrimitive"; + internal const string ODataJsonWriter_UnsupportedValueType = "ODataJsonWriter_UnsupportedValueType"; + internal const string ODataException_GeneralError = "ODataException_GeneralError"; + internal const string ODataErrorException_GeneralError = "ODataErrorException_GeneralError"; + internal const string ODataUriParserException_GeneralError = "ODataUriParserException_GeneralError"; + internal const string ODataMessageWriter_WriterAlreadyUsed = "ODataMessageWriter_WriterAlreadyUsed"; + internal const string ODataMessageWriter_EntityReferenceLinksInRequestNotAllowed = "ODataMessageWriter_EntityReferenceLinksInRequestNotAllowed"; + internal const string ODataMessageWriter_ErrorPayloadInRequest = "ODataMessageWriter_ErrorPayloadInRequest"; + internal const string ODataMessageWriter_ServiceDocumentInRequest = "ODataMessageWriter_ServiceDocumentInRequest"; + internal const string ODataMessageWriter_MetadataDocumentInRequest = "ODataMessageWriter_MetadataDocumentInRequest"; + internal const string ODataMessageWriter_DeltaInRequest = "ODataMessageWriter_DeltaInRequest"; + internal const string ODataMessageWriter_AsyncInRequest = "ODataMessageWriter_AsyncInRequest"; + internal const string ODataMessageWriter_CannotWriteTopLevelNull = "ODataMessageWriter_CannotWriteTopLevelNull"; + internal const string ODataMessageWriter_CannotWriteNullInRawFormat = "ODataMessageWriter_CannotWriteNullInRawFormat"; + internal const string ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind = "ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind"; + internal const string ODataMessageWriter_IncompatiblePayloadKinds = "ODataMessageWriter_IncompatiblePayloadKinds"; + internal const string ODataMessageWriter_CannotWriteStreamPropertyAsTopLevelProperty = "ODataMessageWriter_CannotWriteStreamPropertyAsTopLevelProperty"; + internal const string ODataMessageWriter_WriteErrorAlreadyCalled = "ODataMessageWriter_WriteErrorAlreadyCalled"; + internal const string ODataMessageWriter_CannotWriteInStreamErrorForRawValues = "ODataMessageWriter_CannotWriteInStreamErrorForRawValues"; + internal const string ODataMessageWriter_CannotWriteMetadataWithoutModel = "ODataMessageWriter_CannotWriteMetadataWithoutModel"; + internal const string ODataMessageWriter_CannotSpecifyOperationWithoutModel = "ODataMessageWriter_CannotSpecifyOperationWithoutModel"; + internal const string ODataMessageWriter_JsonPaddingOnInvalidContentType = "ODataMessageWriter_JsonPaddingOnInvalidContentType"; + internal const string ODataMessageWriter_NonCollectionType = "ODataMessageWriter_NonCollectionType"; + internal const string ODataMessageWriter_NotAllowedWriteTopLevelPropertyWithResourceValue = "ODataMessageWriter_NotAllowedWriteTopLevelPropertyWithResourceValue"; + internal const string ODataMessageWriterSettings_MessageWriterSettingsXmlCustomizationCallbacksMustBeSpecifiedBoth = "ODataMessageWriterSettings_MessageWriterSettingsXmlCustomizationCallbacksMustBeSpecifiedBoth"; + internal const string ODataCollectionWriterCore_InvalidTransitionFromStart = "ODataCollectionWriterCore_InvalidTransitionFromStart"; + internal const string ODataCollectionWriterCore_InvalidTransitionFromCollection = "ODataCollectionWriterCore_InvalidTransitionFromCollection"; + internal const string ODataCollectionWriterCore_InvalidTransitionFromItem = "ODataCollectionWriterCore_InvalidTransitionFromItem"; + internal const string ODataCollectionWriterCore_WriteEndCalledInInvalidState = "ODataCollectionWriterCore_WriteEndCalledInInvalidState"; + internal const string ODataCollectionWriterCore_SyncCallOnAsyncWriter = "ODataCollectionWriterCore_SyncCallOnAsyncWriter"; + internal const string ODataCollectionWriterCore_AsyncCallOnSyncWriter = "ODataCollectionWriterCore_AsyncCallOnSyncWriter"; + internal const string ODataBatch_InvalidHttpMethodForChangeSetRequest = "ODataBatch_InvalidHttpMethodForChangeSetRequest"; + internal const string ODataBatchOperationHeaderDictionary_KeyNotFound = "ODataBatchOperationHeaderDictionary_KeyNotFound"; + internal const string ODataBatchOperationHeaderDictionary_DuplicateCaseInsensitiveKeys = "ODataBatchOperationHeaderDictionary_DuplicateCaseInsensitiveKeys"; + internal const string ODataParameterWriter_InStreamErrorNotSupported = "ODataParameterWriter_InStreamErrorNotSupported"; + internal const string ODataParameterWriter_CannotCreateParameterWriterOnResponseMessage = "ODataParameterWriter_CannotCreateParameterWriterOnResponseMessage"; + internal const string ODataParameterWriterCore_SyncCallOnAsyncWriter = "ODataParameterWriterCore_SyncCallOnAsyncWriter"; + internal const string ODataParameterWriterCore_AsyncCallOnSyncWriter = "ODataParameterWriterCore_AsyncCallOnSyncWriter"; + internal const string ODataParameterWriterCore_CannotWriteStart = "ODataParameterWriterCore_CannotWriteStart"; + internal const string ODataParameterWriterCore_CannotWriteParameter = "ODataParameterWriterCore_CannotWriteParameter"; + internal const string ODataParameterWriterCore_CannotWriteEnd = "ODataParameterWriterCore_CannotWriteEnd"; + internal const string ODataParameterWriterCore_CannotWriteInErrorOrCompletedState = "ODataParameterWriterCore_CannotWriteInErrorOrCompletedState"; + internal const string ODataParameterWriterCore_DuplicatedParameterNameNotAllowed = "ODataParameterWriterCore_DuplicatedParameterNameNotAllowed"; + internal const string ODataParameterWriterCore_CannotWriteValueOnNonValueTypeKind = "ODataParameterWriterCore_CannotWriteValueOnNonValueTypeKind"; + internal const string ODataParameterWriterCore_CannotWriteValueOnNonSupportedValueType = "ODataParameterWriterCore_CannotWriteValueOnNonSupportedValueType"; + internal const string ODataParameterWriterCore_CannotCreateCollectionWriterOnNonCollectionTypeKind = "ODataParameterWriterCore_CannotCreateCollectionWriterOnNonCollectionTypeKind"; + internal const string ODataParameterWriterCore_CannotCreateResourceWriterOnNonEntityOrComplexTypeKind = "ODataParameterWriterCore_CannotCreateResourceWriterOnNonEntityOrComplexTypeKind"; + internal const string ODataParameterWriterCore_CannotCreateResourceSetWriterOnNonStructuredCollectionTypeKind = "ODataParameterWriterCore_CannotCreateResourceSetWriterOnNonStructuredCollectionTypeKind"; + internal const string ODataParameterWriterCore_ParameterNameNotFoundInOperation = "ODataParameterWriterCore_ParameterNameNotFoundInOperation"; + internal const string ODataParameterWriterCore_MissingParameterInParameterPayload = "ODataParameterWriterCore_MissingParameterInParameterPayload"; + internal const string ODataBatchWriter_FlushOrFlushAsyncCalledInStreamRequestedState = "ODataBatchWriter_FlushOrFlushAsyncCalledInStreamRequestedState"; + internal const string ODataBatchWriter_CannotCompleteBatchWithActiveChangeSet = "ODataBatchWriter_CannotCompleteBatchWithActiveChangeSet"; + internal const string ODataBatchWriter_CannotStartChangeSetWithActiveChangeSet = "ODataBatchWriter_CannotStartChangeSetWithActiveChangeSet"; + internal const string ODataBatchWriter_CannotCompleteChangeSetWithoutActiveChangeSet = "ODataBatchWriter_CannotCompleteChangeSetWithoutActiveChangeSet"; + internal const string ODataBatchWriter_InvalidTransitionFromStart = "ODataBatchWriter_InvalidTransitionFromStart"; + internal const string ODataBatchWriter_InvalidTransitionFromBatchStarted = "ODataBatchWriter_InvalidTransitionFromBatchStarted"; + internal const string ODataBatchWriter_InvalidTransitionFromChangeSetStarted = "ODataBatchWriter_InvalidTransitionFromChangeSetStarted"; + internal const string ODataBatchWriter_InvalidTransitionFromOperationCreated = "ODataBatchWriter_InvalidTransitionFromOperationCreated"; + internal const string ODataBatchWriter_InvalidTransitionFromOperationContentStreamRequested = "ODataBatchWriter_InvalidTransitionFromOperationContentStreamRequested"; + internal const string ODataBatchWriter_InvalidTransitionFromOperationContentStreamDisposed = "ODataBatchWriter_InvalidTransitionFromOperationContentStreamDisposed"; + internal const string ODataBatchWriter_InvalidTransitionFromChangeSetCompleted = "ODataBatchWriter_InvalidTransitionFromChangeSetCompleted"; + internal const string ODataBatchWriter_InvalidTransitionFromBatchCompleted = "ODataBatchWriter_InvalidTransitionFromBatchCompleted"; + internal const string ODataBatchWriter_CannotCreateRequestOperationWhenWritingResponse = "ODataBatchWriter_CannotCreateRequestOperationWhenWritingResponse"; + internal const string ODataBatchWriter_CannotCreateResponseOperationWhenWritingRequest = "ODataBatchWriter_CannotCreateResponseOperationWhenWritingRequest"; + internal const string ODataBatchWriter_MaxBatchSizeExceeded = "ODataBatchWriter_MaxBatchSizeExceeded"; + internal const string ODataBatchWriter_MaxChangeSetSizeExceeded = "ODataBatchWriter_MaxChangeSetSizeExceeded"; + internal const string ODataBatchWriter_SyncCallOnAsyncWriter = "ODataBatchWriter_SyncCallOnAsyncWriter"; + internal const string ODataBatchWriter_AsyncCallOnSyncWriter = "ODataBatchWriter_AsyncCallOnSyncWriter"; + internal const string ODataBatchWriter_DuplicateContentIDsNotAllowed = "ODataBatchWriter_DuplicateContentIDsNotAllowed"; + internal const string ODataBatchWriter_CannotWriteInStreamErrorForBatch = "ODataBatchWriter_CannotWriteInStreamErrorForBatch"; + internal const string ODataBatchUtils_RelativeUriUsedWithoutBaseUriSpecified = "ODataBatchUtils_RelativeUriUsedWithoutBaseUriSpecified"; + internal const string ODataBatchUtils_RelativeUriStartingWithDollarUsedWithoutBaseUriSpecified = "ODataBatchUtils_RelativeUriStartingWithDollarUsedWithoutBaseUriSpecified"; + internal const string ODataBatchOperationMessage_VerifyNotCompleted = "ODataBatchOperationMessage_VerifyNotCompleted"; + internal const string ODataBatchOperationStream_Disposed = "ODataBatchOperationStream_Disposed"; + internal const string ODataBatchReader_CannotCreateRequestOperationWhenReadingResponse = "ODataBatchReader_CannotCreateRequestOperationWhenReadingResponse"; + internal const string ODataBatchReader_CannotCreateResponseOperationWhenReadingRequest = "ODataBatchReader_CannotCreateResponseOperationWhenReadingRequest"; + internal const string ODataBatchReader_InvalidStateForCreateOperationRequestMessage = "ODataBatchReader_InvalidStateForCreateOperationRequestMessage"; + internal const string ODataBatchReader_OperationRequestMessageAlreadyCreated = "ODataBatchReader_OperationRequestMessageAlreadyCreated"; + internal const string ODataBatchReader_OperationResponseMessageAlreadyCreated = "ODataBatchReader_OperationResponseMessageAlreadyCreated"; + internal const string ODataBatchReader_InvalidStateForCreateOperationResponseMessage = "ODataBatchReader_InvalidStateForCreateOperationResponseMessage"; + internal const string ODataBatchReader_CannotUseReaderWhileOperationStreamActive = "ODataBatchReader_CannotUseReaderWhileOperationStreamActive"; + internal const string ODataBatchReader_SyncCallOnAsyncReader = "ODataBatchReader_SyncCallOnAsyncReader"; + internal const string ODataBatchReader_AsyncCallOnSyncReader = "ODataBatchReader_AsyncCallOnSyncReader"; + internal const string ODataBatchReader_ReadOrReadAsyncCalledInInvalidState = "ODataBatchReader_ReadOrReadAsyncCalledInInvalidState"; + internal const string ODataBatchReader_MaxBatchSizeExceeded = "ODataBatchReader_MaxBatchSizeExceeded"; + internal const string ODataBatchReader_MaxChangeSetSizeExceeded = "ODataBatchReader_MaxChangeSetSizeExceeded"; + internal const string ODataBatchReader_NoMessageWasCreatedForOperation = "ODataBatchReader_NoMessageWasCreatedForOperation"; + internal const string ODataBatchReader_ReaderModeNotInitilized = "ODataBatchReader_ReaderModeNotInitilized"; + internal const string ODataBatchReader_JsonBatchTopLevelPropertyMissing = "ODataBatchReader_JsonBatchTopLevelPropertyMissing"; + internal const string ODataBatchReader_DuplicateContentIDsNotAllowed = "ODataBatchReader_DuplicateContentIDsNotAllowed"; + internal const string ODataBatchReader_DuplicateAtomicityGroupIDsNotAllowed = "ODataBatchReader_DuplicateAtomicityGroupIDsNotAllowed"; + internal const string ODataBatchReader_RequestPropertyMissing = "ODataBatchReader_RequestPropertyMissing"; + internal const string ODataBatchReader_SameRequestIdAsAtomicityGroupIdNotAllowed = "ODataBatchReader_SameRequestIdAsAtomicityGroupIdNotAllowed"; + internal const string ODataBatchReader_SelfReferenceDependsOnRequestIdNotAllowed = "ODataBatchReader_SelfReferenceDependsOnRequestIdNotAllowed"; + internal const string ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed = "ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed"; + internal const string ODataBatchReader_DependsOnIdNotFound = "ODataBatchReader_DependsOnIdNotFound"; + internal const string ODataBatchReader_AbsoluteURINotMatchingBaseUri = "ODataBatchReader_AbsoluteURINotMatchingBaseUri"; + internal const string ODataBatchReader_ReferenceIdNotIncludedInDependsOn = "ODataBatchReader_ReferenceIdNotIncludedInDependsOn"; + internal const string ODataBatch_GroupIdOrChangeSetIdCannotBeNull = "ODataBatch_GroupIdOrChangeSetIdCannotBeNull"; + internal const string ODataBatchReader_MessageIdPositionedIncorrectly = "ODataBatchReader_MessageIdPositionedIncorrectly"; + internal const string ODataBatchReader_ReaderStreamChangesetBoundaryCannotBeNull = "ODataBatchReader_ReaderStreamChangesetBoundaryCannotBeNull"; + internal const string ODataBatchReaderStream_InvalidHeaderSpecified = "ODataBatchReaderStream_InvalidHeaderSpecified"; + internal const string ODataBatchReaderStream_InvalidRequestLine = "ODataBatchReaderStream_InvalidRequestLine"; + internal const string ODataBatchReaderStream_InvalidResponseLine = "ODataBatchReaderStream_InvalidResponseLine"; + internal const string ODataBatchReaderStream_InvalidHttpVersionSpecified = "ODataBatchReaderStream_InvalidHttpVersionSpecified"; + internal const string ODataBatchReaderStream_NonIntegerHttpStatusCode = "ODataBatchReaderStream_NonIntegerHttpStatusCode"; + internal const string ODataBatchReaderStream_MissingContentTypeHeader = "ODataBatchReaderStream_MissingContentTypeHeader"; + internal const string ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader = "ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader"; + internal const string ODataBatchReaderStream_InvalidContentTypeSpecified = "ODataBatchReaderStream_InvalidContentTypeSpecified"; + internal const string ODataBatchReaderStream_InvalidContentLengthSpecified = "ODataBatchReaderStream_InvalidContentLengthSpecified"; + internal const string ODataBatchReaderStream_DuplicateHeaderFound = "ODataBatchReaderStream_DuplicateHeaderFound"; + internal const string ODataBatchReaderStream_NestedChangesetsAreNotSupported = "ODataBatchReaderStream_NestedChangesetsAreNotSupported"; + internal const string ODataBatchReaderStream_MultiByteEncodingsNotSupported = "ODataBatchReaderStream_MultiByteEncodingsNotSupported"; + internal const string ODataBatchReaderStream_UnexpectedEndOfInput = "ODataBatchReaderStream_UnexpectedEndOfInput"; + internal const string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached = "ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached"; + internal const string ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse = "ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse"; + internal const string ODataAsyncWriter_CannotCreateResponseMoreThanOnce = "ODataAsyncWriter_CannotCreateResponseMoreThanOnce"; + internal const string ODataAsyncWriter_SyncCallOnAsyncWriter = "ODataAsyncWriter_SyncCallOnAsyncWriter"; + internal const string ODataAsyncWriter_AsyncCallOnSyncWriter = "ODataAsyncWriter_AsyncCallOnSyncWriter"; + internal const string ODataAsyncWriter_CannotWriteInStreamErrorForAsync = "ODataAsyncWriter_CannotWriteInStreamErrorForAsync"; + internal const string ODataAsyncReader_InvalidHeaderSpecified = "ODataAsyncReader_InvalidHeaderSpecified"; + internal const string ODataAsyncReader_CannotCreateResponseWhenNotReadingResponse = "ODataAsyncReader_CannotCreateResponseWhenNotReadingResponse"; + internal const string ODataAsyncReader_InvalidResponseLine = "ODataAsyncReader_InvalidResponseLine"; + internal const string ODataAsyncReader_InvalidHttpVersionSpecified = "ODataAsyncReader_InvalidHttpVersionSpecified"; + internal const string ODataAsyncReader_NonIntegerHttpStatusCode = "ODataAsyncReader_NonIntegerHttpStatusCode"; + internal const string ODataAsyncReader_DuplicateHeaderFound = "ODataAsyncReader_DuplicateHeaderFound"; + internal const string ODataAsyncReader_MultiByteEncodingsNotSupported = "ODataAsyncReader_MultiByteEncodingsNotSupported"; + internal const string ODataAsyncReader_InvalidNewLineEncountered = "ODataAsyncReader_InvalidNewLineEncountered"; + internal const string ODataAsyncReader_UnexpectedEndOfInput = "ODataAsyncReader_UnexpectedEndOfInput"; + internal const string ODataAsyncReader_SyncCallOnAsyncReader = "ODataAsyncReader_SyncCallOnAsyncReader"; + internal const string ODataAsyncReader_AsyncCallOnSyncReader = "ODataAsyncReader_AsyncCallOnSyncReader"; + internal const string HttpUtils_MediaTypeUnspecified = "HttpUtils_MediaTypeUnspecified"; + internal const string HttpUtils_MediaTypeRequiresSlash = "HttpUtils_MediaTypeRequiresSlash"; + internal const string HttpUtils_MediaTypeRequiresSubType = "HttpUtils_MediaTypeRequiresSubType"; + internal const string HttpUtils_MediaTypeMissingParameterValue = "HttpUtils_MediaTypeMissingParameterValue"; + internal const string HttpUtils_MediaTypeMissingParameterName = "HttpUtils_MediaTypeMissingParameterName"; + internal const string HttpUtils_EscapeCharWithoutQuotes = "HttpUtils_EscapeCharWithoutQuotes"; + internal const string HttpUtils_EscapeCharAtEnd = "HttpUtils_EscapeCharAtEnd"; + internal const string HttpUtils_ClosingQuoteNotFound = "HttpUtils_ClosingQuoteNotFound"; + internal const string HttpUtils_InvalidCharacterInQuotedParameterValue = "HttpUtils_InvalidCharacterInQuotedParameterValue"; + internal const string HttpUtils_ContentTypeMissing = "HttpUtils_ContentTypeMissing"; + internal const string HttpUtils_MediaTypeRequiresSemicolonBeforeParameter = "HttpUtils_MediaTypeRequiresSemicolonBeforeParameter"; + internal const string HttpUtils_InvalidQualityValueStartChar = "HttpUtils_InvalidQualityValueStartChar"; + internal const string HttpUtils_InvalidQualityValue = "HttpUtils_InvalidQualityValue"; + internal const string HttpUtils_CannotConvertCharToInt = "HttpUtils_CannotConvertCharToInt"; + internal const string HttpUtils_MissingSeparatorBetweenCharsets = "HttpUtils_MissingSeparatorBetweenCharsets"; + internal const string HttpUtils_InvalidSeparatorBetweenCharsets = "HttpUtils_InvalidSeparatorBetweenCharsets"; + internal const string HttpUtils_InvalidCharsetName = "HttpUtils_InvalidCharsetName"; + internal const string HttpUtils_UnexpectedEndOfQValue = "HttpUtils_UnexpectedEndOfQValue"; + internal const string HttpUtils_ExpectedLiteralNotFoundInString = "HttpUtils_ExpectedLiteralNotFoundInString"; + internal const string HttpUtils_InvalidHttpMethodString = "HttpUtils_InvalidHttpMethodString"; + internal const string HttpUtils_NoOrMoreThanOneContentTypeSpecified = "HttpUtils_NoOrMoreThanOneContentTypeSpecified"; + internal const string HttpHeaderValueLexer_UnrecognizedSeparator = "HttpHeaderValueLexer_UnrecognizedSeparator"; + internal const string HttpHeaderValueLexer_TokenExpectedButFoundQuotedString = "HttpHeaderValueLexer_TokenExpectedButFoundQuotedString"; + internal const string HttpHeaderValueLexer_FailedToReadTokenOrQuotedString = "HttpHeaderValueLexer_FailedToReadTokenOrQuotedString"; + internal const string HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString = "HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString"; + internal const string HttpHeaderValueLexer_EndOfFileAfterSeparator = "HttpHeaderValueLexer_EndOfFileAfterSeparator"; + internal const string MediaType_EncodingNotSupported = "MediaType_EncodingNotSupported"; + internal const string MediaTypeUtils_DidNotFindMatchingMediaType = "MediaTypeUtils_DidNotFindMatchingMediaType"; + internal const string MediaTypeUtils_CannotDetermineFormatFromContentType = "MediaTypeUtils_CannotDetermineFormatFromContentType"; + internal const string MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified = "MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified"; + internal const string MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads = "MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads"; + internal const string ExpressionLexer_ExpectedLiteralToken = "ExpressionLexer_ExpectedLiteralToken"; + internal const string ODataUriUtils_ConvertToUriLiteralUnsupportedType = "ODataUriUtils_ConvertToUriLiteralUnsupportedType"; + internal const string ODataUriUtils_ConvertFromUriLiteralTypeRefWithoutModel = "ODataUriUtils_ConvertFromUriLiteralTypeRefWithoutModel"; + internal const string ODataUriUtils_ConvertFromUriLiteralTypeVerificationFailure = "ODataUriUtils_ConvertFromUriLiteralTypeVerificationFailure"; + internal const string ODataUriUtils_ConvertFromUriLiteralNullTypeVerificationFailure = "ODataUriUtils_ConvertFromUriLiteralNullTypeVerificationFailure"; + internal const string ODataUriUtils_ConvertFromUriLiteralNullOnNonNullableType = "ODataUriUtils_ConvertFromUriLiteralNullOnNonNullableType"; + internal const string ODataUtils_CannotConvertValueToRawString = "ODataUtils_CannotConvertValueToRawString"; + internal const string ODataUtils_DidNotFindDefaultMediaType = "ODataUtils_DidNotFindDefaultMediaType"; + internal const string ODataUtils_UnsupportedVersionHeader = "ODataUtils_UnsupportedVersionHeader"; + internal const string ODataUtils_MaxProtocolVersionExceeded = "ODataUtils_MaxProtocolVersionExceeded"; + internal const string ODataUtils_UnsupportedVersionNumber = "ODataUtils_UnsupportedVersionNumber"; + internal const string ODataUtils_ModelDoesNotHaveContainer = "ODataUtils_ModelDoesNotHaveContainer"; + internal const string ReaderUtils_EnumerableModified = "ReaderUtils_EnumerableModified"; + internal const string ReaderValidationUtils_NullValueForNonNullableType = "ReaderValidationUtils_NullValueForNonNullableType"; + internal const string ReaderValidationUtils_NullNamedValueForNonNullableType = "ReaderValidationUtils_NullNamedValueForNonNullableType"; + internal const string ReaderValidationUtils_EntityReferenceLinkMissingUri = "ReaderValidationUtils_EntityReferenceLinkMissingUri"; + internal const string ReaderValidationUtils_ValueWithoutType = "ReaderValidationUtils_ValueWithoutType"; + internal const string ReaderValidationUtils_ResourceWithoutType = "ReaderValidationUtils_ResourceWithoutType"; + internal const string ReaderValidationUtils_CannotConvertPrimitiveValue = "ReaderValidationUtils_CannotConvertPrimitiveValue"; + internal const string ReaderValidationUtils_MessageReaderSettingsBaseUriMustBeNullOrAbsolute = "ReaderValidationUtils_MessageReaderSettingsBaseUriMustBeNullOrAbsolute"; + internal const string ReaderValidationUtils_UndeclaredPropertyBehaviorKindSpecifiedOnRequest = "ReaderValidationUtils_UndeclaredPropertyBehaviorKindSpecifiedOnRequest"; + internal const string ReaderValidationUtils_ContextUriValidationInvalidExpectedEntitySet = "ReaderValidationUtils_ContextUriValidationInvalidExpectedEntitySet"; + internal const string ReaderValidationUtils_ContextUriValidationInvalidExpectedEntityType = "ReaderValidationUtils_ContextUriValidationInvalidExpectedEntityType"; + internal const string ReaderValidationUtils_ContextUriValidationNonMatchingPropertyNames = "ReaderValidationUtils_ContextUriValidationNonMatchingPropertyNames"; + internal const string ReaderValidationUtils_ContextUriValidationNonMatchingDeclaringTypes = "ReaderValidationUtils_ContextUriValidationNonMatchingDeclaringTypes"; + internal const string ReaderValidationUtils_NonMatchingPropertyNames = "ReaderValidationUtils_NonMatchingPropertyNames"; + internal const string ReaderValidationUtils_TypeInContextUriDoesNotMatchExpectedType = "ReaderValidationUtils_TypeInContextUriDoesNotMatchExpectedType"; + internal const string ReaderValidationUtils_ContextUriDoesNotReferTypeAssignableToExpectedType = "ReaderValidationUtils_ContextUriDoesNotReferTypeAssignableToExpectedType"; + internal const string ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint = "ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint"; + internal const string ODataMessageReader_ReaderAlreadyUsed = "ODataMessageReader_ReaderAlreadyUsed"; + internal const string ODataMessageReader_ErrorPayloadInRequest = "ODataMessageReader_ErrorPayloadInRequest"; + internal const string ODataMessageReader_ServiceDocumentInRequest = "ODataMessageReader_ServiceDocumentInRequest"; + internal const string ODataMessageReader_MetadataDocumentInRequest = "ODataMessageReader_MetadataDocumentInRequest"; + internal const string ODataMessageReader_DeltaInRequest = "ODataMessageReader_DeltaInRequest"; + internal const string ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata = "ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata"; + internal const string ODataMessageReader_EntitySetSpecifiedWithoutMetadata = "ODataMessageReader_EntitySetSpecifiedWithoutMetadata"; + internal const string ODataMessageReader_OperationImportSpecifiedWithoutMetadata = "ODataMessageReader_OperationImportSpecifiedWithoutMetadata"; + internal const string ODataMessageReader_OperationSpecifiedWithoutMetadata = "ODataMessageReader_OperationSpecifiedWithoutMetadata"; + internal const string ODataMessageReader_ExpectedCollectionTypeWrongKind = "ODataMessageReader_ExpectedCollectionTypeWrongKind"; + internal const string ODataMessageReader_ExpectedPropertyTypeEntityCollectionKind = "ODataMessageReader_ExpectedPropertyTypeEntityCollectionKind"; + internal const string ODataMessageReader_ExpectedPropertyTypeEntityKind = "ODataMessageReader_ExpectedPropertyTypeEntityKind"; + internal const string ODataMessageReader_ExpectedPropertyTypeStream = "ODataMessageReader_ExpectedPropertyTypeStream"; + internal const string ODataMessageReader_ExpectedValueTypeWrongKind = "ODataMessageReader_ExpectedValueTypeWrongKind"; + internal const string ODataMessageReader_NoneOrEmptyContentTypeHeader = "ODataMessageReader_NoneOrEmptyContentTypeHeader"; + internal const string ODataMessageReader_WildcardInContentType = "ODataMessageReader_WildcardInContentType"; + internal const string ODataMessageReader_GetFormatCalledBeforeReadingStarted = "ODataMessageReader_GetFormatCalledBeforeReadingStarted"; + internal const string ODataMessageReader_DetectPayloadKindMultipleTimes = "ODataMessageReader_DetectPayloadKindMultipleTimes"; + internal const string ODataMessageReader_PayloadKindDetectionRunning = "ODataMessageReader_PayloadKindDetectionRunning"; + internal const string ODataMessageReader_PayloadKindDetectionInServerMode = "ODataMessageReader_PayloadKindDetectionInServerMode"; + internal const string ODataMessageReader_ParameterPayloadInResponse = "ODataMessageReader_ParameterPayloadInResponse"; + internal const string ODataMessageReader_SingletonNavigationPropertyForEntityReferenceLinks = "ODataMessageReader_SingletonNavigationPropertyForEntityReferenceLinks"; + internal const string ODataAsyncResponseMessage_MustNotModifyMessage = "ODataAsyncResponseMessage_MustNotModifyMessage"; + internal const string ODataMessage_MustNotModifyMessage = "ODataMessage_MustNotModifyMessage"; + internal const string ODataReaderCore_SyncCallOnAsyncReader = "ODataReaderCore_SyncCallOnAsyncReader"; + internal const string ODataReaderCore_AsyncCallOnSyncReader = "ODataReaderCore_AsyncCallOnSyncReader"; + internal const string ODataReaderCore_ReadOrReadAsyncCalledInInvalidState = "ODataReaderCore_ReadOrReadAsyncCalledInInvalidState"; + internal const string ODataReaderCore_CreateReadStreamCalledInInvalidState = "ODataReaderCore_CreateReadStreamCalledInInvalidState"; + internal const string ODataReaderCore_CreateTextReaderCalledInInvalidState = "ODataReaderCore_CreateTextReaderCalledInInvalidState"; + internal const string ODataReaderCore_ReadCalledWithOpenStream = "ODataReaderCore_ReadCalledWithOpenStream"; + internal const string ODataReaderCore_NoReadCallsAllowed = "ODataReaderCore_NoReadCallsAllowed"; + internal const string ODataWriterCore_PropertyValueAlreadyWritten = "ODataWriterCore_PropertyValueAlreadyWritten"; + internal const string ODataJsonReader_CannotReadResourcesOfResourceSet = "ODataJsonReader_CannotReadResourcesOfResourceSet"; + internal const string ODataJsonReaderUtils_CannotConvertInt32 = "ODataJsonReaderUtils_CannotConvertInt32"; + internal const string ODataJsonReaderUtils_CannotConvertDouble = "ODataJsonReaderUtils_CannotConvertDouble"; + internal const string ODataJsonReaderUtils_CannotConvertBoolean = "ODataJsonReaderUtils_CannotConvertBoolean"; + internal const string ODataJsonReaderUtils_CannotConvertDecimal = "ODataJsonReaderUtils_CannotConvertDecimal"; + internal const string ODataJsonReaderUtils_CannotConvertDateTime = "ODataJsonReaderUtils_CannotConvertDateTime"; + internal const string ODataJsonReaderUtils_CannotConvertDateTimeOffset = "ODataJsonReaderUtils_CannotConvertDateTimeOffset"; + internal const string ODataJsonReaderUtils_ConflictBetweenInputFormatAndParameter = "ODataJsonReaderUtils_ConflictBetweenInputFormatAndParameter"; + internal const string ODataJsonReaderUtils_MultipleErrorPropertiesWithSameName = "ODataJsonReaderUtils_MultipleErrorPropertiesWithSameName"; + internal const string ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustSpecifyTarget = "ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustSpecifyTarget"; + internal const string ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustNotHaveDuplicateTarget = "ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustNotHaveDuplicateTarget"; + internal const string ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty = "ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty"; + internal const string ODataJsonErrorDeserializer_TopLevelErrorMessageValueWithInvalidProperty = "ODataJsonErrorDeserializer_TopLevelErrorMessageValueWithInvalidProperty"; + internal const string ODataCollectionReaderCore_ReadOrReadAsyncCalledInInvalidState = "ODataCollectionReaderCore_ReadOrReadAsyncCalledInInvalidState"; + internal const string ODataCollectionReaderCore_SyncCallOnAsyncReader = "ODataCollectionReaderCore_SyncCallOnAsyncReader"; + internal const string ODataCollectionReaderCore_AsyncCallOnSyncReader = "ODataCollectionReaderCore_AsyncCallOnSyncReader"; + internal const string ODataCollectionReaderCore_ExpectedItemTypeSetInInvalidState = "ODataCollectionReaderCore_ExpectedItemTypeSetInInvalidState"; + internal const string ODataParameterReaderCore_ReadOrReadAsyncCalledInInvalidState = "ODataParameterReaderCore_ReadOrReadAsyncCalledInInvalidState"; + internal const string ODataParameterReaderCore_SyncCallOnAsyncReader = "ODataParameterReaderCore_SyncCallOnAsyncReader"; + internal const string ODataParameterReaderCore_AsyncCallOnSyncReader = "ODataParameterReaderCore_AsyncCallOnSyncReader"; + internal const string ODataParameterReaderCore_SubReaderMustBeCreatedAndReadToCompletionBeforeTheNextReadOrReadAsyncCall = "ODataParameterReaderCore_SubReaderMustBeCreatedAndReadToCompletionBeforeTheNextReadOrReadAsyncCall"; + internal const string ODataParameterReaderCore_SubReaderMustBeInCompletedStateBeforeTheNextReadOrReadAsyncCall = "ODataParameterReaderCore_SubReaderMustBeInCompletedStateBeforeTheNextReadOrReadAsyncCall"; + internal const string ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState = "ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState"; + internal const string ODataParameterReaderCore_CreateReaderAlreadyCalled = "ODataParameterReaderCore_CreateReaderAlreadyCalled"; + internal const string ODataParameterReaderCore_ParameterNameNotInMetadata = "ODataParameterReaderCore_ParameterNameNotInMetadata"; + internal const string ODataParameterReaderCore_DuplicateParametersInPayload = "ODataParameterReaderCore_DuplicateParametersInPayload"; + internal const string ODataParameterReaderCore_ParametersMissingInPayload = "ODataParameterReaderCore_ParametersMissingInPayload"; + internal const string ValidationUtils_ActionsAndFunctionsMustSpecifyMetadata = "ValidationUtils_ActionsAndFunctionsMustSpecifyMetadata"; + internal const string ValidationUtils_ActionsAndFunctionsMustSpecifyTarget = "ValidationUtils_ActionsAndFunctionsMustSpecifyTarget"; + internal const string ValidationUtils_EnumerableContainsANullItem = "ValidationUtils_EnumerableContainsANullItem"; + internal const string ValidationUtils_AssociationLinkMustSpecifyName = "ValidationUtils_AssociationLinkMustSpecifyName"; + internal const string ValidationUtils_AssociationLinkMustSpecifyUrl = "ValidationUtils_AssociationLinkMustSpecifyUrl"; + internal const string ValidationUtils_TypeNameMustNotBeEmpty = "ValidationUtils_TypeNameMustNotBeEmpty"; + internal const string ValidationUtils_PropertyDoesNotExistOnType = "ValidationUtils_PropertyDoesNotExistOnType"; + internal const string ValidationUtils_ResourceMustSpecifyUrl = "ValidationUtils_ResourceMustSpecifyUrl"; + internal const string ValidationUtils_ResourceMustSpecifyName = "ValidationUtils_ResourceMustSpecifyName"; + internal const string ValidationUtils_ServiceDocumentElementUrlMustNotBeNull = "ValidationUtils_ServiceDocumentElementUrlMustNotBeNull"; + internal const string ValidationUtils_NonPrimitiveTypeForPrimitiveValue = "ValidationUtils_NonPrimitiveTypeForPrimitiveValue"; + internal const string ValidationUtils_UnsupportedPrimitiveType = "ValidationUtils_UnsupportedPrimitiveType"; + internal const string ValidationUtils_IncompatiblePrimitiveItemType = "ValidationUtils_IncompatiblePrimitiveItemType"; + internal const string ValidationUtils_NonNullableCollectionElementsMustNotBeNull = "ValidationUtils_NonNullableCollectionElementsMustNotBeNull"; + internal const string ValidationUtils_InvalidCollectionTypeName = "ValidationUtils_InvalidCollectionTypeName"; + internal const string ValidationUtils_UnrecognizedTypeName = "ValidationUtils_UnrecognizedTypeName"; + internal const string ValidationUtils_IncorrectTypeKind = "ValidationUtils_IncorrectTypeKind"; + internal const string ValidationUtils_IncorrectTypeKindNoTypeName = "ValidationUtils_IncorrectTypeKindNoTypeName"; + internal const string ValidationUtils_IncorrectValueTypeKind = "ValidationUtils_IncorrectValueTypeKind"; + internal const string ValidationUtils_LinkMustSpecifyName = "ValidationUtils_LinkMustSpecifyName"; + internal const string ValidationUtils_MismatchPropertyKindForStreamProperty = "ValidationUtils_MismatchPropertyKindForStreamProperty"; + internal const string ValidationUtils_NestedCollectionsAreNotSupported = "ValidationUtils_NestedCollectionsAreNotSupported"; + internal const string ValidationUtils_StreamReferenceValuesNotSupportedInCollections = "ValidationUtils_StreamReferenceValuesNotSupportedInCollections"; + internal const string ValidationUtils_IncompatibleType = "ValidationUtils_IncompatibleType"; + internal const string ValidationUtils_OpenCollectionProperty = "ValidationUtils_OpenCollectionProperty"; + internal const string ValidationUtils_OpenStreamProperty = "ValidationUtils_OpenStreamProperty"; + internal const string ValidationUtils_InvalidCollectionTypeReference = "ValidationUtils_InvalidCollectionTypeReference"; + internal const string ValidationUtils_ResourceWithMediaResourceAndNonMLEType = "ValidationUtils_ResourceWithMediaResourceAndNonMLEType"; + internal const string ValidationUtils_ResourceWithoutMediaResourceAndMLEType = "ValidationUtils_ResourceWithoutMediaResourceAndMLEType"; + internal const string ValidationUtils_ResourceTypeNotAssignableToExpectedType = "ValidationUtils_ResourceTypeNotAssignableToExpectedType"; + internal const string ValidationUtils_NavigationPropertyExpected = "ValidationUtils_NavigationPropertyExpected"; + internal const string ValidationUtils_InvalidBatchBoundaryDelimiterLength = "ValidationUtils_InvalidBatchBoundaryDelimiterLength"; + internal const string ValidationUtils_RecursionDepthLimitReached = "ValidationUtils_RecursionDepthLimitReached"; + internal const string ValidationUtils_MaxDepthOfNestedEntriesExceeded = "ValidationUtils_MaxDepthOfNestedEntriesExceeded"; + internal const string ValidationUtils_NullCollectionItemForNonNullableType = "ValidationUtils_NullCollectionItemForNonNullableType"; + internal const string ValidationUtils_PropertiesMustNotContainReservedChars = "ValidationUtils_PropertiesMustNotContainReservedChars"; + internal const string ValidationUtils_WorkspaceResourceMustNotContainNullItem = "ValidationUtils_WorkspaceResourceMustNotContainNullItem"; + internal const string ValidationUtils_InvalidMetadataReferenceProperty = "ValidationUtils_InvalidMetadataReferenceProperty"; + internal const string WriterValidationUtils_PropertyMustNotBeNull = "WriterValidationUtils_PropertyMustNotBeNull"; + internal const string WriterValidationUtils_PropertiesMustHaveNonEmptyName = "WriterValidationUtils_PropertiesMustHaveNonEmptyName"; + internal const string WriterValidationUtils_MissingTypeNameWithMetadata = "WriterValidationUtils_MissingTypeNameWithMetadata"; + internal const string WriterValidationUtils_NextPageLinkInRequest = "WriterValidationUtils_NextPageLinkInRequest"; + internal const string WriterValidationUtils_DefaultStreamWithContentTypeWithoutReadLink = "WriterValidationUtils_DefaultStreamWithContentTypeWithoutReadLink"; + internal const string WriterValidationUtils_DefaultStreamWithReadLinkWithoutContentType = "WriterValidationUtils_DefaultStreamWithReadLinkWithoutContentType"; + internal const string WriterValidationUtils_StreamReferenceValueMustHaveEditLinkOrReadLink = "WriterValidationUtils_StreamReferenceValueMustHaveEditLinkOrReadLink"; + internal const string WriterValidationUtils_StreamReferenceValueMustHaveEditLinkToHaveETag = "WriterValidationUtils_StreamReferenceValueMustHaveEditLinkToHaveETag"; + internal const string WriterValidationUtils_StreamReferenceValueEmptyContentType = "WriterValidationUtils_StreamReferenceValueEmptyContentType"; + internal const string WriterValidationUtils_EntriesMustHaveNonEmptyId = "WriterValidationUtils_EntriesMustHaveNonEmptyId"; + internal const string WriterValidationUtils_MessageWriterSettingsBaseUriMustBeNullOrAbsolute = "WriterValidationUtils_MessageWriterSettingsBaseUriMustBeNullOrAbsolute"; + internal const string WriterValidationUtils_EntityReferenceLinkUrlMustNotBeNull = "WriterValidationUtils_EntityReferenceLinkUrlMustNotBeNull"; + internal const string WriterValidationUtils_EntityReferenceLinksLinkMustNotBeNull = "WriterValidationUtils_EntityReferenceLinksLinkMustNotBeNull"; + internal const string WriterValidationUtils_NestedResourceTypeNotCompatibleWithParentPropertyType = "WriterValidationUtils_NestedResourceTypeNotCompatibleWithParentPropertyType"; + internal const string WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceContent = "WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceContent"; + internal const string WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetContent = "WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetContent"; + internal const string WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceMetadata = "WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceMetadata"; + internal const string WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetMetadata = "WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetMetadata"; + internal const string WriterValidationUtils_ExpandedLinkWithResourceSetPayloadAndResourceMetadata = "WriterValidationUtils_ExpandedLinkWithResourceSetPayloadAndResourceMetadata"; + internal const string WriterValidationUtils_ExpandedLinkWithResourcePayloadAndResourceSetMetadata = "WriterValidationUtils_ExpandedLinkWithResourcePayloadAndResourceSetMetadata"; + internal const string WriterValidationUtils_CollectionPropertiesMustNotHaveNullValue = "WriterValidationUtils_CollectionPropertiesMustNotHaveNullValue"; + internal const string WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue = "WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue"; + internal const string WriterValidationUtils_StreamPropertiesMustNotHaveNullValue = "WriterValidationUtils_StreamPropertiesMustNotHaveNullValue"; + internal const string WriterValidationUtils_OperationInRequest = "WriterValidationUtils_OperationInRequest"; + internal const string WriterValidationUtils_AssociationLinkInRequest = "WriterValidationUtils_AssociationLinkInRequest"; + internal const string WriterValidationUtils_StreamPropertyInRequest = "WriterValidationUtils_StreamPropertyInRequest"; + internal const string WriterValidationUtils_MessageWriterSettingsServiceDocumentUriMustBeNullOrAbsolute = "WriterValidationUtils_MessageWriterSettingsServiceDocumentUriMustBeNullOrAbsolute"; + internal const string WriterValidationUtils_NavigationLinkMustSpecifyUrl = "WriterValidationUtils_NavigationLinkMustSpecifyUrl"; + internal const string WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection = "WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection"; + internal const string WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage = "WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage"; + internal const string WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint = "WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint"; + internal const string XmlReaderExtension_InvalidNodeInStringValue = "XmlReaderExtension_InvalidNodeInStringValue"; + internal const string XmlReaderExtension_InvalidRootNode = "XmlReaderExtension_InvalidRootNode"; + internal const string ODataMetadataInputContext_ErrorReadingMetadata = "ODataMetadataInputContext_ErrorReadingMetadata"; + internal const string ODataMetadataOutputContext_ErrorWritingMetadata = "ODataMetadataOutputContext_ErrorWritingMetadata"; + internal const string ODataAtomDeserializer_RelativeUriUsedWithoutBaseUriSpecified = "ODataAtomDeserializer_RelativeUriUsedWithoutBaseUriSpecified"; + internal const string ODataAtomPropertyAndValueDeserializer_InvalidCollectionElement = "ODataAtomPropertyAndValueDeserializer_InvalidCollectionElement"; + internal const string ODataAtomPropertyAndValueDeserializer_NavigationPropertyInProperties = "ODataAtomPropertyAndValueDeserializer_NavigationPropertyInProperties"; + internal const string JsonLightInstanceAnnotationWriter_NullValueNotAllowedForInstanceAnnotation = "JsonLightInstanceAnnotationWriter_NullValueNotAllowedForInstanceAnnotation"; + internal const string EdmLibraryExtensions_OperationGroupReturningActionsAndFunctionsModelInvalid = "EdmLibraryExtensions_OperationGroupReturningActionsAndFunctionsModelInvalid"; + internal const string EdmLibraryExtensions_UnBoundOperationsFoundFromIEdmModelFindMethodIsInvalid = "EdmLibraryExtensions_UnBoundOperationsFoundFromIEdmModelFindMethodIsInvalid"; + internal const string EdmLibraryExtensions_NoParameterBoundOperationsFoundFromIEdmModelFindMethodIsInvalid = "EdmLibraryExtensions_NoParameterBoundOperationsFoundFromIEdmModelFindMethodIsInvalid"; + internal const string EdmLibraryExtensions_ValueOverflowForUnderlyingType = "EdmLibraryExtensions_ValueOverflowForUnderlyingType"; + internal const string ODataAtomResourceDeserializer_ContentWithWrongType = "ODataAtomResourceDeserializer_ContentWithWrongType"; + internal const string ODataAtomErrorDeserializer_MultipleErrorElementsWithSameName = "ODataAtomErrorDeserializer_MultipleErrorElementsWithSameName"; + internal const string ODataAtomErrorDeserializer_MultipleInnerErrorElementsWithSameName = "ODataAtomErrorDeserializer_MultipleInnerErrorElementsWithSameName"; + internal const string CollectionWithoutExpectedTypeValidator_InvalidItemTypeKind = "CollectionWithoutExpectedTypeValidator_InvalidItemTypeKind"; + internal const string CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeKind = "CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeKind"; + internal const string CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeName = "CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeName"; + internal const string ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes = "ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes"; + internal const string MessageStreamWrappingStream_ByteLimitExceeded = "MessageStreamWrappingStream_ByteLimitExceeded"; + internal const string MetadataUtils_ResolveTypeName = "MetadataUtils_ResolveTypeName"; + internal const string MetadataUtils_CalculateBindableOperationsForType = "MetadataUtils_CalculateBindableOperationsForType"; + internal const string EdmValueUtils_UnsupportedPrimitiveType = "EdmValueUtils_UnsupportedPrimitiveType"; + internal const string EdmValueUtils_IncorrectPrimitiveTypeKind = "EdmValueUtils_IncorrectPrimitiveTypeKind"; + internal const string EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName = "EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName"; + internal const string EdmValueUtils_CannotConvertTypeToClrValue = "EdmValueUtils_CannotConvertTypeToClrValue"; + internal const string ODataEdmStructuredValue_UndeclaredProperty = "ODataEdmStructuredValue_UndeclaredProperty"; + internal const string ODataMetadataBuilder_MissingEntitySetUri = "ODataMetadataBuilder_MissingEntitySetUri"; + internal const string ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix = "ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix"; + internal const string ODataMetadataBuilder_MissingEntityInstanceUri = "ODataMetadataBuilder_MissingEntityInstanceUri"; + internal const string ODataMetadataBuilder_MissingParentIdOrContextUrl = "ODataMetadataBuilder_MissingParentIdOrContextUrl"; + internal const string ODataMetadataBuilder_UnknownEntitySet = "ODataMetadataBuilder_UnknownEntitySet"; + internal const string ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType = "ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType"; + internal const string ODataJsonLightInputContext_PayloadKindDetectionForRequest = "ODataJsonLightInputContext_PayloadKindDetectionForRequest"; + internal const string ODataJsonLightInputContext_OperationCannotBeNullForCreateParameterReader = "ODataJsonLightInputContext_OperationCannotBeNullForCreateParameterReader"; + internal const string ODataJsonLightInputContext_NoEntitySetForRequest = "ODataJsonLightInputContext_NoEntitySetForRequest"; + internal const string ODataJsonLightInputContext_ModelRequiredForReading = "ODataJsonLightInputContext_ModelRequiredForReading"; + internal const string ODataJsonLightInputContext_ItemTypeRequiredForCollectionReaderInRequests = "ODataJsonLightInputContext_ItemTypeRequiredForCollectionReaderInRequests"; + internal const string ODataJsonLightDeserializer_ContextLinkNotFoundAsFirstProperty = "ODataJsonLightDeserializer_ContextLinkNotFoundAsFirstProperty"; + internal const string ODataJsonLightDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation = "ODataJsonLightDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation"; + internal const string ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue = "ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue"; + internal const string ODataJsonLightWriter_EntityReferenceLinkAfterResourceSetInRequest = "ODataJsonLightWriter_EntityReferenceLinkAfterResourceSetInRequest"; + internal const string ODataJsonLightWriter_InstanceAnnotationNotSupportedOnExpandedResourceSet = "ODataJsonLightWriter_InstanceAnnotationNotSupportedOnExpandedResourceSet"; + internal const string ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForResourceValueRequest = "ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForResourceValueRequest"; + internal const string ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForCollectionValueInRequest = "ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForCollectionValueInRequest"; + internal const string ODataResourceTypeContext_MetadataOrSerializationInfoMissing = "ODataResourceTypeContext_MetadataOrSerializationInfoMissing"; + internal const string ODataResourceTypeContext_ODataResourceTypeNameMissing = "ODataResourceTypeContext_ODataResourceTypeNameMissing"; + internal const string ODataContextUriBuilder_ValidateDerivedType = "ODataContextUriBuilder_ValidateDerivedType"; + internal const string ODataContextUriBuilder_TypeNameMissingForTopLevelCollection = "ODataContextUriBuilder_TypeNameMissingForTopLevelCollection"; + internal const string ODataContextUriBuilder_UnsupportedPayloadKind = "ODataContextUriBuilder_UnsupportedPayloadKind"; + internal const string ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource = "ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource"; + internal const string ODataContextUriBuilder_NavigationSourceOrTypeNameMissingForResourceOrResourceSet = "ODataContextUriBuilder_NavigationSourceOrTypeNameMissingForResourceOrResourceSet"; + internal const string ODataContextUriBuilder_ODataUriMissingForIndividualProperty = "ODataContextUriBuilder_ODataUriMissingForIndividualProperty"; + internal const string ODataContextUriBuilder_TypeNameMissingForProperty = "ODataContextUriBuilder_TypeNameMissingForProperty"; + internal const string ODataContextUriBuilder_ODataPathInvalidForContainedElement = "ODataContextUriBuilder_ODataPathInvalidForContainedElement"; + internal const string ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties = "ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties"; + internal const string ODataJsonLightPropertyAndValueDeserializer_UnexpectedPropertyAnnotation = "ODataJsonLightPropertyAndValueDeserializer_UnexpectedPropertyAnnotation"; + internal const string ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation = "ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation"; + internal const string ODataJsonLightPropertyAndValueDeserializer_UnexpectedProperty = "ODataJsonLightPropertyAndValueDeserializer_UnexpectedProperty"; + internal const string ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload = "ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload"; + internal const string ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName = "ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName"; + internal const string ODataJsonLightPropertyAndValueDeserializer_InvalidTypeName = "ODataJsonLightPropertyAndValueDeserializer_InvalidTypeName"; + internal const string ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty = "ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty"; + internal const string ODataJsonLightPropertyAndValueDeserializer_ResourceValuePropertyAnnotationWithoutProperty = "ODataJsonLightPropertyAndValueDeserializer_ResourceValuePropertyAnnotationWithoutProperty"; + internal const string ODataJsonLightPropertyAndValueDeserializer_ComplexValueWithPropertyTypeAnnotation = "ODataJsonLightPropertyAndValueDeserializer_ComplexValueWithPropertyTypeAnnotation"; + internal const string ODataJsonLightPropertyAndValueDeserializer_ResourceTypeAnnotationNotFirst = "ODataJsonLightPropertyAndValueDeserializer_ResourceTypeAnnotationNotFirst"; + internal const string ODataJsonLightPropertyAndValueDeserializer_UnexpectedDataPropertyAnnotation = "ODataJsonLightPropertyAndValueDeserializer_UnexpectedDataPropertyAnnotation"; + internal const string ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty = "ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty"; + internal const string ODataJsonLightPropertyAndValueDeserializer_ODataTypeAnnotationInPrimitiveValue = "ODataJsonLightPropertyAndValueDeserializer_ODataTypeAnnotationInPrimitiveValue"; + internal const string ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyWithPrimitiveNullValue = "ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyWithPrimitiveNullValue"; + internal const string ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty = "ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty"; + internal const string ODataJsonLightPropertyAndValueDeserializer_NoPropertyAndAnnotationAllowedInNullPayload = "ODataJsonLightPropertyAndValueDeserializer_NoPropertyAndAnnotationAllowedInNullPayload"; + internal const string ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected = "ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected"; + internal const string ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected = "ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected"; + internal const string ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue = "ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue"; + internal const string ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource = "ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource"; + internal const string ODataJsonLightReaderUtils_AnnotationWithNullValue = "ODataJsonLightReaderUtils_AnnotationWithNullValue"; + internal const string ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation = "ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation"; + internal const string JsonLightInstanceAnnotationWriter_DuplicateAnnotationNameInCollection = "JsonLightInstanceAnnotationWriter_DuplicateAnnotationNameInCollection"; + internal const string ODataJsonLightContextUriParser_NullMetadataDocumentUri = "ODataJsonLightContextUriParser_NullMetadataDocumentUri"; + internal const string ODataJsonLightContextUriParser_ContextUriDoesNotMatchExpectedPayloadKind = "ODataJsonLightContextUriParser_ContextUriDoesNotMatchExpectedPayloadKind"; + internal const string ODataJsonLightContextUriParser_InvalidEntitySetNameOrTypeName = "ODataJsonLightContextUriParser_InvalidEntitySetNameOrTypeName"; + internal const string ODataJsonLightContextUriParser_InvalidPayloadKindWithSelectQueryOption = "ODataJsonLightContextUriParser_InvalidPayloadKindWithSelectQueryOption"; + internal const string ODataJsonLightContextUriParser_NoModel = "ODataJsonLightContextUriParser_NoModel"; + internal const string ODataJsonLightContextUriParser_InvalidContextUrl = "ODataJsonLightContextUriParser_InvalidContextUrl"; + internal const string ODataJsonLightContextUriParser_LastSegmentIsKeySegment = "ODataJsonLightContextUriParser_LastSegmentIsKeySegment"; + internal const string ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute = "ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute"; + internal const string ODataJsonLightResourceDeserializer_DeltaRemovedAnnotationMustBeObject = "ODataJsonLightResourceDeserializer_DeltaRemovedAnnotationMustBeObject"; + internal const string ODataJsonLightResourceDeserializer_ResourceTypeAnnotationNotFirst = "ODataJsonLightResourceDeserializer_ResourceTypeAnnotationNotFirst"; + internal const string ODataJsonLightResourceDeserializer_ResourceInstanceAnnotationPrecededByProperty = "ODataJsonLightResourceDeserializer_ResourceInstanceAnnotationPrecededByProperty"; + internal const string ODataJsonLightResourceDeserializer_CannotReadResourceSetContentStart = "ODataJsonLightResourceDeserializer_CannotReadResourceSetContentStart"; + internal const string ODataJsonLightResourceDeserializer_ExpectedResourceSetPropertyNotFound = "ODataJsonLightResourceDeserializer_ExpectedResourceSetPropertyNotFound"; + internal const string ODataJsonLightResourceDeserializer_InvalidNodeTypeForItemsInResourceSet = "ODataJsonLightResourceDeserializer_InvalidNodeTypeForItemsInResourceSet"; + internal const string ODataJsonLightResourceDeserializer_InvalidPropertyAnnotationInTopLevelResourceSet = "ODataJsonLightResourceDeserializer_InvalidPropertyAnnotationInTopLevelResourceSet"; + internal const string ODataJsonLightResourceDeserializer_InvalidPropertyInTopLevelResourceSet = "ODataJsonLightResourceDeserializer_InvalidPropertyInTopLevelResourceSet"; + internal const string ODataJsonLightResourceDeserializer_PropertyWithoutValueWithWrongType = "ODataJsonLightResourceDeserializer_PropertyWithoutValueWithWrongType"; + internal const string ODataJsonLightResourceDeserializer_OpenPropertyWithoutValue = "ODataJsonLightResourceDeserializer_OpenPropertyWithoutValue"; + internal const string ODataJsonLightResourceDeserializer_StreamPropertyInRequest = "ODataJsonLightResourceDeserializer_StreamPropertyInRequest"; + internal const string ODataJsonLightResourceDeserializer_UnexpectedStreamPropertyAnnotation = "ODataJsonLightResourceDeserializer_UnexpectedStreamPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_StreamPropertyWithValue = "ODataJsonLightResourceDeserializer_StreamPropertyWithValue"; + internal const string ODataJsonLightResourceDeserializer_UnexpectedDeferredLinkPropertyAnnotation = "ODataJsonLightResourceDeserializer_UnexpectedDeferredLinkPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_CannotReadSingletonNestedResource = "ODataJsonLightResourceDeserializer_CannotReadSingletonNestedResource"; + internal const string ODataJsonLightResourceDeserializer_CannotReadCollectionNestedResource = "ODataJsonLightResourceDeserializer_CannotReadCollectionNestedResource"; + internal const string ODataJsonLightResourceDeserializer_CannotReadNestedResource = "ODataJsonLightResourceDeserializer_CannotReadNestedResource"; + internal const string ODataJsonLightResourceDeserializer_UnexpectedExpandedSingletonNavigationLinkPropertyAnnotation = "ODataJsonLightResourceDeserializer_UnexpectedExpandedSingletonNavigationLinkPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_UnexpectedExpandedCollectionNavigationLinkPropertyAnnotation = "ODataJsonLightResourceDeserializer_UnexpectedExpandedCollectionNavigationLinkPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_UnexpectedComplexCollectionPropertyAnnotation = "ODataJsonLightResourceDeserializer_UnexpectedComplexCollectionPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_DuplicateNestedResourceSetAnnotation = "ODataJsonLightResourceDeserializer_DuplicateNestedResourceSetAnnotation"; + internal const string ODataJsonLightResourceDeserializer_UnexpectedPropertyAnnotationAfterExpandedResourceSet = "ODataJsonLightResourceDeserializer_UnexpectedPropertyAnnotationAfterExpandedResourceSet"; + internal const string ODataJsonLightResourceDeserializer_UnexpectedNavigationLinkInRequestPropertyAnnotation = "ODataJsonLightResourceDeserializer_UnexpectedNavigationLinkInRequestPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_ArrayValueForSingletonBindPropertyAnnotation = "ODataJsonLightResourceDeserializer_ArrayValueForSingletonBindPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_StringValueForCollectionBindPropertyAnnotation = "ODataJsonLightResourceDeserializer_StringValueForCollectionBindPropertyAnnotation"; + internal const string ODataJsonLightResourceDeserializer_EmptyBindArray = "ODataJsonLightResourceDeserializer_EmptyBindArray"; + internal const string ODataJsonLightResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink = "ODataJsonLightResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink"; + internal const string ODataJsonLightResourceDeserializer_SingletonNavigationPropertyWithBindingAndValue = "ODataJsonLightResourceDeserializer_SingletonNavigationPropertyWithBindingAndValue"; + internal const string ODataJsonLightResourceDeserializer_PropertyWithoutValueWithUnknownType = "ODataJsonLightResourceDeserializer_PropertyWithoutValueWithUnknownType"; + internal const string ODataJsonLightResourceDeserializer_OperationIsNotActionOrFunction = "ODataJsonLightResourceDeserializer_OperationIsNotActionOrFunction"; + internal const string ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation = "ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation"; + internal const string ODataJsonLightResourceDeserializer_OperationMissingTargetProperty = "ODataJsonLightResourceDeserializer_OperationMissingTargetProperty"; + internal const string ODataJsonLightResourceDeserializer_MetadataReferencePropertyInRequest = "ODataJsonLightResourceDeserializer_MetadataReferencePropertyInRequest"; + internal const string ODataJsonLightValidationUtils_OperationPropertyCannotBeNull = "ODataJsonLightValidationUtils_OperationPropertyCannotBeNull"; + internal const string ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported = "ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported"; + internal const string ODataJsonLightDeserializer_RelativeUriUsedWithouODataMetadataAnnotation = "ODataJsonLightDeserializer_RelativeUriUsedWithouODataMetadataAnnotation"; + internal const string ODataJsonLightResourceMetadataContext_MetadataAnnotationMustBeInPayload = "ODataJsonLightResourceMetadataContext_MetadataAnnotationMustBeInPayload"; + internal const string ODataJsonLightCollectionDeserializer_ExpectedCollectionPropertyNotFound = "ODataJsonLightCollectionDeserializer_ExpectedCollectionPropertyNotFound"; + internal const string ODataJsonLightCollectionDeserializer_CannotReadCollectionContentStart = "ODataJsonLightCollectionDeserializer_CannotReadCollectionContentStart"; + internal const string ODataJsonLightCollectionDeserializer_CannotReadCollectionEnd = "ODataJsonLightCollectionDeserializer_CannotReadCollectionEnd"; + internal const string ODataJsonLightCollectionDeserializer_InvalidCollectionTypeName = "ODataJsonLightCollectionDeserializer_InvalidCollectionTypeName"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkMustBeObjectValue = "ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkMustBeObjectValue"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLink = "ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLink"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_InvalidAnnotationInEntityReferenceLink = "ODataJsonLightEntityReferenceLinkDeserializer_InvalidAnnotationInEntityReferenceLink"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyInEntityReferenceLink = "ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyInEntityReferenceLink"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_MissingEntityReferenceLinkProperty = "ODataJsonLightEntityReferenceLinkDeserializer_MissingEntityReferenceLinkProperty"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_MultipleUriPropertiesInEntityReferenceLink = "ODataJsonLightEntityReferenceLinkDeserializer_MultipleUriPropertiesInEntityReferenceLink"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkUrlCannotBeNull = "ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkUrlCannotBeNull"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLinks = "ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLinks"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_InvalidEntityReferenceLinksPropertyFound = "ODataJsonLightEntityReferenceLinkDeserializer_InvalidEntityReferenceLinksPropertyFound"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyAnnotationInEntityReferenceLinks = "ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyAnnotationInEntityReferenceLinks"; + internal const string ODataJsonLightEntityReferenceLinkDeserializer_ExpectedEntityReferenceLinksPropertyNotFound = "ODataJsonLightEntityReferenceLinkDeserializer_ExpectedEntityReferenceLinksPropertyNotFound"; + internal const string ODataJsonOperationsDeserializerUtils_OperationPropertyCannotBeNull = "ODataJsonOperationsDeserializerUtils_OperationPropertyCannotBeNull"; + internal const string ODataJsonOperationsDeserializerUtils_OperationsPropertyMustHaveObjectValue = "ODataJsonOperationsDeserializerUtils_OperationsPropertyMustHaveObjectValue"; + internal const string ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocument = "ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocument"; + internal const string ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement = "ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement"; + internal const string ODataJsonLightServiceDocumentDeserializer_MissingValuePropertyInServiceDocument = "ODataJsonLightServiceDocumentDeserializer_MissingValuePropertyInServiceDocument"; + internal const string ODataJsonLightServiceDocumentDeserializer_MissingRequiredPropertyInServiceDocumentElement = "ODataJsonLightServiceDocumentDeserializer_MissingRequiredPropertyInServiceDocumentElement"; + internal const string ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocument = "ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocument"; + internal const string ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocument = "ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocument"; + internal const string ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocumentElement = "ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocumentElement"; + internal const string ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocumentElement = "ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocumentElement"; + internal const string ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocumentElement = "ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocumentElement"; + internal const string ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocument = "ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocument"; + internal const string ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationWithoutProperty = "ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationWithoutProperty"; + internal const string ODataJsonLightParameterDeserializer_PropertyAnnotationForParameters = "ODataJsonLightParameterDeserializer_PropertyAnnotationForParameters"; + internal const string ODataJsonLightParameterDeserializer_PropertyAnnotationWithoutPropertyForParameters = "ODataJsonLightParameterDeserializer_PropertyAnnotationWithoutPropertyForParameters"; + internal const string ODataJsonLightParameterDeserializer_UnsupportedPrimitiveParameterType = "ODataJsonLightParameterDeserializer_UnsupportedPrimitiveParameterType"; + internal const string ODataJsonLightParameterDeserializer_NullCollectionExpected = "ODataJsonLightParameterDeserializer_NullCollectionExpected"; + internal const string ODataJsonLightParameterDeserializer_UnsupportedParameterTypeKind = "ODataJsonLightParameterDeserializer_UnsupportedParameterTypeKind"; + internal const string SelectedPropertiesNode_StarSegmentNotLastSegment = "SelectedPropertiesNode_StarSegmentNotLastSegment"; + internal const string SelectedPropertiesNode_StarSegmentAfterTypeSegment = "SelectedPropertiesNode_StarSegmentAfterTypeSegment"; + internal const string ODataJsonLightErrorDeserializer_PropertyAnnotationNotAllowedInErrorPayload = "ODataJsonLightErrorDeserializer_PropertyAnnotationNotAllowedInErrorPayload"; + internal const string ODataJsonLightErrorDeserializer_InstanceAnnotationNotAllowedInErrorPayload = "ODataJsonLightErrorDeserializer_InstanceAnnotationNotAllowedInErrorPayload"; + internal const string ODataJsonLightErrorDeserializer_PropertyAnnotationWithoutPropertyForError = "ODataJsonLightErrorDeserializer_PropertyAnnotationWithoutPropertyForError"; + internal const string ODataJsonLightErrorDeserializer_TopLevelErrorValueWithInvalidProperty = "ODataJsonLightErrorDeserializer_TopLevelErrorValueWithInvalidProperty"; + internal const string ODataConventionalUriBuilder_EntityTypeWithNoKeyProperties = "ODataConventionalUriBuilder_EntityTypeWithNoKeyProperties"; + internal const string ODataConventionalUriBuilder_NullKeyValue = "ODataConventionalUriBuilder_NullKeyValue"; + internal const string ODataResourceMetadataContext_EntityTypeWithNoKeyProperties = "ODataResourceMetadataContext_EntityTypeWithNoKeyProperties"; + internal const string ODataResourceMetadataContext_NullKeyValue = "ODataResourceMetadataContext_NullKeyValue"; + internal const string ODataResourceMetadataContext_KeyOrETagValuesMustBePrimitiveValues = "ODataResourceMetadataContext_KeyOrETagValuesMustBePrimitiveValues"; + internal const string ODataResource_PropertyValueCannotBeODataResourceValue = "ODataResource_PropertyValueCannotBeODataResourceValue"; + internal const string EdmValueUtils_NonPrimitiveValue = "EdmValueUtils_NonPrimitiveValue"; + internal const string EdmValueUtils_PropertyDoesntExist = "EdmValueUtils_PropertyDoesntExist"; + internal const string ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromNull = "ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromNull"; + internal const string ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromUnsupportedValueType = "ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromUnsupportedValueType"; + internal const string ODataInstanceAnnotation_NeedPeriodInName = "ODataInstanceAnnotation_NeedPeriodInName"; + internal const string ODataInstanceAnnotation_ReservedNamesNotAllowed = "ODataInstanceAnnotation_ReservedNamesNotAllowed"; + internal const string ODataInstanceAnnotation_BadTermName = "ODataInstanceAnnotation_BadTermName"; + internal const string ODataInstanceAnnotation_ValueCannotBeODataStreamReferenceValue = "ODataInstanceAnnotation_ValueCannotBeODataStreamReferenceValue"; + internal const string ODataJsonLightValueSerializer_MissingTypeNameOnCollection = "ODataJsonLightValueSerializer_MissingTypeNameOnCollection"; + internal const string ODataJsonLightValueSerializer_MissingRawValueOnUntyped = "ODataJsonLightValueSerializer_MissingRawValueOnUntyped"; + internal const string AtomInstanceAnnotation_MissingTermAttributeOnAnnotationElement = "AtomInstanceAnnotation_MissingTermAttributeOnAnnotationElement"; + internal const string AtomInstanceAnnotation_AttributeValueNotationUsedWithIncompatibleType = "AtomInstanceAnnotation_AttributeValueNotationUsedWithIncompatibleType"; + internal const string AtomInstanceAnnotation_AttributeValueNotationUsedOnNonEmptyElement = "AtomInstanceAnnotation_AttributeValueNotationUsedOnNonEmptyElement"; + internal const string AtomInstanceAnnotation_MultipleAttributeValueNotationAttributes = "AtomInstanceAnnotation_MultipleAttributeValueNotationAttributes"; + internal const string AnnotationFilterPattern_InvalidPatternMissingDot = "AnnotationFilterPattern_InvalidPatternMissingDot"; + internal const string AnnotationFilterPattern_InvalidPatternEmptySegment = "AnnotationFilterPattern_InvalidPatternEmptySegment"; + internal const string AnnotationFilterPattern_InvalidPatternWildCardInSegment = "AnnotationFilterPattern_InvalidPatternWildCardInSegment"; + internal const string AnnotationFilterPattern_InvalidPatternWildCardMustBeInLastSegment = "AnnotationFilterPattern_InvalidPatternWildCardMustBeInLastSegment"; + internal const string SyntacticTree_UriMustBeAbsolute = "SyntacticTree_UriMustBeAbsolute"; + internal const string SyntacticTree_MaxDepthInvalid = "SyntacticTree_MaxDepthInvalid"; + internal const string SyntacticTree_InvalidSkipQueryOptionValue = "SyntacticTree_InvalidSkipQueryOptionValue"; + internal const string SyntacticTree_InvalidTopQueryOptionValue = "SyntacticTree_InvalidTopQueryOptionValue"; + internal const string SyntacticTree_InvalidCountQueryOptionValue = "SyntacticTree_InvalidCountQueryOptionValue"; + internal const string SyntacticTree_InvalidIndexQueryOptionValue = "SyntacticTree_InvalidIndexQueryOptionValue"; + internal const string QueryOptionUtils_QueryParameterMustBeSpecifiedOnce = "QueryOptionUtils_QueryParameterMustBeSpecifiedOnce"; + internal const string UriBuilder_NotSupportedClrLiteral = "UriBuilder_NotSupportedClrLiteral"; + internal const string UriBuilder_NotSupportedQueryToken = "UriBuilder_NotSupportedQueryToken"; + internal const string UriQueryExpressionParser_TooDeep = "UriQueryExpressionParser_TooDeep"; + internal const string UriQueryExpressionParser_ExpressionExpected = "UriQueryExpressionParser_ExpressionExpected"; + internal const string UriQueryExpressionParser_OpenParenExpected = "UriQueryExpressionParser_OpenParenExpected"; + internal const string UriQueryExpressionParser_CloseParenOrCommaExpected = "UriQueryExpressionParser_CloseParenOrCommaExpected"; + internal const string UriQueryExpressionParser_CloseParenOrOperatorExpected = "UriQueryExpressionParser_CloseParenOrOperatorExpected"; + internal const string UriQueryExpressionParser_CannotCreateStarTokenFromNonStar = "UriQueryExpressionParser_CannotCreateStarTokenFromNonStar"; + internal const string UriQueryExpressionParser_RangeVariableAlreadyDeclared = "UriQueryExpressionParser_RangeVariableAlreadyDeclared"; + internal const string UriQueryExpressionParser_AsExpected = "UriQueryExpressionParser_AsExpected"; + internal const string UriQueryExpressionParser_WithExpected = "UriQueryExpressionParser_WithExpected"; + internal const string UriQueryExpressionParser_UnrecognizedWithMethod = "UriQueryExpressionParser_UnrecognizedWithMethod"; + internal const string UriQueryExpressionParser_PropertyPathExpected = "UriQueryExpressionParser_PropertyPathExpected"; + internal const string UriQueryExpressionParser_KeywordOrIdentifierExpected = "UriQueryExpressionParser_KeywordOrIdentifierExpected"; + internal const string UriQueryExpressionParser_InnerMostExpandRequireFilter = "UriQueryExpressionParser_InnerMostExpandRequireFilter"; + internal const string UriQueryPathParser_RequestUriDoesNotHaveTheCorrectBaseUri = "UriQueryPathParser_RequestUriDoesNotHaveTheCorrectBaseUri"; + internal const string UriQueryPathParser_SyntaxError = "UriQueryPathParser_SyntaxError"; + internal const string UriQueryPathParser_TooManySegments = "UriQueryPathParser_TooManySegments"; + internal const string UriQueryPathParser_InvalidEscapeUri = "UriQueryPathParser_InvalidEscapeUri"; + internal const string UriUtils_DateTimeOffsetInvalidFormat = "UriUtils_DateTimeOffsetInvalidFormat"; + internal const string SelectionItemBinder_NonNavigationPathToken = "SelectionItemBinder_NonNavigationPathToken"; + internal const string MetadataBinder_UnsupportedQueryTokenKind = "MetadataBinder_UnsupportedQueryTokenKind"; + internal const string MetadataBinder_PropertyNotDeclared = "MetadataBinder_PropertyNotDeclared"; + internal const string MetadataBinder_PropertyNotDeclaredOrNotKeyInKeyValue = "MetadataBinder_PropertyNotDeclaredOrNotKeyInKeyValue"; + internal const string MetadataBinder_QualifiedFunctionNameWithParametersNotDeclared = "MetadataBinder_QualifiedFunctionNameWithParametersNotDeclared"; + internal const string MetadataBinder_UnnamedKeyValueOnTypeWithMultipleKeyProperties = "MetadataBinder_UnnamedKeyValueOnTypeWithMultipleKeyProperties"; + internal const string MetadataBinder_DuplicitKeyPropertyInKeyValues = "MetadataBinder_DuplicitKeyPropertyInKeyValues"; + internal const string MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues = "MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues"; + internal const string MetadataBinder_CannotConvertToType = "MetadataBinder_CannotConvertToType"; + internal const string MetadataBinder_FilterExpressionNotSingleValue = "MetadataBinder_FilterExpressionNotSingleValue"; + internal const string MetadataBinder_OrderByExpressionNotSingleValue = "MetadataBinder_OrderByExpressionNotSingleValue"; + internal const string MetadataBinder_PropertyAccessWithoutParentParameter = "MetadataBinder_PropertyAccessWithoutParentParameter"; + internal const string MetadataBinder_BinaryOperatorOperandNotSingleValue = "MetadataBinder_BinaryOperatorOperandNotSingleValue"; + internal const string MetadataBinder_UnaryOperatorOperandNotSingleValue = "MetadataBinder_UnaryOperatorOperandNotSingleValue"; + internal const string MetadataBinder_LeftOperandNotSingleValue = "MetadataBinder_LeftOperandNotSingleValue"; + internal const string MetadataBinder_RightOperandNotCollectionValue = "MetadataBinder_RightOperandNotCollectionValue"; + internal const string MetadataBinder_PropertyAccessSourceNotSingleValue = "MetadataBinder_PropertyAccessSourceNotSingleValue"; + internal const string MetadataBinder_IncompatibleOperandsError = "MetadataBinder_IncompatibleOperandsError"; + internal const string MetadataBinder_IncompatibleOperandError = "MetadataBinder_IncompatibleOperandError"; + internal const string MetadataBinder_UnknownFunction = "MetadataBinder_UnknownFunction"; + internal const string MetadataBinder_FunctionArgumentNotSingleValue = "MetadataBinder_FunctionArgumentNotSingleValue"; + internal const string MetadataBinder_NoApplicableFunctionFound = "MetadataBinder_NoApplicableFunctionFound"; + internal const string MetadataBinder_BoundNodeCannotBeNull = "MetadataBinder_BoundNodeCannotBeNull"; + internal const string MetadataBinder_TopRequiresNonNegativeInteger = "MetadataBinder_TopRequiresNonNegativeInteger"; + internal const string MetadataBinder_SkipRequiresNonNegativeInteger = "MetadataBinder_SkipRequiresNonNegativeInteger"; + internal const string MetadataBinder_QueryOptionsBindStateCannotBeNull = "MetadataBinder_QueryOptionsBindStateCannotBeNull"; + internal const string MetadataBinder_QueryOptionsBindMethodCannotBeNull = "MetadataBinder_QueryOptionsBindMethodCannotBeNull"; + internal const string MetadataBinder_HierarchyNotFollowed = "MetadataBinder_HierarchyNotFollowed"; + internal const string MetadataBinder_LambdaParentMustBeCollection = "MetadataBinder_LambdaParentMustBeCollection"; + internal const string MetadataBinder_ParameterNotInScope = "MetadataBinder_ParameterNotInScope"; + internal const string MetadataBinder_NavigationPropertyNotFollowingSingleEntityType = "MetadataBinder_NavigationPropertyNotFollowingSingleEntityType"; + internal const string MetadataBinder_AnyAllExpressionNotSingleValue = "MetadataBinder_AnyAllExpressionNotSingleValue"; + internal const string MetadataBinder_CastOrIsOfExpressionWithWrongNumberOfOperands = "MetadataBinder_CastOrIsOfExpressionWithWrongNumberOfOperands"; + internal const string MetadataBinder_CastOrIsOfFunctionWithoutATypeArgument = "MetadataBinder_CastOrIsOfFunctionWithoutATypeArgument"; + internal const string MetadataBinder_CastOrIsOfCollectionsNotSupported = "MetadataBinder_CastOrIsOfCollectionsNotSupported"; + internal const string MetadataBinder_CollectionOpenPropertiesNotSupportedInThisRelease = "MetadataBinder_CollectionOpenPropertiesNotSupportedInThisRelease"; + internal const string MetadataBinder_IllegalSegmentType = "MetadataBinder_IllegalSegmentType"; + internal const string MetadataBinder_QueryOptionNotApplicable = "MetadataBinder_QueryOptionNotApplicable"; + internal const string StringItemShouldBeQuoted = "StringItemShouldBeQuoted"; + internal const string StreamItemInvalidPrimitiveKind = "StreamItemInvalidPrimitiveKind"; + internal const string ApplyBinder_AggregateExpressionIncompatibleTypeForMethod = "ApplyBinder_AggregateExpressionIncompatibleTypeForMethod"; + internal const string ApplyBinder_UnsupportedAggregateMethod = "ApplyBinder_UnsupportedAggregateMethod"; + internal const string ApplyBinder_UnsupportedAggregateKind = "ApplyBinder_UnsupportedAggregateKind"; + internal const string ApplyBinder_AggregateExpressionNotSingleValue = "ApplyBinder_AggregateExpressionNotSingleValue"; + internal const string ApplyBinder_GroupByPropertyNotPropertyAccessValue = "ApplyBinder_GroupByPropertyNotPropertyAccessValue"; + internal const string ApplyBinder_UnsupportedType = "ApplyBinder_UnsupportedType"; + internal const string ApplyBinder_UnsupportedGroupByChild = "ApplyBinder_UnsupportedGroupByChild"; + internal const string AggregateTransformationNode_UnsupportedAggregateExpressions = "AggregateTransformationNode_UnsupportedAggregateExpressions"; + internal const string FunctionCallBinder_CannotFindASuitableOverload = "FunctionCallBinder_CannotFindASuitableOverload"; + internal const string FunctionCallBinder_UriFunctionMustHaveHaveNullParent = "FunctionCallBinder_UriFunctionMustHaveHaveNullParent"; + internal const string FunctionCallBinder_CallingFunctionOnOpenProperty = "FunctionCallBinder_CallingFunctionOnOpenProperty"; + internal const string FunctionCallParser_DuplicateParameterOrEntityKeyName = "FunctionCallParser_DuplicateParameterOrEntityKeyName"; + internal const string ODataUriParser_InvalidCount = "ODataUriParser_InvalidCount"; + internal const string CastBinder_ChildTypeIsNotEntity = "CastBinder_ChildTypeIsNotEntity"; + internal const string CastBinder_EnumOnlyCastToOrFromString = "CastBinder_EnumOnlyCastToOrFromString"; + internal const string Binder_IsNotValidEnumConstant = "Binder_IsNotValidEnumConstant"; + internal const string BatchReferenceSegment_InvalidContentID = "BatchReferenceSegment_InvalidContentID"; + internal const string SelectExpandBinder_UnknownPropertyType = "SelectExpandBinder_UnknownPropertyType"; + internal const string SelectionItemBinder_NoExpandForSelectedProperty = "SelectionItemBinder_NoExpandForSelectedProperty"; + internal const string SelectExpandPathBinder_FollowNonTypeSegment = "SelectExpandPathBinder_FollowNonTypeSegment"; + internal const string SelectPropertyVisitor_SystemTokenInSelect = "SelectPropertyVisitor_SystemTokenInSelect"; + internal const string SelectPropertyVisitor_DisparateTypeSegmentsInSelectExpand = "SelectPropertyVisitor_DisparateTypeSegmentsInSelectExpand"; + internal const string SelectBinder_MultiLevelPathInSelect = "SelectBinder_MultiLevelPathInSelect"; + internal const string ExpandItemBinder_TraversingANonNormalizedTree = "ExpandItemBinder_TraversingANonNormalizedTree"; + internal const string ExpandItemBinder_CannotFindType = "ExpandItemBinder_CannotFindType"; + internal const string ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty = "ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty"; + internal const string ExpandItemBinder_TypeSegmentNotFollowedByPath = "ExpandItemBinder_TypeSegmentNotFollowedByPath"; + internal const string ExpandItemBinder_PathTooDeep = "ExpandItemBinder_PathTooDeep"; + internal const string ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath = "ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath"; + internal const string ExpandItemBinder_LevelsNotAllowedOnIncompatibleRelatedType = "ExpandItemBinder_LevelsNotAllowedOnIncompatibleRelatedType"; + internal const string ExpandItemBinder_InvaidSegmentInExpand = "ExpandItemBinder_InvaidSegmentInExpand"; + internal const string Nodes_CollectionNavigationNode_MustHaveSingleMultiplicity = "Nodes_CollectionNavigationNode_MustHaveSingleMultiplicity"; + internal const string Nodes_NonentityParameterQueryNodeWithEntityType = "Nodes_NonentityParameterQueryNodeWithEntityType"; + internal const string Nodes_CollectionNavigationNode_MustHaveManyMultiplicity = "Nodes_CollectionNavigationNode_MustHaveManyMultiplicity"; + internal const string Nodes_PropertyAccessShouldBeNonEntityProperty = "Nodes_PropertyAccessShouldBeNonEntityProperty"; + internal const string Nodes_PropertyAccessTypeShouldNotBeCollection = "Nodes_PropertyAccessTypeShouldNotBeCollection"; + internal const string Nodes_PropertyAccessTypeMustBeCollection = "Nodes_PropertyAccessTypeMustBeCollection"; + internal const string Nodes_NonStaticEntitySetExpressionsAreNotSupportedInThisRelease = "Nodes_NonStaticEntitySetExpressionsAreNotSupportedInThisRelease"; + internal const string Nodes_CollectionFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum = "Nodes_CollectionFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum"; + internal const string Nodes_EntityCollectionFunctionCallNode_ItemTypeMustBeAnEntity = "Nodes_EntityCollectionFunctionCallNode_ItemTypeMustBeAnEntity"; + internal const string Nodes_SingleValueFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum = "Nodes_SingleValueFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum"; + internal const string Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType = "Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType"; + internal const string ExpandTreeNormalizer_NonPathInPropertyChain = "ExpandTreeNormalizer_NonPathInPropertyChain"; + internal const string UriExpandParser_TermIsNotValidForStar = "UriExpandParser_TermIsNotValidForStar"; + internal const string UriExpandParser_TermIsNotValidForStarRef = "UriExpandParser_TermIsNotValidForStarRef"; + internal const string UriExpandParser_ParentEntityIsNull = "UriExpandParser_ParentEntityIsNull"; + internal const string UriExpandParser_TermWithMultipleStarNotAllowed = "UriExpandParser_TermWithMultipleStarNotAllowed"; + internal const string UriSelectParser_TermIsNotValid = "UriSelectParser_TermIsNotValid"; + internal const string UriSelectParser_InvalidTopOption = "UriSelectParser_InvalidTopOption"; + internal const string UriSelectParser_InvalidSkipOption = "UriSelectParser_InvalidSkipOption"; + internal const string UriSelectParser_InvalidCountOption = "UriSelectParser_InvalidCountOption"; + internal const string UriSelectParser_InvalidLevelsOption = "UriSelectParser_InvalidLevelsOption"; + internal const string UriSelectParser_SystemTokenInSelectExpand = "UriSelectParser_SystemTokenInSelectExpand"; + internal const string UriParser_MissingExpandOption = "UriParser_MissingExpandOption"; + internal const string UriParser_MissingSelectOption = "UriParser_MissingSelectOption"; + internal const string UriParser_RelativeUriMustBeRelative = "UriParser_RelativeUriMustBeRelative"; + internal const string UriParser_NeedServiceRootForThisOverload = "UriParser_NeedServiceRootForThisOverload"; + internal const string UriParser_UriMustBeAbsolute = "UriParser_UriMustBeAbsolute"; + internal const string UriParser_NegativeLimit = "UriParser_NegativeLimit"; + internal const string UriParser_ExpandCountExceeded = "UriParser_ExpandCountExceeded"; + internal const string UriParser_ExpandDepthExceeded = "UriParser_ExpandDepthExceeded"; + internal const string UriParser_TypeInvalidForSelectExpand = "UriParser_TypeInvalidForSelectExpand"; + internal const string UriParser_ContextHandlerCanNotBeNull = "UriParser_ContextHandlerCanNotBeNull"; + internal const string UriParserMetadata_MultipleMatchingPropertiesFound = "UriParserMetadata_MultipleMatchingPropertiesFound"; + internal const string UriParserMetadata_MultipleMatchingNavigationSourcesFound = "UriParserMetadata_MultipleMatchingNavigationSourcesFound"; + internal const string UriParserMetadata_MultipleMatchingTypesFound = "UriParserMetadata_MultipleMatchingTypesFound"; + internal const string UriParserMetadata_MultipleMatchingKeysFound = "UriParserMetadata_MultipleMatchingKeysFound"; + internal const string UriParserMetadata_MultipleMatchingParametersFound = "UriParserMetadata_MultipleMatchingParametersFound"; + internal const string PathParser_EntityReferenceNotSupported = "PathParser_EntityReferenceNotSupported"; + internal const string PathParser_CannotUseValueOnCollection = "PathParser_CannotUseValueOnCollection"; + internal const string PathParser_TypeMustBeRelatedToSet = "PathParser_TypeMustBeRelatedToSet"; + internal const string PathParser_TypeCastOnlyAllowedAfterStructuralCollection = "PathParser_TypeCastOnlyAllowedAfterStructuralCollection"; + internal const string PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint = "PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint"; + internal const string ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink = "ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink"; + internal const string ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty = "ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty"; + internal const string ODataExpandPath_InvalidExpandPathSegment = "ODataExpandPath_InvalidExpandPathSegment"; + internal const string ODataSelectPath_CannotOnlyHaveTypeSegment = "ODataSelectPath_CannotOnlyHaveTypeSegment"; + internal const string ODataSelectPath_InvalidSelectPathSegmentType = "ODataSelectPath_InvalidSelectPathSegmentType"; + internal const string ODataSelectPath_OperationSegmentCanOnlyBeLastSegment = "ODataSelectPath_OperationSegmentCanOnlyBeLastSegment"; + internal const string ODataSelectPath_NavPropSegmentCanOnlyBeLastSegment = "ODataSelectPath_NavPropSegmentCanOnlyBeLastSegment"; + internal const string RequestUriProcessor_TargetEntitySetNotFound = "RequestUriProcessor_TargetEntitySetNotFound"; + internal const string RequestUriProcessor_FoundInvalidFunctionImport = "RequestUriProcessor_FoundInvalidFunctionImport"; + internal const string OperationSegment_ReturnTypeForMultipleOverloads = "OperationSegment_ReturnTypeForMultipleOverloads"; + internal const string OperationSegment_CannotReturnNull = "OperationSegment_CannotReturnNull"; + internal const string FunctionOverloadResolver_NoSingleMatchFound = "FunctionOverloadResolver_NoSingleMatchFound"; + internal const string FunctionOverloadResolver_MultipleActionOverloads = "FunctionOverloadResolver_MultipleActionOverloads"; + internal const string FunctionOverloadResolver_MultipleActionImportOverloads = "FunctionOverloadResolver_MultipleActionImportOverloads"; + internal const string FunctionOverloadResolver_MultipleOperationImportOverloads = "FunctionOverloadResolver_MultipleOperationImportOverloads"; + internal const string FunctionOverloadResolver_MultipleOperationOverloads = "FunctionOverloadResolver_MultipleOperationOverloads"; + internal const string FunctionOverloadResolver_FoundInvalidOperation = "FunctionOverloadResolver_FoundInvalidOperation"; + internal const string FunctionOverloadResolver_FoundInvalidOperationImport = "FunctionOverloadResolver_FoundInvalidOperationImport"; + internal const string CustomUriFunctions_AddCustomUriFunction_BuiltInExistsNotAddingAsOverload = "CustomUriFunctions_AddCustomUriFunction_BuiltInExistsNotAddingAsOverload"; + internal const string CustomUriFunctions_AddCustomUriFunction_BuiltInExistsFullSignature = "CustomUriFunctions_AddCustomUriFunction_BuiltInExistsFullSignature"; + internal const string CustomUriFunctions_AddCustomUriFunction_CustomFunctionOverloadExists = "CustomUriFunctions_AddCustomUriFunction_CustomFunctionOverloadExists"; + internal const string RequestUriProcessor_InvalidValueForEntitySegment = "RequestUriProcessor_InvalidValueForEntitySegment"; + internal const string RequestUriProcessor_InvalidValueForKeySegment = "RequestUriProcessor_InvalidValueForKeySegment"; + internal const string RequestUriProcessor_CannotApplyFilterOnSingleEntities = "RequestUriProcessor_CannotApplyFilterOnSingleEntities"; + internal const string RequestUriProcessor_CannotApplyEachOnSingleEntities = "RequestUriProcessor_CannotApplyEachOnSingleEntities"; + internal const string RequestUriProcessor_FilterPathSegmentSyntaxError = "RequestUriProcessor_FilterPathSegmentSyntaxError"; + internal const string RequestUriProcessor_NoNavigationSourceFound = "RequestUriProcessor_NoNavigationSourceFound"; + internal const string RequestUriProcessor_OnlySingleOperationCanFollowEachPathSegment = "RequestUriProcessor_OnlySingleOperationCanFollowEachPathSegment"; + internal const string RequestUriProcessor_EmptySegmentInRequestUrl = "RequestUriProcessor_EmptySegmentInRequestUrl"; + internal const string RequestUriProcessor_SyntaxError = "RequestUriProcessor_SyntaxError"; + internal const string RequestUriProcessor_CountOnRoot = "RequestUriProcessor_CountOnRoot"; + internal const string RequestUriProcessor_FilterOnRoot = "RequestUriProcessor_FilterOnRoot"; + internal const string RequestUriProcessor_EachOnRoot = "RequestUriProcessor_EachOnRoot"; + internal const string RequestUriProcessor_RefOnRoot = "RequestUriProcessor_RefOnRoot"; + internal const string RequestUriProcessor_MustBeLeafSegment = "RequestUriProcessor_MustBeLeafSegment"; + internal const string RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment = "RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment"; + internal const string RequestUriProcessor_MissingSegmentAfterLink = "RequestUriProcessor_MissingSegmentAfterLink"; + internal const string RequestUriProcessor_CountNotSupported = "RequestUriProcessor_CountNotSupported"; + internal const string RequestUriProcessor_CannotQueryCollections = "RequestUriProcessor_CannotQueryCollections"; + internal const string RequestUriProcessor_SegmentDoesNotSupportKeyPredicates = "RequestUriProcessor_SegmentDoesNotSupportKeyPredicates"; + internal const string RequestUriProcessor_ValueSegmentAfterScalarPropertySegment = "RequestUriProcessor_ValueSegmentAfterScalarPropertySegment"; + internal const string RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType = "RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType"; + internal const string OpenNavigationPropertiesNotSupportedOnOpenTypes = "OpenNavigationPropertiesNotSupportedOnOpenTypes"; + internal const string BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation = "BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation"; + internal const string DataServiceConfiguration_ResponseVersionIsBiggerThanProtocolVersion = "DataServiceConfiguration_ResponseVersionIsBiggerThanProtocolVersion"; + internal const string BadRequest_KeyCountMismatch = "BadRequest_KeyCountMismatch"; + internal const string RequestUriProcessor_KeysMustBeNamed = "RequestUriProcessor_KeysMustBeNamed"; + internal const string RequestUriProcessor_ResourceNotFound = "RequestUriProcessor_ResourceNotFound"; + internal const string RequestUriProcessor_BatchedActionOnEntityCreatedInSameChangeset = "RequestUriProcessor_BatchedActionOnEntityCreatedInSameChangeset"; + internal const string RequestUriProcessor_Forbidden = "RequestUriProcessor_Forbidden"; + internal const string RequestUriProcessor_OperationSegmentBoundToANonEntityType = "RequestUriProcessor_OperationSegmentBoundToANonEntityType"; + internal const string RequestUriProcessor_NoBoundEscapeFunctionSupported = "RequestUriProcessor_NoBoundEscapeFunctionSupported"; + internal const string RequestUriProcessor_EscapeFunctionMustHaveOneStringParameter = "RequestUriProcessor_EscapeFunctionMustHaveOneStringParameter"; + internal const string General_InternalError = "General_InternalError"; + internal const string ExceptionUtils_CheckIntegerNotNegative = "ExceptionUtils_CheckIntegerNotNegative"; + internal const string ExceptionUtils_CheckIntegerPositive = "ExceptionUtils_CheckIntegerPositive"; + internal const string ExceptionUtils_CheckLongPositive = "ExceptionUtils_CheckLongPositive"; + internal const string ExceptionUtils_ArgumentStringNullOrEmpty = "ExceptionUtils_ArgumentStringNullOrEmpty"; + internal const string ExpressionToken_OnlyRefAllowWithStarInExpand = "ExpressionToken_OnlyRefAllowWithStarInExpand"; + internal const string ExpressionToken_NoPropAllowedAfterRef = "ExpressionToken_NoPropAllowedAfterRef"; + internal const string ExpressionToken_NoSegmentAllowedBeforeStarInExpand = "ExpressionToken_NoSegmentAllowedBeforeStarInExpand"; + internal const string ExpressionToken_IdentifierExpected = "ExpressionToken_IdentifierExpected"; + internal const string ExpressionLexer_UnterminatedStringLiteral = "ExpressionLexer_UnterminatedStringLiteral"; + internal const string ExpressionLexer_InvalidCharacter = "ExpressionLexer_InvalidCharacter"; + internal const string ExpressionLexer_SyntaxError = "ExpressionLexer_SyntaxError"; + internal const string ExpressionLexer_UnterminatedLiteral = "ExpressionLexer_UnterminatedLiteral"; + internal const string ExpressionLexer_DigitExpected = "ExpressionLexer_DigitExpected"; + internal const string ExpressionLexer_UnbalancedBracketExpression = "ExpressionLexer_UnbalancedBracketExpression"; + internal const string ExpressionLexer_InvalidNumericString = "ExpressionLexer_InvalidNumericString"; + internal const string ExpressionLexer_InvalidEscapeSequence = "ExpressionLexer_InvalidEscapeSequence"; + internal const string UriQueryExpressionParser_UnrecognizedLiteral = "UriQueryExpressionParser_UnrecognizedLiteral"; + internal const string UriQueryExpressionParser_UnrecognizedLiteralWithReason = "UriQueryExpressionParser_UnrecognizedLiteralWithReason"; + internal const string UriPrimitiveTypeParsers_FailedToParseTextToPrimitiveValue = "UriPrimitiveTypeParsers_FailedToParseTextToPrimitiveValue"; + internal const string UriPrimitiveTypeParsers_FailedToParseStringToGeography = "UriPrimitiveTypeParsers_FailedToParseStringToGeography"; + internal const string UriCustomTypeParsers_AddCustomUriTypeParserAlreadyExists = "UriCustomTypeParsers_AddCustomUriTypeParserAlreadyExists"; + internal const string UriCustomTypeParsers_AddCustomUriTypeParserEdmTypeExists = "UriCustomTypeParsers_AddCustomUriTypeParserEdmTypeExists"; + internal const string UriParserHelper_InvalidPrefixLiteral = "UriParserHelper_InvalidPrefixLiteral"; + internal const string CustomUriTypePrefixLiterals_AddCustomUriTypePrefixLiteralAlreadyExists = "CustomUriTypePrefixLiterals_AddCustomUriTypePrefixLiteralAlreadyExists"; + internal const string ValueParser_InvalidDuration = "ValueParser_InvalidDuration"; + internal const string PlatformHelper_DateTimeOffsetMustContainTimeZone = "PlatformHelper_DateTimeOffsetMustContainTimeZone"; + internal const string JsonReader_UnexpectedComma = "JsonReader_UnexpectedComma"; + internal const string JsonReader_ArrayClosureMismatch = "JsonReader_ArrayClosureMismatch"; + internal const string JsonReader_MultipleTopLevelValues = "JsonReader_MultipleTopLevelValues"; + internal const string JsonReader_EndOfInputWithOpenScope = "JsonReader_EndOfInputWithOpenScope"; + internal const string JsonReader_UnexpectedToken = "JsonReader_UnexpectedToken"; + internal const string JsonReader_UnrecognizedToken = "JsonReader_UnrecognizedToken"; + internal const string JsonReader_MissingColon = "JsonReader_MissingColon"; + internal const string JsonReader_UnrecognizedEscapeSequence = "JsonReader_UnrecognizedEscapeSequence"; + internal const string JsonReader_UnexpectedEndOfString = "JsonReader_UnexpectedEndOfString"; + internal const string JsonReader_InvalidNumberFormat = "JsonReader_InvalidNumberFormat"; + internal const string JsonReader_InvalidBinaryFormat = "JsonReader_InvalidBinaryFormat"; + internal const string JsonReader_MissingComma = "JsonReader_MissingComma"; + internal const string JsonReader_InvalidPropertyNameOrUnexpectedComma = "JsonReader_InvalidPropertyNameOrUnexpectedComma"; + internal const string JsonReader_MaxBufferReached = "JsonReader_MaxBufferReached"; + internal const string JsonReader_CannotAccessValueInStreamState = "JsonReader_CannotAccessValueInStreamState"; + internal const string JsonReader_CannotCallReadInStreamState = "JsonReader_CannotCallReadInStreamState"; + internal const string JsonReader_CannotCreateReadStream = "JsonReader_CannotCreateReadStream"; + internal const string JsonReader_CannotCreateTextReader = "JsonReader_CannotCreateTextReader"; + internal const string JsonReaderExtensions_UnexpectedNodeDetected = "JsonReaderExtensions_UnexpectedNodeDetected"; + internal const string JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName = "JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName"; + internal const string JsonReaderExtensions_CannotReadPropertyValueAsString = "JsonReaderExtensions_CannotReadPropertyValueAsString"; + internal const string JsonReaderExtensions_CannotReadValueAsString = "JsonReaderExtensions_CannotReadValueAsString"; + internal const string JsonReaderExtensions_CannotReadValueAsDouble = "JsonReaderExtensions_CannotReadValueAsDouble"; + internal const string JsonReaderExtensions_UnexpectedInstanceAnnotationName = "JsonReaderExtensions_UnexpectedInstanceAnnotationName"; + internal const string BufferUtils_InvalidBufferOrSize = "BufferUtils_InvalidBufferOrSize"; + internal const string ServiceProviderExtensions_NoServiceRegistered = "ServiceProviderExtensions_NoServiceRegistered"; + + static TextRes loader = null; + ResourceManager resources; + + internal TextRes() { +#if !PORTABLELIB + resources = new System.Resources.ResourceManager("Microsoft.OData.Core", this.GetType().Assembly); +#else + resources = new System.Resources.ResourceManager("Microsoft.OData.Core", this.GetType().GetTypeInfo().Assembly); +#endif + } + + private static TextRes GetLoader() { + if (loader == null) { + TextRes sr = new TextRes(); + Interlocked.CompareExchange(ref loader, sr, null); + } + return loader; + } + + private static CultureInfo Culture { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + public static ResourceManager Resources { + get { + return GetLoader().resources; + } + } + + public static string GetString(string name, params object[] args) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, TextRes.Culture); + + if (args != null && args.Length > 0) { + for (int i = 0; i < args.Length; i ++) { + String value = args[i] as String; + if (value != null && value.Length > 1024) { + args[i] = value.Substring(0, 1024 - 3) + "..."; + } + } + return String.Format(CultureInfo.CurrentCulture, res, args); + } + else { + return res; + } + } + + public static string GetString(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetString(name, TextRes.Culture); + } + + public static string GetString(string name, out bool usedFallback) { + // always false for this version of gensr + usedFallback = false; + return GetString(name); + } +#if !PORTABLELIB + public static object GetObject(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetObject(name, TextRes.Culture); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj new file mode 100644 index 0000000..78be153 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj @@ -0,0 +1,712 @@ + + + + Microsoft.OData.Core + Library + v4.5 + Profile111 + .NETPortable + true + true + false + Microsoft.OData + {989A83CC-B864-4A75-8BF3-5EDA99203A86} + $(AssemblyName).xml + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(DefineConstants);ODATA_CORE;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + true + + + + + + {7D921888-FE03-4C3F-80FE-2F624505461C} + Microsoft.OData.Edm + + + {5D921888-FE03-4C3F-40FE-2F624505461D} + Microsoft.Spatial + + + + + + Microsoft.OData.Core + true + true + internal + true + Microsoft.OData.Core.TextRes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EdmValueParser.cs + + + EdmValueWriter.cs + + + PlatformHelper.cs + + + + false + + + false + + + + + + false + + + false + + + + + TextTemplatingFileGenerator + Microsoft.OData.Core.cs + + + True + True + Microsoft.OData.Core.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Core.cs + + + True + True + Parameterized.Microsoft.OData.Core.tt + true + + + + + + false + + + false + + + $(SuiteBinPath) + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.tt new file mode 100644 index 0000000..3983d56 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.tt @@ -0,0 +1,22 @@ +<#@ include file="..\..\tools\StringResourceGenerator\ResourceClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Core"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // Indicates whether to skip generation of string resource attributes. + public const bool SkipSRAttributes = false; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.OData.Core.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.txt new file mode 100644 index 0000000..55c5fee --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Microsoft.OData.Core.txt @@ -0,0 +1,968 @@ + +; NOTE: don't use \", use ' instead +; NOTE: don't use #, use ; instead for comments +; NOTE: leave the [strings] alone +; See ResourceManager documentation and the ResGen tool. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error Messages + +ExceptionUtils_ArgumentStringEmpty=Value cannot be empty. + +ODataRequestMessage_AsyncNotAvailable=An asynchronous operation was requested on an IODataRequestMessage instance. For asynchronous operations to succeed, the request message instance must implement IODataRequestMessageAsync. +ODataRequestMessage_StreamTaskIsNull=The IODataRequestMessageAsync.GetStreamAsync method returned null. An asynchronous method that returns a task can never return null. +ODataRequestMessage_MessageStreamIsNull=The IODataRequestMessage.GetStream or IODataRequestMessageAsync.GetStreamAsync method returned a null stream value. The message can never return a null stream. +ODataResponseMessage_AsyncNotAvailable=An asynchronous operation was requested on an IODataResponseMessage instance. For asynchronous operations to succeed, the response message instance must implement IODataResponseMessageAsync. +ODataResponseMessage_StreamTaskIsNull=The IODataResponseMessageAsync.GetStreamAsync method returned null. An asynchronous method that returns a task can never return null. +ODataResponseMessage_MessageStreamIsNull=The IODataResponseMessage.GetStream or IODataResponseMessageAsync.GetStreamAsync method returned a null stream value. The message can never return a null stream. + +AsyncBufferedStream_WriterDisposedWithoutFlush=A writer or stream has been disposed with data still in the buffer. You must call Flush or FlushAsync before calling Dispose when some data has already been written. +ODataFormat_AtomFormatObsoleted=ATOM support is obsolete. +ODataOutputContext_UnsupportedPayloadKindForFormat=The format '{0}' does not support writing a payload of kind '{1}'. + +ODataInputContext_UnsupportedPayloadKindForFormat=The format '{0}' does not support reading a payload of kind '{1}'. + +ODataOutputContext_MetadataDocumentUriMissing=The ServiceRoot property in ODataMessageWriterSettings.ODataUri must be set when writing a payload. + +ODataJsonLightSerializer_RelativeUriUsedWithoutMetadataDocumentUriOrMetadata=A relative URI value '{0}' was specified in the data to write, but the metadata document URI or the metadata for the item to be written was not specified for the writer. The metadata document URI and the metadata for the item to be written must be provided to the writer when using relative URI values. + +ODataWriter_RelativeUriUsedWithoutBaseUriSpecified=A relative URI value '{0}' was specified in the data to write, but a base URI was not specified for the writer. A base URI must be set when using relative URI values. +ODataWriter_StreamPropertiesMustBePropertiesOfODataResource=The property '{0}' is a stream property, but it is not a property of an ODataResource instance. In OData, stream properties must be properties of ODataResource instances. + +ODataWriterCore_InvalidStateTransition=An invalid state transition has been detected in an OData writer. Cannot transition from state '{0}' to state '{1}'. +ODataWriterCore_InvalidTransitionFromStart=Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write a resource or a resource set. +ODataWriterCore_InvalidTransitionFromResource=Cannot transition from state '{0}' to state '{1}'. The only valid action in state '{0}' is to write a property or a nested resource. +ODataWriterCore_InvalidTransitionFrom40DeletedResource=Cannot transition from state '{0}' to state '{1}' when writing an OData 4.0 payload. To write content to a deleted resource, please specify ODataVersion 4.01 or greater in MessageWriterSettings. +ODataWriterCore_InvalidTransitionFromNullResource=Cannot transition from state '{0}' to state '{1}'. You must first call ODataWriter.WriteEnd to finish writing a null ODataResource. +ODataWriterCore_InvalidTransitionFromResourceSet=Cannot transition from state '{0}' to state '{1}'. The only valid action in state '{0}' is to write a resource. +ODataWriterCore_InvalidTransitionFromExpandedLink=Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write a resource or a resource set. +ODataWriterCore_InvalidTransitionFromCompleted=Cannot transition from state '{0}' to state '{1}'. Nothing further can be written once the writer has completed. +ODataWriterCore_InvalidTransitionFromError=Cannot transition from state '{0}' to state '{1}'. Nothing can be written once the writer entered the error state. +ODataJsonLightDeltaWriter_InvalidTransitionFromNestedResource=Cannot transition from state '{0}' to state '{1}'. State transition is not allowed while writing an expanded navigation property, complex property or complex collection property. +ODataJsonLightDeltaWriter_InvalidTransitionToNestedResource=Cannot transition from state '{0}' to state '{1}'. Nested resource can only be written within a delta resource. +ODataJsonLightDeltaWriter_WriteStartExpandedResourceSetCalledInInvalidState=WriteStart(expandedResourceSet) was called in an invalid state ('{0}'); WriteStart(expandedResourceSet) is only supported in state 'ExpandedNavigationProperty'. +ODataWriterCore_WriteEndCalledInInvalidState=ODataWriter.WriteEnd was called in an invalid state ('{0}'); WriteEnd is only supported in states 'Resource', 'ResourceSet', 'NavigationLink', and 'NavigationLinkWithContent'. +ODataWriterCore_StreamNotDisposed=ODataWriter.Write or ODataWriter.WriteEnd was called while streaming a value. Stream or TextWriter must be disposed before calling additional methods on ODataWriter. + +ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties=No Id or key properties were found. A resource in a delta response requires an ID or key properties be specified. +ODataWriterCore_QueryCountInRequest=The ODataResourceSet.Count must be null for request payloads. Query counts are only supported in responses. +ODataWriterCore_QueryNextLinkInRequest=The NextPageLink must be null for request payloads. Next page links are only supported in responses. +ODataWriterCore_QueryDeltaLinkInRequest=The DeltaLink must be null for request payloads. Delta links are only supported in responses. +ODataWriterCore_CannotWriteDeltaWithResourceSetWriter=Cannot write a delta deleted resource, link, or deleted link using ODataResourceSetWriter. Please use an ODataDeltaResourceSetWriter. +ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry=Nested content is not allowed in an OData 4.0 deleted entry. For content in deleted entries, please specify OData 4.01 or greater. +ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter=Cannot write a top-level resource set with a writer that was created to write a top-level resource. +ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter=Cannot write a top-level resource with a writer that was created to write a top-level resource set. +ODataWriterCore_SyncCallOnAsyncWriter=A synchronous operation was called on an asynchronous writer. Calls on a writer instance must be either all synchronous or all asynchronous. +ODataWriterCore_AsyncCallOnSyncWriter=An asynchronous operation was called on a synchronous writer. Calls on a writer instance must be either all synchronous or all asynchronous. +ODataWriterCore_EntityReferenceLinkWithoutNavigationLink=An entity reference link was written without a surrounding navigation link. The WriteEntityReferenceLink or WriteEntityReferenceLinkAsync methods can only be used when writing the content of a navigation link. +ODataWriterCore_DeferredLinkInRequest=A deferred link was written into a request. In requests, each nested resource info must have a resource set, resource, or entity reference link written into it. +ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent=More than one item was written into the content of a nested resource. In OData, a nested resource can only contain more than one item in its content when ODataNestedResourceInfo.IsCollection set to true, and the writer is writing a request. +ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet=The ODataResourceSet.DeltaLink property must be null for expanded resource sets. Delta link is not supported on expanded resource sets. +ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement=The Path property in ODataMessageWriterSettings.ODataUri must be set when writing contained elements. + +DuplicatePropertyNamesNotAllowed=Multiple properties with the name '{0}' were detected in a resource or a complex value. In OData, duplicate property names are not allowed. +DuplicateAnnotationNotAllowed=Multiple annotations with the name '{0}' were detected. In OData, duplicate annotations are not allowed. +DuplicateAnnotationForPropertyNotAllowed=Multiple annotations with the name '{0}' were detected for the property with name '{1}'. In OData, duplicate annotations are not allowed. +DuplicateAnnotationForInstanceAnnotationNotAllowed=Multiple annotations with the name '{0}' were detected for the instance annotation with name '{1}'. In OData, duplicate annotations are not allowed. +PropertyAnnotationAfterTheProperty=An annotation with name '{0}' for property '{1}' was detected after the property, or after an annotation for another property. In OData, annotations for a property must be in a single group and must appear before the property they annotate. + +AtomValueUtils_CannotConvertValueToAtomPrimitive=Cannot convert a value of type '{0}' to the string representation of an Atom primitive value. + +ODataJsonWriter_UnsupportedValueType=The value of type '{0}' is not supported and cannot be converted to a JSON representation. + +ODataException_GeneralError=An error occurred while processing the OData message. +ODataErrorException_GeneralError=An error was read from the payload. See the 'Error' property for more details. +ODataUriParserException_GeneralError=An error occurred while parsing part of the URI. + +ODataMessageWriter_WriterAlreadyUsed=The ODataMessageWriter has already been used to write a message payload. An ODataMessageWriter can only be used once to write a payload for a given message. +ODataMessageWriter_EntityReferenceLinksInRequestNotAllowed=Top-level entity reference link collection payloads are not allowed in requests. +ODataMessageWriter_ErrorPayloadInRequest=An error cannot be written to a request payload. Errors are only supported in responses. +ODataMessageWriter_ServiceDocumentInRequest=A service document cannot be written to request payloads. Service documents are only supported in responses. +ODataMessageWriter_MetadataDocumentInRequest=A metadata document cannot be written to request payloads. Metadata documents are only supported in responses. +ODataMessageWriter_DeltaInRequest=Cannot write delta in request payload. +ODataMessageWriter_AsyncInRequest=Cannot write async in request payload. +ODataMessageWriter_CannotWriteTopLevelNull=Cannot write the value 'null' in top level property; return 204 instead. +ODataMessageWriter_CannotWriteNullInRawFormat=Cannot write the value 'null' in raw format. +ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind=Cannot set message headers for the invalid payload kind '{0}'. +ODataMessageWriter_IncompatiblePayloadKinds=The payload kind '{0}' used in the last call to ODataUtils.SetHeadersForPayload is incompatible with the payload being written, which is of kind '{1}'. +ODataMessageWriter_CannotWriteStreamPropertyAsTopLevelProperty=The stream property '{0}' cannot be written to the payload as a top level property. +ODataMessageWriter_WriteErrorAlreadyCalled=The WriteError method or the WriteErrorAsync method on the ODataMessageWriter has already been called to write an error payload. Only a single error payload can be written with each ODataMessageWriter instance. +ODataMessageWriter_CannotWriteInStreamErrorForRawValues=The WriteError method or the WriteErrorAsync method on ODataMessageWriter cannot be called after the WriteValue method or the WriteValueAsync method is called. In OData, writing an in-stream error for raw values is not supported. +ODataMessageWriter_CannotWriteMetadataWithoutModel=No model was specified in the ODataMessageWriterSettings; a model has to be provided in the ODataMessageWriterSettings in order to write a metadata document. +ODataMessageWriter_CannotSpecifyOperationWithoutModel=No model was specified in the ODataMessageWriterSettings; a model has to be provided in the ODataMessageWriterSettings when CreateODataParameterWriter is called with a non-null operation. +ODataMessageWriter_JsonPaddingOnInvalidContentType=A JsonPaddingFunctionName was specified, but the content-type '{0}' is not supported with Json Padding. +ODataMessageWriter_NonCollectionType=The type '{0}' specified as the collection's item type is not primitive, enum or complex. An ODataCollectionWriter can only write collections of primitive, enum or complex values. +ODataMessageWriter_NotAllowedWriteTopLevelPropertyWithResourceValue=Not allowed to write top level property '{0}' with 'ODataResourceValue' or collection of resource value. + +ODataMessageWriterSettings_MessageWriterSettingsXmlCustomizationCallbacksMustBeSpecifiedBoth=Both startResourceXmlCustomizationCallback and endResourceXmlCustomizationCallback must be either null or non-null. + + +ODataCollectionWriterCore_InvalidTransitionFromStart=Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write the collection or to write nothing at all. +ODataCollectionWriterCore_InvalidTransitionFromCollection=Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write an item or to write the end of the collection. +ODataCollectionWriterCore_InvalidTransitionFromItem=Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write an item or the end of the collection. +ODataCollectionWriterCore_WriteEndCalledInInvalidState=ODataCollectionWriter.WriteEnd was called in an invalid state ('{0}'); WriteEnd is only supported in states 'Start', 'Collection', and 'Item'. +ODataCollectionWriterCore_SyncCallOnAsyncWriter=A synchronous operation was called on an asynchronous collection writer. All calls on a collection writer instance must be either synchronous or asynchronous. +ODataCollectionWriterCore_AsyncCallOnSyncWriter=An asynchronous operation was called on a synchronous collection writer. All calls on a collection writer instance must be either synchronous or asynchronous. + +ODataBatch_InvalidHttpMethodForChangeSetRequest=An invalid HTTP method '{0}' was detected for a request in a change set. Requests in change sets only support the HTTP methods 'POST', 'PUT', 'DELETE', and 'PATCH'. + +ODataBatchOperationHeaderDictionary_KeyNotFound=The header with name '{0}' was not present in the header collection of the batch operation. +ODataBatchOperationHeaderDictionary_DuplicateCaseInsensitiveKeys=Multiple headers with names that match '{0}', when using a case insensitive comparison, have been added. When case-insensitive header names are used, at most one header can be added for each name. + +ODataParameterWriter_InStreamErrorNotSupported=Writing an in-stream error is not supported when writing a parameter payload. +ODataParameterWriter_CannotCreateParameterWriterOnResponseMessage=CreateParameterWriter was called on a response message. A parameter payload is only allowed in a request message. + +ODataParameterWriterCore_SyncCallOnAsyncWriter=A synchronous operation was called on an asynchronous parameter writer. All calls on a parameter writer instance must be either synchronous or asynchronous. +ODataParameterWriterCore_AsyncCallOnSyncWriter=An asynchronous operation was called on a synchronous parameter writer. All calls on a parameter writer instance must be either synchronous or asynchronous. +ODataParameterWriterCore_CannotWriteStart=WriteStart can only be called once, and it must be called before writing anything else. +ODataParameterWriterCore_CannotWriteParameter=WriteValue and CreateCollectionWriter can only be called after WriteStart and before WriteEnd; they cannot be called until the previously created sub-writer is completed. +ODataParameterWriterCore_CannotWriteEnd=WriteEnd can only be called after WriteStart and after the previously created sub-writer has completed. +ODataParameterWriterCore_CannotWriteInErrorOrCompletedState=The writer is in either the 'Error' or 'Completed' state. No further writes can be performed on this writer. +ODataParameterWriterCore_DuplicatedParameterNameNotAllowed=The parameter '{0}' has already been written. Duplicate parameter names are not allowed in the parameter payload. +ODataParameterWriterCore_CannotWriteValueOnNonValueTypeKind=The parameter '{0}' is of Edm type kind '{1}'. You cannot call WriteValue on a parameter that is not of Edm type kinds 'Primitive', 'Enum' or 'Complex'. +ODataParameterWriterCore_CannotWriteValueOnNonSupportedValueType=The value for parameter '{0}' is of type '{1}'. WriteValue can only write null, ODataEnumValue and primitive types that are not Stream type. +ODataParameterWriterCore_CannotCreateCollectionWriterOnNonCollectionTypeKind=The parameter '{0}' is of Edm type kind '{1}'. You cannot call CreateCollectionWriter on a parameter that is not of Edm type kind 'Collection'. +ODataParameterWriterCore_CannotCreateResourceWriterOnNonEntityOrComplexTypeKind=The parameter '{0}' is of Edm type kind '{1}'. You cannot call CreateResourceWriter on a parameter that is not of Edm type kind 'Entity' or 'Complex'. +ODataParameterWriterCore_CannotCreateResourceSetWriterOnNonStructuredCollectionTypeKind=The parameter '{0}' is of Edm type kind '{1}'. You cannot call CreateResourceSetWriter on a parameter that is not of Edm type kind 'Collection(Entity)' or 'Collection(Complex)'. +ODataParameterWriterCore_ParameterNameNotFoundInOperation=The name '{0}' is not a recognized parameter name for operation '{1}'. +ODataParameterWriterCore_MissingParameterInParameterPayload=The parameters {0} of the operation '{1}' could not be found when writing the parameter payload. All parameters present in the operation must be written to the parameter payload. + +ODataBatchWriter_FlushOrFlushAsyncCalledInStreamRequestedState=ODataBatchWriter.Flush or ODataBatchWriter.FlushAsync was called while a stream being used to write operation content, obtained from the operation message by using GetStream or GetStreamAsync, was still active. This is not allowed. ODataBatchWriter.Flush or ODataBatchWriter.FlushAsync can only be called when an active stream for the operation content does not exist. +ODataBatchWriter_CannotCompleteBatchWithActiveChangeSet=An invalid method call on ODataBatchWriter was detected. You cannot call ODataBatchWriter.WriteEndBatch with an active change set; you must first call ODataBatchWriter.WriteEndChangeset. +ODataBatchWriter_CannotStartChangeSetWithActiveChangeSet=An invalid method call on ODataBatchWriter was detected. You cannot call ODataBatchWriter.WriteStartChangeset with an active change set; you must first call ODataBatchWriter.WriteEndChangeset. +ODataBatchWriter_CannotCompleteChangeSetWithoutActiveChangeSet=An invalid method call on ODataBatchWriter was detected. You cannot call ODataBatchWriter.WriteEndChangeset without an active change set; you must first call ODataBatchWriter.WriteStartChangeset. +ODataBatchWriter_InvalidTransitionFromStart=An invalid method call on ODataBatchWriter was detected. After creating the writer, the only valid methods are ODataBatchWriter.WriteStartBatch and ODataBatchWriter.FlushAsync. +ODataBatchWriter_InvalidTransitionFromBatchStarted=An invalid method call on ODataBatchWriter was detected. After calling WriteStartBatch, the only valid methods on ODataBatchWriter are WriteStartChangeset, CreateOperationRequestMessage, CreateOperationResponseMessage, WriteEndBatch, and FlushAsync. +ODataBatchWriter_InvalidTransitionFromChangeSetStarted=An invalid method call on ODataBatchWriter was detected. After calling WriteStartChangeset, the only valid methods on ODataBatchWriter are CreateOperationRequestMessage, CreateOperationResponseMessage, WriteEndChangeset, and FlushAsync. +ODataBatchWriter_InvalidTransitionFromOperationCreated=An invalid method call on ODataBatchWriter was detected. After calling CreateOperationRequestMessage or CreateOperationResponseMessage, the only valid methods on ODataBatchWriter are WriteStartChangeset, WriteEndChangeset, WriteEndBatch, and FlushAsync. +ODataBatchWriter_InvalidTransitionFromOperationContentStreamRequested=An invalid method call on ODataBatchWriter was detected. You cannot use the batch writer while another writer is writing the content of an operation. Dispose the stream for the operation before continuing to use the ODataBatchWriter. +ODataBatchWriter_InvalidTransitionFromOperationContentStreamDisposed=An invalid method call on ODataBatchWriter was detected. After writing the content of an operation, the only valid methods on ODataBatchWriter are CreateOperationRequestMessage, CreateOperationResponseMessage, WriteStartChangeset, WriteEndChangeset, WriteEndBatch and FlushAsync. +ODataBatchWriter_InvalidTransitionFromChangeSetCompleted=An invalid method call on ODataBatchWriter was detected. After calling WriteEndChangeset, the only valid methods on ODataBatchWriter are CreateOperationRequestMessage, CreateOperationResponseMessage, WriteStartChangeset, WriteEndBatch, and FlushAsync. +ODataBatchWriter_InvalidTransitionFromBatchCompleted=An invalid method call on ODataBatchWriter was detected. You can only call ODataBatchWriter.FlushAsync after ODataBatchWriter.WriteEndBatch has been called. +ODataBatchWriter_CannotCreateRequestOperationWhenWritingResponse=When writing a batch response, you cannot create a batch operation request message. +ODataBatchWriter_CannotCreateResponseOperationWhenWritingRequest=When writing a batch request, you cannot create a batch operation response message. +ODataBatchWriter_MaxBatchSizeExceeded=The current batch message contains too many parts. Only batch messages with a maximum number of '{0}' query operations and change sets are allowed. +ODataBatchWriter_MaxChangeSetSizeExceeded=The current change set contains too many operations. Only change sets with a maximum number of '{0}' operations are allowed. +ODataBatchWriter_SyncCallOnAsyncWriter=A synchronous operation was called on an asynchronous batch writer. Calls on a batch writer instance must be either all synchronous or all asynchronous. +ODataBatchWriter_AsyncCallOnSyncWriter=An asynchronous operation was called on a synchronous batch writer. Calls on a batch writer instance must be either all synchronous or all asynchronous. +ODataBatchWriter_DuplicateContentIDsNotAllowed=The content ID '{0}' was found more than once in the same change set or same batch request. Content IDs have to be unique across all operations of a change set for OData V4.0 and have to be unique across all operations in the whole batch request for OData V4.01. +ODataBatchWriter_CannotWriteInStreamErrorForBatch=The WriteError and WriteErrorAsync methods on ODataMessageWriter cannot be called when a batch is being written by using ODataBatchWriter. In OData, writing an in-stream error for a batch payload is not supported. + +ODataBatchUtils_RelativeUriUsedWithoutBaseUriSpecified=The relative URI '{0}' was specified in a batch operation, but a base URI was not specified for the batch writer or batch reader. +ODataBatchUtils_RelativeUriStartingWithDollarUsedWithoutBaseUriSpecified=The relative URI '{0}' was specified in a batch operation, but a base URI was not specified for the batch writer or batch reader. When the relative URI is a reference to a content ID, the content ID does not exist in the current change set. + +ODataBatchOperationMessage_VerifyNotCompleted=An attempt to change the properties of the message or to retrieve the payload stream for the message has failed. Either the payload stream has already been requested or the processing of the message has been completed. In both cases, no more changes can be made to the message. + +ODataBatchOperationStream_Disposed=Cannot access a closed stream. + +ODataBatchReader_CannotCreateRequestOperationWhenReadingResponse=When reading a batch response, you cannot create a batch operation request message. +ODataBatchReader_CannotCreateResponseOperationWhenReadingRequest=When reading a batch request, you cannot create a batch operation response message. +ODataBatchReader_InvalidStateForCreateOperationRequestMessage=The method CreateOperationRequestMessage was called in state '{0}', which is not allowed. CreateOperationRequestMessage can only be called in state 'Operation'. +ODataBatchReader_OperationRequestMessageAlreadyCreated=A request message for the operation has already been created. You cannot create a request message for the same operation multiple times. +ODataBatchReader_OperationResponseMessageAlreadyCreated=A response message for the operation has already been created. You cannot create a response message for the same operation multiple times. +ODataBatchReader_InvalidStateForCreateOperationResponseMessage=The method CreateOperationResponseMessage was called in state '{0}', which is not allowed. CreateOperationResponseMessage can only be called in state 'Operation'. +ODataBatchReader_CannotUseReaderWhileOperationStreamActive=You cannot use a batch reader while the stream for the content of an operation is still active. You must first dispose the operation stream before further calls to the batch reader are made. +ODataBatchReader_SyncCallOnAsyncReader=A synchronous operation was called on an asynchronous batch reader. Calls on a batch reader instance must be either all synchronous or all asynchronous. +ODataBatchReader_AsyncCallOnSyncReader=An asynchronous operation was called on a synchronous batch reader. Calls on a batch reader instance must be either all synchronous or all asynchronous. +ODataBatchReader_ReadOrReadAsyncCalledInInvalidState=ODataBatchReader.ReadAsync or ODataBatchReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'. +ODataBatchReader_MaxBatchSizeExceeded=The current batch message contains too many parts. A maximum number of '{0}' query operations and change sets are allowed in a batch message. +ODataBatchReader_MaxChangeSetSizeExceeded=The current change set contains too many operations. A maximum number of '{0}' operations are allowed in a change set. +ODataBatchReader_NoMessageWasCreatedForOperation=An operation was detected, but no message was created for it. You must create a message for every operation found in a batch or change set. +ODataBatchReader_ReaderModeNotInitilized=Reader mode is not setup correctly. +ODataBatchReader_JsonBatchTopLevelPropertyMissing=JsonLight batch format requires top level property name 'requests' or 'response' but it is missing. +ODataBatchReader_DuplicateContentIDsNotAllowed=The content ID '{0}' was found more than once in the same change set or same batch request. Content IDs have to be unique across all operations of a change set for OData V4.0 and have to be unique across all operations in the whole batch request for OData V4.01. +ODataBatchReader_DuplicateAtomicityGroupIDsNotAllowed=The atomicityGroup ID [{0}] was found duplicated in the batch request. AtomicityGroup IDs have to be adjacent, otherwise would be detected as duplicated. +ODataBatchReader_RequestPropertyMissing=Request property [{0}] is required but is missing. +ODataBatchReader_SameRequestIdAsAtomicityGroupIdNotAllowed=The dependsOn request Id [{0}] is same as atomicityGroup property value [{1}], and is not allowed. +ODataBatchReader_SelfReferenceDependsOnRequestIdNotAllowed=The dependsOn request Id [{0}] is same as id property value [{1}], and it is not allowed. +ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed=The dependsOn request Id [{0}] is part of atomic group [{1}]. Therefore dependsOn property should refer to atomic group Id [{1}] instead. +ODataBatchReader_DependsOnIdNotFound=The dependsOn Id: [{0}] in request [{1}] is not matching any of the request Id and atomic group Id seen so far. Forward reference is not allowed. +ODataBatchReader_AbsoluteURINotMatchingBaseUri=Absolute URI {0} is not start with the base URI [{1}] specified by the operation message. +ODataBatchReader_ReferenceIdNotIncludedInDependsOn=Request Id reference [{0}] in Uri [{1}] is not found in effective depends-on-Ids [{2}] of the request. +ODataBatch_GroupIdOrChangeSetIdCannotBeNull=Group id or changeset GUID cannot be null. +ODataBatchReader_MessageIdPositionedIncorrectly=Message with id [{0}] is positioned incorrectly: all messages of same groupId [{1}] must be adjacent. +ODataBatchReader_ReaderStreamChangesetBoundaryCannotBeNull=Changeset boundary must have been set by now. + +ODataBatchReaderStream_InvalidHeaderSpecified=The message header '{0}' is invalid. The header value must be of the format '
    :
    '. +ODataBatchReaderStream_InvalidRequestLine=The request line '{0}' is invalid. The request line at the start of each operation must be of the format 'HttpMethod RequestUrl HttpVersion'. +ODataBatchReaderStream_InvalidResponseLine=The response line '{0}' is invalid. The response line at the start of each operation must be of the format 'HttpVersion StatusCode StatusCodeString'. +ODataBatchReaderStream_InvalidHttpVersionSpecified=The HTTP version '{0}' used in a batch operation request or response is not valid. The value must be '{1}'. +ODataBatchReaderStream_NonIntegerHttpStatusCode= The HTTP status code '{0}' is invalid. An HTTP status code must be an integer value. +ODataBatchReaderStream_MissingContentTypeHeader=The 'Content-Type' header is missing. The 'Content-Type' header must be specified for each MIME part of a batch message. +ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader=A missing or invalid '{0}' header was found. The '{0}' header must be specified for each batch operation, and its value must be '{1}'. +ODataBatchReaderStream_InvalidContentTypeSpecified=The '{0}' header value '{1}' is invalid. When this is the start of the change set, the value must be '{2}'; otherwise it must be '{3}'. +ODataBatchReaderStream_InvalidContentLengthSpecified=The content length header '{0}' is not valid. The content length header must be a valid Int32 literal and must be greater than or equal to 0. +ODataBatchReaderStream_DuplicateHeaderFound=The header '{0}' was specified multiple times. Each header must appear only once in a batch part. +ODataBatchReaderStream_NestedChangesetsAreNotSupported=Nested change sets in a batch payload are not supported. +ODataBatchReaderStream_MultiByteEncodingsNotSupported=Invalid multi-byte encoding '{0}' detected. Multi-byte encodings other than UTF-8 are only supported for operation payloads. They are not supported in batch or change set parts. +ODataBatchReaderStream_UnexpectedEndOfInput=Encountered an unexpected end of input while reading the batch payload. + +ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached=Too many white spaces after a boundary delimiter and before the terminating line resource set. For security reasons, the total number of characters for a boundary including white spaces must not exceed {0}. + +ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse=When not writing an async response, you cannot create an async response message. +ODataAsyncWriter_CannotCreateResponseMoreThanOnce=You cannot create an async response message more than once. +ODataAsyncWriter_SyncCallOnAsyncWriter=A synchronous operation was called on an asynchronous async writer. Calls on an async writer instance must be either all synchronous or all asynchronous. +ODataAsyncWriter_AsyncCallOnSyncWriter=An asynchronous operation was called on a synchronous async writer. Calls on an async writer instance must be either all synchronous or all asynchronous. +ODataAsyncWriter_CannotWriteInStreamErrorForAsync=The WriteError and WriteErrorAsync methods on ODataMessageWriter cannot be called when an async message is being written by using ODataAsyncWriter. In OData, writing an in-stream error for an async payload is not supported. + +ODataAsyncReader_InvalidHeaderSpecified=The message header '{0}' is invalid. The header value must be of the format '
    :
    '. +ODataAsyncReader_CannotCreateResponseWhenNotReadingResponse=When not reading an async response, you cannot create an async response message. +ODataAsyncReader_InvalidResponseLine=The response line '{0}' is invalid. The response line at the start of the async response must be of the format 'HttpVersion StatusCode StatusCodeString'. +ODataAsyncReader_InvalidHttpVersionSpecified=The HTTP version '{0}' used in an async response is not valid. The value must be '{1}'. +ODataAsyncReader_NonIntegerHttpStatusCode=The HTTP status code '{0}' is invalid. An HTTP status code must be an integer value. +ODataAsyncReader_DuplicateHeaderFound=The header '{0}' was specified multiple times. Each header must appear only once. +ODataAsyncReader_MultiByteEncodingsNotSupported=Invalid multi-byte encoding '{0}' detected. Multi-byte encodings other than UTF-8 are only supported for async payloads. They are not supported in batch or change set parts. +ODataAsyncReader_InvalidNewLineEncountered=Invalid new line '{0}' encountered. Should be '\r\n'. +ODataAsyncReader_UnexpectedEndOfInput=Encountered an unexpected end of input while reading the async payload. Could be due to calling CreateResponseMessage() more than once. +ODataAsyncReader_SyncCallOnAsyncReader=A synchronous operation was called on an asynchronous async reader. Calls on an async reader instance must be either all synchronous or all asynchronous. +ODataAsyncReader_AsyncCallOnSyncReader=An asynchronous operation was called on a synchronous async reader. Calls on an async reader instance must be either all synchronous or all asynchronous. + +HttpUtils_MediaTypeUnspecified=The MIME type '{0}' is invalid or unspecified. +HttpUtils_MediaTypeRequiresSlash=The MIME type '{0}' requires a '/' character between type and subtype, such as 'text/plain'. +HttpUtils_MediaTypeRequiresSubType=The MIME type '{0}' requires a subtype definition. +HttpUtils_MediaTypeMissingParameterValue=The MIME type is missing a parameter value for a parameter with the name '{0}'. +HttpUtils_MediaTypeMissingParameterName=The MIME type is missing a parameter name for a parameter definition. +HttpUtils_EscapeCharWithoutQuotes=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the escape character '{3}' is not inside a quoted-string. +HttpUtils_EscapeCharAtEnd=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because it terminates with the escape character '{3}'. In a quoted-string, the escape characters must always be followed by a character. +HttpUtils_ClosingQuoteNotFound=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the closing quote character was not found for the quoted-string. +HttpUtils_InvalidCharacterInQuotedParameterValue=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the character '{3}' is not allowed in a quoted-string. For more information, see RFC 2616, Sections 3.6 and 2.2. +HttpUtils_ContentTypeMissing=The value for the Content-Type header is missing. +HttpUtils_MediaTypeRequiresSemicolonBeforeParameter=The MIME type '{0}' requires a semi-colon character (';') before a parameter definition. +HttpUtils_InvalidQualityValueStartChar=An invalid quality value was detected in the header string '{0}'; quality values must start with '0' or '1' but not with '{1}'. +HttpUtils_InvalidQualityValue=An invalid quality value '{0}' was detected in the header string '{1}'; quality values must be in the range [0, 1]. +HttpUtils_CannotConvertCharToInt=An error occurred when converting the character '{0}' to an integer. +HttpUtils_MissingSeparatorBetweenCharsets=The separator ',' was missing between charset values in the header '{0}'. +HttpUtils_InvalidSeparatorBetweenCharsets=A separator character was missing between charset values in the header '{0}'. +HttpUtils_InvalidCharsetName=An invalid (empty) charset name found in the header '{0}'. +HttpUtils_UnexpectedEndOfQValue=An unexpected end of the q-Value was detected in the header '{0}'. +HttpUtils_ExpectedLiteralNotFoundInString=The expected literal '{0}' was not found at position '{1}' in the string '{2}'. +HttpUtils_InvalidHttpMethodString=The string '{0}' cannot be converted into a supported HTTP method. The only supported HTTP methods are GET, DELETE, PUT, POST and PATCH. +HttpUtils_NoOrMoreThanOneContentTypeSpecified=The specified content type '{0}' contains either no media type or more than one media type, which is not allowed. You must specify exactly one media type as the content type. + +HttpHeaderValueLexer_UnrecognizedSeparator=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because '{3}' is not a recognized separator. The supported separators are ',', ';', and '='. +HttpHeaderValueLexer_TokenExpectedButFoundQuotedString=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because a token is expected but a quoted-string is found instead. +HttpHeaderValueLexer_FailedToReadTokenOrQuotedString=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because a token or a quoted-string is expected at this position but were not found. +HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because '{3}' is not a valid separator after a quoted-string. +HttpHeaderValueLexer_EndOfFileAfterSeparator=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the header value should not end with the separator '{3}'. + +MediaType_EncodingNotSupported=The character set '{0}' is not supported. + +MediaTypeUtils_DidNotFindMatchingMediaType=A supported MIME type could not be found that matches the acceptable MIME types for the request. The supported type(s) '{0}' do not match any of the acceptable MIME types '{1}'. +MediaTypeUtils_CannotDetermineFormatFromContentType=A supported MIME type could not be found that matches the content type of the response. None of the supported type(s) '{0}' matches the content type '{1}'. +MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified=The specified content type '{0}' contains either no media type or more than one media type, which is not allowed. You must specify exactly one media type as the content type. +MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads=The content type '{0}' specifies a batch payload; however, the payload either does not include a batch boundary or includes more than one boundary. In OData, batch payload content types must specify exactly one batch boundary in the '{1}' parameter of the content type. + +ExpressionLexer_ExpectedLiteralToken=Expected literal type token but found token '{0}'. + + +ODataUriUtils_ConvertToUriLiteralUnsupportedType=The type '{0}' is not supported when converting to a URI literal. +ODataUriUtils_ConvertFromUriLiteralTypeRefWithoutModel=An IEdmTypeReference must be provided with a matching IEdmModel. No model was provided. +ODataUriUtils_ConvertFromUriLiteralTypeVerificationFailure=Type verification failed. Expected type '{0}' but received the value '{1}'. +ODataUriUtils_ConvertFromUriLiteralNullTypeVerificationFailure=Type verification failed. Expected type '{0}' but received non-matching null value with associated type '{1}'. +ODataUriUtils_ConvertFromUriLiteralNullOnNonNullableType=Type verification failed. Expected non-nullable type '{0}' but received a null value. + +ODataUtils_CannotConvertValueToRawString=The value of type '{0}' could not be converted to a raw string. +ODataUtils_DidNotFindDefaultMediaType=A default MIME type could not be found for the requested payload in format '{0}'. +ODataUtils_UnsupportedVersionHeader=The value '{0}' of the OData-Version HTTP header is invalid. Only '4.0' and '4.01' are supported as values for the OData-Version header. +ODataUtils_MaxProtocolVersionExceeded=An OData version of {0} was specified and the maximum supported OData version is {1}. +ODataUtils_UnsupportedVersionNumber=An invalid enum value was specified for the version number. +ODataUtils_ModelDoesNotHaveContainer=The provided model does not contain an entity container. + +ReaderUtils_EnumerableModified=The value returned by the '{0}' property cannot be modified until the end of the owning resource is reported by the reader. + +ReaderValidationUtils_NullValueForNonNullableType=A null value was found with the expected type '{0}[Nullable=False]'. The expected type '{0}[Nullable=False]' does not allow null values. +ReaderValidationUtils_NullNamedValueForNonNullableType=A null value was found for the property named '{0}', which has the expected type '{1}[Nullable=False]'. The expected type '{1}[Nullable=False]' does not allow null values. +ReaderValidationUtils_EntityReferenceLinkMissingUri=No URI value was found for an entity reference link. A single URI value was expected. +ReaderValidationUtils_ValueWithoutType=A value without a type name was found and no expected type is available. When the model is specified, each value in the payload must have a type which can be either specified in the payload, explicitly by the caller or implicitly inferred from the parent value. +ReaderValidationUtils_ResourceWithoutType=A resource without a type name was found, but no expected type was specified. To allow entries without type information, the expected type must also be specified when the model is specified. +ReaderValidationUtils_CannotConvertPrimitiveValue=Cannot convert the literal '{0}' to the expected type '{1}'. +ReaderValidationUtils_MessageReaderSettingsBaseUriMustBeNullOrAbsolute=The base URI '{0}' specified in ODataMessageReaderSettings.BaseUri is invalid; it must be either null or an absolute URI. +ReaderValidationUtils_UndeclaredPropertyBehaviorKindSpecifiedOnRequest=The ODataMessageReaderSettings.UndeclaredPropertyBehaviorKinds is not set to ODataUndeclaredPropertyBehaviorKinds.None. When reading request payloads, the ODataMessageReaderSettings.UndeclaredPropertyBehaviorKinds property must be set to ODataUndeclaredPropertyBehaviorKinds.None; other values are not supported. +ReaderValidationUtils_ContextUriValidationInvalidExpectedEntitySet=The context URI '{0}' references the entity set with name '{1}'; however, the name of the expected entity set is '{2}' and does not match the entity set referenced in the context URI. +ReaderValidationUtils_ContextUriValidationInvalidExpectedEntityType=The context URI '{0}' references the entity type with name '{1}'; however, the name of the expected entity type is '{2}' which is not compatible with the entity type with name '{1}'. +ReaderValidationUtils_ContextUriValidationNonMatchingPropertyNames=The context URI '{0}' references the property with name '{1}' on type '{2}'; however, the name of the expected property is '{3}'. +ReaderValidationUtils_ContextUriValidationNonMatchingDeclaringTypes=The context URI '{0}' references the property with name '{1}' on type '{2}'; however, the declaring type of the expected property is '{3}'. +ReaderValidationUtils_NonMatchingPropertyNames=The property or operation import name '{0}' was read from the payload; however, the name of the expected property or operation import is '{1}'. +ReaderValidationUtils_TypeInContextUriDoesNotMatchExpectedType=The context URI '{0}' references the type '{1}'; however the expected type is '{2}'. +ReaderValidationUtils_ContextUriDoesNotReferTypeAssignableToExpectedType=The context URI '{0}' refers to the item type '{1}' which is not assignable to the expected item type '{2}'. +ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint=The value type '{0}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation on {1} '{2}'. + +ODataMessageReader_ReaderAlreadyUsed=The ODataMessageReader has already been used to read a message payload. An ODataMessageReader can only be used once to read a payload for a given message. +ODataMessageReader_ErrorPayloadInRequest=A top-level error cannot be read from request payloads. Top-level errors are only supported in responses. +ODataMessageReader_ServiceDocumentInRequest=A service document cannot be read from request payloads. Service documents are only supported in responses. +ODataMessageReader_MetadataDocumentInRequest=A metadata document cannot be read from request payloads. Metadata documents are only supported in responses. +ODataMessageReader_DeltaInRequest=Delta are only supported in responses. +ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata=The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The expected type can only be specified if metadata is made available to the reader. +ODataMessageReader_EntitySetSpecifiedWithoutMetadata=The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The entity set can only be specified if metadata is made available to the reader. +ODataMessageReader_OperationImportSpecifiedWithoutMetadata=The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The operation import can only be specified if metadata is made available to the reader. +ODataMessageReader_OperationSpecifiedWithoutMetadata=The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The operation can only be specified if metadata is made available to the reader. +ODataMessageReader_ExpectedCollectionTypeWrongKind=The expected type for a collection reader is of kind '{0}'. Only types of Primitive or ComplexType kind can be specified as the expected type for a collection reader. +ODataMessageReader_ExpectedPropertyTypeEntityCollectionKind=The expected type for property reading is of entity collection kind. Top-level properties can only be of primitive, complex, primitive collection or complex collection kind. +ODataMessageReader_ExpectedPropertyTypeEntityKind=The expected type for property reading is of entity kind. Top-level properties cannot be of entity type. +ODataMessageReader_ExpectedPropertyTypeStream=The expected type for property reading is Edm.Stream. Top-level properties cannot be of stream type. +ODataMessageReader_ExpectedValueTypeWrongKind=The expected type for a value is of kind '{0}'. Only types of Primitive kind can be specified as the expected type for reading a value. +ODataMessageReader_NoneOrEmptyContentTypeHeader=A missing or empty content type header was found when trying to read a message. The content type header is required. +ODataMessageReader_WildcardInContentType=The wildcard '*' was detected in the value '{0}' of the content type header. The value of the content type header cannot contain wildcards. +ODataMessageReader_GetFormatCalledBeforeReadingStarted=GetFormat was called before reading was started. GetFormat can only be called after a read method was called or a reader was created. +ODataMessageReader_DetectPayloadKindMultipleTimes=DetectPayloadKind or DetectPayloadKindAsync was called more than once; DetectPayloadKind or DetectPayloadKindAsync can only be called once. +ODataMessageReader_PayloadKindDetectionRunning=Payload kind detection has not completed. Read or create methods cannot be called on the ODataMessageReader before payload kind detection is complete. +ODataMessageReader_PayloadKindDetectionInServerMode=The ODataMessageReader is using the server behavior for WCF Data Services, as specified in its settings. Payload kind detection is not supported when using the WCF Data services server behavior. +ODataMessageReader_ParameterPayloadInResponse=A parameter payload cannot be read from a response payload. Parameter payloads are only supported in requests. +ODataMessageReader_SingletonNavigationPropertyForEntityReferenceLinks=The navigation property '{0}' with singleton cardinality on type '{1}' was specified for reading a collection of entity reference links. A navigation property with collection cardinality has to be provided. + +ODataAsyncResponseMessage_MustNotModifyMessage=An attempt was made to modify the message. The message cannot be modified. +ODataMessage_MustNotModifyMessage=An attempt was made to modify the message. The message cannot be modified. + + +ODataReaderCore_SyncCallOnAsyncReader=A synchronous operation was called on an asynchronous reader. Calls on a reader instance must be either all synchronous or all asynchronous. +ODataReaderCore_AsyncCallOnSyncReader=An asynchronous operation was called on a synchronous reader. Calls on a reader instance must be either all synchronous or all asynchronous. +ODataReaderCore_ReadOrReadAsyncCalledInInvalidState=ODataReader.ReadAsync or ODataReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'. +ODataReaderCore_CreateReadStreamCalledInInvalidState=CreateReadStream was called in an invalid state. CreateReadStream can only be called once in ReaderState.Stream. +ODataReaderCore_CreateTextReaderCalledInInvalidState=CreateTextReader was called in an invalid state. CreateTextReader can only be called once in ReaderState.Stream. +ODataReaderCore_ReadCalledWithOpenStream=Read called with an open stream or textreader. Please close any open streams or text readers before calling Read. +ODataReaderCore_NoReadCallsAllowed=Calling Read or ReadAsync on an ODataReader instance is not allowed in state '{0}'. +ODataWriterCore_PropertyValueAlreadyWritten=Attempted to write a value for a property {0} whose value has already been written. +ODataJsonReader_CannotReadResourcesOfResourceSet=A node of type '{0}' was read from the JSON reader when trying to read the resources of a resource set. A 'StartObject' or 'EndArray' node was expected. + +ODataJsonReaderUtils_CannotConvertInt32=Cannot convert a value of type 'Edm.Int32' to the expected target type '{0}'. +ODataJsonReaderUtils_CannotConvertDouble=Cannot convert a value of type 'Edm.Double' to the expected target type '{0}'. +ODataJsonReaderUtils_CannotConvertBoolean=Cannot convert a value of type 'Edm.Boolean' to the expected target type '{0}'. +ODataJsonReaderUtils_CannotConvertDecimal=Cannot convert a value of type 'Edm.Decimal' to the expected target type '{0}'. +ODataJsonReaderUtils_CannotConvertDateTime=Cannot convert a value of type 'Edm.DateTime' to the expected target type '{0}'. +ODataJsonReaderUtils_CannotConvertDateTimeOffset=Cannot convert a value of type 'Edm.DateTimeOffset' to the expected target type '{0}'. +ODataJsonReaderUtils_ConflictBetweenInputFormatAndParameter=Cannot convert a value to target type '{0}' because of conflict between input format string/number and parameter 'IEEE754Compatible' false/true. +ODataJsonReaderUtils_MultipleErrorPropertiesWithSameName=Multiple '{0}' properties were found in an error or inner error object. In OData, an error or inner error must have at most one '{0}' property. + +ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustSpecifyTarget=Multiple operations have the same 'Metadata' property value of '{0}'. The 'Target' property value of these operations must be set to a non-null value. +ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustNotHaveDuplicateTarget=Multiple operations have the same 'Metadata' property value of '{0}' and the same 'Target' property value of '{1}'. When multiple operations have the same 'Metadata' property value, their 'Target' property values must be unique. + +ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty=A property with name '{0}' was found in the error object when reading a top-level error. In OData, a top-level error object must have exactly one property with name 'error'. +ODataJsonErrorDeserializer_TopLevelErrorMessageValueWithInvalidProperty=A property with name '{0}' was found in the message value of a top-level error. In OData, the message value of a top-level error value can only have properties with name 'lang' or 'value'. + + +ODataCollectionReaderCore_ReadOrReadAsyncCalledInInvalidState=ODataCollectionReader.ReadAsync or ODataCollectionReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'. +ODataCollectionReaderCore_SyncCallOnAsyncReader=A synchronous operation was called on an asynchronous collection reader. All calls on a collection reader instance must be either synchronous or asynchronous. +ODataCollectionReaderCore_AsyncCallOnSyncReader=An asynchronous operation was called on a synchronous collection reader. All calls on a collection reader instance must be either synchronous or asynchronous. +ODataCollectionReaderCore_ExpectedItemTypeSetInInvalidState=The current state of the collection reader is '{0}'; however, the expected item type of a collection reader can only be set in state '{1}'. + +ODataParameterReaderCore_ReadOrReadAsyncCalledInInvalidState=ODataParameterReader.ReadAsync or ODataParameterReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'. +ODataParameterReaderCore_SyncCallOnAsyncReader=A synchronous operation was called on an asynchronous parameter reader. All calls on a parameter reader instance must be either synchronous or asynchronous. +ODataParameterReaderCore_AsyncCallOnSyncReader=An asynchronous operation was called on a synchronous parameter reader. All calls on a parameter reader instance must be either synchronous or asynchronous. +ODataParameterReaderCore_SubReaderMustBeCreatedAndReadToCompletionBeforeTheNextReadOrReadAsyncCall=ODataParameterReader.ReadAsync or ODataParameterReader.Read was called in the '{0}' state. '{1}' must be called in this state, and the created reader must be in the 'Completed' state before the next ODataParameterReader.ReadAsync or ODataParameterReader.Read can be called. +ODataParameterReaderCore_SubReaderMustBeInCompletedStateBeforeTheNextReadOrReadAsyncCall=ODataParameterReader.ReadAsync or ODataParameterReader.Read was called in the '{0}' state and '{1}' was called but the created reader is not in the 'Completed' state. The created reader must be in 'Completed' state before the next ODataParameterReader.ReadAsync or ODataParameterReader.Read can be called. +ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState=You cannot call the method '{0}' in state '{1}'. +ODataParameterReaderCore_CreateReaderAlreadyCalled=The '{0}' method has already been called for the parameter '{1}'. Only one create reader method call is allowed for each resource, resource set, or collection parameter. +ODataParameterReaderCore_ParameterNameNotInMetadata=The parameter '{0}' in the request payload is not a valid parameter for the operation '{1}'. +ODataParameterReaderCore_DuplicateParametersInPayload=Multiple parameters with the name '{0}' were found in the request payload. +ODataParameterReaderCore_ParametersMissingInPayload=One or more parameters of the operation '{0}' are missing from the request payload. The missing parameters are: {1}. + + + + +ValidationUtils_ActionsAndFunctionsMustSpecifyMetadata=The 'Metadata' property on an {0} must be set to a non-null value. +ValidationUtils_ActionsAndFunctionsMustSpecifyTarget=The 'Target' property on an {0} must be set to a non-null value. +ValidationUtils_EnumerableContainsANullItem=The '{0}' enumerable contains a null item. This enumerable cannot contain null items. +ValidationUtils_AssociationLinkMustSpecifyName=The 'Name' property on an ODataAssociationLink must be set to a non-empty string. +ValidationUtils_AssociationLinkMustSpecifyUrl=The 'Url' property on an ODataAssociationLink must be set to a non-null value that represents the association or associations the link references. +ValidationUtils_TypeNameMustNotBeEmpty=An empty type name was found; the name of a type cannot be an empty string. +ValidationUtils_PropertyDoesNotExistOnType=The property '{0}' does not exist on type '{1}'. Make sure to only use property names that are defined by the type or mark the type as open type. +ValidationUtils_ResourceMustSpecifyUrl=The 'Url' property on a resource collection must be set to a non-null value. +ValidationUtils_ResourceMustSpecifyName=The 'Name' property on a resource collection with the 'Url' '{0}' must be set to a non-null value. +ValidationUtils_ServiceDocumentElementUrlMustNotBeNull=A service document element without a Url was detected; a service document element must have a non-null Url value. +ValidationUtils_NonPrimitiveTypeForPrimitiveValue=A primitive value was specified; however, a value of the non-primitive type '{0}' was expected. +ValidationUtils_UnsupportedPrimitiveType=Unsupported primitive type. A primitive type could not be determined for an instance of type '{0}'. +ValidationUtils_IncompatiblePrimitiveItemType=An incompatible primitive type '{0}[Nullable={1}]' was found for an item that was expected to be of type '{2}[Nullable={3}]'. +ValidationUtils_NonNullableCollectionElementsMustNotBeNull=A null value was detected in the items of a collection property value; non-nullable instances of collection types do not support null values as items. +ValidationUtils_InvalidCollectionTypeName=Type name '{0}' is an invalid collection type name; a collection type name must be in the format 'Collection()'. +ValidationUtils_UnrecognizedTypeName=A type named '{0}' could not be resolved by the model. When a model is available, each type name must resolve to a valid type. +ValidationUtils_IncorrectTypeKind=Incompatible type kinds were found. The type '{0}' was found to be of kind '{2}' instead of the expected kind '{1}'. +ValidationUtils_IncorrectTypeKindNoTypeName=Incompatible type kinds were found. Found type kind '{0}' instead of the expected kind '{1}'. +ValidationUtils_IncorrectValueTypeKind=A value with type '{0}' was found, which is of kind '{1}'. Value can only be of kind 'Primitive', 'Complex' or 'Collection'. +ValidationUtils_LinkMustSpecifyName=The 'Name' property on an ODataNestedResourceInfo must be set to a non-empty string. +ValidationUtils_MismatchPropertyKindForStreamProperty=The property '{0}' cannot be a stream property because it is not of kind EdmPrimitiveTypeKind.Stream. +ValidationUtils_NestedCollectionsAreNotSupported=Nested collection instances are not allowed. +ValidationUtils_StreamReferenceValuesNotSupportedInCollections=An ODataStreamReferenceValue item was found in a collection property value, which is not allowed. Collection properties can only have primitive and complex values as items. +ValidationUtils_IncompatibleType=A value was encountered that has a type name that is incompatible with the metadata. The value specified its type as '{0}', but the type specified in the metadata is '{1}'. +ValidationUtils_OpenCollectionProperty=An open collection property '{0}' was found. In OData, open collection properties are not supported. +ValidationUtils_OpenStreamProperty=An open stream property '{0}' was found. In OData, open stream properties are not supported. +ValidationUtils_InvalidCollectionTypeReference=An invalid collection type kind '{0}' was found. In OData, collection types must be of kind 'Collection'. +ValidationUtils_ResourceWithMediaResourceAndNonMLEType=A resource with type '{0}' was found with a media resource, but this entity type is not a media link resource (MLE). When the type is not an MLE entity, the resource cannot have a media resource. +ValidationUtils_ResourceWithoutMediaResourceAndMLEType=A resource with type '{0}' was found without a media resource, but this entity type is a media link resource (MLE). When the type is an MLE entity, the resource must have a media resource. +ValidationUtils_ResourceTypeNotAssignableToExpectedType=A resource with type '{0}' was found, but it is not assignable to the expected type '{1}'. The type specified in the resource must be equal to either the expected type or a derived type. +ValidationUtils_NavigationPropertyExpected=A property with name '{0}' on type '{1}' has kind '{2}', but it is expected to be of kind 'Navigation'. +ValidationUtils_InvalidBatchBoundaryDelimiterLength=The boundary delimiter '{0}' is invalid. A boundary delimiter must be non-null, be non-empty, and have a maximum of {1} characters. +ValidationUtils_RecursionDepthLimitReached=The maximum recursion depth limit was reached. The depth of nested values in a single property cannot exceed {0}. +ValidationUtils_MaxDepthOfNestedEntriesExceeded=The depth limit for entries in nested expanded navigation links was reached. The number of nested expanded entries cannot exceed {0}. +ValidationUtils_NullCollectionItemForNonNullableType=A null value was found in a collection, but the expected collection item type '{0}' does not allow null values. +ValidationUtils_PropertiesMustNotContainReservedChars=The property name '{0}' is invalid; property names must not contain any of the reserved characters {1}. +ValidationUtils_WorkspaceResourceMustNotContainNullItem=A null value was detected when enumerating the collections in a workspace. Workspace collections cannot be null. +ValidationUtils_InvalidMetadataReferenceProperty=Encountered a property '{0}' that was expected to be a reference to a location in the $metadata document but does not contain a '#' character or is otherwise not a valid metadata reference property. A metadata reference property must contain a '#' and be a valid absolute URI or begin with a '#' and be a valid URI fragment. + +WriterValidationUtils_PropertyMustNotBeNull=The 'ODataResource.Properties' enumerable contains a null item. This enumerable cannot contain null items. +WriterValidationUtils_PropertiesMustHaveNonEmptyName=An ODataProperty instance without a name was detected; an ODataProperty must have a non-null, non-empty name. +WriterValidationUtils_MissingTypeNameWithMetadata=No TypeName was found for an ODataResource of an open property, ODataResource or custom instance annotation, even though metadata was specified. If a model is passed to the writer, each complex value on an open property, resource or custom instance annotation must have a type name. +WriterValidationUtils_NextPageLinkInRequest=The ODataResourceSet.NextPageLink must be null for request payloads. A next link is only supported in responses. +WriterValidationUtils_DefaultStreamWithContentTypeWithoutReadLink=A default stream ODataStreamReferenceValue was detected with a 'ContentType' property but without a ReadLink value. In OData, a default stream must either have both a content type and a read link, or neither of them. +WriterValidationUtils_DefaultStreamWithReadLinkWithoutContentType=A default stream ODataStreamReferenceValue was detected with a 'ReadLink' property but without a ContentType value. In OData, a default stream must either have both a content type and a read link, or neither of them. +WriterValidationUtils_StreamReferenceValueMustHaveEditLinkOrReadLink=An ODataStreamReferenceValue was detected with null values for both EditLink and ReadLink. In OData, a stream resource must have at least an edit link or a read link. +WriterValidationUtils_StreamReferenceValueMustHaveEditLinkToHaveETag=An ODataStreamReferenceValue was detected with an ETag but without an edit link. In OData, a stream resource must have an edit link to have an ETag. +WriterValidationUtils_StreamReferenceValueEmptyContentType=An ODataStreamReferenceValue was detected with an empty string 'ContentType' property. In OData, a stream resource must either have a non-empty content type or it must be null. +WriterValidationUtils_EntriesMustHaveNonEmptyId=A resource with an empty ID value was detected. In OData, a resource must either a non-empty ID value or no ID value. +WriterValidationUtils_MessageWriterSettingsBaseUriMustBeNullOrAbsolute=The base URI '{0}' specified in ODataMessageWriterSettings.BaseUri is invalid; it must either be null or an absolute URI. +WriterValidationUtils_EntityReferenceLinkUrlMustNotBeNull=An ODataEntityReferenceLink with a null Url was detected; an ODataEntityReferenceLink must have a non-null Url. +WriterValidationUtils_EntityReferenceLinksLinkMustNotBeNull=The 'ODataEntityReferenceLinks.Links' enumerable contains a null item. This enumerable cannot contain null items. +WriterValidationUtils_NestedResourceTypeNotCompatibleWithParentPropertyType=The type '{0}' of a resource in an expanded link is not compatible with the element type '{1}' of the expanded link. Entries in an expanded link must have entity types that are assignable to the element type of the expanded link. +WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceContent=The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource set, but the actual payload is a resource. +WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetContent=The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource, but the actual payload is a resource set. +WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceMetadata=The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource set, but the metadata declares it as a resource. +WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetMetadata=The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource, but the metadata declares it as resource set. +WriterValidationUtils_ExpandedLinkWithResourceSetPayloadAndResourceMetadata=The content of the ODataNestedResourceInfo with the URL value '{0}' is a resource set, but the metadata declares it as a resource. +WriterValidationUtils_ExpandedLinkWithResourcePayloadAndResourceSetMetadata=The content of the ODataNestedResourceInfo with the URL value '{0}' is a resource, but the metadata declares it as resource set. +WriterValidationUtils_CollectionPropertiesMustNotHaveNullValue=The collection property '{0}' has a null value, which is not allowed. In OData, collection properties cannot have null values. +WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue=The property '{0}[Nullable=False]' of type '{1}' has a null value, which is not allowed. +WriterValidationUtils_StreamPropertiesMustNotHaveNullValue=The stream property '{0}' has a null value, which is not allowed. In OData, stream properties cannot have null values. +WriterValidationUtils_OperationInRequest=An action or a function with metadata '{0}' was detected when writing a request; actions and functions are only supported in responses. +WriterValidationUtils_AssociationLinkInRequest=An association link with name '{0}' could not be written to the request payload. Association links are only supported in responses. +WriterValidationUtils_StreamPropertyInRequest=The stream property {0} in a request payload cannot contain etag, editLink, or readLink values. +WriterValidationUtils_MessageWriterSettingsServiceDocumentUriMustBeNullOrAbsolute=The service document URI '{0}' specified is invalid; it must be either null or an absolute URI. +WriterValidationUtils_NavigationLinkMustSpecifyUrl=The ODataNestedResourceInfo.Url property on an navigation link '{0}' is null. The ODataNestedResourceInfo.Url property must be set to a non-null value that represents the entity or entities the navigation link references. +WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection=The ODataNestedResourceInfo.IsCollection property on a nested resource info '{0}' is null. The ODataNestedResourceInfo.IsCollection property must be specified when writing a nested resource into a request. +WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage=A JSON Padding function was specified on ODataMessageWriterSettings when trying to write a request message. JSON Padding is only for writing responses. +WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint=The value type '{0}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation on {1} '{2}'. + +XmlReaderExtension_InvalidNodeInStringValue=An XML node of type '{0}' was found in a string value. An element with a string value can only contain Text, CDATA, SignificantWhitespace, Whitespace or Comment nodes. +XmlReaderExtension_InvalidRootNode=An XML node of type '{0}' was found at the root level. The root level of an OData payload must contain a single XML element and no text nodes. + + +ODataMetadataInputContext_ErrorReadingMetadata=The metadata document could not be read from the message content.\r\n{0} + +ODataMetadataOutputContext_ErrorWritingMetadata=The metadata document could not be written as specified.\r\n{0} + +ODataAtomDeserializer_RelativeUriUsedWithoutBaseUriSpecified=A relative URI value '{0}' was specified in the payload, but no base URI for it was found. When the payload contains a relative URI, there must be an xml:base in the payload or else a base URI must specified in the reader settings. + +ODataAtomPropertyAndValueDeserializer_InvalidCollectionElement=The element with name '{0}' is not a valid collection item. The name of the collection item element must be 'element' and it must belong to the '{1}' namespace. +ODataAtomPropertyAndValueDeserializer_NavigationPropertyInProperties=The property '{0}' on type '{1}' was found in the {{http://docs.oasis-open.org/odata/ns/metadata}}:properties element, and it is declared as a navigation property. Navigation properties in ATOM must be represented as {{http://www.w3.org/2005/Atom}}:link elements. + +JsonLightInstanceAnnotationWriter_NullValueNotAllowedForInstanceAnnotation=Writing null value for the instance annotation '{0}' is not allowed. The instance annotation '{0}' has the expected type '{1}[Nullable=False]'. + +EdmLibraryExtensions_OperationGroupReturningActionsAndFunctionsModelInvalid=When resolving operations '{0}' the group returned has both actions and functions with an invalid IEdmModel. +EdmLibraryExtensions_UnBoundOperationsFoundFromIEdmModelFindMethodIsInvalid=Invalid implementation of an IEdmModel, an operation '{0}' was found using the IEdmModel method 'FindDeclaredBoundOperations' should never return non-bound operations. +EdmLibraryExtensions_NoParameterBoundOperationsFoundFromIEdmModelFindMethodIsInvalid=Invalid implementation of an IEdmModel, an operation '{0}' was found using the IEdmModel method 'FindDeclaredBoundOperations' should never return bound operations without any parameters. +EdmLibraryExtensions_ValueOverflowForUnderlyingType=Value '{0}' was either too large or too small for a '{1}'. + +ODataAtomResourceDeserializer_ContentWithWrongType=The 'type' attribute on element {{http://www.w3.org/2005/Atom}}:content is either missing or has an invalid value '{0}'. Only 'application/xml' and 'application/atom+xml' are supported as the value of the 'type' attribute on the {{http://www.w3.org/2005/Atom}}:content element. + +ODataAtomErrorDeserializer_MultipleErrorElementsWithSameName=Multiple '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' elements were found in a top-level error value. In OData, the value of a top-level error value can have no more than one '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' element +ODataAtomErrorDeserializer_MultipleInnerErrorElementsWithSameName=Multiple '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' elements were found in an inner error value. In OData, the value of an inner error value can have at most one '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' element. + +CollectionWithoutExpectedTypeValidator_InvalidItemTypeKind=An invalid item type kind '{0}' was found. Items in a collection can only be of type kind 'Primitive' or 'Complex', but not of type kind '{0}'. +CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeKind=An item of type kind '{0}' was found in a collection that otherwise has items of type kind '{1}'. In OData, all items in a collection must have the same type kind. +CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeName=An item with type name '{0}' was found in a collection of items with type name '{1}'. In OData, all items in a collection must have the same type name. + +ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes=A resource of type '{0}' was found in a resource set that otherwise has entries of type '{1}'. In OData, all entries in a resource set must have a common base type. + +MessageStreamWrappingStream_ByteLimitExceeded=The maximum number of bytes allowed to be read from the stream has been exceeded. After the last read operation, a total of {0} bytes has been read from the stream; however a maximum of {1} bytes is allowed. + +MetadataUtils_ResolveTypeName=The custom type resolver set in ODataMessageWriterSettings.EnableWcfDataServicesClientBehavior returned 'null' when resolving the type '{0}'. When a custom type resolver is specified, it cannot return null. +MetadataUtils_CalculateBindableOperationsForType=The method 'FindDeclaredBoundOperations' on the IEdmModel has thrown an exception when looking for operations with a binding type {0}. See inner exception for more details. + +EdmValueUtils_UnsupportedPrimitiveType=The type '{0}' was found for a primitive value. In OData, the type '{0}' is not a supported primitive type. +EdmValueUtils_IncorrectPrimitiveTypeKind=Incompatible primitive type kinds were found. The type '{0}' was found to be of kind '{2}' instead of the expected kind '{1}'. +EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName=Incompatible primitive type kinds were found. Found type kind '{0}' instead of the expected kind '{1}'. +EdmValueUtils_CannotConvertTypeToClrValue=A value with primitive kind '{0}' cannot be converted into a primitive object value. + +ODataEdmStructuredValue_UndeclaredProperty=The property '{0}' is not declared on the non-open type '{1}'. + + +ODataMetadataBuilder_MissingEntitySetUri=The entity set '{0}' doesn't have the 'OData.EntitySetUri' annotation. This annotation is required. +ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix=The entity set '{0}' has a URI '{1}' which has no path segments. An entity set URI suffix cannot be appended to a URI without path segments. +ODataMetadataBuilder_MissingEntityInstanceUri=Neither the 'OData.EntityInstanceUri' nor the 'OData.EntitySetUriSuffix' annotation was found for entity set '{0}'. One of these annotations is required. +ODataMetadataBuilder_MissingParentIdOrContextUrl=Parent id or contained context url is missing which is required to compute id for contained instance. Specify ODataUri in the ODataMessageWriterSettings or return parent id or context url in the payload. +ODataMetadataBuilder_UnknownEntitySet=The Id cannot be computed, since the navigation source '{0}' cannot be resolved to a known entity set from model. + +ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType=The entity type '{0}' is not compatible with the base type '{1}' of the provided entity set '{2}'. When an entity type is specified for an OData resource set or resource reader, it has to be the same or a subtype of the base type of the specified entity set. +ODataJsonLightInputContext_PayloadKindDetectionForRequest=ODataMessageReader.DetectPayloadKind was called for a request payload. Payload kind detection is only supported for responses in JSON Light. +ODataJsonLightInputContext_OperationCannotBeNullForCreateParameterReader=The parameter '{0}' is specified with a null value. For JSON Light, the '{0}' argument to the 'CreateParameterReader' method cannot be null. +ODataJsonLightInputContext_NoEntitySetForRequest=Parsing JSON Light resource sets or entries in requests without entity set is not supported. Pass in the entity set as a parameter to ODataMessageReader.CreateODataResourceReader or ODataMessageReader.CreateODataResourceSetReader method. +ODataJsonLightInputContext_ModelRequiredForReading=Parsing JSON Light payloads without a model is only supported for error payloads. +ODataJsonLightInputContext_ItemTypeRequiredForCollectionReaderInRequests=An attempt to read a collection request payload without specifying a collection item type was detected. When reading collection payloads in requests, an expected item type has to be provided. + +ODataJsonLightDeserializer_ContextLinkNotFoundAsFirstProperty=The required instance annotation 'odata.context' was not found at the beginning of a response payload. +ODataJsonLightDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation=The annotation '{0}' was targeting the instance annotation '{1}'. Only the '{2}' annotation is allowed to target an instance annotation. +ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue=The annotation '{0}' is found targeting the instance annotation '{1}'. However the value for the instance annotation '{1}' is not found immediately after. In JSON Light, an annotation targeting an instance annotation must be immediately followed by the value of the targeted instance annotation. + +ODataJsonLightWriter_EntityReferenceLinkAfterResourceSetInRequest=An attempt to write an entity reference link inside a navigation link after a resource set has been written inside the same navigation link in a request was detected. In JSON Light requests, all entity reference links inside a navigation link have to be written before all resource sets inside the same navigation link. +ODataJsonLightWriter_InstanceAnnotationNotSupportedOnExpandedResourceSet=The ODataResourceSet.InstanceAnnotations collection must be empty for expanded resource sets. Custom instance annotations are not supported on expanded resource sets. + +ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForResourceValueRequest=Neither an expected type nor a type name in the OData object model was provided for a resource value. When writing a request payload, either an expected type or a type name has to be specified. +ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForCollectionValueInRequest=Neither an expected type nor a type name in the OData object model was provided for a collection property. When writing a request payload, either an expected type or a type name has to be specified. + +ODataResourceTypeContext_MetadataOrSerializationInfoMissing=When writing a JSON response, a user model must be specified and the entity set and entity type must be passed to the ODataMessageWriter.CreateODataResourceWriter method or the ODataResourceSerializationInfo must be set on the ODataResource or ODataResourceSet that is being written. +ODataResourceTypeContext_ODataResourceTypeNameMissing=When writing a JSON response in full metadata mode, a user model must be specified and the entity set and entity type must be passed to the ODataMessageWriter.CreateODataResourceWriter method or the ODataResource.TypeName must be set. + +ODataContextUriBuilder_ValidateDerivedType=The base type '{0}' of the entity set specified for writing a payload is not assignable from the specified entity type '{1}'. When an entity type is specified it has to be the same or derived from the base type of the entity set. +ODataContextUriBuilder_TypeNameMissingForTopLevelCollection=The collection type name for the top level collection is unknown. When writing a response, the item type must be passed to the ODataMessageWriter.CreateODataCollectionWriter method or the ODataCollectionStartSerializationInfo must be set on the ODataCollectionStart. +ODataContextUriBuilder_UnsupportedPayloadKind=Context URL for payload kind '{0}' is not supported. +ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource=The stream value must be a property of an ODataResource instance. +ODataContextUriBuilder_NavigationSourceOrTypeNameMissingForResourceOrResourceSet=The navigationSource for resource or resource set is unknown or the Type is null. When writing a response, the navigation source or the type must be passed to the ODataMessageWriter.CreateODataResourceWriter/ODataMessageWriter.CreateODataResourceSetWriter method or the ODataResourceSerializationInfo must be set on the resource/resource set. +ODataContextUriBuilder_ODataUriMissingForIndividualProperty=The ODataMessageWriterSetting.ODataUri must be set when writing individual property. +ODataContextUriBuilder_TypeNameMissingForProperty=The type name for the top level property is unknown. When writing a response, the ODataValue must have a type name on itself or have a SerializationTypeNameAnnotation. +ODataContextUriBuilder_ODataPathInvalidForContainedElement=The Path property '{0}' of ODataMessageWriterSetting.ODataUri must end with the navigation property which the contained elements being written belong to. + +ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties=The annotation '{0}' was found. This annotation is either not recognized or not expected at the current position. +ODataJsonLightPropertyAndValueDeserializer_UnexpectedPropertyAnnotation=The property '{0}' has a property annotation '{1}'. This annotation is either not recognized or not expected at the current position. +ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation=An OData property annotation '{0}' was found. This property annotation is either not recognized or not expected at the current position. +ODataJsonLightPropertyAndValueDeserializer_UnexpectedProperty=A property with name '{0}' was found. This property is either not recognized or not expected at the current position. +ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload=No top-level properties were found. A top-level property or collection in JSON Light must be represented as a JSON object with exactly one property which is not an annotation. +ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName=A top-level property with name '{0}' was found in the payload; however, property and collection payloads must always have a top-level property with name '{1}'. +ODataJsonLightPropertyAndValueDeserializer_InvalidTypeName=The 'odata.type' instance annotation value '{0}' is not a valid type name. The value of the 'odata.type' instance annotation must be a non-empty string. +ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty=One or more property annotations for property '{0}' were found in the top-level property or collection payload without the property to annotate. Top-level property and collection payloads must contain a single property, with optional annotations for this property. +ODataJsonLightPropertyAndValueDeserializer_ResourceValuePropertyAnnotationWithoutProperty=One or more property annotations for property '{0}' were found in the resource value without the property to annotate. Resource values must only contain property annotations for existing properties. +ODataJsonLightPropertyAndValueDeserializer_ComplexValueWithPropertyTypeAnnotation=A complex property with an '{0}' property annotation was found. Complex properties must not have the '{0}' property annotation, instead the '{0}' should be specified as an instance annotation in the complex value. +ODataJsonLightPropertyAndValueDeserializer_ResourceTypeAnnotationNotFirst=The 'odata.type' instance annotation in a resource object is not the first property of the object. In OData, the 'odata.type' instance annotation must be the first property of the resource object. +ODataJsonLightPropertyAndValueDeserializer_UnexpectedDataPropertyAnnotation=The property '{0}' has a property annotation '{1}'. Primitive, complex, collection or open properties can only have an 'odata.type' property annotation. +ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty=The property with name '{0}' was found after the data property with name '{1}'. If a type is specified for a data property, it must appear before the data property. +ODataJsonLightPropertyAndValueDeserializer_ODataTypeAnnotationInPrimitiveValue=An '{0}' annotation was read inside a JSON object representing a primitive value; type annotations for primitive values have to be property annotations of the owning property. +ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyWithPrimitiveNullValue=A top-level property with an invalid primitive null value was found. In OData, top-level properties with null value have to be serialized as JSON object with an '{0}' annotation that has the value '{1}'. +ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty=Encountered a metadata reference property '{0}' in a scope other than a resource. In OData, a property name with a '#' character indicates a reference into the metadata and is only supported for describing operations bound to a resource. +ODataJsonLightPropertyAndValueDeserializer_NoPropertyAndAnnotationAllowedInNullPayload=The property with name '{0}' was found in a null payload. In OData, no properties or OData annotations can appear in a null payload. +ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected=A collection type of '{0}' was specified for a non-collection value. +ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected=A non-collection type of '{0}' was specified for a collection value. + +ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue=The value specified for the spatial property was not valid. You must specify a valid spatial value. + +ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource=If a primitive value is representing a resource, the resource must be null. +ODataJsonLightReaderUtils_AnnotationWithNullValue=The '{0}' instance or property annotation has a null value. In OData, the '{0}' instance or property annotation must have a non-null string value. +ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation=An '{0}' annotation was found with an invalid value. In OData, the only valid value for the '{0}' annotation is '{1}'. + +JsonLightInstanceAnnotationWriter_DuplicateAnnotationNameInCollection=The InstanceAnnotations collection has more than one instance annotations with the name '{0}'. All instance annotation names must be unique within the collection. + +ODataJsonLightContextUriParser_NullMetadataDocumentUri=A null metadata document URI was found in the payload. Metadata document URIs must not be null. +ODataJsonLightContextUriParser_ContextUriDoesNotMatchExpectedPayloadKind=The context URI '{0}' is not valid for the expected payload kind '{1}'. +ODataJsonLightContextUriParser_InvalidEntitySetNameOrTypeName=The context URI '{0}' references the entity set or type '{1}'. However, no entity set or type with name '{1}' is declared in the metadata. +ODataJsonLightContextUriParser_InvalidPayloadKindWithSelectQueryOption=A '$select' query option was found for the payload kind '{0}'. In OData, a '$select' query option is only supported for payload kinds 'Resource' and 'ResourceSet'. +ODataJsonLightContextUriParser_NoModel=No model was specified for the ODataMessageReader. A message reader requires a model for JSON Light payload to be specified in the ODataMessageReader constructor. +ODataJsonLightContextUriParser_InvalidContextUrl=The context URL '{0}' is invalid. +ODataJsonLightContextUriParser_LastSegmentIsKeySegment=Last segment in context URL '{0}' should not be KeySegment. +ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute=The top level context URL '{0}' should be an absolute Uri. + +ODataJsonLightResourceDeserializer_DeltaRemovedAnnotationMustBeObject=Invalid primitive value '{0}' for @removed annotation. @removed annotation must be a JSON object, optionally containing a 'reason' property. +ODataJsonLightResourceDeserializer_ResourceTypeAnnotationNotFirst=The 'odata.type' instance annotation in a resource object is preceded by an invalid property. In OData, the 'odata.type' instance annotation must be either the first property in the JSON object or the second if the 'odata.context' instance annotation is present. +ODataJsonLightResourceDeserializer_ResourceInstanceAnnotationPrecededByProperty=The '{0}' instance annotation in a resource object is preceded by a property or property annotation. In OData, the '{0}' instance annotation must be before any property or property annotation in a resource object. +ODataJsonLightResourceDeserializer_CannotReadResourceSetContentStart=A node of type '{0}' was read from the JSON reader when trying to read the start of the content of a resource set; however, a node of type 'StartArray' was expected. +ODataJsonLightResourceDeserializer_ExpectedResourceSetPropertyNotFound=Did not find the required '{0}' property for the expected resource set. +ODataJsonLightResourceDeserializer_InvalidNodeTypeForItemsInResourceSet=A node of type '{0}' was read from the JSON reader when trying to read the entries of a typed resource set; however, a node of type 'StartObject' or 'EndArray', or a null value, was expected. +ODataJsonLightResourceDeserializer_InvalidPropertyAnnotationInTopLevelResourceSet=A property annotation for a property with name '{0}' was found when reading a top-level resource set. No property annotations, only instance annotations are allowed when reading top-level resource sets. +ODataJsonLightResourceDeserializer_InvalidPropertyInTopLevelResourceSet=A property with name '{0}' was found when reading a top-level resource set. No properties other than the resource set property with name '{1}' are allowed. +ODataJsonLightResourceDeserializer_PropertyWithoutValueWithWrongType=A property '{0}' which only has property annotations in the payload but no property value is declared to be of type '{1}'. In OData, only navigation properties and named streams can be represented as properties without values. +ODataJsonLightResourceDeserializer_OpenPropertyWithoutValue=A property '{0}' which only has property annotations in the payload but no property value is an open property. In OData, open property must be represented as a property with value. +ODataJsonLightResourceDeserializer_StreamPropertyInRequest=A stream property {0} was found in a JSON Light request payload. Stream properties are only supported in responses. +ODataJsonLightResourceDeserializer_UnexpectedStreamPropertyAnnotation=The stream property '{0}' has a property annotation '{1}'. Stream property can only have the 'odata.mediaEditLink', 'odata.mediaReadLink', 'odata.mediaEtag' and 'odata.mediaContentType' property annotations. +ODataJsonLightResourceDeserializer_StreamPropertyWithValue=A stream property '{0}' has a value in the payload. In OData, stream property must not have a value, it must only use property annotations. +ODataJsonLightResourceDeserializer_UnexpectedDeferredLinkPropertyAnnotation=The navigation property '{0}' has a property annotation '{1}'. Deferred navigation links can only have the 'odata.navigationLink' and 'odata.associationLink' property annotations. +ODataJsonLightResourceDeserializer_CannotReadSingletonNestedResource=A node of type '{0}' was read from the JSON reader when trying to read the contents of the property '{1}'; however, a 'StartObject' node or 'PrimitiveValue' node with null value was expected. +ODataJsonLightResourceDeserializer_CannotReadCollectionNestedResource=A node of type '{0}' was read from the JSON reader when trying to read the contents of the property '{1}'; however, a 'StartArray' node was expected. +ODataJsonLightResourceDeserializer_CannotReadNestedResource=A 'PrimitiveValue' node with non-null value was found when trying to read the value of the property '{0}'; however, a 'StartArray' node, a 'StartObject' node, or a 'PrimitiveValue' node with null value was expected. +ODataJsonLightResourceDeserializer_UnexpectedExpandedSingletonNavigationLinkPropertyAnnotation=The navigation property '{0}' has a property annotation '{1}'. Expanded resource navigation links can only have the 'odata.context', 'odata.navigationLink' and 'odata.associationLink' property annotations. +ODataJsonLightResourceDeserializer_UnexpectedExpandedCollectionNavigationLinkPropertyAnnotation=The navigation property '{0}' has a property annotation '{1}'. Expanded resource set navigation links can only have the 'odata.context', 'odata.navigationLink', 'odata.associationLink' and 'odata.nextLink' property annotations +ODataJsonLightResourceDeserializer_UnexpectedComplexCollectionPropertyAnnotation=The property '{0}' has a property annotation '{1}'. The complex collection property can only have the 'odata.count', 'odata.type' and 'odata.nextLink' property annotations. +ODataJsonLightResourceDeserializer_DuplicateNestedResourceSetAnnotation=Multiple property annotations '{0}' were found when reading the nested resource '{1}'. Only a single property annotation '{0}' can be specified for a nested resource. +ODataJsonLightResourceDeserializer_UnexpectedPropertyAnnotationAfterExpandedResourceSet=A property annotation '{0}' was found after the property '{1}' it is annotating. Only the 'odata.nextLink' property annotation can be used after the property it is annotating. +ODataJsonLightResourceDeserializer_UnexpectedNavigationLinkInRequestPropertyAnnotation=The navigation property '{0}' has a property annotation '{1}'. Navigation links in request payloads can only have the '{2}' property annotation. +ODataJsonLightResourceDeserializer_ArrayValueForSingletonBindPropertyAnnotation=The resource reference navigation property '{0}' has a property annotation '{1}' with an array value. Resource reference navigation properties can only have a property annotation '{1}' with a string value. +ODataJsonLightResourceDeserializer_StringValueForCollectionBindPropertyAnnotation=The resource set reference navigation property '{0}' has a property annotation '{1}' with a string value. Resource set reference navigation properties can only have a property annotation '{1}' with an array value. +ODataJsonLightResourceDeserializer_EmptyBindArray=The value of '{0}' property annotation is an empty array. The '{0}' property annotation must have a non-empty array as its value. +ODataJsonLightResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink=The navigation property '{0}' has no expanded value and no '{1}' property annotation. Navigation property in request without expanded value must have the '{1}' property annotation. +ODataJsonLightResourceDeserializer_SingletonNavigationPropertyWithBindingAndValue=The resource reference navigation property '{0}' has both the '{1}' property annotation as well as a value. Resource reference navigation properties can have either '{1}' property annotations or values, but not both. +ODataJsonLightResourceDeserializer_PropertyWithoutValueWithUnknownType=An undeclared property '{0}' which only has property annotations in the payload but no property value was found in the payload. In OData, only declared navigation properties and declared named streams can be represented as properties without values. +ODataJsonLightResourceDeserializer_OperationIsNotActionOrFunction=Encountered the operation '{0}' which can not be resolved to an ODataAction or ODataFunction. +ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation=Multiple '{0}' properties were found for an operation '{1}'. In OData, an operation can have at most one '{0}' property. +ODataJsonLightResourceDeserializer_OperationMissingTargetProperty=Multiple target bindings encountered for the operation '{0}' but the 'target' property was not found in an operation value. To differentiate between multiple target bindings, each operation value must have exactly one 'target' property. +ODataJsonLightResourceDeserializer_MetadataReferencePropertyInRequest=A metadata reference property was found in a JSON Light request payload. Metadata reference properties are only supported in responses. + +ODataJsonLightValidationUtils_OperationPropertyCannotBeNull=The '{0}' property of the operation '{1}' cannot have a null value. +ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported=Encountered a reference into metadata '{0}' which does not refer to the known metadata url '{1}'. Open metadata reference properties are not supported. + +ODataJsonLightDeserializer_RelativeUriUsedWithouODataMetadataAnnotation=A relative URI value '{0}' was specified in the payload, but the {1} annotation is missing from the payload. The payload must only contain absolute URIs or the {1} annotation must be on the payload. + +ODataJsonLightResourceMetadataContext_MetadataAnnotationMustBeInPayload=The {0} annotation is missing from the payload. + +ODataJsonLightCollectionDeserializer_ExpectedCollectionPropertyNotFound=When trying to read the start of a collection, the expected collection property with name '{0}' was not found. +ODataJsonLightCollectionDeserializer_CannotReadCollectionContentStart=A node of type '{0}' was read from the JSON reader when trying to read the items of a collection; however, a 'StartArray' node was expected. +ODataJsonLightCollectionDeserializer_CannotReadCollectionEnd=A property or annotation for a property with name '{0}' or an instance annotation with name '{0}' was found after reading the items of a top-level collection. No additional properties or annotations are allowed after the collection property. +ODataJsonLightCollectionDeserializer_InvalidCollectionTypeName=An 'odata.type' annotation with value '{0}' was found for a top-level collection payload; however, top-level collections must specify a collection type. + +ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkMustBeObjectValue=A node of type '{0}' was read from the JSON reader when trying to read the start of an entity reference link. In JSON Light, entity reference links must be objects. +ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLink=A property annotation with name '{0}' was detected when reading an entity reference link; entity reference links do not support property annotations. +ODataJsonLightEntityReferenceLinkDeserializer_InvalidAnnotationInEntityReferenceLink=An instance annotation with name '{0}' or a property annotation for the property with name '{0}' was found when reading an entity reference link. No OData property or instance annotations are allowed when reading entity reference links. +ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyInEntityReferenceLink=A property with name '{0}' was found when reading an entity reference link. No properties other than the entity reference link property with name '{1}' are allowed. +ODataJsonLightEntityReferenceLinkDeserializer_MissingEntityReferenceLinkProperty=The required property '{0}' for an entity reference link was not found. +ODataJsonLightEntityReferenceLinkDeserializer_MultipleUriPropertiesInEntityReferenceLink=Multiple '{0}' properties were found in an entity reference link object; however, a single '{0}' property was expected. +ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkUrlCannotBeNull=The '{0}' property of an entity reference link object cannot have a null value. +ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLinks=A property annotation was found for entity reference links; however, entity reference links only support instance annotations. +ODataJsonLightEntityReferenceLinkDeserializer_InvalidEntityReferenceLinksPropertyFound=A property with name '{0}' or a property annotation for a property with name '{0}' was found when trying to read a collection of entity reference links; however, a property with name '{1}' was expected. +ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyAnnotationInEntityReferenceLinks=A property annotation for a property with name '{0}' was found when reading an entity reference links payload. No property annotations, only instance annotations are allowed when reading entity reference links. +ODataJsonLightEntityReferenceLinkDeserializer_ExpectedEntityReferenceLinksPropertyNotFound=Did not find the required '{0}' property for an entity reference links payload. + +ODataJsonOperationsDeserializerUtils_OperationPropertyCannotBeNull=The '{0}' property of an operation '{1}' in '{2}' cannot have a null value. +ODataJsonOperationsDeserializerUtils_OperationsPropertyMustHaveObjectValue=Found a node of type '{1}' when starting to read the '{0}' operations value, however a node of type 'StartObject' was expected. The '{0}' operations value must have an object value. + +ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocument=Multiple '{0}' properties were found in a service document. In OData, a service document must have exactly one '{0}' property. +ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement=Multiple '{0}' properties were found in a service document element. In OData, a service document element must have exactly one '{0}' property. +ODataJsonLightServiceDocumentDeserializer_MissingValuePropertyInServiceDocument=No '{0}' property was found for a service document. In OData, a service document must have exactly one '{0}' property. +ODataJsonLightServiceDocumentDeserializer_MissingRequiredPropertyInServiceDocumentElement=Encountered a service document element without a '{0}' property. In service documents, service document elements must contain a '{0}' property. +ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocument=An unrecognized property annotation '{0}' was found in a '{1}' object in a service document. OData property annotations are not allowed in workspaces. +ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocument=An unrecognized instance annotation '{0}' was found in a '{1}' object in a service document. OData instance annotations are not allowed in workspaces. +ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocumentElement=An unrecognized property annotation '{0}' was found in a service document element. OData property annotations are not allowed in service document elements. +ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocumentElement=An unrecognized instance annotation '{0}' was found in a service document element. OData instance annotations are not allowed in service document elements. +ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocumentElement=Encountered unexpected property '{0}' in a service document element. In service documents, service document element may only have '{1}' and '{2}' properties. +ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocument=Encountered unexpected property '{0}' in a service document. The top level object of a service document may only have a '{1}' property. +ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationWithoutProperty=Encountered a property annotation for the property '{0}' which wasn't immediately followed by the property. Property annotations must occur directly before the property being annotated. + +ODataJsonLightParameterDeserializer_PropertyAnnotationForParameters=An OData property annotation was found for a parameter payload; however, parameter payloads do not support OData property annotations. +ODataJsonLightParameterDeserializer_PropertyAnnotationWithoutPropertyForParameters=One or more property annotations for property '{0}' were found in a parameter payload without the property to annotate. Parameter payloads must not contain property annotations for properties that are not in the payload. +ODataJsonLightParameterDeserializer_UnsupportedPrimitiveParameterType=The parameter '{0}' is of the '{1}' primitive type, which is not supported in JSON Light. +ODataJsonLightParameterDeserializer_NullCollectionExpected=When trying to read a null collection parameter value in JSON Light, a node of type '{0}' with the value '{1}' was read from the JSON reader; however, a primitive 'null' value was expected. +ODataJsonLightParameterDeserializer_UnsupportedParameterTypeKind=The parameter '{0}' is of an unsupported type kind '{1}'. Only primitive, enum, complex, primitive collection, enum collection and complex collection types are supported. + +SelectedPropertiesNode_StarSegmentNotLastSegment=When parsing a select clause a '*' segment was found before last segment of a property path. In OData, a '*' segment can only appear as last segment of a property path. +SelectedPropertiesNode_StarSegmentAfterTypeSegment=When parsing a select clause a '*' segment was found immediately after a type segment in a property path. In OData, a '*' segment cannot appear following a type segment. + +ODataJsonLightErrorDeserializer_PropertyAnnotationNotAllowedInErrorPayload=An OData property annotation '{0}' was found in an error payload; however, error payloads do not support OData property annotations. +ODataJsonLightErrorDeserializer_InstanceAnnotationNotAllowedInErrorPayload=An OData instance annotation '{0}' was found in an error payload; however, error payloads do not support OData instance annotations. +ODataJsonLightErrorDeserializer_PropertyAnnotationWithoutPropertyForError=One or more property annotations for property '{0}' were found in an error payload without the property to annotate. Error payloads must not contain property annotations for properties that are not in the payload. +ODataJsonLightErrorDeserializer_TopLevelErrorValueWithInvalidProperty=A property with name '{0}' was found in the error value of a top-level error. In OData, a top-level error value can only have properties with name 'code', 'message', or 'innererror', or custom instance annotations. + +ODataConventionalUriBuilder_EntityTypeWithNoKeyProperties=The entity type '{0}' has no key properties. Entity types must define at least one key property. +ODataConventionalUriBuilder_NullKeyValue=The key property '{0}' on type '{1}' has a null value. Key properties must not have null values. + +ODataResourceMetadataContext_EntityTypeWithNoKeyProperties=An ODataResource of type '{0}' is found without key properties. When writing without a user model, each resource must contain at least one property whose 'ODataProperty.SerializationInfo.PropertyKind' set to 'ODataPropertyKind.Key'. When writing with a user model, the entity type '{0}' defined in the model must define at least one key property. +ODataResourceMetadataContext_NullKeyValue=The key property '{0}' on type '{1}' has a null value. Key properties must not have null values. +ODataResourceMetadataContext_KeyOrETagValuesMustBePrimitiveValues=The property '{0}' on type '{1}' is a non-primitive value. All key and etag properties must be of primitive types. +ODataResource_PropertyValueCannotBeODataResourceValue=The value of a property '{0}' in ODataResource cannot be of type ODataResourceValue or collection of ODataResourceValue. + +EdmValueUtils_NonPrimitiveValue=The primitive property '{0}' on type '{1}' has a value which is not a primitive value. +EdmValueUtils_PropertyDoesntExist=The entity instance value of type '{0}' doesn't have a value for property '{1}'. To compute an entity's metadata, its key and concurrency-token property values must be provided. + +ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromNull=Cannot create an ODataPrimitiveValue from null; use ODataNullValue instead. +ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromUnsupportedValueType=An ODataPrimitiveValue was instantiated with a value of type '{0}'. ODataPrimitiveValue can only wrap values which can be represented as primitive EDM types. + +ODataInstanceAnnotation_NeedPeriodInName='{0}' is an invalid instance annotation name. An instance annotation name must contain a period that is not at the start or end of the name. +ODataInstanceAnnotation_ReservedNamesNotAllowed='{0}' is a reserved instance annotation name because it starts with '{1}'. Reserved names are not allowed for custom instance annotations. +ODataInstanceAnnotation_BadTermName='{0}' is an invalid instance annotation name. +ODataInstanceAnnotation_ValueCannotBeODataStreamReferenceValue=The value of an instance annotation cannot be of type ODataStreamReferenceValue. + +ODataJsonLightValueSerializer_MissingTypeNameOnCollection=A type name was not provided for an instance of ODataCollectionValue. +ODataJsonLightValueSerializer_MissingRawValueOnUntyped=A raw value was not provided for an instance of ODataUntypedValue. + +AtomInstanceAnnotation_MissingTermAttributeOnAnnotationElement=Encountered an 'annotation' element without a 'term' attribute. All 'annotation' elements must have a 'term' attribute. +AtomInstanceAnnotation_AttributeValueNotationUsedWithIncompatibleType=The value of the 'type' attribute on an 'annotation' element was '{0}', which is incompatible with the '{1}' attribute. +AtomInstanceAnnotation_AttributeValueNotationUsedOnNonEmptyElement=Encountered the attribute '{0}' on a non-empty 'annotation' element. If attribute value notation is used to specify the annotation's value, then there can be no body to the element. +AtomInstanceAnnotation_MultipleAttributeValueNotationAttributes=Encountered an 'annotation' element with more than one attribute from following set: 'int', 'string', 'decimal', 'float', and 'bool'. Only one such attribute may appear on an 'annotation' element. + +AnnotationFilterPattern_InvalidPatternMissingDot=The pattern '{0}' is not a valid pattern to match an annotation. It must contain at least one '.' separating the namespace and the name segments of an annotation. +AnnotationFilterPattern_InvalidPatternEmptySegment=The pattern '{0}' is not a valid pattern to match an annotation. It must not contain a namespace or name segment that is empty. +AnnotationFilterPattern_InvalidPatternWildCardInSegment=The pattern '{0}' is not a supported pattern to match an annotation. It must not contain '*' as part of a segment. +AnnotationFilterPattern_InvalidPatternWildCardMustBeInLastSegment=The pattern '{0}' is not a supported pattern to match an annotation. '*' must be the last segment of the pattern. + + +; URI PARSER +SyntacticTree_UriMustBeAbsolute=The specified URI '{0}' must be absolute. +SyntacticTree_MaxDepthInvalid=The maximum depth setting must be a number greater than zero. +SyntacticTree_InvalidSkipQueryOptionValue=Invalid value '{0}' for $skip query option found. The $skip query option requires a non-negative integer value. +SyntacticTree_InvalidTopQueryOptionValue=Invalid value '{0}' for $top query option found. The $top query option requires a non-negative integer value. +SyntacticTree_InvalidCountQueryOptionValue=Invalid value '{0}' for $count query option found. Valid values are '{1}'. +SyntacticTree_InvalidIndexQueryOptionValue=Invalid value '{0}' for $index query option found. The $index query option requires an integer value. + +QueryOptionUtils_QueryParameterMustBeSpecifiedOnce=Query option '{0}' was specified more than once, but it must be specified at most once. + +UriBuilder_NotSupportedClrLiteral=The CLR literal of type '{0}' is not supported to be written as a Uri part. +UriBuilder_NotSupportedQueryToken=QueryToken '{0}' is not supported to be written as a Uri part. + +UriQueryExpressionParser_TooDeep=Recursion depth exceeded allowed limit. +UriQueryExpressionParser_ExpressionExpected=Expression expected at position {0} in '{1}'. +UriQueryExpressionParser_OpenParenExpected='(' expected at position {0} in '{1}'. +UriQueryExpressionParser_CloseParenOrCommaExpected=')' or ',' expected at position {0} in '{1}'. +UriQueryExpressionParser_CloseParenOrOperatorExpected=')' or operator expected at position {0} in '{1}'. +UriQueryExpressionParser_CannotCreateStarTokenFromNonStar=Expecting a Star token but got: '{0}'. +UriQueryExpressionParser_RangeVariableAlreadyDeclared=The range variable '{0}' has already been declared. +UriQueryExpressionParser_AsExpected='as' expected at position {0} in '{1}'. +UriQueryExpressionParser_WithExpected='with' expected at position {0} in '{1}'. +UriQueryExpressionParser_UnrecognizedWithMethod=Unrecognized with '{0}' at '{1}' in '{2}'. +UriQueryExpressionParser_PropertyPathExpected=Expression expected at position {0} in '{1}'. +UriQueryExpressionParser_KeywordOrIdentifierExpected='{0}' expected at position {1} in '{2}'. +UriQueryExpressionParser_InnerMostExpandRequireFilter=The inner most expand transformation requires a filter transformation at position {0} in '{1}'. + +UriQueryPathParser_RequestUriDoesNotHaveTheCorrectBaseUri=The URI '{0}' is not valid because it is not based on '{1}'. +UriQueryPathParser_SyntaxError=Bad Request: there was an error in the query syntax. +UriQueryPathParser_TooManySegments=Too many segments in URI. +UriQueryPathParser_InvalidEscapeUri=The URI part '{0}' is not valid because there's no leading escape character. + +UriUtils_DateTimeOffsetInvalidFormat=The DateTimeOffset text '{0}' should be in format 'yyyy-mm-ddThh:mm:ss('.'s+)?(zzzzzz)?' and each field value is within valid range. + +SelectionItemBinder_NonNavigationPathToken=Inner or start path segments must be navigation properties in $select. + +MetadataBinder_UnsupportedQueryTokenKind=An unsupported query token kind '{0}' was found. +MetadataBinder_PropertyNotDeclared=Could not find a property named '{1}' on type '{0}'. +MetadataBinder_PropertyNotDeclaredOrNotKeyInKeyValue=Property '{0}' is not declared on type '{1}' or is not a key property. Only key properties can be used in key lookups. +MetadataBinder_QualifiedFunctionNameWithParametersNotDeclared=Could not find a function named '{0}' with parameters '{1}'. +MetadataBinder_UnnamedKeyValueOnTypeWithMultipleKeyProperties=An unnamed key value was used in a key lookup on a type '{0}' which has more than one key property. Unnamed key value can only be used on a type with one key property. +MetadataBinder_DuplicitKeyPropertyInKeyValues=A key property '{0}' was found twice in a key lookup. Each key property can be specified just once in a key lookup. +MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues=A key lookup on type '{0}' didn't specify values for all key properties. All key properties must be specified in a key lookup. +MetadataBinder_CannotConvertToType=Expression of type '{0}' cannot be converted to type '{1}'. +MetadataBinder_FilterExpressionNotSingleValue=The $filter expression must evaluate to a single boolean value. +MetadataBinder_OrderByExpressionNotSingleValue=The $orderby expression must evaluate to a single value of primitive type. +MetadataBinder_PropertyAccessWithoutParentParameter=A PropertyAccessQueryToken without a parent was encountered outside of $filter or $orderby expression. The PropertyAccessQueryToken without a parent token is only allowed inside $filter or $orderby expressions. +MetadataBinder_BinaryOperatorOperandNotSingleValue=The operand for a binary operator '{0}' is not a single value. Binary operators require both operands to be single values. +MetadataBinder_UnaryOperatorOperandNotSingleValue=The operand for a unary operator '{0}' is not a single value. Unary operators require the operand to be a single value. +MetadataBinder_LeftOperandNotSingleValue=The left operand for the IN operation is not a single value. IN operations require the left operand to be a single value and the right operand to be a collection value. +MetadataBinder_RightOperandNotCollectionValue=The right operand for the IN operation is not a collection value. IN operations require the left operand to be a single value and the right operand to be a collection value. +MetadataBinder_PropertyAccessSourceNotSingleValue=The parent value for a property access of a property '{0}' is not a single value. Property access can only be applied to a single value. +MetadataBinder_IncompatibleOperandsError=A binary operator with incompatible types was detected. Found operand types '{0}' and '{1}' for operator kind '{2}'. +MetadataBinder_IncompatibleOperandError=A unary operator with an incompatible type was detected. Found operand type '{0}' for operator kind '{1}'. +MetadataBinder_UnknownFunction=An unknown function with name '{0}' was found. This may also be a function import or a key lookup on a navigation property, which is not allowed. +MetadataBinder_FunctionArgumentNotSingleValue=The argument for an invocation of a function with name '{0}' is not a single value. All arguments for this function must be single values. +MetadataBinder_NoApplicableFunctionFound=No function signature for the function with name '{0}' matches the specified arguments. The function signatures considered are: {1}. +MetadataBinder_BoundNodeCannotBeNull=A token of kind '{0}' was bound to the value null; this is invalid. A query token must always be bound to a non-null query node. +MetadataBinder_TopRequiresNonNegativeInteger=The value '{0}' is not a non-negative integer value. In OData, the $top query option must specify a non-negative integer value. +MetadataBinder_SkipRequiresNonNegativeInteger=The value '{0}' is not a non-negative integer value. In OData, the $skip query option must specify a non-negative integer value. +MetadataBinder_QueryOptionsBindStateCannotBeNull=The bind state cannot be null. In OData, the bind state for query options should not be null and there should be query options in the object. +MetadataBinder_QueryOptionsBindMethodCannotBeNull=The bind method cannot be null. In OData, the processing of query options should have a corresponding bind method. +MetadataBinder_HierarchyNotFollowed=Encountered invalid type cast. '{0}' is not assignable from '{1}'. +MetadataBinder_LambdaParentMustBeCollection=Any/All may only be used following a collection. +MetadataBinder_ParameterNotInScope=The parameter '{0}' is not in scope. +MetadataBinder_NavigationPropertyNotFollowingSingleEntityType=A navigation property can only follow single entity nodes. +MetadataBinder_AnyAllExpressionNotSingleValue=The Any/All query expression must evaluate to a single boolean value. +MetadataBinder_CastOrIsOfExpressionWithWrongNumberOfOperands=The Cast or IsOf expression has an invalid number of operands: number of operands is '{0}' and it should be 1 or 2. +MetadataBinder_CastOrIsOfFunctionWithoutATypeArgument=Cast or IsOf Function must have a type in its arguments. +MetadataBinder_CastOrIsOfCollectionsNotSupported=The Cast and IsOf functions do not support collection arguments or types. +MetadataBinder_CollectionOpenPropertiesNotSupportedInThisRelease=Collection open properties are not supported in this release. +MetadataBinder_IllegalSegmentType=Can only bind segments that are Navigation, Structural, Complex, or Collections. We found a segment '{0}' that isn't any of those. Please revise the query. +MetadataBinder_QueryOptionNotApplicable=The '{0}' option cannot be applied to the query path. '{0}' can only be applied to a collection of entities. +StringItemShouldBeQuoted=String item should be single/double quoted: '{0}'. +StreamItemInvalidPrimitiveKind=Invalid PrimitiveTypeKind {0}. A Stream item must be of type binary or string, or none if unknown." +ApplyBinder_AggregateExpressionIncompatibleTypeForMethod=$apply/aggregate expression '{0}' operation does not support value type '{1}'. +ApplyBinder_UnsupportedAggregateMethod=$apply/aggregate does not support method '{0}'. +ApplyBinder_UnsupportedAggregateKind=$apply/aggregate expression token kind '{0}' not supported. +ApplyBinder_AggregateExpressionNotSingleValue=$apply/aggregate expression '{0}' must evaluate to a single value. +ApplyBinder_GroupByPropertyNotPropertyAccessValue=$apply/groupby grouping expression '{0}' must evaluate to a property access value. +ApplyBinder_UnsupportedType=$apply clause does not support type '{0}'. +ApplyBinder_UnsupportedGroupByChild=$apply/groupby not support '{0}' as child transformation + +AggregateTransformationNode_UnsupportedAggregateExpressions=There are unsupported aggregation expressions in the transformation node. + +FunctionCallBinder_CannotFindASuitableOverload=Cannot find a suitable overload for function '{0}' that takes '{1}' arguments. +FunctionCallBinder_UriFunctionMustHaveHaveNullParent=Found a Uri function '{0}' with a parent token. Uri functions cannot have parent tokens. +FunctionCallBinder_CallingFunctionOnOpenProperty=Found a function '{0}' on an open property. Functions on open properties are not supported. +FunctionCallParser_DuplicateParameterOrEntityKeyName=Parameter or entity key names must be unique. There is most likely an error in the model. + +ODataUriParser_InvalidCount='{0}' is not a valid count option. + +CastBinder_ChildTypeIsNotEntity=The child type '{0}' in a cast was not an entity type. Casts can only be performed on entity types. +CastBinder_EnumOnlyCastToOrFromString=Enumeration type value can only be casted to or from string. +Binder_IsNotValidEnumConstant=The string '{0}' is not a valid enumeration type constant. + +BatchReferenceSegment_InvalidContentID=Invalid content-id '{0}' for batch reference segment. + +SelectExpandBinder_UnknownPropertyType=Property '{0}' is of an unrecognized EdmPropertyKind. +SelectionItemBinder_NoExpandForSelectedProperty=Only properties specified in $expand can be traversed in $select query options. Selected item was '{0}'. + +SelectExpandPathBinder_FollowNonTypeSegment=Trying to follow type segments on a segment that isn't a type. Segment was '{0}'. + +SelectPropertyVisitor_SystemTokenInSelect=Found a system token, '{0}', while parsing a select clause. +SelectPropertyVisitor_DisparateTypeSegmentsInSelectExpand=Any selection that is expanded must have the same type qualifier on both selection and expansion. + +SelectBinder_MultiLevelPathInSelect=Found a path with multiple navigation properties or a bad complex property path in a select clause. Please reword your query such that each level of select or expand only contains either TypeSegments or Properties. + +ExpandItemBinder_TraversingANonNormalizedTree=Trying to traverse a non-normalized expand tree. +ExpandItemBinder_CannotFindType=The type '{0}' is not defined in the model. +ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty=Property '{0}' on type '{1}' is not a navigation property or complex property. Only navigation properties can be expanded. +ExpandItemBinder_TypeSegmentNotFollowedByPath=Found a path within a select or expand query option that isn't ended by a non-type segment. +ExpandItemBinder_PathTooDeep=Trying to parse a type segment path that is too long. +ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath=Found a path traversing multiple navigation properties. Please rephrase the query such that each expand path contains only type segments and navigation properties. +ExpandItemBinder_LevelsNotAllowedOnIncompatibleRelatedType=The $level option on navigation property '{0}' is not allowed, because the related entity type '{1}' could not be cast to source entity type '{2}'. +ExpandItemBinder_InvaidSegmentInExpand=Segment '{0}' is not valid in expand path. Before navigation property, only type segment or entity or complex property can exist. + +Nodes_CollectionNavigationNode_MustHaveSingleMultiplicity=The navigation property must have a target multiplicity of 'One' or 'ZeroOrOne' to create a SingleNavigationNode. +Nodes_NonentityParameterQueryNodeWithEntityType=An entity type '{0}' was given to NonEntityParameterQueryNode. Use EntityParameterQueryNode instead. +Nodes_CollectionNavigationNode_MustHaveManyMultiplicity=The navigation property must have a target multiplicity of 'Many' to create a CollectionNavigationNode. +Nodes_PropertyAccessShouldBeNonEntityProperty=A node of this kind requires the associated property to be a structural, non-collection type, but property '{0}' is not structural. +Nodes_PropertyAccessTypeShouldNotBeCollection=A node of this kind requires the associated property to be a structural, non-collection type, but property '{0}' is a collection. +Nodes_PropertyAccessTypeMustBeCollection=A node of this kind requires the associated property to be a structural, collection type, but property '{0}' is not a collection. +Nodes_NonStaticEntitySetExpressionsAreNotSupportedInThisRelease=Only static Entity Set reference expressions are supported currently. +Nodes_CollectionFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum=An instance of CollectionFunctionCallNode can only be created with a primitive, complex or enum collection type. For functions returning a collection of entities, use EntityCollectionFunctionCallNode instead. +Nodes_EntityCollectionFunctionCallNode_ItemTypeMustBeAnEntity=An instance of EntityCollectionFunctionCallNode can only be created with an entity collection type. For functions returning a collection of primitive or complex values, use CollectionFunctionCallNode instead. +Nodes_SingleValueFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum=An instance of SingleValueFunctionCallNode can only be created with a primitive, complex or enum type. For functions returning a single entity, use SingleEntityFunctionCallNode instead. +Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType=An instance of InNode can only be created where the item types of the right operand '{0}' and the left operand '{1}' can be compared. + +ExpandTreeNormalizer_NonPathInPropertyChain=Found a segment that isn't a path while parsing the path within a select or expand query option. + +UriExpandParser_TermIsNotValidForStar=Term '{0}' is not valid in a $expand expression, as only $level option is allowed when the expanded navigation property is star. +UriExpandParser_TermIsNotValidForStarRef=Term '{0}' is not valid in a $expand expression, no option is allowed when the expanded navigation property is */$ref. +UriExpandParser_ParentEntityIsNull=Cannot get parent entity type for term '{0}' to auto populate all navigation properties. +UriExpandParser_TermWithMultipleStarNotAllowed=Term '{0}' is not valid in a $expand expression as multiple stars are not allowed. +UriSelectParser_TermIsNotValid=Term '{0}' is not valid in a $select or $expand expression. +UriSelectParser_InvalidTopOption=Top option must be a non-negative integer, it is set to '{0}' instead. +UriSelectParser_InvalidSkipOption=Skip option must be a non-negative integer, it is set to '{0}' instead. +UriSelectParser_InvalidCountOption=Count option must be a boolean value, it is set to '{0}' instead. +UriSelectParser_InvalidLevelsOption=Levels option must be a non-negative integer or 'max', it is set to '{0}' instead. +UriSelectParser_SystemTokenInSelectExpand=Found system token '{0}' in select or expand clause '{1}'. +UriParser_MissingExpandOption=Missing expand option on navigation property '{0}'. If a parenthesis expression follows an expanded navigation property, then at least one expand option must be provided. +UriParser_MissingSelectOption=Missing select option on property '{0}'. If a parenthesis expression follows a selected property, then at least one query option must be provided. +UriParser_RelativeUriMustBeRelative=Parameter 'relativeUri' must be a relative Uri if serviceRoot is not specified. +UriParser_NeedServiceRootForThisOverload=A service root URI must be provided to the ODataUriParser in order to use this method. +UriParser_UriMustBeAbsolute=The URI '{0}' must be an absolute URI. +UriParser_NegativeLimit=The limit must be greater than or equal to zero +UriParser_ExpandCountExceeded=The result of parsing $expand contained at least {0} items, but the maximum allowed is {1}. +UriParser_ExpandDepthExceeded=The result of parsing $expand was at least {0} items deep, but the maximum allowed is {1}. +UriParser_TypeInvalidForSelectExpand=The type '{0}' is not valid for $select or $expand, only structured types are allowed. +UriParser_ContextHandlerCanNotBeNull=The handler property for context '{0}' should not return null. +UriParserMetadata_MultipleMatchingPropertiesFound=More than one properties match the name '{0}' were found in type '{1}'. +UriParserMetadata_MultipleMatchingNavigationSourcesFound=More than one navigation sources match the name '{0}' were found in model. +UriParserMetadata_MultipleMatchingTypesFound=More than one types match the name '{0}' were found in model. +UriParserMetadata_MultipleMatchingKeysFound=More than one keys match the name '{0}' were found. +UriParserMetadata_MultipleMatchingParametersFound=More than one parameters match the name '{0}' were found. + +PathParser_EntityReferenceNotSupported=The request URI is not valid. $ref cannot be applied to the segment '{0}' since $ref can only follow an entity segment or entity collection segment. +PathParser_CannotUseValueOnCollection=$value cannot be applied to a collection. +PathParser_TypeMustBeRelatedToSet=The type '{0}' does not inherit from and is not a base type of '{1}'. The type of '{2}' must be related to the Type of the EntitySet. +PathParser_TypeCastOnlyAllowedAfterStructuralCollection=Type cast segment '{0}' after a collection which is not of entity or complex type is not allowed. +PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint=Type cast segment '{0}' on {1} '{2}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation. + +ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink=A resource set may contain a next page link, a delta link or neither, but must not contain both. + +ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty=The last segment, and only the last segment, must be a navigation property in $expand. +ODataExpandPath_InvalidExpandPathSegment=Found a segment of type '{0} in an expand path, but only NavigationProperty, Property and Type segments are allowed. + +ODataSelectPath_CannotOnlyHaveTypeSegment=TypeSegment cannot be the only segment in a $select. +ODataSelectPath_InvalidSelectPathSegmentType=Found a segment of type '{0} in a select path, but only TypeSegment, NavigationPropertySegment, PropertySegment, OperationSegment or OpenPropertySegments are allowed. +ODataSelectPath_OperationSegmentCanOnlyBeLastSegment=An operation can only be the last segment in $select. +ODataSelectPath_NavPropSegmentCanOnlyBeLastSegment=A navigation property can only be the last segment in $select. + +RequestUriProcessor_TargetEntitySetNotFound=The target Entity Set of Navigation Property '{0}' could not be found. This is most likely an error in the IEdmModel. +RequestUriProcessor_FoundInvalidFunctionImport=The function overloads matching '{0}' are invalid. This is most likely an error in the IEdmModel. + +OperationSegment_ReturnTypeForMultipleOverloads=No type could be computed for this Segment since there were multiple possible operations with varying return types. +OperationSegment_CannotReturnNull=The return type from the operation is not possible with the given entity set. + +FunctionOverloadResolver_NoSingleMatchFound=Unable to resolve function overloads to a single function. There was more than one function in the model with name '{0}' and parameter names '{1}'. +FunctionOverloadResolver_MultipleActionOverloads=Multiple action overloads were found with the same binding parameter for '{0}'. +FunctionOverloadResolver_MultipleActionImportOverloads=Multiple action import overloads were found with the same binding parameter for '{0}'. +FunctionOverloadResolver_MultipleOperationImportOverloads=Multiple action import and function import overloads for '{0}' were found. +FunctionOverloadResolver_MultipleOperationOverloads=Multiple action and function overloads for '{0}' were found. +FunctionOverloadResolver_FoundInvalidOperation=The operation overloads matching '{0}' are invalid. This is most likely an error in the IEdmModel. +FunctionOverloadResolver_FoundInvalidOperationImport=The operation import overloads matching '{0}' are invalid. This is most likely an error in the IEdmModel. + +CustomUriFunctions_AddCustomUriFunction_BuiltInExistsNotAddingAsOverload=The given custom function '{0}' already exists as a Built-In function. Consider use 'addAsOverloadToBuiltInFunction = true' parameter. +CustomUriFunctions_AddCustomUriFunction_BuiltInExistsFullSignature=The given custom function '{0}' already exists as a Built-In function in one of it's overloads. Thus cannot override the Built-In function. +CustomUriFunctions_AddCustomUriFunction_CustomFunctionOverloadExists=The given function name '{0}' already exists as a custom function with the same overload. + +RequestUriProcessor_InvalidValueForEntitySegment=The ODataPathSegment provided (Id = {0}) is not an EntitySetSegment. +RequestUriProcessor_InvalidValueForKeySegment=The KeySegment provided (Id = {0}) is either null, having no keys, or does not target a single resource. + +RequestUriProcessor_CannotApplyFilterOnSingleEntities=$filter path segment cannot be applied on single entities or singletons. Entity type: '{0}'. +RequestUriProcessor_CannotApplyEachOnSingleEntities=$each set-based operation cannot be applied on single entities or singletons. Entity type: '{0}'. +RequestUriProcessor_FilterPathSegmentSyntaxError=The $filter path segment must be in the form $filter(expression), where the expression resolves to a boolean. +RequestUriProcessor_NoNavigationSourceFound=There are no navigation sources found to apply '{0}'. +RequestUriProcessor_OnlySingleOperationCanFollowEachPathSegment=Only a single operation can follow $each. + +; Copied from the server, will be incrementally replaced. +RequestUriProcessor_EmptySegmentInRequestUrl=Empty segment encountered in request URL. Please make sure that a valid request URL is specified. +RequestUriProcessor_SyntaxError=Bad Request - Error in query syntax. +RequestUriProcessor_CountOnRoot=The request URI is not valid, the segment $count cannot be applied to the root of the service. +RequestUriProcessor_FilterOnRoot=The request URI is not valid, the segment $filter cannot be applied to the root of the service. +RequestUriProcessor_EachOnRoot=The request URI is not valid, the segment $each cannot be applied to the root of the service. +RequestUriProcessor_RefOnRoot=The request URI is not valid, the segment $ref cannot be applied to the root of the service. +RequestUriProcessor_MustBeLeafSegment=The request URI is not valid. The segment '{0}' must be the last segment in the URI because it is one of the following: $ref, $batch, $count, $value, $metadata, a named media resource, an action, a noncomposable function, an action import, a noncomposable function import, an operation with void return type, or an operation import with void return type. +RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment=The request URI is not valid. The segment '{0}' must refer to a navigation property since the previous segment identifier is '{1}'. +RequestUriProcessor_MissingSegmentAfterLink=The request URI is not valid. There must a segment specified after the '{0}' segment and the segment must refer to a entity resource. +RequestUriProcessor_CountNotSupported=The request URI is not valid. $count cannot be applied to the segment '{0}' since $count can only follow an entity set, a collection navigation property, a structural property of collection type, an operation returning collection type or an operation import returning collection type. +RequestUriProcessor_CannotQueryCollections=The request URI is not valid. Since the segment '{0}' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource. +RequestUriProcessor_SegmentDoesNotSupportKeyPredicates=The request URI is not valid. The segment '{0}' cannot include key predicates, however it may end with empty parenthesis. +RequestUriProcessor_ValueSegmentAfterScalarPropertySegment=The segment '{1}' in the request URI is not valid. The segment '{0}' refers to a primitive property, function, or service operation, so the only supported value from the next segment is '$value'. +RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType=The type '{0}' specified in the URI is neither a base type nor a sub-type of the previously-specified type '{1}'. +OpenNavigationPropertiesNotSupportedOnOpenTypes=Open navigation properties are not supported on OpenTypes. Property name: '{0}'. +BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation=Error processing request stream. In batch mode, a resource can be cross-referenced only for bind/unbind operations. +DataServiceConfiguration_ResponseVersionIsBiggerThanProtocolVersion=The response requires that version {0} of the protocol be used, but the MaxProtocolVersion of the data service is set to {1}. +BadRequest_KeyCountMismatch=The number of keys specified in the URI does not match number of key properties for the resource '{0}'. +RequestUriProcessor_KeysMustBeNamed=Segments with multiple key values must specify them in 'name=value' form. +RequestUriProcessor_ResourceNotFound=Resource not found for the segment '{0}'. +RequestUriProcessor_BatchedActionOnEntityCreatedInSameChangeset=Batched service action '{0}' cannot be invoked because it was bound to an entity created in the same changeset. +RequestUriProcessor_Forbidden=Forbidden +RequestUriProcessor_OperationSegmentBoundToANonEntityType=Found an operation bound to a non-entity type. +RequestUriProcessor_NoBoundEscapeFunctionSupported=The request URI is not valid. The bound function binding to '{0}' does not support the escape function annotation. +RequestUriProcessor_EscapeFunctionMustHaveOneStringParameter=The UrlEscape function '{0}' must have exactly one non-binding parameter of type 'Edm.String'. + +; Note: The below list of error messages are common to both the OData and the OData.Query project. +General_InternalError=An internal error '{0}' occurred. + +ExceptionUtils_CheckIntegerNotNegative=A non-negative integer value was expected, but the value '{0}' is not a valid non-negative integer. +ExceptionUtils_CheckIntegerPositive=A positive integer value was expected, but the value '{0}' is not a valid positive integer. +ExceptionUtils_CheckLongPositive=A positive long value was expected; however, the value '{0}' is not a valid positive long value. +ExceptionUtils_ArgumentStringNullOrEmpty=Value cannot be null or empty. + +ExpressionToken_OnlyRefAllowWithStarInExpand=Only $ref is allowed with star in $expand option. +ExpressionToken_NoPropAllowedAfterRef=No property is allowed after $ref segment. +ExpressionToken_NoSegmentAllowedBeforeStarInExpand=No segment is allowed before star in $expand. +ExpressionToken_IdentifierExpected=An identifier was expected at position {0}. + +ExpressionLexer_UnterminatedStringLiteral=There is an unterminated string literal at position {0} in '{1}'. +ExpressionLexer_InvalidCharacter=Syntax error: character '{0}' is not valid at position {1} in '{2}'. +ExpressionLexer_SyntaxError=Syntax error at position {0} in '{1}'. +ExpressionLexer_UnterminatedLiteral=There is an unterminated literal at position {0} in '{1}'. +ExpressionLexer_DigitExpected=A digit was expected at position {0} in '{1}'. +ExpressionLexer_UnbalancedBracketExpression=Found an unbalanced bracket expression. +ExpressionLexer_InvalidNumericString=Numeric string '{0}' is not a valid Int32/Int64/Double/Decimal. +ExpressionLexer_InvalidEscapeSequence=An unrecognized escape sequence '\\{0}' was found at position {1} in '{2}'. + +UriQueryExpressionParser_UnrecognizedLiteral=Unrecognized '{0}' literal '{1}' at '{2}' in '{3}'. +UriQueryExpressionParser_UnrecognizedLiteralWithReason=Unrecognized '{0}' literal '{1}' at '{2}' in '{3}' with reason '{4}'. + +UriPrimitiveTypeParsers_FailedToParseTextToPrimitiveValue=Failed to parse '{0}' of Edm type '{1}' to primitive type. +UriPrimitiveTypeParsers_FailedToParseStringToGeography=Failed to parse string to Geography. + +UriCustomTypeParsers_AddCustomUriTypeParserAlreadyExists=The given uri custom type parser already exists. +UriCustomTypeParsers_AddCustomUriTypeParserEdmTypeExists=An existing custom UriTypeParser is already registered to the given EdmTypeReference '{0}'. + +UriParserHelper_InvalidPrefixLiteral=The given type prefix literal name '{0}' must contain letters or '.' only. + +CustomUriTypePrefixLiterals_AddCustomUriTypePrefixLiteralAlreadyExists=The given type literal prefix '{0}' already exists as a custom uri type literal prefix. + +; NOTE: these error messages are copied from EdmLib because they appear in shared source files. +ValueParser_InvalidDuration=The value '{0}' is not a valid duration value. + +; Note: The below list of error messages are common to the ODataLib, EdmLib, Spatial, Server and Client projects. +PlatformHelper_DateTimeOffsetMustContainTimeZone=The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information. + + +; Note: The below list of error messages are common to both the OData and the Spatial project. + +JsonReader_UnexpectedComma=Invalid JSON. An unexpected comma was found in scope '{0}'. A comma is only valid between properties of an object or between elements of an array. +JsonReader_ArrayClosureMismatch=Invalid JSON. A array closure mismatch occurred. A '{0}' was expected to match '{1}', but instead '{2}' was found. +JsonReader_MultipleTopLevelValues=Invalid JSON. More than one value was found at the root of the JSON content. JSON content can only have one value at the root level, which is an array, an object or a primitive value. +JsonReader_EndOfInputWithOpenScope=Invalid JSON. Unexpected end of input was found in JSON content. Not all object and array scopes were closed. +JsonReader_UnexpectedToken=Invalid JSON. Unexpected token '{0}'. +JsonReader_UnrecognizedToken=Invalid JSON. A token was not recognized in the JSON content. +JsonReader_MissingColon=Invalid JSON. A colon character ':' is expected after the property name '{0}', but none was found. +JsonReader_UnrecognizedEscapeSequence=Invalid JSON. An unrecognized escape sequence '{0}' was found in a JSON string value. +JsonReader_UnexpectedEndOfString=Invalid JSON. Unexpected end of input reached while processing a JSON string value. +JsonReader_InvalidNumberFormat=Invalid JSON. The value '{0}' is not a valid number. +JsonReader_InvalidBinaryFormat=Invalid Binary value. The value '{0}' is not a valid Base64 encoded value. +JsonReader_MissingComma=Invalid JSON. A comma character ',' was expected in scope '{0}'. Every two elements in an array and properties of an object must be separated by commas. +JsonReader_InvalidPropertyNameOrUnexpectedComma=Invalid JSON. The property name '{0}' is not valid. The name of a property cannot be empty. +JsonReader_MaxBufferReached=Cannot increase the JSON reader buffer to hold the input JSON which has very long token. +JsonReader_CannotAccessValueInStreamState=Cannot access the Value property while streaming a value. Please dispose the StreamReader or TextReader before continuing. +JsonReader_CannotCallReadInStreamState=Cannot call Read while streaming a value. Please dispose the StreamReader or TextReader before continuing. +JsonReader_CannotCreateReadStream=Cannot create a Stream in the current state. A Stream can only be created for reading a JSON string value when positioned on, and before accessing, the value. +JsonReader_CannotCreateTextReader=Cannot create a TextReader in the current state. A TextReader can only be created for reading a JSON string value when positioned on, and before accessing, the value. + +JsonReaderExtensions_UnexpectedNodeDetected=An unexpected '{1}' node was found when reading from the JSON reader. A '{0}' node was expected. +JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName=An unexpected '{1}' node was found for property named '{2}' when reading from the JSON reader. A '{0}' node was expected. +JsonReaderExtensions_CannotReadPropertyValueAsString=Cannot read the value '{0}' for the property '{1}' as a quoted JSON string value. +JsonReaderExtensions_CannotReadValueAsString=Cannot read the value '{0}' as a quoted JSON string value. +JsonReaderExtensions_CannotReadValueAsDouble=Cannot read the value '{0}' as a double numeric value. +JsonReaderExtensions_UnexpectedInstanceAnnotationName=An unexpected instance annotation name '{0}' was found when reading from the JSON reader, In OData, Instance annotation name must start with @. + +BufferUtils_InvalidBufferOrSize=The buffer from pool cannot be null or less than the required minimal size '{0}'. + +ServiceProviderExtensions_NoServiceRegistered=No service for type '{0}' has been registered. \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MimeConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MimeConstants.cs new file mode 100644 index 0000000..16068d5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MimeConstants.cs @@ -0,0 +1,134 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Constant values related to media types. + /// + internal static class MimeConstants + { + /// Media type for requesting any media type. + internal const string MimeAny = "*/*"; + + /// 'application' - media type for application types. + internal const string MimeApplicationType = "application"; + + /// 'text' - media type for text subtypes. + internal const string MimeTextType = "text"; + + /// 'multipart' - media type. + internal const string MimeMultipartType = "multipart"; + + /// 'atom+xml' - constant for atom+xml subtypes. + internal const string MimeAtomXmlSubType = "atom+xml"; + + /// 'atomsvc+xml' - constant for atomsvc+xml subtypes. + internal const string MimeAtomSvcXmlSubType = "atomsvc+xml"; + + /// 'xml' - constant for xml subtypes. + internal const string MimeXmlSubType = "xml"; + + /// 'json' - constant for JSON subtypes. + internal const string MimeJsonSubType = "json"; + + /// 'plain' - constant for text subtypes. + internal const string MimePlainSubType = "plain"; + + /// 'javascript' - constant for javascript subtypes. + internal const string MimeJavaScriptType = "javascript"; + + /// 'octet-stream' subtype. + internal const string MimeOctetStreamSubType = "octet-stream"; + + /// 'mixed' subtype. + internal const string MimeMixedSubType = "mixed"; + + /// 'http' subtype. + internal const string MimeHttpSubType = "http"; + + /// Parameter name for 'type' parameters. + internal const string MimeTypeParameterName = "type"; + + /// Parameter value for type 'resource'. + internal const string MimeTypeParameterValueEntry = "resource"; + + /// Parameter value for type 'feed'. + internal const string MimeTypeParameterValueFeed = "feed"; + + /// JSON Light 4.01 short parameter name for 'metadata' parameter. + internal const string MimeShortMetadataParameterName = "metadata"; + + /// JSON Light parameter name for 'odata.metadata' parameter. + internal const string MimeMetadataParameterName = "odata.metadata"; + + /// Parameter value for 'verbose' JSON. + internal const string MimeMetadataParameterValueVerbose = "verbose"; + + /// JSON Light parameter value 'full'. + internal const string MimeMetadataParameterValueFull = "full"; + + /// JSON Light parameter value 'minimal'. + internal const string MimeMetadataParameterValueMinimal = "minimal"; + + /// JSON Light parameter value 'none'. + internal const string MimeMetadataParameterValueNone = "none"; + + /// JSON Light 4.01 short parameter name for 'streaming' parameter. + internal const string MimeShortStreamingParameterName = "streaming"; + + /// JSON Light Parameter name for 'odata.streaming' parameter. + internal const string MimeStreamingParameterName = "odata.streaming"; + + /// JSON Light parameter name for 'IEEE754Compatible' parameter. + internal const string MimeIeee754CompatibleParameterName = "IEEE754Compatible"; + + /// JSON Light parameter value 'true'. + internal const string MimeParameterValueTrue = "true"; + + /// JSON Light parameter value 'false'. + internal const string MimeParameterValueFalse = "false"; + + /// Media type for XML bodies. + internal const string MimeApplicationXml = MimeApplicationType + Separator + MimeXmlSubType; + + /// Media type for ATOM payloads. + internal const string MimeApplicationAtomXml = MimeApplicationType + Separator + MimeAtomXmlSubType; + + /// Media type for links referencing a single resource. + internal const string MimeApplicationAtomXmlTypeEntry = MimeApplicationAtomXml + ";" + MimeTypeParameterName + "=" + MimeTypeParameterValueEntry; + + /// Media type for links referencing a collection of entries. + internal const string MimeApplicationAtomXmlTypeFeed = MimeApplicationAtomXml + ";" + MimeTypeParameterName + "=" + MimeTypeParameterValueFeed; + + /// Media type for JSON payloads. + internal const string MimeApplicationJson = MimeApplicationType + Separator + MimeJsonSubType; + + /// Media type for binary raw content. + internal const string MimeApplicationOctetStream = MimeApplicationType + Separator + MimeOctetStreamSubType; + + /// Media type for batch parts. + internal const string MimeApplicationHttp = MimeApplicationType + Separator + MimeHttpSubType; + + /// Media type for Xml bodies (deprecated). + internal const string MimeTextXml = MimeTextType + Separator + MimeXmlSubType; + + /// Media type for raw content (except binary). + internal const string MimeTextPlain = MimeTextType + Separator + MimePlainSubType; + + /// Media type for javascript content. + internal const string TextJavaScript = MimeTextType + Separator + MimeJavaScriptType; + + /// Media type for raw content (except binary). + internal const string MimeMultipartMixed = MimeMultipartType + Separator + MimeMixedSubType; + + /// The '*' wildcard usable in type names and subtype names. + internal const string MimeStar = "*"; + + /// Separator between mediat type and subtype. + private const string Separator = "/"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/DependsOnIdsTracker.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/DependsOnIdsTracker.cs new file mode 100644 index 0000000..e4c6fd7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/DependsOnIdsTracker.cs @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.OData.MultipartMixed +{ + /// + /// This class is to keep track of dependsOn ids and associated change set state, + /// and to return dependsOn ids pertaining to current state of the batch processing. + /// + internal sealed class DependsOnIdsTracker + { + /// + /// List of top-level dependsOn ids seen so far. + /// + private readonly IList topLevelDependsOnIds; + + /// + /// List of depeondsOn ids seen so far in current change set. + /// It should be empty if current processing is not within a change set. + /// + private readonly IList changeSetDependsOnIds; + + /// + /// Indicates whether current processing is inside a change set. + /// + private bool isInChangeSet; + + /// + /// Constructor. + /// Initial state is batch start, and within change set. + /// + internal DependsOnIdsTracker() + { + this.topLevelDependsOnIds = new List(); + this.changeSetDependsOnIds = new List(); + this.isInChangeSet = false; + } + + /// + /// Sets up the internal states corresponding to starting of change set. + /// + internal void ChangeSetStarted() + { + Debug.Assert(!isInChangeSet, "!isInChangeSet"); + Debug.Assert(this.changeSetDependsOnIds.Count == 0); + this.isInChangeSet = true; + } + + /// + /// Sets up the internal states corresponding to ending of change set. + /// + internal void ChangeSetEnded() + { + Debug.Assert(isInChangeSet, "isInChangeSet"); + this.isInChangeSet = false; + this.changeSetDependsOnIds.Clear(); + } + + /// + /// Adds the id into the tracker. + /// + /// The operation request id to add. + internal void AddDependsOnId(string id) + { + if (isInChangeSet) + { + this.changeSetDependsOnIds.Add(id); + } + else + { + this.topLevelDependsOnIds.Add(id); + } + } + + /// + /// Get the list of dependsOn ids for the current state. + /// + /// The read-only list of dependsOn ids for the current batch operation request. + internal IEnumerable GetDependsOnIds() + { + return isInChangeSet ? this.changeSetDependsOnIds : this.topLevelDependsOnIds; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchFormat.cs new file mode 100644 index 0000000..8463675 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchFormat.cs @@ -0,0 +1,206 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.MultipartMixed +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + #endregion Namespaces + + /// + /// The $batch OData format. + /// + internal sealed class ODataMultipartMixedBatchFormat : ODataFormat + { + /// + /// The text representation - the name of the format. + /// + /// The name of the format. + public override string ToString() + { + return "Batch"; + } + + /// + /// Detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The set of s that are supported with the specified payload. + public override IEnumerable DetectPayloadKind( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + return DetectPayloadKindImplementation(messageInfo.MediaType); + } + + /// + /// Creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The newly created input context. + public override ODataInputContext CreateInputContext( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + + return new ODataMultipartMixedBatchInputContext(this, messageInfo, messageReaderSettings); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// The newly created output context. + public override ODataOutputContext CreateOutputContext( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + return new ODataMultipartMixedBatchOutputContext(this, messageInfo, messageWriterSettings); + } + +#if PORTABLELIB + /// + /// Asynchronously detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// A task that when completed returns the set of s + /// that are supported with the specified payload. + public override Task> DetectPayloadKindAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + return TaskUtils.GetTaskForSynchronousOperation(() => DetectPayloadKindImplementation(messageInfo.MediaType)); + } + + /// + /// Asynchronously creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// Task which when completed returned the newly created input context. + public override Task CreateInputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + return Task.FromResult( + new ODataMultipartMixedBatchInputContext(this, messageInfo, messageReaderSettings)); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// Task which represents the pending create operation. + public override Task CreateOutputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + return Task.FromResult( + new ODataMultipartMixedBatchOutputContext(this, messageInfo, messageWriterSettings)); + } +#endif + + /// + /// Returns the content type for the MultipartMime Batch format + /// + /// The specified media type. + /// The specified encoding. + /// True if the message writer is being used to write a response. + /// The resultant parameters list of the media type. + /// For multipart/mixed batch type, boundary parameter will be created as required and be added to parameters list. + /// + /// The content-type value for the format. + internal override string GetContentType(ODataMediaType mediaType, Encoding encoding, + bool writingResponse, out IEnumerable> mediaTypeParameters) + { + ExceptionUtils.CheckArgumentNotNull(mediaType, "mediaType"); + + IEnumerable> origParameters = mediaType.Parameters != null + ? mediaType.Parameters + : new List>(); + + IEnumerable> boundaryParameters = origParameters.Where( + p => + string.Compare(p.Key, ODataConstants.HttpMultipartBoundary, StringComparison.OrdinalIgnoreCase) == 0); + + string batchBoundary; + if (boundaryParameters.Count() > 1) + { + throw new ODataContentTypeException( + Strings.MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified(mediaType.ToText())); + } + else if (boundaryParameters.Count() == 1) + { + batchBoundary = boundaryParameters.First().Value; + mediaTypeParameters = mediaType.Parameters; + } + else + { + // No boundary parameters found. + // Create and add the boundary parameter required by the multipart/mixed batch format. + batchBoundary = ODataMultipartMixedBatchWriterUtils.CreateBatchBoundary(writingResponse); + List> newList = new List>(origParameters); + newList.Add(new KeyValuePair(ODataConstants.HttpMultipartBoundary, batchBoundary)); + mediaTypeParameters = newList; + } + + // Set the content type header here since all headers have to be set before getting the stream + // Note that the mediaType may have additional parameters, which we ignore here (intentional as per MIME spec). + // Note that we always generate a new boundary string here, even if the accept header contained one. + // We need the boundary to be as unique as possible to avoid possible collision with content of the batch operation payload. + // Our boundary string are generated to fulfill this requirement, client specified ones might not which might lead to wrong responses + // and at least in theory security issues. + return ODataMultipartMixedBatchWriterUtils.CreateMultipartMixedContentType(batchBoundary); + } + + /// + /// Detects the payload kind(s) from the message stream. + /// + /// The content type of the message. + /// An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + private static IEnumerable DetectPayloadKindImplementation(ODataMediaType contentType) + { + // NOTE: for batch payloads we only use the content type header of the message to detect the payload kind. + // We assume a valid batch payload if the content type is multipart/mixed and a boundary parameter exists + // Require 'multipart/mixed' content type with a boundary parameter to be considered batch. + if (HttpUtils.CompareMediaTypeNames(MimeConstants.MimeMultipartType, contentType.Type) && + HttpUtils.CompareMediaTypeNames(MimeConstants.MimeMixedSubType, contentType.SubType) && + contentType.Parameters != null && + contentType.Parameters.Any(kvp => HttpUtils.CompareMediaTypeParameterNames(ODataConstants.HttpMultipartBoundary, kvp.Key))) + { + return new ODataPayloadKind[] { ODataPayloadKind.Batch }; + } + + return Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchInputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchInputContext.cs new file mode 100644 index 0000000..3bf34aa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchInputContext.cs @@ -0,0 +1,85 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.MultipartMixed +{ + #region Namespaces + using System; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// MultipartMixed batch format output context. + /// + internal sealed class ODataMultipartMixedBatchInputContext : ODataRawInputContext + { + /// The boundary for writing a batch. + private string batchBoundary; + + /// Constructor. + /// The format for this input context. + /// The context information for the message. + /// Configuration settings of the OData reader. + public ODataMultipartMixedBatchInputContext( + ODataFormat format, + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + : base(format, messageInfo, messageReaderSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + Debug.Assert(messageInfo.MediaType != null, "Media type should have been set in messageInfo prior to creating Raw Input Context for Batch"); + try + { + this.batchBoundary = + ODataMultipartMixedBatchWriterUtils.GetBatchBoundaryFromMediaType(messageInfo.MediaType); + } + catch (Exception e) + { + // Dispose the message stream if we failed to get a batch boundary. + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + messageInfo.MessageStream.Dispose(); + } + + throw; + } + } + + /// + /// Create a . + /// + /// The newly created . + internal override ODataBatchReader CreateBatchReader() + { + return this.CreateBatchReaderImplementation(/*synchronous*/ true); + } + +#if PORTABLELIB + /// + /// Asynchronously create a . + /// + /// Task which when completed returns the newly created . + internal override Task CreateBatchReaderAsync() + { + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateBatchReaderImplementation(/*synchronous*/ false)); + } +#endif + + /// + /// Create a . + /// + /// If the reader should be created for synchronous or asynchronous API. + /// The newly created . + private ODataBatchReader CreateBatchReaderImplementation(bool synchronous) + { + return new ODataMultipartMixedBatchReader(this, this.batchBoundary, this.Encoding, synchronous); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchOutputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchOutputContext.cs new file mode 100644 index 0000000..42f10ca --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchOutputContext.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.MultipartMixed +{ + #region Namespaces + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// MultipartMixed batch format output context. + /// + internal sealed class ODataMultipartMixedBatchOutputContext : ODataRawOutputContext + { + /// The boundary for writing a batch. + private readonly string batchBoundary; + + /// + /// Constructor. + /// + /// The format for this output context. + /// The context information for the message. + /// Configuration settings of the OData writer. + internal ODataMultipartMixedBatchOutputContext( + ODataFormat format, + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + : base(format, messageInfo, messageWriterSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + Debug.Assert(messageInfo.MediaType != null, "Media type should have been set in messageInfo prior to creating Raw Input Context for Batch"); + this.batchBoundary = ODataMultipartMixedBatchWriterUtils.GetBatchBoundaryFromMediaType(messageInfo.MediaType); + } + + /// + /// Creates an to write a batch of requests or responses. + /// + /// The created batch writer. + /// We don't plan to make this public! + /// The write must flush the output when it's finished (inside the last Write call). + internal override ODataBatchWriter CreateODataBatchWriter() + { + this.AssertSynchronous(); + + return this.CreateODataBatchWriterImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a batch of requests or responses. + /// + /// A running task for the created batch writer. + /// We don't plan to make this public! + /// The write must flush the output when it's finished (inside the last Write call). + internal override Task CreateODataBatchWriterAsync() + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataBatchWriterImplementation()); + } +#endif + + /// + /// Creates a batch writer. + /// + /// The newly created batch writer. + private ODataBatchWriter CreateODataBatchWriterImplementation() + { + // Batch writer needs the default encoding to not use the preamble. + this.encoding = this.encoding ?? MediaTypeUtils.EncodingUtf8NoPreamble; + ODataBatchWriter batchWriter = new ODataMultipartMixedBatchWriter(this, this.batchBoundary); + this.outputInStreamErrorListener = batchWriter; + return batchWriter; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReader.cs new file mode 100644 index 0000000..14319fb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReader.cs @@ -0,0 +1,398 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.MultipartMixed +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Text; + +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + internal sealed class ODataMultipartMixedBatchReader : ODataBatchReader + { + /// The batch stream used by the batch reader to divide a batch payload into parts. + private readonly ODataMultipartMixedBatchReaderStream batchStream; + + /// + /// The dependsOnIds tracker for reader processing. + /// + private readonly DependsOnIdsTracker dependsOnIdsTracker; + + /// + /// ContentId to apply to the next request. For legacy reasons, this might appear in the mime part headers + /// (which is why we have a property to remember it) but it should appear in the headers for the individual request (which will + /// be read when the individual request is created). + /// + private string currentContentId; + + /// + /// Constructor. + /// + /// The input context to read the content from. + /// The boundary string for the batch structure itself. + /// The encoding to use to read from the batch stream. + /// true if the reader is created for synchronous operation; false for asynchronous. + internal ODataMultipartMixedBatchReader(ODataMultipartMixedBatchInputContext inputContext, string batchBoundary, Encoding batchEncoding, bool synchronous) + : base(inputContext, synchronous) + { + Debug.Assert(inputContext != null, "inputContext != null"); + Debug.Assert(!string.IsNullOrEmpty(batchBoundary), "!string.IsNullOrEmpty(batchBoundary)"); + + this.batchStream = new ODataMultipartMixedBatchReaderStream(this.MultipartMixedBatchInputContext, batchBoundary, batchEncoding); + this.dependsOnIdsTracker = new DependsOnIdsTracker(); + } + + /// + /// Gets the reader's input context as the real runtime type. + /// + private ODataMultipartMixedBatchInputContext MultipartMixedBatchInputContext + { + get + { + return this.InputContext as ODataMultipartMixedBatchInputContext; + } + } + + /// + /// Returns the cached for reading the content of an operation + /// in a batch request. + /// + /// The message that can be used to read the content of the batch request operation from. + protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation() + { + string requestLine = this.batchStream.ReadFirstNonEmptyLine(); + + string httpMethod; + Uri requestUri; + this.ParseRequestLine(requestLine, out httpMethod, out requestUri); + + // Read all headers and create the request message + ODataBatchOperationHeaders headers = this.batchStream.ReadHeaders(); + + if (this.batchStream.ChangeSetBoundary != null) + { + if (this.currentContentId == null) + { + headers.TryGetValue(ODataConstants.ContentIdHeader, out this.currentContentId); + + if (this.currentContentId == null) + { + throw new ODataException(Strings.ODataBatchOperationHeaderDictionary_KeyNotFound(ODataConstants.ContentIdHeader)); + } + } + } + + ODataBatchOperationRequestMessage requestMessage = BuildOperationRequestMessage( + () => ODataBatchUtils.CreateBatchOperationReadStream(this.batchStream, headers, this), + httpMethod, + requestUri, + headers, + this.currentContentId, + this.batchStream.ChangeSetBoundary, + this.dependsOnIdsTracker.GetDependsOnIds(), /*dependsOnIdsValidationRequired*/false); + + if (this.currentContentId != null) + { + this.dependsOnIdsTracker.AddDependsOnId(this.currentContentId); + } + + this.currentContentId = null; + + return requestMessage; + } + + /// + /// Returns the cached for reading the content of an operation + /// in a batch request. + /// + /// The message that can be used to read the content of the batch request operation from. + protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation() + { + string responseLine = this.batchStream.ReadFirstNonEmptyLine(); + + int statusCode = this.ParseResponseLine(responseLine); + + // Read all headers and create the response message + ODataBatchOperationHeaders headers = this.batchStream.ReadHeaders(); + + if (this.currentContentId == null) + { + headers.TryGetValue(ODataConstants.ContentIdHeader, out this.currentContentId); + } + + // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs + // so use the URL resolver from the batch message instead. + // We don't have correlation of changeset boundary between request and response messages in + // changesets, so use null value for groupId. + ODataBatchOperationResponseMessage responseMessage = BuildOperationResponseMessage( + () => ODataBatchUtils.CreateBatchOperationReadStream(this.batchStream, headers, this), + statusCode, + headers, + this.currentContentId, + /*groupId*/ null); + + //// NOTE: Content-IDs for cross referencing are only supported in request messages; in responses + //// we allow a Content-ID header but don't process it (i.e., don't add the content ID to the URL resolver). + this.currentContentId = null; + return responseMessage; + } + + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// The batch reader state after the read. + protected override ODataBatchReaderState ReadAtStartImplementation() + { + return this.SkipToNextPartAndReadHeaders(); + } + + /// + /// Implementation of the reader logic when in state 'Operation'. + /// + /// The batch reader state after the read. + protected override ODataBatchReaderState ReadAtOperationImplementation() + { + return this.SkipToNextPartAndReadHeaders(); + } + + /// + /// Implementation of the reader logic when in state 'ChangesetStart'. + /// First it validates the reader state is setup properly when reader stream is at the start of changeset. + /// + /// The batch reader state after the read. + protected override ODataBatchReaderState ReadAtChangesetStartImplementation() + { + if (this.batchStream.ChangeSetBoundary == null) + { + ThrowODataException(Strings.ODataBatchReader_ReaderStreamChangesetBoundaryCannotBeNull); + } + + this.dependsOnIdsTracker.ChangeSetStarted(); + return this.SkipToNextPartAndReadHeaders(); + } + + /// + /// Implementation of the reader logic when in state 'ChangesetEnd'. + /// + /// The batch reader state after the read. + protected override ODataBatchReaderState ReadAtChangesetEndImplementation() + { + this.batchStream.ResetChangeSetBoundary(); + this.dependsOnIdsTracker.ChangeSetEnded(); + return this.SkipToNextPartAndReadHeaders(); + } + + /// + /// Returns the next state of the batch reader after an end boundary has been found. + /// + /// The next state of the batch reader. + private ODataBatchReaderState GetEndBoundaryState() + { + switch (this.State) + { + case ODataBatchReaderState.Initial: + // If we find an end boundary when in state 'Initial' it means that we + // have an empty batch. The next state will be 'Completed'. + return ODataBatchReaderState.Completed; + + case ODataBatchReaderState.Operation: + // If we find an end boundary in state 'Operation' we have finished + // processing an operation and found the end boundary of either the + // current changeset or the batch. + return this.batchStream.ChangeSetBoundary == null + ? ODataBatchReaderState.Completed + : ODataBatchReaderState.ChangesetEnd; + + case ODataBatchReaderState.ChangesetStart: + // If we find an end boundary when in state 'ChangeSetStart' it means that + // we have an empty changeset. The next state will be 'ChangeSetEnd' + return ODataBatchReaderState.ChangesetEnd; + + case ODataBatchReaderState.ChangesetEnd: + // If we are at the end of a changeset and find an end boundary + // we reached the end of the batch + return ODataBatchReaderState.Completed; + + case ODataBatchReaderState.Completed: + // We should never get here when in Completed state. + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReader_GetEndBoundary_Completed)); + + case ODataBatchReaderState.Exception: + // We should never get here when in Exception state. + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReader_GetEndBoundary_Exception)); + + default: + // Invalid enum value + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReader_GetEndBoundary_UnknownValue)); + } + } + + /// + /// Parses the request line of a batch operation request. + /// + /// The request line as a string. + /// The parsed HTTP method of the request. + /// The parsed of the request. + private void ParseRequestLine(string requestLine, out string httpMethod, out Uri requestUri) + { + Debug.Assert(!this.MultipartMixedBatchInputContext.ReadingResponse, "Must only be called for requests."); + + // Batch Request: POST /Customers HTTP/1.1 + // Since the uri can contain spaces, the only way to read the request url, is to + // check for first space character and last space character and anything between + // them. + int firstSpaceIndex = requestLine.IndexOf(' '); + + // Check whether there are enough characters after the first space for the 2nd and 3rd segments + // (and a whitespace in between) + if (firstSpaceIndex <= 0 || requestLine.Length - 3 <= firstSpaceIndex) + { + // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments + throw new ODataException(Strings.ODataBatchReaderStream_InvalidRequestLine(requestLine)); + } + + int lastSpaceIndex = requestLine.LastIndexOf(' '); + if (lastSpaceIndex < 0 || lastSpaceIndex - firstSpaceIndex - 1 <= 0 || requestLine.Length - 1 <= lastSpaceIndex) + { + // only 2 segments or empty 2nd or 3rd segments + // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments + throw new ODataException(Strings.ODataBatchReaderStream_InvalidRequestLine(requestLine)); + } + + httpMethod = requestLine.Substring(0, firstSpaceIndex); // Request - Http method + string uriSegment = requestLine.Substring(firstSpaceIndex + 1, lastSpaceIndex - firstSpaceIndex - 1); // Request - Request uri + string httpVersionSegment = requestLine.Substring(lastSpaceIndex + 1); // Request - Http version + + // Validate HttpVersion + if (string.CompareOrdinal(ODataConstants.HttpVersionInBatching, httpVersionSegment) != 0) + { + throw new ODataException(Strings.ODataBatchReaderStream_InvalidHttpVersionSpecified(httpVersionSegment, ODataConstants.HttpVersionInBatching)); + } + + // NOTE: this method will throw if the method is not recognized. + HttpUtils.ValidateHttpMethod(httpMethod); + + // Validate the HTTP method when reading a request + if (this.batchStream.ChangeSetBoundary != null) + { + // allow all methods except for GET + if (HttpUtils.IsQueryMethod(httpMethod)) + { + throw new ODataException(Strings.ODataBatch_InvalidHttpMethodForChangeSetRequest(httpMethod)); + } + } + + requestUri = new Uri(uriSegment, UriKind.RelativeOrAbsolute); + } + + /// + /// Parses the response line of a batch operation response. + /// + /// The response line as a string. + /// The parsed status code from the response line. + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "'this' is used when built in debug")] + private int ParseResponseLine(string responseLine) + { + Debug.Assert(this.MultipartMixedBatchInputContext.ReadingResponse, "Must only be called for responses."); + + // Batch Response: HTTP/1.1 200 Ok + // Since the http status code strings have spaces in them, we cannot use the same + // logic. We need to check for the second space and anything after that is the error + // message. + int firstSpaceIndex = responseLine.IndexOf(' '); + if (firstSpaceIndex <= 0 || responseLine.Length - 3 <= firstSpaceIndex) + { + // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments + throw new ODataException(Strings.ODataBatchReaderStream_InvalidResponseLine(responseLine)); + } + + int secondSpaceIndex = responseLine.IndexOf(' ', firstSpaceIndex + 1); + if (secondSpaceIndex < 0 || secondSpaceIndex - firstSpaceIndex - 1 <= 0 || responseLine.Length - 1 <= secondSpaceIndex) + { + // only 2 segments or empty 2nd or 3rd segments + // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments + throw new ODataException(Strings.ODataBatchReaderStream_InvalidResponseLine(responseLine)); + } + + string httpVersionSegment = responseLine.Substring(0, firstSpaceIndex); + string statusCodeSegment = responseLine.Substring(firstSpaceIndex + 1, secondSpaceIndex - firstSpaceIndex - 1); + + // Validate HttpVersion + if (string.CompareOrdinal(ODataConstants.HttpVersionInBatching, httpVersionSegment) != 0) + { + throw new ODataException(Strings.ODataBatchReaderStream_InvalidHttpVersionSpecified(httpVersionSegment, ODataConstants.HttpVersionInBatching)); + } + + int intResult; + if (!Int32.TryParse(statusCodeSegment, out intResult)) + { + throw new ODataException(Strings.ODataBatchReaderStream_NonIntegerHttpStatusCode(statusCodeSegment)); + } + + return intResult; + } + + /// + /// Skips all data in the stream until the next part is detected; then reads the part's request/response line and headers. + /// + /// The next state of the batch reader after skipping to the next part and reading the part's beginning. + private ODataBatchReaderState SkipToNextPartAndReadHeaders() + { + bool isEndBoundary, isParentBoundary; + bool foundBoundary = this.batchStream.SkipToBoundary(out isEndBoundary, out isParentBoundary); + + if (!foundBoundary) + { + // We did not find the expected boundary at all in the payload; + // we are done reading. Depending on where we are report changeset end or completed state + if (this.batchStream.ChangeSetBoundary == null) + { + return ODataBatchReaderState.Completed; + } + else + { + return ODataBatchReaderState.ChangesetEnd; + } + } + + ODataBatchReaderState nextState; + if (isEndBoundary || isParentBoundary) + { + // We detected an end boundary or detected that the end boundary is missing + // because we found a parent boundary + nextState = this.GetEndBoundaryState(); + } + else + { + bool currentlyInChangeSet = this.batchStream.ChangeSetBoundary != null; + bool isChangeSetPart = this.batchStream.ProcessPartHeader(out this.currentContentId); + + // Compute the next reader state + if (currentlyInChangeSet) + { + Debug.Assert(!isChangeSetPart, "Should have validated that nested changesets are not allowed."); + nextState = ODataBatchReaderState.Operation; + } + else + { + // We are at the top level (not inside a changeset) + nextState = isChangeSetPart + ? ODataBatchReaderState.ChangesetStart + : ODataBatchReaderState.Operation; + } + } + + return nextState; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReaderStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReaderStream.cs new file mode 100644 index 0000000..cffb24d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchReaderStream.cs @@ -0,0 +1,812 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.MultipartMixed +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; + #endregion Namespaces + + /// + /// Class used by the to read the various pieces of a batch payload + /// in multipart/mixed format. + /// + /// + /// This stream separates a batch payload into multiple parts by scanning ahead and matching + /// a boundary string against the current payload. + /// + internal sealed class ODataMultipartMixedBatchReaderStream : ODataBatchReaderStream + { + /// + /// The default length for the line buffer byte array used to read lines; expecting lines to normally be less than 2000 bytes. + /// + private const int LineBufferLength = 2000; + + /// + /// The byte array used for reading lines from the stream. We cache the byte array on the stream instance + /// rather than allocating a new one for each ReadLine call. + /// + private readonly byte[] lineBuffer; + + /// + /// The boundary string for the batch structure itself. + /// + private readonly string batchBoundary; + + /// The media type resolver to use when interpreting the incoming content type. + private readonly ODataMediaTypeResolver mediaTypeResolver; + + /// The encoding to use to read from the batch stream. + private Encoding batchEncoding; + + /// The encoding for a given changeset. + private Encoding changesetEncoding; + + /// + /// The boundary string for a changeset (or null if not in a changeset part). + /// + private string changesetBoundary; + + /// + /// The raw input context used by the reader. + /// + private ODataMultipartMixedBatchInputContext multipartMixedBatchInputContext; + + /// + /// Constructor. + /// + /// The input context to read the content from. + /// The boundary string for the batch structure itself. + /// The encoding to use to read from the batch stream. + internal ODataMultipartMixedBatchReaderStream( + ODataMultipartMixedBatchInputContext inputContext, + string batchBoundary, + Encoding batchEncoding) + { + Debug.Assert(!string.IsNullOrEmpty(batchBoundary), "!string.IsNullOrEmpty(batchBoundary)"); + + this.batchEncoding = batchEncoding; + this.multipartMixedBatchInputContext = inputContext; + this.batchBoundary = batchBoundary; + + // When we allocate a batch reader stream we will in almost all cases also call ReadLine + // (to read the headers of the parts); so allocating it here. + this.lineBuffer = new byte[LineBufferLength]; + + this.mediaTypeResolver = ODataMediaTypeResolver.GetMediaTypeResolver(inputContext.Container); + } + + /// + /// The boundary string for the batch structure itself. + /// + internal string BatchBoundary + { + get + { + return this.batchBoundary; + } + } + + /// + /// The boundary string for the current changeset (only set when reading a changeset + /// or an operation in a changeset). + /// + /// When not reading a changeset (or operation in a changeset) this field is null. + internal string ChangeSetBoundary + { + get + { + return this.changesetBoundary; + } + } + + /// + /// The current encoding to use when reading from the stream. + /// + /// This is the changeset encoding when reading a changeset or the batch encoding otherwise. + private Encoding CurrentEncoding + { + get + { + return this.changesetEncoding ?? this.batchEncoding; + } + } + + /// + /// The current boundary string to be used for reading with delimiter. + /// + /// This is the changeset boundary when reading a changeset or the batch boundary otherwise. + private IEnumerable CurrentBoundaries + { + get + { + if (this.changesetBoundary != null) + { + yield return this.changesetBoundary; + } + + yield return this.batchBoundary; + } + } + + /// + /// Resets the changeset boundary at the end of the changeset. + /// + internal void ResetChangeSetBoundary() + { + Debug.Assert(this.changesetBoundary != null, "Only valid to reset a changeset boundary if one exists."); + this.changesetBoundary = null; + this.changesetEncoding = null; + } + + /// + /// Skips all the data in the stream until a boundary is found. + /// + /// true if the boundary that was found is an end boundary; otherwise false. + /// true if the detected boundary is a parent boundary (i.e., the expected boundary is missing). + /// true if a boundary was found; otherwise false. + internal bool SkipToBoundary(out bool isEndBoundary, out bool isParentBoundary) + { + // Ensure we have a batch encoding; if not detect it on the first read/skip. + this.EnsureBatchEncoding(this.multipartMixedBatchInputContext.Stream); + + ODataBatchReaderStreamScanResult scanResult = ODataBatchReaderStreamScanResult.NoMatch; + while (scanResult != ODataBatchReaderStreamScanResult.Match) + { + int boundaryStartPosition, boundaryEndPosition; + scanResult = this.BatchBuffer.ScanForBoundary( + this.CurrentBoundaries, + /*stopAfterIfNotFound*/int.MaxValue, + out boundaryStartPosition, + out boundaryEndPosition, + out isEndBoundary, + out isParentBoundary); + switch (scanResult) + { + case ODataBatchReaderStreamScanResult.NoMatch: + if (this.underlyingStreamExhausted) + { + // there is nothing else to load from the underlying stream; the requested boundary does not exist + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + this.BatchBuffer.NumberOfBytesInBuffer); + return false; + } + + // skip everything in the buffer and refill it from the underlying stream; continue scanning + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.multipartMixedBatchInputContext.Stream, /*preserveFrom*/ODataBatchReaderStreamBuffer.BufferLength); + + break; + case ODataBatchReaderStreamScanResult.PartialMatch: + if (this.underlyingStreamExhausted) + { + // there is nothing else to load from the underlying stream; the requested boundary does not exist + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + this.BatchBuffer.NumberOfBytesInBuffer); + return false; + } + + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.multipartMixedBatchInputContext.Stream, /*preserveFrom*/boundaryStartPosition); + + break; + case ODataBatchReaderStreamScanResult.Match: + // If we found the expected boundary, position the reader on the position after the boundary end. + // If we found a parent boundary, position the reader on the boundary start so we'll detect the boundary + // again on the next Read call. + this.BatchBuffer.SkipTo(isParentBoundary ? boundaryStartPosition : boundaryEndPosition + 1); + return true; + + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStream_SkipToBoundary)); + } + } + + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStream_SkipToBoundary)); + } + + /// + /// Reads from the batch stream while ensuring that we stop reading at each boundary. + /// + /// The byte array to read bytes into. + /// The offset in the buffer where to start reading bytes into. + /// The number of bytes to read. + /// The number of bytes actually read. + internal override int ReadWithDelimiter(byte[] userBuffer, int userBufferOffset, int count) + { + Debug.Assert(userBuffer != null, "userBuffer != null"); + Debug.Assert(userBufferOffset >= 0 && userBufferOffset < userBuffer.Length, "Offset must be within the range of the user buffer."); + Debug.Assert(count >= 0, "count >= 0"); + Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); + + if (count == 0) + { + // Nothing to read. + return 0; + } + + int remainingNumberOfBytesToRead = count; + ODataBatchReaderStreamScanResult scanResult = ODataBatchReaderStreamScanResult.NoMatch; + + while (remainingNumberOfBytesToRead > 0 && scanResult != ODataBatchReaderStreamScanResult.Match) + { + int boundaryStartPosition, boundaryEndPosition; + bool isEndBoundary, isParentBoundary; + scanResult = this.BatchBuffer.ScanForBoundary( + this.CurrentBoundaries, + remainingNumberOfBytesToRead, + out boundaryStartPosition, + out boundaryEndPosition, + out isEndBoundary, + out isParentBoundary); + + int bytesBeforeBoundaryStart; + switch (scanResult) + { + case ODataBatchReaderStreamScanResult.NoMatch: + // The boundary was not found in the buffer or after the required number of bytes to be read; + // Check whether we can satisfy the full read request from the buffer + // or whether we have to split the request and read more data into the buffer. + if (this.BatchBuffer.NumberOfBytesInBuffer >= remainingNumberOfBytesToRead) + { + // we can satisfy the full read request from the buffer + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, remainingNumberOfBytesToRead); + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + remainingNumberOfBytesToRead); + return count; + } + else + { + // we can only partially satisfy the read request + int availableBytesToRead = this.BatchBuffer.NumberOfBytesInBuffer; + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, availableBytesToRead); + remainingNumberOfBytesToRead -= availableBytesToRead; + userBufferOffset += availableBytesToRead; + + // we exhausted the buffer; if the underlying stream is not exceeded, refill the buffer + if (this.underlyingStreamExhausted) + { + // We cannot fully satisfy the read request since there are not enough bytes in the stream. + // Return the number of bytes we read. + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + availableBytesToRead); + return count - remainingNumberOfBytesToRead; + } + else + { + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.multipartMixedBatchInputContext.Stream, /*preserveFrom*/ ODataBatchReaderStreamBuffer.BufferLength); + } + } + + break; + case ODataBatchReaderStreamScanResult.PartialMatch: + // A partial match for the boundary was found at the end of the buffer. + // If the underlying stream is not exceeded, refill the buffer. Otherwise return + // the available bytes. + if (this.underlyingStreamExhausted) + { + // We cannot fully satisfy the read request since there are not enough bytes in the stream. + // Return the remaining bytes in the buffer independently of where a potentially boundary + // start was detected since no full boundary can ever be detected if the stream is exhausted. + int bytesToReturn = Math.Min(this.BatchBuffer.NumberOfBytesInBuffer, remainingNumberOfBytesToRead); + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, bytesToReturn); + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + bytesToReturn); + remainingNumberOfBytesToRead -= bytesToReturn; + return count - remainingNumberOfBytesToRead; + } + else + { + // Copy the bytes prior to the potential boundary start into the user buffer, refill the buffer and continue. + bytesBeforeBoundaryStart = boundaryStartPosition - this.BatchBuffer.CurrentReadPosition; + Debug.Assert(bytesBeforeBoundaryStart < remainingNumberOfBytesToRead, "When reporting a partial match we should never have read all the remaining bytes to read (or more)."); + + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, bytesBeforeBoundaryStart); + remainingNumberOfBytesToRead -= bytesBeforeBoundaryStart; + userBufferOffset += bytesBeforeBoundaryStart; + + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.multipartMixedBatchInputContext.Stream, /*preserveFrom*/ boundaryStartPosition); + } + + break; + case ODataBatchReaderStreamScanResult.Match: + // We found the full boundary match; copy everything before the boundary to the buffer + bytesBeforeBoundaryStart = boundaryStartPosition - this.BatchBuffer.CurrentReadPosition; + Debug.Assert(bytesBeforeBoundaryStart <= remainingNumberOfBytesToRead, "When reporting a full match we should never have read more than the remaining bytes to read."); + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, bytesBeforeBoundaryStart); + remainingNumberOfBytesToRead -= bytesBeforeBoundaryStart; + userBufferOffset += bytesBeforeBoundaryStart; + + // position the reader on the position of the boundary start + this.BatchBuffer.SkipTo(boundaryStartPosition); + + // return the number of bytes that were read + return count - remainingNumberOfBytesToRead; + + default: + break; + } + } + + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStream_ReadWithDelimiter)); + } + + /// + /// Reads from the batch stream without checking for a boundary delimiter since we + /// know the length of the stream. + /// + /// The byte array to read bytes into. + /// The offset in the buffer where to start reading bytes into. + /// The number of bytes to read. + /// The number of bytes actually read. + internal override int ReadWithLength(byte[] userBuffer, int userBufferOffset, int count) + { + Debug.Assert(userBuffer != null, "userBuffer != null"); + Debug.Assert(userBufferOffset >= 0, "userBufferOffset >= 0"); + Debug.Assert(count >= 0, "count >= 0"); + Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); + + //// NOTE: if we have a stream with length we don't even check for boundaries but rely solely on the content length + + int remainingNumberOfBytesToRead = count; + while (remainingNumberOfBytesToRead > 0) + { + // check whether we can satisfy the full read request from the buffer + // or whether we have to split the request and read more data into the buffer. + if (this.BatchBuffer.NumberOfBytesInBuffer >= remainingNumberOfBytesToRead) + { + // we can satisfy the full read request from the buffer + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, remainingNumberOfBytesToRead); + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + remainingNumberOfBytesToRead); + remainingNumberOfBytesToRead = 0; + } + else + { + // we can only partially satisfy the read request + int availableBytesToRead = this.BatchBuffer.NumberOfBytesInBuffer; + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, userBuffer, userBufferOffset, availableBytesToRead); + remainingNumberOfBytesToRead -= availableBytesToRead; + userBufferOffset += availableBytesToRead; + + // we exhausted the buffer; if the underlying stream is not exhausted, refill the buffer + if (this.underlyingStreamExhausted) + { + // We cannot fully satisfy the read request since there are not enough bytes in the stream. + // This means that the content length of the stream was incorrect; this should never happen + // since the caller should already have checked this. + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStreamBuffer_ReadWithLength)); + } + else + { + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.multipartMixedBatchInputContext.Stream, /*preserveFrom*/ ODataBatchReaderStreamBuffer.BufferLength); + } + } + } + + // return the number of bytes that were read + return count - remainingNumberOfBytesToRead; + } + + /// + /// Reads the headers of a part. + /// + /// Content-ID read from changeset header, null if changeset part detected + /// true if the start of a changeset part was detected; otherwise false. + internal bool ProcessPartHeader(out string contentId) + { + Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); + + bool isChangeSetPart; + ODataBatchOperationHeaders headers = this.ReadPartHeaders(out isChangeSetPart); + + contentId = null; + + if (isChangeSetPart) + { + // determine the changeset boundary and the changeset encoding from the content type header + this.DetermineChangesetBoundaryAndEncoding(headers[ODataConstants.ContentTypeHeader]); + + if (this.changesetEncoding == null) + { + // NOTE: No changeset encoding was specified in the changeset's content type header. + // Determine the changeset encoding from the first bytes in the changeset. + // NOTE: We do not have to skip over the potential preamble of the encoding + // because the batch reader will skip over everything (incl. the preamble) + // until it finds the first changeset (or batch) boundary + this.changesetEncoding = this.DetectEncoding(this.multipartMixedBatchInputContext.Stream); + } + + // Verify that we only allow single byte encodings and UTF-8 for now. + ReaderValidationUtils.ValidateEncodingSupportedInBatch(this.changesetEncoding); + } + else + { + // Read the contentId not only for request in changeset; but also + // top-level request (if available), so that top-level request dependsOn + // ids can be derived (even though top-level request ids are typically + // not showing up on the wire). + headers.TryGetValue(ODataConstants.ContentIdHeader, out contentId); + } + + return isChangeSetPart; + } + + /// + /// Reads the headers of a batch part or an operation. + /// + /// A dictionary of header names to header values; never null. + internal ODataBatchOperationHeaders ReadHeaders() + { + Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); + + // [Astoria-ODataLib-Integration] WCF DS Batch reader compares header names in batch part using case insensitive comparison, ODataLib is case sensitive + ODataBatchOperationHeaders headers = new ODataBatchOperationHeaders(); + + // Read all the headers + string headerLine = this.ReadLine(); + while (headerLine != null && headerLine.Length > 0) + { + string headerName, headerValue; + ValidateHeaderLine(headerLine, out headerName, out headerValue); + + if (headers.ContainsKeyOrdinal(headerName)) + { + throw new ODataException(Strings.ODataBatchReaderStream_DuplicateHeaderFound(headerName)); + } + + headers.Add(headerName, headerValue); + headerLine = this.ReadLine(); + } + + return headers; + } + + /// + /// Read and return the next line from the batch stream, skipping all empty lines. + /// + /// This method will throw if end-of-input was reached while looking for the next line. + /// The text of the first non-empty line (not including any terminating newline characters). + internal string ReadFirstNonEmptyLine() + { + string line; + do + { + line = this.ReadLine(); + + // null indicates end of input, which is unexpected at this point. + if (line == null) + { + throw new ODataException(Strings.ODataBatchReaderStream_UnexpectedEndOfInput); + } + } + while (line.Length == 0); + + return line; + } + + /// + /// Parses a header line and validates that it has the correct format. + /// + /// The header line to validate. + /// The name of the header. + /// The value of the header. + private static void ValidateHeaderLine(string headerLine, out string headerName, out string headerValue) + { + Debug.Assert(headerLine != null && headerLine.Length > 0, "Expected non-empty header line."); + + int colon = headerLine.IndexOf(':'); + if (colon <= 0) + { + throw new ODataException(Strings.ODataBatchReaderStream_InvalidHeaderSpecified(headerLine)); + } + + headerName = headerLine.Substring(0, colon).Trim(); + headerValue = headerLine.Substring(colon + 1).Trim(); + } + + /// + /// Reads a line (all bytes until a line resource set) from the underlying stream. + /// + /// Returns the string that was read from the underlying stream (not including a terminating line resource set), or null if the end of input was reached. + private string ReadLine() + { + Debug.Assert(this.batchEncoding != null, "Batch encoding should have been established on first call to SkipToBoundary."); + Debug.Assert(this.lineBuffer != null && this.lineBuffer.Length == LineBufferLength, "Line buffer should have been created."); + + // The number of bytes in the line buffer that make up the line. + int lineBufferSize = 0; + + // Start with the pre-allocated line buffer array. + byte[] bytesForString = this.lineBuffer; + + ODataBatchReaderStreamScanResult scanResult = ODataBatchReaderStreamScanResult.NoMatch; + while (scanResult != ODataBatchReaderStreamScanResult.Match) + { + int byteCount, lineEndStartPosition, lineEndEndPosition; + scanResult = this.BatchBuffer.ScanForLineEnd(out lineEndStartPosition, out lineEndEndPosition); + + switch (scanResult) + { + case ODataBatchReaderStreamScanResult.NoMatch: + // Copy all the bytes in the BatchBuffer into the result byte[] and then continue + byteCount = this.BatchBuffer.NumberOfBytesInBuffer; + if (byteCount > 0) + { + // TODO: [Design] Consider security limits for data being read + ODataBatchUtils.EnsureArraySize(ref bytesForString, lineBufferSize, byteCount); + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, bytesForString, lineBufferSize, byteCount); + lineBufferSize += byteCount; + } + + if (this.underlyingStreamExhausted) + { + if (lineBufferSize == 0) + { + // If there's nothing more to pull from the underlying stream, and we didn't read anything + // in this invocation of ReadLine(), return null to indicate end of input. + return null; + } + + // Nothing more to read; stop looping + scanResult = ODataBatchReaderStreamScanResult.Match; + this.BatchBuffer.SkipTo(this.BatchBuffer.CurrentReadPosition + byteCount); + } + else + { + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.multipartMixedBatchInputContext.Stream, /*preserveFrom*/ ODataBatchReaderStreamBuffer.BufferLength); + } + + break; + case ODataBatchReaderStreamScanResult.PartialMatch: + // We found the start of a line end in the buffer but could not verify whether we saw all of it. + // This can happen if a line end is represented as \r\n and we found \r at the very last position in the buffer. + // In this case we copy the bytes into the result byte[] and continue at the start of the line end; this will guarantee + // that the next scan will find the full line end, not find any additional bytes and then skip the full line end. + // It is safe to copy the string right here because we will also accept \r as a line end; we are just not sure whether there + // will be a subsequent \n. + // This can also happen if the last byte in the stream is \r. + byteCount = lineEndStartPosition - this.BatchBuffer.CurrentReadPosition; + if (byteCount > 0) + { + ODataBatchUtils.EnsureArraySize(ref bytesForString, lineBufferSize, byteCount); + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, bytesForString, lineBufferSize, byteCount); + lineBufferSize += byteCount; + } + + if (this.underlyingStreamExhausted) + { + // Nothing more to read; stop looping + scanResult = ODataBatchReaderStreamScanResult.Match; + this.BatchBuffer.SkipTo(lineEndStartPosition + 1); + } + else + { + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(this.multipartMixedBatchInputContext.Stream, /*preserveFrom*/ lineEndStartPosition); + } + + break; + case ODataBatchReaderStreamScanResult.Match: + // We found a line end in the buffer + Debug.Assert(lineEndStartPosition >= this.BatchBuffer.CurrentReadPosition, "Line end must be at or after current position."); + Debug.Assert(lineEndEndPosition < this.BatchBuffer.CurrentReadPosition + this.BatchBuffer.NumberOfBytesInBuffer, "Line end must finish withing buffer range."); + + byteCount = lineEndStartPosition - this.BatchBuffer.CurrentReadPosition; + if (byteCount > 0) + { + ODataBatchUtils.EnsureArraySize(ref bytesForString, lineBufferSize, byteCount); + Buffer.BlockCopy(this.BatchBuffer.Bytes, this.BatchBuffer.CurrentReadPosition, bytesForString, lineBufferSize, byteCount); + lineBufferSize += byteCount; + } + + this.BatchBuffer.SkipTo(lineEndEndPosition + 1); + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStream_ReadLine)); + } + } + + Debug.Assert(bytesForString != null, "bytesForString != null"); + + return this.CurrentEncoding.GetString(bytesForString, 0, lineBufferSize); + } + + /// + /// Reads and validates the headers of a batch part. + /// + /// true if the headers indicate a changeset part; otherwise false. + /// A dictionary of header names to header values; never null. + private ODataBatchOperationHeaders ReadPartHeaders(out bool isChangeSetPart) + { + ODataBatchOperationHeaders partHeaders = this.ReadHeaders(); + return this.ValidatePartHeaders(partHeaders, out isChangeSetPart); + } + + + /// + /// Validates the headers that have been read for a part. + /// + /// The set of headers to validate. + /// true if the headers indicate a changeset part; otherwise false. + /// The set of validated headers. + /// + /// An operation part is required to have content type 'application/http' and content transfer + /// encoding 'binary'. A changeset is required to have content type 'multipart/mixed'. + /// Note that we allow additional headers for batch parts; clients of the library can choose + /// to be more strict. + /// + private ODataBatchOperationHeaders ValidatePartHeaders(ODataBatchOperationHeaders headers, out bool isChangeSetPart) + { + string contentType; + if (!headers.TryGetValue(ODataConstants.ContentTypeHeader, out contentType)) + { + throw new ODataException(Strings.ODataBatchReaderStream_MissingContentTypeHeader); + } + + if (MediaTypeUtils.MediaTypeAndSubtypeAreEqual(contentType, MimeConstants.MimeApplicationHttp)) + { + isChangeSetPart = false; + + // An operation part is required to have application/http content type and + // binary content transfer encoding. + string transferEncoding; + if (!headers.TryGetValue(ODataConstants.ContentTransferEncoding, out transferEncoding) || + string.Compare(transferEncoding, ODataConstants.BatchContentTransferEncoding, StringComparison.OrdinalIgnoreCase) != 0) + { + throw new ODataException(Strings.ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader( + ODataConstants.ContentTransferEncoding, + ODataConstants.BatchContentTransferEncoding)); + } + } + else if (MediaTypeUtils.MediaTypeStartsWithTypeAndSubtype(contentType, MimeConstants.MimeMultipartMixed)) + { + isChangeSetPart = true; + + if (this.changesetBoundary != null) + { + // Nested changesets are not supported + throw new ODataException(Strings.ODataBatchReaderStream_NestedChangesetsAreNotSupported); + } + } + else + { + throw new ODataException(Strings.ODataBatchReaderStream_InvalidContentTypeSpecified( + ODataConstants.ContentTypeHeader, + contentType, + MimeConstants.MimeMultipartMixed, + MimeConstants.MimeApplicationHttp)); + } + + return headers; + } + + /// + /// Parse the content type header value to retrieve the boundary and encoding of a changeset. + /// + /// The content type to parse. + private void DetermineChangesetBoundaryAndEncoding(string contentType) + { + Debug.Assert(!string.IsNullOrEmpty(contentType), "Should have validated that non-null, non-empty content type header exists."); + + ODataMediaType mediaType; + ODataPayloadKind readerPayloadKind; + MediaTypeUtils.GetFormatFromContentType( + contentType, + new ODataPayloadKind[] { ODataPayloadKind.Batch }, + this.mediaTypeResolver, + out mediaType, + out this.changesetEncoding, + out readerPayloadKind); + Debug.Assert(readerPayloadKind == ODataPayloadKind.Batch, "Must find batch payload kind."); + Debug.Assert(HttpUtils.CompareMediaTypeNames(MimeConstants.MimeMultipartMixed, mediaType.FullTypeName), "Must be multipart/mixed media type."); + this.changesetBoundary = ODataMultipartMixedBatchWriterUtils.GetBatchBoundaryFromMediaType(mediaType); + Debug.Assert(this.changesetBoundary != null && this.changesetBoundary.Length > 0, "Boundary string should have been validated by now."); + } + + + /// + /// Ensure that a batch encoding exists; if not, detect it from the first couple of bytes of the stream. + /// + /// The stream to read. + private void EnsureBatchEncoding(Stream stream) + { + // If no batch encoding is specified we detect it from the first bytes in the buffer. + if (this.batchEncoding == null) + { + // NOTE: The batch encoding will only ever be null on the first call to this method which + // happens before the batch reader skips to the first boundary. + this.batchEncoding = this.DetectEncoding(stream); + } + + // Verify that we only allow single byte encodings and UTF-8 for now. + ReaderValidationUtils.ValidateEncodingSupportedInBatch(this.batchEncoding); + } + + /// Detect the encoding based data from the stream. + /// The stream to read. + /// The encoding discovered from the bytes in the buffer or the fallback encoding. + /// + /// We don't have to skip a potential preamble of the encoding since the batch reader + /// will skip over everything (incl. the potential preamble) until it finds the first + /// boundary. + /// + private Encoding DetectEncoding(Stream stream) + { + // We need at most 4 bytes in the buffer to determine the encoding; if we have less than that, + // refill the buffer. + while (!this.underlyingStreamExhausted && this.BatchBuffer.NumberOfBytesInBuffer < 4) + { + this.underlyingStreamExhausted = this.BatchBuffer.RefillFrom(stream, this.BatchBuffer.CurrentReadPosition); + } + + // Now we should have a full buffer unless the underlying stream did not have enough bytes. + int numberOfBytesInBuffer = this.BatchBuffer.NumberOfBytesInBuffer; + if (numberOfBytesInBuffer < 2) + { + Debug.Assert(this.underlyingStreamExhausted, "Underlying stream must be exhausted if we have less than 2 bytes in the buffer after refilling."); + + // If we cannot read any of the known preambles we fall back to the default encoding, which is US-ASCII. +#if !ORCAS + // ASCII not available; use UTF8 without preamble + return MediaTypeUtils.FallbackEncoding; +#else + return Encoding.ASCII; +#endif + } + else if (this.BatchBuffer[this.BatchBuffer.CurrentReadPosition] == 0xFE && this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 1] == 0xFF) + { + // Big Endian Unicode + return new UnicodeEncoding(/*bigEndian*/ true, /*byteOrderMark*/ true); + } + else if (this.BatchBuffer[this.BatchBuffer.CurrentReadPosition] == 0xFF && this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 1] == 0xFE) + { + // Little Endian Unicode, or possibly little endian UTF32 + if (numberOfBytesInBuffer >= 4 && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 2] == 0 && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 3] == 0) + { +#if !ORCAS + // Little Endian UTF32 not available + throw Error.NotSupported(); +#else + return new UTF32Encoding(/*bigEndian*/ false, /*byteOrderMark*/ true); +#endif + } + else + { + return new UnicodeEncoding(/*bigEndian*/ false, /*byteOrderMark*/ true); + } + } + else if (numberOfBytesInBuffer >= 3 && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition] == 0xEF && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 1] == 0xBB && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 2] == 0xBF) + { + // UTF-8 + return Encoding.UTF8; + } + else if (numberOfBytesInBuffer >= 4 && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition] == 0 && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 1] == 0 && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 2] == 0xFE && + this.BatchBuffer[this.BatchBuffer.CurrentReadPosition + 3] == 0xFF) + { + // Big Endian UTF32 +#if !ORCAS + // Big Endian UTF32 not available + throw Error.NotSupported(); +#else + return new UTF32Encoding(/*bigEndian*/ true, /*byteOrderMark*/ true); +#endif + } + else + { +#if !ORCAS + // ASCII not available; use UTF8 without preamble + return MediaTypeUtils.FallbackEncoding; +#else + return Encoding.ASCII; +#endif + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriter.cs new file mode 100644 index 0000000..ea58334 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriter.cs @@ -0,0 +1,448 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.MultipartMixed +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + internal sealed class ODataMultipartMixedBatchWriter : ODataBatchWriter + { + /// The boundary string for the batch structure itself. + private readonly string batchBoundary; + + /// + /// The dependsOnIds tracker for writer processing. + /// + private readonly DependsOnIdsTracker dependsOnIdsTracker; + + /// + /// The boundary string for the current changeset (only set when writing a changeset, + /// e.g., after WriteStartChangeSet has been called and before WriteEndChangeSet is called). + /// + /// When not writing a changeset this field is null. + private string changeSetBoundary; + + /// + /// A flag to indicate whether the batch start boundary has been written or not; important to support writing of empty batches. + /// + private bool batchStartBoundaryWritten; + + /// + /// A flags to indicate whether the current changeset start boundary has been written or not. + /// This is false if a changeset has been started by no changeset boundary was written, and true once the first changeset + /// boundary for the current changeset has been written. + /// + private bool changesetStartBoundaryWritten; + + /// + /// Constructor. + /// + /// The output context to write to. + /// The boundary string for the batch structure itself. + internal ODataMultipartMixedBatchWriter(ODataMultipartMixedBatchOutputContext rawOutputContext, string batchBoundary) + : base(rawOutputContext) + { + Debug.Assert(rawOutputContext != null, "rawOutputContext != null"); + ExceptionUtils.CheckArgumentNotNull(batchBoundary, "batchBoundary is null"); + this.batchBoundary = batchBoundary; + this.RawOutputContext.InitializeRawValueWriter(); + this.dependsOnIdsTracker = new DependsOnIdsTracker(); + } + + /// + /// Gets the writer's output context as the real runtime type. + /// + private ODataMultipartMixedBatchOutputContext RawOutputContext + { + get { return this.OutputContext as ODataMultipartMixedBatchOutputContext; } + } + + /// + /// The message for the operation that is currently written; or null if no operation is written right now. + /// + private ODataBatchOperationMessage CurrentOperationMessage + { + get + { + Debug.Assert(this.CurrentOperationRequestMessage == null || this.CurrentOperationResponseMessage == null, + "Only request or response message can be set, not both."); + if (this.CurrentOperationRequestMessage != null) + { + Debug.Assert(!this.RawOutputContext.WritingResponse, "Request message can only be set when writing request."); + return this.CurrentOperationRequestMessage.OperationMessage; + } + else if (this.CurrentOperationResponseMessage != null) + { + Debug.Assert(this.RawOutputContext.WritingResponse, "Response message can only be set when writing response."); + return this.CurrentOperationResponseMessage.OperationMessage; + } + else + { + return null; + } + } + } + + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + public override void StreamRequested() + { + // Write any pending data and flush the batch writer to the async buffered stream + this.StartBatchOperationContent(); + + // Flush the async buffered stream to the underlying message stream (if there's any) + this.RawOutputContext.FlushBuffers(); + + // Dispose the batch writer (since we are now writing the operation content) and set the corresponding state. + this.DisposeBatchWriterAndSetContentStreamRequestedState(); + } + +#if PORTABLELIB + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + /// + /// A task representing any action that is running as part of the status change of the operation; + /// null if no such action exists. + /// + public override Task StreamRequestedAsync() + { + // Write any pending data and flush the batch writer to the async buffered stream + this.StartBatchOperationContent(); + + // Asynchronously flush the async buffered stream to the underlying message stream (if there's any); + // then dispose the batch writer (since we are now writing the operation content) and set the corresponding state. + return this.RawOutputContext.FlushBuffersAsync() + .FollowOnSuccessWith(task => this.DisposeBatchWriterAndSetContentStreamRequestedState()); + } +#endif + + /// + /// This method is called to notify that the content stream of a batch operation has been disposed. + /// + public override void StreamDisposed() + { + Debug.Assert(this.CurrentOperationMessage != null, "Expected non-null operation message!"); + + this.SetState(BatchWriterState.OperationStreamDisposed); + this.CurrentOperationRequestMessage = null; + this.CurrentOperationResponseMessage = null; + this.RawOutputContext.InitializeRawValueWriter(); + } + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + public override void OnInStreamError() + { + this.RawOutputContext.VerifyNotDisposed(); + this.SetState(BatchWriterState.Error); + this.RawOutputContext.TextWriter.Flush(); + + // The OData protocol spec did not defined the behavior when an exception is encountered outside of a batch operation. The batch writer + // should not allow WriteError in this case. Note that WCF DS Server does serialize the error in XML format when it encounters one outside of a + // batch operation. + throw new ODataException(Strings.ODataBatchWriter_CannotWriteInStreamErrorForBatch); + } + + /// + /// Flush the output. + /// + protected override void FlushSynchronously() + { + this.RawOutputContext.Flush(); + } + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected override Task FlushAsynchronously() + { + return this.RawOutputContext.FlushAsync(); + } +#endif + + /// + /// Starts a new changeset - implementation of the actual functionality. + /// + /// The value for changeset boundary for multipart batch. + protected override void WriteStartChangesetImplementation(string changeSetId) + { + Debug.Assert(changeSetId != null, "changeSetId != null"); + + // write pending message data (headers, response line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + this.SetState(BatchWriterState.ChangesetStarted); + Debug.Assert(this.changeSetBoundary == null, "this.changeSetBoundary == null"); + this.changeSetBoundary = ODataMultipartMixedBatchWriterUtils.CreateChangeSetBoundary(this.RawOutputContext.WritingResponse, changeSetId); + + // write the boundary string + ODataMultipartMixedBatchWriterUtils.WriteStartBoundary(this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten); + this.batchStartBoundaryWritten = true; + + // write the change set headers + ODataMultipartMixedBatchWriterUtils.WriteChangeSetPreamble(this.RawOutputContext.TextWriter, this.changeSetBoundary); + this.changesetStartBoundaryWritten = false; + + // Set state to track dependsOn Ids. + this.dependsOnIdsTracker.ChangeSetStarted(); + } + + /// + /// Given an enumerable of dependsOnIds, return an enumeration of equivalent request ids. + /// + /// The dependsOn ids specifying current request's prerequisites. + /// If dependsOnIds is null, this is the implicit case therefore returns + /// an enumerable consists of request id from the dependsOnIdsTracker; + /// otherwise, this is explicit case therefore returns value passed in directly. + protected override IEnumerable GetDependsOnRequestIds(IEnumerable dependsOnIds) + { + return dependsOnIds ?? this.dependsOnIdsTracker.GetDependsOnIds(); + } + + /// + /// Creates an for writing an operation of a batch request + /// - implementation of the actual functionality. + /// + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The Content-ID value to write in ChangeSet head. + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// The prerequisite request ids of this request. By default its value should be null for Multipart/Mixed + /// format and the dependsOnIds implicitly derived per the protocol will be used; Otherwise, non-null will be used as override after + /// validation. + /// The message that can be used to write the request operation. + protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation( + string method, Uri uri, string contentId, BatchPayloadUriOption payloadUriOption, + IEnumerable dependsOnIds) + { + // write pending message data (headers, response line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + // create the new request operation + // For Multipart batch format, validate dependsOnIds if it is user explicit input, otherwise skip validation + // when it is implicitly derived per protocol. + ODataBatchOperationRequestMessage operationRequestMessage = BuildOperationRequestMessage( + this.RawOutputContext.OutputStream, + method, uri, contentId, + this.changeSetBoundary, + dependsOnIds); + + this.SetState(BatchWriterState.OperationCreated); + + // write the operation's start boundary string + this.WriteStartBoundaryForOperation(); + + if (contentId != null) + { + this.dependsOnIdsTracker.AddDependsOnId(contentId); + } + + // write the headers and request line + ODataMultipartMixedBatchWriterUtils.WriteRequestPreamble(this.RawOutputContext.TextWriter, method, uri, + this.RawOutputContext.MessageWriterSettings.BaseUri, changeSetBoundary != null, contentId, + payloadUriOption); + + return operationRequestMessage; + } + + /// + /// Ends a batch - implementation of the actual functionality. + /// + protected override void WriteEndBatchImplementation() + { + Debug.Assert( + this.batchStartBoundaryWritten || this.CurrentOperationMessage == null, + "If not batch boundary was written we must not have an active message."); + + // write pending message data (headers, response line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + this.SetState(BatchWriterState.BatchCompleted); + + // write the end boundary for the batch + ODataMultipartMixedBatchWriterUtils.WriteEndBoundary(this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten); + + // For compatibility with WCF DS we write a newline after the end batch boundary. + // Technically it's not needed, but it doesn't violate anything either. + this.RawOutputContext.TextWriter.WriteLine(); + } + + /// + /// Ends an active changeset - implementation of the actual functionality. + /// + protected override void WriteEndChangesetImplementation() + { + // write pending message data (headers, response line) for a previously unclosed message/request + this.WritePendingMessageData(true); + + string currentChangeSetBoundary = this.changeSetBoundary; + + // change the state first so we validate the change set boundary before attempting to write it. + this.SetState(BatchWriterState.ChangesetCompleted); + + this.dependsOnIdsTracker.ChangeSetEnded(); + + Debug.Assert(this.changeSetBoundary != null, "this.changeSetBoundary != null"); + this.changeSetBoundary = null; + + // In the case of an empty changeset the start changeset boundary has not been written yet + // we will leave it like that, since we want the empty changeset to be represented only as + // the end changeset boundary. + // Due to WCF DS V2 compatibility we must not write the start boundary in this case + // otherwise WCF DS V2 won't be able to read it (it fails on the start-end boundary empty changeset). + + // write the end boundary for the change set + ODataMultipartMixedBatchWriterUtils.WriteEndBoundary(this.RawOutputContext.TextWriter, currentChangeSetBoundary, !this.changesetStartBoundaryWritten); + } + + /// + /// Creates an for writing an operation of a batch response - implementation of the actual functionality. + /// + /// The Content-ID value to write in ChangeSet head. + /// The message that can be used to write the response operation. + protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation(string contentId) + { + this.WritePendingMessageData(true); + + // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs + // so use the URL resolver from the batch message instead. + this.CurrentOperationResponseMessage = BuildOperationResponseMessage( + this.RawOutputContext.OutputStream, contentId, + this.changeSetBoundary); + + this.SetState(BatchWriterState.OperationCreated); + + // write the operation's start boundary string + this.WriteStartBoundaryForOperation(); + + // write the headers and request separator line + ODataMultipartMixedBatchWriterUtils.WriteResponsePreamble(this.RawOutputContext.TextWriter, changeSetBoundary != null, contentId); + + return this.CurrentOperationResponseMessage; + } + + /// + /// Verifies that the writer is not disposed. + /// + protected override void VerifyNotDisposed() + { + this.RawOutputContext.VerifyNotDisposed(); + } + + /// + /// Starts a new batch - implementation of the actual functionality. + /// + protected override void WriteStartBatchImplementation() + { + this.SetState(BatchWriterState.BatchStarted); + } + + /// + /// Writes all the pending headers and prepares the writer to write a content of the operation. + /// + private void StartBatchOperationContent() + { + Debug.Assert(this.CurrentOperationMessage != null, "Expected non-null operation message!"); + Debug.Assert(this.RawOutputContext.TextWriter != null, "Must have a batch writer!"); + + // write the pending headers (if any) + this.WritePendingMessageData(false); + + // flush the text writer to make sure all buffers of the text writer + // are flushed to the underlying async stream + this.RawOutputContext.TextWriter.Flush(); + } + + /// + /// Disposes the batch writer and set the 'OperationStreamRequested' batch writer state; + /// called after the flush operation(s) have completed. + /// + private void DisposeBatchWriterAndSetContentStreamRequestedState() + { + this.RawOutputContext.CloseWriter(); + + this.SetState(BatchWriterState.OperationStreamRequested); + } + + /// + /// Writes the start boundary for an operation. This is either the batch or the changeset boundary. + /// + private void WriteStartBoundaryForOperation() + { + if (this.changeSetBoundary == null) + { + ODataMultipartMixedBatchWriterUtils.WriteStartBoundary(this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten); + this.batchStartBoundaryWritten = true; + } + else + { + ODataMultipartMixedBatchWriterUtils.WriteStartBoundary(this.RawOutputContext.TextWriter, this.changeSetBoundary, !this.changesetStartBoundaryWritten); + this.changesetStartBoundaryWritten = true; + } + } + + /// + /// Write any pending headers for the current operation message (if any). + /// + /// + /// A flag to control whether after writing the pending data we report writing the message to be completed or not. + /// + private void WritePendingMessageData(bool reportMessageCompleted) + { + if (this.CurrentOperationMessage != null) + { + Debug.Assert(this.RawOutputContext.TextWriter != null, "Must have a batch writer if pending data exists."); + + if (this.CurrentOperationResponseMessage != null) + { + Debug.Assert(this.RawOutputContext.WritingResponse, "If the response message is available we must be writing response."); + int statusCode = this.CurrentOperationResponseMessage.StatusCode; + string statusMessage = HttpUtils.GetStatusMessage(statusCode); + this.RawOutputContext.TextWriter.WriteLine("{0} {1} {2}", ODataConstants.HttpVersionInBatching, statusCode, statusMessage); + } + + IEnumerable> headers = this.CurrentOperationMessage.Headers; + if (headers != null) + { + foreach (KeyValuePair headerPair in headers) + { + string headerName = headerPair.Key; + string headerValue = headerPair.Value; + this.RawOutputContext.TextWriter.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", headerName, headerValue)); + } + } + + // write CRLF after the headers (or request/response line if there are no headers) + this.RawOutputContext.TextWriter.WriteLine(); + + if (reportMessageCompleted) + { + this.CurrentOperationMessage.PartHeaderProcessingCompleted(); + this.CurrentOperationRequestMessage = null; + this.CurrentOperationResponseMessage = null; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriterUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriterUtils.cs new file mode 100644 index 0000000..a02c428 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/MultipartMixed/ODataMultipartMixedBatchWriterUtils.cs @@ -0,0 +1,264 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.MultipartMixed +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + #endregion Namespaces + + /// + /// Helper methods used by the ODataBatchWriter. + /// + internal static class ODataMultipartMixedBatchWriterUtils + { + /// + /// Creates a new batch boundary string based on a randomly created GUID. + /// + /// A flag indicating whether the boundary should be created for a request or a response. + /// The newly created batch boundary as string. + internal static string CreateBatchBoundary(bool isResponse) + { + string template = isResponse ? ODataConstants.BatchResponseBoundaryTemplate : ODataConstants.BatchRequestBoundaryTemplate; + return string.Format(CultureInfo.InvariantCulture, template, Guid.NewGuid()); + } + + /// + /// Creates a new changeset boundary string based on an id. + /// + /// A flag indicating whether the boundary should be created for a request or a response. + /// The value for construction of changeset boundary for multipart batch. + /// The newly created changeset boundary as string. + internal static string CreateChangeSetBoundary(bool isResponse, string changesetId) + { + string template = isResponse ? ODataConstants.ResponseChangeSetBoundaryTemplate : ODataConstants.RequestChangeSetBoundaryTemplate; + return string.Format(CultureInfo.InvariantCulture, template, changesetId); + } + + /// + /// Creates the multipart/mixed content type with the specified boundary (if any). + /// + /// The boundary to be used for this operation or null if no boundary should be included. + /// The multipart/mixed content type with the specified boundary (if any). + internal static string CreateMultipartMixedContentType(string boundary) + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}; {1}={2}", + MimeConstants.MimeMultipartMixed, + ODataConstants.HttpMultipartBoundary, + boundary); + } + + /// + /// Gets the boundary from a multipart/mixed batch media type. + /// + /// The multipart/mixed batch media type with a boundary type parameter. + /// The boundary for the media type. + internal static string GetBatchBoundaryFromMediaType(ODataMediaType mediaType) + { + string batchBoundary; + KeyValuePair boundaryPair = default(KeyValuePair); + IEnumerable> parameters = mediaType.Parameters; + if (parameters != null) + { + bool boundaryPairFound = false; + foreach (KeyValuePair pair in parameters.Where(p => HttpUtils.CompareMediaTypeParameterNames(ODataConstants.HttpMultipartBoundary, p.Key))) + { + if (boundaryPairFound) + { + throw new ODataException(Strings.MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads(mediaType.ToText(), ODataConstants.HttpMultipartBoundary)); + } + + boundaryPair = pair; + boundaryPairFound = true; + } + } + + if (boundaryPair.Key == null) + { + throw new ODataException(Strings.MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads(mediaType.ToText(), ODataConstants.HttpMultipartBoundary)); + } + + batchBoundary = boundaryPair.Value; + ValidationUtils.ValidateBoundaryString(batchBoundary); + return batchBoundary; + } + + /// + /// Write the start boundary. + /// + /// Writer to which the boundary needs to be written. + /// Boundary string. + /// true if this is the first start boundary. + internal static void WriteStartBoundary(TextWriter writer, string boundary, bool firstBoundary) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(boundary != null, "boundary != null"); + + // write the CRLF that belongs to the boundary (see RFC 2046, Section 5.1.1) + // but only if it's not the first boundary, the new line is not required by the boundary + // and we don't want to write it unless necessary. + if (!firstBoundary) + { + writer.WriteLine(); + } + + writer.WriteLine("--{0}", boundary); + } + + /// + /// Write the end boundary. + /// + /// Writer to which the end boundary needs to be written. + /// Boundary string. + /// true if there was no start boundary written before this end boundary. + internal static void WriteEndBoundary(TextWriter writer, string boundary, bool missingStartBoundary) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(boundary != null, "boundary != null"); + + // write the CRLF that belongs to the boundary (see RFC 2046, Section 5.1.1) + // but only if it's not the only boundary, the new line is not required by the first boundary + // and we don't want to write it unless necessary. + if (!missingStartBoundary) + { + writer.WriteLine(); + } + + // Note that we don't write a newline AFTER the end boundary since there's no need to. + writer.Write("--{0}--", boundary); + } + + /// + /// Writes the headers, (optional) Content-ID and the request line. + /// + /// Writer to write to. + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The service root Uri to be used for this request operation. + /// Whether we are in ChangeSetBound. + /// The Content-ID value to write in ChangeSet head. + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + internal static void WriteRequestPreamble( + TextWriter writer, + string httpMethod, + Uri uri, + Uri baseUri, + bool inChangeSetBound, + string contentId, + BatchPayloadUriOption payloadUriOption) + { + Debug.Assert(writer != null, "writer != null"); + Debug.Assert(uri != null, "uri != null"); + Debug.Assert(uri.IsAbsoluteUri || UriUtils.UriToString(uri).StartsWith("$", StringComparison.Ordinal), "uri.IsAbsoluteUri || uri.OriginalString.StartsWith(\"$\")"); + + // write the headers + WriteHeaders(writer, inChangeSetBound, contentId); + + // write separator line between headers and the request line + writer.WriteLine(); + + // write request line + WriteRequestUri(writer, httpMethod, uri, baseUri, payloadUriOption); + } + + /// + /// Writes the headers and response line. + /// + /// Writer to write to. + /// Whether we are in ChangeSetBound. + /// The Content-ID value to write in ChangeSet head. + internal static void WriteResponsePreamble(TextWriter writer, bool inChangeSetBound, string contentId) + { + Debug.Assert(writer != null, "writer != null"); + + // write the headers + WriteHeaders(writer, inChangeSetBound, contentId); + + // write separator line between headers and the response line + writer.WriteLine(); + } + + /// + /// Writes the preamble for a change set (e.g., the content-type header). + /// + /// Writer to write to. + /// The boundary string to use for the change set. + internal static void WriteChangeSetPreamble(TextWriter writer, string changeSetBoundary) + { + Debug.Assert(changeSetBoundary != null, "changeSetBoundary != null"); + + string multipartContentType = CreateMultipartMixedContentType(changeSetBoundary); + writer.WriteLine("{0}: {1}", ODataConstants.ContentTypeHeader, multipartContentType); + + // write separator line between headers and first change set operation + writer.WriteLine(); + } + + /// + /// Writes the headers. + /// + /// Writer to write headers. + /// Whether we are in ChangeSetBound. + /// The Content-ID value to write in ChangeSet head. + private static void WriteHeaders(TextWriter writer, bool inChangeSetBound, string contentId) + { + writer.WriteLine("{0}: {1}", ODataConstants.ContentTypeHeader, MimeConstants.MimeApplicationHttp); + writer.WriteLine("{0}: {1}", ODataConstants.ContentTransferEncoding, ODataConstants.BatchContentTransferEncoding); + if (inChangeSetBound && contentId != null) + { + writer.WriteLine("{0}: {1}", ODataConstants.ContentIdHeader, contentId); + } + } + + /// + /// Writes the request line. + /// + /// Writer to write request uri. + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The service root Uri to be used for this request operation. + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + private static void WriteRequestUri(TextWriter writer, string httpMethod, Uri uri, Uri baseUri, BatchPayloadUriOption payloadUriOption) + { + if (uri.IsAbsoluteUri) + { + string absoluteUriString = uri.AbsoluteUri; + + switch (payloadUriOption) + { + case BatchPayloadUriOption.AbsoluteUri: + writer.WriteLine("{0} {1} {2}", httpMethod, UriUtils.UriToString(uri), ODataConstants.HttpVersionInBatching); + break; + + case BatchPayloadUriOption.AbsoluteUriUsingHostHeader: + string absoluteResourcePath = absoluteUriString.Substring(absoluteUriString.IndexOf('/', absoluteUriString.IndexOf("//", StringComparison.Ordinal) + 2)); + writer.WriteLine("{0} {1} {2}", httpMethod, absoluteResourcePath, ODataConstants.HttpVersionInBatching); + writer.WriteLine("Host: {0}:{1}", uri.Host, uri.Port); + break; + + case BatchPayloadUriOption.RelativeUri: + Debug.Assert(baseUri != null, "baseUri != null"); + string baseUriString = UriUtils.UriToString(baseUri); + Debug.Assert(absoluteUriString.StartsWith(baseUriString, StringComparison.Ordinal), "absoluteUriString.StartsWith(baseUriString)"); + string relativeResourcePath = absoluteUriString.Substring(baseUriString.Length); + writer.WriteLine("{0} {1} {2}", httpMethod, relativeResourcePath, ODataConstants.HttpVersionInBatching); + break; + } + } + else + { + writer.WriteLine("{0} {1} {2}", httpMethod, UriUtils.UriToString(uri), ODataConstants.HttpVersionInBatching); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/NonDisposingStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/NonDisposingStream.cs new file mode 100644 index 0000000..f29ed5f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/NonDisposingStream.cs @@ -0,0 +1,200 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading; + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Stream wrapper for the message stream to ignore the Stream.Dispose method so that readers/writers on top of + /// it can be disposed without affecting it. + /// + internal sealed class NonDisposingStream : Stream + { + /// + /// Stream that is being wrapped. + /// + private readonly Stream innerStream; + + /// + /// Constructs an instance of the stream wrapper class. + /// + /// Stream that is being wrapped. + internal NonDisposingStream(Stream innerStream) + { + Debug.Assert(innerStream != null, "innerStream != null"); + this.innerStream = innerStream; + } + + /// + /// Determines if the stream can read. + /// + public override bool CanRead + { + get { return this.innerStream.CanRead; } + } + + /// + /// Determines if the stream can seek. + /// + public override bool CanSeek + { + get { return this.innerStream.CanSeek; } + } + + /// + /// Determines if the stream can write. + /// + public override bool CanWrite + { + get { return this.innerStream.CanWrite; } + } + + /// + /// Returns the length of the stream. + /// + public override long Length + { + get { return this.innerStream.Length; } + } + + /// + /// Gets or sets the position in the stream. + /// + public override long Position + { + get { return this.innerStream.Position; } + set { this.innerStream.Position = value; } + } + + /// + /// Flush the stream to the underlying storage. + /// + public override void Flush() + { + this.innerStream.Flush(); + } + + /// + /// Reads data from the stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + return this.innerStream.Read(buffer, offset, count); + } + +#if PORTABLELIB + /// + public async override Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken) + { + int bytesRead = await this.innerStream.ReadAsync(buffer, offset, count, cancellationToken); + return bytesRead; + } +#else + /// + /// Begins a read operation from the stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The async callback. + /// The async state. + /// Async result representing the asynchornous operation. + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.innerStream.BeginRead(buffer, offset, count, callback, state); + } + + /// + /// Ends a read operation from the stream. + /// + /// The async result representing the read operation. + /// The number of bytes actually read. + public override int EndRead(IAsyncResult asyncResult) + { + return this.innerStream.EndRead(asyncResult); + } +#endif + + /// + /// Seeks the stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + return this.innerStream.Seek(offset, origin); + } + + /// + /// Sets the length of the stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + this.innerStream.SetLength(value); + } + + /// + /// Writes to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + this.innerStream.Write(buffer, offset, count); + } + +#if PORTABLELIB + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.innerStream.WriteAsync(buffer, offset, count, cancellationToken); + } +#else + /// + /// Begins an asynchronous write operation to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + /// The async callback. + /// The async state. + /// Async result representing the write operation. + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.innerStream.BeginWrite(buffer, offset, count, callback, state); + } + + /// + /// Ends the asynchronous write operation. + /// + /// Async result representing the write operation. + public override void EndWrite(IAsyncResult asyncResult) + { + this.innerStream.EndWrite(asyncResult); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAction.cs new file mode 100644 index 0000000..a7660be --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAction.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + #endregion Namespaces + + /// Represents an OData action. + [DebuggerDisplay("{Metadata}")] + public sealed class ODataAction : ODataOperation + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAnnotatable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAnnotatable.cs new file mode 100644 index 0000000..2a90e80 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAnnotatable.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; + +namespace Microsoft.OData +{ + /// + /// Base class for all annotatable types in OData library. + /// +#if ORCAS + [Serializable] +#endif + public abstract class ODataAnnotatable + { + /// + /// Collection of custom instance annotations. + /// +#if ORCAS + [NonSerialized] +#endif + private ICollection instanceAnnotations = new Collection(); + + /// + /// The annotation for storing @odata.type. + /// + public ODataTypeAnnotation TypeAnnotation { get; set; } + + /// + /// Gets the custom instance annotations. + /// + /// The custom instance annotations. + internal ICollection GetInstanceAnnotations() + { + Debug.Assert(this.instanceAnnotations != null, "this.instanceAnnotations != null"); + return this.instanceAnnotations; + } + + /// + /// Sets the custom instance annotations. + /// + /// The new value to set. + internal void SetInstanceAnnotations(ICollection value) + { + ExceptionUtils.CheckArgumentNotNull(value, "value"); + this.instanceAnnotations = value; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousReader.cs new file mode 100644 index 0000000..9f9c8fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousReader.cs @@ -0,0 +1,313 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Class for reading OData async messages. + /// + public sealed class ODataAsynchronousReader + { + /// + /// The input context to read the content from. + /// + private readonly ODataRawInputContext rawInputContext; + + /// + /// The optional dependency injection container to get related services for message writing. + /// + private readonly IServiceProvider container; + + /// + /// Constructor. + /// + /// The input context to read the content from. + /// The encoding for the underlying stream. + internal ODataAsynchronousReader(ODataRawInputContext rawInputContext, Encoding encoding) + { + Debug.Assert(rawInputContext != null, "rawInputContext != null"); + + // Currently we only support single-byte UTF8 in async reader. + if (encoding != null) + { + ReaderValidationUtils.ValidateEncodingSupportedInAsync(encoding); + } + + this.rawInputContext = rawInputContext; + this.container = rawInputContext.Container; + } + + /// + /// Returns a message for reading the content of an async response. + /// + /// A response message for reading the content of the async response. + public ODataAsynchronousResponseMessage CreateResponseMessage() + { + this.VerifyCanCreateResponseMessage(true); + + return this.CreateResponseMessageImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously returns a message for reading the content of an async response. + /// + /// A response message for reading the content of the async response. + public Task CreateResponseMessageAsync() + { + this.VerifyCanCreateResponseMessage(false); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResponseMessageImplementation()); + } +#endif + + /// + /// Validates that the async reader is not disposed. + /// + private void ValidateReaderNotDisposed() + { + this.rawInputContext.VerifyNotDisposed(); + } + + /// + /// Verifies that a call is allowed to the reader. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.rawInputContext.Synchronous) + { + throw new ODataException(Strings.ODataAsyncReader_SyncCallOnAsyncReader); + } + } + else + { +#if PORTABLELIB + if (this.rawInputContext.Synchronous) + { + throw new ODataException(Strings.ODataAsyncReader_AsyncCallOnSyncReader); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Verifies that calling CreateResponseMessage is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateResponseMessage(bool synchronousCall) + { + this.ValidateReaderNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + if (!this.rawInputContext.ReadingResponse) + { + throw new ODataException(Strings.ODataAsyncReader_CannotCreateResponseWhenNotReadingResponse); + } + } + + /// + /// Returns an for reading the content of an async response - implementation of the actual functionality. + /// + /// The message that can be used to read the response. + private ODataAsynchronousResponseMessage CreateResponseMessageImplementation() + { + int statusCode; + IDictionary headers; + + this.ReadInnerEnvelope(out statusCode, out headers); + + return ODataAsynchronousResponseMessage.CreateMessageForReading(this.rawInputContext.Stream, statusCode, headers, this.container); + } + + /// + /// Reads the envelope from the inner HTTP message. + /// + /// The status code to use for the async response message. + /// The headers to use for the async response message. + private void ReadInnerEnvelope(out int statusCode, out IDictionary headers) + { + string responseLine = this.ReadFirstNonEmptyLine(); + + Debug.Assert(this.rawInputContext.ReadingResponse, "Must only be called for responses."); + statusCode = ParseResponseLine(responseLine); + headers = this.ReadHeaders(); + } + + /// + /// Read and return the next line from the stream, skipping all empty lines. + /// + /// The text of the first non-empty line (not including any terminating newline characters). + private string ReadFirstNonEmptyLine() + { + string line = this.ReadLine(); + + while (line.Length == 0) + { + line = this.ReadLine(); + } + + return line; + } + + /// + /// Parses the response line of an async response. + /// + /// The response line as a string. + /// The parsed status code from the response line. + private static int ParseResponseLine(string responseLine) + { + // Async Response: HTTP/1.1 200 Ok + // Since the http status code strings have spaces in them, we cannot use the same + // logic. We need to check for the second space and anything after that is the error + // message. + if (responseLine.Length == 0) + { + // empty line + throw new ODataException(Strings.ODataAsyncReader_InvalidResponseLine(responseLine)); + } + + int firstSpaceIndex = responseLine.IndexOf(' '); + if (firstSpaceIndex <= 0 || responseLine.Length - 3 <= firstSpaceIndex) + { + // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments + throw new ODataException(Strings.ODataAsyncReader_InvalidResponseLine(responseLine)); + } + + int secondSpaceIndex = responseLine.IndexOf(' ', firstSpaceIndex + 1); + if (secondSpaceIndex < 0 || secondSpaceIndex - firstSpaceIndex - 1 <= 0 || responseLine.Length - 1 <= secondSpaceIndex) + { + // only 2 segments or empty 2nd or 3rd segments + // only 1 segment or empty first segment or not enough left for 2nd and 3rd segments + throw new ODataException(Strings.ODataAsyncReader_InvalidResponseLine(responseLine)); + } + + string httpVersionSegment = responseLine.Substring(0, firstSpaceIndex); + string statusCodeSegment = responseLine.Substring(firstSpaceIndex + 1, secondSpaceIndex - firstSpaceIndex - 1); + + // Validate HttpVersion + if (string.CompareOrdinal(ODataConstants.HttpVersionInAsync, httpVersionSegment) != 0) + { + throw new ODataException(Strings.ODataAsyncReader_InvalidHttpVersionSpecified(httpVersionSegment, ODataConstants.HttpVersionInAsync)); + } + + int intResult; + if (!Int32.TryParse(statusCodeSegment, out intResult)) + { + throw new ODataException(Strings.ODataAsyncReader_NonIntegerHttpStatusCode(statusCodeSegment)); + } + + return intResult; + } + + /// + /// Reads the headers of an async response. + /// + /// A dictionary of header names to header values; never null. + private IDictionary ReadHeaders() + { + var headers = new Dictionary(StringComparer.Ordinal); + + // Read all the headers + string headerLine = this.ReadLine(); + while (!string.IsNullOrEmpty(headerLine)) + { + string headerName, headerValue; + ValidateHeaderLine(headerLine, out headerName, out headerValue); + + if (headers.ContainsKey(headerName)) + { + throw new ODataException(Strings.ODataAsyncReader_DuplicateHeaderFound(headerName)); + } + + headers.Add(headerName, headerValue); + headerLine = this.ReadLine(); + } + + return headers; + } + + /// + /// Parses a header line and validates that it has the correct format. + /// + /// The header line to validate. + /// The name of the header. + /// The value of the header. + private static void ValidateHeaderLine(string headerLine, out string headerName, out string headerValue) + { + Debug.Assert(!string.IsNullOrEmpty(headerLine), "Expected non-empty header line."); + + int colon = headerLine.IndexOf(':'); + if (colon <= 0) + { + throw new ODataException(Strings.ODataAsyncReader_InvalidHeaderSpecified(headerLine)); + } + + headerName = headerLine.Substring(0, colon).Trim(); + headerValue = headerLine.Substring(colon + 1).Trim(); + } + + /// + /// Reads a line from the underlying stream. + /// + /// The line read from the stream. + private string ReadLine() + { + StringBuilder lineBuilder = new StringBuilder(); + + int ch = this.ReadByte(); + + while (ch != -1) + { + if (ch == '\n') + { + throw new ODataException(Strings.ODataAsyncReader_InvalidNewLineEncountered('\n')); + } + + if (ch == '\r') + { + ch = this.ReadByte(); + + if (ch != '\n') + { + throw new ODataException(Strings.ODataAsyncReader_InvalidNewLineEncountered('\r')); + } + + return lineBuilder.ToString(); + } + + lineBuilder.Append((char)ch); + ch = this.ReadByte(); + } + + throw new ODataException(Strings.ODataAsyncReader_UnexpectedEndOfInput); + } + + /// + /// Reads a byte from the underlying stream. + /// + /// The byte read from the stream. + private int ReadByte() + { + return this.rawInputContext.Stream.ReadByte(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousResponseMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousResponseMessage.cs new file mode 100644 index 0000000..27db1bc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousResponseMessage.cs @@ -0,0 +1,217 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Representing the message of a non-batch async response. + /// + public sealed class ODataAsynchronousResponseMessage : IContainerProvider, +#if PORTABLELIB + IODataResponseMessageAsync +#else + IODataResponseMessage +#endif + { + /// True if we are wrting the response; false if we are reading it. + private readonly bool writing; + + /// The stream of the response message. + private readonly Stream stream; + + /// The action to write envelope for the inner message before returning the stream. + private readonly Action writeEnvelope; + + /// The dependency injection container to get related services. + private readonly IServiceProvider container; + + /// Prevent the envelope for the inner message from being written more than one time. + private bool envelopeWritten; + + /// The result status code of the response message. + private int statusCode; + + /// The set of headers of the response message. + private IDictionary headers; + + /// + /// Constructor. + /// + /// The stream underlying the response message. + /// The status code to use for the async response message. + /// The headers to use for the async response message. + /// The action to write envelope for the inner message before returning the stream. + /// true if the response message is being written; false when it is read. + /// The dependency injection container to get related services. + private ODataAsynchronousResponseMessage( + Stream stream, + int statusCode, + IDictionary headers, + Action writeEnvelope, + bool writing, + IServiceProvider container) + { + Debug.Assert(stream != null, "stream != null"); + + this.stream = stream; + this.statusCode = statusCode; + this.headers = headers; + this.writeEnvelope = writeEnvelope; + this.writing = writing; + this.container = container; + } + + /// Gets or sets the result status code of the response message. + /// The result status code of the response message. + public int StatusCode + { + get + { + return this.statusCode; + } + + set + { + this.VerifyCanSetHeaderAndStatusCode(); + + this.statusCode = value; + } + } + + /// Gets an enumerable over all the headers for this message. + /// An enumerable over all the headers for this message. + public IEnumerable> Headers + { + get { return this.headers; } + } + + /// + /// The dependency injection container to get related services. + /// + public IServiceProvider Container + { + get { return this.container; } + } + + /// Returns a value of an HTTP header of this operation. + /// The value of the HTTP header, or null if no such header was present on the message. + /// The name of the header to get. + public string GetHeader(string headerName) + { + if (this.headers != null) + { + string headerValue; + if (this.headers.TryGetValue(headerName, out headerValue)) + { + return headerValue; + } + } + + return null; + } + + /// Sets the value of an HTTP header of this operation. + /// The name of the header to set. + /// The value of the HTTP header or null if the header should be removed. + public void SetHeader(string headerName, string headerValue) + { + this.VerifyCanSetHeaderAndStatusCode(); + + if (headerValue == null) + { + if (this.headers != null) + { + this.headers.Remove(headerName); + } + } + else + { + if (this.headers == null) + { + this.headers = new Dictionary(StringComparer.Ordinal); + } + + this.headers[headerName] = headerValue; + } + } + + /// Gets the stream backing for this message. + /// The stream backing for this message. + public Stream GetStream() + { + // If writing response, the envelope for the inner message should be written once and only once before returning the stream. + if (this.writing && !this.envelopeWritten) + { + if (this.writeEnvelope != null) + { + this.writeEnvelope(this); + } + + this.envelopeWritten = true; + } + + return this.stream; + } + +#if PORTABLELIB + /// Asynchronously get the stream backing for this message. + /// The stream backing for this message. + public Task GetStreamAsync() + { + return Task.Factory.StartNew(this.GetStream); + } +#endif + + /// + /// Creates an async response message that can be used to write the response content to. + /// + /// The output stream underlying the response message. + /// The action to write envelope for the inner message before returning the stream. + /// The dependency injection container to get related services. + /// An that can be used to write the response content. + internal static ODataAsynchronousResponseMessage CreateMessageForWriting(Stream outputStream, Action writeEnvelope, IServiceProvider container) + { + return new ODataAsynchronousResponseMessage(outputStream, /*statusCode*/0, /*headers*/null, writeEnvelope, /*writing*/true, container); + } + + /// + /// Creates an async response message that can be used to read the response content from. + /// + /// The input stream underlying the response message. + /// The status code to use for the async response message. + /// The headers to use for the async response message. + /// The dependency injection container to get related services. + /// An that can be used to read the response content. + internal static ODataAsynchronousResponseMessage CreateMessageForReading(Stream stream, int statusCode, IDictionary headers, IServiceProvider container) + { + return new ODataAsynchronousResponseMessage(stream, statusCode, headers, /*writeEnvelope*/null, /*writing*/false, container); + } + + /// + /// Verifies that setting a header or the status code is allowed + /// + /// + /// We allow modifying the headers and the status code only if we are writing the message. + /// + private void VerifyCanSetHeaderAndStatusCode() + { + if (!this.writing) + { + throw new ODataException(Strings.ODataAsyncResponseMessage_MustNotModifyMessage); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousWriter.cs new file mode 100644 index 0000000..0bc15b8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataAsynchronousWriter.cs @@ -0,0 +1,214 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Globalization; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Class for writing OData async messages; also verifies the proper count of write calls on the writer. + /// + public sealed class ODataAsynchronousWriter : IODataOutputInStreamErrorListener + { + /// + /// The output context to write to. + /// + private readonly ODataRawOutputContext rawOutputContext; + + /// + /// The optional dependency injection container to get related services for message writing. + /// + private readonly IServiceProvider container; + + /// + /// Prevent the response message from being created more than one time since an async response message can only contain one inner message. + /// + private bool responseMessageCreated; + + /// + /// Constructor. + /// + /// The output context to write to. + internal ODataAsynchronousWriter(ODataRawOutputContext rawOutputContext) + { + Debug.Assert(rawOutputContext != null, "rawOutputContext != null"); + + this.rawOutputContext = rawOutputContext; + this.container = rawOutputContext.Container; + this.rawOutputContext.InitializeRawValueWriter(); + } + + /// + /// Creates a message for writing an async response. + /// + /// The message that can be used to write the async response. + public ODataAsynchronousResponseMessage CreateResponseMessage() + { + this.VerifyCanCreateResponseMessage(true); + + return this.CreateResponseMessageImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously creates a message for writing an async response. + /// + /// The message that can be used to write the async response. + public Task CreateResponseMessageAsync() + { + this.VerifyCanCreateResponseMessage(false); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateResponseMessageImplementation()); + } +#endif + + /// + /// Flushes the write buffer to the underlying stream. + /// + public void Flush() + { + this.VerifyCanFlush(true); + + this.rawOutputContext.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously flushes the write buffer to the underlying stream. + /// + /// A task instance that represents the asynchronous operation. + public Task FlushAsync() + { + this.VerifyCanFlush(false); + + return this.rawOutputContext.FlushAsync(); + } +#endif + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + void IODataOutputInStreamErrorListener.OnInStreamError() + { + this.rawOutputContext.VerifyNotDisposed(); + this.rawOutputContext.TextWriter.Flush(); + + throw new ODataException(Strings.ODataAsyncWriter_CannotWriteInStreamErrorForAsync); + } + + /// + /// Validates that the async writer is not disposed. + /// + private void ValidateWriterNotDisposed() + { + this.rawOutputContext.VerifyNotDisposed(); + } + + /// + /// Verifies that a call is allowed to the writer. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.rawOutputContext.Synchronous) + { + throw new ODataException(Strings.ODataAsyncWriter_SyncCallOnAsyncWriter); + } + } + else + { +#if PORTABLELIB + if (this.rawOutputContext.Synchronous) + { + throw new ODataException(Strings.ODataAsyncWriter_AsyncCallOnSyncWriter); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Verifies that the writer is in correct state for the Flush operation. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanFlush(bool synchronousCall) + { + this.rawOutputContext.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling CreateResponseMessage is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateResponseMessage(bool synchronousCall) + { + this.ValidateWriterNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + if (!this.rawOutputContext.WritingResponse) + { + throw new ODataException(Strings.ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse); + } + + if (responseMessageCreated) + { + throw new ODataException(Strings.ODataAsyncWriter_CannotCreateResponseMoreThanOnce); + } + } + + /// + /// Creates an for writing an operation of an async response - implementation of the actual functionality. + /// + /// The message that can be used to write the response. + private ODataAsynchronousResponseMessage CreateResponseMessageImplementation() + { + var responseMessage = ODataAsynchronousResponseMessage.CreateMessageForWriting(rawOutputContext.OutputStream, this.WriteInnerEnvelope, this.container); + + responseMessageCreated = true; + + return responseMessage; + } + + /// + /// Writes the envelope for the inner HTTP message. + /// + /// The response message to write the envelope. + private void WriteInnerEnvelope(ODataAsynchronousResponseMessage responseMessage) + { + // Write response line. + string statusMessage = HttpUtils.GetStatusMessage(responseMessage.StatusCode); + this.rawOutputContext.TextWriter.WriteLine("{0} {1} {2}", ODataConstants.HttpVersionInAsync, responseMessage.StatusCode, statusMessage); + + // Write headers. + if (responseMessage.Headers != null) + { + foreach (var headerPair in responseMessage.Headers) + { + string headerName = headerPair.Key; + string headerValue = headerPair.Value; + this.rawOutputContext.TextWriter.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}: {1}", headerName, headerValue)); + } + } + + // Write CRLF. + this.rawOutputContext.TextWriter.WriteLine(); + + // Flush the writer since we won't be able to access it anymore. + this.rawOutputContext.TextWriter.Flush(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationHeaders.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationHeaders.cs new file mode 100644 index 0000000..197763a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationHeaders.cs @@ -0,0 +1,169 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections; + using System.Collections.Generic; + #endregion Namespaces + + /// + /// A dictionary implementation with special key-matching semantics; it accepts case-insensitive matches + /// but prefers a case-sensitive one (if present). + /// + /// As an implementation choice we did not use a second dictionary to maintain a cache of case-insensitive + /// keys since we don't want to pay the price of an extra dictionary for cases where the looked up keys + /// match case sensitively (as per spec, should be the default case). + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + public sealed class ODataBatchOperationHeaders : IEnumerable> + { + /// The backing dictionary using case-sensitive key comparison. + private readonly Dictionary caseSensitiveDictionary; + + /// + /// Constructor. + /// + public ODataBatchOperationHeaders() + { + this.caseSensitiveDictionary = new Dictionary(StringComparer.Ordinal); + } + + /// + /// Gets or sets the element with the specified key. + /// + /// The key of the element to get or set. + /// The element with the specified key. + public string this[string key] + { + get + { + string value; + if (this.TryGetValue(key, out value)) + { + return value; + } + + throw new KeyNotFoundException(Strings.ODataBatchOperationHeaderDictionary_KeyNotFound(key)); + } + + set + { + this.caseSensitiveDictionary[key] = value; + } + } + + /// + /// Adds an element with the provided key and value to the dictionary. + /// + /// The object to use as the key of the element to add. + /// The object to use as the value of the element to add. + public void Add(string key, string value) + { + this.caseSensitiveDictionary.Add(key, value); + } + + /// + /// Determines whether the dictionary contains an element with the specified key using case-sensitive comparison. + /// + /// The key to locate in the dictionary. + /// true if the dictionary contains an element with the ; otherwise, false. + /// This method will only try to match the key using case-sensitive comparison. + public bool ContainsKeyOrdinal(string key) + { + return this.caseSensitiveDictionary.ContainsKey(key); + } + + /// + /// Removes the resource with the specified from the headers. + /// + /// The key of the item to remove. + /// true if the item with the specified was removed; otherwise false. + public bool Remove(string key) + { + if (this.caseSensitiveDictionary.Remove(key)) + { + return true; + } + + key = this.FindKeyIgnoreCase(key); + if (key == null) + { + return false; + } + + return this.caseSensitiveDictionary.Remove(key); + } + + /// + /// Gets the value associated with the specified key. + /// + /// The key whose value to get. + /// When this method returns, the value associated with the specified key, if the key is found; + /// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized. + /// true if the dictionary contains an element with the specified key; otherwise, false. + public bool TryGetValue(string key, out string value) + { + if (this.caseSensitiveDictionary.TryGetValue(key, out value)) + { + return true; + } + + key = this.FindKeyIgnoreCase(key); + if (key == null) + { + value = null; + return false; + } + + return this.caseSensitiveDictionary.TryGetValue(key, out value); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + public IEnumerator> GetEnumerator() + { + return this.caseSensitiveDictionary.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return this.caseSensitiveDictionary.GetEnumerator(); + } + + /// + /// Finds in the case sensitive dictionary ignoring the case for comparison. + /// + /// The key to find. + /// The key from the case sensitive dictionary that matched the or null if no match was found. + /// This method throws if multiple case insensitive matches for the specified exist. + private string FindKeyIgnoreCase(string key) + { + string match = null; + foreach (string caseSensitiveKey in this.caseSensitiveDictionary.Keys) + { + if (string.Compare(caseSensitiveKey, key, StringComparison.OrdinalIgnoreCase) == 0) + { + if (match != null) + { + throw new ODataException(Strings.ODataBatchOperationHeaderDictionary_DuplicateCaseInsensitiveKeys(key)); + } + + match = caseSensitiveKey; + } + } + + return match; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationMessage.cs new file mode 100644 index 0000000..b4f8951 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationMessage.cs @@ -0,0 +1,213 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Implementation class wrapped by the and + /// implementations. + /// + internal sealed class ODataBatchOperationMessage : ODataMessage + { + /// Listener interface to be notified of operation changes. + private readonly IODataStreamListener operationListener; + + /// The URL converter to perform custom URL conversion for URLs read or written from/to the payload. + private readonly IODataPayloadUriConverter payloadUriConverter; + + /// A function to retrieve the content stream for this batch operation message. + private Func contentStreamCreatorFunc; + + /// The set of headers for this operation. + private ODataBatchOperationHeaders headers; + + /// + /// Constructor. Base class constructor to create a message for an operation of a batch request/response. + /// + /// A function to retrieve the content stream for this batch operation message. + /// The headers of the batch operation message. + /// Listener interface to be notified of part changes. + /// The URL resolver to perform custom URL resolution for URLs read or written from/to the payload. + /// true if the request message is being written; false when it is read. + internal ODataBatchOperationMessage( + Func contentStreamCreatorFunc, + ODataBatchOperationHeaders headers, + IODataStreamListener operationListener, + IODataPayloadUriConverter payloadUriConverter, + bool writing) + : base(writing, /*disableMessageStreamDisposal*/ false, /*maxMessageSize*/ -1) + { + Debug.Assert(contentStreamCreatorFunc != null, "contentStreamCreatorFunc != null"); + Debug.Assert(operationListener != null, "operationListener != null"); + + this.contentStreamCreatorFunc = contentStreamCreatorFunc; + this.operationListener = operationListener; + this.headers = headers; + this.payloadUriConverter = payloadUriConverter; + } + + /// + /// Returns an enumerable over all the headers for this message. + /// + public override IEnumerable> Headers + { + get + { + return this.headers ?? Enumerable.Empty>(); + } + } + + /// + /// Returns a value of an HTTP header of this operation. + /// + /// The name of the header to get. + /// The value of the HTTP header, or null if no such header was present on the message. + public override string GetHeader(string headerName) + { + if (this.headers != null) + { + string headerValue; + if (this.headers.TryGetValue(headerName, out headerValue)) + { + return headerValue; + } + } + + return null; + } + + /// + /// Sets the value of an HTTP header of this operation. + /// + /// The name of the header to set. + /// The value of the HTTP header or 'null' if the header should be removed. + public override void SetHeader(string headerName, string headerValue) + { + this.VerifyNotCompleted(); + this.VerifyCanSetHeader(); + + if (headerValue == null) + { + if (this.headers != null) + { + this.headers.Remove(headerName); + } + } + else + { + if (this.headers == null) + { + this.headers = new ODataBatchOperationHeaders(); + } + + this.headers[headerName] = headerValue; + } + } + + /// + /// Get the stream backing this message. + /// + /// The stream for this message. + public override Stream GetStream() + { + this.VerifyNotCompleted(); + + // notify the listener that the stream has been requested + this.operationListener.StreamRequested(); + + // now remember that we are done processing the part header data (and only the payload is missing) + Stream contentStream = this.contentStreamCreatorFunc(); + this.PartHeaderProcessingCompleted(); + return contentStream; + } + +#if PORTABLELIB + /// + /// Asynchronously get the stream backing this message. + /// + /// The stream for this message. + public override Task GetStreamAsync() + { + this.VerifyNotCompleted(); + + // notify the listener that the stream has been requested + Task listenerTask = this.operationListener.StreamRequestedAsync(); + + // now remember that we are done processing the part header data (and only the payload is missing) + Stream contentStream = this.contentStreamCreatorFunc(); + this.PartHeaderProcessingCompleted(); + return listenerTask.FollowOnSuccessWith(task => { return (Stream)contentStream; }); + } +#endif + + /// + /// Queries the message for the specified interface type. + /// + /// The type of the interface to query for. + /// The instance of the interface asked for or null if it was not implemented by the message. + internal override TInterface QueryInterface() + { + return null; + } + + /// + /// Method to implement a custom URL resolution scheme. + /// This method returns null if not custom resolution is desired. + /// If the method returns a non-null URL that value will be used without further validation. + /// + /// The (optional) base URI to use for the resolution. + /// The URI read from the payload. + /// + /// A instance that reflects the custom resolution of the method arguments + /// into a URL or null if no custom resolution is desired; in that case the default resolution is used. + /// + internal Uri ResolveUrl(Uri baseUri, Uri payloadUri) + { + ExceptionUtils.CheckArgumentNotNull(payloadUri, "payloadUri"); + + if (this.payloadUriConverter != null) + { + return this.payloadUriConverter.ConvertPayloadUri(baseUri, payloadUri); + } + + // Return null to indicate that no custom URL resolution is desired. + return null; + } + + /// + /// Indicates that the headers and request/response line have been read or written. + /// Can be called only once per batch part and headers cannot be modified + /// anymore after this method was called. + /// + internal void PartHeaderProcessingCompleted() + { + this.contentStreamCreatorFunc = null; + } + + /// + /// Verifies that writing of the message has not been completed; this is called from all methods + /// that are only valid to be called before the message content is written or the message + /// + internal void VerifyNotCompleted() + { + if (this.contentStreamCreatorFunc == null) + { + throw new ODataException(Strings.ODataBatchOperationMessage_VerifyNotCompleted); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationRequestMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationRequestMessage.cs new file mode 100644 index 0000000..c5d1e1c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationRequestMessage.cs @@ -0,0 +1,200 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Message representing an operation in a batch request. + /// +#if PORTABLELIB + public sealed class ODataBatchOperationRequestMessage : IODataRequestMessageAsync, IODataPayloadUriConverter, IContainerProvider +#else + public sealed class ODataBatchOperationRequestMessage : IODataRequestMessage, IODataPayloadUriConverter, IContainerProvider +#endif + { + /// + /// The Content-ID for this request message. + /// The Content-ID for this request message. + public readonly string ContentId; + + /// + /// The Group Id for this request message. Can be null. + /// + /// The Group Id for this request message. + private readonly string groupId; + + /// + /// The actual implementation of the message. + /// We don't derive from this class since we want the actual implementation to remain internal + /// while this class is public. + /// + private readonly ODataBatchOperationMessage message; + + /// + /// The list of request prerequisites for execution of current batch operation. + /// ODL-caller needs to ensure that all the prerequisites have returned successfully + /// before current operation can start. + /// + private readonly List dependsOnIds; + + /// + /// Constructor. Creates a request message for an operation of a batch request. + /// + /// A function to create the content stream. + /// The HTTP method used for this request message. + /// The request Url for this request message. + /// The headers for this request message. + /// Listener interface to be notified of operation changes. + /// The content-ID for the operation request message. + /// The optional URL converter to perform custom URL conversion for URLs written to the payload. + /// true if the request message is being written; false when it is read. + /// The dependency injection container to get related services. + /// + /// Request or group Ids that current request has dependency on. Values are added to a new list. + /// Empty list will be created if value is null. + /// + /// Value for the group id that current request belongs to. Can be null. + internal ODataBatchOperationRequestMessage( + Func contentStreamCreatorFunc, + string method, + Uri requestUrl, + ODataBatchOperationHeaders headers, + IODataStreamListener operationListener, + string contentId, + IODataPayloadUriConverter payloadUriConverter, + bool writing, + IServiceProvider container, + IEnumerable dependsOnIds, + string groupId) + { + Debug.Assert(contentStreamCreatorFunc != null, "contentStreamCreatorFunc != null"); + Debug.Assert(operationListener != null, "operationListener != null"); + Debug.Assert(payloadUriConverter != null, "payloadUriConverter != null"); + + this.Method = method; + this.Url = requestUrl; + this.ContentId = contentId; + this.groupId = groupId; + + this.message = new ODataBatchOperationMessage(contentStreamCreatorFunc, headers, operationListener, payloadUriConverter, writing); + this.Container = container; + + this.dependsOnIds = dependsOnIds == null + ? new List() + : new List(dependsOnIds); + } + + /// Gets an enumerable over all the headers for this message. + /// An enumerable over all the headers for this message. + public IEnumerable> Headers + { + get { return this.message.Headers; } + } + + /// Gets or sets the request URL for this request message. + /// The request URL for this request message. + public Uri Url + { + get; + set; + } + + /// Gets or Sets the HTTP method used for this request message. + /// The HTTP method used for this request message. + public string Method + { + get; + set; + } + + /// + /// The dependency injection container to get related services. + /// + public IServiceProvider Container { get; private set; } + + /// + /// The Group Id for this request message. Can be null. + /// + /// The Group Id for this request message. + public string GroupId + { + get { return this.groupId; } + } + + /// + /// Gets the prerequisite request or group ids. + /// + public IEnumerable DependsOnIds + { + get + { + return this.dependsOnIds; + } + } + + /// + /// Returns the actual operation message which is being wrapped. + /// + internal ODataBatchOperationMessage OperationMessage + { + get + { + return this.message; + } + } + + /// Returns a value of an HTTP header of this operation. + /// The value of the HTTP header, or null if no such header was present on the message. + /// The name of the header to get. + public string GetHeader(string headerName) + { + return this.message.GetHeader(headerName); + } + + /// Sets the value of an HTTP header of this operation. + /// The name of the header to set. + /// The value of the HTTP header or 'null' if the header should be removed. + public void SetHeader(string headerName, string headerValue) + { + this.message.SetHeader(headerName, headerValue); + } + + /// Gets the stream backing for this message. + /// The stream backing for this message. + public Stream GetStream() + { + return this.message.GetStream(); + } + +#if PORTABLELIB + /// Asynchronously get the stream backing for this message. + /// The stream backing for this message. + public Task GetStreamAsync() + { + return this.message.GetStreamAsync(); + } +#endif + + /// Implements a custom URL resolution scheme. + /// A instance that reflects the custom resolution of the method arguments into a URL or null if no custom resolution is desired; in that case the default resolution is used. + /// The (optional) base URI to use for the resolution. + /// The URI read from the payload. + Uri IODataPayloadUriConverter.ConvertPayloadUri(Uri baseUri, Uri payloadUri) + { + return this.message.ResolveUrl(baseUri, payloadUri); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationResponseMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationResponseMessage.cs new file mode 100644 index 0000000..2e05fde --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchOperationResponseMessage.cs @@ -0,0 +1,156 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Message representing an operation in a batch response. + /// +#if PORTABLELIB + public sealed class ODataBatchOperationResponseMessage : IODataResponseMessageAsync, IODataPayloadUriConverter, IContainerProvider +#else + public sealed class ODataBatchOperationResponseMessage : IODataResponseMessage, IODataPayloadUriConverter, IContainerProvider +#endif + { + /// The Content-ID for this response message. + public readonly string ContentId; + + /// + /// The actual implementation of the message. + /// We don't derive from this class since we want the actual implementation to remain internal + /// while this class is public. + /// + private readonly ODataBatchOperationMessage message; + + /// The result status code of the response message. + private int statusCode; + + /// + /// Constructor. + /// + /// A function to retrieve the content stream for this batch operation message. + /// The headers of the batch operation message. + /// Listener interface to be notified of part changes. + /// The content-ID for the operation response message. + /// The optional URL converter to perform custom URL conversion for URLs written to the payload. + /// true if the request message is being written; false when it is read. + /// The dependency injection container to get related services. + /// Value for the group id that corresponding request belongs to. Can be null. + internal ODataBatchOperationResponseMessage( + Func contentStreamCreatorFunc, + ODataBatchOperationHeaders headers, + IODataStreamListener operationListener, + string contentId, + IODataPayloadUriConverter payloadUriConverter, + bool writing, + IServiceProvider container, + string groupId) + { + Debug.Assert(contentStreamCreatorFunc != null, "contentStreamCreatorFunc != null"); + Debug.Assert(operationListener != null, "operationListener != null"); + + this.message = new ODataBatchOperationMessage(contentStreamCreatorFunc, headers, operationListener, payloadUriConverter, writing); + this.ContentId = contentId; + this.Container = container; + this.GroupId = groupId; + } + + /// Gets or sets the result status code of the response message. + /// The result status code of the response message. + public int StatusCode + { + get + { + return this.statusCode; + } + + set + { + this.message.VerifyNotCompleted(); + this.statusCode = value; + } + } + + /// Gets an enumerable over all the headers for this message. + /// An enumerable over all the headers for this message. + public IEnumerable> Headers + { + get { return this.message.Headers; } + } + + /// + /// The dependency injection container to get related services. + /// + public IServiceProvider Container { get; private set; } + + /// + /// The id of the group or change set that this response message is part of. + /// + public string GroupId { get; } + + /// + /// Returns the actual operation message which is being wrapped. + /// + internal ODataBatchOperationMessage OperationMessage + { + get + { + return this.message; + } + } + + /// Returns a value of an HTTP header of this operation. + /// The value of the HTTP header, or null if no such header was present on the message. + /// The name of the header to get. + public string GetHeader(string headerName) + { + return this.message.GetHeader(headerName); + } + + /// Sets the value of an HTTP header of this operation. + /// The name of the header to set. + /// The value of the HTTP header or null if the header should be removed. + public void SetHeader(string headerName, string headerValue) + { + this.message.SetHeader(headerName, headerValue); + } + + /// Gets the stream backing for this message. + /// The stream backing for this message. + public Stream GetStream() + { + return this.message.GetStream(); + } + +#if PORTABLELIB + /// Asynchronously get the stream backing for this message. + /// The stream backing for this message. + public Task GetStreamAsync() + { + return this.message.GetStreamAsync(); + } +#endif + + /// Method to implement a custom URL resolution scheme. This method returns null if not custom resolution is desired. If the method returns a non-null URL that value will be used without further validation. + /// A instance that reflects the custom resolution of the method arguments into a URL or null if no custom resolution is desired; in that case the default resolution is used. + /// The (optional) base URI to use for the resolution. + /// The URI read from the payload. + Uri IODataPayloadUriConverter.ConvertPayloadUri(Uri baseUri, Uri payloadUri) + { + return this.message.ResolveUrl(baseUri, payloadUri); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriConverter.cs new file mode 100644 index 0000000..c767ac1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriConverter.cs @@ -0,0 +1,154 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + #endregion Namespaces + + /// + /// Implementation of the batch-specific URL converter that converts cross-referencing URLs properly. + /// + internal sealed class ODataBatchPayloadUriConverter : IODataPayloadUriConverter + { + /// The URL converter from the batch message. + private readonly IODataPayloadUriConverter batchMessagePayloadUriConverter; + + /// A hashset with all content IDs used so far in the batch; this is used for cross-referencing URL conversion. + private HashSet contentIdCache; + + /// + /// Constructor. + /// + /// The URL converter from the batch message. + internal ODataBatchPayloadUriConverter(IODataPayloadUriConverter batchMessagePayloadUriConverter) + { + this.batchMessagePayloadUriConverter = batchMessagePayloadUriConverter; + } + + /// + /// The URL converter from the batch message. + /// + internal IODataPayloadUriConverter BatchMessagePayloadUriConverter + { + get + { + return this.batchMessagePayloadUriConverter; + } + } + + /// + /// A read-only enumeration of contentIdCache. + /// + internal IEnumerable ContentIdCache + { + get { return this.contentIdCache; } + } + + /// + /// Method to implement a custom URL conversion scheme. + /// This method returns null if not custom conversion is desired. + /// If the method returns a non-null URL that value will be used without further validation. + /// + /// The (optional) base URI to use for the conversion. + /// The URI read from the payload. + /// + /// A instance that reflects the custom conversion of the method arguments + /// into a URL or null if no custom conversion is desired; in that case the default conversion is used. + /// + Uri IODataPayloadUriConverter.ConvertPayloadUri(Uri baseUri, Uri payloadUri) + { + ExceptionUtils.CheckArgumentNotNull(payloadUri, "payloadUri"); + + if (this.contentIdCache != null && !payloadUri.IsAbsoluteUri) + { + // On relative URIs none of the properties except for OriginalString, IsAbsoluteUri and UserEscaped are allowed + string originalString = UriUtils.UriToString(payloadUri); + Debug.Assert(originalString != null, "Original strings cannot be null in System.Uri."); + + if (originalString.Length > 0 && originalString[0] == '$') + { + // if the original strings starts with '$' find the first '/' and see whether + // the characters before it are recognized as content ID token + string tokenString; + int ix = originalString.IndexOf('/', 1); + if (ix > 0) + { + tokenString = originalString.Substring(1, ix - 1); + } + else + { + tokenString = originalString.Substring(1); + } + + if (this.contentIdCache.Contains(tokenString)) + { + // We found a valid content ID cross reference; return the payload URI unchanged. + return payloadUri; + } + } + } + + if (this.batchMessagePayloadUriConverter != null) + { + // If we have a converter from the batch message use it as the fallback. + return this.batchMessagePayloadUriConverter.ConvertPayloadUri(baseUri, payloadUri); + } + + // Otherwise return null to use the default URL conversion instead. + return null; + } + + /// + /// Add the content ID to the hashset of valid content IDs. + /// + /// The (non-null) content ID to add. + internal void AddContentId(string contentId) + { + Debug.Assert(contentId != null, "contentId != null"); + + if (this.contentIdCache == null) + { + this.contentIdCache = new HashSet(StringComparer.Ordinal); + } + + Debug.Assert(!this.contentIdCache.Contains(contentId), "Should have verified that we don't see duplicate content IDs."); + this.contentIdCache.Add(contentId); + } + + /// + /// Checks whether a given (non-null) content ID is already in the content ID cache. + /// + /// The content ID to check for. + /// true if the content ID cache already contains a content ID with value ; otherwise false. + internal bool ContainsContentId(string contentId) + { + Debug.Assert(contentId != null, "contentId != null"); + + if (this.contentIdCache == null) + { + return false; + } + + return this.contentIdCache.Contains(contentId); + } + + /// + /// Resets the cache of content IDs. This is called at the end of each changeset + /// since content IDs are only unique within a changeset. + /// + internal void Reset() + { + if (this.contentIdCache != null) + { + this.contentIdCache.Clear(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriOptions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriOptions.cs new file mode 100644 index 0000000..4eb2183 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchPayloadUriOptions.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Indicates the format of Request-URI in each sub request in the batch operation. + /// + public enum BatchPayloadUriOption + { + /// + /// Absolute URI with schema, host, port, and absolute resource path. + /// + /// Example: + /// GET https://host:1234/path/service/People(1) HTTP/1.1 + AbsoluteUri, + + /// + /// Absolute resource path and separate Host header. + /// + /// Example: + /// GET /path/service/People(1) HTTP/1.1 + /// Host: myserver.mydomain.org:1234 + AbsoluteUriUsingHostHeader, + + /// + /// Resource path relative to the batch request URI. + /// + /// Example: + /// GET People(1) HTTP/1.1 + RelativeUri + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReader.cs new file mode 100644 index 0000000..909a510 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReader.cs @@ -0,0 +1,673 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Abstract class for reading OData batch messages; also verifies the proper sequence of read calls on the reader. + /// + public abstract class ODataBatchReader : IODataStreamListener + { + /// The input context to read the content from. + private readonly ODataInputContext inputContext; + + /// True if the writer was created for synchronous operation; false for asynchronous. + private readonly bool synchronous; + + /// The batch-specific URL converter that stores the content IDs found in a changeset and supports resolving cross-referencing URLs. + private readonly ODataBatchPayloadUriConverter payloadUriConverter; + + /// The dependency injection container to get related services. + private readonly IServiceProvider container; + + /// The current state of the batch reader. + private ODataBatchReaderState batchReaderState; + + /// The current size of the batch message, i.e., how many query operations and changesets have been read. + private uint currentBatchSize; + + /// The current size of the active changeset, i.e., how many operations have been read for the changeset. + private uint currentChangeSetSize; + + /// An enumeration tracking the state of the current batch operation. + private OperationState operationState; + + /// Whether the reader is currently reading within a changeset. + private bool isInChangeset; + + /// + /// Content-ID header value for request part with associated entity, which can be referenced by subsequent requests + /// in the same changeset or other changesets. + /// + private string contentIdToAddOnNextRead; + + /// + /// Constructor. + /// + /// The input context to read the content from. + /// true if the reader is created for synchronous operation; false for asynchronous. + protected ODataBatchReader(ODataInputContext inputContext, bool synchronous) + { + Debug.Assert(inputContext != null, "inputContext != null"); + + this.inputContext = inputContext; + this.container = inputContext.Container; + this.synchronous = synchronous; + this.payloadUriConverter = new ODataBatchPayloadUriConverter(inputContext.PayloadUriConverter); + } + + /// + /// An enumeration to track the state of a batch operation. + /// + private enum OperationState + { + /// No action has been performed on the operation. + None, + + /// The batch message for the operation has been created and returned to the caller. + MessageCreated, + + /// The stream of the batch operation message has been requested. + StreamRequested, + + /// The stream of the batch operation message has been disposed. + StreamDisposed, + } + + /// Gets the current state of the batch reader. + /// The current state of the batch reader. + public ODataBatchReaderState State + { + get + { + this.inputContext.VerifyNotDisposed(); + return this.batchReaderState; + } + + private set + { + this.batchReaderState = value; + } + } + + /// + /// Public property for the current group id the reader is processing. + /// The primary usage of this to correlate atomic group id in request and + /// response operation messages as needed. + /// + public string CurrentGroupId + { + get + { + return GetCurrentGroupIdImplementation(); + } + } + + /// + /// The input context to read the content from. + /// + protected ODataInputContext InputContext + { + get { return this.inputContext; } + } + + /// + /// The reader's Operation state + /// + private OperationState ReaderOperationState + { + get { return this.operationState; } + set { this.operationState = value; } + } + + /// Reads the next part from the batch message payload. + /// True if more items were read; otherwise false. + public bool Read() + { + this.VerifyCanRead(true); + return this.InterceptException((Func)this.ReadSynchronously); + } + +#if PORTABLELIB + /// Asynchronously reads the next part from the batch message payload. + /// A task that when completed indicates whether more items were read. + public Task ReadAsync() + { + this.VerifyCanRead(false); + return this.ReadAsynchronously().FollowOnFaultWith(t => this.State = ODataBatchReaderState.Exception); + } +#endif + + /// Returns an for reading the content of a batch operation. + /// A request message for reading the content of a batch operation. + public ODataBatchOperationRequestMessage CreateOperationRequestMessage() + { + this.VerifyCanCreateOperationRequestMessage(/*synchronousCall*/ true); + ODataBatchOperationRequestMessage result = + this.InterceptException((Func)this.CreateOperationRequestMessageImplementation); + this.ReaderOperationState = OperationState.MessageCreated; + this.contentIdToAddOnNextRead = result.ContentId; + return result; + } + +#if PORTABLELIB + /// Asynchronously returns an for reading the content of a batch operation. + /// A task that when completed returns a request message for reading the content of a batch operation. + public Task CreateOperationRequestMessageAsync() + { + this.VerifyCanCreateOperationRequestMessage(/*synchronousCall*/ false); + return TaskUtils.GetTaskForSynchronousOperation( + this.CreateOperationRequestMessageImplementation) + .FollowOnSuccessWithTask( + t => + { + this.ReaderOperationState = OperationState.MessageCreated; + this.contentIdToAddOnNextRead = t.Result.ContentId; + return t; + }) + .FollowOnFaultWith(t => this.State = ODataBatchReaderState.Exception); + } +#endif + + /// Returns an for reading the content of a batch operation. + /// A response message for reading the content of a batch operation. + public ODataBatchOperationResponseMessage CreateOperationResponseMessage() + { + this.VerifyCanCreateOperationResponseMessage(/*synchronousCall*/ true); + ODataBatchOperationResponseMessage result = + this.InterceptException((Func)this.CreateOperationResponseMessageImplementation); + this.ReaderOperationState = OperationState.MessageCreated; + return result; + } + +#if PORTABLELIB + /// Asynchronously returns an for reading the content of a batch operation. + /// A task that when completed returns a response message for reading the content of a batch operation. + public Task CreateOperationResponseMessageAsync() + { + this.VerifyCanCreateOperationResponseMessage(/*synchronousCall*/ false); + return TaskUtils.GetTaskForSynchronousOperation( + this.CreateOperationResponseMessageImplementation) + .FollowOnSuccessWithTask( + t => + { + this.ReaderOperationState = OperationState.MessageCreated; + return t; + }) + .FollowOnFaultWith(t => this.State = ODataBatchReaderState.Exception); + } +#endif + + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + void IODataStreamListener.StreamRequested() + { + this.operationState = OperationState.StreamRequested; + } + +#if PORTABLELIB + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + /// + /// A task representing any action that is running as part of the status change of the reader; + /// null if no such action exists. + /// + Task IODataStreamListener.StreamRequestedAsync() + { + this.operationState = OperationState.StreamRequested; + return TaskUtils.CompletedTask; + } +#endif + + /// + /// This method is called to notify that the content stream of a batch operation has been disposed. + /// + void IODataStreamListener.StreamDisposed() + { + this.operationState = OperationState.StreamDisposed; + } + + /// + /// Sets the 'Exception' state and then throws an ODataException with the specified error message. + /// + /// The error message for the exception. + protected void ThrowODataException(string errorMessage) + { + this.State = ODataBatchReaderState.Exception; + throw new ODataException(errorMessage); + } + + /// + /// Gets the group id for the current request. + /// Default implementation here is provided returning null. + /// + /// The group id for the current request. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A method for consistency with the rest of the API.")] + protected virtual string GetCurrentGroupIdImplementation() + { + return null; + } + + /// + /// Returns the cached for reading the content of an operation + /// in a batch request. + /// + /// The message that can be used to read the content of the batch request operation from. + protected abstract ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation(); + + /// + /// Returns the cached for reading the content of an operation + /// in a batch request. + /// + /// The message that can be used to read the content of the batch request operation from. + protected abstract ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation(); + + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// The batch reader state after the read. + protected abstract ODataBatchReaderState ReadAtStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'Operation'. + /// + /// The batch reader state after the read. + protected abstract ODataBatchReaderState ReadAtOperationImplementation(); + + /// + /// Implementation of the reader logic when in state 'ChangesetStart'. + /// + /// The batch reader state after the read. + protected abstract ODataBatchReaderState ReadAtChangesetStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'ChangesetEnd'. + /// + /// The batch reader state after the read. + protected abstract ODataBatchReaderState ReadAtChangesetEndImplementation(); + + /// + /// Instantiate an instance. + /// + /// The function for stream creation. + /// The HTTP method used for this request message. + /// The request Url for this request message. + /// The headers for this request message. + /// The contentId of this request message. + /// The group id that this request belongs to. Can be null. + /// + /// The prerequisite request Ids of this request message that could be specified by caller + /// explicitly. + /// + /// + /// Whether the dependsOnIds value needs to be validated. + /// The instance. + protected ODataBatchOperationRequestMessage BuildOperationRequestMessage( + Func streamCreatorFunc, + string method, + Uri requestUri, + ODataBatchOperationHeaders headers, + string contentId, + string groupId, + IEnumerable dependsOnRequestIds, + bool dependsOnIdsValidationRequired) + { + if (dependsOnRequestIds != null && dependsOnIdsValidationRequired) + { + foreach (string id in dependsOnRequestIds) + { + if (!this.payloadUriConverter.ContainsContentId(id)) + { + throw new ODataException(Strings.ODataBatchReader_DependsOnIdNotFound(id, contentId)); + } + } + } + + Uri uri = ODataBatchUtils.CreateOperationRequestUri( + requestUri, this.inputContext.MessageReaderSettings.BaseUri, this.payloadUriConverter); + + ODataBatchUtils.ValidateReferenceUri(requestUri, dependsOnRequestIds, + this.inputContext.MessageReaderSettings.BaseUri); + + return new ODataBatchOperationRequestMessage(streamCreatorFunc, method, uri, headers, this, + contentId, this.payloadUriConverter, /*writing*/ false, this.container, dependsOnRequestIds, groupId); + } + + /// + /// Instantiate an instance and set the status code. + /// + /// The function for stream creation. + /// The status code for the response. + /// The headers for this response message. + /// The contentId of this request message. + /// The groupId of this request message. + /// The instance. + protected ODataBatchOperationResponseMessage BuildOperationResponseMessage( + Func streamCreatorFunc, + int statusCode, + ODataBatchOperationHeaders headers, + string contentId, + string groupId) + { + ODataBatchOperationResponseMessage responseMessage = new ODataBatchOperationResponseMessage( + streamCreatorFunc, headers, this, + contentId, + this.payloadUriConverter.BatchMessagePayloadUriConverter, /*writing*/ false, this.container, groupId) + { + StatusCode = statusCode + }; + return responseMessage; + } + + /// + /// Increases the size of the current batch message; throws if the allowed limit is exceeded. + /// + private void IncreaseBatchSize() + { + if (this.currentBatchSize == this.inputContext.MessageReaderSettings.MessageQuotas.MaxPartsPerBatch) + { + throw new ODataException(Strings.ODataBatchReader_MaxBatchSizeExceeded(this.inputContext.MessageReaderSettings.MessageQuotas.MaxPartsPerBatch)); + } + + this.currentBatchSize++; + } + + /// + /// Increases the size of the current change set; throws if the allowed limit is exceeded. + /// + private void IncreaseChangesetSize() + { + if (this.currentChangeSetSize == this.inputContext.MessageReaderSettings.MessageQuotas.MaxOperationsPerChangeset) + { + throw new ODataException(Strings.ODataBatchReader_MaxChangeSetSizeExceeded(this.inputContext.MessageReaderSettings.MessageQuotas.MaxOperationsPerChangeset)); + } + + this.currentChangeSetSize++; + } + + /// + /// Resets the size of the current change set to 0. + /// + private void ResetChangesetSize() + { + this.currentChangeSetSize = 0; + } + + /// + /// Reads the next part from the batch message payload. + /// + /// true if more information was read; otherwise false. + private bool ReadSynchronously() + { + return this.ReadImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously reads the next part from the batch message payload. + /// + /// A task that when completed indicates whether more information was read. + [SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "API design calls for a bool being returned from the task here.")] + private Task ReadAsynchronously() + { + // We are reading from the fully buffered read stream here; thus it is ok + // to use synchronous reads and then return a completed task + // NOTE: once we switch to fully async reading this will have to change + return TaskUtils.GetTaskForSynchronousOperation(this.ReadImplementation); + } +#endif + + /// + /// Continues reading from the batch message payload. + /// + /// true if more items were read; otherwise false. + private bool ReadImplementation() + { + Debug.Assert(this.ReaderOperationState != OperationState.StreamRequested, "Should have verified that no operation stream is still active."); + + switch (this.State) + { + case ODataBatchReaderState.Initial: + // The stream should be positioned at the beginning of the batch content, + // that is before the first boundary (or the preamble if there is one). + this.State = this.ReadAtStartImplementation(); + break; + + case ODataBatchReaderState.Operation: + // When reaching this state we already read the MIME headers of the operation. + // Clients MUST call CreateOperationRequestMessage + // or CreateOperationResponseMessage to read at least the headers of the operation. + // This is important since we need to read the ContentId header (if present) and + // add it to the URL resolver. + if (this.ReaderOperationState == OperationState.None) + { + // No message was created; fail + throw new ODataException(Strings.ODataBatchReader_NoMessageWasCreatedForOperation); + } + + // Reset the operation state; the operation state only + // tracks the state of a batch operation while in state Operation. + this.ReaderOperationState = OperationState.None; + + // Also add a pending ContentId header to the URL resolver now. We ensured above + // that a message has been created for this operation and thus the headers (incl. + // a potential content ID header) have been read. + if (this.contentIdToAddOnNextRead != null) + { + if (this.payloadUriConverter.ContainsContentId(this.contentIdToAddOnNextRead)) + { + throw new ODataException( + Strings.ODataBatchReader_DuplicateContentIDsNotAllowed(this.contentIdToAddOnNextRead)); + } + + this.payloadUriConverter.AddContentId(this.contentIdToAddOnNextRead); + this.contentIdToAddOnNextRead = null; + } + + // When we are done with an operation, we have to skip ahead to the next part + // when Read is called again. Note that if the operation stream was never requested + // and the content of the operation has not been read, we'll skip it here. + Debug.Assert(this.ReaderOperationState == OperationState.None, "Operation state must be 'None' at the end of the operation."); + + if (this.isInChangeset) + { + this.IncreaseChangesetSize(); + } + else + { + this.IncreaseBatchSize(); + } + + this.State = this.ReadAtOperationImplementation(); + + break; + + case ODataBatchReaderState.ChangesetStart: + // When at the start of a changeset, skip ahead to the first operation in the + // changeset (or the end boundary of the changeset). + if (this.isInChangeset) + { + ThrowODataException(Strings.ODataBatchReaderStream_NestedChangesetsAreNotSupported); + } + + // Increment the batch size at the start of the changeset since we haven't counted it yet + // when this state was transitioned into upon detection of this sub-batch. + this.IncreaseBatchSize(); + + this.State = this.ReadAtChangesetStartImplementation(); + if (this.inputContext.MessageReaderSettings.MaxProtocolVersion <= ODataVersion.V4) + { + this.payloadUriConverter.Reset(); + } + + this.isInChangeset = true; + break; + + case ODataBatchReaderState.ChangesetEnd: + // When at the end of a changeset, reset the changeset boundary and the + // changeset size and then skip to the next part. + this.ResetChangesetSize(); + this.isInChangeset = false; + this.State = this.ReadAtChangesetEndImplementation(); + break; + + case ODataBatchReaderState.Exception: // fall through + case ODataBatchReaderState.Completed: + Debug.Assert(false, "Should have checked in VerifyCanRead that we are not in one of these states."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReader_ReadImplementation)); + + default: + Debug.Assert(false, "Unsupported reader state " + this.State + " detected."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReader_ReadImplementation)); + } + + return this.State != ODataBatchReaderState.Completed && this.State != ODataBatchReaderState.Exception; + } + + /// + /// Verifies that calling CreateOperationRequestMessage if valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateOperationRequestMessage(bool synchronousCall) + { + this.VerifyReaderReady(); + this.VerifyCallAllowed(synchronousCall); + + if (this.inputContext.ReadingResponse) + { + this.ThrowODataException(Strings.ODataBatchReader_CannotCreateRequestOperationWhenReadingResponse); + } + + if (this.State != ODataBatchReaderState.Operation) + { + this.ThrowODataException(Strings.ODataBatchReader_InvalidStateForCreateOperationRequestMessage(this.State)); + } + + if (this.operationState != OperationState.None) + { + this.ThrowODataException(Strings.ODataBatchReader_OperationRequestMessageAlreadyCreated); + } + } + + /// + /// Verifies that calling CreateOperationResponseMessage if valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateOperationResponseMessage(bool synchronousCall) + { + this.VerifyReaderReady(); + this.VerifyCallAllowed(synchronousCall); + + if (!this.inputContext.ReadingResponse) + { + this.ThrowODataException(Strings.ODataBatchReader_CannotCreateResponseOperationWhenReadingRequest); + } + + if (this.State != ODataBatchReaderState.Operation) + { + this.ThrowODataException(Strings.ODataBatchReader_InvalidStateForCreateOperationResponseMessage(this.State)); + } + + if (this.operationState != OperationState.None) + { + this.ThrowODataException(Strings.ODataBatchReader_OperationResponseMessageAlreadyCreated); + } + } + + /// + /// Verifies that calling Read is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanRead(bool synchronousCall) + { + this.VerifyReaderReady(); + this.VerifyCallAllowed(synchronousCall); + + if (this.State == ODataBatchReaderState.Exception || this.State == ODataBatchReaderState.Completed) + { + throw new ODataException(Strings.ODataBatchReader_ReadOrReadAsyncCalledInInvalidState(this.State)); + } + } + + /// + /// Validates that the batch reader is ready to process a new read or create message request. + /// + private void VerifyReaderReady() + { + this.inputContext.VerifyNotDisposed(); + + // If the operation stream was requested but not yet disposed, the batch reader can't be used to do anything. + if (this.operationState == OperationState.StreamRequested) + { + throw new ODataException(Strings.ODataBatchReader_CannotUseReaderWhileOperationStreamActive); + } + } + + /// + /// Verifies that a call is allowed to the reader. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.synchronous) + { + throw new ODataException(Strings.ODataBatchReader_SyncCallOnAsyncReader); + } + } + else + { +#if PORTABLELIB + if (this.synchronous) + { + throw new ODataException(Strings.ODataBatchReader_AsyncCallOnSyncReader); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state Exception and then rethrow the exception. + /// + /// The type of the result returned from the . + /// The action to execute. + /// The result of the . + private T InterceptException(Func action) + { + try + { + return action(); + } + catch (Exception e) + { + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + this.State = ODataBatchReaderState.Exception; + } + + throw; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderState.cs new file mode 100644 index 0000000..740c5e1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderState.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// Enumeration with all the states the batch reader can be in. + public enum ODataBatchReaderState + { + /// The state the batch reader is in after having been created. + Initial, + + /// The batch reader detected an operation. + /// In this state the start boundary, the request/response line + /// and the operation headers have already been read. + Operation, + + /// The batch reader detected the start of a change set. + /// In this state the start boundary and the change set + /// headers have already been read. + ChangesetStart, + + /// The batch reader completed reading a change set. + ChangesetEnd, + + /// The batch reader completed reading the batch payload. + /// The batch reader cannot be used in this state anymore. + Completed, + + /// The batch reader encountered an error reading the batch payload. + /// The batch reader cannot be used in this state anymore. + Exception, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStream.cs new file mode 100644 index 0000000..a8a28e3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStream.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.IO; + using System.Text; + #endregion Namespaces + + /// + /// Class used by the to read the various pieces of a batch payload. + /// + /// + /// This is the base class for format-specific derived classes to process batch requests in different formats, + /// such as multipart/mixed format and application/json format. + /// + internal abstract class ODataBatchReaderStream + { + /// The buffer used by the batch reader stream to scan for boundary strings. + protected readonly ODataBatchReaderStreamBuffer BatchBuffer; + + + /// + /// true if the underlying stream was exhausted during a read operation; we won't try to read from the + /// underlying stream again once it was exhausted. + /// + protected bool underlyingStreamExhausted; + + /// + /// Constructor. + /// + internal ODataBatchReaderStream() + { + this.BatchBuffer = new ODataBatchReaderStreamBuffer(); + } + + /// + /// Reads from the batch stream while ensuring that we stop reading at each boundary. + /// + /// The byte array to read bytes into. + /// The offset in the buffer where to start reading bytes into. + /// The number of bytes to read. + /// The number of bytes actually read. + internal abstract int ReadWithDelimiter(byte[] userBuffer, int userBufferOffset, int count); + + + /// + /// Reads from the batch stream without checking for a boundary delimiter since we + /// know the length of the stream. + /// + /// The byte array to read bytes into. + /// The offset in the buffer where to start reading bytes into. + /// The number of bytes to read. + /// The number of bytes actually read. + internal abstract int ReadWithLength(byte[] userBuffer, int userBufferOffset, int count); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamBuffer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamBuffer.cs new file mode 100644 index 0000000..fa8cacf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamBuffer.cs @@ -0,0 +1,617 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + #endregion Namespaces + + /// + /// This class represents the internal buffer of the . + /// + internal sealed class ODataBatchReaderStreamBuffer + { + /// The size of the look-ahead buffer. + internal const int BufferLength = 8000; + + /// Length of the longest supported line terminator character sequence; makes the code easier to read. + private const int MaxLineFeedLength = 2; + + /// The length of two '-' characters to make the code easier to read. + private const int TwoDashesLength = 2; + + /// The byte array storing the actual bytes of the buffer. + private readonly byte[] bytes = new byte[BufferLength]; + + /// The current position inside the buffer. + /// This is the position of the byte that is the next to be read. + private int currentReadPosition = 0; + + /// The number of (not yet consumed) bytes currently in the buffer. + private int numberOfBytesInBuffer; + + /// + /// The byte array that acts as the actual storage of the buffered data. + /// + internal byte[] Bytes + { + get + { + return this.bytes; + } + } + + /// + /// The current position inside the buffer. + /// + /// This is the position of the byte that is the next to be read. + internal int CurrentReadPosition + { + get + { + return this.currentReadPosition; + } + } + + /// + /// The number of (not yet consumed) bytes currently in the buffer. + /// + internal int NumberOfBytesInBuffer + { + get + { + return this.numberOfBytesInBuffer; + } + } + + /// + /// Indexer into the byte buffer. + /// + /// The position in the buffer to get. + /// The byte at position in the buffer. + internal byte this[int index] + { + get + { + return this.bytes[index]; + } + } + + /// + /// Skip to the specified position in the buffer. + /// Adjust the current position and the number of bytes in the buffer. + /// + /// The position to skip to. + internal void SkipTo(int newPosition) + { + Debug.Assert(newPosition >= this.currentReadPosition, "Can only skip forward."); + Debug.Assert(newPosition <= this.currentReadPosition + this.numberOfBytesInBuffer, "Cannot skip beyond end of buffer."); + + int diff = newPosition - this.currentReadPosition; + this.currentReadPosition = newPosition; + this.numberOfBytesInBuffer -= diff; + } + + /// + /// Refills the buffer from the specified stream. + /// + /// The stream to refill the buffer from. + /// The index in the current buffer starting from which the + /// currently buffered data should be preserved. + /// true if the underlying stream got exhausted while refilling. + /// This method will first shift any data that is to be preserved to the beginning + /// of the buffer and then refill the rest of the buffer from the . + internal bool RefillFrom(Stream stream, int preserveFrom) + { + Debug.Assert(stream != null, "stream != null"); + Debug.Assert(preserveFrom >= this.currentReadPosition, "preserveFrom must be at least as big as the current position in the buffer."); + + this.ShiftToBeginning(preserveFrom); + int numberOfBytesToRead = ODataBatchReaderStreamBuffer.BufferLength - this.numberOfBytesInBuffer; + int numberOfBytesRead = stream.Read(this.bytes, this.numberOfBytesInBuffer, numberOfBytesToRead); + this.numberOfBytesInBuffer += numberOfBytesRead; + + // return true if the underlying stream is exhausted + return numberOfBytesRead == 0; + } + + /// + /// Scans the current buffer for a line end. + /// + /// The start position of the line terminator or -1 if not found. + /// The end position of the line terminator or -1 if not found. + /// An enumeration value indicating whether the line termintor was found completely, partially or not at all. + internal ODataBatchReaderStreamScanResult ScanForLineEnd(out int lineEndStartPosition, out int lineEndEndPosition) + { + bool endOfBufferReached; + return this.ScanForLineEnd( + this.currentReadPosition, + BufferLength, + /*allowLeadingWhitespaceOnly*/ false, + out lineEndStartPosition, + out lineEndEndPosition, + out endOfBufferReached); + } + + /// + /// Scans the current buffer for the specified boundary. + /// + /// The boundary strings to search for; this enumerable is sorted from the inner-most boundary + /// to the top-most boundary. The boundary strings don't include the leading line terminator or the leading dashes. + /// Stop if no boundary (or boundary start) is found after this number of bytes. + /// The start position of the boundary or -1 if not found. + /// Note that the start position is the first byte of the leading line terminator. + /// The end position of the boundary or -1 if not found. + /// Note that the end position is the last byte of the trailing line terminator. + /// true if the boundary is an end boundary (followed by two dashes); otherwise false. + /// true if the detected boundary is the parent boundary; otherwise false. + /// An enumeration value indicating whether the boundary was completely, partially or not found in the buffer. + internal ODataBatchReaderStreamScanResult ScanForBoundary( + IEnumerable boundaries, + int maxDataBytesToScan, + out int boundaryStartPosition, + out int boundaryEndPosition, + out bool isEndBoundary, + out bool isParentBoundary) + { + Debug.Assert(boundaries != null, "boundaries != null"); + + boundaryStartPosition = -1; + boundaryEndPosition = -1; + isEndBoundary = false; + isParentBoundary = false; + + int lineScanStartIndex = this.currentReadPosition; + + while (true) + { + // NOTE: a boundary has to start with a line terminator followed by two dashes ('-'), + // the actual boundary string, another two dashes (for an end boundary), + // optional whitespace characters and another line terminator. + // NOTE: for WCF DS compatibility we do not require the leading line terminator. + int lineEndStartPosition, boundaryDelimiterStartPosition; + ODataBatchReaderStreamScanResult lineEndScanResult = this.ScanForBoundaryStart( + lineScanStartIndex, + maxDataBytesToScan, + out lineEndStartPosition, + out boundaryDelimiterStartPosition); + + switch (lineEndScanResult) + { + case ODataBatchReaderStreamScanResult.NoMatch: + // Did not find a line end or boundary delimiter in the buffer or after reading maxDataBytesToScan bytes. + // Report no boundary match. + return ODataBatchReaderStreamScanResult.NoMatch; + + case ODataBatchReaderStreamScanResult.PartialMatch: + // Found a partial line end or boundary delimiter at the end of the buffer but before reading maxDataBytesToScan. + // Report a partial boundary match. + boundaryStartPosition = lineEndStartPosition < 0 ? boundaryDelimiterStartPosition : lineEndStartPosition; + return ODataBatchReaderStreamScanResult.PartialMatch; + + case ODataBatchReaderStreamScanResult.Match: + // Found a full line end or boundary delimiter start ('--') before reading maxDataBytesToScan or + // hitting the end of the buffer. Start matching the boundary delimiters. + // + // Start with the expected boundary (the first one in the enumerable): + // * if we find a full match - return match because we are done + // * if we find a partial match - return partial match because we have to continue checking the expected boundary. + // * if we find no match - we know that it is not the expected boundary; check the parent boundary (if it exists). + isParentBoundary = false; + foreach (string boundary in boundaries) + { + ODataBatchReaderStreamScanResult boundaryMatch = this.MatchBoundary( + lineEndStartPosition, + boundaryDelimiterStartPosition, + boundary, + out boundaryStartPosition, + out boundaryEndPosition, + out isEndBoundary); + switch (boundaryMatch) + { + case ODataBatchReaderStreamScanResult.Match: + return ODataBatchReaderStreamScanResult.Match; + + case ODataBatchReaderStreamScanResult.PartialMatch: + boundaryEndPosition = -1; + isEndBoundary = false; + return ODataBatchReaderStreamScanResult.PartialMatch; + + case ODataBatchReaderStreamScanResult.NoMatch: + // try the parent boundary (if any) or continue scanning + boundaryStartPosition = -1; + boundaryEndPosition = -1; + isEndBoundary = false; + isParentBoundary = true; + break; + + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStreamBuffer_ScanForBoundary)); + } + } + + // If we could not match the boundary, try again starting at the current boundary start index. Or if we already did that + // move one character ahead. + lineScanStartIndex = lineScanStartIndex == boundaryDelimiterStartPosition + ? boundaryDelimiterStartPosition + 1 + : boundaryDelimiterStartPosition; + + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStreamBuffer_ScanForBoundary)); + } + } + } + + /// + /// Scans the current buffer for a boundary start, which is either a line resource set or two dashes (since we don't require the leading line resource set). + /// + /// The index at which to start scanning for the boundary start. + /// Stop if no boundary start was found after this number of non end-of-line bytes. + /// The start position of the line end or -1 if not found. + /// The start position of the boundary delimiter or -1 if not found. + /// An enumeration value indicating whether the boundary start was completely, partially or not found in the buffer. + private ODataBatchReaderStreamScanResult ScanForBoundaryStart( + int scanStartIx, + int maxDataBytesToScan, + out int lineEndStartPosition, + out int boundaryDelimiterStartPosition) + { + int lastByteToCheck = this.currentReadPosition + Math.Min(maxDataBytesToScan, this.numberOfBytesInBuffer) - 1; + + for (int i = scanStartIx; i <= lastByteToCheck; ++i) + { + char ch = (char)this.bytes[i]; + + // Note the following common line resource set chars: + // \n - UNIX \r\n - DOS \r - Mac + if (ch == '\r' || ch == '\n') + { + // We found a line end; now we have to check whether it + // consists of a second character or not. + lineEndStartPosition = i; + + // We only want to report PartialMatch if we ran out of bytes in the buffer; + // so even if we found the start of a line end in the last byte to check (and + // have more bytes in the buffer) we still look ahead by one. + if (ch == '\r' && i == lastByteToCheck && maxDataBytesToScan >= this.numberOfBytesInBuffer) + { + boundaryDelimiterStartPosition = i; + return ODataBatchReaderStreamScanResult.PartialMatch; + } + + // If the next char is '\n' we found a line end consisting of two characters; adjust the end position. + boundaryDelimiterStartPosition = (ch == '\r' && (char)this.bytes[i + 1] == '\n') ? i + 2 : i + 1; + return ODataBatchReaderStreamScanResult.Match; + } + else if (ch == '-') + { + lineEndStartPosition = -1; + + // We found a potential start of a boundary; we only want to report PartialMatch + // if we ran out of bytes in the buffer. + // So even if we found the start of a potential boundary start in the last byte to check (and + // have more bytes in the buffer) we still look ahead by one. + if (i == lastByteToCheck && maxDataBytesToScan >= this.numberOfBytesInBuffer) + { + boundaryDelimiterStartPosition = i; + return ODataBatchReaderStreamScanResult.PartialMatch; + } + + if ((char)this.bytes[i + 1] == '-') + { + boundaryDelimiterStartPosition = i; + return ODataBatchReaderStreamScanResult.Match; + } + } + } + + lineEndStartPosition = -1; + boundaryDelimiterStartPosition = -1; + return ODataBatchReaderStreamScanResult.NoMatch; + } + + /// + /// Scans the current buffer for a line end. + /// + /// The index at which to start scanning for the line terminator. + /// Stop if no line end (or beginning of line end) was found after this number of non end-of-line bytes. + /// true if only whitespace data bytes are expected before the end-of-line characters; otherwise false. + /// The start position of the line terminator or -1 if not found. + /// The end position of the line terminator or -1 if not found. + /// true if the end of the buffer was reached while scanning for the line end; otherwise false. + /// An enumeration value indicating whether the line termintor was found completely, partially or not at all. + /// This method only returns if we found the start + /// of a line terminator at the last character in the buffer. + private ODataBatchReaderStreamScanResult ScanForLineEnd( + int scanStartIx, + int maxDataBytesToScan, + bool allowLeadingWhitespaceOnly, + out int lineEndStartPosition, + out int lineEndEndPosition, + out bool endOfBufferReached) + { + endOfBufferReached = false; + + int lastByteToCheck = this.currentReadPosition + Math.Min(maxDataBytesToScan, this.numberOfBytesInBuffer) - 1; + + for (int i = scanStartIx; i <= lastByteToCheck; ++i) + { + char ch = (char)this.bytes[i]; + + // Note the following common line resource set chars: + // \n - UNIX \r\n - DOS \r - Mac + if (ch == '\r' || ch == '\n') + { + // We found a line end; now we have to check whether it + // consists of a second character or not. + lineEndStartPosition = i; + + // We only want to report PartialMatch if we ran out of bytes in the buffer; + // so if we found the start of a line end in the last byte to check we still + // look ahead by one. + if (ch == '\r' && i == lastByteToCheck && maxDataBytesToScan >= this.numberOfBytesInBuffer) + { + lineEndEndPosition = -1; + return ODataBatchReaderStreamScanResult.PartialMatch; + } + + lineEndEndPosition = lineEndStartPosition; + + // If the next char is '\n' we found a line end consisting of two characters; adjust the end position. + if (ch == '\r' && (char)this.bytes[i + 1] == '\n') + { + lineEndEndPosition++; + } + + return ODataBatchReaderStreamScanResult.Match; + } + else if (allowLeadingWhitespaceOnly && !char.IsWhiteSpace(ch)) + { + // We found a non-whitespace character but only whitespace characters are allowed + lineEndStartPosition = -1; + lineEndEndPosition = -1; + return ODataBatchReaderStreamScanResult.NoMatch; + } + } + + endOfBufferReached = true; + lineEndStartPosition = -1; + lineEndEndPosition = -1; + return ODataBatchReaderStreamScanResult.NoMatch; + } + + /// + /// Check whether the bytes in the buffer at the specified start index match the expected boundary string. + /// + /// The start of the line resource set preceding the boundary (if present). + /// The start position of the boundary delimiter. + /// The boundary string to check for. + /// If a match is detected, the start of the boundary delimiter, + /// i.e., either the start of the leading line resource set or of the leading dashes. + /// If a match is detected, the position of the boundary end; otherwise -1. + /// true if the detected boundary is an end boundary; otherwise false. + /// An indicating whether a match, a partial match or no match was found. + private ODataBatchReaderStreamScanResult MatchBoundary( + int lineEndStartPosition, + int boundaryDelimiterStartPosition, + string boundary, + out int boundaryStartPosition, + out int boundaryEndPosition, + out bool isEndBoundary) + { + boundaryStartPosition = -1; + boundaryEndPosition = -1; + + int bufferLastByte = this.currentReadPosition + this.numberOfBytesInBuffer - 1; + int boundaryEndPositionAfterLineFeed = + boundaryDelimiterStartPosition + TwoDashesLength + boundary.Length + TwoDashesLength - 1; + + // NOTE: for simplicity reasons we require that the full end (!) boundary plus the maximum size + // of the line terminator fits into the buffer to get a non-partial match. + // By doing so we can reliably detect whether we are dealing with an end boundary or not. + // Otherwise the logic to figure out whether we found a boundary or not is riddled with + // corner cases that only complicate the code. + bool isPartialMatch; + int matchLength; + if (bufferLastByte < boundaryEndPositionAfterLineFeed + MaxLineFeedLength) + { + isPartialMatch = true; + matchLength = Math.Min(bufferLastByte, boundaryEndPositionAfterLineFeed) - boundaryDelimiterStartPosition + 1; + } + else + { + isPartialMatch = false; + matchLength = boundaryEndPositionAfterLineFeed - boundaryDelimiterStartPosition + 1; + } + + if (this.MatchBoundary(boundary, boundaryDelimiterStartPosition, matchLength, out isEndBoundary)) + { + boundaryStartPosition = lineEndStartPosition < 0 ? boundaryDelimiterStartPosition : lineEndStartPosition; + + if (isPartialMatch) + { + isEndBoundary = false; + return ODataBatchReaderStreamScanResult.PartialMatch; + } + else + { + // If we fully matched the boundary compute the boundary end position + boundaryEndPosition = boundaryDelimiterStartPosition + TwoDashesLength + boundary.Length - 1; + if (isEndBoundary) + { + boundaryEndPosition += TwoDashesLength; + } + + // Once we could match all the characters and delimiters of the boundary string + // (and the optional trailing '--') we now have to continue reading until the next + // line resource set that terminates the boundary. Only whitespace characters may exist + // after the boundary and before the line resource set. + int terminatingLineResourceSetStartPosition, terminatingLineFeedEndPosition; + bool endOfBufferReached; + ODataBatchReaderStreamScanResult terminatingLineFeedScanResult = + this.ScanForLineEnd( + boundaryEndPosition + 1, + int.MaxValue, + /*allowLeadingWhitespaceOnly*/true, + out terminatingLineResourceSetStartPosition, + out terminatingLineFeedEndPosition, + out endOfBufferReached); + + switch (terminatingLineFeedScanResult) + { + case ODataBatchReaderStreamScanResult.NoMatch: + if (endOfBufferReached) + { + // Reached the end of the buffer and only found whitespaces. + if (boundaryStartPosition == 0) + { + // If the boundary starts at the first position in the buffer + // and we still could not find the end of it because there are + // so many whitespaces before the terminating line resource set - fail + // (security limit on the whitespaces) + throw new ODataException(Strings.ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached(BufferLength)); + } + + // Report a partial match. + isEndBoundary = false; + return ODataBatchReaderStreamScanResult.PartialMatch; + } + else + { + // Found a different character than whitespace or end-of-line + // so we did not match the boundary. + break; + } + + case ODataBatchReaderStreamScanResult.PartialMatch: + // Found only a partial line end at the end of the buffer. + if (boundaryStartPosition == 0) + { + // If the boundary starts at the first position in the buffer + // and we still could not find the end of it because there are + // so many whitespaces before the terminating line resource set - fail + // (security limit on the whitespaces) + throw new ODataException(Strings.ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached(BufferLength)); + } + + // Report a partial match. + isEndBoundary = false; + return ODataBatchReaderStreamScanResult.PartialMatch; + + case ODataBatchReaderStreamScanResult.Match: + // At this point we only found whitespace characters and then the terminating line resource set; + // adjust the boundary end position + boundaryEndPosition = terminatingLineFeedEndPosition; + return ODataBatchReaderStreamScanResult.Match; + + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchReaderStreamBuffer_ScanForBoundary)); + } + } + } + + return ODataBatchReaderStreamScanResult.NoMatch; + } + + /// + /// Try to match the specified boundary string starting at the specified position. + /// + /// The boundary string to search for; this does not include + /// the leading line terminator or the leading dashes. + /// The index at which to start matching the boundary. + /// The number of characters to match. + /// true if the boundary string is used in an end boundary; otherwise false. + /// true if it was established that the buffer starting at + /// matches the ; otherwise false. + /// This method also returns false if the boundary string was completly matched against the + /// buffer but it could not be determined whether it is used in an end boundary or not. + private bool MatchBoundary(string boundary, int startIx, int matchLength, out bool isEndBoundary) + { + Debug.Assert(!string.IsNullOrEmpty(boundary), "!string.IsNullOrEmpty(boundary)"); + Debug.Assert(matchLength >= 0, "Match length must not be negative."); + Debug.Assert(matchLength <= boundary.Length + 4, "Must never try to match more than the boundary string and the delimiting '--' on each side."); + Debug.Assert(startIx + matchLength <= this.currentReadPosition + this.numberOfBytesInBuffer, "Match length must not exceed buffer."); + + isEndBoundary = false; + + if (matchLength == 0) + { + return true; + } + + int trailingDashes = 0; + int currentIx = startIx; + + // Shift the range by 2 so that 'i' will line up with the index of the character in the boundary + // string. 'i < 0' means check for a leading '-', 'i >= boundary.Length' means check for a trailing '-'. + for (int i = -TwoDashesLength; i < matchLength - TwoDashesLength; ++i) + { + if (i < 0) + { + // Check for a leading '-' + if (this.bytes[currentIx] != '-') + { + return false; + } + } + else if (i < boundary.Length) + { + // Compare against the character in the boundary string + if (this.bytes[currentIx] != boundary[i]) + { + return false; + } + } + else + { + // Check for a trailing '-' + if (this.bytes[currentIx] != '-') + { + // We matched the full boundary; return true but it is not an end boundary. + return true; + } + + trailingDashes++; + } + + currentIx++; + } + + Debug.Assert(trailingDashes <= TwoDashesLength, "Should never look for more than " + TwoDashesLength + " trailing dashes."); + isEndBoundary = trailingDashes == TwoDashesLength; + return true; + } + + /// + /// Shifts all bytes in the buffer after a specified start index to the beginning of the buffer. + /// + /// The start index where to start shifting. + private void ShiftToBeginning(int startIndex) + { + Debug.Assert(startIndex >= this.currentReadPosition, "Start of shift must be greater or equal than current position."); + + int bytesToShift = this.currentReadPosition + this.numberOfBytesInBuffer - startIndex; + + this.currentReadPosition = 0; + + if (bytesToShift <= 0) + { + // Nothing to shift; start index is too large + this.numberOfBytesInBuffer = 0; + } + else + { + this.numberOfBytesInBuffer = bytesToShift; + Buffer.BlockCopy(this.bytes, startIndex, this.bytes, 0, bytesToShift); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamScanResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamScanResult.cs new file mode 100644 index 0000000..7e49f12 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchReaderStreamScanResult.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// An enumeration representing the result of a scan operation through + /// the batch reader stream's buffer. + /// + internal enum ODataBatchReaderStreamScanResult + { + /// No match with the requested boundary was found (not even a partial one). + NoMatch, + + /// A partial match with the requested boundary was found. + PartialMatch, + + /// A complete match with the requested boundary was found. + /// + /// This is only returned if we could also check whether the boundary is an end + /// boundary or not; otherwise a partial match is returned. + /// + Match, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchUtils.cs new file mode 100644 index 0000000..4908695 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchUtils.cs @@ -0,0 +1,220 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using Microsoft.OData.JsonLight; + + #endregion Namespaces + + /// + /// Helper methods used by the ODataBatchWriter and ODataBatchReader (and related classes). + /// + internal static class ODataBatchUtils + { + /// + /// Creates the URI for a batch request operation. + /// + /// The uri to process. + /// The base Uri to use. + /// An optional custom URL converter to convert URLs for writing them into the payload. + /// An URI to be used in the request line of a batch request operation. It uses the + /// first and falls back to the default URI building schema if the no URL resolver is specified or the URL resolver + /// returns null. In the default scheme, the method either returns the specified if it was absolute, + /// or it's combination with the if it was relative. + /// + /// This method will fail if no custom resolution is implemented and the specified is + /// relative and there's no base URI available. + /// + internal static Uri CreateOperationRequestUri(Uri uri, Uri baseUri, IODataPayloadUriConverter payloadUriConverter) + { + Debug.Assert(uri != null, "uri != null"); + + Uri resultUri; + if (payloadUriConverter != null) + { + // The resolver returns 'null' if no custom resolution is desired. + resultUri = payloadUriConverter.ConvertPayloadUri(baseUri, uri); + if (resultUri != null) + { + return resultUri; + } + } + + if (uri.IsAbsoluteUri) + { + resultUri = uri; + } + else + { + if (baseUri == null) + { + string errorMessage = UriUtils.UriToString(uri).StartsWith("$", StringComparison.Ordinal) + ? Strings.ODataBatchUtils_RelativeUriStartingWithDollarUsedWithoutBaseUriSpecified(UriUtils.UriToString(uri)) + : Strings.ODataBatchUtils_RelativeUriUsedWithoutBaseUriSpecified(UriUtils.UriToString(uri)); + throw new ODataException(errorMessage); + } + + resultUri = UriUtils.UriToAbsoluteUri(baseUri, uri); + } + + return resultUri; + } + + /// + /// Creates a batch operation stream from the specified batch stream. + /// + /// The batch stream to create the operation read stream for. + /// The headers of the current part; based on the header we create different, optimized stream implementations. + /// The operation listener to be passed to the newly created read stream. + /// A new instance. + internal static ODataReadStream CreateBatchOperationReadStream( + ODataBatchReaderStream batchReaderStream, + ODataBatchOperationHeaders headers, + IODataStreamListener operationListener) + { + Debug.Assert(batchReaderStream != null, "batchReaderStream != null"); + Debug.Assert(operationListener != null, "operationListener != null"); + + // See whether we have a Content-Length header + string contentLengthValue; + if (headers.TryGetValue(ODataConstants.ContentLengthHeader, out contentLengthValue)) + { + int length = Int32.Parse(contentLengthValue, CultureInfo.InvariantCulture); + if (length < 0) + { + throw new ODataException(Strings.ODataBatchReaderStream_InvalidContentLengthSpecified(contentLengthValue)); + } + + return ODataReadStream.Create(batchReaderStream, operationListener, length); + } + + return ODataReadStream.Create(batchReaderStream, operationListener); + } + + /// + /// Creates a batch operation write stream over the specified output stream. + /// + /// The output stream to create the operation write stream over. + /// The operation listener to be passed to the newly created write stream. + /// A new instance. + internal static ODataWriteStream CreateBatchOperationWriteStream( + Stream outputStream, + IODataStreamListener operationListener) + { + Debug.Assert(outputStream != null, "outputStream != null"); + Debug.Assert(operationListener != null, "operationListener != null"); + + return new ODataWriteStream(outputStream, operationListener); + } + + /// + /// Grows the specified byte array by the specified amount. + /// + /// The byte array to grow. + /// The number of bytes currently in the buffer. + /// The number of bytes to be added to the array. + internal static void EnsureArraySize(ref byte[] buffer, int numberOfBytesInBuffer, int requiredByteCount) + { + Debug.Assert(buffer != null, "bytes != null"); + Debug.Assert(numberOfBytesInBuffer >= 0, "numberOfBytesInBuffer >= 0"); + Debug.Assert(requiredByteCount >= 0, "byteCount >= 0"); + + int remainingUnusedBytesInBuffer = buffer.Length - numberOfBytesInBuffer; + if (requiredByteCount <= remainingUnusedBytesInBuffer) + { + // Still enough room in the buffer to store all required bytes + return; + } + + int numberOfAdditionalBytesNeeded = requiredByteCount - remainingUnusedBytesInBuffer; + Debug.Assert(numberOfAdditionalBytesNeeded > 0, "Expected a positive number of additional bytes."); + + // NOTE: grow the array only by the exact number of needed bytes; we expect the + // caller to specify a larger required byte count to grow the array more. + byte[] oldBytes = buffer; + buffer = new byte[buffer.Length + numberOfAdditionalBytesNeeded]; + Buffer.BlockCopy(oldBytes, 0, buffer, 0, numberOfBytesInBuffer); + } + + /// + /// Validates that the uri's reference of $requestId, if used, is one of the depends-on requests. + /// The Uri can be either absolute or relative. + /// Exception is thrown if the request Id reference is not found in the list of depends-on requests. + /// + /// The Uri to validate the request Id reference. + /// Enumeration of request Ids used to lookup the request Id reference. + /// The baseUri used for validation. + internal static void ValidateReferenceUri(Uri uri, IEnumerable dependsOnRequestIds, Uri baseUri) + { + Debug.Assert(uri != null, "uri != null"); + + if (UriUtils.UriToString(uri).IndexOf('$') == -1) + { + // uri does not use $requestId, + return; + } + + string relativePath = null; + if (uri.IsAbsoluteUri) + { + if (baseUri == null) + { + // The absolute Uri can contain $ character followed by name of system resource + // such as $all, $metadata, etc, along with an unspecified baseUri. In such cases there are + // not reference Uri to validate. + return; + } + + string baseUriString = UriUtils.UriToString(baseUri); + if (!uri.AbsoluteUri.StartsWith(baseUriString, StringComparison.Ordinal)) + { + // Skip validation if uri doesn't start with the baseUri. + return; + } + + relativePath = uri.AbsoluteUri.Substring(baseUriString.Length); + } + else + { + relativePath = UriUtils.UriToString(uri); + } + + // Trim the starting forward slashes as needed. + while (relativePath.StartsWith("/", StringComparison.Ordinal)) + { + relativePath = relativePath.Substring(1); + } + + if (relativePath.Length > 0 && relativePath[0] == '$') + { + // If the relative path starts with '$', find the request id reference optionally ended by '/'. + int idx = relativePath.IndexOf('/', 1); + + string referenceId = idx > 0 + ? relativePath.Substring(1, idx - 1) + : relativePath.Substring(1); + + // Validate that dependsOn contains the Id reference. + // Note that the dependsOnRequestIds here is already flattened and consists of individual request + // ids, therefore it should contain the request id referenced by the Uri. + if (dependsOnRequestIds == null || !dependsOnRequestIds.Contains(referenceId)) + { + throw new ODataException(Strings.ODataBatchReader_ReferenceIdNotIncludedInDependsOn( + referenceId, UriUtils.UriToString(uri), + dependsOnRequestIds != null ? string.Join(",", dependsOnRequestIds.ToArray()) : "null")); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchWriter.cs new file mode 100644 index 0000000..1f50c4e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBatchWriter.cs @@ -0,0 +1,1045 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + #endregion Namespaces + + /// + /// Abstract class for writing OData batch messages; also verifies the proper sequence of write calls on the writer. + /// + public abstract class ODataBatchWriter : IODataStreamListener, IODataOutputInStreamErrorListener + { + /// The output context to write to. + private readonly ODataOutputContext outputContext; + + /// The batch-specific URL converter that stores the content IDs found in a changeset and supports resolving cross-referencing URLs. + private readonly ODataBatchPayloadUriConverter payloadUriConverter; + + /// The dependency injection container to get related services. + private readonly IServiceProvider container; + + /// The state the writer currently is in. + private BatchWriterState state; + + /// The request message for the operation that is currently written if it's a request; + /// or null if no part is written right now or it's a response part. + private ODataBatchOperationRequestMessage currentOperationRequestMessage; + + /// The response message for the operation that is currently written if it's a response; + /// or null if no part is written right now or it's a request part. + private ODataBatchOperationResponseMessage currentOperationResponseMessage; + + /// + /// The value of the Content-ID header of the current operation (or null if no Content-ID header exists). + /// + /// + /// Note that the current Content-ID header is not included immediately in the content ID cache + /// since the current content ID will only be visible to subsequent operations. + /// + private string currentOperationContentId; + + /// The current size of the batch message, i.e., how many query operations and changesets have been written. + private uint currentBatchSize; + + /// The current size of the active changeset, i.e., how many request have been written for the changeset. + private uint currentChangeSetSize; + + /// + /// Whether the writer is currently processing inside a changeset or atomic group. + /// + private bool isInChangset; + + /// + /// Constructor. + /// + /// The output context to write to. + internal ODataBatchWriter(ODataOutputContext outputContext) + { + Debug.Assert(outputContext != null, "outputContext != null"); + Debug.Assert( + outputContext.MessageWriterSettings.BaseUri == null || outputContext.MessageWriterSettings.BaseUri.IsAbsoluteUri, + "We should have validated that baseUri is absolute."); + + this.outputContext = outputContext; + this.container = outputContext.Container; + this.payloadUriConverter = new ODataBatchPayloadUriConverter(outputContext.PayloadUriConverter); + } + + /// + /// An enumeration representing the current state of the writer. + /// + protected enum BatchWriterState + { + /// The writer is in initial state; nothing has been written yet. + Start, + + /// WriteStartBatch has been called. + BatchStarted, + + /// WriteStartChangeSet has been called. + ChangesetStarted, + + /// CreateOperationRequestMessage/CreateOperationResponseMessage has been called. + OperationCreated, + + /// + /// ODataMessage.GetStreamAsync() has been called on an operation which caused a to be created; + /// the batch writer is unusable while an operation is being written. + /// + OperationStreamRequested, + + /// The stream for writing the content of an operation has been disposed. The batch writer can now be used again. + OperationStreamDisposed, + + /// WriteEndChangeSet has been called. + ChangesetCompleted, + + /// WriteEndBatch has been called. + BatchCompleted, + + /// The writer is in error state; nothing can be written anymore except the error payload. + Error + } + + /// The request message for the operation that is currently written if it's a request; or null if no operation is written right now or it's a response operation. + protected ODataBatchOperationRequestMessage CurrentOperationRequestMessage + { + get + { + Debug.Assert(this.currentOperationRequestMessage == null || !this.outputContext.WritingResponse, "Request message can only be filled when writing request."); + Debug.Assert(this.currentOperationRequestMessage == null || this.currentOperationResponseMessage == null, "Only request or response message can be set, not both."); + return this.currentOperationRequestMessage; + } + + set + { + Debug.Assert(value == null || !this.outputContext.WritingResponse, "Can only set the request message if we're writing a request."); + Debug.Assert(this.currentOperationRequestMessage == null || this.currentOperationResponseMessage == null, "Only request or response message can be set, not both."); + this.currentOperationRequestMessage = value; + } + } + + /// The response message for the operation that is currently written if it's a response; + /// or null if no operation is written right now or it's a request operation. + protected ODataBatchOperationResponseMessage CurrentOperationResponseMessage + { + get + { + Debug.Assert(this.currentOperationResponseMessage == null || this.outputContext.WritingResponse, "Response message can only be filled when writing response."); + Debug.Assert(this.currentOperationRequestMessage == null || this.currentOperationResponseMessage == null, "Only request or response message can be set, not both."); + return this.currentOperationResponseMessage; + } + + set + { + Debug.Assert(value == null || this.outputContext.WritingResponse, "Can only set the response message if we're writing a response."); + Debug.Assert(this.currentOperationRequestMessage == null || this.currentOperationResponseMessage == null, "Only request or response message can be set, not both."); + this.currentOperationResponseMessage = value; + } + } + + /// + /// The output context to write to. + /// + protected ODataOutputContext OutputContext + { + get { return this.outputContext; } + } + + /// Starts a new batch; can be only called once and as first call. + public void WriteStartBatch() + { + this.VerifyCanWriteStartBatch(true); + this.WriteStartBatchImplementation(); + } + +#if PORTABLELIB + /// Asynchronously starts a new batch; can be only called once and as first call. + /// A task instance that represents the asynchronous write operation. + public Task WriteStartBatchAsync() + { + this.VerifyCanWriteStartBatch(false); + return TaskUtils.GetTaskForSynchronousOperation(this.WriteStartBatchImplementation); + } +#endif + + /// Ends a batch; can only be called after WriteStartBatch has been called and if no other active changeset or operation exist. + public void WriteEndBatch() + { + this.VerifyCanWriteEndBatch(true); + this.WriteEndBatchImplementation(); + + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + this.Flush(); + } + +#if PORTABLELIB + /// Asynchronously ends a batch; can only be called after WriteStartBatch has been called and if no other active change set or operation exist. + /// A task instance that represents the asynchronous write operation. + public Task WriteEndBatchAsync() + { + this.VerifyCanWriteEndBatch(false); + return TaskUtils.GetTaskForSynchronousOperation(this.WriteEndBatchImplementation) + + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + .FollowOnSuccessWithTask(task => this.FlushAsync()); + } +#endif + + /// + /// Starts a new changeset without specifying group id. + /// This can only be called after WriteStartBatch and if no other active operation or changeset exists. + /// + public void WriteStartChangeset() + { + this.WriteStartChangeset(Guid.NewGuid().ToString()); + } + + /// + /// Starts a new atomic group or changeset with the specified group id or changeset GUID corresponding to change set boundary. + /// This can only be called after WriteStartBatch and if no other active operation or changeset exists. + /// The change set Id of the batch request. Cannot be null. + /// Thrown if the is null. + public void WriteStartChangeset(string changesetId) + { + ExceptionUtils.CheckArgumentNotNull(changesetId, "changesetId"); + + this.VerifyCanWriteStartChangeset(true); + this.WriteStartChangesetImplementation(changesetId); + this.FinishWriteStartChangeset(); + } + +#if PORTABLELIB + /// Asynchronously starts a new change set without specifying group id; + /// This can only be called after WriteStartBatch and if no other active operation or change set exists. + /// A task instance that represents the asynchronous write operation. + public Task WriteStartChangesetAsync() + { + return WriteStartChangesetAsync(Guid.NewGuid().ToString()); + } + + /// + /// Asynchronously starts a new change set; can only be called after WriteStartBatch and if no other active operation or change set exists. + /// + /// The change set Id of the batch request. Cannot be null. + /// A task instance that represents the asynchronous write operation. + /// Thrown if the is null. + public Task WriteStartChangesetAsync(string changesetId) + { + ExceptionUtils.CheckArgumentNotNull(changesetId, "changesetId"); + + this.VerifyCanWriteStartChangeset(false); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartChangesetImplementation(changesetId)) + .FollowOnSuccessWith(t => this.FinishWriteStartChangeset()); + } +#endif + + /// Ends an active changeset; this can only be called after WriteStartChangeset and only once for each changeset. + public void WriteEndChangeset() + { + this.VerifyCanWriteEndChangeset(true); + this.WriteEndChangesetImplementation(); + FinishWriteEndChangeset(); + } + +#if PORTABLELIB + /// Asynchronously ends an active change set; this can only be called after WriteStartChangeset and only once for each change set. + /// A task instance that represents the asynchronous write operation. + public Task WriteEndChangesetAsync() + { + this.VerifyCanWriteEndChangeset(false); + return TaskUtils.GetTaskForSynchronousOperation(this.WriteEndChangesetImplementation) + .FollowOnSuccessWith(t => this.FinishWriteEndChangeset()); + } +#endif + + /// Creates an for writing an operation of a batch request. + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The Content-ID value to write in ChangeSet header, would be ignored if is "GET". + /// The message that can be used to write the request operation. + public ODataBatchOperationRequestMessage CreateOperationRequestMessage(string method, Uri uri, string contentId) + { + return CreateOperationRequestMessage(method, uri, contentId, BatchPayloadUriOption.AbsoluteUri); + } + + /// Creates an for writing an operation of a batch request. + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The Content-ID value to write in ChangeSet header, would be ignored if is "GET". + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// The message that can be used to write the request operation. + public ODataBatchOperationRequestMessage CreateOperationRequestMessage(string method, Uri uri, string contentId, + BatchPayloadUriOption payloadUriOption) + { + return CreateOperationRequestMessage(method, uri, contentId, payloadUriOption, /*dependsOnIds*/null); + } + + /// + /// Creates an for writing an operation of a batch request. + /// + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// + /// The Content-ID value to write in ChangeSet header, would be ignored if is "GET". + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// The prerequisite request ids of this request. + /// The message that can be used to write the request operation. + public ODataBatchOperationRequestMessage CreateOperationRequestMessage(string method, Uri uri, string contentId, + BatchPayloadUriOption payloadUriOption, IEnumerable dependsOnIds) + { + this.VerifyCanCreateOperationRequestMessage(true, method, uri, contentId); + return CreateOperationRequestMessageInternal(method, uri, contentId, payloadUriOption, dependsOnIds); + } + +#if PORTABLELIB + /// Creates a message for asynchronously writing an operation of a batch request. + /// The message that can be used to asynchronously write the request operation. + /// The HTTP method to be used for this request operation. + /// The URI to be used for this request operation. + /// + /// The Content-ID value to write in ChangeSet header, would be ignored if is "GET". + /// A task that when completed returns the newly created operation request message. + public Task CreateOperationRequestMessageAsync(string method, Uri uri, string contentId) + { + return CreateOperationRequestMessageAsync(method, uri, contentId, BatchPayloadUriOption.AbsoluteUri); + } + + /// Creates a message for asynchronously writing an operation of a batch request. + /// The message that can be used to asynchronously write the request operation. + /// The HTTP method to be used for this request operation. + /// The URI to be used for this request operation. + /// + /// The Content-ID value to write in ChangeSet header, would be ignored if is "GET". + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// A task that when completed returns the newly created operation request message. + public Task CreateOperationRequestMessageAsync(string method, Uri uri, string contentId, + BatchPayloadUriOption payloadUriOption) + { + return CreateOperationRequestMessageAsync(method, uri, contentId, payloadUriOption, /*dependsOnIds*/null); + } + + /// + /// Creates a message for asynchronously writing an operation of a batch request. + /// The message that can be used to asynchronously write the request operation. + /// The HTTP method to be used for this request operation. + /// The URI to be used for this request operation. + /// + /// The Content-ID value to write in ChangeSet header, would be ignored if is "GET". + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// The prerequisite request ids of this request. + /// A task that when completed returns the newly created operation request message. + public Task CreateOperationRequestMessageAsync(string method, Uri uri, string contentId, + BatchPayloadUriOption payloadUriOption, IList dependsOnIds) + { + this.VerifyCanCreateOperationRequestMessage(false, method, uri, contentId); + + return TaskUtils.GetTaskForSynchronousOperation(() => + CreateOperationRequestMessageInternal(method, uri, contentId, payloadUriOption, dependsOnIds)); + } +#endif + + /// Creates a message for writing an operation of a batch response. + /// The Content-ID value to write in ChangeSet head. + /// The message that can be used to write the response operation. + public ODataBatchOperationResponseMessage CreateOperationResponseMessage(string contentId) + { + this.VerifyCanCreateOperationResponseMessage(true); + return this.CreateOperationResponseMessageImplementation(contentId); + } + +#if PORTABLELIB + /// Asynchronously creates an for writing an operation of a batch response. + /// The Content-ID value to write in ChangeSet head. + /// A task that when completed returns the newly created operation response message. + public Task CreateOperationResponseMessageAsync(string contentId) + { + this.VerifyCanCreateOperationResponseMessage(false); + return TaskUtils.GetTaskForSynchronousOperation( + () => this.CreateOperationResponseMessageImplementation(contentId)); + } +#endif + + + /// Flushes the write buffer to the underlying stream. + public void Flush() + { + this.VerifyCanFlush(true); + + // make sure we switch to state FatalExceptionThrown if an exception is thrown during flushing. + try + { + this.FlushSynchronously(); + } + catch + { + this.SetState(BatchWriterState.Error); + throw; + } + } + +#if PORTABLELIB + /// Flushes the write buffer to the underlying stream asynchronously. + /// A task instance that represents the asynchronous operation. + public Task FlushAsync() + { + this.VerifyCanFlush(false); + + // Make sure we switch to writer state Error if an exception is thrown during flushing. + return this.FlushAsynchronously().FollowOnFaultWith(t => this.SetState(BatchWriterState.Error)); + } +#endif + + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + public abstract void StreamRequested(); + +#if PORTABLELIB + /// + /// This method is called to notify that the content stream for a batch operation has been requested. + /// + /// + /// A task representing any action that is running as part of the status change of the operation; + /// null if no such action exists. + /// + public abstract Task StreamRequestedAsync(); +#endif + + /// + /// This method is called to notify that the content stream of a batch operation has been disposed. + /// + public abstract void StreamDisposed(); + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + public abstract void OnInStreamError(); + + /// + /// Flush the output. + /// + protected abstract void FlushSynchronously(); + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected abstract Task FlushAsynchronously(); +#endif + + /// + /// Ends a batch. + /// + protected abstract void WriteEndBatchImplementation(); + + /// + /// Starts a new changeset. + /// + /// + /// The atomic group id, aka changeset GUID of the batch request. + /// + protected abstract void WriteStartChangesetImplementation(string groupOrChangesetId); + + /// + /// Ends an active changeset. + /// + protected abstract void WriteEndChangesetImplementation(); + + /// + /// Creates an for writing an operation of a batch response. + /// + /// The Content-ID value to write in ChangeSet head. + /// The message that can be used to write the response operation. + protected abstract ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation(string contentId); + + /// + /// Creates an for writing an operation of a batch request. + /// + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The Content-ID value to write in ChangeSet head. + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// The prerequisite request ids of this request. + /// The message that can be used to write the request operation. + protected abstract ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation(string method, Uri uri, + string contentId, BatchPayloadUriOption payloadUriOption, IEnumerable dependsOnIds); + + + /// + /// Sets a new writer state; verifies that the transition from the current state into new state is valid. + /// + /// The writer state to transition into. + protected void SetState(BatchWriterState newState) + { + this.InterceptException(() => this.ValidateTransition(newState)); + + this.state = newState; + } + + /// + /// Verifies that the writer is not disposed. + /// + protected abstract void VerifyNotDisposed(); + + /// + /// Starts a new batch. + /// + protected abstract void WriteStartBatchImplementation(); + + /// + /// Given an enumerable of dependsOn ids containing request ids and group ids, return an enumeration + /// of equivalent request ids. + /// + /// The dependsOn ids specifying current request's prerequisites. + /// An enumerable consists of request ids. + protected abstract IEnumerable GetDependsOnRequestIds(IEnumerable dependsOnIds); + + /// + /// Wrapper method to create an operation request message that can be used to write the operation content to, utilizing + /// private members and . + /// + /// The output stream underlying the operation message. + /// The HTTP method to use for the message to create. + /// The request URL for the message to create. + /// The contentId of this request message. + /// The group id that this request belongs to. Can be null. + /// The prerequisite request ids of this request. + /// An to write the request content to. + protected ODataBatchOperationRequestMessage BuildOperationRequestMessage(Stream outputStream, string method, Uri uri, + string contentId, string groupId, IEnumerable dependsOnIds) + { + IEnumerable convertedDependsOnIds = GetDependsOnRequestIds(dependsOnIds); + Debug.Assert(convertedDependsOnIds != null, "convertedDependsOnIds != null"); + + if (dependsOnIds != null) + { + // Validate explicit dependsOnIds cases. + foreach (string id in convertedDependsOnIds) + { + if (!this.payloadUriConverter.ContainsContentId(id)) + { + throw new ODataException(Strings.ODataBatchReader_DependsOnIdNotFound(id, contentId)); + } + } + } + + // If dependsOnIds is not specified, use the payloadUrlConverter; otherwise use the dependOnIds converted + // from specified value. + IEnumerable requestIdsForUrlReferenceValidation = + dependsOnIds == null ? this.payloadUriConverter.ContentIdCache : convertedDependsOnIds; + + ODataBatchUtils.ValidateReferenceUri(uri, requestIdsForUrlReferenceValidation, this.outputContext.MessageWriterSettings.BaseUri); + + Func streamCreatorFunc = () => ODataBatchUtils.CreateBatchOperationWriteStream(outputStream, this); + ODataBatchOperationRequestMessage requestMessage = + new ODataBatchOperationRequestMessage(streamCreatorFunc, method, uri, /*headers*/ null, this, contentId, + this.payloadUriConverter, /*writing*/ true, this.container, dependsOnIds, groupId); + + return requestMessage; + } + + /// + /// Wrapper method to create an operation response message that can be used to write the operation content to, utilizing + /// private members and . + /// + /// The output stream underlying the operation message. + /// The contentId of this response message. + /// The group id of the response message, should be the same as the group id + /// in the corresponding request message. + /// An that can be used to write the operation content. + protected ODataBatchOperationResponseMessage BuildOperationResponseMessage(Stream outputStream, + string contentId, string groupId) + { + Func streamCreatorFunc = () => ODataBatchUtils.CreateBatchOperationWriteStream(outputStream, this); + return new ODataBatchOperationResponseMessage(streamCreatorFunc, /*headers*/ null, this, contentId, + this.payloadUriConverter.BatchMessagePayloadUriConverter, /*writing*/ true, this.container, groupId); + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state ExceptionThrown and then re-throw the exception. + /// + /// The action to execute. + private void InterceptException(Action action) + { + try + { + action(); + } + catch + { + if (!IsErrorState(this.state)) + { + this.SetState(BatchWriterState.Error); + } + + throw; + } + } + + /// + /// Sets the 'Error' state and then throws an ODataException with the specified error message. + /// + /// The error message for the exception. + private void ThrowODataException(string errorMessage) + { + this.SetState(BatchWriterState.Error); + throw new ODataException(errorMessage); + } + + /// + /// Internal method to create an for writing + /// an operation of a batch request. + /// + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// + /// For batch in multipart format, the Content-ID value to write in ChangeSet header, would be ignored if + /// is "GET". + /// For batch in Json format, if the value passed in is null, an GUID will be generated and used as the request id. + /// + /// + /// The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath. + /// The prerequisite request ids of this request. + /// The message that can be used to write the request operation. + private ODataBatchOperationRequestMessage CreateOperationRequestMessageInternal(string method, Uri uri, string contentId, + BatchPayloadUriOption payloadUriOption, IEnumerable dependsOnIds) + { + if (!this.isInChangset) + { + this.InterceptException(this.IncreaseBatchSize); + } + else + { + this.InterceptException(this.IncreaseChangeSetSize); + } + + // Add a potential Content-ID header to the URL resolver so that it will be available + // to subsequent operations. + // Note that what we add here is the Content-ID header of the previous operation (if any). + // This also means that the Content-ID of the last operation in a changeset will never get + // added to the cache which is fine since we cannot reference it anywhere. + if (this.currentOperationContentId != null) + { + this.payloadUriConverter.AddContentId(this.currentOperationContentId); + } + + this.InterceptException(() => + uri = ODataBatchUtils.CreateOperationRequestUri(uri, this.outputContext.MessageWriterSettings.BaseUri, this.payloadUriConverter)); + + this.CurrentOperationRequestMessage = this.CreateOperationRequestMessageImplementation( + method, uri, contentId, payloadUriOption, dependsOnIds); + + if (this.isInChangset || this.outputContext.MessageWriterSettings.Version > ODataVersion.V4) + { + // The content Id can be generated if the value passed in is null, therefore here we get the real value of the content Id. + this.RememberContentIdHeader(this.CurrentOperationRequestMessage.ContentId); + } + + return this.CurrentOperationRequestMessage; + } + + /// + /// Perform updates after changeset is started. + /// + private void FinishWriteStartChangeset() + { + // Reset the cache of content IDs here. As per spec, + // For version up to V4 (inclusive): Content-IDs uniqueness scope is whole batch, + // but existing implementation is change set. We don't want to introduce breaking change, and shouldn't + // throw error for existing scenario of having same content Id in different changesets. + // For version above V4: Content-IDs uniqueness scope is whole batch. + if (this.outputContext.MessageWriterSettings.Version <= ODataVersion.V4) + { + this.payloadUriConverter.Reset(); + } + + // reset the size of the current changeset and increase the size of the batch. + this.ResetChangeSetSize(); + this.InterceptException(this.IncreaseBatchSize); + this.isInChangset = true; + } + + /// + /// Perform updates after changeset is ended. + /// + private void FinishWriteEndChangeset() + { + // When change set ends, only reset content Id for V4 (and below); + // We need to carry on the content Id for >V4 to ensure uniqueness (and therefore referable). + if (this.outputContext.MessageWriterSettings.Version <= ODataVersion.V4) + { + this.currentOperationContentId = null; + } + + this.isInChangset = false; + } + + /// + /// Remember a non-null Content-ID header for change set request operations. + /// If a non-null content ID header is specified for a change set request operation, record it in the URL resolver. + /// + /// The Content-ID header value read from the message. + /// + /// Note that the content ID of this operation will only + /// become visible once this operation has been written + /// and OperationCompleted has been called on the URL resolver. + /// + private void RememberContentIdHeader(string contentId) + { + // The Content-ID header is only supported in request messages and inside of changeSets. + Debug.Assert(this.CurrentOperationRequestMessage != null, "this.CurrentOperationRequestMessage != null"); + + // Check for duplicate content IDs; we have to do this here instead of in the cache itself + // since the content ID of the last operation never gets added to the cache but we still + // want to fail on the duplicate. + if (contentId != null && this.payloadUriConverter.ContainsContentId(contentId)) + { + throw new ODataException(Strings.ODataBatchWriter_DuplicateContentIDsNotAllowed(contentId)); + } + + // Set the current content ID. If no Content-ID header is found in the message, + // the 'contentId' argument will be null and this will reset the current operation content ID field. + this.currentOperationContentId = contentId; + } + + + + /// + /// Increases the size of the current batch message; throws if the allowed limit is exceeded. + /// + private void IncreaseBatchSize() + { + this.currentBatchSize++; + + if (this.currentBatchSize > this.outputContext.MessageWriterSettings.MessageQuotas.MaxPartsPerBatch) + { + throw new ODataException(Strings.ODataBatchWriter_MaxBatchSizeExceeded(this.outputContext.MessageWriterSettings.MessageQuotas.MaxPartsPerBatch)); + } + } + + /// + /// Increases the size of the current change set; throws if the allowed limit is exceeded. + /// + private void IncreaseChangeSetSize() + { + this.currentChangeSetSize++; + + if (this.currentChangeSetSize > this.outputContext.MessageWriterSettings.MessageQuotas.MaxOperationsPerChangeset) + { + throw new ODataException(Strings.ODataBatchWriter_MaxChangeSetSizeExceeded(this.outputContext.MessageWriterSettings.MessageQuotas.MaxOperationsPerChangeset)); + } + } + + /// + /// Resets the size of the current change set to 0. + /// + private void ResetChangeSetSize() + { + this.currentChangeSetSize = 0; + } + + /// + /// Verifies that a call is allowed to the writer. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataBatchWriter_SyncCallOnAsyncWriter); + } + } + else + { +#if PORTABLELIB + if (this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataBatchWriter_AsyncCallOnSyncWriter); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Verifies that calling CreateOperationRequestMessage is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The Http method to be used for this request operation. + /// The Uri to be used for this request operation. + /// The Content-ID value to write in ChangeSet head. + private void VerifyCanCreateOperationRequestMessage(bool synchronousCall, string method, Uri uri, + string contentId) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(method, "method"); + + ExceptionUtils.CheckArgumentNotNull(uri, "uri"); + + // For the case within a changeset, verify CreateOperationRequestMessage is valid. + if (this.isInChangset) + { + if (HttpUtils.IsQueryMethod(method)) + { + this.ThrowODataException(Strings.ODataBatch_InvalidHttpMethodForChangeSetRequest(method)); + } + + if (string.IsNullOrEmpty(contentId)) + { + this.ThrowODataException(Strings.ODataBatchOperationHeaderDictionary_KeyNotFound(ODataConstants.ContentIdHeader)); + } + } + + // Common verification. + this.ValidateWriterReady(); + this.VerifyCallAllowed(synchronousCall); + + if (this.outputContext.WritingResponse) + { + this.ThrowODataException(Strings.ODataBatchWriter_CannotCreateRequestOperationWhenWritingResponse); + } + } + + /// + /// Verifies that the writer is in correct state for the Flush operation. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanFlush(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + if (this.state == BatchWriterState.OperationStreamRequested) + { + this.ThrowODataException(Strings.ODataBatchWriter_FlushOrFlushAsyncCalledInStreamRequestedState); + } + } + + /// + /// Validates that the batch writer is ready to process a new write request. + /// + private void ValidateWriterReady() + { + VerifyNotDisposed(); + + // If the operation stream was requested but not yet disposed, the writer can't be used to do anything. + if (this.state == BatchWriterState.OperationStreamRequested) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromOperationContentStreamRequested); + } + } + + /// + /// Determines whether a given writer state is considered an error state. + /// + /// The writer state to check. + /// True if the writer state is an error state; otherwise false. + private static bool IsErrorState(BatchWriterState state) + { + return state == BatchWriterState.Error; + } + + /// + /// Verifies that calling WriteStartBatch is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteStartBatch(bool synchronousCall) + { + this.ValidateWriterReady(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling WriteEndBatch is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEndBatch(bool synchronousCall) + { + this.ValidateWriterReady(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling WriteStartChangeset is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteStartChangeset(bool synchronousCall) + { + this.ValidateWriterReady(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling WriteEndChangeset is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEndChangeset(bool synchronousCall) + { + this.ValidateWriterReady(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling CreateOperationResponseMessage is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateOperationResponseMessage(bool synchronousCall) + { + this.ValidateWriterReady(); + this.VerifyCallAllowed(synchronousCall); + Debug.Assert(this.currentOperationContentId == null, "The Content-ID header is only supported in request messages."); + + if (!this.outputContext.WritingResponse) + { + this.ThrowODataException(Strings.ODataBatchWriter_CannotCreateResponseOperationWhenWritingRequest); + } + } + + /// + /// Verifies that the transition from the current state into new state is valid . + /// + /// The new writer state to transition into. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Validating the transition in the state machine should stay in a single method.")] + private void ValidateTransition(BatchWriterState newState) + { + if (!IsErrorState(this.state) && IsErrorState(newState)) + { + // we can always transition into an error state if we are not already in an error state + return; + } + + switch (newState) + { + case BatchWriterState.ChangesetStarted: + // make sure that we are not starting a changeset when one is already active + if (this.isInChangset) + { + throw new ODataException(Strings.ODataBatchWriter_CannotStartChangeSetWithActiveChangeSet); + } + + break; + case BatchWriterState.ChangesetCompleted: + // make sure that we are not completing a changeset without an active changeset + if (!this.isInChangset) + { + throw new ODataException(Strings.ODataBatchWriter_CannotCompleteChangeSetWithoutActiveChangeSet); + } + + break; + case BatchWriterState.BatchCompleted: + // make sure that we are not completing a batch while a changeset is still active + if (this.isInChangset) + { + throw new ODataException(Strings.ODataBatchWriter_CannotCompleteBatchWithActiveChangeSet); + } + + break; + } + + switch (this.state) + { + case BatchWriterState.Start: + if (newState != BatchWriterState.BatchStarted) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromStart); + } + + break; + case BatchWriterState.BatchStarted: + if (newState != BatchWriterState.ChangesetStarted && newState != BatchWriterState.OperationCreated && newState != BatchWriterState.BatchCompleted) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromBatchStarted); + } + + break; + case BatchWriterState.ChangesetStarted: + if (newState != BatchWriterState.OperationCreated && newState != BatchWriterState.ChangesetCompleted) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromChangeSetStarted); + } + + break; + case BatchWriterState.OperationCreated: + if (newState != BatchWriterState.OperationCreated && + newState != BatchWriterState.OperationStreamRequested && + newState != BatchWriterState.ChangesetStarted && + newState != BatchWriterState.ChangesetCompleted && + newState != BatchWriterState.BatchCompleted) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromOperationCreated); + } + + Debug.Assert(newState != BatchWriterState.OperationStreamDisposed, "newState != BatchWriterState.OperationStreamDisposed"); + + break; + case BatchWriterState.OperationStreamRequested: + if (newState != BatchWriterState.OperationStreamDisposed) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromOperationContentStreamRequested); + } + + break; + case BatchWriterState.OperationStreamDisposed: + if (newState != BatchWriterState.OperationCreated && + newState != BatchWriterState.ChangesetStarted && + newState != BatchWriterState.ChangesetCompleted && + newState != BatchWriterState.BatchCompleted) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromOperationContentStreamDisposed); + } + + break; + case BatchWriterState.ChangesetCompleted: + if (newState != BatchWriterState.BatchCompleted && + newState != BatchWriterState.ChangesetStarted && + newState != BatchWriterState.OperationCreated) + { + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromChangeSetCompleted); + } + + break; + case BatchWriterState.BatchCompleted: + // no more state transitions should happen once in completed state + throw new ODataException(Strings.ODataBatchWriter_InvalidTransitionFromBatchCompleted); + + case BatchWriterState.Error: + if (newState != BatchWriterState.Error) + { + // No more state transitions once we are in error state except for the fatal error + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromError(this.state.ToString(), newState.ToString())); + } + + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataBatchWriter_ValidateTransition_UnreachableCodePath)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamReader.cs new file mode 100644 index 0000000..d3b7a58 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamReader.cs @@ -0,0 +1,175 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.IO; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + #endregion Namespaces + + /// + /// A stream for reading base64 (possibly URL encoded) binary values. + /// + internal sealed class ODataBinaryStreamReader : Stream + { + /// + /// A function to read a specified number of characters into + /// a character array. Returns the actual number of characters read. + /// + private readonly Func reader; + + /// Size of character buffer. + /// + /// In Base64 encoding, four characters represent three bytes, so character array size + /// must be divisible by four to prevent truncating bytes + /// + private readonly int charLength = 1024; + + /// Character buffer for reading base64 URL encoded string. + private char[] chars; + + /// Current offset into buffer. + private int bytesOffset = 0; + + /// Buffer for reading the stream content. + private byte[] bytes = new byte[0]; + + /// + /// Constructor. + /// + /// A function from which to read character values. + internal ODataBinaryStreamReader(Func reader) + { + Debug.Assert(reader != null, "reader cannot be null"); + + this.reader = reader; + chars = new char[charLength]; + } + + /// + /// Determines if the stream can read - this one can + /// + public override bool CanRead + { + get { return true; } + } + + /// + /// Determines if the stream can seek - this one can't + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Determines if the stream can write - this one can't + /// + public override bool CanWrite + { + get { return false; } + } + + /// + /// Returns the length of the stream. Not supported by this stream. + /// + public override long Length + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the position in the stream. Not supported by this stream. + /// + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override int Read(byte[] buffer, int offset, int count) + { + int bytesCopied = 0; + int bytesRemaining = bytes.Length - bytesOffset; + + while (bytesCopied < count) + { + if (bytesRemaining == 0) + { + int charsRead = reader(chars, offset, charLength); + if (charsRead < 1) + { + break; + } + + // chars = chars.Select(c => c == '_' ? '/' : c == '-' ? '+' : c).ToArray(); + bytes = Convert.FromBase64CharArray(chars, 0, charsRead); + + bytesRemaining = bytes.Length; + bytesOffset = 0; + + // If the remaining characters were padding characters then no bytes will be returned + if (bytesRemaining < 1) + { + break; + } + } + + buffer[bytesCopied] = bytes[bytesOffset]; + bytesCopied++; + bytesOffset++; + bytesRemaining--; + } + + return bytesCopied; + } + + /// + /// Flush the stream; not supported for a read stream. + /// + public override void Flush() + { + throw new NotSupportedException(); + } + + /// + /// Sets the length of the stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// Seeks the stream. This operation is not supported by this stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// Writes to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamWriter.cs new file mode 100644 index 0000000..1599977 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataBinaryStreamWriter.cs @@ -0,0 +1,227 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Threading; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Buffers; + using Microsoft.OData.Json; + + /// + /// A stream for writing base64 encoded binary values. + /// + internal sealed class ODataBinaryStreamWriter : Stream + { + /// The writer to write to the underlying stream. + private readonly TextWriter Writer; + + /// Trailing bytes from a previous write to be prepended to the next write. + private Byte[] trailingBytes = new Byte[0]; + + /// + /// The buffer to help with streaming responses. + /// + private char[] streamingBuffer; + + /// + /// Get/sets the character buffer pool for streaming. + /// + private ICharArrayPool bufferPool; + + /// An empty byte[]. + private static byte[] emptyByteArray = new byte[0]; + + /// + /// Constructor. + /// + /// A Textwriter for writing to the stream. + public ODataBinaryStreamWriter(TextWriter writer) + { + Debug.Assert(writer != null, "Creating a binary stream writer for a null textWriter."); + + this.Writer = writer; + } + + /// + /// Constructor. + /// + /// A Textwriter for writing to the stream. + /// A temporary buffer to use when converting binary values. + /// Array pool for renting a buffer. + public ODataBinaryStreamWriter(TextWriter writer, ref char[] streamingBuffer, ICharArrayPool bufferPool) + { + this.Writer = writer; + this.streamingBuffer = streamingBuffer; + this.bufferPool = bufferPool; + } + + /// + /// Determines if the stream can read - this one can't + /// + public override bool CanRead + { + get + { + return false; + } + } + + /// + /// Determines if the stream can seek - this one can't + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Determines if the stream can write - this one can + /// + public override bool CanWrite + { + get + { + return true; + } + } + + /// + /// Returns the length of the stream. + /// + public override long Length + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Gets or sets the position in the stream. Setting of the position is not supported since the stream doesn't support seeking. + /// + public override long Position + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + /// + /// Writes to the stream. + /// + /// The bytes to write to the stream. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] bytes, int offset, int count) + { + byte[] prefixByteString = emptyByteArray; + int trailingByteLength = trailingBytes.Length; + int numberOfBytesToPrefix = trailingByteLength > 0 ? 3 - trailingByteLength : 0; + + // if we have less than 3 bytes, store the bytes and continue + if (count + trailingByteLength < 3) + { + trailingBytes = trailingBytes.Concat(bytes.Skip(offset).Take(count)).ToArray(); + return; + } + + // if we have bytes left over from the previous write, prepend them + if (trailingByteLength > 0) + { + // convert the trailing bytes plus the first 3-trailingByteLength bytes of the new byte[] + prefixByteString = trailingBytes.Concat(bytes.Skip(offset).Take(numberOfBytesToPrefix)).ToArray(); + } + + // compute if there will be trailing bytes from this write + int remainingBytes = (count - numberOfBytesToPrefix) % 3; + trailingBytes = bytes.Skip(offset + count - remainingBytes).Take(remainingBytes).ToArray(); + + JsonValueUtils.WriteBinaryString(this.Writer, prefixByteString.Concat(bytes.Skip(offset + numberOfBytesToPrefix).Take(count - numberOfBytesToPrefix - remainingBytes)).ToArray(), ref streamingBuffer, this.bufferPool); + } + + #if PORTABLELIB + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return TaskUtils.GetTaskForSynchronousOperation(() => + this.Write(buffer, offset, count)); + } +#endif + + /// + /// Reads data from the stream. This operation is not supported by this stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + /// + /// Seeks the stream. This operation is not supported by this stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + /// + /// Sets the length of the stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + /// + /// Flush the stream to the underlying batch stream. + /// + public override void Flush() + { + Writer.Flush(); + } + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called form the finalizer. + protected override void Dispose(bool disposing) + { + // write any trailing bytes to stream + if (disposing && this.trailingBytes != null && this.trailingBytes.Length > 0) + { + this.Writer.Write(Convert.ToBase64String(trailingBytes, 0, trailingBytes.Length)); + trailingBytes = null; + } + + this.Writer.Flush(); + base.Dispose(disposing); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReader.cs new file mode 100644 index 0000000..cf05fa2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReader.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Base class for OData collection readers. + /// + public abstract class ODataCollectionReader + { + /// Gets the current state of the reader. + /// The current state of the reader. + public abstract ODataCollectionReaderState State + { + get; + } + + /// Gets the most recent item that has been read. + /// The most recent item that has been read. + /// + /// This property returns an when in state ODataCollectionReaderState.CollectionStart + /// or ODataCollectionReaderState.CollectionEnd. It returns either a primitive value or 'null' when + /// in state ODataCollectionReaderState.Value and 'null' in all other states. + /// + public abstract object Item + { + get; + } + + /// Reads the next item from the message payload. + /// True if more items were read; otherwise false. + public abstract bool Read(); + +#if PORTABLELIB + /// Asynchronously reads the next item from the message payload. + /// A task that when completed indicates whether more items were read. + public abstract Task ReadAsync(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCore.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCore.cs new file mode 100644 index 0000000..88f811e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCore.cs @@ -0,0 +1,475 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Base class for OData collection readers that verifies a proper sequence of read calls on the reader. + /// + internal abstract class ODataCollectionReaderCore : ODataCollectionReader + { + /// The input context to read from. + private readonly ODataInputContext inputContext; + + /// Stack of reader scopes to keep track of the current context of the reader. + private readonly Stack scopes = new Stack(); + + /// If not null, the reader will notify the implementer of the interface of relevant state changes in the reader. + private readonly IODataReaderWriterListener listener; + + /// The collection validator instance if no expected item type has been specified; otherwise null. + private CollectionWithoutExpectedTypeValidator collectionValidator; + + /// The expected item type reference for the items in the collection. + /// If an expected type is specified the collection has to be homogeneous. + private IEdmTypeReference expectedItemTypeReference; + + /// + /// Constructor. + /// + /// The input to read from. + /// The expected type reference for the items in the collection. + /// If not null, the reader will notify the implementer of the interface of relevant state changes in the reader. + protected ODataCollectionReaderCore( + ODataInputContext inputContext, + IEdmTypeReference expectedItemTypeReference, + IODataReaderWriterListener listener) + { + this.inputContext = inputContext; + this.expectedItemTypeReference = expectedItemTypeReference; + + if (this.expectedItemTypeReference == null) + { + // NOTE: collections cannot specify a type name for the collection itself, so always passing null. + this.collectionValidator = new CollectionWithoutExpectedTypeValidator(/*expectedItemTypeName*/ null); + } + + this.listener = listener; + this.EnterScope(ODataCollectionReaderState.Start, null); + } + + /// + /// The current state of the reader. + /// + public override sealed ODataCollectionReaderState State + { + get + { + this.inputContext.VerifyNotDisposed(); + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek().State; + } + } + + /// + /// The most recent item that has been read. + /// + public override sealed object Item + { + get + { + this.inputContext.VerifyNotDisposed(); + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek().Item; + } + } + + /// + /// The expected item type for the items in the collection. + /// + protected IEdmTypeReference ExpectedItemTypeReference + { + get + { + return this.expectedItemTypeReference; + } + + set + { + ExceptionUtils.CheckArgumentNotNull(value, "value"); + + Debug.Assert(this.State == ODataCollectionReaderState.Start, "this.State == ODataCollectionReaderState.Start"); + + if (this.expectedItemTypeReference != value) + { + this.expectedItemTypeReference = value; + + // If we set an expected item type reference (e.g., from the + // Json Light context URI), we need to reset the collection validator. + this.collectionValidator = null; + } + } + } + + /// + /// The collection validator instance if no expected item type has been specified; otherwise null. + /// + protected CollectionWithoutExpectedTypeValidator CollectionValidator + { + get + { + return this.collectionValidator; + } + } + + /// + /// Returns true if we are reading a nested payload, e.g. a resource, a resource set or a collection within a parameters payload. + /// + protected bool IsReadingNestedPayload + { + get + { + return this.listener != null; + } + } + + /// + /// Reads the next item from the message payload. + /// + /// true if more items were read; otherwise false. + public override sealed bool Read() + { + this.VerifyCanRead(true); + return this.InterceptException(this.ReadSynchronously); + } + +#if PORTABLELIB + /// + /// Asynchronously reads the next item from the message payload. + /// + /// A task that when completed indicates whether more items were read. + public override sealed Task ReadAsync() + { + this.VerifyCanRead(false); + return this.ReadAsynchronously().FollowOnFaultWith(t => this.EnterScope(ODataCollectionReaderState.Exception, null)); + } +#endif + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + protected bool ReadImplementation() + { + bool result; + switch (this.State) + { + case ODataCollectionReaderState.Start: + result = this.ReadAtStartImplementation(); + break; + + case ODataCollectionReaderState.CollectionStart: + result = this.ReadAtCollectionStartImplementation(); + break; + + case ODataCollectionReaderState.Value: + result = this.ReadAtValueImplementation(); + break; + + case ODataCollectionReaderState.CollectionEnd: + result = this.ReadAtCollectionEndImplementation(); + break; + + default: + Debug.Assert(false, "Unsupported collection reader state " + this.State + " detected."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataCollectionReaderCore_ReadImplementation)); + } + + return result; + } + + /// + /// Implementation of the collection reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'CollectionStart'. + /// + /// true if more nodes can be read from the reader; otherwise false. + protected abstract bool ReadAtCollectionStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'Value'. + /// + /// true if more nodes can be read from the reader; otherwise false. + protected abstract bool ReadAtValueImplementation(); + + /// + /// Implementation of the reader logic when in state 'CollectionEnd'. + /// + /// Should be false since no more nodes can be read from the reader after the collection ends. + protected abstract bool ReadAtCollectionEndImplementation(); + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + protected bool ReadSynchronously() + { + return this.ReadImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously reads the next from the message payload. + /// + /// A task that when completed indicates whether more items were read. + protected virtual Task ReadAsynchronously() + { + // We are reading from the fully buffered read stream here; thus it is ok + // to use synchronous reads and then return a completed task + // NOTE: once we switch to fully async reading this will have to change + return TaskUtils.GetTaskForSynchronousOperation(this.ReadImplementation); + } +#endif + + /// + /// Creates a new for the specified and + /// with the provided and pushes it on the stack of scopes. + /// + /// The to use for the new scope. + /// The item to attach with the state in the new scope. + protected void EnterScope(ODataCollectionReaderState state, object item) + { + this.EnterScope(state, item, false); + } + + /// + /// Creates a new for the specified and + /// with the provided and pushes it on the stack of scopes. + /// + /// The to use for the new scope. + /// The item to attach with the state in the new scope. + /// The state of the collection element - empty or not-empty. + protected void EnterScope(ODataCollectionReaderState state, object item, bool isCollectionElementEmpty) + { + if (state == ODataCollectionReaderState.Value) + { + ValidationUtils.ValidateCollectionItem(item, true /* isNullable */); + } + + this.scopes.Push(new Scope(state, item, isCollectionElementEmpty)); + if (this.listener != null) + { + if (state == ODataCollectionReaderState.Exception) + { + this.listener.OnException(); + } + else if (state == ODataCollectionReaderState.Completed) + { + this.listener.OnCompleted(); + } + } + } + + /// + /// Replaces the current scope with a new with the specified and + /// the item of the current scope. + /// + /// The to use for the new scope. + /// The item associated with the replacement state. + protected void ReplaceScope(ODataCollectionReaderState state, object item) + { + Debug.Assert(this.scopes.Count > 0, "Stack must always be non-empty."); + + if (state == ODataCollectionReaderState.Value) + { + ValidationUtils.ValidateCollectionItem(item, true /* isNullable */); + } + + this.scopes.Pop(); + this.EnterScope(state, item); + } + + /// + /// Removes the current scope from the stack of all scopes. + /// + /// The expected state of the current scope (to be popped). + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "state", Justification = "Used in debug builds in assertions.")] + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "scope", Justification = "Used in debug builds in assertions.")] + protected void PopScope(ODataCollectionReaderState state) + { + Debug.Assert(this.scopes.Count > 1, "Stack must have more than 1 items in order to pop an item."); + + Scope scope = this.scopes.Pop(); + Debug.Assert(scope.State == state, "scope.State == state"); + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the reader into + /// state ExceptionThrown and then rethrow the exception. + /// + /// The type returned from the to execute. + /// The action to execute. + /// The result of executing the . + private T InterceptException(Func action) + { + try + { + return action(); + } + catch (Exception e) + { + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + this.EnterScope(ODataCollectionReaderState.Exception, null); + } + + throw; + } + } + + /// + /// Verifies that calling Read is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanRead(bool synchronousCall) + { + this.inputContext.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + if (this.State == ODataCollectionReaderState.Exception || this.State == ODataCollectionReaderState.Completed) + { + throw new ODataException(Strings.ODataCollectionReaderCore_ReadOrReadAsyncCalledInInvalidState(this.State)); + } + } + + /// + /// Verifies that a call is allowed to the reader. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + this.VerifySynchronousCallAllowed(); + } + else + { +#if PORTABLELIB + this.VerifyAsynchronousCallAllowed(); +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Verifies that a synchronous operation is allowed on this reader. + /// + private void VerifySynchronousCallAllowed() + { + if (!this.inputContext.Synchronous) + { + throw new ODataException(Strings.ODataCollectionReaderCore_SyncCallOnAsyncReader); + } + } + +#if PORTABLELIB + /// + /// Verifies that an asynchronous operation is allowed on this reader. + /// + private void VerifyAsynchronousCallAllowed() + { + if (this.inputContext.Synchronous) + { + throw new ODataException(Strings.ODataCollectionReaderCore_AsyncCallOnSyncReader); + } + } +#endif + + /// + /// A collection reader scope; keeping track of the current reader state and an item associated with this state. + /// + protected sealed class Scope + { + /// The reader state of this scope. + private readonly ODataCollectionReaderState state; + + /// The item attached to this scope. + private readonly object item; + + /// True, if the collection element attached to this scope is empty. False otherwise. + [SuppressMessage("Microsoft.Performance", "CA1823", Justification = "isCollectionElementEmpty is used in debug.")] + private readonly bool isCollectionElementEmpty; + + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of this scope. + /// The item attached to this scope. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")] + public Scope(ODataCollectionReaderState state, object item) : this(state, item, false) + { + } + + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of this scope. + /// The item attached to this scope. + /// The state of the collection element - empty or not-empty + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")] + public Scope(ODataCollectionReaderState state, object item, bool isCollectionElementEmpty) + { + Debug.Assert( + state == ODataCollectionReaderState.Start && item == null || + state == ODataCollectionReaderState.CollectionStart && item is ODataCollectionStart || + state == ODataCollectionReaderState.Value && (item == null || EdmLibraryExtensions.IsPrimitiveType(item.GetType()) || item is ODataEnumValue) || + state == ODataCollectionReaderState.CollectionEnd && item is ODataCollectionStart || + state == ODataCollectionReaderState.Exception && item == null || + state == ODataCollectionReaderState.Completed && item == null, + "Reader state and associated item do not match."); + + this.state = state; + this.item = item; + this.isCollectionElementEmpty = isCollectionElementEmpty; + + + // When isCollectionElementEmpty is true, Reader needs to be in CollectionStart state. + Debug.Assert(!this.isCollectionElementEmpty || + (this.isCollectionElementEmpty && state == ODataCollectionReaderState.CollectionStart), + "Expected state to be CollectionStart if isCollectionElementyEmpty is true."); + } + + /// + /// The reader state of this scope. + /// + public ODataCollectionReaderState State + { + get + { + return this.state; + } + } + + /// + /// The item attached to this scope. + /// + public object Item + { + get + { + return this.item; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCoreAsync.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCoreAsync.cs new file mode 100644 index 0000000..be01bd3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderCoreAsync.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Base class for OData collection readers that verifies a proper sequence of read calls on the reader and which support true async operations. + /// + internal abstract class ODataCollectionReaderCoreAsync : ODataCollectionReaderCore + { + /// + /// Constructor. + /// + /// The input to read from. + /// The expected type reference for the items in the collection. + /// If not null, the reader will notify the implementer of the interface of relevant state changes in the reader. + protected ODataCollectionReaderCoreAsync( + ODataInputContext inputContext, + IEdmTypeReference expectedItemTypeReference, + IODataReaderWriterListener listener) + : base(inputContext, expectedItemTypeReference, listener) + { + } + +#if PORTABLELIB + /// + /// Implementation of the collection reader logic when in state 'Start'. + /// + /// Task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtStartImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'CollectionStart'. + /// + /// Task which returns true if more nodes can be read from the reader; otherwise false. + protected abstract Task ReadAtCollectionStartImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'Value'. + /// + /// Task which returns true if more nodes can be read from the reader; otherwise false. + protected abstract Task ReadAtValueImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'CollectionEnd'. + /// + /// Task which should return false since no more nodes can be read from the reader after the collection ends. + protected abstract Task ReadAtCollectionEndImplementationAsync(); + + /// + /// Asynchronously reads the next from the message payload. + /// + /// A task that when completed indicates whether more items were read. + /// The base class already implements this but only for fully synchronous readers, the implementation here + /// allows fully asynchronous readers. + protected override Task ReadAsynchronously() + { + switch (this.State) + { + case ODataCollectionReaderState.Start: + return this.ReadAtStartImplementationAsync(); + + case ODataCollectionReaderState.CollectionStart: + return this.ReadAtCollectionStartImplementationAsync(); + + case ODataCollectionReaderState.Value: + return this.ReadAtValueImplementationAsync(); + + case ODataCollectionReaderState.CollectionEnd: + return this.ReadAtCollectionEndImplementationAsync(); + + default: + Debug.Assert(false, "Unsupported collection reader state " + this.State + " detected."); + return TaskUtils.GetFaultedTask(new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataCollectionReaderCoreAsync_ReadAsynchronously))); + } + } +#endif + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderState.cs new file mode 100644 index 0000000..4ffe798 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionReaderState.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Enumeration of all possible states of an . + /// + public enum ODataCollectionReaderState + { + /// The reader is at the start; nothing has been read yet. + /// In this state, the Item property of the returns null. + Start, + + /// + /// The reader has started reading and is reading the start element of the collection wrapper (if any). + /// No items have been read. + /// + /// + /// In this state, the Item property of the returns + /// an instance of . + /// + CollectionStart, + + /// + /// The reader read an item from the collection. + /// + /// In this state, the Item property of the returns the read item (a primitive value or null). + Value, + + /// + /// The reader has finished reading and is reading the end element of the collection wrapper (if any). + /// All items have been read. + /// + /// + /// In this state, the Item property of the returns the same + /// instance of as in state CollectionStart. + /// + CollectionEnd, + + /// The reader has thrown an exception; nothing can be read from the reader anymore. + /// + /// In this state, the Item property of the returns null. + /// + Exception, + + /// The reader has completed; nothing can be read anymore. + /// + /// In this state, the Item property of the returns null. + /// + Completed, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStart.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStart.cs new file mode 100644 index 0000000..700de5e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStart.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + /// + /// OData representation of a top-level collection. + /// + public sealed class ODataCollectionStart : ODataAnnotatable + { + /// + /// Provides additional serialization information to the for this . + /// + private ODataCollectionStartSerializationInfo serializationInfo; + + /// Gets or sets the name of the collection (ATOM only). + /// The name of the collection. + public string Name + { + get; + set; + } + + /// Gets the number of items in the collection. + /// The number of items in the collection. + public long? Count + { + get; + set; + } + + /// Gets the URI representing the next page link. + /// The URI representing the next page link. + public Uri NextPageLink + { + get; + set; + } + + /// + /// Provides additional serialization information to the for this . + /// + internal ODataCollectionStartSerializationInfo SerializationInfo + { + get + { + return this.serializationInfo; + } + + set + { + this.serializationInfo = ODataCollectionStartSerializationInfo.Validate(value); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStartSerializationInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStartSerializationInfo.cs new file mode 100644 index 0000000..a0a0b19 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionStartSerializationInfo.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class to provide additional serialization information to the for an . + /// + public sealed class ODataCollectionStartSerializationInfo + { + /// + /// The fully qualified type name of the collection to be written. + /// + private string collectionTypeName; + + /// + /// The fully qualified type name of the collection to be written. + /// + public string CollectionTypeName + { + get + { + return this.collectionTypeName; + } + + set + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(value, "CollectionTypeName"); + ValidationUtils.ValidateCollectionTypeName(value); + this.collectionTypeName = value; + } + } + + /// + /// Validates the instance. + /// + /// The serialization info instance to validate. + /// The instance. + internal static ODataCollectionStartSerializationInfo Validate(ODataCollectionStartSerializationInfo serializationInfo) + { + if (serializationInfo != null) + { + ExceptionUtils.CheckArgumentNotNull(serializationInfo.CollectionTypeName, "serializationInfo.CollectionTypeName"); + } + + return serializationInfo; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriter.cs new file mode 100644 index 0000000..b6fab87 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriter.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Base class for OData collection writers. + /// + public abstract class ODataCollectionWriter + { + /// Start writing a collection. + /// The representing the collection. + public abstract void WriteStart(ODataCollectionStart collectionStart); + +#if PORTABLELIB + /// Asynchronously start writing a collection. + /// A task instance that represents the asynchronous write operation. + /// The representing the collection. + public abstract Task WriteStartAsync(ODataCollectionStart collectionStart); +#endif + + /// Write a collection item. + /// The collection item to write. + public abstract void WriteItem(object item); + +#if PORTABLELIB + /// Asynchronously write a collection item. + /// A task instance that represents the asynchronous write operation. + /// The collection item to write. + public abstract Task WriteItemAsync(object item); +#endif + + /// Finishes writing a collection. + public abstract void WriteEnd(); + +#if PORTABLELIB + /// Asynchronously finish writing a collection. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteEndAsync(); +#endif + + /// Flushes the write buffer to the underlying stream. + public abstract void Flush(); + +#if PORTABLELIB + /// Flushes the write buffer to the underlying stream asynchronously. + /// A task instance that represents the asynchronous operation. + public abstract Task FlushAsync(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriterCore.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriterCore.cs new file mode 100644 index 0000000..f213540 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataCollectionWriterCore.cs @@ -0,0 +1,668 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Base class for OData collection writers that verifies a proper sequence of write calls on the writer. + /// + internal abstract class ODataCollectionWriterCore : ODataCollectionWriter, IODataOutputInStreamErrorListener + { + /// The output context to write to. + private readonly ODataOutputContext outputContext; + + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + private readonly IODataReaderWriterListener listener; + + /// Stack of writer scopes to keep track of the current context of the writer. + private readonly Stack scopes = new Stack(); + + /// The expected type of the items in the collection or null if no expected item type exists. + private readonly IEdmTypeReference expectedItemType; + + /// Checker to detect duplicate property names on complex collection items. + private IDuplicatePropertyNameChecker duplicatePropertyNameChecker; + + /// The collection validator instance if no expected item type has been specified; otherwise null. + private CollectionWithoutExpectedTypeValidator collectionValidator; + + /// + /// Constructor. + /// + /// The output context to write to. + /// The item type of the collection being written or null if no metadata is available. + protected ODataCollectionWriterCore(ODataOutputContext outputContext, IEdmTypeReference itemTypeReference) + : this(outputContext, itemTypeReference, null) + { + } + + /// + /// Constructor. + /// + /// The output context to write to. + /// The type reference of the expected item type or null if no expected item type exists. + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + protected ODataCollectionWriterCore(ODataOutputContext outputContext, IEdmTypeReference expectedItemType, IODataReaderWriterListener listener) + { + Debug.Assert(outputContext != null, "outputContext != null"); + + this.outputContext = outputContext; + this.expectedItemType = expectedItemType; + this.listener = listener; + this.scopes.Push(new Scope(CollectionWriterState.Start, null)); + } + + /// + /// An enumeration representing the current state of the writer. + /// + internal enum CollectionWriterState + { + /// The writer is at the start; nothing has been written yet. + Start, + + /// + /// The writer has started writing and is writing the wrapper elements for the + /// collection items (if any). No or all items have been written. + /// + Collection, + + /// The writer is in a state where collection items can be written. + Item, + + /// The writer has completed; nothing can be written anymore. + Completed, + + /// Writer has written an error; nothing can be written anymore. + Error + } + + /// + /// The current state of the writer. + /// + protected CollectionWriterState State + { + get + { + return this.scopes.Peek().State; + } + } + + /// Checker to detect duplicate property names on complex collection items. + protected IDuplicatePropertyNameChecker DuplicatePropertyNameChecker + { + get + { + return duplicatePropertyNameChecker + ?? (duplicatePropertyNameChecker + = outputContext.MessageWriterSettings.Validator.CreateDuplicatePropertyNameChecker()); + } + } + + /// + /// The collection validator instance. + /// + protected CollectionWithoutExpectedTypeValidator CollectionValidator + { + get + { + return this.collectionValidator; + } + } + + /// + /// The item type of the collection being written or null if no metadata is available. + /// + protected IEdmTypeReference ItemTypeReference + { + get + { + return this.expectedItemType; + } + } + + /// + /// Flushes the write buffer to the underlying stream. + /// + public sealed override void Flush() + { + this.VerifyCanFlush(true); + + // make sure we switch to writer state Error if an exception is thrown during flushing. + try + { + this.FlushSynchronously(); + } + catch + { + this.ReplaceScope(CollectionWriterState.Error, null); + throw; + } + } + +#if PORTABLELIB + /// + /// Asynchronously flushes the write buffer to the underlying stream. + /// + /// A task instance that represents the asynchronous operation. + public sealed override Task FlushAsync() + { + this.VerifyCanFlush(false); + + // make sure we switch to writer state Error if an exception is thrown during flushing. + return this.FlushAsynchronously().FollowOnFaultWith(t => this.ReplaceScope(CollectionWriterState.Error, null)); + } +#endif + + /// + /// Start writing a collection. + /// + /// The representing the collection. + public sealed override void WriteStart(ODataCollectionStart collectionStart) + { + this.VerifyCanWriteStart(true, collectionStart); + this.WriteStartImplementation(collectionStart); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a collection. + /// + /// The representing the collection. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteStartAsync(ODataCollectionStart collection) + { + this.VerifyCanWriteStart(false, collection); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartImplementation(collection)); + } +#endif + + /// + /// Write a collection item. + /// + /// The collection item to write. + public sealed override void WriteItem(object item) + { + this.VerifyCanWriteItem(true); + this.WriteItemImplementation(item); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a collection item. + /// + /// The collection item to write. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteItemAsync(object item) + { + this.VerifyCanWriteItem(false); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteItemImplementation(item)); + } +#endif + + /// + /// Finish writing a collection. + /// + public sealed override void WriteEnd() + { + this.VerifyCanWriteEnd(true); + this.WriteEndImplementation(); + + if (this.scopes.Peek().State == CollectionWriterState.Completed) + { + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + this.Flush(); + } + } + +#if PORTABLELIB + /// + /// Asynchronously finish writing a collection. + /// + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteEndAsync() + { + this.VerifyCanWriteEnd(false); + return TaskUtils.GetTaskForSynchronousOperation(this.WriteEndImplementation) + .FollowOnSuccessWithTask( + task => + { + if (this.scopes.Peek().State == CollectionWriterState.Completed) + { + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + return this.FlushAsync(); + } + else + { + return TaskUtils.CompletedTask; + } + }); + } +#endif + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + void IODataOutputInStreamErrorListener.OnInStreamError() + { + this.VerifyNotDisposed(); + + // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might + // introduce another top-level element in XML) + if (this.State == CollectionWriterState.Completed) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), CollectionWriterState.Error.ToString())); + } + + this.StartPayloadInStartState(); + this.EnterScope(CollectionWriterState.Error, this.scopes.Peek().Item); + } + + /// + /// Determines whether a given writer state is considered an error state. + /// + /// The writer state to check. + /// True if the writer state is an error state; otherwise false. + protected static bool IsErrorState(CollectionWriterState state) + { + return state == CollectionWriterState.Error; + } + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + protected abstract void VerifyNotDisposed(); + + /// + /// Flush the output. + /// + protected abstract void FlushSynchronously(); + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected abstract Task FlushAsynchronously(); +#endif + + /// + /// Start writing an OData payload. + /// + protected abstract void StartPayload(); + + /// + /// Finish writing an OData payload. + /// + protected abstract void EndPayload(); + + /// + /// Start writing a collection. + /// + /// The representing the collection. + protected abstract void StartCollection(ODataCollectionStart collectionStart); + + /// + /// Finish writing a collection. + /// + protected abstract void EndCollection(); + + /// + /// Writes a collection item (either primitive or complex) + /// + /// The collection item to write. + /// The expected type of the collection item or null if no expected item type exists. + protected abstract void WriteCollectionItem(object item, IEdmTypeReference expectedItemTypeReference); + + /// + /// Verifies that calling WriteStart is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The representing the collection. + private void VerifyCanWriteStart(bool synchronousCall, ODataCollectionStart collectionStart) + { + ExceptionUtils.CheckArgumentNotNull(collectionStart, "collection"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Start writing a collection - implementation of the actual functionality. + /// + /// The representing the collection. + private void WriteStartImplementation(ODataCollectionStart collectionStart) + { + this.StartPayloadInStartState(); + this.EnterScope(CollectionWriterState.Collection, collectionStart); + this.InterceptException(() => + { + if (this.expectedItemType == null) + { + this.collectionValidator = new CollectionWithoutExpectedTypeValidator(/*expectedItemTypeName*/ null); + } + + this.StartCollection(collectionStart); + }); + } + + /// + /// Verify that calling WriteItem is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteItem(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Write a collection item - implementation of the actual functionality. + /// + /// The collection item to write. + private void WriteItemImplementation(object item) + { + if (this.scopes.Peek().State != CollectionWriterState.Item) + { + this.EnterScope(CollectionWriterState.Item, item); + } + + this.InterceptException(() => + { + ValidationUtils.ValidateCollectionItem(item, true /* isNullable */); + this.WriteCollectionItem(item, this.expectedItemType); + }); + } + + /// + /// Verifies that calling WriteEnd is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEnd(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Finish writing a collection - implementation of the actual functionality. + /// + private void WriteEndImplementation() + { + this.InterceptException(() => + { + Scope currentScope = this.scopes.Peek(); + + switch (currentScope.State) + { + case CollectionWriterState.Collection: + this.EndCollection(); + break; + case CollectionWriterState.Item: + this.LeaveScope(); + Debug.Assert(this.scopes.Peek().State == CollectionWriterState.Collection, "Expected to find collection state after popping from item state."); + this.EndCollection(); + break; + case CollectionWriterState.Start: // fall through + case CollectionWriterState.Completed: // fall through + case CollectionWriterState.Error: // fall through + throw new ODataException(Strings.ODataCollectionWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString())); + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataCollectionWriterCore_WriteEnd_UnreachableCodePath)); + } + + this.LeaveScope(); + }); + } + + /// + /// Verifies that calling Flush is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanFlush(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that a call is allowed to the writer. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataCollectionWriterCore_SyncCallOnAsyncWriter); + } + } + else + { +#if PORTABLELIB + if (this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataCollectionWriterCore_AsyncCallOnSyncWriter); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Checks whether we are currently writing the first top-level element; if so call StartPayload + /// + private void StartPayloadInStartState() + { + Scope current = this.scopes.Peek(); + if (current.State == CollectionWriterState.Start) + { + this.InterceptException(this.StartPayload); + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state ExceptionThrown and then rethrow the exception. + /// + /// The action to execute. + private void InterceptException(Action action) + { + try + { + action(); + } + catch + { + if (!IsErrorState(this.State)) + { + this.EnterScope(CollectionWriterState.Error, this.scopes.Peek().Item); + } + + throw; + } + } + + /// + /// Notifies the implementer of the interface of relevant state changes in the writer. + /// + /// The new writer state. + private void NotifyListener(CollectionWriterState newState) + { + if (this.listener != null) + { + if (IsErrorState(newState)) + { + this.listener.OnException(); + } + else if (newState == CollectionWriterState.Completed) + { + this.listener.OnCompleted(); + } + } + } + + /// + /// Enter a new writer scope; verifies that the transition from the current state into new state is valid + /// and attaches the item to the new scope. + /// + /// The writer state to transition into. + /// The item to associate with the new scope. + private void EnterScope(CollectionWriterState newState, object item) + { + this.InterceptException(() => this.ValidateTransition(newState)); + this.scopes.Push(new Scope(newState, item)); + this.NotifyListener(newState); + } + + /// + /// Leave the current writer scope and return to the previous scope. + /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope. + /// + /// Note that this method is never called once an error has been written or a fatal exception has been thrown. + private void LeaveScope() + { + Debug.Assert(this.State != CollectionWriterState.Error, "this.State != WriterState.Error"); + + this.scopes.Pop(); + + // if we are back at the root replace the 'Start' state with the 'Completed' state + if (this.scopes.Count == 1) + { + this.scopes.Pop(); + this.scopes.Push(new Scope(CollectionWriterState.Completed, null)); + this.InterceptException(this.EndPayload); + this.NotifyListener(CollectionWriterState.Completed); + } + } + + /// + /// Replaces the current scope with a new scope; checks that the transition is valid. + /// + /// The new state to transition into. + /// The item associated with the new state. + private void ReplaceScope(CollectionWriterState newState, ODataItem item) + { + this.ValidateTransition(newState); + this.scopes.Pop(); + this.scopes.Push(new Scope(newState, item)); + this.NotifyListener(newState); + } + + /// + /// Verify that the transition from the current state into new state is valid . + /// + /// The new writer state to transition into. + private void ValidateTransition(CollectionWriterState newState) + { + if (!IsErrorState(this.State) && IsErrorState(newState)) + { + // we can always transition into an error state if we are not already in an error state + return; + } + + switch (this.State) + { + case CollectionWriterState.Start: + if (newState != CollectionWriterState.Collection && newState != CollectionWriterState.Completed) + { + throw new ODataException(Strings.ODataCollectionWriterCore_InvalidTransitionFromStart(this.State.ToString(), newState.ToString())); + } + + break; + case CollectionWriterState.Collection: + if (newState != CollectionWriterState.Item && newState != CollectionWriterState.Completed) + { + throw new ODataException(Strings.ODataCollectionWriterCore_InvalidTransitionFromCollection(this.State.ToString(), newState.ToString())); + } + + break; + case CollectionWriterState.Item: + if (newState != CollectionWriterState.Completed) + { + throw new ODataException(Strings.ODataCollectionWriterCore_InvalidTransitionFromItem(this.State.ToString(), newState.ToString())); + } + + break; + case CollectionWriterState.Completed: + // we should never see a state transition when in state 'Completed' + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), newState.ToString())); + case CollectionWriterState.Error: + if (newState != CollectionWriterState.Error) + { + // No more state transitions once we are in error state + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromError(this.State.ToString(), newState.ToString())); + } + + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataCollectionWriterCore_ValidateTransition_UnreachableCodePath)); + } + } + + /// + /// A writer scope; keeping track of the current writer state and an item associated with this state. + /// + private sealed class Scope + { + /// The writer state of this scope. + private readonly CollectionWriterState state; + + /// The item attached to this scope. + private readonly object item; + + /// + /// Constructor creating a new writer scope. + /// + /// The writer state of this scope. + /// The item attached to this scope. + public Scope(CollectionWriterState state, object item) + { + this.state = state; + this.item = item; + } + + /// + /// The writer state of this scope. + /// + public CollectionWriterState State + { + get + { + return this.state; + } + } + + /// + /// The item attached to this scope. + /// + public object Item + { + get + { + return this.item; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataConstants.cs new file mode 100644 index 0000000..bd314dd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataConstants.cs @@ -0,0 +1,210 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics.CodeAnalysis; + #endregion Namespaces + + /// + /// Constant values used by the OData or HTTP protocol or OData library. + /// +#if ODATA_CORE + public static class ODataConstants +#else + internal static class ODataInternalConstants +#endif + { + /// + /// HTTP method name for GET requests. + /// + public const string MethodGet = "GET"; + + /// + /// HTTP method name for POST requests. + /// + public const string MethodPost = "POST"; + + /// + /// HTTP method name for PUT requests. + /// + public const string MethodPut = "PUT"; + + /// + /// HTTP method name for DELETE requests. + /// + public const string MethodDelete = "DELETE"; + + /// + /// HTTP method name for PATCH requests. + /// + public const string MethodPatch = "PATCH"; + + /// + /// Name of the HTTP content type header. + /// + public const string ContentTypeHeader = "Content-Type"; + + /// + /// Name of the OData 'OData-Version' HTTP header. + /// + public const string ODataVersionHeader = "OData-Version"; + + /// + /// Name of the HTTP content-ID header. + /// + public const string ContentIdHeader = "Content-ID"; + + /// + /// Name of the Content-Length HTTP header. + /// + internal const string ContentLengthHeader = "Content-Length"; + + /// + /// 'q' - HTTP q-value parameter name. + /// + internal const string HttpQValueParameter = "q"; + + /// Http Version in batching requests and response. + internal const string HttpVersionInBatching = "HTTP/1.1"; + + /// Http Version in async responses. + internal const string HttpVersionInAsync = "HTTP/1.1"; + + /// 'charset' - HTTP parameter name. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Charset", Justification = "Member name chosen based on HTTP header name.")] + internal const string Charset = "charset"; + + /// multi-part keyword in content-type to identify batch separator + internal const string HttpMultipartBoundary = "boundary"; + + /// Name of the HTTP content transfer encoding header. + internal const string ContentTransferEncoding = "Content-Transfer-Encoding"; + + /// Content-Transfer-Encoding value for batch payloads. + internal const string BatchContentTransferEncoding = "binary"; + + /// The default protocol version to use in ODataLib if none is specified. + internal const ODataVersion ODataDefaultProtocolVersion = ODataVersion.V4; + + /// The template used when computing a batch request boundary. + internal const string BatchRequestBoundaryTemplate = "batch_{0}"; + + /// The template used when computing a batch response boundary. + internal const string BatchResponseBoundaryTemplate = "batchresponse_{0}"; + + /// The template used when computing a request changeset boundary. + internal const string RequestChangeSetBoundaryTemplate = "changeset_{0}"; + + /// The template used when computing a response changeset boundary. + internal const string ResponseChangeSetBoundaryTemplate = "changesetresponse_{0}"; + + /// Weak etags in HTTP must start with W/. + /// Look in http://www.ietf.org/rfc/rfc2616.txt?number=2616 section 14.19 for more information. + internal const string HttpWeakETagPrefix = "W/\""; + + /// Weak etags in HTTP must end with ". + /// Look in http://www.ietf.org/rfc/rfc2616.txt?number=2616 section 14.19 for more information. + internal const string HttpWeakETagSuffix = "\""; + + /// The default maximum allowed recursion depth for recursive payload definitions, such as complex values inside complex values. + internal const int DefaultMaxRecursionDepth = 100; + + /// The default maximum number of bytes that should be read from a message. + internal const long DefaultMaxReadMessageSize = 1024 * 1024; + + /// The default maximum number of top-level operations and changesets per batch payload. + internal const int DefaultMaxPartsPerBatch = 100; + + /// The default maximum number of operations per changeset. + internal const int DefaultMaxOperationsPerChangeset = 1000; + + /// The '/' (forward slash) which is the URI segment separator. + internal const string UriSegmentSeparator = "/"; + + /// The '/' (forward slash) which is the URI segment separator. + internal const char UriSegmentSeparatorChar = '/'; + + /// The '$ref' segment name for constructing association links. + internal const string EntityReferenceSegmentName = "$ref"; + + /// The 'Collection' segment name for constructing collection of association links. + internal const string CollectionPrefix = "Collection"; + + /// The '$value' segment name for the default stream value. + internal const string DefaultStreamSegmentName = "$value"; + + /// The prefix of type name. + internal const string TypeNamePrefix = "#"; + + /// A segment name in a URI that indicates metadata is being requested. + internal const string UriMetadataSegment = "$metadata"; + + /// The OData prefix + internal const string ODataPrefix = "odata"; + + #region Context URL + + /// + /// Constant "#Collection($ref)" used to represent collection of entity references in Context URL + /// Note that if a response is a collection of entity references, the context URL does not contain the type of the referenced entities + /// + internal const string CollectionOfEntityReferencesContextUrlSegment = "#Collection($ref)"; + + /// + /// Constant "#$ref"used to represent single entity reference in Context URL + /// Note that if a response is a collection of entity references, the context URL does not contain the type of the referenced entities + /// + internal const string SingleEntityReferencesContextUrlSegment = "#$ref"; + + /// The hash sign acting as fragment indicator in a context URI. + internal const char ContextUriFragmentIndicator = '#'; + + /// The $entity token that indicates that the payload is a single item from a set. + internal const string ContextUriFragmentItemSelector = "/$entity"; + + /// The '(' used to mark the start of Select and Expand clauses in the fragment of a context URI. + internal const char ContextUriProjectionStart = '('; + + /// The ')' used to mark the end of Select and Expand clauses in the fragment of a context URI. + internal const char ContextUriProjectionEnd = ')'; + + /// The "," used to split properties of Select and Expand fragment a context URI. + internal const string ContextUriProjectionPropertySeparator = ","; + + /// The token that indicates the payload is a property with null value. + internal const string ContextUriFragmentNull = "Edm.Null"; + + /// The token that indicates the payload is a property with an untyped value. + internal const string ContextUriFragmentUntyped = "Edm.Untyped"; + + /// The $delta token indicates delta resource set. + internal const string DeltaResourceSet = "$delta"; + + /// The $delta token indicates delta resource set. + internal const string ContextUriDeltaResourceSet = UriSegmentSeparator + DeltaResourceSet; + + /// The $deletedEntity token indicates deleted resource. + internal const string DeletedEntry = "$deletedEntity"; + + /// The $deletedEntity token indicates deleted resource. + internal const string ContextUriDeletedEntry = UriSegmentSeparator + DeletedEntry; + + /// The $link token indicates delta link. + internal const string DeltaLink = "$link"; + + /// The $link token indicates delta link. + internal const string ContextUriDeltaLink = UriSegmentSeparator + DeltaLink; + + /// The $deletedLink token indicates delta deleted link. + internal const string DeletedLink = "$deletedLink"; + + /// The $deletedLink token indicates delta deleted link. + internal const string ContextUriDeletedLink = UriSegmentSeparator + DeletedLink; + #endregion Context URL + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContentTypeException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContentTypeException.cs new file mode 100644 index 0000000..304b42b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContentTypeException.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +#if ORCAS +using System.Runtime.Serialization; +#endif + +namespace Microsoft.OData +{ + /// Exception type representing exception when Content-Type of a message is not supported. +#if !PORTABLELIB + [Serializable] +#endif + [DebuggerDisplay("{Message}")] + public class ODataContentTypeException : ODataException + { + /// Creates a new instance of the class. + /// + /// The Message property is initialized to a system-supplied message + /// that describes the error. This message takes into account the + /// current system culture. + /// + public ODataContentTypeException() + : this(Strings.ODataException_GeneralError) + { + } + + /// Creates a new instance of the class. + /// Plain text error message for this exception. + public ODataContentTypeException(string message) + : this(message, null) + { + } + + /// Creates a new instance of the class. + /// Plain text error message for this exception. + /// Exception that caused this exception to be thrown. + public ODataContentTypeException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if ORCAS + /// Creates a new instance of the class from the specified SerializationInfo and StreamingContext instances. + /// A SerializationInfo containing the information required to serialize the new ODataException. + /// A StreamingContext containing the source of the serialized stream associated with the new ODataException. + [SuppressMessage("Microsoft.Design", "CA1047", Justification = "Follows serialization info pattern.")] + [SuppressMessage("Microsoft.Design", "CA1032", Justification = "Follows serialization info pattern.")] + protected ODataContentTypeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUriBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUriBuilder.cs new file mode 100644 index 0000000..fb8983a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUriBuilder.cs @@ -0,0 +1,297 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; + #endregion Namespaces + + /// + /// Builder class to construct the context url for the various payload kinds. + /// + internal sealed class ODataContextUriBuilder + { + /// + /// The base context Url + /// + private readonly Uri baseContextUrl; + + /// + /// Whether to throw exception when error(missing fields) occurs. + /// + private readonly bool throwIfMissingInfo; + + /// + /// Stores the validation method mapping for supported payload kind. + /// + private static readonly Dictionary> ValidationDictionary = new Dictionary>(EqualityComparer.Default) + { + { ODataPayloadKind.ServiceDocument, null }, + { ODataPayloadKind.EntityReferenceLink, null }, + { ODataPayloadKind.EntityReferenceLinks, null }, + { ODataPayloadKind.IndividualProperty, ValidateResourcePath }, + { ODataPayloadKind.Collection, ValidateCollectionType }, + { ODataPayloadKind.Property, ValidateType }, + { ODataPayloadKind.Resource, ValidateNavigationSource }, + { ODataPayloadKind.ResourceSet, ValidateNavigationSource }, + { ODataPayloadKind.Delta, ValidateDelta }, + }; + + /// + /// Initializes a new instance of the class. + /// + /// Base context URI. + /// Indicates whether to throw exception when error(e.g. required fields missing) occurs. + private ODataContextUriBuilder(Uri baseContextUrl, bool throwIfMissingInfo) + { + this.baseContextUrl = baseContextUrl; + this.throwIfMissingInfo = throwIfMissingInfo; + } + + /// + /// Initializes a new instance of the class. + /// + /// Base context URI. + /// Indicates whether to throw exception when error(e.g. required fields missing) occurs. + /// The context uri builder for use. + internal static ODataContextUriBuilder Create(Uri baseContextUrl, bool throwIfMissingInfo) + { + if (baseContextUrl == null && throwIfMissingInfo) + { + throw new ODataException(Strings.ODataOutputContext_MetadataDocumentUriMissing); + } + + return new ODataContextUriBuilder(baseContextUrl, throwIfMissingInfo); + } + + /// + /// Create context URL from ODataPayloadKind and ODataContextUrlInfo. + /// should make the context uri correct for null primitive / null enum value / normal enum value + /// ODataEnumValue is allowed to have null or arbitrary TypeName, but the output ContextUri must have correct type name. + /// + /// The ODataPayloadKind for the context URI. + /// The ODataContextUrlInfo to be used. + /// The generated context url. + internal Uri BuildContextUri(ODataPayloadKind payloadKind, ODataContextUrlInfo contextInfo = null) + { + if (this.baseContextUrl == null) + { + return null; + } + + Action verifyAction; + if (ValidationDictionary.TryGetValue(payloadKind, out verifyAction)) + { + if (verifyAction != null && throwIfMissingInfo) + { + Debug.Assert(contextInfo != null, "contextInfo != null"); + verifyAction(contextInfo); + } + } + else + { + throw new ODataException(Strings.ODataContextUriBuilder_UnsupportedPayloadKind(payloadKind.ToString())); + } + + switch (payloadKind) + { + case ODataPayloadKind.ServiceDocument: + return this.baseContextUrl; + case ODataPayloadKind.EntityReferenceLink: + return new Uri(this.baseContextUrl, ODataConstants.SingleEntityReferencesContextUrlSegment); + case ODataPayloadKind.EntityReferenceLinks: + return new Uri(this.baseContextUrl, ODataConstants.CollectionOfEntityReferencesContextUrlSegment); + } + + return CreateFromContextUrlInfo(contextInfo); + } + + /// + /// Create context URL from ODataContextUrlInfo. + /// + /// The ODataContextUrlInfo to be used. + /// The generated context url. + private Uri CreateFromContextUrlInfo(ODataContextUrlInfo info) + { + StringBuilder builder = new StringBuilder(); + + // # + builder.Append(ODataConstants.ContextUriFragmentIndicator); + + if (!string.IsNullOrEmpty(info.ResourcePath)) + { + builder.Append(info.ResourcePath); + + // For navigation property under complex property + if (info.DeltaKind == ODataDeltaKind.None) + { + AppendTypeCastAndQueryClause(builder, info); + } + } + else if (!string.IsNullOrEmpty(info.NavigationPath)) + { + // #ContainerName.NavigationSourceName + builder.Append(info.NavigationPath); + + if (info.DeltaKind == ODataDeltaKind.None || info.DeltaKind == ODataDeltaKind.ResourceSet || info.DeltaKind == ODataDeltaKind.Resource) + { + AppendTypeCastAndQueryClause(builder, info); + } + + switch (info.DeltaKind) + { + case ODataDeltaKind.None: + case ODataDeltaKind.Resource: + if (info.IncludeFragmentItemSelector) + { + // #ContainerName.NavigationSourceName ==> #ContainerName.NavigationSourceName/$entity + builder.Append(ODataConstants.ContextUriFragmentItemSelector); + } + + break; + case ODataDeltaKind.ResourceSet: + builder.Append(ODataConstants.ContextUriDeltaResourceSet); + break; + case ODataDeltaKind.DeletedEntry: + builder.Append(ODataConstants.ContextUriDeletedEntry); + break; + case ODataDeltaKind.Link: + builder.Append(ODataConstants.ContextUriDeltaLink); + break; + case ODataDeltaKind.DeletedLink: + builder.Append(ODataConstants.ContextUriDeletedLink); + break; + } + } + else + { + // No path information + switch (info.DeltaKind) + { + case ODataDeltaKind.ResourceSet: + return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeltaResourceSet, UriKind.Relative); + case ODataDeltaKind.DeletedEntry: + return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeletedEntry, UriKind.Relative); + case ODataDeltaKind.Link: + return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeltaLink, UriKind.Relative); + case ODataDeltaKind.DeletedLink: + return new Uri(ODataConstants.ContextUriFragmentIndicator + ODataConstants.DeletedLink, UriKind.Relative); + } + + if (!string.IsNullOrEmpty(info.TypeName)) + { // #TypeName + builder.Append(info.TypeName); + } + else + { + return null; + } + } + + return new Uri(this.baseContextUrl, builder.ToString()); + } + + /// + /// Append type cast and query clause info to string builder if any. + /// + /// The string builder to append info. + /// The ODataContextUrlInfo includes type cast and query clause info. + private static void AppendTypeCastAndQueryClause(StringBuilder builder, ODataContextUrlInfo info) + { + // #ContainerName.NavigationSourceName ==> #ContainerName.NavigationSourceName/Namespace.DerivedTypeName + if (!string.IsNullOrEmpty(info.TypeCast)) + { + builder.Append(ODataConstants.UriSegmentSeparatorChar); + builder.Append(info.TypeCast); + } + + // #ContainerName.NavigationSourceName ==> #ContainerName.NavigationSourceName(selectedPropertyList) + if (!string.IsNullOrEmpty(info.QueryClause)) + { + builder.Append(info.QueryClause); + } + } + + /// + /// Validate TypeName for given ODataContextUrlInfo for property. + /// + /// The ODataContextUrlInfo to evaluate on. + private static void ValidateType(ODataContextUrlInfo contextUrlInfo) + { + if (string.IsNullOrEmpty(contextUrlInfo.TypeName)) + { + throw new ODataException(Strings.ODataContextUriBuilder_TypeNameMissingForProperty); + } + } + + /// + /// Validate TypeName for given ODataContextUrlInfo for collection. + /// + /// The ODataContextUrlInfo to evaluate on. + private static void ValidateCollectionType(ODataContextUrlInfo contextUrlInfo) + { + if (string.IsNullOrEmpty(contextUrlInfo.TypeName)) + { + throw new ODataException(Strings.ODataContextUriBuilder_TypeNameMissingForTopLevelCollection); + } + } + + /// + /// Validate NavigationSource for given ODataContextUrlInfo for resource or resource set. + /// + /// The ODataContextUrlInfo to evaluate on. + private static void ValidateNavigationSource(ODataContextUrlInfo contextUrlInfo) + { + // For complex or complex collection property, it doesn't have any navigation source, + // Then the TypeName should be provided. + if (!contextUrlInfo.HasNavigationSourceInfo) + { + if (string.IsNullOrEmpty(contextUrlInfo.TypeName)) + { + throw new ODataException(Strings.ODataContextUriBuilder_NavigationSourceOrTypeNameMissingForResourceOrResourceSet); + } + + return; + } + + // For navigation property without navigation target, navigation path should be null so + // validate its navigation source (should be the name of the navigation property) which + // at least requires EdmUnknownEntitySet to be present; otherwise validate its navigation + // path as before. + if (!contextUrlInfo.IsUnknownEntitySet && string.IsNullOrEmpty(contextUrlInfo.NavigationPath) || + contextUrlInfo.IsUnknownEntitySet && string.IsNullOrEmpty(contextUrlInfo.NavigationSource) && + string.IsNullOrEmpty(contextUrlInfo.TypeName)) + { + throw new ODataException(Strings.ODataContextUriBuilder_NavigationSourceOrTypeNameMissingForResourceOrResourceSet); + } + } + + /// + /// Validate ResourcePath for given ODataContextUrlInfo for individual property. + /// + /// The ODataContextUrlInfo to evaluate on. + private static void ValidateResourcePath(ODataContextUrlInfo contextUrlInfo) + { + if (string.IsNullOrEmpty(contextUrlInfo.ResourcePath)) + { + throw new ODataException(Strings.ODataContextUriBuilder_ODataUriMissingForIndividualProperty); + } + } + + /// + /// Validate the given ODataContextUrlInfo for delta + /// + /// The ODataContextUrlInfo to evaluate on. + private static void ValidateDelta(ODataContextUrlInfo contextUrlInfo) + { + Debug.Assert(contextUrlInfo.DeltaKind != ODataDeltaKind.None, "contextUrlInfo.DeltaKind != ODataDeltaKind.None"); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlInfo.cs new file mode 100644 index 0000000..642c06c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlInfo.cs @@ -0,0 +1,440 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Metadata; + using Microsoft.OData.UriParser.Aggregation; + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Class representing required information for context URL. + /// + internal sealed class ODataContextUrlInfo + { + /// + /// Default constructor for + /// + private ODataContextUrlInfo() + { + DeltaKind = ODataDeltaKind.None; + } + + /// The delta kind used for building context Url + internal ODataDeltaKind DeltaKind { get; private set; } + + /// Whether target is unknown entity set + internal bool IsUnknownEntitySet { get; private set; } + + /// + /// Whether the current target has a navigation source or has a meanful the navigation source kind + /// e.g. When writing a Resource or Resource Set for Complex type. + /// EdmNavigationSourceKind.None means the target doesn't have a navigation source + /// + internal bool HasNavigationSourceInfo { get; private set; } + + /// Name of navigation path used for building context Url + internal string NavigationPath { get; private set; } + + /// Name of the navigation source used for building context Url + internal string NavigationSource { get; private set; } + + /// ResourcePath used for building context Url + internal string ResourcePath { get; private set; } + + /// if the context url represents an undeclared property + internal bool? IsUndeclared { get; private set; } + + /// Query clause used for building context Url + internal string QueryClause { get; private set; } + + /// Entity type name used for building context Url + internal string TypeName { get; private set; } + + /// TypeCast segment used for building context Url + internal string TypeCast { get; private set; } + + /// Whether context Url is for single item used for building context Url + internal bool IncludeFragmentItemSelector { get; private set; } + + /// + /// Create ODataContextUrlInfo for OdataValue. + /// + /// The ODataValue to be used. + /// OData Version. + /// The odata uri info for current query. + /// The model used to handle unsigned int conversions. + /// The generated ODataContextUrlInfo. + internal static ODataContextUrlInfo Create(ODataValue value, ODataVersion version, ODataUri odataUri = null, IEdmModel model = null) + { + return new ODataContextUrlInfo() + { + TypeName = GetTypeNameForValue(value, model), + ResourcePath = ComputeResourcePath(odataUri), + QueryClause = ComputeQueryClause(odataUri, version), + IsUndeclared = ComputeIfIsUndeclared(odataUri) + }; + } + + /// + /// Create ODataContextUrlInfo from ODataCollectionStartSerializationInfo + /// + /// The ODataCollectionStartSerializationInfo to be used. + /// ItemTypeReference specifying element type. + /// The generated ODataContextUrlInfo. + internal static ODataContextUrlInfo Create(ODataCollectionStartSerializationInfo info, IEdmTypeReference itemTypeReference) + { + string collectionTypeName = null; + if (info != null) + { + collectionTypeName = info.CollectionTypeName; + } + else if (itemTypeReference != null) + { + collectionTypeName = EdmLibraryExtensions.GetCollectionTypeName(itemTypeReference.FullName()); + } + + return new ODataContextUrlInfo() + { + TypeName = collectionTypeName, + }; + } + + /// + /// Create ODataContextUrlInfo from basic information + /// + /// Navigation source for current element.\ + /// The expectedEntity for current element. + /// Whether target is single item. + /// The odata uri info for current query. + /// The OData Version of the response. + /// The generated ODataContextUrlInfo. + internal static ODataContextUrlInfo Create(IEdmNavigationSource navigationSource, string expectedEntityTypeName, bool isSingle, ODataUri odataUri, ODataVersion version) + { + EdmNavigationSourceKind kind = navigationSource.NavigationSourceKind(); + string navigationSourceEntityType = navigationSource.EntityType().FullName(); + return new ODataContextUrlInfo() + { + IsUnknownEntitySet = kind == EdmNavigationSourceKind.UnknownEntitySet, + NavigationSource = navigationSource.Name, + TypeCast = navigationSourceEntityType == expectedEntityTypeName ? null : expectedEntityTypeName, + TypeName = navigationSourceEntityType, + IncludeFragmentItemSelector = isSingle && kind != EdmNavigationSourceKind.Singleton, + NavigationPath = ComputeNavigationPath(kind, odataUri, navigationSource.Name), + ResourcePath = ComputeResourcePath(odataUri), + QueryClause = ComputeQueryClause(odataUri, version), + IsUndeclared = ComputeIfIsUndeclared(odataUri) + }; + } + + /// + /// Create ODataContextUrlInfo from ODataResourceTypeContext + /// + /// The ODataResourceTypeContext to be used. + /// The OData Version of the response + /// Whether target is single item. + /// The odata uri info for current query. + /// The generated ODataContextUrlInfo. + internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext, ODataVersion version, bool isSingle, ODataUri odataUri = null) + { + Debug.Assert(typeContext != null, "typeContext != null"); + + var hasNavigationSourceInfo = typeContext.NavigationSourceKind != EdmNavigationSourceKind.None + || !string.IsNullOrEmpty(typeContext.NavigationSourceName); + + var typeName = hasNavigationSourceInfo + ? typeContext.NavigationSourceFullTypeName + : typeContext.ExpectedResourceTypeName == null + ? null + : isSingle + ? typeContext.ExpectedResourceTypeName + : EdmLibraryExtensions.GetCollectionTypeName(typeContext.ExpectedResourceTypeName); + + return new ODataContextUrlInfo() + { + HasNavigationSourceInfo = hasNavigationSourceInfo, + IsUnknownEntitySet = typeContext.NavigationSourceKind == EdmNavigationSourceKind.UnknownEntitySet, + NavigationSource = typeContext.NavigationSourceName, + TypeCast = typeContext.NavigationSourceEntityTypeName == null + || typeContext.ExpectedResourceTypeName == null + || typeContext.ExpectedResourceType is IEdmComplexType + || typeContext.NavigationSourceEntityTypeName == typeContext.ExpectedResourceTypeName + ? null : typeContext.ExpectedResourceTypeName, + TypeName = typeName, + IncludeFragmentItemSelector = isSingle && typeContext.NavigationSourceKind != EdmNavigationSourceKind.Singleton, + NavigationPath = ComputeNavigationPath(typeContext.NavigationSourceKind, odataUri, typeContext.NavigationSourceName), + ResourcePath = ComputeResourcePath(odataUri), + QueryClause = ComputeQueryClause(odataUri, version), + IsUndeclared = ComputeIfIsUndeclared(odataUri) + }; + } + + /// + /// Create contextUrlInfo for delta + /// + /// The ODataResourceTypeContext to be used. + /// The OData version of the response. + /// The delta kind. + /// The odata uri info for current query. + /// The generated ODataContextUrlInfo. + internal static ODataContextUrlInfo Create(ODataResourceTypeContext typeContext, ODataVersion version, ODataDeltaKind kind, ODataUri odataUri = null) + { + Debug.Assert(typeContext != null, "typeContext != null"); + + ODataContextUrlInfo contextUriInfo = new ODataContextUrlInfo() + { + IsUnknownEntitySet = typeContext.NavigationSourceKind == EdmNavigationSourceKind.UnknownEntitySet, + NavigationSource = typeContext.NavigationSourceName, + TypeCast = typeContext.NavigationSourceEntityTypeName == typeContext.ExpectedResourceTypeName ? null : typeContext.ExpectedResourceTypeName, + TypeName = typeContext.NavigationSourceEntityTypeName, + IncludeFragmentItemSelector = kind == ODataDeltaKind.Resource && typeContext.NavigationSourceKind != EdmNavigationSourceKind.Singleton, + DeltaKind = kind, + NavigationPath = ComputeNavigationPath(typeContext.NavigationSourceKind, null, typeContext.NavigationSourceName), + }; + + // Only use odata uri in with model case. + if (typeContext is ODataResourceTypeContext.ODataResourceTypeContextWithModel) + { + contextUriInfo.NavigationPath = ComputeNavigationPath(typeContext.NavigationSourceKind, odataUri, + typeContext.NavigationSourceName); + contextUriInfo.ResourcePath = ComputeResourcePath(odataUri); + contextUriInfo.QueryClause = ComputeQueryClause(odataUri, version); + contextUriInfo.IsUndeclared = ComputeIfIsUndeclared(odataUri); + } + + return contextUriInfo; + } + + /// + /// Determine whether current contextUrlInfo could be determined from parent contextUrlInfo. + /// + /// The parent contextUrlInfo. + /// Whether current contextUrlInfo could be determined from parent contextUrlInfo. + internal bool IsHiddenBy(ODataContextUrlInfo parentContextUrlInfo) + { + if (parentContextUrlInfo == null) + { + return false; + } + + if (parentContextUrlInfo.NavigationPath == NavigationPath && + parentContextUrlInfo.DeltaKind == ODataDeltaKind.ResourceSet && + this.DeltaKind == ODataDeltaKind.Resource) + { + return true; + } + + return false; + } + + private static string ComputeNavigationPath(EdmNavigationSourceKind kind, ODataUri odataUri, string navigationSource) + { + bool isContained = kind == EdmNavigationSourceKind.ContainedEntitySet; + bool isUnknownEntitySet = kind == EdmNavigationSourceKind.UnknownEntitySet; + + if (isUnknownEntitySet) + { + // If the navigation target is not specified, i.e., UnknownEntitySet, + // the navigation path should be null so that type name will be used + // to build the context Url. + return null; + } + + string navigationPath = null; + if (isContained && odataUri != null && odataUri.Path != null) + { + ODataPath odataPath = odataUri.Path.TrimEndingTypeSegment().TrimEndingKeySegment(); + if (!(odataPath.LastSegment is NavigationPropertySegment) && !(odataPath.LastSegment is OperationSegment)) + { + throw new ODataException(Strings.ODataContextUriBuilder_ODataPathInvalidForContainedElement(odataPath.ToContextUrlPathString())); + } + + navigationPath = odataPath.ToContextUrlPathString(); + } + + return navigationPath ?? navigationSource; + } + + private static string ComputeResourcePath(ODataUri odataUri) + { + if (odataUri != null && odataUri.Path != null && odataUri.Path.IsIndividualProperty()) + { + return odataUri.Path.ToContextUrlPathString(); + } + + return string.Empty; + } + + private static string ComputeQueryClause(ODataUri odataUri, ODataVersion version) + { + if (odataUri != null) + { + // TODO: Figure out how to deal with $select after $apply + if (odataUri.Apply != null) + { + return CreateApplyUriSegment(odataUri.Apply); + } + else + { + return CreateSelectExpandContextUriSegment(odataUri.SelectAndExpand); + } + } + + return null; + } + + private static bool? ComputeIfIsUndeclared(ODataUri odataUri) + { + if (odataUri != null && odataUri.Path != null) + { + return odataUri.Path.IsUndeclared(); + } + + return null; + } + + /// + /// Gets the type name based on the given odata value. + /// + /// The value. + /// The model used to handle unsigned int conversions. + /// The type name for the context URI. + private static string GetTypeNameForValue(ODataValue value, IEdmModel model) + { + if (value == null) + { + return null; + } + + // special identifier for null values. + if (value.IsNullValue) + { + return ODataConstants.ContextUriFragmentNull; + } + + if (value.TypeAnnotation != null && !string.IsNullOrEmpty(value.TypeAnnotation.TypeName)) + { + return value.TypeAnnotation.TypeName; + } + + var collectionValue = value as ODataCollectionValue; + if (collectionValue != null) + { + return EdmLibraryExtensions.GetCollectionTypeFullName(collectionValue.TypeName); + } + + var enumValue = value as ODataEnumValue; + if (enumValue != null) + { + return enumValue.TypeName; + } + + var resourceValue = value as ODataResourceValue; + if (resourceValue != null) + { + return resourceValue.TypeName; + } + + var untypedValue = value as ODataUntypedValue; + if (untypedValue != null) + { + return ODataConstants.ContextUriFragmentUntyped; + } + + ODataPrimitiveValue primitive = value as ODataPrimitiveValue; + if (primitive == null) + { + Debug.Assert(value is ODataStreamReferenceValue, "value is ODataStreamReferenceValue"); + throw new ODataException(Strings.ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource); + } + + // Try convert to underlying type if the primitive value is unsigned int. + IEdmTypeDefinitionReference typeDefinitionReference = model.ResolveUIntTypeDefinition(primitive.Value); + if (typeDefinitionReference != null) + { + return typeDefinitionReference.FullName(); + } + + IEdmPrimitiveTypeReference primitiveValueTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(primitive.Value.GetType()); + return primitiveValueTypeReference == null ? null : primitiveValueTypeReference.FullName(); + } + + private static string CreateApplyUriSegment(ApplyClause applyClause) + { + if (applyClause != null) + { + return applyClause.GetContextUri(); + } + + return string.Empty; + } + + #region SelectAndExpand Convert + /// + /// Build the expand clause for a given level in the selectExpandClause + /// + /// the current level select expand clause + /// the select and expand segment for context url in this level. + private static string CreateSelectExpandContextUriSegment(SelectExpandClause selectExpandClause) + { + if (selectExpandClause != null) + { + string contextUri; + selectExpandClause.Traverse(ProcessSubExpand, CombineSelectAndExpandResult, out contextUri); + if (!string.IsNullOrEmpty(contextUri)) + { + return ODataConstants.ContextUriProjectionStart + contextUri + ODataConstants.ContextUriProjectionEnd; + } + } + + return string.Empty; + } + + /// Process sub expand node, contact with subexpand result + /// The current expanded node. + /// Generated sub expand node. + /// The generated expand string. + private static string ProcessSubExpand(string expandNode, string subExpand) + { + return expandNode + ODataConstants.ContextUriProjectionStart + subExpand + ODataConstants.ContextUriProjectionEnd; + } + + /// Create combined result string using selected items list and expand items list. + /// A list of selected item names. + /// A list of sub expanded item names. + /// The generated expand string. + private static string CombineSelectAndExpandResult(IList selectList, IList expandList) + { + string currentExpandClause = string.Empty; + + if (selectList.Any()) + { + currentExpandClause += String.Join(ODataConstants.ContextUriProjectionPropertySeparator, selectList.ToArray()); + } + + if (expandList.Any()) + { + if (!string.IsNullOrEmpty(currentExpandClause)) + { + currentExpandClause += ODataConstants.ContextUriProjectionPropertySeparator; + } + + currentExpandClause += String.Join(ODataConstants.ContextUriProjectionPropertySeparator, expandList.ToArray()); + } + + return currentExpandClause; + } + #endregion SelectAndExpand Convert + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlLevel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlLevel.cs new file mode 100644 index 0000000..49cda1b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataContextUrlLevel.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Enumeration representing the different levels of context URL. + /// + internal enum ODataContextUrlLevel + { + /// + /// No context URL + /// Used for json with odata.metadata=none + /// + None = 0, + + /// + /// Show root context URL of the payload and the context URL for any deleted entries or added or deleted links in a delta response, + /// or for entities or entity collections whose set cannot be determined from the root context URL + /// Used for atom and json with odata.metadata=minimal + /// + OnDemand = 1, + + /// + /// Show context URL for a collection, entity, primitive value, or service document. + /// Used for json with odata.metadata=full + /// + Full = 2 + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedEntry.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedEntry.cs new file mode 100644 index 0000000..4462a6a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedEntry.cs @@ -0,0 +1,144 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + /// + /// The reason of deleted resource in delta response. + /// + public enum DeltaDeletedEntryReason + { + /// + /// Entity is deleted (destroyed). + /// + Deleted = 0, + + /// + /// Entity is removed from membership in the result(i.e., due to a data change). + /// + Changed = 1, + } + + /// + /// Represents a deleted entity in delta response. + /// + public sealed class ODataDeletedResource : ODataResourceBase + { + /// + /// Initializes a new . + /// + public ODataDeletedResource() + { + } + + /// + /// Initializes a new . + /// + /// The id of the deleted entity, which may be absolute or relative. + /// The reason of deleted resource. + public ODataDeletedResource(System.Uri id, DeltaDeletedEntryReason reason) + { + if (id != null) + { + this.Id = id; + } + + this.Reason = reason; + } + + /// + /// Optional. Either deleted, if the entity was deleted (destroyed), or changed if the entity was removed from membership in the result (i.e., due to a data change). + /// + public DeltaDeletedEntryReason? Reason { get; set; } + } + + /// + /// Represents a deleted entity in delta response. + /// + public sealed class ODataDeltaDeletedEntry : ODataItem + { + /// + /// Provides additional serialization information to the for this . + /// + private ODataDeltaSerializationInfo serializationInfo; + + /// + /// Initializes a new . + /// + /// The id of the deleted entity, which may be absolute or relative. + /// The reason of deleted resource. + public ODataDeltaDeletedEntry(string id, DeltaDeletedEntryReason reason) + { + this.Id = id; + this.Reason = reason; + } + + /// + /// The id of the deleted entity (same as the odata.id returned or computed when calling GET on resource), which may be absolute or relative. + /// + public string Id { get; set; } + + /// + /// Optional. Either deleted, if the entity was deleted (destroyed), or changed if the entity was removed from membership in the result (i.e., due to a data change). + /// + public DeltaDeletedEntryReason? Reason { get; set; } + + /// + /// Provides additional serialization information to the for this . + /// + internal ODataDeltaSerializationInfo SerializationInfo + { + get + { + return this.serializationInfo; + } + + set + { + this.serializationInfo = ODataDeltaSerializationInfo.Validate(value); + } + } + + /// Gets an ODataDeltaDeletedEntry representation of the ODataDeletedResource + /// The ODataDeletedResource. + /// A returned ODataDeltaDeletedEntry to write. + internal static ODataDeltaDeletedEntry GetDeltaDeletedEntry(ODataDeletedResource entry) + { + ODataDeltaDeletedEntry deletedEntry = new ODataDeltaDeletedEntry(entry.Id.OriginalString, entry.Reason ?? DeltaDeletedEntryReason.Deleted); + if (entry.SerializationInfo != null) + { + deletedEntry.SetSerializationInfo(entry.SerializationInfo); + } + + return deletedEntry; + } + + /// Gets an ODataDeletedResource representation of the ODataDeltaDeletedEntry + /// The ODataDeltaDeletedEntry. + /// A returned ODataDeletedResource to write. + internal static ODataDeletedResource GetDeletedResource(ODataDeltaDeletedEntry entry) + { + Uri id = UriUtils.StringToUri(entry.Id); + ODataDeletedResource deletedResource = new ODataDeletedResource() + { + Id = id, + Reason = entry.Reason, + }; + + if (entry.SerializationInfo != null) + { + deletedResource.SerializationInfo = new ODataResourceSerializationInfo() + { + NavigationSourceName = entry.SerializationInfo == null ? null : entry.SerializationInfo.NavigationSourceName + }; + } + + return deletedResource; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedLink.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedLink.cs new file mode 100644 index 0000000..b1fbbd6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaDeletedLink.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + /// + /// Represents a deleted link in delta response. + /// + public sealed class ODataDeltaDeletedLink : ODataDeltaLinkBase + { + /// + /// Initializes a new . + /// + /// The id of the entity from which the relationship is defined, which may be absolute or relative. + /// The id of the related entity, which may be absolute or relative. + /// The name of the relationship property on the parent object. + public ODataDeltaDeletedLink(Uri source, Uri target, string relationship) + : base(source, target, relationship) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaKind.cs new file mode 100644 index 0000000..f31c015 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaKind.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Delta kinds + /// + internal enum ODataDeltaKind + { + /// None delta + None, + + /// Delta resource set + ResourceSet, + + /// Delta resource + Resource, + + /// Delta deleted resource + DeletedEntry, + + /// Delta link + Link, + + /// Delta deleted link + DeletedLink, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLink.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLink.cs new file mode 100644 index 0000000..92725ed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLink.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + /// + /// Represents an added link in delta response. + /// + public sealed class ODataDeltaLink : ODataDeltaLinkBase + { + /// + /// Initializes a new . + /// + /// The id of the entity from which the relationship is defined, which may be absolute or relative. + /// The id of the related entity, which may be absolute or relative. + /// The name of the relationship property on the parent object. + public ODataDeltaLink(Uri source, Uri target, string relationship) + : base(source, target, relationship) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLinkBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLinkBase.cs new file mode 100644 index 0000000..aec75b1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaLinkBase.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + /// + /// Represents either an added link or a deleted link in delta response. + /// + public abstract class ODataDeltaLinkBase : ODataItem + { + /// + /// Provides additional serialization information to the for this . + /// + private ODataDeltaSerializationInfo serializationInfo; + + /// + /// Initializes a new . + /// + /// The id of the entity from which the relationship is defined, which may be absolute or relative. + /// The id of the related entity, which may be absolute or relative. + /// The name of the relationship property on the parent object. + protected ODataDeltaLinkBase(Uri source, Uri target, string relationship) + { + this.Source = source; + this.Target = target; + this.Relationship = relationship; + } + + /// + /// The id of the entity from which the relationship is defined, which may be absolute or relative. + /// + public Uri Source { get; set; } + + /// + /// The id of the related entity, which may be absolute or relative. + /// + public Uri Target { get; set; } + + /// + /// The name of the relationship property on the parent object. + /// + public string Relationship { get; set; } + + /// + /// Provides additional serialization information to the for this . + /// + internal ODataDeltaSerializationInfo SerializationInfo + { + get + { + return this.serializationInfo; + } + + set + { + this.serializationInfo = ODataDeltaSerializationInfo.Validate(value); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReader.cs new file mode 100644 index 0000000..f337cf4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReader.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Base class for OData delta readers. + /// + public abstract class ODataDeltaReader + { + /// Gets the current state of the reader. + /// The current state of the reader. + public abstract ODataDeltaReaderState State { get; } + + /// Gets the current sub state of the reader. + /// The current sub state of the reader. + /// + /// The sub state is a complement to the current state if the current state itself is not enough to determine + /// the real state of the reader. The sub state is only meaningful in ExpandedNavigationProperty state. + /// + public abstract ODataReaderState SubState { get; } + + /// Gets the most recent that has been read. + /// The most recent that has been read. + public abstract ODataItem Item { get; } + + /// Reads the next from the message payload. + /// true if more items were read; otherwise false. + public abstract bool Read(); + +#if PORTABLELIB + /// Asynchronously reads the next from the message payload. + /// A task that when completed indicates whether more items were read. + public abstract Task ReadAsync(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReaderState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReaderState.cs new file mode 100644 index 0000000..be3130a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaReaderState.cs @@ -0,0 +1,89 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Enumeration of all possible states of an . + /// + public enum ODataDeltaReaderState + { + /// The reader is at the start; nothing has been read yet. + /// In this state, the Item property of the returns null. + Start, + + /// The start of a delta resource set has been read. + /// + /// In this state the Item property of the returns + /// an but no properties may be filled in until the DeltaResourceSetEnd state is reached. + /// + DeltaResourceSetStart, + + /// The end of a delta resource set has been read. + /// + /// In this state the Item property of the returns + /// an with all properties filled in. + /// + DeltaResourceSetEnd, + + /// The start of a delta resource has been read. + /// + /// In this state the Item property of the returns + /// an but no properties may be filled in until the EntryEnd state is reached. + /// + DeltaResourceStart, + + /// The end of a delta resource has been read. + /// + /// In this state the Item property of the returns + /// an with all properties filled in. + /// + DeltaResourceEnd, + + /// An delta deleted resource was read. + /// + /// In this state the Item property of the returns + /// an which is fully populated. + /// Note that there's no End state for this item. + /// + DeltaDeletedEntry, + + /// An delta link was read. + /// + /// In this state the Item property of the returns + /// an which is fully populated. + /// Note that there's no End state for this item. + /// + DeltaLink, + + /// An delta deleted link was read. + /// + /// In this state the Item property of the returns + /// an which is fully populated. + /// Note that there's no End state for this item. + /// + DeltaDeletedLink, + + /// The reader has thrown an exception; nothing can be read from the reader anymore. + /// + /// In this state the Item property of the returns null. + /// + Exception, + + /// The reader has completed; nothing can be read anymore. + /// + /// In this state, the Item property of the returns null. + /// + Completed, + + /// A nested resource info was read. + /// + /// In this state the Item property of the returns + /// the current item of the underlying nested resource reader. + /// + NestedResource, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSet.cs new file mode 100644 index 0000000..f48617b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSet.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Describes a set of delta changes. + /// + public sealed class ODataDeltaResourceSet : ODataResourceSetBase + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSetSerializationInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSetSerializationInfo.cs new file mode 100644 index 0000000..0089781 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaResourceSetSerializationInfo.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class to provide additional serialization information to the for an . + /// + public sealed class ODataDeltaResourceSetSerializationInfo + { + /// + /// The entity set name of the resource/source resource to be written. Should be fully qualified if the entity set is not in the default container. + /// + private string entitySetName; + + /// + /// The namespace qualified entity type name of the entity set. + /// + private string entityTypeName; + + /// + /// The namespace qualified type name of the expected entity type. + /// + private string expectedEntityTypeName; + + /// + /// The entity set name of the resource/source resource to be written. Should be fully qualified if the entity set is not in the default container. + /// + public string EntitySetName + { + get + { + return this.entitySetName; + } + + set + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(value, "EntitySetName"); + this.entitySetName = value; + } + } + + /// + /// The namespace qualified element type name of the entity set. + /// + public string EntityTypeName + { + get + { + return this.entityTypeName; + } + + set + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(value, "EntityTypeName"); + this.entityTypeName = value; + } + } + + /// + /// The namespace qualified type name of the expected entity type. + /// + public string ExpectedTypeName + { + get + { + return this.expectedEntityTypeName ?? this.EntityTypeName; + } + + set + { + ExceptionUtils.CheckArgumentStringNotEmpty(value, "ExpectedTypeName"); + this.expectedEntityTypeName = value; + } + } + + /// + /// Validates the instance. + /// + /// The serialization info instance to validate. + /// The instance. + internal static ODataDeltaResourceSetSerializationInfo Validate(ODataDeltaResourceSetSerializationInfo serializationInfo) + { + if (serializationInfo != null) + { + ExceptionUtils.CheckArgumentNotNull(serializationInfo.EntitySetName, "serializationInfo.EntitySetName"); + ExceptionUtils.CheckArgumentNotNull(serializationInfo.EntityTypeName, "serializationInfo.EntityTypeName"); + } + + return serializationInfo; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaSerializationInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaSerializationInfo.cs new file mode 100644 index 0000000..516ba8a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaSerializationInfo.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class to provide additional serialization information to the . + /// + public sealed class ODataDeltaSerializationInfo + { + /// + /// The navigation source name of the resource/source resource to be written. Should be fully qualified if the navigatio nsource is not in the default container. + /// + private string navigationSourceName; + + /// + /// The navigation source name of the resource/source resource to be written. Should be fully qualified if the navigation source is not in the default container. + /// + public string NavigationSourceName + { + get + { + return this.navigationSourceName; + } + + set + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(value, "NavigationSourceName"); + this.navigationSourceName = value; + } + } + + /// + /// Validates the instance. + /// + /// The serialization info instance to validate. + /// The instance. + internal static ODataDeltaSerializationInfo Validate(ODataDeltaSerializationInfo serializationInfo) + { + if (serializationInfo != null) + { + ExceptionUtils.CheckArgumentNotNull(serializationInfo.NavigationSourceName, "serializationInfo.EntitySetName"); + } + + return serializationInfo; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaWriter.cs new file mode 100644 index 0000000..a0eed91 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeltaWriter.cs @@ -0,0 +1,151 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Base class for OData delta writer. + /// + public abstract class ODataDeltaWriter + { + /// + /// Start writing a delta resource set. + /// + /// Delta resource set/collection to write. + public abstract void WriteStart(ODataDeltaResourceSet deltaResourceSet); + +#if PORTABLELIB + /// + /// Asynchronously start writing a delta resource set. + /// + /// Delta resource set/collection to write. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet); +#endif + + /// + /// Finish writing a delta resource set. + /// + public abstract void WriteEnd(); + +#if PORTABLELIB + /// + /// Asynchronously finish writing a delta resource set. + /// + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteEndAsync(); +#endif + + /// + /// Start writing a nested resource info. + /// + /// The nested resource info to write. + public abstract void WriteStart(ODataNestedResourceInfo nestedResourceInfo); + +#if PORTABLELIB + /// + /// Asynchronously start writing a nested resource info. + /// + /// The nested resource info to write. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo); +#endif + + /// + /// Start writing an expanded resource set. + /// + /// The expanded resource set to write. + public abstract void WriteStart(ODataResourceSet expandedResourceSet); + +#if PORTABLELIB + /// + /// Asynchronously start writing an expanded resource set. + /// + /// The expanded resource set to write. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteStartAsync(ODataResourceSet expandedResourceSet); +#endif + + /// + /// Start writing a delta resource. + /// + /// The delta resource to write. + public abstract void WriteStart(ODataResource deltaResource); + +#if PORTABLELIB + /// + /// Asynchronously start writing a delta resource. + /// + /// The delta resource to write. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteStartAsync(ODataResource deltaResource); +#endif + + /// + /// Writing a delta deleted resource. + /// + /// The delta deleted resource to write. + public abstract void WriteDeltaDeletedEntry(ODataDeltaDeletedEntry deltaDeletedEntry); + +#if PORTABLELIB + /// + /// Asynchronously writing a delta deleted resource. + /// + /// The delta deleted resource to write. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteDeltaDeletedEntryAsync(ODataDeltaDeletedEntry deltaDeletedEntry); +#endif + + /// + /// Writing a delta link. + /// + /// The delta link to write. + public abstract void WriteDeltaLink(ODataDeltaLink deltaLink); + +#if PORTABLELIB + /// + /// Asynchronously writing a delta link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink); +#endif + + /// + /// Writing a delta deleted link. + /// + /// The delta deleted link to write. + public abstract void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaDeletedLink); + +#if PORTABLELIB + /// + /// Asynchronously writing a delta deleted link. + /// + /// The delta deleted link to write. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaDeletedLink); +#endif + + /// + /// Flushes the write buffer to the underlying stream. + /// + public abstract void Flush(); + +#if PORTABLELIB + /// + /// Asynchronously flushes the write buffer to the underlying stream. + /// + /// A task instance that represents the asynchronous operation. + public abstract Task FlushAsync(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeserializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeserializer.cs new file mode 100644 index 0000000..04f8660 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataDeserializer.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System.Diagnostics; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Base class for all OData deserializers. + /// + internal abstract class ODataDeserializer + { + /// The reader validator to use for reading. + protected IReaderValidator ReaderValidator; + + /// The input context to use for reading. + private readonly ODataInputContext inputContext; + + /// + /// Constructor. + /// + /// The input context to read from. + protected ODataDeserializer(ODataInputContext inputContext) + { + Debug.Assert(inputContext != null, "inputContext != null"); + + this.inputContext = inputContext; + this.ReaderValidator = this.inputContext.MessageReaderSettings.Validator; + } + + /// + /// The message reader settings. + /// + internal ODataMessageReaderSettings MessageReaderSettings + { + get + { + return this.inputContext.MessageReaderSettings; + } + } + + /// + /// true if the input is a response payload; false if it's a request payload. + /// + internal bool ReadingResponse + { + get + { + return this.inputContext.ReadingResponse; + } + } + + /// + /// The model to use. + /// + internal IEdmModel Model + { + get + { + return this.inputContext.Model; + } + } + + /// + /// Creates a new instance of a duplicate property names checker. + /// + /// The newly created instance of duplicate property names checker. + internal PropertyAndAnnotationCollector CreatePropertyAndAnnotationCollector() + { + return this.inputContext.CreatePropertyAndAnnotationCollector(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEdmPropertyAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEdmPropertyAnnotation.cs new file mode 100644 index 0000000..c0d74ce --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEdmPropertyAnnotation.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.OData +{ + /// Represents an annotation to hold information for a particular property. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm", Justification = "Camel-casing in class name.")] + public sealed class ODataEdmPropertyAnnotation + { + /// Gets the behavior for readers when reading property with null value. + /// The behavior for readers when reading property with null value. + public ODataNullValueBehaviorKind NullValueReadBehaviorKind + { + get; + set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLink.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLink.cs new file mode 100644 index 0000000..5ff5683 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLink.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Represents an entity reference link (the result of a $link query). + /// + [DebuggerDisplay("{Url.OriginalString}")] + public sealed class ODataEntityReferenceLink : ODataItem + { + /// Gets or sets the URI representing the URL of the referenced entity. + /// The URI representing the URL of the referenced entity. + /// This URL should be usable to retrieve or modify the referenced entity. + public Uri Url + { + get; + set; + } + + /// + /// Collection of custom instance annotations. + /// + public ICollection InstanceAnnotations + { + get { return this.GetInstanceAnnotations(); } + set { this.SetInstanceAnnotations(value); } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLinks.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLinks.cs new file mode 100644 index 0000000..5d272f2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntityReferenceLinks.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + + #endregion Namespaces + + /// + /// Represents a collection of entity reference links (the result of a $ref query). + /// Might include an inline count and a next link. + /// + public sealed class ODataEntityReferenceLinks : ODataAnnotatable + { + /// Gets or sets the optional inline count of the $ref collection. + /// The optional inline count of the $ref collection. + public long? Count + { + get; + set; + } + + /// Gets or sets the optional next link of the $ref collection. + /// The optional next link of the $ref collection. + public Uri NextPageLink + { + get; + set; + } + + /// Gets or sets the enumerable of instances representing the links of the referenced entities. + /// The enumerable of instances. + /// These links should be usable to retrieve or modify the referenced entities. + public IEnumerable Links + { + get; + set; + } + + /// + /// Collection of custom instance annotations. + /// + public ICollection InstanceAnnotations + { + get { return this.GetInstanceAnnotations(); } + set { this.SetInstanceAnnotations(value); } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntitySetInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntitySetInfo.cs new file mode 100644 index 0000000..f254355 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataEntitySetInfo.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class representing a entity set in a service document. + /// + public sealed class ODataEntitySetInfo : ODataServiceDocumentElement + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataError.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataError.cs new file mode 100644 index 0000000..f3e1e94 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataError.cs @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Class representing an error payload. + /// + [DebuggerDisplay("{ErrorCode}: {Message}")] +#if ORCAS + [Serializable] +#endif + public sealed class ODataError : ODataAnnotatable + { + /// Gets or sets the error code to be used in payloads. + /// The error code to be used in payloads. + public string ErrorCode + { + get; + set; + } + + /// Gets or sets the error message. + /// The error message. + public string Message + { + get; + set; + } + + /// Gets or sets the target of the particular error. + /// For example, the name of the property in error + public string Target { get; set; } + + /// + /// A collection of JSON objects that MUST contain name/value pairs for code and message, and MAY contain + /// a name/value pair for target, as described above. + /// + /// The error details. + public ICollection Details { get; set; } + + /// Gets or sets the implementation specific debugging information to help determine the cause of the error. + /// The implementation specific debugging information. + public ODataInnerError InnerError + { + get; + set; + } + + /// + /// Collection of custom instance annotations. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow the same instance annotation collection instance to be shared across ODataLib OM instances.")] + public ICollection InstanceAnnotations + { + get { return this.GetInstanceAnnotations(); } + set { this.SetInstanceAnnotations(value); } + } + + /// + /// Serialization to Json format string representing the error object. + /// + /// The string in Json format. + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, + "{{\"error\":{{" + + "\"code\":\"{0}\"," + + "\"message\":\"{1}\"," + + "\"target\":\"{2}\"," + + "\"details\":{3}," + + "\"innererror\":{4}" + + " }}}}", + this.ErrorCode == null ? "" : JsonValueUtils.GetEscapedJsonString(this.ErrorCode), + this.Message == null ? "" : JsonValueUtils.GetEscapedJsonString(this.Message), + this.Target == null ? "" : JsonValueUtils.GetEscapedJsonString(this.Target), + this.Details == null ? "{}" : GetJsonStringForDetails(), + this.InnerError == null ? "{}" : this.InnerError.ToJson()); + } + + /// + /// Convert the Details property to Json format string. + /// + /// Json format string representing collection. + private string GetJsonStringForDetails() + { + return "[" + String.Join(",", this.Details.Select(i => i.ToJson()).ToArray()) + "]"; + } +} +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorDetail.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorDetail.cs new file mode 100644 index 0000000..a293aa4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorDetail.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + + +namespace Microsoft.OData +{ + #region + using System.Globalization; + #endregion + + /// + /// Class representing an error detail. + /// + public sealed class ODataErrorDetail + { + /// Gets or sets the error code to be used in payloads. + /// The error code to be used in payloads. + public string ErrorCode { get; set; } + + /// Gets or sets the error message. + /// The error message. + public string Message { get; set; } + + /// Gets or sets the target of the particular error. + /// For example, the name of the property in error + public string Target { get; set; } + + /// + /// Serialization to Json format string. + /// + /// The string in Json format + internal string ToJson() + { + return string.Format(CultureInfo.InvariantCulture, + "{{ \"errorcode\": \"{0}\", \"message\": \"{1}\", \"target\": \"{2}\" }}", + this.ErrorCode ?? "", this.Message ?? "", this.Target ?? ""); + } +} +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorException.cs new file mode 100644 index 0000000..6fc555b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataErrorException.cs @@ -0,0 +1,182 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; +#if ORCAS + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + using System.Security.Permissions; +#endif + #endregion Namespaces + + /// + /// Exception type representing an in-stream error parsed when reading a payload. + /// +#if ORCAS + [Serializable] +#endif + [DebuggerDisplay("{Message}")] + public sealed class ODataErrorException : ODataException + { + /// The value containing instance representing the error + /// read from the payload. + /// +#if ORCAS + // Because we don't want the exception state to be serialized normally, we take care of that in the constructor. + [NonSerialized] +#endif + private ODataErrorExceptionSafeSerializationState state; + + /// Creates a new instance of the class with default values. + /// + /// The Message property is initialized to a system-supplied message + /// that describes the error. This message takes into account the + /// current system culture. The Error property will be initialized with an empty instance. + /// + public ODataErrorException() + : this(Strings.ODataErrorException_GeneralError) + { + } + + /// Creates a new instance of the class with an error message. + /// The plain text error message for this exception. + /// + /// The Error property will be initialized with an empty instance. + /// + public ODataErrorException(string message) + : this(message, (Exception)null) + { + } + + /// Creates a new instance of the class with an error message and an inner exception. + /// The plain text error message for this exception. + /// The inner exception that is the cause of this exception to be thrown. + /// + /// The Error property will be initialized with an empty instance. + /// + public ODataErrorException(string message, Exception innerException) + : this(message, innerException, new ODataError()) + { + } + + /// Creates a new instance of the class with an object. + /// The instance representing the error read from the payload. + /// + /// The Message property is initialized to a system-supplied message + /// that describes the error. This message takes into account the + /// current system culture. + /// + public ODataErrorException(ODataError error) + : this(Strings.ODataErrorException_GeneralError, null, error) + { + } + + /// Creates a new instance of the class with an error message and an object. + /// The plain text error message for this exception. + /// The instance representing the error read from the payload. + public ODataErrorException(string message, ODataError error) + : this(message, null, error) + { + } + + /// Creates a new instance of the class with an error message, an inner exception, and an object. + /// The plain text error message for this exception. + /// The inner exception that is the cause of this exception to be thrown. + /// The instance representing the error read from the payload. + public ODataErrorException(string message, Exception innerException, ODataError error) + : base(message, innerException) + { + this.state.ODataError = error; + } + +#if ORCAS +#pragma warning disable 0628 + // Warning CS0628: + // A sealed class cannot introduce a protected member because no other class will be able to inherit from the + // sealed class and use the protected member. + // + // This method is used by the runtime when deserializing an exception. + + /// + /// Initializes a new instance of the ODataErrorException class from the + /// specified SerializationInfo and StreamingContext instances. + /// + /// + /// A SerializationInfo containing the information required to serialize + /// the new ODataException. + /// + /// + /// A StreamingContext containing the source of the serialized stream + /// associated with the new ODataErrorException. + /// + [SuppressMessage("Microsoft.Design", "CA1047", Justification = "Follows serialization info pattern.")] + [SuppressMessage("Microsoft.Design", "CA1032", Justification = "Follows serialization info pattern.")] + protected ODataErrorException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + ExceptionUtils.CheckArgumentNotNull(info, "serializationInfo"); + + this.state.ODataError = (ODataError)info.GetValue("Error", typeof(ODataError)); + } +#pragma warning restore 0628 +#endif + + /// Gets the instance representing the error read from the payload. + /// The instance representing the error read from the payload. + public ODataError Error + { + get + { + return this.state.ODataError; + } + } + +#if ORCAS + /// + /// Recreates the instance of the exception. + /// + /// + /// A SerializationInfo containing the information required to serialize + /// the ODataErrorException. + /// + /// + /// A StreamingContext containing the source of the serialized stream + /// associated with the new ODataErrorException. + /// + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + ExceptionUtils.CheckArgumentNotNull(info, "serializationInfo"); + + base.GetObjectData(info, context); + info.AddValue("Error", this.state.ODataError); + } +#endif + + /// + /// Implement the ISafeSerializationData interface to contain custom exception data in a partially trusted assembly. + /// Use this interface in post-ORCAS to replace the Exception.GetObjectData method, which is marked with the SecurityCriticalAttribute. + /// +#if ORCAS + [Serializable] +#endif + private struct ODataErrorExceptionSafeSerializationState + { + /// + /// Gets or sets the object. + /// + public ODataError ODataError + { + get; + set; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataException.cs new file mode 100644 index 0000000..55a26cd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataException.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +#if ORCAS +using System.Runtime.Serialization; +#endif + +namespace Microsoft.OData +{ + /// + /// Exception type representing exceptions in the OData library. + /// +#if ORCAS + [Serializable] +#endif + [DebuggerDisplay("{Message}")] + public class ODataException : InvalidOperationException + { + /// Creates a new instance of the class with default values. + /// + /// The Message property is initialized to a system-supplied message + /// that describes the error. This message takes into account the + /// current system culture. + /// + public ODataException() + : this(Strings.ODataException_GeneralError) + { + } + + /// Creates a new instance of the class with an error message. + /// The plain text error message for this exception. + public ODataException(string message) + : this(message, null) + { + } + + /// Creates a new instance of the class with an error message and an inner exception. + /// The plain text error message for this exception. + /// The inner exception that is the cause of this exception to be thrown. + public ODataException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if ORCAS + /// Creates a new instance of the class from the specified and instances. + /// A containing the information required to serialize the new . + /// A containing the source of the serialized stream associated with the new . + [SuppressMessage("Microsoft.Design", "CA1047", Justification = "Follows serialization info pattern.")] + [SuppressMessage("Microsoft.Design", "CA1032", Justification = "Follows serialization info pattern.")] + protected ODataException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFormat.cs new file mode 100644 index 0000000..aa0153a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFormat.cs @@ -0,0 +1,152 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections.Generic; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using System.Text; + using Microsoft.OData.Json; + using Microsoft.OData.MultipartMixed; + #endregion Namespaces + + /// + /// Representation of an OData format. + /// + public abstract class ODataFormat + { + /// The JSON Light format instance. + private static ODataJsonFormat JsonFormat = new ODataJsonFormat(); + + /// The RAW format instance. + private static ODataRawValueFormat rawValueFormat = new ODataRawValueFormat(); + + /// The batch format instance. + private static ODataMultipartMixedBatchFormat batchFormat = new ODataMultipartMixedBatchFormat(); + + /// The metadata format instance. + private static ODataMetadataFormat metadataFormat = new ODataMetadataFormat(); + + /// Specifies the JSON format. + /// The JSON format. + public static ODataFormat Json + { + get + { + return JsonFormat; + } + } + + /// Specifies the RAW format; used for raw values. + /// The RAW format. + public static ODataFormat RawValue + { + get + { + return rawValueFormat; + } + } + + /// Gets the batch format instance. + /// The batch format instance. + public static ODataFormat Batch + { + get + { + return batchFormat; + } + } + + /// Gets the metadata format instance. + /// The metadata format instance. + public static ODataFormat Metadata + { + get + { + return metadataFormat; + } + } + + /// + /// Detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The set of s that are supported with the specified payload. + /// + /// The stream returned by GetMessageStream of could be used for reading for + /// payload kind detection. Reading this stream won't affect later reading operations of payload processing. + /// + public abstract IEnumerable DetectPayloadKind(ODataMessageInfo messageInfo, ODataMessageReaderSettings settings); + + /// + /// Creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The newly created input context. + public abstract ODataInputContext CreateInputContext(ODataMessageInfo messageInfo, ODataMessageReaderSettings messageReaderSettings); + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// The newly created output context. + public abstract ODataOutputContext CreateOutputContext(ODataMessageInfo messageInfo, ODataMessageWriterSettings messageWriterSettings); + +#if PORTABLELIB + /// + /// Asynchronously detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// A task that when completed returns the set of s + /// that are supported with the specified payload. + /// + /// The stream returned by GetMessageStream of could be used for reading for + /// payload kind detection. Reading this stream won't affect later reading operations of payload processing. + /// + public abstract Task> DetectPayloadKindAsync(ODataMessageInfo messageInfo, ODataMessageReaderSettings settings); + + /// + /// Asynchronously creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// Task which when completed returned the newly created input context. + public abstract Task CreateInputContextAsync(ODataMessageInfo messageInfo, ODataMessageReaderSettings messageReaderSettings); + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// Task which represents the pending create operation. + public abstract Task CreateOutputContextAsync(ODataMessageInfo messageInfo, ODataMessageWriterSettings messageWriterSettings); +#endif + + /// + /// Returns the appropriate content-type for this format. + /// + /// The specified media type. + /// The specified encoding. + /// True if the message writer is being used to write a response. + /// The resultant parameters list of the media type. Parameters list could be updated + /// when getting content type and should be returned if that is the case. + /// + /// The content-type value for the format. + internal virtual string GetContentType(ODataMediaType mediaType, Encoding encoding, + bool writingResponse, out IEnumerable> mediaTypeParameters) + { + mediaTypeParameters = mediaType.Parameters; + return HttpUtils.BuildContentType(mediaType, encoding); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunction.cs new file mode 100644 index 0000000..9aef981 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunction.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + #endregion Namespaces + + /// Represents an OData function. + [DebuggerDisplay("{Metadata}")] + public sealed class ODataFunction : ODataOperation + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunctionImportInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunctionImportInfo.cs new file mode 100644 index 0000000..67b8ba3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataFunctionImportInfo.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class representing a function Import in a service document. + /// + public sealed class ODataFunctionImportInfo : ODataServiceDocumentElement + { + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInnerError.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInnerError.cs new file mode 100644 index 0000000..783699e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInnerError.cs @@ -0,0 +1,97 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Globalization; + using Microsoft.OData.Json; + #endregion Namespaces + + /// + /// Class representing implementation specific debugging information to help determine the cause of the error. + /// + [DebuggerDisplay("{Message}")] +#if ORCAS + [Serializable] +#endif + public sealed class ODataInnerError + { + /// Initializes a new instance of the class with default values. + public ODataInnerError() + { + } + + /// Initializes a new instance of the class with exception object. + /// The used to create the inner error. + public ODataInnerError(Exception exception) + { + ExceptionUtils.CheckArgumentNotNull(exception, "exception"); + + this.Message = exception.Message ?? string.Empty; + this.TypeName = exception.GetType().FullName; + this.StackTrace = exception.StackTrace; + + if (exception.InnerException != null) + { + this.InnerError = new ODataInnerError(exception.InnerException); + } + } + + /// Gets or sets the error message. + /// The error message. + public string Message + { + get; + set; + } + + /// Gets or sets the type name of this error, for example, the type name of an exception. + /// The type name of this error. + public string TypeName + { + get; + set; + } + + /// Gets or sets the stack trace for this error. + /// The stack trace for this error. + public string StackTrace + { + get; + set; + } + + /// Gets or sets the nested implementation specific debugging information. + /// The nested implementation specific debugging information. + public ODataInnerError InnerError + { + get; + set; + } + + /// + /// Serialization to Json format string. + /// + /// The string in Json format + internal string ToJson() + { + return string.Format(CultureInfo.InvariantCulture, + "{{" + + "\"message\":\"{0}\"," + + "\"type\":\"{1}\"," + + "\"stacktrace\":\"{2}\"," + + "\"innererror\":{3}" + + "}}", + this.Message == null ? "" : JsonValueUtils.GetEscapedJsonString(this.Message), + this.TypeName == null ? "" : JsonValueUtils.GetEscapedJsonString(this.TypeName), + this.StackTrace == null ? "" : JsonValueUtils.GetEscapedJsonString(this.StackTrace), + this.InnerError == null ? "{}" : this.InnerError.ToJson()); + } +} +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInputContext.cs new file mode 100644 index 0000000..cb00144 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInputContext.cs @@ -0,0 +1,667 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using System.Xml; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + #endregion Namespaces + + /// + /// Base class for all input contexts, defines the interface + /// to be implemented by the specific formats. + /// + public abstract class ODataInputContext : IDisposable + { + /// The format for this input context. + private readonly ODataFormat format; + + /// The message reader settings to be used for reading. + private readonly ODataMessageReaderSettings messageReaderSettings; + + /// Set to true if this context is reading a response payload. + private readonly bool readingResponse; + + /// true if the input should be read synchronously; false if it should be read asynchronously. + private readonly bool synchronous; + + /// The optional URL resolver to perform custom URL resolution for URLs read from the payload. + private readonly IODataPayloadUriConverter payloadUriConverter; + + /// The optional dependency injection container to get related services for message reading. + private readonly IServiceProvider container; + + /// The model to use. + private readonly IEdmModel model; + + /// The type resolver to use. + private readonly EdmTypeResolver edmTypeResolver; + + /// The payload value converter to use. + private readonly ODataPayloadValueConverter payloadValueConverter; + + /// + /// The ODataSimplifiedOptions used in reader. + /// + private readonly ODataSimplifiedOptions odataSimplifiedOptions; + + /// Set to true if the input was disposed. + private bool disposed; + + /// + /// Constructor. + /// + /// The format for this input context. + /// The context information for the message. + /// Configuration settings of the OData reader. + protected ODataInputContext( + ODataFormat format, + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(format, "format"); + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + this.format = format; + this.messageReaderSettings = messageReaderSettings; + this.readingResponse = messageInfo.IsResponse; + this.synchronous = !messageInfo.IsAsync; + this.model = messageInfo.Model ?? EdmCoreModel.Instance; + this.payloadUriConverter = messageInfo.PayloadUriConverter; + this.container = messageInfo.Container; + this.edmTypeResolver = new EdmTypeReaderResolver(this.Model, this.MessageReaderSettings.ClientCustomTypeResolver); + this.payloadValueConverter = ODataPayloadValueConverter.GetPayloadValueConverter(this.container); + this.odataSimplifiedOptions = ODataSimplifiedOptions.GetODataSimplifiedOptions(this.container, messageReaderSettings.Version); + } + + /// + /// The message reader settings to be used for reading. + /// + public ODataMessageReaderSettings MessageReaderSettings + { + get + { + return this.messageReaderSettings; + } + } + + /// + /// Set to true if a response is being read. + /// + public bool ReadingResponse + { + get + { + return this.readingResponse; + } + } + + /// + /// true if the input should be read synchronously; false if it should be read asynchronously. + /// + public bool Synchronous + { + get + { + return this.synchronous; + } + } + + /// + /// The model to use or null if no metadata is available. + /// + public IEdmModel Model + { + get + { + return this.model; + } + } + + /// + /// The optional URL converter to perform custom URL conversion for URLs read from the payload. + /// + public IODataPayloadUriConverter PayloadUriConverter + { + get + { + return this.payloadUriConverter; + } + } + + /// + /// The optional dependency injection container to get related services for message reading. + /// + internal IServiceProvider Container + { + get + { + return this.container; + } + } + + /// + /// The type resolver to use. + /// + internal EdmTypeResolver EdmTypeResolver + { + get + { + return this.edmTypeResolver; + } + } + + /// + /// The payload value converter to use. + /// + internal ODataPayloadValueConverter PayloadValueConverter + { + get + { + return this.payloadValueConverter; + } + } + + /// + /// The ODataSimplifiedOptions used in reader. + /// + internal ODataSimplifiedOptions ODataSimplifiedOptions + { + get + { + return this.odataSimplifiedOptions; + } + } + + /// + /// IDisposable.Dispose() implementation to cleanup unmanaged resources of the context. + /// + public void Dispose() + { + if (this.disposed) + { + return; + } + + this.Dispose(true); + GC.SuppressFinalize(this); + this.disposed = true; + } + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// The newly created . + public virtual ODataReader CreateResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// Task which when completed returns the newly created . + public virtual Task CreateResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Creates an to read a delta resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// The newly created . + public virtual ODataReader CreateDeltaResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a delta resource set. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// Task which when completed returns the newly created . + public virtual Task CreateDeltaResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Creates an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// The newly created . + public virtual ODataReader CreateResourceReader(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Resource); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// Task which when completed returns the newly created . + public virtual Task CreateResourceReaderAsync(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Resource); + } +#endif + + /// + /// Create a . + /// + /// The expected type reference for the items in the collection. + /// The newly created . + public virtual ODataCollectionReader CreateCollectionReader(IEdmTypeReference expectedItemTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Collection); + } + +#if PORTABLELIB + /// + /// Asynchronously create a . + /// + /// The expected type reference for the items in the collection. + /// Task which when completed returns the newly created . + public virtual Task CreateCollectionReaderAsync(IEdmTypeReference expectedItemTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Collection); + } +#endif + + /// + /// Read the EDM structural property from the input and + /// return an representing the read property. + /// + /// The producing the property to be read. + /// The expected type reference of the property to read. + /// An representing the read property. + public virtual ODataProperty ReadProperty(IEdmStructuralProperty edmStructuralProperty, IEdmTypeReference expectedPropertyTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Property); + } + +#if PORTABLELIB + /// + /// Asynchronously read the EDM structural property from the input and + /// return an representing the read property. + /// + /// The producing the property to be read. + /// The expected type reference of the property to read. + /// Task which when completed returns an representing the read property. + public virtual Task ReadPropertyAsync(IEdmStructuralProperty edmStructuralProperty, IEdmTypeReference expectedPropertyTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Property); + } +#endif + + /// + /// Read a top-level error. + /// + /// An representing the read error. + public virtual ODataError ReadError() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } + +#if PORTABLELIB + /// + /// Asynchronously read a top-level error. + /// + /// Task which when completed returns an representing the read error. + public virtual Task ReadErrorAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } +#endif + + /// + /// Creates an to read a resource in a Uri operation parameter. + /// + /// The navigation source we are going to read resources for. + /// The expected resource type for the resource to be read. + /// The newly created . + public virtual ODataReader CreateUriParameterResourceReader(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Parameter); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource in a Uri operation parameter. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// Task which when completed returns the newly created . + public virtual Task CreateUriParameterResourceReaderAsync(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Parameter); + } +#endif + + /// + /// Creates an to read a resource set in a Uri operation parameter. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// The newly created . + public virtual ODataReader CreateUriParameterResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set in a Uri operation parameter. + /// + /// The entity set we are going to read resources for. + /// The expected structured type for the items in the resource set. + /// Task which when completed returns the newly created . + public virtual Task CreateUriParameterResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + /// + /// Create a . + /// + /// The operation whose parameters are being read. + /// The newly created . + public virtual ODataParameterReader CreateParameterReader(IEdmOperation operation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Parameter); + } + +#if PORTABLELIB + /// + /// Asynchronously create a . + /// + /// The operation whose parameters are being read. + /// Task which when completed returns the newly created . + public virtual Task CreateParameterReaderAsync(IEdmOperation operation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Parameter); + } +#endif + + /// + /// Create an . + /// + /// The newly created . + internal virtual ODataAsynchronousReader CreateAsynchronousReader() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Asynchronous); + } + +#if PORTABLELIB + /// + /// Asynchronously create an . + /// + /// Task which when completed returns the newly created . + internal virtual Task CreateAsynchronousReaderAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Asynchronous); + } +#endif + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base entity type for the entries in the delta response. + /// The newly created . + internal virtual ODataDeltaReader CreateDeltaReader(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base entity type for the entries in the delta response. + /// Task which when completed returns the newly created . + internal virtual Task CreateDeltaReaderAsync(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Create a . + /// + /// The newly created . + /// + /// Since we don't want to support batch format extensibility (at least not yet) this method should remain internal. + /// + internal virtual ODataBatchReader CreateBatchReader() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Batch); + } + +#if PORTABLELIB + /// + /// Asynchronously create a . + /// + /// Task which when completed returns the newly created . + /// + /// Since we don't want to support batch format extensibility (at least not yet) this method should remain internal. + /// + internal virtual Task CreateBatchReaderAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Batch); + } +#endif + + /// + /// Read a service document. + /// This method reads the service document from the input and returns + /// an that represents the read service document. + /// + /// An representing the read service document. + internal virtual ODataServiceDocument ReadServiceDocument() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ServiceDocument); + } + +#if PORTABLELIB + /// + /// Asynchronously read a service document. + /// This method reads the service document from the input and returns + /// an that represents the read service document. + /// + /// Task which when completed returns an representing the read service document. + internal virtual Task ReadServiceDocumentAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ServiceDocument); + } +#endif + + /// + /// Read a metadata document. + /// This method reads the metadata document from the input and returns + /// an that represents the read metadata document. + /// + /// The function to load referenced model xml. If null, will stop loading the referenced models. Normally it should throw no exception. + /// An representing the read metadata document. + internal virtual IEdmModel ReadMetadataDocument(Func getReferencedModelReaderFunc) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.MetadataDocument); + } + + /// + /// Read a set of top-level entity reference links. + /// + /// An representing the read links. + internal virtual ODataEntityReferenceLinks ReadEntityReferenceLinks() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLinks); + } + +#if PORTABLELIB + /// + /// Asynchronously read a set of top-level entity reference links. + /// + /// Task which when completed returns an representing the read links. + internal virtual Task ReadEntityReferenceLinksAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLinks); + } +#endif + + /// + /// Read a top-level entity reference link. + /// + /// An representing the read entity reference link. + internal virtual ODataEntityReferenceLink ReadEntityReferenceLink() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLink); + } + +#if PORTABLELIB + /// + /// Asynchronously read a top-level entity reference link. + /// + /// Task which when completed returns an representing the read entity reference link. + internal virtual Task ReadEntityReferenceLinkAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLink); + } +#endif + + /// + /// Read a top-level value. + /// + /// The expected type reference for the value to be read; null if no expected type is available. + /// An representing the read value. + internal virtual object ReadValue(IEdmPrimitiveTypeReference expectedPrimitiveTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Value); + } + +#if PORTABLELIB + /// + /// Asynchronously read a top-level value. + /// + /// The expected type reference for the value to be read; null if no expected type is available. + /// Task which when completed returns an representing the read value. + internal virtual Task ReadValueAsync(IEdmPrimitiveTypeReference expectedPrimitiveTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Value); + } +#endif + + /// + /// Check if the object has been disposed. + /// + /// If the object has already been disposed. + internal void VerifyNotDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException(this.GetType().FullName); + } + } + + /// + /// Asserts that the input context was created for synchronous operation. + /// + [Conditional("DEBUG")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Needs to access this in debug only.")] + internal void AssertSynchronous() + { +#if DEBUG + Debug.Assert(this.synchronous, "The method should only be called on a synchronous input context."); +#endif + } + + /// + /// Asserts that the input context was created for asynchronous operation. + /// + [Conditional("DEBUG")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Needs to access this in debug only.")] + internal void AssertAsynchronous() + { +#if DEBUG + Debug.Assert(!this.synchronous, "The method should only be called on an asynchronous input context."); +#endif + } + + /// + /// Creates a new instance of a duplicate property names checker. + /// + /// The newly created instance of duplicate property names checker. + internal PropertyAndAnnotationCollector CreatePropertyAndAnnotationCollector() + { + return messageReaderSettings.Validator.CreatePropertyAndAnnotationCollector(); + } + + /// + /// Method to use the custom URL resolver to resolve a base URI and a payload URI. + /// This method returns null if not custom resolution is desired. + /// If the method returns a non-null URL that value will be used without further validation. + /// + /// The (optional) base URI to use for the resolution. + /// The URI read from the payload. + /// + /// A instance that reflects the custom resolution of the method arguments + /// into a URL or null if no custom resolution is desired; in that case the default resolution is used. + /// + internal Uri ResolveUri(Uri baseUri, Uri payloadUri) + { + Debug.Assert(payloadUri != null, "uri != null"); + + if (this.PayloadUriConverter != null) + { + return this.PayloadUriConverter.ConvertPayloadUri(baseUri, payloadUri); + } + + return null; + } + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected virtual void Dispose(bool disposing) + { + } + + /// + /// Creates an exception which reports that the specified payload kind if not support by this format. + /// + /// The payload kind which is not supported. + /// An exception to throw. + private ODataException CreatePayloadKindNotSupportedException(ODataPayloadKind payloadKind) + { + return new ODataException(Strings.ODataInputContext_UnsupportedPayloadKindForFormat(this.format.ToString(), payloadKind.ToString())); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInstanceAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInstanceAnnotation.cs new file mode 100644 index 0000000..441b439 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataInstanceAnnotation.cs @@ -0,0 +1,94 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Xml; + using Microsoft.OData.JsonLight; + + /// + /// Represents an instance annotation. + /// + public sealed class ODataInstanceAnnotation : ODataAnnotatable + { + /// + /// Constructs a new instance. + /// + /// The name of the instance annotation. + /// The value of the instance annotation. + public ODataInstanceAnnotation(string name, ODataValue value) + : this(name, value, false) + { + } + + /// + /// Constructs a new instance. + /// + /// The name of the instance annotation. + /// The value of the instance annotation. + /// If the name is not for built-in OData annotation. + internal ODataInstanceAnnotation(string annotationName, ODataValue annotationValue, bool isCustomAnnotation) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(annotationName, "annotationName"); + if (!isCustomAnnotation && ODataAnnotationNames.IsODataAnnotationName(annotationName)) + { + // isCustomAnnotation==true includes '@odata.', which won't cause the below exception. + throw new ArgumentException(Strings.ODataInstanceAnnotation_ReservedNamesNotAllowed(annotationName, JsonLightConstants.ODataAnnotationNamespacePrefix)); + } + + ValidateName(annotationName); + ValidateValue(annotationValue); + this.Name = annotationName; + this.Value = annotationValue; + } + + /// + /// Instance annotation name. + /// + public string Name { get; private set; } + + /// + /// Instance annotation value. + /// + public ODataValue Value { get; private set; } + + /// + /// Validates that the given is a valid instance annotation name. + /// + /// Name to validate. + internal static void ValidateName(string name) + { + if (name.IndexOf('.') < 0 || name[0] == '.' || name[name.Length - 1] == '.') + { + throw new ArgumentException(Strings.ODataInstanceAnnotation_NeedPeriodInName(name)); + } + + try + { + XmlConvert.VerifyNCName(name); + } + catch (XmlException e) + { + throw new ArgumentException(Strings.ODataInstanceAnnotation_BadTermName(name), e); + } + } + + /// + /// Validates the given is a valid instance annotation value. + /// + /// Value to validate. + internal static void ValidateValue(ODataValue value) + { + ExceptionUtils.CheckArgumentNotNull(value, "value"); + + if (value is ODataStreamReferenceValue) + { + throw new ArgumentException(Strings.ODataInstanceAnnotation_ValueCannotBeODataStreamReferenceValue, "value"); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataItem.cs new file mode 100644 index 0000000..a4a3023 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataItem.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for deltaResourceSetlicense information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Base class for Resource Set and Resource. + /// + public abstract class ODataItem : ODataAnnotatable + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataJsonDateTimeFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataJsonDateTimeFormat.cs new file mode 100644 index 0000000..51f019f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataJsonDateTimeFormat.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Enumeration describing the various serialization formats for dates in JSON + /// + internal enum ODataJsonDateTimeFormat + { + /// + /// Represents a DateTime value in the OData format of \/Date(ticksrepresentingdatetime)\/ + /// + ODataDateTime, + + /// + /// Represents a DateTime value in the ISO 8601 format of YYYY-MM-DDThh:mm:ss.sTZD eg 1997-07-16T19:20:30.45+01:00 + /// + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", Justification = "ISO is a standards body and should be represented as all-uppercase in the API.")] + ISO8601DateTime + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataLibraryCompatibility.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataLibraryCompatibility.cs new file mode 100644 index 0000000..1ee0074 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataLibraryCompatibility.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.OData +{ + /// + /// Library compatibility levels. + /// + [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] + public enum ODataLibraryCompatibility + { + /// + /// Version 6.x + /// + Version6 = 060000, + + /// + /// Version 7.x + /// + Version7 = 070000, + + /// + /// The latest version of the library. + /// + Latest = int.MaxValue + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaType.cs new file mode 100644 index 0000000..a1a41ac --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaType.cs @@ -0,0 +1,233 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + #endregion Namespaces + + /// + /// Class representing a media type definition. + /// + [DebuggerDisplay("MediaType [{ToText()}]")] + public sealed class ODataMediaType + { + /// Parameters specified on the media type. + private readonly IEnumerable> parameters; + + /// Sub-type specification (for example, 'plain'). + private readonly string subType; + + /// Type specification (for example, 'text'). + private readonly string type; + + /// + /// Initializes a new read-only instance. + /// + /// Type specification (for example, 'text'). + /// Sub-type specification (for example, 'plain'). + public ODataMediaType(string type, string subType) + : this(type, subType, null) + { + } + + /// + /// Initializes a new read-only instance. + /// + /// Type specification (for example, 'text'). + /// Sub-type specification (for example, 'plain'). + /// Parameters specified on the media type. + public ODataMediaType(string type, string subType, IEnumerable> parameters) + { + Debug.Assert(type != null, "type != null"); + Debug.Assert(subType != null, "subType != null"); + + this.type = type; + this.subType = subType; + this.parameters = parameters; + } + + /// + /// Initializes a new read-only instance. + /// + /// Type specification (for example, 'text'). + /// Sub-type specification (for example, 'plain'). + /// The parameter specified on the media type. + internal ODataMediaType(string type, string subType, KeyValuePair parameter) + : this(type, subType, new[] { parameter }) + { + } + + /// Returns the subtype part of the media type. + public string SubType + { + get + { + return this.subType; + } + } + + /// Returns the type part of the media type. + public string Type + { + get + { + return this.type; + } + } + + /// media type parameters + public IEnumerable> Parameters + { + get + { + return this.parameters; + } + } + + /// Returns the full media type in standard type/subtype form, without parameters. + internal string FullTypeName + { + get + { + return this.type + "/" + this.subType; + } + } + + /// + /// Selects the encoding appropriate for this media type specification + /// (possibly null). + /// + /// + /// The encoding explicitly defined on the media type specification, or + /// the default encoding for well-known media types. + /// + /// + /// As per http://tools.ietf.org/html/rfc2616#section-3.7, the type, + /// subtype and parameter name attributes are case-insensitive. + /// + internal Encoding SelectEncoding() + { + if (this.parameters != null) + { + foreach (string encodingName in + this.parameters.Where(parameter => + HttpUtils.CompareMediaTypeParameterNames(ODataConstants.Charset, parameter.Key)) + .Select(parameter => parameter.Value.Trim()) + .Where(encodingName => encodingName.Length > 0)) + { + return EncodingFromName(encodingName); + } + } + + // Select the default encoding for this media type. + if (HttpUtils.CompareMediaTypeNames(MimeConstants.MimeTextType, this.type)) + { + // HTTP 3.7.1 Canonicalization and Text Defaults + // "text" subtypes default to ISO-8859-1 + // + // Unless the subtype is XML, in which case we should default + // to us-ascii. Instead we return null, to let the encoding + // in the PI win (http://tools.ietf.org/html/rfc3023#section-3.1) + return HttpUtils.CompareMediaTypeNames(MimeConstants.MimeXmlSubType, this.subType) ? null : MediaTypeUtils.MissingEncoding; + } + + if (HttpUtils.CompareMediaTypeNames(MimeConstants.MimeApplicationType, this.type) && + HttpUtils.CompareMediaTypeNames(MimeConstants.MimeJsonSubType, this.subType)) + { + // http://tools.ietf.org/html/rfc4627#section-3 + // The default encoding is UTF-8. + return MediaTypeUtils.FallbackEncoding; + } + + return null; + } + + /// + /// Converts the current to a string representation suitable for use in a content-type header. + /// + /// The string representation of media type. + internal string ToText() + { + return ToText(null); + } + + /// + /// Converts the current to a string representation suitable for use in a content-type header. + /// + /// The encoding to use when converting the media type into text. + /// The string representation of the current media type. + internal string ToText(Encoding encoding) + { + // TODO: for now we include all the parameters since we know that we will not have accept parameters (after the quality value) + // that needed to be ignored. + if (this.parameters == null || !this.parameters.Any()) + { + string typeName = this.FullTypeName; + if (encoding != null) + { + typeName = string.Concat(typeName, ";", ODataConstants.Charset, "=", encoding.WebName); + } + + return typeName; + } + + StringBuilder builder = new StringBuilder(this.FullTypeName); + foreach (KeyValuePair parameter in this.parameters) + { + // ignore the char set if specified in the parameters; we write the one from the encoding + if (HttpUtils.CompareMediaTypeParameterNames(ODataConstants.Charset, parameter.Key)) + { + continue; + } + + builder.Append(";"); + builder.Append(parameter.Key); + builder.Append("="); + builder.Append(parameter.Value); + } + + // write the encoding (if any) + if (encoding != null) + { + builder.Append(";"); + builder.Append(ODataConstants.Charset); + builder.Append("="); + builder.Append(encoding.WebName); + } + + return builder.ToString(); + } + + /// Gets the named encoding if specified. + /// Name (possibly null or empty). + /// + /// The named encoding if specified; the encoding for HTTP missing + /// charset specification otherwise. + /// + /// + /// See http://tools.ietf.org/html/rfc2616#section-3.4.1 for details. + /// + private static Encoding EncodingFromName(string name) + { + Debug.Assert(!string.IsNullOrEmpty(name), "!string.IsNullOrEmpty(name)"); + Debug.Assert(name.Trim() == name, "Should already be trimmed."); + + Encoding result = HttpUtils.GetEncodingFromCharsetName(name); + if (result == null) + { + throw new ODataException(Strings.MediaType_EncodingNotSupported(name)); + } + + return result; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeFormat.cs new file mode 100644 index 0000000..9bb6c6c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeFormat.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + + + #endregion Namespaces + + /// + /// A helper class to associate a with a media type. + /// + public sealed class ODataMediaTypeFormat + { + /// + /// Constructor for + /// + /// MediaType to be used. + /// Associated format. + public ODataMediaTypeFormat(ODataMediaType mediaType, ODataFormat format) + { + this.MediaType = mediaType; + this.Format = format; + } + + /// The media type. + public ODataMediaType MediaType + { + get; + internal set; + } + + /// + /// The for this media type. + /// + public ODataFormat Format + { + get; + internal set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeResolver.cs new file mode 100644 index 0000000..31dda15 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMediaTypeResolver.cs @@ -0,0 +1,177 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Linq; + #endregion Namespaces + + /// + /// Class with the responsibility of resolving media types (MIME types) into formats and payload kinds. + /// + public class ODataMediaTypeResolver + { + /// + /// The set of payload kinds which are supported for the JSON formats. + /// + private static readonly HashSet JsonPayloadKindSet = new HashSet + { + ODataPayloadKind.ResourceSet, + ODataPayloadKind.Resource, + ODataPayloadKind.Property, + ODataPayloadKind.EntityReferenceLink, + ODataPayloadKind.EntityReferenceLinks, + ODataPayloadKind.Collection, + ODataPayloadKind.ServiceDocument, + ODataPayloadKind.Error, + ODataPayloadKind.Parameter, + ODataPayloadKind.Delta, + ODataPayloadKind.IndividualProperty + }; + + /// + /// MediaTypeResolver without atom support + /// + private static readonly ODataMediaTypeResolver MediaTypeResolver = new ODataMediaTypeResolver(); + + /// + /// The special map of payload kind and supported media type. + /// + private static IDictionary> SpecialMediaTypeFormat = new Dictionary> + { + { + ODataPayloadKind.Batch, + new[] { new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeMultipartType, MimeConstants.MimeMixedSubType), ODataFormat.Batch) } + }, + { + ODataPayloadKind.Value, + new[] { new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeTextType, MimeConstants.MimePlainSubType), ODataFormat.RawValue) } + }, + { + ODataPayloadKind.BinaryValue, + new[] { new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeOctetStreamSubType), ODataFormat.RawValue) } + }, + { + ODataPayloadKind.MetadataDocument, + new[] { new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeXmlSubType), ODataFormat.Metadata) } + }, + { + ODataPayloadKind.Asynchronous, + new[] { new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeHttpSubType), ODataFormat.RawValue) } + } + }; + + /// + /// Array of supported media types and formats for JSON related payload kind. + /// + private static IEnumerable JsonMediaTypeFormats = SetJsonLightMediaTypes(); + + /// + /// Gets the supported media types and formats for the given payload kind. + /// + /// The payload kind to get media types for. + /// Media type-format pairs, sorted by priority. + public virtual IEnumerable GetMediaTypeFormats(ODataPayloadKind payloadKind) + { + if (JsonPayloadKindSet.Contains(payloadKind)) + { + return JsonMediaTypeFormats; + } + else if (payloadKind == ODataPayloadKind.Batch) + { + return SpecialMediaTypeFormat[payloadKind].Concat(JsonMediaTypeFormats); + } + else + { + return SpecialMediaTypeFormat[payloadKind]; + } + } + + internal static ODataMediaTypeResolver GetMediaTypeResolver(IServiceProvider container) + { + if (container == null) + { + return MediaTypeResolver; + } + + return container.GetRequiredService(); + } + + private static IEnumerable SetJsonLightMediaTypes() + { + var minimal = new KeyValuePair(MimeConstants.MimeMetadataParameterName, MimeConstants.MimeMetadataParameterValueMinimal); + var full = new KeyValuePair(MimeConstants.MimeMetadataParameterName, MimeConstants.MimeMetadataParameterValueFull); + var none = new KeyValuePair(MimeConstants.MimeMetadataParameterName, MimeConstants.MimeMetadataParameterValueNone); + + var streamTrue = new KeyValuePair(MimeConstants.MimeStreamingParameterName, MimeConstants.MimeParameterValueTrue); + var streamFalse = new KeyValuePair(MimeConstants.MimeStreamingParameterName, MimeConstants.MimeParameterValueFalse); + + var ieeeFalse = new KeyValuePair(MimeConstants.MimeIeee754CompatibleParameterName, MimeConstants.MimeParameterValueFalse); + var ieeeTrue = new KeyValuePair(MimeConstants.MimeIeee754CompatibleParameterName, MimeConstants.MimeParameterValueTrue); + + return new List + { + // minimal + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, streamTrue, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, streamTrue, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, streamTrue }), ODataFormat.Json), + + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, streamFalse, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, streamFalse, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, streamFalse }), ODataFormat.Json), + + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { minimal }), ODataFormat.Json), + + // full + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, streamTrue, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, streamTrue, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, streamTrue }), ODataFormat.Json), + + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, streamFalse, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, streamFalse, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, streamFalse }), ODataFormat.Json), + + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { full }), ODataFormat.Json), + + // none + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, streamTrue, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, streamTrue, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, streamTrue }), ODataFormat.Json), + + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, streamFalse, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, streamFalse, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, streamFalse }), ODataFormat.Json), + + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { none }), ODataFormat.Json), + + // without metadata + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { streamTrue, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { streamTrue, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { streamTrue }), ODataFormat.Json), + + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { streamFalse, ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { streamFalse, ieeeTrue }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { streamFalse }), ODataFormat.Json), + + // without metadata and stream + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { ieeeFalse }), ODataFormat.Json), + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType, new[] { ieeeTrue }), ODataFormat.Json), + + // without parameters + new ODataMediaTypeFormat(new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType), ODataFormat.Json) + }; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessage.cs new file mode 100644 index 0000000..c7a8e2d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessage.cs @@ -0,0 +1,342 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Base class for the internal wrappers around IODataRequestMessageAsync and IODataResponseMessageAsync. + /// + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "This class does not own the BufferingReadStream instance.")] + internal abstract class ODataMessage + { + /// true if the message is being written; false when it is read. + private readonly bool writing; + + /// true if the stream returned should be disposed calls. + private readonly bool enableMessageStreamDisposal; + + /// The maximum size of the message in bytes (or null if no maximum applies). + private readonly long maxMessageSize; + + /// true to use a buffering read stream wrapper around the actual message stream; otherwise false. + private bool? useBufferingReadStream; + + /// The buffering read stream used for payload kind detection; only non-null inside of payload kind detection. + private BufferingReadStream bufferingReadStream; + + /// + /// Constructs a new ODataMessage. + /// + /// true if the message is being written; false when it is read. + /// true if the stream returned should be disposed calls. + /// The maximum size of the message in bytes (or a negative value if no maximum applies). + protected ODataMessage(bool writing, bool enableMessageStreamDisposal, long maxMessageSize) + { + this.writing = writing; + this.enableMessageStreamDisposal = enableMessageStreamDisposal; + this.maxMessageSize = maxMessageSize; + } + + /// + /// Returns an enumerable over all the headers for this message. + /// + public abstract IEnumerable> Headers + { + // TODO: do we want to impose a certain order of the headers? + get; + } + + /// + /// true to use a buffering read stream wrapper around the actual message stream; otherwise false. + /// + protected internal BufferingReadStream BufferingReadStream + { + get + { + return this.bufferingReadStream; + } + } + + /// + /// true to use a buffering read stream wrapper around the actual message stream; otherwise false. + /// + protected internal bool? UseBufferingReadStream + { + get + { + return this.useBufferingReadStream; + } + + set + { + Debug.Assert(!this.writing, "UseBufferingReadStream should only be set when reading."); + this.useBufferingReadStream = value; + } + } + + /// + /// Returns a value of an HTTP header. + /// + /// The name of the header to get. + /// The value of the HTTP header, or null if no such header was present on the message. + public abstract string GetHeader(string headerName); + + /// + /// Sets the value of an HTTP header. + /// + /// The name of the header to set. + /// The value for the header with name . + public abstract void SetHeader(string headerName, string headerValue); + + /// + /// Get the stream backing this message. + /// + /// The stream for this message. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Intentionally a method.")] + public abstract Stream GetStream(); + +#if PORTABLELIB + /// + /// Asynchronously get the stream backing this message. + /// + /// The stream for this message. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Intentionally a method.")] + public abstract Task GetStreamAsync(); +#endif + + /// + /// Queries the message for the specified interface type. + /// + /// The type of the interface to query for. + /// The instance of the interface asked for or null if it was not implemented by the message. + /// We need this method since the input contexts don't get access to the actual instance of the message given to us by the user + /// instead they get this class, and thus they can't just cast to get to the interface they want. + internal abstract TInterface QueryInterface() where TInterface : class; + + /// + /// Synchronously get the stream backing this message. + /// + /// A function that returns the stream backing the message. + /// true if the message is a request message; false for a response message. + /// The backing the message. + protected internal Stream GetStream(Func messageStreamFunc, bool isRequest) + { + // Check whether we have an existing buffering read stream when reading + if (!this.writing) + { + BufferingReadStream existingBufferingReadStream = this.TryGetBufferingReadStream(); + if (existingBufferingReadStream != null) + { + Debug.Assert(this.useBufferingReadStream.HasValue, "UseBufferingReadStream must have been set."); + return existingBufferingReadStream; + } + } + + // Get the message stream + Stream messageStream = messageStreamFunc(); + ValidateMessageStream(messageStream, isRequest); + + // When reading, wrap the stream in a byte counting stream if a max message size was specified. + // When requested, wrap the stream in a non-disposing stream. + bool needByteCountingStream = !this.writing && this.maxMessageSize > 0; + if (!this.enableMessageStreamDisposal && needByteCountingStream) + { + messageStream = MessageStreamWrapper.CreateNonDisposingStreamWithMaxSize(messageStream, this.maxMessageSize); + } + else if (!this.enableMessageStreamDisposal) + { + messageStream = MessageStreamWrapper.CreateNonDisposingStream(messageStream); + } + else if (needByteCountingStream) + { + messageStream = MessageStreamWrapper.CreateStreamWithMaxSize(messageStream, this.maxMessageSize); + } + + // If a buffering read stream is required, create it now + if (!this.writing && this.useBufferingReadStream == true) + { + Debug.Assert(!this.writing, "The buffering read stream should only be used when reading."); + this.bufferingReadStream = new BufferingReadStream(messageStream); + messageStream = this.bufferingReadStream; + } + + return messageStream; + } + +#if PORTABLELIB + /// + /// Asynchronously get the stream backing this message. + /// + /// A function that returns a task for the stream backing the message. + /// true if the message is a request message; false for a response message. + /// A task that when completed returns the stream backing the message. + protected internal Task GetStreamAsync(Func> streamFuncAsync, bool isRequest) + { + // Check whether we have an existing buffering read stream when reading + if (!this.writing) + { + Stream existingBufferingReadStream = this.TryGetBufferingReadStream(); + Debug.Assert(!this.writing || existingBufferingReadStream == null, "The buffering read stream should only be used when reading."); + if (existingBufferingReadStream != null) + { + Debug.Assert(this.useBufferingReadStream.HasValue, "UseBufferingReadStream must have been set."); + return TaskUtils.GetCompletedTask(existingBufferingReadStream); + } + } + + Task task = streamFuncAsync(); + ValidateMessageStreamTask(task, isRequest); + + // Wrap it in a non-disposing stream if requested + task = task.FollowOnSuccessWith( + streamTask => + { + Stream messageStream = streamTask.Result; + ValidateMessageStream(messageStream, isRequest); + + // When reading, wrap the stream in a byte counting stream if a max message size was specified. + // When requested, wrap the stream in a non-disposing stream. + bool needByteCountingStream = !this.writing && this.maxMessageSize > 0; + if (!this.enableMessageStreamDisposal && needByteCountingStream) + { + messageStream = MessageStreamWrapper.CreateNonDisposingStreamWithMaxSize(messageStream, this.maxMessageSize); + } + else if (!this.enableMessageStreamDisposal) + { + messageStream = MessageStreamWrapper.CreateNonDisposingStream(messageStream); + } + else if (needByteCountingStream) + { + messageStream = MessageStreamWrapper.CreateStreamWithMaxSize(messageStream, this.maxMessageSize); + } + + return messageStream; + }); + + // When we are reading, also buffer the input stream + if (!this.writing) + { + task = task + .FollowOnSuccessWithTask( + streamTask => + { + return BufferedReadStream.BufferStreamAsync(streamTask.Result); + }) + .FollowOnSuccessWith( + streamTask => + { + BufferedReadStream bufferedReadStream = streamTask.Result; + return (Stream)bufferedReadStream; + }); + + // If requested also create a buffering stream for payload kind detection + if (this.useBufferingReadStream == true) + { + task = task.FollowOnSuccessWith( + streamTask => + { + Stream messageStream = streamTask.Result; + this.bufferingReadStream = new BufferingReadStream(messageStream); + messageStream = this.bufferingReadStream; + return messageStream; + }); + } + } + + return task; + } +#endif + + /// + /// Verifies that setting a header is allowed + /// + /// + /// We allow modifying the headers only if we are writing the message and we are not + /// detecting the payload kind. + /// + protected void VerifyCanSetHeader() + { + if (!this.writing) + { + throw new ODataException(Strings.ODataMessage_MustNotModifyMessage); + } + + Debug.Assert(this.bufferingReadStream == null, "The buffering stream should only be used when reading."); + } + + /// + /// Validates that a given message stream can be used. + /// + /// The stream to validate. + /// true if the message is a request message; false for a response message. + private static void ValidateMessageStream(Stream stream, bool isRequest) + { + if (stream == null) + { + string error = isRequest + ? Strings.ODataRequestMessage_MessageStreamIsNull + : Strings.ODataResponseMessage_MessageStreamIsNull; + throw new ODataException(error); + } + } + +#if PORTABLELIB + /// + /// Validates that a given task providing the message stream can be used. + /// + /// The task to validate. + /// true if the message is a request message; false for a response message. + private static void ValidateMessageStreamTask(Task streamTask, bool isRequest) + { + if (streamTask == null) + { + string error = isRequest + ? Strings.ODataRequestMessage_StreamTaskIsNull + : Strings.ODataResponseMessage_StreamTaskIsNull; + throw new ODataException(error); + } + } +#endif + + /// + /// Gets the buffering read stream if one is available; otherwise returns null. + /// + /// The currently being used or null if no buffering stream is currently being used. + private BufferingReadStream TryGetBufferingReadStream() + { + if (this.bufferingReadStream == null) + { + return null; + } + + // We already have a buffering read stream; reset it if necessary and return it; + // if the stream is not buffering anymore we are done with payload kind detection + // and don't need the buffering stream anymore - we start the actual reading now. + BufferingReadStream stream = this.bufferingReadStream; + if (this.bufferingReadStream.IsBuffering) + { + this.bufferingReadStream.ResetStream(); + } + else + { + this.bufferingReadStream = null; + } + + return stream; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageExtensions.cs new file mode 100644 index 0000000..b1c1bcd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageExtensions.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + /// + /// Extension methods to IODataRequestMessage and IODataResponseMessage. + /// + public static class ODataMessageExtensions + { + /// + /// Reads the OData-Version header from the and parses it. + /// If no OData-Version header is found it sets the default version to be used for reading. + /// + /// The message to get the OData-Version header from. + /// The default version to use if the header was not specified. + /// + /// The retrieved from the OData-Version header of the message. + /// The default version if none is specified in the header. + /// + public static ODataVersion GetODataVersion(this IODataResponseMessage message, ODataVersion defaultVersion) + { + // translation from IODataResponseMessage to ODataMessage to pass into GetODataVersion. + // we retain all of the data we need from the message, with a few extra data that aren't used in. + // GetODataVersion. Not ideal, but it works. + ODataMessage odataMessage = new ODataResponseMessage(message, false /*writing*/, false/*disableMessageStreamDisposal*/, Int64.MaxValue /*maxMessageSize*/); + return ODataUtilsInternal.GetODataVersion(odataMessage, defaultVersion); + } + + /// + /// Reads the OData-Version header from the and parses it. + /// If no OData-Version header is found it sets the default version to be used for reading. + /// + /// The message to get the OData-Version version header from. + /// The default version to use if the header was not specified. + /// + /// The retrieved from the OData-Version header of the message. + /// The default version if none is specified in the header. + /// + public static ODataVersion GetODataVersion(this IODataRequestMessage message, ODataVersion defaultVersion) + { + // translation from IODataResponseMessage to ODataMessage to pass into GetODataVersion. + // we retain all of the data we need from the message, with a few extra data that aren't used in. + // GetODataVersion. Not ideal, but it works. + ODataMessage odataMessage = new ODataRequestMessage(message, false /*writing*/, false/*disableMessageStreamDisposal*/, Int64.MaxValue /*maxMessageSize*/); + return ODataUtilsInternal.GetODataVersion(odataMessage, defaultVersion); + } + + /// + /// Gets the instance to get or set preferences on the "Prefer" header of the . + /// + /// The request message to get or set the "Prefer" header. + /// Returns the instance to get or set preferences on the "Prefer" header of the . + public static ODataPreferenceHeader PreferHeader(this IODataRequestMessage requestMessage) + { + ExceptionUtils.CheckArgumentNotNull(requestMessage, "requestMessage"); + return new ODataPreferenceHeader(requestMessage); + } + + /// + /// Gets the instance to get or set preferences on the "Preference-Applied" header of the . + /// + /// The response message to get or set the "Preference-Applied" header. + /// Returns the instance to get or set preferences on the "Preference-Applied" header of the . + public static ODataPreferenceHeader PreferenceAppliedHeader(this IODataResponseMessage responseMessage) + { + ExceptionUtils.CheckArgumentNotNull(responseMessage, "responseMessage"); + return new ODataPreferenceHeader(responseMessage); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageInfo.cs new file mode 100644 index 0000000..8d09547 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageInfo.cs @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.IO; + using System.Text; + using Microsoft.OData.Edm; + + /// + /// Class provides context informatation of certain + /// or + /// + public sealed class ODataMessageInfo + { + /// The parsed content type as . + public ODataMediaType MediaType { get; set; } + + /// The encoding specified in the charset parameter of contentType or the default encoding from MediaType. + public Encoding Encoding { get; set; } + + /// The for the payload. + public IEdmModel Model { get; set; } + + /// + /// Whether is dealing with response. + /// + public bool IsResponse { get; set; } + + /// + /// The optional URL converter to perform custom URL conversion for URLs read from the payload. + /// + public IODataPayloadUriConverter PayloadUriConverter { get; set; } + + /// + /// The optional dependency injection container to get related services for message writing. + /// + public IServiceProvider Container { get; set; } + + /// + /// Whether the message should be read or written asynchronously. + /// + public bool IsAsync { get; set; } + + /// + /// The message stream created by GetMessageStream or GetMessageAsync. + /// + public Stream MessageStream { get; set; } + + /// + /// The payload kind for the message, currently used by only. + /// + internal ODataPayloadKind PayloadKind { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageQuotas.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageQuotas.cs new file mode 100644 index 0000000..993fbdf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageQuotas.cs @@ -0,0 +1,113 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + #endregion Namespaces + + /// Quotas to use for limiting resource consumption when reading or writing OData messages. + public sealed class ODataMessageQuotas + { + /// The maximum number of top level query operations and changesets allowed in a single batch. + private int maxPartsPerBatch; + + /// The maximum number of operations allowed in a single changeset. + private int maxOperationsPerChangeset; + + /// The maximum depth of nesting allowed when reading or writing recursive payloads. + private int maxNestingDepth; + + /// The maximum number of bytes that should be read from the message. + private long maxReceivedMessageSize; + + /// Initializes a new instance of the class. + public ODataMessageQuotas() + { + this.maxPartsPerBatch = ODataConstants.DefaultMaxPartsPerBatch; + this.maxOperationsPerChangeset = ODataConstants.DefaultMaxOperationsPerChangeset; + this.maxNestingDepth = ODataConstants.DefaultMaxRecursionDepth; + this.maxReceivedMessageSize = ODataConstants.DefaultMaxReadMessageSize; + } + + /// Initializes a new instance of the class. + /// The instance to copy. + public ODataMessageQuotas(ODataMessageQuotas other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + + this.maxPartsPerBatch = other.maxPartsPerBatch; + this.maxOperationsPerChangeset = other.maxOperationsPerChangeset; + this.maxNestingDepth = other.maxNestingDepth; + this.maxReceivedMessageSize = other.maxReceivedMessageSize; + } + + /// Gets or sets the maximum number of top level query operations and changesets allowed in a single batch. + /// The maximum number of top level query operations and changesets allowed in a single batch. + public int MaxPartsPerBatch + { + get + { + return this.maxPartsPerBatch; + } + + set + { + ExceptionUtils.CheckIntegerNotNegative(value, "MaxPartsPerBatch"); + this.maxPartsPerBatch = value; + } + } + + /// Gets or sets the maximum number of operations allowed in a single changeset. + /// The maximum number of operations allowed in a single changeset. + public int MaxOperationsPerChangeset + { + get + { + return this.maxOperationsPerChangeset; + } + + set + { + ExceptionUtils.CheckIntegerNotNegative(value, "MaxOperationsPerChangeset"); + this.maxOperationsPerChangeset = value; + } + } + + /// Gets or sets the maximum depth of nesting allowed when reading or writing recursive payloads. + /// The maximum depth of nesting allowed when reading or writing recursive payloads. + public int MaxNestingDepth + { + get + { + return this.maxNestingDepth; + } + + set + { + ExceptionUtils.CheckIntegerPositive(value, "MaxNestingDepth"); + this.maxNestingDepth = value; + } + } + + /// Gets or sets the maximum number of bytes that should be read from the message. + /// The maximum number of bytes that should be read from the message. + public long MaxReceivedMessageSize + { + get + { + return this.maxReceivedMessageSize; + } + + set + { + ExceptionUtils.CheckLongPositive(value, "MaxReceivedMessageSize"); + this.maxReceivedMessageSize = value; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReader.cs new file mode 100644 index 0000000..6af0f3c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReader.cs @@ -0,0 +1,1517 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using System.Xml; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Reader class used to read all OData payloads (resources, resource sets, metadata documents, service documents, etc.). + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Main resource point for reader functionality")] + public sealed class ODataMessageReader : IDisposable + { + /// The message for which the message reader was created. + private readonly ODataMessage message; + + /// A flag indicating whether we are reading a request or a response message. + private readonly bool readingResponse; + + /// The message reader settings to use when reading the message payload. + private readonly ODataMessageReaderSettings settings; + + /// The model. Non-null if we do have metadata available. + private readonly IEdmModel model; + + /// The optional URL converter to perform custom URL conversion for URLs read from the payload. + private readonly IODataPayloadUriConverter payloadUriConverter; + + /// The optional dependency injection container to get related services for message reading. + private readonly IServiceProvider container; + + /// The resolver to use when determining an entity set's element type. + private readonly EdmTypeResolver edmTypeResolver; + + /// The media type resolver to use when interpreting the incoming content type. + private readonly ODataMediaTypeResolver mediaTypeResolver; + + /// Flag to ensure that only a single read method is called on the message reader. + private bool readMethodCalled; + + /// true if Dispose() has been called on this message reader, false otherwise. + private bool isDisposed; + + /// The input context used to read the message content. + private ODataInputContext inputContext; + + /// The payload kind of the payload to be read with this reader. + /// This field is set implicitly when one of the read (or reader creation) methods is called. + private ODataPayloadKind readerPayloadKind = ODataPayloadKind.Unsupported; + + /// The of the payload to be read with this reader. + /// This field is set implicitly when one of the read (or reader creation) methods is called. + private ODataFormat format; + + /// The parsed from the content type header. + /// This field is set implicitly when one of the read (or reader creation) methods is called. + private ODataMediaType contentType; + + /// The of the payload to be read with this reader. + /// This field is set implicitly when one of the read (or reader creation) methods is called. + private Encoding encoding; + + /// The context information for the message. + private ODataMessageInfo messageInfo; + + /// Creates a new for the given request message. + /// The request message for which to create the reader. + public ODataMessageReader(IODataRequestMessage requestMessage) + : this(requestMessage, null) + { + } + + /// Creates a new for the given request message and message reader settings. + /// The request message for which to create the reader. + /// The message reader settings to use for reading the message payload. + public ODataMessageReader(IODataRequestMessage requestMessage, ODataMessageReaderSettings settings) + : this(requestMessage, settings, null) + { + } + + /// + /// Creates a new ODataMessageReader for the given request message and message reader settings. + /// + /// The request message for which to create the reader. + /// The message reader settings to use for reading the message payload. + /// The model to use. + public ODataMessageReader(IODataRequestMessage requestMessage, ODataMessageReaderSettings settings, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(requestMessage, "requestMessage"); + + this.container = GetContainer(requestMessage); + this.settings = ODataMessageReaderSettings.CreateReaderSettings(this.container, settings); + ReaderValidationUtils.ValidateMessageReaderSettings(this.settings, /*readingResponse*/ false); + + this.readingResponse = false; + this.message = new ODataRequestMessage(requestMessage, /*writing*/ false, this.settings.EnableMessageStreamDisposal, this.settings.MessageQuotas.MaxReceivedMessageSize); + this.payloadUriConverter = requestMessage as IODataPayloadUriConverter; + this.mediaTypeResolver = ODataMediaTypeResolver.GetMediaTypeResolver(this.container); + + // Validate OData version against request message. + ODataVersion requestedVersion = ODataUtilsInternal.GetODataVersion(this.message, this.settings.MaxProtocolVersion); + if (requestedVersion > this.settings.MaxProtocolVersion) + { + throw new ODataException(Strings.ODataUtils_MaxProtocolVersionExceeded(ODataUtils.ODataVersionToString(requestedVersion), ODataUtils.ODataVersionToString(this.settings.MaxProtocolVersion))); + } + + this.model = model ?? GetModel(this.container); + this.edmTypeResolver = new EdmTypeReaderResolver(this.model, this.settings.ClientCustomTypeResolver); + } + + /// Creates a new for the given response message. + /// The response message for which to create the reader. + public ODataMessageReader(IODataResponseMessage responseMessage) + : this(responseMessage, null) + { + } + + /// Creates a new for the given response message and message reader settings. + /// The response message for which to create the reader. + /// The message reader settings to use for reading the message payload. + public ODataMessageReader(IODataResponseMessage responseMessage, ODataMessageReaderSettings settings) + : this(responseMessage, settings, null) + { + } + + /// + /// Creates a new ODataMessageReader for the given response message and message reader settings. + /// + /// The response message for which to create the reader. + /// The message reader settings to use for reading the message payload. + /// The model to use. + public ODataMessageReader(IODataResponseMessage responseMessage, ODataMessageReaderSettings settings, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(responseMessage, "responseMessage"); + + this.container = GetContainer(responseMessage); + this.settings = ODataMessageReaderSettings.CreateReaderSettings(this.container, settings); + ReaderValidationUtils.ValidateMessageReaderSettings(this.settings, /*readingResponse*/ true); + + this.readingResponse = true; + this.message = new ODataResponseMessage(responseMessage, /*writing*/ false, this.settings.EnableMessageStreamDisposal, this.settings.MessageQuotas.MaxReceivedMessageSize); + this.payloadUriConverter = responseMessage as IODataPayloadUriConverter; + this.mediaTypeResolver = ODataMediaTypeResolver.GetMediaTypeResolver(this.container); + + // Validate OData version against response message. + ODataVersion requestedVersion = ODataUtilsInternal.GetODataVersion(this.message, this.settings.MaxProtocolVersion); + if (requestedVersion > this.settings.MaxProtocolVersion) + { + throw new ODataException(Strings.ODataUtils_MaxProtocolVersionExceeded(ODataUtils.ODataVersionToString(requestedVersion), ODataUtils.ODataVersionToString(this.settings.MaxProtocolVersion))); + } + + this.model = model ?? GetModel(this.container); + this.edmTypeResolver = new EdmTypeReaderResolver(this.model, this.settings.ClientCustomTypeResolver); + + // If the Preference-Applied header on the response message contains an annotation filter, we set the filter + // to the reader settings if it's not already set, so that we would only read annotations that satisfy the filter. + string annotationFilter = responseMessage.PreferenceAppliedHeader().AnnotationFilter; + if (this.settings.ShouldIncludeAnnotation == null && !string.IsNullOrEmpty(annotationFilter)) + { + this.settings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter(annotationFilter); + } + } + + /// + /// The message reader settings to use when reading the message payload. + /// + internal ODataMessageReaderSettings Settings + { + get + { + return this.settings; + } + } + + /// Determines the potential payload kinds and formats of the payload being read and returns it. + /// The set of potential payload kinds and formats for the payload being read by this reader. + /// When this method is called it first analyzes the content type and determines whether there + /// are multiple matching payload kinds registered for the message's content type. If there are, it then + /// runs the payload kind detection on all formats that have a matching payload kind registered. + /// Note that this method can return multiple results if a payload is valid for multiple payload kinds but + /// will always at most return a single result per payload kind. + /// + public IEnumerable DetectPayloadKind() + { + IEnumerable payloadKindsFromContentType; + if (this.TryGetSinglePayloadKindResultFromContentType(out payloadKindsFromContentType)) + { + return payloadKindsFromContentType; + } + + // Otherwise we have to do sniffing + List detectedPayloadKinds = new List(); + try + { + // Group the payload kinds by format so we call the payload kind detection method only + // once per format. + IEnumerable> payloadKindFromContentTypeGroups = + payloadKindsFromContentType.GroupBy(kvp => kvp.Format); + + foreach (IGrouping payloadKindGroup in payloadKindFromContentTypeGroups) + { + ODataMessageInfo messageInfo = this.GetOrCreateMessageInfo(this.message.GetStream(), false); + + // Call the payload kind detection code on the format + IEnumerable detectionResult = payloadKindGroup.Key.DetectPayloadKind(messageInfo, this.settings); + + if (detectionResult != null) + { + foreach (ODataPayloadKind kind in detectionResult) + { + // Only include the payload kinds that we expect + if (payloadKindsFromContentType.Any(pk => pk.PayloadKind == kind)) + { + Debug.Assert(!detectedPayloadKinds.Any(dpk => dpk.PayloadKind == kind), "Each kind must appear at most once."); + detectedPayloadKinds.Add(new ODataPayloadKindDetectionResult(kind, payloadKindGroup.Key)); + } + } + } + } + } + finally + { + // We are done sniffing; stop buffering + this.message.UseBufferingReadStream = false; + this.message.BufferingReadStream.StopBuffering(); + } + + // Always sort by payload kind to guarantee stable order of results in case clients rely on it + detectedPayloadKinds.Sort(this.ComparePayloadKindDetectionResult); + + return detectedPayloadKinds; + } + +#if PORTABLELIB + /// Determines the potential payload kinds and formats of the payload being read and returns it. + /// The set of potential payload kinds and formats for the payload being read by this reader. + /// When this method is called it first analyzes the content type and determines whether there + /// are multiple matching payload kinds registered for the message's content type. If there are, it then + /// runs the payload kind detection on all formats that have a matching payload kind registered. + /// Note that this method can return multiple results if a payload is valid for multiple payload kinds but + /// will always at most return a single result per payload kind. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Need to a return a task of an enumerable.")] + public Task> DetectPayloadKindAsync() + { + IEnumerable payloadKindsFromContentType; + if (this.TryGetSinglePayloadKindResultFromContentType(out payloadKindsFromContentType)) + { + return TaskUtils.GetCompletedTask(payloadKindsFromContentType); + } + + // Otherwise we have to do sniffing + List detectedPayloadKinds = new List(); + + // NOTE: this relies on the lazy eval of the enumerator + return Task.Factory.Iterate(this.GetPayloadKindDetectionTasks(payloadKindsFromContentType, detectedPayloadKinds)) + .FollowAlwaysWith( + t => + { + // We are done sniffing; stop buffering. + this.message.UseBufferingReadStream = false; + this.message.BufferingReadStream.StopBuffering(); + }) + .FollowOnSuccessWith( + t => + { + // Always sort by payload kind to guarantee stable order of results in case clients rely on it + detectedPayloadKinds.Sort(this.ComparePayloadKindDetectionResult); + + return (IEnumerable)detectedPayloadKinds; + }); + } +#endif + + /// Creates an to read an async response. + /// The created async reader. + public ODataAsynchronousReader CreateODataAsynchronousReader() + { + this.VerifyCanCreateODataAsynchronousReader(); + return this.ReadFromInput( + (context) => context.CreateAsynchronousReader(), + ODataPayloadKind.Asynchronous); + } + +#if PORTABLELIB + /// Asynchronously creates an to read an async response. + /// A running task for the created async reader. + public Task CreateODataAsynchronousReaderAsync() + { + this.VerifyCanCreateODataAsynchronousReader(); + return this.ReadFromInputAsync( + (context) => context.CreateAsynchronousReaderAsync(), + ODataPayloadKind.Asynchronous); + } +#endif + + /// Creates an to read a resource set. + /// The created reader. + public ODataReader CreateODataResourceSetReader() + { + return this.CreateODataResourceSetReader(/*entitySet*/null, /*expectedResourceType*/null); + } + + /// + /// Creates an to read a resource set. + /// + /// The expected type for the resources in the resource set. + /// The created reader. + public ODataReader CreateODataResourceSetReader(IEdmStructuredType expectedResourceType) + { + return this.CreateODataResourceSetReader(/*entitySet*/null, expectedResourceType); + } + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read resources for. + /// The expected type for the items in the resource set. + /// The created reader. + public ODataReader CreateODataResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceSetReader(entitySet, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInput( + (context) => context.CreateResourceSetReader(entitySet, expectedResourceType), + ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// Asynchronously creates an to read a resource set. + /// A running task for the created reader. + public Task CreateODataResourceSetReaderAsync() + { + return this.CreateODataResourceSetReaderAsync(/*entitySet*/null, /*entityType*/null); + } + + /// + /// Asynchronously creates an to read a resource set. + /// + /// The expected type for the items in the resource set. + /// A running task for the created reader. + public Task CreateODataResourceSetReaderAsync(IEdmStructuredType expectedResourceType) + { + return this.CreateODataResourceSetReaderAsync(/*entitySet*/null, expectedResourceType); + } + + /// + /// Asynchronously creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected type for the items in the resource set. + /// A running task for the created reader. + public Task CreateODataResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceSetReader(entitySet, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInputAsync( + (context) => context.CreateResourceSetReaderAsync(entitySet, expectedResourceType), + ODataPayloadKind.ResourceSet); + } +#endif + + /// Creates an to read a delta resource set. + /// The created reader. + public ODataReader CreateODataDeltaResourceSetReader() + { + return this.CreateODataDeltaResourceSetReader(/*entitySet*/null, /*expectedResourceType*/null); + } + + /// + /// Creates an to read a delta resource set. + /// + /// The expected type for the resources in the resource set. + /// The created reader. + public ODataReader CreateODataDeltaResourceSetReader(IEdmStructuredType expectedResourceType) + { + return this.CreateODataDeltaResourceSetReader(/*entitySet*/null, expectedResourceType); + } + + /// + /// Creates an to read a delta resource set. + /// + /// The entity set we are going to read resources for. + /// The expected type for the items in the resource set. + /// The created reader. + public ODataReader CreateODataDeltaResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceSetReader(entitySet, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInput( + (context) => context.CreateDeltaResourceSetReader(entitySet, expectedResourceType), + ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// Asynchronously creates an to read a delta resource set. + /// A running task for the created reader. + public Task CreateODataDeltaResourceSetReaderAsync() + { + return this.CreateODataDeltaResourceSetReaderAsync(/*entitySet*/null, /*entityType*/null); + } + + /// + /// Asynchronously creates an to read a delta resource set. + /// + /// The expected type for the items in the resource set. + /// A running task for the created reader. + public Task CreateODataDeltaResourceSetReaderAsync(IEdmStructuredType expectedResourceType) + { + return this.CreateODataDeltaResourceSetReaderAsync(/*entitySet*/null, expectedResourceType); + } + + /// + /// Asynchronously creates an to read a delta resource set. + /// + /// The entity set we are going to read entities for. + /// The expected type for the items in the resource set. + /// A running task for the created reader. + public Task CreateODataDeltaResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceSetReader(entitySet, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInputAsync( + (context) => context.CreateDeltaResourceSetReaderAsync(entitySet, expectedResourceType), + ODataPayloadKind.Delta); + } +#endif + + /// + /// Creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base type for the entities in the delta response. + /// The created reader. + [Obsolete("Use CreateODataDeltaResourceSetReader.", false)] + public ODataDeltaReader CreateODataDeltaReader(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + this.VerifyCanCreateODataDeltaReader(entitySet, expectedBaseEntityType); + expectedBaseEntityType = expectedBaseEntityType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInput( + (context) => context.CreateDeltaReader(entitySet, expectedBaseEntityType), + ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base type for the entities in the delta response. + /// A running task for the created reader. + [Obsolete("Use CreateODataDeltaResourceSetReader.", false)] + public Task CreateODataDeltaReaderAsync(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + this.VerifyCanCreateODataResourceSetReader(entitySet, expectedBaseEntityType); + expectedBaseEntityType = expectedBaseEntityType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInputAsync( + (context) => context.CreateDeltaReaderAsync(entitySet, expectedBaseEntityType), + ODataPayloadKind.ResourceSet); + } +#endif + + /// Creates an to read a resource. + /// The created reader. + public ODataReader CreateODataResourceReader() + { + return this.CreateODataResourceReader(/*entitySet*/null, /*resourceType*/null); + } + + /// + /// Creates an to read a resource. + /// + /// The expected resource type for the resource to be read. + /// The created reader. + public ODataReader CreateODataResourceReader(IEdmStructuredType resourceType) + { + return this.CreateODataResourceReader(/*entitySet*/null, resourceType); + } + + /// + /// Creates an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected resource type for the resource to be read. + /// The created reader. + public ODataReader CreateODataResourceReader(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceReader(navigationSource, resourceType); + resourceType = resourceType ?? this.edmTypeResolver.GetElementType(navigationSource); + return this.ReadFromInput( + (context) => context.CreateResourceReader(navigationSource, resourceType), + ODataPayloadKind.Resource); + } + +#if PORTABLELIB + /// Asynchronously creates an to read a resource. + /// A running task for the created reader. + public Task CreateODataResourceReaderAsync() + { + return this.CreateODataResourceReaderAsync(/*entitySet*/null, /*entityType*/null); + } + + /// + /// Asynchronously creates an to read a resource. + /// + /// The expected structured type for the resource to be read. + /// A running task for the created reader. + public Task CreateODataResourceReaderAsync(IEdmStructuredType resourceType) + { + return this.CreateODataResourceReaderAsync(/*entitySet*/null, resourceType); + } + + /// + /// Asynchronously creates an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// A running task for the created reader. + public Task CreateODataResourceReaderAsync(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceReader(navigationSource, resourceType); + resourceType = resourceType ?? this.edmTypeResolver.GetElementType(navigationSource); + return this.ReadFromInputAsync( + (context) => context.CreateResourceReaderAsync(navigationSource, resourceType), + ODataPayloadKind.Resource); + } +#endif + + /// Creates an to read a collection of primitive or complex values (as result of a service operation invocation). + /// The created collection reader. + public ODataCollectionReader CreateODataCollectionReader() + { + return this.CreateODataCollectionReader(null /*expectedItemTypeReference*/); + } + + /// + /// Creates an to read a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The expected type reference for the items in the collection. + /// The created collection reader. + public ODataCollectionReader CreateODataCollectionReader(IEdmTypeReference expectedItemTypeReference) + { + this.VerifyCanCreateODataCollectionReader(expectedItemTypeReference); + return this.ReadFromInput( + (context) => context.CreateCollectionReader(expectedItemTypeReference), + ODataPayloadKind.Collection); + } + +#if PORTABLELIB + /// Asynchronously creates an to read a collection of primitive or complex values (as result of a service operation invocation). + /// A running task for the created collection reader. + public Task CreateODataCollectionReaderAsync() + { + return this.CreateODataCollectionReaderAsync(null /*expectedItemTypeReference*/); + } + + /// + /// Asynchronously creates an to read a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The expected type reference for the items in the collection. + /// A running task for the created collection reader. + public Task CreateODataCollectionReaderAsync(IEdmTypeReference expectedItemTypeReference) + { + this.VerifyCanCreateODataCollectionReader(expectedItemTypeReference); + return this.ReadFromInputAsync( + (context) => context.CreateCollectionReaderAsync(expectedItemTypeReference), + ODataPayloadKind.Collection); + } + +#endif + + /// Creates an to read a batch of requests or responses. + /// The created batch reader. + public ODataBatchReader CreateODataBatchReader() + { + this.VerifyCanCreateODataBatchReader(); + return this.ReadFromInput( + (context) => context.CreateBatchReader(), + ODataPayloadKind.Batch); + } + +#if PORTABLELIB + /// Asynchronously creates an to read a batch of requests or responses. + /// A running task for the created batch reader. + public Task CreateODataBatchReaderAsync() + { + this.VerifyCanCreateODataBatchReader(); + return this.ReadFromInputAsync( + (context) => context.CreateBatchReaderAsync(), + ODataPayloadKind.Batch); + } +#endif + + /// + /// Creates an to read a resource in a Uri operation parameter. + /// + /// The navigation source we are going to read resources for. + /// The expected resource type for the resource to be read. + /// The created reader. + public ODataReader CreateODataUriParameterResourceReader(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceReader(navigationSource, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(navigationSource); + return this.ReadFromInput( + (context) => context.CreateUriParameterResourceReader(navigationSource, expectedResourceType), + ODataPayloadKind.Resource); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource in a Uri operation parameter. + /// + /// The navigation source we are going to read resources for. + /// The expected structured type for the resource to be read. + /// A running task for the created reader. + public Task CreateODataUriParameterResourceReaderAsync(IEdmNavigationSource navigationSource, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceReader(navigationSource, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(navigationSource); + return this.ReadFromInputAsync( + (context) => context.CreateUriParameterResourceReaderAsync(navigationSource, expectedResourceType), + ODataPayloadKind.Resource); + } +#endif + /// + /// Creates an to read a resource set in a Uri operation parameter. + /// + /// The entity set we are going to read resources for. + /// The expected type for the items in the resource set. + /// The created reader. + public ODataReader CreateODataUriParameterResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceSetReader(entitySet, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInput( + (context) => context.CreateUriParameterResourceSetReader(entitySet, expectedResourceType), + ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read a resource set in a Uri operation parameter. + /// + /// The entity set we are going to read entities for. + /// The expected type for the items in the resource set. + /// A running task for the created reader. + public Task CreateODataUriParameterResourceSetReaderAsync(IEdmEntitySetBase entitySet, IEdmStructuredType expectedResourceType) + { + this.VerifyCanCreateODataResourceSetReader(entitySet, expectedResourceType); + expectedResourceType = expectedResourceType ?? this.edmTypeResolver.GetElementType(entitySet); + return this.ReadFromInputAsync( + (context) => context.CreateUriParameterResourceSetReaderAsync(entitySet, expectedResourceType), + ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Creates an to read the parameters for . + /// + /// The operation whose parameters are being read. + /// The created parameter reader. + public ODataParameterReader CreateODataParameterReader(IEdmOperation operation) + { + this.VerifyCanCreateODataParameterReader(operation); + return this.ReadFromInput( + (context) => context.CreateParameterReader(operation), + ODataPayloadKind.Parameter); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to read the parameters for . + /// + /// The operation whose parameters are being read. + /// A running task for the created parameter reader. + public Task CreateODataParameterReaderAsync(IEdmOperation operation) + { + this.VerifyCanCreateODataParameterReader(operation); + return this.ReadFromInputAsync( + (context) => context.CreateParameterReaderAsync(operation), + ODataPayloadKind.Parameter); + } +#endif + + /// Reads a service document payload. + /// The service document read. + public ODataServiceDocument ReadServiceDocument() + { + this.VerifyCanReadServiceDocument(); + return this.ReadFromInput( + (context) => context.ReadServiceDocument(), + ODataPayloadKind.ServiceDocument); + } + +#if PORTABLELIB + /// Asynchronously reads a service document payload. + /// A task representing the asynchronous operation of reading the service document. + public Task ReadServiceDocumentAsync() + { + this.VerifyCanReadServiceDocument(); + return this.ReadFromInputAsync( + (context) => context.ReadServiceDocumentAsync(), + ODataPayloadKind.ServiceDocument); + } +#endif + + /// Reads an as message payload. + /// The property read from the payload. + public ODataProperty ReadProperty() + { + return this.ReadProperty((IEdmTypeReference)null); + } + + /// + /// Reads an as message payload. + /// + /// The expected type reference of the property to read. + /// The property read from the payload. + public ODataProperty ReadProperty(IEdmTypeReference expectedPropertyTypeReference) + { + this.VerifyCanReadProperty(expectedPropertyTypeReference); + return this.ReadFromInput( + (context) => context.ReadProperty(/*property*/null, expectedPropertyTypeReference), + ODataPayloadKind.Property); + } + + /// + /// Reads an as message payload. + /// + /// The metadata of the property to read. + /// The property read from the payload. + public ODataProperty ReadProperty(IEdmStructuralProperty property) + { + this.VerifyCanReadProperty(property); + return this.ReadFromInput( + (context) => context.ReadProperty(property, property.Type), + ODataPayloadKind.Property); + } + +#if PORTABLELIB + /// Asynchronously reads an as message payload. + /// A task representing the asynchronous operation of reading the property. + public Task ReadPropertyAsync() + { + return this.ReadPropertyAsync((IEdmTypeReference)null); + } + + /// + /// Asynchronously reads an as message payload. + /// + /// The expected type reference of the property to read. + /// A task representing the asynchronous operation of reading the property. + public Task ReadPropertyAsync(IEdmTypeReference expectedPropertyTypeReference) + { + this.VerifyCanReadProperty(expectedPropertyTypeReference); + return this.ReadFromInputAsync( + (context) => context.ReadPropertyAsync(/*propertyOrFunctionImport*/null, expectedPropertyTypeReference), + ODataPayloadKind.Property); + } + + /// + /// Asynchronously reads an as message payload. + /// + /// The metadata of the property to read. + /// A task representing the asynchronous operation of reading the property. + public Task ReadPropertyAsync(IEdmStructuralProperty property) + { + this.VerifyCanReadProperty(property); + return this.ReadFromInputAsync( + (context) => context.ReadPropertyAsync(property, property.Type), + ODataPayloadKind.Property); + } + +#endif + + /// Reads an as the message payload. + /// The read from the message payload. + public ODataError ReadError() + { + this.VerifyCanReadError(); + return this.ReadFromInput( + (context) => context.ReadError(), + ODataPayloadKind.Error); + } + +#if PORTABLELIB + /// Asynchronously reads an as the message payload. + /// A task representing the asynchronous operation of reading the error. + public Task ReadErrorAsync() + { + this.VerifyCanReadError(); + return this.ReadFromInputAsync( + (context) => context.ReadErrorAsync(), + ODataPayloadKind.Error); + } +#endif + + /// Reads the result of a $ref query (entity reference links) as the message payload. + /// The entity reference links read as message payload. + public ODataEntityReferenceLinks ReadEntityReferenceLinks() + { + this.VerifyCanReadEntityReferenceLinks(); + return this.ReadFromInput( + (context) => context.ReadEntityReferenceLinks(), + ODataPayloadKind.EntityReferenceLinks); + } + +#if PORTABLELIB + /// Asynchronously reads the result of a $ref query as the message payload. + /// A task representing the asynchronous reading of the entity reference links. + public Task ReadEntityReferenceLinksAsync() + { + this.VerifyCanReadEntityReferenceLinks(); + return this.ReadFromInputAsync( + (context) => context.ReadEntityReferenceLinksAsync(), + ODataPayloadKind.EntityReferenceLinks); + } +#endif + + /// Reads a singleton result of a $ref query (entity reference link) as the message payload. + /// The entity reference link read from the message payload. + public ODataEntityReferenceLink ReadEntityReferenceLink() + { + this.VerifyCanReadEntityReferenceLink(); + return this.ReadFromInput( + (context) => context.ReadEntityReferenceLink(), + ODataPayloadKind.EntityReferenceLink); + } + +#if PORTABLELIB + /// Asynchronously reads a singleton result of a $ref query (entity reference link) as the message payload. + /// A running task representing the reading of the entity reference link. + public Task ReadEntityReferenceLinkAsync() + { + this.VerifyCanReadEntityReferenceLink(); + return this.ReadFromInputAsync( + (context) => context.ReadEntityReferenceLinkAsync(), + ODataPayloadKind.EntityReferenceLink); + } +#endif + + /// + /// Reads a single value as the message body. + /// + /// The expected type reference for the value to be read; null if no expected type is available. + /// The read value. + public object ReadValue(IEdmTypeReference expectedTypeReference) + { + ODataPayloadKind[] supportedPayloadKinds = this.VerifyCanReadValue(expectedTypeReference); + + return this.ReadFromInput( + (context) => context.ReadValue(expectedTypeReference.AsPrimitiveOrNull()), + supportedPayloadKinds); + } + +#if PORTABLELIB + /// + /// Asynchronously reads a single value as the message body. + /// + /// The expected type reference for the value to be read; null if no expected type is available. + /// A running task representing the reading of the value. + public Task ReadValueAsync(IEdmTypeReference expectedTypeReference) + { + ODataPayloadKind[] supportedPayloadKinds = this.VerifyCanReadValue(expectedTypeReference); + + return this.ReadFromInputAsync( + (context) => context.ReadValueAsync((IEdmPrimitiveTypeReference)expectedTypeReference), + supportedPayloadKinds); + } +#endif + + /// Reads the message body as metadata document. + /// Returns . + public IEdmModel ReadMetadataDocument() + { + this.VerifyCanReadMetadataDocument(); + return this.ReadFromInput( + (context) => context.ReadMetadataDocument(null), + ODataPayloadKind.MetadataDocument); + } + + /// Reads the message body as metadata document. + /// The function to load referenced model xml. If null, will stop loading the referenced models. Normally it should throw no exception. + /// Returns . + /// + /// User should handle the disposal of XmlReader created by getReferencedModelReaderFunc. + /// + public IEdmModel ReadMetadataDocument(Func getReferencedModelReaderFunc) + { + this.VerifyCanReadMetadataDocument(); + return this.ReadFromInput( + (context) => context.ReadMetadataDocument(getReferencedModelReaderFunc), + ODataPayloadKind.MetadataDocument); + } + + /// implementation to cleanup unmanaged resources of the reader. + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Determines the format of the payload being read and returns it. + /// + /// The format of the payload being read by this reader. + /// + /// The format of the payload is determined when starting to read the message; + /// if this method is called before reading has started it will throw. + /// + internal ODataFormat GetFormat() + { + if (this.format == null) + { + throw new ODataException(Strings.ODataMessageReader_GetFormatCalledBeforeReadingStarted); + } + + return this.format; + } + + private static IServiceProvider GetContainer(T message) + where T : class + { +#if PORTABLELIB + var containerProvider = message as IContainerProvider; + return containerProvider == null ? null : containerProvider.Container; +#else + return null; +#endif + } + + private static IEdmModel GetModel(IServiceProvider container) + { +#if PORTABLELIB + return container == null ? EdmCoreModel.Instance : container.GetRequiredService(); +#else + return EdmCoreModel.Instance; +#endif + } + + private ODataMessageInfo GetOrCreateMessageInfo(Stream messageStream, bool isAsync) + { + if (this.messageInfo == null) + { + if (this.container == null) + { + this.messageInfo = new ODataMessageInfo(); + } + else + { + this.messageInfo = this.container.GetRequiredService(); + } + + this.messageInfo.Encoding = this.encoding; + this.messageInfo.IsResponse = this.readingResponse; + this.messageInfo.IsAsync = isAsync; + this.messageInfo.MediaType = this.contentType; + this.messageInfo.Model = this.model; + this.messageInfo.PayloadUriConverter = this.payloadUriConverter; + this.messageInfo.Container = this.container; + this.messageInfo.MessageStream = messageStream; + this.messageInfo.PayloadKind = this.readerPayloadKind; + } + + return this.messageInfo; + } + + /// + /// Processes the content type header of the message to determine the format of the payload, the encoding, and the payload kind. + /// + /// All possible kinds of payload to be read with this message reader; must not include ODataPayloadKind.Unsupported. + private void ProcessContentType(params ODataPayloadKind[] payloadKinds) + { + Debug.Assert(!payloadKinds.Contains(ODataPayloadKind.Unsupported), "!payloadKinds.Contains(ODataPayloadKind.Unsupported)"); + Debug.Assert(this.format == null, "this.format == null"); + Debug.Assert(this.readerPayloadKind == ODataPayloadKind.Unsupported, "this.readerPayloadKind == ODataPayloadKind.Unsupported"); + + // Set the format, encoding and payload kind. + string contentTypeHeader = this.GetContentTypeHeader(payloadKinds); + this.format = MediaTypeUtils.GetFormatFromContentType(contentTypeHeader, payloadKinds, this.mediaTypeResolver, out this.contentType, out this.encoding, out this.readerPayloadKind); + } + + /// + /// Gets the content type header of the message and validates that it is present and not empty. + /// + /// All possible kinds of payload to be read with this message reader; must not include ODataPayloadKind.Unsupported. + /// The content type header of the message. + private string GetContentTypeHeader(params ODataPayloadKind[] payloadKinds) + { + string contentTypeHeader = this.message.GetHeader(ODataConstants.ContentTypeHeader); + contentTypeHeader = contentTypeHeader == null ? null : contentTypeHeader.Trim(); + if (string.IsNullOrEmpty(contentTypeHeader)) + { + if (this.GetContentLengthHeader() != 0) + { + throw new ODataContentTypeException(Strings.ODataMessageReader_NoneOrEmptyContentTypeHeader); + } + + // Set a default format if content type is null and content length is 0. + if (payloadKinds.Contains(ODataPayloadKind.Value)) + { + contentTypeHeader = MimeConstants.MimeTextPlain; + } + else if (payloadKinds.Contains(ODataPayloadKind.BinaryValue)) + { + contentTypeHeader = MimeConstants.MimeApplicationOctetStream; + } + else + { + contentTypeHeader = MimeConstants.MimeApplicationJson; + } + } + + return contentTypeHeader; + } + + /// + /// Gets the value of the content length header of the message. + /// + /// The value of the content length header, or 0 if no such header. + private int GetContentLengthHeader() + { + int contentLength = 0; + int.TryParse(this.message.GetHeader(ODataConstants.ContentLengthHeader), out contentLength); + + return contentLength; + } + + /// + /// Verify arguments for creation of an to read a resource set. + /// + /// The entity set we are going to read resources for. + /// The expected base structured type for the items in the resource set. + private void VerifyCanCreateODataResourceSetReader(IEdmEntitySetBase entitySet, IEdmStructuredType expectedBaseResourceType) + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (!this.model.IsUserModel()) + { + if (entitySet != null) + { + throw new ArgumentException(Strings.ODataMessageReader_EntitySetSpecifiedWithoutMetadata("entitySet"), "entitySet"); + } + + if (expectedBaseResourceType != null) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata("expectedBaseEntityType"), "expectedBaseEntityType"); + } + } + } + + /// + /// Verify arguments for creation of an to read a resource set. + /// + /// The entity set we are going to read entities for. + /// The expected base entity type for the entities in the delta response. + private void VerifyCanCreateODataDeltaReader(IEdmEntitySetBase entitySet, IEdmEntityType expectedBaseEntityType) + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (!this.readingResponse) + { + throw new ODataException(Strings.ODataMessageReader_DeltaInRequest); + } + + if (!this.model.IsUserModel()) + { + if (entitySet != null) + { + throw new ArgumentException(Strings.ODataMessageReader_EntitySetSpecifiedWithoutMetadata("entitySet"), "entitySet"); + } + + if (expectedBaseEntityType != null) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata("expectedBaseEntityType"), "expectedBaseEntityType"); + } + } + } + + /// + /// Verify arguments for creation of an to read a resource. + /// + /// The navigation source we are going to read resources for. + /// The expected resource type for the resource to be read. + private void VerifyCanCreateODataResourceReader(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (!this.model.IsUserModel()) + { + if (navigationSource != null) + { + throw new ArgumentException(Strings.ODataMessageReader_EntitySetSpecifiedWithoutMetadata("navigationSource"), "navigationSource"); + } + + if (resourceType != null) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata("resourceType"), "resourceType"); + } + } + } + + /// + /// Verify arguments for creation of an to read a collection of primitive or complex values + /// (as result of a service operation invocation). + /// + /// The expected type for the items in the collection. + private void VerifyCanCreateODataCollectionReader(IEdmTypeReference expectedItemTypeReference) + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (expectedItemTypeReference != null) + { + if (!this.model.IsUserModel()) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata("expectedItemTypeReference"), "expectedItemTypeReference"); + } + + if (!expectedItemTypeReference.IsODataPrimitiveTypeKind() + && expectedItemTypeReference.TypeKind() != EdmTypeKind.Complex + && expectedItemTypeReference.TypeKind() != EdmTypeKind.Enum) + { + throw new ArgumentException( + Strings.ODataMessageReader_ExpectedCollectionTypeWrongKind(expectedItemTypeReference.TypeKind().ToString()), + "expectedItemTypeReference"); + } + } + } + + /// + /// Verify arguments for creation of an async response as the message body. + /// + private void VerifyCanCreateODataAsynchronousReader() + { + this.VerifyReaderNotDisposedAndNotUsed(); + } + + /// + /// Verify arguments for creation of a batch as the message body. + /// + private void VerifyCanCreateODataBatchReader() + { + this.VerifyReaderNotDisposedAndNotUsed(); + } + + /// + /// Verify arguments for creation of an to read the parameters for . + /// + /// The operation whose parameters are being read. + private void VerifyCanCreateODataParameterReader(IEdmOperation operation) + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (this.readingResponse) + { + throw new ODataException(Strings.ODataMessageReader_ParameterPayloadInResponse); + } + + if (operation != null && !this.model.IsUserModel()) + { + throw new ArgumentException(Strings.ODataMessageReader_OperationSpecifiedWithoutMetadata("operation"), "operation"); + } + } + + /// + /// Verify arguments for reading of a service document payload. + /// + private void VerifyCanReadServiceDocument() + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (!this.readingResponse) + { + throw new ODataException(Strings.ODataMessageReader_ServiceDocumentInRequest); + } + } + + /// + /// Verify arguments for reading of a metadata document payload. + /// + private void VerifyCanReadMetadataDocument() + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (!this.readingResponse) + { + throw new ODataException(Strings.ODataMessageReader_MetadataDocumentInRequest); + } + } + + /// + /// Verify arguments for reading of an as message payload. + /// + /// The metadata of the property to read. + private void VerifyCanReadProperty(IEdmStructuralProperty property) + { + if (property == null) + { + return; + } + + this.VerifyCanReadProperty(property.Type); + } + + /// + /// Verify arguments for reading of an as message payload. + /// + /// The expected type reference of the property to read. + private void VerifyCanReadProperty(IEdmTypeReference expectedPropertyTypeReference) + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (expectedPropertyTypeReference != null) + { + if (!this.model.IsUserModel()) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata("expectedPropertyTypeReference"), "expectedPropertyTypeReference"); + } + + IEdmCollectionType collectionType = expectedPropertyTypeReference.Definition as IEdmCollectionType; + if (collectionType != null && collectionType.ElementType.IsODataEntityTypeKind()) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedPropertyTypeEntityCollectionKind, "expectedPropertyTypeReference"); + } + + if (expectedPropertyTypeReference.IsODataEntityTypeKind()) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedPropertyTypeEntityKind, "expectedPropertyTypeReference"); + } + else if (expectedPropertyTypeReference.IsStream()) + { + throw new ArgumentException(Strings.ODataMessageReader_ExpectedPropertyTypeStream, "expectedPropertyTypeReference"); + } + } + } + + /// + /// Verify arguments for reading of an as the message payload. + /// + private void VerifyCanReadError() + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (!this.readingResponse) + { + // top-level errors can only be read for response messages + throw new ODataException(Strings.ODataMessageReader_ErrorPayloadInRequest); + } + } + + /// + /// Verify arguments for reading of the result of a $ref query (entity reference links) as the message payload. + /// + private void VerifyCanReadEntityReferenceLinks() + { + // NOTE: we decided to not stream links for now but only make reading them async. + this.VerifyReaderNotDisposedAndNotUsed(); + } + + /// + /// Verify arguments for reading of a singleton result of a $ref query (entity reference link) as the message payload. + /// + private void VerifyCanReadEntityReferenceLink() + { + this.VerifyReaderNotDisposedAndNotUsed(); + } + + /// + /// Verify arguments for reading of a single value as the message body. + /// + /// The expected type reference for the value to be read; null if no expected type is available. + /// The payload kinds allowed for the given expected type. + private ODataPayloadKind[] VerifyCanReadValue(IEdmTypeReference expectedTypeReference) + { + this.VerifyReaderNotDisposedAndNotUsed(); + + if (expectedTypeReference != null) + { + if (!expectedTypeReference.IsODataPrimitiveTypeKind() && !expectedTypeReference.IsODataTypeDefinitionTypeKind()) + { + throw new ArgumentException( + Strings.ODataMessageReader_ExpectedValueTypeWrongKind(expectedTypeReference.TypeKind().ToString()), + "expectedTypeReference"); + } + + if (expectedTypeReference.IsBinary()) + { + return new ODataPayloadKind[] { ODataPayloadKind.BinaryValue }; + } + else + { + return new ODataPayloadKind[] { ODataPayloadKind.Value }; + } + } + + return new ODataPayloadKind[] { ODataPayloadKind.Value, ODataPayloadKind.BinaryValue }; + } + + /// + /// Verifies that the ODataMessageReader has not been used before; an ODataMessageReader can only be used to + /// read a single message payload but cannot be reused later. + /// + private void VerifyReaderNotDisposedAndNotUsed() + { + this.VerifyNotDisposed(); + if (this.readMethodCalled) + { + throw new ODataException(Strings.ODataMessageReader_ReaderAlreadyUsed); + } + + if (this.message.BufferingReadStream != null && this.message.BufferingReadStream.IsBuffering) + { + throw new ODataException(Strings.ODataMessageReader_PayloadKindDetectionRunning); + } + + this.readMethodCalled = true; + } + + /// + /// Check if the object has been disposed. Throws an ObjectDisposedException if the object has already been disposed. + /// + private void VerifyNotDisposed() + { + if (this.isDisposed) + { + throw new ObjectDisposedException(this.GetType().FullName); + } + } + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + private void Dispose(bool disposing) + { + this.isDisposed = true; + if (disposing) + { + try + { + if (this.inputContext != null) + { + this.inputContext.Dispose(); + } + } + finally + { + this.inputContext = null; + } + + // If we still have a buffering read stream only the payload kind detection was triggered but + // the actual reading never started. Dispose the stream now (if disposal is not disabled). + if (this.settings.EnableMessageStreamDisposal && this.message.BufferingReadStream != null) + { + this.message.BufferingReadStream.Dispose(); + } + } + } + + /// + /// Method which creates an input context around the input message and calls a func to read the input. + /// + /// The type returned by the read method. + /// The read function which will be called over the created input context. + /// All possible kinds of payload to read. + /// The read value from the input. + private T ReadFromInput(Func readFunc, params ODataPayloadKind[] payloadKinds) where T : class + { + this.ProcessContentType(payloadKinds); + Debug.Assert(this.format != null, "By now we should have figured out which format to use."); + + this.inputContext = this.format.CreateInputContext( + this.GetOrCreateMessageInfo(this.message.GetStream(), false), + this.settings); + + return readFunc(this.inputContext); + } + + /// + /// Gets all the supported payload kinds for a given content type across all formats and returns them. + /// + /// The set of supported payload kinds for the content type of the message. + /// true if no or a single payload kind was found for the content type; false if more than one payload kind was found. + private bool TryGetSinglePayloadKindResultFromContentType(out IEnumerable payloadKindResults) + { + if (this.message.UseBufferingReadStream == true) + { + // This method must be called at most once and not after the actual reading has started. + throw new ODataException(Strings.ODataMessageReader_DetectPayloadKindMultipleTimes); + } + + string contentTypeHeader = this.GetContentTypeHeader(); + IList payloadKindsFromContentType = MediaTypeUtils.GetPayloadKindsForContentType(contentTypeHeader, this.mediaTypeResolver, out this.contentType, out this.encoding); + payloadKindResults = payloadKindsFromContentType.Where(r => ODataUtilsInternal.IsPayloadKindSupported(r.PayloadKind, !this.readingResponse)); + + if (payloadKindResults.Count() > 1) + { + // Set UseBufferingReadStream to 'true' to use the buffering read stream when + // being asked for the message stream. + this.message.UseBufferingReadStream = true; + return false; + } + + return true; + } + + /// + /// Compares two payload kind detection results. + /// + /// The first . + /// The second . + /// -1 if is considered less than , + /// 0 if the kinds are considered equal, 1 if is considered greater than . + private int ComparePayloadKindDetectionResult(ODataPayloadKindDetectionResult first, ODataPayloadKindDetectionResult second) + { + ODataPayloadKind firstKind = first.PayloadKind; + ODataPayloadKind secondKind = second.PayloadKind; + + if (firstKind == secondKind) + { + return 0; + } + + return first.PayloadKind < second.PayloadKind ? -1 : 1; + } + +#if PORTABLELIB + /// + /// Get an enumerable of tasks to get the supported payload kinds for all formats. + /// + /// All payload kinds for which we found matches in some format based on the content type. + /// The list of combined detection results after sniffing. + /// A lazy enumerable of tasks to get the supported payload kinds for all formats. + private IEnumerable GetPayloadKindDetectionTasks( + IEnumerable payloadKindsFromContentType, + List detectionResults) + { + // Group the payload kinds by format so we call the payload kind detection method only + // once per format. + IEnumerable> payloadKindFromContentTypeGroups = + payloadKindsFromContentType.GroupBy(kvp => kvp.Format); + + foreach (IGrouping payloadKindGroup in payloadKindFromContentTypeGroups) + { + // Call the payload kind detection code on the format + Task> detectionResult = + this.message.GetStreamAsync() + .FollowOnSuccessWithTask(streamTask => + payloadKindGroup.Key.DetectPayloadKindAsync( + this.GetOrCreateMessageInfo(streamTask.Result, true), + this.settings)); + + yield return detectionResult + .FollowOnSuccessWith( + t => + { + IEnumerable result = t.Result; + if (result != null) + { + foreach (ODataPayloadKind kind in result) + { + // Only include the payload kinds that we expect + if (payloadKindsFromContentType.Any(pk => pk.PayloadKind == kind)) + { + Debug.Assert(!detectionResults.Any(dpk => dpk.PayloadKind == kind), "Each kind must appear at most once."); + detectionResults.Add(new ODataPayloadKindDetectionResult(kind, payloadKindGroup.Key)); + } + } + } + }); + } + } + + /// + /// Method which asynchronously creates an input context around the input message and calls a func to read the input. + /// + /// The type returned by the read method. + /// The read function which will be called over the created input context. + /// All possible kinds of payload to read. + /// A task which when completed return the read value from the input. + private Task ReadFromInputAsync(Func> readFunc, params ODataPayloadKind[] payloadKinds) where T : class + { + this.ProcessContentType(payloadKinds); + Debug.Assert(this.format != null, "By now we should have figured out which format to use."); + + return this.message.GetStreamAsync() + .FollowOnSuccessWithTask( + streamTask => this.format.CreateInputContextAsync( + this.GetOrCreateMessageInfo(streamTask.Result, true), + this.settings)) + .FollowOnSuccessWithTask( + createInputContextTask => + { + this.inputContext = createInputContextTask.Result; + return readFunc(this.inputContext); + }); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs new file mode 100644 index 0000000..041cbbf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs @@ -0,0 +1,292 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using Microsoft.OData.Buffers; + using Microsoft.OData.Edm; + + /// + /// Configuration settings for OData message readers. + /// + public sealed class ODataMessageReaderSettings + { + /// + /// The base uri used in payload. + /// + private Uri baseUri; + + /// Quotas to use for limiting resource consumption when reading an OData message. + private ODataMessageQuotas messageQuotas; + + /// + /// Validation settings. + /// + private ValidationKinds validations; + + /// Initializes a new instance of the class + /// with default values for OData 4.0. + public ODataMessageReaderSettings() : this(ODataConstants.ODataDefaultProtocolVersion) + { + } + + /// Initializes a new instance of the class + /// with default values for the specified OData version. + /// OData Version for which to create default settings. + public ODataMessageReaderSettings(ODataVersion odataVersion) + { + this.ClientCustomTypeResolver = null; + this.PrimitiveTypeResolver = null; + this.EnablePrimitiveTypeConversion = true; + this.EnableMessageStreamDisposal = true; + this.EnableCharactersCheck = false; + this.Version = odataVersion; + this.LibraryCompatibility = ODataLibraryCompatibility.Latest; + + Validator = new ReaderValidator(this); + if (odataVersion < ODataVersion.V401) + { + Validations = ValidationKinds.All; + this.ReadUntypedAsString = true; + this.MaxProtocolVersion = ODataConstants.ODataDefaultProtocolVersion; + } + else + { + Validations = ValidationKinds.All & ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; + this.ReadUntypedAsString = false; + this.MaxProtocolVersion = odataVersion; + } + } + + /// + /// Gets or sets library compatibility version. Default value is , + /// + public ODataLibraryCompatibility LibraryCompatibility { get; set; } + + /// Gets or sets the OData protocol version to be used for reading payloads. + /// The OData protocol version to be used for reading payloads. + public ODataVersion? Version { get; set; } + + /// + /// Get/sets the character buffer pool. + /// + public ICharArrayPool ArrayPool { get; set; } + + /// + /// Gets or sets validation settings. + /// + public ValidationKinds Validations + { + get + { + return validations; + } + + set + { + validations = value; + ThrowOnDuplicatePropertyNames = (validations & ValidationKinds.ThrowOnDuplicatePropertyNames) != 0; + ThrowIfTypeConflictsWithMetadata = (validations & ValidationKinds.ThrowIfTypeConflictsWithMetadata) != 0; + ThrowOnUndeclaredPropertyForNonOpenType = (validations & ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType) != 0; + } + } + + /// + /// Gets or sets the document base URI (used as base for all relative URIs). If this is set, it must be an absolute URI. + /// ODataMessageReaderSettings.BaseUri may be deprecated in the future, please use ODataMessageReaderSettings.baseUri instead. + /// + /// The base URI used in payload. + /// + /// This URI will be used in ATOM format only, it would be overridden by <xml:base /> element in ATOM payload. + /// If the URI does not end with a slash, a slash would be appended automatically. + /// + public Uri BaseUri + { + get + { + return baseUri; + } + + set + { + this.baseUri = UriUtils.EnsureTaillingSlash(value); + } + } + + /// + /// Gets or sets custom type resolver used by the Client. + /// + public Func ClientCustomTypeResolver { get; set; } + + /// + /// Gets or sets a custom resolver for resolving untyped primitive values + /// + public Func PrimitiveTypeResolver { get; set; } + + /// + /// Gets or sets a value that indicates whether to convert all primitive values to the type specified in the model or provided as an expected type. Note that values will still be converted to the type specified in the payload itself. + /// + /// false if primitive values and report values are not converted; true if all primitive values are converted to the type specified in the model or provided as an expected type. The default value is true. + public bool EnablePrimitiveTypeConversion { get; set; } + + /// + /// Gets or sets a value that indicates whether the message stream will be disposed after finishing writing with the message. + /// + /// true if the message stream will be disposed after finishing writing with the message; otherwise false. The default value is true. + public bool EnableMessageStreamDisposal { get; set; } + + /// + /// Flag to control whether the reader should check for valid Xml characters or not. + /// + public bool EnableCharactersCheck { get; set; } + + /// + /// Gets or sets the maximum OData protocol version the reader should accept and understand. + /// + /// The maximum OData protocol version the reader should accept and understand. + /// + /// If the payload to be read has higher OData-Version than the value specified for this property + /// the reader will fail. + /// Reader will also not report features which require higher version than specified for this property. + /// It may either ignore such features in the payload or fail on them. + /// + public ODataVersion MaxProtocolVersion { get; set; } + + /// + /// Quotas to use for limiting resource consumption when reading an OData message. + /// + public ODataMessageQuotas MessageQuotas + { + get + { + if (this.messageQuotas == null) + { + this.messageQuotas = new ODataMessageQuotas(); + } + + return this.messageQuotas; + } + + set + { + this.messageQuotas = value; + } + } + + /// + /// Whether to read untyped values as a raw string. + /// + public bool ReadUntypedAsString { get; set; } + + /// + /// Func to evaluate whether a property should be read as a stream. Note that IEdmProperty may be null when reading + /// within a collection + /// + /// + /// Function takes: + /// * Primitive type of the value being read, or null if unknown + /// * Whether the value being read is a collection + /// * The name of the property being read (null for values within a collection) + /// * The property being read (null for dynamic property or value within a collection) + /// Function returns: + /// * True, to have the value streamed, otherwise false + /// + public Func ReadAsStreamFunc { get; set; } + + /// + /// Func to evaluate whether an annotation should be read or skipped by the reader. The func should return true if the annotation should + /// be read and false if the annotation should be skipped. A null value indicates that all annotations should be skipped. + /// + public Func ShouldIncludeAnnotation { get; set; } + + /// + /// Gets the bound validator. + /// + internal IReaderValidator Validator { get; private set; } + + /// + /// Returns whether ThrowOnDuplicatePropertyNames validation setting is enabled. + /// + internal bool ThrowOnDuplicatePropertyNames { get; private set; } + + /// + /// Returns whether ThrowIfTypeConflictsWithMetadata is enabled. + /// + internal bool ThrowIfTypeConflictsWithMetadata { get; private set; } + + /// + /// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled. + /// + internal bool ThrowOnUndeclaredPropertyForNonOpenType { get; private set; } + + /// + /// Creates a shallow copy of this . + /// + /// A shallow copy of this . + public ODataMessageReaderSettings Clone() + { + var copy = new ODataMessageReaderSettings(); + copy.CopyFrom(this); + return copy; + } + + internal static ODataMessageReaderSettings CreateReaderSettings( + IServiceProvider container, + ODataMessageReaderSettings other) + { + ODataMessageReaderSettings readerSettings; + if (container == null) + { + readerSettings = new ODataMessageReaderSettings(); + } + else + { + readerSettings = container.GetRequiredService(); + } + + if (other != null) + { + readerSettings.CopyFrom(other); + } + + return readerSettings; + } + + /// + /// Returns true to indicate that the annotation with the name should be skipped, false otherwise. + /// + /// The name of the annotation in question. + /// Returns true to indicate that the annotation with the name should be skipped, false otherwise. + internal bool ShouldSkipAnnotation(string annotationName) + { + return this.ShouldIncludeAnnotation == null || !this.ShouldIncludeAnnotation(annotationName); + } + + private void CopyFrom(ODataMessageReaderSettings other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + + this.BaseUri = other.BaseUri; + this.ClientCustomTypeResolver = other.ClientCustomTypeResolver; + this.PrimitiveTypeResolver = other.PrimitiveTypeResolver; + this.EnableMessageStreamDisposal = other.EnableMessageStreamDisposal; + this.EnablePrimitiveTypeConversion = other.EnablePrimitiveTypeConversion; + this.EnableCharactersCheck = other.EnableCharactersCheck; + this.messageQuotas = new ODataMessageQuotas(other.MessageQuotas); + this.MaxProtocolVersion = other.MaxProtocolVersion; + this.ReadUntypedAsString = other.ReadUntypedAsString; + this.ShouldIncludeAnnotation = other.ShouldIncludeAnnotation; + this.validations = other.validations; + this.ThrowOnDuplicatePropertyNames = other.ThrowOnDuplicatePropertyNames; + this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata; + this.ThrowOnUndeclaredPropertyForNonOpenType = other.ThrowOnUndeclaredPropertyForNonOpenType; + this.LibraryCompatibility = other.LibraryCompatibility; + this.Version = other.Version; + this.ReadAsStreamFunc = other.ReadAsStreamFunc; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriter.cs new file mode 100644 index 0000000..0767764 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriter.cs @@ -0,0 +1,1355 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Writer class used to write all OData payloads (entries, resource sets, metadata documents, service documents, etc.). + /// + public sealed class ODataMessageWriter : IDisposable + { + /// The message for which the message writer was created. + private readonly ODataMessage message; + + /// A flag indicating whether we are writing a request or a response message. + private readonly bool writingResponse; + + /// The message writer settings to use when writing the message payload. + private readonly ODataMessageWriterSettings settings; + + /// The model. Non-null if we do have metadata available. + private readonly IEdmModel model; + + /// The optional URL converter to perform custom URL conversion for URLs written to the payload. + private readonly IODataPayloadUriConverter payloadUriConverter; + + /// The optional dependency injection container to get related services for message writing. + private readonly IServiceProvider container; + + /// The media type resolver to use when interpreting the incoming content type. + private readonly ODataMediaTypeResolver mediaTypeResolver; + + /// Flag to ensure that only a single write method is called on the message writer. + private bool writeMethodCalled; + + /// True if Dispose() has been called on this message writer, False otherwise. + private bool isDisposed; + + /// The output context we're using to write the payload. + /// This is null until the first write operation is called. + private ODataOutputContext outputContext; + + /// The payload kind of the payload to be written with this writer. + /// This is either set via the SetHeadersForPayload method or implicitly when one of the write (or writer creation) methods is called. + private ODataPayloadKind writerPayloadKind = ODataPayloadKind.Unsupported; + + /// The of the payload to be written with this writer. + /// This is either set via the SetHeadersForPayload method or implicitly when one of the write (or writer creation) methods is called. + private ODataFormat format; + + /// The of the payload to be written with this writer. + /// This is either set via the SetHeadersForPayload method or implicitly when one of the write (or writer creation) methods is called. + private Encoding encoding; + + /// Flag to prevent writing more than one error to the payload. + private bool writeErrorCalled; + + /// The of the payload to be written with this writer. + /// This is either set via the SetHeadersForPayload method or implicitly when one of the write (or writer creation) methods is called. + private ODataMediaType mediaType; + + /// The context information for the message. + private ODataMessageInfo messageInfo; + + /// Creates a new for the given request message. + /// The request message for which to create the writer. + public ODataMessageWriter(IODataRequestMessage requestMessage) + : this(requestMessage, null) + { + } + + /// Creates a new for the given request message and message writer settings. + /// The request message for which to create the writer. + /// The message writer settings to use for writing the message payload. + public ODataMessageWriter(IODataRequestMessage requestMessage, ODataMessageWriterSettings settings) + : this(requestMessage, settings, null) + { + } + + /// + /// Creates a new ODataMessageWriter for the given request message and message writer settings. + /// + /// The request message for which to create the writer. + /// The message writer settings to use for writing the message payload. + /// The model to use. + public ODataMessageWriter(IODataRequestMessage requestMessage, ODataMessageWriterSettings settings, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(requestMessage, "requestMessage"); + + this.container = GetContainer(requestMessage); + this.settings = ODataMessageWriterSettings.CreateWriterSettings(this.container, settings); + this.writingResponse = false; + this.payloadUriConverter = requestMessage as IODataPayloadUriConverter; + this.mediaTypeResolver = ODataMediaTypeResolver.GetMediaTypeResolver(this.container); + this.model = model ?? GetModel(this.container); + WriterValidationUtils.ValidateMessageWriterSettings(this.settings, this.writingResponse); + this.message = new ODataRequestMessage(requestMessage, /*writing*/ true, this.settings.EnableMessageStreamDisposal, /*maxMessageSize*/ -1); + + // Always include all annotations when writting request message. + Debug.Assert(this.settings.ShouldIncludeAnnotation == null, "this.settings.ShouldIncludeAnnotation == null"); + this.settings.ShouldIncludeAnnotation = AnnotationFilter.CreateInclueAllFilter().Matches; + } + + /// Creates a new for the given response message. + /// The response message for which to create the writer. + public ODataMessageWriter(IODataResponseMessage responseMessage) + : this(responseMessage, null) + { + } + + /// Creates a new for the given response message and message writer settings. + /// The response message for which to create the writer. + /// The message writer settings to use for writing the message payload. + public ODataMessageWriter(IODataResponseMessage responseMessage, ODataMessageWriterSettings settings) + : this(responseMessage, settings, null) + { + } + + /// + /// Creates a new ODataMessageWriter for the given response message and message writer settings. + /// + /// The response message for which to create the writer. + /// The message writer settings to use for writing the message payload. + /// The model to use. + public ODataMessageWriter(IODataResponseMessage responseMessage, ODataMessageWriterSettings settings, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(responseMessage, "responseMessage"); + + this.container = GetContainer(responseMessage); + this.settings = ODataMessageWriterSettings.CreateWriterSettings(this.container, settings); + this.writingResponse = true; + this.payloadUriConverter = responseMessage as IODataPayloadUriConverter; + this.mediaTypeResolver = ODataMediaTypeResolver.GetMediaTypeResolver(this.container); + this.model = model ?? GetModel(this.container); + WriterValidationUtils.ValidateMessageWriterSettings(this.settings, this.writingResponse); + this.message = new ODataResponseMessage(responseMessage, /*writing*/ true, this.settings.EnableMessageStreamDisposal, /*maxMessageSize*/ -1); + + // If the Preference-Applied header on the response message contains an annotation filter, we set the filter + // to the writer settings so that we would only write annotations that satisfy the filter. + string annotationFilter = responseMessage.PreferenceAppliedHeader().AnnotationFilter; + if (!string.IsNullOrEmpty(annotationFilter)) + { + this.settings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter(annotationFilter); + } + } + + /// + /// The message writer settings to use when writing the message payload. + /// + internal ODataMessageWriterSettings Settings + { + get + { + return this.settings; + } + } + + /// Creates an to write an async response. + /// The created writer. + public ODataAsynchronousWriter CreateODataAsynchronousWriter() + { + this.VerifyCanCreateODataAsyncWriter(); + return this.WriteToOutput( + ODataPayloadKind.Asynchronous, + context => context.CreateODataAsynchronousWriter()); + } + + /// Creates an to write a resource set. + /// The created writer. + public ODataWriter CreateODataResourceSetWriter() + { + return CreateODataResourceSetWriter(/*entitySet*/null, /*entityType*/null); + } + + /// + /// Creates an to write a resource set. + /// + /// The created writer. + /// The entity set we are going to write entities for. + public ODataWriter CreateODataResourceSetWriter(IEdmEntitySetBase entitySet) + { + return CreateODataResourceSetWriter(entitySet, /*entityType*/null); + } + + /// + /// Creates an to write a resource set. + /// + /// The created writer. + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + public ODataWriter CreateODataResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceSetWriter(); + return this.WriteToOutput( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataResourceSetWriter(entitySet, resourceType)); + } + +#if PORTABLELIB + /// Asynchronously creates an to write an async response. + /// A running task for the created writer. + public Task CreateODataAsynchronousWriterAsync() + { + this.VerifyCanCreateODataAsyncWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.Asynchronous, + (context) => context.CreateODataAsynchronousWriterAsync()); + } + + /// Asynchronously creates an to write a resource set. + /// A running task for the created writer. + public Task CreateODataResourceSetWriterAsync() + { + return CreateODataResourceSetWriterAsync(/*entitySet*/null, /*entityType*/null); + } + + /// + /// Asynchronously creates an to write a resource set. + /// + /// The entity set we are going to write entities for. + /// A running task for the created writer. + public Task CreateODataResourceSetWriterAsync(IEdmEntitySetBase entitySet) + { + return CreateODataResourceSetWriterAsync(entitySet, /*entityType*/null); + } + + /// + /// Asynchronously creates an to write a resource set. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + public Task CreateODataResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + this.VerifyCanCreateODataResourceSetWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataResourceSetWriterAsync(entitySet, entityType)); + } +#endif + + /// Creates an to write a delta resource set. + /// The created writer. + public ODataWriter CreateODataDeltaResourceSetWriter() + { + return CreateODataDeltaResourceSetWriter(/*entitySet*/null, /*entityType*/null); + } + + /// + /// Creates an to write a delta resource set. + /// + /// The created writer. + /// The entity set we are going to write entities for. + public ODataWriter CreateODataDeltaResourceSetWriter(IEdmEntitySetBase entitySet) + { + return CreateODataDeltaResourceSetWriter(entitySet, /*entityType*/null); + } + + /// + /// Creates an to write a resource set. + /// + /// The created writer. + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + public ODataWriter CreateODataDeltaResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceSetWriter(); + return this.WriteToOutput( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataDeltaResourceSetWriter(entitySet, resourceType)); + } + +#if PORTABLELIB + + /// Asynchronously creates an to write a delta resource set. + /// A running task for the created writer. + public Task CreateODataDeltaResourceSetWriterAsync() + { + return CreateODataDeltaResourceSetWriterAsync(/*entitySet*/null, /*entityType*/null); + } + + /// + /// Asynchronously creates an to write a delta resource set. + /// + /// The entity set we are going to write entities for. + /// A running task for the created writer. + public Task CreateODataDeltaResourceSetWriterAsync(IEdmEntitySetBase entitySet) + { + return CreateODataDeltaResourceSetWriterAsync(entitySet, /*entityType*/null); + } + + /// + /// Asynchronously creates an to write a delta resource set. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + public Task CreateODataDeltaResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + this.VerifyCanCreateODataResourceSetWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataDeltaResourceSetWriterAsync(entitySet, entityType)); + } +#endif + + /// + /// Creates an to write a delta response. + /// + /// The created writer. + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + [Obsolete("Use CreateODataDeltaResourceSetWriter.", false)] + public ODataDeltaWriter CreateODataDeltaWriter(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + this.VerifyCanCreateODataDeltaWriter(); + return this.WriteToOutput( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataDeltaWriter(entitySet, entityType)); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a delta response. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + [Obsolete("Use CreateODataDeltaResourceSetWriterAsync.", false)] + public Task CreateODataDeltaWriterAsync(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + this.VerifyCanCreateODataResourceSetWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataDeltaWriterAsync(entitySet, entityType)); + } +#endif + + /// Creates an to write a resource. + /// The created writer. + public ODataWriter CreateODataResourceWriter() + { + return CreateODataResourceWriter(/*navigationSource*/null, /*resourceType*/ null); + } + + /// + /// Creates an to write a resource. + /// + /// The navigation source we are going to write resources for. + /// The created writer. + public ODataWriter CreateODataResourceWriter(IEdmNavigationSource navigationSource) + { + return CreateODataResourceWriter(navigationSource, /*resourceType*/ null); + } + + /// + /// Creates an to write a resource. + /// + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + public ODataWriter CreateODataResourceWriter(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceWriter(); + return this.WriteToOutput( + ODataPayloadKind.Resource, + (context) => context.CreateODataResourceWriter(navigationSource, resourceType)); + } + +#if PORTABLELIB + /// Asynchronously creates an to write a resource. + /// A running task for the created writer. + public Task CreateODataResourceWriterAsync() + { + return CreateODataResourceWriterAsync(/*entitySet*/null, /*resourceType*/null); + } + + /// + /// Asynchronously creates an to write a resource. + /// + /// The navigation source we are going to write entities for. + /// A running task for the created writer. + public Task CreateODataResourceWriterAsync(IEdmNavigationSource navigationSource) + { + return CreateODataResourceWriterAsync(navigationSource, /*resourceType*/null); + } + + /// + /// Asynchronously creates an to write a resource. + /// + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + public Task CreateODataResourceWriterAsync(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.Resource, + (context) => context.CreateODataResourceWriterAsync(navigationSource, resourceType)); + } +#endif + + /// + /// Creates an to write a Uri operation parameter. + /// + /// The navigation source we are going to write resource for. + /// The structured type for the resources in the resource set to be written. + /// The created uri parameter writer. + public ODataWriter CreateODataUriParameterResourceWriter(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceWriter(); + return this.WriteToOutput( + ODataPayloadKind.Resource, + (context) => context.CreateODataUriParameterResourceWriter(navigationSource, resourceType)); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write Uri operation parameter. + /// + /// The navigation source we are going to write resource for. + /// The structured type for the resources in the resource set to be written. + /// A running task for the created uri parameter writer. + public Task CreateODataUriParameterResourceWriterAsync(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.Resource, + (context) => context.CreateODataUriParameterResourceWriterAsync(navigationSource, resourceType)); + } +#endif + + /// + /// Creates an to write a Uri operation parameter. + /// + /// The resource set we are going to write resources for. + /// The structured type for the resources in the resource set to be written. + /// The created uri parameter writer. + public ODataWriter CreateODataUriParameterResourceSetWriter(IEdmEntitySetBase entitySetBase, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceSetWriter(); + return this.WriteToOutput( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataUriParameterResourceSetWriter(entitySetBase, resourceType)); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write Uri operation parameter. + /// + /// The resource set we are going to write resources for. + /// The structured type for the resources in the resource set to be written. + /// A running task for the created uri parameter writer. + public Task CreateODataUriParameterResourceSetWriterAsync(IEdmEntitySetBase entitySetBase, IEdmStructuredType resourceType) + { + this.VerifyCanCreateODataResourceSetWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.ResourceSet, + (context) => context.CreateODataUriParameterResourceSetWriterAsync(entitySetBase, resourceType)); + } +#endif + + /// Creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// The created collection writer. + public ODataCollectionWriter CreateODataCollectionWriter() + { + return this.CreateODataCollectionWriter(/*collectionType*/ null); + } + + /// + /// Creates an to write a collection of primitive , enum or complex values (as result of a service operation invocation). + /// + /// The item type of the collection being written or null if no metadata is available. + /// The created collection writer. + public ODataCollectionWriter CreateODataCollectionWriter(IEdmTypeReference itemTypeReference) + { + this.VerifyCanCreateODataCollectionWriter(itemTypeReference); + return this.WriteToOutput( + ODataPayloadKind.Collection, + (context) => context.CreateODataCollectionWriter(itemTypeReference)); + } + +#if PORTABLELIB + /// Asynchronously creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// A running task for the created collection writer. + public Task CreateODataCollectionWriterAsync() + { + return this.CreateODataCollectionWriterAsync(null); + } + + /// + /// Asynchronously creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The item type of the collection being written or null if no metadata is available. + /// A running task for the created collection writer. + public Task CreateODataCollectionWriterAsync(IEdmTypeReference itemTypeReference) + { + this.VerifyCanCreateODataCollectionWriter(itemTypeReference); + return this.WriteToOutputAsync( + ODataPayloadKind.Collection, + (context) => context.CreateODataCollectionWriterAsync(itemTypeReference)); + } +#endif + + /// Creates an to write a batch of requests or responses. + /// The created batch writer. + public ODataBatchWriter CreateODataBatchWriter() + { + this.VerifyCanCreateODataBatchWriter(); + return this.WriteToOutput( + ODataPayloadKind.Batch, + (context) => context.CreateODataBatchWriter()); + } + +#if PORTABLELIB + /// Asynchronously creates an to write a batch of requests or responses. + /// A running task for the created batch writer. + public Task CreateODataBatchWriterAsync() + { + this.VerifyCanCreateODataBatchWriter(); + return this.WriteToOutputAsync( + ODataPayloadKind.Batch, + (context) => context.CreateODataBatchWriterAsync()); + } +#endif + + /// + /// Creates an to write a parameter payload. + /// + /// The operation whose parameters will be written. + /// The created parameter writer. + public ODataParameterWriter CreateODataParameterWriter(IEdmOperation operation) + { + this.VerifyCanCreateODataParameterWriter(operation); + return this.WriteToOutput( + ODataPayloadKind.Parameter, + (context) => context.CreateODataParameterWriter(operation)); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a parameter payload. + /// + /// The operation whose parameters will be written. + /// A running task for the created parameter writer. + public Task CreateODataParameterWriterAsync(IEdmOperation operation) + { + this.VerifyCanCreateODataParameterWriter(operation); + return this.WriteToOutputAsync( + ODataPayloadKind.Parameter, + (context) => context.CreateODataParameterWriterAsync(operation)); + } +#endif + + /// Writes a service document with the specified as the message payload. + /// The service document to write. + public void WriteServiceDocument(ODataServiceDocument serviceDocument) + { + this.VerifyCanWriteServiceDocument(serviceDocument); + this.WriteToOutput( + ODataPayloadKind.ServiceDocument, + (context) => context.WriteServiceDocument(serviceDocument)); + } + +#if PORTABLELIB + /// Asynchronously writes a service document with the specified as the message payload. + /// A task representing the asynchronous operation of writing the service document. + /// The service document to write. + public Task WriteServiceDocumentAsync(ODataServiceDocument serviceDocument) + { + this.VerifyCanWriteServiceDocument(serviceDocument); + return this.WriteToOutputAsync( + ODataPayloadKind.ServiceDocument, + (context) => context.WriteServiceDocumentAsync(serviceDocument)); + } +#endif + + /// Writes an as the message payload. + /// The property to write. + public void WriteProperty(ODataProperty property) + { + this.VerifyCanWriteProperty(property); + this.WriteToOutput( + ODataPayloadKind.Property, + (context) => context.WriteProperty(property)); + } + +#if PORTABLELIB + /// Asynchronously writes an as the message payload. + /// A task representing the asynchronous operation of writing the property. + /// The property to write + public Task WritePropertyAsync(ODataProperty property) + { + this.VerifyCanWriteProperty(property); + return this.WriteToOutputAsync( + ODataPayloadKind.Property, + (context) => context.WritePropertyAsync(property)); + } +#endif + + /// Writes an as the message payload. + /// The error to write. + /// A flag indicating whether debug information (for example, the inner error from the ) should be included in the payload. This should only be used in debug scenarios. + public void WriteError(ODataError error, bool includeDebugInformation) + { + // We currently assume that the error is top-level if no create/write method has been called on this message writer. + // It is possible that the user would create a Json writer, but writes nothing to it before writes the first error. + // For example it is valid to call CreateResourceWriter() and then WriteError(). In that case the Json payload would + // contain just an error without the Json wrapper. + if (this.outputContext == null) + { + // Top-level error + this.VerifyCanWriteTopLevelError(error); + this.WriteToOutput( + ODataPayloadKind.Error, + (context) => context.WriteError(error, includeDebugInformation)); + return; + } + + // In-stream error + this.VerifyCanWriteInStreamError(error); + this.outputContext.WriteInStreamError(error, includeDebugInformation); + } + +#if PORTABLELIB + /// Asynchronously writes an as the message payload. + /// A task representing the asynchronous operation of writing the error. + /// The error to write. + /// A flag indicating whether debug information (for example, the inner error from the ) should be included in the payload. This should only be used in debug scenarios. + public Task WriteErrorAsync(ODataError error, bool includeDebugInformation) + { + // We currently assume that the error is top-level if no create/write method has been called on this message writer. + // It is possible that the user would create a Json writer, but writes nothing to it before writes the first error. + // For example it is valid to call CreateResourceWriter() and then WriteError(). In that case the Json payload would + // contain just an error without the Json wrapper. + if (this.outputContext == null) + { + // Top-level error + this.VerifyCanWriteTopLevelError(error); + return this.WriteToOutputAsync( + ODataPayloadKind.Error, + (context) => context.WriteErrorAsync(error, includeDebugInformation)); + } + + // In-stream error + this.VerifyCanWriteInStreamError(error); + return this.outputContext.WriteInStreamErrorAsync(error, includeDebugInformation); + } +#endif + + /// Writes the result of a $ref query as the message payload. + /// The entity reference links to write as message payload. + public void WriteEntityReferenceLinks(ODataEntityReferenceLinks links) + { + this.VerifyCanWriteEntityReferenceLinks(links); + this.WriteToOutput( + ODataPayloadKind.EntityReferenceLinks, + (context) => context.WriteEntityReferenceLinks(links)); + } + +#if PORTABLELIB + /// Asynchronously writes the result of a $ref query as the message payload. + /// A task representing the asynchronous writing of the entity reference links. + /// The entity reference links to write as message payload. + public Task WriteEntityReferenceLinksAsync(ODataEntityReferenceLinks links) + { + this.VerifyCanWriteEntityReferenceLinks(links); + return this.WriteToOutputAsync( + ODataPayloadKind.EntityReferenceLinks, + (context) => context.WriteEntityReferenceLinksAsync(links)); + } +#endif + + /// Writes a singleton result of a $ref query as the message payload. + /// The entity reference link to write as the message payload. + public void WriteEntityReferenceLink(ODataEntityReferenceLink link) + { + this.VerifyCanWriteEntityReferenceLink(link); + this.WriteToOutput( + ODataPayloadKind.EntityReferenceLink, + (context) => context.WriteEntityReferenceLink(link)); + } + +#if PORTABLELIB + /// Asynchronously writes a singleton result of a $ref query as the message payload. + /// A running task representing the writing of the link. + /// The link result to write as the message payload. + public Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink link) + { + this.VerifyCanWriteEntityReferenceLink(link); + return this.WriteToOutputAsync( + ODataPayloadKind.EntityReferenceLink, + (context) => context.WriteEntityReferenceLinkAsync(link)); + } +#endif + + /// Writes a single value as the message body. + /// The value to write. + public void WriteValue(object value) + { + ODataPayloadKind payloadKind = this.VerifyCanWriteValue(value); + this.WriteToOutput( + payloadKind, + (context) => context.WriteValue(value)); + } + +#if PORTABLELIB + /// Asynchronously writes a single value as the message body. + /// A running task representing the writing of the value. + /// The value to write. + public Task WriteValueAsync(object value) + { + ODataPayloadKind payloadKind = this.VerifyCanWriteValue(value); + return this.WriteToOutputAsync( + payloadKind, + (context) => context.WriteValueAsync(value)); + } +#endif + + /// Writes the metadata document as the message body. + public void WriteMetadataDocument() + { + this.VerifyCanWriteMetadataDocument(); + this.WriteToOutput( + ODataPayloadKind.MetadataDocument, + (context) => context.WriteMetadataDocument()); + } + + /// implementation to cleanup unmanaged resources of the writer. + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Sets the content-type and OData-Version headers on the message used by the message writer. + /// This method can be called if it is important to set all the message headers before calling any of the + /// write (or writer creation) methods on the . + /// If it is sufficient to set the headers when the write (or writer creation) methods on the + /// are called, you don't have to call this method and setting the headers will happen automatically. + /// + /// The kind of payload to be written with this message writer. + /// The used for the specified . + internal ODataFormat SetHeaders(ODataPayloadKind payloadKind) + { + Debug.Assert(payloadKind != ODataPayloadKind.Unsupported, "payloadKind != ODataPayloadKind.Unsupported"); + + this.writerPayloadKind = payloadKind; + + // Make sure we have a version set on the message writer settings; if none was specified on the settings, try to read + // it from the message headers. If not specified in the headers either, fall back to a default. + // NOTE: This method will potentially also set the OData-Version header. + this.EnsureODataVersion(); + Debug.Assert(this.settings.Version.HasValue, "ODataVersion must have been set by now."); + + // Make sure we have a content-type and compute the format from the settings and/or the message headers. + // NOTE: This method will potentially also set the content type header. + this.EnsureODataFormatAndContentType(); + return this.format; + } + + private static IServiceProvider GetContainer(T message) + where T : class + { +#if PORTABLELIB + var containerProvider = message as IContainerProvider; + return containerProvider == null ? null : containerProvider.Container; +#else + return null; +#endif + } + + private static IEdmModel GetModel(IServiceProvider container) + { +#if PORTABLELIB + return container == null ? EdmCoreModel.Instance : container.GetRequiredService(); +#else + return EdmCoreModel.Instance; +#endif + } + + /// + /// If no headers have been set, sets the content-type and OData-Version headers on the message used by the message writer. + /// If headers have been set explicitly (via ODataUtils.SetHeaderForPayload) this method verifies that the payload kind used to + /// create the headers is the same as the one being passed in . + /// + /// The kind of payload to be written with this message writer. + private void SetOrVerifyHeaders(ODataPayloadKind payloadKind) + { + Debug.Assert(payloadKind != ODataPayloadKind.Unsupported, "payloadKind != ODataPayloadKind.Unsupported"); + + // verify that no payload kind has been set or that the payload kind set previously and the + // payload that is attempted to being written are the same + this.VerifyPayloadKind(payloadKind); + + if (this.writerPayloadKind == ODataPayloadKind.Unsupported) + { + // no payload kind or headers have been set; set them now + this.SetHeaders(payloadKind); + } + } + + /// + /// Ensures that the version of the OData protocol is set. + /// + /// + /// If a version is specified explicitly on the writer settings, it is used. + /// Otherwise the method tries to read the version from the message headers. + /// If there is a version header but the value cannot be parsed, we fail. + /// If there is no version header, we fall back to the default version. + /// + private void EnsureODataVersion() + { + // if no version was specified in the user settings, try to read it from the message headers + if (!this.settings.Version.HasValue) + { + // Read the version header and parse it; fail if we can't parse it. + // Fall back to a default if we don't find a version header. + this.settings.Version = ODataUtilsInternal.GetODataVersion(this.message, ODataConstants.ODataDefaultProtocolVersion); + Debug.Assert(this.settings.Version.HasValue, "The version must have been set by now."); + + // Append OData-Version header if it hasn't been set. + if (string.IsNullOrEmpty(this.message.GetHeader(ODataConstants.ODataVersionHeader))) + { + ODataUtilsInternal.SetODataVersion(this.message, this.settings); + } + } + else + { + // Set the OData-Version + ODataUtilsInternal.SetODataVersion(this.message, this.settings); + } + } + + /// + /// Ensures that the OData format is computed and set; if needed, sets the content type + /// header of the message. + /// + /// + /// This method computes and ensures that a content type exists and computes the + /// OData format from it. If a content type is explicitly specified through + /// + /// or it will be used. If no + /// content type is specified in either place, the message headers are checked for + /// a content type header. + /// If the content type is computed from settings, the content type header is set on the message. + /// + private void EnsureODataFormatAndContentType() + { + Debug.Assert(this.writerPayloadKind != ODataPayloadKind.Unsupported, "Writer payload kind should have been set by now."); + + string contentType = null; + + // If neither format nor accept headers were specified in the writer settings, try to read the content type from the message headers. + if (!this.settings.UseFormat.HasValue) + { + contentType = this.message.GetHeader(ODataConstants.ContentTypeHeader); + contentType = contentType == null ? null : contentType.Trim(); + } + + // If we found a content type header, use it. Otherwise use the default behavior. + if (!string.IsNullOrEmpty(contentType)) + { + ODataPayloadKind computedPayloadKind; + this.format = MediaTypeUtils.GetFormatFromContentType(contentType, new ODataPayloadKind[] { this.writerPayloadKind }, this.mediaTypeResolver, out this.mediaType, out this.encoding, out computedPayloadKind); + Debug.Assert(this.writerPayloadKind == computedPayloadKind, "The payload kinds must always match."); + + if (this.settings.HasJsonPaddingFunction()) + { + // Note: we change the media type being written from "application/json" to "text/javascript", + // but we internally keep "application/json" as the value of the mediaType field. + contentType = MediaTypeUtils.AlterContentTypeForJsonPadding(contentType); + + // Override the header even though they set it. + this.message.SetHeader(ODataConstants.ContentTypeHeader, contentType); + } + } + else + { + // Determine the content type and format from the settings. Note that if neither format nor accept headers have been specified in the settings + // we fall back to a default (of null accept headers). + this.format = MediaTypeUtils.GetContentTypeFromSettings(this.settings, this.writerPayloadKind, this.mediaTypeResolver, out this.mediaType, out this.encoding); + + IEnumerable> updatedParameters; + contentType = format.GetContentType(this.mediaType, this.encoding, this.writingResponse, out updatedParameters); + + // Re-create the media type if the parameters list is updated. + if (this.mediaType.Parameters != updatedParameters) + { + this.mediaType = new ODataMediaType(mediaType.Type, mediaType.SubType, updatedParameters); + } + + if (this.settings.HasJsonPaddingFunction()) + { + // Note: we change the media type being written from "application/json" to "text/javascript", + // but we internally keep "application/json" as the value of the mediaType field. + contentType = MediaTypeUtils.AlterContentTypeForJsonPadding(contentType); + } + + // NOTE: set the content type header here since all headers have to be set before getting the stream + this.message.SetHeader(ODataConstants.ContentTypeHeader, contentType); + } + } + + /// + /// Verifies that async writer can be created. + /// + private void VerifyCanCreateODataAsyncWriter() + { + if (!this.writingResponse) + { + throw new ODataException(Strings.ODataMessageWriter_AsyncInRequest); + } + + // VerifyWriterNotDisposedAndNotUsed changes the state of the message writer, it should be called after + // we check the error conditions above. + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that resource set writer can be created. + /// + private void VerifyCanCreateODataResourceSetWriter() + { + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that delta writer can be created. + /// + private void VerifyCanCreateODataDeltaWriter() + { + if (!this.writingResponse) + { + throw new ODataException(Strings.ODataMessageWriter_DeltaInRequest); + } + + // VerifyWriterNotDisposedAndNotUsed changes the state of the message writer, it should be called after + // we check the error conditions above. + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that resource writer can be created. + /// + private void VerifyCanCreateODataResourceWriter() + { + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that collection writer can be created. + /// there is also a similar ODataParameterWriterCore.VerifyCanCreateCollectionWriter() method. + /// + /// The item type of the collection being written or null if no metadata is available. + [SuppressMessage("Microsoft.Naming", "CA2204:LiteralsShouldBeSpelledCorrectly", Justification = "Names are correct. String can't be localized after string freeze.")] + private void VerifyCanCreateODataCollectionWriter(IEdmTypeReference itemTypeReference) + { + if (itemTypeReference != null && !(itemTypeReference.IsPrimitive() || itemTypeReference.IsComplex() || itemTypeReference.IsEnum() || itemTypeReference.IsTypeDefinition())) + { + throw new ODataException(Strings.ODataMessageWriter_NonCollectionType(itemTypeReference.FullName())); + } + + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that batch writer can be created. + /// + private void VerifyCanCreateODataBatchWriter() + { + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that parameter writer can be created. + /// + /// The operation whose parameters will be written. + private void VerifyCanCreateODataParameterWriter(IEdmOperation operation) + { + if (this.writingResponse) + { + throw new ODataException(Strings.ODataParameterWriter_CannotCreateParameterWriterOnResponseMessage); + } + + if (operation != null) + { + // check that we have a user model + if (!this.model.IsUserModel()) + { + throw new ODataException(Strings.ODataMessageWriter_CannotSpecifyOperationWithoutModel); + } + } + + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that service document can be written. + /// + /// The service document to write. + private void VerifyCanWriteServiceDocument(ODataServiceDocument serviceDocument) + { + ExceptionUtils.CheckArgumentNotNull(serviceDocument, "serviceDocument"); + + if (!this.writingResponse) + { + throw new ODataException(Strings.ODataMessageWriter_ServiceDocumentInRequest); + } + + // VerifyWriterNotDisposedAndNotUsed changes the state of the message writer, it should be called after + // we check the error conditions above. + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that property can be written. + /// + /// The property to write. + private void VerifyCanWriteProperty(ODataProperty property) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + if (property.Value is ODataStreamReferenceValue) + { + throw new ODataException(Strings.ODataMessageWriter_CannotWriteStreamPropertyAsTopLevelProperty(property.Name)); + } + + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that top-level error can be written. + /// + /// The error to write. + private void VerifyCanWriteTopLevelError(ODataError error) + { + ExceptionUtils.CheckArgumentNotNull(error, "error"); + + if (!this.writingResponse) + { + // errors can only be written for response messages + throw new ODataException(Strings.ODataMessageWriter_ErrorPayloadInRequest); + } + + // Note that this verifies that the writer is only used once, so it also verifies that + // there's nothing written before the error. + this.VerifyWriterNotDisposedAndNotUsed(); + + // Mark it as error written so that we don't allow writing in-stream errors after this. + this.writeErrorCalled = true; + } + + /// + /// Verifies that in-stream error can be written. + /// + /// The error to write. + private void VerifyCanWriteInStreamError(ODataError error) + { + ExceptionUtils.CheckArgumentNotNull(error, "error"); + + this.VerifyNotDisposed(); + + // We only allow writing top-level error to response messages. Do we have any scenario to write in-stream errors to request messages? + // We decided to not allow in-stream errors in requests. + if (!this.writingResponse) + { + // errors can only be written for response messages + throw new ODataException(Strings.ODataMessageWriter_ErrorPayloadInRequest); + } + + if (this.writeErrorCalled) + { + throw new ODataException(Strings.ODataMessageWriter_WriteErrorAlreadyCalled); + } + + this.writeErrorCalled = true; + + // Need to mark the writer as already used, so that we don't allow any other calls to it. + this.writeMethodCalled = true; + } + + /// + /// Verifies that entity reference links can be written. + /// + /// The entity reference links to write as message payload. + private void VerifyCanWriteEntityReferenceLinks(ODataEntityReferenceLinks links) + { + // NOTE: we decided to not stream links for now but only make writing them async. + ExceptionUtils.CheckArgumentNotNull(links, "ref"); + + // Top-level EntityReferenceLinks payload write requests are not allowed. + if (!this.writingResponse) + { + throw new ODataException(Strings.ODataMessageWriter_EntityReferenceLinksInRequestNotAllowed); + } + + // VerifyWriterNotDisposedAndNotUsed changes the state of the message writer, it should be called after + // we check the error conditions above. + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that entity reference link can be written. + /// + /// The link result to write as message payload. + private void VerifyCanWriteEntityReferenceLink(ODataEntityReferenceLink link) + { + ExceptionUtils.CheckArgumentNotNull(link, "link"); + + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that value can be written. + /// + /// The value to write. + /// The payload kind to use when writing this value. + private ODataPayloadKind VerifyCanWriteValue(object value) + { + if (value == null) + { + // TODO: OIPI doc seems to indicate in Section 2.2.6.4.1 that 'null' is permissible but the product does not support it. + // We also throw in this case. + throw new ODataException(Strings.ODataMessageWriter_CannotWriteNullInRawFormat); + } + + this.VerifyWriterNotDisposedAndNotUsed(); + + // We cannot use ODataRawValueUtils.TryConvertPrimitiveToString for all cases since binary values are + // converted into unencoded byte streams in the raw format + // (as opposed to base64 encoded byte streams in the ODataRawValueUtils); see OIPI 2.2.6.4.1. + return value is byte[] ? ODataPayloadKind.BinaryValue : ODataPayloadKind.Value; + } + + /// + /// Verifies that metadata document can be written. + /// + private void VerifyCanWriteMetadataDocument() + { + if (!this.writingResponse) + { + throw new ODataException(Strings.ODataMessageWriter_MetadataDocumentInRequest); + } + + // check that we have a user model + if (!this.model.IsUserModel()) + { + throw new ODataException(Strings.ODataMessageWriter_CannotWriteMetadataWithoutModel); + } + + // VerifyWriterNotDisposedAndNotUsed changes the state of the message writer, it should be called after + // we check the error conditions above. + this.VerifyWriterNotDisposedAndNotUsed(); + } + + /// + /// Verifies that the ODataMessageWriter has not been disposed and has not been used before. An ODataMessageWriter + /// can only be used to write a single message payload but can't be reused later except for writing an in-stream error. + /// + private void VerifyWriterNotDisposedAndNotUsed() + { + this.VerifyNotDisposed(); + if (this.writeMethodCalled) + { + throw new ODataException(Strings.ODataMessageWriter_WriterAlreadyUsed); + } + + this.writeMethodCalled = true; + } + + /// + /// Check if the object has been disposed. Throws an ObjectDisposedException if the object has already been disposed. + /// + private void VerifyNotDisposed() + { + if (this.isDisposed) + { + throw new ObjectDisposedException(this.GetType().FullName); + } + } + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + private void Dispose(bool disposing) + { + this.isDisposed = true; + if (disposing) + { + try + { + if (this.outputContext != null) + { + this.outputContext.Dispose(); + } + } + finally + { + this.outputContext = null; + } + } + } + + /// + /// Verifies that, if a payload kind has been set via SetHeaders, the payload kind that + /// is being written is the same. + /// + /// The payload kind that is attempted to write. + private void VerifyPayloadKind(ODataPayloadKind payloadKindToWrite) + { + Debug.Assert(payloadKindToWrite != ODataPayloadKind.Unsupported, "payloadKindToWrite != ODataPayloadKind.Unsupported"); + + if (this.writerPayloadKind != ODataPayloadKind.Unsupported && this.writerPayloadKind != payloadKindToWrite) + { + // if a payload kind has been set via SetHeaders that is not the same as the payload kind + // that is attempted to write, we fail. + throw new ODataException(Strings.ODataMessageWriter_IncompatiblePayloadKinds(this.writerPayloadKind, payloadKindToWrite)); + } + } + + private ODataMessageInfo GetOrCreateMessageInfo(Stream messageStream, bool isAsync) + { + if (this.messageInfo == null) + { + if (this.container == null) + { + this.messageInfo = new ODataMessageInfo(); + } + else + { + this.messageInfo = this.container.GetRequiredService(); + } + + this.messageInfo.Encoding = this.encoding; + this.messageInfo.IsResponse = this.writingResponse; + this.messageInfo.IsAsync = isAsync; + this.messageInfo.MediaType = this.mediaType; + this.messageInfo.Model = this.model; + this.messageInfo.PayloadUriConverter = this.payloadUriConverter; + this.messageInfo.Container = this.container; + this.messageInfo.MessageStream = messageStream; + } + + return this.messageInfo; + } + + /// + /// Creates an output context and invokes a write operation on it. + /// + /// The payload kind to write. + /// The write operation to invoke on the output. + private void WriteToOutput(ODataPayloadKind payloadKind, Action writeAction) + { + // Set the content type header here since all headers have to be set before getting the stream + this.SetOrVerifyHeaders(payloadKind); + + // Create the output context + this.outputContext = this.format.CreateOutputContext( + this.GetOrCreateMessageInfo(this.message.GetStream(), false), + this.settings); + writeAction(this.outputContext); + } + + /// + /// Creates an output context and invokes a write operation on it. + /// + /// The type of the result of the write operation. + /// The payload kind to write. + /// The write operation to invoke on the output. + /// The result of the write operation. + private TResult WriteToOutput(ODataPayloadKind payloadKind, Func writeFunc) + { + // Set the content type header here since all headers have to be set before getting the stream + this.SetOrVerifyHeaders(payloadKind); + + // Create the output context + this.outputContext = this.format.CreateOutputContext( + this.GetOrCreateMessageInfo(this.message.GetStream(), false), + this.settings); + return writeFunc(this.outputContext); + } + +#if PORTABLELIB + /// + /// Creates an output context and invokes a write operation on it. + /// + /// The payload kind to write. + /// The write operation to invoke on the output. + /// Task which represents the pending write operation. + private Task WriteToOutputAsync(ODataPayloadKind payloadKind, Func writeAsyncAction) + { + // Set the content type header here since all headers have to be set before getting the stream + this.SetOrVerifyHeaders(payloadKind); + + // Create the output context + return this.message.GetStreamAsync() + .FollowOnSuccessWithTask( + streamTask => this.format.CreateOutputContextAsync( + this.GetOrCreateMessageInfo(streamTask.Result, true), + this.settings)) + .FollowOnSuccessWithTask( + createOutputContextTask => + { + this.outputContext = createOutputContextTask.Result; + return writeAsyncAction(this.outputContext); + }); + } + + /// + /// Creates an output context and invokes a write operation on it. + /// + /// The type of the result of the write operation. + /// The payload kind to write. + /// The write operation to invoke on the output. + /// Task which represents the pending write operation. + private Task WriteToOutputAsync(ODataPayloadKind payloadKind, Func> writeFunc) + { + // Set the content type header here since all headers have to be set before getting the stream + this.SetOrVerifyHeaders(payloadKind); + + // Create the output context + return this.message.GetStreamAsync() + .FollowOnSuccessWithTask( + streamTask => this.format.CreateOutputContextAsync( + this.GetOrCreateMessageInfo(streamTask.Result, true), + this.settings)) + .FollowOnSuccessWithTask( + createOutputContextTask => + { + this.outputContext = createOutputContextTask.Result; + return writeFunc(this.outputContext); + }); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs new file mode 100644 index 0000000..9fc860a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs @@ -0,0 +1,421 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using Microsoft.OData.Buffers; + using Microsoft.OData.UriParser; + #endregion Namespaces + + /// + /// Configuration settings for OData message writers. + /// + public sealed class ODataMessageWriterSettings + { + /// + /// The acceptable charsets used to the determine the encoding of the message. + /// This is a comma separated list of charsets as specified in RFC 2616, Section 14.2 + /// + private string acceptCharSets; + + /// + /// The acceptable media types used to determine the content type of the message. + /// This is a comma separated list of content types as specified in RFC 2616, Section 14.1 + /// + private string acceptMediaTypes; + + /// + /// The base uri used in payload. + /// + private Uri baseUri; + + /// + /// The format to use when writing the payload; this replaces the 'AcceptHeader' and 'AcceptCharSetHeader' + /// fields and uses the default values for the respective format. If null is specified + /// the default format and the default media type will be picked depending on the writer these settings are used with. + /// + private ODataFormat format; + + /// Quotas to use for limiting resource consumption when writing an OData message. + private ODataMessageQuotas messageQuotas; + + /// + /// The parse result of request Uri + /// + private ODataUri odataUri; + + /// + /// Func to evaluate whether an annotation should be written by the writer. The func should return true if the annotation should + /// be written and false if the annotation should be skipped. + /// + private Func shouldIncludeAnnotation; + + /// + /// true if the Format property should be used to compute the media type; + /// false if AcceptableMediaTypes and AcceptableCharsets should be used. + /// null if neither the format nor the acceptable media types/charsets have been set. + /// + private bool? useFormat; + + /// + /// Validation settings. + /// + private ValidationKinds validations; + + /// Initializes a new instance of the class with default settings. + public ODataMessageWriterSettings() + { + this.EnableMessageStreamDisposal = true; + this.EnableCharactersCheck = false; + this.Validations = ValidationKinds.All; + this.Validator = new WriterValidator(this); + this.LibraryCompatibility = ODataLibraryCompatibility.Latest; + } + + /// + /// Gets or sets library compatibility version. Default value is , + /// + public ODataLibraryCompatibility LibraryCompatibility { get; set; } + + /// + /// Gets or sets validations to perform. Default value is , + /// + public ValidationKinds Validations + { + get + { + return validations; + } + + set + { + validations = value; + ThrowIfTypeConflictsWithMetadata = (validations & ValidationKinds.ThrowIfTypeConflictsWithMetadata) != 0; + ThrowOnDuplicatePropertyNames = (validations & ValidationKinds.ThrowOnDuplicatePropertyNames) != 0; + ThrowOnUndeclaredPropertyForNonOpenType = (validations & ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType) != 0; + } + } + + /// Gets or sets the document base URI which is used as base for all relative URIs. + /// The document base URI which is used as base for all relative URIs. + /// + /// Base URI is context URI; if the URI does not end with a slash, a slash would be appended automatically. + /// + public Uri BaseUri + { + get + { + return this.baseUri; + } + + set + { + this.baseUri = UriUtils.EnsureTaillingSlash(value); + } + } + + /// Gets or sets a value that indicates whether the message stream will be disposed after finishing writing with the message. + /// true if the message stream will be disposed after finishing writing with the message; otherwise false. The default value is true. + public bool EnableMessageStreamDisposal { get; set; } + + /// + /// Flag to control whether the writer should check for valid Xml characters or not. + /// + public bool EnableCharactersCheck { get; set; } + + /// Gets or sets a callback function use to wrap the response from server. + /// The callback function used to wrap the response from server. + /// If it has a value and we are writing a JSON response, then we will wrap the entirety of the response in + /// the provided function name and parenthesis for JSONP. Otherwise this value is ignored. + public string JsonPCallback { get; set; } + + /// + /// Get/sets the character buffer pool. + /// + public ICharArrayPool ArrayPool { get; set; } + + /// + /// Quotas to use for limiting resource consumption when writing an OData message. + /// + public ODataMessageQuotas MessageQuotas + { + get + { + if (this.messageQuotas == null) + { + this.messageQuotas = new ODataMessageQuotas(); + } + + return this.messageQuotas; + } + + set + { + this.messageQuotas = value; + } + } + + /// + /// The OData Uri of an incoming request. Call 's methods, + /// and assign properties (e.g., ) to . + /// + public ODataUri ODataUri + { + get { return this.odataUri ?? (this.odataUri = new ODataUri()); } + set { this.odataUri = value; } + } + + /// Gets or sets the OData protocol version to be used for writing payloads. + /// The OData protocol version to be used for writing payloads. + public ODataVersion? Version { get; set; } + + /// + /// Gets the validator corresponding to the validation settings. + /// + internal IWriterValidator Validator { get; private set; } + + /// + /// Returns whether ThrowIfTypeConflictsWithMetadata validation should be performed. + /// + internal bool ThrowIfTypeConflictsWithMetadata { get; private set; } + + /// + /// Returns whether ThrowOnDuplicatePropertyNames validation setting is enabled. + /// + internal bool ThrowOnDuplicatePropertyNames { get; private set; } + + /// + /// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled. + /// + internal bool ThrowOnUndeclaredPropertyForNonOpenType { get; private set; } + + /// + /// The acceptable media types used to determine the content type of the message. + /// This is a comma separated list of content types as specified in RFC 2616, Section 14.1 + /// + /// A null or empty accept header means that all content types are acceptable. + /// For response messages this is usually the 'Accept' header of the request message. + internal string AcceptableMediaTypes + { + get + { + return this.acceptMediaTypes; + } + } + + /// + /// The acceptable charsets used to the determine the encoding of the message. + /// This is a comma separated list of charsets as specified in RFC 2616, Section 14.2 + /// + /// A null or empty accept charset header means that all charsets are acceptable. + /// For response messages this is usually the 'Accept-Charset' header of the request message. + internal string AcceptableCharsets + { + get + { + return this.acceptCharSets; + } + } + + /// + /// The format to use when writing the payload; this replaces the 'AcceptHeader' and 'AcceptCharSetHeader' + /// properties and uses the default values for the respective format. If null is specified + /// the default format and the default media type will be picked depending on the writer these settings are used with. + /// + internal ODataFormat Format + { + get + { + return this.format; + } + } + + /// + /// Gets the value indicating whether the payload represents an individual property + /// + internal bool IsIndividualProperty + { + get + { + return this.ODataUri.Path != null && this.ODataUri.Path.IsIndividualProperty(); + } + } + + /// + /// Gets the metadata document URI that has been set on the settings, or null if it has not been set. + /// + internal Uri MetadataDocumentUri + { + get + { + return this.ODataUri.MetadataDocumentUri; + } + } + + /// + /// true if the Format property should be used to compute the media type; + /// false if AcceptableMediaTypes and AcceptableCharsets should be used. + /// null if neither the format nor the acceptable media types/charsets have been set. + /// + internal bool? UseFormat + { + get + { + return this.useFormat; + } + } + + /// + /// Gets the SelectExpand clause used when generating metadata links. + /// + internal SelectExpandClause SelectExpandClause + { + get + { + return this.ODataUri.SelectAndExpand; + } + } + + /// + /// Gets the SelectedPropertiesNode clause generated from SelectExpandClause. + /// + internal SelectedPropertiesNode SelectedProperties + { + get + { + return this.SelectExpandClause != null + ? SelectedPropertiesNode.Create(this.SelectExpandClause) + : new SelectedPropertiesNode(SelectedPropertiesNode.SelectionType.EntireSubtree); + } + } + + /// + /// Func to evaluate whether an annotation should be written by the writer. The func should return true if the annotation should + /// be written and false if the annotation should be skipped. + /// + internal Func ShouldIncludeAnnotation + { + get + { + return this.shouldIncludeAnnotation; + } + + set + { + this.shouldIncludeAnnotation = value; + } + } + + /// + /// Creates a shallow copy of this . + /// + /// A shallow copy of this . + public ODataMessageWriterSettings Clone() + { + var copy = new ODataMessageWriterSettings(); + copy.CopyFrom(this); + return copy; + } + + /// Sets the acceptable media types and character sets from which the content type will be computed when writing the payload. + /// The acceptable media types used to determine the content type of the message. This is a comma separated list of content types as specified in RFC 2616, Section 14.1. + /// The acceptable charsets to use to determine the encoding of the message. This is a comma separated list of charsets as specified in RFC 2616, Section 14.2 + /// Calling this method replaces any previously set content-type settings. + public void SetContentType(string acceptableMediaTypes, string acceptableCharSets) + { + // Should accept json as application/json + this.acceptMediaTypes = string.Equals(acceptableMediaTypes, MimeConstants.MimeJsonSubType, StringComparison.OrdinalIgnoreCase) ? MimeConstants.MimeApplicationJson : acceptableMediaTypes; + this.acceptCharSets = acceptableCharSets; + this.format = null; + this.useFormat = false; + } + + /// Sets the format to be used when writing the payload. This will automatically set a compatible content type header. + /// The format to use for writing the payload. + /// Calling this method replaces any previously set content-type settings. + public void SetContentType(ODataFormat payloadFormat) + { + this.acceptCharSets = null; + this.acceptMediaTypes = null; + this.format = payloadFormat; + this.useFormat = true; + } + + internal static ODataMessageWriterSettings CreateWriterSettings( + IServiceProvider container, + ODataMessageWriterSettings other) + { + ODataMessageWriterSettings writerSettings; + if (container == null) + { + writerSettings = new ODataMessageWriterSettings(); + } + else + { + writerSettings = container.GetRequiredService(); + } + + if (other != null) + { + writerSettings.CopyFrom(other); + } + + return writerSettings; + } + + /// Sets the URI of the metadata document. + /// The URI of the service document. + internal void SetServiceDocumentUri(Uri serviceDocumentUri) + { + this.ODataUri.ServiceRoot = serviceDocumentUri; + } + + /// + /// Determines if there is a JSON padding function defined. + /// + /// True if the JsonPCallback property is not null or empty. + internal bool HasJsonPaddingFunction() + { + return !string.IsNullOrEmpty(this.JsonPCallback); + } + + /// + /// Returns true to indicate that the annotation with the name should not be written, false otherwise. + /// + /// The name of the annotation in question. + /// Returns true to indicate that the annotation with the name should not be written, false otherwise. + internal bool ShouldSkipAnnotation(string annotationName) + { + return this.ShouldIncludeAnnotation == null || !this.ShouldIncludeAnnotation(annotationName); + } + + private void CopyFrom(ODataMessageWriterSettings other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + + this.acceptCharSets = other.acceptCharSets; + this.acceptMediaTypes = other.acceptMediaTypes; + this.BaseUri = other.BaseUri; + this.EnableMessageStreamDisposal = other.EnableMessageStreamDisposal; + this.EnableCharactersCheck = other.EnableCharactersCheck; + this.format = other.format; + this.JsonPCallback = other.JsonPCallback; + this.messageQuotas = new ODataMessageQuotas(other.MessageQuotas); + this.ODataUri = other.ODataUri.Clone(); + this.shouldIncludeAnnotation = other.shouldIncludeAnnotation; + this.useFormat = other.useFormat; + this.Version = other.Version; + this.LibraryCompatibility = other.LibraryCompatibility; + + this.validations = other.validations; + this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata; + this.ThrowOnDuplicatePropertyNames = other.ThrowOnDuplicatePropertyNames; + this.ThrowOnUndeclaredPropertyForNonOpenType = other.ThrowOnUndeclaredPropertyForNonOpenType; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataFormat.cs new file mode 100644 index 0000000..3e4f04a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataFormat.cs @@ -0,0 +1,168 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// The metadata OData format. + /// + internal sealed class ODataMetadataFormat : ODataFormat + { + /// + /// The text representation - the name of the format. + /// + /// The name of the format. + public override string ToString() + { + return "Metadata"; + } + + /// + /// Detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The set of s that are supported with the specified payload. + public override IEnumerable DetectPayloadKind( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + + // Metadata is not supported in requests! + return messageInfo.IsResponse + ? DetectPayloadKindImplementation(messageInfo, settings) + : Enumerable.Empty(); + } + + /// + /// Creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The newly created input context. + public override ODataInputContext CreateInputContext( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + return new ODataMetadataInputContext(messageInfo, messageReaderSettings); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// The newly created output context. + public override ODataOutputContext CreateOutputContext( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + return new ODataMetadataOutputContext(messageInfo, messageWriterSettings); + } + +#if PORTABLELIB + /// + /// Asynchronously detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// A task that when completed returns the set of s + /// that are supported with the specified payload. + public override Task> DetectPayloadKindAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + return messageInfo.IsResponse + ? Task.FromResult(DetectPayloadKindImplementation(messageInfo, settings)) + : TaskUtils.GetCompletedTask(Enumerable.Empty()); + } + + /// + /// Asynchronously creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// Task which when completed returned the newly created input context. + public override Task CreateInputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataMetadataFormat_CreateInputContextAsync)); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// Task which represents the pending create operation. + public override Task CreateOutputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataMetadataFormat_CreateOutputContextAsync)); + } +#endif + + /// + /// Detects the payload kind(s) from the message stream. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// An enumerable of zero or one payload kinds depending on whether the metadata payload kind was detected or not. + private static IEnumerable DetectPayloadKindImplementation( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + var detectionInfo = new ODataPayloadKindDetectionInfo(messageInfo, settings); + try + { + using (var reader = ODataMetadataReaderUtils.CreateXmlReader( + messageInfo.MessageStream, detectionInfo.GetEncoding(), detectionInfo.MessageReaderSettings)) + { + if (reader.TryReadToNextElement() + && string.CompareOrdinal(EdmConstants.EdmxName, reader.LocalName) == 0 + && reader.NamespaceURI == EdmConstants.EdmxOasisNamespace) + { + return new ODataPayloadKind[] { ODataPayloadKind.MetadataDocument }; + } + } + } + catch (XmlException) + { + // If we are not able to read the payload as XML it is not a metadata document. + // Return no detected payload kind below. + } + + return Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataInputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataInputContext.cs new file mode 100644 index 0000000..5752423 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataInputContext.cs @@ -0,0 +1,131 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Xml; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData +{ + /// + /// Implementation of the OData input for metadata documents. + /// + internal sealed class ODataMetadataInputContext : ODataInputContext + { + /// The XML reader used to parse the input. + /// Do not use this to actually read the input, instead use the xmlReader. + private XmlReader baseXmlReader; + + /// The XML reader to read from. + private BufferingXmlReader xmlReader; + + /// Constructor. + /// The context information for the message. + /// Configuration settings of the OData reader. + public ODataMetadataInputContext( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + : base(ODataFormat.Metadata, messageInfo, messageReaderSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + + try + { + // Which encoding do we use when reading XML payloads + this.baseXmlReader = ODataMetadataReaderUtils.CreateXmlReader(messageInfo.MessageStream, messageInfo.Encoding, messageReaderSettings); + + // We use the buffering reader here only for in-stream error detection (not for buffering). + this.xmlReader = new BufferingXmlReader( + this.baseXmlReader, + /*parentXmlReader*/ null, + messageReaderSettings.BaseUri, + /*disableXmlBase*/ false, + messageReaderSettings.MessageQuotas.MaxNestingDepth); + } + catch (Exception e) + { + // Dispose the message stream if we failed to create the input context. + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + messageInfo.MessageStream.Dispose(); + } + + throw; + } + } + + /// + /// Read a metadata document. + /// This method reads the metadata document from the input and returns + /// an that represents the read metadata document. + /// + /// The function to load referenced model xml. If null, will stop loading the referenced models. Normally it should throw no exception. + /// An representing the read metadata document. + internal override IEdmModel ReadMetadataDocument(Func getReferencedModelReaderFunc) + { + return this.ReadMetadataDocumentImplementation(getReferencedModelReaderFunc); + } + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (this.baseXmlReader != null) + { + ((IDisposable)this.baseXmlReader).Dispose(); + } + } + finally + { + this.baseXmlReader = null; + this.xmlReader = null; + } + } + + base.Dispose(disposing); + } + + /// + /// This methods reads the metadata from the input and returns an + /// representing the read metadata information. + /// + /// The function to load referenced model xml. If null, will stop loading the referenced models. Normally it should throw no exception. + /// An instance representing the read metadata. + private IEdmModel ReadMetadataDocumentImplementation(Func getReferencedModelReaderFunc) + { + IEdmModel model; + IEnumerable errors; + if (!CsdlReader.TryParse(this.xmlReader, getReferencedModelReaderFunc, out model, out errors)) + { + Debug.Assert(errors != null, "errors != null"); + + StringBuilder builder = new StringBuilder(); + foreach (EdmError error in errors) + { + builder.AppendLine(error.ToString()); + } + + throw new ODataException(Strings.ODataMetadataInputContext_ErrorReadingMetadata(builder.ToString())); + } + + Debug.Assert(model != null, "model != null"); + + return model; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataOutputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataOutputContext.cs new file mode 100644 index 0000000..319e317 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataMetadataOutputContext.cs @@ -0,0 +1,153 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData +{ + /// + /// RAW format output context. + /// + internal sealed class ODataMetadataOutputContext : ODataOutputContext + { + /// The message output stream. + private Stream messageOutputStream; + + /// The XmlWriter to write to. + private XmlWriter xmlWriter; + + /// + /// Constructor. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + internal ODataMetadataOutputContext( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + : base(ODataFormat.Metadata, messageInfo, messageWriterSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + Debug.Assert(!messageInfo.IsAsync, "Metadata output context is only supported in synchronous operations."); + + try + { + this.messageOutputStream = messageInfo.MessageStream; + this.xmlWriter = ODataMetadataWriterUtils.CreateXmlWriter(this.messageOutputStream, messageWriterSettings, messageInfo.Encoding); + } + catch (Exception e) + { + // Dispose the message stream if we failed to create the output context. + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + this.messageOutputStream.Dispose(); + } + + throw; + } + } + + /// + /// Synchronously flush the writer. + /// + internal void Flush() + { + this.AssertSynchronous(); + + // XmlWriter.Flush will call the underlying Stream.Flush. + // In the synchronous case the underlying stream is the message stream itself, which will then Flush as well. + this.xmlWriter.Flush(); + } + + /// + /// Writes an into the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// + /// This method is called if the ODataMessageWriter.WriteError is called once some other + /// write operation has already started. + /// The method should write the in-stream error representation for the specific format into the current payload. + /// Before the method is called no flush is performed on the output context or any active writer. + /// It is the responsibility of this method to flush the output before the method returns. + /// + internal override void WriteInStreamError(ODataError error, bool includeDebugInformation) + { + this.AssertSynchronous(); + + ODataMetadataWriterUtils.WriteError(this.xmlWriter, error, includeDebugInformation, this.MessageWriterSettings.MessageQuotas.MaxNestingDepth); + this.Flush(); + } + + /// + /// Writes the metadata document as the message body. + /// + /// It is the responsibility of this method to flush the output before the method returns. + internal override void WriteMetadataDocument() + { + this.AssertSynchronous(); + + IEnumerable errors; + if (!CsdlWriter.TryWriteCsdl(this.Model, this.xmlWriter, CsdlTarget.OData, out errors)) + { + Debug.Assert(errors != null, "errors != null"); + + StringBuilder builder = new StringBuilder(); + foreach (EdmError error in errors) + { + builder.AppendLine(error.ToString()); + } + + throw new ODataException(Strings.ODataMetadataOutputContext_ErrorWritingMetadata(builder.ToString())); + } + + this.Flush(); + } + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected override void Dispose(bool disposing) + { + try + { + if (this.xmlWriter != null) + { + // XmlWriter.Flush will call the underlying Stream.Flush. + this.xmlWriter.Flush(); + + // XmlWriter.Dispose calls XmlWriter.Close which writes missing end elements. + // Thus we can't dispose the XmlWriter since that might end up writing more data into the stream right here + // and thus callers would have no way to prevent us from writing synchronously into the underlying stream. + // Also in case of in-stream error we intentionally want to not write the end elements to keep the payload invalid. + // In the async case the underlying stream is the async buffered stream, so we have to flush that explicitely. + + // Dipose the message stream (note that we OWN this stream, so we always dispose it). + this.messageOutputStream.Dispose(); + } + } + finally + { + this.messageOutputStream = null; + this.xmlWriter = null; + } + + base.Dispose(disposing); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs new file mode 100644 index 0000000..26b4d7c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfo.cs @@ -0,0 +1,130 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using Microsoft.OData.Evaluation; + #endregion Namespaces + + /// + /// Represents a single link. + /// + [System.Diagnostics.DebuggerDisplay("{Name}")] + public sealed class ODataNestedResourceInfo : ODataItem + { + /// the metadata builder for this nested resource info. + private ODataResourceMetadataBuilder metadataBuilder; + + /// URI representing the Unified Resource Locator (Url) of the link as provided by the user or seen on the wire (never computed). + private Uri url; + + /// true if the navigation link has been set by the user or seen on the wire or computed by the metadata builder, false otherwise. + private bool hasNavigationLink; + + /// The association link URL for this navigation link as provided by the user or seen on the wire (never computed). + private Uri associationLinkUrl; + + /// true if the association link has been set by the user or seen on the wire or computed by the metadata builder, false otherwise. + private bool hasAssociationUrl; + + /// Gets or sets a value that indicates whether the nested resource info represents a collection or a resource. + /// true if the nested resource info represents a collection; false if the navigation represents a resource. + public bool? IsCollection + { + get; + set; + } + + /// Gets or sets the name of the link. + /// The name of the link. + public string Name + { + get; + set; + } + + /// Gets or sets the URI representing the Unified Resource Locator (URL) of the link. + /// The URI representing the Unified Resource Locator (URL) of the link. + public Uri Url + { + get + { + if (this.metadataBuilder != null && !this.IsComplex) + { + this.url = this.metadataBuilder.GetNavigationLinkUri(this.Name, this.url, this.hasNavigationLink); + this.hasNavigationLink = true; + } + + return this.url; + } + + set + { + this.url = value; + this.hasNavigationLink = true; + } + } + + /// The association link URL for this navigation link. + public Uri AssociationLinkUrl + { + get + { + if (this.metadataBuilder != null && !this.IsComplex) + { + this.associationLinkUrl = this.metadataBuilder.GetAssociationLinkUri(this.Name, this.associationLinkUrl, this.hasAssociationUrl); + this.hasAssociationUrl = true; + } + + return this.associationLinkUrl; + } + + set + { + this.associationLinkUrl = value; + this.hasAssociationUrl = true; + } + } + + /// Gets or sets the context url for this nested resource info. + /// The URI representing the context url of the nested resource info. + internal Uri ContextUrl { get; set; } + + /// Gets or sets metadata builder for this nested resource info. + /// The metadata builder used to compute values from model annotations. + internal ODataResourceMetadataBuilder MetadataBuilder + { + get + { + return this.metadataBuilder; + } + + set + { + Debug.Assert(value != null, "MetadataBuilder != null"); + + this.metadataBuilder = value; + } + } + + /// + /// Provides additional serialization information to the for this . + /// + internal ODataNestedResourceInfoSerializationInfo SerializationInfo + { + get; + set; + } + + /// + /// Whether this is a complex property. + /// + internal bool IsComplex { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfoSerializationInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfoSerializationInfo.cs new file mode 100644 index 0000000..6cdbb76 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNestedResourceInfoSerializationInfo.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class to provide additional serialization information to the for an . + /// + public sealed class ODataNestedResourceInfoSerializationInfo + { + /// + /// Gets or sets the value that indicates the nested resource for a complex property or collection of complex property. + /// + public bool IsComplex { get; set; } + + /// + /// Gets or sets the value that indicates the nested resource is undeclared or not. + /// + public bool IsUndeclared { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationReader.cs new file mode 100644 index 0000000..af31d00 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationReader.cs @@ -0,0 +1,134 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + /// + /// Wrapper for TextReader to listen for dispose. + /// + internal sealed class ODataNotificationReader : TextReader + { + private readonly TextReader textReader; + private IODataStreamListener listener; + + internal ODataNotificationReader(TextReader textReader, IODataStreamListener listener) + { + Debug.Assert(textReader != null, "Creating a notification reader for a null textReader."); + Debug.Assert(listener != null, "Creating a notification reader with a null textReader."); + + this.textReader = textReader; + this.listener = listener; + } + + /// + public override int GetHashCode() + { + return this.textReader.GetHashCode(); + } + + /// + public override string ToString() + { + return this.textReader.ToString(); + } + + /// + public override int Peek() + { + return this.textReader.Peek(); + } + + #region Read methods + + /// + public override int Read() + { + return this.textReader.Read(); + } + + /// + public override int Read(char[] buffer, int index, int count) + { + return this.textReader.Read(buffer, index, count); + } + + /// + public override int ReadBlock(char[] buffer, int index, int count) + { + return this.textReader.ReadBlock(buffer, index, count); + } + + /// + public override string ReadLine() + { + return this.textReader.ReadLine(); + } + + /// + public override string ReadToEnd() + { + return this.textReader.ReadToEnd(); + } + + #endregion + + #region asyncMethods + +#if PORTABLELIB + /// + public override Task ReadAsync(char[] buffer, int index, int count) + { + return this.textReader.ReadAsync(buffer, index, count); + } + + /// + public override Task ReadBlockAsync(char[] buffer, int index, int count) + { + return this.ReadBlockAsync(buffer, index, count); + } + + /// + public override Task ReadLineAsync() + { + return this.textReader.ReadLineAsync(); + } + + /// + public override Task ReadToEndAsync() + { + return this.textReader.ReadToEndAsync(); + } + +#endif + #endregion + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called from the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.listener != null) + { + // Tell the listener that the stream is being disposed. + this.listener.StreamDisposed(); + this.listener = null; + } + } + + this.textReader.Dispose(); + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationStream.cs new file mode 100644 index 0000000..c5c197b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationStream.cs @@ -0,0 +1,217 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Diagnostics; + using System.IO; + using System.Threading; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + /// + /// Wrapper to listen for dispose on a stream + /// + internal sealed class ODataNotificationStream : Stream + { + private readonly Stream stream; + private IODataStreamListener listener; + + internal ODataNotificationStream(Stream underlyingStream, IODataStreamListener listener) + { + Debug.Assert(underlyingStream != null, "Creating a notification stream for a null stream."); + Debug.Assert(listener != null, "Creating a notification stream with a null listener."); + + this.stream = underlyingStream; + this.listener = listener; + } + + /// + public override bool CanRead + { + get + { + return this.stream.CanRead; + } + } + + /// + public override bool CanSeek + { + get + { + return this.stream.CanSeek; + } + } + + /// + public override bool CanWrite + { + get + { + return this.stream.CanWrite; + } + } + + /// + public override long Length + { + get + { + return this.stream.Length; + } + } + + /// + public override long Position + { + get + { + return this.stream.Position; + } + + set + { + this.stream.Position = value; + } + } + + /// + public override bool CanTimeout + { + get + { + return this.stream.CanTimeout; + } + } + + /// + public override int ReadTimeout + { + get + { + return this.stream.ReadTimeout; + } + + set + { + this.stream.ReadTimeout = value; + } + } + + /// + public override int WriteTimeout + { + get + { + return this.stream.WriteTimeout; + } + + set + { + this.stream.WriteTimeout = value; + } + } + + /// + public override void Flush() + { + this.stream.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + return this.stream.Read(buffer, offset, count); + } + + /// + public override void SetLength(long value) + { + this.stream.SetLength(value); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.stream.Write(buffer, offset, count); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + return this.stream.Seek(offset, origin); + } + + /// + public override int ReadByte() + { + return this.stream.ReadByte(); + } + + /// + public override void WriteByte(byte value) + { + this.stream.WriteByte(value); + } + + /// + public override string ToString() + { + return this.stream.ToString(); + } + + #region async methods + +#if PORTABLELIB + + /// + public override Task FlushAsync(CancellationToken cancellationToken) + { + return this.stream.FlushAsync(cancellationToken); + } + + /// + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return this.stream.CopyToAsync(destination, bufferSize, cancellationToken); + } + + /// + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.stream.ReadAsync(buffer, offset, count, cancellationToken); + } + + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.stream.WriteAsync(buffer, offset, count, cancellationToken); + } +#endif + #endregion + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called from the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.listener != null) + { + // Tell the listener that the stream is being disposed. + this.listener.StreamDisposed(); + this.listener = null; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationWriter.cs new file mode 100644 index 0000000..4a860ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNotificationWriter.cs @@ -0,0 +1,341 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + /// + /// Wrapper for TextWriter to listen for dispose. + /// + internal sealed class ODataNotificationWriter : TextWriter + { + private readonly TextWriter textWriter; + private IODataStreamListener listener; + + internal ODataNotificationWriter(TextWriter textWriter, IODataStreamListener listener) + : base(System.Globalization.CultureInfo.InvariantCulture) + { + Debug.Assert(textWriter != null, "Creating a notification writer for a null textWriter."); + Debug.Assert(listener != null, "Creating a notification writer with a null listener."); + + this.textWriter = textWriter; + this.listener = listener; + } + + /// + public override Encoding Encoding + { + get + { + return this.textWriter.Encoding; + } + } + + /// + public override string NewLine + { + get + { + return this.textWriter.NewLine; + } + + set + { + this.textWriter.NewLine = value; + } + } + + /// + public override IFormatProvider FormatProvider + { + get + { + return this.textWriter.FormatProvider; + } + } + + /// + public override void Flush() + { + this.textWriter.Flush(); + } + + /// + public override int GetHashCode() + { + return this.textWriter.GetHashCode(); + } + + /// + public override string ToString() + { + return this.textWriter.ToString(); + } + + #region Write methods + /// + public override void Write(char value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(bool value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(string value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(char[] buffer) + { + this.textWriter.Write(buffer); + } + + /// + public override void Write(char[] buffer, int index, int count) + { + this.textWriter.Write(buffer, index, count); + } + + /// + public override void Write(string format, params object[] arg) + { + this.textWriter.Write(format, arg); + } + + /// + public override void Write(decimal value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(object value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(double value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(float value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(int value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(long value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(uint value) + { + this.textWriter.Write(value); + } + + /// + public override void Write(ulong value) + { + this.textWriter.Write(value); + } + + #endregion + + #region WriteLine methods + /// + public override void WriteLine() + { + this.textWriter.WriteLine(); + } + + /// + public override void WriteLine(bool value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(char value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(char[] buffer) + { + this.textWriter.WriteLine(buffer); + } + + /// + public override void WriteLine(char[] buffer, int index, int count) + { + this.textWriter.WriteLine(buffer, index, count); + } + + /// + public override void WriteLine(decimal value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(double value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(float value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(int value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(long value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(object value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(string format, params object[] arg) + { + this.textWriter.WriteLine(format, arg); + } + + /// + public override void WriteLine(string value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(uint value) + { + this.textWriter.WriteLine(value); + } + + /// + public override void WriteLine(ulong value) + { + this.textWriter.WriteLine(value); + } + + #endregion + + #region async methods + +#if PORTABLELIB + + /// + public override Task FlushAsync() + { + return this.textWriter.FlushAsync(); + } + + /// + public override Task WriteAsync(char value) + { + return this.textWriter.WriteAsync(value); + } + + /// + public override Task WriteAsync(char[] buffer, int index, int count) + { + return this.textWriter.WriteAsync(buffer, index, count); + } + + /// + public override Task WriteAsync(string value) + { + return this.textWriter.WriteAsync(value); + } + + /// + public override Task WriteLineAsync() + { + return this.textWriter.WriteLineAsync(); + } + + /// + public override Task WriteLineAsync(char value) + { + return this.textWriter.WriteLineAsync(value); + } + + /// + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + return this.textWriter.WriteLineAsync(buffer, index, count); + } + + /// + public override Task WriteLineAsync(string value) + { + return this.textWriter.WriteLineAsync(value); + } + +#endif + + #endregion + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called from the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.listener != null) + { + // Tell the listener that the stream is being disposed. + this.listener.StreamDisposed(); + this.listener = null; + } + } + + this.textWriter.Dispose(); + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNullValueBehaviorKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNullValueBehaviorKind.cs new file mode 100644 index 0000000..e7ca4ff --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataNullValueBehaviorKind.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// Represents the behavior of readers when reading property with null value. + public enum ODataNullValueBehaviorKind + { + /// + /// The default behavior - this means validate the null value against the declared type + /// and then report the null value. + /// + Default = 0, + + /// + /// This means to not report the value and not validate it against the model. + /// + /// + /// This setting can be used to correctly work with clients that send null values + /// for uninitialized properties in requests instead of omitting them altogether. + /// + IgnoreValue = 1, + + /// + /// This means to report the value, but not validate it against the model. + /// + DisableValidation = 2, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataObjectModelExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataObjectModelExtensions.cs new file mode 100644 index 0000000..274edd4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataObjectModelExtensions.cs @@ -0,0 +1,149 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- +namespace Microsoft.OData +{ + using Microsoft.OData.Edm; + + /// + /// Extension methods on the OData object model. + /// + public static class ODataObjectModelExtensions + { + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataResourceSet resourceSet, ODataResourceSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet"); + resourceSet.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataResourceBase resource, ODataResourceSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(resource, "resource"); + resource.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataResource resource, ODataResourceSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(resource, "resource"); + resource.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataNestedResourceInfo nestedResourceInfo, ODataNestedResourceInfoSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(nestedResourceInfo, "resource"); + nestedResourceInfo.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataProperty property, ODataPropertySerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + property.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataCollectionStart collectionStart, ODataCollectionStartSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(collectionStart, "collectionStart"); + collectionStart.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataDeltaResourceSet deltaResourceSet, ODataDeltaResourceSetSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet"); + ODataResourceSerializationInfo resourceSerializationInfo = new ODataResourceSerializationInfo() + { + NavigationSourceName = serializationInfo.EntitySetName, + NavigationSourceEntityTypeName = serializationInfo.EntityTypeName, + NavigationSourceKind = EdmNavigationSourceKind.EntitySet, + ExpectedTypeName = serializationInfo.ExpectedTypeName + }; + + deltaResourceSet.SerializationInfo = resourceSerializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataDeltaResourceSet deltaResourceSet, ODataResourceSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet"); + deltaResourceSet.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataDeltaDeletedEntry deltaDeletedEntry, ODataResourceSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(deltaDeletedEntry, "deltaDeletedEntry"); + ODataDeltaSerializationInfo resourceSerializationInfo = new ODataDeltaSerializationInfo() + { + NavigationSourceName = serializationInfo.NavigationSourceName, + }; + + deltaDeletedEntry.SerializationInfo = resourceSerializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataDeltaDeletedEntry deltaDeletedEntry, ODataDeltaSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(deltaDeletedEntry, "deltaDeletedEntry"); + deltaDeletedEntry.SerializationInfo = serializationInfo; + } + + /// + /// Provide additional serialization information to the for . + /// + /// The instance to set the serialization info. + /// The serialization info to set. + public static void SetSerializationInfo(this ODataDeltaLinkBase deltalink, ODataDeltaSerializationInfo serializationInfo) + { + ExceptionUtils.CheckArgumentNotNull(deltalink, "deltalink"); + deltalink.SerializationInfo = serializationInfo; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOperation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOperation.cs new file mode 100644 index 0000000..8ab3847 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOperation.cs @@ -0,0 +1,119 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using Microsoft.OData.Evaluation; + using Microsoft.OData.JsonLight; + #endregion + + /// + /// Represents a function or an action. + /// + public abstract class ODataOperation : ODataAnnotatable + { + /// the metadata builder for this operation. + private ODataResourceMetadataBuilder metadataBuilder; + + /// A human-readable description of the or the . + private string title; + + /// true if a title was provided by the user or seen on the wire, false otherwise. + private bool hasNonComputedTitle; + + /// A human-readable description of the or the , computed by the metadata builder. + private string computedTitle; + + /// The URI to invoke the or the . + private Uri target; + + /// true if a target was provided by the user or seen on the wire, false otherwise. + private bool hasNonComputedTarget; + + /// The URI to invoke the or the , computed by the metadata builder. + private Uri computedTarget; + + /// The cached full name of the operation to use. + private string operationFullName; + + /// The parameter names for this operation. + private string parameterNames; + + /// Gets or sets the URI to get metadata for the . + /// The URI to get metadata for the . + public Uri Metadata { get; set; } + + /// Gets or sets a human-readable description of the . + /// A human-readable description of the . + public string Title + { + get + { + return this.hasNonComputedTitle + ? this.title : + (this.computedTitle ?? (this.metadataBuilder == null ? null : this.computedTitle = this.metadataBuilder.GetOperationTitle(this.operationFullName))); + } + + set + { + this.title = value; + this.hasNonComputedTitle = true; + } + } + + /// Gets or sets the URI to invoke the . + /// The URI to invoke the . + public Uri Target + { + get + { + return this.hasNonComputedTarget + ? this.target + : (this.computedTarget ?? (this.metadataBuilder == null ? null : this.computedTarget = this.metadataBuilder.GetOperationTargetUri(this.operationFullName, this.BindingParameterTypeName, this.parameterNames))); + } + + set + { + this.target = value; + this.hasNonComputedTarget = true; + } + } + + /// + /// The binding parameter type name for this operation. + /// + internal string BindingParameterTypeName { get; set; } + + /// + /// Sets the metadata builder for this operation. + /// + /// The metadata builder used to compute values from model annotations. + /// The metadata document Uri. + internal void SetMetadataBuilder(ODataResourceMetadataBuilder builder, Uri metadataDocumentUri) + { + Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); + Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri"); + + ODataJsonLightValidationUtils.ValidateOperation(metadataDocumentUri, this); + this.metadataBuilder = builder; + this.operationFullName = ODataJsonLightUtils.GetFullyQualifiedOperationName(metadataDocumentUri, UriUtils.UriToString(this.Metadata), out this.parameterNames); + this.computedTitle = null; + this.computedTarget = null; + } + + /// + /// Gets the metadata builder for this operation. + /// + /// The metadata builder used to compute values. + internal ODataResourceMetadataBuilder GetMetadataBuilder() + { + return this.metadataBuilder; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOutputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOutputContext.cs new file mode 100644 index 0000000..2a8846a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataOutputContext.cs @@ -0,0 +1,708 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Base class for all output contexts, defines the interface + /// to be implemented by the specific formats. + /// + public abstract class ODataOutputContext : IDisposable + { + /// The format for this output context. + private readonly ODataFormat format; + + /// The message writer settings to be used for writing. + private readonly ODataMessageWriterSettings messageWriterSettings; + + /// Set to true if this context is writing a response payload. + private readonly bool writingResponse; + + /// true if the input should be written synchronously; false if it should be written asynchronously. + private readonly bool synchronous; + + /// The model to use. + private readonly IEdmModel model; + + /// The optional URL resolver to perform custom URL resolution for URLs written to the payload. + private readonly IODataPayloadUriConverter payloadUriConverter; + + /// The optional dependency injection container to get related services for message writing. + private readonly IServiceProvider container; + + /// The type resolver to use. + private readonly EdmTypeResolver edmTypeResolver; + + /// The payload value converter to use. + private readonly ODataPayloadValueConverter payloadValueConverter; + + /// The writer validator used in writing. + private readonly IWriterValidator writerValidator; + + /// + /// The simplified options used in writing. + /// + private readonly ODataSimplifiedOptions odataSimplifiedOptions; + + /// + /// Constructor. + /// + /// The format for this output context. + /// The context information for the message. + /// Configuration settings of the OData writer. + protected ODataOutputContext( + ODataFormat format, + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(format, "format"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + this.format = format; + this.messageWriterSettings = messageWriterSettings; + this.writingResponse = messageInfo.IsResponse; + this.synchronous = !messageInfo.IsAsync; + this.model = messageInfo.Model ?? EdmCoreModel.Instance; + this.payloadUriConverter = messageInfo.PayloadUriConverter; + this.container = messageInfo.Container; + this.edmTypeResolver = EdmTypeWriterResolver.Instance; + this.payloadValueConverter = ODataPayloadValueConverter.GetPayloadValueConverter(this.container); + this.writerValidator = messageWriterSettings.Validator; + this.odataSimplifiedOptions = ODataSimplifiedOptions.GetODataSimplifiedOptions(this.container, messageWriterSettings.Version); + } + + /// + /// The message writer settings to be used for writing. + /// + public ODataMessageWriterSettings MessageWriterSettings + { + get + { + return this.messageWriterSettings; + } + } + + /// + /// Set to true if a response is being written. + /// + public bool WritingResponse + { + get + { + return this.writingResponse; + } + } + + /// + /// true if the output should be written synchronously; false if it should be written asynchronously. + /// + public bool Synchronous + { + get + { + return this.synchronous; + } + } + + /// + /// The model to use or null if no metadata is available. + /// + public IEdmModel Model + { + get + { + Debug.Assert(this.model != null, "this.model != null"); + return this.model; + } + } + + /// + /// The optional URL converter to perform custom URL conversion for URLs written to the payload. + /// + public IODataPayloadUriConverter PayloadUriConverter + { + get + { + return this.payloadUriConverter; + } + } + + /// + /// The optional dependency injection container to get related services for message writing. + /// + internal IServiceProvider Container + { + get + { + return this.container; + } + } + + /// + /// The type resolver to use. + /// + internal EdmTypeResolver EdmTypeResolver + { + get + { + return this.edmTypeResolver; + } + } + + /// + /// The payload value converter to use + /// + internal ODataPayloadValueConverter PayloadValueConverter + { + get + { + return this.payloadValueConverter; + } + } + + /// + /// The writer validator used in writing. + /// + internal IWriterValidator WriterValidator + { + get + { + return this.writerValidator; + } + } + + /// + /// The ODataSimplifiedOptions used in writing + /// + internal ODataSimplifiedOptions ODataSimplifiedOptions + { + get + { + return this.odataSimplifiedOptions; + } + } + + /// + /// IDisposable.Dispose() implementation to cleanup unmanaged resources of the context. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Creates an to write a resource set. + /// + /// The entity set we are going to write entities for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual ODataWriter CreateODataResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a resource set. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual Task CreateODataResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmStructuredType entityType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Creates an to write a delta resource set. + /// + /// The entity set we are going to write entities for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual ODataWriter CreateODataDeltaResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a delta resource set. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual Task CreateODataDeltaResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmStructuredType entityType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Creates an to write a resource. + /// + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual ODataWriter CreateODataResourceWriter(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Resource); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a resource. + /// + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual Task CreateODataResourceWriterAsync(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Resource); + } +#endif + + /// + /// Creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The item type of the collection being written or null if no metadata is available. + /// The created collection writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual ODataCollectionWriter CreateODataCollectionWriter(IEdmTypeReference itemTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Collection); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a collection of primitive or complex values (as result of a service operation invocation). + /// + /// The item type of the collection being written or null if no metadata is available. + /// A running task for the created collection writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual Task CreateODataCollectionWriterAsync(IEdmTypeReference itemTypeReference) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Collection); + } +#endif + + /// + /// Creates an to write a resource into a Uri operation parameter. + /// + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual ODataWriter CreateODataUriParameterResourceWriter(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Resource); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a resource into a Uri operation parameter. + /// + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual Task CreateODataUriParameterResourceWriterAsync(IEdmNavigationSource navigationSource, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Resource); + } +#endif + + /// + /// Creates an to write a resource set into a Uri operation parameter. + /// + /// The created writer. + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// The write must flush the output when it's finished (inside the last Write call). + public virtual ODataWriter CreateODataUriParameterResourceSetWriter(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously Creates an to write a resource set into a Uri operation parameter. + /// + /// The entity set we are going to write resources for. + /// The resource type for the items in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual Task CreateODataUriParameterResourceSetWriterAsync(IEdmEntitySetBase entitySet, IEdmStructuredType resourceType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Creates an to write a parameter payload. + /// + /// The operation whose parameters will be written. + /// The created parameter writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual ODataParameterWriter CreateODataParameterWriter(IEdmOperation operation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a parameter payload. + /// + /// The operation whose parameters will be written. + /// A running task for the created parameter writer. + /// The write must flush the output when it's finished (inside the last Write call). + public virtual Task CreateODataParameterWriterAsync(IEdmOperation operation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } +#endif + + /// + /// Writes an as message payload. + /// + /// The OData property to write. + /// It is the responsibility of this method to flush the output before the method returns. + public virtual void WriteProperty(ODataProperty odataProperty) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Property); + } + +#if PORTABLELIB + /// + /// Asynchronously writes an as message payload. + /// + /// The OData property to write + /// A task representing the asynchronous operation of writing the OData property. + /// It is the responsibility of this method to flush the output before the task finishes. + public virtual Task WritePropertyAsync(ODataProperty odataProperty) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Property); + } +#endif + + /// + /// Writes an as the message payload. + /// + /// The OData error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// It is the responsibility of this method to flush the output before the method returns. + public virtual void WriteError(ODataError odataError, bool includeDebugInformation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } + +#if PORTABLELIB + /// + /// Asynchronously writes an as the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// A task representing the asynchronous operation of writing the error. + /// It is the responsibility of this method to flush the output before the task finishes. + public virtual Task WriteErrorAsync(ODataError odataError, bool includeDebugInformation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } +#endif + + /// + /// Writes an into the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// + /// This method is called if the ODataMessageWriter.WriteError is called once some other + /// write operation has already started. + /// The method should write the in-stream error representation for the specific format into the current payload. + /// Before the method is called no flush is performed on the output context or any active writer. + /// It is the responsibility of this method to flush the output before the method returns. + /// + internal virtual void WriteInStreamError(ODataError error, bool includeDebugInformation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } + +#if PORTABLELIB + /// + /// Writes an into the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// Task which represents the pending write operation. + /// + /// This method is called if the ODataMessageWriter.WriteError is called once some other + /// write operation has already started. + /// The method should write the in-stream error representation for the specific format into the current payload. + /// Before the method is called no flush is performed on the output context or any active writer. + /// It is the responsibility of this method to make sure that all the data up to this point are written before + /// the in-stream error is written. + /// It is the responsibility of this method to flush the output before the task finishes. + /// + internal virtual Task WriteInStreamErrorAsync(ODataError error, bool includeDebugInformation) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Error); + } +#endif + + /// + /// Creates an to write an async response. + /// + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + internal virtual ODataAsynchronousWriter CreateODataAsynchronousWriter() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Asynchronous); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write an async response. + /// + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + internal virtual Task CreateODataAsynchronousWriterAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Asynchronous); + } +#endif + + /// + /// Creates an to write a delta response. + /// + /// The created writer. + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The write must flush the output when it's finished (inside the last Write call). + internal virtual ODataDeltaWriter CreateODataDeltaWriter(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a delta response. + /// + /// The entity set we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + internal virtual Task CreateODataDeltaWriterAsync(IEdmEntitySetBase entitySet, IEdmEntityType entityType) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ResourceSet); + } +#endif + + /// + /// Creates an to write a batch of requests or responses. + /// + /// The created batch writer. + /// We don't plan to make this public! + /// + /// The write must flush the output when it's finished (inside the last Write call). + /// Since we don't want to support batch format extensibility (at least not yet) this method should remain internal. + /// + internal virtual ODataBatchWriter CreateODataBatchWriter() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Batch); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write a batch of requests or responses. + /// + /// A running task for the created batch writer. + /// We don't plan to make this public! + /// + /// The write must flush the output when it's finished (inside the last Write call). + /// Since we don't want to support batch format extensibility (at least not yet) this method should remain internal. + /// + internal virtual Task CreateODataBatchWriterAsync() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Batch); + } +#endif + + /// + /// Writes a service document with the specified + /// as message payload. + /// + /// The service document to write in the service document. + /// It is the responsibility of this method to flush the output before the method returns. + internal virtual void WriteServiceDocument(ODataServiceDocument serviceDocument) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ServiceDocument); + } + +#if PORTABLELIB + /// + /// Asynchronously writes a service document with the specified + /// as message payload. + /// + /// The service document to write in the service document. + /// A task representing the asynchronous operation of writing the service document. + /// It is the responsibility of this method to flush the output before the task finishes. + internal virtual Task WriteServiceDocumentAsync(ODataServiceDocument serviceDocument) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.ServiceDocument); + } +#endif + + /// + /// Writes the result of a $ref query as the message payload. + /// + /// The entity reference links to write as message payload. + /// It is the responsibility of this method to flush the output before the method returns. + internal virtual void WriteEntityReferenceLinks(ODataEntityReferenceLinks links) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLinks); + } + +#if PORTABLELIB + /// + /// Asynchronously writes the result of a $ref query as the message payload. + /// + /// The entity reference links to write as message payload. + /// A task representing the asynchronous writing of the entity reference links. + /// It is the responsibility of this method to flush the output before the task finishes. + internal virtual Task WriteEntityReferenceLinksAsync(ODataEntityReferenceLinks links) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLinks); + } +#endif + + /// + /// Writes a singleton result of a $ref query as the message payload. + /// + /// The entity reference link to write as message payload. + /// It is the responsibility of this method to flush the output before the method returns. + internal virtual void WriteEntityReferenceLink(ODataEntityReferenceLink link) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLink); + } + +#if PORTABLELIB + /// + /// Asynchronously writes a singleton result of a $ref query as the message payload. + /// + /// The link result to write as message payload. + /// A running task representing the writing of the link. + /// It is the responsibility of this method to flush the output before the task finishes. + internal virtual Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink link) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.EntityReferenceLink); + } +#endif + + /// + /// Writes a single value as the message body. + /// + /// The value to write. + /// It is the responsibility of this method to flush the output before the method returns. + internal virtual void WriteValue(object value) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Value); + } + +#if PORTABLELIB + /// + /// Asynchronously writes a single value as the message body. + /// + /// The value to write. + /// A running task representing the writing of the value. + /// It is the responsibility of this method to flush the output before the task finishes. + internal virtual Task WriteValueAsync(object value) + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.Value); + } +#endif + + /// + /// Writes the metadata document as the message body. + /// + /// It is the responsibility of this method to flush the output before the method returns. + internal virtual void WriteMetadataDocument() + { + throw this.CreatePayloadKindNotSupportedException(ODataPayloadKind.MetadataDocument); + } + + /// + /// Asserts that the input context was created for synchronous operation. + /// + [Conditional("DEBUG")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Needs to access this in debug only.")] + internal void AssertSynchronous() + { +#if DEBUG + Debug.Assert(this.synchronous, "The method should only be called on a synchronous output context."); +#endif + } + + /// + /// Asserts that the input context was created for asynchronous operation. + /// + [Conditional("DEBUG")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Needs to access this in debug only.")] + internal void AssertAsynchronous() + { +#if DEBUG + Debug.Assert(!this.synchronous, "The method should only be called on an asynchronous output context."); +#endif + } + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected virtual void Dispose(bool disposing) + { + } + + /// + /// Creates an exception which reports that the specified payload kind if not support by this format. + /// + /// The payload kind which is not supported. + /// An exception to throw. + private ODataException CreatePayloadKindNotSupportedException(ODataPayloadKind payloadKind) + { + return new ODataException(Strings.ODataOutputContext_UnsupportedPayloadKindForFormat(this.format.ToString(), payloadKind.ToString())); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReader.cs new file mode 100644 index 0000000..37c9443 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReader.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// Base class for OData parameter readers. + public abstract class ODataParameterReader + { + /// Gets the current state of the reader. + /// The current state of the reader. + public abstract ODataParameterReaderState State + { + get; + } + + /// Gets the name of the current parameter that is being read. + /// The name of the current parameter that is being read. + public abstract string Name + { + get; + } + + /// Gets the value of the current parameter that is being read. + /// The value of the current parameter that is being read. + /// + /// This property returns a primitive value or null when State is ODataParameterReaderState.Value. + /// This property returns null when State is ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet or ODataParameterReaderState.Collection. + /// + public abstract object Value + { + get; + } + + /// + /// This method creates an to read the resource value when the state is ODataParameterReaderState.Resource. + /// + /// + /// When the state is ODataParameterReaderState.Resource, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + /// Returns an to read the resource value when the state is ODataParameterReaderState.Resource. + public abstract ODataReader CreateResourceReader(); + + /// + /// This method creates an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + /// + /// + /// When the state is ODataParameterReaderState.ResourceSet, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + /// Returns an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + public abstract ODataReader CreateResourceSetReader(); + + /// Creates an to read the collection value when the state is ODataParameterReaderState.Collection. + /// An to read the collection value when the state is ODataParameterReaderState.Collection. + /// + /// When the state is ODataParameterReaderState.Collection, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + public abstract ODataCollectionReader CreateCollectionReader(); + + /// Reads the next parameter from the message payload. + /// true if more items were read; otherwise false. + public abstract bool Read(); + +#if PORTABLELIB + /// Asynchronously reads the next item from the message payload. + /// A task that when completed indicates whether more items were read. + public abstract Task ReadAsync(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCore.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCore.cs new file mode 100644 index 0000000..0e2ee4d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCore.cs @@ -0,0 +1,637 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Base class for OData parameter readers that verifies a proper sequence of read calls on the reader. + /// + internal abstract class ODataParameterReaderCore : ODataParameterReader, IODataReaderWriterListener + { + /// The input context to read from. + private readonly ODataInputContext inputContext; + + /// The operation whose parameters are being read. + private readonly IEdmOperation operation; + + /// Stack of reader scopes to keep track of the current context of the reader. + private readonly Stack scopes = new Stack(); + + /// Hash set to keep track of all the parameters read from the payload. + private readonly HashSet parametersRead = new HashSet(StringComparer.Ordinal); + + /// Tracks the state of the sub-reader. + private SubReaderState subReaderState; + + /// + /// Constructor. + /// + /// The input to read from. + /// The operation import whose parameters are being read. + protected ODataParameterReaderCore( + ODataInputContext inputContext, + IEdmOperation operation) + { + this.inputContext = inputContext; + this.operation = operation; + + this.EnterScope(ODataParameterReaderState.Start, null, null); + } + + /// Enum to track the state of the sub-reader. + private enum SubReaderState + { + /// No sub-reader has been created for the current parameter. + None, + + /// CreateResourceReader(), CreateResourceSetReader() or CreateCollectionReader() has been called for the current parameter + /// and the newly created reader is not in Completed state. + /// If the sub-reader is in Error state, the ODataParameterReader will enter ODataParameterReaderState.Error. + Active, + + /// The created sub-reader is in Completed state. + Completed, + } + + /// + /// The current state of the reader. + /// + public override sealed ODataParameterReaderState State + { + get + { + this.inputContext.VerifyNotDisposed(); + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek().State; + } + } + + /// + /// The name of the current parameter that is being read. + /// + public override string Name + { + get + { + this.inputContext.VerifyNotDisposed(); + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek().Name; + } + } + + /// + /// The value of the current parameter that is being read. + /// + /// + /// This property returns a primitive value or null when State is ODataParameterReaderState.Value. + /// This property returns null when State is ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet or ODataParameterReaderState.Collection. + /// + public override object Value + { + get + { + this.inputContext.VerifyNotDisposed(); + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek().Value; + } + } + + /// + /// The operation whose parameters are being read. + /// + protected IEdmOperation Operation + { + get + { + return this.operation; + } + } + + /// + /// This method creates an to read the resource value when the state is ODataParameterReaderState.Resource. + /// + /// + /// When the state is ODataParameterReaderState.Resource, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + /// Returns an to read the resource value when the state is ODataParameterReaderState.Resource. + public override ODataReader CreateResourceReader() + { + this.VerifyCanCreateSubReader(ODataParameterReaderState.Resource); + this.subReaderState = SubReaderState.Active; + Debug.Assert(this.Name != null, "this.Name != null"); + Debug.Assert(this.Value == null, "this.Value == null"); + IEdmStructuredType expectedResourceType = (IEdmStructuredType)this.GetParameterTypeReference(this.Name).Definition; + return this.CreateResourceReader(expectedResourceType); + } + + /// + /// This method creates an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + /// + /// + /// When the state is ODataParameterReaderState.ResourceSet, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + /// Returns an to read the resource set value when the state is ODataParameterReaderState.ResourceSet. + public override ODataReader CreateResourceSetReader() + { + this.VerifyCanCreateSubReader(ODataParameterReaderState.ResourceSet); + this.subReaderState = SubReaderState.Active; + Debug.Assert(this.Name != null, "this.Name != null"); + Debug.Assert(this.Value == null, "this.Value == null"); + IEdmStructuredType expectedResourceType = (IEdmStructuredType)((IEdmCollectionType)this.GetParameterTypeReference(this.Name).Definition).ElementType.Definition; + return this.CreateResourceSetReader(expectedResourceType); + } + + /// + /// This method creates an to read the collection value when the state is ODataParameterReaderState.Collection. + /// + /// + /// When the state is ODataParameterReaderState.Collection, the Name property of the returns the name of the parameter + /// and the Value property of the returns null. Calling this method in any other state will cause an ODataException to be thrown. + /// + /// Returns an to read the collection value when the state is ODataParameterReaderState.Collection. + public override ODataCollectionReader CreateCollectionReader() + { + this.VerifyCanCreateSubReader(ODataParameterReaderState.Collection); + this.subReaderState = SubReaderState.Active; + Debug.Assert(this.Name != null, "this.Name != null"); + Debug.Assert(this.Value == null, "this.Value == null"); + IEdmTypeReference expectedItemTypeReference = ((IEdmCollectionType)this.GetParameterTypeReference(this.Name).Definition).ElementType; + return this.CreateCollectionReader(expectedItemTypeReference); + } + + /// + /// Reads the next item from the message payload. + /// + /// true if more items were read; otherwise false. + public override sealed bool Read() + { + this.VerifyCanRead(true); + return this.InterceptException(this.ReadSynchronously); + } + +#if PORTABLELIB + /// + /// Asynchronously reads the next item from the message payload. + /// + /// A task that when completed indicates whether more items were read. + public override sealed Task ReadAsync() + { + this.VerifyCanRead(false); + return this.ReadAsynchronously().FollowOnFaultWith(t => this.EnterScope(ODataParameterReaderState.Exception, null, null)); + } +#endif + + /// + /// This method notifies the implementer of this interface that the created reader is in Exception state. + /// + void IODataReaderWriterListener.OnException() + { + Debug.Assert( + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || this.State == ODataParameterReaderState.Collection, + "OnException called in unexpected state: " + this.State); + Debug.Assert(this.State == ODataParameterReaderState.Exception || this.subReaderState == SubReaderState.Active, "OnException called in unexpected subReaderState: " + this.subReaderState); + this.EnterScope(ODataParameterReaderState.Exception, null, null); + } + + /// + /// This method notifies the implementer of this interface that the created reader is in Completed state. + /// + void IODataReaderWriterListener.OnCompleted() + { + Debug.Assert( + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || +this.State == ODataParameterReaderState.Collection, + "OnCompleted called in unexpected state: " + this.State); + Debug.Assert(this.State == ODataParameterReaderState.Exception || this.subReaderState == SubReaderState.Active, "OnCompleted called in unexpected subReaderState: " + this.subReaderState); + this.subReaderState = SubReaderState.Completed; + } + + /// + /// Returns the type reference of the parameter in question. + /// + /// Name of the parameter in question. + /// Returns the type reference of the parameter in question. + protected internal IEdmTypeReference GetParameterTypeReference(string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + IEdmOperationParameter parameter = this.Operation.FindParameter(parameterName); + if (parameter == null) + { + throw new ODataException(Strings.ODataParameterReaderCore_ParameterNameNotInMetadata(parameterName, this.Operation.Name)); + } + + return this.inputContext.EdmTypeResolver.GetParameterType(parameter); + } + + /// + /// Creates a new for the specified with the provided + /// and and pushes it on the stack of scopes. + /// + /// The to use for the new scope. + /// The parameter name to attach with the state in the new scope. + /// The parameter value to attach with the state in the new scope. + protected internal void EnterScope(ODataParameterReaderState state, string name, object value) + { + if (state == ODataParameterReaderState.Value) + { + if (value != null && !EdmLibraryExtensions.IsPrimitiveType(value.GetType()) && !(value is ODataEnumValue)) + { + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterReaderCore_ValueMustBePrimitiveOrNull)); + } + } + + Debug.Assert(this.scopes.Count == 0 || this.State != ODataParameterReaderState.Exception || state == ODataParameterReaderState.Exception, "If the reader is already in Exception state, we shouldn't call EnterScope() except for another Exception."); + + // We only need to enter the exception state once. + if (this.scopes.Count == 0 || this.State != ODataParameterReaderState.Exception) + { + Debug.Assert( + state == ODataParameterReaderState.Exception || + state == ODataParameterReaderState.Start && this.scopes.Count == 0 || + state == ODataParameterReaderState.Completed && this.scopes.Count == 0 || + this.scopes.Count == 1 && this.scopes.Peek().State == ODataParameterReaderState.Start, + "Unexpected state in the scopes stack."); + + // Make sure there aren't any missing parameter in the payload. + if (state == ODataParameterReaderState.Completed) + { + List missingParameters = new List(); + + // Note that the binding parameter will be specified on the Uri rather than the payload, skip the binding parameter. + foreach (IEdmOperationParameter parameter in this.Operation.Parameters.Skip(this.Operation.IsBound ? 1 : 0)) + { + // only check the non-optional parameter + if (!(parameter is IEdmOptionalParameter)) + { + if (!this.parametersRead.Contains(parameter.Name) && !this.inputContext.EdmTypeResolver.GetParameterType(parameter).IsNullable) + { + missingParameters.Add(parameter.Name); + } + } + } + + if (missingParameters.Count > 0) + { + this.scopes.Push(new Scope(ODataParameterReaderState.Exception, null, null)); + throw new ODataException(Strings.ODataParameterReaderCore_ParametersMissingInPayload(this.Operation.Name, string.Join(",", missingParameters.ToArray()))); + } + } + else if (name != null) + { + // Record the parameter names we read and check for duplicates. + if (this.parametersRead.Contains(name)) + { + throw new ODataException(Strings.ODataParameterReaderCore_DuplicateParametersInPayload(name)); + } + + this.parametersRead.Add(name); + } + + this.scopes.Push(new Scope(state, name, value)); + } + } + + /// + /// Removes the current scope from the stack of all scopes. + /// + /// The expected state of the current scope (to be popped). + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "state", Justification = "Used in debug builds in assertions.")] + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "scope", Justification = "Used in debug builds in assertions.")] + protected internal void PopScope(ODataParameterReaderState state) + { + Debug.Assert(this.scopes.Count > 0, "Stack must have more than 1 items in order to pop an item."); + + Scope scope = this.scopes.Pop(); + Debug.Assert(scope.State == state, "scope.State == state"); + } + + /// + /// Called when the a parameter was completed. + /// + protected void OnParameterCompleted() + { + Debug.Assert(this.State == ODataParameterReaderState.Value || this.subReaderState == SubReaderState.Completed, "this.State == ODataParameterReaderState.Value || this.subReaderState == SubReaderState.Completed"); + this.subReaderState = SubReaderState.None; + } + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + protected bool ReadImplementation() + { + bool result = false; + switch (this.State) + { + case ODataParameterReaderState.Start: + result = this.ReadAtStartImplementation(); + Debug.Assert( + this.State == ODataParameterReaderState.Value || + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || + this.State == ODataParameterReaderState.Collection || + this.State == ODataParameterReaderState.Completed, + "ReadAtStartImplementation should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); + break; + + case ODataParameterReaderState.Value: // fall through + case ODataParameterReaderState.Resource: + case ODataParameterReaderState.ResourceSet: + case ODataParameterReaderState.Collection: + this.OnParameterCompleted(); + result = this.ReadNextParameterImplementation(); + Debug.Assert( + this.State == ODataParameterReaderState.Value || + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || + this.State == ODataParameterReaderState.Collection || + this.State == ODataParameterReaderState.Completed, + "ReadNextParameterImplementation should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); + break; + + case ODataParameterReaderState.Exception: // fall through + case ODataParameterReaderState.Completed: + Debug.Assert(false, "This case should have been caught earlier."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterReaderCore_ReadImplementation)); + + default: + Debug.Assert(false, "Unsupported parameter reader state " + this.State + " detected."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterReaderCore_ReadImplementation)); + } + + return result; + } + + /// + /// Implementation of the parameter reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtStartImplementation(); + + /// + /// Implementation of the reader logic when in state Value, Resource, Resource Set or Collection state. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadNextParameterImplementation(); + + /// + /// Creates an to read the resource value of type . + /// + /// Expected entity type to read. + /// An to read the resource value of type . + protected abstract ODataReader CreateResourceReader(IEdmStructuredType expectedResourceType); + + /// + /// Cretes an to read the resource set value of type . + /// + /// Expected resource set element type to read. + /// An to read the resource set value of type . + protected abstract ODataReader CreateResourceSetReader(IEdmStructuredType expectedResourceType); + + /// + /// Creates an to read the collection with type . + /// + /// Expected item type reference of the collection to read. + /// An to read the collection with type . + protected abstract ODataCollectionReader CreateCollectionReader(IEdmTypeReference expectedItemTypeReference); + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + protected bool ReadSynchronously() + { + return this.ReadImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously reads the next from the message payload. + /// + /// A task that when completed indicates whether more items were read. + protected virtual Task ReadAsynchronously() + { + // We are reading from the fully buffered read stream here; thus it is ok + // to use synchronous reads and then return a completed task + // NOTE: once we switch to fully async reading this will have to change + return TaskUtils.GetTaskForSynchronousOperation(this.ReadImplementation); + } +#endif + + /// + /// Gets the corresponding create reader method name for the given state. + /// + /// State in question. + /// Returns the name of the method to create the correct reader for the given state. + private static string GetCreateReaderMethodName(ODataParameterReaderState state) + { + Debug.Assert(state == ODataParameterReaderState.Resource || state == ODataParameterReaderState.ResourceSet || state == ODataParameterReaderState.Collection, "state must be Resource, ResourceSet or Collection."); + return "Create" + state.ToString() + "Reader"; + } + + /// + /// Verifies that one of CreateResourceReader(), CreateResourceSetReader() or CreateCollectionReader() can be called. + /// + /// The expected state of the reader. + private void VerifyCanCreateSubReader(ODataParameterReaderState expectedState) + { + this.inputContext.VerifyNotDisposed(); + if (this.State != expectedState) + { + throw new ODataException(Strings.ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState(ODataParameterReaderCore.GetCreateReaderMethodName(expectedState), this.State)); + } + + if (this.subReaderState != SubReaderState.None) + { + Debug.Assert(this.Name != null, "this.Name != null"); + throw new ODataException(Strings.ODataParameterReaderCore_CreateReaderAlreadyCalled(ODataParameterReaderCore.GetCreateReaderMethodName(expectedState), this.Name)); + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the reader into + /// state ExceptionThrown and then rethrow the exception. + /// + /// The type returned from the to execute. + /// The action to execute. + /// The result of executing the . + private T InterceptException(Func action) + { + try + { + return action(); + } + catch (Exception e) + { + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + this.EnterScope(ODataParameterReaderState.Exception, null, null); + } + + throw; + } + } + + /// + /// Verifies that calling Read is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanRead(bool synchronousCall) + { + this.inputContext.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + if (this.State == ODataParameterReaderState.Exception || this.State == ODataParameterReaderState.Completed) + { + throw new ODataException(Strings.ODataParameterReaderCore_ReadOrReadAsyncCalledInInvalidState(this.State)); + } + + if (this.State == ODataParameterReaderState.Resource || this.State == ODataParameterReaderState.ResourceSet || this.State == ODataParameterReaderState.Collection) + { + if (this.subReaderState == SubReaderState.None) + { + throw new ODataException(Strings.ODataParameterReaderCore_SubReaderMustBeCreatedAndReadToCompletionBeforeTheNextReadOrReadAsyncCall(this.State, ODataParameterReaderCore.GetCreateReaderMethodName(this.State))); + } + else if (this.subReaderState == SubReaderState.Active) + { + throw new ODataException(Strings.ODataParameterReaderCore_SubReaderMustBeInCompletedStateBeforeTheNextReadOrReadAsyncCall(this.State, ODataParameterReaderCore.GetCreateReaderMethodName(this.State))); + } + } + } + + /// + /// Verifies that a call is allowed to the reader. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + this.VerifySynchronousCallAllowed(); + } + else + { +#if PORTABLELIB + this.VerifyAsynchronousCallAllowed(); +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Verifies that a synchronous operation is allowed on this reader. + /// + private void VerifySynchronousCallAllowed() + { + if (!this.inputContext.Synchronous) + { + throw new ODataException(Strings.ODataParameterReaderCore_SyncCallOnAsyncReader); + } + } + +#if PORTABLELIB + /// + /// Verifies that an asynchronous operation is allowed on this reader. + /// + private void VerifyAsynchronousCallAllowed() + { + if (this.inputContext.Synchronous) + { + throw new ODataException(Strings.ODataParameterReaderCore_AsyncCallOnSyncReader); + } + } +#endif + + /// + /// A parameter reader scope; keeping track of the current reader state and an item associated with this state. + /// + protected sealed class Scope + { + /// The reader state of this scope. + private readonly ODataParameterReaderState state; + + /// The parameter name attached to this scope. + private readonly string name; + + /// The parameter value attached to this scope. + private readonly object value; + + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of this scope. + /// The parameter name attached to this scope. + /// The parameter value attached to this scope. + public Scope(ODataParameterReaderState state, string name, object value) + { + Debug.Assert( + state == ODataParameterReaderState.Start && name == null && value == null || + state == ODataParameterReaderState.Value && !string.IsNullOrEmpty(name) && (value == null || value is ODataEnumValue || EdmLibraryExtensions.IsPrimitiveType(value.GetType())) || + state == ODataParameterReaderState.Resource && !string.IsNullOrEmpty(name) && value == null || + state == ODataParameterReaderState.ResourceSet && !string.IsNullOrEmpty(name) && value == null || + state == ODataParameterReaderState.Collection && !string.IsNullOrEmpty(name) && value == null || + state == ODataParameterReaderState.Exception && name == null && value == null || + state == ODataParameterReaderState.Completed && name == null && value == null, + "Reader state and associated item do not match."); + + this.state = state; + this.name = name; + this.value = value; + } + + /// + /// The reader state of this scope. + /// + public ODataParameterReaderState State + { + get + { + return this.state; + } + } + + /// + /// The parameter name attached to this scope. + /// + public string Name + { + get + { + return this.name; + } + } + + /// + /// The parameter value attached to this scope. + /// + public object Value + { + get + { + return this.value; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs new file mode 100644 index 0000000..81397f2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderCoreAsync.cs @@ -0,0 +1,131 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Base class for OData parameter readers that verifies a proper sequence of read calls on the reader with truly async operations. + /// + internal abstract class ODataParameterReaderCoreAsync : ODataParameterReaderCore + { + /// + /// Constructor. + /// + /// The input to read from. + /// The operation whose parameters are being read. + protected ODataParameterReaderCoreAsync( + ODataInputContext inputContext, + IEdmOperation operation) + : base(inputContext, operation) + { + } + +#if PORTABLELIB + /// + /// Implementation of the parameter reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtStartImplementationAsync(); + + /// + /// Implementation of the reader logic when in state Value, Resource, Resource Set or Collection state. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract Task ReadNextParameterImplementationAsync(); + + /// + /// Creates an to read the resource value of type . + /// + /// Expected entity type to read. + /// An to read the resource value of type . + protected abstract Task CreateResourceReaderAsync(IEdmStructuredType expectedResourceType); + + /// + /// Cretes an to read the resource set value of type . + /// + /// Expected resource set element type to read. + /// An to read the resource set value of type . + protected abstract Task CreateResourceSetReaderAsync(IEdmStructuredType expectedResourceType); + + /// + /// Creates an to read the collection with type . + /// + /// Expected item type reference of the collection to read. + /// An to read the collection with type . + protected abstract Task CreateCollectionReaderAsync(IEdmTypeReference expectedItemTypeReference); + + /// + /// Asynchronously reads the next from the message payload. + /// + /// A task that when completed indicates whether more items were read. + /// The base class already implements this but only for fully synchronous readers, the implementation here + /// allows fully asynchronous readers. + protected override Task ReadAsynchronously() + { + switch (this.State) + { + case ODataParameterReaderState.Start: +#if DEBUG + return this.ReadAtStartImplementationAsync() + .FollowOnSuccessWith(t => + { + Debug.Assert( + this.State == ODataParameterReaderState.Value || + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || + this.State == ODataParameterReaderState.Collection || + this.State == ODataParameterReaderState.Completed, + "ReadAtStartImplementationAsync should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); + return t.Result; + }); +#else + return this.ReadAtStartImplementationAsync(); +#endif + + case ODataParameterReaderState.Value: // fall through + case ODataParameterReaderState.Resource: + case ODataParameterReaderState.ResourceSet: + case ODataParameterReaderState.Collection: + this.OnParameterCompleted(); +#if DEBUG + return this.ReadNextParameterImplementationAsync() + .FollowOnSuccessWith(t => + { + Debug.Assert( + this.State == ODataParameterReaderState.Value || + this.State == ODataParameterReaderState.Resource || + this.State == ODataParameterReaderState.ResourceSet || + this.State == ODataParameterReaderState.Collection || + this.State == ODataParameterReaderState.Completed, + "ReadNextParameterImplementationAsync should transition the state to ODataParameterReaderState.Value, ODataParameterReaderState.Resource, ODataParameterReaderState.ResourceSet, ODataParameterReaderState.Collection or ODataParameterReaderState.Completed. The current state is: " + this.State); + return t.Result; + }); +#else + return this.ReadNextParameterImplementationAsync(); +#endif + + case ODataParameterReaderState.Exception: // fall through + case ODataParameterReaderState.Completed: + Debug.Assert(false, "This case should have been caught earlier."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterReaderCoreAsync_ReadAsynchronously)); + + default: + Debug.Assert(false, "Unsupported parameter reader state " + this.State + " detected."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterReaderCoreAsync_ReadAsynchronously)); + } + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderState.cs new file mode 100644 index 0000000..3bde29e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterReaderState.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// Enumeration of all possible states of an . + public enum ODataParameterReaderState + { + /// The reader is at the start; nothing has been read yet. + /// In this state the Name and Value properties of the returns null. + Start, + + /// The reader read a primitive or a complex parameter. + /// In this state the Name property of the returns the name of the parameter + /// and the Value property of the returns the value read (e.g. a primitive value, or null). + Value, + + /// The reader is reading a collection parameter. + /// In this state the Name property of the returns the name of the parameter + /// and the Value property of the returns null. The CreateCollectionReader() method on the + /// must be called to get the reader to read the collection value. + Collection, + + /// The reader has thrown an exception; nothing can be read from the reader anymore. + /// In this state the Name and Value properties of the return null. + Exception, + + /// The reader has completed; nothing can be read anymore. + /// In this state the Name and Value properties of the return null. + Completed, + + /// The reader is reading a resource parameter. + /// In this state the Name property of the returns the name of the parameter + /// and the Value property of the returns null. The CreateResourceReader() method on the + /// must be called to get the reader to read the resource value. + Resource, + + /// The reader is reading a resource set parameter. + /// In this state the Name property of the returns the name of the parameter + /// and the Value property of the returns null. The CreateResourceSetReader() method on the + /// must be called to get the reader to read the resource set value. + ResourceSet, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriter.cs new file mode 100644 index 0000000..bd9de74 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriter.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + #if PORTABLELIB + using System.Threading.Tasks; +#endif + + #endregion Namespaces + + /// Base class for OData collection writers. + public abstract class ODataParameterWriter + { + /// Start writing a parameter payload. + public abstract void WriteStart(); + +#if PORTABLELIB + /// Asynchronously start writing a parameter payload. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteStartAsync(); +#endif + + /// Start writing a value parameter. + /// The name of the parameter to write. + /// The value of the parameter to write (null/ODataEnumValue/primitiveClrValue). + public abstract void WriteValue(string parameterName, object parameterValue); + +#if PORTABLELIB + /// Asynchronously start writing a value parameter. + /// A task instance that represents the asynchronous write operation. + /// The name of the parameter to write. + /// The value of the parameter to write. + public abstract Task WriteValueAsync(string parameterName, object parameterValue); +#endif + + /// Creates an to write the value of a collection parameter. + /// The newly created . + /// The name of the collection parameter to write. + public abstract ODataCollectionWriter CreateCollectionWriter(string parameterName); + +#if PORTABLELIB + /// Asynchronously creates an to write the value of a collection parameter. + /// The asynchronously created . + /// The name of the collection parameter to write. + public abstract Task CreateCollectionWriterAsync(string parameterName); +#endif + + /// Creates an to write a resource. + /// The name of the parameter to write. + /// The created writer. + public abstract ODataWriter CreateResourceWriter(string parameterName); + +#if PORTABLELIB + /// Asynchronously creates an to write a resource. + /// The name of the parameter to write. + /// The asynchronously created . + public abstract Task CreateResourceWriterAsync(string parameterName); +#endif + + /// Creates an to write a resource set. + /// The name of the parameter to write. + /// The created writer. + public abstract ODataWriter CreateResourceSetWriter(string parameterName); + +#if PORTABLELIB + /// Asynchronously creates an to write a resource set. + /// The name of the parameter to write. + /// The asynchronously created . + public abstract Task CreateResourceSetWriterAsync(string parameterName); +#endif + + /// Finish writing a parameter payload. + public abstract void WriteEnd(); + +#if PORTABLELIB + /// Asynchronously finish writing a parameter payload. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteEndAsync(); +#endif + + /// Flushes the write buffer to the underlying stream. + public abstract void Flush(); + +#if PORTABLELIB + /// Asynchronously flushes the write buffer to the underlying stream. + /// A task instance that represents the asynchronous operation. + public abstract Task FlushAsync(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriterCore.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriterCore.cs new file mode 100644 index 0000000..a562c8a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataParameterWriterCore.cs @@ -0,0 +1,828 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Base class for OData parameter writers that verifies a proper sequence of write calls on the writer. + /// + internal abstract class ODataParameterWriterCore : ODataParameterWriter, IODataReaderWriterListener, IODataOutputInStreamErrorListener + { + /// The output context to write to. + private readonly ODataOutputContext outputContext; + + /// The operation whose parameters will be written. + private readonly IEdmOperation operation; + + /// Stack of writer scopes to keep track of the current context of the writer. + private Stack scopes = new Stack(); + + /// Parameter names that have already been written, used to detect duplicate writes on a parameter. + private HashSet parameterNamesWritten = new HashSet(StringComparer.Ordinal); + + /// Checker to detect duplicate property names on complex parameter values. + private IDuplicatePropertyNameChecker duplicatePropertyNameChecker; + + /// + /// Constructor. + /// + /// The output context to write to. + /// The operation import whose parameters will be written. + protected ODataParameterWriterCore(ODataOutputContext outputContext, IEdmOperation operation) + { + Debug.Assert(outputContext != null, "outputContext != null"); + + this.outputContext = outputContext; + this.operation = operation; + this.scopes.Push(ParameterWriterState.Start); + } + + /// + /// An enumeration representing the current state of the writer. + /// + private enum ParameterWriterState + { + /// The writer is at the start; nothing has been written yet. + Start, + + /// + /// The writer is in a state where the next parameter can be written. + /// The writer enters this state after WriteStart() or after the previous parameter is written. + /// + CanWriteParameter, + + /// One of the create writer method has been called and the created sub writer is not in Completed state. + ActiveSubWriter, + + /// The writer has completed; nothing can be written anymore. + Completed, + + /// An error had occurred while writing the payload; nothing can be written anymore. + Error + } + + /// Checker to detect duplicate property names on complex parameter values. + protected IDuplicatePropertyNameChecker DuplicatePropertyNameChecker + { + get + { + return this.duplicatePropertyNameChecker ?? + (this.duplicatePropertyNameChecker = + outputContext.MessageWriterSettings.Validator + .CreateDuplicatePropertyNameChecker()); + } + } + + /// + /// The current state of the writer. + /// + private ParameterWriterState State + { + get { return this.scopes.Peek(); } + } + + /// + /// Flushes the write buffer to the underlying stream. + /// + public sealed override void Flush() + { + this.VerifyCanFlush(true /*synchronousCall*/); + + // make sure we switch to writer state Error if an exception is thrown during flushing. + this.InterceptException(this.FlushSynchronously); + } + +#if PORTABLELIB + /// + /// Asynchronously flushes the write buffer to the underlying stream. + /// + /// A task instance that represents the asynchronous operation. + public sealed override Task FlushAsync() + { + this.VerifyCanFlush(false /*synchronousCall*/); + + // make sure we switch to writer state Error if an exception is thrown during flushing. + return this.FlushAsynchronously().FollowOnFaultWith(t => this.EnterErrorScope()); + } +#endif + + /// + /// Start writing a parameter payload. + /// + public sealed override void WriteStart() + { + this.VerifyCanWriteStart(true /*synchronousCall*/); + this.InterceptException(() => this.WriteStartImplementation()); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a parameter payload. + /// + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteStartAsync() + { + this.VerifyCanWriteStart(false /*synchronousCall*/); + return TaskUtils.GetTaskForSynchronousOperation(() => this.InterceptException(() => this.WriteStartImplementation())); + } +#endif + + /// + /// Start writing a value parameter. + /// + /// The name of the parameter to write. + /// The value of the parameter to write (null/ODataEnumValue/primitiveClrValue). + public sealed override void WriteValue(string parameterName, object parameterValue) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference expectedTypeReference = this.VerifyCanWriteValueParameter(true /*synchronousCall*/, parameterName, parameterValue); + this.InterceptException(() => this.WriteValueImplementation(parameterName, parameterValue, expectedTypeReference)); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a value parameter. + /// + /// The name of the parameter to write. + /// The value of the parameter to write. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteValueAsync(string parameterName, object parameterValue) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference expectedTypeReference = this.VerifyCanWriteValueParameter(false /*synchronousCall*/, parameterName, parameterValue); + return TaskUtils.GetTaskForSynchronousOperation(() => this.InterceptException(() => this.WriteValueImplementation(parameterName, parameterValue, expectedTypeReference))); + } +#endif + + /// + /// Creates an to write the value of a collection parameter. + /// + /// The name of the collection parameter to write. + /// The newly created . + public sealed override ODataCollectionWriter CreateCollectionWriter(string parameterName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference itemTypeReference = this.VerifyCanCreateCollectionWriter(true /*synchronousCall*/, parameterName); + return this.InterceptException(() => this.CreateCollectionWriterImplementation(parameterName, itemTypeReference)); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write the value of a collection parameter. + /// + /// The name of the collection parameter to write. + /// A running task for the created writer. + public sealed override Task CreateCollectionWriterAsync(string parameterName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference itemTypeReference = this.VerifyCanCreateCollectionWriter(false /*synchronousCall*/, parameterName); + return TaskUtils.GetTaskForSynchronousOperation( + () => this.InterceptException(() => this.CreateCollectionWriterImplementation(parameterName, itemTypeReference))); + } +#endif + + /// Creates an to write a resource. + /// The name of the parameter to write. + /// The created writer. + public sealed override ODataWriter CreateResourceWriter(string parameterName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference itemTypeReference = this.VerifyCanCreateResourceWriter(true /*synchronousCall*/, parameterName); + return this.InterceptException(() => this.CreateResourceWriterImplementation(parameterName, itemTypeReference)); + } + +#if PORTABLELIB + /// Asynchronously creates an to write a resource. + /// The name of the parameter to write. + /// The asynchronously created . + public sealed override Task CreateResourceWriterAsync(string parameterName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference itemTypeReference = this.VerifyCanCreateResourceWriter(false /*synchronousCall*/, parameterName); + return TaskUtils.GetTaskForSynchronousOperation( + () => this.InterceptException(() => this.CreateResourceWriterImplementation(parameterName, itemTypeReference))); + } +#endif + + /// Creates an to write a resource set. + /// The name of the parameter to write. + /// The created writer. + public sealed override ODataWriter CreateResourceSetWriter(string parameterName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference itemTypeReference = this.VerifyCanCreateResourceSetWriter(true /*synchronousCall*/, parameterName); + return this.InterceptException(() => this.CreateResourceSetWriterImplementation(parameterName, itemTypeReference)); + } + +#if PORTABLELIB + /// Asynchronously creates an to write a resource set. + /// The name of the parameter to write. + /// The asynchronously created . + public sealed override Task CreateResourceSetWriterAsync(string parameterName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(parameterName, "parameterName"); + IEdmTypeReference itemTypeReference = this.VerifyCanCreateResourceSetWriter(false /*synchronousCall*/, parameterName); + return TaskUtils.GetTaskForSynchronousOperation( + () => this.InterceptException(() => this.CreateResourceSetWriterImplementation(parameterName, itemTypeReference))); + } +#endif + + /// + /// Finish writing a parameter payload. + /// + public sealed override void WriteEnd() + { + this.VerifyCanWriteEnd(true /*synchronousCall*/); + this.InterceptException(() => this.WriteEndImplementation()); + if (this.State == ParameterWriterState.Completed) + { + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + this.Flush(); + } + } + +#if PORTABLELIB + /// + /// Asynchronously finish writing a parameter payload. + /// + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteEndAsync() + { + this.VerifyCanWriteEnd(false /*synchronousCall*/); + return TaskUtils.GetTaskForSynchronousOperation(() => this.InterceptException(() => this.WriteEndImplementation())) + .FollowOnSuccessWithTask( + task => + { + if (this.State == ParameterWriterState.Completed) + { + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + return this.FlushAsync(); + } + else + { + return TaskUtils.CompletedTask; + } + }); + } +#endif + + /// + /// This method notifies the implementer of this interface that the created reader is in Exception state. + /// + void IODataReaderWriterListener.OnException() + { + Debug.Assert(this.State == ParameterWriterState.ActiveSubWriter, "this.State == ParameterWriterState.ActiveSubWriter"); + this.ReplaceScope(ParameterWriterState.Error); + } + + /// + /// This method notifies the implementer of this interface that the created reader is in Completed state. + /// + void IODataReaderWriterListener.OnCompleted() + { + Debug.Assert(this.State == ParameterWriterState.ActiveSubWriter, "this.State == ParameterWriterState.ActiveSubWriter"); + this.ReplaceScope(ParameterWriterState.CanWriteParameter); + } + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + void IODataOutputInStreamErrorListener.OnInStreamError() + { + // The parameter payload is writen by the client and read by the server, we do not support + // writing an in-stream error payload in this scenario. + throw new ODataException(Strings.ODataParameterWriter_InStreamErrorNotSupported); + } + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + protected abstract void VerifyNotDisposed(); + + /// + /// Flush the output. + /// + protected abstract void FlushSynchronously(); + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected abstract Task FlushAsynchronously(); +#endif + + /// + /// Start writing an OData payload. + /// + protected abstract void StartPayload(); + + /// + /// Writes a value parameter (either primitive or complex). + /// + /// The name of the parameter to write. + /// The value of the parameter to write. + /// The expected type reference of the parameter value. + protected abstract void WriteValueParameter(string parameterName, object parameterValue, IEdmTypeReference expectedTypeReference); + + /// + /// Creates a format specific to write the value of a collection parameter. + /// + /// The name of the collection parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + protected abstract ODataCollectionWriter CreateFormatCollectionWriter(string parameterName, IEdmTypeReference expectedItemType); + + /// Creates a format specific to write a resource. + /// The name of the parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + protected abstract ODataWriter CreateFormatResourceWriter(string parameterName, IEdmTypeReference expectedItemType); + + /// Creates a format specific to write a resource set. + /// The name of the parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + protected abstract ODataWriter CreateFormatResourceSetWriter(string parameterName, IEdmTypeReference expectedItemType); + + /// + /// Finish writing an OData payload. + /// + protected abstract void EndPayload(); + + /// + /// Verifies that calling WriteStart is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteStart(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + if (this.State != ParameterWriterState.Start) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotWriteStart); + } + } + + /// + /// Start writing a parameter payload - implementation of the actual functionality. + /// + private void WriteStartImplementation() + { + Debug.Assert(this.State == ParameterWriterState.Start, "this.State == ParameterWriterState.Start"); + this.InterceptException(this.StartPayload); + this.EnterScope(ParameterWriterState.CanWriteParameter); + } + + /// + /// Verifies that the parameter with name can be written and returns the + /// type reference of the parameter. + /// + /// true if the call is to be synchronous; false otherwise. + /// The name of the parameter to be written. + /// The type reference of the parameter; null if no operation import was specified to the writer. + private IEdmTypeReference VerifyCanWriteParameterAndGetTypeReference(bool synchronousCall, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + this.VerifyNotInErrorOrCompletedState(); + if (this.State != ParameterWriterState.CanWriteParameter) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotWriteParameter); + } + + if (this.parameterNamesWritten.Contains(parameterName)) + { + throw new ODataException(Strings.ODataParameterWriterCore_DuplicatedParameterNameNotAllowed(parameterName)); + } + + this.parameterNamesWritten.Add(parameterName); + return this.GetParameterTypeReference(parameterName); + } + + /// + /// Verify that calling WriteValue is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The name of the parameter to be written. + /// The value of the parameter to write. + /// The type reference of the parameter; null if no operation import was specified to the writer. + private IEdmTypeReference VerifyCanWriteValueParameter(bool synchronousCall, string parameterName, object parameterValue) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + IEdmTypeReference parameterTypeReference = this.VerifyCanWriteParameterAndGetTypeReference(synchronousCall, parameterName); + if (parameterTypeReference != null && !parameterTypeReference.IsODataPrimitiveTypeKind() && !parameterTypeReference.IsODataEnumTypeKind() && !parameterTypeReference.IsODataTypeDefinitionTypeKind()) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotWriteValueOnNonValueTypeKind(parameterName, parameterTypeReference.TypeKind())); + } + + if (parameterValue != null && (!EdmLibraryExtensions.IsPrimitiveType(parameterValue.GetType()) || parameterValue is Stream) && !(parameterValue is ODataEnumValue)) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotWriteValueOnNonSupportedValueType(parameterName, parameterValue.GetType())); + } + + return parameterTypeReference; + } + + /// + /// Verify that calling CreateCollectionWriter is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The name of the parameter to be written. + /// The expected item type of the items in the collection or null if no item type is available. + private IEdmTypeReference VerifyCanCreateCollectionWriter(bool synchronousCall, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + IEdmTypeReference parameterTypeReference = this.VerifyCanWriteParameterAndGetTypeReference(synchronousCall, parameterName); + + // TODO : Change to structureds Collection check + if (parameterTypeReference != null && !parameterTypeReference.IsNonEntityCollectionType()) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotCreateCollectionWriterOnNonCollectionTypeKind(parameterName, parameterTypeReference.TypeKind())); + } + + return parameterTypeReference == null ? null : parameterTypeReference.GetCollectionItemType(); + } + + /// + /// Verify that calling CreateResourceWriter is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The name of the parameter to be written. + /// The expected item type of the resource or null. + private IEdmTypeReference VerifyCanCreateResourceWriter(bool synchronousCall, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + IEdmTypeReference parameterTypeReference = this.VerifyCanWriteParameterAndGetTypeReference(synchronousCall, parameterName); + if (parameterTypeReference != null && !parameterTypeReference.IsStructured()) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotCreateResourceWriterOnNonEntityOrComplexTypeKind(parameterName, parameterTypeReference.TypeKind())); + } + + return parameterTypeReference; + } + + /// + /// Verify that calling CreateResourceSetWriter is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// The name of the parameter to be written. + /// The expected item type of the item in resource set or null. + private IEdmTypeReference VerifyCanCreateResourceSetWriter(bool synchronousCall, string parameterName) + { + Debug.Assert(!string.IsNullOrEmpty(parameterName), "!string.IsNullOrEmpty(parameterName)"); + IEdmTypeReference parameterTypeReference = this.VerifyCanWriteParameterAndGetTypeReference(synchronousCall, parameterName); + if (parameterTypeReference != null && !parameterTypeReference.IsStructuredCollectionType()) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotCreateResourceSetWriterOnNonStructuredCollectionTypeKind(parameterName, parameterTypeReference.TypeKind())); + } + + return parameterTypeReference; + } + + /// + /// Gets the type reference of the parameter in question. Returns null if no operation import was specified to the writer. + /// + /// The name of the parameter in question. + /// The type reference of the parameter; null if no operation import was specified to the writer. + private IEdmTypeReference GetParameterTypeReference(string parameterName) + { + if (this.operation != null) + { + IEdmOperationParameter parameter = this.operation.FindParameter(parameterName); + if (parameter == null) + { + throw new ODataException(Strings.ODataParameterWriterCore_ParameterNameNotFoundInOperation(parameterName, this.operation.Name)); + } + + return this.outputContext.EdmTypeResolver.GetParameterType(parameter); + } + + return null; + } + + /// + /// Write a value parameter - implementation of the actual functionality. + /// + /// The name of the parameter to write. + /// The value of the parameter to write (null/ODataEnumValue/primitiveClrValue). + /// The expected type reference of the parameter value. + private void WriteValueImplementation(string parameterName, object parameterValue, IEdmTypeReference expectedTypeReference) + { + Debug.Assert(this.State == ParameterWriterState.CanWriteParameter, "this.State == ParameterWriterState.CanWriteParameter"); + this.InterceptException(() => this.WriteValueParameter(parameterName, parameterValue, expectedTypeReference)); + } + + /// + /// Creates an to write the value of a collection parameter. + /// + /// The name of the collection parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + private ODataCollectionWriter CreateCollectionWriterImplementation(string parameterName, IEdmTypeReference expectedItemType) + { + Debug.Assert(this.State == ParameterWriterState.CanWriteParameter, "this.State == ParameterWriterState.CanWriteParameter"); + ODataCollectionWriter collectionWriter = this.CreateFormatCollectionWriter(parameterName, expectedItemType); + this.ReplaceScope(ParameterWriterState.ActiveSubWriter); + return collectionWriter; + } + + /// + /// Creates an to write a resource parameter. + /// + /// The name of the parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + private ODataWriter CreateResourceWriterImplementation(string parameterName, IEdmTypeReference expectedItemType) + { + Debug.Assert(this.State == ParameterWriterState.CanWriteParameter, "this.State == ParameterWriterState.CanWriteParameter"); + ODataWriter resourceWriter = this.CreateFormatResourceWriter(parameterName, expectedItemType); + this.ReplaceScope(ParameterWriterState.ActiveSubWriter); + return resourceWriter; + } + + /// + /// Creates an to write a resource set parameter. + /// + /// The name of the collection parameter to write. + /// The type reference of the expected item type or null if no expected item type exists. + /// The newly created . + private ODataWriter CreateResourceSetWriterImplementation(string parameterName, IEdmTypeReference expectedItemType) + { + Debug.Assert(this.State == ParameterWriterState.CanWriteParameter, "this.State == ParameterWriterState.CanWriteParameter"); + ODataWriter resourceSetWriter = this.CreateFormatResourceSetWriter(parameterName, expectedItemType); + this.ReplaceScope(ParameterWriterState.ActiveSubWriter); + return resourceSetWriter; + } + + /// + /// Verifies that calling WriteEnd is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEnd(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + this.VerifyNotInErrorOrCompletedState(); + if (this.State != ParameterWriterState.CanWriteParameter) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotWriteEnd); + } + + this.VerifyAllParametersWritten(); + } + + /// + /// If an is specified, then this method ensures that all parameters present in the + /// operation import are written to the payload. + /// + /// The binding parameter is optional in the payload. Hence this method will not check for missing binding parameter. + private void VerifyAllParametersWritten() + { + Debug.Assert(this.State == ParameterWriterState.CanWriteParameter, "this.State == ParameterWriterState.CanWriteParameter"); + + if (this.operation != null && this.operation.Parameters != null) + { + IEnumerable parameters = null; + if (this.operation.IsBound) + { + // The binding parameter may or may not be present in the payload. Hence we don't throw error if the binding parameter is missing. + parameters = this.operation.Parameters.Skip(1); + } + else + { + parameters = this.operation.Parameters; + } + + IEnumerable missingParameters = parameters.Where(p => !this.parameterNamesWritten.Contains(p.Name) && !this.outputContext.EdmTypeResolver.GetParameterType(p).IsNullable).Select(p => p.Name); + if (missingParameters.Any()) + { + missingParameters = missingParameters.Select(name => String.Format(CultureInfo.InvariantCulture, "'{0}'", name)); + + // We're calling the ToArray here since not all platforms support the string.Join which takes IEnumerable. + throw new ODataException(Strings.ODataParameterWriterCore_MissingParameterInParameterPayload(string.Join(", ", missingParameters.ToArray()), this.operation.Name)); + } + } + } + + /// + /// Finish writing a parameter payload - implementation of the actual functionality. + /// + private void WriteEndImplementation() + { + this.InterceptException(() => this.EndPayload()); + this.LeaveScope(); + } + + /// + /// Verifies that the current state is not Error or Completed. + /// + private void VerifyNotInErrorOrCompletedState() + { + if (this.State == ParameterWriterState.Error || this.State == ParameterWriterState.Completed) + { + throw new ODataException(Strings.ODataParameterWriterCore_CannotWriteInErrorOrCompletedState); + } + } + + /// + /// Verifies that calling Flush is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanFlush(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that a call is allowed to the writer. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataParameterWriterCore_SyncCallOnAsyncWriter); + } + } + else + { +#if PORTABLELIB + if (this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataParameterWriterCore_AsyncCallOnSyncWriter); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state ExceptionThrown and then rethrow the exception. + /// + /// The action to execute. + private void InterceptException(Action action) + { + try + { + action(); + } + catch + { + this.EnterErrorScope(); + throw; + } + } + + /// + /// Catch any exception thrown by the function passed in; in the exception case move the writer into + /// state ExceptionThrown and then rethrow the exception. + /// + /// The return type of . + /// The function to execute. + /// Returns the return value from executing . + private T InterceptException(Func function) + { + try + { + return function(); + } + catch + { + this.EnterErrorScope(); + throw; + } + } + + /// + /// Enters the Error scope if we are not already in Error state. + /// + private void EnterErrorScope() + { + if (this.State != ParameterWriterState.Error) + { + this.EnterScope(ParameterWriterState.Error); + } + } + + /// + /// Verifies that the transition from the current state into new state is valid and enter a new writer scope. + /// + /// The writer state to transition into. + private void EnterScope(ParameterWriterState newState) + { + this.ValidateTransition(newState); + this.scopes.Push(newState); + } + + /// + /// Leave the current writer scope and return to the previous scope. + /// When reaching the top-level replace the 'Start' scope with a 'Completed' scope. + /// + /// Note that this method is never called once the writer is in 'Error' state. + private void LeaveScope() + { + Debug.Assert(this.State != ParameterWriterState.Error, "this.State != WriterState.Error"); + this.ValidateTransition(ParameterWriterState.Completed); + + // scopes is either [Start, CanWriteParameter] or [Start] + if (this.State == ParameterWriterState.CanWriteParameter) + { + this.scopes.Pop(); + } + + Debug.Assert(this.State == ParameterWriterState.Start, "this.State == ParameterWriterState.Start"); + this.ReplaceScope(ParameterWriterState.Completed); + } + + /// + /// Replaces the current scope with a new scope; checks that the transition is valid. + /// + /// The new state to transition into. + private void ReplaceScope(ParameterWriterState newState) + { + this.ValidateTransition(newState); + this.scopes.Pop(); + this.scopes.Push(newState); + } + + /// + /// Verify that the transition from the current state into new state is valid. + /// + /// The new writer state to transition into. + private void ValidateTransition(ParameterWriterState newState) + { + if (this.State != ParameterWriterState.Error && newState == ParameterWriterState.Error) + { + // we can always transition into an error state if we are not already in an error state + return; + } + + switch (this.State) + { + case ParameterWriterState.Start: + if (newState != ParameterWriterState.CanWriteParameter && newState != ParameterWriterState.Completed) + { + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromStart)); + } + + break; + case ParameterWriterState.CanWriteParameter: + if (newState != ParameterWriterState.CanWriteParameter && newState != ParameterWriterState.ActiveSubWriter && newState != ParameterWriterState.Completed) + { + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromCanWriteParameter)); + } + + break; + case ParameterWriterState.ActiveSubWriter: + if (newState != ParameterWriterState.CanWriteParameter) + { + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromActiveSubWriter)); + } + + break; + case ParameterWriterState.Completed: + // we should never see a state transition when in state 'Completed' + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromCompleted)); + case ParameterWriterState.Error: + if (newState != ParameterWriterState.Error) + { + // No more state transitions once we are in error state + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterWriterCore_ValidateTransition_InvalidTransitionFromError)); + } + + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataParameterWriterCore_ValidateTransition_UnreachableCodePath)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKind.cs new file mode 100644 index 0000000..da33ce7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKind.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Enumeration representing the different kinds of payloads ODatLib can write. + /// + public enum ODataPayloadKind + { + /// Payload kind for a resource set. + ResourceSet = 0, + + /// Payload kind for a resource. + Resource = 1, + + /// Payload kind for a property. + Property = 2, + + /// Payload kind for an entity reference link. + EntityReferenceLink = 3, + + /// Payload kind for entity reference links. + EntityReferenceLinks = 4, + + /// Payload kind for a raw value. + Value = 5, + + /// Payload kind for a binary value. + BinaryValue = 6, + + /// Payload kind for a collection. + Collection = 7, + + /// Payload kind for a service document. + ServiceDocument = 8, + + /// Payload kind for a metadata document. + MetadataDocument = 9, + + /// Payload kind for an error. + Error = 10, + + /// Payload kind for a batch. + Batch = 11, + + /// Payload kind for parameters for a service action. + Parameter = 12, + + /// Payload kind for individual property in an entity. + IndividualProperty = 13, + + /// Payload kind for delta. + Delta = 14, + + /// Payload kind for async. + Asynchronous = 15, + + /// Unknown format + Unsupported = int.MaxValue, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionInfo.cs new file mode 100644 index 0000000..e332df8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionInfo.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System.Diagnostics.CodeAnalysis; + using System.Text; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Represents the set of information available for payload kind detection. + /// + /// This class is used to represent the input to run payload kind detection using + /// . See the documentation of that method for more + /// information. + internal sealed class ODataPayloadKindDetectionInfo + { + /// The parsed content type as . + private readonly ODataMediaType contentType; + + /// The encoding specified in the charset parameter of contentType or the default encoding from MediaType. + private readonly Encoding encoding; + + /// The being used for reading the message. + private readonly ODataMessageReaderSettings messageReaderSettings; + + /// The for the payload. + private readonly IEdmModel model; + + /// + /// Constructor. + /// + /// The context information for the message. + /// The being used for reading the message. + internal ODataPayloadKindDetectionInfo( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo.MediaType, "messageInfo.MediaType"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "readerSettings"); + + this.contentType = messageInfo.MediaType; + this.encoding = messageInfo.Encoding; + this.messageReaderSettings = messageReaderSettings; + this.model = messageInfo.Model; + } + + /// + /// The being used for reading the message. + /// + public ODataMessageReaderSettings MessageReaderSettings + { + get { return this.messageReaderSettings; } + } + + /// + /// The for the payload. + /// + public IEdmModel Model + { + get { return this.model; } + } + + /// + /// The being used for reading the message. + /// + internal ODataMediaType ContentType + { + get + { + return this.contentType; + } + } + + /// + /// The encoding derived from the content type or the default encoding. + /// + /// The encoding derived from the content type or the default encoding. + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "There is computation needed to get the encoding from the content type; thus a method.")] + public Encoding GetEncoding() + { + return this.encoding ?? this.contentType.SelectEncoding(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionResult.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionResult.cs new file mode 100644 index 0000000..d440beb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadKindDetectionResult.cs @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + #endregion Namespaces + + /// Represents the result of running payload kind detection for a specified payload kind and format. + /// This class is used to represent the result of running payload kind detection using + /// . See the documentation of that method for more + /// information. + public sealed class ODataPayloadKindDetectionResult + { + /// The detected payload kind. + private readonly ODataPayloadKind payloadKind; + + /// The format for the detected payload kind. + private readonly ODataFormat format; + + /// + /// Constructor. + /// + /// The detected payload kind. + /// The format for the detected payload kind. + internal ODataPayloadKindDetectionResult(ODataPayloadKind payloadKind, ODataFormat format) + { + this.payloadKind = payloadKind; + this.format = format; + } + + /// Gets the detected payload kind. + /// The detected payload kind. + public ODataPayloadKind PayloadKind + { + get { return this.payloadKind; } + } + + /// Gets the format for the detected payload kind. + /// The format for the detected payload kind. + public ODataFormat Format + { + get { return this.format; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadValueConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadValueConverter.cs new file mode 100644 index 0000000..4b5ed3f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPayloadValueConverter.cs @@ -0,0 +1,262 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Diagnostics; + using System.Globalization; + using Microsoft.OData.Edm; + using Microsoft.OData.Json; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class for defining a payload value conversion for given model. Currently supports primitive only. + /// + public class ODataPayloadValueConverter + { + /// + /// The default instance for . + /// + private static readonly ODataPayloadValueConverter Default = new ODataPayloadValueConverter(); + + /// + /// Converts the given primitive value defined in a type definition from the payload object. + /// + /// The given CLR value. + /// The expected type reference from model. + /// The converted payload value of the underlying type. + public virtual object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference) + { + return value; + } + + /// + /// Converts the given payload value to the type defined in a type definition. + /// + /// The given payload value. + /// The expected type reference from model. + /// The converted value of the type. + public virtual object ConvertFromPayloadValue(object value, IEdmTypeReference edmTypeReference) + { + IEdmPrimitiveTypeReference primitiveTypeReference = edmTypeReference as IEdmPrimitiveTypeReference; + Debug.Assert(primitiveTypeReference != null, "primitiveTypeReference != null"); + if (primitiveTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.PrimitiveType) + { + return value; + } + + try + { + Type targetType = EdmLibraryExtensions.GetPrimitiveClrType(primitiveTypeReference.PrimitiveDefinition(), false); + + string stringValue = value as string; + if (stringValue != null) + { + return ConvertStringValue(stringValue, targetType); + } + else if (value is Int32) + { + return ConvertInt32Value((int)value, targetType, primitiveTypeReference); + } + else if (value is Decimal) + { + Decimal decimalValue = (Decimal)value; + if (targetType == typeof(Int64)) + { + return Convert.ToInt64(decimalValue); + } + + if (targetType == typeof(Double)) + { + return Convert.ToDouble(decimalValue); + } + + if (targetType == typeof(Single)) + { + return Convert.ToSingle(decimalValue); + } + + if (targetType != typeof(Decimal)) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderUtils_CannotConvertDecimal(primitiveTypeReference.FullName())); + } + } + else if (value is Double) + { + Double doubleValue = (Double)value; + if (targetType == typeof(Single)) + { + return Convert.ToSingle(doubleValue); + } + + if (targetType != typeof(Double)) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderUtils_CannotConvertDouble(primitiveTypeReference.FullName())); + } + } + else if (value is bool) + { + if (targetType != typeof(bool)) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderUtils_CannotConvertBoolean(primitiveTypeReference.FullName())); + } + } + else if (value is DateTime) + { + if (targetType != typeof(DateTime)) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderUtils_CannotConvertDateTime(primitiveTypeReference.FullName())); + } + } + else if (value is DateTimeOffset) + { + if (targetType != typeof(DateTimeOffset)) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderUtils_CannotConvertDateTimeOffset(primitiveTypeReference.FullName())); + } + } + } + catch (Exception e) + { + if (!ExceptionUtils.IsCatchableExceptionType(e)) + { + throw; + } + + throw ReaderValidationUtils.GetPrimitiveTypeConversionException(primitiveTypeReference, e, value.ToString()); + } + + // otherwise just return the value without doing any conversion + return value; + } + + internal static ODataPayloadValueConverter GetPayloadValueConverter(IServiceProvider container) + { + if (container == null) + { + return Default; + } + + return container.GetRequiredService(); + } + + /// + /// Converts the given JSON string value to the expected type as per OData conversion rules for JSON values. + /// + /// String value to the converted. + /// Target type to which the string value needs to be converted. + /// Object which is in sync with the target type. + private static object ConvertStringValue(string stringValue, Type targetType) + { + // COMPAT 53: Support for System.Data.Linq.Binary and System.Xml.Linq.XElement + if (targetType == typeof(byte[])) + { + return Convert.FromBase64String(stringValue); + } + + if (targetType == typeof(Guid)) + { + return new Guid(stringValue); + } + + // Convert.ChangeType does not support TimeSpan. + if (targetType == typeof(TimeSpan)) + { + return EdmValueParser.ParseDuration(stringValue); + } + + // Date + if (targetType == typeof(Date)) + { + return PlatformHelper.ConvertStringToDate(stringValue); + } + + // Time + if (targetType == typeof(TimeOfDay)) + { + return PlatformHelper.ConvertStringToTimeOfDay(stringValue); + } + + // DateTimeOffset needs to be read using the XML rules (as per the JSON Light spec). + if (targetType == typeof(DateTimeOffset)) + { + return PlatformHelper.ConvertStringToDateTimeOffset(stringValue); + } + + if (targetType == typeof(Double) || targetType == typeof(Single)) + { + // Accept Infinity and -Infinity to perserve consistence + if (stringValue == CultureInfo.InvariantCulture.NumberFormat.PositiveInfinitySymbol) + { + stringValue = JsonValueUtils.ODataJsonPositiveInfinitySymbol; + } + else if (stringValue == CultureInfo.InvariantCulture.NumberFormat.NegativeInfinitySymbol) + { + stringValue = JsonValueUtils.ODataJsonNegativeInfinitySymbol; + } + + return Convert.ChangeType(stringValue, targetType, JsonValueUtils.ODataNumberFormatInfo); + } + + // For string types, we support conversion to all possible primitive types + return Convert.ChangeType(stringValue, targetType, CultureInfo.InvariantCulture); + } + + /// + /// Converts the given JSON int value to the expected type as per OData conversion rules for JSON values. + /// + /// Int32 value to the converted. + /// Target type to which the int value needs to be converted. + /// Type reference to which the value needs to be converted. + /// Object which is in sync with the property type. + private static object ConvertInt32Value(int intValue, Type targetType, IEdmPrimitiveTypeReference primitiveTypeReference) + { + if (targetType == typeof(Int16)) + { + return Convert.ToInt16(intValue); + } + + if (targetType == typeof(Byte)) + { + return Convert.ToByte(intValue); + } + + if (targetType == typeof(SByte)) + { + return Convert.ToSByte(intValue); + } + + if (targetType == typeof(Single)) + { + return Convert.ToSingle(intValue); + } + + if (targetType == typeof(Double)) + { + return Convert.ToDouble(intValue); + } + + if (targetType == typeof(Decimal)) + { + return Convert.ToDecimal(intValue); + } + + if (targetType == typeof(Int64)) + { + return Convert.ToInt64(intValue); + } + + if (targetType != typeof(Int32)) + { + throw new ODataException(ODataErrorStrings.ODataJsonReaderUtils_CannotConvertInt32(primitiveTypeReference.FullName())); + } + + return intValue; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPreferenceHeader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPreferenceHeader.cs new file mode 100644 index 0000000..ee30545 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPreferenceHeader.cs @@ -0,0 +1,501 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + + /// + /// Class to set the "Prefer" header on an or + /// the "Preference-Applied" header on an . + /// + public class ODataPreferenceHeader + { + /// + /// The return preference token. + /// + private const string ReturnPreferenceTokenName = "return"; + + /// + /// The return=representation preference token value. + /// + private const string ReturnRepresentationPreferenceTokenValue = "representation"; + + /// + /// The return=minimalpreference token value. + /// + private const string ReturnMinimalPreferenceTokenValue = "minimal"; + + /// + /// The odata-annotations preference-extensions token. + /// + private const string ODataAnnotationPreferenceToken = "odata.include-annotations"; + + /// + /// The respond-async preference token. + /// + private const string RespondAsyncPreferenceToken = "respond-async"; + + /// + /// The wait preference token. + /// + private const string WaitPreferenceTokenName = "wait"; + + /// + /// The odata.continue-on-error preference token. + /// + private const string ODataContinueOnErrorPreferenceToken = "odata.continue-on-error"; + + /// + /// The odata.maxpagesize=# preference token. + /// + private const string ODataMaxPageSizePreferenceToken = "odata.maxpagesize"; + + /// + /// The odata.track-changes preference token. + /// + private const string ODataTrackChangesPreferenceToken = "odata.track-changes"; + + /// + /// The Prefer header name. + /// + private const string PreferHeaderName = "Prefer"; + + /// + /// The Preference-Applied header name. + /// + private const string PreferenceAppliedHeaderName = "Preference-Applied"; + + /// + /// Empty header parameters + /// + private static readonly KeyValuePair[] EmptyParameters = new KeyValuePair[0]; + + /// + /// The odata.continue-on-error preference. + /// + private static readonly HttpHeaderValueElement ContinueOnErrorPreference = new HttpHeaderValueElement(ODataContinueOnErrorPreferenceToken, null, EmptyParameters); + + /// + /// The return=minimal preference. + /// + private static readonly HttpHeaderValueElement ReturnMinimalPreference = new HttpHeaderValueElement(ReturnPreferenceTokenName, ReturnMinimalPreferenceTokenValue, EmptyParameters); + + /// + /// The return=representation preference. + /// + private static readonly HttpHeaderValueElement ReturnRepresentationPreference = new HttpHeaderValueElement(ReturnPreferenceTokenName, ReturnRepresentationPreferenceTokenValue, EmptyParameters); + + /// + /// The respond-async preference. + /// + private static readonly HttpHeaderValueElement RespondAsyncPreference = new HttpHeaderValueElement(RespondAsyncPreferenceToken, null, EmptyParameters); + + /// + /// The odata.track-changes preference. + /// + private static readonly HttpHeaderValueElement TrackChangesPreference = new HttpHeaderValueElement(ODataTrackChangesPreferenceToken, null, EmptyParameters); + + /// + /// The message to set the preference header to and to get the preference header from. + /// + private readonly ODataMessage message; + + /// + /// "Prefer" if message is an IODataRequestMessage; "Preference-Applied" if message is an IODataResponseMessage. + /// + private readonly string preferenceHeaderName; + + /// + /// Dictionary of preferences in the header + /// + private HttpHeaderValue preferences; + + /// + /// Internal constructor to instantiate an from an . + /// + /// The request message to get and set the "Prefer" header. + internal ODataPreferenceHeader(IODataRequestMessage requestMessage) + { + Debug.Assert(requestMessage != null, "requestMessage != null"); + this.message = new ODataRequestMessage(requestMessage, /*writing*/ true, /*disableMessageStreamDisposal*/ false, /*maxMessageSize*/ -1); + this.preferenceHeaderName = PreferHeaderName; + } + + /// + /// Internal constructor to instantiate an from an . + /// + /// The response message to get and set the "Preference-Applied" header. + internal ODataPreferenceHeader(IODataResponseMessage responseMessage) + { + Debug.Assert(responseMessage != null, "responseMessage != null"); + this.message = new ODataResponseMessage(responseMessage, /*writing*/ true, /*disableMessageStreamDisposal*/ false, /*maxMessageSize*/ -1); + this.preferenceHeaderName = PreferenceAppliedHeaderName; + } + + /// + /// Property to get and set the "return=representation" and "return=minimal" preferences to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// Setting true sets the "return=representation" preference and clears the "return=minimal" preference. + /// Setting false sets the "return=minimal" preference and clears the "return=representation" preference. + /// Setting null clears the "return=representation" and "return=minimal" preferences. + /// Returns true if the "return=representation" preference is on the header. Otherwise returns false if the "return=minimal" is on the header. + /// Returning null indicates that "return=representation" and "return=minimal" are not on the header. + /// + public bool? ReturnContent + { + get + { + var returnContentPreference = this.Get(ReturnPreferenceTokenName); + if (returnContentPreference != null && returnContentPreference.Value != null) + { + if (returnContentPreference.Value.ToLowerInvariant().Equals(ReturnRepresentationPreferenceTokenValue)) + { + return true; + } + + if (returnContentPreference.Value.ToLowerInvariant().Equals(ReturnMinimalPreferenceTokenValue)) + { + return false; + } + } + + return null; + } + + set + { + // if the value is null, the "ReturnPreferenceTokenName" is cleared. + this.Clear(ReturnPreferenceTokenName); + + if (value == true) + { + this.Set(ReturnRepresentationPreference); + } + + if (value == false) + { + this.Set(ReturnMinimalPreference); + } + } + } + + /// + /// Property to get and set the "odata.include-annotations" preference with the given filter to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// If the "odata-annotations" preference is already on the header, set replaces the existing instance. + /// Returning null indicates that the "odata.include-annotations" preference is not on the header. + /// + /// The filter string may be a comma delimited list of any of the following supported patterns: + /// "*" -- Matches all annotation names. + /// "ns.*" -- Matches all annotation names under the namespace "ns". + /// "ns.name" -- Matches only the annotation name "ns.name". + /// "-" -- The exclude operator may be used with any of the supported pattern, for example: + /// "-ns.*" -- Excludes all annotation names under the namespace "ns". + /// "-ns.name" -- Excludes only the annotation name "ns.name". + /// Null or empty filter is equivalent to "-*". + /// + /// The relative priority of the pattern is base on the relative specificity of the patterns being compared. If pattern1 is under the namespace pattern2, + /// pattern1 is more specific than pattern2 because pattern1 matches a subset of what pattern2 matches. We give higher priority to the pattern that is more specific. + /// For example: + /// "ns.*" has higher priority than "*" + /// "ns.name" has higher priority than "ns.*" + /// "ns1.name" has same priority as "ns2.*" + /// + /// Patterns with the exclude operator takes higher precedence than the same pattern without. + /// For example: "-ns.name" has higher priority than "ns.name". + /// + /// Examples: + /// "ns1.*,ns.name" -- Matches any annotation name under the "ns1" namespace and the "ns.name" annotation. + /// "*,-ns.*,ns.name" -- Matches any annotation name outside of the "ns" namespace and only "ns.name" under the "ns" namespace. + /// + public string AnnotationFilter + { + get + { + var odataAnnotations = this.Get(ODataAnnotationPreferenceToken); + + if (odataAnnotations != null && odataAnnotations.Value != null) + { + return odataAnnotations.Value.Trim('"'); + } + + return null; + } + + set + { + ExceptionUtils.CheckArgumentStringNotEmpty(value, "AnnotationFilter"); + + if (value == null) + { + this.Clear(ODataAnnotationPreferenceToken); + } + else + { + this.Set(new HttpHeaderValueElement(ODataAnnotationPreferenceToken, AddQuotes(value), EmptyParameters)); + } + } + } + + /// + /// Property to get and set the "respond-async" preference to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// Setting true sets the "respond-async" preference. + /// Setting false clears the "respond-async" preference. + /// Returns true if the "respond-async" preference is on the header. Otherwise returns false if the "respond-async" is not on the header. + /// + public bool RespondAsync + { + get + { + return this.Get(RespondAsyncPreferenceToken) != null; + } + + set + { + if (value) + { + this.Set(RespondAsyncPreference); + } + else + { + this.Clear(RespondAsyncPreferenceToken); + } + } + } + + /// + /// Property to get and set the "wait" preference to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// Setting N sets the "wait=N" preference. + /// Setting null clears the "wait" preference. + /// Returns N if the "wait=N" preference is on the header. + /// Returning null indicates that "wait" is not on the header. + /// + public int? Wait + { + get + { + var wait = this.Get(WaitPreferenceTokenName); + + if (wait != null && wait.Value != null) + { + int value; + if (int.TryParse(wait.Value, out value)) + { + return value; + } + + // TODO: Fix hard code string before Loc of 6.16 release + throw new ODataException(string.Format(CultureInfo.InvariantCulture, + "Invalid value '{0}' for {1} preference header found. The {1} preference header requires an integer value.", + wait.Value, ODataPreferenceHeader.WaitPreferenceTokenName)); + } + + return null; + } + + set + { + if (value != null) + { + this.Set(new HttpHeaderValueElement(WaitPreferenceTokenName, string.Format(CultureInfo.InvariantCulture, "{0}", value), EmptyParameters)); + } + else + { + this.Clear(WaitPreferenceTokenName); + } + } + } + + /// + /// Property to get and set the "odata.continue-on-error" preference to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// Setting true sets the "odata.continue-on-error" preference. + /// Setting false clears the "odata.continue-on-error" preference. + /// Returns true of the "odata.continue-on-error" preference is on the header. Otherwise returns false if the "odata.continue-on-error" is not on the header. + /// + public bool ContinueOnError + { + get + { + return this.Get(ODataContinueOnErrorPreferenceToken) != null; + } + + set + { + if (value) + { + this.Set(ContinueOnErrorPreference); + } + else + { + this.Clear(ODataContinueOnErrorPreferenceToken); + } + } + } + + /// + /// Property to get and set the "odata.maxpagesize" preference to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// Setting N sets the "odata.maxpagesize=N" preference. + /// Setting null clears the "odata.maxpagesize" preference. + /// Returns N if the "odata.maxpagesize=N" preference is on the header. + /// Returning null indicates that "odata.maxpagesize" is not on the header. + /// + public int? MaxPageSize + { + get + { + var maxPageSizeHttpHeaderValueElement = this.Get(ODataMaxPageSizePreferenceToken); + + if (maxPageSizeHttpHeaderValueElement != null && maxPageSizeHttpHeaderValueElement.Value != null) + { + int value; + if (int.TryParse(maxPageSizeHttpHeaderValueElement.Value, out value)) + { + return value; + } + + // TODO: Fix hard code string before Loc of 6.16 release + throw new ODataException(string.Format(CultureInfo.InvariantCulture, + "Invalid value '{0}' for {1} preference header found. The {1} preference header requires an integer value.", + maxPageSizeHttpHeaderValueElement.Value, ODataPreferenceHeader.ODataMaxPageSizePreferenceToken)); + } + + return null; + } + + set + { + if (value.HasValue) + { + this.Set(new HttpHeaderValueElement(ODataMaxPageSizePreferenceToken, string.Format(CultureInfo.InvariantCulture, "{0}", value.Value), EmptyParameters)); + } + else + { + this.Clear(ODataMaxPageSizePreferenceToken); + } + } + } + + /// + /// Property to get and set the "odata.track-changes" preference to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// Setting true sets the "odata.track-changes" preference. + /// Setting false clears the "odata.track-changes" preference. + /// Returns true of the "odata.track-changes" preference is on the header. Otherwise returns false if the "odata.track-changes" is not on the header. + /// + public bool TrackChanges + { + get + { + return this.Get(ODataTrackChangesPreferenceToken) != null; + } + + set + { + if (value) + { + this.Set(TrackChangesPreference); + } + else + { + this.Clear(ODataTrackChangesPreferenceToken); + } + } + } + + /// + /// Dictionary of preferences in the header. + /// + private HttpHeaderValue Preferences + { + get { return this.preferences ?? (this.preferences = this.ParsePreferences()); } + } + + /// + /// Clears the from the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// + /// The preference to clear. + protected void Clear(string preference) + { + Debug.Assert(!string.IsNullOrEmpty(preference), "!string.IsNullOrEmpty(preference)"); + if (this.Preferences.Remove(preference)) + { + this.SetPreferencesToMessageHeader(); + } + } + + /// + /// Sets the to the "Prefer" header on the underlying IODataRequestMessage or + /// the "Preference-Applied" header on the underlying IODataResponseMessage. + /// + /// The preference to set. + /// + /// If is already on the header, this method does a replace rather than adding another instance of the same preference. + /// + protected void Set(HttpHeaderValueElement preference) + { + Debug.Assert(preference != null, "preference != null"); + this.Preferences[preference.Name] = preference; + this.SetPreferencesToMessageHeader(); + } + + /// + /// Gets the from the "Prefer" header from the underlying or + /// the "Preference-Applied" header from the underlying . + /// + /// The preference to get. + /// Returns a key value pair of the and its value. The Value property of the key value pair may be null since not + /// all preferences have value. If the is missing from the header, null is returned. + protected HttpHeaderValueElement Get(string preferenceName) + { + Debug.Assert(!string.IsNullOrEmpty(preferenceName), "!string.IsNullOrEmpty(preferenceName)"); + HttpHeaderValueElement value; + if (!this.Preferences.TryGetValue(preferenceName, out value)) + { + return null; + } + + return value; + } + + /// + /// Adds quotes around the given text value. + /// + /// text to quote. + /// Returns the quoted text. + private static string AddQuotes(string text) + { + return "\"" + text + "\""; + } + + /// + /// Parses the current preference values to a dictionary of preference and value pairs. + /// + /// Returns a dictionary of preference and value pairs; null if the preference header has not been set. + private HttpHeaderValue ParsePreferences() + { + string preferenceHeaderValue = this.message.GetHeader(this.preferenceHeaderName); + HttpHeaderValueLexer preferenceHeaderLexer = HttpHeaderValueLexer.Create(this.preferenceHeaderName, preferenceHeaderValue); + return preferenceHeaderLexer.ToHttpHeaderValue(); + } + + /// + /// Sets the "Prefer" or the "Preference-Applied" header to the underlying message. + /// + private void SetPreferencesToMessageHeader() + { + Debug.Assert(this.preferences != null, "this.preferences != null"); + this.message.SetHeader(this.preferenceHeaderName, this.Preferences.ToString()); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataProperty.cs new file mode 100644 index 0000000..0906dfd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataProperty.cs @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Represents a single property of a resource with a value. + /// + public sealed class ODataProperty : ODataPropertyInfo + { + /// Gets or sets the property value. + /// The property value. + public object Value + { + get + { + if (this.ODataValue == null) + { + return null; + } + + return this.ODataValue.FromODataValue(); + } + + set + { + this.ODataValue = value.ToODataValue(); + } + } + + /// + /// Property value, represented as an ODataValue. + /// + /// + /// This value is the same as , except that primitive types are wrapped + /// in an instance of ODataPrimitiveValue, and null values are represented by an instance of ODataNullValue. + /// + internal ODataValue ODataValue + { + get; private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyInfo.cs new file mode 100644 index 0000000..f37b7bf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyInfo.cs @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + + /// + /// Represents information about a single property of a resource. + /// + public class ODataPropertyInfo : ODataItem + { + /// Gets or sets the property name. + /// The property name. + public string Name + { + get; + set; + } + + /// Gets or sets the kind of primitive type of the property. + /// The of the property. + public virtual EdmPrimitiveTypeKind PrimitiveTypeKind + { + get; + set; + } + + /// + /// Collection of custom instance annotations. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow the same instance annotation collection instance to be shared across ODataLib OM instances.")] + public ICollection InstanceAnnotations + { + get { return this.GetInstanceAnnotations(); } + set { this.SetInstanceAnnotations(value); } + } + + /// + /// Provides additional serialization information to the for this . + /// + internal ODataPropertySerializationInfo SerializationInfo { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyKind.cs new file mode 100644 index 0000000..01e9e27 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertyKind.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// The enum of property kinds. + /// + public enum ODataPropertyKind + { + /// + /// Unspecified property kind or if the property is not a key property, an etag property or an open property. + /// + Unspecified = 0, + + /// + /// The property is a key property. + /// + Key, + + /// + /// The property is an etag property + /// + ETag, + + /// + /// The property is an open property + /// + Open + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertySerializationInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertySerializationInfo.cs new file mode 100644 index 0000000..e1eaedb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataPropertySerializationInfo.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class to provide additional serialization information to the for an . + /// + public sealed class ODataPropertySerializationInfo + { + /// + /// The kind of the property + /// + public ODataPropertyKind PropertyKind + { + get; + set; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawInputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawInputContext.cs new file mode 100644 index 0000000..c79858b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawInputContext.cs @@ -0,0 +1,261 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Implementation of the OData input for RAW OData format (raw value and batch). + /// + internal class ODataRawInputContext : ODataInputContext + { + /// The encoding to use to read from the batch stream. + protected readonly Encoding Encoding; + + /// Use a buffer size of 4k that is read from the stream at a time. + private const int BufferSize = 4096; + + /// The to read. + private readonly ODataPayloadKind readerPayloadKind; + + /// The input stream to read the data from. + private Stream stream; + + /// The text reader to read non-binary values from. + private TextReader textReader; + + /// Constructor. + /// The format for this input context. + /// The context information for the message. + /// Configuration settings of the OData reader. + public ODataRawInputContext( + ODataFormat format, + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + : base(format, messageInfo, messageReaderSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + Debug.Assert(messageInfo.PayloadKind != ODataPayloadKind.Unsupported, "readerPayloadKind != ODataPayloadKind.Unsupported"); + + try + { + this.stream = messageInfo.MessageStream; + this.Encoding = messageInfo.Encoding; + this.readerPayloadKind = messageInfo.PayloadKind; + } + catch (Exception e) + { + // Dispose the message stream if we failed to create the input context. + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + messageInfo.MessageStream.Dispose(); + } + + throw; + } + } + + /// + /// The stream of the raw input context. + /// + public Stream Stream + { + get + { + return this.stream; + } + } + + /// + /// Create an . + /// + /// The newly created . + internal override ODataAsynchronousReader CreateAsynchronousReader() + { + return this.CreateAsynchronousReaderImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously create an . + /// + /// Task which when completed returns the newly created . + internal override Task CreateAsynchronousReaderAsync() + { + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateAsynchronousReaderImplementation()); + } +#endif + + /// + /// Read a top-level value. + /// + /// The expected primitive type for the value to be read; null if no expected type is available. + /// An representing the read value. + internal override object ReadValue(IEdmPrimitiveTypeReference expectedPrimitiveTypeReference) + { + return this.ReadValueImplementation(expectedPrimitiveTypeReference); + } + +#if PORTABLELIB + /// + /// Asynchronously read a top-level value. + /// + /// The expected type reference for the value to be read; null if no expected type is available. + /// Task which when completed returns an representing the read value. + internal override Task ReadValueAsync(IEdmPrimitiveTypeReference expectedPrimitiveTypeReference) + { + // Note that the reading is actually synchronous since we buffer the entire input when getting the stream from the message. + return TaskUtils.GetTaskForSynchronousOperation(() => this.ReadValueImplementation(expectedPrimitiveTypeReference)); + } +#endif + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected override void Dispose(bool disposing) + { + if (disposing) + { + try + { + if (this.textReader != null) + { + this.textReader.Dispose(); + } + else if (this.stream != null) + { + this.stream.Dispose(); + } + } + finally + { + this.textReader = null; + this.stream = null; + } + } + + base.Dispose(disposing); + } + + /// + /// Create a . + /// + /// The newly created . + private ODataAsynchronousReader CreateAsynchronousReaderImplementation() + { + return new ODataAsynchronousReader(this, this.Encoding); + } + + /// + /// Read a top-level value. + /// + /// The expected primitive type for the value to be read; null if no expected type is available. + /// An representing the read value. + private object ReadValueImplementation(IEdmPrimitiveTypeReference expectedPrimitiveTypeReference) + { + // if an expected primitive type is specified it trumps the content type/reader payload kind + bool readBinary; + if (expectedPrimitiveTypeReference == null) + { + readBinary = this.readerPayloadKind == ODataPayloadKind.BinaryValue; + } + else + { + if (expectedPrimitiveTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.Binary || + expectedPrimitiveTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.Stream) + { + readBinary = true; + } + else + { + readBinary = false; + } + } + + if (readBinary) + { + return this.ReadBinaryValue(); + } + else + { + Debug.Assert(this.textReader == null, "this.textReader == null"); + this.textReader = this.Encoding == null ? new StreamReader(this.stream) : new StreamReader(this.stream, this.Encoding); + return this.ReadRawValue(expectedPrimitiveTypeReference); + } + } + + /// + /// Read the binary value from the stream. + /// + /// A byte array containing all the data read. + private byte[] ReadBinaryValue() + { + //// This method is copied from Deserializer.ReadByteStream + + byte[] data; + + long numberOfBytesRead = 0; + int result; + List byteData = new List(); + + do + { + data = new byte[BufferSize]; + result = this.stream.Read(data, 0, data.Length); + numberOfBytesRead += result; + byteData.Add(data); + } + while (result == data.Length); + + // Find out the total number of bytes read and copy data from byteData to data + data = new byte[numberOfBytesRead]; + for (int i = 0; i < byteData.Count - 1; i++) + { + Buffer.BlockCopy(byteData[i], 0, data, i * BufferSize, BufferSize); + } + + // For the last buffer, copy the remaining number of bytes, not always the buffer size + Buffer.BlockCopy(byteData[byteData.Count - 1], 0, data, (byteData.Count - 1) * BufferSize, result); + return data; + } + + /// + /// Reads the content of a text reader as string and, if is specified and primitive type conversion + /// is enabled, converts the string to the expected type. + /// + /// The expected type of the value being read or null if no type conversion should be performed. + /// The raw value that was read from the text reader either as string or converted to the provided . + private object ReadRawValue(IEdmPrimitiveTypeReference expectedPrimitiveTypeReference) + { + string stringFromStream = this.textReader.ReadToEnd(); + + object rawValue; + if (expectedPrimitiveTypeReference != null && this.MessageReaderSettings.EnablePrimitiveTypeConversion) + { + rawValue = ODataRawValueUtils.ConvertStringToPrimitive(stringFromStream, expectedPrimitiveTypeReference); + } + else + { + rawValue = stringFromStream; + } + + return rawValue; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawOutputContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawOutputContext.cs new file mode 100644 index 0000000..14f7ca6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawOutputContext.cs @@ -0,0 +1,399 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Text; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// RAW format output context. Used by RAW values and batch. + /// + internal class ODataRawOutputContext : ODataOutputContext + { + /// The encoding to use for the output. + protected Encoding encoding; + + /// Listener to notify when writing in-stream errors. + protected IODataOutputInStreamErrorListener outputInStreamErrorListener; + + /// The message output stream. + private Stream messageOutputStream; + + /// The asynchronous output stream if we're writing asynchronously. + private AsyncBufferedStream asynchronousOutputStream; + + /// The output stream to write to (both sync and async cases). + private Stream outputStream; + + /// RawValueWriter used to write actual values to the stream. + private RawValueWriter rawValueWriter; + + /// + /// Constructor. + /// + /// The format for this output context. + /// The context information for the message. + /// Configuration settings of the OData writer. + internal ODataRawOutputContext( + ODataFormat format, + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + : base(format, messageInfo, messageWriterSettings) + { + Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); + + try + { + this.messageOutputStream = messageInfo.MessageStream; + this.encoding = messageInfo.Encoding; + + if (this.Synchronous) + { + this.outputStream = this.messageOutputStream; + } + else + { + this.asynchronousOutputStream = new AsyncBufferedStream(this.messageOutputStream); + this.outputStream = this.asynchronousOutputStream; + } + } + catch + { + this.messageOutputStream.Dispose(); + throw; + } + } + + /// + /// The output stream to write the payload to. + /// + internal Stream OutputStream + { + get + { + return this.outputStream; + } + } + + /// + /// The text writer to use to write text into the payload. + /// + /// + /// InitializeRawValueWriter must be called before this is used. + /// + /// Also, within this class we should be using RawValueWriter for everything. Ideally we wouldn't leak the TextWriter out, but + /// the Batch writer needs it at the moment. + /// + internal TextWriter TextWriter + { + get + { + return this.rawValueWriter.TextWriter; + } + } + + /// + /// Synchronously flush the writer. + /// + internal void Flush() + { + this.AssertSynchronous(); + + if (this.rawValueWriter != null) + { + this.rawValueWriter.Flush(); + } + } + +#if PORTABLELIB + /// + /// Asynchronously flush the writer. + /// + /// Task which represents the pending flush operation. + /// The method should not throw directly if the flush operation itself fails, it should instead return a faulted task. + internal Task FlushAsync() + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + if (this.rawValueWriter != null) + { + this.rawValueWriter.Flush(); + } + + Debug.Assert(this.asynchronousOutputStream != null, "In async writing we must have the async buffered stream."); + return this.asynchronousOutputStream.FlushAsync(); + }) + .FollowOnSuccessWithTask((asyncBufferedStreamFlushTask) => this.messageOutputStream.FlushAsync()); + } +#endif + + /// + /// Writes an into the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// + /// This method is called if the ODataMessageWriter.WriteError is called once some other + /// write operation has already started. + /// The method should write the in-stream error representation for the specific format into the current payload. + /// Before the method is called no flush is performed on the output context or any active writer. + /// It is the responsibility of this method to flush the output before the method returns. + /// + internal override void WriteInStreamError(ODataError error, bool includeDebugInformation) + { + if (this.outputInStreamErrorListener != null) + { + this.outputInStreamErrorListener.OnInStreamError(); + } + + throw new ODataException(Strings.ODataMessageWriter_CannotWriteInStreamErrorForRawValues); + } + +#if PORTABLELIB + /// + /// Writes an into the message payload. + /// + /// The error to write. + /// + /// A flag indicating whether debug information (e.g., the inner error from the ) should + /// be included in the payload. This should only be used in debug scenarios. + /// + /// Task which represents the pending write operation. + /// + /// This method is called if the ODataMessageWriter.WriteError is called once some other + /// write operation has already started. + /// The method should write the in-stream error representation for the specific format into the current payload. + /// Before the method is called no flush is performed on the output context or any active writer. + /// It is the responsibility of this method to make sure that all the data up to this point are written before + /// the in-stream error is written. + /// It is the responsibility of this method to flush the output before the task finishes. + /// + internal override Task WriteInStreamErrorAsync(ODataError error, bool includeDebugInformation) + { + if (this.outputInStreamErrorListener != null) + { + this.outputInStreamErrorListener.OnInStreamError(); + } + + throw new ODataException(Strings.ODataMessageWriter_CannotWriteInStreamErrorForRawValues); + } +#endif + + /// + /// Creates an to write an async response. + /// + /// The created writer. + /// The write must flush the output when it's finished (inside the last Write call). + internal override ODataAsynchronousWriter CreateODataAsynchronousWriter() + { + this.AssertSynchronous(); + + return this.CreateODataAsynchronousWriterImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously creates an to write an async response. + /// + /// A running task for the created writer. + /// The write must flush the output when it's finished (inside the last Write call). + internal override Task CreateODataAsynchronousWriterAsync() + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateODataAsynchronousWriterImplementation()); + } +#endif + + /// + /// Writes a single value as the message body. + /// + /// The value to write. + /// It is the responsibility of this method to flush the output before the method returns. + internal override void WriteValue(object value) + { + this.AssertSynchronous(); + + this.WriteValueImplementation(value); + this.Flush(); + } + +#if PORTABLELIB + /// + /// Asynchronously writes a single value as the message body. + /// + /// The value to write. + /// A running task representing the writing of the value. + /// It is the responsibility of this method to flush the output before the task finishes. + internal override Task WriteValueAsync(object value) + { + this.AssertAsynchronous(); + + return TaskUtils.GetTaskForSynchronousOperationReturningTask( + () => + { + this.WriteValueImplementation(value); + return this.FlushAsync(); + }); + } +#endif + + /// + /// Initialized a new text writer over the message payload stream. + /// + /// This can only be called if the text writer was not yet initialized or it has been closed. + /// It can be called several times with CloseWriter calls in between though. + internal void InitializeRawValueWriter() + { + Debug.Assert(this.rawValueWriter == null, "The rawValueWriter has already been initialized."); + + this.rawValueWriter = new RawValueWriter(this.MessageWriterSettings, this.outputStream, this.encoding); + } + + /// + /// Closes the text writer. + /// + internal void CloseWriter() + { + Debug.Assert(this.rawValueWriter != null, "The text writer has not been initialized yet."); + + this.rawValueWriter.Dispose(); + this.rawValueWriter = null; + } + + /// + /// Verifies the output context was not yet disposed, fails otherwise. + /// + internal void VerifyNotDisposed() + { + if (this.messageOutputStream == null) + { + throw new ObjectDisposedException(this.GetType().FullName); + } + } + + /// + /// Flushes all buffered data to the underlying stream synchronously. + /// + internal void FlushBuffers() + { + if (this.asynchronousOutputStream != null) + { + this.asynchronousOutputStream.FlushSync(); + } + } + +#if PORTABLELIB + /// + /// Flushes all buffered data to the underlying stream asynchronously. + /// + /// Task which represents the pending operation. + internal Task FlushBuffersAsync() + { + if (this.asynchronousOutputStream != null) + { + return this.asynchronousOutputStream.FlushAsync(); + } + else + { + return TaskUtils.CompletedTask; + } + } +#endif + + /// + /// Perform the actual cleanup work. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "rawValueWriter", Justification = "We intentionally don't dispose rawValueWriter, we instead dispose the underlying stream manually.")] + protected override void Dispose(bool disposing) + { + try + { + if (this.messageOutputStream != null) + { + if (this.rawValueWriter != null) + { + this.rawValueWriter.Flush(); + } + + // In the async case the underlying stream is the async buffered stream, so we have to flush that explicitly. + if (this.asynchronousOutputStream != null) + { + this.asynchronousOutputStream.FlushSync(); + this.asynchronousOutputStream.Dispose(); + } + + // Dispose the message stream (note that we OWN this stream, so we always dispose it). + this.messageOutputStream.Dispose(); + } + } + finally + { + this.messageOutputStream = null; + this.asynchronousOutputStream = null; + this.outputStream = null; + this.rawValueWriter = null; + } + + base.Dispose(disposing); + } + + /// + /// Writes a single value as the message body. + /// + /// The value to write. + /// Once the method returns all the data should be written, the only other call after this will be Dispose on the output context. + private void WriteValueImplementation(object value) + { + byte[] binaryValue = value as byte[]; + + if (binaryValue != null) + { + // write the bytes directly + this.OutputStream.Write(binaryValue, 0, binaryValue.Length); + } + else + { + value = this.Model.ConvertToUnderlyingTypeIfUIntValue(value); + + this.InitializeRawValueWriter(); + this.rawValueWriter.Start(); + this.rawValueWriter.WriteRawValue(value); + this.rawValueWriter.End(); + } + } + + /// + /// Creates an async writer. + /// + /// The newly created async writer. + private ODataAsynchronousWriter CreateODataAsynchronousWriterImplementation() + { + // Async writer needs the default encoding to not use the preamble. + this.encoding = this.encoding ?? MediaTypeUtils.EncodingUtf8NoPreamble; + ODataAsynchronousWriter asyncWriter = new ODataAsynchronousWriter(this); + this.outputInStreamErrorListener = asyncWriter; + return asyncWriter; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueConverter.cs new file mode 100644 index 0000000..4d26c25 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueConverter.cs @@ -0,0 +1,176 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Xml; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Helper to convert values to strings compliant to the raw value format + /// + internal static class ODataRawValueConverter + { + /// 'true' literal + private const string RawValueTrueLiteral = "true"; + + /// 'false' literal + private const string RawValueFalseLiteral = "false"; + + /// + /// Converts a boolean to the corresponding raw value string representation. + /// + /// The boolean value to convert. + /// The raw value strings representing boolean literals. + internal static string ToString(bool b) + { + return b ? RawValueTrueLiteral : RawValueFalseLiteral; + } + + /// + /// Converts a byte to the corresponding raw value string representation. + /// + /// The byte value to convert. + /// The raw value strings representing the byte value. + internal static string ToString(byte b) + { + return XmlConvert.ToString(b); + } + + /// + /// Converts a decimal to the corresponding raw value string representation. + /// + /// The decimal value to convert. + /// The raw value strings representing the decimal value. + internal static string ToString(decimal d) + { + return XmlConvert.ToString(d); + } + + /// + /// Converts the given DateTimeOffset value to string appropriate for raw value format. + /// + /// Given DateTimeOffset value. + /// raw value format string representation of . + internal static string ToString(DateTimeOffset dateTime) + { + return XmlConvert.ToString(dateTime); + } + + /// + /// Converts the given timespan value to the string appropriate for raw value format + /// + /// The timespan value to convert. + /// The string version of the timespan value in raw value format. + internal static string ToString(this TimeSpan ts) + { + return EdmValueWriter.DurationAsXml(ts); + } + + /// + /// Converts the given double value to the string appropriate for raw value format + /// + /// The double value to convert. + /// The string version of the double value in raw value format. + internal static string ToString(this double d) + { + return XmlConvert.ToString(d); + } + + /// + /// Converts the given Int16 value to the string appropriate for raw value format + /// + /// The Int16 value to convert. + /// The string version of the Int16 value in raw value format. + internal static string ToString(this Int16 i) + { + return XmlConvert.ToString(i); + } + + /// + /// Converts the given Int32 value to the string appropriate for raw value format. + /// + /// The Int32 value to convert. + /// The string version of the Int32 in raw value format. + internal static string ToString(this Int32 i) + { + return XmlConvert.ToString(i); + } + + /// + /// Converts the given Int64 value to the string appropriate for raw value format. + /// + /// The Int64 value to convert. + /// The string version of the Int64 in raw value format. + internal static string ToString(this Int64 i) + { + return XmlConvert.ToString(i); + } + + /// + /// Converts the given SByte value to the string appropriate for raw value format. + /// + /// The SByte value to convert. + /// The string version of the SByte in raw value format. + internal static string ToString(this SByte sb) + { + return XmlConvert.ToString(sb); + } + + /// + /// Converts the given byte array value to the string appropriate for raw value format. + /// + /// The byte array to convert. + /// The string version of the byte array in raw value format. + internal static string ToString(this byte[] bytes) + { + return Convert.ToBase64String(bytes); + } + + /// + /// Converts the given Single value to the string appropriate for raw value format. + /// + /// The Single value to convert. + /// The string version of the Single in raw value format. + internal static string ToString(this Single s) + { + return XmlConvert.ToString(s); + } + + /// + /// Converts the given Guid value to the string appropriate for raw value format. + /// + /// The Guid value to convert. + /// The string version of the Guid in raw value format. + internal static string ToString(this Guid guid) + { + return XmlConvert.ToString(guid); + } + + /// + /// Converts the given Date value to the string appropriate for raw value format. + /// + /// The Date value to convert. + /// The string version of the Date in raw value format. + internal static string ToString(Date date) + { + return date.ToString(); + } + + /// + /// Converts the given TimeOfDay value to the string appropriate for raw value format. + /// + /// The TimeOfDay value to convert. + /// The string version of the TimeOfDay in raw value format + internal static string ToString(TimeOfDay time) + { + return time.ToString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueFormat.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueFormat.cs new file mode 100644 index 0000000..5ca8d5c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueFormat.cs @@ -0,0 +1,147 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections.Generic; + using System.Diagnostics; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + #endregion Namespaces + + /// + /// The RAW OData format. + /// + internal sealed class ODataRawValueFormat : ODataFormat + { + /// + /// The text representation - the name of the format. + /// + /// The name of the format. + public override string ToString() + { + return "RawValue"; + } + + /// + /// Detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The set of s that are supported with the specified payload. + public override IEnumerable DetectPayloadKind( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + return DetectPayloadKindImplementation(messageInfo.MediaType); + } + + /// + /// Creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// The newly created input context. + public override ODataInputContext CreateInputContext( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + return new ODataRawInputContext(this, messageInfo, messageReaderSettings); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// The newly created output context. + public override ODataOutputContext CreateOutputContext( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + return new ODataRawOutputContext(this, messageInfo, messageWriterSettings); + } + +#if PORTABLELIB + /// + /// Asynchronously detects the payload kinds supported by this format for the specified message payload. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// A task that when completed returns the set of s + /// that are supported with the specified payload. + public override Task> DetectPayloadKindAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings settings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + return TaskUtils.GetTaskForSynchronousOperation(() => DetectPayloadKindImplementation(messageInfo.MediaType)); + } + + /// + /// Asynchronously creates an instance of the input context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData reader. + /// Task which when completed returned the newly created input context. + public override Task CreateInputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageReaderSettings messageReaderSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "messageInfo"); + ExceptionUtils.CheckArgumentNotNull(messageReaderSettings, "messageReaderSettings"); + + return Task.FromResult( + new ODataRawInputContext(this, messageInfo, messageReaderSettings)); + } + + /// + /// Creates an instance of the output context for this format. + /// + /// The context information for the message. + /// Configuration settings of the OData writer. + /// Task which represents the pending create operation. + public override Task CreateOutputContextAsync( + ODataMessageInfo messageInfo, + ODataMessageWriterSettings messageWriterSettings) + { + ExceptionUtils.CheckArgumentNotNull(messageInfo, "message"); + ExceptionUtils.CheckArgumentNotNull(messageWriterSettings, "messageWriterSettings"); + + return Task.FromResult( + new ODataRawOutputContext(this, messageInfo, messageWriterSettings)); + } +#endif + + /// + /// Detects the payload kind(s) from the message stream. + /// + /// The content type of the message. + /// An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream. + private static IEnumerable DetectPayloadKindImplementation(ODataMediaType contentType) + { + Debug.Assert(contentType != null, "contentType != null"); + + if (HttpUtils.CompareMediaTypeNames(MimeConstants.MimeTextType, contentType.Type) && + HttpUtils.CompareMediaTypeNames(MimeConstants.MimeTextPlain, contentType.SubType)) + { + return new ODataPayloadKind[] { ODataPayloadKind.Value }; + } + + return new ODataPayloadKind[] { ODataPayloadKind.BinaryValue }; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueUtils.cs new file mode 100644 index 0000000..aeb07a3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRawValueUtils.cs @@ -0,0 +1,251 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Xml; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Utility methods around writing of raw values. + /// + internal static class ODataRawValueUtils + { + /// The characters that are considered to be whitespace by XmlConvert. + private static readonly char[] XmlWhitespaceChars = new char[] { ' ', '\t', '\n', '\r' }; + + /// Converts the specified value to a serializable string in ATOM format. + /// Non-null value to convert. + /// The specified value converted to an ATOM string. + /// boolean value indicating conversion successful conversion + internal static bool TryConvertPrimitiveToString(object value, out string result) + { + Debug.Assert(value != null, "value != null"); + + if (value is bool) + { + result = ODataRawValueConverter.ToString((bool)value); + return true; + } + + if (value is byte) + { + result = ODataRawValueConverter.ToString((byte)value); + return true; + } + + if (value is decimal) + { + result = ODataRawValueConverter.ToString((decimal)value); + return true; + } + + if (value is double) + { + result = ODataRawValueConverter.ToString((double)value); + return true; + } + + if (value is Int16) + { + result = ODataRawValueConverter.ToString((Int16)value); + return true; + } + + if (value is Int32) + { + result = ODataRawValueConverter.ToString((Int32)value); + return true; + } + + if (value is Int64) + { + result = ODataRawValueConverter.ToString((Int64)value); + return true; + } + + if (value is SByte) + { + result = ODataRawValueConverter.ToString((SByte)value); + return true; + } + + var str = value as string; + if (str != null) + { + result = str; + return true; + } + + if (value is Single) + { + result = ODataRawValueConverter.ToString((Single)value); + return true; + } + + byte[] bytes = value as byte[]; + if (bytes != null) + { + result = ODataRawValueConverter.ToString(bytes); + return true; + } + + if (value is DateTimeOffset) + { + result = ODataRawValueConverter.ToString((DateTimeOffset)value); + return true; + } + + if (value is Guid) + { + result = ODataRawValueConverter.ToString((Guid)value); + return true; + } + + if (value is TimeSpan) + { + // Edm.Duration + result = ODataRawValueConverter.ToString((TimeSpan)value); + return true; + } + + if (value is Date) + { + // Edm.Date + result = ODataRawValueConverter.ToString((Date)value); + return true; + } + + if (value is TimeOfDay) + { + // Edm.TimeOfDay + result = ODataRawValueConverter.ToString((TimeOfDay)value); + return true; + } + + result = null; + return false; + } + + /// + /// Converts a string to a primitive value. + /// + /// The string text to convert. + /// Type to convert the string to. + /// The value converted to the target type. + /// This method does not convert null value. + internal static object ConvertStringToPrimitive(string text, IEdmPrimitiveTypeReference targetTypeReference) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(targetTypeReference != null, "targetTypeReference != null"); + + try + { + EdmPrimitiveTypeKind primitiveKind = targetTypeReference.PrimitiveKind(); + + switch (primitiveKind) + { + case EdmPrimitiveTypeKind.Binary: + return Convert.FromBase64String(text); + case EdmPrimitiveTypeKind.Boolean: + return ConvertXmlBooleanValue(text); + case EdmPrimitiveTypeKind.Byte: + return XmlConvert.ToByte(text); + case EdmPrimitiveTypeKind.DateTimeOffset: + return PlatformHelper.ConvertStringToDateTimeOffset(text); + case EdmPrimitiveTypeKind.Decimal: + return XmlConvert.ToDecimal(text); + case EdmPrimitiveTypeKind.Double: + return XmlConvert.ToDouble(text); + case EdmPrimitiveTypeKind.Guid: + return new Guid(text); + case EdmPrimitiveTypeKind.Int16: + return XmlConvert.ToInt16(text); + case EdmPrimitiveTypeKind.Int32: + return XmlConvert.ToInt32(text); + case EdmPrimitiveTypeKind.Int64: + return XmlConvert.ToInt64(text); + case EdmPrimitiveTypeKind.SByte: + return XmlConvert.ToSByte(text); + case EdmPrimitiveTypeKind.Single: + return XmlConvert.ToSingle(text); + case EdmPrimitiveTypeKind.String: + case EdmPrimitiveTypeKind.PrimitiveType: + return text; + case EdmPrimitiveTypeKind.Duration: + return EdmValueParser.ParseDuration(text); + case EdmPrimitiveTypeKind.Date: + return PlatformHelper.ConvertStringToDate(text); + case EdmPrimitiveTypeKind.TimeOfDay: + return PlatformHelper.ConvertStringToTimeOfDay(text); + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.None: + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + default: + // Note that Astoria supports XElement and Binary as well, but they are serialized as string or byte[] + // and the metadata will actually talk about string and byte[] as well. Astoria will perform the conversion if necessary. + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataRawValueUtils_ConvertStringToPrimitive)); + } + } + catch (Exception e) + { + if (!ExceptionUtils.IsCatchableExceptionType(e)) + { + throw; + } + + throw ReaderValidationUtils.GetPrimitiveTypeConversionException(targetTypeReference, e, text); + } + } + + /// + /// Reimplementation of XmlConvert.ToBoolean that accepts 'True' and 'False' in addition + /// to 'true' and 'false'. + /// + /// The string value read from the Xml reader. + /// The converted boolean value. + private static bool ConvertXmlBooleanValue(string text) + { + text = text.Trim(XmlWhitespaceChars); + + switch (text) + { + case "true": + case "True": + case "1": + return true; + + case "false": + case "False": + case "0": + return false; + + default: + // We know that this will throw; call XmlConvert for the appropriate error message. + return XmlConvert.ToBoolean(text); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReadStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReadStream.cs new file mode 100644 index 0000000..97f3a57 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReadStream.cs @@ -0,0 +1,227 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// A stream handed to clients from ODataBatchOperationMessage.GetStream or ODataBatchOperationMessage.GetStreamAsync. + /// This stream communicates status changes to the owning batch reader (via IODataBatchOperationListener) + /// to prevent clients to use the batch reader while a content stream is still in use. + /// + internal abstract class ODataReadStream : ODataStream + { + /// + /// The batch stream underlying this operation stream. + /// + protected ODataBatchReaderStream batchReaderStream; + + /// + /// Constructor. + /// + /// The underlying stream to read from. + /// Listener interface to be notified of operation changes. + private ODataReadStream(ODataBatchReaderStream batchReaderStream, IODataStreamListener listener) + : base(listener) + { + Debug.Assert(batchReaderStream != null, "batchReaderStream != null"); + this.batchReaderStream = batchReaderStream; + } + + /// + /// Determines if the stream can read - this one can + /// + public override bool CanRead + { + get { return true; } + } + + /// + /// Determines if the stream can seek - this one can't + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Determines if the stream can write - this one can't + /// + public override bool CanWrite + { + get { return false; } + } + + /// + /// Returns the length of the stream. Not supported by this stream. + /// + public override long Length + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets or sets the position in the stream. Not supported by this stream. + /// + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + /// + /// Flush the stream; not supported for a read stream. + /// + public override void Flush() + { + throw new NotSupportedException(); + } + + /// + /// Sets the length of the stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// Writes to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + /// Create a batch operation read stream over the specified batch stream with a given content length. + /// + /// The batch stream underlying the operation stream to create. + /// The batch operation listener. + /// The content length of the operation stream. + /// A to read the content of a batch operation from. + internal static ODataReadStream Create(ODataBatchReaderStream batchReaderStream, IODataStreamListener listener, int length) + { + return new ODataBatchOperationReadStreamWithLength(batchReaderStream, listener, length); + } + + /// + /// Create a batch operation read stream over the specified batch stream using the batch delimiter to detect the end of the stream. + /// + /// The batch stream underlying the operation stream to create. + /// The batch operation listener. + /// A to read the content of a batch operation from. + internal static ODataReadStream Create(ODataBatchReaderStream batchReaderStream, IODataStreamListener listener) + { + return new ODataBatchOperationReadStreamWithDelimiter(batchReaderStream, listener); + } + + /// + /// A batch operation stream with the content length specified. + /// + private sealed class ODataBatchOperationReadStreamWithLength : ODataReadStream + { + /// The length of the operation content. + private int length; + + /// + /// Constructor. + /// + /// The underlying batch stream to write the message to. + /// Listener interface to be notified of operation changes. + /// The total length of the stream. + internal ODataBatchOperationReadStreamWithLength(ODataBatchReaderStream batchReaderStream, IODataStreamListener listener, int length) + : base(batchReaderStream, listener) + { + ExceptionUtils.CheckIntegerNotNegative(length, "length"); + this.length = length; + } + + /// + /// Reads data from the stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + ExceptionUtils.CheckArgumentNotNull(buffer, "buffer"); + ExceptionUtils.CheckIntegerNotNegative(offset, "offset"); + ExceptionUtils.CheckIntegerNotNegative(count, "count"); + this.ValidateNotDisposed(); + + if (this.length == 0) + { + // Nothing left to read. + return 0; + } + + int bytesRead = this.batchReaderStream.ReadWithLength(buffer, offset, Math.Min(count, this.length)); + this.length -= bytesRead; + Debug.Assert(this.length >= 0, "Read beyond expected length."); + return bytesRead; + } + } + + /// + /// A batch operation read stream with no content length so we have to check for the boundary. + /// + private sealed class ODataBatchOperationReadStreamWithDelimiter : ODataReadStream + { + /// true if the stream has been exhausted and no further reads can happen; otherwise false. + private bool exhausted; + + /// + /// Constructor. + /// + /// The underlying batch stream to write the message to. + /// Listener interface to be notified of operation changes. + internal ODataBatchOperationReadStreamWithDelimiter(ODataBatchReaderStream batchReaderStream, IODataStreamListener listener) + : base(batchReaderStream, listener) + { + } + + /// + /// Reads data from the stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + ExceptionUtils.CheckArgumentNotNull(buffer, "buffer"); + ExceptionUtils.CheckIntegerNotNegative(offset, "offset"); + ExceptionUtils.CheckIntegerNotNegative(count, "count"); + + this.ValidateNotDisposed(); + + if (this.exhausted) + { + return 0; + } + + int bytesRead = this.batchReaderStream.ReadWithDelimiter(buffer, offset, count); + if (bytesRead < count) + { + this.exhausted = true; + } + + return bytesRead; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReader.cs new file mode 100644 index 0000000..48c1a72 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReader.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics.CodeAnalysis; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Base class for OData readers. + /// + public abstract class ODataReader + { + /// Gets the current state of the reader. + /// The current state of the reader. + public abstract ODataReaderState State { get; } + + /// Gets the most recent that has been read. + /// The most recent that has been read. + public abstract ODataItem Item { get; } + + /// Reads the next from the message payload. + /// true if more items were read; otherwise false. + public abstract bool Read(); + + /// Creates a stream for reading an inline stream property. + /// A stream for reading the stream property. + public virtual Stream CreateReadStream() + { + throw new NotImplementedException(); + } + + /// Creates a TextReader for reading an inline string property. + /// A TextReader for reading the text property. + public virtual TextReader CreateTextReader() + { + throw new NotImplementedException(); + } + +#if PORTABLELIB + /// Asynchronously reads the next from the message payload. + /// A task that when completed indicates whether more items were read. + public abstract Task ReadAsync(); + + /// Asynchronously creates a stream for reading an inline stream property. + /// A stream for reading the stream property. + public virtual Task CreateReadStreamAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateReadStream()); + } + + /// Asynchronously creates a stream for reading an inline stream property. + /// A stream for reading the stream property. + public virtual Task CreateTextReaderAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateTextReader()); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCore.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCore.cs new file mode 100644 index 0000000..987870c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCore.cs @@ -0,0 +1,1179 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + #endregion Namespaces + + /// + /// Base class for OData readers that verifies a proper sequence of read calls on the reader. + /// + internal abstract class ODataReaderCore : ODataReader, IODataStreamListener + { + /// The input to read the payload from. + private readonly ODataInputContext inputContext; + + /// true if the reader is created for reading a resource set; false when it is created for reading a resource. + private readonly bool readingResourceSet; + + /// true if the reader is created for reading expanded navigation property in delta response; false otherwise. + private readonly bool readingDelta; + + /// Stack of reader scopes to keep track of the current context of the reader. + private readonly Stack scopes = new Stack(); + + /// If not null, the reader will notify the implementer of the interface of relevant state changes in the reader. + private readonly IODataReaderWriterListener listener; + + /// The number of entries which have been started but not yet ended. + private int currentResourceDepth; + + /// + /// Constructor. + /// + /// The input to read the payload from. + /// true if the reader is created for reading a resource set; false when it is created for reading a resource. + /// true if the reader is created for reading expanded navigation property in delta response; false otherwise. + /// If not null, the reader will notify the implementer of the interface of relevant state changes in the reader. + protected ODataReaderCore( + ODataInputContext inputContext, + bool readingResourceSet, + bool readingDelta, + IODataReaderWriterListener listener) + { + Debug.Assert(inputContext != null, "inputContext != null"); + + this.inputContext = inputContext; + this.readingResourceSet = readingResourceSet; + this.readingDelta = readingDelta; + this.listener = listener; + this.currentResourceDepth = 0; + this.Version = inputContext.MessageReaderSettings.Version; + } + + /// + /// Enum used to describe the current state of the stream. + /// + internal enum StreamingState + { + None = 0, + Streaming, + Completed + } + + /// + /// The current state of the reader. + /// + public override sealed ODataReaderState State + { + get + { + this.inputContext.VerifyNotDisposed(); + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek().State; + } + } + + /// + /// The most recent that has been read. + /// + public override sealed ODataItem Item + { + get + { + this.inputContext.VerifyNotDisposed(); + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek().Item; + } + } + + /// + /// OData Version being read. + /// + internal ODataVersion? Version { get; } + + /// + /// Returns the current item as . Must only be called if the item actually is a resource set. + /// + protected ODataResourceSet CurrentResourceSet + { + get + { + Debug.Assert(this.Item is ODataResourceSet, "this.Item is ODataResourceSet"); + return (ODataResourceSet)this.Item; + } + } + + /// + /// Returns the current item as . Must only be called if the item actually is a delta resource set. + /// + protected ODataDeltaResourceSet CurrentDeltaResourceSet + { + get + { + Debug.Assert(this.Item is ODataDeltaResourceSet, "this.Item is ODataDeltaResourceSet"); + return (ODataDeltaResourceSet)this.Item; + } + } + + /// + /// Returns the current item as ODataDeltaLink + /// + protected ODataDeltaLink CurrentDeltaLink + { + get + { + Debug.Assert(this.Item == null || this.Item is ODataDeltaLink, "this.Item is ODataDeltaLink"); + return (ODataDeltaLink)this.Item; + } + } + + /// + /// Returns the current item as ODataDeltaDeletedLink. + /// + protected ODataDeltaDeletedLink CurrentDeltaDeletedLink + { + get + { + Debug.Assert(this.Item == null || this.Item is ODataDeltaDeletedLink, "this.Item is ODataDeltaDeletedLink"); + return (ODataDeltaDeletedLink)this.Item; + } + } + + /// + /// Returns the current resource depth. + /// + protected int CurrentResourceDepth + { + get + { + return this.currentResourceDepth; + } + } + + /// + /// Returns the current item as . Must only be called if the item actually is a nested resource info. + /// + protected ODataNestedResourceInfo CurrentNestedResourceInfo + { + get + { + Debug.Assert(this.Item is ODataNestedResourceInfo, "this.Item is ODataNestedResourceInfo"); + return (ODataNestedResourceInfo)this.Item; + } + } + + /// + /// Returns the current item as . Must only be called if the item actually is an entity reference link. + /// + protected ODataEntityReferenceLink CurrentEntityReferenceLink + { + get + { + Debug.Assert(this.Item is ODataEntityReferenceLink, "this.Item is ODataEntityReferenceLink"); + return (ODataEntityReferenceLink)this.Item; + } + } + + /// + /// Returns the expected resource type for the current scope. + /// + protected IEdmType CurrentResourceType + { + get + { + if (CurrentResourceTypeReference != null) + { + return CurrentResourceTypeReference.Definition; + } + + return null; + } + } + + /// + /// Gets and Sets the expected resource type reference for the current scope. + /// + protected IEdmTypeReference CurrentResourceTypeReference + { + get + { + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + IEdmTypeReference resourceTypeReference = this.scopes.Peek().ResourceTypeReference; + Debug.Assert(resourceTypeReference == null || this.inputContext.Model.IsUserModel(), "We can only have structured type if we also have metadata."); + + return resourceTypeReference; + } + + set + { + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + this.scopes.Peek().ResourceTypeReference = value; + } + } + + /// + /// Returns the navigation source for the current scope. + /// + protected IEdmNavigationSource CurrentNavigationSource + { + get + { + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + IEdmNavigationSource navigationSource = this.scopes.Peek().NavigationSource; + Debug.Assert(navigationSource == null || this.inputContext.Model.IsUserModel(), "We can only have navigation source if we also have metadata."); + return navigationSource; + } + } + + /// + /// Returns the current scope. + /// + protected Scope CurrentScope + { + get + { + Debug.Assert(this.scopes != null && this.scopes.Count > 0, "A scope must always exist."); + return this.scopes.Peek(); + } + } + + protected Stack Scopes + { + get { return this.scopes; } + } + + /// + /// Returns the parent scope. + /// + protected Scope ParentScope + { + get + { + Debug.Assert(this.scopes != null && this.scopes.Count > 1, "We must have at least two scopes in the stack."); + return this.scopes.Skip(1).First(); + } + } + + /// + /// A flag indicating whether the reader is at the top level. + /// + protected bool IsTopLevel + { + get + { + Debug.Assert(this.scopes != null, "Scopes must exist."); + + // there is the root scope at the top (when the writer has not started or has completed) + // and then the top-level scope (the top-level resource/resource set item) as the second scope on the stack + return this.scopes.Count <= 2; + } + } + + /// + /// If the current scope is a content of an expanded link, this returns the parent nested resource info scope, otherwise null. + /// + protected Scope ExpandedLinkContentParentScope + { + get + { + Debug.Assert(this.scopes != null, "this.scopes != null"); + if (this.scopes.Count > 1) + { + Scope parentScope = this.scopes.Skip(1).First(); + if (parentScope.State == ODataReaderState.NestedResourceInfoStart) + { + return parentScope; + } + } + + return null; + } + } + + /// + /// True if we are reading a resource or resource set that is the direct content of an expanded link. Otherwise false. + /// + protected bool IsExpandedLinkContent + { + get + { + return this.ExpandedLinkContentParentScope != null; + } + } + + /// + /// Set to true if a resource set is being read. + /// + protected bool ReadingResourceSet + { + get + { + return this.readingResourceSet; + } + } + + /// + /// Set to true if a delta response is being read. + /// + protected bool ReadingDelta + { + get + { + return this.readingDelta; + } + } + + /// + /// Returns true if we are reading a nested payload, + /// e.g. an expanded resource or resource set within a delta payload, + /// or a resource or a resource set within a parameters payload. + /// + protected bool IsReadingNestedPayload + { + get + { + return this.listener != null; + } + } + + /// + /// Validator to validate consistency of entries in top-level resource sets. + /// + /// We only use this for top-level resource sets since we support collection validation for + /// resource sets only when metadata is available and in these cases we already validate the + /// types of the entries in nested resource sets. + protected ResourceSetWithoutExpectedTypeValidator CurrentResourceSetValidator + { + get + { + Debug.Assert(this.State == ODataReaderState.ResourceStart || this.State == ODataReaderState.DeletedResourceStart, "CurrentResourceSetValidator should only be called while reading a resource."); + return this.ParentScope == null ? null : this.ParentScope.ResourceTypeValidator; + } + } + + /// + /// Validator to validate the derived type constraint. + /// + protected DerivedTypeValidator CurrentDerivedTypeValidator + { + get + { + Debug.Assert(this.State == ODataReaderState.ResourceStart || this.State == ODataReaderState.DeletedResourceStart, "CurrentDerivedTypeValidator should only be called while reading a resource."); + return this.ParentScope == null ? null : this.ParentScope.DerivedTypeValidator; + } + } + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + public override sealed bool Read() + { + this.VerifyCanRead(true); + return this.InterceptException(this.ReadSynchronously); + } + +#if PORTABLELIB + /// + /// Asynchronously reads the next from the message payload. + /// + /// A task that when completed indicates whether more items were read. + public override sealed Task ReadAsync() + { + this.VerifyCanRead(false); + return this.ReadAsynchronously().FollowOnFaultWith(t => this.EnterScope(new Scope(ODataReaderState.Exception, null, null))); + } +#endif + + /// + /// Creates a stream for reading an inline stream property. + /// + /// A stream for reading the stream property. + public override sealed Stream CreateReadStream() + { + if (this.State != ODataReaderState.Stream) + { + throw new ODataException(Strings.ODataReaderCore_CreateReadStreamCalledInInvalidState); + } + + StreamScope scope = this.CurrentScope as StreamScope; + Debug.Assert(scope != null, "ODataReaderState.Stream when Scope is not a StreamScope"); + if (scope.StreamingState != StreamingState.None) + { + throw new ODataException(Strings.ODataReaderCore_CreateReadStreamCalledInInvalidState); + } + + scope.StreamingState = StreamingState.Streaming; + return new ODataNotificationStream(this.InterceptException(this.CreateReadStreamImplementation), this); + } + + /// + /// Creates a TextWriter for reading an inline stream property. + /// + /// A TextWriter for reading the stream property. + public override sealed TextReader CreateTextReader() + { + if (this.State == ODataReaderState.Stream) + { + StreamScope scope = this.CurrentScope as StreamScope; + Debug.Assert(scope != null, "ODataReaderState.Stream when Scope is not a StreamScope"); + if (scope.StreamingState != StreamingState.None) + { + throw new ODataException(Strings.ODataReaderCore_CreateReadStreamCalledInInvalidState); + } + + scope.StreamingState = StreamingState.Streaming; + return new ODataNotificationReader(this.InterceptException(this.CreateTextReaderImplementation), this); + } + else + { + throw new ODataException(Strings.ODataReaderCore_CreateTextReaderCalledInInvalidState); + } + } + + /// + /// This method is called when a stream is requested. It is a no-op. + /// + void IODataStreamListener.StreamRequested() + { + } + +#if PORTABLELIB + /// + /// This method is called when an async stream is requested. It is a no-op. + /// + /// A task for method called when a stream is requested. + Task IODataStreamListener.StreamRequestedAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(() => ((IODataStreamListener)this).StreamRequested()); + } +#endif + + /// + /// This method is called when a stream is disposed. + /// + void IODataStreamListener.StreamDisposed() + { + Debug.Assert(this.State == ODataReaderState.Stream, "Stream was disposed when not in ReaderState.Stream state."); + StreamScope scope = this.CurrentScope as StreamScope; + Debug.Assert(scope != null, "Stream disposed when not in stream scope"); + Debug.Assert(scope.StreamingState == StreamingState.Streaming, "StreamDisposed called when reader was not streaming"); + scope.StreamingState = StreamingState.Completed; + } + + /// + /// Seek scope in the stack which is type of . + /// + /// The type of scope to seek. + /// The max depth to seek. + /// The scope with type of + internal Scope SeekScope(int maxDepth) where T : Scope + { + int count = 1; + + foreach (Scope scope in this.scopes) + { + if (count > maxDepth) + { + return null; + } + + if (scope is T) + { + return scope; + } + + count++; + } + + return null; + } + + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'ResourceSetStart'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtResourceSetStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'ResourceSetEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtResourceSetEndImplementation(); + + /// + /// Implementation of the reader logic when in state 'EntryStart'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtResourceStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'EntryEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtResourceEndImplementation(); + + /// + /// Implementation of the reader logic when in state 'Primitive'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtPrimitiveImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'PropertyInfo'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtNestedPropertyInfoImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'Stream'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtStreamImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Creates a Stream for reading a stream property when in state 'Stream'. + /// + /// A stream for reading the stream property. + protected virtual Stream CreateReadStreamImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Creates a TextReader for reading a string property when in state 'Text'. + /// + /// A TextReader for reading the string property. + protected virtual TextReader CreateTextReaderImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoStart'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtNestedResourceInfoStartImplementation(); + + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtNestedResourceInfoEndImplementation(); + + /// + /// Implementation of the reader logic when in state 'EntityReferenceLink'. + /// + /// true if more items can be read from the reader; otherwise false. + protected abstract bool ReadAtEntityReferenceLink(); + + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetStart'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtDeltaResourceSetStartImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtDeltaResourceSetEndImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'DeletedResourceStart'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtDeletedResourceStartImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'DeletedResourceEnd'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtDeletedResourceEndImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'DeltaLink'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtDeltaLinkImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Implementation of the reader logic when in state 'DeltaDeletedLink'. + /// + /// true if more items can be read from the reader; otherwise false. + protected virtual bool ReadAtDeltaDeletedLinkImplementation() + { + throw new NotImplementedException(); + } + + /// + /// Pushes the on the stack of scopes. + /// + /// The scope to enter. + protected void EnterScope(Scope scope) + { + Debug.Assert(scope != null, "scope != null"); + + if ((scope.State == ODataReaderState.ResourceSetStart || scope.State == ODataReaderState.DeltaResourceSetStart) + && this.inputContext.Model.IsUserModel()) + { + scope.ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(scope.ResourceType); + } + + if (scope.State == ODataReaderState.ResourceSetStart || scope.State == ODataReaderState.DeltaResourceSetStart) + { + scope.DerivedTypeValidator = this.CurrentScope.DerivedTypeValidator; + } + + // TODO: implement some basic validation that the transitions are ok + this.scopes.Push(scope); + if (this.listener != null) + { + if (scope.State == ODataReaderState.Exception) + { + this.listener.OnException(); + } + else if (scope.State == ODataReaderState.Completed) + { + this.listener.OnCompleted(); + } + } + } + + /// + /// Replaces the current scope with the specified . + /// + /// The scope to replace the current scope with. + protected void ReplaceScope(Scope scope) + { + Debug.Assert(this.scopes.Count > 0, "Stack must always be non-empty."); + Debug.Assert(scope != null, "scope != null"); + Debug.Assert(scope.State != ODataReaderState.ResourceEnd, "Call EndEntry instead."); + + // TODO: implement some basic validation that the transitions are ok + this.scopes.Pop(); + this.EnterScope(scope); + } + + /// + /// Removes the current scope from the stack of all scopes. + /// + /// The expected state of the current scope (to be popped). + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "state", Justification = "Used in debug builds in assertions.")] + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "scope", Justification = "Used in debug builds in assertions.")] + protected void PopScope(ODataReaderState state) + { + Debug.Assert(this.scopes.Count > 1, "Stack must have more than 1 items in order to pop an item."); + + Scope scope = this.scopes.Pop(); + Debug.Assert(scope.State == state, "scope.State == state"); + } + + /// + /// Called to transition into the EntryEnd state. + /// + /// The scope for the EntryEnd state. + protected void EndEntry(Scope scope) + { + Debug.Assert(this.scopes.Count > 0, "Stack must always be non-empty."); + Debug.Assert(scope != null, "scope != null"); + Debug.Assert(scope.State == ODataReaderState.ResourceEnd | scope.State == ODataReaderState.DeletedResourceEnd, "Called EndEntry when not in ResourceEnd or DeletedResourceEnd state"); + + this.scopes.Pop(); + this.EnterScope(scope); + } + + /// + /// If an entity type name is found in the payload this method is called to apply it to the current scope. + /// This method should be called even if the type name was not found in which case a null should be passed in. + /// The method validates that some type will be available as the current entity type after it returns (if we are parsing using metadata). + /// + /// The entity type name found in the payload or null if no type was specified in the payload. + protected void ApplyResourceTypeNameFromPayload(string resourceTypeNameFromPayload) + { + Debug.Assert( + this.scopes.Count > 0 && this.scopes.Peek().Item is ODataResourceBase, + "Resource type can be applied only when in resource scope."); + + ODataTypeAnnotation typeAnnotation; + EdmTypeKind targetTypeKind; + IEdmStructuredTypeReference targetResourceTypeReference = + (IEdmStructuredTypeReference)this.inputContext.MessageReaderSettings.Validator.ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind.None, + /*expectStructuredType*/ true, + /*defaultPrimitivePayloadType*/ null, + this.CurrentResourceTypeReference, + resourceTypeNameFromPayload, + this.inputContext.Model, + () => EdmTypeKind.Entity, + out targetTypeKind, + out typeAnnotation); + + IEdmStructuredType targetResourceType = null; + ODataResourceBase resource = this.Item as ODataResourceBase; + if (targetResourceTypeReference != null) + { + targetResourceType = targetResourceTypeReference.StructuredDefinition(); + resource.TypeName = targetResourceType.FullTypeName(); + + if (typeAnnotation != null) + { + resource.TypeAnnotation = typeAnnotation; + } + } + else if (resourceTypeNameFromPayload != null) + { + resource.TypeName = resourceTypeNameFromPayload; + } + + // Set the current resource type since the type might be derived from the expected one. + this.CurrentResourceTypeReference = targetResourceTypeReference; + } + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + protected bool ReadSynchronously() + { + return this.ReadImplementation(); + } + +#if PORTABLELIB + /// + /// Asynchronously reads the next from the message payload. + /// + /// A task that when completed indicates whether more items were read. + protected virtual Task ReadAsynchronously() + { + // We are reading from the fully buffered read stream here; thus it is ok + // to use synchronous reads and then return a completed task + // NOTE: once we switch to fully async reading this will have to change + return TaskUtils.GetTaskForSynchronousOperation(this.ReadImplementation); + } +#endif + + /// + /// Increments the nested resource count by one and fails if the new value exceeds the maxiumum nested resource depth limit. + /// + protected void IncreaseResourceDepth() + { + this.currentResourceDepth++; + + if (this.currentResourceDepth > this.inputContext.MessageReaderSettings.MessageQuotas.MaxNestingDepth) + { + throw new ODataException(Strings.ValidationUtils_MaxDepthOfNestedEntriesExceeded(this.inputContext.MessageReaderSettings.MessageQuotas.MaxNestingDepth)); + } + } + + /// + /// Decrements the nested resource count by one. + /// + protected void DecreaseResourceDepth() + { + Debug.Assert(this.currentResourceDepth > 0, "Resource depth should never become negative."); + + this.currentResourceDepth--; + } + + /// + /// Reads the next from the message payload. + /// + /// true if more items were read; otherwise false. + private bool ReadImplementation() + { + bool result; + switch (this.State) + { + case ODataReaderState.Start: + result = this.ReadAtStartImplementation(); + break; + + case ODataReaderState.ResourceSetStart: + result = this.ReadAtResourceSetStartImplementation(); + break; + + case ODataReaderState.ResourceSetEnd: + result = this.ReadAtResourceSetEndImplementation(); + break; + + case ODataReaderState.ResourceStart: + this.IncreaseResourceDepth(); + result = this.ReadAtResourceStartImplementation(); + break; + + case ODataReaderState.ResourceEnd: + this.DecreaseResourceDepth(); + result = this.ReadAtResourceEndImplementation(); + break; + + case ODataReaderState.Primitive: + result = this.ReadAtPrimitiveImplementation(); + break; + + case ODataReaderState.Stream: + result = this.ReadAtStreamImplementation(); + break; + + case ODataReaderState.NestedProperty: + result = this.ReadAtNestedPropertyInfoImplementation(); + break; + + case ODataReaderState.NestedResourceInfoStart: + result = this.ReadAtNestedResourceInfoStartImplementation(); + break; + + case ODataReaderState.NestedResourceInfoEnd: + result = this.ReadAtNestedResourceInfoEndImplementation(); + break; + + case ODataReaderState.EntityReferenceLink: + result = this.ReadAtEntityReferenceLink(); + break; + + case ODataReaderState.DeltaResourceSetStart: + result = this.ReadAtDeltaResourceSetStartImplementation(); + break; + + case ODataReaderState.DeltaResourceSetEnd: + result = this.ReadAtDeltaResourceSetEndImplementation(); + break; + + case ODataReaderState.DeletedResourceStart: + result = this.ReadAtDeletedResourceStartImplementation(); + break; + + case ODataReaderState.DeletedResourceEnd: + result = this.ReadAtDeletedResourceEndImplementation(); + break; + + case ODataReaderState.DeltaLink: + result = this.ReadAtDeltaLinkImplementation(); + break; + + case ODataReaderState.DeltaDeletedLink: + result = this.ReadAtDeltaDeletedLinkImplementation(); + break; + + case ODataReaderState.Exception: // fall through + case ODataReaderState.Completed: + throw new ODataException(Strings.ODataReaderCore_NoReadCallsAllowed(this.State)); + + default: + Debug.Assert(false, "Unsupported reader state " + this.State + " detected."); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataReaderCore_ReadImplementation)); + } + + return result; + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the reader into + /// state ExceptionThrown and then rethrow the exception. + /// + /// The type returned from the to execute. + /// The action to execute. + /// The result of executing the . + private T InterceptException(Func action) + { + try + { + return action(); + } + catch (Exception e) + { + if (ExceptionUtils.IsCatchableExceptionType(e)) + { + this.EnterScope(new Scope(ODataReaderState.Exception, null, null)); + } + + throw; + } + } + + /// + /// Verifies that calling Read is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanRead(bool synchronousCall) + { + this.inputContext.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + if (this.State == ODataReaderState.Exception || this.State == ODataReaderState.Completed) + { + throw new ODataException(Strings.ODataReaderCore_ReadOrReadAsyncCalledInInvalidState(this.State)); + } + + if (this.State == ODataReaderState.Stream) + { + StreamScope scope = this.CurrentScope as StreamScope; + Debug.Assert(scope != null, "In stream state without a stream scope"); + if (scope.StreamingState != StreamingState.Completed) + { + throw new ODataException(Strings.ODataReaderCore_ReadCalledWithOpenStream); + } + } + } + + /// + /// Verifies that a call is allowed to the reader. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.inputContext.Synchronous) + { + throw new ODataException(Strings.ODataReaderCore_SyncCallOnAsyncReader); + } + } + else + { +#if PORTABLELIB + if (this.inputContext.Synchronous) + { + throw new ODataException(Strings.ODataReaderCore_AsyncCallOnSyncReader); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// A reader scope; keeping track of the current reader state and an item associated with this state. + /// + protected internal class Scope + { + /// The reader state of this scope. + private readonly ODataReaderState state; + + /// The item attached to this scope. + private readonly ODataItem item; + + /// The odataUri parsed based on the context uri attached to this scope. + private readonly ODataUri odataUri; + + /// + /// The to use for entries in this resourceSet. + /// + private ResourceSetWithoutExpectedTypeValidator resourceTypeValidator; + + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of this scope. + /// The item attached to this scope. + /// The odataUri parsed based on the context uri for current scope + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")] + internal Scope(ODataReaderState state, ODataItem item, ODataUri odataUri) + { + Debug.Assert( + state == ODataReaderState.Exception && item == null || + state == ODataReaderState.ResourceStart && (item == null || item is ODataResource) || + state == ODataReaderState.ResourceEnd && (item is ODataResource || item == null) || + state == ODataReaderState.Primitive && (item == null || item is ODataPrimitiveValue || item is ODataNullValue) || + state == ODataReaderState.Stream && (item == null || item is ODataStreamItem) || + state == ODataReaderState.NestedProperty && (item == null || item is ODataPropertyInfo) || + state == ODataReaderState.ResourceSetStart && item is ODataResourceSet || + state == ODataReaderState.ResourceSetEnd && item is ODataResourceSet || + state == ODataReaderState.NestedResourceInfoStart && item is ODataNestedResourceInfo || + state == ODataReaderState.NestedResourceInfoEnd && item is ODataNestedResourceInfo || + state == ODataReaderState.EntityReferenceLink && item is ODataEntityReferenceLink || + state == ODataReaderState.DeletedResourceStart && (item == null || item is ODataDeletedResource) || + state == ODataReaderState.DeletedResourceEnd && (item is ODataDeletedResource || item == null) || + state == ODataReaderState.DeltaResourceSetStart && item is ODataDeltaResourceSet || + state == ODataReaderState.DeltaResourceSetEnd && item is ODataDeltaResourceSet || + state == ODataReaderState.DeltaLink && (item == null || item is ODataDeltaLink) || + state == ODataReaderState.DeltaDeletedLink && (item == null || item is ODataDeltaDeletedLink) || + state == ODataReaderState.Start && item == null || + state == ODataReaderState.Completed && item == null, + "Reader state and associated item do not match."); + + this.state = state; + this.item = item; + this.odataUri = odataUri; + } + + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of this scope. + /// The item attached to this scope. + /// The navigation source we are going to read entities for. + /// The expected resource type reference for the scope. + /// The odataUri parsed based on the context uri for current scope + /// The has the following meanings for given state: + /// Start - it's the expected base type reference of the top-level resource or resources in the top-level resource set. + /// ResourceSetStart - it's the expected base type reference of the resources in the resource set. + /// note that it might be a more derived type than the base type of the entity set for the resource set. + /// EntryStart - it's the expected base type reference of the resource. If the resource has no type name specified + /// this type will be assumed. Otherwise the specified type name must be + /// the expected type or a more derived type. + /// NestedResourceInfoStart - it's the expected base type reference the entries in the expanded link (either the single resource + /// or entries in the expanded resource set). + /// EntityReferenceLink - it's null, no need for types on entity reference links. + /// In all cases the specified type must be an structured type. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")] + internal Scope(ODataReaderState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmTypeReference expectedResourceTypeReference, ODataUri odataUri) + : this(state, item, odataUri) + { + this.NavigationSource = navigationSource; + this.ResourceTypeReference = expectedResourceTypeReference; + } + + /// + /// The reader state of this scope. + /// + internal ODataReaderState State + { + get + { + return this.state; + } + } + + /// + /// The item attached to this scope. + /// + internal ODataItem Item + { + get + { + return this.item; + } + } + + /// + /// The odataUri parsed based on the context url to this scope. + /// + internal ODataUri ODataUri + { + get + { + return this.odataUri; + } + } + + /// + /// The navigation source we are reading entries from (possibly null). + /// + internal IEdmNavigationSource NavigationSource { get; set; } + + /// + /// The resource type for this scope. Can be either the expected one if the real one + /// was not found yet, or the one specified in the payload itself (the real one). + /// + internal IEdmType ResourceType + { + get + { + if (this.ResourceTypeReference != null) + { + return ResourceTypeReference.Definition; + } + + return null; + } + } + + /// + /// The resource type reference for this scope. Can be either the expected one if the real one + /// was not found yet, or the one specified in the payload itself (the real one). + /// + internal IEdmTypeReference ResourceTypeReference { get; set; } + + /// + /// Validator for resource type. + /// + internal ResourceSetWithoutExpectedTypeValidator ResourceTypeValidator + { + get + { + return this.resourceTypeValidator; + } + + set + { + this.resourceTypeValidator = value; + } + } + + /// + /// Gets or sets the derived type constraint validator. + /// + internal DerivedTypeValidator DerivedTypeValidator { get; set; } + } + + protected internal class StreamScope : Scope + { + /// + /// Constructor creating a new reader scope. + /// + /// The reader state of this scope. + /// The item attached to this scope. + /// The navigation source we are going to read entities for. + /// The expected resource type for the scope. + /// The odataUri parsed based on the context uri for current scope + internal StreamScope(ODataReaderState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmTypeReference expectedResourceType, ODataUri odataUri) + : base(state, item, navigationSource, expectedResourceType, odataUri) + { + this.StreamingState = StreamingState.None; + } + + /// + /// Current state of the stream. + /// + internal StreamingState StreamingState { get; set; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCoreAsync.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCoreAsync.cs new file mode 100644 index 0000000..fbf8b9f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderCoreAsync.cs @@ -0,0 +1,272 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Base class for OData readers that verifies a proper sequence of read calls on the reader with true async operations. + /// + internal abstract class ODataReaderCoreAsync : ODataReaderCore + { + /// + /// Constructor. + /// + /// The input to read the payload from. + /// true if the reader is created for reading a resource set; false when it is created for reading a resource. + /// true if the reader is created for reading expanded navigation property in delta response; false otherwise. + /// If not null, the reader will notify the implementer of the interface of relevant state changes in the reader. + protected ODataReaderCoreAsync( + ODataInputContext inputContext, + bool readingResourceSet, + bool readingDelta, + IODataReaderWriterListener listener) + : base(inputContext, readingResourceSet, readingDelta, listener) + { + } + +#if PORTABLELIB + /// + /// Implementation of the reader logic when in state 'Start'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtStartImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'ResourceSetStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtResourceSetStartImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'ResourceSetEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtResourceSetEndImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'EntryStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtResourceStartImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'EntryEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtResourceEndImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'DeletedResourceEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtDeletedResourceEndImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'Primitive'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtPrimitiveImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtPrimitiveImplementation); + } + + /// + /// Implementation of the reader logic when in state 'PropertyInfo'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtNestedPropertyInfoImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtNestedPropertyInfoImplementation); + } + + /// + /// Implementation of the reader logic when in state 'Stream'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtStreamImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtStreamImplementation); + } + + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtNestedResourceInfoStartImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'NestedResourceInfoEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtNestedResourceInfoEndImplementationAsync(); + + /// + /// Implementation of the reader logic when in state 'EntityReferenceLink'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected abstract Task ReadAtEntityReferenceLinkAsync(); + + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtDeltaResourceSetStartImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeltaResourceSetStartImplementation); + } + + /// + /// Implementation of the reader logic when in state 'DeltaResourceSetEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtDeltaResourceSetEndImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeltaResourceSetEndImplementation); + } + + /// + /// Implementation of the reader logic when in state 'DeletedResourceStart'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtDeletedResourceStartImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeletedResourceStartImplementation); + } + + /// + /// Implementation of the reader logic when in state 'DeletedResourceEnd'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadDeletedResourceEndImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeletedResourceEndImplementation); + } + + /// + /// Implementation of the reader logic when in state 'DeltaLink'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtDeltaLinkImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeltaLinkImplementation); + } + + /// + /// Implementation of the reader logic when in state 'DeltaDeletedLink'. + /// + /// A task which returns true if more items can be read from the reader; otherwise false. + protected virtual Task ReadAtDeltaDeletedLinkImplementationAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(this.ReadAtDeltaDeletedLinkImplementation); + } + + /// + /// Asynchronously reads the next from the message payload. + /// + /// A task that when completed indicates whether more items were read. + /// The base class already implements this but only for fully synchronous readers, the implementation here + /// allows fully asynchronous readers. + protected override Task ReadAsynchronously() + { + Task result; + switch (this.State) + { + case ODataReaderState.Start: + result = this.ReadAtStartImplementationAsync(); + break; + + case ODataReaderState.ResourceSetStart: + result = this.ReadAtResourceSetStartImplementationAsync(); + break; + + case ODataReaderState.ResourceSetEnd: + result = this.ReadAtResourceSetEndImplementationAsync(); + break; + + case ODataReaderState.ResourceStart: + result = TaskUtils.GetTaskForSynchronousOperation(() => this.IncreaseResourceDepth()) + .FollowOnSuccessWithTask(t => this.ReadAtResourceStartImplementationAsync()); + break; + + case ODataReaderState.ResourceEnd: + result = TaskUtils.GetTaskForSynchronousOperation(() => this.DecreaseResourceDepth()) + .FollowOnSuccessWithTask(t => this.ReadAtResourceEndImplementationAsync()); + break; + + case ODataReaderState.Primitive: + result = this.ReadAtPrimitiveImplementationAsync(); + break; + + case ODataReaderState.Stream: + result = this.ReadAtStreamImplementationAsync(); + break; + + case ODataReaderState.NestedProperty: + result = this.ReadAtNestedPropertyInfoImplementationAsync(); + break; + + case ODataReaderState.NestedResourceInfoStart: + result = this.ReadAtNestedResourceInfoStartImplementationAsync(); + break; + + case ODataReaderState.NestedResourceInfoEnd: + result = this.ReadAtNestedResourceInfoEndImplementationAsync(); + break; + + case ODataReaderState.EntityReferenceLink: + result = this.ReadAtEntityReferenceLinkAsync(); + break; + + case ODataReaderState.DeltaResourceSetStart: + result = this.ReadAtDeltaResourceSetStartImplementationAsync(); + break; + + case ODataReaderState.DeltaResourceSetEnd: + result = this.ReadAtDeltaResourceSetEndImplementationAsync(); + break; + + case ODataReaderState.DeletedResourceStart: + result = TaskUtils.GetTaskForSynchronousOperation(() => this.IncreaseResourceDepth()) + .FollowOnSuccessWithTask(t => this.ReadAtDeletedResourceStartImplementationAsync()); + break; + + case ODataReaderState.DeletedResourceEnd: + result = TaskUtils.GetTaskForSynchronousOperation(() => this.DecreaseResourceDepth()) + .FollowOnSuccessWithTask(t => this.ReadAtDeletedResourceEndImplementationAsync()); + break; + + case ODataReaderState.DeltaLink: + result = this.ReadAtDeltaLinkImplementationAsync(); + break; + + case ODataReaderState.DeltaDeletedLink: + result = this.ReadAtDeltaDeletedLinkImplementationAsync(); + break; + + case ODataReaderState.Exception: // fall through + case ODataReaderState.Completed: + result = TaskUtils.GetFaultedTask(new ODataException(Strings.ODataReaderCore_NoReadCallsAllowed(this.State))); + break; + + default: + Debug.Assert(false, "Unsupported reader state " + this.State + " detected."); + result = TaskUtils.GetFaultedTask(new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataReaderCoreAsync_ReadAsynchronously))); + break; + } + + return result.FollowOnSuccessWith(t => t.Result); + } +#endif + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderState.cs new file mode 100644 index 0000000..b3dc96c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataReaderState.cs @@ -0,0 +1,143 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Enumeration of all possible states of an . + /// + public enum ODataReaderState + { + /// The reader is at the start; nothing has been read yet. + /// In this state the Item property of the returns null. + Start, + + /// The start of a resource set has been read. + /// + /// In this state the Item property of the returns + /// an but no properties may be filled in until the ResourceSetEnd state is reached. + /// + ResourceSetStart, + + /// The end of a resource set has been read. + /// + /// In this state the Item property of the returns + /// an with all properties filled in. + /// + ResourceSetEnd, + + /// The start of a resource has been read. + /// + /// In this state the Item property of the returns + /// an but no properties may be filled in until the EntryEnd state is reached. + /// + ResourceStart, + + /// The end of a resource has been read. + /// + /// In this state the Item property of the returns + /// an with all properties filled in. + /// + ResourceEnd, + + /// The start of a nested resource info has been read. + /// + /// In this state the Item property of the returns + /// an but no properties may be filled in until the LinkEnd state is reached. + /// + NestedResourceInfoStart, + + /// The end of a nested resource info has been read. + /// + /// In this state the Item property of the returns + /// an with all properties filled in. + /// + NestedResourceInfoEnd, + + /// An entity reference link was read. + /// + /// In this state the Item property of the returns + /// an which is fully populated. + /// Note that there's no End state for this item. + /// + EntityReferenceLink, + + /// The reader has thrown an exception; nothing can be read from the reader anymore. + /// + /// In this state the Item property of the returns null. + /// + Exception, + + /// The reader has completed; nothing can be read anymore. + /// + /// In this state the Item property of the returns null. + /// + Completed, + + /// The reader is positioned on a non-null primivite value within an untyped collection. + /// + /// In this state the Item property of the returns + /// an . + /// + Primitive, + + /// The start of a delta resource set has been read. + /// + /// In this state the Item property of the returns + /// an . + /// + DeltaResourceSetStart, + + /// The end of a delta resource set has been read. + /// + /// In this state the Item property of the returns + /// an . + /// + DeltaResourceSetEnd, + + /// The start of a deleted resource has been read. + /// + /// In this state the Item property of the returns + /// an . + /// + DeletedResourceStart, + + /// The end of a deleted resource has been read. + /// + /// In this state the Item property of the returns + /// an . + /// + DeletedResourceEnd, + + /// The reder is positioned on a delta link. + /// + /// In this state the Item property of the returns + /// an . + /// + DeltaLink, + + /// The reader is positioned on a delta deleted link. + /// + /// In this state the Item property of the returns + /// an . + /// + DeltaDeletedLink, + + /// The reader is reading a property. + /// + /// In this state the Item property of the returns + /// an . + /// + NestedProperty, + + /// The reader is reading a stream. + /// + /// In this state the Item property of the returns + /// an . + /// + Stream + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRequestMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRequestMessage.cs new file mode 100644 index 0000000..cb1cd88 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataRequestMessage.cs @@ -0,0 +1,148 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Wrapper class around an IODataRequestMessageAsync to isolate our code from the interface implementation. + /// + internal sealed class ODataRequestMessage : ODataMessage, +#if PORTABLELIB + IODataRequestMessageAsync +#else + IODataRequestMessage +#endif + { + /// The request message this class is wrapping. + private readonly IODataRequestMessage requestMessage; + + /// + /// Constructs an internal wrapper around the + /// that isolates the internal implementation of the ODataLib from the interface. + /// + /// The request message to wrap. + /// true if the request message is being written; false when it is read. + /// true if the stream returned should be disposed calls. + /// The maximum size of the message in bytes (or a negative value if no maximum applies). + internal ODataRequestMessage(IODataRequestMessage requestMessage, bool writing, bool enableMessageStreamDisposal, long maxMessageSize) + : base(writing, enableMessageStreamDisposal, maxMessageSize) + { + Debug.Assert(requestMessage != null, "requestMessage != null"); + + this.requestMessage = requestMessage; + } + + /// + /// The request Url for this request message. + /// + public Uri Url + { + get + { + return this.requestMessage.Url; + } + + set + { + throw new ODataException(Strings.ODataMessage_MustNotModifyMessage); + } + } + + /// + /// The HTTP method used for this request message. + /// + public string Method + { + get + { + return this.requestMessage.Method; + } + + set + { + throw new ODataException(Strings.ODataMessage_MustNotModifyMessage); + } + } + + /// + /// Returns an enumerable over all the headers for this message. + /// + public override IEnumerable> Headers + { + get + { + return this.requestMessage.Headers; + } + } + + /// + /// Returns a value of an HTTP header. + /// + /// The name of the header to get. + /// The value of the HTTP header, or null if no such header was present on the message. + public override string GetHeader(string headerName) + { + return this.requestMessage.GetHeader(headerName); + } + + /// + /// Sets the value of an HTTP header. + /// + /// The name of the header to set. + /// The value of the HTTP header or 'null' if the header should be removed. + public override void SetHeader(string headerName, string headerValue) + { + this.VerifyCanSetHeader(); + this.requestMessage.SetHeader(headerName, headerValue); + } + + /// + /// Synchronously get the stream backing this message. + /// + /// The stream for this message. + public override Stream GetStream() + { + return this.GetStream(this.requestMessage.GetStream, /*isRequest*/ true); + } + +#if PORTABLELIB + /// + /// Asynchronously get the stream backing this message. + /// + /// The stream for this message. + public override Task GetStreamAsync() + { + IODataRequestMessageAsync asyncRequestMessage = this.requestMessage as IODataRequestMessageAsync; + if (asyncRequestMessage == null) + { + throw new ODataException(Strings.ODataRequestMessage_AsyncNotAvailable); + } + + return this.GetStreamAsync(asyncRequestMessage.GetStreamAsync, /*isRequest*/ true); + } +#endif + + /// + /// Queries the message for the specified interface type. + /// + /// The type of the interface to query for. + /// The instance of the interface asked for or null if it was not implemented by the message. + internal override TInterface QueryInterface() + { + return this.requestMessage as TInterface; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResource.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResource.cs new file mode 100644 index 0000000..ef08de5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResource.cs @@ -0,0 +1,426 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Evaluation; + #endregion Namespaces + + /// + /// Represents a single entity. + /// + public sealed class ODataResource : ODataResourceBase + { + /// + /// Provides additional serialization information to the for this . + /// + internal override ODataResourceSerializationInfo SerializationInfo + { + set + { + base.SerializationInfo = ODataResourceSerializationInfo.Validate(value); + } + } + } + + /// + /// Base class for a resource or deleted resource. + /// + [DebuggerDisplay("Id: {Id} TypeName: {TypeName}")] + public abstract class ODataResourceBase : ODataItem + { + /// the metadata builder for this OData resource. + private ODataResourceMetadataBuilder metadataBuilder; + + /// The resource ETag, as provided by the user or seen on the wire (never computed). + private string etag; + + /// true if an etag was provided by the user or seen on the wire, false otherwise. + private bool hasNonComputedETag; + + /// The Resource ID, as provided by the user or seen on the wire (never computed). + private Uri id; + + /// true if an id was provided by the user or seen on the wire, false otherwise. + private bool hasNonComputedId; + + /// Link used to edit the resource, as provided by the user or seen on the wire (never computed). + private Uri editLink; + + /// true if an edit link was provided by the user or seen on the wire, false otherwise. + private bool hasNonComputedEditLink; + + /// A link that can be used to read the resource, as provided by the user or seen on the wire (never computed). + private Uri readLink; + + /// true if a read link was provided by the user or seen on the wire, false otherwise. + private bool hasNonComputedReadLink; + + /// The default media resource of the media link resource, as provided by the user or seen on the wire (never computed). + private ODataStreamReferenceValue mediaResource; + + /// The resource properties provided by the user or seen on the wire (never computed). + private IEnumerable properties; + + /// The resource actions provided by the user or seen on the wire (never computed). + private List actions = new List(); + + /// The resource functions provided by the user or seen on the wire (never computed). + private List functions = new List(); + + /// + /// Provides additional serialization information to the for this . + /// + private ODataResourceSerializationInfo serializationInfo; + + /// Gets or sets the resource ETag. + /// The resource ETag. + public string ETag + { + get + { + return this.MetadataBuilder.GetETag(); + } + + set + { + this.etag = value; + this.hasNonComputedETag = true; + } + } + + /// Gets or sets the Resource identifier. + /// The Resource identifier. + public Uri Id + { + get + { + return this.MetadataBuilder.GetId(); + } + + set + { + this.id = value; + this.hasNonComputedId = true; + } + } + + /// Gets or sets the link used to edit the resource. + /// The link used to edit the resource. + public Uri EditLink + { + get + { + return this.MetadataBuilder.GetEditLink(); + } + + set + { + this.editLink = value; + this.hasNonComputedEditLink = true; + } + } + + /// Gets or sets the value that shows if the resource is a transient resource or not + /// true if the resource is a transient entity, false otherwise. + public bool IsTransient + { + get; + set; + } + + /// Gets or sets a link that can be used to read the resource. + /// The link that can be used to read the resource. + public Uri ReadLink + { + get + { + return this.MetadataBuilder.GetReadLink(); + } + + set + { + this.readLink = value; + this.hasNonComputedReadLink = true; + } + } + + /// Gets or sets the default media resource of the media link resource. + /// The default media resource of the media link resource. + public ODataStreamReferenceValue MediaResource + { + get { return this.MetadataBuilder.GetMediaResource(); } + set { this.mediaResource = value; } + } + + /// Gets the entity actions. + /// The entity actions. + public IEnumerable Actions + { + get { return this.MetadataBuilder.GetActions(); } + } + + /// Gets the entity functions. + /// The entity functions. + public IEnumerable Functions + { + get { return this.MetadataBuilder.GetFunctions(); } + } + + /// Gets or sets the resource properties. + /// The resource properties. + /// + /// Non-property content goes to annotations. + /// + public IEnumerable Properties + { + get + { + return this.MetadataBuilder.GetProperties(this.properties); + } + + set + { + VerifyProperties(value); + this.properties = value; + } + } + + /// Gets or sets the type name of the resource. + /// The type name of the resource. + public string TypeName + { + get; + set; + } + + /// + /// Collection of custom instance annotations. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow the same instance annotation collection instance to be shared across ODataLib OM instances.")] + public ICollection InstanceAnnotations + { + get { return this.GetInstanceAnnotations(); } + set { this.SetInstanceAnnotations(value); } + } + + /// + /// The metadata builder for this OData resource. + /// + internal ODataResourceMetadataBuilder MetadataBuilder + { + get + { + if (this.metadataBuilder == null) + { + this.metadataBuilder = new NoOpResourceMetadataBuilder(this); + } + + return this.metadataBuilder; + } + + set + { + Debug.Assert(value != null, "MetadataBuilder != null"); + this.metadataBuilder = value; + } + } + + /// + /// Returns the resource's Id property that has been set directly, and was not computed using the metadata builder. + /// + internal Uri NonComputedId + { + get + { + return this.id; + } + } + + /// + /// true if an id was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedId + { + get + { + return this.hasNonComputedId; + } + } + + /// + /// Returns the resource's EditLink property that has been set directly, and was not computed using the metadata builder. + /// + internal Uri NonComputedEditLink + { + get + { + return this.editLink; + } + } + + /// + /// true if an edit link was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedEditLink + { + get + { + return this.hasNonComputedEditLink; + } + } + + /// + /// Returns the resource's ReadLink property that has been set directly, and was not computed using the metadata builder. + /// + internal Uri NonComputedReadLink + { + get + { + return this.readLink; + } + } + + /// + /// true if a read link was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedReadLink + { + get + { + return this.hasNonComputedReadLink; + } + } + + /// + /// Returns the resource's ETag property that has been set directly, and was not computed using the metadata builder. + /// + internal string NonComputedETag + { + get + { + return this.etag; + } + } + + /// + /// true if an etag was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedETag + { + get + { + return this.hasNonComputedETag; + } + } + + /// Returns the default media resource of the media link resource that has been set directly and was not computed using the metadata builder. + internal ODataStreamReferenceValue NonComputedMediaResource + { + get + { + return this.mediaResource; + } + } + + /// Returns the entity properties that has been set directly and was not computed using the metadata builder. + internal IEnumerable NonComputedProperties + { + get + { + return this.properties; + } + } + + /// Returns the entity actions that has been set directly and was not computed using the metadata builder. + internal IEnumerable NonComputedActions + { + get + { + return this.actions == null ? null : new ReadOnlyEnumerable(this.actions); + } + } + + /// Returns the entity functions that has been set directly and was not computed using the metadata builder. + internal IEnumerable NonComputedFunctions + { + get + { + return this.functions == null ? null : new ReadOnlyEnumerable(this.functions); + } + } + + /// + /// Provides additional serialization information to the for this . + /// + internal virtual ODataResourceSerializationInfo SerializationInfo + { + get + { + return this.serializationInfo; + } + + set + { + this.serializationInfo = value; + } + } + + /// + /// Add action to resource. + /// + /// The action to add. + public void AddAction(ODataAction action) + { + ExceptionUtils.CheckArgumentNotNull(action, "action"); + if (!this.actions.Contains(action)) + { + this.actions.Add(action); + } + } + + /// + /// Add function to resource. + /// + /// The function to add. + public void AddFunction(ODataFunction function) + { + ExceptionUtils.CheckArgumentNotNull(function, "function"); + if (!this.functions.Contains(function)) + { + this.functions.Add(function); + } + } + + private static void VerifyProperties(IEnumerable properties) + { + if (properties != null) + { + ODataCollectionValue collection = null; + foreach (var property in properties) + { + if (property.Value is ODataResourceValue) + { + throw new ODataException(Strings.ODataResource_PropertyValueCannotBeODataResourceValue(property.Name)); + } + else if ((collection = property.Value as ODataCollectionValue) != null) + { + if (collection != null && collection.Items != null && collection.Items.Any(t => t is ODataResourceValue)) + { + throw new ODataException(Strings.ODataResource_PropertyValueCannotBeODataResourceValue(property.Name)); + } + } + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSerializationInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSerializationInfo.cs new file mode 100644 index 0000000..0796a1e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSerializationInfo.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using Microsoft.OData.Edm; + + /// + /// Class to provide additional serialization information to the for an . + /// + public sealed class ODataResourceSerializationInfo + { + /// + /// The navigation source name of the resource to be written. Should be fully qualified if the navigation source is not in the default container. + /// + private string navigationSourceName; + + /// + /// The namespace qualified entity type name of the navigation source. + /// + private string navigationSourceEntityTypeName; + + /// + /// The namespace qualified type name of the expected entity type. + /// + private string expectedTypeName; + + /// + /// The navigation source name of the resource to be written. Should be fully qualified if the navigation source is not in the default container. + /// + public string NavigationSourceName + { + get + { + return this.navigationSourceName; + } + + set + { + this.navigationSourceName = value; + } + } + + /// + /// The namespace qualified element type name of the navigation source. + /// + public string NavigationSourceEntityTypeName + { + get + { + return this.navigationSourceEntityTypeName; + } + + set + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(value, "NavigationSourceEntityTypeName"); + this.navigationSourceEntityTypeName = value; + } + } + + /// + /// The kind of the navigation source. + /// + public EdmNavigationSourceKind NavigationSourceKind { get; set; } + + /// + /// The flag we use to identify if the current resource is from a navigation property with collection type or not. + /// + public bool IsFromCollection { get; set; } + + /// + /// The namespace qualified type name of the expected resource type. + /// + public string ExpectedTypeName + { + get + { + return this.expectedTypeName ?? this.NavigationSourceEntityTypeName; + } + + set + { + ExceptionUtils.CheckArgumentStringNotEmpty(value, "ExpectedTypeName"); + this.expectedTypeName = value; + } + } + + /// + /// Validates the instance. + /// + /// The serialization info instance to validate. + /// The instance. + internal static ODataResourceSerializationInfo Validate(ODataResourceSerializationInfo serializationInfo) + { + if (serializationInfo != null && serializationInfo.NavigationSourceKind != EdmNavigationSourceKind.None) + { + if (serializationInfo.NavigationSourceKind != EdmNavigationSourceKind.UnknownEntitySet) + { + ExceptionUtils.CheckArgumentNotNull(serializationInfo.NavigationSourceName, "serializationInfo.NavigationSourceName"); + } + + ExceptionUtils.CheckArgumentNotNull(serializationInfo.NavigationSourceEntityTypeName, "serializationInfo.NavigationSourceEntityTypeName"); + } + + return serializationInfo; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSet.cs new file mode 100644 index 0000000..df32bef --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSet.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using Microsoft.OData.Metadata; + + /// + /// Describes a collection of entities. + /// + public sealed class ODataResourceSet : ODataResourceSetBase + { + /// The resource set actions provided by the user or seen on the wire (never computed). + private List actions = new List(); + + /// The resource set functions provided by the user or seen on the wire (never computed). + private List functions = new List(); + + /// Gets the resource set actions. + /// The resource set actions. + public IEnumerable Actions + { + get { return this.actions; } + } + + /// Gets the resource set functions. + /// The resource set functions. + public IEnumerable Functions + { + get { return this.functions; } + } + + /// + /// Add action to resource set. + /// + /// The action to add. + public void AddAction(ODataAction action) + { + ExceptionUtils.CheckArgumentNotNull(action, "action"); + if (!this.actions.Contains(action)) + { + this.actions.Add(action); + } + } + + /// + /// Add function to resource set. + /// + /// The function to add. + public void AddFunction(ODataFunction function) + { + ExceptionUtils.CheckArgumentNotNull(function, "function"); + if (!this.functions.Contains(function)) + { + this.functions.Add(function); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSetBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSetBase.cs new file mode 100644 index 0000000..9538dc4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceSetBase.cs @@ -0,0 +1,145 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// Describes a collection of entities. + /// + public abstract class ODataResourceSetBase : ODataItem + { + /// + /// URI representing the next page link. + /// + private Uri nextPageLink; + + /// + /// URI representing the delta link. + /// + private Uri deltaLink; + + /// + /// Provides additional serialization information to the for this . + /// + private ODataResourceSerializationInfo serializationInfo; + + /// + /// The type name of the resource set. + /// + private string typeName; + + /// Gets the resource set type name. + /// The resource set type name. + public string TypeName + { + get + { + if (typeName == null && this.SerializationInfo != null && this.SerializationInfo.ExpectedTypeName != null) + { + typeName = EdmLibraryExtensions.GetCollectionTypeName(this.SerializationInfo.ExpectedTypeName); + } + + return typeName; + } + + set + { + this.typeName = value; + } + } + + /// Gets or sets the number of items in the resource set. + /// The number of items in the resource set. + public long? Count + { + get; + set; + } + + /// Gets or sets the URI that identifies the entity set represented by the resource set. + /// The URI that identifies the entity set represented by the resource set. + public Uri Id + { + get; + set; + } + + /// Gets or sets the URI representing the next page link. + /// The URI representing the next page link. + public Uri NextPageLink + { + get + { + return this.nextPageLink; + } + + set + { + if (this.DeltaLink != null && value != null) + { + throw new ODataException(ODataErrorStrings.ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink); + } + + this.nextPageLink = value; + } + } + + /// + /// URI representing the delta link. + /// + public Uri DeltaLink + { + get + { + return this.deltaLink; + } + + set + { + if (this.NextPageLink != null && value != null) + { + throw new ODataException(ODataErrorStrings.ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink); + } + + this.deltaLink = value; + } + } + + /// + /// Collection of custom instance annotations. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "We want to allow the same instance annotation collection instance to be shared across ODataLib OM instances.")] + public ICollection InstanceAnnotations + { + get { return this.GetInstanceAnnotations(); } + set { this.SetInstanceAnnotations(value); } + } + + /// + /// Provides additional serialization information to the for this . + /// + internal ODataResourceSerializationInfo SerializationInfo + { + get + { + return this.serializationInfo; + } + + set + { + this.serializationInfo = ODataResourceSerializationInfo.Validate(value); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceTypeContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceTypeContext.cs new file mode 100644 index 0000000..e2a1425 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResourceTypeContext.cs @@ -0,0 +1,457 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + /// + /// The context object to answer basic questions regarding the type of the resource or resource set. + /// + internal class ODataResourceTypeContext : IODataResourceTypeContext + { + /// + /// The expected resource type of the resource set or resource. + /// + protected IEdmStructuredType expectedResourceType; + + /// + /// The expected resource type name of the resource set or resource. + /// + protected string expectedResourceTypeName; + + /// + /// If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined. + /// + private readonly bool throwIfMissingTypeInfo; + + /// + /// Constructs an instance of . + /// + /// If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined. + private ODataResourceTypeContext(bool throwIfMissingTypeInfo) + { + this.throwIfMissingTypeInfo = throwIfMissingTypeInfo; + } + + /// + /// Constructs an instance of . + /// + /// The expected resource type of resource set or resource. + /// If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined. + private ODataResourceTypeContext(IEdmStructuredType expectedResourceType, bool throwIfMissingTypeInfo) + { + this.expectedResourceType = expectedResourceType; + this.throwIfMissingTypeInfo = throwIfMissingTypeInfo; + } + + /// + /// The navigation source name of the resource set or resource. + /// + public virtual string NavigationSourceName + { + get { return this.ValidateAndReturn(default(string)); } + } + + /// + /// The entity type name of the navigation source of the resource set or resource. + /// + public virtual string NavigationSourceEntityTypeName + { + get { return this.ValidateAndReturn(default(string)); } + } + + /// + /// The full type name of the navigation source of the resource set or resource. + /// + public virtual string NavigationSourceFullTypeName + { + get { return this.ValidateAndReturn(default(string)); } + } + + /// + /// The kind of the navigation source of the resource set or resource. + /// + public virtual EdmNavigationSourceKind NavigationSourceKind + { + get { return EdmNavigationSourceKind.None; } + } + + /// + /// The expected entity type name of the resource. + /// For example, in the request URI 'http://example.com/Service.svc/People/Namespace.VIP_Person', the expected entity type is Namespace.VIP_Person. + /// (The entity set element type name in this example may be Person, and the actual entity type of a particular entity might be a type more derived than VIP_Person) + /// + public virtual string ExpectedResourceTypeName + { + get + { + if (this.expectedResourceTypeName == null) + { + this.expectedResourceTypeName = this.expectedResourceType == null ? null : this.expectedResourceType.FullTypeName(); + } + + return this.expectedResourceTypeName; + } + } + + /// + /// The expected resource type. + /// For example, in the request URI 'http://example.com/Service.svc/People/Namespace.VIP_Person', the expected resource type is Namespace.VIP_Person. + /// (The entity set element type name in this example may be Person, and the actual entity type of a particular entity might be a type more derived than VIP_Person) + /// + public virtual IEdmStructuredType ExpectedResourceType + { + get { return this.expectedResourceType; } + } + + /// + /// The flag we use to identify if the current resource is from a collection type or not. + /// + public virtual bool IsFromCollection + { + get { return false; } + } + + /// + /// true if the resource is an MLE, false otherwise. + /// + public virtual bool IsMediaLinkEntry + { + get { return false; } + } + + /// + /// Creates an instance of . + /// + /// The serialization info from the resource set or resource instance. + /// The navigation source of the resource set or resource. + /// The entity type of the navigation source. + /// The expected structured type of the resource set or resource. + /// If true, throw if any of the set or type name cannot be determined; if false, return null when any of the set or type name cannot determined. + /// A new instance of . + internal static ODataResourceTypeContext Create(ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType navigationSourceEntityType, IEdmStructuredType expectedResourceType, bool throwIfMissingTypeInfo) + { + if (serializationInfo != null) + { + return new ODataResourceTypeContextWithoutModel(serializationInfo); + } + + // We are creating an ODataResourceTypeContext for a complex item with navigation source is null. + if (expectedResourceType != null && expectedResourceType.IsODataComplexTypeKind()) + { + return new ODataResourceTypeContextWithModel(null, null, expectedResourceType); + } + + // We are creating an ODataResourceTypeContext for an item in Navigation Source(e.g. an entity set). + if (navigationSource != null && expectedResourceType != null) + { + return new ODataResourceTypeContextWithModel(navigationSource, navigationSourceEntityType, expectedResourceType); + } + + return new ODataResourceTypeContext(expectedResourceType, throwIfMissingTypeInfo); + } + + /// + /// Validate and return the given value. + /// + /// The type of the value to validate. + /// The value to validate. + /// The return value. + private T ValidateAndReturn(T value) where T : class + { + if (this.throwIfMissingTypeInfo && value == null) + { + throw new ODataException(Strings.ODataResourceTypeContext_MetadataOrSerializationInfoMissing); + } + + return value; + } + + /// + /// The context object to answer basic questions regarding the type of the resource or resource set based on the serialization info. + /// + internal sealed class ODataResourceTypeContextWithoutModel : ODataResourceTypeContext + { + /// + /// The serialization info of the resource for writing without model. + /// + private readonly ODataResourceSerializationInfo serializationInfo; + + /// + /// Constructs an instance of . + /// + /// The serialization info from the resource set or resource instance. + internal ODataResourceTypeContextWithoutModel(ODataResourceSerializationInfo serializationInfo) + : base(/*throwIfMissingTypeInfo*/false) + { + Debug.Assert(serializationInfo != null, "serializationInfo != null"); + this.serializationInfo = serializationInfo; + } + + /// + /// The navigation source name of the resource set or resource. + /// + public override string NavigationSourceName + { + get { return this.serializationInfo.NavigationSourceName; } + } + + /// + /// The entity type name of the navigation source of the resource set or resource. + /// + public override string NavigationSourceEntityTypeName + { + get { return this.serializationInfo.NavigationSourceEntityTypeName; } + } + + /// + /// The full type name of the navigation source of the resource set or resource. + /// + public override string NavigationSourceFullTypeName + { + get + { + if (this.IsFromCollection) + { + return EdmLibraryExtensions.GetCollectionTypeName( + this.serializationInfo.NavigationSourceEntityTypeName); + } + else + { + return this.serializationInfo.NavigationSourceEntityTypeName; + } + } + } + + /// + /// The kind of the navigation source of the resource set or resource. + /// + public override EdmNavigationSourceKind NavigationSourceKind + { + get { return this.serializationInfo.NavigationSourceKind; } + } + + /// + /// The expected entity type name of the resource. + /// For example, in the request URI 'http://example.com/Service.svc/People/Namespace.VIP_Person', the expected entity type is Namespace.VIP_Person. + /// (The entity set element type name in this example may be Person, and the actual entity type of a particular entity might be a type more derived than VIP_Person) + /// + public override string ExpectedResourceTypeName + { + get { return this.serializationInfo.ExpectedTypeName; } + } + + /// + /// The expected resource type. + /// For example, in the request URI 'http://example.com/Service.svc/People/Namespace.VIP_Person', the expected resource type is Namespace.VIP_Person. + /// (The entity set element type name in this example may be Person, and the actual entity type of a particular entity might be a type more derived than VIP_Person) + /// + public override IEdmStructuredType ExpectedResourceType + { + get { return null; } + } + + /// + /// true if the resource is an MLE, false otherwise. + /// + public override bool IsMediaLinkEntry + { + get + { + // When writing without model, user should always set the ODataResource.MediaResource property if the resource is a media link resource. + // This is consistent with the requirement for the with model scenario. + // Returning false here so the metadata builder won't create a new instance of the ODataStreamReferenceValue. + return false; + } + } + + /// + /// The flag we use to identify if the current resource is from a collection type or not. + /// + public override bool IsFromCollection + { + get { return this.serializationInfo.IsFromCollection; } + } + } + + /// + /// The context object to answer basic questions regarding the type of the resource or resource set based on the metadata. + /// + internal sealed class ODataResourceTypeContextWithModel : ODataResourceTypeContext + { + /// + /// The navigation source of the entity set or entity. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// The entity type of the navigation source of the entity set or entity. + /// + private readonly IEdmEntityType navigationSourceEntityType; + + /// + /// The navigation source name of the resource set or resource. + /// + private readonly string navigationSourceName; + + /// + /// true if the resource is an media link resource or if the resource set contains media link entries, false otherwise. + /// + private readonly bool isMediaLinkEntry; + + /// + /// The flag we use to identify if the current resource is from a navigation property with collection type or not. + /// + private readonly bool isFromCollection = false; + + /// + /// The full type name of the navigation source of the entity set or entity. + /// + private string navigationSourceFullTypeName; + + /// + /// The entity type name of the navigation source of the entity set or entity. + /// + private string navigationSourceEntityTypeName; + + /// + /// Constructs an instance of . + /// + /// The navigation source of the resource set or resource. + /// The entity type of the navigation source. + /// The expected resource type of the resource set or resource. + internal ODataResourceTypeContextWithModel(IEdmNavigationSource navigationSource, IEdmEntityType navigationSourceEntityType, IEdmStructuredType expectedResourceType) + : base(expectedResourceType, /*throwIfMissingTypeInfo*/false) + { + Debug.Assert(expectedResourceType != null, "expectedResourceType != null"); + Debug.Assert(navigationSource != null + && navigationSourceEntityType != null + || expectedResourceType.IsODataComplexTypeKind(), + "navigationSource != null && navigationSourceEntityType != null || expectedResourceType.IsODataComplexTypeKind()"); + + this.navigationSource = navigationSource; + this.navigationSourceEntityType = navigationSourceEntityType; + + IEdmContainedEntitySet containedEntitySet = navigationSource as IEdmContainedEntitySet; + if (containedEntitySet != null) + { + if (containedEntitySet.NavigationProperty.Type.TypeKind() == EdmTypeKind.Collection) + { + this.isFromCollection = true; + } + } + + IEdmUnknownEntitySet unknownEntitySet = navigationSource as IEdmUnknownEntitySet; + if (unknownEntitySet != null) + { + if (unknownEntitySet.Type.TypeKind == EdmTypeKind.Collection) + { + this.isFromCollection = true; + } + } + + this.navigationSourceName = this.navigationSource == null ? null : this.navigationSource.Name; + var entityType = this.expectedResourceType as IEdmEntityType; + this.isMediaLinkEntry = entityType == null ? false : entityType.HasStream; + } + + /// + /// The navigation source name of the resource set or resource. + /// + public override string NavigationSourceName + { + get { return this.navigationSourceName; } + } + + /// + /// The entity type name of the navigation source of the resource set or resource. + /// + public override string NavigationSourceEntityTypeName + { + get + { + if (navigationSourceEntityType != null) + { + this.navigationSourceEntityTypeName = navigationSourceEntityType.FullName(); + } + + return this.navigationSourceEntityTypeName; + } + } + + /// + /// The full type name of the navigation source of the resource set or resource. + /// + public override string NavigationSourceFullTypeName + { + get + { + if (this.navigationSource != null) + { + this.navigationSourceFullTypeName = this.navigationSource.Type.FullTypeName(); + } + + return this.navigationSourceFullTypeName; + } + } + + /// + /// The kind of the navigation source of the resource set or resource. + /// + public override EdmNavigationSourceKind NavigationSourceKind + { + get { return this.navigationSource.NavigationSourceKind(); } + } + + /// + /// The expected resource type name of the resource. + /// For example, in the request URI 'http://example.com/Service.svc/People/Namespace.VIP_Person', the expected resource type is Namespace.VIP_Person. + /// (The entity set element type name in this example may be Person, and the actual resource type of a particular resource might be a type more derived than VIP_Person) + /// + public override string ExpectedResourceTypeName + { + get { return this.expectedResourceType.FullTypeName(); } + } + + /// + /// The expected resource type. + /// For example, in the request URI 'http://example.com/Service.svc/People/Namespace.VIP_Person', the expected resource type is Namespace.VIP_Person. + /// (The entity set element type name in this example may be Person, and the actual entity type of a particular entity might be a type more derived than VIP_Person) + /// + public override IEdmStructuredType ExpectedResourceType + { + get { return this.expectedResourceType; } + } + + /// + /// true if the resource is an MLE, false otherwise. + /// + public override bool IsMediaLinkEntry + { + get { return this.isMediaLinkEntry; } + } + + /// + /// The flag we use to identify if the current resource is from a collection type or not. + /// + public override bool IsFromCollection + { + get { return this.isFromCollection; } + } + + /// + /// The entity type of the navigation source of the resource set or resource. + /// + internal IEdmEntityType NavigationSourceEntityType + { + get { return this.navigationSourceEntityType; } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResponseMessage.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResponseMessage.cs new file mode 100644 index 0000000..b66bd6d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataResponseMessage.cs @@ -0,0 +1,136 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Wrapper class around an IODataResponseMessageAsync to isolate our code from the interface implementation. + /// + /// + /// This class also implements the message interface since it is passed to the payload kind + /// detection logic on the format implementations and manages the buffering read stream. + /// + internal sealed class ODataResponseMessage : ODataMessage, +#if PORTABLELIB + IODataResponseMessageAsync +#else + IODataResponseMessage +#endif + { + /// The response message this class is wrapping. + private readonly IODataResponseMessage responseMessage; + + /// + /// Constructs an internal wrapper around the + /// that isolates the internal implementation of the ODataLib from the interface. + /// + /// The response message to wrap. + /// true if the message is being written; false when it is read. + /// true if the stream returned should be disposed calls. + /// The maximum size of the message in bytes (or a negative number if no maximum applies). + internal ODataResponseMessage(IODataResponseMessage responseMessage, bool writing, bool enableMessageStreamDisposal, long maxMessageSize) + : base(writing, enableMessageStreamDisposal, maxMessageSize) + { + Debug.Assert(responseMessage != null, "responseMessage != null"); + + this.responseMessage = responseMessage; + } + + /// + /// The result status code of the response message. + /// + public int StatusCode + { + get + { + return this.responseMessage.StatusCode; + } + + set + { + throw new ODataException(Strings.ODataMessage_MustNotModifyMessage); + } + } + + /// + /// Returns an enumerable over all the headers for this message. + /// + public override IEnumerable> Headers + { + get + { + return this.responseMessage.Headers; + } + } + + /// + /// Returns a value of an HTTP header. + /// + /// The name of the header to get. + /// The value of the HTTP header, or null if no such header was present on the message. + public override string GetHeader(string headerName) + { + return this.responseMessage.GetHeader(headerName); + } + + /// + /// Sets the value of an HTTP header. + /// + /// The name of the header to set. + /// The value of the HTTP header or 'null' if the header should be removed. + public override void SetHeader(string headerName, string headerValue) + { + this.VerifyCanSetHeader(); + this.responseMessage.SetHeader(headerName, headerValue); + } + + /// + /// Get the stream backing this message. + /// + /// The stream for this message. + public override Stream GetStream() + { + return this.GetStream(this.responseMessage.GetStream, /*isRequest*/ false); + } + +#if PORTABLELIB + /// + /// Asynchronously get the stream backing this message. + /// + /// The stream for this message. + public override Task GetStreamAsync() + { + IODataResponseMessageAsync asyncResponseMessage = this.responseMessage as IODataResponseMessageAsync; + if (asyncResponseMessage == null) + { + throw new ODataException(Strings.ODataResponseMessage_AsyncNotAvailable); + } + + return this.GetStreamAsync(asyncResponseMessage.GetStreamAsync, /*isRequest*/ false); + } +#endif + + /// + /// Queries the message for the specified interface type. + /// + /// The type of the interface to query for. + /// The instance of the interface asked for or null if it was not implemented by the message. + internal override TInterface QueryInterface() + { + return this.responseMessage as TInterface; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSerializer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSerializer.cs new file mode 100644 index 0000000..68a74c1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSerializer.cs @@ -0,0 +1,92 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Base class for all OData serializers. + /// + internal abstract class ODataSerializer + { + /// The writer validator used during serializing. + protected readonly IWriterValidator WriterValidator; + + /// + /// The output context to write to. + /// + private readonly ODataOutputContext outputContext; + + /// + /// Constructor. + /// + /// The output context to write to. + protected ODataSerializer(ODataOutputContext outputContext) + { + Debug.Assert(outputContext != null, "outputContext != null"); + + this.outputContext = outputContext; + this.WriterValidator = outputContext.WriterValidator; + } + + /// + /// The message writer settings. + /// + internal ODataMessageWriterSettings MessageWriterSettings + { + get + { + return this.outputContext.MessageWriterSettings; + } + } + + /// + /// The URL converter. + /// + internal IODataPayloadUriConverter PayloadUriConverter + { + get + { + return this.outputContext.PayloadUriConverter; + } + } + + /// + /// true if the output is a response payload; false if it's a request payload. + /// + internal bool WritingResponse + { + get + { + return this.outputContext.WritingResponse; + } + } + + /// + /// The model to use. + /// + internal IEdmModel Model + { + get + { + return this.outputContext.Model; + } + } + + /// + /// Creates a new instance of a duplicate property names checker. + /// + /// The newly created instance of duplicate property names checker. + internal IDuplicatePropertyNameChecker CreateDuplicatePropertyNameChecker() + { + return MessageWriterSettings.Validator.CreateDuplicatePropertyNameChecker(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocument.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocument.cs new file mode 100644 index 0000000..e26f8d8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocument.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections.Generic; + #endregion Namespaces + + /// + /// Class representing the a service document. + /// + public sealed class ODataServiceDocument : ODataAnnotatable + { + /// Gets or sets the set of entity sets in the service document. + /// The set of entity sets in the service document. + public IEnumerable EntitySets + { + get; + set; + } + + /// Gets or sets the set of singletons in the service document. + /// The set of singletons in the service document. + public IEnumerable Singletons + { + get; + set; + } + + /// Gets or sets the set of function imports in the service document. + /// The set of function imports in the service document. + public IEnumerable FunctionImports + { + get; + set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocumentElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocumentElement.cs new file mode 100644 index 0000000..6de06cd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataServiceDocumentElement.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + #endregion Namespaces + + /// + /// Abstract class representing an element (EntitySet, Singleton) in a service document. + /// + public abstract class ODataServiceDocumentElement : ODataAnnotatable + { + /// Gets or sets the URI representing the Unified Resource Locator (URL) to the element. + /// The URI representing the Unified Resource Locator (URL) to the element. + public Uri Url + { + get; + set; + } + + /// Gets or sets the name of the element; this is the entity set or singleton name in JSON and the HREF in Atom. + /// The name of the element. + /// + /// This property is required when reading and writing the JSON light format. + /// If present in ATOM, it will be used to populate the title element. + /// + public string Name + { + get; + set; + } + + /// Gets or sets the title of the element; this is the title in JSON. + /// The title of the element. + /// + /// This property is optional in JSON light format, containing a human-readable, language-dependent title for the object. + /// The value is null if it is not present. + /// + public string Title + { + get; + set; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSimplifiedOptions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSimplifiedOptions.cs new file mode 100644 index 0000000..d1cebea --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSimplifiedOptions.cs @@ -0,0 +1,209 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData +{ + /// + /// Options which used to control the behaviour related odata simplified. + /// + public sealed class ODataSimplifiedOptions + { + // default setting for writing control information without a prefix + private bool enableWritingODataAnnotationWithoutPrefix; + + // OData 4.0-specific setting for writing control information without a prefix + private bool omitODataPrefix40 = false; + + // OData 4.01 and greater setting for writing control information without a prefix + private bool omitODataPrefix = true; + + /// + /// Constructor of ODataSimplifiedOptions + /// + public ODataSimplifiedOptions() : this(null /*version*/) + { + } + + /// + /// Constructor of ODataSimplifiedOptions + /// + /// The ODataVersion to create Default Options for. + public ODataSimplifiedOptions(ODataVersion? version) + { + this.EnableParsingKeyAsSegmentUrl = true; + this.EnableWritingKeyAsSegment = false; + this.EnableReadingKeyAsSegment = false; + + if (version == null || version < ODataVersion.V401) + { + this.EnableReadingODataAnnotationWithoutPrefix = false; + this.enableWritingODataAnnotationWithoutPrefix = this.omitODataPrefix40; + } + else + { + this.EnableReadingODataAnnotationWithoutPrefix = true; + this.enableWritingODataAnnotationWithoutPrefix = this.omitODataPrefix; + } + } + + /// + /// True if url parser support parsing path with key as segment, otherwise false. The defualt is true. + /// + public bool EnableParsingKeyAsSegmentUrl { get; set; } + + /// + /// Gets or sets a value that indicates whether the reader should put key values in their own URI segment when automatically building URIs. + /// If this value is false, automatically-generated URLs will take the form "../EntitySet('KeyValue')/..". + /// If this value is true, automatically-generated URLs will take the form "../EntitySet/KeyValue/..". + /// This setting only applies to URLs that are automatically generated by the and the URLs explicitly provided by the server won't be modified. + /// + public bool EnableReadingKeyAsSegment { get; set; } + + /// + /// True if can read reserved annotation name without prefix 'odata.', otherwise false. + /// The default value is false for OData 4.0 and true for OData 4.01. + /// The option is applied during deserialization. + /// + public bool EnableReadingODataAnnotationWithoutPrefix { get; set; } + + /// + /// Gets or sets a value that indicates whether the writer should put key values in their own URI segment when automatically building URIs. + /// If this value is false, automatically-generated URLs will take the form "../EntitySet('KeyValue')/..". + /// If this value is true, automatically-generated URLs will take the form "../EntitySet/KeyValue/..". + /// This setting only applies to URLs that are automatically generated by the and does not modify URLs explicitly provided by the user. + /// + public bool EnableWritingKeyAsSegment { get; set; } + + /// + /// True if control information should be written without the prefix 'odata.', otherwise false. + /// The default value is false for OData 4.0, true for OData 4.01. + /// The option is applied during serialization. + /// + [Obsolete("Deprecated. Use Get/SetOmitODataPrefix()")] + public bool EnableWritingODataAnnotationWithoutPrefix + { + get + { + return this.enableWritingODataAnnotationWithoutPrefix; + } + + set + { + this.enableWritingODataAnnotationWithoutPrefix = + this.omitODataPrefix = + this.omitODataPrefix40 = value; + } + } + + /// + /// Creates a shallow copy of this . + /// + /// A shallow copy of this . + public ODataSimplifiedOptions Clone() + { + var copy = new ODataSimplifiedOptions(); + copy.CopyFrom(this); + return copy; + } + + /// + /// Get whether to write OData control information without a prefix + /// True if control information can be read without prefix 'odata.', otherwise false. + /// The default value is false for OData 4.0 and true for OData 4.01. + /// The option is applied during deserialization. + /// + /// Whether to omit the OData prefix for the specified version. + public bool GetOmitODataPrefix() + { + return this.enableWritingODataAnnotationWithoutPrefix; + } + + /// + /// Version-specific behavior for writing OData control information without a prefix + /// True if control information can be read without prefix 'odata.', otherwise false. + /// The default value is false for OData 4.0 and true for OData 4.01. + /// The option is applied during deserialization. + /// + /// The version of the version-specific behavior being requested. + /// Whether to omit the OData prefix for the specified version. + public bool GetOmitODataPrefix(ODataVersion version) + { + if (version >= ODataVersion.V401) + { + return this.omitODataPrefix; + } + + return this.omitODataPrefix40; + } + + + /// + /// Whether to write OData control information without a prefix + /// True to read control information without the prefix 'odata.', otherwise false. + /// The default value is false for OData 4.0 and true for OData 4.01. + /// The option is applied during deserialization. + /// + /// True to omit writing the OData prefix, False to write the prefix. + public void SetOmitODataPrefix(bool enabled) + { + this.enableWritingODataAnnotationWithoutPrefix = + this.omitODataPrefix = + this.omitODataPrefix40 = enabled; + } + + /// + /// Version-specific behavior for writing OData control information without a prefix + /// True to read control information without the prefix 'odata.', otherwise false. + /// The default value is false for OData 4.0 and true for OData 4.01. + /// The option is applied during deserialization. + /// + /// True to omit writing the OData prefix, False to write the prefix. + /// The version for which to specify the omit prefix behavior. + public void SetOmitODataPrefix(bool enabled, ODataVersion version) + { + if (version == ODataVersion.V4) + { + this.omitODataPrefix40 = enabled; + } + else + { + this.omitODataPrefix = enabled; + } + } + + /// + /// Return the instatnce of ODataSimplifiedOptions from container if it container not null. + /// Otherwise return the static instance of ODataSimplifiedOptions. + /// + /// Container + /// OData Version + /// Instance of GetODataSimplifiedOptions + internal static ODataSimplifiedOptions GetODataSimplifiedOptions(IServiceProvider container, ODataVersion? version = null) + { + if (container == null) + { + return new ODataSimplifiedOptions(version); + } + + return container.GetRequiredService(); + } + + private void CopyFrom(ODataSimplifiedOptions other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + + this.EnableParsingKeyAsSegmentUrl = other.EnableParsingKeyAsSegmentUrl; + this.EnableReadingKeyAsSegment = other.EnableReadingKeyAsSegment; + this.EnableReadingODataAnnotationWithoutPrefix = other.EnableReadingODataAnnotationWithoutPrefix; + this.EnableWritingKeyAsSegment = other.EnableWritingKeyAsSegment; + this.enableWritingODataAnnotationWithoutPrefix = other.enableWritingODataAnnotationWithoutPrefix; + this.omitODataPrefix40 = other.omitODataPrefix40; + this.omitODataPrefix = other.omitODataPrefix; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSingletonInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSingletonInfo.cs new file mode 100644 index 0000000..48239ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataSingletonInfo.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Class representing a singleton in a service document. + /// + public sealed class ODataSingletonInfo : ODataServiceDocumentElement + { + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStream.cs new file mode 100644 index 0000000..7db831b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStream.cs @@ -0,0 +1,78 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.IO; + #endregion Namespaces + + /// + /// A stream handed to clients from ODataBatchOperationMessage.GetStream or ODataBatchOperationMessage.GetStreamAsync. + /// or representing a stream value. + /// This stream communicates status changes to an IODataStreamListener instance. + /// + internal abstract class ODataStream : Stream + { + /// Listener interface to be notified of operation changes. + private IODataStreamListener listener; + + /// + /// Constructor. + /// + /// Listener interface to be notified of operation changes. + internal ODataStream(IODataStreamListener listener) + { + Debug.Assert(listener != null, "listener != null"); + + this.listener = listener; + } + + /// + /// Seeks the stream. This operation is not supported by this stream. + /// + /// The offset to seek to. + /// The origin of the seek operation. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// Disposes the object. + /// + /// True if called from Dispose; false if called form the finalizer. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.listener != null) + { + // Tell the listener that the stream is being disposed; we expect + // that no asynchronous action is triggered by doing so. + this.listener.StreamDisposed(); + this.listener = null; + } + } + + base.Dispose(disposing); + } + + /// + /// Validates that the stream was not already disposed. + /// + protected void ValidateNotDisposed() + { + if (this.listener == null) + { + throw new ObjectDisposedException(null, Strings.ODataBatchOperationStream_Disposed); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamItem.cs new file mode 100644 index 0000000..02cb7fc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamItem.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using Edm; + #endregion + + /// + /// A class to represent a stream value + /// + public sealed class ODataStreamItem : ODataItem + { + /// Primitive Type, if known. Either String, Binary, or None. + private EdmPrimitiveTypeKind typeKind; + + /// + /// Creates an ODataStreamValue with a specified typeKind. + /// + /// The type of stream value. + public ODataStreamItem(EdmPrimitiveTypeKind primitiveTypeKind) : this(primitiveTypeKind, null) + { + } + + /// + /// Creates an ODataStreamValue with a specified typeKind. + /// + /// PrimitiveTypeKind of the stream value. + /// The mime type of the stream value. + /// primitiveTypeKind is overridden if the MimeType is known. + public ODataStreamItem(EdmPrimitiveTypeKind primitiveTypeKind, string contentType) + { + this.PrimitiveTypeKind = primitiveTypeKind; + this.ContentType = contentType; + } + + /// + /// PrimitiveTypeKind of the stream. + /// + /// + /// EdmPrimitiveTypeKind.String, if the contents is a plain text string; + /// EdmPrimitiveTypeKind.Binary, if the contents is binary; + /// EdmPrimitiveTypeKind.None is returned if the contents could be binary or plain text + /// + public EdmPrimitiveTypeKind PrimitiveTypeKind + { + get + { + return this.typeKind; + } + + private set + { + if (typeKind != EdmPrimitiveTypeKind.String && + typeKind != EdmPrimitiveTypeKind.Binary && + typeKind != EdmPrimitiveTypeKind.None) + { + throw new ODataException(Strings.StreamItemInvalidPrimitiveKind(value)); + } + + this.typeKind = value; + } + } + + /// + /// ContentType of the stream, if known. + /// + /// + /// The Mime type of the stream, if known. + /// + public string ContentType + { + get; private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamPropertyInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamPropertyInfo.cs new file mode 100644 index 0000000..b6d7ae7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataStreamPropertyInfo.cs @@ -0,0 +1,156 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using Microsoft.OData.Edm; + using Microsoft.OData.Evaluation; + + /// + /// Represents information about a single stream property of a resource. + /// + public sealed class ODataStreamPropertyInfo : ODataPropertyInfo, IODataStreamReferenceInfo + { + /// The name of the named stream this value belongs to; null for the default media resource. + private string edmPropertyName; + + /// the metadata builder for this OData resource. + private ODataResourceMetadataBuilder metadataBuilder; + + /// Edit link for media resource. + private Uri editLink; + + /// Edit link for media resource. + private Uri computedEditLink; + + /// Read link for media resource. + private Uri readLink; + + /// Read link for media resource. + private Uri computedReadLink; + + /// PrimitiveTypeKind of the media resource. + private EdmPrimitiveTypeKind primitiveTypeKind; + + /// Gets or sets the edit link for media resource. + /// The edit link for media resource. + public Uri EditLink + { + get + { + return this.HasNonComputedEditLink + ? this.editLink + : (this.computedEditLink ?? (this.metadataBuilder == null ? null : this.computedEditLink = this.metadataBuilder.GetStreamEditLink(edmPropertyName))); + } + + set + { + this.editLink = value; + this.HasNonComputedEditLink = true; + } + } + + /// Gets or sets the read link for media resource. + /// The read link for media resource. + public Uri ReadLink + { + get + { + return this.HasNonComputedReadLink + ? this.readLink + : (this.computedReadLink ?? (this.metadataBuilder == null ? null : + this.computedReadLink = this.metadataBuilder.GetStreamReadLink(edmPropertyName))); + } + + set + { + this.readLink = value; + this.HasNonComputedReadLink = true; + } + } + + /// Gets or sets the content media type. + /// The content media type. + public string ContentType + { + get; set; + } + + /// Gets or sets the media resource ETag. + /// The media resource ETag. + public string ETag + { + get; + set; + } + + /// + /// Gets or sets the kind of primitive type of the property. + /// The PrimitiveTypeKind of an ODataStreamPropertyInfo must be EdmPrimitiveTypeKind.String, EdmPrimitiveTypeKind.Binary, or EdmPrimitiveTypeKind.None. + /// + /// The of the property. + public override EdmPrimitiveTypeKind PrimitiveTypeKind + { + get + { + return this.primitiveTypeKind; + } + + set + { + if (value != EdmPrimitiveTypeKind.Binary && + value != EdmPrimitiveTypeKind.String && + value != EdmPrimitiveTypeKind.None) + { + throw new ODataException(Strings.StreamItemInvalidPrimitiveKind(value)); + } + + this.primitiveTypeKind = value; + } + } + + /// + /// true if an edit link was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedEditLink + { + get; + private set; + } + + /// + /// true if a read link was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedReadLink + { + get; + private set; + } + + /// + /// Sets the metadata builder for this stream reference value. + /// + /// The metadata builder used to compute values from model annotations. + /// The property name for the named stream; null for the default media resource. + internal void SetMetadataBuilder(ODataResourceMetadataBuilder builder, string propertyName) + { + this.metadataBuilder = builder; + this.edmPropertyName = propertyName; + this.computedEditLink = null; + this.computedReadLink = null; + } + + /// + /// Gets the metadata builder for this stream reference value. + /// + /// The metadata builder used to compute links. + internal ODataResourceMetadataBuilder GetMetadataBuilder() + { + return this.metadataBuilder; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTextStreamReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTextStreamReader.cs new file mode 100644 index 0000000..21dfe17 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTextStreamReader.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.IO; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// A textreader for reading a text value. + /// + internal sealed class ODataTextStreamReader : TextReader + { + private Func reader; + + /// + /// Constructor. + /// + /// A function from which to read character values. + internal ODataTextStreamReader(Func reader) + { + Debug.Assert(reader != null, "reader cannot be null"); + this.reader = reader; + } + + public override int Read(char[] buffer, int offset, int count) + { + return reader(buffer, offset, count); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTypeAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTypeAnnotation.cs new file mode 100644 index 0000000..9f1ff15 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataTypeAnnotation.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Annotation which stores the EDM type information of a value. + /// + /// + /// This annotation will be used on ODataResource and ODataCollectionValue. + /// + public sealed class ODataTypeAnnotation + { + /// + /// Creates a new instance of the type annotation without a type name. + /// + public ODataTypeAnnotation() + { + } + + /// + /// Creates a new instance of the type annotation with a type name. + /// + /// The type name read from the input. + public ODataTypeAnnotation(string typeName) + { + this.TypeName = typeName; + } + + /// + /// Creates a new instance of the type annotation with a type. + /// + /// The type name read from the input. + /// The type read from the input. + internal ODataTypeAnnotation(string typeName, IEdmType type) + : this(typeName) + { + ExceptionUtils.CheckArgumentNotNull(type, "type"); + + this.Type = type; + } + + /// Gets the type name to serialize, for the annotated item. + /// The type name to serialize, for the annotated item. + /// + /// If this property is null, no type name will be written. + /// If this property is non-null, the property value will be used as the type name written to the payload. + /// If is present, it always overrides the type name specified on the annotated item. + /// If is not present, the value of the TypeName property on the ODataResource, ODataCollectionValue + /// is used as the type name in the payload. + /// + public string TypeName { get; private set; } + + /// + /// This property is redundant info about TypeName but to improve reader performance. + /// + internal IEdmType Type { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtils.cs new file mode 100644 index 0000000..e805551 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtils.cs @@ -0,0 +1,327 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Utility methods used with the OData library. + /// + public static class ODataUtils + { + /// String representation of the version 4.0 of the OData protocol. + private const string Version4NumberString = "4.0"; + + /// String representation of the version 4.01 of the OData protocol. + private const string Version401NumberString = "4.01"; + + /// Sets the content-type and OData-Version headers on the message used by the message writer. + /// The content-type and OData-Version headers on the message used by the message writer. + /// The message writer to set the headers for. + /// The kind of payload to be written with the message writer. + /// + /// This method can be called if it is important to set all the message headers before calling any of the + /// write methods on the . + /// If it is sufficient to set the headers when the write methods on the + /// are called, you don't have to call this method and setting the headers will happen automatically. + /// + public static ODataFormat SetHeadersForPayload(ODataMessageWriter messageWriter, ODataPayloadKind payloadKind) + { + ExceptionUtils.CheckArgumentNotNull(messageWriter, "messageWriter"); + + if (payloadKind == ODataPayloadKind.Unsupported) + { + throw new ArgumentException(Strings.ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind(payloadKind), "payloadKind"); + } + + return messageWriter.SetHeaders(payloadKind); + } + + /// Returns the format used by the message reader for reading the payload. + /// The format used by the messageReader for reading the payload. + /// The to get the read format from. + /// This method must only be called once reading has started. + /// This means that a read method has been called on the or that a reader (for entries, resource sets, collections, etc.) has been created. + /// If the method is called prior to that it will throw. + public static ODataFormat GetReadFormat(ODataMessageReader messageReader) + { + ExceptionUtils.CheckArgumentNotNull(messageReader, "messageReader"); + return messageReader.GetFormat(); + } + + + /// + /// Gets the reader behavior for null property value on the specified property. + /// + /// The model containing the annotation. + /// The property to check. + /// The behavior to use when reading null value for this property. + public static ODataNullValueBehaviorKind NullValueReadBehaviorKind(this IEdmModel model, IEdmProperty property) + { + ExceptionUtils.CheckArgumentNotNull(model, "model"); + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + ODataEdmPropertyAnnotation annotation = model.GetAnnotationValue(property); + return annotation == null ? ODataNullValueBehaviorKind.Default : annotation.NullValueReadBehaviorKind; + } + + /// + /// Adds a transient annotation to indicate how null values for the specified property should be read. + /// + /// The containing the annotations. + /// The to modify. + /// The new behavior for reading null values for this property. + public static void SetNullValueReaderBehavior(this IEdmModel model, IEdmProperty property, ODataNullValueBehaviorKind nullValueReadBehaviorKind) + { + ExceptionUtils.CheckArgumentNotNull(model, "model"); + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + ODataEdmPropertyAnnotation annotation = model.GetAnnotationValue(property); + if (annotation == null) + { + if (nullValueReadBehaviorKind != ODataNullValueBehaviorKind.Default) + { + annotation = new ODataEdmPropertyAnnotation + { + NullValueReadBehaviorKind = nullValueReadBehaviorKind + }; + model.SetAnnotationValue(property, annotation); + } + } + else + { + annotation.NullValueReadBehaviorKind = nullValueReadBehaviorKind; + } + } + + /// Displays the OData version to string representation. + /// The OData version. + /// The OData version. + public static string ODataVersionToString(ODataVersion version) + { + switch (version) + { + case ODataVersion.V4: + return Version4NumberString; + + case ODataVersion.V401: + return Version401NumberString; + + default: + // invalid enum value - unreachable. + throw new ODataException(Strings.ODataUtils_UnsupportedVersionNumber); + } + } + + /// Displays a string to OData version representation. + /// The OData version. + /// The OData version. + public static ODataVersion StringToODataVersion(string version) + { + // don't want to edit the string later. + string modifiedVersion = version; + + // version must not be null or empty + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(version, "version"); + + // removes the ";" and the user agent string from the version. + int ix = modifiedVersion.IndexOf(';'); + if (ix >= 0) + { + modifiedVersion = modifiedVersion.Substring(0, ix); + } + + switch (modifiedVersion.Trim()) + { + case Version4NumberString: + return ODataVersion.V4; + + case Version401NumberString: + return ODataVersion.V401; + + default: + // invalid version string + throw new ODataException(Strings.ODataUtils_UnsupportedVersionHeader(version)); + } + } + + /// + /// Translates the to a func that would evalutate whether the filter would match a given annotation name. + /// The func would evaluate to true if the matches the annotation name that's passed to the it, and false otherwise. + /// + /// + /// The filter string may be a comma delimited list of any of the following supported patterns: + /// "*" -- Matches all annotation names. + /// "ns.*" -- Matches all annotation names under the namespace "ns". + /// "ns.name" -- Matches only the annotation name "ns.name". + /// "-" -- The exclude operator may be used with any of the supported pattern, for example: + /// "-ns.*" -- Excludes all annotation names under the namespace "ns". + /// "-ns.name" -- Excludes only the annotation name "ns.name". + /// Null or empty filter is equivalent to "-*". + /// + /// The relative priority of the pattern is base on the relative specificity of the patterns being compared. If pattern1 is under the namespace pattern2, + /// pattern1 is more specific than pattern2 because pattern1 matches a subset of what pattern2 matches. We give higher priority to the pattern that is more specific. + /// For example: + /// "ns.*" has higher priority than "*" + /// "ns.name" has higher priority than "ns.*" + /// "ns1.name" has same priority as "ns2.*" + /// + /// Patterns with the exclude operator takes higher precedence than the same pattern without. + /// For example: "-ns.name" has higher priority than "ns.name". + /// + /// Examples: + /// "ns1.*,ns.name" -- Matches any annotation name under the "ns1" namespace and the "ns.name" annotation. + /// "*,-ns.*,ns.name" -- Matches any annotation name outside of the "ns" namespace and only "ns.name" under the "ns" namespace. + /// + /// Returns a func which would evaluate to true if the matches the annotation name that's passed to the it, + /// and false otherwise. + public static Func CreateAnnotationFilter(string annotationFilter) + { + AnnotationFilter filter = AnnotationFilter.Create(annotationFilter); + return filter.Matches; + } + + /// + /// Generate a default ODataServiceDocument instance from model. + /// + /// The Edm Model frm which to generate the service document. + /// The generated service document. + public static ODataServiceDocument GenerateServiceDocument(this IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + if (model.EntityContainer == null) + { + throw new ODataException(Strings.ODataUtils_ModelDoesNotHaveContainer); + } + + ODataServiceDocument serviceDocument = new ODataServiceDocument(); + serviceDocument.EntitySets = model.EntityContainer.EntitySets() + .Select(entitySet => new ODataEntitySetInfo() { Name = entitySet.Name, Title = entitySet.Name, Url = new Uri(entitySet.Name, UriKind.RelativeOrAbsolute) }).ToList(); + serviceDocument.Singletons = model.EntityContainer.Singletons() + .Select(singleton => new ODataSingletonInfo() { Name = singleton.Name, Title = singleton.Name, Url = new Uri(singleton.Name, UriKind.RelativeOrAbsolute) }).ToList(); + serviceDocument.FunctionImports = model.EntityContainer.OperationImports().OfType().Where(functionImport => functionImport.IncludeInServiceDocument && !functionImport.Function.Parameters.Any()) + .Select(functionImport => new ODataFunctionImportInfo() { Name = functionImport.Name, Title = functionImport.Name, Url = new Uri(functionImport.Name, UriKind.RelativeOrAbsolute) }).ToList(); + + return serviceDocument; + } + + /// + /// Append default values required by OData to specified HTTP header. + /// + /// When header name is ODataConstants.ContentTypeHeader: + /// If header value is application/json, append the following default values: + /// (odata.)metadata=minimal + /// (odata.)streaming=true + /// IEEE754Compatible=false + /// + /// The name of the header to append default values. + /// The original header value string. + /// The header value string with appended default values. + public static string AppendDefaultHeaderValue(string headerName, string headerValue) + { + return AppendDefaultHeaderValue(headerName, headerValue, ODataVersion.V4); + } + + /// + /// Append default values required by OData to specified HTTP header. + /// + /// When header name is ODataConstants.ContentTypeHeader, if header value is application/json + /// append the following default values for 4.0: + /// odata.metadata=minimal + /// odata.streaming=true + /// IEEE754Compatible=false + /// append the following default values for 4.01: + /// metadata=minimal + /// streaming=true + /// IEEE754Compatible=false + /// + /// The name of the header to append default values. + /// The original header value string. + /// The ODataVersion for which to create the default header value + /// The header value string with appended default values. + public static string AppendDefaultHeaderValue(string headerName, string headerValue, ODataVersion version) + { + if (string.CompareOrdinal(headerName, ODataConstants.ContentTypeHeader) != 0) + { + return headerValue; + } + + if (headerValue == null) + { + return null; + } + + var mediaTypeList = HttpUtils.MediaTypesFromString(headerValue); + var mediaType = mediaTypeList.Single().Key; + var encoding = HttpUtils.GetEncodingFromCharsetName(mediaTypeList.Single().Value); + + if (string.CompareOrdinal(mediaType.FullTypeName, MimeConstants.MimeApplicationJson) != 0) + { + return headerValue; + } + + var extendedParameters = new List>(); + var extendedMediaType = new ODataMediaType(mediaType.Type, mediaType.SubType, extendedParameters); + + var hasMetadata = false; + var hasStreaming = false; + var hasIeee754Compatible = false; + + if (mediaType.Parameters != null) + { + foreach (var parameter in mediaType.Parameters) + { + extendedParameters.Add(parameter); + + if (HttpUtils.IsMetadataParameter(parameter.Key)) + { + hasMetadata = true; + } + + if (HttpUtils.IsStreamingParameter(parameter.Key)) + { + hasStreaming = true; + } + + if (string.Compare(parameter.Key, MimeConstants.MimeIeee754CompatibleParameterName, StringComparison.OrdinalIgnoreCase) == 0) + { + hasIeee754Compatible = true; + } + } + } + + if (!hasMetadata) + { + extendedParameters.Add(new KeyValuePair( + version < ODataVersion.V401 ? MimeConstants.MimeMetadataParameterName : MimeConstants.MimeShortMetadataParameterName, + MimeConstants.MimeMetadataParameterValueMinimal)); + } + + if (!hasStreaming) + { + extendedParameters.Add(new KeyValuePair( + version < ODataVersion.V401 ? MimeConstants.MimeStreamingParameterName : MimeConstants.MimeShortStreamingParameterName, + MimeConstants.MimeParameterValueTrue)); + } + + if (!hasIeee754Compatible) + { + extendedParameters.Add(new KeyValuePair( + MimeConstants.MimeIeee754CompatibleParameterName, + MimeConstants.MimeParameterValueFalse)); + } + + return extendedMediaType.ToText(encoding); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtilsInternal.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtilsInternal.cs new file mode 100644 index 0000000..5a661e7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataUtilsInternal.cs @@ -0,0 +1,135 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Internal utility methods used in the OData library. + /// + internal static class ODataUtilsInternal + { + /// + /// Sets the 'OData-Version' HTTP header on the message based on the protocol version specified in the settings. + /// + /// The message to set the OData-Version header on. + /// The determining the protocol version to use. + internal static void SetODataVersion(ODataMessage message, ODataMessageWriterSettings settings) + { + Debug.Assert(message != null, "message != null"); + Debug.Assert(settings != null, "settings != null"); + Debug.Assert(settings.Version.HasValue, "settings.Version.HasValue"); + + string odataVersionString = ODataUtils.ODataVersionToString(settings.Version.Value); + message.SetHeader(ODataConstants.ODataVersionHeader, odataVersionString); + } + + /// + /// Reads the OData-Version header from the and parses it. + /// If no OData-Version header is found it sets the default version to be used for reading. + /// + /// The message to get the OData-Version header from. + /// The default version to use if the header was not specified. + /// + /// The retrieved from the OData-Version header of the message. + /// The default version if none is specified in the header. + /// + internal static ODataVersion GetODataVersion(ODataMessage message, ODataVersion defaultVersion) + { + Debug.Assert(message != null, "message != null"); + + string headerValue = message.GetHeader(ODataConstants.ODataVersionHeader); + + return string.IsNullOrEmpty(headerValue) + ? defaultVersion + : ODataUtils.StringToODataVersion(headerValue); + } + + /// + /// Checks whether a payload kind is supported in a request or a response. + /// + /// The to check. + /// true if the check is for a request; false for a response. + /// true if the is valid in a request or response respectively based on . + internal static bool IsPayloadKindSupported(ODataPayloadKind payloadKind, bool inRequest) + { + switch (payloadKind) + { + // These payload kinds are valid in requests and responses + case ODataPayloadKind.Value: + case ODataPayloadKind.BinaryValue: + case ODataPayloadKind.Batch: + case ODataPayloadKind.Resource: + case ODataPayloadKind.Property: + case ODataPayloadKind.EntityReferenceLink: + return true; + + // These payload kinds are only valid in responses + case ODataPayloadKind.ResourceSet: + case ODataPayloadKind.EntityReferenceLinks: + case ODataPayloadKind.Collection: + case ODataPayloadKind.ServiceDocument: + case ODataPayloadKind.MetadataDocument: + case ODataPayloadKind.Error: + case ODataPayloadKind.IndividualProperty: + case ODataPayloadKind.Delta: + case ODataPayloadKind.Asynchronous: + return !inRequest; + + // These payload kidns are only valid in requests + case ODataPayloadKind.Parameter: + return inRequest; + + // Anything else should never show up + default: + Debug.Assert(false, "Unsupported payload kind found: " + payloadKind.ToString()); + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataUtilsInternal_IsPayloadKindSupported_UnreachableCodePath)); + } + } + + /// + /// Concats two enumerables. + /// + /// Element type of the enumerable. + /// Enumerable 1 to concat. + /// Enumerable 2 to concat. + /// Returns the combined enumerable. + internal static IEnumerable ConcatEnumerables(IEnumerable enumerable1, IEnumerable enumerable2) + { + // Note that we allow null, instead of empty enumerable, to be returned here because the OData OM can return null for IE. + if (enumerable1 == null) + { + return enumerable2; + } + + if (enumerable2 == null) + { + return enumerable1; + } + + return enumerable1.Concat(enumerable2); + } + + /// + /// Returns true if this reference refers to a nullable type. + /// + /// Type reference. + /// This reference refers to a nullable type. + internal static bool IsNullable(this IEdmTypeReference type) + { + return type == null || type.IsNullable; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersion.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersion.cs new file mode 100644 index 0000000..6447c51 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersion.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Enumeration representing the OData protocol version. + /// + public enum ODataVersion + { + /// Version 4.0. + V4, + + /// Version 4.01. + V401, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersionCache.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersionCache.cs new file mode 100644 index 0000000..9edfd83 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataVersionCache.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Diagnostics; + + /// + /// Simple ODataVersion specific cache. + /// + /// The type of the item being cached. + internal sealed class ODataVersionCache + { + /// + /// Lazy constructing T for ODataVersion.V4. + /// + private readonly SimpleLazy v4; + + /// + /// Lazy constructing T for ODataVersion.V401. + /// + private readonly SimpleLazy v401; + + /// + /// Constructs an instance of the ODataVersionCache. + /// + /// The method to call to create a new instance of for a given ODataVersion. + internal ODataVersionCache(Func factory) + { + Debug.Assert(factory != null, "factory != null"); + + this.v4 = new SimpleLazy(() => factory(ODataVersion.V4), true /*isThreadSafe*/); + this.v401 = new SimpleLazy(() => factory(ODataVersion.V401), true /*isThreadSafe*/); + } + + /// + /// Indexer to get the cached item when given the ODataVersion. + /// + /// The ODataVersion to look up. + /// The cached item. + internal T this[ODataVersion version] + { + get + { + switch (version) + { + case ODataVersion.V4: + return this.v4.Value; + + case ODataVersion.V401: + return this.v401.Value; + + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataVersionCache_UnknownVersion)); + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriteStream.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriteStream.cs new file mode 100644 index 0000000..7bf097f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriteStream.cs @@ -0,0 +1,187 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.IO; +#if PORTABLELIB + using System.Threading; + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// A stream handed to clients from ODataBatchOperationMessage.GetStream or ODataBatchOperationMessage.GetStreamAsync, + /// or to write an inline stream value. + /// This stream communicates status changes to the owning writer (via IODataStreamListener) + /// to properly flush buffered data and move the writer's state machine forward. + /// + internal sealed class ODataWriteStream : ODataStream + { + /// The batch stream underlying this operation stream. + private Stream stream; + + /// + /// Constructor. + /// + /// The underlying stream to write the message to. + /// Listener interface to be notified of operation changes. + internal ODataWriteStream(Stream stream, IODataStreamListener listener) + : base(listener) + { + Debug.Assert(stream != null, "stream != null"); + this.stream = stream; + } + + /// + /// Determines if the stream can read - this one can't + /// + public override bool CanRead + { + get { return false; } + } + + /// + /// Determines if the stream can seek - this one can't + /// + public override bool CanSeek + { + get { return false; } + } + + /// + /// Determines if the stream can write - this one can + /// + public override bool CanWrite + { + get { return true; } + } + + /// + /// Returns the length of the stream. + /// + public override long Length + { + get + { + this.ValidateNotDisposed(); + return this.stream.Length; + } + } + + /// + /// Gets or sets the position in the stream. Setting of the position is not supported since the stream doesn't support seeking. + /// + public override long Position + { + get + { + this.ValidateNotDisposed(); + return this.stream.Position; + } + + set + { + throw new NotSupportedException(); + } + } + + /// + /// Sets the length of the stream. + /// + /// The length in bytes to set. + public override void SetLength(long value) + { + this.ValidateNotDisposed(); + this.stream.SetLength(value); + } + + /// + /// Writes to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + this.ValidateNotDisposed(); + this.stream.Write(buffer, offset, count); + } + +#if PORTABLELIB + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + this.ValidateNotDisposed(); + return this.stream.WriteAsync(buffer, offset, count, cancellationToken); + } +#else + /// + /// Writes to the stream. + /// + /// The buffer to get data from. + /// The offset in the buffer to start from. + /// The number of bytes to write. + /// The callback to be called when the asynchronous operation completes. + /// A custom state object to be associated with the asynchronous operation. + /// An for the asynchronous writing of the buffer to the stream. + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + this.ValidateNotDisposed(); + return this.stream.BeginWrite(buffer, offset, count, callback, state); + } + + /// + /// Finish the asynchronous write operation. + /// + /// The returned from BaginWrite. + public override void EndWrite(IAsyncResult asyncResult) + { + this.ValidateNotDisposed(); + this.stream.EndWrite(asyncResult); + } +#endif + + /// + /// Reads data from the stream. This operation is not supported by this stream. + /// + /// The buffer to read the data to. + /// The offset in the buffer to write to. + /// The number of bytes to read. + /// The number of bytes actually read. + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + /// Flush the stream to the underlying batch stream. + /// + public override void Flush() + { + this.ValidateNotDisposed(); + this.stream.Flush(); + } + + /// + /// Dispose the operation stream. + /// + /// If 'true' this method is called from user code; if 'false' it is called by the runtime. + protected override void Dispose(bool disposing) + { + if (disposing) + { + // NOTE: don't dispose the batch stream since this instance does not own it. + this.stream = null; + } + + base.Dispose(disposing); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriter.cs new file mode 100644 index 0000000..b66d0af --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriter.cs @@ -0,0 +1,419 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + + #endregion Namespaces + + /// + /// Base class for OData writers. + /// + public abstract class ODataWriter + { + /// Starts the writing of a resource set. + /// The resource set or collection to write. + public abstract void WriteStart(ODataResourceSet resourceSet); + + /// Writes a resource set. + /// The resource set or collection to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataResourceSet resourceSet) + { + WriteStart(resourceSet); + WriteEnd(); + return this; + } + + /// Writes a resource set and performs an action in-between. + /// The resource set or collection to write. + /// The action to perform in-between writing the resource set. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataResourceSet resourceSet, Action nestedAction) + { + WriteStart(resourceSet); + nestedAction(); + WriteEnd(); + return this; + } + +#if PORTABLELIB + /// Asynchronously start writing a resource set. + /// A task instance that represents the asynchronous write operation. + /// The resource set or collection to write. + public abstract Task WriteStartAsync(ODataResourceSet resourceSet); +#endif + + /// Starts the writing of a delta resource set. + /// The resource set or collection to write. + public virtual void WriteStart(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } + + /// Writes a delta resource set. + /// The delta resource set or collection to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataDeltaResourceSet deltaResourceSet) + { + WriteStart(deltaResourceSet); + WriteEnd(); + return this; + } + + /// Writes a delta resource set and performs an action in-between. + /// The delta resource set or collection to write. + /// The action to perform in-between writing the resource set. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataDeltaResourceSet deltaResourceSet, Action nestedAction) + { + WriteStart(deltaResourceSet); + nestedAction(); + WriteEnd(); + return this; + } + +#if PORTABLELIB + /// Asynchronously start writing a resource set. + /// A task instance that represents the asynchronous write operation. + /// The resource set or collection to write. + public virtual Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } +#endif + + /// Starts the writing of a resource. + /// The resource or item to write. + public abstract void WriteStart(ODataResource resource); + + /// Writes a resource. + /// The resource or item to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataResource resource) + { + WriteStart(resource); + WriteEnd(); + return this; + } + + /// Writes a resource and performs an action in-between. + /// The resource or item to write. + /// The action to perform in-between the writing. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataResource resource, Action nestedAction) + { + WriteStart(resource); + nestedAction(); + WriteEnd(); + return this; + } + + /// Writes a deleted resource. + /// The deleted resource to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataDeletedResource deletedResource) + { + WriteStart(deletedResource); + WriteEnd(); + return this; + } + + /// Writes a deleted resource and performs an action in-between. + /// The deletedresource to write. + /// The action to perform in-between the writing. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataDeletedResource deletedResource, Action nestedAction) + { + WriteStart(deletedResource); + nestedAction(); + WriteEnd(); + return this; + } + + /// Writes a delta link. + /// The delta link to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataDeltaLink deltaLink) + { + WriteDeltaLink(deltaLink); + return this; + } + + /// Writes a deleted link. + /// The delta deleted link to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataDeltaDeletedLink deltaDeletedLink) + { + WriteDeltaDeletedLink(deltaDeletedLink); + return this; + } + +#if PORTABLELIB + /// Asynchronously start writing a resource. + /// A task instance that represents the asynchronous write operation. + /// The resource or item to write. + public abstract Task WriteStartAsync(ODataResource resource); +#endif + + + /// Starts writing a deleted resource. + /// The deleted resource to write. + public virtual void WriteStart(ODataDeletedResource deletedResource) + { + throw new NotImplementedException(); + } + +#if PORTABLELIB + /// + /// Asynchronously writing a delta deleted resource. + /// + /// The deleted resource to write. + /// A task instance that represents the asynchronous write operation. + public virtual Task WriteStartAsync(ODataDeletedResource deletedResource) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStart(deletedResource)); + } + +#endif + + /// + /// Write a delta link. + /// + /// The delta link to write. + public virtual void WriteDeltaLink(ODataDeltaLink deltaLink) + { + throw new NotImplementedException(); + } + +#if PORTABLELIB + /// + /// Asynchronously writing a delta link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public virtual Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteDeltaLink(deltaLink)); + } + +#endif + + /// + /// Write a delta deleted link. + /// + /// The delta deleted link to write. + public virtual void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaDeletedLink) + { + throw new NotImplementedException(); + } + +#if PORTABLELIB + /// + /// Asynchronously write a delta deleted link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public virtual Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaDeletedLink) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteDeltaDeletedLink(deltaDeletedLink)); + } + +#endif + + /// Starts the writing of a nested resource info. + /// The nested resource info to write. + public abstract void WriteStart(ODataNestedResourceInfo nestedResourceInfo); + + /// Writes a nested resource info. + /// The nested resource info to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataNestedResourceInfo nestedResourceInfo) + { + WriteStart(nestedResourceInfo); + WriteEnd(); + return this; + } + + /// Writes a nested resource info and performs an action in-between. + /// The nested resource info to write. + /// The action to perform in-between the writing. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataNestedResourceInfo nestedResourceInfo, Action nestedAction) + { + WriteStart(nestedResourceInfo); + nestedAction(); + WriteEnd(); + return this; + } + +#if PORTABLELIB + /// Asynchronously start writing a nested resource info. + /// A task instance that represents the asynchronous write operation. + /// The nested resource info to writer. + public abstract Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo); +#endif + + /// Writes a primitive value within an untyped collection. + /// The primitive value to write. + public virtual void WritePrimitive(ODataPrimitiveValue primitiveValue) + { + throw new NotImplementedException(); + } + + /// Writes a primitive value within an untyped collection. + /// The primitive value to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter Write(ODataPrimitiveValue primitiveValue) + { + WritePrimitive(primitiveValue); + return this; + } + +#if PORTABLELIB + /// Asynchronously write a primitive value within an untyped collection. + /// A task instance that represents the asynchronous write operation. + /// The primitive value to write. + public virtual Task WritePrimitiveAsync(ODataPrimitiveValue primitiveValue) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.WritePrimitive(primitiveValue)); + } +#endif + + /// Writes a primitive property within a resource. + /// The primitive property to write. + public virtual void WriteStart(ODataPropertyInfo primitiveProperty) + { + throw new NotImplementedException(); + } + + /// Writes a primitive property within a resource. + /// The primitive property to write. + /// This ODataWriter, allowing for chaining operations. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] + public ODataWriter Write(ODataProperty primitiveProperty) + { + WriteStart(primitiveProperty); + WriteEnd(); + return this; + } + + /// Writes a primitive property within a resource. + /// The primitive property to write. + /// The action to perform in-between the writing. + /// This ODataWriter, allowing for chaining operations. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] + public ODataWriter Write(ODataProperty primitiveProperty, Action nestedAction) + { + WriteStart(primitiveProperty); + nestedAction(); + WriteEnd(); + return this; + } + +#if PORTABLELIB + /// Asynchronously write a primitive property within a resource. + /// A task instance that represents the asynchronous write operation. + /// The primitive property to write. + public virtual Task WriteStartAsync(ODataProperty primitiveProperty) + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStart(primitiveProperty)); + } +#endif + + /// Creates a stream for writing a binary value. + /// A stream to write a binary value to. + public virtual Stream CreateBinaryWriteStream() + { + throw new NotImplementedException(); + } + + /// Creates a stream for writing a binary value. + /// The stream to write. + /// This ODataWriter, allowing for chaining operations. + public ODataWriter WriteStream(ODataBinaryStreamValue stream) + { + Stream writeStream = this.CreateBinaryWriteStream(); + stream.Stream.CopyTo(writeStream); + writeStream.Flush(); + writeStream.Dispose(); + return this; + } + +#if PORTABLELIB + /// Asynchronously creates a stream for writing a binary value. + /// A stream to write a binary value to. + public virtual Task CreateBinaryWriteStreamAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateBinaryWriteStream()); + } +#endif + + /// Creates a TextWriter for writing a string value. + /// A TextWriter to write a string value. + public virtual TextWriter CreateTextWriter() + { + throw new NotImplementedException(); + } + +#if PORTABLELIB + /// Asynchronously creates a TextWriter for writing a string value. + /// A TextWriter to write a string value. + public virtual Task CreateTextWriterAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateTextWriter()); + } +#endif + + /// Finishes the writing of a resource set, a resource, or a nested resource info. + public abstract void WriteEnd(); + +#if PORTABLELIB + /// Asynchronously finish writing a resource set, resource, or nested resource info. + /// A task instance that represents the asynchronous write operation. + public abstract Task WriteEndAsync(); +#endif + + /// Writes an entity reference link, which is used to represent binding to an existing resource in a request payload. + /// The entity reference link to write. + /// + /// This method can only be called for writing request messages. The entity reference link must be surrounded + /// by a nested resource info written through WriteStart/WriteEnd. + /// The will be ignored in that case and the Uri from the will be used + /// as the binding URL to be written. + /// + public abstract void WriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink); + +#if PORTABLELIB + /// Asynchronously writes an entity reference link, which is used to represent binding to an existing resource in a request payload. + /// A task instance that represents the asynchronous write operation. + /// The entity reference link to write. + /// + /// This method can only be called for writing request messages. The entity reference link must be surrounded + /// by a nested resource info written through WriteStart/WriteEnd. + /// The will be ignored in that case and the Uri from the will be used + /// as the binding URL to be written. + /// + public abstract Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink entityReferenceLink); +#endif + + /// Flushes the write buffer to the underlying stream. + public abstract void Flush(); + +#if PORTABLELIB + /// Flushes the write buffer to the underlying stream asynchronously. + /// A task instance that represents the asynchronous operation. + public abstract Task FlushAsync(); +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriterCore.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriterCore.cs new file mode 100644 index 0000000..43f75aa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ODataWriterCore.cs @@ -0,0 +1,3462 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Linq; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +#if PORTABLELIB +using System.Threading.Tasks; +#endif +using Microsoft.OData.Evaluation; +using Microsoft.OData.UriParser; +using Microsoft.OData.Edm; +using Microsoft.OData.Metadata; + +namespace Microsoft.OData +{ + /// + /// Base class for OData writers that verifies a proper sequence of write calls on the writer. + /// + internal abstract class ODataWriterCore : ODataWriter, IODataOutputInStreamErrorListener, IODataStreamListener + { + /// The writer validator to use. + protected readonly IWriterValidator WriterValidator; + + /// The output context to write to. + private readonly ODataOutputContext outputContext; + + /// True if the writer was created for writing a resourceSet; false when it was created for writing a resource. + private readonly bool writingResourceSet; + + /// True if the writer was created for writing a delta response; false otherwise. + private readonly bool writingDelta; + + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + private readonly IODataReaderWriterListener listener; + + /// Stack of writer scopes to keep track of the current context of the writer. + private readonly ScopeStack scopeStack = new ScopeStack(); + + /// The number of entries which have been started but not yet ended. + private int currentResourceDepth; + + /// + /// Constructor. + /// + /// The output context to write to. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// True if the writer is created for writing a resourceSet; false when it is created for writing a resource. + /// True if the writer is created for writing a delta response; false otherwise. + /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer. + protected ODataWriterCore( + ODataOutputContext outputContext, + IEdmNavigationSource navigationSource, + IEdmStructuredType resourceType, + bool writingResourceSet, + bool writingDelta = false, + IODataReaderWriterListener listener = null) + { + Debug.Assert(outputContext != null, "outputContext != null"); + + this.outputContext = outputContext; + this.writingResourceSet = writingResourceSet; + this.writingDelta = writingDelta; + this.WriterValidator = outputContext.WriterValidator; + this.Version = outputContext.MessageWriterSettings.Version; + + if (navigationSource != null && resourceType == null) + { + resourceType = this.outputContext.EdmTypeResolver.GetElementType(navigationSource); + } + + ODataUri odataUri = outputContext.MessageWriterSettings.ODataUri.Clone(); + + // Remove key for top level resource + if (!writingResourceSet && odataUri != null && odataUri.Path != null) + { + odataUri.Path = odataUri.Path.TrimEndingKeySegment(); + } + + this.listener = listener; + + this.scopeStack.Push(new Scope(WriterState.Start, /*item*/null, navigationSource, resourceType, /*skipWriting*/false, outputContext.MessageWriterSettings.SelectedProperties, odataUri)); + this.CurrentScope.DerivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource); + } + + /// + /// An enumeration representing the current state of the writer. + /// + internal enum WriterState + { + /// The writer is at the start; nothing has been written yet. + Start, + + /// The writer is currently writing a resource. + Resource, + + /// The writer is currently writing a resourceSet. + ResourceSet, + + /// The writer is currently writing a delta resource set. + DeltaResourceSet, + + /// The writer is currently writing a deleted resource. + DeletedResource, + + /// The writer is currently writing a delta link. + DeltaLink, + + /// The writer is currently writing a delta deleted link. + DeltaDeletedLink, + + /// The writer is currently writing a nested resource info (possibly an expanded link but we don't know yet). + /// + /// This state is used when a nested resource info was started but we didn't see any children for it yet. + /// + NestedResourceInfo, + + /// The writer is currently writing a nested resource info with content. + /// + /// This state is used when a nested resource info with either an entity reference link or expanded resourceSet/resource was written. + /// + NestedResourceInfoWithContent, + + /// The writer is currently writing a primitive value. + Primitive, + + /// The writer is currently writing a single property. + Property, + + /// The writer is currently writing a stream value. + Stream, + + /// The writer is currently writing a string value. + String, + + /// The writer has completed; nothing can be written anymore. + Completed, + + /// The writer is in error state; nothing can be written anymore. + Error + } + + /// + /// OData Version being written. + /// + internal ODataVersion? Version { get; } + + /// + /// The current scope for the writer. + /// + protected Scope CurrentScope + { + get + { + Debug.Assert(this.scopeStack.Count > 0, "We should have at least one active scope all the time."); + return this.scopeStack.Peek(); + } + } + + /// + /// The current state of the writer. + /// + protected WriterState State + { + get + { + return this.CurrentScope.State; + } + } + + /// + /// true if the writer should not write any input specified and should just skip it. + /// + protected bool SkipWriting + { + get + { + return this.CurrentScope.SkipWriting; + } + } + + /// + /// A flag indicating whether the writer is at the top level. + /// + protected bool IsTopLevel + { + get + { + Debug.Assert(this.State != WriterState.Start && this.State != WriterState.Completed, "IsTopLevel should only be called while writing the payload."); + + // there is the root scope at the top (when the writer has not started or has completed) + // and then the top-level scope (the top-level resource/resourceSet item) as the second scope on the stack + return this.scopeStack.Count == 2; + } + } + + /// + /// The scope level the writer is writing. + /// + protected int ScopeLevel + { + get { return this.scopeStack.Count; } + } + + /// + /// Returns the immediate parent link which is being expanded, or null if no such link exists + /// + protected ODataNestedResourceInfo ParentNestedResourceInfo + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfo should only be called while writing a resource or a resourceSet."); + + Scope linkScope = this.scopeStack.ParentOrNull; + return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo); + } + } + + /// + /// Returns the nested info that current resource belongs to. + /// + protected ODataNestedResourceInfo BelongingNestedResourceInfo + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.ResourceSet || this.State == WriterState.DeletedResource || this.State == WriterState.DeltaResourceSet, "BelongingNestedResourceInfo should only be called while writing a (deleted) resource or a (delta) resourceSet."); + + Scope linkScope = this.scopeStack.ParentOrNull; + + // For single navigation + if (linkScope is NestedResourceInfoScope) + { + return linkScope.Item as ODataNestedResourceInfo; + } + else if (linkScope is ResourceSetBaseScope) + { + // For resource under collection of navigation/complex, parent is ResourceSetScope, so we need find parent of parent. + linkScope = this.scopeStack.ParentOfParent; + return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo); + } + else + { + return null; + } + } + } + + /// + /// Returns the resource type of the immediate parent resource for which a nested resource info is being written. + /// + protected IEdmStructuredType ParentResourceType + { + get + { + Debug.Assert( + this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent, + "ParentResourceType should only be called while writing a nested resource info (with or without content), or within an untyped ResourceSet."); + Scope resourceScope = this.scopeStack.Parent; + return resourceScope.ResourceType; + } + } + + /// + /// Returns the navigation source of the immediate parent resource for which a nested resource info is being written. + /// + protected IEdmNavigationSource ParentResourceNavigationSource + { + get + { + Scope resourceScope = this.scopeStack.Parent; + return resourceScope == null ? null : resourceScope.NavigationSource; + } + } + + /// + /// Returns the parent scope of current scope. + /// + protected Scope ParentScope + { + get + { + Debug.Assert(this.scopeStack.Count > 1); + return this.scopeStack.Scopes.Skip(1).First(); + } + } + + /// + /// Returns the number of items seen so far on the current resource set scope. + /// + /// Can only be accessed on a resource set scope. + protected int ResourceSetScopeResourceCount + { + get + { + Debug.Assert(this.State == WriterState.ResourceSet, "ResourceSetScopeResourceCount should only be called while writing a resource set."); + return ((ResourceSetBaseScope)this.CurrentScope).ResourceCount; + } + } + + /// + /// Checker to detect duplicate property names. + /// + protected IDuplicatePropertyNameChecker DuplicatePropertyNameChecker + { + get + { + Debug.Assert( + this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent || this.State == WriterState.Property, + "PropertyAndAnnotationCollector should only be called while writing a resource or an (expanded or deferred) nested resource info."); + + ResourceBaseScope resourceScope; + switch (this.State) + { + case WriterState.DeletedResource: + case WriterState.Resource: + resourceScope = (ResourceBaseScope)this.CurrentScope; + break; + case WriterState.Property: + case WriterState.NestedResourceInfo: + case WriterState.NestedResourceInfoWithContent: + resourceScope = (ResourceBaseScope)this.scopeStack.Parent; + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_PropertyAndAnnotationCollector)); + } + + return resourceScope.DuplicatePropertyNameChecker; + } + } + + /// + /// The structured type of the current resource. + /// + protected IEdmStructuredType ResourceType + { + get + { + return this.CurrentScope.ResourceType; + } + } + + /// + /// Returns the parent nested resource info scope of a resource in an expanded link (if it exists). + /// The resource can either be the content of the expanded link directly or nested inside a resourceSet. + /// + /// The parent navigation scope of a resource in an expanded link (if it exists). + protected NestedResourceInfoScope ParentNestedResourceInfoScope + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfoScope should only be called while writing a resource or a resourceSet."); + Debug.Assert(this.scopeStack.Count >= 2, "We should have at least the resource scope and the start scope on the stack."); + + Scope parentScope = this.scopeStack.Parent; + if (parentScope.State == WriterState.Start) + { + // Top-level resource. + return null; + } + + if (parentScope.State == WriterState.ResourceSet || parentScope.State == WriterState.DeltaResourceSet) + { + Debug.Assert(this.scopeStack.Count >= 3, "We should have at least the resource scope, the resourceSet scope and the start scope on the stack."); + + // Get the resourceSet's parent + parentScope = this.scopeStack.ParentOfParent; + if (parentScope.State == WriterState.Start || + (parentScope.State == WriterState.ResourceSet && + parentScope.ResourceType != null && + parentScope.ResourceType.TypeKind == EdmTypeKind.Untyped)) + { + // Top-level resourceSet, or resourceSet within an untyped resourceSet. + return null; + } + } + + if (parentScope.State == WriterState.NestedResourceInfoWithContent) + { + // Get the scope of the nested resource info + return (NestedResourceInfoScope)parentScope; + } + + // The parent scope of a resource can only be a resourceSet or an expanded nav link + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ParentNestedResourceInfoScope)); + } + } + + /// + /// Validator to validate consistency of collection items (or null if no such validator applies to the current scope). + /// + private ResourceSetWithoutExpectedTypeValidator CurrentResourceSetValidator + { + get + { + Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.Primitive, "CurrentCollectionValidator should only be called while writing a resource."); + + ResourceSetBaseScope resourceSetScope = this.ParentScope as ResourceSetBaseScope; + return resourceSetScope == null ? null : resourceSetScope.ResourceTypeValidator; + } + } + + /// + /// Flushes the write buffer to the underlying stream. + /// + public sealed override void Flush() + { + this.VerifyCanFlush(true); + + // Make sure we switch to writer state FatalExceptionThrown if an exception is thrown during flushing. + try + { + this.FlushSynchronously(); + } + catch + { + this.EnterScope(WriterState.Error, null); + throw; + } + } + +#if PORTABLELIB + /// + /// Asynchronously flushes the write buffer to the underlying stream. + /// + /// A task instance that represents the asynchronous operation. + public sealed override Task FlushAsync() + { + this.VerifyCanFlush(false); + + // Make sure we switch to writer state Error if an exception is thrown during flushing. + return this.FlushAsynchronously().FollowOnFaultWith(t => this.EnterScope(WriterState.Error, null)); + } +#endif + + /// + /// Start writing a resourceSet. + /// + /// Resource Set/collection to write. + public sealed override void WriteStart(ODataResourceSet resourceSet) + { + this.VerifyCanWriteStartResourceSet(true, resourceSet); + this.WriteStartResourceSetImplementation(resourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a resourceSet. + /// + /// Resource Set/collection to write. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteStartAsync(ODataResourceSet resourceSet) + { + this.VerifyCanWriteStartResourceSet(false, resourceSet); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartResourceSetImplementation(resourceSet)); + } +#endif + + /// + /// Start writing a delta resource Set. + /// + /// Resource Set/collection to write. + public sealed override void WriteStart(ODataDeltaResourceSet deltaResourceSet) + { + this.VerifyCanWriteStartDeltaResourceSet(true, deltaResourceSet); + this.WriteStartDeltaResourceSetImplementation(deltaResourceSet); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a delta resourceSet. + /// + /// Resource Set/collection to write. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet) + { + this.VerifyCanWriteStartDeltaResourceSet(false, deltaResourceSet); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartDeltaResourceSetImplementation(deltaResourceSet)); + } +#endif + + /// + /// Start writing a resource. + /// + /// Resource/item to write. + public sealed override void WriteStart(ODataResource resource) + { + this.VerifyCanWriteStartResource(true, resource); + this.WriteStartResourceImplementation(resource); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a resource. + /// + /// Resource/item to write. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteStartAsync(ODataResource resource) + { + this.VerifyCanWriteStartResource(false, resource); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartResourceImplementation(resource)); + } +#endif + + /// + /// Start writing a delta deleted resource. + /// + /// The delta deleted resource to write. + public sealed override void WriteStart(ODataDeletedResource deletedResource) + { + this.VerifyCanWriteStartDeletedResource(true, deletedResource); + this.WriteStartDeletedResourceImplementation(deletedResource); + } + +#if PORTABLELIB + /// + /// Asynchronously write a delta deleted resource. + /// + /// The delta deleted resource to write. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteStartAsync(ODataDeletedResource deletedResource) + { + this.VerifyCanWriteStartDeletedResource(false, deletedResource); + return TaskUtils.GetTaskForSynchronousOperation(() => + { + this.WriteStartDeletedResourceImplementation(deletedResource); + }); + } +#endif + + /// + /// Writing a delta link. + /// + /// The delta link to write. + public override void WriteDeltaLink(ODataDeltaLink deltaLink) + { + this.VerifyCanWriteLink(true, deltaLink); + this.WriteDeltaLinkImplementation(deltaLink); + } + +#if PORTABLELIB + /// + /// Asynchronously writing a delta link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink) + { + this.VerifyCanWriteLink(false, deltaLink); + return TaskUtils.GetTaskForSynchronousOperation(() => + { + this.WriteDeltaLinkImplementation(deltaLink); + }); + } +#endif + + /// + /// Writing a delta deleted link. + /// + /// The delta link to write. + public override void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaLink) + { + this.VerifyCanWriteLink(true, deltaLink); + this.WriteDeltaLinkImplementation(deltaLink); + } + +#if PORTABLELIB + /// + /// Asynchronously writing a delta link. + /// + /// The delta link to write. + /// A task instance that represents the asynchronous write operation. + public override Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaLink) + { + this.VerifyCanWriteLink(false, deltaLink); + return TaskUtils.GetTaskForSynchronousOperation(() => + { + this.WriteDeltaLinkImplementation(deltaLink); + }); + } +#endif + + /// + /// Write a primitive value within an untyped collection. + /// + /// Primitive value to write. + public sealed override void WritePrimitive(ODataPrimitiveValue primitiveValue) + { + this.VerifyCanWritePrimitive(true, primitiveValue); + this.WritePrimitiveValueImplementation(primitiveValue); + } + +#if PORTABLELIB + /// + /// Asynchronously write a primitive value. + /// + /// Primitive value to write. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WritePrimitiveAsync(ODataPrimitiveValue primitiveValue) + { + this.VerifyCanWritePrimitive(false, primitiveValue); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WritePrimitiveValueImplementation(primitiveValue)); + } +#endif + + /// Writes a primitive property within a resource. + /// The primitive property to write. + public sealed override void WriteStart(ODataPropertyInfo primitiveProperty) + { + this.VerifyCanWriteProperty(true, primitiveProperty); + this.WriteStartPropertyImplementation(primitiveProperty); + } + +#if PORTABLELIB + /// Asynchronously write a primitive property within a resource. + /// A task instance that represents the asynchronous write operation. + /// The primitive property to write. + public sealed override Task WriteStartAsync(ODataProperty primitiveProperty) + { + this.VerifyCanWriteProperty(false, primitiveProperty); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartPropertyImplementation(primitiveProperty)); + } +#endif + + /// Creates a stream for writing a binary value. + /// A stream to write a binary value to. + public sealed override Stream CreateBinaryWriteStream() + { + this.VerifyCanCreateWriteStream(true); + return this.CreateWriteStreamImplementation(); + } + +#if PORTABLELIB + /// Asynchronously creates a stream for writing a binary value. + /// A stream to write a binary value to. + public sealed override Task CreateBinaryWriteStreamAsync() + { + this.VerifyCanCreateWriteStream(false); + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateWriteStreamImplementation()); + } +#endif + + /// Creates a TextWriter for writing a string value. + /// A TextWriter to write a string value to. + public sealed override TextWriter CreateTextWriter() + { + this.VerifyCanCreateTextWriter(true); + return this.CreateTextWriterImplementation(); + } + +#if PORTABLELIB + /// Asynchronously creates a stream for writing a binary value. + /// A stream to write a binary value to. + public sealed override Task CreateTextWriterAsync() + { + this.VerifyCanCreateWriteStream(false); + return TaskUtils.GetTaskForSynchronousOperation(() => this.CreateTextWriterImplementation()); + } +#endif + + /// + /// Start writing a nested resource info. + /// + /// Navigation link to write. + public sealed override void WriteStart(ODataNestedResourceInfo nestedResourceInfo) + { + this.VerifyCanWriteStartNestedResourceInfo(true, nestedResourceInfo); + this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo); + } + +#if PORTABLELIB + /// + /// Asynchronously start writing a nested resource info. + /// + /// Navigation link to writer. + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo) + { + this.VerifyCanWriteStartNestedResourceInfo(false, nestedResourceInfo); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo)); + } +#endif + + /// + /// Finish writing a resourceSet/resource/nested resource info. + /// + public sealed override void WriteEnd() + { + this.VerifyCanWriteEnd(true); + this.WriteEndImplementation(); + if (this.CurrentScope.State == WriterState.Completed) + { + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + this.Flush(); + } + } + +#if PORTABLELIB + /// + /// Asynchronously finish writing a resourceSet/resource/nested resource info. + /// + /// A task instance that represents the asynchronous write operation. + public sealed override Task WriteEndAsync() + { + this.VerifyCanWriteEnd(false); + return TaskUtils.GetTaskForSynchronousOperation(this.WriteEndImplementation) + .FollowOnSuccessWithTask( + task => + { + if (this.CurrentScope.State == WriterState.Completed) + { + // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. + return this.FlushAsync(); + } + else + { + return TaskUtils.CompletedTask; + } + }); + } +#endif + + /// + /// Writes an entity reference link, which is used to represent binding to an existing resource in a request payload. + /// + /// The entity reference link to write. + /// + /// This method can only be called for writing request messages. The entity reference link must be surrounded + /// by a navigation link written through WriteStart/WriteEnd. + /// The will be ignored in that case and the Uri from the will be used + /// as the binding URL to be written. + /// + public sealed override void WriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink) + { + this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, true); + this.WriteEntityReferenceLinkImplementation(entityReferenceLink); + } + +#if PORTABLELIB + /// + /// Asynchronously writes an entity reference link, which is used to represent binding to an existing resource in a request payload. + /// + /// The entity reference link to write. + /// A task instance that represents the asynchronous write operation. + /// + /// This method can only be called for writing request messages. The entity reference link must be surrounded + /// by a navigation link written through WriteStart/WriteEnd. + /// The will be ignored in that case and the Uri from the will be used + /// as the binding URL to be written. + /// + public sealed override Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink entityReferenceLink) + { + this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, false); + return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteEntityReferenceLinkImplementation(entityReferenceLink)); + } +#endif + + /// + /// This method notifies the listener, that an in-stream error is to be written. + /// + /// + /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position. + /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload. + /// + void IODataOutputInStreamErrorListener.OnInStreamError() + { + this.VerifyNotDisposed(); + + // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might + // introduce another top-level element in XML) + if (this.State == WriterState.Completed) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString())); + } + + this.StartPayloadInStartState(); + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + /// + /// This method is called when a stream is requested. It is a no-op. + /// + void IODataStreamListener.StreamRequested() + { + } + +#if PORTABLELIB + /// + /// This method is called when an async stream is requested. It is a no-op. + /// + /// A task for method called when a stream is requested. + Task IODataStreamListener.StreamRequestedAsync() + { + return TaskUtils.GetTaskForSynchronousOperation(() => ((IODataStreamListener)this).StreamRequested()); + } +#endif + + /// + /// This method is called when a stream is disposed. + /// + void IODataStreamListener.StreamDisposed() + { + Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String, "Stream was disposed when not in WriterState.Stream state."); + + // Complete writing the stream + if (this.State == WriterState.Stream) + { + this.EndBinaryStream(); + } + else if (this.State == WriterState.String) + { + this.EndTextWriter(); + } + + this.LeaveScope(); + } + + /// + /// Get instance of the parent resource scope + /// + /// + /// The parent resource scope + /// Or null if there is no parent resource scope + /// + protected ResourceScope GetParentResourceScope() + { + ScopeStack scopeStack = new ScopeStack(); + Scope parentResourceScope = null; + + if (this.scopeStack.Count > 0) + { + // pop current scope and push into scope stack + scopeStack.Push(this.scopeStack.Pop()); + } + + while (this.scopeStack.Count > 0) + { + Scope scope = this.scopeStack.Pop(); + scopeStack.Push(scope); + + if (scope is ResourceScope) + { + parentResourceScope = scope; + break; + } + } + + while (scopeStack.Count > 0) + { + Scope scope = scopeStack.Pop(); + this.scopeStack.Push(scope); + } + + return parentResourceScope as ResourceScope; + } + + /// + /// Determines whether a given writer state is considered an error state. + /// + /// The writer state to check. + /// True if the writer state is an error state; otherwise false. + protected static bool IsErrorState(WriterState state) + { + return state == WriterState.Error; + } + + /// + /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object + /// has already been disposed. + /// + protected abstract void VerifyNotDisposed(); + + /// + /// Flush the output. + /// + protected abstract void FlushSynchronously(); + +#if PORTABLELIB + /// + /// Flush the output. + /// + /// Task representing the pending flush operation. + protected abstract Task FlushAsynchronously(); +#endif + + /// + /// Start writing an OData payload. + /// + protected abstract void StartPayload(); + + /// + /// Start writing a resource. + /// + /// The resource to write. + protected abstract void StartResource(ODataResource resource); + + /// + /// Finish writing a resource. + /// + /// The resource to write. + protected abstract void EndResource(ODataResource resource); + + /// + /// Start writing a single property. + /// + /// The property to write. + protected virtual void StartProperty(ODataPropertyInfo property) + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a property. + /// + /// The property to write. + protected virtual void EndProperty(ODataPropertyInfo property) + { + throw new NotImplementedException(); + } + + /// + /// Start writing a resourceSet. + /// + /// The resourceSet to write. + protected abstract void StartResourceSet(ODataResourceSet resourceSet); + + /// + /// Start writing a delta resource set. + /// + /// The delta resource set to write. + protected virtual void StartDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } + + /// + /// Start writing a deleted resource. + /// + /// The deleted entry to write. + protected virtual void StartDeletedResource(ODataDeletedResource deletedEntry) + { + throw new NotImplementedException(); + } + + /// + /// Write a delta link or delta deleted link. + /// + /// The deleted entry to write. + protected virtual void StartDeltaLink(ODataDeltaLinkBase deltaLink) + { + throw new NotImplementedException(); + } + + /// + /// Create a stream to write a binary value. + /// + /// A stream for writing the binary value. + protected virtual Stream StartBinaryStream() + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a stream. + /// + protected virtual void EndBinaryStream() + { + throw new NotImplementedException(); + } + + /// + /// Create a TextWriter to write a string value. + /// + /// A TextWriter for writing the string value. + protected virtual TextWriter StartTextWriter() + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a string value. + /// + protected virtual void EndTextWriter() + { + throw new NotImplementedException(); + } + + /// + /// Finish writing an OData payload. + /// + protected abstract void EndPayload(); + + /// + /// Finish writing a resourceSet. + /// + /// The resourceSet to write. + protected abstract void EndResourceSet(ODataResourceSet resourceSet); + + /// + /// Finish writing a delta resource set. + /// + /// The delta resource set to write. + protected virtual void EndDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet) + { + throw new NotImplementedException(); + } + + /// + /// Finish writing a deleted resource. + /// + /// The delta resource set to write. + protected virtual void EndDeletedResource(ODataDeletedResource deletedResource) + { + throw new NotImplementedException(); + } + + /// + /// Write a primitive value within an untyped collection. + /// + /// The primitive value to write. + protected virtual void WritePrimitiveValue(ODataPrimitiveValue primitiveValue) + { + throw new NotImplementedException(); + } + + /// + /// Write a deferred (non-expanded) nested resource info. + /// + /// The nested resource info to write. + protected abstract void WriteDeferredNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Start writing a nested resource info with content. + /// + /// The nested resource info to write. + protected abstract void StartNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Finish writing a nested resource info with content. + /// + /// The nested resource info to write. + protected abstract void EndNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo); + + /// + /// Write an entity reference link into a navigation link content. + /// + /// The parent navigation link which is being written around the entity reference link. + /// The entity reference link to write. + protected abstract void WriteEntityReferenceInNavigationLinkContent(ODataNestedResourceInfo parentNestedResourceInfo, ODataEntityReferenceLink entityReferenceLink); + + /// + /// Create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource set is for an undeclared property + /// The newly create scope. + protected abstract ResourceSetScope CreateResourceSetScope(ODataResourceSet resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared); + + /// + /// Create a new delta resource set scope. + /// + /// The delta resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource set is for an undeclared property + /// The newly create scope. + protected virtual DeltaResourceSetScope CreateDeltaResourceSetScope(ODataDeltaResourceSet deltaResourceSet, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) + { + throw new NotImplementedException(); + } + + /// + /// Create a new resource scope. + /// + /// The resource for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + /// The newly create scope. + protected abstract ResourceScope CreateResourceScope(ODataResource resource, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared); + + /// + /// Create a new resource scope. + /// + /// The (deleted) resource for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// true if the resource is for an undeclared property + /// The newly create scope. + protected virtual DeletedResourceScope CreateDeletedResourceScope(ODataDeletedResource resource, IEdmNavigationSource navigationSource, IEdmEntityType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared) + { + throw new NotImplementedException(); + } + + /// + /// Create a new property scope. + /// + /// The property for the new scope. + /// The navigation source. + /// The structured type for the resource containing the property to be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly created property scope. + protected virtual PropertyInfoScope CreatePropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + throw new NotImplementedException(); + } + + /// + /// Create a new delta link scope. + /// + /// The link for the new scope. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly create scope. + protected virtual DeltaLinkScope CreateDeltaLinkScope(ODataDeltaLinkBase link, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + throw new NotImplementedException(); + } + + /// + /// Gets the serialization info for the given resource. + /// + /// The resource to get the serialization info for. + /// The serialization info for the given resource. + protected ODataResourceSerializationInfo GetResourceSerializationInfo(ODataResourceBase resource) + { + // Need to check for null for the resource since we can be writing a null reference to a navigation property. + ODataResourceSerializationInfo serializationInfo = resource == null ? null : resource.SerializationInfo; + + // Always try to use the serialization info from the resource first. If it is not found on the resource, use the one inherited from the parent resourceSet. + // Note that we don't try to guard against inconsistent serialization info between entries and their parent resourceSet. + if (serializationInfo != null) + { + return serializationInfo; + } + + ODataResourceSetBase resourceSet = this.CurrentScope.Item as ODataResourceSetBase; + if (resourceSet != null) + { + return resourceSet.SerializationInfo; + } + + return null; + } + + /// + /// Gets the serialization info for the given delta link. + /// + /// The resource to get the serialization info for. + /// The serialization info for the given resource. + protected ODataResourceSerializationInfo GetLinkSerializationInfo(ODataItem item) + { + Debug.Assert(item != null, "item != null"); + + ODataDeltaSerializationInfo deltaSerializationInfo = null; + ODataResourceSerializationInfo resourceSerializationInfo = null; + + var deltaLink = item as ODataDeltaLink; + if (deltaLink != null) + { + deltaSerializationInfo = deltaLink.SerializationInfo; + } + + var deltaDeletedLink = item as ODataDeltaDeletedLink; + if (deltaDeletedLink != null) + { + deltaSerializationInfo = deltaDeletedLink.SerializationInfo; + } + + if (deltaSerializationInfo == null) + { + DeltaResourceSetScope parentDeltaResourceSetScope = this.CurrentScope as DeltaResourceSetScope; + if (parentDeltaResourceSetScope != null) + { + ODataDeltaResourceSet resourceSet = (ODataDeltaResourceSet)parentDeltaResourceSetScope.Item; + Debug.Assert(resourceSet != null, "resourceSet != null"); + + ODataResourceSerializationInfo deltaSetSerializationInfo = resourceSet.SerializationInfo; + if (deltaSetSerializationInfo != null) + { + resourceSerializationInfo = deltaSetSerializationInfo; + } + } + } + else + { + resourceSerializationInfo = new ODataResourceSerializationInfo() + { + NavigationSourceName = deltaSerializationInfo.NavigationSourceName + }; + } + + return resourceSerializationInfo; + } + + /// + /// Creates a new nested resource info scope. + /// + /// The writer state for the new scope. + /// The nested resource info for the new scope. + /// The navigation source we are going to write entities for. + /// The type for the items in the resourceSet to be written (or null if the resource set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + /// The newly created nested resource info scope. + protected virtual NestedResourceInfoScope CreateNestedResourceInfoScope( + WriterState writerState, + ODataNestedResourceInfo navLink, + IEdmNavigationSource navigationSource, + IEdmType itemType, + bool skipWriting, + SelectedPropertiesNode selectedProperties, + ODataUri odataUri) + { + return new NestedResourceInfoScope(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri); + } + + /// + /// Place where derived writers can perform custom steps before the resource is writen, at the begining of WriteStartEntryImplementation. + /// + /// The ResourceScope. + /// Resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected virtual void PrepareResourceForWriteStart(ResourceScope resourceScope, ODataResource resource, bool writingResponse, SelectedPropertiesNode selectedProperties) + { + // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder + // into the resource before writing. + // Actually we can inject the metadata builder in here and + // remove virtual from this method. + } + + /// + /// Place where derived writers can perform custom steps before the deleted resource is writen, at the begining of WriteStartEntryImplementation. + /// + /// The ResourceScope. + /// Resource to write. + /// True if writing response. + /// The selected properties of this scope. + protected virtual void PrepareDeletedResourceForWriteStart(DeletedResourceScope resourceScope, ODataDeletedResource deletedResource, bool writingResponse, SelectedPropertiesNode selectedProperties) + { + // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder + // into the resource before writing. + // Actually we can inject the metadata builder in here and + // remove virtual from this method. + } + + /// + /// Gets the type of the resource and validates it against the model. + /// + /// The resource to get the type for. + /// The validated structured type. + protected IEdmStructuredType GetResourceType(ODataResourceBase resource) + { + return TypeNameOracle.ResolveAndValidateTypeFromTypeName( + this.outputContext.Model, + this.CurrentScope.ResourceType, + resource.TypeName, + this.WriterValidator); + } + + /// + /// Gets the element type of the resource set and validates it against the model. + /// + /// The resource set to get the element type for. + /// The validated structured element type. + protected IEdmStructuredType GetResourceSetType(ODataResourceSetBase resourceSet) + { + return TypeNameOracle.ResolveAndValidateTypeFromTypeName( + this.outputContext.Model, + this.CurrentScope.ResourceType, + EdmLibraryExtensions.GetCollectionItemTypeName(resourceSet.TypeName), + this.WriterValidator); + } + + /// + /// Validates that the ODataResourceSet.DeltaLink is null for the given expanded resourceSet. + /// + /// The expanded resourceSet in question. + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "An instance field is used in a debug assert.")] + protected void ValidateNoDeltaLinkForExpandedResourceSet(ODataResourceSet resourceSet) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + Debug.Assert( + this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value == true), + "This should only be called when writing an expanded resourceSet."); + + if (resourceSet.DeltaLink != null) + { + throw new ODataException(Strings.ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet); + } + } + + /// + /// Verifies that calling WriteStart resourceSet is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource Set/collection to write. + private void VerifyCanWriteStartResourceSet(bool synchronousCall, ODataResourceSet resourceSet) + { + ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + this.StartPayloadInStartState(); + } + + /// + /// Start writing a resourceSet - implementation of the actual functionality. + /// + /// The resource set to write. + private void WriteStartResourceSetImplementation(ODataResourceSet resourceSet) + { + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, resourceSet); + this.EnterScope(WriterState.ResourceSet, resourceSet); + + if (!this.SkipWriting) + { + this.InterceptException(() => + { + // Verify query count + if (resourceSet.Count.HasValue) + { + // Check that Count is not set for requests + if (!this.outputContext.WritingResponse) + { + this.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSet); + } + + // Verify version requirements + } + + this.StartResourceSet(resourceSet); + }); + } + } + + /// + /// Verifies that calling WriteStart deltaResourceSet is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource Set/collection to write. + private void VerifyCanWriteStartDeltaResourceSet(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet) + { + ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet"); + + this.VerifyWritingDelta(); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + this.StartPayloadInStartState(); + } + + /// + /// Start writing a delta resource set - implementation of the actual functionality. + /// + /// The delta resource Set to write. + private void WriteStartDeltaResourceSetImplementation(ODataDeltaResourceSet deltaResourceSet) + { + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, deltaResourceSet); + this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet); + + this.InterceptException(() => + { + // Check that links are not set for requests + if (!this.outputContext.WritingResponse) + { + if (deltaResourceSet.NextPageLink != null) + { + this.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSet); + } + + if (deltaResourceSet.DeltaLink != null) + { + this.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSet); + } + } + + this.StartDeltaResourceSet(deltaResourceSet); + }); + } + + /// + /// Verifies that calling WriteStart resource is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource/item to write. + private void VerifyCanWriteStartResource(bool synchronousCall, ODataResource resource) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling WriteDeletedResource is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Resource/item to write. + private void VerifyCanWriteStartDeletedResource(bool synchronousCall, ODataDeletedResource resource) + { + ExceptionUtils.CheckArgumentNotNull(resource, "resource"); + + this.VerifyWritingDelta(); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Start writing a resource - implementation of the actual functionality. + /// + /// Resource/item to write. + private void WriteStartResourceImplementation(ODataResource resource) + { + this.StartPayloadInStartState(); + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource); + this.EnterScope(WriterState.Resource, resource); + if (!this.SkipWriting) + { + this.IncreaseResourceDepth(); + this.InterceptException(() => + { + if (resource != null) + { + ResourceScope resourceScope = (ResourceScope)this.CurrentScope; + this.ValidateResourceForResourceSet(resource, resourceScope); + this.PrepareResourceForWriteStart( + resourceScope, + resource, + this.outputContext.WritingResponse, + resourceScope.SelectedProperties); + } + + this.StartResource(resource); + }); + } + } + + /// + /// Start writing a delta deleted resource - implementation of the actual functionality. + /// + /// Resource/item to write. + private void WriteStartDeletedResourceImplementation(ODataDeletedResource resource) + { + Debug.Assert(resource != null, "resource != null"); + + this.StartPayloadInStartState(); + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource); + this.EnterScope(WriterState.DeletedResource, resource); + this.IncreaseResourceDepth(); + + this.InterceptException(() => + { + DeletedResourceScope resourceScope = this.CurrentScope as DeletedResourceScope; + this.ValidateResourceForResourceSet(resource, resourceScope); + this.PrepareDeletedResourceForWriteStart( + resourceScope, + resource, + this.outputContext.WritingResponse, + resourceScope.SelectedProperties); + this.StartDeletedResource(resource); + }); + } + + /// + /// Verifies that calling WriteStart for a property is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Primitive property to write. + private void VerifyCanWriteProperty(bool synchronousCall, ODataPropertyInfo property) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Start writing a property - implementation of the actual functionality. + /// + /// Property to write. + private void WriteStartPropertyImplementation(ODataPropertyInfo property) + { + this.EnterScope(WriterState.Property, property); + if (!this.SkipWriting) + { + this.InterceptException(() => + { + this.StartProperty(property); + if (property is ODataProperty) + { + PropertyInfoScope scope = this.CurrentScope as PropertyInfoScope; + Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope"); + scope.ValueWritten = true; + } + }); + } + } + + /// + /// Start writing a delta link or delta delted link - implementation of the actual functionality. + /// + /// Delta (deleted) link to write. + private void WriteDeltaLinkImplementation(ODataDeltaLinkBase deltaLink) + { + this.EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink); + this.StartDeltaLink(deltaLink); + this.WriteEnd(); + } + + /// + /// Verifies that calling WriteStart nested resource info is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Navigation link to write. + private void VerifyCanWriteStartNestedResourceInfo(bool synchronousCall, ODataNestedResourceInfo nestedResourceInfo) + { + ExceptionUtils.CheckArgumentNotNull(nestedResourceInfo, "nestedResourceInfo"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Start writing a nested resource info - implementation of the actual functionality. + /// + /// Navigation link to write. + private void WriteStartNestedResourceInfoImplementation(ODataNestedResourceInfo nestedResourceInfo) + { + this.EnterScope(WriterState.NestedResourceInfo, nestedResourceInfo); + + // If the parent resource has a metadata builder, use that metadatabuilder on the nested resource info as well. + Debug.Assert(this.scopeStack.Parent != null, "Navigation link scopes must have a parent scope."); + Debug.Assert(this.scopeStack.Parent.Item is ODataResourceBase, "The parent of a nested resource info scope should always be a resource"); + ODataResourceBase parentResource = (ODataResourceBase)this.scopeStack.Parent.Item; + if (parentResource.MetadataBuilder != null) + { + nestedResourceInfo.MetadataBuilder = parentResource.MetadataBuilder; + } + } + + /// + /// Verifies that calling WritePrimitive is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Primitive value to write. + private void VerifyCanWritePrimitive(bool synchronousCall, ODataPrimitiveValue primitiveValue) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Write primitive value - implementation of the actual functionality. + /// + /// Primitive value to write. + private void WritePrimitiveValueImplementation(ODataPrimitiveValue primitiveValue) + { + this.InterceptException(() => + { + this.EnterScope(WriterState.Primitive, primitiveValue); + if (!(this.CurrentResourceSetValidator == null) && primitiveValue != null) + { + Debug.Assert(primitiveValue.Value != null, "PrimitiveValue.Value should never be null!"); + IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.Value.GetType()).Definition; + this.CurrentResourceSetValidator.ValidateResource(itemType); + } + + this.WritePrimitiveValue(primitiveValue); + this.WriteEnd(); + }); + } + + /// + /// Verifies that calling CreateWriteStream is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateWriteStream(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Create a write stream - implementation of the actual functionality. + /// + /// A stream for writing the binary value. + private Stream CreateWriteStreamImplementation() + { + this.EnterScope(WriterState.Stream, null); + return new ODataNotificationStream(this.StartBinaryStream(), this); + } + + /// + /// Verifies that calling CreateTextWriter is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanCreateTextWriter(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Create a text writer - implementation of the actual functionality. + /// + /// A TextWriter for writing the string value. + private TextWriter CreateTextWriterImplementation() + { + this.EnterScope(WriterState.String, null); + return new ODataNotificationWriter(this.StartTextWriter(), this); + } + + /// + /// Verify that calling WriteEnd is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEnd(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Finish writing a resourceSet/resource/nested resource info. + /// + private void WriteEndImplementation() + { + this.InterceptException(() => + { + Scope currentScope = this.CurrentScope; + + switch (currentScope.State) + { + case WriterState.Resource: + if (!this.SkipWriting) + { + ODataResource resource = (ODataResource)currentScope.Item; + + this.EndResource(resource); + this.DecreaseResourceDepth(); + } + + break; + case WriterState.DeletedResource: + if (!this.SkipWriting) + { + ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item; + + this.EndDeletedResource(resource); + this.DecreaseResourceDepth(); + } + + break; + case WriterState.ResourceSet: + if (!this.SkipWriting) + { + ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item; + WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !this.outputContext.WritingResponse); + this.EndResourceSet(resourceSet); + } + + break; + case WriterState.DeltaLink: + case WriterState.DeltaDeletedLink: + break; + case WriterState.DeltaResourceSet: + if (!this.SkipWriting) + { + ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item; + WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !this.outputContext.WritingResponse); + this.EndDeltaResourceSet(deltaResourceSet); + } + + break; + case WriterState.NestedResourceInfo: + if (!this.outputContext.WritingResponse) + { + throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest); + } + + if (!this.SkipWriting) + { + ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; + this.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link); + this.WriteDeferredNestedResourceInfo(link); + + this.MarkNestedResourceInfoAsProcessed(link); + } + + break; + case WriterState.NestedResourceInfoWithContent: + if (!this.SkipWriting) + { + ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item; + this.EndNestedResourceInfoWithContent(link); + + this.MarkNestedResourceInfoAsProcessed(link); + } + + break; + case WriterState.Property: + { + ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item; + this.EndProperty(property); + } + + break; + case WriterState.Primitive: + // WriteEnd for WriterState.Primitive is a no-op; just leave scope + break; + case WriterState.Stream: + case WriterState.String: + throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); + case WriterState.Start: // fall through + case WriterState.Completed: // fall through + case WriterState.Error: // fall through + throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString())); + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath)); + } + + this.LeaveScope(); + }); + } + + /// + /// Marks the navigation currently being written as processed in the parent entity's metadata builder. + /// This is needed so that at the end of writing the resource we can query for all the unwritten navigation properties + /// defined on the entity type and write out their metadata in fullmetadata mode. + /// + /// The nested resource info being written. + private void MarkNestedResourceInfoAsProcessed(ODataNestedResourceInfo link) + { + Debug.Assert( + this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent, + "This method should only be called when we're writing a nested resource info."); + + ODataResourceBase parent = (ODataResourceBase)this.scopeStack.Parent.Item; + Debug.Assert(parent.MetadataBuilder != null, "parent.MetadataBuilder != null"); + parent.MetadataBuilder.MarkNestedResourceInfoProcessed(link.Name); + } + + /// + /// Verifies that calling WriteEntityReferenceLink is valid. + /// + /// The entity reference link to write. + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanWriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink, bool synchronousCall) + { + ExceptionUtils.CheckArgumentNotNull(entityReferenceLink, "entityReferenceLink"); + + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that calling Write(Deleted)DeltaLink is valid. + /// + /// true if the call is to be synchronous; false otherwise. + /// Delta link to write. + private void VerifyCanWriteLink(bool synchronousCall, ODataDeltaLinkBase deltaLink) + { + this.VerifyWritingDelta(); + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + + ExceptionUtils.CheckArgumentNotNull(deltaLink, "delta link"); + } + + /// + /// Write an entity reference link. + /// + /// The entity reference link to write. + private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink entityReferenceLink) + { + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.EntityReferenceLink, null); + Debug.Assert( + this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo, + "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info."); + + if (!this.SkipWriting) + { + this.InterceptException(() => + { + WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLink); + + ODataNestedResourceInfo nestedInfo = this.CurrentScope.Item as ODataNestedResourceInfo; + if (nestedInfo == null) + { + NestedResourceInfoScope nestedResourceInfoScope = this.ParentNestedResourceInfoScope; + Debug.Assert(nestedResourceInfoScope != null); + nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item; + } + + this.WriteEntityReferenceInNavigationLinkContent(nestedInfo, entityReferenceLink); + }); + } + } + + /// + /// Verifies that calling Flush is valid. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCanFlush(bool synchronousCall) + { + this.VerifyNotDisposed(); + this.VerifyCallAllowed(synchronousCall); + } + + /// + /// Verifies that a call is allowed to the writer. + /// + /// true if the call is to be synchronous; false otherwise. + private void VerifyCallAllowed(bool synchronousCall) + { + if (synchronousCall) + { + if (!this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataWriterCore_SyncCallOnAsyncWriter); + } + } + else + { +#if PORTABLELIB + if (this.outputContext.Synchronous) + { + throw new ODataException(Strings.ODataWriterCore_AsyncCallOnSyncWriter); + } +#else + Debug.Assert(false, "Async calls are not allowed in this build."); +#endif + } + } + + /// + /// Verifies that the writer is a delta writer. + /// + private void VerifyWritingDelta() + { + if (!this.writingDelta) + { + throw new ODataException(Strings.ODataWriterCore_CannotWriteDeltaWithResourceSetWriter); + } + } + + /// + /// Enters the 'ExceptionThrown' state and then throws an ODataException with the specified error message. + /// + /// The error message for the exception. + /// The OData item to associate with the 'ExceptionThrown' state. + private void ThrowODataException(string errorMessage, ODataItem item) + { + this.EnterScope(WriterState.Error, item); + throw new ODataException(errorMessage); + } + + /// + /// Checks whether we are currently writing the first top-level element; if so call StartPayload + /// + private void StartPayloadInStartState() + { + if (this.State == WriterState.Start) + { + this.InterceptException(this.StartPayload); + } + } + + /// + /// Checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do. + /// + /// + /// What kind of payload kind is being written as the content of a nested resource info. + /// Only Resource Set, Resource or EntityReferenceLink are allowed. + /// + /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink. + private void CheckForNestedResourceInfoWithContent(ODataPayloadKind contentPayloadKind, ODataItem contentPayload) + { + Debug.Assert( + contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink, + "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content."); + + Scope currentScope = this.CurrentScope; + if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent) + { + ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item; + this.InterceptException(() => + { + if (this.ParentResourceType != null) + { + IEdmStructuralProperty structuralProperty = this.ParentResourceType.FindProperty(currentNestedResourceInfo.Name) as IEdmStructuralProperty; + if (structuralProperty != null) + { + this.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType(); + IEdmNavigationSource parentNavigationSource = this.ParentResourceNavigationSource; + + this.CurrentScope.NavigationSource = parentNavigationSource; + } + else + { + IEdmNavigationProperty navigationProperty = + this.WriterValidator.ValidateNestedResourceInfo(currentNestedResourceInfo, this.ParentResourceType, contentPayloadKind); + if (navigationProperty != null) + { + this.CurrentScope.ResourceType = navigationProperty.ToEntityType(); + IEdmNavigationSource parentNavigationSource = this.ParentResourceNavigationSource; + + if (this.CurrentScope.NavigationSource == null) + { + IEdmPathExpression bindingPath; + this.CurrentScope.NavigationSource = parentNavigationSource == null ? + null : + parentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, this.CurrentScope.ODataUri.Path.ToList(), out bindingPath); + } + } + } + } + }); + + if (currentScope.State == WriterState.NestedResourceInfoWithContent) + { + // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items + // into the nested resource info content. This is only allowed for collection navigation property in request/response. + if (currentNestedResourceInfo.IsCollection != true) + { + this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo); + } + + // Note that we don't invoke duplicate property checker in this case as it's not necessary. + // What happens inside the nested resource info was already validated by the condition above. + // For collection in request we allow any combination anyway. + // For everything else we only allow a single item in the content and thus we will fail above. + } + else + { + // we are writing a nested resource info with content; change the state + this.PromoteNestedResourceInfoScope(contentPayload); + + if (!this.SkipWriting) + { + this.InterceptException(() => + { + if (!(currentNestedResourceInfo.SerializationInfo != null && currentNestedResourceInfo.SerializationInfo.IsComplex) + && (this.CurrentScope.ItemType == null || this.CurrentScope.ItemType.IsEntityOrEntityCollectionType())) + { + this.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfo); + this.StartNestedResourceInfoWithContent(currentNestedResourceInfo); + } + }); + } + } + } + else + { + if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink) + { + Scope parenScope = this.ParentNestedResourceInfoScope; + Debug.Assert(parenScope != null); + if (parenScope.State != WriterState.NestedResourceInfo && parenScope.State != WriterState.NestedResourceInfoWithContent) + { + this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null); + } + } + } + } + + /// + /// Verifies that the (deleted) resource has the correct type for the (delta) resource set. + /// + /// The resource to be validated. + /// The scope for the resource to be validated. + private void ValidateResourceForResourceSet(ODataResourceBase resource, ResourceBaseScope resourceScope) + { + IEdmStructuredType resourceType = GetResourceType(resource); + NestedResourceInfoScope parentNestedResourceInfoScope = this.ParentNestedResourceInfoScope; + if (parentNestedResourceInfoScope != null) + { + // Validate the consistency of resource types in the nested resourceSet/resource + this.WriterValidator.ValidateResourceInNestedResourceInfo(resourceType, parentNestedResourceInfoScope.ResourceType); + resourceScope.ResourceTypeFromMetadata = parentNestedResourceInfoScope.ResourceType; + + this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata, + parentNestedResourceInfoScope.DerivedTypeConstraints, "property", ((ODataNestedResourceInfo)parentNestedResourceInfoScope.Item).Name); + } + else + { + resourceScope.ResourceTypeFromMetadata = this.ParentScope.ResourceType; + if (this.CurrentResourceSetValidator != null) + { + if (this.ParentScope.State == WriterState.DeltaResourceSet + && this.currentResourceDepth <= 1 + && resourceScope.NavigationSource != null) + { + // if the (deleted) resource is in the top level of a delta resource set, it doesn't + // need to match the delta resource set, but must match the navigation source resolved for + // the current scope + if (!resourceScope.NavigationSource.EntityType().IsAssignableFrom(resourceType)) + { + throw new ODataException(Strings.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(resourceType.FullTypeName(), resourceScope.NavigationSource.EntityType())); + } + + resourceScope.ResourceTypeFromMetadata = resourceScope.NavigationSource.EntityType(); + } + else + { + // Validate the consistency of resource types + this.CurrentResourceSetValidator.ValidateResource(resourceType); + } + } + + if (this.ParentScope.NavigationSource != null) + { + this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata, + this.ParentScope.DerivedTypeConstraints, "navigation source", this.ParentScope.NavigationSource.Name); + } + } + + resourceScope.ResourceType = resourceType; + + // If writing in a delta resource set, the entity must have all key properties or the id set + if (this.ParentScope.State == WriterState.DeltaResourceSet) + { + IEdmEntityType entityType = resourceType as IEdmEntityType; + if (resource.Id == null && + entityType != null && + (resource is ODataDeletedResource || this.outputContext.MessageWriterSettings.Version > ODataVersion.V4) && + !HasKeyProperties(entityType, resource.Properties)) + { + throw new ODataException(Strings.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties); + } + } + } + + /// + /// Determines whether a collection contains all key properties for a particular entity type. + /// + /// The entity type. + /// The set of properties. + /// True if the set of properties include all key properties for the entity type; otherwise false. + private static bool HasKeyProperties(IEdmEntityType entityType, IEnumerable properties) + { + Debug.Assert(entityType != null, "entityType null"); + if (properties == null) + { + return false; + } + + return entityType.Key().All(keyProp => properties.Select(p => p.Name).Contains(keyProp.Name)); + } + + /// + /// Catch any exception thrown by the action passed in; in the exception case move the writer into + /// state ExceptionThrown and then rethrow the exception. + /// + /// The action to execute. + private void InterceptException(Action action) + { + try + { + action(); + } + catch + { + if (!IsErrorState(this.State)) + { + this.EnterScope(WriterState.Error, this.CurrentScope.Item); + } + + throw; + } + } + + /// + /// Increments the nested resource count by one and fails if the new value exceeds the maxiumum nested resource depth limit. + /// + private void IncreaseResourceDepth() + { + this.currentResourceDepth++; + + if (this.currentResourceDepth > this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth) + { + this.ThrowODataException(Strings.ValidationUtils_MaxDepthOfNestedEntriesExceeded(this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth), null); + } + } + + /// + /// Decrements the nested resource count by one. + /// + private void DecreaseResourceDepth() + { + Debug.Assert(this.currentResourceDepth > 0, "Resource depth should never become negative."); + + this.currentResourceDepth--; + } + + + /// + /// Notifies the implementer of the interface of relevant state changes in the writer. + /// + /// The new writer state. + private void NotifyListener(WriterState newState) + { + if (this.listener != null) + { + if (IsErrorState(newState)) + { + this.listener.OnException(); + } + else if (newState == WriterState.Completed) + { + this.listener.OnCompleted(); + } + } + } + + /// + /// Enter a new writer scope; verifies that the transition from the current state into new state is valid + /// and attaches the item to the new scope. + /// + /// The writer state to transition into. + /// The item to associate with the new scope. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug only cast.")] + private void EnterScope(WriterState newState, ODataItem item) + { + this.InterceptException(() => this.ValidateTransition(newState)); + + // If the parent scope was marked for skipping content, the new child scope should be as well. + bool skipWriting = this.SkipWriting; + + Scope currentScope = this.CurrentScope; + + IEdmNavigationSource navigationSource = null; + IEdmType itemType = null; + SelectedPropertiesNode selectedProperties = currentScope.SelectedProperties; + ODataUri odataUri = currentScope.ODataUri.Clone(); + if (odataUri.Path == null) + { + odataUri.Path = new ODataPath(); + } + + IEnumerable derivedTypeConstraints = null; + + WriterState currentState = currentScope.State; + + if (newState == WriterState.Resource || newState == WriterState.ResourceSet || newState == WriterState.Primitive || newState == WriterState.DeltaResourceSet || newState == WriterState.DeletedResource) + { + // if we're in a DeltaResourceSet and writing a resource or deleted resource then the parent may not be the navigation source + ODataResourceBase resource = item as ODataResourceBase; + if (resource != null) + { + IEdmModel model = this.outputContext.Model; + if (model != null && model.IsUserModel()) + { + try + { + string typeNameFromResource = resource.TypeName; + if (!String.IsNullOrEmpty(typeNameFromResource)) + { + // try resolving type from resource TypeName + itemType = TypeNameOracle.ResolveAndValidateTypeName( + model, + typeNameFromResource, + EdmTypeKind.None, + /* expectStructuredType */ true, + this.outputContext.WriterValidator); + } + + // Try resolving navigation source from serialization info. + ODataResourceSerializationInfo serializationInfo = resource.SerializationInfo; + if (serializationInfo != null) + { + if (serializationInfo.NavigationSourceName != null) + { + ODataUriParser uriParser = new ODataUriParser(model, new Uri(serializationInfo.NavigationSourceName, UriKind.Relative), this.outputContext.Container); + odataUri = uriParser.ParseUri(); + navigationSource = odataUri.Path.NavigationSource(); + itemType = itemType ?? navigationSource.EntityType(); + } + + if (typeNameFromResource == null) + { + // Try resolving entity type from SerializationInfo + if (!string.IsNullOrEmpty(serializationInfo.ExpectedTypeName)) + { + itemType = TypeNameOracle.ResolveAndValidateTypeName( + model, + serializationInfo.ExpectedTypeName, + EdmTypeKind.None, + /* expectStructuredType */ true, + this.outputContext.WriterValidator); + } + else if (!string.IsNullOrEmpty(serializationInfo.NavigationSourceEntityTypeName)) + { + itemType = TypeNameOracle.ResolveAndValidateTypeName( + model, + serializationInfo.NavigationSourceEntityTypeName, + EdmTypeKind.Entity, + /* expectStructuredType */ true, + this.outputContext.WriterValidator); + } + } + } + } + catch (ODataException) + { + // SerializationInfo doesn't match model. + // This should be an error but, for legacy reasons, we ignore this. + } + } + } + + if (navigationSource == null) + { + derivedTypeConstraints = currentScope.DerivedTypeConstraints; + } + else + { + derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource); + } + + navigationSource = navigationSource ?? currentScope.NavigationSource; + itemType = itemType ?? currentScope.ItemType; + + // This is to resolve the item type for a resource set for an undeclared nested resource info. + if (itemType == null + && (currentState == WriterState.Start || currentState == WriterState.NestedResourceInfo || currentState == WriterState.NestedResourceInfoWithContent) + && (newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet)) + { + var resourceSet = item as ODataResourceSetBase; + if (resourceSet != null && resourceSet.TypeName != null && this.outputContext.Model.IsUserModel()) + { + var collectionType = TypeNameOracle.ResolveAndValidateTypeName( + this.outputContext.Model, + resourceSet.TypeName, + EdmTypeKind.Collection, + false, + this.outputContext.WriterValidator) as IEdmCollectionType; + + if (collectionType != null) + { + itemType = collectionType.ElementType.Definition; + } + } + } + } + + // When writing a nested resource info, check if the link is being projected. + // If we are projecting properties, but the nav. link is not projected mark it to skip its content. + if ((currentState == WriterState.Resource || currentState == WriterState.DeletedResource) && newState == WriterState.NestedResourceInfo) + { + Debug.Assert(currentScope.Item is ODataResourceBase, "If the current state is Resource the current Item must be resource as well (and not null either)."); + Debug.Assert(item is ODataNestedResourceInfo, "If the new state is NestedResourceInfo the new item must be a nested resource info as well (and not null either)."); + ODataNestedResourceInfo nestedResourceInfo = (ODataNestedResourceInfo)item; + + if (!skipWriting) + { + selectedProperties = currentScope.SelectedProperties.GetSelectedPropertiesForNavigationProperty(currentScope.ResourceType, nestedResourceInfo.Name); + + if (this.outputContext.WritingResponse || this.writingDelta) + { + ODataPath odataPath = odataUri.Path; + IEdmStructuredType currentResourceType = currentScope.ResourceType; + + var resourceScope = currentScope as ResourceBaseScope; + TypeSegment resourceTypeCast = null; + if (resourceScope.ResourceTypeFromMetadata != currentResourceType) + { + resourceTypeCast = new TypeSegment(currentResourceType, null); + } + + var structuredProperty = this.WriterValidator.ValidatePropertyDefined( + nestedResourceInfo.Name, currentResourceType) + as IEdmStructuralProperty; + + // Handle primitive or complex type property. + if (structuredProperty != null) + { + odataPath = AppendEntitySetKeySegment(odataPath, false); + itemType = structuredProperty.Type == null ? null : structuredProperty.Type.Definition.AsElementType(); + navigationSource = null; + + if (resourceTypeCast != null) + { + odataPath.Add(resourceTypeCast); + } + + odataPath = odataPath.AppendPropertySegment(structuredProperty); + + derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(structuredProperty); + } + else + { + IEdmNavigationProperty navigationProperty = this.WriterValidator.ValidateNestedResourceInfo(nestedResourceInfo, currentResourceType, /*payloadKind*/null); + if (navigationProperty != null) + { + derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationProperty); + + itemType = navigationProperty.ToEntityType(); + if (!nestedResourceInfo.IsCollection.HasValue) + { + nestedResourceInfo.IsCollection = navigationProperty.Type.IsEntityCollectionType(); + } + + if (!nestedResourceInfo.IsCollection.HasValue) + { + nestedResourceInfo.IsCollection = navigationProperty.Type.IsEntityCollectionType(); + } + + IEdmNavigationSource currentNavigationSource = currentScope.NavigationSource; + IEdmPathExpression bindingPath; + + if (resourceTypeCast != null) + { + odataPath.Add(resourceTypeCast); + } + + navigationSource = currentNavigationSource == null + ? null + : currentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, odataPath.ToList(), out bindingPath); + + SelectExpandClause clause = odataUri.SelectAndExpand; + TypeSegment typeCastFromExpand = null; + if (clause != null) + { + SelectExpandClause subClause; + clause.GetSubSelectExpandClause(nestedResourceInfo.Name, out subClause, out typeCastFromExpand); + odataUri.SelectAndExpand = subClause; + } + + switch (navigationSource.NavigationSourceKind()) + { + case EdmNavigationSourceKind.ContainedEntitySet: + // Containment cannot be written alone without odata uri. + if (odataPath.Count == 0) + { + throw new ODataException(Strings.ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement); + } + + odataPath = AppendEntitySetKeySegment(odataPath, true); + + if (odataPath != null && typeCastFromExpand != null) + { + odataPath.Add(typeCastFromExpand); + } + + Debug.Assert(navigationSource is IEdmContainedEntitySet, "If the NavigationSourceKind is ContainedEntitySet, the navigationSource must be IEdmContainedEntitySet."); + IEdmContainedEntitySet containedEntitySet = (IEdmContainedEntitySet)navigationSource; + odataPath = odataPath.AppendNavigationPropertySegment(containedEntitySet.NavigationProperty, containedEntitySet); + break; + case EdmNavigationSourceKind.EntitySet: + odataPath = new ODataPath(new EntitySetSegment(navigationSource as IEdmEntitySet)); + break; + case EdmNavigationSourceKind.Singleton: + odataPath = new ODataPath(new SingletonSegment(navigationSource as IEdmSingleton)); + break; + default: + odataPath = null; + break; + } + } + } + + odataUri.Path = odataPath; + } + } + } + else if ((currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) && (newState == WriterState.Resource || newState == WriterState.Primitive || newState == WriterState.ResourceSet || newState == WriterState.DeletedResource)) + { + // When writing a new resource to a resourceSet, increment the count of entries on that resourceSet. + if (currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) + { + ((ResourceSetBaseScope)currentScope).ResourceCount++; + } + } + + if (navigationSource == null) + { + navigationSource = this.CurrentScope.NavigationSource ?? odataUri.Path.TargetNavigationSource(); + } + + this.PushScope(newState, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, derivedTypeConstraints); + + this.NotifyListener(newState); + } + + /// + /// Attempt to append key segment to ODataPath. + /// + /// The ODataPath to be evaluated. + /// Whether throw if fails to append key segment. + /// The new odata path. + private ODataPath AppendEntitySetKeySegment(ODataPath odataPath, bool throwIfFail) + { + ODataPath path = odataPath; + + try + { + if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType)) + { + IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType; + ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase; + Debug.Assert(resource != null, + "If the current state is Resource the current item must be an ODataResource as well (and not null either)."); + KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, + this.GetResourceSerializationInfo(resource), currentEntityType); + path = path.AppendKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource); + } + } + catch (ODataException) + { + if (throwIfFail) + { + throw; + } + } + + return path; + } + + /// + /// Leave the current writer scope and return to the previous scope. + /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope. + /// + /// Note that this method is never called once an error has been written or a fatal exception has been thrown. + private void LeaveScope() + { + Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error"); + + this.scopeStack.Pop(); + + // if we are back at the root replace the 'Start' state with the 'Completed' state + if (this.scopeStack.Count == 1) + { + Scope startScope = this.scopeStack.Pop(); + Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start"); + this.PushScope(WriterState.Completed, /*item*/null, startScope.NavigationSource, startScope.ResourceType, /*skipWriting*/false, startScope.SelectedProperties, startScope.ODataUri, null); + this.InterceptException(this.EndPayload); + this.NotifyListener(WriterState.Completed); + } + } + + /// + /// Promotes the current nested resource info scope to a nested resource info scope with content. + /// + /// The nested content to write. May be of either ODataResource or ODataResourceSet type. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Second cast only in debug.")] + private void PromoteNestedResourceInfoScope(ODataItem content) + { + Debug.Assert( + this.State == WriterState.NestedResourceInfo, + "Only a NestedResourceInfo state can be promoted right now. If this changes please review the scope replacement code below."); + Debug.Assert( + this.CurrentScope.Item != null && this.CurrentScope.Item is ODataNestedResourceInfo, + "Item must be a non-null nested resource info."); + Debug.Assert(content == null || content is ODataResourceBase || content is ODataResourceSetBase); + + this.ValidateTransition(WriterState.NestedResourceInfoWithContent); + NestedResourceInfoScope previousScope = (NestedResourceInfoScope)this.scopeStack.Pop(); + NestedResourceInfoScope newScope = previousScope.Clone(WriterState.NestedResourceInfoWithContent); + + this.scopeStack.Push(newScope); + if (newScope.ItemType == null && content != null && !SkipWriting && !(content is ODataPrimitiveValue)) + { + ODataPrimitiveValue primitiveValue = content as ODataPrimitiveValue; + if (primitiveValue != null) + { + newScope.ItemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.GetType()).Definition; + } + else + { + ODataResourceBase resource = content as ODataResourceBase; + newScope.ResourceType = resource != null + ? GetResourceType(resource) + : GetResourceSetType(content as ODataResourceSetBase); + } + } + } + + /// + /// Verify that the transition from the current state into new state is valid . + /// + /// The new writer state to transition into. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "All the transition checks are encapsulated in this method.")] + private void ValidateTransition(WriterState newState) + { + if (!IsErrorState(this.State) && IsErrorState(newState)) + { + // we can always transition into an error state if we are not already in an error state + return; + } + + switch (this.State) + { + case WriterState.Start: + if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.DeltaResourceSet) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromStart(this.State.ToString(), newState.ToString())); + } + + if ((newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet) && !this.writingResourceSet) + { + throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter); + } + + if (newState == WriterState.Resource && this.writingResourceSet) + { + throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter); + } + + break; + case WriterState.DeletedResource: + case WriterState.Resource: + { + if (this.CurrentScope.Item == null) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromNullResource(this.State.ToString(), newState.ToString())); + } + + if (newState != WriterState.NestedResourceInfo && newState != WriterState.Property) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResource(this.State.ToString(), newState.ToString())); + } + + if (newState == WriterState.DeletedResource && this.ParentScope.State != WriterState.DeltaResourceSet) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); + } + + if (this.State == WriterState.DeletedResource && this.Version < ODataVersion.V401 && newState == WriterState.NestedResourceInfo) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFrom40DeletedResource(this.State.ToString(), newState.ToString())); + } + } + + break; + case WriterState.ResourceSet: + // Within a typed resource set we can only write a resource. + // Within an untyped resource set we can also write a primitive value or nested resource set. + if (newState != WriterState.Resource && + (this.CurrentScope.ResourceType != null && + (this.CurrentScope.ResourceType.TypeKind != EdmTypeKind.Untyped || + (newState != WriterState.Primitive && newState != WriterState.Stream && newState != WriterState.String && newState != WriterState.ResourceSet)))) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.DeltaResourceSet: + if (newState != WriterState.Resource && + newState != WriterState.DeletedResource && + !(this.ScopeLevel < 3 && (newState == WriterState.DeltaDeletedLink || newState == WriterState.DeltaLink))) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.NestedResourceInfo: + if (newState != WriterState.NestedResourceInfoWithContent) + { + throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.NestedResourceInfoWithContent: + if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.Primitive && (this.Version < ODataVersion.V401 || (newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource))) + { + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromExpandedLink(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.Property: + PropertyInfoScope propertyScope = this.CurrentScope as PropertyInfoScope; + Debug.Assert(propertyScope != null, "Scope in WriterState.Property is not PropertyInfoScope"); + if (propertyScope.ValueWritten) + { + // we've already written the value for this property + ODataPropertyInfo propertyInfo = propertyScope.Item as ODataPropertyInfo; + Debug.Assert(propertyInfo != null, "Item in PropertyInfoScope is not ODataPropertyInfo"); + throw new ODataException(Strings.ODataWriterCore_PropertyValueAlreadyWritten(propertyInfo.Name)); + } + + if (newState == WriterState.Stream || newState == WriterState.String || newState == WriterState.Primitive) + { + propertyScope.ValueWritten = true; + } + else + { + throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString())); + } + + break; + case WriterState.Stream: + case WriterState.String: + throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed); + case WriterState.Completed: + // we should never see a state transition when in state 'Completed' + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), newState.ToString())); + case WriterState.Error: + if (newState != WriterState.Error) + { + // No more state transitions once we are in error state except for the fatal error + throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromError(this.State.ToString(), newState.ToString())); + } + + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ValidateTransition_UnreachableCodePath)); + } + } + + /// + /// Create a new writer scope. + /// + /// The writer state of the scope to create. + /// The item attached to the scope to create. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the navigationSource base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The OdataUri info of this scope. + /// The derived type constraints. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")] + private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, + IEnumerable derivedTypeConstraints) + { + IEdmStructuredType resourceType = itemType as IEdmStructuredType; + + Debug.Assert( + state == WriterState.Error || + state == WriterState.Resource && (item == null || item is ODataResource) || + state == WriterState.DeletedResource && (item == null || item is ODataDeletedResource) || + state == WriterState.DeltaLink && (item == null || item is ODataDeltaLink) || + state == WriterState.DeltaDeletedLink && (item == null || item is ODataDeltaDeletedLink) || + state == WriterState.ResourceSet && item is ODataResourceSet || + state == WriterState.DeltaResourceSet && item is ODataDeltaResourceSet || + state == WriterState.Primitive && (item == null || item is ODataPrimitiveValue) || + state == WriterState.Property && (item is ODataPropertyInfo) || + state == WriterState.NestedResourceInfo && item is ODataNestedResourceInfo || + state == WriterState.NestedResourceInfoWithContent && item is ODataNestedResourceInfo || + state == WriterState.Stream && item == null || + state == WriterState.String && item == null || + state == WriterState.Start && item == null || + state == WriterState.Completed && item == null, + "Writer state and associated item do not match."); + + bool isUndeclaredResourceOrResourceSet = false; + if ((state == WriterState.Resource || state == WriterState.ResourceSet) + && (this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent)) + { + isUndeclaredResourceOrResourceSet = this.IsUndeclared(this.CurrentScope.Item as ODataNestedResourceInfo); + } + + Scope scope; + switch (state) + { + case WriterState.Resource: + scope = this.CreateResourceScope((ODataResource)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + break; + case WriterState.DeletedResource: + scope = this.CreateDeletedResourceScope((ODataDeletedResource)item, navigationSource, (IEdmEntityType)itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + break; + case WriterState.DeltaLink: + case WriterState.DeltaDeletedLink: + scope = this.CreateDeltaLinkScope((ODataDeltaLinkBase)item, navigationSource, (IEdmEntityType)itemType, selectedProperties, odataUri); + break; + case WriterState.ResourceSet: + scope = this.CreateResourceSetScope((ODataResourceSet)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + if (this.outputContext.Model.IsUserModel()) + { + Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a resource set that is not a ResourceSetBaseScope"); + ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(itemType); + } + + break; + case WriterState.DeltaResourceSet: + scope = this.CreateDeltaResourceSetScope((ODataDeltaResourceSet)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet); + if (this.outputContext.Model.IsUserModel()) + { + Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a delta resource set that is not a ResourceSetBaseScope"); + ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(resourceType); + } + + break; + case WriterState.Property: + scope = this.CreatePropertyInfoScope((ODataPropertyInfo)item, navigationSource, resourceType, selectedProperties, odataUri); + break; + case WriterState.NestedResourceInfo: // fall through + case WriterState.NestedResourceInfoWithContent: + scope = this.CreateNestedResourceInfoScope(state, (ODataNestedResourceInfo)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri); + break; + case WriterState.Primitive: // fall through + case WriterState.Stream: // fall through + case WriterState.String: // fall through + case WriterState.Start: // fall through + case WriterState.Completed: // fall through + case WriterState.Error: + scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri); + break; + default: + string errorMessage = Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_Scope_Create_UnreachableCodePath); + Debug.Assert(false, errorMessage); + throw new ODataException(errorMessage); + } + + scope.DerivedTypeConstraints = derivedTypeConstraints; + this.scopeStack.Push(scope); + } + + /// + /// Test to see if for a complex property or a collection of complex property, or a navigation property is declared or not. + /// + /// The nested info in question + /// true if the nested info is undeclared; false if it is not, or if it cannot be determined + private bool IsUndeclared(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + + if (nestedResourceInfo.SerializationInfo != null) + { + return nestedResourceInfo.SerializationInfo.IsUndeclared; + } + else + { + return this.ParentResourceType != null && (this.ParentResourceType.FindProperty((this.CurrentScope.Item as ODataNestedResourceInfo).Name) == null); + } + } + + /// + /// Lightweight wrapper for the stack of scopes which exposes a few helper properties for getting parent scopes. + /// + internal sealed class ScopeStack + { + /// + /// Use a list to store the scopes instead of a true stack so that parent/grandparent lookups will be fast. + /// + private readonly Stack scopes = new Stack(); + + /// + /// Initializes a new instance of the class. + /// + internal ScopeStack() + { + } + + /// + /// Gets the count of items in the stack. + /// + internal int Count + { + get + { + return this.scopes.Count; + } + } + + /// + /// Gets the scope below the current scope on top of the stack. + /// + internal Scope Parent + { + get + { + Debug.Assert(this.scopes.Count > 1, "this.scopes.Count > 1"); + Scope current = this.scopes.Pop(); + Scope parent = this.scopes.Peek(); + this.scopes.Push(current); + return parent; + } + } + + /// + /// Gets the scope below the parent of the current scope on top of the stack. + /// + internal Scope ParentOfParent + { + get + { + Debug.Assert(this.scopes.Count > 2, "this.scopes.Count > 2"); + Scope current = this.scopes.Pop(); + Scope parent = this.scopes.Pop(); + Scope parentOfParent = this.scopes.Peek(); + this.scopes.Push(parent); + this.scopes.Push(current); + return parentOfParent; + } + } + + /// + /// Gets the scope below the current scope on top of the stack or null if there is only one item on the stack or the stack is empty. + /// + internal Scope ParentOrNull + { + get + { + return this.Count == 0 ? null : this.Parent; + } + } + + internal Stack Scopes + { + get { return this.scopes; } + } + + /// + /// Pushes the specified scope onto the stack. + /// + /// The scope. + internal void Push(Scope scope) + { + Debug.Assert(scope != null, "scope != null"); + this.scopes.Push(scope); + } + + /// + /// Pops the current scope off the stack. + /// + /// The popped scope. + internal Scope Pop() + { + Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0"); + return this.scopes.Pop(); + } + + /// + /// Peeks at the current scope on the top of the stack. + /// + /// The current scope at the top of the stack. + internal Scope Peek() + { + Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0"); + return this.scopes.Peek(); + } + } + + /// + /// A writer scope; keeping track of the current writer state and an item associated with this state. + /// + internal class Scope + { + /// The writer state of this scope. + private readonly WriterState state; + + /// The item attached to this scope. + private readonly ODataItem item; + + /// Set to true if the content of the scope should not be written. + /// This is used when writing navigation links which were not projected on the owning resource. + private readonly bool skipWriting; + + /// The selected properties for the current scope. + private readonly SelectedPropertiesNode selectedProperties; + + /// The navigation source we are going to write entities for. + private IEdmNavigationSource navigationSource; + + /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used). + private IEdmStructuredType resourceType; + + /// The IEdmType of the item (may not be structured for primitive types). + private IEdmType itemType; + + /// The odata uri info for current scope. + private ODataUri odataUri; + + /// + /// Constructor creating a new writer scope. + /// + /// The writer state of this scope. + /// The item attached to this scope. + /// The navigation source we are going to write resource set for. + /// The type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of this scope should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal Scope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + { + this.state = state; + this.item = item; + this.itemType = itemType; + this.resourceType = itemType as IEdmStructuredType; + this.navigationSource = navigationSource; + this.skipWriting = skipWriting; + this.selectedProperties = selectedProperties; + this.odataUri = odataUri; + } + + /// + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// + public IEdmStructuredType ResourceType + { + get + { + return this.resourceType; + } + + set + { + this.resourceType = value; + this.itemType = value; + } + } + + /// + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// + public IEdmType ItemType + { + get + { + return this.itemType; + } + + set + { + this.itemType = value; + this.resourceType = value as IEdmStructuredType; + } + } + + /// + /// The writer state of this scope. + /// + internal WriterState State + { + get + { + return this.state; + } + } + + /// + /// The item attached to this scope. + /// + internal ODataItem Item + { + get + { + return this.item; + } + } + + /// The navigation source we are going to write entities for. + internal IEdmNavigationSource NavigationSource + { + get + { + return this.navigationSource; + } + + set + { + this.navigationSource = value; + } + } + + /// The selected properties for the current scope. + internal SelectedPropertiesNode SelectedProperties + { + get + { + return this.selectedProperties; + } + } + + /// The odata Uri for the current scope. + internal ODataUri ODataUri + { + get + { + Debug.Assert(this.odataUri != null, "this.odataUri != null"); + return this.odataUri; + } + } + + /// + /// Set to true if the content of this scope should not be written. + /// + internal bool SkipWriting + { + get + { + return this.skipWriting; + } + } + + /// Gets or sets the derived type constraints for the current scope. + internal IEnumerable DerivedTypeConstraints { get; set; } + } + + /// + /// A base scope for a resourceSet. + /// + internal abstract class ResourceSetBaseScope : Scope + { + /// The serialization info for the current resourceSet. + private readonly ODataResourceSerializationInfo serializationInfo; + + /// + /// The to use for entries in this resourceSet. + /// + private ResourceSetWithoutExpectedTypeValidator resourceTypeValidator; + + /// The number of entries in this resourceSet seen so far. + private int resourceCount; + + /// Maintains the write status for each annotation using its key. + private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker; + + /// The type context to answer basic questions regarding the type info of the resource. + private ODataResourceTypeContext typeContext; + + /// + /// Constructor to create a new resource set scope. + /// + /// The writer state for the scope. + /// The resourceSet for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal ResourceSetBaseScope(WriterState writerState, ODataResourceSetBase resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(writerState, resourceSet, navigationSource, itemType, skipWriting, selectedProperties, odataUri) + { + this.serializationInfo = resourceSet.SerializationInfo; + } + + /// + /// The number of entries in this resource Set seen so far. + /// + internal int ResourceCount + { + get + { + return this.resourceCount; + } + + set + { + this.resourceCount = value; + } + } + + /// + /// Tracks the write status of the annotations. + /// + internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker + { + get + { + if (this.instanceAnnotationWriteTracker == null) + { + this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker(); + } + + return this.instanceAnnotationWriteTracker; + } + } + + /// + /// Validator for resource type. + /// + internal ResourceSetWithoutExpectedTypeValidator ResourceTypeValidator + { + get + { + return this.resourceTypeValidator; + } + + set + { + this.resourceTypeValidator = value; + } + } + + /// + /// Gets or creates the type context to answer basic questions regarding the type info of the resource. + /// + /// True if writing a response payload, false otherwise. + /// The type context to answer basic questions regarding the type info of the resource. + internal ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse) + { + if (this.typeContext == null) + { + // For Entity, currently we check the navigation source. + // For Complex, we don't have navigation source, So we shouldn't check it. + // If ResourceType is not provided, serialization info or navigation source info should be provided. + var throwIfMissingTypeInfo = writingResponse && (this.ResourceType == null || this.ResourceType.TypeKind == EdmTypeKind.Entity); + + this.typeContext = ODataResourceTypeContext.Create( + this.serializationInfo, + this.NavigationSource, + EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), + this.ResourceType, + throwIfMissingTypeInfo); + } + + return this.typeContext; + } + } + + /// + /// A scope for a resource set. + /// + internal abstract class ResourceSetScope : ResourceSetBaseScope + { + /// + /// Constructor to create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The type of the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected ResourceSetScope(ODataResourceSet item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.ResourceSet, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri) + { + } + } + + /// + /// A scope for a delta resource set. + /// + internal abstract class DeltaResourceSetScope : ResourceSetBaseScope + { + /// + /// Constructor to create a new resource set scope. + /// + /// The resource set for the new scope. + /// The navigation source we are going to write resource set for. + /// The structured type of the items in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected DeltaResourceSetScope(ODataDeltaResourceSet item, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.DeltaResourceSet, item, navigationSource, resourceType, false /*skip writing*/, selectedProperties, odataUri) + { + } + + /// + /// The context uri info created for this scope. + /// + public ODataContextUrlInfo ContextUriInfo { get; set; } + } + + /// + /// A base scope for a resource. + /// + internal class ResourceBaseScope : Scope + { + /// Checker to detect duplicate property names. + private readonly IDuplicatePropertyNameChecker duplicatePropertyNameChecker; + + /// The serialization info for the current resource. + private readonly ODataResourceSerializationInfo serializationInfo; + + /// The resource type which was derived from the model (may be either the same as structured type or its base type. + private IEdmStructuredType resourceTypeFromMetadata; + + /// The type context to answer basic questions regarding the type info of the resource. + private ODataResourceTypeContext typeContext; + + /// Maintains the write status for each annotation using its key. + private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker; + + /// + /// Constructor to create a new resource scope. + /// + /// The writer state of this scope. + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write resource set for. + /// The type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal ResourceBaseScope(WriterState state, ODataResourceBase resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(state, resource, navigationSource, itemType, skipWriting, selectedProperties, odataUri) + { + Debug.Assert(writerSettings != null, "writerBehavior != null"); + + if (resource != null) + { + duplicatePropertyNameChecker = writerSettings.Validator.CreateDuplicatePropertyNameChecker(); + } + + this.serializationInfo = serializationInfo; + } + + /// + /// The structured type which was derived from the model, i.e. the expected structured type, which may be either the same as structured type or its base type. + /// For example, if we are writing a resource set of Customers and the current resource is of DerivedCustomer, this.ResourceTypeFromMetadata would be Customer and this.ResourceType would be DerivedCustomer. + /// + public IEdmStructuredType ResourceTypeFromMetadata + { + get + { + return this.resourceTypeFromMetadata; + } + + internal set + { + this.resourceTypeFromMetadata = value; + } + } + + /// + /// The serialization info for the current resource. + /// + public ODataResourceSerializationInfo SerializationInfo + { + get { return this.serializationInfo; } + } + + /// + /// Checker to detect duplicate property names. + /// + internal IDuplicatePropertyNameChecker DuplicatePropertyNameChecker + { + get + { + return duplicatePropertyNameChecker; + } + } + + /// + /// Tracks the write status of the annotations. + /// + internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker + { + get + { + if (this.instanceAnnotationWriteTracker == null) + { + this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker(); + } + + return this.instanceAnnotationWriteTracker; + } + } + + /// + /// Gets or creates the type context to answer basic questions regarding the type info of the resource. + /// + /// True if writing a response payload, false otherwise. + /// The type context to answer basic questions regarding the type info of the resource. + public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse) + { + if (this.typeContext == null) + { + IEdmStructuredType expectedResourceType = this.ResourceTypeFromMetadata ?? this.ResourceType; + + // For entity, we will check the navigation source info + bool throwIfMissingTypeInfo = writingResponse && (expectedResourceType == null || expectedResourceType.TypeKind == EdmTypeKind.Entity); + + this.typeContext = ODataResourceTypeContext.Create( + this.serializationInfo, + this.NavigationSource, + EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), + expectedResourceType, + throwIfMissingTypeInfo); + } + + return this.typeContext; + } + } + + /// + /// A base scope for a resource. + /// + internal class ResourceScope : ResourceBaseScope + { + /// + /// Constructor to create a new resource scope. + /// + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write resource set for. + /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected ResourceScope(ODataResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.Resource, resource, serializationInfo, navigationSource, resourceType, skipWriting, writerSettings, selectedProperties, odataUri) + { + } + } + + /// + /// Base class for DeletedResourceScope. + /// + internal class DeletedResourceScope : ResourceBaseScope + { + /// + /// Constructor to create a new resource scope. + /// + /// The resource for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The The settings of the writer. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected DeletedResourceScope(ODataDeletedResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.DeletedResource, resource, serializationInfo, navigationSource, entityType, false /*skipWriting*/, writerSettings, selectedProperties, odataUri) + { + } + } + + /// + /// A scope for a delta link. + /// + internal abstract class DeltaLinkScope : Scope + { + /// The serialization info for the current link. + private readonly ODataResourceSerializationInfo serializationInfo; + + /// + /// Fake entity type to be passed to context. + /// + private readonly EdmEntityType fakeEntityType = new EdmEntityType("MyNS", "Fake"); + + /// The type context to answer basic questions regarding the type info of the link. + private ODataResourceTypeContext typeContext; + + /// + /// Constructor to create a new delta link scope. + /// + /// The writer state of this scope. + /// The link for the new scope. + /// The serialization info for the current resource. + /// The navigation source we are going to write entities for. + /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used). + /// The selected properties of this scope. + /// The ODataUri info of this scope. + protected DeltaLinkScope(WriterState state, ODataItem link, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(state, link, navigationSource, entityType, /*skipWriting*/false, selectedProperties, odataUri) + { + Debug.Assert(link != null, "link != null"); + Debug.Assert( + state == WriterState.DeltaLink && link is ODataDeltaLink || + state == WriterState.DeltaDeletedLink && link is ODataDeltaDeletedLink, + "link must be either DeltaLink or DeltaDeletedLink."); + + this.serializationInfo = serializationInfo; + } + + /// + /// Gets or creates the type context to answer basic questions regarding the type info of the resource. + /// + /// Whether writing Json payload. Should always be true. + /// The type context to answer basic questions regarding the type info of the resource. + public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse = true) + { + if (this.typeContext == null) + { + this.typeContext = ODataResourceTypeContext.Create( + this.serializationInfo, + this.NavigationSource, + EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource), + this.fakeEntityType, + writingResponse); + } + + return this.typeContext; + } + } + + /// + /// A scope for writing a single property within a resource. + /// + internal class PropertyInfoScope : Scope + { + /// + /// Constructor to create a new property scope. + /// + /// The property for the new scope. + /// The navigation source. + /// The structured type for the resource containing the property to be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal PropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(WriterState.Property, property, navigationSource, resourceType, /*skipWriting*/ false, selectedProperties, odataUri) + { + ValueWritten = false; + } + + public ODataPropertyInfo Property + { + get + { + Debug.Assert(this.Item is ODataProperty, "The item of a property scope is not an item."); + return this.Item as ODataProperty; + } + } + + internal bool ValueWritten { get; set; } + } + + /// + /// A scope for a nested resource info. + /// + internal class NestedResourceInfoScope : Scope + { + /// + /// Constructor to create a new nested resource info scope. + /// + /// The writer state for the new scope. + /// The nested resource info for the new scope. + /// The navigation source we are going to write resource set for. + /// The type for the items in the resource set to be written (or null if the entity set base type should be used). + /// true if the content of the scope to create should not be written. + /// The selected properties of this scope. + /// The ODataUri info of this scope. + internal NestedResourceInfoScope(WriterState writerState, ODataNestedResourceInfo navLink, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri) + : base(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri) + { + } + + /// + /// Clones this nested resource info scope and sets a new writer state. + /// + /// The to set. + /// The cloned nested resource info scope with the specified writer state. + internal virtual NestedResourceInfoScope Clone(WriterState newWriterState) + { + return new NestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ItemType, this.SkipWriting, this.SelectedProperties, this.ODataUri) + { + DerivedTypeConstraints = this.DerivedTypeConstraints + }; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs new file mode 100644 index 0000000..9ef8298 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -0,0 +1,6182 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData { + using System; + using System.Resources; + + /// + /// Strongly-typed and parameterized string resources. + /// + internal static class Strings { + /// + /// A string like "Value cannot be empty." + /// + internal static string ExceptionUtils_ArgumentStringEmpty { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExceptionUtils_ArgumentStringEmpty); + } + } + + /// + /// A string like "An asynchronous operation was requested on an IODataRequestMessage instance. For asynchronous operations to succeed, the request message instance must implement IODataRequestMessageAsync." + /// + internal static string ODataRequestMessage_AsyncNotAvailable { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataRequestMessage_AsyncNotAvailable); + } + } + + /// + /// A string like "The IODataRequestMessageAsync.GetStreamAsync method returned null. An asynchronous method that returns a task can never return null." + /// + internal static string ODataRequestMessage_StreamTaskIsNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataRequestMessage_StreamTaskIsNull); + } + } + + /// + /// A string like "The IODataRequestMessage.GetStream or IODataRequestMessageAsync.GetStreamAsync method returned a null stream value. The message can never return a null stream." + /// + internal static string ODataRequestMessage_MessageStreamIsNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataRequestMessage_MessageStreamIsNull); + } + } + + /// + /// A string like "An asynchronous operation was requested on an IODataResponseMessage instance. For asynchronous operations to succeed, the response message instance must implement IODataResponseMessageAsync." + /// + internal static string ODataResponseMessage_AsyncNotAvailable { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResponseMessage_AsyncNotAvailable); + } + } + + /// + /// A string like "The IODataResponseMessageAsync.GetStreamAsync method returned null. An asynchronous method that returns a task can never return null." + /// + internal static string ODataResponseMessage_StreamTaskIsNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResponseMessage_StreamTaskIsNull); + } + } + + /// + /// A string like "The IODataResponseMessage.GetStream or IODataResponseMessageAsync.GetStreamAsync method returned a null stream value. The message can never return a null stream." + /// + internal static string ODataResponseMessage_MessageStreamIsNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResponseMessage_MessageStreamIsNull); + } + } + + /// + /// A string like "A writer or stream has been disposed with data still in the buffer. You must call Flush or FlushAsync before calling Dispose when some data has already been written." + /// + internal static string AsyncBufferedStream_WriterDisposedWithoutFlush { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AsyncBufferedStream_WriterDisposedWithoutFlush); + } + } + + /// + /// A string like "ATOM support is obsolete." + /// + internal static string ODataFormat_AtomFormatObsoleted { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataFormat_AtomFormatObsoleted); + } + } + + /// + /// A string like "The format '{0}' does not support writing a payload of kind '{1}'." + /// + internal static string ODataOutputContext_UnsupportedPayloadKindForFormat(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataOutputContext_UnsupportedPayloadKindForFormat, p0, p1); + } + + /// + /// A string like "The format '{0}' does not support reading a payload of kind '{1}'." + /// + internal static string ODataInputContext_UnsupportedPayloadKindForFormat(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataInputContext_UnsupportedPayloadKindForFormat, p0, p1); + } + + /// + /// A string like "The ServiceRoot property in ODataMessageWriterSettings.ODataUri must be set when writing a payload." + /// + internal static string ODataOutputContext_MetadataDocumentUriMissing { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataOutputContext_MetadataDocumentUriMissing); + } + } + + /// + /// A string like "A relative URI value '{0}' was specified in the data to write, but the metadata document URI or the metadata for the item to be written was not specified for the writer. The metadata document URI and the metadata for the item to be written must be provided to the writer when using relative URI values." + /// + internal static string ODataJsonLightSerializer_RelativeUriUsedWithoutMetadataDocumentUriOrMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightSerializer_RelativeUriUsedWithoutMetadataDocumentUriOrMetadata, p0); + } + + /// + /// A string like "A relative URI value '{0}' was specified in the data to write, but a base URI was not specified for the writer. A base URI must be set when using relative URI values." + /// + internal static string ODataWriter_RelativeUriUsedWithoutBaseUriSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriter_RelativeUriUsedWithoutBaseUriSpecified, p0); + } + + /// + /// A string like "The property '{0}' is a stream property, but it is not a property of an ODataResource instance. In OData, stream properties must be properties of ODataResource instances." + /// + internal static string ODataWriter_StreamPropertiesMustBePropertiesOfODataResource(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriter_StreamPropertiesMustBePropertiesOfODataResource, p0); + } + + /// + /// A string like "An invalid state transition has been detected in an OData writer. Cannot transition from state '{0}' to state '{1}'." + /// + internal static string ODataWriterCore_InvalidStateTransition(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidStateTransition, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write a resource or a resource set." + /// + internal static string ODataWriterCore_InvalidTransitionFromStart(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFromStart, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. The only valid action in state '{0}' is to write a property or a nested resource." + /// + internal static string ODataWriterCore_InvalidTransitionFromResource(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFromResource, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}' when writing an OData 4.0 payload. To write content to a deleted resource, please specify ODataVersion 4.01 or greater in MessageWriterSettings." + /// + internal static string ODataWriterCore_InvalidTransitionFrom40DeletedResource(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFrom40DeletedResource, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. You must first call ODataWriter.WriteEnd to finish writing a null ODataResource." + /// + internal static string ODataWriterCore_InvalidTransitionFromNullResource(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFromNullResource, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. The only valid action in state '{0}' is to write a resource." + /// + internal static string ODataWriterCore_InvalidTransitionFromResourceSet(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFromResourceSet, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write a resource or a resource set." + /// + internal static string ODataWriterCore_InvalidTransitionFromExpandedLink(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFromExpandedLink, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. Nothing further can be written once the writer has completed." + /// + internal static string ODataWriterCore_InvalidTransitionFromCompleted(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFromCompleted, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. Nothing can be written once the writer entered the error state." + /// + internal static string ODataWriterCore_InvalidTransitionFromError(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_InvalidTransitionFromError, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. State transition is not allowed while writing an expanded navigation property, complex property or complex collection property." + /// + internal static string ODataJsonLightDeltaWriter_InvalidTransitionFromNestedResource(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightDeltaWriter_InvalidTransitionFromNestedResource, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. Nested resource can only be written within a delta resource." + /// + internal static string ODataJsonLightDeltaWriter_InvalidTransitionToNestedResource(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightDeltaWriter_InvalidTransitionToNestedResource, p0, p1); + } + + /// + /// A string like "WriteStart(expandedResourceSet) was called in an invalid state ('{0}'); WriteStart(expandedResourceSet) is only supported in state 'ExpandedNavigationProperty'." + /// + internal static string ODataJsonLightDeltaWriter_WriteStartExpandedResourceSetCalledInInvalidState(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightDeltaWriter_WriteStartExpandedResourceSetCalledInInvalidState, p0); + } + + /// + /// A string like "ODataWriter.WriteEnd was called in an invalid state ('{0}'); WriteEnd is only supported in states 'Resource', 'ResourceSet', 'NavigationLink', and 'NavigationLinkWithContent'." + /// + internal static string ODataWriterCore_WriteEndCalledInInvalidState(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_WriteEndCalledInInvalidState, p0); + } + + /// + /// A string like "ODataWriter.Write or ODataWriter.WriteEnd was called while streaming a value. Stream or TextWriter must be disposed before calling additional methods on ODataWriter." + /// + internal static string ODataWriterCore_StreamNotDisposed { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_StreamNotDisposed); + } + } + + /// + /// A string like "No Id or key properties were found. A resource in a delta response requires an ID or key properties be specified." + /// + internal static string ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties); + } + } + + /// + /// A string like "The ODataResourceSet.Count must be null for request payloads. Query counts are only supported in responses." + /// + internal static string ODataWriterCore_QueryCountInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_QueryCountInRequest); + } + } + + /// + /// A string like "The NextPageLink must be null for request payloads. Next page links are only supported in responses." + /// + internal static string ODataWriterCore_QueryNextLinkInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_QueryNextLinkInRequest); + } + } + + /// + /// A string like "The DeltaLink must be null for request payloads. Delta links are only supported in responses." + /// + internal static string ODataWriterCore_QueryDeltaLinkInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_QueryDeltaLinkInRequest); + } + } + + /// + /// A string like "Cannot write a delta deleted resource, link, or deleted link using ODataResourceSetWriter. Please use an ODataDeltaResourceSetWriter." + /// + internal static string ODataWriterCore_CannotWriteDeltaWithResourceSetWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_CannotWriteDeltaWithResourceSetWriter); + } + } + + /// + /// A string like "Nested content is not allowed in an OData 4.0 deleted entry. For content in deleted entries, please specify OData 4.01 or greater." + /// + internal static string ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_NestedContentNotAllowedIn40DeletedEntry); + } + } + + /// + /// A string like "Cannot write a top-level resource set with a writer that was created to write a top-level resource." + /// + internal static string ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter); + } + } + + /// + /// A string like "Cannot write a top-level resource with a writer that was created to write a top-level resource set." + /// + internal static string ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter); + } + } + + /// + /// A string like "A synchronous operation was called on an asynchronous writer. Calls on a writer instance must be either all synchronous or all asynchronous." + /// + internal static string ODataWriterCore_SyncCallOnAsyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_SyncCallOnAsyncWriter); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous writer. Calls on a writer instance must be either all synchronous or all asynchronous." + /// + internal static string ODataWriterCore_AsyncCallOnSyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_AsyncCallOnSyncWriter); + } + } + + /// + /// A string like "An entity reference link was written without a surrounding navigation link. The WriteEntityReferenceLink or WriteEntityReferenceLinkAsync methods can only be used when writing the content of a navigation link." + /// + internal static string ODataWriterCore_EntityReferenceLinkWithoutNavigationLink { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink); + } + } + + /// + /// A string like "A deferred link was written into a request. In requests, each nested resource info must have a resource set, resource, or entity reference link written into it." + /// + internal static string ODataWriterCore_DeferredLinkInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_DeferredLinkInRequest); + } + } + + /// + /// A string like "More than one item was written into the content of a nested resource. In OData, a nested resource can only contain more than one item in its content when ODataNestedResourceInfo.IsCollection set to true, and the writer is writing a request." + /// + internal static string ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent); + } + } + + /// + /// A string like "The ODataResourceSet.DeltaLink property must be null for expanded resource sets. Delta link is not supported on expanded resource sets." + /// + internal static string ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet); + } + } + + /// + /// A string like "The Path property in ODataMessageWriterSettings.ODataUri must be set when writing contained elements." + /// + internal static string ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement); + } + } + + /// + /// A string like "Multiple properties with the name '{0}' were detected in a resource or a complex value. In OData, duplicate property names are not allowed." + /// + internal static string DuplicatePropertyNamesNotAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.DuplicatePropertyNamesNotAllowed, p0); + } + + /// + /// A string like "Multiple annotations with the name '{0}' were detected. In OData, duplicate annotations are not allowed." + /// + internal static string DuplicateAnnotationNotAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.DuplicateAnnotationNotAllowed, p0); + } + + /// + /// A string like "Multiple annotations with the name '{0}' were detected for the property with name '{1}'. In OData, duplicate annotations are not allowed." + /// + internal static string DuplicateAnnotationForPropertyNotAllowed(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.DuplicateAnnotationForPropertyNotAllowed, p0, p1); + } + + /// + /// A string like "Multiple annotations with the name '{0}' were detected for the instance annotation with name '{1}'. In OData, duplicate annotations are not allowed." + /// + internal static string DuplicateAnnotationForInstanceAnnotationNotAllowed(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.DuplicateAnnotationForInstanceAnnotationNotAllowed, p0, p1); + } + + /// + /// A string like "An annotation with name '{0}' for property '{1}' was detected after the property, or after an annotation for another property. In OData, annotations for a property must be in a single group and must appear before the property they annotate." + /// + internal static string PropertyAnnotationAfterTheProperty(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.PropertyAnnotationAfterTheProperty, p0, p1); + } + + /// + /// A string like "Cannot convert a value of type '{0}' to the string representation of an Atom primitive value." + /// + internal static string AtomValueUtils_CannotConvertValueToAtomPrimitive(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AtomValueUtils_CannotConvertValueToAtomPrimitive, p0); + } + + /// + /// A string like "The value of type '{0}' is not supported and cannot be converted to a JSON representation." + /// + internal static string ODataJsonWriter_UnsupportedValueType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonWriter_UnsupportedValueType, p0); + } + + /// + /// A string like "An error occurred while processing the OData message." + /// + internal static string ODataException_GeneralError { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataException_GeneralError); + } + } + + /// + /// A string like "An error was read from the payload. See the 'Error' property for more details." + /// + internal static string ODataErrorException_GeneralError { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataErrorException_GeneralError); + } + } + + /// + /// A string like "An error occurred while parsing part of the URI." + /// + internal static string ODataUriParserException_GeneralError { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUriParserException_GeneralError); + } + } + + /// + /// A string like "The ODataMessageWriter has already been used to write a message payload. An ODataMessageWriter can only be used once to write a payload for a given message." + /// + internal static string ODataMessageWriter_WriterAlreadyUsed { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_WriterAlreadyUsed); + } + } + + /// + /// A string like "Top-level entity reference link collection payloads are not allowed in requests." + /// + internal static string ODataMessageWriter_EntityReferenceLinksInRequestNotAllowed { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_EntityReferenceLinksInRequestNotAllowed); + } + } + + /// + /// A string like "An error cannot be written to a request payload. Errors are only supported in responses." + /// + internal static string ODataMessageWriter_ErrorPayloadInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_ErrorPayloadInRequest); + } + } + + /// + /// A string like "A service document cannot be written to request payloads. Service documents are only supported in responses." + /// + internal static string ODataMessageWriter_ServiceDocumentInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_ServiceDocumentInRequest); + } + } + + /// + /// A string like "A metadata document cannot be written to request payloads. Metadata documents are only supported in responses." + /// + internal static string ODataMessageWriter_MetadataDocumentInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_MetadataDocumentInRequest); + } + } + + /// + /// A string like "Cannot write delta in request payload." + /// + internal static string ODataMessageWriter_DeltaInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_DeltaInRequest); + } + } + + /// + /// A string like "Cannot write async in request payload." + /// + internal static string ODataMessageWriter_AsyncInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_AsyncInRequest); + } + } + + /// + /// A string like "Cannot write the value 'null' in top level property; return 204 instead." + /// + internal static string ODataMessageWriter_CannotWriteTopLevelNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotWriteTopLevelNull); + } + } + + /// + /// A string like "Cannot write the value 'null' in raw format." + /// + internal static string ODataMessageWriter_CannotWriteNullInRawFormat { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotWriteNullInRawFormat); + } + } + + /// + /// A string like "Cannot set message headers for the invalid payload kind '{0}'." + /// + internal static string ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind, p0); + } + + /// + /// A string like "The payload kind '{0}' used in the last call to ODataUtils.SetHeadersForPayload is incompatible with the payload being written, which is of kind '{1}'." + /// + internal static string ODataMessageWriter_IncompatiblePayloadKinds(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_IncompatiblePayloadKinds, p0, p1); + } + + /// + /// A string like "The stream property '{0}' cannot be written to the payload as a top level property." + /// + internal static string ODataMessageWriter_CannotWriteStreamPropertyAsTopLevelProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotWriteStreamPropertyAsTopLevelProperty, p0); + } + + /// + /// A string like "The WriteError method or the WriteErrorAsync method on the ODataMessageWriter has already been called to write an error payload. Only a single error payload can be written with each ODataMessageWriter instance." + /// + internal static string ODataMessageWriter_WriteErrorAlreadyCalled { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_WriteErrorAlreadyCalled); + } + } + + /// + /// A string like "The WriteError method or the WriteErrorAsync method on ODataMessageWriter cannot be called after the WriteValue method or the WriteValueAsync method is called. In OData, writing an in-stream error for raw values is not supported." + /// + internal static string ODataMessageWriter_CannotWriteInStreamErrorForRawValues { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotWriteInStreamErrorForRawValues); + } + } + + /// + /// A string like "No model was specified in the ODataMessageWriterSettings; a model has to be provided in the ODataMessageWriterSettings in order to write a metadata document." + /// + internal static string ODataMessageWriter_CannotWriteMetadataWithoutModel { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotWriteMetadataWithoutModel); + } + } + + /// + /// A string like "No model was specified in the ODataMessageWriterSettings; a model has to be provided in the ODataMessageWriterSettings when CreateODataParameterWriter is called with a non-null operation." + /// + internal static string ODataMessageWriter_CannotSpecifyOperationWithoutModel { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotSpecifyOperationWithoutModel); + } + } + + /// + /// A string like "A JsonPaddingFunctionName was specified, but the content-type '{0}' is not supported with Json Padding." + /// + internal static string ODataMessageWriter_JsonPaddingOnInvalidContentType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_JsonPaddingOnInvalidContentType, p0); + } + + /// + /// A string like "The type '{0}' specified as the collection's item type is not primitive, enum or complex. An ODataCollectionWriter can only write collections of primitive, enum or complex values." + /// + internal static string ODataMessageWriter_NonCollectionType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_NonCollectionType, p0); + } + + /// + /// A string like "Not allowed to write top level property '{0}' with 'ODataResourceValue' or collection of resource value." + /// + internal static string ODataMessageWriter_NotAllowedWriteTopLevelPropertyWithResourceValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_NotAllowedWriteTopLevelPropertyWithResourceValue, p0); + } + + /// + /// A string like "Both startResourceXmlCustomizationCallback and endResourceXmlCustomizationCallback must be either null or non-null." + /// + internal static string ODataMessageWriterSettings_MessageWriterSettingsXmlCustomizationCallbacksMustBeSpecifiedBoth { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriterSettings_MessageWriterSettingsXmlCustomizationCallbacksMustBeSpecifiedBoth); + } + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write the collection or to write nothing at all." + /// + internal static string ODataCollectionWriterCore_InvalidTransitionFromStart(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionWriterCore_InvalidTransitionFromStart, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write an item or to write the end of the collection." + /// + internal static string ODataCollectionWriterCore_InvalidTransitionFromCollection(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionWriterCore_InvalidTransitionFromCollection, p0, p1); + } + + /// + /// A string like "Cannot transition from state '{0}' to state '{1}'. The only valid actions in state '{0}' are to write an item or the end of the collection." + /// + internal static string ODataCollectionWriterCore_InvalidTransitionFromItem(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionWriterCore_InvalidTransitionFromItem, p0, p1); + } + + /// + /// A string like "ODataCollectionWriter.WriteEnd was called in an invalid state ('{0}'); WriteEnd is only supported in states 'Start', 'Collection', and 'Item'." + /// + internal static string ODataCollectionWriterCore_WriteEndCalledInInvalidState(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionWriterCore_WriteEndCalledInInvalidState, p0); + } + + /// + /// A string like "A synchronous operation was called on an asynchronous collection writer. All calls on a collection writer instance must be either synchronous or asynchronous." + /// + internal static string ODataCollectionWriterCore_SyncCallOnAsyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionWriterCore_SyncCallOnAsyncWriter); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous collection writer. All calls on a collection writer instance must be either synchronous or asynchronous." + /// + internal static string ODataCollectionWriterCore_AsyncCallOnSyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionWriterCore_AsyncCallOnSyncWriter); + } + } + + /// + /// A string like "An invalid HTTP method '{0}' was detected for a request in a change set. Requests in change sets only support the HTTP methods 'POST', 'PUT', 'DELETE', and 'PATCH'." + /// + internal static string ODataBatch_InvalidHttpMethodForChangeSetRequest(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatch_InvalidHttpMethodForChangeSetRequest, p0); + } + + /// + /// A string like "The header with name '{0}' was not present in the header collection of the batch operation." + /// + internal static string ODataBatchOperationHeaderDictionary_KeyNotFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchOperationHeaderDictionary_KeyNotFound, p0); + } + + /// + /// A string like "Multiple headers with names that match '{0}', when using a case insensitive comparison, have been added. When case-insensitive header names are used, at most one header can be added for each name." + /// + internal static string ODataBatchOperationHeaderDictionary_DuplicateCaseInsensitiveKeys(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchOperationHeaderDictionary_DuplicateCaseInsensitiveKeys, p0); + } + + /// + /// A string like "Writing an in-stream error is not supported when writing a parameter payload." + /// + internal static string ODataParameterWriter_InStreamErrorNotSupported { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriter_InStreamErrorNotSupported); + } + } + + /// + /// A string like "CreateParameterWriter was called on a response message. A parameter payload is only allowed in a request message." + /// + internal static string ODataParameterWriter_CannotCreateParameterWriterOnResponseMessage { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriter_CannotCreateParameterWriterOnResponseMessage); + } + } + + /// + /// A string like "A synchronous operation was called on an asynchronous parameter writer. All calls on a parameter writer instance must be either synchronous or asynchronous." + /// + internal static string ODataParameterWriterCore_SyncCallOnAsyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_SyncCallOnAsyncWriter); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous parameter writer. All calls on a parameter writer instance must be either synchronous or asynchronous." + /// + internal static string ODataParameterWriterCore_AsyncCallOnSyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_AsyncCallOnSyncWriter); + } + } + + /// + /// A string like "WriteStart can only be called once, and it must be called before writing anything else." + /// + internal static string ODataParameterWriterCore_CannotWriteStart { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotWriteStart); + } + } + + /// + /// A string like "WriteValue and CreateCollectionWriter can only be called after WriteStart and before WriteEnd; they cannot be called until the previously created sub-writer is completed." + /// + internal static string ODataParameterWriterCore_CannotWriteParameter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotWriteParameter); + } + } + + /// + /// A string like "WriteEnd can only be called after WriteStart and after the previously created sub-writer has completed." + /// + internal static string ODataParameterWriterCore_CannotWriteEnd { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotWriteEnd); + } + } + + /// + /// A string like "The writer is in either the 'Error' or 'Completed' state. No further writes can be performed on this writer." + /// + internal static string ODataParameterWriterCore_CannotWriteInErrorOrCompletedState { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotWriteInErrorOrCompletedState); + } + } + + /// + /// A string like "The parameter '{0}' has already been written. Duplicate parameter names are not allowed in the parameter payload." + /// + internal static string ODataParameterWriterCore_DuplicatedParameterNameNotAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_DuplicatedParameterNameNotAllowed, p0); + } + + /// + /// A string like "The parameter '{0}' is of Edm type kind '{1}'. You cannot call WriteValue on a parameter that is not of Edm type kinds 'Primitive', 'Enum' or 'Complex'." + /// + internal static string ODataParameterWriterCore_CannotWriteValueOnNonValueTypeKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotWriteValueOnNonValueTypeKind, p0, p1); + } + + /// + /// A string like "The value for parameter '{0}' is of type '{1}'. WriteValue can only write null, ODataEnumValue and primitive types that are not Stream type." + /// + internal static string ODataParameterWriterCore_CannotWriteValueOnNonSupportedValueType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotWriteValueOnNonSupportedValueType, p0, p1); + } + + /// + /// A string like "The parameter '{0}' is of Edm type kind '{1}'. You cannot call CreateCollectionWriter on a parameter that is not of Edm type kind 'Collection'." + /// + internal static string ODataParameterWriterCore_CannotCreateCollectionWriterOnNonCollectionTypeKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotCreateCollectionWriterOnNonCollectionTypeKind, p0, p1); + } + + /// + /// A string like "The parameter '{0}' is of Edm type kind '{1}'. You cannot call CreateResourceWriter on a parameter that is not of Edm type kind 'Entity' or 'Complex'." + /// + internal static string ODataParameterWriterCore_CannotCreateResourceWriterOnNonEntityOrComplexTypeKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotCreateResourceWriterOnNonEntityOrComplexTypeKind, p0, p1); + } + + /// + /// A string like "The parameter '{0}' is of Edm type kind '{1}'. You cannot call CreateResourceSetWriter on a parameter that is not of Edm type kind 'Collection(Entity)' or 'Collection(Complex)'." + /// + internal static string ODataParameterWriterCore_CannotCreateResourceSetWriterOnNonStructuredCollectionTypeKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_CannotCreateResourceSetWriterOnNonStructuredCollectionTypeKind, p0, p1); + } + + /// + /// A string like "The name '{0}' is not a recognized parameter name for operation '{1}'." + /// + internal static string ODataParameterWriterCore_ParameterNameNotFoundInOperation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_ParameterNameNotFoundInOperation, p0, p1); + } + + /// + /// A string like "The parameters {0} of the operation '{1}' could not be found when writing the parameter payload. All parameters present in the operation must be written to the parameter payload." + /// + internal static string ODataParameterWriterCore_MissingParameterInParameterPayload(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterWriterCore_MissingParameterInParameterPayload, p0, p1); + } + + /// + /// A string like "ODataBatchWriter.Flush or ODataBatchWriter.FlushAsync was called while a stream being used to write operation content, obtained from the operation message by using GetStream or GetStreamAsync, was still active. This is not allowed. ODataBatchWriter.Flush or ODataBatchWriter.FlushAsync can only be called when an active stream for the operation content does not exist." + /// + internal static string ODataBatchWriter_FlushOrFlushAsyncCalledInStreamRequestedState { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_FlushOrFlushAsyncCalledInStreamRequestedState); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. You cannot call ODataBatchWriter.WriteEndBatch with an active change set; you must first call ODataBatchWriter.WriteEndChangeset." + /// + internal static string ODataBatchWriter_CannotCompleteBatchWithActiveChangeSet { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_CannotCompleteBatchWithActiveChangeSet); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. You cannot call ODataBatchWriter.WriteStartChangeset with an active change set; you must first call ODataBatchWriter.WriteEndChangeset." + /// + internal static string ODataBatchWriter_CannotStartChangeSetWithActiveChangeSet { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_CannotStartChangeSetWithActiveChangeSet); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. You cannot call ODataBatchWriter.WriteEndChangeset without an active change set; you must first call ODataBatchWriter.WriteStartChangeset." + /// + internal static string ODataBatchWriter_CannotCompleteChangeSetWithoutActiveChangeSet { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_CannotCompleteChangeSetWithoutActiveChangeSet); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. After creating the writer, the only valid methods are ODataBatchWriter.WriteStartBatch and ODataBatchWriter.FlushAsync." + /// + internal static string ODataBatchWriter_InvalidTransitionFromStart { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromStart); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. After calling WriteStartBatch, the only valid methods on ODataBatchWriter are WriteStartChangeset, CreateOperationRequestMessage, CreateOperationResponseMessage, WriteEndBatch, and FlushAsync." + /// + internal static string ODataBatchWriter_InvalidTransitionFromBatchStarted { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromBatchStarted); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. After calling WriteStartChangeset, the only valid methods on ODataBatchWriter are CreateOperationRequestMessage, CreateOperationResponseMessage, WriteEndChangeset, and FlushAsync." + /// + internal static string ODataBatchWriter_InvalidTransitionFromChangeSetStarted { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromChangeSetStarted); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. After calling CreateOperationRequestMessage or CreateOperationResponseMessage, the only valid methods on ODataBatchWriter are WriteStartChangeset, WriteEndChangeset, WriteEndBatch, and FlushAsync." + /// + internal static string ODataBatchWriter_InvalidTransitionFromOperationCreated { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromOperationCreated); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. You cannot use the batch writer while another writer is writing the content of an operation. Dispose the stream for the operation before continuing to use the ODataBatchWriter." + /// + internal static string ODataBatchWriter_InvalidTransitionFromOperationContentStreamRequested { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromOperationContentStreamRequested); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. After writing the content of an operation, the only valid methods on ODataBatchWriter are CreateOperationRequestMessage, CreateOperationResponseMessage, WriteStartChangeset, WriteEndChangeset, WriteEndBatch and FlushAsync." + /// + internal static string ODataBatchWriter_InvalidTransitionFromOperationContentStreamDisposed { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromOperationContentStreamDisposed); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. After calling WriteEndChangeset, the only valid methods on ODataBatchWriter are CreateOperationRequestMessage, CreateOperationResponseMessage, WriteStartChangeset, WriteEndBatch, and FlushAsync." + /// + internal static string ODataBatchWriter_InvalidTransitionFromChangeSetCompleted { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromChangeSetCompleted); + } + } + + /// + /// A string like "An invalid method call on ODataBatchWriter was detected. You can only call ODataBatchWriter.FlushAsync after ODataBatchWriter.WriteEndBatch has been called." + /// + internal static string ODataBatchWriter_InvalidTransitionFromBatchCompleted { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_InvalidTransitionFromBatchCompleted); + } + } + + /// + /// A string like "When writing a batch response, you cannot create a batch operation request message." + /// + internal static string ODataBatchWriter_CannotCreateRequestOperationWhenWritingResponse { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_CannotCreateRequestOperationWhenWritingResponse); + } + } + + /// + /// A string like "When writing a batch request, you cannot create a batch operation response message." + /// + internal static string ODataBatchWriter_CannotCreateResponseOperationWhenWritingRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_CannotCreateResponseOperationWhenWritingRequest); + } + } + + /// + /// A string like "The current batch message contains too many parts. Only batch messages with a maximum number of '{0}' query operations and change sets are allowed." + /// + internal static string ODataBatchWriter_MaxBatchSizeExceeded(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_MaxBatchSizeExceeded, p0); + } + + /// + /// A string like "The current change set contains too many operations. Only change sets with a maximum number of '{0}' operations are allowed." + /// + internal static string ODataBatchWriter_MaxChangeSetSizeExceeded(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_MaxChangeSetSizeExceeded, p0); + } + + /// + /// A string like "A synchronous operation was called on an asynchronous batch writer. Calls on a batch writer instance must be either all synchronous or all asynchronous." + /// + internal static string ODataBatchWriter_SyncCallOnAsyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_SyncCallOnAsyncWriter); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous batch writer. Calls on a batch writer instance must be either all synchronous or all asynchronous." + /// + internal static string ODataBatchWriter_AsyncCallOnSyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_AsyncCallOnSyncWriter); + } + } + + /// + /// A string like "The content ID '{0}' was found more than once in the same change set or same batch request. Content IDs have to be unique across all operations of a change set for OData V4.0 and have to be unique across all operations in the whole batch request for OData V4.01." + /// + internal static string ODataBatchWriter_DuplicateContentIDsNotAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_DuplicateContentIDsNotAllowed, p0); + } + + /// + /// A string like "The WriteError and WriteErrorAsync methods on ODataMessageWriter cannot be called when a batch is being written by using ODataBatchWriter. In OData, writing an in-stream error for a batch payload is not supported." + /// + internal static string ODataBatchWriter_CannotWriteInStreamErrorForBatch { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchWriter_CannotWriteInStreamErrorForBatch); + } + } + + /// + /// A string like "The relative URI '{0}' was specified in a batch operation, but a base URI was not specified for the batch writer or batch reader." + /// + internal static string ODataBatchUtils_RelativeUriUsedWithoutBaseUriSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchUtils_RelativeUriUsedWithoutBaseUriSpecified, p0); + } + + /// + /// A string like "The relative URI '{0}' was specified in a batch operation, but a base URI was not specified for the batch writer or batch reader. When the relative URI is a reference to a content ID, the content ID does not exist in the current change set." + /// + internal static string ODataBatchUtils_RelativeUriStartingWithDollarUsedWithoutBaseUriSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchUtils_RelativeUriStartingWithDollarUsedWithoutBaseUriSpecified, p0); + } + + /// + /// A string like "An attempt to change the properties of the message or to retrieve the payload stream for the message has failed. Either the payload stream has already been requested or the processing of the message has been completed. In both cases, no more changes can be made to the message." + /// + internal static string ODataBatchOperationMessage_VerifyNotCompleted { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchOperationMessage_VerifyNotCompleted); + } + } + + /// + /// A string like "Cannot access a closed stream." + /// + internal static string ODataBatchOperationStream_Disposed { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchOperationStream_Disposed); + } + } + + /// + /// A string like "When reading a batch response, you cannot create a batch operation request message." + /// + internal static string ODataBatchReader_CannotCreateRequestOperationWhenReadingResponse { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_CannotCreateRequestOperationWhenReadingResponse); + } + } + + /// + /// A string like "When reading a batch request, you cannot create a batch operation response message." + /// + internal static string ODataBatchReader_CannotCreateResponseOperationWhenReadingRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_CannotCreateResponseOperationWhenReadingRequest); + } + } + + /// + /// A string like "The method CreateOperationRequestMessage was called in state '{0}', which is not allowed. CreateOperationRequestMessage can only be called in state 'Operation'." + /// + internal static string ODataBatchReader_InvalidStateForCreateOperationRequestMessage(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_InvalidStateForCreateOperationRequestMessage, p0); + } + + /// + /// A string like "A request message for the operation has already been created. You cannot create a request message for the same operation multiple times." + /// + internal static string ODataBatchReader_OperationRequestMessageAlreadyCreated { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_OperationRequestMessageAlreadyCreated); + } + } + + /// + /// A string like "A response message for the operation has already been created. You cannot create a response message for the same operation multiple times." + /// + internal static string ODataBatchReader_OperationResponseMessageAlreadyCreated { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_OperationResponseMessageAlreadyCreated); + } + } + + /// + /// A string like "The method CreateOperationResponseMessage was called in state '{0}', which is not allowed. CreateOperationResponseMessage can only be called in state 'Operation'." + /// + internal static string ODataBatchReader_InvalidStateForCreateOperationResponseMessage(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_InvalidStateForCreateOperationResponseMessage, p0); + } + + /// + /// A string like "You cannot use a batch reader while the stream for the content of an operation is still active. You must first dispose the operation stream before further calls to the batch reader are made." + /// + internal static string ODataBatchReader_CannotUseReaderWhileOperationStreamActive { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_CannotUseReaderWhileOperationStreamActive); + } + } + + /// + /// A string like "A synchronous operation was called on an asynchronous batch reader. Calls on a batch reader instance must be either all synchronous or all asynchronous." + /// + internal static string ODataBatchReader_SyncCallOnAsyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_SyncCallOnAsyncReader); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous batch reader. Calls on a batch reader instance must be either all synchronous or all asynchronous." + /// + internal static string ODataBatchReader_AsyncCallOnSyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_AsyncCallOnSyncReader); + } + } + + /// + /// A string like "ODataBatchReader.ReadAsync or ODataBatchReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'." + /// + internal static string ODataBatchReader_ReadOrReadAsyncCalledInInvalidState(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_ReadOrReadAsyncCalledInInvalidState, p0); + } + + /// + /// A string like "The current batch message contains too many parts. A maximum number of '{0}' query operations and change sets are allowed in a batch message." + /// + internal static string ODataBatchReader_MaxBatchSizeExceeded(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_MaxBatchSizeExceeded, p0); + } + + /// + /// A string like "The current change set contains too many operations. A maximum number of '{0}' operations are allowed in a change set." + /// + internal static string ODataBatchReader_MaxChangeSetSizeExceeded(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_MaxChangeSetSizeExceeded, p0); + } + + /// + /// A string like "An operation was detected, but no message was created for it. You must create a message for every operation found in a batch or change set." + /// + internal static string ODataBatchReader_NoMessageWasCreatedForOperation { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_NoMessageWasCreatedForOperation); + } + } + + /// + /// A string like "Reader mode is not setup correctly." + /// + internal static string ODataBatchReader_ReaderModeNotInitilized { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_ReaderModeNotInitilized); + } + } + + /// + /// A string like "JsonLight batch format requires top level property name 'requests' or 'response' but it is missing." + /// + internal static string ODataBatchReader_JsonBatchTopLevelPropertyMissing { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_JsonBatchTopLevelPropertyMissing); + } + } + + /// + /// A string like "The content ID '{0}' was found more than once in the same change set or same batch request. Content IDs have to be unique across all operations of a change set for OData V4.0 and have to be unique across all operations in the whole batch request for OData V4.01." + /// + internal static string ODataBatchReader_DuplicateContentIDsNotAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_DuplicateContentIDsNotAllowed, p0); + } + + /// + /// A string like "The atomicityGroup ID [{0}] was found duplicated in the batch request. AtomicityGroup IDs have to be adjacent, otherwise would be detected as duplicated." + /// + internal static string ODataBatchReader_DuplicateAtomicityGroupIDsNotAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_DuplicateAtomicityGroupIDsNotAllowed, p0); + } + + /// + /// A string like "Request property [{0}] is required but is missing." + /// + internal static string ODataBatchReader_RequestPropertyMissing(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_RequestPropertyMissing, p0); + } + + /// + /// A string like "The dependsOn request Id [{0}] is same as atomicityGroup property value [{1}], and is not allowed." + /// + internal static string ODataBatchReader_SameRequestIdAsAtomicityGroupIdNotAllowed(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_SameRequestIdAsAtomicityGroupIdNotAllowed, p0, p1); + } + + /// + /// A string like "The dependsOn request Id [{0}] is same as id property value [{1}], and it is not allowed." + /// + internal static string ODataBatchReader_SelfReferenceDependsOnRequestIdNotAllowed(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_SelfReferenceDependsOnRequestIdNotAllowed, p0, p1); + } + + /// + /// A string like "The dependsOn request Id [{0}] is part of atomic group [{1}]. Therefore dependsOn property should refer to atomic group Id [{1}] instead." + /// + internal static string ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_DependsOnRequestIdIsPartOfAtomicityGroupNotAllowed, p0, p1); + } + + /// + /// A string like "The dependsOn Id: [{0}] in request [{1}] is not matching any of the request Id and atomic group Id seen so far. Forward reference is not allowed." + /// + internal static string ODataBatchReader_DependsOnIdNotFound(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_DependsOnIdNotFound, p0, p1); + } + + /// + /// A string like "Absolute URI {0} is not start with the base URI [{1}] specified by the operation message." + /// + internal static string ODataBatchReader_AbsoluteURINotMatchingBaseUri(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_AbsoluteURINotMatchingBaseUri, p0, p1); + } + + /// + /// A string like "Request Id reference [{0}] in Uri [{1}] is not found in effective depends-on-Ids [{2}] of the request." + /// + internal static string ODataBatchReader_ReferenceIdNotIncludedInDependsOn(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_ReferenceIdNotIncludedInDependsOn, p0, p1, p2); + } + + /// + /// A string like "Group id or changeset GUID cannot be null." + /// + internal static string ODataBatch_GroupIdOrChangeSetIdCannotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatch_GroupIdOrChangeSetIdCannotBeNull); + } + } + + /// + /// A string like "Message with id [{0}] is positioned incorrectly: all messages of same groupId [{1}] must be adjacent." + /// + internal static string ODataBatchReader_MessageIdPositionedIncorrectly(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_MessageIdPositionedIncorrectly, p0, p1); + } + + /// + /// A string like "Changeset boundary must have been set by now." + /// + internal static string ODataBatchReader_ReaderStreamChangesetBoundaryCannotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReader_ReaderStreamChangesetBoundaryCannotBeNull); + } + } + + /// + /// A string like "The message header '{0}' is invalid. The header value must be of the format '<header name>: <header value>'." + /// + internal static string ODataBatchReaderStream_InvalidHeaderSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_InvalidHeaderSpecified, p0); + } + + /// + /// A string like "The request line '{0}' is invalid. The request line at the start of each operation must be of the format 'HttpMethod RequestUrl HttpVersion'." + /// + internal static string ODataBatchReaderStream_InvalidRequestLine(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_InvalidRequestLine, p0); + } + + /// + /// A string like "The response line '{0}' is invalid. The response line at the start of each operation must be of the format 'HttpVersion StatusCode StatusCodeString'." + /// + internal static string ODataBatchReaderStream_InvalidResponseLine(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_InvalidResponseLine, p0); + } + + /// + /// A string like "The HTTP version '{0}' used in a batch operation request or response is not valid. The value must be '{1}'." + /// + internal static string ODataBatchReaderStream_InvalidHttpVersionSpecified(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_InvalidHttpVersionSpecified, p0, p1); + } + + /// + /// A string like " The HTTP status code '{0}' is invalid. An HTTP status code must be an integer value." + /// + internal static string ODataBatchReaderStream_NonIntegerHttpStatusCode(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_NonIntegerHttpStatusCode, p0); + } + + /// + /// A string like "The 'Content-Type' header is missing. The 'Content-Type' header must be specified for each MIME part of a batch message." + /// + internal static string ODataBatchReaderStream_MissingContentTypeHeader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_MissingContentTypeHeader); + } + } + + /// + /// A string like "A missing or invalid '{0}' header was found. The '{0}' header must be specified for each batch operation, and its value must be '{1}'." + /// + internal static string ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_MissingOrInvalidContentEncodingHeader, p0, p1); + } + + /// + /// A string like "The '{0}' header value '{1}' is invalid. When this is the start of the change set, the value must be '{2}'; otherwise it must be '{3}'." + /// + internal static string ODataBatchReaderStream_InvalidContentTypeSpecified(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_InvalidContentTypeSpecified, p0, p1, p2, p3); + } + + /// + /// A string like "The content length header '{0}' is not valid. The content length header must be a valid Int32 literal and must be greater than or equal to 0." + /// + internal static string ODataBatchReaderStream_InvalidContentLengthSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_InvalidContentLengthSpecified, p0); + } + + /// + /// A string like "The header '{0}' was specified multiple times. Each header must appear only once in a batch part." + /// + internal static string ODataBatchReaderStream_DuplicateHeaderFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_DuplicateHeaderFound, p0); + } + + /// + /// A string like "Nested change sets in a batch payload are not supported." + /// + internal static string ODataBatchReaderStream_NestedChangesetsAreNotSupported { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_NestedChangesetsAreNotSupported); + } + } + + /// + /// A string like "Invalid multi-byte encoding '{0}' detected. Multi-byte encodings other than UTF-8 are only supported for operation payloads. They are not supported in batch or change set parts." + /// + internal static string ODataBatchReaderStream_MultiByteEncodingsNotSupported(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_MultiByteEncodingsNotSupported, p0); + } + + /// + /// A string like "Encountered an unexpected end of input while reading the batch payload." + /// + internal static string ODataBatchReaderStream_UnexpectedEndOfInput { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStream_UnexpectedEndOfInput); + } + } + + /// + /// A string like "Too many white spaces after a boundary delimiter and before the terminating line resource set. For security reasons, the total number of characters for a boundary including white spaces must not exceed {0}." + /// + internal static string ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataBatchReaderStreamBuffer_BoundaryLineSecurityLimitReached, p0); + } + + /// + /// A string like "When not writing an async response, you cannot create an async response message." + /// + internal static string ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncWriter_CannotCreateResponseWhenNotWritingResponse); + } + } + + /// + /// A string like "You cannot create an async response message more than once." + /// + internal static string ODataAsyncWriter_CannotCreateResponseMoreThanOnce { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncWriter_CannotCreateResponseMoreThanOnce); + } + } + + /// + /// A string like "A synchronous operation was called on an asynchronous async writer. Calls on an async writer instance must be either all synchronous or all asynchronous." + /// + internal static string ODataAsyncWriter_SyncCallOnAsyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncWriter_SyncCallOnAsyncWriter); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous async writer. Calls on an async writer instance must be either all synchronous or all asynchronous." + /// + internal static string ODataAsyncWriter_AsyncCallOnSyncWriter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncWriter_AsyncCallOnSyncWriter); + } + } + + /// + /// A string like "The WriteError and WriteErrorAsync methods on ODataMessageWriter cannot be called when an async message is being written by using ODataAsyncWriter. In OData, writing an in-stream error for an async payload is not supported." + /// + internal static string ODataAsyncWriter_CannotWriteInStreamErrorForAsync { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncWriter_CannotWriteInStreamErrorForAsync); + } + } + + /// + /// A string like "The message header '{0}' is invalid. The header value must be of the format '<header name>: <header value>'." + /// + internal static string ODataAsyncReader_InvalidHeaderSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_InvalidHeaderSpecified, p0); + } + + /// + /// A string like "When not reading an async response, you cannot create an async response message." + /// + internal static string ODataAsyncReader_CannotCreateResponseWhenNotReadingResponse { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_CannotCreateResponseWhenNotReadingResponse); + } + } + + /// + /// A string like "The response line '{0}' is invalid. The response line at the start of the async response must be of the format 'HttpVersion StatusCode StatusCodeString'." + /// + internal static string ODataAsyncReader_InvalidResponseLine(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_InvalidResponseLine, p0); + } + + /// + /// A string like "The HTTP version '{0}' used in an async response is not valid. The value must be '{1}'." + /// + internal static string ODataAsyncReader_InvalidHttpVersionSpecified(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_InvalidHttpVersionSpecified, p0, p1); + } + + /// + /// A string like "The HTTP status code '{0}' is invalid. An HTTP status code must be an integer value." + /// + internal static string ODataAsyncReader_NonIntegerHttpStatusCode(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_NonIntegerHttpStatusCode, p0); + } + + /// + /// A string like "The header '{0}' was specified multiple times. Each header must appear only once." + /// + internal static string ODataAsyncReader_DuplicateHeaderFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_DuplicateHeaderFound, p0); + } + + /// + /// A string like "Invalid multi-byte encoding '{0}' detected. Multi-byte encodings other than UTF-8 are only supported for async payloads. They are not supported in batch or change set parts." + /// + internal static string ODataAsyncReader_MultiByteEncodingsNotSupported(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_MultiByteEncodingsNotSupported, p0); + } + + /// + /// A string like "Invalid new line '{0}' encountered. Should be '\r\n'." + /// + internal static string ODataAsyncReader_InvalidNewLineEncountered(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_InvalidNewLineEncountered, p0); + } + + /// + /// A string like "Encountered an unexpected end of input while reading the async payload. Could be due to calling CreateResponseMessage() more than once." + /// + internal static string ODataAsyncReader_UnexpectedEndOfInput { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_UnexpectedEndOfInput); + } + } + + /// + /// A string like "A synchronous operation was called on an asynchronous async reader. Calls on an async reader instance must be either all synchronous or all asynchronous." + /// + internal static string ODataAsyncReader_SyncCallOnAsyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_SyncCallOnAsyncReader); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous async reader. Calls on an async reader instance must be either all synchronous or all asynchronous." + /// + internal static string ODataAsyncReader_AsyncCallOnSyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncReader_AsyncCallOnSyncReader); + } + } + + /// + /// A string like "The MIME type '{0}' is invalid or unspecified." + /// + internal static string HttpUtils_MediaTypeUnspecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_MediaTypeUnspecified, p0); + } + + /// + /// A string like "The MIME type '{0}' requires a '/' character between type and subtype, such as 'text/plain'." + /// + internal static string HttpUtils_MediaTypeRequiresSlash(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_MediaTypeRequiresSlash, p0); + } + + /// + /// A string like "The MIME type '{0}' requires a subtype definition." + /// + internal static string HttpUtils_MediaTypeRequiresSubType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_MediaTypeRequiresSubType, p0); + } + + /// + /// A string like "The MIME type is missing a parameter value for a parameter with the name '{0}'." + /// + internal static string HttpUtils_MediaTypeMissingParameterValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_MediaTypeMissingParameterValue, p0); + } + + /// + /// A string like "The MIME type is missing a parameter name for a parameter definition." + /// + internal static string HttpUtils_MediaTypeMissingParameterName { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_MediaTypeMissingParameterName); + } + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the escape character '{3}' is not inside a quoted-string." + /// + internal static string HttpUtils_EscapeCharWithoutQuotes(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_EscapeCharWithoutQuotes, p0, p1, p2, p3); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because it terminates with the escape character '{3}'. In a quoted-string, the escape characters must always be followed by a character." + /// + internal static string HttpUtils_EscapeCharAtEnd(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_EscapeCharAtEnd, p0, p1, p2, p3); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the closing quote character was not found for the quoted-string." + /// + internal static string HttpUtils_ClosingQuoteNotFound(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_ClosingQuoteNotFound, p0, p1, p2); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the character '{3}' is not allowed in a quoted-string. For more information, see RFC 2616, Sections 3.6 and 2.2." + /// + internal static string HttpUtils_InvalidCharacterInQuotedParameterValue(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_InvalidCharacterInQuotedParameterValue, p0, p1, p2, p3); + } + + /// + /// A string like "The value for the Content-Type header is missing." + /// + internal static string HttpUtils_ContentTypeMissing { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_ContentTypeMissing); + } + } + + /// + /// A string like "The MIME type '{0}' requires a semi-colon character (';') before a parameter definition." + /// + internal static string HttpUtils_MediaTypeRequiresSemicolonBeforeParameter(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_MediaTypeRequiresSemicolonBeforeParameter, p0); + } + + /// + /// A string like "An invalid quality value was detected in the header string '{0}'; quality values must start with '0' or '1' but not with '{1}'." + /// + internal static string HttpUtils_InvalidQualityValueStartChar(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_InvalidQualityValueStartChar, p0, p1); + } + + /// + /// A string like "An invalid quality value '{0}' was detected in the header string '{1}'; quality values must be in the range [0, 1]." + /// + internal static string HttpUtils_InvalidQualityValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_InvalidQualityValue, p0, p1); + } + + /// + /// A string like "An error occurred when converting the character '{0}' to an integer." + /// + internal static string HttpUtils_CannotConvertCharToInt(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_CannotConvertCharToInt, p0); + } + + /// + /// A string like "The separator ',' was missing between charset values in the header '{0}'." + /// + internal static string HttpUtils_MissingSeparatorBetweenCharsets(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_MissingSeparatorBetweenCharsets, p0); + } + + /// + /// A string like "A separator character was missing between charset values in the header '{0}'." + /// + internal static string HttpUtils_InvalidSeparatorBetweenCharsets(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_InvalidSeparatorBetweenCharsets, p0); + } + + /// + /// A string like "An invalid (empty) charset name found in the header '{0}'." + /// + internal static string HttpUtils_InvalidCharsetName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_InvalidCharsetName, p0); + } + + /// + /// A string like "An unexpected end of the q-Value was detected in the header '{0}'." + /// + internal static string HttpUtils_UnexpectedEndOfQValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_UnexpectedEndOfQValue, p0); + } + + /// + /// A string like "The expected literal '{0}' was not found at position '{1}' in the string '{2}'." + /// + internal static string HttpUtils_ExpectedLiteralNotFoundInString(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_ExpectedLiteralNotFoundInString, p0, p1, p2); + } + + /// + /// A string like "The string '{0}' cannot be converted into a supported HTTP method. The only supported HTTP methods are GET, DELETE, PUT, POST and PATCH." + /// + internal static string HttpUtils_InvalidHttpMethodString(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_InvalidHttpMethodString, p0); + } + + /// + /// A string like "The specified content type '{0}' contains either no media type or more than one media type, which is not allowed. You must specify exactly one media type as the content type." + /// + internal static string HttpUtils_NoOrMoreThanOneContentTypeSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpUtils_NoOrMoreThanOneContentTypeSpecified, p0); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because '{3}' is not a recognized separator. The supported separators are ',', ';', and '='." + /// + internal static string HttpHeaderValueLexer_UnrecognizedSeparator(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpHeaderValueLexer_UnrecognizedSeparator, p0, p1, p2, p3); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because a token is expected but a quoted-string is found instead." + /// + internal static string HttpHeaderValueLexer_TokenExpectedButFoundQuotedString(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpHeaderValueLexer_TokenExpectedButFoundQuotedString, p0, p1, p2); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because a token or a quoted-string is expected at this position but were not found." + /// + internal static string HttpHeaderValueLexer_FailedToReadTokenOrQuotedString(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpHeaderValueLexer_FailedToReadTokenOrQuotedString, p0, p1, p2); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because '{3}' is not a valid separator after a quoted-string." + /// + internal static string HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString, p0, p1, p2, p3); + } + + /// + /// A string like "An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the header value should not end with the separator '{3}'." + /// + internal static string HttpHeaderValueLexer_EndOfFileAfterSeparator(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpHeaderValueLexer_EndOfFileAfterSeparator, p0, p1, p2, p3); + } + + /// + /// A string like "The character set '{0}' is not supported." + /// + internal static string MediaType_EncodingNotSupported(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MediaType_EncodingNotSupported, p0); + } + + /// + /// A string like "A supported MIME type could not be found that matches the acceptable MIME types for the request. The supported type(s) '{0}' do not match any of the acceptable MIME types '{1}'." + /// + internal static string MediaTypeUtils_DidNotFindMatchingMediaType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MediaTypeUtils_DidNotFindMatchingMediaType, p0, p1); + } + + /// + /// A string like "A supported MIME type could not be found that matches the content type of the response. None of the supported type(s) '{0}' matches the content type '{1}'." + /// + internal static string MediaTypeUtils_CannotDetermineFormatFromContentType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MediaTypeUtils_CannotDetermineFormatFromContentType, p0, p1); + } + + /// + /// A string like "The specified content type '{0}' contains either no media type or more than one media type, which is not allowed. You must specify exactly one media type as the content type." + /// + internal static string MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified, p0); + } + + /// + /// A string like "The content type '{0}' specifies a batch payload; however, the payload either does not include a batch boundary or includes more than one boundary. In OData, batch payload content types must specify exactly one batch boundary in the '{1}' parameter of the content type." + /// + internal static string MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads, p0, p1); + } + + /// + /// A string like "Expected literal type token but found token '{0}'." + /// + internal static string ExpressionLexer_ExpectedLiteralToken(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_ExpectedLiteralToken, p0); + } + + /// + /// A string like "The type '{0}' is not supported when converting to a URI literal." + /// + internal static string ODataUriUtils_ConvertToUriLiteralUnsupportedType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUriUtils_ConvertToUriLiteralUnsupportedType, p0); + } + + /// + /// A string like "An IEdmTypeReference must be provided with a matching IEdmModel. No model was provided." + /// + internal static string ODataUriUtils_ConvertFromUriLiteralTypeRefWithoutModel { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUriUtils_ConvertFromUriLiteralTypeRefWithoutModel); + } + } + + /// + /// A string like "Type verification failed. Expected type '{0}' but received the value '{1}'." + /// + internal static string ODataUriUtils_ConvertFromUriLiteralTypeVerificationFailure(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUriUtils_ConvertFromUriLiteralTypeVerificationFailure, p0, p1); + } + + /// + /// A string like "Type verification failed. Expected type '{0}' but received non-matching null value with associated type '{1}'." + /// + internal static string ODataUriUtils_ConvertFromUriLiteralNullTypeVerificationFailure(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUriUtils_ConvertFromUriLiteralNullTypeVerificationFailure, p0, p1); + } + + /// + /// A string like "Type verification failed. Expected non-nullable type '{0}' but received a null value." + /// + internal static string ODataUriUtils_ConvertFromUriLiteralNullOnNonNullableType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUriUtils_ConvertFromUriLiteralNullOnNonNullableType, p0); + } + + /// + /// A string like "The value of type '{0}' could not be converted to a raw string." + /// + internal static string ODataUtils_CannotConvertValueToRawString(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUtils_CannotConvertValueToRawString, p0); + } + + /// + /// A string like "A default MIME type could not be found for the requested payload in format '{0}'." + /// + internal static string ODataUtils_DidNotFindDefaultMediaType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUtils_DidNotFindDefaultMediaType, p0); + } + + /// + /// A string like "The value '{0}' of the OData-Version HTTP header is invalid. Only '4.0' and '4.01' are supported as values for the OData-Version header." + /// + internal static string ODataUtils_UnsupportedVersionHeader(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUtils_UnsupportedVersionHeader, p0); + } + + /// + /// A string like "An OData version of {0} was specified and the maximum supported OData version is {1}." + /// + internal static string ODataUtils_MaxProtocolVersionExceeded(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUtils_MaxProtocolVersionExceeded, p0, p1); + } + + /// + /// A string like "An invalid enum value was specified for the version number." + /// + internal static string ODataUtils_UnsupportedVersionNumber { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUtils_UnsupportedVersionNumber); + } + } + + /// + /// A string like "The provided model does not contain an entity container." + /// + internal static string ODataUtils_ModelDoesNotHaveContainer { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUtils_ModelDoesNotHaveContainer); + } + } + + /// + /// A string like "The value returned by the '{0}' property cannot be modified until the end of the owning resource is reported by the reader." + /// + internal static string ReaderUtils_EnumerableModified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderUtils_EnumerableModified, p0); + } + + /// + /// A string like "A null value was found with the expected type '{0}[Nullable=False]'. The expected type '{0}[Nullable=False]' does not allow null values." + /// + internal static string ReaderValidationUtils_NullValueForNonNullableType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_NullValueForNonNullableType, p0); + } + + /// + /// A string like "A null value was found for the property named '{0}', which has the expected type '{1}[Nullable=False]'. The expected type '{1}[Nullable=False]' does not allow null values." + /// + internal static string ReaderValidationUtils_NullNamedValueForNonNullableType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_NullNamedValueForNonNullableType, p0, p1); + } + + /// + /// A string like "No URI value was found for an entity reference link. A single URI value was expected." + /// + internal static string ReaderValidationUtils_EntityReferenceLinkMissingUri { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_EntityReferenceLinkMissingUri); + } + } + + /// + /// A string like "A value without a type name was found and no expected type is available. When the model is specified, each value in the payload must have a type which can be either specified in the payload, explicitly by the caller or implicitly inferred from the parent value." + /// + internal static string ReaderValidationUtils_ValueWithoutType { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ValueWithoutType); + } + } + + /// + /// A string like "A resource without a type name was found, but no expected type was specified. To allow entries without type information, the expected type must also be specified when the model is specified." + /// + internal static string ReaderValidationUtils_ResourceWithoutType { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ResourceWithoutType); + } + } + + /// + /// A string like "Cannot convert the literal '{0}' to the expected type '{1}'." + /// + internal static string ReaderValidationUtils_CannotConvertPrimitiveValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_CannotConvertPrimitiveValue, p0, p1); + } + + /// + /// A string like "The base URI '{0}' specified in ODataMessageReaderSettings.BaseUri is invalid; it must be either null or an absolute URI." + /// + internal static string ReaderValidationUtils_MessageReaderSettingsBaseUriMustBeNullOrAbsolute(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_MessageReaderSettingsBaseUriMustBeNullOrAbsolute, p0); + } + + /// + /// A string like "The ODataMessageReaderSettings.UndeclaredPropertyBehaviorKinds is not set to ODataUndeclaredPropertyBehaviorKinds.None. When reading request payloads, the ODataMessageReaderSettings.UndeclaredPropertyBehaviorKinds property must be set to ODataUndeclaredPropertyBehaviorKinds.None; other values are not supported." + /// + internal static string ReaderValidationUtils_UndeclaredPropertyBehaviorKindSpecifiedOnRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_UndeclaredPropertyBehaviorKindSpecifiedOnRequest); + } + } + + /// + /// A string like "The context URI '{0}' references the entity set with name '{1}'; however, the name of the expected entity set is '{2}' and does not match the entity set referenced in the context URI." + /// + internal static string ReaderValidationUtils_ContextUriValidationInvalidExpectedEntitySet(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ContextUriValidationInvalidExpectedEntitySet, p0, p1, p2); + } + + /// + /// A string like "The context URI '{0}' references the entity type with name '{1}'; however, the name of the expected entity type is '{2}' which is not compatible with the entity type with name '{1}'." + /// + internal static string ReaderValidationUtils_ContextUriValidationInvalidExpectedEntityType(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ContextUriValidationInvalidExpectedEntityType, p0, p1, p2); + } + + /// + /// A string like "The context URI '{0}' references the property with name '{1}' on type '{2}'; however, the name of the expected property is '{3}'." + /// + internal static string ReaderValidationUtils_ContextUriValidationNonMatchingPropertyNames(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ContextUriValidationNonMatchingPropertyNames, p0, p1, p2, p3); + } + + /// + /// A string like "The context URI '{0}' references the property with name '{1}' on type '{2}'; however, the declaring type of the expected property is '{3}'." + /// + internal static string ReaderValidationUtils_ContextUriValidationNonMatchingDeclaringTypes(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ContextUriValidationNonMatchingDeclaringTypes, p0, p1, p2, p3); + } + + /// + /// A string like "The property or operation import name '{0}' was read from the payload; however, the name of the expected property or operation import is '{1}'." + /// + internal static string ReaderValidationUtils_NonMatchingPropertyNames(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_NonMatchingPropertyNames, p0, p1); + } + + /// + /// A string like "The context URI '{0}' references the type '{1}'; however the expected type is '{2}'." + /// + internal static string ReaderValidationUtils_TypeInContextUriDoesNotMatchExpectedType(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_TypeInContextUriDoesNotMatchExpectedType, p0, p1, p2); + } + + /// + /// A string like "The context URI '{0}' refers to the item type '{1}' which is not assignable to the expected item type '{2}'." + /// + internal static string ReaderValidationUtils_ContextUriDoesNotReferTypeAssignableToExpectedType(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ContextUriDoesNotReferTypeAssignableToExpectedType, p0, p1, p2); + } + + /// + /// A string like "The value type '{0}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation on {1} '{2}'." + /// + internal static string ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint, p0, p1, p2); + } + + /// + /// A string like "The ODataMessageReader has already been used to read a message payload. An ODataMessageReader can only be used once to read a payload for a given message." + /// + internal static string ODataMessageReader_ReaderAlreadyUsed { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ReaderAlreadyUsed); + } + } + + /// + /// A string like "A top-level error cannot be read from request payloads. Top-level errors are only supported in responses." + /// + internal static string ODataMessageReader_ErrorPayloadInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ErrorPayloadInRequest); + } + } + + /// + /// A string like "A service document cannot be read from request payloads. Service documents are only supported in responses." + /// + internal static string ODataMessageReader_ServiceDocumentInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ServiceDocumentInRequest); + } + } + + /// + /// A string like "A metadata document cannot be read from request payloads. Metadata documents are only supported in responses." + /// + internal static string ODataMessageReader_MetadataDocumentInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_MetadataDocumentInRequest); + } + } + + /// + /// A string like "Delta are only supported in responses." + /// + internal static string ODataMessageReader_DeltaInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_DeltaInRequest); + } + } + + /// + /// A string like "The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The expected type can only be specified if metadata is made available to the reader." + /// + internal static string ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ExpectedTypeSpecifiedWithoutMetadata, p0); + } + + /// + /// A string like "The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The entity set can only be specified if metadata is made available to the reader." + /// + internal static string ODataMessageReader_EntitySetSpecifiedWithoutMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_EntitySetSpecifiedWithoutMetadata, p0); + } + + /// + /// A string like "The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The operation import can only be specified if metadata is made available to the reader." + /// + internal static string ODataMessageReader_OperationImportSpecifiedWithoutMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_OperationImportSpecifiedWithoutMetadata, p0); + } + + /// + /// A string like "The parameter '{0}' is specified with a non-null value, but no metadata is available for the reader. The operation can only be specified if metadata is made available to the reader." + /// + internal static string ODataMessageReader_OperationSpecifiedWithoutMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_OperationSpecifiedWithoutMetadata, p0); + } + + /// + /// A string like "The expected type for a collection reader is of kind '{0}'. Only types of Primitive or ComplexType kind can be specified as the expected type for a collection reader." + /// + internal static string ODataMessageReader_ExpectedCollectionTypeWrongKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ExpectedCollectionTypeWrongKind, p0); + } + + /// + /// A string like "The expected type for property reading is of entity collection kind. Top-level properties can only be of primitive, complex, primitive collection or complex collection kind." + /// + internal static string ODataMessageReader_ExpectedPropertyTypeEntityCollectionKind { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ExpectedPropertyTypeEntityCollectionKind); + } + } + + /// + /// A string like "The expected type for property reading is of entity kind. Top-level properties cannot be of entity type." + /// + internal static string ODataMessageReader_ExpectedPropertyTypeEntityKind { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ExpectedPropertyTypeEntityKind); + } + } + + /// + /// A string like "The expected type for property reading is Edm.Stream. Top-level properties cannot be of stream type." + /// + internal static string ODataMessageReader_ExpectedPropertyTypeStream { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ExpectedPropertyTypeStream); + } + } + + /// + /// A string like "The expected type for a value is of kind '{0}'. Only types of Primitive kind can be specified as the expected type for reading a value." + /// + internal static string ODataMessageReader_ExpectedValueTypeWrongKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ExpectedValueTypeWrongKind, p0); + } + + /// + /// A string like "A missing or empty content type header was found when trying to read a message. The content type header is required." + /// + internal static string ODataMessageReader_NoneOrEmptyContentTypeHeader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_NoneOrEmptyContentTypeHeader); + } + } + + /// + /// A string like "The wildcard '*' was detected in the value '{0}' of the content type header. The value of the content type header cannot contain wildcards." + /// + internal static string ODataMessageReader_WildcardInContentType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_WildcardInContentType, p0); + } + + /// + /// A string like "GetFormat was called before reading was started. GetFormat can only be called after a read method was called or a reader was created." + /// + internal static string ODataMessageReader_GetFormatCalledBeforeReadingStarted { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_GetFormatCalledBeforeReadingStarted); + } + } + + /// + /// A string like "DetectPayloadKind or DetectPayloadKindAsync was called more than once; DetectPayloadKind or DetectPayloadKindAsync can only be called once." + /// + internal static string ODataMessageReader_DetectPayloadKindMultipleTimes { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_DetectPayloadKindMultipleTimes); + } + } + + /// + /// A string like "Payload kind detection has not completed. Read or create methods cannot be called on the ODataMessageReader before payload kind detection is complete." + /// + internal static string ODataMessageReader_PayloadKindDetectionRunning { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_PayloadKindDetectionRunning); + } + } + + /// + /// A string like "The ODataMessageReader is using the server behavior for WCF Data Services, as specified in its settings. Payload kind detection is not supported when using the WCF Data services server behavior." + /// + internal static string ODataMessageReader_PayloadKindDetectionInServerMode { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_PayloadKindDetectionInServerMode); + } + } + + /// + /// A string like "A parameter payload cannot be read from a response payload. Parameter payloads are only supported in requests." + /// + internal static string ODataMessageReader_ParameterPayloadInResponse { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_ParameterPayloadInResponse); + } + } + + /// + /// A string like "The navigation property '{0}' with singleton cardinality on type '{1}' was specified for reading a collection of entity reference links. A navigation property with collection cardinality has to be provided." + /// + internal static string ODataMessageReader_SingletonNavigationPropertyForEntityReferenceLinks(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageReader_SingletonNavigationPropertyForEntityReferenceLinks, p0, p1); + } + + /// + /// A string like "An attempt was made to modify the message. The message cannot be modified." + /// + internal static string ODataAsyncResponseMessage_MustNotModifyMessage { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAsyncResponseMessage_MustNotModifyMessage); + } + } + + /// + /// A string like "An attempt was made to modify the message. The message cannot be modified." + /// + internal static string ODataMessage_MustNotModifyMessage { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessage_MustNotModifyMessage); + } + } + + /// + /// A string like "A synchronous operation was called on an asynchronous reader. Calls on a reader instance must be either all synchronous or all asynchronous." + /// + internal static string ODataReaderCore_SyncCallOnAsyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataReaderCore_SyncCallOnAsyncReader); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous reader. Calls on a reader instance must be either all synchronous or all asynchronous." + /// + internal static string ODataReaderCore_AsyncCallOnSyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataReaderCore_AsyncCallOnSyncReader); + } + } + + /// + /// A string like "ODataReader.ReadAsync or ODataReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'." + /// + internal static string ODataReaderCore_ReadOrReadAsyncCalledInInvalidState(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataReaderCore_ReadOrReadAsyncCalledInInvalidState, p0); + } + + /// + /// A string like "CreateReadStream was called in an invalid state. CreateReadStream can only be called once in ReaderState.Stream." + /// + internal static string ODataReaderCore_CreateReadStreamCalledInInvalidState { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataReaderCore_CreateReadStreamCalledInInvalidState); + } + } + + /// + /// A string like "CreateTextReader was called in an invalid state. CreateTextReader can only be called once in ReaderState.Stream." + /// + internal static string ODataReaderCore_CreateTextReaderCalledInInvalidState { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataReaderCore_CreateTextReaderCalledInInvalidState); + } + } + + /// + /// A string like "Read called with an open stream or textreader. Please close any open streams or text readers before calling Read." + /// + internal static string ODataReaderCore_ReadCalledWithOpenStream { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataReaderCore_ReadCalledWithOpenStream); + } + } + + /// + /// A string like "Calling Read or ReadAsync on an ODataReader instance is not allowed in state '{0}'." + /// + internal static string ODataReaderCore_NoReadCallsAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataReaderCore_NoReadCallsAllowed, p0); + } + + /// + /// A string like "Attempted to write a value for a property {0} whose value has already been written." + /// + internal static string ODataWriterCore_PropertyValueAlreadyWritten(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_PropertyValueAlreadyWritten, p0); + } + + /// + /// A string like "A node of type '{0}' was read from the JSON reader when trying to read the resources of a resource set. A 'StartObject' or 'EndArray' node was expected." + /// + internal static string ODataJsonReader_CannotReadResourcesOfResourceSet(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReader_CannotReadResourcesOfResourceSet, p0); + } + + /// + /// A string like "Cannot convert a value of type 'Edm.Int32' to the expected target type '{0}'." + /// + internal static string ODataJsonReaderUtils_CannotConvertInt32(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_CannotConvertInt32, p0); + } + + /// + /// A string like "Cannot convert a value of type 'Edm.Double' to the expected target type '{0}'." + /// + internal static string ODataJsonReaderUtils_CannotConvertDouble(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_CannotConvertDouble, p0); + } + + /// + /// A string like "Cannot convert a value of type 'Edm.Boolean' to the expected target type '{0}'." + /// + internal static string ODataJsonReaderUtils_CannotConvertBoolean(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_CannotConvertBoolean, p0); + } + + /// + /// A string like "Cannot convert a value of type 'Edm.Decimal' to the expected target type '{0}'." + /// + internal static string ODataJsonReaderUtils_CannotConvertDecimal(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_CannotConvertDecimal, p0); + } + + /// + /// A string like "Cannot convert a value of type 'Edm.DateTime' to the expected target type '{0}'." + /// + internal static string ODataJsonReaderUtils_CannotConvertDateTime(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_CannotConvertDateTime, p0); + } + + /// + /// A string like "Cannot convert a value of type 'Edm.DateTimeOffset' to the expected target type '{0}'." + /// + internal static string ODataJsonReaderUtils_CannotConvertDateTimeOffset(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_CannotConvertDateTimeOffset, p0); + } + + /// + /// A string like "Cannot convert a value to target type '{0}' because of conflict between input format string/number and parameter 'IEEE754Compatible' false/true." + /// + internal static string ODataJsonReaderUtils_ConflictBetweenInputFormatAndParameter(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_ConflictBetweenInputFormatAndParameter, p0); + } + + /// + /// A string like "Multiple '{0}' properties were found in an error or inner error object. In OData, an error or inner error must have at most one '{0}' property." + /// + internal static string ODataJsonReaderUtils_MultipleErrorPropertiesWithSameName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderUtils_MultipleErrorPropertiesWithSameName, p0); + } + + /// + /// A string like "Multiple operations have the same 'Metadata' property value of '{0}'. The 'Target' property value of these operations must be set to a non-null value." + /// + internal static string ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustSpecifyTarget(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustSpecifyTarget, p0); + } + + /// + /// A string like "Multiple operations have the same 'Metadata' property value of '{0}' and the same 'Target' property value of '{1}'. When multiple operations have the same 'Metadata' property value, their 'Target' property values must be unique." + /// + internal static string ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustNotHaveDuplicateTarget(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceSerializer_ActionsAndFunctionsGroupMustNotHaveDuplicateTarget, p0, p1); + } + + /// + /// A string like "A property with name '{0}' was found in the error object when reading a top-level error. In OData, a top-level error object must have exactly one property with name 'error'." + /// + internal static string ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonErrorDeserializer_TopLevelErrorWithInvalidProperty, p0); + } + + /// + /// A string like "A property with name '{0}' was found in the message value of a top-level error. In OData, the message value of a top-level error value can only have properties with name 'lang' or 'value'." + /// + internal static string ODataJsonErrorDeserializer_TopLevelErrorMessageValueWithInvalidProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonErrorDeserializer_TopLevelErrorMessageValueWithInvalidProperty, p0); + } + + /// + /// A string like "ODataCollectionReader.ReadAsync or ODataCollectionReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'." + /// + internal static string ODataCollectionReaderCore_ReadOrReadAsyncCalledInInvalidState(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionReaderCore_ReadOrReadAsyncCalledInInvalidState, p0); + } + + /// + /// A string like "A synchronous operation was called on an asynchronous collection reader. All calls on a collection reader instance must be either synchronous or asynchronous." + /// + internal static string ODataCollectionReaderCore_SyncCallOnAsyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionReaderCore_SyncCallOnAsyncReader); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous collection reader. All calls on a collection reader instance must be either synchronous or asynchronous." + /// + internal static string ODataCollectionReaderCore_AsyncCallOnSyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionReaderCore_AsyncCallOnSyncReader); + } + } + + /// + /// A string like "The current state of the collection reader is '{0}'; however, the expected item type of a collection reader can only be set in state '{1}'." + /// + internal static string ODataCollectionReaderCore_ExpectedItemTypeSetInInvalidState(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataCollectionReaderCore_ExpectedItemTypeSetInInvalidState, p0, p1); + } + + /// + /// A string like "ODataParameterReader.ReadAsync or ODataParameterReader.Read was called in an invalid state. No further calls can be made to the reader in state '{0}'." + /// + internal static string ODataParameterReaderCore_ReadOrReadAsyncCalledInInvalidState(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_ReadOrReadAsyncCalledInInvalidState, p0); + } + + /// + /// A string like "A synchronous operation was called on an asynchronous parameter reader. All calls on a parameter reader instance must be either synchronous or asynchronous." + /// + internal static string ODataParameterReaderCore_SyncCallOnAsyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_SyncCallOnAsyncReader); + } + } + + /// + /// A string like "An asynchronous operation was called on a synchronous parameter reader. All calls on a parameter reader instance must be either synchronous or asynchronous." + /// + internal static string ODataParameterReaderCore_AsyncCallOnSyncReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_AsyncCallOnSyncReader); + } + } + + /// + /// A string like "ODataParameterReader.ReadAsync or ODataParameterReader.Read was called in the '{0}' state. '{1}' must be called in this state, and the created reader must be in the 'Completed' state before the next ODataParameterReader.ReadAsync or ODataParameterReader.Read can be called." + /// + internal static string ODataParameterReaderCore_SubReaderMustBeCreatedAndReadToCompletionBeforeTheNextReadOrReadAsyncCall(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_SubReaderMustBeCreatedAndReadToCompletionBeforeTheNextReadOrReadAsyncCall, p0, p1); + } + + /// + /// A string like "ODataParameterReader.ReadAsync or ODataParameterReader.Read was called in the '{0}' state and '{1}' was called but the created reader is not in the 'Completed' state. The created reader must be in 'Completed' state before the next ODataParameterReader.ReadAsync or ODataParameterReader.Read can be called." + /// + internal static string ODataParameterReaderCore_SubReaderMustBeInCompletedStateBeforeTheNextReadOrReadAsyncCall(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_SubReaderMustBeInCompletedStateBeforeTheNextReadOrReadAsyncCall, p0, p1); + } + + /// + /// A string like "You cannot call the method '{0}' in state '{1}'." + /// + internal static string ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_InvalidCreateReaderMethodCalledForState, p0, p1); + } + + /// + /// A string like "The '{0}' method has already been called for the parameter '{1}'. Only one create reader method call is allowed for each resource, resource set, or collection parameter." + /// + internal static string ODataParameterReaderCore_CreateReaderAlreadyCalled(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_CreateReaderAlreadyCalled, p0, p1); + } + + /// + /// A string like "The parameter '{0}' in the request payload is not a valid parameter for the operation '{1}'." + /// + internal static string ODataParameterReaderCore_ParameterNameNotInMetadata(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_ParameterNameNotInMetadata, p0, p1); + } + + /// + /// A string like "Multiple parameters with the name '{0}' were found in the request payload." + /// + internal static string ODataParameterReaderCore_DuplicateParametersInPayload(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_DuplicateParametersInPayload, p0); + } + + /// + /// A string like "One or more parameters of the operation '{0}' are missing from the request payload. The missing parameters are: {1}." + /// + internal static string ODataParameterReaderCore_ParametersMissingInPayload(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataParameterReaderCore_ParametersMissingInPayload, p0, p1); + } + + /// + /// A string like "The 'Metadata' property on an {0} must be set to a non-null value." + /// + internal static string ValidationUtils_ActionsAndFunctionsMustSpecifyMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ActionsAndFunctionsMustSpecifyMetadata, p0); + } + + /// + /// A string like "The 'Target' property on an {0} must be set to a non-null value." + /// + internal static string ValidationUtils_ActionsAndFunctionsMustSpecifyTarget(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ActionsAndFunctionsMustSpecifyTarget, p0); + } + + /// + /// A string like "The '{0}' enumerable contains a null item. This enumerable cannot contain null items." + /// + internal static string ValidationUtils_EnumerableContainsANullItem(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_EnumerableContainsANullItem, p0); + } + + /// + /// A string like "The 'Name' property on an ODataAssociationLink must be set to a non-empty string." + /// + internal static string ValidationUtils_AssociationLinkMustSpecifyName { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_AssociationLinkMustSpecifyName); + } + } + + /// + /// A string like "The 'Url' property on an ODataAssociationLink must be set to a non-null value that represents the association or associations the link references." + /// + internal static string ValidationUtils_AssociationLinkMustSpecifyUrl { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_AssociationLinkMustSpecifyUrl); + } + } + + /// + /// A string like "An empty type name was found; the name of a type cannot be an empty string." + /// + internal static string ValidationUtils_TypeNameMustNotBeEmpty { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_TypeNameMustNotBeEmpty); + } + } + + /// + /// A string like "The property '{0}' does not exist on type '{1}'. Make sure to only use property names that are defined by the type or mark the type as open type." + /// + internal static string ValidationUtils_PropertyDoesNotExistOnType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_PropertyDoesNotExistOnType, p0, p1); + } + + /// + /// A string like "The 'Url' property on a resource collection must be set to a non-null value." + /// + internal static string ValidationUtils_ResourceMustSpecifyUrl { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ResourceMustSpecifyUrl); + } + } + + /// + /// A string like "The 'Name' property on a resource collection with the 'Url' '{0}' must be set to a non-null value." + /// + internal static string ValidationUtils_ResourceMustSpecifyName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ResourceMustSpecifyName, p0); + } + + /// + /// A string like "A service document element without a Url was detected; a service document element must have a non-null Url value." + /// + internal static string ValidationUtils_ServiceDocumentElementUrlMustNotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ServiceDocumentElementUrlMustNotBeNull); + } + } + + /// + /// A string like "A primitive value was specified; however, a value of the non-primitive type '{0}' was expected." + /// + internal static string ValidationUtils_NonPrimitiveTypeForPrimitiveValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_NonPrimitiveTypeForPrimitiveValue, p0); + } + + /// + /// A string like "Unsupported primitive type. A primitive type could not be determined for an instance of type '{0}'." + /// + internal static string ValidationUtils_UnsupportedPrimitiveType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_UnsupportedPrimitiveType, p0); + } + + /// + /// A string like "An incompatible primitive type '{0}[Nullable={1}]' was found for an item that was expected to be of type '{2}[Nullable={3}]'." + /// + internal static string ValidationUtils_IncompatiblePrimitiveItemType(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_IncompatiblePrimitiveItemType, p0, p1, p2, p3); + } + + /// + /// A string like "A null value was detected in the items of a collection property value; non-nullable instances of collection types do not support null values as items." + /// + internal static string ValidationUtils_NonNullableCollectionElementsMustNotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_NonNullableCollectionElementsMustNotBeNull); + } + } + + /// + /// A string like "Type name '{0}' is an invalid collection type name; a collection type name must be in the format 'Collection(<itemTypeName>)'." + /// + internal static string ValidationUtils_InvalidCollectionTypeName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_InvalidCollectionTypeName, p0); + } + + /// + /// A string like "A type named '{0}' could not be resolved by the model. When a model is available, each type name must resolve to a valid type." + /// + internal static string ValidationUtils_UnrecognizedTypeName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_UnrecognizedTypeName, p0); + } + + /// + /// A string like "Incompatible type kinds were found. The type '{0}' was found to be of kind '{2}' instead of the expected kind '{1}'." + /// + internal static string ValidationUtils_IncorrectTypeKind(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_IncorrectTypeKind, p0, p1, p2); + } + + /// + /// A string like "Incompatible type kinds were found. Found type kind '{0}' instead of the expected kind '{1}'." + /// + internal static string ValidationUtils_IncorrectTypeKindNoTypeName(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_IncorrectTypeKindNoTypeName, p0, p1); + } + + /// + /// A string like "A value with type '{0}' was found, which is of kind '{1}'. Value can only be of kind 'Primitive', 'Complex' or 'Collection'." + /// + internal static string ValidationUtils_IncorrectValueTypeKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_IncorrectValueTypeKind, p0, p1); + } + + /// + /// A string like "The 'Name' property on an ODataNestedResourceInfo must be set to a non-empty string." + /// + internal static string ValidationUtils_LinkMustSpecifyName { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_LinkMustSpecifyName); + } + } + + /// + /// A string like "The property '{0}' cannot be a stream property because it is not of kind EdmPrimitiveTypeKind.Stream." + /// + internal static string ValidationUtils_MismatchPropertyKindForStreamProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_MismatchPropertyKindForStreamProperty, p0); + } + + /// + /// A string like "Nested collection instances are not allowed." + /// + internal static string ValidationUtils_NestedCollectionsAreNotSupported { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_NestedCollectionsAreNotSupported); + } + } + + /// + /// A string like "An ODataStreamReferenceValue item was found in a collection property value, which is not allowed. Collection properties can only have primitive and complex values as items." + /// + internal static string ValidationUtils_StreamReferenceValuesNotSupportedInCollections { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_StreamReferenceValuesNotSupportedInCollections); + } + } + + /// + /// A string like "A value was encountered that has a type name that is incompatible with the metadata. The value specified its type as '{0}', but the type specified in the metadata is '{1}'." + /// + internal static string ValidationUtils_IncompatibleType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_IncompatibleType, p0, p1); + } + + /// + /// A string like "An open collection property '{0}' was found. In OData, open collection properties are not supported." + /// + internal static string ValidationUtils_OpenCollectionProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_OpenCollectionProperty, p0); + } + + /// + /// A string like "An open stream property '{0}' was found. In OData, open stream properties are not supported." + /// + internal static string ValidationUtils_OpenStreamProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_OpenStreamProperty, p0); + } + + /// + /// A string like "An invalid collection type kind '{0}' was found. In OData, collection types must be of kind 'Collection'." + /// + internal static string ValidationUtils_InvalidCollectionTypeReference(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_InvalidCollectionTypeReference, p0); + } + + /// + /// A string like "A resource with type '{0}' was found with a media resource, but this entity type is not a media link resource (MLE). When the type is not an MLE entity, the resource cannot have a media resource." + /// + internal static string ValidationUtils_ResourceWithMediaResourceAndNonMLEType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ResourceWithMediaResourceAndNonMLEType, p0); + } + + /// + /// A string like "A resource with type '{0}' was found without a media resource, but this entity type is a media link resource (MLE). When the type is an MLE entity, the resource must have a media resource." + /// + internal static string ValidationUtils_ResourceWithoutMediaResourceAndMLEType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ResourceWithoutMediaResourceAndMLEType, p0); + } + + /// + /// A string like "A resource with type '{0}' was found, but it is not assignable to the expected type '{1}'. The type specified in the resource must be equal to either the expected type or a derived type." + /// + internal static string ValidationUtils_ResourceTypeNotAssignableToExpectedType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_ResourceTypeNotAssignableToExpectedType, p0, p1); + } + + /// + /// A string like "A property with name '{0}' on type '{1}' has kind '{2}', but it is expected to be of kind 'Navigation'." + /// + internal static string ValidationUtils_NavigationPropertyExpected(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_NavigationPropertyExpected, p0, p1, p2); + } + + /// + /// A string like "The boundary delimiter '{0}' is invalid. A boundary delimiter must be non-null, be non-empty, and have a maximum of {1} characters." + /// + internal static string ValidationUtils_InvalidBatchBoundaryDelimiterLength(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_InvalidBatchBoundaryDelimiterLength, p0, p1); + } + + /// + /// A string like "The maximum recursion depth limit was reached. The depth of nested values in a single property cannot exceed {0}." + /// + internal static string ValidationUtils_RecursionDepthLimitReached(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_RecursionDepthLimitReached, p0); + } + + /// + /// A string like "The depth limit for entries in nested expanded navigation links was reached. The number of nested expanded entries cannot exceed {0}." + /// + internal static string ValidationUtils_MaxDepthOfNestedEntriesExceeded(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_MaxDepthOfNestedEntriesExceeded, p0); + } + + /// + /// A string like "A null value was found in a collection, but the expected collection item type '{0}' does not allow null values." + /// + internal static string ValidationUtils_NullCollectionItemForNonNullableType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_NullCollectionItemForNonNullableType, p0); + } + + /// + /// A string like "The property name '{0}' is invalid; property names must not contain any of the reserved characters {1}." + /// + internal static string ValidationUtils_PropertiesMustNotContainReservedChars(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_PropertiesMustNotContainReservedChars, p0, p1); + } + + /// + /// A string like "A null value was detected when enumerating the collections in a workspace. Workspace collections cannot be null." + /// + internal static string ValidationUtils_WorkspaceResourceMustNotContainNullItem { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_WorkspaceResourceMustNotContainNullItem); + } + } + + /// + /// A string like "Encountered a property '{0}' that was expected to be a reference to a location in the $metadata document but does not contain a '#' character or is otherwise not a valid metadata reference property. A metadata reference property must contain a '#' and be a valid absolute URI or begin with a '#' and be a valid URI fragment." + /// + internal static string ValidationUtils_InvalidMetadataReferenceProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValidationUtils_InvalidMetadataReferenceProperty, p0); + } + + /// + /// A string like "The 'ODataResource.Properties' enumerable contains a null item. This enumerable cannot contain null items." + /// + internal static string WriterValidationUtils_PropertyMustNotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_PropertyMustNotBeNull); + } + } + + /// + /// A string like "An ODataProperty instance without a name was detected; an ODataProperty must have a non-null, non-empty name." + /// + internal static string WriterValidationUtils_PropertiesMustHaveNonEmptyName { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_PropertiesMustHaveNonEmptyName); + } + } + + /// + /// A string like "No TypeName was found for an ODataResource of an open property, ODataResource or custom instance annotation, even though metadata was specified. If a model is passed to the writer, each complex value on an open property, resource or custom instance annotation must have a type name." + /// + internal static string WriterValidationUtils_MissingTypeNameWithMetadata { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_MissingTypeNameWithMetadata); + } + } + + /// + /// A string like "The ODataResourceSet.NextPageLink must be null for request payloads. A next link is only supported in responses." + /// + internal static string WriterValidationUtils_NextPageLinkInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_NextPageLinkInRequest); + } + } + + /// + /// A string like "A default stream ODataStreamReferenceValue was detected with a 'ContentType' property but without a ReadLink value. In OData, a default stream must either have both a content type and a read link, or neither of them." + /// + internal static string WriterValidationUtils_DefaultStreamWithContentTypeWithoutReadLink { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_DefaultStreamWithContentTypeWithoutReadLink); + } + } + + /// + /// A string like "A default stream ODataStreamReferenceValue was detected with a 'ReadLink' property but without a ContentType value. In OData, a default stream must either have both a content type and a read link, or neither of them." + /// + internal static string WriterValidationUtils_DefaultStreamWithReadLinkWithoutContentType { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_DefaultStreamWithReadLinkWithoutContentType); + } + } + + /// + /// A string like "An ODataStreamReferenceValue was detected with null values for both EditLink and ReadLink. In OData, a stream resource must have at least an edit link or a read link." + /// + internal static string WriterValidationUtils_StreamReferenceValueMustHaveEditLinkOrReadLink { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_StreamReferenceValueMustHaveEditLinkOrReadLink); + } + } + + /// + /// A string like "An ODataStreamReferenceValue was detected with an ETag but without an edit link. In OData, a stream resource must have an edit link to have an ETag." + /// + internal static string WriterValidationUtils_StreamReferenceValueMustHaveEditLinkToHaveETag { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_StreamReferenceValueMustHaveEditLinkToHaveETag); + } + } + + /// + /// A string like "An ODataStreamReferenceValue was detected with an empty string 'ContentType' property. In OData, a stream resource must either have a non-empty content type or it must be null." + /// + internal static string WriterValidationUtils_StreamReferenceValueEmptyContentType { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_StreamReferenceValueEmptyContentType); + } + } + + /// + /// A string like "A resource with an empty ID value was detected. In OData, a resource must either a non-empty ID value or no ID value." + /// + internal static string WriterValidationUtils_EntriesMustHaveNonEmptyId { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_EntriesMustHaveNonEmptyId); + } + } + + /// + /// A string like "The base URI '{0}' specified in ODataMessageWriterSettings.BaseUri is invalid; it must either be null or an absolute URI." + /// + internal static string WriterValidationUtils_MessageWriterSettingsBaseUriMustBeNullOrAbsolute(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_MessageWriterSettingsBaseUriMustBeNullOrAbsolute, p0); + } + + /// + /// A string like "An ODataEntityReferenceLink with a null Url was detected; an ODataEntityReferenceLink must have a non-null Url." + /// + internal static string WriterValidationUtils_EntityReferenceLinkUrlMustNotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_EntityReferenceLinkUrlMustNotBeNull); + } + } + + /// + /// A string like "The 'ODataEntityReferenceLinks.Links' enumerable contains a null item. This enumerable cannot contain null items." + /// + internal static string WriterValidationUtils_EntityReferenceLinksLinkMustNotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_EntityReferenceLinksLinkMustNotBeNull); + } + } + + /// + /// A string like "The type '{0}' of a resource in an expanded link is not compatible with the element type '{1}' of the expanded link. Entries in an expanded link must have entity types that are assignable to the element type of the expanded link." + /// + internal static string WriterValidationUtils_NestedResourceTypeNotCompatibleWithParentPropertyType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_NestedResourceTypeNotCompatibleWithParentPropertyType, p0, p1); + } + + /// + /// A string like "The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource set, but the actual payload is a resource." + /// + internal static string WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceContent(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceContent, p0); + } + + /// + /// A string like "The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource, but the actual payload is a resource set." + /// + internal static string WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetContent(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetContent, p0); + } + + /// + /// A string like "The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource set, but the metadata declares it as a resource." + /// + internal static string WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceMetadata, p0); + } + + /// + /// A string like "The ODataNestedResourceInfo with the URL value '{0}' specifies in its 'IsCollection' property that its payload is a resource, but the metadata declares it as resource set." + /// + internal static string WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetMetadata, p0); + } + + /// + /// A string like "The content of the ODataNestedResourceInfo with the URL value '{0}' is a resource set, but the metadata declares it as a resource." + /// + internal static string WriterValidationUtils_ExpandedLinkWithResourceSetPayloadAndResourceMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ExpandedLinkWithResourceSetPayloadAndResourceMetadata, p0); + } + + /// + /// A string like "The content of the ODataNestedResourceInfo with the URL value '{0}' is a resource, but the metadata declares it as resource set." + /// + internal static string WriterValidationUtils_ExpandedLinkWithResourcePayloadAndResourceSetMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ExpandedLinkWithResourcePayloadAndResourceSetMetadata, p0); + } + + /// + /// A string like "The collection property '{0}' has a null value, which is not allowed. In OData, collection properties cannot have null values." + /// + internal static string WriterValidationUtils_CollectionPropertiesMustNotHaveNullValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_CollectionPropertiesMustNotHaveNullValue, p0); + } + + /// + /// A string like "The property '{0}[Nullable=False]' of type '{1}' has a null value, which is not allowed." + /// + internal static string WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue, p0, p1); + } + + /// + /// A string like "The stream property '{0}' has a null value, which is not allowed. In OData, stream properties cannot have null values." + /// + internal static string WriterValidationUtils_StreamPropertiesMustNotHaveNullValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_StreamPropertiesMustNotHaveNullValue, p0); + } + + /// + /// A string like "An action or a function with metadata '{0}' was detected when writing a request; actions and functions are only supported in responses." + /// + internal static string WriterValidationUtils_OperationInRequest(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_OperationInRequest, p0); + } + + /// + /// A string like "An association link with name '{0}' could not be written to the request payload. Association links are only supported in responses." + /// + internal static string WriterValidationUtils_AssociationLinkInRequest(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_AssociationLinkInRequest, p0); + } + + /// + /// A string like "The stream property {0} in a request payload cannot contain etag, editLink, or readLink values." + /// + internal static string WriterValidationUtils_StreamPropertyInRequest(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_StreamPropertyInRequest, p0); + } + + /// + /// A string like "The service document URI '{0}' specified is invalid; it must be either null or an absolute URI." + /// + internal static string WriterValidationUtils_MessageWriterSettingsServiceDocumentUriMustBeNullOrAbsolute(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_MessageWriterSettingsServiceDocumentUriMustBeNullOrAbsolute, p0); + } + + /// + /// A string like "The ODataNestedResourceInfo.Url property on an navigation link '{0}' is null. The ODataNestedResourceInfo.Url property must be set to a non-null value that represents the entity or entities the navigation link references." + /// + internal static string WriterValidationUtils_NavigationLinkMustSpecifyUrl(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_NavigationLinkMustSpecifyUrl, p0); + } + + /// + /// A string like "The ODataNestedResourceInfo.IsCollection property on a nested resource info '{0}' is null. The ODataNestedResourceInfo.IsCollection property must be specified when writing a nested resource into a request." + /// + internal static string WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection, p0); + } + + /// + /// A string like "A JSON Padding function was specified on ODataMessageWriterSettings when trying to write a request message. JSON Padding is only for writing responses." + /// + internal static string WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage); + } + } + + /// + /// A string like "The value type '{0}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation on {1} '{2}'." + /// + internal static string WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint, p0, p1, p2); + } + + /// + /// A string like "An XML node of type '{0}' was found in a string value. An element with a string value can only contain Text, CDATA, SignificantWhitespace, Whitespace or Comment nodes." + /// + internal static string XmlReaderExtension_InvalidNodeInStringValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.XmlReaderExtension_InvalidNodeInStringValue, p0); + } + + /// + /// A string like "An XML node of type '{0}' was found at the root level. The root level of an OData payload must contain a single XML element and no text nodes." + /// + internal static string XmlReaderExtension_InvalidRootNode(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.XmlReaderExtension_InvalidRootNode, p0); + } + + /// + /// A string like "The metadata document could not be read from the message content.\r\n{0}" + /// + internal static string ODataMetadataInputContext_ErrorReadingMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMetadataInputContext_ErrorReadingMetadata, p0); + } + + /// + /// A string like "The metadata document could not be written as specified.\r\n{0}" + /// + internal static string ODataMetadataOutputContext_ErrorWritingMetadata(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMetadataOutputContext_ErrorWritingMetadata, p0); + } + + /// + /// A string like "A relative URI value '{0}' was specified in the payload, but no base URI for it was found. When the payload contains a relative URI, there must be an xml:base in the payload or else a base URI must specified in the reader settings." + /// + internal static string ODataAtomDeserializer_RelativeUriUsedWithoutBaseUriSpecified(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAtomDeserializer_RelativeUriUsedWithoutBaseUriSpecified, p0); + } + + /// + /// A string like "The element with name '{0}' is not a valid collection item. The name of the collection item element must be 'element' and it must belong to the '{1}' namespace." + /// + internal static string ODataAtomPropertyAndValueDeserializer_InvalidCollectionElement(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAtomPropertyAndValueDeserializer_InvalidCollectionElement, p0, p1); + } + + /// + /// A string like "The property '{0}' on type '{1}' was found in the {{http://docs.oasis-open.org/odata/ns/metadata}}:properties element, and it is declared as a navigation property. Navigation properties in ATOM must be represented as {{http://www.w3.org/2005/Atom}}:link elements." + /// + internal static string ODataAtomPropertyAndValueDeserializer_NavigationPropertyInProperties(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAtomPropertyAndValueDeserializer_NavigationPropertyInProperties, p0, p1); + } + + /// + /// A string like "Writing null value for the instance annotation '{0}' is not allowed. The instance annotation '{0}' has the expected type '{1}[Nullable=False]'." + /// + internal static string JsonLightInstanceAnnotationWriter_NullValueNotAllowedForInstanceAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonLightInstanceAnnotationWriter_NullValueNotAllowedForInstanceAnnotation, p0, p1); + } + + /// + /// A string like "When resolving operations '{0}' the group returned has both actions and functions with an invalid IEdmModel." + /// + internal static string EdmLibraryExtensions_OperationGroupReturningActionsAndFunctionsModelInvalid(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmLibraryExtensions_OperationGroupReturningActionsAndFunctionsModelInvalid, p0); + } + + /// + /// A string like "Invalid implementation of an IEdmModel, an operation '{0}' was found using the IEdmModel method 'FindDeclaredBoundOperations' should never return non-bound operations." + /// + internal static string EdmLibraryExtensions_UnBoundOperationsFoundFromIEdmModelFindMethodIsInvalid(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmLibraryExtensions_UnBoundOperationsFoundFromIEdmModelFindMethodIsInvalid, p0); + } + + /// + /// A string like "Invalid implementation of an IEdmModel, an operation '{0}' was found using the IEdmModel method 'FindDeclaredBoundOperations' should never return bound operations without any parameters." + /// + internal static string EdmLibraryExtensions_NoParameterBoundOperationsFoundFromIEdmModelFindMethodIsInvalid(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmLibraryExtensions_NoParameterBoundOperationsFoundFromIEdmModelFindMethodIsInvalid, p0); + } + + /// + /// A string like "Value '{0}' was either too large or too small for a '{1}'." + /// + internal static string EdmLibraryExtensions_ValueOverflowForUnderlyingType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmLibraryExtensions_ValueOverflowForUnderlyingType, p0, p1); + } + + /// + /// A string like "The 'type' attribute on element {{http://www.w3.org/2005/Atom}}:content is either missing or has an invalid value '{0}'. Only 'application/xml' and 'application/atom+xml' are supported as the value of the 'type' attribute on the {{http://www.w3.org/2005/Atom}}:content element." + /// + internal static string ODataAtomResourceDeserializer_ContentWithWrongType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAtomResourceDeserializer_ContentWithWrongType, p0); + } + + /// + /// A string like "Multiple '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' elements were found in a top-level error value. In OData, the value of a top-level error value can have no more than one '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' element" + /// + internal static string ODataAtomErrorDeserializer_MultipleErrorElementsWithSameName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAtomErrorDeserializer_MultipleErrorElementsWithSameName, p0); + } + + /// + /// A string like "Multiple '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' elements were found in an inner error value. In OData, the value of an inner error value can have at most one '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' element." + /// + internal static string ODataAtomErrorDeserializer_MultipleInnerErrorElementsWithSameName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataAtomErrorDeserializer_MultipleInnerErrorElementsWithSameName, p0); + } + + /// + /// A string like "An invalid item type kind '{0}' was found. Items in a collection can only be of type kind 'Primitive' or 'Complex', but not of type kind '{0}'." + /// + internal static string CollectionWithoutExpectedTypeValidator_InvalidItemTypeKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CollectionWithoutExpectedTypeValidator_InvalidItemTypeKind, p0); + } + + /// + /// A string like "An item of type kind '{0}' was found in a collection that otherwise has items of type kind '{1}'. In OData, all items in a collection must have the same type kind." + /// + internal static string CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeKind, p0, p1); + } + + /// + /// A string like "An item with type name '{0}' was found in a collection of items with type name '{1}'. In OData, all items in a collection must have the same type name." + /// + internal static string CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeName(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CollectionWithoutExpectedTypeValidator_IncompatibleItemTypeName, p0, p1); + } + + /// + /// A string like "A resource of type '{0}' was found in a resource set that otherwise has entries of type '{1}'. In OData, all entries in a resource set must have a common base type." + /// + internal static string ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes, p0, p1); + } + + /// + /// A string like "The maximum number of bytes allowed to be read from the stream has been exceeded. After the last read operation, a total of {0} bytes has been read from the stream; however a maximum of {1} bytes is allowed." + /// + internal static string MessageStreamWrappingStream_ByteLimitExceeded(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MessageStreamWrappingStream_ByteLimitExceeded, p0, p1); + } + + /// + /// A string like "The custom type resolver set in ODataMessageWriterSettings.EnableWcfDataServicesClientBehavior returned 'null' when resolving the type '{0}'. When a custom type resolver is specified, it cannot return null." + /// + internal static string MetadataUtils_ResolveTypeName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataUtils_ResolveTypeName, p0); + } + + /// + /// A string like "The method 'FindDeclaredBoundOperations' on the IEdmModel has thrown an exception when looking for operations with a binding type {0}. See inner exception for more details." + /// + internal static string MetadataUtils_CalculateBindableOperationsForType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataUtils_CalculateBindableOperationsForType, p0); + } + + /// + /// A string like "The type '{0}' was found for a primitive value. In OData, the type '{0}' is not a supported primitive type." + /// + internal static string EdmValueUtils_UnsupportedPrimitiveType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmValueUtils_UnsupportedPrimitiveType, p0); + } + + /// + /// A string like "Incompatible primitive type kinds were found. The type '{0}' was found to be of kind '{2}' instead of the expected kind '{1}'." + /// + internal static string EdmValueUtils_IncorrectPrimitiveTypeKind(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmValueUtils_IncorrectPrimitiveTypeKind, p0, p1, p2); + } + + /// + /// A string like "Incompatible primitive type kinds were found. Found type kind '{0}' instead of the expected kind '{1}'." + /// + internal static string EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmValueUtils_IncorrectPrimitiveTypeKindNoTypeName, p0, p1); + } + + /// + /// A string like "A value with primitive kind '{0}' cannot be converted into a primitive object value." + /// + internal static string EdmValueUtils_CannotConvertTypeToClrValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmValueUtils_CannotConvertTypeToClrValue, p0); + } + + /// + /// A string like "The property '{0}' is not declared on the non-open type '{1}'." + /// + internal static string ODataEdmStructuredValue_UndeclaredProperty(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataEdmStructuredValue_UndeclaredProperty, p0, p1); + } + + /// + /// A string like "The entity set '{0}' doesn't have the 'OData.EntitySetUri' annotation. This annotation is required." + /// + internal static string ODataMetadataBuilder_MissingEntitySetUri(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMetadataBuilder_MissingEntitySetUri, p0); + } + + /// + /// A string like "The entity set '{0}' has a URI '{1}' which has no path segments. An entity set URI suffix cannot be appended to a URI without path segments." + /// + internal static string ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMetadataBuilder_MissingSegmentForEntitySetUriSuffix, p0, p1); + } + + /// + /// A string like "Neither the 'OData.EntityInstanceUri' nor the 'OData.EntitySetUriSuffix' annotation was found for entity set '{0}'. One of these annotations is required." + /// + internal static string ODataMetadataBuilder_MissingEntityInstanceUri(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMetadataBuilder_MissingEntityInstanceUri, p0); + } + + /// + /// A string like "Parent id or contained context url is missing which is required to compute id for contained instance. Specify ODataUri in the ODataMessageWriterSettings or return parent id or context url in the payload." + /// + internal static string ODataMetadataBuilder_MissingParentIdOrContextUrl { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMetadataBuilder_MissingParentIdOrContextUrl); + } + } + + /// + /// A string like "The Id cannot be computed, since the navigation source '{0}' cannot be resolved to a known entity set from model." + /// + internal static string ODataMetadataBuilder_UnknownEntitySet(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMetadataBuilder_UnknownEntitySet, p0); + } + + /// + /// A string like "The entity type '{0}' is not compatible with the base type '{1}' of the provided entity set '{2}'. When an entity type is specified for an OData resource set or resource reader, it has to be the same or a subtype of the base type of the specified entity set." + /// + internal static string ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightInputContext_EntityTypeMustBeCompatibleWithEntitySetBaseType, p0, p1, p2); + } + + /// + /// A string like "ODataMessageReader.DetectPayloadKind was called for a request payload. Payload kind detection is only supported for responses in JSON Light." + /// + internal static string ODataJsonLightInputContext_PayloadKindDetectionForRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightInputContext_PayloadKindDetectionForRequest); + } + } + + /// + /// A string like "The parameter '{0}' is specified with a null value. For JSON Light, the '{0}' argument to the 'CreateParameterReader' method cannot be null." + /// + internal static string ODataJsonLightInputContext_OperationCannotBeNullForCreateParameterReader(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightInputContext_OperationCannotBeNullForCreateParameterReader, p0); + } + + /// + /// A string like "Parsing JSON Light resource sets or entries in requests without entity set is not supported. Pass in the entity set as a parameter to ODataMessageReader.CreateODataResourceReader or ODataMessageReader.CreateODataResourceSetReader method." + /// + internal static string ODataJsonLightInputContext_NoEntitySetForRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightInputContext_NoEntitySetForRequest); + } + } + + /// + /// A string like "Parsing JSON Light payloads without a model is only supported for error payloads." + /// + internal static string ODataJsonLightInputContext_ModelRequiredForReading { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightInputContext_ModelRequiredForReading); + } + } + + /// + /// A string like "An attempt to read a collection request payload without specifying a collection item type was detected. When reading collection payloads in requests, an expected item type has to be provided." + /// + internal static string ODataJsonLightInputContext_ItemTypeRequiredForCollectionReaderInRequests { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightInputContext_ItemTypeRequiredForCollectionReaderInRequests); + } + } + + /// + /// A string like "The required instance annotation 'odata.context' was not found at the beginning of a response payload." + /// + internal static string ODataJsonLightDeserializer_ContextLinkNotFoundAsFirstProperty { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightDeserializer_ContextLinkNotFoundAsFirstProperty); + } + } + + /// + /// A string like "The annotation '{0}' was targeting the instance annotation '{1}'. Only the '{2}' annotation is allowed to target an instance annotation." + /// + internal static string ODataJsonLightDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation, p0, p1, p2); + } + + /// + /// A string like "The annotation '{0}' is found targeting the instance annotation '{1}'. However the value for the instance annotation '{1}' is not found immediately after. In JSON Light, an annotation targeting an instance annotation must be immediately followed by the value of the targeted instance annotation." + /// + internal static string ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue, p0, p1); + } + + /// + /// A string like "An attempt to write an entity reference link inside a navigation link after a resource set has been written inside the same navigation link in a request was detected. In JSON Light requests, all entity reference links inside a navigation link have to be written before all resource sets inside the same navigation link." + /// + internal static string ODataJsonLightWriter_EntityReferenceLinkAfterResourceSetInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightWriter_EntityReferenceLinkAfterResourceSetInRequest); + } + } + + /// + /// A string like "The ODataResourceSet.InstanceAnnotations collection must be empty for expanded resource sets. Custom instance annotations are not supported on expanded resource sets." + /// + internal static string ODataJsonLightWriter_InstanceAnnotationNotSupportedOnExpandedResourceSet { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightWriter_InstanceAnnotationNotSupportedOnExpandedResourceSet); + } + } + + /// + /// A string like "Neither an expected type nor a type name in the OData object model was provided for a resource value. When writing a request payload, either an expected type or a type name has to be specified." + /// + internal static string ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForResourceValueRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForResourceValueRequest); + } + } + + /// + /// A string like "Neither an expected type nor a type name in the OData object model was provided for a collection property. When writing a request payload, either an expected type or a type name has to be specified." + /// + internal static string ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForCollectionValueInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueSerializer_NoExpectedTypeOrTypeNameSpecifiedForCollectionValueInRequest); + } + } + + /// + /// A string like "When writing a JSON response, a user model must be specified and the entity set and entity type must be passed to the ODataMessageWriter.CreateODataResourceWriter method or the ODataResourceSerializationInfo must be set on the ODataResource or ODataResourceSet that is being written." + /// + internal static string ODataResourceTypeContext_MetadataOrSerializationInfoMissing { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResourceTypeContext_MetadataOrSerializationInfoMissing); + } + } + + /// + /// A string like "When writing a JSON response in full metadata mode, a user model must be specified and the entity set and entity type must be passed to the ODataMessageWriter.CreateODataResourceWriter method or the ODataResource.TypeName must be set." + /// + internal static string ODataResourceTypeContext_ODataResourceTypeNameMissing { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResourceTypeContext_ODataResourceTypeNameMissing); + } + } + + /// + /// A string like "The base type '{0}' of the entity set specified for writing a payload is not assignable from the specified entity type '{1}'. When an entity type is specified it has to be the same or derived from the base type of the entity set." + /// + internal static string ODataContextUriBuilder_ValidateDerivedType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_ValidateDerivedType, p0, p1); + } + + /// + /// A string like "The collection type name for the top level collection is unknown. When writing a response, the item type must be passed to the ODataMessageWriter.CreateODataCollectionWriter method or the ODataCollectionStartSerializationInfo must be set on the ODataCollectionStart." + /// + internal static string ODataContextUriBuilder_TypeNameMissingForTopLevelCollection { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_TypeNameMissingForTopLevelCollection); + } + } + + /// + /// A string like "Context URL for payload kind '{0}' is not supported." + /// + internal static string ODataContextUriBuilder_UnsupportedPayloadKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_UnsupportedPayloadKind, p0); + } + + /// + /// A string like "The stream value must be a property of an ODataResource instance." + /// + internal static string ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource); + } + } + + /// + /// A string like "The navigationSource for resource or resource set is unknown or the Type is null. When writing a response, the navigation source or the type must be passed to the ODataMessageWriter.CreateODataResourceWriter/ODataMessageWriter.CreateODataResourceSetWriter method or the ODataResourceSerializationInfo must be set on the resource/resource set." + /// + internal static string ODataContextUriBuilder_NavigationSourceOrTypeNameMissingForResourceOrResourceSet { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_NavigationSourceOrTypeNameMissingForResourceOrResourceSet); + } + } + + /// + /// A string like "The ODataMessageWriterSetting.ODataUri must be set when writing individual property." + /// + internal static string ODataContextUriBuilder_ODataUriMissingForIndividualProperty { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_ODataUriMissingForIndividualProperty); + } + } + + /// + /// A string like "The type name for the top level property is unknown. When writing a response, the ODataValue must have a type name on itself or have a SerializationTypeNameAnnotation." + /// + internal static string ODataContextUriBuilder_TypeNameMissingForProperty { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_TypeNameMissingForProperty); + } + } + + /// + /// A string like "The Path property '{0}' of ODataMessageWriterSetting.ODataUri must end with the navigation property which the contained elements being written belong to." + /// + internal static string ODataContextUriBuilder_ODataPathInvalidForContainedElement(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataContextUriBuilder_ODataPathInvalidForContainedElement, p0); + } + + /// + /// A string like "The annotation '{0}' was found. This annotation is either not recognized or not expected at the current position." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties, p0); + } + + /// + /// A string like "The property '{0}' has a property annotation '{1}'. This annotation is either not recognized or not expected at the current position." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_UnexpectedPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_UnexpectedPropertyAnnotation, p0, p1); + } + + /// + /// A string like "An OData property annotation '{0}' was found. This property annotation is either not recognized or not expected at the current position." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation, p0); + } + + /// + /// A string like "A property with name '{0}' was found. This property is either not recognized or not expected at the current position." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_UnexpectedProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_UnexpectedProperty, p0); + } + + /// + /// A string like "No top-level properties were found. A top-level property or collection in JSON Light must be represented as a JSON object with exactly one property which is not an annotation." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload); + } + } + + /// + /// A string like "A top-level property with name '{0}' was found in the payload; however, property and collection payloads must always have a top-level property with name '{1}'." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName, p0, p1); + } + + /// + /// A string like "The 'odata.type' instance annotation value '{0}' is not a valid type name. The value of the 'odata.type' instance annotation must be a non-empty string." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_InvalidTypeName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_InvalidTypeName, p0); + } + + /// + /// A string like "One or more property annotations for property '{0}' were found in the top-level property or collection payload without the property to annotate. Top-level property and collection payloads must contain a single property, with optional annotations for this property." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty, p0); + } + + /// + /// A string like "One or more property annotations for property '{0}' were found in the resource value without the property to annotate. Resource values must only contain property annotations for existing properties." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_ResourceValuePropertyAnnotationWithoutProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_ResourceValuePropertyAnnotationWithoutProperty, p0); + } + + /// + /// A string like "A complex property with an '{0}' property annotation was found. Complex properties must not have the '{0}' property annotation, instead the '{0}' should be specified as an instance annotation in the complex value." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_ComplexValueWithPropertyTypeAnnotation(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_ComplexValueWithPropertyTypeAnnotation, p0); + } + + /// + /// A string like "The 'odata.type' instance annotation in a resource object is not the first property of the object. In OData, the 'odata.type' instance annotation must be the first property of the resource object." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_ResourceTypeAnnotationNotFirst { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_ResourceTypeAnnotationNotFirst); + } + } + + /// + /// A string like "The property '{0}' has a property annotation '{1}'. Primitive, complex, collection or open properties can only have an 'odata.type' property annotation." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_UnexpectedDataPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_UnexpectedDataPropertyAnnotation, p0, p1); + } + + /// + /// A string like "The property with name '{0}' was found after the data property with name '{1}'. If a type is specified for a data property, it must appear before the data property." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty, p0, p1); + } + + /// + /// A string like "An '{0}' annotation was read inside a JSON object representing a primitive value; type annotations for primitive values have to be property annotations of the owning property." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_ODataTypeAnnotationInPrimitiveValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_ODataTypeAnnotationInPrimitiveValue, p0); + } + + /// + /// A string like "A top-level property with an invalid primitive null value was found. In OData, top-level properties with null value have to be serialized as JSON object with an '{0}' annotation that has the value '{1}'." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyWithPrimitiveNullValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyWithPrimitiveNullValue, p0, p1); + } + + /// + /// A string like "Encountered a metadata reference property '{0}' in a scope other than a resource. In OData, a property name with a '#' character indicates a reference into the metadata and is only supported for describing operations bound to a resource." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty, p0); + } + + /// + /// A string like "The property with name '{0}' was found in a null payload. In OData, no properties or OData annotations can appear in a null payload." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_NoPropertyAndAnnotationAllowedInNullPayload(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_NoPropertyAndAnnotationAllowedInNullPayload, p0); + } + + /// + /// A string like "A collection type of '{0}' was specified for a non-collection value." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_CollectionTypeNotExpected, p0); + } + + /// + /// A string like "A non-collection type of '{0}' was specified for a collection value." + /// + internal static string ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightPropertyAndValueDeserializer_CollectionTypeExpected, p0); + } + + /// + /// A string like "The value specified for the spatial property was not valid. You must specify a valid spatial value." + /// + internal static string ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonReaderCoreUtils_CannotReadSpatialPropertyValue); + } + } + + /// + /// A string like "If a primitive value is representing a resource, the resource must be null." + /// + internal static string ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightReader_UnexpectedPrimitiveValueForODataResource); + } + } + + /// + /// A string like "The '{0}' instance or property annotation has a null value. In OData, the '{0}' instance or property annotation must have a non-null string value." + /// + internal static string ODataJsonLightReaderUtils_AnnotationWithNullValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightReaderUtils_AnnotationWithNullValue, p0); + } + + /// + /// A string like "An '{0}' annotation was found with an invalid value. In OData, the only valid value for the '{0}' annotation is '{1}'." + /// + internal static string ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation, p0, p1); + } + + /// + /// A string like "The InstanceAnnotations collection has more than one instance annotations with the name '{0}'. All instance annotation names must be unique within the collection." + /// + internal static string JsonLightInstanceAnnotationWriter_DuplicateAnnotationNameInCollection(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonLightInstanceAnnotationWriter_DuplicateAnnotationNameInCollection, p0); + } + + /// + /// A string like "A null metadata document URI was found in the payload. Metadata document URIs must not be null." + /// + internal static string ODataJsonLightContextUriParser_NullMetadataDocumentUri { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_NullMetadataDocumentUri); + } + } + + /// + /// A string like "The context URI '{0}' is not valid for the expected payload kind '{1}'." + /// + internal static string ODataJsonLightContextUriParser_ContextUriDoesNotMatchExpectedPayloadKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_ContextUriDoesNotMatchExpectedPayloadKind, p0, p1); + } + + /// + /// A string like "The context URI '{0}' references the entity set or type '{1}'. However, no entity set or type with name '{1}' is declared in the metadata." + /// + internal static string ODataJsonLightContextUriParser_InvalidEntitySetNameOrTypeName(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_InvalidEntitySetNameOrTypeName, p0, p1); + } + + /// + /// A string like "A '$select' query option was found for the payload kind '{0}'. In OData, a '$select' query option is only supported for payload kinds 'Resource' and 'ResourceSet'." + /// + internal static string ODataJsonLightContextUriParser_InvalidPayloadKindWithSelectQueryOption(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_InvalidPayloadKindWithSelectQueryOption, p0); + } + + /// + /// A string like "No model was specified for the ODataMessageReader. A message reader requires a model for JSON Light payload to be specified in the ODataMessageReader constructor." + /// + internal static string ODataJsonLightContextUriParser_NoModel { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_NoModel); + } + } + + /// + /// A string like "The context URL '{0}' is invalid." + /// + internal static string ODataJsonLightContextUriParser_InvalidContextUrl(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_InvalidContextUrl, p0); + } + + /// + /// A string like "Last segment in context URL '{0}' should not be KeySegment." + /// + internal static string ODataJsonLightContextUriParser_LastSegmentIsKeySegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_LastSegmentIsKeySegment, p0); + } + + /// + /// A string like "The top level context URL '{0}' should be an absolute Uri." + /// + internal static string ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute, p0); + } + + /// + /// A string like "Invalid primitive value '{0}' for @removed annotation. @removed annotation must be a JSON object, optionally containing a 'reason' property." + /// + internal static string ODataJsonLightResourceDeserializer_DeltaRemovedAnnotationMustBeObject(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_DeltaRemovedAnnotationMustBeObject, p0); + } + + /// + /// A string like "The 'odata.type' instance annotation in a resource object is preceded by an invalid property. In OData, the 'odata.type' instance annotation must be either the first property in the JSON object or the second if the 'odata.context' instance annotation is present." + /// + internal static string ODataJsonLightResourceDeserializer_ResourceTypeAnnotationNotFirst { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_ResourceTypeAnnotationNotFirst); + } + } + + /// + /// A string like "The '{0}' instance annotation in a resource object is preceded by a property or property annotation. In OData, the '{0}' instance annotation must be before any property or property annotation in a resource object." + /// + internal static string ODataJsonLightResourceDeserializer_ResourceInstanceAnnotationPrecededByProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_ResourceInstanceAnnotationPrecededByProperty, p0); + } + + /// + /// A string like "A node of type '{0}' was read from the JSON reader when trying to read the start of the content of a resource set; however, a node of type 'StartArray' was expected." + /// + internal static string ODataJsonLightResourceDeserializer_CannotReadResourceSetContentStart(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_CannotReadResourceSetContentStart, p0); + } + + /// + /// A string like "Did not find the required '{0}' property for the expected resource set." + /// + internal static string ODataJsonLightResourceDeserializer_ExpectedResourceSetPropertyNotFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_ExpectedResourceSetPropertyNotFound, p0); + } + + /// + /// A string like "A node of type '{0}' was read from the JSON reader when trying to read the entries of a typed resource set; however, a node of type 'StartObject' or 'EndArray', or a null value, was expected." + /// + internal static string ODataJsonLightResourceDeserializer_InvalidNodeTypeForItemsInResourceSet(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_InvalidNodeTypeForItemsInResourceSet, p0); + } + + /// + /// A string like "A property annotation for a property with name '{0}' was found when reading a top-level resource set. No property annotations, only instance annotations are allowed when reading top-level resource sets." + /// + internal static string ODataJsonLightResourceDeserializer_InvalidPropertyAnnotationInTopLevelResourceSet(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_InvalidPropertyAnnotationInTopLevelResourceSet, p0); + } + + /// + /// A string like "A property with name '{0}' was found when reading a top-level resource set. No properties other than the resource set property with name '{1}' are allowed." + /// + internal static string ODataJsonLightResourceDeserializer_InvalidPropertyInTopLevelResourceSet(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_InvalidPropertyInTopLevelResourceSet, p0, p1); + } + + /// + /// A string like "A property '{0}' which only has property annotations in the payload but no property value is declared to be of type '{1}'. In OData, only navigation properties and named streams can be represented as properties without values." + /// + internal static string ODataJsonLightResourceDeserializer_PropertyWithoutValueWithWrongType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_PropertyWithoutValueWithWrongType, p0, p1); + } + + /// + /// A string like "A property '{0}' which only has property annotations in the payload but no property value is an open property. In OData, open property must be represented as a property with value." + /// + internal static string ODataJsonLightResourceDeserializer_OpenPropertyWithoutValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_OpenPropertyWithoutValue, p0); + } + + /// + /// A string like "A stream property {0} was found in a JSON Light request payload. Stream properties are only supported in responses." + /// + internal static string ODataJsonLightResourceDeserializer_StreamPropertyInRequest(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_StreamPropertyInRequest, p0); + } + + /// + /// A string like "The stream property '{0}' has a property annotation '{1}'. Stream property can only have the 'odata.mediaEditLink', 'odata.mediaReadLink', 'odata.mediaEtag' and 'odata.mediaContentType' property annotations." + /// + internal static string ODataJsonLightResourceDeserializer_UnexpectedStreamPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_UnexpectedStreamPropertyAnnotation, p0, p1); + } + + /// + /// A string like "A stream property '{0}' has a value in the payload. In OData, stream property must not have a value, it must only use property annotations." + /// + internal static string ODataJsonLightResourceDeserializer_StreamPropertyWithValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_StreamPropertyWithValue, p0); + } + + /// + /// A string like "The navigation property '{0}' has a property annotation '{1}'. Deferred navigation links can only have the 'odata.navigationLink' and 'odata.associationLink' property annotations." + /// + internal static string ODataJsonLightResourceDeserializer_UnexpectedDeferredLinkPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_UnexpectedDeferredLinkPropertyAnnotation, p0, p1); + } + + /// + /// A string like "A node of type '{0}' was read from the JSON reader when trying to read the contents of the property '{1}'; however, a 'StartObject' node or 'PrimitiveValue' node with null value was expected." + /// + internal static string ODataJsonLightResourceDeserializer_CannotReadSingletonNestedResource(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_CannotReadSingletonNestedResource, p0, p1); + } + + /// + /// A string like "A node of type '{0}' was read from the JSON reader when trying to read the contents of the property '{1}'; however, a 'StartArray' node was expected." + /// + internal static string ODataJsonLightResourceDeserializer_CannotReadCollectionNestedResource(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_CannotReadCollectionNestedResource, p0, p1); + } + + /// + /// A string like "A 'PrimitiveValue' node with non-null value was found when trying to read the value of the property '{0}'; however, a 'StartArray' node, a 'StartObject' node, or a 'PrimitiveValue' node with null value was expected." + /// + internal static string ODataJsonLightResourceDeserializer_CannotReadNestedResource(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_CannotReadNestedResource, p0); + } + + /// + /// A string like "The navigation property '{0}' has a property annotation '{1}'. Expanded resource navigation links can only have the 'odata.context', 'odata.navigationLink' and 'odata.associationLink' property annotations." + /// + internal static string ODataJsonLightResourceDeserializer_UnexpectedExpandedSingletonNavigationLinkPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_UnexpectedExpandedSingletonNavigationLinkPropertyAnnotation, p0, p1); + } + + /// + /// A string like "The navigation property '{0}' has a property annotation '{1}'. Expanded resource set navigation links can only have the 'odata.context', 'odata.navigationLink', 'odata.associationLink' and 'odata.nextLink' property annotations" + /// + internal static string ODataJsonLightResourceDeserializer_UnexpectedExpandedCollectionNavigationLinkPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_UnexpectedExpandedCollectionNavigationLinkPropertyAnnotation, p0, p1); + } + + /// + /// A string like "The property '{0}' has a property annotation '{1}'. The complex collection property can only have the 'odata.count', 'odata.type' and 'odata.nextLink' property annotations." + /// + internal static string ODataJsonLightResourceDeserializer_UnexpectedComplexCollectionPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_UnexpectedComplexCollectionPropertyAnnotation, p0, p1); + } + + /// + /// A string like "Multiple property annotations '{0}' were found when reading the nested resource '{1}'. Only a single property annotation '{0}' can be specified for a nested resource." + /// + internal static string ODataJsonLightResourceDeserializer_DuplicateNestedResourceSetAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_DuplicateNestedResourceSetAnnotation, p0, p1); + } + + /// + /// A string like "A property annotation '{0}' was found after the property '{1}' it is annotating. Only the 'odata.nextLink' property annotation can be used after the property it is annotating." + /// + internal static string ODataJsonLightResourceDeserializer_UnexpectedPropertyAnnotationAfterExpandedResourceSet(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_UnexpectedPropertyAnnotationAfterExpandedResourceSet, p0, p1); + } + + /// + /// A string like "The navigation property '{0}' has a property annotation '{1}'. Navigation links in request payloads can only have the '{2}' property annotation." + /// + internal static string ODataJsonLightResourceDeserializer_UnexpectedNavigationLinkInRequestPropertyAnnotation(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_UnexpectedNavigationLinkInRequestPropertyAnnotation, p0, p1, p2); + } + + /// + /// A string like "The resource reference navigation property '{0}' has a property annotation '{1}' with an array value. Resource reference navigation properties can only have a property annotation '{1}' with a string value." + /// + internal static string ODataJsonLightResourceDeserializer_ArrayValueForSingletonBindPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_ArrayValueForSingletonBindPropertyAnnotation, p0, p1); + } + + /// + /// A string like "The resource set reference navigation property '{0}' has a property annotation '{1}' with a string value. Resource set reference navigation properties can only have a property annotation '{1}' with an array value." + /// + internal static string ODataJsonLightResourceDeserializer_StringValueForCollectionBindPropertyAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_StringValueForCollectionBindPropertyAnnotation, p0, p1); + } + + /// + /// A string like "The value of '{0}' property annotation is an empty array. The '{0}' property annotation must have a non-empty array as its value." + /// + internal static string ODataJsonLightResourceDeserializer_EmptyBindArray(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_EmptyBindArray, p0); + } + + /// + /// A string like "The navigation property '{0}' has no expanded value and no '{1}' property annotation. Navigation property in request without expanded value must have the '{1}' property annotation." + /// + internal static string ODataJsonLightResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_NavigationPropertyWithoutValueAndEntityReferenceLink, p0, p1); + } + + /// + /// A string like "The resource reference navigation property '{0}' has both the '{1}' property annotation as well as a value. Resource reference navigation properties can have either '{1}' property annotations or values, but not both." + /// + internal static string ODataJsonLightResourceDeserializer_SingletonNavigationPropertyWithBindingAndValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_SingletonNavigationPropertyWithBindingAndValue, p0, p1); + } + + /// + /// A string like "An undeclared property '{0}' which only has property annotations in the payload but no property value was found in the payload. In OData, only declared navigation properties and declared named streams can be represented as properties without values." + /// + internal static string ODataJsonLightResourceDeserializer_PropertyWithoutValueWithUnknownType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_PropertyWithoutValueWithUnknownType, p0); + } + + /// + /// A string like "Encountered the operation '{0}' which can not be resolved to an ODataAction or ODataFunction." + /// + internal static string ODataJsonLightResourceDeserializer_OperationIsNotActionOrFunction(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_OperationIsNotActionOrFunction, p0); + } + + /// + /// A string like "Multiple '{0}' properties were found for an operation '{1}'. In OData, an operation can have at most one '{0}' property." + /// + internal static string ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_MultipleOptionalPropertiesInOperation, p0, p1); + } + + /// + /// A string like "Multiple target bindings encountered for the operation '{0}' but the 'target' property was not found in an operation value. To differentiate between multiple target bindings, each operation value must have exactly one 'target' property." + /// + internal static string ODataJsonLightResourceDeserializer_OperationMissingTargetProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_OperationMissingTargetProperty, p0); + } + + /// + /// A string like "A metadata reference property was found in a JSON Light request payload. Metadata reference properties are only supported in responses." + /// + internal static string ODataJsonLightResourceDeserializer_MetadataReferencePropertyInRequest { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceDeserializer_MetadataReferencePropertyInRequest); + } + } + + /// + /// A string like "The '{0}' property of the operation '{1}' cannot have a null value." + /// + internal static string ODataJsonLightValidationUtils_OperationPropertyCannotBeNull(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightValidationUtils_OperationPropertyCannotBeNull, p0, p1); + } + + /// + /// A string like "Encountered a reference into metadata '{0}' which does not refer to the known metadata url '{1}'. Open metadata reference properties are not supported." + /// + internal static string ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported, p0, p1); + } + + /// + /// A string like "A relative URI value '{0}' was specified in the payload, but the {1} annotation is missing from the payload. The payload must only contain absolute URIs or the {1} annotation must be on the payload." + /// + internal static string ODataJsonLightDeserializer_RelativeUriUsedWithouODataMetadataAnnotation(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightDeserializer_RelativeUriUsedWithouODataMetadataAnnotation, p0, p1); + } + + /// + /// A string like "The {0} annotation is missing from the payload." + /// + internal static string ODataJsonLightResourceMetadataContext_MetadataAnnotationMustBeInPayload(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightResourceMetadataContext_MetadataAnnotationMustBeInPayload, p0); + } + + /// + /// A string like "When trying to read the start of a collection, the expected collection property with name '{0}' was not found." + /// + internal static string ODataJsonLightCollectionDeserializer_ExpectedCollectionPropertyNotFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightCollectionDeserializer_ExpectedCollectionPropertyNotFound, p0); + } + + /// + /// A string like "A node of type '{0}' was read from the JSON reader when trying to read the items of a collection; however, a 'StartArray' node was expected." + /// + internal static string ODataJsonLightCollectionDeserializer_CannotReadCollectionContentStart(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightCollectionDeserializer_CannotReadCollectionContentStart, p0); + } + + /// + /// A string like "A property or annotation for a property with name '{0}' or an instance annotation with name '{0}' was found after reading the items of a top-level collection. No additional properties or annotations are allowed after the collection property." + /// + internal static string ODataJsonLightCollectionDeserializer_CannotReadCollectionEnd(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightCollectionDeserializer_CannotReadCollectionEnd, p0); + } + + /// + /// A string like "An 'odata.type' annotation with value '{0}' was found for a top-level collection payload; however, top-level collections must specify a collection type." + /// + internal static string ODataJsonLightCollectionDeserializer_InvalidCollectionTypeName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightCollectionDeserializer_InvalidCollectionTypeName, p0); + } + + /// + /// A string like "A node of type '{0}' was read from the JSON reader when trying to read the start of an entity reference link. In JSON Light, entity reference links must be objects." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkMustBeObjectValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkMustBeObjectValue, p0); + } + + /// + /// A string like "A property annotation with name '{0}' was detected when reading an entity reference link; entity reference links do not support property annotations." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLink(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLink, p0); + } + + /// + /// A string like "An instance annotation with name '{0}' or a property annotation for the property with name '{0}' was found when reading an entity reference link. No OData property or instance annotations are allowed when reading entity reference links." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_InvalidAnnotationInEntityReferenceLink(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_InvalidAnnotationInEntityReferenceLink, p0); + } + + /// + /// A string like "A property with name '{0}' was found when reading an entity reference link. No properties other than the entity reference link property with name '{1}' are allowed." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyInEntityReferenceLink(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyInEntityReferenceLink, p0, p1); + } + + /// + /// A string like "The required property '{0}' for an entity reference link was not found." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_MissingEntityReferenceLinkProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_MissingEntityReferenceLinkProperty, p0); + } + + /// + /// A string like "Multiple '{0}' properties were found in an entity reference link object; however, a single '{0}' property was expected." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_MultipleUriPropertiesInEntityReferenceLink(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_MultipleUriPropertiesInEntityReferenceLink, p0); + } + + /// + /// A string like "The '{0}' property of an entity reference link object cannot have a null value." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkUrlCannotBeNull(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_EntityReferenceLinkUrlCannotBeNull, p0); + } + + /// + /// A string like "A property annotation was found for entity reference links; however, entity reference links only support instance annotations." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLinks { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_PropertyAnnotationForEntityReferenceLinks); + } + } + + /// + /// A string like "A property with name '{0}' or a property annotation for a property with name '{0}' was found when trying to read a collection of entity reference links; however, a property with name '{1}' was expected." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_InvalidEntityReferenceLinksPropertyFound(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_InvalidEntityReferenceLinksPropertyFound, p0, p1); + } + + /// + /// A string like "A property annotation for a property with name '{0}' was found when reading an entity reference links payload. No property annotations, only instance annotations are allowed when reading entity reference links." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyAnnotationInEntityReferenceLinks(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_InvalidPropertyAnnotationInEntityReferenceLinks, p0); + } + + /// + /// A string like "Did not find the required '{0}' property for an entity reference links payload." + /// + internal static string ODataJsonLightEntityReferenceLinkDeserializer_ExpectedEntityReferenceLinksPropertyNotFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightEntityReferenceLinkDeserializer_ExpectedEntityReferenceLinksPropertyNotFound, p0); + } + + /// + /// A string like "The '{0}' property of an operation '{1}' in '{2}' cannot have a null value." + /// + internal static string ODataJsonOperationsDeserializerUtils_OperationPropertyCannotBeNull(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonOperationsDeserializerUtils_OperationPropertyCannotBeNull, p0, p1, p2); + } + + /// + /// A string like "Found a node of type '{1}' when starting to read the '{0}' operations value, however a node of type 'StartObject' was expected. The '{0}' operations value must have an object value." + /// + internal static string ODataJsonOperationsDeserializerUtils_OperationsPropertyMustHaveObjectValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonOperationsDeserializerUtils_OperationsPropertyMustHaveObjectValue, p0, p1); + } + + /// + /// A string like "Multiple '{0}' properties were found in a service document. In OData, a service document must have exactly one '{0}' property." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocument(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocument, p0); + } + + /// + /// A string like "Multiple '{0}' properties were found in a service document element. In OData, a service document element must have exactly one '{0}' property." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_DuplicatePropertiesInServiceDocumentElement, p0); + } + + /// + /// A string like "No '{0}' property was found for a service document. In OData, a service document must have exactly one '{0}' property." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_MissingValuePropertyInServiceDocument(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_MissingValuePropertyInServiceDocument, p0); + } + + /// + /// A string like "Encountered a service document element without a '{0}' property. In service documents, service document elements must contain a '{0}' property." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_MissingRequiredPropertyInServiceDocumentElement(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_MissingRequiredPropertyInServiceDocumentElement, p0); + } + + /// + /// A string like "An unrecognized property annotation '{0}' was found in a '{1}' object in a service document. OData property annotations are not allowed in workspaces." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocument(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocument, p0, p1); + } + + /// + /// A string like "An unrecognized instance annotation '{0}' was found in a '{1}' object in a service document. OData instance annotations are not allowed in workspaces." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocument(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocument, p0, p1); + } + + /// + /// A string like "An unrecognized property annotation '{0}' was found in a service document element. OData property annotations are not allowed in service document elements." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocumentElement(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationInServiceDocumentElement, p0); + } + + /// + /// A string like "An unrecognized instance annotation '{0}' was found in a service document element. OData instance annotations are not allowed in service document elements." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocumentElement(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_InstanceAnnotationInServiceDocumentElement, p0); + } + + /// + /// A string like "Encountered unexpected property '{0}' in a service document element. In service documents, service document element may only have '{1}' and '{2}' properties." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocumentElement(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocumentElement, p0, p1, p2); + } + + /// + /// A string like "Encountered unexpected property '{0}' in a service document. The top level object of a service document may only have a '{1}' property." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocument(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_UnexpectedPropertyInServiceDocument, p0, p1); + } + + /// + /// A string like "Encountered a property annotation for the property '{0}' which wasn't immediately followed by the property. Property annotations must occur directly before the property being annotated." + /// + internal static string ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationWithoutProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightServiceDocumentDeserializer_PropertyAnnotationWithoutProperty, p0); + } + + /// + /// A string like "An OData property annotation was found for a parameter payload; however, parameter payloads do not support OData property annotations." + /// + internal static string ODataJsonLightParameterDeserializer_PropertyAnnotationForParameters { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightParameterDeserializer_PropertyAnnotationForParameters); + } + } + + /// + /// A string like "One or more property annotations for property '{0}' were found in a parameter payload without the property to annotate. Parameter payloads must not contain property annotations for properties that are not in the payload." + /// + internal static string ODataJsonLightParameterDeserializer_PropertyAnnotationWithoutPropertyForParameters(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightParameterDeserializer_PropertyAnnotationWithoutPropertyForParameters, p0); + } + + /// + /// A string like "The parameter '{0}' is of the '{1}' primitive type, which is not supported in JSON Light." + /// + internal static string ODataJsonLightParameterDeserializer_UnsupportedPrimitiveParameterType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightParameterDeserializer_UnsupportedPrimitiveParameterType, p0, p1); + } + + /// + /// A string like "When trying to read a null collection parameter value in JSON Light, a node of type '{0}' with the value '{1}' was read from the JSON reader; however, a primitive 'null' value was expected." + /// + internal static string ODataJsonLightParameterDeserializer_NullCollectionExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightParameterDeserializer_NullCollectionExpected, p0, p1); + } + + /// + /// A string like "The parameter '{0}' is of an unsupported type kind '{1}'. Only primitive, enum, complex, primitive collection, enum collection and complex collection types are supported." + /// + internal static string ODataJsonLightParameterDeserializer_UnsupportedParameterTypeKind(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightParameterDeserializer_UnsupportedParameterTypeKind, p0, p1); + } + + /// + /// A string like "When parsing a select clause a '*' segment was found before last segment of a property path. In OData, a '*' segment can only appear as last segment of a property path." + /// + internal static string SelectedPropertiesNode_StarSegmentNotLastSegment { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectedPropertiesNode_StarSegmentNotLastSegment); + } + } + + /// + /// A string like "When parsing a select clause a '*' segment was found immediately after a type segment in a property path. In OData, a '*' segment cannot appear following a type segment." + /// + internal static string SelectedPropertiesNode_StarSegmentAfterTypeSegment { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectedPropertiesNode_StarSegmentAfterTypeSegment); + } + } + + /// + /// A string like "An OData property annotation '{0}' was found in an error payload; however, error payloads do not support OData property annotations." + /// + internal static string ODataJsonLightErrorDeserializer_PropertyAnnotationNotAllowedInErrorPayload(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightErrorDeserializer_PropertyAnnotationNotAllowedInErrorPayload, p0); + } + + /// + /// A string like "An OData instance annotation '{0}' was found in an error payload; however, error payloads do not support OData instance annotations." + /// + internal static string ODataJsonLightErrorDeserializer_InstanceAnnotationNotAllowedInErrorPayload(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightErrorDeserializer_InstanceAnnotationNotAllowedInErrorPayload, p0); + } + + /// + /// A string like "One or more property annotations for property '{0}' were found in an error payload without the property to annotate. Error payloads must not contain property annotations for properties that are not in the payload." + /// + internal static string ODataJsonLightErrorDeserializer_PropertyAnnotationWithoutPropertyForError(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightErrorDeserializer_PropertyAnnotationWithoutPropertyForError, p0); + } + + /// + /// A string like "A property with name '{0}' was found in the error value of a top-level error. In OData, a top-level error value can only have properties with name 'code', 'message', or 'innererror', or custom instance annotations." + /// + internal static string ODataJsonLightErrorDeserializer_TopLevelErrorValueWithInvalidProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightErrorDeserializer_TopLevelErrorValueWithInvalidProperty, p0); + } + + /// + /// A string like "The entity type '{0}' has no key properties. Entity types must define at least one key property." + /// + internal static string ODataConventionalUriBuilder_EntityTypeWithNoKeyProperties(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataConventionalUriBuilder_EntityTypeWithNoKeyProperties, p0); + } + + /// + /// A string like "The key property '{0}' on type '{1}' has a null value. Key properties must not have null values." + /// + internal static string ODataConventionalUriBuilder_NullKeyValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataConventionalUriBuilder_NullKeyValue, p0, p1); + } + + /// + /// A string like "An ODataResource of type '{0}' is found without key properties. When writing without a user model, each resource must contain at least one property whose 'ODataProperty.SerializationInfo.PropertyKind' set to 'ODataPropertyKind.Key'. When writing with a user model, the entity type '{0}' defined in the model must define at least one key property." + /// + internal static string ODataResourceMetadataContext_EntityTypeWithNoKeyProperties(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResourceMetadataContext_EntityTypeWithNoKeyProperties, p0); + } + + /// + /// A string like "The key property '{0}' on type '{1}' has a null value. Key properties must not have null values." + /// + internal static string ODataResourceMetadataContext_NullKeyValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResourceMetadataContext_NullKeyValue, p0, p1); + } + + /// + /// A string like "The property '{0}' on type '{1}' is a non-primitive value. All key and etag properties must be of primitive types." + /// + internal static string ODataResourceMetadataContext_KeyOrETagValuesMustBePrimitiveValues(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResourceMetadataContext_KeyOrETagValuesMustBePrimitiveValues, p0, p1); + } + + /// + /// A string like "The value of a property '{0}' in ODataResource cannot be of type ODataResourceValue or collection of ODataResourceValue." + /// + internal static string ODataResource_PropertyValueCannotBeODataResourceValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResource_PropertyValueCannotBeODataResourceValue, p0); + } + + /// + /// A string like "The primitive property '{0}' on type '{1}' has a value which is not a primitive value." + /// + internal static string EdmValueUtils_NonPrimitiveValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmValueUtils_NonPrimitiveValue, p0, p1); + } + + /// + /// A string like "The entity instance value of type '{0}' doesn't have a value for property '{1}'. To compute an entity's metadata, its key and concurrency-token property values must be provided." + /// + internal static string EdmValueUtils_PropertyDoesntExist(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.EdmValueUtils_PropertyDoesntExist, p0, p1); + } + + /// + /// A string like "Cannot create an ODataPrimitiveValue from null; use ODataNullValue instead." + /// + internal static string ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromNull); + } + } + + /// + /// A string like "An ODataPrimitiveValue was instantiated with a value of type '{0}'. ODataPrimitiveValue can only wrap values which can be represented as primitive EDM types." + /// + internal static string ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromUnsupportedValueType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromUnsupportedValueType, p0); + } + + /// + /// A string like "'{0}' is an invalid instance annotation name. An instance annotation name must contain a period that is not at the start or end of the name." + /// + internal static string ODataInstanceAnnotation_NeedPeriodInName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataInstanceAnnotation_NeedPeriodInName, p0); + } + + /// + /// A string like "'{0}' is a reserved instance annotation name because it starts with '{1}'. Reserved names are not allowed for custom instance annotations." + /// + internal static string ODataInstanceAnnotation_ReservedNamesNotAllowed(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataInstanceAnnotation_ReservedNamesNotAllowed, p0, p1); + } + + /// + /// A string like "'{0}' is an invalid instance annotation name." + /// + internal static string ODataInstanceAnnotation_BadTermName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataInstanceAnnotation_BadTermName, p0); + } + + /// + /// A string like "The value of an instance annotation cannot be of type ODataStreamReferenceValue." + /// + internal static string ODataInstanceAnnotation_ValueCannotBeODataStreamReferenceValue { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataInstanceAnnotation_ValueCannotBeODataStreamReferenceValue); + } + } + + /// + /// A string like "A type name was not provided for an instance of ODataCollectionValue." + /// + internal static string ODataJsonLightValueSerializer_MissingTypeNameOnCollection { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightValueSerializer_MissingTypeNameOnCollection); + } + } + + /// + /// A string like "A raw value was not provided for an instance of ODataUntypedValue." + /// + internal static string ODataJsonLightValueSerializer_MissingRawValueOnUntyped { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataJsonLightValueSerializer_MissingRawValueOnUntyped); + } + } + + /// + /// A string like "Encountered an 'annotation' element without a 'term' attribute. All 'annotation' elements must have a 'term' attribute." + /// + internal static string AtomInstanceAnnotation_MissingTermAttributeOnAnnotationElement { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AtomInstanceAnnotation_MissingTermAttributeOnAnnotationElement); + } + } + + /// + /// A string like "The value of the 'type' attribute on an 'annotation' element was '{0}', which is incompatible with the '{1}' attribute." + /// + internal static string AtomInstanceAnnotation_AttributeValueNotationUsedWithIncompatibleType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AtomInstanceAnnotation_AttributeValueNotationUsedWithIncompatibleType, p0, p1); + } + + /// + /// A string like "Encountered the attribute '{0}' on a non-empty 'annotation' element. If attribute value notation is used to specify the annotation's value, then there can be no body to the element." + /// + internal static string AtomInstanceAnnotation_AttributeValueNotationUsedOnNonEmptyElement(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AtomInstanceAnnotation_AttributeValueNotationUsedOnNonEmptyElement, p0); + } + + /// + /// A string like "Encountered an 'annotation' element with more than one attribute from following set: 'int', 'string', 'decimal', 'float', and 'bool'. Only one such attribute may appear on an 'annotation' element." + /// + internal static string AtomInstanceAnnotation_MultipleAttributeValueNotationAttributes { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AtomInstanceAnnotation_MultipleAttributeValueNotationAttributes); + } + } + + /// + /// A string like "The pattern '{0}' is not a valid pattern to match an annotation. It must contain at least one '.' separating the namespace and the name segments of an annotation." + /// + internal static string AnnotationFilterPattern_InvalidPatternMissingDot(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AnnotationFilterPattern_InvalidPatternMissingDot, p0); + } + + /// + /// A string like "The pattern '{0}' is not a valid pattern to match an annotation. It must not contain a namespace or name segment that is empty." + /// + internal static string AnnotationFilterPattern_InvalidPatternEmptySegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AnnotationFilterPattern_InvalidPatternEmptySegment, p0); + } + + /// + /// A string like "The pattern '{0}' is not a supported pattern to match an annotation. It must not contain '*' as part of a segment." + /// + internal static string AnnotationFilterPattern_InvalidPatternWildCardInSegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AnnotationFilterPattern_InvalidPatternWildCardInSegment, p0); + } + + /// + /// A string like "The pattern '{0}' is not a supported pattern to match an annotation. '*' must be the last segment of the pattern." + /// + internal static string AnnotationFilterPattern_InvalidPatternWildCardMustBeInLastSegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AnnotationFilterPattern_InvalidPatternWildCardMustBeInLastSegment, p0); + } + + /// + /// A string like "The specified URI '{0}' must be absolute." + /// + internal static string SyntacticTree_UriMustBeAbsolute(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SyntacticTree_UriMustBeAbsolute, p0); + } + + /// + /// A string like "The maximum depth setting must be a number greater than zero." + /// + internal static string SyntacticTree_MaxDepthInvalid { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SyntacticTree_MaxDepthInvalid); + } + } + + /// + /// A string like "Invalid value '{0}' for $skip query option found. The $skip query option requires a non-negative integer value." + /// + internal static string SyntacticTree_InvalidSkipQueryOptionValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SyntacticTree_InvalidSkipQueryOptionValue, p0); + } + + /// + /// A string like "Invalid value '{0}' for $top query option found. The $top query option requires a non-negative integer value." + /// + internal static string SyntacticTree_InvalidTopQueryOptionValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SyntacticTree_InvalidTopQueryOptionValue, p0); + } + + /// + /// A string like "Invalid value '{0}' for $count query option found. Valid values are '{1}'." + /// + internal static string SyntacticTree_InvalidCountQueryOptionValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SyntacticTree_InvalidCountQueryOptionValue, p0, p1); + } + + /// + /// A string like "Invalid value '{0}' for $index query option found. The $index query option requires an integer value." + /// + internal static string SyntacticTree_InvalidIndexQueryOptionValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SyntacticTree_InvalidIndexQueryOptionValue, p0); + } + + /// + /// A string like "Query option '{0}' was specified more than once, but it must be specified at most once." + /// + internal static string QueryOptionUtils_QueryParameterMustBeSpecifiedOnce(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce, p0); + } + + /// + /// A string like "The CLR literal of type '{0}' is not supported to be written as a Uri part." + /// + internal static string UriBuilder_NotSupportedClrLiteral(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriBuilder_NotSupportedClrLiteral, p0); + } + + /// + /// A string like "QueryToken '{0}' is not supported to be written as a Uri part." + /// + internal static string UriBuilder_NotSupportedQueryToken(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriBuilder_NotSupportedQueryToken, p0); + } + + /// + /// A string like "Recursion depth exceeded allowed limit." + /// + internal static string UriQueryExpressionParser_TooDeep { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_TooDeep); + } + } + + /// + /// A string like "Expression expected at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_ExpressionExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_ExpressionExpected, p0, p1); + } + + /// + /// A string like "'(' expected at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_OpenParenExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_OpenParenExpected, p0, p1); + } + + /// + /// A string like "')' or ',' expected at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_CloseParenOrCommaExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_CloseParenOrCommaExpected, p0, p1); + } + + /// + /// A string like "')' or operator expected at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_CloseParenOrOperatorExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_CloseParenOrOperatorExpected, p0, p1); + } + + /// + /// A string like "Expecting a Star token but got: '{0}'." + /// + internal static string UriQueryExpressionParser_CannotCreateStarTokenFromNonStar(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_CannotCreateStarTokenFromNonStar, p0); + } + + /// + /// A string like "The range variable '{0}' has already been declared." + /// + internal static string UriQueryExpressionParser_RangeVariableAlreadyDeclared(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_RangeVariableAlreadyDeclared, p0); + } + + /// + /// A string like "'as' expected at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_AsExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_AsExpected, p0, p1); + } + + /// + /// A string like "'with' expected at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_WithExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_WithExpected, p0, p1); + } + + /// + /// A string like "Unrecognized with '{0}' at '{1}' in '{2}'." + /// + internal static string UriQueryExpressionParser_UnrecognizedWithMethod(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_UnrecognizedWithMethod, p0, p1, p2); + } + + /// + /// A string like "Expression expected at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_PropertyPathExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_PropertyPathExpected, p0, p1); + } + + /// + /// A string like "'{0}' expected at position {1} in '{2}'." + /// + internal static string UriQueryExpressionParser_KeywordOrIdentifierExpected(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_KeywordOrIdentifierExpected, p0, p1, p2); + } + + /// + /// A string like "The inner most expand transformation requires a filter transformation at position {0} in '{1}'." + /// + internal static string UriQueryExpressionParser_InnerMostExpandRequireFilter(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_InnerMostExpandRequireFilter, p0, p1); + } + + /// + /// A string like "The URI '{0}' is not valid because it is not based on '{1}'." + /// + internal static string UriQueryPathParser_RequestUriDoesNotHaveTheCorrectBaseUri(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryPathParser_RequestUriDoesNotHaveTheCorrectBaseUri, p0, p1); + } + + /// + /// A string like "Bad Request: there was an error in the query syntax." + /// + internal static string UriQueryPathParser_SyntaxError { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryPathParser_SyntaxError); + } + } + + /// + /// A string like "Too many segments in URI." + /// + internal static string UriQueryPathParser_TooManySegments { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryPathParser_TooManySegments); + } + } + + /// + /// A string like "The URI part '{0}' is not valid because there's no leading escape character." + /// + internal static string UriQueryPathParser_InvalidEscapeUri(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryPathParser_InvalidEscapeUri, p0); + } + + /// + /// A string like "The DateTimeOffset text '{0}' should be in format 'yyyy-mm-ddThh:mm:ss('.'s+)?(zzzzzz)?' and each field value is within valid range." + /// + internal static string UriUtils_DateTimeOffsetInvalidFormat(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriUtils_DateTimeOffsetInvalidFormat, p0); + } + + /// + /// A string like "Inner or start path segments must be navigation properties in $select." + /// + internal static string SelectionItemBinder_NonNavigationPathToken { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectionItemBinder_NonNavigationPathToken); + } + } + + /// + /// A string like "An unsupported query token kind '{0}' was found." + /// + internal static string MetadataBinder_UnsupportedQueryTokenKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_UnsupportedQueryTokenKind, p0); + } + + /// + /// A string like "Could not find a property named '{1}' on type '{0}'." + /// + internal static string MetadataBinder_PropertyNotDeclared(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_PropertyNotDeclared, p0, p1); + } + + /// + /// A string like "Property '{0}' is not declared on type '{1}' or is not a key property. Only key properties can be used in key lookups." + /// + internal static string MetadataBinder_PropertyNotDeclaredOrNotKeyInKeyValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_PropertyNotDeclaredOrNotKeyInKeyValue, p0, p1); + } + + /// + /// A string like "Could not find a function named '{0}' with parameters '{1}'." + /// + internal static string MetadataBinder_QualifiedFunctionNameWithParametersNotDeclared(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_QualifiedFunctionNameWithParametersNotDeclared, p0, p1); + } + + /// + /// A string like "An unnamed key value was used in a key lookup on a type '{0}' which has more than one key property. Unnamed key value can only be used on a type with one key property." + /// + internal static string MetadataBinder_UnnamedKeyValueOnTypeWithMultipleKeyProperties(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_UnnamedKeyValueOnTypeWithMultipleKeyProperties, p0); + } + + /// + /// A string like "A key property '{0}' was found twice in a key lookup. Each key property can be specified just once in a key lookup." + /// + internal static string MetadataBinder_DuplicitKeyPropertyInKeyValues(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_DuplicitKeyPropertyInKeyValues, p0); + } + + /// + /// A string like "A key lookup on type '{0}' didn't specify values for all key properties. All key properties must be specified in a key lookup." + /// + internal static string MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues, p0); + } + + /// + /// A string like "Expression of type '{0}' cannot be converted to type '{1}'." + /// + internal static string MetadataBinder_CannotConvertToType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_CannotConvertToType, p0, p1); + } + + /// + /// A string like "The $filter expression must evaluate to a single boolean value." + /// + internal static string MetadataBinder_FilterExpressionNotSingleValue { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_FilterExpressionNotSingleValue); + } + } + + /// + /// A string like "The $orderby expression must evaluate to a single value of primitive type." + /// + internal static string MetadataBinder_OrderByExpressionNotSingleValue { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_OrderByExpressionNotSingleValue); + } + } + + /// + /// A string like "A PropertyAccessQueryToken without a parent was encountered outside of $filter or $orderby expression. The PropertyAccessQueryToken without a parent token is only allowed inside $filter or $orderby expressions." + /// + internal static string MetadataBinder_PropertyAccessWithoutParentParameter { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_PropertyAccessWithoutParentParameter); + } + } + + /// + /// A string like "The operand for a binary operator '{0}' is not a single value. Binary operators require both operands to be single values." + /// + internal static string MetadataBinder_BinaryOperatorOperandNotSingleValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_BinaryOperatorOperandNotSingleValue, p0); + } + + /// + /// A string like "The operand for a unary operator '{0}' is not a single value. Unary operators require the operand to be a single value." + /// + internal static string MetadataBinder_UnaryOperatorOperandNotSingleValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_UnaryOperatorOperandNotSingleValue, p0); + } + + /// + /// A string like "The left operand for the IN operation is not a single value. IN operations require the left operand to be a single value and the right operand to be a collection value." + /// + internal static string MetadataBinder_LeftOperandNotSingleValue { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_LeftOperandNotSingleValue); + } + } + + /// + /// A string like "The right operand for the IN operation is not a collection value. IN operations require the left operand to be a single value and the right operand to be a collection value." + /// + internal static string MetadataBinder_RightOperandNotCollectionValue { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_RightOperandNotCollectionValue); + } + } + + /// + /// A string like "The parent value for a property access of a property '{0}' is not a single value. Property access can only be applied to a single value." + /// + internal static string MetadataBinder_PropertyAccessSourceNotSingleValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_PropertyAccessSourceNotSingleValue, p0); + } + + /// + /// A string like "A binary operator with incompatible types was detected. Found operand types '{0}' and '{1}' for operator kind '{2}'." + /// + internal static string MetadataBinder_IncompatibleOperandsError(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_IncompatibleOperandsError, p0, p1, p2); + } + + /// + /// A string like "A unary operator with an incompatible type was detected. Found operand type '{0}' for operator kind '{1}'." + /// + internal static string MetadataBinder_IncompatibleOperandError(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_IncompatibleOperandError, p0, p1); + } + + /// + /// A string like "An unknown function with name '{0}' was found. This may also be a function import or a key lookup on a navigation property, which is not allowed." + /// + internal static string MetadataBinder_UnknownFunction(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_UnknownFunction, p0); + } + + /// + /// A string like "The argument for an invocation of a function with name '{0}' is not a single value. All arguments for this function must be single values." + /// + internal static string MetadataBinder_FunctionArgumentNotSingleValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_FunctionArgumentNotSingleValue, p0); + } + + /// + /// A string like "No function signature for the function with name '{0}' matches the specified arguments. The function signatures considered are: {1}." + /// + internal static string MetadataBinder_NoApplicableFunctionFound(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_NoApplicableFunctionFound, p0, p1); + } + + /// + /// A string like "A token of kind '{0}' was bound to the value null; this is invalid. A query token must always be bound to a non-null query node." + /// + internal static string MetadataBinder_BoundNodeCannotBeNull(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_BoundNodeCannotBeNull, p0); + } + + /// + /// A string like "The value '{0}' is not a non-negative integer value. In OData, the $top query option must specify a non-negative integer value." + /// + internal static string MetadataBinder_TopRequiresNonNegativeInteger(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_TopRequiresNonNegativeInteger, p0); + } + + /// + /// A string like "The value '{0}' is not a non-negative integer value. In OData, the $skip query option must specify a non-negative integer value." + /// + internal static string MetadataBinder_SkipRequiresNonNegativeInteger(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_SkipRequiresNonNegativeInteger, p0); + } + + /// + /// A string like "The bind state cannot be null. In OData, the bind state for query options should not be null and there should be query options in the object." + /// + internal static string MetadataBinder_QueryOptionsBindStateCannotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_QueryOptionsBindStateCannotBeNull); + } + } + + /// + /// A string like "The bind method cannot be null. In OData, the processing of query options should have a corresponding bind method." + /// + internal static string MetadataBinder_QueryOptionsBindMethodCannotBeNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_QueryOptionsBindMethodCannotBeNull); + } + } + + /// + /// A string like "Encountered invalid type cast. '{0}' is not assignable from '{1}'." + /// + internal static string MetadataBinder_HierarchyNotFollowed(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_HierarchyNotFollowed, p0, p1); + } + + /// + /// A string like "Any/All may only be used following a collection." + /// + internal static string MetadataBinder_LambdaParentMustBeCollection { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_LambdaParentMustBeCollection); + } + } + + /// + /// A string like "The parameter '{0}' is not in scope." + /// + internal static string MetadataBinder_ParameterNotInScope(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_ParameterNotInScope, p0); + } + + /// + /// A string like "A navigation property can only follow single entity nodes." + /// + internal static string MetadataBinder_NavigationPropertyNotFollowingSingleEntityType { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_NavigationPropertyNotFollowingSingleEntityType); + } + } + + /// + /// A string like "The Any/All query expression must evaluate to a single boolean value." + /// + internal static string MetadataBinder_AnyAllExpressionNotSingleValue { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_AnyAllExpressionNotSingleValue); + } + } + + /// + /// A string like "The Cast or IsOf expression has an invalid number of operands: number of operands is '{0}' and it should be 1 or 2." + /// + internal static string MetadataBinder_CastOrIsOfExpressionWithWrongNumberOfOperands(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_CastOrIsOfExpressionWithWrongNumberOfOperands, p0); + } + + /// + /// A string like "Cast or IsOf Function must have a type in its arguments." + /// + internal static string MetadataBinder_CastOrIsOfFunctionWithoutATypeArgument { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_CastOrIsOfFunctionWithoutATypeArgument); + } + } + + /// + /// A string like "The Cast and IsOf functions do not support collection arguments or types." + /// + internal static string MetadataBinder_CastOrIsOfCollectionsNotSupported { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_CastOrIsOfCollectionsNotSupported); + } + } + + /// + /// A string like "Collection open properties are not supported in this release." + /// + internal static string MetadataBinder_CollectionOpenPropertiesNotSupportedInThisRelease { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_CollectionOpenPropertiesNotSupportedInThisRelease); + } + } + + /// + /// A string like "Can only bind segments that are Navigation, Structural, Complex, or Collections. We found a segment '{0}' that isn't any of those. Please revise the query." + /// + internal static string MetadataBinder_IllegalSegmentType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_IllegalSegmentType, p0); + } + + /// + /// A string like "The '{0}' option cannot be applied to the query path. '{0}' can only be applied to a collection of entities." + /// + internal static string MetadataBinder_QueryOptionNotApplicable(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_QueryOptionNotApplicable, p0); + } + + /// + /// A string like "String item should be single/double quoted: '{0}'." + /// + internal static string StringItemShouldBeQuoted(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.StringItemShouldBeQuoted, p0); + } + + /// + /// A string like "Invalid PrimitiveTypeKind {0}. A Stream item must be of type binary or string, or none if unknown."" + /// + internal static string StreamItemInvalidPrimitiveKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.StreamItemInvalidPrimitiveKind, p0); + } + + /// + /// A string like "$apply/aggregate expression '{0}' operation does not support value type '{1}'." + /// + internal static string ApplyBinder_AggregateExpressionIncompatibleTypeForMethod(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ApplyBinder_AggregateExpressionIncompatibleTypeForMethod, p0, p1); + } + + /// + /// A string like "$apply/aggregate does not support method '{0}'." + /// + internal static string ApplyBinder_UnsupportedAggregateMethod(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ApplyBinder_UnsupportedAggregateMethod, p0); + } + + /// + /// A string like "$apply/aggregate expression token kind '{0}' not supported." + /// + internal static string ApplyBinder_UnsupportedAggregateKind(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ApplyBinder_UnsupportedAggregateKind, p0); + } + + /// + /// A string like "$apply/aggregate expression '{0}' must evaluate to a single value." + /// + internal static string ApplyBinder_AggregateExpressionNotSingleValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ApplyBinder_AggregateExpressionNotSingleValue, p0); + } + + /// + /// A string like "$apply/groupby grouping expression '{0}' must evaluate to a property access value." + /// + internal static string ApplyBinder_GroupByPropertyNotPropertyAccessValue(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ApplyBinder_GroupByPropertyNotPropertyAccessValue, p0); + } + + /// + /// A string like "$apply clause does not support type '{0}'." + /// + internal static string ApplyBinder_UnsupportedType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ApplyBinder_UnsupportedType, p0); + } + + /// + /// A string like "$apply/groupby not support '{0}' as child transformation" + /// + internal static string ApplyBinder_UnsupportedGroupByChild(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ApplyBinder_UnsupportedGroupByChild, p0); + } + + /// + /// A string like "There are unsupported aggregation expressions in the transformation node." + /// + internal static string AggregateTransformationNode_UnsupportedAggregateExpressions { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.AggregateTransformationNode_UnsupportedAggregateExpressions); + } + } + + /// + /// A string like "Cannot find a suitable overload for function '{0}' that takes '{1}' arguments." + /// + internal static string FunctionCallBinder_CannotFindASuitableOverload(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionCallBinder_CannotFindASuitableOverload, p0, p1); + } + + /// + /// A string like "Found a Uri function '{0}' with a parent token. Uri functions cannot have parent tokens." + /// + internal static string FunctionCallBinder_UriFunctionMustHaveHaveNullParent(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionCallBinder_UriFunctionMustHaveHaveNullParent, p0); + } + + /// + /// A string like "Found a function '{0}' on an open property. Functions on open properties are not supported." + /// + internal static string FunctionCallBinder_CallingFunctionOnOpenProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionCallBinder_CallingFunctionOnOpenProperty, p0); + } + + /// + /// A string like "Parameter or entity key names must be unique. There is most likely an error in the model." + /// + internal static string FunctionCallParser_DuplicateParameterOrEntityKeyName { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionCallParser_DuplicateParameterOrEntityKeyName); + } + } + + /// + /// A string like "'{0}' is not a valid count option." + /// + internal static string ODataUriParser_InvalidCount(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataUriParser_InvalidCount, p0); + } + + /// + /// A string like "The child type '{0}' in a cast was not an entity type. Casts can only be performed on entity types." + /// + internal static string CastBinder_ChildTypeIsNotEntity(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CastBinder_ChildTypeIsNotEntity, p0); + } + + /// + /// A string like "Enumeration type value can only be casted to or from string." + /// + internal static string CastBinder_EnumOnlyCastToOrFromString { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CastBinder_EnumOnlyCastToOrFromString); + } + } + + /// + /// A string like "The string '{0}' is not a valid enumeration type constant." + /// + internal static string Binder_IsNotValidEnumConstant(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Binder_IsNotValidEnumConstant, p0); + } + + /// + /// A string like "Invalid content-id '{0}' for batch reference segment." + /// + internal static string BatchReferenceSegment_InvalidContentID(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.BatchReferenceSegment_InvalidContentID, p0); + } + + /// + /// A string like "Property '{0}' is of an unrecognized EdmPropertyKind." + /// + internal static string SelectExpandBinder_UnknownPropertyType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectExpandBinder_UnknownPropertyType, p0); + } + + /// + /// A string like "Only properties specified in $expand can be traversed in $select query options. Selected item was '{0}'." + /// + internal static string SelectionItemBinder_NoExpandForSelectedProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectionItemBinder_NoExpandForSelectedProperty, p0); + } + + /// + /// A string like "Trying to follow type segments on a segment that isn't a type. Segment was '{0}'." + /// + internal static string SelectExpandPathBinder_FollowNonTypeSegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectExpandPathBinder_FollowNonTypeSegment, p0); + } + + /// + /// A string like "Found a system token, '{0}', while parsing a select clause." + /// + internal static string SelectPropertyVisitor_SystemTokenInSelect(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectPropertyVisitor_SystemTokenInSelect, p0); + } + + /// + /// A string like "Any selection that is expanded must have the same type qualifier on both selection and expansion." + /// + internal static string SelectPropertyVisitor_DisparateTypeSegmentsInSelectExpand { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectPropertyVisitor_DisparateTypeSegmentsInSelectExpand); + } + } + + /// + /// A string like "Found a path with multiple navigation properties or a bad complex property path in a select clause. Please reword your query such that each level of select or expand only contains either TypeSegments or Properties." + /// + internal static string SelectBinder_MultiLevelPathInSelect { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.SelectBinder_MultiLevelPathInSelect); + } + } + + /// + /// A string like "Trying to traverse a non-normalized expand tree." + /// + internal static string ExpandItemBinder_TraversingANonNormalizedTree { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_TraversingANonNormalizedTree); + } + } + + /// + /// A string like "The type '{0}' is not defined in the model." + /// + internal static string ExpandItemBinder_CannotFindType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_CannotFindType, p0); + } + + /// + /// A string like "Property '{0}' on type '{1}' is not a navigation property or complex property. Only navigation properties can be expanded." + /// + internal static string ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty, p0, p1); + } + + /// + /// A string like "Found a path within a select or expand query option that isn't ended by a non-type segment." + /// + internal static string ExpandItemBinder_TypeSegmentNotFollowedByPath { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_TypeSegmentNotFollowedByPath); + } + } + + /// + /// A string like "Trying to parse a type segment path that is too long." + /// + internal static string ExpandItemBinder_PathTooDeep { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_PathTooDeep); + } + } + + /// + /// A string like "Found a path traversing multiple navigation properties. Please rephrase the query such that each expand path contains only type segments and navigation properties." + /// + internal static string ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); + } + } + + /// + /// A string like "The $level option on navigation property '{0}' is not allowed, because the related entity type '{1}' could not be cast to source entity type '{2}'." + /// + internal static string ExpandItemBinder_LevelsNotAllowedOnIncompatibleRelatedType(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_LevelsNotAllowedOnIncompatibleRelatedType, p0, p1, p2); + } + + /// + /// A string like "Segment '{0}' is not valid in expand path. Before navigation property, only type segment or entity or complex property can exist." + /// + internal static string ExpandItemBinder_InvaidSegmentInExpand(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandItemBinder_InvaidSegmentInExpand, p0); + } + + /// + /// A string like "The navigation property must have a target multiplicity of 'One' or 'ZeroOrOne' to create a SingleNavigationNode." + /// + internal static string Nodes_CollectionNavigationNode_MustHaveSingleMultiplicity { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_CollectionNavigationNode_MustHaveSingleMultiplicity); + } + } + + /// + /// A string like "An entity type '{0}' was given to NonEntityParameterQueryNode. Use EntityParameterQueryNode instead." + /// + internal static string Nodes_NonentityParameterQueryNodeWithEntityType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_NonentityParameterQueryNodeWithEntityType, p0); + } + + /// + /// A string like "The navigation property must have a target multiplicity of 'Many' to create a CollectionNavigationNode." + /// + internal static string Nodes_CollectionNavigationNode_MustHaveManyMultiplicity { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_CollectionNavigationNode_MustHaveManyMultiplicity); + } + } + + /// + /// A string like "A node of this kind requires the associated property to be a structural, non-collection type, but property '{0}' is not structural." + /// + internal static string Nodes_PropertyAccessShouldBeNonEntityProperty(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_PropertyAccessShouldBeNonEntityProperty, p0); + } + + /// + /// A string like "A node of this kind requires the associated property to be a structural, non-collection type, but property '{0}' is a collection." + /// + internal static string Nodes_PropertyAccessTypeShouldNotBeCollection(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_PropertyAccessTypeShouldNotBeCollection, p0); + } + + /// + /// A string like "A node of this kind requires the associated property to be a structural, collection type, but property '{0}' is not a collection." + /// + internal static string Nodes_PropertyAccessTypeMustBeCollection(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_PropertyAccessTypeMustBeCollection, p0); + } + + /// + /// A string like "Only static Entity Set reference expressions are supported currently." + /// + internal static string Nodes_NonStaticEntitySetExpressionsAreNotSupportedInThisRelease { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_NonStaticEntitySetExpressionsAreNotSupportedInThisRelease); + } + } + + /// + /// A string like "An instance of CollectionFunctionCallNode can only be created with a primitive, complex or enum collection type. For functions returning a collection of entities, use EntityCollectionFunctionCallNode instead." + /// + internal static string Nodes_CollectionFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_CollectionFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum); + } + } + + /// + /// A string like "An instance of EntityCollectionFunctionCallNode can only be created with an entity collection type. For functions returning a collection of primitive or complex values, use CollectionFunctionCallNode instead." + /// + internal static string Nodes_EntityCollectionFunctionCallNode_ItemTypeMustBeAnEntity { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_EntityCollectionFunctionCallNode_ItemTypeMustBeAnEntity); + } + } + + /// + /// A string like "An instance of SingleValueFunctionCallNode can only be created with a primitive, complex or enum type. For functions returning a single entity, use SingleEntityFunctionCallNode instead." + /// + internal static string Nodes_SingleValueFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_SingleValueFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum); + } + } + + /// + /// A string like "An instance of InNode can only be created where the item types of the right operand '{0}' and the left operand '{1}' can be compared." + /// + internal static string Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType, p0, p1); + } + + /// + /// A string like "Found a segment that isn't a path while parsing the path within a select or expand query option." + /// + internal static string ExpandTreeNormalizer_NonPathInPropertyChain { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpandTreeNormalizer_NonPathInPropertyChain); + } + } + + /// + /// A string like "Term '{0}' is not valid in a $expand expression, as only $level option is allowed when the expanded navigation property is star." + /// + internal static string UriExpandParser_TermIsNotValidForStar(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriExpandParser_TermIsNotValidForStar, p0); + } + + /// + /// A string like "Term '{0}' is not valid in a $expand expression, no option is allowed when the expanded navigation property is */$ref." + /// + internal static string UriExpandParser_TermIsNotValidForStarRef(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriExpandParser_TermIsNotValidForStarRef, p0); + } + + /// + /// A string like "Cannot get parent entity type for term '{0}' to auto populate all navigation properties." + /// + internal static string UriExpandParser_ParentEntityIsNull(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriExpandParser_ParentEntityIsNull, p0); + } + + /// + /// A string like "Term '{0}' is not valid in a $expand expression as multiple stars are not allowed." + /// + internal static string UriExpandParser_TermWithMultipleStarNotAllowed(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriExpandParser_TermWithMultipleStarNotAllowed, p0); + } + + /// + /// A string like "Term '{0}' is not valid in a $select or $expand expression." + /// + internal static string UriSelectParser_TermIsNotValid(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriSelectParser_TermIsNotValid, p0); + } + + /// + /// A string like "Top option must be a non-negative integer, it is set to '{0}' instead." + /// + internal static string UriSelectParser_InvalidTopOption(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriSelectParser_InvalidTopOption, p0); + } + + /// + /// A string like "Skip option must be a non-negative integer, it is set to '{0}' instead." + /// + internal static string UriSelectParser_InvalidSkipOption(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriSelectParser_InvalidSkipOption, p0); + } + + /// + /// A string like "Count option must be a boolean value, it is set to '{0}' instead." + /// + internal static string UriSelectParser_InvalidCountOption(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriSelectParser_InvalidCountOption, p0); + } + + /// + /// A string like "Levels option must be a non-negative integer or 'max', it is set to '{0}' instead." + /// + internal static string UriSelectParser_InvalidLevelsOption(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriSelectParser_InvalidLevelsOption, p0); + } + + /// + /// A string like "Found system token '{0}' in select or expand clause '{1}'." + /// + internal static string UriSelectParser_SystemTokenInSelectExpand(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriSelectParser_SystemTokenInSelectExpand, p0, p1); + } + + /// + /// A string like "Missing expand option on navigation property '{0}'. If a parenthesis expression follows an expanded navigation property, then at least one expand option must be provided." + /// + internal static string UriParser_MissingExpandOption(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_MissingExpandOption, p0); + } + + /// + /// A string like "Missing select option on property '{0}'. If a parenthesis expression follows a selected property, then at least one query option must be provided." + /// + internal static string UriParser_MissingSelectOption(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_MissingSelectOption, p0); + } + + /// + /// A string like "Parameter 'relativeUri' must be a relative Uri if serviceRoot is not specified." + /// + internal static string UriParser_RelativeUriMustBeRelative { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_RelativeUriMustBeRelative); + } + } + + /// + /// A string like "A service root URI must be provided to the ODataUriParser in order to use this method." + /// + internal static string UriParser_NeedServiceRootForThisOverload { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_NeedServiceRootForThisOverload); + } + } + + /// + /// A string like "The URI '{0}' must be an absolute URI." + /// + internal static string UriParser_UriMustBeAbsolute(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_UriMustBeAbsolute, p0); + } + + /// + /// A string like "The limit must be greater than or equal to zero" + /// + internal static string UriParser_NegativeLimit { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_NegativeLimit); + } + } + + /// + /// A string like "The result of parsing $expand contained at least {0} items, but the maximum allowed is {1}." + /// + internal static string UriParser_ExpandCountExceeded(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_ExpandCountExceeded, p0, p1); + } + + /// + /// A string like "The result of parsing $expand was at least {0} items deep, but the maximum allowed is {1}." + /// + internal static string UriParser_ExpandDepthExceeded(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_ExpandDepthExceeded, p0, p1); + } + + /// + /// A string like "The type '{0}' is not valid for $select or $expand, only structured types are allowed." + /// + internal static string UriParser_TypeInvalidForSelectExpand(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_TypeInvalidForSelectExpand, p0); + } + + /// + /// A string like "The handler property for context '{0}' should not return null." + /// + internal static string UriParser_ContextHandlerCanNotBeNull(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_ContextHandlerCanNotBeNull, p0); + } + + /// + /// A string like "More than one properties match the name '{0}' were found in type '{1}'." + /// + internal static string UriParserMetadata_MultipleMatchingPropertiesFound(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParserMetadata_MultipleMatchingPropertiesFound, p0, p1); + } + + /// + /// A string like "More than one navigation sources match the name '{0}' were found in model." + /// + internal static string UriParserMetadata_MultipleMatchingNavigationSourcesFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParserMetadata_MultipleMatchingNavigationSourcesFound, p0); + } + + /// + /// A string like "More than one types match the name '{0}' were found in model." + /// + internal static string UriParserMetadata_MultipleMatchingTypesFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParserMetadata_MultipleMatchingTypesFound, p0); + } + + /// + /// A string like "More than one keys match the name '{0}' were found." + /// + internal static string UriParserMetadata_MultipleMatchingKeysFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParserMetadata_MultipleMatchingKeysFound, p0); + } + + /// + /// A string like "More than one parameters match the name '{0}' were found." + /// + internal static string UriParserMetadata_MultipleMatchingParametersFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParserMetadata_MultipleMatchingParametersFound, p0); + } + + /// + /// A string like "The request URI is not valid. $ref cannot be applied to the segment '{0}' since $ref can only follow an entity segment or entity collection segment." + /// + internal static string PathParser_EntityReferenceNotSupported(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.PathParser_EntityReferenceNotSupported, p0); + } + + /// + /// A string like "$value cannot be applied to a collection." + /// + internal static string PathParser_CannotUseValueOnCollection { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.PathParser_CannotUseValueOnCollection); + } + } + + /// + /// A string like "The type '{0}' does not inherit from and is not a base type of '{1}'. The type of '{2}' must be related to the Type of the EntitySet." + /// + internal static string PathParser_TypeMustBeRelatedToSet(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.PathParser_TypeMustBeRelatedToSet, p0, p1, p2); + } + + /// + /// A string like "Type cast segment '{0}' after a collection which is not of entity or complex type is not allowed." + /// + internal static string PathParser_TypeCastOnlyAllowedAfterStructuralCollection(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.PathParser_TypeCastOnlyAllowedAfterStructuralCollection, p0); + } + + /// + /// A string like "Type cast segment '{0}' on {1} '{2}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation." + /// + internal static string PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint, p0, p1, p2); + } + + /// + /// A string like "A resource set may contain a next page link, a delta link or neither, but must not contain both." + /// + internal static string ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataResourceSet_MustNotContainBothNextPageLinkAndDeltaLink); + } + } + + /// + /// A string like "The last segment, and only the last segment, must be a navigation property in $expand." + /// + internal static string ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + } + } + + /// + /// A string like "Found a segment of type '{0} in an expand path, but only NavigationProperty, Property and Type segments are allowed." + /// + internal static string ODataExpandPath_InvalidExpandPathSegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataExpandPath_InvalidExpandPathSegment, p0); + } + + /// + /// A string like "TypeSegment cannot be the only segment in a $select." + /// + internal static string ODataSelectPath_CannotOnlyHaveTypeSegment { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataSelectPath_CannotOnlyHaveTypeSegment); + } + } + + /// + /// A string like "Found a segment of type '{0} in a select path, but only TypeSegment, NavigationPropertySegment, PropertySegment, OperationSegment or OpenPropertySegments are allowed." + /// + internal static string ODataSelectPath_InvalidSelectPathSegmentType(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataSelectPath_InvalidSelectPathSegmentType, p0); + } + + /// + /// A string like "An operation can only be the last segment in $select." + /// + internal static string ODataSelectPath_OperationSegmentCanOnlyBeLastSegment { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataSelectPath_OperationSegmentCanOnlyBeLastSegment); + } + } + + /// + /// A string like "A navigation property can only be the last segment in $select." + /// + internal static string ODataSelectPath_NavPropSegmentCanOnlyBeLastSegment { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataSelectPath_NavPropSegmentCanOnlyBeLastSegment); + } + } + + /// + /// A string like "The target Entity Set of Navigation Property '{0}' could not be found. This is most likely an error in the IEdmModel." + /// + internal static string RequestUriProcessor_TargetEntitySetNotFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_TargetEntitySetNotFound, p0); + } + + /// + /// A string like "The function overloads matching '{0}' are invalid. This is most likely an error in the IEdmModel." + /// + internal static string RequestUriProcessor_FoundInvalidFunctionImport(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_FoundInvalidFunctionImport, p0); + } + + /// + /// A string like "No type could be computed for this Segment since there were multiple possible operations with varying return types." + /// + internal static string OperationSegment_ReturnTypeForMultipleOverloads { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.OperationSegment_ReturnTypeForMultipleOverloads); + } + } + + /// + /// A string like "The return type from the operation is not possible with the given entity set." + /// + internal static string OperationSegment_CannotReturnNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.OperationSegment_CannotReturnNull); + } + } + + /// + /// A string like "Unable to resolve function overloads to a single function. There was more than one function in the model with name '{0}' and parameter names '{1}'." + /// + internal static string FunctionOverloadResolver_NoSingleMatchFound(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionOverloadResolver_NoSingleMatchFound, p0, p1); + } + + /// + /// A string like "Multiple action overloads were found with the same binding parameter for '{0}'." + /// + internal static string FunctionOverloadResolver_MultipleActionOverloads(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionOverloadResolver_MultipleActionOverloads, p0); + } + + /// + /// A string like "Multiple action import overloads were found with the same binding parameter for '{0}'." + /// + internal static string FunctionOverloadResolver_MultipleActionImportOverloads(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionOverloadResolver_MultipleActionImportOverloads, p0); + } + + /// + /// A string like "Multiple action import and function import overloads for '{0}' were found." + /// + internal static string FunctionOverloadResolver_MultipleOperationImportOverloads(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionOverloadResolver_MultipleOperationImportOverloads, p0); + } + + /// + /// A string like "Multiple action and function overloads for '{0}' were found." + /// + internal static string FunctionOverloadResolver_MultipleOperationOverloads(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionOverloadResolver_MultipleOperationOverloads, p0); + } + + /// + /// A string like "The operation overloads matching '{0}' are invalid. This is most likely an error in the IEdmModel." + /// + internal static string FunctionOverloadResolver_FoundInvalidOperation(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionOverloadResolver_FoundInvalidOperation, p0); + } + + /// + /// A string like "The operation import overloads matching '{0}' are invalid. This is most likely an error in the IEdmModel." + /// + internal static string FunctionOverloadResolver_FoundInvalidOperationImport(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.FunctionOverloadResolver_FoundInvalidOperationImport, p0); + } + + /// + /// A string like "The given custom function '{0}' already exists as a Built-In function. Consider use 'addAsOverloadToBuiltInFunction = true' parameter." + /// + internal static string CustomUriFunctions_AddCustomUriFunction_BuiltInExistsNotAddingAsOverload(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CustomUriFunctions_AddCustomUriFunction_BuiltInExistsNotAddingAsOverload, p0); + } + + /// + /// A string like "The given custom function '{0}' already exists as a Built-In function in one of it's overloads. Thus cannot override the Built-In function." + /// + internal static string CustomUriFunctions_AddCustomUriFunction_BuiltInExistsFullSignature(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CustomUriFunctions_AddCustomUriFunction_BuiltInExistsFullSignature, p0); + } + + /// + /// A string like "The given function name '{0}' already exists as a custom function with the same overload." + /// + internal static string CustomUriFunctions_AddCustomUriFunction_CustomFunctionOverloadExists(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CustomUriFunctions_AddCustomUriFunction_CustomFunctionOverloadExists, p0); + } + + /// + /// A string like "The ODataPathSegment provided (Id = {0}) is not an EntitySetSegment." + /// + internal static string RequestUriProcessor_InvalidValueForEntitySegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_InvalidValueForEntitySegment, p0); + } + + /// + /// A string like "The KeySegment provided (Id = {0}) is either null, having no keys, or does not target a single resource." + /// + internal static string RequestUriProcessor_InvalidValueForKeySegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_InvalidValueForKeySegment, p0); + } + + /// + /// A string like "$filter path segment cannot be applied on single entities or singletons. Entity type: '{0}'." + /// + internal static string RequestUriProcessor_CannotApplyFilterOnSingleEntities(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_CannotApplyFilterOnSingleEntities, p0); + } + + /// + /// A string like "$each set-based operation cannot be applied on single entities or singletons. Entity type: '{0}'." + /// + internal static string RequestUriProcessor_CannotApplyEachOnSingleEntities(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_CannotApplyEachOnSingleEntities, p0); + } + + /// + /// A string like "The $filter path segment must be in the form $filter(expression), where the expression resolves to a boolean." + /// + internal static string RequestUriProcessor_FilterPathSegmentSyntaxError { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_FilterPathSegmentSyntaxError); + } + } + + /// + /// A string like "There are no navigation sources found to apply '{0}'." + /// + internal static string RequestUriProcessor_NoNavigationSourceFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_NoNavigationSourceFound, p0); + } + + /// + /// A string like "Only a single operation can follow $each." + /// + internal static string RequestUriProcessor_OnlySingleOperationCanFollowEachPathSegment { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_OnlySingleOperationCanFollowEachPathSegment); + } + } + + /// + /// A string like "Empty segment encountered in request URL. Please make sure that a valid request URL is specified." + /// + internal static string RequestUriProcessor_EmptySegmentInRequestUrl { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_EmptySegmentInRequestUrl); + } + } + + /// + /// A string like "Bad Request - Error in query syntax." + /// + internal static string RequestUriProcessor_SyntaxError { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_SyntaxError); + } + } + + /// + /// A string like "The request URI is not valid, the segment $count cannot be applied to the root of the service." + /// + internal static string RequestUriProcessor_CountOnRoot { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_CountOnRoot); + } + } + + /// + /// A string like "The request URI is not valid, the segment $filter cannot be applied to the root of the service." + /// + internal static string RequestUriProcessor_FilterOnRoot { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_FilterOnRoot); + } + } + + /// + /// A string like "The request URI is not valid, the segment $each cannot be applied to the root of the service." + /// + internal static string RequestUriProcessor_EachOnRoot { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_EachOnRoot); + } + } + + /// + /// A string like "The request URI is not valid, the segment $ref cannot be applied to the root of the service." + /// + internal static string RequestUriProcessor_RefOnRoot { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_RefOnRoot); + } + } + + /// + /// A string like "The request URI is not valid. The segment '{0}' must be the last segment in the URI because it is one of the following: $ref, $batch, $count, $value, $metadata, a named media resource, an action, a noncomposable function, an action import, a noncomposable function import, an operation with void return type, or an operation import with void return type." + /// + internal static string RequestUriProcessor_MustBeLeafSegment(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_MustBeLeafSegment, p0); + } + + /// + /// A string like "The request URI is not valid. The segment '{0}' must refer to a navigation property since the previous segment identifier is '{1}'." + /// + internal static string RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_LinkSegmentMustBeFollowedByEntitySegment, p0, p1); + } + + /// + /// A string like "The request URI is not valid. There must a segment specified after the '{0}' segment and the segment must refer to a entity resource." + /// + internal static string RequestUriProcessor_MissingSegmentAfterLink(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_MissingSegmentAfterLink, p0); + } + + /// + /// A string like "The request URI is not valid. $count cannot be applied to the segment '{0}' since $count can only follow an entity set, a collection navigation property, a structural property of collection type, an operation returning collection type or an operation import returning collection type." + /// + internal static string RequestUriProcessor_CountNotSupported(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_CountNotSupported, p0); + } + + /// + /// A string like "The request URI is not valid. Since the segment '{0}' refers to a collection, this must be the last segment in the request URI or it must be followed by an function or action that can be bound to it otherwise all intermediate segments must refer to a single resource." + /// + internal static string RequestUriProcessor_CannotQueryCollections(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_CannotQueryCollections, p0); + } + + /// + /// A string like "The request URI is not valid. The segment '{0}' cannot include key predicates, however it may end with empty parenthesis." + /// + internal static string RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates, p0); + } + + /// + /// A string like "The segment '{1}' in the request URI is not valid. The segment '{0}' refers to a primitive property, function, or service operation, so the only supported value from the next segment is '$value'." + /// + internal static string RequestUriProcessor_ValueSegmentAfterScalarPropertySegment(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_ValueSegmentAfterScalarPropertySegment, p0, p1); + } + + /// + /// A string like "The type '{0}' specified in the URI is neither a base type nor a sub-type of the previously-specified type '{1}'." + /// + internal static string RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType, p0, p1); + } + + /// + /// A string like "Open navigation properties are not supported on OpenTypes. Property name: '{0}'." + /// + internal static string OpenNavigationPropertiesNotSupportedOnOpenTypes(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.OpenNavigationPropertiesNotSupportedOnOpenTypes, p0); + } + + /// + /// A string like "Error processing request stream. In batch mode, a resource can be cross-referenced only for bind/unbind operations." + /// + internal static string BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation); + } + } + + /// + /// A string like "The response requires that version {0} of the protocol be used, but the MaxProtocolVersion of the data service is set to {1}." + /// + internal static string DataServiceConfiguration_ResponseVersionIsBiggerThanProtocolVersion(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.DataServiceConfiguration_ResponseVersionIsBiggerThanProtocolVersion, p0, p1); + } + + /// + /// A string like "The number of keys specified in the URI does not match number of key properties for the resource '{0}'." + /// + internal static string BadRequest_KeyCountMismatch(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.BadRequest_KeyCountMismatch, p0); + } + + /// + /// A string like "Segments with multiple key values must specify them in 'name=value' form." + /// + internal static string RequestUriProcessor_KeysMustBeNamed { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_KeysMustBeNamed); + } + } + + /// + /// A string like "Resource not found for the segment '{0}'." + /// + internal static string RequestUriProcessor_ResourceNotFound(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_ResourceNotFound, p0); + } + + /// + /// A string like "Batched service action '{0}' cannot be invoked because it was bound to an entity created in the same changeset." + /// + internal static string RequestUriProcessor_BatchedActionOnEntityCreatedInSameChangeset(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_BatchedActionOnEntityCreatedInSameChangeset, p0); + } + + /// + /// A string like "Forbidden" + /// + internal static string RequestUriProcessor_Forbidden { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_Forbidden); + } + } + + /// + /// A string like "Found an operation bound to a non-entity type." + /// + internal static string RequestUriProcessor_OperationSegmentBoundToANonEntityType { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_OperationSegmentBoundToANonEntityType); + } + } + + /// + /// A string like "The request URI is not valid. The bound function binding to '{0}' does not support the escape function annotation." + /// + internal static string RequestUriProcessor_NoBoundEscapeFunctionSupported(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_NoBoundEscapeFunctionSupported, p0); + } + + /// + /// A string like "The UrlEscape function '{0}' must have exactly one non-binding parameter of type 'Edm.String'." + /// + internal static string RequestUriProcessor_EscapeFunctionMustHaveOneStringParameter(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.RequestUriProcessor_EscapeFunctionMustHaveOneStringParameter, p0); + } + + /// + /// A string like "An internal error '{0}' occurred." + /// + internal static string General_InternalError(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.General_InternalError, p0); + } + + /// + /// A string like "A non-negative integer value was expected, but the value '{0}' is not a valid non-negative integer." + /// + internal static string ExceptionUtils_CheckIntegerNotNegative(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExceptionUtils_CheckIntegerNotNegative, p0); + } + + /// + /// A string like "A positive integer value was expected, but the value '{0}' is not a valid positive integer." + /// + internal static string ExceptionUtils_CheckIntegerPositive(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExceptionUtils_CheckIntegerPositive, p0); + } + + /// + /// A string like "A positive long value was expected; however, the value '{0}' is not a valid positive long value." + /// + internal static string ExceptionUtils_CheckLongPositive(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExceptionUtils_CheckLongPositive, p0); + } + + /// + /// A string like "Value cannot be null or empty." + /// + internal static string ExceptionUtils_ArgumentStringNullOrEmpty { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExceptionUtils_ArgumentStringNullOrEmpty); + } + } + + /// + /// A string like "Only $ref is allowed with star in $expand option." + /// + internal static string ExpressionToken_OnlyRefAllowWithStarInExpand { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionToken_OnlyRefAllowWithStarInExpand); + } + } + + /// + /// A string like "No property is allowed after $ref segment." + /// + internal static string ExpressionToken_NoPropAllowedAfterRef { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionToken_NoPropAllowedAfterRef); + } + } + + /// + /// A string like "No segment is allowed before star in $expand." + /// + internal static string ExpressionToken_NoSegmentAllowedBeforeStarInExpand { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionToken_NoSegmentAllowedBeforeStarInExpand); + } + } + + /// + /// A string like "An identifier was expected at position {0}." + /// + internal static string ExpressionToken_IdentifierExpected(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionToken_IdentifierExpected, p0); + } + + /// + /// A string like "There is an unterminated string literal at position {0} in '{1}'." + /// + internal static string ExpressionLexer_UnterminatedStringLiteral(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_UnterminatedStringLiteral, p0, p1); + } + + /// + /// A string like "Syntax error: character '{0}' is not valid at position {1} in '{2}'." + /// + internal static string ExpressionLexer_InvalidCharacter(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_InvalidCharacter, p0, p1, p2); + } + + /// + /// A string like "Syntax error at position {0} in '{1}'." + /// + internal static string ExpressionLexer_SyntaxError(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_SyntaxError, p0, p1); + } + + /// + /// A string like "There is an unterminated literal at position {0} in '{1}'." + /// + internal static string ExpressionLexer_UnterminatedLiteral(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_UnterminatedLiteral, p0, p1); + } + + /// + /// A string like "A digit was expected at position {0} in '{1}'." + /// + internal static string ExpressionLexer_DigitExpected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_DigitExpected, p0, p1); + } + + /// + /// A string like "Found an unbalanced bracket expression." + /// + internal static string ExpressionLexer_UnbalancedBracketExpression { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_UnbalancedBracketExpression); + } + } + + /// + /// A string like "Numeric string '{0}' is not a valid Int32/Int64/Double/Decimal." + /// + internal static string ExpressionLexer_InvalidNumericString(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_InvalidNumericString, p0); + } + + /// + /// A string like "An unrecognized escape sequence '\\{0}' was found at position {1} in '{2}'." + /// + internal static string ExpressionLexer_InvalidEscapeSequence(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ExpressionLexer_InvalidEscapeSequence, p0, p1, p2); + } + + /// + /// A string like "Unrecognized '{0}' literal '{1}' at '{2}' in '{3}'." + /// + internal static string UriQueryExpressionParser_UnrecognizedLiteral(object p0, object p1, object p2, object p3) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_UnrecognizedLiteral, p0, p1, p2, p3); + } + + /// + /// A string like "Unrecognized '{0}' literal '{1}' at '{2}' in '{3}' with reason '{4}'." + /// + internal static string UriQueryExpressionParser_UnrecognizedLiteralWithReason(object p0, object p1, object p2, object p3, object p4) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriQueryExpressionParser_UnrecognizedLiteralWithReason, p0, p1, p2, p3, p4); + } + + /// + /// A string like "Failed to parse '{0}' of Edm type '{1}' to primitive type." + /// + internal static string UriPrimitiveTypeParsers_FailedToParseTextToPrimitiveValue(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriPrimitiveTypeParsers_FailedToParseTextToPrimitiveValue, p0, p1); + } + + /// + /// A string like "Failed to parse string to Geography." + /// + internal static string UriPrimitiveTypeParsers_FailedToParseStringToGeography { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriPrimitiveTypeParsers_FailedToParseStringToGeography); + } + } + + /// + /// A string like "The given uri custom type parser already exists." + /// + internal static string UriCustomTypeParsers_AddCustomUriTypeParserAlreadyExists { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriCustomTypeParsers_AddCustomUriTypeParserAlreadyExists); + } + } + + /// + /// A string like "An existing custom UriTypeParser is already registered to the given EdmTypeReference '{0}'." + /// + internal static string UriCustomTypeParsers_AddCustomUriTypeParserEdmTypeExists(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriCustomTypeParsers_AddCustomUriTypeParserEdmTypeExists, p0); + } + + /// + /// A string like "The given type prefix literal name '{0}' must contain letters or '.' only." + /// + internal static string UriParserHelper_InvalidPrefixLiteral(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParserHelper_InvalidPrefixLiteral, p0); + } + + /// + /// A string like "The given type literal prefix '{0}' already exists as a custom uri type literal prefix." + /// + internal static string CustomUriTypePrefixLiterals_AddCustomUriTypePrefixLiteralAlreadyExists(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.CustomUriTypePrefixLiterals_AddCustomUriTypePrefixLiteralAlreadyExists, p0); + } + + /// + /// A string like "The value '{0}' is not a valid duration value." + /// + internal static string ValueParser_InvalidDuration(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ValueParser_InvalidDuration, p0); + } + + /// + /// A string like "The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information." + /// + internal static string PlatformHelper_DateTimeOffsetMustContainTimeZone(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.PlatformHelper_DateTimeOffsetMustContainTimeZone, p0); + } + + /// + /// A string like "Invalid JSON. An unexpected comma was found in scope '{0}'. A comma is only valid between properties of an object or between elements of an array." + /// + internal static string JsonReader_UnexpectedComma(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_UnexpectedComma, p0); + } + + /// + /// A string like "Invalid JSON. A array closure mismatch occurred. A '{0}' was expected to match '{1}', but instead '{2}' was found." + /// + internal static string JsonReader_ArrayClosureMismatch(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_ArrayClosureMismatch, p0, p1, p2); + } + + /// + /// A string like "Invalid JSON. More than one value was found at the root of the JSON content. JSON content can only have one value at the root level, which is an array, an object or a primitive value." + /// + internal static string JsonReader_MultipleTopLevelValues { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_MultipleTopLevelValues); + } + } + + /// + /// A string like "Invalid JSON. Unexpected end of input was found in JSON content. Not all object and array scopes were closed." + /// + internal static string JsonReader_EndOfInputWithOpenScope { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_EndOfInputWithOpenScope); + } + } + + /// + /// A string like "Invalid JSON. Unexpected token '{0}'." + /// + internal static string JsonReader_UnexpectedToken(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_UnexpectedToken, p0); + } + + /// + /// A string like "Invalid JSON. A token was not recognized in the JSON content." + /// + internal static string JsonReader_UnrecognizedToken { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_UnrecognizedToken); + } + } + + /// + /// A string like "Invalid JSON. A colon character ':' is expected after the property name '{0}', but none was found." + /// + internal static string JsonReader_MissingColon(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_MissingColon, p0); + } + + /// + /// A string like "Invalid JSON. An unrecognized escape sequence '{0}' was found in a JSON string value." + /// + internal static string JsonReader_UnrecognizedEscapeSequence(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_UnrecognizedEscapeSequence, p0); + } + + /// + /// A string like "Invalid JSON. Unexpected end of input reached while processing a JSON string value." + /// + internal static string JsonReader_UnexpectedEndOfString { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_UnexpectedEndOfString); + } + } + + /// + /// A string like "Invalid JSON. The value '{0}' is not a valid number." + /// + internal static string JsonReader_InvalidNumberFormat(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_InvalidNumberFormat, p0); + } + + /// + /// A string like "Invalid Binary value. The value '{0}' is not a valid Base64 encoded value." + /// + internal static string JsonReader_InvalidBinaryFormat(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_InvalidBinaryFormat, p0); + } + + /// + /// A string like "Invalid JSON. A comma character ',' was expected in scope '{0}'. Every two elements in an array and properties of an object must be separated by commas." + /// + internal static string JsonReader_MissingComma(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_MissingComma, p0); + } + + /// + /// A string like "Invalid JSON. The property name '{0}' is not valid. The name of a property cannot be empty." + /// + internal static string JsonReader_InvalidPropertyNameOrUnexpectedComma(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_InvalidPropertyNameOrUnexpectedComma, p0); + } + + /// + /// A string like "Cannot increase the JSON reader buffer to hold the input JSON which has very long token." + /// + internal static string JsonReader_MaxBufferReached { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_MaxBufferReached); + } + } + + /// + /// A string like "Cannot access the Value property while streaming a value. Please dispose the StreamReader or TextReader before continuing." + /// + internal static string JsonReader_CannotAccessValueInStreamState { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_CannotAccessValueInStreamState); + } + } + + /// + /// A string like "Cannot call Read while streaming a value. Please dispose the StreamReader or TextReader before continuing." + /// + internal static string JsonReader_CannotCallReadInStreamState { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_CannotCallReadInStreamState); + } + } + + /// + /// A string like "Cannot create a Stream in the current state. A Stream can only be created for reading a JSON string value when positioned on, and before accessing, the value." + /// + internal static string JsonReader_CannotCreateReadStream { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_CannotCreateReadStream); + } + } + + /// + /// A string like "Cannot create a TextReader in the current state. A TextReader can only be created for reading a JSON string value when positioned on, and before accessing, the value." + /// + internal static string JsonReader_CannotCreateTextReader { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReader_CannotCreateTextReader); + } + } + + /// + /// A string like "An unexpected '{1}' node was found when reading from the JSON reader. A '{0}' node was expected." + /// + internal static string JsonReaderExtensions_UnexpectedNodeDetected(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReaderExtensions_UnexpectedNodeDetected, p0, p1); + } + + /// + /// A string like "An unexpected '{1}' node was found for property named '{2}' when reading from the JSON reader. A '{0}' node was expected." + /// + internal static string JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName(object p0, object p1, object p2) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReaderExtensions_UnexpectedNodeDetectedWithPropertyName, p0, p1, p2); + } + + /// + /// A string like "Cannot read the value '{0}' for the property '{1}' as a quoted JSON string value." + /// + internal static string JsonReaderExtensions_CannotReadPropertyValueAsString(object p0, object p1) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReaderExtensions_CannotReadPropertyValueAsString, p0, p1); + } + + /// + /// A string like "Cannot read the value '{0}' as a quoted JSON string value." + /// + internal static string JsonReaderExtensions_CannotReadValueAsString(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReaderExtensions_CannotReadValueAsString, p0); + } + + /// + /// A string like "Cannot read the value '{0}' as a double numeric value." + /// + internal static string JsonReaderExtensions_CannotReadValueAsDouble(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReaderExtensions_CannotReadValueAsDouble, p0); + } + + /// + /// A string like "An unexpected instance annotation name '{0}' was found when reading from the JSON reader, In OData, Instance annotation name must start with @." + /// + internal static string JsonReaderExtensions_UnexpectedInstanceAnnotationName(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.JsonReaderExtensions_UnexpectedInstanceAnnotationName, p0); + } + + /// + /// A string like "The buffer from pool cannot be null or less than the required minimal size '{0}'." + /// + internal static string BufferUtils_InvalidBufferOrSize(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.BufferUtils_InvalidBufferOrSize, p0); + } + + /// + /// A string like "No service for type '{0}' has been registered." + /// + internal static string ServiceProviderExtensions_NoServiceRegistered(object p0) { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ServiceProviderExtensions_NoServiceRegistered, p0); + } + + } + + /// + /// Strongly-typed and parameterized exception factory. + /// + internal static partial class Error { + + /// + /// The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument. + /// + internal static Exception ArgumentNull(string paramName) { + return new ArgumentNullException(paramName); + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method. + /// + internal static Exception ArgumentOutOfRange(string paramName) { + return new ArgumentOutOfRangeException(paramName); + } + + /// + /// The exception that is thrown when the author has not yet implemented the logic at this point in the program. This can act as an exception based TODO tag. + /// + internal static Exception NotImplemented() { + return new NotImplementedException(); + } + + /// + /// The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality. + /// + internal static Exception NotSupported() { + return new NotSupportedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.tt new file mode 100644 index 0000000..49c2944 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.tt @@ -0,0 +1,19 @@ +<#@ include file="..\..\tools\StringResourceGenerator\StringsClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Core"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.OData.Core.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/PrimitiveConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PrimitiveConverter.cs new file mode 100644 index 0000000..0d12fcd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PrimitiveConverter.cs @@ -0,0 +1,129 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Xml; + using Microsoft.OData.Json; + using Microsoft.Spatial; + #endregion + + /// + /// Handles serialization and deserialization for a specified set of primitive types. + /// + internal sealed class PrimitiveConverter + { + /// Instance of GeographyTypeConverter to register for all Geography types. + private static readonly IPrimitiveTypeConverter geographyTypeConverter = new GeographyTypeConverter(); + + /// Instance of GeographyTypeConverter to register for all Geography types. + private static readonly IPrimitiveTypeConverter geometryTypeConverter = new GeometryTypeConverter(); + + /// Set of type converters that implement their own conversion using IPrimitiveTypeConverter. + private static readonly PrimitiveConverter primitiveConverter = + new PrimitiveConverter( + new KeyValuePair[] + { + new KeyValuePair(typeof(GeographyPoint), geographyTypeConverter), + new KeyValuePair(typeof(GeographyLineString), geographyTypeConverter), + new KeyValuePair(typeof(GeographyPolygon), geographyTypeConverter), + new KeyValuePair(typeof(GeographyCollection), geographyTypeConverter), + new KeyValuePair(typeof(GeographyMultiPoint), geographyTypeConverter), + new KeyValuePair(typeof(GeographyMultiLineString), geographyTypeConverter), + new KeyValuePair(typeof(GeographyMultiPolygon), geographyTypeConverter), + new KeyValuePair(typeof(Geography), geographyTypeConverter), + + new KeyValuePair(typeof(GeometryPoint), geometryTypeConverter), + new KeyValuePair(typeof(GeometryLineString), geometryTypeConverter), + new KeyValuePair(typeof(GeometryPolygon), geometryTypeConverter), + new KeyValuePair(typeof(GeometryCollection), geometryTypeConverter), + new KeyValuePair(typeof(GeometryMultiPoint), geometryTypeConverter), + new KeyValuePair(typeof(GeometryMultiLineString), geometryTypeConverter), + new KeyValuePair(typeof(GeometryMultiPolygon), geometryTypeConverter), + new KeyValuePair(typeof(Geometry), geometryTypeConverter), + }); + + /// Set of type converters that are known to this instance which convert values based on the ISpatial type. + private readonly Dictionary spatialPrimitiveTypeConverters; + + /// + /// Create a new instance of the converter. + /// + /// Set of type converters to register for the ISpatial based values. + internal PrimitiveConverter(KeyValuePair[] spatialPrimitiveTypeConverters) + { + Debug.Assert(spatialPrimitiveTypeConverters != null && spatialPrimitiveTypeConverters.Length != 0, "PrimitiveConverter requires a non-null and non-empty array of type converters for ISpatial."); + this.spatialPrimitiveTypeConverters = new Dictionary(EqualityComparer.Default); + foreach (KeyValuePair spatialPrimitiveTypeConverter in spatialPrimitiveTypeConverters) + { + this.spatialPrimitiveTypeConverters.Add(spatialPrimitiveTypeConverter.Key, spatialPrimitiveTypeConverter.Value); + } + } + + /// PrimitiveConverter instance for use by the Atom and Json readers and writers. + internal static PrimitiveConverter Instance + { + get + { + return primitiveConverter; + } + } + + /// + /// Try to write the JSON Lite representation of using a registered primitive type converter + /// + /// Object to convert to JSON representation. + /// JsonWriter instance to write to. + internal void WriteJsonLight(object instance, IJsonWriter jsonWriter) + { + Debug.Assert(instance != null, "Expected a non-null instance to write."); + + Type instanceType = instance.GetType(); + + IPrimitiveTypeConverter primitiveTypeConverter; + this.TryGetConverter(instanceType, out primitiveTypeConverter); + Debug.Assert(primitiveTypeConverter != null, "primitiveTypeConverter != null"); + primitiveTypeConverter.WriteJsonLight(instance, jsonWriter); + } + + /// + /// Get the primitive type converter for the given type. + /// + /// Clr type whose primitive type converter needs to be returned. + /// Converter for the given clr type. + /// True if a converter was found for the given type, otherwise returns false. + private bool TryGetConverter(Type type, out IPrimitiveTypeConverter primitiveTypeConverter) + { + if (typeof(ISpatial).IsAssignableFrom(type)) + { + KeyValuePair bestMatch = new KeyValuePair(typeof(object), null); + foreach (KeyValuePair possibleMatch in this.spatialPrimitiveTypeConverters) + { + // If the current primitive type is assignable from the given parameter type and + // is a more derived type from the previous match, then the current type is a better match. + if (possibleMatch.Key.IsAssignableFrom(type) && bestMatch.Key.IsAssignableFrom(possibleMatch.Key)) + { + bestMatch = possibleMatch; + } + } + + primitiveTypeConverter = bestMatch.Value; + return bestMatch.Value != null; + } + else + { + primitiveTypeConverter = null; + return false; + } + } + } +} + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyAndAnnotationCollector.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyAndAnnotationCollector.cs new file mode 100644 index 0000000..d2c47e7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyAndAnnotationCollector.cs @@ -0,0 +1,576 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + using Microsoft.OData.Json; + using Microsoft.OData.JsonLight; + #endregion Namespaces + + using Annotation = System.Collections.Generic.KeyValuePair; + + /// + /// This class has the following responsibilities: + /// 1) Validates that no duplicate OData scope/property annotations exist. + /// Duplicate custom scope/property annotations are allowed. + /// 2) Collects OData and custom scope/property annotations. + /// 3) Validates that no duplicate properties exist. + /// 4) Validates that property annotations come in group and immediately precede the annotated property. + /// + /// + /// Scope annotations are those that do not apply to specific properties, and start directly with "@". + /// + internal sealed class PropertyAndAnnotationCollector + { + private static readonly IDictionary emptyDictionary = new Dictionary(); + + /// + /// Whether to enable duplicate property validation so that an exception is thrown when detected. + /// + /// + /// If disabled and duplicate properties exist, the behavior is unspecified. + /// + private readonly bool throwOnDuplicateProperty; + + /// + /// Caches OData scope annotations. + /// + private IDictionary odataScopeAnnotations = new Dictionary(); + + /// + /// Caches custom scope annotations. + /// + private IList customScopeAnnotations = new List(); + + /// + /// Caches property data. + /// + private IDictionary propertyData = new Dictionary(); + + /// + /// Creates a PropertyAndAnnotationCollector instance. + /// + /// Whether to enable duplicate property validation. + internal PropertyAndAnnotationCollector(bool throwOnDuplicateProperty) + { + this.throwOnDuplicateProperty = throwOnDuplicateProperty; + } + + /// + /// Processing state of a property. + /// + /// + /// Models a state machine. + /// + private enum PropertyState + { + /// + /// Initial state or when property annotations have been processed. + /// + AnnotationSeen, + + /// + /// Non-nested non-navigation property value has been processed. + /// + SimpleProperty, + + /// + /// 1) Nested property value has been processed. + /// 2) Navigation property value has been processed. + /// 3) Association link has been processed. + /// + NavigationProperty + } + + /// + /// Validates that no duplicate property exists. + /// + /// The property to be validated. + internal void CheckForDuplicatePropertyNames(ODataProperty property) + { + Debug.Assert(property != null); + + if (!throwOnDuplicateProperty) + { + return; + } + + string propertyName = property.Name; + PropertyData data; + if (!propertyData.TryGetValue(propertyName, out data)) + { + propertyData[propertyName] = new PropertyData(PropertyState.SimpleProperty); + } + else if (data.State == PropertyState.AnnotationSeen) + { + data.State = PropertyState.SimpleProperty; + } + else + { + throw new ODataException( + Strings.DuplicatePropertyNamesNotAllowed( + propertyName)); + } + } + + /// + /// Validates that no duplicate property exists when the nested resource info has started, + /// but we don't know yet if it's expanded or not. + /// + /// The nested resource info to be validated. + internal void ValidatePropertyUniquenessOnNestedResourceInfoStart(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null); + + if (!throwOnDuplicateProperty) + { + return; + } + + // Dry run, no write. + string propertyName = nestedResourceInfo.Name; + PropertyData data; + if (propertyData.TryGetValue(propertyName, out data)) + { + CheckNestedResourceInfoDuplicateNameForExistingDuplicationRecord(propertyName, data); + } + } + + /// + /// Validates that no duplicate property exists and gets the corresponding association link if available. + /// + /// The nested resource info to be checked. + /// Corresponding association link if available. + internal Uri ValidatePropertyUniquenessAndGetAssociationLink(ODataNestedResourceInfo nestedResourceInfo) + { + string propertyName = nestedResourceInfo.Name; + PropertyData data; + if (!propertyData.TryGetValue(propertyName, out data)) + { + propertyData[propertyName] = new PropertyData(PropertyState.NavigationProperty) + { + NestedResourceInfo = nestedResourceInfo + }; + return null; + } + else + { + if (throwOnDuplicateProperty) + { + CheckNestedResourceInfoDuplicateNameForExistingDuplicationRecord(propertyName, data); + } + + data.State = PropertyState.NavigationProperty; + data.NestedResourceInfo = nestedResourceInfo; + return data.AssociationLinkUrl; + } + } + + /// + /// Validates that no duplicate "odata.assocationLink" annotation exists and gets the corresponding + /// ODataNestedResourceInfo of the annotated property if available. + /// + /// Name of the annotated property. + /// Annotation value. + /// Corresponding ODataNestedResourceInfo of the annotated property if available. + internal ODataNestedResourceInfo ValidatePropertyOpenForAssociationLinkAndGetNestedResourceInfo(string propertyName, Uri associationLinkUrl) + { + Debug.Assert(propertyName != null); + + PropertyData data; + if (!propertyData.TryGetValue(propertyName, out data)) + { + propertyData[propertyName] = new PropertyData(PropertyState.NavigationProperty) + { + AssociationLinkUrl = associationLinkUrl, + }; + return null; + } + else + { + if (data.State == PropertyState.AnnotationSeen + || (data.State == PropertyState.NavigationProperty + && data.AssociationLinkUrl == null)) + { + data.State = PropertyState.NavigationProperty; + data.AssociationLinkUrl = associationLinkUrl; + } + else + { + throw new ODataException( + Strings.DuplicateAnnotationNotAllowed( + "odata.associationLink")); + } + + return data.NestedResourceInfo; + } + } + + /// + /// Resets to initial state for reuse. + /// + internal void Reset() + { + propertyData.Clear(); + } + + /// + /// Adds an OData scope annotation. + /// + /// Name of the annotation. + /// Value of the annotation. + /// + /// Scope annotations are those that do not apply to specific properties, and start directly with "@". + /// + internal void AddODataScopeAnnotation(string annotationName, object annotationValue) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName)); + Debug.Assert(JsonLight.ODataJsonLightReaderUtils.IsODataAnnotationName(annotationName)); + + if (annotationValue == null) + { + return; + } + + try + { + odataScopeAnnotations.Add(annotationName, annotationValue); + } + catch (ArgumentException) + { + throw new ODataException( + Strings.DuplicateAnnotationNotAllowed( + annotationName)); + } + } + + /// + /// Adds a custom scope annotation. + /// + /// Name of the annotation. + /// Value of the annotation. + /// + /// Scope annotations are those that do not apply to specific properties, and start directly with "@". + /// + internal void AddCustomScopeAnnotation(string annotationName, object annotationValue) + { + Debug.Assert(!string.IsNullOrEmpty(annotationName)); + Debug.Assert(!JsonLight.ODataJsonLightReaderUtils.IsODataAnnotationName(annotationName)); + + if (annotationValue == null) + { + return; + } + + customScopeAnnotations.Add(new Annotation(annotationName, annotationValue)); + } + + /// + /// Gets OData scope annotations. + /// + /// OData scope annotation key value pairs. + /// + /// Scope annotations are those that do not apply to specific properties, and start directly with "@". + /// + internal IDictionary GetODataScopeAnnotation() + { + return odataScopeAnnotations; + } + + /// + /// Gets custom scope annotations. + /// + /// Custom scope annotation key value pairs. + /// + /// Scope annotations are those that do not apply to specific properties, and start directly with "@". + /// + internal IEnumerable GetCustomScopeAnnotation() + { + return customScopeAnnotations; + } + + /// + /// Adds an OData property annotation. + /// + /// Name of the property. + /// Name of the annotation. + /// Value of the annotation. + internal void AddODataPropertyAnnotation(string propertyName, string annotationName, object annotationValue) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName)); + Debug.Assert(!string.IsNullOrEmpty(annotationName)); + Debug.Assert(JsonLight.ODataJsonLightReaderUtils.IsODataAnnotationName(annotationName)); + + if (annotationValue == null) + { + return; + } + + PropertyData data; + if (!propertyData.TryGetValue(propertyName, out data)) + { + propertyData[propertyName] = data = new PropertyData(PropertyState.AnnotationSeen); + } + else + { + if (data.Processed) + { + throw new ODataException( + Strings.PropertyAnnotationAfterTheProperty( + annotationName, propertyName)); + } + } + + try + { + data.ODataAnnotations.Add(annotationName, annotationValue); + } + catch (ArgumentException) + { + throw new ODataException( + ODataJsonLightReaderUtils.IsAnnotationProperty(propertyName) + ? Strings.DuplicateAnnotationForInstanceAnnotationNotAllowed(annotationName, propertyName) + : Strings.DuplicateAnnotationForPropertyNotAllowed(annotationName, propertyName)); + } + } + + /// + /// Adds a custom property annotation. + /// + /// Name of the property. + /// Name of the annotation. + /// Value of the annotation. + internal void AddCustomPropertyAnnotation(string propertyName, string annotationName, object annotationValue) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName)); + Debug.Assert(!string.IsNullOrEmpty(annotationName)); + Debug.Assert(!JsonLight.ODataJsonLightReaderUtils.IsODataAnnotationName(annotationName)); + + if (annotationValue == null) + { + return; + } + + PropertyData data; + if (!propertyData.TryGetValue(propertyName, out data)) + { + propertyData[propertyName] = data = new PropertyData(PropertyState.AnnotationSeen); + } + else + { + if (data.Processed) + { + throw new ODataException( + Strings.PropertyAnnotationAfterTheProperty( + annotationName, propertyName)); + } + } + + data.CustomAnnotations.Add(new Annotation(annotationName, annotationValue)); + } + + /// + /// Set the derived type validator for a property. + /// + /// Name of the property. + /// Derived type validator. + internal void SetDerivedTypeValidator(string propertyName, DerivedTypeValidator validator) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName)); + + if (validator == null) + { + return; + } + + PropertyData data; + if (!propertyData.TryGetValue(propertyName, out data)) + { + propertyData[propertyName] = data = new PropertyData(PropertyState.AnnotationSeen); + } + + data.DerivedTypeValidator = validator; + } + + /// + /// Gets the derived type validator for a property. + /// + /// Name of the property. + /// Null or the derived type validator. + internal DerivedTypeValidator GetDerivedTypeValidator(string propertyName) + { + Debug.Assert(propertyName != null); + + PropertyData data; + return propertyData.TryGetValue(propertyName, out data) + ? data.DerivedTypeValidator + : null; + } + + /// + /// Returns OData annotations for a property. + /// + /// Name of the property. + /// OData property annotation name value pairs. + internal IDictionary GetODataPropertyAnnotations(string propertyName) + { + Debug.Assert(propertyName != null); + + PropertyData data; + return propertyData.TryGetValue(propertyName, out data) + ? data.ODataAnnotations + : emptyDictionary; + } + + /// + /// Returns custom annotations for a property. + /// + /// Name of the property. + /// Custom property annotation name value pairs. + internal IEnumerable GetCustomPropertyAnnotations(string propertyName) + { + Debug.Assert(propertyName != null); + + PropertyData data; + return propertyData.TryGetValue(propertyName, out data) + ? data.CustomAnnotations + : Enumerable.Empty(); + } + + /// + /// Marks a property to note that all its annotations should have been processed by now. + /// + /// Name of the property. + /// + /// It's an error if more annotations for a marked property are found later in the payload. + /// + internal void MarkPropertyAsProcessed(string propertyName) + { + Debug.Assert(propertyName != null); + + PropertyData data; + if (!propertyData.TryGetValue(propertyName, out data)) + { + propertyData[propertyName] = data = new PropertyData(PropertyState.AnnotationSeen); + } + + if (data.Processed) + { + throw new ODataException( + ODataJsonLightReaderUtils.IsAnnotationProperty(propertyName) + && !ODataJsonLightUtils.IsMetadataReferenceProperty(propertyName) + ? Strings.DuplicateAnnotationNotAllowed(propertyName) + : Strings.DuplicatePropertyNamesNotAllowed(propertyName)); + } + + data.Processed = true; + } + + /// + /// Validates that the property is open to be added more annotations. + /// + /// Name of the property. + /// Name of the annotation. + /// + /// A property is no longer open for annotations when it has been marked as processed. + /// + internal void CheckIfPropertyOpenForAnnotations(string propertyName, string annotationName) + { + PropertyData data; + if (propertyData.TryGetValue(propertyName, out data) + && data.Processed) + { + throw new ODataException( + Strings.PropertyAnnotationAfterTheProperty( + annotationName, propertyName)); + } + } + + /// + /// Validates that no duplicate property exists (dry run). + /// + /// Name of the property. + /// Corresponding property data. + private static void CheckNestedResourceInfoDuplicateNameForExistingDuplicationRecord(string propertyName, PropertyData propertyData) + { + if (propertyData.State == PropertyState.NavigationProperty + && propertyData.AssociationLinkUrl != null + && propertyData.NestedResourceInfo == null) + { + // Association link has been processed, and this is the corresponding navigation property. + } + else if (propertyData.State != PropertyState.AnnotationSeen) + { + throw new ODataException( + Strings.DuplicatePropertyNamesNotAllowed( + propertyName)); + } + } + + private sealed class PropertyData + { + /// + /// Constructor. + /// + /// Initial property state. + internal PropertyData(PropertyState propertyState) + { + State = propertyState; + ODataAnnotations = new Dictionary(); + CustomAnnotations = new List(); + } + + /// + /// Current processing state. + /// + internal PropertyState State { get; set; } + + /// + /// The nested resource info of the property if found. + /// + internal ODataNestedResourceInfo NestedResourceInfo { get; set; } + + /// + /// The association link for the property if found. + /// + internal Uri AssociationLinkUrl { get; set; } + + /// + /// OData property annotations. + /// + /// + /// The key is the fully qualified annotation name like "odata.type". + /// The value is the parsed value of the annotation, which is annotation specific. + /// + internal IDictionary ODataAnnotations { get; private set; } + + /// + /// Custom property annotations. + /// + /// + /// The key is the fully qualified annotation name. + /// The value is the parsed value of the annotation, which is annotation specific. + /// + internal IList CustomAnnotations { get; private set; } + + /// + /// Denotes whether the property has been marked as processed. + /// + internal bool Processed { get; set; } + + /// + /// The derived type validator for property. + /// + internal DerivedTypeValidator DerivedTypeValidator { get; set; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCache.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCache.cs new file mode 100644 index 0000000..997e2a1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCache.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.OData +{ + /// + /// The cache to store property info during serialization. + /// + internal class PropertyCache + { + private readonly Dictionary propertyInfoDictionary = new Dictionary(); + + public PropertySerializationInfo GetProperty(IEdmModel model, string name, string uniqueName, IEdmStructuredType owningType) + { + PropertySerializationInfo propertyInfo; + if (!propertyInfoDictionary.TryGetValue(uniqueName, out propertyInfo)) + { + propertyInfo = new PropertySerializationInfo(model, name, owningType); + propertyInfoDictionary[uniqueName] = propertyInfo; + } + + return propertyInfo; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCacheHandler.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCacheHandler.cs new file mode 100644 index 0000000..36095e2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyCacheHandler.cs @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Microsoft.OData.Edm; + +namespace Microsoft.OData +{ + /// + /// Manage PropertyCache for ODataResourceSet in serialization. + /// One ODataResourceSet has one PropertyCache. + /// ODataResourceSets with same resource type share the same PropertyCache. + /// + internal class PropertyCacheHandler + { + private const string PropertyTypeDelimiter = "-"; + + private readonly Stack cacheStack = new Stack(); + + private readonly Stack scopeLevelStack = new Stack(); + + private readonly Dictionary cacheDictionary = new Dictionary(); + + private PropertyCache currentPropertyCache; + + private int resourceSetScopeLevel; + + private int currentResourceScopeLevel; + + public PropertySerializationInfo GetProperty(IEdmModel model, string name, IEdmStructuredType owningType) + { + int depth = this.currentResourceScopeLevel - this.resourceSetScopeLevel; + + Debug.Assert(depth >= 1, "'depth' should always be greater than or equal to 1"); + + // In production, depthStr == 1 in most cases. So we optimize string allocation for this case. + string depthStr = depth == 1 ? string.Empty : depth.ToString(CultureInfo.InvariantCulture); + + string uniqueName = owningType != null + ? string.Concat(owningType.FullTypeName(), PropertyCacheHandler.PropertyTypeDelimiter, depthStr, name) + : string.Concat(depthStr, name); + + return this.currentPropertyCache.GetProperty(model, name, uniqueName, owningType); + } + + public void SetCurrentResourceScopeLevel(int level) + { + this.currentResourceScopeLevel = level; + } + + public void EnterResourceSetScope(IEdmStructuredType resourceType, int scopeLevel) + { + // Set cache for current resource set + PropertyCache propertyCache; + if (resourceType != null) + { + if (!cacheDictionary.TryGetValue(resourceType, out propertyCache)) + { + propertyCache = new PropertyCache(); + cacheDictionary[resourceType] = propertyCache; + } + } + else + { + propertyCache = new PropertyCache(); + } + + this.cacheStack.Push(this.currentPropertyCache); + this.currentPropertyCache = propertyCache; + + // Set scope level for current resource set + this.scopeLevelStack.Push(this.resourceSetScopeLevel); + this.resourceSetScopeLevel = scopeLevel; + } + + public void LeaveResourceSetScope() + { + Debug.Assert(this.cacheStack.Count != 0, "this.cacheStack.Count != 0"); + Debug.Assert(this.scopeLevelStack.Count != 0, "this.scopeLevelStack.Count != 0"); + + this.resourceSetScopeLevel = this.scopeLevelStack.Pop(); + this.currentPropertyCache = this.cacheStack.Pop(); + } + + public bool InResourceSetScope() + { + return this.resourceSetScopeLevel > 0; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyMetadataTypeInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyMetadataTypeInfo.cs new file mode 100644 index 0000000..a4bc811 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyMetadataTypeInfo.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.OData +{ + /// + /// The class to present a property type info which is resolved from metadata. + /// + internal class PropertyMetadataTypeInfo + { + private bool derivedTypeConstraintsLoaded; + private IEnumerable derivedTypeConstraints; + private IEdmModel model; + + public PropertyMetadataTypeInfo(IEdmModel model, string name, IEdmStructuredType owningType) + { + this.PropertyName = name; + this.OwningType = owningType; + this.EdmProperty = owningType == null ? null : owningType.FindProperty(name); + this.IsUndeclaredProperty = this.EdmProperty == null; + this.IsOpenProperty = (owningType != null && owningType.IsOpen && this.IsUndeclaredProperty); + this.TypeReference = this.IsUndeclaredProperty ? null : this.EdmProperty.Type; + this.FullName = this.TypeReference == null ? null : this.TypeReference.Definition.AsActualType().FullTypeName(); + + this.model = model; + derivedTypeConstraintsLoaded = false; + } + + public string PropertyName { get; private set; } + + public IEdmProperty EdmProperty { get; private set; } + + public bool IsOpenProperty { get; private set; } + + public bool IsUndeclaredProperty { get; private set; } + + public IEdmStructuredType OwningType { get; private set; } + + public IEdmTypeReference TypeReference { get; private set; } + + public string FullName { get; private set; } + + public IEnumerable DerivedTypeConstraints + { + get + { + if (!derivedTypeConstraintsLoaded) + { + this.derivedTypeConstraints = EdmProperty == null ? null : this.model.GetDerivedTypeConstraints(EdmProperty); + derivedTypeConstraintsLoaded = true; + } + + return this.derivedTypeConstraints; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertySerializationInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertySerializationInfo.cs new file mode 100644 index 0000000..a135afa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertySerializationInfo.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; +using Microsoft.OData.JsonLight; + +namespace Microsoft.OData +{ + /// + /// The class to hold all the info needed for a property in serialization. + /// + internal class PropertySerializationInfo + { + private bool isTopLevel; + + public PropertySerializationInfo(IEdmModel model, string name, IEdmStructuredType owningType) + { + this.PropertyName = name; + this.IsTopLevel = false; + this.MetadataType = new PropertyMetadataTypeInfo(model, name, owningType); + } + + /// Name of current property. + public string PropertyName { get; private set; } + + /// + /// The type info resolved from property value. + /// The value type info might change for a property in different ODataResource of an ODataResourceSet. + /// + public PropertyValueTypeInfo ValueType { get; set; } + + /// The type info resolved from metadata. + public PropertyMetadataTypeInfo MetadataType { get; private set; } + + /// + /// The value of '@odata.type' for current property, if the value is null, no type annotation needed. + /// + public string TypeNameToWrite { get; set; } + + /// The property name written in the wire. + public string WireName { get; private set; } + + /// Whether the property is top level. + public bool IsTopLevel + { + get + { + return isTopLevel; + } + + set + { + isTopLevel = value; + this.WireName = isTopLevel ? JsonLightConstants.ODataValuePropertyName : this.PropertyName; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyValueTypeInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyValueTypeInfo.cs new file mode 100644 index 0000000..2e08c56 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/PropertyValueTypeInfo.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; + +namespace Microsoft.OData +{ + /// + /// The class to present the property type info which is resolved from property value. + /// + internal class PropertyValueTypeInfo + { + public PropertyValueTypeInfo(string typeName, IEdmTypeReference typeReference) + { + this.TypeName = typeName; + this.TypeReference = typeReference; + if (typeReference != null) + { + this.FullName = typeReference.FullName(); + this.IsPrimitive = typeReference.IsPrimitive(); + this.IsComplex = typeReference.IsComplex(); + this.PrimitiveTypeKind = this.IsPrimitive ? typeReference.AsPrimitive().PrimitiveKind() : EdmPrimitiveTypeKind.None; + } + } + + public IEdmTypeReference TypeReference { get; private set; } + + public string FullName { get; private set; } + + public bool IsPrimitive { get; private set; } + + public bool IsComplex { get; private set; } + + public string TypeName { get; private set; } + + public EdmPrimitiveTypeKind PrimitiveTypeKind { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/RawValueWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/RawValueWriter.cs new file mode 100644 index 0000000..5b89200 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/RawValueWriter.cs @@ -0,0 +1,181 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Text; + using Microsoft.OData.Json; + using Microsoft.Spatial; + + /// + /// Class that hanldes writing top level raw values to a stream. + /// + internal sealed class RawValueWriter : IDisposable + { + /// + /// Writer settings. + /// + private readonly ODataMessageWriterSettings settings; + + /// + /// Underlying stream. + /// + private readonly Stream stream; + + /// + /// Encoding that the TextWriter should use. + /// + private readonly Encoding encoding; + + /// + /// TextWriter instance for writing values. + /// + private TextWriter textWriter; + + /// + /// JsonWriter instance for writing values. + /// + private JsonWriter jsonWriter; + + /// + /// Initializes a new instance of the class. + /// Initializes the TextWriter. + /// + /// The writer settings. + /// The stream. It should be the same underlying stream the TextWriter uses. + /// The encoding to use in the text writer. + internal RawValueWriter(ODataMessageWriterSettings settings, Stream stream, Encoding encoding) + { + this.settings = settings; + this.stream = stream; + this.encoding = encoding; + this.InitializeTextWriter(); + } + + /// + /// Gets the text writer. + /// + internal TextWriter TextWriter + { + get { return this.textWriter; } + } + + /// + /// Gets the json writer. + /// + internal JsonWriter JsonWriter + { + get { return this.jsonWriter; } + } + + /// + /// Disposes the RawValueWriter. It flushes itself and then disposes its inner TextWriter. + /// + public void Dispose() + { + Debug.Assert(this.textWriter != null, "The text writer has not been initialized yet."); + + this.textWriter.Dispose(); + this.textWriter = null; + } + + /// + /// Start writing a raw output. This should only be called once. + /// + internal void Start() + { + if (this.settings.HasJsonPaddingFunction()) + { + this.textWriter.Write(this.settings.JsonPCallback); + this.textWriter.Write(JsonConstants.StartPaddingFunctionScope); + } + } + + /// + /// End the writing of a raw output. This should be the last thing called. + /// + internal void End() + { + if (this.settings.HasJsonPaddingFunction()) + { + this.textWriter.Write(JsonConstants.EndPaddingFunctionScope); + } + } + + /// + /// Converts the specified into its raw format and writes it to the output. + /// The value has to be of enumeration or primitive type. Only one WriteRawValue call should be made before this object gets disposed. + /// + /// The (non-binary) value to write. + /// We do not accept binary values here; WriteBinaryValue should be used for binary data. + internal void WriteRawValue(object value) + { + Debug.Assert(!(value is byte[]), "!(value is byte[])"); + + string valueAsString; + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + this.textWriter.Write(enumValue.Value); + } + else if (value is Geometry || value is Geography) + { + PrimitiveConverter.Instance.WriteJsonLight(value, jsonWriter); + } + else if (ODataRawValueUtils.TryConvertPrimitiveToString(value, out valueAsString)) + { + this.textWriter.Write(valueAsString); + } + else + { + // throw an exception because the value is neither enum nor primitive + throw new ODataException(Strings.ODataUtils_CannotConvertValueToRawString(value.GetType().FullName)); + } + } + + /// + /// Flushes the RawValueWriter. + /// The call gets pushed to the TextWriter (if there is one). In production code, this is StreamWriter.Flush, which turns into Stream.Flush. + /// In the synchronous case the underlying stream is the message stream itself, which will then Flush as well. + /// In the async case the underlying stream is the async buffered stream, which ignores Flush call. + /// + internal void Flush() + { + if (this.TextWriter != null) + { + this.TextWriter.Flush(); + } + } + + /// + /// Initialized a new text writer over the message payload stream. + /// + /// This can only be called if the text writer was not yet initialized or it has been closed. + /// It can be called several times with CloseWriter calls in between though. + private void InitializeTextWriter() + { + // We must create the text writer over a stream which will ignore Dispose, since we need to be able to Dispose + // the writer without disposing the underlying message stream. + Stream nonDisposingStream; + if (MessageStreamWrapper.IsNonDisposingStream(this.stream) || this.stream is AsyncBufferedStream) + { + // AsynBufferedStream ignores Dispose + nonDisposingStream = this.stream; + } + else + { + nonDisposingStream = MessageStreamWrapper.CreateNonDisposingStream(this.stream); + } + + this.textWriter = new StreamWriter(nonDisposingStream, this.encoding); + this.jsonWriter = new JsonWriter(this.textWriter, isIeee754Compatible: false); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerable.cs new file mode 100644 index 0000000..7e177ce --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerable.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections; + using System.Diagnostics; + #endregion Namespaces + + /// + /// Implementation of IEnumerable which is based on another IEnumerable + /// but only exposes readonly access to that collection. This class doesn't implement + /// any other public interfaces or public API unlike most other IEnumerable implementations + /// which also implement other public interfaces. + /// + internal class ReadOnlyEnumerable : IEnumerable + { + /// + /// The IEnumerable to wrap. + /// + private readonly IEnumerable sourceEnumerable; + + /// + /// Constructor. + /// + /// The enumerable to wrap. + internal ReadOnlyEnumerable(IEnumerable sourceEnumerable) + { + Debug.Assert(sourceEnumerable != null, "sourceEnumerable != null"); + + this.sourceEnumerable = sourceEnumerable; + } + + /// + /// Returns the enumerator to iterate through the items. + /// + /// The enumerator object to use. + IEnumerator IEnumerable.GetEnumerator() + { + return this.sourceEnumerable.GetEnumerator(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableExtensions.cs new file mode 100644 index 0000000..76f7c89 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableExtensions.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + #endregion Namespaces + + /// + /// Extension methods for ReadOnlyEnumerable and ReadOnlyEnumerableOfT + /// + internal static class ReadOnlyEnumerableExtensions + { + /// + /// true if is the same instance as ReadOnlyEnumerableOfT.Empty(). false otherwise. + /// + /// The element type of the enumerable. + /// The enumerable in question. + /// Returns true if is the empty ReadOnlyEnumerableOfT. false otherwise. + internal static bool IsEmptyReadOnlyEnumerable(this IEnumerable source) + { + return ReferenceEquals(source, ReadOnlyEnumerable.Empty()); + } + + /// + /// Casts an IEnumerableOfT to ReadOnlyEnumerableOfT. + /// + /// The element type of the enumerable. + /// The source enumerable. + /// The name of the collection to report in case there's an error. + /// The casted ReadOnlyEnumerableOfT. + internal static ReadOnlyEnumerable ToReadOnlyEnumerable(this IEnumerable source, string collectionName) + { + Debug.Assert(!String.IsNullOrEmpty(collectionName), "!string.IsNullOrEmpty(collectionName)"); + + ReadOnlyEnumerable readonlyCollection = source as ReadOnlyEnumerable; + if (readonlyCollection == null) + { + throw new ODataException(Strings.ReaderUtils_EnumerableModified(collectionName)); + } + + return readonlyCollection; + } + + /// + /// Returns the as ReadOnlyEnumerableOfT or + /// a new instance of ReadOnlyEnumerableOfT if is the same instance as ReadOnlyEnumerableOfT.Empty(). + /// + /// The element type of the enumerable. + /// The source enumerable in question. + /// The name of the collection to report in case there's an error. + /// Returns the as ReadOnlyEnumerableOfT or + /// a new instance of ReadOnlyEnumerableOfT if is the same instance as ReadOnlyEnumerableOfT.Empty(). + internal static ReadOnlyEnumerable GetOrCreateReadOnlyEnumerable(this IEnumerable source, string collectionName) + { + Debug.Assert(!String.IsNullOrEmpty(collectionName), "!string.IsNullOrEmpty(collectionName)"); + + if (source.IsEmptyReadOnlyEnumerable()) + { + return new ReadOnlyEnumerable(); + } + + return source.ToReadOnlyEnumerable(collectionName); + } + + /// + /// Returns a ReadOnlyEnumerableOfT that is the result of plus . + /// + /// The element type of the enumerable. + /// The source enumerable to concat. + /// The name of the collection to report in case there's an error. + /// Item to concat to the source enumerable. + /// Returns a ReadOnlyEnumerableOfT that is the result of plus . + internal static ReadOnlyEnumerable ConcatToReadOnlyEnumerable(this IEnumerable source, string collectionName, T item) + { + ReadOnlyEnumerable readOnlyEnumerable = source.GetOrCreateReadOnlyEnumerable(collectionName); + readOnlyEnumerable.AddToSourceList(item); + return readOnlyEnumerable; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableOfT.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableOfT.cs new file mode 100644 index 0000000..70b711a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReadOnlyEnumerableOfT.cs @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + #endregion Namespaces + + /// + /// Implementation of IEnumerable>T< which is based on a List>T< + /// but only exposes readonly access to that collection. This class doesn't implement + /// any other public interfaces or public API unlike most other IEnumerable implementations + /// which also implement other public interfaces. + /// + /// The type of a single item in the enumeration. + internal sealed class ReadOnlyEnumerable : ReadOnlyEnumerable, IEnumerable + { + /// + /// The IEnumerable to wrap. + /// + private readonly IList sourceList; + + /// + /// The empty instance of ReadOnlyEnumerableOfT. + /// + private static readonly SimpleLazy> EmptyInstance = new SimpleLazy>(() => new ReadOnlyEnumerable(new ReadOnlyCollection(new List(0))), true /*isThreadSafe*/); + + /// + /// Constructor which initializes the enumerable with an empty list storage. + /// + internal ReadOnlyEnumerable() + : this(new List()) + { + } + + /// + /// Constructor. + /// + /// The list of values to wrap. + internal ReadOnlyEnumerable(IList sourceList) + : base(sourceList) + { + Debug.Assert(sourceList != null, "sourceList != null"); + + this.sourceList = sourceList; + } + + /// + /// Returns the enumerator to iterate through the items. + /// + /// The enumerator object to use. + IEnumerator IEnumerable.GetEnumerator() + { + return this.sourceList.GetEnumerator(); + } + + /// + /// Gets the empty instance of ReadOnlyEnumerableOfT. + /// + /// Returns the empty instance of ReadOnlyEnumerableOfT. + internal static ReadOnlyEnumerable Empty() + { + return EmptyInstance.Value; + } + + /// + /// This internal method adds to the wrapped source list. From the public's perspective, this enumerable is still readonly. + /// + /// Item to add to the source list. + internal void AddToSourceList(T itemToAdd) + { + Debug.Assert(this.sourceList != null, "this.sourceList != null"); + + this.sourceList.Add(itemToAdd); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderUtils.cs new file mode 100644 index 0000000..cc27b6e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderUtils.cs @@ -0,0 +1,189 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Class with utility methods for reading OData content. + /// + internal static class ReaderUtils + { + /// + /// Gets the expected type kind based on the given , or EdmTypeKind.None if no specific type should be expected. + /// + /// The expected type reference. + /// Whether primitive type conversion is enabled. + /// The expected type kind based on the settings and type reference, or EdmTypeKind.None if no specific type should be expected. + internal static EdmTypeKind GetExpectedTypeKind(IEdmTypeReference expectedTypeReference, + bool enablePrimitiveTypeConversion) + { + IEdmType expectedTypeDefinition; + if (expectedTypeReference == null || (expectedTypeDefinition = expectedTypeReference.Definition) == null) + { + return EdmTypeKind.None; + } + + // If the EnablePrimitiveTypeConversion is off, we must not infer the type kind from the expected type + // but instead we need to read it from the payload. + // This is necessary to correctly fail on complex/collection as well as to correctly read spatial values. + EdmTypeKind expectedTypeKind = expectedTypeDefinition.TypeKind; + if (!enablePrimitiveTypeConversion + && (expectedTypeKind == EdmTypeKind.Primitive && !expectedTypeDefinition.IsStream())) + { + return EdmTypeKind.None; + } + + // Otherwise, if we have an expected type, use that. + return expectedTypeKind; + } + + /// + /// Creates a new instance to return to the user. + /// + /// The newly created resource. + /// The method populates the Properties property with an empty read only enumeration. + internal static ODataResource CreateNewResource() + { + return new ODataResource + { + Properties = new ReadOnlyEnumerable() + }; + } + + /// + /// Creates a new instance to return to the user. + /// + /// The id of the deleted resource, or null if not yet known. + /// The for the deleted resource. + /// The newly created deleted resource. + /// The method populates the Properties property with an empty read only enumeration. + internal static ODataDeletedResource CreateDeletedResource(Uri id, DeltaDeletedEntryReason reason) + { + return new ODataDeletedResource(id, reason) + { + Properties = new ReadOnlyEnumerable() + }; + } + + /// Checks for duplicate navigation links and if there already is an association link with the same name + /// sets the association link URL on the nested resource info. + /// The duplicate property names checker for the current scope. + /// The nested resource info to be checked. + internal static void CheckForDuplicateNestedResourceInfoNameAndSetAssociationLink( + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + + Uri associationLinkUrl = propertyAndAnnotationCollector.ValidatePropertyUniquenessAndGetAssociationLink(nestedResourceInfo); + + // We must not set the AssociationLinkUrl to null since that would disable templating on it, but we want templating to work if the association link was not in the payload. + if (associationLinkUrl != null && nestedResourceInfo.AssociationLinkUrl == null) + { + nestedResourceInfo.AssociationLinkUrl = associationLinkUrl; + } + } + + /// Checks that for duplicate association links and if there already is a nested resource info with the same name + /// sets the association link URL on that nested resource info. + /// The duplicate property names checker for the current scope. + /// The name of association link to be checked. + /// The url of association link to be checked. + internal static void CheckForDuplicateAssociationLinkAndUpdateNestedResourceInfo( + PropertyAndAnnotationCollector propertyAndAnnotationCollector, + string associationLinkName, + Uri associationLinkUrl) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + Debug.Assert(associationLinkName != null, "associationLinkName != null"); + + ODataNestedResourceInfo nestedResourceInfo = propertyAndAnnotationCollector.ValidatePropertyOpenForAssociationLinkAndGetNestedResourceInfo(associationLinkName, associationLinkUrl); + + // We must not set the AssociationLinkUrl to null since that would disable templating on it, but we want templating to work if the association link was not in the payload. + if (nestedResourceInfo != null && nestedResourceInfo.AssociationLinkUrl == null && associationLinkUrl != null) + { + nestedResourceInfo.AssociationLinkUrl = associationLinkUrl; + } + } + + /// + /// Gets the expected property name from the specified property or operation import. + /// + /// The to get the expected property name for (or null if none is specified). + /// The expected name of the property to be read from the payload. + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Ignoring violation because of Debug.Assert.")] + internal static string GetExpectedPropertyName(IEdmStructuralProperty expectedProperty) + { + if (expectedProperty == null) + { + return null; + } + + return expectedProperty.Name; + } + + /// + /// Remove the prefix (#) from type name if there is. + /// + /// The type name which may be prefixed (#). + /// The type name with prefix removed, if there is. + internal static string RemovePrefixOfTypeName(string typeName) + { + string prefixRemovedTypeName = typeName; + if (!string.IsNullOrEmpty(typeName) && typeName.StartsWith(ODataConstants.TypeNamePrefix, StringComparison.Ordinal)) + { + prefixRemovedTypeName = typeName.Substring(ODataConstants.TypeNamePrefix.Length); + + Debug.Assert(!prefixRemovedTypeName.StartsWith(ODataConstants.TypeNamePrefix, StringComparison.Ordinal), "The type name not start with " + ODataConstants.TypeNamePrefix + "after removing prefix"); + } + + return prefixRemovedTypeName; + } + + /// + /// Add the Edm. prefix to the primitive type if there isn't. + /// + /// The type name which may be not prefixed (Edm.). + /// The type name with Edm. prefix + internal static string AddEdmPrefixOfTypeName(string typeName) + { + if (!string.IsNullOrEmpty(typeName)) + { + string itemTypeName = EdmLibraryExtensions.GetCollectionItemTypeName(typeName); + if (itemTypeName == null) + { + // This is the primitive type + IEdmSchemaType primitiveType = EdmLibraryExtensions.ResolvePrimitiveTypeName(typeName); + if (primitiveType != null) + { + return primitiveType.FullName(); + } + } + else + { + // This is the collection type + IEdmSchemaType primitiveType = EdmLibraryExtensions.ResolvePrimitiveTypeName(itemTypeName); + if (primitiveType != null) + { + return EdmLibraryExtensions.GetCollectionTypeName(primitiveType.FullName()); + } + } + } + + // Return the origin type name + return typeName; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidationUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidationUtils.cs new file mode 100644 index 0000000..add098b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidationUtils.cs @@ -0,0 +1,1172 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Linq; + using System.Text; + using Microsoft.OData.Edm; + using Microsoft.OData.JsonLight; + using Microsoft.OData.Metadata; + using Microsoft.OData.UriParser; + #endregion Namespaces + + /// + /// Class with utility methods for validating OData content when reading. + /// + internal static class ReaderValidationUtils + { + /// + /// Validates that message reader settings are correct. + /// + /// The message reader settings to validate. + /// true if the settings were specified when reading a response, false when reading a request. + internal static void ValidateMessageReaderSettings(ODataMessageReaderSettings messageReaderSettings, bool readingResponse) + { + Debug.Assert(messageReaderSettings != null, "messageReaderSettings != null"); + + if (messageReaderSettings.BaseUri != null && !messageReaderSettings.BaseUri.IsAbsoluteUri) + { + throw new ODataException(Strings.ReaderValidationUtils_MessageReaderSettingsBaseUriMustBeNullOrAbsolute(UriUtils.UriToString(messageReaderSettings.BaseUri))); + } + } + + /// + /// Validates an entity reference link. + /// + /// The entity reference link to check. + internal static void ValidateEntityReferenceLink(ODataEntityReferenceLink link) + { + Debug.Assert(link != null, "link != null"); + + if (link.Url == null) + { + throw new ODataException(Strings.ReaderValidationUtils_EntityReferenceLinkMissingUri); + } + } + + /// + /// Validates a stream reference property. + /// + /// The stream info for the property to check. + /// The name of the property being checked. + /// The owning type of the stream property or null if no metadata is available. + /// The stream property defined by the model. + /// Whether ThrowOnUndeclaredLinkProperty validation setting is enabled. + internal static void ValidateStreamReferenceProperty( + IODataStreamReferenceInfo streamInfo, string propertyName, IEdmStructuredType structuredType, + IEdmProperty streamEdmProperty, bool throwOnUndeclaredLinkProperty) + { + Debug.Assert(streamInfo != null, "streamInfo != null"); + + ValidationUtils.ValidateStreamPropertyInfo(streamInfo, streamEdmProperty, propertyName); + + if (structuredType != null && structuredType.IsOpen) + { + if (streamEdmProperty == null && throwOnUndeclaredLinkProperty) + { + throw new ODataException(Strings.ValidationUtils_OpenStreamProperty(propertyName)); + } + } + } + + /// + /// Validate a null value. + /// + /// The expected type of the null value. + /// Whether primitive type conversion is enabled. + /// true to validate the null value; false to only check whether the type is supported. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// Indicates whether the property is dynamic or unknown. + internal static void ValidateNullValue( + IEdmTypeReference expectedTypeReference, + bool enablePrimitiveTypeConversion, + bool validateNullValue, + string propertyName, + bool? isDynamicProperty) + { + // For a null value if we have an expected type + // - we validate that the expected type is nullable if type conversion is enabled + // - don't validate any primitive types if primitive type conversion is disabled + // For a null value without an expected type + // - we simply return null (treat it is as primitive null value) + if (expectedTypeReference != null) + { + if (enablePrimitiveTypeConversion || expectedTypeReference.TypeKind() != EdmTypeKind.Primitive) + { + ValidateNullValueAllowed(expectedTypeReference, validateNullValue, propertyName, isDynamicProperty); + } + } + } + + /// + /// Validates that a property with the specified name exists on a given structured type. + /// The structured type can be null if no metadata is available. + /// + /// The name of the property to validate. + /// The owning type of the property with name + /// or null if no metadata is available. + /// Whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled. + /// The instance representing the property with name + /// or null if no metadata is available. + internal static IEdmProperty ValidatePropertyDefined(string propertyName, + IEdmStructuredType owningStructuredType, + bool throwOnUndeclaredPropertyForNonOpenType) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + if (owningStructuredType == null) + { + return null; + } + + IEdmProperty property = owningStructuredType.FindProperty(propertyName); + if (property == null && !owningStructuredType.IsOpen) + { + if (throwOnUndeclaredPropertyForNonOpenType) + { + throw new ODataException(Strings.ValidationUtils_PropertyDoesNotExistOnType(propertyName, owningStructuredType.FullTypeName())); + } + } + + return property; + } + + /// + /// Creates an exception used when primitive type conversion fails. + /// + /// The target type reference to which the conversion failed. + /// Possible inner exception with more information about the failure. + /// The string representation for the value. + /// The exception object to throw. + internal static ODataException GetPrimitiveTypeConversionException(IEdmPrimitiveTypeReference targetTypeReference, Exception innerException, string stringValue) + { + Debug.Assert(targetTypeReference != null, "targetTypeReference != null"); + + return new ODataException(Strings.ReaderValidationUtils_CannotConvertPrimitiveValue(stringValue, targetTypeReference.FullName()), innerException); + } + + /// + /// Resolved the payload type name to the type. + /// + /// The model to use for the resolution. + /// The expected type reference, or null if no expected type is available. + /// The payload type name to resolve. + /// The default payload type kind, this is used when the resolution is not possible, + /// but the type name is not empty. (Should be either Complex or Entity). + /// The function of client custom type resolver. + /// This is set to the detected payload type kind, or None if the type was not specified. + /// The resolved type. This may be null if either no user-specified model is specified, or the type name is not recognized by the model. + /// The method detects the payload kind even if the model does not recognize the type. It figures out primitive and collection types always, + /// and uses the for the rest. + internal static IEdmType ResolvePayloadTypeName( + IEdmModel model, + IEdmTypeReference expectedTypeReference, + string payloadTypeName, + EdmTypeKind expectedTypeKind, + Func clientCustomTypeResolver, + out EdmTypeKind payloadTypeKind) + { + if (payloadTypeName == null) + { + payloadTypeKind = EdmTypeKind.None; + return null; + } + + // Empty type names are allowed. + if (payloadTypeName.Length == 0) + { + payloadTypeKind = expectedTypeKind; + return null; + } + + IEdmType payloadType = MetadataUtils.ResolveTypeNameForRead( + model, + expectedTypeReference == null ? null : expectedTypeReference.Definition, + payloadTypeName, + clientCustomTypeResolver, + out payloadTypeKind); + if (payloadTypeKind == EdmTypeKind.None) + { + payloadTypeKind = expectedTypeKind; + } + + return payloadType; + } + + /// + /// Resolves and validates the payload type against the expected type and returns the target type. + /// + /// The expected type kind for the value. + /// This value indicates if a structured type is expected to be return. + /// True for structured type, false for non-structured type, null for indetermination. + /// The default payload type if none is specified in the payload; + /// for ATOM this is Edm.String, for JSON it is null since there is no payload type name for primitive types in the payload. + /// The expected type reference, or null if no expected type is available. + /// The payload type name, or null if no payload type was specified. + /// The model to use. + /// Custom type resolver used by the client. + /// Whether ThrowIfTypeConflictsWithMetadata is enabled. + /// Whether primitive type conversion is enabled. + /// A func to compute the type kind from the payload shape if it could not be determined from the expected type or the payload type. + /// The target type kind to be used to read the payload. + /// Potentially non-null instance of an annotation to put on the value reported from the reader. + /// + /// The target type reference to use for parsing the value. + /// If there is no user specified model, this will return null. + /// If there is a user specified model, this method never returns null. + /// + /// + /// This method cannot be used for primitive type resolution. Primitive type resolution is format dependent and format specific methods should be used instead. + /// + internal static IEdmTypeReference ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind expectedTypeKind, + bool? expectStructuredType, + IEdmType defaultPrimitivePayloadType, + IEdmTypeReference expectedTypeReference, + string payloadTypeName, + IEdmModel model, + Func clientCustomTypeResolver, + bool throwIfTypeConflictsWithMetadata, + bool enablePrimitiveTypeConversion, + Func typeKindFromPayloadFunc, + out EdmTypeKind targetTypeKind, + out ODataTypeAnnotation typeAnnotation) + { + Debug.Assert(typeKindFromPayloadFunc != null, "typeKindFromPayloadFunc != null"); + Debug.Assert( + expectedTypeKind == EdmTypeKind.None + || expectedTypeKind.IsStructured() && expectStructuredType != false + || !expectedTypeKind.IsStructured() && expectStructuredType != true, + "expectedTypeKind == EdmTypeKind.None || expectedTypeKind.IsStructured() && expectStructuredType != false || !expectedTypeKind.IsStructured() && expectStructuredType != true"); + + typeAnnotation = null; + + // What is the right behavior if both expected and actual types are specified for complex value? + // We decided that they have to match exactly. + + // Resolve the type name and get the payload type kind; that is the type kind of the payload + // type if available or the expected type kind if no payload type could be resolved. Since + // we always detect primitive and collection types the expected type for unrecognized payload + // types is EdmTypeKind.Complex. + EdmTypeKind payloadTypeKind; + IEdmType payloadType = ResolvePayloadTypeName( + model, + expectedTypeReference, + payloadTypeName, + EdmTypeKind.Complex, + clientCustomTypeResolver, + out payloadTypeKind); + + // Compute the target type kind based on the expected type, the payload type kind + // and a function to detect the target type kind from the shape of the payload. + bool forResource = expectStructuredType == true + || !expectStructuredType.HasValue && payloadTypeKind.IsStructured(); + + targetTypeKind = ComputeTargetTypeKind( + expectedTypeReference, + forResource, + payloadTypeName, + payloadTypeKind, + clientCustomTypeResolver, + throwIfTypeConflictsWithMetadata, + enablePrimitiveTypeConversion, + typeKindFromPayloadFunc); + + // Resolve potential conflicts between payload and expected types and apply all the various behavior changing flags from settings + IEdmTypeReference targetTypeReference; + if (targetTypeKind == EdmTypeKind.Primitive) + { + targetTypeReference = ReaderValidationUtils.ResolveAndValidatePrimitiveTargetType( + expectedTypeReference, + payloadTypeKind, + payloadType, + payloadTypeName, + defaultPrimitivePayloadType, + model, + clientCustomTypeResolver, + enablePrimitiveTypeConversion, + throwIfTypeConflictsWithMetadata); + } + else + { + targetTypeReference = ReaderValidationUtils.ResolveAndValidateNonPrimitiveTargetType( + targetTypeKind, + expectedTypeReference, + payloadTypeKind, + payloadType, + payloadTypeName, + model, + clientCustomTypeResolver, + throwIfTypeConflictsWithMetadata); + + if (targetTypeReference != null) + { + typeAnnotation = CreateODataTypeAnnotation(payloadTypeName, payloadType, targetTypeReference); + } + } + + if ((expectedTypeKind != EdmTypeKind.None || (targetTypeKind != EdmTypeKind.Untyped && expectStructuredType == true)) && targetTypeReference != null) + { + ValidationUtils.ValidateTypeKind(targetTypeKind, expectedTypeKind, forResource, payloadTypeName); + } + + return targetTypeReference; + } + + /// + /// Resolves the primitive payload type versus the expected type and validates that such combination is allowed. + /// + /// The expected type reference, if any. + /// The kind of the payload type, or None if the detection was not possible. + /// The resolved payload type, or null if no payload type was specified. + /// The name of the payload type, or null if no payload type was specified. + /// The default payload type if none is specified in the payload; + /// for ATOM this is Edm.String, for JSON it is null since there is no payload type name for primitive types in the payload. + /// The model to use. + /// Custom type resolver used by client, or null if none. + /// Whether primitive type conversion is enabled. + /// Whether ThrowIfTypeConflictsWithMetadata is enabled. + /// The target type reference to use for parsing the value. This method never returns null. + internal static IEdmTypeReference ResolveAndValidatePrimitiveTargetType( + IEdmTypeReference expectedTypeReference, + EdmTypeKind payloadTypeKind, + IEdmType payloadType, + string payloadTypeName, + IEdmType defaultPayloadType, + IEdmModel model, + Func clientCustomTypeResolver, + bool enablePrimitiveTypeConversion, + bool throwIfTypeConflictsWithMetadata) + { + Debug.Assert( + payloadTypeKind == EdmTypeKind.Primitive || payloadTypeKind == EdmTypeKind.Complex || + payloadTypeKind == EdmTypeKind.Entity || payloadTypeKind == EdmTypeKind.Collection || + payloadTypeKind == EdmTypeKind.None || payloadTypeKind == EdmTypeKind.TypeDefinition, + "The payload type kind must be one of None, Primitive, Complex, Entity, Collection or TypeDefinition."); + Debug.Assert( + expectedTypeReference == null || expectedTypeReference.TypeKind() == EdmTypeKind.Primitive, + "This method only works for primitive expected type."); + Debug.Assert( + payloadType == null || payloadType.TypeKind == payloadTypeKind, + "The payload type kind must match the payload type if that is available."); + Debug.Assert(payloadType == null || payloadTypeName != null, "If we have a payload type, we must have its name as well."); + + bool useExpectedTypeOnlyForTypeResolution = clientCustomTypeResolver != null && payloadType != null; + + // Validate type kinds except for open properties or when in lax mode, but only if primitive type conversion is enabled. + // If primitive type conversion is disabled, the type kind must match, no matter what validation mode is used. + // The rules for primitive types are: + // - In the strict mode the payload value must be convertible to the expected type. So the payload type must be a primitive type. + // - In the lax mode the payload type is ignored, so its type kind is not verified in any way + // - If the DisablePrimitiveTypeConversion == true, the lax/strict mode doesn't matter and we will read the payload value on its own. + // In this case we require the payload value to always be a primitive type (so type kinds must match), but it may not be convertible + // to the expected type, it will still be reported to the caller. + if (payloadTypeKind != EdmTypeKind.None && (!enablePrimitiveTypeConversion || throwIfTypeConflictsWithMetadata)) + { + // Make sure that the type kinds match. + ValidationUtils.ValidateTypeKind(payloadTypeKind, EdmTypeKind.Primitive, null, payloadTypeName); + } + + if (!model.IsUserModel()) + { + // If there's no model, it means we should not have the expected type either, and that there's no type to use, + // no metadata validation to perform. + Debug.Assert(expectedTypeReference == null, "If we don't have a model, we must not have expected type either."); + return MetadataUtils.GetNullablePayloadTypeReference(payloadType ?? defaultPayloadType); + } + + // If the primitive type conversion is off, use the payload type always. + // If there's no expected type or the expected type is ignored, use the payload type as well. + if (expectedTypeReference == null || useExpectedTypeOnlyForTypeResolution || !enablePrimitiveTypeConversion) + { + // If there's no payload type, use the default payload type. + // Note that in collections the items without type should inherit the type name from the collection, in that case the expectedTypeReference + // is never null (assuming we do have a model), so we won't get here. + return MetadataUtils.GetNullablePayloadTypeReference(payloadType ?? defaultPayloadType); + } + + // The server ignores the payload type when expected type is specified + if (!throwIfTypeConflictsWithMetadata) + { + // Always use the expected type, the payload type is ignored. + return expectedTypeReference; + } + + // We assume the expected type in the case where no payload type is specified + // for a primitive value; if no expected type is available we assume Edm.String. + if (payloadType != null) + { + // The payload type must be convertible to the expected type. + // Note that we compare the type definitions, since we want to ignore nullability (the payload type doesn't specify nullability). + if (!MetadataUtilsCommon.CanConvertPrimitiveTypeTo( + null /* sourceNodeOrNull */, + (IEdmPrimitiveType)payloadType.AsActualType(), + (IEdmPrimitiveType)(expectedTypeReference.Definition))) + { + throw new ODataException(Strings.ValidationUtils_IncompatibleType(payloadTypeName, expectedTypeReference.FullName())); + } + + if (expectedTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.PrimitiveType) + { + return payloadType.ToTypeReference(expectedTypeReference.IsNullable); + } + } + + // Read using the expected type. + // If there was a payload type we verified that it's convertible and thus we can safely read + // the content of the value as the expected type. + return expectedTypeReference; + } + + /// + /// Resolves the payload type versus the expected type and validates that such combination is allowed. + /// + /// The expected type kind for the value. + /// The expected type reference, or null if no expected type is available. + /// The payload type kind, this may be the one from the type itself, or one detected without resolving the type. + /// The payload type, or null if the payload type was not specified, or it didn't resolve against the model. + /// The payload type name, or null if no payload type was specified. + /// The model to use. + /// Custom type resolver used by client, or null if none. + /// Whether ThrowIfTypeConflictsWithMetadata is enabled. + /// + /// The target type reference to use for parsing the value. + /// If there is no user specified model, this will return null. + /// If there is a user specified model, this method never returns null. + /// + /// + /// This method cannot be used for primitive type resolution. Primitive type resolution is format dependent and format specific methods should be used instead. + /// + internal static IEdmTypeReference ResolveAndValidateNonPrimitiveTargetType( + EdmTypeKind expectedTypeKind, + IEdmTypeReference expectedTypeReference, + EdmTypeKind payloadTypeKind, + IEdmType payloadType, + string payloadTypeName, + IEdmModel model, + Func clientCustomTypeResolver, + bool throwIfTypeConflictsWithMetadata) + { + Debug.Assert( + expectedTypeKind == EdmTypeKind.Enum || expectedTypeKind == EdmTypeKind.Complex || expectedTypeKind == EdmTypeKind.Entity || + expectedTypeKind == EdmTypeKind.Collection || expectedTypeKind == EdmTypeKind.TypeDefinition || expectedTypeKind == EdmTypeKind.Untyped, + "The expected type kind must be one of Enum, Complex, Entity, Collection or TypeDefinition."); + Debug.Assert( + payloadTypeKind == EdmTypeKind.Complex || payloadTypeKind == EdmTypeKind.Entity || + payloadTypeKind == EdmTypeKind.Collection || payloadTypeKind == EdmTypeKind.None || + payloadTypeKind == EdmTypeKind.Primitive || payloadTypeKind == EdmTypeKind.Enum || + payloadTypeKind == EdmTypeKind.TypeDefinition || payloadTypeKind == EdmTypeKind.Untyped, + "The payload type kind must be one of None, Primitive, Enum, Untyped, Complex, Entity, Collection or TypeDefinition."); + Debug.Assert( + expectedTypeReference == null || expectedTypeReference.TypeKind() == expectedTypeKind, + "The expected type kind must match the expected type reference if that is available."); + Debug.Assert( + payloadType == null || payloadType.TypeKind == payloadTypeKind, + "The payload type kind must match the payload type if that is available."); + Debug.Assert(payloadType == null || payloadTypeName != null, "If we have a payload type, we must have its name as well."); + + bool useExpectedTypeOnlyForTypeResolution = clientCustomTypeResolver != null && payloadType != null; + if (!useExpectedTypeOnlyForTypeResolution) + { + // We should validate that the payload type resolved before anything else to produce reasonable error messages + // Otherwise we might report errors which are somewhat confusing (like "Type '' is Complex but Collection was expected."). + if (model.IsUserModel() && (expectedTypeReference == null || throwIfTypeConflictsWithMetadata)) + { + // When using a type resolver (i.e., useExpectedTypeOnlyForTypeResolution == true) then we don't have to + // call this method because the contract with the type resolver is to always resolve the type name and thus + // we will always get a defined type. + VerifyPayloadTypeDefined(payloadTypeName, payloadType); + } + } + + if (payloadTypeKind != EdmTypeKind.None && (throwIfTypeConflictsWithMetadata || expectedTypeReference == null)) + { + // Make sure that the type kinds match. + ValidationUtils.ValidateTypeKind(payloadTypeKind, expectedTypeKind, null, payloadTypeName); + } + + if (!model.IsUserModel()) + { + // If there's no model, it means we should not have the expected type either, and that there's no type to use, + // no metadata validation to perform. + Debug.Assert(expectedTypeReference == null, "If we don't have a model, we must not have expected type either."); + return null; + } + + if (expectedTypeReference == null || useExpectedTypeOnlyForTypeResolution) + { + Debug.Assert(payloadTypeName == null || payloadType != null, "The payload type must have resolved before we get here."); + return ResolveAndValidateTargetTypeWithNoExpectedType( + expectedTypeKind, + payloadType); + } + + if (!throwIfTypeConflictsWithMetadata) + { + return ResolveAndValidateTargetTypeStrictValidationDisabled( + expectedTypeKind, + expectedTypeReference, + payloadType); + } + + Debug.Assert(payloadTypeName == null || payloadType != null, "The payload type must have resolved before we get here."); + return ResolveAndValidateTargetTypeStrictValidationEnabled( + expectedTypeKind, + expectedTypeReference, + payloadType); + } + + /// + /// Validates that the specified encoding is supported in batch/changeset envelopes (headers, boundaries, preamble, etc.). + /// + /// The to check. + internal static void ValidateEncodingSupportedInBatch(Encoding encoding) + { + Debug.Assert(encoding != null, "encoding != null"); + +#if !ORCAS + if (string.CompareOrdinal(Encoding.UTF8.WebName, encoding.WebName) != 0) +#else + if (!encoding.IsSingleByte && Encoding.UTF8.CodePage != encoding.CodePage) +#endif + { + // TODO: Batch reader does not support multi codepoint encodings + // We decided to not support multi-byte encodings other than UTF8 for now. + throw new ODataException(Strings.ODataBatchReaderStream_MultiByteEncodingsNotSupported(encoding.WebName)); + } + } + + /// + /// Validates that the specified encoding is supported in async envelopes (headers, preamble, etc.). + /// + /// The to check. + internal static void ValidateEncodingSupportedInAsync(Encoding encoding) + { + Debug.Assert(encoding != null, "encoding != null"); + +#if !ORCAS + if (string.CompareOrdinal(Encoding.UTF8.WebName, encoding.WebName) != 0) +#else + if (!encoding.IsSingleByte && Encoding.UTF8.CodePage != encoding.CodePage) +#endif + { + // We decided to not support multi-byte encodings other than UTF8 for now. + throw new ODataException(Strings.ODataAsyncReader_MultiByteEncodingsNotSupported(encoding.WebName)); + } + } + + /// + /// Validates that the parsed context URI from the payload is consistent with the expected + /// entity set and entity type when reading a resource set or resource payload. + /// + /// The parse result of the context URI from the payload. + /// The top-level scope representing the reader state. + /// Whether to update scope when validating. + internal static void ValidateResourceSetOrResourceContextUri(ODataJsonLightContextUriParseResult contextUriParseResult, ODataReaderCore.Scope scope, bool updateScope) + { + if (contextUriParseResult.EdmType is IEdmCollectionType) + { + ValidateResourceSetContextUri(contextUriParseResult, scope, updateScope); + return; + } + + Debug.Assert(contextUriParseResult != null, "contextUriParseResult != null"); + + // Set the navigation source name or make sure the navigation source names match. + if (scope.NavigationSource == null) + { + if (updateScope) + { + scope.NavigationSource = contextUriParseResult.NavigationSource; + } + } + else if (contextUriParseResult.NavigationSource != null && string.CompareOrdinal(scope.NavigationSource.FullNavigationSourceName(), contextUriParseResult.NavigationSource.FullNavigationSourceName()) != 0) + { + throw new ODataException(Strings.ReaderValidationUtils_ContextUriValidationInvalidExpectedEntitySet( + UriUtils.UriToString(contextUriParseResult.ContextUri), + contextUriParseResult.NavigationSource.FullNavigationSourceName(), + scope.NavigationSource.FullNavigationSourceName())); + } + + // Set the resource type or make sure the resource types are assignable. + IEdmStructuredType payloadEntityType = (IEdmStructuredType)contextUriParseResult.EdmType; + + if (payloadEntityType == null) + { + // for dynmaic path, the contextUriParseResult.EdmType might be null; + return; + } + + if (scope.ResourceType == null) + { + if (updateScope) + { + scope.ResourceTypeReference = payloadEntityType.ToTypeReference(true).AsStructured(); + } + } + else if (scope.ResourceType.IsAssignableFrom(payloadEntityType)) + { + if (updateScope) + { + scope.ResourceTypeReference = payloadEntityType.ToTypeReference(true).AsStructured(); + } + } + else if (!payloadEntityType.IsAssignableFrom(scope.ResourceType)) + { + throw new ODataException(Strings.ReaderValidationUtils_ContextUriValidationInvalidExpectedEntityType( + UriUtils.UriToString(contextUriParseResult.ContextUri), + contextUriParseResult.EdmType.FullTypeName(), + scope.ResourceType.FullTypeName())); + } + } + + /// + /// Validates that the parsed context URI from the payload is consistent with the expected + /// collection item type when reading collection payloads. + /// + /// The parse result of the context URI from the payload. + /// The expected item type of the collection items. + /// The actual item type of the collection items. + internal static IEdmTypeReference ValidateCollectionContextUriAndGetPayloadItemTypeReference( + ODataJsonLightContextUriParseResult contextUriParseResult, + IEdmTypeReference expectedItemTypeReference) + { + if (contextUriParseResult == null || contextUriParseResult.EdmType == null) + { + return expectedItemTypeReference; + } + + if (contextUriParseResult.EdmType is IEdmCollectionType) + { + Debug.Assert(contextUriParseResult.EdmType is IEdmCollectionType, "contextUriParseResult.EdmType is IEdmCollectionType"); + IEdmCollectionType actualCollectionType = (IEdmCollectionType)contextUriParseResult.EdmType; + if (expectedItemTypeReference != null) + { + // We allow co-variance in collection types (e.g., expecting the item type of Geography from a payload of Collection(GeographyPoint). + if (!expectedItemTypeReference.IsAssignableFrom(actualCollectionType.ElementType)) + { + throw new ODataException(Strings.ReaderValidationUtils_ContextUriDoesNotReferTypeAssignableToExpectedType( + UriUtils.UriToString(contextUriParseResult.ContextUri), + actualCollectionType.ElementType.FullName(), + expectedItemTypeReference.FullName())); + } + } + + return actualCollectionType.ElementType; + } + + Debug.Assert(contextUriParseResult.EdmType is IEdmComplexType && contextUriParseResult.DetectedPayloadKinds.Any(k => k == ODataPayloadKind.Collection), + "contextUriParseResult.EdmType is IEdmComplexType"); + if (expectedItemTypeReference != null && !expectedItemTypeReference.Definition.IsAssignableFrom(contextUriParseResult.EdmType)) + { + throw new ODataException(Strings.ReaderValidationUtils_ContextUriDoesNotReferTypeAssignableToExpectedType( + UriUtils.UriToString(contextUriParseResult.ContextUri), + contextUriParseResult.EdmType.FullTypeName(), + expectedItemTypeReference.Definition.FullTypeName())); + } + + return contextUriParseResult.EdmType.ToTypeReference(true); + } + + /// + /// Validates that the property in an operation (an action or a function) is valid. + /// + /// The value of the property. + /// The name of the property (used for error reporting). + /// The metadata value for the operation (used for error reporting). + /// The header for the operation, either 'actions' or 'functions'. + internal static void ValidateOperationProperty(object propertyValue, string propertyName, string metadata, string operationsHeader) + { + Debug.Assert(!String.IsNullOrEmpty(metadata), "!string.IsNullOrEmpty(metadata)"); + Debug.Assert(!String.IsNullOrEmpty(operationsHeader), "!string.IsNullOrEmpty(operationsHeader)"); + + if (propertyValue == null) + { + throw new ODataException(Strings.ODataJsonOperationsDeserializerUtils_OperationPropertyCannotBeNull( + propertyName, + metadata, + operationsHeader)); + } + } + + /// + /// Resolves the payload type if there's no expected type. + /// + /// The expected type kind for the value. + /// The payload type, or null if the payload type was not specified, or it didn't resolve against the model. + /// The target type reference to use for parsing the value. + private static IEdmTypeReference ResolveAndValidateTargetTypeWithNoExpectedType( + EdmTypeKind expectedTypeKind, + IEdmType payloadType) + { + // No expected type (for example an open property, but other scenarios are possible) + // We need some type to go on. We do have a model, so we must perform metadata validation and for that we need a type. + if (payloadType == null) + { + if (expectedTypeKind == EdmTypeKind.Entity) + { + throw new ODataException(Strings.ReaderValidationUtils_ResourceWithoutType); + } + + return null; // supports undeclared property + } + + // Payload types are always nullable. + IEdmTypeReference payloadTypeReference = payloadType.ToTypeReference(/*nullable*/ true); + + // Use the payload type (since we don't have any other). + return payloadTypeReference; + } + + /// + /// Resolves the payload type versus the expected type and validates that such combination is allowed when the strict validation is disabled. + /// + /// The expected type kind for the value. + /// The expected type reference, or null if no expected type is available. + /// The payload type, or null if the payload type was not specified, or it didn't resolve against the model. + /// The target type reference to use for parsing the value. + private static IEdmTypeReference ResolveAndValidateTargetTypeStrictValidationDisabled( + EdmTypeKind expectedTypeKind, + IEdmTypeReference expectedTypeReference, + IEdmType payloadType) + { + // Lax validation logic + switch (expectedTypeKind) + { + case EdmTypeKind.Complex: + // if the expectedTypeKind is different from the payloadType.TypeKind the types are not related + // in any way. In that case we will just use the expected type. + if (payloadType != null && expectedTypeKind == payloadType.TypeKind) + { + if (EdmLibraryExtensions.IsAssignableFrom(expectedTypeReference.AsComplex().ComplexDefinition(), (IEdmComplexType)payloadType)) + { + return payloadType.ToTypeReference(/*nullable*/ true); + } + } + + break; + case EdmTypeKind.Entity: + // if the expectedTypeKind is different from the payloadType.TypeKind the types are not related + // in any way. In that case we will just use the expected type. + if (payloadType != null && expectedTypeKind == payloadType.TypeKind) + { + // If the type is assignable (equal or derived) we will use the payload type, since we want to allow derived entities + if (EdmLibraryExtensions.IsAssignableFrom(expectedTypeReference.AsEntity().EntityDefinition(), (IEdmEntityType)payloadType)) + { + IEdmTypeReference payloadTypeReference = payloadType.ToTypeReference(/*nullable*/ true); + return payloadTypeReference; + } + } + + break; + case EdmTypeKind.Collection: + // if the expectedTypeKind is different from the payloadType.TypeKind the types are not related + // in any way. In that case we will just use the expected type. + if (payloadType != null && expectedTypeKind == payloadType.TypeKind) + { + VerifyCollectionComplexItemType(expectedTypeReference, payloadType); + } + + break; + case EdmTypeKind.Untyped: // untyped: no validation (can be anything) + + break; + case EdmTypeKind.Enum: // enum: no validation + + break; + case EdmTypeKind.TypeDefinition: // type definition: no validation + + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ReaderValidationUtils_ResolveAndValidateTypeName_Strict_TypeKind)); + } + + // Either there's no payload type, in which case use the expected one, or the payload one and the expected one are equal. + return expectedTypeReference; + } + + /// + /// Resolves the payload type versus the expected type and validates that such combination is allowed when strict validation is enabled. + /// + /// The expected type kind for the value. + /// The expected type reference, or null if no expected type is available. + /// The payload type, or null if the payload type was not specified, or it didn't resolve against the model. + /// The target type reference to use for parsing the value. + private static IEdmTypeReference ResolveAndValidateTargetTypeStrictValidationEnabled( + EdmTypeKind expectedTypeKind, + IEdmTypeReference expectedTypeReference, + IEdmType payloadType) + { + // Strict validation logic + switch (expectedTypeKind) + { + case EdmTypeKind.Complex: + if (payloadType != null) + { + // The payload type must be compatible to the expected type. + VerifyComplexType(expectedTypeReference, payloadType, /* failIfNotRelated */ true); + + // Use the payload type + return payloadType.ToTypeReference(/*nullable*/ true); + } + + break; + case EdmTypeKind.Entity: + if (payloadType != null) + { + // The payload type must be assignable to the expected type. + IEdmTypeReference payloadTypeReference = payloadType.ToTypeReference(/*nullable*/ true); + ValidationUtils.ValidateEntityTypeIsAssignable((IEdmEntityTypeReference)expectedTypeReference, (IEdmEntityTypeReference)payloadTypeReference); + + // Use the payload type + return payloadTypeReference; + } + + break; + case EdmTypeKind.Enum: + if (payloadType != null && string.CompareOrdinal(payloadType.FullTypeName(), expectedTypeReference.FullName()) != 0) + { + throw new ODataException(Strings.ValidationUtils_IncompatibleType(payloadType.FullTypeName(), expectedTypeReference.FullName())); + } + + break; + case EdmTypeKind.Collection: + // The type must be exactly equal - note that we intentionally ignore nullability of the items here, since the payload type + // can't specify that. + if (payloadType != null && !payloadType.IsElementTypeEquivalentTo(expectedTypeReference.Definition)) + { + VerifyCollectionComplexItemType(expectedTypeReference, payloadType); + + throw new ODataException(Strings.ValidationUtils_IncompatibleType(payloadType.FullTypeName(), expectedTypeReference.FullName())); + } + + break; + case EdmTypeKind.TypeDefinition: + if (payloadType != null && !expectedTypeReference.Definition.IsAssignableFrom(payloadType)) + { + throw new ODataException(Strings.ValidationUtils_IncompatibleType(payloadType.FullTypeName(), expectedTypeReference.FullName())); + } + + break; + case EdmTypeKind.Untyped: + break; + default: + throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ReaderValidationUtils_ResolveAndValidateTypeName_Strict_TypeKind)); + } + + // Either there's no payload type, in which case use the expected one, or the payload one and the expected one are equal. + return expectedTypeReference; + } + + /// + /// Verifies that payload type is defined if the payload type name is present. + /// + /// The type name from the payload. + /// The resolved type from the model. + private static void VerifyPayloadTypeDefined(string payloadTypeName, IEdmType payloadType) + { + if (payloadTypeName != null && payloadType == null) + { + throw new ODataException(Strings.ValidationUtils_UnrecognizedTypeName(payloadTypeName)); + } + } + + /// + /// Verifies that complex type is valid against the expected type. + /// + /// The expected type reference. + /// The payload type. + /// true if the method should fail if the doesn't match the ; + /// false if the method should just return in that case. + /// + /// The method verifies that the equals to or derives from the + /// and always fails in other cases. + /// + private static void VerifyComplexType(IEdmTypeReference expectedTypeReference, IEdmType payloadType, bool failIfNotRelated) + { + Debug.Assert(expectedTypeReference != null, "expectedTypeReference != null"); + Debug.Assert(payloadType != null, "payloadType != null"); + + // Note that we compare the type definitions, since we want to ignore nullability (the payload type doesn't specify nullability). + IEdmStructuredType structuredExpectedType = expectedTypeReference.AsStructured().StructuredDefinition(); + IEdmStructuredType structuredPayloadType = (IEdmStructuredType)payloadType; + + if (!EdmLibraryExtensions.IsAssignableFrom(structuredExpectedType, structuredPayloadType)) + { + if (failIfNotRelated) + { + // And now the generic one when the types are not related at all. + throw new ODataException(Strings.ValidationUtils_IncompatibleType(structuredPayloadType.FullTypeName(), structuredExpectedType.FullTypeName())); + } + } + } + + /// + /// Verifies that in case of collection types, the item type is valid. + /// + /// The expected type reference. + /// The payload type. + /// + /// This method verifies that item type is compatible with expected type. + /// + private static void VerifyCollectionComplexItemType(IEdmTypeReference expectedTypeReference, IEdmType payloadType) + { + Debug.Assert(expectedTypeReference != null, "expectedTypeReference != null"); + Debug.Assert(payloadType != null, "payloadType != null"); + Debug.Assert(expectedTypeReference.IsNonEntityCollectionType(), "This method only works on atomic collections."); + Debug.Assert(payloadType.IsNonEntityCollectionType(), "This method only works on atomic collections."); + + IEdmCollectionTypeReference collectionTypeReference = ValidationUtils.ValidateCollectionType(expectedTypeReference); + IEdmTypeReference expectedItemTypeReference = collectionTypeReference.GetCollectionItemType(); + if (expectedItemTypeReference != null && expectedItemTypeReference.IsODataComplexTypeKind()) + { + IEdmCollectionTypeReference payloadCollectionTypeReference = ValidationUtils.ValidateCollectionType(payloadType.ToTypeReference()); + IEdmTypeReference payloadItemTypeReference = payloadCollectionTypeReference.GetCollectionItemType(); + if (payloadItemTypeReference != null && payloadItemTypeReference.IsODataComplexTypeKind()) + { + // Note that this method is called from both strict and lax code paths, so we must not fail if the types are not related. + VerifyComplexType(expectedItemTypeReference, payloadItemTypeReference.Definition, /* failIfNotRelated */ false); + } + } + } + + /// + /// Conditionally creates the annotation to put on the read value in order to retain the type name from the payload. + /// + /// The payload type name. + /// The payload type. + /// The type reference into which we're going to parse. + /// The annotation to report to the reader for adding on the read value. + private static ODataTypeAnnotation CreateODataTypeAnnotation(string payloadTypeName, IEdmType payloadType, IEdmTypeReference targetTypeReference) + { + Debug.Assert(targetTypeReference != null, "targetTypeReference != null"); + + if (payloadType != null && !payloadType.IsEquivalentTo(targetTypeReference.Definition)) + { + return new ODataTypeAnnotation(payloadTypeName, payloadType); + } + + // Add the annotation with a null type name so that we know when the payload type is inferred from the expected type. + // This is useful when validating a payload that inserts a derived entity (that does not specify a payload type) into the entity set. + if (payloadType == null) + { + return new ODataTypeAnnotation(); + } + + return null; + } + + /// + /// Computes the type kind to be used to read the payload from the expected type, the payload type and + /// possibly the payload shape. + /// + /// The expected type reference used to read the payload value. + /// true when resolving a type name for a resource; false for a non-resource. + /// The type name read from the payload. + /// The type kind of the payload value. + /// Custom type resolver used by the client. + /// Whether ThrowIfTypeConflictsWithMetadata is enabled. + /// Whether primitive type conversion is disabled. + /// A func to determine the type kind of the value by analyzing the payload data. + /// The type kind to be used to read the payload. + private static EdmTypeKind ComputeTargetTypeKind( + IEdmTypeReference expectedTypeReference, + bool forResource, + string payloadTypeName, + EdmTypeKind payloadTypeKind, + Func clientCustomTypeResolver, + bool throwIfTypeConflictsWithMetadata, + bool enablePrimitiveTypeConversion, + Func typeKindFromPayloadFunc) + { + Debug.Assert(typeKindFromPayloadFunc != null, "typeKindFromPayloadFunc != null"); + + // If we have a type resolver, we always use the type returned by the resolver + // and use the expected type only for the resolution. + bool useExpectedTypeOnlyForTypeResolution = clientCustomTypeResolver != null && payloadTypeKind != EdmTypeKind.None; + + // Determine the target type kind + // If the EnablePrimitiveTypeConversion is off, we must not infer the type kind from the expected type + // but instead we need to read it from the payload. + // This is necessary to correctly fail on complex/collection as well as to correctly read spatial values. + EdmTypeKind expectedTypeKind = EdmTypeKind.None; + if (!useExpectedTypeOnlyForTypeResolution) + { + expectedTypeKind = ReaderUtils.GetExpectedTypeKind(expectedTypeReference, enablePrimitiveTypeConversion); + } + + EdmTypeKind targetTypeKind; + if (expectedTypeKind != EdmTypeKind.None) + { + // If we have an expected type, use that. + targetTypeKind = expectedTypeKind; + } + else + { + if (payloadTypeKind != EdmTypeKind.None) + { + // If we have a type kind based on the type name, use it. + if (!forResource) + { + ValidationUtils.ValidateValueTypeKind(payloadTypeKind, payloadTypeName); + } + + targetTypeKind = payloadTypeKind; + } + else + { + // If payloadTypeKind == EdmTypeKind.None, we could not determine the payload type kind + // because there was no type name in the payload; detect the type kind from the shape of the payload. + targetTypeKind = typeKindFromPayloadFunc(); + } + } + + Debug.Assert(targetTypeKind != EdmTypeKind.None, "We should have determined the target type kind by now."); + + if (ShouldValidatePayloadTypeKind( + clientCustomTypeResolver, + throwIfTypeConflictsWithMetadata, + enablePrimitiveTypeConversion, + expectedTypeReference, payloadTypeKind)) + { + ValidationUtils.ValidateTypeKind(targetTypeKind, expectedTypeReference.TypeKind(), null, payloadTypeName); + } + + return targetTypeKind; + } + + /// + /// Determines if the expect value type and the current settings mandate us to validate type kinds of payload values. + /// + /// Custom type resolver used by the client. + /// Whether ThrowIfTypeConflictsWithMetadata is enabled. + /// Whether primitive type conversion is enabled. + /// The expected type reference for the value inferred from the model. + /// The type kind of the payload value. + /// true if the payload value kind must be verified, false otherwise. + /// This method deals with the strict versus lax behavior, as well as with the behavior when primitive type conversion is disabled. + private static bool ShouldValidatePayloadTypeKind( + Func clientCustomTypeResolver, + bool throwIfTypeConflictsWithMetadata, + bool enablePrimitiveTypeConversion, + IEdmTypeReference expectedValueTypeReference, + EdmTypeKind payloadTypeKind) + { + // If we have a type resolver, we always use the type returned by the resolver + // and use the expected type only for the resolution. + bool useExpectedTypeOnlyForTypeResolution = clientCustomTypeResolver != null && payloadTypeKind != EdmTypeKind.None; + + // Type kind validation must happen when + // - ThrowIfTypeConflictsWithMetadata is set + // - Target type is primitive and primitive type conversion is disabled + // And the EnablePrimitiveTypeConversion overrides the ThrowIfTypeConflictsWithMetadata behavior. + // If there's no expected type, then there's nothing to validate against (open property). + return expectedValueTypeReference != null && + (throwIfTypeConflictsWithMetadata || + useExpectedTypeOnlyForTypeResolution || + (expectedValueTypeReference.IsODataPrimitiveTypeKind() && !enablePrimitiveTypeConversion)); + } + + /// + /// Validates that the specified allows null values. + /// + /// The expected type for the value, or null if no such type is available. + /// true to validate the null value; otherwise false. + /// The name of the property whose value is being read, if applicable (used for error reporting). + /// Indicates whether the property is dynamic or unknown. + private static void ValidateNullValueAllowed(IEdmTypeReference expectedValueTypeReference, bool validateNullValue, string propertyName, bool? isDynamicProperty) + { + if (validateNullValue && expectedValueTypeReference != null) + { + Debug.Assert( + expectedValueTypeReference.IsODataPrimitiveTypeKind() || + expectedValueTypeReference.IsODataTypeDefinitionTypeKind() || + expectedValueTypeReference.IsODataEnumTypeKind() || + expectedValueTypeReference.IsODataComplexTypeKind() || + expectedValueTypeReference.IsUntyped() || + expectedValueTypeReference.IsNonEntityCollectionType(), + "Only primitive, type definition, Enum, complex and collection types are supported by this method."); + + if (expectedValueTypeReference.IsODataPrimitiveTypeKind()) + { + // COMPAT 55: WCF DS allows null values for non-nullable properties + // For now ODataLib will always fail on null value when it is to be reported for a non-nullable property + // We should add a knob since WCF DS might need the different behavior. + if (!expectedValueTypeReference.IsNullable) + { + ThrowNullValueForNonNullableTypeException(expectedValueTypeReference, propertyName); + } + } + else if (expectedValueTypeReference.IsODataEnumTypeKind()) + { + if (!expectedValueTypeReference.IsNullable) + { + ThrowNullValueForNonNullableTypeException(expectedValueTypeReference, propertyName); + } + } + else if (expectedValueTypeReference.IsNonEntityCollectionType()) + { + if (isDynamicProperty != true) + { + ThrowNullValueForNonNullableTypeException(expectedValueTypeReference, propertyName); + } + } + else if (expectedValueTypeReference.IsODataComplexTypeKind()) + { + IEdmComplexTypeReference complexTypeReference = expectedValueTypeReference.AsComplex(); + if (!complexTypeReference.IsNullable) + { + ThrowNullValueForNonNullableTypeException(expectedValueTypeReference, propertyName); + } + } + else if (expectedValueTypeReference.IsUntyped()) + { + if (!expectedValueTypeReference.IsNullable) + { + ThrowNullValueForNonNullableTypeException(expectedValueTypeReference, propertyName); + } + } + } + } + + /// + /// Create and throw exception that a null value was found when the expected type is non-nullable. + /// + /// The expected type for this value. + /// The name of the property whose value is being read, if applicable. + private static void ThrowNullValueForNonNullableTypeException(IEdmTypeReference expectedValueTypeReference, string propertyName) + { + if (string.IsNullOrEmpty(propertyName)) + { + throw new ODataException(Strings.ReaderValidationUtils_NullValueForNonNullableType(expectedValueTypeReference.FullName())); + } + + throw new ODataException(Strings.ReaderValidationUtils_NullNamedValueForNonNullableType(propertyName, expectedValueTypeReference.FullName())); + } + + + /// + /// The validate resource set context uri. + /// + /// + /// The context uri parse result. + /// + /// + /// The scope. + /// + /// + /// The update scope. + /// + private static void ValidateResourceSetContextUri(ODataJsonLightContextUriParseResult contextUriParseResult, ODataReaderCore.Scope scope, bool updateScope) + { + // TODO: add validation logic for a resource set context uri + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidator.cs new file mode 100644 index 0000000..d6e799b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReaderValidator.cs @@ -0,0 +1,143 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using Microsoft.OData.Edm; + + /// + /// Reader validator that binds to an ODataMessageReaderSettings instance. + /// + internal class ReaderValidator : IReaderValidator + { + /// + /// References the bound ODataMessageReaderSettings instance. + /// + private readonly ODataMessageReaderSettings settings; + + /// + /// Constructs a ReaderValidator instance that binds to settings. + /// + /// The ODataMessageReaderSettings instance to bind to. + internal ReaderValidator(ODataMessageReaderSettings settings) + { + this.settings = settings; + } + + /// + /// Validates that the specified is a valid resource as per the specified type. + /// + /// The resource to validate. + /// Optional entity type to validate the resource against. + /// If the is available only resource-level tests are performed, + /// properties and such are not validated. + public virtual void ValidateMediaResource(ODataResourceBase resource, IEdmEntityType resourceType) + { + ValidationUtils.ValidateMediaResource(resource, resourceType); + } + + /// + /// Creates a PropertyAndAnnotationCollector instance. + /// + /// The created instance. + public PropertyAndAnnotationCollector CreatePropertyAndAnnotationCollector() + { + return new PropertyAndAnnotationCollector(settings.ThrowOnDuplicatePropertyNames); + } + + /// + /// Validate a null value. + /// + /// The expected type of the null value. + /// true to validate the null value; false to only check whether the type is + /// supported. + /// The name of the property whose value is being read, if applicable + /// (used for error reporting). + /// Indicates whether the property is dynamic or unknown. + public void ValidateNullValue(IEdmTypeReference expectedTypeReference, + bool validateNullValue, string propertyName, + bool? isDynamicProperty) + { + if (settings.ThrowIfTypeConflictsWithMetadata) + { + ReaderValidationUtils.ValidateNullValue(expectedTypeReference, settings.EnablePrimitiveTypeConversion, + validateNullValue, propertyName, isDynamicProperty); + } + } + + /// + /// Resolves and validates the payload type against the expected type and returns the target type. + /// + /// The expected type kind for the value. + /// This value indicates if a structured type is expected to be return. + /// True for structured type, false for non-structured type, null for indetermination. + /// The default payload type if none is specified in the payload; + /// for ATOM this is Edm.String, for JSON it is null since there is no payload type name for primitive types in the payload. + /// The expected type reference, or null if no expected type is available. + /// The payload type name, or null if no payload type was specified. + /// The model to use. + /// A func to compute the type kind from the payload shape if it could not be determined from the expected type or the payload type. + /// The target type kind to be used to read the payload. + /// Potentially non-null instance of an annotation to put on the value reported from the reader. + /// + /// The target type reference to use for parsing the value. + /// If there is no user specified model, this will return null. + /// If there is a user specified model, this method never returns null. + /// + /// + /// This method cannot be used for primitive type resolution. Primitive type resolution is format dependent and format specific methods should be used instead. + /// + public IEdmTypeReference ResolvePayloadTypeNameAndComputeTargetType( + EdmTypeKind expectedTypeKind, + bool? expectStructuredType, + IEdmType defaultPrimitivePayloadType, + IEdmTypeReference expectedTypeReference, + string payloadTypeName, + IEdmModel model, + Func typeKindFromPayloadFunc, + out EdmTypeKind targetTypeKind, + out ODataTypeAnnotation typeAnnotation) + { + return ReaderValidationUtils.ResolvePayloadTypeNameAndComputeTargetType( + expectedTypeKind, expectStructuredType, defaultPrimitivePayloadType, expectedTypeReference, payloadTypeName, model, + settings.ClientCustomTypeResolver, settings.ThrowIfTypeConflictsWithMetadata, + settings.EnablePrimitiveTypeConversion, + typeKindFromPayloadFunc, out targetTypeKind, out typeAnnotation); + } + + /// + /// Validates that a property with the specified name exists on a given structured type. + /// The structured type can be null if no metadata is available. + /// + /// The name of the property to validate. + /// The owning type of the property with name + /// or null if no metadata is available. + /// The instance representing the property with name + /// or null if no metadata is available. + public IEdmProperty ValidatePropertyDefined(string propertyName, + IEdmStructuredType owningStructuredType) + { + return ReaderValidationUtils.ValidatePropertyDefined(propertyName, owningStructuredType, this.settings.ThrowOnUndeclaredPropertyForNonOpenType); + } + + /// + /// Validates a stream reference property. + /// + /// The stream property to check. + /// The name of the property being checked. + /// The owning type of the stream property or null if no metadata is available. + /// The stream property defined by the model. + public void ValidateStreamReferenceProperty(IODataStreamReferenceInfo streamInfo, + string propertyName, + IEdmStructuredType structuredType, + IEdmProperty streamEdmProperty) + { + ReaderValidationUtils.ValidateStreamReferenceProperty( + streamInfo, propertyName, structuredType, streamEdmProperty, settings.ThrowOnUndeclaredPropertyForNonOpenType); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReferenceEqualityComparer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReferenceEqualityComparer.cs new file mode 100644 index 0000000..faeba0c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ReferenceEqualityComparer.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections.Generic; + #endregion Namespaces + + /// + /// Use this class to compare objects by reference in collections such as + /// dictionary or hashsets. + /// + /// Type of objects to compare. + /// + /// Typically accessed statically as eg + /// ReferenceEqualityComparer<Expression>.Instance. + /// + internal sealed class ReferenceEqualityComparer : IEqualityComparer where T : class + { + #region Private fields + /// + /// Single instance per 'T' for comparison. + /// + private static ReferenceEqualityComparer instance; + #endregion Private fields + + #region Constructors + /// + /// Initializes a new ReferenceEqualityComparer instance. + /// + private ReferenceEqualityComparer() + { + } + #endregion Constructors + + #region Properties. + /// + /// Returns a singleton instance for this comparer type. + /// + internal static ReferenceEqualityComparer Instance + { + get + { + if (instance == null) + { + ReferenceEqualityComparer newInstance = new ReferenceEqualityComparer(); + System.Threading.Interlocked.CompareExchange(ref instance, newInstance, null); + } + + return instance; + } + } + #endregion Properties. + + #region Methods. + /// + /// Determines whether two objects are the same. + /// + /// First object to compare. + /// Second object to compare. + /// true if both are the same; false otherwise. + public bool Equals(T x, T y) + { + return object.ReferenceEquals(x, y); + } + + /// + /// Serves as hashing function for collections. + /// + /// Object to hash. + /// + /// Hash code for the object; shouldn't change through the lifetime + /// of . + /// + public int GetHashCode(T obj) + { + return obj == null ? 0 : obj.GetHashCode(); + } + + #endregion Methods. + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ResourceSetWithoutExpectedTypeValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ResourceSetWithoutExpectedTypeValidator.cs new file mode 100644 index 0000000..0b11514 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ResourceSetWithoutExpectedTypeValidator.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Helper class to verify that all items of a collection are of the same kind and type. + /// + /// This class is only used if no expected item type is specified for the collection; + /// otherwise all items are already validated against the expected item type. + internal sealed class ResourceSetWithoutExpectedTypeValidator + { + /// + /// The base type for all items in the resource set. + /// + private IEdmType itemType; + + /// + /// Constructor. + /// + /// The type of the resource set, or null. + public ResourceSetWithoutExpectedTypeValidator(IEdmType memberType) + { + this.itemType = memberType; + } + + /// + /// Validates the type of a resource in a top-level resource set. + /// + /// The type of the resource. + internal void ValidateResource(IEdmType itemType) + { + if (this.itemType == null || this.itemType.TypeKind == EdmTypeKind.Untyped) + { + return; + } + + // Validate the expected and actual types. + if (this.itemType.IsEquivalentTo(itemType)) + { + return; + } + + IEdmStructuredType structuredType = itemType as IEdmStructuredType; + IEdmStructuredType thisStructuredType = this.itemType as IEdmStructuredType; + + if (structuredType == null || thisStructuredType == null) + { + throw new ODataException(Strings.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(itemType.FullTypeName(), this.itemType.FullTypeName())); + } + + // Make sure the resource types is same or derived type of expected type + if (!this.itemType.IsAssignableFrom(itemType)) + { + throw new ODataException(Strings.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(itemType.FullTypeName(), this.itemType.FullTypeName())); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/SelectedPropertiesNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/SelectedPropertiesNode.cs new file mode 100644 index 0000000..75f12f1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/SelectedPropertiesNode.cs @@ -0,0 +1,870 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Metadata; + using Microsoft.OData.UriParser; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Represents a tree of selected properties based on the $select query option. + /// + /// + /// When reading, it controls the template expansion in JSON Light. + /// + internal sealed class SelectedPropertiesNode + { + /// Singleton which indicates that the nothing is selected. + internal static readonly SelectedPropertiesNode Empty = new SelectedPropertiesNode(SelectionType.Empty); + + /// Singleton which indicates that the entire subtree is selected. + internal static readonly SelectedPropertiesNode EntireSubtree = new SelectedPropertiesNode(SelectionType.EntireSubtree); + + /// An empty set of stream properties to return when nothing is selected. + private static readonly Dictionary EmptyStreamProperties = new Dictionary(StringComparer.Ordinal); + + /// An empty set of navigation properties to return when nothing is selected. + private static readonly IEnumerable EmptyNavigationProperties = Enumerable.Empty(); + + /// The type of the current node. + private SelectionType selectionType = SelectionType.PartialSubtree; + + /// + /// Boolean flag indicating whether this is an expanded navigation property. + /// + internal readonly bool isExpandedNavigationProperty; + + /// The Edm structured type of the current node. + private IEdmStructuredType structuredType; + + /// The Edm model. + private IEdmModel edmModel; + + /// The separator character used to separate property names in a path. + private const char PathSeparator = '/'; + + /// The separator character used to separate paths from each other. + private const char ItemSeparator = ','; + + /// The special '*' segment indicating that all properties are selected. + private const string StarSegment = "*"; + + /// The list of selected properties at the current level. + private HashSet selectedProperties; + + /// A dictionary of property name to child nodes. + private Dictionary children; + + /// Indicates that this node had a wildcard selection and all properties at this level should be reported. + private bool hasWildcard; + + /// Current node name, null if root node. + private string nodeName; + + /// + /// Constructor. + /// + /// The string representation of the selected property hierarchy using + /// the same format as in the $select query option. + /// The Edm structured type of this node. + /// The Edm model. + internal SelectedPropertiesNode(string selectClause, IEdmStructuredType structuredType, IEdmModel edmModel) + : this(SelectionType.PartialSubtree) + { + Debug.Assert(!string.IsNullOrEmpty(selectClause), "!string.IsNullOrEmpty(selectClause)"); + + this.structuredType = structuredType; + this.edmModel = edmModel; + + // NOTE: The select clause is a list of paths and parenthesized expand tokens separated by comma (','). + // E.g, Accounts('abc')?$expand=User($select(FirstName, LastName)), for the case of projected expanded entity. + this.Parse(selectClause); + } + + private void Parse(string selectClause) + { + string[] paths = GetTopLevelItems(selectClause); + Debug.Assert(paths.Length > 0, "Paths should never be empty"); + foreach (string t in paths) + { + string[] segments = null; + int idxLP = t.IndexOf('('); + if (-1 == idxLP) + { + segments = t.Split(PathSeparator); + } + else + { + // Take the substring before the left parenthesis and get the path segments. + segments = t.Substring(0, idxLP).Split(PathSeparator); + + // Add parenthesized part to the last path segment. + segments[segments.Length - 1] += t.Substring(idxLP); + } + + this.ParsePathSegment(segments, 0); + } + + DetermineSelectionType(); + } + + private void DetermineSelectionType() + { + if (this.children != null) + { + foreach (SelectedPropertiesNode childNode in this.children.Values) + { + childNode.DetermineSelectionType(); + } + } + + if ((this.selectedProperties == null || this.selectedProperties.Count == 0) + && (this.children == null || this.children.Values.All(n => n.selectionType == SelectionType.EntireSubtree))) + { + this.selectionType = SelectionType.EntireSubtree; + } + } + + private static string[] GetTopLevelItems(string selectClause) + { + List result = new List(); + int parenBalance = 0; + int startIdx = 0; + char[] chars = selectClause.ToCharArray(); + for (int i = 0; i < chars.Length; i++) + { + switch (chars[i]) + { + case '(': + ++parenBalance; + break; + case ')': + --parenBalance; + break; + case ItemSeparator: + if (parenBalance == 0) + { + string item = selectClause.Substring(startIdx, i - startIdx); + if (item.Length != 0) + { + // Add non-empty item only. + result.Add(item); + } + startIdx = i + 1; + } + break; + } + } + + if (startIdx < chars.Length) + { + // Add the last item, which is non-empty. + result.Add(selectClause.Substring(startIdx, chars.Length - startIdx)); + } + return result.ToArray(); + } + + /// + /// Prevents a default instance of the class from being created. + /// + /// Type of the selection. + internal SelectedPropertiesNode(SelectionType selectionType) + :this(selectionType, /*isExpandedNavigationProperty*/ false) + { + } + + /// + /// Prevents a default instance of the class from being created. + /// + /// Type of the selection. + /// Boolean flag indicating whether this is an expanded navigation property. + private SelectedPropertiesNode(SelectionType selectionType, bool isExpandedNavigationProperty) + { + this.selectionType = selectionType; + this.isExpandedNavigationProperty = isExpandedNavigationProperty; + } + + /// + /// Enum representing the different special cases of selection. + /// + internal enum SelectionType + { + /// + /// Represents the case where no properties are selected. + /// + Empty = 0, + + /// + /// Represents the case where an entire subtree is selected. + /// + EntireSubtree = 1, + + /// + /// The normal case where a partial subtree has been selected. + /// + PartialSubtree = 2, + } + + /// + /// Creates a node from the given raw $select query option value, structural type information and service model. + /// + /// The value of the $select query option. + /// The structured type of this node. + /// The Edm model. + /// A tree representation of the selected properties specified in the query option. + internal static SelectedPropertiesNode Create(string selectQueryOption, IEdmStructuredType structuredType, IEdmModel edmModel) + { + if (selectQueryOption == null) + { + return new SelectedPropertiesNode(SelectionType.EntireSubtree); + } + + selectQueryOption = selectQueryOption.Trim(); + + // NOTE: an empty select query option is interpreted as not selecting anything. + if (selectQueryOption.Length == 0) + { + return Empty; + } + + return new SelectedPropertiesNode(selectQueryOption, structuredType, edmModel); + } + + /// + /// Creates a node from the given raw $select query option value without structural type information. + /// + /// The value of the $select query option. + /// A tree representation of the selected properties specified in the query option. + internal static SelectedPropertiesNode Create(string selectQueryOption) + { + return Create(selectQueryOption, /*structuredType*/ null, /*edmModel*/ null); + } + + /// + /// Creates a node from the given SelectExpandClause. + /// + /// The value of the $select query option. + /// A tree representation of the selected properties specified in the query option. + internal static SelectedPropertiesNode Create(SelectExpandClause selectExpandClause) + { + if (selectExpandClause.AllSelected + && selectExpandClause.SelectedItems.OfType().All(_ => _.SelectAndExpand.AllSelected)) + { + // All items are selected and all expanded entities are all-selected. + return new SelectedPropertiesNode(SelectionType.EntireSubtree); + } + + return CreateFromSelectExpandClause(selectExpandClause); + } + + /// + /// Recursively combines the left and right nodes. Used when there are type segments present in the select paths which + /// causes there to be multiple children for the same property/navigation. + /// + /// The left node. + /// The right node. + /// The combined node. + internal static SelectedPropertiesNode CombineNodes(SelectedPropertiesNode left, SelectedPropertiesNode right) + { + Debug.Assert(left != null, "left != null"); + Debug.Assert(right != null, "right != null"); + + // if either one includes the entire subtree, then so does the result + if (left.selectionType == SelectionType.EntireSubtree || right.selectionType == SelectionType.EntireSubtree) + { + return new SelectedPropertiesNode(SelectionType.EntireSubtree); + } + + // if the left hand side is empty, then use the right hand side + if (left.selectionType == SelectionType.Empty) + { + // even if this is empty too, this all works + return right; + } + + // likewise, if the right hand side is empty, use the left + if (right.selectionType == SelectionType.Empty) + { + return left; + } + + Debug.Assert(left.selectionType == SelectionType.PartialSubtree, "left.selectionType == SelectionType.PartialSubtree"); + Debug.Assert(right.selectionType == SelectionType.PartialSubtree, "right.selectionType == SelectionType.PartialSubtree"); + + var combined = new SelectedPropertiesNode(SelectionType.PartialSubtree) + { + hasWildcard = left.hasWildcard | right.hasWildcard + }; + + // copy over selected properties, combining as needed + if (left.selectedProperties != null && right.selectedProperties != null) + { + combined.selectedProperties = CreateSelectedPropertiesHashSet(left.selectedProperties.AsEnumerable().Concat(right.selectedProperties)); + } + else if (left.selectedProperties != null) + { + combined.selectedProperties = CreateSelectedPropertiesHashSet(left.selectedProperties); + } + else if (right.selectedProperties != null) + { + combined.selectedProperties = CreateSelectedPropertiesHashSet(right.selectedProperties); + } + + // copy over children, combining as needed + if (left.children != null && right.children != null) + { + combined.children = new Dictionary(left.children); + foreach (var child in right.children) + { + SelectedPropertiesNode fromLeft; + if (combined.children.TryGetValue(child.Key, out fromLeft)) + { + combined.children[child.Key] = CombineNodes(fromLeft, child.Value); + } + else + { + combined.children[child.Key] = child.Value; + } + } + } + else if (left.children != null) + { + combined.children = new Dictionary(left.children); + } + else if (right.children != null) + { + combined.children = new Dictionary(right.children); + } + + return combined; + } + + /// + /// Gets the selected properties node for the specified navigation property. + /// + /// The current structured type. + /// The name of the navigation property. + /// The selected properties node for the property with name . + internal SelectedPropertiesNode GetSelectedPropertiesForNavigationProperty(IEdmStructuredType structuredType, string navigationPropertyName) + { + if (this.selectionType == SelectionType.Empty) + { + return Empty; + } + + if (this.selectionType == SelectionType.EntireSubtree) + { + return new SelectedPropertiesNode(SelectionType.EntireSubtree); + } + + // We cannot determine the selected navigation properties without the user model. This means we won't be computing the missing navigation properties. + // For reading we will report what's on the wire and for writing we just write what the user explicitly told us to write. + if (structuredType == null) + { + return new SelectedPropertiesNode(SelectionType.EntireSubtree); + } + + // $select=Orders will include the entire subtree when there are no same expanded entity. + if (this.selectedProperties != null && this.selectedProperties.Contains(navigationPropertyName) && + (this.children == null || !this.children.Any(n => n.Key.Equals(navigationPropertyName) && n.Value.isExpandedNavigationProperty))) + { + return new SelectedPropertiesNode(SelectionType.EntireSubtree); + } + + if (this.children != null) + { + SelectedPropertiesNode child; + + // try to find an immediate child. + if (!this.children.TryGetValue(navigationPropertyName, out child)) + { + child = Empty; + } + + // try to find a child with a type segment before it that matches the current type. + // Note: the result of this aggregation will be either empty or a found child node. + return this.GetMatchingTypeSegments(structuredType) + .Select(typeSegmentChild => typeSegmentChild.GetSelectedPropertiesForNavigationProperty(structuredType, navigationPropertyName)) + .Aggregate(child, CombineNodes); + } + + // $select=* will include Orders, but none of its properties + return Empty; + } + + /// + /// Gets the selected navigation properties for the current node. + /// + /// The current structured type. + /// The set of selected navigation properties. + internal IEnumerable GetSelectedNavigationProperties(IEdmStructuredType structuredType) + { + if (this.selectionType == SelectionType.Empty) + { + return EmptyNavigationProperties; + } + + // We cannot determine the selected navigation properties without the user model. This means we won't be computing the missing navigation properties. + // For reading we will report what's on the wire and for writing we just write what the user explicitly told us to write. + if (structuredType == null) + { + return EmptyNavigationProperties; + } + + if (this.selectionType == SelectionType.EntireSubtree || this.hasWildcard + || ((this.selectedProperties == null || this.selectedProperties.Count() == 0) && this.children.Values.All( n => n.isExpandedNavigationProperty))) + { + return structuredType.NavigationProperties(); + } + + // Find all the selected navigation properties + // NOTE: the assumption is that the number of selected properties usually is a lot smaller + // than the number of all properties on the type and that FindProperty for each selected + // property is faster than iterating through all the properties on the type. + IEnumerable navigationPropertyNames = this.selectedProperties ?? CreateSelectedPropertiesHashSet(); + if (this.children != null) + { + navigationPropertyNames = this.children.Keys.Concat(navigationPropertyNames); + } + + IEnumerable selectedNavigationProperties = navigationPropertyNames + .Select(structuredType.FindProperty) + .OfType(); + + // gather up the selected navigations from any child nodes that have type segments matching the current type and append them. + foreach (SelectedPropertiesNode typeSegmentChild in this.GetMatchingTypeSegments(structuredType)) + { + selectedNavigationProperties = selectedNavigationProperties.Concat(typeSegmentChild.GetSelectedNavigationProperties(structuredType)); + } + + // ensure no duplicates are returned. + return selectedNavigationProperties.Distinct(); + } + + /// + /// Gets the selected stream properties for the current node. + /// + /// The current entity type. + /// The selected stream properties. + internal IDictionary GetSelectedStreamProperties(IEdmEntityType entityType) + { + if (this.selectionType == SelectionType.Empty) + { + return EmptyStreamProperties; + } + + // We cannot determine the selected stream properties without the user model. This means we won't be computing the missing stream properties. + // For reading we will report what's on the wire and for writing we just write what the user explicitly told us to write. + if (entityType == null) + { + return EmptyStreamProperties; + } + + if (this.selectionType == SelectionType.EntireSubtree || this.hasWildcard) + { + return entityType.StructuralProperties().Where(sp => sp.Type.IsStream()).ToDictionary(sp => sp.Name, StringComparer.Ordinal); + } + + IDictionary selectedStreamProperties = + this.selectedProperties == null ? + new Dictionary() : + this.selectedProperties + .Select(entityType.FindProperty) + .OfType() + .Where(p => p.Type.IsStream()) + .ToDictionary(p => p.Name, StringComparer.Ordinal); + + // gather up the selected stream from any child nodes that have type segments matching the current type and add them to the dictionary. + foreach (SelectedPropertiesNode typeSegmentChild in this.GetMatchingTypeSegments(entityType)) + { + var streamPropertiesForTypeSegment = typeSegmentChild.GetSelectedStreamProperties(entityType); + foreach (var kvp in streamPropertiesForTypeSegment) + { + selectedStreamProperties[kvp.Key] = kvp.Value; + } + } + + return selectedStreamProperties; + } + + /// + /// Determines whether or not the given operation is selected and takes type-segments into account. + /// + /// The current resource type. + /// The operation. + /// Whether or not the operation name must be container qualified in the $select string. + /// + /// true if the operation is selected; otherwise, false. + /// + internal bool IsOperationSelected(IEdmStructuredType structuredType, IEdmOperation operation, bool mustBeNamespaceQualified) + { + Debug.Assert(structuredType != null, "structuredType != null"); + Debug.Assert(operation != null, "operation != null"); + + // If the operation name conflicts with a property name then it must be namespace qualified + mustBeNamespaceQualified = mustBeNamespaceQualified || structuredType.FindProperty(operation.Name) != null; + + return this.IsOperationSelectedAtThisLevel(operation, mustBeNamespaceQualified) || this.GetMatchingTypeSegments(structuredType).Any(typeSegment => typeSegment.IsOperationSelectedAtThisLevel(operation, mustBeNamespaceQualified)); + } + + /// + /// Returns whether the selection type of this node is SelectionType.EntireSubtree. + /// + /// true if entire subtree is selected; otherwise false. + internal bool IsEntireSubtree() + { + return this.selectionType == SelectionType.EntireSubtree; + } + + /// + /// Gets an enumerable containing the given type and all of its base/ancestor types. + /// + /// The starting resource type. Will be included in the returned enumeration. + /// An enumerable containing the given type and all of its base/ancestor types. + private static IEnumerable GetBaseTypesAndSelf(IEdmStructuredType structuredType) + { + for (IEdmStructuredType currentType = structuredType; currentType != null; currentType = currentType.BaseType()) + { + yield return currentType; + } + } + + /// + /// Creates a new hash set for storing the names of selected properties. + /// + /// The initial set of selected properties to store in the hash set. + /// The hash set. + private static HashSet CreateSelectedPropertiesHashSet(IEnumerable properties) + { + HashSet propertiesHashSet = CreateSelectedPropertiesHashSet(); + + // doing this so that it works on platforms that don't have the constructor parameter. + foreach (var property in properties) + { + propertiesHashSet.Add(property); + } + + return propertiesHashSet; + } + + /// + /// Creates a new hash set for storing the names of selected properties. + /// + /// The hash set. + private static HashSet CreateSelectedPropertiesHashSet() + { + return new HashSet(StringComparer.Ordinal); + } + + /// + /// Gets the possible identifiers that could cause the given operation to be selected. + /// + /// The operation. + /// Whether the operations must be namespace qualified. + /// The identifiers to look for in the $select string when determining if this action is selected. + private static IEnumerable GetPossibleMatchesForSelectedOperation(IEdmOperation operation, bool mustBeNamespaceQualified) + { + string operationName = operation.Name; + string operationNameWithParameters = operation.NameWithParameters(); + + // if the operation is defined on an open type, it will need to be container qualified, so skip over the unqualified names. + if (!mustBeNamespaceQualified) + { + // first, try matching just the name. If there are multiple overloads, this intentionally matches all of them. + yield return operationName; + + // then, try matching the name with parameters. This would refer to a specific overload. + yield return operationNameWithParameters; + } + + // last, try matching wildcards and specific names, but qualified with the namespace-qualified name. + string qualifiedContainerName = operation.Namespace + "."; + yield return qualifiedContainerName + StarSegment; + yield return qualifiedContainerName + operationName; + yield return qualifiedContainerName + operationNameWithParameters; + } + + private bool IsValidExpandToken(string item) + { + // Expand token must contain '(' and end with ')', must not contain a dot, and must be related to navigation property. + int idxLP = item.IndexOf('('); + if (idxLP == -1 + || !item.EndsWith(")", StringComparison.Ordinal) + || !IsNavigationPropertyToken(item.Substring(0, idxLP))) + { + // selected item is not properly parenthesized, or is an operation token. + return false; + } + + return true; + } + + /// + /// Checks the specified model and structuredType and see whether the token can be resolved to a navigation property name. + /// + /// The token to check. + /// true if token can be resolved to a navigation property; false otherwise. + private bool IsNavigationPropertyToken(string token) + { + const char nameSpaceSeparator = '.'; + + /* Decision tree: + #1. if the name contains a dot => it's not a navigation property + #2. otherwise, if it matches a defined navigation property => treat it as a navigation property + #3. otherwise, if it matches an unqualified bound operation name => it's not a navigation property + #4. otherwise, it's a navigation property + */ + + // For better readability, set the value in if-else branches corresponding to decision tree above. + bool found; + if (token.IndexOf(nameSpaceSeparator) != -1) + { + // #1 + found = false; + } + else if (this.structuredType == null || this.structuredType.NavigationProperties().Any(_ => _.Name.Equals(token, StringComparison.Ordinal))) + { + // #2 + // Note that action and function names in a contextUrl *SHOULD* always be qualified, + // So if we can't validate against the structured type, should assume it's a nav prop + found = true; + } + else if (this.edmModel != null && this.edmModel.FindBoundOperations(this.structuredType).Any(op => op.Name.Equals(token, StringComparison.Ordinal))) + { + //#3 + found = false; + } + else + { + // #4 + found = true; + } + + return found; + } + + /// + /// Gets the matching type segments for the given type based on this node's children. + /// + /// The structured type to match. + /// All child nodes which start with a type segment in the given types hierarchy. + private IEnumerable GetMatchingTypeSegments(IEdmStructuredType structuredType) + { + if (this.children != null) + { + foreach (IEdmStructuredType currentType in GetBaseTypesAndSelf(structuredType)) + { + SelectedPropertiesNode typeSegmentChild; + if (this.children.TryGetValue(currentType.FullTypeName(), out typeSegmentChild)) + { + if (typeSegmentChild.hasWildcard) + { + throw new ODataException(ODataErrorStrings.SelectedPropertiesNode_StarSegmentAfterTypeSegment); + } + + yield return typeSegmentChild; + } + } + } + } + + /// + /// Parses the segments of a path in the select clause. + /// + /// The segments of the select path. + /// The index of the segment to parse. + private void ParsePathSegment(string[] segments, int index) + { + Debug.Assert(segments != null, "segments != null"); + Debug.Assert(index >= 0 && index < segments.Length, "index >= 0 && index < segments.Length"); + + // NOTE: Each path is the name of a property or a series of property names + // separated by slash ('/'). The special star ('*') character is only supported at the end of a path. + string currentSegment = segments[index].Trim(); + bool isStar = string.CompareOrdinal(StarSegment, currentSegment) == 0; + int idxLP = currentSegment.IndexOf('('); + + if (idxLP != -1 && IsValidExpandToken(currentSegment)) + { + string token = currentSegment.Substring(0, idxLP); + SelectedPropertiesNode childNode = this.EnsureChildNode(token, /* isExpandedNavigationProperty */ true); + childNode.edmModel = this.edmModel; + + if (idxLP < currentSegment.Length - 2) + { + string clause = currentSegment.Substring(idxLP + 1, currentSegment.Length - idxLP - 2).Trim(); + if (!String.IsNullOrEmpty(clause)) + { + // Setup the edm model and structured type for the child node before start parsing the select clause. + IEdmNavigationProperty navProp = this.structuredType?.DeclaredNavigationProperties() + ?.SingleOrDefault(p => p.Name.Equals(token, StringComparison.Ordinal)); + + if (navProp?.Type != null) + { + // navigation property could be structural type or collection of structural type. + childNode.structuredType = navProp.Type.Definition.AsElementType() as IEdmStructuredType; + } + + childNode.Parse(clause); + } + } + else + { + childNode.selectionType = SelectionType.EntireSubtree; + } + } + else + { + bool isLastSegment = index == segments.Length - 1; + if (!isLastSegment) + { + if (isStar) + { + throw new ODataException(ODataErrorStrings.SelectedPropertiesNode_StarSegmentNotLastSegment); + } + + SelectedPropertiesNode childNode = this.EnsureChildNode(currentSegment, false); + childNode.ParsePathSegment(segments, index + 1); + } + else + { + if (this.selectedProperties == null) + { + this.selectedProperties = CreateSelectedPropertiesHashSet(); + } + + this.selectedProperties.Add(currentSegment); + } + } + + this.hasWildcard |= isStar; + } + + /// + /// Ensures that a child node for the specified segment name already exists; if not creates one. + /// + /// The segment name to get the child node for. + /// Boolean flag indicating whether this is an expanded navigation property. + + /// The existing or newly created child node for the . + private SelectedPropertiesNode EnsureChildNode(string segmentName, bool isExpandedNavigationProperty) + { + Debug.Assert(segmentName != null, "segmentName != null"); + + if (this.children == null) + { + this.children = new Dictionary(StringComparer.Ordinal); + } + + SelectedPropertiesNode childNode; + if (!this.children.TryGetValue(segmentName, out childNode)) + { + childNode = new SelectedPropertiesNode(SelectionType.PartialSubtree, isExpandedNavigationProperty); + this.children.Add(segmentName, childNode); + } + + return childNode; + } + + /// + /// Determines whether or not the given operation is selected without taking type segments into account. + /// + /// The operation. + /// Whether the operations must be container qualified. + /// + /// true if the operation is selected; otherwise, false. + /// + private bool IsOperationSelectedAtThisLevel(IEdmOperation operation, bool mustBeNamespaceQualified) + { + Debug.Assert(operation != null, "operation != null"); + + if (this.selectionType == SelectionType.EntireSubtree) + { + return true; + } + + if (this.selectionType == SelectionType.Empty || this.selectedProperties == null ) + { + return false; + } + + return GetPossibleMatchesForSelectedOperation(operation, mustBeNamespaceQualified).Any(possibleMatch => this.selectedProperties.Contains(possibleMatch)); + } + + /// Create SelectedPropertiesNode from SelectExpandClause. + /// The SelectExpandClause representing $select and $expand clauses. + /// SelectedPropertiesNode generated using + private static SelectedPropertiesNode CreateFromSelectExpandClause(SelectExpandClause selectExpandClause) + { + SelectedPropertiesNode node; + selectExpandClause.Traverse(ProcessSubExpand, CombineSelectAndExpandResult, out node); + return node; + } + + /// Process sub expand node, set name for the node. + /// Node name for the subexpandnode. + /// Generated sub expand node. + /// The sub expanded node passed in. + private static SelectedPropertiesNode ProcessSubExpand(string nodeName, SelectedPropertiesNode subExpandNode) + { + if (subExpandNode != null) + { + subExpandNode.nodeName = nodeName; + } + + return subExpandNode; + } + + /// Create SelectedPropertiesNode using selected name list and expand node list. + /// An enumerable of selected item names. + /// An enumerable of sub expanded nodes. + /// The generated SelectedPropertiesNode. + private static SelectedPropertiesNode CombineSelectAndExpandResult(IEnumerable selectList, IEnumerable expandList) + { + List rawSelect = selectList.ToList(); + rawSelect.RemoveAll(expandList.Select(m => m.nodeName).Contains); + + if (rawSelect.Count == 0 && expandList.All( n => n.IsEntireSubtree())) + { + return new SelectedPropertiesNode(SelectionType.EntireSubtree); + } + + SelectedPropertiesNode node = new SelectedPropertiesNode(SelectionType.PartialSubtree) + { + selectedProperties = rawSelect.Count > 0 ? CreateSelectedPropertiesHashSet() : null, + children = new Dictionary(StringComparer.Ordinal) + }; + + foreach (string selectItem in rawSelect) + { + if (StarSegment == selectItem) + { + node.hasWildcard = true; + } + else + { + node.selectedProperties.Add(selectItem); + } + } + + foreach (var expandItem in expandList) + { + node.children[expandItem.nodeName] = expandItem; + } + + return node; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceLifetime.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceLifetime.cs new file mode 100644 index 0000000..ec3668b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceLifetime.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Enumerates all kinds of lifetime of a service in an . + /// + public enum ServiceLifetime + { + /// + /// Indicates that a single instance of the service will be created. + /// + Singleton, + + /// + /// Indicates that a new instance of the service will be created for each scope. + /// + Scoped, + + /// + /// Indicates that a new instance of the service will be created every time it is requested. + /// + Transient + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServicePrototype.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServicePrototype.cs new file mode 100644 index 0000000..12eb3b7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServicePrototype.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; + +namespace Microsoft.OData +{ + internal class ServicePrototype + { + public ServicePrototype(TService instance) + { + Debug.Assert(instance != null, "instance != null"); + + this.Instance = instance; + } + + public TService Instance { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceProviderExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceProviderExtensions.cs new file mode 100644 index 0000000..e9ede7d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ServiceProviderExtensions.cs @@ -0,0 +1,107 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.OData +{ + /// + /// Extension methods for . + /// + internal static class ServiceProviderExtensions + { + /// + /// Gets a service of type from the . + /// + /// The type of service object to get. + /// The to retrieve the service object from. + /// A service object of type or null if there is no such service. + public static T GetService(this IServiceProvider container) + { + Debug.Assert(container != null, "container != null"); + + return (T)container.GetService(typeof(T)); + } + + /// + /// Gets a service of type from the . + /// + /// The to retrieve the service object from. + /// An object that specifies the type of service object to get. + /// A service object of type . + /// There is no service of type . + public static object GetRequiredService(this IServiceProvider container, Type serviceType) + { + Debug.Assert(container != null, "container != null"); + Debug.Assert(serviceType != null, "serviceType != null"); + + var service = container.GetService(serviceType); + if (service == null) + { + throw new ODataException(Strings.ServiceProviderExtensions_NoServiceRegistered(serviceType)); + } + + return service; + } + + /// + /// Gets a service of type from the . + /// + /// The type of service object to get. + /// The to retrieve the service object from. + /// A service object of type . + /// There is no service of type . + public static T GetRequiredService(this IServiceProvider container) + { + Debug.Assert(container != null, "container != null"); + + return (T)container.GetRequiredService(typeof(T)); + } + + /// + /// Gets an enumeration of services of type from the . + /// + /// The type of service object to get. + /// The to retrieve the services from. + /// An enumeration of services of type . + public static IEnumerable GetServices(this IServiceProvider container) + { + Debug.Assert(container != null, "container != null"); + + return container.GetRequiredService>(); + } + + /// + /// Gets an enumeration of services of type from the . + /// + /// The to retrieve the services from. + /// An object that specifies the type of service object to get. + /// An enumeration of services of type . + public static IEnumerable GetServices(this IServiceProvider container, Type serviceType) + { + Debug.Assert(container != null, "container != null"); + Debug.Assert(serviceType != null, "serviceType != null"); + + var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType); + return (IEnumerable)container.GetRequiredService(genericEnumerable); + } + + /// + /// Gets the service prototype of type from the . + /// + /// The type of service prototype to get. + /// The to retrieve the services from. + /// The service prototype of type . + public static TService GetServicePrototype(this IServiceProvider container) + { + Debug.Assert(container != null, "container != null"); + + return container.GetRequiredService>().Instance; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ShippingAssemblyAttributes.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ShippingAssemblyAttributes.cs new file mode 100644 index 0000000..05ed7a3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ShippingAssemblyAttributes.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma warning disable 436 +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Client.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.OData.TDD.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.OData.User.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.OData.Query.TDD.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.Taupo.OData.Scenario.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.Taupo.OData.Query.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.Taupo.OData.WCFService" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.Taupo.OData.Reader.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.Taupo.OData.Writer.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.Taupo.OData" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Core.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Client.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Test.OData.DependencyInjection.NetCore" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Core.Tests.NetCore" + AssemblyRef.TestPublicKey)] +#pragma warning restore 436 diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/SimpleLazy.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/SimpleLazy.cs new file mode 100644 index 0000000..eef1c26 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/SimpleLazy.cs @@ -0,0 +1,109 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#endif +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#endif +#if !ODATA_CLIENT && !ODATA_SERVICE +namespace Microsoft.OData +#endif +{ + using System; + using System.Diagnostics; + + /// + /// A simple implementation of LazyOfT since the framework version is not available in all platforms we compile this code in... + /// + /// Type to lazy create. + internal sealed class SimpleLazy + { + /// + /// For thread safty in creating the value. + /// + private readonly object mutex; + + /// + /// The factory method to create the lazy instance. + /// + private readonly Func factory; + + /// + /// Holds the lazy instance to create. + /// + private T value; + + /// + /// true if the factory method has been called, false otherwise. + /// + private bool valueCreated; + + /// + /// Creates an instance of ODataLazyOfT. + /// + /// The factory method to create the lazy instance. + internal SimpleLazy(Func factory) + : this(factory, false) + { + } + + /// + /// Creates an instance of ODataLazyOfT. + /// + /// The factory method to create the lazy instance. + /// true if the value will be created in a thread safety, false assume single thread access to Value. + internal SimpleLazy(Func factory, bool isThreadSafe) + { + Debug.Assert(factory != null, "factory != null"); + this.factory = factory; + this.valueCreated = false; + if (isThreadSafe) + { + this.mutex = new object(); + } + } + + /// + /// Creates the value if it hasn't already been created and returns the created value. + /// + internal T Value + { + get + { + if (!this.valueCreated) + { + if (this.mutex != null) + { + lock (this.mutex) + { + if (!this.valueCreated) + { + this.CreateValue(); + } + } + } + else + { + this.CreateValue(); + } + } + + return this.value; + } + } + + /// + /// Creates the value. + /// + private void CreateValue() + { + this.value = this.factory(); + this.valueCreated = true; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/StreamExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/StreamExtensions.cs new file mode 100644 index 0000000..db20fc2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/StreamExtensions.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.IO; + + /// + /// Extension methods to Stream for .NET35. + /// + internal static class StreamExtensions + { +#if !PORTABLELIB + private static int BUFFER_SIZE = 81920; + private static byte[] buffer = null; + + /// Extension method to copy one stream to another + /// Stream to copy from + /// Stream to copy to + internal static void CopyTo(this Stream source, Stream target) + { + if (buffer == null) + { + buffer = new byte[BUFFER_SIZE]; + } + + int bytesRead; + while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) + { + target.Write(buffer, 0, bytesRead); + } + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/TaskUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/TaskUtils.cs new file mode 100644 index 0000000..0b085f3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/TaskUtils.cs @@ -0,0 +1,853 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if PORTABLELIB || ODATA_CLIENT +#if ODATA_CORE +namespace Microsoft.OData +#else +namespace Microsoft.OData.Client +#endif +{ +#region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; +#if ODATA_CLIENT + using ExceptionUtils = CommonUtil; +#endif + #endregion Namespaces + + /// + /// Class with utility methods for working with and implementing Task based APIs + /// + internal static class TaskUtils + { +#region Completed task + /// + /// Already completed task. + /// + private static Task completedTask; + + /// + /// Returns already completed task instance. + /// + internal static Task CompletedTask + { + get + { + // Note that in case of two threads competing here we would create two completed tasks, but only one + // will be stored in the static variable. In any case, they are identical for all other purposes, + // so it doesn't matter which one wins + if (completedTask == null) + { + // Create a TaskCompletionSource - since there's no non-generic version use a dummy one + // and then cast to the non-generic version. + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + taskCompletionSource.SetResult(null); + completedTask = taskCompletionSource.Task; + } + + return completedTask; + } + } + + /// + /// Returns an already completed task instance with the specified result. + /// + /// Type of the result. + /// The value of the result. + /// An already completed task with the specified result. + internal static Task GetCompletedTask(T value) + { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + taskCompletionSource.SetResult(value); + return taskCompletionSource.Task; + } + #endregion + +#region Faulted task + /// + /// Returns an already completed task instance with the specified error. + /// + /// The exception of the faulted result. + /// An already completed task with the specified exception. + internal static Task GetFaultedTask(Exception exception) + { + // Since there's no non-generic version use a dummy object return value and cast to non-generic version. + return GetFaultedTask(exception); + } + + /// + /// Returns an already completed task instance with the specified error. + /// + /// Type of the result. + /// The exception of the faulted result. + /// An already completed task with the specified exception. + internal static Task GetFaultedTask(Exception exception) + { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + taskCompletionSource.SetException(exception); + return taskCompletionSource.Task; + } + #endregion + +#region GetTaskForSynchronousOperation + /// + /// Returns an already completed task for the specified synchronous operation. + /// + /// The synchronous operation to perform. + /// An already completed task. If the succeeded this will be a successfully completed task, + /// otherwise it will be a faulted task holding the exception thrown. + /// The advantage of this method over CompletedTask property is that if the fails + /// this method returns a faulted task, instead of throwing exception. + internal static Task GetTaskForSynchronousOperation(Action synchronousOperation) + { + Debug.Assert(synchronousOperation != null, "synchronousOperation != null"); + + try + { + synchronousOperation(); + return TaskUtils.CompletedTask; + } + catch (Exception e) + { + if (!ExceptionUtils.IsCatchableExceptionType(e)) + { + throw; + } + + return TaskUtils.GetFaultedTask(e); + } + } + + /// + /// Returns an already completed task for the specified synchronous operation. + /// + /// The type of the result returned by the operation. This MUST NOT be a Task type. + /// The synchronous operation to perform. + /// An already completed task. If the succeeded this will be a successfully completed task, + /// otherwise it will be a faulted task holding the exception thrown. + /// The advantage of this method over GetCompletedTask property is that if the fails + /// this method returns a faulted task, instead of throwing exception. + internal static Task GetTaskForSynchronousOperation(Func synchronousOperation) + { + Debug.Assert(synchronousOperation != null, "synchronousOperation != null"); + Debug.Assert(!(typeof(Task).IsAssignableFrom(typeof(T))), "This method doesn't support operations returning Task instances."); + + try + { + T result = synchronousOperation(); + return TaskUtils.GetCompletedTask(result); + } + catch (Exception e) + { + if (!ExceptionUtils.IsCatchableExceptionType(e)) + { + throw; + } + + return TaskUtils.GetFaultedTask(e); + } + } + + /// + /// Returns an already completed task for the specified synchronous operation which returns a task. + /// + /// The synchronous operation to perform. + /// The task returned by the or a faulted task if the operation failed. + /// The advantage of this method over direct call is that if the fails + /// this method returns a faulted task, instead of throwing exception. + internal static Task GetTaskForSynchronousOperationReturningTask(Func synchronousOperation) + { + try + { + return synchronousOperation(); + } + catch (Exception exception) + { + if (!ExceptionUtils.IsCatchableExceptionType(exception)) + { + throw; + } + + return TaskUtils.GetFaultedTask(exception); + } + } + + /// + /// Returns an already completed task for the specified synchronous operation which returns a task. + /// + /// The type of the task result. + /// The synchronous operation to perform. + /// The task returned by the or a faulted task if the operation failed. + /// The advantage of this method over direct call is that if the fails + /// this method returns a faulted task, instead of throwing exception. + internal static Task GetTaskForSynchronousOperationReturningTask(Func> synchronousOperation) + { + try + { + return synchronousOperation(); + } + catch (Exception exception) + { + if (!ExceptionUtils.IsCatchableExceptionType(exception)) + { + throw; + } + + return TaskUtils.GetFaultedTask(exception); + } + } + #endregion + +#region FollowOnSuccessWith + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + internal static Task FollowOnSuccessWith(this Task antecedentTask, Action operation) + { + return FollowOnSuccessWithImplementation(antecedentTask, t => { operation(t); return null; }); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The result type of the operation. This MUST NOT be a Task or a type derived from Task. + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// This method doesn't support operations which return another Task instance, to use that call FollowOnSuccessWithTask instead. + /// + internal static Task FollowOnSuccessWith(this Task antecedentTask, Func operation) + { + return FollowOnSuccessWithImplementation(antecedentTask, operation); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The result type of the antecedent task. + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + internal static Task FollowOnSuccessWith(this Task antecedentTask, Action> operation) + { + return FollowOnSuccessWithImplementation(antecedentTask, t => { operation((Task)t); return null; }); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The result type of the antecedent task. + /// The result type of the operation. This MUST NOT be a Task or a type derived from Task. + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// This method doesn't support operations which return another Task instance, to use that call FollowOnSuccessWithTask instead. + /// + internal static Task FollowOnSuccessWith(this Task antecedentTask, Func, TFollowupTaskResult> operation) + { + return FollowOnSuccessWithImplementation(antecedentTask, t => operation((Task)t)); + } + #endregion + +#region FollowOnSuccessWithTask + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// This method handles operation which returns another task. The method will unwrap and return a task which finishes when both + /// the antecedent task, the operation as well as the task returned by that operation finished. + /// + internal static Task FollowOnSuccessWithTask(this Task antecedentTask, Func operation) + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(operation != null, "operation != null"); + + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + antecedentTask.ContinueWith( + (taskToContinueOn) => FollowOnSuccessWithContinuation(taskToContinueOn, taskCompletionSource, operation), + TaskContinuationOptions.ExecuteSynchronously); + return taskCompletionSource.Task.Unwrap(); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The result type of the operation. This MUST NOT be a Task or a type derived from Task. + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// This method handles operation which returns another task. The method will unwrap and return a task which finishes when both + /// the antecedent task, the operation as well as the task returned by that operation finished. + /// + internal static Task FollowOnSuccessWithTask(this Task antecedentTask, Func> operation) + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(operation != null, "operation != null"); + + TaskCompletionSource> taskCompletionSource = new TaskCompletionSource>(); + antecedentTask.ContinueWith( + (taskToContinueOn) => FollowOnSuccessWithContinuation(taskToContinueOn, taskCompletionSource, operation), + TaskContinuationOptions.ExecuteSynchronously); + return taskCompletionSource.Task.Unwrap(); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The result type of the antecedent task. + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// This method handles operation which returns another task. The method will unwrap and return a task which finishes when both + /// the antecedent task, the operation as well as the task returned by that operation finished. + /// + internal static Task FollowOnSuccessWithTask(this Task antecedentTask, Func, Task> operation) + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(operation != null, "operation != null"); + + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + antecedentTask.ContinueWith( + (taskToContinueOn) => FollowOnSuccessWithContinuation(taskToContinueOn, taskCompletionSource, (taskForOperation) => operation((Task)taskForOperation)), + TaskContinuationOptions.ExecuteSynchronously); + return taskCompletionSource.Task.Unwrap(); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task succeeded. + /// + /// The result type of the antecedent task. + /// The result type of the operation. This MUST NOT be a Task or a type derived from Task. + /// The task to "append" the operation to. + /// The operation to execute if the succeeded. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// This method handles operation which returns another task. The method will unwrap and return a task which finishes when both + /// the antecedent task, the operation as well as the task returned by that operation finished. + /// + internal static Task FollowOnSuccessWithTask(this Task antecedentTask, Func, Task> operation) + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(operation != null, "operation != null"); + + TaskCompletionSource> taskCompletionSource = new TaskCompletionSource>(); + antecedentTask.ContinueWith( + (taskToContinueOn) => FollowOnSuccessWithContinuation(taskToContinueOn, taskCompletionSource, (taskForOperation) => operation((Task)taskForOperation)), + TaskContinuationOptions.ExecuteSynchronously); + return taskCompletionSource.Task.Unwrap(); + } + #endregion + +#region FollowOnFaultWith + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task faulted. + /// + /// The task to "append" the operation to. + /// The operation to execute if the faulted. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + internal static Task FollowOnFaultWith(this Task antecedentTask, Action operation) + { + return FollowOnFaultWithImplementation(antecedentTask, t => null, operation); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task faulted. + /// + /// The type of the result of the task. + /// The task to "append" the operation to. + /// The operation to execute if the faulted. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + internal static Task FollowOnFaultWith(this Task antecedentTask, Action> operation) + { + return FollowOnFaultWithImplementation(antecedentTask, t => ((Task)t).Result, t => operation((Task)t)); + } + #endregion + +#region FollowOnFaultAndCatchExceptionWith + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will only be invoked if the antecendent task faulted and it failed with exception of type TExceptionType. + /// + /// The type of the result of the task. + /// The exception type to catch. + /// The task to "append" the operation to. + /// The operation to execute if the faulted with an exception of type TExceptionType. + /// A new task which represents the antecedent task followed by a conditional invoke to the operation. + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails and the exception is not of the TExceptionType, + /// if the exception type matches, the task will return the value returned by the catchBlock. + internal static Task FollowOnFaultAndCatchExceptionWith( + this Task antecedentTask, + Func catchBlock) + where TExceptionType : Exception + { + return FollowOnFaultAndCatchExceptionWithImplementation(antecedentTask, t => ((Task)t).Result, catchBlock); + } + #endregion + +#region FollowAlwaysWith + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will get called no matter what the result of the antecedent task was. + /// + /// The task to "append" the operation to. + /// The operation to execute after the finished. + /// A new task which represents the antecedent task followed by an invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// Note that the operation may not return any value, since the original result of the antecedent task will be used always. + /// Also if the operation fails, the resulting task fails. If both tasks fail, the antecedent task failure is reported only. + internal static Task FollowAlwaysWith(this Task antecedentTask, Action operation) + { + return FollowAlwaysWithImplementation(antecedentTask, t => null, operation); + } + + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will get called no matter what the result of the antecedent task was. + /// + /// The type of the result of the task. + /// The task to "append" the operation to. + /// The operation to execute after the finished. + /// A new task which represents the antecedent task followed by an invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// Note that the operation may not return any value, since the original result of the antecedent task will be used always. + /// Also if the operation fails, the resulting task fails. If both tasks fail, the antecedent task failure is reported only. + internal static Task FollowAlwaysWith(this Task antecedentTask, Action> operation) + { + return FollowAlwaysWithImplementation(antecedentTask, t => ((Task)t).Result, t => operation((Task)t)); + } + #endregion + +#region Task.IgnoreExceptions - copied from Samples for Parallel Programming + /// Suppresses default exception handling of a Task that would otherwise reraise the exception on the finalizer thread. + /// The Task to be monitored. + /// The original Task. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", Justification = "Need to access t.Exception to invoke the getter which will mark the Task to not throw the exception.")] + internal static Task IgnoreExceptions(this Task task) + { + task.ContinueWith( + t => { var ignored = t.Exception; }, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted, + TaskScheduler.Default); + return task; + } + #endregion Task.IgnoreExceptions - copied from Samples for Parallel Programming + +#region TaskFactory.GetTargetScheduler - copied from Samples for Parallel Programming + /// Gets the TaskScheduler instance that should be used to schedule tasks. + /// Factory to get the scheduler for. + /// The scheduler for the specified factory. + internal static TaskScheduler GetTargetScheduler(this TaskFactory factory) + { + Debug.Assert(factory != null, "factory != null"); + return factory.Scheduler ?? TaskScheduler.Current; + } + #endregion TaskFactory.GetTargetScheduler - copied from Samples for Parallel Programming + +#region TaskFactory.Iterate - copied from Samples for Parallel Programming + //// Note that if we would migrate to .NET 4.5 and could get dependency on the "await" keyword, all of this is not needed + //// and we could use the await functionality instead. + + /// Asynchronously iterates through an enumerable of tasks. + /// The target factory. + /// The enumerable containing the tasks to be iterated through. + /// A Task that represents the complete asynchronous operation. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Stores the exception so that it doesn't bring down the process but isntead rethrows on the task calling thread.")] + internal static Task Iterate( + this TaskFactory factory, + IEnumerable source) + { + // Validate/update parameters + Debug.Assert(factory != null, "factory != null"); + Debug.Assert(source != null, "source != null"); + + // Get an enumerator from the enumerable + var enumerator = source.GetEnumerator(); + Debug.Assert(enumerator != null, "enumerator != null"); + + // Create the task to be returned to the caller. And ensure + // that when everything is done, the enumerator is cleaned up. + var trc = new TaskCompletionSource(null, factory.CreationOptions); + trc.Task.ContinueWith(_ => enumerator.Dispose(), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + + // This will be called every time more work can be done. + Action recursiveBody = null; + recursiveBody = antecedent => + { + try + { + // If the previous task completed with any exceptions, bail + if (antecedent != null && antecedent.IsFaulted) + { + trc.TrySetException(antecedent.Exception); + return; + } + + // If we should continue iterating and there's more to iterate + // over, create a continuation to continue processing. We only + // want to continue processing once the current Task (as yielded + // from the enumerator) is complete. + if (enumerator.MoveNext()) + { + var nextTask = enumerator.Current; + + // The IgnoreException here is effective only for the recursiveBody code + // (not the nextTask, which is being checked by the recursiveBody above). + // And since the recursiveBody already catches all exceptions except for the uncatchable + // ones, we think it's OK to ignore all those exception in the finalizer thread. + nextTask.ContinueWith(recursiveBody).IgnoreExceptions(); + } + else + { + // Otherwise, we're done + trc.TrySetResult(null); + } + } + catch (Exception exc) + { + if (!ExceptionUtils.IsCatchableExceptionType(exc)) + { + throw; + } + + // If MoveNext throws an exception, propagate that to the user, + // either as cancellation or as a fault + var oce = exc as OperationCanceledException; + if (oce != null && oce.CancellationToken == factory.CancellationToken) + { + trc.TrySetCanceled(); + } + else + { + trc.TrySetException(exc); + } + } + }; + + // Get things started by launching the first task + // The IgnoreException here is effective only for the recursiveBody code + // (not the nextTask, which is being checked by the recursiveBody above). + // And since the recursiveBody already catches all exceptions except for the uncatchable + // ones, we think it's OK to ignore all those exception in the finalizer thread. + factory.StartNew(() => recursiveBody(null), CancellationToken.None, TaskCreationOptions.None, factory.GetTargetScheduler()).IgnoreExceptions(); + + // Return the representative task to the user + return trc.Task; + } + #endregion TaskFactory.Iterate - copied from Samples for Parallel Programming + +#region FollowOnSuccess helpers + /// + /// The func used as the continuation (the func in the ContinueWith) for FollowOnSuccess implementations. + /// + /// The type of the result of the operation to follow up with. + /// The task which just finished. + /// The task completion source to apply the result to. + /// The func to execute as the follow up action in case of success of the . + private static void FollowOnSuccessWithContinuation(Task antecedentTask, TaskCompletionSource taskCompletionSource, Func operation) + { + switch (antecedentTask.Status) + { + case TaskStatus.RanToCompletion: + try + { + taskCompletionSource.TrySetResult(operation(antecedentTask)); + } + catch (Exception exception) + { + if (!ExceptionUtils.IsCatchableExceptionType(exception)) + { + throw; + } + + taskCompletionSource.TrySetException(exception); + } + + break; + case TaskStatus.Faulted: + taskCompletionSource.TrySetException(antecedentTask.Exception); + break; + case TaskStatus.Canceled: + taskCompletionSource.TrySetCanceled(); + break; + } + } + + /// + /// The implementation helper for FollowOnSuccess methods which don't allow result type of Task. + /// + /// The type of the result of the followup operation, this MUST NOT be a Task type. + /// The task to follow with operation. + /// The operation to follow up with. + /// A new Task which wraps both the and the conditional execution of . + private static Task FollowOnSuccessWithImplementation(Task antecedentTask, Func operation) + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(operation != null, "operation != null"); + Debug.Assert( + !(typeof(Task).IsAssignableFrom(typeof(TResult))), + "It's not valid to call FollowOnSucessWith on an operation which returns a Task, instead use FollowOnSuccessWithTask."); + + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + antecedentTask.ContinueWith( + (taskToContinueOn) => FollowOnSuccessWithContinuation(taskToContinueOn, taskCompletionSource, operation), + TaskContinuationOptions.ExecuteSynchronously) + + // This is only for the body of delegate in the .ContinueWith, the antecedent task failures are handles by the body itself. + .IgnoreExceptions(); + return taskCompletionSource.Task; + } + #endregion + +#region FollowOnFault helpers + /// + /// The implementation helper for FollowOnFault methods. + /// + /// The type of the result of the task. + /// The task to follow with operation in case of fault. + /// Func which gets a task result value. + /// The operation to follow up with. + /// A new Task which wraps both the and the conditional execution of . + private static Task FollowOnFaultWithImplementation(Task antecedentTask, Func getTaskResult, Action operation) + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(operation != null, "operation != null"); + + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + antecedentTask.ContinueWith( + t => + { + switch (t.Status) + { + case TaskStatus.RanToCompletion: + taskCompletionSource.TrySetResult(getTaskResult(t)); + break; + case TaskStatus.Faulted: + try + { + operation(t); + taskCompletionSource.TrySetException(t.Exception); + } + catch (Exception exception) + { + if (!ExceptionUtils.IsCatchableExceptionType(exception)) + { + throw; + } + + AggregateException aggregateException = new AggregateException(t.Exception, exception); + taskCompletionSource.TrySetException(aggregateException); + } + + break; + case TaskStatus.Canceled: + taskCompletionSource.TrySetCanceled(); + break; + } + }, + TaskContinuationOptions.ExecuteSynchronously) + + // This is only for the body of delegate in the .ContinueWith, the antecedent task failures are handles by the body itself. + .IgnoreExceptions(); + return taskCompletionSource.Task; + } + #endregion + +#region FollowOnFaultAndCatchException helpers + /// + /// The implementation helper for FollowOnFaultAndCatchException methods. + /// + /// The type of the result of the task. + /// The type of the exception to catch. + /// The task to follow with operation in case of fault. + /// Func which gets a task result value. + /// The operation to follow up with. + /// A new Task which wraps both the and the conditional execution of . + private static Task FollowOnFaultAndCatchExceptionWithImplementation( + Task antecedentTask, + Func getTaskResult, + Func catchBlock) + where TExceptionType : Exception + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(catchBlock != null, "catchBlock != null"); + + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + antecedentTask.ContinueWith( + t => + { + switch (t.Status) + { + case TaskStatus.RanToCompletion: + taskCompletionSource.TrySetResult(getTaskResult(t)); + break; + case TaskStatus.Faulted: + Exception exception = t.Exception; + AggregateException aggregateException = exception as AggregateException; + if (aggregateException != null) + { + aggregateException = aggregateException.Flatten(); + if (aggregateException.InnerExceptions.Count == 1) + { + exception = aggregateException.InnerExceptions[0]; + } + } + + if (exception is TExceptionType) + { + try + { + taskCompletionSource.TrySetResult(catchBlock((TExceptionType)exception)); + } + catch (Exception secondException) + { + if (!ExceptionUtils.IsCatchableExceptionType(secondException)) + { + throw; + } + + AggregateException secondAggregateException = new AggregateException(exception, secondException); + taskCompletionSource.TrySetException(secondAggregateException); + } + } + else + { + taskCompletionSource.TrySetException(exception); + } + + break; + case TaskStatus.Canceled: + taskCompletionSource.TrySetCanceled(); + break; + } + }, + TaskContinuationOptions.ExecuteSynchronously) + + // This is only for the body of delegate in the .ContinueWith, the antecedent task failures are handles by the body itself. + .IgnoreExceptions(); + return taskCompletionSource.Task; + } + #endregion + +#region FollowAlways helpers + /// + /// Returns a new task which will consist of the followed by a call to the + /// which will get called no matter what the result of the antecedent task was. + /// + /// The type of the result of the task. + /// The task to "append" the operation to. + /// Function which gets a task result. + /// The operation to execute after the finished. + /// A new task which represents the antecedent task followed by an invoke to the operation. + /// + /// This method unlike ContinueWith will return a task which will fail if the antecedent task fails, thus it propagates failures. + /// Note that the operation may not return any value, since the original result of the antecedent task will be used always. + /// Also if the operation fails, the resulting task fails. If both tasks fail, the antecedent task failure is reported only. + private static Task FollowAlwaysWithImplementation(this Task antecedentTask, Func getTaskResult, Action operation) + { + Debug.Assert(antecedentTask != null, "antecedentTask != null"); + Debug.Assert(operation != null, "operation != null"); + + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + antecedentTask.ContinueWith( + t => + { + Exception operationException = null; + try + { + operation(t); + } + catch (Exception exception) + { + if (!ExceptionUtils.IsCatchableExceptionType(exception)) + { + throw; + } + + operationException = exception; + } + + switch (t.Status) + { + case TaskStatus.RanToCompletion: + if (operationException != null) + { + taskCompletionSource.TrySetException(operationException); + } + else + { + taskCompletionSource.TrySetResult(getTaskResult(t)); + } + + break; + case TaskStatus.Faulted: + Exception exceptionToReport = t.Exception; + if (operationException != null) + { + exceptionToReport = new AggregateException(exceptionToReport, operationException); + } + + taskCompletionSource.TrySetException(exceptionToReport); + break; + case TaskStatus.Canceled: + if (operationException != null) + { + taskCompletionSource.TrySetException(operationException); + } + else + { + taskCompletionSource.TrySetCanceled(); + } + + break; + } + }, + TaskContinuationOptions.ExecuteSynchronously) + + // This is only for the body of delegate in the .ContinueWith, the antecedent task failures are handles by the body itself. + .IgnoreExceptions(); + return taskCompletionSource.Task; + } + #endregion + } +} +#endif \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeNameOracle.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeNameOracle.cs new file mode 100644 index 0000000..b02f972 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeNameOracle.cs @@ -0,0 +1,328 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System.Diagnostics; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Class to validate and resolve the type name to be serialized. + /// + internal class TypeNameOracle + { + /// + /// Validates a type name to ensure that it's not an empty string and resolves it against the provided . + /// + /// The model to use. + /// The type name to validate. + /// The expected type kind for the given type name. + /// This value indicates if a structured type is expected to be return. + /// True for structured type, false for non-structured type, null for indetermination. + /// The writer validator to use for validation. + /// The type with the given name and kind if a user model was available, otherwise null. + internal static IEdmType ResolveAndValidateTypeName(IEdmModel model, string typeName, EdmTypeKind expectedTypeKind, bool? expectStructuredType, IWriterValidator writerValidator) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert( + !expectStructuredType.HasValue + || !expectStructuredType.Value && !expectedTypeKind.IsStructured() + || expectStructuredType.Value && (expectedTypeKind.IsStructured() || expectedTypeKind == EdmTypeKind.None), + "!expectStructuredType.HasValue || !expectStructuredType.Value && !expectedTypeKind.IsStructured() || expectStructuredType.Value && (expectedTypeKind.IsStructured() || expectedTypeKind == EdmTypeKind.None)"); + + if (typeName == null) + { + // if we have metadata, the type name of a resource must not be null + if (model.IsUserModel()) + { + throw new ODataException(Strings.WriterValidationUtils_MissingTypeNameWithMetadata); + } + + return null; + } + + if (typeName.Length == 0) + { + throw new ODataException(Strings.ValidationUtils_TypeNameMustNotBeEmpty); + } + + if (!model.IsUserModel()) + { + return null; + } + + // If we do have metadata, lookup the type and translate it to a type. + IEdmType resolvedType = MetadataUtils.ResolveTypeNameForWrite(model, typeName); + if (resolvedType == null) + { + throw new ODataException(Strings.ValidationUtils_UnrecognizedTypeName(typeName)); + } + + if (resolvedType.TypeKind != EdmTypeKind.Untyped) + { + writerValidator.ValidateTypeKind(resolvedType.TypeKind, expectedTypeKind, expectStructuredType, resolvedType); + } + + return resolvedType; + } + + /// + /// Resolve a resource type name + /// + /// The model to use. + /// The type inferred from the model or null if the model is not a user model. + /// Name of the type to resolve. + /// The writer validator to use for validation. + /// A type for primitive value + internal static IEdmStructuredType ResolveAndValidateTypeFromTypeName(IEdmModel model, IEdmStructuredType expectedType, string typeName, IWriterValidator writerValidator) + { + if (typeName == null && expectedType != null) + { + return expectedType; + } + + // TODO: Clean up handling of expected types/sets during writing + IEdmType typeFromResource = ResolveAndValidateTypeName(model, typeName, EdmTypeKind.None, /* expectStructuredType */ true, writerValidator); + IEdmTypeReference typeReferenceFromValue = ResolveTypeFromMetadataAndValue( + expectedType.ToTypeReference(), + typeFromResource == null ? null : typeFromResource.ToTypeReference(), + writerValidator); + + if (typeReferenceFromValue != null && typeReferenceFromValue.IsUntyped()) + { + return new EdmUntypedStructuredType(); + } + + return typeReferenceFromValue == null ? null : typeReferenceFromValue.ToStructuredType(); + } + + /// + /// Resolve a primitive value type name + /// + /// The value to get the type name from. + /// A type for primitive value + internal static IEdmTypeReference ResolveAndValidateTypeForPrimitiveValue(ODataPrimitiveValue primitiveValue) + { + return EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.Value.GetType()); + } + + /// + /// Resolve a type name against the provided . If not payload type name is specified, + /// derive the type from the model type (if available). + /// + /// The model to use. + /// The value in question to resolve the type for. + /// True if the type name belongs to an open property. + /// A type for the or null if no type name is specified and no metadata is available. + internal static IEdmTypeReference ResolveAndValidateTypeForEnumValue(IEdmModel model, ODataEnumValue enumValue, bool isOpenPropertyType) + { + Debug.Assert(model != null, "model != null"); + + ValidateIfTypeNameMissing(enumValue.TypeName, model, isOpenPropertyType); + + // starting from enum type, we want to skip validation (but still let the above makes sure open type's enum value has .TypeName) + return null; + } + + /// + /// Resolve a type name against the provided . If not payload type name is specified, + /// derive the type from the model type (if available). + /// + /// The model to use. + /// The type inferred from the model or null if the model is not a user model. + /// The value in question to resolve the type for. + /// True if the type name belongs to an open (dynamic) property. + /// The writer validator to use for validation. + /// A type for the or null if no type name is specified and no metadata is available. + internal static IEdmTypeReference ResolveAndValidateTypeForResourceValue(IEdmModel model, IEdmTypeReference typeReferenceFromMetadata, + ODataResourceValue resourceValue, bool isOpenPropertyType, IWriterValidator writerValidator) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(resourceValue != null, "resourceValue != null"); + + var typeName = resourceValue.TypeName; + + ValidateIfTypeNameMissing(typeName, model, isOpenPropertyType); + + // It's ok to use "EdmTypeKind.Complex" because the validation will check "IsStructured()". + IEdmType typeFromValue = typeName == null ? null : ResolveAndValidateTypeName(model, typeName, EdmTypeKind.Complex, true, writerValidator); + if (typeReferenceFromMetadata != null) + { + // It's ok to use "EdmTypeKind.Complex" because the parameter "expectStructuredType" is set to "true". + writerValidator.ValidateTypeKind(EdmTypeKind.Complex, typeReferenceFromMetadata.TypeKind(), true, typeFromValue); + } + + return ResolveTypeFromMetadataAndValue(typeReferenceFromMetadata, + typeFromValue == null ? null : typeFromValue.ToTypeReference(), writerValidator); + } + + /// + /// Resolve a type name against the provided . If not payload type name is specified, + /// derive the type from the model type (if available). + /// + /// The model to use. + /// The type inferred from the model or null if the model is not a user model. + /// The value in question to resolve the type for. + /// True if the type name belongs to an open property. + /// The writer validator to use for validation. + /// A type for the or null if no type name is specified and no metadata is available. + internal static IEdmTypeReference ResolveAndValidateTypeForCollectionValue(IEdmModel model, IEdmTypeReference typeReferenceFromMetadata, ODataCollectionValue collectionValue, bool isOpenPropertyType, IWriterValidator writerValidator) + { + Debug.Assert(model != null, "model != null"); + + var typeName = collectionValue.TypeName; + + ValidateIfTypeNameMissing(typeName, model, isOpenPropertyType); + + IEdmType typeFromValue = typeName == null ? null : ResolveAndValidateTypeName(model, typeName, EdmTypeKind.Collection, false, writerValidator); + if (typeReferenceFromMetadata != null) + { + writerValidator.ValidateTypeKind(EdmTypeKind.Collection, typeReferenceFromMetadata.TypeKind(), false, typeFromValue); + } + + IEdmTypeReference typeReferenceFromValue = ResolveTypeFromMetadataAndValue(typeReferenceFromMetadata, typeFromValue == null ? null : typeFromValue.ToTypeReference(), writerValidator); + if (typeReferenceFromValue != null) + { + // update nullability from metadata + if (typeReferenceFromMetadata != null) + { + typeReferenceFromValue = typeReferenceFromMetadata; + } + + // validate that the collection type represents a valid Collection type (e.g., is unordered). + typeReferenceFromValue = ValidationUtils.ValidateCollectionType(typeReferenceFromValue); + } + + return typeReferenceFromValue; + } + + /// + /// Try to get type name from ODataValue annotation. + /// + /// The value to get type annotation. + /// The type name from annotation + /// True if there is type name annotation. + internal static bool TryGetTypeNameFromAnnotation(ODataValue value, out string propertyName) + { + if (value.TypeAnnotation != null) + { + propertyName = value.TypeAnnotation.TypeName; + return true; + } + + propertyName = null; + return false; + } + + /// + /// Gets the type name from the given . + /// + /// The value to get the type name from. This can be an ODataPrimitiveValue, an ODataCollectionValue or a Clr primitive object. + /// The type name for the given . + protected static string GetTypeNameFromValue(object value) + { + Debug.Assert(value != null, "value != null"); + + ODataPrimitiveValue primitiveValue = value as ODataPrimitiveValue; + if (primitiveValue != null) + { + // primitiveValueTypeReference == null means: the EDM type of the primitive value cannot be determined. + // This could possibly be due to value being an unsigned int. + // In this case, simply return null because: + // - If the property is regular property, the type is not needed since service model knows its exact type. + // - If the property is dynamic property, ODL does not support dynamic property containing unsigned int value + // since we don't know its underlying type as well as how to serialize it. + IEdmPrimitiveTypeReference primitiveValueTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.Value.GetType()); + return primitiveValueTypeReference == null ? null : primitiveValueTypeReference.FullName(); + } + + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + return enumValue.TypeName; + } + + ODataResourceValue resourceValue = value as ODataResourceValue; + if (resourceValue != null) + { + return resourceValue.TypeName; + } + + ODataCollectionValue collectionValue = value as ODataCollectionValue; + if (collectionValue != null) + { + return EdmLibraryExtensions.GetCollectionTypeFullName(collectionValue.TypeName); + } + + ODataBinaryStreamValue binaryStreamValue = value as ODataBinaryStreamValue; + if (binaryStreamValue != null) + { + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Binary, true).FullName(); + } + + ODataStreamReferenceValue streamValue = value as ODataStreamReferenceValue; + if (streamValue != null) + { + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Stream, true).FullName(); + } + + IEdmPrimitiveTypeReference primitiveTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(value.GetType()); + if (primitiveTypeReference == null) + { + throw new ODataException(Strings.ValidationUtils_UnsupportedPrimitiveType(value.GetType().FullName)); + } + + return primitiveTypeReference.FullName(); + } + + /// + /// Validate if type name is missing + /// + /// Type name of the property to be validated. + /// The model to use. + /// If the property is open. + private static void ValidateIfTypeNameMissing(string typeName, IEdmModel model, bool isOpenPropertyType) + { + // if we have metadata, the type name of an open (dynamic) property value must not be null + if (typeName == null && model.IsUserModel() && isOpenPropertyType) + { + throw new ODataException(Strings.WriterValidationUtils_MissingTypeNameWithMetadata); + } + } + + /// + /// Validates that the (optional) is the same as the (optional) . + /// + /// The (optional) type from the metadata definition (the expected type). + /// The (optional) type from the value (the actual type). + /// The writer validator to use for validation. + /// The type as derived from the and/or . + private static IEdmTypeReference ResolveTypeFromMetadataAndValue(IEdmTypeReference typeReferenceFromMetadata, IEdmTypeReference typeReferenceFromValue, IWriterValidator writerValidator) + { + if (typeReferenceFromMetadata == null) + { + // if we have no metadata information there is nothing to validate + return typeReferenceFromValue; + } + + if (typeReferenceFromValue == null) + { + // derive the property type from the metadata + return typeReferenceFromMetadata; + } + + Debug.Assert(typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind(), "typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind()"); + + writerValidator.ValidateTypeReference(typeReferenceFromMetadata, typeReferenceFromValue); + + return typeReferenceFromValue; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeUtils.cs new file mode 100644 index 0000000..7cbbd1b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/TypeUtils.cs @@ -0,0 +1,110 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + #endregion Namespaces + + /// + /// Utility methods for working with CLR types. + /// + internal static class TypeUtils + { + /// Checks whether the specified type is a generic nullable type. + /// Type to check. + /// true if is nullable; false otherwise. + internal static bool IsNullableType(Type type) + { + //// This is a copy of WebUtil.IsNullableType from the product. + + return type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + /// Gets a non-nullable version of the specified type. + /// Type to get non-nullable version for. + /// + /// if type is a reference type or a + /// non-nullable type; otherwise, the underlying value type. + /// + internal static Type GetNonNullableType(Type type) + { + //// This is a copy of RequestQueryParser.GetNonNullableType from the product. + + return Nullable.GetUnderlyingType(type) ?? type; + } + + /// + /// Checks whether the specified can be assigned null. If it is a non-nullable + /// value type it creates the corresonding nullable type and returns it. + /// + /// The type to check. + /// The if it allows null or the corresponding nullable type. + internal static Type GetNullableType(Type type) + { + Debug.Assert(type != null, "type != null"); + + if (!TypeAllowsNull(type)) + { + type = typeof(Nullable<>).MakeGenericType(type); + } + + return type; + } + + /// Checks whether the specified can be assigned null. + /// Type to check. + /// true if type is a reference type or a Nullable type; false otherwise. + internal static bool TypeAllowsNull(Type type) + { + Debug.Assert(type != null, "type != null"); + + return !type.IsValueType() || IsNullableType(type); + } + + /// + /// Determines if two CLR types are equivalent. + /// + /// First type to compare. + /// Second type to compare. + /// true if the types are equivalent (they both represent the same type), or false otherwise. + /// This method abstracts away the necessity to call Type.IsEquivalentTo method in .NET 4 and higher but + /// use simple reference equality on platforms which don't have that method (like Silverlight). + internal static bool AreTypesEquivalent(Type typeA, Type typeB) + { + if (typeA == null || typeB == null) + { + return false; + } + else + { + return typeA == typeB; + } + } + + /// + /// Parses a qualified type name and returns the type namespace and type name + /// + /// The fully qualified type name. + /// The returned namespace name. + /// The returned type name. + /// Returns whether or not the returned type is a collection. + internal static void ParseQualifiedTypeName(string qualifiedTypeName, out string namespaceName, out string typeName, out bool isCollection) + { + isCollection = qualifiedTypeName.StartsWith(ODataConstants.CollectionPrefix + "(", StringComparison.Ordinal); + if (isCollection) + { + qualifiedTypeName = qualifiedTypeName.Substring(ODataConstants.CollectionPrefix.Length + 1).TrimEnd(')'); + } + + int separator = qualifiedTypeName.LastIndexOf(".", StringComparison.Ordinal); + namespaceName = qualifiedTypeName.Substring(0, separator); + typeName = qualifiedTypeName.Substring(separator == 0 ? 0 : separator + 1); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UnknownEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UnknownEntitySet.cs new file mode 100644 index 0000000..0e648c4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UnknownEntitySet.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.OData +{ + internal class UnknownEntitySet : IEdmUnknownEntitySet + { + private readonly IEdmNavigationProperty navigationProperty; + private readonly IEdmNavigationSource parentNavigationSource; + private IEdmPathExpression path; + + public UnknownEntitySet(IEdmNavigationSource parentNavigationSource, IEdmNavigationProperty navigationProperty) + { + this.parentNavigationSource = parentNavigationSource; + this.navigationProperty = navigationProperty; + } + + public string Name + { + get { return this.navigationProperty.Name; } + } + + public IEnumerable NavigationPropertyBindings + { + get { return null; } + } + + public IEdmPathExpression Path + { + get { return this.path ?? (this.path = ComputePath()); } + } + + public IEdmType Type + { + get + { + return this.navigationProperty.Type.Definition; + } + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty) + { + return null; + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + return null; + } + + public IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + return null; + } + + private IEdmPathExpression ComputePath() + { + List newPath = new List(this.parentNavigationSource.Path.PathSegments); + newPath.Add(this.navigationProperty.Name); + return new EdmPathExpression(newPath.ToArray()); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ApplyClauseToStringBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ApplyClauseToStringBuilder.cs new file mode 100644 index 0000000..a9a238e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ApplyClauseToStringBuilder.cs @@ -0,0 +1,296 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.OData.UriParser; +using Microsoft.OData.UriParser.Aggregation; + +namespace Microsoft.OData +{ + internal sealed class ApplyClauseToStringBuilder + { + private readonly NodeToStringBuilder nodeToStringBuilder; + private readonly StringBuilder query; + + public ApplyClauseToStringBuilder() + { + nodeToStringBuilder = new NodeToStringBuilder(); + query = new StringBuilder(); + } + + public string TranslateApplyClause(ApplyClause applyClause) + { + ExceptionUtils.CheckArgumentNotNull(applyClause, nameof(applyClause)); + + query.Append(ExpressionConstants.QueryOptionApply); + query.Append(ExpressionConstants.SymbolEqual); + + bool appendSlash = false; + foreach (TransformationNode transformation in applyClause.Transformations) + { + appendSlash = AppendSlash(appendSlash); + Translate(transformation); + } + + return appendSlash ? query.ToString() : string.Empty; + } + + private bool AppendComma(bool appendComma) + { + if (appendComma) + { + query.Append(ExpressionConstants.SymbolComma); + } + + return true; + } + + private void AppendExpression(SingleValueNode expression) + { + string text = Uri.EscapeDataString(nodeToStringBuilder.TranslateNode(expression)); + query.Append(text); + } + + private void AppendExpression(ODataExpandPath path) + { + string text = path.ToContextUrlPathString(); + query.Append(text); + } + + private bool AppendSlash(bool appendSlash) + { + if (appendSlash) + { + query.Append(ExpressionConstants.SymbolForwardSlash); + } + + return true; + } + + private void AppendWord(string word) + { + query.Append(word); + query.Append(ExpressionConstants.SymbolEscapedSpace); + } + + private static string GetAggregationMethodName(AggregateExpression aggExpression) + { + switch (aggExpression.Method) + { + case AggregationMethod.Average: + return ExpressionConstants.KeywordAverage; + case AggregationMethod.CountDistinct: + return ExpressionConstants.KeywordCountDistinct; + case AggregationMethod.Max: + return ExpressionConstants.KeywordMax; + case AggregationMethod.Min: + return ExpressionConstants.KeywordMin; + case AggregationMethod.Sum: + return ExpressionConstants.KeywordSum; + case AggregationMethod.VirtualPropertyCount: + return ExpressionConstants.QueryOptionCount; + case AggregationMethod.Custom: + return aggExpression.MethodDefinition.MethodLabel; + default: + throw new ArgumentOutOfRangeException(nameof(aggExpression), "unknown AggregationMethod " + aggExpression.Method.ToString()); + } + } + + private void Translate(AggregateTransformationNode transformation) + { + Translate(transformation.AggregateExpressions); + } + + private void Translate(IEnumerable expressions) + { + bool appendComma = false; + foreach (AggregateExpressionBase expression in expressions) + { + appendComma = AppendComma(appendComma); + + switch (expression.AggregateKind) + { + case AggregateExpressionKind.PropertyAggregate: + AggregateExpression aggExpression = expression as AggregateExpression; + if (aggExpression.Method != AggregationMethod.VirtualPropertyCount) + { + AppendExpression(aggExpression.Expression); + query.Append(ExpressionConstants.SymbolEscapedSpace); + AppendWord(ExpressionConstants.KeywordWith); + } + + AppendWord(GetAggregationMethodName(aggExpression)); + AppendWord(ExpressionConstants.KeywordAs); + query.Append(aggExpression.Alias); + break; + case AggregateExpressionKind.EntitySetAggregate: + EntitySetAggregateExpression entitySetExpression = expression as EntitySetAggregateExpression; + query.Append(entitySetExpression.Alias); + query.Append(ExpressionConstants.SymbolOpenParen); + Translate(entitySetExpression.Children); + query.Append(ExpressionConstants.SymbolClosedParen); + break; + } + } + } + + private void Translate(ComputeTransformationNode transformation) + { + bool appendComma = false; + foreach (ComputeExpression computeExpression in transformation.Expressions) + { + appendComma = AppendComma(appendComma); + + AppendExpression(computeExpression.Expression); + query.Append(ExpressionConstants.SymbolEscapedSpace); + AppendWord(ExpressionConstants.KeywordAs); + query.Append(computeExpression.Alias); + } + } + + private void Translate(ExpandTransformationNode transformation) + { + ExpandedNavigationSelectItem expandedNavigation = transformation.ExpandClause.SelectedItems.Single() as ExpandedNavigationSelectItem; + AppendExpandExpression(expandedNavigation); + } + + private void AppendExpandExpression(ExpandedNavigationSelectItem expandedNavigation) + { + AppendExpression(expandedNavigation.PathToNavigationProperty); + + // Append filter + if (expandedNavigation.FilterOption != null) + { + AppendComma(true); + query.Append(ExpressionConstants.SymbolEscapedSpace); + query.Append(ExpressionConstants.KeywordFilter); + query.Append(ExpressionConstants.SymbolOpenParen); + AppendExpression(expandedNavigation.FilterOption.Expression); + query.Append(ExpressionConstants.SymbolClosedParen); + } + + // Append nested expands + if (expandedNavigation.SelectAndExpand != null) + { + foreach (var navigation in expandedNavigation.SelectAndExpand.SelectedItems.OfType()) + { + AppendComma(true); + query.Append(ExpressionConstants.SymbolEscapedSpace); + query.Append(ExpressionConstants.KeywordExpand); + query.Append(ExpressionConstants.SymbolOpenParen); + AppendExpandExpression(navigation); + query.Append(ExpressionConstants.SymbolClosedParen); + } + } + } + + private void Translate(FilterTransformationNode transformation) + { + AppendExpression(transformation.FilterClause.Expression); + } + + private void Translate(GroupByTransformationNode transformation) + { + bool appendComma = false; + foreach (GroupByPropertyNode node in transformation.GroupingProperties) + { + if (appendComma) + { + AppendComma(appendComma); + } + else + { + appendComma = true; + query.Append(ExpressionConstants.SymbolOpenParen); + } + + if (node.Expression != null) + { + AppendExpression(node.Expression); + } + + bool appendCommaChild = false; + foreach (GroupByPropertyNode childNode in node.ChildTransformations) + { + appendCommaChild = AppendComma(appendCommaChild); + AppendExpression(childNode.Expression); + } + } + + if (appendComma) + { + query.Append(ExpressionConstants.SymbolClosedParen); + } + + if (transformation.ChildTransformations != null) + { + AppendComma(true); + Translate(transformation.ChildTransformations); + } + } + + private void Translate(TransformationNode transformation) + { + switch (transformation.Kind) + { + case TransformationNodeKind.Aggregate: + query.Append(ExpressionConstants.KeywordAggregate); + break; + case TransformationNodeKind.GroupBy: + query.Append(ExpressionConstants.KeywordGroupBy); + break; + case TransformationNodeKind.Filter: + query.Append(ExpressionConstants.KeywordFilter); + break; + case TransformationNodeKind.Compute: + query.Append(ExpressionConstants.KeywordCompute); + break; + case TransformationNodeKind.Expand: + query.Append(ExpressionConstants.KeywordExpand); + break; + default: + throw new NotSupportedException("unknown TransformationNodeKind value " + transformation.Kind.ToString()); + } + + query.Append(ExpressionConstants.SymbolOpenParen); + + GroupByTransformationNode groupByTransformation; + AggregateTransformationNode aggTransformation; + FilterTransformationNode filterTransformation; + ComputeTransformationNode computeTransformation; + ExpandTransformationNode expandTransformation; + if ((groupByTransformation = transformation as GroupByTransformationNode) != null) + { + Translate(groupByTransformation); + } + else if ((aggTransformation = transformation as AggregateTransformationNode) != null) + { + Translate(aggTransformation); + } + else if ((filterTransformation = transformation as FilterTransformationNode) != null) + { + Translate(filterTransformation); + } + else if ((computeTransformation = transformation as ComputeTransformationNode) != null) + { + Translate(computeTransformation); + } + else if ((expandTransformation = transformation as ExpandTransformationNode) != null) + { + Translate(expandTransformation); + } + else + { + throw new NotSupportedException("unknown TransformationNode type " + transformation.GetType().Name); + } + + query.Append(ExpressionConstants.SymbolClosedParen); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs new file mode 100644 index 0000000..b331096 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ExpressionConstants.cs @@ -0,0 +1,243 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// This type provides constants used in URI query expressions. + internal static class ExpressionConstants + { + /// "$it" keyword for expressions. + internal const string It = "$it"; + + /// "add" keyword for expressions. + internal const string KeywordAdd = "add"; + + /// "and" keyword for expressions. + internal const string KeywordAnd = "and"; + + /// "asc" keyword for expressions. + internal const string KeywordAscending = "asc"; + + /// "desc" keyword for expressions. + internal const string KeywordDescending = "desc"; + + /// "div" keyword for expressions. + internal const string KeywordDivide = "div"; + + /// "mod" keyword for expressions. + internal const string KeywordModulo = "mod"; + + /// "mul" keyword for expressions. + internal const string KeywordMultiply = "mul"; + + /// "not" keyword for expressions. + internal const string KeywordNot = "not"; + + /// "or" keyword for expressions. + internal const string KeywordOr = "or"; + + /// "sub" keyword for expressions. + internal const string KeywordSub = "sub"; + + /// '-' constant to represent an negate unary operator. + internal const string SymbolNegate = "-"; + + /// '=' constant to represent an assignment in name=value. + internal const string SymbolEqual = "="; + + /// ',' constant to represent an value list separator. + internal const string SymbolComma = ","; + + /// '.' constant to represent the value of a dot separator. + internal const string SymbolDot = "."; + + /// '/' constant to represent the forward slash used in a query. + internal const string SymbolForwardSlash = "/"; + + /// '(' constant to represent an open parenthesis. + internal const string SymbolOpenParen = "("; + + /// ')' constant to represent an closed parenthesis. + internal const string SymbolClosedParen = ")"; + + /// '?' constant to represent the start of the query part. + internal const string SymbolQueryStart = "?"; + + /// '&' constant to represent the concatenation of query parts. + internal const string SymbolQueryConcatenate = "&"; + + /// '\'' constant to represent a single quote as prefix/suffix for literals. + internal const string SymbolSingleQuote = "'"; + + /// "''" constant to represent a single-quote escape character in a string literal. + internal const string SymbolSingleQuoteEscaped = "''"; + + /// " " constant to represent a space character in a Uri query part. + internal const string SymbolEscapedSpace = "%20"; + + /// "eq" keyword for expressions. + internal const string KeywordEqual = "eq"; + + /// "false" keyword for expressions. + internal const string KeywordFalse = "false"; + + /// "gt" keyword for expressions. + internal const string KeywordGreaterThan = "gt"; + + /// "ge" keyword for expressions. + internal const string KeywordGreaterThanOrEqual = "ge"; + + /// "lt" keyword for expressions. + internal const string KeywordLessThan = "lt"; + + /// "le" keyword for expressions. + internal const string KeywordLessThanOrEqual = "le"; + + /// "ne" keyword for expressions. + internal const string KeywordNotEqual = "ne"; + + /// "has" keyword for expressions. + internal const string KeywordHas = "has"; + + /// "null" keyword for expressions. + internal const string KeywordNull = "null"; + + /// "true" keyword for expressions. + internal const string KeywordTrue = "true"; + + /// "max" keyword for expressions. + internal const string KeywordMax = "max"; + + /// "in" keyword for expressions. + internal const string KeywordIn = "in"; + + /// "cast" function + internal const string UnboundFunctionCast = "cast"; + + /// "isof function + internal const string UnboundFunctionIsOf = "isof"; + + /// Spatial length function + internal const string UnboundFunctionLength = "geo.length"; + + /// Spatial intersects function. + internal const string UnboundFunctionIntersects = "geo.intersects"; + + /// "INF" literal used to represent infinity. + internal const string InfinityLiteral = "INF"; + + /// "NaN" literal used to represent not-a-number values. + internal const string NaNLiteral = "NaN"; + + /// 'duration' constant prefixed to duration literals. + internal const string LiteralPrefixDuration = "duration"; + + /// 'geometry' constant prefixed to geometry literals. + internal const string LiteralPrefixGeometry = "geometry"; + + /// 'geography' constant prefixed to geography literals. + internal const string LiteralPrefixGeography = "geography"; + + /// 'binary' constant prefixed to binary literals. + internal const string LiteralPrefixBinary = "binary"; + + /// 'L': Suffix for long (int64) type's string representation + internal const string LiteralSuffixInt64 = "L"; + + /// 'f': Suffix for float (single) type's string representation + internal const string LiteralSuffixSingle = "f"; + + /// 'D': Suffix for double (Real) type's string representation + internal const string LiteralSuffixDouble = "D"; + + /// 'M': Suffix for decimal type's string representation + internal const string LiteralSuffixDecimal = "M"; + + /// 'datetime' constant prefixed to datetime literals. + internal const string LiteralSingleQuote = "'"; + + /// the filter query option + internal const string QueryOptionFilter = "$filter"; + + /// the orderby query option + internal const string QueryOptionOrderby = "$orderby"; + + /// the top query option + internal const string QueryOptionTop = "$top"; + + /// the skip query option + internal const string QueryOptionSkip = "$skip"; + + /// the count query option + internal const string QueryOptionCount = "$count"; + + /// the levels query option + internal const string QueryOptionLevels = "$levels"; + + /// the search query option + internal const string QueryOptionSearch = "$search"; + + /// the select query option + internal const string QueryOptionSelect = "$select"; + + /// the expand query option + internal const string QueryOptionExpand = "$expand"; + + /// "AND" keyword for search option. + internal const string SearchKeywordAnd = "AND"; + + /// "NOT" keyword for search option. + internal const string SearchKeywordNot = "NOT"; + + /// "OR" keyword for search option. + internal const string SearchKeywordOr = "OR"; + + /// "any" keyword for expressions. + internal const string KeywordAny = "any"; + + /// "all" keyword for expressions. + internal const string KeywordAll = "all"; + + /// the apply query option. + internal const string QueryOptionApply = "$apply"; + + /// "aggregate" keyword for $apply. + internal const string KeywordAggregate = "aggregate"; + + /// "filter" keyword for $apply. + internal const string KeywordFilter = "filter"; + + /// "groupby" keyword for $apply. + internal const string KeywordGroupBy = "groupby"; + + /// "compute" keyword for $apply. + internal const string KeywordCompute = "compute"; + + /// "sum" keyword for expressions. + internal const string KeywordSum = "sum"; + + /// "min" keyword for expressions. + internal const string KeywordMin = "min"; + + /// "average" keyword for expressions. + internal const string KeywordAverage = "average"; + + /// "countdistinct" keyword for expressions. + internal const string KeywordCountDistinct = "countdistinct"; + + /// "as" keyword for alias expressions. + internal const string KeywordAs = "as"; + + /// "with" keyword for aggregate verb expressions. + internal const string KeywordWith = "with"; + + /// the compute query option. + internal const string QueryOptionCompute = "$compute"; + + /// "expand" keyword for $apply. + internal const string KeywordExpand = "expand"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/NodeToStringBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/NodeToStringBuilder.cs new file mode 100644 index 0000000..cbc15d7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/NodeToStringBuilder.cs @@ -0,0 +1,712 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Text; + using System.Text.RegularExpressions; + using Microsoft.OData.Edm; + using Microsoft.OData.UriParser; + + /// + /// Build QueryNode to String Representation + /// + internal sealed class NodeToStringBuilder : QueryNodeVisitor + { + /// + /// whether translating search options or others + /// + private bool searchFlag; + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(AllNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + + String result = String.Concat(this.TranslateNode(node.Source), ExpressionConstants.SymbolForwardSlash, ExpressionConstants.KeywordAll, ExpressionConstants.SymbolOpenParen, node.CurrentRangeVariable.Name, ":", this.TranslateNode(node.Body), ExpressionConstants.SymbolClosedParen); + return result; + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(AnyNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + + if (node.CurrentRangeVariable == null && node.Body.Kind == QueryNodeKind.Constant) + { + return String.Concat(this.TranslateNode(node.Source), ExpressionConstants.SymbolForwardSlash, ExpressionConstants.KeywordAny, ExpressionConstants.SymbolOpenParen, ExpressionConstants.SymbolClosedParen); + } + else + { + return String.Concat(this.TranslateNode(node.Source), ExpressionConstants.SymbolForwardSlash, ExpressionConstants.KeywordAny, ExpressionConstants.SymbolOpenParen, node.CurrentRangeVariable.Name, ":", this.TranslateNode(node.Body), ExpressionConstants.SymbolClosedParen); + } + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(BinaryOperatorNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + + var left = this.TranslateNode(node.Left); + if (node.Left.Kind == QueryNodeKind.BinaryOperator && TranslateBinaryOperatorPriority(((BinaryOperatorNode)node.Left).OperatorKind) < TranslateBinaryOperatorPriority(node.OperatorKind) || + node.Left.Kind == QueryNodeKind.Convert && ((ConvertNode)node.Left).Source.Kind == QueryNodeKind.BinaryOperator && + TranslateBinaryOperatorPriority(((BinaryOperatorNode)((ConvertNode)node.Left).Source).OperatorKind) < TranslateBinaryOperatorPriority(node.OperatorKind)) + { + left = String.Concat(ExpressionConstants.SymbolOpenParen, left, ExpressionConstants.SymbolClosedParen); + } + + var right = this.TranslateNode(node.Right); + if (node.Right.Kind == QueryNodeKind.BinaryOperator && TranslateBinaryOperatorPriority(((BinaryOperatorNode)node.Right).OperatorKind) < TranslateBinaryOperatorPriority(node.OperatorKind) || + node.Right.Kind == QueryNodeKind.Convert && ((ConvertNode)node.Right).Source.Kind == QueryNodeKind.BinaryOperator && + TranslateBinaryOperatorPriority(((BinaryOperatorNode)((ConvertNode)node.Right).Source).OperatorKind) < TranslateBinaryOperatorPriority(node.OperatorKind)) + { + right = String.Concat(ExpressionConstants.SymbolOpenParen, right, ExpressionConstants.SymbolClosedParen); + } + + return String.Concat(left, ' ', this.BinaryOperatorNodeToString(node.OperatorKind), ' ', right); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(InNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + + string left = this.TranslateNode(node.Left); + string right = this.TranslateNode(node.Right); + + return String.Concat(left, ' ', ExpressionConstants.KeywordIn, ' ', right); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(CountNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + + String source = this.TranslateNode(node.Source); + return string.Concat(source, ExpressionConstants.SymbolForwardSlash, UriQueryConstants.CountSegment); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(CollectionNavigationNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.NavigationProperty.Name, node.NavigationSource); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(CollectionPropertyAccessNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.Property.Name); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(CollectionComplexNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.Property.Name); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(ConstantNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + if (node.Value == null) + { + return ExpressionConstants.KeywordNull; + } + + return node.LiteralText; + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(CollectionConstantNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + if (String.IsNullOrEmpty(node.LiteralText)) + { + return ExpressionConstants.KeywordNull; + } + + return node.LiteralText; + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(ConvertNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslateNode(node.Source); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String of CollectionResourceCastNode. + public override String Visit(CollectionResourceCastNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.ItemStructuredType.Definition.ToString()); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(ResourceRangeVariableReferenceNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + if (node.Name == "$it") + { + return String.Empty; + } + else + { + return node.Name; + } + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(NonResourceRangeVariableReferenceNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return node.Name; + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(SingleResourceCastNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.StructuredTypeReference.Definition.ToString()); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(SingleNavigationNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.NavigationProperty.Name, node.NavigationSource); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(SingleResourceFunctionCallNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + String result = node.Name; + if (node.Source != null) + { + result = this.TranslatePropertyAccess(node.Source, result); + } + + return this.TranslateFunctionCall(result, node.Parameters); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(SingleValueFunctionCallNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + String result = node.Name; + if (node.Source != null) + { + result = this.TranslatePropertyAccess(node.Source, result); + } + + return this.TranslateFunctionCall(result, node.Parameters); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String of CollectionFunctionCallNode. + public override String Visit(CollectionFunctionCallNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + String result = node.Name; + if (node.Source != null) + { + result = this.TranslatePropertyAccess(node.Source, result); + } + + return this.TranslateFunctionCall(result, node.Parameters); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String of CollectionResourceFunctionCallNode. + public override String Visit(CollectionResourceFunctionCallNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + String result = node.Name; + if (node.Source != null) + { + result = this.TranslatePropertyAccess(node.Source, result); + } + + return this.TranslateFunctionCall(result, node.Parameters); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(SingleValueOpenPropertyAccessNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.Name); + } + + /// + /// Translates an into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(CollectionOpenPropertyAccessNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.Name); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(SingleValuePropertyAccessNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.Property.Name); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(SingleComplexNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return this.TranslatePropertyAccess(node.Source, node.Property.Name); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(ParameterAliasNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return node.Alias; + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String of NamedFunctionParameterNode. + public override string Visit(NamedFunctionParameterNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + return String.Concat(node.Name, ExpressionConstants.SymbolEqual, this.TranslateNode(node.Value)); + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String of SearchTermNode. + public override string Visit(SearchTermNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + + if (false == IsValidSearchWord(node.Text)) + { + return String.Concat("\"", node.Text, "\""); + } + else + { + return node.Text; + } + } + + /// + /// Translates a into a corresponding . + /// + /// The node to translate. + /// The translated String. + public override String Visit(UnaryOperatorNode node) + { + ExceptionUtils.CheckArgumentNotNull(node, "node"); + String result = null; + if (node.OperatorKind == UnaryOperatorKind.Negate) + { + result = ExpressionConstants.SymbolNegate; + } + + // if current translated node is SearchNode, the UnaryOperator should return NOT, or return not + if (node.OperatorKind == UnaryOperatorKind.Not) + { + if (searchFlag) + { + result = ExpressionConstants.SearchKeywordNot; + } + else + { + result = ExpressionConstants.KeywordNot; + } + } + + if (node.Operand.Kind == QueryNodeKind.Constant || node.Operand.Kind == QueryNodeKind.SearchTerm) + { + return String.Concat(result, ' ', this.TranslateNode(node.Operand)); + } + else + { + return String.Concat(result, ExpressionConstants.SymbolOpenParen, this.TranslateNode(node.Operand), ExpressionConstants.SymbolClosedParen); + } + } + + /// Translates a into a string. + /// The levels clause to translate. + /// The translated String. + internal static string TranslateLevelsClause(LevelsClause levelsClause) + { + Debug.Assert(levelsClause != null, "levelsClause != null"); + string levelsStr = levelsClause.IsMaxLevel + ? ExpressionConstants.KeywordMax + : levelsClause.Level.ToString(CultureInfo.InvariantCulture); + return levelsStr; + } + + /// + /// Main dispatching visit method for translating query-nodes into expressions. + /// + /// The node to visit/translate. + /// The LINQ String resulting from visiting the node. + internal String TranslateNode(QueryNode node) + { + Debug.Assert(node != null, "node != null"); + return node.Accept(this); + } + + /// Translates a into a string. + /// The filter clause to translate. + /// The translated String. + internal String TranslateFilterClause(FilterClause filterClause) + { + Debug.Assert(filterClause != null, "filterClause != null"); + return this.TranslateNode(filterClause.Expression); + } + + /// Translates a into a string. + /// The orderBy clause to translate. + /// The translated String. + internal String TranslateOrderByClause(OrderByClause orderByClause) + { + Debug.Assert(orderByClause != null, "orderByClause != null"); + + String expr = this.TranslateNode(orderByClause.Expression); + if (orderByClause.Direction == OrderByDirection.Descending) + { + expr = String.Concat(expr, ' ', ExpressionConstants.KeywordDescending); + } + + orderByClause = orderByClause.ThenBy; + if (orderByClause == null) + { + return expr; + } + else + { + return String.Concat(expr, ExpressionConstants.SymbolComma, this.TranslateOrderByClause(orderByClause)); + } + } + + /// Translates a into a string. + /// The search clause to translate. + /// The translated String. + internal String TranslateSearchClause(SearchClause searchClause) + { + Debug.Assert(searchClause != null, "searchClause != null"); + searchFlag = true; + string searchStr = this.TranslateNode(searchClause.Expression); + searchFlag = false; + return searchStr; + } + + /// Translates a into a string. + /// The compute clause to translate. + /// The translated String. + internal string TranslateComputeClause(ComputeClause computeClause) + { + Debug.Assert(computeClause != null, "computeClause != null"); + + bool appendComma = false; + StringBuilder sb = new StringBuilder(); + foreach (var item in computeClause.ComputedItems) + { + if (appendComma) + { + sb.Append(ExpressionConstants.SymbolComma); + } + else + { + appendComma = true; + } + + sb.Append(this.TranslateNode(item.Expression)); + sb.Append(ExpressionConstants.SymbolEscapedSpace); // "%20" + sb.Append(ExpressionConstants.KeywordAs); + sb.Append(ExpressionConstants.SymbolEscapedSpace); // "%20" + sb.Append(item.Alias); + } + + return sb.ToString(); + } + + /// + /// Add dictinoary to url and each alias value will be URL encoded. + /// + /// Dictionary + /// The url query string of dictionary's key value pairs (URL encoded) + internal String TranslateParameterAliasNodes(IDictionary dictionary) + { + String result = null; + if (dictionary != null) + { + foreach (KeyValuePair keyValuePair in dictionary) + { + if (keyValuePair.Value != null) + { + String tmp = this.TranslateNode(keyValuePair.Value); + result = string.IsNullOrEmpty(tmp) ? result : string.Concat(result, String.IsNullOrEmpty(result) ? null : ExpressionConstants.SymbolQueryConcatenate, keyValuePair.Key, ExpressionConstants.SymbolEqual, Uri.EscapeDataString(tmp)); + } + } + } + + return result; + } + + /// + /// Helper for translating an access to a metadata-defined property or navigation. + /// + /// The source of the property access. + /// The structural or navigation property being accessed. + /// The navigation source of the result, required for navigations. + /// The translated String. + private String TranslatePropertyAccess(QueryNode sourceNode, String edmPropertyName, IEdmNavigationSource navigationSource = null) + { + ExceptionUtils.CheckArgumentNotNull(sourceNode, "sourceNode"); + ExceptionUtils.CheckArgumentNotNull(edmPropertyName, "edmPropertyName"); + + String source = this.TranslateNode(sourceNode); + return String.IsNullOrEmpty(source) ? edmPropertyName : string.Concat(source, ExpressionConstants.SymbolForwardSlash, edmPropertyName); + } + + /// + /// Translates a function call into a corresponding . + /// + /// Name of the function. + /// The argument nodes. + /// + /// The translated String. + /// + private String TranslateFunctionCall(string functionName, IEnumerable argumentNodes) + { + ExceptionUtils.CheckArgumentNotNull(functionName, "functionName"); + + String result = String.Empty; + foreach (QueryNode queryNode in argumentNodes) + { + result = String.Concat(result, String.IsNullOrEmpty(result) ? null : ExpressionConstants.SymbolComma, this.TranslateNode(queryNode)); + } + + return String.Concat(functionName, ExpressionConstants.SymbolOpenParen, result, ExpressionConstants.SymbolClosedParen); + } + + /// + /// Build BinaryOperatorNode to uri + /// + /// the kind of the BinaryOperatorNode + /// String format of the operator + private String BinaryOperatorNodeToString(BinaryOperatorKind operatorKind) + { + switch (operatorKind) + { + case BinaryOperatorKind.Has: + return ExpressionConstants.KeywordHas; + case BinaryOperatorKind.Equal: + return ExpressionConstants.KeywordEqual; + case BinaryOperatorKind.NotEqual: + return ExpressionConstants.KeywordNotEqual; + case BinaryOperatorKind.GreaterThan: + return ExpressionConstants.KeywordGreaterThan; + case BinaryOperatorKind.GreaterThanOrEqual: + return ExpressionConstants.KeywordGreaterThanOrEqual; + case BinaryOperatorKind.LessThan: + return ExpressionConstants.KeywordLessThan; + case BinaryOperatorKind.LessThanOrEqual: + return ExpressionConstants.KeywordLessThanOrEqual; + + // if current translated node is SearchNode, the BinaryOperator should return AND, OR; or return and, or. + case BinaryOperatorKind.And: + if (searchFlag) + { + return ExpressionConstants.SearchKeywordAnd; + } + + return ExpressionConstants.KeywordAnd; + case BinaryOperatorKind.Or: + if (searchFlag) + { + return ExpressionConstants.SearchKeywordOr; + } + + return ExpressionConstants.KeywordOr; + case BinaryOperatorKind.Add: + return ExpressionConstants.KeywordAdd; + case BinaryOperatorKind.Subtract: + return ExpressionConstants.KeywordSub; + case BinaryOperatorKind.Multiply: + return ExpressionConstants.KeywordMultiply; + case BinaryOperatorKind.Divide: + return ExpressionConstants.KeywordDivide; + case BinaryOperatorKind.Modulo: + return ExpressionConstants.KeywordModulo; + default: + return null; + } + } + + /// + /// Get the priority of BinaryOperatorNode + /// This priority table is from http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html (5.1.1.9 Operator Precedence ) + /// + /// binary operator + /// the priority value of the binary operator + private static int TranslateBinaryOperatorPriority(BinaryOperatorKind operatorKind) + { + switch (operatorKind) + { + case BinaryOperatorKind.Or: + return 1; + case BinaryOperatorKind.And: + return 2; + case BinaryOperatorKind.Equal: + case BinaryOperatorKind.NotEqual: + case BinaryOperatorKind.GreaterThan: + case BinaryOperatorKind.GreaterThanOrEqual: + case BinaryOperatorKind.LessThan: + case BinaryOperatorKind.LessThanOrEqual: + return 3; + case BinaryOperatorKind.Add: + case BinaryOperatorKind.Subtract: + return 4; + case BinaryOperatorKind.Divide: + case BinaryOperatorKind.Multiply: + case BinaryOperatorKind.Modulo: + return 5; + case BinaryOperatorKind.Has: + return 6; + default: + return -1; + } + } + + /// + /// Judge a string text is a valid SearchWord or not ? + /// + /// string text to be judged + /// if the string is a valid SearchWord, return true, or return false. + private static bool IsValidSearchWord(string text) + { + Match match = SearchLexer.InvalidWordPattern.Match(text); + if (match.Success || + String.Equals(text, "AND", StringComparison.Ordinal) || + String.Equals(text, "OR", StringComparison.Ordinal) || + String.Equals(text, "NOT", StringComparison.Ordinal)) + { + return false; + } + else + { + return true; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUri.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUri.cs new file mode 100644 index 0000000..2830564 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUri.cs @@ -0,0 +1,247 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Microsoft.OData.UriParser.Aggregation; + using Microsoft.OData.UriParser; + #endregion Namespaces + + /// + /// The root node of a query. Holds the query itself plus additional metadata about the query. + /// + public sealed class ODataUri + { + /// + /// Cache MetadataSegment as relative Uri + /// + private static readonly Uri MetadataSegment = new Uri(ODataConstants.UriMetadataSegment, UriKind.Relative); + + /// + /// service Root Uri + /// + private Uri serviceRoot; + + /// + /// Initializes a new instance of the class. + /// + public ODataUri() + { + IDictionary dictionary = new Dictionary(StringComparer.Ordinal); + this.ParameterAliasValueAccessor = new ParameterAliasValueAccessor(dictionary); + } + + /// + /// Create a new ODataUri. This contains the semantic meaning of the + /// entire uri. + /// + /// The ParameterAliasValueAccessor. + /// The top level path for this uri. + /// Any custom query options for this uri. Can be null. + /// Any $select or $expand option for this uri. Can be null. + /// Any $filter option for this uri. Can be null. + /// Any $orderby option for this uri. Can be null + /// Any $search option for this uri. Can be null + /// Any $apply option for this uri. Can be null + /// Any $skip option for this uri. Can be null. + /// Any $top option for this uri. Can be null. + /// Any $index for this uri. Can be null. + /// Any query $count option for this uri. Can be null. + /// Any query $compute option for this uri. Can be null. + internal ODataUri( + ParameterAliasValueAccessor parameterAliasValueAccessor, + ODataPath path, + IEnumerable customQueryOptions, + SelectExpandClause selectAndExpand, + FilterClause filter, + OrderByClause orderby, + SearchClause search, + ApplyClause apply, + long? skip, + long? top, + long? index, + bool? queryCount, + ComputeClause compute = null) + { + this.ParameterAliasValueAccessor = parameterAliasValueAccessor; + this.Path = path; + this.CustomQueryOptions = new ReadOnlyCollection(customQueryOptions.ToList()); + this.SelectAndExpand = selectAndExpand; + this.Filter = filter; + this.OrderBy = orderby; + this.Search = search; + this.Apply = apply; + this.Skip = skip; + this.Top = top; + this.Index = index; + this.QueryCount = queryCount; + this.Compute = compute; + } + + /// + /// Gets or sets the request Uri. + /// + public Uri RequestUri { get; set; } + + /// + /// Gets or sets the service root Uri. + /// + public Uri ServiceRoot + { + get + { + return this.serviceRoot; + } + + set + { + if (value == null) + { + this.serviceRoot = null; + this.MetadataDocumentUri = null; + return; + } + + if (!value.IsAbsoluteUri) + { + throw new ODataException(Strings.WriterValidationUtils_MessageWriterSettingsServiceDocumentUriMustBeNullOrAbsolute(UriUtils.UriToString(value))); + } + + this.serviceRoot = UriUtils.EnsureTaillingSlash(value); + this.MetadataDocumentUri = new Uri(this.serviceRoot, MetadataSegment); + } + } + + /// + /// Get the parameter alias nodes info. + /// + public IDictionary ParameterAliasNodes + { + get + { + if (this.ParameterAliasValueAccessor == null) + { + return null; + } + + return this.ParameterAliasValueAccessor.ParameterAliasValueNodesCached; + } + } + + /// + /// Gets or sets the top level path for this uri. + /// + public ODataPath Path { get; set; } + + /// + /// Gets or sets any custom query options for this uri. + /// + public IEnumerable CustomQueryOptions { get; set; } + + /// + /// Gets or sets any $select or $expand option for this uri. + /// + public SelectExpandClause SelectAndExpand { get; set; } + + /// + /// Gets or sets any $filter option for this uri. + /// + public FilterClause Filter { get; set; } + + /// + /// Gets or sets any $orderby option for this uri. + /// + public OrderByClause OrderBy { get; set; } + + /// + /// Gets or sets any $search option for this uri. + /// + public SearchClause Search { get; set; } + + /// + /// Gets or sets any $apply option for this uri. + /// + public ApplyClause Apply { get; set; } + + /// + /// Gets or sets any $compute option for this uri. + /// + public ComputeClause Compute { get; set; } + + /// + /// Gets or sets any $skip option for this uri. + /// + public long? Skip { get; set; } + + /// + /// Gets or sets any $top option for this uri. + /// + public long? Top { get; set; } + + /// + /// Gets or sets any $index option for this uri. + /// + public long? Index { get; set; } + + /// + /// Get or sets any query $count option for this uri. + /// + public bool? QueryCount { get; set; } + + /// + /// Gets or sets any $skiptoken option for this uri. + /// + public string SkipToken { get; set; } + + /// + /// Gets or sets any $deltatoken option for this uri. + /// + public string DeltaToken { get; set; } + + /// + /// Get or sets the MetadataDocumentUri, which is always ServiceRoot + $metadata + /// + internal Uri MetadataDocumentUri { get; private set; } + + /// + /// Gets or sets the ParameterAliasValueAccessor. + /// + internal ParameterAliasValueAccessor ParameterAliasValueAccessor { get; set; } + + /// + /// Return a copy of current ODataUri. + /// + /// A copy of current ODataUri. + public ODataUri Clone() + { + return new ODataUri() + { + RequestUri = RequestUri, + serviceRoot = ServiceRoot, // Use field instead of property for perf. + MetadataDocumentUri = MetadataDocumentUri, + ParameterAliasValueAccessor = ParameterAliasValueAccessor, + Path = Path, + CustomQueryOptions = CustomQueryOptions, + SelectAndExpand = SelectAndExpand, + Apply = Apply, + Filter = Filter, + OrderBy = OrderBy, + Search = Search, + Skip = Skip, + Top = Top, + Index = Index, + QueryCount = QueryCount, + SkipToken = SkipToken, + DeltaToken = DeltaToken, + }; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriConversionUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriConversionUtils.cs new file mode 100644 index 0000000..63e1563 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriConversionUtils.cs @@ -0,0 +1,637 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Text; +using Microsoft.OData.Edm; +using Microsoft.OData.Evaluation; +using Microsoft.OData.JsonLight; +using Microsoft.OData.Metadata; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData +{ + /// + /// Utility functions for writing values for use in a URL. + /// + internal static class ODataUriConversionUtils + { + /// + /// Converts a primitive to a string for use in a Url. + /// + /// Value to convert. + /// OData version to be compliant with. + /// A string representation of to be added to a Url. + internal static string ConvertToUriPrimitiveLiteral(object value, ODataVersion version) + { + ExceptionUtils.CheckArgumentNotNull(value, "value"); + + // TODO: Differences between Astoria and ODL's Uri literals + /* This should have the same behavior of Astoria with these differences: + * 1) Cannot handle the System.Data.Linq.Binary type + * 2) Cannot handle the System.Data.Linq.XElement type + * 3) Astoria does not put a 'd' or 'D' on double values + */ + + // for legacy backwards compatibility reasons, use the formatter which does not URL-encode the resulting string. + return LiteralFormatter.ForConstantsWithoutEncoding.Format(value); + } + + /// + /// Converts an enum value to a string for use in a Url. + /// + /// Value to convert. + /// OData version to be compliant with. + /// A string representation of to be added to a Url. + internal static string ConvertToUriEnumLiteral(ODataEnumValue value, ODataVersion version) + { + ExceptionUtils.CheckArgumentNotNull(value, "value"); + ExceptionUtils.CheckArgumentNotNull(value.TypeName, "value.TypeName"); + ExceptionUtils.CheckArgumentNotNull(value.Value, "value.Value"); + + // not URL-encode the resulting string: + return string.Format(CultureInfo.InvariantCulture, "{0}'{1}'", value.TypeName, value.Value); + } + + /// + /// Converts the given string to an ODataResourceValue and returns it. + /// + /// Does not handle primitive values. + /// Value to be deserialized. + /// Model to use for verification. + /// Expected type reference from deserialization. If null, verification will be skipped. + /// An ODataResourceValue that results from the deserialization of . + internal static object ConvertFromResourceValue(string value, IEdmModel model, IEdmTypeReference typeReference) + { + object result = ConvertFromResourceOrCollectionValue(value, model, typeReference); + Debug.Assert(result is ODataResourceValue, "result is ODataResourceValue"); + return result; + } + + /// + /// Converts the given string to an ODataCollectionValue and returns it. + /// Tries in both JSON light and Verbose JSON. + /// + /// Does not handle primitive values. + /// Value to be deserialized. + /// Model to use for verification. + /// Expected type reference from deserialization. If null, verification will be skipped. + /// An ODataCollectionValue that results from the deserialization of . + internal static object ConvertFromCollectionValue(string value, IEdmModel model, IEdmTypeReference typeReference) + { + object result = ConvertFromResourceOrCollectionValue(value, model, typeReference); + Debug.Assert(result is ODataCollectionValue, "result is ODataCollectionValue"); + return result; + } + + /// + /// Verifies that the given is or can be coerced to , and coerces it if necessary. + /// + /// An EDM primitive value to verify. + /// The literal value that was parsed as this primitiveValue. + /// Model to verify against. + /// Expected type reference. + /// Coerced version of the . + internal static object VerifyAndCoerceUriPrimitiveLiteral( + object primitiveValue, + string literalValue, + IEdmModel model, + IEdmTypeReference expectedTypeReference) + { + ExceptionUtils.CheckArgumentNotNull(primitiveValue, "primitiveValue"); + ExceptionUtils.CheckArgumentNotNull(literalValue, "literalValue"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + ExceptionUtils.CheckArgumentNotNull(expectedTypeReference, "expectedTypeReference"); + + // First deal with null literal + ODataNullValue nullValue = primitiveValue as ODataNullValue; + if (nullValue != null) + { + if (!expectedTypeReference.IsNullable) + { + throw new ODataException(ODataErrorStrings.ODataUriUtils_ConvertFromUriLiteralNullOnNonNullableType(expectedTypeReference.FullName())); + } + + return nullValue; + } + + // Only other positive case is a numeric primitive that needs to be coerced + IEdmPrimitiveTypeReference expectedPrimitiveTypeReference = expectedTypeReference.AsPrimitiveOrNull(); + if (expectedPrimitiveTypeReference == null) + { + throw new ODataException(ODataErrorStrings.ODataUriUtils_ConvertFromUriLiteralTypeVerificationFailure(expectedTypeReference.FullName(), literalValue)); + } + + object coercedResult = CoerceNumericType(primitiveValue, expectedPrimitiveTypeReference.PrimitiveDefinition()); + if (coercedResult != null) + { + return coercedResult; + } + + // if expectedTypeReference is set, need to coerce cast + coercedResult = CoerceTemporalType(primitiveValue, expectedPrimitiveTypeReference.PrimitiveDefinition()); + if (coercedResult != null) + { + return coercedResult; + } + + Type actualType = primitiveValue.GetType(); + Type targetType = TypeUtils.GetNonNullableType(EdmLibraryExtensions.GetPrimitiveClrType(expectedPrimitiveTypeReference)); + + // If target type is assignable from actual type, we're OK + if (targetType.IsAssignableFrom(actualType)) + { + return primitiveValue; + } + + throw new ODataException(ODataErrorStrings.ODataUriUtils_ConvertFromUriLiteralTypeVerificationFailure(expectedPrimitiveTypeReference.FullName(), literalValue)); + } + + /// + /// Converts a to a string for use in a Url. + /// + /// Instance to convert. + /// Model to be used for validation. User model is optional. The EdmLib core model is expected as a minimum. + /// A string representation of to be added to a Url. + internal static string ConvertToUriEntityLiteral(ODataResourceBase resource, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(resource, "resource"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + return ConverToJsonLightLiteral( + model, + context => + { + ODataWriter writer = context.CreateODataUriParameterResourceWriter(null, null); + WriteStartResource(writer, resource); + writer.WriteEnd(); + }); + } + + /// + /// Converts a list of to a string for use in a Url. + /// + /// Instance to convert. + /// Model to be used for validation. User model is optional. The EdmLib core model is expected as a minimum. + /// A string representation of to be added to a Url. + internal static string ConvertToUriEntitiesLiteral(IEnumerable entries, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(entries, "entries"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + return ConverToJsonLightLiteral( + model, + context => + { + ODataWriter writer = context.CreateODataUriParameterResourceSetWriter(null, null); + writer.WriteStart(new ODataResourceSet()); + + // TODO: Write Complex Properties in entry + foreach (var resource in entries) + { + WriteStartResource(writer, resource); + writer.WriteEnd(); + } + + writer.WriteEnd(); + }); + } + + /// + /// Converts a to a string for use in a Url. + /// + /// Instance to convert. + /// Model to be used for validation. User model is optional. The EdmLib core model is expected as a minimum. + /// A string representation of to be added to a Url. + internal static string ConvertToUriEntityReferenceLiteral(ODataEntityReferenceLink link, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(link, "link"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + return ConverToJsonLightLiteral(model, context => context.WriteEntityReferenceLink(link)); + } + + /// + /// Converts a to a string for use in a Url. + /// + /// Instance to convert. + /// Model to be used for validation. User model is optional. The EdmLib core model is expected as a minimum. + /// A string representation of to be added to a Url. + internal static string ConvertToUriEntityReferencesLiteral(ODataEntityReferenceLinks links, IEdmModel model) + { + ExceptionUtils.CheckArgumentNotNull(links, "links"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + return ConverToJsonLightLiteral(model, context => context.WriteEntityReferenceLinks(links)); + } + + /// + /// Converts a to a string. + /// + /// Instance to convert. + /// Model to be used for validation. User model is optional. The EdmLib core model is expected as a minimum. + /// Version to be compliant with. + /// A string representation of to be added. + internal static string ConvertToResourceLiteral(ODataResourceValue resourceValue, IEdmModel model, ODataVersion version) + { + ExceptionUtils.CheckArgumentNotNull(resourceValue, "resourceValue"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + StringBuilder builder = new StringBuilder(); + using (TextWriter textWriter = new StringWriter(builder, CultureInfo.InvariantCulture)) + { + ODataMessageWriterSettings messageWriterSettings = new ODataMessageWriterSettings() + { + Version = version, + Validations = ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType, + + // Should write instance annotations for the literal + ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*") + }; + + WriteJsonLightLiteral( + model, + messageWriterSettings, + textWriter, + (serializer) => serializer.WriteResourceValue( + resourceValue, /* resourceValue */ + null, /* metadataTypeReference */ + true, /* isOpenPropertyType */ + serializer.CreateDuplicatePropertyNameChecker())); + } + + return builder.ToString(); + } + + /// + /// Converts a to a string for use in a Url. + /// + /// Instance to convert. + /// Model to be used for validation. User model is optional. The EdmLib core model is expected as a minimum. + /// Version to be compliant with. Collection requires >= V3. + /// A string representation of to be added to a Url. + internal static string ConvertToUriCollectionLiteral(ODataCollectionValue collectionValue, IEdmModel model, ODataVersion version) + { + ExceptionUtils.CheckArgumentNotNull(collectionValue, "collectionValue"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + StringBuilder builder = new StringBuilder(); + using (TextWriter textWriter = new StringWriter(builder, CultureInfo.InvariantCulture)) + { + ODataMessageWriterSettings messageWriterSettings = new ODataMessageWriterSettings() + { + Version = version, + Validations = ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType, + + // TBD: Should write instance annotations for the literal??? + ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*") + }; + + WriteJsonLightLiteral( + model, + messageWriterSettings, + textWriter, + (serializer) => serializer.WriteCollectionValue( + collectionValue, + null /*metadataTypeReference*/, + null /*valueTypeReference*/, + false /*isTopLevelProperty*/, + true /*isInUri*/, + false /*isOpenPropertyType*/)); + } + + return builder.ToString(); + } + + /// + /// Coerces the given to the appropriate CLR type based on . + /// + /// Primitive value to coerce. + /// Edm primitive type to check against. + /// as the corresponding CLR type indicated by , or null if unable to coerce. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Centralized method for coercing numeric types in easiest to understand.")] + internal static object CoerceNumericType(object primitiveValue, IEdmPrimitiveType targetEdmType) + { + // This is implemented to match TypePromotionUtils and MetadataUtilsCommon.CanConvertPrimitiveTypeTo() + ExceptionUtils.CheckArgumentNotNull(primitiveValue, "primitiveValue"); + ExceptionUtils.CheckArgumentNotNull(targetEdmType, "targetEdmType"); + + EdmPrimitiveTypeKind targetPrimitiveKind = targetEdmType.PrimitiveKind; + + if (primitiveValue is SByte) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.SByte: + return primitiveValue; + case EdmPrimitiveTypeKind.Int16: + return Convert.ToInt16((SByte)primitiveValue); + case EdmPrimitiveTypeKind.Int32: + return Convert.ToInt32((SByte)primitiveValue); + case EdmPrimitiveTypeKind.Int64: + return Convert.ToInt64((SByte)primitiveValue); + case EdmPrimitiveTypeKind.Single: + return Convert.ToSingle((SByte)primitiveValue); + case EdmPrimitiveTypeKind.Double: + return Convert.ToDouble((SByte)primitiveValue); + case EdmPrimitiveTypeKind.Decimal: + return Convert.ToDecimal((SByte)primitiveValue); + } + } + + if (primitiveValue is Byte) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Byte: + return primitiveValue; + case EdmPrimitiveTypeKind.Int16: + return Convert.ToInt16((Byte)primitiveValue); + case EdmPrimitiveTypeKind.Int32: + return Convert.ToInt32((Byte)primitiveValue); + case EdmPrimitiveTypeKind.Int64: + return Convert.ToInt64((Byte)primitiveValue); + case EdmPrimitiveTypeKind.Single: + return Convert.ToSingle((Byte)primitiveValue); + case EdmPrimitiveTypeKind.Double: + return Convert.ToDouble((Byte)primitiveValue); + case EdmPrimitiveTypeKind.Decimal: + return Convert.ToDecimal((Byte)primitiveValue); + } + } + + if (primitiveValue is Int16) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Int16: + return primitiveValue; + case EdmPrimitiveTypeKind.Int32: + return Convert.ToInt32((Int16)primitiveValue); + case EdmPrimitiveTypeKind.Int64: + return Convert.ToInt64((Int16)primitiveValue); + case EdmPrimitiveTypeKind.Single: + return Convert.ToSingle((Int16)primitiveValue); + case EdmPrimitiveTypeKind.Double: + return Convert.ToDouble((Int16)primitiveValue); + case EdmPrimitiveTypeKind.Decimal: + return Convert.ToDecimal((Int16)primitiveValue); + } + } + + if (primitiveValue is Int32) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Int32: + return primitiveValue; + case EdmPrimitiveTypeKind.Int64: + return Convert.ToInt64((Int32)primitiveValue); + case EdmPrimitiveTypeKind.Single: + return Convert.ToSingle((Int32)primitiveValue); + case EdmPrimitiveTypeKind.Double: + return Convert.ToDouble((Int32)primitiveValue); + case EdmPrimitiveTypeKind.Decimal: + return Convert.ToDecimal((Int32)primitiveValue); + } + } + + if (primitiveValue is Int64) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Int64: + return primitiveValue; + case EdmPrimitiveTypeKind.Single: + return Convert.ToSingle((Int64)primitiveValue); + case EdmPrimitiveTypeKind.Double: + return Convert.ToDouble((Int64)primitiveValue); + case EdmPrimitiveTypeKind.Decimal: + return Convert.ToDecimal((Int64)primitiveValue); + } + } + + if (primitiveValue is Single) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Single: + return primitiveValue; + case EdmPrimitiveTypeKind.Double: + /*to string then to double, avoid losing precision like "(double)123.001f" which is 123.00099945068359, instead of 123.001d.*/ + return double.Parse(((Single)primitiveValue).ToString("R", CultureInfo.InvariantCulture), + CultureInfo.InvariantCulture); + case EdmPrimitiveTypeKind.Decimal: + return Convert.ToDecimal((Single)primitiveValue); + } + } + + if (primitiveValue is Double) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Double: + return primitiveValue; + case EdmPrimitiveTypeKind.Decimal: + // TODO: extract these convertion steps to an individual function + decimal doubleToDecimalR; + + // To keep the full presion of the current value, which if necessary is all 17 digits of precision supported by the Double type. + if (decimal.TryParse(((Double)primitiveValue).ToString("R", CultureInfo.InvariantCulture), + out doubleToDecimalR)) + { + return doubleToDecimalR; + } + + return Convert.ToDecimal((Double)primitiveValue); + } + } + + if (primitiveValue is Decimal) + { + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.Decimal: + return primitiveValue; + } + } + + return null; + } + + /// + /// Coerces the given to the appropriate CLR type based on . + /// + /// Primitive value to coerce. + /// Edm primitive type to check against. + /// as the corresponding CLR type indicated by , or null if unable to coerce. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", + Justification = "Centralized method for coercing temporal types in easiest to understand.")] + internal static object CoerceTemporalType(object primitiveValue, IEdmPrimitiveType targetEdmType) + { + // This is implemented to match TypePromotionUtils and MetadataUtilsCommon.CanConvertPrimitiveTypeTo() + ExceptionUtils.CheckArgumentNotNull(primitiveValue, "primitiveValue"); + ExceptionUtils.CheckArgumentNotNull(targetEdmType, "targetEdmType"); + + EdmPrimitiveTypeKind targetPrimitiveKind = targetEdmType.PrimitiveKind; + + switch (targetPrimitiveKind) + { + case EdmPrimitiveTypeKind.DateTimeOffset: + if (primitiveValue is Date) + { + var dateValue = (Date)primitiveValue; + return new DateTimeOffset(dateValue.Year, dateValue.Month, dateValue.Day, 0, 0, 0, new TimeSpan(0)); + } + + break; + + case EdmPrimitiveTypeKind.Date: + var stringValue = primitiveValue as string; + if (stringValue != null) + { + // Coerce to Date Type from String. + return PlatformHelper.ConvertStringToDate(stringValue); + } + + break; + } + + return null; + } + + /// + /// Writes an as either a resource or a deleted resource. + /// + /// The to use to write the (deleted) resource. + /// The resource, or deleted resource, to write. + private static void WriteStartResource(ODataWriter writer, ODataResourceBase resource) + { + ODataDeletedResource deletedResource = resource as ODataDeletedResource; + if (deletedResource != null) + { + writer.WriteStart(deletedResource); + } + else + { + // will write a null resource if resource is not an ODataResource + writer.WriteStart(resource as ODataResource); + } + } + + /// + /// Write a literal value in JSON Light format. + /// + /// EDM Model to use for validation and type lookups. + /// Settings to use when writing. + /// TextWriter to use as the output for the value. + /// Delegate to use to actually write the value. + private static void WriteJsonLightLiteral(IEdmModel model, ODataMessageWriterSettings messageWriterSettings, TextWriter textWriter, Action writeValue) + { + // Calling dispose since it's the right thing to do, but when created from a custom-built TextWriter + // the output context Dispose will not actually dispose anything, it will just cleanup itself. + // TODO: URI parser will also support DI container in the future but set the container to null at this moment. + ODataMessageInfo messageInfo = new ODataMessageInfo + { + Model = model, + IsAsync = false, + IsResponse = false + }; + + using (ODataJsonLightOutputContext jsonOutputContext = + new ODataJsonLightOutputContext(textWriter, messageInfo, messageWriterSettings)) + { + ODataJsonLightValueSerializer jsonLightValueSerializer = new ODataJsonLightValueSerializer(jsonOutputContext); + writeValue(jsonLightValueSerializer); + jsonLightValueSerializer.AssertRecursionDepthIsZero(); + } + } + + /// + /// Convert to a literal value in JSON Light format. + /// + /// EDM Model to use for validation and type lookups. + /// Delegate to use to actually write the value. + /// The literal value string. + private static string ConverToJsonLightLiteral(IEdmModel model, Action writeAction) + { + using (MemoryStream stream = new MemoryStream()) + { + ODataMessageWriterSettings messageWriterSettings = new ODataMessageWriterSettings() + { + Version = ODataVersion.V4, + Validations = ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType, + }; + + ODataMediaType mediaType = new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType); + + ODataMessageInfo messageInfo = new ODataMessageInfo + { + MessageStream = stream, + Encoding = Encoding.UTF8, + IsAsync = false, + IsResponse = false, + MediaType = mediaType, + Model = model + }; + + // TODO: URI parser will also support DI container in the future but set the container to null at this moment. + using (ODataJsonLightOutputContext jsonOutputContext = + new ODataJsonLightOutputContext(messageInfo, messageWriterSettings)) + { + writeAction(jsonOutputContext); + stream.Position = 0; + return new StreamReader(stream).ReadToEnd(); + } + } + } + + private static object ConvertFromResourceOrCollectionValue(string value, IEdmModel model, IEdmTypeReference typeReference) + { + ODataMessageReaderSettings settings = new ODataMessageReaderSettings(); + settings.Validations &= ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; + + using (StringReader reader = new StringReader(value)) + { + ODataMessageInfo messageInfo = new ODataMessageInfo + { + MediaType = new ODataMediaType(MimeConstants.MimeApplicationType, MimeConstants.MimeJsonSubType), + Model = model, + IsResponse = false, + IsAsync = false, + MessageStream = null, + }; + + using (ODataJsonLightInputContext context = new ODataJsonLightInputContext(reader, messageInfo, settings)) + { + ODataJsonLightPropertyAndValueDeserializer deserializer = new ODataJsonLightPropertyAndValueDeserializer(context); + + // TODO: The way JSON array literals look in the URI is different that response payload with an array in it. + // The fact that we have to manually setup the underlying reader shows this different in the protocol. + // There is a discussion on if we should change this or not. + deserializer.JsonReader.Read(); // Move to first thing + object rawResult = deserializer.ReadNonEntityValue( + null /*payloadTypeName*/, + typeReference, + null /*DuplicatePropertyNameChecker*/, + null /*CollectionWithoutExpectedTypeValidator*/, + true /*validateNullValue*/, + false /*isTopLevelPropertyValue*/, + false /*insideResourceValue*/, + null /*propertyName*/); + deserializer.ReadPayloadEnd(false); + + return rawResult; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriExtensions.cs new file mode 100644 index 0000000..575a360 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriExtensions.cs @@ -0,0 +1,145 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using Microsoft.OData.UriParser; + + /// + /// Extension methods to ODataUri + /// + public static class ODataUriExtensions + { + /// + /// Build ODataUri into a Uri, the result Uri's query options are URL encoded. + /// + /// ODataUri which will be build to relative url + /// Value from ODataUrlKeyDelimiter + /// Uri of the semantic tree + public static Uri BuildUri(this ODataUri odataUri, ODataUrlKeyDelimiter urlKeyDelimiter) + { + NodeToStringBuilder nodeToStringBuilder = new NodeToStringBuilder(); + SelectExpandClauseToStringBuilder selectExpandClauseToStringBuilder = new SelectExpandClauseToStringBuilder(); + + String queryOptions = String.Empty; + bool writeQueryPrefix = true; + if (odataUri.Filter != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$filter", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(nodeToStringBuilder.TranslateFilterClause(odataUri.Filter))); + } + + if (odataUri.SelectAndExpand != null) + { + string result = selectExpandClauseToStringBuilder.TranslateSelectExpandClause(odataUri.SelectAndExpand, true); + if (!string.IsNullOrEmpty(result)) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, result); + } + } + + if (odataUri.Apply != null) + { + var applyClauseToStringBuilder = new ApplyClauseToStringBuilder(); + string result = applyClauseToStringBuilder.TranslateApplyClause(odataUri.Apply); + if (!string.IsNullOrEmpty(result)) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, result); + } + } + + if (odataUri.OrderBy != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$orderby", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(nodeToStringBuilder.TranslateOrderByClause(odataUri.OrderBy))); + } + + if (odataUri.Top != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$top", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(odataUri.Top.ToString())); + } + + if (odataUri.Skip != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$skip", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(odataUri.Skip.ToString())); + } + + if (odataUri.Index != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$index", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(odataUri.Index.ToString())); + } + + if (odataUri.QueryCount != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$count", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(odataUri.QueryCount == true ? ExpressionConstants.KeywordTrue : ExpressionConstants.KeywordFalse)); + } + + if (odataUri.Search != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$search", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(nodeToStringBuilder.TranslateSearchClause(odataUri.Search))); + } + + if (odataUri.SkipToken != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$skiptoken", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(odataUri.SkipToken)); + } + + if (odataUri.DeltaToken != null) + { + queryOptions = WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions); + writeQueryPrefix = false; + queryOptions = string.Concat(queryOptions, "$deltatoken", ExpressionConstants.SymbolEqual, Uri.EscapeDataString(odataUri.DeltaToken)); + } + + if (odataUri.ParameterAliasNodes != null && odataUri.ParameterAliasNodes.Count > 0) + { + string aliasNode = nodeToStringBuilder.TranslateParameterAliasNodes(odataUri.ParameterAliasNodes); + queryOptions = String.IsNullOrEmpty(aliasNode) ? queryOptions : String.Concat(WriteQueryPrefixOrSeparator(writeQueryPrefix, queryOptions), aliasNode); + writeQueryPrefix = false; + } + + string res = String.Concat(odataUri.Path.ToResourcePathString(urlKeyDelimiter), queryOptions); + return odataUri.ServiceRoot == null ? new Uri(res, UriKind.Relative) : new Uri(odataUri.ServiceRoot, new Uri(res, UriKind.Relative)); + } + + /// + /// Write ? or & depending on whether it is the start of the whole query or query part. + /// + /// True if start of whole query, false if not. + /// add a queryPrefix to the queryOptions. + /// return the queryOptions with a queryPrefix + private static String WriteQueryPrefixOrSeparator(bool writeQueryPrefix, String queryOptions) + { + if (writeQueryPrefix) + { + return String.Concat(queryOptions, ExpressionConstants.SymbolQueryStart); + } + else + { + return String.Concat(queryOptions, ExpressionConstants.SymbolQueryConcatenate); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriUtils.cs new file mode 100644 index 0000000..b11d213 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUriUtils.cs @@ -0,0 +1,180 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm; +using Microsoft.OData.Metadata; +using Microsoft.OData.UriParser; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData +{ + /// + /// URI Utility methods. + /// + public static class ODataUriUtils + { + /// + /// Converts the given to a corresponding CLR type. Expects the + /// to have already been properly unescaped from an actual Uri. + /// + /// Value from a Uri to be converted. + /// Version to be compliant with. + /// A CLR object that the represents (won't be EnumNode). + public static object ConvertFromUriLiteral(string value, ODataVersion version) + { + return ODataUriUtils.ConvertFromUriLiteral(value, version, null, null); + } + + /// + /// Converts the given to a corresponding CLR type. Expects the + /// to have already been properly unescaped from an actual Uri. + /// + /// Value from a Uri to be converted. + /// Version to be compliant with. + /// Optional model to perform verification against. + /// Optional IEdmTypeReference to perform verification against. + /// Callers must provide a containing this type if it is specified. + /// A CLR object that the represents or an EnumNode. + public static object ConvertFromUriLiteral(string value, ODataVersion version, IEdmModel model, IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(value, "value"); + if (typeReference != null && model == null) + { + throw new ODataException(ODataErrorStrings.ODataUriUtils_ConvertFromUriLiteralTypeRefWithoutModel); + } + + if (model == null) + { + model = Microsoft.OData.Edm.EdmCoreModel.Instance; + } + + // Let ExpressionLexer try to get a primitive + ExpressionLexer lexer = new ExpressionLexer(value, false /*moveToFirstToken*/, false /*useSemicolonDelimeter*/); + Exception error; + ExpressionToken token; + + lexer.TryPeekNextToken(out token, out error); + + if (token.Kind == ExpressionTokenKind.BracedExpression && typeReference != null && typeReference.IsStructured()) + { + return ODataUriConversionUtils.ConvertFromResourceValue(value, model, typeReference); + } + + if (token.Kind == ExpressionTokenKind.BracketedExpression) + { + return ODataUriConversionUtils.ConvertFromCollectionValue(value, model, typeReference); + } + + QueryNode enumConstNode; + if ((token.Kind == ExpressionTokenKind.Identifier) // then try parsing the entire text as enum value + && EnumBinder.TryBindIdentifier(lexer.ExpressionText, null, model, out enumConstNode)) + { + return ((ConstantNode)enumConstNode).Value; + } + + object result = lexer.ReadLiteralToken(); + + // If we have a typeReference then perform verification and convert if necessary + if (typeReference != null) + { + result = ODataUriConversionUtils.VerifyAndCoerceUriPrimitiveLiteral(result, value, model, typeReference); + } + + return result; + } + + /// + /// Converts the given object to a string for use in a Uri. Does not perform any of the escaping that provides. + /// No type verification is used. + /// + /// Value to be converted. + /// Version to be compliant with. + /// A string representation of for use in a Url. + [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "designed to aid the creation on a URI, not create a full one")] + public static string ConvertToUriLiteral(object value, ODataVersion version) + { + return ODataUriUtils.ConvertToUriLiteral(value, version, null); + } + + /// + /// Converts the given object to a string in the specified format for use in a Uri. Does not perform any of the escaping that provides. + /// Will perform type verification based on the given model if possible. + /// + /// Value to be converted (can be EnumNode). + /// Version to be compliant with. + /// Optional model to perform verification against. + /// A string representation of for use in a Url. + [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "designed to aid the creation on a URI, not create a full one")] + public static string ConvertToUriLiteral(object value, ODataVersion version, IEdmModel model) + { + if (value == null) + { + value = new ODataNullValue(); + } + + if (model == null) + { + model = Microsoft.OData.Edm.EdmCoreModel.Instance; + } + + ODataNullValue nullValue = value as ODataNullValue; + if (nullValue != null) + { + return ExpressionConstants.KeywordNull; + } + + ODataResourceValue resourceValue = value as ODataResourceValue; + if (resourceValue != null) + { + return ODataUriConversionUtils.ConvertToResourceLiteral(resourceValue, model, version); + } + + ODataCollectionValue collectionValue = value as ODataCollectionValue; + if (collectionValue != null) + { + return ODataUriConversionUtils.ConvertToUriCollectionLiteral(collectionValue, model, version); + } + + ODataEnumValue enumValue = value as ODataEnumValue; + if (enumValue != null) + { + return ODataUriConversionUtils.ConvertToUriEnumLiteral(enumValue, version); + } + + ODataResourceBase resource = value as ODataResourceBase; + if (resource != null) + { + return ODataUriConversionUtils.ConvertToUriEntityLiteral(resource, model); + } + + ODataEntityReferenceLink link = value as ODataEntityReferenceLink; + if (link != null) + { + return ODataUriConversionUtils.ConvertToUriEntityReferenceLiteral(link, model); + } + + ODataEntityReferenceLinks links = value as ODataEntityReferenceLinks; + if (links != null) + { + return ODataUriConversionUtils.ConvertToUriEntityReferencesLiteral(links, model); + } + + IEnumerable list = value as IEnumerable; + if (list != null) + { + return ODataUriConversionUtils.ConvertToUriEntitiesLiteral(list, model); + } + + // Try to convert uints to their underlying type first according to the model. + value = model.ConvertToUnderlyingTypeIfUIntValue(value); + + return ODataUriConversionUtils.ConvertToUriPrimitiveLiteral(value, version); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUrlKeyDelimiter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUrlKeyDelimiter.cs new file mode 100644 index 0000000..a060f90 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/ODataUrlKeyDelimiter.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData +{ + /// + /// Component for controlling what convention are used for generating URLs. + /// + public sealed class ODataUrlKeyDelimiter + { + /// + /// Instance of slash delimiter. + /// + private static readonly ODataUrlKeyDelimiter slashDelimiter = new ODataUrlKeyDelimiter(enablekeyAsSegment: true); + + /// + /// Instance of parentheses delimiter. + /// + private static readonly ODataUrlKeyDelimiter parenthesesDelimiter = new ODataUrlKeyDelimiter(enablekeyAsSegment: false); + + /// + /// Whether to generate entity keys as '/'-delimited segments instead of using parenthesis. + /// + private readonly bool enableKeyAsSegment; + + /// + /// Prevents instances of the class from being created. + /// + /// if enable key-as-segment in url parser. + private ODataUrlKeyDelimiter(bool enablekeyAsSegment) + { + this.enableKeyAsSegment = enablekeyAsSegment; + } + + /// + /// An instance of which uses Parentheses as key delimiter in URL. + /// Specifically, this instance will produce keys that use parentheses like "Customers('ALFKI')". + /// + public static ODataUrlKeyDelimiter Parentheses + { + get { return parenthesesDelimiter; } + } + + /// + /// An instance of which uses slash as key delimiter in URL. + /// Specifically, this instance will produce keys that use segments like "Customers/ALFKI". + /// + public static ODataUrlKeyDelimiter Slash + { + get { return slashDelimiter; } + } + + internal bool EnableKeyAsSegment + { + get + { + return this.enableKeyAsSegment; + } + } + + internal static ODataUrlKeyDelimiter GetODataUrlKeyDelimiter(IServiceProvider container) + { + return ODataSimplifiedOptions.GetODataSimplifiedOptions(container).EnableParsingKeyAsSegmentUrl + ? ODataUrlKeyDelimiter.Slash + : ODataUrlKeyDelimiter.Parentheses; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/SelectExpandClauseToStringBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/SelectExpandClauseToStringBuilder.cs new file mode 100644 index 0000000..f673e18 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Uri/SelectExpandClauseToStringBuilder.cs @@ -0,0 +1,229 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.UriParser; + + /// + /// Build SelectExpandQueryNode to String Representation + /// + internal sealed class SelectExpandClauseToStringBuilder : SelectItemTranslator + { + /// + /// the flag used to mark the SelectItem first appeared + /// + private bool isFirstSelectItem = true; + + /// + /// Build the expand clause for a given level in the selectExpandClause + /// + /// the current level select expand clause + /// whether is inner SelectExpandClause + /// the select and expand segment for context url in this level. + public string TranslateSelectExpandClause(SelectExpandClause selectExpandClause, bool firstFlag) + { + ExceptionUtils.CheckArgumentNotNull(selectExpandClause, "selectExpandClause"); + + List selectList = selectExpandClause.GetCurrentLevelSelectList(); + string selectClause = null; + if (selectList.Any()) + { + selectClause = String.Join(ODataConstants.ContextUriProjectionPropertySeparator, selectList.ToArray()); + } + + selectClause = string.IsNullOrEmpty(selectClause) ? null : string.Concat("$select", ExpressionConstants.SymbolEqual, isFirstSelectItem ? Uri.EscapeDataString(selectClause) : selectClause); + isFirstSelectItem = false; + + string expandClause = null; + foreach (ExpandedNavigationSelectItem expandSelectItem in selectExpandClause.SelectedItems.Where(I => I.GetType() == typeof(ExpandedNavigationSelectItem))) + { + if (string.IsNullOrEmpty(expandClause)) + { + expandClause = firstFlag ? expandClause : string.Concat("$expand", ExpressionConstants.SymbolEqual); + } + else + { + expandClause += ExpressionConstants.SymbolComma; + } + + expandClause += this.Translate(expandSelectItem); + } + + foreach (ExpandedReferenceSelectItem expandSelectItem in selectExpandClause.SelectedItems.Where(I => I.GetType() == typeof(ExpandedReferenceSelectItem))) + { + if (string.IsNullOrEmpty(expandClause)) + { + expandClause = firstFlag ? expandClause : string.Concat("$expand", ExpressionConstants.SymbolEqual); + } + else + { + expandClause += ExpressionConstants.SymbolComma; + } + + expandClause += this.Translate(expandSelectItem) + "/$ref"; + } + + if (string.IsNullOrEmpty(expandClause)) + { + return selectClause; + } + else + { + if (firstFlag) + { + return string.IsNullOrEmpty(selectClause) ? string.Concat("$expand=", Uri.EscapeDataString(expandClause)) : string.Concat(selectClause, "&$expand=", Uri.EscapeDataString(expandClause)); + } + else + { + return string.IsNullOrEmpty(selectClause) ? expandClause : string.Concat(selectClause, ";" + expandClause); + } + } + } + + /// + /// Translate a WildcardSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public override string Translate(WildcardSelectItem item) + { + return string.Empty; + } + + /// + /// Translate a PathSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public override string Translate(PathSelectItem item) + { + return string.Empty; + } + + /// + /// Translate a ContainerQualifiedWildcardSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public override string Translate(NamespaceQualifiedWildcardSelectItem item) + { + return item.Namespace; + } + + /// + /// Translate an ExpandedNavigationSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public override string Translate(ExpandedNavigationSelectItem item) + { + string res = string.Empty; + if (item.SelectAndExpand != null) + { + string selectExpand = this.TranslateSelectExpandClause(item.SelectAndExpand, false); + res = string.Concat(res, !string.IsNullOrEmpty(res) && !string.IsNullOrEmpty(selectExpand) ? ";" : null, selectExpand); + } + + if (item.LevelsOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + res += ExpressionConstants.QueryOptionLevels; + res += ExpressionConstants.SymbolEqual; + res += NodeToStringBuilder.TranslateLevelsClause(item.LevelsOption); + } + + string currentExpandClause = Translate((ExpandedReferenceSelectItem)item); + if (currentExpandClause.EndsWith(ExpressionConstants.SymbolClosedParen, StringComparison.Ordinal)) + { + return currentExpandClause.Insert(currentExpandClause.Length - 1, string.IsNullOrEmpty(res) ? string.Empty : ";" + res); + } + else + { + return string.Concat(currentExpandClause, string.IsNullOrEmpty(res) ? null : string.Concat(ExpressionConstants.SymbolOpenParen, res, ExpressionConstants.SymbolClosedParen)); + } + } + + /// + /// Translate an ExpandedReferenceSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public override string Translate(ExpandedReferenceSelectItem item) + { + NodeToStringBuilder nodeToStringBuilder = new NodeToStringBuilder(); + string currentExpandClause = String.Join("/", item.PathToNavigationProperty.WalkWith(PathSegmentToStringTranslator.Instance).ToArray()); + string res = string.Empty; + if (item.FilterOption != null) + { + res += "$filter=" + nodeToStringBuilder.TranslateFilterClause(item.FilterOption); + } + + if (item.OrderByOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + res += "$orderby=" + nodeToStringBuilder.TranslateOrderByClause(item.OrderByOption); + } + + if (item.TopOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + res += "$top=" + item.TopOption.ToString(); + } + + if (item.SkipOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + res += "$skip=" + item.SkipOption.ToString(); + } + + if (item.CountOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + res += "$count"; + res += ExpressionConstants.SymbolEqual; + if (item.CountOption == true) + { + res += ExpressionConstants.KeywordTrue; + } + else + { + res += ExpressionConstants.KeywordFalse; + } + } + + if (item.SearchOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + res += "$search"; + res += ExpressionConstants.SymbolEqual; + res += nodeToStringBuilder.TranslateSearchClause(item.SearchOption); + } + + // nested $compute= + if (item.ComputeOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + res += "$compute"; + res += ExpressionConstants.SymbolEqual; + res += nodeToStringBuilder.TranslateComputeClause(item.ComputeOption); + } + + // nested $apply= + if (item.ApplyOption != null) + { + res += string.IsNullOrEmpty(res) ? null : ";"; + var applyClauseToStringBuilder = new ApplyClauseToStringBuilder(); + res += applyClauseToStringBuilder.TranslateApplyClause(item.ApplyOption); + } + + return string.Concat(currentExpandClause, string.IsNullOrEmpty(res) ? null : string.Concat(ExpressionConstants.SymbolOpenParen, res, ExpressionConstants.SymbolClosedParen)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpression.cs new file mode 100644 index 0000000..fd6294a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpression.cs @@ -0,0 +1,101 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using Microsoft.OData.Edm; + + /// + /// Represents an aggregate expression that aggregates a single value entity. + /// + public sealed class AggregateExpression : AggregateExpressionBase + { + private readonly AggregationMethod method; + + private readonly AggregationMethodDefinition methodDefinition; + + private readonly SingleValueNode expression; + + private readonly IEdmTypeReference typeReference; + + /// + /// Create a PropertyAggregateExpression. + /// + /// The aggregation expression. + /// The . + /// The aggregation alias. + /// The of this aggregate expression. + public AggregateExpression(SingleValueNode expression, AggregationMethod method, string alias, IEdmTypeReference typeReference) + : base(AggregateExpressionKind.PropertyAggregate, alias) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + ExceptionUtils.CheckArgumentNotNull(alias, "alias"); + + this.expression = expression; + this.method = method; + + //// TypeRefrence is null for dynamic properties + this.typeReference = typeReference; + } + + /// + /// Create a AggregateExpression. + /// + /// The aggregation expression. + /// The . + /// The aggregation alias. + /// The of this aggregate expression. + public AggregateExpression(SingleValueNode expression, AggregationMethodDefinition methodDefinition, string alias, IEdmTypeReference typeReference) + : this(expression, methodDefinition.MethodKind, alias, typeReference) + { + this.methodDefinition = methodDefinition; + } + + /// + /// Gets the aggregation expression. + /// + public SingleValueNode Expression + { + get + { + return expression; + } + } + + /// + /// Gets the . + /// + public AggregationMethod Method + { + get + { + return method; + } + } + + /// + /// Gets the . + /// + public AggregationMethodDefinition MethodDefinition + { + get + { + return methodDefinition; + } + } + + /// + /// Gets the of this aggregate expression. + /// + public IEdmTypeReference TypeReference + { + get + { + return this.typeReference; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionBase.cs new file mode 100644 index 0000000..b3a7611 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionBase.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + /// + /// Enumeration of the possible types of aggregations. + /// + public enum AggregateExpressionKind + { + /// Value used to treat non recognized aggregations. + None = 0, + + /// Aggregation of a single value property. + PropertyAggregate = 1, + + /// Aggregation of a entity set property. + EntitySetAggregate = 2 + } + + /// + /// A aggregate expression representing a aggregation transformation. + /// + public abstract class AggregateExpressionBase + { + /// Base constructor for concrete subclasses use for convenience. + /// The of the expression. + /// Alias of the resulting aggregated value. + protected AggregateExpressionBase(AggregateExpressionKind kind, string alias) + { + AggregateKind = kind; + Alias = alias; + } + + /// Returns the of the expression. + public AggregateExpressionKind AggregateKind { get; private set; } + + /// Returns the alias of the expression. + public string Alias { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionToken.cs new file mode 100644 index 0000000..56fa0b5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateExpressionToken.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +{ + using Microsoft.OData.UriParser.Aggregation; +#else +namespace Microsoft.OData.UriParser.Aggregation +{ +#endif + using Microsoft.OData.UriParser; + + /// + /// Query token representing an Aggregate expression. + /// + public sealed class AggregateExpressionToken : AggregateTokenBase + { + private readonly QueryToken expression; + + private readonly AggregationMethod method; + private readonly AggregationMethodDefinition methodDefinition; + + private readonly string alias; + + /// + /// Create an AggregateExpressionToken. + /// + /// The aggregate expression. + /// The aggregation method. + /// The alias for this query token. + public AggregateExpressionToken(QueryToken expression, AggregationMethod method, string alias) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + ExceptionUtils.CheckArgumentNotNull(alias, "alias"); + + this.expression = expression; + this.method = method; + this.alias = alias; + } + + /// + /// Create an AggregateExpressionToken. + /// + /// The aggregate expression. + /// The aggregate method definition. + /// The alias for this query token. + public AggregateExpressionToken(QueryToken expression, AggregationMethodDefinition methodDefinition, string alias) + : this(expression, methodDefinition.MethodKind, alias) + { + this.methodDefinition = methodDefinition; + } + + /// + /// Gets the kind of this token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.AggregateExpression; } + } + + /// + /// Gets the AggregationMethod of this token. + /// + public AggregationMethod Method + { + get { return this.method; } + } + + /// + /// Gets the expression. + /// + public QueryToken Expression + { + get { return this.expression; } + } + + /// + /// Gets the aggregate method definition. + /// + public AggregationMethodDefinition MethodDefinition + { + get + { + return methodDefinition; + } + } + + /// + /// Gets the alias. + /// + public string Alias + { + get { return this.alias; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateToken.cs new file mode 100644 index 0000000..8a31c69 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateToken.cs @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser.Aggregation +#endif +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Query token representing an Aggregate token. + /// + public sealed class AggregateToken : ApplyTransformationToken + { + private readonly IEnumerable expressions; + + /// + /// Create an AggregateTransformationToken. + /// + /// The aggregate expressions. + public AggregateToken(IEnumerable expressions) + { + ExceptionUtils.CheckArgumentNotNull(expressions, "expressions"); + this.expressions = expressions; + } + + /// + /// Gets the kind of this token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.Aggregate; } + } + + /// + /// Create an AggregateToken. + /// + /// The list of AggregateExpressionToken. + [Obsolete("Use AggregateExpressions for all aggregation expressions or AggregateExpressions.OfType() for aggregate(..) expressions only.")] + public IEnumerable Expressions + { + get + { + return expressions.OfType(); + } + } + + /// + /// Gets the expressions of this token. + /// + public IEnumerable AggregateExpressions + { + get + { + return expressions; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTokenBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTokenBase.cs new file mode 100644 index 0000000..6aaa52c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTokenBase.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser.Aggregation +#endif +{ + /// + /// Base class for Aggregate transformation tokens + /// + public abstract class AggregateTokenBase : ApplyTransformationToken + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTransformationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTransformationNode.cs new file mode 100644 index 0000000..8733db0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregateTransformationNode.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using System; + using System.Collections.Generic; + using System.Linq; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Node representing a aggregate transformation. + /// + public sealed class AggregateTransformationNode : TransformationNode + { + private readonly IEnumerable expressions; + + /// + /// Create a AggregateTransformationNode. + /// + /// A list of . + public AggregateTransformationNode(IEnumerable expressions) + { + ExceptionUtils.CheckArgumentNotNull(expressions, "expressions"); + + this.expressions = expressions; + } + + /// + /// Property that only return s. + /// + [Obsolete("Use AggregateExpressions for all aggregation expressions or AggregateExpressions.OfType() for aggregate(..) expressions only.")] + public IEnumerable Expressions + { + get + { + return expressions.OfType(); + } + } + + /// + /// Property that returns a list of all s of this transformation node. + /// + public IEnumerable AggregateExpressions + { + get + { + return expressions; + } + } + + /// + /// Gets the kind of the transformation node. + /// + public override TransformationNodeKind Kind + { + get + { + return TransformationNodeKind.Aggregate; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregationMethod.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregationMethod.cs new file mode 100644 index 0000000..99b90a6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/AggregationMethod.cs @@ -0,0 +1,89 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; + +namespace Microsoft.OData.UriParser.Aggregation +{ + /// + /// Enumeration of methods used in the aggregation clause + /// + public enum AggregationMethod + { + /// The aggregation method Sum. + Sum, + + /// The aggregation method Min. + Min, + + /// The aggregation method Max. + Max, + + /// The aggregation method Average. + Average, + + /// The aggregation method CountDistinct. + CountDistinct, + + /// The aggregation method Count. Used only internally to represent the virtual property $count. + VirtualPropertyCount, + + /// A custom aggregation method. + Custom + } + + /// + /// Class that encapsulates all the information needed to define a aggregation method. + /// + public sealed class AggregationMethodDefinition + { + /// Returns a definition for the sum aggregation method. + public static AggregationMethodDefinition Sum = new AggregationMethodDefinition(AggregationMethod.Sum); + + /// Returns a definition for the min aggregation method. + public static AggregationMethodDefinition Min = new AggregationMethodDefinition(AggregationMethod.Min); + + /// Returns a definition for the max aggregation method. + public static AggregationMethodDefinition Max = new AggregationMethodDefinition(AggregationMethod.Max); + + /// Returns a definition for the average aggregation method. + public static AggregationMethodDefinition Average = new AggregationMethodDefinition(AggregationMethod.Average); + + /// Returns a definition for the countdistinct aggregation method. + public static AggregationMethodDefinition CountDistinct = new AggregationMethodDefinition(AggregationMethod.CountDistinct); + + /// Returns a definition for the aggregation method used to calculate $count. + public static AggregationMethodDefinition VirtualPropertyCount = new AggregationMethodDefinition(AggregationMethod.VirtualPropertyCount); + + /// Private constructor. Instances should be aquired via static fields of via Custom method. + /// The of this method definition. + private AggregationMethodDefinition(AggregationMethod aggregationMethodType) + { + this.MethodKind = aggregationMethodType; + } + + /// Returns the of this method definition. + public AggregationMethod MethodKind { get; private set; } + + /// Returns the label of this method definition. + public string MethodLabel { get; private set; } + + /// Creates a custom method definition from it's label. + /// The label to call the custom method definition. + /// The custom method created. + public static AggregationMethodDefinition Custom(string customMethodLabel) + { + ExceptionUtils.CheckArgumentNotNull(customMethodLabel, "customMethodLabel"); + + // Custom aggregation methods MUST use a namespace-qualified name (see [OData-ABNF]), i.e. contain at least one dot. + Debug.Assert(customMethodLabel.Contains(OData.ExpressionConstants.SymbolDot)); + + var aggregationMethod = new AggregationMethodDefinition(AggregationMethod.Custom); + aggregationMethod.MethodLabel = customMethodLabel; + return aggregationMethod; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyBinder.cs new file mode 100644 index 0000000..876059e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyBinder.cs @@ -0,0 +1,369 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser.Aggregation +{ + internal sealed class ApplyBinder + { + private MetadataBinder.QueryTokenVisitor bindMethod; + + private BindingState state; + + private FilterBinder filterBinder; + private ODataUriParserConfiguration configuration; + private ODataPathInfo odataPathInfo; + + private IEnumerable aggregateExpressionsCache; + + public ApplyBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) + : this(bindMethod, state, null, null) + { + } + + public ApplyBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) + { + this.bindMethod = bindMethod; + this.state = state; + this.filterBinder = new FilterBinder(bindMethod, state); + this.configuration = configuration; + this.odataPathInfo = odataPathInfo; + } + + public ApplyClause BindApply(IEnumerable tokens) + { + ExceptionUtils.CheckArgumentNotNull(tokens, "tokens"); + + List transformations = new List(); + foreach (QueryToken token in tokens) + { + switch (token.Kind) + { + case QueryTokenKind.Aggregate: + AggregateTransformationNode aggregate = BindAggregateToken((AggregateToken)(token)); + transformations.Add(aggregate); + aggregateExpressionsCache = aggregate.AggregateExpressions; + state.AggregatedPropertyNames = new HashSet(aggregate.AggregateExpressions.Select(statement => new EndPathToken(statement.Alias, null))); + state.IsCollapsed = true; + break; + case QueryTokenKind.AggregateGroupBy: + GroupByTransformationNode groupBy = BindGroupByToken((GroupByToken)(token)); + transformations.Add(groupBy); + state.IsCollapsed = true; + break; + case QueryTokenKind.Compute: + var compute = BindComputeToken((ComputeToken)token); + transformations.Add(compute); + state.AggregatedPropertyNames = new HashSet(compute.Expressions.Select(statement => new EndPathToken(statement.Alias, null))); + break; + case QueryTokenKind.Expand: + SelectExpandClause expandClause = SelectExpandSemanticBinder.Bind(this.odataPathInfo, (ExpandToken)token, null, this.configuration, null); + ExpandTransformationNode expandNode = new ExpandTransformationNode(expandClause); + transformations.Add(expandNode); + break; + default: + FilterClause filterClause = this.filterBinder.BindFilter(token); + FilterTransformationNode filterNode = new FilterTransformationNode(filterClause); + transformations.Add(filterNode); + break; + } + } + + return new ApplyClause(transformations); + } + + private AggregateTransformationNode BindAggregateToken(AggregateToken token) + { + IEnumerable aggregateTokens = MergeEntitySetAggregates(token.AggregateExpressions); + List statements = new List(); + + foreach (AggregateTokenBase statementToken in aggregateTokens) + { + statements.Add(BindAggregateExpressionToken(statementToken)); + } + + return new AggregateTransformationNode(statements); + } + + private static IEnumerable MergeEntitySetAggregates(IEnumerable tokens) + { + List mergedTokens = new List(); + Dictionary entitySetTokens = new Dictionary(); + + foreach (AggregateTokenBase token in tokens) + { + switch (token.Kind) + { + case QueryTokenKind.EntitySetAggregateExpression: + { + AggregateTokenBase currentValue; + EntitySetAggregateToken entitySetToken = token as EntitySetAggregateToken; + string key = entitySetToken.Path(); + + if (entitySetTokens.TryGetValue(key, out currentValue)) + { + entitySetTokens.Remove(key); + } + + entitySetTokens.Add(key, EntitySetAggregateToken.Merge(entitySetToken, currentValue as EntitySetAggregateToken)); + break; + } + + case QueryTokenKind.AggregateExpression: + { + mergedTokens.Add(token); + break; + } + } + } + + return mergedTokens.Concat(entitySetTokens.Values).ToList(); + } + + private AggregateExpressionBase BindAggregateExpressionToken(AggregateTokenBase aggregateToken) + { + switch (aggregateToken.Kind) + { + case QueryTokenKind.AggregateExpression: + { + AggregateExpressionToken token = aggregateToken as AggregateExpressionToken; + SingleValueNode expression = this.bindMethod(token.Expression) as SingleValueNode; + IEdmTypeReference typeReference = CreateAggregateExpressionTypeReference(expression, token.MethodDefinition); + + // TODO: Determine source + return new AggregateExpression(expression, token.MethodDefinition, token.Alias, typeReference); + } + + case QueryTokenKind.EntitySetAggregateExpression: + { + EntitySetAggregateToken token = aggregateToken as EntitySetAggregateToken; + QueryNode boundPath = this.bindMethod(token.EntitySet); + + state.InEntitySetAggregation = true; + IEnumerable children = token.Expressions.Select(x => BindAggregateExpressionToken(x)).ToList(); + state.InEntitySetAggregation = false; + return new EntitySetAggregateExpression((CollectionNavigationNode)boundPath, children); + } + + default: + throw new ODataException(ODataErrorStrings.ApplyBinder_UnsupportedAggregateKind(aggregateToken.Kind)); + } + } + + private IEdmTypeReference CreateAggregateExpressionTypeReference(SingleValueNode expression, AggregationMethodDefinition method) + { + IEdmTypeReference expressionType = expression.TypeReference; + if (expressionType == null && aggregateExpressionsCache != null) + { + SingleValueOpenPropertyAccessNode openProperty = expression as SingleValueOpenPropertyAccessNode; + if (openProperty != null) + { + expressionType = GetTypeReferenceByPropertyName(openProperty.Name); + } + } + + switch (method.MethodKind) + { + case AggregationMethod.Average: + EdmPrimitiveTypeKind expressionPrimitiveKind = expressionType.PrimitiveKind(); + switch (expressionPrimitiveKind) + { + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Double: + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Double, expressionType.IsNullable); + case EdmPrimitiveTypeKind.Decimal: + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Decimal, expressionType.IsNullable); + case EdmPrimitiveTypeKind.None: + return expressionType; + default: + throw new ODataException( + ODataErrorStrings.ApplyBinder_AggregateExpressionIncompatibleTypeForMethod(expression, + expressionPrimitiveKind)); + } + + case AggregationMethod.VirtualPropertyCount: + case AggregationMethod.CountDistinct: + // Issue #758: CountDistinct and $Count should return type Edm.Decimal with Scale="0" and sufficient Precision. + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Int64, false); + case AggregationMethod.Max: + case AggregationMethod.Min: + case AggregationMethod.Sum: + return expressionType; + default: + // Only the EdmModel knows which type the custom aggregation methods returns. + // Since we do not have a reference for it, right now we are assuming that all custom aggregation methods returns Doubles + // TODO: find a appropriate way of getting the return type. + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Double, expressionType.IsNullable); + } + } + + private IEdmTypeReference GetTypeReferenceByPropertyName(string name) + { + if (aggregateExpressionsCache != null) + { + AggregateExpression expression = aggregateExpressionsCache.OfType() + .FirstOrDefault(statement => statement.AggregateKind == AggregateExpressionKind.PropertyAggregate && statement.Alias.Equals(name)); + if (expression != null) + { + return expression.TypeReference; + } + } + + return null; + } + + private GroupByTransformationNode BindGroupByToken(GroupByToken token) + { + List properties = new List(); + + foreach (EndPathToken propertyToken in token.Properties) + { + QueryNode bindResult = this.bindMethod(propertyToken); + SingleValuePropertyAccessNode property = bindResult as SingleValuePropertyAccessNode; + SingleComplexNode complexProperty = bindResult as SingleComplexNode; + + if (property != null) + { + RegisterProperty(properties, ReversePropertyPath(property)); + } + else if (complexProperty != null) + { + RegisterProperty(properties, ReversePropertyPath(complexProperty)); + } + else + { + SingleValueOpenPropertyAccessNode openProperty = bindResult as SingleValueOpenPropertyAccessNode; + if (openProperty != null) + { + IEdmTypeReference type = GetTypeReferenceByPropertyName(openProperty.Name); + properties.Add(new GroupByPropertyNode(openProperty.Name, openProperty, type)); + } + else + { + throw new ODataException( + ODataErrorStrings.ApplyBinder_GroupByPropertyNotPropertyAccessValue(propertyToken.Identifier)); + } + } + } + + var newProperties = new HashSet(((GroupByToken)token).Properties); + + TransformationNode aggregate = null; + if (token.Child != null) + { + if (token.Child.Kind == QueryTokenKind.Aggregate) + { + aggregate = BindAggregateToken((AggregateToken)token.Child); + aggregateExpressionsCache = ((AggregateTransformationNode)aggregate).AggregateExpressions; + newProperties.UnionWith(aggregateExpressionsCache.Select(statement => new EndPathToken(statement.Alias, null))); + } + else + { + throw new ODataException(ODataErrorStrings.ApplyBinder_UnsupportedGroupByChild(token.Child.Kind)); + } + } + + state.AggregatedPropertyNames = newProperties; + + // TODO: Determine source + return new GroupByTransformationNode(properties, aggregate, null); + } + + private static bool IsPropertyNode(SingleValueNode node) + { + return node.Kind == QueryNodeKind.SingleValuePropertyAccess || + node.Kind == QueryNodeKind.SingleComplexNode || + node.Kind == QueryNodeKind.SingleNavigationNode; + } + + private static Stack ReversePropertyPath(SingleValueNode node) + { + Stack result = new Stack(); + do + { + result.Push(node); + if (node.Kind == QueryNodeKind.SingleValuePropertyAccess) + { + node = ((SingleValuePropertyAccessNode)node).Source; + } + else if (node.Kind == QueryNodeKind.SingleComplexNode) + { + node = (SingleValueNode)((SingleComplexNode)node).Source; + } + else if (node.Kind == QueryNodeKind.SingleNavigationNode) + { + node = ((SingleNavigationNode)node).NavigationSource as SingleValueNode; + } + } + while (node != null && IsPropertyNode(node)); + + return result; + } + + private static void RegisterProperty(IList properties, Stack propertyStack) + { + SingleValueNode property = propertyStack.Pop(); + string propertyName = GetNodePropertyName(property); + + if (propertyStack.Count != 0) + { + // Not at the leaf, let's add to the container. + GroupByPropertyNode containerProperty = properties.FirstOrDefault(p => p.Name == propertyName); + if (containerProperty == null) + { + // We do not have container yet. Create it. + containerProperty = new GroupByPropertyNode(propertyName, null); + properties.Add(containerProperty); + } + + RegisterProperty(containerProperty.ChildTransformations, propertyStack); + } + else + { + // It's the leaf just add. + properties.Add(new GroupByPropertyNode(propertyName, property, property.TypeReference)); + } + } + + private static string GetNodePropertyName(SingleValueNode property) + { + if (property.Kind == QueryNodeKind.SingleValuePropertyAccess) + { + return ((SingleValuePropertyAccessNode)property).Property.Name; + } + else if (property.Kind == QueryNodeKind.SingleComplexNode) + { + return ((SingleComplexNode)property).Property.Name; + } + else if (property.Kind == QueryNodeKind.SingleNavigationNode) + { + return ((SingleNavigationNode)property).NavigationProperty.Name; + } + else + { + throw new NotSupportedException(); + } + } + + private ComputeTransformationNode BindComputeToken(ComputeToken token) + { + var statements = new List(); + foreach (ComputeExpressionToken statementToken in token.Expressions) + { + var singleValueNode = (SingleValueNode)bindMethod(statementToken.Expression); + statements.Add(new ComputeExpression(singleValueNode, statementToken.Alias, singleValueNode.TypeReference)); + } + + return new ComputeTransformationNode(statements); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyClause.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyClause.cs new file mode 100644 index 0000000..7d495e6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyClause.cs @@ -0,0 +1,177 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + + /// + /// Represents the set of transformations to perform as part of $apply. + /// + public sealed class ApplyClause + { + private readonly IEnumerable transformations; + + private readonly IEnumerable lastAggregateExpressions; + + private readonly IEnumerable lastGroupByPropertyNodes; + + private readonly List lastComputeExpressions; + + /// + /// Create a ApplyClause. + /// + /// The s. + public ApplyClause(IList transformations) + { + ExceptionUtils.CheckArgumentNotNull(transformations, "transformations"); + + this.transformations = transformations; + + for (int i = transformations.Count - 1; i >= 0; i--) + { + if (transformations[i].Kind == TransformationNodeKind.Aggregate) + { + lastAggregateExpressions = (transformations[i] as AggregateTransformationNode).AggregateExpressions; + break; + } + else if (transformations[i].Kind == TransformationNodeKind.GroupBy) + { + var groupByTransformationNode = + transformations[i] as GroupByTransformationNode; + lastGroupByPropertyNodes = groupByTransformationNode.GroupingProperties; + var childTransformation = groupByTransformationNode.ChildTransformations; + if (childTransformation != null && childTransformation.Kind == TransformationNodeKind.Aggregate) + { + lastAggregateExpressions = (childTransformation as AggregateTransformationNode).AggregateExpressions; + } + + break; + } + else if (transformations[i].Kind == TransformationNodeKind.Compute) + { + lastComputeExpressions = lastComputeExpressions ?? new List(); + lastComputeExpressions.AddRange((transformations[i] as ComputeTransformationNode).Expressions); + } + } + } + + /// + /// The collection of transformations to perform. + /// + public IEnumerable Transformations + { + get + { + return this.transformations; + } + } + + internal string GetContextUri() + { + IEnumerable computeExpressions = transformations.OfType().SelectMany(n => n.Expressions); + return CreatePropertiesUriSegment(lastGroupByPropertyNodes, lastAggregateExpressions, computeExpressions); + } + + internal HashSet GetLastAggregatedPropertyNames() + { + if (lastAggregateExpressions == null && lastComputeExpressions == null && lastGroupByPropertyNodes == null) + { + return null; + } + + HashSet result = new HashSet(); + if (lastAggregateExpressions != null) + { + result.UnionWith(lastAggregateExpressions.Select(statement => new EndPathToken(statement.Alias, null))); + } + + if (lastComputeExpressions != null) + { + result.UnionWith(lastComputeExpressions.Select(statement => new EndPathToken(statement.Alias, null))); + } + + if (lastGroupByPropertyNodes != null) + { + result.UnionWith(GetGroupByPaths(lastGroupByPropertyNodes, null)); + } + + return result; + } + + private IEnumerable GetGroupByPaths(IEnumerable nodes, EndPathToken token) + { + foreach (var node in nodes) + { + var nodeToken = new EndPathToken(node.Name, token); + if (node.ChildTransformations == null || !node.ChildTransformations.Any()) + { + yield return nodeToken; + } + else + { + foreach (var child in GetGroupByPaths(node.ChildTransformations, nodeToken)) + { + yield return child; + } + } + } + } + + private string CreatePropertiesUriSegment( + IEnumerable groupByPropertyNodes, + IEnumerable aggregateExpressions, + IEnumerable computeExpressions) + { + string result = string.Empty; + if (groupByPropertyNodes != null) + { + var groupByPropertyArray = + groupByPropertyNodes.Select(prop => prop.Name + CreatePropertiesUriSegment(prop.ChildTransformations, null, null)) + .ToArray(); + result = string.Join(",", groupByPropertyArray); + result = aggregateExpressions == null + ? result + : string.Format(CultureInfo.InvariantCulture, "{0},{1}", result, CreateAggregatePropertiesUriSegment(aggregateExpressions)); + } + else + { + result = aggregateExpressions == null + ? string.Empty + : CreateAggregatePropertiesUriSegment(aggregateExpressions); + } + + if (computeExpressions != null) + { + string computeProperties = string.Join(",", computeExpressions.Select(e => e.Alias).ToArray()); + if (!string.IsNullOrEmpty(computeProperties)) + { + result = string.IsNullOrEmpty(result) + ? computeProperties + : string.Format(CultureInfo.InvariantCulture, "{0},{1}", result, computeProperties); + } + } + + return string.IsNullOrEmpty(result) + ? result + : ODataConstants.ContextUriProjectionStart + result + ODataConstants.ContextUriProjectionEnd; + } + + private static string CreateAggregatePropertiesUriSegment(IEnumerable aggregateExpressions) + { + if (aggregateExpressions != null) + { + return string.Join(",", aggregateExpressions.Select(statement => statement.Alias).ToArray()); + } + else + { + return string.Empty; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyTransformationToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyTransformationToken.cs new file mode 100644 index 0000000..b278704 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ApplyTransformationToken.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser.Aggregation +#endif +{ + /// + /// Base class for Apply transformation tokens + /// + public abstract class ApplyTransformationToken : QueryToken + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ComputeTransformationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ComputeTransformationNode.cs new file mode 100644 index 0000000..fa7c7c4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ComputeTransformationNode.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using System.Collections.Generic; + + /// + /// Node representing a compute expression. + /// + public sealed class ComputeTransformationNode : TransformationNode + { + private readonly IEnumerable expressions; + + /// + /// Create a ComputeTransformationNode. + /// + /// A list of . + public ComputeTransformationNode(IEnumerable expressions) + { + ExceptionUtils.CheckArgumentNotNull(expressions, "expressions"); + + this.expressions = expressions; + } + + /// + /// Gets the list of . + /// + public IEnumerable Expressions + { + get + { + return expressions; + } + } + + /// + /// Gets the kind of the transformation node. + /// + public override TransformationNodeKind Kind + { + get + { + return TransformationNodeKind.Compute; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateExpression.cs new file mode 100644 index 0000000..6eccc34 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateExpression.cs @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using System.Collections.Generic; + + /// + /// Represents an aggregate expression that aggregates an entity set. + /// An example of a OData query that generates it can be found in example 59 of OData Extension for Data Aggregation Version 4.0. + /// + public sealed class EntitySetAggregateExpression : AggregateExpressionBase + { + private readonly CollectionNavigationNode expression; + + private readonly IEnumerable children; + + /// Constructor. + /// Navigation node used to create the expression. + /// Children of the expression. + public EntitySetAggregateExpression(CollectionNavigationNode expression, IEnumerable children) + : base(AggregateExpressionKind.EntitySetAggregate, expression.NavigationProperty.Name) + { + this.expression = expression; + this.children = children; + } + + /// Returns the collection navigation node of this expression. + public CollectionNavigationNode Expression + { + get + { + return this.expression; + } + } + + /// Returns the children expression of this expression. + public IEnumerable Children + { + get + { + return this.children; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateToken.cs new file mode 100644 index 0000000..a3e9ee9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/EntitySetAggregateToken.cs @@ -0,0 +1,123 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser.Aggregation +#endif +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Query token representing an Entity Set Aggregate expression. + /// + public sealed class EntitySetAggregateToken : AggregateTokenBase + { + private readonly QueryToken entitySet; + private readonly IEnumerable expressions; + + /// + /// Create an EntitySetAggregateToken. + /// + /// The entity set. + /// The aggregate expressions. + public EntitySetAggregateToken(QueryToken entitySet, IEnumerable expressions) + { + ExceptionUtils.CheckArgumentNotNull(expressions, "expressions"); + this.expressions = expressions; + this.entitySet = entitySet; + } + + /// + /// Gets the kind of this token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.EntitySetAggregateExpression; } + } + + /// + /// Gets the expressions associated with the aggregation token. + /// + public IEnumerable Expressions + { + get + { + return expressions; + } + } + + /// + /// Gets the entity set associated with the aggregation token. + /// + public QueryToken EntitySet + { + get + { + return entitySet; + } + } + + /// + /// Merges two that have the same entity set into one. + /// If the parameters do not share the the entity set, an exception is thrown. + /// + /// First token that is going to be merged. + /// Second token that is going to be merged. + /// + /// Returns a token with the same entitySet as the parameters and with expressions from both objects. + /// + public static EntitySetAggregateToken Merge(EntitySetAggregateToken token1, EntitySetAggregateToken token2) + { + if (token1 == null) + { + return token2; + } + + if (token2 == null) + { + return token1; + } + + ExceptionUtils.Equals(token1.entitySet, token2.entitySet); + return new EntitySetAggregateToken(token1.entitySet, token1.expressions.Concat(token2.expressions)); + } + + /// + /// Returns the path to access the entity set. + /// + /// Returns a that contains the path to access the entity set. + public string Path() + { + List properties = new List(); + QueryToken token = entitySet; + PathToken pathToken = token as PathToken; + + while (pathToken != null) + { + properties.Add(pathToken.Identifier); + pathToken = pathToken.NextToken as PathToken; + } + + properties.Reverse(); + return String.Join("/", properties.ToArray()); + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ExpandTransformationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ExpandTransformationNode.cs new file mode 100644 index 0000000..9dfc66e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/ExpandTransformationNode.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using Microsoft.OData.UriParser; + + /// + /// Node representing a expand transformation. + /// + public sealed class ExpandTransformationNode : TransformationNode + { + private readonly SelectExpandClause expandClause; + + /// + /// Create a ExpandTransformationNode. + /// + /// A representing the metadata bound expand expression. + public ExpandTransformationNode(SelectExpandClause expandClause) + { + ExceptionUtils.CheckArgumentNotNull(expandClause, "expandClause"); + + this.expandClause = expandClause; + } + + /// + /// Gets the representing the metadata bound expand expression. + /// + public SelectExpandClause ExpandClause + { + get + { + return this.expandClause; + } + } + + /// + /// Gets the kind of the transformation node. + /// + public override TransformationNodeKind Kind + { + get + { + return TransformationNodeKind.Expand; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/FilterTransformationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/FilterTransformationNode.cs new file mode 100644 index 0000000..0b55603 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/FilterTransformationNode.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using Microsoft.OData.UriParser; + + /// + /// Node representing a filter transformation. + /// + public sealed class FilterTransformationNode : TransformationNode + { + private readonly FilterClause filterClause; + + /// + /// Create a FilterTransformationNode. + /// + /// A representing the metadata bound filter expression. + public FilterTransformationNode(FilterClause filterClause) + { + ExceptionUtils.CheckArgumentNotNull(filterClause, "filterClause"); + + this.filterClause = filterClause; + } + + /// + /// Gets the representing the metadata bound filter expression. + /// + public FilterClause FilterClause + { + get + { + return this.filterClause; + } + } + + /// + /// Gets the kind of the transformation node. + /// + public override TransformationNodeKind Kind + { + get + { + return TransformationNodeKind.Filter; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByPropertyNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByPropertyNode.cs new file mode 100644 index 0000000..36eeb5b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByPropertyNode.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using System.Collections.Generic; + using Microsoft.OData.Edm; + using Microsoft.OData.UriParser; + + /// + /// A node representing a grouping property. + /// + public sealed class GroupByPropertyNode + { + private IList childTransformations = new List(); + + private IEdmTypeReference typeReference; + + /// + /// Create a GroupByPropertyNode. + /// + /// The name of this node. + /// The of this node. + public GroupByPropertyNode(string name, SingleValueNode expression) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + + this.Name = name; + this.Expression = expression; + } + + /// + /// Create a GroupByPropertyNode. + /// + /// The name of this node. + /// The of this node. + /// The of this node. + public GroupByPropertyNode(string name, SingleValueNode expression, IEdmTypeReference type) + : this(name, expression) + { + this.typeReference = type; + } + + /// + /// Gets the name of this node. + /// + public string Name { get; private set; } + + /// + /// Gets the of this node. + /// + public SingleValueNode Expression { get; private set; } + + /// + /// Gets the of this node. + /// + public IEdmTypeReference TypeReference + { + get + { + if (Expression == null) + { + return null; + } + else + { + return typeReference; + } + } + } + + /// + /// Gets or sets the child transformations s of this node. + /// + public IList ChildTransformations + { + get + { + return childTransformations; + } + + set + { + childTransformations = value; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByToken.cs new file mode 100644 index 0000000..3e4e2f1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByToken.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser.Aggregation +#endif +{ + using System.Collections.Generic; + using Microsoft.OData.UriParser; + + /// + /// Query token representing a GroupBy token. + /// + public sealed class GroupByToken : ApplyTransformationToken + { + private readonly IEnumerable properties; + + private readonly ApplyTransformationToken child; + + /// + /// Create a GroupByToken. + /// + /// The list of group by properties. + /// The child of this token. + public GroupByToken(IEnumerable properties, ApplyTransformationToken child) + { + ExceptionUtils.CheckArgumentNotNull(properties, "properties"); + + this.properties = properties; + this.child = child; + } + + /// + /// Gets the kind of this token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.AggregateGroupBy; } + } + + /// + /// Gets the list of group by properties. + /// + public IEnumerable Properties + { + get { return this.properties; } + } + + /// + /// Gets the child of this token. + /// + public ApplyTransformationToken Child + { + get { return this.child; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByTransformationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByTransformationNode.cs new file mode 100644 index 0000000..6b4e17f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/GroupByTransformationNode.cs @@ -0,0 +1,85 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + using System.Collections.Generic; + using Microsoft.OData.UriParser; + + /// + /// Node representing a groupBy transformation. + /// + public sealed class GroupByTransformationNode : TransformationNode + { + private readonly CollectionNode source; + + private readonly TransformationNode childTransformations; + + private readonly IEnumerable groupingProperties; + + /// + /// Create a GroupByTransformationNode. + /// + /// A list of . + /// The child . + /// The representing the source. + public GroupByTransformationNode( + IList groupingProperties, + TransformationNode childTransformations, + CollectionNode source) + { + ExceptionUtils.CheckArgumentNotNull(groupingProperties, "groupingProperties"); + + this.groupingProperties = groupingProperties; + this.childTransformations = childTransformations; + this.source = source; + } + + /// + /// Gets the list of . + /// + public IEnumerable GroupingProperties + { + get + { + return groupingProperties; + } + } + + /// + /// Gets the child . + /// + public TransformationNode ChildTransformations + { + get + { + return childTransformations; + } + } + + /// + /// Gets the representing the source. + /// + public CollectionNode Source + { + get + { + return source; + } + } + + /// + /// Gets the kind of the transformation node. + /// + public override TransformationNodeKind Kind + { + get + { + return TransformationNodeKind.GroupBy; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNode.cs new file mode 100644 index 0000000..04b79bd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNode.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + /// + /// Base class for all transformation nodes. + /// + public abstract class TransformationNode + { + /// + /// Gets kind of transformation: groupby, aggregate, filter etc. + /// + public abstract TransformationNodeKind Kind + { + get; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNodeKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNodeKind.cs new file mode 100644 index 0000000..038a08f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Aggregation/TransformationNodeKind.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser.Aggregation +{ + /// + /// Enumeration of kinds of transformation nodes. + /// + public enum TransformationNodeKind + { + /// + /// Aggregations of values + /// + Aggregate = 0, + + /// + /// A grouping of values by properties + /// + GroupBy = 1, + + /// + /// A filter clause + /// + Filter = 2, + + /// + /// A Compute expressions + /// + Compute = 3, + + /// + /// A Expand expressions + /// + Expand = 4, + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinaryOperatorBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinaryOperatorBinder.cs new file mode 100644 index 0000000..25e7ec5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinaryOperatorBinder.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class that knows how to bind binary operators. + /// + internal sealed class BinaryOperatorBinder + { + /// + /// Method to use for binding the parent node, if needed. + /// + private readonly Func bindMethod; + + /// + /// Resolver for parsing + /// + private readonly ODataUriResolver resolver; + + /// + /// Constructs a BinaryOperatorBinder with the given method to be used binding the parent token if needed. + /// + /// Method to use for binding the parent token, if needed. + /// Resolver for parsing. + internal BinaryOperatorBinder(Func bindMethod, ODataUriResolver resolver) + { + this.bindMethod = bindMethod; + this.resolver = resolver; + } + + /// + /// Binds a binary operator token. + /// + /// The binary operator token to bind. + /// The bound binary operator token. + internal QueryNode BindBinaryOperator(BinaryOperatorToken binaryOperatorToken) + { + ExceptionUtils.CheckArgumentNotNull(binaryOperatorToken, "binaryOperatorToken"); + + SingleValueNode left = this.GetOperandFromToken(binaryOperatorToken.OperatorKind, binaryOperatorToken.Left); + SingleValueNode right = this.GetOperandFromToken(binaryOperatorToken.OperatorKind, binaryOperatorToken.Right); + + IEdmTypeReference typeReference; + this.resolver.PromoteBinaryOperandTypes(binaryOperatorToken.OperatorKind, ref left, ref right, out typeReference); + + return new BinaryOperatorNode(binaryOperatorToken.OperatorKind, left, right, typeReference); + } + + /// + /// Promote the left and right operand types + /// + /// the operator kind + /// the left operand + /// the right operand + /// Promotion rules for type facets. + internal static void PromoteOperandTypes(BinaryOperatorKind binaryOperatorKind, ref SingleValueNode left, ref SingleValueNode right, TypeFacetsPromotionRules facetsPromotionRules) + { + IEdmTypeReference leftType; + IEdmTypeReference rightType; + if (!TypePromotionUtils.PromoteOperandTypes(binaryOperatorKind, left, right, out leftType, out rightType, facetsPromotionRules)) + { + string leftTypeName = left.TypeReference == null ? "" : left.TypeReference.FullName(); + string rightTypeName = right.TypeReference == null ? "" : right.TypeReference.FullName(); + throw new ODataException(ODataErrorStrings.MetadataBinder_IncompatibleOperandsError(leftTypeName, rightTypeName, binaryOperatorKind)); + } + + left = MetadataBindingUtils.ConvertToTypeIfNeeded(left, leftType); + right = MetadataBindingUtils.ConvertToTypeIfNeeded(right, rightType); + } + + /// + /// Retrieve SingleValueNode bound with given query token. + /// + /// the query token kind + /// the query token + /// the corresponding SingleValueNode + private SingleValueNode GetOperandFromToken(BinaryOperatorKind operatorKind, QueryToken queryToken) + { + SingleValueNode operand = this.bindMethod(queryToken) as SingleValueNode; + if (operand == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_BinaryOperatorOperandNotSingleValue(operatorKind.ToString())); + } + + return operand; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinderBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinderBase.cs new file mode 100644 index 0000000..48f772b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BinderBase.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Base class for binders + /// + internal abstract class BinderBase + { + /// + /// Method to use for binding the parent node, if needed. + /// + protected MetadataBinder.QueryTokenVisitor bindMethod; + + /// + /// State of metadata binding. + /// + protected BindingState state; + + /// + /// Constructor for binderbase. + /// + /// Method to use for binding the parent token, if needed. + /// State of the metadata binding. + protected BinderBase(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(bindMethod, "bindMethod"); + ExceptionUtils.CheckArgumentNotNull(state, "state"); + this.bindMethod = bindMethod; + this.state = state; + } + + /// + /// Resolver for uri parser. + /// + protected ODataUriResolver Resolver + { + get { return state.Configuration.Resolver; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs new file mode 100644 index 0000000..4e6996b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/BindingState.cs @@ -0,0 +1,186 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Encapsulates the state of metadata binding. + /// TODO: finish moving fields from MetadataBinder here and see if anything can be removed. + /// + internal sealed class BindingState + { + /// + /// The configuration used for binding. + /// + private readonly ODataUriParserConfiguration configuration; + + /// + /// The dictionary used to store mappings between Any visitor and corresponding segment paths + /// + private readonly Stack rangeVariables = new Stack(); + + /// + /// If there is a $filter or $orderby, then this member holds the reference to the parameter node for the + /// implicit parameter ($it) for all expressions. + /// + private RangeVariable implicitRangeVariable; + + /// + /// The current recursion depth of binding. + /// + private int BindingRecursionDepth; + + /// + /// Collection of query option tokens associated with the current query being processed. + /// If a given query option is bound it should be removed from this collection. + /// + private List queryOptions; + + /// + /// The parsed segments in path and query option. + /// + private List parsedSegments = new List(); + + /// + /// Constructs a with the given . + /// + /// The configuration used for binding. + internal BindingState(ODataUriParserConfiguration configuration) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + this.configuration = configuration; + this.BindingRecursionDepth = 0; + } + + internal BindingState(ODataUriParserConfiguration configuration, List parsedSegments) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + this.configuration = configuration; + this.BindingRecursionDepth = 0; + this.parsedSegments = parsedSegments; + } + + /// + /// The model used for binding. + /// + internal IEdmModel Model + { + get + { + return this.configuration.Model; + } + } + + /// + /// The configuration used for binding. + /// + internal ODataUriParserConfiguration Configuration + { + get + { + return this.configuration; + } + } + + /// + /// If there is a $filter or $orderby, then this member holds the reference to the parameter node for the + /// implicit parameter ($it) for all expressions. + /// + internal RangeVariable ImplicitRangeVariable + { + get + { + return this.implicitRangeVariable; + } + + set + { + Debug.Assert(this.implicitRangeVariable == null || value == null, "This should only get set once when first starting to bind a tree."); + this.implicitRangeVariable = value; + } + } + + /// + /// The dictionary used to store mappings between Any visitor and corresponding segment paths + /// + internal Stack RangeVariables + { + get + { + return this.rangeVariables; + } + } + + /// + /// Collection of query option tokens associated with the current query being processed. + /// If a given query option is bound it should be removed from this collection. + /// + internal List QueryOptions + { + get + { + return this.queryOptions; + } + + set + { + this.queryOptions = value; + } + } + + /// + /// Collection of aggregated property names after applying an aggregate transformation. + /// + internal HashSet AggregatedPropertyNames { get; set; } + + /// + /// The property set when group by or aggregation is done and properties are collapsed out of scope + /// + internal bool IsCollapsed { get; set; } + + /// + /// The property set when entering entity set aggregation context + /// + internal bool InEntitySetAggregation { get; set; } + + /// + /// The parsed segments in path and query option. + /// + internal List ParsedSegments + { + get { return parsedSegments; } + } + + /// + /// Marks the fact that a recursive method was entered, and checks that the depth is allowed. + /// + internal void RecurseEnter() + { + this.BindingRecursionDepth++; + + // TODO: add BindingLimit, use uniform error message + if (this.BindingRecursionDepth > this.configuration.Settings.FilterLimit) + { + throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_TooDeep); + } + } + + /// + /// Marks the fact that a recursive method is leaving. + /// + internal void RecurseLeave() + { + Debug.Assert(this.BindingRecursionDepth > 0, "Decreasing recursion depth below zero, imbalanced recursion calls."); + + this.BindingRecursionDepth--; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ComputeBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ComputeBinder.cs new file mode 100644 index 0000000..397a21c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ComputeBinder.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + internal sealed class ComputeBinder + { + private MetadataBinder.QueryTokenVisitor bindMethod; + + public ComputeBinder(MetadataBinder.QueryTokenVisitor bindMethod) + { + this.bindMethod = bindMethod; + } + + public ComputeClause BindCompute(ComputeToken token) + { + ExceptionUtils.CheckArgumentNotNull(token, "token"); + + List transformations = new List(); + foreach (ComputeExpressionToken expression in token.Expressions) + { + ComputeExpression compute = this.BindComputeExpressionToken(expression); + transformations.Add(compute); + } + + return new ComputeClause(transformations); + } + + private ComputeExpression BindComputeExpressionToken(ComputeExpressionToken token) + { + SingleValueNode node = this.bindMethod(token.Expression) as SingleValueNode; + ComputeExpression expression = new ComputeExpression(node, token.Alias, node.TypeReference); + + return expression; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/DottedIdentifierBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/DottedIdentifierBinder.cs new file mode 100644 index 0000000..dacf56e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/DottedIdentifierBinder.cs @@ -0,0 +1,117 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Class that knows how to bind CastTokens. + /// + internal sealed class DottedIdentifierBinder : BinderBase + { + /// + /// Constructs a DottedIdentifierBinder with the given method to be used binding the parent token if needed. + /// + /// Method to use for binding the parent token, if needed. + /// State of the metadata binding. + internal DottedIdentifierBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) + : base(bindMethod, state) + { + } + + /// + /// Binds a DottedIdentifierToken and it's parent node (if needed). + /// + /// Token to bind to metadata. + /// A bound node representing the cast. + internal QueryNode BindDottedIdentifier(DottedIdentifierToken dottedIdentifierToken) + { + ExceptionUtils.CheckArgumentNotNull(dottedIdentifierToken, "castToken"); + ExceptionUtils.CheckArgumentNotNull(state, "state"); + + QueryNode parent = null; + IEdmType parentType = null; + if (state.ImplicitRangeVariable != null) + { + if (dottedIdentifierToken.NextToken == null) + { + parent = NodeFactory.CreateRangeVariableReferenceNode(state.ImplicitRangeVariable); + parentType = state.ImplicitRangeVariable.TypeReference.Definition; + } + else + { + parent = this.bindMethod(dottedIdentifierToken.NextToken); + parentType = parent.GetEdmType(); + } + } + + SingleResourceNode parentAsSingleResource = parent as SingleResourceNode; + IEdmSchemaType childType = UriEdmHelpers.FindTypeFromModel(state.Model, dottedIdentifierToken.Identifier, this.Resolver); + IEdmStructuredType childStructuredType = childType as IEdmStructuredType; + if (childStructuredType == null) + { + SingleValueNode singleValueNode = parent as SingleValueNode; + FunctionCallBinder functionCallBinder = new FunctionCallBinder(bindMethod, state); + QueryNode functionCallNode; + if (functionCallBinder.TryBindDottedIdentifierAsFunctionCall(dottedIdentifierToken, singleValueNode, out functionCallNode)) + { + return functionCallNode; + } + else if ((!string.IsNullOrEmpty(dottedIdentifierToken.Identifier)) + && (dottedIdentifierToken.Identifier[dottedIdentifierToken.Identifier.Length - 1] == '\'')) + { + // check if it is enum or not + QueryNode enumNode; + if (EnumBinder.TryBindDottedIdentifierAsEnum(dottedIdentifierToken, parentAsSingleResource, state, this.Resolver, out enumNode)) + { + return enumNode; + } + else + { + throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(dottedIdentifierToken.Identifier)); + } + } + else + { + IEdmTypeReference edmTypeReference = UriEdmHelpers.FindTypeFromModel(state.Model, dottedIdentifierToken.Identifier, this.Resolver).ToTypeReference(); + if (edmTypeReference is IEdmPrimitiveTypeReference || edmTypeReference is IEdmEnumTypeReference) + { + IEdmPrimitiveType childPrimitiveType = childType as IEdmPrimitiveType; + if (childPrimitiveType != null && dottedIdentifierToken.NextToken != null) + { + return new SingleValueCastNode(singleValueNode, childPrimitiveType); + } + else + { + return new ConstantNode(dottedIdentifierToken.Identifier, dottedIdentifierToken.Identifier); + } + } + else + { + throw new ODataException(ODataErrorStrings.CastBinder_ChildTypeIsNotEntity(dottedIdentifierToken.Identifier)); + } + } + } + + // Check whether childType is a derived type of the type of its parent node + UriEdmHelpers.CheckRelatedTo(parentType, childType); + + this.state.ParsedSegments.Add(new TypeSegment(childType, parentType, null)); + + CollectionResourceNode parentAsCollection = parent as CollectionResourceNode; + if (parentAsCollection != null) + { + return new CollectionResourceCastNode(parentAsCollection, childStructuredType); + } + + return new SingleResourceCastNode(parentAsSingleResource, childStructuredType); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs new file mode 100644 index 0000000..9e8b6fe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EndPathBinder.cs @@ -0,0 +1,236 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class that knows how to bind an end path token, which could be several things. + /// + internal sealed class EndPathBinder : BinderBase + { + /// + /// The function call binder to use to bind this end path to a function if necessary. + /// + private readonly FunctionCallBinder functionCallBinder; + + /// + /// Constructs a EndPathBinder object using the given function to bind parent token. + /// + /// Method to bind the EndPathToken's parent, if there is one. + /// State of the metadata binding.am> + /// + /// Make bindMethod of return type SingleValueQueryNode. + /// + internal EndPathBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) + : base(bindMethod, state) + { + this.functionCallBinder = new FunctionCallBinder(bindMethod, state); + } + + /// + /// Generates a bound query node representing an given an already semantically bound parent node. + /// + /// The semantically bound source node of this end path token + /// The that will be bound to this node. Must not be primitive collection + /// The state of binding. + /// QueryNode bound to this property. + internal static QueryNode GeneratePropertyAccessQueryNode(SingleResourceNode parentNode, IEdmProperty property, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(parentNode, "parentNode"); + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + // TODO: Remove this check. + // We should verify that the top level of an expression is a bool rather than arbitrarily restrict property types. + // We can get here if there is a query like $filter=MyCollectionProperty eq 'foo' or something. + // If it was $filter=MyCollectionProperty/any(...) then we would have gone down the 'NonRootSegment' code path instead of this one + if (property.Type.IsNonEntityCollectionType()) + { + // if this happens to be a top level node (i.e. $filter=MyCollection), then it will fail further up the chain, so + // don't need to worry about checking for that here. + if (property.Type.IsStructuredCollectionType()) + { + return new CollectionComplexNode(parentNode, property); + } + else + { + return new CollectionPropertyAccessNode(parentNode, property); + } + } + + if (property.PropertyKind == EdmPropertyKind.Navigation) + { + // These are error cases in practice, but we let ourselves throw later for better context-sensitive error messages + IEdmNavigationProperty edmNavigationProperty = (IEdmNavigationProperty)property; + if (edmNavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) + { + return new CollectionNavigationNode(parentNode, edmNavigationProperty, state.ParsedSegments); + } + + return new SingleNavigationNode(parentNode, edmNavigationProperty, state.ParsedSegments); + } + + if (property.Type.IsComplex()) + { + return new SingleComplexNode(parentNode, property); + } + + return new SingleValuePropertyAccessNode(parentNode, property); + } + + /// + /// Constructs parent node from binding state + /// + /// Current binding state + /// The parent node. + internal static SingleValueNode CreateParentFromImplicitRangeVariable(BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(state, "state"); + + // If the Parent is null, then it must be referring to the implicit $it parameter + if (state.ImplicitRangeVariable == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyAccessWithoutParentParameter); + } + + return NodeFactory.CreateRangeVariableReferenceNode(state.ImplicitRangeVariable); + } + + /// + /// This method generates a for properties of open type + /// + /// EndPathToken to bind into an open property node. + /// Parent node of this open property + /// Will return a when open types are supported + internal SingleValueOpenPropertyAccessNode GeneratePropertyAccessQueryForOpenType(EndPathToken endPathToken, SingleValueNode parentNode) + { + if (parentNode.TypeReference == null || + parentNode.TypeReference.Definition.IsOpen() || + IsAggregatedProperty(endPathToken)) + { + return new SingleValueOpenPropertyAccessNode(parentNode, endPathToken.Identifier); + } + else + { + throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared( + parentNode.TypeReference.FullName(), + endPathToken.Identifier)); + } + } + + /// + /// Binds an end path token into a PropertyAccessToken, OpenPropertyToken, or FunctionCallToken. + /// + /// The property access token to bind. + /// A Query node representing this endpath token, bound to metadata. + internal QueryNode BindEndPath(EndPathToken endPathToken) + { + ExceptionUtils.CheckArgumentNotNull(endPathToken, "EndPathToken"); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(endPathToken.Identifier, "EndPathToken.Identifier"); + + // Set the parent (get the parent type, so you can check whether the Identifier inside EndPathToken really is legit offshoot of the parent type) + QueryNode parent = this.DetermineParentNode(endPathToken); + + QueryNode boundFunction; + + SingleValueNode singleValueParent = parent as SingleValueNode; + if (singleValueParent != null) + { + if (endPathToken.Identifier == ExpressionConstants.QueryOptionCount) + { + return new CountVirtualPropertyNode(); + } + + if (state.IsCollapsed && !IsAggregatedProperty(endPathToken)) + { + throw new ODataException(ODataErrorStrings.ApplyBinder_GroupByPropertyNotPropertyAccessValue(endPathToken.Identifier)); + } + + // Now that we have the parent type, can find its corresponding EDM type + IEdmStructuredTypeReference structuredParentType = + singleValueParent.TypeReference == null ? null : singleValueParent.TypeReference.AsStructuredOrNull(); + + IEdmProperty property = + structuredParentType == null ? null : this.Resolver.ResolveProperty(structuredParentType.StructuredDefinition(), endPathToken.Identifier); + + if (property != null) + { + return GeneratePropertyAccessQueryNode(singleValueParent as SingleResourceNode, property, state); + } + + if (functionCallBinder.TryBindEndPathAsFunctionCall(endPathToken, singleValueParent, state, out boundFunction)) + { + return boundFunction; + } + + return GeneratePropertyAccessQueryForOpenType(endPathToken, singleValueParent); + } + + // Collection with any or all expression is already supported and handled separately. + // Add support of collection with $count segment. + CollectionNode colNode = parent as CollectionNode; + if (colNode != null && endPathToken.Identifier.Equals(UriQueryConstants.CountSegment)) + { + // create a collection count node for collection node property. + return new CountNode(colNode); + } + + CollectionNavigationNode collectionParent = parent as CollectionNavigationNode; + if (collectionParent != null) + { + IEdmEntityTypeReference parentType = collectionParent.EntityItemType; + IEdmProperty property = this.Resolver.ResolveProperty(parentType.StructuredDefinition(), endPathToken.Identifier); + + if (property.PropertyKind == EdmPropertyKind.Structural + && !property.Type.IsCollection() + && this.state.InEntitySetAggregation) + { + return new AggregatedCollectionPropertyNode(collectionParent, property); + } + } + + if (functionCallBinder.TryBindEndPathAsFunctionCall(endPathToken, parent, state, out boundFunction)) + { + return boundFunction; + } + + throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyAccessSourceNotSingleValue(endPathToken.Identifier)); + } + + /// + /// Determines the parent node. If the token has a parent, that token is bound. If not, then we + /// use the implicit parameter from the BindingState as the parent node. + /// + /// Token to determine the parent node for. + /// A SingleValueQueryNode that is the parent node of the . + private QueryNode DetermineParentNode(EndPathToken segmentToken) + { + ExceptionUtils.CheckArgumentNotNull(segmentToken, "segmentToken"); + ExceptionUtils.CheckArgumentNotNull(state, "state"); + + if (segmentToken.NextToken != null) + { + return this.bindMethod(segmentToken.NextToken); + } + else + { + RangeVariable implicitRangeVariable = state.ImplicitRangeVariable; + return NodeFactory.CreateRangeVariableReferenceNode(implicitRangeVariable); + } + } + + /// + /// Determines the token if represents an aggregated property or not. + /// + /// EndPathToken to check in the list of aggregated properties. + /// Whether the token represents an aggregated property. + private bool IsAggregatedProperty(EndPathToken endPath) => state?.AggregatedPropertyNames?.Contains(endPath) ?? false; + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EnumBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EnumBinder.cs new file mode 100644 index 0000000..e89923f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/EnumBinder.cs @@ -0,0 +1,134 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.UriParser +{ + using System.Diagnostics; + using System.Globalization; + using Microsoft.OData.Edm; + + /// + /// Enum binder + /// + internal sealed class EnumBinder + { + /// + /// Try to bind a dotted identifier as enum node + /// + /// a dotted identifier token + /// the parent node + /// the current state of the binding algorithm + /// ODataUriResolver + /// the output bound enum node + /// true if we bound an enum node, false otherwise. + internal static bool TryBindDottedIdentifierAsEnum(DottedIdentifierToken dottedIdentifierToken, SingleValueNode parent, BindingState state, ODataUriResolver resolver, out QueryNode boundEnum) + { + return TryBindIdentifier(dottedIdentifierToken.Identifier, null, state.Model, resolver, out boundEnum); + } + + /// + /// Try to bind an identifier to a EnumNode + /// + /// the identifier to bind + /// the enum typeReference + /// the current model when no enum typeReference. + /// an enum node . + /// true if we bound an enum for this token. + internal static bool TryBindIdentifier(string identifier, IEdmEnumTypeReference typeReference, IEdmModel modelWhenNoTypeReference, out QueryNode boundEnum) + { + return TryBindIdentifier(identifier, typeReference, modelWhenNoTypeReference, null, out boundEnum); + } + + /// + /// Try to bind an identifier to a EnumNode + /// + /// the identifier to bind + /// the enum typeReference + /// the current model when no enum typeReference. + /// ODataUriResolver . + /// an enum node . + /// true if we bound an enum for this token. + internal static bool TryBindIdentifier(string identifier, IEdmEnumTypeReference typeReference, IEdmModel modelWhenNoTypeReference, ODataUriResolver resolver, out QueryNode boundEnum) + { + boundEnum = null; + string text = identifier; + + // parse the string, e.g., NS.Color'Green' + // get type information, and also convert Green into an ODataEnumValue + + // find the first ', before that, it is namespace.type + int indexOfSingleQuote = text.IndexOf('\''); + if (indexOfSingleQuote < 0) + { + return false; + } + + string namespaceAndType = text.Substring(0, indexOfSingleQuote); + Debug.Assert((typeReference == null) || (modelWhenNoTypeReference == null), "((typeReference == null) || (modelWhenNoTypeReference == null)"); + + // validate typeReference but allow type name not found in model for delayed throwing. + if ((typeReference != null) && !string.Equals(namespaceAndType, typeReference.FullName(), StringComparison.Ordinal)) + { + return false; + } + + // get the type + IEdmEnumType enumType = typeReference != null + ? + (IEdmEnumType)typeReference.Definition + : + UriEdmHelpers.FindEnumTypeFromModel(modelWhenNoTypeReference, namespaceAndType, resolver); + if (enumType == null) + { + return false; + } + + // now, find out the value + UriParserHelper.TryRemovePrefix(namespaceAndType, ref text); + UriParserHelper.TryRemoveQuotes(ref text); + + // parse string or int value to edm enum value + string enumValueString = text; + ODataEnumValue enumValue; + + if (!TryParseEnum(enumType, enumValueString, out enumValue)) + { + return false; + } + + // create an enum node, enclosing an odata enum value + IEdmEnumTypeReference enumTypeReference = typeReference ?? new EdmEnumTypeReference(enumType, false); + boundEnum = new ConstantNode(enumValue, identifier, enumTypeReference); + + return true; + } + + + /// + /// Parse string or integer to enum value + /// + /// edm enum type + /// input string value + /// output edm enum value + /// true if parse succeeds, false if fails + internal static bool TryParseEnum(IEdmEnumType enumType, string value, out ODataEnumValue enumValue) + { + long parsedValue; + bool success = enumType.TryParseEnum(value, true, out parsedValue); + enumValue = null; + if (success) + { + // ODataEnumValue.Value will always be numeric string like '3', '10' instead of 'Cyan', 'Solid,Yellow', etc. + // so user code can easily Enum.Parse() them into CLR value. + enumValue = new ODataEnumValue(parsedValue.ToString(CultureInfo.InvariantCulture), enumType.FullTypeName()); + } + + return success; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ExpandTreeNormalizer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ExpandTreeNormalizer.cs new file mode 100644 index 0000000..013a4c0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ExpandTreeNormalizer.cs @@ -0,0 +1,228 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Translator from the old expand syntax tree to the new Expand Option syntax tree + /// + internal sealed class ExpandTreeNormalizer + { + /// + /// Normalize an expand syntax tree into the new ExpandOption syntax. + /// + /// the tree to normalize + /// a new tree, in the new ExpandOption syntax + public ExpandToken NormalizeExpandTree(ExpandToken treeToNormalize) + { + // To normalize the expand tree we need to + // 1) invert the path tree on each of its expand term tokens + // 2) combine terms that start with the path tree + ExpandToken invertedPathTree = this.NormalizePaths(treeToNormalize); + + return CombineTerms(invertedPathTree); + } + + /// + /// Invert the all of the paths in an expandToken, such that they are now in the same order as they are present in the + /// base url + /// + /// the tree to invert paths on + /// a new tree with all of its paths inverted + public ExpandToken NormalizePaths(ExpandToken treeToInvert) + { + // iterate through each expand term token, and reverse the tree in its path property + List updatedTerms = new List(); + foreach (ExpandTermToken term in treeToInvert.ExpandTerms) + { + PathReverser pathReverser = new PathReverser(); + PathSegmentToken reversedPath = term.PathToNavigationProp.Accept(pathReverser); + + // we also need to call the select token normalizer for this level to reverse the select paths + SelectToken newSelectToken = term.SelectOption; + if (term.SelectOption != null) + { + newSelectToken = SelectTreeNormalizer.NormalizeSelectTree(term.SelectOption); + } + + ExpandToken subExpandTree; + if (term.ExpandOption != null) + { + subExpandTree = this.NormalizePaths(term.ExpandOption); + } + else + { + subExpandTree = null; + } + + ExpandTermToken newTerm = new ExpandTermToken(reversedPath, term.FilterOption, term.OrderByOptions, term.TopOption, term.SkipOption, term.CountQueryOption, term.LevelsOption, term.SearchOption, newSelectToken, subExpandTree, term.ComputeOption, term.ApplyOptions); + updatedTerms.Add(newTerm); + } + + return new ExpandToken(updatedTerms); + } + + /// + /// Collapse all redundant terms in an expand tree + /// + /// the tree to collapse + /// A new tree with all redundant terms collapsed. + public ExpandToken CombineTerms(ExpandToken treeToCollapse) + { + var combinedTerms = new Dictionary(new PathSegmentTokenEqualityComparer()); + foreach (ExpandTermToken termToken in treeToCollapse.ExpandTerms) + { + ExpandTermToken finalTermToken = termToken; + if (termToken.ExpandOption != null) + { + ExpandToken newSubExpand = CombineTerms(termToken.ExpandOption); + finalTermToken = new ExpandTermToken( + termToken.PathToNavigationProp, + termToken.FilterOption, + termToken.OrderByOptions, + termToken.TopOption, + termToken.SkipOption, + termToken.CountQueryOption, + termToken.LevelsOption, + termToken.SearchOption, + RemoveDuplicateSelect(termToken.SelectOption), + newSubExpand, + termToken.ComputeOption, + termToken.ApplyOptions); + } + + AddOrCombine(combinedTerms, finalTermToken); + } + + return new ExpandToken(combinedTerms.Values); + } + + /// + /// add a new expandTermToken into an exisiting token, adding any additional levels and trees along the way. + /// + /// the exisiting (already expanded) token + /// the new (already expanded) token + /// the combined token, or, if the two are mutually exclusive, the same tokens + public ExpandTermToken CombineTerms(ExpandTermToken existingToken, ExpandTermToken newToken) + { + Debug.Assert(new PathSegmentTokenEqualityComparer().Equals(existingToken.PathToNavigationProp, newToken.PathToNavigationProp), "Paths should be equal."); + + List childNodes = CombineChildNodes(existingToken, newToken).ToList(); + SelectToken combinedSelects = CombineSelects(existingToken, newToken); + return new ExpandTermToken( + existingToken.PathToNavigationProp, + existingToken.FilterOption, + existingToken.OrderByOptions, + existingToken.TopOption, + existingToken.SkipOption, + existingToken.CountQueryOption, + existingToken.LevelsOption, + existingToken.SearchOption, + combinedSelects, + new ExpandToken(childNodes), + existingToken.ComputeOption, + existingToken.ApplyOptions); + } + + /// + /// Combine the child nodes of twoExpandTermTokens into one list of tokens + /// + /// the existing token to to + /// the new token containing terms to add + /// a combined list of the all child nodes of the two tokens. + public IEnumerable CombineChildNodes(ExpandTermToken existingToken, ExpandTermToken newToken) + { + if (existingToken.ExpandOption == null && newToken.ExpandOption == null) + { + return new List(); + } + + var childNodes = new Dictionary(new PathSegmentTokenEqualityComparer()); + if (existingToken.ExpandOption != null) + { + AddChildOptionsToDictionary(existingToken, childNodes); + } + + if (newToken.ExpandOption != null) + { + AddChildOptionsToDictionary(newToken, childNodes); + } + + return childNodes.Values; + } + + /// + /// Add child options to a new dictionary + /// + /// the token with child nodes to add to the dictionary + /// dictionary to add child nodes to + private void AddChildOptionsToDictionary(ExpandTermToken newToken, Dictionary combinedTerms) + { + foreach (ExpandTermToken expandedTerm in newToken.ExpandOption.ExpandTerms) + { + AddOrCombine(combinedTerms, expandedTerm); + } + } + + /// + /// Adds the expand token to the dictionary or combines it with an existing or combines it with another existing token with an equivalent path. + /// + /// The combined terms dictionary. + /// The expanded term to add or combine. + private void AddOrCombine(IDictionary combinedTerms, ExpandTermToken expandedTerm) + { + ExpandTermToken existingTerm; + if (combinedTerms.TryGetValue(expandedTerm.PathToNavigationProp, out existingTerm)) + { + combinedTerms[expandedTerm.PathToNavigationProp] = CombineTerms(expandedTerm, existingTerm); + } + else + { + combinedTerms.Add(expandedTerm.PathToNavigationProp, expandedTerm); + } + } + + /// + /// Combine together the select clauses of two ExpandTermTokens + /// + /// the already existing expand term token + /// the new expand term token to be added + /// A new select term containing each of the selected entries. + private static SelectToken CombineSelects(ExpandTermToken existingToken, ExpandTermToken newToken) + { + if (existingToken.SelectOption == null) + { + return newToken.SelectOption; + } + + if (newToken.SelectOption == null) + { + return existingToken.SelectOption; + } + + List newSelects = existingToken.SelectOption.Properties.ToList(); + newSelects.AddRange(newToken.SelectOption.Properties); + return new SelectToken(newSelects.Distinct(new PathSegmentTokenEqualityComparer())); + } + + /// + /// Get rid of duplicate selected item in SelectToken + /// + /// Select token to be dealt with + /// A new select term containing each of the + private static SelectToken RemoveDuplicateSelect(SelectToken selectToken) + { + return selectToken != null + ? new SelectToken(selectToken.Properties.Distinct(new PathSegmentTokenEqualityComparer())) + : null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FilterBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FilterBinder.cs new file mode 100644 index 0000000..ae3e5fe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FilterBinder.cs @@ -0,0 +1,74 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class responsible for binding a syntactic filter expression into a bound tree of semantic nodes. + /// + internal sealed class FilterBinder + { + /// + /// Method to use to visit the token tree and bind the tokens recursively. + /// + private readonly MetadataBinder.QueryTokenVisitor bindMethod; + + /// + /// State to use for binding. + /// + private readonly BindingState state; + + /// + /// Creates a FilterBinder. + /// + /// Method to use to visit the token tree and bind the tokens recursively. + /// State to use for binding. + internal FilterBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) + { + this.bindMethod = bindMethod; + this.state = state; + } + + /// + /// Binds the given filter token. + /// + /// The filter token to bind. + /// A FilterNode with the given path linked to it (if provided). + internal FilterClause BindFilter(QueryToken filter) + { + ExceptionUtils.CheckArgumentNotNull(filter, "filter"); + + QueryNode expressionNode = this.bindMethod(filter); + + SingleValueNode expressionResultNode = expressionNode as SingleValueNode; + if (expressionResultNode == null || + (expressionResultNode.TypeReference != null && !expressionResultNode.TypeReference.IsODataPrimitiveTypeKind())) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_FilterExpressionNotSingleValue); + } + + // The type may be null here if the query statically represents the null literal or an open property. + IEdmTypeReference expressionResultType = expressionResultNode.TypeReference; + if (expressionResultType != null) + { + IEdmPrimitiveTypeReference primitiveExpressionResultType = expressionResultType.AsPrimitiveOrNull(); + if (primitiveExpressionResultType == null || + primitiveExpressionResultType.PrimitiveKind() != EdmPrimitiveTypeKind.Boolean) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_FilterExpressionNotSingleValue); + } + } + + FilterClause filterNode = new FilterClause(expressionResultNode, this.state.ImplicitRangeVariable); + + return filterNode; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs new file mode 100644 index 0000000..b4b8014 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs @@ -0,0 +1,833 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Class that knows how to bind function call tokens. + /// + internal sealed class FunctionCallBinder : BinderBase + { + /// + /// The names of functions that we don't bind to BuiltInFunctions + /// + private static readonly string[] UnboundFunctionNames = new string[] + { + ExpressionConstants.UnboundFunctionCast, + ExpressionConstants.UnboundFunctionIsOf, + }; + + /// + /// Constructs a FunctionCallBinder with the given method to be used binding the parent token if needed. + /// + /// Method to use for binding the parent token, if needed. + /// State of the metadata binding. + internal FunctionCallBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) + : base(bindMethod, state) + { + } + + /// + /// Promotes types of arguments to match signature if possible. + /// + /// The signature to match the types to. + /// The types to promote. + internal static void TypePromoteArguments(FunctionSignatureWithReturnType signature, List argumentNodes) + { + // Convert all argument nodes to the best signature argument type + Debug.Assert(signature.ArgumentTypes.Length == argumentNodes.Count, "The best signature match doesn't have the same number of arguments."); + for (int i = 0; i < argumentNodes.Count; i++) + { + Debug.Assert(argumentNodes[i] is SingleValueNode, "We should have already verified that all arguments are single values."); + SingleValueNode argumentNode = (SingleValueNode)argumentNodes[i]; + IEdmTypeReference signatureArgumentType = signature.ArgumentTypes[i]; + Debug.Assert(signatureArgumentType.IsODataPrimitiveTypeKind() || signatureArgumentType.IsODataEnumTypeKind(), "Only primitive or enum types should be able to get here."); + argumentNodes[i] = MetadataBindingUtils.ConvertToTypeIfNeeded(argumentNode, signatureArgumentType); + } + } + + /// + /// Checks that all arguments are SingleValueNodes + /// + /// The name of the function the arguments are from. + /// The arguments to validate. + /// SingleValueNode array + internal static SingleValueNode[] ValidateArgumentsAreSingleValue(string functionName, List argumentNodes) + { + ExceptionUtils.CheckArgumentNotNull(functionName, "functionCallToken"); + ExceptionUtils.CheckArgumentNotNull(argumentNodes, "argumentNodes"); + + // Right now all functions take a single value for all arguments + SingleValueNode[] ret = new SingleValueNode[argumentNodes.Count]; + for (int i = 0; i < argumentNodes.Count; i++) + { + SingleValueNode argumentNode = argumentNodes[i] as SingleValueNode; + if (argumentNode == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_FunctionArgumentNotSingleValue(functionName)); + } + + ret[i] = argumentNode; + } + + return ret; + } + + /// + /// Finds the signature that best matches the arguments + /// + /// The name of the function + /// The nodes of the arguments, can be new {null,null}. + /// The name-signature pairs to match against + /// Returns the matching signature or throws + internal static KeyValuePair MatchSignatureToUriFunction(string functionCallToken, SingleValueNode[] argumentNodes, + IList> nameSignatures) + { + KeyValuePair nameSignature; + + IEdmTypeReference[] argumentTypes = argumentNodes.Select(s => s.TypeReference).ToArray(); + + // Handle the cases where we don't have type information (null literal, open properties) for ANY of the arguments + int argumentCount = argumentTypes.Length; + if (argumentTypes.All(a => a == null) && argumentCount > 0) + { + // we specifically want to find just the first function that matches the number of arguments, we don't care about + // ambiguity here because we're already in an ambiguous case where we don't know what kind of types + // those arguments are. + KeyValuePair found = nameSignatures.FirstOrDefault(pair => pair.Value.ArgumentTypes.Count() == argumentCount); + if (found.Equals(TypePromotionUtils.NotFoundKeyValuePair)) + { + throw new ODataException(ODataErrorStrings.FunctionCallBinder_CannotFindASuitableOverload(functionCallToken, argumentTypes.Count())); + } + else + { + // in this case we can't assert the return type, we can only assert that a function exists... so + // we need to set the return type to null. + nameSignature = new KeyValuePair( + found.Key, new FunctionSignatureWithReturnType(null, found.Value.ArgumentTypes)); + } + } + else + { + nameSignature = + TypePromotionUtils.FindBestFunctionSignature(nameSignatures, argumentNodes, functionCallToken); + if (nameSignature.Equals(TypePromotionUtils.NotFoundKeyValuePair)) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_NoApplicableFunctionFound( + functionCallToken, + UriFunctionsHelper.BuildFunctionSignatureListDescription(functionCallToken, nameSignatures.Select(sig => sig.Value)))); + } + } + + return nameSignature; + } + + /// + /// Finds all signatures for the given function name. + /// Search in both BuiltIn uri functions and Custom uri functions. + /// Combine and return the signatures overloads of the results. + /// + /// The function call token to get the signatures for. + /// Optional flag for whether case insensitive match is enabled. + /// The signatures which match the supplied function name. + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "need to use lower characters for built-in functions.")] + internal static IList> GetUriFunctionSignatures(string functionCallToken, bool enableCaseInsensitive = false) + { + IList> customUriFunctionsNameSignatures = null; + FunctionSignatureWithReturnType[] builtInUriFunctionsSignatures = null; + IList> builtInUriFunctionsNameSignatures = null; + + // Try to find the function in the user custom functions + bool customFound = CustomUriFunctions.TryGetCustomFunction(functionCallToken, out customUriFunctionsNameSignatures, + enableCaseInsensitive); + + // And find in our built-in functions + // Since list of all built-in functions is a fixed list and is initialized with names in lower case, + // such as "endswith", "geo.distance", "maxdatetime" and "round", + // => For case-insensitive searching, it is more efficient to convert the search key to lower case first + // and then do a case-sensitive match. + string nameKey = enableCaseInsensitive + ? functionCallToken.ToLowerInvariant() + : functionCallToken; + bool builtInFound = BuiltInUriFunctions.TryGetBuiltInFunction(nameKey, out builtInUriFunctionsSignatures); + + // Populate the matched names found for built-in function + if (builtInFound) + { + builtInUriFunctionsNameSignatures = + builtInUriFunctionsSignatures.Select(sig => new KeyValuePair(nameKey, sig)).ToList(); + } + + if (!customFound && !builtInFound) + { + // Not found in both built-in and custom. + throw new ODataException(ODataErrorStrings.MetadataBinder_UnknownFunction(functionCallToken)); + } + + if (!customFound) + { + Debug.Assert(builtInUriFunctionsNameSignatures != null, "No Built-in functions found"); + return builtInUriFunctionsNameSignatures; + } + + if (!builtInFound) + { + Debug.Assert(customUriFunctionsNameSignatures != null, "No Custom functions found"); + return customUriFunctionsNameSignatures; + } + + return builtInUriFunctionsNameSignatures.Concat(customUriFunctionsNameSignatures).ToArray(); + } + + internal static FunctionSignatureWithReturnType[] ExtractSignatures( + IList> nameSignatures) + { + return nameSignatures.Select(nameSig => nameSig.Value).ToArray(); + } + + /// + /// Binds the token to a SingleValueFunctionCallNode or a SingleResourceFunctionCallNode for complex + /// + /// Token to bind + /// The resulting SingleValueFunctionCallNode/SingleResourceFunctionCallNode + internal QueryNode BindFunctionCall(FunctionCallToken functionCallToken) + { + ExceptionUtils.CheckArgumentNotNull(functionCallToken, "functionCallToken"); + ExceptionUtils.CheckArgumentNotNull(functionCallToken.Name, "functionCallToken.Name"); + + // Bind the parent, if present. + // TODO: parent can be a collection as well, so we need to loosen this to QueryNode. + QueryNode parent = null; + if (state.ImplicitRangeVariable != null) + { + if (functionCallToken.Source != null) + { + parent = this.bindMethod(functionCallToken.Source); + } + else + { + parent = NodeFactory.CreateRangeVariableReferenceNode(state.ImplicitRangeVariable); + } + } + + // First see if there is a custom function for this + QueryNode boundFunction; + if (this.TryBindIdentifier(functionCallToken.Name, functionCallToken.Arguments, parent, state, out boundFunction)) + { + return boundFunction; + } + + // then check if there is a global custom function(i.e with out a parent node) + if (this.TryBindIdentifier(functionCallToken.Name, functionCallToken.Arguments, null, state, out boundFunction)) + { + return boundFunction; + } + + // If there isn't, bind as Uri function + // Bind all arguments + List argumentNodes = new List(functionCallToken.Arguments.Select(ar => this.bindMethod(ar))); + return BindAsUriFunction(functionCallToken, argumentNodes); + } + + /// + /// Try to bind an end path token as a function call. Used for bound functions without parameters + /// that parse as end path tokens syntactically + /// + /// the end path token to bind + /// the parent node to this end path token. + /// the current state of the binding algorithm + /// a single value function call node representing the function call, if it exists + /// true if we found a function for this token, false otherwise. + internal bool TryBindEndPathAsFunctionCall(EndPathToken endPathToken, QueryNode parent, BindingState state, out QueryNode boundFunction) + { + return this.TryBindIdentifier(endPathToken.Identifier, null, parent, state, out boundFunction); + } + + /// + /// Try to bind an inner path token as a function call. Used for bound functions without parameters + /// that parse as inner path tokens syntactically + /// + /// the end path token to bind + /// the parent node to this end path token. + /// a single value function call node representing the function call, if it exists + /// true if we found a function for this token, false otherwise. + internal bool TryBindInnerPathAsFunctionCall(InnerPathToken innerPathToken, QueryNode parent, out QueryNode boundFunction) + { + return this.TryBindIdentifier(innerPathToken.Identifier, null, parent, state, out boundFunction); + } + + /// + /// Try to bind a as a function call. Used for container qualified functions without parameters. + /// + /// the dotted identifier token to bind + /// the semantically bound parent node for this dotted identifier + /// a single value function call node representing the function call, if we found one. + /// true if we found a function for this token, false otherwise. + internal bool TryBindDottedIdentifierAsFunctionCall(DottedIdentifierToken dottedIdentifierToken, SingleValueNode parent, out QueryNode boundFunction) + { + return this.TryBindIdentifier(dottedIdentifierToken.Identifier, null, parent, state, out boundFunction); + } + + /// + /// Bind this function call token as a Uri function + /// + /// the function call token to bind + /// list of semantically bound arguments + /// A function call node bound to this function. + private QueryNode BindAsUriFunction(FunctionCallToken functionCallToken, List argumentNodes) + { + if (functionCallToken.Source != null) + { + // the parent must be null for a Uri function. + throw new ODataException(ODataErrorStrings.FunctionCallBinder_UriFunctionMustHaveHaveNullParent(functionCallToken.Name)); + } + + // There are some functions (IsOf and Cast for example) that don't necessarily need to be bound to a function signature, + // for these, we just Bind them directly to a SingleValueFunctionCallNode + string matchedFunctionCallTokenName = IsUnboundFunction(functionCallToken.Name); + if (matchedFunctionCallTokenName != null) + { + return CreateUnboundFunctionNode(matchedFunctionCallTokenName, argumentNodes); + } + + // Do some validation and get potential Uri functions that could match what we saw + IList> nameSignatures = GetUriFunctionSignatures(functionCallToken.Name, + this.state.Configuration.EnableCaseInsensitiveUriFunctionIdentifier); + + SingleValueNode[] argumentNodeArray = ValidateArgumentsAreSingleValue(functionCallToken.Name, argumentNodes); + KeyValuePair nameSignature = MatchSignatureToUriFunction(functionCallToken.Name, argumentNodeArray, nameSignatures); + Debug.Assert(nameSignature.Key != null, "nameSignature.Key != null"); + + string canonicalName = nameSignature.Key; + FunctionSignatureWithReturnType signature = nameSignature.Value; + if (signature.ReturnType != null) + { + TypePromoteArguments(signature, argumentNodes); + } + + if (signature.ReturnType != null && signature.ReturnType.IsStructured()) + { + return new SingleResourceFunctionCallNode(canonicalName, new ReadOnlyCollection(argumentNodes), signature.ReturnType.AsStructured(), null); + } + + return new SingleValueFunctionCallNode(canonicalName, new ReadOnlyCollection(argumentNodes), signature.ReturnType); + } + + /// + /// Try to bind an identifier to a FunctionCallNode + /// + /// the identifier to bind + /// the semantically bound list of arguments. + /// a semantically bound parent node. + /// the current state of the binding algorithm + /// a single value function call node representing this function call, if we found one. + /// true if we found a function for this token. + private bool TryBindIdentifier(string identifier, IEnumerable arguments, QueryNode parent, BindingState state, out QueryNode boundFunction) + { + boundFunction = null; + + IEdmType bindingType = null; + SingleValueNode singleValueParent = parent as SingleValueNode; + if (singleValueParent != null) + { + if (singleValueParent.TypeReference != null) + { + bindingType = singleValueParent.TypeReference.Definition; + } + } + else + { + CollectionNode collectionValueParent = parent as CollectionNode; + if (collectionValueParent != null) + { + bindingType = collectionValueParent.CollectionType.Definition; + } + } + + if (!UriEdmHelpers.IsBindingTypeValid(bindingType)) + { + return false; + } + + // All functions should be fully qualified, if they aren't they they aren't functions. + // When using extension, there may be function call with unqualified name. So loose the restriction here. + if (identifier.IndexOf(".", StringComparison.Ordinal) == -1 && this.Resolver.GetType() == typeof(ODataUriResolver)) + { + return false; + } + + IEdmOperation operation; + List syntacticArguments = arguments == null ? new List() : arguments.ToList(); + if (!FunctionOverloadResolver.ResolveOperationFromList(identifier, syntacticArguments.Select(ar => ar.ParameterName).ToList(), bindingType, state.Model, out operation, this.Resolver)) + { + // TODO: FunctionOverloadResolver.ResolveOperationFromList() looks up the function by parameter names, but it shouldn't ignore parameter types. (test case ParseFilter_AliasInFunction_PropertyAsValue_TypeMismatch should fail) + return false; + } + + if (singleValueParent != null && singleValueParent.TypeReference == null) + { + // if the parent exists, but has no type information, then we're in open type land, and we + // shouldn't go any farther. + throw new ODataException(ODataErrorStrings.FunctionCallBinder_CallingFunctionOnOpenProperty(identifier)); + } + + if (operation.IsAction()) + { + return false; + } + + IEdmFunction function = (IEdmFunction)operation; + + // TODO: $filter $orderby parameter expression which contains complex or collection should NOT be supported in this way + // but should be parsed into token tree, and binded to node tree: parsedParameters.Select(p => this.bindMethod(p)); + ICollection parsedParameters = HandleComplexOrCollectionParameterValueIfExists(state.Configuration.Model, function, syntacticArguments, state.Configuration.Resolver.EnableCaseInsensitive); + + IEnumerable boundArguments = parsedParameters.Select(p => this.bindMethod(p)); + boundArguments = boundArguments.ToList(); // force enumerable to run : will immediately evaluate all this.bindMethod(p). + IEdmTypeReference returnType = function.ReturnType; + IEdmEntitySetBase returnSet = null; + SingleResourceNode singleEntityNode = parent as SingleResourceNode; + if (singleEntityNode != null) + { + returnSet = function.GetTargetEntitySet(singleEntityNode.NavigationSource, state.Model); + } + + string functionName = function.FullName(); + + if (returnType.IsEntity()) + { + boundFunction = new SingleResourceFunctionCallNode(functionName, new[] { function }, boundArguments, (IEdmEntityTypeReference)returnType.Definition.ToTypeReference(), returnSet, parent); + } + else if (returnType.IsStructuredCollection()) + { + IEdmCollectionTypeReference collectionTypeReference = (IEdmCollectionTypeReference)returnType; + boundFunction = new CollectionResourceFunctionCallNode(functionName, new[] { function }, boundArguments, collectionTypeReference, returnSet, parent); + } + else if (returnType.IsCollection()) + { + IEdmCollectionTypeReference collectionTypeReference = (IEdmCollectionTypeReference)returnType; + boundFunction = new CollectionFunctionCallNode(functionName, new[] { function }, boundArguments, collectionTypeReference, parent); + } + else + { + boundFunction = new SingleValueFunctionCallNode(functionName, new[] { function }, boundArguments, + returnType, parent); + } + + return true; + } + + /// + /// Bind path segment's operation or operationImport's parameters. + /// + /// The ODataUriParserConfiguration. + /// The function or operation. + /// The parameter tokens to be binded. + /// The binded semantic nodes. + internal static List BindSegmentParameters(ODataUriParserConfiguration configuration, IEdmOperation functionOrOpertion, ICollection segmentParameterTokens) + { + // TODO: HandleComplexOrCollectionParameterValueIfExists is temp work around for single copmlex or colleciton type, it can't handle nested complex or collection value. + ICollection parametersParsed = FunctionCallBinder.HandleComplexOrCollectionParameterValueIfExists(configuration.Model, functionOrOpertion, segmentParameterTokens, configuration.Resolver.EnableCaseInsensitive, configuration.EnableUriTemplateParsing); + + // Bind it to metadata + BindingState state = new BindingState(configuration); + state.ImplicitRangeVariable = null; + state.RangeVariables.Clear(); + MetadataBinder binder = new MetadataBinder(state); + List boundParameters = new List(); + + IDictionary input = new Dictionary(StringComparer.Ordinal); + foreach (FunctionParameterToken paraToken in parametersParsed) + { + // TODO: considering another better exception + if (paraToken.ValueToken is EndPathToken) + { + throw new ODataException(Strings.MetadataBinder_ParameterNotInScope( + string.Format(CultureInfo.InvariantCulture, "{0}={1}", paraToken.ParameterName, (paraToken.ValueToken as EndPathToken).Identifier))); + } + + SingleValueNode boundNode = (SingleValueNode)binder.Bind(paraToken.ValueToken); + + if (!input.ContainsKey(paraToken.ParameterName)) + { + input.Add(paraToken.ParameterName, boundNode); + } + } + + IDictionary result = configuration.Resolver.ResolveOperationParameters(functionOrOpertion, input); + + foreach (KeyValuePair item in result) + { + SingleValueNode boundNode = item.Value; + + // ensure node type is compatible with parameter type. + IEdmTypeReference sourceTypeReference = boundNode.GetEdmTypeReference(); + bool sourceIsNullOrOpenType = (sourceTypeReference == null); + if (!sourceIsNullOrOpenType) + { + // if the node has been rewritten, no further conversion is needed. + if (!TryRewriteIntegralConstantNode(ref boundNode, item.Key.Type)) + { + boundNode = MetadataBindingUtils.ConvertToTypeIfNeeded(boundNode, item.Key.Type); + } + } + + OperationSegmentParameter boundParamer = new OperationSegmentParameter(item.Key.Name, boundNode); + boundParameters.Add(boundParamer); + } + + return boundParameters; + } + + /// + /// Try to rewrite an Edm.Int32 constant node if its value is within the valid range of the target integer type. + /// + /// The node to be rewritten. + /// The target type reference. + /// If the node is successfully rewritten. + private static bool TryRewriteIntegralConstantNode(ref SingleValueNode boundNode, IEdmTypeReference targetType) + { + if (targetType == null || !targetType.IsByte() && !targetType.IsSByte() && !targetType.IsInt16()) + { + return false; + } + + ConstantNode constantNode = boundNode as ConstantNode; + if (constantNode == null) + { + return false; + } + + IEdmTypeReference sourceType = constantNode.TypeReference; + if (sourceType == null || !sourceType.IsInt32()) + { + return false; + } + + int sourceValue = (int)constantNode.Value; + object targetValue = null; + switch (targetType.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Byte: + if (sourceValue >= byte.MinValue && sourceValue <= byte.MaxValue) + { + targetValue = (byte)sourceValue; + } + + break; + case EdmPrimitiveTypeKind.SByte: + if (sourceValue >= sbyte.MinValue && sourceValue <= sbyte.MaxValue) + { + targetValue = (sbyte)sourceValue; + } + + break; + case EdmPrimitiveTypeKind.Int16: + if (sourceValue >= short.MinValue && sourceValue <= short.MaxValue) + { + targetValue = (short)sourceValue; + } + + break; + } + + if (targetValue == null) + { + return false; + } + + boundNode = new ConstantNode(targetValue, constantNode.LiteralText, targetType); + return true; + } + + /// + /// This is temp work around for $filter $orderby parameter expression which contains complex or collection + /// like "Fully.Qualified.Namespace.CanMoveToAddresses(addresses=[{\"Street\":\"NE 24th St.\",\"City\":\"Redmond\"},{\"Street\":\"Pine St.\",\"City\":\"Seattle\"}])"; + /// TODO: $filter $orderby parameter expression which contains nested complex or collection should NOT be supported in this way + /// but should be parsed into token tree, and binded to node tree: parsedParameters.Select(p => this.bindMethod(p)); + /// + /// The model. + /// IEdmFunction or IEdmOperation + /// The tokens to bind. + /// Whether to enable case-insensitive when resolving parameter name. + /// Whether Uri template parsing is enabled. + /// The FunctionParameterTokens with complex or collection values converted from string like "{...}", or "[..,..,..]". + private static ICollection HandleComplexOrCollectionParameterValueIfExists(IEdmModel model, IEdmOperation operation, ICollection parameterTokens, bool enableCaseInsensitive, bool enableUriTemplateParsing = false) + { + ICollection partiallyParsedParametersWithComplexOrCollection = new Collection(); + foreach (FunctionParameterToken paraToken in parameterTokens) + { + FunctionParameterToken funcParaToken; + IEdmOperationParameter functionParameter = operation.FindParameter(paraToken.ParameterName); + if (enableCaseInsensitive && functionParameter == null) + { + functionParameter = ODataUriResolver.ResolveOperationParameterNameCaseInsensitive(operation, paraToken.ParameterName); + + // The functionParameter can not be null here, else this method won't be called. + funcParaToken = new FunctionParameterToken(functionParameter.Name, paraToken.ValueToken); + } + else + { + funcParaToken = paraToken; + } + + FunctionParameterAliasToken aliasToken = funcParaToken.ValueToken as FunctionParameterAliasToken; + if (aliasToken != null) + { + aliasToken.ExpectedParameterType = functionParameter.Type; + } + + LiteralToken valueToken = funcParaToken.ValueToken as LiteralToken; + string valueStr = null; + if (valueToken != null && (valueStr = valueToken.Value as string) != null && !string.IsNullOrEmpty(valueToken.OriginalText)) + { + ExpressionLexer lexer = new ExpressionLexer(valueToken.OriginalText, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/); + if (lexer.CurrentToken.Kind == ExpressionTokenKind.BracketedExpression || lexer.CurrentToken.Kind == ExpressionTokenKind.BracedExpression) + { + object result; + UriTemplateExpression expression; + + if (enableUriTemplateParsing && UriTemplateParser.TryParseLiteral(lexer.CurrentToken.Text, functionParameter.Type, out expression)) + { + result = expression; + } + else if (!functionParameter.Type.IsStructured() && !functionParameter.Type.IsStructuredCollectionType()) + { + // ExpressionTokenKind.BracketedExpression means text like [1,2] + // so now try convert it to collection type value: + result = ODataUriUtils.ConvertFromUriLiteral(valueStr, ODataVersion.V4, model, functionParameter.Type); + } + else + { + // For complex & colleciton of complex directly return the raw string. + partiallyParsedParametersWithComplexOrCollection.Add(funcParaToken); + continue; + } + + LiteralToken newValueToken = new LiteralToken(result, valueToken.OriginalText); + FunctionParameterToken newFuncParaToken = new FunctionParameterToken(funcParaToken.ParameterName, newValueToken); + partiallyParsedParametersWithComplexOrCollection.Add(newFuncParaToken); + continue; + } + } + + partiallyParsedParametersWithComplexOrCollection.Add(funcParaToken); + } + + return partiallyParsedParametersWithComplexOrCollection; + } + + /// + /// Determines whether this is a unbound BuiltInFunction according to the configured case-sensitiveness. + /// + /// name of the function + /// matched unbound function names; null if no matches found. + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "need to use lower characters for unbound functions.")] + private string IsUnboundFunction(string functionName) + { + functionName = this.state.Configuration.EnableCaseInsensitiveUriFunctionIdentifier + ? functionName.ToLowerInvariant() + : functionName; + return UnboundFunctionNames.FirstOrDefault(name => name.Equals(functionName, StringComparison.Ordinal)); + } + + /// + /// Build a SingleValueFunctionCallNode for a function that isn't bound to a BuiltInFunction + /// + /// Name for the function + /// list of already bound query nodes for this function + /// A single value function call node bound to this function. + private SingleValueNode CreateUnboundFunctionNode(string functionCallTokenName, List args) + { + // need to figure out the return type and check the correct number of arguments based on the function name + IEdmTypeReference returnType = null; + switch (functionCallTokenName) + { + case ExpressionConstants.UnboundFunctionIsOf: + { + returnType = ValidateAndBuildIsOfArgs(state, ref args); + break; + } + + case ExpressionConstants.UnboundFunctionCast: + { + returnType = ValidateAndBuildCastArgs(state, ref args); + if (returnType.IsStructured()) + { + SingleResourceNode entityNode = args.ElementAt(0) as SingleResourceNode; + + return new SingleResourceFunctionCallNode(functionCallTokenName, args, + returnType.AsStructured(), entityNode != null ? entityNode.NavigationSource : null); + } + + break; + } + + default: + { + break; + } + } + + // we have everything else we need, so return the new SingleValueFunctionCallNode. + return new SingleValueFunctionCallNode(functionCallTokenName, args, returnType); + } + + /// + /// Validate the args list (adding the implicit range variable if necessary), and determine the correct return type for a cast function + /// + /// current binding state, used to get the implicit range variable if necessary + /// list of arguments, could be changed + /// the return type from this cast function + private static IEdmTypeReference ValidateAndBuildCastArgs(BindingState state, ref List args) + { + return ValidateIsOfOrCast(state, true, ref args); + } + + /// + /// Validate the arguments (adding the implicit range variable if necessary), and determine the correct return type + /// for an IsOf function + /// + /// the current state of the binding algorithm, used to get the implicit range variable if necessary + /// current list of args, can be changed + /// the correct return type for this function. + private static IEdmTypeReference ValidateAndBuildIsOfArgs(BindingState state, ref List args) + { + return ValidateIsOfOrCast(state, false, ref args); + } + + /// + /// Validate the arguments to either isof or cast + /// + /// the current state of the binding algorithm + /// flag to indicate which function we're validating + /// the list of arguments, which could be changed + /// the return type of the function. + private static IEdmTypeReference ValidateIsOfOrCast(BindingState state, bool isCast, ref List args) + { + if (args.Count != 1 && args.Count != 2) + { + throw new ODataErrorException( + ODataErrorStrings.MetadataBinder_CastOrIsOfExpressionWithWrongNumberOfOperands(args.Count)); + } + + ConstantNode typeArgument = args.Last() as ConstantNode; + + IEdmTypeReference returnType = null; + if (typeArgument != null) + { + returnType = TryGetTypeReference(state.Model, typeArgument.Value as string, state.Configuration.Resolver); + } + + if (returnType == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_CastOrIsOfFunctionWithoutATypeArgument); + } + + if (returnType.IsCollection()) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_CastOrIsOfCollectionsNotSupported); + } + + // if we only have one argument, then add the implicit range variable as the first argument. + if (args.Count == 1) + { + args = new List() + { + new ResourceRangeVariableReferenceNode( + state.ImplicitRangeVariable.Name, + state.ImplicitRangeVariable as ResourceRangeVariable), + args[0] + }; + } + else if (!(args[0] is SingleValueNode)) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_CastOrIsOfCollectionsNotSupported); + } + + if (isCast && (args.Count == 2)) + { + // throw if cast enum to not-string : + if ((args[0].GetEdmTypeReference() is IEdmEnumTypeReference) + && !string.Equals(typeArgument.Value as string, Microsoft.OData.Metadata.EdmConstants.EdmStringTypeName, StringComparison.Ordinal)) + { + throw new ODataException(ODataErrorStrings.CastBinder_EnumOnlyCastToOrFromString); + } + + // throw if cast not-string to enum : + while (returnType is IEdmEnumTypeReference) + { + IEdmTypeReference edmTypeReference = args[0].GetEdmTypeReference(); + if (edmTypeReference == null) + { + // Support cast null to enum + break; + } + + IEdmPrimitiveTypeReference referenceTmp = edmTypeReference as IEdmPrimitiveTypeReference; + if (referenceTmp != null) + { + IEdmPrimitiveType typeTmp = referenceTmp.Definition as IEdmPrimitiveType; + if ((typeTmp != null) && (typeTmp.PrimitiveKind == EdmPrimitiveTypeKind.String)) + { + break; + } + } + + throw new ODataException(ODataErrorStrings.CastBinder_EnumOnlyCastToOrFromString); + } + } + + if (isCast) + { + return returnType; + } + else + { + return EdmCoreModel.Instance.GetBoolean(true); + } + } + + /// + /// Try to get an IEdmTypeReference for a given type as a string, returns null if none exists + /// + /// the model for validation + /// the type name to find + /// Resolver for this func. + /// an IEdmTypeReference for this type string. + private static IEdmTypeReference TryGetTypeReference(IEdmModel model, string fullTypeName, ODataUriResolver resolver) + { + IEdmTypeReference typeReference = UriEdmHelpers.FindTypeFromModel(model, fullTypeName, resolver).ToTypeReference(); + if (typeReference == null) + { + if (fullTypeName.StartsWith("Collection", StringComparison.Ordinal)) + { + string[] tokenizedString = fullTypeName.Split('('); + string baseElementType = tokenizedString[1].Split(')')[0]; + return EdmCoreModel.GetCollection(UriEdmHelpers.FindTypeFromModel(model, baseElementType, resolver).ToTypeReference()); + } + else + { + return null; + } + } + + return typeReference; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs new file mode 100644 index 0000000..7732dcf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InBinder.cs @@ -0,0 +1,210 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class that knows how to bind the In operator. + /// + internal sealed class InBinder + { + /// + /// Method to use for binding the parent node, if needed. + /// + private readonly Func bindMethod; + + /// + /// Constructs a InBinder with the given method to be used binding the parent token if needed. + /// + /// Method to use for binding the parent token, if needed. + internal InBinder(Func bindMethod) + { + this.bindMethod = bindMethod; + } + + /// + /// Delegate for a function that normalizes a string item representing a certain type. + /// Each type should define a different implementation of the delegate. + /// + /// The item to be normalized. + /// Normalized string of the item. + private delegate string NormalizeFunction(string item); + + /// + /// Binds an In operator token. + /// + /// The In operator token to bind. + /// State of the metadata binding. + /// The bound In operator token. + internal QueryNode BindInOperator(InToken inToken, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(inToken, "inToken"); + + SingleValueNode left = this.GetSingleValueOperandFromToken(inToken.Left); + CollectionNode right = this.GetCollectionOperandFromToken( + inToken.Right, new EdmCollectionTypeReference(new EdmCollectionType(left.TypeReference)), state.Model); + + return new InNode(left, right); + } + + /// + /// Retrieve SingleValueNode bound with given query token. + /// + /// The query token + /// The corresponding SingleValueNode + private SingleValueNode GetSingleValueOperandFromToken(QueryToken queryToken) + { + SingleValueNode operand = this.bindMethod(queryToken) as SingleValueNode; + if (operand == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_LeftOperandNotSingleValue); + } + + return operand; + } + + /// + /// Retrieve CollectionNode bound with given query token. + /// + /// The query token + /// The expected type that this collection holds + /// The Edm model + /// The corresponding CollectionNode + private CollectionNode GetCollectionOperandFromToken(QueryToken queryToken, IEdmTypeReference expectedType, IEdmModel model) + { + CollectionNode operand = null; + LiteralToken literalToken = queryToken as LiteralToken; + if (literalToken != null) + { + string originalLiteralText = literalToken.OriginalText; + + // Parentheses-based collections are not standard JSON but bracket-based ones are. + // Temporarily switch our collection to bracket-based so that the JSON reader will + // correctly parse the collection. Then pass the original literal text to the token. + string bracketLiteralText = originalLiteralText; + if (bracketLiteralText[0] == '(') + { + Debug.Assert(bracketLiteralText[bracketLiteralText.Length - 1] == ')', + "Collection with opening '(' should have corresponding ')'"); + + StringBuilder replacedText = new StringBuilder(bracketLiteralText); + replacedText[0] = '['; + replacedText[replacedText.Length - 1] = ']'; + bracketLiteralText = replacedText.ToString(); + + Debug.Assert(expectedType.IsCollection()); + string expectedTypeFullName = expectedType.Definition.AsElementType().FullTypeName(); + if (expectedTypeFullName.Equals("Edm.String")) + { + // For collection of strings, need to convert single-quoted string to double-quoted string, + // and also, per ABNF, two consecutive single quotes to one single quote. + // Sample: ['a''bc','''def','xyz'''] ==> ["a'bc","'def","xyz'"], which is legitimate Json format. + bracketLiteralText = NormalizeCollectionItems(bracketLiteralText, NormalizeStringItem); + } + else if (expectedTypeFullName.Equals("Edm.Guid")) + { + // For collection of Guids, need to convert the Guid literals to single-quoted form, so that it is compatible + // with the Json reader used for deserialization. + // Sample: (D01663CF-EB21-4A0E-88E0-361C10ACE7FD, 492CF54A-84C9-490C-A7A4-B5010FAD8104) + // ==> ('D01663CF-EB21-4A0E-88E0-361C10ACE7FD', '492CF54A-84C9-490C-A7A4-B5010FAD8104') + bracketLiteralText = NormalizeCollectionItems(bracketLiteralText, NormalizeGuidItem); + } + } + + object collection = ODataUriConversionUtils.ConvertFromCollectionValue(bracketLiteralText, model, expectedType); + LiteralToken collectionLiteralToken = new LiteralToken(collection, originalLiteralText, expectedType); + operand = this.bindMethod(collectionLiteralToken) as CollectionConstantNode; + } + else + { + operand = this.bindMethod(queryToken) as CollectionNode; + } + + if (operand == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_RightOperandNotCollectionValue); + } + + return operand; + } + + private static string NormalizeCollectionItems(string bracketLiteralText, NormalizeFunction normalizeFunc) + { + string[] items = bracketLiteralText.Substring(1, bracketLiteralText.Length - 2).Split(',') + .Select(s => s.Trim()).ToArray(); + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < items.Length; i++) + { + string convertedItem = normalizeFunc(items[i]); + if (i != items.Length - 1) + { + builder.AppendFormat(CultureInfo.InvariantCulture, "{0},", convertedItem); + } + else + { + // No trailing comma separator for last str of the collection. + builder.Append(convertedItem); + } + } + + return String.Format(CultureInfo.InvariantCulture, "[{0}]", builder.ToString()); + } + + /// + /// Function to normalize quoted string, ensuring single quotes are escaped properly. + /// If the string is double-quoted, no op since single quote doesn't need to be escaped. + /// + /// The quoted string item to be normalized. + /// The double-quoted string with single quotes properly escaped. + private static string NormalizeStringItem(string str) + { + // Validate the string item is quoted properly. + if (!((str[0] == '\'' && str[str.Length - 1] == '\'') || (str[0] == '"' && str[str.Length - 1] == '"'))) + { + throw new ODataException(ODataErrorStrings.StringItemShouldBeQuoted(str)); + } + + // Skip conversion if the items are already in double-quote format (for backward compatibility). + // Note that per ABNF, query option strings should use single quotes. + string convertedString = str; + if (str[0] == '\'') + { + convertedString = String.Format(CultureInfo.InvariantCulture, "\"{0}\"", UriParserHelper.RemoveQuotes(str)); + } + + return convertedString; + } + + /// + /// Function to normalize string representing GUID so that it is compatible with Json reader for de-serialization. + /// No op if the input string is ready in quoted form. + /// + /// The GUID. + /// A Guid string in quoted form. + private static string NormalizeGuidItem(string guid) + { + // Skip conversion if the items are already in quoted format (for backward compatibility). + // Otherwise, make it single-quoted. + if (guid[0] == '\'' || guid[0] == '"') + { + return guid; + } + else + { + return String.Format(CultureInfo.InvariantCulture, "'{0}'", guid); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InnerPathTokenBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InnerPathTokenBinder.cs new file mode 100644 index 0000000..d042af6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/InnerPathTokenBinder.cs @@ -0,0 +1,245 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Class responsible for binding a InnerPathToken into: + /// 1. SingleNavigationNode + /// 2. CollectionNavigationNode + /// 3. SinglePropertyAccessNode (complex) + /// 4. CollectionPropertyAccessNode (primitive | complex) + /// 5. KeyLookupNode + /// 6. SingleValueFunctionCallNode + /// 7. SingleResourceFunctionCallNode + /// + /// + /// TODO: The binder does support key lookup on collection navigation properties, however at this time + /// the synctactic parser does not set things up correctly to allow end-to-end scenarios to work. + /// + internal sealed class InnerPathTokenBinder : BinderBase + { + /// + /// Constructs a InnerPathTokenBinder. + /// + /// Bind method to use for binding a parent node, if needed. + /// State of the metadata binding. + internal InnerPathTokenBinder(MetadataBinder.QueryTokenVisitor bindMethod, BindingState state) + : base(bindMethod, state) + { + } + + /// + /// Ensures that the parent node is of structured type, throwing if it is not. + /// + /// Parent node to a navigation property. + /// The given parent node as a SingleResourceNode. + internal static SingleResourceNode EnsureParentIsResourceForNavProp(SingleValueNode parent) + { + ExceptionUtils.CheckArgumentNotNull(parent, "parent"); + + SingleResourceNode parentResource = parent as SingleResourceNode; + if (parentResource == null) + { + // TODO: update error message #644 + throw new ODataException(ODataErrorStrings.MetadataBinder_NavigationPropertyNotFollowingSingleEntityType); + } + + return parentResource; + } + + /// + /// Given a property name, if the associated type reference is structured, then this returns + /// the property of the structured type. Otherwise, it returns null. + /// + /// The parent type to be used to find binding options. + /// The string designated the property name to be bound. + /// Resolver for uri parser. + /// The property associated with string and parent type. + internal static IEdmProperty BindProperty(IEdmTypeReference parentReference, string propertyName, ODataUriResolver resolver) + { + ExceptionUtils.CheckArgumentNotNull(resolver, "resolver"); + + IEdmStructuredTypeReference structuredParentType = + parentReference == null ? null : parentReference.AsStructuredOrNull(); + return structuredParentType == null ? null : resolver.ResolveProperty(structuredParentType.StructuredDefinition(), propertyName); + } + + /// + /// Builds an appropriate navigation query node (collection or single) for the given property and parent node. + /// + /// Navigation property. + /// Parent Node. + /// Named values (key values) that were included in the node we are binding, if any. + /// State of binding. + /// Object to perform binding on any key values that are present. + /// The navigation source of the navigation node. + /// A new CollectionNavigationNode or SingleNavigationNode to capture the navigation property access. + internal static QueryNode GetNavigationNode(IEdmNavigationProperty property, SingleResourceNode parent, IEnumerable namedValues, BindingState state, KeyBinder keyBinder, out IEdmNavigationSource navigationSource) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + ExceptionUtils.CheckArgumentNotNull(parent, "parent"); + ExceptionUtils.CheckArgumentNotNull(state, "state"); + ExceptionUtils.CheckArgumentNotNull(keyBinder, "keyBinder"); + + // Handle collection navigation property + if (property.TargetMultiplicity() == EdmMultiplicity.Many) + { + CollectionNavigationNode collectionNavigationNode = new CollectionNavigationNode(parent, property, state.ParsedSegments); + navigationSource = collectionNavigationNode.NavigationSource; + + // Doing key lookup on the collection navigation property + if (namedValues != null) + { + return keyBinder.BindKeyValues(collectionNavigationNode, namedValues, state.Model); + } + + // Otherwise it's just a normal collection of entities + return collectionNavigationNode; + } + + Debug.Assert(namedValues == null || !namedValues.Any(), "namedValues should not exist if it isn't a collection"); + + // Otherwise it's a single navigation property + SingleNavigationNode singleNavigationNode = new SingleNavigationNode(parent, property, state.ParsedSegments); + navigationSource = singleNavigationNode.NavigationSource; + return singleNavigationNode; + } + + /// + /// Binds a . + /// This includes more than just navigations - it includes complex property access and primitive collections. + /// + /// The segment token to bind. + /// The bound node. + internal QueryNode BindInnerPathSegment(InnerPathToken segmentToken) + { + FunctionCallBinder functionCallBinder = new FunctionCallBinder(this.bindMethod, state); + + // First we get the parent node + QueryNode parent = this.DetermineParentNode(segmentToken, state); + Debug.Assert(parent != null, "parent should never be null"); + + SingleValueNode singleValueParent = parent as SingleValueNode; + + if (singleValueParent == null) + { + QueryNode boundFunction; + if (functionCallBinder.TryBindInnerPathAsFunctionCall(segmentToken, parent, out boundFunction)) + { + return boundFunction; + } + + CollectionNavigationNode collectionParent = parent as CollectionNavigationNode; + + if (collectionParent != null) + { + IEdmEntityTypeReference parentType = collectionParent.EntityItemType; + IEdmProperty collectionProperty = this.Resolver.ResolveProperty(parentType.StructuredDefinition(), segmentToken.Identifier); + if (collectionProperty != null && collectionProperty.PropertyKind == EdmPropertyKind.Structural) + { + return new AggregatedCollectionPropertyNode(collectionParent, collectionProperty); + } + } + + throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyAccessSourceNotSingleValue(segmentToken.Identifier)); + } + + // Using the parent and name of this token, we try to get the IEdmProperty it represents + IEdmProperty property = BindProperty(singleValueParent.TypeReference, segmentToken.Identifier, this.Resolver); + + if (property == null) + { + QueryNode boundFunction; + if (functionCallBinder.TryBindInnerPathAsFunctionCall(segmentToken, parent, out boundFunction)) + { + return boundFunction; + } + + if (singleValueParent.TypeReference != null && !singleValueParent.TypeReference.Definition.IsOpen()) + { + throw new ODataException( + ODataErrorStrings.MetadataBinder_PropertyNotDeclared( + parent.GetEdmTypeReference().FullName(), segmentToken.Identifier)); + } + + return new SingleValueOpenPropertyAccessNode(singleValueParent, segmentToken.Identifier); + } + + IEdmStructuralProperty structuralProperty = property as IEdmStructuralProperty; + if (property.Type.IsComplex()) + { + // Generate a segment to parsed segments for the parsed token + state.ParsedSegments.Add(new PropertySegment(structuralProperty)); + return new SingleComplexNode(singleValueParent as SingleResourceNode, property); + } + else if (property.Type.IsPrimitive()) + { + return new SingleValuePropertyAccessNode(singleValueParent, property); + } + + // Note - this means nonentity collection (primitive or complex) + if (property.Type.IsNonEntityCollectionType()) + { + if (property.Type.IsStructuredCollectionType()) + { + // Generate a segment to parsed segments for the parsed token + state.ParsedSegments.Add(new PropertySegment(structuralProperty)); + return new CollectionComplexNode(singleValueParent as SingleResourceNode, property); + } + + return new CollectionPropertyAccessNode(singleValueParent, property); + } + + IEdmNavigationProperty navigationProperty = property as IEdmNavigationProperty; + if (navigationProperty == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_IllegalSegmentType(property.Name)); + } + + SingleResourceNode parentResource = EnsureParentIsResourceForNavProp(singleValueParent); + + IEdmNavigationSource navigationSource; + QueryNode node = GetNavigationNode(navigationProperty, parentResource, segmentToken.NamedValues, state, + new KeyBinder(this.bindMethod), out navigationSource); + + // Generate a segment to parsed segments for the parsed token + state.ParsedSegments.Add(new NavigationPropertySegment(navigationProperty, navigationSource)); + + return node; + } + + /// + /// Determines the parent node. If the token has a parent, that token is bound. If not, then we + /// use the implicit parameter from the BindingState as the parent node. + /// + /// Token to determine the parent node for. + /// Current state of binding. + /// A SingleValueQueryNode that is the parent node of the . + private QueryNode DetermineParentNode(InnerPathToken segmentToken, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(segmentToken, "segmentToken"); + ExceptionUtils.CheckArgumentNotNull(state, "state"); + + if (segmentToken.NextToken != null) + { + return this.bindMethod(segmentToken.NextToken); + } + else + { + RangeVariable implicitRangeVariable = state.ImplicitRangeVariable; + return NodeFactory.CreateRangeVariableReferenceNode(implicitRangeVariable); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/KeyBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/KeyBinder.cs new file mode 100644 index 0000000..128e08f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/KeyBinder.cs @@ -0,0 +1,226 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class that knows how to bind key values. + /// + internal sealed class KeyBinder + { + /// + /// Method to bind the value of a key. + /// TODO: Make it of return type SingleValueQueryNode. + /// + private readonly MetadataBinder.QueryTokenVisitor keyValueBindMethod; + + /// + /// Constructs a KeyBinder object using the given function to bind key values. + /// + /// Method to call to bind a value in a key. + internal KeyBinder(MetadataBinder.QueryTokenVisitor keyValueBindMethod) + { + this.keyValueBindMethod = keyValueBindMethod; + } + + /// + /// Binds key values to a key lookup on a collection. + /// + /// Already bound collection node. + /// The named value tokens to bind. + /// The model to be used. + /// The bound key lookup. + internal QueryNode BindKeyValues(CollectionResourceNode collectionNode, IEnumerable namedValues, IEdmModel model) + { + Debug.Assert(namedValues != null, "namedValues != null"); + Debug.Assert(collectionNode != null, "CollectionNode != null"); + Debug.Assert(model != null, "model != null"); + + IEdmEntityTypeReference collectionItemType = collectionNode.ItemStructuredType as IEdmEntityTypeReference; + + IEdmEntityType collectionItemEntityType = collectionItemType.EntityDefinition(); + QueryNode keyLookupNode; + + if (TryBindToDeclaredKey(collectionNode, namedValues, model, collectionItemEntityType, out keyLookupNode)) + { + return keyLookupNode; + } + else if (TryBindToDeclaredAlternateKey(collectionNode, namedValues, model, collectionItemEntityType, out keyLookupNode)) + { + return keyLookupNode; + } + else + { + throw new ODataException(ODataErrorStrings.MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues(collectionNode.ItemStructuredType.FullName())); + } + } + + /// + /// Tries to bind key values to a key lookup on a collection. + /// + /// Already bound collection node. + /// The named value tokens to bind. + /// The model to be used. + /// The type of a single item in a collection to apply the key value to. + /// The bound key lookup. + /// Returns true if binding succeeded. + private bool TryBindToDeclaredAlternateKey(CollectionResourceNode collectionNode, IEnumerable namedValues, IEdmModel model, IEdmEntityType collectionItemEntityType, out QueryNode keyLookupNode) + { + IEnumerable> alternateKeys = model.GetAlternateKeysAnnotation(collectionItemEntityType); + foreach (IDictionary keys in alternateKeys) + { + if (TryBindToKeys(collectionNode, namedValues, model, collectionItemEntityType, keys, out keyLookupNode)) + { + return true; + } + } + + keyLookupNode = null; + return false; + } + + /// + /// Tries to bind key values to a key lookup on a collection. + /// + /// Already bound collection node. + /// The named value tokens to bind. + /// The model to be used. + /// The type of a single item in a collection to apply the key value to. + /// The bound key lookup. + /// Returns true if binding succeeded. + private bool TryBindToDeclaredKey(CollectionResourceNode collectionNode, IEnumerable namedValues, IEdmModel model, IEdmEntityType collectionItemEntityType, out QueryNode keyLookupNode) + { + Dictionary keys = new Dictionary(StringComparer.Ordinal); + foreach (IEdmStructuralProperty property in collectionItemEntityType.Key()) + { + keys[property.Name] = property; + } + + return TryBindToKeys(collectionNode, namedValues, model, collectionItemEntityType, keys, out keyLookupNode); + } + + /// + /// Binds key values to a key lookup on a collection. + /// + /// Already bound collection node. + /// The named value tokens to bind. + /// The model to be used. + /// The type of a single item in a collection to apply the key value to. + /// Dictionary of aliases to structural property names for the key. + /// The bound key lookup. + /// Returns true if binding succeeded. + private bool TryBindToKeys(CollectionResourceNode collectionNode, IEnumerable namedValues, IEdmModel model, IEdmEntityType collectionItemEntityType, IDictionary keys, out QueryNode keyLookupNode) + { + List keyPropertyValues = new List(); + HashSet keyPropertyNames = new HashSet(StringComparer.Ordinal); + foreach (NamedValue namedValue in namedValues) + { + KeyPropertyValue keyPropertyValue; + + if (!this.TryBindKeyPropertyValue(namedValue, collectionItemEntityType, keys, out keyPropertyValue)) + { + keyLookupNode = null; + return false; + } + + Debug.Assert(keyPropertyValue != null, "keyPropertyValue != null"); + Debug.Assert(keyPropertyValue.KeyProperty != null, "keyPropertyValue.KeyProperty != null"); + + if (!keyPropertyNames.Add(keyPropertyValue.KeyProperty.Name)) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_DuplicitKeyPropertyInKeyValues(keyPropertyValue.KeyProperty.Name)); + } + + keyPropertyValues.Add(keyPropertyValue); + } + + if (keyPropertyValues.Count == 0) + { + // No key values specified, for example '/Customers()', do not include the key lookup at all + keyLookupNode = collectionNode; + return true; + } + else if (keyPropertyValues.Count != collectionItemEntityType.Key().Count()) + { + keyLookupNode = null; + return false; + } + else + { + keyLookupNode = new KeyLookupNode(collectionNode, new ReadOnlyCollection(keyPropertyValues)); + return true; + } + } + + /// + /// Binds a key property value. + /// + /// The named value to bind. + /// The type of a single item in a collection to apply the key value to. + /// Dictionary of alias to keys. + /// The bound key property value node. + /// The bound key property value node. + private bool TryBindKeyPropertyValue(NamedValue namedValue, IEdmEntityType collectionItemEntityType, IDictionary keys, out KeyPropertyValue keyPropertyValue) + { + // These are exception checks because the data comes directly from the potentially user specified tree. + ExceptionUtils.CheckArgumentNotNull(namedValue, "namedValue"); + ExceptionUtils.CheckArgumentNotNull(namedValue.Value, "namedValue.Value"); + Debug.Assert(collectionItemEntityType != null, "collectionItemType != null"); + + IEdmProperty keyProperty = null; + if (namedValue.Name == null) + { + foreach (IEdmProperty p in keys.Values) + { + if (keyProperty == null) + { + keyProperty = p; + } + else + { + throw new ODataException(ODataErrorStrings.MetadataBinder_UnnamedKeyValueOnTypeWithMultipleKeyProperties(collectionItemEntityType.FullTypeName())); + } + } + } + else + { + keyProperty = keys.SingleOrDefault(k => string.CompareOrdinal(k.Key, namedValue.Name) == 0).Value; + + if (keyProperty == null) + { + keyPropertyValue = null; + return false; + } + } + + IEdmTypeReference keyPropertyType = keyProperty.Type; + + SingleValueNode value = (SingleValueNode)this.keyValueBindMethod(namedValue.Value); + + // TODO: Check that the value is of primitive type + Debug.Assert(keyPropertyType.IsODataPrimitiveTypeKind(), "The key's type must be primitive."); + value = MetadataBindingUtils.ConvertToTypeIfNeeded(value, keyPropertyType); + + Debug.Assert(keyProperty != null, "keyProperty != null"); + keyPropertyValue = new KeyPropertyValue() + { + KeyProperty = keyProperty, + KeyValue = value + }; + + return true; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LambdaBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LambdaBinder.cs new file mode 100644 index 0000000..832cd14 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LambdaBinder.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Class that knows how to bind a LambdaToken. + /// + internal sealed class LambdaBinder + { + /// + /// Method used to bind a parent token. + /// + private readonly MetadataBinder.QueryTokenVisitor bindMethod; + + /// + /// Constructs a LambdaBinder. + /// + /// Method used to bind a parent token. + internal LambdaBinder(MetadataBinder.QueryTokenVisitor bindMethod) + { + ExceptionUtils.CheckArgumentNotNull(bindMethod, "bindMethod"); + this.bindMethod = bindMethod; + } + + /// + /// Binds a LambdaToken to metadata. + /// + /// Token to bind. + /// Object to hold the state of binding. + /// A metadata bound any or all node. + internal LambdaNode BindLambdaToken(LambdaToken lambdaToken, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(lambdaToken, "LambdaToken"); + ExceptionUtils.CheckArgumentNotNull(state, "state"); + + // Start by binding the parent token + CollectionNode parent = this.BindParentToken(lambdaToken.Parent); + RangeVariable rangeVariable = null; + + // Add the lambda variable to the stack + if (lambdaToken.Parameter != null) + { + rangeVariable = NodeFactory.CreateParameterNode(lambdaToken.Parameter, parent); + state.RangeVariables.Push(rangeVariable); + } + + // Bind the expression + SingleValueNode expression = this.BindExpressionToken(lambdaToken.Expression); + + // Create the node + LambdaNode lambdaNode = NodeFactory.CreateLambdaNode(state, parent, expression, rangeVariable, lambdaToken.Kind); + + // Remove the lambda variable as it is now out of scope + if (rangeVariable != null) + { + state.RangeVariables.Pop(); + } + + return lambdaNode; + } + + /// + /// Bind the parent of the LambdaToken + /// + /// the parent token + /// the bound parent node + private CollectionNode BindParentToken(QueryToken queryToken) + { + QueryNode parentNode = this.bindMethod(queryToken); + CollectionNode parentCollectionNode = parentNode as CollectionNode; + if (parentCollectionNode == null) + { + SingleValueOpenPropertyAccessNode parentOpenPropertyNode = + parentNode as SingleValueOpenPropertyAccessNode; + + if (parentOpenPropertyNode == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_LambdaParentMustBeCollection); + } + + // support open collection properties + return new CollectionOpenPropertyAccessNode(parentOpenPropertyNode.Source, parentOpenPropertyNode.Name); + } + + return parentCollectionNode; + } + + /// + /// Bind the expression of the LambdaToken + /// + /// the expression token + /// the bound expression node + private SingleValueNode BindExpressionToken(QueryToken queryToken) + { + SingleValueNode expression = this.bindMethod(queryToken) as SingleValueNode; + if (expression == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_AnyAllExpressionNotSingleValue); + } + + // type reference is allowed to be null for open properties. + IEdmTypeReference expressionTypeReference = expression.GetEdmTypeReference(); + if (expressionTypeReference != null && !expressionTypeReference.AsPrimitive().IsBoolean()) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_AnyAllExpressionNotSingleValue); + } + + return expression; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LiteralBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LiteralBinder.cs new file mode 100644 index 0000000..fbf2c2e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/LiteralBinder.cs @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Diagnostics; + + /// + /// Class that knows how to bind literal values. + /// + internal sealed class LiteralBinder + { + /// + /// Binds a literal value to a ConstantNode + /// + /// Literal token to bind. + /// Bound query node. + internal static QueryNode BindLiteral(LiteralToken literalToken) + { + ExceptionUtils.CheckArgumentNotNull(literalToken, "literalToken"); + + if (!string.IsNullOrEmpty(literalToken.OriginalText)) + { + if (literalToken.ExpectedEdmTypeReference != null) + { + return new ConstantNode(literalToken.Value, literalToken.OriginalText, literalToken.ExpectedEdmTypeReference); + } + + return new ConstantNode(literalToken.Value, literalToken.OriginalText); + } + + return new ConstantNode(literalToken.Value); + } + + /// + /// Binds a literal value to a ConstantNode + /// + /// Literal token to bind. + /// Bound query node. + internal static QueryNode BindInLiteral(LiteralToken literalToken) + { + ExceptionUtils.CheckArgumentNotNull(literalToken, "literalToken"); + + if (!string.IsNullOrEmpty(literalToken.OriginalText)) + { + if (literalToken.ExpectedEdmTypeReference != null) + { + OData.Edm.IEdmCollectionTypeReference collectionReference = + literalToken.ExpectedEdmTypeReference as OData.Edm.IEdmCollectionTypeReference; + if (collectionReference != null) + { + ODataCollectionValue collectionValue = literalToken.Value as ODataCollectionValue; + if (collectionValue != null) + { + return new CollectionConstantNode(collectionValue.Items, literalToken.OriginalText, collectionReference); + } + } + + return new ConstantNode(literalToken.Value, literalToken.OriginalText, literalToken.ExpectedEdmTypeReference); + } + + return new ConstantNode(literalToken.Value, literalToken.OriginalText); + } + + return new ConstantNode(literalToken.Value); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs new file mode 100644 index 0000000..3ed12db --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs @@ -0,0 +1,374 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + #region Namespaces + + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Binder which applies metadata to a lexical QueryToken tree and produces a bound semantic QueryNode tree. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Keeping the visitor in one place makes sense.")] + internal class MetadataBinder + { + /// + /// Encapsulates the state of the metadata binding. + /// + private BindingState bindingState; + + /// + /// Constructs a MetadataBinder with the given . + /// This constructor gets used if you are not calling the top level resource point ParseQuery. + /// This is an at-your-own-risk constructor, since you must provide valid initial state. + /// + /// The initialState to use for binding. + /// Throws if initial state is null. + /// Throws if initialState.Model is null. + internal MetadataBinder(BindingState initialState) + { + ExceptionUtils.CheckArgumentNotNull(initialState, "initialState"); + ExceptionUtils.CheckArgumentNotNull(initialState.Model, "initialState.Model"); + + this.BindingState = initialState; + } + + /// + /// Delegate for a function that visits a QueryToken and translates it into a bound QueryNode. + /// TODO: Eventually replace this with a real interface for a visitor. + /// + /// QueryToken to visit. + /// Metadata bound QueryNode. + internal delegate QueryNode QueryTokenVisitor(QueryToken token); + + /// + /// Encapsulates the state of the metadata binding. + /// + internal BindingState BindingState + { + get + { + return this.bindingState; + } + + private set + { + this.bindingState = value; + } + } + + /// + /// Processes the skip operator (if any) and returns the combined query. + /// + /// The skip amount or null if none was specified. + /// the skip clause + /// Throws if skip is less than 0. + public static long? ProcessSkip(long? skip) + { + if (skip.HasValue) + { + if (skip < 0) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_SkipRequiresNonNegativeInteger(skip.ToString())); + } + + return skip; + } + + return null; + } + + /// + /// Processes the top operator (if any) and returns the combined query. + /// + /// The top amount or null if none was specified. + /// the top clause + /// Throws if top is less than 0. + public static long? ProcessTop(long? top) + { + if (top.HasValue) + { + if (top < 0) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_TopRequiresNonNegativeInteger(top.ToString())); + } + + return top; + } + + return null; + } + + /// + /// Process the remaining query options (represent the set of custom query options after + /// service operation parameters and system query options have been removed). + /// + /// the current state of the binding algorithm. + /// pointer to a binder method. + /// The list of instances after binding. + /// Throws if bindingState is null. + /// Throws if bindMethod is null. + public static List ProcessQueryOptions(BindingState bindingState, MetadataBinder.QueryTokenVisitor bindMethod) + { + if (bindingState == null || bindingState.QueryOptions == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_QueryOptionsBindStateCannotBeNull); + } + + if (bindMethod == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_QueryOptionsBindMethodCannotBeNull); + } + + List customQueryOptionNodes = new List(); + + foreach (CustomQueryOptionToken queryToken in bindingState.QueryOptions) + { + QueryNode customQueryOptionNode = bindMethod(queryToken); + if (customQueryOptionNode != null) + { + customQueryOptionNodes.Add(customQueryOptionNode); + } + } + + bindingState.QueryOptions = null; + return customQueryOptionNodes; + } + + /// + /// Visits a in the lexical tree and binds it to metadata producing a semantic . + /// + /// The query token on the input. + /// The bound query node output. + protected internal QueryNode Bind(QueryToken token) + { + ExceptionUtils.CheckArgumentNotNull(token, "token"); + this.BindingState.RecurseEnter(); + QueryNode result; + switch (token.Kind) + { + case QueryTokenKind.Any: + result = this.BindAnyAll((AnyToken)token); + break; + case QueryTokenKind.All: + result = this.BindAnyAll((AllToken)token); + break; + case QueryTokenKind.InnerPath: + result = this.BindInnerPathSegment((InnerPathToken)token); + break; + case QueryTokenKind.Literal: + result = this.BindLiteral((LiteralToken)token); + break; + case QueryTokenKind.StringLiteral: + result = this.BindStringLiteral((StringLiteralToken)token); + break; + case QueryTokenKind.BinaryOperator: + result = this.BindBinaryOperator((BinaryOperatorToken)token); + break; + case QueryTokenKind.UnaryOperator: + result = this.BindUnaryOperator((UnaryOperatorToken)token); + break; + case QueryTokenKind.EndPath: + result = this.BindEndPath((EndPathToken)token); + break; + case QueryTokenKind.FunctionCall: + result = this.BindFunctionCall((FunctionCallToken)token); + break; + case QueryTokenKind.DottedIdentifier: + result = this.BindCast((DottedIdentifierToken)token); + break; + case QueryTokenKind.RangeVariable: + result = this.BindRangeVariable((RangeVariableToken)token); + break; + case QueryTokenKind.FunctionParameterAlias: + result = this.BindParameterAlias((FunctionParameterAliasToken)token); + break; + case QueryTokenKind.FunctionParameter: + result = this.BindFunctionParameter((FunctionParameterToken)token); + break; + case QueryTokenKind.In: + result = this.BindIn((InToken)token); + break; + default: + throw new ODataException(ODataErrorStrings.MetadataBinder_UnsupportedQueryTokenKind(token.Kind)); + } + + if (result == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_BoundNodeCannotBeNull(token.Kind)); + } + + this.BindingState.RecurseLeave(); + return result; + } + + /// + /// Bind parameter alias (figuring out its type by first parsing and binding its value expression). + /// + /// The alias syntatics token. + /// The semantics node for parameter alias. + protected virtual SingleValueNode BindParameterAlias(FunctionParameterAliasToken functionParameterAliasToken) + { + ParameterAliasBinder binder = new ParameterAliasBinder(this.Bind); + return binder.BindParameterAlias(this.BindingState, functionParameterAliasToken); + } + + /// + /// Bind a function parameter token + /// + /// The token to bind. + /// A semantically bound FunctionCallNode + protected virtual QueryNode BindFunctionParameter(FunctionParameterToken token) + { + // TODO: extract this into its own binder class. + if (token.ParameterName != null) + { + return new NamedFunctionParameterNode(token.ParameterName, this.Bind(token.ValueToken)); + } + + return this.Bind(token.ValueToken); + } + + /// + /// Binds an InnerPathToken. + /// + /// Token to bind. + /// Either a SingleNavigationNode, CollectionNavigationNode, SinglePropertyAccessNode (complex), + /// or CollectionPropertyAccessNode (primitive or complex) that is the metadata-bound version of the given token. + protected virtual QueryNode BindInnerPathSegment(InnerPathToken token) + { + InnerPathTokenBinder innerPathTokenBinder = new InnerPathTokenBinder(this.Bind, this.BindingState); + return innerPathTokenBinder.BindInnerPathSegment(token); + } + + /// + /// Binds a parameter token. + /// + /// The parameter token to bind. + /// The bound query node. + protected virtual SingleValueNode BindRangeVariable(RangeVariableToken rangeVariableToken) + { + return RangeVariableBinder.BindRangeVariableToken(rangeVariableToken, this.BindingState); + } + + /// + /// Binds a literal token. + /// + /// The literal token to bind. + /// The bound literal token. + protected virtual QueryNode BindLiteral(LiteralToken literalToken) + { + return LiteralBinder.BindLiteral(literalToken); + } + + /// + /// Binds a binary operator token. + /// + /// The binary operator token to bind. + /// The bound binary operator token. + protected virtual QueryNode BindBinaryOperator(BinaryOperatorToken binaryOperatorToken) + { + BinaryOperatorBinder binaryOperatorBinder = new BinaryOperatorBinder(this.Bind, this.BindingState.Configuration.Resolver); + return binaryOperatorBinder.BindBinaryOperator(binaryOperatorToken); + } + + /// + /// Binds a unary operator token. + /// + /// The unary operator token to bind. + /// The bound unary operator token. + protected virtual QueryNode BindUnaryOperator(UnaryOperatorToken unaryOperatorToken) + { + UnaryOperatorBinder unaryOperatorBinder = new UnaryOperatorBinder(this.Bind); + return unaryOperatorBinder.BindUnaryOperator(unaryOperatorToken); + } + + /// + /// Binds a type startPath token. + /// + /// The type startPath token to bind. + /// The bound type startPath token. + protected virtual QueryNode BindCast(DottedIdentifierToken dottedIdentifierToken) + { + DottedIdentifierBinder dottedIdentifierBinder = new DottedIdentifierBinder(this.Bind, this.BindingState); + return dottedIdentifierBinder.BindDottedIdentifier(dottedIdentifierToken); + } + + /// + /// Binds a LambdaToken. + /// + /// The LambdaToken to bind. + /// A bound Any or All node. + protected virtual QueryNode BindAnyAll(LambdaToken lambdaToken) + { + ExceptionUtils.CheckArgumentNotNull(lambdaToken, "LambdaToken"); + + LambdaBinder binder = new LambdaBinder(this.Bind); + return binder.BindLambdaToken(lambdaToken, this.BindingState); + } + + /// + /// Binds a property access token. + /// + /// The property access token to bind. + /// The bound property access token. + protected virtual QueryNode BindEndPath(EndPathToken endPathToken) + { + EndPathBinder endPathBinder = new EndPathBinder(this.Bind, this.BindingState); + return endPathBinder.BindEndPath(endPathToken); + } + + /// + /// Binds a function call token. + /// + /// The function call token to bind. + /// The bound function call token. + protected virtual QueryNode BindFunctionCall(FunctionCallToken functionCallToken) + { + FunctionCallBinder functionCallBinder = new FunctionCallBinder(this.Bind, this.BindingState); + return functionCallBinder.BindFunctionCall(functionCallToken); + } + + /// + /// Binds a StringLiteral token. + /// + /// The StringLiteral token to bind. + /// The bound StringLiteral token. + protected virtual QueryNode BindStringLiteral(StringLiteralToken stringLiteralToken) + { + return new SearchTermNode(stringLiteralToken.Text); + } + + /// + /// Binds an In token. + /// + /// The In token to bind. + /// The bound In token. + protected virtual QueryNode BindIn(InToken inToken) + { + Func InBinderMethod = (queryToken) => + { + ExceptionUtils.CheckArgumentNotNull(queryToken, "queryToken"); + + if (queryToken.Kind == QueryTokenKind.Literal) + { + return LiteralBinder.BindInLiteral((LiteralToken)queryToken); + } + + return this.Bind(queryToken); + }; + + InBinder inBinder = new InBinder(InBinderMethod); + return inBinder.BindInOperator(inToken, this.BindingState); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs new file mode 100644 index 0000000..44de5d6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs @@ -0,0 +1,180 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Helper methods for metadata binding. + /// + internal static class MetadataBindingUtils + { + /// + /// If the source node is not of the specified type, then we check if type promotion is possible and inject a convert node. + /// If the source node is the same type as the target type (or if the target type is null), we just return the source node as is. + /// + /// The source node to apply the conversion to. + /// The target primitive type. May be null - this method will do nothing in that case. + /// The converted query node, or the original source node unchanged. + internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference) + { + Debug.Assert(source != null, "source != null"); + + if (targetTypeReference == null) + { + return source; + } + + if (source.TypeReference != null) + { + if (source.TypeReference.IsEquivalentTo(targetTypeReference)) + { + // For source is type definition, if source's underlying type == target type. + // We create a conversion node from source to its underlying type (target type) + // so that the service can convert value of source clr type to underlying clr type. + if (source.TypeReference.IsTypeDefinition()) + { + return new ConvertNode(source, targetTypeReference); + } + + return source; + } + + // Structured type in url will be translated into a node with raw string value. + // We create a conversion node from string to structured type. + if (targetTypeReference.IsStructured() || targetTypeReference.IsStructuredCollectionType()) + { + return new ConvertNode(source, targetTypeReference); + } + + ConstantNode constantNode = source as ConstantNode; + if (constantNode != null && constantNode.Value != null && source.TypeReference.IsString() && targetTypeReference.IsEnum()) + { + string memberName = constantNode.Value.ToString(); + IEdmEnumType enumType = targetTypeReference.Definition as IEdmEnumType; + if (enumType.Members.Any(m => string.Compare(m.Name, memberName, StringComparison.Ordinal) == 0)) + { + string literalText = ODataUriUtils.ConvertToUriLiteral(constantNode.Value, default(ODataVersion)); + return new ConstantNode(new ODataEnumValue(constantNode.Value.ToString(), targetTypeReference.Definition.ToString()), literalText, targetTypeReference); + } + else + { + throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(memberName)); + } + } + + if (!TypePromotionUtils.CanConvertTo(source, source.TypeReference, targetTypeReference)) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_CannotConvertToType(source.TypeReference.FullName(), targetTypeReference.FullName())); + } + else + { + if (source.TypeReference.IsEnum() && constantNode != null) + { + return new ConstantNode(constantNode.Value, ODataUriUtils.ConvertToUriLiteral(constantNode.Value, ODataVersion.V4), targetTypeReference); + } + + object originalPrimitiveValue; + if (MetadataUtilsCommon.TryGetConstantNodePrimitiveLDMF(source, out originalPrimitiveValue) && (originalPrimitiveValue != null)) + { + // L F D M types : directly create a ConvertNode. + // 1. NodeToExpressionTranslator.cs won't allow implicitly converting single/double to decimal, which should be done here at Node tree level. + // 2. And prevent losing precision in float -> double, e.g. (double)1.234f => 1.2339999675750732d not 1.234d + object targetPrimitiveValue = ODataUriConversionUtils.CoerceNumericType(originalPrimitiveValue, targetTypeReference.AsPrimitive().Definition as IEdmPrimitiveType); + + if (string.IsNullOrEmpty(constantNode.LiteralText)) + { + return new ConstantNode(targetPrimitiveValue); + } + + var candidate = new ConstantNode(targetPrimitiveValue, constantNode.LiteralText); + var decimalType = candidate.TypeReference as IEdmDecimalTypeReference; + if (decimalType != null) + { + var targetDecimalType = (IEdmDecimalTypeReference)targetTypeReference; + return decimalType.Precision == targetDecimalType.Precision && + decimalType.Scale == targetDecimalType.Scale ? + (SingleValueNode)candidate : + (SingleValueNode)(new ConvertNode(candidate, targetTypeReference)); + } + else + { + return candidate; + } + } + else + { + // other type conversion : ConvertNode + return new ConvertNode(source, targetTypeReference); + } + } + } + else + { + // If the source doesn't have a type (possibly an open property), then it's possible to convert it + // cause we don't know for sure. + return new ConvertNode(source, targetTypeReference); + } + } + + /// + /// Retrieves type associated to a segment. + /// + /// The node to retrieve the type from. + /// The type of the node, or item type for collections. + internal static IEdmType GetEdmType(this QueryNode segment) + { + SingleValueNode singleNode = segment as SingleValueNode; + + if (singleNode != null) + { + IEdmTypeReference typeRef = singleNode.TypeReference; + return (typeRef != null) ? typeRef.Definition : null; + } + + CollectionNode collectionNode = segment as CollectionNode; + + if (collectionNode != null) + { + IEdmTypeReference typeRef = collectionNode.ItemType; + return (typeRef != null) ? typeRef.Definition : null; + } + + return null; + } + + /// + /// Retrieves the type reference associated to a segment. + /// + /// The node to retrive the type reference from. + /// The Type reference of the node (item type reference for collections). + internal static IEdmTypeReference GetEdmTypeReference(this QueryNode segment) + { + SingleValueNode singleNode = segment as SingleValueNode; + + if (singleNode != null) + { + return singleNode.TypeReference; + } + + CollectionNode collectionNode = segment as CollectionNode; + + if (collectionNode != null) + { + return collectionNode.ItemType; + } + + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/OrderByBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/OrderByBinder.cs new file mode 100644 index 0000000..0d94c2c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/OrderByBinder.cs @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class to handle the binding of orderby tokens. + /// + internal sealed class OrderByBinder + { + /// + /// Method to use to visit the token tree and bind the tokens recursively. + /// + private readonly MetadataBinder.QueryTokenVisitor bindMethod; + + /// + /// Creates an OrderByBinder + /// + /// Method to use to visit the token tree and bind the tokens recursively. + internal OrderByBinder(MetadataBinder.QueryTokenVisitor bindMethod) + { + ExceptionUtils.CheckArgumentNotNull(bindMethod, "bindMethod"); + this.bindMethod = bindMethod; + } + + /// + /// Processes the order-by tokens of a entityCollection (if any). + /// + /// State to use for binding. + /// The order-by tokens to bind. + /// An OrderByClause representing the orderby statements expressed in the tokens. + internal OrderByClause BindOrderBy(BindingState state, IEnumerable orderByTokens) + { + ExceptionUtils.CheckArgumentNotNull(state, "state"); + ExceptionUtils.CheckArgumentNotNull(orderByTokens, "orderByTokens"); + + OrderByClause orderByClause = null; + + // Go through the orderby tokens starting from the last one + foreach (OrderByToken orderByToken in orderByTokens.Reverse()) + { + orderByClause = this.ProcessSingleOrderBy(state, orderByClause, orderByToken); + } + + return orderByClause; + } + + /// + /// Processes the specified order-by token. + /// + /// State to use for binding. + /// The next OrderBy node, or null if there is no orderby after this. + /// The order-by token to bind. + /// Returns the combined entityCollection including the ordering. + private OrderByClause ProcessSingleOrderBy(BindingState state, OrderByClause thenBy, OrderByToken orderByToken) + { + ExceptionUtils.CheckArgumentNotNull(state, "state"); + ExceptionUtils.CheckArgumentNotNull(orderByToken, "orderByToken"); + + QueryNode expressionNode = this.bindMethod(orderByToken.Expression); + + // The order-by expressions need to be primitive / enumeration types + SingleValueNode expressionResultNode = expressionNode as SingleValueNode; + if (expressionResultNode == null || + (expressionResultNode.TypeReference != null && + !expressionResultNode.TypeReference.IsODataPrimitiveTypeKind() && + !expressionResultNode.TypeReference.IsODataEnumTypeKind() && + !expressionResultNode.TypeReference.IsODataTypeDefinitionTypeKind())) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_OrderByExpressionNotSingleValue); + } + + OrderByClause orderByNode = new OrderByClause( + thenBy, + expressionResultNode, + orderByToken.Direction, + state.ImplicitRangeVariable); + + return orderByNode; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ParameterAliasBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ParameterAliasBinder.cs new file mode 100644 index 0000000..7384b26 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/ParameterAliasBinder.cs @@ -0,0 +1,131 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// This class binds parameter alias by : + /// (1) parse and bind the alias value's expression into SingleValueNode, then get its type. + /// (2) asign SingleValueNode's type to alias' ParameterAliasNode. + /// + internal sealed class ParameterAliasBinder + { + /// + /// Method to use to visit the token tree and bind the tokens recursively. + /// + private readonly MetadataBinder.QueryTokenVisitor bindMethod; + + /// + /// Creates an OrderByBinder + /// + /// Method to use to visit the token tree and bind the tokens recursively. + internal ParameterAliasBinder(MetadataBinder.QueryTokenVisitor bindMethod) + { + ExceptionUtils.CheckArgumentNotNull(bindMethod, "bindMethod"); + this.bindMethod = bindMethod; + } + + /// + /// Bind a parameter alias which is inside another alias value. + /// + /// The alias name which is inside another alias value. + /// The cache of alias value nodes + /// The semantics node tree for alias (the @p1 in "@p1=...", not alias value expression) + internal ParameterAliasNode BindParameterAlias(BindingState bindingState, FunctionParameterAliasToken aliasToken) + { + ExceptionUtils.CheckArgumentNotNull(bindingState, "bindingState"); + ExceptionUtils.CheckArgumentNotNull(aliasToken, "aliasToken"); + + string alias = aliasToken.Alias; + ParameterAliasValueAccessor aliasValueAccessor = bindingState.Configuration.ParameterAliasValueAccessor; + if (aliasValueAccessor == null) + { + return new ParameterAliasNode(alias, null); + } + + // in cache? + SingleValueNode aliasValueNode = null; + if (!aliasValueAccessor.ParameterAliasValueNodesCached.TryGetValue(alias, out aliasValueNode)) + { + // has value expression? + string aliasValueExpression = aliasValueAccessor.GetAliasValueExpression(alias); + if (aliasValueExpression == null) + { + aliasValueAccessor.ParameterAliasValueNodesCached[alias] = null; + } + else + { + aliasValueNode = this.ParseAndBindParameterAliasValueExpression(bindingState, aliasValueExpression, aliasToken.ExpectedParameterType); + aliasValueAccessor.ParameterAliasValueNodesCached[alias] = aliasValueNode; + } + } + + return new ParameterAliasNode(alias, aliasValueNode.GetEdmTypeReference()); + } + + /// + /// Parse expression into syntaxs token tree, and bind it into semantics node tree. + /// + /// The BindingState. + /// The alias value's expression text. + /// The edm type of the parameter. + /// The semantcs node of the expression text. + private SingleValueNode ParseAndBindParameterAliasValueExpression(BindingState bindingState, string aliasValueExpression, IEdmTypeReference parameterType) + { + // Get the syntactic representation of the filter expression + // TODO: change Settings.FilterLimit to ParameterAliasValueLimit + UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(bindingState.Configuration.Settings.FilterLimit); + QueryToken aliasValueToken = expressionParser.ParseExpressionText(aliasValueExpression); + + // Special logic to handle parameter alias token. + aliasValueToken = ParseComplexOrCollectionAlias(aliasValueToken, parameterType, bindingState.Model); + + // Get the semantic node, and check for SingleValueNode + QueryNode aliasValueNode = this.bindMethod(aliasValueToken); + SingleValueNode result = aliasValueNode as SingleValueNode; + if (result == null) + { + // TODO: add string resource + throw new ODataException("ODataErrorStrings.MetadataBinder_ParameterAliasValueExpressionNotSingleValue"); + } + + return result; + } + + /// + /// Parse the complex/collection value in parameter alias. + /// + /// The parsed token. + /// The expected parameter type. + /// The model + /// Token with complex/collection value passed. + private static QueryToken ParseComplexOrCollectionAlias(QueryToken queryToken, IEdmTypeReference parameterType, IEdmModel model) + { + LiteralToken valueToken = queryToken as LiteralToken; + string valueStr; + if (valueToken != null && (valueStr = valueToken.Value as string) != null && !string.IsNullOrEmpty(valueToken.OriginalText)) + { + var lexer = new ExpressionLexer(valueToken.OriginalText, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/); + if (lexer.CurrentToken.Kind == ExpressionTokenKind.BracketedExpression || lexer.CurrentToken.Kind == ExpressionTokenKind.BracedExpression) + { + object result = valueStr; + if (!parameterType.IsStructured() && !parameterType.IsStructuredCollectionType()) + { + result = ODataUriUtils.ConvertFromUriLiteral(valueStr, ODataVersion.V4, model, parameterType); + } + + // For non-primitive type, we have to pass parameterType to LiteralToken, then to ConstantNode so the service can know what the type it is. + return new LiteralToken(result, valueToken.OriginalText, parameterType); + } + } + + return queryToken; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/RangeVariableBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/RangeVariableBinder.cs new file mode 100644 index 0000000..6a2c46e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/RangeVariableBinder.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Linq; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Class that knows how to bind ParameterQueryTokens. + /// + internal sealed class RangeVariableBinder + { + /// + /// Binds a parameter token. + /// + /// The parameter token to bind. + /// The state of metadata binding. + /// The bound query node. + internal static SingleValueNode BindRangeVariableToken(RangeVariableToken rangeVariableToken, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(rangeVariableToken, "rangeVariableToken"); + + RangeVariable rangeVariable = state.RangeVariables.SingleOrDefault(p => p.Name == rangeVariableToken.Name); + + if (rangeVariable == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_ParameterNotInScope(rangeVariableToken.Name)); + } + + return NodeFactory.CreateRangeVariableReferenceNode(rangeVariable); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SearchBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SearchBinder.cs new file mode 100644 index 0000000..499e006 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SearchBinder.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class responsible for binding a syntactic filter expression into a bound tree of semantic nodes. + /// + internal sealed class SearchBinder + { + /// + /// Method to use to visit the token tree and bind the tokens recursively. + /// + private readonly MetadataBinder.QueryTokenVisitor bindMethod; + + /// + /// Creates a SearchBinder. + /// + /// Method to use to visit the token tree and bind the tokens recursively. + internal SearchBinder(MetadataBinder.QueryTokenVisitor bindMethod) + { + this.bindMethod = bindMethod; + } + + /// + /// Binds the given filter token. + /// + /// The search token to bind. + /// A SearchClause with for given Token. + internal SearchClause BindSearch(QueryToken search) + { + ExceptionUtils.CheckArgumentNotNull(search, "filter"); + + QueryNode expressionNode = this.bindMethod(search); + + SingleValueNode expressionResultNode = expressionNode as SingleValueNode; + + SearchClause searchClause = new SearchClause(expressionResultNode); + + return searchClause; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectBinder.cs new file mode 100644 index 0000000..3b44573 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectBinder.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Linq; + using Microsoft.OData.Edm; + + /// + /// Use a Select syntactic tree to populate the correct values for Selection in an already parsed + /// Expand Semantic Tree. + /// + internal sealed class SelectBinder + { + /// + /// Visitor object to walk the select tree + /// + private readonly SelectPropertyVisitor visitor; + + /// + /// Constructs a new SelectBinder. + /// + /// The model used for binding. + /// The entity type that the $select is being applied to. + /// the maximum recursive depth. + /// The already built expand clause to decorate + /// Resolver for uri parser. + /// Binding state for the SelectBinder. + public SelectBinder(IEdmModel model, IEdmStructuredType edmType, int maxDepth, SelectExpandClause expandClauseToDecorate, ODataUriResolver resolver, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(model, "tokenIn"); + ExceptionUtils.CheckArgumentNotNull(edmType, "entityType"); + ExceptionUtils.CheckArgumentNotNull(resolver, "resolver"); + + this.visitor = new SelectPropertyVisitor(model, edmType, maxDepth, expandClauseToDecorate, resolver, state); + } + + /// + /// Visits the top level select token + /// + /// the select token to visit + /// A new SelectExpandClause decorated with the information from the selectToken + public SelectExpandClause Bind(SelectToken tokenIn) + { + if (tokenIn == null || !tokenIn.Properties.Any()) + { + // if there are no properties selected for this level, then by default we select + // all properties (including nav prop links, functions, actions, and structural properties) + this.visitor.DecoratedExpandClause.SetAllSelected(true); + } + else + { + // if there are properties selected for this level, then we return only + // those specific properties in the payload, so clear the all selected flag + // for this level. + this.visitor.DecoratedExpandClause.SetAllSelected(false); + foreach (PathSegmentToken property in tokenIn.Properties) + { + property.Accept(this.visitor); + } + } + + return this.visitor.DecoratedExpandClause; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs new file mode 100644 index 0000000..c5cac5f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -0,0 +1,452 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser.Aggregation; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// ExpandOption variant of an SelectExpandBinder, where the default selection item for a given level is based on the select at that level + /// instead of the top level select clause. If nothing is selected for a given expand in the ExpandOption syntax, then we by default + /// select all from that item, instead of selecting nothing (and therefore pruning the expand off of the tree). + /// + internal sealed class SelectExpandBinder + { + /// + /// The configuration used for binding. + /// + private readonly ODataUriParserConfiguration configuration; + + /// + /// The navigation source at the current level expand. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// The entity type at the current level expand. + /// + private readonly IEdmStructuredType edmType; + + /// + /// The segments parsed in path and query option. + /// + private List parsedSegments = new List(); + + private BindingState state; + + public SelectExpandBinder(ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo, BindingState state) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + ExceptionUtils.CheckArgumentNotNull(odataPathInfo.TargetStructuredType, "edmType"); + + this.configuration = configuration; + this.edmType = odataPathInfo.TargetStructuredType; + this.navigationSource = odataPathInfo.TargetNavigationSource; + this.parsedSegments = odataPathInfo.Segments.ToList(); + this.state = state; + } + + /// + /// The model used for binding. + /// + public IEdmModel Model + { + get { return this.configuration.Model; } + } + + /// + /// The top level type. + /// + public IEdmStructuredType EdmType + { + get { return this.edmType; } + } + + /// + /// The top level navigation source for this level. + /// + public IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// The settings to use when binding. + /// + private ODataUriParserSettings Settings + { + get + { + return this.configuration.Settings; + } + } + + /// + /// The configuration used for binding. + /// + private ODataUriParserConfiguration Configuration + { + get + { + return this.configuration; + } + } + + /// + /// Bind the top level expand. + /// + /// the token to visit + /// a SelectExpand clause based on this ExpandToken + public SelectExpandClause Bind(ExpandToken tokenIn) + { + ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); + + // the top level represents $it then has only select populated.. + List expandedTerms = new List(); + if (tokenIn.ExpandTerms.Single().ExpandOption != null) + { + expandedTerms.AddRange(tokenIn.ExpandTerms.Single().ExpandOption.ExpandTerms.Select(this.GenerateExpandItem).Where(expandedNavigationSelectItem => expandedNavigationSelectItem != null)); + } + + // if there are any select items at this level then allSelected is false, otherwise it's true. + bool isAllSelected = tokenIn.ExpandTerms.Single().SelectOption == null; + SelectExpandClause topLevelExpand = new SelectExpandClause(expandedTerms, isAllSelected); + if (!isAllSelected) + { + SelectBinder selectBinder = new SelectBinder(this.Model, this.EdmType, this.Configuration.Settings.SelectExpandLimit, topLevelExpand, configuration.Resolver, this.state); + selectBinder.Bind(tokenIn.ExpandTerms.Single().SelectOption); + } + + return topLevelExpand; + } + + /// + /// Bind a sub level expand + /// + /// the token to visit + /// a SelectExpand clause based on this ExpandToken + private SelectExpandClause BindSubLevel(ExpandToken tokenIn) + { + List expandTerms = tokenIn.ExpandTerms.Select(this.GenerateExpandItem).Where(expandedNavigationSelectItem => expandedNavigationSelectItem != null).ToList(); + + return new SelectExpandClause(expandTerms, true); + } + + /// + /// Generate a SubExpand based on the current nav property and the curren token + /// + /// the current token + /// a new SelectExpand clause bound to the current token and nav prop + private SelectExpandClause GenerateSubExpand(ExpandTermToken tokenIn) + { + SelectExpandBinder nextLevelBinder = new SelectExpandBinder(this.Configuration, new ODataPathInfo(new ODataPath(this.parsedSegments)), null); + return nextLevelBinder.BindSubLevel(tokenIn.ExpandOption); + } + + /// + /// Decorate an expand tree using a select token. + /// + /// the already built sub expand + /// the current navigation property + /// the select token to use + /// list of properties generated in $compute/$apply + /// A new SelectExpand clause decorated with the select token. + private SelectExpandClause DecorateExpandWithSelect(SelectExpandClause subExpand, IEdmNavigationProperty currentNavProp, SelectToken select, BindingState state) + { + + SelectBinder selectBinder = new SelectBinder(this.Model, currentNavProp.ToEntityType(), this.Settings.SelectExpandLimit, subExpand, this.configuration.Resolver, state); + return selectBinder.Bind(select); + } + + /// + /// Build a expand clause for a nested expand. + /// + /// A new SelectExpandClause. + private static SelectExpandClause BuildDefaultSubExpand() + { + return new SelectExpandClause(new Collection(), true); + } + + /// + /// Generate an expand item (and a select item for the implicit nav prop if necessary) based on an ExpandTermToken + /// + /// the expandTerm token to visit + /// the expand item for this expand term token. + private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) + { + ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); + + PathSegmentToken currentToken = tokenIn.PathToNavigationProp; + + IEdmStructuredType currentLevelEntityType = this.EdmType; + List pathSoFar = new List(); + PathSegmentToken firstNonTypeToken = currentToken; + + if (currentToken.IsNamespaceOrContainerQualified()) + { + pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentLevelEntityType, out firstNonTypeToken)); + } + + IEdmProperty edmProperty = this.configuration.Resolver.ResolveProperty(currentLevelEntityType, firstNonTypeToken.Identifier); + if (edmProperty == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentLevelEntityType.FullTypeName(), currentToken.Identifier)); + } + + IEdmNavigationProperty currentNavProp = edmProperty as IEdmNavigationProperty; + IEdmStructuralProperty currentComplexProp = edmProperty as IEdmStructuralProperty; + if (currentNavProp == null && currentComplexProp == null) + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, currentLevelEntityType.FullTypeName())); + } + + if (currentComplexProp != null) + { + currentNavProp = ParseComplexTypesBeforeNavigation(currentComplexProp, ref firstNonTypeToken, pathSoFar); + } + + // ensure that we're always dealing with proper V4 syntax + if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null) + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); + } + + bool isRef = false; + if (firstNonTypeToken.NextToken != null) + { + // lastly... make sure that, since we're on a NavProp, that the next token isn't null. + if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.RefSegment) + { + isRef = true; + } + else + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath); + } + } + + // Add the segments in select and expand to parsed segments + this.parsedSegments.AddRange(pathSoFar); + + IEdmNavigationSource targetNavigationSource = null; + if (this.NavigationSource != null) + { + IEdmPathExpression bindingPath; + targetNavigationSource = this.NavigationSource.FindNavigationTarget(currentNavProp, BindingPathHelper.MatchBindingPath, this.parsedSegments, out bindingPath); + } + + NavigationPropertySegment navSegment = new NavigationPropertySegment(currentNavProp, targetNavigationSource); + pathSoFar.Add(navSegment); + this.parsedSegments.Add(navSegment); // Add the navigation property segment to parsed segments for future usage. + ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar); + + ApplyClause applyOption = null; + if (tokenIn.ApplyOptions != null) + { + MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource); + ApplyBinder applyBinder = new ApplyBinder(binder.Bind, binder.BindingState); + applyOption = applyBinder.BindApply(tokenIn.ApplyOptions); + } + + ComputeClause computeOption = null; + if (tokenIn.ComputeOption != null) + { + MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource); + ComputeBinder computeBinder = new ComputeBinder(binder.Bind); + computeOption = computeBinder.BindCompute(tokenIn.ComputeOption); + } + + var generatedProperties = GetGeneratedProperties(computeOption, applyOption); + bool collapsed = applyOption?.Transformations.Any(t => t.Kind == TransformationNodeKind.Aggregate || t.Kind == TransformationNodeKind.GroupBy) ?? false; + + // call MetadataBinder to build the filter clause + FilterClause filterOption = null; + if (tokenIn.FilterOption != null) + { + MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource, generatedProperties, collapsed); + FilterBinder filterBinder = new FilterBinder(binder.Bind, binder.BindingState); + filterOption = filterBinder.BindFilter(tokenIn.FilterOption); + } + + // call MetadataBinder again to build the orderby clause + OrderByClause orderbyOption = null; + if (tokenIn.OrderByOptions != null) + { + MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource, generatedProperties, collapsed); + OrderByBinder orderByBinder = new OrderByBinder(binder.Bind); + orderbyOption = orderByBinder.BindOrderBy(binder.BindingState, tokenIn.OrderByOptions); + } + + SearchClause searchOption = null; + if (tokenIn.SearchOption != null) + { + MetadataBinder binder = this.BuildNewMetadataBinder(targetNavigationSource); + SearchBinder searchBinder = new SearchBinder(binder.Bind); + searchOption = searchBinder.BindSearch(tokenIn.SearchOption); + } + + + if (isRef) + { + return new ExpandedReferenceSelectItem(pathToNavProp, targetNavigationSource, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, computeOption, applyOption); + } + + SelectExpandClause subSelectExpand; + if (tokenIn.ExpandOption != null) + { + subSelectExpand = this.GenerateSubExpand(tokenIn); + } + else + { + subSelectExpand = BuildDefaultSubExpand(); + } + + subSelectExpand = this.DecorateExpandWithSelect(subSelectExpand, currentNavProp, tokenIn.SelectOption, this.CreateBindingState(targetNavigationSource, generatedProperties, collapsed)); + + LevelsClause levelsOption = ParseLevels(tokenIn.LevelsOption, currentLevelEntityType, currentNavProp); + return new ExpandedNavigationSelectItem(pathToNavProp, targetNavigationSource, subSelectExpand, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, levelsOption, computeOption, applyOption); + } + + private static HashSet GetGeneratedProperties(ComputeClause computeOption, ApplyClause applyOption) + { + HashSet generatedProperties = null; + + if (applyOption != null) + { + generatedProperties = applyOption.GetLastAggregatedPropertyNames(); + } + + if (computeOption != null) + { + var computedProperties = new HashSet(computeOption.ComputedItems.Select(i => new EndPathToken(i.Alias, null))); + if (generatedProperties == null) + { + generatedProperties = computedProperties; + } + else + { + generatedProperties.UnionWith(computedProperties); + } + } + + return generatedProperties; + } + + private IEdmNavigationProperty ParseComplexTypesBeforeNavigation(IEdmStructuralProperty edmProperty, ref PathSegmentToken currentToken, List pathSoFar) + { + pathSoFar.Add(new PropertySegment(edmProperty)); + + if (currentToken.NextToken == null) + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, edmProperty.DeclaringType.FullTypeName())); + } + + currentToken = currentToken.NextToken; + + IEdmType complexType = edmProperty.Type.Definition; + + IEdmCollectionType collectionType = complexType as IEdmCollectionType; + if (collectionType != null) + { + complexType = collectionType.ElementType.Definition; + } + + IEdmStructuredType currentType = complexType as IEdmStructuredType; + if (currentType == null) + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_InvaidSegmentInExpand(currentToken.Identifier)); + } + + if (currentToken.IsNamespaceOrContainerQualified()) + { + pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentType, out currentToken)); + } + + IEdmProperty property = this.configuration.Resolver.ResolveProperty(currentType, currentToken.Identifier); + if (edmProperty == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentType.FullTypeName(), currentToken.Identifier)); + } + + IEdmStructuralProperty complexProp = property as IEdmStructuralProperty; + if (complexProp != null) + { + property = ParseComplexTypesBeforeNavigation(complexProp, ref currentToken, pathSoFar); + } + + IEdmNavigationProperty navProp = property as IEdmNavigationProperty; + if (navProp != null) + { + return navProp; + } + else + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, currentType.FullTypeName())); + } + } + + /// + /// Parse from levelsOption token to LevelsClause. + /// Negative value would be treated as max. + /// + /// The levelsOption for current expand. + /// The type of current level navigation source. + /// Navigation property for current expand. + /// The LevelsClause parsed, null if levelsOption is null + private static LevelsClause ParseLevels(long? levelsOption, IEdmType sourceType, IEdmNavigationProperty property) + { + if (!levelsOption.HasValue) + { + return null; + } + + IEdmType relatedType = property.ToEntityType(); + + if (sourceType != null && relatedType != null && !UriEdmHelpers.IsRelatedTo(sourceType, relatedType)) + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_LevelsNotAllowedOnIncompatibleRelatedType(property.Name, relatedType.FullTypeName(), sourceType.FullTypeName())); + } + + return new LevelsClause(levelsOption.Value < 0, levelsOption.Value); + } + + /// + /// Build a new MetadataBinder to use for expand options. + /// + /// The navigation source being expanded. + /// A new MetadataBinder ready to bind a Filter or Orderby clause. + private MetadataBinder BuildNewMetadataBinder(IEdmNavigationSource targetNavigationSource, HashSet generatedProperties = null, bool collapsed = false) + { + BindingState state = CreateBindingState(targetNavigationSource, generatedProperties, collapsed); + return new MetadataBinder(state); + } + + private BindingState CreateBindingState(IEdmNavigationSource targetNavigationSource, HashSet generatedProperties, bool collapsed) + { + if (targetNavigationSource == null) + { + return null; + } + + BindingState state = new BindingState(this.configuration) + { + ImplicitRangeVariable = + NodeFactory.CreateImplicitRangeVariable(targetNavigationSource.EntityType().ToTypeReference(), targetNavigationSource) + }; + state.RangeVariables.Push(state.ImplicitRangeVariable); + state.AggregatedPropertyNames = generatedProperties; + state.IsCollapsed = collapsed; + return state; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandClauseFinisher.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandClauseFinisher.cs new file mode 100644 index 0000000..2c02add --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandClauseFinisher.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Fixup step for a completed select expand clause. + /// + internal sealed class SelectExpandClauseFinisher + { + /// + /// Add any explicit nav prop links to a select expand clause as necessary. + /// + /// the select expand clause to modify. + public static void AddExplicitNavPropLinksWhereNecessary(SelectExpandClause clause) + { + IEnumerable selectItems = clause.SelectedItems; + + // make sure that there are already selects for this level, otherwise we change the select semantics. + bool anyPathSelectItems = selectItems.Any(x => x is PathSelectItem); + + // if there are selects for this level, then we need to add nav prop select items for each + // expanded nav prop + IEnumerable selectedPaths = selectItems.OfType().Select(item => item.SelectedPath); + foreach (ExpandedNavigationSelectItem navigationSelect in selectItems.Where(I => I.GetType() == typeof(ExpandedNavigationSelectItem))) + { + AddExplicitNavPropLinksWhereNecessary(navigationSelect.SelectAndExpand); + } + + foreach (ExpandedReferenceSelectItem navigationSelect in selectItems.Where(I => I.GetType() == typeof(ExpandedReferenceSelectItem))) + { + if (anyPathSelectItems && !selectedPaths.Any(x => x.Equals(navigationSelect.PathToNavigationProperty.ToSelectPath()))) + { + clause.AddToSelectedItems(new PathSelectItem(navigationSelect.PathToNavigationProperty.ToSelectPath())); + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandPathBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandPathBinder.cs new file mode 100644 index 0000000..63e62e9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandPathBinder.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Walk down a chain of type segments, checking that we find the correct type at each level. + /// + internal static class SelectExpandPathBinder + { + /// + /// Follow any type segments on the path, stopping at the first segment that isn't a type token. + /// + /// the first type segment + /// the model these types are contained in. + /// the maximum recursive depth + /// Resolver for uri parser. + /// the top level type, will be overwritten with the last entity type in the chain + /// the first non type token in the path + /// A path with type segments added to it. + public static IEnumerable FollowTypeSegments(PathSegmentToken firstTypeToken, IEdmModel model, int maxDepth, ODataUriResolver resolver, ref IEdmStructuredType currentLevelType, out PathSegmentToken firstNonTypeToken) + { + ExceptionUtils.CheckArgumentNotNull(firstTypeToken, "firstTypeToken"); + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + if (!firstTypeToken.IsNamespaceOrContainerQualified()) + { + throw new ODataException(ODataErrorStrings.SelectExpandPathBinder_FollowNonTypeSegment(firstTypeToken.Identifier)); + } + + int index = 0; + List pathToReturn = new List(); + PathSegmentToken currentToken = firstTypeToken; + while (currentToken.IsNamespaceOrContainerQualified() && currentToken.NextToken != null) + { + IEdmType previousLevelEntityType = currentLevelType; + currentLevelType = UriEdmHelpers.FindTypeFromModel(model, currentToken.Identifier, resolver) as IEdmStructuredType; + if (currentLevelType == null) + { + // TODO: fix this error message? + throw new ODataException(ODataErrorStrings.ExpandItemBinder_CannotFindType(currentToken.Identifier)); + } + + UriEdmHelpers.CheckRelatedTo(previousLevelEntityType, currentLevelType); + pathToReturn.Add(new TypeSegment(currentLevelType, /*entitySet*/null)); + + index++; + currentToken = currentToken.NextToken; + + if (index >= maxDepth) + { + throw new ODataException(ODataErrorStrings.ExpandItemBinder_PathTooDeep); + } + } + + firstNonTypeToken = currentToken; + + return pathToReturn; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSemanticBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSemanticBinder.cs new file mode 100644 index 0000000..9aa73a4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSemanticBinder.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Add semantic meaning to a Select or Expand token. + /// + internal sealed class SelectExpandSemanticBinder + { + /// + /// Add semantic meaning to a Select or Expand Token + /// + /// The path info from Uri path. + /// the syntactically parsed expand token + /// the syntactically parsed select token + /// The configuration to use for parsing. + /// The state of binding. + /// A select expand clause bound to metadata. + public static SelectExpandClause Bind( + ODataPathInfo odataPathInfo, + ExpandToken expandToken, + SelectToken selectToken, + ODataUriParserConfiguration configuration, + BindingState state) + { + ExpandToken unifiedSelectExpandToken = SelectExpandSyntacticUnifier.Combine(expandToken, selectToken); + + ExpandTreeNormalizer expandTreeNormalizer = new ExpandTreeNormalizer(); + ExpandToken normalizedSelectExpandToken = expandTreeNormalizer.NormalizeExpandTree(unifiedSelectExpandToken); + + SelectExpandBinder selectExpandBinder = new SelectExpandBinder(configuration, odataPathInfo, state); + SelectExpandClause clause = selectExpandBinder.Bind(normalizedSelectExpandToken); + + SelectExpandClauseFinisher.AddExplicitNavPropLinksWhereNecessary(clause); + + new ExpandDepthAndCountValidator(configuration.Settings.MaximumExpansionDepth, configuration.Settings.MaximumExpansionCount).Validate(clause); + + return clause; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSyntacticUnifier.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSyntacticUnifier.cs new file mode 100644 index 0000000..a140161 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandSyntacticUnifier.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + + /// + /// Combine a top level select and expand token. + /// + internal static class SelectExpandSyntacticUnifier + { + /// + /// Combine a top level select and expand token + /// + /// the original expand token + /// the original select token + /// A new ExpandToken with the original select token embedded within a new top level expand token. + public static ExpandToken Combine(ExpandToken expand, SelectToken select) + { + // build a new top level expand token embedding the top level select token. + ExpandTermToken newTopLevelExpandTerm = new ExpandTermToken(new SystemToken(ExpressionConstants.It, null), select, expand); + return new ExpandToken(new List() { newTopLevelExpandTerm }); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectPathSegmentTokenBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectPathSegmentTokenBinder.cs new file mode 100644 index 0000000..60164f1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectPathSegmentTokenBinder.cs @@ -0,0 +1,262 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Edm.Vocabularies; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Builds segments from tokens within $select. + /// + internal static class SelectPathSegmentTokenBinder + { + /// + /// Build a segment from a token. + /// + /// the token to bind + /// The model. + /// the type of the current scope based on type segments. + /// Resolver for uri parser. + /// The segment created from the token. + public static ODataPathSegment ConvertNonTypeTokenToSegment(PathSegmentToken tokenIn, IEdmModel model, IEdmStructuredType edmType, ODataUriResolver resolver, BindingState state = null) + { + ExceptionUtils.CheckArgumentNotNull(resolver, "resolver"); + + ODataPathSegment nextSegment; + if (UriParserHelper.IsAnnotation(tokenIn.Identifier)) + { + if (TryBindAsDeclaredTerm(tokenIn, model, resolver, out nextSegment)) + { + return nextSegment; + } + + string qualifiedTermName = tokenIn.Identifier.Remove(0, 1); + int separator = qualifiedTermName.LastIndexOf(".", StringComparison.Ordinal); + string namespaceName = qualifiedTermName.Substring(0, separator); + string termName = qualifiedTermName.Substring(separator == 0 ? 0 : separator + 1); + + // Don't allow selecting odata control information + if (String.Compare(namespaceName, ODataConstants.ODataPrefix, StringComparison.OrdinalIgnoreCase) == 0) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(tokenIn.Identifier)); + } + + return new AnnotationSegment(new EdmTerm(namespaceName, termName, EdmCoreModel.Instance.GetUntyped())); + } + + EndPathToken endPathToken = new EndPathToken(tokenIn.Identifier, null); + + if ((state?.IsCollapsed ?? false) && !(state?.AggregatedPropertyNames?.Contains(endPathToken) ?? false)) + { + throw new ODataException(ODataErrorStrings.ApplyBinder_GroupByPropertyNotPropertyAccessValue(tokenIn.Identifier)); + } + + if (TryBindAsDeclaredProperty(tokenIn, edmType, resolver, out nextSegment)) + { + return nextSegment; + } + + // Operations must be container-qualified, and because the token type indicates it was not a .-seperated identifier, we should not try to look up operations. + if (tokenIn.IsNamespaceOrContainerQualified()) + { + if (TryBindAsOperation(tokenIn, model, edmType, out nextSegment)) + { + return nextSegment; + } + + // If an action or function is requested in a selectItem using a qualifiedActionName or a qualifiedFunctionName + // and that operation cannot be bound to the entities requested, the service MUST ignore the selectItem. + if (!edmType.IsOpen) + { + return null; + } + } + + if (edmType.IsOpen || (state?.AggregatedPropertyNames?.Contains(endPathToken) ?? false)) + { + return new DynamicPathSegment(tokenIn.Identifier); + } + + throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(edmType.FullTypeName(), tokenIn.Identifier)); + } + + /// + /// Build a wildcard selection item + /// + /// the token to bind to a wildcard + /// the model to search for this wildcard + /// the new wildcard selection item, if we found one + /// true if we successfully bound to a wildcard, false otherwise + public static bool TryBindAsWildcard(PathSegmentToken tokenIn, IEdmModel model, out SelectItem item) + { + bool isTypeToken = tokenIn.IsNamespaceOrContainerQualified(); + bool wildcard = tokenIn.Identifier.EndsWith("*", StringComparison.Ordinal); + + if (isTypeToken && wildcard) + { + string namespaceName = tokenIn.Identifier.Substring(0, tokenIn.Identifier.Length - 2); + + if (model.DeclaredNamespaces.Any(declaredNamespace => declaredNamespace.Equals(namespaceName, StringComparison.Ordinal))) + { + item = new NamespaceQualifiedWildcardSelectItem(namespaceName); + return true; + } + } + + if (tokenIn.Identifier == "*") + { + item = new WildcardSelectItem(); + return true; + } + + item = null; + return false; + } + + /// + /// Tries to bind a given token as an Operation. + /// + /// Token to bind. + /// The model. + /// the current entity type to use as the binding type when looking for operations. + /// Bound segment if the token was bound to an operation successfully, or null. + /// True if the token was bound successfully, or false otherwise. + internal static bool TryBindAsOperation(PathSegmentToken pathToken, IEdmModel model, IEdmStructuredType entityType, out ODataPathSegment segment) + { + Debug.Assert(pathToken != null, "pathToken != null"); + Debug.Assert(entityType != null, "bindingType != null"); + + IEnumerable possibleFunctions = Enumerable.Empty(); + IList parameterNames = new List(); + + // Catch all catchable exceptions as FindDeclaredBoundOperations is implemented by anyone. + // If an exception occurs it will be supressed and the possible functions will be empty and return false. + try + { + int wildCardPos = pathToken.Identifier.IndexOf("*", StringComparison.Ordinal); + if (wildCardPos > -1) + { + string namespaceName = pathToken.Identifier.Substring(0, wildCardPos - 1); + possibleFunctions = model.FindBoundOperations(entityType).Where(o => o.Namespace == namespaceName); + } + else + { + NonSystemToken nonSystemToken = pathToken as NonSystemToken; + if (nonSystemToken != null && nonSystemToken.NamedValues != null) + { + parameterNames = nonSystemToken.NamedValues.Select(s => s.Name).ToList(); + } + + if (parameterNames.Count > 0) + { + // Always force to use fully qualified name when select operation + possibleFunctions = model.FindBoundOperations(entityType).FilterByName(true, pathToken.Identifier).FilterOperationsByParameterNames(parameterNames, false); + } + else + { + possibleFunctions = model.FindBoundOperations(entityType).FilterByName(true, pathToken.Identifier); + } + } + } + catch (Exception exc) + { + if (!ExceptionUtils.IsCatchableExceptionType(exc)) + { + throw; + } + } + + // Only filter if there is more than one and its needed. + if (possibleFunctions.Count() > 1) + { + possibleFunctions = possibleFunctions.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(entityType); + } + + // If more than one overload matches, try to select based on optional parameters + if (possibleFunctions.Count() > 1 && parameterNames.Count > 0) + { + possibleFunctions = possibleFunctions.FindBestOverloadBasedOnParameters(parameterNames); + } + + if (!possibleFunctions.HasAny()) + { + segment = null; + return false; + } + + possibleFunctions.EnsureOperationsBoundWithBindingParameter(); + + segment = new OperationSegment(possibleFunctions, null /*entitySet*/); + return true; + } + + /// + /// Tries to bind a given token as an a declared structural or navigation property. + /// + /// Token to bind. + /// the type to search for this property + /// Resolver for uri parser. + /// Bound segment if the token was bound to a declared property successfully, or null. + /// True if the token was bound successfully, or false otherwise. + private static bool TryBindAsDeclaredProperty(PathSegmentToken tokenIn, IEdmStructuredType edmType, ODataUriResolver resolver, out ODataPathSegment segment) + { + IEdmProperty prop = resolver.ResolveProperty(edmType, tokenIn.Identifier); + if (prop == null) + { + segment = null; + return false; + } + + if (prop.PropertyKind == EdmPropertyKind.Structural) + { + segment = new PropertySegment((IEdmStructuralProperty)prop); + return true; + } + + if (prop.PropertyKind == EdmPropertyKind.Navigation) + { + segment = new NavigationPropertySegment((IEdmNavigationProperty)prop, null /*TODO: set*/); + return true; + } + + throw new ODataException(ODataErrorStrings.SelectExpandBinder_UnknownPropertyType(prop.Name)); + } + + /// + /// Tries to bind a given token as a declared annotation term. + /// + /// Token to bind. + /// The model to search for this term + /// Resolver for uri parser. + /// Bound segment if the token was bound to a declared term successfully, or null. + /// True if the token was bound successfully, or false otherwise. + private static bool TryBindAsDeclaredTerm(PathSegmentToken tokenIn, IEdmModel model, ODataUriResolver resolver, out ODataPathSegment segment) + { + if (!UriParserHelper.IsAnnotation(tokenIn.Identifier)) + { + segment = null; + return false; + } + + IEdmTerm term = resolver.ResolveTerm(model, tokenIn.Identifier.Remove(0, 1)); + if (term == null) + { + segment = null; + return false; + } + + segment = new AnnotationSegment(term); + return true; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectTreeNormalizer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectTreeNormalizer.cs new file mode 100644 index 0000000..a7e114b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/SelectTreeNormalizer.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Translate a select tree into the right format to be used with an expand tree. + /// + internal sealed class SelectTreeNormalizer + { + /// + /// Normalize a SelectToken into something that can be used to trim an expand tree. + /// + /// The select token to normalize + /// Normalized SelectToken + public static SelectToken NormalizeSelectTree(SelectToken treeToNormalize) + { + PathReverser pathReverser = new PathReverser(); + List invertedPaths = (from property in treeToNormalize.Properties + select property.Accept(pathReverser)).ToList(); + + // to normalize a select token we just need to invert its paths, so that + // we match the ordering on an ExpandToken. + return new SelectToken(invertedPaths); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/UnaryOperatorBinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/UnaryOperatorBinder.cs new file mode 100644 index 0000000..6d2858a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Binders/UnaryOperatorBinder.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Diagnostics; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Class that knows how to bind unary operators. + /// + internal sealed class UnaryOperatorBinder + { + /// + /// Method to use for binding the parent node, if needed. + /// + private readonly Func bindMethod; + + /// + /// Constructs a UnaryOperatorBinder with the given method to be used binding the parent token if needed. + /// + /// Method to use for binding the parent token, if needed. + internal UnaryOperatorBinder(Func bindMethod) + { + this.bindMethod = bindMethod; + } + + /// + /// Binds a unary operator token. + /// + /// The unary operator token to bind. + /// The bound unary operator token. + internal QueryNode BindUnaryOperator(UnaryOperatorToken unaryOperatorToken) + { + ExceptionUtils.CheckArgumentNotNull(unaryOperatorToken, "unaryOperatorToken"); + + SingleValueNode operand = this.GetOperandFromToken(unaryOperatorToken); + + IEdmTypeReference typeReference = UnaryOperatorBinder.PromoteOperandType(operand, unaryOperatorToken.OperatorKind); + Debug.Assert(typeReference == null || typeReference.IsODataPrimitiveTypeKind(), "Only primitive types should be able to get here."); + operand = MetadataBindingUtils.ConvertToTypeIfNeeded(operand, typeReference); + + return new UnaryOperatorNode(unaryOperatorToken.OperatorKind, operand); + } + + /// + /// Get the promoted type reference of the operand + /// + /// the operand + /// the operator kind + /// the type reference of the operand + private static IEdmTypeReference PromoteOperandType(SingleValueNode operand, UnaryOperatorKind unaryOperatorKind) + { + IEdmTypeReference typeReference = operand.TypeReference; + if (!TypePromotionUtils.PromoteOperandType(unaryOperatorKind, ref typeReference)) + { + string typeName = operand.TypeReference == null ? "" : operand.TypeReference.FullName(); + throw new ODataException(ODataErrorStrings.MetadataBinder_IncompatibleOperandError(typeName, unaryOperatorKind)); + } + + return typeReference; + } + + /// + /// Retrieve SingleValueNode operand from given token. + /// + /// The token + /// the SingleValueNode operand + private SingleValueNode GetOperandFromToken(UnaryOperatorToken unaryOperatorToken) + { + SingleValueNode operand = this.bindMethod(unaryOperatorToken.Operand) as SingleValueNode; + if (operand == null) + { + throw new ODataException(ODataErrorStrings.MetadataBinder_UnaryOperatorOperandNotSingleValue(unaryOperatorToken.OperatorKind.ToString())); + } + + return operand; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/BuiltInUriFunctions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/BuiltInUriFunctions.cs new file mode 100644 index 0000000..fca9020 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/BuiltInUriFunctions.cs @@ -0,0 +1,539 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Class containing definitions of all the built-in functions in OData Protocol. + /// + internal static class BuiltInUriFunctions + { + /// + /// Dictionary of the name of the built-in function and all the signatures. + /// + private static readonly Dictionary builtInFunctions = InitializeBuiltInFunctions(); + + /// + /// Returns a list of signatures for a function name. + /// + /// The name of the function to look for. + /// The list of signatures available for the function name. + /// true if the function was found, or false otherwise. + internal static bool TryGetBuiltInFunction(string name, out FunctionSignatureWithReturnType[] signatures) + { + Debug.Assert(name != null, "name != null"); + + return builtInFunctions.TryGetValue(name, out signatures); + } + + /// + /// Creates all of the spatial functions + /// + /// Dictionary of functions to add to. + internal static void CreateSpatialFunctions(IDictionary functions) + { + // double geo.distance(geographyPoint, geographyPoint) + // double geo.distance(geometryPoint, geometryPoint) + FunctionSignatureWithReturnType[] signatures = new FunctionSignatureWithReturnType[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDouble(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyPoint, true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyPoint, true)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDouble(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeometryPoint, true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeometryPoint, true)) + }; + functions.Add("geo.distance", signatures); + + // bool geo.intersects(geometry.Point, geometry.Polygon) + // bool geo.intersects(geometry.Polygon, geomtery.Point) + // bool geo.intersects(geography.Point, geography.Polygon) + // bool geo.intersects(geography.Polygon, geography.Point) + signatures = new FunctionSignatureWithReturnType[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetBoolean(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeometryPoint, true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeometryPolygon, true)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetBoolean(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeometryPolygon, true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeometryPoint, true)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetBoolean(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyPoint, true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyPolygon, true)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetBoolean(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyPolygon, true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyPoint, true)) + }; + functions.Add("geo.intersects", signatures); + + // double geo.length(geometryLineString) + // double geo.length(geographyLineString) + signatures = new FunctionSignatureWithReturnType[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDouble(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeometryLineString, true)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDouble(true), + EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyLineString, true)), + }; + functions.Add("geo.length", signatures); + } + + /// + /// Builds the list of all built-in functions. + /// + /// Returns a dictionary of built in functions. + private static Dictionary InitializeBuiltInFunctions() + { + var functions = new Dictionary(StringComparer.Ordinal); + CreateStringFunctions(functions); + CreateSpatialFunctions(functions); + CreateDateTimeFunctions(functions); + CreateMathFunctions(functions); + return functions; + } + + /// + /// Creates all string functions. + /// + /// Dictionary of functions to add to. + private static void CreateStringFunctions(IDictionary functions) + { + FunctionSignatureWithReturnType signature; + FunctionSignatureWithReturnType[] signatures; + + // bool endswith(string, string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetBoolean(false), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("endswith", new FunctionSignatureWithReturnType[] { signature }); + + // int indexof(string, string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("indexof", new FunctionSignatureWithReturnType[] { signature }); + + // string replace(string, string, string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("replace", new FunctionSignatureWithReturnType[] { signature }); + + // bool startswith(string, string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetBoolean(false), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("startswith", new FunctionSignatureWithReturnType[] { signature }); + + // string tolower(string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("tolower", new FunctionSignatureWithReturnType[] { signature }); + + // string toupper(string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("toupper", new FunctionSignatureWithReturnType[] { signature }); + + // string trim(string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("trim", new FunctionSignatureWithReturnType[] { signature }); + + signatures = new FunctionSignatureWithReturnType[] + { + // string substring(string, int) + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetInt32(false)), + + // string substring(string, int?) + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetInt32(true)), + + // string substring(string, int, int) + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetInt32(false)), + + // string substring(string, int?, int) + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetInt32(true), + EdmCoreModel.Instance.GetInt32(false)), + + // string substring(string, int, int?) + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetInt32(true)), + + // string substring(string, int?, int?) + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetInt32(true), + EdmCoreModel.Instance.GetInt32(true)), + }; + functions.Add("substring", signatures); + + // bool contains(string, string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetBoolean(false), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("contains", new FunctionSignatureWithReturnType[] { signature }); + + // string concat(string, string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true), + EdmCoreModel.Instance.GetString(true)); + functions.Add("concat", new FunctionSignatureWithReturnType[] { signature }); + + // int length(string) + signature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetString(true)); + functions.Add("length", new FunctionSignatureWithReturnType[] { signature }); + } + + /// + /// Creates all date and time functions. + /// + /// Dictionary of functions to add to. + private static void CreateDateTimeFunctions(IDictionary functions) + { + // Basic Signature according to type + FunctionSignatureWithReturnType[] dateTimeOffsetReturnInt = CreateDateTimeFunctionSignatureArray(); + FunctionSignatureWithReturnType[] dateTimeoffsetReturnDate = CreateDateTimeOffsetReturnDate(); + FunctionSignatureWithReturnType[] dateTimeOffsetReturnDecimal = CreateDateTimeOffsetReturnDecimal(); + FunctionSignatureWithReturnType[] dateTimeoffsetReturnTimeOfDay = CreateDateTimeOffsetReturnTimeOfDay(); + FunctionSignatureWithReturnType[] dateTimeOffsetOrDurationReturnInt = dateTimeOffsetReturnInt.Concat(CreateDurationFunctionSignatures()).ToArray(); + FunctionSignatureWithReturnType[] voidReturnDateTimeOffset = CreateVoidReturnDateTimeOffset(); + FunctionSignatureWithReturnType[] durationReturnDecimal = CreateDurationReturnDecimal(); + FunctionSignatureWithReturnType[] dateReturnInt = CreateDateReturnInt(); + FunctionSignatureWithReturnType[] timeOfDayReturnInt = CreateTimeOfDayReturnInt(); + FunctionSignatureWithReturnType[] timeOfDayReturnDecimal = CreateTimeOfDayReturnDecimal(); + + // Signature array according to function name + FunctionSignatureWithReturnType[] yearMonthDayFunctionSignatures = dateTimeOffsetReturnInt.Concat(dateReturnInt).ToArray(); + FunctionSignatureWithReturnType[] hourMinuteSecondFunctionSignatures = dateTimeOffsetOrDurationReturnInt.Concat(timeOfDayReturnInt).ToArray(); + FunctionSignatureWithReturnType[] fractionalsecondsFunctionSignatures = dateTimeOffsetReturnDecimal.Concat(timeOfDayReturnDecimal).ToArray(); + + // int year(DateTimeOffset) + // int year(DateTimeOffset?) + // int year(Date) + // int year(Date?) + functions.Add("year", yearMonthDayFunctionSignatures); + + // int month(DateTimeOffset) + // int month(DateTimeOffset?) + // int month(Date) + // int month(Date?) + functions.Add("month", yearMonthDayFunctionSignatures); + + // int day(DateTimeOffset) + // int day(DateTimeOffset?) + // int day(Date) + // int day(Date?) + functions.Add("day", yearMonthDayFunctionSignatures); + + // int hour(DateTimeOffset) + // int hour(DateTimeOffset?) + // int hour(TimeSpan) + // int hour(TimeSpan?) + // int hour(TimeOfDay) + // int hour(TimeOfDay?) + functions.Add("hour", hourMinuteSecondFunctionSignatures); + + // int minute(DateTimeOffset) + // int minute(DateTimeOffset?) + // int minute(TimeSpan) + // int minute(TimeSpan?) + // int minute(TimeOfDay) + // int minute(TimeOfDay?) + functions.Add("minute", hourMinuteSecondFunctionSignatures); + + // int second(DateTimeOffset) + // int second(DateTimeOffset?) + // int second(TimeSpan) + // int second(TimeSpan?) + // int second(TimeOfDay) + // int second(TimeOfDay?) + functions.Add("second", hourMinuteSecondFunctionSignatures); + + // Use protocol signature + // Edm.Decimal fractionalseconds(Edm.DateTimeOffset) + // Edm.Decimal fractionalseconds(Edm.TimeOfDay) + functions.Add("fractionalseconds", fractionalsecondsFunctionSignatures); + + // Edm.Int32 totaloffsetminutes(Edm.DateTimeOffset) + functions.Add("totaloffsetminutes", dateTimeOffsetReturnInt); + + // Edm.DateTimeOffset now() + functions.Add("now", voidReturnDateTimeOffset); + + // Edm.DateTimeOffset maxdatetime() + functions.Add("maxdatetime", voidReturnDateTimeOffset); + + // Edm.DateTimeOffset mindatetime() + functions.Add("mindatetime", voidReturnDateTimeOffset); + + // Edm.Decimal totalseconds(Edm.Duration) + functions.Add("totalseconds", durationReturnDecimal); + + // Edm.Date date(Edm.DateTimeOffset) + functions.Add("date", dateTimeoffsetReturnDate); + + // Edm.TimeOfDay time(Edm.DateTimeOffset) + functions.Add("time", dateTimeoffsetReturnTimeOfDay); + } + + /// + /// Builds an array of signatures for date time functions. + /// + /// The array of signatures for a date time functions. + private static FunctionSignatureWithReturnType[] CreateDateTimeFunctionSignatureArray() + { + FunctionSignatureWithReturnType dateTimeOffsetNonNullable = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, false)); + + FunctionSignatureWithReturnType dateTimeOffsetNullable = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, true)); + + return new[] { dateTimeOffsetNonNullable, dateTimeOffsetNullable }; + } + + /// + /// Builds the set of signatures for duration functions. + /// + /// The set of signatures for duration functions. + private static IEnumerable CreateDurationFunctionSignatures() + { + yield return new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, false)); + + yield return new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, true)); + } + + /// + /// Builds the set of signatures for 'DateTimeOffset function()'. + /// + /// The set of signatures for 'DateTimeOffset function()'. + private static FunctionSignatureWithReturnType[] CreateVoidReturnDateTimeOffset() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, false)), + }; + } + + /// + /// Builds the set of signatures for 'Date function(DateTimeOffset)'. + /// + /// The set of signatures for 'Date function(DateTimeOffset)'. + private static FunctionSignatureWithReturnType[] CreateDateTimeOffsetReturnDate() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDate(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, false)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDate(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, true)), + }; + } + + /// + /// Builds the set of signatures for 'Decimal function(DateTimeOffset)'. + /// + /// The set of signatures for 'Decimal function(DateTimeOffset)'. + private static FunctionSignatureWithReturnType[] CreateDateTimeOffsetReturnDecimal() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, false)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, true)), + }; + } + + /// + /// Builds the set of signatures for 'TimeOfDay function(DateTimeOffset)'. + /// + /// The set of signatures for 'TimeOfDay function(DateTimeOffset)'. + private static FunctionSignatureWithReturnType[] CreateDateTimeOffsetReturnTimeOfDay() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, false)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, true)), + }; + } + + /// + /// Builds the set of signatures for 'Decimal function(Duration)'. + /// + /// The set of signatures for 'Decimal function(Duration)'. + private static FunctionSignatureWithReturnType[] CreateDurationReturnDecimal() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, false)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, true)), + }; + } + + /// + /// Builds the set of signatures for 'Int function(Date)'. + /// + /// The set of signatures for 'Int function(Date)'. + private static FunctionSignatureWithReturnType[] CreateDateReturnInt() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetDate(false)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetDate(true)), + }; + } + + /// + /// Builds the set of signatures for 'Int function(TimeOfDay)'. + /// + /// The set of signatures for 'Int function(TimeOfDay)'. + private static FunctionSignatureWithReturnType[] CreateTimeOfDayReturnInt() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, false)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetInt32(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, true)), + }; + } + + /// + /// Builds the set of signatures for 'Decimal function(TimeOfDay)'. + /// + /// The set of signatures for 'Decimal function(TimeOfDay)'. + private static FunctionSignatureWithReturnType[] CreateTimeOfDayReturnDecimal() + { + return new[] + { + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, false)), + new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, true)), + }; + } + + /// + /// Creates all math functions. + /// + /// Dictionary of functions to add to. + private static void CreateMathFunctions(IDictionary functions) + { + // double round(double) + // decimal round(decimal) + // double round(double?) + // decimal round(decimal?) + functions.Add("round", CreateMathFunctionSignatureArray()); + + // double floor(double) + // decimal floor(decimal) + // double floor(double?) + // decimal floor(decimal?) + functions.Add("floor", CreateMathFunctionSignatureArray()); + + // double ceiling(double) + // decimal ceiling(decimal) + // double ceiling(double?) + // decimal ceiling(decimal?) + functions.Add("ceiling", CreateMathFunctionSignatureArray()); + } + + /// + /// Builds an array of signatures for math functions. + /// + /// The array of signatures for math functions. + private static FunctionSignatureWithReturnType[] CreateMathFunctionSignatureArray() + { + FunctionSignatureWithReturnType doubleSignature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDouble(false), + EdmCoreModel.Instance.GetDouble(false)); + FunctionSignatureWithReturnType nullableDoubleSignature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDouble(false), + EdmCoreModel.Instance.GetDouble(true)); + FunctionSignatureWithReturnType decimalSignature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetDecimal(false)); + FunctionSignatureWithReturnType nullableDecimalSignature = new FunctionSignatureWithReturnType( + EdmCoreModel.Instance.GetDecimal(false), + EdmCoreModel.Instance.GetDecimal(true)); + + return new FunctionSignatureWithReturnType[] { doubleSignature, decimalSignature, nullableDoubleSignature, nullableDecimalSignature }; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriFunctions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriFunctions.cs new file mode 100644 index 0000000..55f6a9b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriFunctions.cs @@ -0,0 +1,260 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region NameSpaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + + #endregion + + /// + /// Class represents functions signatures of custom uri functions. + /// + public static class CustomUriFunctions + { + #region Static Fields + + /// + /// Dictionary of the name of the custom function and all the signatures. + /// + private static readonly Dictionary CustomFunctions + = new Dictionary(StringComparer.Ordinal); + + private static readonly object Locker = new object(); + + #endregion + + #region Public Methods + + /// + /// Add a custom uri function to extend uri functions. + /// In case the function name already exists as a custom function, the signature will be added as an another overload. + /// + /// The new custom function name + /// The new custom function signature + /// Arguments are null, or function signature return type is null + /// Throws if built-in function name already exists. + /// Throws if built-in function signature overload already exists. + /// Throws if custom function signature overload already exists + public static void AddCustomUriFunction(string functionName, FunctionSignatureWithReturnType functionSignature) + { + // Parameters validation + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(functionName, "customFunctionName"); + ExceptionUtils.CheckArgumentNotNull(functionSignature, "newCustomFunctionSignature"); + + ValidateFunctionWithReturnType(functionSignature); + + // Thread saftey - before using the custom functions dictionary + lock (Locker) + { + // Check if the function does already exists in the Built-In functions + // If 'addAsOverloadToBuiltInFunction' parameter is false - throw expection + // Else, add as a custom function + FunctionSignatureWithReturnType[] existingBuiltInFunctionOverload; + if (BuiltInUriFunctions.TryGetBuiltInFunction(functionName, out existingBuiltInFunctionOverload)) + { + // Function name exists, check if full signature exists among the overloads. + if (existingBuiltInFunctionOverload.Any(builtInFunction => + AreFunctionsSignatureEqual(functionSignature, builtInFunction))) + { + throw new ODataException(Strings.CustomUriFunctions_AddCustomUriFunction_BuiltInExistsFullSignature(functionName)); + } + } + + AddCustomFunction(functionName, functionSignature); + } + } + + /// + /// Removes the specific function overload from the custum uri functions. + /// + /// Custom function name to remove + /// The specific signature overload of the function to remove + /// 'False' if custom function signature doesn't exist. 'True' if function has been removed successfully + /// Arguments are null, or function signature return type is null + public static bool RemoveCustomUriFunction(string functionName, FunctionSignatureWithReturnType functionSignature) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(functionName, "customFunctionName"); + ExceptionUtils.CheckArgumentNotNull(functionSignature, "customFunctionSignature"); + + ValidateFunctionWithReturnType(functionSignature); + + lock (Locker) + { + FunctionSignatureWithReturnType[] existingCustomFunctionOverloads; + if (!CustomFunctions.TryGetValue(functionName, out existingCustomFunctionOverloads)) + { + return false; + } + + // Get all function sigature overloads without the overload which is requested to be removed + FunctionSignatureWithReturnType[] customFunctionOverloadsWithoutTheOneToRemove = + existingCustomFunctionOverloads.SkipWhile(funcOverload => AreFunctionsSignatureEqual(funcOverload, functionSignature)).ToArray(); + + // Nothing was removed - Requested overload doesn't exist + if (customFunctionOverloadsWithoutTheOneToRemove.Length == existingCustomFunctionOverloads.Length) + { + return false; + } + + // No overloads have left in this function name. Delete the function name + if (customFunctionOverloadsWithoutTheOneToRemove.Length == 0) + { + return CustomFunctions.Remove(functionName); + } + else + { + // Requested overload has been removed. + // Update the custom functions to the overloads wihtout that one requested to be removed + CustomFunctions[functionName] = customFunctionOverloadsWithoutTheOneToRemove; + return true; + } + } + } + + /// + /// Removes all the function overloads from the custom uri functions. + /// + /// The custom function name + /// 'False' if custom function signature doesn't exist. 'True' if function has been removed successfully + /// Arguments are null, or function signature return type is null + public static bool RemoveCustomUriFunction(string functionName) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(functionName, "customFunctionName"); + + lock (Locker) + { + return CustomFunctions.Remove(functionName); + } + } + + #endregion + + #region Internal Methods + + /// + /// Returns a list of name-signature pairs for a function name. + /// + /// The name of the function to look for. + /// + /// Output for the list of signature objects for matched function names, with canonical name of the function; + /// null if no matches found. + /// + /// Whether to perform case-insensitive match for function name. + /// true if the function was found, or false otherwise. + internal static bool TryGetCustomFunction(string functionCallToken, out IList> nameSignatures, + bool enableCaseInsensitive = false) + { + Debug.Assert(functionCallToken != null, "name != null"); + + lock (Locker) + { + IList> bufferedKeyValuePairs + = new List>(); + + foreach (KeyValuePair func in CustomFunctions) + { + if (func.Key.Equals(functionCallToken, enableCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) + { + foreach (FunctionSignatureWithReturnType sig in func.Value) + { + bufferedKeyValuePairs.Add(new KeyValuePair(func.Key, sig)); + } + } + } + + // Setup the output values. + nameSignatures = bufferedKeyValuePairs.Count != 0 ? bufferedKeyValuePairs : null; + + return nameSignatures != null; + } + } + + #endregion + + #region Private Methods + + private static void AddCustomFunction(string customFunctionName, FunctionSignatureWithReturnType newCustomFunctionSignature) + { + FunctionSignatureWithReturnType[] existingCustomFunctionOverloads; + + // In case the function doesn't already exist + if (!CustomFunctions.TryGetValue(customFunctionName, out existingCustomFunctionOverloads)) + { + CustomFunctions.Add(customFunctionName, new FunctionSignatureWithReturnType[] { newCustomFunctionSignature }); + } + else + { + // Function does already exist as a custom function in cache + // Check if the function overload doesn't already exist + bool isOverloadAlreadyExist = + existingCustomFunctionOverloads.Any(existingFunction => AreFunctionsSignatureEqual(existingFunction, newCustomFunctionSignature)); + + if (isOverloadAlreadyExist) + { + // Throw if already exists - User is stupid (inserted the same function twice) + throw new ODataException(Strings.CustomUriFunctions_AddCustomUriFunction_CustomFunctionOverloadExists(customFunctionName)); + } + + // Add the custom function as an overload to the same function name + CustomFunctions[customFunctionName] = + existingCustomFunctionOverloads.Concat(new FunctionSignatureWithReturnType[] { newCustomFunctionSignature }).ToArray(); + } + } + + private static bool AreFunctionsSignatureEqual(FunctionSignatureWithReturnType functionOne, FunctionSignatureWithReturnType functionTwo) + { + Debug.Assert(functionOne != null, "functionOne != null"); + Debug.Assert(functionTwo != null, "functionTwo != null"); + + // Check if ReturnTypes are equal + if (!functionOne.ReturnType.IsEquivalentTo(functionTwo.ReturnType)) + { + return false; + } + + // Check the length of the Arguments of the two functions + if (functionOne.ArgumentTypes.Length != functionTwo.ArgumentTypes.Length) + { + return false; + } + + // Check if the arguments are equal + for (int argumentIndex = 0; argumentIndex < functionOne.ArgumentTypes.Length; argumentIndex++) + { + if (!functionOne.ArgumentTypes[argumentIndex].IsEquivalentTo(functionTwo.ArgumentTypes[argumentIndex])) + { + return false; + } + } + + return true; + } + + /// + /// Check if FunctionSignatureWithReturnType is valid. + /// Vaild if the signature has a ReturnType + /// + /// Function signature to validate + private static void ValidateFunctionWithReturnType(FunctionSignatureWithReturnType functionSignature) + { + if (functionSignature == null) + { + return; + } + + ExceptionUtils.CheckArgumentNotNull(functionSignature.ReturnType, "functionSignatureWithReturnType must contain a return type"); + } + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriLiteralPrefixes.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriLiteralPrefixes.cs new file mode 100644 index 0000000..bba90f5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/CustomUriLiteralPrefixes.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region NameSpaces + + using System; + using System.Collections.Generic; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion + + /// + /// Extends the uri parsing system of Literal Prefix. + /// With this class, you can add a custom literal prefix for any EdmType. + /// + public static class CustomUriLiteralPrefixes + { + #region Fields + + private static readonly object Locker = new object(); + + private static Dictionary CustomLiteralPrefixesOfEdmTypes = new Dictionary(StringComparer.Ordinal); + + #endregion + + #region Public Static Methods + + /// + /// Add a literal prefix for the given EdmType. + /// + /// filter=MyProperty eq MyCustomLiteral'VALUE'. + /// "MyCustomLiteral" is the literal prefix and the is the type of the "VALUE". + /// The custom name of the literal prefix + /// The edm type of the custom literal + /// Arguments are null or empty + /// The given literal prefix is not valid + /// The given literal prefix already exists + public static void AddCustomLiteralPrefix(string literalPrefix, IEdmTypeReference literalEdmTypeReference) + { + // Arguments validation + ExceptionUtils.CheckArgumentNotNull(literalEdmTypeReference, "literalEdmTypeReference"); + + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(literalPrefix, "literalPrefix"); + + UriParserHelper.ValidatePrefixLiteral(literalPrefix); + + // Try to add the custom uri literal to cache + lock (Locker) + { + // Check if literal does already exists + if (CustomLiteralPrefixesOfEdmTypes.ContainsKey(literalPrefix)) + { + throw new ODataException(ODataErrorStrings.CustomUriTypePrefixLiterals_AddCustomUriTypePrefixLiteralAlreadyExists(literalPrefix)); + } + + CustomLiteralPrefixesOfEdmTypes.Add(literalPrefix, literalEdmTypeReference); + } + } + + /// + /// Remove the given literal prefix + /// + /// The custom name of the literal prefix + /// 'true' if the literal prefix is successfully found and removed; otherwise, 'false'. + /// Argument is null or empty + public static bool RemoveCustomLiteralPrefix(string literalPrefix) + { + // Arguments validation + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(literalPrefix, "literalPrefix"); + + UriParserHelper.ValidatePrefixLiteral(literalPrefix); + + // Try to remove the custom uri literal prefix from cache + lock (Locker) + { + return CustomLiteralPrefixesOfEdmTypes.Remove(literalPrefix); + } + } + + #endregion + + #region Internal Methods + + /// + /// Gets the EdmTypeReference of the given literal prefix + /// + /// The literal prefix of the EdmType + /// Null if the custom literal prefix has no registered EdmType. + internal static IEdmTypeReference GetEdmTypeByCustomLiteralPrefix(string literalPrefix) + { + lock (Locker) + { + IEdmTypeReference edmTypeReference; + if (CustomLiteralPrefixesOfEdmTypes.TryGetValue(literalPrefix, out edmTypeReference)) + { + return edmTypeReference; + } + } + + return null; + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExceptionUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExceptionUtil.cs new file mode 100644 index 0000000..cebcb55 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExceptionUtil.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Helper class for throwing exceptions during URI parsing. + /// + internal static class ExceptionUtil + { + /// Creates a new "Resource Not Found" exception. + /// segment identifier information for which resource was not found. + /// A new exception to indicate the requested resource cannot be found. + internal static ODataException CreateResourceNotFoundError(string identifier) + { + // 404: Not Found + return ResourceNotFoundError(Strings.RequestUriProcessor_ResourceNotFound(identifier)); + } + + /// Creates a new "Resource Not Found" exception. + /// Plain text error message for this exception. + /// A new exception to indicate the requested resource cannot be found. + internal static ODataException ResourceNotFoundError(string errorMessage) + { + // 404: Not Found + return new ODataUnrecognizedPathException(errorMessage); + } + + /// Creates a new exception to indicate a syntax error. + /// A new exception to indicate a syntax error. + internal static ODataException CreateSyntaxError() + { + return CreateBadRequestError(Strings.RequestUriProcessor_SyntaxError); + } + + /// + /// Creates a new exception to indicate BadRequest error. + /// + /// Plain text error message for this exception. + /// A new exception to indicate a bad request error. + internal static ODataException CreateBadRequestError(string message) + { + // 400 - Bad Request + return new ODataException(message); + } + + /// + /// Throws if the type is not related to the type of the given set. + /// + /// Type to check. + /// Second type, which should be related to the first type. + /// The segment that is checking this. + internal static void ThrowIfTypesUnrelated(IEdmType type, IEdmType secondType, string segmentName) + { + if (!UriEdmHelpers.IsRelatedTo(type.AsElementType(), secondType.AsElementType())) + { + throw new ODataException(Strings.PathParser_TypeMustBeRelatedToSet(type, secondType, segmentName)); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs new file mode 100644 index 0000000..64a7d77 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexer.cs @@ -0,0 +1,1340 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Text; + using Microsoft.OData; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// Use this class to parse an expression in the OData URI format. + /// + /// Literals (non-normative "handy" reference - see spec for correct expression): + /// Null null + /// Boolean true | false + /// Int32 (digit+) + /// Int64 (digit+)[L|l] + /// Decimal (digit+ ['.' digit+])[M|m] + /// Float (digit+ ['.' digit+][e|E [+|-] digit+)[f|F] + /// Double (digit+ ['.' digit+][e|E [+|-] digit+)[d|D] + /// String "'" .* "'" + /// DateTime datetime"'"dddd-dd-dd[T|' ']dd:mm[ss[.fffffff]]"'" + /// DateTimeOffset dddd-dd-dd[T|' ']dd:mm[ss[.fffffff]]-dd:mm + /// Duration time"'"dd:mm[ss[.fffffff]]"'" + /// Binary (binary|X)'digit*' + /// GUID 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 12HEXDIG + /// + /// Note: ABNF v4.0 actually has forbidden numeric string's trailing L,M,F,D though we allow them to be optional + /// http://docs.oasis-open.org/odata/odata/v4.0/cs01/abnf/odata-abnf-construction-rules.txt + /// decimalValue = [SIGN] 1*DIGIT ["." 1*DIGIT] + /// doubleValue = decimalValue [ "e" [SIGN] 1*DIGIT ] / nanInfinity ; with restricted number range + /// singleValue = doubleValue ; with restricted number range + /// nanInfinity = 'NaN' / '-INF' / 'INF' + /// + /// + [DebuggerDisplay("ExpressionLexer ({text} @ {textPos} [{token}])")] + internal class ExpressionLexer + { + #region Protected and Private fields + + /// Text being parsed. + protected readonly string Text; + + /// Length of text being parsed. + protected readonly int TextLen; + + /// Position on text being parsed. + protected int textPos; + + /// Character being processed. + protected char? ch; + + /// Token being processed. + protected ExpressionToken token; + + /// + /// For an identifier, EMD supports chars that match the regex [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Cf}] + /// IsLetterOrDigit covers Ll, Lu, Lt, Lo, Lm, Nd, this set covers the rest + /// + private static readonly HashSet AdditionalUnicodeCategoriesForIdentifier = new HashSet(new UnicodeCategoryEqualityComparer()) + { + UnicodeCategory.LetterNumber, + UnicodeCategory.NonSpacingMark, + UnicodeCategory.SpacingCombiningMark, + UnicodeCategory.ConnectorPunctuation, // covers "_" + UnicodeCategory.Format + }; + + /// flag to indicate whether to delimit on a semicolon. + private readonly bool useSemicolonDelimiter; + + /// Whether the lexer is being used to parse function parameters. If true, will allow/recognize parameter aliases and typed nulls. + private readonly bool parsingFunctionParameters; + + /// Lexer ignores whitespace + private bool ignoreWhitespace; + + #endregion Protected and Private fields + + #region Constructors + + /// Initializes a new . + /// Expression to parse. + /// If true, this constructor will call NextToken() to move to the first token. + /// If true, the lexer will tokenize based on semicolons as well. + internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemicolonDelimiter) + : this(expression, moveToFirstToken, useSemicolonDelimiter, false /*parsingFunctionParameters*/) + { + } + + /// Initializes a new . + /// Expression to parse. + /// If true, this constructor will call NextToken() to move to the first token. + /// If true, the lexer will tokenize based on semicolons as well. + /// Whether the lexer is being used to parse function parameters. If true, will allow/recognize parameter aliases and typed nulls. + internal ExpressionLexer(string expression, bool moveToFirstToken, bool useSemicolonDelimiter, bool parsingFunctionParameters) + { + Debug.Assert(expression != null, "expression != null"); + + this.ignoreWhitespace = true; + this.Text = expression; + this.TextLen = this.Text.Length; + this.useSemicolonDelimiter = useSemicolonDelimiter; + this.parsingFunctionParameters = parsingFunctionParameters; + + this.SetTextPos(0); + + if (moveToFirstToken) + { + this.NextToken(); + } + } + + #endregion Constructors + + #region Internal properties + + /// Token being processed. + internal ExpressionToken CurrentToken + { + get + { + return this.token; + } + + set + { + this.token = value; + } + } + + /// Text being parsed. + internal string ExpressionText + { + get + { + return this.Text; + } + } + + /// Position on text being parsed. + internal int Position + { + get + { + return this.token.Position; + } + } + + #endregion Internal properties + + #region Private properties + /// + /// Gets if the current char is whitespace. + /// + protected bool IsValidWhiteSpace + { + get + { + return this.ch != null && Char.IsWhiteSpace(this.ch.Value); + } + } + + /// + /// Gets if the current char is digit. + /// + private bool IsValidDigit + { + get + { + return this.ch != null && Char.IsDigit(this.ch.Value); + } + } + + /// + /// Is the current char a valid starting char for an identifier. + /// Valid starting chars for identifier include all that are supported by EDM ([\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}]) and '_'. + /// + private bool IsValidStartingCharForIdentifier + { + get + { + return this.ch != null && ( + Char.IsLetter(this.ch.Value) || // IsLetter covers: Ll, Lu, Lt, Lo, Lm + this.ch == '_' || + this.ch == '$' || + PlatformHelper.GetUnicodeCategory(this.ch.Value) == UnicodeCategory.LetterNumber); + } + } + + /// + /// Is the current char a valid non-starting char for an identifier. + /// Valid non-starting chars for identifier include all that are supported + /// by EDM [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Cf}]. + /// This list includes '_', which is ConnectorPunctuation (Pc) + /// + private bool IsValidNonStartingCharForIdentifier + { + get + { + return this.ch != null && ( + Char.IsLetterOrDigit(this.ch.Value) || // covers: Ll, Lu, Lt, Lo, Lm, Nd + AdditionalUnicodeCategoriesForIdentifier.Contains(PlatformHelper.GetUnicodeCategory(this.ch.Value))); // covers the rest + } + } + #endregion + + #region Internal methods + /// + /// Determines if the next token can be processed without error without advancing the token. + /// + /// The next ExpressionToken. This value is undefined if error is defined. + /// Exception generated from trying to process the next token. + /// True if the next token can be processed, false otherwise. + internal bool TryPeekNextToken(out ExpressionToken resultToken, out Exception error) + { + int savedTextPos = this.textPos; + char? savedChar = this.ch; + ExpressionToken savedToken = this.token; + + resultToken = this.NextTokenImplementation(out error); + + this.textPos = savedTextPos; + this.ch = savedChar; + this.token = savedToken; + + return error == null; + } + + /// Reads the next token, skipping whitespace as necessary, advancing the Lexer. + /// The next token. + /// Throws on error. + internal ExpressionToken NextToken() + { + Exception error = null; + ExpressionToken nextToken = this.NextTokenImplementation(out error); + + if (error != null) + { + throw error; + } + + return nextToken; + } + + /// + /// Starting from an identifier, reads a sequence of dots and + /// identifiers, and returns the text for it, with whitespace + /// stripped. + /// + /// do we allow a star in this identifier + /// The dotted identifier starting at the current identifier. + internal string ReadDottedIdentifier(bool acceptStar) + { + this.ValidateToken(ExpressionTokenKind.Identifier); + StringBuilder builder = null; + string result = this.CurrentToken.Text; + this.NextToken(); + while (this.CurrentToken.Kind == ExpressionTokenKind.Dot) + { + this.NextToken(); + if (this.CurrentToken.Kind != ExpressionTokenKind.Identifier && + this.CurrentToken.Kind != ExpressionTokenKind.QuotedLiteral) + { + if (this.CurrentToken.Kind == ExpressionTokenKind.Star) + { + // if we accept a star and this is the last token in the identifier, then we're ok... otherwise we throw. + if (!acceptStar || (this.PeekNextToken().Kind != ExpressionTokenKind.End && this.PeekNextToken().Kind != ExpressionTokenKind.Comma)) + { + throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); + } + } + else + { + throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); + } + } + + if (builder == null) + { + builder = new StringBuilder(result, result.Length + 1 + this.CurrentToken.Text.Length); + } + + builder.Append('.'); + builder.Append(this.CurrentToken.Text); + this.NextToken(); + } + + if (builder != null) + { + result = builder.ToString(); + } + + return result; + } + + /// Returns the next token without advancing the lexer. + /// The next token. + internal ExpressionToken PeekNextToken() + { + ExpressionToken outToken; + Exception error; + this.TryPeekNextToken(out outToken, out error); + + if (error != null) + { + throw error; + } + + return outToken; + } + + /// + /// Check whether the current identifier is a function. If so, expand the token text to the function signature + /// + /// True if the current identifier is a function call + internal bool ExpandIdentifierAsFunction() + { + // FUNCTION := ( {}) ... + // if we fail to match then we leave the token as it + ExpressionTokenKind id = this.token.Kind; + if (id != ExpressionTokenKind.Identifier) + { + return false; + } + + int savedTextPos = this.textPos; + char? savedChar = this.ch; + ExpressionToken savedToken = this.token; + bool savedIgnoreWs = this.ignoreWhitespace; + this.ignoreWhitespace = false; + + // Expansion left anchor + int tokenStartPos = this.token.Position; + + while (this.MoveNextWhenMatch(ExpressionTokenKind.Dot) && this.MoveNextWhenMatch(ExpressionTokenKind.Identifier)) + { + } + + bool matched = false; + if (this.CurrentToken.Kind == ExpressionTokenKind.Identifier) + { + ExpressionTokenKind nextKind = this.PeekNextToken().Kind; + + // Special case for 'in' operator: It is legal to have a custom function with the same name as an operator. + // If the current identifier is 'in', PeekNextToken() will return ParenthesesExpression instead of OpenParen. + // Nevertheless, we should still treat it as a function. + matched = + nextKind == ExpressionTokenKind.OpenParen || + (nextKind == ExpressionTokenKind.ParenthesesExpression && this.CurrentToken.Text == ExpressionConstants.KeywordIn); + } + + if (matched) + { + this.token.Text = this.Text.Substring(tokenStartPos, this.textPos - tokenStartPos); + this.token.Position = tokenStartPos; + } + else + { + this.textPos = savedTextPos; + this.ch = savedChar; + this.token = savedToken; + } + + this.ignoreWhitespace = savedIgnoreWs; + + return matched; + } + + /// Validates the current token is of the specified kind. + /// Expected token kind. + internal void ValidateToken(ExpressionTokenKind t) + { + if (this.token.Kind != t) + { + throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); + } + } + + /// + /// Advances the lexer until a semicolon, an unbalanced close parens occurs, or the text ends. + /// Any string literals (text in single quotes) will be skipped when checking for delimiters. + /// The CurrentToken of the lexer after this method call will be whatever comes after the advanced text. + /// + /// All of the text that was read. + internal string AdvanceThroughExpandOption() + { + int startingPosition = this.textPos; + string textToReturn; + + while (true) + { + if (this.ch == '\'') + { + this.AdvanceToNextOccuranceOf('\''); + } + + if (this.ch == '(') + { + this.NextChar(); + this.AdvanceThroughBalancedParentheticalExpression(); + continue; + } + + if (this.ch == ';' || this.ch == ')') + { + textToReturn = this.Text.Substring(startingPosition, this.textPos - startingPosition); + break; + } + + if (this.ch == null) + { + textToReturn = this.Text.Substring(startingPosition); + break; + } + + this.NextChar(); + } + + // Move the lexer to be on the delimiter (or past, if the delimiter isn't something special) + this.NextToken(); + + return textToReturn; + } + + /// + /// Advances through a balanced expression that we do not want to parse, beginning with a '(' and ending with a ')'. + /// + /// + /// 1. This method will identify and advance through inner pairs of parenthesis. + /// 2. The lexer is expected to have a CurrentToken which is the open parenthesis at the start, meaning that we are positioned + /// on the first character of the expression inside that. + /// 3. When we are done we will be right after the closing ')', but we will have have set CurrentToken to anything. + /// For this reason, you probably want to call NextToken() after this method, since CurrentToken wil be garbage. + /// + /// The parenthesis expression, including the outer parenthesis. + internal string AdvanceThroughBalancedParentheticalExpression() + { + int startPosition = this.Position; + this.AdvanceThroughBalancedExpression('(', ')'); + string expressionText = this.Text.Substring(startPosition, this.textPos - startPosition); + + //// TODO: Consider introducing a token type and setting up the current token instead of returning string. + //// We've done weird stuff, and the state of hte lexer is weird now. All will be well once NextToken() is called, + //// but until then CurrentToken is stale and misleading. + + return expressionText; + } + + /// + /// Get the current position in this lexer that can be used restore the lexer to this position later. + /// + /// + /// Returns a snapshot position used to call RestorePosition. + /// + internal ExpressionLexerPosition SnapshotPosition() + { + return new ExpressionLexerPosition(this, this.textPos, this.token); + } + + /// + /// Sets the current position to the specified position. + /// + /// The position to restore, returned from SnapshotPosition. + /// + /// The specified position must have been retrieved by GetPostion on this instance. + /// + internal void RestorePosition(ExpressionLexerPosition position) + { + Debug.Assert(position.Lexer == this, "Position was not taken from this ExpressionLexer instance."); + + if (position.TextPos.HasValue) + { + SetTextPos(position.TextPos.Value); + } + + if (position.Token.HasValue) + { + this.token = position.Token.Value; + } + } + + #endregion Internal methods + + #region Private methods + /// Creates an exception for a parse error. + /// Message text. + /// A new Exception. + protected static Exception ParseError(string message) + { + return new ODataException(message); + } + + /// Advanced to the next character. + protected void NextChar() + { + if (this.textPos < this.TextLen) + { + this.textPos++; + if (this.textPos < this.TextLen) + { + this.ch = this.Text[this.textPos]; + return; + } + } + + this.ch = null; + } + + /// + /// Parses white spaces + /// + protected void ParseWhitespace() + { + while (this.IsValidWhiteSpace) + { + this.NextChar(); + } + } + + /// + /// Advance the pointer to the next occurance of the given value, swallowing all characters in between. + /// + /// the ending delimiter. + protected void AdvanceToNextOccuranceOf(char endingValue) + { + this.NextChar(); + while (this.ch.HasValue && (this.ch != endingValue)) + { + this.NextChar(); + } + } + + /// Reads the next token, skipping whitespace as necessary. + /// Error that occurred while trying to process the next token. + /// The next token, which may be 'bad' if an error occurs. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This parser method is all about the switch statement and would be harder to maintain if it were broken up.")] + protected virtual ExpressionToken NextTokenImplementation(out Exception error) + { + error = null; + + if (this.ignoreWhitespace) + { + this.ParseWhitespace(); + } + + ExpressionTokenKind t; + int tokenPos = this.textPos; + switch (this.ch) + { + case '(': + if (this.CurrentToken.Text == ExpressionConstants.KeywordIn) + { + this.NextChar(); + this.AdvanceThroughBalancedExpression('(', ')'); + t = ExpressionTokenKind.ParenthesesExpression; + } + else + { + this.NextChar(); + t = ExpressionTokenKind.OpenParen; + } + + break; + case ')': + this.NextChar(); + t = ExpressionTokenKind.CloseParen; + break; + case ',': + this.NextChar(); + t = ExpressionTokenKind.Comma; + break; + case '-': + bool hasNext = this.textPos + 1 < this.TextLen; + if (hasNext && Char.IsDigit(this.Text[this.textPos + 1])) + { + // don't separate '-' and its following digits : -2147483648 is valid int.MinValue, but 2147483648 is long. + t = this.ParseFromDigit(); + if (ExpressionLexerUtils.IsNumeric(t)) + { + break; + } + + // If it looked like a numeric but wasn't (because it was a binary 0x... value for example), + // we'll rewind and fall through to a simple '-' token. + this.SetTextPos(tokenPos); + } + else if (hasNext && this.Text[tokenPos + 1] == ExpressionConstants.InfinityLiteral[0]) + { + this.NextChar(); + this.ParseIdentifier(); + string currentIdentifier = this.Text.Substring(tokenPos + 1, this.textPos - tokenPos - 1); + + if (ExpressionLexerUtils.IsInfinityLiteralDouble(currentIdentifier)) + { + t = ExpressionTokenKind.DoubleLiteral; + break; + } + else if (ExpressionLexerUtils.IsInfinityLiteralSingle(currentIdentifier)) + { + t = ExpressionTokenKind.SingleLiteral; + break; + } + + // If it looked like '-INF' but wasn't we'll rewind and fall through to a simple '-' token. + this.SetTextPos(tokenPos); + } + + this.NextChar(); + t = ExpressionTokenKind.Minus; + break; + case '=': + this.NextChar(); + t = ExpressionTokenKind.Equal; + break; + case '/': + this.NextChar(); + t = ExpressionTokenKind.Slash; + break; + case '?': + this.NextChar(); + t = ExpressionTokenKind.Question; + break; + case '.': + this.NextChar(); + t = ExpressionTokenKind.Dot; + break; + case '\'': + char quote = this.ch.Value; + do + { + this.AdvanceToNextOccuranceOf(quote); + + if (this.textPos == this.TextLen) + { + error = ParseError(ODataErrorStrings.ExpressionLexer_UnterminatedStringLiteral(this.textPos, this.Text)); + } + + this.NextChar(); + } + while (this.ch.HasValue && (this.ch.Value == quote)); + t = ExpressionTokenKind.StringLiteral; + break; + case '*': + this.NextChar(); + t = ExpressionTokenKind.Star; + break; + case ':': + this.NextChar(); + t = ExpressionTokenKind.Colon; + break; + case '{': + this.NextChar(); + this.AdvanceThroughBalancedExpression('{', '}'); + t = ExpressionTokenKind.BracedExpression; + break; + case '[': + this.NextChar(); + this.AdvanceThroughBalancedExpression('[', ']'); + t = ExpressionTokenKind.BracketedExpression; + break; + default: + if (this.IsValidWhiteSpace) + { + Debug.Assert(!this.ignoreWhitespace, "should not hit ws while ignoring it"); + this.ParseWhitespace(); + t = ExpressionTokenKind.Unknown; + break; + } + + if (this.IsValidStartingCharForIdentifier) + { + this.ParseIdentifier(); + + // Guids will have '-' in them + // guidValue = 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 12HEXDIG + if (this.ch == '-' + && this.TryParseGuid(tokenPos)) + { + t = ExpressionTokenKind.GuidLiteral; + break; + } + + t = ExpressionTokenKind.Identifier; + break; + } + + if (this.IsValidDigit) + { + t = this.ParseFromDigit(); + break; + } + + if (this.textPos == this.TextLen) + { + t = ExpressionTokenKind.End; + break; + } + + if (this.useSemicolonDelimiter && this.ch == ';') + { + this.NextChar(); + t = ExpressionTokenKind.SemiColon; + break; + } + + if (this.ch == '@') + { + this.NextChar(); + + if (this.textPos == this.TextLen) + { + error = ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); + t = ExpressionTokenKind.Unknown; + break; + } + + if (!this.IsValidStartingCharForIdentifier) + { + error = ParseError(ODataErrorStrings.ExpressionLexer_InvalidCharacter(this.ch, this.textPos, this.Text)); + t = ExpressionTokenKind.Unknown; + break; + } + + int start = this.textPos; + + // Include dots for the case of annotation. + this.ParseIdentifier(true /*includingDots*/); + + // Extract the identifier from expression. + string leftToken = ExpressionText.Substring(start, this.textPos - start); + + + t = this.parsingFunctionParameters && !leftToken.Contains(".") + ? ExpressionTokenKind.ParameterAlias + : ExpressionTokenKind.Identifier; + break; + } + + error = ParseError(ODataErrorStrings.ExpressionLexer_InvalidCharacter(this.ch, this.textPos, this.Text)); + t = ExpressionTokenKind.Unknown; + break; + } + + this.token.Kind = t; + this.token.Text = this.Text.Substring(tokenPos, this.textPos - tokenPos); + this.token.Position = tokenPos; + + this.HandleTypePrefixedLiterals(); + + return this.token; + } + + /// + /// Expand the token selection if the next token matches the input token + /// + /// the list of token id to match + /// true if matched + private bool MoveNextWhenMatch(ExpressionTokenKind id) + { + ExpressionToken next = this.PeekNextToken(); + + if (id == next.Kind) + { + this.NextToken(); + return true; + } + + return false; + } + + /// Handles lexeres that are formed by identifiers. + /// This method modified the token field as necessary. + private void HandleTypePrefixedLiterals() + { + if (this.token.Kind != ExpressionTokenKind.Identifier) + { + return; + } + + // Get literal of quoted values + if (this.ch == '\'') + { + // Get custom literal if exists. + IEdmTypeReference edmTypeOfCustomLiteral = CustomUriLiteralPrefixes.GetEdmTypeByCustomLiteralPrefix(this.token.Text); + if (edmTypeOfCustomLiteral != null) + { + this.token.SetCustomEdmTypeLiteral(edmTypeOfCustomLiteral); + } + else + { + // Get built in type literal prefix for quoted values + this.token.Kind = this.GetBuiltInTypesLiteralPrefixWithQuotedValue(this.token.Text); + } + + this.HandleQuotedValues(); + } + else + { + // Handle keywords. + // Get built in type literal prefix. + ExpressionTokenKind? regularTokenKind = GetBuiltInTypesLiteralPrefix(this.token.Text); + if (regularTokenKind.HasValue) + { + this.token.Kind = regularTokenKind.Value; + } + } + } + + private void HandleQuotedValues() + { + int startPosition = this.token.Position; + + do + { + do + { + this.NextChar(); + } + while (this.ch.HasValue && this.ch != '\''); + + if (this.ch == null) + { + throw ParseError(ODataErrorStrings.ExpressionLexer_UnterminatedLiteral(this.textPos, this.Text)); + } + + this.NextChar(); + } + while (this.ch.HasValue && this.ch == '\''); + + // Update token.Text to include the literal + the quoted value + this.token.Text = this.Text.Substring(startPosition, this.textPos - startPosition); + } + + /// + /// Get type-prefixed literals such as double, boolean... + /// + /// Token texk + /// ExpressionTokenKind by the token text + private static ExpressionTokenKind? GetBuiltInTypesLiteralPrefix(string tokenText) + { + if (ExpressionLexerUtils.IsInfinityOrNaNDouble(tokenText)) + { + return ExpressionTokenKind.DoubleLiteral; + } + else if (ExpressionLexerUtils.IsInfinityOrNanSingle(tokenText)) + { + return ExpressionTokenKind.SingleLiteral; + } + else if (tokenText == ExpressionConstants.KeywordTrue || tokenText == ExpressionConstants.KeywordFalse) + { + return ExpressionTokenKind.BooleanLiteral; + } + else if (tokenText == ExpressionConstants.KeywordNull) + { + return ExpressionTokenKind.NullLiteral; + } + + return null; + } + + /// + /// Get type-prefixed literals with quoted values duration, binary and spatial types. + /// + /// Token text + /// ExpressionTokenKind + /// geometry'POINT (79 84)'. 'geometry' is the tokenText + private ExpressionTokenKind GetBuiltInTypesLiteralPrefixWithQuotedValue(string tokenText) + { + if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixDuration, StringComparison.OrdinalIgnoreCase)) + { + return ExpressionTokenKind.DurationLiteral; + } + else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixBinary, StringComparison.OrdinalIgnoreCase)) + { + return ExpressionTokenKind.BinaryLiteral; + } + else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixGeography, StringComparison.OrdinalIgnoreCase)) + { + return ExpressionTokenKind.GeographyLiteral; + } + else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixGeometry, StringComparison.OrdinalIgnoreCase)) + { + return ExpressionTokenKind.GeometryLiteral; + } + else if (string.Equals(tokenText, ExpressionConstants.KeywordNull, StringComparison.OrdinalIgnoreCase)) + { + // typed null literals are not supported. + throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); + } + else + { + // treat as quoted literal + return ExpressionTokenKind.QuotedLiteral; + } + } + + /// Parses a token that starts with a digit. + /// The kind of token recognized. + private ExpressionTokenKind ParseFromDigit() + { + Debug.Assert(this.IsValidDigit || ('-' == this.ch), "this.IsValidDigit || ('-' == this.ch)"); + ExpressionTokenKind result; + int tokenPos = this.textPos; + char startChar = this.ch.Value; + this.NextChar(); + if (startChar == '0' && (this.ch == 'x' || this.ch == 'X')) + { + result = ExpressionTokenKind.BinaryLiteral; + do + { + this.NextChar(); + } + while (this.ch.HasValue && UriParserHelper.IsCharHexDigit(this.ch.Value)); + } + else + { + result = ExpressionTokenKind.IntegerLiteral; + while (this.IsValidDigit) + { + this.NextChar(); + } + + // DateTimeOffset, Date and Guids will have '-' in them + if (this.ch == '-') + { + if (this.TryParseDate(tokenPos)) + { + return ExpressionTokenKind.DateLiteral; + } + else if (this.TryParseDateTimeoffset(tokenPos)) + { + return ExpressionTokenKind.DateTimeOffsetLiteral; + } + else if (this.TryParseGuid(tokenPos)) + { + return ExpressionTokenKind.GuidLiteral; + } + } + + // TimeOfDay will have ":" in them + if (this.ch == ':') + { + if (this.TryParseTimeOfDay(tokenPos)) + { + return ExpressionTokenKind.TimeOfDayLiteral; + } + } + + // Guids will have alpha-numeric characters along with '-', so if a letter is encountered + // try to see if this is Guid or not. + if (this.ch.HasValue && Char.IsLetter(this.ch.Value)) + { + if (this.TryParseGuid(tokenPos)) + { + return ExpressionTokenKind.GuidLiteral; + } + } + + if (this.ch == '.') + { + result = ExpressionTokenKind.DoubleLiteral; + this.NextChar(); + this.ValidateDigit(); + + do + { + this.NextChar(); + } + while (this.IsValidDigit); + } + + if (this.ch == 'E' || this.ch == 'e') + { + result = ExpressionTokenKind.DoubleLiteral; + this.NextChar(); + if (this.ch == '+' || this.ch == '-') + { + this.NextChar(); + } + + this.ValidateDigit(); + do + { + this.NextChar(); + } + while (this.IsValidDigit); + } + + if (this.ch == 'M' || this.ch == 'm') + { + result = ExpressionTokenKind.DecimalLiteral; + this.NextChar(); + } + else if (this.ch == 'd' || this.ch == 'D') + { + result = ExpressionTokenKind.DoubleLiteral; + this.NextChar(); + } + else if (this.ch == 'L' || this.ch == 'l') + { + result = ExpressionTokenKind.Int64Literal; + this.NextChar(); + } + else if (this.ch == 'f' || this.ch == 'F') + { + result = ExpressionTokenKind.SingleLiteral; + this.NextChar(); + } + else + { + string valueStr = this.Text.Substring(tokenPos, this.textPos - tokenPos); + result = MakeBestGuessOnNoSuffixStr(valueStr, result); + } + } + + return result; + } + + /// + /// Tries to parse Guid from current text + /// If it's not Guid, then this.textPos and this.ch are reset + /// + /// Start index + /// True if the substring that starts from tokenPos is a Guid, false otherwise + private bool TryParseGuid(int tokenPos) + { + int initialIndex = this.textPos; + + string guidStr = ParseLiteral(tokenPos); + + Guid tmpGuidValue; + if (UriUtils.TryUriStringToGuid(guidStr, out tmpGuidValue)) + { + return true; + } + else + { + this.textPos = initialIndex; + this.ch = this.Text[initialIndex]; + return false; + } + } + + /// + /// Tries to parse Guid from current text + /// If it's not Guid, then this.textPos and this.ch are reset + /// + /// Start index + /// True if the substring that starts from tokenPos is a Guid, false otherwise + private bool TryParseDateTimeoffset(int tokenPos) + { + int initialIndex = this.textPos; + + string datetimeOffsetStr = ParseLiteral(tokenPos); + + DateTimeOffset tmpdatetimeOffsetValue; + if (UriUtils.ConvertUriStringToDateTimeOffset(datetimeOffsetStr, out tmpdatetimeOffsetValue)) + { + return true; + } + else + { + this.textPos = initialIndex; + this.ch = this.Text[initialIndex]; + return false; + } + } + + private bool TryParseDate(int tokenPos) + { + int initialIndex = this.textPos; + string dateStr = ParseLiteral(tokenPos); + Date tmpDateValue; + if (UriUtils.TryUriStringToDate(dateStr, out tmpDateValue)) + { + return true; + } + else + { + this.textPos = initialIndex; + this.ch = this.Text[initialIndex]; + return false; + } + } + + /// + /// Tries to parse TimeOfDay from current text + /// If it's not TimeOfDay, then this.textPos and this.ch are reset + /// + /// Start index + /// True if the substring that starts from tokenPos is a TimeOfDay, false otherwise + private bool TryParseTimeOfDay(int tokenPos) + { + int initialIndex = this.textPos; + + string timeOfDayStr = ParseLiteral(tokenPos); + + TimeOfDay tmpTimeOfDayValue; + if (UriUtils.TryUriStringToTimeOfDay(timeOfDayStr, out tmpTimeOfDayValue)) + { + return true; + } + else + { + this.textPos = initialIndex; + this.ch = this.Text[initialIndex]; + return false; + } + } + + /// + /// Parses a literal be checking for delimiting characters '\0', ',',')' and ' ' + /// + /// Index from which the substring starts + /// Substring from this.text that has parsed the literal and ends in one of above delimiting characters + private string ParseLiteral(int tokenPos) + { + do + { + this.NextChar(); + } + while (this.ch.HasValue && this.ch != ',' && this.ch != ')' && this.ch != ' '); + + if (this.ch == null) + { + this.NextChar(); + } + + string numericStr = this.Text.Substring(tokenPos, this.textPos - tokenPos); + return numericStr; + } + + /// + /// Makes best guess on numeric string without trailing letter like L, F, M, D + /// + /// The numeric string. + /// The possbile kind (IntegerLiteral or DoubleLiteral) from ParseFromDigit() method. + /// A more accurate ExpressionTokenKind + private static ExpressionTokenKind MakeBestGuessOnNoSuffixStr(string numericStr, ExpressionTokenKind guessedKind) + { + // no suffix, so + // (1) make a best guess (note: later we support promoting each to later one: int32->int64->single->double->decimal). + // look at value: "2147483647" may be Int32/long, "2147483649" must be long. + // look at precision: "3258.67876576549" may be sinle/double/decimal, "3258.678765765489753678965390" must be decimal. + // (2) then let MetadataUtilsCommon.CanConvertPrimitiveTypeTo() method does further promotion when knowing expected sematics type. + int tmpInt = 0; + long tmpLong = 0; + float tmpFloat = 0; + double tmpDouble = 0; + decimal tmpDecimal = 0; + + if (guessedKind == ExpressionTokenKind.IntegerLiteral) + { + if (int.TryParse(numericStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpInt)) + { + return ExpressionTokenKind.IntegerLiteral; + } + + if (long.TryParse(numericStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpLong)) + { + return ExpressionTokenKind.Int64Literal; + } + } + + bool canBeSingle = float.TryParse(numericStr, NumberStyles.Float, CultureInfo.InvariantCulture, out tmpFloat); + bool canBeDouble = double.TryParse(numericStr, NumberStyles.Float, CultureInfo.InvariantCulture, out tmpDouble); + bool canBeDecimal = decimal.TryParse(numericStr, NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out tmpDecimal); + + // 1. try high precision -> low precision + if (canBeDouble && canBeDecimal) + { + decimal doubleToDecimalR; + decimal doubleToDecimalN; + + // To keep the full precision of the current value, which if necessary is all 17 digits of precision supported by the Double type. + bool doubleCanBeDecimalR = decimal.TryParse(tmpDouble.ToString("R", CultureInfo.InvariantCulture), NumberStyles.Float, CultureInfo.InvariantCulture, out doubleToDecimalR); + + // To cover the scientific notation case, such as 1e+19 in the tmpDouble + bool doubleCanBeDecimalN = decimal.TryParse(tmpDouble.ToString("N29", CultureInfo.InvariantCulture), NumberStyles.Number, CultureInfo.InvariantCulture, out doubleToDecimalN); + + if ((doubleCanBeDecimalR && doubleToDecimalR != tmpDecimal) || (!doubleCanBeDecimalR && doubleCanBeDecimalN && doubleToDecimalN != tmpDecimal)) + { + // losing precision as double, so choose decimal + return ExpressionTokenKind.DecimalLiteral; + } + } + + // here can't use normal casting like the above double VS decimal. + // prevent losing precision in float -> double, e.g. (double)1.234f will be 1.2339999675750732d not 1.234d + if (canBeSingle && canBeDouble && (double.Parse(tmpFloat.ToString("R", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture) != tmpDouble)) + { + // losing precision as single, so choose double + return ExpressionTokenKind.DoubleLiteral; + } + + // 2. try most compatible -> least compatible + if (canBeSingle) + { + return ExpressionTokenKind.SingleLiteral; + } + + if (canBeDouble) + { + return ExpressionTokenKind.DoubleLiteral; + } + + throw new ODataException(ODataErrorStrings.ExpressionLexer_InvalidNumericString(numericStr)); + } + + /// + /// Parses an expression of text that we do not know how to handle in this class, which is between a + /// and an . + /// + /// the starting delimiter + /// the ending delimiter. + private void AdvanceThroughBalancedExpression(char startingCharacter, char endingCharacter) + { + int currentBracketDepth = 1; + + while (currentBracketDepth > 0) + { + if (this.ch == '\'') + { + this.AdvanceToNextOccuranceOf('\''); + } + + if (this.ch == startingCharacter) + { + currentBracketDepth++; + } + else if (this.ch == endingCharacter) + { + currentBracketDepth--; + } + + if (this.ch == null) + { + throw new ODataException(ODataErrorStrings.ExpressionLexer_UnbalancedBracketExpression); + } + + this.NextChar(); + } + } + + + + /// Parses an identifier by advancing the current character. + /// Optional flag for whether to include dots as part of the identifier. + private void ParseIdentifier(bool includingDots = false) + { + Debug.Assert(this.IsValidStartingCharForIdentifier || this.ch == UriQueryConstants.AnnotationPrefix, "Expected valid starting char for identifier"); + do + { + this.NextChar(); + } + while (this.IsValidNonStartingCharForIdentifier || (includingDots && this.ch == '.')); + } + + /// Sets the text position. + /// New text position. + private void SetTextPos(int pos) + { + this.textPos = pos; + this.ch = this.textPos < this.TextLen ? this.Text[this.textPos] : (char?)null; + } + + /// Validates the current character is a digit. + private void ValidateDigit() + { + if (!this.IsValidDigit) + { + throw ParseError(ODataErrorStrings.ExpressionLexer_DigitExpected(this.textPos, this.Text)); + } + } + + #endregion Private methods + + #region Private classes + /// + /// Provides fields to remember an ExpresionLexer's position. + /// + internal class ExpressionLexerPosition + { + public ExpressionLexerPosition(ExpressionLexer lexer, int? textPos, ExpressionToken? token) + { + this.Lexer = lexer; + this.TextPos = textPos; + this.Token = token; + } + + public ExpressionLexer Lexer { get; private set; } + + public int? TextPos { get; private set; } + + public ExpressionToken? Token { get; private set; } + } + + /// This class implements IEqualityComparer for UnicodeCategory + /// + /// Using this class rather than EqualityComparer<T>.Default + /// saves from JIT'ing it in each AppDomain. + /// + private sealed class UnicodeCategoryEqualityComparer : IEqualityComparer + { + /// + /// Checks whether two unicode categories are equal + /// + /// first unicode category + /// second unicode category + /// true if they are equal, false otherwise + public bool Equals(UnicodeCategory x, UnicodeCategory y) + { + return x == y; + } + + /// + /// Gets a hash code for the specified unicode category + /// + /// the input value + /// The hash code for the given input unicode category, the underlying int + public int GetHashCode(UnicodeCategory obj) + { + return (int)obj; + } + } + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerLiteralExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerLiteralExtensions.cs new file mode 100644 index 0000000..3ccf89f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerLiteralExtensions.cs @@ -0,0 +1,164 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Diagnostics; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// A set of extensions to for parsing literals. + /// + internal static class ExpressionLexerLiteralExtensions + { + /// + /// Returns whether the is a primitive literal type: + /// Binary, Boolean, DateTime, Decimal, Double, Guid, In64, Integer, Null, Single, or String. + /// Internal for test use only + /// + /// InternalKind of token. + /// Whether the is a literal type. + internal static Boolean IsLiteralType(this ExpressionTokenKind tokenKind) + { + switch (tokenKind) + { + case ExpressionTokenKind.BinaryLiteral: + case ExpressionTokenKind.BooleanLiteral: + case ExpressionTokenKind.DateTimeLiteral: + case ExpressionTokenKind.DecimalLiteral: + case ExpressionTokenKind.DoubleLiteral: + case ExpressionTokenKind.GuidLiteral: + case ExpressionTokenKind.Int64Literal: + case ExpressionTokenKind.IntegerLiteral: + case ExpressionTokenKind.NullLiteral: + case ExpressionTokenKind.SingleLiteral: + case ExpressionTokenKind.StringLiteral: + case ExpressionTokenKind.TimeOfDayLiteral: + case ExpressionTokenKind.DateLiteral: + case ExpressionTokenKind.DateTimeOffsetLiteral: + case ExpressionTokenKind.DurationLiteral: + case ExpressionTokenKind.GeographyLiteral: + case ExpressionTokenKind.GeometryLiteral: + return true; + default: + return false; + } + } + + /// Reads the next token, checks that it is a literal token type, converts to to a Common Language Runtime value as appropriate, and returns the value. + /// The expression lexer. + /// The value represented by the next token. + internal static object ReadLiteralToken(this ExpressionLexer expressionLexer) + { + expressionLexer.NextToken(); + + if (expressionLexer.CurrentToken.Kind.IsLiteralType()) + { + return TryParseLiteral(expressionLexer); + } + + throw new ODataException(ODataErrorStrings.ExpressionLexer_ExpectedLiteralToken(expressionLexer.CurrentToken.Text)); + } + + /// + /// Parses null literals. + /// + /// The expression lexer. + /// The literal token produced by building the given literal. + private static object ParseNullLiteral(this ExpressionLexer expressionLexer) + { + Debug.Assert(expressionLexer.CurrentToken.Kind == ExpressionTokenKind.NullLiteral, "this.lexer.CurrentToken.InternalKind == ExpressionTokenKind.NullLiteral"); + + expressionLexer.NextToken(); + ODataNullValue nullValue = new ODataNullValue(); + return nullValue; + } + + /// + /// Parses typed literals. + /// + /// The expression lexer. + /// Expected type to be parsed. + /// The literal token produced by building the given literal. + private static object ParseTypedLiteral(this ExpressionLexer expressionLexer, IEdmTypeReference targetTypeReference) + { + UriLiteralParsingException typeParsingException; + object targetValue = DefaultUriLiteralParser.Instance.ParseUriStringToType(expressionLexer.CurrentToken.Text, targetTypeReference, out typeParsingException); + if (targetValue == null) + { + string message; + + if (typeParsingException == null) + { + message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteral( + targetTypeReference.FullName(), + expressionLexer.CurrentToken.Text, + expressionLexer.CurrentToken.Position, + expressionLexer.ExpressionText); + + throw new ODataException(message); + } + else + { + message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteralWithReason( + targetTypeReference.FullName(), + expressionLexer.CurrentToken.Text, + expressionLexer.CurrentToken.Position, + expressionLexer.ExpressionText, + typeParsingException.Message); + + throw new ODataException(message, typeParsingException); + } + } + + expressionLexer.NextToken(); + return targetValue; + } + + /// + /// Parses a literal. + /// Precondition: lexer is at a literal token type: Boolean, DateTime, Decimal, Null, String, Int64, Integer, Double, Single, Guid, Binary. + /// + /// The expression lexer. + /// The literal query token or null if something else was found. + private static object TryParseLiteral(this ExpressionLexer expressionLexer) + { + Debug.Assert(expressionLexer.CurrentToken.Kind.IsLiteralType(), "TryParseLiteral called when not at a literal type token"); + + switch (expressionLexer.CurrentToken.Kind) + { + case ExpressionTokenKind.NullLiteral: + return ParseNullLiteral(expressionLexer); + case ExpressionTokenKind.BooleanLiteral: + case ExpressionTokenKind.DecimalLiteral: + case ExpressionTokenKind.StringLiteral: + case ExpressionTokenKind.Int64Literal: + case ExpressionTokenKind.IntegerLiteral: + case ExpressionTokenKind.DoubleLiteral: + case ExpressionTokenKind.SingleLiteral: + case ExpressionTokenKind.GuidLiteral: + case ExpressionTokenKind.BinaryLiteral: + case ExpressionTokenKind.DateLiteral: + case ExpressionTokenKind.DateTimeOffsetLiteral: + case ExpressionTokenKind.DurationLiteral: + case ExpressionTokenKind.GeographyLiteral: + case ExpressionTokenKind.GeometryLiteral: + case ExpressionTokenKind.QuotedLiteral: + case ExpressionTokenKind.TimeOfDayLiteral: + case ExpressionTokenKind.CustomTypeLiteral: + return ParseTypedLiteral(expressionLexer, expressionLexer.CurrentToken.GetLiteralEdmTypeReference()); + default: + return null; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerUtils.cs new file mode 100644 index 0000000..2cefce2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionLexerUtils.cs @@ -0,0 +1,126 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CORE +namespace Microsoft.OData.UriParser +#else +namespace Microsoft.OData.Service.Parsing +#endif +{ + #region Namespaces + using System; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Utilities needed by which are relatively simple and standalone. + /// + internal sealed class ExpressionLexerUtils + { + /// Suffix for single literals. + private const char SingleSuffixLower = 'f'; + + /// Suffix for single literals. + private const char SingleSuffixUpper = 'F'; + + /// Whether the specified token identifier is a numeric literal. + /// Token to check. + /// true if it's a numeric literal; false otherwise. + internal static bool IsNumeric(ExpressionTokenKind id) + { + return + id == ExpressionTokenKind.IntegerLiteral || id == ExpressionTokenKind.DecimalLiteral || + id == ExpressionTokenKind.DoubleLiteral || id == ExpressionTokenKind.Int64Literal || + id == ExpressionTokenKind.SingleLiteral; + } + + /// + /// Checks if the is INF or NaN. + /// Internal for testing only. + /// + /// Input token. + /// true if match found, false otherwise. + internal static bool IsInfinityOrNaNDouble(string tokenText) + { + Debug.Assert(tokenText != null, "tokenText != null"); + + // COMPAT 30 - INFd/INFD and NaNd/NaND are rejected as Edm.Double literals + // For now we behave exactly the same as WCF DS, we should consider "fixing" this bug in ODataLib, but it is technically a breaking change! + if (tokenText.Length == 3) + { + if (tokenText[0] == ExpressionConstants.InfinityLiteral[0]) + { + return IsInfinityLiteralDouble(tokenText); + } + else + { + if (tokenText[0] == ExpressionConstants.NaNLiteral[0]) + { + return String.CompareOrdinal(tokenText, 0, ExpressionConstants.NaNLiteral, 0, 3) == 0; + } + } + } + + return false; + } + + /// + /// Checks whether equals to 'INF' + /// Internal for testing only + /// + /// Text to look in. + /// true if the substring is equal using an ordinal comparison; false otherwise. + internal static bool IsInfinityLiteralDouble(string text) + { + Debug.Assert(text != null, "text != null"); + + // COMPAT 30 - INFd/INFD and NaNd/NaND are rejected as Edm.Double literals + // For now we behave exactly the same as WCF DS, we should consider "fixing" this bug in ODataLib, but it is technically a breaking change! + return String.CompareOrdinal(text, 0, ExpressionConstants.InfinityLiteral, 0, text.Length) == 0; + } + + /// + /// Checks if the is INFf/INFF or NaNf/NaNF. + /// Internal for testing only. + /// + /// Input token. + /// true if match found, false otherwise. + internal static bool IsInfinityOrNanSingle(string tokenText) + { + Debug.Assert(tokenText != null, "tokenText != null"); + + if (tokenText.Length == 4) + { + if (tokenText[0] == ExpressionConstants.InfinityLiteral[0]) + { + return IsInfinityLiteralSingle(tokenText); + } + else if (tokenText[0] == ExpressionConstants.NaNLiteral[0]) + { + return (tokenText[3] == SingleSuffixLower || tokenText[3] == SingleSuffixUpper) && + String.CompareOrdinal(tokenText, 0, ExpressionConstants.NaNLiteral, 0, 3) == 0; + } + } + + return false; + } + + /// + /// Checks whether EQUALS to 'INFf' or 'INFF'. + /// Internal for testing only. + /// + /// Text to look in. + /// true if the substring is equal using an ordinal comparison; false otherwise. + internal static bool IsInfinityLiteralSingle(string text) + { + Debug.Assert(text != null, "text != null"); + return text.Length == 4 && + (text[3] == SingleSuffixLower || text[3] == SingleSuffixUpper) && + String.CompareOrdinal(text, 0, ExpressionConstants.InfinityLiteral, 0, 3) == 0; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionToken.cs new file mode 100644 index 0000000..7708237 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ExpressionToken.cs @@ -0,0 +1,125 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Diagnostics; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// Use this class to represent a lexical expression token. + [DebuggerDisplay("{InternalKind} @ {Position}: [{Text}]")] + internal struct ExpressionToken + { + /// Token representing gt keyword + internal static readonly ExpressionToken GreaterThan = new ExpressionToken { Text = ExpressionConstants.KeywordGreaterThan, Kind = ExpressionTokenKind.Identifier, Position = 0 }; + + /// Token representing eq keyword + internal static readonly ExpressionToken EqualsTo = new ExpressionToken { Text = ExpressionConstants.KeywordEqual, Kind = ExpressionTokenKind.Identifier, Position = 0 }; + + /// Token representing lt keyword + internal static readonly ExpressionToken LessThan = new ExpressionToken { Text = ExpressionConstants.KeywordLessThan, Kind = ExpressionTokenKind.Identifier, Position = 0 }; + + /// InternalKind of token. + internal ExpressionTokenKind Kind; + + /// Token text. + internal string Text; + + /// Position of token. + internal int Position; + + /// The edm type of the expression token literal. + private IEdmTypeReference LiteralEdmType; + + /// Checks whether this token is a valid token for a key value. + internal bool IsKeyValueToken + { + get + { + return + this.Kind == ExpressionTokenKind.BinaryLiteral || + this.Kind == ExpressionTokenKind.BooleanLiteral || + this.Kind == ExpressionTokenKind.DateLiteral || + this.Kind == ExpressionTokenKind.DateTimeLiteral || + this.Kind == ExpressionTokenKind.DateTimeOffsetLiteral || + this.Kind == ExpressionTokenKind.DurationLiteral || + this.Kind == ExpressionTokenKind.GuidLiteral || + this.Kind == ExpressionTokenKind.StringLiteral || + this.Kind == ExpressionTokenKind.GeographyLiteral || + this.Kind == ExpressionTokenKind.GeometryLiteral || + this.Kind == ExpressionTokenKind.QuotedLiteral || + this.Kind == ExpressionTokenKind.TimeOfDayLiteral || + ExpressionLexerUtils.IsNumeric(this.Kind); + } + } + + /// Checks whether this token is a valid token for a function parameter. + internal bool IsFunctionParameterToken + { + get + { + return this.IsKeyValueToken + || this.Kind == ExpressionTokenKind.BracketedExpression + || this.Kind == ExpressionTokenKind.BracedExpression + || this.Kind == ExpressionTokenKind.NullLiteral; + } + } + + /// Provides a string representation of this token. + /// String representation of this token. + public override string ToString() + { + return String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} @ {1}: [{2}]", this.Kind, this.Position, this.Text); + } + + /// Gets the current identifier text. + /// The current identifier text. + internal string GetIdentifier() + { + if (this.Kind != ExpressionTokenKind.Identifier) + { + string message = Strings.ExpressionToken_IdentifierExpected(this.Position); + throw new ODataException(message); + } + + Debug.Assert(this.Text != null, "Text is null"); + return this.Text; + } + + /// Checks that this token has the specified identifier. + /// Identifier to check. + /// whether to allow case insensitive. + /// true if this is an identifier with the specified text. + internal bool IdentifierIs(string id, bool enableCaseInsensitive) + { + return this.Kind == ExpressionTokenKind.Identifier + && string.Equals(this.Text, id, enableCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + } + + internal void SetCustomEdmTypeLiteral(IEdmTypeReference edmType) + { + this.Kind = ExpressionTokenKind.CustomTypeLiteral; + this.LiteralEdmType = edmType; + } + + internal IEdmTypeReference GetLiteralEdmTypeReference() + { + Debug.Assert(this.Kind != ExpressionTokenKind.CustomTypeLiteral || this.LiteralEdmType != null, "ExpressionTokenKind is marked as CustomTypeLiteral but not EdmType was set"); + + if (this.LiteralEdmType == null && this.Kind != ExpressionTokenKind.CustomTypeLiteral) + { + this.LiteralEdmType = UriParserHelper.GetLiteralEdmTypeReference(this.Kind); + } + + return this.LiteralEdmType; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignature.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignature.cs new file mode 100644 index 0000000..8b1c869 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignature.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Class representing a function signature using EDM types. + /// + internal sealed class FunctionSignature + { + /// + /// The argument types for this function signature. + /// + private readonly IEdmTypeReference[] argumentTypes; + + /// + /// Factories for creating argument types with proper facets. + /// + private CreateArgumentTypeWithFacets[] createArgumentTypesWithFacets; + + /// + /// Constructor taking all the argument types, and the factories for creating argument types with proper facets. + /// + /// The argument types for this function signature. + /// Factories for creating argument types with proper facets. + internal FunctionSignature( + IEdmTypeReference[] argumentTypes, + CreateArgumentTypeWithFacets[] createArgumentTypesWithFacets) + { + this.argumentTypes = argumentTypes; + this.createArgumentTypesWithFacets = createArgumentTypesWithFacets; + } + + /// + /// Delegate for creating an argument type with specified facets. + /// + /// The precision facet. + /// The scale facet. + /// An argument type with specified facets. + internal delegate IEdmTypeReference CreateArgumentTypeWithFacets(int? precision, int? scale); + + /// + /// The argument types for this function signature. + /// + internal IEdmTypeReference[] ArgumentTypes + { + get + { + return this.argumentTypes; + } + } + + /// + /// Gets the type with specified facets for the index-th argument. + /// + /// Index of the argument for which to get the type for. + /// The precision facet. + /// The scale facet. + /// The type with specified facets for the index-th argument. + internal IEdmTypeReference GetArgumentTypeWithFacets(int index, int? precision, int? scale) + { + if (createArgumentTypesWithFacets == null) + { + return argumentTypes[index]; + } + + var create = createArgumentTypesWithFacets[index]; + return create != null ? create(precision, scale) : argumentTypes[index]; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignatureWithReturnType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignatureWithReturnType.cs new file mode 100644 index 0000000..a6d29c2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/FunctionSignatureWithReturnType.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Class representing a function signature using EDM types. + /// + public sealed class FunctionSignatureWithReturnType + { + /// The argument types for this function signature. + private readonly IEdmTypeReference[] argumentTypes; + + /// + /// The return type of this function signature. + /// + private readonly IEdmTypeReference returnType; + + /// + /// Constructor taking all the argument types. + /// + /// The return type of this function signature. + /// The argument types for this function signature. + public FunctionSignatureWithReturnType(IEdmTypeReference returnType, params IEdmTypeReference[] argumentTypes) + { + this.argumentTypes = argumentTypes; + this.returnType = returnType; + } + + /// + /// The return type of this function signature. + /// + public IEdmTypeReference ReturnType + { + get + { + return this.returnType; + } + } + + /// + /// The argument types for this function signature. + /// + public IEdmTypeReference[] ArgumentTypes + { + get + { + return this.argumentTypes; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/InternalErrorCodes.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/InternalErrorCodes.cs new file mode 100644 index 0000000..4b342e0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/InternalErrorCodes.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// An enumeration that lists the internal errors. + /// + internal enum InternalErrorCodes + { + /// Unreachable codepath in TypePromotionUtils.GetFunctionSignatures(BinaryOperatorKind), unrecognized kind of binary operator. + TypePromotionUtils_GetFunctionSignatures_Binary_UnreachableCodepath, + + /// Unreachable codepath in TypePromotionUtils.GetFunctionSignatures(UnaryOperatorKind), unrecognized kind of unary operator. + TypePromotionUtils_GetFunctionSignatures_Unary_UnreachableCodepath, + + /// Unreachable codepath in UriQueryExpressionParser.ParseComparison + /// Was a new binary operator keyword without adding it to the switch in the ParseComparison? + UriQueryExpressionParser_ParseComparison, + + /// Unreachable codepath in UriPrimitiveTypeParser.TryUriStringToPrimitive + /// Unsupported type was asked to be parsed. + UriPrimitiveTypeParser_TryUriStringToPrimitive, + + /// Unreachable codepath in QueryNodeUtils.BinaryOperatorResultType, unrecognized kind of binary operator. + QueryNodeUtils_BinaryOperatorResultType_UnreachableCodepath, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/KeyPropertyValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/KeyPropertyValue.cs new file mode 100644 index 0000000..bd10233 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/KeyPropertyValue.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Class representing a single key property value in a key lookup. + /// + internal sealed class KeyPropertyValue + { + /// + /// Gets or sets the key property. + /// + public IEdmProperty KeyProperty + { + get; + set; + } + + /// + /// Gets or sets the value of the key property. + /// + public SingleValueNode KeyValue + { + get; + set; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/LiteralUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/LiteralUtils.cs new file mode 100644 index 0000000..299ddb8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/LiteralUtils.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System.IO; + using Microsoft.Spatial; + #endregion Namespaces + + /// + /// Helper methods for with literals. + /// + internal static class LiteralUtils + { + /// + /// The formatter to create/format text to and from spatial. + /// + private static WellKnownTextSqlFormatter Formatter + { + get + { + return SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(false /*allowOnlyTwoDimensions*/); + } + } + + /// + /// Parse the given text as a Geography literal. + /// + /// The text to parse. + /// The Geography object if succeeded, else a ParseErrorException is thrown. + internal static Geography ParseGeography(string text) + { + using (StringReader reader = new StringReader(text)) + { + return Formatter.Read(reader); + } + } + + /// + /// Parse the given text as a Geometry literal. + /// + /// The text to parse. + /// The Geometry object if succeeded, else a ParseErrorException is thrown. + internal static Geometry ParseGeometry(string text) + { + using (StringReader reader = new StringReader(text)) + { + return Formatter.Read(reader); + } + } + + /// + /// Convert to string the given Geography instance. + /// + /// Instance to convert. + /// Well-known text representation. + internal static string ToWellKnownText(Geography instance) + { + return Formatter.Write(instance); + } + + /// + /// Convert to string the given Geometry instance. + /// + /// Instance to convert. + /// Well-known text representation. + internal static string ToWellKnownText(Geometry instance) + { + return Formatter.Write(instance); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/NamedValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/NamedValue.cs new file mode 100644 index 0000000..e592c43 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/NamedValue.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Class representing a single named value (name and value pair). + /// + public sealed class NamedValue + { + /// + /// The name of the value. Or null if the name was not used for this value. + /// + private readonly string name; + + /// + /// The value - a literal. + /// + private readonly LiteralToken value; + + /// + /// Create a new NamedValue lookup given name and value. + /// + /// The name of the value. Or null if the name was not used for this value. + /// The value - a literal. + public NamedValue(string name, LiteralToken value) + { + ExceptionUtils.CheckArgumentNotNull(value, "value"); + + this.name = name; + this.value = value; + } + + /// + /// The name of the value. Or null if the name was not used for this value. + /// + public string Name + { + get { return this.name; } + } + + /// + /// The value - a literal. + /// + public LiteralToken Value + { + get { return this.value; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs new file mode 100644 index 0000000..47f3e1a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + internal class ODataPathInfo + { + private readonly IEdmType targetEdmType; + private readonly IEdmNavigationSource targetNavigationSource; + private readonly IEnumerable segments; + + public ODataPathInfo(ODataPath odataPath) + { + ODataPathSegment lastSegment = odataPath.LastSegment; + ODataPathSegment previous = null; + var segs = odataPath.GetEnumerator(); + int count = 0; + while (++count < odataPath.Count && segs.MoveNext()) + { + } + + previous = segs.Current; + if (lastSegment != null) + { + // use previous segment if the last one is Key or Count Segment + if (lastSegment is KeySegment || lastSegment is CountSegment) + { + lastSegment = previous; + } + + this.targetNavigationSource = lastSegment.TargetEdmNavigationSource; + this.targetEdmType = lastSegment.TargetEdmType; + if (this.targetEdmType != null) + { + IEdmCollectionType collectionType = this.targetEdmType as IEdmCollectionType; + if (collectionType != null) + { + this.targetEdmType = collectionType.ElementType.Definition; + } + } + } + + this.segments = odataPath; + } + + public ODataPathInfo(IEdmType targetEdmType, IEdmNavigationSource targetNavigationSource) + { + this.targetEdmType = targetEdmType; + this.targetNavigationSource = targetNavigationSource; + this.segments = new List(); + } + + + public IEdmType TargetEdmType + { + get { return targetEdmType; } + } + + public IEdmNavigationSource TargetNavigationSource + { + get { return targetNavigationSource; } + } + + public IEnumerable Segments + { + get { return segments; } + } + + public IEdmStructuredType TargetStructuredType + { + get { return (IEdmStructuredType)targetEdmType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs new file mode 100644 index 0000000..3e8d86f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataQueryOptionParser.cs @@ -0,0 +1,704 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Metadata; +using Microsoft.OData.UriParser.Aggregation; + +namespace Microsoft.OData.UriParser +{ + /// + /// Parser for query options + /// + public class ODataQueryOptionParser + { + #region private fields + /// Target Edm type. + private readonly IEdmType targetEdmType; + + /// Dictionary of query options + private readonly IDictionary queryOptions; + + /// Filter clause. + private FilterClause filterClause; + + /// SelectAndExpand clause. + private SelectExpandClause selectExpandClause; + + /// Orderby clause. + private OrderByClause orderByClause; + + /// Search clause. + private SearchClause searchClause; + + /// + /// Apply clause for aggregation queries + /// + private ApplyClause applyClause; + + /// + /// Compute clause for computation queries + /// + private ComputeClause computeClause; + + /// + /// The path info about parsed segments and target edm type and navigation source. + /// + private ODataPathInfo odataPathInfo; + + #endregion private fields + + #region constructor + /// + /// Constructor for ODataQueryOptionParser + /// + /// Model to use for metadata binding. + /// The target EdmType to apply the query option on. + /// The target navigation source to apply the query option on. + /// The dictionary storing query option key-value pairs. + public ODataQueryOptionParser(IEdmModel model, IEdmType targetEdmType, IEdmNavigationSource targetNavigationSource, IDictionary queryOptions) + : this(model, targetEdmType, targetNavigationSource, queryOptions, null) + { + } + + /// + /// Constructor for ODataQueryOptionParser + /// + /// Model to use for metadata binding. + /// The target EdmType to apply the query option on. + /// The target navigation source to apply the query option on. + /// The dictionary storing query option key-value pairs. + /// The optional dependency injection container to get related services for URI parsing. + public ODataQueryOptionParser(IEdmModel model, IEdmType targetEdmType, IEdmNavigationSource targetNavigationSource, IDictionary queryOptions, IServiceProvider container) + { + ExceptionUtils.CheckArgumentNotNull(queryOptions, "queryOptions"); + + this.odataPathInfo = new ODataPathInfo(targetEdmType, targetNavigationSource); + this.targetEdmType = this.odataPathInfo.TargetEdmType; + this.queryOptions = queryOptions; + this.Configuration = new ODataUriParserConfiguration(model, container) + { + ParameterAliasValueAccessor = new ParameterAliasValueAccessor(queryOptions.Where(_ => _.Key.StartsWith("@", StringComparison.Ordinal)).ToDictionary(_ => _.Key, _ => _.Value)) + }; + } + + /// + /// Constructor for ODataQueryOptionParser + /// + /// Model to use for metadata binding. + /// The odata path to apply the query option on. + /// The dictionary storing query option key-value pairs. + public ODataQueryOptionParser(IEdmModel model, ODataPath odataPath, IDictionary queryOptions) + : this(model, odataPath, queryOptions, null) + { + } + + /// + /// Constructor for ODataQueryOptionParser + /// + /// Model to use for metadata binding. + /// The odata path to apply the query option on. + /// The dictionary storing query option key-value pairs. + /// The optional dependency injection container to get related services for URI parsing. + public ODataQueryOptionParser(IEdmModel model, ODataPath odataPath, IDictionary queryOptions, IServiceProvider container) + { + ExceptionUtils.CheckArgumentNotNull(odataPath, "odataPath"); + ExceptionUtils.CheckArgumentNotNull(queryOptions, "queryOptions"); + + this.odataPathInfo = new ODataPathInfo(odataPath); + this.targetEdmType = this.odataPathInfo.TargetEdmType; + + this.queryOptions = queryOptions; + this.Configuration = new ODataUriParserConfiguration(model, container) + { + ParameterAliasValueAccessor = new ParameterAliasValueAccessor(queryOptions.Where(_ => _.Key.StartsWith("@", StringComparison.Ordinal)).ToDictionary(_ => _.Key, _ => _.Value)) + }; + } + + #endregion constructor + + #region properties + /// + /// The settings for this instance of . Refer to the documentation for the individual properties of for more information. + /// + public ODataUriParserSettings Settings + { + get { return this.Configuration.Settings; } + } + + /// + /// Get the parameter alias nodes info. + /// + public IDictionary ParameterAliasNodes + { + get { return this.Configuration.ParameterAliasValueAccessor.ParameterAliasValueNodesCached; } + } + + /// + /// Gets or sets the for . + /// + public ODataUriResolver Resolver + { + get { return this.Configuration.Resolver; } + set { this.Configuration.Resolver = value; } + } + + /// The parser's configuration. + internal ODataUriParserConfiguration Configuration { get; set; } + #endregion properties + + #region public methods + /// + /// Parses a filter clause on the given full Uri, binding + /// the text into semantic nodes using the constructed mode. + /// + /// A representing the metadata bound filter expression. + public FilterClause ParseFilter() + { + if (this.filterClause != null) + { + return this.filterClause; + } + + string filterQuery; + + if (!this.TryGetQueryOption(UriQueryConstants.FilterQueryOption, out filterQuery) + || string.IsNullOrEmpty(filterQuery) + || this.targetEdmType == null) + { + return null; + } + + this.filterClause = ParseFilterImplementation(filterQuery, this.Configuration, this.odataPathInfo); + return this.filterClause; + } + + /// + /// Parses a apply clause on the given full Uri, binding + /// the text into semantic nodes using the constructed mode. + /// + /// A representing the aggregation query. + public ApplyClause ParseApply() + { + if (this.applyClause != null) + { + return this.applyClause; + } + + string applyQuery; + + if (!this.TryGetQueryOption(UriQueryConstants.ApplyQueryOption, out applyQuery) + || string.IsNullOrEmpty(applyQuery) + || this.targetEdmType == null) + { + return null; + } + + this.applyClause = ParseApplyImplementation(applyQuery, this.Configuration, this.odataPathInfo); + return this.applyClause; + } + + /// + /// ParseSelectAndExpand from an instantiated class + /// + /// A SelectExpandClause with the semantic representation of select and expand terms + public SelectExpandClause ParseSelectAndExpand() + { + if (this.selectExpandClause != null) + { + return this.selectExpandClause; + } + + string selectQuery, expandQuery; + + // Intended to use bitwise AND & instead of logic AND && here, prevent short-circuiting. + if ((!this.TryGetQueryOption(UriQueryConstants.SelectQueryOption, out selectQuery) || selectQuery == null) + & (!this.TryGetQueryOption(UriQueryConstants.ExpandQueryOption, out expandQuery) || expandQuery == null) + || this.targetEdmType == null) + { + return null; + } + + IEdmStructuredType structuredType = this.targetEdmType as IEdmStructuredType; + if (structuredType == null) + { + throw new ODataException(Strings.UriParser_TypeInvalidForSelectExpand(this.targetEdmType)); + } + + this.selectExpandClause = ParseSelectAndExpandImplementation(selectQuery, expandQuery, this.Configuration, this.odataPathInfo); + return this.selectExpandClause; + } + + /// + /// Parses an orderBy clause on the given full Uri, binding + /// the text into semantic nodes using the constructed mode. + /// + /// A representing the metadata bound orderby expression. + public OrderByClause ParseOrderBy() + { + if (this.orderByClause != null) + { + return this.orderByClause; + } + + string orderByQuery; + if (!this.TryGetQueryOption(UriQueryConstants.OrderByQueryOption, out orderByQuery) + || string.IsNullOrEmpty(orderByQuery) + || this.targetEdmType == null) + { + return null; + } + + this.orderByClause = ParseOrderByImplementation(orderByQuery, this.Configuration, this.odataPathInfo); + return this.orderByClause; + } + + /// + /// Parses a $top query option + /// + /// A value representing that top option, null if $top query does not exist. + public long? ParseTop() + { + string topQuery; + return this.TryGetQueryOption(UriQueryConstants.TopQueryOption, out topQuery) ? ParseTop(topQuery) : null; + } + + /// + /// Parses a $skip query option + /// + /// A value representing that skip option, null if $skip query does not exist. + public long? ParseSkip() + { + string skipQuery; + return this.TryGetQueryOption(UriQueryConstants.SkipQueryOption, out skipQuery) ? ParseSkip(skipQuery) : null; + } + + /// + /// Parses a $index query option + /// + /// A value representing that index option, null if $index query does not exist. + public long? ParseIndex() + { + string indexQuery; + return this.TryGetQueryOption(UriQueryConstants.IndexQueryOption, out indexQuery) ? ParseIndex(indexQuery) : null; + } + + /// + /// Parses a $count query option + /// + /// A count representing that count option, null if $count query does not exist. + public bool? ParseCount() + { + string countQuery; + return this.TryGetQueryOption(UriQueryConstants.CountQueryOption, out countQuery) ? ParseCount(countQuery) : null; + } + + /// + /// Parses the $search. + /// + /// SearchClause representing $search. + public SearchClause ParseSearch() + { + if (this.searchClause != null) + { + return this.searchClause; + } + + string searchQuery; + if (!this.TryGetQueryOption(UriQueryConstants.SearchQueryOption, out searchQuery) + || searchQuery == null) + { + return null; + } + + this.searchClause = ParseSearchImplementation(searchQuery, this.Configuration); + return searchClause; + } + + /// + /// Parses a $skiptoken query option + /// + /// A value representing that skip token option, null if $skiptoken query does not exist. + public string ParseSkipToken() + { + string skipTokenQuery; + return this.TryGetQueryOption(UriQueryConstants.SkipTokenQueryOption, out skipTokenQuery) ? skipTokenQuery : null; + } + + /// + /// Parses a $deltatoken query option + /// + /// A value representing that delta token option, null if $deltatoken query does not exist. + public string ParseDeltaToken() + { + string deltaTokenQuery; + return this.TryGetQueryOption(UriQueryConstants.DeltaTokenQueryOption, out deltaTokenQuery) ? deltaTokenQuery : null; + } + + /// + /// Parses a compute clause on the given full Uri, binding + /// the text into semantic nodes using the constructed mode. + /// + /// A representing the computed properties. + public ComputeClause ParseCompute() + { + if (this.computeClause != null) + { + return this.computeClause; + } + + string computeQuery; + + if (!this.TryGetQueryOption(UriQueryConstants.ComputeQueryOption, out computeQuery) + || string.IsNullOrEmpty(computeQuery) + || this.targetEdmType == null) + { + return null; + } + + this.computeClause = ParseComputeImplementation(computeQuery, this.Configuration, this.odataPathInfo); + return this.computeClause; + } + #endregion public methods + + #region private methods + /// + /// Parses a clause, binding + /// the text into semantic nodes using the provided model. + /// + /// String representation of the filter expression. + /// The configuration used for binding. + /// The path info from Uri path. + /// A representing the metadata bound filter expression. + private FilterClause ParseFilterImplementation(string filter, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + ExceptionUtils.CheckArgumentNotNull(odataPathInfo, "odataPathInfo"); + ExceptionUtils.CheckArgumentNotNull(filter, "filter"); + + // Get the syntactic representation of the filter expression + UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier); + QueryToken filterToken = expressionParser.ParseFilter(filter); + + // Bind it to metadata + BindingState state = CreateBindingState(configuration, odataPathInfo); + + MetadataBinder binder = new MetadataBinder(state); + FilterBinder filterBinder = new FilterBinder(binder.Bind, state); + FilterClause boundNode = filterBinder.BindFilter(filterToken); + + return boundNode; + } + + /// + /// Parses an clause, binding + /// the text into a metadata-bound or dynamic properties to be applied using the provided model. + /// + /// String representation of the apply expression. + /// The configuration used for binding. + /// The path info from Uri path. + /// A representing the metadata bound apply expression. + private static ApplyClause ParseApplyImplementation(string apply, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + ExceptionUtils.CheckArgumentNotNull(apply, "apply"); + + // Get the syntactic representation of the apply expression + UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier); + var applyTokens = expressionParser.ParseApply(apply); + + // Bind it to metadata + BindingState state = new BindingState(configuration, odataPathInfo.Segments.ToList()); + state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(odataPathInfo.TargetEdmType.ToTypeReference(), odataPathInfo.TargetNavigationSource); + state.RangeVariables.Push(state.ImplicitRangeVariable); + MetadataBinder binder = new MetadataBinder(state); + ApplyBinder applyBinder = new ApplyBinder(binder.Bind, state, configuration, odataPathInfo); + ApplyClause boundNode = applyBinder.BindApply(applyTokens); + + return boundNode; + } + + /// + /// Parses the and clauses, binding + /// the text into a metadata-bound list of properties to be selected using the provided model. + /// + /// String representation of the select expression from the URI. + /// String representation of the expand expression from the URI. + /// The configuration used for binding. + /// The path info from Uri path. + /// A representing the metadata bound select and expand expression. + private SelectExpandClause ParseSelectAndExpandImplementation(string select, string expand, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + ExceptionUtils.CheckArgumentNotNull(configuration.Model, "model"); + + ExpandToken expandTree; + SelectToken selectTree; + + // syntactic pass , pass in the expand parent entity type name, in case expand option contains star, will get all the parent entity navigation properties (both declared and dynamical). + SelectExpandSyntacticParser.Parse(select, expand, odataPathInfo.TargetStructuredType, configuration, out expandTree, out selectTree); + + // semantic pass + BindingState state = CreateBindingState(configuration, odataPathInfo); + return SelectExpandSemanticBinder.Bind(odataPathInfo, expandTree, selectTree, configuration, state); + } + + /// + /// Parses an clause, binding + /// the text into semantic nodes using the provided model. + /// + /// String representation of the orderby expression. + /// The configuration used for binding. + /// The path info from Uri path. + /// An representing the metadata bound orderby expression. + private OrderByClause ParseOrderByImplementation(string orderBy, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + ExceptionUtils.CheckArgumentNotNull(configuration.Model, "model"); + ExceptionUtils.CheckArgumentNotNull(orderBy, "orderBy"); + + // Get the syntactic representation of the orderby expression + UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.OrderByLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier); + var orderByQueryTokens = expressionParser.ParseOrderBy(orderBy); + + // Bind it to metadata + BindingState state = CreateBindingState(configuration, odataPathInfo); + + MetadataBinder binder = new MetadataBinder(state); + OrderByBinder orderByBinder = new OrderByBinder(binder.Bind); + OrderByClause orderByClause = orderByBinder.BindOrderBy(state, orderByQueryTokens); + + return orderByClause; + } + + private BindingState CreateBindingState(ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) + { + BindingState state = new BindingState(configuration, odataPathInfo.Segments.ToList()); + state.ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable(odataPathInfo.TargetEdmType.ToTypeReference(), odataPathInfo.TargetNavigationSource); + state.RangeVariables.Push(state.ImplicitRangeVariable); + if (applyClause != null) + { + state.AggregatedPropertyNames = applyClause.GetLastAggregatedPropertyNames(); + if (applyClause.Transformations.Any(x => x.Kind == TransformationNodeKind.GroupBy || x.Kind == TransformationNodeKind.Aggregate)) + { + state.IsCollapsed = true; + } + } + + if (computeClause != null) + { + var computedProperties = new HashSet(computeClause.ComputedItems.Select(i => new EndPathToken(i.Alias, null))); + if (state.AggregatedPropertyNames == null) + { + state.AggregatedPropertyNames = computedProperties; + } + else + { + state.AggregatedPropertyNames.UnionWith(computedProperties); + } + } + + return state; + } + + /// + /// Parses a $top query option + /// + /// The topQuery from the query + /// A value representing that top option, null if $top query does not exist. + /// Throws if the input count is not a valid $top value. + private static long? ParseTop(string topQuery) + { + if (topQuery == null) + { + return null; + } + + long topValue; + if (!long.TryParse(topQuery, out topValue) || topValue < 0) + { + throw new ODataException(Strings.SyntacticTree_InvalidTopQueryOptionValue(topQuery)); + } + + return topValue; + } + + /// + /// Parses a $skip query option + /// + /// The count skipQuery from the query + /// A value representing that skip option, null if $skip query does not exist. + /// Throws if the input count is not a valid $skip value. + private static long? ParseSkip(string skipQuery) + { + if (skipQuery == null) + { + return null; + } + + long skipValue; + if (!long.TryParse(skipQuery, out skipValue) || skipValue < 0) + { + throw new ODataException(Strings.SyntacticTree_InvalidSkipQueryOptionValue(skipQuery)); + } + + return skipValue; + } + + /// + /// Parses a $index query option + /// + /// The value of $index from the query + /// A value representing that index option, null if $index query does not exist. + /// Throws if the input value is not a valid $index value. + private static long? ParseIndex(string indexQuery) + { + if (indexQuery == null) + { + return null; + } + + // A negative ordinal number indexes from the end of the collection, + // with -1 representing an insert as the last item in the collection. + long indexValue; + if (!long.TryParse(indexQuery, out indexValue)) + { + throw new ODataException(Strings.SyntacticTree_InvalidIndexQueryOptionValue(indexQuery)); + } + + return indexValue; + } + + /// + /// Parses a query count option + /// Valid Samples: $count=true; $count=false + /// Invalid Samples: $count=True; $count=ture + /// + /// The count string from the query + /// query count true of false + /// Throws if the input count is not a valid $count value. + private static bool? ParseCount(string count) + { + if (count == null) + { + return null; + } + + switch (count.Trim()) + { + case ExpressionConstants.KeywordTrue: + return true; + case ExpressionConstants.KeywordFalse: + return false; + default: + throw new ODataException(Strings.ODataUriParser_InvalidCount(count)); + } + } + + /// + /// Parses the clause, binding + /// the text into a metadata-bound list of properties to be selected using the provided model. + /// + /// String representation of the search expression from the URI. + /// The configuration used for binding. + /// A representing the metadata bound search expression. + private static SearchClause ParseSearchImplementation(string search, ODataUriParserConfiguration configuration) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + ExceptionUtils.CheckArgumentNotNull(search, "search"); + + SearchParser searchParser = new SearchParser(configuration.Settings.SearchLimit); + QueryToken queryToken = searchParser.ParseSearch(search); + + // Bind it to metadata + BindingState state = new BindingState(configuration); + MetadataBinder binder = new MetadataBinder(state); + SearchBinder searchBinder = new SearchBinder(binder.Bind); + + return searchBinder.BindSearch(queryToken); + } + + /// + /// Parses the clause, binding + /// the text into a metadata-bound list of compuations using the provided model. + /// + /// String representation of the compute expression from the URI. + /// The configuration used for binding. + /// The path info from Uri path. + /// A representing the metadata bound compute expression. + private ComputeClause ParseComputeImplementation(string compute, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo) + { + ExceptionUtils.CheckArgumentNotNull(configuration, "configuration"); + ExceptionUtils.CheckArgumentNotNull(compute, "compute"); + + // Get the syntactic representation of the apply expression + UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier); + ComputeToken computeToken = expressionParser.ParseCompute(compute); + + // Bind it to metadata + BindingState state = CreateBindingState(configuration, odataPathInfo); + MetadataBinder binder = new MetadataBinder(state); + ComputeBinder computeBinder = new ComputeBinder(binder.Bind); + ComputeClause boundNode = computeBinder.BindCompute(computeToken); + + return boundNode; + } + + /// + /// Gets query options according to case sensitivity and whether no dollar query options is enabled. + /// + /// The query option name with $ prefix. + /// The value of the query option. + /// Whether value successfully retrived. + private bool TryGetQueryOption(string name, out string value) + { + value = null; + if (name == null) + { + return false; + } + + // Trim name to prevent caller from passing in untrimmed name for comparison with already trimmed keys in queryOptions dictionary. + string trimmedName = name.Trim(); + + bool isCaseInsensitiveEnabled = this.Resolver.EnableCaseInsensitive; + bool isNoDollarQueryOptionsEnabled = this.Configuration.EnableNoDollarQueryOptions; + + if (!isCaseInsensitiveEnabled && !isNoDollarQueryOptionsEnabled) + { + return this.queryOptions.TryGetValue(trimmedName, out value); + } + + StringComparison stringComparison = isCaseInsensitiveEnabled ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + string nameWithoutDollarPrefix = (isNoDollarQueryOptionsEnabled && trimmedName.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) ? + trimmedName.Substring(1) : null; + + var list = this.queryOptions + .Where(pair => string.Equals(trimmedName, pair.Key, stringComparison) + || (nameWithoutDollarPrefix != null && string.Equals(nameWithoutDollarPrefix, pair.Key, stringComparison))) + .ToList(); + + if (list.Count == 0) + { + return false; + } + else if (list.Count == 1) + { + value = list.First().Value; + return true; + } + + throw new ODataException(Strings.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce( + isNoDollarQueryOptionsEnabled ? string.Format(CultureInfo.InvariantCulture, "${0}/{0}", nameWithoutDollarPrefix ?? trimmedName) : trimmedName)); + } + #endregion private methods + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUnrecognizedPathException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUnrecognizedPathException.cs new file mode 100644 index 0000000..5c26400 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUnrecognizedPathException.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; +#if ORCAS + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + using System.Security.Permissions; +#endif + + /// + /// The exception that is thrown when path parsing detects an unrecognized or unresolvable token in a path (which servers should treat as a 404). + /// +#if ORCAS + [Serializable] +#endif + [DebuggerDisplay("{Message}")] + public sealed class ODataUnrecognizedPathException : ODataException + { + /// + /// Initializes a new instance of the ODataUnrecognizedPathException class. + /// + /// + /// The Message property is initialized to a system-supplied message + /// that describes the error. This message takes into account the + /// current system culture. + /// + public ODataUnrecognizedPathException() + : this((string)Strings.ODataUriParserException_GeneralError, (Exception)null) + { + } + + /// + /// Initializes a new instance of the ODataUnrecognizedPathException class. + /// + /// Plain text error message for this exception. + public ODataUnrecognizedPathException(string message) + : this(message, (Exception)null) + { + } + + /// + /// Initializes a new instance of the DataServiceException class. + /// + /// Plain text error message for this exception. + /// Exception that caused this exception to be thrown. + public ODataUnrecognizedPathException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if ORCAS + /// Creates a new instance of the class from the specified SerializationInfo and StreamingContext instances. + /// A SerializationInfo containing the information required to serialize the new ODataUnrecognizedPathException. + /// A StreamingContext containing the source of the serialized stream associated with the new ODataUnrecognizedPathException. + [SuppressMessage("Microsoft.Design", "CA1047", Justification = "Follows serialization info pattern.")] + [SuppressMessage("Microsoft.Design", "CA1032", Justification = "Follows serialization info pattern.")] + private ODataUnrecognizedPathException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + + /// + /// Segments that have been successfully parsed when this exception was thrown. + /// + public IEnumerable ParsedSegments { get; set; } + + /// + /// Current segment UriParser was dealing with when exception was thrown. + /// + public string CurrentSegment { get; set; } + + /// + /// Unparsed segments. + /// + public IEnumerable UnparsedSegments { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParser.cs new file mode 100644 index 0000000..fb13793 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParser.cs @@ -0,0 +1,610 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.UriParser.Aggregation; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Main Public API to parse an ODataURI. + /// + public sealed class ODataUriParser + { + #region Fields + /// The parser's configuration. + private readonly ODataUriParserConfiguration configuration; + + /// + /// Absolute URI of the service root. + /// + private readonly Uri serviceRoot; + + /// The absolute or relative URI to be parsed. + private readonly Uri uri; + + /// Query option list + private readonly List queryOptions; + + /// Store query option dictionary. + private IDictionary queryOptionDic; + + /// Store non-OData query options, duplicates are allowed. + private IList> customQueryOptions; + + /// Parser for query option. + private ODataQueryOptionParser queryOptionParser; + + /// OData Path. + private ODataPath odataPath; + + /// EntityId Segment. + private EntityIdSegment entityIdSegment; + #endregion + + /// + /// Build an ODataUriParser + /// + /// Model to use for metadata binding. + /// Absolute URI of the service root. + /// Absolute or relative URI to be parsed. + public ODataUriParser(IEdmModel model, Uri serviceRoot, Uri uri) + : this(model, serviceRoot, uri, null) + { + } + + /// + /// Build an ODataUriParser + /// + /// Model to use for metadata binding. + /// Absolute URI of the service root. + /// Absolute or relative URI to be parsed. + /// The optional dependency injection container to get related services for URI parsing. + public ODataUriParser(IEdmModel model, Uri serviceRoot, Uri uri, IServiceProvider container) + { + ExceptionUtils.CheckArgumentNotNull(uri, "uri"); + + if (serviceRoot == null) + { + throw new ODataException(ODataErrorStrings.UriParser_NeedServiceRootForThisOverload); + } + + if (!serviceRoot.IsAbsoluteUri) + { + throw new ODataException(ODataErrorStrings.UriParser_UriMustBeAbsolute(serviceRoot)); + } + + this.configuration = new ODataUriParserConfiguration(model, container); + this.serviceRoot = UriUtils.EnsureTaillingSlash(serviceRoot); + this.uri = uri.IsAbsoluteUri ? uri : UriUtils.UriToAbsoluteUri(this.ServiceRoot, uri); + this.queryOptions = QueryOptionUtils.ParseQueryOptions(this.uri); + } + + /// + /// Build an ODataUriParser + /// + /// Model to use for metadata binding. + /// Relative URI to be parsed. + public ODataUriParser(IEdmModel model, Uri relativeUri) + : this(model, relativeUri, (IServiceProvider)null) + { + } + + /// + /// Build an ODataUriParser + /// + /// Model to use for metadata binding. + /// Relative URI to be parsed. + /// The optional dependency injection container to get related services for URI parsing. + public ODataUriParser(IEdmModel model, Uri relativeUri, IServiceProvider container) + { + ExceptionUtils.CheckArgumentNotNull(relativeUri, "relativeUri"); + + if (relativeUri.IsAbsoluteUri) + { + throw new ODataException(Strings.UriParser_RelativeUriMustBeRelative); + } + + this.configuration = new ODataUriParserConfiguration(model, container); + this.uri = relativeUri; + this.queryOptions = QueryOptionUtils.ParseQueryOptions(UriUtils.CreateMockAbsoluteUri(this.uri)); + } + + /// + /// The settings for this instance of . Refer to the documentation for the individual properties of for more information. + /// + public ODataUriParserSettings Settings + { + get { return this.configuration.Settings; } + } + + /// + /// Gets the model for this ODataUriParser + /// + public IEdmModel Model + { + get { return this.configuration.Model; } + } + + /// + /// The optional dependency injection container to get related services for URI parsing. + /// + public IServiceProvider Container + { + get { return this.configuration.Container; } + } + + /// + /// Gets the absolute URI of the service root. + /// + public Uri ServiceRoot + { + get { return this.serviceRoot; } + } + + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not. + /// + /// Throws if the input value is null. + public ODataUrlKeyDelimiter UrlKeyDelimiter + { + get { return this.configuration.UrlKeyDelimiter; } + set { this.configuration.UrlKeyDelimiter = value; } + } + + /// + /// Gets or Sets a callback that returns a BatchReferenceSegment (to be used for $0 in batch) + /// + public Func BatchReferenceCallback + { + get { return this.configuration.BatchReferenceCallback; } + set { this.configuration.BatchReferenceCallback = value; } + } + + /// + /// Whether no dollar query options is enabled. + /// If it is enabled, the '$' prefix of system query options becomes optional. + /// For example, "select" and "$select" are equivalent in this case. + /// + public bool EnableNoDollarQueryOptions + { + get { return this.configuration.EnableNoDollarQueryOptions; } + set { this.configuration.EnableNoDollarQueryOptions = value; } + } + + /// + /// Whether Uri template parsing is enabled. Uri template for keys and function parameters are supported. + /// See class for detail. + /// + public bool EnableUriTemplateParsing + { + get { return this.configuration.EnableUriTemplateParsing; } + set { this.configuration.EnableUriTemplateParsing = value; } + } + + /// + /// Gets or sets the for . + /// + public ODataUriResolver Resolver + { + get { return this.configuration.Resolver; } + set { this.configuration.Resolver = value; } + } + + /// + /// Gets or sets the function which can be used to parse an unknown path segment or an open property segment. + /// + public ParseDynamicPathSegment ParseDynamicPathSegmentFunc + { + get { return this.configuration.ParseDynamicPathSegmentFunc; } + set { this.configuration.ParseDynamicPathSegmentFunc = value; } + } + + /// + /// Get the parameter alias nodes info. + /// + public IDictionary ParameterAliasNodes + { + get + { + if (this.ParameterAliasValueAccessor == null) + { + this.Initialize(); + } + + return this.ParameterAliasValueAccessor.ParameterAliasValueNodesCached; + } + } + + /// + /// Gets non-OData query options. + /// + public IList> CustomQueryOptions + { + get + { + if (customQueryOptions == null) + { + InitQueryOptionDic(); + } + + return customQueryOptions; + } + } + + /// + /// Gets or sets the parameter aliases info for MetadataBinder to resolve parameter alias' metadata type. + /// + internal ParameterAliasValueAccessor ParameterAliasValueAccessor + { + get { return this.configuration.ParameterAliasValueAccessor; } + set { this.configuration.ParameterAliasValueAccessor = value; } + } + + /// + /// Parses the odata path on the given full Uri + /// + /// An representing OData path. + public ODataPath ParsePath() + { + this.Initialize(); + return this.odataPath; + } + + /// + /// Parses a filter clause on the given full Uri, binding + /// the text into semantic nodes using the constructed mode. + /// + /// A representing the metadata bound filter expression. + public FilterClause ParseFilter() + { + this.Initialize(); + return queryOptionParser.ParseFilter(); + } + + /// + /// Parses a orderBy clause on the given full Uri, binding + /// the text into semantic nodes using the constructed mode. + /// + /// A representing the metadata bound orderby expression. + public OrderByClause ParseOrderBy() + { + this.Initialize(); + return this.queryOptionParser.ParseOrderBy(); + } + + /// + /// ParseSelectAndExpand from an instantiated class + /// + /// A SelectExpandClause with the semantic representation of select and expand terms + public SelectExpandClause ParseSelectAndExpand() + { + this.Initialize(); + return this.queryOptionParser.ParseSelectAndExpand(); + } + + /// + /// Parses the entity identifier. + /// + /// EntityIdSegment contained absolute Uri representing $id + public EntityIdSegment ParseEntityId() + { + if (this.entityIdSegment != null) + { + return this.entityIdSegment; + } + + InitQueryOptionDic(); + string idQuery = null; + + if (!this.queryOptionDic.TryGetValue(UriQueryConstants.IdQueryOption, out idQuery) && !this.Resolver.EnableCaseInsensitive) + { + return null; + } + + if (idQuery == null && this.Resolver.EnableCaseInsensitive) + { + var list = this.queryOptionDic + .Where(pair => string.Equals(UriQueryConstants.IdQueryOption, pair.Key, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (list.Count == 0) + { + return null; + } + else if (list.Count == 1) + { + idQuery = list.First().Value; + } + else + { + throw new ODataException(Strings.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce(UriQueryConstants.IdQueryOption)); + } + } + + Uri idUri = new Uri(idQuery, UriKind.RelativeOrAbsolute); + + if (!idUri.IsAbsoluteUri) + { + if (!this.uri.IsAbsoluteUri) + { + Uri baseUri = UriUtils.CreateMockAbsoluteUri(); + Uri c = new Uri(UriUtils.CreateMockAbsoluteUri(this.uri), idUri); + idUri = baseUri.MakeRelativeUri(c); + } + else + { + idUri = new Uri(this.uri, idUri); + } + } + + this.entityIdSegment = new EntityIdSegment(idUri); + return this.entityIdSegment; + } + + /// + /// Parses a $top query option + /// + /// A value representing that top option, null if $top query does not exist. + public long? ParseTop() + { + this.Initialize(); + return this.queryOptionParser.ParseTop(); + } + + /// + /// Parses a $skip query option + /// + /// A value representing that skip option, null if $skip query does not exist. + public long? ParseSkip() + { + this.Initialize(); + return this.queryOptionParser.ParseSkip(); + } + + /// + /// Parses a $index query option + /// + /// A value representing that index option, null if $index query does not exist. + public long? ParseIndex() + { + this.Initialize(); + return this.queryOptionParser.ParseIndex(); + } + + /// + /// Parses a $count query option + /// + /// An count representing that count option, null if $count query does not exist. + public bool? ParseCount() + { + this.Initialize(); + return this.queryOptionParser.ParseCount(); + } + + /// + /// Parses the $search. + /// + /// SearchClause representing $search. + public SearchClause ParseSearch() + { + this.Initialize(); + return this.queryOptionParser.ParseSearch(); + } + + /// + /// Parses the $apply. + /// + /// ApplyClause representing $apply. + public ApplyClause ParseApply() + { + this.Initialize(); + return this.queryOptionParser.ParseApply(); + } + + /// + /// Parses a $skiptoken query option + /// + /// A value representing that skip token option, null if $skiptoken query does not exist. + public string ParseSkipToken() + { + this.Initialize(); + return this.queryOptionParser.ParseSkipToken(); + } + + /// + /// Parses a $deltatoken query option + /// + /// A value representing that delta token option, null if $deltatoken query does not exist. + public string ParseDeltaToken() + { + this.Initialize(); + return this.queryOptionParser.ParseDeltaToken(); + } + + /// + /// Parses the $compute. + /// + /// ComputeClause representing $compute. + public ComputeClause ParseCompute() + { + this.Initialize(); + return this.queryOptionParser.ParseCompute(); + } + + /// + /// Parse a full Uri into its contingent parts with semantic meaning attached to each part. + /// See . + /// + /// An representing the full uri. + public ODataUri ParseUri() + { + ExceptionUtils.CheckArgumentNotNull(this.configuration.Model, "model"); + ExceptionUtils.CheckArgumentNotNull(this.uri, "uri"); + + ODataPath path = this.ParsePath(); + SelectExpandClause selectExpand = this.ParseSelectAndExpand(); + FilterClause filter = this.ParseFilter(); + OrderByClause orderBy = this.ParseOrderBy(); + SearchClause search = this.ParseSearch(); + ApplyClause apply = this.ParseApply(); + ComputeClause compute = this.ParseCompute(); + long? top = this.ParseTop(); + long? skip = this.ParseSkip(); + long? index = this.ParseIndex(); + bool? count = this.ParseCount(); + string skipToken = this.ParseSkipToken(); + string deltaToken = this.ParseDeltaToken(); + + // TODO: check it shouldn't be empty + List boundQueryOptions = new List(); + + ODataUri odataUri = new ODataUri(this.ParameterAliasValueAccessor, path, boundQueryOptions, selectExpand, filter, orderBy, search, apply, skip, top, index, count, compute); + odataUri.ServiceRoot = this.serviceRoot; + odataUri.SkipToken = skipToken; + odataUri.DeltaToken = deltaToken; + return odataUri; + } + + /// + /// Parses a the fullUri into a semantic object. + /// + /// + /// This is designed to parse the Path of a URL. If it is used to parse paths that are contained + /// within other places, such as $filter expressions, then it may not enforce correct rules. + /// + /// An representing the metadata-bound path expression. + /// Throws if the input path is not an absolute uri. + private ODataPath ParsePathImplementation() + { + Uri pathUri = this.uri; + ExceptionUtils.CheckArgumentNotNull(pathUri, "pathUri"); + + UriPathParser pathParser = null; + if (this.Container == null) + { + pathParser = new UriPathParser(this.Settings); + } + else + { + pathParser = this.Container.GetService(); + } + + var rawSegments = pathParser.ParsePathIntoSegments(pathUri, this.ServiceRoot); + return ODataPathFactory.BindPath(rawSegments, this.configuration); + } + + /// + /// Initialize a UriParser. We have to initialize UriParser separately for parsing path, because we may set BatchReferenceCallback before ParsePath. + /// + private void Initialize() + { + if (this.odataPath != null) + { + return; + } + + // When parse path failed first time, the alias node would be not null, but already removed from query options, so do not set it in this case. + if (this.ParameterAliasValueAccessor == null) + { + this.ParameterAliasValueAccessor = new ParameterAliasValueAccessor(queryOptions.GetParameterAliases()); + } + + this.odataPath = this.ParsePathImplementation(); + + InitQueryOptionDic(); + + this.queryOptionParser = new ODataQueryOptionParser(this.Model, this.odataPath, queryOptionDic) + { + Configuration = this.configuration + }; + } + + /// + /// Resolve query options to dictionary. + /// + private void InitQueryOptionDic() + { + if (queryOptionDic != null) + { + return; + } + + queryOptionDic = new Dictionary(StringComparer.Ordinal); + customQueryOptions = new List>(); + + if (queryOptions != null) + { + foreach (var queryOption in queryOptions) + { + string queryOptionName = queryOption.Name; + if (queryOptionName == null) + { + continue; + } + + // If EnableNoDollarQueryOptions is true, we will treat all reserved OData query options without "$" prefix as odata query options. + // Or, they will be treated as custom query options which could be duplicated. + bool shouldAddDollarPrefix = this.EnableNoDollarQueryOptions && !queryOption.Name.StartsWith("$", StringComparison.Ordinal); + string fixedQueryOptionName = shouldAddDollarPrefix ? UriQueryConstants.DollarSign + queryOptionName : queryOptionName; + + if (IsODataQueryOption(fixedQueryOptionName)) + { + if (queryOptionDic.ContainsKey(fixedQueryOptionName)) + { + throw new ODataException(Strings.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce( + this.EnableNoDollarQueryOptions + ? string.Format(CultureInfo.InvariantCulture, "${0}/{0}", fixedQueryOptionName.TrimStart('$')) + : fixedQueryOptionName)); + } + + queryOptionDic.Add(fixedQueryOptionName, queryOption.Value); + } + else + { + customQueryOptions.Add(new KeyValuePair(queryOptionName, queryOption.Value)); + } + } + } + } + + /// + /// Judge if optionName belongs to UriQueryConstants (Case ignored). + /// + /// The name of a query option. + /// True if optionName is OData query option, vise versa. + private bool IsODataQueryOption(string optionName) + { + switch (this.Resolver.EnableCaseInsensitive ? optionName.ToLowerInvariant() : optionName) + { + case UriQueryConstants.FilterQueryOption: + case UriQueryConstants.ApplyQueryOption: + case UriQueryConstants.OrderByQueryOption: + case UriQueryConstants.SelectQueryOption: + case UriQueryConstants.ExpandQueryOption: + case UriQueryConstants.SkipQueryOption: + case UriQueryConstants.SkipTokenQueryOption: + case UriQueryConstants.DeltaTokenQueryOption: + case UriQueryConstants.IdQueryOption: + case UriQueryConstants.TopQueryOption: + case UriQueryConstants.IndexQueryOption: + case UriQueryConstants.CountQueryOption: + case UriQueryConstants.FormatQueryOption: + case UriQueryConstants.SearchQueryOption: + case UriQueryConstants.ComputeQueryOption: + return true; + default: + return false; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserConfiguration.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserConfiguration.cs new file mode 100644 index 0000000..f60a1c8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserConfiguration.cs @@ -0,0 +1,161 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using Microsoft.OData.Edm; + + /// + /// Internal class for storing all the configuration information about the URI parser. Allows us to flow these values around without passing an actual parser. + /// + internal sealed class ODataUriParserConfiguration + { + /// The conventions to use when parsing URLs. + private ODataUrlKeyDelimiter urlKeyDelimiter; + + /// The resolver to use when parsing URLs. + private ODataUriResolver uriResolver; + + /// + /// Initializes a new instance of . + /// + /// Model to use for metadata binding. + /// The optional dependency injection container to get related services for URI parsing. + /// Throws if input model is null. + /// Throws if the input serviceRoot is not an AbsoluteUri + public ODataUriParserConfiguration(IEdmModel model, IServiceProvider container) + { + ExceptionUtils.CheckArgumentNotNull(model, "model"); + + this.Model = model; + this.Container = container; + this.Resolver = ODataUriResolver.GetUriResolver(container); + this.urlKeyDelimiter = ODataUrlKeyDelimiter.GetODataUrlKeyDelimiter(container); + + if (this.Container == null) + { + this.Settings = new ODataUriParserSettings(); + } + else + { + this.Settings = this.Container.GetRequiredService(); + } + + this.EnableUriTemplateParsing = false; + } + + /// + /// Initializes a new instance of . + /// + /// Model to use for metadata binding. + /// Throws if input model is null. + /// Throws if the input serviceRoot is not an AbsoluteUri + internal ODataUriParserConfiguration(IEdmModel model) + : this(model, null) + { + } + + /// + /// The settings for this instance of . Refer to the documentation for the individual properties of for more information. + /// + public ODataUriParserSettings Settings { get; private set; } + + /// + /// Gets the model for this ODataUriParser + /// + public IEdmModel Model { get; private set; } + + /// + /// The optional dependency injection container to get related services for URI parsing. + /// + public IServiceProvider Container { get; private set; } + + /// + /// Gets or Sets the to use while parsing, specifically + /// whether to recognize keys as segments or not. + /// + /// Throws if the input value is null. + public ODataUrlKeyDelimiter UrlKeyDelimiter + { + get + { + return this.urlKeyDelimiter; + } + + set + { + ExceptionUtils.CheckArgumentNotNull(value, "UrlKeyDelimiter"); + this.urlKeyDelimiter = value; + } + } + + /// + /// Gets or Sets a callback that returns a BatchReferenceSegment (to be used for $0 in batch) + /// + public Func BatchReferenceCallback { get; set; } + + /// + /// Gets or sets the function which can be used to parse an unknown path segment or an open property segment. + /// + public ParseDynamicPathSegment ParseDynamicPathSegmentFunc { get; set; } + + /// + /// Whether to allow case insensitive for builtin identifier. + /// + internal bool EnableCaseInsensitiveUriFunctionIdentifier + { + get + { + return Resolver.EnableCaseInsensitive; + } + + set + { + Resolver.EnableCaseInsensitive = value; + } + } + + /// + /// Gets or Sets an option whether no dollar query options is enabled. + /// If it is enabled, the '$' prefix of system query options becomes optional. + /// For example, "select" and "$select" are equivalent in this case. + /// + internal bool EnableNoDollarQueryOptions + { + get { return this.Resolver.EnableNoDollarQueryOptions; } + set { this.Resolver.EnableNoDollarQueryOptions = value; } + } + + /// + /// Whether Uri template parsing is enabled. See class for detail. + /// + internal bool EnableUriTemplateParsing { get; set; } + + /// + /// Gets or sets the parameter aliases info for MetadataBinder to resolve parameter alias' metadata type. + /// + internal ParameterAliasValueAccessor ParameterAliasValueAccessor { get; set; } + + /// + /// Gets or sets the . + /// + internal ODataUriResolver Resolver + { + get + { + return this.uriResolver; + } + + set + { + ExceptionUtils.CheckArgumentNotNull(value, "Resolver"); + this.uriResolver = value; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserSettings.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserSettings.cs new file mode 100644 index 0000000..6ada360 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ODataUriParserSettings.cs @@ -0,0 +1,281 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Settings used by . + /// + public sealed class ODataUriParserSettings + { + /// + /// Default recursive call limit for Filter + /// + internal const int DefaultFilterLimit = 800; + + /// + /// Default recursive call limit for OrderBy + /// + internal const int DefaultOrderByLimit = 800; + + /// + /// Default tree depth for Select and Expand + /// + internal const int DefaultSelectExpandLimit = 800; + + /// + /// Default limit for the path parser. + /// + internal const int DefaultPathLimit = 100; + + /// + /// Default limit for the search parser. + /// + internal const int DefaultSearchLimit = 100; + + /// + /// the recursive depth of the Syntactic tree for a filter clause + /// + private int filterLimit; + + /// + /// the maximum depth of the syntactic tree for an orderby clause + /// + private int orderByLimit; + + /// + /// the maximum number of segments in a path + /// + private int pathLimit; + + /// + /// the maximum depth of the Syntactic or Semantic tree for a Select or Expand clause + /// + private int selectExpandLimit; + + /// + /// The maximum depth of the tree that results from parsing $expand. + /// + private int maxExpandDepth; + + /// + /// The maximum number of instances that can appear in the tree that results from parsing $expand. + /// + private int maxExpandCount; + + /// + /// the maximum depth of the syntactic tree for an search clause + /// + private int searchLimit; + + /// + /// Initializes a new instance of with default values. + /// + public ODataUriParserSettings() + { + this.FilterLimit = DefaultFilterLimit; + this.OrderByLimit = DefaultOrderByLimit; + this.PathLimit = DefaultPathLimit; + this.SelectExpandLimit = DefaultSelectExpandLimit; + this.SearchLimit = DefaultSearchLimit; + + this.MaximumExpansionDepth = int.MaxValue; + this.MaximumExpansionCount = int.MaxValue; + } + + /// + /// Gets or sets the maximum depth of the tree that results from parsing $expand. + /// + /// + /// This will be validated after parsing completes, and so should not be used to prevent the instantiation of large trees. + /// Further, redundant expansions will be pruned before validation and will not count towards the maximum. + /// + public int MaximumExpansionDepth + { + get + { + return this.maxExpandDepth; + } + + set + { + if (value < 0) + { + throw new ODataException(ODataErrorStrings.UriParser_NegativeLimit); + } + + this.maxExpandDepth = value; + } + } + + /// + /// Gets or sets the maximum number of instances that can appear in the tree that results from parsing $expand. + /// + /// + /// This will be validated after parsing completes, and so should not be used to prevent the instantiation of large trees. + /// Further, redundant expansions will be pruned before validation and will not count towards the maximum. + /// + public int MaximumExpansionCount + { + get + { + return this.maxExpandCount; + } + + set + { + if (value < 0) + { + throw new ODataException(ODataErrorStrings.UriParser_NegativeLimit); + } + + this.maxExpandCount = value; + } + } + + /// + /// Gets or Sets the maximum recursive depth for a select and expand clause, which limits the maximum depth of the tree that can be parsed by the + /// syntactic parser. This guarantees a set level of performance. + /// + /// + /// The number here doesn't necessarily correspond exactly with the actual maximum recursive depth of the syntactic tree, + /// i.e a limit of 20 doesn't necessarily mean that a tree will have depth exactly 20, it may have depth 10 (but never over 20). + /// Think of it more as an upper bound. + /// + /// Throws if the input value is negative. + internal int SelectExpandLimit + { + get + { + return this.selectExpandLimit; + } + + set + { + if (value < 0) + { + throw new ODataException(ODataErrorStrings.UriParser_NegativeLimit); + } + + this.selectExpandLimit = value; + } + } + + /// + /// Gets or Sets the limit on the maximum depth of the filter tree that can be parsed by the + /// syntactic parser. This guarantees a set level of performance. + /// + /// + /// The number here doesn't necessarily correspond exactly with the actual maximum recursive depth of the syntactic tree, + /// i.e a limit of 20 doesn't necessarily mean that a tree will have depth exactly 20, it may have depth 10 (but never over 20). + /// Think of it more as an upper bound. + /// + /// Throws if the input value is negative. + internal int FilterLimit + { + get + { + return this.filterLimit; + } + + set + { + if (value < 0) + { + throw new ODataException(ODataErrorStrings.UriParser_NegativeLimit); + } + + this.filterLimit = value; + } + } + + /// + /// Gets or sets the maximum recursive depth for an orderby clause, which limits the maximum depth of the tree that can be parsed by the + /// syntactic parser. This guarantees a set level of performance. + /// + /// + /// The number here doesn't necessarily correspond exactly with the actual maximum recursive depth of the syntactic tree, + /// i.e a limit of 20 doesn't necessarily mean that a tree will have depth exactly 20, it may have depth 10 (but never over 20). + /// Think of it more as an upper bound. + /// + /// Throws if the input value is negative. + internal int OrderByLimit + { + get + { + return this.orderByLimit; + } + + set + { + if (value < 0) + { + throw new ODataException(ODataErrorStrings.UriParser_NegativeLimit); + } + + this.orderByLimit = value; + } + } + + /// + /// Gets or Sets the limit on the maximum number of segments that can be parsed by the + /// syntactic parser. This guarantees a set level of performance. + /// + /// + /// Unlike Filter, OrderBy, and SelectExpand, this Limit is more concrete, and will + /// limit the segments to exactly the number that is specified... i.e. a limit of + /// 20 will throw if and only if there are more than 20 segments in the path. + /// + /// Throws if the input value is negative. + internal int PathLimit + { + get + { + return this.pathLimit; + } + + set + { + if (value < 0) + { + throw new ODataException(ODataErrorStrings.UriParser_NegativeLimit); + } + + this.pathLimit = value; + } + } + + /// + /// Gets or sets the maximum recursive depth for an search clause, which limits the maximum depth of the tree that can be parsed by the + /// syntactic parser. This guarantees a set level of performance. + /// + /// + /// The number here doesn't necessarily correspond exactly with the actual maximum recursive depth of the syntactic tree, + /// i.e a limit of 20 doesn't necessarily mean that a tree will have depth exactly 20, it may have depth 10 (but never over 20). + /// Think of it more as an upper bound. + /// + /// Throws if the input value is negative. + internal int SearchLimit + { + get + { + return this.searchLimit; + } + + set + { + if (value < 0) + { + throw new ODataException(ODataErrorStrings.UriParser_NegativeLimit); + } + + this.searchLimit = value; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/OrderByDirection.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/OrderByDirection.cs new file mode 100644 index 0000000..af5bc4a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/OrderByDirection.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + #endregion Namespaces + + /// + /// Enumeration of order by directions. + /// + public enum OrderByDirection + { + /// + /// Ascending order. + /// + Ascending = 0, + + /// + /// Descending order. + /// + Descending = 1, + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ParseDynamicPathSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ParseDynamicPathSegment.cs new file mode 100644 index 0000000..35c709a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ParseDynamicPathSegment.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Represents a delegate for parsing an unknown path segment or an open property segment + /// + /// previous segment info. + /// name of the segment. + /// The section of the segment inside parentheses, or null if there was none. + /// A collection of describing the given + public delegate ICollection ParseDynamicPathSegment(ODataPathSegment previous, string identifier, string parenthesisExpression); +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/CustomUriLiteralParsers.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/CustomUriLiteralParsers.cs new file mode 100644 index 0000000..ac3418b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/CustomUriLiteralParsers.cs @@ -0,0 +1,246 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Linq; + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion + + /// + /// This class is the custom literal parser manager and parser. + /// Add a Uri custom literal parser through this class. + /// This class is also used as an UriLiteralParser. + /// + public sealed class CustomUriLiteralParsers : IUriLiteralParser + { + #region Fields + + private static readonly object Locker = new object(); + + /// + /// Used for General uri literal parsers. These parsers will be called for every text that has to parsed. + /// The parses could parse multiple EdmTypes. + /// + private static IUriLiteralParser[] customUriLiteralParsers = new IUriLiteralParser[0]; + + /// + /// "Registered" uri literal parser to an EdmType. These parsers will be called when the text has to be parsed to the + /// specific EdmType they had registered to. Each of these parsers could parse only one EdmType. Better performace. + /// + private static UriLiteralParserPerEdmType[] customUriLiteralParserPerEdmType = new UriLiteralParserPerEdmType[0]; + + //// TODO: Consider use Dictionary which is a better solution. + //// The problem with dictionary is to generate an HashCode for an EdmTypeReference. + + #endregion + + #region Singleton + + // Internal Singleton so only interal assemblies could parse by the custom parsers. + private static CustomUriLiteralParsers singleInstance; + + private CustomUriLiteralParsers() + { + } + + internal static CustomUriLiteralParsers Instance + { + get + { + if (CustomUriLiteralParsers.singleInstance == null) + { + CustomUriLiteralParsers.singleInstance = new CustomUriLiteralParsers(); + } + + return CustomUriLiteralParsers.singleInstance; + } + } + + #endregion + + #region IUriLiteralParser Implementation - Internal + + /// + /// Parse the given uri text. + /// Try to parse with a specific Uri literal parser regeistered for the target EdmType. + /// If no parser is registered, try to parse with the general parsers. + /// This method is public becuase of the Interface, but the Singleton instance in internal so it could not be accessed by clients. + /// + /// Part of the Uri which has to be parsed to a value of EdmType + /// The type which the uri text has to be parsed to + /// Assign the exception only in case the text could be parsed to the but failed during the parsing process + /// If parsing proceess has succeeded, returns the parsed object, otherwise returns 'Null' + public object ParseUriStringToType(string text, IEdmTypeReference targetType, out UriLiteralParsingException parsingException) + { + IUriLiteralParser uriLiteralParserForEdmType = CustomUriLiteralParsers.GetUriLiteralParserByEdmType(targetType); + + // Search for Uri literal parser which is registered for the given EdmType + if (uriLiteralParserForEdmType != null) + { + return uriLiteralParserForEdmType.ParseUriStringToType(text, targetType, out parsingException); + } + + // Parse with all the general parsers + // Stop when a parser succeeded parsing the text. + IUriLiteralParser[] localCustomUriLiteralParsers = CustomUriLiteralParsers.customUriLiteralParsers; + foreach (IUriLiteralParser customUriLiteralParser in localCustomUriLiteralParsers) + { + // Try to parse + object targetValue = customUriLiteralParser.ParseUriStringToType(text, targetType, out parsingException); + + // The uriCustomParser could parse the given targetType but failed during the parsing process + if (parsingException != null) + { + return null; + } + + // In case of no exception and no value - The parse cannot parse the given text + if (targetValue != null) + { + return targetValue; + } + } + + // No uriCustomParser could parse the requested uri text. + parsingException = null; + return null; + } + + #endregion + + #region Public Static Methods + + /// + /// Add a custom 'IUriLiteralParser' which will be called to parse uri values during the uri parsing process. + /// + /// The custom uri parser + /// is null + /// The given IUriLiteralParser instance already exists + public static void AddCustomUriLiteralParser(IUriLiteralParser customUriLiteralParser) + { + ExceptionUtils.CheckArgumentNotNull(customUriLiteralParser, "customUriLiteralParser"); + + lock (CustomUriLiteralParsers.Locker) + { + if (CustomUriLiteralParsers.customUriLiteralParsers.Contains(customUriLiteralParser)) + { + throw new ODataException(ODataErrorStrings.UriCustomTypeParsers_AddCustomUriTypeParserAlreadyExists); + } + + CustomUriLiteralParsers.customUriLiteralParsers = CustomUriLiteralParsers.customUriLiteralParsers.Concat(new IUriLiteralParser[] { customUriLiteralParser }).ToArray(); + } + } + + /// + /// Add a custom 'IUriLiteralParser' which will be called to parse a value of the given EdmType during the UriParsing process. + /// + /// The EdmType the Uri literal parser can parse. + /// The custom uri type parser to add. + /// is null. + /// is null. + /// Another Uri literal parser is already registered for the given EdmType + public static void AddCustomUriLiteralParser(IEdmTypeReference edmTypeReference, IUriLiteralParser customUriLiteralParser) + { + ExceptionUtils.CheckArgumentNotNull(customUriLiteralParser, "customUriLiteralParser"); + ExceptionUtils.CheckArgumentNotNull(edmTypeReference, "edmTypeReference"); + + lock (CustomUriLiteralParsers.Locker) + { + if (CustomUriLiteralParsers.IsEdmTypeAlreadyRegistered(edmTypeReference)) + { + throw new ODataException(ODataErrorStrings.UriCustomTypeParsers_AddCustomUriTypeParserEdmTypeExists(edmTypeReference.FullName())); + } + + CustomUriLiteralParsers.customUriLiteralParserPerEdmType = CustomUriLiteralParsers.customUriLiteralParserPerEdmType.Concat( + new UriLiteralParserPerEdmType[] + { + new UriLiteralParserPerEdmType + { + EdmTypeOfUriParser = edmTypeReference, + UriLiteralParser = customUriLiteralParser + } + }) + .ToArray(); + } + } + + /// + /// Remove the given custom 'IUriLiteralParser' form cache. + /// It will be removed from both regular parsers and parsers registered with EdmType. + /// + /// The custom uri type parser to remove + /// 'False' if the given parser to remove doesn't exist. 'True' if the parser has successfully removed + /// Uri literal parser is null + public static bool RemoveCustomUriLiteralParser(IUriLiteralParser customUriLiteralParser) + { + ExceptionUtils.CheckArgumentNotNull(customUriLiteralParser, "customUriLiteralParser"); + + lock (CustomUriLiteralParsers.Locker) + { + // Remove parser from the customUriLiteralParserPerEdmType. Same instance can be registered to multiple EdmTypes. + UriLiteralParserPerEdmType[] newCustomUriLiteralParserPerEdmType = CustomUriLiteralParsers.customUriLiteralParserPerEdmType + .Where((parser) => !parser.UriLiteralParser.Equals(customUriLiteralParser)) + .ToArray(); + + // Remove parser from the general custom uri literal parsers. Same instance can be add only once. + IUriLiteralParser[] newCustomUriLiteralParsers = CustomUriLiteralParsers.customUriLiteralParsers + .Where((parser) => !parser.Equals(customUriLiteralParser)) + .ToArray(); + + // Returns 'True' if at least one parser has been removed from the general parser of those registered to EdmType + bool removed = newCustomUriLiteralParserPerEdmType.Length < CustomUriLiteralParsers.customUriLiteralParserPerEdmType.Length || + newCustomUriLiteralParsers.Length < CustomUriLiteralParsers.customUriLiteralParsers.Length; + + CustomUriLiteralParsers.customUriLiteralParserPerEdmType = newCustomUriLiteralParserPerEdmType; + CustomUriLiteralParsers.customUriLiteralParsers = newCustomUriLiteralParsers; + + return removed; + } + } + + #endregion + + #region Private Methods + + private static bool IsEdmTypeAlreadyRegistered(IEdmTypeReference edmTypeReference) + { + return CustomUriLiteralParsers.customUriLiteralParserPerEdmType.Any(uriParserOfEdmType => + EdmElementComparer.IsEquivalentTo(uriParserOfEdmType.EdmTypeOfUriParser, edmTypeReference)); + } + + private static IUriLiteralParser GetUriLiteralParserByEdmType(IEdmTypeReference edmTypeReference) + { + UriLiteralParserPerEdmType requestedUriLiteralParser = + CustomUriLiteralParsers.customUriLiteralParserPerEdmType.FirstOrDefault(uriParserOfEdmType => + uriParserOfEdmType.EdmTypeOfUriParser.IsEquivalentTo(edmTypeReference)); + + if (requestedUriLiteralParser == null) + { + return null; + } + + return requestedUriLiteralParser.UriLiteralParser; + } + + + #endregion + + private sealed class UriLiteralParserPerEdmType + { + internal IEdmTypeReference EdmTypeOfUriParser { get; set; } + + internal IUriLiteralParser UriLiteralParser { get; set; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/DefaultUriLiteralParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/DefaultUriLiteralParser.cs new file mode 100644 index 0000000..6e424e8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/DefaultUriLiteralParser.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + internal sealed class DefaultUriLiteralParser : IUriLiteralParser + { + #region Fields + + // All Uri Literal Parsers + private List uriTypeParsers; + + #endregion + + #region Singleton + + private static DefaultUriLiteralParser singleInstance = new DefaultUriLiteralParser(); + + private DefaultUriLiteralParser() + { + // It is important that UriCustomTypeParsers will be added first, so it will be called before the others built-in parsers + this.uriTypeParsers = new List + { + { CustomUriLiteralParsers.Instance }, + { UriPrimitiveTypeParser.Instance } + }; + } + + internal static DefaultUriLiteralParser Instance + { + get + { + return singleInstance; + } + } + + #endregion + + #region IUriLiteralParser Implementation + + /// + /// Try to parse the given text by each parser. + /// + /// Part of the Uri which has to be parsed to a value of EdmType + /// The type which the uri text has to be parsed to + /// Assign the exception only in case the text could be parsed to the but failed during the parsing process + /// If the parsing proceess has succeeded, returns the parsed object, otherwise returns 'Null' + public object ParseUriStringToType(string text, IEdmTypeReference targetType, out UriLiteralParsingException parsingException) + { + parsingException = null; + object targetValue; + + // Try to parse the uri text with each parser + foreach (IUriLiteralParser uriTypeParser in this.uriTypeParsers) + { + targetValue = uriTypeParser.ParseUriStringToType(text, targetType, out parsingException); + + // Stop in case the parser has returned an excpetion + if (parsingException != null) + { + return null; + } + + // In case of no exception and no value - The parse cannot parse the given text + if (targetValue != null) + { + return targetValue; + } + } + + return null; + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ExpandDepthAndCountValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ExpandDepthAndCountValidator.cs new file mode 100644 index 0000000..6fdc5ba --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ExpandDepthAndCountValidator.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Diagnostics; + using System.Linq; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// A component for walking an expand tree and determining if the depth or number of items exceed user-specified limits. + /// + internal sealed class ExpandDepthAndCountValidator + { + /// + /// The maximum depth of any expand tree being validated. + /// + private readonly int maxDepth; + + /// + /// The maximum number of expand items allowed in any expand tree being validated, including leaf and non-leaf nodes. + /// + private readonly int maxCount; + + /// + /// The current count when validating a particular tree. + /// + private int currentCount; + + /// + /// Initializes a new instance of . + /// + /// The maximum depth of an expand tree. + /// The maximum number of expanded items allowed in a tree. + internal ExpandDepthAndCountValidator(int maxDepth, int maxCount) + { + Debug.Assert(maxDepth >= 0, "Max depth cannot be negative."); + Debug.Assert(maxCount >= 0, "Max count cannot be negative."); + this.maxDepth = maxDepth; + this.maxCount = maxCount; + } + + /// + /// Validates the given tree against the user-specified limits. + /// + /// The expand tree to validate. + internal void Validate(SelectExpandClause expandTree) + { + this.currentCount = 0; + this.EnsureMaximumCountAndDepthAreNotExceeded(expandTree, /*currentDepth*/ 0); + } + + /// + /// Recursively ensures that the maximum count/depth are not exceeded by walking the tree. + /// + /// The expand tree to walk and validate. + /// The current depth of the tree walk. + private void EnsureMaximumCountAndDepthAreNotExceeded(SelectExpandClause expandTree, int currentDepth) + { + Debug.Assert(expandTree != null, "expandTree != null"); + if (currentDepth > this.maxDepth) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandDepthExceeded(currentDepth, this.maxDepth)); + } + + foreach (ExpandedNavigationSelectItem expandItem in expandTree.SelectedItems.Where(I => I.GetType() == typeof(ExpandedNavigationSelectItem))) + { + this.currentCount++; + if (this.currentCount > this.maxCount) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandCountExceeded(this.currentCount, this.maxCount)); + } + + this.EnsureMaximumCountAndDepthAreNotExceeded(expandItem.SelectAndExpand, currentDepth + 1); + } + + this.currentCount += expandTree.SelectedItems.Where(I => I.GetType() == typeof(ExpandedReferenceSelectItem)).Count(); + if (this.currentCount > this.maxCount) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandCountExceeded(this.currentCount, this.maxCount)); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs new file mode 100644 index 0000000..f360572 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionCallParser.cs @@ -0,0 +1,217 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Implementation of IFunctionCallParser that allows functions calls and parses arguments with a provided method. + /// TODO: This inmplementaiton is incomplete. + /// + internal sealed class FunctionCallParser : IFunctionCallParser + { + /// + /// Reference to the lexer. + /// + private readonly ExpressionLexer lexer; + + /// + /// Method used to parse arguments. + /// + private readonly UriQueryExpressionParser parser; + + /// + /// If set to true, catches any ODataException thrown while trying to parse function arguments. + /// + private readonly bool restoreStateIfFail; + + /// + /// Create a new FunctionCallParser. + /// + /// Lexer positioned at a function identifier. + /// The UriQueryExpressionParser. + public FunctionCallParser(ExpressionLexer lexer, UriQueryExpressionParser parser) + : this(lexer, parser, false /* restoreStateIfFail */) + { + } + + /// + /// Create a new FunctionCallParser. + /// + /// Lexer positioned at a function identifier. + /// The UriQueryExpressionParser. + /// If set to true, catches any ODataException thrown while trying to parse function arguments. + public FunctionCallParser(ExpressionLexer lexer, UriQueryExpressionParser parser, bool restoreStateIfFail) + { + ExceptionUtils.CheckArgumentNotNull(lexer, "lexer"); + ExceptionUtils.CheckArgumentNotNull(parser, "parser"); + this.lexer = lexer; + this.parser = parser; + this.restoreStateIfFail = restoreStateIfFail; + } + + /// + /// Reference to the underlying UriQueryExpressionParser. + /// + public UriQueryExpressionParser UriQueryExpressionParser + { + get { return this.parser; } + } + + /// + /// Reference to the lexer. + /// + public ExpressionLexer Lexer + { + get { return this.lexer; } + } + + /// + /// Try to parse an identifier that represents a function. If the parser instance has + /// set as false, then an + /// is thrown if the parser finds an error. + /// + /// Token for the parent of the function being parsed. + /// QueryToken representing this function. + /// True if the parsing was successful. + public bool TryParseIdentifierAsFunction(QueryToken parent, out QueryToken result) + { + result = null; + string functionName; + + ExpressionLexer.ExpressionLexerPosition position = lexer.SnapshotPosition(); + + if (this.Lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot) + { + // handle the case where we prefix a function with its namespace. + functionName = this.Lexer.ReadDottedIdentifier(false); + } + else + { + Debug.Assert(this.Lexer.CurrentToken.Kind == ExpressionTokenKind.Identifier, "Only identifier tokens can be treated as function calls."); + functionName = this.Lexer.CurrentToken.Text; + this.Lexer.NextToken(); + } + + FunctionParameterToken[] arguments = this.ParseArgumentListOrEntityKeyList(() => lexer.RestorePosition(position)); + if (arguments != null) + { + result = new FunctionCallToken(functionName, arguments, parent); + } + + return result != null; + } + + /// + /// Parses argument lists or entity key value list. + /// + /// Action invoked for restoring a state during failure. + /// The lexical tokens representing the arguments. + public FunctionParameterToken[] ParseArgumentListOrEntityKeyList(Action restoreAction = null) + { + if (this.Lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + if (restoreStateIfFail && restoreAction != null) + { + restoreAction(); + return null; + } + + throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.Lexer.CurrentToken.Position, this.Lexer.ExpressionText)); + } + + this.Lexer.NextToken(); + FunctionParameterToken[] arguments; + if (this.Lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) + { + arguments = FunctionParameterToken.EmptyParameterList; + } + else + { + arguments = this.ParseArguments(); + } + + if (this.Lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + if (restoreStateIfFail && restoreAction != null) + { + restoreAction(); + return null; + } + + throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrCommaExpected(this.Lexer.CurrentToken.Position, this.Lexer.ExpressionText)); + } + + this.Lexer.NextToken(); + return arguments; + } + + /// + /// Parses comma-separated arguments. + /// + /// + /// Arguments can either be of the form a=1,b=2,c=3 or 1,2,3. + /// They cannot be mixed between those two styles. + /// + /// The lexical tokens representing the arguments. + public FunctionParameterToken[] ParseArguments() + { + ICollection argList; + if (this.TryReadArgumentsAsNamedValues(out argList)) + { + return argList.ToArray(); + } + + return this.ReadArgumentsAsPositionalValues().ToArray(); + } + + /// + /// Read the list of arguments as a set of positional values + /// + /// A list of FunctionParameterTokens representing each argument + private List ReadArgumentsAsPositionalValues() + { + List argList = new List(); + while (true) + { + argList.Add(new FunctionParameterToken(null, this.parser.ParseExpression())); + if (this.Lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) + { + break; + } + + this.Lexer.NextToken(); + } + + return argList; + } + + /// + /// Try to read the list of arguments as a set of named values + /// + /// the parsed list of arguments + /// true if the arguments were successfully read. + private bool TryReadArgumentsAsNamedValues(out ICollection argList) + { + if (this.parser.TrySplitFunctionParameters(out argList)) + { + if (argList.Select(t => t.ParameterName).Distinct().Count() != argList.Count) + { + throw new ODataException(ODataErrorStrings.FunctionCallParser_DuplicateParameterOrEntityKeyName); + } + + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionOverloadResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionOverloadResolver.cs new file mode 100644 index 0000000..6728aa4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionOverloadResolver.cs @@ -0,0 +1,261 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Helper class to help bind function overloads. + /// This is shared between path and filter/orderby function resolution. + /// + internal static class FunctionOverloadResolver + { + /// + /// Try to resolve a function from the given inputs. + /// + /// The identifier of the function that we're trying to find + /// the names of the parameters to search for. + /// the model to use to look up the operation import + /// The single matching function found. + /// Resolver to be used. + /// True if a function was matched, false otherwise. Will throw if the model has illegal operation imports. + internal static bool ResolveOperationImportFromList(string identifier, IList parameterNames, IEdmModel model, out IEdmOperationImport matchingOperationImport, ODataUriResolver resolver) + { + IEnumerable candidateMatchingOperationImports = null; + IList foundActionImportsWhenLookingForFunctions = new List(); + + try + { + if (parameterNames.Count > 0) + { + // In this case we have to return a function so filter out actions because the number of parameters > 0. + candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier) + .RemoveActionImports(out foundActionImportsWhenLookingForFunctions) + .FilterOperationsByParameterNames(parameterNames, resolver.EnableCaseInsensitive); + } + else + { + candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier); + } + } + catch (Exception exc) + { + if (!ExceptionUtils.IsCatchableExceptionType(exc)) + { + throw; + } + + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperationImport(identifier), exc); + } + + if (foundActionImportsWhenLookingForFunctions.Count > 0) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); + } + + // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL + if (candidateMatchingOperationImports.Any(f => f.IsActionImport())) + { + if (candidateMatchingOperationImports.Count() > 1) + { + if (candidateMatchingOperationImports.Any(o => o.IsFunctionImport())) + { + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier)); + } + else + { + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionImportOverloads(identifier)); + } + } + + if (parameterNames.Count() != 0) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); + } + + matchingOperationImport = candidateMatchingOperationImports.Single(); + return true; + } + + // If parameter count is zero and there is one function import whoese parameter count is zero, return this function import. + if (candidateMatchingOperationImports.Count() > 1 && parameterNames.Count == 0) + { + candidateMatchingOperationImports = candidateMatchingOperationImports.Where(operationImport => operationImport.Operation.Parameters.Count() == 0); + } + + if (!candidateMatchingOperationImports.HasAny()) + { + matchingOperationImport = null; + return false; + } + + // If more than one overload matches, try to select based on optional parameters + if (candidateMatchingOperationImports.Count() > 1) + { + candidateMatchingOperationImports = candidateMatchingOperationImports.FindBestOverloadBasedOnParameters(parameterNames); + } + + if (candidateMatchingOperationImports.Count() > 1) + { + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier)); + } + + matchingOperationImport = candidateMatchingOperationImports.Single(); + + return matchingOperationImport != null; + } + + /// + /// Try to resolve a function from the given inputs. + /// + /// The identifier of the function that we're trying to find + /// the names of the parameters to search for. + /// the type of the previous segment + /// the model to use to look up the operation import + /// The single matching function found. + /// Resolver to be used. + /// True if a function was matched, false otherwise. Will throw if the model has illegal operation imports. + internal static bool ResolveOperationFromList(string identifier, IEnumerable parameterNames, IEdmType bindingType, IEdmModel model, out IEdmOperation matchingOperation, ODataUriResolver resolver) + { + // TODO: update code that is duplicate between operation and operation import, add more tests. + // If the previous segment is an open type, the service action name is required to be fully qualified or else we always treat it as an open property name. + if (bindingType != null) + { + // TODO: look up actual container names here? + // When using extension, there may be function call with unqualified name. So loose the restriction here. + if (bindingType.IsOpen() && !identifier.Contains(".") && resolver.GetType() == typeof(ODataUriResolver)) + { + matchingOperation = null; + return false; + } + } + + IEnumerable candidateMatchingOperations = null; + + // The extension method FindBoundOperations & FindOperations call IEdmModel.FindDeclaredBoundOperations which can be implemented by anyone and it could throw any type of exception + // so catching all of them and simply putting it in the inner exception. + try + { + if (bindingType != null) + { + candidateMatchingOperations = resolver.ResolveBoundOperations(model, identifier, bindingType); + } + else + { + candidateMatchingOperations = resolver.ResolveUnboundOperations(model, identifier); + } + } + catch (Exception exc) + { + if (ExceptionUtils.IsCatchableExceptionType(exc)) + { + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperation(identifier), exc); + } + + throw; + } + + IList foundActionsWhenLookingForFunctions = new List(); + bool hasParameters = parameterNames.Count() > 0; + + if (bindingType != null) + { + candidateMatchingOperations.EnsureOperationsBoundWithBindingParameter(); + } + + // If the number of parameters > 0 then this has to be a function as actions can't have parameters on the uri only in the payload. Filter further by parameters in this case, otherwise don't. + if (hasParameters) + { + // can only be a function as only functions have parameters on the uri. + candidateMatchingOperations = candidateMatchingOperations.RemoveActions(out foundActionsWhenLookingForFunctions) + .FilterOperationsByParameterNames(parameterNames, resolver.EnableCaseInsensitive); + } + else if (bindingType != null) + { + // Filter out functions with more than one parameter. Actions should not be filtered as the parameters are in the payload not the uri + candidateMatchingOperations = candidateMatchingOperations.Where(o => + (o.IsFunction() && (o.Parameters.Count() == 1 || o.Parameters.Skip(1).All(p => p is IEdmOptionalParameter))) || o.IsAction()); + } + else + { + // Filter out functions with any parameters + candidateMatchingOperations = candidateMatchingOperations.Where(o => (o.IsFunction() && !o.Parameters.Any()) || o.IsAction()); + } + + // Only filter if there is more than one and its needed. + if (candidateMatchingOperations.Count() > 1) + { + candidateMatchingOperations = candidateMatchingOperations.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(bindingType); + } + + // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL + if (candidateMatchingOperations.Any(f => f.IsAction())) + { + if (candidateMatchingOperations.Count() > 1) + { + if (candidateMatchingOperations.Any(o => o.IsFunction())) + { + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationOverloads(identifier)); + } + else + { + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionOverloads(identifier)); + } + } + + if (hasParameters) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); + } + + matchingOperation = candidateMatchingOperations.Single(); + return true; + } + + if (foundActionsWhenLookingForFunctions.Count > 0) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); + } + + // If more than one overload matches, try to select based on optional parameters + if (candidateMatchingOperations.Count() > 1) + { + candidateMatchingOperations = candidateMatchingOperations.FindBestOverloadBasedOnParameters(parameterNames); + } + + if (candidateMatchingOperations.Count() > 1) + { + throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_NoSingleMatchFound(identifier, string.Join(",", parameterNames.ToArray()))); + } + + matchingOperation = candidateMatchingOperations.SingleOrDefault(); + return matchingOperation != null; + } + + internal static bool HasAny(this IEnumerable enumerable) where T : class + { + IList list = enumerable as IList; + if (list != null) + { + return list.Count > 0; + } + + T[] array = enumerable as T[]; + if (array != null) + { + return array.Length > 0; + } + + return enumerable.FirstOrDefault() != null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionParameterParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionParameterParser.cs new file mode 100644 index 0000000..328cb9a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/FunctionParameterParser.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Component for parsing function parameters in both $filter/$orderby expressions and in paths. + /// TODO: update code that is duplicate between operation and operation import, add more tests. + /// + internal static class FunctionParameterParser + { + /// + /// Tries to parse a collection of function parameters. Allows path and filter to share the core algorithm while representing parameters differently. + /// + /// The UriQueryExpressionParser to read from. + /// The parameters if they were successfully split. + /// Whether the parameters could be split. + internal static bool TrySplitFunctionParameters(this UriQueryExpressionParser parser, out ICollection splitParameters) + { + return parser.TrySplitOperationParameters(ExpressionTokenKind.CloseParen, out splitParameters); + } + + /// + /// Tries to parse a collection of function parameters for path. + /// + /// The contents of the parentheses portion of the current path segment. + /// The ODataUriParserConfiguration to create a UriQueryExpressionParser. + /// The parameters if they were successfully split. + /// Whether the parameters could be split. + internal static bool TrySplitOperationParameters(string parenthesisExpression, ODataUriParserConfiguration configuration, out ICollection splitParameters) + { + ExpressionLexer lexer = new ExpressionLexer(parenthesisExpression, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/); + UriQueryExpressionParser parser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, lexer); + var ret = parser.TrySplitOperationParameters(ExpressionTokenKind.End, out splitParameters); + + // check duplicate names + if (splitParameters.Select(t => t.ParameterName).Distinct().Count() != splitParameters.Count) + { + throw new ODataException(ODataErrorStrings.FunctionCallParser_DuplicateParameterOrEntityKeyName); + } + + return ret; + } + + /// + /// Tries to parse a collection of function parameters. Allows path and filter to share the core algorithm while representing parameters differently. + /// + /// The UriQueryExpressionParser to read from. + /// The token kind that marks the end of the parameters. + /// The parameters if they were successfully split. + /// Whether the parameters could be split. + private static bool TrySplitOperationParameters(this UriQueryExpressionParser parser, ExpressionTokenKind endTokenKind, out ICollection splitParameters) + { + Debug.Assert(parser != null, "parser != null"); + var lexer = parser.Lexer; + var parameters = new List(); + splitParameters = parameters; + + ExpressionToken currentToken = lexer.CurrentToken; + if (currentToken.Kind == endTokenKind) + { + return true; + } + + if (currentToken.Kind != ExpressionTokenKind.Identifier || lexer.PeekNextToken().Kind != ExpressionTokenKind.Equal) + { + return false; + } + + while (currentToken.Kind != endTokenKind) + { + lexer.ValidateToken(ExpressionTokenKind.Identifier); + string identifier = lexer.CurrentToken.GetIdentifier(); + lexer.NextToken(); + + lexer.ValidateToken(ExpressionTokenKind.Equal); + lexer.NextToken(); + + // the below UriQueryExpressionParser.ParseExpression() is able to parse common expression per ABNF: + // functionExprParameter = parameterName EQ ( parameterAlias / parameterValue ) + // parameterValue = arrayOrObject + // / commonExpr + QueryToken parameterValue = parser.ParseExpression(); + parameters.Add(new FunctionParameterToken(identifier, parameterValue)); + + // the above parser.ParseExpression() already moves to the next token, now get CurrentToken checking a comma followed by something + currentToken = lexer.CurrentToken; + if (currentToken.Kind == ExpressionTokenKind.Comma) + { + lexer.NextToken(); + currentToken = lexer.CurrentToken; + if (currentToken.Kind == endTokenKind) + { + // Trailing comma. + throw new ODataException(ODataErrorStrings.ExpressionLexer_SyntaxError(lexer.Position, lexer.ExpressionText)); + } + } + } + + return true; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IFunctionCallParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IFunctionCallParser.cs new file mode 100644 index 0000000..eb05e7c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IFunctionCallParser.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Interface for a class that can parse an identifier as a function and return a representitive QueryToken. + /// + internal interface IFunctionCallParser + { + /// + /// Reference to the lexer. + /// + ExpressionLexer Lexer { get; } + + /// + /// Parses an identifier that represents a function. + /// + /// the syntactically bound parent of this identifier. + /// QueryToken representing this function. + /// True if the parsing was successful. + bool TryParseIdentifierAsFunction(QueryToken parent, out QueryToken result); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IUriLiteralParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IUriLiteralParser.cs new file mode 100644 index 0000000..cedc93f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IUriLiteralParser.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Interface for Uri literal parser. + /// To parse the uri of an OData request into objects, the ODataUriParser uses UriLiteralParses. + /// Implementation of this interface will parse a text of an EdmType to it's instance. + /// + public interface IUriLiteralParser + { + /// + /// Parse the given text of EdmType to it's object instance. + /// Return 'Null' if the text could not be parsed to the requested . + /// Assign paramter only in case the text could be parsed to the requested , but failed during the parsing proccess. + /// + /// Part of the uri which has to be parsed to a value of EdmType + /// The type which the uri text has to be parsed to + /// Assign the exception only in case the text could be parsed to the but failed during the parsing process + /// If the parsing proceess has succeeded, returns the parsed object, otherwise returns 'Null' + object ParseUriStringToType(string text, IEdmTypeReference targetType, out UriLiteralParsingException parsingException); + + // Consider add this API: + // bool TryParseUriStringToType(string text, IEdmTypeReference targetType,out object targetValue, out UriTypeParsingException parsingException); + // This can be problematic because of the the 'bool' return type + the out exception parameter. The 'Try' function could return 'false' with null exception because it doesn't support the given type, + // bnd this not a 'standart' function so it can confuse the developers. Standart 'Try' function assign the out Exception paramter when return value is 'false'. + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IdentifierTokenizer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IdentifierTokenizer.cs new file mode 100644 index 0000000..a62da74 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/IdentifierTokenizer.cs @@ -0,0 +1,125 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Class that knows how to parse an identifier using an ExpressionLexer that is appropriately positioned. + /// + internal sealed class IdentifierTokenizer + { + /// + /// Reference to the lexer. + /// + private readonly ExpressionLexer lexer; + + /// + /// parameters from the expression parser + /// + private readonly HashSet parameters; + + /// + /// Object to handle the parsing of things that look like function calls. + /// + private readonly IFunctionCallParser functionCallParser; + + /// + /// Parse an Identifier into the right QueryToken + /// + /// parameters passed in to the UriQueryExpressionParser + /// Object to use to handle parsing function calls. + public IdentifierTokenizer(HashSet parameters, IFunctionCallParser functionCallParser) + { + ExceptionUtils.CheckArgumentNotNull(parameters, "parameters"); + ExceptionUtils.CheckArgumentNotNull(functionCallParser, "functionCallParser"); + this.lexer = functionCallParser.Lexer; + this.parameters = parameters; + this.functionCallParser = functionCallParser; + } + + /// + /// Parses identifiers. + /// + /// the syntactically bound parent of this identifier. + /// The lexical token representing the expression. + public QueryToken ParseIdentifier(QueryToken parent) + { + this.lexer.ValidateToken(ExpressionTokenKind.Identifier); + + // An open paren here would indicate calling a method in regular C# syntax. + // TODO: Make this more generalized to work with any kind of function. + bool identifierIsFunction = this.lexer.ExpandIdentifierAsFunction(); + QueryToken functionCallToken; + if (identifierIsFunction && this.functionCallParser.TryParseIdentifierAsFunction(parent, out functionCallToken)) + { + return functionCallToken; + } + + if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot) + { + string fullIdentifier = this.lexer.ReadDottedIdentifier(false); + return new DottedIdentifierToken(fullIdentifier, parent); + } + + return this.ParseMemberAccess(parent); + } + + /// + /// Parses member access. + /// + /// Instance being accessed. + /// The lexical token representing the expression. + public QueryToken ParseMemberAccess(QueryToken instance) + { + if (this.lexer.CurrentToken.Text == UriQueryConstants.Star) + { + return this.ParseStarMemberAccess(instance); + } + + string identifier = this.lexer.CurrentToken.GetIdentifier(); + if (instance == null && this.parameters.Contains(identifier)) + { + this.lexer.NextToken(); + return new RangeVariableToken(identifier); + } + + this.lexer.NextToken(); + return new EndPathToken(identifier, instance); + } + + /// + /// Parses * (all member) access at the beginning of a select expression. + /// + /// Instance being accessed. + /// The lexical token representing the expression. + public QueryToken ParseStarMemberAccess(QueryToken instance) + { + if (this.lexer.CurrentToken.Text != UriQueryConstants.Star) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CannotCreateStarTokenFromNonStar(this.lexer.CurrentToken.Text)); + } + + this.lexer.NextToken(); + return new StarToken(instance); + } + + /// Creates an exception for a parse error. + /// Message text. + /// A new Exception. + private static Exception ParseError(string message) + { + return new ODataException(message); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/KeyFinder.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/KeyFinder.cs new file mode 100644 index 0000000..b50dca9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/KeyFinder.cs @@ -0,0 +1,196 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + + /// + /// Find the key from a previous key segment and use it to construct the current key + /// + internal static class KeyFinder + { + /// + /// Find any related keys from the parent key segment, if it exists, and add them to the raw key values that + /// we already have from the uri. + /// + /// The raw key values as we've parsed them from the uri. + /// The list of key properties on the target entity. + /// The current navigation property that we're trying to follow using the raw key values + /// The key segment of the parent entity in this path, if it exists. Null otherwise + /// A new SegmentArgumentParser with any keys that were found added to its list of NamedValues. + /// Thorws if the input currentNavigationProperty is null. + public static SegmentArgumentParser FindAndUseKeysFromRelatedSegment(SegmentArgumentParser rawKeyValuesFromUri, IEnumerable targetEntityKeyProperties, IEdmNavigationProperty currentNavigationProperty, KeySegment keySegmentOfParentEntity) + { + ExceptionUtils.CheckArgumentNotNull(currentNavigationProperty, "currentNavigationProperty"); + ExceptionUtils.CheckArgumentNotNull(rawKeyValuesFromUri, "rawKeyValuesFromUri"); + + ReadOnlyCollection targetKeyPropertyList = targetEntityKeyProperties != null ? new ReadOnlyCollection(targetEntityKeyProperties.ToList()) : new ReadOnlyCollection(new List()); + + // should only get here if the number of raw parameters from the uri is different than the number of key properties for the target entity. + Debug.Assert(rawKeyValuesFromUri.ValueCount < targetKeyPropertyList.Count(), "rawKeyValuesFromUri.ValueCount < targetEntityKeyProperties.Count()"); + + // if the raw key from the uri has positional values, there must be only one of them + // its important to cache this value here because we'll change it when we add new + // named values below (the implementation of AreValuesNamed is just namedValues !=null) + bool hasPositionalValues = !rawKeyValuesFromUri.AreValuesNamed; + if (hasPositionalValues && rawKeyValuesFromUri.ValueCount > 1) + { + return rawKeyValuesFromUri; + } + + if (keySegmentOfParentEntity == null) + { + return rawKeyValuesFromUri; + } + + // TODO: p2 merge the below 2 pieces of codes + // find out if any target entity key properties have referential constraints that link them to the previous rawKeyValuesFromUri. + List keysFromReferentialIntegrityConstraint = ExtractMatchingPropertyPairsFromNavProp(currentNavigationProperty, targetKeyPropertyList).ToList(); + + foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint) + { + KeyValuePair valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.DependentProperty.Name); + if (valueFromParent.Key != null) + { + // if the key from the referential integrity constraint is one of the target key properties + // and that key property isn't already populated in the raw key values from the uri, then + // we set that value to the value from the parent key segment. + if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name)) + { + rawKeyValuesFromUri.AddNamedValue( + keyFromReferentialIntegrityConstraint.PrincipalProperty.Name, + ConvertKeyValueToUriLiteral(valueFromParent.Value, rawKeyValuesFromUri.KeyAsSegment)); + } + } + } + + // also need to look to see if any nav props exist in the target set that refer back to this same set, which might have + // referential constraints also. + keysFromReferentialIntegrityConstraint.Clear(); + IEdmNavigationProperty reverseNavProp = currentNavigationProperty.Partner; + if (reverseNavProp != null) + { + keysFromReferentialIntegrityConstraint.AddRange(ExtractMatchingPropertyPairsFromReversedNavProp(reverseNavProp, targetKeyPropertyList)); + } + + foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint) + { + KeyValuePair valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name); + if (valueFromParent.Key != null) + { + // if the key from the referential integrity constraint is one of the target key properties + // and that key property isn't already populated in the raw key values from the uri, then + // we set that value to the value from the parent key segment. + if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.DependentProperty.Name)) + { + rawKeyValuesFromUri.AddNamedValue( + keyFromReferentialIntegrityConstraint.DependentProperty.Name, + ConvertKeyValueToUriLiteral(valueFromParent.Value, rawKeyValuesFromUri.KeyAsSegment)); + } + } + } + + // if we had a positional value before, then we need to add that value as a new named value. + // the name that we choose will be the only value from the target entity key properties + // that isn't already set in the NamedValues list. + if (hasPositionalValues) + { + if (rawKeyValuesFromUri.NamedValues != null) + { + List unassignedProperties = targetKeyPropertyList.Where(x => !rawKeyValuesFromUri.NamedValues.ContainsKey(x.Name)).ToList(); + + if (unassignedProperties.Count == 1) + { + rawKeyValuesFromUri.AddNamedValue(unassignedProperties[0].Name, rawKeyValuesFromUri.PositionalValues[0]); + } + else + { + return rawKeyValuesFromUri; + } + + // clear out the positional value so that we keep a consistent state in the + // raw keys from uri. + rawKeyValuesFromUri.PositionalValues.Clear(); + } + else + { + return rawKeyValuesFromUri; + } + } + + return rawKeyValuesFromUri; + } + + /// + /// Find any referential constraint property pairs in a given nav prop that match any of the provided key properties. + /// + /// The navigation property to search + /// The list of key properties that we're searching for + /// All referential constraint property pairs that match the list of target key properties. + private static IEnumerable ExtractMatchingPropertyPairsFromNavProp(IEdmNavigationProperty currentNavigationProperty, IEnumerable targetKeyPropertyList) + { + if (currentNavigationProperty != null && currentNavigationProperty.ReferentialConstraint != null) + { + // currentNavigationProperty.ReferentialConstraint has mapping of source property(dependent)-> target referencedProperty(principal) + // so check PrincipalProperty against targetKeyPropertyList + return currentNavigationProperty.ReferentialConstraint.PropertyPairs.Where(x => targetKeyPropertyList.Any(y => y == x.PrincipalProperty)); + } + else + { + return new List(); + } + } + + /// + /// Find any referential constraint property pairs in a given reversed nav prop that match any of the provided key properties. + /// + /// The navigation property to search + /// The list of key properties that we're searching for + /// All referential constraint property pairs that match the list of target key properties. + private static IEnumerable ExtractMatchingPropertyPairsFromReversedNavProp(IEdmNavigationProperty currentNavigationProperty, IEnumerable targetKeyPropertyList) + { + if (currentNavigationProperty != null && currentNavigationProperty.ReferentialConstraint != null) + { + // currentNavigationProperty.ReferentialConstraint has mapping of target property(dependent)-> source referencedProperty(principal) + // so check DependentProperty against targetKeyPropertyList + return currentNavigationProperty.ReferentialConstraint.PropertyPairs.Where(x => targetKeyPropertyList.Any(y => y == x.DependentProperty)); + } + else + { + return new List(); + } + } + + /// + /// Convert the given key value to a URI literal. + /// + /// The key value to convert. + /// Whether the KeyAsSegment convention is enabled. + /// The converted URI literal for the given key value. + private static string ConvertKeyValueToUriLiteral(object value, bool keyAsSegment) + { + // For Default convention, + // ~/Customers('Peter') => key value is "Peter" => URI literal is "'Peter'" + // + // For KeyAsSegment convention, + // ~/Customers/Peter => key value is "Peter" => URI literal is "Peter" + string stringValue = value as string; + if (keyAsSegment && stringValue != null) + { + return stringValue; + } + + // All key values are primitives so use this instead of ODataUriUtils.ConvertToUriLiteral() + // to improve efficiency. + return ODataUriConversionUtils.ConvertToUriPrimitiveLiteral(value, ODataVersion.V4); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/LiteralParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/LiteralParser.cs new file mode 100644 index 0000000..67b589b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/LiteralParser.cs @@ -0,0 +1,661 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; +using System.Xml; +using Microsoft.OData.Edm; +using Microsoft.Spatial; + +namespace Microsoft.OData.UriParser +{ + /// Use this class to parse literals from keys, etags, skiptokens, and filter/orderby expression constants. + internal abstract class LiteralParser + { + /// + /// Default singleton instance of the literal parser. + /// + private static readonly LiteralParser DefaultInstance = new DefaultLiteralParser(); + + /// + /// Singleton instance of the literal parser for when keys-as-segments is turned on, which does not wrap the formatted strings in any quotes or type-markers. + /// + private static readonly LiteralParser KeysAsSegmentsInstance = new KeysAsSegmentsLiteralParser(); + + /// + /// Mapping between primitive CLR types and lightweight parser classes for that type. + /// + private static readonly IDictionary Parsers = new Dictionary(ReferenceEqualityComparer.Instance) + { + // Type-specific parsers. + { typeof(byte[]), new BinaryPrimitiveParser() }, + { typeof(String), new StringPrimitiveParser() }, + { typeof(Decimal), new DecimalPrimitiveParser() }, + { typeof(Date), new DatePrimitiveParser() }, + + // Types without single-quotes or type markers + { typeof(Boolean), DelegatingPrimitiveParser.WithoutMarkup(XmlConvert.ToBoolean) }, + { typeof(Byte), DelegatingPrimitiveParser.WithoutMarkup(XmlConvert.ToByte) }, + { typeof(SByte), DelegatingPrimitiveParser.WithoutMarkup(XmlConvert.ToSByte) }, + { typeof(Int16), DelegatingPrimitiveParser.WithoutMarkup(XmlConvert.ToInt16) }, + { typeof(Int32), DelegatingPrimitiveParser.WithoutMarkup(XmlConvert.ToInt32) }, + { typeof(DateTimeOffset), DelegatingPrimitiveParser.WithoutMarkup(XmlConvert.ToDateTimeOffset) }, + { typeof(Guid), DelegatingPrimitiveParser.WithoutMarkup(XmlConvert.ToGuid) }, + + // Types with prefixes and single-quotes. + { typeof(TimeSpan), DelegatingPrimitiveParser.WithPrefix(EdmValueParser.ParseDuration, ExpressionConstants.LiteralPrefixDuration) }, + + // Types with suffixes. + { typeof(Int64), DelegatingPrimitiveParser.WithSuffix(XmlConvert.ToInt64, ExpressionConstants.LiteralSuffixInt64, /*required*/ false) }, + { typeof(Single), DelegatingPrimitiveParser.WithSuffix(XmlConvert.ToSingle, ExpressionConstants.LiteralSuffixSingle, /*required*/ false) }, + { typeof(Double), DelegatingPrimitiveParser.WithSuffix(XmlConvert.ToDouble, ExpressionConstants.LiteralSuffixDouble, /*required*/ false) } + }; + + /// + /// Gets the literal parser to use for ETags. + /// + internal static LiteralParser ForETags + { + get + { + return DefaultInstance; + } + } + + /// + /// Gets the literal parser for keys, based on whether the keys are formatted as segments. + /// + /// Whether or not the keys is formatted as a segment. + /// The literal parser to use. + internal static LiteralParser ForKeys(bool keyAsSegment) + { + return keyAsSegment ? KeysAsSegmentsInstance : DefaultInstance; + } + + /// Converts a string to a primitive value. + /// Type to convert string to. + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + internal abstract bool TryParseLiteral(Type targetType, string text, out object result); + +#if DEBUG + /// + /// Test if the type is an ISpatial derived type + /// + /// the type to be tested + /// true if the type implements the ISpatial interface, false otherwise. + private static bool IsSpatial(Type type) + { + return typeof(ISpatial).IsAssignableFrom(type); + } +#endif + + /// + /// Default literal parser which has type-markers and single-quotes. Also supports arbitrary literals being re-encoded in binary form. + /// + private sealed class DefaultLiteralParser : LiteralParser + { + /// Converts a string to a primitive value. + /// Type to convert string to. + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + internal override bool TryParseLiteral(Type targetType, string text, out object result) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(targetType != null, "expectedType != null"); +#if DEBUG + Debug.Assert(!IsSpatial(targetType), "Not supported for spatial types, as they cannot be part of a key, etag, or skiptoken"); +#endif + + targetType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + bool binaryResult = TryRemoveFormattingAndConvert(text, typeof(byte[]), out result); + if (binaryResult) + { + byte[] byteArrayValue = (byte[])result; + if (targetType == typeof(byte[])) + { + result = byteArrayValue; + return true; + } + + // we allow arbitary values to be encoded as a base 64 array, so we may have + // found a binary value in place of another type. If so, convert it to a UTF-8 + // string and interpret it normally. + string keyValue = Encoding.UTF8.GetString(byteArrayValue, 0, byteArrayValue.Length); + return TryRemoveFormattingAndConvert(keyValue, targetType, out result); + } + + if (targetType == typeof(byte[])) + { + // if we got here, then the value was not binary. + result = null; + return false; + } + + return TryRemoveFormattingAndConvert(text, targetType, out result); + } + + /// + /// Tries to parse the literal by first removing required formatting for the expected type, then converting the resulting string. + /// + /// String text to convert. + /// Type to convert string to. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + private static bool TryRemoveFormattingAndConvert(string text, Type targetType, out object targetValue) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(targetType != null, "expectedType != null"); +#if DEBUG + Debug.Assert(!IsSpatial(targetType), "Not supported for spatial types, as they cannot be part of a key, etag, or skiptoken"); +#endif + + Debug.Assert(Parsers.ContainsKey(targetType), "Unexpected type: " + targetType); + PrimitiveParser parser = Parsers[targetType]; + if (!parser.TryRemoveFormatting(ref text)) + { + targetValue = null; + return false; + } + + return parser.TryConvert(text, out targetValue); + } + } + + /// + /// Simplified literal parser for keys-as-segments which does not expect type-markers, single-quotes, etc. Does not support re-encoding literals as binary. + /// + private sealed class KeysAsSegmentsLiteralParser : LiteralParser + { + /// Converts a string to a primitive value. + /// Type to convert string to. + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + internal override bool TryParseLiteral(Type targetType, string text, out object result) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(targetType != null, "expectedType != null"); + + text = UnescapeLeadingDollarSign(text); + + targetType = Nullable.GetUnderlyingType(targetType) ?? targetType; + +#if DEBUG + Debug.Assert(!IsSpatial(targetType), "Not supported for spatial types, as they cannot be part of a key, etag, or skiptoken"); +#endif + Debug.Assert(Parsers.ContainsKey(targetType), "Unexpected type: " + targetType); + return Parsers[targetType].TryConvert(text, out result); + } + + /// + /// If the string starts with '$', removes it. + /// Also asserts that the 2nd character is also '$', as otherwise the string would be treated as a system segment. + /// + /// The text. + /// The string value with a leading '$' removed, if the string started with one. + private static string UnescapeLeadingDollarSign(string text) + { + Debug.Assert(text != null, "text != null"); + if (text.Length > 1 && text[0] == '$') + { + Debug.Assert(text.Length > 0 && text[1] == '$', "2nd character should also be '$', otherwise it would have been treated as a system segment."); + text = text.Substring(1); + } + + return text; + } + } + + /// + /// Helper class for parsing a specific type of primitive literal. + /// + private abstract class PrimitiveParser + { + /// XML whitespace characters to trim around literals. + private static readonly char[] XmlWhitespaceChars = new char[] { ' ', '\t', '\n', '\r' }; + + /// + /// The expected prefix for the literal. Null indicates no prefix is expected. + /// + private readonly string prefix; + + /// + /// The expected suffix for the literal. Null indicates that no suffix is expected. + /// + private readonly string suffix; + + /// + /// Whether or not the suffix is required. + /// + private readonly bool suffixRequired; + + /// + /// The expected type for this parser. + /// + private readonly Type expectedType; + + /// + /// Initializes a new instance of the class. + /// + /// The expected type for this parser. + /// The expected suffix for the literal. Null indicates that no suffix is expected. + /// Whether or not the suffix is required. + protected PrimitiveParser(Type expectedType, string suffix, bool suffixRequired) + : this(expectedType) + { + Debug.Assert(suffix != null, "suffix != null"); + this.prefix = null; + this.suffix = suffix; + this.suffixRequired = suffixRequired; + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected type for this parser. + /// The expected prefix for the literal. + protected PrimitiveParser(Type expectedType, string prefix) + : this(expectedType) + { + Debug.Assert(prefix != null, "prefix != null"); + this.prefix = prefix; + this.suffix = null; + this.suffixRequired = false; + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected type for this parser. + protected PrimitiveParser(Type expectedType) + { + Debug.Assert(expectedType != null, "expectedType != null"); + this.expectedType = expectedType; + } + + /// + /// Tries to convert the given text into this parser's expected type. Conversion only, formatting should already have been removed. + /// + /// The text to convert. + /// The target value. + /// Whether or not conversion was successful. + internal abstract bool TryConvert(string text, out object targetValue); + + /// + /// Tries to remove formatting specific to this parser's expected type. + /// + /// The text to remove formatting from. + /// Whether or not the expected formatting was found and succesfully removed. + internal virtual bool TryRemoveFormatting(ref string text) + { + if (this.prefix != null) + { + if (!UriParserHelper.TryRemovePrefix(this.prefix, ref text)) + { + return false; + } + } + + bool shouldBeQuoted = this.prefix != null || ValueOfTypeCanContainQuotes(this.expectedType); + if (shouldBeQuoted && !UriParserHelper.TryRemoveQuotes(ref text)) + { + return false; + } + + if (this.suffix != null) + { + // we need to try to remove the literal even if it isn't required. + if (!TryRemoveLiteralSuffix(this.suffix, ref text) && this.suffixRequired) + { + return false; + } + } + + return true; + } + + /// + /// Check and strip the input for literal + /// + /// The suffix value + /// The string to check + /// A string that has been striped of the suffix + internal static bool TryRemoveLiteralSuffix(string suffix, ref string text) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(suffix != null, "suffix != null"); + + text = text.Trim(XmlWhitespaceChars); + if (text.Length <= suffix.Length || IsValidNumericConstant(text) || !text.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + text = text.Substring(0, text.Length - suffix.Length); + return true; + } + + /// + /// Determines whether the values for the specified types should be + /// quoted in URI keys. + /// + /// Type to check. + /// + /// true if values of require quotes; false otherwise. + /// + private static bool ValueOfTypeCanContainQuotes(Type type) + { + Debug.Assert(type != null, "type != null"); + return type == typeof(string); + } + + /// + /// Checks if text is '-INF' or 'INF' or 'NaN'. + /// + /// numeric string + /// true or false + private static bool IsValidNumericConstant(string text) + { + return string.Equals(text, ExpressionConstants.InfinityLiteral, StringComparison.OrdinalIgnoreCase) + || string.Equals(text, "-" + ExpressionConstants.InfinityLiteral, StringComparison.OrdinalIgnoreCase) + || string.Equals(text, ExpressionConstants.NaNLiteral, StringComparison.OrdinalIgnoreCase); + } + } + + /// + /// Primitive parser which uses a delegate for conversion. + /// + /// The expected CLR type when parsing. + private class DelegatingPrimitiveParser : PrimitiveParser + { + /// + /// The delegate to use for conversion. + /// + private readonly Func convertMethod; + + /// + /// Initializes a new instance of the class. + /// + /// The delegate to use for conversion. + /// The expected suffix for the literal. Null indicates that no suffix is expected. + /// Whether or not the suffix is required. + protected DelegatingPrimitiveParser(Func convertMethod, string suffix, bool suffixRequired) + : base(typeof(T), suffix, suffixRequired) + { + Debug.Assert(convertMethod != null, "convertMethod != null"); + this.convertMethod = convertMethod; + } + + /// + /// Prevents a default instance of the class from being created. + /// + /// The delegate to use for conversion. + private DelegatingPrimitiveParser(Func convertMethod) + : base(typeof(T)) + { + Debug.Assert(convertMethod != null, "convertMethod != null"); + this.convertMethod = convertMethod; + } + + /// + /// Prevents a default instance of the class from being created. + /// + /// The delegate to use for conversion. + /// The expected prefix for the literal. + private DelegatingPrimitiveParser(Func convertMethod, string prefix) + : base(typeof(T), prefix) + { + Debug.Assert(convertMethod != null, "convertMethod != null"); + this.convertMethod = convertMethod; + } + + /// + /// Creates a primitive parser which wraps the given delegate and does not expect any extra markup in serialized literal. + /// + /// The delegate to use for conversion. + /// A new primitive parser. + internal static DelegatingPrimitiveParser WithoutMarkup(Func convertMethod) + { + return new DelegatingPrimitiveParser(convertMethod); + } + + /// + /// Creates a primitive parser which wraps the given delegate and expects serialized literals to start with one of the given prefixes. + /// + /// The delegate to use for conversion. + /// The expected prefix for the literal. + /// A new primitive parser. + internal static DelegatingPrimitiveParser WithPrefix(Func convertMethod, string prefix) + { + return new DelegatingPrimitiveParser(convertMethod, prefix); + } + + /// + /// Creates a primitive parser which wraps the given delegate and expects serialized literals to end with the given suffix. + /// + /// The delegate to use for conversion. + /// The expected suffix for the literal. Null indicates that no suffix is expected. + /// A new primitive parser. + internal static DelegatingPrimitiveParser WithSuffix(Func convertMethod, string suffix) + { + return WithSuffix(convertMethod, suffix, /*required*/ true); + } + + /// + /// Creates a primitive parser which wraps the given delegate and expects serialized literals to end with the given suffix. + /// + /// The delegate to use for conversion. + /// The expected suffix for the literal. Null indicates that no suffix is expected. + /// Whether or not the suffix is required. + /// A new primitive parser. + internal static DelegatingPrimitiveParser WithSuffix(Func convertMethod, string suffix, bool required) + { + return new DelegatingPrimitiveParser(convertMethod, suffix, required); + } + + /// + /// Tries to convert the given text into this parser's expected type. Conversion only, formatting should already have been removed. + /// + /// The text to convert. + /// The target value. + /// + /// Whether or not conversion was successful. + /// + internal override bool TryConvert(string text, out object targetValue) + { + try + { + targetValue = this.convertMethod(text); + return true; + } + catch (FormatException) + { + targetValue = default(T); + return false; + } + catch (OverflowException) + { + targetValue = default(T); + return false; + } + } + } + + /// + /// Parser specific to the Edm.Decimal type. + /// + private sealed class DecimalPrimitiveParser : DelegatingPrimitiveParser + { + /// + /// Initializes a new instance of the class. + /// + internal DecimalPrimitiveParser() + : base(ConvertDecimal, ExpressionConstants.LiteralSuffixDecimal, false) + { + } + + /// + /// Special helper to convert a string to a decimal that will allow more than what XmlConvert.ToDecimal supports by default. + /// + /// The text to convert. + /// The converted decimal value. + private static Decimal ConvertDecimal(string text) + { + try + { + return XmlConvert.ToDecimal(text); + } + catch (FormatException) + { + // we need to support exponential format for decimals since we used to support them in V1 + decimal result; + if (Decimal.TryParse(text, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out result)) + { + return result; + } + + throw; + } + } + } + + /// + /// Parser specific to the Edm.Binary type. + /// + private sealed class BinaryPrimitiveParser : PrimitiveParser + { + /// + /// Initializes a new instance of the class. + /// + internal BinaryPrimitiveParser() + : base(typeof(byte[])) + { + } + + /// + /// Tries to convert the given text into this parser's expected type. Conversion only, formatting should already have been removed. + /// + /// The text to convert. + /// The target value. + /// + /// Whether or not conversion was successful. + /// + internal override bool TryConvert(string text, out object targetValue) + { + try + { + targetValue = Convert.FromBase64String(text); + } + catch (FormatException) + { + targetValue = null; + return false; + } + + return true; + } + + /// + /// Tries to remove formatting specific to this parser's expected type. + /// + /// The text to remove formatting from. + /// + /// Whether or not the expected formatting was found and succesfully removed. + /// + internal override bool TryRemoveFormatting(ref string text) + { + if (!UriParserHelper.TryRemovePrefix(ExpressionConstants.LiteralPrefixBinary, ref text)) + { + return false; + } + + if (!UriParserHelper.TryRemoveQuotes(ref text)) + { + return false; + } + + return true; + } + } + + /// + /// Parser specific to the Edm.String type. + /// + private sealed class StringPrimitiveParser : PrimitiveParser + { + /// + /// Initializes a new instance of the class. + /// + public StringPrimitiveParser() + : base(typeof(string)) + { + } + + /// + /// Tries to convert the given text into this parser's expected type. Conversion only, formatting should already have been removed. + /// + /// The text to convert. + /// The target value. + /// + /// Whether or not conversion was successful. + /// + internal override bool TryConvert(string text, out object targetValue) + { + targetValue = text; + return true; + } + + /// + /// Tries to remove formatting specific to this parser's expected type. + /// + /// The text to remove formatting from. + /// + /// Whether or not the expected formatting was found and succesfully removed. + /// + internal override bool TryRemoveFormatting(ref string text) + { + return UriParserHelper.TryRemoveQuotes(ref text); + } + } + + /// + /// Parser specific to the Edm.Date type. + /// + private sealed class DatePrimitiveParser : PrimitiveParser + { + /// + /// Initializes a new instance of the class. + /// + public DatePrimitiveParser() + : base(typeof(Date)) + { + } + + /// + /// Tries to convert the given text into this parser's expected type. Conversion only, formatting should already have been removed. + /// + /// The text to convert. + /// The target value. + /// + /// Whether or not conversion was successful. + /// + internal override bool TryConvert(string text, out object targetValue) + { + Date? date; + bool isSucceed = EdmValueParser.TryParseDate(text, out date); + targetValue = date; + return isSucceed; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/NodeFactory.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/NodeFactory.cs new file mode 100644 index 0000000..0960094 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/NodeFactory.cs @@ -0,0 +1,143 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Factory class to build IParameterQueryNodes. + /// + internal static class NodeFactory + { + /// + /// Creates a for an implicit parameter ($it) from an . + /// + /// that the range variable is iterating over. + /// A new . + internal static RangeVariable CreateImplicitRangeVariable(ODataPath path) + { + ExceptionUtils.CheckArgumentNotNull(path, "path"); + IEdmTypeReference elementType = path.EdmType(); + + if (elementType == null) + { + // This case if for something like a void service operation + // This is pretty ugly; if pratice we shouldn't be creating a parameter node for this case I think + return null; + } + + if (elementType.IsCollection()) + { + elementType = elementType.AsCollection().ElementType(); + } + + if (elementType.IsStructured()) + { + IEdmStructuredTypeReference structuredTypeReference = elementType.AsStructured(); + return new ResourceRangeVariable(ExpressionConstants.It, structuredTypeReference, path.NavigationSource()); + } + + return new NonResourceRangeVariable(ExpressionConstants.It, elementType, null); + } + + /// + /// Creates a ParameterQueryNode for an implicit parameter ($it). + /// + /// Element type the parameter represents. + /// The navigation source. May be null and must be null for non structured types. + /// A new IParameterNode. + internal static RangeVariable CreateImplicitRangeVariable(IEdmTypeReference elementType, IEdmNavigationSource navigationSource) + { + if (elementType.IsStructured()) + { + return new ResourceRangeVariable(ExpressionConstants.It, elementType as IEdmStructuredTypeReference, navigationSource); + } + + Debug.Assert(navigationSource == null, "if the type wasn't a structured type then there should be no navigation source"); + return new NonResourceRangeVariable(ExpressionConstants.It, elementType, null); + } + + /// + /// Creates a RangeVariableReferenceNode for a given range variable + /// + /// Name of the rangeVariable. + /// A new SingleValueNode (either a Resource or NonResource RangeVariableReferenceNode. + internal static SingleValueNode CreateRangeVariableReferenceNode(RangeVariable rangeVariable) + { + if (rangeVariable.Kind == RangeVariableKind.NonResource) + { + return new NonResourceRangeVariableReferenceNode(rangeVariable.Name, (NonResourceRangeVariable)rangeVariable); + } + else + { + ResourceRangeVariable resourceRangeVariable = (ResourceRangeVariable)rangeVariable; + return new ResourceRangeVariableReferenceNode(resourceRangeVariable.Name, resourceRangeVariable); + } + } + + /// + /// Creates a ParameterQueryNode for an explicit parameter. + /// + /// Name of the parameter. + /// CollectionNode that the parameter is iterating over. + /// A new RangeVariable. + internal static RangeVariable CreateParameterNode(string parameter, CollectionNode nodeToIterateOver) + { + IEdmTypeReference elementType = nodeToIterateOver.ItemType; + + if (elementType != null && elementType.IsStructured()) + { + var collectionResourceNode = nodeToIterateOver as CollectionResourceNode; + Debug.Assert(collectionResourceNode != null, "IF the element type was structured, the node type should be a resource collection"); + return new ResourceRangeVariable(parameter, elementType as IEdmStructuredTypeReference, collectionResourceNode); + } + + return new NonResourceRangeVariable(parameter, elementType, null); + } + + /// + /// Creates an AnyNode or an AllNode from the given + /// + /// State of binding. + /// Parent node to the lambda. + /// Bound Lambda expression. + /// The new range variable being added by this lambda node. + /// Token kind. + /// A new LambdaNode bound to metadata. + internal static LambdaNode CreateLambdaNode( + BindingState state, + CollectionNode parent, + SingleValueNode lambdaExpression, + RangeVariable newRangeVariable, + QueryTokenKind queryTokenKind) + { + LambdaNode lambdaNode; + if (queryTokenKind == QueryTokenKind.Any) + { + lambdaNode = new AnyNode(new Collection(state.RangeVariables.ToList()), newRangeVariable) + { + Body = lambdaExpression, + Source = parent, + }; + } + else + { + Debug.Assert(queryTokenKind == QueryTokenKind.All, "LambdaQueryNodes must be Any or All only."); + lambdaNode = new AllNode(new Collection(state.RangeVariables.ToList()), newRangeVariable) + { + Body = lambdaExpression, + Source = parent, + }; + } + + return lambdaNode; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathFactory.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathFactory.cs new file mode 100644 index 0000000..f4ba424 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathFactory.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + + /// Translates from an IPathSegment into an ODataPath + internal static class ODataPathFactory + { + /// + /// Binds a collection of to metadata, creating a semantic ODataPath object. + /// + /// Collection of path segments. + /// The configuration to use when binding the path. + /// A semantic object to describe the path. + internal static ODataPath BindPath(ICollection segments, ODataUriParserConfiguration configuration) + { + ODataPathParser semanticPathParser = new ODataPathParser(configuration); + var intermediateSegments = semanticPathParser.ParsePath(segments); + ODataPath path = new ODataPath(intermediateSegments); + return path; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathParser.cs new file mode 100644 index 0000000..d0e9f08 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/ODataPathParser.cs @@ -0,0 +1,1653 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Metadata; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Semantic parser for the path of the request URI. + /// + internal sealed class ODataPathParser + { + /// + /// regex pattern to match a contentID + /// + internal static readonly Regex ContentIdRegex = PlatformHelper.CreateCompiled(@"^\$[0-9]+$", RegexOptions.Singleline); + + /// Empty list of strings. + private static readonly IList EmptyList = new List(); + + /// + /// The queue of segments remaining to be parsed. Should be populated and cleared out on each pass through the main path parsing loop. + /// + private readonly Queue segmentQueue = new Queue(); + + /// + /// The collection of segments that have been parsed so far. + /// + private readonly List parsedSegments = new List(); + + /// + /// The parser's current configuration. + /// + private readonly ODataUriParserConfiguration configuration; + + /// + /// Indicates that the next segment encountered must refer to something in 'metadata-space' and cannot be a key expression. + /// + private bool nextSegmentMustReferToMetadata; + + /// + /// Last navigation source that has been parsed. + /// + private IEdmNavigationSource lastNavigationSource; + + /// + /// Initializes a new instance of . + /// + /// The parser's current configuration. + internal ODataPathParser(ODataUriParserConfiguration configuration) + { + Debug.Assert(configuration != null, "configuration != null"); + + this.configuration = configuration; + } + + /// + /// Extracts the segment identifier and, if there are parenthesis in the segment, the expression in the parenthesis. + /// Will throw if identifier is not found or if the parenthesis expression is malformed. This function does not validate + /// anything and simply provides the raw text of both the identifier and parenthetical expression. + /// + /// Internal only so it can be called from tests. Should not be used outside . + /// The segment text. + /// The identifier that was found. + /// The query portion that was found. Will be null after the call if no query portion was present. + internal static void ExtractSegmentIdentifierAndParenthesisExpression(string segmentText, out string identifier, out string parenthesisExpression) + { + Debug.Assert(segmentText != null, "segment != null"); + + int parenthesisStart = segmentText.IndexOf('('); + if (parenthesisStart < 0) + { + identifier = segmentText; + parenthesisExpression = null; + } + else + { + if (segmentText[segmentText.Length - 1] != ')') + { + throw ExceptionUtil.CreateSyntaxError(); + } + + // split the string to grab the identifier and remove the parentheses + identifier = segmentText.Substring(0, parenthesisStart); + parenthesisExpression = segmentText.Substring(parenthesisStart + 1, segmentText.Length - identifier.Length - 2); + } + + // We allow a single trailing '/', which results in an empty segment. + // However System.Uri removes it, so any empty segment we see is a 404 error. + if (identifier.Length == 0) + { + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_EmptySegmentInRequestUrl); + } + } + + /// Creates an array for the given . + /// Segments to process. + /// Segment information describing the given . + internal IList ParsePath(ICollection segments) + { + Debug.Assert(segments != null, "segments != null"); + Debug.Assert(this.parsedSegments.Count == 0, "Segment storage should be empty."); + Debug.Assert(this.segmentQueue.Count == 0, "Segment queue should be empty."); + + // populate the queue that will be used to drive the rest of the algorithm. + foreach (var segment in segments) + { + this.segmentQueue.Enqueue(segment); + } + + string segmentText = null; + + try + { + while (this.TryGetNextSegmentText(out segmentText)) + { + if (this.parsedSegments.Count == 0) + { + this.CreateFirstSegment(segmentText); + } + else + { + this.CreateNextSegment(segmentText); + } + + // Keep track of last navigation source. + IEdmNavigationSource navigationSource = parsedSegments.Last().TranslateWith(new DetermineNavigationSourceTranslator()); + if (navigationSource != null) + { + lastNavigationSource = navigationSource; + } + } + } + catch (ODataUnrecognizedPathException ex) + { + ex.ParsedSegments = this.parsedSegments; + ex.CurrentSegment = segmentText; + ex.UnparsedSegments = this.segmentQueue.ToList(); + throw; + } + + List validatedSegments = CreateValidatedPathSegments(); + this.parsedSegments.Clear(); + + return validatedSegments; + } + + /// + /// Tries to find a single matching operation import for the given identifier, and parameters. + /// + /// The identifier from the URI. + /// The parenthesis expression contianing parameters, if any. + /// The configuration of the parser. + /// The parsed parameters from the parenthesis expression. + /// The single matching operation import if one could be determined. + /// Whether or not a matching operation import could be found. + private static bool TryBindingParametersAndMatchingOperationImport(string identifier, string parenthesisExpression, ODataUriParserConfiguration configuration, out ICollection boundParameters, out IEdmOperationImport matchingFunctionImport) + { + matchingFunctionImport = null; + ICollection splitParameters = null; + if (!String.IsNullOrEmpty(parenthesisExpression)) + { + if (!FunctionParameterParser.TrySplitOperationParameters(parenthesisExpression, configuration, out splitParameters)) + { + IEdmOperationImport possibleMatchingOperationImport = null; + + // Look for an overload that returns an entity collection by the specified name. If so parthensis is just key parameters. + if (FunctionOverloadResolver.ResolveOperationImportFromList(identifier, EmptyList, configuration.Model, out possibleMatchingOperationImport, configuration.Resolver)) + { + IEdmCollectionTypeReference collectionReturnType = possibleMatchingOperationImport.Operation.ReturnType as IEdmCollectionTypeReference; + if (collectionReturnType != null && collectionReturnType.ElementType().IsEntity()) + { + matchingFunctionImport = possibleMatchingOperationImport; + boundParameters = null; + return true; + } + else + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); + } + } + + boundParameters = null; + return false; + } + } + else + { + splitParameters = new Collection(); + } + + // Resolve the specific overload. + if (FunctionOverloadResolver.ResolveOperationImportFromList(identifier, splitParameters.Select(k => k.ParameterName).ToList(), configuration.Model, out matchingFunctionImport, configuration.Resolver)) + { + var matchingOperation = matchingFunctionImport.Operation; + boundParameters = FunctionCallBinder.BindSegmentParameters(configuration, matchingOperation, splitParameters); + return true; + } + + boundParameters = null; + return false; + } + + /// + /// Tries to find a single matching operation import for the given identifier, parametes, and binding type. + /// + /// The identifier from the URI. + /// The parenthesis expression contianing parameters, if any. + /// The current binding type or null if there isn't one. + /// The configuration of the parser. + /// The parsed parameters from the parenthesis expression. + /// The single matching operation import if one could be determined. + /// Whether or not a matching operation import could be found. + private static bool TryBindingParametersAndMatchingOperation(string identifier, string parenthesisExpression, IEdmType bindingType, ODataUriParserConfiguration configuration, out ICollection boundParameters, out IEdmOperation matchingOperation) + { + // If the name isn't fully qualified then it can't be a function or action. + // When using extension, there may be function call with unqualified name. So loose the restriction here. + if (identifier != null && identifier.IndexOf(".", StringComparison.Ordinal) == -1 && configuration.Resolver.GetType() == typeof(ODataUriResolver)) + { + boundParameters = null; + matchingOperation = null; + return false; + } + + // TODO: update code that is duplicate between operation and operation import, add more tests. + matchingOperation = null; + ICollection splitParameters; + if (!String.IsNullOrEmpty(parenthesisExpression)) + { + if (!FunctionParameterParser.TrySplitOperationParameters(parenthesisExpression, configuration, out splitParameters)) + { + IEdmOperation possibleMatchingOperation = null; + + // Look for an overload that returns an entity collection by the specified name. If so parthensis is just key parameters. + if (FunctionOverloadResolver.ResolveOperationFromList(identifier, new List(), bindingType, configuration.Model, out possibleMatchingOperation, configuration.Resolver)) + { + IEdmCollectionTypeReference collectionReturnType = possibleMatchingOperation.ReturnType as IEdmCollectionTypeReference; + if (collectionReturnType != null && collectionReturnType.ElementType().IsEntity()) + { + matchingOperation = possibleMatchingOperation; + boundParameters = null; + return true; + } + else + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier)); + } + } + + boundParameters = null; + return false; + } + } + else + { + splitParameters = new Collection(); + } + + // Resolve the specific overload. + if (FunctionOverloadResolver.ResolveOperationFromList(identifier, splitParameters.Select(k => k.ParameterName).ToList(), bindingType, configuration.Model, out matchingOperation, configuration.Resolver)) + { + boundParameters = FunctionCallBinder.BindSegmentParameters(configuration, matchingOperation, splitParameters); + return true; + } + + boundParameters = null; + return false; + } + + /// + /// Checks for single result, otherwise throws. + /// + /// indicates whether the current result is single result or not. + /// current segment identifier. + private static void CheckSingleResult(bool isSingleResult, string identifier) + { + if (!isSingleResult) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_CannotQueryCollections(identifier)); + } + } + + /// + /// Tries to get the next segment's text to parse. + /// + /// The segment text to parse. + /// Whether there was a next segment. + private bool TryGetNextSegmentText(out string segmentText) + { + return TryGetNextSegmentText(false, out segmentText); + } + + /// + /// Tries to get the next segment's text to parse. Should not be called except by the other overload which does not have the extra parameter. + /// + /// Whether the previous segment was an escape marker. + /// The segment text to parse. + /// Whether there was a next segment. + private bool TryGetNextSegmentText(bool previousSegmentWasEscapeMarker, out string segmentText) + { + if (this.segmentQueue.Count == 0) + { + segmentText = null; + return false; + } + + segmentText = this.segmentQueue.Dequeue(); + + // If this segment is the special escape-marker segment, then remember that the next segment cannot be a key, + // even if we are in key-as-segments mode. Essentially, it is an escape into 'metadata-space', so to speak. + // + // DEVNOTE (mmeehan): We went back and forth several times on whether this should be allowed everywhere or only + // where a key could appear. We landed on allowing it absolutely everywhere for several reasons: + // 1) The WCF DS client naively adds the escape marker before all type segments, regardless of whether the + // prior segment is a collection. + // 2) The WCF DS server already allowed the escape marker almost everywhere in 5.3 + // 3) It's better to be either extremely loose or extremely strict than allow it in some cases and not in others. + // Note that this is not publicly documented in OData V3 nor is it planned to be documented in OData V4, but it + // is a part of supporting the Key-As-Segment conventions that are used by many Azure services. + if (segmentText == "$") + { + this.nextSegmentMustReferToMetadata = true; + return TryGetNextSegmentText(true, out segmentText); + } + + if (!previousSegmentWasEscapeMarker) + { + this.nextSegmentMustReferToMetadata = false; + } + + if (this.parsedSegments.Count > 0) + { + ThrowIfMustBeLeafSegment(this.parsedSegments[this.parsedSegments.Count - 1]); + } + + return true; + } + + /// + /// Tries to handle the given text as a key if the URL conventions support it and it was not preceeded by an escape segment. + /// + /// The text which might be a key. + /// Whether or not the text was handled as a key. + private bool TryHandleAsKeySegment(string segmentText) + { + ODataPathSegment previous = this.parsedSegments[this.parsedSegments.Count - 1]; + KeySegment previousKeySegment = this.FindPreviousKeySegment(); + + KeySegment keySegment; + if (!this.nextSegmentMustReferToMetadata && SegmentKeyHandler.TryHandleSegmentAsKey(segmentText, previous, previousKeySegment, this.configuration.UrlKeyDelimiter, this.configuration.Resolver, out keySegment, this.configuration.EnableUriTemplateParsing)) + { + this.parsedSegments.Add(keySegment); + return true; + } + + return false; + } + + /// + /// Find the ParentNode's key segment + /// + /// The parent nodes key segment. + private KeySegment FindPreviousKeySegment() + { + return (KeySegment)this.parsedSegments.LastOrDefault(s => s is KeySegment); + } + + /// + /// Throws if the given segment must be a leaf, as a later segment is being created. + /// + /// The previous segment which may need to be a leaf. + private static void ThrowIfMustBeLeafSegment(ODataPathSegment previous) + { + OperationImportSegment operationImportSegment = previous as OperationImportSegment; + if (operationImportSegment != null) + { + foreach (var operationImport in operationImportSegment.OperationImports) + { + if (operationImport.IsActionImport() || (operationImport.IsFunctionImport() && !((IEdmFunctionImport)operationImport).Function.IsComposable)) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_MustBeLeafSegment(previous.Identifier)); + } + } + } + + OperationSegment operationSegment = previous as OperationSegment; + if (operationSegment != null) + { + foreach (var operation in operationSegment.Operations) + { + if (operation.IsAction() || (operation.IsFunction() && !((IEdmFunction)operation).IsComposable)) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_MustBeLeafSegment(previous.Identifier)); + } + } + } + + if (previous.TargetKind == RequestTargetKind.Batch /* $batch */ + || previous.TargetKind == RequestTargetKind.Metadata /* $metadata */ + || previous.TargetKind == RequestTargetKind.PrimitiveValue /* $value, see TryCreateValueSegment */ + || previous.TargetKind == RequestTargetKind.DynamicValue /* $value, see TryCreateValueSegment */ + || previous.TargetKind == RequestTargetKind.EnumValue /* $value, see TryCreateValueSegment */ + || previous.TargetKind == RequestTargetKind.MediaResource /* $value or Media resource, see TryCreateValueSegment/CreateNamedStreamSegment */ + || previous.TargetKind == RequestTargetKind.VoidOperation /* service operation with void return type */ + || previous.TargetKind == RequestTargetKind.Nothing /* Nothing targeted (e.g. PathTemplate) */) + { + // Nothing can come after a $metadata, $value or $batch segment. + // Nothing can come after a service operation with void return type. + // Nothing can come after a collection property. + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_MustBeLeafSegment(previous.Identifier)); + } + } + + /// + /// Try to handle the segment as $count. + /// + /// The identifier that was parsed from this raw segment. + /// The query portion was parsed from this raw segment. + /// This value can be null if there is no query portion. + /// Whether the segment was $count. + private bool TryCreateCountSegment(string identifier, string parenthesisExpression) + { + if (!IdentifierIs(UriQueryConstants.CountSegment, identifier)) + { + return false; + } + + // The server used to allow arbitrary key expressions after $count because this check was missing. + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + ODataPathSegment previous = this.parsedSegments[this.parsedSegments.Count - 1]; + if ((previous.TargetKind != RequestTargetKind.Resource || previous.SingleResult) && previous.TargetKind != RequestTargetKind.Collection) + { + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_CountNotSupported(previous.Identifier)); + } + + this.parsedSegments.Add(CountSegment.Instance); + return true; + } + + /// + /// Creates a filter clause from a navigation source and filter expression. + /// + /// Navigation source to which the filter refers. + /// Target type for the entity being referenced. + /// Filter expression. + /// Filter clause with the navigation source and filter information. + private FilterClause GenerateFilterClause(IEdmNavigationSource navigationSource, IEdmType targetEdmType, string filter) + { + Debug.Assert(navigationSource != null, "navigationSource != null"); + Debug.Assert(targetEdmType != null, "targetEdmType != null"); + Debug.Assert(filter.Length > 0, "filter.Length > 0"); + + ODataPathInfo currentOdataPathInfo = new ODataPathInfo(targetEdmType, navigationSource); + + // Get the syntactic representation of the filter expression. + UriQueryExpressionParser expressionParser = new UriQueryExpressionParser( + configuration.Settings.FilterLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier); + QueryToken filterToken = expressionParser.ParseFilter(filter); + + // Bind it to metadata. + BindingState state = new BindingState(configuration, currentOdataPathInfo.Segments.ToList()) + { + ImplicitRangeVariable = NodeFactory.CreateImplicitRangeVariable( + currentOdataPathInfo.TargetEdmType.ToTypeReference(), currentOdataPathInfo.TargetNavigationSource) + }; + state.RangeVariables.Push(state.ImplicitRangeVariable); + + MetadataBinder binder = new MetadataBinder(state); + FilterBinder filterBinder = new FilterBinder(binder.Bind, state); + + return filterBinder.BindFilter(filterToken); + } + + /// + /// Try to handle the segment as $filter. + /// + /// The raw segment text. + /// Whether the segment was $filter. + /// + /// $filter path segment is different from existing path segments in that it strictly + /// follows the format of "$filter(expression)", expression could be an alias or inline expression + /// that resolves to a boolean. Thus, this function should validate the format of the path + /// segment closely. + /// + private bool TryCreateFilterSegment(string segmentText) + { + Debug.Assert(segmentText != null, "segmentText != null"); + Debug.Assert(parsedSegments.Count > 0, "parsedSegments.Count > 0"); + + /* + * 1) Check whether the path segment starts with $filter. + * 2) Ensure that the expression that follows the identifier is enclosed in parentheses. + * 3) Extract the expression and validate it syntactically. + * 4) Add the filter segment to list of parsed segments. + */ + + // 1) Check whether the path segment starts with $filter. Past this point, we will throw invalid syntax exceptions + // because we will assume that the user is attempting to use the $filter path segment. + if (!segmentText.StartsWith( + UriQueryConstants.FilterSegment, + this.configuration.EnableCaseInsensitiveUriFunctionIdentifier ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) + { + return false; + } + + // 2) The expression that follows UriQueryConstants.FilterSegment should be enclosed in parentheses. + // Step 3) performs the expression validation (e.g. illegal characters). + // - the length of this segment should be longer than "$filter()", indicating that there's a valid expression + int index = UriQueryConstants.FilterSegment.Length; + if (segmentText.Length <= index + 2 || segmentText[index] != '(' || segmentText[segmentText.Length - 1] != ')') + { + throw new ODataException(ODataErrorStrings.RequestUriProcessor_FilterPathSegmentSyntaxError); + } + + // 3) Extract the expression and perform the rest of the validations on it. + if (lastNavigationSource == null) + { + throw new ODataException(ODataErrorStrings.RequestUriProcessor_NoNavigationSourceFound(UriQueryConstants.FilterSegment)); + } + + if (lastNavigationSource is IEdmSingleton || this.parsedSegments.Last() is KeySegment) + { + throw new ODataException(ODataErrorStrings.RequestUriProcessor_CannotApplyFilterOnSingleEntities(lastNavigationSource.Name)); + } + + // The "index + 1" is to move past the '(' and the '-2' accounts for the two paren characters. + string filterExpression = segmentText.Substring(index + 1, segmentText.Length - UriQueryConstants.FilterSegment.Length - 2); + + // If the previous segment is a type segment, then the entity set has been casted and the filter expression should reflect the cast. + TypeSegment typeSegment = this.parsedSegments.Last() as TypeSegment; + + // Creating a filter clause helps validate the expression and create the expression nodes (including nested parameter aliases). + FilterClause filterClause = GenerateFilterClause( + lastNavigationSource, + typeSegment == null ? lastNavigationSource.EntityType() : typeSegment.TargetEdmType, + filterExpression); + + // 4) Create filter segment with the validated expression and add it to parsed segments. + FilterSegment filterSegment = new FilterSegment(filterClause.Expression, filterClause.RangeVariable, lastNavigationSource); + this.parsedSegments.Add(filterSegment); + + return true; + } + + /// + /// Try to handle the segment as $each. + /// + /// The identifier that was parsed from this raw segment. + /// The query portion was parsed from this raw segment. + /// This value can be null if there is no query portion. + /// Whether the segment was $each. + private bool TryCreateEachSegment(string identifier, string parenthesisExpression) + { + if (!IdentifierIs(UriQueryConstants.EachSegment, identifier)) + { + return false; + } + + // $each is not supposed to have parenthesis expressions after it. + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + ODataPathSegment prevSegment = this.parsedSegments.Last(); + if (lastNavigationSource == null) + { + throw new ODataException(ODataErrorStrings.RequestUriProcessor_NoNavigationSourceFound(UriQueryConstants.EachSegment)); + } + + if (lastNavigationSource is IEdmSingleton || prevSegment is KeySegment) + { + throw new ODataException(ODataErrorStrings.RequestUriProcessor_CannotApplyEachOnSingleEntities(lastNavigationSource.Name)); + } + + EachSegment eachSegment = new EachSegment(lastNavigationSource, prevSegment.TargetEdmType.AsElementType()); + this.parsedSegments.Add(eachSegment); + + return true; + } + + /// + /// Tries to handle the segment as $ref. If it is $ref, then the rest of the path will be parsed/validated in this call. + /// + /// The identifier that was parsed from this raw segment. + /// The query portion was parsed from this raw segment. + /// This value can be null if there is no query portion. + /// Whether the text was $ref. + private bool TryCreateEntityReferenceSegment(string identifier, string parenthesisExpression) + { + if (!this.IdentifierIs(UriQueryConstants.RefSegment, identifier)) + { + return false; + } + + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + // The algorithm below looks for the last NavigationPropertySegment before the $ref segment. Whether a NavPropSeg exists + // becomes two different code paths: + // 1) Backwards compatibility behavior (<= ODL 7.4.x): If the NavPropSeg exists, then it is expected to either be before + // KeySegments or before the $ref (i.e. NavPropSeg/KeySeg/KeySeg/.../$ref or NavPropSeg/$ref). Then the NavPropSeg is replaced + // with NavigationPropertyLinkSegment. In a previous implementation, if a NavPropSeg didn't exist before KeySegments or + // $ref, then this function would throw. This was not correct as $ref can apply to entity sets and similar segments + // (e.g. TypeSegment and FilterSegment), and therefore 2) below is implemented to support those scenarios. + // 2) If the NavPropSeg does not exist, then this algorithm appends a ReferenceSegment to the existing list of parsed segments. + + // Determine the index of the NavigationPropertySegment in either of the following formats: + // NavPropSeg/KeySeg/KeySeg/.../$ref or NavPropSeg/$ref + int indexOfNavPropSeg = this.parsedSegments.Count - 1; + while (indexOfNavPropSeg > 0 && this.parsedSegments[indexOfNavPropSeg] is KeySegment) + { + --indexOfNavPropSeg; + } + + NavigationPropertySegment navPropSegment = this.parsedSegments[indexOfNavPropSeg] as NavigationPropertySegment; + if (navPropSegment != null) + { + if (navPropSegment.TargetKind != RequestTargetKind.Resource) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.PathParser_EntityReferenceNotSupported(navPropSegment.Identifier)); + } + + // If this is a navigation property, find target navigation source + Debug.Assert(indexOfNavPropSeg - 1 >= 0, "indexOfNavPropSeg - 1 >= 0"); + IEdmPathExpression bindingPath; + var targetNavigationSource = this.parsedSegments[indexOfNavPropSeg - 1].TargetEdmNavigationSource.FindNavigationTarget( + navPropSegment.NavigationProperty, BindingPathHelper.MatchBindingPath, this.parsedSegments, out bindingPath); + + // If we can't compute the target navigation source, then pretend the navigation property does not exist + if (targetNavigationSource == null) + { + throw ExceptionUtil.CreateResourceNotFoundError(navPropSegment.NavigationProperty.Name); + } + + // Create new NavigationPropertyLinkSegment + var navPropLinkSegment = new NavigationPropertyLinkSegment(navPropSegment.NavigationProperty, targetNavigationSource); + + // Replace the NavigationPropertySegment with the $ref NavigationPropertyLinkSegment + this.parsedSegments[indexOfNavPropSeg] = navPropLinkSegment; + } + else + { + ODataPathSegment lastSegment = this.parsedSegments.Last(); + if (lastSegment.TargetKind != RequestTargetKind.Resource) + { + throw ExceptionUtil.CreateBadRequestError( + ODataErrorStrings.PathParser_EntityReferenceNotSupported(lastSegment.Identifier)); + } + + ReferenceSegment referenceSegment = new ReferenceSegment(lastNavigationSource); + referenceSegment.SingleResult = lastSegment.SingleResult; + this.parsedSegments.Add(referenceSegment); + } + + // Nothing is allowed after $ref. + string nextSegmentText; + if (this.TryGetNextSegmentText(out nextSegmentText)) + { + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_MustBeLeafSegment(UriQueryConstants.RefSegment)); + } + + return true; + } + + /// + /// Tries to bind a key from the parenthetical section of a segment. + /// + /// The section of the segment inside parentheses, or null if there was none. + /// Returns True if a key segment was found and added from the paratheses section otherwise false. + private bool TryBindKeyFromParentheses(string parenthesesSection) + { + if (parenthesesSection == null) + { + return false; + } + + ODataPathSegment keySegment; + ODataPathSegment previous = this.parsedSegments[this.parsedSegments.Count - 1]; + KeySegment previousKeySegment = this.FindPreviousKeySegment(); + if (!SegmentKeyHandler.TryCreateKeySegmentFromParentheses(previous, previousKeySegment, parenthesesSection, this.configuration.Resolver, out keySegment, this.configuration.EnableUriTemplateParsing)) + { + return false; + } + + this.parsedSegments.Add(keySegment); + return true; + } + + /// + /// Try to handle the segment as $value. + /// + /// The identifier that was parsed from this raw segment. + /// The query portion was parsed from this raw segment. + /// This value can be null if there is no query portion. + /// Whether the segment was $value. + private bool TryCreateValueSegment(string identifier, string parenthesisExpression) + { + if (!this.IdentifierIs(UriQueryConstants.ValueSegment, identifier)) + { + return false; + } + + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + ODataPathSegment previous = this.parsedSegments[this.parsedSegments.Count - 1]; + + ODataPathSegment segment = new ValueSegment(previous.EdmType); + if ((previous.TargetKind == RequestTargetKind.Primitive) + || (previous.TargetKind == RequestTargetKind.Enum)) + { + segment.CopyValuesFrom(previous); + } + else + { + segment.TargetEdmType = previous.TargetEdmType; + } + + segment.Identifier = UriQueryConstants.ValueSegment; + segment.SingleResult = true; + CheckSingleResult(previous.SingleResult, previous.Identifier); + + if (previous.TargetKind == RequestTargetKind.Primitive) + { + segment.TargetKind = RequestTargetKind.PrimitiveValue; + } + else if (previous.TargetKind == RequestTargetKind.Enum) + { + segment.TargetKind = RequestTargetKind.EnumValue; + } + else if (previous.TargetKind == RequestTargetKind.Dynamic) + { + segment.TargetKind = RequestTargetKind.DynamicValue; + } + else + { + // If the previous segment is an entity, we expect it to be an MLE. We cannot validate our assumption + // until later when we get the actual instance of the entity because the type hierarchy can contain + // a mix of MLE and non-MLE types. + segment.TargetKind = RequestTargetKind.MediaResource; + } + + this.parsedSegments.Add(segment); + return true; + } + + /// + /// Creates a new segment for an unknown path segment or an open property. + /// + /// previous segment info. + /// name of the segment. + /// whether this segment has a query portion or not. + private void CreateDynamicPathSegment(ODataPathSegment previous, string identifier, string parenthesisExpression) + { + if (this.configuration.ParseDynamicPathSegmentFunc != null) + { + var segments = this.configuration.ParseDynamicPathSegmentFunc(previous, identifier, parenthesisExpression); + this.parsedSegments.AddRange(segments); + return; + } + + if (previous == null) + { + throw ExceptionUtil.CreateResourceNotFoundError(identifier); + } + + CheckSingleResult(previous.SingleResult, previous.Identifier); + + // Handle an open type property. If the current leaf isn't an + // object (which implies it's already an open type), then + // it should be marked as an open type. + if ((previous.TargetEdmType != null && !previous.TargetEdmType.IsOpen())) + { + throw ExceptionUtil.CreateResourceNotFoundError(identifier); + } + + // Open navigation properties are not supported on OpenTypes. + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.OpenNavigationPropertiesNotSupportedOnOpenTypes(identifier)); + } + + ODataPathSegment segment = new DynamicPathSegment(identifier); + this.parsedSegments.Add(segment); + } + + /// + /// Creates a named stream segment + /// + /// previous segment info. + /// stream property to create the segment for. + private void CreateNamedStreamSegment(ODataPathSegment previous, IEdmProperty streamProperty) + { + Debug.Assert(streamProperty.Type.IsStream(), "streamProperty.Type.IsStream()"); + + // Handle Named Stream. + ODataPathSegment segment = new PropertySegment((IEdmStructuralProperty)streamProperty); + segment.TargetKind = RequestTargetKind.MediaResource; + segment.SingleResult = true; + segment.TargetEdmType = previous.TargetEdmType; + Debug.Assert(segment.Identifier != UriQueryConstants.ValueSegment, "'$value' cannot be the name of a named stream."); + + this.parsedSegments.Add(segment); + } + + /// Creates the first for a request. + /// The text of the segment. + private void CreateFirstSegment(string segmentText) + { + string identifier; + string parenthesisExpression; + ExtractSegmentIdentifierAndParenthesisExpression(segmentText, out identifier, out parenthesisExpression); + + Debug.Assert(identifier != null, "identifier != null"); + + // Look for well-known system resource points. + if (this.IdentifierIs(UriQueryConstants.MetadataSegment, identifier)) + { + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + this.parsedSegments.Add(MetadataSegment.Instance); + return; + } + + if (this.IdentifierIs(UriQueryConstants.BatchSegment, identifier)) + { + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + this.parsedSegments.Add(BatchSegment.Instance); + return; + } + + if (this.IdentifierIs(UriQueryConstants.CountSegment, identifier)) + { + // $count on root: throw + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_CountOnRoot); + } + + if (this.IdentifierIs(UriQueryConstants.FilterSegment, identifier)) + { + // $filter on root: throw + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_FilterOnRoot); + } + + if (this.IdentifierIs(UriQueryConstants.EachSegment, identifier)) + { + // $each on root: throw + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_EachOnRoot); + } + + if (this.IdentifierIs(UriQueryConstants.RefSegment, identifier)) + { + // $ref on root: throw + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_RefOnRoot); + } + + if (this.configuration.BatchReferenceCallback != null && ContentIdRegex.IsMatch(identifier)) + { + if (parenthesisExpression != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + BatchReferenceSegment crossReferencedSegement = this.configuration.BatchReferenceCallback(identifier); + if (crossReferencedSegement != null) + { + this.parsedSegments.Add(crossReferencedSegement); + return; + } + } + + if (this.TryCreateSegmentForNavigationSource(identifier, parenthesisExpression)) + { + return; + } + + if (this.TryCreateSegmentForOperationImport(identifier, parenthesisExpression)) + { + return; + } + + this.CreateDynamicPathSegment(null, identifier, parenthesisExpression); + } + + /// + /// Tries to parse a segment as an entity set or singleton. + /// + /// The name of the segment + /// The parenthesis expression + /// Whether or not the identifier referred to an entity set or singleton. + private bool TryCreateSegmentForNavigationSource(string identifier, string parenthesisExpression) + { + ODataPathSegment segment = null; + IEdmEntitySet targetEdmEntitySet; + IEdmSingleton targetEdmSingleton; + + IEdmNavigationSource source = this.configuration.Resolver.ResolveNavigationSource(this.configuration.Model, identifier); + + if ((targetEdmEntitySet = source as IEdmEntitySet) != null) + { + segment = new EntitySetSegment(targetEdmEntitySet) { Identifier = identifier }; + } + else if ((targetEdmSingleton = source as IEdmSingleton) != null) + { + segment = new SingletonSegment(targetEdmSingleton) { Identifier = identifier }; + } + + if (segment != null) + { + this.parsedSegments.Add(segment); + this.TryBindKeyFromParentheses(parenthesisExpression); + return true; + } + + return false; + } + + /// + /// Tries to parse a segment as a functionImport or actionImport. + /// + /// The name of the segment + /// The query portion + /// Whether or not the identifier referred to an action. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "IEdmModel", Justification = "The spelling is correct.")] + private bool TryCreateSegmentForOperationImport(string identifier, string parenthesisExpression) + { + ICollection resolvedParameters; + IEdmOperationImport singleImport; + if (!TryBindingParametersAndMatchingOperationImport(identifier, parenthesisExpression, this.configuration, out resolvedParameters, out singleImport)) + { + return false; + } + + IEdmTypeReference returnType = singleImport.Operation.ReturnType; + IEdmEntitySetBase targetset = null; + + if (returnType != null) + { + targetset = singleImport.GetTargetEntitySet(null, this.configuration.Model); + } + + ODataPathSegment segment = new OperationImportSegment(singleImport, targetset, resolvedParameters); + + this.parsedSegments.Add(segment); + + this.TryBindKeySegmentIfNoResolvedParametersAndParathesisValueExsts(parenthesisExpression, returnType, resolvedParameters, segment); + + return true; + } + + /// + /// Tries the bind key segment if no resolved parameters and parathesis value exsts. + /// + /// The parenthesis expression. + /// Type of the return. + /// The resolved parameters. + /// The segment. + private void TryBindKeySegmentIfNoResolvedParametersAndParathesisValueExsts(string parenthesisExpression, IEdmTypeReference returnType, ICollection resolvedParameters, ODataPathSegment segment) + { + IEdmCollectionTypeReference collectionTypeReference = returnType as IEdmCollectionTypeReference; + if (collectionTypeReference != null && collectionTypeReference.ElementType().IsEntity() && resolvedParameters == null && parenthesisExpression != null) + { + // The parameters in the parathesis is a key segment. + if (this.TryBindKeyFromParentheses(parenthesisExpression)) + { + ThrowIfMustBeLeafSegment(segment); + } + } + } + + /// + /// Tries to parse a segment as a function or action. + /// + /// The previous segment before the operation to be invoked. + /// The name of the segment + /// The query portion + /// Whether or not the identifier referred to an action. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "IEdmModel", Justification = "The spelling is correct.")] + private bool TryCreateSegmentForOperation(ODataPathSegment previousSegment, string identifier, string parenthesisExpression) + { + // Parse Arguments syntactically + IEdmType bindingType = null; + if (previousSegment != null) + { + // Use TargetEdmType for EachSegment to represent a pseudo-single entity. + bindingType = (previousSegment is EachSegment) ? previousSegment.TargetEdmType : previousSegment.EdmType; + } + + if (!String.IsNullOrEmpty(identifier) && identifier[0] == ':' && bindingType != null) + { + identifier = ResolveEscapeFunction(identifier, bindingType, configuration.Model, out parenthesisExpression); + } + + ICollection resolvedParameters; + IEdmOperation singleOperation; + if (!TryBindingParametersAndMatchingOperation(identifier, parenthesisExpression, bindingType, this.configuration, out resolvedParameters, out singleOperation)) + { + return false; + } + + if (!UriEdmHelpers.IsBindingTypeValid(bindingType)) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_OperationSegmentBoundToANonEntityType); + } + + if (previousSegment != null && bindingType == null) + { + throw new ODataException(ODataErrorStrings.FunctionCallBinder_CallingFunctionOnOpenProperty(identifier)); + } + + IEdmTypeReference returnType = singleOperation.ReturnType; + IEdmEntitySetBase targetset = null; + + if (returnType != null) + { + IEdmNavigationSource source = previousSegment == null ? null : previousSegment.TargetEdmNavigationSource; + targetset = singleOperation.GetTargetEntitySet(source, this.configuration.Model); + } + + // If previous segment is cross-referenced then we explicitly dissallow the service action call + if (previousSegment is BatchReferenceSegment) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_BatchedActionOnEntityCreatedInSameChangeset(identifier)); + } + + CheckOperationTypeCastSegmentRestriction(singleOperation); + + ODataPathSegment segment = new OperationSegment(singleOperation, resolvedParameters, targetset) + { + Identifier = identifier + }; + + this.parsedSegments.Add(segment); + + this.TryBindKeySegmentIfNoResolvedParametersAndParathesisValueExsts(parenthesisExpression, returnType, resolvedParameters, segment); + + return true; + } + + /// + /// Creates the next segment. + /// + /// The text for the next segment. + private void CreateNextSegment(string text) + { + string identifier; + string parenthesisExpression; + ExtractSegmentIdentifierAndParenthesisExpression(text, out identifier, out parenthesisExpression); + + /* + * For Non-KeyAsSegment, try to handle it as a key property value, unless it was preceeded by an excape - marker segment('$'). + * For KeyAsSegment, the following precedence rules should be supported[ODATA - 799]: + * Try to match an OData segment(starting with $). + * - Note: $filter path segment is a special case that has the format "$filter(@a)", where @a represents an alias. + * Try to match an alias - qualified bound action name, bound function overload, or type name. + * Try to match a namespace-qualified bound action name, bound function overload, or type name. + * Try to match an unqualified bound action name, bound function overload, or type name in a default namespace. + * Treat as a key. + */ + + // $value + if (this.TryCreateValueSegment(identifier, parenthesisExpression)) + { + return; + } + + ODataPathSegment previous = this.parsedSegments[this.parsedSegments.Count - 1]; + if (previous.TargetKind == RequestTargetKind.Primitive) + { + // only $value is allowed after a primitive property + throw ExceptionUtil.ResourceNotFoundError(ODataErrorStrings.RequestUriProcessor_ValueSegmentAfterScalarPropertySegment(previous.Identifier, text)); + } + + // $ref + if (this.TryCreateEntityReferenceSegment(identifier, parenthesisExpression)) + { + return; + } + + // $count + if (this.TryCreateCountSegment(identifier, parenthesisExpression)) + { + return; + } + + // $filter + if (this.TryCreateFilterSegment(text)) + { + return; + } + + // $each + if (this.TryCreateEachSegment(identifier, parenthesisExpression)) + { + return; + } + + // property if previous is single + if (previous.SingleResult) + { + // if its not one of the recognized special segments, then it must be a property, type-segment, or key value. + Debug.Assert( + previous.TargetKind == RequestTargetKind.Resource + || previous.TargetKind == RequestTargetKind.Dynamic, + "previous.TargetKind(" + previous.TargetKind + ") can have properties"); + + if (previous.TargetEdmType == null) + { + // A segment will correspond to a property in the object model; + // if we are processing an open type, anything further in the + // URI also represents an open type property. + Debug.Assert(previous.TargetKind == RequestTargetKind.Dynamic, "For open properties, the target resource type must be null"); + } + else + { + // if the segment corresponds to a declared property, handle it + // otherwise, fall back to type-segments, actions, and dynamic/open properties + IEdmProperty projectedProperty; + if (this.TryBindProperty(identifier, out projectedProperty)) + { + CheckSingleResult(previous.SingleResult, previous.Identifier); + this.CreatePropertySegment(previous, projectedProperty, parenthesisExpression); + return; + } + } + } + + // Type cast + if (text.IndexOf('.') >= 0 && // type-cast should use qualified type names + this.TryCreateTypeNameSegment(previous, identifier, parenthesisExpression)) + { + return; + } + + // Operation + if (this.TryCreateSegmentForOperation(previous, identifier, parenthesisExpression)) + { + return; + } + + // For KeyAsSegment, try to handle as key segment + if (this.configuration.UrlKeyDelimiter.EnableKeyAsSegment && this.TryHandleAsKeySegment(text)) + { + return; + } + + // Parse as path template segment if EnableUriTemplateParsing is enabled. + if (this.configuration.EnableUriTemplateParsing && UriTemplateParser.IsValidTemplateLiteral(text)) + { + this.parsedSegments.Add(new PathTemplateSegment(text)); + return; + } + + // Dynamic property + this.CreateDynamicPathSegment(previous, identifier, parenthesisExpression); + } + + /// + /// Tries to bind the identifier as a property. + /// + /// The identifier to bind. + /// The property, if one was found. + /// Whether a property matching the identifier was found. + private bool TryBindProperty(string identifier, out IEdmProperty projectedProperty) + { + ODataPathSegment previous = this.parsedSegments[this.parsedSegments.Count - 1]; + Debug.Assert(previous.TargetEdmType != null, "Previous wasn't open, so it should have a resource type"); + Debug.Assert(previous.TargetEdmNavigationSource == null || previous.TargetEdmType.IsStructuredOrStructuredCollectionType(), "if the previous segment has a target resource set, then its target resource type must be an entity or a complex"); + + // Note that we try resolve the property on the root entity type for the set. Properties/Name streams defined on derived types + // are not supported. This is a general problem with properties as we don't have the entity instance here to validate + // whether the property exists. + projectedProperty = null; + var structuredType = previous.TargetEdmType as IEdmStructuredType; + if (structuredType == null) + { + var collectionType = previous.TargetEdmType as IEdmCollectionType; + if (collectionType != null) + { + structuredType = collectionType.ElementType.Definition as IEdmStructuredType; + } + } + + if (structuredType == null) + { + return false; + } + + projectedProperty = this.configuration.Resolver.ResolveProperty(structuredType, identifier); + return projectedProperty != null; + } + + /// + /// Tries to create a type name segment if the given identifier refers to a known type. + /// + /// previous segment info. + /// The current raw segment identifier being interpreted. + /// Parenthesis expression of this segment. + /// Whether or not a type segment was created for the identifier. + private bool TryCreateTypeNameSegment(ODataPathSegment previous, string identifier, string parenthesisExpression) + { + IEdmType targetEdmType; + if (previous.TargetEdmType == null || (targetEdmType = UriEdmHelpers.FindTypeFromModel(this.configuration.Model, identifier, this.configuration.Resolver)) == null) + { + return false; + } + + // if the new type segment prevents any results from possibly being returned, then short-circuit and throw a 404. + IEdmType previousEdmType = previous.TargetEdmType; + Debug.Assert(previousEdmType != null, "previous.TargetEdmType != null"); + + if (previousEdmType.TypeKind == EdmTypeKind.Collection) + { + previousEdmType = ((IEdmCollectionType)previousEdmType).ElementType.Definition; + } + + if (!targetEdmType.IsOrInheritsFrom(previousEdmType) && !previousEdmType.IsOrInheritsFrom(targetEdmType)) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_InvalidTypeIdentifier_UnrelatedType(targetEdmType.FullTypeName(), previousEdmType.FullTypeName())); + } + + CheckTypeCastSegmentRestriction(previous, targetEdmType); + + // We want the type of the type segment to be a collection if the previous segment was a collection + IEdmType actualTypeOfTheTypeSegment = targetEdmType; + + if (previous.EdmType.TypeKind == EdmTypeKind.Collection) + { + // creating a new collection type here because the type in the request is just the item type, there is no user-provided collection type. + var actualEntityTypeOfTheTypeSegment = actualTypeOfTheTypeSegment as IEdmEntityType; + if (actualEntityTypeOfTheTypeSegment != null) + { + actualTypeOfTheTypeSegment = new EdmCollectionType(new EdmEntityTypeReference(actualEntityTypeOfTheTypeSegment, false)); + } + else + { + // Complex collection supports type cast too. + var actualComplexTypeOfTheTypeSegment = actualTypeOfTheTypeSegment as IEdmComplexType; + if (actualComplexTypeOfTheTypeSegment != null) + { + actualTypeOfTheTypeSegment = new EdmCollectionType(new EdmComplexTypeReference(actualComplexTypeOfTheTypeSegment, false)); + } + else + { + throw new ODataException(Strings.PathParser_TypeCastOnlyAllowedAfterStructuralCollection(identifier)); + } + } + } + + var typeNameSegment = (ODataPathSegment)new TypeSegment(actualTypeOfTheTypeSegment, previous.EdmType, previous.TargetEdmNavigationSource) + { + Identifier = identifier, + TargetKind = previous.TargetKind, + SingleResult = previous.SingleResult, + TargetEdmType = targetEdmType + }; + + this.parsedSegments.Add(typeNameSegment); + + // Key expressions are allowed on Type segments + this.TryBindKeyFromParentheses(parenthesisExpression); + + return true; + } + + /// + /// Creates a property segment + /// + /// previous segment info. + /// property to create the segment for. + /// query portion for this segment, if specified. + [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "IEdmModel", Justification = "The spelling is correct.")] + private void CreatePropertySegment(ODataPathSegment previous, IEdmProperty property, string queryPortion) + { + Debug.Assert(previous != null, "previous != null"); + + if (property.Type.IsStream()) + { + // The server used to allow arbitrary key expressions after named streams because this check was missing. + if (queryPortion != null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + this.CreateNamedStreamSegment(previous, property); + return; + } + + // Handle a strongly-typed property. + ODataPathSegment segment = null; + + if (property.PropertyKind == EdmPropertyKind.Navigation) + { + var navigationProperty = (IEdmNavigationProperty)property; + + IEdmNavigationSource navigationSource = null; + if (previous.TargetEdmNavigationSource != null) + { + IEdmPathExpression bindingPath; + navigationSource = previous.TargetEdmNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, this.parsedSegments, out bindingPath); + } + + // Relationship between TargetMultiplicity and navigation property: + // 1) EdmMultiplicity.Many <=> collection navigation property + // 2) EdmMultiplicity.ZeroOrOne <=> nullable singleton navigation property + // 3) EdmMultiplicity.One <=> non-nullable singleton navigation property + // + // According to OData Spec CSDL 7.1.3: + // 1) non-nullable singleton navigation property => navigation source required + // 2) the other cases => navigation source optional + if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.One + && navigationSource is IEdmUnknownEntitySet) + { + // Specifically not throwing ODataUriParserException since it's more an an internal server error + throw new ODataException(ODataErrorStrings.RequestUriProcessor_TargetEntitySetNotFound(property.Name)); + } + + segment = new NavigationPropertySegment(navigationProperty, navigationSource); + } + else + { + segment = new PropertySegment((IEdmStructuralProperty)property); + switch (property.Type.TypeKind()) + { + case EdmTypeKind.Complex: + segment.TargetKind = RequestTargetKind.Resource; + segment.TargetEdmNavigationSource = previous.TargetEdmNavigationSource; + break; + case EdmTypeKind.Collection: + if (property.Type.IsStructuredCollectionType()) + { + segment.TargetKind = RequestTargetKind.Resource; + segment.TargetEdmNavigationSource = previous.TargetEdmNavigationSource; + } + + segment.TargetKind = RequestTargetKind.Collection; + break; + case EdmTypeKind.Enum: + segment.TargetKind = RequestTargetKind.Enum; + break; + default: + Debug.Assert(property.Type.IsPrimitive() || property.Type.IsTypeDefinition(), "must be primitive type or type definition property"); + segment.TargetKind = RequestTargetKind.Primitive; + break; + } + } + + this.parsedSegments.Add(segment); + + if (!(queryPortion == null || property.Type.IsCollection() && property.Type.AsCollection().ElementType().IsEntity())) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + this.TryBindKeyFromParentheses(queryPortion); + } + + /// + /// Check whether identifiers matches according to case in sensitive option. + /// + /// The expected identifer. + /// Identifier to be evaluated. + /// Whether the identifier matches. + private bool IdentifierIs(string expected, string identifier) + { + return string.Equals( + expected, + identifier, + this.configuration.EnableCaseInsensitiveUriFunctionIdentifier ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + } + + /// + /// Validates the existing parsed segments and returns a list of validated segments. + /// + /// List of validated path segments. + private List CreateValidatedPathSegments() + { + List validatedSegments = new List(this.parsedSegments.Count); + for (int index = 0, segmentCount = this.parsedSegments.Count; index < segmentCount; ++index) + { + CheckDollarEachSegmentRestrictions(index); +#if DEBUG + this.parsedSegments[index].AssertValid(); +#endif + validatedSegments.Add(this.parsedSegments[index]); + } + + return validatedSegments; + } + + /// + /// Per OData 4.01 spec, only one operation may follow $each. This function enforces that restriction. + /// + /// Index of path segment to examine in the list of parsed segments. + /// Throws if there's a violation of $each restrictions. + /// Should the restrictions on the $each be removed, this function can be deleted. + private void CheckDollarEachSegmentRestrictions(int index) + { + Debug.Assert(index < this.parsedSegments.Count, "index < this.parsedSegments.Count"); + + int numOfSegmentsAfterDollarEach = this.parsedSegments.Count - index - 1; + + // Perform restriction checks only if the current segment being examined is $each and there are subsequent segments. + if (this.parsedSegments[index] is EachSegment && numOfSegmentsAfterDollarEach > 0) + { + // Only one segment is allowed after $each... + if (numOfSegmentsAfterDollarEach > 1) + { + throw new ODataException(ODataErrorStrings.RequestUriProcessor_OnlySingleOperationCanFollowEachPathSegment); + } + + // And if there exists a single segment after $each, then it must be an OperationSegment. + if (!(this.parsedSegments[index + 1] is OperationSegment)) + { + throw new ODataException(ODataErrorStrings.RequestUriProcessor_OnlySingleOperationCanFollowEachPathSegment); + } + } + } + + private void CheckTypeCastSegmentRestriction(ODataPathSegment previous, IEdmType targetEdmType) + { + Debug.Assert(previous != null); + Debug.Assert(targetEdmType != null); + + // Make sure: cast to itself can pass the validation. + IEdmType previousTargetEdmType = previous.TargetEdmType.AsElementType(); + if (previousTargetEdmType == targetEdmType) + { + return; + } + + string fullTypeName = targetEdmType.FullTypeName(); + + // Singleton, for example: ~/Me/NS.Cast + SingletonSegment singletonSegment = previous as SingletonSegment; + if (singletonSegment != null) + { + VerifyDerivedTypeConstraints(this.configuration.Model, singletonSegment.Singleton, fullTypeName, "singleton", singletonSegment.Singleton.Name); + return; + } + + // EntitySet, for example: ~/Users/NS.Cast + EntitySetSegment entitySetSegment = previous as EntitySetSegment; + if (entitySetSegment != null) + { + VerifyDerivedTypeConstraints(this.configuration.Model, entitySetSegment.EntitySet, fullTypeName, "entity set", entitySetSegment.EntitySet.Name); + return; + } + + // EntitySet with key, for example: ~/Users(1)/NS.Cast + // Or Navigation Property with key ~/Users(1)/Orders(2)/NS.Cast + NavigationPropertySegment navigationPropertySegment; + KeySegment keySegment = previous as KeySegment; + if (keySegment != null) + { + ODataPathSegment previousPrevious = this.parsedSegments[this.parsedSegments.Count - 2]; // -2 means skip the "KeySegment" + entitySetSegment = previousPrevious as EntitySetSegment; + navigationPropertySegment = previousPrevious as NavigationPropertySegment; + if (entitySetSegment != null || navigationPropertySegment != null) + { + // entitySet or Navigation property + IEdmVocabularyAnnotatable target; + string kind, name; + if (entitySetSegment != null) + { + target = entitySetSegment.EntitySet; + kind = "entity set"; + name = entitySetSegment.EntitySet.Name; + } + else + { + target = navigationPropertySegment.NavigationProperty; + kind = "navigation property"; + name = navigationPropertySegment.NavigationProperty.Name; + } + + VerifyDerivedTypeConstraints(this.configuration.Model, target, fullTypeName, kind, name); + } + + return; + } + + // Navigation property: ~/Users(1)/Orders/NS.Cast + navigationPropertySegment = previous as NavigationPropertySegment; + if (navigationPropertySegment != null) + { + VerifyDerivedTypeConstraints(this.configuration.Model, navigationPropertySegment.NavigationProperty, fullTypeName, "navigation property", navigationPropertySegment.NavigationProperty.Name); + return; + } + + // Structural property: ~/Users(1)/Addresses/NS.Cast + PropertySegment propertySegment = previous as PropertySegment; + if (propertySegment != null) + { + // Verify the DerivedTypeConstrictions on property. + IEdmProperty edmProperty = propertySegment.Property; + VerifyDerivedTypeConstraints(this.configuration.Model, edmProperty, fullTypeName, "property", edmProperty.Name); + + // Verify the Type Definition, the following codes should work if fix: https://github.com/OData/odata.net/issues/1326 + /* + IEdmTypeReference propertyTypeReference = edmProperty.Type; + if (edmProperty.Type.IsCollection()) + { + propertyTypeReference = edmProperty.Type.AsCollection().ElementType(); + } + + if (propertyTypeReference.IsTypeDefinition()) + { + IEdmTypeDefinition edmTypeDefinition = propertyTypeReference.AsTypeDefinition().TypeDefinition(); + VerifyDerivedTypeConstraints(this.configuration.Model, edmTypeDefinition, fullTypeName, "type definition", edmTypeDefinition.FullName()); + } + */ + + return; + } + + // operation: ~/Users(1)/NS.Operation(...)/NS.Cast + // TODO: we should support to verify the casting for the operation return type. + // however, ODL doesn't support to annotation on the return type, see https://github.com/OData/odata.net/issues/52 + // Once ODL supports to annotation on the return type, we should support to verify it. + /* + OperationSegment operationSegment = previous as OperationSegment; + if (operationSegment != null) + { + } + */ + } + + private void CheckOperationTypeCastSegmentRestriction(IEdmOperation operation) + { + Debug.Assert(operation != null); + + if (this.parsedSegments == null) + { + return; + } + + TypeSegment lastTypeSegment = this.parsedSegments.LastOrDefault(s => s is TypeSegment) as TypeSegment; + if (lastTypeSegment == null) + { + return; + } + + ODataPathSegment previous = this.parsedSegments[this.parsedSegments.Count - 1]; + ODataPathSegment previousPrevious = this.parsedSegments.Count >= 2 ? this.parsedSegments[this.parsedSegments.Count - 2] : null; + + if ((lastTypeSegment == previous) || (lastTypeSegment == previousPrevious && previous is KeySegment)) + { + if (!operation.IsBound) + { + return; + } + + string fullTypeName = lastTypeSegment.TargetEdmType.FullTypeName(); + IEdmOperationParameter bindingParaemter = operation.Parameters.First(); + IEdmType bindingType = bindingParaemter.Type.Definition; + bindingType = bindingType.AsElementType(); + if (fullTypeName == bindingType.FullTypeName()) + { + return; + } + + VerifyDerivedTypeConstraints(this.configuration.Model, bindingParaemter, fullTypeName, "operation", operation.Name); + } + } + + private static void VerifyDerivedTypeConstraints(IEdmModel model, IEdmVocabularyAnnotatable target, string fullTypeName, string kind, string name) + { + IEnumerable derivedTypes = model.GetDerivedTypeConstraints(target); + if (derivedTypes == null || derivedTypes.Any(d => d == fullTypeName)) + { + return; + } + + throw new ODataException(Strings.PathParser_TypeCastOnlyAllowedInDerivedTypeConstraint(fullTypeName, kind, name)); + } + + private static string ResolveEscapeFunction(string identifier, IEdmType bindingType, IEdmModel model, out string parenthesisExpression) + { + Debug.Assert(identifier != null && identifier.Length >= 1 && identifier[0] == ':'); + Debug.Assert(bindingType != null); + + bool isComposableRequired = identifier.Length >= 2 && identifier[identifier.Length - 1] == ':'; + IEdmFunction function = model.FindBoundOperations(bindingType) + .OfType().FirstOrDefault(f => f.IsComposable == isComposableRequired && IsUrlEscapeFunction(model, f)); + if (function == null) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_NoBoundEscapeFunctionSupported(bindingType.FullTypeName())); + } + + if (function.Parameters == null || function.Parameters.Count() != 2 || !function.Parameters.ElementAt(1).Type.IsString()) + { + throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_EscapeFunctionMustHaveOneStringParameter(function.FullName())); + } + + parenthesisExpression = function.Parameters.ElementAt(1).Name + "='" + (isComposableRequired ? identifier.Substring(1, identifier.Length - 2) : identifier.Substring(1)) + "'"; + return function.FullName(); + } + + internal static bool IsUrlEscapeFunction(IEdmModel model, IEdmFunction function) + { + Debug.Assert(model != null); + Debug.Assert(function != null); + + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(function, + Edm.Vocabularies.Community.V1.CommunityVocabularyModel.UrlEscapeFunctionTerm).FirstOrDefault(); + if (annotation != null) + { + if (annotation.Value == null) + { + // If the annotation is applied but a value is not specified then the value is assumed to be true. + return true; + } + + IEdmBooleanConstantExpression tagConstant = annotation.Value as IEdmBooleanConstantExpression; + if (tagConstant != null) + { + return tagConstant.Value; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathParserModelUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathParserModelUtils.cs new file mode 100644 index 0000000..e0a40c4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathParserModelUtils.cs @@ -0,0 +1,150 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Metadata; + +namespace Microsoft.OData.UriParser +{ + /// + /// Set of helpers and extensions to make it easier to convert the semantic path parser + /// to using and the related classes. + /// + internal static class PathParserModelUtils + { + /// + /// Gets the target entity set for the given operation import. + /// + /// The operation import. + /// The source entity set. + /// The model. + /// The target entity set of the operation import or null if it could not be determined. + internal static IEdmEntitySetBase GetTargetEntitySet(this IEdmOperationImport operationImport, IEdmEntitySetBase sourceEntitySet, IEdmModel model) + { + IEdmEntitySetBase targetEntitySet; + if (operationImport.TryGetStaticEntitySet(model, out targetEntitySet)) + { + return targetEntitySet; + } + + if (sourceEntitySet == null) + { + return null; + } + + if (operationImport.Operation.IsBound && operationImport.Operation.Parameters.Any()) + { + IEdmOperationParameter parameter; + Dictionary path; + IEnumerable errors; + + if (operationImport.TryGetRelativeEntitySetPath(model, out parameter, out path, out errors)) + { + IEdmEntitySetBase currentEntitySet = sourceEntitySet; + + foreach (var navigation in path) + { + currentEntitySet = currentEntitySet.FindNavigationTarget(navigation.Key, navigation.Value) as IEdmEntitySetBase; + if (currentEntitySet is IEdmUnknownEntitySet) + { + return currentEntitySet; + } + } + + return currentEntitySet; + } + else + { + if (errors.Any( + e => e.ErrorCode == EdmErrorCode.InvalidPathFirstPathParameterNotMatchingFirstParameterName)) + { + throw ExceptionUtil.CreateSyntaxError(); + } + } + } + + return null; + } + + /// + /// Gets the target entity set for the given operation import. + /// + /// The operation. + /// The source of operation. + /// The model. + /// The target entity set of the operation import or null if it could not be determined. + internal static IEdmEntitySetBase GetTargetEntitySet(this IEdmOperation operation, IEdmNavigationSource source, IEdmModel model) + { + if (source == null) + { + return null; + } + + if (operation.IsBound && operation.Parameters.Any()) + { + IEdmOperationParameter parameter; + Dictionary path; + IEdmEntityType lastEntityType; + IEnumerable errors; + + if (operation.TryGetRelativeEntitySetPath(model, out parameter, out path, out lastEntityType, out errors)) + { + IEdmNavigationSource target = source; + + foreach (var navigation in path) + { + target = target.FindNavigationTarget(navigation.Key, navigation.Value); + } + + return target as IEdmEntitySetBase; + } + else + { + if (errors.Any( + e => e.ErrorCode == EdmErrorCode.InvalidPathFirstPathParameterNotMatchingFirstParameterName)) + { + throw ExceptionUtil.CreateSyntaxError(); + } + } + } + + return null; + } + + /// Determines a matching target kind from the specified type. + /// ResourceType of element to get kind for. + /// An appropriate for the specified . + internal static RequestTargetKind GetTargetKindFromType(this IEdmType type) + { + Debug.Assert(type != null, "type != null"); + + switch (type.TypeKind) + { + case EdmTypeKind.Complex: + case EdmTypeKind.Entity: + return RequestTargetKind.Resource; + case EdmTypeKind.Collection: + if (type.IsStructuredCollectionType()) + { + return RequestTargetKind.Resource; + } + + return RequestTargetKind.Collection; + case EdmTypeKind.Enum: + return RequestTargetKind.Enum; + case EdmTypeKind.TypeDefinition: + return RequestTargetKind.Primitive; + default: + Debug.Assert(type.TypeKind == EdmTypeKind.Primitive, "typeKind == ResourceTypeKind.Primitive"); + return RequestTargetKind.Primitive; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathReverser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathReverser.cs new file mode 100644 index 0000000..8b422b5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/PathReverser.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Reverse a Path + /// + internal sealed class PathReverser : PathSegmentTokenVisitor + { + /// + /// any children of the root, will always be null on first call + /// + private readonly PathSegmentToken childToken; + + /// + /// Build a PathReverser at the top level (with no child token) + /// + public PathReverser() + { + this.childToken = null; + } + + /// + /// Build a PathReverser based on a child token. + /// + /// the new child of this token + private PathReverser(PathSegmentToken childToken) + { + this.childToken = childToken; + } + + /// + /// Reverse a NonSystemToken + /// + /// the non system token to reverse + /// the reversed NonSystemToken + public override PathSegmentToken Visit(NonSystemToken tokenIn) + { + ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); + if (tokenIn.NextToken != null) + { + NonSystemToken newNonSystemToken = new NonSystemToken(tokenIn.Identifier, tokenIn.NamedValues, this.childToken); + return BuildNextStep(tokenIn.NextToken, newNonSystemToken); + } + else + { + return new NonSystemToken(tokenIn.Identifier, tokenIn.NamedValues, this.childToken); + } + } + + /// + /// Reverse a SystemToken + /// + /// the SystemToken to reverse + /// the reversed SystemToken + public override PathSegmentToken Visit(SystemToken tokenIn) + { + ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); + if (tokenIn.NextToken != null) + { + SystemToken newNonSystemToken = new SystemToken(tokenIn.Identifier, this.childToken); + return BuildNextStep(tokenIn.NextToken, newNonSystemToken); + } + else + { + return new SystemToken(tokenIn.Identifier, this.childToken); + } + } + + /// + /// Build the next level PathReverser + /// + /// the next level token + /// the next levels child token + /// the path token from the next level. + private static PathSegmentToken BuildNextStep(PathSegmentToken nextLevelToken, PathSegmentToken nextChildToken) + { + PathReverser nextStepReverser = new PathReverser(nextChildToken); + return nextLevelToken.Accept(nextStepReverser); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SearchParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SearchParser.cs new file mode 100644 index 0000000..5fb660f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SearchParser.cs @@ -0,0 +1,236 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System; + using System.Diagnostics; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Parser which consumes the $search expression and produces the lexical object model. + /// + internal sealed class SearchParser + { + /// + /// The maximum number of recursion nesting allowed. + /// + private readonly int maxDepth; + + /// + /// The current recursion depth. + /// + private int recursionDepth; + + /// + /// The lexer being used for the parsing. + /// + private ExpressionLexer lexer; + + /// + /// Constructor. + /// + /// The maximum depth of each part of the query - a recursion limit. + internal SearchParser(int maxDepth) + { + Debug.Assert(maxDepth >= 0, "maxDepth >= 0"); + + this.maxDepth = maxDepth; + } + + /// + /// Parse expression text into Token. + /// + /// The expression string to Parse. + /// The lexical token representing the expression text. + internal QueryToken ParseSearch(string expressionText) + { + Debug.Assert(expressionText != null, "expressionText != null"); + + this.recursionDepth = 0; + this.lexer = new SearchLexer(expressionText); + QueryToken result = this.ParseExpression(); + this.lexer.ValidateToken(ExpressionTokenKind.End); + + return result; + } + + /// Creates an exception for a parse error. + /// Message text. + /// A new Exception. + private static Exception ParseError(string message) + { + return new ODataException(message); + } + + /// + /// Parses the expression. + /// + /// The lexical token representing the expression. + private QueryToken ParseExpression() + { + this.RecurseEnter(); + QueryToken token = this.ParseLogicalOr(); + this.RecurseLeave(); + return token; + } + + /// + /// Parses the or operator. + /// + /// The lexical token representing the expression. + private QueryToken ParseLogicalOr() + { + this.RecurseEnter(); + QueryToken left = this.ParseLogicalAnd(); + while (this.TokenIdentifierIs(ExpressionConstants.SearchKeywordOr)) + { + this.lexer.NextToken(); + QueryToken right = this.ParseLogicalAnd(); + left = new BinaryOperatorToken(BinaryOperatorKind.Or, left, right); + } + + this.RecurseLeave(); + return left; + } + + /// + /// Parses the and operator. + /// + /// The lexical token representing the expression. + private QueryToken ParseLogicalAnd() + { + this.RecurseEnter(); + QueryToken left = this.ParseUnary(); + while (this.TokenIdentifierIs(ExpressionConstants.SearchKeywordAnd) + || this.TokenIdentifierIs(ExpressionConstants.SearchKeywordNot) + || this.lexer.CurrentToken.Kind == ExpressionTokenKind.StringLiteral + || this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) + { + // Handle A NOT B, A (B) + // Bypass only when next token is AND + if (this.TokenIdentifierIs(ExpressionConstants.SearchKeywordAnd)) + { + this.lexer.NextToken(); + } + + QueryToken right = this.ParseUnary(); + left = new BinaryOperatorToken(BinaryOperatorKind.And, left, right); + } + + this.RecurseLeave(); + return left; + } + + /// + /// Parses the -, not unary operators. + /// + /// The lexical token representing the expression. + private QueryToken ParseUnary() + { + this.RecurseEnter(); + if (this.TokenIdentifierIs(ExpressionConstants.SearchKeywordNot)) + { + this.lexer.NextToken(); + QueryToken operand = this.ParseUnary(); + + this.RecurseLeave(); + return new UnaryOperatorToken(UnaryOperatorKind.Not, operand); + } + + this.RecurseLeave(); + return this.ParsePrimary(); + } + + /// + /// Parses the primary expressions. + /// + /// The lexical token representing the expression. + private QueryToken ParsePrimary() + { + QueryToken expr = null; + this.RecurseEnter(); + + switch (this.lexer.CurrentToken.Kind) + { + case ExpressionTokenKind.OpenParen: + expr = this.ParseParenExpression(); + break; + case ExpressionTokenKind.StringLiteral: + expr = new StringLiteralToken(this.lexer.CurrentToken.Text); + this.lexer.NextToken(); + break; + default: + throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_ExpressionExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.RecurseLeave(); + return expr; + } + + /// + /// Parses parenthesized expressions. + /// + /// The lexical token representing the expression. + private QueryToken ParseParenExpression() + { + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + QueryToken result = this.ParseExpression(); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrOperatorExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + return result; + } + + + /// + /// Checks that the current token has the specified identifier. + /// + /// Identifier to check. + /// true if the current token is an identifier with the specified text. + private bool TokenIdentifierIs(string id) + { + return this.lexer.CurrentToken.IdentifierIs(id, false); + } + + /// + /// Marks the fact that a recursive method was entered, and checks that the depth is allowed. + /// + private void RecurseEnter() + { + Debug.Assert(this.lexer != null, "Trying to recurse without a lexer, nothing to parse without a lexer."); + Debug.Assert(this.recursionDepth <= this.maxDepth, "The recursion depth was already exceeded, we should have failed."); + + this.recursionDepth++; + if (this.recursionDepth > this.maxDepth) + { + throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_TooDeep); + } + } + + /// + /// Marks the fact that a recursive method is leaving. + /// + private void RecurseLeave() + { + Debug.Assert(this.lexer != null, "Trying to recurse without a lexer, nothing to parse without a lexer."); + Debug.Assert(this.recursionDepth > 0, "Decreasing recursion depth below zero, imbalanced recursion calls."); + + this.recursionDepth--; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentArgumentParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentArgumentParser.cs new file mode 100644 index 0000000..e646f89 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentArgumentParser.cs @@ -0,0 +1,364 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// Provides a class used to represent a key for a resource. + /// + /// Internally, every key instance has a collection of values. These values + /// can be named or positional, depending on how they were specified + /// if parsed from a URI. + /// + internal sealed class SegmentArgumentParser + { + /// Empty key singleton. + private static readonly SegmentArgumentParser Empty = new SegmentArgumentParser(); + + /// Whether or not the key was formatted as a segment. + private readonly bool keysAsSegment; + + /// Whether Uri template parsing is enabled. + private readonly bool enableUriTemplateParsing; + + /// Named values. + private Dictionary namedValues; + + /// Positional values. + private List positionalValues; + + /// Initializes a new empty instance. + private SegmentArgumentParser() + { + } + + /// Initializes a new instance. + /// Named values. + /// Positional values for this instance. + /// Whether or not the key was formatted as a segment. + /// Whether Uri template parsing is enabled. + /// + /// One of namedValues or positionalValues should be non-null, but not both. + /// + private SegmentArgumentParser(Dictionary namedValues, List positionalValues, bool keysAsSegment, bool enableUriTemplateParsing) + { + Debug.Assert( + (namedValues == null) != (positionalValues == null), + "namedValues == null != positionalValues == null -- one or the other should be assigned, but not both"); + this.namedValues = namedValues; + this.positionalValues = positionalValues; + this.keysAsSegment = keysAsSegment; + this.enableUriTemplateParsing = enableUriTemplateParsing; + } + + /// Whether the values have a name. + public bool AreValuesNamed + { + get + { + return this.namedValues != null; + } + } + + /// Checks whether this key has any values. + public bool IsEmpty + { + get + { + return this == Empty; + } + } + + /// Returns a dictionary of named values when they AreValuesNamed is true. + public IDictionary NamedValues + { + get + { + return this.namedValues; + } + } + + /// Returns a list of values when they AreValuesNamed is false. + public IList PositionalValues + { + get + { + return this.positionalValues; + } + } + + /// Whether or not the key was formatted as a segment. + public bool KeyAsSegment + { + get { return this.keysAsSegment; } + } + + /// Number of values in the key. + public int ValueCount + { + get + { + if (this == Empty) + { + return 0; + } + + if (this.namedValues != null) + { + return this.namedValues.Count; + } + + Debug.Assert(this.positionalValues != null, "this.positionalValues != null"); + return this.positionalValues.Count; + } + } + + /// + /// Add a new value to the named values list. + /// + /// the new key + /// the new value + public void AddNamedValue(string key, string value) + { + CreateIfNull(ref this.namedValues); + if (!namedValues.ContainsKey(key)) + { + this.namedValues[key] = value; + } + } + + /// Attempts to parse key values from the specified text. + /// Text to parse (not null). + /// After invocation, the parsed key instance. + /// Whether Uri template parsing is enabled. + /// + /// true if the key instance was parsed; false if there was a + /// syntactic error. + /// + /// + /// The returned instance contains only string values. To get typed values, a call to + /// TryConvertValues is necessary. + /// + public static bool TryParseKeysFromUri(string text, out SegmentArgumentParser instance, bool enableUriTemplateParsing) + { + return TryParseFromUri(text, out instance, enableUriTemplateParsing); + } + + /// + /// Creates a key instance from the given raw segment text with a single positional value. + /// + /// The segment text. + /// Whether Uri template parsing is enabled. + /// A key instance with the given segment text as its only value. + public static SegmentArgumentParser FromSegment(string segmentText, bool enableUriTemplateParsing) + { + return new SegmentArgumentParser(null, new List { segmentText }, true, enableUriTemplateParsing); + } + + /// Attempts to parse nullable values (only positional values, no name-value pairs) from the specified text. + /// Text to parse (not null). + /// After invocation, the parsed key instance. + /// + /// true if the given values were parsed; false if there was a + /// syntactic error. + /// + /// + /// The returned instance contains only string values. To get typed values, a call to + /// TryConvertValues is necessary. + /// + public static bool TryParseNullableTokens(string text, out SegmentArgumentParser instance) + { + return TryParseFromUri(text, out instance, false); + } + + /// Tries to convert values to the keys of the specified type. + /// The specified type. + /// The converted key-value pairs. + /// The resolver to use. + /// true if all values were converted; false otherwise. + public bool TryConvertValues(IEdmEntityType targetEntityType, out IEnumerable> keyPairs, ODataUriResolver resolver) + { + Debug.Assert(!this.IsEmpty, "!this.IsEmpty -- caller should check"); + + if (this.NamedValues != null) + { + keyPairs = resolver.ResolveKeys(targetEntityType, this.NamedValues, this.ConvertValueWrapper); + } + else + { + Debug.Assert(this.positionalValues != null, "positionalValues != null -- otherwise this is Empty"); + keyPairs = resolver.ResolveKeys(targetEntityType, this.PositionalValues, this.ConvertValueWrapper); + } + + return true; + } + + /// + /// Wrapper for TryConvertValue + /// + /// the type to convert to (primitive or enum type) + /// the value to convert + /// Converted value, null if fails. + private object ConvertValueWrapper(IEdmTypeReference typeReference, string valueText) + { + object value; + if (!this.TryConvertValue(typeReference, valueText, out value)) + { + return null; + } + + return value; + } + + /// + /// Try to convert a value into an EDM primitive type, if template parsing enabled, the matching + /// template would be converted into corresponding UriTemplateExpression. + /// + /// the type to convert to (primitive or enum type) + /// the value to convert + /// The converted value, if conversion succeeded. + /// true if the conversion was successful. + private bool TryConvertValue(IEdmTypeReference typeReference, string valueText, out object convertedValue) + { + UriTemplateExpression expression; + if (this.enableUriTemplateParsing && UriTemplateParser.TryParseLiteral(valueText, typeReference, out expression)) + { + convertedValue = expression; + return true; + } + + if (typeReference.IsEnum()) + { + QueryNode enumNode = null; + if (EnumBinder.TryBindIdentifier(valueText, typeReference.AsEnum(), null, out enumNode)) + { + convertedValue = enumNode; + return true; + } + + convertedValue = null; + return false; + } + + IEdmPrimitiveTypeReference primitiveType = typeReference.AsPrimitive(); + Type primitiveClrType = EdmLibraryExtensions.GetPrimitiveClrType((IEdmPrimitiveType)primitiveType.Definition, primitiveType.IsNullable); + LiteralParser literalParser = LiteralParser.ForKeys(this.keysAsSegment); + return literalParser.TryParseLiteral(primitiveClrType, valueText, out convertedValue); + } + + /// Attempts to parse key values from the specified text. + /// Text to parse (not null). + /// After invocation, the parsed key instance. + /// Whether Uri template parsing is enabled. + /// + /// true if the key instance was parsed; false if there was a + /// syntactic error. + /// + /// + /// The returned instance contains only string values. To get typed values, a call to + /// TryConvertValues is necessary. + /// + private static bool TryParseFromUri(string text, out SegmentArgumentParser instance, bool enableUriTemplateParsing) + { + Debug.Assert(text != null, "text != null"); + + Dictionary namedValues = null; + List positionalValues = null; + + // parse keys just like function parameters + ExpressionLexer lexer = new ExpressionLexer("(" + text + ")", true, false); + UriQueryExpressionParser exprParser = new UriQueryExpressionParser(ODataUriParserSettings.DefaultFilterLimit /* default limit for parsing key value */, lexer); + var tmp = (new FunctionCallParser(lexer, exprParser)).ParseArgumentListOrEntityKeyList(); + if (lexer.CurrentToken.Kind != ExpressionTokenKind.End) + { + instance = null; + return false; + } + + if (tmp.Length == 0) + { + instance = Empty; + return true; + } + + foreach (FunctionParameterToken t in tmp) + { + string valueText = null; + LiteralToken literalToken = t.ValueToken as LiteralToken; + if (literalToken != null) + { + valueText = literalToken.OriginalText; + + // disallow "{...}" if enableUriTemplateParsing is false (which could have been seen as valid function parameter, e.g. array notation) + if (!enableUriTemplateParsing && UriTemplateParser.IsValidTemplateLiteral(valueText)) + { + instance = null; + return false; + } + } + else + { + DottedIdentifierToken dottedIdentifierToken = t.ValueToken as DottedIdentifierToken; // for enum + if (dottedIdentifierToken != null) + { + valueText = dottedIdentifierToken.Identifier; + } + } + + if (valueText != null) + { + if (t.ParameterName == null) + { + if (namedValues != null) + { + instance = null; // We cannot mix named and non-named values. + return false; + } + + CreateIfNull(ref positionalValues); + positionalValues.Add(valueText); + } + else + { + if (positionalValues != null) + { + instance = null; // We cannot mix named and non-named values. + return false; + } + + CreateIfNull(ref namedValues); + namedValues.Add(t.ParameterName, valueText); + } + } + else + { + instance = null; + return false; + } + } + + instance = new SegmentArgumentParser(namedValues, positionalValues, false, enableUriTemplateParsing); + return true; + } + + /// Creates a new instance if the specified value is null. + /// Type of variable. + /// Current value. + private static void CreateIfNull(ref T value) where T : new() + { + if (value == null) + { + value = new T(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentKeyHandler.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentKeyHandler.cs new file mode 100644 index 0000000..dfe4d9a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SegmentKeyHandler.cs @@ -0,0 +1,181 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Component for handling key expressions in URIs. + /// + internal static class SegmentKeyHandler + { + /// Tries to create a key segment for the given filter if it is non empty. + /// Segment on which to compose. + /// The parent node's key segment. + /// Parenthesis expression of segment. + /// The resolver to use. + /// The key segment that was created if the key was non-empty. + /// Whether Uri template parsing is enabled. + /// Whether the key was non-empty. + internal static bool TryCreateKeySegmentFromParentheses(ODataPathSegment previous, KeySegment previousKeySegment, string parenthesisExpression, ODataUriResolver resolver, out ODataPathSegment keySegment, bool enableUriTemplateParsing = false) + { + Debug.Assert(parenthesisExpression != null, "parenthesisExpression != null"); + Debug.Assert(previous != null, "segment!= null"); + Debug.Assert(resolver != null, "resolver != null"); + + if (previous.SingleResult) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + SegmentArgumentParser key; + if (!SegmentArgumentParser.TryParseKeysFromUri(parenthesisExpression, out key, enableUriTemplateParsing)) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + // People/NS.Employees() is OK, just like People() is OK + if (key.IsEmpty) + { + keySegment = null; + return false; + } + + keySegment = CreateKeySegment(previous, previousKeySegment, key, resolver); + return true; + } + + /// + /// Tries to handle the current segment as a key property value. + /// + /// The segment text. + /// The previous segment. + /// The parent node's key segment. + /// Key delimiter used in url. + /// The resolver to use. + /// The key segment that was created if the segment could be interpreted as a key. + /// Whether Uri template parsing is enabled. + /// Whether or not the segment was interpreted as a key. + internal static bool TryHandleSegmentAsKey(string segmentText, ODataPathSegment previous, KeySegment previousKeySegment, ODataUrlKeyDelimiter odataUrlKeyDelimiter, ODataUriResolver resolver, out KeySegment keySegment, bool enableUriTemplateParsing = false) + { + Debug.Assert(previous != null, "previous != null"); + Debug.Assert(odataUrlKeyDelimiter != null, "odataUrlKeyDelimiter != null"); + Debug.Assert(resolver != null, "resolver != null"); + + keySegment = null; + + // If the current convention does not support keys-as-segments, then this does not apply. + if (!odataUrlKeyDelimiter.EnableKeyAsSegment) + { + return false; + } + + // Keys only apply to collections, so if the prior segment is already a singleton, do not treat this segment as a key. + if (previous.SingleResult) + { + return false; + } + + // System segments (ie '$count') are never keys. + if (IsSystemSegment(segmentText)) + { + return false; + } + + // If the previous type is not an entity collection type + // TODO: collapse this and SingleResult. + IEdmEntityType targetEntityType; + if (previous.TargetEdmType == null || !previous.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType)) + { + return false; + } + + // Previously KeyAsSegment only allows single key, but we can also leverage related key finder to auto fill + // missed key value from referential constraint information, which would be done in CreateKeySegment. + // CreateKeySegment method will check whether key properties are missing after taking in related key values. + keySegment = CreateKeySegment(previous, previousKeySegment, SegmentArgumentParser.FromSegment(segmentText, enableUriTemplateParsing), resolver); + + return true; + } + + /// + /// Determines whether the segment text is a system-reserved identifier like $'count'. + /// + /// The segment text. + /// + /// true if the segment text is a system-reserved identifier like $'count'; otherwise, false. + /// + private static bool IsSystemSegment(string segmentText) + { + Debug.Assert(!string.IsNullOrEmpty(segmentText), "!string.IsNullOrEmpty(segmentText)"); + + // system segments must start with '$' + if (segmentText[0] != '$') + { + return false; + } + + // if the 2nd character is also a '$' then its an escaped key, not a system segment; + return segmentText.Length < 2 || segmentText[1] != '$'; + } + + /// + /// Parses the key properties based on the segment's target type, then creates a new segment for the key. + /// + /// The segment to apply the key to. + /// The parent node's key segment. + /// The key to apply. + /// The resolver to use. + /// The newly created key segment. + private static KeySegment CreateKeySegment(ODataPathSegment segment, KeySegment previousKeySegment, SegmentArgumentParser key, ODataUriResolver resolver) + { + Debug.Assert(segment != null, "segment != null"); + Debug.Assert(key != null && !key.IsEmpty, "key != null && !key.IsEmpty"); + Debug.Assert(segment.SingleResult == false, "segment.SingleResult == false"); + + IEdmEntityType targetEntityType = null; + if (!(segment.TargetEdmType != null && segment.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType))) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + Debug.Assert(targetEntityType != null, "targetEntityType != null"); + + // Adjust the keys for navigation segment. + var keyProperties = targetEntityType.Key().ToList(); + if (keyProperties.Count != key.ValueCount) + { + NavigationPropertySegment currentNavPropSegment = segment as NavigationPropertySegment; + if (currentNavPropSegment != null) + { + key = KeyFinder.FindAndUseKeysFromRelatedSegment(key, keyProperties, currentNavPropSegment.NavigationProperty, previousKeySegment); + } + } + + if (!key.AreValuesNamed && key.ValueCount > 1 && resolver.GetType() == typeof(ODataUriResolver)) + { + throw ExceptionUtil.CreateBadRequestError(ErrorStrings.RequestUriProcessor_KeysMustBeNamed); + } + + IEnumerable> keyPairs; + if (!key.TryConvertValues(targetEntityType, out keyPairs, resolver)) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + return new KeySegment(segment, keyPairs, targetEntityType, segment.TargetEdmNavigationSource); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandOptionParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandOptionParser.cs new file mode 100644 index 0000000..d01bd08 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandOptionParser.cs @@ -0,0 +1,703 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Parser that knows how to parse expand options that could come after the path part of an expand term. + /// Delegates to other parsing code as needed. I.E., when a nested $filter comes along, this will + /// fire up the filter parsing code to figure that out. That code won't even know that it came from a nested location. + /// + internal sealed class SelectExpandOptionParser + { + /// + /// The URI resolver which will resolve different kinds of Uri parsing context + /// + private readonly ODataUriResolver resolver; + + /// + /// The parent structured type for select/expand option in case expand option is star, get all parent navigation properties + /// + private readonly IEdmStructuredType parentStructuredType; + + /// + /// Max recursion depth. As we recurse, each new instance of this class will have this lowered by 1. + /// + private readonly int maxRecursionDepth; + + /// + /// Whether to enable no dollar query options. + /// + private readonly bool enableNoDollarQueryOptions; + + /// + /// Whether to allow case insensitive for builtin identifier. + /// + private readonly bool enableCaseInsensitiveBuiltinIdentifier; + + /// + /// Lexer to parse over the optionsText for a single $expand term. This is NOT the same lexer used by + /// to parse over the entirety of $select or $expand. + /// + private ExpressionLexer lexer; + + /// + /// Creates an instance of this class to parse options. + /// + /// Max recursion depth left. + /// Whether to allow case insensitive for builtin identifier. + /// Whether to enable no dollar query options. + internal SelectExpandOptionParser( + int maxRecursionDepth, + bool enableCaseInsensitiveBuiltinIdentifier = false, + bool enableNoDollarQueryOptions = false) + { + this.maxRecursionDepth = maxRecursionDepth; + this.enableCaseInsensitiveBuiltinIdentifier = enableCaseInsensitiveBuiltinIdentifier; + this.enableNoDollarQueryOptions = enableNoDollarQueryOptions; + } + + /// + /// Creates an instance of this class to parse options. + /// + /// The URI resolver which will resolve different kinds of Uri parsing context + /// The parent structured type for expand option + /// Max recursion depth left. + /// Whether to allow case insensitive for builtin identifier. + /// Whether to enable no dollar query options. + internal SelectExpandOptionParser( + ODataUriResolver resolver, + IEdmStructuredType parentStructuredType, + int maxRecursionDepth, + bool enableCaseInsensitiveBuiltinIdentifier = false, + bool enableNoDollarQueryOptions = false) + : this(maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier, enableNoDollarQueryOptions) + { + this.resolver = resolver; + this.parentStructuredType = parentStructuredType; + } + + /// + /// The maximum depth for $filter nested in $expand. + /// + internal int MaxFilterDepth { get; set; } + + /// + /// The maximum depth for $orderby nested in $expand. + /// + internal int MaxOrderByDepth { get; set; } + + /// + /// The maximum depth for $search nested in $expand. + /// + internal int MaxSearchDepth { get; set; } + + /// + /// Building off a PathSegmentToken, continue parsing any select options (nested $filter, $expand, etc) + /// to build up an SelectTermToken which fully represents the tree that makes up this select term. + /// + /// The PathSegmentToken representing the parsed select path whose options we are now parsing. + /// A string of the text between the parenthesis after a select option. + /// The select term token based on the path token, and all available select options. + internal SelectTermToken BuildSelectTermToken(PathSegmentToken pathToken, string optionsText) + { + // Setup a new lexer for parsing the optionsText + this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); + + QueryToken filterOption = null; + IEnumerable orderByOptions = null; + long? topOption = null; + long? skipOption = null; + bool? countOption = null; + QueryToken searchOption = null; + SelectToken selectOption = null; + ExpandToken expandOption = null; + ComputeToken computeOption = null; + + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) + { + // advance past the '(' + this.lexer.NextToken(); + + // Check for (), which is not allowed. + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) + { + throw new ODataException(ODataErrorStrings.UriParser_MissingSelectOption(pathToken.Identifier)); + } + + // Look for all the supported query options + while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + string text = this.enableCaseInsensitiveBuiltinIdentifier + ? this.lexer.CurrentToken.Text.ToLowerInvariant() + : this.lexer.CurrentToken.Text; + + // Prepend '$' prefix if needed. + if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) + { + text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); + } + + switch (text) + { + case ExpressionConstants.QueryOptionFilter: // inner $filter + filterOption = ParseInnerFilter(); + break; + + case ExpressionConstants.QueryOptionOrderby: // inner $orderby + orderByOptions = ParseInnerOrderBy(); + break; + + case ExpressionConstants.QueryOptionTop: // inner $top + topOption = ParseInnerTop(); + break; + + case ExpressionConstants.QueryOptionSkip: // innner $skip + skipOption = ParseInnerSkip(); + break; + + case ExpressionConstants.QueryOptionCount: // inner $count + countOption = ParseInnerCount(); + break; + + case ExpressionConstants.QueryOptionSearch: // inner $search + searchOption = ParseInnerSearch(); + break; + + case ExpressionConstants.QueryOptionSelect: // inner $select + selectOption = ParseInnerSelect(pathToken); + break; + + case ExpressionConstants.QueryOptionExpand: // inner $expand + expandOption = ParseInnerExpand(pathToken); + break; + + case ExpressionConstants.QueryOptionCompute: // inner $compute + computeOption = ParseInnerCompute(); + break; + + default: + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + } + + // Move past the ')' + this.lexer.NextToken(); + } + + // Either there was no '(' at all or we just read past the ')' so we should be at the end + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + + return new SelectTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, searchOption, selectOption, expandOption, computeOption); + } + + /// + /// Building of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) + /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. + /// + /// The PathSegmentToken representing the parsed expand path whose options we are now parsing. + /// A string of the text between the parenthesis after an expand option. + /// The list of expand term tokens based on the path token, and all available expand options. + internal List BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) + { + // Setup a new lexer for parsing the optionsText + this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); + + // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. + if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) + { + return BuildStarExpandTermToken(pathToken); + } + + QueryToken filterOption = null; + IEnumerable orderByOptions = null; + long? topOption = null; + long? skipOption = null; + bool? countOption = null; + long? levelsOption = null; + QueryToken searchOption = null; + SelectToken selectOption = null; + ExpandToken expandOption = null; + ComputeToken computeOption = null; + IEnumerable applyOptions = null; + + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) + { + // advance past the '(' + this.lexer.NextToken(); + + // Check for (), which is not allowed. + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) + { + throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); + } + + // Look for all the supported query options + while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + string text = this.enableCaseInsensitiveBuiltinIdentifier + ? this.lexer.CurrentToken.Text.ToLowerInvariant() + : this.lexer.CurrentToken.Text; + + // Prepend '$' prefix if needed. + if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) + { + text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); + } + + switch (text) + { + case ExpressionConstants.QueryOptionFilter: // inner $filter + filterOption = ParseInnerFilter(); + break; + + case ExpressionConstants.QueryOptionOrderby: // inner $orderby + orderByOptions = ParseInnerOrderBy(); + break; + + case ExpressionConstants.QueryOptionTop: // inner $top + topOption = ParseInnerTop(); + break; + + case ExpressionConstants.QueryOptionSkip: // innner $skip + skipOption = ParseInnerSkip(); + break; + + case ExpressionConstants.QueryOptionCount: // inner $count + countOption = ParseInnerCount(); + break; + + case ExpressionConstants.QueryOptionSearch: // inner $search + searchOption = ParseInnerSearch(); + break; + + case ExpressionConstants.QueryOptionLevels: // inner $level + levelsOption = ParseInnerLevel(); + break; + + case ExpressionConstants.QueryOptionSelect: // inner $select + selectOption = ParseInnerSelect(pathToken); + break; + + case ExpressionConstants.QueryOptionExpand: // inner $expand + expandOption = ParseInnerExpand(pathToken); + break; + + case ExpressionConstants.QueryOptionCompute: // inner $compute + computeOption = ParseInnerCompute(); + break; + + case ExpressionConstants.QueryOptionApply: // inner $apply + applyOptions = ParseInnerApply(); + break; + + default: + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + } + } + + // Move past the ')' + this.lexer.NextToken(); + } + + // Either there was no '(' at all or we just read past the ')' so we should be at the end + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + + // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. + List expandTermTokenList = new List(); + ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, + skipOption, countOption, levelsOption, searchOption, selectOption, expandOption, computeOption, applyOptions); + expandTermTokenList.Add(currentToken); + + return expandTermTokenList; + } + + + /// + /// Building off of a PathSegmentToken whose value is star, only nested level options is allowed. + /// + /// The PathSegmentToken representing the parsed expand path whose options we are now parsing. + /// An expand term token based on the path token, and all available expand options. + private List BuildStarExpandTermToken(PathSegmentToken pathToken) + { + List expandTermTokenList = new List(); + long? levelsOption = null; + bool isRefExpand = (pathToken.Identifier == UriQueryConstants.RefSegment); + + // Based on the specification, + // For star in expand, this will be supported, + // $expand=* + // $expand=EntitySet($expand=* ) + // $expand=*/$ref + // $expand=*,EntitySet + // $expand=EntitySet, * + // $expand=*/$ref,EntitySet + // Parenthesized set of expand options for star expand option supported are $level per specification. + // And this will throw exception, + // $expand= * /$count + // Parenthesized set of expand options for star expand option which will also cause exception are $filter, $select, $orderby, $skip, $top, $count, $search, and $expand per specification. + // And level is not supported with "*/$ref". + + // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) + { + // advance past the '(' + this.lexer.NextToken(); + + // Check for (), which is not allowed. + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) + { + throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); + } + + // Only level option is supported by expand. + while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + string text = this.enableCaseInsensitiveBuiltinIdentifier + ? this.lexer.CurrentToken.Text.ToLowerInvariant() + : this.lexer.CurrentToken.Text; + switch (text) + { + case ExpressionConstants.QueryOptionLevels: + { + if (!isRefExpand) + { + levelsOption = ParseInnerLevel(); + } + else + { + // no option is allowed when expand with star per specification + throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStarRef(this.lexer.ExpressionText)); + } + + break; + } + + default: + { + throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStar(this.lexer.ExpressionText)); + } + } + } + + // Move past the ')' + this.lexer.NextToken(); + } + + // Either there was no '(' at all or we just read past the ')' so we should be at the end + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + + // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. + var entityType = this.parentStructuredType as IEdmEntityType; + if (entityType == null) + { + throw new ODataException(ODataErrorStrings.UriExpandParser_ParentEntityIsNull(this.lexer.ExpressionText)); + } + + foreach (var navigationProperty in entityType.NavigationProperties()) + { + var tmpPathToken = default(PathSegmentToken); + + // create path token for each navigation properties. + if (pathToken.Identifier.Equals(UriQueryConstants.RefSegment)) + { + tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken.NextToken); + tmpPathToken = new NonSystemToken(UriQueryConstants.RefSegment, null, tmpPathToken); + } + else + { + tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken); + } + + ExpandTermToken currentToken = new ExpandTermToken(tmpPathToken, null, null, + null, null, null, levelsOption, null, null, null, null, null); + expandTermTokenList.Add(currentToken); + } + + return expandTermTokenList; + } + + /// + /// Parse the filter option in the select/expand option text. + /// + /// The filter option for select/expand + private QueryToken ParseInnerFilter() + { + // advance to the equal sign + this.lexer.NextToken(); + string filterText = this.ReadQueryOption(); + + UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier); + return filterParser.ParseFilter(filterText); + } + + /// + /// Parse the orderby option in the select/expand option text. + /// + /// The orderby option for select/expand + private IEnumerable ParseInnerOrderBy() + { + // advance to the equal sign + this.lexer.NextToken(); + string orderByText = this.ReadQueryOption(); + + UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); + return orderbyParser.ParseOrderBy(orderByText); + } + + /// + /// Parse the top option in the select/expand option text. + /// + /// The top option for select/expand + private long? ParseInnerTop() + { + // advance to the equal sign + this.lexer.NextToken(); + string topText = this.ReadQueryOption(); + + // TryParse requires a non-nullable non-negative long. + long top; + if (!long.TryParse(topText, out top) || top < 0) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText)); + } + + return top; + } + + /// + /// Parse the skip option in the select/expand option text. + /// + /// The skip option for select/expand + private long? ParseInnerSkip() + { + // advance to the equal sign + this.lexer.NextToken(); + string skipText = this.ReadQueryOption(); + + // TryParse requires a non-nullable non-negative long. + long skip; + if (!long.TryParse(skipText, out skip) || skip < 0) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText)); + } + + return skip; + } + + /// + /// Parse the count option in the select/expand option text. + /// + /// The count option for select/expand + private bool? ParseInnerCount() + { + // advance to the equal sign + this.lexer.NextToken(); + string countText = this.ReadQueryOption(); + switch (countText) + { + case ExpressionConstants.KeywordTrue: + return true; + + case ExpressionConstants.KeywordFalse: + return false; + + default: + throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText)); + } + } + + /// + /// Parse the search option in the select/expand option text. + /// + /// The search option for select/expand + private QueryToken ParseInnerSearch() + { + // advance to the equal sign + this.lexer.NextToken(); + string searchText = this.ReadQueryOption(); + + SearchParser searchParser = new SearchParser(this.MaxSearchDepth); + return searchParser.ParseSearch(searchText); + } + + /// + /// Parse the select option in the select/expand option text. + /// + /// The path segment token + /// The select option for select/expand + private SelectToken ParseInnerSelect(PathSegmentToken pathToken) + { + // advance to the equal sign + this.lexer.NextToken(); + string selectText = this.ReadQueryOption(); + + IEdmStructuredType targetStructuredType = null; + if (this.resolver != null && this.parentStructuredType != null) + { + var parentProperty = this.resolver.ResolveProperty(parentStructuredType, pathToken.Identifier); + + // It is a property, need to find the type. + // or for select query like: $select=Address($expand=City) + if (parentProperty != null) + { + targetStructuredType = parentProperty.Type.ToStructuredType(); + } + } + + SelectExpandParser innerSelectParser = new SelectExpandParser( + resolver, + selectText, + targetStructuredType, + this.maxRecursionDepth - 1, + this.enableCaseInsensitiveBuiltinIdentifier, + this.enableNoDollarQueryOptions); + + return innerSelectParser.ParseSelect(); + } + + /// + /// Parse the expand option in the select/expand option text. + /// + /// The path segment token + /// The expand option for select/expand + private ExpandToken ParseInnerExpand(PathSegmentToken pathToken) + { + // advance to the equal sign + this.lexer.NextToken(); + + string expandText = this.ReadQueryOption(); + + IEdmStructuredType targetStructuredType = null; + if (this.resolver != null && this.parentStructuredType != null) + { + var parentProperty = this.resolver.ResolveProperty(parentStructuredType, pathToken.Identifier); + + // it is a property, need to find the type. + // Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)", + // find navigation property Trips of Friends, then get Entity type of Trips. + // or for select query like: $select=Address($expand=City) + if (parentProperty != null) + { + targetStructuredType = parentProperty.Type.ToStructuredType(); + } + } + + SelectExpandParser innerExpandParser = new SelectExpandParser( + resolver, + expandText, + targetStructuredType, + this.maxRecursionDepth - 1, + this.enableCaseInsensitiveBuiltinIdentifier, + this.enableNoDollarQueryOptions); + + return innerExpandParser.ParseExpand(); + } + + /// + /// Parse the level option in the expand option text. + /// + /// The level option for expand in long type + private long? ParseInnerLevel() + { + long? levelsOption = null; + + // advance to the equal sign + this.lexer.NextToken(); + string levelsText = this.ReadQueryOption(); + long level; + + if (string.Equals( + ExpressionConstants.KeywordMax, + levelsText, + this.enableCaseInsensitiveBuiltinIdentifier ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) + { + levelsOption = long.MinValue; + } + else if (!long.TryParse(levelsText, NumberStyles.None, CultureInfo.InvariantCulture, out level) || level < 0) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidLevelsOption(levelsText)); + } + else + { + levelsOption = level; + } + + return levelsOption; + } + + /// + /// Parse the compute option in the expand option text. + /// + /// The compute option for expand + private ComputeToken ParseInnerCompute() + { + this.lexer.NextToken(); + string computeText = this.ReadQueryOption(); + + UriQueryExpressionParser computeParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); + return computeParser.ParseCompute(computeText); + } + + /// + /// Parse the apply option in the expand option text. + /// + /// The apply option for expand + private IEnumerable ParseInnerApply() + { + this.lexer.NextToken(); + string applyText = this.ReadQueryOption(); + + UriQueryExpressionParser applyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); + return applyParser.ParseApply(applyText); + } + + /// + /// Read a query option from the lexer. + /// + /// The query option as a string. + private string ReadQueryOption() + { + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Equal) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + + // get the full text from the current location onward + // there could be literals like 'A string literal; tricky!' in there, so we need to be careful. + // Also there could be more nested (...) expressions that we ignore until we recurse enough times to get there. + string expressionText = this.lexer.AdvanceThroughExpandOption(); + + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.SemiColon) + { + // Move over the ';' seperator + this.lexer.NextToken(); + return expressionText; + } + + // If there wasn't a semicolon, it MUST be the last option. We must be at ')' in this case + this.lexer.ValidateToken(ExpressionTokenKind.CloseParen); + return expressionText; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs new file mode 100644 index 0000000..ac0f06d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandParser.cs @@ -0,0 +1,382 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Object that knows how to parse a select or expand expression. That is, a path to a property, + /// a wildcard, operation name, etc, including nested expand options. + /// + internal sealed class SelectExpandParser + { + /// + /// The URI resolver which will resolve different kinds of Uri parsing context + /// + private readonly ODataUriResolver resolver; + + /// + /// The parent entity type for expand option in case expand option is star, get all parent entity navigation properties, it is an IEdmStructuredType. + /// + private readonly IEdmStructuredType parentEntityType; + + /// + /// The maximum allowable recursive depth. + /// + private readonly int maxRecursiveDepth; + + /// + /// Whether to enable no dollar query options. + /// + private readonly bool enableNoDollarQueryOptions; + + /// + /// Whether to allow case insensitive for builtin identifier. + /// + private readonly bool enableCaseInsensitiveBuiltinIdentifier; + + /// + /// Object to handle the parsing of any nested select/expand options that we discover. + /// + private SelectExpandOptionParser selectExpandOptionParser; + + /// + /// Lexer used to parse an the $select or $expand string. + /// + private ExpressionLexer lexer; + + /// + /// True if we are we parsing $select. + /// + private bool isSelect; + + /// + /// Build the SelectOption strategy. + /// TODO: Really should not take the clauseToParse here. Instead it should be provided with a call to ParseSelect() or ParseExpand(). + /// + /// the clause to parse + /// max recursive depth + /// Whether to allow case insensitive for builtin identifier. + /// Whether to enable no dollar query options. + public SelectExpandParser( + string clauseToParse, + int maxRecursiveDepth, + bool enableCaseInsensitiveBuiltinIdentifier = false, + bool enableNoDollarQueryOptions = false) + { + this.maxRecursiveDepth = maxRecursiveDepth; + + // Set max recursive depth for path, $filter, $orderby and $search to maxRecursiveDepth in case they were not be be specified. + this.MaxPathDepth = maxRecursiveDepth; + this.MaxFilterDepth = maxRecursiveDepth; + this.MaxOrderByDepth = maxRecursiveDepth; + this.MaxSearchDepth = maxRecursiveDepth; + + // Sets up our lexer. We don't turn useSemicolonDelimiter on since the parsing code for expand options, + // which is the only thing that needs it, is in a different class that uses it's own lexer. + this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false /*moveToFirstToken*/, false /*useSemicolonDelimiter*/) : null; + + this.enableCaseInsensitiveBuiltinIdentifier = enableCaseInsensitiveBuiltinIdentifier; + + this.enableNoDollarQueryOptions = enableNoDollarQueryOptions; + } + + /// + /// Build the ExpandOption strategy (SelectOption build does not need resolover and parentEntityType now). + /// + /// the URI resolver which will resolve different kinds of Uri parsing context + /// the clause to parse + /// the parent entity type for expand option + /// max recursive depth + /// Whether to allow case insensitive for builtin identifier. + /// Whether to enable no dollar query options. + public SelectExpandParser( + ODataUriResolver resolver, + string clauseToParse, + IEdmStructuredType parentEntityType, + int maxRecursiveDepth, + bool enableCaseInsensitiveBuiltinIdentifier = false, + bool enableNoDollarQueryOptions = false) + : this(clauseToParse, maxRecursiveDepth, enableCaseInsensitiveBuiltinIdentifier, enableNoDollarQueryOptions) + { + this.resolver = resolver; + this.parentEntityType = parentEntityType; + } + + /// + /// Gets the parser for inner select/expand options. + /// + internal SelectExpandOptionParser SelectExpandOptionParser + { + get + { + if (this.selectExpandOptionParser == null) + { + this.selectExpandOptionParser = new SelectExpandOptionParser( + this.resolver, + this.parentEntityType, + this.maxRecursiveDepth, + this.enableCaseInsensitiveBuiltinIdentifier, + this.enableNoDollarQueryOptions) + { + MaxFilterDepth = MaxFilterDepth, + MaxOrderByDepth = MaxOrderByDepth, + MaxSearchDepth = MaxSearchDepth + }; + } + + return this.selectExpandOptionParser; + } + } + + /// + /// The maximum depth for path nested in $expand. + /// + internal int MaxPathDepth { get; set; } + + /// + /// The maximum depth for $filter nested in $expand. + /// + internal int MaxFilterDepth { get; set; } + + /// + /// The maximum depth for $orderby nested in $expand. + /// + internal int MaxOrderByDepth { get; set; } + + /// + /// The maximum depth for $search nested in $expand. + /// + internal int MaxSearchDepth { get; set; } + + /// + /// Parses a full $select expression. + /// + /// The lexical token representing the select. + public SelectToken ParseSelect() + { + this.isSelect = true; + return this.ParseCommaSeperatedSelectList(termTokens => new SelectToken(termTokens), this.ParseSingleSelectTerm); + } + + /// + /// Parses a full $expand expression. + /// + /// The lexical token representing the select. + public ExpandToken ParseExpand() + { + this.isSelect = false; + return this.ParseCommaSeperatedExpandList(termTokens => new ExpandToken(termTokens), this.ParseSingleExpandTerm); + } + + /// + /// Parses a single term in a comma separated list of things to select. + /// + /// A token representing thing to select. + private SelectTermToken ParseSingleSelectTerm() + { + this.isSelect = true; + + var termParser = new SelectExpandTermParser(this.lexer, this.MaxPathDepth, this.isSelect); + PathSegmentToken pathToken = termParser.ParseTerm(); + + string optionsText = null; + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) + { + optionsText = this.lexer.AdvanceThroughBalancedParentheticalExpression(); + + // Move lexer to what is after the parenthesis expression. Now CurrentToken will be the next thing. + this.lexer.NextToken(); + } + + return this.SelectExpandOptionParser.BuildSelectTermToken(pathToken, optionsText); + } + + /// + /// Parses a single term in a comma separated list of things to expand. + /// + /// A token list representing thing to expand, the expand option star will have more than one items in the list. + private List ParseSingleExpandTerm() + { + this.isSelect = false; + + var termParser = new SelectExpandTermParser(this.lexer, this.MaxPathDepth, this.isSelect); + PathSegmentToken pathToken = termParser.ParseTerm(allowRef: true); + + string optionsText = null; + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) + { + optionsText = this.lexer.AdvanceThroughBalancedParentheticalExpression(); + + // Move lexer to what is after the parenthesis expression. Now CurrentToken will be the next thing. + this.lexer.NextToken(); + } + + return this.SelectExpandOptionParser.BuildExpandTermToken(pathToken, optionsText); + } + + /// + /// Parsed a comma separated list of $expand terms. + /// + /// A method to construct the final token from the term tokens. + /// A method to parse each individual term. + /// A token representing the entire $expand clause syntactically. + private ExpandToken ParseCommaSeperatedExpandList(Func, ExpandToken> ctor, Func> termParsingFunc) + { + List termTokens = new List(); + + List starTermTokens = new List(); + + // TODO Per specification, "A navigation property MUST NOT appear in more than one expandItem.", so should be some check to make sure explicitly specified property does not show multiple times. + + // This happens if we were passed a null string + if (this.lexer == null) + { + return ctor(termTokens); + } + + // Move to the first token + this.lexer.NextToken(); + + // This happens if it was just whitespace. e.g. fake.svc/Customers?$expand= &$filter=IsCool&$orderby=ID + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.End) + { + return ctor(termTokens); + } + + // Process first term + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star) + { + starTermTokens = termParsingFunc(); + } + else + { + termTokens.AddRange(termParsingFunc()); + } + + // If it was a list of terms, then commas will be separating them + while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Comma) + { + // Move over the ',' to the next term + this.lexer.NextToken(); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End && this.lexer.CurrentToken.Kind != ExpressionTokenKind.Star) + { + termTokens.AddRange(termParsingFunc()); + } + else if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star) + { + // Multiple stars is not allowed here. + if (starTermTokens.Count > 0) + { + throw new ODataException(ODataErrorStrings.UriExpandParser_TermWithMultipleStarNotAllowed(this.lexer.ExpressionText)); + } + + starTermTokens = termParsingFunc(); + } + else + { + break; + } + } + + // If there is * with other property, per specification, other properties will take precedence over the star operator + if (starTermTokens.Count > 0) + { + List explicitedTokens = new List(); + foreach (var tmpTokens in termTokens) + { + var pathToNav = tmpTokens.PathToNavigationProp; + if (pathToNav.Identifier != UriQueryConstants.RefSegment) + { + explicitedTokens.Add(pathToNav.Identifier); + } + else + { + explicitedTokens.Add(pathToNav.NextToken.Identifier); + } + } + + // Add navigation path if it is not in list yet + foreach (var tmpTokens in starTermTokens) + { + var pathToNav = tmpTokens.PathToNavigationProp; + if (pathToNav.Identifier != UriQueryConstants.RefSegment && !explicitedTokens.Contains(pathToNav.Identifier)) + { + termTokens.Add(tmpTokens); + } + else if (pathToNav.Identifier == UriQueryConstants.RefSegment && !explicitedTokens.Contains(pathToNav.NextToken.Identifier)) + { + termTokens.Add(tmpTokens); + } + } + } + + // If there isn't a comma, then we must be done. Otherwise there is a syntax error + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + + return ctor(termTokens); + } + + /// + /// Parsed a comma separated list of $select terms. + /// + /// A method to construct the final token from the term tokens. + /// A method to parse each individual term. + /// A token representing the entire $select clause syntactically. + private SelectToken ParseCommaSeperatedSelectList(Func, SelectToken> ctor, Func termParsingFunc) + { + List termTokens = new List(); + + // This happens if we were passed a null string + if (this.lexer == null) + { + return ctor(termTokens); + } + + // Move to the first token + this.lexer.NextToken(); + + // This happens if it was just whitespace. e.g. fake.svc/Customers?$expand= &$filter=IsCool&$orderby=ID + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.End) + { + return ctor(termTokens); + } + + // Process first term + termTokens.Add(termParsingFunc()); + + // If it was a list of terms, then commas will be separating them + while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Comma) + { + // Move over the ',' to the next term + this.lexer.NextToken(); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) + { + termTokens.Add(termParsingFunc()); + } + else + { + break; + } + } + + // If there isn't a comma, then we must be done. Otherwise there is a syntax error + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); + } + + return ctor(termTokens); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandSyntacticParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandSyntacticParser.cs new file mode 100644 index 0000000..2a812e1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandSyntacticParser.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Parse the raw select and expand clause syntax. + /// + internal static class SelectExpandSyntacticParser + { + /// + /// Parse the raw select and expand strings into Abstract Syntax Trees + /// + /// the raw select string + /// the raw expand string + /// the parent entity type for expand option + /// the OData URI parser configuration + /// the resulting expand AST + /// the resulting select AST + public static void Parse( + string selectClause, + string expandClause, + IEdmStructuredType parentStructuredType, + ODataUriParserConfiguration configuration, + out ExpandToken expandTree, + out SelectToken selectTree) + { + SelectExpandParser selectParser = new SelectExpandParser(selectClause, configuration.Settings.SelectExpandLimit, configuration.EnableCaseInsensitiveUriFunctionIdentifier) + { + MaxPathDepth = configuration.Settings.PathLimit + }; + selectTree = selectParser.ParseSelect(); + + SelectExpandParser expandParser = new SelectExpandParser( + configuration.Resolver, + expandClause, + parentStructuredType, + configuration.Settings.SelectExpandLimit, + configuration.EnableCaseInsensitiveUriFunctionIdentifier, + configuration.EnableNoDollarQueryOptions) + { + MaxPathDepth = configuration.Settings.PathLimit, + MaxFilterDepth = configuration.Settings.FilterLimit, + MaxOrderByDepth = configuration.Settings.OrderByLimit, + MaxSearchDepth = configuration.Settings.SearchLimit + }; + expandTree = expandParser.ParseExpand(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandTermParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandTermParser.cs new file mode 100644 index 0000000..960a9ed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/SelectExpandTermParser.cs @@ -0,0 +1,164 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Sub-parser that uses to parse a single select or expand term. + /// Uses a provided Lexer, which must be positioned at the term, to parse the term. + /// + internal sealed class SelectExpandTermParser + { + /// + /// Lexer provided by . + /// + private readonly ExpressionLexer lexer; + + /// + /// Max length of a select or expand path. + /// + private readonly int maxPathLength; + + /// + /// True if we are parsing select, false if we are parsing expand. + /// + private readonly bool isSelect; + + /// + /// Constructs a term parser. + /// + /// Lexer to use for parsing the term. Should be position at the term to parse. + /// Max length of a select or expand path. + /// True if we are parsing select, false if we are parsing expand. + internal SelectExpandTermParser(ExpressionLexer lexer, int maxPathLength, bool isSelect) + { + this.lexer = lexer; + this.maxPathLength = maxPathLength; + this.isSelect = isSelect; + } + + /// + /// Parses a select or expand term into a PathSegmentToken. + /// Assumes the lexer is positioned at the beginning of the term to parse. + /// When done, the lexer will be positioned at whatever is after the identifier. + /// + /// Whether the $ref operation is valid in this token. + /// parsed query token + internal PathSegmentToken ParseTerm(bool allowRef = false) + { + int pathLength; + PathSegmentToken token = this.ParseSegment(null, allowRef); + if (token != null) + { + pathLength = 1; + } + else + { + return null; + } + + this.CheckPathLength(pathLength); + + // If this property was a path, walk that path. e.g. SomeComplex/SomeInnerComplex/SomeNavProp + while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Slash) + { + // Move from '/' to the next segment + this.lexer.NextToken(); + + // TODO: Could remove V4 if we don't want to allow a trailing '/' character + // Allow a single trailing slash for backwards compatibility with the WCF DS Server parser. + if (pathLength > 1 && this.lexer.CurrentToken.Kind == ExpressionTokenKind.End) + { + break; + } + + token = this.ParseSegment(token, allowRef); + if (token != null) + { + this.CheckPathLength(++pathLength); + } + } + + return token; + } + + /// + /// Check that the current path length is less than the maximum path length + /// + /// the current path length + private void CheckPathLength(int pathLength) + { + if (pathLength > this.maxPathLength) + { + throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_TooDeep); + } + } + + /// + /// Uses the ExpressionLexer to visit the next ExpressionToken, and delegates parsing of segments, type segments, identifiers, + /// and the star token to other methods. + /// + /// Previously parsed PathSegmentToken, or null if this is the first token. + /// Whether the $ref operation is valid in this token. + /// A parsed PathSegmentToken representing the next segment in this path. + private PathSegmentToken ParseSegment(PathSegmentToken previousSegment, bool allowRef) + { + // TODO $count is defined in specification for expand, it is not supported now. Also note $count is not supported with star as expand option. + if (this.lexer.CurrentToken.Text.StartsWith("$", StringComparison.Ordinal) && (!allowRef || this.lexer.CurrentToken.Text != UriQueryConstants.RefSegment)) + { + throw new ODataException(ODataErrorStrings.UriSelectParser_SystemTokenInSelectExpand(this.lexer.CurrentToken.Text, this.lexer.ExpressionText)); + } + + // Some check here to throw exception, both prop1/*/prop2 and */$ref/prop will throw exception, both are for $expand cases + if (!isSelect) + { + if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.Star && this.lexer.CurrentToken.GetIdentifier() != UriQueryConstants.RefSegment) + { + // Star can only be followed with $ref + throw new ODataException(ODataErrorStrings.ExpressionToken_OnlyRefAllowWithStarInExpand); + } + else if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.RefSegment) + { + // $ref should not have more property followed. + throw new ODataException(ODataErrorStrings.ExpressionToken_NoPropAllowedAfterRef); + } + } + + string propertyName; + + if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot) + { + propertyName = this.lexer.ReadDottedIdentifier(this.isSelect); + } + else if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star) + { + // "*/$ref" is supported in expand + if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash && isSelect) + { + throw new ODataException(ODataErrorStrings.ExpressionToken_IdentifierExpected(this.lexer.Position)); + } + else if (previousSegment != null && !isSelect) + { + // expand option like "customer?$expand=VIPCUstomer/*" is not allowed as specification does not allowed any property before *. + throw new ODataException(ODataErrorStrings.ExpressionToken_NoSegmentAllowedBeforeStarInExpand); + } + + propertyName = this.lexer.CurrentToken.Text; + this.lexer.NextToken(); + } + else + { + propertyName = this.lexer.CurrentToken.GetIdentifier(); + this.lexer.NextToken(); + } + + return new NonSystemToken(propertyName, null, previousSegment); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriLiteralParsingException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriLiteralParsingException.cs new file mode 100644 index 0000000..0f5623a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriLiteralParsingException.cs @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; + +namespace Microsoft.OData.UriParser +{ + ////#if ORCAS // Not working in .Net3.5 solution, why ?? + //// [Serializable] + ////#endif + + /// + /// Throw this expcetion when the parser can parse the target type but failed to do so. + /// Do not throw when parser is not able to parse the target type. + /// + [DebuggerDisplay("{Message}")] + public sealed class UriLiteralParsingException : ODataException + { + /// Creates a new instance of the class with default values. + /// + /// The Message property is initialized to a system-supplied message + /// that describes the error. This message takes into account the + /// current system culture. + /// + public UriLiteralParsingException() + : base() + { + } + + /// Creates a new instance of the class with an error message. + /// The plain text error message for this exception. + public UriLiteralParsingException(string message) + : base(message) + { + } + + /// Creates a new instance of the class with an error message and an inner exception. + /// The plain text error message for this exception. + /// The inner exception that is the cause of this exception to be thrown. + public UriLiteralParsingException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriParserHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriParserHelper.cs new file mode 100644 index 0000000..f9cd2a8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriParserHelper.cs @@ -0,0 +1,292 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion + + internal static class UriParserHelper + { + #region Internal Methods + + /// Determines whether the specified character is a valid hexadecimal digit. + /// Character to check. + /// true if is a valid hex digit; false otherwise. + internal static bool IsCharHexDigit(char c) + { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } + + /// + /// Tries to remove a literal from the specified . + /// + /// Prefix to remove; one-letter prefixes are case-sensitive, others insensitive. + /// Text to attempt to remove prefix from. + /// true if the prefix was found and removed; false otherwise. + /// Copy of WebConvert.TryRemoveLiteralPrefix. + internal static bool TryRemovePrefix(string prefix, ref string text) + { + return TryRemoveLiteralPrefix(prefix, ref text); + } + + /// + /// Removes quotes from the single-quotes text. + /// + /// Text to remove quotes from. + /// Whether quotes were successfully removed. + /// Copy of WebConvert.TryRemoveQuotes. + internal static bool TryRemoveQuotes(ref string text) + { + Debug.Assert(text != null, "text != null"); + + if (text.Length < 2) + { + return false; + } + + char quote = text[0]; + if (quote != '\'' || text[text.Length - 1] != quote) + { + return false; + } + + string s = text.Substring(1, text.Length - 2); + int start = 0; + while (true) + { + int i = s.IndexOf(quote, start); + if (i < 0) + { + break; + } + + s = s.Remove(i, 1); + if (s.Length < i + 1 || s[i] != quote) + { + return false; + } + + start = i + 1; + } + + text = s; + return true; + } + + /// + /// Removes quotes from the single-quotes text. + /// + /// Text to remove quotes from. + /// The specified with single quotes removed. + /// Copy of WebConvert.RemoveQuotes. + /// TODO: Consider combine this method with the method 'TryRemoveQuotes' + internal static string RemoveQuotes(string text) + { + Debug.Assert(!String.IsNullOrEmpty(text), "!String.IsNullOrEmpty(text)"); + + char quote = text[0]; + Debug.Assert(quote == '\'', "quote == '\''"); + Debug.Assert(text[text.Length - 1] == '\'', "text should end with '\''."); + + string s = text.Substring(1, text.Length - 2); + int start = 0; + while (true) + { + int i = s.IndexOf(quote, start); + if (i < 0) + { + break; + } + + Debug.Assert(i + 1 < s.Length && s[i + 1] == '\'', @"Each single quote should be propertly escaped with double single quotes."); + s = s.Remove(i, 1); + start = i + 1; + } + + return s; + } + + /// + /// Check and strip the input for literal + /// + /// The suffix value + /// The string to check + /// A string that has been striped of the suffix + /// Copy of WebConvert.TryRemoveLiteralSuffix. + internal static bool TryRemoveLiteralSuffix(string suffix, ref string text) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(suffix != null, "suffix != null"); + + text = text.Trim(); + if (text.Length <= suffix.Length || IsValidNumericConstant(text) || !text.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + else + { + text = text.Substring(0, text.Length - suffix.Length); + return true; + } + } + + /// + /// Tries to remove a literal from the specified . + /// + /// Prefix to remove; one-letter prefixes are case-sensitive, others insensitive. + /// Text to attempt to remove prefix from. + /// true if the prefix was found and removed; false otherwise. + /// Copy of WebConvert.TryRemoveLiteralPrefix. + internal static bool TryRemoveLiteralPrefix(string prefix, ref string text) + { + Debug.Assert(prefix != null, "prefix != null"); + + if (text.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + text = text.Remove(0, prefix.Length); + return true; + } + else + { + return false; + } + } + + /// + /// Make sure the given literal contains letters or '.' only. + /// + /// typePrefixLiteralName + /// Literal is not valid + internal static void ValidatePrefixLiteral(string typePrefixLiteralName) + { + bool isLettersOnly = typePrefixLiteralName.ToCharArray().All(x => char.IsLetter(x) || x == '.'); + + if (!isLettersOnly) + { + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, ODataErrorStrings.UriParserHelper_InvalidPrefixLiteral(typePrefixLiteralName))); + } + } + + /// + /// Checks whether the specified text is a correctly formatted quoted value. + /// + /// Text to check. + /// true if the text is correctly formatted, false otherwise. + /// Copy of WebConvert.IsKeyValueQuoted. + internal static bool IsUriValueQuoted(string text) + { + Debug.Assert(text != null, "text != null"); + + if (text.Length < 2 || text[0] != '\'' || text[text.Length - 1] != '\'') + { + return false; + } + else + { + int startIndex = 1; + while (startIndex < text.Length - 1) + { + // Check whether the Uri contains a valid escaped single quote. + // Example: 'aaa''bbb'. + int match = text.IndexOf('\'', startIndex, text.Length - startIndex - 1); + if (match == -1) + { + break; + } + else if (match == text.Length - 2 || text[match + 1] != '\'') + { + return false; + } + else + { + startIndex = match + 2; + } + } + + return true; + } + } + + internal static IEdmTypeReference GetLiteralEdmTypeReference(ExpressionTokenKind tokenKind) + { + switch (tokenKind) + { + case ExpressionTokenKind.BooleanLiteral: + return EdmCoreModel.Instance.GetBoolean(false); + case ExpressionTokenKind.DecimalLiteral: + return EdmCoreModel.Instance.GetDecimal(false); + case ExpressionTokenKind.StringLiteral: + return EdmCoreModel.Instance.GetString(true); + case ExpressionTokenKind.Int64Literal: + return EdmCoreModel.Instance.GetInt64(false); + case ExpressionTokenKind.IntegerLiteral: + return EdmCoreModel.Instance.GetInt32(false); + case ExpressionTokenKind.DoubleLiteral: + return EdmCoreModel.Instance.GetDouble(false); + case ExpressionTokenKind.SingleLiteral: + return EdmCoreModel.Instance.GetSingle(false); + case ExpressionTokenKind.GuidLiteral: + return EdmCoreModel.Instance.GetGuid(false); + case ExpressionTokenKind.BinaryLiteral: + return EdmCoreModel.Instance.GetBinary(true); + case ExpressionTokenKind.DateLiteral: + return EdmCoreModel.Instance.GetDate(false); + case ExpressionTokenKind.DateTimeOffsetLiteral: + return EdmCoreModel.Instance.GetDateTimeOffset(false); + case ExpressionTokenKind.DurationLiteral: + return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, false); + case ExpressionTokenKind.GeographyLiteral: + return EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.Geography, false); + case ExpressionTokenKind.GeometryLiteral: + return EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.Geometry, false); + case ExpressionTokenKind.QuotedLiteral: + return EdmCoreModel.Instance.GetString(true); + case ExpressionTokenKind.TimeOfDayLiteral: + return EdmCoreModel.Instance.GetTimeOfDay(false); + } + + return null; + } + + /// + /// Determines whether or not an identifier is an annotation term name + /// + /// The identifer that may be an annotation term name + /// True if the identifier is an annotation term, otherwise false + internal static bool IsAnnotation(string identifier) + { + return !string.IsNullOrEmpty(identifier) && identifier[0] == UriQueryConstants.AnnotationPrefix && identifier.Contains("."); + } + + #endregion + + #region Private Methods + + /// + /// Checks if text is '-INF' or 'INF' or 'NaN'. + /// + /// numeric string + /// true or false + private static bool IsValidNumericConstant(string text) + { + return string.Equals(text, ExpressionConstants.InfinityLiteral, StringComparison.OrdinalIgnoreCase) + || string.Equals(text, "-" + ExpressionConstants.InfinityLiteral, StringComparison.OrdinalIgnoreCase) + || string.Equals(text, ExpressionConstants.NaNLiteral, StringComparison.OrdinalIgnoreCase); + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPathParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPathParser.cs new file mode 100644 index 0000000..f775aa2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPathParser.cs @@ -0,0 +1,167 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.OData.UriParser +{ + /// + /// Parser which consumes the URI path and produces the lexical object model. + /// + public class UriPathParser + { + /// + /// The maximum number of segments allowed. + /// + private readonly int maxSegments; + + /// + /// Constructor. + /// + /// The Uri parser settings. + public UriPathParser(ODataUriParserSettings settings) + { + this.maxSegments = settings.PathLimit; + } + + /// + /// Returns list of segments in the specified path (eg: /abc/pqr -> abc, pqr). + /// + /// The full URI of the request. + /// The service base URI for the request. + /// List of unescaped segments. + public virtual ICollection ParsePathIntoSegments(Uri fullUri, Uri serviceBaseUri) + { + if (serviceBaseUri == null) + { + Debug.Assert(!fullUri.IsAbsoluteUri, "fullUri must be relative Uri"); + serviceBaseUri = UriUtils.CreateMockAbsoluteUri(); + fullUri = UriUtils.CreateMockAbsoluteUri(fullUri); + } + + if (!UriUtils.UriInvariantInsensitiveIsBaseOf(serviceBaseUri, fullUri)) + { + throw new ODataException(Strings.UriQueryPathParser_RequestUriDoesNotHaveTheCorrectBaseUri(fullUri, serviceBaseUri)); + } + + // COMPAT 29: Slash in key lookup breaks URI parser + // TODO: The code below has a bug that / in the named values will be considered a segment separator + // so for example /Customers('abc/pqr') is treated as two segments, which is wrong. + try + { + Uri uri = fullUri; + int numberOfSegmentsToSkip = 0; + + // Skip over the base URI segments +#if !ORCAS + // need to calculate the number of segments to skip in the full + // uri (so that we can skip over http://blah.com/basePath for example, + // get only the odata specific parts of the path). + // + // because of differences in system.uri between portable lib and + // the desktop library, we need to handle this differently. + // in this case we get the number of segments to skip as simply + // then number of tokens in the serviceBaseUri split on slash, with + // length - 1 since its a zero based array. + numberOfSegmentsToSkip = serviceBaseUri.AbsolutePath.Split('/').Length - 1; + string[] uriSegments = uri.AbsolutePath.Split('/'); +#else + numberOfSegmentsToSkip = serviceBaseUri.Segments.Length; + string[] uriSegments = uri.Segments; +#endif + + int escapedStart = -1; + List segments = new List(); + for (int i = numberOfSegmentsToSkip; i < uriSegments.Length; i++) + { + string segment = uriSegments[i]; + + // Skip the empty segment or the "/" segment + if (segment.Length == 0 || segment == "/") + { + continue; + } + + // When we use "uri.Segments" to get the segments, + // The segment element includes the "/", we should remove that. + if (segment[segment.Length - 1] == '/') + { + segment = segment.Substring(0, segment.Length - 1); + } + + if (segments.Count == this.maxSegments) + { + throw new ODataException(Strings.UriQueryPathParser_TooManySegments); + } + + // Handle the "root...::/{xyz}" + if (segment.Length >= 2 && segment.EndsWith("::", StringComparison.Ordinal)) + { + // It should be the terminal of the provious escape segment and the start of next escape semgent. + // Otherwise, it's an invalid Uri. + if (escapedStart == -1) + { + throw new ODataException(Strings.UriQueryPathParser_InvalidEscapeUri(segment)); + } + else + { + string value = String.Join("/", uriSegments, escapedStart, i - escapedStart + 1); + segments.Add(":" + value.Substring(0, value.Length - 1)); // because the last one has "::", remove one. + escapedStart = i + 1; + } + } + else if (segment.Length >= 1 && segment[segment.Length - 1] == ':') + { + // root:/{abc}.... + if (escapedStart == -1) + { + if (segment != ":") + { + segments.Add(segment.Substring(0, segment.Length - 1)); // remove the last ':' + } + + escapedStart = i + 1; + } + else + { + // root:/{abc}:.... + string escapedSegment = ":" + String.Join("/", uriSegments, escapedStart, i - escapedStart + 1); // the last has one ":"; + segments.Add(escapedSegment); + escapedStart = -1; + } + } + else + { + // if we didn't find a starting escape, the current segment is normal segment, accept it. + // otherwise, it's part of the escape, skip it and process it when we find the ending delimiter. + if (escapedStart == -1) + { + segments.Add(Uri.UnescapeDataString(segment)); + } + } + } + + if (escapedStart != -1 && escapedStart < uriSegments.Length) + { + string escapedSegment = ":" + String.Join("/", uriSegments, escapedStart, uriSegments.Length - escapedStart); + segments.Add(escapedSegment); // We should not use "segments.Add(Uri.UnescapeDataString(escapedSegment));" to keep the orignal string. + } + + return segments.ToArray(); + } +#if !ORCAS + catch (FormatException uriFormatException) +#else + catch (UriFormatException uriFormatException) +#endif + { + throw new ODataException(Strings.UriQueryPathParser_SyntaxError, uriFormatException); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPrimitiveTypeParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPrimitiveTypeParser.cs new file mode 100644 index 0000000..9d256b7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriPrimitiveTypeParser.cs @@ -0,0 +1,422 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; +using System.Xml; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; +using Microsoft.Spatial; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Parser which consumes the URI format of primitive types and converts it to primitive types. + /// + [System.Runtime.InteropServices.Guid("A77303D9-3A04-4829-BDDE-3B4D43E21CFC")] + internal sealed class UriPrimitiveTypeParser : IUriLiteralParser + { + #region Singletons + + private static UriPrimitiveTypeParser singleInstance = new UriPrimitiveTypeParser(); + + private UriPrimitiveTypeParser() + { + } + + public static UriPrimitiveTypeParser Instance + { + get + { + return singleInstance; + } + } + + #endregion + + #region IUriLiteralParser Implementation + + public object ParseUriStringToType(string text, IEdmTypeReference targetType, out UriLiteralParsingException parsingException) + { + object targetValue; + + if (this.TryUriStringToPrimitive(text, targetType, out targetValue, out parsingException)) + { + return targetValue; + } + + return null; + } + + #endregion + + #region Internal Methods + + internal bool TryParseUriStringToType(string text, IEdmTypeReference targetType, out object targetValue, out UriLiteralParsingException parsingException) + { + return this.TryUriStringToPrimitive(text, targetType, out targetValue, out parsingException); + } + + #endregion + + #region Private Methods + + /// Converts a string to a primitive value. + /// String text to convert. + /// Type to convert string to. + /// After invocation, converted value. + /// The detailed parsing exception. + /// true if the value was converted; false otherwise. + /// Copy of the WebConvert.TryKeyStringToPrimitive + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Complexity is not too high; handling all the cases in one method is preferable to refactoring.")] + private bool TryUriStringToPrimitive(string text, IEdmTypeReference targetType, out object targetValue, out UriLiteralParsingException exception) + { + Debug.Assert(text != null, "text != null"); + Debug.Assert(targetType != null, "targetType != null"); + exception = null; + + try + { + if (targetType.IsNullable) + { + // COMPAT 38: Product does not support "null" literals in service operation arguments + // check for the 'null' constant for nullable types + if (text == ExpressionConstants.KeywordNull) + { + targetValue = null; + return true; + } + } + + IEdmPrimitiveTypeReference primitiveTargetType = targetType.AsPrimitiveOrNull(); + if (primitiveTargetType == null) + { + targetValue = null; + return false; + } + + EdmPrimitiveTypeKind targetTypeKind = primitiveTargetType.PrimitiveKind(); + + byte[] byteArrayValue; + bool binaryResult = TryUriStringToByteArray(text, out byteArrayValue); + if (targetTypeKind == EdmPrimitiveTypeKind.Binary) + { + targetValue = (object)byteArrayValue; + return binaryResult; + } + else if (binaryResult) + { + string keyValue = Encoding.UTF8.GetString(byteArrayValue, 0, byteArrayValue.Length); + return this.TryUriStringToPrimitive(keyValue, targetType, out targetValue); + } + else if (targetTypeKind == EdmPrimitiveTypeKind.Guid) + { + Guid guidValue; + bool result = UriUtils.TryUriStringToGuid(text, out guidValue); + targetValue = guidValue; + return result; + } + else if (targetTypeKind == EdmPrimitiveTypeKind.Date) + { + Date dateValue; + bool result = UriUtils.TryUriStringToDate(text, out dateValue); + targetValue = dateValue; + return result; + } + else if (targetTypeKind == EdmPrimitiveTypeKind.DateTimeOffset) + { + DateTimeOffset dateTimeOffsetValue; + bool result = UriUtils.ConvertUriStringToDateTimeOffset(text, out dateTimeOffsetValue); + targetValue = dateTimeOffsetValue; + return result; + } + else if (targetTypeKind == EdmPrimitiveTypeKind.Duration) + { + TimeSpan timespanValue; + bool result = TryUriStringToDuration(text, out timespanValue); + targetValue = timespanValue; + return result; + } + else if (targetTypeKind == EdmPrimitiveTypeKind.Geography) + { + Geography geographyValue; + bool result = TryUriStringToGeography(text, out geographyValue, out exception); + targetValue = geographyValue; + return result; + } + else if (targetTypeKind == EdmPrimitiveTypeKind.Geometry) + { + Geometry geometryValue; + bool result = TryUriStringToGeometry(text, out geometryValue, out exception); + targetValue = geometryValue; + return result; + } + else if (targetTypeKind == EdmPrimitiveTypeKind.TimeOfDay) + { + TimeOfDay timeOfDayValue; + bool result = UriUtils.TryUriStringToTimeOfDay(text, out timeOfDayValue); + targetValue = timeOfDayValue; + return result; + } + + bool quoted = targetTypeKind == EdmPrimitiveTypeKind.String; + if (quoted != UriParserHelper.IsUriValueQuoted(text)) + { + targetValue = null; + return false; + } + + if (quoted) + { + text = UriParserHelper.RemoveQuotes(text); + } + + try + { + switch (targetTypeKind) + { + case EdmPrimitiveTypeKind.String: + targetValue = text; + break; + case EdmPrimitiveTypeKind.Boolean: + targetValue = XmlConvert.ToBoolean(text); + break; + case EdmPrimitiveTypeKind.Byte: + targetValue = XmlConvert.ToByte(text); + break; + case EdmPrimitiveTypeKind.SByte: + targetValue = XmlConvert.ToSByte(text); + break; + case EdmPrimitiveTypeKind.Int16: + targetValue = XmlConvert.ToInt16(text); + break; + case EdmPrimitiveTypeKind.Int32: + targetValue = XmlConvert.ToInt32(text); + break; + case EdmPrimitiveTypeKind.Int64: + UriParserHelper.TryRemoveLiteralSuffix(ExpressionConstants.LiteralSuffixInt64, ref text); + targetValue = XmlConvert.ToInt64(text); + break; + case EdmPrimitiveTypeKind.Single: + UriParserHelper.TryRemoveLiteralSuffix(ExpressionConstants.LiteralSuffixSingle, ref text); + targetValue = XmlConvert.ToSingle(text); + break; + case EdmPrimitiveTypeKind.Double: + UriParserHelper.TryRemoveLiteralSuffix(ExpressionConstants.LiteralSuffixDouble, ref text); + targetValue = XmlConvert.ToDouble(text); + break; + case EdmPrimitiveTypeKind.Decimal: + UriParserHelper.TryRemoveLiteralSuffix(ExpressionConstants.LiteralSuffixDecimal, ref text); + try + { + targetValue = XmlConvert.ToDecimal(text); + } + catch (FormatException) + { + // we need to support exponential format for decimals since we used to support them in V1 + decimal result; + if (Decimal.TryParse(text, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out result)) + { + targetValue = result; + } + else + { + targetValue = default(Decimal); + return false; + } + } + + break; + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.UriPrimitiveTypeParser_TryUriStringToPrimitive)); + } + + return true; + } + catch (FormatException) + { + targetValue = null; + return false; + } + catch (OverflowException) + { + targetValue = null; + return false; + } + } + catch (Exception primitiveParserException) + { + exception = new UriLiteralParsingException( + string.Format(CultureInfo.InvariantCulture, ODataErrorStrings.UriPrimitiveTypeParsers_FailedToParseTextToPrimitiveValue(text, targetType), + primitiveParserException)); + targetValue = null; + return false; + } + } + + /// Converts a string to a primitive value. + /// String text to convert. + /// Type to convert string to. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + /// Copy of the WebConvert.TryKeyStringToPrimitive + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Complexity is not too high; handling all the cases in one method is preferable to refactoring.")] + private bool TryUriStringToPrimitive(string text, IEdmTypeReference targetType, out object targetValue) + { + UriLiteralParsingException exception; + return this.TryUriStringToPrimitive(text, targetType, out targetValue, out exception); + } + + /// + /// Converts a string to a byte[] value. + /// + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + /// Copy of WebConvert.TryKeyStringToByteArray. + private static bool TryUriStringToByteArray(string text, out byte[] targetValue) + { + Debug.Assert(text != null, "text != null"); + + if (!UriParserHelper.TryRemoveLiteralPrefix(ExpressionConstants.LiteralPrefixBinary, ref text)) + { + targetValue = null; + return false; + } + + if (!UriParserHelper.TryRemoveQuotes(ref text)) + { + targetValue = null; + return false; + } + + try + { + targetValue = Convert.FromBase64String(text); + } + catch (FormatException) + { + targetValue = null; + return false; + } + + return true; + } + + /// + /// Converts a string to a Duration value. + /// + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + /// Copy of WebConvert.TryKeyStringToTime. + private static bool TryUriStringToDuration(string text, out TimeSpan targetValue) + { + if (!UriParserHelper.TryRemoveLiteralPrefix(ExpressionConstants.LiteralPrefixDuration, ref text)) + { + targetValue = default(TimeSpan); + return false; + } + + if (!UriParserHelper.TryRemoveQuotes(ref text)) + { + targetValue = default(TimeSpan); + return false; + } + + try + { + targetValue = EdmValueParser.ParseDuration(text); + return true; + } + catch (FormatException) + { + targetValue = default(TimeSpan); + return false; + } + } + + /// + /// Try to parse the given text to a Geography object. + /// + /// Text to parse. + /// Geography to return. + /// The detailed reason of parsing error. + /// True if succeeds, false if not. + private static bool TryUriStringToGeography(string text, out Geography targetValue, out UriLiteralParsingException parsingException) + { + parsingException = null; + + if (!UriParserHelper.TryRemoveLiteralPrefix(ExpressionConstants.LiteralPrefixGeography, ref text)) + { + targetValue = default(Geography); + return false; + } + + if (!UriParserHelper.TryRemoveQuotes(ref text)) + { + targetValue = default(Geography); + return false; + } + + try + { + targetValue = LiteralUtils.ParseGeography(text); + return true; + } + catch (ParseErrorException e) + { + targetValue = default(Geography); + parsingException = new UriLiteralParsingException(e.Message); + return false; + } + } + + /// + /// Try to parse the given text to a Geometry object. + /// + /// Text to parse. + /// Geometry to return. + /// The detailed reason of parsing error. + /// True if succeeds, false if not. + private static bool TryUriStringToGeometry(string text, out Geometry targetValue, out UriLiteralParsingException parsingFailureReasonException) + { + parsingFailureReasonException = null; + + if (!UriParserHelper.TryRemoveLiteralPrefix(ExpressionConstants.LiteralPrefixGeometry, ref text)) + { + targetValue = default(Geometry); + return false; + } + + if (!UriParserHelper.TryRemoveQuotes(ref text)) + { + targetValue = default(Geometry); + return false; + } + + try + { + targetValue = LiteralUtils.ParseGeometry(text); + return true; + } + catch (ParseErrorException e) + { + targetValue = default(Geometry); + + parsingFailureReasonException = + new UriLiteralParsingException(e.Message); + return false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs new file mode 100644 index 0000000..2d446c9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs @@ -0,0 +1,1302 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.UriParser.Aggregation; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// Parser which consumes the query expression ($filter, $orderby) and produces the lexical object model. + /// + public sealed class UriQueryExpressionParser + { + /// + /// The maximum number of recursion nesting allowed. + /// + private readonly int maxDepth; + + /// + /// List of supported $apply keywords + /// + private static readonly string supportedKeywords = string.Join("|", new string[] { ExpressionConstants.KeywordAggregate, ExpressionConstants.KeywordFilter, ExpressionConstants.KeywordGroupBy, ExpressionConstants.KeywordCompute, ExpressionConstants.KeywordExpand }); + + /// + /// Set of parsed parameters + /// + private readonly HashSet parameters = new HashSet(StringComparer.Ordinal) + { + ExpressionConstants.It + }; + + /// + /// The current recursion depth. + /// + private int recursionDepth; + + /// + /// The lexer being used for the parsing. + /// + private ExpressionLexer lexer; + + /// + /// Whether to allow case insensitive for builtin identifier. + /// + private bool enableCaseInsensitiveBuiltinIdentifier = false; + + /// + /// Tracks the depth of aggregate expression recursion. + /// + private int parseAggregateExpresionDepth = 0; + + /// + /// Tracks expression parents of aggregate expression recursion. + /// + private Stack aggregateExpressionParents = new Stack(); + + /// + /// Creates a UriQueryExpressionParser. + /// + /// The maximum depth of each part of the query - a recursion limit. + public UriQueryExpressionParser(int maxDepth) + : this(maxDepth, false) + { + } + + /// + /// Constructor. + /// + /// The maximum depth of each part of the query - a recursion limit. + /// Whether to allow case insensitive for builtin identifier. + internal UriQueryExpressionParser(int maxDepth, bool enableCaseInsensitiveBuiltinIdentifier = false) + { + Debug.Assert(maxDepth >= 0, "maxDepth >= 0"); + + this.maxDepth = maxDepth; + this.enableCaseInsensitiveBuiltinIdentifier = enableCaseInsensitiveBuiltinIdentifier; + } + + /// + /// Constructor. + /// + /// The maximum depth of each part of the query - a recursion limit. + /// The ExpressionLexer containing text to be parsed. + internal UriQueryExpressionParser(int maxDepth, ExpressionLexer lexer) : this(maxDepth) + { + Debug.Assert(lexer != null, "lexer != null"); + this.lexer = lexer; + } + + /// + /// Delegate for a function that parses an expression and translates it into a QueryToken. + /// + /// A QueryToken + internal delegate QueryToken Parser(); + + /// + /// Reference to the lexer. + /// + internal ExpressionLexer Lexer + { + get { return this.lexer; } + } + + /// + /// Gets if this parser is currently within an aggregate expression parsing stack. + /// + private bool IsInAggregateExpression + { + get + { + return this.parseAggregateExpresionDepth > 0; + } + } + + /// + /// Parses the $filter expression. + /// + /// The $filter expression string to parse. + /// The lexical token representing the filter. + public QueryToken ParseFilter(string filter) + { + return this.ParseExpressionText(filter); + } + + /// + /// Parses a literal. + /// + /// The lexer to use. + /// The literal query token or null if something else was found. + internal static LiteralToken TryParseLiteral(ExpressionLexer lexer) + { + Debug.Assert(lexer != null, "lexer != null"); + + switch (lexer.CurrentToken.Kind) + { + case ExpressionTokenKind.BooleanLiteral: + case ExpressionTokenKind.DateLiteral: + case ExpressionTokenKind.DecimalLiteral: + case ExpressionTokenKind.StringLiteral: + case ExpressionTokenKind.Int64Literal: + case ExpressionTokenKind.IntegerLiteral: + case ExpressionTokenKind.DoubleLiteral: + case ExpressionTokenKind.SingleLiteral: + case ExpressionTokenKind.GuidLiteral: + case ExpressionTokenKind.BinaryLiteral: + case ExpressionTokenKind.GeographyLiteral: + case ExpressionTokenKind.GeometryLiteral: + case ExpressionTokenKind.QuotedLiteral: + case ExpressionTokenKind.DurationLiteral: + case ExpressionTokenKind.TimeOfDayLiteral: + case ExpressionTokenKind.DateTimeOffsetLiteral: + case ExpressionTokenKind.CustomTypeLiteral: + IEdmTypeReference literalEdmTypeReference = lexer.CurrentToken.GetLiteralEdmTypeReference(); + + // Why not using EdmTypeReference.FullName? (literalEdmTypeReference.FullName) + string edmConstantName = GetEdmConstantNames(literalEdmTypeReference); + return ParseTypedLiteral(lexer, literalEdmTypeReference, edmConstantName); + + case ExpressionTokenKind.BracedExpression: + case ExpressionTokenKind.BracketedExpression: + case ExpressionTokenKind.ParenthesesExpression: + { + LiteralToken result = new LiteralToken(lexer.CurrentToken.Text, lexer.CurrentToken.Text); + lexer.NextToken(); + return result; + } + + case ExpressionTokenKind.NullLiteral: + return ParseNullLiteral(lexer); + + default: + return null; + } + } + + internal static string GetEdmConstantNames(IEdmTypeReference edmTypeReference) + { + Debug.Assert(edmTypeReference != null, "Cannot be null"); + + switch (edmTypeReference.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Boolean: + return Microsoft.OData.Metadata.EdmConstants.EdmBooleanTypeName; + case EdmPrimitiveTypeKind.TimeOfDay: + return Microsoft.OData.Metadata.EdmConstants.EdmTimeOfDayTypeName; + case EdmPrimitiveTypeKind.Date: + return Microsoft.OData.Metadata.EdmConstants.EdmDateTypeName; + case EdmPrimitiveTypeKind.DateTimeOffset: + return Microsoft.OData.Metadata.EdmConstants.EdmDateTimeOffsetTypeName; + case EdmPrimitiveTypeKind.Duration: + return Microsoft.OData.Metadata.EdmConstants.EdmDurationTypeName; + case EdmPrimitiveTypeKind.Decimal: + return Microsoft.OData.Metadata.EdmConstants.EdmDecimalTypeName; + case EdmPrimitiveTypeKind.String: + return Microsoft.OData.Metadata.EdmConstants.EdmStringTypeName; + case EdmPrimitiveTypeKind.Int64: + return Microsoft.OData.Metadata.EdmConstants.EdmInt64TypeName; + case EdmPrimitiveTypeKind.Int32: + return Microsoft.OData.Metadata.EdmConstants.EdmInt32TypeName; + case EdmPrimitiveTypeKind.Double: + return Microsoft.OData.Metadata.EdmConstants.EdmDoubleTypeName; + case EdmPrimitiveTypeKind.Single: + return Microsoft.OData.Metadata.EdmConstants.EdmSingleTypeName; + case EdmPrimitiveTypeKind.Guid: + return Microsoft.OData.Metadata.EdmConstants.EdmGuidTypeName; + case EdmPrimitiveTypeKind.Binary: + return Microsoft.OData.Metadata.EdmConstants.EdmBinaryTypeName; + case EdmPrimitiveTypeKind.Geography: + return Microsoft.OData.Metadata.EdmConstants.EdmGeographyTypeName; + case EdmPrimitiveTypeKind.Geometry: + return Microsoft.OData.Metadata.EdmConstants.EdmGeometryTypeName; + default: + return edmTypeReference.Definition.FullTypeName(); + } + } + + // parses $apply compute expression (.e.g. compute(UnitPrice mul SalesPrice as computePrice) + internal ComputeToken ParseCompute() + { + Debug.Assert(TokenIdentifierIs(ExpressionConstants.KeywordCompute), "token identifier is compute"); + + lexer.NextToken(); + + // '(' + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + lexer.NextToken(); + + List transformationTokens = new List(); + + while (true) + { + ComputeExpressionToken computed = this.ParseComputeExpression(); + transformationTokens.Add(computed); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) + { + break; + } + + this.lexer.NextToken(); + } + + // ")" + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrCommaExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + return new ComputeToken(transformationTokens); + } + + // parses $compute query option. + internal ComputeToken ParseCompute(string compute) + { + Debug.Assert(compute != null, "compute != null"); + + List transformationTokens = new List(); + + if (string.IsNullOrEmpty(compute)) + { + return new ComputeToken(transformationTokens); + } + + this.recursionDepth = 0; + this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(compute); + + while (true) + { + ComputeExpressionToken computed = this.ParseComputeExpression(); + transformationTokens.Add(computed); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) + { + break; + } + + this.lexer.NextToken(); + } + + this.lexer.ValidateToken(ExpressionTokenKind.End); + + return new ComputeToken(transformationTokens); + } + + internal ExpandToken ParseExpand() + { + Debug.Assert(TokenIdentifierIs(ExpressionConstants.KeywordExpand), "token identifier is expand"); + lexer.NextToken(); + + // '(' + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + List termTokens = new List(); + + // First token must be Path + var termParser = new SelectExpandTermParser(this.lexer, this.maxDepth - 1, false); + PathSegmentToken pathToken = termParser.ParseTerm(allowRef: true); + + QueryToken filterToken = null; + ExpandToken nestedExpand = null; + + // Followed (optionally) by filter and expand + // Syntax for expand inside $apply is different (and much simplier) from $expand clause => had to use different parsing approach + while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Comma) + { + this.lexer.NextToken(); + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Identifier) + { + switch (this.lexer.CurrentToken.GetIdentifier()) + { + case ExpressionConstants.KeywordFilter: + filterToken = this.ParseApplyFilter(); + break; + case ExpressionConstants.KeywordExpand: + ExpandToken tempNestedExpand = ParseExpand(); + nestedExpand = nestedExpand == null + ? tempNestedExpand + : new ExpandToken(nestedExpand.ExpandTerms.Concat(tempNestedExpand.ExpandTerms)); + break; + default: + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_KeywordOrIdentifierExpected(supportedKeywords, this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + } + } + + // Leaf level expands require filter + if (filterToken == null && nestedExpand == null) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_InnerMostExpandRequireFilter(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + ExpandTermToken expandTermToken = new ExpandTermToken(pathToken, filterToken, null, null, null, null, null, null, null, nestedExpand); + termTokens.Add(expandTermToken); + + // ")" + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrCommaExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + + return new ExpandToken(termTokens); + } + + internal IEnumerable ParseApply(string apply) + { + Debug.Assert(apply != null, "apply != null"); + + List transformationTokens = new List(); + + if (string.IsNullOrEmpty(apply)) + { + return transformationTokens; + } + + this.recursionDepth = 0; + this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(apply); + + while (true) + { + switch (this.lexer.CurrentToken.GetIdentifier()) + { + case ExpressionConstants.KeywordAggregate: + transformationTokens.Add(ParseAggregate()); + break; + case ExpressionConstants.KeywordFilter: + transformationTokens.Add(ParseApplyFilter()); + break; + case ExpressionConstants.KeywordGroupBy: + transformationTokens.Add(ParseGroupBy()); + break; + case ExpressionConstants.KeywordCompute: + transformationTokens.Add(ParseCompute()); + break; + case ExpressionConstants.KeywordExpand: + transformationTokens.Add(ParseExpand()); + break; + default: + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_KeywordOrIdentifierExpected(supportedKeywords, this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + // '/' indicates there are more transformations + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Slash) + { + break; + } + + this.lexer.NextToken(); + } + + this.lexer.ValidateToken(ExpressionTokenKind.End); + + return new ReadOnlyCollection(transformationTokens); + } + + // parses $apply aggregate tranformation (.e.g. aggregate(UnitPrice with sum as TotalUnitPrice)) + internal AggregateToken ParseAggregate() + { + Debug.Assert(TokenIdentifierIs(ExpressionConstants.KeywordAggregate), "token identifier is aggregate"); + lexer.NextToken(); + + return new AggregateToken(ParseAggregateExpressions()); + } + + internal List ParseAggregateExpressions() + { + // '(' + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + // series of statements separates by commas + List statements = new List(); + while (true) + { + statements.Add(this.ParseAggregateExpression()); + + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) + { + break; + } + + this.lexer.NextToken(); + } + + // ")" + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrCommaExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + return statements; + } + + internal AggregateTokenBase ParseAggregateExpression() + { + try + { + this.parseAggregateExpresionDepth++; + + // expression + QueryToken expression = ParseLogicalOr(); + + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) + { + // When there's a parenthesis after the expression we have a entity set aggregation. + // The syntax is the same as the aggregate expression itself, so we recurse on ParseAggregateExpressions. + this.aggregateExpressionParents.Push(expression); + List statements = ParseAggregateExpressions(); + this.aggregateExpressionParents.Pop(); + + return new EntitySetAggregateToken(expression, statements); + } + + AggregationMethodDefinition verb; + + // "with" verb + EndPathToken endPathExpression = expression as EndPathToken; + if (endPathExpression != null && endPathExpression.Identifier == ExpressionConstants.QueryOptionCount) + { + // e.g. aggregate($count as Count) + verb = AggregationMethodDefinition.VirtualPropertyCount; + } + else + { + // e.g. aggregate(UnitPrice with sum as Total) + verb = this.ParseAggregateWith(); + } + + // "as" alias + StringLiteralToken alias = this.ParseAggregateAs(); + + return new AggregateExpressionToken(expression, verb, alias.Text); + } + finally + { + this.parseAggregateExpresionDepth--; + } + } + + // parses $apply groupby tranformation (.e.g. groupby(ProductID, CategoryId, aggregate(UnitPrice with sum as TotalUnitPrice)) + internal GroupByToken ParseGroupBy() + { + Debug.Assert(TokenIdentifierIs(ExpressionConstants.KeywordGroupBy), "token identifier is groupby"); + lexer.NextToken(); + + // '(' + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + // '(' + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + // properties + var properties = new List(); + while (true) + { + var expression = this.ParsePrimary() as EndPathToken; + + if (expression == null) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_ExpressionExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + properties.Add(expression); + + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) + { + break; + } + + this.lexer.NextToken(); + } + + // ")" + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrOperatorExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + // optional child transformation + ApplyTransformationToken transformationToken = null; + + // "," (comma) + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Comma) + { + this.lexer.NextToken(); + + if (TokenIdentifierIs(ExpressionConstants.KeywordAggregate)) + { + transformationToken = this.ParseAggregate(); + } + else + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_KeywordOrIdentifierExpected(ExpressionConstants.KeywordAggregate, this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + } + + // ")" + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrCommaExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + return new GroupByToken(properties, transformationToken); + } + + // parses $apply filter tranformation (.e.g. filter(ProductName eq 'Aniseed Syrup')) + internal QueryToken ParseApplyFilter() + { + Debug.Assert(TokenIdentifierIs(ExpressionConstants.KeywordFilter), "token identifier is filter"); + lexer.NextToken(); + + // '(' expression ')' + return this.ParseParenExpression(); + } + + /// + /// Parse compute expression text into a token. + /// + /// The lexical token representing the compute expression text. + internal ComputeExpressionToken ParseComputeExpression() + { + // expression + QueryToken expression = this.ParseExpression(); + + // "as" alias + StringLiteralToken alias = this.ParseAggregateAs(); + + return new ComputeExpressionToken(expression, alias.Text); + } + + /// + /// Parse expression text into Token. + /// + /// The expression string to Parse. + /// The lexical token representing the expression text. + internal QueryToken ParseExpressionText(string expressionText) + { + Debug.Assert(expressionText != null, "expressionText != null"); + + this.recursionDepth = 0; + this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(expressionText); + QueryToken result = this.ParseExpression(); + this.lexer.ValidateToken(ExpressionTokenKind.End); + + return result; + } + + /// + /// Parses the $orderby expression. + /// + /// The $orderby expression string to parse. + /// The enumeraion of lexical tokens representing order by tokens. + internal IEnumerable ParseOrderBy(string orderBy) + { + Debug.Assert(orderBy != null, "orderBy != null"); + + this.recursionDepth = 0; + this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(orderBy); + + List orderByTokens = new List(); + while (true) + { + QueryToken expression = this.ParseExpression(); + bool ascending = true; + if (this.TokenIdentifierIs(ExpressionConstants.KeywordAscending)) + { + this.lexer.NextToken(); + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordDescending)) + { + this.lexer.NextToken(); + ascending = false; + } + + OrderByToken orderByToken = new OrderByToken(expression, ascending ? OrderByDirection.Ascending : OrderByDirection.Descending); + orderByTokens.Add(orderByToken); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) + { + break; + } + + this.lexer.NextToken(); + } + + this.lexer.ValidateToken(ExpressionTokenKind.End); + + return new ReadOnlyCollection(orderByTokens); + } + + /// + /// Parses the expression. + /// + /// The lexical token representing the expression. + internal QueryToken ParseExpression() + { + this.RecurseEnter(); + QueryToken token = this.ParseLogicalOr(); + this.RecurseLeave(); + return token; + } + + /// + /// Creates a new for the given filter, orderby or apply expression. + /// + /// The expression. + /// The lexer for the expression, which will have already moved to the first token. + private static ExpressionLexer CreateLexerForFilterOrOrderByOrApplyExpression(string expression) + { + return new ExpressionLexer(expression, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/); + } + + /// Creates an exception for a parse error. + /// Message text. + /// A new Exception. + private static Exception ParseError(string message) + { + return new ODataException(message); + } + + + /// Creates an exception for a parse error. + /// Message text. + /// Type Parsing exception + /// A new Exception. + private static Exception ParseError(string message, UriLiteralParsingException parsingException) + { + return new ODataException(message, parsingException); + } + + /// + /// Parses parameter alias into token. + /// + /// The lexer to use. + /// The parameter alias token. + private static FunctionParameterAliasToken ParseParameterAlias(ExpressionLexer lexer) + { + Debug.Assert(lexer != null, "lexer != null"); + FunctionParameterAliasToken ret = new FunctionParameterAliasToken(lexer.CurrentToken.Text); + lexer.NextToken(); + return ret; + } + + /// + /// Parses typed literals. + /// + /// The lexer to use. + /// Expected type to be parsed. + /// The EDM type name of the expected type to be parsed. + /// The literal token produced by building the given literal. + private static LiteralToken ParseTypedLiteral(ExpressionLexer lexer, IEdmTypeReference targetTypeReference, string targetTypeName) + { + Debug.Assert(lexer != null, "lexer != null"); + + UriLiteralParsingException typeParsingException; + object targetValue = DefaultUriLiteralParser.Instance.ParseUriStringToType(lexer.CurrentToken.Text, targetTypeReference, out typeParsingException); + + if (targetValue == null) + { + string message; + + if (typeParsingException == null) + { + message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteral( + targetTypeName, + lexer.CurrentToken.Text, + lexer.CurrentToken.Position, + lexer.ExpressionText); + + throw ParseError(message); + } + else + { + message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteralWithReason( + targetTypeName, + lexer.CurrentToken.Text, + lexer.CurrentToken.Position, + lexer.ExpressionText, + typeParsingException.Message); + + throw ParseError(message, typeParsingException); + } + } + + LiteralToken result = new LiteralToken(targetValue, lexer.CurrentToken.Text); + lexer.NextToken(); + return result; + } + + /// + /// Parses null literals. + /// + /// The lexer to use. + /// The literal token produced by building the given literal. + private static LiteralToken ParseNullLiteral(ExpressionLexer lexer) + { + Debug.Assert(lexer != null, "lexer != null"); + Debug.Assert(lexer.CurrentToken.Kind == ExpressionTokenKind.NullLiteral, "this.lexer.CurrentToken.InternalKind == ExpressionTokenKind.NullLiteral"); + + LiteralToken result = new LiteralToken(null, lexer.CurrentToken.Text); + + lexer.NextToken(); + return result; + } + + /// + /// Parses the or operator. + /// + /// The lexical token representing the expression. + private QueryToken ParseLogicalOr() + { + this.RecurseEnter(); + QueryToken left = this.ParseLogicalAnd(); + while (this.TokenIdentifierIs(ExpressionConstants.KeywordOr)) + { + this.lexer.NextToken(); + QueryToken right = this.ParseLogicalAnd(); + left = new BinaryOperatorToken(BinaryOperatorKind.Or, left, right); + } + + this.RecurseLeave(); + return left; + } + + /// + /// Parses the and operator. + /// + /// The lexical token representing the expression. + private QueryToken ParseLogicalAnd() + { + this.RecurseEnter(); + QueryToken left = this.ParseComparison(); + while (this.TokenIdentifierIs(ExpressionConstants.KeywordAnd)) + { + this.lexer.NextToken(); + QueryToken right = this.ParseComparison(); + left = new BinaryOperatorToken(BinaryOperatorKind.And, left, right); + } + + this.RecurseLeave(); + return left; + } + + /// + /// Parses the eq, ne, lt, gt, le, ge, has, and in operators. + /// + /// The lexical token representing the expression. + private QueryToken ParseComparison() + { + this.RecurseEnter(); + QueryToken left = this.ParseAdditive(); + while (true) + { + if (this.TokenIdentifierIs(ExpressionConstants.KeywordIn)) + { + this.lexer.NextToken(); + QueryToken right = this.ParseAdditive(); + left = new InToken(left, right); + } + else + { + BinaryOperatorKind binaryOperatorKind; + if (this.TokenIdentifierIs(ExpressionConstants.KeywordEqual)) + { + binaryOperatorKind = BinaryOperatorKind.Equal; + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordNotEqual)) + { + binaryOperatorKind = BinaryOperatorKind.NotEqual; + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordGreaterThan)) + { + binaryOperatorKind = BinaryOperatorKind.GreaterThan; + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordGreaterThanOrEqual)) + { + binaryOperatorKind = BinaryOperatorKind.GreaterThanOrEqual; + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordLessThan)) + { + binaryOperatorKind = BinaryOperatorKind.LessThan; + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordLessThanOrEqual)) + { + binaryOperatorKind = BinaryOperatorKind.LessThanOrEqual; + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordHas)) + { + binaryOperatorKind = BinaryOperatorKind.Has; + } + else + { + break; + } + + this.lexer.NextToken(); + QueryToken right = this.ParseAdditive(); + left = new BinaryOperatorToken(binaryOperatorKind, left, right); + } + } + + this.RecurseLeave(); + return left; + } + + /// + /// Parses the add, sub operators. + /// + /// The lexical token representing the expression. + private QueryToken ParseAdditive() + { + this.RecurseEnter(); + QueryToken left = this.ParseMultiplicative(); + while (this.TokenIdentifierIs(ExpressionConstants.KeywordAdd) || + this.TokenIdentifierIs(ExpressionConstants.KeywordSub)) + { + BinaryOperatorKind binaryOperatorKind; + if (this.TokenIdentifierIs(ExpressionConstants.KeywordAdd)) + { + binaryOperatorKind = BinaryOperatorKind.Add; + } + else + { + Debug.Assert(this.TokenIdentifierIs(ExpressionConstants.KeywordSub), "Was a new binary operator added?"); + binaryOperatorKind = BinaryOperatorKind.Subtract; + } + + this.lexer.NextToken(); + QueryToken right = this.ParseMultiplicative(); + left = new BinaryOperatorToken(binaryOperatorKind, left, right); + } + + this.RecurseLeave(); + return left; + } + + /// + /// Parses the mul, div, mod operators. + /// + /// The lexical token representing the expression. + private QueryToken ParseMultiplicative() + { + this.RecurseEnter(); + QueryToken left = this.ParseUnary(); + while (this.TokenIdentifierIs(ExpressionConstants.KeywordMultiply) || + this.TokenIdentifierIs(ExpressionConstants.KeywordDivide) || + this.TokenIdentifierIs(ExpressionConstants.KeywordModulo)) + { + BinaryOperatorKind binaryOperatorKind; + if (this.TokenIdentifierIs(ExpressionConstants.KeywordMultiply)) + { + binaryOperatorKind = BinaryOperatorKind.Multiply; + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordDivide)) + { + binaryOperatorKind = BinaryOperatorKind.Divide; + } + else + { + Debug.Assert(this.TokenIdentifierIs(ExpressionConstants.KeywordModulo), "Was a new binary operator added?"); + binaryOperatorKind = BinaryOperatorKind.Modulo; + } + + this.lexer.NextToken(); + QueryToken right = this.ParseUnary(); + left = new BinaryOperatorToken(binaryOperatorKind, left, right); + } + + this.RecurseLeave(); + return left; + } + + /// + /// Parses the -, not unary operators. + /// + /// The lexical token representing the expression. + private QueryToken ParseUnary() + { + this.RecurseEnter(); + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Minus || this.TokenIdentifierIs(ExpressionConstants.KeywordNot)) + { + ExpressionToken operatorToken = this.lexer.CurrentToken; + this.lexer.NextToken(); + if (operatorToken.Kind == ExpressionTokenKind.Minus && (ExpressionLexerUtils.IsNumeric(this.lexer.CurrentToken.Kind))) + { + ExpressionToken numberLiteral = this.lexer.CurrentToken; + numberLiteral.Text = "-" + numberLiteral.Text; + numberLiteral.Position = operatorToken.Position; + this.lexer.CurrentToken = numberLiteral; + this.RecurseLeave(); + return this.ParsePrimary(); + } + + QueryToken operand = this.ParseUnary(); + UnaryOperatorKind unaryOperatorKind; + if (operatorToken.Kind == ExpressionTokenKind.Minus) + { + unaryOperatorKind = UnaryOperatorKind.Negate; + } + else + { + Debug.Assert(operatorToken.IdentifierIs(ExpressionConstants.KeywordNot, enableCaseInsensitiveBuiltinIdentifier), "Was a new unary operator added?"); + unaryOperatorKind = UnaryOperatorKind.Not; + } + + this.RecurseLeave(); + return new UnaryOperatorToken(unaryOperatorKind, operand); + } + + this.RecurseLeave(); + return this.ParsePrimary(); + } + + /// + /// Parses the primary expressions. + /// + /// The lexical token representing the expression. + private QueryToken ParsePrimary() + { + this.RecurseEnter(); + QueryToken expr = this.aggregateExpressionParents.Count > 0 ? this.aggregateExpressionParents.Peek() : null; + if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash) + { + expr = this.ParseSegment(expr); + } + else + { + expr = this.ParsePrimaryStart(); + } + + while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Slash) + { + this.lexer.NextToken(); + if (this.TokenIdentifierIs(ExpressionConstants.KeywordAny)) + { + expr = this.ParseAny(expr); + } + else if (this.TokenIdentifierIs(ExpressionConstants.KeywordAll)) + { + expr = this.ParseAll(expr); + } + else if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash) + { + expr = this.ParseSegment(expr); + } + else + { + IdentifierTokenizer identifierTokenizer = new IdentifierTokenizer(this.parameters, new FunctionCallParser(this.lexer, this, this.IsInAggregateExpression)); + expr = identifierTokenizer.ParseIdentifier(expr); + } + } + + this.RecurseLeave(); + return expr; + } + + /// + /// Handles the start of primary expressions. + /// + /// The lexical token representing the expression. + private QueryToken ParsePrimaryStart() + { + switch (this.lexer.CurrentToken.Kind) + { + case ExpressionTokenKind.ParameterAlias: + { + return ParseParameterAlias(this.lexer); + } + + case ExpressionTokenKind.Identifier: + { + IdentifierTokenizer identifierTokenizer = new IdentifierTokenizer(this.parameters, new FunctionCallParser(this.lexer, this, this.IsInAggregateExpression)); + QueryToken parent = this.aggregateExpressionParents.Count > 0 ? this.aggregateExpressionParents.Peek() : null; + return identifierTokenizer.ParseIdentifier(parent); + } + + case ExpressionTokenKind.OpenParen: + { + return this.ParseParenExpression(); + } + + case ExpressionTokenKind.Star: + { + IdentifierTokenizer identifierTokenizer = new IdentifierTokenizer(this.parameters, new FunctionCallParser(this.lexer, this, this.IsInAggregateExpression)); + return identifierTokenizer.ParseStarMemberAccess(null); + } + + default: + { + QueryToken primitiveLiteralToken = TryParseLiteral(this.lexer); + if (primitiveLiteralToken == null) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_ExpressionExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + return primitiveLiteralToken; + } + } + } + + /// + /// Parses parenthesized expressions. + /// + /// The lexical token representing the expression. + private QueryToken ParseParenExpression() + { + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + QueryToken result = this.ParseExpression(); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrOperatorExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + return result; + } + + /// + /// Parses the Any portion of the query + /// + /// The parent of the Any node. + /// The lexical token representing the Any query. + private QueryToken ParseAny(QueryToken parent) + { + return this.ParseAnyAll(parent, true); + } + + /// + /// Parses the All portion of the query + /// + /// The parent of the All node. + /// The lexical token representing the All query. + private QueryToken ParseAll(QueryToken parent) + { + return this.ParseAnyAll(parent, false); + } + + /// + /// Parses the Any/All portion of the query + /// + /// The parent of the Any/All node. + /// Denotes whether an Any or All is to be parsed. + /// The lexical token representing the Any/All query. + private QueryToken ParseAnyAll(QueryToken parent, bool isAny) + { + this.lexer.NextToken(); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_OpenParenExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + this.lexer.NextToken(); + + // When faced with Any(), return the same thing as if you encountered Any(a : true) + if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) + { + this.lexer.NextToken(); + if (isAny) + { + return new AnyToken(new LiteralToken(true, "True"), null, parent); + } + else + { + return new AllToken(new LiteralToken(true, "True"), null, parent); + } + } + + string parameter = this.lexer.CurrentToken.GetIdentifier(); + if (!this.parameters.Add(parameter)) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_RangeVariableAlreadyDeclared(parameter)); + } + + // read the ':' separating the range variable from the expression. + this.lexer.NextToken(); + this.lexer.ValidateToken(ExpressionTokenKind.Colon); + + this.lexer.NextToken(); + QueryToken expr = this.ParseExpression(); + if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_CloseParenOrCommaExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + // forget about the range variable after parsing the expression for this lambda. + this.parameters.Remove(parameter); + + this.lexer.NextToken(); + if (isAny) + { + return new AnyToken(expr, parameter, parent); + } + else + { + return new AllToken(expr, parameter, parent); + } + } + + /// + /// Parses a segment. + /// + /// The parent of the segment node. + /// The lexical token representing the segment. + private QueryToken ParseSegment(QueryToken parent) + { + string propertyName = this.lexer.CurrentToken.GetIdentifier(); + this.lexer.NextToken(); + if (this.parameters.Contains(propertyName) && parent == null) + { + return new RangeVariableToken(propertyName); + } + + return new InnerPathToken(propertyName, parent, null); + } + + private AggregationMethodDefinition ParseAggregateWith() + { + if (!TokenIdentifierIs(ExpressionConstants.KeywordWith)) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_WithExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + lexer.NextToken(); + + AggregationMethodDefinition verb; + int identifierStartPosition = lexer.CurrentToken.Position; + string methodLabel = lexer.ReadDottedIdentifier(false /* acceptStar */); + + switch (methodLabel) + { + case ExpressionConstants.KeywordAverage: + verb = AggregationMethodDefinition.Average; + break; + case ExpressionConstants.KeywordCountDistinct: + verb = AggregationMethodDefinition.CountDistinct; + break; + case ExpressionConstants.KeywordMax: + verb = AggregationMethodDefinition.Max; + break; + case ExpressionConstants.KeywordMin: + verb = AggregationMethodDefinition.Min; + break; + case ExpressionConstants.KeywordSum: + verb = AggregationMethodDefinition.Sum; + break; + default: + if (!methodLabel.Contains(OData.ExpressionConstants.SymbolDot)) + { + throw ParseError( + ODataErrorStrings.UriQueryExpressionParser_UnrecognizedWithMethod( + methodLabel, + identifierStartPosition, + this.lexer.ExpressionText)); + } + + verb = AggregationMethodDefinition.Custom(methodLabel); + break; + } + + return verb; + } + + private StringLiteralToken ParseAggregateAs() + { + if (!TokenIdentifierIs(ExpressionConstants.KeywordAs)) + { + throw ParseError(ODataErrorStrings.UriQueryExpressionParser_AsExpected(this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); + } + + lexer.NextToken(); + + var alias = new StringLiteralToken(lexer.CurrentToken.Text); + + lexer.NextToken(); + + return alias; + } + + + /// + /// Checks that the current token has the specified identifier. + /// + /// Identifier to check. + /// true if the current token is an identifier with the specified text. + private bool TokenIdentifierIs(string id) + { + return this.lexer.CurrentToken.IdentifierIs(id, enableCaseInsensitiveBuiltinIdentifier); + } + + /// + /// Marks the fact that a recursive method was entered, and checks that the depth is allowed. + /// + private void RecurseEnter() + { + Debug.Assert(this.lexer != null, "Trying to recurse without a lexer, nothing to parse without a lexer."); + Debug.Assert(this.recursionDepth <= this.maxDepth, "The recursion depth was already exceeded, we should have failed."); + + this.recursionDepth++; + if (this.recursionDepth > this.maxDepth) + { + throw new ODataException(ODataErrorStrings.UriQueryExpressionParser_TooDeep); + } + } + + /// + /// Marks the fact that a recursive method is leaving. + /// + private void RecurseLeave() + { + Debug.Assert(this.lexer != null, "Trying to recurse without a lexer, nothing to parse without a lexer."); + Debug.Assert(this.recursionDepth > 0, "Decreasing recursion depth below zero, imbalanced recursion calls."); + + this.recursionDepth--; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriTemplateParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriTemplateParser.cs new file mode 100644 index 0000000..46e2323 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Parsers/UriTemplateParser.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using Microsoft.OData.Edm; + + /// + /// Parser for Uri Template. See class for detail. + /// + internal sealed class UriTemplateParser + { + /// + /// Check whether given literal matches the uri template pattern {literals}, See class for detail. + /// + /// The text to be evaluated + /// True if is valid for Uri template + internal static bool IsValidTemplateLiteral(string literalText) + { + return (!string.IsNullOrEmpty(literalText) + && literalText.StartsWith("{", StringComparison.Ordinal) + && literalText.EndsWith("}", StringComparison.Ordinal)); + } + + /// + /// Parse a literal as Uri template. + /// + /// The input text. + /// The expected type of the object which the Uri template stands for. + /// The representing the Uri template. + /// True if successfully expression parsed, false otherwise. + internal static bool TryParseLiteral(string literalText, IEdmTypeReference expectedType, out UriTemplateExpression expression) + { + if (IsValidTemplateLiteral(literalText)) + { + expression = new UriTemplateExpression { LiteralText = literalText, ExpectedType = expectedType }; + return true; + } + + expression = null; + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryNodeUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryNodeUtils.cs new file mode 100644 index 0000000..2bf0fef --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryNodeUtils.cs @@ -0,0 +1,182 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// Helper methods for working with query nodes. + /// + internal static class QueryNodeUtils + { + /// + /// Pre-look map for BinaryOperator kind resolver + /// + private static Dictionary, EdmPrimitiveTypeKind> additionalMap = + new Dictionary, EdmPrimitiveTypeKind>(EqualityComparer>.Default) + { + { + new Tuple( + BinaryOperatorKind.Add, + EdmPrimitiveTypeKind.DateTimeOffset, + EdmPrimitiveTypeKind.Duration), + EdmPrimitiveTypeKind.DateTimeOffset + }, + { + new Tuple( + BinaryOperatorKind.Add, + EdmPrimitiveTypeKind.Duration, + EdmPrimitiveTypeKind.DateTimeOffset), + EdmPrimitiveTypeKind.DateTimeOffset + }, + { + new Tuple( + BinaryOperatorKind.Add, + EdmPrimitiveTypeKind.Date, + EdmPrimitiveTypeKind.Duration), + EdmPrimitiveTypeKind.Date + }, + { + new Tuple( + BinaryOperatorKind.Add, + EdmPrimitiveTypeKind.Duration, + EdmPrimitiveTypeKind.Date), + EdmPrimitiveTypeKind.Date + }, + { + new Tuple( + BinaryOperatorKind.Add, + EdmPrimitiveTypeKind.Duration, + EdmPrimitiveTypeKind.Duration), + EdmPrimitiveTypeKind.Duration + }, + { + new Tuple( + BinaryOperatorKind.Subtract, + EdmPrimitiveTypeKind.DateTimeOffset, + EdmPrimitiveTypeKind.Duration), + EdmPrimitiveTypeKind.DateTimeOffset + }, + { + new Tuple( + BinaryOperatorKind.Subtract, + EdmPrimitiveTypeKind.DateTimeOffset, + EdmPrimitiveTypeKind.DateTimeOffset), + EdmPrimitiveTypeKind.Duration + }, + { + new Tuple( + BinaryOperatorKind.Subtract, + EdmPrimitiveTypeKind.Date, + EdmPrimitiveTypeKind.Duration), + EdmPrimitiveTypeKind.Date + }, + { + new Tuple( + BinaryOperatorKind.Subtract, + EdmPrimitiveTypeKind.Date, + EdmPrimitiveTypeKind.Date), + EdmPrimitiveTypeKind.Duration + }, + { + new Tuple( + BinaryOperatorKind.Subtract, + EdmPrimitiveTypeKind.Duration, + EdmPrimitiveTypeKind.Duration), + EdmPrimitiveTypeKind.Duration + }, + }; + + /// + /// Compute the result type of a binary operator based on the type of its operands and the operator kind. + /// + /// The type reference on the left hand. + /// The type reference on the right hand. + /// The kind of operator. + /// The result type reference of the binary operator. + internal static IEdmPrimitiveTypeReference GetBinaryOperatorResultType(IEdmPrimitiveTypeReference left, IEdmPrimitiveTypeReference right, BinaryOperatorKind operatorKind) + { + Debug.Assert(left != null, "type != null"); + Debug.Assert(right != null, "type != null"); + + EdmPrimitiveTypeKind kind; + if (additionalMap.TryGetValue(new Tuple(operatorKind, left.PrimitiveKind(), right.PrimitiveKind()), out kind)) + { + return EdmCoreModel.Instance.GetPrimitive(kind, left.IsNullable); + } + + switch (operatorKind) + { + case BinaryOperatorKind.Or: // fall through + case BinaryOperatorKind.And: // fall through + case BinaryOperatorKind.Equal: // fall through + case BinaryOperatorKind.NotEqual: // fall through + case BinaryOperatorKind.GreaterThan: // fall through + case BinaryOperatorKind.GreaterThanOrEqual: // fall through + case BinaryOperatorKind.LessThan: // fall through + case BinaryOperatorKind.LessThanOrEqual: + case BinaryOperatorKind.Has: + return EdmCoreModel.Instance.GetBoolean(left.IsNullable); + + case BinaryOperatorKind.Add: // fall through + case BinaryOperatorKind.Subtract: // fall through + case BinaryOperatorKind.Multiply: // fall through + case BinaryOperatorKind.Divide: // fall through + case BinaryOperatorKind.Modulo: + return left; + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.QueryNodeUtils_BinaryOperatorResultType_UnreachableCodepath)); + } + } + +#if ORCAS + /// + /// The tuple for .NET35 + /// + /// First component type. + /// Second component type. + /// Third component type. + private class Tuple + { + /// + /// Initializes a new instance of this class. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + internal Tuple(T1 first, T2 second, T3 third) + { + First = first; + Second = second; + Third = third; + } + + /// + /// The value of the tuple's first component. + /// + public T1 First { get; private set; } + + /// + /// The value of the tuple's second component. + /// + public T2 Second { get; private set; } + + /// + /// The value of the tuple's third component. + /// + public T3 Third { get; private set; } + } +#endif + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryOptionUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryOptionUtils.cs new file mode 100644 index 0000000..2305a7f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/QueryOptionUtils.cs @@ -0,0 +1,200 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Helper methods for working with query options. + /// + internal static class QueryOptionUtils + { + /// + /// Gets parameter alias names and strings, which starts with '@' char. and remove them from queryOptions collection. + /// + /// The queryOptions collection. + /// The dictionary of parameter alias names and strings. + internal static Dictionary GetParameterAliases(this List queryOptions) + { + Debug.Assert(queryOptions != null, "queryOptions != null"); + Dictionary ret = new Dictionary(StringComparer.Ordinal); + List tokensToRemove = new List(); + foreach (var s in queryOptions) + { + if ((!string.IsNullOrEmpty(s.Name)) && s.Name[0] == '@') + { + ret[s.Name] = s.Value; // always keep the last one if name is duplicated. + tokensToRemove.Add(s); + } + } + + foreach (var s in tokensToRemove) + { + queryOptions.Remove(s); + } + + return ret; + } + + /// + /// Returns a query option value by its name from the collection. + /// + /// The collection of query options. + /// The name of the query option to get. + /// The value of the query option or null if no such query option exists. + internal static string GetQueryOptionValue(this List queryOptions, string queryOptionName) + { + Debug.Assert(queryOptions != null, "queryOptions != null"); + Debug.Assert(queryOptionName == null || queryOptionName.Length > 0, "queryOptionName == null || queryOptionName.Length > 0"); + + CustomQueryOptionToken option = null; + + foreach (var queryOption in queryOptions) + { + if (queryOption.Name == queryOptionName) + { + if (option == null) + { + option = queryOption; + } + else + { + throw new ODataException(Strings.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce(queryOptionName)); + } + } + } + + return option == null ? null : option.Value; + } + + /// + /// Returns a query option value by its name and removes the query option from the collection. + /// Currently, it is only used by un-exposed syntatic tree parsing. + /// + /// The collection of query options. + /// The name of the query option to get. + /// The value of the query option or null if no such query option exists. + internal static string GetQueryOptionValueAndRemove(this List queryOptions, string queryOptionName) + { + Debug.Assert(queryOptions != null, "queryOptions != null"); + Debug.Assert(queryOptionName == null || queryOptionName.Length > 0, "queryOptionName == null || queryOptionName.Length > 0"); + + CustomQueryOptionToken option = null; + + for (int i = 0; i < queryOptions.Count; ++i) + { + if (queryOptions[i].Name == queryOptionName) + { + if (option == null) + { + option = queryOptions[i]; + } + else + { + throw new ODataException(Strings.QueryOptionUtils_QueryParameterMustBeSpecifiedOnce(queryOptionName)); + } + + queryOptions.RemoveAt(i); + i--; + } + } + + return option == null ? null : option.Value; + } + + /// + /// Parses query options from a specified URI into a dictionary. + /// + /// The uri to get the query options from. + /// The parsed query options. + /// This method returns with all the query options. + /// Note that it is valid to include multiple query options with the same name. + internal static List ParseQueryOptions(Uri uri) + { + Debug.Assert(uri != null, "uri != null"); + + List queryOptions = new List(); + + // COMPAT 31: Query options parsing + // This method is a copy of System.Web.HttpValueCollection.FillFromString which is effectively the implementation + // behind the System.Web.HttpUtility.ParseQueryString. + // TODO: The System.Uri class does not replace/unescape URIs that use the '+' character to escape spaces; + // this however is common on the Web (also ASP.Net) and we have to figure out how we want to support it. + string queryString = uri.Query.Replace('+', ' '); + int length; + if (queryString != null) + { + if (queryString.Length > 0 && queryString[0] == '?') + { + queryString = queryString.Substring(1); + } + + length = queryString.Length; + } + else + { + length = 0; + } + + for (int i = 0; i < length; i++) + { + int startIndex = i; + int equalSignIndex = -1; + while (i < length) + { + char ch = queryString[i]; + if (ch == '=') + { + if (equalSignIndex < 0) + { + equalSignIndex = i; + } + } + else if (ch == '&') + { + break; + } + + i++; + } + + string queryOptionsName = null; + string queryOptionValue = null; + if (equalSignIndex >= 0) + { + queryOptionsName = queryString.Substring(startIndex, equalSignIndex - startIndex); + queryOptionValue = queryString.Substring(equalSignIndex + 1, (i - equalSignIndex) - 1); + } + else + { + queryOptionValue = queryString.Substring(startIndex, i - startIndex); + } + + // COMPAT 31: Query options parsing + // The System.Web version of the code uses HttpUtility.UrlDecode here, which calls into System.Web's own implementation + // of the decoder. It's unclear if it's OK to use Uri.UnescapeDataString instead. + queryOptionsName = queryOptionsName == null ? null : Uri.UnescapeDataString(queryOptionsName).Trim(); + queryOptionValue = queryOptionValue == null ? null : Uri.UnescapeDataString(queryOptionValue).Trim(); + + queryOptions.Add(new CustomQueryOptionToken(queryOptionsName, queryOptionValue)); + + if ((i == (length - 1)) && (queryString[i] == '&')) + { + queryOptions.Add(new CustomQueryOptionToken(null, string.Empty)); + } + } + + return queryOptions; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ReadOnlyEnumerableForUriParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ReadOnlyEnumerableForUriParser.cs new file mode 100644 index 0000000..9b198ae --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/ReadOnlyEnumerableForUriParser.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Implementation of IEnumerable which is based on another IEnumerable + /// but only exposes readonly access to that collection. This class doesn't implement + /// any other public interfaces or public API unlike most other IEnumerable implementations + /// which also implement other public interfaces. + /// + /// The type of the items in the read-only enumerable. + internal sealed class ReadOnlyEnumerableForUriParser : IEnumerable + { + /// + /// The IEnumerable to wrap. + /// + private IEnumerable sourceEnumerable; + + /// + /// Constructor. + /// + /// The enumerable to wrap. + internal ReadOnlyEnumerableForUriParser(IEnumerable sourceEnumerable) + { + Debug.Assert(sourceEnumerable != null, "sourceEnumerable != null"); + + this.sourceEnumerable = sourceEnumerable; + } + + /// + /// Returns the enumerator to iterate through the items. + /// + /// The enumerator object to use. + IEnumerator IEnumerable.GetEnumerator() + { + return this.sourceEnumerable.GetEnumerator(); + } + + /// + /// Returns the (non-generic) enumerator to iterate through the items. + /// + /// The enumerator object to use. + IEnumerator IEnumerable.GetEnumerator() + { + return this.sourceEnumerable.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs new file mode 100644 index 0000000..c9bd4a6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs @@ -0,0 +1,142 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Edm; + + /// + /// Implementation for resolving the alternate keys. + /// + public sealed class AlternateKeysODataUriResolver : ODataUriResolver + { + /// + /// Model to be used for resolving the alternate keys. + /// + private readonly IEdmModel model; + + /// + /// Constructs a AlternateKeysODataUriResolver with the given edmModel to be used for resolving alternate keys + /// + /// The model to be used. + public AlternateKeysODataUriResolver(IEdmModel model) + { + this.model = model; + } + + /// + /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') + /// + /// Type for current entityset. + /// The dictionary of name value pairs. + /// The convert function to be used for value converting. + /// The resolved key list. + public override IEnumerable> ResolveKeys(IEdmEntityType type, IDictionary namedValues, Func convertFunc) + { + IEnumerable> convertedPairs; + try + { + convertedPairs = base.ResolveKeys(type, namedValues, convertFunc); + } + catch (ODataException) + { + if (!TryResolveAlternateKeys(type, namedValues, convertFunc, out convertedPairs)) + { + throw; + } + } + + return convertedPairs; + } + + /// + /// Try to resolve alternate keys for certain entity type, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') + /// + /// Type for current entityset. + /// The dictionary of name value pairs. + /// The convert function to be used for value converting. + /// The resolved key list. + /// True if resolve succeeded. + private bool TryResolveAlternateKeys(IEdmEntityType type, IDictionary namedValues, Func convertFunc, out IEnumerable> convertedPairs) + { + IEnumerable> alternateKeys = model.GetAlternateKeysAnnotation(type); + foreach (IDictionary keys in alternateKeys) + { + if (TryResolveKeys(type, namedValues, keys, convertFunc, out convertedPairs)) + { + return true; + } + } + + convertedPairs = null; + return false; + } + + /// + /// Try to resolve keys for certain entity type, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') + /// + /// Type for current entityset. + /// The dictionary of name value pairs. + /// Dictionary of alias to key properties. + /// The convert function to be used for value converting. + /// The resolved key list. + /// True if resolve succeeded. + private bool TryResolveKeys(IEdmEntityType type, IDictionary namedValues, IDictionary keyProperties, Func convertFunc, out IEnumerable> convertedPairs) + { + if (namedValues.Count != keyProperties.Count) + { + // Count of name value pair does not match the alias count in this set of + // alternative keys ==> Unresolvable for this set. + convertedPairs = null; + return false; + } + + Dictionary pairs = new Dictionary(StringComparer.Ordinal); + + foreach (KeyValuePair kvp in keyProperties) + { + string valueText; + + if (!namedValues.TryGetValue(kvp.Key, out valueText) && !EnableCaseInsensitive) + { + convertedPairs = null; + return false; + } + + if (valueText == null) + { + var list = namedValues.Keys.Where(key => string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)).ToList(); + if (list.Count > 1) + { + throw new ODataException(Strings.UriParserMetadata_MultipleMatchingKeysFound(kvp.Key)); + } + else if (list.Count == 0) + { + convertedPairs = null; + return false; + } + + valueText = namedValues[list.Single()]; + } + + object convertedValue = convertFunc(kvp.Value.Type, valueText); + if (convertedValue == null) + { + convertedPairs = null; + return false; + } + + pairs[kvp.Key] = convertedValue; + } + + convertedPairs = pairs; + return true; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/ODataUriResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/ODataUriResolver.cs new file mode 100644 index 0000000..758fd13 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/ODataUriResolver.cs @@ -0,0 +1,459 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.UriParser +{ + /// + /// Class for resolving different kinds of Uri parsing context. + /// + public class ODataUriResolver + { + /// + /// Instance for . + /// + private static readonly ODataUriResolver Default = new ODataUriResolver(); + + /// + /// Promotion rules for type facets. + /// + private TypeFacetsPromotionRules typeFacetsPromotionRules = new TypeFacetsPromotionRules(); + + /// + /// Whether to enable case insensitive for the resolver. + /// + /// + /// All extensions should look at this property and keep case sensitive behavior consistent. + /// + public virtual bool EnableCaseInsensitive { get; set; } + + /// + /// Gets and sets the optional-$-sign-prefix for OData system query option. + /// + /// + /// All extensions should look at this property and keep case sensitive behavior consistent. + /// + public virtual bool EnableNoDollarQueryOptions { get; set; } + + /// + /// Gets and sets promotion rules for type facets. + /// + public TypeFacetsPromotionRules TypeFacetsPromotionRules + { + get + { + return typeFacetsPromotionRules; + } + + set + { + typeFacetsPromotionRules = value; + } + } + + /// + /// Promote the left and right operand types + /// + /// the operator kind + /// the left operand + /// the right operand + /// type reference for the result BinaryOperatorNode. + public virtual void PromoteBinaryOperandTypes( + BinaryOperatorKind binaryOperatorKind, + ref SingleValueNode leftNode, + ref SingleValueNode rightNode, + out IEdmTypeReference typeReference) + { + typeReference = null; + BinaryOperatorBinder.PromoteOperandTypes(binaryOperatorKind, ref leftNode, ref rightNode, typeFacetsPromotionRules); + } + + /// + /// Resolve navigation source from model. + /// + /// The model to be used. + /// The identifier to be resolved. + /// The resolved navigation source. + public virtual IEdmNavigationSource ResolveNavigationSource(IEdmModel model, string identifier) + { + IEdmNavigationSource navSource = model.FindDeclaredNavigationSource(identifier); + if (navSource != null || !EnableCaseInsensitive) + { + return navSource; + } + + IEdmEntityContainer container = model.EntityContainer; + if (container == null) + { + return null; + } + + var result = container.Elements.OfType() + .Where(source => string.Equals(identifier, source.Name, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (result.Count > 1) + { + throw new ODataException(Strings.UriParserMetadata_MultipleMatchingNavigationSourcesFound(identifier)); + } + + return result.SingleOrDefault(); + } + + /// + /// Resolve property from property name + /// + /// The declaring type. + /// The property name. + /// The resolved + public virtual IEdmProperty ResolveProperty(IEdmStructuredType type, string propertyName) + { + IEdmProperty property = type.FindProperty(propertyName); + if (property != null || !EnableCaseInsensitive) + { + return property; + } + + var result = type.Properties() + .Where(_ => string.Equals(propertyName, _.Name, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (result.Count > 1) + { + throw new ODataException(Strings.UriParserMetadata_MultipleMatchingPropertiesFound(propertyName, type.FullTypeName())); + } + + return result.SingleOrDefault(); + } + + /// + /// Resolve term name from model. + /// + /// The model to be used. + /// The term name to be resolved. + /// Resolved term. + public virtual IEdmTerm ResolveTerm(IEdmModel model, string termName) + { + IEdmTerm term = model.FindTerm(termName); + if (term != null || !EnableCaseInsensitive) + { + return term; + } + + IList results = FindAcrossModels(model, termName, /*caseInsensitive*/ true); + + if (results.Count > 1) + { + throw new ODataException(Strings.UriParserMetadata_MultipleMatchingTypesFound(termName)); + } + + return results.SingleOrDefault(); + } + + /// + /// Resolve type name from model. + /// + /// The model to be used. + /// The type name to be resolved. + /// Resolved type. + public virtual IEdmSchemaType ResolveType(IEdmModel model, string typeName) + { + IEdmSchemaType type = model.FindType(typeName); + if (type != null || !EnableCaseInsensitive) + { + return type; + } + + IList results = FindAcrossModels(model, typeName, /*caseInsensitive*/ true); + if (results.Count > 1) + { + throw new ODataException(Strings.UriParserMetadata_MultipleMatchingTypesFound(typeName)); + } + + return results.SingleOrDefault(); + } + + /// + /// Resolve bound operations based on name. + /// + /// The model to be used. + /// The operation name. + /// The type operation was binding to. + /// Resolved operation list. + public virtual IEnumerable ResolveBoundOperations(IEdmModel model, string identifier, IEdmType bindingType) + { + IEnumerable results = model.FindBoundOperations(identifier, bindingType); + if (results.Any() || !EnableCaseInsensitive) + { + return results; + } + + IList operations = FindAcrossModels(model, identifier, /*caseInsensitive*/ true); + if (operations != null && operations.Count() > 0) + { + IList matchedOperation = new List(); + for (int i = 0; i < operations.Count(); i++) + { + if (operations[i].HasEquivalentBindingType(bindingType)) + { + matchedOperation.Add(operations[i]); + } + } + + return matchedOperation; + } + + return Enumerable.Empty(); + } + + /// + /// Resolve unbound operations based on name. + /// + /// The model to be used. + /// The operation name. + /// Resolved operation list. + public virtual IEnumerable ResolveUnboundOperations(IEdmModel model, string identifier) + { + IEnumerable results = model.FindOperations(identifier); + if (results.Any() || !EnableCaseInsensitive) + { + return results; + } + + IList operations = FindAcrossModels(model, identifier, /*caseInsensitive*/ true); + if (operations != null && operations.Count() > 0) + { + IList matchedOperation = new List(); + for (int i = 0; i < operations.Count(); i++) + { + if (!operations[i].IsBound) + { + matchedOperation.Add(operations[i]); + } + } + + return matchedOperation; + } + + return Enumerable.Empty(); + } + + /// + /// Resolve operation imports with certain name. + /// + /// The model to search. + /// The qualified name of the operation import which may or may not include the container name. + /// All operation imports that can be found by the specified name, returns an empty enumerable if no operation import exists. + public virtual IEnumerable ResolveOperationImports(IEdmModel model, string identifier) + { + IEnumerable results = model.FindDeclaredOperationImports(identifier); + if (results.Any() || !EnableCaseInsensitive) + { + return results; + } + + IEdmEntityContainer container = model.EntityContainer; + if (container == null) + { + return null; + } + + return container.Elements.OfType() + .Where(source => string.Equals(identifier, source.Name, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Resolve operation's parameters. + /// + /// Current operation for parameters. + /// A dictionary the parameter list. + /// A dictionary containing resolved parameters. + public virtual IDictionary ResolveOperationParameters(IEdmOperation operation, IDictionary input) + { + Dictionary result = new Dictionary(EqualityComparer.Default); + foreach (var item in input) + { + IEdmOperationParameter functionParameter = null; + if (EnableCaseInsensitive) + { + functionParameter = ResolveOperationParameterNameCaseInsensitive(operation, item.Key); + } + else + { + functionParameter = operation.FindParameter(item.Key); + } + + // ensure parameter name existis + if (functionParameter == null) + { + throw new ODataException(Strings.ODataParameterWriterCore_ParameterNameNotFoundInOperation(item.Key, operation.Name)); + } + + result.Add(functionParameter, item.Value); + } + + return result; + } + + /// + /// Resolve keys for certain entity set, this function would be called when key is specified as positional values. E.g. EntitySet('key') + /// + /// Type for current entityset. + /// The list of positional values. + /// The convert function to be used for value converting. + /// The resolved key list. + public virtual IEnumerable> ResolveKeys(IEdmEntityType type, IList positionalValues, Func convertFunc) + { + var keyProperties = type.Key().ToList(); + + // Throw an error if key size from url doesn't match that from model. + // Other derived ODataUriResolver intended for alternative key resolution, such as the built in AlternateKeysODataUriResolver, + // should override this ResolveKeys method. + if (keyProperties.Count != positionalValues.Count) + { + throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(type.FullName())); + } + + var keyPairList = new List>(positionalValues.Count); + + for (int i = 0; i < keyProperties.Count; i++) + { + string valueText = positionalValues[i]; + IEdmProperty keyProperty = keyProperties[i]; + object convertedValue = convertFunc(keyProperty.Type, valueText); + if (convertedValue == null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + keyPairList.Add(new KeyValuePair(keyProperty.Name, convertedValue)); + } + + return keyPairList; + } + + /// + /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') + /// + /// Type for current entityset. + /// The dictionary of name value pairs. + /// The convert function to be used for value converting. + /// The resolved key list. + public virtual IEnumerable> ResolveKeys(IEdmEntityType type, IDictionary namedValues, Func convertFunc) + { + var convertedPairs = new Dictionary(StringComparer.Ordinal); + var keyProperties = type.Key().ToList(); + + // Throw an error if key size from url doesn't match that from model. + // Other derived ODataUriResolver intended for alternative key resolution, such as the built in AlternateKeysODataUriResolver, + // should override this ResolveKeys method. + if (keyProperties.Count != namedValues.Count) + { + throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(type.FullName())); + } + + foreach (IEdmStructuralProperty property in keyProperties) + { + string valueText; + + if (!namedValues.TryGetValue(property.Name, out valueText)) + { + if (EnableCaseInsensitive) + { + var list = namedValues.Keys.Where(key => string.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase)).ToList(); + if (list.Count > 1) + { + throw new ODataException(Strings.UriParserMetadata_MultipleMatchingKeysFound(property.Name)); + } + else if (list.Count == 0) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + valueText = namedValues[list.Single()]; + } + else + { + throw ExceptionUtil.CreateSyntaxError(); + } + } + + object convertedValue = convertFunc(property.Type, valueText); + if (convertedValue == null) + { + throw ExceptionUtil.CreateSyntaxError(); + } + + convertedPairs[property.Name] = convertedValue; + } + + return convertedPairs; + } + + /// + /// Resolve an operation parameter's name with case insensitive enabled + /// + /// The operation. + /// Name for the parameter. + /// The resolved operation parameter. + internal static IEdmOperationParameter ResolveOperationParameterNameCaseInsensitive(IEdmOperation operation, string identifier) + { + // first look for a case-sensitive match + var list = operation.Parameters.Where(parameter => string.Equals(identifier, parameter.Name, StringComparison.Ordinal)).ToList(); + if (list.Count == 0) + { + // if no case sensitive, try case-insensitive + list = operation.Parameters.Where(parameter => string.Equals(identifier, parameter.Name, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (list.Count > 1) + { + throw new ODataException(Strings.UriParserMetadata_MultipleMatchingParametersFound(identifier)); + } + + if (list.Count == 1) + { + return list.Single(); + } + + return null; + } + + internal static ODataUriResolver GetUriResolver(IServiceProvider container) + { + if (container == null) + { + return Default; + } + + return container.GetRequiredService(); + } + + private static List FindAcrossModels(IEdmModel model, String qualifiedName, bool caseInsensitive) where T : IEdmSchemaElement + { + List results = FindSchemaElements(model, qualifiedName, caseInsensitive).ToList(); + + foreach (IEdmModel reference in model.ReferencedModels) + { + results.AddRange(FindSchemaElements(reference, qualifiedName, caseInsensitive)); + } + + return results; + } + + private static IEnumerable FindSchemaElements(IEdmModel model, string qualifiedName, bool caseInsensitive) where T : IEdmSchemaElement + { + return model.SchemaElements.OfType() + .Where(e => string.Equals(qualifiedName, e.FullName(), caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/StringAsEnumResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/StringAsEnumResolver.cs new file mode 100644 index 0000000..577c249 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/StringAsEnumResolver.cs @@ -0,0 +1,184 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using Microsoft.OData.Edm; + + /// + /// Implementation for resolving a literal value without qualified namespace to enum type. + /// + public sealed class StringAsEnumResolver : ODataUriResolver + { + /// + /// Promote the left and right operand types, supports enum property and string constant scenario. + /// + /// the operator kind + /// the left operand + /// the right operand + /// type reference for the result BinaryOperatorNode. + public override void PromoteBinaryOperandTypes( + BinaryOperatorKind binaryOperatorKind, + ref SingleValueNode leftNode, + ref SingleValueNode rightNode, + out IEdmTypeReference typeReference) + { + typeReference = null; + + if (leftNode.TypeReference != null && rightNode.TypeReference != null) + { + if ((leftNode.TypeReference.IsEnum()) && (rightNode.TypeReference.IsString()) && rightNode is ConstantNode) + { + string text = ((ConstantNode)rightNode).Value as string; + ODataEnumValue val; + IEdmTypeReference typeRef = leftNode.TypeReference; + + if (TryParseEnum(typeRef.Definition as IEdmEnumType, text, out val)) + { + rightNode = new ConstantNode(val, text, typeRef); + return; + } + } + else if ((rightNode.TypeReference.IsEnum()) && (leftNode.TypeReference.IsString()) && leftNode is ConstantNode) + { + string text = ((ConstantNode)leftNode).Value as string; + ODataEnumValue val; + IEdmTypeReference typeRef = rightNode.TypeReference; + if (TryParseEnum(typeRef.Definition as IEdmEnumType, text, out val)) + { + leftNode = new ConstantNode(val, text, typeRef); + return; + } + } + } + + // fallback + base.PromoteBinaryOperandTypes(binaryOperatorKind, ref leftNode, ref rightNode, out typeReference); + } + + /// + /// Resolve operation's parameters. Using this extension, enum value could be written as string value. + /// + /// Current operation for parameters. + /// A dictionary the parameter list. + /// A dictionary containing resolved parameters. + public override IDictionary ResolveOperationParameters(IEdmOperation operation, IDictionary input) + { + Dictionary result = new Dictionary(EqualityComparer.Default); + foreach (var item in input) + { + IEdmOperationParameter functionParameter = null; + if (EnableCaseInsensitive) + { + functionParameter = ODataUriResolver.ResolveOperationParameterNameCaseInsensitive(operation, item.Key); + } + else + { + functionParameter = operation.FindParameter(item.Key); + } + + // ensure parameter name existis + if (functionParameter == null) + { + throw new ODataException(Strings.ODataParameterWriterCore_ParameterNameNotFoundInOperation(item.Key, operation.Name)); + } + + SingleValueNode newVal = item.Value; + + if (functionParameter.Type.IsEnum() + && newVal is ConstantNode + && newVal.TypeReference != null + && newVal.TypeReference.IsString()) + { + string text = ((ConstantNode)item.Value).Value as string; + ODataEnumValue val; + IEdmTypeReference typeRef = functionParameter.Type; + + if (TryParseEnum(typeRef.Definition as IEdmEnumType, text, out val)) + { + newVal = new ConstantNode(val, text, typeRef); + } + } + + result.Add(functionParameter, newVal); + } + + return result; + } + + /// + /// Resolve keys for certain entity set, this function would be called when key is specified as positional values. E.g. EntitySet('key') + /// Enum value could omit type name prefix using this resolver. + /// + /// Type for current entityset. + /// The list of positional values. + /// The convert function to be used for value converting. + /// The resolved key list. + public override IEnumerable> ResolveKeys(IEdmEntityType type, IList positionalValues, Func convertFunc) + { + return base.ResolveKeys( + type, + positionalValues, + (typeRef, valueText) => + { + if (typeRef.IsEnum() && valueText.StartsWith("'", StringComparison.Ordinal) && valueText.EndsWith("'", StringComparison.Ordinal)) + { + valueText = typeRef.FullName() + valueText; + } + + return convertFunc(typeRef, valueText); + }); + } + + /// + /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') + /// Enum value could omit type name prefix using this resolver. + /// + /// Type for current entityset. + /// The dictionary of name value pairs. + /// The convert function to be used for value converting. + /// The resolved key list. + public override IEnumerable> ResolveKeys(IEdmEntityType type, IDictionary namedValues, Func convertFunc) + { + return base.ResolveKeys( + type, + namedValues, + (typeRef, valueText) => + { + if (typeRef.IsEnum() && valueText.StartsWith("'", StringComparison.Ordinal) && valueText.EndsWith("'", StringComparison.Ordinal)) + { + valueText = typeRef.FullName() + valueText; + } + + return convertFunc(typeRef, valueText); + }); + } + + /// + /// Parse string or integer to enum value + /// + /// edm enum type + /// input string value + /// output edm enum value + /// true if parse succeeds, false if fails + private static bool TryParseEnum(IEdmEnumType enumType, string value, out ODataEnumValue enumValue) + { + long parsedValue; + bool success = enumType.TryParseEnum(value, true, out parsedValue); + enumValue = null; + if (success) + { + enumValue = new ODataEnumValue(parsedValue.ToString(CultureInfo.InvariantCulture), enumType.FullTypeName()); + } + + return success; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/UnqualifiedODataUriResolver.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/UnqualifiedODataUriResolver.cs new file mode 100644 index 0000000..0940619 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Resolver/UnqualifiedODataUriResolver.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Edm; + + /// + /// Resolver that supports bound function calls. + /// + public class UnqualifiedODataUriResolver : ODataUriResolver + { + /// + /// Resolve unbound operations based on name. + /// + /// The model to be used. + /// The operation name. + /// Resolved operation list. + public override IEnumerable ResolveUnboundOperations(IEdmModel model, string identifier) + { + if (identifier.Contains(".")) + { + return base.ResolveUnboundOperations(model, identifier); + } + + return FindAcrossModels(model, identifier, this.EnableCaseInsensitive) + .Where(operation => !operation.IsBound); + } + + /// + /// Resolve bound operations based on name. + /// + /// The model to be used. + /// The operation name. + /// The type operation was binding to. + /// Resolved operation list. + public override IEnumerable ResolveBoundOperations(IEdmModel model, string identifier, IEdmType bindingType) + { + if (identifier.Contains(".")) + { + return base.ResolveBoundOperations(model, identifier, bindingType); + } + + return FindAcrossModels(model, identifier, this.EnableCaseInsensitive) + .Where(operation => + operation.IsBound + && operation.Parameters.Any() + && operation.HasEquivalentBindingType(bindingType)); + } + + private static IEnumerable FindAcrossModels(IEdmModel model, String qualifiedName, bool caseInsensitive) where T : IEdmSchemaElement + { + Func> finder = (refModel) => + refModel.SchemaElements.OfType() + .Where(e => string.Equals(qualifiedName, e.Name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); + + IEnumerable results = finder(model); + + foreach (IEdmModel reference in model.ReferencedModels) + { + results.Concat(finder(reference)); + } + + return results; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SearchLexer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SearchLexer.cs new file mode 100644 index 0000000..7cbf0b7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SearchLexer.cs @@ -0,0 +1,188 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Text.RegularExpressions; + #endregion Namespaces + + /// + /// Lexer used for search query, note this is a little different ExpressionLexer, that it use double quote as string indicator. + /// TODO: Extend the expression lexer. + /// The result generated by this lexer: + /// AND, OR, NOT Identifier + /// ( OpenParen + /// ) CloseParen + /// (others) StringLiteral + /// + [DebuggerDisplay("SearchLexer ({text} @ {textPos} [{token}])")] + internal sealed class SearchLexer : ExpressionLexer + { + /// + /// Pattern for searchWord + /// From ABNF rule: + /// searchWord = 1*ALPHA ; Actually: any character from the Unicode categories L or Nl, + /// ; but not the words AND, OR, and NOT + /// + /// \p{L} means any kind of letter from any language, include [Lo] such as CJK single character. + /// + internal static readonly Regex InvalidWordPattern = new Regex(@"([^\p{L}\p{Nl}])"); + + /// + /// Escape character used in search query + /// + private const char EscapeChar = '\\'; + + /// + /// Characters that could be escaped + /// + private const string EscapeSequenceSet = "\\\""; + + /// + /// Keeps all keywords can be used in search query. + /// + private static readonly HashSet KeyWords = new HashSet(StringComparer.Ordinal) { ExpressionConstants.SearchKeywordAnd, ExpressionConstants.SearchKeywordOr, ExpressionConstants.SearchKeywordNot }; + + /// + /// Indicate whether current char is escaped. + /// + private bool isEscape; + + /// Initializes a new . + /// Expression to parse. + internal SearchLexer(string expression) + : base(expression, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/) + { + } + + /// Reads the next token, skipping whitespace as necessary. + /// Error that occurred while trying to process the next token. + /// The next token, which may be 'bad' if an error occurs. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This parser method is all about the switch statement and would be harder to maintain if it were broken up.")] + protected override ExpressionToken NextTokenImplementation(out Exception error) + { + error = null; + + this.ParseWhitespace(); + + ExpressionTokenKind t; + int tokenPos = this.textPos; + switch (this.ch) + { + case '(': + this.NextChar(); + t = ExpressionTokenKind.OpenParen; + break; + case ')': + this.NextChar(); + t = ExpressionTokenKind.CloseParen; + break; + case '"': + char quote = this.ch.Value; + + this.AdvanceToNextOccuranceOfWithEscape(quote); + + if (this.textPos == this.TextLen) + { + throw ParseError(Strings.ExpressionLexer_UnterminatedStringLiteral(this.textPos, this.Text)); + } + + this.NextChar(); + + t = ExpressionTokenKind.StringLiteral; + break; + default: + if (this.textPos == this.TextLen) + { + t = ExpressionTokenKind.End; + } + else + { + t = ExpressionTokenKind.Identifier; + do + { + this.NextChar(); + } while (this.ch.HasValue && IsValidSearchTermChar(this.ch.Value)); + } + + break; + } + + this.token.Kind = t; + this.token.Text = this.Text.Substring(tokenPos, this.textPos - tokenPos); + this.token.Position = tokenPos; + + if (this.token.Kind == ExpressionTokenKind.StringLiteral) + { + this.token.Text = this.token.Text.Substring(1, this.token.Text.Length - 2).Replace("\\\\", "\\").Replace("\\\"", "\""); + if (string.IsNullOrEmpty(this.token.Text)) + { + throw ParseError(Strings.ExpressionToken_IdentifierExpected(this.token.Position)); + } + } + + if ((this.token.Kind == ExpressionTokenKind.Identifier) && !KeyWords.Contains(this.token.Text)) + { + Match match = InvalidWordPattern.Match(this.token.Text); + if (match.Success) + { + int index = match.Groups[0].Index; + throw ParseError(Strings.ExpressionLexer_InvalidCharacter(this.token.Text[index], this.token.Position + index, this.Text)); + } + + this.token.Kind = ExpressionTokenKind.StringLiteral; + } + + return this.token; + } + + /// + /// Evaluate whether the given char is valid for a SearchTerm + /// + /// The char to be evaluated on. + /// Whether the given char is valid for a SearchTerm + private static bool IsValidSearchTermChar(char val) + { + return !Char.IsWhiteSpace(val) && val != ')'; + } + + /// + /// Move to next char, with escape char support. + /// + private void NextCharWithEscape() + { + this.isEscape = false; + this.NextChar(); + if (this.ch == EscapeChar) + { + this.isEscape = true; + this.NextChar(); + + if (!this.ch.HasValue || EscapeSequenceSet.IndexOf(this.ch.Value) < 0) + { + throw ParseError(Strings.ExpressionLexer_InvalidEscapeSequence(this.ch, this.textPos, this.Text)); + } + } + } + + /// + /// Advance to certain char, with escpae char support. + /// + /// the ending delimiter. + private void AdvanceToNextOccuranceOfWithEscape(char endingValue) + { + this.NextCharWithEscape(); + while (this.ch.HasValue && !(this.ch == endingValue && !this.isEscape)) + { + this.NextCharWithEscape(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AggregatedCollectionPropertyNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AggregatedCollectionPropertyNode.cs new file mode 100644 index 0000000..dd5981d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AggregatedCollectionPropertyNode.cs @@ -0,0 +1,138 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a originally collection resource that's been aggregated into a single value. + /// + public sealed class AggregatedCollectionPropertyNode : SingleResourceNode + { + /// + /// The collection that was aggregated and contains this property. + /// + private readonly CollectionNavigationNode source; + + /// + /// The EDM property which is to be accessed. + /// + /// Only non-entity, non-collection properties are supported by this node. + private readonly IEdmProperty property; + + /// + /// The target type that the property represents. + /// + private readonly IEdmComplexTypeReference typeReference; + + /// + /// The navigation source containing the source entity. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Constructs a . + /// + /// The value containing this property. + /// The EDM property which is to be accessed. + /// Throws if input source or property is null. + /// Throws if input property is not structural, or is a collection. + public AggregatedCollectionPropertyNode(CollectionNavigationNode source, IEdmProperty property) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + if (property.PropertyKind != EdmPropertyKind.Structural) + { + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessShouldBeNonEntityProperty(property.Name)); + } + + if (property.Type.IsCollection()) + { + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessTypeShouldNotBeCollection(property.Name)); + } + + this.source = source; + this.property = property; + this.typeReference = property.Type.AsComplex(); + this.navigationSource = source.NavigationSource; + } + + /// + /// Gets the value containing this property. + /// + public CollectionNavigationNode Source + { + get { return this.source; } + } + + /// + /// Gets the EDM property which is to be accessed. + /// + /// Only non-entity, non-collection properties are supported by this node. + public IEdmProperty Property + { + get { return this.property; } + } + + /// + /// Gets the type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get { return this.Property.Type; } + } + + /// + /// Gets the navigation source of this node. + /// + public override IEdmNavigationSource NavigationSource + { + get + { + return navigationSource; + } + } + + /// + /// Gets the type reference of this node. + /// + public override IEdmStructuredTypeReference StructuredTypeReference + { + get + { + return typeReference; + } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.AggregatedCollectionPropertyNode; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AllNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AllNode.cs new file mode 100644 index 0000000..378666c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AllNode.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.ObjectModel; + using System.Diagnostics; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Query node representing an All query. + /// + public sealed class AllNode : LambdaNode + { + /// + /// Create an AllNode + /// + /// The name of the rangeVariables list. + public AllNode(Collection rangeVariables) : this(rangeVariables, null) + { + Debug.Assert(false, "Don't ever call this, its for backcompat"); + } + + /// + /// Create an AllNode + /// + /// The name of the rangeVariables list. + /// The new range variable being added by this all node + public AllNode(Collection rangeVariables, RangeVariable currentRangeVariable) + : base(rangeVariables, currentRangeVariable) + { + } + + /// + /// The resource type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get { return EdmCoreModel.Instance.GetBoolean(true); } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.All; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnnotationSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnnotationSegment.cs new file mode 100644 index 0000000..11107db --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnnotationSegment.cs @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Edm.Vocabularies; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing an Annotation + /// + public sealed class AnnotationSegment : ODataPathSegment + { + /// + /// The annotation term referred to by this segment + /// + private readonly IEdmTerm term; + + /// + /// Build a segment based on an annotation term + /// + /// The annotation term that this segment represents. + /// Throws if the input annotation term is null. + public AnnotationSegment(IEdmTerm term) + { + ExceptionUtils.CheckArgumentNotNull(term, "term"); + + this.term = term; + + this.Identifier = term.Name; + this.TargetEdmType = term.Type.Definition; + this.SingleResult = !term.Type.IsCollection(); + } + + /// + /// Gets the annotation term that this segment represents. + /// + public IEdmTerm Term + { + get { return this.term; } + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return this.term.Type.Definition; } + } + + /// + /// Translate a using an instance of />. + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + AnnotationSegment otherTerm = other as AnnotationSegment; + return otherTerm != null && otherTerm.term == this.term; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnyNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnyNode.cs new file mode 100644 index 0000000..42cbc3f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/AnyNode.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.ObjectModel; + using System.Diagnostics; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Query node representing an Any query. + /// + public sealed class AnyNode : LambdaNode + { + /// + /// Create a AnyNode + /// + /// The name of the parameter list. + public AnyNode(Collection parameters) : this(parameters, null) + { + Debug.Assert(false, "Don't ever call this, its for backcompat"); + } + + /// + /// Create a AnyNode + /// + /// The name of the parameter list. + /// The name of the new range variable being added by this AnyNode + public AnyNode(Collection parameters, RangeVariable currentRangeVariable) + : base(parameters, currentRangeVariable) + { + } + + /// + /// The resource type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get { return EdmCoreModel.Instance.GetBoolean(true); } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.Any; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchReferenceSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchReferenceSegment.cs new file mode 100644 index 0000000..88fac00 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchReferenceSegment.cs @@ -0,0 +1,132 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// A segment representing an alias to another url in a batch. + /// + public sealed class BatchReferenceSegment : ODataPathSegment + { + /// + /// The of the resource that this placeholder represents. + /// + private readonly IEdmType edmType; + + /// + /// The entity set from the alias. + /// + private readonly IEdmEntitySetBase entitySet; + + /// + /// The contentId that this alias referrs to. + /// + private readonly string contentId; + + /// + /// Build a BatchReferenceSegment + /// + /// The contentId of this segment is referring to + /// The of the resource that this placeholder represents. + /// The resulting entity set + /// Throws if the input edmType of contentID is null. + /// Throws if the contentID is not in the right format. + public BatchReferenceSegment(string contentId, IEdmType edmType, IEdmEntitySetBase entitySet) + { + ExceptionUtils.CheckArgumentNotNull(edmType, "resultingType"); + ExceptionUtils.CheckArgumentNotNull(contentId, "contentId"); + if (!ODataPathParser.ContentIdRegex.IsMatch(contentId)) + { + throw new ODataException(ODataErrorStrings.BatchReferenceSegment_InvalidContentID(contentId)); + } + + this.edmType = edmType; + this.entitySet = entitySet; + this.contentId = contentId; + + this.Identifier = this.ContentId; + this.TargetEdmType = edmType; + this.TargetEdmNavigationSource = this.EntitySet; + this.SingleResult = true; + this.TargetKind = RequestTargetKind.Resource; + + if (entitySet != null) + { + ExceptionUtil.ThrowIfTypesUnrelated(edmType, entitySet.EntityType(), "BatchReferenceSegments"); + } + } + + /// + /// Gets the of the resource that this placeholder represents. + /// + public override IEdmType EdmType + { + get { return this.edmType; } + } + + /// + /// Gets the resulting entity set for this batch reference segment. + /// + public IEdmEntitySetBase EntitySet + { + get { return this.entitySet; } + } + + /// + /// Gets the contentId this alias is referrring to + /// + public string ContentId + { + get { return this.contentId; } + } + + /// + /// Translate this into something else. + /// + /// Type that the translator will return after translating this segment. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an implementation of the interface. + /// + /// An implementation of the Handler interface. + /// Throws if the input Handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + internal override bool Equals(ODataPathSegment other) + { + BatchReferenceSegment otherBatchReferenceSegment = other as BatchReferenceSegment; + return otherBatchReferenceSegment != null && + otherBatchReferenceSegment.EdmType == this.edmType && + otherBatchReferenceSegment.EntitySet == this.entitySet && + otherBatchReferenceSegment.ContentId == this.contentId; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchSegment.cs new file mode 100644 index 0000000..f78626b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BatchSegment.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing $batch + /// + public sealed class BatchSegment : ODataPathSegment + { + /// + /// Gets the singleton instance of the batch segment. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "BatchSegment is immutable")] + public static readonly BatchSegment Instance = new BatchSegment(); + + /// + /// Build a segment to represent $batch. + /// + private BatchSegment() + { + this.Identifier = UriQueryConstants.BatchSegment; + this.TargetKind = RequestTargetKind.Batch; + } + + /// + /// Gets the of this , which is always null. + /// + public override IEdmType EdmType + { + get { return null; } + } + + /// + /// Translate a into something else using an implementation of . + /// + /// Type that the translator will return after translating this segment. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an implementation of . + /// + /// An implementation of the Handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// The other segment to check. + /// True if the other segment is equivalent to this one. + /// Throws if the input other is null + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + return other is BatchSegment; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BinaryOperatorNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BinaryOperatorNode.cs new file mode 100644 index 0000000..74cc1e0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/BinaryOperatorNode.cs @@ -0,0 +1,159 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Query node representing a binary operator. + /// + public sealed class BinaryOperatorNode : SingleValueNode + { + /// + /// The operator represented by this node. + /// + private readonly BinaryOperatorKind operatorKind; + + /// + /// The left operand. + /// + private readonly SingleValueNode left; + + /// + /// The right operand. + /// + private readonly SingleValueNode right; + + /// + /// Cache for the TypeReference after it has been calculated for the current state of the node. + /// This can be an expensive calculation so we want to avoid doing it repeatedly. + /// + private IEdmTypeReference typeReference; + + /// + /// Create a BinaryOperatorNode + /// + /// The binary operator type. + /// The left operand. + /// The right operand. + /// Throws if the left or right inputs are null. + /// Throws if the two operands don't have the same type. + public BinaryOperatorNode(BinaryOperatorKind operatorKind, SingleValueNode left, SingleValueNode right) + : this(operatorKind, left, right, /*typeReference*/ null) + { + } + + /// + /// Create a BinaryOperatorNode + /// + /// The binary operator type. + /// The left operand. + /// The right operand. + /// The result typeReference. + /// Throws if the left or right inputs are null. + internal BinaryOperatorNode(BinaryOperatorKind operatorKind, SingleValueNode left, SingleValueNode right, IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(left, "left"); + ExceptionUtils.CheckArgumentNotNull(right, "right"); + this.operatorKind = operatorKind; + this.left = left; + this.right = right; + + // set the TypeReference if explictly given, otherwise based on the Operands. + if (typeReference != null) + { + this.typeReference = typeReference; + } + else if (this.Left == null || this.Right == null || this.Left.TypeReference == null || this.Right.TypeReference == null) + { + this.typeReference = null; + } + else + { + // Get a primitive type reference; this must not fail since we checked that the type is of kind 'primitive'. + IEdmPrimitiveTypeReference leftType = this.Left.TypeReference.AsPrimitive(); + IEdmPrimitiveTypeReference rightType = this.Right.TypeReference.AsPrimitive(); + + this.typeReference = QueryNodeUtils.GetBinaryOperatorResultType(leftType, rightType, this.OperatorKind); + } + } + + /// + /// Gets the operator represented by this node. + /// + public BinaryOperatorKind OperatorKind + { + get + { + return this.operatorKind; + } + } + + /// + /// Gets the left operand. + /// + public SingleValueNode Left + { + get + { + return this.left; + } + } + + /// + /// Gets the right operand. + /// + public SingleValueNode Right + { + get + { + return this.right; + } + } + + /// + /// Gets the resource type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get + { + return this.typeReference; + } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.BinaryOperator; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionComplexNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionComplexNode.cs new file mode 100644 index 0000000..43d2b9b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionComplexNode.cs @@ -0,0 +1,150 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node represents a collection complex property. + /// + public class CollectionComplexNode : CollectionResourceNode + { + /// + /// The resource node containing the property. + /// + private readonly SingleResourceNode source; + + /// + /// The EDM property which is to be accessed. + /// + private readonly IEdmProperty property; + + /// + /// The complex type of a single item from the collection represented by this node. + /// + private readonly IEdmComplexTypeReference itemType; + + /// + /// The type of the collection represented by this node. + /// + private readonly IEdmCollectionTypeReference collectionTypeReference; + + /// + /// The navigation source that our collection comes from. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Constructs a new . + /// + /// The value containing the property. + /// The EDM property which is to be accessed. + /// Throws if the input source or property is null. + /// Throws if the input property is not a collection of structural properties + public CollectionComplexNode(SingleResourceNode source, IEdmProperty property) + : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, property) + { + this.source = source; + } + + /// + /// Constructs a new . + /// + /// The navigation source containing the property. + /// The EDM property which is to be accessed. + /// Throws if the input source or property is null. + /// Throws if the input property is not a collection of structural properties + private CollectionComplexNode(IEdmNavigationSource navigationSource, IEdmProperty property) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + if (property.PropertyKind != EdmPropertyKind.Structural) + { + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessShouldBeNonEntityProperty(property.Name)); + } + + this.property = property; + this.collectionTypeReference = property.Type.AsCollection(); + this.itemType = this.collectionTypeReference.ElementType().AsComplex(); + this.navigationSource = navigationSource; + } + + /// + /// Gets the resource node containing the property. + /// + public SingleResourceNode Source + { + get { return this.source; } + } + + /// + /// Gets the EDM property which is to be accessed. + /// + public IEdmProperty Property + { + get { return this.property; } + } + + /// + /// Gets the type of a single item from the collection represented by this node. + /// + public override IEdmTypeReference ItemType + { + get { return this.itemType; } + } + + /// + /// The type of the collection represented by this node. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return this.collectionTypeReference; } + } + + /// + /// Gets the structured type of a single item from the collection represented by this node. + /// + public override IEdmStructuredTypeReference ItemStructuredType + { + get { return this.itemType; } + } + + /// + /// Gets the navigation source that our collection comes from. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionComplexNode; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionConstantNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionConstantNode.cs new file mode 100644 index 0000000..b8380b7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionConstantNode.cs @@ -0,0 +1,120 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Node representing a constant value, can either be primitive, complex, entity, or collection value. + /// + public sealed class CollectionConstantNode : CollectionNode + { + /// + /// Collection of ConstantNodes. + /// + private readonly IList collection = new List(); + + /// + /// Cache for the TypeReference after it has been calculated for the current state of the node. + /// + private readonly IEdmTypeReference itemType; + + /// + /// The type of the collection represented by this node. + /// + private readonly IEdmCollectionTypeReference collectionTypeReference; + + /// + /// Create a CollectionConstantNode + /// + /// A collection of objects. + /// The literal text for this node's value, formatted according to the OData URI literal formatting rules. + /// The reference to the collection type. + /// Throws if the input literalText is null. + public CollectionConstantNode(IEnumerable objectCollection, string literalText, IEdmCollectionTypeReference collectionType) + { + ExceptionUtils.CheckArgumentNotNull(objectCollection, "objectCollection"); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(literalText, "literalText"); + ExceptionUtils.CheckArgumentNotNull(collectionType, "collectionType"); + + this.LiteralText = literalText; + EdmCollectionType edmCollectionType = collectionType.Definition as EdmCollectionType; + this.itemType = edmCollectionType.ElementType; + this.collectionTypeReference = collectionType; + + foreach (object item in objectCollection) + { + this.collection.Add(new ConstantNode(item, item != null ? item.ToString() : "null", this.itemType)); + } + } + + /// + /// Gets the collection of ConstantNodes. + /// + public IList Collection + { + get + { + return new ReadOnlyCollection(this.collection); + } + } + + /// + /// Get or Set the literal text for this node's value, formatted according to the OData URI literal formatting rules. May be null if the text was not provided at construction time. + /// + public string LiteralText { get; private set; } + + /// + /// Gets the resource type of a single item from the collection represented by this node. + /// + public override IEdmTypeReference ItemType + { + get + { + return this.itemType; + } + } + + /// + /// The type of the collection represented by this node. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return this.collectionTypeReference; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionConstant; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionFunctionCallNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionFunctionCallNode.cs new file mode 100644 index 0000000..a1f23ef --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionFunctionCallNode.cs @@ -0,0 +1,152 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Node to represent a function call that returns a Collection + /// + public sealed class CollectionFunctionCallNode : CollectionNode + { + /// + /// the name of this function + /// + private readonly string name; + + /// + /// the list of operation imports + /// + private readonly ReadOnlyCollection functions; + + /// + /// the list of parameters provided to this function + /// + private readonly ReadOnlyCollection parameters; + + /// + /// the individual item type returned by this function + /// + private readonly IEdmTypeReference itemType; + + /// + /// the collection type returned by this function + /// + private readonly IEdmCollectionTypeReference returnedCollectionType; + + /// + /// The semantically bound parent of this function. + /// + private readonly QueryNode source; + + /// + /// Creates a CollectionFunctionCallNode to represent a operation call that returns a collection + /// + /// The name of this operation. + /// the list of functions that this node should represent. + /// the list of already bound parameters to this operation + /// the type of the collection returned by this operation. + /// The parent of this CollectionFunctionCallNode. + /// Throws if the provided name is null. + /// Throws if the provided collection type reference is null. + /// Throws if the element type of the provided collection type reference is not a primitive or complex type. + public CollectionFunctionCallNode(string name, IEnumerable functions, IEnumerable parameters, IEdmCollectionTypeReference returnedCollectionType, QueryNode source) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + ExceptionUtils.CheckArgumentNotNull(returnedCollectionType, "returnedCollectionType"); + this.name = name; + this.functions = new ReadOnlyCollection(functions == null ? new List() : functions.ToList()); + this.parameters = new ReadOnlyCollection(parameters == null ? new List() : parameters.ToList()); + this.returnedCollectionType = returnedCollectionType; + this.itemType = returnedCollectionType.ElementType(); + + if (!this.itemType.IsPrimitive() && !this.itemType.IsComplex() && !this.itemType.IsEnum()) + { + throw new ArgumentException(ODataErrorStrings.Nodes_CollectionFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum); + } + + this.source = source; + } + + /// + /// Gets the name of this function. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the list of operation imports represeted by this node + /// + public IEnumerable Functions + { + get { return this.functions; } + } + + /// + /// Gets the list of parameters to this function + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Gets the individual item type returned by this function + /// + public override IEdmTypeReference ItemType + { + get { return itemType; } + } + + /// + /// The type of the collection represented by this node. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return this.returnedCollectionType; } + } + + /// + /// Gets the semantically bound parent node of this CollectionFunctionCallNode. + /// + public QueryNode Source + { + get { return this.source; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionFunctionCall; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNavigationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNavigationNode.cs new file mode 100644 index 0000000..9088a2c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNavigationNode.cs @@ -0,0 +1,230 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OData.Metadata; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Query node representing a collection navigation property. + /// + public sealed class CollectionNavigationNode : CollectionResourceNode + { + /// + /// The navigation property of the single entity this node represents. + /// + private readonly IEdmNavigationProperty navigationProperty; + + /// + /// The resource type of a single entity item from the collection represented by this node. + /// + private readonly IEdmEntityTypeReference edmEntityTypeReference; + + /// + /// The type of the collection represented by this node. + /// + private readonly IEdmCollectionTypeReference collectionTypeReference; + + /// + /// The parent node. + /// + private readonly SingleResourceNode source; + + /// + /// The navigation source from which the collection of entities comes from. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// The parsed segments in the path. + /// + private readonly List parsedSegments; + + /// + /// The binding path of current navigation property. + /// + private readonly IEdmPathExpression bindingPath; + + /// + /// Creates a CollectionNavigationNode. + /// + /// The parent of this collection navigation node. + /// The navigation property that defines the collection node. + /// The binding path of navigation property + /// The collection node. + /// Throws if the input source or navigation property is null. + /// Throws if the input navigation doesn't target a collection. + public CollectionNavigationNode(SingleResourceNode source, IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, navigationProperty, bindingPath) + { + this.source = source; + } + + /// + /// Creates a CollectionNavigationNode. + /// + /// The navigation source that this of the previous segment. + /// The navigation property that defines the collection node. + /// The binding path of navigation property + /// The collection node. + /// Throws if the input navigation property is null. + /// Throws if the input navigation doesn't target a collection. + internal CollectionNavigationNode(IEdmNavigationSource navigationSource, IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + : this(navigationProperty) + { + this.bindingPath = bindingPath; + this.navigationSource = navigationSource != null ? navigationSource.FindNavigationTarget(navigationProperty, bindingPath) : null; + } + + /// + /// Constructs a CollectionNavigationNode. + /// + /// The previous node in the path. + /// The navigation property this node represents. + /// The path segments parsed in path and query option. + internal CollectionNavigationNode(SingleResourceNode source, IEdmNavigationProperty navigationProperty, List parsedSegments) + : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, navigationProperty, parsedSegments) + { + this.source = source; + } + + /// + /// Constructs a CollectionNavigationNode. + /// + /// The navigation source that this of the previous segment. + /// The navigation property this node represents. + /// The path segments parsed in path and query option. + private CollectionNavigationNode(IEdmNavigationSource navigationSource, IEdmNavigationProperty navigationProperty, List parsedSegments) + : this(navigationProperty) + { + this.parsedSegments = parsedSegments; + + this.navigationSource = navigationSource != null ? navigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, this.parsedSegments, out this.bindingPath) : null; + } + + /// + /// Creates a CollectionNavigationNode. + /// + /// The navigation property that defines the collection node. + /// The collection node. + /// Throws if the input navigation property is null. + /// Throws if the input navigation doesn't target a collection. + private CollectionNavigationNode(IEdmNavigationProperty navigationProperty) + { + ExceptionUtils.CheckArgumentNotNull(navigationProperty, "navigationProperty"); + + if (navigationProperty.TargetMultiplicity() != EdmMultiplicity.Many) + { + throw new ArgumentException(ODataErrorStrings.Nodes_CollectionNavigationNode_MustHaveManyMultiplicity); + } + + this.navigationProperty = navigationProperty; + this.collectionTypeReference = navigationProperty.Type.AsCollection(); + this.edmEntityTypeReference = this.collectionTypeReference.ElementType().AsEntityOrNull(); + } + + /// + /// Gets the parent node of this Collection Navigation Node. + /// + public SingleResourceNode Source + { + get { return this.source; } + } + + /// + /// Gets the target multiplicity. + /// + public EdmMultiplicity TargetMultiplicity + { + get { return this.navigationProperty.TargetMultiplicity(); } + } + + /// + /// Gets the Navigation Property that defines this collection Node. + /// + /// The navigation property that defines this collection node. + public IEdmNavigationProperty NavigationProperty + { + get { return this.navigationProperty; } + } + + /// + /// Gets a reference to the resource type a single entity in the collection. + /// + public override IEdmTypeReference ItemType + { + get { return this.edmEntityTypeReference; } + } + + /// + /// The type of the collection represented by this node. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return this.collectionTypeReference; } + } + + /// + /// Gets the resource type of a single entity from the collection. + /// + public IEdmEntityTypeReference EntityItemType + { + get { return this.edmEntityTypeReference; } + } + + /// + /// Gets the resource type of a single entity from the collection. + /// + public override IEdmStructuredTypeReference ItemStructuredType + { + get { return this.edmEntityTypeReference; } + } + + /// + /// Gets the navigation source containing this collection. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// The binding path of current navigation property. + /// + public IEdmPathExpression BindingPath + { + get { return bindingPath; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionNavigationNode; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNode.cs new file mode 100644 index 0000000..8b73756 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionNode.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Base class for all semantic metadata bound nodes which represent a composable collection of values. + /// + public abstract class CollectionNode : QueryNode + { + /// + /// The resouce type of a single item from the collection represented by this node. + /// + public abstract IEdmTypeReference ItemType + { + get; + } + + /// + /// The type of the collection represented by this node. + /// + public abstract IEdmCollectionTypeReference CollectionType + { + get; + } + + /// + /// Gets the kind of this node. + /// + public override QueryNodeKind Kind + { + get { return (QueryNodeKind)this.InternalKind; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionOpenPropertyAccessNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionOpenPropertyAccessNode.cs new file mode 100644 index 0000000..bd023e8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionOpenPropertyAccessNode.cs @@ -0,0 +1,105 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Node representing an access to a open collection property value. + /// + public sealed class CollectionOpenPropertyAccessNode : CollectionNode + { + /// + /// The value containing the property. + /// + private readonly SingleValueNode source; + + /// + /// The name of the open collection property to be bound outside the EDM model. + /// + private readonly string name; + + /// + /// Constructs a new . + /// + /// The value containing the property. + /// The name of the open collection property to be bound outside the EDM model. + /// Throws if the input source or openPropertyName is null. + public CollectionOpenPropertyAccessNode(SingleValueNode source, string openPropertyName) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentNotNull(openPropertyName, "openPropertyName"); + + this.source = source; + this.name = openPropertyName; + } + + /// + /// Gets the value containing the property. + /// + public SingleValueNode Source + { + get { return this.source; } + } + + /// + /// Gets the name of the open property to be bound outside the EDM model. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the resouce type of a single item from the collection represented by this node. + /// + public override IEdmTypeReference ItemType + { + get { return null; } + } + + /// + /// The type of the collection represented by this node. + /// + /// /// + /// The value of this property will always be null for open collection properties. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return null; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionOpenPropertyAccess; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionPropertyAccessNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionPropertyAccessNode.cs new file mode 100644 index 0000000..4ebd04a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionPropertyAccessNode.cs @@ -0,0 +1,131 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Node representing an access to a collection property value. + /// + public sealed class CollectionPropertyAccessNode : CollectionNode + { + /// + /// The value containing the property. + /// + private readonly SingleValueNode source; + + /// + /// The EDM property which is to be accessed. + /// + /// Only non-entity, collection properties are supported by this node. + private readonly IEdmProperty property; + + /// + /// The resouce type of a single item from the collection represented by this node. + /// + private readonly IEdmTypeReference itemType; + + /// + /// The type of the collection represented by this node. + /// + private readonly IEdmCollectionTypeReference collectionTypeReference; + + /// + /// Constructs a new . + /// + /// The value containing the property. + /// The EDM property which is to be accessed. + /// Throws if the input source or property is null. + /// Throws if the input property is not a collection of structural properties + public CollectionPropertyAccessNode(SingleValueNode source, IEdmProperty property) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + if (property.PropertyKind != EdmPropertyKind.Structural) + { + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessShouldBeNonEntityProperty(property.Name)); + } + + if (!property.Type.IsCollection()) + { + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessTypeMustBeCollection(property.Name)); + } + + this.source = source; + this.property = property; + this.collectionTypeReference = property.Type.AsCollection(); + this.itemType = this.collectionTypeReference.ElementType(); + } + + /// + /// Gets the value containing the property. + /// + public SingleValueNode Source + { + get { return this.source; } + } + + /// + /// Gets the EDM property which is to be accessed. + /// + /// Only non-entity, collection properties are supported by this node. + public IEdmProperty Property + { + get { return this.property; } + } + + /// + /// Gets the resource type of a single item from the collection represented by this node. + /// + public override IEdmTypeReference ItemType + { + get + { + return this.itemType; + } + } + + /// + /// The type of the collection represented by this node. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return this.collectionTypeReference; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionPropertyAccess; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceCastNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceCastNode.cs new file mode 100644 index 0000000..7622c28 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceCastNode.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a type segment that casts a resource collection node. + /// + public sealed class CollectionResourceCastNode : CollectionResourceNode + { + /// + /// The resource collection node that we're casting. + /// + private readonly CollectionResourceNode source; + + /// + /// The target type that we're casting our resource collection node to. + /// + private readonly IEdmStructuredTypeReference edmTypeReference; + + /// + /// the type of the collection returned by this function + /// + private readonly IEdmCollectionTypeReference collectionTypeReference; + + /// + /// The navigation source that our collection comes from. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Create a CollectionCastNode with the given source node and the given target type. + /// + /// Parent that is being cast. + /// Type to cast to. + /// Throws if the input source or structuredType are null. + public CollectionResourceCastNode(CollectionResourceNode source, IEdmStructuredType structuredType) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentNotNull(structuredType, "structuredType"); + this.source = source; + this.edmTypeReference = structuredType.GetTypeReference(); + this.navigationSource = source.NavigationSource; + + // creating a new collection type here because the type in the request is just the item type, there is no user-provided collection type. + this.collectionTypeReference = EdmCoreModel.GetCollection(this.edmTypeReference); + } + + /// + /// Gets the entity collection node that we're casting. + /// + public CollectionResourceNode Source + { + get { return this.source; } + } + + /// + /// Gets the type that we're casting all items in this collection to. + /// + public override IEdmTypeReference ItemType + { + get { return this.edmTypeReference; } + } + + /// + /// The type of the collection represented by this node. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return this.collectionTypeReference; } + } + + /// + /// Gets the resource type that we're casting all items in this collection to. + /// + public override IEdmStructuredTypeReference ItemStructuredType + { + get { return this.edmTypeReference; } + } + + /// + /// Gets the navigation source that our collection comes from. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionResourceCast; + } + } + + /// + /// Accept a that walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceFunctionCallNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceFunctionCallNode.cs new file mode 100644 index 0000000..bad3ddd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceFunctionCallNode.cs @@ -0,0 +1,178 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// Node to represent a function call that returns a collection of entities. + /// + public sealed class CollectionResourceFunctionCallNode : CollectionResourceNode + { + /// + /// the name of this function. + /// + private readonly string name; + + /// + /// list of functions that this node represents. + /// + private readonly ReadOnlyCollection functions; + + /// + /// list of parameters provided to this function + /// + private readonly ReadOnlyCollection parameters; + + /// + /// the structured type of a resource returned by this function + /// + private readonly IEdmStructuredTypeReference structuredTypeReference; + + /// + /// the type of the collection returned by this function + /// + private readonly IEdmCollectionTypeReference returnedCollectionTypeReference; + + /// + /// the set containing the entities returned by this function. + /// + private readonly IEdmEntitySetBase navigationSource; + + /// + /// The semantically bound parent of this CollectionResourceFunctionCallNode. + /// + private readonly QueryNode source; + + /// + /// Creates an CollectionResourceFunctionCallNode to represent a operation call that returns a collection of entities. + /// + /// The name of this operation. + /// the list of functions that this node should represent. + /// the list of parameters to this operation + /// the type the entity collection returned by this operation. The element type must be an entity type. + /// the set containing entities returned by this operation + /// the semantically bound parent of this CollectionResourceFunctionCallNode. + /// Throws if the provided name is null. + /// Throws if the provided collection type reference is null. + /// Throws if the element type of the provided collection type reference is not an entity type. + /// Throws if the input operation imports is null + public CollectionResourceFunctionCallNode(string name, IEnumerable functions, IEnumerable parameters, IEdmCollectionTypeReference returnedCollectionTypeReference, IEdmEntitySetBase navigationSource, QueryNode source) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + ExceptionUtils.CheckArgumentNotNull(returnedCollectionTypeReference, "returnedCollectionTypeReference"); + this.name = name; + this.functions = new ReadOnlyCollection(functions == null ? new List() : functions.ToList()); + this.parameters = new ReadOnlyCollection(parameters == null ? new List() : parameters.ToList()); + this.returnedCollectionTypeReference = returnedCollectionTypeReference; + this.navigationSource = navigationSource; + + this.structuredTypeReference = returnedCollectionTypeReference.ElementType().AsStructuredOrNull(); + if (this.structuredTypeReference == null) + { + // TODO: Update error message #644 + throw new ArgumentException(ODataErrorStrings.Nodes_EntityCollectionFunctionCallNode_ItemTypeMustBeAnEntity); + } + + this.source = source; + } + + /// + /// Gets the name of this function + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the list of operation imports that this node represents. + /// + public IEnumerable Functions + { + get { return this.functions; } + } + + /// + /// Gets the list of parameters provided to this function. + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Gets the individual item type returned by this function. + /// + public override IEdmTypeReference ItemType + { + get { return this.structuredTypeReference; } + } + + /// + /// The type of the collection represented by this node. + /// + public override IEdmCollectionTypeReference CollectionType + { + get { return this.returnedCollectionTypeReference; } + } + + /// + /// Gets the individual structured type returned by this function. + /// + public override IEdmStructuredTypeReference ItemStructuredType + { + get { return this.structuredTypeReference; } + } + + /// + /// Gets the navigation source contaiing the entities returned by this function. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the semantically bound parent of this function. + /// + public QueryNode Source + { + get { return this.source; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.CollectionResourceFunctionCall; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceNode.cs new file mode 100644 index 0000000..f6bd0e9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CollectionResourceNode.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Base class for all semantically bound nodes which represent a composable collection of values. + /// + public abstract class CollectionResourceNode : CollectionNode + { + /// + /// Get the type of a single resource from the collection represented by this node. + /// + public abstract IEdmStructuredTypeReference ItemStructuredType { get; } + + /// + /// Get the navigation source that contains this collection. + /// + public abstract IEdmNavigationSource NavigationSource { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeClause.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeClause.cs new file mode 100644 index 0000000..3e9d2fd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeClause.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + /// + /// The result of parsing a $compute query option. + /// + public sealed class ComputeClause + { + /// + /// The computed properties and operations. + /// + private ReadOnlyCollection computedItems; + + /// + /// Constructs a from the given parameters. + /// + /// The computed properties and operations. + public ComputeClause(IEnumerable computedItems) + { + this.computedItems = computedItems != null ? + new ReadOnlyCollection(computedItems.ToList()) : + new ReadOnlyCollection(new List()); + } + + /// + /// Gets the computed properties and operations. + /// + public IEnumerable ComputedItems + { + get + { + return this.computedItems.AsEnumerable(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeExpression.cs new file mode 100644 index 0000000..c65dd50 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ComputeExpression.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// An item that has been computed by the query at the current level of the tree. + /// + public sealed class ComputeExpression + { + private readonly SingleValueNode expression; + + private readonly string alias; + + private readonly IEdmTypeReference typeReference; + + /// + /// Create a ComputeExpression. + /// + /// The compute expression. + /// The compute alias. + /// The of this aggregate expression. + public ComputeExpression(SingleValueNode expression, string alias, IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + ExceptionUtils.CheckArgumentNotNull(alias, "alias"); + + this.expression = expression; + this.alias = alias; + //// TypeRefrence is null for dynamic properties + this.typeReference = typeReference; + } + + /// + /// Gets the aggregation expression. + /// + public SingleValueNode Expression + { + get + { + return this.expression; + } + } + + /// + /// Gets the aggregation alias. + /// + public string Alias + { + get + { + return this.alias; + } + } + + /// + /// Gets the of this aggregate expression. + /// + public IEdmTypeReference TypeReference + { + get + { + return this.typeReference; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConstantNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConstantNode.cs new file mode 100644 index 0000000..d325959 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConstantNode.cs @@ -0,0 +1,121 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + #endregion Namespaces + + /// + /// Node representing a constant value, can either be primitive, complex, entity, or collection value. + /// + public sealed class ConstantNode : SingleValueNode + { + /// + /// The constant value. + /// + private readonly object constantValue; + + /// + /// Cache for the TypeReference after it has been calculated for the current state of the node. + /// + private readonly IEdmTypeReference typeReference; + + /// + /// Create a ConstantNode + /// + /// This node's primitive value. + /// The literal text for this node's value, formatted according to the OData URI literal formatting rules. + /// Throws if the input literalText is null. + public ConstantNode(object constantValue, string literalText) + : this(constantValue) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(literalText, "literalText"); + this.LiteralText = literalText; + } + + /// + /// Create a ConstantNode + /// + /// This node's primitive value. + /// The literal text for this node's value, formatted according to the OData URI literal formatting rules. + /// The typeReference of this node's value. + /// Throws if the input literalText is null. + public ConstantNode(object constantValue, string literalText, IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(literalText, "literalText"); + + this.constantValue = constantValue; + this.LiteralText = literalText; + this.typeReference = typeReference; + } + + /// + /// Create a ConstantNode + /// + /// This node's primitive value. + public ConstantNode(object constantValue) + { + this.constantValue = constantValue; + this.typeReference = constantValue == null ? null : EdmLibraryExtensions.GetPrimitiveTypeReference(constantValue.GetType()); + } + + /// + /// Gets the primitive constant value. + /// + public object Value + { + get + { + return this.constantValue; + } + } + + /// + /// Get or Set the literal text for this node's value, formatted according to the OData URI literal formatting rules. May be null if the text was not provided at construction time. + /// + public string LiteralText { get; private set; } + + /// + /// Gets the resouce type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get + { + return this.typeReference; + } + } + + /// + /// Gets the kind of the query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.Constant; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConvertNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConvertNode.cs new file mode 100644 index 0000000..9d49496 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ConvertNode.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Node representing a conversion of primitive type to another type. + /// + public sealed class ConvertNode : SingleValueNode + { + /// + /// The source value to convert. + /// + private readonly SingleValueNode source; + + /// + /// The target type that the source will be converted to. + /// + private readonly IEdmTypeReference typeReference; + + /// + /// Constructs a ConvertNode. + /// + /// The node to convert. + /// The type to convert the node to + /// Throws if the input source or typeReference is null. + public ConvertNode(SingleValueNode source, IEdmTypeReference typeReference) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentNotNull(typeReference, "typeReference"); + this.source = source; + this.typeReference = typeReference; + } + + /// + /// Get the source value to convert. + /// + public SingleValueNode Source + { + get { return this.source; } + } + + /// + /// Get the type we're converting to. + /// + public override IEdmTypeReference TypeReference + { + get { return this.typeReference; } + } + + /// + /// Get the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.Convert; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountNode.cs new file mode 100644 index 0000000..7623ba1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountNode.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Node representing count of related entities or items within a collection-valued property. + /// + public sealed class CountNode : SingleValueNode + { + /// + /// The collection to be counted, could be any type of collection includes primitive type, enum type, complex type or entity type collection. + /// + private readonly CollectionNode source; + + /// + /// Constructs a new . + /// + /// The value containing the property. + /// Throws if the input source is null. + public CountNode(CollectionNode source) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + + this.source = source; + } + + /// + /// Gets the collection property node to be counted. + /// + public CollectionNode Source + { + get { return this.source; } + } + + /// + /// Gets the value type this node represents. + /// + public override IEdmTypeReference TypeReference + { + // The value type is same type as the type returned by IQueryable LongCount method + get { return EdmCoreModel.Instance.GetInt64(false); } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.Count; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountSegment.cs new file mode 100644 index 0000000..1806bee --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountSegment.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing $count in a path + /// + public sealed class CountSegment : ODataPathSegment + { + /// + /// Return the singleton instance of Count + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "CountSegment is immutable")] + public static readonly CountSegment Instance = new CountSegment(); + + /// + /// Build a segment representing $count + /// + private CountSegment() + { + this.Identifier = UriQueryConstants.CountSegment; + this.SingleResult = true; + this.TargetKind = RequestTargetKind.PrimitiveValue; + } + + /// + /// Gets the of this , which is always Edm.Int32. + /// + public override IEdmType EdmType + { + get { return EdmCoreModel.Instance.GetInt32(false).Definition; } + } + + /// + /// Translate a using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + return other is CountSegment; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountVirtualPropertyNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountVirtualPropertyNode.cs new file mode 100644 index 0000000..d7f620f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/CountVirtualPropertyNode.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Dummy class that allows virtual property $count + /// to work like any other aggregation method. + /// + public sealed class CountVirtualPropertyNode : SingleValueNode + { + /// Constructor. + public CountVirtualPropertyNode() + { + } + + /// Kind of the single value node. + public override QueryNodeKind Kind + { + get + { + return QueryNodeKind.Count; + } + } + + /// Type returned by the $count virtual property. + public override IEdmTypeReference TypeReference + { + get + { + // Issue #758: CountDistinct and $Count should return type Edm.Decimal with Scale="0" and sufficient Precision. + return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Int64, false); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DetermineNavigationSourceTranslator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DetermineNavigationSourceTranslator.cs new file mode 100644 index 0000000..c352cd4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DetermineNavigationSourceTranslator.cs @@ -0,0 +1,249 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Translator that determines the navigation source of a segment. + /// + internal sealed class DetermineNavigationSourceTranslator : PathSegmentTranslator + { + /// + /// Determine the NavigationSource of a NavigationPropertyLinkSegment + /// + /// The NavigationPropertyLinkSegment to look in. + /// The IEdmNavigationSource of this NavigationPropertyLinkSegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(NavigationPropertyLinkSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.NavigationSource; + } + + /// + /// Determine the NavigationSource of a TypeSegment + /// + /// The TypeSegment to look in. + /// The IEdmNavigationSource of this TypeSegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(TypeSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.NavigationSource; + } + + /// + /// Determine the NavigationSource of a NavigationPropertySegment + /// + /// The NavigationPropertySegment to look in. + /// The IEdmNavigationSource of this NavigationPropertySegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(NavigationPropertySegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.NavigationSource; + } + + /// + /// Determine the NavigationSource of an EntitySetSegment + /// + /// The EntitySetSegment to look in. + /// The IEdmNavigationSource of this EntitySetSegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(EntitySetSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.EntitySet; + } + + /// + /// Determine the NavigationSource of a SingletonSegment + /// + /// The SingletonSegment to look in. + /// The IEdmNavigationSource of this SingletonSegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(SingletonSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.Singleton; + } + + /// + /// Determine the NavigationSource of a KeySegment + /// + /// The KeySegment to look in. + /// The IEdmNavigationSource of this KeySegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(KeySegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.NavigationSource; + } + + /// + /// Determine the NavigationSource of a PropertySegment + /// + /// The PropertySegment to look in. + /// null, since a property doesn't necessarily have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(PropertySegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + + if (segment.EdmType.AsElementType() is IEdmComplexType) + { + return segment.TargetEdmNavigationSource; + } + + return null; + } + + /// + /// Translate a OperationImportSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override IEdmNavigationSource Translate(OperationImportSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.EntitySet; + } + + /// + /// Determine the NavigationSource of an OperationSegment + /// + /// The OperationSegment to look in. + /// The IEdmNavigationSource of this OperationSegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(OperationSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.EntitySet; + } + + /// + /// Determine the NavigationSource of a CountSegment + /// + /// The CountSegment to look in. + /// null, since $count doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(CountSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return null; + } + + /// + /// Determine the NavigationSource of a FilterSegment + /// + /// The FilterSegment to look in. + /// null, since $filter doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(FilterSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.TargetEdmNavigationSource; + } + + /// + /// Determine the NavigationSource of a ReferenceSegment + /// + /// The ReferenceSegment to look in. + /// null, since $filter doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(ReferenceSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.TargetEdmNavigationSource; + } + + /// + /// Determine the NavigationSource of a EachSegment + /// + /// The FilterSegment to look in. + /// null, since $filter doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(EachSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.TargetEdmNavigationSource; + } + + /// + /// Determine the NavigationSource of a OpenPropertySegment + /// + /// The OpenPropertySegment to look in. + /// null, since an OpenProperty doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(DynamicPathSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return null; + } + + /// + /// Determine the NavigationSource of a ValueSegment + /// + /// The ValueSegment to look in. + /// null, since $value doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(ValueSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return null; + } + + /// + /// Determine the NavigationSource of a BatchSegment + /// + /// The BatchSegment to look in. + /// null, since $batch doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(BatchSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return null; + } + + /// + /// Determine the NavigationSource of a BatchReferenceSegment + /// + /// The BatchReferenceSegment to look in. + /// The IEdmNavigationSource of this BatchReferenceSegment + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(BatchReferenceSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.EntitySet; + } + + /// + /// Determine the NavigationSource of a MetadataSegment + /// + /// The MetadataSegment to look in. + /// null, since $batch doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(MetadataSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return null; + } + + /// + /// Determine the NavigationSource of a PathTemplateSegment + /// + /// The PathTemplateSegment to look in. + /// null, since $batch doesn't have an navigation source + /// Throws if the input segment is null. + public override IEdmNavigationSource Translate(PathTemplateSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DynamicPathSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DynamicPathSegment.cs new file mode 100644 index 0000000..9ebf426 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/DynamicPathSegment.cs @@ -0,0 +1,100 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + #endregion Namespaces + + /// + /// A segment representing an unknown path or an open property. + /// + public sealed class DynamicPathSegment : ODataPathSegment + { + /// + /// Build a segment to represent an unknown path or an open property. + /// + /// The identifier of the dynamic path segment + public DynamicPathSegment(string identifier) + { + ExceptionUtils.CheckArgumentNotNull(identifier, "identifier"); + this.Identifier = identifier; + this.TargetEdmType = null; + this.TargetKind = RequestTargetKind.Dynamic; + this.SingleResult = true; + } + + /// + /// Build a segment to represent an unknown path or an open property. + /// + /// The identifier of the dynamic path segment. + /// the IEdmType of this segment + /// The navigation source targeted by this segment. Can be null. + /// Whether the segment targets a single result or not. + public DynamicPathSegment(string identifier, IEdmType edmType, IEdmNavigationSource navigationSource, bool singleResult) + { + ExceptionUtils.CheckArgumentNotNull(identifier, "identifier"); + this.Identifier = identifier; + this.TargetEdmType = edmType; + this.SingleResult = singleResult; + this.TargetKind = edmType == null ? RequestTargetKind.Dynamic : edmType.GetTargetKindFromType(); + this.TargetEdmNavigationSource = navigationSource; + } + + /// + /// Gets the of this , which might be null. + /// + public override IEdmType EdmType + { + get { return this.TargetEdmType; } + } + + /// + /// Translate a . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + DynamicPathSegment otherDynmaicPathSegment = other as DynamicPathSegment; + return otherDynmaicPathSegment != null + && otherDynmaicPathSegment.Identifier == this.Identifier + && otherDynmaicPathSegment.EdmType == this.EdmType + && otherDynmaicPathSegment.TargetEdmNavigationSource == this.TargetEdmNavigationSource + && otherDynmaicPathSegment.SingleResult == this.SingleResult; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EachSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EachSegment.cs new file mode 100644 index 0000000..0d53734 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EachSegment.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// A segment representing $each in a path. + /// + public sealed class EachSegment : ODataPathSegment + { + /// + /// The of the resource that this represents. + /// + private readonly IEdmType edmType; + + /// + /// Build a segment representing $each. + /// + /// The entity collection that this set-based operation applies to. + /// Target type for the entity being referenced. + /// Throws if any input parameter is null. + /// $each cannot be applied on singletons. + public EachSegment(IEdmNavigationSource navigationSource, IEdmType targetEdmType) + { + ExceptionUtils.CheckArgumentNotNull(navigationSource, "navigationSource"); + ExceptionUtils.CheckArgumentNotNull(targetEdmType, "targetEdmType"); + + this.Identifier = UriQueryConstants.EachSegment; + this.SingleResult = false; + this.TargetEdmNavigationSource = navigationSource; + this.TargetEdmType = targetEdmType; + this.TargetKind = targetEdmType.GetTargetKindFromType(); + + this.edmType = navigationSource.Type; + } + + /// + /// Gets the to which this applies. + /// + public override IEdmType EdmType + { + get { return this.edmType; } + } + + /// + /// Translate a using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + EachSegment otherSegment = other as EachSegment; + + return otherSegment != null && + otherSegment.TargetEdmNavigationSource == this.TargetEdmNavigationSource; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntityIdSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntityIdSegment.cs new file mode 100644 index 0000000..46d64d8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntityIdSegment.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// A segment representing an entity id represented by $id query option + /// + public sealed class EntityIdSegment + { + /// + /// Initializes a new instance of the class. + /// + /// Uri correspoding to $id + internal EntityIdSegment(Uri id) + { + this.Id = id; + } + + /// + /// Gets the original Id Uri for $id. + /// + public Uri Id { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntitySetSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntitySetSegment.cs new file mode 100644 index 0000000..d2528f8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/EntitySetSegment.cs @@ -0,0 +1,101 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// A segment representing an EntitySet in a path. + /// + public sealed class EntitySetSegment : ODataPathSegment + { + /// + /// The entity set represented by this segment. + /// + private readonly IEdmEntitySet entitySet; + + /// + /// Type of the entities in the set represented by this segment. + /// + private readonly IEdmType type; + + /// + /// Build a segment representing an entity set + /// + /// The entity set represented by this segment. + /// Throws if the input entitySet is null. + public EntitySetSegment(IEdmEntitySet entitySet) + { + ExceptionUtils.CheckArgumentNotNull(entitySet, "entitySet"); + + this.entitySet = entitySet; + + // creating a new collection type here because the type in the entity set is just the item type, there is no user-provided collection type. + this.type = new EdmCollectionType(new EdmEntityTypeReference(this.entitySet.EntityType(), false)); + + this.TargetEdmNavigationSource = entitySet; + this.TargetEdmType = entitySet.EntityType(); + this.TargetKind = RequestTargetKind.Resource; + this.SingleResult = false; + } + + /// + /// Gets the entity set represented by this segment. + /// + public IEdmEntitySet EntitySet + { + get { return this.entitySet; } + } + + /// + /// Gets the of this . + /// This will always be an for the that this set contains. + /// + public override IEdmType EdmType + { + get { return this.type; } + } + + /// + /// Translate an into another type using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle an using the an instance of the . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + EntitySetSegment otherEntitySet = other as EntitySetSegment; + return otherEntitySet != null && otherEntitySet.EntitySet == this.EntitySet; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedNavigationSelectItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedNavigationSelectItem.cs new file mode 100644 index 0000000..d7fc1fe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedNavigationSelectItem.cs @@ -0,0 +1,172 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Aggregation; + using Microsoft.OData.Edm; + + /// + /// This represents one level of expansion for a particular expansion tree. + /// + public sealed class ExpandedNavigationSelectItem : ExpandedReferenceSelectItem + { + /// + /// Create an Expand item using a nav prop, its entity set and a SelectExpandClause + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this ExpandItem + /// This level select and any sub expands for this expand item. + /// Throws if input pathToNavigationProperty is null. + public ExpandedNavigationSelectItem(ODataExpandPath pathToNavigationProperty, IEdmNavigationSource navigationSource, SelectExpandClause selectExpandOption) + : this(pathToNavigationProperty, navigationSource, selectExpandOption, null, null, null, null, null, null, null) + { + } + + /// + /// Create an expand item, using a navigationProperty, its entity set, and any expand options. + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this expand level. + /// This level select and any sub expands for this expand item. + /// A filter clause for this expand (can be null) + /// An Orderby clause for this expand (can be null) + /// A top clause for this expand (can be null) + /// A skip clause for this expand (can be null) + /// An query count clause for this expand (can be null) + /// An levels clause for this expand (can be null) + /// An levels clause for this expand (can be null) + /// Throws if input pathToNavigationProperty is null. + public ExpandedNavigationSelectItem( + ODataExpandPath pathToNavigationProperty, + IEdmNavigationSource navigationSource, + SelectExpandClause selectAndExpand, + FilterClause filterOption, + OrderByClause orderByOption, + long? topOption, + long? skipOption, + bool? countOption, + SearchClause searchOption, + LevelsClause levelsOption) + : base(pathToNavigationProperty, navigationSource, filterOption, orderByOption, topOption, skipOption, countOption, searchOption) + { + ExceptionUtils.CheckArgumentNotNull(pathToNavigationProperty, "pathToNavigationProperty"); + + this.SelectAndExpand = selectAndExpand; + this.LevelsOption = levelsOption; + } + + /// + /// Create an expand item, using a navigationProperty, its entity set, and any expand options. + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this expand level. + /// This level select and any sub expands for this expand item. + /// A filter clause for this expand (can be null) + /// An Orderby clause for this expand (can be null) + /// A top clause for this expand (can be null) + /// A skip clause for this expand (can be null) + /// An query count clause for this expand (can be null) + /// An levels clause for this expand (can be null) + /// An levels clause for this expand (can be null) + /// A compute clause for this expand (can be null) + /// Throws if input pathToNavigationProperty is null. + public ExpandedNavigationSelectItem( + ODataExpandPath pathToNavigationProperty, + IEdmNavigationSource navigationSource, + SelectExpandClause selectAndExpand, + FilterClause filterOption, + OrderByClause orderByOption, + long? topOption, + long? skipOption, + bool? countOption, + SearchClause searchOption, + LevelsClause levelsOption, + ComputeClause computeOption) + : base(pathToNavigationProperty, navigationSource, filterOption, orderByOption, topOption, skipOption, countOption, searchOption, computeOption) + { + ExceptionUtils.CheckArgumentNotNull(pathToNavigationProperty, "pathToNavigationProperty"); + + this.SelectAndExpand = selectAndExpand; + this.LevelsOption = levelsOption; + } + + /// + /// Create an expand item, using a navigationProperty, its entity set, and any expand options. + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this expand level. + /// This level select and any sub expands for this expand item. + /// A filter clause for this expand (can be null) + /// An Orderby clause for this expand (can be null) + /// A top clause for this expand (can be null) + /// A skip clause for this expand (can be null) + /// An query count clause for this expand (can be null) + /// An levels clause for this expand (can be null) + /// An levels clause for this expand (can be null) + /// A compute clause for this expand (can be null) + /// An apply clause for this expand (can be null) + /// Throws if input pathToNavigationProperty is null. + public ExpandedNavigationSelectItem( + ODataExpandPath pathToNavigationProperty, + IEdmNavigationSource navigationSource, + SelectExpandClause selectAndExpand, + FilterClause filterOption, + OrderByClause orderByOption, + long? topOption, + long? skipOption, + bool? countOption, + SearchClause searchOption, + LevelsClause levelsOption, + ComputeClause computeOption, + ApplyClause applyOption) + : base(pathToNavigationProperty, navigationSource, filterOption, orderByOption, topOption, skipOption, countOption, searchOption, computeOption, applyOption) + { + ExceptionUtils.CheckArgumentNotNull(pathToNavigationProperty, "pathToNavigationProperty"); + + this.SelectAndExpand = selectAndExpand; + this.LevelsOption = levelsOption; + } + + /// + /// The select and expand clause for this expanded navigation. + /// + public SelectExpandClause SelectAndExpand + { + get; private set; + } + + /// + /// Gets the levels clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public LevelsClause LevelsOption + { + get; private set; + } + + /// + /// Translate using a . + /// + /// Type that the translator will return after visiting this item. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(SelectItemTranslator translator) + { + return translator.Translate(this); + } + + /// + /// Handle using a . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(SelectItemHandler handler) + { + handler.Handle(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedReferenceSelectItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedReferenceSelectItem.cs new file mode 100644 index 0000000..0debe3c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ExpandedReferenceSelectItem.cs @@ -0,0 +1,223 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Aggregation; + using Microsoft.OData.Edm; + + /// + /// This represents one level of expansion for a particular expansion tree with $ref operation. + /// + public class ExpandedReferenceSelectItem : SelectItem + { + /// + /// Create an Expand item using a nav prop, its entity set + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this ExpandItem + /// Throws if input pathToNavigationProperty is null. + public ExpandedReferenceSelectItem(ODataExpandPath pathToNavigationProperty, IEdmNavigationSource navigationSource) + : this(pathToNavigationProperty, navigationSource, null, null, null, null, null, null) + { + } + + /// + /// Create an expand item, using a navigationProperty, its entity set, and any expand options. + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this expand level. + /// A filter clause for this expand (can be null) + /// An Orderby clause for this expand (can be null) + /// A top clause for this expand (can be null) + /// A skip clause for this expand (can be null) + /// An query count clause for this expand (can be null) + /// A search clause for this expand (can be null) + /// Throws if input pathToNavigationProperty is null. + public ExpandedReferenceSelectItem( + ODataExpandPath pathToNavigationProperty, + IEdmNavigationSource navigationSource, + FilterClause filterOption, + OrderByClause orderByOption, + long? topOption, + long? skipOption, + bool? countOption, + SearchClause searchOption) + : this(pathToNavigationProperty, navigationSource, filterOption, orderByOption, topOption, skipOption, countOption, searchOption, null) + { + } + + /// + /// Create an expand item, using a navigationProperty, its entity set, and any expand options. + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this expand level. + /// A filter clause for this expand (can be null) + /// An Orderby clause for this expand (can be null) + /// A top clause for this expand (can be null) + /// A skip clause for this expand (can be null) + /// An query count clause for this expand (can be null) + /// A search clause for this expand (can be null) + /// A compute clause for this expand (can be null) + /// Throws if input pathToNavigationProperty is null. + public ExpandedReferenceSelectItem( + ODataExpandPath pathToNavigationProperty, + IEdmNavigationSource navigationSource, + FilterClause filterOption, + OrderByClause orderByOption, + long? topOption, + long? skipOption, + bool? countOption, + SearchClause searchOption, + ComputeClause computeOption) + : this(pathToNavigationProperty, navigationSource, filterOption, orderByOption, topOption, skipOption, countOption, searchOption, computeOption, null) + { + } + + /// + /// Create an expand item, using a navigationProperty, its entity set, and any expand options. + /// + /// the path to the navigation property for this expand item, including any type segments + /// the navigation source for this expand level. + /// A filter clause for this expand (can be null) + /// An Orderby clause for this expand (can be null) + /// A top clause for this expand (can be null) + /// A skip clause for this expand (can be null) + /// An query count clause for this expand (can be null) + /// A search clause for this expand (can be null) + /// A compute clause for this expand (can be null) + /// A apply clause for this expand (can be null) + /// Throws if input pathToNavigationProperty is null. + public ExpandedReferenceSelectItem( + ODataExpandPath pathToNavigationProperty, + IEdmNavigationSource navigationSource, + FilterClause filterOption, + OrderByClause orderByOption, + long? topOption, + long? skipOption, + bool? countOption, + SearchClause searchOption, + ComputeClause computeOption, + ApplyClause applyOption) + { + ExceptionUtils.CheckArgumentNotNull(pathToNavigationProperty, "pathToNavigationProperty"); + + this.PathToNavigationProperty = pathToNavigationProperty; + this.NavigationSource = navigationSource; + this.FilterOption = filterOption; + this.OrderByOption = orderByOption; + this.TopOption = topOption; + this.SkipOption = skipOption; + this.CountOption = countOption; + this.SearchOption = searchOption; + this.ComputeOption = computeOption; + this.ApplyOption = applyOption; + } + + /// + /// Gets the Path for this expand level. + /// This path includes zero or more type segments followed by exactly one Navigation Property. + /// + public ODataExpandPath PathToNavigationProperty + { + get; private set; + } + + /// + /// Gets the navigation source for this level. + /// + public IEdmNavigationSource NavigationSource + { + get; private set; + } + + /// + /// The filter clause for this expand item + /// + public FilterClause FilterOption + { + get; private set; + } + + /// + /// Gets the levels clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public SearchClause SearchOption + { + get; private set; + } + + /// + /// Gets the orderby clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public OrderByClause OrderByOption + { + get; private set; + } + + /// + /// Gets the compute clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public ComputeClause ComputeOption + { + get; private set; + } + + /// + /// Gets the apply clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public ApplyClause ApplyOption + { + get; private set; + } + + /// + /// Gets the top clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public long? TopOption + { + get; private set; + } + + /// + /// Gets the skip clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public long? SkipOption + { + get; private set; + } + + /// + /// Gets the count clause for this expand item. Can be null if not specified(and will always be null in NonOptionMode). + /// + public bool? CountOption + { + get; private set; + } + + /// + /// Translate using a . + /// + /// Type that the translator will return after visiting this item. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(SelectItemTranslator translator) + { + return translator.Translate(this); + } + + /// + /// Handle using a . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(SelectItemHandler handler) + { + handler.Handle(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterClause.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterClause.cs new file mode 100644 index 0000000..344245a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterClause.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// The result of parsing a $filter query option. + /// + public sealed class FilterClause + { + /// + /// The filter expression - this should evaluate to a single boolean value. + /// + private readonly SingleValueNode expression; + + /// + /// The parameter for the expression which represents a single value from the collection. + /// + private readonly RangeVariable rangeVariable; + + /// + /// Creates a . + /// + /// The filter expression - this should evaluate to a single boolean value. Cannot be null. + /// The parameter for the expression which represents a single value from the collection. Cannot be null. + /// Throws if the input expression or rangeVariable is null. + public FilterClause(SingleValueNode expression, RangeVariable rangeVariable) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + ExceptionUtils.CheckArgumentNotNull(rangeVariable, "parameter"); + + this.expression = expression; + this.rangeVariable = rangeVariable; + } + + /// + /// Gets the filter expression - this should evaluate to a single boolean value. + /// + public SingleValueNode Expression + { + get { return this.expression; } + } + + /// + /// Gets the parameter for the expression which represents a single value from the collection. + /// + public RangeVariable RangeVariable + { + get { return this.rangeVariable; } + } + + /// + /// Gets the type of item returned by this clause. + /// + public IEdmTypeReference ItemType + { + get + { + return this.RangeVariable.TypeReference; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterSegment.cs new file mode 100644 index 0000000..d188003 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/FilterSegment.cs @@ -0,0 +1,150 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// A segment representing $filter in a path. + /// + public sealed class FilterSegment : ODataPathSegment + { + /// + /// The filter expression - this should evaluate to a single boolean value. + /// + private readonly SingleValueNode expression; + + /// + /// The parameter for the alias which represents a single value from the collection. + /// + private readonly RangeVariable rangeVariable; + + /// + /// The type that this segment is bound to. + /// + private readonly IEdmType bindingType; + + /// + /// String representing "$filter(expression)". + /// + private readonly string literalText; + + /// + /// Build a segment representing $filter. + /// + /// The filter expression - this should evaluate to a single boolean value. + /// An expression that represents a single value from the collection. + /// The navigation source that this filter applies to. + /// Throws if any input parameter is null. + /// $filter should not be applied on singletons or single entities. + public FilterSegment(SingleValueNode expression, RangeVariable rangeVariable, IEdmNavigationSource navigationSource) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + ExceptionUtils.CheckArgumentNotNull(rangeVariable, "rangeVariable"); + ExceptionUtils.CheckArgumentNotNull(navigationSource, "navigationSource"); + + this.Identifier = UriQueryConstants.FilterSegment; + this.SingleResult = false; + this.TargetEdmNavigationSource = navigationSource; + this.TargetKind = RequestTargetKind.Resource; + this.TargetEdmType = rangeVariable.TypeReference.Definition; + + this.expression = expression; + this.rangeVariable = rangeVariable; + this.bindingType = navigationSource.Type; + + NodeToStringBuilder nodeToStringBuilder = new NodeToStringBuilder(); + string expressionString = nodeToStringBuilder.TranslateNode(expression); + this.literalText = UriQueryConstants.FilterSegment + ExpressionConstants.SymbolOpenParen + expressionString + + ExpressionConstants.SymbolClosedParen; + } + + /// + /// Gets the filter expression - this should evaluate to a single boolean value. + /// + public SingleValueNode Expression + { + get { return this.expression; } + } + + /// + /// Gets the parameter for the expression which represents a single value from the collection. + /// + public RangeVariable RangeVariable + { + get { return this.rangeVariable; } + } + + /// + /// Gets the type of item returned by this clause. + /// + public IEdmTypeReference ItemType + { + get { return this.RangeVariable.TypeReference; } + } + + /// + /// Gets the to which this applies. + /// + public override IEdmType EdmType + { + get { return this.bindingType; } + } + + /// + /// Gets the string representing "$filter(expression)". + /// + public string LiteralText + { + get { return this.literalText; } + } + + /// + /// Translate a using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + FilterSegment otherSegment = other as FilterSegment; + + return otherSegment != null && + otherSegment.TargetEdmNavigationSource == this.TargetEdmNavigationSource && + otherSegment.Expression == this.Expression && + otherSegment.ItemType == this.ItemType && + otherSegment.RangeVariable == this.RangeVariable; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/InNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/InNode.cs new file mode 100644 index 0000000..6ee2e0e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/InNode.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using Microsoft.OData; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Query node representing an In operator. + /// + public sealed class InNode : SingleValueNode + { + /// + /// Bool reference, as In always returns boolean. + /// + private readonly IEdmTypeReference boolTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(typeof(bool)); + + /// + /// The left operand. + /// + private readonly SingleValueNode left; + + /// + /// The right operand. + /// + private readonly CollectionNode right; + + /// + /// Create a InNode + /// + /// The left operand. + /// The right operand. + /// Throws if the left or right inputs are null. + /// Throws if the right operand single item type isn't the same type as the left operand. + public InNode(SingleValueNode left, CollectionNode right) + { + ExceptionUtils.CheckArgumentNotNull(left, "left"); + ExceptionUtils.CheckArgumentNotNull(right, "right"); + this.left = left; + this.right = right; + + if (!this.left.GetEdmTypeReference().IsAssignableFrom(this.right.ItemType) && + !this.right.ItemType.IsAssignableFrom(this.left.GetEdmTypeReference())) + { + throw new ArgumentException(ODataErrorStrings.Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType( + this.right.ItemType.FullName(), this.left.GetEdmTypeReference().FullName())); + } + } + + /// + /// Gets the left operand. + /// + public SingleValueNode Left + { + get + { + return this.left; + } + } + + /// + /// Gets the right operand. + /// + public CollectionNode Right + { + get + { + return this.right; + } + } + + /// + /// Gets the resource type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get + { + return this.boolTypeReference; + } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.In; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeyLookupNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeyLookupNode.cs new file mode 100644 index 0000000..df7c81f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeyLookupNode.cs @@ -0,0 +1,116 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System.Collections.Generic; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Node representing a key lookup on a collection. + /// + internal sealed class KeyLookupNode : SingleEntityNode + { + /// + /// The collection that this key is referring to. + /// + private readonly CollectionResourceNode source; + + /// + /// The navigation source containing the collection this key refers to. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// The resource type of the single value the key refers to. + /// + private readonly IEdmEntityTypeReference entityTypeReference; + + /// + /// List of the properties and their values that we use to look up our return value. + /// + private readonly IEnumerable keyPropertyValues; + + /// + /// Constructs a KeyLookupNode. + /// + /// The collection that this key is referring to. + /// List of the properties and their values that we use to look up our return value. + /// Throws if the input source is null. + public KeyLookupNode(CollectionResourceNode source, IEnumerable keyPropertyValues) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + this.source = source; + this.navigationSource = source.NavigationSource; + this.entityTypeReference = source.ItemStructuredType as IEdmEntityTypeReference; + this.keyPropertyValues = keyPropertyValues; + } + + /// + /// Gets the collection that this key is referring to. + /// + public CollectionResourceNode Source + { + get { return this.source; } + } + + /// + /// Gets the list of the properties and their values that we use to look up our return value. + /// + public IEnumerable KeyPropertyValues + { + get { return this.keyPropertyValues; } + } + + /// + /// Gets the resource type of the single value that the key refers to. + /// + public override IEdmTypeReference TypeReference + { + get + { + return this.entityTypeReference; + } + } + + /// + /// Gets the resource type of the single value that the key refers to. + /// + public override IEdmEntityTypeReference EntityTypeReference + { + get { return this.entityTypeReference; } + } + + /// + /// Gets the navigation source that contains the collection this key refers to. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the resource structured type of the single value that the key refers to. + /// + public override IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.entityTypeReference; } + } + + /// + /// Gets the kind for this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.KeyLookup; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeySegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeySegment.cs new file mode 100644 index 0000000..c57b877 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/KeySegment.cs @@ -0,0 +1,144 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing a key lookup in a path. + /// + public sealed class KeySegment : ODataPathSegment + { + /// + /// The set of key property names and the values to be used in searching for the given item. + /// + private readonly ReadOnlyCollection> keys; + + /// + /// The type of the item this key returns. + /// + private readonly IEdmEntityType edmType; + + /// + /// The navigation source that this key is used to search. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Construct a Segment that represents a key lookup. + /// + /// The set of key property names and the values to be used in searching for the given item. + /// The type of the item this key returns. + /// The navigation source that this key is used to search. + /// Throws if the input entity set is not related to the input type. + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Using key value pair is exactly what we want here.")] + public KeySegment(IEnumerable> keys, IEdmEntityType edmType, IEdmNavigationSource navigationSource) + { + this.keys = new ReadOnlyCollection>(keys.ToList()); + this.edmType = edmType; + this.navigationSource = navigationSource; + this.SingleResult = true; + + // Check that the type they gave us is related to the type of the set + if (navigationSource != null) + { + ExceptionUtil.ThrowIfTypesUnrelated(edmType, navigationSource.EntityType(), "KeySegments"); + } + } + + /// + /// Construct a Segment that represents a key lookup. + /// + /// The segment to apply the key to. + /// The set of key property names and the values to be used in searching for the given item. + /// The type of the item this key returns. + /// The navigation source that this key is used to search. + /// Throws if the input entity set is not related to the input type. + public KeySegment(ODataPathSegment previous, IEnumerable> keys, IEdmEntityType edmType, IEdmNavigationSource navigationSource) + : this(keys, edmType, navigationSource) + { + if (previous != null) + { + this.CopyValuesFrom(previous); + this.SingleResult = true; + } + } + + /// + /// Gets the set of key property names and the values to be used in searching for the given item. + /// + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Using key value pair is exactly what we want here.")] + public IEnumerable> Keys + { + get { return this.keys.AsEnumerable(); } + } + + /// + /// Gets the type of the item this key returns + /// + public override IEdmType EdmType + { + get { return this.edmType; } + } + + /// + /// Gets the navigation source that this key is used to search. + /// + public IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Translate a using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + KeySegment otherKeySegment = other as KeySegment; + return otherKeySegment != null && + otherKeySegment.Keys.SequenceEqual(this.Keys) && + otherKeySegment.EdmType == this.edmType && + otherKeySegment.NavigationSource == this.navigationSource; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LambdaNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LambdaNode.cs new file mode 100644 index 0000000..f9f6a2e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LambdaNode.cs @@ -0,0 +1,85 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.ObjectModel; + using System.Diagnostics; + + #endregion Namespaces + + /// + /// Node representing an Any/All query. + /// + public abstract class LambdaNode : SingleValueNode + { + /// + /// The collection of rangeVariables in scope for this Any or All. + /// + private readonly Collection rangeVariables; + + /// + /// The newest range variable added for by this Any or All. + /// + private readonly RangeVariable currentRangeVariable; + + /// + /// Create a LambdaNode + /// + /// The collection of rangeVariables in scope for this Any or All. + protected LambdaNode(Collection rangeVariables) : this(rangeVariables, null) + { + Debug.Assert(false, "Don't ever call this, its for backcompat"); + } + + /// + /// Create a LambdaNode + /// + /// The collection of rangeVariables in scope for this Any or All. + /// The newest range variable added for by this Any or All. + protected LambdaNode(Collection rangeVariables, RangeVariable currentRangeVariable) + { + this.rangeVariables = rangeVariables; + this.currentRangeVariable = currentRangeVariable; + } + + /// + /// Gets the collection of rangeVariables in scope for this Any or All. + /// + public Collection RangeVariables + { + get { return this.rangeVariables; } + } + + /// + /// Gets the newest range variable added for by this Any or All. + /// + public RangeVariable CurrentRangeVariable + { + get { return this.currentRangeVariable; } + } + + /// + /// Gets or Sets the associated boolean expression + /// + public SingleValueNode Body + { + get; + set; + } + + /// + /// Gets or Sets the parent entity set or navigation property + /// + public CollectionNode Source + { + get; + set; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LevelsClause.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LevelsClause.cs new file mode 100644 index 0000000..8181c24 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/LevelsClause.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// The result of parsing $levels option + /// + public sealed class LevelsClause + { + /// + /// Whether targeting at level max. + /// + private bool isMaxLevel; + + /// + /// The level value. + /// + private long level; + + /// + /// Constructs a from given parameters. + /// + /// Flag indicating max level is specified. + /// + /// The level value for the LevelsClause. + /// This value is only used when is set to false. + /// + public LevelsClause(bool isMaxLevel, long level) + { + this.isMaxLevel = isMaxLevel; + this.level = level; + } + + /// + /// Get a flag indicating whether max level is specified. + /// + public bool IsMaxLevel + { + get { return this.isMaxLevel; } + } + + /// + /// The level value for current expand option. + /// + /// This value is trivial when IsMaxLevel is True. + public long Level + { + get { return level; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/MetadataSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/MetadataSegment.cs new file mode 100644 index 0000000..4431eef --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/MetadataSegment.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing $metadata in a path. + /// + public sealed class MetadataSegment : ODataPathSegment + { + /// + /// Gets the singleton instance of MetadataSegment + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "MetadataSegment is immutable")] + public static readonly MetadataSegment Instance = new MetadataSegment(); + + /// + /// Build a segment to represent $metadata + /// + private MetadataSegment() + { + this.Identifier = UriQueryConstants.MetadataSegment; + this.TargetKind = RequestTargetKind.Metadata; + } + + /// + /// Gets the of this , which is always null. + /// + public override IEdmType EdmType + { + get { return null; } + } + + /// + /// Translate a . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Translate a . + /// + /// An implementation of the translator interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + return other is MetadataSegment; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamedFunctionParameterNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamedFunctionParameterNode.cs new file mode 100644 index 0000000..700761e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamedFunctionParameterNode.cs @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a semantically parsed parameter to a function. + /// + public class NamedFunctionParameterNode : QueryNode + { + /// + /// The name of this parameter + /// + private readonly string name; + + /// + /// The semantically parsed value of this parameter + /// + private readonly QueryNode value; + + /// + /// Creates a NamedFunctionParameterNode to represent a semantically parsed parameter to a function. + /// + /// the name of this function + /// the already semantically parsed value of this parameter. + public NamedFunctionParameterNode(string name, QueryNode value) + { + this.name = name; + this.value = value; + } + + /// + /// Gets the name of this parameter + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the semantically parsed value of this parameter. + /// + public QueryNode Value + { + get { return value; } + } + + /// + /// Gets the kind of this node + /// + public override QueryNodeKind Kind + { + get { return (QueryNodeKind)this.InternalKind; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.NamedFunctionParameter; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamespaceQualifiedWildcardSelectItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamespaceQualifiedWildcardSelectItem.cs new file mode 100644 index 0000000..a35e1eb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NamespaceQualifiedWildcardSelectItem.cs @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Class to represent the selection of all the actions and functions in a specified namespace. + /// + public sealed class NamespaceQualifiedWildcardSelectItem : SelectItem + { + /// + /// Creates an instance of this class with the specified with the Namespace. + /// + /// The namespace of the wildcard. + /// Throws if the input container is null. + public NamespaceQualifiedWildcardSelectItem(string namespaceName) + { + ExceptionUtils.CheckArgumentNotNull(namespaceName, "namespaceName"); + this.Namespace = namespaceName; + } + + /// + /// Gets the whose actions and functions should be selected. + /// + public string Namespace { get; private set; } + + /// + /// Translate using a . + /// + /// Type that the translator will return after visiting this item. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(SelectItemTranslator translator) + { + return translator.Translate(this); + } + + /// + /// Handle using a . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(SelectItemHandler handler) + { + handler.Handle(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertyLinkSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertyLinkSegment.cs new file mode 100644 index 0000000..8cbbc8c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertyLinkSegment.cs @@ -0,0 +1,105 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + using Microsoft.OData; + + #endregion Namespaces + + /// + /// A segment representing $ref (backwards compatibility version for below ODL 7.4.x) + /// + public sealed class NavigationPropertyLinkSegment : ODataPathSegment + { + /// + /// The navigation property this link or ref acts on. + /// + private readonly IEdmNavigationProperty navigationProperty; + + /// + /// Build a segment to represent $ref on a Nav prop + /// + /// The navigation property this link or ref acts on + /// The navigation source of entities linked to by this . This can be null. + /// Throws if the input navigationProperty is null. + public NavigationPropertyLinkSegment(IEdmNavigationProperty navigationProperty, IEdmNavigationSource navigationSource) + { + ExceptionUtils.CheckArgumentNotNull(navigationProperty, "navigationProperty"); + this.navigationProperty = navigationProperty; + this.TargetEdmNavigationSource = navigationSource; + + this.Identifier = navigationProperty.Name; + this.TargetEdmType = navigationProperty.Type.Definition; + this.SingleResult = !navigationProperty.Type.IsCollection(); + this.TargetKind = RequestTargetKind.Resource; + } + + /// + /// Gets the navigation property this link or ref acts on. + /// + public IEdmNavigationProperty NavigationProperty + { + get { return this.navigationProperty; } + } + + /// + /// Gets the navigation source of entities linked to by this . + /// + public IEdmNavigationSource NavigationSource + { + get { return this.TargetEdmNavigationSource; } + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return this.navigationProperty.Type.Definition; } + } + + /// + /// Translate a + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Translate a to walk a tree of s. + /// + /// An implementation of the translator interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another. + /// + /// The other segment to check. + /// True if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + NavigationPropertyLinkSegment otherLinkSegment = other as NavigationPropertyLinkSegment; + return otherLinkSegment != null && otherLinkSegment.NavigationProperty == this.navigationProperty; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertySegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertySegment.cs new file mode 100644 index 0000000..2e895cc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NavigationPropertySegment.cs @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing a navigation property + /// + public sealed class NavigationPropertySegment : ODataPathSegment + { + /// + /// The navigation property this segment represents. + /// + private readonly IEdmNavigationProperty navigationProperty; + + /// + /// Build a segment representing a navigation property. + /// + /// The navigation property this segment represents. + /// The navigation source of the entities targetted by this navigation property. This can be null. + /// Throws if the input navigationProperty is null. + public NavigationPropertySegment(IEdmNavigationProperty navigationProperty, IEdmNavigationSource navigationSource) + { + ExceptionUtils.CheckArgumentNotNull(navigationProperty, "navigationProperty"); + + this.navigationProperty = navigationProperty; + this.TargetEdmNavigationSource = navigationSource; + + this.Identifier = navigationProperty.Name; + this.TargetEdmType = navigationProperty.Type.Definition; + this.SingleResult = !navigationProperty.Type.IsCollection(); + this.TargetKind = RequestTargetKind.Resource; + } + + /// + /// Gets the navigation property represented by this NavigationPropertySegment. + /// + public IEdmNavigationProperty NavigationProperty + { + get { return this.navigationProperty; } + } + + /// + /// Gets the navigation source of the entities targetted by this Navigation Property. + /// This can be null. + /// + public IEdmNavigationSource NavigationSource + { + get { return this.TargetEdmNavigationSource; } + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return this.navigationProperty.Type.Definition; } + } + + /// + /// Translate a . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Translate a to walk a tree of s. + /// + /// An implementation of the translator interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Checks if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + NavigationPropertySegment otherNavPropSegment = other as NavigationPropertySegment; + return otherNavPropSegment != null && otherNavPropSegment.NavigationProperty == this.NavigationProperty; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariable.cs new file mode 100644 index 0000000..f3d9a1f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariable.cs @@ -0,0 +1,94 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + #endregion Namespaces + + /// + /// A rangeVariable from an Any or All that doesn't refer to an entity set or complex collection. + /// + public sealed class NonResourceRangeVariable : RangeVariable + { + /// + /// The name of the associated rangeVariable + /// + private readonly string name; + + /// + /// The collection that this rangeVariable node iterates over, can be null in the case of + /// single value nodes. + /// + private readonly CollectionNode collectionNode; + + /// + /// The type of the value the range variable represents + /// + private readonly IEdmTypeReference typeReference; + + /// + /// Creates a . + /// + /// The name of the associated range variable. + /// The type of the value the range variable represents. + /// The collection that this rangeVariable node iterates over, can be null in the case of single value nodes. + /// Throws if the input name is null. + /// Throws if the input type reference is an entity type. + public NonResourceRangeVariable(string name, IEdmTypeReference typeReference, CollectionNode collectionNode) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + this.name = name; + if (typeReference != null) + { + if (typeReference.Definition.TypeKind.IsStructured()) + { + // TODO: update message #644 + throw new ArgumentException( + ODataErrorStrings.Nodes_NonentityParameterQueryNodeWithEntityType(typeReference.FullName())); + } + } + + this.typeReference = typeReference; + this.collectionNode = collectionNode; + } + + /// + /// Gets the name of the associated range variable. + /// + public override string Name + { + get { return this.name; } + } + + /// + /// Gets the type of the value the range variable represents. + /// + public override IEdmTypeReference TypeReference + { + get { return this.typeReference; } + } + + /// + /// Gets the collection that this range variable node iterates over, can be null in the case of single value nodes. + /// + public CollectionNode CollectionNode + { + get { return this.collectionNode; } + } + + /// + /// Gets the kind of this range variable. + /// + public override int Kind + { + get { return RangeVariableKind.NonResource; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariableReferenceNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariableReferenceNode.cs new file mode 100644 index 0000000..b7a70ae --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/NonResourceRangeVariableReferenceNode.cs @@ -0,0 +1,97 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// A node that represents a rangeVariable that iterates over a non resource collection. + /// + public sealed class NonResourceRangeVariableReferenceNode : SingleValueNode + { + /// + /// The name of the associated rangeVariable + /// + private readonly string name; + + /// + /// The type item referred to by this rangeVariable. + /// + private readonly IEdmTypeReference typeReference; + + /// + /// Reference to a rangeVariable on the binding stack. + /// + private readonly NonResourceRangeVariable rangeVariable; + + /// + /// Creates a . + /// + /// The name of the associated rangeVariable + /// Reference to a rangeVariable on the binding stack. + /// Throws if input name or rangeVariable is null. + public NonResourceRangeVariableReferenceNode(string name, NonResourceRangeVariable rangeVariable) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + ExceptionUtils.CheckArgumentNotNull(rangeVariable, "rangeVariable"); + this.name = name; + this.typeReference = rangeVariable.TypeReference; + this.rangeVariable = rangeVariable; + } + + /// + /// Gets the name of the associated rangeVariable. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the type item referred to by this rangeVariable. + /// + public override IEdmTypeReference TypeReference + { + get { return this.typeReference; } + } + + /// + /// Gets the reference to a rangeVariable on the binding stack. + /// + public NonResourceRangeVariable RangeVariable + { + get { return this.rangeVariable; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.NonResourceRangeVariableReference; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs new file mode 100644 index 0000000..0726469 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataExpandPath.cs @@ -0,0 +1,93 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// A specific type of which can only contain instances of or or of complex. + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "ODataExpandPathCollection just doesn't sound right")] + public class ODataExpandPath : ODataPath + { + /// + /// Create an ODataPath object to represent a path semantically + /// + /// The list of segments in the path. + /// Throws if this list of segments doesn't match the requirements for a $expand + public ODataExpandPath(IEnumerable segments) + : base(segments) + { + this.ValidatePath(); + } + + /// + /// Create an ODataPath object based on a single segment + /// + /// A list of segments in the path. + /// Throws if this list of segments doesn't match the requirements for a $expand + public ODataExpandPath(params ODataPathSegment[] segments) + : base(segments) + { + this.ValidatePath(); + } + + /// + /// Gets the navigation property for this expand path. + /// + /// the navigation property for this expand path. + internal IEdmNavigationProperty GetNavigationProperty() + { + return ((NavigationPropertySegment)this.LastSegment).NavigationProperty; + } + + /// + /// Ensure that this expand path contains only valid segment types. + /// + /// Throws if this list of segments doesn't match the requirements for a $expand + private void ValidatePath() + { + int index = 0; + bool foundNavProp = false; + foreach (ODataPathSegment segment in this) + { + if (segment is TypeSegment) + { + if (index == this.Count - 1) + { + throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + } + } + else if (segment is PropertySegment) + { + if (index == this.Count - 1) + { + throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + } + } + else if (segment is NavigationPropertySegment) + { + if (index < this.Count - 1 || foundNavProp) + { + throw new ODataException(ODataErrorStrings.ODataExpandPath_OnlyLastSegmentMustBeNavigationProperty); + } + + foundNavProp = true; + } + else + { + throw new ODataException(ODataErrorStrings.ODataExpandPath_InvalidExpandPathSegment(segment.GetType().Name)); + } + + index++; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPath.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPath.cs new file mode 100644 index 0000000..0856bce --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPath.cs @@ -0,0 +1,156 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + + #endregion Namespaces + + /// + /// A representation of the path portion of an OData URI which is made up of s. + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "ODataPathCollection just doesn't sound right")] + public class ODataPath : IEnumerable + { + /// + /// The segments that make up this path. + /// + private readonly IList segments; + + /// + /// Creates a new instance of containing the given segments. + /// + /// The segments that make up the path. + /// Throws if input segments is null. + public ODataPath(IEnumerable segments) + { + ExceptionUtils.CheckArgumentNotNull(segments, "segments"); + this.segments = segments.ToList(); + if (this.segments.Any(s => s == null)) + { + throw Error.ArgumentNull("segments"); + } + } + + /// + /// Creates a new instance of containing the given segments. + /// + /// The segments that make up the path. + /// Throws if input segments is null. + public ODataPath(params ODataPathSegment[] segments) + : this((IEnumerable)segments) + { + } + + /// + /// Gets the first segment in the path. Returns null if the path is empty. + /// + public ODataPathSegment FirstSegment + { + get + { + return this.segments.Count == 0 ? null : this.segments[0]; + } + } + + /// + /// Get the last segment in the path. Returns null if the path is empty. + /// + public ODataPathSegment LastSegment + { + get + { + return this.segments.Count == 0 ? null : this.segments[this.segments.Count - 1]; + } + } + + /// + /// Get the number of segments in this path. + /// + public int Count + { + get { return this.segments.Count; } + } + + /// + /// Get the segments enumerator + /// + /// The segments enumerator + public IEnumerator GetEnumerator() + { + return this.segments.GetEnumerator(); + } + + /// + /// get the segments enumerator + /// + /// The segments enumerator. + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + /// Walk this path using a translator + /// + /// the return type of the translator + /// a user defined translation path + /// an enumerable containing user defined objects for each segment + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We would rather ship the PathSegmentTranslator so that its more extensible later")] + public IEnumerable WalkWith(PathSegmentTranslator translator) + { + return this.segments.Select(segment => segment.TranslateWith(translator)); + } + + /// + /// Walk this path using a handler + /// + /// the handler that will be applied to each segment + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We would rather ship the PathSegmentHandler so that its more extensible later")] + public void WalkWith(PathSegmentHandler handler) + { + foreach (ODataPathSegment segment in this.segments) + { + segment.HandleWith(handler); + } + } + + /// + /// Checks if this path is equal to another path. + /// + /// The other path to compare it to + /// True if the two paths are equal + /// Throws if the input other is null. + internal bool Equals(ODataPath other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + if (this.segments.Count != other.segments.Count) + { + return false; + } + + return !this.segments.Where((t, i) => !t.Equals(other.segments[i])).Any(); + } + + /// + /// Add a segment to this path. + /// + /// the segment to add + /// Throws if the input newSegment is null. + internal void Add(ODataPathSegment newSegment) + { + ExceptionUtils.CheckArgumentNotNull(newSegment, "newSegment"); + this.segments.Add(newSegment); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs new file mode 100644 index 0000000..8e80c52 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathExtensions.cs @@ -0,0 +1,307 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; + +namespace Microsoft.OData.UriParser +{ + using System.Text; + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + /// + /// Extension methods for . These method provide convenince functions. + /// TODO: Implement this class and it's visitors. These are stubs. + /// + /// + /// The values that these methods compute are not cached. + /// + internal static class ODataPathExtensions + { + /// + /// Computes the of the resource identified by this . + /// + /// Path to compute the type for. + /// The of the resource, or null if the path does not identify a + /// resource with a type. + public static IEdmTypeReference EdmType(this ODataPath path) + { + return path.LastSegment.EdmType.ToTypeReference(); + } + + /// + /// Computes the of the resource identified by this . + /// + /// Path to compute the set for. + /// The of the resource, or null if the path does not identify a + /// resource that is part of a set. + public static IEdmNavigationSource NavigationSource(this ODataPath path) + { + return path.LastSegment.TranslateWith(new DetermineNavigationSourceTranslator()); + } + + /// + /// Computes whether or not the resource identified by this is a collection. + /// + /// Path to perform the computation on. + /// True if the resource if a resource set or collection of primitive or complex types. False otherwise. + public static bool IsCollection(this ODataPath path) + { + return path.LastSegment.TranslateWith(new IsCollectionTranslator()); + } + + /// + /// Build a segment representing a navigation property. + /// + /// Path to perform the computation on. + /// The navigation property this segment represents. + /// The navigation source of the entities targeted by this navigation property. This can be null. + /// The ODataPath with navigation property segment appended in the end. + public static ODataPath AppendNavigationPropertySegment(this ODataPath path, IEdmNavigationProperty navigationProperty, IEdmNavigationSource navigationSource) + { + var newPath = new ODataPath(path); + NavigationPropertySegment np = new NavigationPropertySegment(navigationProperty, navigationSource); + newPath.Add(np); + return newPath; + } + + /// + /// Build a segment representing a property. + /// + /// Path to perform the computation on. + /// The property this segment represents. + /// >The ODataPath with property segment appended in the end. + public static ODataPath AppendPropertySegment(this ODataPath path, IEdmStructuralProperty property) + { + var newPath = new ODataPath(path); + PropertySegment propertySegment = new PropertySegment(property); + newPath.Add(propertySegment); + return newPath; + } + + /// + /// Append the key segment in the end of ODataPath, the method does not modify current ODataPath instance, + /// it returns a new ODataPath without ending type segment. + /// If last segment is type cast, the key would be appended before type cast segment. + /// + /// Path to perform the computation on. + /// The set of key property names and the values to be used in searching for the given item. + /// The type of the item this key returns. + /// The navigation source that this key is used to search. + /// The ODataPath with key segment appended + public static ODataPath AppendKeySegment(this ODataPath path, IEnumerable> keys, IEdmEntityType edmType, IEdmNavigationSource navigationSource) + { + var handler = new SplitEndingSegmentOfTypeHandler(); + path.WalkWith(handler); + KeySegment keySegment = new KeySegment(keys, edmType, navigationSource); + ODataPath newPath = handler.FirstPart; + newPath.Add(keySegment); + foreach (var segment in handler.LastPart) + { + newPath.Add(segment); + } + + return newPath; + } + + /// + /// Remove the key segment in the end of ODataPath, the method does not modify current ODataPath instance, + /// it returns a new ODataPath without ending type segment. + /// If last segment is type cast, the key before type cast segment would be removed. + /// + /// Path to perform the computation on. + /// The ODataPath without key segment removed + public static ODataPath TrimEndingKeySegment(this ODataPath path) + { + var typeHandler = new SplitEndingSegmentOfTypeHandler(); + var keyHandler = new SplitEndingSegmentOfTypeHandler(); + path.WalkWith(typeHandler); + typeHandler.FirstPart.WalkWith(keyHandler); + ODataPath newPath = keyHandler.FirstPart; + foreach (var segment in typeHandler.LastPart) + { + newPath.Add(segment); + } + + return newPath; + } + + /// + /// Remove the type-cast segment in the end of ODataPath, the method does not modify current ODataPath instance, + /// it returns a new ODataPath without ending type segment. + /// + /// Path to perform the computation on. + /// The ODataPath without type-cast in the end + public static ODataPath TrimEndingTypeSegment(this ODataPath path) + { + var handler = new SplitEndingSegmentOfTypeHandler(); + path.WalkWith(handler); + return handler.FirstPart; + } + + /// + /// Computes whether or not the ODataPath targets at an individual property. + /// + /// Path to perform the computation on. + /// True if the the ODataPath targets at an individual property. False otherwise. + public static bool IsIndividualProperty(this ODataPath path) + { + ODataPathSegment lastNonTypeCastSegment = path.TrimEndingTypeSegment().LastSegment; + return lastNonTypeCastSegment is PropertySegment || lastNonTypeCastSegment is DynamicPathSegment; + } + + /// + /// Computes whether or not the ODataPath targets at an unknown segment. + /// + /// Path to perform the computation on. + /// True if the the ODataPath targets at an unknown segment. False otherwise. + public static bool IsUndeclared(this ODataPath path) + { + ODataPathSegment lastNonTypeCastSegment = path.TrimEndingTypeSegment().LastSegment; + return lastNonTypeCastSegment is DynamicPathSegment; + } + + /// + /// Get the string representation of . + /// mainly translate Context Url path. + /// + /// Path to perform the computation on. + /// The string representation of the Context Url path. + public static string ToContextUrlPathString(this ODataPath path) + { + StringBuilder pathString = new StringBuilder(); + PathSegmentToContextUrlPathTranslator pathTranslator = PathSegmentToContextUrlPathTranslator.DefaultInstance; + ODataPathSegment priorSegment = null; + bool foundOperationWithoutPath = false; + foreach (ODataPathSegment segment in path) + { + OperationSegment operationSegment = segment as OperationSegment; + OperationImportSegment operationImportSegment = segment as OperationImportSegment; + if (operationImportSegment != null) + { + IEdmOperationImport operationImport = operationImportSegment.OperationImports.FirstOrDefault(); + Debug.Assert(operationImport != null); + + EdmPathExpression pathExpression = operationImport.EntitySet as EdmPathExpression; + if (pathExpression != null) + { + Debug.Assert(priorSegment == null); // operation import is always the first segment? + pathString.Append(pathExpression.Path); + } + else + { + pathString = operationImport.Operation.ReturnType != null ? + new StringBuilder(operationImport.Operation.ReturnType.FullName()) : + new StringBuilder("Edm.Untyped"); + + foundOperationWithoutPath = true; + } + } + else if (operationSegment != null) + { + IEdmOperation operation = operationSegment.Operations.FirstOrDefault(); + Debug.Assert(operation != null); + + if (operation.IsBound && + priorSegment != null && + operation.Parameters.First().Type.Definition == priorSegment.EdmType) + { + if (operation.EntitySetPath != null) + { + foreach (string pathSegment in operation.EntitySetPath.PathSegments.Skip(1)) + { + pathString.Append('/'); + pathString.Append(pathSegment); + } + } + else if (operationSegment.EntitySet != null) + { + // Is it correct to check EntitySet? + pathString = new StringBuilder(operationSegment.EntitySet.Name); + } + else + { + pathString = operation.ReturnType != null ? + new StringBuilder(operation.ReturnType.FullName()) : + new StringBuilder("Edm.Untyped"); + + foundOperationWithoutPath = true; + } + } + } + else + { + if (foundOperationWithoutPath) + { + pathString = new StringBuilder(segment.EdmType.FullTypeName()); + foundOperationWithoutPath = false; + } + else + { + pathString.Append(segment.TranslateWith(pathTranslator)); + } + } + + priorSegment = segment; + } + + return pathString.ToString().TrimStart('/'); + } + + /// + /// Get the string representation of . + /// mainly translate Query Url path. + /// + /// Path to perform the computation on. + /// Mark whether key is segment + /// The string representation of the Query Url path. + public static string ToResourcePathString(this ODataPath path, ODataUrlKeyDelimiter urlKeyDelimiter) + { + return string.Concat(path.WalkWith(new PathSegmentToResourcePathTranslator(urlKeyDelimiter)).ToArray()).TrimStart('/'); + } + + /// + /// Translate an ODataExpandPath into an ODataSelectPath + /// Depending on the constructor of ODataSelectPath to validate that we aren't adding any + /// segments that are illegal for a select. + /// + /// the ODataExpandPath to translate + /// A new ODataSelect path with the same segments as the expand path. + public static ODataSelectPath ToSelectPath(this ODataExpandPath path) + { + return new ODataSelectPath(path); + } + + /// + /// Translate an ODataSelectPath into an ODataExpandPath + /// Depending on the constructor of ODataExpandPath to validate that we aren't adding any + /// segments that are illegal for an expand. + /// + /// the ODataSelectPath to translate + /// A new ODataExpand path with the same segments as the select path. + public static ODataExpandPath ToExpandPath(this ODataSelectPath path) + { + return new ODataExpandPath(path); + } + + /// + /// Gets the target navigation source to the ODataPath. + /// + /// Path to compute the set for. + /// The target navigation source to the ODataPath. + internal static IEdmNavigationSource TargetNavigationSource(this ODataPath path) + { + if (path == null) + { + return null; + } + + return new ODataPathInfo(path).TargetNavigationSource; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathSegment.cs new file mode 100644 index 0000000..5d6399d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataPathSegment.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Diagnostics; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// The semantic representation of a segment in a path. + /// + public abstract class ODataPathSegment + { + /// + /// Creates a new Segment and copies values from another Segment. + /// + /// Segment to copy values from. + internal ODataPathSegment(ODataPathSegment other) + { + this.CopyValuesFrom(other); + } + + /// + /// Creates a new Segment. + /// + protected ODataPathSegment() + { + } + + /// + /// Gets the of this . + /// + /// This property can be null. Not all segments have a Type, such as a . + public abstract IEdmType EdmType { get; } + + /// Returns the identifier for this segment i.e. string part without the keys. + public string Identifier { get; set; } + + #region Temporary Internal Properties + /// Whether the segment targets a single result or not. + internal bool SingleResult { get; set; } + + /// The navigation source targeted by this segment. Can be null. + internal IEdmNavigationSource TargetEdmNavigationSource { get; set; } + + /// The type targeted by this segment. Can be null. + internal IEdmType TargetEdmType { get; set; } + + /// The kind of resource targeted by this segment. + internal RequestTargetKind TargetKind { get; set; } + #endregion + + /// + /// Translate a using an implementation of. + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + public abstract T TranslateWith(PathSegmentTranslator translator); + + /// + /// Handle a using an implementation of a . + /// + /// An implementation of the handler interface. + public abstract void HandleWith(PathSegmentHandler handler); + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check + /// true if the segments are equal. + internal virtual bool Equals(ODataPathSegment other) + { + return ReferenceEquals(this, other); + } + +#if DEBUG + /// In DEBUG builds, ensures that invariants for the class hold. + internal void AssertValid() + { + Debug.Assert(Enum.IsDefined(typeof(RequestTargetKind), this.TargetKind), "enum value is not valid"); + Debug.Assert( + this.TargetKind != RequestTargetKind.Resource || + this.TargetEdmNavigationSource != null || + this.TargetEdmType != null || + this.EdmType != null || + this.TargetKind == RequestTargetKind.Dynamic || + this is OperationSegment || + this is OperationImportSegment, + "All resource targets (except for some service operations and open properties) should have a container."); + Debug.Assert(!string.IsNullOrEmpty(this.Identifier), "identifier must not be empty or null."); + } +#endif + + /// + /// Copies over all the values of the internal-only properties from one segment to another. + /// + /// The segment to copy from. + internal void CopyValuesFrom(ODataPathSegment other) + { + Debug.Assert(other != null, "other != null"); + this.Identifier = other.Identifier; + this.SingleResult = other.SingleResult; + this.TargetEdmNavigationSource = other.TargetEdmNavigationSource; + this.TargetKind = other.TargetKind; + this.TargetEdmType = other.TargetEdmType; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataSelectPath.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataSelectPath.cs new file mode 100644 index 0000000..310ea08 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataSelectPath.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using ODataErrorStrings = Microsoft.OData.Strings; + + /// + /// A specific type of which can only contain instances of , , + /// , , or . + /// + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "ODataSelectPathCollection just doesn't sound right")] + public class ODataSelectPath : ODataPath + { + /// + /// Create an ODataSelectPath + /// + /// The list of segments that makes up this path. + /// Throws if the list of segments doesn't match the requirements for a path in $select + public ODataSelectPath(IEnumerable segments) + : base(segments) + { + this.ValidatePath(); + } + + /// + /// Create an ODataPath object based on a single segment + /// + /// The list of segments that makes up this path. + /// Throws if the list of segments doesn't match the requirements for a path in $select + public ODataSelectPath(params ODataPathSegment[] segments) + : base(segments) + { + this.ValidatePath(); + } + + /// + /// Ensure that the segments given to us are valid select segments. + /// + /// Throws if the list of segments doesn't match the requirements for a path in $select + private void ValidatePath() + { + int index = 0; + + if (this.Count == 1 && this.FirstSegment is TypeSegment) + { + throw new ODataException(ODataErrorStrings.ODataSelectPath_CannotOnlyHaveTypeSegment); + } + + foreach (ODataPathSegment segment in this) + { + if (segment is NavigationPropertySegment) + { + if (index != this.Count - 1) + { + throw new ODataException(ODataErrorStrings.ODataSelectPath_NavPropSegmentCanOnlyBeLastSegment); + } + } + else if (segment is OperationSegment) + { + if (index != this.Count - 1) + { + throw new ODataException(ODataErrorStrings.ODataSelectPath_OperationSegmentCanOnlyBeLastSegment); + } + } + else if (segment is DynamicPathSegment || segment is PropertySegment || segment is TypeSegment || segment is AnnotationSegment) + { + index++; + continue; + } + else + { + throw new ODataException( + ODataErrorStrings.ODataSelectPath_InvalidSelectPathSegmentType(segment.GetType().Name)); + } + + index++; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataUnresolvedFunctionParameterAlias.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataUnresolvedFunctionParameterAlias.cs new file mode 100644 index 0000000..440f2af --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ODataUnresolvedFunctionParameterAlias.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Represents an aliased parameter in a function call that has not yet been resolved to a specific value. + /// + public class ODataUnresolvedFunctionParameterAlias + { + /// + /// Initializes a new instance of . + /// + /// The alias provided as the parameter value. + /// The EDM type of the parameter represented by this alias. + public ODataUnresolvedFunctionParameterAlias(string alias, IEdmTypeReference type) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(alias, "alias"); + this.Alias = alias; + this.Type = type; + } + + /// + /// The EDM type of the parameter represented by this alias. + /// + public IEdmTypeReference Type { get; private set; } + + /// + /// The alias provided as the parameter value. + /// + public string Alias { get; private set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationImportSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationImportSegment.cs new file mode 100644 index 0000000..8c3566a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationImportSegment.cs @@ -0,0 +1,277 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// A segment representing a call to an actionimport, or functionImport. + /// + //// TODO: update code that is duplicate between operation and operation import segment, likely could share a baseclass. + public sealed class OperationImportSegment : ODataPathSegment + { + /// + /// Sentinel type marking that we could not determine the return type for this segment. + /// + private static readonly IEdmType UnknownSentinel = new EdmEnumType("Sentinel", "UndeterminableTypeMarker"); + + /// + /// The list of possible FunctionImport overloads for this segment. + /// + private readonly ReadOnlyCollection operationImports; + + /// + /// the list of parameters to this operation. + /// + private readonly ReadOnlyCollection parameters; + + /// + /// The containing the entities that this function returns. + /// This will be null if entities are not returned by this operation, or if there is any ambiguity. + /// + private readonly IEdmEntitySetBase entitySet; + + /// + /// The type of item returned by the operation(s), if known. + /// + private readonly IEdmType computedReturnEdmType; + + /// + /// Build a segment representing a call to an operation - action, function, or service operation. + /// + /// A single operation import that this segment will represent. + /// The containing the entities that this function returns. + /// Throws if the input operation is null. + public OperationImportSegment(IEdmOperationImport operationImport, IEdmEntitySetBase entitySet) + : this() + { + ExceptionUtils.CheckArgumentNotNull(operationImport, "operationImport"); + this.operationImports = new ReadOnlyCollection(new[] { operationImport }); + this.Identifier = operationImport.Name; + this.entitySet = entitySet; + this.computedReturnEdmType = operationImport.Operation.ReturnType != null ? operationImport.Operation.ReturnType.Definition : null; + this.EnsureTypeAndSetAreCompatable(); + + if (this.computedReturnEdmType != null) + { + this.TargetEdmNavigationSource = entitySet; + this.TargetEdmType = computedReturnEdmType; + this.TargetKind = this.TargetEdmType.GetTargetKindFromType(); + this.SingleResult = computedReturnEdmType.TypeKind != EdmTypeKind.Collection; + } + else + { + this.TargetEdmNavigationSource = null; + this.TargetEdmType = null; + this.TargetKind = RequestTargetKind.VoidOperation; + } + } + + /// + /// Build a segment representing a call to an operation - action, function, or service operation. + /// + /// A single operation import that this segment will represent. + /// The containing the entities that this function returns. + /// The list of parameters supplied to this segment. + public OperationImportSegment(IEdmOperationImport operationImport, IEdmEntitySetBase entitySet, IEnumerable parameters) + : this(operationImport, entitySet) + { + this.parameters = new ReadOnlyCollection(parameters == null ? new List() : parameters.ToList()); + } + + /// + /// Build a segment representing a call to an operation - action, function, or service operation. + /// + /// The list of possible FunctionImport overloads for this segment. + /// The containing the entities that this function returns. + /// Throws if the input operations is null. + public OperationImportSegment(IEnumerable operationImports, IEdmEntitySetBase entitySet) + : this() + { + // DEVNOTE: This ctor is only used in Select and Expand currently. + ExceptionUtils.CheckArgumentNotNull(operationImports, "operations"); + this.operationImports = new ReadOnlyCollection(operationImports.ToList()); + + // check for empty after we copy locally, so that we don't do multiple enumeration of input + ExceptionUtils.CheckArgumentCollectionNotNullOrEmpty(this.operationImports, "operations"); + + this.Identifier = this.operationImports.First().Name; + + // Determine the return type of the operation. This is only possible if all the candidate operations agree on the return type. + // TODO: Because we work on types and not type references, if there are nullability differences we'd ignore them... + IEdmType typeSoFar = this.operationImports.First().Operation.ReturnType != null + ? this.operationImports.First().Operation.ReturnType.Definition + : null; + if (typeSoFar == null) + { + // This is for void operations + if (this.operationImports.Any(operation => operation.Operation.ReturnType != null)) + { + typeSoFar = UnknownSentinel; + } + } + else if (this.operationImports.Any(operationImport => !typeSoFar.IsEquivalentTo(operationImport.Operation.ReturnType.Definition))) + { + typeSoFar = UnknownSentinel; + } + + this.computedReturnEdmType = typeSoFar; + this.entitySet = entitySet; + this.EnsureTypeAndSetAreCompatable(); + } + + /// + /// Creates a segment representing a call to an operation - action, function or service operation. + /// + /// The list of possible FunctionImport overloads for this segment. + /// The containing the entities that this function returns. + /// The list of parameters supplied to this segment. + public OperationImportSegment(IEnumerable operationImports, IEdmEntitySetBase entitySet, IEnumerable parameters) + : this(operationImports, entitySet) + { + this.parameters = new ReadOnlyCollection(parameters == null ? new List() : parameters.ToList()); + } + + /// + /// Creates a segment representing a call to an operation - action, function or service operation. + /// + private OperationImportSegment() + { + this.parameters = new ReadOnlyCollection(new List()); + } + + /// + /// Gets the list of possible operation import overloads for this segment. + /// TODO: Change this to a property that returns just an operationImport. + /// + public IEnumerable OperationImports + { + get { return this.operationImports.AsEnumerable(); } + } + + /// + /// Gets the list of parameters for this segment. + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Gets the of this . + /// + /// + /// This value will be null for void service operations. + /// If there are multiple candidate operations with varying return types, then this property will throw. + /// + /// Throws if the type is unknown. + public override IEdmType EdmType + { + get + { + if (ReferenceEquals(this.computedReturnEdmType, UnknownSentinel)) + { + throw new ODataException(ODataErrorStrings.OperationSegment_ReturnTypeForMultipleOverloads); + } + + return this.computedReturnEdmType; + } + } + + /// + /// Gets the containing the entities that this function returns. + /// This will be null if entities are not returned by this operation, or if there is any ambiguity. + /// + public IEdmEntitySetBase EntitySet + { + get { return this.entitySet; } + } + + /// + /// Translate a . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a . + /// + /// An implementation of the handle interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + OperationImportSegment otherOperation = other as OperationImportSegment; + return otherOperation != null && + otherOperation.OperationImports.SequenceEqual(this.OperationImports) && + otherOperation.EntitySet == this.entitySet; + } + + /// + /// Ensures that the entity set and computed return type make sense. + /// + /// Throws if the return type computed from the function call is null, or if the return type is not in the same hierarchy as the entity set provided. + private void EnsureTypeAndSetAreCompatable() + { + // The return type should be in the type hierarchy of the set, We could be a little tighter but we don't want + // to overdo it here. + // If they didn't get us an entity set, or if we couldn't compute a single return type, then we don't need these checks + if (this.entitySet == null || this.computedReturnEdmType == UnknownSentinel) + { + return; + } + + // Void operations cannot specify return entity set + if (this.computedReturnEdmType == null) + { + throw new ODataException(ODataErrorStrings.OperationSegment_CannotReturnNull); + } + + // Unwrap the return type if it's a collection + var unwrappedCollectionType = this.computedReturnEdmType; + var collectionType = this.computedReturnEdmType as IEdmCollectionType; + if (collectionType != null) + { + unwrappedCollectionType = collectionType.ElementType.Definition; + } + + // Ensure that the return type is in the same type hierarhcy as the entity set provided + if (!this.entitySet.EntityType().IsOrInheritsFrom(unwrappedCollectionType) && !unwrappedCollectionType.IsOrInheritsFrom(this.entitySet.EntityType())) + { + throw new ODataException(ODataErrorStrings.OperationSegment_CannotReturnNull); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegment.cs new file mode 100644 index 0000000..85d4f8b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegment.cs @@ -0,0 +1,273 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// A segment representing a call to an action, function, or service operation. + /// + //// TODO: update code that is duplicate between operation and operation import segment, likely could share a baseclass. + public sealed class OperationSegment : ODataPathSegment + { + /// + /// Sentinel type marking that we could not determine the return type for this segment. + /// + private static readonly IEdmType UnknownSentinel = new EdmEnumType("Sentinel", "UndeterminableTypeMarker"); + + /// + /// The list of possible operations overloads for this segment. + /// + private readonly ReadOnlyCollection operations; + + /// + /// the list of parameters to this operation. + /// + private readonly ReadOnlyCollection parameters; + + /// + /// The containing the entities that this function returns. + /// This will be null if entities are not returned by this operation, or if there is any ambiguity. + /// + private readonly IEdmEntitySetBase entitySet; + + /// + /// The type of item returned by the operation(s), if known. + /// + private readonly IEdmType computedReturnEdmType; + + /// + /// Build a segment representing a call to an operation - action, function, or service operation. + /// + /// A single operation import that this segment will represent. + /// The containing the entities that this function returns. + /// Throws if the input operation is null. + public OperationSegment(IEdmOperation operation, IEdmEntitySetBase entitySet) + : this() + { + ExceptionUtils.CheckArgumentNotNull(operation, "operation"); + this.operations = new ReadOnlyCollection(new[] { operation }); + this.entitySet = entitySet; + this.computedReturnEdmType = operation.ReturnType != null ? operation.ReturnType.Definition : null; + this.EnsureTypeAndSetAreCompatable(); + + if (this.computedReturnEdmType != null) + { + this.TargetEdmNavigationSource = entitySet; + this.TargetEdmType = computedReturnEdmType; + this.TargetKind = this.TargetEdmType.GetTargetKindFromType(); + this.SingleResult = computedReturnEdmType.TypeKind != EdmTypeKind.Collection; + } + else + { + this.TargetEdmNavigationSource = null; + this.TargetEdmType = null; + this.TargetKind = RequestTargetKind.VoidOperation; + } + } + + /// + /// Build a segment representing a call to an operation - action, function, or service operation. + /// + /// A single operation import that this segment will represent. + /// The list of parameters supplied to this segment. + /// The containing the entities that this function returns. + public OperationSegment(IEdmOperation operation, IEnumerable parameters, IEdmEntitySetBase entitySet) + : this(operation, entitySet) + { + this.parameters = new ReadOnlyCollection(parameters == null ? new List() : parameters.ToList()); + } + + /// + /// Build a segment representing a call to an operation - action, function, or service operation. + /// + /// The list of possible operation overloads for this segment. + /// The containing the entities that this function returns. + /// Throws if the input operations is null. + public OperationSegment(IEnumerable operations, IEdmEntitySetBase entitySet) + : this() + { + // DEVNOTE: This ctor is only used in Select and Expand currently. + ExceptionUtils.CheckArgumentNotNull(operations, "operations"); + this.operations = new ReadOnlyCollection(operations.ToList()); + + // check for empty after we copy locally, so that we don't do multiple enumeration of input + ExceptionUtils.CheckArgumentCollectionNotNullOrEmpty(this.operations, "operations"); + + // Determine the return type of the operation. This is only possible if all the candidate operations agree on the return type. + // TODO: Because we work on types and not type references, if there are nullability differences we'd ignore them... + IEdmType typeSoFar = this.operations.First().ReturnType != null + ? this.operations.First().ReturnType.Definition + : null; + if (typeSoFar == null) + { + // This is for void operations + if (this.operations.Any(operation => operation.ReturnType != null)) + { + typeSoFar = UnknownSentinel; + } + } + else if (this.operations.Any(operationImport => !typeSoFar.IsEquivalentTo(operationImport.ReturnType.Definition))) + { + typeSoFar = UnknownSentinel; + } + + this.computedReturnEdmType = typeSoFar; + this.entitySet = entitySet; + this.EnsureTypeAndSetAreCompatable(); + } + + /// + /// Creates a segment representing a call to an operation - action, function or service operation. + /// + /// The list of possible operation overloads for this segment. + /// The list of parameters supplied to this segment. + /// The containing the entities that this function returns. + public OperationSegment(IEnumerable operations, IEnumerable parameters, IEdmEntitySetBase entitySet) + : this(operations, entitySet) + { + this.parameters = new ReadOnlyCollection(parameters == null ? new List() : parameters.ToList()); + } + + /// + /// Creates a segment representing a call to an operation - action, function or service operation. + /// + private OperationSegment() + { + this.parameters = new ReadOnlyCollection(new List()); + } + + /// + /// Gets the list of possible operation import overloads for this segment. + /// + public IEnumerable Operations + { + get { return this.operations.AsEnumerable(); } + } + + /// + /// Gets the list of parameters for this segment. + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Gets the of this . + /// + /// + /// This value will be null for void service operations. + /// If there are multiple candidate operations with varying return types, then this property will throw. + /// + /// Throws if the type is unknown. + public override IEdmType EdmType + { + get + { + if (ReferenceEquals(this.computedReturnEdmType, UnknownSentinel)) + { + throw new ODataException(ODataErrorStrings.OperationSegment_ReturnTypeForMultipleOverloads); + } + + return this.computedReturnEdmType; + } + } + + /// + /// Gets the containing the entities that this function returns. + /// This will be null if entities are not returned by this operation, or if there is any ambiguity. + /// + public IEdmEntitySetBase EntitySet + { + get { return this.entitySet; } + } + + /// + /// Translate a . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a . + /// + /// An implementation of the handle interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + OperationSegment otherOperation = other as OperationSegment; + return otherOperation != null && + otherOperation.Operations.SequenceEqual(this.Operations) && + otherOperation.EntitySet == this.entitySet; + } + + /// + /// Ensures that the entity set and computed return type make sense. + /// + /// Throws if the return type computed from the function call is null, or if the return type is not in the same hierarchy as the entity set provided. + private void EnsureTypeAndSetAreCompatable() + { + // The return type should be in the type hierarchy of the set, We could be a little tighter but we don't want + // to overdo it here. + // If they didn't get us an entity set, or if we couldn't compute a single return type, then we don't need these checks + if (this.entitySet == null || this.computedReturnEdmType == UnknownSentinel) + { + return; + } + + // Void operations cannot specificy return entity set + if (this.computedReturnEdmType == null) + { + throw new ODataException(ODataErrorStrings.OperationSegment_CannotReturnNull); + } + + // Unwrap the return type if it's a collection + var unwrappedCollectionType = this.computedReturnEdmType; + var collectionType = this.computedReturnEdmType as IEdmCollectionType; + if (collectionType != null) + { + unwrappedCollectionType = collectionType.ElementType.Definition; + } + + // Ensure that the return type is in the same type hierarhcy as the entity set provided + if (!this.entitySet.EntityType().IsOrInheritsFrom(unwrappedCollectionType) && !unwrappedCollectionType.IsOrInheritsFrom(this.entitySet.EntityType())) + { + throw new ODataException(ODataErrorStrings.OperationSegment_CannotReturnNull); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegmentParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegmentParameter.cs new file mode 100644 index 0000000..950a524 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OperationSegmentParameter.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Represents a named parameter value for invoking an operation in an OData path. + /// + public sealed class OperationSegmentParameter + { + /// + /// Initializes a new instance of . + /// + /// The name of the parameter. Cannot be null or empty. + /// The value of the parameter. + public OperationSegmentParameter(string name, object value) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(name, "name"); + this.Name = name; + this.Value = value; + } + + /// + /// The name of the parameter. + /// + public string Name { get; private set; } + + /// + /// The parameter value. + /// + public object Value { get; private set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OrderByClause.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OrderByClause.cs new file mode 100644 index 0000000..dd3ba99 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/OrderByClause.cs @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Represents the result of parsing the $orderby query option. + /// + public sealed class OrderByClause + { + /// + /// The order-by expression. + /// + private readonly SingleValueNode expression; + + /// + /// The direction to order. + /// + private readonly OrderByDirection direction; + + /// + /// The rangeVariable for the expression which represents a single value from the collection we iterate over. + /// + private readonly RangeVariable rangeVariable; + + /// + /// The next orderby to perform after performing this orderby, can be null in the case of only a single orderby expression. + /// + private readonly OrderByClause thenBy; + + /// + /// Creates an . + /// + /// The next orderby to perform after performing this orderby, can be null in the case of only a single orderby expression. + /// The order-by expression. Cannot be null. + /// The direction to order. + /// The rangeVariable for the expression which represents a single value from the collection we iterate over. + /// Throws if the input expression or rangeVariable is null. + public OrderByClause(OrderByClause thenBy, SingleValueNode expression, OrderByDirection direction, RangeVariable rangeVariable) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + ExceptionUtils.CheckArgumentNotNull(rangeVariable, "parameter"); + + this.thenBy = thenBy; + this.expression = expression; + this.direction = direction; + this.rangeVariable = rangeVariable; + } + + /// + /// Gets the next orderby to perform after performing this orderby, can be null in the case of only a single orderby expression. + /// + public OrderByClause ThenBy + { + get + { + return this.thenBy; + } + } + + /// + /// Gets the order-by expression. + /// + public SingleValueNode Expression + { + get { return this.expression; } + } + + /// + /// Gets the direction to order. + /// + public OrderByDirection Direction + { + get { return this.direction; } + } + + /// + /// Gets the rangeVariable for the expression which represents a single value from the collection we iterate over. + /// + public RangeVariable RangeVariable + { + get { return this.rangeVariable; } + } + + /// + /// Gets the type of a single item from the collection returned after ordering. + /// + public IEdmTypeReference ItemType + { + get + { + return this.RangeVariable.TypeReference; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasNode.cs new file mode 100644 index 0000000..3daf9b2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasNode.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Represents a parameter alias that appears in uri path, $filter or $orderby. + /// + public class ParameterAliasNode : SingleValueNode + { + /// + /// The alias' type which is infered from the type of alias value's SingleValueNode. + /// + private readonly IEdmTypeReference typeReference; + + /// + /// Constructor. + /// + /// The parameter alias. + /// The alias' type which is infered from the type of alias value's SingleValueNode. + public ParameterAliasNode(string alias, IEdmTypeReference typeReference) + { + this.Alias = alias; + this.typeReference = typeReference; + } + + /// + /// The parameter alias. + /// + public string Alias { get; private set; } + + /// + /// The alias' type which is infered from the type of alias value's SingleValueNode + /// + public override IEdmTypeReference TypeReference + { + get { return this.typeReference; } + } + + /// + /// Is InternalQueryNodeKind.ParameterAlias. + /// + internal override InternalQueryNodeKind InternalKind + { + get { return InternalQueryNodeKind.ParameterAlias; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasValueAccessor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasValueAccessor.cs new file mode 100644 index 0000000..5172d3e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ParameterAliasValueAccessor.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + + /// + /// Provides syntactic information of the paramenter aliases in query uri, and caches the semantics information of the aliases' values. + /// + internal sealed class ParameterAliasValueAccessor + { + /// + /// Constructor + /// + /// The parameter alias value expressions from uri. + public ParameterAliasValueAccessor(IDictionary parameterAliasValueExpressions) + { + ExceptionUtils.CheckArgumentNotNull(parameterAliasValueExpressions, "parameterAliasValueExpressions"); + this.ParameterAliasValueExpressions = new Dictionary(parameterAliasValueExpressions, StringComparer.Ordinal); + this.ParameterAliasValueNodesCached = new Dictionary(StringComparer.Ordinal); + } + + /// + /// Gets the up-to-now cached semantics nodes of parameter alias value expressions (StringComparer.Ordinal) + /// Only referenced parameter alias will have their value nodes cached. + /// + public IDictionary ParameterAliasValueNodesCached { get; private set; } + + /// + /// Gets the parameter alias's value expressions like @p1=... (StringComparer.Ordinal) + /// + internal IDictionary ParameterAliasValueExpressions { get; private set; } + + /// + /// Gets a parameter alias's value expression (e.g. the string content of @p1=...). + /// + /// The parameter alias. + /// The alias value expression text. + public string GetAliasValueExpression(string alias) + { + string ret = null; + if (this.ParameterAliasValueExpressions.TryGetValue(alias, out ret)) + { + return ret; + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathSelectItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathSelectItem.cs new file mode 100644 index 0000000..6bcf5ab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathSelectItem.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Class to represent the selection of a specific path. + /// + public sealed class PathSelectItem : SelectItem + { + /// + /// The selected path. + /// + private readonly ODataSelectPath selectedPath; + + /// + /// Constructs a to indicate that a specific path is selected. + /// + /// The selected path. + /// Throws if the input selectedPath is null. + public PathSelectItem(ODataSelectPath selectedPath) + { + ExceptionUtils.CheckArgumentNotNull(selectedPath, "selectedPath"); + this.selectedPath = selectedPath; + } + + /// + /// Gets the selected path. + /// + public ODataSelectPath SelectedPath + { + get { return this.selectedPath; } + } + + /// + /// Translate using a . + /// + /// Type that the translator will return after visiting this item. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(SelectItemTranslator translator) + { + return translator.Translate(this); + } + + /// + /// Handle using a . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(SelectItemHandler handler) + { + handler.Handle(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathTemplateSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathTemplateSegment.cs new file mode 100644 index 0000000..93c0024 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PathTemplateSegment.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// A segment representing an path template in a path. + /// + public sealed class PathTemplateSegment : ODataPathSegment + { + /// + /// Build a segment representing a path template. + /// + /// The literal text for the template segment. + public PathTemplateSegment(string literalText) + { + this.LiteralText = literalText; + this.Identifier = literalText; + this.SingleResult = true; // This single result value is meaningless as we don't know what it should be. + this.TargetKind = RequestTargetKind.Nothing; + } + + /// + /// The literal text for the template segment. + /// + public string LiteralText { get; private set; } + + /// + /// Gets the of this . + /// + /// + /// Path template does not resolve to certain EdmType, so this value is always null. + /// + public override IEdmType EdmType + { + get { return null; } + } + + /// + /// Translate a using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PropertySegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PropertySegment.cs new file mode 100644 index 0000000..636e231 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/PropertySegment.cs @@ -0,0 +1,94 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing a structural property + /// + public sealed class PropertySegment : ODataPathSegment + { + /// + /// The structural property referred to by this segment + /// + private readonly IEdmStructuralProperty property; + + /// + /// Build a segment based on a structural property + /// + /// The structural property that this segment represents. + /// Throws if the input property is null. + public PropertySegment(IEdmStructuralProperty property) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + this.property = property; + + this.Identifier = property.Name; + this.TargetEdmType = property.Type.Definition; + this.SingleResult = !property.Type.IsCollection(); + } + + /// + /// Gets the structural property that this segment represents. + /// + public IEdmStructuralProperty Property + { + get { return this.property; } + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return this.Property.Type.Definition; } + } + + /// + /// Translate a using an instance of />. + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + PropertySegment otherProperty = other as PropertySegment; + return otherProperty != null && otherProperty.Property == this.Property; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/QueryNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/QueryNode.cs new file mode 100644 index 0000000..140d5ca --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/QueryNode.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Base class for all semantic metadata bound nodes. + /// + public abstract class QueryNode + { + /// + /// Gets the kind of this node. + /// + public abstract QueryNodeKind Kind { get; } + + /// + /// Gets the kind of this node. + /// + internal virtual InternalQueryNodeKind InternalKind + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public virtual T Accept(QueryNodeVisitor visitor) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariable.cs new file mode 100644 index 0000000..21d9884 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariable.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// A RangeVariable, which represents an iterator variable either over a collection, either of entities or not. + /// Exists outside of the main SemanticAST, but hooked in via a RangeVariableReferenceNode (either Non-Entity or Entity). + /// + public abstract class RangeVariable + { + /// + /// Gets the name of the associated rangeVariable. + /// + public abstract string Name { get; } + + /// + /// Gets the type of entity referenced by this rangeVariable + /// + public abstract IEdmTypeReference TypeReference { get; } + + /// + /// Gets the kind of this rangeVariable. + /// + public abstract int Kind { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariableKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariableKind.cs new file mode 100644 index 0000000..2d8f556 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/RangeVariableKind.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Enumeration of the different kinds of RangeVariables. + /// + public static class RangeVariableKind + { + /// + /// A range variable that refers to entity type or a complex. + /// + public const int Resource = 0; + + /// + /// A range variable that refers to non-entity/complex types. + /// + public const int NonResource = 1; + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ReferenceSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ReferenceSegment.cs new file mode 100644 index 0000000..baca450 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ReferenceSegment.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// A segment representing $ref in a path. + /// + public sealed class ReferenceSegment : ODataPathSegment + { + /// + /// Build a segment representing $ref. + /// + /// The navigation source to which this segment references. + /// Throws if any input parameter is null. + public ReferenceSegment(IEdmNavigationSource navigationSource) + { + ExceptionUtils.CheckArgumentNotNull(navigationSource, "navigationSource"); + + this.Identifier = UriQueryConstants.RefSegment; + this.SingleResult = navigationSource.Type.TypeKind != EdmTypeKind.Collection; + this.TargetEdmNavigationSource = navigationSource; + this.TargetKind = RequestTargetKind.Resource; + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return null; } + } + + /// + /// Translate a using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + ReferenceSegment otherSegment = other as ReferenceSegment; + + return otherSegment != null && otherSegment.TargetEdmNavigationSource == this.TargetEdmNavigationSource; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariable.cs new file mode 100644 index 0000000..8786b4a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariable.cs @@ -0,0 +1,122 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A RangeVariable inside an any or all expression that refers to an entity or a complex. + /// + public sealed class ResourceRangeVariable : RangeVariable + { + /// + /// The name of the associated any/all parameter (null if none) + /// + private readonly string name; + + /// + /// The resource collection that this rangeVariable node iterates over + /// + private readonly CollectionResourceNode collectionResourceNode; + + /// + /// The navigation source of the collection this node iterates over. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// The structured type of each item in the collection that this range variable iterates over. + /// + private readonly IEdmStructuredTypeReference structuredTypeReference; + + /// + /// Creates a . + /// + /// The name of the associated any/all parameter (null if none) + /// The structured type of each item in the collection that this range variable iterates over. + /// The resource collection that this rangeVariable node iterates over + /// Throws if the input name or entityType is null. + public ResourceRangeVariable(string name, IEdmStructuredTypeReference structuredType, CollectionResourceNode collectionResourceNode) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + ExceptionUtils.CheckArgumentNotNull(structuredType, "structuredType"); + this.name = name; + this.structuredTypeReference = structuredType; + this.collectionResourceNode = collectionResourceNode; + this.navigationSource = collectionResourceNode != null ? collectionResourceNode.NavigationSource : null; + } + + /// + /// Creates a . + /// + /// The name of the associated any/all parameter (null if none) + /// The structured type of each item in the collection that this range variable iterates over. + /// The navigation source of the collection this node iterates over. + /// Throws if the input name or entityType is null. + public ResourceRangeVariable(string name, IEdmStructuredTypeReference structuredType, IEdmNavigationSource navigationSource) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + ExceptionUtils.CheckArgumentNotNull(structuredType, "structuredType"); + this.name = name; + this.structuredTypeReference = structuredType; + this.collectionResourceNode = null; + this.navigationSource = navigationSource; + } + + /// + /// Gets the name of the associated any/all parameter (null if none) + /// + public override string Name + { + get { return this.name; } + } + + /// + /// Gets the resource collection that this rangeVariable node iterates over + /// + public CollectionResourceNode CollectionResourceNode + { + get { return this.collectionResourceNode; } + } + + /// + /// Gets the navigation source of the collection this node iterates over. + /// + public IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the entity type of each item in the collection that this range variable iterates over. + /// + public override IEdmTypeReference TypeReference + { + get { return this.structuredTypeReference; } + } + + /// + /// Gets the structured type of each item in the collection that this range variable iterates over. + /// + public IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.structuredTypeReference; } + } + + /// + /// Gets the kind of this node. + /// + public override int Kind + { + get { return RangeVariableKind.Resource; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariableReferenceNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariableReferenceNode.cs new file mode 100644 index 0000000..8fc343f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ResourceRangeVariableReferenceNode.cs @@ -0,0 +1,120 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Node to represent a range variable in an Any or All clause that refers to an entity or a complex. + /// + public sealed class ResourceRangeVariableReferenceNode : SingleResourceNode + { + /// + /// The name of the associated range variable (null if none) + /// + private readonly string name; + + /// + /// The structured type of the associated range variable. + /// + private readonly IEdmStructuredTypeReference structuredTypeReference; + + /// + /// The range variable that the node represents. + /// + private readonly ResourceRangeVariable rangeVariable; + + /// + /// The navigation source containing the collection that this range variable iterates over. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Creates an . + /// + /// The name of the associated range variable (null if none) + /// The actual range variable on the bind stack that this refers to + /// Throws if the input name or rangeVariable is null. + public ResourceRangeVariableReferenceNode(string name, ResourceRangeVariable rangeVariable) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + ExceptionUtils.CheckArgumentNotNull(rangeVariable, "rangeVariable"); + this.name = name; + this.navigationSource = rangeVariable.NavigationSource; + this.structuredTypeReference = rangeVariable.StructuredTypeReference; + this.rangeVariable = rangeVariable; + } + + /// + /// Gets the name of the associated range variable (null if none) + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the entity type of the associated range variable. + /// + public override IEdmTypeReference TypeReference + { + get { return this.structuredTypeReference; } + } + + /// + /// Gets a reference to the range variable that this node represents. + /// + public ResourceRangeVariable RangeVariable + { + get { return this.rangeVariable; } + } + + /// + /// Gets the navigation source containing the collection that this range variable iterates over. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the structured type of the associated range variable. + /// + public override IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.structuredTypeReference; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.ResourceRangeVariableReference; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchClause.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchClause.cs new file mode 100644 index 0000000..a88b56f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchClause.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// The result of parsing a $search query option. + /// + public sealed class SearchClause + { + /// + /// The filter expression this should evaluate to a single boolean value. + /// + private readonly SingleValueNode expression; + + /// + /// Creates a . + /// + /// The filter expression - this should evaluate to a single boolean value. Cannot be null. + /// Throws if the input expression or rangeVariable is null. + public SearchClause(SingleValueNode expression) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + + this.expression = expression; + } + + /// + /// Gets the filter expression - this should evaluate to a single boolean value. + /// + public SingleValueNode Expression + { + get { return this.expression; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchTermNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchTermNode.cs new file mode 100644 index 0000000..d990be6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SearchTermNode.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + #endregion Namespaces + + /// + /// Node representing a search term. + /// + public sealed class SearchTermNode : SingleValueNode + { + /// + /// Bool reference, as search term is used for matching + /// + private static readonly IEdmTypeReference BoolTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(typeof(bool)); + + /// + /// The search term value. + /// + private readonly string text; + + /// + /// Create a SearchTermNode + /// + /// The literal text for this node's value, formatted according to the OData URI literal formatting rules. + /// Throws if the input literalText is null. + public SearchTermNode(string text) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(text, "literalText"); + this.text = text; + } + + /// + /// Gets the search term value. + /// + public string Text + { + get + { + return this.text; + } + } + + /// + /// Gets the resouce type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get + { + return BoolTypeReference; + } + } + + /// + /// Gets the kind of the query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SearchTerm; + } + } + + /// + /// Accept a that walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClause.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClause.cs new file mode 100644 index 0000000..dc232a4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClause.cs @@ -0,0 +1,138 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + /// + /// Class representing the combined semantic meaning of any select or expand clauses in the uri. + /// + public sealed class SelectExpandClause + { + /// + /// The selected properties and operations. + /// + /// This list includes expanded navigations properties, which may have additional nested selections and expansions. + private ReadOnlyCollection selectedItems; + + /// + /// Gets a flag indicating that everything at this level has been selected. + /// + /// + /// If true, then all structural properties, bound actions and functions, and all navigations in the SelectedItems list have been selected. + /// + private bool? allSelected; + + /// + /// Constructs a from the given parameters. + /// + /// The selected properties and operations. This list should include any expanded navigation properties. + /// Flag indicating if all items have been selected at this level. + public SelectExpandClause(IEnumerable selectedItems, bool allSelected) + { + this.selectedItems = selectedItems != null ? new ReadOnlyCollection(selectedItems.ToList()) : new ReadOnlyCollection(new List()); + this.allSelected = allSelected; + } + + /// + /// Gets the selected properties and operations. + /// + /// This list includes expanded navigations properties, which may have additional nested selections and expansions. + public IEnumerable SelectedItems + { + get + { + ////Debug.Assert(!this.usedInternalLegacyConstructor || this.selectedItems != null, "You cannot get the list of selected items until processing is complete."); + return this.selectedItems.AsEnumerable(); + } + } + + /// + /// Gets a flag indicating that everything at this level has been selected. + /// + /// + /// If true, then all structural properties, bound actions and functions, and all navigations in the SelectedItems list have been selected. + /// + public bool AllSelected + { + get + { + return this.allSelected.Value; + } + } + + /// + /// Add a select item to the current list of selection items + /// + /// The item to add + internal void AddToSelectedItems(SelectItem itemToAdd) + { + ExceptionUtils.CheckArgumentNotNull(itemToAdd, "itemToAdd"); + + if (this.selectedItems.Any(x => x is WildcardSelectItem) && IsStructuralOrNavigationPropertySelectionItem(itemToAdd)) + { + return; + } + + bool isWildcard = itemToAdd is WildcardSelectItem; + + List newSelectedItems = new List(); + + foreach (SelectItem selectedItem in this.selectedItems) + { + if (isWildcard) + { + if (!IsStructuralSelectionItem(selectedItem)) + { + newSelectedItems.Add(selectedItem); + } + } + else + { + newSelectedItems.Add(selectedItem); + } + } + + newSelectedItems.Add(itemToAdd); + this.selectedItems = new ReadOnlyCollection(newSelectedItems); + } + + + /// + /// Sets all the value of AllSelected + /// + /// the new value to set + internal void SetAllSelected(bool newValue) + { + this.allSelected = newValue; + } + + /// + /// is this selection item a structural or navigation property selection item. + /// + /// the selection item to check + /// true if this selection item is a structural property selection item. + private static bool IsStructuralOrNavigationPropertySelectionItem(SelectItem selectItem) + { + PathSelectItem pathSelectItem = selectItem as PathSelectItem; + return pathSelectItem != null && (pathSelectItem.SelectedPath.LastSegment is NavigationPropertySegment || pathSelectItem.SelectedPath.LastSegment is PropertySegment); + } + + /// + /// is this selection item a structural selection item. + /// + /// the selection item to check + /// true if this selection item is a structural property selection item. + private static bool IsStructuralSelectionItem(SelectItem selectItem) + { + PathSelectItem pathSelectItem = selectItem as PathSelectItem; + return pathSelectItem != null && (pathSelectItem.SelectedPath.LastSegment is PropertySegment); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClauseExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClauseExtensions.cs new file mode 100644 index 0000000..8bc826e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectExpandClauseExtensions.cs @@ -0,0 +1,244 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + + /// + /// Extension methods for . + /// + internal static class SelectExpandClauseExtensions + { + /// + /// Get sub select and expand clause by property name, if the propertyname is in form of TypeCast/Property, the typeSegment would also be returned. + /// + /// The root clause. + /// The property name to navigate to. + /// The sub select and expand clause under sub property. + /// Type cast segment, if exists. + internal static void GetSubSelectExpandClause(this SelectExpandClause clause, string propertyName, out SelectExpandClause subSelectExpand, out TypeSegment typeSegment) + { + subSelectExpand = null; + typeSegment = null; + + ExpandedNavigationSelectItem selectedItem = clause + .SelectedItems + .OfType() + .FirstOrDefault( + m => m.PathToNavigationProperty.LastSegment != null + && m.PathToNavigationProperty.LastSegment.TranslateWith(PathSegmentToStringTranslator.Instance) == propertyName); + + if (selectedItem != null) + { + subSelectExpand = selectedItem.SelectAndExpand; + typeSegment = selectedItem.PathToNavigationProperty.FirstSegment as TypeSegment; + } + } + + /// + /// Gets the select and expand clauses as strings. + /// + /// The select expand clause to get the paths from. + /// OData version. + /// Returns the select clause. + /// Returns the expand clause. + internal static void GetSelectExpandPaths(this SelectExpandClause selectExpandClause, ODataVersion version, out string selectClause, out string expandClause) + { + Debug.Assert(selectExpandClause != null, "selectExpandCluase != null"); + + StringBuilder selectClauseBuilder, expandClauseBuilder; + selectExpandClause.GetSelectExpandPaths(version, out selectClauseBuilder, out expandClauseBuilder); + + selectClause = selectClauseBuilder.ToString(); + expandClause = expandClauseBuilder.ToString(); + } + + /// + /// Gets the select and expand clauses as . + /// + /// The select expand clause to get the paths from. + /// OData version. + /// Returns the select clause. + /// Returns the expand clause. + internal static void GetSelectExpandPaths(this SelectExpandClause selectExpandClause, ODataVersion version, out StringBuilder selectClause, out StringBuilder expandClause) + { + Debug.Assert(selectExpandClause != null, "selectExpandClause != null"); + + selectClause = new StringBuilder(); + expandClause = new StringBuilder(); + selectClause.Append(BuildTopLevelSelect(selectExpandClause)); + expandClause.Append(BuildExpandsForNode(selectExpandClause)); + } + + /// + /// Gets a list of strings representing current selected property name in current level. + /// + /// The select expand clause used. + /// String list generated from selected items + internal static List GetCurrentLevelSelectList(this SelectExpandClause selectExpandClause) + { + return selectExpandClause.SelectedItems.Select(GetSelectString).Where(i => i != null).ToList(); + } + + /// + /// Traverse a SelectExpandClause using given functions. + /// + /// Type of the sub processing result for expand items. + /// The select expand clause for evaluation. + /// The method to deal with sub expand result. + /// The method to combine select and expand result lists. + /// The result of the traversing. + internal static void Traverse(this SelectExpandClause selectExpandClause, Func processSubResult, Func, IList, T> combineSelectAndExpand, out T result) + { + List selectList = selectExpandClause.GetCurrentLevelSelectList(); + List expandList = new List(); + + foreach (ExpandedNavigationSelectItem expandSelectItem in selectExpandClause.SelectedItems.Where(I => I.GetType() == typeof(ExpandedNavigationSelectItem))) + { + string currentExpandClause = String.Join("/", expandSelectItem.PathToNavigationProperty.WalkWith(PathSegmentToStringTranslator.Instance).ToArray()); + T subResult = default(T); + if (expandSelectItem.SelectAndExpand.SelectedItems.Any()) + { + Traverse(expandSelectItem.SelectAndExpand, processSubResult, combineSelectAndExpand, + out subResult); + } + + var expandItem = processSubResult(currentExpandClause, subResult); + + if (expandItem != null) + { + expandList.Add(expandItem); + } + } + + foreach (ExpandedReferenceSelectItem expandSelectItem in selectExpandClause.SelectedItems.Where(I => I.GetType() == typeof(ExpandedReferenceSelectItem))) + { + string currentExpandClause = String.Join("/", expandSelectItem.PathToNavigationProperty.WalkWith(PathSegmentToStringTranslator.Instance).ToArray()); + currentExpandClause += "/$ref"; + + var expandItem = processSubResult(currentExpandClause, default(T)); + if (expandItem != null) + { + expandList.Add(expandItem); + } + } + + result = combineSelectAndExpand(selectList, expandList); + } + + /// + /// Build the top level select clause as a string. + /// + /// top level select expand clause. + /// the string representation of the top level select clause. + private static string BuildTopLevelSelect(SelectExpandClause selectExpandClause) + { + // Special case to build the top level select clause (this it the only time that we actualy + // modify the selectClause because in V4 the the top level select clause can only modify the top + // level). + return String.Join(",", selectExpandClause.GetCurrentLevelSelectList().ToArray()); + } + + /// + /// Get the string representation of a select item (that isn't an expandedNavPropSelectItem + /// + /// the select item to translate + /// the string representation of this select item, or null if the select item is an expandedNavPropSelectItem + private static string GetSelectString(SelectItem selectedItem) + { + WildcardSelectItem wildcardSelect = selectedItem as WildcardSelectItem; + NamespaceQualifiedWildcardSelectItem namespaceQualifiedWildcard = selectedItem as NamespaceQualifiedWildcardSelectItem; + PathSelectItem pathSelectItem = selectedItem as PathSelectItem; + + if (wildcardSelect != null) + { + return "*"; + } + else if (namespaceQualifiedWildcard != null) + { + return namespaceQualifiedWildcard.Namespace + ".*"; + } + else if (pathSelectItem != null) + { + return String.Join("/", pathSelectItem.SelectedPath.WalkWith(PathSegmentToStringTranslator.Instance).ToArray()); + } + else + { + return null; + } + } + + /// + /// Build the expand clause for a given level in the selectExpandClause + /// + /// the current level select expand clause + /// the expand clause for this level. + private static string BuildExpandsForNode(SelectExpandClause selectExpandClause) + { + List currentLevelExpandClauses = new List(); + foreach (ExpandedNavigationSelectItem expandItem in selectExpandClause.SelectedItems.Where(I => I.GetType() == typeof(ExpandedNavigationSelectItem))) + { + string currentExpandClause = String.Join("/", expandItem.PathToNavigationProperty.WalkWith(PathSegmentToStringTranslator.Instance).ToArray()); + string expandStr; + expandItem.SelectAndExpand.Traverse(ProcessSubExpand, CombineSelectAndExpandResult, out expandStr); + if (!string.IsNullOrEmpty(expandStr)) + { + currentExpandClause += "(" + expandStr + ")"; + } + + currentLevelExpandClauses.Add(currentExpandClause); + } + + foreach (ExpandedReferenceSelectItem expandItem in selectExpandClause.SelectedItems.Where(I => I.GetType() == typeof(ExpandedReferenceSelectItem))) + { + string currentExpandClause = String.Join("/", expandItem.PathToNavigationProperty.WalkWith(PathSegmentToStringTranslator.Instance).ToArray()); + currentExpandClause += "/$ref"; + currentLevelExpandClauses.Add(currentExpandClause); + } + + return String.Join(",", currentLevelExpandClauses.ToArray()); + } + + /// Process sub expand node, contact with subexpad result + /// The current expanded node. + /// Generated sub expand node. + /// The generated expand string. + private static string ProcessSubExpand(string expandNode, string subExpand) + { + return string.IsNullOrEmpty(subExpand) ? expandNode : expandNode + "(" + subExpand + ")"; + } + + /// Create combined result string using selected items list and expand items list. + /// A list of selected item names. + /// A list of sub expanded item names. + /// The generated expand string. + private static string CombineSelectAndExpandResult(IList selectList, IList expandList) + { + string currentExpandClause = ""; + if (selectList.Any()) + { + currentExpandClause += "$select=" + String.Join(",", selectList.ToArray()); + } + + if (expandList.Any()) + { + if (!string.IsNullOrEmpty(currentExpandClause)) + { + currentExpandClause += ";"; + } + + currentExpandClause += "$expand=" + String.Join(",", expandList.ToArray()); + } + + return currentExpandClause; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectItem.cs new file mode 100644 index 0000000..2400639 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SelectItem.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// An item that has been selected by the query at the current level of the tree. + /// + public abstract class SelectItem + { + /// + /// Translate a using an implemntation of. + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + public abstract T TranslateWith(SelectItemTranslator translator); + + /// + /// Handle a using an implementation of a . + /// + /// An implementation of the handler interface. + public abstract void HandleWith(SelectItemHandler handler); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleComplexNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleComplexNode.cs new file mode 100644 index 0000000..69cf8b7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleComplexNode.cs @@ -0,0 +1,134 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a complex. + /// + public class SingleComplexNode : SingleResourceNode + { + /// + /// The resource node containing this property. + /// + private readonly SingleResourceNode source; + + /// + /// The EDM property which is to be accessed. + /// + private readonly IEdmProperty property; + + /// + /// The target type that the property represents. + /// + private readonly IEdmComplexTypeReference typeReference; + + /// + /// The navigation source containing the source entity. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Constructs a . + /// + /// The value containing this property. + /// The EDM property which is to be accessed. + /// Throws if input source or property is null. + /// Throws if input property is not structural, or is a collection. + public SingleComplexNode(SingleResourceNode source, IEdmProperty property) + : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, property) + { + this.source = source; + } + + /// + /// Constructs a . + /// + /// The navigation source containing the property. + /// The EDM property which is to be accessed. + private SingleComplexNode(IEdmNavigationSource navigationSource, IEdmProperty property) + { + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + if (property.PropertyKind != EdmPropertyKind.Structural) + { + // TODO: update error message #644 + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessShouldBeNonEntityProperty(property.Name)); + } + + this.property = property; + this.navigationSource = navigationSource; + this.typeReference = property.Type.AsComplex(); + } + + /// + /// Gets the resource node containing this property. + /// + public SingleResourceNode Source + { + get { return this.source; } + } + + /// + /// Gets the EDM property which is to be accessed. + /// + public IEdmProperty Property + { + get { return this.property; } + } + + /// + /// The target type that the property represents. + /// + public override IEdmTypeReference TypeReference + { + get { return this.typeReference; } + } + + /// + /// Gets the navigation source containing the complex. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// The target type that the property represents. + /// + public override IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.typeReference; } + } + + /// + /// Gets the kind of this query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SingleComplexNode; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleEntityNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleEntityNode.cs new file mode 100644 index 0000000..6a50eeb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleEntityNode.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Base class for all semantic metadata bound nodes which represent a single composable entity value. + /// + public abstract class SingleEntityNode : SingleResourceNode + { + /// + /// Gets the type of this single entity. + /// + public abstract IEdmEntityTypeReference EntityTypeReference { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleNavigationNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleNavigationNode.cs new file mode 100644 index 0000000..d5430da --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleNavigationNode.cs @@ -0,0 +1,210 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a single navigation property. + /// + public sealed class SingleNavigationNode : SingleEntityNode + { + /// + /// The navigation source that this NavigationProperty targets. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// The previous node in the path. + /// + private readonly SingleResourceNode source; + + /// + /// The navigation property this node represents. + /// + private readonly IEdmNavigationProperty navigationProperty; + + /// + /// The type of entity that this NavigationProperty targets. + /// + private readonly IEdmEntityTypeReference entityTypeReference; + + /// + /// The parsed segments in the path. + /// + private readonly List parsedSegments; + + /// + /// The binding path of current navigation property. + /// + private readonly IEdmPathExpression bindingPath; + + /// + /// Constructs a SingleNavigationNode. + /// + /// The previous node in the path. + /// The navigation property this node represents. + /// The binding path of navigation property + /// Throws if the input navigationProperty or source is null. + /// Throws if the input navigationProperty targets more than one entity. + public SingleNavigationNode(SingleResourceNode source, IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, navigationProperty, bindingPath) + { + this.source = source; + } + + /// + /// Constructs a SingleNavigationNode. + /// + /// The navigation source that this of the previous segment. + /// The navigation property this node represents. + /// The binding path of navigation property + /// Throws if the input navigationProperty. + /// Throws if the input navigationProperty targets more than one entity. + internal SingleNavigationNode(IEdmNavigationSource navigationSource, IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + ExceptionUtils.CheckArgumentNotNull(navigationProperty, "navigationProperty"); + + EdmMultiplicity multiplicity = navigationProperty.TargetMultiplicity(); + if (multiplicity != EdmMultiplicity.One && multiplicity != EdmMultiplicity.ZeroOrOne) + { + throw new ArgumentException(ODataErrorStrings.Nodes_CollectionNavigationNode_MustHaveSingleMultiplicity); + } + + this.navigationProperty = navigationProperty; + this.entityTypeReference = (IEdmEntityTypeReference)this.NavigationProperty.Type; + this.bindingPath = bindingPath; + this.navigationSource = navigationSource != null ? navigationSource.FindNavigationTarget(navigationProperty, bindingPath) : null; + } + + /// + /// Constructs a SingleNavigationNode. + /// + /// he previous node in the path. + /// The navigation property this node represents. + /// The path segments parsed in path and query option. + internal SingleNavigationNode(SingleResourceNode source, IEdmNavigationProperty navigationProperty, List segments) + : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, navigationProperty, segments) + { + this.source = source; + } + + /// + /// Constructs a SingleNavigationNode. + /// + /// The navigation source that this of the previous segment. + /// The navigation property this node represents. + /// The path segments parsed in path and query option. + private SingleNavigationNode(IEdmNavigationSource navigationSource, + IEdmNavigationProperty navigationProperty, List segments) + { + ExceptionUtils.CheckArgumentNotNull(navigationProperty, "navigationProperty"); + + EdmMultiplicity multiplicity = navigationProperty.TargetMultiplicity(); + if (multiplicity != EdmMultiplicity.One && multiplicity != EdmMultiplicity.ZeroOrOne) + { + throw new ArgumentException(ODataErrorStrings.Nodes_CollectionNavigationNode_MustHaveSingleMultiplicity); + } + + this.navigationProperty = navigationProperty; + this.entityTypeReference = (IEdmEntityTypeReference)this.NavigationProperty.Type; + this.parsedSegments = segments; + this.navigationSource = navigationSource != null ? navigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, this.parsedSegments, out this.bindingPath) : null; + } + + /// + /// Gets the previous node in the path. + /// + public SingleResourceNode Source + { + get { return this.source; } + } + + /// + /// Gets the navigation property this node represents. + /// + public IEdmNavigationProperty NavigationProperty + { + get { return this.navigationProperty; } + } + + /// + /// Gets the target multiplicity. + /// + public EdmMultiplicity TargetMultiplicity + { + get { return this.NavigationProperty.TargetMultiplicity(); } + } + + /// + /// Gets the type of entity that this NavigationProperty targets. + /// + public override IEdmTypeReference TypeReference + { + get { return this.entityTypeReference; } + } + + /// + /// Gets the type of entity that this NavigationProperty targets. + /// + public override IEdmEntityTypeReference EntityTypeReference + { + get { return this.entityTypeReference; } + } + + /// + /// Gets the navigation source that this NavigationProperty targets. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the structured type of entity that this NavigationProperty targets. + /// + public override IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.entityTypeReference; } + } + + /// + /// The binding path of current navigation property. + /// + public IEdmPathExpression BindingPath + { + get { return bindingPath; } + } + + /// + /// Gets the kind of this query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SingleNavigationNode; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceCastNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceCastNode.cs new file mode 100644 index 0000000..a64e6b4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceCastNode.cs @@ -0,0 +1,100 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a type segment that casts a single entity/complex parent node. + /// + public sealed class SingleResourceCastNode : SingleResourceNode + { + /// + /// The resource that we're casting to a different type. + /// + private readonly SingleResourceNode source; + + /// + /// The target type that the source is cast to. + /// + private readonly IEdmStructuredTypeReference structuredTypeReference; + + /// + /// The navigation source containing the source entity. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Created a SingleResourceCastNode with the given source node and the given type to cast to. + /// + /// Source that is being cast. + /// Type to cast to. + /// Throws if the input entityType is null. + public SingleResourceCastNode(SingleResourceNode source, IEdmStructuredType structuredType) + { + ExceptionUtils.CheckArgumentNotNull(structuredType, "structuredType"); + this.source = source; + this.navigationSource = source != null ? source.NavigationSource : null; + this.structuredTypeReference = structuredType.GetTypeReference(); + } + + /// + /// Gets the resource that we're casting to a different type. + /// + public SingleResourceNode Source + { + get { return this.source; } + } + + /// + /// Gets the target type that the source is cast to. + /// + public override IEdmTypeReference TypeReference + { + get { return this.structuredTypeReference; } + } + + /// + /// Gets the navigation source containing the source entity.. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the target type that the source is cast to. + /// + public override IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.structuredTypeReference; } + } + + /// + /// Gets the kind of this query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SingleResourceCast; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceFunctionCallNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceFunctionCallNode.cs new file mode 100644 index 0000000..37da912 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceFunctionCallNode.cs @@ -0,0 +1,169 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Node representing a function call which returns a single entity or complex. + /// + public sealed class SingleResourceFunctionCallNode : SingleResourceNode + { + /// + /// the name of this function + /// + private readonly string name; + + /// + /// the list of functions represented by this node. + /// + private readonly ReadOnlyCollection functions; + + /// + /// List of arguments provided to the function. + /// + private readonly IEnumerable parameters; + + /// + /// The return type of this function. + /// + private readonly IEdmStructuredTypeReference returnedStructuredTypeReference; + + /// + /// The entity set or singleton containing the single entity that this function returns. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// The semantically bound parent of this function. + /// + private readonly QueryNode source; + + /// + /// Create a SingleResourceFunctionCallNode + /// + /// The name of the function to call + /// List of arguments provided to the operation import. Can be null. + /// The return type of this function. + /// The entity set or singleton containing the single entity that this operation import returns. + /// Throws if the input name, returnedEntityTypeReference, or navigationSource is null. + public SingleResourceFunctionCallNode(string name, IEnumerable parameters, IEdmStructuredTypeReference returnedStructuredTypeReference, IEdmNavigationSource navigationSource) + : this(name, null, parameters, returnedStructuredTypeReference, navigationSource, null) + { + } + + /// + /// Create a SingleResourceFunctionCallNode + /// + /// The name of the operation import to call + /// the list of functions this node represents. + /// List of arguments provided to the function. Can be null. + /// The return type of this operation import. + /// The entity set or singleton containing the single entity that this operation import returns. + /// The semantically bound parent of this operation import. + /// Throws if the input name, returnedEntityTypeReference, or navigationSource is null. + public SingleResourceFunctionCallNode(string name, IEnumerable functions, IEnumerable parameters, IEdmStructuredTypeReference returnedStructuredTypeReference, IEdmNavigationSource navigationSource, QueryNode source) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + ExceptionUtils.CheckArgumentNotNull(returnedStructuredTypeReference, "returnedStructuredTypeReference"); + + this.name = name; + this.functions = new ReadOnlyCollection(functions != null ? functions.ToList() : new List()); + this.parameters = new ReadOnlyCollection(parameters == null ? new List() : parameters.ToList()); + this.returnedStructuredTypeReference = returnedStructuredTypeReference; + this.navigationSource = navigationSource; + this.source = source; + } + + /// + /// Gets the name of the function to call + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the list of functions that this node represents + /// + public IEnumerable Functions + { + get { return this.functions; } + } + + /// + /// Gets the list of arguments provided to the function. + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Gets the return type of this function. + /// + public override IEdmTypeReference TypeReference + { + get { return this.returnedStructuredTypeReference; } + } + + /// + /// Gets the navigation source containing the single entity that this function returns. + /// + public override IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Gets the return type of this function. + /// + public override IEdmStructuredTypeReference StructuredTypeReference + { + get { return this.returnedStructuredTypeReference; } + } + + /// + /// Gets the semantically bound parent of this function. + /// + public QueryNode Source + { + get { return this.source; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SingleResourceFunctionCall; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceNode.cs new file mode 100644 index 0000000..9a4f56f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleResourceNode.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a resource, including entity and complex. + /// + public abstract class SingleResourceNode : SingleValueNode + { + /// + /// Gets the navigation source containing this single entity/complex. + /// + public abstract IEdmNavigationSource NavigationSource { get; } + + /// + /// Gets the type of this single entity/complex. + /// + public abstract IEdmStructuredTypeReference StructuredTypeReference { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueCastNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueCastNode.cs new file mode 100644 index 0000000..7b2ed78 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueCastNode.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Node representing a type segment that casts a single primitive value node. + /// + public sealed class SingleValueCastNode : SingleValueNode + { + /// + /// The resource that we're casting to a different type. + /// + private readonly SingleValueNode source; + + /// + /// The target type that the source is cast to. + /// + private readonly IEdmPrimitiveTypeReference primitiveTypeReference; + + /// + /// Created a SingleValueCastNode with the given source node and the given type to cast to. + /// + /// Source that is being cast. + /// Type to cast to. + /// Throws if the input primitiveType is null. + public SingleValueCastNode(SingleValueNode source, IEdmPrimitiveType primitiveType) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentNotNull(primitiveType, "primitiveType"); + this.source = source; + this.primitiveTypeReference = new EdmPrimitiveTypeReference(primitiveType, true); + } + + /// + /// Gets the property that we're casting to a different type. + /// + public SingleValueNode Source + { + get { return this.source; } + } + + /// + /// Gets the target type that the source is cast to. + /// + public override IEdmTypeReference TypeReference + { + get { return this.primitiveTypeReference; } + } + + /// + /// Gets the kind of this query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get { return InternalQueryNodeKind.SingleValueCast; } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueFunctionCallNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueFunctionCallNode.cs new file mode 100644 index 0000000..ba08ddf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueFunctionCallNode.cs @@ -0,0 +1,159 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System.Collections.Generic; + using System.Linq; + using System; + using System.Collections.ObjectModel; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Node representing a function call which returns a single value. + /// + public sealed class SingleValueFunctionCallNode : SingleValueNode + { + /// + /// the name of this function. + /// + private readonly string name; + + /// + /// The list of functions + /// + private readonly ReadOnlyCollection functions; + + /// + /// List of arguments to this function call. + /// + private readonly IEnumerable parameters; + + /// + /// The type of value returned by this function. + /// + private readonly IEdmTypeReference returnedTypeReference; + + /// + /// The semantically bound parent of this function + /// + private readonly QueryNode source; + + /// + /// Create a SingleValueFunctionCallNode + /// + /// The name of the function to call + /// List of arguments to this function call. + /// The type of value returned by this function. + /// Throws if the input name is null. + public SingleValueFunctionCallNode(string name, IEnumerable parameters, IEdmTypeReference returnedTypeReference) + : this(name, null, parameters, returnedTypeReference, null) + { + } + + /// + /// Create a SingleValueFunctionCallNode + /// + /// The name of the function to call + /// the list of functions that this node should represent. + /// the list of arguments to this function + /// the type of the value returned by this function. + /// The semantically bound parent of this function. + /// Throws if the input operationImports is null. + public SingleValueFunctionCallNode(string name, IEnumerable functions, IEnumerable parameters, IEdmTypeReference returnedTypeReference, QueryNode source) + { + ExceptionUtils.CheckArgumentNotNull(name, "name"); + + this.name = name; + this.functions = new ReadOnlyCollection(functions != null ? functions.ToList() : new List()); + + this.parameters = parameters ?? Enumerable.Empty(); + + if (returnedTypeReference != null) + { + if (returnedTypeReference.IsCollection() + || !(returnedTypeReference.IsComplex() || returnedTypeReference.IsPrimitive() || returnedTypeReference.IsEnum())) + { + throw new ArgumentException(ODataErrorStrings.Nodes_SingleValueFunctionCallNode_ItemTypeMustBePrimitiveOrComplexOrEnum); + } + } + + this.returnedTypeReference = returnedTypeReference; + this.source = source; + } + + /// + /// Gets the name of the function to call. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the list of operation imports. + /// + public IEnumerable Functions + { + get + { + return this.functions; + } + } + + /// + /// Gets the list of arguments to this function call. + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Gets The type of value returned by this function. + /// + public override IEdmTypeReference TypeReference + { + get { return this.returnedTypeReference; } + } + + /// + /// Gets the semantically bound parent of this function. + /// + public QueryNode Source + { + get { return this.source; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SingleValueFunctionCall; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueNode.cs new file mode 100644 index 0000000..92bae5b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueNode.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Base class for all semantic metadata bound nodes which represent a single composable value. + /// + public abstract class SingleValueNode : QueryNode + { + /// + /// Gets the type of the single value this node represents. + /// + public abstract IEdmTypeReference TypeReference + { + get; + } + + /// + /// Gets the kind of this node. + /// + public override QueryNodeKind Kind + { + get { return (QueryNodeKind)this.InternalKind; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueOpenPropertyAccessNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueOpenPropertyAccessNode.cs new file mode 100644 index 0000000..bf219fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValueOpenPropertyAccessNode.cs @@ -0,0 +1,92 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// Semantic node that represents a single-value open property access, which is not bound to an EDM model. + /// + public sealed class SingleValueOpenPropertyAccessNode : SingleValueNode + { + /// + /// The value containing this property. + /// + private readonly SingleValueNode source; + + /// + /// The name of the open property to be bound outside the EDM model. + /// + private readonly string name; + + /// + /// Constructs a . + /// + /// The value containing this property. + /// The name of the open property to be bound outside the EDM model. + /// Throws if the input source or openPropertyName is null. + public SingleValueOpenPropertyAccessNode(SingleValueNode source, string openPropertyName) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(openPropertyName, "openPropertyName"); + + this.name = openPropertyName; + this.source = source; + } + + /// + /// Gets the value containing this property. + /// + public SingleValueNode Source + { + get { return this.source; } + } + + /// + /// Gets the name of the open property to be bound outside the EDM model. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the type of the single value this node represents. + /// + /// + /// The value of this property will always be null for open properties. + /// + public override IEdmTypeReference TypeReference + { + get { return null; } + } + + /// + /// Gets the kind of this query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SingleValueOpenPropertyAccess; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValuePropertyAccessNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValuePropertyAccessNode.cs new file mode 100644 index 0000000..6ff095a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingleValuePropertyAccessNode.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using Microsoft.OData.Edm; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Node representing an access to a property value. + /// + public sealed class SingleValuePropertyAccessNode : SingleValueNode + { + /// + /// The value containing this property. + /// + private readonly SingleValueNode source; + + /// + /// The EDM property which is to be accessed. + /// + /// Only non-entity, non-collection properties are supported by this node. + private readonly IEdmProperty property; + + /// + /// Constructs a . + /// + /// The value containing this property. + /// The EDM property which is to be accessed. + /// Throws if input source or property is null. + /// Throws if input property is not structural, or is a collection. + public SingleValuePropertyAccessNode(SingleValueNode source, IEdmProperty property) + { + ExceptionUtils.CheckArgumentNotNull(source, "source"); + ExceptionUtils.CheckArgumentNotNull(property, "property"); + + if (property.PropertyKind != EdmPropertyKind.Structural) + { + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessShouldBeNonEntityProperty(property.Name)); + } + + if (property.Type.IsCollection()) + { + throw new ArgumentException(ODataErrorStrings.Nodes_PropertyAccessTypeShouldNotBeCollection(property.Name)); + } + + this.source = source; + this.property = property; + } + + /// + /// Gets the value containing this property. + /// + public SingleValueNode Source + { + get { return this.source; } + } + + /// + /// Gets the EDM property which is to be accessed. + /// + /// Only non-entity, non-collection properties are supported by this node. + public IEdmProperty Property + { + get { return this.property; } + } + + /// + /// Gets the type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get { return this.Property.Type; } + } + + /// + /// Gets the kind of this node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.SingleValuePropertyAccess; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingletonSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingletonSegment.cs new file mode 100644 index 0000000..c6b71de --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/SingletonSegment.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing an singleton in a path. + /// + public sealed class SingletonSegment : ODataPathSegment + { + /// + /// The singleton represented by this segment. + /// + private readonly IEdmSingleton singleton; + + /// + /// Build a segment representing an singleton + /// + /// The singleton represented by this segment. + /// Throws if the input singleton is null. + public SingletonSegment(IEdmSingleton singleton) + { + ExceptionUtils.CheckArgumentNotNull(singleton, "singleton"); + + this.singleton = singleton; + + this.TargetEdmNavigationSource = singleton; + this.TargetEdmType = singleton.EntityType(); + this.TargetKind = RequestTargetKind.Resource; + this.SingleResult = true; + } + + /// + /// Gets the singleton represented by this segment. + /// + public IEdmSingleton Singleton + { + get { return this.singleton; } + } + + /// + /// Gets the of this . + /// This will always be an of this singleton. + /// + public override IEdmType EdmType + { + get { return this.singleton.EntityType(); } + } + + /// + /// Translate an into another type using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle an using the an instance of the . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + SingletonSegment otherSingleton = other as SingletonSegment; + return otherSingleton != null && otherSingleton.singleton == this.Singleton; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/TypeSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/TypeSegment.cs new file mode 100644 index 0000000..9a44d1c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/TypeSegment.cs @@ -0,0 +1,122 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// A segment representing a cast on the previous segment to another type. + /// + public sealed class TypeSegment : ODataPathSegment + { + /// + /// The actual edm type of this type segment. + /// + private readonly IEdmType edmType; + + /// + /// The navigation source containing the entities that we are casting. + /// + private readonly IEdmNavigationSource navigationSource; + + /// + /// Build a type segment using the given . + /// + /// The target type of this segment, which may be collection type. + /// The navigation source containing the entities that we are casting. This can be null. + /// Throws if the actual edmType is null. + /// Throws if the actual edmType is not related to the type of elements in the input navigationSource. + public TypeSegment(IEdmType actualType, IEdmNavigationSource navigationSource) + : this(actualType, navigationSource == null ? actualType : navigationSource.EntityType(), navigationSource) + { + } + + /// + /// Build the type segment based on the giving and + /// + /// The actual type of this segment passed from Uri, which may be collection type. + /// The type reflected from model. + /// The navigation source containing the entity or complex that we are casting. This can be null. + /// Throws if the actual or expected edmType is null. + /// Throws if the actual edmType is not related to the expected type of elements in the input navigationSource. + public TypeSegment(IEdmType actualType, IEdmType expectedType, IEdmNavigationSource navigationSource) + { + ExceptionUtils.CheckArgumentNotNull(actualType, "actualType"); + ExceptionUtils.CheckArgumentNotNull(expectedType, "expectedType"); + + this.edmType = actualType; + this.navigationSource = navigationSource; + + this.TargetEdmType = expectedType; + this.TargetEdmNavigationSource = navigationSource; + + // Check that the type they gave us is related to the type of the set + if (navigationSource != null) + { + ExceptionUtil.ThrowIfTypesUnrelated(actualType, expectedType, "TypeSegments"); + } + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return this.edmType; } + } + + /// + /// Gets the navigation source containing the entities that we are casting. + /// + public IEdmNavigationSource NavigationSource + { + get { return this.navigationSource; } + } + + /// + /// Translate a into another type using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + /// Throws if the input other is null. + internal override bool Equals(ODataPathSegment other) + { + ExceptionUtils.CheckArgumentNotNull(other, "other"); + TypeSegment otherType = other as TypeSegment; + return otherType != null && otherType.EdmType == this.EdmType; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UnaryOperatorNode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UnaryOperatorNode.cs new file mode 100644 index 0000000..890625d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UnaryOperatorNode.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Node representing a unary operator. + /// + public sealed class UnaryOperatorNode : SingleValueNode + { + /// + /// The operand of the unary operator. + /// + private readonly SingleValueNode operand; + + /// + /// The operator represented by this node. + /// + private readonly UnaryOperatorKind operatorKind; + + /// + /// Cache for the TypeReference after it has been calculated for the current state of the node. + /// This can be an expensive calculation so we want to avoid doing it repeatedly. + /// + private IEdmTypeReference typeReference; + + /// + /// Creates a UnaryOperatorNode + /// + /// the kind of operator this node represents + /// the operand that this operator modifies + /// Throws if the input operand is null. + public UnaryOperatorNode(UnaryOperatorKind operatorKind, SingleValueNode operand) + { + ExceptionUtils.CheckArgumentNotNull(operand, "operand"); + this.operand = operand; + this.operatorKind = operatorKind; + + if (operand == null || operand.TypeReference == null) + { + this.typeReference = null; + } + else + { + this.typeReference = operand.TypeReference; + } + } + + /// + /// Gets the operator represented by this node. + /// + public UnaryOperatorKind OperatorKind + { + get { return this.operatorKind; } + } + + /// + /// Gets the operand of the unary operator. + /// + public SingleValueNode Operand + { + get { return this.operand; } + } + + /// + /// Gets the type of the single value this node represents. + /// + public override IEdmTypeReference TypeReference + { + get + { + return this.typeReference; + } + } + + /// + /// Gets the kind of this query node. + /// + internal override InternalQueryNodeKind InternalKind + { + get + { + return InternalQueryNodeKind.UnaryOperator; + } + } + + /// + /// Accept a that walks a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + /// Throws if the input visitor is null. + public override T Accept(QueryNodeVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UriTemplateExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UriTemplateExpression.cs new file mode 100644 index 0000000..c959370 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/UriTemplateExpression.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using Microsoft.OData.Edm; + + /// + /// The class representing the URI Template parsing result. + /// + /// + /// A URI Template is a string wrapped in brackets, it is capable for describing a range of Uniform Resource Identifiers. + /// The URI Template can be used for both building and parsing URI. + /// Set the EnableUriTemplateParsing property of to true, to enable parsing the Uri template. + /// + /// For example, in the following URI + /// http://example.org/service/Customers({CID}) + /// {CID} is a validate Uri template for Customers' ID segment, in the parsing result of , + /// would be the original literal "{CID}", and would be the actual type for Customers' ID. + /// + /// See RFC6570 for detail. + /// + public sealed class UriTemplateExpression + { + /// + /// The original text for the Uri template. + /// + public string LiteralText { get; internal set; } + + /// + /// The expected type of the object which the Uri template stands for. + /// + public IEdmTypeReference ExpectedType { get; internal set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ValueSegment.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ValueSegment.cs new file mode 100644 index 0000000..4d40975 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/ValueSegment.cs @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// A segment representing $value + /// + public sealed class ValueSegment : ODataPathSegment + { + /// + /// The of this . + /// + private readonly IEdmType edmType; + + /// + /// Build a segment to represnt $value. + /// + /// The type of the segment before $value. This may be null, for cases such as open properties. + /// Throws if the input previousType is a colleciton type. + public ValueSegment(IEdmType previousType) + { + this.Identifier = UriQueryConstants.ValueSegment; + this.SingleResult = true; + + if (previousType is IEdmCollectionType) + { + throw new ODataException(ODataErrorStrings.PathParser_CannotUseValueOnCollection); + } + + if (previousType is IEdmEntityType) + { + // TODO: Throw if the entity type does not have a HasStream attribute + // $value on an entity type means default stream + this.edmType = EdmCoreModel.Instance.GetStream(false).Definition; + } + else + { + // Otherwise $value is the value of the previous property (null is OK for open properties) + this.edmType = previousType; + } + } + + /// + /// Gets the of this . + /// + public override IEdmType EdmType + { + get { return this.edmType; } + } + + /// + /// Translate a into another object using an instance of . + /// + /// Type that the translator will return after visiting this token. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(PathSegmentTranslator translator) + { + ExceptionUtils.CheckArgumentNotNull(translator, "translator"); + return translator.Translate(this); + } + + /// + /// Handle a using an instance of . + /// + /// An implementation of the translator interface. + /// Throws if the input handler is null. + public override void HandleWith(PathSegmentHandler handler) + { + ExceptionUtils.CheckArgumentNotNull(handler, "handler"); + handler.Handle(this); + } + + /// + /// Check if this segment is equal to another segment. + /// + /// the other segment to check. + /// true if the other segment is equal. + internal override bool Equals(ODataPathSegment other) + { + ValueSegment otherValueSegment = other as ValueSegment; + return otherValueSegment != null && + otherValueSegment.EdmType == this.edmType; + } + } +} + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/WildcardSelectItem.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/WildcardSelectItem.cs new file mode 100644 index 0000000..a8eb5b5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SemanticAst/WildcardSelectItem.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Class to represent a '*' selection item, indicating that all structural properties should be selected. + /// + public sealed class WildcardSelectItem : SelectItem + { + /// + /// Translate using a . + /// + /// Type that the translator will return after visiting this item. + /// An implementation of the translator interface. + /// An object whose type is determined by the type parameter of the translator. + /// Throws if the input translator is null. + public override T TranslateWith(SelectItemTranslator translator) + { + return translator.Translate(this); + } + + /// + /// Handle using a . + /// + /// An implementation of the handler interface. + /// Throws if the input handler is null. + public override void HandleWith(SelectItemHandler handler) + { + handler.Handle(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AllToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AllToken.cs new file mode 100644 index 0000000..48aaea4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AllToken.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + #endregion Namespaces + + /// + /// Lexical token representing the All Query + /// + public sealed class AllToken : LambdaToken + { + /// + /// Create a AllToken given the expression, parameter, and parent + /// + /// The associated expression. + /// The parameter denoting source type. + /// The parent token. Pass null if this property has no parent. + public AllToken(QueryToken expression, string parameter, QueryToken parent) : base(expression, parameter, parent) + { + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.All; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AnyToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AnyToken.cs new file mode 100644 index 0000000..d3b1f0e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/AnyToken.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + #endregion Namespaces + + /// + /// Lexical token representing the Any Query + /// + public sealed class AnyToken : LambdaToken + { + /// + /// Create a AnyToken given the expression, parameter, and parent + /// + /// The associated expression. + /// The parameter denoting source type. + /// The parent token. Pass null if this property has no parent. + public AnyToken(QueryToken expression, string parameter, QueryToken parent) : base(expression, parameter, parent) + { + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.Any; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/BinaryOperatorToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/BinaryOperatorToken.cs new file mode 100644 index 0000000..35791dd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/BinaryOperatorToken.cs @@ -0,0 +1,94 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.UriParser; + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing a binary operator. + /// + public sealed class BinaryOperatorToken : QueryToken + { + /// + /// The operator represented by this node. + /// + private readonly BinaryOperatorKind operatorKind; + + /// + /// The left operand. + /// + private readonly QueryToken left; + + /// + /// The right operand. + /// + private readonly QueryToken right; + + /// + /// Create a new BinaryOperatorToken given the operator, left and right query. + /// + /// The operator represented by this node. + /// The left operand. + /// The right operand. + public BinaryOperatorToken(BinaryOperatorKind operatorKind, QueryToken left, QueryToken right) + { + ExceptionUtils.CheckArgumentNotNull(left, "left"); + ExceptionUtils.CheckArgumentNotNull(right, "right"); + + this.operatorKind = operatorKind; + this.left = left; + this.right = right; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.BinaryOperator; } + } + + /// + /// The operator represented by this node. + /// + public BinaryOperatorKind OperatorKind + { + get { return this.operatorKind; } + } + + /// + /// The left operand. + /// + public QueryToken Left + { + get { return this.left; } + } + + /// + /// The right operand. + /// + public QueryToken Right + { + get { return this.right; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeExpressionToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeExpressionToken.cs new file mode 100644 index 0000000..373cb86 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeExpressionToken.cs @@ -0,0 +1,89 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using System; + using System.Collections.Generic; + + /// + /// Query token representing an Aggregate token. + /// + public sealed class ComputeExpressionToken : QueryToken + { + private QueryToken expression; + + private string alias; + + /// + /// Create an ComputeExpressionToken. + /// + /// The computation token. + /// The alias for the computation. + public ComputeExpressionToken(QueryToken expression, string alias) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(alias, "alias"); + + this.expression = expression; + this.alias = alias; + } + + /// + /// Gets the kind of this token. + /// + public override QueryTokenKind Kind + { + get + { + return QueryTokenKind.ComputeExpression; + } + } + + /// + /// Gets the QueryToken. + /// + public QueryToken Expression + { + get + { + return this.expression; + } + } + + /// + /// Gets the alias of the computation. + /// + public string Alias + { + get + { + return this.alias; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + SyntacticTreeVisitor implementation = visitor as SyntacticTreeVisitor; + if (implementation != null) + { + return implementation.Visit(this); + } + + throw new NotImplementedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeToken.cs new file mode 100644 index 0000000..1039c04 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ComputeToken.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using System; + using System.Collections.Generic; +#if !ODATA_CLIENT + using Aggregation; +#endif + + /// + /// Query token representing an Compute token. + /// + public sealed class ComputeToken : ApplyTransformationToken + { + private readonly IEnumerable expressions; + + /// + /// Create an ComputeToken. + /// + /// The list of ComputeExpressionToken. + public ComputeToken(IEnumerable expressions) + { + ExceptionUtils.CheckArgumentNotNull(expressions, "expressions"); + this.expressions = expressions; + } + + /// + /// Gets the kind of this token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.Compute; } + } + + /// + /// Gets the list of ComputeExpressionToken. + /// + public IEnumerable Expressions + { + get + { + return expressions; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + SyntacticTreeVisitor implementation = visitor as SyntacticTreeVisitor; + if (implementation != null) + { + return implementation.Visit(this); + } + + throw new NotImplementedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/CustomQueryOptionToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/CustomQueryOptionToken.cs new file mode 100644 index 0000000..8965473 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/CustomQueryOptionToken.cs @@ -0,0 +1,74 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing a query option. + /// + public sealed class CustomQueryOptionToken : QueryToken + { + /// + /// The name of the query option. + /// + private readonly string name; + + /// + /// The value of the query option. + /// + private readonly string value; + + /// + /// Create a new CustomQueryOptionToken given name and value. + /// + /// The name of the query option. + /// The value of the query option. + public CustomQueryOptionToken(string name, string value) + { + this.name = name; + this.value = value; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.CustomQueryOption; } + } + + /// + /// The name of the query option. + /// + public string Name + { + get { return this.name; } + } + + /// + /// The value of the query option. + /// + public string Value + { + get { return this.value; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/DottedIdentifierToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/DottedIdentifierToken.cs new file mode 100644 index 0000000..8869989 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/DottedIdentifierToken.cs @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing a type segment. + /// + public sealed class DottedIdentifierToken : PathToken + { + /// + /// The Identifier of the type segment. + /// + private readonly string identifier; + + /// + /// The parent segment. + /// + private QueryToken nextToken; + + /// + /// Create a TypeSegmentQueryToken given the Identifier and the parent (if any) + /// + /// The Identifier of the type segment, including the namespace. + /// The parent segment. + public DottedIdentifierToken(string identifier, QueryToken nextToken) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(identifier, "Identifier"); + + this.identifier = identifier; + this.nextToken = nextToken; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.DottedIdentifier; } + } + + /// + /// The full name of the type. + /// + public override string Identifier + { + get { return this.identifier; } + } + + /// + /// The parent. + /// + public override QueryToken NextToken + { + get { return this.nextToken; } + set { this.nextToken = value; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/EndPathToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/EndPathToken.cs new file mode 100644 index 0000000..6e8a6f6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/EndPathToken.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing the last segment in a path. + /// + public sealed class EndPathToken : PathToken + { + /// + /// The Identifier of the property to access. + /// + private readonly string identifier; + + /// + /// The NextToken token to access the property on. + /// If this is null, then the property access has no NextToken. That usually means to access the property + /// on the implicit parameter for the expression, the result on which the expression is being applied. + /// + private QueryToken nextToken; + + /// + /// Create a EndPathToken given the Identifier and the NextToken (if any) + /// + /// The Identifier of the property to access. + /// The NextToken token to access the property on. + public EndPathToken(string identifier, QueryToken nextToken) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(identifier, "Identifier"); + + this.identifier = identifier; + this.nextToken = nextToken; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.EndPath; } + } + + /// + /// The NextToken token to access the property on. + /// If this is null, then the property access has no NextToken. That usually means to access the property + /// on the implicit parameter for the expression, the result on which the expression is being applied. + /// + public override QueryToken NextToken + { + get { return this.nextToken; } + set { this.nextToken = value; } + } + + /// + /// The Identifier of the property to access. + /// + public override string Identifier + { + get { return this.identifier; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandTermToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandTermToken.cs new file mode 100644 index 0000000..d3a5eb2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandTermToken.cs @@ -0,0 +1,165 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + using System.Collections.Generic; + + #endregion Namespaces + + /// + /// Lexical token representing an expand operation. + /// + public sealed class ExpandTermToken : SelectExpandTermToken + { + /// + /// Create an expand term token using only a property + /// + /// the path to the navigation property + public ExpandTermToken(PathSegmentToken pathToNavigationProp) + : this(pathToNavigationProp, null, null) + { + } + + /// + /// Create an expand term using only the property and its subexpand/select + /// + /// the path to the navigation property for this expand term + /// the sub select for this token + /// the sub expand for this token + public ExpandTermToken(PathSegmentToken pathToNavigationProp, SelectToken selectOption, ExpandToken expandOption) + : this(pathToNavigationProp, null, null, null, null, null, null, null, selectOption, expandOption) + { + } + + /// + /// Create an expand term token + /// + /// the nav prop for this expand term + /// the filter option for this expand term + /// the orderby options for this expand term + /// the top option for this expand term + /// the skip option for this expand term + /// the query count option for this expand term + /// the levels option for this expand term + /// the search option for this expand term + /// the select option for this expand term + /// the expand option for this expand term + public ExpandTermToken(PathSegmentToken pathToNavigationProp, QueryToken filterOption, IEnumerable orderByOptions, long? topOption, long? skipOption, bool? countQueryOption, long? levelsOption, QueryToken searchOption, SelectToken selectOption, ExpandToken expandOption) + : this(pathToNavigationProp, filterOption, orderByOptions, topOption, skipOption, countQueryOption, levelsOption, searchOption, selectOption, expandOption, null) + { + } + + /// + /// Create an expand term token + /// + /// the nav prop for this expand term + /// the filter option for this expand term + /// the orderby options for this expand term + /// the top option for this expand term + /// the skip option for this expand term + /// the query count option for this expand term + /// the levels option for this expand term + /// the search option for this expand term + /// the select option for this expand term + /// the expand option for this expand term + /// the compute option for this expand term. + public ExpandTermToken( + PathSegmentToken pathToNavigationProp, + QueryToken filterOption, + IEnumerable orderByOptions, + long? topOption, + long? skipOption, + bool? countQueryOption, + long? levelsOption, + QueryToken searchOption, + SelectToken selectOption, + ExpandToken expandOption, + ComputeToken computeOption) + : this(pathToNavigationProp, filterOption, orderByOptions, topOption, skipOption, countQueryOption, levelsOption, searchOption, selectOption, expandOption, computeOption, null) + { + } + + /// + /// Create an expand term token + /// + /// the nav prop for this expand term + /// the filter option for this expand term + /// the orderby options for this expand term + /// the top option for this expand term + /// the skip option for this expand term + /// the query count option for this expand term + /// the levels option for this expand term + /// the search option for this expand term + /// the select option for this expand term + /// the expand option for this expand term + /// the compute option for this expand term. + /// the apply options for this expand term. + public ExpandTermToken( + PathSegmentToken pathToNavigationProp, + QueryToken filterOption, + IEnumerable orderByOptions, + long? topOption, + long? skipOption, + bool? countQueryOption, + long? levelsOption, + QueryToken searchOption, + SelectToken selectOption, + ExpandToken expandOption, + ComputeToken computeOption, + IEnumerable applyOptions) + : base(pathToNavigationProp, filterOption, orderByOptions, topOption, skipOption, countQueryOption, searchOption, selectOption, expandOption, computeOption) + { + LevelsOption = levelsOption; + ApplyOptions = applyOptions; + } + + /// + /// Gets the navigation property for this expand term. + /// + public PathSegmentToken PathToNavigationProp + { + get + { + return PathToProperty; + } + } + + /// + /// Gets the levels option for this expand term. + /// + public long? LevelsOption { get; private set; } + + /// + /// Gets the apply options for this expand term. + /// + public IEnumerable ApplyOptions { get; private set; } + + /// + /// Gets the kind of this expand term. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.ExpandTerm; } + } + + /// + /// Implement the visitor for this Token + /// + /// The type to return + /// A tree visitor that will visit this node. + /// Determined by the return type of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandToken.cs new file mode 100644 index 0000000..3fd79ce --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/ExpandToken.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + + using System.Collections.Generic; + + #endregion Namespaces + + /// + /// Lexical token representing an expand operation. + /// + public sealed class ExpandToken : QueryToken + { + /// + /// The properties according to which to expand in the results. + /// + private readonly IEnumerable expandTerms; + + /// + /// Create a ExpandToken given the property-accesses of the expand query. + /// + /// The properties according to which to expand the results. + public ExpandToken(IEnumerable expandTerms) + { + this.expandTerms = new ReadOnlyEnumerableForUriParser(expandTerms ?? new ExpandTermToken[0]); + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.Expand; } + } + + /// + /// The properties according to which to expand in the results. + /// + public IEnumerable ExpandTerms + { + get { return this.expandTerms; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionCallToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionCallToken.cs new file mode 100644 index 0000000..b69d6ca --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionCallToken.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + + using System.Collections.Generic; + using System.Linq; + + #endregion Namespaces + + /// + /// Lexical token representing a function call. + /// + public sealed class FunctionCallToken : QueryToken + { + /// + /// The name of the function to call. + /// + private readonly string name; + + /// + /// The arguments for the function. + /// + private readonly IEnumerable arguments; + + /// + /// the source token for this function call + /// + private readonly QueryToken source; + + /// + /// Create a new FunctionCallToken using the given function name and argument values. + /// + /// The name of the function to call. + /// The argument values for the function. + public FunctionCallToken(string name, IEnumerable argumentValues) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(name, "name"); + + this.name = name; + this.arguments = argumentValues == null ? + new ReadOnlyEnumerableForUriParser(FunctionParameterToken.EmptyParameterList) : + new ReadOnlyEnumerableForUriParser(argumentValues.Select(v => new FunctionParameterToken(null, v))); + this.source = null; + } + + /// + /// Create a new FunctionCallToken using the given function name and parameter tokens. + /// + /// The name of the function to call. + /// The arguments for the function. + /// The syntactically bound parent of this function + public FunctionCallToken(string name, IEnumerable arguments, QueryToken source) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(name, "name"); + + this.name = name; + this.arguments = new ReadOnlyEnumerableForUriParser(arguments ?? FunctionParameterToken.EmptyParameterList); + this.source = source; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.FunctionCall; } + } + + /// + /// The name of the function to call. + /// + public string Name + { + get { return this.name; } + } + + /// + /// The arguments for the function. + /// + public IEnumerable Arguments + { + get { return this.arguments; } + } + + /// + /// The syntactically bound parent of this function. + /// + public QueryToken Source + { + get { return this.source; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterAliasToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterAliasToken.cs new file mode 100644 index 0000000..ed58fd2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterAliasToken.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using System; + using Microsoft.OData.Edm; + + /// + /// A token to represent a parameter alias in a function call. + /// + internal sealed class FunctionParameterAliasToken : QueryToken + { + /// + /// Creates a FunctionParameterAliasToken + /// + /// the alias being used for the parameter. + public FunctionParameterAliasToken(string alias) + { + ExceptionUtils.CheckArgumentStringNotNullOrEmpty(alias, "alias"); + this.Alias = alias; + } + + /// + /// Gets the alias. + /// + public string Alias { get; private set; } + + /// + /// Gets the kind of this token + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.FunctionParameterAlias; } + } + + /// + /// The expected edm type of this parameter. + /// + internal IEdmTypeReference ExpectedParameterType { get; set; } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterToken.cs new file mode 100644 index 0000000..7be4e64 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/FunctionParameterToken.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// A token to represent a parameter to a function call. + /// + public sealed class FunctionParameterToken : QueryToken + { + /// + /// get an empty list of parameters + /// + public static FunctionParameterToken[] EmptyParameterList = new FunctionParameterToken[0]; + + /// + /// The name of the parameter + /// + private readonly string parameterName; + + /// + /// The value of this parameter + /// + private readonly QueryToken valueToken; + + /// + /// Creates a FunctionParameterToken + /// + /// the name of this parameter + /// the syntactically parsed value + public FunctionParameterToken(string parameterName, QueryToken valueToken) + { + this.parameterName = parameterName; + this.valueToken = valueToken; + } + + /// + /// Gets the name of this parameter + /// + public string ParameterName + { + get { return parameterName; } + } + + /// + /// Gets the syntactically parsed value of this token. + /// + public QueryToken ValueToken + { + get { return valueToken; } + } + + /// + /// Gets the kind of this token + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.FunctionParameter; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InToken.cs new file mode 100644 index 0000000..0843e5d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InToken.cs @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing an In operation. + /// + public sealed class InToken : QueryToken + { + /// + /// The left operand. + /// + private readonly QueryToken left; + + /// + /// The right operand. + /// + private readonly QueryToken right; + + /// + /// Create a new InToken given the left and right query tokens. + /// + /// The left operand. + /// The right operand. + public InToken(QueryToken left, QueryToken right) + { + ExceptionUtils.CheckArgumentNotNull(left, "left"); + ExceptionUtils.CheckArgumentNotNull(right, "right"); + + this.left = left; + this.right = right; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.In; } + } + + /// + /// The left operand. + /// + public QueryToken Left + { + get { return this.left; } + } + + /// + /// The right operand. + /// + public QueryToken Right + { + get { return this.right; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InnerPathToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InnerPathToken.cs new file mode 100644 index 0000000..91f9f66 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/InnerPathToken.cs @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + + using System.Collections.Generic; + + #endregion Namespaces + + /// + /// Lexical token representing a single nonroot segment in the query path. + /// + public sealed class InnerPathToken : PathToken + { + /// + /// The Identifier of the segment. + /// + private readonly string identifier; + + /// + /// The named values in the key lookup for this segment. + /// If the segment has no key lookup, then this property is null. + /// If the segment has empty key lookup (), then this property is an empty collection. + /// + private readonly IEnumerable namedValues; + + /// + /// The NextToken segment. + /// + private QueryToken nextToken; + + /// + /// Create a new StartPathToken given the Identifier and NextToken and namedValues if any + /// + /// The Identifier of the segment, the identifier. + /// The NextToken segment, or null if this is the root segment. + /// The named values in the key lookup for this segment. + public InnerPathToken(string identifier, QueryToken nextToken, IEnumerable namedValues) + { + // We allow an "empty" Identifier segment so we can create one for the case of a service-document URL (which has no path) + ExceptionUtils.CheckArgumentNotNull(identifier, "Identifier"); + + this.identifier = identifier; + this.nextToken = nextToken; + this.namedValues = namedValues == null ? null : new ReadOnlyEnumerableForUriParser(namedValues); + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.InnerPath; } + } + + /// + /// The Identifier of the segment, the identifier. + /// + public override string Identifier + { + get { return this.identifier; } + } + + /// + /// The NextToken segment, or null if this is the root segment. + /// + public override QueryToken NextToken + { + get { return this.nextToken; } + set { this.nextToken = value; } + } + + /// + /// The named values in the key lookup for this segment. + /// If the segment has no key lookup, then this property is null. + /// If the segment has empty key lookup (), then this property is an empty collection. + /// + public IEnumerable NamedValues + { + get { return this.namedValues; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LambdaToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LambdaToken.cs new file mode 100644 index 0000000..c7bd881 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LambdaToken.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing the Any/All Query + /// + public abstract class LambdaToken : QueryToken + { + /// + /// The parent token. + /// + private readonly QueryToken parent; + + /// + /// The parameter which denotes source type. + /// + private readonly string parameter; + + /// + /// The expression component of Any. + /// + private readonly QueryToken expression; + + /// + /// Create a AnyAllQueryToken given the expression, parameter, and parent + /// + /// The associated expression. + /// The parameter denoting source type. + /// The parent token. Pass null if this property has no parent. + protected LambdaToken(QueryToken expression, string parameter, QueryToken parent) + { + this.expression = expression; + this.parameter = parameter; + this.parent = parent; + } + + /// + /// The parent token. + /// + public QueryToken Parent + { + get { return this.parent; } + } + + /// + /// The expression. + /// + public QueryToken Expression + { + get { return this.expression; } + } + + /// + /// The parameter. + /// + public string Parameter + { + get { return this.parameter; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LiteralToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LiteralToken.cs new file mode 100644 index 0000000..3a5b1c5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/LiteralToken.cs @@ -0,0 +1,122 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using Microsoft.OData.Edm; + + /// + /// Lexical token representing a literal value. + /// + public sealed class LiteralToken : QueryToken + { + /// + /// The original text value of the literal. + /// + /// This is used internally to simulate correct compat behavior with WCF DS, and parameter alias. + /// We should use this during type promotion when applying metadata. + private readonly string originalText; + + /// + /// The value of the literal. This is a parsed primitive value. + /// + private readonly object value; + + /// + /// The expected EDM type of literal. + /// + private readonly IEdmTypeReference expectedEdmTypeReference; + + /// + /// Create a new LiteralToken given value and originalText + /// + /// The value of the literal. This is a parsed primitive value. + public LiteralToken(object value) + { + this.value = value; + } + + /// + /// Create a new LiteralToken given value and originalText + /// + /// The value of the literal. This is a parsed primitive value. + /// The original text value of the literal. + /// This is used internally to simulate correct compat behavior with WCF DS, and parameter alias. + internal LiteralToken(object value, string originalText) + : this(value) + { + this.originalText = originalText; + } + + /// + /// Create a new LiteralToken given value and originalText + /// + /// The value of the literal. This is a parsed primitive value. + /// The original text value of the literal. + /// The expected EDM type of literal.. + /// This is used internally to simulate correct compat behavior with WCF DS, and parameter alias. + internal LiteralToken(object value, string originalText, IEdmTypeReference expectedEdmTypeReference) + : this(value, originalText) + { + this.expectedEdmTypeReference = expectedEdmTypeReference; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.Literal; } + } + + /// + /// The value of the literal. This is a parsed primitive value. + /// + public object Value + { + get { return this.value; } + } + + /// + /// The original text value of the literal. + /// + /// This is used internally to simulate correct compat behavior with WCF DS, and parameter alias. + /// We should use this during type promotion when applying metadata. + internal string OriginalText + { + get + { + return this.originalText; + } + } + + /// + /// The expected EDM type of literal. + /// + internal IEdmTypeReference ExpectedEdmTypeReference + { + get + { + return this.expectedEdmTypeReference; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/NonSystemToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/NonSystemToken.cs new file mode 100644 index 0000000..6bea762 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/NonSystemToken.cs @@ -0,0 +1,97 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + + using System.Collections.Generic; + + #endregion Namespaces + + /// + /// Lexical token representing a segment in a path. + /// + /// + public sealed class NonSystemToken : PathSegmentToken + { + /// + /// Any named values for this NonSystemToken + /// + private readonly IEnumerable namedValues; + + /// + /// The identifier for this token. + /// + private readonly string identifier; + + /// + /// Build a NonSystemToken + /// + /// the identifier of this token + /// a list of named values for this token + /// the next token in the path + public NonSystemToken(string identifier, IEnumerable namedValues, PathSegmentToken nextToken) + : base(nextToken) + { + ExceptionUtils.CheckArgumentNotNull(identifier, "identifier"); + + this.identifier = identifier; + this.namedValues = namedValues; + } + + /// + /// Get the list of named values for this token. + /// + public IEnumerable NamedValues + { + get { return this.namedValues; } + } + + /// + /// Get the identifier for this token. + /// + public override string Identifier + { + get { return this.identifier; } + } + + /// + /// Is this token namespace or container qualified. + /// + /// true if this token is namespace or container qualified. + public override bool IsNamespaceOrContainerQualified() + { + return this.identifier.Contains("."); + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(IPathSegmentTokenVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + + /// + /// Accept a to walk a tree of s. + /// + /// An implementation of the visitor interface. + public override void Accept(IPathSegmentTokenVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/OrderByToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/OrderByToken.cs new file mode 100644 index 0000000..08215da --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/OrderByToken.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.UriParser; + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using System; + + /// + /// Lexical token representing an order by operation. + /// + public sealed class OrderByToken : QueryToken + { + /// + /// The direction of the ordering. + /// + private readonly OrderByDirection direction; + + /// + /// The expression according to which to order the results. + /// + private readonly QueryToken expression; + + /// + /// Create a new OrderByToken given the expression and direction + /// + /// The expression according to which to order the results. + /// The direction of the ordering. + public OrderByToken(QueryToken expression, OrderByDirection direction) + { + ExceptionUtils.CheckArgumentNotNull(expression, "expression"); + + this.expression = expression; + this.direction = direction; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.OrderBy; } + } + + /// + /// The direction of the ordering. + /// + public OrderByDirection Direction + { + get { return this.direction; } + } + + /// + /// The expression according to which to order the results. + /// + public QueryToken Expression + { + get { return this.expression; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathSegmentToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathSegmentToken.cs new file mode 100644 index 0000000..73034a1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathSegmentToken.cs @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing a segment in a path. + /// + public abstract class PathSegmentToken + { + /// + /// build this segment token using the next token + /// + /// the next token in the path + protected PathSegmentToken(PathSegmentToken nextToken) + { + NextToken = nextToken; + } + + /// + /// Get the NextToken in the path + /// + public PathSegmentToken NextToken { get; private set; } + + /// + /// The name of the property to access. + /// + public abstract string Identifier { get; } + + /// + /// Is this a structural property + /// + public bool IsStructuralProperty { get; set; } + + /// + /// Is this token namespace or container qualified. + /// + /// true if this token is namespace or container qualified. + public abstract bool IsNamespaceOrContainerQualified(); + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public abstract T Accept(IPathSegmentTokenVisitor visitor); + + /// + /// Accept a to walk a tree of s. + /// + /// An implementation of the visitor interface. + public abstract void Accept(IPathSegmentTokenVisitor visitor); + + /// + /// internal setter for the next token. + /// + /// the next token to set. + internal void SetNextToken(PathSegmentToken nextTokenIn) + { + NextToken = nextTokenIn; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathToken.cs new file mode 100644 index 0000000..895218c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/PathToken.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing a segment in a path. + /// + /// + public abstract class PathToken : QueryToken + { + /// + /// The NextToken in the path(can either be the parent or the child depending on whether the tree has + /// been normalized for expand or not. + /// TODO: need to revisit this and the rest of the syntactic parser to make it ready for public consumption. + /// + public abstract QueryToken NextToken { get; set; } + + /// + /// The name of the property to access. + /// + public abstract string Identifier { get; } + + /// Indicates the Equals overload. + /// True if equal. + /// The other PathToken. + public override bool Equals(object obj) + { + var otherPath = obj as PathToken; + if (otherPath == null) + { + return false; + } + + return this.Identifier.Equals(otherPath.Identifier) + && (this.NextToken == null && otherPath.NextToken == null + || this.NextToken.Equals(otherPath.NextToken)); + } + + /// Returns a hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + public override int GetHashCode() + { + int identifierHashCode = this.Identifier.GetHashCode(); + if (this.NextToken != null) + { + identifierHashCode = Combine(identifierHashCode, this.NextToken.GetHashCode()); + } + + return identifierHashCode; + } + + private static int Combine(int h1, int h2) + { + // RyuJIT optimizes this to use the ROL instruction + // Related GitHub pull request: dotnet/coreclr#1830 + // Based on https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Numerics/Hashing/HashHelpers.cs + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryToken.cs new file mode 100644 index 0000000..68dbaf1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryToken.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + + using System.Diagnostics.CodeAnalysis; + using Microsoft.OData; + + #endregion + + /// + /// Base class for all lexical tokens of OData query. + /// + public abstract class QueryToken + { + /// + /// Empty list of arguments. + /// + [SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly", Justification = "Modeled after Type.EmptyTypes")] + public static readonly QueryToken[] EmptyTokens = new QueryToken[0]; + + /// + /// The kind of the query token. + /// + public abstract QueryTokenKind Kind { get; } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public abstract T Accept(ISyntacticTreeVisitor visitor); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs new file mode 100644 index 0000000..5ca980b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs @@ -0,0 +1,159 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Enumeration of kinds of query tokens. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] + public enum QueryTokenKind + { + /// + /// The binary operator. + /// + BinaryOperator = 3, + + /// + /// The unary operator. + /// + UnaryOperator = 4, + + /// + /// The literal value. + /// + Literal = 5, + + /// + /// The function call. + /// + FunctionCall = 6, + + /// + /// The property access. + /// + EndPath = 7, + + /// + /// The order by operation. + /// + OrderBy = 8, + + /// + /// A query option. + /// + CustomQueryOption = 9, + + /// + /// The Select query. + /// + Select = 10, + + /// + /// The *. + /// + Star = 11, + + /// + /// The Expand query. + /// + Expand = 13, + + /// + /// Type segment. + /// + TypeSegment = 14, + + /// + /// Any query. + /// + Any = 15, + + /// + /// Non root segment. + /// + InnerPath = 16, + + /// + /// type segment. + /// + DottedIdentifier = 17, + + /// + /// Parameter token. + /// + RangeVariable = 18, + + /// + /// All query. + /// + All = 19, + + /// + /// ExpandTerm Token + /// + ExpandTerm = 20, + + /// + /// FunctionParameterToken + /// + FunctionParameter = 21, + + /// + /// FunctionParameterAlias + /// + FunctionParameterAlias = 22, + + /// + /// the string literal for search query + /// + StringLiteral = 23, + + /// + /// $apply aggregate token + /// + Aggregate = 24, + + /// + /// $apply aggregate statement to a property token + /// + AggregateExpression = 25, + + /// + /// $apply groupby token + /// + AggregateGroupBy = 26, + + /// + /// $compute token + /// + Compute = 27, + + /// + /// $compute expression token + /// + ComputeExpression = 28, + + /// + /// $apply aggregate statement to a entity set token + /// + EntitySetAggregateExpression = 29, + + /// + /// In operator. + /// + In = 30, + + /// + /// SelectTerm Token + /// + SelectTerm = 31 + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/RangeVariableToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/RangeVariableToken.cs new file mode 100644 index 0000000..d0234cc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/RangeVariableToken.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing the parameter for an Any/All query. + /// + public sealed class RangeVariableToken : QueryToken + { + /// + /// The name of the Any/All parameter. + /// + private readonly string name; + + /// + /// Create a new RangeVariableToken + /// + /// The name of the visitor for the Any/All query. + public RangeVariableToken(string name) + { + ExceptionUtils.CheckArgumentNotNull(name, "visitor"); + + this.name = name; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.RangeVariable; } + } + + /// + /// The name of the parameter. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + + /// Indicates the Equals overload. + /// True if equal. + /// The other RangeVariableToken. + public override bool Equals(object obj) + { + var otherPath = obj as RangeVariableToken; + if (otherPath == null) + { + return false; + } + + return this.Name.Equals(otherPath.Name); + } + + /// Returns a hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + public override int GetHashCode() + { + return this.Name.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectExpandTermToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectExpandTermToken.cs new file mode 100644 index 0000000..82ea130 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectExpandTermToken.cs @@ -0,0 +1,112 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + using System.Collections.Generic; + + #endregion Namespaces + + /// + /// Base class for and . + /// + public abstract class SelectExpandTermToken : QueryToken + { + /// + /// Initializes a new instance of class. + /// + /// the path to property for this select or expand term + /// the filter option for this select or expand term + /// the orderby options for this select or expand term + /// the top option for this select or expand term + /// the skip option for this select or expand term + /// the query count option for this select or expand term + /// the search option for this select or expand term + /// the select option for this select or expand term + /// the expand option for this select or expand term + /// the compute option for this select or expand term. + protected SelectExpandTermToken( + PathSegmentToken pathToProperty, + QueryToken filterOption, + IEnumerable orderByOptions, + long? topOption, + long? skipOption, + bool? countQueryOption, + QueryToken searchOption, + SelectToken selectOption, + ExpandToken expandOption, + ComputeToken computeOption) + { + ExceptionUtils.CheckArgumentNotNull(pathToProperty, "property"); + + PathToProperty = pathToProperty; + FilterOption = filterOption; + OrderByOptions = orderByOptions; + TopOption = topOption; + SkipOption = skipOption; + CountQueryOption = countQueryOption; + SearchOption = searchOption; + SelectOption = selectOption; + ExpandOption = expandOption; + ComputeOption = computeOption; + } + + /// + /// Gets the property for this select or expand term. + /// + public PathSegmentToken PathToProperty { get; private set; } + + /// + /// Gets the filter option for this select or expand term. + /// + public QueryToken FilterOption { get; private set; } + + /// + /// Gets the orderby options for this select or expand term. + /// + public IEnumerable OrderByOptions { get; private set; } + + /// + /// Gets the search option for this select or expand term. + /// + public QueryToken SearchOption { get; private set; } + + /// + /// Gets the top option for this select or expand term. + /// + public long? TopOption { get; private set; } + + /// + /// Gets the skip option for this select or expand term. + /// + public long? SkipOption { get; private set; } + + /// + /// Gets the query count option for this select or expand term. + /// + public bool? CountQueryOption { get; private set; } + + /// + /// Gets the select option for this select or expand term. + /// + public SelectToken SelectOption { get; private set; } + + /// + /// Gets the expand option for this select or expand term. + /// + public ExpandToken ExpandOption { get; private set; } + + /// + /// Gets the compute option for this select or expand term. + /// + public ComputeToken ComputeOption { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectTermToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectTermToken.cs new file mode 100644 index 0000000..6c5e564 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectTermToken.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + using System.Collections.Generic; + #endregion Namespaces + + /// + /// Lexical token representing a select operation. + /// + public sealed class SelectTermToken : SelectExpandTermToken + { + /// + /// Initializes a new instance of class. + /// + /// the path to the property for this select term + public SelectTermToken(PathSegmentToken pathToProperty) + : this(pathToProperty, null, null) + { + } + + /// + /// Create an select term using only the property and its subexpand/select + /// + /// the path to the property for this select term + /// the sub select for this token + /// the sub expand for this token + public SelectTermToken(PathSegmentToken pathToProperty, SelectToken selectOption, ExpandToken expandOption) + : this(pathToProperty, null, null, null, null, null, null, selectOption, expandOption, null) + { + } + + /// + /// Create a select term using only the property and its supporting query options. + /// + /// the path to the property for this select term + /// the filter option for this select term + /// the orderby options for this select term + /// the top option for this select term + /// the skip option for this select term + /// the query count option for this select term + /// the search option for this select term + /// the select option for this select term + /// the expand option for this select term + public SelectTermToken(PathSegmentToken pathToProperty, + QueryToken filterOption, IEnumerable orderByOptions, long? topOption, long? skipOption, bool? countQueryOption, QueryToken searchOption, SelectToken selectOption, ExpandToken expandOption, ComputeToken computeOption) + : base(pathToProperty, filterOption, orderByOptions, topOption, skipOption, countQueryOption, searchOption, selectOption, expandOption, computeOption) + { + } + + /// + /// Gets the kind of this expand term. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.SelectTerm; } + } + + /// + /// Implement the visitor for this Token + /// + /// The type to return + /// A tree visitor that will visit this node. + /// Determined by the return type of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectToken.cs new file mode 100644 index 0000000..55a7554 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SelectToken.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + + using System.Collections.Generic; + using System.Linq; + + #endregion Namespaces + + /// + /// Lexical token representing a select operation. + /// + public sealed class SelectToken : QueryToken + { + /// + /// The properties according to which to select in the results. + /// + private readonly IEnumerable selectTerms; + + /// + /// Create a given the property-accesses of the select query. + /// + /// The properties according to which to select the results. + public SelectToken(IEnumerable properties) + : this(properties == null ? null : properties.Select(e => new SelectTermToken(e))) + { + } + + /// + /// Create a given the property-accesses of the select query. + /// + /// The select term tokes according to which to select the results. + public SelectToken(IEnumerable selectTerms) + { + this.selectTerms = selectTerms != null ? + new ReadOnlyEnumerableForUriParser(selectTerms) : + new ReadOnlyEnumerableForUriParser(new SelectTermToken[0]); + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.Select; } + } + + /// + /// The properties according to which to select the results. + /// + public IEnumerable Properties + { + get + { + return this.selectTerms.Select(e => e.PathToProperty); + } + } + + /// + /// The properties according to which to expand in the results. + /// + public IEnumerable SelectTerms + { + get { return this.selectTerms; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StarToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StarToken.cs new file mode 100644 index 0000000..e6e5e2d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StarToken.cs @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + #endregion Namespaces + + /// + /// Lexical token representing an all-properties access. + /// + public sealed class StarToken : PathToken + { + /// + /// The NextToken token to access the property on. + /// If this is null, then the property access has no NextToken. That usually means to access the property + /// on the implicit parameter for the expression, the result on which the expression is being applied. + /// + private QueryToken nextToken; + + /// + /// Create a new StarToken given the NextToken (if any). + /// + /// The NextToken token to access the property on. Pass no if this property has no NextToken. + public StarToken(QueryToken nextToken) + { + this.nextToken = nextToken; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.Star; } + } + + /// + /// The NextToken token to access the property on. + /// If this is null, then the property access has no NextToken. That usually means to access the property + /// on the implicit parameter for the expression, the result on which the expression is being applied. + /// + public override QueryToken NextToken + { + get { return this.nextToken; } + set { this.nextToken = value; } + } + + /// + /// the name of this token(inherited from PathToken), which in this case is always "*" + /// + public override string Identifier + { + get { return "*"; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StringLiteralToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StringLiteralToken.cs new file mode 100644 index 0000000..41d6264 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/StringLiteralToken.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using System; + using System.Diagnostics; + + /// + /// Lexical token representing a string literal value. + /// + [DebuggerDisplay("StringLiteralToken ({text})")] + internal sealed class StringLiteralToken : QueryToken + { + /// + /// Raw text value for this token. + /// + private readonly string text; + + /// + /// Constructor for the StringLiteralToken + /// + /// The text value for this token + internal StringLiteralToken(string text) + { + this.text = text; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.StringLiteral; } + } + + /// + /// The original text value of the literal. + /// + /// This is used only internally to simulate correct compat behavior with WCF DS. + /// We should only use this during type promotion when applying metadata. + internal string Text + { + get + { + return this.text; + } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SystemToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SystemToken.cs new file mode 100644 index 0000000..b13a666 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/SystemToken.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + #region Namespaces + + + #endregion Namespaces + + /// + /// Lexical token representing a System token such as $count + /// + public sealed class SystemToken : PathSegmentToken + { + /// + /// The identifier for this SystemToken + /// + private readonly string identifier; + + /// + /// Build a new System Token + /// + /// the identifier for this token. + /// the next token in the path + public SystemToken(string identifier, PathSegmentToken nextToken) + : base(nextToken) + { + ExceptionUtils.CheckArgumentNotNull(identifier, "identifier"); + this.identifier = identifier; + } + + /// + /// Get the identifier for this token + /// + public override string Identifier + { + get { return this.identifier; } + } + + /// + /// Is this token namespace or container qualified. + /// + /// always false, since this is a system token. + public override bool IsNamespaceOrContainerQualified() + { + return false; + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(IPathSegmentTokenVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + return visitor.Visit(this); + } + + /// + /// Accept a to walk a tree of s. + /// + /// An implementation of the visitor interface. + public override void Accept(IPathSegmentTokenVisitor visitor) + { + ExceptionUtils.CheckArgumentNotNull(visitor, "visitor"); + visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/UnaryOperatorToken.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/UnaryOperatorToken.cs new file mode 100644 index 0000000..3ebf275 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/SyntacticAst/UnaryOperatorToken.cs @@ -0,0 +1,78 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.UriParser; + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Lexical token representing a unary operator. + /// + public sealed class UnaryOperatorToken : QueryToken + { + /// + /// The operator represented by this node. + /// + private readonly UnaryOperatorKind operatorKind; + + /// + /// The operand. + /// + private readonly QueryToken operand; + + /// + /// Create a new UnaryOperatorToken given the operator and operand + /// + /// The operator represented by this node. + /// The operand. + public UnaryOperatorToken(UnaryOperatorKind operatorKind, QueryToken operand) + { + ExceptionUtils.CheckArgumentNotNull(operand, "operand"); + + this.operatorKind = operatorKind; + this.operand = operand; + } + + /// + /// The kind of the query token. + /// + public override QueryTokenKind Kind + { + get { return QueryTokenKind.UnaryOperator; } + } + + /// + /// The operator represented by this node. + /// + public UnaryOperatorKind OperatorKind + { + get { return this.operatorKind; } + } + + /// + /// The operand. + /// + public QueryToken Operand + { + get { return this.operand; } + } + + /// + /// Accept a to walk a tree of s. + /// + /// Type that the visitor will return after visiting this token. + /// An implementation of the visitor interface. + /// An object whose type is determined by the type parameter of the visitor. + public override T Accept(ISyntacticTreeVisitor visitor) + { + return visitor.Visit(this); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/BinaryOperatorKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/BinaryOperatorKind.cs new file mode 100644 index 0000000..ecffa3a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/BinaryOperatorKind.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + #endregion Namespaces + + /// + /// Enumeration of binary operators. + /// + public enum BinaryOperatorKind + { + /// + /// The logical or operator. + /// + Or = 0, + + /// + /// The logical and operator. + /// + And = 1, + + /// + /// The eq operator. + /// + Equal = 2, + + /// + /// The ne operator. + /// + NotEqual = 3, + + /// + /// The gt operator. + /// + GreaterThan = 4, + + /// + /// The ge operator. + /// + GreaterThanOrEqual = 5, + + /// + /// The lt operator. + /// + LessThan = 6, + + /// + /// The le operator. + /// + LessThanOrEqual = 7, + + /// + /// The add operator. + /// + Add = 8, + + /// + /// The sub operator. + /// + Subtract = 9, + + /// + /// The mul operator. + /// + Multiply = 10, + + /// + /// The div operator. + /// + Divide = 11, + + /// + /// The mod operator. + /// + Modulo = 12, + + /// + /// The has operator. + /// + Has = 13, + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/ExpressionTokenKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/ExpressionTokenKind.cs new file mode 100644 index 0000000..5e221aa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/ExpressionTokenKind.cs @@ -0,0 +1,126 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// Enumeration values for token kinds. + internal enum ExpressionTokenKind + { + /// Unknown. + Unknown = 0, + + /// End of text. + End = 1, + + /// '=' - equality character. + Equal = 2, + + /// Identifier. + Identifier = 3, + + /// NullLiteral. + NullLiteral = 4, + + /// BooleanLiteral. + BooleanLiteral = 5, + + /// StringLiteral. + StringLiteral = 6, + + /// IntegerLiteral. + IntegerLiteral = 7, + + /// Int64 literal. + Int64Literal = 8, + + /// Single literal. + SingleLiteral = 9, + + /// DateTime literal. + DateTimeLiteral = 10, + + /// DateTimeOffset literal. + DateTimeOffsetLiteral = 11, + + /// Duration literal. + DurationLiteral = 12, + + /// Decimal literal. + DecimalLiteral = 13, + + /// Double literal. + DoubleLiteral = 14, + + /// GUID literal. + GuidLiteral = 15, + + /// Binary literal. + BinaryLiteral = 16, + + /// Geography literal. + GeographyLiteral = 17, + + /// Geometry literal. + GeometryLiteral = 18, + + /// Exclamation. + Exclamation = 19, + + /// OpenParen. + OpenParen = 20, + + /// CloseParen. + CloseParen = 21, + + /// Comma. + Comma = 22, + + /// Colon. + Colon = 23, + + /// Minus. + Minus = 24, + + /// Slash. + Slash = 25, + + /// Question. + Question = 26, + + /// Dot. + Dot = 27, + + /// Star. + Star = 28, + + /// SemiColon + SemiColon = 29, + + /// ParameterAlias + ParameterAlias = 30, + + /// A BracedExpression is an expression within braces. It contains a JSON object. + BracedExpression = 31, + + /// A BracketedExpression is an expression within brackets. It contains a JSON array. + BracketedExpression = 32, + + /// Quoted + QuotedLiteral = 33, + + /// Date literal. + DateLiteral = 34, + + /// TimeOfDay literal. + TimeOfDayLiteral = 35, + + /// CustomType Literal. + CustomTypeLiteral = 36, + + /// A ParenthesesExpression is an expression within parentheses. It contains a list of objects. + ParenthesesExpression = 37, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs new file mode 100644 index 0000000..fe1f2c4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs @@ -0,0 +1,363 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Public enumeration of kinds of query nodes. A subset of InternalQueryNodeKind + /// + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags", Justification = "QueryNodeKind is not a flag.")] + public enum QueryNodeKind + { + /// + /// No query node kind... the default value. + /// + None = InternalQueryNodeKind.None, + + /// + /// A constant value. + /// + Constant = InternalQueryNodeKind.Constant, + + /// + /// A node that represents conversion from one type to another. + /// + Convert = InternalQueryNodeKind.Convert, + + /// + /// Non-resource node referencing a range variable. + /// + NonResourceRangeVariableReference = InternalQueryNodeKind.NonResourceRangeVariableReference, + + /// + /// Node used to represent a binary operator. + /// + BinaryOperator = InternalQueryNodeKind.BinaryOperator, + + /// + /// Node used to represent a unary operator. + /// + UnaryOperator = InternalQueryNodeKind.UnaryOperator, + + /// + /// Node describing access to a property which is a single (non-collection) non-entity value. + /// + SingleValuePropertyAccess = InternalQueryNodeKind.SingleValuePropertyAccess, + + /// + /// Node describing access to a property which is a non-entity collection value. + /// + CollectionPropertyAccess = InternalQueryNodeKind.CollectionPropertyAccess, + + /// + /// Function call returning a single value. + /// + SingleValueFunctionCall = InternalQueryNodeKind.SingleValueFunctionCall, + + /// + /// Any query. + /// + Any = InternalQueryNodeKind.Any, + + /// + /// Node for a navigation property with target multiplicity Many. + /// + CollectionNavigationNode = InternalQueryNodeKind.CollectionNavigationNode, + + /// + /// Node for a navigation property with target multiplicity ZeroOrOne or One. + /// + SingleNavigationNode = InternalQueryNodeKind.SingleNavigationNode, + + /// + /// Single-value property access that refers to an open property. + /// + SingleValueOpenPropertyAccess = InternalQueryNodeKind.SingleValueOpenPropertyAccess, + + /// + /// Cast on a single resource. + /// + SingleResourceCast = InternalQueryNodeKind.SingleResourceCast, + + /// + /// All query. + /// + All = InternalQueryNodeKind.All, + + /// + /// Cast on a collection of resources. + /// + CollectionResourceCast = InternalQueryNodeKind.CollectionResourceCast, + + /// + /// Placeholder node referencing a rangeVariable on the binding stack that references an entity or a complex. + /// + ResourceRangeVariableReference = InternalQueryNodeKind.ResourceRangeVariableReference, + + /// + /// Node the represents a function call that returns a single entity or complex. + /// + SingleResourceFunctionCall = InternalQueryNodeKind.SingleResourceFunctionCall, + + /// + /// Node that represents a function call that returns a collection. + /// + CollectionFunctionCall = InternalQueryNodeKind.CollectionFunctionCall, + + /// + /// Node that represents a function call that returns a collection of resources. + /// + CollectionResourceFunctionCall = InternalQueryNodeKind.CollectionResourceFunctionCall, + + /// + /// Node that represents a named function parameter. + /// + NamedFunctionParameter = InternalQueryNodeKind.NamedFunctionParameter, + + /// + /// The parameter alias node. + /// + ParameterAlias = InternalQueryNodeKind.ParameterAlias, + + /// + /// The entity set node. + /// + EntitySet = InternalQueryNodeKind.EntitySet, + + /// + /// The key lookup on a collection. + /// + KeyLookup = InternalQueryNodeKind.KeyLookup, + + /// + /// The search term node. + /// + SearchTerm = InternalQueryNodeKind.SearchTerm, + + /// + /// Node describing access to a open property which is a non-entity collection value. + /// + CollectionOpenPropertyAccess = InternalQueryNodeKind.CollectionOpenPropertyAccess, + + /// + /// Node represents a collection of complex property. + /// + CollectionComplexNode = InternalQueryNodeKind.CollectionComplexNode, + + /// + /// Node represents a single complex property. + /// + SingleComplexNode = InternalQueryNodeKind.SingleComplexNode, + + /// + /// Count of a collection contains primitive or enum or complex or entity type. + /// + Count = InternalQueryNodeKind.Count, + + /// + /// Cast on a single value. + /// + SingleValueCast = InternalQueryNodeKind.SingleValueCast, + + /// + /// Node represents a property of a collection. + /// + CollectionPropertyNode = InternalQueryNodeKind.CollectionPropertyNode, + + /// + /// Node represents a property of a aggregated collection. + /// + AggregatedCollectionPropertyNode = InternalQueryNodeKind.AggregatedCollectionPropertyNode, + + /// + /// In operator node. + /// + In = InternalQueryNodeKind.In, + + /// + /// Node that represents a collection of constants. + /// + CollectionConstant = InternalQueryNodeKind.CollectionConstant, + } + + /// + /// Internal enumeration of kinds of query nodes. A superset of QueryNodeKind + /// + internal enum InternalQueryNodeKind + { + /// + /// none... default value. + /// + None = 0, + + /// + /// The constant value. + /// + Constant = 1, + + /// + /// A node that signifies the promotion of a primitive type. + /// + Convert = 2, + + /// + /// Non-resource node referencing a range variable. + /// + NonResourceRangeVariableReference = 3, + + /// + /// Parameter node used to represent a binary operator. + /// + BinaryOperator = 4, + + /// + /// Parameter node used to represent a unary operator. + /// + UnaryOperator = 5, + + /// + /// Node describing access to a property which is a single (non-collection) non-entity value. + /// + SingleValuePropertyAccess = 6, + + /// + /// Node describing access to a property which is a non-entity collection value. + /// + CollectionPropertyAccess = 7, + + /// + /// Function call returning a single value. + /// + SingleValueFunctionCall = 8, + + /// + /// Any query. + /// + Any = 9, + + /// + /// Node for a navigation property with target multiplicity Many. + /// + CollectionNavigationNode = 10, + + /// + /// Node for a navigation property with target multiplicity ZeroOrOne or One. + /// + SingleNavigationNode = 11, + + /// + /// Single-value property access that refers to an open property. + /// + SingleValueOpenPropertyAccess = 12, + + /// + /// Cast on a single resource. + /// + SingleResourceCast = 13, + + /// + /// All query. + /// + All = 14, + + /// + /// Cast on a resource collection. + /// + CollectionResourceCast = 15, + + /// + /// Resource node referencing a range variable. + /// + ResourceRangeVariableReference = 16, + + /// + /// SingleResourceFunctionCall node. + /// + SingleResourceFunctionCall = 17, + + /// + /// Node that represents a function call that returns a collection. + /// + CollectionFunctionCall = 18, + + /// + /// Node that represents a function call that returns a collection of resources. + /// + CollectionResourceFunctionCall = 19, + + /// + /// Node that represents a named function parameter. + /// + NamedFunctionParameter = 20, + + /// + /// The parameter alias node. + /// + ParameterAlias = 21, + + /// + /// The entity set node. + /// + EntitySet = 22, + + /// + /// The key lookup on a collection. + /// + KeyLookup = 23, + + /// + /// The search Term. + /// + SearchTerm = 24, + + /// + /// Node describing access to a open property which is a non-entity collection value. + /// + CollectionOpenPropertyAccess = 25, + + /// + /// Node represents a collection of complex property. + /// + CollectionComplexNode = 26, + + /// + /// Node represents a single complex property. + /// + SingleComplexNode = 27, + + /// + /// Node describing count of a collection contains primitive or enum or complex or entity type. + /// + Count = 28, + + /// + /// Cast on a single value. + /// + SingleValueCast = 29, + + /// + /// Node represents a property of a collection. + /// + CollectionPropertyNode = 30, + + /// + /// Node represents a property of a aggregated collection. + /// + AggregatedCollectionPropertyNode = 31, + + /// + /// In operator node. + /// + In = 32, + + /// + /// Node that represents a collection of constants. + /// + CollectionConstant = 33, + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/RequestTargetKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/RequestTargetKind.cs new file mode 100644 index 0000000..b926769 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/RequestTargetKind.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Provides values to describe the kind of thing targetted by a + /// client request. + /// + internal enum RequestTargetKind + { + /// Nothing specific is being requested. + Nothing, + + /// A top-level directory of service capabilities. + ServiceDirectory, + + /// Resource is requested - it can be a collection or a single value of entity type or complex type + Resource, + + /// A single primitive property is requested (eg: a Picture property). + Primitive, + + /// A single primitive value is requested (eg: the raw stream of a Picture). + PrimitiveValue, + + /// A single enumeration property is requested (eg:the property value like <d:ColorFlags ... >SolidYellow</d:ColorFlags>). + Enum, + + /// A single enumeration value is requested (eg: the raw value like 'SolidYellow'). + EnumValue, + + /// System metadata. + Metadata, + + /// A data-service-defined operation that doesn't return anything. + VoidOperation, + + /// The request is a batch request. + Batch, + + /// An unknown path or a dynamic property is requested. + Dynamic, + + /// The value of an unknown path or a dynamic property value is requested. + DynamicValue, + + /// A stream property value is requested. + MediaResource, + + /// A single collection of primitive or enum values is requested. + Collection, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/UnaryOperatorKind.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/UnaryOperatorKind.cs new file mode 100644 index 0000000..d0df067 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/UnaryOperatorKind.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + #endregion Namespaces + + /// + /// Enumeration of binary operators. + /// + public enum UnaryOperatorKind + { + /// + /// The unary - operator. + /// + Negate = 0, + + /// + /// The not operator. + /// + Not = 1, + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypeFacetsPromotionRules.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypeFacetsPromotionRules.cs new file mode 100644 index 0000000..bf8ef59 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypeFacetsPromotionRules.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Defines the promotion rules for type facets. + /// + public class TypeFacetsPromotionRules + { + /// + /// Computes the promoted precision value for left and right. + /// The default implementation works as follows: + /// 1) if both left and right are null, return null; + /// 2) if only one is null, return the other; + /// 3) otherwise, return the larger. + /// + /// Left-hand-side precision value. + /// Right-hand-side precision value. + /// The promoted precision value. + public virtual int? GetPromotedPrecision(int? left, int? right) + { + return left == null ? right : + right == null ? left : + Math.Max((int)left, (int)right); + } + + /// + /// Computes the promoted scale value for left and right. + /// The default implementation works as follows: + /// 1) if both left and right are null, return null; + /// 2) if only one is null, return the other; + /// 3) otherwise, return the larger. + /// + /// Left-hand-side scale value. + /// Right-hand-side scale value. + /// The promoted scale value. + public virtual int? GetPromotedScale(int? left, int? right) + { + return left == null ? right : + right == null ? left : + Math.Max((int)left, (int)right); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypePromotionUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypePromotionUtils.cs new file mode 100644 index 0000000..a92c440 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/TypePromotionUtils.cs @@ -0,0 +1,1186 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + using ODataErrorStrings = Microsoft.OData.Strings; + + #endregion Namespaces + + /// + /// Helper methods for promoting argument types of operators and function calls. + /// + /// + /// Note that the lists of signatures are for matching primitive types to functions. + /// Equality (eq and ne) operators are a bit special since they are also defined for + /// entity and complex types. + /// + internal static class TypePromotionUtils + { + /// + /// Struct constant for not found result of name-signature search. + /// + internal static readonly KeyValuePair NotFoundKeyValuePair = + new KeyValuePair(); + + /// Function signatures for logical operators (and, or). + private static readonly FunctionSignature[] logicalSignatures = new FunctionSignature[] + { + new FunctionSignature(new[] { EdmCoreModel.Instance.GetBoolean(false), EdmCoreModel.Instance.GetBoolean(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetBoolean(true), EdmCoreModel.Instance.GetBoolean(true) }, null), + }; + + /// Function signatures for the 'not' operator. + private static readonly FunctionSignature[] notSignatures = new FunctionSignature[] + { + new FunctionSignature(new[] { EdmCoreModel.Instance.GetBoolean(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetBoolean(true) }, null), + }; + + /// Function signatures for arithmetic operators (add, sub, mul, div, mod). + private static readonly FunctionSignature[] arithmeticSignatures = new FunctionSignature[] + { + // COMPAT 41: Type promotion in the product does not strictly follow the OIPI spec + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt32(false), EdmCoreModel.Instance.GetInt32(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt32(true), EdmCoreModel.Instance.GetInt32(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt64(false), EdmCoreModel.Instance.GetInt64(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt64(true), EdmCoreModel.Instance.GetInt64(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetSingle(false), EdmCoreModel.Instance.GetSingle(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetSingle(true), EdmCoreModel.Instance.GetSingle(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDouble(false), EdmCoreModel.Instance.GetDouble(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDouble(true), EdmCoreModel.Instance.GetDouble(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDecimal(false), EdmCoreModel.Instance.GetDecimal(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, false); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDecimal(true), EdmCoreModel.Instance.GetDecimal(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, true); } + }), + }; + + /// + /// Signature for add + /// + private static readonly FunctionSignature[] AdditionSignatures = arithmeticSignatures.Concat(GetAdditionTermporalSignatures()).ToArray(); + + /// + /// Signature for sub + /// + private static readonly FunctionSignature[] SubtractionSignatures = arithmeticSignatures.Concat(GetSubtractionTermporalSignatures()).ToArray(); + + /// Function signatures for relational operators (eq, ne, lt, le, gt, ge). + private static readonly FunctionSignature[] relationalSignatures = new FunctionSignature[] + { + // COMPAT 41: Type promotion in the product does not strictly follow the OIPI spec + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt32(false), EdmCoreModel.Instance.GetInt32(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt32(true), EdmCoreModel.Instance.GetInt32(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt64(false), EdmCoreModel.Instance.GetInt64(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt64(true), EdmCoreModel.Instance.GetInt64(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetSingle(false), EdmCoreModel.Instance.GetSingle(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetSingle(true), EdmCoreModel.Instance.GetSingle(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDouble(false), EdmCoreModel.Instance.GetDouble(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDouble(true), EdmCoreModel.Instance.GetDouble(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDecimal(false), EdmCoreModel.Instance.GetDecimal(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, false); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDecimal(true), EdmCoreModel.Instance.GetDecimal(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, true); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetString(true), EdmCoreModel.Instance.GetString(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetBinary(true), EdmCoreModel.Instance.GetBinary(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetBoolean(false), EdmCoreModel.Instance.GetBoolean(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetBoolean(true), EdmCoreModel.Instance.GetBoolean(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetGuid(false), EdmCoreModel.Instance.GetGuid(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetGuid(true), EdmCoreModel.Instance.GetGuid(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(false), EdmCoreModel.Instance.GetDate(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(true), EdmCoreModel.Instance.GetDate(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, false), EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, false); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, true), EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, true); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, false), EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, true), EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, false), EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, p, false); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, true), EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.TimeOfDay, p, true); } + }), + }; + + /// Function signatures for the 'negate' operator. + private static readonly FunctionSignature[] negationSignatures = new FunctionSignature[] + { + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt32(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt32(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt64(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetInt64(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetSingle(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetSingle(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDouble(false) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDouble(true) }, null), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDecimal(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, false); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDecimal(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetDecimal(p, s, true); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }), + new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }), + }; + + /// Numeric type kinds. + private enum NumericTypeKind + { + /// A type that is not numeric. + NotNumeric = 0, + + /// A type that is a char, single, double or decimal. + NotIntegral = 1, + + /// A type that is a signed integral. + SignedIntegral = 2, + + /// A type that is an unsigned integral. + UnsignedIntegral = 3, + } + + /// + /// Gets the facets of a type. + /// + /// The type in question. + /// The precision facet. + /// The scale facet. + internal static void GetTypeFacets(IEdmTypeReference type, out int? precision, out int? scale) + { + precision = null; + scale = null; + + var decimalType = type as IEdmDecimalTypeReference; + if (decimalType != null) + { + precision = decimalType.Precision; + scale = decimalType.Scale; + return; + } + + var temporalType = type as IEdmTemporalTypeReference; + if (temporalType != null) + { + precision = temporalType.Precision; + return; + } + } + + /// Checks that the operands (possibly promoted) are valid for the specified operation. + /// The operator kind to promote the operand types for. + /// The left operand node. + /// The right operand node. + /// The left operand type after promotion. + /// The right operand type after promotion. + /// Promotion rules for type facets. + /// True if a valid function signature was found that matches the given types after any necessary promotions are made. + /// False if there is no binary operators + internal static bool PromoteOperandTypes( + BinaryOperatorKind operatorKind, + SingleValueNode leftNode, + SingleValueNode rightNode, + out IEdmTypeReference left, + out IEdmTypeReference right, + TypeFacetsPromotionRules facetsPromotionRules) + { + left = leftNode.TypeReference; + right = rightNode.TypeReference; + + // The types for the operands can be null + // if they (a) represent the null literal or (b) represent an open type/property. + // If both argument types are null we lack type information on both sides and cannot promote arguments. + if (left == null && right == null) + { + // if we find null literals or open properties on both sides we cannot promote; the result type will also be null + return true; + } + + if (operatorKind == BinaryOperatorKind.NotEqual || operatorKind == BinaryOperatorKind.Equal) + { + if (TryHandleEqualityOperatorForEntityOrComplexTypes(ref left, ref right)) + { + return true; + } + + // Comparing an enum with a string is valid + if (left != null && right != null && left.IsEnum() && right.IsString()) + { + right = left; + return true; + } + + if (left != null && right != null && right.IsEnum() && left.IsString()) + { + left = right; + return true; + } + + // enum and spatial type support equality operator for null operand: + if ((left == null) && (right != null) && (right.IsEnum() || right is IEdmSpatialTypeReference)) + { + left = right; + return true; + } + + if ((right == null) && (left != null) && (left.IsEnum() || left is IEdmSpatialTypeReference)) + { + right = left; + return true; + } + } + + // enum support, check type full names + if (left != null && right != null && left.IsEnum() && right.IsEnum()) + { + return string.Equals(left.FullName(), right.FullName(), StringComparison.Ordinal); + } + + // type definition support, treat type definitions as their underlying types. + if (left != null && left.IsTypeDefinition()) + { + left = left.AsPrimitive(); + } + + if (right != null && right.IsTypeDefinition()) + { + right = right.AsPrimitive(); + } + + // Except for above, binary operators are only supported on primitive types + if (left != null && !left.IsODataPrimitiveTypeKind() || right != null && !right.IsODataPrimitiveTypeKind()) + { + return false; + } + + // The following will match primitive argument types to build in function signatures, choosing the best one possible. + FunctionSignature bestMatch; + bool success = FindBestSignature( + GetFunctionSignatures(operatorKind), + new[] { leftNode, rightNode }, + new[] { left, right }, + out bestMatch) + == 1; + + if (success) + { + int? leftPrecision, leftScale; + int? rightPrecision, rightScale; + GetTypeFacets(left, out leftPrecision, out leftScale); + GetTypeFacets(right, out rightPrecision, out rightScale); + var resultPrecision = facetsPromotionRules.GetPromotedPrecision(leftPrecision, rightPrecision); + var resultScale = facetsPromotionRules.GetPromotedScale(leftScale, rightScale); + + left = bestMatch.GetArgumentTypeWithFacets(0, resultPrecision, resultScale); + right = bestMatch.GetArgumentTypeWithFacets(1, resultPrecision, resultScale); + } + + return success; + } + + /// Checks that the operands (possibly promoted) are valid for the specified operation. + /// The operator kind to promote the operand types for. + /// Type of the operand. + /// True if the type could be promoted; otherwise false. + internal static bool PromoteOperandType(UnaryOperatorKind operatorKind, ref IEdmTypeReference typeReference) + { + // The type for the operands can be null + // if it (a) represents the null literal or (b) represents an open type/property. + // If argument type is null we lack type information and cannot promote the argument type. + if (typeReference == null) + { + // if we find a null literal or open property we cannot promote; the result type will also be null + return true; + } + + FunctionSignature bestMatch; + bool success = FindBestSignature( + GetFunctionSignatures(operatorKind), + new SingleValueNode[] { null }, + new[] { typeReference }, + out bestMatch) + == 1; + + if (success) + { + int? precision, scale; + GetTypeFacets(typeReference, out precision, out scale); + typeReference = bestMatch.GetArgumentTypeWithFacets(0, precision, scale); + } + + return success; + } + + /// Finds the best fitting function for the specified arguments. + /// Functions with names to consider. + /// Nodes of the arguments for the function, can be new {null,null}. + /// Function call token used for case-sensitive matching to resolve ambiguous cases. + /// The best fitting function; null if none found or ambiguous. + internal static KeyValuePair FindBestFunctionSignature( + IList> nameFunctions, + SingleValueNode[] argumentNodes, string functionCallToken) + { + IEdmTypeReference[] argumentTypes = argumentNodes.Select(s => s.TypeReference).ToArray(); + Debug.Assert(nameFunctions != null, "nameFunctions != null"); + Debug.Assert(argumentTypes != null, "argumentTypes != null"); + Debug.Assert(functionCallToken != null, "functionCallToken != null"); + IList> applicableNameFunctions + = new List>(nameFunctions.Count); + + // Build a list of applicable functions (and cache their promoted arguments). + foreach (KeyValuePair candidate in nameFunctions) + { + if (candidate.Value.ArgumentTypes.Length != argumentTypes.Length) + { + continue; + } + + bool argumentsMatch = true; + for (int i = 0; i < candidate.Value.ArgumentTypes.Length; i++) + { + if (!CanPromoteNodeTo(argumentNodes[i], argumentTypes[i], candidate.Value.ArgumentTypes[i])) + { + argumentsMatch = false; + break; + } + } + + if (argumentsMatch) + { + applicableNameFunctions.Add(candidate); + } + } + + // Return the best applicable function. + if (applicableNameFunctions.Count == 0) + { + // No matching function. + return NotFoundKeyValuePair; + } + else if (applicableNameFunctions.Count == 1) + { + return applicableNameFunctions[0]; + } + else + { + // Find a single function which is better than all others. + IList> equallyArgumentsMatchingNameFunctions = + new List>(); + for (int i = 0; i < applicableNameFunctions.Count; i++) + { + bool betterThanAllOthers = true; + for (int j = 0; j < applicableNameFunctions.Count; j++) + { + if (i != j && MatchesArgumentTypesBetterThan(argumentTypes, applicableNameFunctions[j].Value.ArgumentTypes, applicableNameFunctions[i].Value.ArgumentTypes)) + { + betterThanAllOthers = false; + break; + } + } + + if (betterThanAllOthers) + { + // cache equally matching functions for case-sensitive resolution based on null/non-null function call token. + equallyArgumentsMatchingNameFunctions.Add(applicableNameFunctions[i]); + } + } + + KeyValuePair result = NotFoundKeyValuePair; + if (equallyArgumentsMatchingNameFunctions.Count == 1) + { + // Best match count = 1. + result = equallyArgumentsMatchingNameFunctions[0]; + } + else + { + // For multiple best matches based on argument types, should return as ambiguous result + // if no exact match is found. + foreach (KeyValuePair candidate in equallyArgumentsMatchingNameFunctions) + { + if (candidate.Key.Equals(functionCallToken, StringComparison.Ordinal)) + { + // Registered function name keys are guaranteed to be case-sensitively unique + // If one is found, it should be what we are looking for. + result = candidate; + break; + } + } + } + + return result; + } + } + + /// Checks whether the source type is compatible with the target type. + /// The actual argument node. + /// Source type. + /// Target type. + /// true if source can be used in place of target; false otherwise. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "One method to describe all rules around converts.")] + internal static bool CanConvertTo(SingleValueNode sourceNodeOrNull, IEdmTypeReference sourceReference, IEdmTypeReference targetReference) + { + Debug.Assert(sourceReference != null, "sourceReference != null"); + Debug.Assert(targetReference != null, "targetReference != null"); + + //// Copy of the ResourceQueryParser.ExpressionParser.IsCompatibleWith method. + + if (sourceReference.IsEquivalentTo(targetReference)) + { + return true; + } + + if (targetReference.IsODataComplexTypeKind() || targetReference.IsODataEntityTypeKind()) + { + // for structured types, use IsAssignableFrom + return EdmLibraryExtensions.IsAssignableFrom( + (IEdmStructuredType)targetReference.Definition, + (IEdmStructuredType)sourceReference.Definition); + } + + //// This rule stops the parser from considering nullable types as incompatible + //// with non-nullable types. We have however implemented this rule because we just + //// access underlying rules. C# requires an explicit .Value access, and EDM has no + //// nullablity on types and (at the model level) implements null propagation. + //// + //// if (sourceReference.IsNullable && !targetReference.IsNullable) + //// { + //// return false; + //// } + + if (sourceReference.IsEnum() && targetReference.IsEnum()) + { + if (sourceReference.Definition.IsEquivalentTo(targetReference.Definition)) + { + return targetReference.IsNullable() || (!sourceReference.IsNullable()); + } + + return false; + } + + if (targetReference.IsEnum() && sourceReference.IsString()) + { + return true; + } + + IEdmPrimitiveTypeReference sourcePrimitiveTypeReference = sourceReference.AsPrimitiveOrNull(); + IEdmPrimitiveTypeReference targetPrimitiveTypeReference = targetReference.AsPrimitiveOrNull(); + + if (sourcePrimitiveTypeReference == null || targetPrimitiveTypeReference == null) + { + return false; + } + + return MetadataUtilsCommon.CanConvertPrimitiveTypeTo(sourceNodeOrNull, sourcePrimitiveTypeReference.PrimitiveDefinition(), targetPrimitiveTypeReference.PrimitiveDefinition()); + } + + /// + /// function signatures for temporal + /// + /// temporal function signatures for temporal for add + private static IEnumerable GetAdditionTermporalSignatures() + { + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDateTimeOffset(false), EdmCoreModel.Instance.GetDuration(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDateTimeOffset(true), EdmCoreModel.Instance.GetDuration(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(false), EdmCoreModel.Instance.GetDateTimeOffset(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(true), EdmCoreModel.Instance.GetDateTimeOffset(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(false), EdmCoreModel.Instance.GetDuration(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(true), EdmCoreModel.Instance.GetDuration(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(false), EdmCoreModel.Instance.GetDuration(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + null, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(true), EdmCoreModel.Instance.GetDuration(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + null, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(false), EdmCoreModel.Instance.GetDate(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); }, + null + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(true), EdmCoreModel.Instance.GetDate(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); }, + null + }); + } + + /// + /// function signatures for temporal + /// + /// temporal function signatures for temporal for sub + private static IEnumerable GetSubtractionTermporalSignatures() + { + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDateTimeOffset(false), EdmCoreModel.Instance.GetDuration(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDateTimeOffset(true), EdmCoreModel.Instance.GetDuration(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(false), EdmCoreModel.Instance.GetDuration(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDuration(true), EdmCoreModel.Instance.GetDuration(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDateTimeOffset(false), EdmCoreModel.Instance.GetDateTimeOffset(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, false); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDateTimeOffset(true), EdmCoreModel.Instance.GetDateTimeOffset(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, true); }, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.DateTimeOffset, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(false), EdmCoreModel.Instance.GetDuration(false) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + null, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, false); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(true), EdmCoreModel.Instance.GetDuration(true) }, + new FunctionSignature.CreateArgumentTypeWithFacets[] + { + null, + (int? p, int? s) => { return EdmCoreModel.Instance.GetTemporal(EdmPrimitiveTypeKind.Duration, p, true); } + }); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(false), EdmCoreModel.Instance.GetDate(false) }, null); + yield return new FunctionSignature(new[] { EdmCoreModel.Instance.GetDate(true), EdmCoreModel.Instance.GetDate(true) }, null); + } + + /// + /// Gets the correct set of function signatures for type promotion for a given binary operator. + /// + /// The operator kind to get the signatures for. + /// The set of signatures for the specified . + private static FunctionSignature[] GetFunctionSignatures(BinaryOperatorKind operatorKind) + { + switch (operatorKind) + { + case BinaryOperatorKind.Or: // fall through + case BinaryOperatorKind.And: + return logicalSignatures; + + case BinaryOperatorKind.Equal: // fall through + case BinaryOperatorKind.NotEqual: // fall through + case BinaryOperatorKind.GreaterThan: // fall through + case BinaryOperatorKind.GreaterThanOrEqual: // fall through + case BinaryOperatorKind.LessThan: // fall through + case BinaryOperatorKind.LessThanOrEqual: + return relationalSignatures; + + case BinaryOperatorKind.Add: + return AdditionSignatures; + + case BinaryOperatorKind.Subtract: + return SubtractionSignatures; + + case BinaryOperatorKind.Multiply: // fall through + case BinaryOperatorKind.Divide: // fall through + case BinaryOperatorKind.Modulo: // fall through + return arithmeticSignatures; + + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.TypePromotionUtils_GetFunctionSignatures_Binary_UnreachableCodepath)); + } + } + + /// + /// Gets the correct set of function signatures for type promotion for a given binary operator. + /// + /// The operator kind to get the signatures for. + /// The set of signatures for the specified . + private static FunctionSignature[] GetFunctionSignatures(UnaryOperatorKind operatorKind) + { + switch (operatorKind) + { + case UnaryOperatorKind.Negate: + return negationSignatures; + + case UnaryOperatorKind.Not: + return notSignatures; + default: + throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.TypePromotionUtils_GetFunctionSignatures_Unary_UnreachableCodepath)); + } + } + + /// Finds the best methods for the specified arguments given a candidate method enumeration. + /// The candidate function signatures. + /// The argument nodes, can be new {null,null}. + /// The argument type references to match. + /// The best signature found or null. + /// The number of "best match" methods. + private static int FindBestSignature(FunctionSignature[] signatures, SingleValueNode[] argumentNodes, IEdmTypeReference[] argumentTypes, out FunctionSignature bestMatch) + { + Debug.Assert(signatures != null, "signatures != null"); + Debug.Assert(argumentTypes != null, "argumentTypes != null"); + Debug.Assert(signatures.All(s => s.ArgumentTypes != null && s.ArgumentTypes.All(t => t.IsODataPrimitiveTypeKind())), "All signatures must have only primitive argument types."); + + bestMatch = null; + List applicableSignatures = signatures.Where(signature => IsApplicable(signature, argumentNodes, argumentTypes)).ToList(); + if (applicableSignatures.Count > 1) + { + applicableSignatures = FindBestApplicableSignatures(applicableSignatures, argumentTypes); + } + + int result = applicableSignatures.Count; + + if (result == 1) + { + bestMatch = applicableSignatures[0]; + for (int i = 0; i < argumentTypes.Length; i++) + { + argumentTypes[i] = bestMatch.ArgumentTypes[i]; + } + + return result; + } + + if (result == 2) + { + // We may have the case for operators (which C# doesn't) in which we have a nullable operand + // and a non-nullable operand. We choose to convert the one non-null operand to nullable in that + // case (the binary expression will lift to null). + if (argumentTypes.Length == 2 && + applicableSignatures[0].ArgumentTypes[0].Definition.IsEquivalentTo(applicableSignatures[1].ArgumentTypes[0].Definition) && + applicableSignatures[0].ArgumentTypes[1].Definition.IsEquivalentTo(applicableSignatures[1].ArgumentTypes[1].Definition)) + { + bestMatch = applicableSignatures[0].ArgumentTypes[0].IsNullable + ? applicableSignatures[0] + : applicableSignatures[1]; + argumentTypes[0] = bestMatch.ArgumentTypes[0]; + argumentTypes[1] = bestMatch.ArgumentTypes[1]; + return 1; + } + } + + return result; + } + + /// Checks whether the specified method is applicable given the argument expressions. + /// The candidate function signature to check. + /// The argument nodes, can be new {null,null}. + /// The argument types to match. + /// An applicable function signature if all argument types can be promoted; 'null' otherwise. + private static bool IsApplicable(FunctionSignature signature, SingleValueNode[] argumentNodes, IEdmTypeReference[] argumentTypes) + { + Debug.Assert(signature != null, "signature != null"); + Debug.Assert(argumentTypes != null, "argumentTypes != null"); + + if (signature.ArgumentTypes.Length != argumentTypes.Length) + { + return false; + } + + for (int i = 0; i < argumentTypes.Length; ++i) + { + if (!CanPromoteNodeTo(argumentNodes[i], argumentTypes[i], signature.ArgumentTypes[i])) + { + return false; + } + } + + return true; + } + + /// Promotes the specified expression to the given type if necessary. + /// The actual argument node, may be null. + /// The actual argument type. + /// The required type to promote to. + /// True if the could be promoted; otherwise false. + private static bool CanPromoteNodeTo(SingleValueNode sourceNodeOrNull, IEdmTypeReference sourceType, IEdmTypeReference targetType) + { + Debug.Assert(targetType != null, "targetType != null"); + Debug.Assert(targetType.IsODataPrimitiveTypeKind() || targetType.IsODataEnumTypeKind(), "Type promotion only supported for primitive or enum types."); + + if (sourceType == null) + { + // This indicates that a null literal or an open type has been specified. + // For either case we can promote to the required target type if it is nullable + return targetType.IsNullable; + } + + if (sourceType.IsEquivalentTo(targetType)) + { + return true; + } + + if (CanConvertTo(sourceNodeOrNull, sourceType, targetType)) + { + return true; + } + + // Allow promotion from nullable to non-nullable by directly accessing underlying value. + if (sourceType.IsNullable && targetType.IsODataValueType()) + { + // COMPAT 40: Type promotion in the product allows promotion from a nullable type to arbitrary value types + IEdmTypeReference nonNullableSourceType = sourceType.Definition.ToTypeReference(false); + if (CanConvertTo(sourceNodeOrNull, nonNullableSourceType, targetType)) + { + return true; + } + } + + return false; + } + + /// Finds the best applicable methods from the specified array that match the arguments. + /// The candidate function signatures. + /// The argument types to match. + /// Best applicable methods. + private static List FindBestApplicableSignatures(List signatures, IEdmTypeReference[] argumentTypes) + { + Debug.Assert(signatures != null, "signatures != null"); + + List result = new List(); + foreach (FunctionSignature method in signatures) + { + bool betterThanAllOthers = true; + foreach (FunctionSignature otherMethod in signatures) + { + if (otherMethod != method && MatchesArgumentTypesBetterThan(argumentTypes, otherMethod.ArgumentTypes, method.ArgumentTypes)) + { + betterThanAllOthers = false; + break; + } + } + + if (betterThanAllOthers) + { + result.Add(method); + } + } + + return result; + } + + /// + /// Checks whether the type list has better argument matching against the + /// than the type list. + /// + /// Actual arguments types. + /// First type list to check. + /// Second type list to check. + /// + /// True if has better parameter matching than ; otherwise false. + /// + private static bool MatchesArgumentTypesBetterThan(IEdmTypeReference[] argumentTypes, IEdmTypeReference[] firstCandidate, IEdmTypeReference[] secondCandidate) + { + Debug.Assert(argumentTypes != null, "argumentTypes != null"); + Debug.Assert(firstCandidate != null, "firstCandidate != null"); + Debug.Assert(argumentTypes.Length == firstCandidate.Length, "argumentTypes.Length == firstCandidate.Length"); + Debug.Assert(secondCandidate != null, "secondCandidate != null"); + Debug.Assert(argumentTypes.Length == secondCandidate.Length, "argumentTypes.Length == secondCandidate.Length"); + + bool better = false; + + for (int i = 0; i < argumentTypes.Length; ++i) + { + if (argumentTypes[i] == null) + { + // we don't support typed nulls; instead we have no argument type for null literals. + // since null literals can be converted to any type don't include them in the comparison + // The same is true for open properties. Although we don't know the type of the open property, + // we can promote to suggest what the type is expected to be and it is up to the consumer of + // the semantic AST to ignore or enforce it. + continue; + } + + int c = CompareConversions(argumentTypes[i], firstCandidate[i], secondCandidate[i]); + if (c < 0) + { + return false; + } + else if (c > 0) + { + better = true; + } + } + + return better; + } + + /// Checks which conversion is better. + /// Source type. + /// First candidate type to convert to. + /// Second candidate type to convert to. + /// + /// Return 1 if s -> t1 is a better conversion than s -> t2 + /// Return -1 if s -> t2 is a better conversion than s -> t1 + /// Return 0 if neither conversion is better + /// + private static int CompareConversions(IEdmTypeReference source, IEdmTypeReference targetA, IEdmTypeReference targetB) + { + // If both types are exactly the same, there is no preference. + if (targetA.IsEquivalentTo(targetB)) + { + return 0; + } + + // Look for exact matches. + if (source.IsEquivalentTo(targetA)) + { + return 1; + } + + if (source.IsEquivalentTo(targetB)) + { + return -1; + } + + // If one is compatible and the other is not, choose the compatible type. + bool compatibleT1AndT2 = CanConvertTo(null, targetA, targetB); + bool compatibleT2AndT1 = CanConvertTo(null, targetB, targetA); + if (compatibleT1AndT2 && !compatibleT2AndT1) + { + return 1; + } + + if (compatibleT2AndT1 && !compatibleT1AndT2) + { + return -1; + } + + // Prefer to keep the original nullability. + bool sourceNullable = source.IsNullable; + bool typeNullableA = targetA.IsNullable; + bool typeNullableB = targetB.IsNullable; + if (sourceNullable == typeNullableA && sourceNullable != typeNullableB) + { + return 1; + } + + if (sourceNullable != typeNullableA && sourceNullable == typeNullableB) + { + return -1; + } + + // Prefer signed to unsigned. + if (IsSignedIntegralType(targetA) && IsUnsignedIntegralType(targetB)) + { + return 1; + } + + if (IsSignedIntegralType(targetB) && IsUnsignedIntegralType(targetA)) + { + return -1; + } + + // If both decimal and double are possible or if both decimal and single are possible, then single/double is prefered (int32->long->single->double->decimal). + if (IsDecimalType(targetA) && IsDoubleOrSingle(targetB)) + { + return -1; + } + + if (IsDecimalType(targetB) && IsDoubleOrSingle(targetA)) + { + return 1; + } + + // If both DateTimeOffset and Date are possible, then DateTimeOffset is perfered, as to keep previous behaviour. + if (IsDateTimeOffset(targetA) && IsDate(targetB)) + { + return 1; + } + + if (IsDateTimeOffset(targetB) && IsDate(targetA)) + { + return -1; + } + + return 0; + } + + /// + /// Tries to handle the special eq and ne operators, which have a broader definition than the other binary operators. + /// We try a few special cases and return true if we used one of them. Otherwise we return false, and + /// allow the regular function matching code to handle the primitive cases. + /// + /// Left type. + /// Right type. + /// True if this function was able to handle the promotion of these types, false otherwise. + private static bool TryHandleEqualityOperatorForEntityOrComplexTypes(ref IEdmTypeReference left, ref IEdmTypeReference right) + { + if (left != null && left.IsStructured()) + { + // When one is null and the other isn't, we use the other's type for the null one + if (right == null) + { + right = left; + return true; + } + + // When one is structured but the other primitive, there is no match + if (!right.IsStructured()) + { + return false; + } + + // If they are the same type but have different nullability, we need to choose the nullable one for both + if (left.Definition.IsEquivalentTo(right.Definition)) + { + if (left.IsNullable && !right.IsNullable) + { + right = left; + } + else + { + left = right; + } + + return true; + } + + // I think we should just use IsAssignableFrom instead now + if (CanConvertTo(null, left, right)) + { + left = right; + return true; + } + + if (CanConvertTo(null, right, left)) + { + right = left; + return true; + } + + return false; + } + + // Left was null or primitive + if (right != null && (right.IsStructured())) + { + if (left == null) + { + left = right; + return true; + } + + return false; + } + + return false; + } + + /// Checks whether the specified type is a signed integral type. + /// Type reference to check. + /// true if is a signed integral type; false otherwise. + private static bool IsSignedIntegralType(IEdmTypeReference typeReference) + { + return GetNumericTypeKind(typeReference) == NumericTypeKind.SignedIntegral; + } + + /// Checks whether the specified type is an unsigned integral type. + /// Type to check. + /// true if is an unsigned integral type; false otherwise. + private static bool IsUnsignedIntegralType(IEdmTypeReference typeReference) + { + return GetNumericTypeKind(typeReference) == NumericTypeKind.UnsignedIntegral; + } + + /// Checks if the specified type is a Date or nullable Date type. + /// Type to check. + /// true if is either Date or nullable Date type; false otherwise. + private static bool IsDate(IEdmTypeReference typeReference) + { + IEdmPrimitiveTypeReference primitiveTypeReference = typeReference.AsPrimitiveOrNull(); + if (primitiveTypeReference != null) + { + return primitiveTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.Date; + } + + return false; + } + + /// Checks if the specified type is a DateTimeOffset or nullable DateTimeOffset type. + /// Type to check. + /// true if is either DateTimeOffset or nullable DateTimeOffset type; false otherwise. + private static bool IsDateTimeOffset(IEdmTypeReference typeReference) + { + IEdmPrimitiveTypeReference primitiveTypeReference = typeReference.AsPrimitiveOrNull(); + if (primitiveTypeReference != null) + { + return primitiveTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.DateTimeOffset; + } + + return false; + } + + /// Checks if the specified type is a decimal or nullable decimal type. + /// Type to check. + /// true if is either decimal or nullable decimal type; false otherwise. + private static bool IsDecimalType(IEdmTypeReference typeReference) + { + IEdmPrimitiveTypeReference primitiveTypeReference = typeReference.AsPrimitiveOrNull(); + if (primitiveTypeReference != null) + { + return primitiveTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.Decimal; + } + + return false; + } + + /// Checks if the specified type is either double or single or the nullable variants. + /// Type to check. + /// true if is double, single or nullable double or single; false otherwise. + private static bool IsDoubleOrSingle(IEdmTypeReference typeReference) + { + IEdmPrimitiveTypeReference primitiveTypeReference = typeReference.AsPrimitiveOrNull(); + if (primitiveTypeReference != null) + { + EdmPrimitiveTypeKind primitiveTypeKind = primitiveTypeReference.PrimitiveKind(); + return primitiveTypeKind == EdmPrimitiveTypeKind.Double || primitiveTypeKind == EdmPrimitiveTypeKind.Single; + } + + return false; + } + + /// Gets a flag for the numeric kind of type. + /// Type to get numeric kind for. + /// The of the argument. + private static NumericTypeKind GetNumericTypeKind(IEdmTypeReference typeReference) + { + IEdmPrimitiveTypeReference primitiveTypeReference = typeReference.AsPrimitiveOrNull(); + + if (primitiveTypeReference == null) + { + return NumericTypeKind.NotNumeric; + } + + switch (primitiveTypeReference.PrimitiveDefinition().PrimitiveKind) + { + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Decimal: + case EdmPrimitiveTypeKind.Double: + return NumericTypeKind.NotIntegral; + + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + return NumericTypeKind.SignedIntegral; + + case EdmPrimitiveTypeKind.Byte: + return NumericTypeKind.UnsignedIntegral; + + default: + return NumericTypeKind.NotNumeric; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs new file mode 100644 index 0000000..e5f4194 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriEdmHelpers.cs @@ -0,0 +1,186 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Class to provide methods that wrap EdmLib calls that are normally not allows in ODataLib, but + /// are OK in the Uri Parser. These are OK to suppress because the Uri Parser + /// does not need to go through the behavior knob that the ODL reader/writer does. + /// This should only be used by the Uri Parser. + /// + internal static class UriEdmHelpers + { + /// + /// Wraps a call to IEdmModel.FindType. + /// + /// The model to search. + /// The qualified name of the type to find within the model. + /// Resolver for this func. + /// The requested type, or null if no such type exists. + public static IEdmSchemaType FindTypeFromModel(IEdmModel model, string qualifiedName, ODataUriResolver resolver) + { + return resolver.ResolveType(model, qualifiedName); + } + + /// + /// Wraps call to FindTypeFromModel for an Enum type. + /// + /// the model to search + /// the name to find within the model + /// a type reference to the enum type, or null if no such type exists. + public static IEdmEnumType FindEnumTypeFromModel(IEdmModel model, string qualifiedName) + { + IEdmEnumType enumType = FindTypeFromModel(model, qualifiedName, ODataUriResolver.GetUriResolver(null)) as IEdmEnumType; + return enumType; + } + + /// + /// Check whether the parent and child are properly related types + /// + /// the parent type + /// the child type + /// Throws if the two types are not related. + public static void CheckRelatedTo(IEdmType parentType, IEdmType childType) + { + if (!IsRelatedTo(parentType, childType)) + { + // If the parentType is an open property, parentType will be null and can't have an ODataFullName. + string parentTypeName = (parentType != null) ? parentType.FullTypeName() : ""; + throw new ODataException(Strings.MetadataBinder_HierarchyNotFollowed(childType.FullTypeName(), parentTypeName)); + } + } + + /// + /// Check whether the two are properly related types + /// + /// the first type + /// the second type + /// Whether the two types are related. + public static bool IsRelatedTo(IEdmType first, IEdmType second) + { + return second.IsOrInheritsFrom(first) || first.IsOrInheritsFrom(second); + } + + /// + /// Follow an ODataPath from an Expand to get the Final Nav Prop + /// + /// the path to follow + /// the navigation property at the end of that path. + /// Throws if the last segment in the path is not a nav prop. + public static IEdmNavigationProperty GetNavigationPropertyFromExpandPath(ODataPath path) + { + NavigationPropertySegment navPropSegment = null; + foreach (ODataPathSegment currentSegment in path) + { + TypeSegment typeSegment = currentSegment as TypeSegment; + navPropSegment = currentSegment as NavigationPropertySegment; + if (typeSegment == null && navPropSegment == null) + { + throw new ODataException(Strings.ExpandItemBinder_TypeSegmentNotFollowedByPath); + } + } + + if (navPropSegment == null) + { + throw new ODataException(Strings.ExpandItemBinder_TypeSegmentNotFollowedByPath); + } + else + { + return navPropSegment.NavigationProperty; + } + } + + /// + /// Follow an ODataPath from to get the most derived type + /// + /// the path to follow + /// the starting type before beginning to walk the path. + /// the most derived type in the path. + public static IEdmType GetMostDerivedTypeFromPath(ODataPath path, IEdmType startingType) + { + IEdmType currentType = startingType; + foreach (ODataPathSegment currentSegment in path) + { + TypeSegment typeSegment = currentSegment as TypeSegment; + if (typeSegment != null && typeSegment.EdmType.IsOrInheritsFrom(currentType)) + { + currentType = typeSegment.EdmType; + } + } + + return currentType; + } + + /// + /// Returns true if this type is a structured type collection + /// + /// The type to check + /// true if the type is a structured type collection + public static bool IsStructuredCollection(this IEdmTypeReference type) + { + ExceptionUtils.CheckArgumentNotNull(type, "type"); + IEdmCollectionTypeReference collectionType = type as IEdmCollectionTypeReference; + if (collectionType != null) + { + return collectionType.ElementType().IsStructured(); + } + else + { + return false; + } + } + + /// + /// Returns the type reference of the + /// + /// The structured type + /// The type reference + public static IEdmStructuredTypeReference GetTypeReference(this IEdmStructuredType structuredType) + { + IEdmEntityType entityType = structuredType as IEdmEntityType; + IEdmStructuredTypeReference typeReference; + + if (entityType != null) + { + typeReference = new EdmEntityTypeReference(entityType, false); + } + else + { + typeReference = new EdmComplexTypeReference(structuredType as IEdmComplexType, false); + } + + return typeReference; + } + + /// + /// Is this a valid binding type. i.e. is this an entity, entity collection, or complex type. + /// + /// the binding type + /// true if this binding type is valid + public static bool IsBindingTypeValid(IEdmType bindingType) + { + return bindingType == null || bindingType.IsEntityOrEntityCollectionType() || bindingType.IsODataComplexTypeKind(); + } + + /// + /// Wraps call to FindTypeFromModel for an Enum type. + /// + /// the model to search + /// the name to find within the model + /// ODataUriResolver + /// a type reference to the enum type, or null if no such type exists. + internal static IEdmEnumType FindEnumTypeFromModel(IEdmModel model, string qualifiedName, ODataUriResolver resolver) + { + IEdmEnumType enumType = FindTypeFromModel(model, qualifiedName, resolver ?? ODataUriResolver.GetUriResolver(null)) as IEdmEnumType; + return enumType; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriFunctionsHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriFunctionsHelper.cs new file mode 100644 index 0000000..3810506 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriFunctionsHelper.cs @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region NameSpaces + + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + + #endregion + + internal static class UriFunctionsHelper + { + /// Builds a description of a list of function signatures. + /// Function name. + /// Function signatures. + /// A string with ';'-separated list of function signatures. + public static string BuildFunctionSignatureListDescription(string name, IEnumerable signatures) + { + Debug.Assert(name != null, "name != null"); + Debug.Assert(signatures != null, "signatures != null"); + + StringBuilder builder = new StringBuilder(); + string descriptionSeparator = ""; + foreach (FunctionSignatureWithReturnType signature in signatures) + { + builder.Append(descriptionSeparator); + descriptionSeparator = "; "; + + string parameterSeparator = ""; + builder.Append(name); + builder.Append('('); + foreach (IEdmTypeReference type in signature.ArgumentTypes) + { + builder.Append(parameterSeparator); + parameterSeparator = ", "; + + if (type.IsODataPrimitiveTypeKind() && type.IsNullable) + { + builder.Append(type.FullName()); + builder.Append(" Nullable=true"); + } + else + { + builder.Append(type.FullName()); + } + } + + builder.Append(')'); + } + + return builder.ToString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriQueryConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriQueryConstants.cs new file mode 100644 index 0000000..9ccccca --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/UriQueryConstants.cs @@ -0,0 +1,98 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + /// + /// Constant values related to the URI query syntax. + /// + internal static class UriQueryConstants + { + /// A segment name in a URI that indicates metadata is being requested. + internal const string MetadataSegment = "$metadata"; + + /// A segment name in a URI that indicates a plain primitive value is being requested. + internal const string ValueSegment = "$value"; + + /// A segment name in a URI that indicates batch is being requested. + internal const string BatchSegment = "$batch"; + + /// A segment name in a URI that indicates that this is an entity reference link operation. + internal const string RefSegment = "$ref"; + + /// A segment name in a URI that indicates that this is a count operation. + internal const string CountSegment = "$count"; + + /// A segment name in a URI that indicates that a filter is being applied to a collection. + internal const string FilterSegment = "$filter"; + + /// A segment name in a URI that indicates that a set-based operation is being applied to a collection. + internal const string EachSegment = "$each"; + + /// A filter query option name. + internal const string FilterQueryOption = "$filter"; + + /// An apply query option name. + internal const string ApplyQueryOption = "$apply"; + + /// An aggregate transformation + internal const string AggregateTransformation = "aggregate"; + + /// A group-by transformation + internal const string GroupbyTransformation = "groupby"; + + /// A filter transformation + internal const string FilterTransformation = "filter"; + + /// An order by query option name. + internal const string OrderByQueryOption = "$orderby"; + + /// A select query option name. + internal const string SelectQueryOption = "$select"; + + /// An expand query option name. + internal const string ExpandQueryOption = "$expand"; + + /// A skip query option name. + internal const string SkipQueryOption = "$skip"; + + /// A skip token query option name. + internal const string SkipTokenQueryOption = "$skiptoken"; + + /// A delta token query option name. + internal const string DeltaTokenQueryOption = "$deltatoken"; + + /// An entity id query option name. + internal const string IdQueryOption = "$id"; + + /// A valid value to denote all-properties access. + internal const string Star = "*"; + + /// A top query option name. + internal const string TopQueryOption = "$top"; + + /// A count query option name. + internal const string CountQueryOption = "$count"; + + /// An index query option name. + internal const string IndexQueryOption = "$index"; + + /// A format query option name. + internal const string FormatQueryOption = "$format"; + + /// A search query option name. + internal const string SearchQueryOption = "$search"; + + /// Dollar sign. + internal const string DollarSign = "$"; + + /// A compute query option name. + internal const string ComputeQueryOption = "$compute"; + + /// An annotation prefix in a URL. + internal const char AnnotationPrefix = '@'; + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IPathSegmentTokenVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IPathSegmentTokenVisitor.cs new file mode 100644 index 0000000..bea536a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IPathSegmentTokenVisitor.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + /// + /// Visitor interface for walking the Path Tree. + /// + /// Return type for the visitor methods on this visitor. + public interface IPathSegmentTokenVisitor + { + /// + /// Visit an SystemToken + /// + /// The SystemToken to visit + /// A user defined class + T Visit(SystemToken tokenIn); + + /// + /// Visit an NonSystemToken + /// + /// The NonSystemToken to visit + /// A user defined class + T Visit(NonSystemToken tokenIn); + } + + /// + /// Visitor interface for walking the Path Tree. + /// + public interface IPathSegmentTokenVisitor + { + /// + /// Visit an SystemToken + /// + /// The SystemToken to visit + void Visit(SystemToken tokenIn); + + /// + /// Visit an NonSystemToken + /// + /// The NonSystemToken to visit + void Visit(NonSystemToken tokenIn); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs new file mode 100644 index 0000000..4417031 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs @@ -0,0 +1,189 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using Microsoft.OData.UriParser.Aggregation; + + /// + /// Visitor interface for walking the Syntactic Tree. + /// + /// Return type for the visitor methods on this visitor. + public interface ISyntacticTreeVisitor + { + /// + /// Visit an AllToken + /// + /// The All token to visit + /// An AllNode bound to this token + T Visit(AllToken tokenIn); + + /// + /// Visits an AnyToken + /// + /// The Any token to visit + /// An AnyNode that's bound to this token + T Visit(AnyToken tokenIn); + + /// + /// Visits a BinaryOperatorToken + /// + /// The Binary operator token to visit. + /// A BinaryOperatorNode thats bound to this token + T Visit(BinaryOperatorToken tokenIn); + + /// + /// Visits an InToken + /// + /// The In token to visit. + /// An InNode thats bound to this token + T Visit(InToken tokenIn); + + /// + /// Visits a DottedIdentifierToken + /// + /// The DottedIdentifierToken to visit + /// Either a SingleResourceCastNode, or CollectionResourceCastNode bound to this DottedIdentifierToken + T Visit(DottedIdentifierToken tokenIn); + + /// + /// Visits an ExpandToken + /// + /// The ExpandToken to visit + /// A QueryNode bound to this ExpandToken + T Visit(ExpandToken tokenIn); + + /// + /// Visits an ExpandTermToken + /// + /// The ExpandTermToken to visit + /// A QueryNode bound to this ExpandTermToken + T Visit(ExpandTermToken tokenIn); + + /// + /// Visits a FunctionCallToken + /// + /// The FunctionCallToken to visit + /// A SingleValueFunctionCallNode bound to this FunctionCallToken + T Visit(FunctionCallToken tokenIn); + + /// + /// Visits a LambdaToken + /// + /// The LambdaToken to visit + /// A LambdaNode bound to this LambdaToken + T Visit(LambdaToken tokenIn); + + /// + /// Visits a LiteralToken + /// + /// LiteralToken to visit + /// A ConstantNode bound to this LiteralToken + T Visit(LiteralToken tokenIn); + + /// + /// Visits a InnerPathToken + /// + /// The InnerPathToken to bind + /// A SingleValueNode or SingleEntityNode bound to this InnerPathToken + T Visit(InnerPathToken tokenIn); + + /// + /// Visits an OrderByToken + /// + /// The OrderByToken to bind + /// An OrderByClause bound to this OrderByToken + T Visit(OrderByToken tokenIn); + + /// + /// Visits a EndPathToken + /// + /// The EndPathToken to bind + /// A PropertyAccessNode bound to this EndPathToken + T Visit(EndPathToken tokenIn); + + /// + /// Visits a CustomQueryOptionToken + /// + /// The CustomQueryOptionToken to bind + /// A CustomQueryOptionNode bound to this CustomQueryOptionToken + T Visit(CustomQueryOptionToken tokenIn); + + /// + /// Visits a RangeVariableToken + /// + /// The RangeVariableToken to bind + /// A Resource or NonResource RangeVariableReferenceNode bound to this RangeVariableToken + T Visit(RangeVariableToken tokenIn); + + /// + /// Visits a SelectToken + /// + /// The SelectToken to bind + /// A QueryNode bound to this SelectToken + T Visit(SelectToken tokenIn); + + /// + /// Visits an SelectTermToken + /// + /// The SelectTermToken to visit + /// A QueryNode bound to this SelectTermToken + T Visit(SelectTermToken tokenIn); + + /// + /// Visits a StarToken + /// + /// The StarToken to bind + /// A QueryNode bound to this StarToken + T Visit(StarToken tokenIn); + + /// + /// Visits a UnaryOperatorToken + /// + /// The UnaryOperatorToken to bind + /// A UnaryOperatorNode bound to this UnaryOperatorToken + T Visit(UnaryOperatorToken tokenIn); + + /// + /// Visits a FunctionParameterToken + /// + /// The FunctionParameterTokenb to bind + /// A FunctionParametertoken bound to this UnaryOperatorToken + T Visit(FunctionParameterToken tokenIn); + + /// + /// Visits a AggregateToken + /// + /// The AggregateToken to bind + /// A T node bound to this AggregateToken + T Visit(AggregateToken tokenIn); + + /// + /// Visits a AggregateExpressionToken + /// + /// The AggregateExpressionToken to bind + /// A T node bound to this AggregateExpressionToken + T Visit(AggregateExpressionToken tokenIn); + + /// + /// Visits a EntitySetAggregateToken + /// + /// The EntitySetAggregateToken to bind + /// A T node bound to this EntitySetAggregateToken + T Visit(EntitySetAggregateToken tokenIn); + + /// + /// Visits a GroupByToken + /// + /// The GroupByToken to bind + /// A T node bound to this GroupByToken + T Visit(GroupByToken tokenIn); + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IsCollectionTranslator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IsCollectionTranslator.cs new file mode 100644 index 0000000..ed14ffe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/IsCollectionTranslator.cs @@ -0,0 +1,209 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using Microsoft.OData.Edm; + + /// + /// Segment translator to determine whether a given is a collection. + /// + internal sealed class IsCollectionTranslator : PathSegmentTranslator + { + /// + /// Translate a NavigationPropertySegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(NavigationPropertySegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return segment.NavigationProperty.Type.IsCollection(); + } + + /// + /// Translate an EntitySetSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(EntitySetSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return true; + } + + /// + /// Translate a KeySegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(KeySegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a PropertySegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(PropertySegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate an AnnotationSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(AnnotationSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate an OpenPropertySegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(DynamicPathSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a CountSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(CountSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a FilterSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(FilterSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return true; + } + + /// + /// Translate a ReferenceSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(ReferenceSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return !segment.SingleResult; + } + + /// + /// Translate an EachSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(EachSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a NavigationPropertyLinkSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(NavigationPropertyLinkSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a BatchSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(BatchSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a BatchReferenceSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(BatchReferenceSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a ValueSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(ValueSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + throw new NotImplementedException(segment.ToString()); + } + + /// + /// Translate a MetadataSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(MetadataSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return false; + } + + /// + /// Translate a PathTemplateSegment + /// + /// the segment to Translate + /// UserDefinedValue + /// Throws if the input segment is null. + public override bool Translate(PathTemplateSegment segment) + { + ExceptionUtils.CheckArgumentNotNull(segment, "segment"); + return !segment.SingleResult; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentHandler.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentHandler.cs new file mode 100644 index 0000000..1ec3b88 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentHandler.cs @@ -0,0 +1,205 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Handler interface for walking the path semantic tree. + /// + public abstract class PathSegmentHandler + { + /// + /// Handle a ODataPathSegment + /// + /// the segment to Handle + public virtual void Handle(ODataPathSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a TypeSegment + /// + /// the segment to Handle + public virtual void Handle(TypeSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a NavigationPropertySegment + /// + /// the segment to Handle + public virtual void Handle(NavigationPropertySegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle an EntitySetSegment + /// + /// the segment to Handle + public virtual void Handle(EntitySetSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle an SingletonSegment + /// + /// the segment to Handle + public virtual void Handle(SingletonSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a KeySegment + /// + /// the segment to Handle + public virtual void Handle(KeySegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a PropertySegment + /// + /// the segment to Handle + public virtual void Handle(PropertySegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle an AnnotationSegment + /// + /// the segment to Handle + public virtual void Handle(AnnotationSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle an OperationSegment + /// + /// the segment to Handle + public virtual void Handle(OperationImportSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle an OperationSegment + /// + /// the segment to Handle + public virtual void Handle(OperationSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle an OpenPropertySegment + /// + /// the segment to Handle + public virtual void Handle(DynamicPathSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a CountSegment + /// + /// the segment to Handle + public virtual void Handle(CountSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a FilterSegment + /// + /// the segment to Handle + public virtual void Handle(FilterSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a ReferenceSegment + /// + /// the segment to Handle + public virtual void Handle(ReferenceSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle an EachSegment + /// + /// the segment to Handle + public virtual void Handle(EachSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a LinksSegment + /// + /// the segment to Handle + public virtual void Handle(NavigationPropertyLinkSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a ValueSegment + /// + /// the segment to Handle + public virtual void Handle(ValueSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a BatchSegment + /// + /// the segment to Handle + public virtual void Handle(BatchSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a BatchReferenceSegment + /// + /// the segment to Handle + public virtual void Handle(BatchReferenceSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a MetadataSegment + /// + /// the segment to Handle + public virtual void Handle(MetadataSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Handle a PathTemplateSegment + /// + /// the segment to Handle + public virtual void Handle(PathTemplateSegment segment) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToContextUrlPathTranslator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToContextUrlPathTranslator.cs new file mode 100644 index 0000000..36100a9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToContextUrlPathTranslator.cs @@ -0,0 +1,275 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Text; + using Microsoft.OData.Evaluation; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + #endregion Namespaces + + /// + /// Translator to translate context url path segments to strings. + /// + internal sealed class PathSegmentToContextUrlPathTranslator : PathSegmentTranslator + { + /// + /// Default instance of the translator. + /// + internal static readonly PathSegmentToContextUrlPathTranslator DefaultInstance = new PathSegmentToContextUrlPathTranslator(false); + + /// + /// Instance of the translator that use key as segment in path. + /// + internal static readonly PathSegmentToContextUrlPathTranslator KeyAsSegmentInstance = new PathSegmentToContextUrlPathTranslator(true); + + /// + /// Key Serializer for KeySegment + /// + private KeySerializer KeySerializer; + + /// + /// Private constructor since the singleton instance is sufficient. + /// + /// Whether use key as segment + private PathSegmentToContextUrlPathTranslator(bool keyAsSegment) + { + this.KeySerializer = KeySerializer.Create(keyAsSegment); + } + + /// + /// Translate a TypeSegment + /// + /// the segment to Translate + /// Defined by the implementer + public override string Translate(TypeSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + IEdmType type = segment.EdmType; + IEdmCollectionType collectionType = type as IEdmCollectionType; + + if (collectionType != null) + { + type = collectionType.ElementType.Definition; + } + + return "/" + type.FullTypeName(); + } + + /// + /// Translate a NavigationPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(NavigationPropertySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.NavigationProperty.Name; + } + + /// + /// Translate an EntitySetSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(EntitySetSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.EntitySet.Name; + } + + /// + /// Translate an SingletonSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(SingletonSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Singleton.Name; + } + + /// + /// Translate a KeySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(KeySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + List> keys = segment.Keys.ToList(); + + StringBuilder builder = new StringBuilder(); + KeySerializer.AppendKeyExpression(builder, new Collection>(keys), p => p.Key, p => p.Value); + + return builder.ToString(); + } + + /// + /// Translate a PropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(PropertySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Property.Name; + } + + /// + /// Translate an AnnotationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(AnnotationSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Term.FullName(); + } + + /// + /// Translate an OperationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(OperationSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Operations.OperationGroupFullName(); + } + + /// + /// Translate an OperationImportSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(OperationImportSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return string.Empty; + } + + /// + /// Translate an OpenPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(DynamicPathSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Translate a CountSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(CountSegment segment) + { + return string.Empty; + } + + /// + /// Translate a FilterSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(FilterSegment segment) + { + return string.Empty; + } + + /// + /// Translate a ReferenceSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(ReferenceSegment segment) + { + // How a ReferenceSegment should be addressed in the context URI is still a pending decision. + // https://github.com/OData/odata.net/issues/1292 + return string.Empty; + } + + /// + /// Translate an EachSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(EachSegment segment) + { + return string.Empty; + } + + /// + /// Visit a NavigationPropertyLinkSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(NavigationPropertyLinkSegment segment) + { + return string.Empty; + } + + /// + /// Translate a ValueSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(ValueSegment segment) + { + return string.Empty; + } + + /// + /// Translate a BatchSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(BatchSegment segment) + { + return string.Empty; + } + + /// + /// Translate a BatchReferenceSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(BatchReferenceSegment segment) + { + return string.Empty; + } + + /// + /// Translate a MetadataSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(MetadataSegment segment) + { + return string.Empty; + } + + /// + /// Translate a PathTemplateSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(PathTemplateSegment segment) + { + return "/" + segment.Identifier; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToResourcePathTranslator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToResourcePathTranslator.cs new file mode 100644 index 0000000..e641473 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToResourcePathTranslator.cs @@ -0,0 +1,289 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Microsoft.OData.Evaluation; +using Microsoft.OData.Metadata; +using Microsoft.OData.Edm; + +namespace Microsoft.OData.UriParser +{ + /// + /// Translator to translate query url path segments to strings. + /// + internal sealed class PathSegmentToResourcePathTranslator : PathSegmentTranslator + { + /// + /// Key Serializer for KeySegment + /// + private KeySerializer KeySerializer; + + /// + /// Private constructor since the singleton instance is sufficient. + /// + /// Key delimiter used in url. + public PathSegmentToResourcePathTranslator(ODataUrlKeyDelimiter odataUrlKeyDelimiter) + { + Debug.Assert(odataUrlKeyDelimiter != null, "odataUrlKeyDelimiter != null"); + this.KeySerializer = KeySerializer.Create(odataUrlKeyDelimiter.EnableKeyAsSegment); + } + + /// + /// Translate a TypeSegment + /// + /// the segment to Translate + /// Defined by the implementer + public override string Translate(TypeSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + IEdmType type = segment.EdmType; + IEdmCollectionType collectionType = type as IEdmCollectionType; + + if (collectionType != null) + { + type = collectionType.ElementType.Definition; + } + + return "/" + type.FullTypeName(); + } + + /// + /// Translate a NavigationPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(NavigationPropertySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.NavigationProperty.Name; + } + + /// + /// Translate an EntitySetSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(EntitySetSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.EntitySet.Name; + } + + /// + /// Translate an SingletonSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(SingletonSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Singleton.Name; + } + + /// + /// Translate a KeySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(KeySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + List> keys = segment.Keys.ToList(); + + StringBuilder builder = new StringBuilder(); + this.KeySerializer.AppendKeyExpression(builder, new Collection>(keys), p => p.Key, p => p.Value); + + return builder.ToString(); + } + + /// + /// Translate a PropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(PropertySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Property.Name; + } + + /// + /// Translate an AnnotationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(AnnotationSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Term.FullName(); + } + + /// + /// Translate an OperationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(OperationSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + string res = null; + + NodeToStringBuilder nodeToStringBuilder = new NodeToStringBuilder(); + foreach (OperationSegmentParameter operationSegmentParameter in segment.Parameters) + { + string tmp = nodeToStringBuilder.TranslateNode((QueryNode)operationSegmentParameter.Value); + res = String.Concat(res, String.IsNullOrEmpty(res) ? null : ExpressionConstants.SymbolComma, operationSegmentParameter.Name, ExpressionConstants.SymbolEqual, tmp); + } + + return String.IsNullOrEmpty(res) ? String.Concat("/", segment.Operations.OperationGroupFullName()) : String.Concat("/", segment.Operations.OperationGroupFullName(), ExpressionConstants.SymbolOpenParen, res, ExpressionConstants.SymbolClosedParen); + } + + /// + /// Translate an OperationImportSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(OperationImportSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + NodeToStringBuilder nodeToStringBuilder = new NodeToStringBuilder(); + string res = null; + foreach (OperationSegmentParameter operationSegmentParameter in segment.Parameters) + { + string tmp = nodeToStringBuilder.TranslateNode((QueryNode)operationSegmentParameter.Value); + res = String.Concat(res, String.IsNullOrEmpty(res) ? null : ExpressionConstants.SymbolComma, operationSegmentParameter.Name, ExpressionConstants.SymbolEqual, tmp); + } + + return String.IsNullOrEmpty(res) ? String.Concat("/", segment.Identifier) : String.Concat("/", segment.Identifier, ExpressionConstants.SymbolOpenParen, res, ExpressionConstants.SymbolClosedParen); + } + + /// + /// Translate an OpenPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(DynamicPathSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Translate a CountSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(CountSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Translate a FilterSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(FilterSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.LiteralText; + } + + /// + /// Translate a ReferenceSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(ReferenceSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Translate an EachSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(EachSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Visit a NavigationPropertyLinkSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(NavigationPropertyLinkSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return String.Concat("/", segment.NavigationProperty.Name, "/", UriQueryConstants.RefSegment); + } + + /// + /// Translate a ValueSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(ValueSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Translate a BatchSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(BatchSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Translate a BatchReferenceSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(BatchReferenceSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.ContentId; + } + + /// + /// Translate a MetadataSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(MetadataSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + + /// + /// Translate a PathTemplateSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(PathTemplateSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return "/" + segment.Identifier; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToStringTranslator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToStringTranslator.cs new file mode 100644 index 0000000..f98bfe0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentToStringTranslator.cs @@ -0,0 +1,152 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + using System.Diagnostics; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + + /// + /// Translator to translate segments to strings. + /// + internal sealed class PathSegmentToStringTranslator : PathSegmentTranslator + { + /// + /// Singleton instance of the translator. + /// + internal static readonly PathSegmentToStringTranslator Instance = new PathSegmentToStringTranslator(); + + /// + /// Private constructor since the singleton instance is sufficient. + /// + private PathSegmentToStringTranslator() + { + } + + /// + /// Translate a TypeSegment + /// + /// the segment to Translate + /// Defined by the implementer + public override string Translate(TypeSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.EdmType.FullTypeName(); + } + + /// + /// Translate a NavigationPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(NavigationPropertySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.NavigationProperty.Name; + } + + /// + /// Translate a PropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(PropertySegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.Property.Name; + } + + /// + /// Translate an AnnotationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(AnnotationSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.Term.FullName(); + } + + /// + /// Translate an OperationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(OperationSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.Operations.OperationGroupFullName(); + } + + /// + /// Translate an OperationImportSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(OperationImportSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.OperationImports.OperationImportGroupFullName(); + } + + /// + /// Translate an OpenPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(DynamicPathSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.Identifier; + } + + /// + /// Translate a FilterSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(FilterSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.LiteralText; + } + + /// + /// Translate a ReferenceSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(ReferenceSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.Identifier; + } + + /// + /// Translate an EachSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(EachSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.Identifier; + } + + /// + /// Translate a PathTemplateSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public override string Translate(PathTemplateSegment segment) + { + Debug.Assert(segment != null, "segment != null"); + return segment.LiteralText; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenEqualityComparer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenEqualityComparer.cs new file mode 100644 index 0000000..540e516 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenEqualityComparer.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + + /// + /// Equality comparer for . + /// + internal sealed class PathSegmentTokenEqualityComparer : EqualityComparer + { + /// + /// Determines whether the two paths are equivalent. + /// + /// The first path to compare. + /// The second path to compare. + /// Whether the two paths are equivalent. + public override bool Equals(PathSegmentToken first, PathSegmentToken second) + { + if (first == null && second == null) + { + return true; + } + + if (first == null || second == null) + { + return false; + } + + return this.ToHashableString(first) == this.ToHashableString(second); + } + + /// + /// Returns a hash code for the given path. + /// + /// The path to hash. + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode(PathSegmentToken path) + { + if (path == null) + { + return 0; + } + + return this.ToHashableString(path).GetHashCode(); + } + + /// + /// Converts the token to a string that is sufficiently unique to be hashed or compared. + /// + /// The path token to convert to a string. + /// A string representing the path. + private string ToHashableString(PathSegmentToken token) + { + if (token.NextToken == null) + { + return token.Identifier; + } + + return token.Identifier + "/" + this.ToHashableString(token.NextToken); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenVisitor.cs new file mode 100644 index 0000000..0bd9117 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTokenVisitor.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Visitor interface for walking the Syntactic Tree. + /// + /// Generic type produced by the visitor. + internal abstract class PathSegmentTokenVisitor : IPathSegmentTokenVisitor + { + /// + /// Visit an SystemToken + /// + /// The System token to visit + /// A user defined class + public virtual T Visit(SystemToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit an NonSystemToken + /// + /// The System token to visit + /// A user defined class + public virtual T Visit(NonSystemToken tokenIn) + { + throw new NotImplementedException(); + } + } + + /// + /// Visitor interface for walking the Syntactic Tree. + /// + internal abstract class PathSegmentTokenVisitor : IPathSegmentTokenVisitor + { + /// + /// Visit an SystemToken + /// + /// The System token to visit + public virtual void Visit(SystemToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit an NonSystemToken + /// + /// The System token to visit + public virtual void Visit(NonSystemToken tokenIn) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTranslator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTranslator.cs new file mode 100644 index 0000000..ce01549 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/PathSegmentTranslator.cs @@ -0,0 +1,217 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Translator interface for walking the Syntactic Tree. + /// + /// Generic type produced by the translator. + public abstract class PathSegmentTranslator + { + /// + /// Translate a TypeSegment + /// + /// the segment to Translate + /// Defined by the implementer + public virtual T Translate(TypeSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a NavigationPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(NavigationPropertySegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate an EntitySetSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(EntitySetSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate an SingletonSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(SingletonSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a KeySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(KeySegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a PropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(PropertySegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate an AnnotationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(AnnotationSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate an OperationImportSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(OperationImportSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate an OperationSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(OperationSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate an OpenPropertySegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(DynamicPathSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a CountSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(CountSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a FilterSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(FilterSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a ReferenceSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(ReferenceSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate an EachSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(EachSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Visit a NavigationPropertyLinkSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(NavigationPropertyLinkSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a ValueSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(ValueSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a BatchSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(BatchSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a BatchReferenceSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(BatchReferenceSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a MetadataSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(MetadataSegment segment) + { + throw new NotImplementedException(); + } + + /// + /// Translate a PathTemplateSegment + /// + /// the segment to Translate + /// Defined by the implementer. + public virtual T Translate(PathTemplateSegment segment) + { + throw new NotImplementedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs new file mode 100644 index 0000000..426b886 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs @@ -0,0 +1,317 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Visitor interface for walking the Semantic Tree. + /// + /// Generic type produced by the visitor. + public abstract class QueryNodeVisitor + { + /// + /// Visit an AllNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(AllNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit an AnyNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(AnyNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a BinaryOperatorNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(BinaryOperatorNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CountNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CountNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionNavigationNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionNavigationNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionPropertyAccessNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionPropertyAccessNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionOpenPropertyAccessNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionOpenPropertyAccessNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a ConstantNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(ConstantNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionConstantNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionConstantNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a ConvertNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(ConvertNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit an CollectionResourceCastNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionResourceCastNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit an ResourceRangeVariableReferenceNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(ResourceRangeVariableReferenceNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a NonEntityRangeVariableNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(NonResourceRangeVariableReferenceNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleResourceCastNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleResourceCastNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleNavigationNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleNavigationNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleResourceFunctionCallNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleResourceFunctionCallNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleValueFunctionCallNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleValueFunctionCallNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionResourceFunctionCallNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionResourceFunctionCallNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionFunctionCallNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionFunctionCallNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleValueOpenPropertyAccessNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleValueOpenPropertyAccessNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleValuePropertyAccessNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleValuePropertyAccessNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a UnaryOperatorNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(UnaryOperatorNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a NamedFunctionParameterNode. + /// + /// The node to visit. + /// Defined by the implementer + public virtual T Visit(NamedFunctionParameterNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a ParameterAliasNode + /// + /// The node to visit + /// The translated expression + public virtual T Visit(ParameterAliasNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SearchTermNode + /// + /// The node to visit + /// The translated expression + public virtual T Visit(SearchTermNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleComplexNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleComplexNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionComplexNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(CollectionComplexNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a SingleValueCastNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(SingleValueCastNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit a CollectionComplexNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(AggregatedCollectionPropertyNode nodeIn) + { + throw new NotImplementedException(); + } + + /// + /// Visit an InNode + /// + /// the node to visit + /// Defined by the implementer + public virtual T Visit(InNode nodeIn) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemHandler.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemHandler.cs new file mode 100644 index 0000000..baa322b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemHandler.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Handler interface for walking a select item tree. + /// + public abstract class SelectItemHandler + { + /// + /// Handle a WildcardSelectItem + /// + /// the item to Handle + public virtual void Handle(WildcardSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Handle a PathSelectItem + /// + /// the item to Handle + public virtual void Handle(PathSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Handle a ContainerQualifiedWildcardSelectItem + /// + /// the item to Handle + public virtual void Handle(NamespaceQualifiedWildcardSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Handle an ExpandedNavigationSelectItem + /// + /// the item to Handle + public virtual void Handle(ExpandedNavigationSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Handle an ExpandedReferenceSelectItem + /// + /// the item to Handle + public virtual void Handle(ExpandedReferenceSelectItem item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemTranslator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemTranslator.cs new file mode 100644 index 0000000..4156ec5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectItemTranslator.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System; + + /// + /// Translator interface for walking a Select Item Tree. + /// + /// Generic type produced by the translator. + public abstract class SelectItemTranslator + { + /// + /// Translate a WildcardSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public virtual T Translate(WildcardSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Translate a PathSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public virtual T Translate(PathSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Translate a ContainerQualifiedWildcardSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public virtual T Translate(NamespaceQualifiedWildcardSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Translate an ExpandedNavigationSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public virtual T Translate(ExpandedNavigationSelectItem item) + { + throw new NotImplementedException(); + } + + /// + /// Translate an ExpandedReferenceSelectItem + /// + /// the item to Translate + /// Defined by the implementer + public virtual T Translate(ExpandedReferenceSelectItem item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectPropertyVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectPropertyVisitor.cs new file mode 100644 index 0000000..cdab301 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SelectPropertyVisitor.cs @@ -0,0 +1,253 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.OData.Edm; +using ODataErrorStrings = Microsoft.OData.Strings; + +namespace Microsoft.OData.UriParser +{ + /// + /// Visit a Select property and use it to decorate a SelectExpand Tree + /// + internal sealed class SelectPropertyVisitor : PathSegmentTokenVisitor + { + /// + /// The model used for binding. + /// + private readonly IEdmModel model; + + /// + /// the maximum recursive depth. + /// + private readonly int maxDepth; + + /// + /// The expand tree to decorate. + /// + private readonly SelectExpandClause expandClauseToDecorate; + + /// + /// The type for this level of the select + /// + private readonly IEdmStructuredType edmType; + + /// + /// Resolver for uri parser. + /// + private readonly ODataUriResolver resolver; + + /// + /// Properties generated in $apply or $compute + /// + private BindingState state; + + /// + /// Build a property visitor to visit the select tree and decorate a SelectExpandClause + /// + /// The model used for binding. + /// The entity type that the $select is being applied to. + /// the maximum recursive depth. + /// The already built expand clause to decorate + /// Resolver for uri parser. + /// The binding state of the visitor. + public SelectPropertyVisitor(IEdmModel model, IEdmStructuredType edmType, int maxDepth, SelectExpandClause expandClauseToDecorate, ODataUriResolver resolver, BindingState state) + { + this.model = model; + this.edmType = edmType; + this.maxDepth = maxDepth; + this.expandClauseToDecorate = expandClauseToDecorate; + this.resolver = resolver; + this.state = state; + } + + /// + /// The expand tree that we're decorating + /// + public SelectExpandClause DecoratedExpandClause + { + get { return this.expandClauseToDecorate; } + } + + /// + /// Visit a System Token + /// + /// the system token to visit + public override void Visit(SystemToken tokenIn) + { + ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); + throw new ODataException(ODataErrorStrings.SelectPropertyVisitor_SystemTokenInSelect(tokenIn.Identifier)); + } + + /// + /// Visit a NonSystemToken + /// + /// the non sytem token to visit + public override void Visit(NonSystemToken tokenIn) + { + ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn"); + + // before looking for type segments or paths, handle both of the wildcard cases. + if (tokenIn.NextToken == null) + { + SelectItem newSelectItem; + if (SelectPathSegmentTokenBinder.TryBindAsWildcard(tokenIn, this.model, out newSelectItem)) + { + this.expandClauseToDecorate.AddToSelectedItems(newSelectItem); + return; + } + } + + this.ProcessTokenAsPath(tokenIn); + } + + /// + /// process a nonsystemtoken as a path, following any type segments if necessary + /// + /// the token to process + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "It makes sense to keep all of this logic in one place")] + private void ProcessTokenAsPath(NonSystemToken tokenIn) + { + Debug.Assert(tokenIn != null, "tokenIn != null"); + + List pathSoFar = new List(); + IEdmStructuredType currentLevelType = this.edmType; + + // first, walk through all type segments in a row, converting them from tokens into segments. + if (tokenIn.IsNamespaceOrContainerQualified() && !UriParserHelper.IsAnnotation(tokenIn.Identifier)) + { + PathSegmentToken firstNonTypeToken; + pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(tokenIn, this.model, this.maxDepth, this.resolver, ref currentLevelType, out firstNonTypeToken)); + Debug.Assert(firstNonTypeToken != null, "Did not get last token."); + tokenIn = firstNonTypeToken as NonSystemToken; + if (tokenIn == null) + { + throw new ODataException(ODataErrorStrings.SelectPropertyVisitor_SystemTokenInSelect(firstNonTypeToken.Identifier)); + } + } + + // next, create a segment for the first non-type segment in the path. + ODataPathSegment lastSegment = SelectPathSegmentTokenBinder.ConvertNonTypeTokenToSegment(tokenIn, this.model, currentLevelType, resolver, this.state); + + // next, create an ODataPath and add the segments to it. + if (lastSegment != null) + { + pathSoFar.Add(lastSegment); + + // try create a complex type property path. + while (true) + { + // no need to go on if the current property is not of complex type or collection of complex type, + // unless the segment is a primitive type cast or a property on an open complex property. + currentLevelType = lastSegment.EdmType as IEdmStructuredType; + IEdmCollectionType collectionType = lastSegment.EdmType as IEdmCollectionType; + IEdmPrimitiveType primitiveType = lastSegment.EdmType as IEdmPrimitiveType; + DynamicPathSegment dynamicPath = lastSegment as DynamicPathSegment; + if ((currentLevelType == null || currentLevelType.TypeKind != EdmTypeKind.Complex) + && (collectionType == null || collectionType.ElementType.TypeKind() != EdmTypeKind.Complex) + && (primitiveType == null || primitiveType.TypeKind != EdmTypeKind.Primitive) + && (dynamicPath == null || tokenIn.NextToken == null)) + { + break; + } + + NonSystemToken nextToken = tokenIn.NextToken as NonSystemToken; + if (nextToken == null) + { + break; + } + + if (UriParserHelper.IsAnnotation(nextToken.Identifier)) + { + lastSegment = SelectPathSegmentTokenBinder.ConvertNonTypeTokenToSegment(nextToken, this.model, + currentLevelType, resolver, null); + } + else if (primitiveType == null && dynamicPath == null) + { + // This means last segment a collection of complex type, + // current segment can only be type cast and cannot be property name. + if (currentLevelType == null) + { + currentLevelType = collectionType.ElementType.Definition as IEdmStructuredType; + } + + // If there is no collection type in the path yet, will try to bind property for the next token + // first try bind the segment as property. + lastSegment = SelectPathSegmentTokenBinder.ConvertNonTypeTokenToSegment(nextToken, this.model, + currentLevelType, resolver, null); + } + else + { + // determine whether we are looking at a type cast or a dynamic path segment. + EdmPrimitiveTypeKind nextTypeKind = EdmCoreModel.Instance.GetPrimitiveTypeKind(nextToken.Identifier); + IEdmPrimitiveType castType = EdmCoreModel.Instance.GetPrimitiveType(nextTypeKind); + if (castType != null) + { + lastSegment = new TypeSegment(castType, castType, null); + } + else if (dynamicPath != null) + { + lastSegment = new DynamicPathSegment(nextToken.Identifier); + } + else + { + throw new ODataException(ODataErrorStrings.SelectBinder_MultiLevelPathInSelect); + } + } + + // then try bind the segment as type cast. + if (lastSegment == null) + { + IEdmStructuredType typeFromNextToken = + UriEdmHelpers.FindTypeFromModel(this.model, nextToken.Identifier, this.resolver) as + IEdmStructuredType; + + if (typeFromNextToken.IsOrInheritsFrom(currentLevelType)) + { + lastSegment = new TypeSegment(typeFromNextToken, /*entitySet*/null); + } + } + + // type cast failed too. + if (lastSegment == null) + { + break; + } + + // try move to and add next path segment. + tokenIn = nextToken; + pathSoFar.Add(lastSegment); + } + } + + ODataSelectPath selectedPath = new ODataSelectPath(pathSoFar); + + var selectionItem = new PathSelectItem(selectedPath); + + // non-navigation cases do not allow further segments in $select. + if (tokenIn.NextToken != null) + { + throw new ODataException(ODataErrorStrings.SelectBinder_MultiLevelPathInSelect); + } + + // if the selected item is a nav prop, then see if its already there before we add it. + NavigationPropertySegment trailingNavPropSegment = selectionItem.SelectedPath.LastSegment as NavigationPropertySegment; + if (trailingNavPropSegment != null) + { + if (this.expandClauseToDecorate.SelectedItems.Any(x => x is PathSelectItem && + ((PathSelectItem)x).SelectedPath.Equals(selectedPath))) + { + return; + } + } + + this.expandClauseToDecorate.AddToSelectedItems(selectionItem); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SplitEndingSegmentOfTypeHandler.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SplitEndingSegmentOfTypeHandler.cs new file mode 100644 index 0000000..0f182cc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SplitEndingSegmentOfTypeHandler.cs @@ -0,0 +1,262 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.UriParser +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Split Last Segment Of Certain Kind, the result of this handler is two part of ODataPath, + /// FirstPart contains original ODataPath with out ending segments of type T, + /// LastPart contains the ending segments of type T. + /// + /// The type of ODataPathSegment to split + internal sealed class SplitEndingSegmentOfTypeHandler : PathSegmentHandler where T : ODataPathSegment + { + /// + /// Queue contianing first part of ODataPath segment + /// + private readonly Queue first; + + /// + /// Queue contianing last part of ODataPath segment + /// + private readonly Queue last; + + /// + /// Default constructor + /// + public SplitEndingSegmentOfTypeHandler() + { + this.first = new Queue(); + this.last = new Queue(); + } + + /// + /// The first part of split ODataPath + /// + public ODataPath FirstPart + { + get + { + return new ODataPath(this.first); + } + } + + /// + /// The last part of split ODataPath + /// + public ODataPath LastPart + { + get + { + return new ODataPath(this.last); + } + } + + /// + /// Handle a TypeSegment + /// + /// the segment to Handle + public override void Handle(TypeSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a NavigationPropertySegment + /// + /// the segment to Handle + public override void Handle(NavigationPropertySegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle an EntitySetSegment + /// + /// the segment to Handle + public override void Handle(EntitySetSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle an SingletonSegment + /// + /// the segment to Handle + public override void Handle(SingletonSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a KeySegment + /// + /// the segment to Handle + public override void Handle(KeySegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a PropertySegment + /// + /// the segment to Handle + public override void Handle(PropertySegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle an AnnotationSegment + /// + /// the segment to Handle + public override void Handle(AnnotationSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle an OperationImportSegment + /// + /// the segment to Handle + public override void Handle(OperationImportSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle an OperationSegment + /// + /// the segment to Handle + public override void Handle(OperationSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle an OpenPropertySegment + /// + /// the segment to Handle + public override void Handle(DynamicPathSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a CountSegment + /// + /// the segment to Handle + public override void Handle(CountSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a FilterSegment + /// + /// the segment to Handle + public override void Handle(FilterSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle an EachSegment + /// + /// the segment to Handle + public override void Handle(EachSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a ReferenceSegment + /// + /// the segment to Handle + public override void Handle(ReferenceSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a PathTemplateSegment + /// + /// the segment to Handle + public override void Handle(PathTemplateSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a LinksSegment + /// + /// the segment to Handle + public override void Handle(NavigationPropertyLinkSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a ValueSegment + /// + /// the segment to Handle + public override void Handle(ValueSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a BatchSegment + /// + /// the segment to Handle + public override void Handle(BatchSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a BatchReferenceSegment + /// + /// the segment to Handle + public override void Handle(BatchReferenceSegment segment) + { + CommonHandler(segment); + } + + /// + /// Handle a MetadataSegment + /// + /// the segment to Handle + public override void Handle(MetadataSegment segment) + { + CommonHandler(segment); + } + + /// + /// Common handler to handle segments + /// + /// The segment to deal with + private void CommonHandler(ODataPathSegment segment) + { + if (segment is T) + { + this.last.Enqueue(segment); + } + else + { + while (last.Any()) + { + this.first.Enqueue(this.last.Dequeue()); + } + + this.first.Enqueue(segment); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs new file mode 100644 index 0000000..a9c092b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs @@ -0,0 +1,282 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_CLIENT +namespace Microsoft.OData.Client.ALinq.UriParser +#else +namespace Microsoft.OData.UriParser +#endif +{ + using System; + using Microsoft.OData.UriParser.Aggregation; + + /// + /// Visitor interface for walking the Syntactic Tree. + /// + /// Generic type produced by the visitor. + internal abstract class SyntacticTreeVisitor : ISyntacticTreeVisitor + { + /// + /// Visit an AllToken + /// + /// The All token to visit + /// An AllNode bound to this token + public virtual T Visit(AllToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an AnyToken + /// + /// The Any token to visit + /// An AnyNode that's bound to this token + public virtual T Visit(AnyToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a BinaryOperatorToken + /// + /// The Binary operator token to visit. + /// A BinaryOperatorNode thats bound to this token + public virtual T Visit(BinaryOperatorToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an InToken + /// + /// The In token to visit. + /// An InNode thats bound to this token + public virtual T Visit(InToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a DottedIdentifierToken + /// + /// The DottedIdentifierToken to visit + /// Either a SingleResourceCastNode, or CollectionResourceCastNode bound to this DottedIdentifierToken + public virtual T Visit(DottedIdentifierToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an ExpandToken + /// + /// The ExpandToken to visit + /// A QueryNode bound to this ExpandToken + public virtual T Visit(ExpandToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an ExpandTermToken + /// + /// The ExpandTermToken to visit + /// A QueryNode bound to this ExpandTermToken + public virtual T Visit(ExpandTermToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a FunctionCallToken + /// + /// The FunctionCallToken to visit + /// A SingleValueFunctionCallNode bound to this FunctionCallToken + public virtual T Visit(FunctionCallToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a LiteralToken + /// + /// The LiteralToken to visit + /// A ConstantNode bound to this LambdaToken + public virtual T Visit(LiteralToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an AggregateToken + /// + /// The AggregateToken to visit + /// A T bound to this AggregateToken + public virtual T Visit(AggregateToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a GroupByToken + /// + /// The GroupByToken to visit + /// A T bound to this GroupByToken + public virtual T Visit(GroupByToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an AggregateExpressionToken + /// + /// The AggregateExpressionToken to visit + /// A T bound to this AggregateExpressionToken + public virtual T Visit(AggregateExpressionToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an EntitySetAggregateToken + /// + /// The EntitySetAggregateToken to visit + /// A T bound to this EntitySetAggregateToken + public virtual T Visit(EntitySetAggregateToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a LambdaToken + /// + /// The LambdaToken to visit + /// A LambdaNode bound to this LambdaToken + public virtual T Visit(LambdaToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a InnerPathToken + /// + /// The InnerPathToken to bind + /// A SingleValueNode or SingleEntityNode bound to this InnerPathToken + public virtual T Visit(InnerPathToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an OrderByToken + /// + /// The OrderByToken to bind + /// An OrderByClause bound to this OrderByToken + public virtual T Visit(OrderByToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an EndPathToken + /// + /// The EndPathToken to bind + /// A PropertyAccessClause bound to this EndPathToken + public virtual T Visit(EndPathToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a CustomQueryOptionToken + /// + /// The CustomQueryOptionToken to bind + /// A CustomQueryOptionNode bound to this CustomQueryOptionToken + public virtual T Visit(CustomQueryOptionToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a RangeVariableToken + /// + /// The RangeVariableToken to bind + /// A Resource or NonResource RangeVariableReferenceNode bound to this RangeVariableToken + public virtual T Visit(RangeVariableToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a SelectToken + /// + /// The SelectToken to bind + /// A QueryNode bound to this SelectToken + public virtual T Visit(SelectToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits an SelectTermToken + /// + /// The SelectTermToken to visit + /// A QueryNode bound to this SelectTermToken + public virtual T Visit(SelectTermToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a StarToken + /// + /// The StarToken to bind + /// A QueryNode bound to this StarToken + public virtual T Visit(StarToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a UnaryOperatorToken + /// + /// The UnaryOperatorToken to bind + /// A UnaryOperatorNode bound to this UnaryOperatorToken + public virtual T Visit(UnaryOperatorToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a FuntionParameterToken + /// + /// The FunctionParameterToken to bind + /// A user defined value + public virtual T Visit(FunctionParameterToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a ComputeToken + /// + /// The ComputeToken to bind + /// A user defined value + public virtual T Visit(ComputeToken tokenIn) + { + throw new NotImplementedException(); + } + + /// + /// Visits a ComputeExpressionToken + /// + /// The ComputeExpressionToken to bind + /// A user defined value + public virtual T Visit(ComputeExpressionToken tokenIn) + { + throw new NotImplementedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriUtils.cs new file mode 100644 index 0000000..1c95665 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/UriUtils.cs @@ -0,0 +1,276 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text.RegularExpressions; +using System.Xml; +using Microsoft.OData.Edm; + +namespace Microsoft.OData +{ + /// + /// Uri utility methods. + /// + internal static class UriUtils + { + /// + /// Returns an absolute URI constructed from the specified base URI and a relative URI + /// + /// The base URI to use. + /// The relative URI to use. + /// The absolute URI as a result of combining the base URI with the relative URI. + internal static Uri UriToAbsoluteUri(Uri baseUri, Uri relativeUri) + { + Debug.Assert(baseUri != null, "baseUri != null"); + Debug.Assert(baseUri.IsAbsoluteUri, "baseUri is not absolute."); + Debug.Assert(relativeUri != null, "relativeUri != null"); + Debug.Assert(!relativeUri.IsAbsoluteUri, "relativeUri is not relative."); + + return new Uri(baseUri, relativeUri); + } + + /// + /// A method to ensure that the original string of a relative URI is escaped. + /// + /// The relative to escape. + /// A relative URI instance with guaranteed escaped original string. + internal static Uri EnsureEscapedRelativeUri(Uri uri) + { + Debug.Assert(uri != null && !uri.IsAbsoluteUri, "uri != null && !uri.IsAbsoluteUri"); + + string escapedRelativeUri = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); + if (string.CompareOrdinal(uri.OriginalString, escapedRelativeUri) == 0) + { + return uri; + } + + return new Uri(escapedRelativeUri, UriKind.Relative); + } + + /// + /// Gets the escaped metadata reference property name. + /// + /// The metadata reference property name in question. + /// The Uri escaped metadata reference property name. + internal static string EnsureEscapedFragment(string fragmentString) + { + Debug.Assert(fragmentString[0] == ODataConstants.ContextUriFragmentIndicator, "fragmentString[0] == " + ODataConstants.ContextUriFragmentIndicator); + return ODataConstants.ContextUriFragmentIndicator + Uri.EscapeDataString(fragmentString.Substring(1)); + } + + /// + /// Returns the unescaped string representation of the Uri; if the Uri is absolute returns the absolute Uri otherwise the original string. + /// + /// The Uri to convert to a string. + /// For absolute Uris the string representation of the absolute Uri; otherwise the Uri's original string. + internal static string UriToString(Uri uri) + { + Debug.Assert(uri != null, "uri != null"); + + return uri.IsAbsoluteUri ? uri.AbsoluteUri : uri.OriginalString; + } + + /// + /// Safely returns the specified string as a relative or absolute Uri. + /// + /// The string to convert to a Uri. + /// The string as a Uri. + internal static Uri StringToUri(string uriString) + { + Uri uri = null; + try + { + uri = new Uri(uriString, UriKind.RelativeOrAbsolute); + } + catch (System.FormatException) + { + // The Uri constructor throws a format exception if it can't figure out the type of Uri + uri = new Uri(uriString, UriKind.Relative); + } + + return uri; + } + + /// + /// Ensure the last character of Uri is a "/". + /// + /// The Uri to deal with. + /// The uri with tailling "/". + internal static Uri EnsureTaillingSlash(Uri uri) + { + if (uri == null) + { + return null; + } + + string baseUriString = UriToString(uri); + + if (baseUriString[baseUriString.Length - 1] != ODataConstants.UriSegmentSeparatorChar) + { + return new Uri(baseUriString + ODataConstants.UriSegmentSeparator, UriKind.RelativeOrAbsolute); + } + + return uri; + } + + /// + /// Determines whether the Uri instance is a + /// base of the specified Uri instance. + /// + /// + /// The check is host agnostic. For example, "http://host1.com/Service.svc" is a valid base Uri of "https://host2.org/Service.svc/Bla" + /// but is not a valid base for "http://host1.com/OtherService.svc/Bla". + /// + /// The candidate base URI. + /// The specified Uri instance to test. + /// true if the baseUri Uri instance is a base of uri; otherwise false. + internal static bool UriInvariantInsensitiveIsBaseOf(Uri baseUri, Uri uri) + { + Debug.Assert(baseUri != null, "baseUri != null"); + Debug.Assert(uri != null, "uri != null"); + + Uri upperCurrent = CreateBaseComparableUri(baseUri); + Uri upperUri = CreateBaseComparableUri(uri); + + return upperCurrent.IsBaseOf(upperUri); + } + + /// + /// Converts a string to a GUID value. + /// + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + /// Copy of WebConvert.TryKeyStringToGuid. + internal static bool TryUriStringToGuid(string text, out Guid targetValue) + { + try + { + // ABNF shows guidValue defined as + // guidValue = 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 12HEXDIG + // which comes to length of 36 + string trimmedText = text.Trim(); + if (trimmedText.Length != 36 || trimmedText.IndexOf('-') != 8) + { + targetValue = default(Guid); + return false; + } + + targetValue = XmlConvert.ToGuid(text); + return true; + } + catch (FormatException) + { + targetValue = default(Guid); + return false; + } + } + + /// + /// Converts a string to a DateTimeOffset value. + /// + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + /// Copy of WebConvert.TryKeyStringToDateTimeOffset. + internal static bool ConvertUriStringToDateTimeOffset(string text, out DateTimeOffset targetValue) + { + targetValue = default(DateTimeOffset); + + try + { + targetValue = PlatformHelper.ConvertStringToDateTimeOffset(text); + return true; + } + catch (FormatException exception) + { + // This means it is a string similar to DateTimeOffset String, but cannot be parsed as DateTimeOffset and could not be a digit or GUID .etc. + Match m = PlatformHelper.PotentialDateTimeOffsetValidator.Match(text); + if (m.Success) + { + // The format should be exactly "yyyy-mm-ddThh:mm:ss('.'s+)?(zzzzzz)?" and each field value is within valid range + throw new ODataException(Strings.UriUtils_DateTimeOffsetInvalidFormat(text), exception); + } + + return false; + } + catch (ArgumentOutOfRangeException exception) + { + // This means the timezone number is bigger than 14:00, inclusive exception has detail exception. + throw new ODataException(Strings.UriUtils_DateTimeOffsetInvalidFormat(text), exception); + } + } + + /// + /// Converts a string to a Date value. + /// + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + internal static bool TryUriStringToDate(string text, out Date targetValue) + { + return PlatformHelper.TryConvertStringToDate(text, out targetValue); + } + + /// + /// Converts a string to a TimeOfDay value. + /// + /// String text to convert. + /// After invocation, converted value. + /// true if the value was converted; false otherwise. + internal static bool TryUriStringToTimeOfDay(string text, out TimeOfDay targetValue) + { + return PlatformHelper.TryConvertStringToTimeOfDay(text, out targetValue); + } + + /// + /// Create mock absoulte Uri from given Uri + /// + /// The Uri to be operated on. + /// The mock Uri, the base Uri if given is null + internal static Uri CreateMockAbsoluteUri(Uri uri = null) + { + Uri BaseMockUri = new Uri("http://host/"); + + if (uri == null) + { + return BaseMockUri; + } + + if (uri.IsAbsoluteUri) + { + return uri; + } + else + { + return new Uri(BaseMockUri, uri); + } + } + + /// Creates a URI suitable for host-agnostic comparison purposes. + /// URI to compare. + /// URI suitable for comparison. + private static Uri CreateBaseComparableUri(Uri uri) + { + Debug.Assert(uri != null, "uri != null"); + +#if !ORCAS + uri = new Uri(UriUtils.UriToString(uri).ToUpperInvariant(), UriKind.RelativeOrAbsolute); +#else + uri = new Uri(UriUtils.UriToString(uri).ToUpper(CultureInfo.InvariantCulture), UriKind.RelativeOrAbsolute); +#endif + + UriBuilder builder = new UriBuilder(uri); + builder.Host = "h"; + builder.Port = 80; + builder.Scheme = "http"; + return builder.Uri; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Utils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Utils.cs new file mode 100644 index 0000000..c649cd4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Utils.cs @@ -0,0 +1,123 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.IO; +#if PORTABLELIB + using System.Threading.Tasks; +#endif + #endregion Namespaces + + /// + /// Generic utility methods. + /// + internal static class Utils + { + /// + /// Calls IDisposable.Dispose() on the argument if it is not null + /// and is an IDisposable. + /// + /// The instance to dispose. + /// 'True' if IDisposable.Dispose() was called; 'false' otherwise. + internal static bool TryDispose(object o) + { + IDisposable disposable = o as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + return true; + } + + return false; + } + +#if PORTABLELIB + /// + /// Asynchronously flushes a stream. + /// + /// The stream to flush. + /// Task which represents the pending Flush operation. + internal static Task FlushAsync(this Stream stream) + { + return Task.Factory.StartNew(stream.Flush); + } +#endif + + /// + /// Perform a stable sort of the using the specified . + /// + /// The type of the items in the array to sort. + /// The array to sort. + /// The comparison to use to compare items in the array + /// Array of KeyValuePairs where the sequence of Values is the sorted representation of . + internal static KeyValuePair[] StableSort(this T[] array, Comparison comparison) + { + ExceptionUtils.CheckArgumentNotNull(array, "array"); + ExceptionUtils.CheckArgumentNotNull(comparison, "comparison"); + + KeyValuePair[] keys = new KeyValuePair[array.Length]; + for (int i = 0; i < array.Length; ++i) + { + keys[i] = new KeyValuePair(i, array[i]); + } + + Array.Sort(keys, new StableComparer(comparison)); + return keys; + } + + /// + /// Stable comparer of a sequence of key/value pairs where each pair + /// knows its position in the sequence and its value. + /// + /// The type of the values in the sequence. + private sealed class StableComparer : IComparer> + { + /// + /// The to compare the values. + /// + private readonly Comparison innerComparer; + + /// + /// Constructor. + /// + /// The to compare the values. + public StableComparer(Comparison innerComparer) + { + this.innerComparer = innerComparer; + } + + /// + /// Compares two key/value pairs by first comparing their value. If the values are equal, + /// the position in the array determines the relative order (and preserves the original relative order). + /// + /// First key/value pair. + /// Second key/value pair. + /// + /// A value < 0 if is less than . + /// The value 0 if is equal to . Note this only happens when comparing the same items when used in StableSort. + /// A value > 0 if is greater than . + /// + /// This method will never return the value 0 since the input sequence is constructed in a way + /// that all key/value pairs have unique indeces. + public int Compare(KeyValuePair x, KeyValuePair y) + { + int result = this.innerComparer(x.Value, y.Value); + + if (result == 0) + { + // use the position of the value in the array to preserve the relative order + result = x.Key - y.Key; + } + + return result; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationKinds.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationKinds.cs new file mode 100644 index 0000000..5a97c1b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationKinds.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + + /// + /// Validation kinds used in ODataMessageReaderSettings and ODataMessageWriterSettings. + /// + [Flags] + public enum ValidationKinds + { + /// + /// No validations. + /// + None = 0, + + /// + /// Disallow duplicate properties in ODataResource (i.e., properties with the same name). + /// If no duplication can be guaranteed, this flag can be turned off for better performance. + /// + ThrowOnDuplicatePropertyNames = 1, + + /// + /// Do not support for undeclared property for non open type. + /// + ThrowOnUndeclaredPropertyForNonOpenType = 2, + + /// + /// Validates that the type in input must exactly match the model. + /// If the input can be guaranteed to be valid, this flag can be turned off for better performance. + /// + ThrowIfTypeConflictsWithMetadata = 4, + + /// + /// Enable all validations. + /// + All = ~0 + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationUtils.cs new file mode 100644 index 0000000..91f73c8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/ValidationUtils.cs @@ -0,0 +1,467 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Class with utility methods for validating OData content (applicable for readers and writers). + /// + internal static class ValidationUtils + { + /// The set of characters that are invalid in property names. + /// Keep this array in sync with MetadataProviderUtils.InvalidCharactersInPropertyNames in Astoria. + internal static readonly char[] InvalidCharactersInPropertyNames = new char[] { ':', '.', '@' }; + + /// Maximum batch boundary length supported (not including leading CRLF or '-'). + private const int MaxBoundaryLength = 70; + + /// + /// Validates that an open property value is supported. + /// + /// The name of the open property. + /// The value of the open property. + internal static void ValidateOpenPropertyValue(string propertyName, object value) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + } + + /// + /// Validates a type kind for a value type. + /// + /// The type kind. + /// The name of the type (used for error reporting only). + internal static void ValidateValueTypeKind(EdmTypeKind typeKind, string typeName) + { + Debug.Assert(typeName != null, "typeName != null"); + + if (typeKind != EdmTypeKind.Primitive + && typeKind != EdmTypeKind.Enum + && typeKind != EdmTypeKind.Collection + && typeKind != EdmTypeKind.Untyped) + { + throw new ODataException(Strings.ValidationUtils_IncorrectValueTypeKind(typeName, typeKind.ToString())); + } + } + + /// + /// Validates that is a valid type name for a collection and returns its item type name. + /// + /// The name of the collection type. + /// The item type name for the . + internal static string ValidateCollectionTypeName(string collectionTypeName) + { + string itemTypeName = EdmLibraryExtensions.GetCollectionItemTypeName(collectionTypeName); + + if (itemTypeName == null) + { + throw new ODataException(Strings.ValidationUtils_InvalidCollectionTypeName(collectionTypeName)); + } + + return itemTypeName; + } + + /// + /// Validates that the is assignable to the + /// and fails if it's not. + /// + /// The expected entity type reference, the base type of the entities expected. + /// The payload entity type reference to validate. + internal static void ValidateEntityTypeIsAssignable(IEdmEntityTypeReference expectedEntityTypeReference, IEdmEntityTypeReference payloadEntityTypeReference) + { + Debug.Assert(expectedEntityTypeReference != null, "expectedEntityTypeReference != null"); + Debug.Assert(payloadEntityTypeReference != null, "payloadEntityTypeReference != null"); + + // Entity types must be assignable + if (!EdmLibraryExtensions.IsAssignableFrom(expectedEntityTypeReference.EntityDefinition(), payloadEntityTypeReference.EntityDefinition())) + { + throw new ODataException(Strings.ValidationUtils_ResourceTypeNotAssignableToExpectedType(payloadEntityTypeReference.FullName(), expectedEntityTypeReference.FullName())); + } + } + + /// + /// Validates that the is assignable to the + /// and fails if it's not. + /// + /// The expected complex type reference, the base type of the ComplexType expected. + /// The payload complex type reference to validate. + internal static void ValidateComplexTypeIsAssignable(IEdmComplexType expectedComplexType, IEdmComplexType payloadComplexType) + { + Debug.Assert(expectedComplexType != null, "expectedComplexType != null"); + Debug.Assert(payloadComplexType != null, "payloadComplexType != null"); + + // Complex types could be assignable + if (!EdmLibraryExtensions.IsAssignableFrom(expectedComplexType, payloadComplexType)) + { + throw new ODataException(Strings.ValidationUtils_IncompatibleType(payloadComplexType.FullTypeName(), expectedComplexType.FullTypeName())); + } + } + + /// + /// Validates that the represents a collection type. + /// + /// The type reference to validate. + /// The instance representing the collection passed as . + internal static IEdmCollectionTypeReference ValidateCollectionType(IEdmTypeReference typeReference) + { + IEdmCollectionTypeReference collectionTypeReference = typeReference.AsCollectionOrNull(); + + if (collectionTypeReference != null && !typeReference.IsNonEntityCollectionType()) + { + throw new ODataException(Strings.ValidationUtils_InvalidCollectionTypeReference(typeReference.TypeKind())); + } + + return collectionTypeReference; + } + + /// + /// Validates an item of a collection to ensure it is not of collection and stream reference types. + /// + /// The collection item. + /// True if the items in the collection are nullable, false otherwise. + internal static void ValidateCollectionItem(object item, bool isNullable) + { + if (!isNullable && item == null) + { + throw new ODataException(Strings.ValidationUtils_NonNullableCollectionElementsMustNotBeNull); + } + + if (item is ODataCollectionValue) + { + throw new ODataException(Strings.ValidationUtils_NestedCollectionsAreNotSupported); + } + + if (item is ODataStreamReferenceValue) + { + throw new ODataException(Strings.ValidationUtils_StreamReferenceValuesNotSupportedInCollections); + } + } + + /// + /// Validates a null collection item against the expected type. + /// + /// The expected item type or null if no expected item type exists. + internal static void ValidateNullCollectionItem(IEdmTypeReference expectedItemType) + { + if (expectedItemType != null && expectedItemType.IsODataPrimitiveTypeKind() && !expectedItemType.IsNullable) + { + throw new ODataException(Strings.ValidationUtils_NullCollectionItemForNonNullableType(expectedItemType.FullName())); + } + } + + /// + /// Validates a stream reference property info to ensure it's not null and its name if correct. + /// + /// The stream reference info to validate. + /// Property metadata to validate against. + /// The name of the property being validated. + internal static void ValidateStreamPropertyInfo(IODataStreamReferenceInfo streamInfo, IEdmProperty edmProperty, string propertyName) + { + Debug.Assert(streamInfo != null, "streamInfo != null"); + if (edmProperty != null && !edmProperty.Type.IsStream()) + { + throw new ODataException(Strings.ValidationUtils_MismatchPropertyKindForStreamProperty(propertyName)); + } + } + + /// + /// Increases the given recursion depth, and then verifies that it doesn't exceed the recursion depth limit. + /// + /// The current depth of the payload element hierarchy. + /// The maximum allowed recursion depth. + internal static void IncreaseAndValidateRecursionDepth(ref int recursionDepth, int maxDepth) + { + recursionDepth++; + if (recursionDepth > maxDepth) + { + throw new ODataException(Strings.ValidationUtils_RecursionDepthLimitReached(maxDepth)); + } + } + + /// + /// Validates an to ensure it's not null. + /// + /// The operation to ensure it's not null. + /// Whether is an . + internal static void ValidateOperationNotNull(ODataOperation operation, bool isAction) + { + // null action/function can not appear in the enumeration + if (operation == null) + { + string enumerableName = isAction ? "ODataResource.Actions" : "ODataResource.Functions"; + throw new ODataException(Strings.ValidationUtils_EnumerableContainsANullItem(enumerableName)); + } + } + + /// + /// Validates an to ensure its metadata is specified and valid. + /// + /// The operation to validate. + internal static void ValidateOperationMetadataNotNull(ODataOperation operation) + { + Debug.Assert(operation != null, "operation != null"); + + // ODataOperation must have a Metadata property. + if (operation.Metadata == null) + { + throw new ODataException(Strings.ValidationUtils_ActionsAndFunctionsMustSpecifyMetadata(operation.GetType().Name)); + } + } + + /// + /// Validates an to ensure its target is specified and valid. + /// + /// The operation to validate. + internal static void ValidateOperationTargetNotNull(ODataOperation operation) + { + Debug.Assert(operation != null, "operation != null"); + + // ODataOperation must have a Target property. + if (operation.Target == null) + { + throw new ODataException(Strings.ValidationUtils_ActionsAndFunctionsMustSpecifyTarget(operation.GetType().Name)); + } + } + + /// + /// Validates that the specified is a valid resource as per the specified type. + /// + /// The resource to validate. + /// Optional entity type to validate the resource against. + /// If the is available only resource-level tests are performed, properties and such are not validated. + internal static void ValidateMediaResource(ODataResourceBase resource, IEdmEntityType resourceType) + { + Debug.Assert(resource != null, "resource != null"); + + if (resourceType != null) + { + if (resource.MediaResource == null) + { + if (resourceType.HasStream) + { + throw new ODataException(Strings.ValidationUtils_ResourceWithoutMediaResourceAndMLEType(resourceType.FullTypeName())); + } + } + else + { + if (!resourceType.HasStream) + { + throw new ODataException(Strings.ValidationUtils_ResourceWithMediaResourceAndNonMLEType(resourceType.FullTypeName())); + } + } + } + } + + /// + /// Validates that a given primitive value is of the expected (primitive) type. + /// + /// The value to check. + /// The expected type for the value. + internal static void ValidateIsExpectedPrimitiveType(object value, IEdmTypeReference expectedTypeReference) + { + Debug.Assert(value != null, "value != null"); + Debug.Assert(expectedTypeReference != null, "expectedTypeReference != null"); + + // Note that valueInstanceType is never a nullable type because GetType() on Nullable variables at runtime will always yield + // a Type object that represents the underlying type, not the Nullable type itself. + Type valueInstanceType = value.GetType(); + IEdmPrimitiveTypeReference valuePrimitiveTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(valueInstanceType); + ValidateIsExpectedPrimitiveType(value, valuePrimitiveTypeReference, expectedTypeReference); + } + + /// + /// Validates that a given primitive value is of the expected (primitive) type. + /// + /// The value to check. + /// The primitive type reference for the value - some callers have this already, so we save the lookup here. + /// The expected type for the value. + /// + /// Some callers have the primitive type reference already resolved (from the value type) + /// so this method is an optimized version to not lookup the primitive type reference again. + /// + internal static void ValidateIsExpectedPrimitiveType(object value, IEdmPrimitiveTypeReference valuePrimitiveTypeReference, IEdmTypeReference expectedTypeReference) + { + Debug.Assert(value != null, "value != null"); + Debug.Assert(expectedTypeReference != null, "expectedTypeReference != null"); + + if (valuePrimitiveTypeReference == null) + { + throw new ODataException(Strings.ValidationUtils_UnsupportedPrimitiveType(value.GetType().FullName)); + } + + Debug.Assert(valuePrimitiveTypeReference.IsEquivalentTo(EdmLibraryExtensions.GetPrimitiveTypeReference(value.GetType())), "The value and valuePrimitiveTypeReference don't match."); + if (!expectedTypeReference.IsODataPrimitiveTypeKind() && !expectedTypeReference.IsODataTypeDefinitionTypeKind()) + { + // non-primitive type found for primitive value. + throw new ODataException(Strings.ValidationUtils_NonPrimitiveTypeForPrimitiveValue(expectedTypeReference.FullName())); + } + + ValidateMetadataPrimitiveType(expectedTypeReference, valuePrimitiveTypeReference); + } + + /// + /// Validates that the expected primitive type or type definition matches the actual primitive type. + /// + /// The expected type. + /// The actual type. + internal static void ValidateMetadataPrimitiveType(IEdmTypeReference expectedTypeReference, IEdmTypeReference typeReferenceFromValue) + { + Debug.Assert(expectedTypeReference != null && (expectedTypeReference.IsODataPrimitiveTypeKind() || expectedTypeReference.IsODataTypeDefinitionTypeKind()), "expectedTypeReference must be a primitive type or type definition."); + Debug.Assert(typeReferenceFromValue != null && typeReferenceFromValue.IsODataPrimitiveTypeKind(), "typeReferenceFromValue must be a primitive type."); + + IEdmType expectedType = expectedTypeReference.Definition; + IEdmPrimitiveType typeFromValue = (IEdmPrimitiveType)typeReferenceFromValue.Definition; + + // The two primitive types match if they have the same definition and either both or only the + // expected type is nullable + // NOTE: for strings and binary values we must not check nullability here because the type reference + // from the value is always nullable since C# has no way to express non-nullable strings. + // However, this codepath is only hit if the value is not 'null' so we can assign a non-null + // value to both nullable and non-nullable string/binary types. + bool nullableCompatible = expectedTypeReference.IsNullable == typeReferenceFromValue.IsNullable || + expectedTypeReference.IsNullable && !typeReferenceFromValue.IsNullable || + !MetadataUtilsCommon.IsODataValueType(typeReferenceFromValue); + + bool typeCompatible = expectedType.IsAssignableFrom(typeFromValue); + if (!nullableCompatible || !typeCompatible) + { + // incompatible type name for value! + throw new ODataException(Strings.ValidationUtils_IncompatiblePrimitiveItemType( + typeReferenceFromValue.FullName(), + typeReferenceFromValue.IsNullable, + expectedTypeReference.FullName(), + expectedTypeReference.IsNullable)); + } + } + + /// + /// Validates a element in service document. + /// + /// The element in service document to validate. + /// Format that is being validated. + internal static void ValidateServiceDocumentElement(ODataServiceDocumentElement serviceDocumentElement, ODataFormat format) + { + if (serviceDocumentElement == null) + { + throw new ODataException(Strings.ValidationUtils_WorkspaceResourceMustNotContainNullItem); + } + + // The resource collection URL must not be null; + if (serviceDocumentElement.Url == null) + { + throw new ODataException(Strings.ValidationUtils_ResourceMustSpecifyUrl); + } + + if (format == ODataFormat.Json && string.IsNullOrEmpty(serviceDocumentElement.Name)) + { + throw new ODataException(Strings.ValidationUtils_ResourceMustSpecifyName(serviceDocumentElement.Url.ToString())); + } + } + + /// + /// Validates a service document element's Url. + /// + /// The service document url to validate. + internal static void ValidateServiceDocumentElementUrl(string serviceDocumentUrl) + { + // The service document URL must not be null or empty; + if (serviceDocumentUrl == null) + { + throw new ODataException(Strings.ValidationUtils_ServiceDocumentElementUrlMustNotBeNull); + } + } + + /// + /// Validates that the observed type kind is the expected type kind. + /// + /// The actual type kind to compare. + /// The expected type kind to compare against. + /// This value indicates if the is expected to be complex or entity. + /// True for complex or entity, false for non-structured type kind, null for indetermination. + /// The name of the type to use in the error. + internal static void ValidateTypeKind(EdmTypeKind actualTypeKind, EdmTypeKind expectedTypeKind, bool? expectStructuredType, string typeName) + { + if (expectStructuredType.HasValue && expectStructuredType.Value + && (expectedTypeKind.IsStructured() || expectedTypeKind == EdmTypeKind.None) + && actualTypeKind.IsStructured()) + { + return; + } + + if (expectedTypeKind != actualTypeKind) + { + if (typeName == null) + { + throw new ODataException(Strings.ValidationUtils_IncorrectTypeKindNoTypeName(actualTypeKind.ToString(), expectedTypeKind.ToString())); + } + + if (actualTypeKind == EdmTypeKind.TypeDefinition && expectedTypeKind == EdmTypeKind.Primitive + || actualTypeKind == EdmTypeKind.Primitive && expectedTypeKind == EdmTypeKind.TypeDefinition + || actualTypeKind == EdmTypeKind.Primitive && expectedTypeKind == EdmTypeKind.None) + { + return; + } + + throw new ODataException(Strings.ValidationUtils_IncorrectTypeKind(typeName, expectedTypeKind.ToString(), actualTypeKind.ToString())); + } + } + + /// + /// Validates that a boundary delimiter is valid (non-null, less than 70 chars, only valid chars, etc.) + /// + /// The boundary delimiter to test. + internal static void ValidateBoundaryString(string boundary) + { + // Boundary string must have at least 1 and no more than 70 characters. + if (boundary == null || boundary.Length == 0 || boundary.Length > MaxBoundaryLength) + { + throw new ODataException(Strings.ValidationUtils_InvalidBatchBoundaryDelimiterLength(boundary, MaxBoundaryLength)); + } + + //// NOTE: we do not have to check the validity of the characters in the boundary string + //// since we check their validity when reading the boundary parameter value of the Content-Type header. + //// See HttpUtils.ReadQuotedParameterValue. + } + + /// + /// Validates that a property name is valid in OData. + /// + /// The property name to validate. + /// true if the property name is valid, otherwise false. + internal static bool IsValidPropertyName(string propertyName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "The ATOM or JSON reader should have verified that the property name is not null or empty."); + + return propertyName.IndexOfAny(ValidationUtils.InvalidCharactersInPropertyNames) < 0; + } + + /// + /// Validates a property name to check whether it contains reserved characters. + /// + /// The property name to check. + internal static void ValidatePropertyName(string propertyName) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + if (!IsValidPropertyName(propertyName)) + { + string invalidChars = string.Join( + ", ", + ValidationUtils.InvalidCharactersInPropertyNames.Select(c => string.Format(CultureInfo.InvariantCulture, "'{0}'", c)).ToArray()); + throw new ODataException(Strings.ValidationUtils_PropertiesMustNotContainReservedChars(propertyName, invalidChars)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataBinaryStreamValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataBinaryStreamValue.cs new file mode 100644 index 0000000..cc7c1c0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataBinaryStreamValue.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.IO; + #endregion + + /// + /// A class to represent a binary stream value + /// + public sealed class ODataBinaryStreamValue : ODataValue + { + /// + /// Constructor + /// + /// Input stream + public ODataBinaryStreamValue(Stream stream) + { + ExceptionUtils.CheckArgumentNotNull(stream, "stream"); + + this.Stream = stream; + } + + /// + /// The Stream wrapped by the ODataBinaryStreamValue + /// + public Stream Stream { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataCollectionValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataCollectionValue.cs new file mode 100644 index 0000000..118dd2e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataCollectionValue.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System.Collections; + using System.Collections.Generic; + + #endregion Namespaces + + /// + /// OData representation of a Collection. + /// + public sealed class ODataCollectionValue : ODataValue + { + /// Gets or sets the type of the collection value. + /// The type of the collection value. + public string TypeName + { + get; + set; + } + + /// Gets or sets the items in the bag value. + /// The items in the bag value. + public IEnumerable Items + { + get; + set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataEnumValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataEnumValue.cs new file mode 100644 index 0000000..bc910ef --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataEnumValue.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// OData enum value + /// + public sealed class ODataEnumValue : ODataValue + { + /// Constructor + /// The backing type, can be "3" or "White" or "Black,Yellow,Cyan". + public ODataEnumValue(string value) + { + this.Value = value; + this.TypeName = null; + } + + /// Constructor + /// The backing type, can be "3" or "White" or "Black,Yellow,Cyan". + /// The type name in edm model. + public ODataEnumValue(string value, string typeName) + { + this.Value = value; + this.TypeName = typeName; + } + + /// Get backing type value, can be "3" or "White" or "Black,Yellow,Cyan". + public string Value { get; private set; } + + /// Get the type name in edm model. + public string TypeName { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataNullValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataNullValue.cs new file mode 100644 index 0000000..1bf3320 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataNullValue.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Represents a null property value. + /// + public sealed class ODataNullValue : ODataValue + { + /// + /// Indicates whether the given value is a null value. + /// + /// true, since this object always represents a null value. + internal override bool IsNullValue + { + get + { + return true; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataPrimitiveValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataPrimitiveValue.cs new file mode 100644 index 0000000..40efffa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataPrimitiveValue.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System; + using Microsoft.OData.Metadata; + + /// + /// Represents a primitive property value. + /// + public sealed class ODataPrimitiveValue : ODataValue + { + /// + /// Creates a new primitive value from the given CLR value. + /// + /// The primitive to wrap. + /// The primitive value should not be an instance of ODataValue. + public ODataPrimitiveValue(object value) + { + if (value == null) + { + throw new ArgumentNullException(Strings.ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromNull, (Exception)null); + } + + if (!EdmLibraryExtensions.IsPrimitiveType(value.GetType())) + { + throw new ODataException(Strings.ODataPrimitiveValue_CannotCreateODataPrimitiveValueFromUnsupportedValueType(value.GetType())); + } + + this.Value = value; + } + + /// + /// Gets the underlying CLR object wrapped by this . + /// + /// The underlying primitive CLR value. + public object Value { get; private set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataResourceValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataResourceValue.cs new file mode 100644 index 0000000..13f9298 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataResourceValue.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.OData +{ + /// + /// Represents the value of a resource (complex or entity). + /// It can be used as instance annotation value. + /// + public sealed class ODataResourceValue : ODataValue + { + /// + /// Gets or sets the type name. + /// + public string TypeName { get; set; } + + /// + /// Gets or sets the properties belong to this resource. + /// + public IEnumerable Properties { get; set; } + + /// + /// Collection of custom instance annotations. + /// + [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", + Justification = "We want to allow the same instance annotation collection instance to be shared across ODataLib OM instances.")] + public ICollection InstanceAnnotations + { + get { return this.GetInstanceAnnotations(); } + set { this.SetInstanceAnnotations(value); } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataStreamReferenceValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataStreamReferenceValue.cs new file mode 100644 index 0000000..68e0cff --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataStreamReferenceValue.cs @@ -0,0 +1,132 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using Microsoft.OData.Evaluation; + using Edm; + + #endregion + + /// + /// Represents a Stream reference. + /// + public sealed class ODataStreamReferenceValue : ODataValue, IODataStreamReferenceInfo + { + /// The name of the named stream this value belongs to; null for the default media resource. + private string edmPropertyName; + + /// the metadata builder for this OData resource. + private ODataResourceMetadataBuilder metadataBuilder; + + /// Edit link for media resource. + private Uri editLink; + + /// Edit link for media resource. + private Uri computedEditLink; + + /// Read link for media resource. + private Uri readLink; + + /// Read link for media resource. + private Uri computedReadLink; + + /// Gets or sets the edit link for media resource. + /// The edit link for media resource. + public Uri EditLink + { + get + { + return this.HasNonComputedEditLink + ? this.editLink + : (this.computedEditLink ?? (this.metadataBuilder == null ? null : this.computedEditLink = this.metadataBuilder.GetStreamEditLink(this.edmPropertyName))); + } + + set + { + this.editLink = value; + this.HasNonComputedEditLink = true; + } + } + + /// Gets or sets the read link for media resource. + /// The read link for media resource. + public Uri ReadLink + { + get + { + return this.HasNonComputedReadLink + ? this.readLink + : (this.computedReadLink ?? (this.metadataBuilder == null ? null : + this.computedReadLink = this.metadataBuilder.GetStreamReadLink(this.edmPropertyName))); + } + + set + { + this.readLink = value; + this.HasNonComputedReadLink = true; + } + } + + /// Gets or sets the content media type. + /// The content media type. + public string ContentType + { + get; + set; + } + + /// Gets or sets the media resource ETag. + /// The media resource ETag. + public string ETag + { + get; + set; + } + + /// + /// true if an edit link was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedEditLink + { + get; + private set; + } + + /// + /// true if a read link was provided by the user or seen on the wire, false otherwise. + /// + internal bool HasNonComputedReadLink + { + get; + private set; + } + + /// + /// Sets the metadata builder for this stream reference value. + /// + /// The metadata builder used to compute values from model annotations. + /// The property name for the named stream; null for the default media resource. + internal void SetMetadataBuilder(ODataResourceMetadataBuilder builder, string propertyName) + { + this.metadataBuilder = builder; + this.edmPropertyName = propertyName; + this.computedEditLink = null; + this.computedReadLink = null; + } + + /// + /// Gets the metadata builder for this stream reference value. + /// + /// The metadata builder used to compute links. + internal ODataResourceMetadataBuilder GetMetadataBuilder() + { + return this.metadataBuilder; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataUntypedValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataUntypedValue.cs new file mode 100644 index 0000000..389a274 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataUntypedValue.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// OData representation of an untyped value. + /// + public sealed class ODataUntypedValue : ODataValue + { + /// Gets or sets the raw untyped value. + /// The raw untyped value. + /// + /// The caller of the setter is responsible for formatting the value for the + /// data transmission protocol it will be used in. + /// For instance, if the protocol is JSON, the caller must format this value as JSON. + /// If the protocol is Atom, the caller must format this value as XML. + /// This libarary will not perform any formatting. + /// + public string RawValue + { + get; + set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValue.cs new file mode 100644 index 0000000..29d351f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValue.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + /// + /// Represents the value of a property. + /// + public abstract class ODataValue : ODataItem + { + /// + /// Indicates whether the given value is a null value. + /// + /// true if the value is an ODataNullValue, false otherwise. + internal virtual bool IsNullValue + { + get + { + return false; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValueUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValueUtils.cs new file mode 100644 index 0000000..6916655 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/Value/ODataValueUtils.cs @@ -0,0 +1,74 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#else +namespace Microsoft.OData +#endif +{ + #region Namespaces + #if ODATA_SERVICE + using Microsoft.OData; + #endif + #endregion Namespaces + + /// + /// Class with utility methods to deal with values in ODataLib. + /// + internal static class ODataValueUtils + { + /// + /// Converts an object to an ODataValue. If the given object is already an ODataValue (such as an ODataCollectionValue, etc.), the original object will be returned. + /// + /// The object to convert to an ODataValue + /// The given object as an ODataValue. + internal static ODataValue ToODataValue(this object objectToConvert) + { + if (objectToConvert == null) + { + return new ODataNullValue(); + } + + // If the given object is already an ODataValue, return it as is. + ODataValue odataValue = objectToConvert as ODataValue; + if (odataValue != null) + { + return odataValue; + } + + if (objectToConvert.GetType().IsEnum()) + { + return new ODataEnumValue(objectToConvert.ToString().Replace(", ", ",")); + } + + // Otherwise treat it as a primitive and wrap in an ODataPrimitiveValue. This includes spatial types. + return new ODataPrimitiveValue(objectToConvert); + } + + /// + /// Converts an ODataValue to the old style of representing values, where null values are null and primitive values are just the direct primitive (no longer wrapped by ODataPrimitiveValue). + /// All other value types, such as ODataCollectionValue are returned unchanged. + /// + /// The value to convert. + /// The value behind the given ODataValue. + internal static object FromODataValue(this ODataValue odataValue) + { + if (odataValue is ODataNullValue) + { + return null; + } + + ODataPrimitiveValue primitiveValue = odataValue as ODataPrimitiveValue; + if (primitiveValue != null) + { + return primitiveValue.Value; + } + + return odataValue; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterUtils.cs new file mode 100644 index 0000000..1da6da0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterUtils.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + + using System; + using System.Diagnostics; + using Microsoft.OData.Metadata; + using Microsoft.OData.Edm; + + #endregion Namespaces + + /// + /// Class with utility methods for writing OData content. + /// + internal static class WriterUtils + { + /// + /// Prepare the type name for writing. + /// 1) If it is primitive type, remove the Edm. prefix. + /// 2) If it is a non-primitive type or 4.0, prefix with #. + /// + /// The type name to write + /// OData Version of payload being written + /// The type name for writing + internal static string PrefixTypeNameForWriting(string typeName, ODataVersion version) + { + if (!string.IsNullOrEmpty(typeName)) + { + string itemTypeName = EdmLibraryExtensions.GetCollectionItemTypeName(typeName); + if (itemTypeName == null) + { + IEdmSchemaType primitiveType = EdmLibraryExtensions.ResolvePrimitiveTypeName(typeName); + if (primitiveType != null) + { + typeName = primitiveType.ShortQualifiedName(); + return version < ODataVersion.V401 ? PrefixTypeName(typeName) : typeName; + } + } + else + { + IEdmSchemaType primitiveType = EdmLibraryExtensions.ResolvePrimitiveTypeName(itemTypeName); + if (primitiveType != null) + { + typeName = EdmLibraryExtensions.GetCollectionTypeName(primitiveType.ShortQualifiedName()); + return version < ODataVersion.V401 ? PrefixTypeName(typeName) : typeName; + } + } + } + + return PrefixTypeName(typeName); + } + + /// + /// For JsonLight writer, always prefix the type name with # for payload writting. + /// + /// The type name to prefix + /// The (#) prefixed type name. + private static string PrefixTypeName(string typeName) + { + if (string.IsNullOrEmpty(typeName)) + { + return typeName; + } + + Debug.Assert(!typeName.StartsWith(ODataConstants.TypeNamePrefix, StringComparison.Ordinal), "The type name not start with " + ODataConstants.TypeNamePrefix + "before prefix"); + + return ODataConstants.TypeNamePrefix + typeName; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidationUtils.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidationUtils.cs new file mode 100644 index 0000000..4fa9d1d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidationUtils.cs @@ -0,0 +1,572 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + #region Namespaces + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + #endregion Namespaces + + /// + /// Class with utility methods for validating OData content when writing. + /// + internal static class WriterValidationUtils + { + /// + /// Validates that message writer settings are correct. + /// + /// The message writer settings to validate. + /// True if we are writing a response. + internal static void ValidateMessageWriterSettings(ODataMessageWriterSettings messageWriterSettings, bool writingResponse) + { + Debug.Assert(messageWriterSettings != null, "messageWriterSettings != null"); + + if (messageWriterSettings.BaseUri != null && !messageWriterSettings.BaseUri.IsAbsoluteUri) + { + throw new ODataException(Strings.WriterValidationUtils_MessageWriterSettingsBaseUriMustBeNullOrAbsolute(UriUtils.UriToString(messageWriterSettings.BaseUri))); + } + + if (messageWriterSettings.HasJsonPaddingFunction() && !writingResponse) + { + throw new ODataException(Strings.WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage); + } + } + + /// + /// Validates an for not being null. + /// + /// The property to validate for not being null. + internal static void ValidatePropertyNotNull(ODataPropertyInfo property) + { + if (property == null) + { + throw new ODataException(Strings.WriterValidationUtils_PropertyMustNotBeNull); + } + } + + /// + /// Validates a property name to ensure all required information is specified. + /// + /// The property name to validate. + internal static void ValidatePropertyName(string propertyName) + { + // Properties must have a non-empty name + if (string.IsNullOrEmpty(propertyName)) + { + throw new ODataException(Strings.WriterValidationUtils_PropertiesMustHaveNonEmptyName); + } + + ValidationUtils.ValidatePropertyName(propertyName); + } + + /// + /// Validates that a property with the specified name exists on a given structured type. + /// The structured type can be null if no metadata is available. + /// + /// The name of the property to validate. + /// The owning type of the property with name + /// or null if no metadata is available. + /// Flag indicating whether undeclared property on non open type should be prohibited. + /// The instance representing the property with name + /// or null if no metadata is available. + internal static IEdmProperty ValidatePropertyDefined( + string propertyName, + IEdmStructuredType owningStructuredType, + bool throwOnUndeclaredProperty) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + if (owningStructuredType == null) + { + return null; + } + + IEdmProperty property = owningStructuredType.FindProperty(propertyName); + + if (throwOnUndeclaredProperty && !owningStructuredType.IsOpen && property == null) + { + throw new ODataException(Strings.ValidationUtils_PropertyDoesNotExistOnType(propertyName, owningStructuredType.FullTypeName())); + } + + return property; + } + + /// + /// Validates that the property given is defined. + /// + /// The info of property. + /// Whether undeclared property on non open type should be prohibited. + internal static void ValidatePropertyDefined(PropertySerializationInfo propertyInfo, bool throwOnUndeclaredProperty) + { + if (propertyInfo.MetadataType.OwningType == null) + { + return; + } + + if (throwOnUndeclaredProperty && propertyInfo.MetadataType.IsUndeclaredProperty && !propertyInfo.MetadataType.IsOpenProperty) + { + throw new ODataException(Strings.ValidationUtils_PropertyDoesNotExistOnType(propertyInfo.PropertyName, propertyInfo.MetadataType.OwningType.FullTypeName())); + } + } + + /// + /// Validates that a navigation property with the specified name exists on a given entity type. + /// The entity type can be null if no metadata is available. + /// + /// The name of the property to validate. + /// The owning entity type/complex type or null if no metadata is available. + /// Flag indicating whether undeclared property on non open type should be prohibited. + /// The instance representing the navigation property with name + /// or null if no metadata is available. + internal static IEdmNavigationProperty ValidateNavigationPropertyDefined(string propertyName, IEdmStructuredType owningType, bool throwOnUndeclaredProperty) + { + Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); + + if (owningType == null) + { + return null; + } + + IEdmProperty property = ValidatePropertyDefined(propertyName, owningType, throwOnUndeclaredProperty); + if (property == null) + { + return null; + } + + if (property.PropertyKind != EdmPropertyKind.Navigation) + { + // The property must be a navigation property + throw new ODataException(Strings.ValidationUtils_NavigationPropertyExpected(propertyName, owningType.FullTypeName(), property.PropertyKind.ToString())); + } + + return (IEdmNavigationProperty)property; + } + + /// + /// Validates a resource in an expanded link to make sure the entity types match. + /// + /// The of the resource. + /// The type of the parent navigation property. + internal static void ValidateNestedResource(IEdmStructuredType resourceType, IEdmStructuredType parentNavigationPropertyType) + { + if (parentNavigationPropertyType == null) + { + return; + } + + Debug.Assert(resourceType != null, "If we have a parent navigation property type we should also have a resource type."); + + // Make sure the entity types are compatible + if (!parentNavigationPropertyType.IsAssignableFrom(resourceType)) + { + throw new ODataException(Strings.WriterValidationUtils_NestedResourceTypeNotCompatibleWithParentPropertyType(resourceType.FullTypeName(), parentNavigationPropertyType.FullTypeName())); + } + } + + /// + /// Validates that an can be written. + /// + /// The operation (an action or a function) to validate. + /// true if writing a response; otherwise false. + internal static void ValidateCanWriteOperation(ODataOperation operation, bool writingResponse) + { + Debug.Assert(operation != null, "operation != null"); + + // Operations are only valid in responses; we fail on them in requests + if (!writingResponse) + { + throw new ODataException(Strings.WriterValidationUtils_OperationInRequest(operation.Metadata)); + } + } + + /// + /// Validates an to ensure all required information is specified and valid on the WriteEnd call. + /// + /// The resource set to validate. + /// Flag indicating whether the resource set is written as part of a request or a response. + internal static void ValidateResourceSetAtEnd(ODataResourceSet resourceSet, bool writingRequest) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + // Verify next link + if (resourceSet.NextPageLink != null) + { + // Check that NextPageLink is not set for requests + if (writingRequest) + { + throw new ODataException(Strings.WriterValidationUtils_NextPageLinkInRequest); + } + } + } + + /// + /// Validates an to ensure all required information is specified and valid on the WriteEnd call. + /// + /// The resource set to validate. + /// Flag indicating whether the resource set is written as part of a request or a response. + internal static void ValidateDeltaResourceSetAtEnd(ODataDeltaResourceSet resourceSet, bool writingRequest) + { + Debug.Assert(resourceSet != null, "resourceSet != null"); + + // Verify next link + if (resourceSet.NextPageLink != null) + { + // Check that NextPageLink is not set for requests + if (writingRequest) + { + throw new ODataException(Strings.WriterValidationUtils_NextPageLinkInRequest); + } + } + } + + /// + /// Validates an to ensure all required information is specified and valid on WriteStart call. + /// + /// The resource to validate. + internal static void ValidateResourceAtStart(ODataResourceBase resource) + { + Debug.Assert(resource != null, "resource != null"); + + // Id can be specified at the beginning (and might be written here), so we need to validate it here. + ValidateResourceId(resource.Id); + + // Type name is verified in the format readers/writers since it's shared with other non-entity types + // and verifying it here would mean doing it twice. + } + + /// + /// Validates an to ensure all required information is specified and valid on WriteEnd call. + /// + /// The resource to validate. + internal static void ValidateResourceAtEnd(ODataResourceBase resource) + { + Debug.Assert(resource != null, "resource != null"); + + // If the Id was not specified in the beginning it might have been specified at the end, so validate it here as well. + ValidateResourceId(resource.Id); + } + + /// + /// Validates an to ensure all required information is specified and valid. + /// + /// The stream reference to validate. + /// true if is the default stream for an entity; false if it is a named stream property value. + internal static void ValidateStreamReferenceValue(ODataStreamReferenceValue streamReference, bool isDefaultStream) + { + Debug.Assert(streamReference != null, "streamReference != null"); + + if (streamReference.ContentType != null && streamReference.ContentType.Length == 0) + { + throw new ODataException(Strings.WriterValidationUtils_StreamReferenceValueEmptyContentType); + } + + if (isDefaultStream && streamReference.ReadLink == null && streamReference.ContentType != null) + { + throw new ODataException(Strings.WriterValidationUtils_DefaultStreamWithContentTypeWithoutReadLink); + } + + if (isDefaultStream && streamReference.ReadLink != null && streamReference.ContentType == null) + { + throw new ODataException(Strings.WriterValidationUtils_DefaultStreamWithReadLinkWithoutContentType); + } + + // Default stream can be completely empty (no links or anything) + // that is used to effectively mark the resource as MLE without providing any MR information. + // OData clients when creating new MLE/MR might not have the MR information (yet) when sending the first PUT, but they still + // need to mark the resource as MLE so that properties are written out-of-content. In such scenario the client can just set an empty + // default stream to mark the resource as MLE. + // That will cause the ATOM writer to write the properties outside the content without producing any content element. + if (streamReference.EditLink == null && streamReference.ReadLink == null && !isDefaultStream) + { + throw new ODataException(Strings.WriterValidationUtils_StreamReferenceValueMustHaveEditLinkOrReadLink); + } + + if (streamReference.EditLink == null && streamReference.ETag != null) + { + throw new ODataException(Strings.WriterValidationUtils_StreamReferenceValueMustHaveEditLinkToHaveETag); + } + } + + /// + /// Validates a named stream property to ensure it's not null and it's name if correct. + /// + /// The stream reference property to validate. + /// Property metadata to validate against. + /// The name of the property being validated. + /// true when writing a response; otherwise false. + /// This does NOT validate the value of the stream property, just the property itself. + internal static void ValidateStreamPropertyInfo(IODataStreamReferenceInfo streamPropertyInfo, IEdmProperty edmProperty, string propertyName, bool writingResponse) + { + Debug.Assert(streamPropertyInfo != null, "streamProperty != null"); + + ValidationUtils.ValidateStreamPropertyInfo(streamPropertyInfo, edmProperty, propertyName); + + if (!writingResponse) + { + // Read/Write links and ETags on Stream properties are only valid in responses; writers fail if they encounter them in requests. + if (streamPropertyInfo != null && streamPropertyInfo.EditLink != null || streamPropertyInfo.ReadLink != null || streamPropertyInfo.ETag != null) + { + throw new ODataException(Strings.WriterValidationUtils_StreamPropertyInRequest(propertyName)); + } + } + } + + /// + /// Validates the value type of a property meets the derived type constraints. + /// + /// The property serialization info. + /// This does NOT validate the value of the property, just the type of property. + internal static void ValidatePropertyDerivedTypeConstraint(PropertySerializationInfo propertySerializationInfo) + { + Debug.Assert(propertySerializationInfo != null, "propertySerializationInfo != null"); + + // Skip the undeclared property + if (propertySerializationInfo.MetadataType.IsUndeclaredProperty) + { + return; + } + + PropertyValueTypeInfo valueType = propertySerializationInfo.ValueType; + if (valueType == null || valueType.TypeReference == null) + { + return; + } + + // make sure the same type can pass the validation. + if (propertySerializationInfo.MetadataType.TypeReference.Definition == valueType.TypeReference.Definition) + { + return; + } + + string fullTypeName = valueType.TypeReference.FullName(); + if (propertySerializationInfo.MetadataType.DerivedTypeConstraints == null || + propertySerializationInfo.MetadataType.DerivedTypeConstraints.Any(d => d == fullTypeName)) + { + return; + } + + throw new ODataException(Strings.WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint(fullTypeName, "property", propertySerializationInfo.PropertyName)); + } + + /// + /// Validates that the specified is not null. + /// + /// The entity reference link to validate. + /// This should be called only for entity reference links inside the ODataEntityReferenceLinks.Links collection. + internal static void ValidateEntityReferenceLinkNotNull(ODataEntityReferenceLink entityReferenceLink) + { + if (entityReferenceLink == null) + { + throw new ODataException(Strings.WriterValidationUtils_EntityReferenceLinksLinkMustNotBeNull); + } + } + + /// + /// Validates an entity reference link instance. + /// + /// The entity reference link to validate. + internal static void ValidateEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink) + { + Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null"); + + if (entityReferenceLink.Url == null) + { + throw new ODataException(Strings.WriterValidationUtils_EntityReferenceLinkUrlMustNotBeNull); + } + } + + /// + /// Validates an to ensure all required information is specified and valid. + /// + /// The nested resource info to validate. + /// The declaring the structural property or navigation property; or null if metadata is not available. + /// The of the expanded content of this nested resource info or null for deferred links. + /// Flag indicating whether undeclared property on non open type should be prohibited. + /// The type of the navigation property for this nested resource info; or null if no was specified. + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Keeping the validation code for nested resource info multiplicity in one place.")] + internal static IEdmNavigationProperty ValidateNestedResourceInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmStructuredType declaringStructuredType, + ODataPayloadKind? expandedPayloadKind, + bool throwOnUndeclaredProperty) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + Debug.Assert( + !expandedPayloadKind.HasValue || + expandedPayloadKind.Value == ODataPayloadKind.EntityReferenceLink || + expandedPayloadKind.Value == ODataPayloadKind.Resource || + expandedPayloadKind.Value == ODataPayloadKind.ResourceSet, + "If an expanded payload kind is specified it must be resource, resource set or entity reference link."); + + // Navigation link must have a non-empty name + if (string.IsNullOrEmpty(nestedResourceInfo.Name)) + { + throw new ODataException(Strings.ValidationUtils_LinkMustSpecifyName); + } + + // If we write an entity reference link, don't validate the multiplicity of the IsCollection + // property if it is 'false' (since we allow writing a singleton navigation link for + // a collection navigation property in requests) nor the consistency of payload kind and metadata + // (which is done separately in ODataWriterCore.CheckForNestedResourceInfoWithContent). + bool isEntityReferenceLinkPayload = expandedPayloadKind == ODataPayloadKind.EntityReferenceLink; + + // true only if the expandedPayloadKind has a value and the value is 'Resource Set' + bool isResourceSetPayload = expandedPayloadKind == ODataPayloadKind.ResourceSet; + + // Make sure the IsCollection property agrees with the payload kind for resource and resource set payloads + Func errorTemplate = null; + if (!isEntityReferenceLinkPayload && nestedResourceInfo.IsCollection.HasValue && expandedPayloadKind.HasValue) + { + // For resource set/resource make sure the IsCollection property is set correctly. + if (isResourceSetPayload != nestedResourceInfo.IsCollection.Value) + { + errorTemplate = expandedPayloadKind.Value == ODataPayloadKind.ResourceSet + ? (Func)Strings.WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetContent + : Strings.WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceContent; + } + } + + IEdmNavigationProperty navigationProperty = null; + if (errorTemplate == null && declaringStructuredType != null) + { + navigationProperty = ValidateNavigationPropertyDefined(nestedResourceInfo.Name, declaringStructuredType, throwOnUndeclaredProperty); + if (navigationProperty != null) + { + bool isCollectionType = navigationProperty.Type.TypeKind() == EdmTypeKind.Collection; + + // Make sure the IsCollection property agrees with the metadata type for resource and resource set payloads + if (nestedResourceInfo.IsCollection.HasValue && isCollectionType != nestedResourceInfo.IsCollection) + { + // Ignore the case where IsCollection is 'false' and we are writing an entity reference link + // (see comment above) + if (!(nestedResourceInfo.IsCollection == false && isEntityReferenceLinkPayload)) + { + errorTemplate = isCollectionType + ? (Func)Strings.WriterValidationUtils_ExpandedLinkIsCollectionFalseWithResourceSetMetadata + : Strings.WriterValidationUtils_ExpandedLinkIsCollectionTrueWithResourceMetadata; + } + } + + // Make sure that the payload kind agrees with the metadata. + // For entity reference links we check separately in ODataWriterCore.CheckForNestedResourceInfoWithContent. + if (!isEntityReferenceLinkPayload && expandedPayloadKind.HasValue && isCollectionType != isResourceSetPayload) + { + errorTemplate = isCollectionType + ? (Func)Strings.WriterValidationUtils_ExpandedLinkWithResourcePayloadAndResourceSetMetadata + : Strings.WriterValidationUtils_ExpandedLinkWithResourceSetPayloadAndResourceMetadata; + } + } + } + + if (errorTemplate != null) + { + string uri = nestedResourceInfo.Url == null ? "null" : UriUtils.UriToString(nestedResourceInfo.Url); + throw new ODataException(errorTemplate(uri)); + } + + return navigationProperty; + } + + /// + /// Validates the input meets the derived type constaints on the odata item. + /// + /// The input resource type. + /// The type from metadata. + /// The derived type constraints on the nested resource. + /// The item kind. + /// The item name. + internal static void ValidateDerivedTypeConstraint(IEdmStructuredType resourceType, + IEdmStructuredType metadataType, IEnumerable derivedTypeConstraints, string itemKind, string itemName) + { + if (resourceType == null || metadataType == null || derivedTypeConstraints == null || resourceType == metadataType) + { + return; + } + + string fullTypeName = resourceType.FullTypeName(); + if (derivedTypeConstraints.Any(c => c == fullTypeName)) + { + return; + } + + throw new ODataException(Strings.WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint(fullTypeName, itemKind, itemName)); + } + + /// + /// Validates that the specified nested resource info has cardinality, that is it has the IsCollection value set. + /// + /// The nested resource info to validate. + internal static void ValidateNestedResourceInfoHasCardinality(ODataNestedResourceInfo nestedResourceInfo) + { + Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null"); + + if (!nestedResourceInfo.IsCollection.HasValue) + { + throw new ODataException(Strings.WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection(nestedResourceInfo.Name)); + } + } + + /// + /// Validates that the expected property allows null value. + /// + /// The expected property type or null if we don't have any. + /// The name of the property. + /// The model to use to get the OData version. + internal static void ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, string propertyName, IEdmModel model) + { + Debug.Assert(model != null, "For null validation, model is required."); + + if (expectedPropertyTypeReference != null) + { + if (expectedPropertyTypeReference.IsNonEntityCollectionType()) + { + throw new ODataException(Strings.WriterValidationUtils_CollectionPropertiesMustNotHaveNullValue(propertyName)); + } + + if (expectedPropertyTypeReference.IsODataPrimitiveTypeKind() && !expectedPropertyTypeReference.IsNullable) + { + throw new ODataException(Strings.WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue(propertyName, expectedPropertyTypeReference.FullName())); + } + else if (expectedPropertyTypeReference.IsODataEnumTypeKind() && !expectedPropertyTypeReference.IsNullable) + { + throw new ODataException(Strings.WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue(propertyName, expectedPropertyTypeReference.FullName())); + } + else if (expectedPropertyTypeReference.IsStream()) + { + throw new ODataException(Strings.WriterValidationUtils_StreamPropertiesMustNotHaveNullValue(propertyName)); + } + else if (expectedPropertyTypeReference.IsODataComplexTypeKind()) + { + IEdmComplexTypeReference complexTypeReference = expectedPropertyTypeReference.AsComplex(); + if (!complexTypeReference.IsNullable) + { + throw new ODataException(Strings.WriterValidationUtils_NonNullablePropertiesMustNotHaveNullValue(propertyName, expectedPropertyTypeReference.FullName())); + } + } + } + } + + /// + /// Validates the value of the Id property on a resource. + /// + /// The id value for a resource to validate. + private static void ValidateResourceId(Uri id) + { + // Verify non-empty ID (entries can have no (null) ID for insert scenarios; empty IDs are not allowed) + // TODO: it always passes. Will add more validation or remove the validation after supporting relative Uri. + if (id != null && UriUtils.UriToString(id).Length == 0) + { + throw new ODataException(Strings.WriterValidationUtils_EntriesMustHaveNonEmptyId); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidator.cs new file mode 100644 index 0000000..d2f9a51 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Core/WriterValidator.cs @@ -0,0 +1,270 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData +{ + using System.Collections.Generic; + using Microsoft.OData.Edm; + using Microsoft.OData.Metadata; + + /// + /// Writer validator that binds to an ODataMessageWriterSettings instance. + /// + internal class WriterValidator : IWriterValidator + { + /// + /// References the bound ODataMessageWriterSettings instance. + /// + private readonly ODataMessageWriterSettings settings; + + /// + /// Creates a WriterValidator instance and binds it to settings. + /// + /// The ODataMessageWriterSettings instance to bind to. + internal WriterValidator(ODataMessageWriterSettings settings) + { + this.settings = settings; + } + + /// + /// Creates a DuplicatePropertyNameChecker instance. + /// + /// The created instance. + public IDuplicatePropertyNameChecker CreateDuplicatePropertyNameChecker() + { + return settings.ThrowOnDuplicatePropertyNames + ? (IDuplicatePropertyNameChecker)new DuplicatePropertyNameChecker() + : (IDuplicatePropertyNameChecker)new NullDuplicatePropertyNameChecker(); + } + + /// + /// Validates a resource in an expanded link to make sure that the types match. + /// + /// The of the resource. + /// The type of the parent navigation property or + /// complex property or complex collection property. + public virtual void ValidateResourceInNestedResourceInfo( + IEdmStructuredType resourceType, + IEdmStructuredType parentNavigationPropertyType) + { + if (settings.ThrowIfTypeConflictsWithMetadata) + { + WriterValidationUtils.ValidateNestedResource( + resourceType, parentNavigationPropertyType); + } + } + + /// + /// Validates that the specified nested resource info has cardinality set, i.e., it has the + /// IsCollection value set. + /// + /// The nested resource info to validate. + public virtual void ValidateNestedResourceInfoHasCardinality( + ODataNestedResourceInfo nestedResourceInfo) + { + WriterValidationUtils.ValidateNestedResourceInfoHasCardinality(nestedResourceInfo); + } + + /// + /// Validates that a given primitive value is of the expected (primitive) type. + /// + /// The value to check. + /// The primitive type reference for the value. + /// So some callers have this already, and we save the lookup here. + /// The expected type for the value. + /// + /// Some callers have the primitive type reference already resolved (from the value type), + /// and the method will not lookup the primitive type reference again. + /// + public virtual void ValidateIsExpectedPrimitiveType( + object value, IEdmPrimitiveTypeReference valuePrimitiveTypeReference, + IEdmTypeReference expectedTypeReference) + { + if (settings.ThrowIfTypeConflictsWithMetadata) + { + ValidationUtils.ValidateIsExpectedPrimitiveType( + value, valuePrimitiveTypeReference, expectedTypeReference); + } + } + + /// + /// Validates the value type reference against metadata. + /// + /// The metadata type reference. + /// The value type reference. + public virtual void ValidateTypeReference(IEdmTypeReference typeReferenceFromMetadata, + IEdmTypeReference typeReferenceFromValue) + { + if (settings.ThrowIfTypeConflictsWithMetadata) + { + // Make sure the types are the same + if (typeReferenceFromValue.IsODataPrimitiveTypeKind()) + { + // Primitive types must match exactly except for nullability + ValidationUtils.ValidateMetadataPrimitiveType(typeReferenceFromMetadata, + typeReferenceFromValue); + } + else if (typeReferenceFromMetadata.IsEntity()) + { + ValidationUtils.ValidateEntityTypeIsAssignable( + (IEdmEntityTypeReference)typeReferenceFromMetadata, + (IEdmEntityTypeReference)typeReferenceFromValue); + } + else if (typeReferenceFromMetadata.IsComplex()) + { + ValidationUtils.ValidateComplexTypeIsAssignable( + typeReferenceFromMetadata.Definition as IEdmComplexType, + typeReferenceFromValue.Definition as IEdmComplexType); + } + else if (typeReferenceFromMetadata.IsCollection()) + { + // Collection types must match exactly. + if (!typeReferenceFromMetadata.Definition.IsElementTypeEquivalentTo( + typeReferenceFromValue.Definition)) + { + throw new ODataException( + Strings.ValidationUtils_IncompatibleType( + typeReferenceFromValue.FullName(), + typeReferenceFromMetadata.FullName())); + } + } + else + { + // For other types, compare their full type name. + if (typeReferenceFromMetadata.FullName() != typeReferenceFromValue.FullName()) + { + throw new ODataException( + Strings.ValidationUtils_IncompatibleType( + typeReferenceFromValue.FullName(), + typeReferenceFromMetadata.FullName())); + } + } + } + } + + /// + /// Validates that the observed type kind is the expected type kind. + /// + /// The actual type kind. + /// The expected type kind. + /// This value indicates if the is expected to be complex or entity. + /// True for complex or entity, false for non-structured type kind, null for indetermination. + /// The edm type to use in the error. + /// If expectedStructuredType is true, then expectedTypeKind could be + public virtual void ValidateTypeKind(EdmTypeKind actualTypeKind, + EdmTypeKind expectedTypeKind, + bool? expectStructuredType, + IEdmType edmType) + { + if (settings.ThrowIfTypeConflictsWithMetadata) + { + ValidationUtils.ValidateTypeKind( + actualTypeKind, expectedTypeKind, expectStructuredType, edmType == null ? null : edmType.FullTypeName()); + } + } + + /// + /// Validates that the specified is a valid resource as per the + /// specified type. + /// + /// The resource to validate. + /// Optional entity type to validate the resource against. + /// + /// If the is available, only resource-level tests are + /// performed; properties and such are not validated. + /// + public virtual void ValidateMetadataResource(ODataResourceBase resource, IEdmEntityType resourceType) + { + ValidationUtils.ValidateMediaResource(resource, resourceType); + } + + /// + /// Validates that the expected property allows null value. + /// + /// The expected property type or null if we + /// don't have any. + /// The name of the property. + /// true if the property is top-level. + /// The model used to get the OData version. + public void ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, + string propertyName, bool isTopLevel, IEdmModel model) + { + if (settings.ThrowIfTypeConflictsWithMetadata) + { + WriterValidationUtils.ValidateNullPropertyValue(expectedPropertyTypeReference, propertyName, model); + } + + if (isTopLevel + && (this.settings.LibraryCompatibility >= ODataLibraryCompatibility.Version7 + || this.settings.Version >= ODataVersion.V401)) + { + // From the spec: + // 11.2.3 Requesting Individual Properties + // ... + // If the property is single-valued and has the null value, the service responds with 204 No Content. + // ... + throw new ODataException(Strings.ODataMessageWriter_CannotWriteTopLevelNull); + } + } + + /// + /// Validates a null collection item against the expected type. + /// + /// The expected item type or null if none. + public void ValidateNullCollectionItem(IEdmTypeReference expectedItemType) + { + if (settings.ThrowIfTypeConflictsWithMetadata) + { + ValidationUtils.ValidateNullCollectionItem(expectedItemType); + } + } + + /// + /// Validates that a property with the specified name exists on a given structured type. + /// The structured type can be null if no metadata is available. + /// + /// Name of the property. + /// Hosting type of the property or null if no metadata is + /// available. + /// An instance representing the specified property or + /// null if no metadata is available. + public IEdmProperty ValidatePropertyDefined(string propertyName, + IEdmStructuredType owningStructuredType) + { + return WriterValidationUtils.ValidatePropertyDefined( + propertyName, owningStructuredType, settings.ThrowOnUndeclaredPropertyForNonOpenType); + } + + /// + /// Validates an to ensure all required information is specified and valid. + /// + /// The nested resource info to validate. + /// The declaring the structural property or navigation property; or null if metadata is not available. + /// The of the expanded content of this nested resource info or null for deferred links. + /// The type of the navigation property for this nested resource info; or null if no was specified. + public IEdmNavigationProperty ValidateNestedResourceInfo( + ODataNestedResourceInfo nestedResourceInfo, + IEdmStructuredType declaringStructuredType, + ODataPayloadKind? expandedPayloadKind) + { + return WriterValidationUtils.ValidateNestedResourceInfo(nestedResourceInfo, declaringStructuredType, expandedPayloadKind, settings.ThrowOnUndeclaredPropertyForNonOpenType); + } + + /// + /// Validates the input meets the derived type constaints on the . + /// + /// The input resource type. + /// The type from metadata. + /// The derived type constraints on the nested resource. + /// The item kind. + /// The item name. + public void ValidateDerivedTypeConstraint(IEdmStructuredType resourceType, + IEdmStructuredType metadataType, IEnumerable derivedTypeConstraints, string itemKind, string itemName) + { + WriterValidationUtils.ValidateDerivedTypeConstraint(resourceType, metadataType, derivedTypeConstraints, itemKind, itemName); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.Net35/Microsoft.OData.Edm.NetFX35.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.Net35/Microsoft.OData.Edm.NetFX35.csproj new file mode 100644 index 0000000..bc32599 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.Net35/Microsoft.OData.Edm.NetFX35.csproj @@ -0,0 +1,1555 @@ + + + + true + v3.5 + Microsoft.OData.Edm.NetFX35.V7 + Library + $(AssemblyName).xml + System + {69A15676-2469-4315-9617-F0FFC04D310D} + + + + ..\ + + + + + + + + + + $(EnlistmentRoot)\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj + EdmLibCrossTargettingSourcePath + + + + + + GlobalSuppressions.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlEnumMemberExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEnumMemberExpression.cs + + + Microsoft\OData\Edm\PrimitiveValueConverters\PassThroughPrimitiveValueConverter.cs + + + Microsoft\OData\Edm\Csdl\CsdlLocation.cs + + + Microsoft\OData\Edm\Csdl\EdmEnumValueParser.cs + + + Microsoft\OData\Edm\Csdl\EdmParseException.cs + + + Microsoft\OData\Edm\Csdl\EdmValueParser.cs + + + Microsoft\OData\Edm\Csdl\EdmValueWriter.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlNavigationPropertyPathExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlAbstractNavigationSource.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlAction.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlActionImport.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlApplyExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlCastExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlCollectionExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlEnumMember.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlEnumType.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlFunction.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlFunctionImport.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlIfExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlIsTypeExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlLabeledExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlLabeledExpressionReferenceExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlPathExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlPropertyPathExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlAnnotation.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlAnnotations.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlCollectionType.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlExpressionBase.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlPropertyValue.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlRecordExpression.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlSingleton.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlSpatialTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\ICsdlTypeExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedAction.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\IUnresolvedElement.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedEnumType.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedFunction.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedVocabularyTerm.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsNavigationPropertyPathExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsTypeDefinitionDefinition.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsAction.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsActionImport.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsApplyExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsCastExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsCollectionExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsDateConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsDateTimeOffsetConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsDirectValueAnnotationsManager.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsFunction.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsFunctionImport.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsNavigationSource.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsGuidConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsBinaryConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsIfExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsIsTypeExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsLabeledExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsLabeledExpressionReferenceExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsDirectValueAnnotation.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEnumMember.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEnumTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlTerm.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsAnnotations.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsBooleanConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsCollectionTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsCollectionTypeExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsDecimalConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsElement.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsFloatingConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsIntConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsNamedTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsNullExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsPathExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsPropertyConstructor.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsPropertyPathExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsRecordExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsSingleton.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsSpatialTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsStringConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsTimeOfDayConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsDurationConstantExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsVocabularyAnnotation.cs + + + Microsoft\OData\Edm\Csdl\Serialization\EdmModelReferenceElementsVisitor.cs + + + Microsoft\OData\Edm\Csdl\Serialization\SerializationValidator.cs + + + Microsoft\OData\Edm\Csdl\SerializationExtensionMethods.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlOperationReturn.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEntityReferenceTypeExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsOperationImport.cs + + + Microsoft\OData\Edm\Csdl\Serialization\EdmModelSchemaSeparationSerializationVisitor.cs + + + Microsoft\OData\Edm\PrimitiveValueConverters\DefaultPrimitiveValueConverter.cs + + + Microsoft\OData\Edm\EdmLocation.cs + + + Microsoft\OData\Edm\ExtensionMethods\EdmElementComparer.cs + + + Microsoft\OData\Edm\ExtensionMethods\EnumHelper.cs + + + Microsoft\OData\Edm\PrimitiveValueConverters\IPrimitiveValueConverter.cs + + + Microsoft\OData\Edm\Memoizer.cs + + + Microsoft\OData\Edm\PrimitiveValueConverters\PrimitiveValueConverterConstants.cs + + + Microsoft\OData\Edm\Vocabularies\CapabilitiesVocabularyConstants.cs + + + Microsoft\OData\Edm\Vocabularies\CapabilitiesVocabularyModel.cs + + + Microsoft\OData\Edm\Vocabularies\AlternateKeysVocabularyConstants.cs + + + Microsoft\OData\Edm\Vocabularies\CommunityVocabularyConstants.cs + + + Microsoft\OData\Edm\Vocabularies\CoreVocabularyConstants.cs + + + Microsoft\OData\Edm\Vocabularies\CoreVocabularyModel.cs + + + Microsoft\OData\Edm\Vocabularies\ValidationVocabularyModel.cs + + + Microsoft\OData\Edm\Vocabularies\AlternateKeysVocabularyModel.cs + + + Microsoft\OData\Edm\Vocabularies\CommunityVocabularyModel.cs + + + Microsoft\OData\Edm\Vocabularies\AuthorizationVocabularyModel.cs + + + Microsoft\OData\Edm\Vocabularies\VocabularyModelProvider.cs + + + Microsoft\OData\Edm\Validation\DuplicateOperationValidator.cs + + + Microsoft\OData\Edm\VersioningDictionary.cs + + + Microsoft\OData\Edm\VersioningList.cs + + + Microsoft\OData\Edm\VersioningTree.cs + + + Microsoft\OData\Edm\Validation\ObjectLocation.cs + + + Microsoft\OData\Edm\Validation\ExpressionTypeChecker.cs + + + Microsoft\OData\Edm\ExtensionMethods\ToTraceStringExtensionMethods.cs + + + Microsoft\OData\Edm\Csdl\CsdlConstants.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlDirectValueAnnotation.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlBinaryTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlComplexType.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlDecimalTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlElement.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlEntityContainer.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlEntityReferenceType.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlEntitySet.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlEntityType.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlExpressionTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlOperation.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlFunctionBase.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlOperationImport.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlOperationParameter.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlKey.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlModel.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlNamedElement.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlNamedStructuredType.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlNamedTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlNavigationProperty.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlNavigationPropertyBinding.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlOnDelete.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlPrimitiveTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlProperty.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlPropertyReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlReferentialConstraint.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlReferentialConstraintRole.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlSchema.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlStringTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlStructuredType.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlTemporalTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlUntypedTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Common\EdmXmlDocumentParser.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Common\XmlDocumentParser.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Common\XmlElementInfo.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Common\XmlElementParser.cs + + + true + Microsoft\OData\Edm\Csdl\Parsing\CsdlDocumentParser.cs + + + Microsoft\OData\Edm\Csdl\Parsing\CsdlParser.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsBinaryTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsComplexTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsDecimalTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEntityContainer.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEntityReferenceTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEntitySet.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsEntityTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsOperation.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsOperationParameter.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsOperationReturn.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsOptionalParameter.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsModel.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsNavigationProperty.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedOperation.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedLabeledElement.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedEnumMember.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedParameter.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedReturn.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedPrimitiveType.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsPrimitiveTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsProperty.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsSchema.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsStringTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsStructuredTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsTemporalTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsTypeDefinition.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsTypeExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsTypeDefinitionReference.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsUntypedTypeReference.cs + + + Microsoft\OData\Edm\Csdl\Serialization\EdmModelCsdlSchemaWriter.cs + + + Microsoft\OData\Edm\Csdl\Serialization\EdmModelCsdlSerializationVisitor.cs + + + Microsoft\OData\Edm\Csdl\Serialization\EdmSchema.cs + + + Microsoft\OData\Edm\EdmModelVisitor.cs + + + Microsoft\OData\Edm\ExtensionMethods\ExtensionMethods.cs + + + Microsoft\OData\Edm\Cache.cs + + + Microsoft\OData\Edm\CacheHelper.cs + + + Microsoft\OData\Edm\EdmUtil.cs + + + Microsoft\OData\Edm\HashSetInternal.cs + + + Microsoft\OData\Edm\IDependencyTrigger.cs + + + Microsoft\OData\Edm\IDependent.cs + + + Microsoft\OData\Edm\IFlushCaches.cs + + + Microsoft\OData\Edm\TupleInternal.cs + + + Microsoft\OData\Edm\RegistrationHelper.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedComplexType.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedEntityContainer.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedEntitySet.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedEntityType.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedProperty.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedNavigationPropertyPath.cs + + + Microsoft\OData\Edm\Csdl\Semantics\BadElements\UnresolvedType.cs + + + Microsoft\OData\Edm\ExtensionMethods\EdmTypeSemantics.cs + + + Microsoft\OData\Edm\Validation\EdmError.cs + + + Microsoft\OData\Edm\Validation\EdmValidator.cs + + + Microsoft\OData\Edm\Validation\ValidationContext.cs + + + Microsoft\OData\Edm\Validation\ValidationExtensionMethods.cs + + + Microsoft\OData\Edm\Validation\ValidationRules.cs + + + Microsoft\OData\Edm\Validation\ValidationRule.cs + + + Microsoft\OData\Edm\Validation\ValidationRuleSet.cs + + + Microsoft\OData\Edm\Validation\EdmErrorCode.cs + + + Microsoft\OData\Edm\PlatformHelper.cs + + + Microsoft\OData\Edm\Csdl\CsdlReader.cs + + + Microsoft\OData\Edm\Csdl\CsdlReaderSettings.cs + + + Microsoft\OData\Edm\Csdl\CsdlTarget.cs + + + Microsoft\OData\Edm\Csdl\CsdlWriter.cs + + + Microsoft\OData\Edm\Csdl\Parsing\Ast\CsdlAnnotationPathExpression.cs + + + Microsoft\OData\Edm\Csdl\SchemaReader.cs + + + Microsoft\OData\Edm\Csdl\SchemaWriter.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsAnnotationPathExpression.cs + + + Microsoft\OData\Edm\Csdl\Semantics\CsdlSemanticsTerm.cs + + + Microsoft\OData\Edm\Schema\AmbiguousBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousEntityContainerBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousEntitySetBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousLabeledExpressionBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousOperationBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousOperationImportBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousPropertyBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousSingletonBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousTermBinding.cs + + + Microsoft\OData\Edm\Schema\AmbiguousTypeBinding.cs + + + Microsoft\OData\Edm\Schema\BadBinaryTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadCollectionType.cs + + + Microsoft\OData\Edm\Schema\BadComplexType.cs + + + Microsoft\OData\Edm\Schema\BadComplexTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadDecimalTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadEdmEnumMemberValue.cs + + + Microsoft\OData\Edm\Schema\BadElement.cs + + + Microsoft\OData\Edm\Schema\BadEntityContainer.cs + + + Microsoft\OData\Edm\Schema\BadEntityReferenceType.cs + + + Microsoft\OData\Edm\Schema\BadEntitySet.cs + + + Microsoft\OData\Edm\Schema\BadEntityType.cs + + + Microsoft\OData\Edm\Schema\BadEntityTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadEnumType.cs + + + Microsoft\OData\Edm\Schema\BadLabeledExpression.cs + + + Microsoft\OData\Edm\Schema\BadNamedStructuredType.cs + + + Microsoft\OData\Edm\Schema\BadNavigationProperty.cs + + + Microsoft\OData\Edm\Schema\BadPathType.cs + + + Microsoft\OData\Edm\Schema\BadPathTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadPrimitiveType.cs + + + Microsoft\OData\Edm\Schema\BadPrimitiveTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadProperty.cs + + + Microsoft\OData\Edm\Schema\BadSpatialTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadStringTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadStructuredType.cs + + + Microsoft\OData\Edm\Schema\BadTemporalTypeReference.cs + + + Microsoft\OData\Edm\Schema\BadType.cs + + + Microsoft\OData\Edm\Schema\BadTypeDefinition.cs + + + Microsoft\OData\Edm\Schema\BadTypeReference.cs + + + Microsoft\OData\Edm\Schema\CyclicComplexType.cs + + + Microsoft\OData\Edm\Schema\CyclicEntityContainer.cs + + + Microsoft\OData\Edm\Schema\CyclicEntityType.cs + + + Microsoft\OData\Edm\Schema\Date.cs + + + Microsoft\OData\Edm\Schema\EdmAction.cs + + + Microsoft\OData\Edm\Schema\EdmActionImport.cs + + + Microsoft\OData\Edm\Schema\EdmBinaryTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmCollectionType.cs + + + Microsoft\OData\Edm\Schema\EdmCollectionTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmComplexType.cs + + + Microsoft\OData\Edm\Schema\EdmComplexTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmConstants.cs + + + Microsoft\OData\Edm\Schema\EdmContainedEntitySet.cs + + + Microsoft\OData\Edm\Schema\EdmDecimalTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmElement.cs + + + Microsoft\OData\Edm\Schema\EdmEntityContainer.cs + + + Microsoft\OData\Edm\Schema\EdmEntityReferenceType.cs + + + Microsoft\OData\Edm\Schema\EdmEntityReferenceTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmEntitySet.cs + + + Microsoft\OData\Edm\Schema\EdmEntitySetBase.cs + + + Microsoft\OData\Edm\Schema\EdmEntityType.cs + + + Microsoft\OData\Edm\Schema\EdmEntityTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmEnumMember.cs + + + Microsoft\OData\Edm\Schema\EdmEnumMemberValue.cs + + + Microsoft\OData\Edm\Schema\EdmEnumType.cs + + + Microsoft\OData\Edm\Schema\EdmEnumTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmFunction.cs + + + Microsoft\OData\Edm\Schema\EdmFunctionImport.cs + + + Microsoft\OData\Edm\Schema\EdmInclude.cs + + + Microsoft\OData\Edm\Schema\EdmIncludeAnnotations.cs + + + Microsoft\OData\Edm\Schema\EdmModel.cs + + + Microsoft\OData\Edm\Schema\EdmModelBase.cs + + + Microsoft\OData\Edm\Schema\EdmNamedElement.cs + + + Microsoft\OData\Edm\Schema\EdmNavigationProperty.cs + + + Microsoft\OData\Edm\Schema\EdmNavigationPropertyBinding.cs + + + Microsoft\OData\Edm\Schema\EdmNavigationPropertyInfo.cs + + + Microsoft\OData\Edm\Schema\EdmNavigationSource.cs + + + Microsoft\OData\Edm\Schema\EdmOperation.cs + + + Microsoft\OData\Edm\Schema\EdmOperationImport.cs + + + Microsoft\OData\Edm\Schema\EdmOperationParameter.cs + + + Microsoft\OData\Edm\Schema\EdmOperationReturn.cs + + + Microsoft\OData\Edm\Schema\EdmOptionalParameter.cs + + + Microsoft\OData\Edm\Schema\EdmPathExpression.cs + + + Microsoft\OData\Edm\Schema\EdmPrimitiveTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmProperty.cs + + + Microsoft\OData\Edm\Schema\EdmReference.cs + + + Microsoft\OData\Edm\Schema\EdmReferentialConstraint.cs + + + Microsoft\OData\Edm\Schema\EdmSingleton.cs + + + Microsoft\OData\Edm\Schema\EdmSpatialTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmStringTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmStructuralProperty.cs + + + Microsoft\OData\Edm\Schema\EdmStructuredType.cs + + + Microsoft\OData\Edm\Schema\EdmTemporalTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmType.cs + + + Microsoft\OData\Edm\Schema\EdmTypeDefinition.cs + + + Microsoft\OData\Edm\Schema\EdmTypeDefinitionReference.cs + + + Microsoft\OData\Edm\Schema\EdmTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmUnknownEntitySet.cs + + + Microsoft\OData\Edm\Schema\EdmUntypedStructuredType.cs + + + Microsoft\OData\Edm\Schema\EdmUntypedStructuredTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmPathTypeReference.cs + + + Microsoft\OData\Edm\Schema\EdmUntypedTypeReference.cs + + + Microsoft\OData\Edm\Schema\IEdmNavigationTargetMapping.cs + + + Microsoft\OData\Edm\Schema\IEdmRowType.cs + + + Microsoft\OData\Edm\Schema\CoreModel\EdmCoreModelComplexType.cs + + + Microsoft\OData\Edm\Schema\CoreModel\EdmCoreModelPrimitiveType.cs + + + Microsoft\OData\Edm\Schema\CoreModel\EdmCoreModelUntypedType.cs + + + Microsoft\OData\Edm\Schema\CoreModel\IEdmCoreModelElement.cs + + + Microsoft\OData\Edm\Schema\CoreModel\EdmCoreModelEntityType.cs + + + Microsoft\OData\Edm\Schema\CoreModel\EdmCoreModelPathType.cs + + + Microsoft\OData\Edm\Schema\CoreModel\EdmCoreModel.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmAction.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmActionImport.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmBinaryTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmCheckable.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmCollectionType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmCollectionTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmComplexType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmComplexTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmContainedEntitySet.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmDecimalTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmElement.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntityContainer.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntityContainerElement.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntityReferenceType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntityReferenceTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntitySet.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntitySetBase.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntityType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEntityTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEnumMember.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEnumMemberValue.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEnumType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmEnumTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmExpression.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmFullNamedElement.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmFunction.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmFunctionImport.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmInclude.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmIncludeAnnotations.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmLocatable.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmModel.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmNamedElement.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmNavigationProperty.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmNavigationPropertyBinding.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmNavigationSource.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmOperation.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmOperationImport.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmOperationParameter.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmOperationReturn.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmOptionalParameter.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmPathExpression.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmPathType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmPathTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmPrimitiveType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmPrimitiveTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmProperty.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmReferentialConstraint.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmSchemaElement.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmSchemaType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmSingleton.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmSpatialTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmStringTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmStructuralProperty.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmStructuredType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmStructuredTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmTemporalTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmTypeDefinition.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmUntypedType.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmUntypedTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmTypeDefinitionReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmTypeReference.cs + + + Microsoft\OData\Edm\Schema\Interfaces\IEdmUnknownEntitySet.cs + + + Microsoft\OData\Edm\Schema\TimeOfDay.cs + + + Microsoft\OData\Edm\Validation\InterfaceValidator.cs + + + Microsoft\OData\Edm\Validation\ValidationHelper.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\EdmDirectValueAnnotation.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\EdmDirectValueAnnotationBinding.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\EdmDirectValueAnnotationsManager.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\EdmPropertyValueBinding.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\EdmTerm.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\EdmTypedDirectValueAnnotationBinding.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\EdmVocabularyAnnotation.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\IEdmDirectValueAnnotation.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\IEdmDirectValueAnnotationBinding.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\IEdmDirectValueAnnotationsManager.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\IEdmPropertyValueBinding.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\IEdmTerm.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\IEdmVocabularyAnnotatable.cs + + + Microsoft\OData\Edm\Vocabularies\Annotations\IEdmVocabularyAnnotation.cs + + + Microsoft\OData\Edm\Vocabularies\EdmExpressionEvaluator.cs + + + Microsoft\OData\Edm\Vocabularies\EdmToClrConverter.cs + + + Microsoft\OData\Edm\Vocabularies\EdmToClrEvaluator.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmApplyExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmCastExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmCollectionExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmEnumMemberExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmIfExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmIsTypeExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmLabeledExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmLabeledExpressionReferenceExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmNavigationPropertyPathExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmNullExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmPropertyConstructor.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmPropertyPathExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\EdmRecordExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmApplyExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmBinaryConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmBooleanConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmCastExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmCollectionExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmDateConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmDateTimeOffsetConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmDecimalConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmDurationConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmEnumMemberExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmFloatingConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmGuidConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmIfExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmIntegerConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmIsTypeExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmLabeledExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmLabeledExpressionReferenceExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmNullExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmPropertyConstructor.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmRecordExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmStringConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\Expressions\IEdmTimeOfDayConstantExpression.cs + + + Microsoft\OData\Edm\Vocabularies\TryCreateObjectInstance.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmBinaryConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmBooleanConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmCollectionValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmDateConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmDateTimeOffsetConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmDecimalConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmDurationConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmEnumValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmFloatingConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmGuidConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmIntegerConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmPropertyValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmStringConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmStructuredValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmTimeOfDayConstant.cs + + + Microsoft\OData\Edm\Vocabularies\Values\EdmValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmBinaryValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmBooleanValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmCollectionValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmDateTimeOffsetValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmDateValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmDecimalValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmDelayedValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmDurationValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmEnumValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmFloatingValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmGuidValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmIntegerValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmNullValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmPrimitiveValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmPropertyValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmStringValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmStructuredValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmTimeOfDayValue.cs + + + Microsoft\OData\Edm\Vocabularies\Values\IEdmValue.cs + + + + + false + + + + + CapabilitiesVocabularies.xml + + + CoreVocabularies.xml + + + AlternateKeysVocabularies.xml + + + CommunityVocabularies.xml + + + AuthorizationVocabularies.xml + + + ValidationVocabularies.xml + + + + + Microsoft.OData.Edm + true + true + internal + true + Microsoft.OData.Edm.EntityRes + + + true + + + true + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/Microsoft.OData.Edm.NetStandard.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/Microsoft.OData.Edm.NetStandard.csproj new file mode 100644 index 0000000..b220b3a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/Microsoft.OData.Edm.NetStandard.csproj @@ -0,0 +1,1569 @@ + + + + v5.0 + .NETPortable + + + true + Microsoft.OData.Edm + Library + true + false + true + Microsoft.OData.Edm + $(AssemblyName).xml + {DB301FA8-1CFA-487D-BA1E-803016E2A85C} + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + 14.0 + 14.0 + $(DefineConstants);PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + true + true + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + ..\Microsoft.OData.Edm.StyleCop + + + ..\..\..\bin\AnyCPU\Debug\Product\.NETPortable\v5.0\Microsoft.OData.Edm\ + + + ..\..\..\bin\AnyCPU\Release\Product\.NETPortable\v5.0\Microsoft.OData.Edm\ + + + ..\..\..\bin\AnyCPU\Cover\Product\.NETPortable\v5.0\Microsoft.OData.Edm\ + + + + + + Microsoft.OData.Edm + true + true + internal + true + Microsoft.OData.Edm.EntityRes + false + true + + + + + Csdl\CsdlConstants.cs + + + Csdl\CsdlLocation.cs + + + Csdl\CsdlReader.cs + + + Csdl\CsdlReaderSettings.cs + + + Csdl\CsdlTarget.cs + + + Csdl\CsdlWriter.cs + + + Csdl\EdmEnumValueParser.cs + + + Csdl\EdmParseException.cs + + + Csdl\EdmValueParser.cs + + + Csdl\EdmValueWriter.cs + + + Csdl\Parsing\Ast\CsdlAbstractNavigationSource.cs + + + Csdl\Parsing\Ast\CsdlAction.cs + + + Csdl\Parsing\Ast\CsdlActionImport.cs + + + Csdl\Parsing\Ast\CsdlAnnotation.cs + + + Csdl\Parsing\Ast\CsdlAnnotations.cs + + + Csdl\Parsing\Ast\CsdlApplyExpression.cs + + + Csdl\Parsing\Ast\CsdlBinaryTypeReference.cs + + + Csdl\Parsing\Ast\CsdlCastExpression.cs + + + Csdl\Parsing\Ast\CsdlCollectionExpression.cs + + + Csdl\Parsing\Ast\CsdlCollectionType.cs + + + Csdl\Parsing\Ast\CsdlComplexType.cs + + + Csdl\Parsing\Ast\CsdlConstantExpression.cs + + + Csdl\Parsing\Ast\CsdlDecimalTypeReference.cs + + + Csdl\Parsing\Ast\CsdlDirectValueAnnotation.cs + + + Csdl\Parsing\Ast\CsdlElement.cs + + + Csdl\Parsing\Ast\CsdlEntityContainer.cs + + + Csdl\Parsing\Ast\CsdlEntityReferenceType.cs + + + Csdl\Parsing\Ast\CsdlEntitySet.cs + + + Csdl\Parsing\Ast\CsdlEntityType.cs + + + Csdl\Parsing\Ast\CsdlEnumMember.cs + + + Csdl\Parsing\Ast\CsdlEnumMemberExpression.cs + + + Csdl\Parsing\Ast\CsdlEnumType.cs + + + Csdl\Parsing\Ast\CsdlExpressionBase.cs + + + Csdl\Parsing\Ast\CsdlExpressionTypeReference.cs + + + Csdl\Parsing\Ast\CsdlFunction.cs + + + Csdl\Parsing\Ast\CsdlFunctionBase.cs + + + Csdl\Parsing\Ast\CsdlFunctionImport.cs + + + Csdl\Parsing\Ast\CsdlIfExpression.cs + + + Csdl\Parsing\Ast\CsdlIsTypeExpression.cs + + + Csdl\Parsing\Ast\CsdlKey.cs + + + Csdl\Parsing\Ast\CsdlLabeledExpression.cs + + + Csdl\Parsing\Ast\CsdlLabeledExpressionReferenceExpression.cs + + + Csdl\Parsing\Ast\CsdlModel.cs + + + Csdl\Parsing\Ast\CsdlNamedElement.cs + + + Csdl\Parsing\Ast\CsdlNamedStructuredType.cs + + + Csdl\Parsing\Ast\CsdlNamedTypeReference.cs + + + Csdl\Parsing\Ast\CsdlNavigationProperty.cs + + + Csdl\Parsing\Ast\CsdlNavigationPropertyBinding.cs + + + Csdl\Parsing\Ast\CsdlNavigationPropertyPathExpression.cs + + + Csdl\Parsing\Ast\CsdlOnDelete.cs + + + Csdl\Parsing\Ast\CsdlOperation.cs + + + Csdl\Parsing\Ast\CsdlOperationImport.cs + + + Csdl\Parsing\Ast\CsdlOperationParameter.cs + + + Csdl\Parsing\Ast\CsdlOperationReturn.cs + + + Csdl\Parsing\Ast\CsdlPathExpression.cs + + + Csdl\Parsing\Ast\CsdlPrimitiveTypeReference.cs + + + Csdl\Parsing\Ast\CsdlProperty.cs + + + Csdl\Parsing\Ast\CsdlPropertyPathExpression.cs + + + Csdl\Parsing\Ast\CsdlPropertyReference.cs + + + Csdl\Parsing\Ast\CsdlPropertyValue.cs + + + Csdl\Parsing\Ast\CsdlRecordExpression.cs + + + Csdl\Parsing\Ast\CsdlReferentialConstraint.cs + + + Csdl\Parsing\Ast\CsdlReferentialConstraintRole.cs + + + Csdl\Parsing\Ast\CsdlSchema.cs + + + Csdl\Parsing\Ast\CsdlSingleton.cs + + + Csdl\Parsing\Ast\CsdlSpatialTypeReference.cs + + + Csdl\Parsing\Ast\CsdlStringTypeReference.cs + + + Csdl\Parsing\Ast\CsdlStructuredType.cs + + + Csdl\Parsing\Ast\CsdlTemporalTypeReference.cs + + + Csdl\Parsing\Ast\CsdlTerm.cs + + + Csdl\Parsing\Ast\CsdlTypeDefinition.cs + + + Csdl\Parsing\Ast\CsdlTypeReference.cs + + + Csdl\Parsing\Ast\CsdlUntypedTypeReference.cs + + + Csdl\Parsing\Ast\ICsdlTypeExpression.cs + + + Csdl\Parsing\Ast\CsdlAnnotationPathExpression.cs + + + Csdl\Parsing\Common\EdmXmlDocumentParser.cs + + + Csdl\Parsing\Common\XmlDocumentParser.cs + + + Csdl\Parsing\Common\XmlElementInfo.cs + + + Csdl\Parsing\Common\XmlElementParser.cs + + + Csdl\Parsing\CsdlDocumentParser.cs + true + + + Csdl\Parsing\CsdlParser.cs + + + Csdl\SchemaReader.cs + + + Csdl\SchemaWriter.cs + + + Csdl\Semantics\BadElements\IUnresolvedElement.cs + + + Csdl\Semantics\BadElements\UnresolvedAction.cs + + + Csdl\Semantics\BadElements\UnresolvedComplexType.cs + + + Csdl\Semantics\BadElements\UnresolvedEntityContainer.cs + + + Csdl\Semantics\BadElements\UnresolvedEntitySet.cs + + + Csdl\Semantics\BadElements\UnresolvedEntityType.cs + + + Csdl\Semantics\BadElements\UnresolvedEnumMember.cs + + + Csdl\Semantics\BadElements\UnresolvedEnumType.cs + + + Csdl\Semantics\BadElements\UnresolvedFunction.cs + + + Csdl\Semantics\BadElements\UnresolvedLabeledElement.cs + + + Csdl\Semantics\BadElements\UnresolvedNavigationPropertyPath.cs + + + Csdl\Semantics\BadElements\UnresolvedOperation.cs + + + Csdl\Semantics\BadElements\UnresolvedParameter.cs + + + Csdl\Semantics\BadElements\UnresolvedReturn.cs + + + Csdl\Semantics\BadElements\UnresolvedPrimitiveType.cs + + + Csdl\Semantics\BadElements\UnresolvedProperty.cs + + + Csdl\Semantics\BadElements\UnresolvedType.cs + + + Csdl\Semantics\BadElements\UnresolvedVocabularyTerm.cs + + + Csdl\Semantics\CsdlSemanticsAction.cs + + + Csdl\Semantics\CsdlSemanticsActionImport.cs + + + Csdl\Semantics\CsdlSemanticsAnnotations.cs + + + Csdl\Semantics\CsdlSemanticsApplyExpression.cs + + + Csdl\Semantics\CsdlSemanticsBinaryConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsBinaryTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsBooleanConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsCastExpression.cs + + + Csdl\Semantics\CsdlSemanticsCollectionExpression.cs + + + Csdl\Semantics\CsdlSemanticsCollectionTypeDefinition.cs + + + Csdl\Semantics\CsdlSemanticsCollectionTypeExpression.cs + + + Csdl\Semantics\CsdlSemanticsComplexTypeDefinition.cs + + + Csdl\Semantics\CsdlSemanticsDateConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsDateTimeOffsetConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsDecimalConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsDecimalTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsDirectValueAnnotation.cs + + + Csdl\Semantics\CsdlSemanticsDirectValueAnnotationsManager.cs + + + Csdl\Semantics\CsdlSemanticsDurationConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsElement.cs + + + Csdl\Semantics\CsdlSemanticsEntityContainer.cs + + + Csdl\Semantics\CsdlSemanticsEntityReferenceTypeDefinition.cs + + + Csdl\Semantics\CsdlSemanticsEntityReferenceTypeExpression.cs + + + Csdl\Semantics\CsdlSemanticsEntitySet.cs + + + Csdl\Semantics\CsdlSemanticsEntityTypeDefinition.cs + + + Csdl\Semantics\CsdlSemanticsEnumMember.cs + + + Csdl\Semantics\CsdlSemanticsEnumMemberExpression.cs + + + Csdl\Semantics\CsdlSemanticsEnumTypeDefinition.cs + + + Csdl\Semantics\CsdlSemanticsExpression.cs + + + Csdl\Semantics\CsdlSemanticsFloatingConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsFunction.cs + + + Csdl\Semantics\CsdlSemanticsFunctionImport.cs + + + Csdl\Semantics\CsdlSemanticsGuidConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsIfExpression.cs + + + Csdl\Semantics\CsdlSemanticsIntConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsIsTypeExpression.cs + + + Csdl\Semantics\CsdlSemanticsLabeledExpression.cs + + + Csdl\Semantics\CsdlSemanticsLabeledExpressionReferenceExpression.cs + + + Csdl\Semantics\CsdlSemanticsModel.cs + + + Csdl\Semantics\CsdlSemanticsNamedTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsNavigationProperty.cs + + + Csdl\Semantics\CsdlSemanticsNavigationPropertyPathExpression.cs + + + Csdl\Semantics\CsdlSemanticsNavigationSource.cs + + + Csdl\Semantics\CsdlSemanticsNullExpression.cs + + + Csdl\Semantics\CsdlSemanticsOperation.cs + + + Csdl\Semantics\CsdlSemanticsOperationImport.cs + + + Csdl\Semantics\CsdlSemanticsOperationParameter.cs + + + Csdl\Semantics\CsdlSemanticsOperationReturn.cs + + + Csdl\Semantics\CsdlSemanticsOptionalParameter.cs + + + Csdl\Semantics\CsdlSemanticsPathExpression.cs + + + Csdl\Semantics\CsdlSemanticsPrimitiveTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsProperty.cs + + + Csdl\Semantics\CsdlSemanticsPropertyConstructor.cs + + + Csdl\Semantics\CsdlSemanticsPropertyPathExpression.cs + + + Csdl\Semantics\CsdlSemanticsRecordExpression.cs + + + Csdl\Semantics\CsdlSemanticsSchema.cs + + + Csdl\Semantics\CsdlSemanticsSingleton.cs + + + Csdl\Semantics\CsdlSemanticsSpatialTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsStringConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsStringTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsStructuredTypeDefinition.cs + + + Csdl\Semantics\CsdlSemanticsTemporalTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsTerm.cs + + + Csdl\Semantics\CsdlSemanticsTimeOfDayConstantExpression.cs + + + Csdl\Semantics\CsdlSemanticsTypeDefinition.cs + + + Csdl\Semantics\CsdlSemanticsTypeDefinitionDefinition.cs + + + Csdl\Semantics\CsdlSemanticsTypeDefinitionReference.cs + + + Csdl\Semantics\CsdlSemanticsTypeExpression.cs + + + Csdl\Semantics\CsdlSemanticsUntypedTypeReference.cs + + + Csdl\Semantics\CsdlSemanticsVocabularyAnnotation.cs + + + Csdl\Semantics\CsdlSemanticsAnnotationPathExpression.cs + + + Csdl\SerializationExtensionMethods.cs + + + Csdl\Serialization\EdmModelCsdlSchemaWriter.cs + + + Csdl\Serialization\EdmModelCsdlSerializationVisitor.cs + + + Csdl\Serialization\EdmModelReferenceElementsVisitor.cs + + + Csdl\Serialization\EdmModelSchemaSeparationSerializationVisitor.cs + + + Csdl\Serialization\EdmSchema.cs + + + Csdl\Serialization\SerializationValidator.cs + + + EdmLocation.cs + + + EdmModelVisitor.cs + + + EdmUtil.cs + + + ExtensionMethods\EdmElementComparer.cs + + + ExtensionMethods\EdmTypeSemantics.cs + + + ExtensionMethods\EnumHelper.cs + + + ExtensionMethods\ExtensionMethods.cs + + + ExtensionMethods\ToTraceStringExtensionMethods.cs + + + + PrimitiveValueConverters\DefaultPrimitiveValueConverter.cs + + + PrimitiveValueConverters\IPrimitiveValueConverter.cs + + + PrimitiveValueConverters\PassThroughPrimitiveValueConverter.cs + + + PrimitiveValueConverters\PrimitiveValueConverterConstants.cs + + + Schema\AmbiguousBinding.cs + + + Schema\AmbiguousEntityContainerBinding.cs + + + Schema\AmbiguousEntitySetBinding.cs + + + Schema\AmbiguousLabeledExpressionBinding.cs + + + Schema\AmbiguousOperationBinding.cs + + + Schema\AmbiguousOperationImportBinding.cs + + + Schema\AmbiguousPropertyBinding.cs + + + Schema\AmbiguousSingletonBinding.cs + + + Schema\AmbiguousTermBinding.cs + + + Schema\AmbiguousTypeBinding.cs + + + Schema\BadBinaryTypeReference.cs + + + Schema\BadCollectionType.cs + + + Schema\BadComplexType.cs + + + Schema\BadComplexTypeReference.cs + + + Schema\BadDecimalTypeReference.cs + + + Schema\BadEdmEnumMemberValue.cs + + + Schema\BadElement.cs + + + Schema\BadEntityContainer.cs + + + Schema\BadEntityReferenceType.cs + + + Schema\BadEntitySet.cs + + + Schema\BadEntityType.cs + + + Schema\BadEntityTypeReference.cs + + + Schema\BadEnumType.cs + + + Schema\BadLabeledExpression.cs + + + Schema\BadNamedStructuredType.cs + + + Schema\BadNavigationProperty.cs + + + Schema\BadPathType.cs + + + Schema\BadPathTypeReference.cs + + + Schema\BadPrimitiveType.cs + + + Schema\BadPrimitiveTypeReference.cs + + + Schema\BadProperty.cs + + + Schema\BadSpatialTypeReference.cs + + + Schema\BadStringTypeReference.cs + + + Schema\BadStructuredType.cs + + + Schema\BadTemporalTypeReference.cs + + + Schema\BadType.cs + + + Schema\BadTypeDefinition.cs + + + Schema\BadTypeReference.cs + + + Schema\CyclicComplexType.cs + + + Schema\CyclicEntityContainer.cs + + + Schema\CyclicEntityType.cs + + + Schema\Date.cs + + + Schema\EdmAction.cs + + + Schema\EdmActionImport.cs + + + Schema\EdmBinaryTypeReference.cs + + + Schema\EdmCollectionType.cs + + + Schema\EdmCollectionTypeReference.cs + + + Schema\EdmComplexType.cs + + + Schema\EdmComplexTypeReference.cs + + + Schema\EdmConstants.cs + + + Schema\EdmContainedEntitySet.cs + + + Schema\EdmDecimalTypeReference.cs + + + Schema\EdmElement.cs + + + Schema\EdmEntityContainer.cs + + + Schema\EdmEntityReferenceType.cs + + + Schema\EdmEntityReferenceTypeReference.cs + + + Schema\EdmEntitySet.cs + + + Schema\EdmEntitySetBase.cs + + + Schema\EdmEntityType.cs + + + Schema\EdmEntityTypeReference.cs + + + Schema\EdmEnumMember.cs + + + Schema\EdmEnumMemberValue.cs + + + Schema\EdmEnumType.cs + + + Schema\EdmEnumTypeReference.cs + + + Schema\EdmFunction.cs + + + Schema\EdmFunctionImport.cs + + + Schema\EdmInclude.cs + + + Schema\EdmIncludeAnnotations.cs + + + Schema\EdmModel.cs + + + Schema\EdmModelBase.cs + + + Schema\EdmNamedElement.cs + + + Schema\EdmNavigationProperty.cs + + + Schema\EdmNavigationPropertyBinding.cs + + + Schema\EdmNavigationPropertyInfo.cs + + + Schema\EdmNavigationSource.cs + + + Schema\EdmOperation.cs + + + Schema\EdmOperationImport.cs + + + Schema\EdmOperationParameter.cs + + + Schema\EdmOperationReturn.cs + + + Schema\EdmOptionalParameter.cs + + + Schema\EdmPathExpression.cs + + + Schema\EdmPrimitiveTypeReference.cs + + + Schema\EdmProperty.cs + + + Schema\EdmReference.cs + + + Schema\EdmReferentialConstraint.cs + + + Schema\EdmSingleton.cs + + + Schema\EdmSpatialTypeReference.cs + + + Schema\EdmStringTypeReference.cs + + + Schema\EdmStructuralProperty.cs + + + Schema\EdmStructuredType.cs + + + Schema\EdmTemporalTypeReference.cs + + + Schema\EdmType.cs + + + Schema\EdmTypeDefinition.cs + + + Schema\EdmTypeDefinitionReference.cs + + + Schema\EdmTypeReference.cs + + + Schema\EdmUnknownEntitySet.cs + + + Schema\EdmUntypedStructuredType.cs + + + Schema\EdmUntypedStructuredTypeReference.cs + + + \Schema\EdmPathTypeReference.cs + + + Schema\EdmUntypedTypeReference.cs + + + Schema\IEdmNavigationTargetMapping.cs + + + Schema\IEdmRowType.cs + + + + Schema\CoreModel\EdmCoreModelComplexType.cs + + + Schema\CoreModel\EdmCoreModelPrimitiveType.cs + + + Schema\CoreModel\EdmCoreModelUntypedType.cs + + + Schema\CoreModel\IEdmCoreModelElement.cs + + + Schema\CoreModel\EdmCoreModelEntityType.cs + + + Schema\CoreModel\EdmCoreModelPathType.cs + + + Schema\CoreModel\EdmCoreModel.cs + + + Schema\Interfaces\IEdmAction.cs + + + Schema\Interfaces\IEdmActionImport.cs + + + Schema\Interfaces\IEdmBinaryTypeReference.cs + + + Schema\Interfaces\IEdmCheckable.cs + + + Schema\Interfaces\IEdmCollectionType.cs + + + Schema\Interfaces\IEdmCollectionTypeReference.cs + + + Schema\Interfaces\IEdmComplexType.cs + + + Schema\Interfaces\IEdmComplexTypeReference.cs + + + Schema\Interfaces\IEdmContainedEntitySet.cs + + + Schema\Interfaces\IEdmDecimalTypeReference.cs + + + Schema\Interfaces\IEdmElement.cs + + + Schema\Interfaces\IEdmEntityContainer.cs + + + Schema\Interfaces\IEdmEntityContainerElement.cs + + + Schema\Interfaces\IEdmEntityReferenceType.cs + + + Schema\Interfaces\IEdmEntityReferenceTypeReference.cs + + + Schema\Interfaces\IEdmEntitySet.cs + + + Schema\Interfaces\IEdmEntitySetBase.cs + + + Schema\Interfaces\IEdmEntityType.cs + + + Schema\Interfaces\IEdmEntityTypeReference.cs + + + Schema\Interfaces\IEdmEnumMember.cs + + + Schema\Interfaces\IEdmEnumMemberValue.cs + + + Schema\Interfaces\IEdmEnumType.cs + + + Schema\Interfaces\IEdmEnumTypeReference.cs + + + Schema\Interfaces\IEdmExpression.cs + + + Schema\Interfaces\IEdmFullNamedElement.cs + + + Schema\Interfaces\IEdmFunction.cs + + + Schema\Interfaces\IEdmFunctionImport.cs + + + Schema\Interfaces\IEdmInclude.cs + + + Schema\Interfaces\IEdmIncludeAnnotations.cs + + + Schema\Interfaces\IEdmLocatable.cs + + + Schema\Interfaces\IEdmModel.cs + + + Schema\Interfaces\IEdmNamedElement.cs + + + Schema\Interfaces\IEdmNavigationProperty.cs + + + Schema\Interfaces\IEdmNavigationPropertyBinding.cs + + + Schema\Interfaces\IEdmNavigationSource.cs + + + Schema\Interfaces\IEdmOperation.cs + + + Schema\Interfaces\IEdmOperationImport.cs + + + Schema\Interfaces\IEdmOperationParameter.cs + + + Schema\Interfaces\IEdmOperationReturn.cs + + + Schema\Interfaces\IEdmOptionalParameter.cs + + + Schema\Interfaces\IEdmPathExpression.cs + + + Schema\Interfaces\IEdmPathType.cs + + + Schema\Interfaces\IEdmPathTypeReference.cs + + + Schema\Interfaces\IEdmPrimitiveType.cs + + + Schema\Interfaces\IEdmPrimitiveTypeReference.cs + + + Schema\Interfaces\IEdmProperty.cs + + + Schema\Interfaces\IEdmReference.cs + + + Schema\Interfaces\IEdmReferentialConstraint.cs + + + Schema\Interfaces\IEdmSchemaElement.cs + + + Schema\Interfaces\IEdmSchemaType.cs + + + Schema\Interfaces\IEdmSingleton.cs + + + Schema\Interfaces\IEdmSpatialTypeReference.cs + + + Schema\Interfaces\IEdmStringTypeReference.cs + + + Schema\Interfaces\IEdmStructuralProperty.cs + + + Schema\Interfaces\IEdmStructuredType.cs + + + Schema\Interfaces\IEdmStructuredTypeReference.cs + + + Schema\Interfaces\IEdmTemporalTypeReference.cs + + + Schema\Interfaces\IEdmType.cs + + + Schema\Interfaces\IEdmTypeDefinition.cs + + + Schema\Interfaces\IEdmTypeDefinitionReference.cs + + + Schema\Interfaces\IEdmTypeReference.cs + + + Schema\Interfaces\IEdmUnknownEntitySet.cs + + + Schema\Interfaces\IEdmUntypedType.cs + + + Schema\Interfaces\IEdmUntypedTypeReference.cs + + + Schema\TimeOfDay.cs + + + Validation\DuplicateOperationValidator.cs + + + Validation\EdmError.cs + + + Validation\EdmErrorCode.cs + + + Validation\EdmValidator.cs + + + Validation\ExpressionTypeChecker.cs + + + Validation\InterfaceValidator.cs + + + Validation\ObjectLocation.cs + + + Validation\ValidationContext.cs + + + Validation\ValidationExtensionMethods.cs + + + Validation\ValidationHelper.cs + + + Validation\ValidationRule.cs + + + Validation\ValidationRules.cs + + + Validation\ValidationRuleSet.cs + + + + + + + + + + + + + + + Vocabularies\AlternateKeysVocabularyConstants.cs + + + Vocabularies\AlternateKeysVocabularyModel.cs + + + Vocabularies\CommunityVocabularyConstants.cs + + + Vocabularies\CommunityVocabularyModel.cs + + + Vocabularies\AuthorizationVocabularyModel.cs + + + Vocabularies\Annotations\EdmDirectValueAnnotation.cs + + + Vocabularies\Annotations\EdmDirectValueAnnotationBinding.cs + + + Vocabularies\Annotations\EdmDirectValueAnnotationsManager.cs + + + Vocabularies\Annotations\EdmPropertyValueBinding.cs + + + Vocabularies\Annotations\EdmTerm.cs + + + Vocabularies\Annotations\EdmTypedDirectValueAnnotationBinding.cs + + + Vocabularies\Annotations\EdmVocabularyAnnotation.cs + + + Vocabularies\Annotations\IEdmDirectValueAnnotation.cs + + + Vocabularies\Annotations\IEdmDirectValueAnnotationBinding.cs + + + Vocabularies\Annotations\IEdmDirectValueAnnotationsManager.cs + + + Vocabularies\Annotations\IEdmPropertyValueBinding.cs + + + Vocabularies\Annotations\IEdmTerm.cs + + + Vocabularies\Annotations\IEdmVocabularyAnnotatable.cs + + + Vocabularies\Annotations\IEdmVocabularyAnnotation.cs + + + Vocabularies\CapabilitiesVocabularyConstants.cs + + + Vocabularies\CapabilitiesVocabularyModel.cs + + + Vocabularies\CoreVocabularyConstants.cs + + + Vocabularies\CoreVocabularyModel.cs + + + Vocabularies\ValidationVocabularyModel.cs + + + Vocabularies\VocabularyModelProvider.cs + + + Vocabularies\EdmExpressionEvaluator.cs + + + Vocabularies\EdmToClrConverter.cs + + + Vocabularies\EdmToClrEvaluator.cs + + + Vocabularies\Expressions\EdmApplyExpression.cs + + + Vocabularies\Expressions\EdmCastExpression.cs + + + Vocabularies\Expressions\EdmCollectionExpression.cs + + + Vocabularies\Expressions\EdmEnumMemberExpression.cs + + + Vocabularies\Expressions\EdmIfExpression.cs + + + Vocabularies\Expressions\EdmIsTypeExpression.cs + + + Vocabularies\Expressions\EdmLabeledExpression.cs + + + Vocabularies\Expressions\EdmLabeledExpressionReferenceExpression.cs + + + Vocabularies\Expressions\EdmNavigationPropertyPathExpression.cs + + + Vocabularies\Expressions\EdmNullExpression.cs + + + Vocabularies\Expressions\EdmPropertyConstructor.cs + + + Vocabularies\Expressions\EdmPropertyPathExpression.cs + + + Vocabularies\Expressions\EdmRecordExpression.cs + + + Vocabularies\Expressions\IEdmApplyExpression.cs + + + Vocabularies\Expressions\IEdmBinaryConstantExpression.cs + + + Vocabularies\Expressions\IEdmBooleanConstantExpression.cs + + + Vocabularies\Expressions\IEdmCastExpression.cs + + + Vocabularies\Expressions\IEdmCollectionExpression.cs + + + Vocabularies\Expressions\IEdmDateConstantExpression.cs + + + Vocabularies\Expressions\IEdmDateTimeOffsetConstantExpression.cs + + + Vocabularies\Expressions\IEdmDecimalConstantExpression.cs + + + Vocabularies\Expressions\IEdmDurationConstantExpression.cs + + + Vocabularies\Expressions\IEdmEnumMemberExpression.cs + + + Vocabularies\Expressions\IEdmFloatingConstantExpression.cs + + + Vocabularies\Expressions\IEdmGuidConstantExpression.cs + + + Vocabularies\Expressions\IEdmIfExpression.cs + + + Vocabularies\Expressions\IEdmIntegerConstantExpression.cs + + + Vocabularies\Expressions\IEdmIsTypeExpression.cs + + + Vocabularies\Expressions\IEdmLabeledExpression.cs + + + Vocabularies\Expressions\IEdmLabeledExpressionReferenceExpression.cs + + + Vocabularies\Expressions\IEdmNullExpression.cs + + + Vocabularies\Expressions\IEdmPropertyConstructor.cs + + + Vocabularies\Expressions\IEdmRecordExpression.cs + + + Vocabularies\Expressions\IEdmStringConstantExpression.cs + + + Vocabularies\Expressions\IEdmTimeOfDayConstantExpression.cs + + + Vocabularies\TryCreateObjectInstance.cs + + + Vocabularies\Values\EdmBinaryConstant.cs + + + Vocabularies\Values\EdmBooleanConstant.cs + + + Vocabularies\Values\EdmCollectionValue.cs + + + Vocabularies\Values\EdmDateConstant.cs + + + Vocabularies\Values\EdmDateTimeOffsetConstant.cs + + + Vocabularies\Values\EdmDecimalConstant.cs + + + Vocabularies\Values\EdmDurationConstant.cs + + + Vocabularies\Values\EdmEnumValue.cs + + + Vocabularies\Values\EdmFloatingConstant.cs + + + Vocabularies\Values\EdmGuidConstant.cs + + + Vocabularies\Values\EdmIntegerConstant.cs + + + Vocabularies\Values\EdmPropertyValue.cs + + + Vocabularies\Values\EdmStringConstant.cs + + + Vocabularies\Values\EdmStructuredValue.cs + + + Vocabularies\Values\EdmTimeOfDayConstant.cs + + + Vocabularies\Values\EdmValue.cs + + + Vocabularies\Values\IEdmBinaryValue.cs + + + Vocabularies\Values\IEdmBooleanValue.cs + + + Vocabularies\Values\IEdmCollectionValue.cs + + + Vocabularies\Values\IEdmDateTimeOffsetValue.cs + + + Vocabularies\Values\IEdmDateValue.cs + + + Vocabularies\Values\IEdmDecimalValue.cs + + + Vocabularies\Values\IEdmDelayedValue.cs + + + Vocabularies\Values\IEdmDurationValue.cs + + + Vocabularies\Values\IEdmEnumValue.cs + + + Vocabularies\Values\IEdmFloatingValue.cs + + + Vocabularies\Values\IEdmGuidValue.cs + + + Vocabularies\Values\IEdmIntegerValue.cs + + + Vocabularies\Values\IEdmNullValue.cs + + + Vocabularies\Values\IEdmPrimitiveValue.cs + + + Vocabularies\Values\IEdmPropertyValue.cs + + + Vocabularies\Values\IEdmStringValue.cs + + + Vocabularies\Values\IEdmStructuredValue.cs + + + Vocabularies\Values\IEdmTimeOfDayValue.cs + + + Vocabularies\Values\IEdmValue.cs + + + + + false + + + false + + + + + TextTemplatingFileGenerator + Microsoft.OData.Edm.cs + + + True + True + Microsoft.OData.Edm.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Edm.cs + + + True + True + Parameterized.Microsoft.OData.Edm.tt + true + + + + + + + false + + + false + + + $(SuiteBinPath) + + + + + + + + Vocabularies\AlternateKeysVocabularies.xml + + + Vocabularies\CommunityVocabularies.xml + + + Vocabularies\AuthorizationVocabularies.xml + + + Vocabularies\CapabilitiesVocabularies.xml + + + Vocabularies\CoreVocabularies.xml + + + Vocabularies\ValidationVocabularies.xml + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/project.json b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/project.json new file mode 100644 index 0000000..067f5c2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NetStandard/project.json @@ -0,0 +1,10 @@ +{ + "supports": {}, + "dependencies": { + "NETStandard.Library": "1.6.0", + "System.Resources.ResourceManager": "4.3.0" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Net35.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Net35.Release.nuspec new file mode 100644 index 0000000..68918ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Net35.Release.nuspec @@ -0,0 +1,26 @@ + + + + EdmLib + Microsoft.OData.Edm.Net35 + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionNuGetSemantic$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Classes to represent, construct, parse, serialize and validate entity data models. Supports OData v4. + Classes to represent, construct, parse, serialize and validate entity data models. Supports OData v4. Targets .NET Lib with support for .NET 3.5. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Nightly.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Nightly.Release.nuspec new file mode 100644 index 0000000..8886405 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Nightly.Release.nuspec @@ -0,0 +1,29 @@ + + + + EdmLib + Microsoft.OData.Edm + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionFullSemantic$-Nightly$NightlyBuildVersion$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Classes to represent, construct, parse, serialize and validate entity data models. Supports OData v4. + Classes to represent, construct, parse, serialize and validate entity data models. Supports OData v4. Targets .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Release.nuspec new file mode 100644 index 0000000..96d1f05 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.NuGet/Microsoft.OData.Edm.Release.nuspec @@ -0,0 +1,30 @@ + + + + EdmLib + Microsoft.OData.Edm + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionNuGetSemantic$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Classes to represent, construct, parse, serialize and validate entity data models. Supports OData v4. + Classes to represent, construct, parse, serialize and validate entity data models. Supports OData v4. Targets .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + http://odata.github.io/odata.net/v7/#ODL-$VersionFullSemantic$ + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.props b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.props new file mode 100644 index 0000000..06e2ef9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Build.props @@ -0,0 +1,11 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + $(MSBuildThisFileDirectory)Microsoft.OData.Edm.StyleCop + + + + + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Cache.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Cache.cs new file mode 100644 index 0000000..666ddb9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Cache.cs @@ -0,0 +1,137 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; + +namespace Microsoft.OData.Edm +{ + /// + /// Provides a caching mechanism for semantic properties. + /// + /// Type of the element that contains the cached property + /// Type of the cached property + //// Using a Cache requires a function to compute the cached value at first access and a function + //// to create a result when the computation of the value involves a cyclic dependency. (The second + //// function can be omitted if a cycle is impossible.) + //// + //// Cache provides concurrency safety. + internal class Cache + { + private object value = CacheHelper.Unknown; + + // In order to detect the boundaries of a cycle, we use two sentinel values. When we encounter the first sentinel, we know that a cycle exists and that we are a point on that cycle. + // When we reach an instance of the second sentinel, we know we have made a complete circuit of the cycle and that every node in the cycle has been marked with the second sentinel. + public TProperty GetValue(TContainer container, Func compute, Func onCycle) + { + // If a cycle is present, locking on the cache object can produce a deadlock (if one thread asks for one node in the cycle and another + // thread asks for another). If a cycle is possible, onCycle is required to be nonnull and the same--because there is one instance per + // property per TContainer type--for all participants in the cycle. Locking on onCycle therefore locks out all other computations of + // the property for all instances of TContainer, which by definition includes all participants in a cycle. If no cycle is possible, + // locking on the cache object allows computation of the property for other instances to occur in parallel and so is minimally selfish. + object lockOn = (object)onCycle ?? this; + object result = this.value; + + if (result == CacheHelper.Unknown) + { + lock (lockOn) + { + // If another thread computed a value, use that. If the value is still unknown after aquiring the lock, compute the value. + if (this.value == CacheHelper.Unknown) + { + this.value = CacheHelper.CycleSentinel; + TProperty computedValue; + try + { + computedValue = compute(container); + } + catch + { + this.value = CacheHelper.Unknown; + throw; + } + + // If this.value changed during computation, this cache was involved in a cycle and this.value was already set to the onCycle value + Debug.Assert(this.value != CacheHelper.SecondPassCycleSentinel, "Cycles should already have their cycle value set"); + if (this.value == CacheHelper.CycleSentinel) + { + this.value = typeof(TProperty) == typeof(bool) ? CacheHelper.BoxedBool((bool)(object)computedValue) : computedValue; + } + } + + result = this.value; + } + } + else if (result == CacheHelper.CycleSentinel) + { + lock (lockOn) + { + // If another thread computed a value, use that. If the value is still a sentinel after acquiring the lock, + // by definition this thread is the one computing the value. (Otherwise, the lock taken when the value was + // Unknown would still be in force.) + if (this.value == CacheHelper.CycleSentinel) + { + this.value = CacheHelper.SecondPassCycleSentinel; + try + { + compute(container); + } + catch + { + this.value = CacheHelper.CycleSentinel; + throw; + } + + if (this.value == CacheHelper.SecondPassCycleSentinel) + { + this.value = onCycle(container); + } + } + else if (this.value == CacheHelper.Unknown) + { + // Another thread cleared the cache. + return this.GetValue(container, compute, onCycle); + } + + result = this.value; + } + } + else if (result == CacheHelper.SecondPassCycleSentinel) + { + lock (lockOn) + { + // If another thread computed a value, use that. If the value is still a sentinel after acquiring the lock, + // by definition this thread is the one computing the value. (Otherwise, the lock taken when the value was + // Unknown would still be in force.) + if (this.value == CacheHelper.SecondPassCycleSentinel) + { + this.value = onCycle(container); + } + else if (this.value == CacheHelper.Unknown) + { + // Another thread cleared the cache. + return this.GetValue(container, compute, onCycle); + } + + result = this.value; + } + } + + return (TProperty)result; + } + + public void Clear(Func onCycle) + { + lock ((object)onCycle ?? this) + { + if (this.value != CacheHelper.CycleSentinel && this.value != CacheHelper.SecondPassCycleSentinel) + { + this.value = CacheHelper.Unknown; + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/CacheHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/CacheHelper.cs new file mode 100644 index 0000000..eab7c60 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/CacheHelper.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Helper for Cache class. + /// + internal static class CacheHelper + { + internal static readonly object Unknown = new object(); + internal static readonly object CycleSentinel = new object(); + internal static readonly object SecondPassCycleSentinel = new object(); + + private static readonly object BoxedTrue = true; + private static readonly object BoxedFalse = false; + + internal static object BoxedBool(bool value) + { + return value ? BoxedTrue : BoxedFalse; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlConstants.cs new file mode 100644 index 0000000..8441935 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlConstants.cs @@ -0,0 +1,271 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Constants for CSDL XML. + /// + public static class CsdlConstants + { + #region Public Constants + + #region EDMX Versions + + /// + /// Version 4.0 of EDMX, defined in EDMX namespace "http://docs.oasis-open.org/odata/ns/edmx". + /// + public static readonly Version EdmxVersion4 = EdmConstants.EdmVersion4; + + /// + /// Version 4.01 of EDMX, defined in EDMX namespace "http://docs.oasis-open.org/odata/ns/edmx". + /// + public static readonly Version EdmxVersion401 = EdmConstants.EdmVersion401; + + #endregion + + /// + /// The current latest version of EDMX. + /// + public static readonly Version EdmxVersionLatest = EdmxVersion401; + + #endregion + + #region CSDL + internal const string CsdlFileExtension = ".csdl"; + + internal const string EdmOasisNamespace = "http://docs.oasis-open.org/odata/ns/edm"; + + internal const string SchemaNamespaceAnnotation = "SchemaNamespace"; + internal const string AnnotationSerializationLocationAnnotation = "AnnotationSerializationLocation"; + internal const string NamespacePrefixAnnotation = "NamespacePrefix"; + internal const string IsEnumMemberValueExplicitAnnotation = "IsEnumMemberValueExplicit"; + internal const string IsSerializedAsElementAnnotation = "IsSerializedAsElement"; + internal const string NamespaceAliasAnnotation = "NamespaceAlias"; + internal const string UsedNamespacesAnnotation = "UsedNamespaces"; + internal const string ReferencesAnnotation = "References"; + internal const string PrimitiveValueConverterMapAnnotation = "PrimitiveValueConverterMap"; + + internal const string Attribute_Abstract = "Abstract"; + internal const string Attribute_Action = "Action"; + internal const string Attribute_Alias = "Alias"; + internal const string Attribute_AnnotationPath = "AnnotationPath"; + internal const string Attribute_AppliesTo = "AppliesTo"; + internal const string Attribute_BaseType = "BaseType"; + internal const string Attribute_Binary = "Binary"; + internal const string Attribute_Bool = "Bool"; + internal const string Attribute_ContainsTarget = "ContainsTarget"; + internal const string Attribute_Date = "Date"; + internal const string Attribute_DateTimeOffset = "DateTimeOffset"; + internal const string Attribute_Decimal = "Decimal"; + internal const string Attribute_DefaultValue = "DefaultValue"; + internal const string Attribute_ElementType = "ElementType"; + internal const string Attribute_Extends = "Extends"; + internal const string Attribute_EntityType = "EntityType"; + internal const string Attribute_EntitySet = "EntitySet"; + internal const string Attribute_EntitySetPath = "EntitySetPath"; + internal const string Attribute_EnumMember = "EnumMember"; + internal const string Attribute_Float = "Float"; + internal const string Attribute_Function = "Function"; + internal const string Attribute_Guid = "Guid"; + internal const string Attribute_HasStream = "HasStream"; + internal const string Attribute_Int = "Int"; + internal const string Attribute_IncludeInServiceDocument = "IncludeInServiceDocument"; + internal const string Attribute_IsBound = "IsBound"; + internal const string Attribute_IsComposable = "IsComposable"; + internal const string Attribute_IsFlags = "IsFlags"; + internal const string Attribute_MaxLength = EdmConstants.FacetName_MaxLength; + internal const string Attribute_Name = "Name"; + internal const string Attribute_Namespace = "Namespace"; + internal const string Attribute_NavigationPropertyPath = "NavigationPropertyPath"; + internal const string Attribute_Nullable = EdmConstants.FacetName_Nullable; + internal const string Attribute_OpenType = "OpenType"; + internal const string Attribute_Partner = "Partner"; + internal const string Attribute_Path = "Path"; + internal const string Attribute_Precision = EdmConstants.FacetName_Precision; + internal const string Attribute_Property = "Property"; + internal const string Attribute_PropertyPath = "PropertyPath"; + internal const string Attribute_ReferencedProperty = "ReferencedProperty"; + internal const string Attribute_Qualifier = "Qualifier"; + internal const string Attribute_Scale = EdmConstants.FacetName_Scale; + internal const string Attribute_Srid = EdmConstants.FacetName_Srid; + internal const string Attribute_String = "String"; + internal const string Attribute_Target = "Target"; + internal const string Attribute_Term = "Term"; + internal const string Attribute_Duration = "Duration"; + internal const string Attribute_TimeOfDay = "TimeOfDay"; + internal const string Attribute_Type = "Type"; + internal const string Attribute_UnderlyingType = "UnderlyingType"; + internal const string Attribute_Unicode = "Unicode"; + internal const string Attribute_Value = "Value"; + + internal const string Element_Action = "Action"; + internal const string Element_ActionImport = "ActionImport"; + internal const string Element_Annotation = "Annotation"; + internal const string Element_Annotations = "Annotations"; + internal const string Element_Apply = "Apply"; + internal const string Element_Binary = "Binary"; + internal const string Element_Bool = "Bool"; + internal const string Element_Cast = "Cast"; + internal const string Element_Collection = "Collection"; + internal const string Element_CollectionType = "CollectionType"; + internal const string Element_ComplexType = "ComplexType"; + internal const string Element_Date = "Date"; + internal const string Element_DateTimeOffset = "DateTimeOffset"; + internal const string Element_Decimal = "Decimal"; + internal const string Element_EntityContainer = "EntityContainer"; + internal const string Element_EntitySet = "EntitySet"; + internal const string Element_EntitySetReference = "EntitySetReference"; + internal const string Element_EntityType = "EntityType"; + internal const string Element_EnumMember = "EnumMember"; + internal const string Element_EnumType = "EnumType"; + internal const string Element_Float = "Float"; + internal const string Element_Guid = "Guid"; + internal const string Element_Function = "Function"; + internal const string Element_FunctionImport = "FunctionImport"; + internal const string Element_FunctionReference = "FunctionReference"; + internal const string Element_If = "If"; + internal const string Element_IsType = "IsType"; + internal const string Element_Int = "Int"; + internal const string Element_Key = "Key"; + internal const string Element_LabeledElement = "LabeledElement"; + internal const string Element_LabeledElementReference = "LabeledElementReference"; + internal const string Element_LongDescription = "LongDescription"; + internal const string Element_Member = "Member"; + internal const string Element_NavigationProperty = "NavigationProperty"; + internal const string Element_NavigationPropertyBinding = "NavigationPropertyBinding"; + internal const string Element_NavigationPropertyPath = "NavigationPropertyPath"; + internal const string Element_Null = "Null"; + internal const string Element_OnDelete = "OnDelete"; + internal const string Element_Parameter = "Parameter"; + internal const string Element_ParameterReference = "ParameterReference"; + internal const string Element_Path = "Path"; + internal const string Element_Property = "Property"; + internal const string Element_PropertyPath = "PropertyPath"; + internal const string Element_PropertyRef = "PropertyRef"; + internal const string Element_PropertyReference = "PropertyReference"; + internal const string Element_PropertyValue = "PropertyValue"; + internal const string Element_Record = "Record"; + internal const string Element_ReferenceType = "ReferenceType"; + internal const string Element_ReferentialConstraint = "ReferentialConstraint"; + internal const string Element_ReturnType = "ReturnType"; + internal const string Element_Singleton = "Singleton"; + internal const string Element_Schema = "Schema"; + internal const string Element_String = "String"; + internal const string Element_Summary = "Summary"; + internal const string Element_Duration = "Duration"; + internal const string Element_Term = "Term"; + internal const string Element_TimeOfDay = "TimeOfDay"; + internal const string Element_TypeDefinition = "TypeDefinition"; + internal const string Element_TypeRef = "TypeRef"; + + internal const string Value_Cascade = "Cascade"; + internal const string Value_Collection = "Collection"; + internal const string Value_EndMany = "*"; + internal const string Value_EndOptional = "0..1"; + internal const string Value_EndRequired = "1"; + internal const string Value_Max = EdmConstants.Value_Max; + internal const string Value_None = "None"; + internal const string Value_Ref = "Ref"; + internal const string Value_SridVariable = EdmConstants.Value_SridVariable; + internal const string Value_ScaleVariable = EdmConstants.Value_ScaleVariable; + + internal const string TypeName_Untyped = "Edm.Untyped"; + internal const string TypeName_Untyped_Short = "Untyped"; + + internal const string TypeName_Entity = "Edm.EntityType"; + internal const string TypeName_Entity_Short = "EntityType"; + + internal const string TypeName_Complex = "Edm.ComplexType"; + internal const string TypeName_Complex_Short = "ComplexType"; + + internal const bool Default_Abstract = false; + internal const bool Default_ContainsTarget = false; + internal const bool Default_HasStream = false; + internal const bool Default_IncludeInServiceDocument = false; + internal const bool Default_IsAtomic = false; + internal const bool Default_IsBound = false; + internal const bool Default_IsComposable = false; + internal const bool Default_IsFlags = false; + internal const bool Default_OpenType = false; + internal const bool Default_Nullable = true; + internal const bool Default_IsUnicode = true; + internal const int Default_TemporalPrecision = 0; + internal const int Default_SpatialGeographySrid = 4326; + internal const int Default_SpatialGeometrySrid = 0; + internal const int Default_UnspecifiedSrid = Int32.MinValue; + internal const int Default_Scale = 0; + + internal const int Max_NameLength = 480; + internal const int Max_NamespaceLength = 512; + + #endregion + + #region EDMX + + internal const string EdmxFileExtension = ".edmx"; + + internal const string EdmxOasisNamespace = "http://docs.oasis-open.org/odata/ns/edmx"; + + internal const string ODataMetadataNamespace = "http://docs.oasis-open.org/odata/ns/metadata"; + + /// + /// The local name of the annotation that stores EDMX version of a model. + /// + internal const string EdmxVersionAnnotation = "EdmxVersion"; + + internal const string Prefix_Edmx = "edmx"; + internal const string Prefix_ODataMetadata = "m"; + + internal const string Attribute_TargetNamespace = "TargetNamespace"; + internal const string Attribute_TermNamespace = "TermNamespace"; + internal const string Attribute_Version = "Version"; + internal const string Attribute_Uri = "Uri"; + + internal const string Element_ConceptualModels = "ConceptualModels"; + internal const string Element_Edmx = "Edmx"; + internal const string Element_Runtime = "Runtime"; + internal const string Element_Reference = "Reference"; // e.g. + internal const string Element_Include = "Include"; + internal const string Element_IncludeAnnotations = "IncludeAnnotations"; + internal const string Element_DataServices = "DataServices"; + + internal const string OperationReturnExternalTarget = "$ReturnType"; + + #endregion + + internal static Dictionary SupportedVersions = new Dictionary() + { + { EdmConstants.EdmVersion4, new string[] { EdmOasisNamespace } }, + { EdmConstants.EdmVersion401, new string[] { EdmOasisNamespace } }, + }; + + internal static Dictionary SupportedEdmxVersions = new Dictionary + { + { EdmxVersion4, EdmxOasisNamespace }, + { EdmxVersion401, EdmxOasisNamespace }, + }; + + // Maps a namespace to a default EdmxVersion. + // Note that there may be multiple namespaces mapped to the same EdmxVersion. + internal static Dictionary SupportedEdmxNamespaces = new Dictionary + { + { EdmxOasisNamespace, EdmxVersion4 }, + }; + + internal static Dictionary EdmToEdmxVersions = new Dictionary + { + { EdmConstants.EdmVersion4, EdmxVersion4 }, + { EdmConstants.EdmVersion401, EdmxVersion401 }, + }; + + internal static Dictionary EdmxToEdmVersions = EdmToEdmxVersions.ToDictionary(v => v.Value, v => v.Key); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlLocation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlLocation.cs new file mode 100644 index 0000000..6c1b8ab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlLocation.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Globalization; + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Defines a location in a XML file. + /// + public class CsdlLocation : EdmLocation + { + internal CsdlLocation(int number, int position) + : this(null, number, position) + { + } + + internal CsdlLocation(string source, int number, int position) + { + this.Source = source; + this.LineNumber = number; + this.LinePosition = position; + } + + /// + /// Gets the source of the file. + /// + /// + /// Value 'null' means the source is unknown. + /// Empty value means there is no source (e.g., constructed from in-memory string). + /// Non-empty value indicates there is a certain source. + /// + public string Source { get; private set; } + + /// + /// Gets the line number in the file. + /// + public int LineNumber { get; private set; } + + /// + /// Gets the position in the line. + /// + public int LinePosition { get; private set; } + + /// + /// Gets a string representation of the location. + /// + /// A string representation of the location. + public override string ToString() + { + return "(" + Convert.ToString(this.LineNumber, CultureInfo.InvariantCulture) + ", " + Convert.ToString(this.LinePosition, CultureInfo.InvariantCulture) + ")"; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReader.cs new file mode 100644 index 0000000..707183e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReader.cs @@ -0,0 +1,726 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Csdl.CsdlSemantics; +using Microsoft.OData.Edm.Csdl.Parsing; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies.Community.V1; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Provides CSDL parsing services for EDM models. + /// + public class CsdlReader + { + private static readonly Dictionary EmptyParserLookup = new Dictionary(); + private readonly Dictionary edmxParserLookup; + private readonly Dictionary runtimeParserLookup; + private readonly Dictionary conceptualModelsParserLookup; + private readonly Dictionary dataServicesParserLookup; + private readonly XmlReader reader; + private readonly List errors; + private readonly List edmReferences; + private readonly CsdlParser csdlParser; + private readonly Func getReferencedModelReaderFunc; // Url -> XmlReader + + /// + /// True when either Runtime or DataServices node have been processed. + /// + private bool targetParsed; + + /// + /// Ignore the unexpected attributes and elements. + /// + private bool ignoreUnexpectedAttributesAndElements; + + /// + /// Indicates where the document comes from. + /// + private string source; + + /// + /// Constructor + /// + /// The XmlReader for current CSDL doc + /// The function to load referenced model xml. If null, will stop loading the referenced model. + private CsdlReader(XmlReader reader, Func getReferencedModelReaderFunc) + { + this.reader = reader; + this.getReferencedModelReaderFunc = getReferencedModelReaderFunc; + this.errors = new List(); + this.edmReferences = new List(); + this.csdlParser = new CsdlParser(); + + // Setup the edmx parser. + this.edmxParserLookup = new Dictionary + { + { CsdlConstants.Element_DataServices, this.ParseDataServicesElement }, + { CsdlConstants.Element_Reference, this.ParseReferenceElement }, + { CsdlConstants.Element_Runtime, this.ParseRuntimeElement } + }; + this.dataServicesParserLookup = new Dictionary + { + { CsdlConstants.Element_Schema, this.ParseSchemaElement } + }; + this.runtimeParserLookup = new Dictionary + { + { CsdlConstants.Element_ConceptualModels, this.ParseConceptualModelsElement } + }; + this.conceptualModelsParserLookup = new Dictionary + { + { CsdlConstants.Element_Schema, this.ParseSchemaElement } + }; + } + + /// + /// Tries parsing the given CSDL artifact for an IEdmModel. + /// + /// XmlReader containing the CSDL artifact. + /// The model generated by parsing + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(XmlReader reader, out IEdmModel model, out IEnumerable errors) + { + CsdlReader edmxReader = new CsdlReader(reader, null); + return edmxReader.TryParse(Enumerable.Empty(), true /*includeDefaultVocabularies*/, out model, out errors); + } + + /// + /// Tries parsing the given CSDL artifact for an IEdmModel. + /// + /// XmlReader containing the CSDL artifact. + /// Ignore the unexpected attributes and elements in schema. + /// The model generated by parsing + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(XmlReader reader, bool ignoreUnexpectedAttributesAndElements, out IEdmModel model, out IEnumerable errors) + { + CsdlReader edmxReader = new CsdlReader(reader, null); + edmxReader.ignoreUnexpectedAttributesAndElements = ignoreUnexpectedAttributesAndElements; + return edmxReader.TryParse(Enumerable.Empty(), true /*includeDefaultVocabularies*/, out model, out errors); + } + + /// + /// Returns an IEdmModel for the given CSDL artifact. + /// + /// XmlReader containing the CSDL artifact. + /// The model generated by parsing. + public static IEdmModel Parse(XmlReader reader) + { + IEdmModel model; + IEnumerable parseErrors; + if (!TryParse(reader, out model, out parseErrors)) + { + throw new EdmParseException(parseErrors); + } + + return model; + } + + /// + /// Tries parsing the given CSDL artifact for an IEdmModel. + /// + /// XmlReader containing the CSDL artifact. + /// The function to load referenced model xml. If null, will stop loading the referenced models. Normally it should throw no exception. + /// The model generated by parsing + /// Errors reported while parsing. + /// If getReferencedModelReaderFunc throws exception, it won't be caught internally but will be thrown out for caller to handle. + /// Success of the parse operation. + /// + /// User should handle the disposal of XmlReader created by getReferencedModelReaderFunc. + /// + public static bool TryParse(XmlReader reader, Func getReferencedModelReaderFunc, out IEdmModel model, out IEnumerable errors) + { + CsdlReader edmxReader = new CsdlReader(reader, getReferencedModelReaderFunc); + return edmxReader.TryParse(Enumerable.Empty(), true /*includeDefaultVocabularies*/, out model, out errors); + } + + /// + /// Tries parsing the given CSDL artifact for an IEdmModel. + /// + /// XmlReader containing the CSDL artifact. + /// Models to be referenced by the created model. + /// The model generated by parsing + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(XmlReader reader, IEnumerable references, out IEdmModel model, out IEnumerable errors) + { + return TryParse(reader, references, true /*includeDefaultVocabularies*/, out model, out errors); + } + + /// + /// Tries parsing the given CSDL artifact for an IEdmModel. + /// + /// XmlReader containing the CSDL artifact. + /// Models to be referenced by the created model. + /// A value indicating enable/disable the built-in vocabulary supporting. + /// The model generated by parsing + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(XmlReader reader, IEnumerable references, bool includeDefaultVocabularies, out IEdmModel model, out IEnumerable errors) + { + EdmUtil.CheckArgumentNull(references, "references"); + CsdlReader edmxReader = new CsdlReader(reader, null); + return edmxReader.TryParse(references, includeDefaultVocabularies, out model, out errors); + } + + /// + /// Tries parsing the given CSDL artifact for an IEdmModel. + /// + /// XmlReader containing the CSDL artifact. + /// Model to be referenced by the created model. + /// The model generated by parsing + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(XmlReader reader, IEdmModel reference, out IEdmModel model, out IEnumerable errors) + { + CsdlReader edmxReader = new CsdlReader(reader, null); + return edmxReader.TryParse(new[] { reference }, true /*includeDefaultVocabularies*/, out model, out errors); + } + + /// + /// Returns an IEdmModel for the given CSDL artifact. + /// + /// XmlReader containing the EDMX artifact. + /// Models to be referenced by the created model. + /// The model generated by parsing. + public static IEdmModel Parse(XmlReader reader, IEnumerable referencedModels) + { + IEdmModel model; + IEnumerable parseErrors; + if (!TryParse(reader, referencedModels, out model, out parseErrors)) + { + throw new EdmParseException(parseErrors); + } + + return model; + } + + /// + /// Returns an IEdmModel for the given CSDL artifact. + /// + /// XmlReader containing the CSDL artifact. + /// Model to be referenced by the created model. + /// The model generated by parsing. + public static IEdmModel Parse(XmlReader reader, IEdmModel referencedModel) + { + IEdmModel model; + IEnumerable parseErrors; + if (!TryParse(reader, referencedModel, out model, out parseErrors)) + { + throw new EdmParseException(parseErrors); + } + + return model; + } + + /// + /// Returns an IEdmModel for the given CSDL artifact. + /// + /// XmlReader containing the CSDL artifact. + /// The function to load referenced model xml. If null, will stop loading the referenced model. + /// The model generated by parsing. + public static IEdmModel Parse(XmlReader reader, Func getReferencedModelReaderFunc) + { + IEdmModel model; + IEnumerable parseErrors; + if (!TryParse(reader, getReferencedModelReaderFunc, out model, out parseErrors)) + { + throw new EdmParseException(parseErrors); + } + + return model; + } + + /// + /// Tries parsing the given CSDL artifact for an IEdmModel. + /// + /// XmlReader containing the CSDL artifact. + /// Models to be referenced by the created model. + /// CsdlReader settings for current parser. + /// The model generated by parsing + /// Errors reported while parsing. + /// If getReferencedModelReaderFunc throws exception, it won't be caught internally but will be thrown out for caller to handle. + /// Success of the parse operation. + /// + /// User should handle the disposal of XmlReader created by getReferencedModelReaderFunc. + /// + public static bool TryParse(XmlReader reader, IEnumerable references, CsdlReaderSettings settings, out IEdmModel model, out IEnumerable errors) + { + EdmUtil.CheckArgumentNull(references, "references"); + if (settings == null) + { + return TryParse(reader, references, out model, out errors); + } + + CsdlReader edmxReader = new CsdlReader(reader, settings.GetReferencedModelReaderFunc) + { + ignoreUnexpectedAttributesAndElements = settings.IgnoreUnexpectedAttributesAndElements + }; + return edmxReader.TryParse(references, true /*includeDefaultVocabularies*/, out model, out errors); + } + + /// + /// TryParse does not exist on all platforms, so implementing it here. + /// + /// Input string. + /// Parsed version. + /// False in case of failure. + private static bool TryParseVersion(string input, out Version version) + { + version = null; + + if (String.IsNullOrEmpty(input)) + { + return false; + } + + input = input.Trim(); + + var parts = input.Split('.'); + if (parts.Length != 2) + { + return false; + } + + int major; + int minor; + if (!int.TryParse(parts[0], out major) || !int.TryParse(parts[1], out minor)) + { + return false; + } + + version = new Version(major, minor); + return true; + } + + private bool TryParse(IEnumerable referencedModels, bool includeDefaultVocabularies, out IEdmModel model, out IEnumerable parsingErrors) + { + Version edmxVersion; + CsdlModel astModel; + + TryParseCsdlFileToCsdlModel(out edmxVersion, out astModel); + + if (!this.HasIntolerableError()) + { + List referencedAstModels = this.LoadAndParseReferencedCsdlFiles(edmxVersion); + + IEnumerable csdlErrors; + this.csdlParser.GetResult(out astModel, out csdlErrors); + if (csdlErrors != null) + { + this.errors.AddRange(csdlErrors.Except(this.errors)); + } + + if (!this.HasIntolerableError()) + { + CsdlSemanticsModel tmp = new CsdlSemanticsModel(astModel, new CsdlSemanticsDirectValueAnnotationsManager(), referencedAstModels, includeDefaultVocabularies); + + // add more referenced IEdmModels in addition to the above loaded CsdlModels. + tmp.AddToReferencedModels(referencedModels); + + model = tmp; + Debug.Assert(edmxVersion != null, "edmxVersion != null"); + model.SetEdmxVersion(edmxVersion); + Version edmVersion; + if (CsdlConstants.EdmxToEdmVersions.TryGetValue(edmxVersion, out edmVersion)) + { + model.SetEdmVersion(edmVersion); + } + } + else + { + model = null; + } + } + else + { + model = null; + } + + parsingErrors = this.errors; + + return !this.HasIntolerableError(); + } + + /// + /// Load and parse the referenced model but ignored any further referenced model. + /// + /// The main CSDL version. + /// A list of CsdlModel (no semantics) of the referenced models. + private List LoadAndParseReferencedCsdlFiles(Version mainCsdlVersion) + { + List referencedAstModels = new List(); + if (this.getReferencedModelReaderFunc == null) + { + // don't try to load Edm xml doc, but this.edmReferences's namespace-alias need to be used later. + return referencedAstModels; + } + + foreach (var edmReference in this.edmReferences) + { + if (!edmReference.Includes.Any() && !edmReference.IncludeAnnotations.Any()) + { + this.RaiseError(EdmErrorCode.ReferenceElementMustContainAtLeastOneIncludeOrIncludeAnnotationsElement, Strings.EdmxParser_InvalidReferenceIncorrectNumberOfIncludes); + continue; + } + + if (edmReference.Uri != null && (edmReference.Uri.OriginalString.EndsWith(CoreVocabularyConstants.VocabularyUrlSuffix, StringComparison.Ordinal) || + edmReference.Uri.OriginalString.EndsWith(CapabilitiesVocabularyConstants.VocabularyUrlSuffix, StringComparison.Ordinal) || + edmReference.Uri.OriginalString.EndsWith("/Org.OData.Authorization.V1.xml", StringComparison.Ordinal) || + edmReference.Uri.OriginalString.EndsWith("/Org.OData.Validation.V1.xml", StringComparison.Ordinal) || + edmReference.Uri.OriginalString.EndsWith("/Org.OData.Community.V1.xml", StringComparison.Ordinal) || + edmReference.Uri.OriginalString.EndsWith(AlternateKeysVocabularyConstants.VocabularyUrlSuffix, StringComparison.Ordinal))) + { + continue; + } + + XmlReader referencedXmlReader = this.getReferencedModelReaderFunc(edmReference.Uri); + if (referencedXmlReader == null) + { + this.RaiseError(EdmErrorCode.UnresolvedReferenceUriInEdmxReference, Strings.EdmxParser_UnresolvedReferenceUriInEdmxReference); + continue; + } + + // recursively use CsdlReader to parse sub edm: + CsdlReader referencedEdmxReader = new CsdlReader(referencedXmlReader, /*getReferencedModelReaderFunc*/ null); + referencedEdmxReader.source = edmReference.Uri != null ? edmReference.Uri.OriginalString : null; + referencedEdmxReader.ignoreUnexpectedAttributesAndElements = this.ignoreUnexpectedAttributesAndElements; + Version referencedEdmxVersion; + CsdlModel referencedAstModel; + if (referencedEdmxReader.TryParseCsdlFileToCsdlModel(out referencedEdmxVersion, out referencedAstModel)) + { + if (!mainCsdlVersion.Equals(referencedEdmxVersion)) + { + // TODO: REF add exception message + this.errors.Add(null); + } + + referencedAstModel.AddParentModelReferences(edmReference); + referencedAstModels.Add(referencedAstModel); + } + + this.errors.AddRange(referencedEdmxReader.errors); + } + + return referencedAstModels; + } + + /// + /// Parse CSDL xml doc into CsdlModel, error messages are stored in this.errors. + /// + /// The csdlVersion out. + /// The CsdlModel out. + /// True if succeeded. + private bool TryParseCsdlFileToCsdlModel(out Version csdlVersion, out CsdlModel csdlModel) + { + csdlVersion = null; + csdlModel = null; + try + { + // Advance to root element + if (this.reader.NodeType != XmlNodeType.Element) + { + while (this.reader.Read() && this.reader.NodeType != XmlNodeType.Element) + { + } + } + + // There must be a root element for all current artifacts + if (this.reader.EOF) + { + this.RaiseEmptyFile(); + return false; + } + + if (this.reader.LocalName != CsdlConstants.Element_Edmx || + !CsdlConstants.SupportedEdmxNamespaces.TryGetValue(this.reader.NamespaceURI, out csdlVersion)) + { + this.RaiseError(EdmErrorCode.UnexpectedXmlElement, Edm.Strings.XmlParser_UnexpectedRootElement(this.reader.Name, CsdlConstants.Element_Edmx)); + return false; + } + + csdlVersion = this.ParseEdmxElement(csdlVersion); + IEnumerable err; + if (!this.csdlParser.GetResult(out csdlModel, out err)) + { + this.errors.AddRange(err); + if (this.HasIntolerableError()) + { + return false; + } + } + } + catch (XmlException e) + { + this.errors.Add(new EdmError(new CsdlLocation(this.source, e.LineNumber, e.LinePosition), EdmErrorCode.XmlError, e.Message)); + return false; + } + + csdlModel.AddCurrentModelReferences(this.edmReferences); + return true; + } + + /// + /// Determine if there is any error that could not be ignored. + /// + /// True if there is any error that could not be ignored. + private bool HasIntolerableError() + { + if (this.ignoreUnexpectedAttributesAndElements) + { + return this.errors.Any(error => error.ErrorCode != EdmErrorCode.UnexpectedXmlElement && error.ErrorCode != EdmErrorCode.UnexpectedXmlAttribute); + } + + return this.errors.Any(); + } + + /// + /// All parse functions start with the reader pointing at the start tag of an element, and end after consuming the ending tag for the element. + /// + /// The current element name to be parsed. + /// The parsers for child elements of the current element. + private void ParseElement(string elementName, Dictionary elementParsers) + { + Debug.Assert(this.reader.LocalName == elementName, "Must call ParseElement on correct element type"); + if (this.reader.IsEmptyElement) + { + // Consume the tag. + this.reader.Read(); + } + else + { + // Consume the start tag. + this.reader.Read(); + while (this.reader.NodeType != XmlNodeType.EndElement) + { + if (this.reader.NodeType == XmlNodeType.Element) + { + if (elementParsers.ContainsKey(this.reader.LocalName)) + { + elementParsers[this.reader.LocalName](); + } + else + { + this.ParseElement(this.reader.LocalName, EmptyParserLookup); + } + } + else + { + if (!this.reader.Read()) + { + break; + } + } + } + + Debug.Assert(elementName == this.reader.LocalName, "The XmlReader should have thrown an error if the opening and closing tags do not match"); + + // Consume the ending tag. + this.reader.Read(); + } + } + + private Version ParseEdmxElement(Version edmxVersion) + { + Debug.Assert(this.reader.LocalName == CsdlConstants.Element_Edmx, "this.reader.LocalName == CsdlConstants.Element_Edmx"); + Debug.Assert(edmxVersion != null, "edmxVersion != null"); + + string edmxVersionString = this.GetAttributeValue(null, CsdlConstants.Attribute_Version); + Version edmxVersionFromAttribute = null; + if (edmxVersionString != null && (!TryParseVersion(edmxVersionString, out edmxVersionFromAttribute) || edmxVersionFromAttribute.Major != edmxVersion.Major)) + { + this.RaiseError(EdmErrorCode.InvalidVersionNumber, Edm.Strings.EdmxParser_EdmxVersionMismatch); + } + + this.ParseElement(CsdlConstants.Element_Edmx, this.edmxParserLookup); + + return edmxVersionFromAttribute ?? edmxVersion; + } + + private string GetAttributeValue(string namespaceUri, string localName) + { + //// OData BufferingXmlReader does not support API, so implementing it here. + + string elementNamespace = this.reader.NamespaceURI; + Debug.Assert(!String.IsNullOrEmpty(elementNamespace), "!String.IsNullOrEmpty(elementNamespace)"); + + string value = null; + bool hasAttributes = this.reader.MoveToFirstAttribute(); + while (hasAttributes) + { + if ((namespaceUri != null && this.reader.NamespaceURI == namespaceUri || (String.IsNullOrEmpty(this.reader.NamespaceURI) || this.reader.NamespaceURI == elementNamespace)) && + this.reader.LocalName == localName) + { + value = this.reader.Value; + break; + } + + hasAttributes = this.reader.MoveToNextAttribute(); + } + + // Move back to the element. + this.reader.MoveToElement(); + return value; + } + + private void ParseRuntimeElement() + { + this.ParseTargetElement(CsdlConstants.Element_Runtime, this.runtimeParserLookup); + } + + private void ParseDataServicesElement() + { + this.ParseTargetElement(CsdlConstants.Element_DataServices, this.dataServicesParserLookup); + } + + private void ParseTargetElement(string elementName, Dictionary elementParsers) + { + if (!this.targetParsed) + { + this.targetParsed = true; + } + else + { + // Edmx should contain at most one element - either or . + this.RaiseError(EdmErrorCode.UnexpectedXmlElement, Edm.Strings.EdmxParser_BodyElement(CsdlConstants.Element_DataServices)); + + // Read to the end of the element anyway, to let the caller move on to the rest of the document. + elementParsers = EmptyParserLookup; + } + + this.ParseElement(elementName, elementParsers); + } + + private void ParseConceptualModelsElement() + { + this.ParseElement(CsdlConstants.Element_ConceptualModels, this.conceptualModelsParserLookup); + } + + /// + /// TODO: use XmlDocumentParser + /// + private void ParseReferenceElement() + { + // read 'Uri' attribute + EdmReference result = new EdmReference(new Uri(this.GetAttributeValue(null, CsdlConstants.Attribute_Uri), UriKind.RelativeOrAbsolute)); + if (this.reader.IsEmptyElement) + { + this.reader.Read(); + this.edmReferences.Add(result); + return; + } + + this.reader.Read(); + while (this.reader.NodeType != XmlNodeType.EndElement) + { + while (this.reader.NodeType == XmlNodeType.Whitespace && this.reader.Read()) + { // read white spaces. can be an extension method. + } + + if (this.reader.NodeType != XmlNodeType.Element) + { + break; + } + + if (this.reader.LocalName == CsdlConstants.Element_Include) + { + // parse: + IEdmInclude tmp = new EdmInclude(this.GetAttributeValue(null, CsdlConstants.Attribute_Alias), this.GetAttributeValue(null, CsdlConstants.Attribute_Namespace)); + result.AddInclude(tmp); + } + else if (this.reader.LocalName == CsdlConstants.Element_IncludeAnnotations) + { + // parse: + IEdmIncludeAnnotations tmp = new EdmIncludeAnnotations(this.GetAttributeValue(null, CsdlConstants.Attribute_TermNamespace), this.GetAttributeValue(null, CsdlConstants.Attribute_Qualifier), this.GetAttributeValue(null, CsdlConstants.Attribute_TargetNamespace)); + result.AddIncludeAnnotations(tmp); + } + else if (this.reader.LocalName == CsdlConstants.Element_Annotation) + { + this.reader.Skip(); + this.RaiseError(EdmErrorCode.UnexpectedXmlElement, Edm.Strings.XmlParser_UnexpectedElement(this.reader.LocalName)); + continue; + } + else + { + this.RaiseError(EdmErrorCode.UnexpectedXmlElement, Edm.Strings.XmlParser_UnexpectedElement(this.reader.LocalName)); + } + + if (!this.reader.IsEmptyElement) + { + this.reader.Read(); + while (this.reader.NodeType == XmlNodeType.Whitespace && this.reader.Read()) + { // read white spaces. can be an extension method. + } + + Debug.Assert(this.reader.NodeType == XmlNodeType.EndElement, "The XmlReader should be at the end of element"); + } + + this.reader.Read(); + } + + Debug.Assert(this.reader.NodeType == XmlNodeType.EndElement, "The XmlReader should be at the end of element"); + this.reader.Read(); + this.edmReferences.Add(result); + } + + private void ParseSchemaElement() + { + Debug.Assert(this.reader.LocalName == CsdlConstants.Element_Schema, "Must call ParseCsdlSchemaElement on Schema Element"); + + XmlReaderSettings settings = new XmlReaderSettings(); + IXmlLineInfo lineInfo = this.reader as IXmlLineInfo; + if (lineInfo != null && lineInfo.HasLineInfo()) + { + settings.LineNumberOffset = lineInfo.LineNumber - 1; + settings.LinePositionOffset = lineInfo.LinePosition - 2; + } + + using (StringReader sr = new StringReader(this.reader.ReadOuterXml())) + { + using (XmlReader xr = XmlReader.Create(sr, settings)) + { + this.csdlParser.AddReader(xr, this.source); + } + } + } + + private void RaiseEmptyFile() + { + this.RaiseError(EdmErrorCode.EmptyFile, Edm.Strings.XmlParser_EmptySchemaTextReader); + } + + private CsdlLocation Location() + { + int lineNumber = 0; + int linePosition = 0; + + IXmlLineInfo xmlLineInfo = this.reader as IXmlLineInfo; + if (xmlLineInfo != null && xmlLineInfo.HasLineInfo()) + { + lineNumber = xmlLineInfo.LineNumber; + linePosition = xmlLineInfo.LinePosition; + } + + return new CsdlLocation(this.source, lineNumber, linePosition); + } + + private void RaiseError(EdmErrorCode errorCode, string errorMessage) + { + this.errors.Add(new EdmError(this.Location(), errorCode, errorMessage)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReaderSettings.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReaderSettings.cs new file mode 100644 index 0000000..6582a74 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlReaderSettings.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl +{ + using System; + using System.Xml; + + /// + /// Settings used when parsing CSDL document. + /// + public sealed class CsdlReaderSettings + { + /// + /// Default constructor. + /// + public CsdlReaderSettings() + { + this.IgnoreUnexpectedAttributesAndElements = false; + } + + /// + /// The function to load referenced model xml. If null, will stop loading the referenced models. Normally it should throw no exception. + /// + public Func GetReferencedModelReaderFunc { get; set; } + + /// + /// Ignore the unexpected attributes and elements in schema. + /// + public bool IgnoreUnexpectedAttributesAndElements { get; set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlTarget.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlTarget.cs new file mode 100644 index 0000000..fb800cd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlTarget.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Specifies what target of a CSDL doc. + /// + public enum CsdlTarget + { + /// + /// The target is Entity Framework. + /// + EntityFramework, + + /// + /// The target is OData. + /// + OData + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlWriter.cs new file mode 100644 index 0000000..41bed8b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/CsdlWriter.cs @@ -0,0 +1,177 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Csdl.Serialization; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Provides CSDL serialization services for EDM models. + /// + public class CsdlWriter + { + private readonly IEdmModel model; + private readonly IEnumerable schemas; + private readonly XmlWriter writer; + private readonly Version edmxVersion; + private readonly string edmxNamespace; + private readonly CsdlTarget target; + + private CsdlWriter(IEdmModel model, IEnumerable schemas, XmlWriter writer, Version edmxVersion, CsdlTarget target) + { + this.model = model; + this.schemas = schemas; + this.writer = writer; + this.edmxVersion = edmxVersion; + this.target = target; + + Debug.Assert(CsdlConstants.SupportedEdmxVersions.ContainsKey(edmxVersion), "CsdlConstants.SupportedEdmxVersions.ContainsKey(edmxVersion)"); + this.edmxNamespace = CsdlConstants.SupportedEdmxVersions[edmxVersion]; + } + + /// + /// Outputs a CSDL artifact to the provided XmlWriter. + /// + /// Model to be written. + /// XmlWriter the generated CSDL will be written to. + /// Target implementation of the CSDL being generated. + /// Errors that prevented successful serialization, or no errors if serialization was successful. + /// A value indicating whether serialization was successful. + public static bool TryWriteCsdl(IEdmModel model, XmlWriter writer, CsdlTarget target, out IEnumerable errors) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(writer, "writer"); + + errors = model.GetSerializationErrors(); + if (errors.FirstOrDefault() != null) + { + return false; + } + + Version edmxVersion = model.GetEdmxVersion(); + + if (edmxVersion != null) + { + if (!CsdlConstants.SupportedEdmxVersions.ContainsKey(edmxVersion)) + { + errors = new EdmError[] { new EdmError(new CsdlLocation(0, 0), EdmErrorCode.UnknownEdmxVersion, Edm.Strings.Serializer_UnknownEdmxVersion) }; + return false; + } + } + else if (!CsdlConstants.EdmToEdmxVersions.TryGetValue(model.GetEdmVersion() ?? EdmConstants.EdmVersionDefault, out edmxVersion)) + { + errors = new EdmError[] { new EdmError(new CsdlLocation(0, 0), EdmErrorCode.UnknownEdmVersion, Edm.Strings.Serializer_UnknownEdmVersion) }; + return false; + } + + IEnumerable schemas = new EdmModelSchemaSeparationSerializationVisitor(model).GetSchemas(); + + CsdlWriter edmxWriter = new CsdlWriter(model, schemas, writer, edmxVersion, target); + edmxWriter.WriteCsdl(); + + errors = Enumerable.Empty(); + return true; + } + + private void WriteCsdl() + { + switch (this.target) + { + case CsdlTarget.EntityFramework: + this.WriteEFCsdl(); + break; + case CsdlTarget.OData: + this.WriteODataCsdl(); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_CsdlTarget(this.target.ToString())); + } + } + + private void WriteODataCsdl() + { + this.WriteEdmxElement(); + this.WriteReferenceElements(); + this.WriteDataServicesElement(); + this.WriteSchemas(); + this.EndElement(); // + this.EndElement(); // + } + + private void WriteEFCsdl() + { + this.WriteEdmxElement(); + this.WriteRuntimeElement(); + this.WriteConceptualModelsElement(); + this.WriteSchemas(); + this.EndElement(); // + this.EndElement(); // + this.EndElement(); // + } + + private void WriteEdmxElement() + { + this.writer.WriteStartElement(CsdlConstants.Prefix_Edmx, CsdlConstants.Element_Edmx, this.edmxNamespace); + this.writer.WriteAttributeString(CsdlConstants.Attribute_Version, GetVersionString(this.edmxVersion)); + } + + private void WriteRuntimeElement() + { + this.writer.WriteStartElement(CsdlConstants.Prefix_Edmx, CsdlConstants.Element_Runtime, this.edmxNamespace); + } + + private void WriteConceptualModelsElement() + { + this.writer.WriteStartElement(CsdlConstants.Prefix_Edmx, CsdlConstants.Element_ConceptualModels, this.edmxNamespace); + } + + private void WriteReferenceElements() + { + EdmModelReferenceElementsVisitor visitor = new EdmModelReferenceElementsVisitor(this.model, this.writer, this.edmxVersion); + visitor.VisitEdmReferences(this.model); + } + + private void WriteDataServicesElement() + { + this.writer.WriteStartElement(CsdlConstants.Prefix_Edmx, CsdlConstants.Element_DataServices, this.edmxNamespace); + } + + private void WriteSchemas() + { + // TODO: for referenced model - write alias as is, instead of writing its namespace. + EdmModelCsdlSerializationVisitor visitor; + Version edmVersion = this.model.GetEdmVersion() ?? EdmConstants.EdmVersionDefault; + foreach (EdmSchema schema in this.schemas) + { + visitor = new EdmModelCsdlSerializationVisitor(this.model, this.writer, edmVersion); + visitor.VisitEdmSchema(schema, this.model.GetNamespacePrefixMappings()); + } + } + + private void EndElement() + { + this.writer.WriteEndElement(); + } + + // Gets the string form of the EdmVersion. + // Note that Version 4.01 needs two digits of minor version precision. + private static string GetVersionString(Version version) + { + if (version == EdmConstants.EdmVersion401) + { + return EdmConstants.EdmVersion401String; + } + + return version.ToString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmEnumValueParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmEnumValueParser.cs new file mode 100644 index 0000000..665bf94 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmEnumValueParser.cs @@ -0,0 +1,109 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Csdl.CsdlSemantics; + + /// + /// Internal parser to parse enum value in csdl. + /// + internal static class EdmEnumValueParser + { + /// + /// Try parse enum members specified in a string value from declared schema types + /// + /// Enum value string + /// The model for resolving enum type. + /// The location of the enum member in csdl + /// Parsed enum members + /// True for successfully parsed, false for failed + internal static bool TryParseEnumMember(string value, IEdmModel model, EdmLocation location, out IEnumerable result) + { + result = null; + if (value == null || model == null) + { + return false; + } + + bool isUnresolved = false; + var enumValues = value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (!enumValues.Any()) + { + return false; + } + + string enumTypeName = enumValues[0].Split('/').FirstOrDefault(); + if (string.IsNullOrEmpty(enumTypeName)) + { + return false; + } + + IEdmEnumType enumType = model.FindType(enumTypeName) as IEdmEnumType; + + if (enumType == null) + { + enumType = new UnresolvedEnumType(enumTypeName, location); + isUnresolved = true; + } + else if (enumValues.Count() > 1 && (!enumType.IsFlags || !EdmEnumValueParser.IsEnumIntegerType(enumType))) + { + return false; + } + + List enumMembers = new List(); + foreach (var enumValue in enumValues) + { + string[] path = enumValue.Split('/'); + if (path.Count() != 2) + { + return false; + } + + if (path[0] != enumTypeName) + { + return false; + } + + if (!isUnresolved) + { + IEdmEnumMember member = enumType.Members.SingleOrDefault(m => m.Name == path[1]); + if (member == null) + { + return false; + } + + enumMembers.Add(member); + } + else + { + enumMembers.Add(new UnresolvedEnumMember(path[1], enumType, location)); + } + } + + result = enumMembers; + return true; + } + + /// + /// Determine if the underlying type of the enum type is integer type (byte, sbyte, int16, int32, int64). + /// + /// The enum type. + /// True if the underlying type of enum type is integer type. + internal static bool IsEnumIntegerType(IEdmEnumType enumType) + { + return enumType.UnderlyingType.PrimitiveKind == EdmPrimitiveTypeKind.Byte || + enumType.UnderlyingType.PrimitiveKind == EdmPrimitiveTypeKind.SByte || + enumType.UnderlyingType.PrimitiveKind == EdmPrimitiveTypeKind.Int16 || + enumType.UnderlyingType.PrimitiveKind == EdmPrimitiveTypeKind.Int32 || + enumType.UnderlyingType.PrimitiveKind == EdmPrimitiveTypeKind.Int64; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmParseException.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmParseException.cs new file mode 100644 index 0000000..0c2a635 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmParseException.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Microsoft.OData.Edm.Validation; + using ErrorStrings = Microsoft.OData.Edm.Strings; + + /// + /// Exception type representing a failure to parse an EDM document. Carries the set of errors along with it. + /// + [SuppressMessage("Microsoft.Design", "CA1032", Justification = "We do not intend to support serialization of this exception yet, nor does it need the full suite of constructors.")] + [SuppressMessage("Microsoft.Usage", "CA2237", Justification = "We do not intend to support serialization of this exception yet.")] + [DebuggerDisplay("{Message}")] + public class EdmParseException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The errors encountered while parsing. + public EdmParseException(IEnumerable parseErrors) + : this(parseErrors.ToList()) + { + } + + /// + /// Prevents a default instance of the class from being created. + /// + /// The parse errors. + private EdmParseException(List parseErrors) + : base(ConstructMessage(parseErrors)) + { + this.Errors = new ReadOnlyCollection(parseErrors); + } + + /// + /// Gets the set of errors that were encountered while parsing. + /// + public ReadOnlyCollection Errors { get; private set; } + + /// + /// Constructs an appropriate exception message from the set of parsing errors. + /// + /// The parse errors. + /// The exception message. + private static string ConstructMessage(IEnumerable parseErrors) + { + return ErrorStrings.EdmParseException_ErrorsEncounteredInEdmx(string.Join(Environment.NewLine, parseErrors.Select(p => p.ToString()).ToArray())); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueParser.cs new file mode 100644 index 0000000..234ade8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueParser.cs @@ -0,0 +1,386 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Text.RegularExpressions; +using System.Xml; +using Microsoft.OData.Edm; + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#else +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +#if ODATA_CORE +namespace Microsoft.OData +#else +namespace Microsoft.OData.Edm.Csdl +#endif +#endif +#endif +{ + /// + /// Contains xml parsing methods for Edm. + /// + internal static class EdmValueParser + { + /// + /// This pattern eliminates all durations with year or month fields, leaving only those with day, hour, minutes, and/or seconds fields + /// + internal static readonly Regex DayTimeDurationValidator = PlatformHelper.CreateCompiled("^[^YM]*[DT].*$", RegexOptions.Singleline); + + /// + /// Converts a string to a TimeSpan. + /// + /// The string to be converted. The string must be an edm:Duration expression + /// A TimeSpan equivalent of the string. + /// Throws if the given string is not an edm:Duration expression + /// Throws if the given duration is greater than P10675199DT2H48M5.4775807S or less than P10675199DT2H48M5.4775807S + internal static TimeSpan ParseDuration(string value) + { + if (value == null || !DayTimeDurationValidator.IsMatch(value)) + { + throw new FormatException(Strings.ValueParser_InvalidDuration(value)); + } + + return XmlConvert.ToTimeSpan(value); + } + + /// + /// Attempts to parse a byte[] value from the specified text. + /// + /// Input string + /// The byte[] resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseBinary(string value, out byte[] result) + { + if (value.Length % 2 != 0) + { + result = null; + return false; + } + + result = new byte[value.Length >> 1]; + for (int i = 0; i < value.Length; ++i) + { + byte h; + if (!TryParseCharAsBinary(value[i], out h)) + { + result = null; + return false; + } + + byte l; + if (!TryParseCharAsBinary(value[++i], out l)) + { + result = null; + return false; + } + + result[i >> 1] = (byte)((h << 4) | l); + } + + return true; + } + + /// + /// Attempts to parse a bool value from the specified text. + /// + /// Input string + /// The bool resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseBool(string value, out bool? result) + { + switch (value.Length) + { + case 4: + if ((value[0] == 't' || value[0] == 'T') && + (value[1] == 'r' || value[1] == 'R') && + (value[2] == 'u' || value[2] == 'U') && + (value[3] == 'e' || value[3] == 'E')) + { + result = true; + return true; + } + + break; + + case 5: + if ((value[0] == 'f' || value[0] == 'F') && + (value[1] == 'a' || value[1] == 'A') && + (value[2] == 'l' || value[2] == 'L') && + (value[3] == 's' || value[3] == 'S') && + (value[4] == 'e' || value[4] == 'E')) + { + result = false; + return true; + } + + break; + + case 1: + switch (value[0]) + { + case '1': + result = true; + return true; + case '0': + result = false; + return true; + } + + break; + + default: + break; + } + + result = null; + return false; + } + + /// + /// Attempts to parse a TimeSpan value from the specified text. + /// + /// Input string + /// The TimeSpan resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseDuration(string value, out TimeSpan? result) + { + try + { + result = ParseDuration(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + catch (OverflowException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a DateTimeOffset value from the specified text. + /// + /// Input string + /// The DateTimeOffset resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseDateTimeOffset(string value, out DateTimeOffset? result) + { + try + { + result = PlatformHelper.ConvertStringToDateTimeOffset(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + catch (ArgumentOutOfRangeException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a int value from the specified text. + /// + /// Input string + /// The int resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseInt(string value, out int? result) + { + try + { + result = XmlConvert.ToInt32(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + catch (OverflowException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a long value from the specified text. + /// + /// Input string + /// The long resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseLong(string value, out long? result) + { + try + { + result = XmlConvert.ToInt64(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + catch (OverflowException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a decimal value from the specified text. + /// + /// Input string + /// The decimal resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseDecimal(string value, out decimal? result) + { + try + { + result = XmlConvert.ToDecimal(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + catch (OverflowException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a double value from the specified text. + /// + /// Input string + /// The double resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseFloat(string value, out double? result) + { + try + { + result = XmlConvert.ToDouble(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + catch (OverflowException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a Guid value from the specified text. + /// + /// Input string + /// The Guid resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseGuid(string value, out Guid? result) + { + try + { + result = XmlConvert.ToGuid(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a Date value from the specified text. + /// + /// Input string + /// The Date resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseDate(string value, out Date? result) + { + result = null; + Date targetDate; + if (PlatformHelper.TryConvertStringToDate(value, out targetDate)) + { + result = targetDate; + return true; + } + + return false; + } + + /// + /// Attempts to parse a TimeOfDay value from the specified text. + /// + /// Input string + /// The TimeOfDay resulting from parsing the string value + /// true if the value was parsed successfully, false otherwise + internal static bool TryParseTimeOfDay(string value, out TimeOfDay? result) + { + try + { + result = PlatformHelper.ConvertStringToTimeOfDay(value); + return true; + } + catch (FormatException) + { + result = null; + return false; + } + } + + /// + /// Attempts to parse a byte value from the specified char. + /// + /// Input char + /// The byte resulting from parsing the char value + /// true if the value was parsed successfully, false otherwise + private static bool TryParseCharAsBinary(char c, out byte b) + { + uint v = (uint)c - (uint)'0'; + if (v >= 0 && v <= 9) + { + b = (byte)v; + return true; + } + + v = (uint)c - (uint)'A'; + if (v < 0 || v > 5) + { + v = (uint)c - (uint)'a'; + } + + if (v >= 0 && v <= 5) + { + b = (byte)(v + 10); + return true; + } + + b = default(byte); + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueWriter.cs new file mode 100644 index 0000000..08f6aeb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/EdmValueWriter.cs @@ -0,0 +1,239 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Xml; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#else +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +#if ODATA_CORE +namespace Microsoft.OData +#else +namespace Microsoft.OData.Edm.Csdl +#endif +#endif +#endif +{ + /// + /// Contains methods to convert primitive values to their string representation. + /// + internal static class EdmValueWriter + { + /// + /// Characters used in string representations of hexadecimal values + /// + private static char[] Hex = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +#if !ODATA_CORE && !ODATA_CLIENT && !ODATA_SERVICE + /// + /// Converts the IEdmPrimitiveValue to a String. + /// + /// The value to convert. + /// A string representation of the IEdmPrimitiveValue. + internal static string PrimitiveValueAsXml(IEdmPrimitiveValue v) + { + switch (v.ValueKind) + { + case EdmValueKind.Boolean: + return BooleanAsXml(((IEdmBooleanValue)v).Value); + case EdmValueKind.Integer: + return LongAsXml(((IEdmIntegerValue)v).Value); + case EdmValueKind.Floating: + return FloatAsXml(((IEdmFloatingValue)v).Value); + case EdmValueKind.Guid: + return GuidAsXml(((IEdmGuidValue)v).Value); + case EdmValueKind.Binary: + return BinaryAsXml(((IEdmBinaryValue)v).Value); + case EdmValueKind.Decimal: + return DecimalAsXml(((IEdmDecimalValue)v).Value); + case EdmValueKind.String: + return StringAsXml(((IEdmStringValue)v).Value); + case EdmValueKind.DateTimeOffset: + return DateTimeOffsetAsXml(((IEdmDateTimeOffsetValue)v).Value); + case EdmValueKind.Date: + return DateAsXml(((IEdmDateValue)v).Value); + case EdmValueKind.Duration: + return DurationAsXml(((IEdmDurationValue)v).Value); + case EdmValueKind.TimeOfDay: + return TimeOfDayAsXml(((IEdmTimeOfDayValue)v).Value); + default: + throw new NotSupportedException(Edm.Strings.ValueWriter_NonSerializableValue(v.ValueKind)); + } + } +#endif + /// + /// Converts the String to a String. + /// + /// The value to convert. + /// The value to convert. + internal static string StringAsXml(string s) + { + return s; + } + + /// + /// Converts the Byte[] to a String. + /// + /// The value to convert. + /// A string representation of the Byte[]. + internal static string BinaryAsXml(byte[] binary) + { + var chars = new char[binary.Length * 2]; + for (int i = 0; i < binary.Length; ++i) + { + chars[i << 1] = Hex[binary[i] >> 4]; + chars[i << 1 | 1] = Hex[binary[i] & 0x0F]; + } + + return new string(chars); + } + + /// + /// Converts the Boolean to a String. + /// + /// The value to convert. + /// A string representation of the Boolean, that is, "true" or "false". + internal static string BooleanAsXml(bool b) + { + return XmlConvert.ToString(b); + } + + /// + /// Converts the Boolean? to a String. + /// + /// The value to convert. + /// A string representation of the Boolean, that is, "true" or "false". + internal static string BooleanAsXml(bool? b) + { + Debug.Assert(b.HasValue, "Serialized nullable boolean must have value."); + return BooleanAsXml(b.Value); + } + + /// + /// Converts the Int32 to a String. + /// + /// The value to convert + /// A string representation of the Int32. + internal static string IntAsXml(int i) + { + return XmlConvert.ToString(i); + } + + /// + /// Converts the Int32? to a String. + /// + /// The value to convert + /// A string representation of the Int32. + internal static string IntAsXml(int? i) + { + Debug.Assert(i.HasValue, "Serialized nullable integer must have value."); + return IntAsXml(i.Value); + } + + /// + /// Converts the Int64 to a String. + /// + /// The value to convert. + /// A string representation of the Int64. + internal static string LongAsXml(long l) + { + return XmlConvert.ToString(l); + } + + /// + /// Converts the Double to a String. + /// + /// The value to convert. + /// A string representation of the Double. + internal static string FloatAsXml(double f) + { + return XmlConvert.ToString(f); + } + + /// + /// Converts the Decimal to a String. + /// + /// The value to convert. + /// A string representation of the Decimal. + internal static string DecimalAsXml(decimal d) + { + return XmlConvert.ToString(d); + } + + /// + /// Converts the TimeSpan to a String. + /// + /// The value to convert. + /// A string representation of the TimeSpan. + internal static string DurationAsXml(TimeSpan d) + { + return XmlConvert.ToString(d); + } + + /// + /// Converts the DateTimeOffset to a String. + /// + /// The System.DateTimeOffset to be converted. + /// A System.String representation of the supplied System.DateTimeOffset. + internal static string DateTimeOffsetAsXml(DateTimeOffset d) + { + var value = XmlConvert.ToString(d); + Debug.Assert(EdmValueParser.DayTimeDurationValidator.IsMatch(value), "Edm.Duration values should not have year or month part"); + return value; + } + + /// + /// Converts the Date to a String. + /// + /// The to be converted + /// A System.String representation of the supplied . + internal static string DateAsXml(Date d) + { + var value = d.ToString(); + return value; + } + + /// + /// Converts the TimeOfDay to a String. + /// + /// The to be converted + /// A System.String representation of the supplied . + internal static string TimeOfDayAsXml(TimeOfDay time) + { + var value = time.ToString(); + return value; + } + + /// + /// Converts the Guid to a String. + /// + /// The value to convert. + /// A string representation of the Guid. + internal static string GuidAsXml(Guid g) + { + return XmlConvert.ToString(g); + } + + /// + /// Converts the Uri to a String. + /// + /// The value to convert. + /// A string representation of the Uri. + internal static string UriAsXml(Uri uri) + { + Debug.Assert(uri != null, "uri != null"); + return uri.OriginalString; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAbstractNavigationSource.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAbstractNavigationSource.cs new file mode 100644 index 0000000..60c578c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAbstractNavigationSource.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + using System.Collections.Generic; + + /// + /// Represents an abstract CSDL navigation source. + /// + internal abstract class CsdlAbstractNavigationSource : CsdlNamedElement + { + private readonly List navigationPropertyBindings; + + public CsdlAbstractNavigationSource(string name, IEnumerable navigationPropertyBindings, CsdlLocation location) + : base(name, location) + { + this.navigationPropertyBindings = new List(navigationPropertyBindings); + } + + public IEnumerable NavigationPropertyBindings + { + get { return this.navigationPropertyBindings; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAction.cs new file mode 100644 index 0000000..df2c6a8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAction.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Action. + /// + internal class CsdlAction : CsdlOperation + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The parameters. + /// The return of the action. + /// if set to true [is bound]. + /// The entity set path. + /// The location in the csdl document of the action. + public CsdlAction( + string name, + IEnumerable parameters, + CsdlOperationReturn operationReturn, + bool isBound, + string entitySetPath, + CsdlLocation location) + : base(name, parameters, operationReturn, isBound, entitySetPath, location) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlActionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlActionImport.cs new file mode 100644 index 0000000..0b755d7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlActionImport.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL action import. + /// + internal class CsdlActionImport : CsdlOperationImport + { + public CsdlActionImport( + string name, + string schemaOperationQualifiedTypeName, + string entitySet, + CsdlLocation location) + : base(name, schemaOperationQualifiedTypeName, entitySet, new CsdlOperationParameter[] { }, null /*returnType*/, location) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotation.cs new file mode 100644 index 0000000..9f36c42 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotation.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Common type for a CSDL annotation. + /// + internal class CsdlAnnotation : CsdlElement + { + private readonly CsdlExpressionBase expression; + private readonly string qualifier; + private readonly string term; + + public CsdlAnnotation(string term, string qualifier, CsdlExpressionBase expression, CsdlLocation location) + : base(location) + { + this.expression = expression; + this.qualifier = qualifier; + this.term = term; + } + + public CsdlExpressionBase Expression + { + get { return this.expression; } + } + + public string Qualifier + { + get { return this.qualifier; } + } + + public string Term + { + get { return this.term; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotationPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotationPathExpression.cs new file mode 100644 index 0000000..abe6880 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotationPathExpression.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL annotation Path expression. + /// + internal class CsdlAnnotationPathExpression : CsdlPathExpression + { + public CsdlAnnotationPathExpression(string path, CsdlLocation location) : base(path, location) + { + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.AnnotationPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotations.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotations.cs new file mode 100644 index 0000000..6a83130 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlAnnotations.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Annotations element. + /// + internal class CsdlAnnotations + { + private readonly List annotations; + private readonly string target; + private readonly string qualifier; + + public CsdlAnnotations(IEnumerable annotations, string target, string qualifier) + { + this.annotations = new List(annotations); + this.target = target; + this.qualifier = qualifier; + } + + public IEnumerable Annotations + { + get { return this.annotations; } + } + + public string Qualifier + { + get { return this.qualifier; } + } + + public string Target + { + get { return this.target; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlApplyExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlApplyExpression.cs new file mode 100644 index 0000000..03d7e8a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlApplyExpression.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlApplyExpression : CsdlExpressionBase + { + private readonly string function; + private readonly List arguments; + + public CsdlApplyExpression(string function, IEnumerable arguments, CsdlLocation location) + : base(location) + { + this.function = function; + this.arguments = new List(arguments); + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.FunctionApplication; } + } + + public string Function + { + get { return this.function; } + } + + public IEnumerable Arguments + { + get { return this.arguments; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlBinaryTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlBinaryTypeReference.cs new file mode 100644 index 0000000..3670079 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlBinaryTypeReference.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a reference to a CSDL Binary type. + /// + internal class CsdlBinaryTypeReference : CsdlPrimitiveTypeReference + { + public CsdlBinaryTypeReference(bool isUnbounded, int? maxLength, string typeName, bool isNullable, CsdlLocation location) + : base(EdmPrimitiveTypeKind.Binary, typeName, isNullable, location) + { + this.IsUnbounded = isUnbounded; + this.MaxLength = maxLength; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCastExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCastExpression.cs new file mode 100644 index 0000000..8ff247d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCastExpression.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlCastExpression : CsdlExpressionBase + { + private readonly CsdlTypeReference type; + private readonly CsdlExpressionBase operand; + + public CsdlCastExpression(CsdlTypeReference type, CsdlExpressionBase operand, CsdlLocation location) + : base(location) + { + this.type = type; + this.operand = operand; + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Cast; } + } + + public CsdlTypeReference Type + { + get { return this.type; } + } + + public CsdlExpressionBase Operand + { + get { return this.operand; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionExpression.cs new file mode 100644 index 0000000..b45312f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionExpression.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Collection expression. + /// + internal class CsdlCollectionExpression : CsdlExpressionBase + { + private readonly CsdlTypeReference type; + private readonly List elementValues; + + public CsdlCollectionExpression(CsdlTypeReference type, IEnumerable elementValues, CsdlLocation location) + : base(location) + { + this.type = type; + this.elementValues = new List(elementValues); + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Collection; } + } + + public CsdlTypeReference Type + { + get { return this.type; } + } + + public IEnumerable ElementValues + { + get { return this.elementValues; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionType.cs new file mode 100644 index 0000000..b2e1f61 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlCollectionType.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL collection type. + /// + internal class CsdlCollectionType : CsdlElement, ICsdlTypeExpression + { + private readonly CsdlTypeReference elementType; + + public CsdlCollectionType(CsdlTypeReference elementType, CsdlLocation location) + : base(location) + { + this.elementType = elementType; + } + + public CsdlTypeReference ElementType + { + get { return this.elementType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlComplexType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlComplexType.cs new file mode 100644 index 0000000..826e763 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlComplexType.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL complex type. + /// + internal class CsdlComplexType : CsdlNamedStructuredType + { + public CsdlComplexType(string name, string baseTypeName, bool isAbstract, bool isOpen, IEnumerable structuralProperties, IEnumerable navigationProperties, CsdlLocation location) + : base(name, baseTypeName, isAbstract, isOpen, structuralProperties, navigationProperties, location) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlConstantExpression.cs new file mode 100644 index 0000000..d1d383e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlConstantExpression.cs @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL constant expression. + /// + internal class CsdlConstantExpression : CsdlExpressionBase + { + private readonly EdmValueKind kind; + private readonly string value; + + public CsdlConstantExpression(EdmValueKind kind, string value, CsdlLocation location) + : base(location) + { + this.kind = kind; + this.value = value; + } + + public override EdmExpressionKind ExpressionKind + { + get + { + switch (this.kind) + { + case EdmValueKind.Binary: + return EdmExpressionKind.BinaryConstant; + case EdmValueKind.Boolean: + return EdmExpressionKind.BooleanConstant; + case EdmValueKind.DateTimeOffset: + return EdmExpressionKind.DateTimeOffsetConstant; + case EdmValueKind.Decimal: + return EdmExpressionKind.DecimalConstant; + case EdmValueKind.Floating: + return EdmExpressionKind.FloatingConstant; + case EdmValueKind.Guid: + return EdmExpressionKind.GuidConstant; + case EdmValueKind.Integer: + return EdmExpressionKind.IntegerConstant; + case EdmValueKind.String: + return EdmExpressionKind.StringConstant; + case EdmValueKind.Duration: + return EdmExpressionKind.DurationConstant; + case EdmValueKind.Date: + return EdmExpressionKind.DateConstant; + case EdmValueKind.TimeOfDay: + return EdmExpressionKind.TimeOfDayConstant; + case EdmValueKind.Null: + return EdmExpressionKind.Null; + default: + return EdmExpressionKind.None; + } + } + } + + public EdmValueKind ValueKind + { + get + { + return this.kind; + } + } + + public string Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDecimalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDecimalTypeReference.cs new file mode 100644 index 0000000..02ac843 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDecimalTypeReference.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a reference to a CSDL decimal type. + /// + internal class CsdlDecimalTypeReference : CsdlPrimitiveTypeReference + { + public CsdlDecimalTypeReference(int? precision, int? scale, string typeName, bool isNullable, CsdlLocation location) + : base(EdmPrimitiveTypeKind.Decimal, typeName, isNullable, location) + { + this.Precision = precision; + this.Scale = scale; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDirectValueAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDirectValueAnnotation.cs new file mode 100644 index 0000000..a469fd5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlDirectValueAnnotation.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL annotation. + /// + internal class CsdlDirectValueAnnotation : CsdlElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string value; + private readonly bool isAttribute; + + public CsdlDirectValueAnnotation(string namespaceName, string name, string value, bool isAttribute, CsdlLocation location) + : base(location) + { + this.namespaceName = namespaceName; + this.name = name; + this.value = value; + this.isAttribute = isAttribute; + } + + public string NamespaceName + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + public string Value + { + get { return this.value; } + } + + public bool IsAttribute + { + get { return this.isAttribute; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlElement.cs new file mode 100644 index 0000000..9030466 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlElement.cs @@ -0,0 +1,99 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Common base class for CSDL elements. + /// + internal abstract class CsdlElement + { + protected List annotations; + protected EdmLocation location; + + public CsdlElement(CsdlLocation location) + { + this.location = location; + } + + public virtual bool HasDirectValueAnnotations + { + get { return this.HasAnnotations(); } + } + + public bool HasVocabularyAnnotations + { + get { return this.HasAnnotations(); } + } + + public IEnumerable ImmediateValueAnnotations + { + get + { + return this.GetAnnotations(); + } + } + + public IEnumerable VocabularyAnnotations + { + get + { + return this.GetAnnotations(); + } + } + + public EdmLocation Location + { + get { return this.location; } + } + + public void AddAnnotation(CsdlDirectValueAnnotation annotation) + { + this.AddUntypedAnnotation(annotation); + } + + public void AddAnnotation(CsdlAnnotation annotation) + { + this.AddUntypedAnnotation(annotation); + } + + private IEnumerable GetAnnotations() where T : class + { + return this.annotations != null ? this.annotations.OfType() : Enumerable.Empty(); + } + + private void AddUntypedAnnotation(object annotation) + { + if (this.annotations == null) + { + this.annotations = new List(); + } + + this.annotations.Add(annotation); + } + + private bool HasAnnotations() + { + if (this.annotations == null) + { + return false; + } + + foreach (object annotation in this.annotations) + { + if (annotation is T) + { + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityContainer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityContainer.cs new file mode 100644 index 0000000..7d6da9c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityContainer.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL entity container. + /// + internal class CsdlEntityContainer : CsdlNamedElement + { + private readonly string extends; + private readonly List entitySets; + private readonly List singletons; + private readonly List operationImports; + + public CsdlEntityContainer(string name, string extends, IEnumerable entitySets, IEnumerable singletons, IEnumerable operationImports, CsdlLocation location) + : base(name, location) + { + this.extends = extends; + this.entitySets = new List(entitySets); + this.singletons = new List(singletons); + this.operationImports = new List(operationImports); + } + + public string Extends + { + get { return this.extends; } + } + + public IEnumerable EntitySets + { + get { return this.entitySets; } + } + + public IEnumerable Singletons + { + get { return this.singletons; } + } + + public IEnumerable OperationImports + { + get { return this.operationImports; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityReferenceType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityReferenceType.cs new file mode 100644 index 0000000..f1aa4c7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityReferenceType.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL entity reference type. + /// + internal class CsdlEntityReferenceType : CsdlElement, ICsdlTypeExpression + { + private readonly CsdlTypeReference entityType; + + public CsdlEntityReferenceType(CsdlTypeReference entityType, CsdlLocation location) + : base(location) + { + this.entityType = entityType; + } + + public CsdlTypeReference EntityType + { + get { return this.entityType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntitySet.cs new file mode 100644 index 0000000..8196ec5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntitySet.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + using System.Collections.Generic; + + /// + /// Represents a CSDL Entity Set. + /// + internal class CsdlEntitySet : CsdlAbstractNavigationSource + { + private readonly string elementType; + + public CsdlEntitySet(string name, string elementType, IEnumerable navigationPropertyBindings, CsdlLocation location) + : this(name, elementType, navigationPropertyBindings, location, true) + { + } + + public CsdlEntitySet(string name, string elementType, IEnumerable navigationPropertyBindings, CsdlLocation location, bool includeInServiceDocument) + : base(name, navigationPropertyBindings, location) + { + this.elementType = elementType; + this.IncludeInServiceDocument = includeInServiceDocument; + } + + public string ElementType + { + get { return this.elementType; } + } + + public bool IncludeInServiceDocument { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityType.cs new file mode 100644 index 0000000..b8f5598 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEntityType.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL entity type. + /// + internal class CsdlEntityType : CsdlNamedStructuredType + { + private readonly CsdlKey key; + private readonly bool hasStream; + + public CsdlEntityType(string name, string baseTypeName, bool isAbstract, bool isOpen, bool hasStream, CsdlKey key, IEnumerable structualProperties, IEnumerable navigationProperties, CsdlLocation location) + : base(name, baseTypeName, isAbstract, isOpen, structualProperties, navigationProperties, location) + { + this.key = key; + this.hasStream = hasStream; + } + + public CsdlKey Key + { + get { return this.key; } + } + + public bool HasStream + { + get { return this.hasStream; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMember.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMember.cs new file mode 100644 index 0000000..9e97708 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMember.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL enumeration type member. + /// + internal class CsdlEnumMember : CsdlNamedElement + { + public CsdlEnumMember(string name, long? value, CsdlLocation location) + : base(name, location) + { + this.Value = value; + } + + /// + /// Gets or sets the underlying type value of the member. + /// Value can be null only during deserialization of the declaring enumeration type. + /// When the type's deserialization is complete, all its members get their values assigned. + /// + public long? Value + { + get; + set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMemberExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMemberExpression.cs new file mode 100644 index 0000000..d77c06d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumMemberExpression.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlEnumMemberExpression : CsdlExpressionBase + { + private readonly string enumMemberPath; + + public CsdlEnumMemberExpression(string enumMemberPath, CsdlLocation location) + : base(location) + { + this.enumMemberPath = enumMemberPath; + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.EnumMember; } + } + + public string EnumMemberPath + { + get { return this.enumMemberPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumType.cs new file mode 100644 index 0000000..d0dcf50 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlEnumType.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL enumeration type. + /// + internal class CsdlEnumType : CsdlNamedElement + { + private readonly string underlyingTypeName; + private readonly bool isFlags; + private readonly List members; + + public CsdlEnumType(string name, string underlyingTypeName, bool isFlags, IEnumerable members, CsdlLocation location) + : base(name, location) + { + this.underlyingTypeName = underlyingTypeName; + this.isFlags = isFlags; + this.members = new List(members); + } + + public string UnderlyingTypeName + { + get { return this.underlyingTypeName; } + } + + public bool IsFlags + { + get { return this.isFlags; } + } + + public IEnumerable Members + { + get { return this.members; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionBase.cs new file mode 100644 index 0000000..89863dc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionBase.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Common base type for a CSDL expression. + /// + internal abstract class CsdlExpressionBase : CsdlElement + { + public CsdlExpressionBase(CsdlLocation location) + : base(location) + { + } + + public abstract EdmExpressionKind ExpressionKind { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionTypeReference.cs new file mode 100644 index 0000000..9915f16 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlExpressionTypeReference.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL type reference based on a type expression. + /// + internal class CsdlExpressionTypeReference : CsdlTypeReference + { + private readonly ICsdlTypeExpression typeExpression; + + public CsdlExpressionTypeReference(ICsdlTypeExpression typeExpression, bool isNullable, CsdlLocation location) + : base(isNullable, location) + { + this.typeExpression = typeExpression; + } + + public ICsdlTypeExpression TypeExpression + { + get { return this.typeExpression; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunction.cs new file mode 100644 index 0000000..97a6c69 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunction.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Function. + /// + internal class CsdlFunction : CsdlOperation + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The parameters. + /// The return of the function. + /// if set to true [is bound]. + /// The entity set path. + /// if set to true [is composable]. + /// The location in the csdl document of the function. + public CsdlFunction( + string name, + IEnumerable parameters, + CsdlOperationReturn operationReturn, + bool isBound, + string entitySetPath, + bool isComposable, + CsdlLocation location) + : base(name, parameters, operationReturn, isBound, entitySetPath, location) + { + this.IsComposable = isComposable; + } + + public bool IsComposable { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionBase.cs new file mode 100644 index 0000000..d614911 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionBase.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a base class for CSDL functions and operation imports. + /// + internal abstract class CsdlFunctionBase : CsdlNamedElement + { + private readonly List parameters; + private readonly CsdlOperationReturn operationReturn; + + protected CsdlFunctionBase(string name, IEnumerable parameters, CsdlOperationReturn operationReturn, CsdlLocation location) + : base(name, location) + { + this.parameters = new List(parameters); + this.operationReturn = operationReturn; + } + + public IEnumerable Parameters + { + get { return this.parameters; } + } + + public CsdlOperationReturn Return + { + get { return this.operationReturn; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionImport.cs new file mode 100644 index 0000000..4449ff0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlFunctionImport.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL function import. + /// + internal class CsdlFunctionImport : CsdlOperationImport + { + public CsdlFunctionImport( + string name, + string schemaOperationQualifiedTypeName, + string entitySet, + bool includeInServiceDocument, + CsdlLocation location) + : base(name, schemaOperationQualifiedTypeName, entitySet, new CsdlOperationParameter[] { }, null /*returnType*/, location) + { + this.IncludeInServiceDocument = includeInServiceDocument; + } + + public bool IncludeInServiceDocument { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIfExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIfExpression.cs new file mode 100644 index 0000000..19dede4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIfExpression.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlIfExpression : CsdlExpressionBase + { + private readonly CsdlExpressionBase test; + private readonly CsdlExpressionBase ifTrue; + private readonly CsdlExpressionBase ifFalse; + + public CsdlIfExpression(CsdlExpressionBase test, CsdlExpressionBase ifTrue, CsdlExpressionBase ifFalse, CsdlLocation location) + : base(location) + { + this.test = test; + this.ifTrue = ifTrue; + this.ifFalse = ifFalse; + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.If; } + } + + public CsdlExpressionBase Test + { + get { return this.test; } + } + + public CsdlExpressionBase IfTrue + { + get { return this.ifTrue; } + } + + public CsdlExpressionBase IfFalse + { + get { return this.ifFalse; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIsTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIsTypeExpression.cs new file mode 100644 index 0000000..9c53c6d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlIsTypeExpression.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlIsTypeExpression : CsdlExpressionBase + { + private readonly CsdlTypeReference type; + private readonly CsdlExpressionBase operand; + + public CsdlIsTypeExpression(CsdlTypeReference type, CsdlExpressionBase operand, CsdlLocation location) + : base(location) + { + this.type = type; + this.operand = operand; + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.IsType; } + } + + public CsdlTypeReference Type + { + get { return this.type; } + } + + public CsdlExpressionBase Operand + { + get { return this.operand; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlKey.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlKey.cs new file mode 100644 index 0000000..070578c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlKey.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL key. + /// + internal class CsdlKey : CsdlElement + { + private readonly List properties; + + public CsdlKey(IEnumerable properties, CsdlLocation location) + : base(location) + { + this.properties = new List(properties); + } + + public IEnumerable Properties + { + get { return this.properties; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpression.cs new file mode 100644 index 0000000..998626c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpression.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlLabeledExpression : CsdlExpressionBase + { + private readonly string label; + private readonly CsdlExpressionBase element; + + public CsdlLabeledExpression(string label, CsdlExpressionBase element, CsdlLocation location) + : base(location) + { + this.label = label; + this.element = element; + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Labeled; } + } + + public string Label + { + get { return this.label; } + } + + public CsdlExpressionBase Element + { + get { return this.element; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpressionReferenceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpressionReferenceExpression.cs new file mode 100644 index 0000000..b507857 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlLabeledExpressionReferenceExpression.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlLabeledExpressionReferenceExpression : CsdlExpressionBase + { + private readonly string label; + + public CsdlLabeledExpressionReferenceExpression(string label, CsdlLocation location) + : base(location) + { + this.label = label; + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.LabeledExpressionReference; } + } + + public string Label + { + get { return this.label; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlModel.cs new file mode 100644 index 0000000..6195e17 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlModel.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL model. + /// + internal class CsdlModel + { + private readonly List schemata = new List(); + private readonly List currentModelReferences = new List(); + private readonly List parentModelReferences = new List(); + + /// + /// Represents current model's $lt;edmx:Reference /> + /// + public IEnumerable CurrentModelReferences + { + get { return currentModelReferences; } + } + + /// + /// Represents parent model's $lt;edmx:Reference ... /> + /// + public IEnumerable ParentModelReferences + { + get { return parentModelReferences; } + } + + public IEnumerable Schemata + { + get { return this.schemata; } + } + + public void AddSchema(CsdlSchema schema) + { + this.schemata.Add(schema); + } + + /// + /// Adds from current model. + /// + /// The items to add. + public void AddCurrentModelReferences(IEnumerable referencesToAdd) + { + this.currentModelReferences.AddRange(referencesToAdd); + } + + /// + /// Adds from main model. + /// + /// The IEdmReference to add. + public void AddParentModelReferences(IEdmReference referenceToAdd) + { + this.parentModelReferences.Add(referenceToAdd); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedElement.cs new file mode 100644 index 0000000..de11e43 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedElement.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Common base class for all named CSDL elements. + /// + internal abstract class CsdlNamedElement : CsdlElement + { + private readonly string name; + + protected CsdlNamedElement(string name, CsdlLocation location) + : base(location) + { + this.name = name; + } + + public string Name + { + get { return this.name; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedStructuredType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedStructuredType.cs new file mode 100644 index 0000000..85396c2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedStructuredType.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Common base class for CSDL entity and complex types. + /// + internal abstract class CsdlNamedStructuredType : CsdlStructuredType + { + protected string baseTypeName; + protected bool isAbstract; + protected bool isOpen; + protected string name; + + protected CsdlNamedStructuredType(string name, string baseTypeName, bool isAbstract, bool isOpen, IEnumerable structuralproperties, IEnumerable navigationProperties, CsdlLocation location) + : base(structuralproperties, navigationProperties, location) + { + this.isAbstract = isAbstract; + this.isOpen = isOpen; + this.name = name; + this.baseTypeName = baseTypeName; + } + + public string BaseTypeName + { + get { return this.baseTypeName; } + } + + public bool IsAbstract + { + get { return this.isAbstract; } + } + + public bool IsOpen + { + get { return this.isOpen; } + } + + public string Name + { + get { return this.name; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedTypeReference.cs new file mode 100644 index 0000000..4ad3d27 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNamedTypeReference.cs @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL type reference based on a type referenced by name. + /// + internal class CsdlNamedTypeReference : CsdlTypeReference + { + public CsdlNamedTypeReference(string fullName, bool isNullable, CsdlLocation location) + : this(false, null, null, null, null, null, fullName, isNullable, location) + { + } + + public CsdlNamedTypeReference( + bool isUnbounded, + int? maxLength, + bool? isUnicode, + int? precision, + int? scale, + int? spatialReferenceIdentifier, + string fullName, + bool isNullable, + CsdlLocation location) + : base(isNullable, location) + { + this.IsUnbounded = isUnbounded; + this.MaxLength = maxLength; + this.IsUnicode = isUnicode; + this.Precision = precision; + this.Scale = scale; + this.SpatialReferenceIdentifier = spatialReferenceIdentifier; + this.FullName = fullName; + } + + public bool IsUnbounded { get; protected set; } + + public int? MaxLength { get; protected set; } + + public bool? IsUnicode { get; protected set; } + + public int? Precision { get; protected set; } + + public int? Scale { get; protected set; } + + public int? SpatialReferenceIdentifier { get; protected set; } + + public string FullName { get; protected set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationProperty.cs new file mode 100644 index 0000000..8df0e2e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationProperty.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + using System.Collections.Generic; + + /// + /// Represents a CSDL navigation property. + /// + internal class CsdlNavigationProperty : CsdlNamedElement + { + private readonly string type; + private readonly bool? nullable; + private readonly IEdmPathExpression partnerPath; + private readonly bool containsTarget; + private readonly CsdlOnDelete onDelete; + private readonly IEnumerable referentialConstraints; + + public CsdlNavigationProperty(string name, string type, bool? nullable, string partner, bool containsTarget, CsdlOnDelete onDelete, IEnumerable referentialConstraints, CsdlLocation location) + : base(name, location) + { + this.type = type; + this.nullable = nullable; + this.partnerPath = partner == null ? null : new EdmPathExpression(partner); + this.containsTarget = containsTarget; + this.onDelete = onDelete; + this.referentialConstraints = referentialConstraints; + } + + public string Type + { + get { return this.type; } + } + + public bool? Nullable + { + get { return this.nullable; } + } + + public IEdmPathExpression PartnerPath + { + get { return this.partnerPath; } + } + + public bool ContainsTarget + { + get { return this.containsTarget; } + } + + public CsdlOnDelete OnDelete + { + get { return this.onDelete; } + } + + public IEnumerable ReferentialConstraints + { + get { return this.referentialConstraints; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyBinding.cs new file mode 100644 index 0000000..4c54511 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyBinding.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Navigation Property Binding. + /// + internal class CsdlNavigationPropertyBinding : CsdlElement + { + private readonly string path; + private readonly string target; + + public CsdlNavigationPropertyBinding(string path, string target, CsdlLocation location) + : base(location) + { + this.path = path; + this.target = target; + } + + public string Path + { + get { return this.path; } + } + + public string Target + { + get { return this.target; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyPathExpression.cs new file mode 100644 index 0000000..fbd4253 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlNavigationPropertyPathExpression.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL navigation property Path expression. + /// + internal class CsdlNavigationPropertyPathExpression : CsdlPathExpression + { + public CsdlNavigationPropertyPathExpression(string path, CsdlLocation location) + : base(path, location) + { + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.NavigationPropertyPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOnDelete.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOnDelete.cs new file mode 100644 index 0000000..fa63b2e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOnDelete.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL "on delete" action. + /// + internal class CsdlOnDelete : CsdlElement + { + private readonly EdmOnDeleteAction action; + + public CsdlOnDelete(EdmOnDeleteAction action, CsdlLocation location) + : base(location) + { + this.action = action; + } + + public EdmOnDeleteAction Action + { + get { return this.action; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperation.cs new file mode 100644 index 0000000..b0e4f64 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperation.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Operation. + /// + internal class CsdlOperation : CsdlFunctionBase + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The parameters. + /// The operation return. + /// if set to true [is bound]. + /// The entity set path. + /// The location. + public CsdlOperation( + string name, + IEnumerable parameters, + CsdlOperationReturn operationReturn, + bool isBound, + string entitySetPath, + CsdlLocation location) + : base(name, parameters, operationReturn, location) + { + this.IsBound = isBound; + this.EntitySetPath = entitySetPath; + } + + /// + /// Gets a value indicating whether this instance is bound. + /// + public bool IsBound { get; private set; } + + /// + /// Gets the entity set path. + /// + public string EntitySetPath { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationImport.cs new file mode 100644 index 0000000..5714faf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationImport.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL operation import. + /// + internal abstract class CsdlOperationImport : CsdlFunctionBase + { + private readonly string entitySet; + + protected CsdlOperationImport( + string name, + string schemaOperationQualifiedTypeName, + string entitySet, + IEnumerable parameters, + CsdlOperationReturn returnType, + CsdlLocation location) + : base(name, parameters, returnType, location) + { + this.entitySet = entitySet; + this.SchemaOperationQualifiedTypeName = schemaOperationQualifiedTypeName; + } + + public string EntitySet + { + get { return this.entitySet; } + } + + /// + /// Gets the name of the schema operation qualified type. + /// + public string SchemaOperationQualifiedTypeName { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationParameter.cs new file mode 100644 index 0000000..fc3dddd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationParameter.cs @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL operation parameter. + /// + internal class CsdlOperationParameter : CsdlNamedElement + { + private readonly CsdlTypeReference type; + private readonly bool isOptional = false; + private readonly string defaultValue; + + public CsdlOperationParameter(string name, CsdlTypeReference type, CsdlLocation location) + : base(name, location) + { + this.type = type; + } + + public CsdlOperationParameter(string name, CsdlTypeReference type, CsdlLocation location, bool isOptional, string defaultValue) + : this(name, type, location) + { + this.isOptional = isOptional; + this.defaultValue = defaultValue; + } + + public CsdlTypeReference Type + { + get { return this.type; } + } + + public bool IsOptional + { + get { return this.isOptional; } + } + + public string DefaultValue + { + get { return this.defaultValue; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationReturn.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationReturn.cs new file mode 100644 index 0000000..a164010 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlOperationReturn.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL function return type. + /// + internal class CsdlOperationReturn : CsdlElement + { + private readonly CsdlTypeReference returnType; + + public CsdlOperationReturn(CsdlTypeReference returnType, CsdlLocation location) + : base(location) + { + this.returnType = returnType; + } + + public CsdlTypeReference ReturnType + { + get { return this.returnType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPathExpression.cs new file mode 100644 index 0000000..d77945a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPathExpression.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Path expression. + /// + internal class CsdlPathExpression : CsdlExpressionBase + { + private readonly string path; + + public CsdlPathExpression(string path, CsdlLocation location) + : base(location) + { + this.path = path; + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Path; } + } + + public string Path + { + get { return this.path; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPrimitiveTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPrimitiveTypeReference.cs new file mode 100644 index 0000000..3e69b92 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPrimitiveTypeReference.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a reference to a CSDL primitive type. + /// + internal class CsdlPrimitiveTypeReference : CsdlNamedTypeReference + { + private readonly EdmPrimitiveTypeKind kind; + + public CsdlPrimitiveTypeReference(EdmPrimitiveTypeKind kind, string typeName, bool isNullable, CsdlLocation location) + : base(typeName, isNullable, location) + { + this.kind = kind; + } + + public EdmPrimitiveTypeKind Kind + { + get { return this.kind; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlProperty.cs new file mode 100644 index 0000000..12c9694 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlProperty.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL property. + /// + internal class CsdlProperty : CsdlNamedElement + { + private readonly CsdlTypeReference type; + private readonly string defaultValue; + + public CsdlProperty(string name, CsdlTypeReference type, string defaultValue, CsdlLocation location) + : base(name, location) + { + this.type = type; + this.defaultValue = defaultValue; + } + + public CsdlTypeReference Type + { + get { return this.type; } + } + + public string DefaultValue + { + get { return this.defaultValue; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyPathExpression.cs new file mode 100644 index 0000000..e282107 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyPathExpression.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL property Path expression. + /// + internal class CsdlPropertyPathExpression : CsdlPathExpression + { + public CsdlPropertyPathExpression(string path, CsdlLocation location) + : base(path, location) + { + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.PropertyPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs new file mode 100644 index 0000000..d359158 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL property reference. + /// + internal class CsdlPropertyReference : CsdlElement + { + private readonly string propertyName; + + public CsdlPropertyReference(string propertyName, CsdlLocation location) + : base(location) + { + this.propertyName = propertyName; + } + + public string PropertyName + { + get { return this.propertyName; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyValue.cs new file mode 100644 index 0000000..85b2676 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyValue.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL property value in an annotation. + /// + internal class CsdlPropertyValue : CsdlElement + { + private readonly CsdlExpressionBase expression; + private readonly string property; + + public CsdlPropertyValue(string property, CsdlExpressionBase expression, CsdlLocation location) + : base(location) + { + this.property = property; + this.expression = expression; + } + + public string Property + { + get { return this.property; } + } + + public CsdlExpressionBase Expression + { + get { return this.expression; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlRecordExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlRecordExpression.cs new file mode 100644 index 0000000..293bae0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlRecordExpression.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL Record expression. + /// + internal class CsdlRecordExpression : CsdlExpressionBase + { + private readonly CsdlTypeReference type; + private readonly List propertyValues; + + public CsdlRecordExpression(CsdlTypeReference type, IEnumerable propertyValues, CsdlLocation location) + : base(location) + { + this.type = type; + this.propertyValues = new List(propertyValues); + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Record; } + } + + public CsdlTypeReference Type + { + get { return this.type; } + } + + public IEnumerable PropertyValues + { + get { return this.propertyValues; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraint.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraint.cs new file mode 100644 index 0000000..8c6cdad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraint.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL referential constraint. + /// + internal class CsdlReferentialConstraint : CsdlElement + { + private readonly string propertyName; + private readonly string referencedPropertyName; + + public CsdlReferentialConstraint(string propertyName, string referencedPropertyName, CsdlLocation location) + : base(location) + { + this.propertyName = propertyName; + this.referencedPropertyName = referencedPropertyName; + } + + public string PropertyName + { + get { return this.propertyName; } + } + + public string ReferencedPropertyName + { + get { return this.referencedPropertyName; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraintRole.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraintRole.cs new file mode 100644 index 0000000..72be804 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlReferentialConstraintRole.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL referential constraint role. + /// + internal class CsdlReferentialConstraintRole : CsdlElement + { + private readonly string role; + private readonly List properties; + + public CsdlReferentialConstraintRole(string role, IEnumerable properties, CsdlLocation location) + : base(location) + { + this.role = role; + this.properties = new List(properties); + } + + public string Role + { + get { return this.role; } + } + + public IEnumerable Properties + { + get { return this.properties; } + } + + public int IndexOf(CsdlPropertyReference reference) + { + return this.properties.IndexOf(reference); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSchema.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSchema.cs new file mode 100644 index 0000000..ec6ccea --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSchema.cs @@ -0,0 +1,105 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL schema. + /// + internal class CsdlSchema : CsdlElement + { + private readonly List structuredTypes; + private readonly List enumTypes; + private readonly List operations; + private readonly List terms; + private readonly List entityContainers; + private readonly List outOfLineAnnotations; + private readonly List typeDefinitions; + + private readonly string alias; + private readonly string namespaceName; + private readonly Version version; + + public CsdlSchema( + string namespaceName, + string alias, + Version version, + IEnumerable structuredTypes, + IEnumerable enumTypes, + IEnumerable operations, + IEnumerable terms, + IEnumerable entityContainers, + IEnumerable outOfLineAnnotations, + IEnumerable typeDefinitions, + CsdlLocation location) + : base(location) + { + this.alias = alias; + this.namespaceName = namespaceName; + this.version = version; + this.structuredTypes = new List(structuredTypes); + this.enumTypes = new List(enumTypes); + this.operations = new List(operations); + this.terms = new List(terms); + this.entityContainers = new List(entityContainers); + this.outOfLineAnnotations = new List(outOfLineAnnotations); + this.typeDefinitions = new List(typeDefinitions); + } + + public IEnumerable StructuredTypes + { + get { return this.structuredTypes; } + } + + public IEnumerable EnumTypes + { + get { return this.enumTypes; } + } + + public IEnumerable Operations + { + get { return this.operations; } + } + + public IEnumerable Terms + { + get { return this.terms; } + } + + public IEnumerable EntityContainers + { + get { return this.entityContainers; } + } + + public IEnumerable OutOfLineAnnotations + { + get { return this.outOfLineAnnotations; } + } + + public IEnumerable TypeDefinitions + { + get { return this.typeDefinitions; } + } + + public string Alias + { + get { return this.alias; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public Version Version + { + get { return this.version; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSingleton.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSingleton.cs new file mode 100644 index 0000000..d00577f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSingleton.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + using System.Collections.Generic; + + /// + /// Represents a CSDL Singleton. + /// + internal class CsdlSingleton : CsdlAbstractNavigationSource + { + private readonly string type; + + public CsdlSingleton(string name, string type, IEnumerable navigationPropertyBindings, CsdlLocation location) + : base(name, navigationPropertyBindings, location) + { + this.type = type; + } + + public string Type + { + get { return this.type; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSpatialTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSpatialTypeReference.cs new file mode 100644 index 0000000..4b31b25 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlSpatialTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + internal class CsdlSpatialTypeReference : CsdlPrimitiveTypeReference + { + private readonly int?srid; + + public CsdlSpatialTypeReference(EdmPrimitiveTypeKind kind, int? srid, string typeName, bool isNullable, CsdlLocation location) + : base(kind, typeName, isNullable, location) + { + this.srid = srid; + } + + public int? Srid + { + get { return this.srid; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStringTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStringTypeReference.cs new file mode 100644 index 0000000..d4a1190 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStringTypeReference.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a reference to a CSDL String type. + /// + internal class CsdlStringTypeReference : CsdlPrimitiveTypeReference + { + public CsdlStringTypeReference(bool isUnbounded, int? maxLength, bool? isUnicode, string typeName, bool isNullable, CsdlLocation location) + : base(EdmPrimitiveTypeKind.String, typeName, isNullable, location) + { + this.IsUnbounded = isUnbounded; + this.MaxLength = maxLength; + this.IsUnicode = isUnicode; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStructuredType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStructuredType.cs new file mode 100644 index 0000000..e86683f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlStructuredType.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Common base class for CSDL entity and complex Types. + /// + internal abstract class CsdlStructuredType : CsdlElement + { + protected List structuralProperties; + protected List navigationProperties; + + protected CsdlStructuredType(IEnumerable structuralProperties, IEnumerable navigationProperties, CsdlLocation location) + : base(location) + { + this.structuralProperties = new List(structuralProperties); + this.navigationProperties = new List(navigationProperties); + } + + public IEnumerable StructuralProperties + { + get { return this.structuralProperties; } + } + + public IEnumerable NavigationProperties + { + get { return this.navigationProperties; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTemporalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTemporalTypeReference.cs new file mode 100644 index 0000000..c5e622f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTemporalTypeReference.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a reference to a CSDL temporal type. + /// + internal class CsdlTemporalTypeReference : CsdlPrimitiveTypeReference + { + public CsdlTemporalTypeReference(EdmPrimitiveTypeKind kind, int? precision, string typeName, bool isNullable, CsdlLocation location) + : base(kind, typeName, isNullable, location) + { + this.Precision = precision; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTerm.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTerm.cs new file mode 100644 index 0000000..292fd0c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTerm.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL term. + /// + internal class CsdlTerm : CsdlNamedElement + { + private readonly CsdlTypeReference type; + private readonly string appliesTo; + private readonly string defaultValue; + + public CsdlTerm(string name, CsdlTypeReference type, string appliesTo, string defaultValue, CsdlLocation location) + : base(name, location) + { + this.type = type; + this.appliesTo = appliesTo; + this.defaultValue = defaultValue; + } + + public CsdlTypeReference Type + { + get { return this.type; } + } + + public string AppliesTo + { + get { return this.appliesTo; } + } + + public string DefaultValue + { + get { return this.defaultValue; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeDefinition.cs new file mode 100644 index 0000000..2df449a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeDefinition.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a CSDL type definition. + /// + internal class CsdlTypeDefinition : CsdlNamedElement + { + private readonly string underlyingTypeName; + + public CsdlTypeDefinition(string name, string underlyingTypeName, CsdlLocation location) + : base(name, location) + { + this.underlyingTypeName = underlyingTypeName; + } + + public string UnderlyingTypeName + { + get { return this.underlyingTypeName; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeReference.cs new file mode 100644 index 0000000..fef7d93 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlTypeReference.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Base type for the two kinds of type reference: and . + /// + internal abstract class CsdlTypeReference : CsdlElement + { + private readonly bool isNullable; + + protected CsdlTypeReference(bool isNullable, CsdlLocation location) + : base(location) + { + this.isNullable = isNullable; + } + + public bool IsNullable + { + get { return this.isNullable; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlUntypedTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlUntypedTypeReference.cs new file mode 100644 index 0000000..98358cf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlUntypedTypeReference.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents a reference to a CSDL Untyped type. + /// + internal class CsdlUntypedTypeReference : CsdlNamedTypeReference + { + public CsdlUntypedTypeReference(string typeName, CsdlLocation location) + : base(typeName, true, location) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/ICsdlTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/ICsdlTypeExpression.cs new file mode 100644 index 0000000..94fb0c8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/ICsdlTypeExpression.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.Parsing.Ast +{ + /// + /// Represents an inline type expression, such as and + /// in the context of . + /// Note that nominal type declarations, such as entity, complex and primitive types, are not considered to be type expressions in the context + /// of - these types are handled in . + /// + internal interface ICsdlTypeExpression + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/EdmXmlDocumentParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/EdmXmlDocumentParser.cs new file mode 100644 index 0000000..6739af3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/EdmXmlDocumentParser.cs @@ -0,0 +1,517 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Common +{ + internal abstract class EdmXmlDocumentParser : XmlDocumentParser + { + protected XmlElementInfo currentElement; + private readonly Stack elementStack = new Stack(); + private HashSetInternal edmNamespaces; + + internal EdmXmlDocumentParser(string artifactLocation, XmlReader reader) + : base(reader, artifactLocation) + { + } + + internal abstract IEnumerable> SupportedVersions { get; } + + internal static XmlAttributeInfo GetOptionalAttribute(XmlElementInfo element, string attributeName) + { + return element.Attributes[attributeName]; + } + + internal XmlAttributeInfo GetRequiredAttribute(XmlElementInfo element, string attributeName) + { + var attr = element.Attributes[attributeName]; + if (attr.IsMissing) + { + this.ReportError(element.Location, EdmErrorCode.MissingAttribute, Edm.Strings.XmlParser_MissingAttribute(attributeName, element.Name)); + return attr; + } + + return attr; + } + + protected override XmlReader InitializeReader(XmlReader reader) + { + XmlReaderSettings readerSettings = new XmlReaderSettings + { + CheckCharacters = true, + CloseInput = false, + IgnoreWhitespace = true, + ConformanceLevel = ConformanceLevel.Auto, + IgnoreComments = true, + IgnoreProcessingInstructions = true, +#if !ORCAS + DtdProcessing = DtdProcessing.Prohibit +#endif + }; + + // user specified a stream to read from, read from it. + // The Uri is just used to identify the stream in errors. + return XmlReader.Create(reader, readerSettings); + } + + protected override bool TryGetDocumentVersion(string xmlNamespaceName, out Version version, out string[] expectedNamespaces) + { + expectedNamespaces = this.SupportedVersions.Select(v => v.Value).ToArray(); + version = this.SupportedVersions.Where(v => v.Value == xmlNamespaceName).Select(v => v.Key).FirstOrDefault(); + return version != null; + } + + protected override bool IsOwnedNamespace(string namespaceName) + { + return this.IsEdmNamespace(namespaceName); + } + + #region Utility Methods for derived document parsers + + protected XmlElementParser CsdlElement(string elementName, Func initializer, params XmlElementParser[] childParsers) + where TItem : class + { + return Element( + elementName, + (element, childValues) => + { + BeginItem(element); + TItem result = initializer(element, childValues); + AnnotateItem(result, childValues); + EndItem(); + + return result; + }, + childParsers); + } + + protected void BeginItem(XmlElementInfo element) + { + this.elementStack.Push(element); + this.currentElement = element; + } + + protected abstract void AnnotateItem(object result, XmlElementValueCollection childValues); + + protected void EndItem() + { + this.elementStack.Pop(); + this.currentElement = this.elementStack.Count == 0 ? null : this.elementStack.Peek(); + } + + protected int? OptionalInteger(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + int? value; + if (!EdmValueParser.TryParseInt(attr.Value, out value)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidInteger, Edm.Strings.ValueParser_InvalidInteger(attr.Value)); + } + + return value; + } + + return null; + } + + protected long? OptionalLong(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + long? value; + if (!EdmValueParser.TryParseLong(attr.Value, out value)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidLong, Edm.Strings.ValueParser_InvalidLong(attr.Value)); + } + + return value; + } + + return null; + } + + protected int? OptionalSrid(string attributeName, int defaultSrid) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + int? srid; + if (attr.Value.EqualsOrdinalIgnoreCase(CsdlConstants.Value_SridVariable)) + { + srid = null; + } + else + { + if (!EdmValueParser.TryParseInt(attr.Value, out srid)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidSrid, Edm.Strings.ValueParser_InvalidSrid(attr.Value)); + } + } + + return srid; + } + + return defaultSrid; + } + + protected int? OptionalScale(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + int? scale; + if (attr.Value.EqualsOrdinalIgnoreCase(CsdlConstants.Value_ScaleVariable)) + { + scale = null; + } + else + { + if (!EdmValueParser.TryParseInt(attr.Value, out scale)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidSrid, Edm.Strings.ValueParser_InvalidScale(attr.Value)); + } + } + + return scale; + } + + return CsdlConstants.Default_Scale; + } + + protected int? OptionalMaxLength(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + int? value; + if (!EdmValueParser.TryParseInt(attr.Value, out value)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidMaxLength, Edm.Strings.ValueParser_InvalidMaxLength(attr.Value)); + } + + return value; + } + + return null; + } + + protected EdmMultiplicity RequiredMultiplicity(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + switch (attr.Value) + { + case CsdlConstants.Value_EndRequired: + return EdmMultiplicity.One; + case CsdlConstants.Value_EndOptional: + return EdmMultiplicity.ZeroOrOne; + case CsdlConstants.Value_EndMany: + return EdmMultiplicity.Many; + default: + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidMultiplicity, Edm.Strings.CsdlParser_InvalidMultiplicity(attr.Value)); + break; + } + } + + return EdmMultiplicity.One; + } + + protected EdmOnDeleteAction RequiredOnDeleteAction(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + switch (attr.Value) + { + case CsdlConstants.Value_None: + return EdmOnDeleteAction.None; + case CsdlConstants.Value_Cascade: + return EdmOnDeleteAction.Cascade; + default: + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidOnDelete, Edm.Strings.CsdlParser_InvalidDeleteAction(attr.Value)); + break; + } + } + + return EdmOnDeleteAction.None; + } + + protected bool? OptionalBoolean(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + bool? value; + if (!EdmValueParser.TryParseBool(attr.Value, out value)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidBoolean, Edm.Strings.ValueParser_InvalidBoolean(attr.Value)); + } + + return value; + } + + return null; + } + + protected string Optional(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + return !attr.IsMissing ? attr.Value : null; + } + + protected string Required(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + return !attr.IsMissing ? attr.Value : string.Empty; + } + + protected string OptionalAlias(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateAlias(attr.Value); + } + + return null; + } + + protected string RequiredAlias(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateAlias(attr.Value); + } + + return null; + } + + protected string RequiredEntitySetPath(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateEntitySetPath(attr.Value); + } + + return null; + } + + protected string RequiredEnumMemberPath(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateEnumMemberPath(attr.Value); + } + + return null; + } + + protected string RequiredEnumMemberPath(XmlTextValue text) + { + string enumMemberPath = text != null ? text.TextValue : string.Empty; + return this.ValidateEnumMembersPath(enumMemberPath); + } + + protected string OptionalType(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateTypeName(attr.Value); + } + + return null; + } + + protected string RequiredType(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateTypeName(attr.Value); + } + + return null; + } + + protected string OptionalQualifiedName(string attributeName) + { + XmlAttributeInfo attr = GetOptionalAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateQualifiedName(attr.Value); + } + + return null; + } + + protected string RequiredQualifiedName(string attributeName) + { + XmlAttributeInfo attr = this.GetRequiredAttribute(this.currentElement, attributeName); + if (!attr.IsMissing) + { + return this.ValidateQualifiedName(attr.Value); + } + + return null; + } + + protected string ValidateEnumMembersPath(string path) + { + if (string.IsNullOrEmpty(path.Trim())) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidEnumMemberPath, Edm.Strings.CsdlParser_InvalidEnumMemberPath(path)); + } + + string[] enumValues = path.Split(' ').Where(s => !string.IsNullOrEmpty(s)).ToArray(); + string enumType = null; + foreach (var enumValue in enumValues) + { + string[] segments = enumValue.Split('/'); + if (!(segments.Count() == 2 && + EdmUtil.IsValidDottedName(segments[0]) && + EdmUtil.IsValidUndottedName(segments[1]))) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidEnumMemberPath, Edm.Strings.CsdlParser_InvalidEnumMemberPath(path)); + } + + if (enumType != null && segments[0] != enumType) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidEnumMemberPath, Edm.Strings.CsdlParser_InvalidEnumMemberPath(path)); + } + + enumType = segments[0]; + } + + return string.Join(" ", enumValues); + } + + private string ValidateTypeName(string name) + { + string[] typeInformation = name.Split(new char[] { '(', ')' }); + string typeName = typeInformation[0]; + + // For inline types, we need to check that the name contained inside is a valid type name + switch (typeName) + { + case CsdlConstants.Value_Collection: + // 'Collection' on its own is a valid type string. + if (typeInformation.Count() == 1) + { + return name; + } + else + { + typeName = typeInformation[1]; + } + + break; + case CsdlConstants.Value_Ref: + // 'Ref' on its own is not a valid type string. + if (typeInformation.Count() == 1) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidTypeName, Edm.Strings.CsdlParser_InvalidTypeName(name)); + return name; + } + else + { + typeName = typeInformation[1]; + } + + break; + } + + if (EdmUtil.IsQualifiedName(typeName) || Microsoft.OData.Edm.EdmCoreModel.Instance.GetPrimitiveTypeKind(typeName) != EdmPrimitiveTypeKind.None) + { + return name; + } + else + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidTypeName, Edm.Strings.CsdlParser_InvalidTypeName(name)); + return name; + } + } + + private string ValidateAlias(string name) + { + if (!EdmUtil.IsValidUndottedName(name)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidQualifiedName, Edm.Strings.CsdlParser_InvalidAlias(name)); + } + + return name; + } + + private string ValidateEntitySetPath(string path) + { + string[] segments = path.Split('/'); + if (!(segments.Count() == 2 && + EdmUtil.IsValidDottedName(segments[0]) && + EdmUtil.IsValidUndottedName(segments[1]))) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidEntitySetPath, Edm.Strings.CsdlParser_InvalidEntitySetPath(path)); + } + + return path; + } + + private string ValidateEnumMemberPath(string path) + { + string[] segments = path.Split('/'); + if (!(segments.Count() == 2 && + EdmUtil.IsValidDottedName(segments[0]) && + EdmUtil.IsValidUndottedName(segments[1]))) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidEnumMemberPath, Edm.Strings.CsdlParser_InvalidEnumMemberPath(path)); + } + + return path; + } + + private string ValidateQualifiedName(string qualifiedName) + { + if (!EdmUtil.IsQualifiedName(qualifiedName)) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidQualifiedName, Edm.Strings.CsdlParser_InvalidQualifiedName(qualifiedName)); + } + + return qualifiedName; + } + + private bool IsEdmNamespace(string xmlNamespaceUri) + { + Debug.Assert(!string.IsNullOrEmpty(xmlNamespaceUri), "Ensure namespace URI is not null or empty before calling IsEdmNamespace"); + + if (this.edmNamespaces == null) + { + this.edmNamespaces = new HashSetInternal(); + foreach (var namespaces in CsdlConstants.SupportedVersions.Values) + { + foreach (var edmNamespace in namespaces) + { + this.edmNamespaces.Add(edmNamespace); + } + } + } + + return this.edmNamespaces.Contains(xmlNamespaceUri); + } + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlDocumentParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlDocumentParser.cs new file mode 100644 index 0000000..4190d8f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlDocumentParser.cs @@ -0,0 +1,606 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Xml; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Common +{ + /// + /// Base class for parsers of XML documents + /// + internal abstract class XmlDocumentParser + { + private readonly string docPath; + private readonly Stack currentBranch = new Stack(); + + private XmlReader reader; + private IXmlLineInfo xmlLineInfo; + private List errors; + private StringBuilder currentText; + private CsdlLocation currentTextLocation; + private ElementScope currentScope; + + protected XmlDocumentParser(XmlReader underlyingReader, string documentPath) + { + this.reader = underlyingReader; + this.docPath = documentPath; + this.errors = new List(); + } + + internal string DocumentPath + { + get { return this.docPath; } + } + + internal string DocumentNamespace + { + get; private set; + } + + internal Version DocumentVersion + { + get; private set; + } + + internal CsdlLocation DocumentElementLocation + { + get; private set; + } + + internal bool HasErrors + { + get; private set; + } + + internal XmlElementValue Result + { + get; private set; + } + + internal CsdlLocation Location + { + get + { + int lineNumber = 0; + int linePosition = 0; + + if (this.xmlLineInfo != null && this.xmlLineInfo.HasLineInfo()) + { + lineNumber = this.xmlLineInfo.LineNumber; + linePosition = this.xmlLineInfo.LinePosition; + } + + return new CsdlLocation(DocumentPath, lineNumber, linePosition); + } + } + + internal IEnumerable Errors + { + get { return this.errors; } + } + + private bool IsTextNode + { + get + { + switch (this.reader.NodeType) + { + case XmlNodeType.CDATA: + case XmlNodeType.Text: + case XmlNodeType.SignificantWhitespace: + return true; + + default: + return false; + } + } + } + + internal void ParseDocumentElement() + { + Debug.Assert(this.DocumentNamespace == null && this.xmlLineInfo == null, "Calling MoveToDocumentElement more than once?"); + + this.reader = this.InitializeReader(this.reader); + this.xmlLineInfo = this.reader as IXmlLineInfo; + + // To make life simpler, we skip down to the first/root element, unless we're + // already there + if (this.reader.NodeType != XmlNodeType.Element) + { + while (this.reader.Read() && this.reader.NodeType != XmlNodeType.Element) + { + } + } + + // There must be a root element for all current artifacts + if (this.reader.EOF) + { + this.ReportEmptyFile(); + return; + } + + // The root element must be in an expected namespace that maps to a version of the input artifact type. + this.DocumentNamespace = this.reader.NamespaceURI; + Version discoveredVersion; + string[] expectedNamespaces; + if (this.TryGetDocumentVersion(this.DocumentNamespace, out discoveredVersion, out expectedNamespaces)) + { + this.DocumentVersion = discoveredVersion; + } + else + { + this.ReportUnexpectedRootNamespace(this.reader.LocalName, this.DocumentNamespace, expectedNamespaces); + return; + } + + this.DocumentElementLocation = this.Location; + + // At this point the root element is in one of the expected namespaces but may not be the expected root element for that namespace + bool emptyElement = this.reader.IsEmptyElement; + XmlElementInfo rootElement = this.ReadElement(this.reader.LocalName, this.DocumentElementLocation); + XmlElementParser currentParser; + if (!this.TryGetRootElementParser(this.DocumentVersion, rootElement, out currentParser)) + { + this.ReportUnexpectedRootElement(rootElement.Location, rootElement.Name, this.DocumentNamespace); + return; + } + + this.BeginElement(currentParser, rootElement); + if (emptyElement) + { + this.EndElement(); + } + else + { + this.Parse(); + } + } + + protected void ReportError(CsdlLocation errorLocation, EdmErrorCode errorCode, string errorMessage) + { + this.errors.Add(new EdmError(errorLocation, errorCode, errorMessage)); + this.HasErrors = true; + } + + protected abstract XmlReader InitializeReader(XmlReader inputReader); + + protected abstract bool TryGetDocumentVersion(string xmlNamespaceName, out Version version, out string[] expectedNamespaces); + + protected abstract bool TryGetRootElementParser(Version artifactVersion, XmlElementInfo rootElement, out XmlElementParser parser); + + protected virtual bool IsOwnedNamespace(string namespaceName) + { + return this.DocumentNamespace.EqualsOrdinal(namespaceName); + } + + protected virtual XmlElementParser Element(string elementName, Func parserFunc, params XmlElementParser[] childParsers) + { + return XmlElementParser.Create(elementName, parserFunc, childParsers, null); + } + + private void Parse() + { + Debug.Assert(this.DocumentNamespace != null && !this.HasErrors, "Calling Parse when MoveToDocumentElement failed?"); + Debug.Assert(this.Result == null, "Calling Parse more than once?"); + + while (this.currentBranch.Count > 0 && + this.reader.Read()) + { + this.ProcessNode(); + } + + if (this.reader.EOF) + { + Debug.Assert(!(this.currentBranch.Count > 0), "XmlParser has consumed tags in an imbalanced fashion"); + } + else + { + // this forces the reader to look beyond this top + // level element, and complain if there is another one. + this.reader.Read(); + } + } + + private void EndElement() + { + ElementScope scope = this.currentBranch.Pop(); + this.currentScope = this.currentBranch.Count > 0 ? this.currentBranch.Peek() : null; + + XmlElementParser parser = scope.Parser; + XmlElementValue resultValue = parser.Parse(scope.Element, scope.ChildValues); + + if (resultValue != null) + { + if (this.currentScope != null) + { + this.currentScope.AddChildValue(resultValue); + } + else + { + this.Result = resultValue; + } + } + + foreach (var unused in scope.Element.Attributes.Unused) + { + // there's no handler for (namespace,name) and there wasn't a validation error. + // Report an error of our own if the node is in no namespace or if it is in one of our xml schemas target namespace. + this.ReportUnexpectedAttribute(unused.Location, unused.Name); + } + + // For text nodes, one may be expected but additional text should cause an error. + var textNodes = scope.ChildValues.Where(v => v.IsText); + var unusedText = textNodes.Where(t => !t.IsUsed); + if (unusedText.Any()) + { + XmlTextValue firstInvalidText; + if (unusedText.Count() == textNodes.Count()) + { + // Text is not expected at all for this element + firstInvalidText = (XmlTextValue)textNodes.First(); + } + else + { + // Additional text was unexpected + firstInvalidText = (XmlTextValue)unusedText.First(); + } + + this.ReportTextNotAllowed(firstInvalidText.Location, firstInvalidText.Value); + } + + // If any elements were unused, the csdl is not properly formed. This could be a result of an entirely unexpected element + // or, it could be an expected but superfluous element. + // Consider: + // + // ... + // ... + // ... + // + // + // The second occurrence of 'Principal' will be successfully parsed, but the element parser for ReferentialConstraint will not use its value because only the first occurence is expected. + // This will also catch if only a single type reference (Collection, EntityReference) element was expected but multiple are provided + foreach (var unusedChildValue in scope.ChildValues.Where(v => !v.IsText && !v.IsUsed)) + { + this.ReportUnusedElement(unusedChildValue.Location, unusedChildValue.Name); + } + } + + private void BeginElement(XmlElementParser elementParser, XmlElementInfo element) + { + ElementScope newScope = new ElementScope(elementParser, element); + this.currentBranch.Push(newScope); + this.currentScope = newScope; + } + + private void ProcessNode() + { + Debug.Assert(!this.reader.EOF, "ProcessNode should not be called after reader reaches EOF"); + + // If this is a text node, accumulate the text and return so that sequences of text nodes are coalesced into a single text value. + if (this.IsTextNode) + { + if (this.currentText == null) + { + this.currentText = new StringBuilder(); + this.currentTextLocation = this.Location; + } + + this.currentText.Append(this.reader.Value); + return; + } + + // If this is not a text node and text has been accumulated, set the text on the current parser before control moves to the parser for this new sub-element. + if (this.currentText != null) + { + string textValue = this.currentText.ToString(); + CsdlLocation textLocation = this.currentTextLocation; + this.currentText = null; + this.currentTextLocation = default(CsdlLocation); + + if (!EdmUtil.IsNullOrWhiteSpaceInternal(textValue) && !string.IsNullOrEmpty(textValue)) + { + this.currentScope.AddChildValue(new XmlTextValue(textLocation, textValue)); + } + } + + switch (this.reader.NodeType) + { + // we ignore these childless elements + case XmlNodeType.Whitespace: + case XmlNodeType.XmlDeclaration: + case XmlNodeType.Comment: + case XmlNodeType.Notation: + case XmlNodeType.ProcessingInstruction: + { + return; + } + + // we ignore these elements that can have children + case XmlNodeType.DocumentType: + case XmlNodeType.EntityReference: + { + this.reader.Skip(); + return; + } + + case XmlNodeType.Element: + { + this.ProcessElement(); + return; + } + + case XmlNodeType.EndElement: + { + this.EndElement(); + return; + } + + default: + { + this.ReportUnexpectedNodeType(this.reader.NodeType); + this.reader.Skip(); + return; + } + } + } + + private void ProcessElement() + { + bool emptyElement = this.reader.IsEmptyElement; + string elementNamespace = this.reader.NamespaceURI; + string elementName = this.reader.LocalName; + + if (elementNamespace == this.DocumentNamespace) + { + XmlElementParser newParser; + + if (!this.currentScope.Parser.TryGetChildElementParser(elementName, out newParser)) + { + // Don't error on unexpected annotations, just ignore + if (elementName != CsdlConstants.Element_Annotation) + { + this.ReportUnexpectedElement(this.Location, this.reader.Name); + } + + if (!emptyElement) + { + int depth = reader.Depth; + do + { + reader.Read(); + } + while (reader.Depth > depth); + } + + return; + } + + XmlElementInfo newElement = this.ReadElement(elementName, this.Location); + this.BeginElement(newParser, newElement); + if (emptyElement) + { + this.EndElement(); + } + } + else + { + // This element is not in the expected XML namespace for this artifact. + // we need to report an error if the namespace for this element is a target namespace for the xml schemas we are parsing against. + // otherwise we assume that this is either a valid 'any' element or that the xsd validator has generated an error + if (string.IsNullOrEmpty(elementNamespace) || this.IsOwnedNamespace(elementNamespace)) + { + // Don't error on unexpected annotations, just ignore + if (elementName != CsdlConstants.Element_Annotation) + { + this.ReportUnexpectedElement(this.Location, this.reader.Name); + } + + this.reader.Skip(); + } + else + { + XmlReader elementReader = this.reader.ReadSubtree(); + elementReader.MoveToContent(); + string annotationValue = elementReader.ReadOuterXml(); + this.currentScope.Element.AddAnnotation(new XmlAnnotationInfo(this.Location, elementNamespace, elementName, annotationValue, false)); + } + } + } + + private XmlElementInfo ReadElement(string elementName, CsdlLocation elementLocation) + { + Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "Retrieving attributes from non-element node?"); + + List ownedAttributes = null; + List annotationAttributes = null; + + bool hasAttributes = this.reader.MoveToFirstAttribute(); + while (hasAttributes) + { + string attributeNamespace = this.reader.NamespaceURI; + if (string.IsNullOrEmpty(attributeNamespace) || attributeNamespace.EqualsOrdinal(this.DocumentNamespace)) + { + if (ownedAttributes == null) + { + ownedAttributes = new List(); + } + + ownedAttributes.Add(new XmlAttributeInfo(this.reader.LocalName, this.reader.Value, this.Location)); + } + else + { + if (this.IsOwnedNamespace(attributeNamespace)) + { + this.ReportUnexpectedAttribute(this.Location, this.reader.Name); + } + else + { + if (annotationAttributes == null) + { + annotationAttributes = new List(); + } + + annotationAttributes.Add(new XmlAnnotationInfo(this.Location, this.reader.NamespaceURI, this.reader.LocalName, this.reader.Value, true)); + } + } + + hasAttributes = this.reader.MoveToNextAttribute(); + } + + return new XmlElementInfo(elementName, elementLocation, ownedAttributes, annotationAttributes); + } + + #region Errors + + private void ReportEmptyFile() + { + string errorMessage = this.DocumentPath == null ? + Edm.Strings.XmlParser_EmptySchemaTextReader : + Edm.Strings.XmlParser_EmptyFile(this.DocumentPath); + + this.ReportError( + this.Location, + EdmErrorCode.EmptyFile, + errorMessage); + } + + private void ReportUnexpectedRootNamespace(string elementName, string namespaceUri, string[] expectedNamespaces) + { + string expectedNamespacesString = string.Join(", ", expectedNamespaces); + string errorMessage = string.IsNullOrEmpty(namespaceUri) + ? Edm.Strings.XmlParser_UnexpectedRootElementNoNamespace(expectedNamespacesString) + : Edm.Strings.XmlParser_UnexpectedRootElementWrongNamespace(namespaceUri, expectedNamespacesString); + this.ReportError( + this.Location, + EdmErrorCode.UnexpectedXmlElement, + errorMessage); + } + + private void ReportUnexpectedRootElement(CsdlLocation elementLocation, string elementName, string expectedNamespace) + { + Debug.Assert(!string.IsNullOrEmpty(expectedNamespace), "UnexpectedRootElementInExpectedNamespace requires a valid expected namespace"); + this.ReportError(elementLocation, EdmErrorCode.UnexpectedXmlElement, Edm.Strings.XmlParser_UnexpectedRootElement(elementName, CsdlConstants.Element_Schema)); + } + + private void ReportUnexpectedAttribute(CsdlLocation errorLocation, string attributeName) + { + this.ReportError(errorLocation, EdmErrorCode.UnexpectedXmlAttribute, Edm.Strings.XmlParser_UnexpectedAttribute(attributeName)); + } + + private void ReportUnexpectedNodeType(XmlNodeType nodeType) + { + this.ReportError(this.Location, EdmErrorCode.UnexpectedXmlNodeType, Edm.Strings.XmlParser_UnexpectedNodeType(nodeType)); + } + + private void ReportUnexpectedElement(CsdlLocation errorLocation, string elementName) + { + // Don't error on unexpected annotations, just ignore + if (elementName != CsdlConstants.Element_Annotation) + { + this.ReportError(errorLocation, EdmErrorCode.UnexpectedXmlElement, Edm.Strings.XmlParser_UnexpectedElement(elementName)); + } + } + + private void ReportUnusedElement(CsdlLocation errorLocation, string elementName) + { + this.ReportError(errorLocation, EdmErrorCode.UnexpectedXmlElement, Edm.Strings.XmlParser_UnusedElement(elementName)); + } + + private void ReportTextNotAllowed(CsdlLocation errorLocation, string textValue) + { + this.ReportError(errorLocation, EdmErrorCode.TextNotAllowed, Edm.Strings.XmlParser_TextNotAllowed(textValue)); + } + + #endregion + + #region Scope Management + + private class ElementScope + { + private static readonly IList EmptyValues = new System.Collections.ObjectModel.ReadOnlyCollection(new XmlElementValue[] { }); + + private List childValues; + + internal ElementScope(XmlElementParser parser, XmlElementInfo element) + { + this.Parser = parser; + this.Element = element; + } + + internal XmlElementParser Parser + { + get; + private set; + } + + internal XmlElementInfo Element + { + get; + private set; + } + + internal IList ChildValues + { + get { return this.childValues ?? EmptyValues; } + } + + internal void AddChildValue(XmlElementValue value) + { + if (this.childValues == null) + { + this.childValues = new List(); + } + + this.childValues.Add(value); + } + } + + #endregion + } + + internal abstract class XmlDocumentParser : XmlDocumentParser + { + internal XmlDocumentParser(XmlReader underlyingReader, string documentPath) + : base(underlyingReader, documentPath) + { + } + + internal new XmlElementValue Result + { + get + { + if (base.Result != null) + { + Debug.Assert(base.Result is XmlElementValue && base.Result.UntypedValue is TResult, "DocumentParser without ElementParser as root element parser?"); + return (XmlElementValue)base.Result; + } + + return null; + } + } + + protected override sealed bool TryGetRootElementParser(Version artifactVersion, XmlElementInfo rootElement, out XmlElementParser parser) + { + XmlElementParser typedParser; + if (this.TryGetDocumentElementParser(artifactVersion, rootElement, out typedParser)) + { + parser = typedParser; + return true; + } + + parser = null; + return false; + } + + protected abstract bool TryGetDocumentElementParser(Version artifactVersion, XmlElementInfo rootElement, out XmlElementParser parser); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementInfo.cs new file mode 100644 index 0000000..e58c960 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementInfo.cs @@ -0,0 +1,198 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Common +{ + internal interface IXmlElementAttributes + { + IEnumerable Unused + { + get; + } + + XmlAttributeInfo this[string attributeName] + { + get; + } + } + + internal class XmlElementInfo : IXmlElementAttributes + { + private readonly Dictionary attributes; + private List annotations; + + internal XmlElementInfo(string elementName, CsdlLocation elementLocation, IList attributes, List annotations) + { + this.Name = elementName; + this.Location = elementLocation; + if (attributes != null && attributes.Count > 0) + { + this.attributes = new Dictionary(); + foreach (XmlAttributeInfo newAttr in attributes) + { + Debug.Assert(!this.attributes.ContainsKey(newAttr.Name), "Multiple attributes with the same name are not supported"); + this.attributes.Add(newAttr.Name, newAttr); + } + } + + this.annotations = annotations; + } + + IEnumerable IXmlElementAttributes.Unused + { + get + { + if (this.attributes != null) + { + foreach (XmlAttributeInfo attr in this.attributes.Values.Where(attr => !attr.IsUsed)) + { + yield return attr; + } + } + } + } + + internal string Name + { + get; + private set; + } + + internal CsdlLocation Location + { + get; + private set; + } + + internal IXmlElementAttributes Attributes + { + get { return (IXmlElementAttributes)this; } + } + + internal IList Annotations + { + get { return this.annotations ?? ((IList)new XmlAnnotationInfo[] { }); } + } + + XmlAttributeInfo IXmlElementAttributes.this[string attributeName] + { + get + { + Debug.Assert(!string.IsNullOrEmpty(attributeName), "Ensure attribute name is not null or empty before accessing Attributes"); + + XmlAttributeInfo foundAttr; + if (this.attributes != null && this.attributes.TryGetValue(attributeName, out foundAttr)) + { + foundAttr.IsUsed = true; + return foundAttr; + } + + return XmlAttributeInfo.Missing; + } + } + + internal void AddAnnotation(XmlAnnotationInfo annotation) + { + if (this.annotations == null) + { + this.annotations = new List(); + } + + this.annotations.Add(annotation); + } + } + + internal class XmlAnnotationInfo + { + internal XmlAnnotationInfo(CsdlLocation location, string namespaceName, string name, string value, bool isAttribute) + { + this.Location = location; + this.NamespaceName = namespaceName; + this.Name = name; + this.Value = value; + this.IsAttribute = isAttribute; + } + + internal string NamespaceName + { + get; + private set; + } + + internal string Name + { + get; + private set; + } + + internal CsdlLocation Location + { + get; + private set; + } + + internal string Value + { + get; + private set; + } + + internal bool IsAttribute + { + get; + private set; + } + } + + internal class XmlAttributeInfo + { + internal static readonly XmlAttributeInfo Missing = new XmlAttributeInfo(); + private readonly string name; + private readonly string attributeValue; + private readonly CsdlLocation location; + + internal XmlAttributeInfo(string attrName, string attrValue, CsdlLocation attrLocation) + { + this.name = attrName; + this.attributeValue = attrValue; + this.location = attrLocation; + } + + private XmlAttributeInfo() + { + } + + internal bool IsMissing + { + get { return object.ReferenceEquals(XmlAttributeInfo.Missing, this); } + } + + internal bool IsUsed + { + get; + set; + } + + internal CsdlLocation Location + { + get { return this.location; } + } + + internal string Name + { + get { return this.name; } + } + + internal string Value + { + get { return this.attributeValue; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementParser.cs new file mode 100644 index 0000000..2a04e52 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/Common/XmlElementParser.cs @@ -0,0 +1,318 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.OData.Edm.Csdl.Parsing.Common +{ + internal static class XmlElementValueExtensions + { + internal static IEnumerable> OfResultType(this IEnumerable elements) + where T : class + { + foreach (var element in elements) + { + XmlElementValue result = element as XmlElementValue; + if (result != null) + { + yield return result; + } + else if (element.UntypedValue is T) + { + yield return new XmlElementValue(element.Name, element.Location, element.ValueAs()); + } + } + } + + internal static IEnumerable ValuesOfType(this IEnumerable elements) + where T : class + { + return elements.OfResultType().Select(ev => ev.Value); + } + + internal static IEnumerable OfText(this IEnumerable elements) + { + foreach (var element in elements) + { + if (element.IsText) + { + yield return (XmlTextValue)element; + } + } + } + } + + internal abstract class XmlElementParser + { + private readonly Dictionary childParsers; + + protected XmlElementParser(string elementName, Dictionary children) + { + this.ElementName = elementName; + this.childParsers = children; + } + + internal string ElementName + { + get; + private set; + } + + public void AddChildParser(XmlElementParser child) + { + this.childParsers[child.ElementName] = child; + } + + #region Factory Methods + + internal static XmlElementParser Create(string elementName, Func parserFunc, IEnumerable childParsers, IEnumerable descendantParsers) + { + Dictionary children = null; + if (childParsers != null) + { + children = childParsers.ToDictionary(p => p.ElementName); + } + + return new XmlElementParser(elementName, children, parserFunc); + } + #endregion + + internal abstract XmlElementValue Parse(XmlElementInfo element, IList children); + + internal bool TryGetChildElementParser(string elementName, out XmlElementParser elementParser) + { + elementParser = null; + return this.childParsers != null && this.childParsers.TryGetValue(elementName, out elementParser); + } + } + + internal class XmlElementParser : XmlElementParser + { + private readonly Func parserFunc; + + internal XmlElementParser( + string elementName, + Dictionary children, + Func parser) + : base(elementName, children) + { + this.parserFunc = parser; + } + + internal override XmlElementValue Parse(XmlElementInfo element, IList children) + { + TResult result = this.parserFunc(element, XmlElementValueCollection.FromList(children)); + return new XmlElementValue(element.Name, element.Location, result); + } + } + + internal class XmlElementValueCollection : IEnumerable + { + private static readonly XmlElementValueCollection empty = new XmlElementValueCollection(new XmlElementValue[] { }, new XmlElementValue[] { }.ToLookup(value => value.Name)); + + private readonly IList values; + private ILookup nameLookup; + + private XmlElementValueCollection(IList list, ILookup nameMap) + { + Debug.Assert(list != null, "FromList should replace null list with XmlElementValueCollection.empty"); + this.values = list; + this.nameLookup = nameMap; + } + + internal XmlTextValue FirstText + { + get { return (this.values.OfText().FirstOrDefault() ?? XmlTextValue.Missing); } + } + + internal XmlElementValue this[string elementName] + { + get + { + return this.EnsureLookup()[elementName].FirstOrDefault() ?? MissingXmlElementValue.Instance; + } + } + + public IEnumerator GetEnumerator() + { + return this.values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.values.GetEnumerator(); + } + + internal bool Remove(XmlElementValue value) + { + if (value == null) + { + return false; + } + + return this.values.Remove(value); + } + + internal static XmlElementValueCollection FromList(IList values) + { + if (values == null || values.Count == 0) + { + return XmlElementValueCollection.empty; + } + + return new XmlElementValueCollection(values, null); + } + + internal IEnumerable FindByName(string elementName) + { + return this.EnsureLookup()[elementName]; + } + + internal IEnumerable> FindByName(string elementName) + where TResult : class + { + return this.FindByName(elementName).OfResultType(); + } + + private ILookup EnsureLookup() + { + return this.nameLookup ?? (this.nameLookup = this.values.ToLookup(value => value.Name)); + } + + internal sealed class MissingXmlElementValue : XmlElementValue + { + internal static readonly MissingXmlElementValue Instance = new MissingXmlElementValue(); + + private MissingXmlElementValue() + : base(null, default(CsdlLocation)) + { + } + + internal override object UntypedValue + { + get { return null; } + } + + internal override bool IsUsed + { + get { return false; } + } + } + } + + internal abstract class XmlElementValue + { + internal XmlElementValue(string elementName, CsdlLocation elementLocation) + { + this.Name = elementName; + this.Location = elementLocation; + } + + internal string Name + { + get; + private set; + } + + internal CsdlLocation Location + { + get; + private set; + } + + internal abstract object UntypedValue + { + get; + } + + internal abstract bool IsUsed + { + get; + } + + internal virtual bool IsText + { + get { return false; } + } + + internal virtual string TextValue + { + get { return this.ValueAs(); } + } + + internal virtual TValue ValueAs() where TValue : class + { + return this.UntypedValue as TValue; + } + } + + internal class XmlElementValue : XmlElementValue + { + private readonly TValue value; + private bool isUsed; + + internal XmlElementValue(string name, CsdlLocation location, TValue newValue) + : base(name, location) + { + this.value = newValue; + } + + internal override bool IsText + { + get { return false; } + } + + internal override bool IsUsed + { + get { return this.isUsed; } + } + + internal override object UntypedValue + { + get { return this.value; } + } + + internal TValue Value + { + get + { + this.isUsed = true; + return this.value; + } + } + + internal override T ValueAs() + { + return this.Value as T; + } + } + + internal class XmlTextValue : XmlElementValue + { + internal static readonly XmlTextValue Missing = new XmlTextValue(default(CsdlLocation), null); + + internal const string ElementName = "<\"Text\">"; + + internal XmlTextValue(CsdlLocation textLocation, string textValue) + : base(ElementName, textLocation, textValue) + { + } + + internal override bool IsText + { + get { return true; } + } + + internal override string TextValue + { + get { return this.Value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs new file mode 100644 index 0000000..7a82d1e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs @@ -0,0 +1,1317 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Csdl.Parsing.Common; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Csdl.Parsing +{ + /// + /// CSDL document parser. + /// + internal class CsdlDocumentParser : EdmXmlDocumentParser + { + private Version artifactVersion; + private int entityContainerCount; + + internal CsdlDocumentParser(string documentPath, XmlReader reader) + : base(documentPath, reader) + { + entityContainerCount = 0; + } + + internal override IEnumerable> SupportedVersions + { + get { return CsdlConstants.SupportedVersions.SelectMany(kvp => kvp.Value.Select(ns => new KeyValuePair(kvp.Key, ns))); } + } + + protected override bool TryGetDocumentElementParser(Version csdlArtifactVersion, XmlElementInfo rootElement, out XmlElementParser parser) + { + EdmUtil.CheckArgumentNull(rootElement, "rootElement"); + this.artifactVersion = csdlArtifactVersion; + if (string.Equals(rootElement.Name, CsdlConstants.Element_Schema, StringComparison.Ordinal)) + { + parser = this.CreateRootElementParser(); + return true; + } + + parser = null; + return false; + } + + protected override void AnnotateItem(object result, XmlElementValueCollection childValues) + { + CsdlElement annotatedItem = result as CsdlElement; + if (annotatedItem == null) + { + return; + } + + foreach (var xmlAnnotation in this.currentElement.Annotations) + { + annotatedItem.AddAnnotation(new CsdlDirectValueAnnotation(xmlAnnotation.NamespaceName, xmlAnnotation.Name, xmlAnnotation.Value, xmlAnnotation.IsAttribute, xmlAnnotation.Location)); + } + + foreach (var annotation in childValues.ValuesOfType()) + { + annotatedItem.AddAnnotation(annotation); + } + } + + private XmlElementParser CreateRootElementParser() + { + // There is recursion in the grammar between CollectionType, ReturnType, and Property within RowType. + // This requires breaking up the parser construction into pieces and then weaving them together with AddChildParser. + var referenceTypeParser = + //// + CsdlElement(CsdlConstants.Element_ReferenceType, this.OnEntityReferenceTypeElement); + + var collectionTypeParser = + //// + CsdlElement(CsdlConstants.Element_CollectionType, this.OnCollectionTypeElement, + //// + CsdlElement(CsdlConstants.Element_TypeRef, this.OnTypeRefElement), + //// + referenceTypeParser); + //// + + var nominalTypePropertyElementParser = + //// + CsdlElement(CsdlConstants.Element_Property, this.OnPropertyElement); + + var stringConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_String, OnStringConstantExpression); + + var binaryConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Binary, OnBinaryConstantExpression); + + var intConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Int, OnIntConstantExpression); + + var floatConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Float, OnFloatConstantExpression); + + var guidConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Guid, OnGuidConstantExpression); + + var decimalConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Decimal, OnDecimalConstantExpression); + + var boolConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Bool, OnBoolConstantExpression); + + var durationConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Duration, OnDurationConstantExpression); + + var dateConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Date, OnDateConstantExpression); + + var timeOfDayConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_TimeOfDay, OnTimeOfDayConstantExpression); + + var dateTimeOffsetConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_DateTimeOffset, OnDateTimeOffsetConstantExpression); + + var nullConstantExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Null, OnNullConstantExpression); + + var pathExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Path, OnPathExpression); + + var propertyPathExpressionParser = + //// + CsdlElement(CsdlConstants.Element_PropertyPath, OnPropertyPathExpression); + + var navigationPropertyPathExpressionParser = + //// + CsdlElement(CsdlConstants.Element_NavigationPropertyPath, OnNavigationPropertyPathExpression); + + var enumMemberExpressionParser = + //// + CsdlElement(CsdlConstants.Element_EnumMember, this.OnEnumMemberExpression); + + var ifExpressionParser = + //// + CsdlElement(CsdlConstants.Element_If, this.OnIfExpression); + + var castExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Cast, this.OnCastExpression); + + var isTypeExpressionParser = + //// + CsdlElement(CsdlConstants.Element_IsType, this.OnIsTypeExpression); + + var propertyValueParser = + //// + CsdlElement(CsdlConstants.Element_PropertyValue, this.OnPropertyValueElement); + + var recordExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Record, this.OnRecordElement, + //// + propertyValueParser); + //// + + var labeledElementParser = + //// + CsdlElement(CsdlConstants.Element_LabeledElement, this.OnLabeledElement); + + var collectionExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Collection, this.OnCollectionElement); + + var applyExpressionParser = + //// + CsdlElement(CsdlConstants.Element_Apply, this.OnApplyElement); + + var labeledElementReferenceExpressionParser = + //// + CsdlElement(CsdlConstants.Element_LabeledElementReference, this.OnLabeledElementReferenceExpression); + + XmlElementParser[] expressionParsers = + { + //// + stringConstantExpressionParser, + //// + binaryConstantExpressionParser, + //// + intConstantExpressionParser, + //// + floatConstantExpressionParser, + //// + guidConstantExpressionParser, + //// + decimalConstantExpressionParser, + //// + boolConstantExpressionParser, + //// + dateConstantExpressionParser, + //// + dateTimeOffsetConstantExpressionParser, + //// + durationConstantExpressionParser, + //// + timeOfDayConstantExpressionParser, + //// + nullConstantExpressionParser, + //// + pathExpressionParser, + //// + propertyPathExpressionParser, + //// + navigationPropertyPathExpressionParser, + //// + ifExpressionParser, + //// + isTypeExpressionParser, + //// + castExpressionParser, + //// + recordExpressionParser, + //// + collectionExpressionParser, + //// + labeledElementReferenceExpressionParser, + //// + propertyValueParser, + //// + labeledElementParser, + //// + enumMemberExpressionParser, + //// + applyExpressionParser + }; + + AddChildParsers(ifExpressionParser, expressionParsers); + AddChildParsers(castExpressionParser, expressionParsers); + AddChildParsers(isTypeExpressionParser, expressionParsers); + AddChildParsers(propertyValueParser, expressionParsers); + AddChildParsers(collectionExpressionParser, expressionParsers); + AddChildParsers(labeledElementParser, expressionParsers); + AddChildParsers(applyExpressionParser, expressionParsers); + + var annotationParser = + //// + CsdlElement(CsdlConstants.Element_Annotation, this.OnAnnotationElement); + + AddChildParsers(annotationParser, expressionParsers); + + nominalTypePropertyElementParser.AddChildParser(annotationParser); + + collectionTypeParser.AddChildParser(collectionTypeParser); + + var rootElementParser = + //// + CsdlElement(CsdlConstants.Element_Schema, this.OnSchemaElement, + //// + CsdlElement(CsdlConstants.Element_ComplexType, this.OnComplexTypeElement, + //// + nominalTypePropertyElementParser, + //// + CsdlElement(CsdlConstants.Element_NavigationProperty, this.OnNavigationPropertyElement, + //// + CsdlElement(CsdlConstants.Element_ReferentialConstraint, this.OnReferentialConstraintElement), + //// + CsdlElement(CsdlConstants.Element_OnDelete, this.OnDeleteActionElement), + //// + annotationParser), + //// + + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_EntityType, this.OnEntityTypeElement, + //// + CsdlElement(CsdlConstants.Element_Key, OnEntityKeyElement, + //// + CsdlElement(CsdlConstants.Element_PropertyRef, this.OnPropertyRefElement)), + //// + + //// + nominalTypePropertyElementParser, + + //// + CsdlElement(CsdlConstants.Element_NavigationProperty, this.OnNavigationPropertyElement, + //// + CsdlElement(CsdlConstants.Element_ReferentialConstraint, this.OnReferentialConstraintElement), + //// + CsdlElement(CsdlConstants.Element_OnDelete, this.OnDeleteActionElement), + //// + annotationParser), + //// + + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_EnumType, this.OnEnumTypeElement, + //// + CsdlElement(CsdlConstants.Element_Member, this.OnEnumMemberElement, annotationParser), + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_TypeDefinition, this.OnTypeDefinitionElement, + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_Action, this.OnActionElement, + //// + CsdlElement(CsdlConstants.Element_Parameter, this.OnParameterElement, + //// + CsdlElement(CsdlConstants.Element_TypeRef, this.OnTypeRefElement), + //// + collectionTypeParser, + //// + referenceTypeParser, + //// + annotationParser), + //// + CsdlElement(CsdlConstants.Element_ReturnType, this.OnReturnTypeElement, + //// + CsdlElement(CsdlConstants.Element_TypeRef, this.OnTypeRefElement), + //// + collectionTypeParser, + //// + referenceTypeParser, + //// + annotationParser), + //// + + //// + annotationParser), + //// + + CsdlElement(CsdlConstants.Element_Function, this.OnFunctionElement, + //// + CsdlElement(CsdlConstants.Element_Parameter, this.OnParameterElement, + //// + CsdlElement(CsdlConstants.Element_TypeRef, this.OnTypeRefElement), + //// + collectionTypeParser, + //// + referenceTypeParser, + //// + annotationParser), + //// + CsdlElement(CsdlConstants.Element_ReturnType, this.OnReturnTypeElement, + //// + CsdlElement(CsdlConstants.Element_TypeRef, this.OnTypeRefElement), + //// + collectionTypeParser, + //// + referenceTypeParser, + //// + annotationParser), + //// + + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_Term, this.OnTermElement, + //// + CsdlElement(CsdlConstants.Element_TypeRef, this.OnTypeRefElement), + //// + collectionTypeParser, + //// + referenceTypeParser, + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_Annotations, this.OnAnnotationsElement, + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_EntityContainer, this.OnEntityContainerElement, + //// + CsdlElement(CsdlConstants.Element_EntitySet, this.OnEntitySetElement, + //// + CsdlElement(CsdlConstants.Element_NavigationPropertyBinding, this.OnNavigationPropertyBindingElement), + //// + annotationParser), + //// + + //// + CsdlElement(CsdlConstants.Element_Singleton, this.OnSingletonElement, + //// + CsdlElement(CsdlConstants.Element_NavigationPropertyBinding, this.OnNavigationPropertyBindingElement), + //// + annotationParser), + //// + + //// (CsdlConstants.Element_ActionImport, this.OnActionImportElement, + //// + annotationParser), + ////(CsdlConstants.Element_FunctionImport, this.OnFunctionImportElement, + + //// + CsdlElement(CsdlConstants.Element_Parameter, this.OnFunctionImportParameterElement, + //// + annotationParser), + //// + + //// + annotationParser), + //// + annotationParser)); + //// + //// + + return rootElementParser; + } + + private CsdlSchema OnSchemaElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string namespaceName = Optional(CsdlConstants.Attribute_Namespace) ?? string.Empty; + string alias = OptionalAlias(CsdlConstants.Attribute_Alias); + + CsdlSchema result = + new CsdlSchema( + namespaceName, + alias, + this.artifactVersion, + childValues.ValuesOfType(), + childValues.ValuesOfType(), + childValues.ValuesOfType(), + childValues.ValuesOfType(), + childValues.ValuesOfType(), + childValues.ValuesOfType(), + childValues.ValuesOfType(), + element.Location); + + return result; + } + + private CsdlComplexType OnComplexTypeElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string baseType = OptionalQualifiedName(CsdlConstants.Attribute_BaseType); + bool isOpen = OptionalBoolean(CsdlConstants.Attribute_OpenType) ?? CsdlConstants.Default_OpenType; + bool isAbstract = OptionalBoolean(CsdlConstants.Attribute_Abstract) ?? CsdlConstants.Default_Abstract; + + return new CsdlComplexType(name, baseType, isAbstract, isOpen, childValues.ValuesOfType(), childValues.ValuesOfType(), element.Location); + } + + private CsdlEntityType OnEntityTypeElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string baseType = OptionalQualifiedName(CsdlConstants.Attribute_BaseType); + bool isOpen = OptionalBoolean(CsdlConstants.Attribute_OpenType) ?? CsdlConstants.Default_OpenType; + bool isAbstract = OptionalBoolean(CsdlConstants.Attribute_Abstract) ?? CsdlConstants.Default_Abstract; + bool hasStream = OptionalBoolean(CsdlConstants.Attribute_HasStream) ?? CsdlConstants.Default_HasStream; + + CsdlKey key = childValues.ValuesOfType().FirstOrDefault(); + + return new CsdlEntityType(name, baseType, isAbstract, isOpen, hasStream, key, childValues.ValuesOfType(), childValues.ValuesOfType(), element.Location); + } + + private CsdlProperty OnPropertyElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = OptionalType(CsdlConstants.Attribute_Type); + CsdlTypeReference type = this.ParseTypeReference(typeName, childValues, element.Location, Optionality.Required); + string name = Required(CsdlConstants.Attribute_Name); + string defaultValue = Optional(CsdlConstants.Attribute_DefaultValue); + + return new CsdlProperty(name, type, defaultValue, element.Location); + } + + private CsdlTerm OnTermElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = OptionalType(CsdlConstants.Attribute_Type); + CsdlTypeReference type = this.ParseTypeReference(typeName, childValues, element.Location, Optionality.Required); + string name = Required(CsdlConstants.Attribute_Name); + string appliesTo = Optional(CsdlConstants.Attribute_AppliesTo); + string defaultValue = Optional(CsdlConstants.Attribute_DefaultValue); + + return new CsdlTerm(name, type, appliesTo, defaultValue, element.Location); + } + + private CsdlAnnotations OnAnnotationsElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string target = Required(CsdlConstants.Attribute_Target); + string qualifier = Optional(CsdlConstants.Attribute_Qualifier); + IEnumerable annotations = childValues.ValuesOfType(); + + return new CsdlAnnotations(annotations, target, qualifier); + } + + private CsdlAnnotation OnAnnotationElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string term = RequiredQualifiedName(CsdlConstants.Attribute_Term); + string qualifier = Optional(CsdlConstants.Attribute_Qualifier); + CsdlExpressionBase expression = this.ParseAnnotationExpression(element, childValues); + + return new CsdlAnnotation(term, qualifier, expression, element.Location); + } + + private CsdlPropertyValue OnPropertyValueElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string property = Required(CsdlConstants.Attribute_Property); + CsdlExpressionBase expression = this.ParseAnnotationExpression(element, childValues); + + return new CsdlPropertyValue(property, expression, element.Location); + } + + private CsdlRecordExpression OnRecordElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string type = OptionalQualifiedName(CsdlConstants.Attribute_Type); + IEnumerable propertyValues = childValues.ValuesOfType(); + + return new CsdlRecordExpression(type != null ? new CsdlNamedTypeReference(type, false, element.Location) : null, propertyValues, element.Location); + } + + private CsdlCollectionExpression OnCollectionElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = OptionalType(CsdlConstants.Attribute_Type); + CsdlTypeReference type = this.ParseTypeReference(typeName, childValues, element.Location, Optionality.Optional); + IEnumerable elementValues = childValues.ValuesOfType(); + + return new CsdlCollectionExpression(type, elementValues, element.Location); + } + + private CsdlLabeledExpression OnLabeledElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + IEnumerable expressions = childValues.ValuesOfType(); + if (expressions.Count() != 1) + { + this.ReportError(element.Location, EdmErrorCode.InvalidLabeledElementExpressionIncorrectNumberOfOperands, Edm.Strings.CsdlParser_InvalidLabeledElementExpressionIncorrectNumberOfOperands); + } + + return new CsdlLabeledExpression( + name, + expressions.ElementAtOrDefault(0), + element.Location); + } + + private CsdlApplyExpression OnApplyElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string function = Optional(CsdlConstants.Attribute_Function); + IEnumerable arguments = childValues.ValuesOfType(); + + return new CsdlApplyExpression(function, arguments, element.Location); + } + + private static void AddChildParsers(XmlElementParser parent, IEnumerable children) + { + foreach (XmlElementParser child in children) + { + parent.AddChildParser(child); + } + } + + private static CsdlConstantExpression ConstantExpression(EdmValueKind kind, XmlElementValueCollection childValues, CsdlLocation location) + { + XmlTextValue text = childValues.FirstText; + return new CsdlConstantExpression(kind, text != null ? text.TextValue : string.Empty, location); + } + + private static CsdlConstantExpression OnIntConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Integer, childValues, element.Location); + } + + private static CsdlConstantExpression OnStringConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.String, childValues, element.Location); + } + + private static CsdlConstantExpression OnBinaryConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Binary, childValues, element.Location); + } + + private static CsdlConstantExpression OnFloatConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Floating, childValues, element.Location); + } + + private static CsdlConstantExpression OnGuidConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Guid, childValues, element.Location); + } + + private static CsdlConstantExpression OnDecimalConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Decimal, childValues, element.Location); + } + + private static CsdlConstantExpression OnBoolConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Boolean, childValues, element.Location); + } + + private static CsdlConstantExpression OnDurationConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Duration, childValues, element.Location); + } + + private static CsdlConstantExpression OnDateConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Date, childValues, element.Location); + } + + private static CsdlConstantExpression OnDateTimeOffsetConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.DateTimeOffset, childValues, element.Location); + } + + private static CsdlConstantExpression OnTimeOfDayConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.TimeOfDay, childValues, element.Location); + } + + private static CsdlConstantExpression OnNullConstantExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + return ConstantExpression(EdmValueKind.Null, childValues, element.Location); + } + + private static CsdlPathExpression OnPathExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + XmlTextValue text = childValues.FirstText; + return new CsdlPathExpression(text != null ? text.TextValue : string.Empty, element.Location); + } + + private static CsdlPropertyPathExpression OnPropertyPathExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + XmlTextValue text = childValues.FirstText; + return new CsdlPropertyPathExpression(text != null ? text.TextValue : string.Empty, element.Location); + } + + private static CsdlNavigationPropertyPathExpression OnNavigationPropertyPathExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + XmlTextValue text = childValues.FirstText; + return new CsdlNavigationPropertyPathExpression(text != null ? text.TextValue : string.Empty, element.Location); + } + + private CsdlLabeledExpressionReferenceExpression OnLabeledElementReferenceExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + return new CsdlLabeledExpressionReferenceExpression(name, element.Location); + } + + private CsdlEnumMemberExpression OnEnumMemberExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + string enumMemberPath = this.RequiredEnumMemberPath(childValues.FirstText); + return new CsdlEnumMemberExpression(enumMemberPath, element.Location); + } + + private CsdlExpressionBase OnIfExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + IEnumerable expressions = childValues.ValuesOfType(); + if (expressions.Count() != 3) + { + this.ReportError(element.Location, EdmErrorCode.InvalidIfExpressionIncorrectNumberOfOperands, Edm.Strings.CsdlParser_InvalidIfExpressionIncorrectNumberOfOperands); + } + + return new CsdlIfExpression( + expressions.ElementAtOrDefault(0), + expressions.ElementAtOrDefault(1), + expressions.ElementAtOrDefault(2), + element.Location); + } + + private CsdlExpressionBase OnCastExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = OptionalType(CsdlConstants.Attribute_Type); + CsdlTypeReference type = this.ParseTypeReference(typeName, childValues, element.Location, Optionality.Required); + + IEnumerable expressions = childValues.ValuesOfType(); + if (expressions.Count() != 1) + { + this.ReportError(element.Location, EdmErrorCode.InvalidCastExpressionIncorrectNumberOfOperands, Edm.Strings.CsdlParser_InvalidCastExpressionIncorrectNumberOfOperands); + } + + return new CsdlCastExpression(type, expressions.ElementAtOrDefault(0), element.Location); + } + + private CsdlExpressionBase OnIsTypeExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = OptionalType(CsdlConstants.Attribute_Type); + CsdlTypeReference type = this.ParseTypeReference(typeName, childValues, element.Location, Optionality.Required); + + IEnumerable expressions = childValues.ValuesOfType(); + if (expressions.Count() != 1) + { + this.ReportError(element.Location, EdmErrorCode.InvalidIsTypeExpressionIncorrectNumberOfOperands, Edm.Strings.CsdlParser_InvalidIsTypeExpressionIncorrectNumberOfOperands); + } + + return new CsdlIsTypeExpression(type, expressions.ElementAtOrDefault(0), element.Location); + } + + private CsdlTypeDefinition OnTypeDefinitionElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string underlyingTypeName = RequiredType(CsdlConstants.Attribute_UnderlyingType); + + return new CsdlTypeDefinition(name, underlyingTypeName, element.Location); + } + + private CsdlExpressionBase ParseAnnotationExpression(XmlElementInfo element, XmlElementValueCollection childValues) + { + CsdlExpressionBase expression = childValues.ValuesOfType().FirstOrDefault(); + if (expression != null) + { + return expression; + } + + string pathValue = Optional(CsdlConstants.Attribute_Path); + if (pathValue != null) + { + return new CsdlPathExpression(pathValue, element.Location); + } + + string propertyPathValue = Optional(CsdlConstants.Attribute_PropertyPath); + if (propertyPathValue != null) + { + return new CsdlPropertyPathExpression(propertyPathValue, element.Location); + } + + string navigationPropertyPathValue = Optional(CsdlConstants.Attribute_NavigationPropertyPath); + if (navigationPropertyPathValue != null) + { + return new CsdlNavigationPropertyPathExpression(navigationPropertyPathValue, element.Location); + } + + string enumMemberValue = Optional(CsdlConstants.Attribute_EnumMember); + if (enumMemberValue != null) + { + return new CsdlEnumMemberExpression(this.ValidateEnumMembersPath(enumMemberValue), element.Location); + } + + string annotationPath = Optional(CsdlConstants.Attribute_AnnotationPath); + if (annotationPath != null) + { + return new CsdlAnnotationPathExpression(annotationPath, element.Location); + } + + EdmValueKind kind; + + string value = Optional(CsdlConstants.Attribute_String); + if (value != null) + { + kind = EdmValueKind.String; + } + else + { + value = Optional(CsdlConstants.Attribute_Bool); + if (value != null) + { + kind = EdmValueKind.Boolean; + } + else + { + value = Optional(CsdlConstants.Attribute_Int); + if (value != null) + { + kind = EdmValueKind.Integer; + } + else + { + value = Optional(CsdlConstants.Attribute_Float); + if (value != null) + { + kind = EdmValueKind.Floating; + } + else + { + value = Optional(CsdlConstants.Attribute_DateTimeOffset); + if (value != null) + { + kind = EdmValueKind.DateTimeOffset; + } + else + { + value = Optional(CsdlConstants.Attribute_Duration); + if (value != null) + { + kind = EdmValueKind.Duration; + } + else + { + value = Optional(CsdlConstants.Attribute_Decimal); + if (value != null) + { + kind = EdmValueKind.Decimal; + } + else + { + value = Optional(CsdlConstants.Attribute_Binary); + if (value != null) + { + kind = EdmValueKind.Binary; + } + else + { + value = Optional(CsdlConstants.Attribute_Guid); + if (value != null) + { + kind = EdmValueKind.Guid; + } + else + { + value = Optional(CsdlConstants.Attribute_Date); + if (value != null) + { + kind = EdmValueKind.Date; + } + else + { + value = Optional(CsdlConstants.Attribute_TimeOfDay); + if (value != null) + { + kind = EdmValueKind.TimeOfDay; + } + else + { + //// Annotation expressions are always optional. + return null; + } + } + } + } + } + } + } + } + } + } + } + + return new CsdlConstantExpression(kind, value, element.Location); + } + + private CsdlNamedTypeReference ParseNamedTypeReference(string typeName, bool isNullable, CsdlLocation parentLocation) + { + bool isUnbounded; + int? maxLength; + bool? unicode; + int? precision; + int? scale; + int? srid; + + EdmPrimitiveTypeKind kind = EdmCoreModel.Instance.GetPrimitiveTypeKind(typeName); + switch (kind) + { + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.PrimitiveType: + return new CsdlPrimitiveTypeReference(kind, typeName, isNullable, parentLocation); + + case EdmPrimitiveTypeKind.Binary: + this.ParseBinaryFacets(out isUnbounded, out maxLength); + return new CsdlBinaryTypeReference(isUnbounded, maxLength, typeName, isNullable, parentLocation); + + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + this.ParseTemporalFacets(out precision); + return new CsdlTemporalTypeReference(kind, precision, typeName, isNullable, parentLocation); + + case EdmPrimitiveTypeKind.Decimal: + this.ParseDecimalFacets(out precision, out scale); + return new CsdlDecimalTypeReference(precision, scale, typeName, isNullable, parentLocation); + + case EdmPrimitiveTypeKind.String: + this.ParseStringFacets(out isUnbounded, out maxLength, out unicode); + return new CsdlStringTypeReference(isUnbounded, maxLength, unicode, typeName, isNullable, parentLocation); + + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + this.ParseSpatialFacets(out srid, CsdlConstants.Default_SpatialGeographySrid); + return new CsdlSpatialTypeReference(kind, srid, typeName, isNullable, parentLocation); + + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + this.ParseSpatialFacets(out srid, CsdlConstants.Default_SpatialGeometrySrid); + return new CsdlSpatialTypeReference(kind, srid, typeName, isNullable, parentLocation); + + case EdmPrimitiveTypeKind.None: + if (string.Equals(typeName, CsdlConstants.TypeName_Untyped, StringComparison.Ordinal)) + { + return new CsdlUntypedTypeReference(typeName, parentLocation); + } + + break; + } + + this.ParseTypeDefinitionFacets(out isUnbounded, out maxLength, out unicode, out precision, out scale, out srid); + return new CsdlNamedTypeReference(isUnbounded, maxLength, unicode, precision, scale, srid, typeName, isNullable, parentLocation); + } + + private CsdlTypeReference ParseTypeReference(string typeString, XmlElementValueCollection childValues, CsdlLocation parentLocation, Optionality typeInfoOptionality) + { + bool isNullable = OptionalBoolean(CsdlConstants.Attribute_Nullable) ?? CsdlConstants.Default_Nullable; + + CsdlTypeReference elementType = null; + if (typeString != null) + { + string[] typeInformation = typeString.Split(new char[] { '(', ')' }); + string typeName = typeInformation[0]; + switch (typeName) + { + case CsdlConstants.Value_Collection: + { + string elementTypeName = typeInformation.Count() > 1 ? typeInformation[1] : typeString; + elementType = new CsdlExpressionTypeReference( + new CsdlCollectionType( + this.ParseNamedTypeReference(elementTypeName, isNullable, parentLocation), parentLocation), isNullable, parentLocation); + } + + break; + case CsdlConstants.Value_Ref: + { + string elementTypeName = typeInformation.Count() > 1 ? typeInformation[1] : typeString; + elementType = new CsdlExpressionTypeReference( + new CsdlEntityReferenceType( + this.ParseNamedTypeReference(elementTypeName, isNullable, parentLocation), parentLocation), CsdlConstants.Default_Nullable, parentLocation); + } + + break; + default: + elementType = this.ParseNamedTypeReference(typeName, isNullable, parentLocation); + break; + } + } + else if (childValues != null) + { + elementType = childValues.ValuesOfType().FirstOrDefault(); + } + + if (elementType == null && typeInfoOptionality == Optionality.Required) + { + if (childValues != null) + { + // If childValues is null, then it is the case when a required type attribute was expected. + // In this case, we do not report the error as it should already be reported by EdmXmlDocumentParser.RequiredType method. + this.ReportError(parentLocation, EdmErrorCode.MissingType, Edm.Strings.CsdlParser_MissingTypeAttributeOrElement); + } + else + { + Debug.Assert(this.Errors.Count() > 0, "There should be an error reported for the missing required type attribute."); + } + elementType = new CsdlNamedTypeReference(String.Empty, isNullable, parentLocation); + } + + return elementType; + } + + private CsdlNamedElement OnNavigationPropertyElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + + string typeName = RequiredType(CsdlConstants.Attribute_Type); + bool? isNullable = OptionalBoolean(CsdlConstants.Attribute_Nullable); + string partner = Optional(CsdlConstants.Attribute_Partner); + + bool? containsTarget = OptionalBoolean(CsdlConstants.Attribute_ContainsTarget); + CsdlOnDelete onDelete = childValues.ValuesOfType().FirstOrDefault(); + IEnumerable referentialConstraints = childValues.ValuesOfType().ToList(); + + return new CsdlNavigationProperty(name, typeName, isNullable, partner, containsTarget ?? false, onDelete, referentialConstraints, element.Location); + } + + private static CsdlKey OnEntityKeyElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + return new CsdlKey(new List(childValues.ValuesOfType()), element.Location); + } + + private CsdlPropertyReference OnPropertyRefElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + return new CsdlPropertyReference(Required(CsdlConstants.Attribute_Name), element.Location); + } + + private CsdlEnumType OnEnumTypeElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string underlyingType = OptionalType(CsdlConstants.Attribute_UnderlyingType); + bool? isFlags = OptionalBoolean(CsdlConstants.Attribute_IsFlags); + + return new CsdlEnumType(name, underlyingType, isFlags ?? false, childValues.ValuesOfType(), element.Location); + } + + private CsdlEnumMember OnEnumMemberElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + long? value = OptionalLong(CsdlConstants.Attribute_Value); + + return new CsdlEnumMember(name, value, element.Location); + } + + private CsdlOnDelete OnDeleteActionElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + EdmOnDeleteAction action = RequiredOnDeleteAction(CsdlConstants.Attribute_Action); + + return new CsdlOnDelete(action, element.Location); + } + + private CsdlReferentialConstraint OnReferentialConstraintElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string propertyName = this.Required(CsdlConstants.Attribute_Property); + string referencedPropertyName = this.Required(CsdlConstants.Attribute_ReferencedProperty); + + return new CsdlReferentialConstraint(propertyName, referencedPropertyName, element.Location); + } + + internal CsdlAction OnActionElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + bool isBound = OptionalBoolean(CsdlConstants.Attribute_IsBound) ?? CsdlConstants.Default_IsBound; + string entitySetPath = Optional(CsdlConstants.Attribute_EntitySetPath); + + IEnumerable parameters = childValues.ValuesOfType(); + + CsdlOperationReturn returnElement = childValues.ValuesOfType().FirstOrDefault(); + + this.ReportOperationReadErrorsIfExist(entitySetPath, isBound, name); + + return new CsdlAction(name, parameters, returnElement, isBound, entitySetPath, element.Location); + } + + internal CsdlFunction OnFunctionElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + bool isBound = OptionalBoolean(CsdlConstants.Attribute_IsBound) ?? CsdlConstants.Default_IsBound; + string entitySetPath = Optional(CsdlConstants.Attribute_EntitySetPath); + bool isComposable = OptionalBoolean(CsdlConstants.Attribute_IsComposable) ?? CsdlConstants.Default_IsComposable; + + IEnumerable parameters = childValues.ValuesOfType(); + + CsdlOperationReturn returnElement = childValues.ValuesOfType().FirstOrDefault(); + + this.ReportOperationReadErrorsIfExist(entitySetPath, isBound, name); + + return new CsdlFunction(name, parameters, returnElement, isBound, entitySetPath, isComposable, element.Location); + } + + private void ReportOperationReadErrorsIfExist(string entitySetPath, bool isBound, string name) + { + if (entitySetPath != null && !isBound) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.InvalidEntitySetPath, Edm.Strings.CsdlParser_InvalidEntitySetPathWithUnboundAction(CsdlConstants.Element_Action, name)); + } + } + + private CsdlOperationParameter OnParameterElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string typeName = OptionalType(CsdlConstants.Attribute_Type); + string defaultValue = null; + bool isOptional = false; + + CsdlTypeReference type = this.ParseTypeReference(typeName, childValues, element.Location, Optionality.Required); + + // TODO (Issue #855): handle out-of-line annotations + XmlElementValue optionalAnnotationValue = childValues.Where(c => + c is XmlElementValue && + (c.ValueAs().Term == CoreVocabularyModel.OptionalParameterTerm.ShortQualifiedName() || + c.ValueAs().Term == CoreVocabularyModel.OptionalParameterTerm.FullName()) + ).FirstOrDefault(); + + if (optionalAnnotationValue != null) + { + isOptional = true; + CsdlRecordExpression optionalValueExpression = optionalAnnotationValue.ValueAs().Expression as CsdlRecordExpression; + if (optionalValueExpression != null) + { + foreach (CsdlPropertyValue property in optionalValueExpression.PropertyValues) + { + CsdlConstantExpression propertyValue = property.Expression as CsdlConstantExpression; + if (propertyValue != null) + { + if (property.Property == CsdlConstants.Attribute_DefaultValue) + { + defaultValue = propertyValue.Value; + } + } + } + } + + childValues.Remove(optionalAnnotationValue); + } + + return new CsdlOperationParameter(name, type, element.Location, isOptional, defaultValue); + } + + private CsdlActionImport OnActionImportElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string qualifiedActionName = RequiredQualifiedName(CsdlConstants.Attribute_Action); + string entitySet = Optional(CsdlConstants.Attribute_EntitySet); + + return new CsdlActionImport(name, qualifiedActionName, entitySet, element.Location); + } + + private CsdlFunctionImport OnFunctionImportElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string qualifiedActionName = RequiredQualifiedName(CsdlConstants.Attribute_Function); + string entitySet = Optional(CsdlConstants.Attribute_EntitySet); + bool includeInServiceDocument = OptionalBoolean(CsdlConstants.Attribute_IncludeInServiceDocument) ?? CsdlConstants.Default_IncludeInServiceDocument; + + return new CsdlFunctionImport(name, qualifiedActionName, entitySet, includeInServiceDocument, element.Location); + } + + private CsdlOperationParameter OnFunctionImportParameterElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string typeName = RequiredType(CsdlConstants.Attribute_Type); + CsdlTypeReference type = this.ParseTypeReference(typeName, null, element.Location, Optionality.Required); + return new CsdlOperationParameter(name, type, element.Location); + } + + private CsdlTypeReference OnEntityReferenceTypeElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = RequiredType(CsdlConstants.Attribute_Type); + return new CsdlExpressionTypeReference(new CsdlEntityReferenceType(this.ParseTypeReference(typeName, null, element.Location, Optionality.Required), element.Location), CsdlConstants.Default_Nullable, element.Location); + } + + private CsdlTypeReference OnTypeRefElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = RequiredType(CsdlConstants.Attribute_Type); + return this.ParseTypeReference(typeName, null, element.Location, Optionality.Required); + } + + private CsdlTypeReference OnCollectionTypeElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string elementTypeName = OptionalType(CsdlConstants.Attribute_ElementType); + CsdlTypeReference elementType = this.ParseTypeReference(elementTypeName, childValues, element.Location, Optionality.Required); + + return new CsdlExpressionTypeReference(new CsdlCollectionType(elementType, element.Location), elementType.IsNullable, element.Location); + } + + private CsdlOperationReturn OnReturnTypeElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string typeName = RequiredType(CsdlConstants.Attribute_Type); + CsdlTypeReference type = this.ParseTypeReference(typeName, childValues, element.Location, Optionality.Required); + return new CsdlOperationReturn(type, element.Location); + } + + private CsdlEntityContainer OnEntityContainerElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string extends = Optional(CsdlConstants.Attribute_Extends); + + if (entityContainerCount++ > 0) + { + this.ReportError(this.currentElement.Location, EdmErrorCode.MetadataDocumentCannotHaveMoreThanOneEntityContainer, Edm.Strings.CsdlParser_MetadataDocumentCannotHaveMoreThanOneEntityContainer); + } + + return new CsdlEntityContainer( + name, + extends, + childValues.ValuesOfType(), + childValues.ValuesOfType(), + childValues.ValuesOfType(), + element.Location); + } + + private CsdlEntitySet OnEntitySetElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string entityType = RequiredQualifiedName(CsdlConstants.Attribute_EntityType); + bool? includeInServiceDocument = OptionalBoolean(CsdlConstants.Attribute_IncludeInServiceDocument); + + if (includeInServiceDocument == null) + { + return new CsdlEntitySet(name, entityType, childValues.ValuesOfType(), element.Location); + } + else + { + return new CsdlEntitySet(name, entityType, childValues.ValuesOfType(), element.Location, (bool)includeInServiceDocument); + } + } + + private CsdlSingleton OnSingletonElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string name = Required(CsdlConstants.Attribute_Name); + string type = RequiredQualifiedName(CsdlConstants.Attribute_Type); + + return new CsdlSingleton(name, type, childValues.ValuesOfType(), element.Location); + } + + private CsdlNavigationPropertyBinding OnNavigationPropertyBindingElement(XmlElementInfo element, XmlElementValueCollection childValues) + { + string path = Required(CsdlConstants.Attribute_Path); + string entitySet = Required(CsdlConstants.Attribute_Target); + + return new CsdlNavigationPropertyBinding(path, entitySet, element.Location); + } + + private void ParseMaxLength(out bool Unbounded, out int? maxLength) + { + string max = Optional(CsdlConstants.Attribute_MaxLength); + if (max == null) + { + Unbounded = false; + maxLength = null; + } + else if (max.EqualsOrdinalIgnoreCase(CsdlConstants.Value_Max)) + { + Unbounded = true; + maxLength = null; + } + else + { + Unbounded = false; + maxLength = OptionalMaxLength(CsdlConstants.Attribute_MaxLength); + } + } + + private void ParseBinaryFacets(out bool Unbounded, out int? maxLength) + { + this.ParseMaxLength(out Unbounded, out maxLength); + } + + private void ParseDecimalFacets(out int? precision, out int? scale) + { + precision = OptionalInteger(CsdlConstants.Attribute_Precision); + scale = OptionalScale(CsdlConstants.Attribute_Scale); + } + + private void ParseStringFacets(out bool Unbounded, out int? maxLength, out bool? unicode) + { + this.ParseMaxLength(out Unbounded, out maxLength); + unicode = OptionalBoolean(CsdlConstants.Attribute_Unicode) ?? CsdlConstants.Default_IsUnicode; + } + + private void ParseTemporalFacets(out int? precision) + { + precision = OptionalInteger(CsdlConstants.Attribute_Precision) ?? CsdlConstants.Default_TemporalPrecision; + } + + private void ParseSpatialFacets(out int? srid, int defaultSrid) + { + srid = OptionalSrid(CsdlConstants.Attribute_Srid, defaultSrid); + } + + private void ParseTypeDefinitionFacets( + out bool isUnbounded, + out int? maxLength, + out bool? unicode, + out int? precision, + out int? scale, + out int? srid) + { + this.ParseMaxLength(out isUnbounded, out maxLength); + unicode = OptionalBoolean(CsdlConstants.Attribute_Unicode); + precision = OptionalInteger(CsdlConstants.Attribute_Precision); + scale = OptionalScale(CsdlConstants.Attribute_Scale); + srid = OptionalSrid(CsdlConstants.Attribute_Srid, CsdlConstants.Default_UnspecifiedSrid); + } + + private enum Optionality + { + Optional, + Required + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlParser.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlParser.cs new file mode 100644 index 0000000..f11c86f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlParser.cs @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Xml; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.Parsing +{ + /// + /// Provides for the loading and conversion of one or more CSDL XML readers into Entity Data Model. + /// + internal class CsdlParser + { + private readonly List errorsList = new List(); + private readonly CsdlModel result = new CsdlModel(); + + private bool success = true; + + public static bool TryParse(IEnumerable csdlReaders, out CsdlModel entityModel, out IEnumerable errors) + { + EdmUtil.CheckArgumentNull(csdlReaders, "csdlReaders"); + CsdlParser parser = new CsdlParser(); + int readerCount = 0; + foreach (var inputReader in csdlReaders) + { + if (inputReader != null) + { + try + { + parser.AddReader(inputReader); + } + catch (XmlException e) + { + entityModel = null; + errors = new EdmError[] { new EdmError(new CsdlLocation(e.LineNumber, e.LinePosition), EdmErrorCode.XmlError, e.Message) }; + + return false; + } + } + else + { + entityModel = null; + errors = new EdmError[] { new EdmError(null, EdmErrorCode.NullXmlReader, Edm.Strings.CsdlParser_NullXmlReader) }; + + return false; + } + + readerCount++; + } + + if (readerCount == 0) + { + entityModel = null; + errors = new EdmError[] { new EdmError(null, EdmErrorCode.NoReadersProvided, Edm.Strings.CsdlParser_NoReadersProvided) }; + + return false; + } + + bool success = parser.GetResult(out entityModel, out errors); + if (!success) + { + entityModel = null; + } + + return success; + } + + public bool AddReader(XmlReader csdlReader, string source = null) + { + // If source is determined (empty means no source), use source; + // otherwise try to use BaseURI from XmlReader. + string artifactPath = source ?? csdlReader.BaseURI; + + // Create a new CsdlDocumentParser to parse a single CSDL document. + CsdlDocumentParser docParser = new CsdlDocumentParser(artifactPath, csdlReader); + + // Initialize the parser and continue if a valid root element was found + docParser.ParseDocumentElement(); + + // Do not move on to other readers if errors were encountered + this.success &= !docParser.HasErrors; + + // Gather any errors that occurred, regardless of success + this.errorsList.AddRange(docParser.Errors); + + if (docParser.Result != null) + { + this.result.AddSchema(docParser.Result.Value); + } + + return this.success; + } + + public bool GetResult(out CsdlModel model, out IEnumerable errors) + { + model = this.result; + errors = this.errorsList; + return this.success; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaReader.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaReader.cs new file mode 100644 index 0000000..8378147 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaReader.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Csdl.CsdlSemantics; +using Microsoft.OData.Edm.Csdl.Parsing; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Provides Schema parsing services for EDM models. + /// + public static class SchemaReader + { + /// + /// Returns an IEdmModel for the given Schema artifacts. + /// + /// Collection of XmlReaders containing the Schema artifacts. + /// The model generated by parsing. + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(IEnumerable readers, out IEdmModel model, out IEnumerable errors) + { + return TryParse(readers, Enumerable.Empty(), out model, out errors); + } + + /// + /// Returns an IEdmModel for the given Schema artifacts. + /// + /// Collection of XmlReaders containing the Schema artifacts. + /// Model to be references by the created model. + /// The model generated by parsing. + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(IEnumerable readers, IEdmModel reference, out IEdmModel model, out IEnumerable errors) + { + return TryParse(readers, new IEdmModel[] { reference }, out model, out errors); + } + + /// + /// Returns an IEdmModel for the given Schema artifacts. + /// + /// Collection of XmlReaders containing the Schema artifacts. + /// Models to be references by the created model. + /// The model generated by parsing. + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(IEnumerable readers, IEnumerable references, out IEdmModel model, out IEnumerable errors) + { + return TryParse(readers, references, true /*includeDefaultVocabularies*/, out model, out errors); + } + + /// + /// Returns an IEdmModel for the given Schema artifacts. + /// + /// Collection of XmlReaders containing the Schema artifacts. + /// Models to be references by the created model. + /// A value indicating enable/disable the built-in vocabulary supporting. + /// The model generated by parsing. + /// Errors reported while parsing. + /// Success of the parse operation. + public static bool TryParse(IEnumerable readers, IEnumerable references, bool includeDefaultVocabularies, out IEdmModel model, out IEnumerable errors) + { + CsdlModel ast; + if (CsdlParser.TryParse(readers, out ast, out errors)) + { + CsdlSemanticsModel tmp = new CsdlSemanticsModel(ast, new CsdlSemanticsDirectValueAnnotationsManager(), references, includeDefaultVocabularies); + model = tmp; + return true; + } + + model = null; + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaWriter.cs new file mode 100644 index 0000000..83e1814 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SchemaWriter.cs @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Csdl.Serialization; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Provides Schema serialization services for EDM models. + /// + public static class SchemaWriter + { + /// + /// Outputs a Schema artifact to the provided writer. + /// + /// Model to be written. + /// XmlWriter the generated Schema will be written to. + /// Errors that prevented successful serialization, or no errors if serialization was successful. + /// A value indicating whether serialization was successful. + public static bool TryWriteSchema(this IEdmModel model, XmlWriter writer, out IEnumerable errors) + { + return TryWriteSchema(model, x => writer, true, out errors); + } + + /// + /// Outputs Schema artifacts to the provided writers. + /// + /// Model to be written. + /// A delegate that takes in a Schema namespace name and returns an XmlWriter to write the Schema to. + /// Errors that prevented successful serialization, or no errors if serialization was successful. + /// A value indicating whether serialization was successful. + public static bool TryWriteSchema(this IEdmModel model, Func writerProvider, out IEnumerable errors) + { + return TryWriteSchema(model, writerProvider, false, out errors); + } + + internal static bool TryWriteSchema(IEdmModel model, Func writerProvider, bool singleFileExpected, out IEnumerable errors) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(writerProvider, "writerProvider"); + + errors = model.GetSerializationErrors(); + if (errors.FirstOrDefault() != null) + { + return false; + } + + IEnumerable schemas = new EdmModelSchemaSeparationSerializationVisitor(model).GetSchemas(); + if (schemas.Count() > 1 && singleFileExpected) + { + errors = new EdmError[] { new EdmError(new CsdlLocation(0, 0), EdmErrorCode.SingleFileExpected, Edm.Strings.Serializer_SingleFileExpected) }; + return false; + } + + if (schemas.Count() == 0) + { + errors = new EdmError[] { new EdmError(new CsdlLocation(0, 0), EdmErrorCode.NoSchemasProduced, Edm.Strings.Serializer_NoSchemasProduced) }; + return false; + } + + WriteSchemas(model, schemas, writerProvider); + + errors = Enumerable.Empty(); + return true; + } + + internal static void WriteSchemas(IEdmModel model, IEnumerable schemas, Func writerProvider) + { + EdmModelCsdlSerializationVisitor visitor; + Version edmVersion = model.GetEdmVersion() ?? EdmConstants.EdmVersionDefault; + foreach (EdmSchema schema in schemas) + { + XmlWriter writer = writerProvider(schema.Namespace); + if (writer != null) + { + visitor = new EdmModelCsdlSerializationVisitor(model, writer, edmVersion); + visitor.VisitEdmSchema(schema, model.GetNamespacePrefixMappings()); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/IUnresolvedElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/IUnresolvedElement.cs new file mode 100644 index 0000000..e294993 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/IUnresolvedElement.cs @@ -0,0 +1,12 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal interface IUnresolvedElement + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedAction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedAction.cs new file mode 100644 index 0000000..2542715 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedAction.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Represents information about an EDM action that failed to resolve. + /// + internal class UnresolvedAction : UnresolvedOperation, IEdmAction + { + public UnresolvedAction(string qualifiedName, string errorMessage, EdmLocation location) + : base(qualifiedName, errorMessage, location) + { + } + + public new EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Action; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedComplexType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedComplexType.cs new file mode 100644 index 0000000..0cd00de --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedComplexType.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedComplexType : BadComplexType, IUnresolvedElement + { + public UnresolvedComplexType(string qualifiedName, EdmLocation location) + : base(qualifiedName, new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedComplexType, Edm.Strings.Bad_UnresolvedComplexType(qualifiedName)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityContainer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityContainer.cs new file mode 100644 index 0000000..df6b4b9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityContainer.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedEntityContainer : BadEntityContainer, IUnresolvedElement + { + public UnresolvedEntityContainer(string name, EdmLocation location) + : base(name, new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedEntityContainer, Edm.Strings.Bad_UnresolvedEntityContainer(name)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntitySet.cs new file mode 100644 index 0000000..0c58646 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntitySet.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedEntitySet : BadEntitySet, IUnresolvedElement + { + public UnresolvedEntitySet(string name, IEdmEntityContainer container, EdmLocation location) + : base(name, container, new[] { new EdmError(location, EdmErrorCode.BadUnresolvedEntitySet, Edm.Strings.Bad_UnresolvedEntitySet(name)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityType.cs new file mode 100644 index 0000000..688559f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEntityType.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedEntityType : BadEntityType, IUnresolvedElement + { + public UnresolvedEntityType(string qualifiedName, EdmLocation location) + : base(qualifiedName, new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedEntityType, Edm.Strings.Bad_UnresolvedEntityType(qualifiedName)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumMember.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumMember.cs new file mode 100644 index 0000000..589d9aa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumMember.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedEnumMember : BadElement, IEdmEnumMember + { + private readonly string name; + private readonly IEdmEnumType declaringType; + + // Value cache. + private readonly Cache value = new Cache(); + private static readonly Func ComputeValueFunc = (me) => ComputeValue(); + + public UnresolvedEnumMember(string name, IEdmEnumType declaringType, EdmLocation location) + : base(new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedEnumMember, Edm.Strings.Bad_UnresolvedEnumMember(name)) }) + { + this.name = name ?? string.Empty; + this.declaringType = declaringType; + } + + public string Name + { + get { return this.name; } + } + + public IEdmEnumMemberValue Value + { + get { return this.value.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmEnumType DeclaringType + { + get { return this.declaringType; } + } + + private static IEdmEnumMemberValue ComputeValue() + { + return new EdmEnumMemberValue(0); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumType.cs new file mode 100644 index 0000000..d8fc8f4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedEnumType.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedEnumType : BadEnumType, IUnresolvedElement + { + public UnresolvedEnumType(string qualifiedName, EdmLocation location) + : base(qualifiedName, new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedEnumType, Edm.Strings.Bad_UnresolvedEnumType(qualifiedName)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedFunction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedFunction.cs new file mode 100644 index 0000000..7f32947 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedFunction.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Represents information about an EDM function that failed to resolve. + /// + internal class UnresolvedFunction : UnresolvedOperation, IEdmFunction + { + public UnresolvedFunction(string qualifiedName, string errorMessage, EdmLocation location) + : base(qualifiedName, errorMessage, location) + { + } + + public bool IsComposable + { + get { return false; } + } + + public new EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Function; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedLabeledElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedLabeledElement.cs new file mode 100644 index 0000000..ba3cc65 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedLabeledElement.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedLabeledElement : BadLabeledExpression, IUnresolvedElement + { + public UnresolvedLabeledElement(string label, EdmLocation location) + : base(label, new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedLabeledElement, Edm.Strings.Bad_UnresolvedLabeledElement(label)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedNavigationPropertyPath.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedNavigationPropertyPath.cs new file mode 100644 index 0000000..66b32a9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedNavigationPropertyPath.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + using Microsoft.OData.Edm.Validation; + + /// + /// Represents a navigation property path that could not be resolved. + /// + internal class UnresolvedNavigationPropertyPath : BadNavigationProperty, IUnresolvedElement + { + public UnresolvedNavigationPropertyPath(IEdmStructuredType startingType, string path, EdmLocation location) + : base(startingType, path, new[] { new EdmError(location, EdmErrorCode.BadUnresolvedNavigationPropertyPath, Edm.Strings.Bad_UnresolvedNavigationPropertyPath(path, startingType.FullTypeName())) }) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedOperation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedOperation.cs new file mode 100644 index 0000000..2f6d90f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedOperation.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Represents information about an EDM operation that failed to resolve. + /// + internal class UnresolvedOperation : BadElement, IEdmOperation, IUnresolvedElement, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + private readonly IEdmTypeReference returnType; + + public UnresolvedOperation(string qualifiedName, string errorMessage, EdmLocation location) + : base(new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedOperation, errorMessage) }) + { + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + this.returnType = new BadTypeReference(new BadType(this.Errors), true); + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public IEdmTypeReference ReturnType + { + get { return this.returnType; } + } + + public IEnumerable Parameters + { + get { return Enumerable.Empty(); } + } + + public bool IsBound + { + get { return false; } + } + + public IEdmPathExpression EntitySetPath + { + get { return null; } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.None; } + } + + public IEdmOperationParameter FindParameter(string name) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedParameter.cs new file mode 100644 index 0000000..059515d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedParameter.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedParameter : BadElement, IEdmOperationParameter, IUnresolvedElement + { + // Type cache. + private readonly Cache type = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public UnresolvedParameter(IEdmOperation declaringOperation, string name, EdmLocation location) + : base(new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedParameter, Edm.Strings.Bad_UnresolvedParameter(name)) }) + { + this.Name = name ?? string.Empty; + this.DeclaringOperation = declaringOperation; + } + + public string Name { get; private set; } + + public IEdmTypeReference Type + { + get { return this.type.GetValue(this, ComputeTypeFunc, null); } + } + + public IEdmOperation DeclaringOperation { get; private set; } + + private IEdmTypeReference ComputeType() + { + return new BadTypeReference(new BadType(Errors), true); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPrimitiveType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPrimitiveType.cs new file mode 100644 index 0000000..4fb56a2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPrimitiveType.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedPrimitiveType : BadPrimitiveType, IUnresolvedElement + { + public UnresolvedPrimitiveType(string qualifiedName, EdmLocation location) + : base(qualifiedName, EdmPrimitiveTypeKind.None, new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedPrimitiveType, Edm.Strings.Bad_UnresolvedPrimitiveType(qualifiedName)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedProperty.cs new file mode 100644 index 0000000..eb942f0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedProperty.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedProperty : BadProperty, IUnresolvedElement + { + public UnresolvedProperty(IEdmStructuredType declaringType, string name, EdmLocation location) + : base(declaringType, name, new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedProperty, Edm.Strings.Bad_UnresolvedProperty(name)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedReturn.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedReturn.cs new file mode 100644 index 0000000..0d4f4b0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedReturn.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedReturn : BadElement, IEdmOperationReturn, IUnresolvedElement + { + // Type cache. + private readonly Cache type = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public UnresolvedReturn(IEdmOperation declaringOperation, EdmLocation location) + : base(new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedReturn, Edm.Strings.Bad_UnresolvedReturn(declaringOperation.Name)) }) + { + this.DeclaringOperation = declaringOperation; + } + + public IEdmTypeReference Type + { + get { return this.type.GetValue(this, ComputeTypeFunc, null); } + } + + public IEdmOperation DeclaringOperation { get; private set; } + + private IEdmTypeReference ComputeType() + { + return new BadTypeReference(new BadType(Errors), true); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedType.cs new file mode 100644 index 0000000..eba0199 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedType.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Represents information about an EDM type definition that failed to resolve. + /// + internal class UnresolvedType : BadType, IEdmSchemaType, IUnresolvedElement, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + public UnresolvedType(string qualifiedName, EdmLocation location) + : base(new EdmError[] { new EdmError(location, EdmErrorCode.BadUnresolvedType, Edm.Strings.Bad_UnresolvedType(qualifiedName)) }) + { + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedVocabularyTerm.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedVocabularyTerm.cs new file mode 100644 index 0000000..5606069 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedVocabularyTerm.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedVocabularyTerm : EdmElement, IEdmTerm, IUnresolvedElement, IEdmFullNamedElement + { + private readonly UnresolvedTermTypeReference type = new UnresolvedTermTypeReference(); + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + private readonly string appliesTo = null; + private readonly string defaultValue = null; + + public UnresolvedVocabularyTerm(string qualifiedName) + { + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Term; } + } + + public IEdmTypeReference Type + { + get { return this.type; } + } + + public string AppliesTo + { + get { return this.appliesTo; } + } + + public string DefaultValue + { + get { return this.defaultValue; } + } + + private class UnresolvedTermTypeReference : IEdmTypeReference + { + private readonly UnresolvedTermType definition = new UnresolvedTermType(); + + public bool IsNullable + { + get { return false; } + } + + public IEdmType Definition + { + get { return this.definition; } + } + + private class UnresolvedTermType : IEdmType + { + public EdmTypeKind TypeKind + { + get { return EdmTypeKind.None; } + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAction.cs new file mode 100644 index 0000000..b0e6570 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAction.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlAction + /// + internal class CsdlSemanticsAction : CsdlSemanticsOperation, IEdmAction + { + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The action. + public CsdlSemanticsAction(CsdlSemanticsSchema context, CsdlAction action) + : base(context, action) + { + } + + public override EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Action; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsActionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsActionImport.cs new file mode 100644 index 0000000..8a375db --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsActionImport.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsActionImport : CsdlSemanticsOperationImport, IEdmActionImport + { + public CsdlSemanticsActionImport(CsdlSemanticsEntityContainer container, CsdlActionImport actionImport, IEdmAction backingAction) + : base(container, actionImport, backingAction) + { + } + + public IEdmAction Action + { + get { return (IEdmAction)this.Operation; } + } + + public override EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.ActionImport; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotationPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotationPathExpression.cs new file mode 100644 index 0000000..32401f1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotationPathExpression.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Annotation Path expression. + /// + internal class CsdlSemanticsAnnotationPathExpression : CsdlSemanticsPathExpression + { + public CsdlSemanticsAnnotationPathExpression(CsdlPathExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) : base(expression, bindingContext, schema) + { + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.AnnotationPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotations.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotations.cs new file mode 100644 index 0000000..4c64dfa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsAnnotations.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for an out-of-line CSDL Annotations. + /// + internal class CsdlSemanticsAnnotations + { + private readonly CsdlAnnotations annotations; + private readonly CsdlSemanticsSchema context; + + public CsdlSemanticsAnnotations(CsdlSemanticsSchema context, CsdlAnnotations annotations) + { + this.context = context; + this.annotations = annotations; + } + + public CsdlSemanticsSchema Context + { + get { return this.context; } + } + + public CsdlAnnotations Annotations + { + get { return this.annotations; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsApplyExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsApplyExpression.cs new file mode 100644 index 0000000..7303c62 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsApplyExpression.cs @@ -0,0 +1,161 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsApplyExpression : CsdlSemanticsExpression, IEdmApplyExpression, IEdmCheckable + { + private readonly CsdlApplyExpression expression; + private readonly CsdlSemanticsSchema schema; + private readonly IEdmEntityType bindingContext; + + private readonly Cache appliedFunctionCache = new Cache(); + private static readonly Func ComputeAppliedFunctionFunc = (me) => me.ComputeAppliedFunction(); + + private readonly Cache> argumentsCache = new Cache>(); + private static readonly Func> ComputeArgumentsFunc = (me) => me.ComputeArguments(); + + public CsdlSemanticsApplyExpression(CsdlApplyExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + this.schema = schema; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.FunctionApplication; } + } + + public IEdmFunction AppliedFunction + { + get { return this.appliedFunctionCache.GetValue(this, ComputeAppliedFunctionFunc, null); } + } + + public IEnumerable Arguments + { + get { return this.argumentsCache.GetValue(this, ComputeArgumentsFunc, null); } + } + + public IEnumerable Errors + { + get + { + if (this.AppliedFunction is IUnresolvedElement) + { + return this.AppliedFunction.Errors(); + } + + return Enumerable.Empty(); + } + } + + private IEdmFunction ComputeAppliedFunction() + { + if (this.expression.Function == null) + { + return null; + } + + IEnumerable candidateFunctions = this.schema.FindOperations(this.expression.Function).OfType(); + int candidateCount = candidateFunctions.Count(); + if (candidateCount == 0) + { + return new UnresolvedFunction(this.expression.Function, Edm.Strings.Bad_UnresolvedOperation(this.expression.Function), this.Location); + } + + candidateFunctions = candidateFunctions.Where(this.IsMatchingFunction); + candidateCount = candidateFunctions.Count(); + if (candidateCount > 1) + { + candidateFunctions = candidateFunctions.Where(this.IsExactMatch); + candidateCount = candidateFunctions.Count(); + if (candidateCount != 1) + { + return new UnresolvedFunction(this.expression.Function, Edm.Strings.Bad_AmbiguousOperation(this.expression.Function), this.Location); + } + + return candidateFunctions.Single(); + } + + if (candidateCount == 0) + { + return new UnresolvedFunction(this.expression.Function, Edm.Strings.Bad_OperationParametersDontMatch(this.expression.Function), this.Location); + } + + return candidateFunctions.Single(); + } + + private IEnumerable ComputeArguments() + { + bool skipFirst = this.expression.Function == null; + List result = new List(); + foreach (CsdlExpressionBase argument in this.expression.Arguments) + { + if (skipFirst) + { + skipFirst = false; + } + else + { + result.Add(CsdlSemanticsModel.WrapExpression(argument, this.bindingContext, this.schema)); + } + } + + return result; + } + + private bool IsMatchingFunction(IEdmOperation operation) + { + if (operation.Parameters.Count() != this.Arguments.Count()) + { + return false; + } + + IEnumerator parameterExpressionEnumerator = this.Arguments.GetEnumerator(); + foreach (IEdmOperationParameter parameter in operation.Parameters) + { + parameterExpressionEnumerator.MoveNext(); + IEnumerable recursiveErrors; + if (!parameterExpressionEnumerator.Current.TryCast(parameter.Type, this.bindingContext, false, out recursiveErrors)) + { + return false; + } + } + + return true; + } + + private bool IsExactMatch(IEdmOperation operation) + { + IEnumerator parameterExpressionEnumerator = this.Arguments.GetEnumerator(); + foreach (IEdmOperationParameter parameter in operation.Parameters) + { + parameterExpressionEnumerator.MoveNext(); + IEnumerable recursiveErrors; + if (!parameterExpressionEnumerator.Current.TryCast(parameter.Type, this.bindingContext, true, out recursiveErrors)) + { + return false; + } + } + + return true; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryConstantExpression.cs new file mode 100644 index 0000000..d416dba --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl binary constant expression. + /// + internal class CsdlSemanticsBinaryConstantExpression : CsdlSemanticsExpression, IEdmBinaryConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsBinaryConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public byte[] Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.BinaryConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private byte[] ComputeValue() + { + byte[] binary; + return EdmValueParser.TryParseBinary(this.expression.Value, out binary) ? binary : new byte[0]; + } + + private IEnumerable ComputeErrors() + { + byte[] value; + if (!EdmValueParser.TryParseBinary(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidBinary, Edm.Strings.ValueParser_InvalidBinary(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryTypeReference.cs new file mode 100644 index 0000000..3622eea --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBinaryTypeReference.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides the semantics of a reference to an EDM Binary type. + /// + internal class CsdlSemanticsBinaryTypeReference : CsdlSemanticsPrimitiveTypeReference, IEdmBinaryTypeReference + { + public CsdlSemanticsBinaryTypeReference(CsdlSemanticsSchema schema, CsdlBinaryTypeReference reference) + : base(schema, reference) + { + } + + public bool IsUnbounded + { + get { return ((CsdlBinaryTypeReference)this.Reference).IsUnbounded; } + } + + public int? MaxLength + { + get { return ((CsdlBinaryTypeReference)this.Reference).MaxLength; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBooleanConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBooleanConstantExpression.cs new file mode 100644 index 0000000..a2e51fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsBooleanConstantExpression.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Bool constant expression. + /// + internal class CsdlSemanticsBooleanConstantExpression : CsdlSemanticsExpression, IEdmBooleanConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsBooleanConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public bool Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.BooleanConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private bool ComputeValue() + { + bool? local; + return EdmValueParser.TryParseBool(this.expression.Value, out local) ? local.Value : false; + } + + private IEnumerable ComputeErrors() + { + bool? value; + if (!EdmValueParser.TryParseBool(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidBoolean, Edm.Strings.ValueParser_InvalidBoolean(this.expression.Value)) }; + } + + return Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCastExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCastExpression.cs new file mode 100644 index 0000000..21c4b5d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCastExpression.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsCastExpression : CsdlSemanticsExpression, IEdmCastExpression + { + private readonly CsdlCastExpression expression; + private readonly IEdmEntityType bindingContext; + + private readonly Cache operandCache = new Cache(); + private static readonly Func ComputeOperandFunc = (me) => me.ComputeOperand(); + + private readonly Cache typeCache = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public CsdlSemanticsCastExpression(CsdlCastExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Cast; } + } + + public IEdmExpression Operand + { + get { return this.operandCache.GetValue(this, ComputeOperandFunc, null); } + } + + public IEdmTypeReference Type + { + get { return this.typeCache.GetValue(this, ComputeTypeFunc, null); } + } + + private IEdmExpression ComputeOperand() + { + return CsdlSemanticsModel.WrapExpression(this.expression.Operand, this.bindingContext, this.Schema); + } + + private IEdmTypeReference ComputeType() + { + return CsdlSemanticsModel.WrapTypeReference(this.Schema, this.expression.Type); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionExpression.cs new file mode 100644 index 0000000..c531046 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionExpression.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl collection expression. + /// + internal class CsdlSemanticsCollectionExpression : CsdlSemanticsExpression, IEdmCollectionExpression + { + private readonly CsdlCollectionExpression expression; + private readonly IEdmEntityType bindingContext; + + private readonly Cache declaredTypeCache = new Cache(); + private static readonly Func ComputeDeclaredTypeFunc = (me) => me.ComputeDeclaredType(); + + private readonly Cache> elementsCache = new Cache>(); + private static readonly Func> ComputeElementsFunc = (me) => me.ComputeElements(); + + public CsdlSemanticsCollectionExpression(CsdlCollectionExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Collection; } + } + + public IEdmTypeReference DeclaredType + { + get { return this.declaredTypeCache.GetValue(this, ComputeDeclaredTypeFunc, null); } + } + + public IEnumerable Elements + { + get { return this.elementsCache.GetValue(this, ComputeElementsFunc, null); } + } + + private IEnumerable ComputeElements() + { + List elements = new List(); + + foreach (CsdlExpressionBase elementValue in this.expression.ElementValues) + { + elements.Add(CsdlSemanticsModel.WrapExpression(elementValue, this.bindingContext, this.Schema)); + } + + return elements; + } + + private IEdmTypeReference ComputeDeclaredType() + { + return this.expression.Type != null ? CsdlSemanticsModel.WrapTypeReference(this.Schema, this.expression.Type) : null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeDefinition.cs new file mode 100644 index 0000000..758c058 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeDefinition.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlCollectionType. + /// + internal class CsdlSemanticsCollectionTypeDefinition : CsdlSemanticsTypeDefinition, IEdmCollectionType + { + private readonly CsdlSemanticsSchema schema; + private readonly CsdlCollectionType collection; + + private readonly Cache elementTypeCache = new Cache(); + private static readonly Func ComputeElementTypeFunc = (me) => me.ComputeElementType(); + + public CsdlSemanticsCollectionTypeDefinition(CsdlSemanticsSchema schema, CsdlCollectionType collection) + : base(collection) + { + this.collection = collection; + this.schema = schema; + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Collection; } + } + + public IEdmTypeReference ElementType + { + get { return this.elementTypeCache.GetValue(this, ComputeElementTypeFunc, null); } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + + public override CsdlElement Element + { + get { return this.collection; } + } + + private IEdmTypeReference ComputeElementType() + { + return CsdlSemanticsModel.WrapTypeReference(this.schema, this.collection.ElementType); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeExpression.cs new file mode 100644 index 0000000..d58080e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsCollectionTypeExpression.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsCollectionTypeExpression : CsdlSemanticsTypeExpression, IEdmCollectionTypeReference + { + public CsdlSemanticsCollectionTypeExpression(CsdlExpressionTypeReference expressionUsage, CsdlSemanticsTypeDefinition type) + : base(expressionUsage, type) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsComplexTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsComplexTypeDefinition.cs new file mode 100644 index 0000000..118986a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsComplexTypeDefinition.cs @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlComplexType. + /// + internal class CsdlSemanticsComplexTypeDefinition : CsdlSemanticsStructuredTypeDefinition, IEdmComplexType, IEdmFullNamedElement + { + private readonly string fullName; + private readonly CsdlComplexType complex; + + private readonly Cache baseTypeCache = new Cache(); + private static readonly Func ComputeBaseTypeFunc = (me) => me.ComputeBaseType(); + private static readonly Func OnCycleBaseTypeFunc = (me) => new CyclicComplexType(me.GetCyclicBaseTypeName(me.complex.BaseTypeName), me.Location); + + public CsdlSemanticsComplexTypeDefinition(CsdlSemanticsSchema context, CsdlComplexType complex) + : base(context, complex) + { + this.complex = complex; + this.fullName = EdmUtil.GetFullNameForSchemaElement(context?.Namespace, this.complex?.Name); + } + + public override IEdmStructuredType BaseType + { + get { return this.baseTypeCache.GetValue(this, ComputeBaseTypeFunc, OnCycleBaseTypeFunc); } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Complex; } + } + + public override bool IsAbstract + { + get { return this.complex.IsAbstract; } + } + + public override bool IsOpen + { + get { return this.complex.IsOpen; } + } + + public string Name + { + get { return this.complex.Name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + protected override CsdlStructuredType MyStructured + { + get { return this.complex; } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "baseType2", + Justification = "Value assignment is required by compiler.")] + private IEdmComplexType ComputeBaseType() + { + if (this.complex.BaseTypeName != null) + { + IEdmComplexType baseType = this.Context.FindType(this.complex.BaseTypeName) as IEdmComplexType; + if (baseType != null) + { + // Evaluate the inductive step to detect cycles. + // Overriding BaseType getter from concrete type implementing IEdmComplexType will be invoked to + // detect cycles. The object assignment is required by compiler only. + IEdmStructuredType baseType2 = baseType.BaseType; + } + + return baseType ?? new UnresolvedComplexType(this.Context.UnresolvedName(this.complex.BaseTypeName), this.Location); + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateConstantExpression.cs new file mode 100644 index 0000000..89e0d78 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl date constant expression. + /// + internal class CsdlSemanticsDateConstantExpression : CsdlSemanticsExpression, IEdmDateConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsDateConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public Date Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DateConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private Date ComputeValue() + { + Date? value; + return EdmValueParser.TryParseDate(this.expression.Value, out value) ? value.Value : Date.MinValue; + } + + private IEnumerable ComputeErrors() + { + Date? value; + if (!EdmValueParser.TryParseDate(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidDate, Edm.Strings.ValueParser_InvalidDate(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateTimeOffsetConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateTimeOffsetConstantExpression.cs new file mode 100644 index 0000000..de23de8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDateTimeOffsetConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl datetime with offset constant expression. + /// + internal class CsdlSemanticsDateTimeOffsetConstantExpression : CsdlSemanticsExpression, IEdmDateTimeOffsetConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsDateTimeOffsetConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public DateTimeOffset Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DateTimeOffsetConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private DateTimeOffset ComputeValue() + { + DateTimeOffset? value; + return EdmValueParser.TryParseDateTimeOffset(this.expression.Value, out value) ? value.Value : DateTimeOffset.MinValue; + } + + private IEnumerable ComputeErrors() + { + DateTimeOffset? value; + if (!EdmValueParser.TryParseDateTimeOffset(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidDateTimeOffset, Edm.Strings.ValueParser_InvalidDateTimeOffset(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalConstantExpression.cs new file mode 100644 index 0000000..01276bb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Decimal constant expression. + /// + internal class CsdlSemanticsDecimalConstantExpression : CsdlSemanticsExpression, IEdmDecimalConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsDecimalConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public decimal Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DecimalConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private decimal ComputeValue() + { + decimal? value; + return EdmValueParser.TryParseDecimal(this.expression.Value, out value) ? value.Value : 0; + } + + private IEnumerable ComputeErrors() + { + decimal? value; + if (!EdmValueParser.TryParseDecimal(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidDecimal, Edm.Strings.ValueParser_InvalidDecimal(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalTypeReference.cs new file mode 100644 index 0000000..b2a773b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDecimalTypeReference.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides the semantics of a reference to an EDM Decimal type. + /// + internal class CsdlSemanticsDecimalTypeReference : CsdlSemanticsPrimitiveTypeReference, IEdmDecimalTypeReference + { + public CsdlSemanticsDecimalTypeReference(CsdlSemanticsSchema schema, CsdlDecimalTypeReference reference) + : base(schema, reference) + { + } + + public int? Precision + { + get { return ((CsdlDecimalTypeReference)this.Reference).Precision; } + } + + public int? Scale + { + get { return ((CsdlDecimalTypeReference)this.Reference).Scale; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotation.cs new file mode 100644 index 0000000..8fa3b82 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotation.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlDirectValueAnnotation. + /// + internal class CsdlSemanticsDirectValueAnnotation : CsdlSemanticsElement, IEdmDirectValueAnnotation + { + private readonly CsdlDirectValueAnnotation annotation; + private readonly CsdlSemanticsModel model; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + public CsdlSemanticsDirectValueAnnotation(CsdlDirectValueAnnotation annotation, CsdlSemanticsModel model) + : base(annotation) + { + this.annotation = annotation; + this.model = model; + } + + public override CsdlElement Element + { + get { return this.annotation; } + } + + public override CsdlSemanticsModel Model + { + get { return this.model; } + } + + public string NamespaceUri + { + get { return this.annotation.NamespaceName; } + } + + public string Name + { + get { return this.annotation.Name; } + } + + public object Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + private IEdmValue ComputeValue() + { + IEdmStringValue value = new EdmStringConstant(new EdmStringTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.String), false), this.annotation.Value); + value.SetIsSerializedAsElement(this.model, !this.annotation.IsAttribute); + return value; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotationsManager.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotationsManager.cs new file mode 100644 index 0000000..c83ecfa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDirectValueAnnotationsManager.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides a CSDL-specific annotations manager. + /// + internal class CsdlSemanticsDirectValueAnnotationsManager : EdmDirectValueAnnotationsManager + { + protected override IEnumerable GetAttachedAnnotations(IEdmElement element) + { + CsdlSemanticsElement csdlElement = element as CsdlSemanticsElement; + if (csdlElement != null) + { + return csdlElement.DirectValueAnnotations; + } + + return Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDurationConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDurationConstantExpression.cs new file mode 100644 index 0000000..2b1519f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsDurationConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl duration constant expression. + /// + internal class CsdlSemanticsDurationConstantExpression : CsdlSemanticsExpression, IEdmDurationConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsDurationConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public TimeSpan Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DurationConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private TimeSpan ComputeValue() + { + TimeSpan? value; + return EdmValueParser.TryParseDuration(this.expression.Value, out value) ? value.Value : TimeSpan.Zero; + } + + private IEnumerable ComputeErrors() + { + TimeSpan? value; + if (!EdmValueParser.TryParseDuration(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidDuration, Edm.Strings.ValueParser_InvalidDuration(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsElement.cs new file mode 100644 index 0000000..7abd70a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsElement.cs @@ -0,0 +1,146 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Common base class for CsdlSemantics classes that have Annotations. + /// + internal abstract class CsdlSemanticsElement : IEdmElement, IEdmLocatable + { + private readonly Cache> inlineVocabularyAnnotationsCache; + private static readonly Func> ComputeInlineVocabularyAnnotationsFunc = (me) => me.ComputeInlineVocabularyAnnotations(); + + private readonly Cache> directValueAnnotationsCache; + private static readonly Func> ComputeDirectValueAnnotationsFunc = (me) => me.ComputeDirectValueAnnotations(); + + private static readonly IEnumerable emptyVocabularyAnnotations = Enumerable.Empty(); + + protected CsdlSemanticsElement(CsdlElement element) + { + if (element != null) + { + // Many elements have no attached annotations. For these, save the allocation of the cache and the cost of the cache mechanism. + if (element.HasDirectValueAnnotations) + { + this.directValueAnnotationsCache = new Cache>(); + } + + if (element.HasVocabularyAnnotations) + { + this.inlineVocabularyAnnotationsCache = new Cache>(); + } + } + } + + public abstract CsdlSemanticsModel Model { get; } + + public abstract CsdlElement Element { get; } + + public IEnumerable InlineVocabularyAnnotations + { + get + { + if (this.inlineVocabularyAnnotationsCache == null) + { + return emptyVocabularyAnnotations; + } + + return this.inlineVocabularyAnnotationsCache.GetValue(this, ComputeInlineVocabularyAnnotationsFunc, null); + } + } + + public EdmLocation Location + { + get + { + if (this.Element == null || this.Element.Location == null) + { + return new ObjectLocation(this); + } + + return this.Element.Location; + } + } + + public IEnumerable DirectValueAnnotations + { + get + { + if (this.directValueAnnotationsCache == null) + { + return null; + } + + return this.directValueAnnotationsCache.GetValue(this, ComputeDirectValueAnnotationsFunc, null); + } + } + + /// + /// Allocates a new list if needed, and adds the item to the list. + /// + /// Type of the list. + /// List to add the item to. + /// Item being added. + /// List containing then new item. + protected static List AllocateAndAdd(List list, T item) + { + if (list == null) + { + list = new List(); + } + + list.Add(item); + return list; + } + + protected static List AllocateAndAdd(List list, IEnumerable items) + { + if (list == null) + { + list = new List(); + } + + list.AddRange(items); + return list; + } + + protected virtual IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, null); + } + + protected IEnumerable ComputeDirectValueAnnotations() + { + if (this.Element == null) + { + return null; + } + + List annotations = this.Element.ImmediateValueAnnotations.ToList(); + if (annotations.FirstOrDefault() != null) + { + List wrappedAnnotations = new List(); + + foreach (CsdlDirectValueAnnotation annotation in annotations) + { + wrappedAnnotations.Add(new CsdlSemanticsDirectValueAnnotation(annotation, this.Model)); + } + + return wrappedAnnotations; + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityContainer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityContainer.cs new file mode 100644 index 0000000..d3d2ec5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityContainer.cs @@ -0,0 +1,287 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlEntityContainer. + /// + internal class CsdlSemanticsEntityContainer : CsdlSemanticsElement, IEdmEntityContainer, IEdmCheckable, IEdmFullNamedElement + { + private readonly string fullName; + private readonly CsdlEntityContainer entityContainer; + private readonly CsdlSemanticsSchema context; + + private readonly Cache> elementsCache = new Cache>(); + private static readonly Func> ComputeElementsFunc = (me) => me.ComputeElements(); + + private readonly Cache> entitySetDictionaryCache = new Cache>(); + private static readonly Func> ComputeEntitySetDictionaryFunc = (me) => me.ComputeEntitySetDictionary(); + + private readonly Cache> singletonDictionaryCache = new Cache>(); + private static readonly Func> ComputeSingletonDictionaryFunc = (me) => me.ComputeSingletonDictionary(); + + private readonly Cache> operationImportsDictionaryCache = new Cache>(); + private static readonly Func> ComputeOperationImportsDictionaryFunc = (me) => me.ComputeOperationImportsDictionary(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + private readonly Cache extendsCache = new Cache(); + private static readonly Func ComputeExtendsFunc = (me) => me.ComputeExtends(); + private static readonly Func OnCycleExtendsFunc = (me) => new CyclicEntityContainer(me.entityContainer.Extends, me.Location); + + public CsdlSemanticsEntityContainer(CsdlSemanticsSchema context, CsdlEntityContainer entityContainer) + : base(entityContainer) + { + this.context = context; + this.entityContainer = entityContainer; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.context?.Namespace, this.entityContainer?.Name); + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.EntityContainer; } + } + + public override CsdlSemanticsModel Model + { + get { return this.context.Model; } + } + + public IEnumerable Elements + { + get { return this.elementsCache.GetValue(this, ComputeElementsFunc, null); } + } + + public string Namespace + { + get { return this.context.Namespace; } + } + + public string Name + { + get { return this.entityContainer.Name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + public override CsdlElement Element + { + get { return this.entityContainer; } + } + + internal CsdlSemanticsSchema Context + { + get { return this.context; } + } + + internal IEdmEntityContainer Extends + { + get { return this.extendsCache.GetValue(this, ComputeExtendsFunc, OnCycleExtendsFunc); } + } + + private Dictionary EntitySetDictionary + { + get { return this.entitySetDictionaryCache.GetValue(this, ComputeEntitySetDictionaryFunc, null); } + } + + private Dictionary SingletonDictionary + { + get { return this.singletonDictionaryCache.GetValue(this, ComputeSingletonDictionaryFunc, null); } + } + + private Dictionary OperationImportsDictionary + { + get { return this.operationImportsDictionaryCache.GetValue(this, ComputeOperationImportsDictionaryFunc, null); } + } + + public IEdmEntitySet FindEntitySet(string name) + { + IEdmEntitySet element; + return this.EntitySetDictionary.TryGetValue(name, out element) ? element : null; + } + + public IEdmSingleton FindSingleton(string name) + { + IEdmSingleton element; + return this.SingletonDictionary.TryGetValue(name, out element) ? element : null; + } + + public IEnumerable FindOperationImports(string operationName) + { + object element; + if (this.OperationImportsDictionary.TryGetValue(operationName, out element)) + { + List listElement = element as List; + if (listElement != null) + { + return listElement; + } + + return new IEdmOperationImport[] { (IEdmOperationImport)element }; + } + + return Enumerable.Empty(); + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.Context); + } + + private IEnumerable ComputeElements() + { + List elements = new List(); + + // don't import this.Extends' elements. + // (all IEdmxxx like IEdmEntityContainer should let extension methods handle cross model searches). + foreach (CsdlEntitySet entitySet in this.entityContainer.EntitySets) + { + CsdlSemanticsEntitySet semanticsSet = new CsdlSemanticsEntitySet(this, entitySet); + elements.Add(semanticsSet); + } + + foreach (CsdlSingleton singleton in entityContainer.Singletons) + { + CsdlSemanticsSingleton semanticsSingleton = new CsdlSemanticsSingleton(this, singleton); + elements.Add(semanticsSingleton); + } + + foreach (CsdlOperationImport operationImport in this.entityContainer.OperationImports) + { + this.AddOperationImport(operationImport, elements); + } + + return elements; + } + + private void AddOperationImport(CsdlOperationImport operationImport, List elements) + { + var functionImport = operationImport as CsdlFunctionImport; + var actionImport = operationImport as CsdlActionImport; + CsdlSemanticsOperationImport semanticsOperation = null; + EdmSchemaElementKind filterKind = EdmSchemaElementKind.Action; + if (functionImport != null) + { + filterKind = EdmSchemaElementKind.Function; + } + + // OperationImports only work with non-bound operations hence this extra logic in the where clause + var operations = this.context.FindOperations(operationImport.SchemaOperationQualifiedTypeName).Where(o => o.SchemaElementKind == filterKind && !o.IsBound); + + int operationsCount = 0; + foreach (IEdmOperation operation in operations) + { + if (functionImport != null) + { + semanticsOperation = new CsdlSemanticsFunctionImport(this, functionImport, (IEdmFunction)operation); + } + else + { + Debug.Assert(actionImport != null, "actionImport should not be null"); + semanticsOperation = new CsdlSemanticsActionImport(this, actionImport, (IEdmAction)operation); + } + + operationsCount++; + elements.Add(semanticsOperation); + } + + // If none have been created then its an unresolved operation. + if (operationsCount == 0) + { + if (filterKind == EdmSchemaElementKind.Action) + { + var action = new UnresolvedAction(operationImport.SchemaOperationQualifiedTypeName, Edm.Strings.Bad_UnresolvedOperation(operationImport.SchemaOperationQualifiedTypeName), operationImport.Location); + semanticsOperation = new CsdlSemanticsActionImport(this, actionImport, action); + } + else + { + Debug.Assert(filterKind == EdmSchemaElementKind.Function, "Should be a function"); + var function = new UnresolvedFunction(operationImport.SchemaOperationQualifiedTypeName, Edm.Strings.Bad_UnresolvedOperation(operationImport.SchemaOperationQualifiedTypeName), operationImport.Location); + semanticsOperation = new CsdlSemanticsFunctionImport(this, functionImport, function); + } + + elements.Add(semanticsOperation); + } + } + + private IEnumerable ComputeErrors() + { + List errors = new List(); + if (this.Extends != null && this.Extends.IsBad()) + { + errors.AddRange(((IEdmCheckable)this.Extends).Errors); + } + + return errors; + } + + private Dictionary ComputeEntitySetDictionary() + { + Dictionary sets = new Dictionary(); + foreach (IEdmEntitySet entitySet in this.Elements.OfType()) + { + RegistrationHelper.AddElement(entitySet, entitySet.Name, sets, RegistrationHelper.CreateAmbiguousEntitySetBinding); + } + + return sets; + } + + private Dictionary ComputeSingletonDictionary() + { + Dictionary sets = new Dictionary(); + foreach (IEdmSingleton singleton in this.Elements.OfType()) + { + RegistrationHelper.AddElement(singleton, singleton.Name, sets, RegistrationHelper.CreateAmbiguousSingletonBinding); + } + + return sets; + } + + private Dictionary ComputeOperationImportsDictionary() + { + Dictionary operationImports = new Dictionary(); + foreach (IEdmOperationImport operationImport in this.Elements.OfType()) + { + RegistrationHelper.AddOperationImport(operationImport, operationImport.Name, operationImports); + } + + return operationImports; + } + + private IEdmEntityContainer ComputeExtends() + { + string containerFullNameExtended = this.entityContainer.Extends; + if (containerFullNameExtended != null) + { + IEdmEntityContainer ret = this.Context.FindEntityContainer(containerFullNameExtended); + return ret ?? new UnresolvedEntityContainer(this.entityContainer.Extends, this.Location); + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeDefinition.cs new file mode 100644 index 0000000..908cf6e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeDefinition.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlEntityTypeReference. + /// + internal class CsdlSemanticsEntityReferenceTypeDefinition : CsdlSemanticsTypeDefinition, IEdmEntityReferenceType + { + private readonly CsdlSemanticsSchema schema; + + private readonly Cache entityTypeCache = new Cache(); + private static readonly Func ComputeEntityTypeFunc = (me) => me.ComputeEntityType(); + + private readonly CsdlEntityReferenceType entityTypeReference; + + public CsdlSemanticsEntityReferenceTypeDefinition(CsdlSemanticsSchema schema, CsdlEntityReferenceType entityTypeReference) + : base(entityTypeReference) + { + this.schema = schema; + this.entityTypeReference = entityTypeReference; + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.EntityReference; } + } + + public IEdmEntityType EntityType + { + get { return this.entityTypeCache.GetValue(this, ComputeEntityTypeFunc, null); } + } + + public override CsdlElement Element + { + get { return this.entityTypeReference; } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + + private IEdmEntityType ComputeEntityType() + { + IEdmTypeReference type = CsdlSemanticsModel.WrapTypeReference(this.schema, this.entityTypeReference.EntityType); + return type.TypeKind() == EdmTypeKind.Entity ? type.AsEntity().EntityDefinition() : new UnresolvedEntityType(this.schema.UnresolvedName(type.FullName()), this.Location); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeExpression.cs new file mode 100644 index 0000000..0299004 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityReferenceTypeExpression.cs @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsEntityReferenceTypeExpression : CsdlSemanticsTypeExpression, IEdmEntityReferenceTypeReference + { + public CsdlSemanticsEntityReferenceTypeExpression(CsdlExpressionTypeReference expressionUsage, CsdlSemanticsTypeDefinition type) + : base(expressionUsage, type) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntitySet.cs new file mode 100644 index 0000000..6b10d2f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntitySet.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Csdl.Parsing.Ast; + + /// + /// Provides semantics for CsdlEntitySet. + /// + internal class CsdlSemanticsEntitySet : CsdlSemanticsNavigationSource, IEdmEntitySet + { + public CsdlSemanticsEntitySet(CsdlSemanticsEntityContainer container, CsdlEntitySet entitySet) + : base(container, entitySet) + { + } + + public override IEdmType Type + { + get { return new EdmCollectionType(new EdmEntityTypeReference(this.typeCache.GetValue(this, ComputeElementTypeFunc, null), false)); } + } + + public override EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.EntitySet; } + } + + public bool IncludeInServiceDocument + { + get { return ((CsdlEntitySet)this.navigationSource).IncludeInServiceDocument; } + } + + protected override IEdmEntityType ComputeElementType() + { + string type = ((CsdlEntitySet)this.navigationSource).ElementType; + return this.container.Context.FindType(type) as IEdmEntityType ?? new UnresolvedEntityType(type, this.Location); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs new file mode 100644 index 0000000..2dedc31 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs @@ -0,0 +1,143 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlEntityType. + /// + internal class CsdlSemanticsEntityTypeDefinition : CsdlSemanticsStructuredTypeDefinition, IEdmEntityType, IEdmFullNamedElement + { + private readonly CsdlEntityType entity; + private readonly string fullName; + + private readonly Cache baseTypeCache = new Cache(); + private static readonly Func ComputeBaseTypeFunc = (me) => me.ComputeBaseType(); + private static readonly Func OnCycleBaseTypeFunc = (me) => new CyclicEntityType(me.GetCyclicBaseTypeName(me.entity.BaseTypeName), me.Location); + + private readonly Cache> declaredKeyCache = new Cache>(); + private static readonly Func> ComputeDeclaredKeyFunc = (me) => me.ComputeDeclaredKey(); + + public CsdlSemanticsEntityTypeDefinition(CsdlSemanticsSchema context, CsdlEntityType entity) + : base(context, entity) + { + this.entity = entity; + this.fullName = EdmUtil.GetFullNameForSchemaElement(context?.Namespace, this.entity?.Name); + } + + public override IEdmStructuredType BaseType + { + get { return this.baseTypeCache.GetValue(this, ComputeBaseTypeFunc, OnCycleBaseTypeFunc); } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Entity; } + } + + public string Name + { + get { return this.entity.Name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public override bool IsAbstract + { + get { return this.entity.IsAbstract; } + } + + public override bool IsOpen + { + get { return this.entity.IsOpen; } + } + + public bool HasStream + { + get { return this.entity.HasStream; } + } + + public IEnumerable DeclaredKey + { + get + { + return this.declaredKeyCache.GetValue(this, ComputeDeclaredKeyFunc, null); + } + } + + protected override CsdlStructuredType MyStructured + { + get { return this.entity; } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "baseType2", + Justification = "Value assignment is required by compiler.")] + private IEdmEntityType ComputeBaseType() + { + if (this.entity.BaseTypeName != null) + { + IEdmEntityType baseType = this.Context.FindType(this.entity.BaseTypeName) as IEdmEntityType; + if (baseType != null) + { + // Evaluate the inductive step to detect cycles. + // Overriding BaseType getter from concrete type implementing IEdmComplexType will be invoked to + // detect cycles. The object assignment is required by compiler only. + IEdmStructuredType baseType2 = baseType.BaseType; + } + + return baseType ?? new UnresolvedEntityType(this.Context.UnresolvedName(this.entity.BaseTypeName), this.Location); + } + + return null; + } + + private IEnumerable ComputeDeclaredKey() + { + if (this.entity.Key != null) + { + List key = new List(); + foreach (CsdlPropertyReference keyProperty in this.entity.Key.Properties) + { + IEdmStructuralProperty structuralProperty = this.FindProperty(keyProperty.PropertyName) as IEdmStructuralProperty; + if (structuralProperty != null) + { + key.Add(structuralProperty); + } + else + { + // If keyProperty is a duplicate, it will come back as non-structural from FindProperty, but it still might be structural + // inside the DeclaredProperties, so try it. If it is not in the DeclaredProperties or it is not structural there, + // then fall back to unresolved. + structuralProperty = this.DeclaredProperties.FirstOrDefault(p => p.Name == keyProperty.PropertyName) as IEdmStructuralProperty; + if (structuralProperty != null) + { + key.Add(structuralProperty); + } + else + { + key.Add(new UnresolvedProperty(this, keyProperty.PropertyName, this.Location)); + } + } + } + + return key; + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMember.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMember.cs new file mode 100644 index 0000000..a82cf3a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMember.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlEnumMember. + /// + internal class CsdlSemanticsEnumMember : CsdlSemanticsElement, IEdmEnumMember + { + private readonly CsdlEnumMember member; + private readonly CsdlSemanticsEnumTypeDefinition declaringType; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + public CsdlSemanticsEnumMember(CsdlSemanticsEnumTypeDefinition declaringType, CsdlEnumMember member) + : base(member) + { + this.member = member; + this.declaringType = declaringType; + } + + public string Name + { + get { return this.member.Name; } + } + + public IEdmEnumType DeclaringType + { + get { return this.declaringType; } + } + + public IEdmEnumMemberValue Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public override CsdlSemanticsModel Model + { + get { return this.declaringType.Model; } + } + + public override CsdlElement Element + { + get { return this.member; } + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.declaringType.Context); + } + + private IEdmEnumMemberValue ComputeValue() + { + if (this.member.Value == null) + { + return new BadEdmEnumMemberValue( + new EdmError[] + { + new EdmError(member.Location ?? this.Location, EdmErrorCode.EnumMemberMustHaveValue, Edm.Strings.CsdlSemantics_EnumMemberMustHaveValue) + }); + } + else + { + return new EdmEnumMemberValue(this.member.Value.Value); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMemberExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMemberExpression.cs new file mode 100644 index 0000000..eb1d364 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumMemberExpression.cs @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsEnumMemberExpression : CsdlSemanticsExpression, IEdmEnumMemberExpression, IEdmCheckable + { + private readonly CsdlEnumMemberExpression expression; + private readonly IEdmEntityType bindingContext; + + private readonly Cache> referencedCache = new Cache>(); + private static readonly Func> ComputeReferencedFunc = (me) => me.ComputeReferenced(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsEnumMemberExpression(CsdlEnumMemberExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.EnumMember; } + } + + public IEnumerable EnumMembers + { + get { return this.referencedCache.GetValue(this, ComputeReferencedFunc, null); } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private IEnumerable ComputeReferenced() + { + IEnumerable member; + return EdmEnumValueParser.TryParseEnumMember(this.expression.EnumMemberPath, this.Schema.Model, this.Location, out member) ? member : null; + } + + private IEnumerable ComputeErrors() + { + IEnumerable member; + if (!EdmEnumValueParser.TryParseEnumMember(this.expression.EnumMemberPath, this.Schema.Model, this.Location, out member)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidEnumMemberPath, Edm.Strings.CsdlParser_InvalidEnumMemberPath(this.expression.EnumMemberPath)) }; + } + + return Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumTypeDefinition.cs new file mode 100644 index 0000000..edebadf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEnumTypeDefinition.cs @@ -0,0 +1,149 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlEnumType. + /// + internal class CsdlSemanticsEnumTypeDefinition : CsdlSemanticsTypeDefinition, IEdmEnumType, IEdmFullNamedElement + { + private readonly string fullName; + private readonly CsdlEnumType enumeration; + + private readonly Cache underlyingTypeCache = new Cache(); + private static readonly Func ComputeUnderlyingTypeFunc = (me) => me.ComputeUnderlyingType(); + + private readonly Cache> membersCache = new Cache>(); + private static readonly Func> ComputeMembersFunc = (me) => me.ComputeMembers(); + + public CsdlSemanticsEnumTypeDefinition(CsdlSemanticsSchema context, CsdlEnumType enumeration) + : base(enumeration) + { + this.Context = context; + this.enumeration = enumeration; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.Context?.Namespace, this.enumeration?.Name); + } + + IEdmPrimitiveType IEdmEnumType.UnderlyingType + { + get { return this.underlyingTypeCache.GetValue(this, ComputeUnderlyingTypeFunc, null); } + } + + public IEnumerable Members + { + get { return this.membersCache.GetValue(this, ComputeMembersFunc, null); } + } + + bool IEdmEnumType.IsFlags + { + get { return this.enumeration.IsFlags; } + } + + EdmSchemaElementKind IEdmSchemaElement.SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + public string Namespace + { + get { return this.Context.Namespace; } + } + + public string FullName + { + get { return this.fullName; } + } + + string IEdmNamedElement.Name + { + get { return this.enumeration.Name; } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Enum; } + } + + public override CsdlSemanticsModel Model + { + get { return this.Context.Model; } + } + + public override CsdlElement Element + { + get { return this.enumeration; } + } + + public CsdlSemanticsSchema Context + { + get; + private set; + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.Context); + } + + private IEdmPrimitiveType ComputeUnderlyingType() + { + if (this.enumeration.UnderlyingTypeName != null) + { + var underlyingTypeKind = EdmCoreModel.Instance.GetPrimitiveTypeKind(this.enumeration.UnderlyingTypeName); + return underlyingTypeKind != EdmPrimitiveTypeKind.None ? + EdmCoreModel.Instance.GetPrimitiveType(underlyingTypeKind) : + new UnresolvedPrimitiveType(this.enumeration.UnderlyingTypeName, this.Location); + } + + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int32); + } + + private IEnumerable ComputeMembers() + { + var members = new List(); + + // Walk the members and assign implicit values where needed. + long previousValue = -1; + foreach (CsdlEnumMember member in this.enumeration.Members) + { + IEdmEnumMember semanticsMember; + long? implicitValue = null; + if (!member.Value.HasValue) + { + if (previousValue < long.MaxValue) + { + implicitValue = previousValue + 1; + previousValue = implicitValue.Value; + member.Value = implicitValue; + semanticsMember = new CsdlSemanticsEnumMember(this, member); + } + else + { + semanticsMember = new CsdlSemanticsEnumMember(this, member); + } + + semanticsMember.SetIsValueExplicit(this.Model, false); + } + else + { + previousValue = member.Value.Value; + semanticsMember = new CsdlSemanticsEnumMember(this, member); + semanticsMember.SetIsValueExplicit(this.Model, true); + } + + members.Add(semanticsMember); + } + + return members; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsExpression.cs new file mode 100644 index 0000000..114eda2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsExpression.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal abstract class CsdlSemanticsExpression : CsdlSemanticsElement, IEdmExpression + { + private readonly CsdlSemanticsSchema schema; + + protected CsdlSemanticsExpression(CsdlSemanticsSchema schema, CsdlExpressionBase element) + : base(element) + { + this.schema = schema; + } + + public abstract EdmExpressionKind ExpressionKind + { + get; + } + + public CsdlSemanticsSchema Schema + { + get { return this.schema; } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFloatingConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFloatingConstantExpression.cs new file mode 100644 index 0000000..52b7858 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFloatingConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Float constant expression. + /// + internal class CsdlSemanticsFloatingConstantExpression : CsdlSemanticsExpression, IEdmFloatingConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsFloatingConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public double Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.FloatingConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private double ComputeValue() + { + double? value; + return EdmValueParser.TryParseFloat(this.expression.Value, out value) ? value.Value : 0; + } + + private IEnumerable ComputeErrors() + { + double? value; + if (!EdmValueParser.TryParseFloat(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidFloatingPoint, Edm.Strings.ValueParser_InvalidFloatingPoint(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunction.cs new file mode 100644 index 0000000..879d917 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunction.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlFunction + /// + internal class CsdlSemanticsFunction : CsdlSemanticsOperation, IEdmFunction + { + private readonly CsdlFunction function; + + public CsdlSemanticsFunction(CsdlSemanticsSchema context, CsdlFunction function) + : base(context, function) + { + this.function = function; + } + + public bool IsComposable + { + get { return this.function.IsComposable; } + } + + public override EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Function; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunctionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunctionImport.cs new file mode 100644 index 0000000..4cc19b1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsFunctionImport.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsFunctionImport : CsdlSemanticsOperationImport, IEdmFunctionImport + { + private readonly CsdlFunctionImport functionImport; + private readonly CsdlSemanticsSchema csdlSchema; + + public CsdlSemanticsFunctionImport(CsdlSemanticsEntityContainer container, CsdlFunctionImport functionImport, IEdmFunction backingfunction) + : base(container, functionImport, backingfunction) + { + this.csdlSchema = container.Context; + this.functionImport = functionImport; + } + + public IEdmFunction Function + { + get { return (IEdmFunction)this.Operation; } + } + + public bool IncludeInServiceDocument + { + get { return this.functionImport.IncludeInServiceDocument; } + } + + public override EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.FunctionImport; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsGuidConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsGuidConstantExpression.cs new file mode 100644 index 0000000..bbe11f2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsGuidConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl guid constant expression. + /// + internal class CsdlSemanticsGuidConstantExpression : CsdlSemanticsExpression, IEdmGuidConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsGuidConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public Guid Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.GuidConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private Guid ComputeValue() + { + Guid? value; + return EdmValueParser.TryParseGuid(this.expression.Value, out value) ? value.Value : Guid.Empty; + } + + private IEnumerable ComputeErrors() + { + Guid? value; + if (!EdmValueParser.TryParseGuid(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidGuid, Edm.Strings.ValueParser_InvalidGuid(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIfExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIfExpression.cs new file mode 100644 index 0000000..34cf7f5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIfExpression.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsIfExpression : CsdlSemanticsExpression, IEdmIfExpression + { + private readonly CsdlIfExpression expression; + private readonly IEdmEntityType bindingContext; + + private readonly Cache testCache = new Cache(); + private static readonly Func ComputeTestFunc = (me) => me.ComputeTest(); + + private readonly Cache ifTrueCache = new Cache(); + private static readonly Func ComputeIfTrueFunc = (me) => me.ComputeIfTrue(); + + private readonly Cache ifFalseCache = new Cache(); + private static readonly Func ComputeIfFalseFunc = (me) => me.ComputeIfFalse(); + + public CsdlSemanticsIfExpression(CsdlIfExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public IEdmEntityType BindingContext + { + get { return this.bindingContext; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.If; } + } + + public IEdmExpression TestExpression + { + get { return this.testCache.GetValue(this, ComputeTestFunc, null); } + } + + public IEdmExpression TrueExpression + { + get { return this.ifTrueCache.GetValue(this, ComputeIfTrueFunc, null); } + } + + public IEdmExpression FalseExpression + { + get { return this.ifFalseCache.GetValue(this, ComputeIfFalseFunc, null); } + } + + private IEdmExpression ComputeTest() + { + return CsdlSemanticsModel.WrapExpression(this.expression.Test, this.BindingContext, this.Schema); + } + + private IEdmExpression ComputeIfTrue() + { + return CsdlSemanticsModel.WrapExpression(this.expression.IfTrue, this.BindingContext, this.Schema); + } + + private IEdmExpression ComputeIfFalse() + { + return CsdlSemanticsModel.WrapExpression(this.expression.IfFalse, this.BindingContext, this.Schema); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIntConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIntConstantExpression.cs new file mode 100644 index 0000000..6a298d1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIntConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Int constant expression. + /// + internal class CsdlSemanticsIntConstantExpression : CsdlSemanticsExpression, IEdmIntegerConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsIntConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public Int64 Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.IntegerConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private Int64 ComputeValue() + { + Int64? value; + return EdmValueParser.TryParseLong(this.expression.Value, out value) ? value.Value : 0; + } + + private IEnumerable ComputeErrors() + { + Int64? value; + if (!EdmValueParser.TryParseLong(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidInteger, Edm.Strings.ValueParser_InvalidInteger(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIsTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIsTypeExpression.cs new file mode 100644 index 0000000..2eae8d6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsIsTypeExpression.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsIsTypeExpression : CsdlSemanticsExpression, IEdmIsTypeExpression + { + private readonly CsdlIsTypeExpression expression; + private readonly IEdmEntityType bindingContext; + + private readonly Cache operandCache = new Cache(); + private static readonly Func ComputeOperandFunc = (me) => me.ComputeOperand(); + + private readonly Cache typeCache = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public CsdlSemanticsIsTypeExpression(CsdlIsTypeExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.IsType; } + } + + public IEdmExpression Operand + { + get { return this.operandCache.GetValue(this, ComputeOperandFunc, null); } + } + + public IEdmTypeReference Type + { + get { return this.typeCache.GetValue(this, ComputeTypeFunc, null); } + } + + private IEdmExpression ComputeOperand() + { + return CsdlSemanticsModel.WrapExpression(this.expression.Operand, this.bindingContext, this.Schema); + } + + private IEdmTypeReference ComputeType() + { + return CsdlSemanticsModel.WrapTypeReference(this.Schema, this.expression.Type); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpression.cs new file mode 100644 index 0000000..9dc210c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpression.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsLabeledExpression : CsdlSemanticsElement, IEdmLabeledExpression + { + private readonly string name; + private readonly CsdlExpressionBase sourceElement; + private readonly CsdlSemanticsSchema schema; + private readonly IEdmEntityType bindingContext; + + private readonly Cache expressionCache = new Cache(); + private static readonly Func ComputeExpressionFunc = (me) => me.ComputeExpression(); + + public CsdlSemanticsLabeledExpression(string name, CsdlExpressionBase element, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(element) + { + this.name = name; + this.sourceElement = element; + this.bindingContext = bindingContext; + this.schema = schema; + } + + public override CsdlElement Element + { + get { return this.sourceElement; } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + + public IEdmEntityType BindingContext + { + get { return this.bindingContext; } + } + + public IEdmExpression Expression + { + get { return this.expressionCache.GetValue(this, ComputeExpressionFunc, null); } + } + + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Labeled; } + } + + public string Name + { + get { return this.name; } + } + + private IEdmExpression ComputeExpression() + { + return CsdlSemanticsModel.WrapExpression(this.sourceElement, this.BindingContext, this.schema); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpressionReferenceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpressionReferenceExpression.cs new file mode 100644 index 0000000..2860bc7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsLabeledExpressionReferenceExpression.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsLabeledExpressionReferenceExpression : CsdlSemanticsExpression, IEdmLabeledExpressionReferenceExpression, IEdmCheckable + { + private readonly CsdlLabeledExpressionReferenceExpression expression; + private readonly IEdmEntityType bindingContext; + + private readonly Cache elementCache = new Cache(); + private static readonly Func ComputeElementFunc = (me) => me.ComputeElement(); + + public CsdlSemanticsLabeledExpressionReferenceExpression(CsdlLabeledExpressionReferenceExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.LabeledExpressionReference; } + } + + public IEdmLabeledExpression ReferencedLabeledExpression + { + get { return this.elementCache.GetValue(this, ComputeElementFunc, null); } + } + + public IEnumerable Errors + { + get + { + if (this.ReferencedLabeledExpression is IUnresolvedElement) + { + return this.ReferencedLabeledExpression.Errors(); + } + + return Enumerable.Empty(); + } + } + + private IEdmLabeledExpression ComputeElement() + { + IEdmLabeledExpression result = this.Schema.FindLabeledElement(this.expression.Label, this.bindingContext); + if (result != null) + { + return result; + } + + return new UnresolvedLabeledElement(this.expression.Label, this.Location); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsModel.cs new file mode 100644 index 0000000..d7820ca --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsModel.cs @@ -0,0 +1,614 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#if PORTABLELIB +using System.Collections.Concurrent; +#endif +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for one CsdlModel and its referenced CsdlModels. + /// + [DebuggerDisplay("CsdlSemanticsModel({string.Join(\",\", DeclaredNamespaces)})")] + internal class CsdlSemanticsModel : EdmModelBase, IEdmCheckable + { + private readonly CsdlSemanticsModel mainEdmModel; // parent IEdmModel + private readonly CsdlModel astModel; // current internal CsdlModel + private readonly List schemata = new List(); + private readonly Dictionary> outOfLineAnnotations = new Dictionary>(); +#if PORTABLELIB + private readonly ConcurrentDictionary wrappedAnnotations = new ConcurrentDictionary(); +#else + private readonly Dictionary wrappedAnnotations = new Dictionary(); +#endif + private readonly Dictionary> derivedTypeMappings = new Dictionary>(); + + /// + /// Constructor + /// + /// The raw CsdlModel. + /// The IEdmDirectValueAnnotationsManager. + /// The IEdmModels to be referenced. if any element or namespce is not supposed to be include, you should have removed it before passing to this constructor. + /// A value indicating enable/disable the built-in vocabulary supporting. + public CsdlSemanticsModel(CsdlModel astModel, IEdmDirectValueAnnotationsManager annotationsManager, IEnumerable referencedModels, bool includeDefaultVocabularies = true) + : base(referencedModels, annotationsManager, includeDefaultVocabularies) + { + this.astModel = astModel; + this.SetEdmReferences(astModel.CurrentModelReferences); + foreach (CsdlSchema schema in this.astModel.Schemata) + { + this.AddSchema(schema); + } + } + + /// + /// Constructor + /// + /// The main raw CsdlModel. + /// The IEdmDirectValueAnnotationsManager. + /// The referenced raw CsdlModels. + /// A value indicating enable/disable the built-in vocabulary supporting. + public CsdlSemanticsModel(CsdlModel mainCsdlModel, IEdmDirectValueAnnotationsManager annotationsManager, IEnumerable referencedCsdlModels, bool includeDefaultVocabularies) + : base(Enumerable.Empty(), annotationsManager, includeDefaultVocabularies) + { + this.astModel = mainCsdlModel; + this.SetEdmReferences(astModel.CurrentModelReferences); + + // 1. build semantics for referenced models + foreach (var tmp in referencedCsdlModels) + { + var refModel = new CsdlSemanticsModel(tmp, this.DirectValueAnnotationsManager, this, includeDefaultVocabularies); + this.AddReferencedModel(refModel); + } + + // 2. build semantics for current model + foreach (var include in mainCsdlModel.CurrentModelReferences.SelectMany(s => s.Includes)) + { + this.SetNamespaceAlias(include.Namespace, include.Alias); + } + + foreach (CsdlSchema schema in this.astModel.Schemata) + { + this.AddSchema(schema); + } + } + + /// + /// Constructor for creating a referenced model, is private and only called by the above constructor. + /// + /// The referenced raw CsdlModel. + /// The IEdmDirectValueAnnotationsManager. + /// The CsdlSemanticsModel that will reference this new CsdlSemanticsModel. + /// A value indicating enable/disable the built-in vocabulary supporting. + private CsdlSemanticsModel(CsdlModel referencedCsdlModel, IEdmDirectValueAnnotationsManager annotationsManager, CsdlSemanticsModel mainCsdlSemanticsModel, bool includeDefaultVocabularies) + : base(Enumerable.Empty(), annotationsManager, includeDefaultVocabularies) + { + this.mainEdmModel = mainCsdlSemanticsModel; + Debug.Assert(referencedCsdlModel.ParentModelReferences.Any(), "referencedCsdlModel.ParentModelReferences.Any()"); + this.astModel = referencedCsdlModel; + this.SetEdmReferences(referencedCsdlModel.CurrentModelReferences); + + foreach (var tmp in referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes)) + { + string includeNs = tmp.Namespace; + if (!referencedCsdlModel.Schemata.Any(s => s.Namespace == includeNs)) + { + // edmx:include must be an existing namespace + // TODO: REF throw exception: should include a namespace that exists in referenced model. + } + } + + foreach (var tmp in referencedCsdlModel.CurrentModelReferences.SelectMany(s => s.Includes)) + { + // in any referenced model, alias may point to a further referenced model, now make alias available: + this.SetNamespaceAlias(tmp.Namespace, tmp.Alias); + } + + foreach (var schema in referencedCsdlModel.Schemata) + { + string schemaNamespace = schema.Namespace; + IEdmInclude edmInclude = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).FirstOrDefault(s => s.Namespace == schemaNamespace); + if (edmInclude != null) + { + this.AddSchema(schema, false /*addAnnotations*/); + } + + // TODO: REF add annotations + } + } + + public override IEnumerable SchemaElements + { + get + { + foreach (CsdlSemanticsSchema schema in this.schemata) + { + foreach (IEdmSchemaType type in schema.Types) + { + yield return type; + } + + foreach (IEdmSchemaElement function in schema.Operations) + { + yield return function; + } + + foreach (IEdmSchemaElement valueTerm in schema.Terms) + { + yield return valueTerm; + } + + foreach (IEdmEntityContainer entityContainer in schema.EntityContainers) + { + yield return entityContainer; + } + } + } + } + + public override IEnumerable DeclaredNamespaces + { + get { return this.schemata.Select(s => s.Namespace); } + } + + public IDictionary> OutOfLineAnnotations + { + get + { + return outOfLineAnnotations; + } + } + + public override IEnumerable VocabularyAnnotations + { + get + { + List result = new List(); + + foreach (CsdlSemanticsSchema schema in this.schemata) + { + foreach (CsdlAnnotations sourceAnnotations in ((CsdlSchema)schema.Element).OutOfLineAnnotations) + { + CsdlSemanticsAnnotations annotations = new CsdlSemanticsAnnotations(schema, sourceAnnotations); + foreach (CsdlAnnotation sourceAnnotation in sourceAnnotations.Annotations) + { + IEdmVocabularyAnnotation vocabAnnotation = this.WrapVocabularyAnnotation(sourceAnnotation, schema, null, annotations, sourceAnnotations.Qualifier); + vocabAnnotation.SetSerializationLocation(this, EdmVocabularyAnnotationSerializationLocation.OutOfLine); + vocabAnnotation.SetSchemaNamespace(this, schema.Namespace); + result.Add(vocabAnnotation); + } + } + } + + foreach (IEdmSchemaElement element in this.SchemaElements) + { + result.AddRange(((CsdlSemanticsElement)element).InlineVocabularyAnnotations); + + CsdlSemanticsStructuredTypeDefinition type = element as CsdlSemanticsStructuredTypeDefinition; + if (type != null) + { + foreach (IEdmProperty property in type.DeclaredProperties) + { + result.AddRange(((CsdlSemanticsElement)property).InlineVocabularyAnnotations); + } + } + + CsdlSemanticsOperation operation = element as CsdlSemanticsOperation; + if (operation != null) + { + foreach (IEdmOperationParameter parameter in operation.Parameters) + { + result.AddRange(((CsdlSemanticsElement)parameter).InlineVocabularyAnnotations); + } + } + + CsdlSemanticsEntityContainer container = element as CsdlSemanticsEntityContainer; + if (container != null) + { + foreach (IEdmEntityContainerElement containerElement in container.Elements) + { + result.AddRange(((CsdlSemanticsElement)containerElement).InlineVocabularyAnnotations); + } + } + + CsdlSemanticsEnumTypeDefinition enumType = element as CsdlSemanticsEnumTypeDefinition; + if (enumType != null) + { + foreach (IEdmEnumMember member in enumType.Members) + { + result.AddRange(((CsdlSemanticsElement)member).InlineVocabularyAnnotations); + } + } + } + + return result; + } + } + + /// + /// Gets an error if one exists with the current object. + /// + public IEnumerable Errors + { + get + { + List errors = new List(); + + HashSetInternal usedAlias = new HashSetInternal(); + var usedNamespaces = this.GetUsedNamespacesHavingAlias(); + var mappings = this.GetNamespaceAliases(); + + if (usedNamespaces != null && mappings != null) + { + foreach (var ns in usedNamespaces) + { + string alias; + if (mappings.TryGetValue(ns, out alias) && !usedAlias.Add(alias)) + { + errors.Add(new EdmError(this.Location(), EdmErrorCode.DuplicateAlias, Strings.CsdlSemantics_DuplicateAlias(ns, alias))); + } + } + } + + foreach (CsdlSemanticsSchema schema in this.schemata) + { + errors.AddRange(schema.Errors()); + } + + return errors; + } + } + + /// + /// Gets the main model that is referencing this model. The value may be null. + /// + internal CsdlSemanticsModel MainModel + { + get { return this.mainEdmModel; } + } + + /// + /// Searches for vocabulary annotations specified by this model. + /// + /// The annotated element. + /// The vocabulary annotations for the element. + public override IEnumerable FindDeclaredVocabularyAnnotations(IEdmVocabularyAnnotatable element) + { + // Include the inline annotations only if this model is the one that defined them. + CsdlSemanticsElement semanticsElement = element as CsdlSemanticsElement; + IEnumerable inlineAnnotations = semanticsElement != null && semanticsElement.Model == this ? semanticsElement.InlineVocabularyAnnotations : Enumerable.Empty(); + + List elementAnnotations; + string fullName = EdmUtil.FullyQualifiedName(element); + + if (fullName != null && this.outOfLineAnnotations.TryGetValue(fullName, out elementAnnotations)) + { + List result = new List(); + + foreach (CsdlSemanticsAnnotations annotations in elementAnnotations) + { + foreach (CsdlAnnotation annotation in annotations.Annotations.Annotations) + { + IEdmVocabularyAnnotation vocabAnnotation = this.WrapVocabularyAnnotation(annotation, annotations.Context, null, annotations, annotations.Annotations.Qualifier); + vocabAnnotation.SetSerializationLocation(this, EdmVocabularyAnnotationSerializationLocation.OutOfLine); + result.Add(vocabAnnotation); + } + } + + return inlineAnnotations.Concat(result); + } + + return inlineAnnotations; + + // TODO: REF + // find annotation in referenced models + } + + public override IEnumerable FindDirectlyDerivedTypes(IEdmStructuredType baseType) + { + List ret = new List(); + List candidates; + if (this.derivedTypeMappings.TryGetValue(((IEdmSchemaType)baseType).Name, out candidates)) + { + ret.AddRange(candidates.Where(t => t.BaseType == baseType)); + } + + // find derived type in referenced models + foreach (var tmp in this.ReferencedModels) + { + ret.AddRange(tmp.FindDirectlyDerivedTypes(baseType)); + } + + return ret; + } + + internal void AddToReferencedModels(IEnumerable models) + { + foreach (var edmModel in models) + { + this.AddReferencedModel(edmModel); + } + } + + internal static IEdmExpression WrapExpression(CsdlExpressionBase expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + { + if (expression != null) + { + switch (expression.ExpressionKind) + { + case EdmExpressionKind.Cast: + return new CsdlSemanticsCastExpression((CsdlCastExpression)expression, bindingContext, schema); + case EdmExpressionKind.BinaryConstant: + return new CsdlSemanticsBinaryConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.BooleanConstant: + return new CsdlSemanticsBooleanConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.Collection: + return new CsdlSemanticsCollectionExpression((CsdlCollectionExpression)expression, bindingContext, schema); + case EdmExpressionKind.DateTimeOffsetConstant: + return new CsdlSemanticsDateTimeOffsetConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.DecimalConstant: + return new CsdlSemanticsDecimalConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.EnumMember: + return new CsdlSemanticsEnumMemberExpression((CsdlEnumMemberExpression)expression, bindingContext, schema); + case EdmExpressionKind.FloatingConstant: + return new CsdlSemanticsFloatingConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.Null: + return new CsdlSemanticsNullExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.FunctionApplication: + return new CsdlSemanticsApplyExpression((CsdlApplyExpression)expression, bindingContext, schema); + case EdmExpressionKind.GuidConstant: + return new CsdlSemanticsGuidConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.If: + return new CsdlSemanticsIfExpression((CsdlIfExpression)expression, bindingContext, schema); + case EdmExpressionKind.IntegerConstant: + return new CsdlSemanticsIntConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.IsType: + return new CsdlSemanticsIsTypeExpression((CsdlIsTypeExpression)expression, bindingContext, schema); + case EdmExpressionKind.LabeledExpressionReference: + return new CsdlSemanticsLabeledExpressionReferenceExpression((CsdlLabeledExpressionReferenceExpression)expression, bindingContext, schema); + case EdmExpressionKind.Labeled: + return schema.WrapLabeledElement((CsdlLabeledExpression)expression, bindingContext); + case EdmExpressionKind.Path: + return new CsdlSemanticsPathExpression((CsdlPathExpression)expression, bindingContext, schema); + case EdmExpressionKind.PropertyPath: + return new CsdlSemanticsPropertyPathExpression((CsdlPropertyPathExpression)expression, bindingContext, schema); + case EdmExpressionKind.NavigationPropertyPath: + return new CsdlSemanticsNavigationPropertyPathExpression((CsdlNavigationPropertyPathExpression)expression, bindingContext, schema); + case EdmExpressionKind.Record: + return new CsdlSemanticsRecordExpression((CsdlRecordExpression)expression, bindingContext, schema); + case EdmExpressionKind.StringConstant: + return new CsdlSemanticsStringConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.DurationConstant: + return new CsdlSemanticsDurationConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.DateConstant: + return new CsdlSemanticsDateConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.TimeOfDayConstant: + return new CsdlSemanticsTimeOfDayConstantExpression((CsdlConstantExpression)expression, schema); + case EdmExpressionKind.AnnotationPath: + return new CsdlSemanticsAnnotationPathExpression((CsdlAnnotationPathExpression)expression, bindingContext, schema); + } + } + + return null; + } + + internal static IEdmTypeReference WrapTypeReference(CsdlSemanticsSchema schema, CsdlTypeReference type) + { + var typeReference = type as CsdlNamedTypeReference; + if (typeReference != null) + { + var primitiveReference = typeReference as CsdlPrimitiveTypeReference; + if (primitiveReference != null) + { + switch (primitiveReference.Kind) + { + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Stream: + return new CsdlSemanticsPrimitiveTypeReference(schema, primitiveReference); + + case EdmPrimitiveTypeKind.Binary: + return new CsdlSemanticsBinaryTypeReference(schema, (CsdlBinaryTypeReference)primitiveReference); + + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + return new CsdlSemanticsTemporalTypeReference(schema, (CsdlTemporalTypeReference)primitiveReference); + + case EdmPrimitiveTypeKind.Decimal: + return new CsdlSemanticsDecimalTypeReference(schema, (CsdlDecimalTypeReference)primitiveReference); + + case EdmPrimitiveTypeKind.String: + return new CsdlSemanticsStringTypeReference(schema, (CsdlStringTypeReference)primitiveReference); + + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return new CsdlSemanticsSpatialTypeReference(schema, (CsdlSpatialTypeReference)primitiveReference); + } + } + else + { + CsdlUntypedTypeReference csdlUntypedTypeReference = typeReference as CsdlUntypedTypeReference; + if (csdlUntypedTypeReference != null) + { + return new CsdlSemanticsUntypedTypeReference(schema, csdlUntypedTypeReference); + } + + if (schema.FindType(typeReference.FullName) is IEdmTypeDefinition) + { + return new CsdlSemanticsTypeDefinitionReference(schema, typeReference); + } + } + + return new CsdlSemanticsNamedTypeReference(schema, typeReference); + } + + var typeExpression = type as CsdlExpressionTypeReference; + if (typeExpression != null) + { + var collectionType = typeExpression.TypeExpression as CsdlCollectionType; + if (collectionType != null) + { + return new CsdlSemanticsCollectionTypeExpression(typeExpression, new CsdlSemanticsCollectionTypeDefinition(schema, collectionType)); + } + + var entityReferenceType = typeExpression.TypeExpression as CsdlEntityReferenceType; + if (entityReferenceType != null) + { + return new CsdlSemanticsEntityReferenceTypeExpression(typeExpression, new CsdlSemanticsEntityReferenceTypeDefinition(schema, entityReferenceType)); + } + } + + return null; + } + + internal IEnumerable WrapInlineVocabularyAnnotations(CsdlSemanticsElement element, CsdlSemanticsSchema schema) + { + IEdmVocabularyAnnotatable vocabularyAnnotatableElement = element as IEdmVocabularyAnnotatable; + if (vocabularyAnnotatableElement != null) + { + IEnumerable vocabularyAnnotations = element.Element.VocabularyAnnotations; + if (vocabularyAnnotations.FirstOrDefault() != null) + { + List wrappedAnnotations = new List(); + foreach (CsdlAnnotation vocabularyAnnotation in vocabularyAnnotations) + { + IEdmVocabularyAnnotation vocabAnnotation = this.WrapVocabularyAnnotation(vocabularyAnnotation, schema, vocabularyAnnotatableElement, null, vocabularyAnnotation.Qualifier); + vocabAnnotation.SetSerializationLocation(this, EdmVocabularyAnnotationSerializationLocation.Inline); + wrappedAnnotations.Add(vocabAnnotation); + } + + return wrappedAnnotations; + } + } + + return Enumerable.Empty(); + } + + private IEdmVocabularyAnnotation WrapVocabularyAnnotation(CsdlAnnotation annotation, CsdlSemanticsSchema schema, IEdmVocabularyAnnotatable targetContext, CsdlSemanticsAnnotations annotationsContext, string qualifier) + { + return EdmUtil.DictionaryGetOrUpdate( + this.wrappedAnnotations, + annotation, + ann => new CsdlSemanticsVocabularyAnnotation(schema, targetContext, annotationsContext, ann, qualifier)); + } + + private void AddSchema(CsdlSchema schema) + { + this.AddSchema(schema, true); + } + + private void AddSchema(CsdlSchema schema, bool addAnnotations) + { + CsdlSemanticsSchema schemaWrapper = new CsdlSemanticsSchema(this, schema); + this.schemata.Add(schemaWrapper); + foreach (IEdmSchemaType type in schemaWrapper.Types) + { + CsdlSemanticsStructuredTypeDefinition structuredType = type as CsdlSemanticsStructuredTypeDefinition; + if (structuredType != null) + { + string baseTypeNamespace; + string baseTypeName; + string baseTypeFullName = ((CsdlNamedStructuredType)structuredType.Element).BaseTypeName; + if (baseTypeFullName != null) + { + EdmUtil.TryGetNamespaceNameFromQualifiedName(baseTypeFullName, out baseTypeNamespace, out baseTypeName); + if (baseTypeName != null) + { + List derivedTypes; + if (!this.derivedTypeMappings.TryGetValue(baseTypeName, out derivedTypes)) + { + derivedTypes = new List(); + this.derivedTypeMappings[baseTypeName] = derivedTypes; + } + + // TODO: REF referenced derived types + derivedTypes.Add(structuredType); + } + } + } + + RegisterElement(type); + } + + foreach (IEdmOperation function in schemaWrapper.Operations) + { + RegisterElement(function); + } + + foreach (IEdmTerm valueTerm in schemaWrapper.Terms) + { + RegisterElement(valueTerm); + } + + foreach (IEdmEntityContainer container in schemaWrapper.EntityContainers) + { + RegisterElement(container); + } + + if (!string.IsNullOrEmpty(schema.Alias)) + { + this.SetNamespaceAlias(schema.Namespace, schema.Alias); + } + + if (addAnnotations) + { + foreach (CsdlAnnotations schemaOutOfLineAnnotations in schema.OutOfLineAnnotations) + { + string target = this.ReplaceAlias(schemaOutOfLineAnnotations.Target); + + List annotations; + if (!this.outOfLineAnnotations.TryGetValue(target, out annotations)) + { + annotations = new List(); + this.outOfLineAnnotations[target] = annotations; + } + + annotations.Add(new CsdlSemanticsAnnotations(schemaWrapper, schemaOutOfLineAnnotations)); + } + } + + var edmVersion = this.GetEdmVersion(); + if (edmVersion == null || edmVersion < schema.Version) + { + this.SetEdmVersion(schema.Version); + } + } + } +} + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNamedTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNamedTypeReference.cs new file mode 100644 index 0000000..f089585 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNamedTypeReference.cs @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlNamedTypeReference. + /// + internal class CsdlSemanticsNamedTypeReference : CsdlSemanticsElement, IEdmTypeReference + { + private readonly CsdlSemanticsSchema schema; + private readonly CsdlNamedTypeReference reference; + + private readonly Cache definitionCache = new Cache(); + private static readonly Func ComputeDefinitionFunc = (me) => me.ComputeDefinition(); + + public CsdlSemanticsNamedTypeReference(CsdlSemanticsSchema schema, CsdlNamedTypeReference reference) + : base(reference) + { + this.schema = schema; + this.reference = reference; + } + + public IEdmType Definition + { + get { return this.definitionCache.GetValue(this, ComputeDefinitionFunc, null); } + } + + public bool IsNullable + { + get { return this.reference.IsNullable; } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + + public override CsdlElement Element + { + get { return this.reference; } + } + + public override string ToString() + { + return this.ToTraceString(); + } + + private IEdmType ComputeDefinition() + { + IEdmType binding = this.schema.FindType(this.reference.FullName); + + return binding ?? new UnresolvedType(this.schema.ReplaceAlias(this.reference.FullName), this.Location); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationProperty.cs new file mode 100644 index 0000000..a3baf33 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationProperty.cs @@ -0,0 +1,274 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlNavigationProperty. + /// + internal class CsdlSemanticsNavigationProperty : CsdlSemanticsElement, IEdmNavigationProperty, IEdmCheckable + { + private readonly CsdlNavigationProperty navigationProperty; + private readonly CsdlSemanticsStructuredTypeDefinition declaringType; + + private readonly Cache typeCache = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + private readonly Cache partnerCache = new Cache(); + private static readonly Func ComputePartnerFunc = (me) => me.ComputePartner(); + + private readonly Cache referentialConstraintCache = new Cache(); + private static readonly Func ComputeReferentialConstraintFunc = (me) => me.ComputeReferentialConstraint(); + + private readonly Cache targetEntityTypeCache = new Cache(); + private static readonly Func ComputeTargetEntityTypeFunc = (me) => me.ComputeTargetEntityType(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsNavigationProperty(CsdlSemanticsStructuredTypeDefinition declaringType, CsdlNavigationProperty navigationProperty) + : base(navigationProperty) + { + this.declaringType = declaringType; + this.navigationProperty = navigationProperty; + } + + public override CsdlSemanticsModel Model + { + get { return this.declaringType.Model; } + } + + public override CsdlElement Element + { + get { return this.navigationProperty; } + } + + public string Name + { + get { return this.navigationProperty.Name; } + } + + public EdmOnDeleteAction OnDelete + { + get + { + return (this.navigationProperty.OnDelete != null) ? this.navigationProperty.OnDelete.Action : EdmOnDeleteAction.None; + } + } + + public IEdmStructuredType DeclaringType + { + get { return this.declaringType; } + } + + public bool ContainsTarget + { + get { return this.navigationProperty.ContainsTarget; } + } + + public IEdmTypeReference Type + { + get { return this.typeCache.GetValue(this, ComputeTypeFunc, null); } + } + + public EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.Navigation; } + } + + public IEdmNavigationProperty Partner + { + get { return this.partnerCache.GetValue(this, ComputePartnerFunc, cycle => null); } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + public IEdmReferentialConstraint ReferentialConstraint + { + get { return this.referentialConstraintCache.GetValue(this, ComputeReferentialConstraintFunc, null); } + } + + private IEdmEntityType TargetEntityType + { + get { return this.targetEntityTypeCache.GetValue(this, ComputeTargetEntityTypeFunc, null); } + } + + internal static IEdmNavigationProperty ResolvePartnerPath(IEdmEntityType type, IEdmPathExpression path, IEdmModel model) + { + Debug.Assert(type != null); + Debug.Assert(path != null); + Debug.Assert(model != null); + + IEdmStructuredType currentType = type; + IEdmProperty property = null; + foreach (var segment in path.PathSegments) + { + if (currentType == null) + { + return null; + } + + if (segment.IndexOf('.') < 0) + { + property = currentType.FindProperty(segment); + if (property == null) + { + return null; + } + + currentType = property.Type.Definition.AsElementType() as IEdmStructuredType; + } + else + { + var derivedType = model.FindDeclaredType(segment); + if (derivedType == null || !derivedType.IsOrInheritsFrom(currentType)) + { + return null; + } + + currentType = derivedType as IEdmStructuredType; + property = null; + } + } + + return property != null + ? property as IEdmNavigationProperty + : null; + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.declaringType.Context); + } + + private IEdmEntityType ComputeTargetEntityType() + { + IEdmType target = this.Type.Definition; + if (target.TypeKind == EdmTypeKind.Collection) + { + target = ((IEdmCollectionType)target).ElementType.Definition; + } + + return (IEdmEntityType)target; + } + + private IEdmNavigationProperty ComputePartner() + { + var partnerPropertyPath = this.navigationProperty.PartnerPath; + IEdmEntityType targetEntityType = this.TargetEntityType; + + if (partnerPropertyPath != null) + { + return ResolvePartnerPath(targetEntityType, partnerPropertyPath, Model) + ?? new UnresolvedNavigationPropertyPath(targetEntityType, partnerPropertyPath.Path, Location); + } + + foreach (IEdmNavigationProperty potentialPartner in targetEntityType.NavigationProperties()) + { + if (potentialPartner == this) + { + continue; + } + + if (potentialPartner.Partner == this) + { + return potentialPartner; + } + } + + return null; + } + + private IEdmTypeReference ComputeType() + { + bool wasCollection; + string typeName = this.navigationProperty.Type; + + const string CollectionPrefix = CsdlConstants.Value_Collection + "("; + if (typeName.StartsWith(CollectionPrefix, StringComparison.Ordinal) && typeName.EndsWith(")", StringComparison.Ordinal)) + { + wasCollection = true; + typeName = typeName.Substring(CollectionPrefix.Length, (typeName.Length - CollectionPrefix.Length) - 1); + } + else + { + wasCollection = false; + } + + IEdmEntityType targetType = this.declaringType.Context.FindType(typeName) as IEdmEntityType; + if (targetType == null) + { + targetType = new UnresolvedEntityType(typeName, this.Location); + } + + bool nullable = !wasCollection && (this.navigationProperty.Nullable ?? CsdlConstants.Default_Nullable); + + IEdmEntityTypeReference targetTypeReference = new EdmEntityTypeReference(targetType, nullable); + if (wasCollection) + { + return new EdmCollectionTypeReference(new EdmCollectionType(targetTypeReference)); + } + + return targetTypeReference; + } + + private IEdmReferentialConstraint ComputeReferentialConstraint() + { + if (this.navigationProperty.ReferentialConstraints.Any()) + { + return new EdmReferentialConstraint(this.navigationProperty.ReferentialConstraints.Select(this.ComputeReferentialConstraintPropertyPair)); + } + + return null; + } + + private EdmReferentialConstraintPropertyPair ComputeReferentialConstraintPropertyPair(CsdlReferentialConstraint csdlConstraint) + { + // + // ... + // + // + // + // + // + // the above CategoryID is DependentProperty, ID is PrincipalProperty. + IEdmStructuralProperty dependentProperty = this.declaringType.FindProperty(csdlConstraint.PropertyName) as IEdmStructuralProperty ?? new UnresolvedProperty(this.declaringType, csdlConstraint.PropertyName, csdlConstraint.Location); + IEdmStructuralProperty principalProperty = this.TargetEntityType.FindProperty(csdlConstraint.ReferencedPropertyName) as IEdmStructuralProperty ?? new UnresolvedProperty(this.ToEntityType(), csdlConstraint.ReferencedPropertyName, csdlConstraint.Location); + return new EdmReferentialConstraintPropertyPair(dependentProperty, principalProperty); + } + + private IEnumerable ComputeErrors() + { + List errors = null; + + if (this.Type.IsCollection() && this.navigationProperty.Nullable.HasValue) + { + // TODO: this should happen at parsing time, which should remove + // the code for handling type-ref based collection types and unify this parsing logic + errors = AllocateAndAdd(errors, new EdmError(this.Location, EdmErrorCode.NavigationPropertyWithCollectionTypeCannotHaveNullableAttribute, Strings.CsdlParser_CannotSpecifyNullableAttributeForNavigationPropertyWithCollectionType)); + } + + var badType = this.TargetEntityType as BadEntityType; + if (badType != null) + { + errors = AllocateAndAdd(errors, badType.Errors); + } + + return errors ?? Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationPropertyPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationPropertyPathExpression.cs new file mode 100644 index 0000000..cd96412 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationPropertyPathExpression.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Navigation Property Path expression. + /// + internal class CsdlSemanticsNavigationPropertyPathExpression : CsdlSemanticsPathExpression + { + public CsdlSemanticsNavigationPropertyPathExpression(CsdlPathExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(expression, bindingContext, schema) + { + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.NavigationPropertyPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationSource.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationSource.cs new file mode 100644 index 0000000..a2d9488 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNavigationSource.cs @@ -0,0 +1,223 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + using System; +#if PORTABLELIB + using System.Collections.Concurrent; +#endif + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Csdl.Parsing.Ast; + using Microsoft.OData.Edm.Vocabularies; + + /// + /// Provides semantics for CsdlAbstractNavigationSource. + /// + internal abstract class CsdlSemanticsNavigationSource : CsdlSemanticsElement, IEdmNavigationSource + { + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1304:NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter", Justification = "protected field in internal class.")] + protected readonly CsdlAbstractNavigationSource navigationSource; + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1304:NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter", Justification = "protected field in internal class.")] + protected readonly CsdlSemanticsEntityContainer container; + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1304:NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter", Justification = "protected field in internal class.")] + protected readonly IEdmPathExpression path; + + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1304:NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter", Justification = "protected field in internal class.")] + protected readonly Cache typeCache = new Cache(); + protected static readonly Func ComputeElementTypeFunc = (me) => me.ComputeElementType(); + + private readonly Cache> navigationTargetsCache = new Cache>(); + private static readonly Func> ComputeNavigationTargetsFunc = (me) => me.ComputeNavigationTargets(); + +#if PORTABLELIB + private readonly ConcurrentDictionary containedNavigationPropertyCache = + new ConcurrentDictionary(); + + private readonly ConcurrentDictionary unknownNavigationPropertyCache = + new ConcurrentDictionary(); +#else + private readonly Dictionary containedNavigationPropertyCache = + new Dictionary(); + + private readonly Dictionary unknownNavigationPropertyCache = + new Dictionary(); +#endif + + public CsdlSemanticsNavigationSource(CsdlSemanticsEntityContainer container, CsdlAbstractNavigationSource navigationSource) + : base(navigationSource) + { + this.container = container; + this.navigationSource = navigationSource; + this.path = new EdmPathExpression(this.navigationSource.Name); + } + + public override CsdlSemanticsModel Model + { + get { return this.container.Model; } + } + + public IEdmEntityContainer Container + { + get { return this.container; } + } + + public override CsdlElement Element + { + get { return this.navigationSource; } + } + + public string Name + { + get { return this.navigationSource.Name; } + } + + public IEdmPathExpression Path + { + get { return this.path; } + } + + public abstract IEdmType Type { get; } + + public abstract EdmContainerElementKind ContainerElementKind { get; } + + public IEnumerable NavigationPropertyBindings + { + get { return this.navigationTargetsCache.GetValue(this, ComputeNavigationTargetsFunc, null); } + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty property, IEdmPathExpression bindingPath) + { + EdmUtil.CheckArgumentNull(property, "property"); + + if (!property.ContainsTarget && bindingPath != null) + { + foreach (IEdmNavigationPropertyBinding targetMapping in this.NavigationPropertyBindings) + { + if (targetMapping.NavigationProperty == property && targetMapping.Path.Path == bindingPath.Path) + { + return targetMapping.Target; + } + } + } + else if (property.ContainsTarget) + { + return EdmUtil.DictionaryGetOrUpdate( + this.containedNavigationPropertyCache, + property, + navProperty => new EdmContainedEntitySet(this, navProperty)); + } + + return EdmUtil.DictionaryGetOrUpdate( + this.unknownNavigationPropertyCache, + property, + navProperty => new EdmUnknownEntitySet(this, navProperty)); + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty) + { + bool isDerived = !this.Type.AsElementType().IsOrInheritsFrom(navigationProperty.DeclaringType); + + IEdmPathExpression bindingPath = isDerived + ? new EdmPathExpression(navigationProperty.DeclaringType.FullTypeName(), navigationProperty.Name) + : new EdmPathExpression(navigationProperty.Name); + + return FindNavigationTarget(navigationProperty, bindingPath); + } + + public IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + if (!navigationProperty.ContainsTarget) + { + return this.NavigationPropertyBindings.Where(targetMapping => targetMapping.NavigationProperty == navigationProperty).ToList(); + } + + return null; + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.container.Context); + } + + protected abstract IEdmEntityType ComputeElementType(); + + private IEnumerable ComputeNavigationTargets() + { + return this.navigationSource.NavigationPropertyBindings.Select(this.CreateSemanticMappingForBinding).ToList(); + } + + private IEdmNavigationPropertyBinding CreateSemanticMappingForBinding(CsdlNavigationPropertyBinding binding) + { + IEdmNavigationProperty navigationProperty = this.ResolveNavigationPropertyPathForBinding(binding); + + IEdmNavigationSource targetNavigationSource = this.Container.FindNavigationSourceExtended(binding.Target); + if (targetNavigationSource == null) + { + targetNavigationSource = this.Container.FindSingletonExtended(binding.Target); + if (targetNavigationSource == null) + { + targetNavigationSource = new UnresolvedEntitySet(binding.Target, this.Container, binding.Location); + } + } + + return new EdmNavigationPropertyBinding(navigationProperty, targetNavigationSource, new EdmPathExpression(binding.Path)); + } + + private IEdmNavigationProperty ResolveNavigationPropertyPathForBinding(CsdlNavigationPropertyBinding binding) + { + Debug.Assert(binding != null); + Debug.Assert(binding.Path != null); + var pathSegments = binding.Path.Split('/'); + IEdmStructuredType definingType = this.typeCache.GetValue(this, ComputeElementTypeFunc, null); + for (int index = 0; index < pathSegments.Length - 1; index++) + { + string segment = pathSegments[index]; + if (segment.IndexOf('.') < 0) + { + var property = definingType.FindProperty(segment); + if (property == null) + { + return new UnresolvedNavigationPropertyPath(definingType, binding.Path, binding.Location); + } + + var navProperty = property as IEdmNavigationProperty; + if (navProperty != null && !navProperty.ContainsTarget) + { + // TODO: Improve error message #644. + return new UnresolvedNavigationPropertyPath(definingType, binding.Path, binding.Location); + } + + definingType = property.Type.Definition.AsElementType() as IEdmStructuredType; + if (definingType == null) + { + // TODO: Improve error message #644. + return new UnresolvedNavigationPropertyPath(definingType, binding.Path, binding.Location); + } + } + else + { + var derivedType = container.Context.FindType(segment) as IEdmStructuredType; + if (derivedType == null || !derivedType.IsOrInheritsFrom(definingType)) + { + // TODO: Improve error message #644. + return new UnresolvedNavigationPropertyPath(definingType, binding.Path, binding.Location); + } + + definingType = derivedType; + } + } + + return definingType.FindProperty(pathSegments.Last()) as IEdmNavigationProperty + ?? new UnresolvedNavigationPropertyPath(definingType, binding.Path, binding.Location); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNullExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNullExpression.cs new file mode 100644 index 0000000..1e75389 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsNullExpression.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl null constant expression. + /// + internal class CsdlSemanticsNullExpression : CsdlSemanticsExpression, IEdmNullExpression + { + private readonly CsdlConstantExpression expression; + + public CsdlSemanticsNullExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Null; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEdmTypeReference Type + { + get { return null; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperation.cs new file mode 100644 index 0000000..44e822b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperation.cs @@ -0,0 +1,285 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlOperation + /// + internal abstract class CsdlSemanticsOperation : CsdlSemanticsElement, IEdmOperation, IEdmFullNamedElement + { + private readonly string fullName; + private readonly CsdlOperation operation; + private readonly Cache entitySetPathCache = new Cache(); + private static readonly Func ComputeEntitySetPathFunc = (me) => me.ComputeEntitySetPath(); + + private readonly Cache returnCache = new Cache(); + private static readonly Func ComputeReturnFunc = (me) => me.ComputeReturn(); + + private readonly Cache> parametersCache = new Cache>(); + private static readonly Func> ComputeParametersFunc = (me) => me.ComputeParameters(); + + public CsdlSemanticsOperation(CsdlSemanticsSchema context, CsdlOperation operation) + : base(operation) + { + this.Context = context; + this.operation = operation; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.Context?.Namespace, this.operation?.Name); + } + + public abstract EdmSchemaElementKind SchemaElementKind { get; } + + public override CsdlSemanticsModel Model + { + get { return this.Context.Model; } + } + + public string Name + { + get { return this.operation.Name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public override CsdlElement Element + { + get { return this.operation; } + } + + public string Namespace + { + get { return this.Context.Namespace; } + } + + public bool IsBound + { + get { return this.operation.IsBound; } + } + + public IEdmPathExpression EntitySetPath + { + get + { + return this.entitySetPathCache.GetValue(this, ComputeEntitySetPathFunc, null); + } + } + + public IEdmTypeReference ReturnType + { + get + { + if (this.operation.Return == null) + { + return null; + } + + return Return.Type; + } + } + + public IEdmOperationReturn Return + { + get { return this.returnCache.GetValue(this, ComputeReturnFunc, null); } + } + + public IEnumerable Parameters + { + get { return this.parametersCache.GetValue(this, ComputeParametersFunc, null); } + } + + public CsdlSemanticsSchema Context { get; private set; } + + public IEdmOperationParameter FindParameter(string name) + { + return this.Parameters.SingleOrDefault(p => p.Name == name); + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.Context); + } + + private IEdmPathExpression ComputeEntitySetPath() + { + if (this.operation.EntitySetPath != null) + { + return new OperationPathExpression(this.operation.EntitySetPath) { Location = this.Location }; + } + + return null; + } + + private IEdmOperationReturn ComputeReturn() + { + if (this.operation.Return == null) + { + return null; + } + + return new CsdlSemanticsOperationReturn(this, this.operation.Return); + } + + private IEnumerable ComputeParameters() + { + List parameters = new List(); + + foreach (var parameter in this.operation.Parameters) + { + if (parameter.IsOptional) + { + parameters.Add(new CsdlSemanticsOptionalParameter(this, parameter, parameter.DefaultValue)); + } + else + { + parameters.Add(new CsdlSemanticsOperationParameter(this, parameter)); + } + } + + // Handle the out-of-line optinal parameter annotation for parameters. + // First, use the above built parameters to create the full target name, for example: NS.TestFunction(Edm.String, Edm.String, Edm.String) + // Then, go through each prameters by visiting the out-of-line optional parameter annotation. + // If we find at least one of out-of-line optional parameter annotation, we replace it as an optional parameter. + // Otherwise, re-use the built parameter. + // Be noted: if a parameter has inline and out-of-line optional parameter, the out-of-line will win. + string fullName = Namespace + "." + Name; + string fullParametersName = ParameterizedTargetName(parameters); + + List newParameters = new List(parameters.Count); + foreach (var parameter in parameters) + { + // for example: NS.TestFunction(Edm.String, Edm.String, Edm.String)/OptionalParameter + string fullTargetName = fullName + fullParametersName + "/" + parameter.Name; + + // for example: NS.TestFunction/OptionalParameter + string targetName = fullName + "/" + parameter.Name; + + string defaultValue; + + if (TryGetOptionalParameterOutOfLineAnnotation(fullTargetName, targetName, out defaultValue)) + { + CsdlSemanticsOperationParameter csdlSemanticsParameter = (CsdlSemanticsOperationParameter)parameter; + newParameters.Add(new CsdlSemanticsOptionalParameter(this, (CsdlOperationParameter)csdlSemanticsParameter.Element, defaultValue)); + } + else + { + newParameters.Add(parameter); + } + } + + return newParameters; + } + + private bool TryGetOptionalParameterOutOfLineAnnotation(string fullTargetName, string targetName, out string defaultValue) + { + defaultValue = null; + bool isOptional = false; + + List annotations; + + // OData 4.0 applies annotations to all overloads of a function or action. + // We still need to support annotating the non-overloaded version. + // So, we should probably first check the fullTargetName and, if that doesn't match, check just the function or action name. + bool found = Model.OutOfLineAnnotations.TryGetValue(fullTargetName, out annotations) ? true : + Model.OutOfLineAnnotations.TryGetValue(targetName, out annotations); + + if (found) + { + foreach (var annotation in annotations) + { + var optionalParameterAnnotation = annotation.Annotations.Annotations.FirstOrDefault(a => + a.Term == CoreVocabularyModel.OptionalParameterTerm.ShortQualifiedName() || + a.Term == CoreVocabularyModel.OptionalParameterTerm.FullName()); + + if (optionalParameterAnnotation != null) + { + isOptional = true; + + CsdlRecordExpression optionalValueExpression = optionalParameterAnnotation.Expression as CsdlRecordExpression; + if (optionalValueExpression != null) + { + foreach (CsdlPropertyValue property in optionalValueExpression.PropertyValues) + { + if (property.Property == CsdlConstants.Attribute_DefaultValue) + { + CsdlConstantExpression propertyValue = property.Expression as CsdlConstantExpression; + if (propertyValue != null) + { + defaultValue = propertyValue.Value; + } + } + } + } + + break; + } + } + } + + return isOptional; + } + + internal static string ParameterizedTargetName(IList parameters) + { + int index = 0; + int parameterCount = parameters.Count(); + + StringBuilder sb = new StringBuilder("("); + foreach (IEdmOperationParameter parameter in parameters) + { + string typeName = ""; + if (parameter.Type == null) + { + typeName = CsdlConstants.TypeName_Untyped; + } + else if (parameter.Type.IsCollection()) + { + typeName = CsdlConstants.Value_Collection + "(" + parameter.Type.AsCollection().ElementType().FullName() + ")"; + } + else if (parameter.Type.IsEntityReference()) + { + typeName = CsdlConstants.Value_Ref + "(" + parameter.Type.AsEntityReference().EntityType().FullName() + ")"; + } + else + { + typeName = parameter.Type.FullName(); + } + + sb.Append(typeName); + index++; + if (index < parameterCount) + { + sb.Append(", "); + } + } + + sb.Append(")"); + return sb.ToString(); + } + + private sealed class OperationPathExpression : EdmPathExpression, IEdmLocatable + { + internal OperationPathExpression(string path) : base(path) + { + } + + public EdmLocation Location { get; set; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationImport.cs new file mode 100644 index 0000000..5eda1af --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationImport.cs @@ -0,0 +1,91 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal abstract class CsdlSemanticsOperationImport : CsdlSemanticsElement, IEdmOperationImport + { + private readonly CsdlOperationImport operationImport; + private readonly CsdlSemanticsEntityContainer container; + + private readonly Cache entitySetCache = new Cache(); + private static readonly Func ComputeEntitySetFunc = (me) => me.ComputeEntitySet(); + + protected CsdlSemanticsOperationImport(CsdlSemanticsEntityContainer container, CsdlOperationImport operationImport, IEdmOperation operation) + : base(operationImport) + { + this.container = container; + this.operationImport = operationImport; + this.Operation = operation; + } + + public IEdmOperation Operation { get; private set; } + + public override CsdlSemanticsModel Model + { + get { return this.container.Context.Model; } + } + + public override CsdlElement Element + { + get { return this.operationImport; } + } + + public string Name + { + get { return this.operationImport.Name; } + } + + public IEdmEntityContainer Container + { + get { return this.container; } + } + + public IEdmExpression EntitySet + { + get + { + return this.entitySetCache.GetValue(this, ComputeEntitySetFunc, null); + } + } + + public abstract EdmContainerElementKind ContainerElementKind { get; } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.container.Context); + } + + private IEdmExpression ComputeEntitySet() + { + if (this.operationImport.EntitySet != null) + { + return new OperationImportPathExpression(this.operationImport.EntitySet) { Location = this.Location }; + } + + return null; + } + + private sealed class OperationImportPathExpression : EdmPathExpression, IEdmLocatable + { + internal OperationImportPathExpression(string path) + : base(path) + { + } + + public EdmLocation Location + { + get; + set; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationParameter.cs new file mode 100644 index 0000000..8828649 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationParameter.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlOperationParameter. + /// + internal class CsdlSemanticsOperationParameter : CsdlSemanticsElement, IEdmOperationParameter + { + private readonly CsdlSemanticsOperation declaringOperation; + private readonly CsdlOperationParameter parameter; + + private readonly Cache typeCache = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public CsdlSemanticsOperationParameter(CsdlSemanticsOperation declaringOperation, CsdlOperationParameter parameter) + : base(parameter) + { + this.parameter = parameter; + this.declaringOperation = declaringOperation; + } + + public override CsdlSemanticsModel Model + { + get { return this.declaringOperation.Model; } + } + + public override CsdlElement Element + { + get { return this.parameter; } + } + + public IEdmTypeReference Type + { + get { return this.typeCache.GetValue(this, ComputeTypeFunc, null); } + } + + public string Name + { + get { return this.parameter.Name; } + } + + public IEdmOperation DeclaringOperation + { + get { return this.declaringOperation; } + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.declaringOperation.Context); + } + + private IEdmTypeReference ComputeType() + { + return CsdlSemanticsModel.WrapTypeReference(this.declaringOperation.Context, this.parameter.Type); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationReturn.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationReturn.cs new file mode 100644 index 0000000..8940093 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOperationReturn.cs @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlOperationReturn. + /// + internal class CsdlSemanticsOperationReturn : CsdlSemanticsElement, IEdmOperationReturn + { + private readonly CsdlSemanticsOperation declaringOperation; + private readonly CsdlOperationReturn operationReturn; + + private readonly Cache typeCache = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public CsdlSemanticsOperationReturn(CsdlSemanticsOperation declaringOperation, CsdlOperationReturn operationReturn) + : base(operationReturn) + { + this.declaringOperation = declaringOperation; + this.operationReturn = operationReturn; + } + + public override CsdlSemanticsModel Model + { + get { return this.declaringOperation.Model; } + } + + public override CsdlElement Element + { + get { return this.operationReturn; } + } + + public IEdmTypeReference Type + { + get { return this.typeCache.GetValue(this, ComputeTypeFunc, null); } + } + + public IEdmOperation DeclaringOperation + { + get { return this.declaringOperation; } + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.declaringOperation.Context); + } + + private IEdmTypeReference ComputeType() + { + return CsdlSemanticsModel.WrapTypeReference(this.declaringOperation.Context, this.operationReturn.ReturnType); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOptionalParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOptionalParameter.cs new file mode 100644 index 0000000..ea7dfd9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsOptionalParameter.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlSemanticsOptionalParameter. + /// + internal class CsdlSemanticsOptionalParameter : CsdlSemanticsOperationParameter, IEdmOptionalParameter + { + public CsdlSemanticsOptionalParameter(CsdlSemanticsOperation declaringOperation, CsdlOperationParameter parameter, string defaultValue) + : base(declaringOperation, parameter) + { + this.DefaultValueString = defaultValue; + } + + public string DefaultValueString { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPathExpression.cs new file mode 100644 index 0000000..09c6699 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPathExpression.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Path expression. + /// + internal class CsdlSemanticsPathExpression : CsdlSemanticsExpression, IEdmPathExpression + { + protected readonly CsdlPathExpression Expression; + + protected readonly IEdmEntityType BindingContext; + + protected readonly Cache> PathCache = new Cache>(); + + protected static readonly Func> ComputePathFunc = (me) => me.ComputePath(); + + public CsdlSemanticsPathExpression(CsdlPathExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.Expression = expression; + this.BindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.Expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Path; } + } + + public IEnumerable PathSegments + { + get { return this.PathCache.GetValue(this, ComputePathFunc, null); } + } + + public string Path + { + get { return this.Expression.Path; } + } + + private IEnumerable ComputePath() + { + return this.Expression.Path.Split(new char[] { '/' }, StringSplitOptions.None); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPrimitiveTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPrimitiveTypeReference.cs new file mode 100644 index 0000000..c67bca9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPrimitiveTypeReference.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides the semantics of a reference to an EDM primitive type. + /// + internal class CsdlSemanticsPrimitiveTypeReference : CsdlSemanticsElement, IEdmPrimitiveTypeReference + { + internal readonly CsdlPrimitiveTypeReference Reference; + private readonly CsdlSemanticsSchema schema; + + /// + /// This doesn't need the full caching mechanism because the computation is cheap, and the likelihood of computing a primitive type reference without needing its definition is remote. + /// + private readonly IEdmPrimitiveType definition; + + public CsdlSemanticsPrimitiveTypeReference(CsdlSemanticsSchema schema, CsdlPrimitiveTypeReference reference) + : base(reference) + { + this.schema = schema; + this.Reference = reference; + this.definition = EdmCoreModel.Instance.GetPrimitiveType(this.Reference.Kind); + } + + public bool IsNullable + { + get { return this.Reference.IsNullable; } + } + + public IEdmType Definition + { + get { return this.definition; } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + + public override CsdlElement Element + { + get { return this.Reference; } + } + + public override string ToString() + { + return this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsProperty.cs new file mode 100644 index 0000000..038a9a3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsProperty.cs @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlProperty. + /// + internal class CsdlSemanticsProperty : CsdlSemanticsElement, IEdmStructuralProperty + { + protected CsdlProperty property; + private readonly CsdlSemanticsStructuredTypeDefinition declaringType; + + private readonly Cache typeCache = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public CsdlSemanticsProperty(CsdlSemanticsStructuredTypeDefinition declaringType, CsdlProperty property) + : base(property) + { + this.property = property; + this.declaringType = declaringType; + } + + public string Name + { + get { return this.property.Name; } + } + + public IEdmStructuredType DeclaringType + { + get { return this.declaringType; } + } + + public IEdmTypeReference Type + { + get { return this.typeCache.GetValue(this, ComputeTypeFunc, null); } + } + + public override CsdlSemanticsModel Model + { + get { return this.declaringType.Model; } + } + + public string DefaultValueString + { + get { return this.property.DefaultValue; } + } + + public EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.Structural; } + } + + public override CsdlElement Element + { + get { return this.property; } + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.declaringType.Context); + } + + private IEdmTypeReference ComputeType() + { + return CsdlSemanticsModel.WrapTypeReference(this.declaringType.Context, this.property.Type); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyConstructor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyConstructor.cs new file mode 100644 index 0000000..d9c4be3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyConstructor.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlPropertyValue used in a record expression. + /// + internal class CsdlSemanticsPropertyConstructor : CsdlSemanticsElement, IEdmPropertyConstructor + { + private readonly CsdlPropertyValue property; + private readonly CsdlSemanticsRecordExpression context; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + public CsdlSemanticsPropertyConstructor(CsdlPropertyValue property, CsdlSemanticsRecordExpression context) + : base(property) + { + this.property = property; + this.context = context; + } + + public string Name + { + get { return this.property.Property; } + } + + public IEdmExpression Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public override CsdlElement Element + { + get { return this.property; } + } + + public override CsdlSemanticsModel Model + { + get { return this.context.Model; } + } + + private IEdmExpression ComputeValue() + { + return CsdlSemanticsModel.WrapExpression(this.property.Expression, this.context.BindingContext, this.context.Schema); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyPathExpression.cs new file mode 100644 index 0000000..e8f5bd4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsPropertyPathExpression.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Property Path expression. + /// + internal class CsdlSemanticsPropertyPathExpression : CsdlSemanticsPathExpression + { + public CsdlSemanticsPropertyPathExpression(CsdlPathExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(expression, bindingContext, schema) + { + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.PropertyPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsRecordExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsRecordExpression.cs new file mode 100644 index 0000000..6f72e38 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsRecordExpression.cs @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl Record expression. + /// + internal class CsdlSemanticsRecordExpression : CsdlSemanticsExpression, IEdmRecordExpression + { + private readonly CsdlRecordExpression expression; + private readonly IEdmEntityType bindingContext; + + private readonly Cache declaredTypeCache = new Cache(); + private static readonly Func ComputeDeclaredTypeFunc = (me) => me.ComputeDeclaredType(); + + private readonly Cache> propertiesCache = new Cache>(); + private static readonly Func> ComputePropertiesFunc = (me) => me.ComputeProperties(); + + public CsdlSemanticsRecordExpression(CsdlRecordExpression expression, IEdmEntityType bindingContext, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + this.bindingContext = bindingContext; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Record; } + } + + public IEdmStructuredTypeReference DeclaredType + { + get { return this.declaredTypeCache.GetValue(this, ComputeDeclaredTypeFunc, null); } + } + + public IEnumerable Properties + { + get { return this.propertiesCache.GetValue(this, ComputePropertiesFunc, null); } + } + + public IEdmEntityType BindingContext + { + get { return this.bindingContext; } + } + + private IEnumerable ComputeProperties() + { + List properties = new List(); + + foreach (CsdlPropertyValue propertyValue in this.expression.PropertyValues) + { + properties.Add(new CsdlSemanticsPropertyConstructor(propertyValue, this)); + } + + return properties; + } + + private IEdmStructuredTypeReference ComputeDeclaredType() + { + return this.expression.Type != null ? CsdlSemanticsModel.WrapTypeReference(this.Schema, this.expression.Type).AsStructured() : null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSchema.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSchema.cs new file mode 100644 index 0000000..86826b6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSchema.cs @@ -0,0 +1,471 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlSchema. + /// + internal class CsdlSemanticsSchema : CsdlSemanticsElement, IEdmCheckable + { + private readonly CsdlSemanticsModel model; + private readonly CsdlSchema schema; + + private readonly Cache> typesCache = new Cache>(); + private static readonly Func> ComputeTypesFunc = (me) => me.ComputeTypes(); + + private readonly Cache> operationsCache = new Cache>(); + private static readonly Func> ComputeFunctionsFunc = (me) => me.ComputeOperations(); + + private readonly Cache> entityContainersCache = new Cache>(); + private static readonly Func> ComputeEntityContainersFunc = (me) => me.ComputeEntityContainers(); + + private readonly Cache> termsCache = new Cache>(); + private static readonly Func> ComputeTermsFunc = (me) => me.ComputeTerms(); + + private readonly Cache> labeledExpressionsCache = new Cache>(); + private static readonly Func> ComputeLabeledExpressionsFunc = (me) => me.ComputeLabeledExpressions(); + + private readonly Dictionary semanticsLabeledElements = new Dictionary(); + private readonly Dictionary, IEdmLabeledExpression> ambiguousLabeledExpressions = new Dictionary, IEdmLabeledExpression>(); + + public CsdlSemanticsSchema(CsdlSemanticsModel model, CsdlSchema schema) + : base(schema) + { + this.model = model; + this.schema = schema; + } + + public override CsdlSemanticsModel Model + { + get { return this.model; } + } + + public override CsdlElement Element + { + get { return this.schema; } + } + + public IEnumerable Types + { + get { return this.typesCache.GetValue(this, ComputeTypesFunc, null); } + } + + public IEnumerable Operations + { + get { return this.operationsCache.GetValue(this, ComputeFunctionsFunc, null); } + } + + public IEnumerable Terms + { + get { return this.termsCache.GetValue(this, ComputeTermsFunc, null); } + } + + public IEnumerable EntityContainers + { + get { return this.entityContainersCache.GetValue(this, ComputeEntityContainersFunc, null); } + } + + public string Namespace + { + get { return this.schema.Namespace; } + } + + public IEnumerable Errors + { + get + { + return Enumerable.Empty(); + } + } + + /// + /// Gets the labeled element expressions dictionary. + /// Each value in the dictionary is either a or a list of same. + /// + private Dictionary LabeledExpressions + { + get { return this.labeledExpressionsCache.GetValue(this, ComputeLabeledExpressionsFunc, null); } + } + + public IEnumerable FindOperations(string name) + { + return FindSchemaElement>(name, ExtensionMethods.FindOperationsInModelTree); + } + + public IEdmSchemaType FindType(string name) + { + return FindSchemaElement(name, ExtensionMethods.FindTypeInModelTree); + } + + public IEdmTerm FindTerm(string name) + { + return FindSchemaElement(name, FindTerm); + } + + public IEdmEntityContainer FindEntityContainer(string name) + { + return FindSchemaElement(name, FindEntityContainer); + } + + public T FindSchemaElement(string name, Func modelFinder) + { + string namespaceQualifiedName = this.ReplaceAlias(name); + return modelFinder(this.model, namespaceQualifiedName); + } + + public string UnresolvedName(string qualifiedName) + { + if (qualifiedName == null) + { + return null; + } + + return this.ReplaceAlias(qualifiedName); + } + + public IEdmLabeledExpression FindLabeledElement(string label, IEdmEntityType bindingContext) + { + object labeledElement; + if (this.LabeledExpressions.TryGetValue(label, out labeledElement)) + { + CsdlLabeledExpression labeledElementExpression = labeledElement as CsdlLabeledExpression; + if (labeledElementExpression != null) + { + return this.WrapLabeledElement(labeledElementExpression, bindingContext); + } + + return this.WrapLabeledElementList((List)labeledElement, bindingContext); + } + + return null; + } + + public IEdmLabeledExpression WrapLabeledElement(CsdlLabeledExpression labeledElement, IEdmEntityType bindingContext) + { + IEdmLabeledExpression result; + + // Guarantee that multiple requests to wrap a given labeled element all return the same object. + if (!this.semanticsLabeledElements.TryGetValue(labeledElement, out result)) + { + result = new CsdlSemanticsLabeledExpression(labeledElement.Label, labeledElement.Element, bindingContext, this); + this.semanticsLabeledElements[labeledElement] = result; + } + + return result; + } + + internal string ReplaceAlias(string name) + { + return this.model.ReplaceAlias(name); + } + + private static IEdmTerm FindTerm(IEdmModel model, string name) + { + return model.FindTerm(name); + } + + private static IEdmEntityContainer FindEntityContainer(IEdmModel model, string name) + { + return model.FindEntityContainer(name); + } + + private static void AddLabeledExpressions(CsdlExpressionBase expression, Dictionary result) + { + if (expression == null) + { + return; + } + + switch (expression.ExpressionKind) + { + case EdmExpressionKind.Labeled: + { + CsdlLabeledExpression labeledElement = (CsdlLabeledExpression)expression; + string label = labeledElement.Label; + object duplicateLabeledElement; + if (result.TryGetValue(label, out duplicateLabeledElement)) + { + // If the label has multiple definitions, store the duplicates as a list of labeled elements. + List duplicates = duplicateLabeledElement as List; + if (duplicates == null) + { + duplicates = new List(); + duplicates.Add((CsdlLabeledExpression)duplicateLabeledElement); + result[label] = duplicates; + } + + duplicates.Add(labeledElement); + } + else + { + result[label] = labeledElement; + } + + AddLabeledExpressions(labeledElement.Element, result); + break; + } + + case EdmExpressionKind.Collection: + foreach (CsdlExpressionBase element in ((CsdlCollectionExpression)expression).ElementValues) + { + AddLabeledExpressions(element, result); + } + + break; + case EdmExpressionKind.FunctionApplication: + foreach (CsdlExpressionBase argument in ((CsdlApplyExpression)expression).Arguments) + { + AddLabeledExpressions(argument, result); + } + + break; + case EdmExpressionKind.Record: + foreach (CsdlPropertyValue property in ((CsdlRecordExpression)expression).PropertyValues) + { + AddLabeledExpressions(property.Expression, result); + } + + break; + case EdmExpressionKind.If: + { + CsdlIfExpression ifExpression = (CsdlIfExpression)expression; + AddLabeledExpressions(ifExpression.Test, result); + AddLabeledExpressions(ifExpression.IfTrue, result); + AddLabeledExpressions(ifExpression.IfFalse, result); + + break; + } + + case EdmExpressionKind.IsType: + AddLabeledExpressions(((CsdlIsTypeExpression)expression).Operand, result); + break; + case EdmExpressionKind.Cast: + AddLabeledExpressions(((CsdlCastExpression)expression).Operand, result); + break; + default: + break; + } + } + + private static void AddLabeledExpressions(IEnumerable annotations, Dictionary result) + { + foreach (CsdlAnnotation annotation in annotations) + { + if (annotation != null) + { + AddLabeledExpressions(annotation.Expression, result); + } + } + } + + private IEdmLabeledExpression WrapLabeledElementList(List labeledExpressions, IEdmEntityType bindingContext) + { + IEdmLabeledExpression result; + + // Guarantee that multiple requests to wrap a given labeled element all return the same object. + if (!this.ambiguousLabeledExpressions.TryGetValue(labeledExpressions, out result)) + { + foreach (CsdlLabeledExpression labeledExpression in labeledExpressions) + { + IEdmLabeledExpression wrappedExpression = this.WrapLabeledElement(labeledExpression, bindingContext); + result = + result == null + ? wrappedExpression + : new AmbiguousLabeledExpressionBinding(result, wrappedExpression); + } + + this.ambiguousLabeledExpressions[labeledExpressions] = result; + } + + return result; + } + + private IEnumerable ComputeTerms() + { + List terms = new List(); + foreach (CsdlTerm valueTerm in this.schema.Terms) + { + terms.Add(new CsdlSemanticsTerm(this, valueTerm)); + } + + return terms; + } + + private IEnumerable ComputeEntityContainers() + { + List entityContainers = new List(); + foreach (CsdlEntityContainer entityContainer in this.schema.EntityContainers) + { + entityContainers.Add(new CsdlSemanticsEntityContainer(this, entityContainer)); + } + + return entityContainers; + } + + private IEnumerable ComputeOperations() + { + List operations = new List(); + foreach (CsdlOperation operation in this.schema.Operations) + { + CsdlAction action = operation as CsdlAction; + if (action != null) + { + operations.Add(new CsdlSemanticsAction(this, action)); + } + else + { + CsdlFunction function = operation as CsdlFunction; + Debug.Assert(function != null, "function != null"); + operations.Add(new CsdlSemanticsFunction(this, function)); + } + } + + return operations; + } + + private IEnumerable ComputeTypes() + { + List types = new List(); + + foreach (var typeDefinition in schema.TypeDefinitions) + { + CsdlSemanticsTypeDefinitionDefinition edmTypeDefinition = + new CsdlSemanticsTypeDefinitionDefinition(this, typeDefinition); + this.AttachDefaultPrimitiveValueConverter(typeDefinition, edmTypeDefinition); + types.Add(edmTypeDefinition); + } + + foreach (var structuredType in this.schema.StructuredTypes) + { + CsdlEntityType entity = structuredType as CsdlEntityType; + if (entity != null) + { + types.Add(new CsdlSemanticsEntityTypeDefinition(this, entity)); + } + else + { + CsdlComplexType complex = structuredType as CsdlComplexType; + if (complex != null) + { + types.Add(new CsdlSemanticsComplexTypeDefinition(this, complex)); + } + } + } + + foreach (var enumType in this.schema.EnumTypes) + { + types.Add(new CsdlSemanticsEnumTypeDefinition(this, enumType)); + } + + return types; + } + + /// + /// Attach DefaultPrimitiveValueConverter to the model if the name and the underlying type of the given type definition + /// matches the default unsigned int type definitions defined in . + /// + /// The type definition to be added to the schema. + /// The EDM type definition to be added to the model. + private void AttachDefaultPrimitiveValueConverter(CsdlTypeDefinition typeDefinition, IEdmTypeDefinition edmTypeDefinition) + { + Debug.Assert(typeDefinition != null, "typeDefinition != null"); + + string defaultUnderlyingType; + switch (typeDefinition.Name) + { + case PrimitiveValueConverterConstants.UInt16TypeName: + defaultUnderlyingType = PrimitiveValueConverterConstants.DefaultUInt16UnderlyingType; + break; + case PrimitiveValueConverterConstants.UInt32TypeName: + defaultUnderlyingType = PrimitiveValueConverterConstants.DefaultUInt32UnderlyingType; + break; + case PrimitiveValueConverterConstants.UInt64TypeName: + defaultUnderlyingType = PrimitiveValueConverterConstants.DefaultUInt64UnderlyingType; + break; + default: + // Not unsigned int type definition. + return; + } + + if (String.CompareOrdinal(defaultUnderlyingType, typeDefinition.UnderlyingTypeName) != 0) + { + // Not default underlying type for unsigned int. + return; + } + + this.Model.SetPrimitiveValueConverter(edmTypeDefinition, DefaultPrimitiveValueConverter.Instance); + } + + /// + /// All of the labeled expressions in a schema are collected into a dictionary so that references to them can be bound. + /// The elements of the dictionary are Csdl objects and not CsdlSemantics objects because the semantics objects are not created + /// until and unless necessary. + /// + /// A dictionary containing entries for all labeled expressions in the schema. + private Dictionary ComputeLabeledExpressions() + { + Dictionary result = new Dictionary(); + + foreach (CsdlAnnotations sourceAnnotations in this.schema.OutOfLineAnnotations) + { + AddLabeledExpressions(sourceAnnotations.Annotations, result); + } + + foreach (CsdlStructuredType schemaType in this.schema.StructuredTypes) + { + AddLabeledExpressions(schemaType.VocabularyAnnotations, result); + foreach (CsdlProperty property in schemaType.StructuralProperties) + { + AddLabeledExpressions(property.VocabularyAnnotations, result); + } + } + + foreach (CsdlOperation operation in this.schema.Operations) + { + AddLabeledExpressions(operation.VocabularyAnnotations, result); + foreach (CsdlOperationParameter parameter in operation.Parameters) + { + AddLabeledExpressions(parameter.VocabularyAnnotations, result); + } + } + + foreach (CsdlTerm terms in this.schema.Terms) + { + AddLabeledExpressions(terms.VocabularyAnnotations, result); + } + + foreach (CsdlEntityContainer container in this.schema.EntityContainers) + { + AddLabeledExpressions(container.VocabularyAnnotations, result); + foreach (CsdlEntitySet set in container.EntitySets) + { + AddLabeledExpressions(set.VocabularyAnnotations, result); + } + + foreach (CsdlOperationImport import in container.OperationImports) + { + AddLabeledExpressions(import.VocabularyAnnotations, result); + foreach (CsdlOperationParameter parameter in import.Parameters) + { + AddLabeledExpressions(parameter.VocabularyAnnotations, result); + } + } + } + + return result; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSingleton.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSingleton.cs new file mode 100644 index 0000000..cd42e18 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSingleton.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Csdl.Parsing.Ast; + + /// + /// Provides semantics for CsdlSingleton. + /// + internal class CsdlSemanticsSingleton : CsdlSemanticsNavigationSource, IEdmSingleton + { + public CsdlSemanticsSingleton(CsdlSemanticsEntityContainer container, CsdlSingleton singleton) + : base(container, singleton) + { + } + + public override IEdmType Type + { + get { return this.typeCache.GetValue(this, ComputeElementTypeFunc, null); } + } + + public override EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.Singleton; } + } + + protected override IEdmEntityType ComputeElementType() + { + string type = ((CsdlSingleton)this.navigationSource).Type; + return this.container.Context.FindType(type) as IEdmEntityType ?? new UnresolvedEntityType(type, this.Location); + } + } +} + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSpatialTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSpatialTypeReference.cs new file mode 100644 index 0000000..bbd46a6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsSpatialTypeReference.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class CsdlSemanticsSpatialTypeReference : CsdlSemanticsPrimitiveTypeReference, IEdmSpatialTypeReference + { + public CsdlSemanticsSpatialTypeReference(CsdlSemanticsSchema schema, CsdlSpatialTypeReference reference) + : base(schema, reference) + { + } + + public int? SpatialReferenceIdentifier + { + get { return ((CsdlSpatialTypeReference)this.Reference).Srid; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringConstantExpression.cs new file mode 100644 index 0000000..f020ca2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringConstantExpression.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl String constant expression. + /// + internal class CsdlSemanticsStringConstantExpression : CsdlSemanticsExpression, IEdmStringConstantExpression + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + public CsdlSemanticsStringConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public string Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.StringConstant; } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + private string ComputeValue() + { + // [EdmLib] Determine what escaping is necissary in String expression, or do not cache the value. + return this.expression.Value; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringTypeReference.cs new file mode 100644 index 0000000..6ab85f1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStringTypeReference.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides the semantics of a reference to an EDM String type. + /// + internal class CsdlSemanticsStringTypeReference : CsdlSemanticsPrimitiveTypeReference, IEdmStringTypeReference + { + public CsdlSemanticsStringTypeReference(CsdlSemanticsSchema schema, CsdlStringTypeReference reference) + : base(schema, reference) + { + } + + public bool IsUnbounded + { + get { return ((CsdlStringTypeReference)this.Reference).IsUnbounded; } + } + + public int? MaxLength + { + get { return ((CsdlStringTypeReference)this.Reference).MaxLength; } + } + + public bool? IsUnicode + { + get { return ((CsdlStringTypeReference)this.Reference).IsUnicode; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStructuredTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStructuredTypeDefinition.cs new file mode 100644 index 0000000..2a4a1bf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsStructuredTypeDefinition.cs @@ -0,0 +1,129 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlStructuredType. + /// + internal abstract class CsdlSemanticsStructuredTypeDefinition : CsdlSemanticsTypeDefinition, IEdmStructuredType + { + private readonly CsdlSemanticsSchema context; + + private readonly Cache> declaredPropertiesCache = new Cache>(); + private static readonly Func> ComputeDeclaredPropertiesFunc = (me) => me.ComputeDeclaredProperties(); + + private readonly Cache> propertiesDictionaryCache = new Cache>(); + private static readonly Func> ComputePropertiesDictionaryFunc = (me) => me.ComputePropertiesDictionary(); + + protected CsdlSemanticsStructuredTypeDefinition(CsdlSemanticsSchema context, CsdlStructuredType type) + : base(type) + { + this.context = context; + } + + public virtual bool IsAbstract + { + get { return false; } + } + + public virtual bool IsOpen + { + get { return false; } + } + + public abstract IEdmStructuredType BaseType + { + get; + } + + public override CsdlElement Element + { + get { return this.MyStructured; } + } + + public override CsdlSemanticsModel Model + { + get { return this.context.Model; } + } + + public string Namespace + { + get { return this.context.Namespace; } + } + + public CsdlSemanticsSchema Context + { + get { return this.context; } + } + + public IEnumerable DeclaredProperties + { + get { return this.declaredPropertiesCache.GetValue(this, ComputeDeclaredPropertiesFunc, null); } + } + + protected abstract CsdlStructuredType MyStructured + { + get; + } + + private IDictionary PropertiesDictionary + { + get { return this.propertiesDictionaryCache.GetValue(this, ComputePropertiesDictionaryFunc, null); } + } + + public IEdmProperty FindProperty(string name) + { + IEdmProperty result; + this.PropertiesDictionary.TryGetValue(name, out result); + return result; + } + + protected List ComputeDeclaredProperties() + { + List properties = new List(); + foreach (CsdlProperty property in this.MyStructured.StructuralProperties) + { + properties.Add(new CsdlSemanticsProperty(this, property)); + } + + foreach (CsdlNavigationProperty navigationProperty in this.MyStructured.NavigationProperties) + { + properties.Add(new CsdlSemanticsNavigationProperty(this, navigationProperty)); + } + + return properties; + } + + // Resolves the real name of the base type, in case it was using an alias before. + protected string GetCyclicBaseTypeName(string baseTypeName) + { + IEdmSchemaType schemaBaseType = this.context.FindType(baseTypeName); + return (schemaBaseType != null) ? schemaBaseType.FullName() : baseTypeName; + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.context); + } + + private IDictionary ComputePropertiesDictionary() + { + Dictionary properties = new Dictionary(); + foreach (IEdmProperty property in this.Properties()) + { + RegistrationHelper.RegisterProperty(property, property.Name, properties); + } + + return properties; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTemporalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTemporalTypeReference.cs new file mode 100644 index 0000000..fbc582e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTemporalTypeReference.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides the semantics of a reference to an EDM temporal type. + /// + internal class CsdlSemanticsTemporalTypeReference : CsdlSemanticsPrimitiveTypeReference, IEdmTemporalTypeReference + { + public CsdlSemanticsTemporalTypeReference(CsdlSemanticsSchema schema, CsdlTemporalTypeReference reference) + : base(schema, reference) + { + } + + public int? Precision + { + get { return ((CsdlTemporalTypeReference)this.Reference).Precision; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTerm.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTerm.cs new file mode 100644 index 0000000..2edfabe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTerm.cs @@ -0,0 +1,93 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlTerm. + /// + [System.Diagnostics.DebuggerDisplay("CsdlSemanticsTerm({Name})")] + internal class CsdlSemanticsTerm : CsdlSemanticsElement, IEdmTerm, IEdmFullNamedElement + { + protected readonly CsdlSemanticsSchema Context; + private readonly string fullName; + protected CsdlTerm term; + + private readonly Cache typeCache = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public CsdlSemanticsTerm(CsdlSemanticsSchema context, CsdlTerm valueTerm) + : base(valueTerm) + { + this.Context = context; + this.term = valueTerm; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.Context?.Namespace, this.term?.Name); + } + + public string Name + { + get { return this.term.Name; } + } + + public string Namespace + { + get { return this.Context.Namespace; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Term; } + } + + public IEdmTypeReference Type + { + get { return this.typeCache.GetValue(this, ComputeTypeFunc, null); } + } + + public string AppliesTo + { + get { return this.term.AppliesTo; } + } + + public string DefaultValue + { + get { return this.term.DefaultValue; } + } + + public override CsdlSemanticsModel Model + { + get { return this.Context.Model; } + } + + public override CsdlElement Element + { + get { return this.term; } + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.Context); + } + + private IEdmTypeReference ComputeType() + { + return CsdlSemanticsModel.WrapTypeReference(this.Context, this.term.Type); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTimeOfDayConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTimeOfDayConstantExpression.cs new file mode 100644 index 0000000..6d75370 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTimeOfDayConstantExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a Csdl TimeOfDay constant expression. + /// + internal class CsdlSemanticsTimeOfDayConstantExpression : CsdlSemanticsExpression, IEdmTimeOfDayConstantExpression, IEdmCheckable + { + private readonly CsdlConstantExpression expression; + + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache> errorsCache = new Cache>(); + private static readonly Func> ComputeErrorsFunc = (me) => me.ComputeErrors(); + + public CsdlSemanticsTimeOfDayConstantExpression(CsdlConstantExpression expression, CsdlSemanticsSchema schema) + : base(schema, expression) + { + this.expression = expression; + } + + public override CsdlElement Element + { + get { return this.expression; } + } + + public TimeOfDay Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + public IEdmTypeReference Type + { + get { return null; } + } + + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.TimeOfDayConstant; } + } + + public EdmValueKind ValueKind + { + get { return this.expression.ValueKind; } + } + + public IEnumerable Errors + { + get { return this.errorsCache.GetValue(this, ComputeErrorsFunc, null); } + } + + private TimeOfDay ComputeValue() + { + TimeOfDay? value; + return EdmValueParser.TryParseTimeOfDay(this.expression.Value, out value) ? value.Value : TimeOfDay.MinValue; + } + + private IEnumerable ComputeErrors() + { + TimeOfDay? value; + if (!EdmValueParser.TryParseTimeOfDay(this.expression.Value, out value)) + { + return new EdmError[] { new EdmError(this.Location, EdmErrorCode.InvalidTimeOfDay, Edm.Strings.ValueParser_InvalidTimeOfDay(this.expression.Value)) }; + } + else + { + return Enumerable.Empty(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinition.cs new file mode 100644 index 0000000..972ddbd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinition.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Common base class for the semantics of EDM types. + /// + internal abstract class CsdlSemanticsTypeDefinition : CsdlSemanticsElement, IEdmType + { + protected CsdlSemanticsTypeDefinition(CsdlElement element) + : base(element) + { + } + + public abstract EdmTypeKind TypeKind { get; } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + public override string ToString() + { + return this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionDefinition.cs new file mode 100644 index 0000000..cf21384 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionDefinition.cs @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for CsdlTypeDefinition. + /// + internal class CsdlSemanticsTypeDefinitionDefinition : CsdlSemanticsTypeDefinition, IEdmTypeDefinition, IEdmFullNamedElement + { + private readonly CsdlSemanticsSchema context; + private readonly CsdlTypeDefinition typeDefinition; + private readonly string fullName; + + private readonly Cache underlyingTypeCache = new Cache(); + private static readonly Func ComputeUnderlyingTypeFunc = (me) => me.ComputeUnderlyingType(); + + public CsdlSemanticsTypeDefinitionDefinition(CsdlSemanticsSchema context, CsdlTypeDefinition typeDefinition) + : base(typeDefinition) + { + this.context = context; + this.typeDefinition = typeDefinition; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.context?.Namespace, this.typeDefinition?.Name); + } + + IEdmPrimitiveType IEdmTypeDefinition.UnderlyingType + { + get { return this.underlyingTypeCache.GetValue(this, ComputeUnderlyingTypeFunc, null); } + } + + EdmSchemaElementKind IEdmSchemaElement.SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + public string Namespace + { + get { return this.context.Namespace; } + } + + string IEdmNamedElement.Name + { + get { return this.typeDefinition.Name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.TypeDefinition; } + } + + public override CsdlSemanticsModel Model + { + get { return this.context.Model; } + } + + public override CsdlElement Element + { + get { return this.typeDefinition; } + } + + protected override IEnumerable ComputeInlineVocabularyAnnotations() + { + return this.Model.WrapInlineVocabularyAnnotations(this, this.context); + } + + private IEdmPrimitiveType ComputeUnderlyingType() + { + if (this.typeDefinition.UnderlyingTypeName != null) + { + var underlyingTypeKind = EdmCoreModel.Instance.GetPrimitiveTypeKind(this.typeDefinition.UnderlyingTypeName); + return underlyingTypeKind != EdmPrimitiveTypeKind.None ? + EdmCoreModel.Instance.GetPrimitiveType(underlyingTypeKind) : + new UnresolvedPrimitiveType(this.typeDefinition.UnderlyingTypeName, this.Location); + } + + return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int32); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionReference.cs new file mode 100644 index 0000000..492269a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeDefinitionReference.cs @@ -0,0 +1,132 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides the semantics of a reference to an EDM type definition. + /// + internal class CsdlSemanticsTypeDefinitionReference : CsdlSemanticsNamedTypeReference, IEdmTypeDefinitionReference + { + private static readonly Func ComputeIsUnboundedFunc = me => me.ComputeIsUnbounded(); + private static readonly Func ComputeMaxLengthFunc = me => me.ComputeMaxLength(); + private static readonly Func ComputeIsUnicodeFunc = me => me.ComputeIsUnicode(); + private static readonly Func ComputePrecisionFunc = me => me.ComputePrecision(); + private static readonly Func ComputeScaleFunc = me => me.ComputeScale(); + private static readonly Func ComputeSridFunc = me => me.ComputeSrid(); + + private readonly Cache isUnboundedCache = new Cache(); + private readonly Cache maxLengthCache = new Cache(); + private readonly Cache isUnicodeCache = new Cache(); + private readonly Cache precisionCache = new Cache(); + private readonly Cache scaleCache = new Cache(); + private readonly Cache sridCache = new Cache(); + + public CsdlSemanticsTypeDefinitionReference(CsdlSemanticsSchema schema, CsdlNamedTypeReference reference) + : base(schema, reference) + { + } + + public bool IsUnbounded + { + get { return this.isUnboundedCache.GetValue(this, ComputeIsUnboundedFunc, null); } + } + + public int? MaxLength + { + get { return this.maxLengthCache.GetValue(this, ComputeMaxLengthFunc, null); } + } + + public bool? IsUnicode + { + get { return this.isUnicodeCache.GetValue(this, ComputeIsUnicodeFunc, null); } + } + + public int? Precision + { + get { return this.precisionCache.GetValue(this, ComputePrecisionFunc, null); } + } + + public int? Scale + { + get { return this.scaleCache.GetValue(this, ComputeScaleFunc, null); } + } + + public int? SpatialReferenceIdentifier + { + get { return this.sridCache.GetValue(this, ComputeSridFunc, null); } + } + + private CsdlNamedTypeReference Reference + { + get { return (CsdlNamedTypeReference)this.Element; } + } + + private bool ComputeIsUnbounded() + { + return this.UnderlyingType().CanSpecifyMaxLength() && this.Reference.IsUnbounded; + } + + private int? ComputeMaxLength() + { + return this.UnderlyingType().CanSpecifyMaxLength() ? this.Reference.MaxLength : null; + } + + private bool? ComputeIsUnicode() + { + return this.UnderlyingType().IsString() ? this.Reference.IsUnicode : null; + } + + private int? ComputePrecision() + { + if (this.UnderlyingType().IsDecimal()) + { + return this.Reference.Precision; + } + + if (this.UnderlyingType().IsTemporal()) + { + return this.Reference.Precision ?? CsdlConstants.Default_TemporalPrecision; + } + + return null; + } + + private int? ComputeScale() + { + return this.UnderlyingType().IsDecimal() ? this.Reference.Scale : null; + } + + private int? ComputeSrid() + { + if (this.UnderlyingType().IsGeography()) + { + return DefaultSridIfUnspecified(CsdlConstants.Default_SpatialGeographySrid); + } + + if (this.UnderlyingType().IsGeometry()) + { + return DefaultSridIfUnspecified(CsdlConstants.Default_SpatialGeometrySrid); + } + + return null; + } + + private int? DefaultSridIfUnspecified(int defaultSrid) + { + if (this.Reference.SpatialReferenceIdentifier != CsdlConstants.Default_UnspecifiedSrid) + { + // The SRID is specified. + return this.Reference.SpatialReferenceIdentifier; + } + + return defaultSrid; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeExpression.cs new file mode 100644 index 0000000..538327c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsTypeExpression.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for . + /// + internal abstract class CsdlSemanticsTypeExpression : CsdlSemanticsElement, IEdmTypeReference + { + private readonly CsdlExpressionTypeReference expressionUsage; + private readonly CsdlSemanticsTypeDefinition type; + + protected CsdlSemanticsTypeExpression(CsdlExpressionTypeReference expressionUsage, CsdlSemanticsTypeDefinition type) + : base(expressionUsage) + { + this.expressionUsage = expressionUsage; + this.type = type; + } + + public IEdmType Definition + { + get { return this.type; } + } + + public bool IsNullable + { + get { return this.expressionUsage.IsNullable; } + } + + public override CsdlSemanticsModel Model + { + get { return this.type.Model; } + } + + public override CsdlElement Element + { + get { return this.expressionUsage; } + } + + public override string ToString() + { + return this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsUntypedTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsUntypedTypeReference.cs new file mode 100644 index 0000000..3d803e9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsUntypedTypeReference.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl.Parsing.Ast; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides the semantics of a reference to an EDM untyped type. + /// + internal class CsdlSemanticsUntypedTypeReference : CsdlSemanticsElement, IEdmUntypedTypeReference + { + internal readonly CsdlUntypedTypeReference Reference; + private readonly CsdlSemanticsSchema schema; + + /// + /// The IEdmUntypedType instance from EdmCoreModel. + /// + private readonly IEdmUntypedType definition; + + public CsdlSemanticsUntypedTypeReference(CsdlSemanticsSchema schema, CsdlUntypedTypeReference reference) + : base(reference) + { + this.schema = schema; + this.Reference = reference; + this.definition = EdmCoreModel.Instance.GetUntypedType(); + } + + public bool IsNullable + { + get { return this.Reference.IsNullable; } + } + + public IEdmType Definition + { + get { return this.definition; } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + + public override CsdlElement Element + { + get { return this.Reference; } + } + + public override string ToString() + { + return this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsVocabularyAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsVocabularyAnnotation.cs new file mode 100644 index 0000000..291cc17 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsVocabularyAnnotation.cs @@ -0,0 +1,428 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + /// + /// Provides semantics for a CsdlAnnotation. + /// + internal class CsdlSemanticsVocabularyAnnotation : CsdlSemanticsElement, IEdmVocabularyAnnotation, IEdmCheckable + { + protected readonly CsdlAnnotation Annotation; + private readonly CsdlSemanticsSchema schema; + private readonly string qualifier; + private readonly IEdmVocabularyAnnotatable targetContext; + private readonly CsdlSemanticsAnnotations annotationsContext; + private readonly Cache valueCache = new Cache(); + private static readonly Func ComputeValueFunc = (me) => me.ComputeValue(); + + private readonly Cache termCache = new Cache(); + private static readonly Func ComputeTermFunc = (me) => me.ComputeTerm(); + + // Target cache. + private readonly Cache targetCache = new Cache(); + private static readonly Func ComputeTargetFunc = (me) => me.ComputeTarget(); + + public CsdlSemanticsVocabularyAnnotation(CsdlSemanticsSchema schema, IEdmVocabularyAnnotatable targetContext, CsdlSemanticsAnnotations annotationsContext, CsdlAnnotation annotation, string qualifier) + : base(annotation) + { + this.schema = schema; + this.Annotation = annotation; + this.qualifier = qualifier ?? annotation.Qualifier; + this.targetContext = targetContext; + this.annotationsContext = annotationsContext; + } + + public CsdlSemanticsSchema Schema + { + get { return this.schema; } + } + + public override CsdlElement Element + { + get { return this.Annotation; } + } + + public string Qualifier + { + get { return this.qualifier; } + } + + public override CsdlSemanticsModel Model + { + get { return this.schema.Model; } + } + + public IEdmTerm Term + { + get { return this.termCache.GetValue(this, ComputeTermFunc, null); } + } + + public IEdmVocabularyAnnotatable Target + { + get { return this.targetCache.GetValue(this, ComputeTargetFunc, null); } + } + + public IEnumerable Errors + { + get + { + if (this.Term is IUnresolvedElement) + { + return this.Term.Errors(); + } + + if (this.Target is IUnresolvedElement) + { + return this.Target.Errors(); + } + + return Enumerable.Empty(); + } + } + + /// + /// Gets the type to use as a binding context for expressions in the annotation. If the target of the annotation + /// is an entity type, that is the binding context. If the target is an entity set or singleton, the binding context is the + /// element type of the set or singleton. + /// + public IEdmEntityType TargetBindingContext + { + get + { + IEdmVocabularyAnnotatable bindingTarget = this.Target; + IEdmEntityType bindingContext = bindingTarget as IEdmEntityType; + if (bindingContext == null) + { + IEdmNavigationSource navigationSource = bindingTarget as IEdmNavigationSource; + if (navigationSource != null) + { + bindingContext = navigationSource.EntityType(); + } + } + + return bindingContext; + } + } + + public IEdmExpression Value + { + get { return this.valueCache.GetValue(this, ComputeValueFunc, null); } + } + + protected IEdmTerm ComputeTerm() + { + return this.Schema.FindTerm(this.Annotation.Term) ?? new UnresolvedVocabularyTerm(this.Schema.UnresolvedName(this.Annotation.Term)); + } + + private IEdmExpression ComputeValue() + { + return CsdlSemanticsModel.WrapExpression((this.Annotation).Expression, TargetBindingContext, this.Schema); + } + + private IEdmVocabularyAnnotatable ComputeTarget() + { + if (this.targetContext != null) + { + return this.targetContext; + } + else + { + Debug.Assert(this.annotationsContext != null, "Annotation must either have a target context or annotations context"); + string target = this.annotationsContext.Annotations.Target; + string[] targetSegments = target.Split('/'); + int targetSegmentsCount = targetSegments.Count(); + IEdmEntityContainer container; + + if (targetSegmentsCount == 1) + { + string elementName = targetSegments[0]; + IEdmSchemaType type = this.schema.FindType(elementName); + if (type != null) + { + return type; + } + + IEdmTerm term = this.schema.FindTerm(elementName); + if (term != null) + { + return term; + } + + IEdmOperation operation = this.FindParameterizedOperation(elementName, this.Schema.FindOperations, this.CreateAmbiguousOperation); + if (operation != null) + { + return operation; + } + + container = this.schema.FindEntityContainer(elementName); + if (container != null) + { + return container; + } + + return new UnresolvedType(this.Schema.UnresolvedName(targetSegments[0]), this.Location); + } + + if (targetSegmentsCount == 2) + { + container = this.schema.FindEntityContainer(targetSegments[0]); + if (container != null) + { + IEdmEntityContainerElement containerElement = container.FindEntitySetExtended(targetSegments[1]); + if (containerElement != null) + { + return containerElement; + } + + IEdmOperationImport operationImport = FindParameterizedOperationImport(targetSegments[1], container.FindOperationImportsExtended, this.CreateAmbiguousOperationImport); + if (operationImport != null) + { + return operationImport; + } + + return new UnresolvedEntitySet(targetSegments[1], container, this.Location); + } + + IEdmSchemaType type = this.schema.FindType(targetSegments[0]); + if (type != null) + { + IEdmStructuredType structuredType; + IEdmEnumType enumType; + if ((structuredType = type as IEdmStructuredType) != null) + { + IEdmProperty property = structuredType.FindProperty(targetSegments[1]); + if (property != null) + { + return property; + } + + return new UnresolvedProperty(structuredType, targetSegments[1], this.Location); + } + else if ((enumType = type as IEdmEnumType) != null) + { + foreach (IEdmEnumMember member in enumType.Members) + { + if (String.Equals(member.Name, targetSegments[1], StringComparison.OrdinalIgnoreCase)) + { + return member; + } + } + + return new UnresolvedEnumMember(targetSegments[1], enumType, this.Location); + } + } + + IEdmOperation operation = this.FindParameterizedOperation(targetSegments[0], this.Schema.FindOperations, this.CreateAmbiguousOperation); + if (operation != null) + { + // $ReturnType + if (targetSegments[1] == CsdlConstants.OperationReturnExternalTarget) + { + if (operation.ReturnType != null) + { + return operation.GetReturn(); + } + + return new UnresolvedReturn(operation, this.Location); + } + + IEdmOperationParameter parameter = operation.FindParameter(targetSegments[1]); + if (parameter != null) + { + return parameter; + } + + return new UnresolvedParameter(operation, targetSegments[1], this.Location); + } + + return new UnresolvedProperty(new UnresolvedEntityType(this.Schema.UnresolvedName(targetSegments[0]), this.Location), targetSegments[1], this.Location); + } + + if (targetSegmentsCount == 3) + { + // The only valid target with three segments is a function parameter, or an operation return. + string containerName = targetSegments[0]; + string operationName = targetSegments[1]; + string parameterName = targetSegments[2]; + + container = this.Model.FindEntityContainer(containerName); + if (container != null) + { + IEdmOperationImport operationImport = FindParameterizedOperationImport(operationName, container.FindOperationImportsExtended, this.CreateAmbiguousOperationImport); + if (operationImport != null) + { + // $ReturnType + if (parameterName == CsdlConstants.OperationReturnExternalTarget) + { + if (operationImport.Operation.ReturnType != null) + { + return operationImport.Operation.GetReturn(); + } + + return new UnresolvedReturn(operationImport.Operation, this.Location); + } + + IEdmOperationParameter parameter = operationImport.Operation.FindParameter(parameterName); + if (parameter != null) + { + return parameter; + } + + return new UnresolvedParameter(operationImport.Operation, parameterName, this.Location); + } + } + + string qualifiedOperationName = containerName + "/" + operationName; + UnresolvedOperation unresolvedOperation = new UnresolvedOperation(qualifiedOperationName, Edm.Strings.Bad_UnresolvedOperation(qualifiedOperationName), this.Location); + if (parameterName == CsdlConstants.OperationReturnExternalTarget) + { + return new UnresolvedReturn(unresolvedOperation, this.Location); + } + else + { + return new UnresolvedParameter(unresolvedOperation, parameterName, this.Location); + } + } + + return new BadElement(new EdmError[] { new EdmError(this.Location, EdmErrorCode.ImpossibleAnnotationsTarget, Edm.Strings.CsdlSemantics_ImpossibleAnnotationsTarget(target)) }); + } + } + + private static IEdmOperationImport FindParameterizedOperationImport(string parameterizedName, Func> findFunctions, Func, IEdmOperationImport> ambiguityCreator) + { + IEnumerable matchingFunctions = findFunctions(parameterizedName); + if (matchingFunctions.Count() == 0) + { + return null; + } + + if (matchingFunctions.Count() == 1) + { + return matchingFunctions.First(); + } + else + { + IEdmOperationImport ambiguous = ambiguityCreator(matchingFunctions); + return ambiguous; + } + } + + private IEdmOperation FindParameterizedOperation(string parameterizedName, Func> findFunctions, Func, IEdmOperation> ambiguityCreator) + { + int openParen = parameterizedName.IndexOf('('); + int closeParen = parameterizedName.LastIndexOf(')'); + if (openParen < 0) + { + return null; + } + + string name = parameterizedName.Substring(0, openParen); + string[] parameters = parameterizedName.Substring(openParen + 1, closeParen - (openParen + 1)).Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries); + IEnumerable matchingFunctions = this.FindParameterizedOperationFromList(findFunctions(name).Cast(), parameters); + if (matchingFunctions.Count() == 0) + { + return null; + } + + if (matchingFunctions.Count() == 1) + { + return matchingFunctions.First(); + } + else + { + IEdmOperation ambiguous = ambiguityCreator(matchingFunctions); + return ambiguous; + } + } + + private IEdmOperationImport CreateAmbiguousOperationImport(IEnumerable operations) + { + Debug.Assert(operations.Count() > 1, "Should not make an ambiguous thing with fewer than two items"); + IEnumerator operationEnumerator = operations.GetEnumerator(); + operationEnumerator.MoveNext(); + IEdmOperationImport first = operationEnumerator.Current; + operationEnumerator.MoveNext(); + IEdmOperationImport second = operationEnumerator.Current; + AmbiguousOperationImportBinding ambiguous = new AmbiguousOperationImportBinding(first, second); + while (operationEnumerator.MoveNext()) + { + ambiguous.AddBinding(operationEnumerator.Current); + } + + return ambiguous; + } + + private IEdmOperation CreateAmbiguousOperation(IEnumerable operations) + { + Debug.Assert(operations.Count() > 1, "Should not make an ambiguous thing with fewer than two items"); + IEnumerator operationEnumerator = operations.GetEnumerator(); + operationEnumerator.MoveNext(); + IEdmOperation first = operationEnumerator.Current; + operationEnumerator.MoveNext(); + IEdmOperation second = operationEnumerator.Current; + AmbiguousOperationBinding ambiguous = new AmbiguousOperationBinding(first, second); + while (operationEnumerator.MoveNext()) + { + ambiguous.AddBinding(operationEnumerator.Current); + } + + return ambiguous; + } + + private IEnumerable FindParameterizedOperationFromList(IEnumerable operations, string[] parameters) + { + List matchingOperations = new List(); + foreach (IEdmOperation function in operations) + { + if (function.Parameters.Count() != parameters.Count()) + { + continue; + } + + bool isMatch = true; + IEnumerator parameterTypeNameEnumerator = ((IEnumerable)parameters).GetEnumerator(); + foreach (IEdmOperationParameter parameter in function.Parameters) + { + parameterTypeNameEnumerator.MoveNext(); + string[] typeInformation = parameterTypeNameEnumerator.Current.Split(new char[] { '(', ')' }); + switch (typeInformation[0]) + { + case CsdlConstants.Value_Collection: + isMatch = parameter.Type.IsCollection() && this.Schema.FindType(typeInformation[1]).IsEquivalentTo(parameter.Type.AsCollection().ElementType().Definition); + break; + case CsdlConstants.Value_Ref: + isMatch = parameter.Type.IsEntityReference() && this.Schema.FindType(typeInformation[1]).IsEquivalentTo(parameter.Type.AsEntityReference().EntityType()); + break; + default: + isMatch = EdmCoreModel.Instance.FindDeclaredType(parameterTypeNameEnumerator.Current).IsEquivalentTo(parameter.Type.Definition) || this.Schema.FindType(parameterTypeNameEnumerator.Current).IsEquivalentTo(parameter.Type.Definition); + break; + } + + if (!isMatch) + { + break; + } + } + + if (isMatch) + { + matchingOperations.Add(function); + } + } + + return matchingOperations; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs new file mode 100644 index 0000000..631f915 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs @@ -0,0 +1,849 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Csdl.CsdlSemantics; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Csdl.Serialization +{ + internal class EdmModelCsdlSchemaWriter + { + protected XmlWriter xmlWriter; + protected Version version; + private readonly string edmxNamespace; + private readonly VersioningDictionary namespaceAliasMappings; + private readonly IEdmModel model; + + internal EdmModelCsdlSchemaWriter(IEdmModel model, VersioningDictionary namespaceAliasMappings, XmlWriter xmlWriter, Version edmVersion) + { + this.xmlWriter = xmlWriter; + this.version = edmVersion; + this.edmxNamespace = CsdlConstants.SupportedEdmxVersions[edmVersion]; + this.model = model; + this.namespaceAliasMappings = namespaceAliasMappings; + } + + internal static string PathAsXml(IEnumerable path) + { + return EdmUtil.JoinInternal("/", path); + } + + internal void WriteReferenceElementHeader(IEdmReference reference) + { + // e.g. + this.xmlWriter.WriteStartElement(CsdlConstants.Prefix_Edmx, CsdlConstants.Element_Reference, this.edmxNamespace); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Uri, reference.Uri, EdmValueWriter.UriAsXml); + } + + internal void WriteIncludeElement(IEdmInclude include) + { + // e.g. + this.xmlWriter.WriteStartElement(CsdlConstants.Prefix_Edmx, CsdlConstants.Element_Include, this.edmxNamespace); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Namespace, include.Namespace, EdmValueWriter.StringAsXml); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Alias, include.Alias, EdmValueWriter.StringAsXml); + this.xmlWriter.WriteEndElement(); + } + + internal void WriteIncludeAnnotationsElement(IEdmIncludeAnnotations includeAnnotations) + { + // e.g. + this.xmlWriter.WriteStartElement(CsdlConstants.Prefix_Edmx, CsdlConstants.Element_IncludeAnnotations, this.edmxNamespace); + this.WriteRequiredAttribute(CsdlConstants.Attribute_TermNamespace, includeAnnotations.TermNamespace, EdmValueWriter.StringAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Qualifier, includeAnnotations.Qualifier, EdmValueWriter.StringAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_TargetNamespace, includeAnnotations.TargetNamespace, EdmValueWriter.StringAsXml); + this.xmlWriter.WriteEndElement(); + } + + internal void WriteTermElementHeader(IEdmTerm term, bool inlineType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Term); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, term.Name, EdmValueWriter.StringAsXml); + if (inlineType && term.Type != null) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, term.Type, this.TypeReferenceAsXml); + } + + this.WriteOptionalAttribute(CsdlConstants.Attribute_DefaultValue, term.DefaultValue, EdmValueWriter.StringAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_AppliesTo, term.AppliesTo, EdmValueWriter.StringAsXml); + } + + internal void WriteComplexTypeElementHeader(IEdmComplexType complexType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_ComplexType); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, complexType.Name, EdmValueWriter.StringAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_BaseType, complexType.BaseComplexType(), this.TypeDefinitionAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Abstract, complexType.IsAbstract, CsdlConstants.Default_Abstract, EdmValueWriter.BooleanAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_OpenType, complexType.IsOpen, CsdlConstants.Default_OpenType, EdmValueWriter.BooleanAsXml); + } + + internal void WriteEnumTypeElementHeader(IEdmEnumType enumType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_EnumType); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, enumType.Name, EdmValueWriter.StringAsXml); + if (enumType.UnderlyingType.PrimitiveKind != EdmPrimitiveTypeKind.Int32) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_UnderlyingType, enumType.UnderlyingType, this.TypeDefinitionAsXml); + } + + this.WriteOptionalAttribute(CsdlConstants.Attribute_IsFlags, enumType.IsFlags, CsdlConstants.Default_IsFlags, EdmValueWriter.BooleanAsXml); + } + + internal void WriteEntityContainerElementHeader(IEdmEntityContainer container) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_EntityContainer); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, container.Name, EdmValueWriter.StringAsXml); + CsdlSemanticsEntityContainer tmp = container as CsdlSemanticsEntityContainer; + CsdlEntityContainer csdlContainer = null; + if (tmp != null && (csdlContainer = tmp.Element as CsdlEntityContainer) != null) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_Extends, csdlContainer.Extends, EdmValueWriter.StringAsXml); + } + } + + internal void WriteEntitySetElementHeader(IEdmEntitySet entitySet) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_EntitySet); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, entitySet.Name, EdmValueWriter.StringAsXml); + this.WriteRequiredAttribute(CsdlConstants.Attribute_EntityType, entitySet.EntityType().FullName(), EdmValueWriter.StringAsXml); + } + + internal void WriteSingletonElementHeader(IEdmSingleton singleton) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Singleton); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, singleton.Name, EdmValueWriter.StringAsXml); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, singleton.EntityType().FullName(), EdmValueWriter.StringAsXml); + } + + internal void WriteNavigationPropertyBinding(IEdmNavigationSource navigationSource, IEdmNavigationPropertyBinding binding) + { + this.WriteNavigationPropertyBinding(binding); + } + + internal void WriteEntityTypeElementHeader(IEdmEntityType entityType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_EntityType); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, entityType.Name, EdmValueWriter.StringAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_BaseType, entityType.BaseEntityType(), this.TypeDefinitionAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Abstract, entityType.IsAbstract, CsdlConstants.Default_Abstract, EdmValueWriter.BooleanAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_OpenType, entityType.IsOpen, CsdlConstants.Default_OpenType, EdmValueWriter.BooleanAsXml); + + // HasStream value should be inherited. Only have it on base type is sufficient. + bool writeHasStream = entityType.HasStream && (entityType.BaseEntityType() == null || (entityType.BaseEntityType() != null && !entityType.BaseEntityType().HasStream)); + this.WriteOptionalAttribute(CsdlConstants.Attribute_HasStream, writeHasStream, CsdlConstants.Default_HasStream, EdmValueWriter.BooleanAsXml); + } + + internal void WriteDelaredKeyPropertiesElementHeader() + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Key); + } + + internal void WritePropertyRefElement(IEdmStructuralProperty property) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_PropertyRef); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, property.Name, EdmValueWriter.StringAsXml); + this.WriteEndElement(); + } + + internal void WriteNavigationPropertyElementHeader(IEdmNavigationProperty member) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_NavigationProperty); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, member.Name, EdmValueWriter.StringAsXml); + + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, member.Type, this.TypeReferenceAsXml); + if (!member.Type.IsCollection() && member.Type.IsNullable != CsdlConstants.Default_Nullable) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Nullable, member.Type.IsNullable, EdmValueWriter.BooleanAsXml); + } + + if (member.Partner != null) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Partner, member.GetPartnerPath().Path, EdmValueWriter.StringAsXml); + } + + this.WriteOptionalAttribute(CsdlConstants.Attribute_ContainsTarget, member.ContainsTarget, CsdlConstants.Default_ContainsTarget, EdmValueWriter.BooleanAsXml); + } + + internal void WriteOperationActionElement(string elementName, EdmOnDeleteAction operationAction) + { + this.xmlWriter.WriteStartElement(elementName); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Action, operationAction.ToString(), EdmValueWriter.StringAsXml); + this.WriteEndElement(); + } + + internal void WriteSchemaElementHeader(EdmSchema schema, string alias, IEnumerable> mappings) + { + string xmlNamespace = GetCsdlNamespace(this.version); + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Schema, xmlNamespace); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Namespace, schema.Namespace, string.Empty, EdmValueWriter.StringAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Alias, alias, EdmValueWriter.StringAsXml); + if (mappings != null) + { + foreach (KeyValuePair mapping in mappings) + { + this.xmlWriter.WriteAttributeString(EdmConstants.XmlNamespacePrefix, mapping.Key, null, mapping.Value); + } + } + } + + internal void WriteAnnotationsElementHeader(string annotationsTarget) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Annotations); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Target, annotationsTarget, EdmValueWriter.StringAsXml); + } + + internal void WriteStructuralPropertyElementHeader(IEdmStructuralProperty property, bool inlineType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Property); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, property.Name, EdmValueWriter.StringAsXml); + if (inlineType) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, property.Type, this.TypeReferenceAsXml); + } + + this.WriteOptionalAttribute(CsdlConstants.Attribute_DefaultValue, property.DefaultValueString, EdmValueWriter.StringAsXml); + } + + internal void WriteEnumMemberElementHeader(IEdmEnumMember member) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Member); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, member.Name, EdmValueWriter.StringAsXml); + bool? isExplicit = member.IsValueExplicit(this.model); + if (!isExplicit.HasValue || isExplicit.Value) + { + this.xmlWriter.WriteAttributeString(CsdlConstants.Attribute_Value, EdmValueWriter.LongAsXml(member.Value.Value)); + } + } + + internal void WriteNullableAttribute(IEdmTypeReference reference) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_Nullable, reference.IsNullable, CsdlConstants.Default_Nullable, EdmValueWriter.BooleanAsXml); + } + + internal void WriteTypeDefinitionAttributes(IEdmTypeDefinitionReference reference) + { + IEdmTypeReference actualTypeReference = reference.AsActualTypeReference(); + + if (actualTypeReference.IsBinary()) + { + this.WriteBinaryTypeAttributes(actualTypeReference.AsBinary()); + } + else if (actualTypeReference.IsString()) + { + this.WriteStringTypeAttributes(actualTypeReference.AsString()); + } + else if (actualTypeReference.IsTemporal()) + { + this.WriteTemporalTypeAttributes(actualTypeReference.AsTemporal()); + } + else if (actualTypeReference.IsDecimal()) + { + this.WriteDecimalTypeAttributes(actualTypeReference.AsDecimal()); + } + else if (actualTypeReference.IsSpatial()) + { + this.WriteSpatialTypeAttributes(actualTypeReference.AsSpatial()); + } + } + + internal void WriteBinaryTypeAttributes(IEdmBinaryTypeReference reference) + { + if (reference.IsUnbounded) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_MaxLength, CsdlConstants.Value_Max, EdmValueWriter.StringAsXml); + } + else + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_MaxLength, reference.MaxLength, EdmValueWriter.IntAsXml); + } + } + + internal void WriteDecimalTypeAttributes(IEdmDecimalTypeReference reference) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_Precision, reference.Precision, EdmValueWriter.IntAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Scale, reference.Scale, CsdlConstants.Default_Scale, ScaleAsXml); + } + + internal void WriteSpatialTypeAttributes(IEdmSpatialTypeReference reference) + { + if (reference.IsGeography()) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_Srid, reference.SpatialReferenceIdentifier, CsdlConstants.Default_SpatialGeographySrid, SridAsXml); + } + else if (reference.IsGeometry()) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_Srid, reference.SpatialReferenceIdentifier, CsdlConstants.Default_SpatialGeometrySrid, SridAsXml); + } + } + + internal void WriteStringTypeAttributes(IEdmStringTypeReference reference) + { + if (reference.IsUnbounded) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_MaxLength, CsdlConstants.Value_Max, EdmValueWriter.StringAsXml); + } + else + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_MaxLength, reference.MaxLength, EdmValueWriter.IntAsXml); + } + + if (reference.IsUnicode != null) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_Unicode, reference.IsUnicode, CsdlConstants.Default_IsUnicode, EdmValueWriter.BooleanAsXml); + } + } + + internal void WriteTemporalTypeAttributes(IEdmTemporalTypeReference reference) + { + if (reference.Precision != null) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_Precision, reference.Precision, CsdlConstants.Default_TemporalPrecision, EdmValueWriter.IntAsXml); + } + } + + internal void WriteReferentialConstraintElementHeader(IEdmNavigationProperty constraint) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_ReferentialConstraint); + } + + internal void WriteReferentialConstraintPair(EdmReferentialConstraintPropertyPair pair) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_ReferentialConstraint); + + // + // ... + // + // + // + // + // + // the above CategoryID is DependentProperty, ID is PrincipalProperty. + this.WriteRequiredAttribute(CsdlConstants.Attribute_Property, pair.DependentProperty.Name, EdmValueWriter.StringAsXml); + this.WriteRequiredAttribute(CsdlConstants.Attribute_ReferencedProperty, pair.PrincipalProperty.Name, EdmValueWriter.StringAsXml); + this.WriteEndElement(); + } + + internal void WriteAnnotationStringAttribute(IEdmDirectValueAnnotation annotation) + { + var edmValue = (IEdmPrimitiveValue)annotation.Value; + if (edmValue != null) + { + this.xmlWriter.WriteAttributeString(annotation.Name, annotation.NamespaceUri, EdmValueWriter.PrimitiveValueAsXml(edmValue)); + } + } + + internal void WriteAnnotationStringElement(IEdmDirectValueAnnotation annotation) + { + var edmValue = (IEdmPrimitiveValue)annotation.Value; + if (edmValue != null) + { + this.xmlWriter.WriteRaw(((IEdmStringValue)edmValue).Value); + } + } + + internal void WriteActionElementHeader(IEdmAction action) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Action); + this.WriteOperationElementAttributes(action); + } + + internal void WriteFunctionElementHeader(IEdmFunction function) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Function); + this.WriteOperationElementAttributes(function); + + if (function.IsComposable) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_IsComposable, function.IsComposable, EdmValueWriter.BooleanAsXml); + } + } + + internal void WriteReturnTypeElementHeader() + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_ReturnType); + } + + internal void WriteTypeAttribute(IEdmTypeReference typeReference) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, typeReference, this.TypeReferenceAsXml); + } + + internal void WriteActionImportElementHeader(IEdmActionImport actionImport) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_ActionImport); + this.WriteOperationImportAttributes(actionImport, CsdlConstants.Attribute_Action); + } + + internal void WriteFunctionImportElementHeader(IEdmFunctionImport functionImport) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_FunctionImport); + this.WriteOperationImportAttributes(functionImport, CsdlConstants.Attribute_Function); + this.WriteOptionalAttribute(CsdlConstants.Attribute_IncludeInServiceDocument, functionImport.IncludeInServiceDocument, CsdlConstants.Default_IncludeInServiceDocument, EdmValueWriter.BooleanAsXml); + } + + internal void WriteOperationParameterElementHeader(IEdmOperationParameter parameter, bool inlineType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Parameter); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, parameter.Name, EdmValueWriter.StringAsXml); + if (inlineType) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, parameter.Type, this.TypeReferenceAsXml); + } + } + + internal void WriteOperationParameterEndElement(IEdmOperationParameter parameter) + { + IEdmOptionalParameter optionalParameter = parameter as IEdmOptionalParameter; + if (optionalParameter != null && !(optionalParameter.VocabularyAnnotations(this.model).Any(a => a.Term == CoreVocabularyModel.OptionalParameterTerm))) + { + string defaultValue = optionalParameter.DefaultValueString; + EdmRecordExpression optionalValue = new EdmRecordExpression(); + + this.WriteVocabularyAnnotationElementHeader(new EdmVocabularyAnnotation(parameter, CoreVocabularyModel.OptionalParameterTerm, optionalValue), false); + if (!String.IsNullOrEmpty(defaultValue)) + { + EdmPropertyConstructor property = new EdmPropertyConstructor(CsdlConstants.Attribute_DefaultValue, new EdmStringConstant(defaultValue)); + this.WriteRecordExpressionElementHeader(optionalValue); + this.WritePropertyValueElementHeader(property, true); + this.WriteEndElement(); + this.WriteEndElement(); + } + + this.WriteEndElement(); + } + + this.WriteEndElement(); + } + + internal void WriteCollectionTypeElementHeader(IEdmCollectionType collectionType, bool inlineType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_CollectionType); + if (inlineType) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_ElementType, collectionType.ElementType, this.TypeReferenceAsXml); + } + } + + internal void WriteInlineExpression(IEdmExpression expression) + { + switch (expression.ExpressionKind) + { + case EdmExpressionKind.BinaryConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Binary, ((IEdmBinaryConstantExpression)expression).Value, EdmValueWriter.BinaryAsXml); + break; + case EdmExpressionKind.BooleanConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Bool, ((IEdmBooleanConstantExpression)expression).Value, EdmValueWriter.BooleanAsXml); + break; + case EdmExpressionKind.DateTimeOffsetConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_DateTimeOffset, ((IEdmDateTimeOffsetConstantExpression)expression).Value, EdmValueWriter.DateTimeOffsetAsXml); + break; + case EdmExpressionKind.DecimalConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Decimal, ((IEdmDecimalConstantExpression)expression).Value, EdmValueWriter.DecimalAsXml); + break; + case EdmExpressionKind.FloatingConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Float, ((IEdmFloatingConstantExpression)expression).Value, EdmValueWriter.FloatAsXml); + break; + case EdmExpressionKind.GuidConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Guid, ((IEdmGuidConstantExpression)expression).Value, EdmValueWriter.GuidAsXml); + break; + case EdmExpressionKind.IntegerConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Int, ((IEdmIntegerConstantExpression)expression).Value, EdmValueWriter.LongAsXml); + break; + case EdmExpressionKind.Path: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Path, ((IEdmPathExpression)expression).PathSegments, PathAsXml); + break; + case EdmExpressionKind.PropertyPath: + this.WriteRequiredAttribute(CsdlConstants.Attribute_PropertyPath, ((IEdmPathExpression)expression).PathSegments, PathAsXml); + break; + case EdmExpressionKind.NavigationPropertyPath: + this.WriteRequiredAttribute(CsdlConstants.Attribute_NavigationPropertyPath, ((IEdmPathExpression)expression).PathSegments, PathAsXml); + break; + case EdmExpressionKind.StringConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_String, ((IEdmStringConstantExpression)expression).Value, EdmValueWriter.StringAsXml); + break; + case EdmExpressionKind.DurationConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Duration, ((IEdmDurationConstantExpression)expression).Value, EdmValueWriter.DurationAsXml); + break; + case EdmExpressionKind.DateConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_Date, ((IEdmDateConstantExpression)expression).Value, EdmValueWriter.DateAsXml); + break; + case EdmExpressionKind.TimeOfDayConstant: + this.WriteRequiredAttribute(CsdlConstants.Attribute_TimeOfDay, ((IEdmTimeOfDayConstantExpression)expression).Value, EdmValueWriter.TimeOfDayAsXml); + break; + default: + Debug.Assert(false, "Attempted to inline an expression that was not one of the expected inlineable types."); + break; + } + } + + internal void WriteVocabularyAnnotationElementHeader(IEdmVocabularyAnnotation annotation, bool isInline) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Annotation); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Term, annotation.Term, this.TermAsXml); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Qualifier, annotation.Qualifier, EdmValueWriter.StringAsXml); + if (isInline) + { + this.WriteInlineExpression(annotation.Value); + } + } + + internal void WritePropertyValueElementHeader(IEdmPropertyConstructor value, bool isInline) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_PropertyValue); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Property, value.Name, EdmValueWriter.StringAsXml); + if (isInline) + { + this.WriteInlineExpression(value.Value); + } + } + + internal void WriteRecordExpressionElementHeader(IEdmRecordExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Record); + this.WriteOptionalAttribute(CsdlConstants.Attribute_Type, expression.DeclaredType, this.TypeReferenceAsXml); + } + + internal void WritePropertyConstructorElementHeader(IEdmPropertyConstructor constructor, bool isInline) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_PropertyValue); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Property, constructor.Name, EdmValueWriter.StringAsXml); + if (isInline) + { + this.WriteInlineExpression(constructor.Value); + } + } + + internal void WriteStringConstantExpressionElement(IEdmStringConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_String); + + this.xmlWriter.WriteString(EdmValueWriter.StringAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteBinaryConstantExpressionElement(IEdmBinaryConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Binary); + this.xmlWriter.WriteString(EdmValueWriter.BinaryAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteBooleanConstantExpressionElement(IEdmBooleanConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Bool); + this.xmlWriter.WriteString(EdmValueWriter.BooleanAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteNullConstantExpressionElement(IEdmNullExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Null); + this.WriteEndElement(); + } + + internal void WriteDateConstantExpressionElement(IEdmDateConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Date); + this.xmlWriter.WriteString(EdmValueWriter.DateAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteDateTimeOffsetConstantExpressionElement(IEdmDateTimeOffsetConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_DateTimeOffset); + this.xmlWriter.WriteString(EdmValueWriter.DateTimeOffsetAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteDurationConstantExpressionElement(IEdmDurationConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Duration); + this.xmlWriter.WriteString(EdmValueWriter.DurationAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteDecimalConstantExpressionElement(IEdmDecimalConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Decimal); + this.xmlWriter.WriteString(EdmValueWriter.DecimalAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteFloatingConstantExpressionElement(IEdmFloatingConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Float); + this.xmlWriter.WriteString(EdmValueWriter.FloatAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteFunctionApplicationElementHeader(IEdmApplyExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Apply); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Function, expression.AppliedFunction, this.FunctionAsXml); + } + + internal void WriteGuidConstantExpressionElement(IEdmGuidConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Guid); + this.xmlWriter.WriteString(EdmValueWriter.GuidAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteIntegerConstantExpressionElement(IEdmIntegerConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Int); + this.xmlWriter.WriteString(EdmValueWriter.LongAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WritePathExpressionElement(IEdmPathExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Path); + this.xmlWriter.WriteString(PathAsXml(expression.PathSegments)); + this.WriteEndElement(); + } + + internal void WritePropertyPathExpressionElement(IEdmPathExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_PropertyPath); + this.xmlWriter.WriteString(PathAsXml(expression.PathSegments)); + this.WriteEndElement(); + } + + internal void WriteNavigationPropertyPathExpressionElement(IEdmPathExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_NavigationPropertyPath); + this.xmlWriter.WriteString(PathAsXml(expression.PathSegments)); + this.WriteEndElement(); + } + + internal void WriteIfExpressionElementHeader(IEdmIfExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_If); + } + + internal void WriteCollectionExpressionElementHeader(IEdmCollectionExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Collection); + } + + internal void WriteLabeledElementHeader(IEdmLabeledExpression labeledElement) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_LabeledElement); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, labeledElement.Name, EdmValueWriter.StringAsXml); + } + + internal void WriteTimeOfDayConstantExpressionElement(IEdmTimeOfDayConstantExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_TimeOfDay); + this.xmlWriter.WriteString(EdmValueWriter.TimeOfDayAsXml(expression.Value)); + this.WriteEndElement(); + } + + internal void WriteIsTypeExpressionElementHeader(IEdmIsTypeExpression expression, bool inlineType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_IsType); + if (inlineType) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, expression.Type, this.TypeReferenceAsXml); + } + } + + internal void WriteCastExpressionElementHeader(IEdmCastExpression expression, bool inlineType) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_Cast); + if (inlineType) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Type, expression.Type, this.TypeReferenceAsXml); + } + } + + internal void WriteEnumMemberExpressionElement(IEdmEnumMemberExpression expression) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_EnumMember); + this.xmlWriter.WriteString(EnumMemberAsXml(expression.EnumMembers)); + this.WriteEndElement(); + } + + internal void WriteTypeDefinitionElementHeader(IEdmTypeDefinition typeDefinition) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_TypeDefinition); + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, typeDefinition.Name, EdmValueWriter.StringAsXml); + this.WriteRequiredAttribute(CsdlConstants.Attribute_UnderlyingType, typeDefinition.UnderlyingType, this.TypeDefinitionAsXml); + } + + internal void WriteEndElement() + { + this.xmlWriter.WriteEndElement(); + } + + internal void WriteOptionalAttribute(string attribute, T value, T defaultValue, Func toXml) + { + if (!value.Equals(defaultValue)) + { + this.xmlWriter.WriteAttributeString(attribute, toXml(value)); + } + } + + internal void WriteOptionalAttribute(string attribute, T value, Func toXml) + { + if (value != null) + { + this.xmlWriter.WriteAttributeString(attribute, toXml(value)); + } + } + + internal void WriteRequiredAttribute(string attribute, T value, Func toXml) + { + this.xmlWriter.WriteAttributeString(attribute, toXml(value)); + } + + private void WriteOperationElementAttributes(IEdmOperation operation) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, operation.Name, EdmValueWriter.StringAsXml); + + if (operation.IsBound) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_IsBound, operation.IsBound, EdmValueWriter.BooleanAsXml); + } + + if (operation.EntitySetPath != null) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_EntitySetPath, operation.EntitySetPath.PathSegments, PathAsXml); + } + } + + private void WriteNavigationPropertyBinding(IEdmNavigationPropertyBinding binding) + { + this.xmlWriter.WriteStartElement(CsdlConstants.Element_NavigationPropertyBinding); + + this.WriteRequiredAttribute(CsdlConstants.Attribute_Path, binding.Path.Path, EdmValueWriter.StringAsXml); + + // TODO: handle container names, etc. + IEdmContainedEntitySet containedEntitySet = binding.Target as IEdmContainedEntitySet; + if (containedEntitySet != null) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Target, containedEntitySet.Path.Path, EdmValueWriter.StringAsXml); + } + else + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Target, binding.Target.Name, EdmValueWriter.StringAsXml); + } + + this.xmlWriter.WriteEndElement(); + } + + private static string EnumMemberAsXml(IEnumerable members) + { + string enumTypeName = members.First().DeclaringType.FullName(); + List memberList = new List(); + foreach (var member in members) + { + memberList.Add(enumTypeName + "/" + member.Name); + } + + return string.Join(" ", memberList.ToArray()); + } + + private static string SridAsXml(int? i) + { + return i.HasValue ? Convert.ToString(i.Value, CultureInfo.InvariantCulture) : CsdlConstants.Value_SridVariable; + } + + private static string ScaleAsXml(int? i) + { + return i.HasValue ? Convert.ToString(i.Value, CultureInfo.InvariantCulture) : CsdlConstants.Value_ScaleVariable; + } + + private static string GetCsdlNamespace(Version edmVersion) + { + string[] @namespaces; + if (CsdlConstants.SupportedVersions.TryGetValue(edmVersion, out @namespaces)) + { + return @namespaces[0]; + } + + throw new InvalidOperationException(Strings.Serializer_UnknownEdmVersion); + } + + private void WriteOperationImportAttributes(IEdmOperationImport operationImport, string operationAttributeName) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, operationImport.Name, EdmValueWriter.StringAsXml); + this.WriteRequiredAttribute(operationAttributeName, operationImport.Operation.FullName(), EdmValueWriter.StringAsXml); + + if (operationImport.EntitySet != null) + { + var pathExpression = operationImport.EntitySet as IEdmPathExpression; + if (pathExpression != null) + { + this.WriteOptionalAttribute(CsdlConstants.Attribute_EntitySet, pathExpression.PathSegments, PathAsXml); + } + else + { + throw new InvalidOperationException(Strings.EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid(operationImport.Name)); + } + } + } + + private string SerializationName(IEdmSchemaElement element) + { + if (this.namespaceAliasMappings != null) + { + string alias; + if (this.namespaceAliasMappings.TryGetValue(element.Namespace, out alias)) + { + return alias + "." + element.Name; + } + } + + return element.FullName(); + } + + private string TypeReferenceAsXml(IEdmTypeReference type) + { + if (type.IsCollection()) + { + IEdmCollectionTypeReference collectionReference = type.AsCollection(); + Debug.Assert(collectionReference.ElementType().Definition is IEdmSchemaElement, "Cannot inline parameter type if not a named element or collection of named elements"); + return CsdlConstants.Value_Collection + "(" + this.SerializationName((IEdmSchemaElement)collectionReference.ElementType().Definition) + ")"; + } + else if (type.IsEntityReference()) + { + return CsdlConstants.Value_Ref + "(" + this.SerializationName(type.AsEntityReference().EntityReferenceDefinition().EntityType) + ")"; + } + + Debug.Assert(type.Definition is IEdmSchemaElement, "Cannot inline parameter type if not a named element or collection of named elements"); + return this.SerializationName((IEdmSchemaElement)type.Definition); + } + + private string TypeDefinitionAsXml(IEdmSchemaType type) + { + return this.SerializationName(type); + } + + private string FunctionAsXml(IEdmOperation operation) + { + return this.SerializationName(operation); + } + + private string TermAsXml(IEdmTerm term) + { + if (term == null) + { + return string.Empty; + } + + return this.SerializationName(term); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs new file mode 100644 index 0000000..37ad112 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs @@ -0,0 +1,715 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.Serialization +{ + internal sealed class EdmModelCsdlSerializationVisitor : EdmModelVisitor + { + private readonly Version edmVersion; + private readonly EdmModelCsdlSchemaWriter schemaWriter; + private readonly List navigationProperties = new List(); + private readonly VersioningDictionary namespaceAliasMappings; + + internal EdmModelCsdlSerializationVisitor(IEdmModel model, XmlWriter xmlWriter, Version edmVersion) + : base(model) + { + this.edmVersion = edmVersion; + this.namespaceAliasMappings = model.GetNamespaceAliases(); + this.schemaWriter = new EdmModelCsdlSchemaWriter(model, this.namespaceAliasMappings, xmlWriter, this.edmVersion); + } + + public override void VisitEntityContainerElements(IEnumerable elements) + { + HashSet functionImportsWritten = new HashSet(); + HashSet actionImportsWritten = new HashSet(); + + foreach (IEdmEntityContainerElement element in elements) + { + switch (element.ContainerElementKind) + { + case EdmContainerElementKind.EntitySet: + this.ProcessEntitySet((IEdmEntitySet)element); + break; + case EdmContainerElementKind.Singleton: + this.ProcessSingleton((IEdmSingleton)element); + break; + case EdmContainerElementKind.ActionImport: + // Skip actionImports that have the same name for same overloads of a function. + IEdmActionImport actionImport = (IEdmActionImport)element; + string uniqueActionName = actionImport.Name + "_" + actionImport.Action.FullName() + GetEntitySetString(actionImport); + if (!actionImportsWritten.Contains(uniqueActionName)) + { + actionImportsWritten.Add(uniqueActionName); + this.ProcessActionImport(actionImport); + } + + break; + case EdmContainerElementKind.FunctionImport: + // Skip functionImports that have the same name for same overloads of a function. + IEdmFunctionImport functionImport = (IEdmFunctionImport)element; + string uniqueFunctionName = functionImport.Name + "_" + functionImport.Function.FullName() + GetEntitySetString(functionImport); + if (!functionImportsWritten.Contains(uniqueFunctionName)) + { + functionImportsWritten.Add(uniqueFunctionName); + this.ProcessFunctionImport(functionImport); + } + + break; + case EdmContainerElementKind.None: + this.ProcessEntityContainerElement(element); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_ContainerElementKind(element.ContainerElementKind.ToString())); + } + } + } + + internal void VisitEdmSchema(EdmSchema element, IEnumerable> mappings) + { + string alias = null; + if (this.namespaceAliasMappings != null) + { + this.namespaceAliasMappings.TryGetValue(element.Namespace, out alias); + } + + this.schemaWriter.WriteSchemaElementHeader(element, alias, mappings); + + VisitSchemaElements(element.SchemaElements); + + // EntityContainers are excluded from the EdmSchema.SchemaElements property so they can be forced to the end. + VisitCollection(element.EntityContainers, this.ProcessEntityContainer); + foreach (KeyValuePair> annotationsForTarget in element.OutOfLineAnnotations) + { + this.schemaWriter.WriteAnnotationsElementHeader(annotationsForTarget.Key); + VisitVocabularyAnnotations(annotationsForTarget.Value); + this.schemaWriter.WriteEndElement(); + } + + this.schemaWriter.WriteEndElement(); + } + + protected override void ProcessEntityContainer(IEdmEntityContainer element) + { + this.BeginElement(element, this.schemaWriter.WriteEntityContainerElementHeader); + base.ProcessEntityContainer(element); + this.EndElement(element); + } + + protected override void ProcessEntitySet(IEdmEntitySet element) + { + this.BeginElement(element, this.schemaWriter.WriteEntitySetElementHeader); + + base.ProcessEntitySet(element); + + foreach (IEdmNavigationPropertyBinding binding in element.NavigationPropertyBindings) + { + this.schemaWriter.WriteNavigationPropertyBinding(element, binding); + } + + this.EndElement(element); + } + + protected override void ProcessSingleton(IEdmSingleton element) + { + this.BeginElement(element, this.schemaWriter.WriteSingletonElementHeader); + + base.ProcessSingleton(element); + + foreach (IEdmNavigationPropertyBinding binding in element.NavigationPropertyBindings) + { + this.schemaWriter.WriteNavigationPropertyBinding(element, binding); + } + + this.EndElement(element); + } + + protected override void ProcessEntityType(IEdmEntityType element) + { + this.BeginElement(element, this.schemaWriter.WriteEntityTypeElementHeader); + if (element.DeclaredKey != null && element.DeclaredKey.Any()) + { + this.VisitEntityTypeDeclaredKey(element.DeclaredKey); + } + + this.VisitProperties(element.DeclaredStructuralProperties().Cast()); + this.VisitProperties(element.DeclaredNavigationProperties().Cast()); + this.EndElement(element); + } + + protected override void ProcessStructuralProperty(IEdmStructuralProperty element) + { + bool inlineType = IsInlineType(element.Type); + this.BeginElement(element, (IEdmStructuralProperty t) => { this.schemaWriter.WriteStructuralPropertyElementHeader(t, inlineType); }, e => { this.ProcessFacets(e.Type, inlineType); }); + if (!inlineType) + { + VisitTypeReference(element.Type); + } + + this.EndElement(element); + } + + protected override void ProcessTypeDefinitionReference(IEdmTypeDefinitionReference element) + { + this.schemaWriter.WriteTypeDefinitionAttributes(element); + } + + protected override void ProcessBinaryTypeReference(IEdmBinaryTypeReference element) + { + this.schemaWriter.WriteBinaryTypeAttributes(element); + } + + protected override void ProcessDecimalTypeReference(IEdmDecimalTypeReference element) + { + this.schemaWriter.WriteDecimalTypeAttributes(element); + } + + protected override void ProcessSpatialTypeReference(IEdmSpatialTypeReference element) + { + this.schemaWriter.WriteSpatialTypeAttributes(element); + } + + protected override void ProcessStringTypeReference(IEdmStringTypeReference element) + { + this.schemaWriter.WriteStringTypeAttributes(element); + } + + protected override void ProcessTemporalTypeReference(IEdmTemporalTypeReference element) + { + this.schemaWriter.WriteTemporalTypeAttributes(element); + } + + protected override void ProcessNavigationProperty(IEdmNavigationProperty element) + { + this.BeginElement(element, this.schemaWriter.WriteNavigationPropertyElementHeader); + + if (element.OnDelete != EdmOnDeleteAction.None) + { + this.schemaWriter.WriteOperationActionElement(CsdlConstants.Element_OnDelete, element.OnDelete); + } + + this.ProcessReferentialConstraint(element.ReferentialConstraint); + + this.EndElement(element); + this.navigationProperties.Add(element); + } + + protected override void ProcessComplexType(IEdmComplexType element) + { + this.BeginElement(element, this.schemaWriter.WriteComplexTypeElementHeader); + base.ProcessComplexType(element); + this.EndElement(element); + } + + protected override void ProcessEnumType(IEdmEnumType element) + { + this.BeginElement(element, this.schemaWriter.WriteEnumTypeElementHeader); + base.ProcessEnumType(element); + this.EndElement(element); + } + + protected override void ProcessEnumMember(IEdmEnumMember element) + { + this.BeginElement(element, this.schemaWriter.WriteEnumMemberElementHeader); + this.EndElement(element); + } + + protected override void ProcessTypeDefinition(IEdmTypeDefinition element) + { + this.BeginElement(element, this.schemaWriter.WriteTypeDefinitionElementHeader); + base.ProcessTypeDefinition(element); + this.EndElement(element); + } + + protected override void ProcessTerm(IEdmTerm term) + { + bool inlineType = term.Type != null && IsInlineType(term.Type); + this.BeginElement(term, (IEdmTerm t) => { this.schemaWriter.WriteTermElementHeader(t, inlineType); }, e => { this.ProcessFacets(e.Type, inlineType); }); + if (!inlineType) + { + if (term.Type != null) + { + VisitTypeReference(term.Type); + } + } + + this.EndElement(term); + } + + protected override void ProcessAction(IEdmAction action) + { + this.ProcessOperation(action, this.schemaWriter.WriteActionElementHeader); + } + + protected override void ProcessFunction(IEdmFunction function) + { + this.ProcessOperation(function, this.schemaWriter.WriteFunctionElementHeader); + } + + protected override void ProcessOperationParameter(IEdmOperationParameter element) + { + bool inlineType = IsInlineType(element.Type); + this.BeginElement( + element, + (IEdmOperationParameter t) => { this.schemaWriter.WriteOperationParameterElementHeader(t, inlineType); }, + e => { this.ProcessFacets(e.Type, inlineType); }); + if (!inlineType) + { + VisitTypeReference(element.Type); + } + + this.VisitPrimitiveElementAnnotations(this.Model.DirectValueAnnotations(element)); + IEdmVocabularyAnnotatable vocabularyAnnotatableElement = element as IEdmVocabularyAnnotatable; + if (vocabularyAnnotatableElement != null) + { + this.VisitElementVocabularyAnnotations(this.Model.FindDeclaredVocabularyAnnotations(vocabularyAnnotatableElement).Where(a => a.IsInline(this.Model))); + } + + this.schemaWriter.WriteOperationParameterEndElement(element); + } + + protected override void ProcessOperationReturn(IEdmOperationReturn operationReturn) + { + if (operationReturn == null) + { + return; + } + + bool inlineType = IsInlineType(operationReturn.Type); + this.BeginElement( + operationReturn.Type, + type => this.schemaWriter.WriteReturnTypeElementHeader(), + type => + { + if (inlineType) + { + this.schemaWriter.WriteTypeAttribute(type); + this.ProcessFacets(type, true /*inlineType*/); + } + else + { + this.VisitTypeReference(type); + } + }); + this.EndElement(operationReturn); + } + + protected override void ProcessCollectionType(IEdmCollectionType element) + { + bool inlineType = IsInlineType(element.ElementType); + this.BeginElement( + element, + (IEdmCollectionType t) => this.schemaWriter.WriteCollectionTypeElementHeader(t, inlineType), + e => this.ProcessFacets(e.ElementType, inlineType)); + if (!inlineType) + { + VisitTypeReference(element.ElementType); + } + + this.EndElement(element); + } + + protected override void ProcessActionImport(IEdmActionImport actionImport) + { + this.BeginElement(actionImport, this.schemaWriter.WriteActionImportElementHeader); + this.EndElement(actionImport); + } + + protected override void ProcessFunctionImport(IEdmFunctionImport functionImport) + { + this.BeginElement(functionImport, this.schemaWriter.WriteFunctionImportElementHeader); + this.EndElement(functionImport); + } + + #region Vocabulary Annotations + + protected override void ProcessAnnotation(IEdmVocabularyAnnotation annotation) + { + bool isInline = IsInlineExpression(annotation.Value); + this.BeginElement(annotation, t => this.schemaWriter.WriteVocabularyAnnotationElementHeader(t, isInline)); + if (!isInline) + { + base.ProcessAnnotation(annotation); + } + + this.EndElement(annotation); + } + + #endregion + + #region Expressions + + protected override void ProcessStringConstantExpression(IEdmStringConstantExpression expression) + { + this.schemaWriter.WriteStringConstantExpressionElement(expression); + } + + protected override void ProcessBinaryConstantExpression(IEdmBinaryConstantExpression expression) + { + this.schemaWriter.WriteBinaryConstantExpressionElement(expression); + } + + protected override void ProcessRecordExpression(IEdmRecordExpression expression) + { + this.BeginElement(expression, this.schemaWriter.WriteRecordExpressionElementHeader); + this.VisitPropertyConstructors(expression.Properties); + this.EndElement(expression); + } + + protected override void ProcessLabeledExpression(IEdmLabeledExpression element) + { + if (element.Name == null) + { + base.ProcessLabeledExpression(element); + } + else + { + this.BeginElement(element, this.schemaWriter.WriteLabeledElementHeader); + base.ProcessLabeledExpression(element); + this.EndElement(element); + } + } + + protected override void ProcessPropertyConstructor(IEdmPropertyConstructor constructor) + { + bool isInline = IsInlineExpression(constructor.Value); + this.BeginElement(constructor, t => this.schemaWriter.WritePropertyConstructorElementHeader(t, isInline)); + if (!isInline) + { + base.ProcessPropertyConstructor(constructor); + } + + this.EndElement(constructor); + } + + protected override void ProcessPathExpression(IEdmPathExpression expression) + { + this.schemaWriter.WritePathExpressionElement(expression); + } + + protected override void ProcessPropertyPathExpression(IEdmPathExpression expression) + { + this.schemaWriter.WritePropertyPathExpressionElement(expression); + } + + protected override void ProcessNavigationPropertyPathExpression(IEdmPathExpression expression) + { + this.schemaWriter.WriteNavigationPropertyPathExpressionElement(expression); + } + + protected override void ProcessCollectionExpression(IEdmCollectionExpression expression) + { + this.BeginElement(expression, this.schemaWriter.WriteCollectionExpressionElementHeader); + this.VisitExpressions(expression.Elements); + this.EndElement(expression); + } + + protected override void ProcessIsTypeExpression(IEdmIsTypeExpression expression) + { + bool inlineType = IsInlineType(expression.Type); + this.BeginElement(expression, (IEdmIsTypeExpression t) => { this.schemaWriter.WriteIsTypeExpressionElementHeader(t, inlineType); }, e => { this.ProcessFacets(e.Type, inlineType); }); + if (!inlineType) + { + VisitTypeReference(expression.Type); + } + + this.VisitExpression(expression.Operand); + this.EndElement(expression); + } + + protected override void ProcessIntegerConstantExpression(IEdmIntegerConstantExpression expression) + { + this.schemaWriter.WriteIntegerConstantExpressionElement(expression); + } + + protected override void ProcessIfExpression(IEdmIfExpression expression) + { + this.BeginElement(expression, this.schemaWriter.WriteIfExpressionElementHeader); + base.ProcessIfExpression(expression); + this.EndElement(expression); + } + + protected override void ProcessFunctionApplicationExpression(IEdmApplyExpression expression) + { + this.BeginElement(expression, e => this.schemaWriter.WriteFunctionApplicationElementHeader(e)); + this.VisitExpressions(expression.Arguments); + this.EndElement(expression); + } + + protected override void ProcessFloatingConstantExpression(IEdmFloatingConstantExpression expression) + { + this.schemaWriter.WriteFloatingConstantExpressionElement(expression); + } + + protected override void ProcessGuidConstantExpression(IEdmGuidConstantExpression expression) + { + this.schemaWriter.WriteGuidConstantExpressionElement(expression); + } + + protected override void ProcessEnumMemberExpression(IEdmEnumMemberExpression expression) + { + this.schemaWriter.WriteEnumMemberExpressionElement(expression); + } + + protected override void ProcessDecimalConstantExpression(IEdmDecimalConstantExpression expression) + { + this.schemaWriter.WriteDecimalConstantExpressionElement(expression); + } + + protected override void ProcessDateConstantExpression(IEdmDateConstantExpression expression) + { + this.schemaWriter.WriteDateConstantExpressionElement(expression); + } + + protected override void ProcessDateTimeOffsetConstantExpression(IEdmDateTimeOffsetConstantExpression expression) + { + this.schemaWriter.WriteDateTimeOffsetConstantExpressionElement(expression); + } + + protected override void ProcessDurationConstantExpression(IEdmDurationConstantExpression expression) + { + this.schemaWriter.WriteDurationConstantExpressionElement(expression); + } + + protected override void ProcessTimeOfDayConstantExpression(IEdmTimeOfDayConstantExpression expression) + { + this.schemaWriter.WriteTimeOfDayConstantExpressionElement(expression); + } + + protected override void ProcessBooleanConstantExpression(IEdmBooleanConstantExpression expression) + { + this.schemaWriter.WriteBooleanConstantExpressionElement(expression); + } + + protected override void ProcessNullConstantExpression(IEdmNullExpression expression) + { + this.schemaWriter.WriteNullConstantExpressionElement(expression); + } + + protected override void ProcessCastExpression(IEdmCastExpression expression) + { + bool inlineType = IsInlineType(expression.Type); + this.BeginElement(expression, (IEdmCastExpression t) => { this.schemaWriter.WriteCastExpressionElementHeader(t, inlineType); }, e => { this.ProcessFacets(e.Type, inlineType); }); + if (!inlineType) + { + VisitTypeReference(expression.Type); + } + + this.VisitExpression(expression.Operand); + this.EndElement(expression); + } + + #endregion + + private static bool IsInlineType(IEdmTypeReference reference) + { + if (reference.Definition is IEdmSchemaElement || reference.IsEntityReference()) + { + return true; + } + else if (reference.IsCollection()) + { + return reference.AsCollection().CollectionDefinition().ElementType.Definition is IEdmSchemaElement; + } + + return false; + } + + private static bool IsInlineExpression(IEdmExpression expression) + { + switch (expression.ExpressionKind) + { + case EdmExpressionKind.BinaryConstant: + case EdmExpressionKind.BooleanConstant: + case EdmExpressionKind.DateConstant: + case EdmExpressionKind.DateTimeOffsetConstant: + case EdmExpressionKind.DecimalConstant: + case EdmExpressionKind.DurationConstant: + case EdmExpressionKind.FloatingConstant: + case EdmExpressionKind.GuidConstant: + case EdmExpressionKind.IntegerConstant: + case EdmExpressionKind.Path: + case EdmExpressionKind.PropertyPath: + case EdmExpressionKind.NavigationPropertyPath: + case EdmExpressionKind.StringConstant: + case EdmExpressionKind.TimeOfDayConstant: + return true; + } + + return false; + } + + private static string GetEntitySetString(IEdmOperationImport operationImport) + { + if (operationImport.EntitySet != null) + { + var pathExpression = operationImport.EntitySet as IEdmPathExpression; + if (pathExpression != null) + { + return EdmModelCsdlSchemaWriter.PathAsXml(pathExpression.PathSegments); + } + } + + return null; + } + + private void ProcessOperation(TOperation operation, Action writeElementAction) where TOperation : IEdmOperation + { + this.BeginElement(operation, writeElementAction); + this.VisitOperationParameters(operation.Parameters); + + IEdmOperationReturn operationReturn = operation.GetReturn(); + this.ProcessOperationReturn(operationReturn); + + this.EndElement(operation); + } + + private void ProcessReferentialConstraint(IEdmReferentialConstraint element) + { + if (element != null) + { + foreach (var pair in element.PropertyPairs) + { + this.schemaWriter.WriteReferentialConstraintPair(pair); + } + } + } + + private void ProcessFacets(IEdmTypeReference element, bool inlineType) + { + if (element != null) + { + if (element.IsEntityReference()) + { + // No facets get serialized for an entity reference. + return; + } + + if (inlineType) + { + if (element.TypeKind() == EdmTypeKind.Collection) + { + IEdmCollectionTypeReference collectionElement = element.AsCollection(); + this.schemaWriter.WriteNullableAttribute(collectionElement.CollectionDefinition().ElementType); + VisitTypeReference(collectionElement.CollectionDefinition().ElementType); + } + else + { + this.schemaWriter.WriteNullableAttribute(element); + VisitTypeReference(element); + } + } + } + } + + private void VisitEntityTypeDeclaredKey(IEnumerable keyProperties) + { + this.schemaWriter.WriteDelaredKeyPropertiesElementHeader(); + this.VisitPropertyRefs(keyProperties); + this.schemaWriter.WriteEndElement(); + } + + private void VisitPropertyRefs(IEnumerable properties) + { + foreach (IEdmStructuralProperty property in properties) + { + this.schemaWriter.WritePropertyRefElement(property); + } + } + + private void VisitAttributeAnnotations(IEnumerable annotations) + { + foreach (IEdmDirectValueAnnotation annotation in annotations) + { + if (annotation.NamespaceUri != EdmConstants.InternalUri) + { + var edmValue = annotation.Value as IEdmValue; + if (edmValue != null) + { + if (!edmValue.IsSerializedAsElement(this.Model)) + { + if (edmValue.Type.TypeKind() == EdmTypeKind.Primitive) + { + this.ProcessAttributeAnnotation(annotation); + } + } + } + } + } + } + + private void VisitPrimitiveElementAnnotations(IEnumerable annotations) + { + foreach (IEdmDirectValueAnnotation annotation in annotations) + { + if (annotation.NamespaceUri != EdmConstants.InternalUri) + { + var edmValue = annotation.Value as IEdmValue; + if (edmValue != null) + { + if (edmValue.IsSerializedAsElement(this.Model)) + { + if (edmValue.Type.TypeKind() == EdmTypeKind.Primitive) + { + this.ProcessElementAnnotation(annotation); + } + } + } + } + } + } + + private void ProcessAttributeAnnotation(IEdmDirectValueAnnotation annotation) + { + this.schemaWriter.WriteAnnotationStringAttribute(annotation); + } + + private void ProcessElementAnnotation(IEdmDirectValueAnnotation annotation) + { + this.schemaWriter.WriteAnnotationStringElement(annotation); + } + + private void VisitElementVocabularyAnnotations(IEnumerable annotations) + { + foreach (IEdmVocabularyAnnotation annotation in annotations) + { + this.ProcessAnnotation(annotation); + } + } + + private void BeginElement(TElement element, Action elementHeaderWriter, params Action[] additionalAttributeWriters) + where TElement : IEdmElement + { + elementHeaderWriter(element); + if (additionalAttributeWriters != null) + { + foreach (Action action in additionalAttributeWriters) + { + action(element); + } + } + + this.VisitAttributeAnnotations(this.Model.DirectValueAnnotations(element)); + } + + private void EndElement(IEdmElement element) + { + this.VisitPrimitiveElementAnnotations(this.Model.DirectValueAnnotations(element)); + IEdmVocabularyAnnotatable vocabularyAnnotatableElement = element as IEdmVocabularyAnnotatable; + if (vocabularyAnnotatableElement != null) + { + this.VisitElementVocabularyAnnotations(this.Model.FindDeclaredVocabularyAnnotations(vocabularyAnnotatableElement).Where(a => a.IsInline(this.Model))); + } + + this.schemaWriter.WriteEndElement(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelReferenceElementsVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelReferenceElementsVisitor.cs new file mode 100644 index 0000000..93b7fc0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelReferenceElementsVisitor.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Microsoft.OData.Edm.Csdl.Serialization +{ + /// + /// The visitor for outputing <edmx:referneced> elements for referenced model. + /// + internal class EdmModelReferenceElementsVisitor + { + private readonly EdmModelCsdlSchemaWriter schemaWriter; + + internal EdmModelReferenceElementsVisitor(IEdmModel model, XmlWriter xmlWriter, Version edmxVersion) + { + this.schemaWriter = new EdmModelCsdlSchemaWriter(model, model.GetNamespaceAliases(), xmlWriter, edmxVersion); + } + + #region write IEdmModel.References for referenced models. + internal void VisitEdmReferences(IEdmModel model) + { + IEnumerable references = model.GetEdmReferences(); + if (model != null && references != null) + { + foreach (IEdmReference tmp in references) + { + this.schemaWriter.WriteReferenceElementHeader(tmp); + if (tmp.Includes != null) + { + foreach (IEdmInclude include in tmp.Includes) + { + this.schemaWriter.WriteIncludeElement(include); + } + } + + if (tmp.IncludeAnnotations != null) + { + foreach (IEdmIncludeAnnotations includeAnnotations in tmp.IncludeAnnotations) + { + this.schemaWriter.WriteIncludeAnnotationsElement(includeAnnotations); + } + } + + this.schemaWriter.WriteEndElement(); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelSchemaSeparationSerializationVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelSchemaSeparationSerializationVisitor.cs new file mode 100644 index 0000000..d887daf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelSchemaSeparationSerializationVisitor.cs @@ -0,0 +1,192 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.Serialization +{ + internal class EdmModelSchemaSeparationSerializationVisitor : EdmModelVisitor + { + private bool visitCompleted = false; + private Dictionary modelSchemas = new Dictionary(); + private EdmSchema activeSchema; + + public EdmModelSchemaSeparationSerializationVisitor(IEdmModel visitedModel) + : base(visitedModel) + { + } + + public IEnumerable GetSchemas() + { + if (!this.visitCompleted) + { + this.Visit(); + } + + return this.modelSchemas.Values; + } + + protected void Visit() + { + this.VisitEdmModel(); + this.visitCompleted = true; + } + + protected override void ProcessModel(IEdmModel model) + { + this.ProcessElement(model); + this.VisitSchemaElements(model.SchemaElements); + this.VisitVocabularyAnnotations(model.VocabularyAnnotations.Where(a => !a.IsInline(this.Model))); + } + + protected override void ProcessVocabularyAnnotatable(IEdmVocabularyAnnotatable element) + { + this.VisitAnnotations(this.Model.DirectValueAnnotations(element)); + this.VisitVocabularyAnnotations(this.Model.FindDeclaredVocabularyAnnotations(element).Where(a => a.IsInline(this.Model))); + } + + protected override void ProcessSchemaElement(IEdmSchemaElement element) + { + string namespaceName = element.Namespace; + + // Put all of the namespaceless stuff into one schema. + if (EdmUtil.IsNullOrWhiteSpaceInternal(namespaceName)) + { + namespaceName = string.Empty; + } + + EdmSchema schema; + if (!this.modelSchemas.TryGetValue(namespaceName, out schema)) + { + schema = new EdmSchema(namespaceName); + this.modelSchemas.Add(namespaceName, schema); + } + + schema.AddSchemaElement(element); + this.activeSchema = schema; + + base.ProcessSchemaElement(element); + } + + protected override void ProcessVocabularyAnnotation(IEdmVocabularyAnnotation annotation) + { + if (!annotation.IsInline(this.Model)) + { + var annotationSchemaNamespace = annotation.GetSchemaNamespace(this.Model) ?? this.modelSchemas.Select(s => s.Key).FirstOrDefault() ?? string.Empty; + + EdmSchema annotationSchema; + if (!this.modelSchemas.TryGetValue(annotationSchemaNamespace, out annotationSchema)) + { + annotationSchema = new EdmSchema(annotationSchemaNamespace); + this.modelSchemas.Add(annotationSchema.Namespace, annotationSchema); + } + + annotationSchema.AddVocabularyAnnotation(annotation); + this.activeSchema = annotationSchema; + } + + if (annotation.Term != null) + { + this.CheckSchemaElementReference(annotation.Term); + } + + base.ProcessVocabularyAnnotation(annotation); + } + + /// + /// When we see an entity container, we see if it has . + /// If it does, then we attach it to that schema, otherwise we attached to the first existing schema. + /// If there are no schemas, we create the one named "Default" and attach container to it. + /// + /// The entity container being processed. + protected override void ProcessEntityContainer(IEdmEntityContainer element) + { + var containerSchemaNamespace = element.Namespace; + + EdmSchema containerSchema; + if (!this.modelSchemas.TryGetValue(containerSchemaNamespace, out containerSchema)) + { + containerSchema = new EdmSchema(containerSchemaNamespace); + this.modelSchemas.Add(containerSchema.Namespace, containerSchema); + } + + containerSchema.AddEntityContainer(element); + this.activeSchema = containerSchema; + + base.ProcessEntityContainer(element); + } + + protected override void ProcessComplexTypeReference(IEdmComplexTypeReference element) + { + this.CheckSchemaElementReference(element.ComplexDefinition()); + } + + protected override void ProcessEntityTypeReference(IEdmEntityTypeReference element) + { + this.CheckSchemaElementReference(element.EntityDefinition()); + } + + protected override void ProcessEntityReferenceTypeReference(IEdmEntityReferenceTypeReference element) + { + this.CheckSchemaElementReference(element.EntityType()); + } + + protected override void ProcessEnumTypeReference(IEdmEnumTypeReference element) + { + this.CheckSchemaElementReference(element.EnumDefinition()); + } + + protected override void ProcessTypeDefinitionReference(IEdmTypeDefinitionReference element) + { + this.CheckSchemaElementReference(element.TypeDefinition()); + } + + protected override void ProcessEntityType(IEdmEntityType element) + { + base.ProcessEntityType(element); + if (element.BaseEntityType() != null) + { + this.CheckSchemaElementReference(element.BaseEntityType()); + } + } + + protected override void ProcessComplexType(IEdmComplexType element) + { + base.ProcessComplexType(element); + if (element.BaseComplexType() != null) + { + this.CheckSchemaElementReference(element.BaseComplexType()); + } + } + + protected override void ProcessEnumType(IEdmEnumType element) + { + base.ProcessEnumType(element); + this.CheckSchemaElementReference(element.UnderlyingType); + } + + protected override void ProcessTypeDefinition(IEdmTypeDefinition element) + { + base.ProcessTypeDefinition(element); + this.CheckSchemaElementReference(element.UnderlyingType); + } + + private void CheckSchemaElementReference(IEdmSchemaElement element) + { + this.CheckSchemaElementReference(element.Namespace); + } + + private void CheckSchemaElementReference(string namespaceName) + { + if (this.activeSchema != null) + { + this.activeSchema.AddNamespaceUsing(namespaceName); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmSchema.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmSchema.cs new file mode 100644 index 0000000..c145c16 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/EdmSchema.cs @@ -0,0 +1,82 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.Serialization +{ + internal class EdmSchema + { + private readonly string schemaNamespace; + private readonly List schemaElements; + private readonly List entityContainers; + private readonly Dictionary> annotations; + private readonly List usedNamespaces; + + public EdmSchema(string namespaceString) + { + this.schemaNamespace = namespaceString; + this.schemaElements = new List(); + this.entityContainers = new List(); + this.annotations = new Dictionary>(); + this.usedNamespaces = new List(); + } + + public string Namespace + { + get { return this.schemaNamespace; } + } + + public List SchemaElements + { + get { return this.schemaElements; } + } + + public List EntityContainers + { + get { return this.entityContainers; } + } + + public IEnumerable>> OutOfLineAnnotations + { + get { return this.annotations; } + } + + public void AddSchemaElement(IEdmSchemaElement element) + { + this.schemaElements.Add(element); + } + + public void AddEntityContainer(IEdmEntityContainer container) + { + this.entityContainers.Add(container); + } + + public void AddNamespaceUsing(string usedNamespace) + { + if (usedNamespace != EdmConstants.EdmNamespace) + { + if (!this.usedNamespaces.Contains(usedNamespace)) + { + this.usedNamespaces.Add(usedNamespace); + } + } + } + + public void AddVocabularyAnnotation(IEdmVocabularyAnnotation annotation) + { + List annotationList; + if (!this.annotations.TryGetValue(annotation.TargetString(), out annotationList)) + { + annotationList = new List(); + this.annotations[annotation.TargetString()] = annotationList; + } + + annotationList.Add(annotation); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/SerializationValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/SerializationValidator.cs new file mode 100644 index 0000000..be25a9f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/Serialization/SerializationValidator.cs @@ -0,0 +1,181 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl.Serialization +{ + internal static class SerializationValidator + { + #region Additional Rules + + /// + /// Validates that a type reference refers to a type that can be represented in CSDL. + /// + private static readonly ValidationRule TypeReferenceTargetMustHaveValidName = + new ValidationRule( + (context, typeReference) => + { + IEdmSchemaType schemaType = typeReference.Definition as IEdmSchemaType; + if (schemaType != null) + { + if (!EdmUtil.IsQualifiedName(schemaType.FullName())) + { + context.AddError( + typeReference.Location(), + EdmErrorCode.ReferencedTypeMustHaveValidName, + Strings.Serializer_ReferencedTypeMustHaveValidName(schemaType.FullName())); + } + } + }); + + /// + /// Validates that a type reference refers to a type that can be represented in CSDL. + /// + private static readonly ValidationRule EntityReferenceTargetMustHaveValidName = + new ValidationRule( + (context, entityReference) => + { + if (!EdmUtil.IsQualifiedName(entityReference.EntityType.FullName())) + { + context.AddError( + entityReference.Location(), + EdmErrorCode.ReferencedTypeMustHaveValidName, + Strings.Serializer_ReferencedTypeMustHaveValidName(entityReference.EntityType.FullName())); + } + }); + + /// + /// Validates that an entity set refers to a type that can be represented in CSDL. + /// + private static readonly ValidationRule EntitySetTypeMustHaveValidName = + new ValidationRule( + (context, set) => + { + if (!EdmUtil.IsQualifiedName(set.EntityType().FullName())) + { + context.AddError( + set.Location(), + EdmErrorCode.ReferencedTypeMustHaveValidName, + Strings.Serializer_ReferencedTypeMustHaveValidName(set.EntityType().FullName())); + } + }); + + /// + /// Validates that a structured type's base type can be represented in CSDL. + /// + private static readonly ValidationRule StructuredTypeBaseTypeMustHaveValidName = + new ValidationRule( + (context, type) => + { + IEdmSchemaType schemaBaseType = type.BaseType as IEdmSchemaType; + if (schemaBaseType != null) + { + if (!EdmUtil.IsQualifiedName(schemaBaseType.FullName())) + { + context.AddError( + type.Location(), + EdmErrorCode.ReferencedTypeMustHaveValidName, + Strings.Serializer_ReferencedTypeMustHaveValidName(schemaBaseType.FullName())); + } + } + }); + + /// + /// Validates that vocabulary annotations serialized out of line have a serializable target name. + /// + private static readonly ValidationRule VocabularyAnnotationOutOfLineMustHaveValidTargetName = + new ValidationRule( + (context, annotation) => + { + if (annotation.GetSerializationLocation(context.Model) == EdmVocabularyAnnotationSerializationLocation.OutOfLine && !EdmUtil.IsQualifiedName(annotation.TargetString())) + { + context.AddError( + annotation.Location(), + EdmErrorCode.InvalidName, + Strings.Serializer_OutOfLineAnnotationTargetMustHaveValidName(EdmUtil.FullyQualifiedName(annotation.Target))); + } + }); + + /// + /// Validates that vocabulary annotations have a serializable term name. + /// + private static readonly ValidationRule VocabularyAnnotationMustHaveValidTermName = + new ValidationRule( + (context, annotation) => + { + if (!EdmUtil.IsQualifiedName(annotation.Term.FullName())) + { + context.AddError( + annotation.Location(), + EdmErrorCode.InvalidName, + Strings.Serializer_OutOfLineAnnotationTargetMustHaveValidName(annotation.Term.FullName())); + } + }); + + #endregion + + private static ValidationRuleSet serializationRuleSet = new ValidationRuleSet(new ValidationRule[] + { + TypeReferenceTargetMustHaveValidName, + EntityReferenceTargetMustHaveValidName, + EntitySetTypeMustHaveValidName, + StructuredTypeBaseTypeMustHaveValidName, + VocabularyAnnotationOutOfLineMustHaveValidTargetName, + VocabularyAnnotationMustHaveValidTermName, + ValidationRules.OperationImportEntitySetExpressionIsInvalid, + ValidationRules.TypeMustNotHaveKindOfNone, + ValidationRules.PrimitiveTypeMustNotHaveKindOfNone, + ValidationRules.PropertyMustNotHaveKindOfNone, + ValidationRules.SchemaElementMustNotHaveKindOfNone, + ValidationRules.EntityContainerElementMustNotHaveKindOfNone, + ValidationRules.EnumMustHaveIntegerUnderlyingType, + ValidationRules.EnumMemberValueMustHaveSameTypeAsUnderlyingType + }); + + public static IEnumerable GetSerializationErrors(this IEdmModel root) + { + IEnumerable errors; + root.Validate(serializationRuleSet, out errors); + errors = errors.Where(SignificantToSerialization); + return errors; + } + + internal static bool SignificantToSerialization(EdmError error) + { + if (ValidationHelper.IsInterfaceCritical(error)) + { + return true; + } + + switch (error.ErrorCode) + { + case EdmErrorCode.InvalidName: + case EdmErrorCode.NameTooLong: + case EdmErrorCode.InvalidNamespaceName: + case EdmErrorCode.SystemNamespaceEncountered: + case EdmErrorCode.ReferencedTypeMustHaveValidName: + case EdmErrorCode.OperationImportEntitySetExpressionIsInvalid: + case EdmErrorCode.OperationImportParameterIncorrectType: + case EdmErrorCode.InvalidOperationImportParameterMode: + case EdmErrorCode.TypeMustNotHaveKindOfNone: + case EdmErrorCode.PrimitiveTypeMustNotHaveKindOfNone: + case EdmErrorCode.PropertyMustNotHaveKindOfNone: + case EdmErrorCode.SchemaElementMustNotHaveKindOfNone: + case EdmErrorCode.EntityContainerElementMustNotHaveKindOfNone: + case EdmErrorCode.BinaryValueCannotHaveEmptyValue: + case EdmErrorCode.EnumMustHaveIntegerUnderlyingType: + case EdmErrorCode.EnumMemberValueOutOfRange: + return true; + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SerializationExtensionMethods.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SerializationExtensionMethods.cs new file mode 100644 index 0000000..3dcbec4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Csdl/SerializationExtensionMethods.cs @@ -0,0 +1,308 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Csdl +{ + /// + /// Represents whether a vocabulary annotation should be serialized within the element it applies to or in a separate section of the CSDL. + /// + public enum EdmVocabularyAnnotationSerializationLocation + { + /// + /// The annotation should be serialized within the element being annotated. + /// + Inline, + + /// + /// The annotation should be serialized in a separate section. + /// + OutOfLine + } + + /// + /// Contains extension methods for interfaces that are useful to serialization. + /// + public static class SerializationExtensionMethods + { + #region EdmxVersion + + /// + /// Gets the value for the EDMX version of the . + /// + /// Model the version has been set for. + /// The version. + public static Version GetEdmxVersion(this IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + return model.GetAnnotationValue(model, EdmConstants.InternalUri, CsdlConstants.EdmxVersionAnnotation); + } + + /// + /// Sets a value of EDMX version attribute of the . + /// + /// The model the version should be set for. + /// The version. + public static void SetEdmxVersion(this IEdmModel model, Version version) + { + EdmUtil.CheckArgumentNull(model, "model"); + model.SetAnnotationValue(model, EdmConstants.InternalUri, CsdlConstants.EdmxVersionAnnotation, version); + } + + #endregion + + #region NamespacePrefixMappings + + /// + /// Sets an annotation on the IEdmModel to notify the serializer of preferred prefix mappings for xml namespaces. + /// + /// Reference to the calling object. + /// XmlNamespaceManage containing mappings between namespace prefixes and xml namespaces. + public static void SetNamespacePrefixMappings(this IEdmModel model, IEnumerable> mappings) + { + EdmUtil.CheckArgumentNull(model, "model"); + model.SetAnnotationValue(model, EdmConstants.InternalUri, CsdlConstants.NamespacePrefixAnnotation, mappings); + } + + /// + /// Gets the preferred prefix mappings for xml namespaces from an IEdmModel + /// + /// Reference to the calling object. + /// Namespace prefixes that exist on the model. + public static IEnumerable> GetNamespacePrefixMappings(this IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + return model.GetAnnotationValue>>(model, EdmConstants.InternalUri, CsdlConstants.NamespacePrefixAnnotation); + } + + #endregion + + #region SerializationLocation + + /// + /// Sets the location an annotation should be serialized in. + /// + /// The annotation the location is being specified for. + /// Model containing the annotation. + /// The location the annotation should appear. + public static void SetSerializationLocation(this IEdmVocabularyAnnotation annotation, IEdmModel model, EdmVocabularyAnnotationSerializationLocation? location) + { + EdmUtil.CheckArgumentNull(annotation, "annotation"); + EdmUtil.CheckArgumentNull(model, "model"); + + model.SetAnnotationValue(annotation, EdmConstants.InternalUri, CsdlConstants.AnnotationSerializationLocationAnnotation, (object)location); + } + + /// + /// Gets the location an annotation should be serialized in. + /// + /// Reference to the calling annotation. + /// Model containing the annotation. + /// The location the annotation should be serialized at. + public static EdmVocabularyAnnotationSerializationLocation? GetSerializationLocation(this IEdmVocabularyAnnotation annotation, IEdmModel model) + { + EdmUtil.CheckArgumentNull(annotation, "annotation"); + EdmUtil.CheckArgumentNull(model, "model"); + + return model.GetAnnotationValue(annotation, EdmConstants.InternalUri, CsdlConstants.AnnotationSerializationLocationAnnotation) as EdmVocabularyAnnotationSerializationLocation?; + } + + #endregion + + #region SchemaNamespace + + /// + /// Sets the schema an annotation should appear in. + /// + /// The annotation the schema should be set for. + /// Model containing the annotation. + /// The schema the annotation belongs in. + public static void SetSchemaNamespace(this IEdmVocabularyAnnotation annotation, IEdmModel model, string schemaNamespace) + { + EdmUtil.CheckArgumentNull(annotation, "annotation"); + EdmUtil.CheckArgumentNull(model, "model"); + model.SetAnnotationValue(annotation, EdmConstants.InternalUri, CsdlConstants.SchemaNamespaceAnnotation, schemaNamespace); + } + + /// + /// Gets the schema an annotation should be serialized in. + /// + /// Reference to the calling annotation. + /// Model containing the annotation. + /// Name of the schema the annotation belongs to. + public static string GetSchemaNamespace(this IEdmVocabularyAnnotation annotation, IEdmModel model) + { + EdmUtil.CheckArgumentNull(annotation, "annotation"); + EdmUtil.CheckArgumentNull(model, "model"); + return model.GetAnnotationValue(annotation, EdmConstants.InternalUri, CsdlConstants.SchemaNamespaceAnnotation); + } + + #endregion + + #region IsValueExplicit + + /// + /// Sets an annotation indicating whether the value of an enum member should be explicitly serialized. + /// + /// Member to set the annotation on. + /// Model containing the member. + /// If the value of the enum member should be explicitly serialized + public static void SetIsValueExplicit(this IEdmEnumMember member, IEdmModel model, bool? isExplicit) + { + EdmUtil.CheckArgumentNull(member, "member"); + EdmUtil.CheckArgumentNull(model, "model"); + model.SetAnnotationValue(member, EdmConstants.InternalUri, CsdlConstants.IsEnumMemberValueExplicitAnnotation, (object)isExplicit); + } + + /// + /// Gets an annotation indicating whether the value of an enum member should be explicitly serialized. + /// + /// The member the annotation is on. + /// Model containing the member. + /// Whether the member should have its value serialized. + public static bool? IsValueExplicit(this IEdmEnumMember member, IEdmModel model) + { + EdmUtil.CheckArgumentNull(member, "member"); + EdmUtil.CheckArgumentNull(model, "model"); + return model.GetAnnotationValue(member, EdmConstants.InternalUri, CsdlConstants.IsEnumMemberValueExplicitAnnotation) as bool?; + } + + #endregion + + #region IsSerializedAsElement + + /// + /// Sets an annotation indicating if the value should be serialized as an element. + /// + /// Value to set the annotation on. + /// Model containing the value. + /// Value indicating if the value should be serialized as an element. + public static void SetIsSerializedAsElement(this IEdmValue value, IEdmModel model, bool isSerializedAsElement) + { + EdmUtil.CheckArgumentNull(value, "value"); + EdmUtil.CheckArgumentNull(model, "model"); + if (isSerializedAsElement) + { + EdmError error; + if (!ValidationHelper.ValidateValueCanBeWrittenAsXmlElementAnnotation(value, null, null, out error)) + { + throw new InvalidOperationException(error.ToString()); + } + } + + model.SetAnnotationValue(value, EdmConstants.InternalUri, CsdlConstants.IsSerializedAsElementAnnotation, (object)isSerializedAsElement); + } + + /// + /// Gets an annotation indicating if the value should be serialized as an element. + /// + /// Value the annotation is on. + /// Model containing the value. + /// Value indicating if the string should be serialized as an element. + public static bool IsSerializedAsElement(this IEdmValue value, IEdmModel model) + { + EdmUtil.CheckArgumentNull(value, "value"); + EdmUtil.CheckArgumentNull(model, "model"); + return (model.GetAnnotationValue(value, EdmConstants.InternalUri, CsdlConstants.IsSerializedAsElementAnnotation) as bool?) ?? false; + } + + #endregion + + #region NamespaceAliases + + /// + /// Sets the serialization alias for a given namespace(including current model's schemas namespace-alias, and referenced models' schemas namespace-alias) + /// TODO: REF make sure no duplicated alias. + /// + /// Model that will be serialized. + /// The namespace to set the alias for. + /// The alias for that namespace. + public static void SetNamespaceAlias(this IEdmModel model, string namespaceName, string alias) + { + VersioningDictionary mappings = model.GetAnnotationValue>(model, EdmConstants.InternalUri, CsdlConstants.NamespaceAliasAnnotation); + if (mappings == null) + { + mappings = VersioningDictionary.Create(string.CompareOrdinal); + } + + if (EdmUtil.IsNullOrWhiteSpaceInternal(alias)) + { + string val; + if (mappings.TryGetValue(namespaceName, out val)) + { + mappings = mappings.Remove(namespaceName); + } + } + else + { + mappings = mappings.Set(namespaceName, alias); + } + + model.SetAnnotationValue(model, EdmConstants.InternalUri, CsdlConstants.NamespaceAliasAnnotation, mappings); + + var list = model.GetAnnotationValue>(model, EdmConstants.InternalUri, CsdlConstants.UsedNamespacesAnnotation); + if (list == null) + { + list = VersioningList.Create(); + } + + if (!string.IsNullOrEmpty(namespaceName) && !list.Contains(namespaceName)) + { + list = list.Add(namespaceName); + } + + model.SetAnnotationValue(model, EdmConstants.InternalUri, CsdlConstants.UsedNamespacesAnnotation, list); + } + + /// + /// Gets the serialization alias for a given namespace. + /// + /// Model that will be serialized. + /// Namespace the alias is needed for. + /// The alias of the given namespace, or null if one does not exist. + public static string GetNamespaceAlias(this IEdmModel model, string namespaceName) + { + VersioningDictionary mappings = model.GetAnnotationValue>(model, EdmConstants.InternalUri, CsdlConstants.NamespaceAliasAnnotation); + return mappings.Get(namespaceName); + } + + // This internal method exists so we can get a consistent view of the mappings through the entire serialization process. + // Otherwise, changes to the dictionary durring serialization would result in an invalid or inconsistent output. + internal static VersioningDictionary GetNamespaceAliases(this IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + return model.GetAnnotationValue>(model, EdmConstants.InternalUri, CsdlConstants.NamespaceAliasAnnotation); + } + + /// + /// Gets the namespaces in all schemas having alias, excluding those without alias. + /// + /// The IEdmModel. + /// The namespaces in all schemas having alias. + internal static VersioningList GetUsedNamespacesHavingAlias(this IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + return model.GetAnnotationValue>(model, EdmConstants.InternalUri, CsdlConstants.UsedNamespacesAnnotation); + } + + #endregion + + internal static bool IsInline(this IEdmVocabularyAnnotation annotation, IEdmModel model) + { + return annotation.GetSerializationLocation(model) == EdmVocabularyAnnotationSerializationLocation.Inline || annotation.TargetString() == null; + } + + internal static string TargetString(this IEdmVocabularyAnnotation annotation) + { + return EdmUtil.FullyQualifiedName(annotation.Target); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmLocation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmLocation.cs new file mode 100644 index 0000000..df824b3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmLocation.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents the location of an Edm item. + /// + public abstract class EdmLocation + { + /// + /// Gets a string representation of the location. + /// + /// A string representation of the location. + public abstract override string ToString(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmModelVisitor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmModelVisitor.cs new file mode 100644 index 0000000..60430f0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmModelVisitor.cs @@ -0,0 +1,872 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + internal abstract class EdmModelVisitor + { + protected readonly IEdmModel Model; + + protected EdmModelVisitor(IEdmModel model) + { + this.Model = model; + } + + public void VisitEdmModel() + { + this.ProcessModel(this.Model); + } + + #region Visit Methods + + #region Elements + + public void VisitSchemaElements(IEnumerable elements) + { + VisitCollection(elements, this.VisitSchemaElement); + } + + public void VisitSchemaElement(IEdmSchemaElement element) + { + switch (element.SchemaElementKind) + { + case EdmSchemaElementKind.Action: + this.ProcessAction((IEdmAction)element); + break; + case EdmSchemaElementKind.Function: + this.ProcessFunction((IEdmFunction)element); + break; + case EdmSchemaElementKind.TypeDefinition: + this.VisitSchemaType((IEdmType)element); + break; + case EdmSchemaElementKind.Term: + this.ProcessTerm((IEdmTerm)element); + break; + case EdmSchemaElementKind.EntityContainer: + this.ProcessEntityContainer((IEdmEntityContainer)element); + break; + case EdmSchemaElementKind.None: + this.ProcessSchemaElement(element); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_SchemaElementKind(element.SchemaElementKind)); + } + } + + #endregion + + #region Annotations + + public void VisitAnnotations(IEnumerable annotations) + { + VisitCollection(annotations, this.VisitAnnotation); + } + + public void VisitVocabularyAnnotations(IEnumerable annotations) + { + VisitCollection(annotations, this.VisitVocabularyAnnotation); + } + + public void VisitAnnotation(IEdmDirectValueAnnotation annotation) + { + this.ProcessImmediateValueAnnotation((IEdmDirectValueAnnotation)annotation); + } + + public void VisitVocabularyAnnotation(IEdmVocabularyAnnotation annotation) + { + if (annotation.Term != null) + { + this.ProcessAnnotation(annotation); + } + else + { + this.ProcessVocabularyAnnotation(annotation); + } + } + + public void VisitPropertyValueBindings(IEnumerable bindings) + { + VisitCollection(bindings, this.ProcessPropertyValueBinding); + } + + #endregion + + #region Expressions + + public void VisitExpressions(IEnumerable expressions) + { + VisitCollection(expressions, this.VisitExpression); + } + + public void VisitExpression(IEdmExpression expression) + { + switch (expression.ExpressionKind) + { + case EdmExpressionKind.Cast: + this.ProcessCastExpression((IEdmCastExpression)expression); + break; + case EdmExpressionKind.BinaryConstant: + this.ProcessBinaryConstantExpression((IEdmBinaryConstantExpression)expression); + break; + case EdmExpressionKind.BooleanConstant: + this.ProcessBooleanConstantExpression((IEdmBooleanConstantExpression)expression); + break; + case EdmExpressionKind.Collection: + this.ProcessCollectionExpression((IEdmCollectionExpression)expression); + break; + case EdmExpressionKind.DateConstant: + this.ProcessDateConstantExpression((IEdmDateConstantExpression)expression); + break; + case EdmExpressionKind.DateTimeOffsetConstant: + this.ProcessDateTimeOffsetConstantExpression((IEdmDateTimeOffsetConstantExpression)expression); + break; + case EdmExpressionKind.DecimalConstant: + this.ProcessDecimalConstantExpression((IEdmDecimalConstantExpression)expression); + break; + case EdmExpressionKind.EnumMember: + this.ProcessEnumMemberExpression((IEdmEnumMemberExpression)expression); + break; + case EdmExpressionKind.FloatingConstant: + this.ProcessFloatingConstantExpression((IEdmFloatingConstantExpression)expression); + break; + case EdmExpressionKind.FunctionApplication: + this.ProcessFunctionApplicationExpression((IEdmApplyExpression)expression); + break; + case EdmExpressionKind.GuidConstant: + this.ProcessGuidConstantExpression((IEdmGuidConstantExpression)expression); + break; + case EdmExpressionKind.If: + this.ProcessIfExpression((IEdmIfExpression)expression); + break; + case EdmExpressionKind.IntegerConstant: + this.ProcessIntegerConstantExpression((IEdmIntegerConstantExpression)expression); + break; + case EdmExpressionKind.IsType: + this.ProcessIsTypeExpression((IEdmIsTypeExpression)expression); + break; + case EdmExpressionKind.LabeledExpressionReference: + this.ProcessLabeledExpressionReferenceExpression((IEdmLabeledExpressionReferenceExpression)expression); + break; + case EdmExpressionKind.Labeled: + this.ProcessLabeledExpression((IEdmLabeledExpression)expression); + break; + case EdmExpressionKind.Null: + this.ProcessNullConstantExpression((IEdmNullExpression)expression); + break; + case EdmExpressionKind.Path: + this.ProcessPathExpression((IEdmPathExpression)expression); + break; + case EdmExpressionKind.PropertyPath: + this.ProcessPropertyPathExpression((IEdmPathExpression)expression); + break; + case EdmExpressionKind.NavigationPropertyPath: + this.ProcessNavigationPropertyPathExpression((IEdmPathExpression)expression); + break; + case EdmExpressionKind.Record: + this.ProcessRecordExpression((IEdmRecordExpression)expression); + break; + case EdmExpressionKind.StringConstant: + this.ProcessStringConstantExpression((IEdmStringConstantExpression)expression); + break; + case EdmExpressionKind.TimeOfDayConstant: + this.ProcessTimeOfDayConstantExpression((IEdmTimeOfDayConstantExpression)expression); + break; + case EdmExpressionKind.DurationConstant: + this.ProcessDurationConstantExpression((IEdmDurationConstantExpression)expression); + break; + case EdmExpressionKind.None: + this.ProcessExpression(expression); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_ExpressionKind(expression.ExpressionKind)); + } + } + + public void VisitPropertyConstructors(IEnumerable constructor) + { + VisitCollection(constructor, this.ProcessPropertyConstructor); + } + + #endregion + + #region Data Model + + public virtual void VisitEntityContainerElements(IEnumerable elements) + { + foreach (IEdmEntityContainerElement element in elements) + { + switch (element.ContainerElementKind) + { + case EdmContainerElementKind.EntitySet: + this.ProcessEntitySet((IEdmEntitySet)element); + break; + case EdmContainerElementKind.Singleton: + this.ProcessSingleton((IEdmSingleton)element); + break; + case EdmContainerElementKind.ActionImport: + this.ProcessActionImport((IEdmActionImport)element); + break; + case EdmContainerElementKind.FunctionImport: + this.ProcessFunctionImport((IEdmFunctionImport)element); + break; + case EdmContainerElementKind.None: + this.ProcessEntityContainerElement(element); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_ContainerElementKind(element.ContainerElementKind.ToString())); + } + } + } + + #endregion + + #region Type References + + public void VisitTypeReference(IEdmTypeReference reference) + { + switch (reference.TypeKind()) + { + case EdmTypeKind.Collection: + this.ProcessCollectionTypeReference(reference.AsCollection()); + break; + case EdmTypeKind.Complex: + this.ProcessComplexTypeReference(reference.AsComplex()); + break; + case EdmTypeKind.Entity: + this.ProcessEntityTypeReference(reference.AsEntity()); + break; + case EdmTypeKind.EntityReference: + this.ProcessEntityReferenceTypeReference(reference.AsEntityReference()); + break; + case EdmTypeKind.Enum: + this.ProcessEnumTypeReference(reference.AsEnum()); + break; + case EdmTypeKind.Primitive: + this.VisitPrimitiveTypeReference(reference.AsPrimitive()); + break; + case EdmTypeKind.TypeDefinition: + this.ProcessTypeDefinitionReference(reference.AsTypeDefinition()); + break; + case EdmTypeKind.None: + this.ProcessTypeReference(reference); + break; + case EdmTypeKind.Path: + this.ProcessPathTypeReference(reference.AsPath()); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_TypeKind(reference.TypeKind().ToString())); + } + } + + public void VisitPrimitiveTypeReference(IEdmPrimitiveTypeReference reference) + { + switch (reference.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Binary: + this.ProcessBinaryTypeReference(reference.AsBinary()); + break; + case EdmPrimitiveTypeKind.Decimal: + this.ProcessDecimalTypeReference(reference.AsDecimal()); + break; + case EdmPrimitiveTypeKind.String: + this.ProcessStringTypeReference(reference.AsString()); + break; + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + this.ProcessTemporalTypeReference(reference.AsTemporal()); + break; + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + this.ProcessSpatialTypeReference(reference.AsSpatial()); + break; + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.PrimitiveType: + case EdmPrimitiveTypeKind.None: + this.ProcessPrimitiveTypeReference(reference); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_PrimitiveKind(reference.PrimitiveKind().ToString())); + } + } + + #endregion + + #region Type Definitions + + public void VisitSchemaType(IEdmType definition) + { + switch (definition.TypeKind) + { + case EdmTypeKind.Complex: + this.ProcessComplexType((IEdmComplexType)definition); + break; + case EdmTypeKind.Entity: + this.ProcessEntityType((IEdmEntityType)definition); + break; + case EdmTypeKind.Enum: + this.ProcessEnumType((IEdmEnumType)definition); + break; + case EdmTypeKind.TypeDefinition: + this.ProcessTypeDefinition((IEdmTypeDefinition)definition); + break; + case EdmTypeKind.None: + this.VisitSchemaType(definition); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_TypeKind(definition.TypeKind)); + } + } + + public void VisitProperties(IEnumerable properties) + { + VisitCollection(properties, this.VisitProperty); + } + + public void VisitProperty(IEdmProperty property) + { + switch (property.PropertyKind) + { + case EdmPropertyKind.Navigation: + this.ProcessNavigationProperty((IEdmNavigationProperty)property); + break; + case EdmPropertyKind.Structural: + this.ProcessStructuralProperty((IEdmStructuralProperty)property); + break; + case EdmPropertyKind.None: + this.ProcessProperty(property); + break; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_PropertyKind(property.PropertyKind.ToString())); + } + } + + public void VisitEnumMembers(IEnumerable enumMembers) + { + VisitCollection(enumMembers, this.VisitEnumMember); + } + + public void VisitEnumMember(IEdmEnumMember enumMember) + { + this.ProcessEnumMember(enumMember); + } + + #endregion + + #region Operation Related + + public void VisitOperationParameters(IEnumerable parameters) + { + VisitCollection(parameters, this.ProcessOperationParameter); + } + + #endregion + + protected static void VisitCollection(IEnumerable collection, Action visitMethod) + { + foreach (T element in collection) + { + visitMethod(element); + } + } + #endregion + + #region Process Methods + + protected virtual void ProcessModel(IEdmModel model) + { + this.ProcessElement(model); + + // TODO: also visit referneced models? + this.VisitSchemaElements(model.SchemaElements); + this.VisitVocabularyAnnotations(model.VocabularyAnnotations); + } + + #region Base Element Types + + protected virtual void ProcessElement(IEdmElement element) + { + // TODO: DirectValueAnnotationsInMainSechema (not including those in referenced schemas) + this.VisitAnnotations(this.Model.DirectValueAnnotations(element)); + } + + protected virtual void ProcessNamedElement(IEdmNamedElement element) + { + this.ProcessElement(element); + } + + protected virtual void ProcessSchemaElement(IEdmSchemaElement element) + { + this.ProcessVocabularyAnnotatable(element); + this.ProcessNamedElement(element); + } + + protected virtual void ProcessVocabularyAnnotatable(IEdmVocabularyAnnotatable annotatable) + { + } + + #endregion + + #region Type References + + protected virtual void ProcessComplexTypeReference(IEdmComplexTypeReference reference) + { + this.ProcessStructuredTypeReference(reference); + } + + protected virtual void ProcessEntityTypeReference(IEdmEntityTypeReference reference) + { + this.ProcessStructuredTypeReference(reference); + } + + protected virtual void ProcessEntityReferenceTypeReference(IEdmEntityReferenceTypeReference reference) + { + this.ProcessTypeReference(reference); + this.ProcessEntityReferenceType(reference.EntityReferenceDefinition()); + } + + protected virtual void ProcessCollectionTypeReference(IEdmCollectionTypeReference reference) + { + this.ProcessTypeReference(reference); + this.ProcessCollectionType(reference.CollectionDefinition()); + } + + protected virtual void ProcessEnumTypeReference(IEdmEnumTypeReference reference) + { + this.ProcessTypeReference(reference); + } + + protected virtual void ProcessTypeDefinitionReference(IEdmTypeDefinitionReference reference) + { + this.ProcessTypeReference(reference); + } + + protected virtual void ProcessBinaryTypeReference(IEdmBinaryTypeReference reference) + { + this.ProcessPrimitiveTypeReference(reference); + } + + protected virtual void ProcessDecimalTypeReference(IEdmDecimalTypeReference reference) + { + this.ProcessPrimitiveTypeReference(reference); + } + + protected virtual void ProcessSpatialTypeReference(IEdmSpatialTypeReference reference) + { + this.ProcessPrimitiveTypeReference(reference); + } + + protected virtual void ProcessStringTypeReference(IEdmStringTypeReference reference) + { + this.ProcessPrimitiveTypeReference(reference); + } + + protected virtual void ProcessTemporalTypeReference(IEdmTemporalTypeReference reference) + { + this.ProcessPrimitiveTypeReference(reference); + } + + protected virtual void ProcessPrimitiveTypeReference(IEdmPrimitiveTypeReference reference) + { + this.ProcessTypeReference(reference); + } + + protected virtual void ProcessStructuredTypeReference(IEdmStructuredTypeReference reference) + { + this.ProcessTypeReference(reference); + } + + protected virtual void ProcessTypeReference(IEdmTypeReference element) + { + this.ProcessElement(element); + } + + protected virtual void ProcessPathTypeReference(IEdmPathTypeReference reference) + { + this.ProcessTypeReference(reference); + } + + #endregion + + #region Terms + + protected virtual void ProcessTerm(IEdmTerm term) + { + this.ProcessSchemaElement(term); + this.VisitTypeReference(term.Type); + } + + #endregion + + #region Type Definitions + + protected virtual void ProcessComplexType(IEdmComplexType definition) + { + this.ProcessSchemaElement(definition); + this.ProcessStructuredType(definition); + this.ProcessSchemaType(definition); + } + + protected virtual void ProcessEntityType(IEdmEntityType definition) + { + this.ProcessSchemaElement(definition); + this.ProcessStructuredType(definition); + this.ProcessSchemaType(definition); + } + + protected virtual void ProcessCollectionType(IEdmCollectionType definition) + { + this.ProcessElement(definition); + this.ProcessType(definition); + this.VisitTypeReference(definition.ElementType); + } + + protected virtual void ProcessEnumType(IEdmEnumType definition) + { + this.ProcessSchemaElement(definition); + this.ProcessType(definition); + this.ProcessSchemaType(definition); + this.VisitEnumMembers(definition.Members); + } + + protected virtual void ProcessTypeDefinition(IEdmTypeDefinition definition) + { + this.ProcessSchemaElement(definition); + this.ProcessType(definition); + this.ProcessSchemaType(definition); + } + + protected virtual void ProcessEntityReferenceType(IEdmEntityReferenceType definition) + { + this.ProcessElement(definition); + this.ProcessType(definition); + } + + protected virtual void ProcessStructuredType(IEdmStructuredType definition) + { + this.ProcessType(definition); + this.VisitProperties(definition.DeclaredProperties); + } + + protected virtual void ProcessSchemaType(IEdmSchemaType type) + { + // Do not visit type or schema element, because all types will do that on thier own. + } + + protected virtual void ProcessType(IEdmType definition) + { + } + + #endregion + + #region Definition Components + + protected virtual void ProcessNavigationProperty(IEdmNavigationProperty property) + { + this.ProcessProperty(property); + } + + protected virtual void ProcessStructuralProperty(IEdmStructuralProperty property) + { + this.ProcessProperty(property); + } + + protected virtual void ProcessProperty(IEdmProperty property) + { + this.ProcessVocabularyAnnotatable(property); + this.ProcessNamedElement(property); + this.VisitTypeReference(property.Type); + } + + protected virtual void ProcessEnumMember(IEdmEnumMember enumMember) + { + this.ProcessNamedElement(enumMember); + } + + #endregion + + #region Annotations + + protected virtual void ProcessVocabularyAnnotation(IEdmVocabularyAnnotation annotation) + { + this.ProcessElement(annotation); + } + + protected virtual void ProcessImmediateValueAnnotation(IEdmDirectValueAnnotation annotation) + { + this.ProcessNamedElement(annotation); + } + + protected virtual void ProcessAnnotation(IEdmVocabularyAnnotation annotation) + { + this.ProcessVocabularyAnnotation(annotation); + this.VisitExpression(annotation.Value); + } + + protected virtual void ProcessPropertyValueBinding(IEdmPropertyValueBinding binding) + { + this.VisitExpression(binding.Value); + } + + #endregion + + #region Expressions + + protected virtual void ProcessExpression(IEdmExpression expression) + { + } + + protected virtual void ProcessStringConstantExpression(IEdmStringConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessBinaryConstantExpression(IEdmBinaryConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessRecordExpression(IEdmRecordExpression expression) + { + this.ProcessExpression(expression); + if (expression.DeclaredType != null) + { + this.VisitTypeReference(expression.DeclaredType); + } + + this.VisitPropertyConstructors(expression.Properties); + } + + protected virtual void ProcessPathExpression(IEdmPathExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessPropertyPathExpression(IEdmPathExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessNavigationPropertyPathExpression(IEdmPathExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessCollectionExpression(IEdmCollectionExpression expression) + { + this.ProcessExpression(expression); + this.VisitExpressions(expression.Elements); + } + + protected virtual void ProcessLabeledExpressionReferenceExpression(IEdmLabeledExpressionReferenceExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessIsTypeExpression(IEdmIsTypeExpression expression) + { + this.ProcessExpression(expression); + this.VisitTypeReference(expression.Type); + this.VisitExpression(expression.Operand); + } + + protected virtual void ProcessIntegerConstantExpression(IEdmIntegerConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessIfExpression(IEdmIfExpression expression) + { + this.ProcessExpression(expression); + this.VisitExpression(expression.TestExpression); + this.VisitExpression(expression.TrueExpression); + this.VisitExpression(expression.FalseExpression); + } + + protected virtual void ProcessFunctionApplicationExpression(IEdmApplyExpression expression) + { + this.ProcessExpression(expression); + this.VisitExpressions(expression.Arguments); + } + + protected virtual void ProcessFloatingConstantExpression(IEdmFloatingConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessGuidConstantExpression(IEdmGuidConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessEnumMemberExpression(IEdmEnumMemberExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessDecimalConstantExpression(IEdmDecimalConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessDateConstantExpression(IEdmDateConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessTimeOfDayConstantExpression(IEdmTimeOfDayConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessDateTimeOffsetConstantExpression(IEdmDateTimeOffsetConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessDurationConstantExpression(IEdmDurationConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessBooleanConstantExpression(IEdmBooleanConstantExpression expression) + { + this.ProcessExpression(expression); + } + + protected virtual void ProcessCastExpression(IEdmCastExpression expression) + { + this.ProcessExpression(expression); + this.VisitTypeReference(expression.Type); + this.VisitExpression(expression.Operand); + } + + protected virtual void ProcessLabeledExpression(IEdmLabeledExpression element) + { + this.VisitExpression(element.Expression); + } + + protected virtual void ProcessPropertyConstructor(IEdmPropertyConstructor constructor) + { + this.VisitExpression(constructor.Value); + } + + protected virtual void ProcessNullConstantExpression(IEdmNullExpression expression) + { + this.ProcessExpression(expression); + } + + #endregion + + #region Data Model + + protected virtual void ProcessEntityContainer(IEdmEntityContainer container) + { + this.ProcessVocabularyAnnotatable(container); + this.ProcessNamedElement(container); + this.VisitEntityContainerElements(container.Elements); + } + + protected virtual void ProcessEntityContainerElement(IEdmEntityContainerElement element) + { + this.ProcessNamedElement(element); + } + + protected virtual void ProcessEntitySet(IEdmEntitySet set) + { + this.ProcessEntityContainerElement(set); + } + + protected virtual void ProcessSingleton(IEdmSingleton singleton) + { + this.ProcessEntityContainerElement(singleton); + } + + #endregion + + #region Operation Related + + protected virtual void ProcessAction(IEdmAction action) + { + this.ProcessSchemaElement(action); + this.ProcessOperation(action); + } + + protected virtual void ProcessFunction(IEdmFunction function) + { + this.ProcessSchemaElement(function); + this.ProcessOperation(function); + } + + protected virtual void ProcessActionImport(IEdmActionImport actionImport) + { + this.ProcessEntityContainerElement(actionImport); + } + + protected virtual void ProcessFunctionImport(IEdmFunctionImport functionImport) + { + this.ProcessEntityContainerElement(functionImport); + } + + protected virtual void ProcessOperation(IEdmOperation operation) + { + // Do not visit vocabularyAnnotatable because functions and operation imports are always going to be either a schema element or a container element and will be visited through those paths. + this.VisitOperationParameters(operation.Parameters); + + IEdmOperationReturn operationReturn = operation.GetReturn(); + this.ProcessOperationReturn(operationReturn); + } + + protected virtual void ProcessOperationParameter(IEdmOperationParameter parameter) + { + this.ProcessVocabularyAnnotatable(parameter); + this.ProcessNamedElement(parameter); + this.VisitTypeReference(parameter.Type); + } + + protected virtual void ProcessOperationReturn(IEdmOperationReturn operationReturn) + { + if (operationReturn == null) + { + return; + } + + this.ProcessVocabularyAnnotatable(operationReturn); + this.VisitTypeReference(operationReturn.Type); + } + #endregion + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmUtil.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmUtil.cs new file mode 100644 index 0000000..be64c2b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/EdmUtil.cs @@ -0,0 +1,727 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +#if PORTABLELIB +using System.Collections.Concurrent; +#endif +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Csdl.CsdlSemantics; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Utilities for Edm. + /// + public static class EdmUtil + { + // this is what we should be doing for CDM schemas + // the RegEx for valid identifiers are taken from the C# Language Specification (2.4.2 Identifiers) + // (except that we exclude _ as a valid starting character). + // This results in a somewhat smaller set of identifier from what System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier + // allows. Not all identifiers allowed by IsValidLanguageIndependentIdentifier are valid in C#.IsValidLanguageIndependentIdentifier allows: + // Mn, Mc, and Pc as a leading character (which the spec and C# (at least for some Mn and Mc characters) do not allow) + // characters that Char.GetUnicodeCategory says are in Nl and Cf but which the RegEx does not accept (and which C# does allow). + // + // we could create the StartCharacterExp and OtherCharacterExp dynamically to force inclusion of the missing Nl and Cf characters... + private const string StartCharacterExp = @"[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}]"; + private const string OtherCharacterExp = @"[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Cf}]"; + private const string NameExp = StartCharacterExp + OtherCharacterExp + "{0,}"; + + // private static Regex ValidDottedName=new Regex(@"^"+NameExp+@"(\."+NameExp+@"){0,}$",RegexOptions.Singleline); + private static Regex UndottedNameValidator = PlatformHelper.CreateCompiled(@"^" + NameExp + @"$", RegexOptions.Singleline); + + /// + /// Checks whether the has a MIME type annotation. + /// + /// The containing the annotation. + /// The to check. + /// The (non-null) value of the MIME type annotation of the or null if no MIME type annotation exists. + public static string GetMimeType(this IEdmModel model, IEdmProperty annotatableProperty) + { + return GetStringAnnotationValue(model, annotatableProperty, EdmConstants.MimeTypeAttributeName, () => Strings.EdmUtil_NullValueForMimeTypeAnnotation); + } + + /// + /// Sets the MIME type annotation of the to . + /// + /// The containing the annotation. + /// The to modify. + /// The MIME type value to set as annotation value; if null, an existing annotation will be removed. + /// The MIME type annotation is only supported on service operations and primitive properties for serialization purposes. + public static void SetMimeType(this IEdmModel model, IEdmProperty annotatableProperty, string mimeType) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(annotatableProperty, "annotatableProperty"); + + model.SetAnnotation(annotatableProperty, EdmConstants.MimeTypeAttributeName, mimeType); + } + + /// + /// Checks whether the has a MIME type annotation. + /// + /// The containing the annotation. + /// The to check. + /// The (non-null) value of the MIME type annotation of the or null if no MIME type annotation exists. + public static string GetMimeType(this IEdmModel model, IEdmOperation annotatableOperation) + { + return GetStringAnnotationValue(model, annotatableOperation, EdmConstants.MimeTypeAttributeName, () => Strings.EdmUtil_NullValueForMimeTypeAnnotation); + } + + /// + /// Gets the symbolic string of an annotated element. + /// In the next breaking change, it's better to add a property into . + /// + /// The annotatable element. + /// null or a symbolic string. + public static string GetSymbolicString(this IEdmVocabularyAnnotatable annotatedElement) + { + IEdmSchemaElement schemaElement = annotatedElement as IEdmSchemaElement; + if (schemaElement != null) + { + // EntityType, ComplexType, EnumType, TypeDefinition + if (schemaElement.SchemaElementKind == EdmSchemaElementKind.TypeDefinition) + { + IEdmType edmType = (IEdmType)schemaElement; + switch (edmType.TypeKind) + { + case EdmTypeKind.Complex: + return "ComplexType"; + case EdmTypeKind.Entity: + return "EntityType"; + case EdmTypeKind.Enum: + return "EnumType"; + case EdmTypeKind.TypeDefinition: + return "TypeDefinition"; + default: + return null; + } + } + else + { + // Action, Function, Term, EntityContainer + return schemaElement.SchemaElementKind.ToString(); + } + } + + IEdmEntityContainerElement containerElement = annotatedElement as IEdmEntityContainerElement; + if (containerElement != null) + { + // ActionImport, FunctionImport, EntitySet, Singleton + return containerElement.ContainerElementKind.ToString(); + } + + IEdmProperty property = annotatedElement as IEdmProperty; + if (property != null) + { + // NavigationProperty, Property + switch (property.PropertyKind) + { + case EdmPropertyKind.Navigation: + return "NavigationProperty"; + case EdmPropertyKind.Structural: + return "Property"; + default: + return null; + } + } + + IEdmExpression expression = annotatedElement as IEdmExpression; + if (expression != null) + { + switch (expression.ExpressionKind) + { + case EdmExpressionKind.FunctionApplication: + return "Apply"; + case EdmExpressionKind.IsType: + return "IsOf"; + case EdmExpressionKind.Labeled: + return "LabeledElement"; + case EdmExpressionKind.Cast: + case EdmExpressionKind.Collection: + case EdmExpressionKind.If: + case EdmExpressionKind.Null: + case EdmExpressionKind.Record: + return expression.ExpressionKind.ToString(); + default: + return null; + } + } + + if (annotatedElement is IEdmOperationParameter) + { + return "Parameter"; + } + else if (annotatedElement is IEdmOperationReturn) + { + return "ReturnType"; + } + else if (annotatedElement is IEdmReference) + { + return "Reference"; + } + else if (annotatedElement is IEdmInclude) + { + return "Include"; + } + else if (annotatedElement is IEdmReferentialConstraint) + { + return "ReferentialConstraint"; + } + else if (annotatedElement is IEdmEnumMember) + { + return "Member"; + } + else if (annotatedElement is IEdmVocabularyAnnotation) + { + return "Annotation"; + } + else if (annotatedElement is IEdmPropertyConstructor) + { + return "PropertyValue"; + } + + // It's not supported "Schema, UrlRef, OnDelete" + return null; + } + + /// + /// Sets the MIME type annotation of the to . + /// + /// The containing the annotation. + /// The to modify. + /// The MIME type value to set as annotation value; if null, an existing annotation will be removed. + /// The MIME type annotation is only supported on service operations and primitive properties for serialization purposes. + public static void SetMimeType(this IEdmModel model, IEdmOperation annotatableOperation, string mimeType) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(annotatableOperation, "annotatableOperation"); + + model.SetAnnotation(annotatableOperation, EdmConstants.MimeTypeAttributeName, mimeType); + } + + /// + /// Tries the name of the parse container qualified element. + /// + /// Name of the container qualified container element. + /// Name of the container that was determined. + /// The fully qualified name of the container element that was determined. + /// Returns true if parsing was successful and false if not. + internal static bool TryParseContainerQualifiedElementName(string containerQualifiedElementName, out string containerName, out string containerElementName) + { + containerName = null; + containerElementName = null; + + int indexOfContainerNameAndElementNameSeparator = containerQualifiedElementName.LastIndexOf('.'); + if (indexOfContainerNameAndElementNameSeparator < 0) + { + return false; + } + + containerName = containerQualifiedElementName.Substring(0, indexOfContainerNameAndElementNameSeparator); + containerElementName = containerQualifiedElementName.Substring(indexOfContainerNameAndElementNameSeparator + 1); + if (String.IsNullOrEmpty(containerName) || String.IsNullOrEmpty(containerElementName)) + { + return false; + } + + return true; + } + + internal static bool IsNullOrWhiteSpaceInternal(String value) + { + return value == null || value.ToCharArray().All(Char.IsWhiteSpace); + } + + internal static String JoinInternal(String separator, IEnumerable values) + { + if (values == null) + { + throw new ArgumentNullException("values"); + } + + if (separator == null) + { + separator = String.Empty; + } + + using (IEnumerator en = values.GetEnumerator()) + { + if (!en.MoveNext()) + { + return String.Empty; + } + + StringBuilder result = new StringBuilder(); + if (en.Current != null) + { + // handle the case that the enumeration has null entries + // and the case where their ToString() override is broken + string value = en.Current.ToString(); + if (value != null) + { + result.Append(value); + } + } + + while (en.MoveNext()) + { + result.Append(separator); + if (en.Current != null) + { + // handle the case that the enumeration has null entries + // and the case where their ToString() override is broken + string value = en.Current.ToString(); + if (value != null) + { + result.Append(value); + } + } + } + + return result.ToString(); + } + } + + // This is testing if the name can be parsed and serialized, not if it is valid. + internal static bool IsQualifiedName(string name) + { + string[] nameTokens = name.Split('.'); + if (nameTokens.Count() < 2) + { + return false; + } + + foreach (string token in nameTokens) + { + if (IsNullOrWhiteSpaceInternal(token)) + { + return false; + } + } + + return true; + } + + internal static bool IsValidUndottedName(string name) + { + return (!String.IsNullOrEmpty(name) && UndottedNameValidator.IsMatch(name)); + } + + internal static bool IsValidDottedName(string name) + { + // Each part of the dotted name needs to be a valid name. + return name.Split('.').All(IsValidUndottedName); + } + + internal static string ParameterizedName(IEdmOperation operation) + { + int index = 0; + int parameterCount = operation.Parameters.Count(); + StringBuilder sb = new StringBuilder(); + + UnresolvedOperation unresolvedOperationImport = operation as UnresolvedOperation; + if (unresolvedOperationImport != null) + { + sb.Append(unresolvedOperationImport.Namespace); + sb.Append("/"); + sb.Append(unresolvedOperationImport.Name); + return sb.ToString(); + } + + // If we have a operation (rather than a operation import), we want the parameterized name to include the namespace + IEdmSchemaElement schemaFunction = operation as IEdmSchemaElement; + if (schemaFunction != null) + { + sb.Append(schemaFunction.Namespace); + sb.Append("."); + } + + sb.Append(operation.Name); + sb.Append("("); + foreach (IEdmOperationParameter parameter in operation.Parameters) + { + string typeName = ""; + if (parameter.Type == null) + { + typeName = CsdlConstants.TypeName_Untyped; + } + else if (parameter.Type.IsCollection()) + { + typeName = CsdlConstants.Value_Collection + "(" + parameter.Type.AsCollection().ElementType().FullName() + ")"; + } + else if (parameter.Type.IsEntityReference()) + { + typeName = CsdlConstants.Value_Ref + "(" + parameter.Type.AsEntityReference().EntityType().FullName() + ")"; + } + else + { + typeName = parameter.Type.FullName(); + } + + sb.Append(typeName); + index++; + if (index < parameterCount) + { + sb.Append(", "); + } + } + + sb.Append(")"); + return sb.ToString(); + } + + internal static bool TryGetNamespaceNameFromQualifiedName(string qualifiedName, out string namespaceName, out string name, out string fullName) + { + bool foundNamespace = EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out namespaceName, out name); + + fullName = EdmUtil.GetFullNameForSchemaElement(namespaceName, name); + return foundNamespace; + } + + internal static bool TryGetNamespaceNameFromQualifiedName(string qualifiedName, out string namespaceName, out string name) + { + // Qualified name can be a operation import name which is separated by '/' + int lastSlash = qualifiedName.LastIndexOf('/'); + if (lastSlash < 0) + { + // Not a OperationImport + int lastDot = qualifiedName.LastIndexOf('.'); + if (lastDot < 0) + { + namespaceName = String.Empty; + name = qualifiedName; + return false; + } + + namespaceName = qualifiedName.Substring(0, lastDot); + name = qualifiedName.Substring(lastDot + 1); + return true; + } + + namespaceName = qualifiedName.Substring(0, lastSlash); + name = qualifiedName.Substring(lastSlash + 1); + return true; + } + + internal static string FullyQualifiedName(IEdmVocabularyAnnotatable element) + { + IEdmSchemaElement schemaElement = element as IEdmSchemaElement; + if (schemaElement != null) + { + IEdmOperation operation = schemaElement as IEdmOperation; + if (operation != null) + { + return ParameterizedName(operation); + } + else + { + return schemaElement.FullName(); + } + } + else + { + IEdmEntityContainerElement containerElement = element as IEdmEntityContainerElement; + if (containerElement != null) + { + return containerElement.Container.FullName() + "/" + containerElement.Name; + } + else + { + IEdmProperty property = element as IEdmProperty; + if (property != null) + { + IEdmSchemaType declaringSchemaType = property.DeclaringType as IEdmSchemaType; + if (declaringSchemaType != null) + { + string propertyOwnerName = FullyQualifiedName(declaringSchemaType); + if (propertyOwnerName != null) + { + return propertyOwnerName + "/" + property.Name; + } + } + } + else + { + IEdmOperationReturn operationReturn; + IEdmEnumMember enumMember; + IEdmOperationParameter parameter = element as IEdmOperationParameter; + if (parameter != null) + { + string parameterOwnerName = FullyQualifiedName(parameter.DeclaringOperation); + if (parameterOwnerName != null) + { + return parameterOwnerName + "/" + parameter.Name; + } + } + else if ((enumMember = element as IEdmEnumMember) != null) + { + string enumMemberOwnerName = FullyQualifiedName(enumMember.DeclaringType); + if (enumMemberOwnerName != null) + { + return enumMemberOwnerName + "/" + enumMember.Name; + } + } + else if ((operationReturn = element as IEdmOperationReturn) != null) + { + string operationName = FullyQualifiedName(operationReturn.DeclaringOperation); + if (operationName != null) + { + return operationName + "/" + CsdlConstants.OperationReturnExternalTarget; + } + } + } + } + } + + return null; + } + + internal static T CheckArgumentNull([ValidatedNotNull]T value, string parameterName) where T : class + { + if (null == value) + { + throw new ArgumentNullException(parameterName); + } + + return value; + } + + internal static bool EqualsOrdinal(this string string1, string string2) + { + return String.Equals(string1, string2, StringComparison.Ordinal); + } + + internal static bool EqualsOrdinalIgnoreCase(this string string1, string string2) + { + return String.Equals(string1, string2, StringComparison.OrdinalIgnoreCase); + } + + + /// + /// Sets the annotation with the OData metadata namespace and the specified on the . + /// + /// The containing the annotations."/> + /// The to set the annotation on. + /// The local name of the annotation to set. + /// The value of the annotation to set. + internal static void SetAnnotation(this IEdmModel model, IEdmElement annotatable, string localName, string value) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(annotatable != null, "annotatable != null"); + Debug.Assert(!String.IsNullOrEmpty(localName), "!string.IsNullOrEmpty(localName)"); + + IEdmStringValue stringValue = null; + if (value != null) + { + IEdmStringTypeReference typeReference = EdmCoreModel.Instance.GetString(/*nullable*/true); + stringValue = new EdmStringConstant(typeReference, value); + } + + model.SetAnnotationValue(annotatable, CsdlConstants.ODataMetadataNamespace, localName, stringValue); + } + + /// + /// Returns the annotation in the OData metadata namespace with the specified . + /// + /// The containing the annotation. + /// The to get the annotation from. + /// The local name of the annotation to find. + /// The value of the annotation in the OData metadata namespace and with the specified . + /// true if an annotation with the specified local name was found; otherwise false. + internal static bool TryGetAnnotation(this IEdmModel model, IEdmElement annotatable, string localName, out string value) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(annotatable != null, "annotatable != null"); + Debug.Assert(!String.IsNullOrEmpty(localName), "!string.IsNullOrEmpty(localName)"); + + object annotationValue = model.GetAnnotationValue(annotatable, CsdlConstants.ODataMetadataNamespace, localName); + if (annotationValue == null) + { + value = null; + return false; + } + + IEdmStringValue annotationStringValue = annotationValue as IEdmStringValue; + if (annotationStringValue == null) + { + // invalid annotation type found + throw new InvalidOperationException(Strings.EdmUtil_InvalidAnnotationValue(localName, annotationValue.GetType().FullName)); + } + + value = annotationStringValue.Value; + return true; + } + +#if PORTABLELIB + /// + /// Query dictionary for certain key, and update it if not exist + /// + /// Key type for dictionary + /// Value type for dictionary + /// The dictionary to look up + /// The key property + /// The function to compute value if key not exist in dictionary + /// The value for the key + internal static TValue DictionaryGetOrUpdate( + ConcurrentDictionary dictionary, + TKey key, + Func computeValue) + { + CheckArgumentNull(dictionary, "dictionary"); + CheckArgumentNull(computeValue, "computeValue"); + + return dictionary.GetOrAdd(key, computeValue); + } + + /// + /// Query dictionary for certain key, return default if not present + /// + /// Key type for dictionary + /// Value type for dictionary + /// The dictionary to look up + /// The key property + /// The value for the key, or default if the value does not exist + internal static TValue DictionarySafeGet( + ConcurrentDictionary dictionary, + TKey key) + { + CheckArgumentNull(dictionary, "dictionary"); + + TValue val; + dictionary.TryGetValue(key, out val); + return val; + } + +#else + /// + /// Query dictionary for certain key, and update it if not exist + /// + /// Key type for dictionary + /// Value type for dictionary + /// The dictionary to look up + /// The key property + /// The function to compute value if key not exist in dictionary + /// The value for the key + internal static TValue DictionaryGetOrUpdate( + IDictionary dictionary, + TKey key, + Func computeValue) + { + CheckArgumentNull(dictionary, "dictionary"); + CheckArgumentNull(computeValue, "computeValue"); + + TValue val; + + // Dictionary may reallocate buckets while adding a new item. Then this TryGetValue() might get a very strange result if it is not locked. + lock (dictionary) + { + if (dictionary.TryGetValue(key, out val)) + { + return val; + } + } + + TValue computedValue = computeValue(key); + lock (dictionary) + { + if (!dictionary.TryGetValue(key, out val)) + { + val = computedValue; + dictionary.Add(key, computedValue); + } + } + + return val; + } + + /// + /// Query dictionary for certain key, return default if not present + /// + /// Key type for dictionary + /// Value type for dictionary + /// The dictionary to look up + /// The key property + /// The value for the key, or default if the value does not exist + internal static TValue DictionarySafeGet( + IDictionary dictionary, + TKey key) + { + CheckArgumentNull(dictionary, "dictionary"); + + TValue val; + + lock (dictionary) + { + dictionary.TryGetValue(key, out val); + } + + return val; + } +#endif + + /// + /// Gets full name for the schema element with the provided namespace and name + /// + /// Namespace of the element + /// The element name + /// The full name of the element + internal static string GetFullNameForSchemaElement(string elementNamespace, string elementName) + { + if (elementName == null) + { + return string.Empty; + } + + if (elementNamespace == null) + { + return elementName; + } + + return elementNamespace + "." + elementName; + } + + /// + /// Checks whether the has an annotation. + /// + /// The containing the annotation. + /// The to check. + /// The local name of the annotation to lookup. + /// The error message to throw if a null value is found in an annotation. + /// The (non-null) value of the annotation of the or null if no annotation exists. + /// Any Type that derives from IEdmElement. + private static string GetStringAnnotationValue(this IEdmModel model, TEdmElement annotatable, string localName, Func getFoundAnnotationValueErrorString) where TEdmElement : class, IEdmElement + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(annotatable, "annotatable"); + + string foundValue; + if (model.TryGetAnnotation(annotatable, localName, out foundValue)) + { + if (foundValue == null) + { + throw new InvalidOperationException(getFoundAnnotationValueErrorString()); + } + + return foundValue; + } + + return null; + } + + // Hack to alert FXCop that we do check for null. + private sealed class ValidatedNotNullAttribute : Attribute + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmElementComparer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmElementComparer.cs new file mode 100644 index 0000000..63c647e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmElementComparer.cs @@ -0,0 +1,207 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm +{ + /// + /// Contains IsEquivalentTo() extension methods. + /// + public static class EdmElementComparer + { + /// + /// Returns true if the compared type is semantically equivalent to this type. + /// Schema types () are compared by their object refs. + /// + /// Type being compared. + /// Type being compared to. + /// Equivalence of the two types. + public static bool IsEquivalentTo(this IEdmType thisType, IEdmType otherType) + { + if (thisType == otherType) + { + return true; + } + + if (thisType == null || otherType == null) + { + return false; + } + + thisType = thisType.AsActualType(); + otherType = otherType.AsActualType(); + + if (thisType.TypeKind != otherType.TypeKind) + { + return false; + } + + switch (thisType.TypeKind) + { + case EdmTypeKind.Primitive: + return ((IEdmPrimitiveType)thisType).IsEquivalentTo((IEdmPrimitiveType)otherType); + case EdmTypeKind.Complex: + case EdmTypeKind.Entity: + return ((IEdmSchemaType)thisType).IsEquivalentTo((IEdmSchemaType)otherType); + case EdmTypeKind.Enum: + return ((IEdmEnumType)thisType).IsEquivalentTo((IEdmEnumType)otherType); + case EdmTypeKind.Collection: + return ((IEdmCollectionType)thisType).IsEquivalentTo((IEdmCollectionType)otherType); + case EdmTypeKind.EntityReference: + return ((IEdmEntityReferenceType)thisType).IsEquivalentTo((IEdmEntityReferenceType)otherType); + case EdmTypeKind.None: + return otherType.TypeKind == EdmTypeKind.None; + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_TypeKind(thisType.TypeKind)); + } + } + + /// + /// Returns true if the compared type reference is semantically equivalent to this type reference. + /// Schema types () are compared by their object refs. + /// + /// Type reference being compared. + /// Type referenced being compared to. + /// Equivalence of the two type references. + public static bool IsEquivalentTo(this IEdmTypeReference thisType, IEdmTypeReference otherType) + { + if (thisType == otherType) + { + return true; + } + + if (thisType == null || otherType == null) + { + return false; + } + + thisType = thisType.AsActualTypeReference(); + otherType = otherType.AsActualTypeReference(); + + EdmTypeKind typeKind = thisType.TypeKind(); + if (typeKind != otherType.TypeKind()) + { + return false; + } + + if (typeKind == EdmTypeKind.Primitive) + { + return ((IEdmPrimitiveTypeReference)thisType).IsEquivalentTo((IEdmPrimitiveTypeReference)otherType); + } + else + { + return thisType.IsNullable == otherType.IsNullable && + thisType.Definition.IsEquivalentTo(otherType.Definition); + } + } + + private static bool IsEquivalentTo(this IEdmPrimitiveType thisType, IEdmPrimitiveType otherType) + { + // ODataLib creates one-off instances of primitive type definitions that match by name and kind, but have different object refs. + // So we can't use object ref comparison here like for other IEdmSchemaType objects. + // See "src\Web\Client\System\Data\Services\Client\Serialization\PrimitiveType.cs:CreateEdmPrimitiveType()" for more info. + return thisType.PrimitiveKind == otherType.PrimitiveKind && + thisType.FullName() == otherType.FullName(); + } + + private static bool IsEquivalentTo(this IEdmEnumType thisType, IEdmEnumType otherType) + { + // ODataLib requires to register signatures for custom uri functions in static class + // If we generate multiple models that use the same enum we will have different object refs + return thisType.FullName() == otherType.FullName() + && thisType.UnderlyingType.IsEquivalentTo(otherType.UnderlyingType) + && thisType.IsFlags == otherType.IsFlags; + } + + private static bool IsEquivalentTo(this IEdmSchemaType thisType, IEdmSchemaType otherType) + { + return Object.ReferenceEquals(thisType, otherType); + } + + private static bool IsEquivalentTo(this IEdmCollectionType thisType, IEdmCollectionType otherType) + { + return thisType.ElementType.IsEquivalentTo(otherType.ElementType); + } + + private static bool IsEquivalentTo(this IEdmEntityReferenceType thisType, IEdmEntityReferenceType otherType) + { + return thisType.EntityType.IsEquivalentTo(otherType.EntityType); + } + + private static bool IsEquivalentTo(this IEdmPrimitiveTypeReference thisType, IEdmPrimitiveTypeReference otherType) + { + EdmPrimitiveTypeKind thisTypePrimitiveKind = thisType.PrimitiveKind(); + if (thisTypePrimitiveKind != otherType.PrimitiveKind()) + { + return false; + } + + switch (thisTypePrimitiveKind) + { + case EdmPrimitiveTypeKind.Binary: + return (thisType as IEdmBinaryTypeReference).IsEquivalentTo(otherType as IEdmBinaryTypeReference); + case EdmPrimitiveTypeKind.Decimal: + return (thisType as IEdmDecimalTypeReference).IsEquivalentTo(otherType as IEdmDecimalTypeReference); + case EdmPrimitiveTypeKind.String: + return (thisType as IEdmStringTypeReference).IsEquivalentTo(otherType as IEdmStringTypeReference); + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.DateTimeOffset: + return (thisType as IEdmTemporalTypeReference).IsEquivalentTo(otherType as IEdmTemporalTypeReference); + default: + if (thisTypePrimitiveKind.IsSpatial()) + { + return (thisType as IEdmSpatialTypeReference).IsEquivalentTo(otherType as IEdmSpatialTypeReference); + } + else + { + return thisType.IsNullable == otherType.IsNullable && + thisType.Definition.IsEquivalentTo(otherType.Definition); + } + } + } + + private static bool IsEquivalentTo(this IEdmBinaryTypeReference thisType, IEdmBinaryTypeReference otherType) + { + return thisType != null && otherType != null && + thisType.IsNullable == otherType.IsNullable && + thisType.IsUnbounded == otherType.IsUnbounded && + thisType.MaxLength == otherType.MaxLength; + } + + private static bool IsEquivalentTo(this IEdmDecimalTypeReference thisType, IEdmDecimalTypeReference otherType) + { + return thisType != null && otherType != null && + thisType.IsNullable == otherType.IsNullable && + thisType.Precision == otherType.Precision && + thisType.Scale == otherType.Scale; + } + + private static bool IsEquivalentTo(this IEdmTemporalTypeReference thisType, IEdmTemporalTypeReference otherType) + { + return thisType != null && otherType != null && + thisType.TypeKind() == otherType.TypeKind() && + thisType.IsNullable == otherType.IsNullable && + thisType.Precision == otherType.Precision; + } + + private static bool IsEquivalentTo(this IEdmStringTypeReference thisType, IEdmStringTypeReference otherType) + { + return thisType != null && otherType != null && + thisType.IsNullable == otherType.IsNullable && + thisType.IsUnbounded == otherType.IsUnbounded && + thisType.MaxLength == otherType.MaxLength && + thisType.IsUnicode == otherType.IsUnicode; + } + + private static bool IsEquivalentTo(this IEdmSpatialTypeReference thisType, IEdmSpatialTypeReference otherType) + { + return thisType != null && otherType != null && + thisType.IsNullable == otherType.IsNullable && + thisType.SpatialReferenceIdentifier == otherType.SpatialReferenceIdentifier; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmTypeSemantics.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmTypeSemantics.cs new file mode 100644 index 0000000..d203755 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EdmTypeSemantics.cs @@ -0,0 +1,1420 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Provides semantics of the predefined EDM types. + /// + public static class EdmTypeSemantics + { + #region IsCollection, IsEntity, IsComplex, IsPath... + + /// + /// Returns true if this reference refers to a collection. + /// + /// Type reference. + /// This reference refers to a collection. + public static bool IsCollection(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.Collection; + } + + /// + /// Returns true if this reference refers to an entity type. + /// + /// Type reference. + /// This reference refers to an entity type. + public static bool IsEntity(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.Entity; + } + + /// + /// Returns true if this reference refers to a path type. + /// + /// Type reference. + /// This reference refers to a path type. + public static bool IsPath(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.Path; + } + + /// + /// Returns true if this reference refers to an entity type. + /// + /// Type reference. + /// This reference refers to an entity type. + public static bool IsEntityReference(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.EntityReference; + } + + /// + /// Returns true if this reference refers to a complex type. + /// + /// Type reference. + /// This reference refers to a complex type. + public static bool IsComplex(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.Complex; + } + + /// + /// Returns true if this reference refers to a Edm.Untyped type. + /// + /// Type reference. + /// This reference refers to a Edm.Untyped type. + public static bool IsUntyped(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.Untyped; + } + + /// + /// Returns true if this reference refers to an enumeration type. + /// + /// Type reference. + /// This reference refers to an enumeration type. + public static bool IsEnum(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.Enum; + } + + /// + /// Returns true if this reference refers to a type definition. + /// + /// Type reference. + /// This reference refers to a type definition. + public static bool IsTypeDefinition(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.TypeDefinition; + } + + /// + /// Returns true if this reference refers to a structured type. + /// + /// Type reference. + /// This reference refers to a structured type. + public static bool IsStructured(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + switch (type.TypeKind()) + { + case EdmTypeKind.Entity: + case EdmTypeKind.Complex: + return true; + } + + return false; + } + + /// + /// Returns true if this type kind represents a structured type. + /// + /// Reference to the calling object. + /// This kind refers to a structured type. + public static bool IsStructured(this EdmTypeKind typeKind) + { + switch (typeKind) + { + case EdmTypeKind.Entity: + case EdmTypeKind.Complex: + return true; + } + + return false; + } + + /// + /// Returns true if this reference refers to a primitive type. + /// + /// Type reference. + /// This reference refers to a primitive type. + public static bool IsPrimitive(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind() == EdmTypeKind.Primitive; + } + + /// + /// Returns true if this reference refers to a binary type. + /// + /// Type reference. + /// This reference refers to a binary type. + public static bool IsBinary(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Binary; + } + + /// + /// Returns true if this reference refers to a boolean type. + /// + /// Type reference. + /// This reference refers to a boolean type. + public static bool IsBoolean(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Boolean; + } + + /// + /// Returns true if this reference refers to a temporal type. + /// + /// Type reference. + /// This reference refers to a temporal type. + public static bool IsTemporal(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Definition.IsTemporal(); + } + + /// + /// Returns true if this definition refers to a temporal type. + /// + /// Type reference. + /// This definition refers to a temporal type. + public static bool IsTemporal(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind.IsTemporal(); + } + + /// + /// Returns true if this type kind represents a temporal type. + /// + /// Reference to the calling object. + /// This kind refers to a temporal type. + public static bool IsTemporal(this EdmPrimitiveTypeKind typeKind) + { + switch (typeKind) + { + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.TimeOfDay: + return true; + } + + return false; + } + + /// + /// Returns true if this reference refers to a duration type. + /// + /// Type reference. + /// This reference refers to a duration type. + public static bool IsDuration(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Duration; + } + + /// + /// Returns true if this reference refers to a Date type. + /// + /// Type reference. + /// This reference refers to a Date type. + public static bool IsDate(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Date; + } + + /// + /// Returns true if this reference refers to a DateTimeOffset type. + /// + /// Type reference. + /// This reference refers to a DateTimeOffset type. + public static bool IsDateTimeOffset(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.DateTimeOffset; + } + + /// + /// Returns true if this reference refers to a decimal type. + /// + /// Type reference. + /// This reference refers to a decimal type. + public static bool IsDecimal(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Definition.IsDecimal(); + } + + /// + /// Returns true if this definition refers to a decimal type. + /// + /// Type reference. + /// This definition refers to a decimal type. + public static bool IsDecimal(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind == EdmPrimitiveTypeKind.Decimal; + } + + /// + /// Returns true if this reference refers to a floating point type. + /// + /// Type reference. + /// This reference refers to a floating point type. + public static bool IsFloating(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + switch (type.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Single: + return true; + } + + return false; + } + + /// + /// Returns true if this reference refers to a single type. + /// + /// Type reference. + /// This reference refers to a single type. + public static bool IsSingle(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Single; + } + + /// + /// Returns true if this reference refers to a TimeOfDay type. + /// + /// Type reference. + /// This reference refers to a TimeOfDay type. + public static bool IsTimeOfDay(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.TimeOfDay; + } + + /// + /// Returns true if this reference refers to a double type. + /// + /// Type reference. + /// This reference refers to a double type. + public static bool IsDouble(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Double; + } + + /// + /// Returns true if this reference refers to a GUID type. + /// + /// Type reference. + /// This reference refers to a GUID type. + public static bool IsGuid(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Guid; + } + + /// + /// Returns true if this reference refers to a signed integral type. + /// + /// Type reference. + /// This reference refers to a signed integral type. + public static bool IsSignedIntegral(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + switch (type.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + return true; + } + + return false; + } + + /// + /// Returns true if this reference refers to an SByte type. + /// + /// Type reference. + /// This reference refers to an SByte type. + public static bool IsSByte(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.SByte; + } + + /// + /// Returns true if this reference refers to an Int16 type. + /// + /// Type reference. + /// This reference refers to an Int16 type. + public static bool IsInt16(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Int16; + } + + /// + /// Returns true if this reference refers to an Int32 type. + /// + /// Type reference. + /// This reference refers to an Int32 type. + public static bool IsInt32(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Int32; + } + + /// + /// Returns true if this reference refers to an Int64 type. + /// + /// Type reference. + /// This reference refers to an Int64 type. + public static bool IsInt64(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Int64; + } + + /// + /// Returns true if this reference refers to an integer type. + /// + /// Type reference. + /// This reference refers to an integer type. + public static bool IsIntegral(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + switch (type.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.SByte: + return true; + + default: + return false; + } + } + + /// + /// Returns true if this primitive type kind represents an integer type. + /// + /// Type reference. + /// This kind refers to an integer type. + public static bool IsIntegral(this EdmPrimitiveTypeKind primitiveTypeKind) + { + switch (primitiveTypeKind) + { + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.SByte: + return true; + + default: + return false; + } + } + + /// + /// Returns true if this reference refers to a byte type. + /// + /// Type reference. + /// This reference refers to a byte type. + public static bool IsByte(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.PrimitiveKind() == EdmPrimitiveTypeKind.Byte; + } + + /// + /// Returns true if this reference refers to a string type. + /// + /// Type reference. + /// This reference refers to a string type. + public static bool IsString(this IEdmTypeReference type) + { + return type.Definition.IsString(); + } + + /// + /// Returns true if this definition refers to a string type. + /// + /// Type reference. + /// This definition refers to a string type. + public static bool IsString(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind == EdmPrimitiveTypeKind.String; + } + + /// + /// Returns true if this definition refers to an untyped type. + /// + /// Type reference. + /// This definition refers to a string type. + public static bool IsUntyped(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.TypeKind == EdmTypeKind.Untyped; + } + + /// + /// Returns true if this definition refers to a binary type. + /// + /// Type reference. + /// This definition refers to a binary type. + public static bool IsBinary(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind == EdmPrimitiveTypeKind.Binary; + } + + /// + /// Returns true if this reference refers to a stream type. + /// + /// Type reference. + /// This reference refers to a stream type. + public static bool IsStream(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Definition.IsStream(); + } + + /// + /// Returns true if this reference refers to a stream type. + /// + /// Type reference. + /// This reference refers to a stream type. + public static bool IsStream(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind == EdmPrimitiveTypeKind.Stream; + } + + /// + /// Returns true if this reference refers to a spatial type. + /// + /// Type reference. + /// This reference refers to a spatial type. + public static bool IsSpatial(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Definition.IsSpatial(); + } + + /// + /// Returns true if this definition refers to a spatial type. + /// + /// Type reference. + /// This definition refers to a spatial type. + public static bool IsSpatial(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind.IsSpatial(); + } + + /// + /// Returns true if this type kind represents a spatial type. + /// + /// Type reference. + /// This kind refers to a spatial type. + public static bool IsSpatial(this EdmPrimitiveTypeKind typeKind) + { + switch (typeKind) + { + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return true; + } + + return false; + } + + /// + /// Returns true if this definition refers to a geography type. + /// + /// Type reference. + /// This definition refers to a geography type. + public static bool IsGeography(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind.IsGeography(); + } + + /// + /// Returns true if this reference refers to a geography type. + /// + /// Type reference. + /// This reference refers to a geography type. + public static bool IsGeography(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Definition.IsGeography(); + } + + /// + /// Returns true if this type kind represents a geography type. + /// + /// Type reference. + /// This kind refers to a geography type. + public static bool IsGeography(this EdmPrimitiveTypeKind typeKind) + { + switch (typeKind) + { + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + return true; + } + + return false; + } + + /// + /// Returns true if this definition refers to a geometry type. + /// + /// Type reference. + /// This definition refers to a geometry type. + public static bool IsGeometry(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType == null) + { + return false; + } + + return primitiveType.PrimitiveKind.IsGeometry(); + } + + /// + /// Returns true if this reference refers to a geometry type. + /// + /// Type reference. + /// This reference refers to a geometry type. + public static bool IsGeometry(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Definition.IsGeometry(); + } + + /// + /// Returns true if this type kind represents a geometry type. + /// + /// Type reference. + /// This kind refers to a geometry type. + public static bool IsGeometry(this EdmPrimitiveTypeKind typeKind) + { + switch (typeKind) + { + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return true; + } + + return false; + } + + #endregion + + // The As*** functions never return null -- if the supplied type does not have the appropriate shape, an encoding of a bad type is returned. + #region AsPrimitive, AsCollection, AsStructured, ... + /// + /// If this reference is of a primitive type, this will return a valid primitive type reference to the type definition. Otherwise, it will return a bad primitive type reference. + /// + /// Reference to the calling object. + /// A valid primitive type reference if the definition of the reference is of a primitive type. Otherwise a bad primitive type reference. + public static IEdmPrimitiveTypeReference AsPrimitive(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveTypeReference primitiveReference = type as IEdmPrimitiveTypeReference; + if (primitiveReference != null) + { + return primitiveReference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.Primitive) + { + var primitiveDefinition = typeDefinition as IEdmPrimitiveType; + if (primitiveDefinition != null) + { + switch (primitiveDefinition.PrimitiveKind) + { + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.PrimitiveType: + return new EdmPrimitiveTypeReference(primitiveDefinition, type.IsNullable); + case EdmPrimitiveTypeKind.Binary: + return type.AsBinary(); + case EdmPrimitiveTypeKind.Decimal: + return type.AsDecimal(); + case EdmPrimitiveTypeKind.String: + return type.AsString(); + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.TimeOfDay: + return type.AsTemporal(); + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return type.AsSpatial(); + case EdmPrimitiveTypeKind.None: + break; + } + } + } + else if (typeDefinition.TypeKind == EdmTypeKind.TypeDefinition) + { + IEdmPrimitiveType underlyingType = typeDefinition.UnderlyingType(); + IEdmTypeDefinitionReference reference = type as IEdmTypeDefinitionReference; + if (reference == null) + { + // No facet available if not IEdmTypeDefinitionReference. + return new EdmPrimitiveTypeReference(underlyingType, type.IsNullable); + } + + switch (underlyingType.PrimitiveKind) + { + case EdmPrimitiveTypeKind.Binary: + return new EdmBinaryTypeReference(underlyingType, reference.IsNullable, reference.IsUnbounded, reference.MaxLength); + + case EdmPrimitiveTypeKind.Decimal: + return new EdmDecimalTypeReference(underlyingType, reference.IsNullable, reference.Precision, reference.Scale); + + case EdmPrimitiveTypeKind.String: + return new EdmStringTypeReference(underlyingType, reference.IsNullable, reference.IsUnbounded, reference.MaxLength, reference.IsUnicode); + + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.TimeOfDay: + return new EdmTemporalTypeReference(underlyingType, reference.IsNullable, reference.Precision); + + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return new EdmSpatialTypeReference(underlyingType, reference.IsNullable, reference.SpatialReferenceIdentifier); + + default: + return new EdmPrimitiveTypeReference(underlyingType, reference.IsNullable); + } + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Primitive)); + } + + return new BadPrimitiveTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of a collection type, this will return a valid collection type reference to the type definition. Otherwise, it will return a bad collection type reference. + /// + /// Reference to the calling object. + /// A valid collection type reference if the definition of the reference is of a collection type. Otherwise a bad collection type reference. + public static IEdmCollectionTypeReference AsCollection(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmCollectionTypeReference reference = type as IEdmCollectionTypeReference; + if (reference != null) + { + return reference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.Collection) + { + return new EdmCollectionTypeReference((IEdmCollectionType)typeDefinition); + } + + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), type.FullName(), EdmConstants.Type_Collection)); + } + + return new EdmCollectionTypeReference(new BadCollectionType(errors)); + } + + /// + /// If this reference is of a structured type, this will return a valid structured type reference to the type definition. Otherwise, it will return a bad structured type reference. + /// + /// Reference to the calling object. + /// A valid structured type reference if the definition of the reference is of a structured type. Otherwise a bad structured type reference. + public static IEdmStructuredTypeReference AsStructured(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmStructuredTypeReference reference = type as IEdmStructuredTypeReference; + if (reference != null) + { + return reference; + } + + switch (type.TypeKind()) + { + case EdmTypeKind.Entity: + return type.AsEntity(); + case EdmTypeKind.Complex: + return type.AsComplex(); + } + + string typeFullName = type.FullName(); + List errors = new List(type.TypeErrors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Structured)); + } + + return new BadEntityTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of an enumeration type, this will return a valid enumeration type reference to the type definition. Otherwise, it will return a bad enumeration type reference. + /// + /// Reference to the calling object. + /// A valid enumeration type reference if the definition of the reference is of an enumeration type. Otherwise a bad enumeration type reference. + public static IEdmEnumTypeReference AsEnum(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmEnumTypeReference reference = type as IEdmEnumTypeReference; + if (reference != null) + { + return reference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.Enum) + { + return new EdmEnumTypeReference((IEdmEnumType)typeDefinition, type.IsNullable); + } + + string typeFullName = type.FullName(); + return new EdmEnumTypeReference( + new BadEnumType(typeFullName, ConversionError(type.Location(), typeFullName, EdmConstants.Type_Enum)), + type.IsNullable); + } + + + /// + /// If this reference is of a type definition, this will return a valid type definition reference to the type definition. Otherwise, it will return a bad type definition reference. + /// + /// Reference to the calling object. + /// A valid type definition reference if the definition of the reference is of a type definition. Otherwise a bad type definition reference. + public static IEdmTypeDefinitionReference AsTypeDefinition(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmTypeDefinitionReference reference = type as IEdmTypeDefinitionReference; + if (reference != null) + { + return reference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.TypeDefinition) + { + return new EdmTypeDefinitionReference((IEdmTypeDefinition)typeDefinition, type.IsNullable); + } + + string typeFullName = type.FullName(); + return new EdmTypeDefinitionReference( + new BadTypeDefinition(typeFullName, ConversionError(type.Location(), typeFullName, EdmConstants.Type_TypeDefinition)), + type.IsNullable); + } + + /// + /// If this reference is of an entity type, this will return a valid entity type reference to the type definition. Otherwise, it will return a bad entity type reference. + /// + /// Reference to the calling object. + /// A valid entity type reference if the definition of the reference is of an entity type. Otherwise a bad entity type reference. + public static IEdmEntityTypeReference AsEntity(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmEntityTypeReference reference = type as IEdmEntityTypeReference; + if (reference != null) + { + return reference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.Entity) + { + return new EdmEntityTypeReference((IEdmEntityType)typeDefinition, type.IsNullable); + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Entity)); + } + + return new BadEntityTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of an entity reference type, this will return a valid entity reference type reference to the type definition. Otherwise, it will return a bad entity reference type reference. + /// + /// Reference to the calling object. + /// A valid entity reference type reference if the definition of the reference is of an entity reference type. Otherwise a bad entity reference type reference. + public static IEdmEntityReferenceTypeReference AsEntityReference(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmEntityReferenceTypeReference reference = type as IEdmEntityReferenceTypeReference; + if (reference != null) + { + return reference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.EntityReference) + { + return new EdmEntityReferenceTypeReference((IEdmEntityReferenceType)typeDefinition, type.IsNullable); + } + + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), type.FullName(), EdmConstants.Type_EntityReference)); + } + + return new EdmEntityReferenceTypeReference(new BadEntityReferenceType(errors), type.IsNullable); + } + + /// + /// If this reference is of a complex type, this will return a valid complex type reference to the type definition. Otherwise, it will return a bad complex type reference. + /// + /// Reference to the calling object. + /// A valid complex type reference if the definition of the reference is of a complex type. Otherwise a bad complex type reference. + public static IEdmComplexTypeReference AsComplex(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmComplexTypeReference reference = type as IEdmComplexTypeReference; + if (reference != null) + { + return reference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.Complex) + { + return new EdmComplexTypeReference((IEdmComplexType)typeDefinition, type.IsNullable); + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Complex)); + } + + return new BadComplexTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of a path type, this will return a valid path type reference to the type definition. + /// Otherwise, it will return a bad path type reference. + /// + /// Reference to the calling object. + /// A valid path type reference if the definition of the reference is of a path type. + /// Otherwise a bad path type reference. + public static IEdmPathTypeReference AsPath(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPathTypeReference reference = type as IEdmPathTypeReference; + if (reference != null) + { + return reference; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind == EdmTypeKind.Path) + { + return new EdmPathTypeReference((IEdmPathType)typeDefinition, type.IsNullable); + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Path)); + } + + return new BadPathTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of a spatial type, this will return a valid spatial type reference to the type definition. Otherwise, it will return a bad spatial type reference. + /// + /// Reference to the calling object. + /// A valid spatial type reference if the definition of the reference is of a spatial type. Otherwise a bad spatial type reference. + public static IEdmSpatialTypeReference AsSpatial(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmSpatialTypeReference spatial = type as IEdmSpatialTypeReference; + if (spatial != null) + { + return spatial; + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Spatial)); + } + + return new BadSpatialTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of a temporal type, this will return a valid temporal type reference to the type definition. Otherwise, it will return a bad temporal type reference. + /// + /// Reference to the calling object. + /// A valid temporal type reference if the definition of the reference is of a temporal type. Otherwise a bad temporal type reference. + public static IEdmTemporalTypeReference AsTemporal(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmTemporalTypeReference temporal = type as IEdmTemporalTypeReference; + if (temporal != null) + { + return temporal; + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Temporal)); + } + + return new BadTemporalTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of a decimal type, this will return a valid decimal type reference to the type definition. Otherwise, it will return a bad decimal type reference. + /// + /// Reference to the calling object. + /// A valid decimal type reference if the definition of the reference is of a decimal type. Otherwise a bad decimal type reference. + public static IEdmDecimalTypeReference AsDecimal(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmDecimalTypeReference decimalType = type as IEdmDecimalTypeReference; + if (decimalType != null) + { + return decimalType; + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Decimal)); + } + + return new BadDecimalTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of a string type, this will return a valid string type reference to the type definition. Otherwise, it will return a bad string type reference. + /// + /// Reference to the calling object. + /// A valid string type reference if the definition of the reference is of a string type. Otherwise a bad string type reference. + public static IEdmStringTypeReference AsString(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmStringTypeReference stringType = type as IEdmStringTypeReference; + if (stringType != null) + { + return stringType; + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_String)); + } + + return new BadStringTypeReference(typeFullName, type.IsNullable, errors); + } + + /// + /// If this reference is of a binary type, this will return a valid binary type reference to the type definition. Otherwise, it will return a bad binary type reference. + /// + /// Reference to the calling object. + /// A valid binary type reference if the definition of the reference is of a binary type. Otherwise a bad binary type reference. + public static IEdmBinaryTypeReference AsBinary(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmBinaryTypeReference binaryType = type as IEdmBinaryTypeReference; + if (binaryType != null) + { + return binaryType; + } + + string typeFullName = type.FullName(); + List errors = new List(type.Errors()); + if (errors.Count == 0) + { + errors.AddRange(ConversionError(type.Location(), typeFullName, EdmConstants.Type_Binary)); + } + + return new BadBinaryTypeReference(typeFullName, type.IsNullable, errors); + } + #endregion + + /// + /// Returns the primitive kind of the definition of this reference. + /// + /// Reference to the calling object. + /// The primitive kind of the definition of this reference. + public static EdmPrimitiveTypeKind PrimitiveKind(this IEdmTypeReference type) + { + if (type == null) + { + return EdmPrimitiveTypeKind.None; + } + + IEdmType typeDefinition = type.Definition; + if (typeDefinition.TypeKind != EdmTypeKind.Primitive) + { + return EdmPrimitiveTypeKind.None; + } + + return ((IEdmPrimitiveType)typeDefinition).PrimitiveKind; + } + + /// + /// Determines if the potential base type is in the inheritance hierarchy of the type being tested. + /// + /// Type to be tested for derivation from the other type. + /// The potential base type of the type being tested. + /// True if and only if the type inherits from the potential base type. + public static bool InheritsFrom(this IEdmStructuredType type, IEdmStructuredType potentialBaseType) + { + do + { + type = type.BaseType; + if (type != null && type.IsEquivalentTo(potentialBaseType)) + { + return true; + } + } + while (type != null); + + return false; + } + + /// + /// Determines if a type is equivalent to or derived from another type. + /// + /// Type to be tested for equivalence to or derivation from the other type. + /// Type that is the other type. + /// True if and only if the thisType is equivalent to or inherits from otherType. + public static bool IsOrInheritsFrom(this IEdmType thisType, IEdmType otherType) + { + if (thisType == null || otherType == null) + { + return false; + } + + if (thisType.IsEquivalentTo(otherType)) + { + return true; + } + + EdmTypeKind thisKind = thisType.TypeKind; + if (thisKind != otherType.TypeKind || !(thisKind == EdmTypeKind.Entity || thisKind == EdmTypeKind.Complex)) + { + return false; + } + + return ((IEdmStructuredType)thisType).InheritsFrom((IEdmStructuredType)otherType); + } + + /// + /// Determines whether thisType is the same as otherType, or thisType derives from otherType, or + /// otherType derives from thisType. + /// + /// This EDM type. + /// The other EDM type. + /// true if thisType and otherType are along the same line in the type hierarchy; false otherwise. + public static bool IsOnSameTypeHierarchyLineWith(this IEdmType thisType, IEdmType otherType) + { + return thisType.IsOrInheritsFrom(otherType) || otherType.IsOrInheritsFrom(thisType); + } + + /// + /// Returns the actual type of the given type. + /// If the given type is type definition, the actual type is its underlying type; + /// otherwise, return the given type itself. + /// + /// The given type. + /// The actual type of the given type. + public static IEdmType AsActualType(this IEdmType type) + { + IEdmPrimitiveType underlyingType = type.UnderlyingType(); + + return underlyingType ?? type; + } + + internal static IEdmPrimitiveTypeReference GetPrimitiveTypeReference(this IEdmPrimitiveType type, bool isNullable) + { + switch (type.PrimitiveKind) + { + case EdmPrimitiveTypeKind.Boolean: + case EdmPrimitiveTypeKind.Byte: + case EdmPrimitiveTypeKind.Date: + case EdmPrimitiveTypeKind.Double: + case EdmPrimitiveTypeKind.Guid: + case EdmPrimitiveTypeKind.Int16: + case EdmPrimitiveTypeKind.Int32: + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.SByte: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.PrimitiveType: + return new EdmPrimitiveTypeReference(type, isNullable); + case EdmPrimitiveTypeKind.Binary: + return new EdmBinaryTypeReference(type, isNullable); + case EdmPrimitiveTypeKind.String: + return new EdmStringTypeReference(type, isNullable); + case EdmPrimitiveTypeKind.Decimal: + return new EdmDecimalTypeReference(type, isNullable); + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + return new EdmTemporalTypeReference(type, isNullable); + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return new EdmSpatialTypeReference(type, isNullable); + default: + throw new InvalidOperationException(Edm.Strings.EdmPrimitive_UnexpectedKind); + } + } + + internal static IEdmTypeReference GetTypeReference(this IEdmType type, bool isNullable) + { + IEdmPrimitiveType primitiveType = type as IEdmPrimitiveType; + if (primitiveType != null) + { + return primitiveType.GetPrimitiveTypeReference(isNullable); + } + + IEdmComplexType complexType = type as IEdmComplexType; + if (complexType != null) + { + return new EdmComplexTypeReference(complexType, isNullable); + } + + IEdmEntityType entityType = type as IEdmEntityType; + if (entityType != null) + { + return new EdmEntityTypeReference(entityType, isNullable); + } + + IEdmEnumType enumType = type as IEdmEnumType; + if (enumType != null) + { + return new EdmEnumTypeReference(enumType, isNullable); + } + + IEdmPathType pathType = type as IEdmPathType; + if (pathType != null) + { + return new EdmPathTypeReference(pathType, isNullable); + } + + throw new InvalidOperationException(Edm.Strings.EdmType_UnexpectedEdmType); + } + + internal static IEdmPrimitiveType UnderlyingType(this IEdmType type) + { + if (type == null || type.TypeKind != EdmTypeKind.TypeDefinition) + { + return null; + } + + return ((IEdmTypeDefinition)type).UnderlyingType; + } + + internal static IEdmPrimitiveType UnderlyingType(this IEdmTypeReference type) + { + if (type == null) + { + return null; + } + + return type.Definition.UnderlyingType(); + } + + internal static IEdmTypeReference AsActualTypeReference(this IEdmTypeReference type) + { + if (type == null || type.TypeKind() != EdmTypeKind.TypeDefinition) + { + return type; + } + + return type.AsPrimitive(); + } + + internal static bool CanSpecifyMaxLength(this IEdmPrimitiveType type) + { + switch (type.PrimitiveKind) + { + case EdmPrimitiveTypeKind.Binary: + case EdmPrimitiveTypeKind.Stream: + case EdmPrimitiveTypeKind.String: + return true; + } + + return false; + } + + private static IEnumerable ConversionError(EdmLocation location, string typeName, string typeKindName) + { + return new[] { new EdmError(location, EdmErrorCode.TypeSemanticsCouldNotConvertTypeReference, Edm.Strings.TypeSemantics_CouldNotConvertTypeReference(typeName ?? EdmConstants.Value_UnnamedType, typeKindName)) }; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EnumHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EnumHelper.cs new file mode 100644 index 0000000..01d4ad8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/EnumHelper.cs @@ -0,0 +1,331 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System; +#if PORTABLELIB + using System.Collections.Concurrent; +#endif + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text; + + /// + /// Enum helper + /// + public static class EnumHelper + { + private const int MaxHashElements = 100; + +#if PORTABLELIB + private static readonly ConcurrentDictionary fieldInfoHash = new ConcurrentDictionary(4, EnumHelper.MaxHashElements); +#else + private static readonly IDictionary fieldInfoHash = new Dictionary(); +#endif + + /// + /// Parse an enum literal value to integer. The literal value can be Enum member name (e.g. "Red"), underlying value (e.g. "2"), or combined values (e.g. "Red, Green, Blue", "1,2,4"). + /// + /// edm enum type + /// input string value + /// true if case insensitive, false if case sensitive + /// parse result + /// true if parse succeeds, false if parse fails + public static bool TryParseEnum(this IEdmEnumType enumType, string value, bool ignoreCase, out long parseResult) + { + char[] enumSeperatorCharArray = new[] { ',' }; + + string[] enumNames; + ulong[] enumValues; + IEdmEnumType type = enumType; + + parseResult = 0L; + + if (value == null) + { + return false; + } + + value = value.Trim(); + if (value.Length == 0) + { + return false; + } + + ulong num = 0L; + string[] values = value.Split(enumSeperatorCharArray); + + if ((!enumType.IsFlags) && values.Length > 1) + { + return false; // nonflags can only have 1 value + } + + type.GetCachedValuesAndNames(out enumValues, out enumNames, true, true); + + if ((char.IsDigit(value[0]) || (value[0] == '-')) || (value[0] == '+')) + { + // computed for later use. only meaningful for Enum types with IsFlags=true. + ulong fullBits = 0; + for (int j = 0; j < enumValues.Length; j++) + { + fullBits |= enumValues[j]; + } + + // process each value + for (int i = 0; i < values.Length; i++) + { + long itemValue; + if (long.TryParse(values[i], out itemValue)) + { + // allow any number value, don't validate it against enum definition. + num |= (ulong)itemValue; + } + else + { + return false; + } + } + } + else + { + for (int i = 0; i < values.Length; i++) + { + values[i] = values[i].Trim(); + bool flag = false; + for (int j = 0; j < enumNames.Length; j++) + { + if (ignoreCase) + { + if (string.Compare(enumNames[j], values[i], StringComparison.OrdinalIgnoreCase) != 0) + { + continue; + } + } + else + { + if (!enumNames[j].Equals(values[i])) + { + continue; + } + } + + ulong item = enumValues[j]; + num |= item; + flag = true; + break; + } + + if (!flag) + { + return false; + } + } + } + + parseResult = (long)num; + return true; + } + + /// + /// Convert enum int value to string + /// + /// edm enum type reference + /// input int value + /// string literal of the enum value + public static string ToStringLiteral(this IEdmEnumTypeReference type, Int64 value) + { + if (type != null) + { + // parse the value to string literal + IEdmEnumType enumType = type.Definition as IEdmEnumType; + if (enumType != null) + { + return enumType.IsFlags ? enumType.ToStringWithFlags(value) : enumType.ToStringNoFlags(value); + } + } + + return value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// For enum with flags, use a sequential search for bit masks, and then check if any residual + /// + /// edm enum type + /// input integer value + /// string seperated by comma + private static string ToStringWithFlags(this IEdmEnumType enumType, Int64 value) + { + string[] strArray; + ulong[] numArray; + ulong num = (ulong)value; + enumType.GetCachedValuesAndNames(out numArray, out strArray, true, true); + int index = numArray.Length - 1; + StringBuilder builder = new StringBuilder(); + bool flag = true; + ulong num3 = num; + const int Zero = 0; + const ulong UlongZero = 0L; + + while (index >= Zero) + { + if ((index == Zero) && (numArray[index] == UlongZero)) + { + break; + } + + if ((num & numArray[index]) == numArray[index]) + { + num -= numArray[index]; + if (!flag) + { + builder.Insert(Zero, ", "); + } + + builder.Insert(Zero, strArray[index]); + flag = false; + } + + index--; + } + + if (num != UlongZero) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + if (num3 != UlongZero) + { + return builder.ToString(); + } + + if ((numArray.Length > Zero) && (numArray[Zero] == UlongZero)) + { + return strArray[Zero]; + } + + return Zero.ToString(CultureInfo.InvariantCulture); + } + + /// + /// For enum without flags, use a binary search + /// + /// edm enum type + /// input integer value + /// string + private static string ToStringNoFlags(this IEdmEnumType enumType, Int64 value) + { + ulong[] values; + string[] names; + enumType.GetCachedValuesAndNames(out values, out names, true, true); + ulong num = (ulong)value; + int index = Array.BinarySearch(values, num); + return index >= 0 ? names[index] : value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Get cached values and names from hash table + /// + /// edm enum type + /// output values + /// output names + /// true if get values, false otherwise + /// true if get names, false otherwise + private static void GetCachedValuesAndNames(this IEdmEnumType enumType, out ulong[] values, out string[] names, bool getValues, bool getNames) + { + HashEntry hashEntry = GetHashEntry(enumType); + values = hashEntry.Values; + if (values != null) + { + getValues = false; + } + + names = hashEntry.Names; + if (names != null) + { + getNames = false; + } + + if (!getValues && !getNames) + { + return; + } + + GetEnumValuesAndNames(enumType, ref values, ref names, getValues, getNames); + if (getValues) + { + hashEntry.Values = values; + } + + if (getNames) + { + hashEntry.Names = names; + } + } + + private static void GetEnumValuesAndNames(IEdmEnumType enumType, ref ulong[] values, ref string[] names, bool getValues, bool getNames) + { + Dictionary dict = new Dictionary(); + foreach (var member in enumType.Members) + { + IEdmEnumMemberValue intValue = member.Value; + if (intValue != null) + { + dict.Add(member.Name, (ulong)intValue.Value); + } + } + + Dictionary sortedDict = dict.OrderBy(d => d.Value).ToDictionary(d => d.Key, d => d.Value); + values = sortedDict.Select(d => d.Value).ToArray(); + names = sortedDict.Select(d => d.Key).ToArray(); + } + +#if PORTABLELIB + private static HashEntry GetHashEntry(IEdmEnumType enumType) + { + try + { + return EnumHelper.fieldInfoHash.GetOrAdd(enumType, type => new HashEntry(null, null)); + } + catch (OverflowException) + { + EnumHelper.fieldInfoHash.Clear(); + return EnumHelper.fieldInfoHash.GetOrAdd(enumType, type => new HashEntry(null, null)); + } + } + +#else + private static HashEntry GetHashEntry(IEdmEnumType enumType) + { + if (fieldInfoHash.Count > MaxHashElements) + { + lock (fieldInfoHash) + { + if (fieldInfoHash.Count > MaxHashElements) + { + fieldInfoHash.Clear(); + } + } + } + + return EdmUtil.DictionaryGetOrUpdate(fieldInfoHash, enumType, type => new HashEntry(null, null)); + } +#endif + + private class HashEntry + { + public string[] Names; + public ulong[] Values; + + public HashEntry(string[] names, ulong[] values) + { + this.Names = names; + this.Values = values; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs new file mode 100644 index 0000000..16648c3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs @@ -0,0 +1,3423 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Csdl.CsdlSemantics; +using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Csdl.Serialization; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.Community.V1; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm +{ + /// + /// Contains extension methods for interfaces. + /// + public static class ExtensionMethods + { + private const int ContainerExtendsMaxDepth = 100; + private const string CollectionTypeFormat = EdmConstants.Type_Collection + "({0})"; + + private static readonly IEnumerable EmptyStructuralProperties = new Collection(); + private static readonly IEnumerable EmptyNavigationProperties = new Collection(); + + #region IEdmModel + + private static readonly Func findType = (model, qualifiedName) => model.FindDeclaredType(qualifiedName); + private static readonly Func> findBoundOperations = (model, bindingType) => model.FindDeclaredBoundOperations(bindingType); + private static readonly Func findTerm = (model, qualifiedName) => model.FindDeclaredTerm(qualifiedName); + private static readonly Func> findOperations = (model, qualifiedName) => model.FindDeclaredOperations(qualifiedName); + private static readonly Func findEntityContainer = (model, qualifiedName) => { return model.ExistsContainer(qualifiedName) ? model.EntityContainer : null; }; + private static readonly Func, IEnumerable, IEnumerable> mergeFunctions = (f1, f2) => Enumerable.Concat(f1, f2); + + /// + /// Gets the value for the EDM version of the . + /// + /// Model the version has been set for. + /// The version. + public static Version GetEdmVersion(this IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + return model.GetAnnotationValue(model, EdmConstants.InternalUri, EdmConstants.EdmVersionAnnotation); + } + + /// + /// Sets a value of EDM version attribute of the . + /// + /// The model the version should be set for. + /// The version. + public static void SetEdmVersion(this IEdmModel model, Version version) + { + EdmUtil.CheckArgumentNull(model, "model"); + model.SetAnnotationValue(model, EdmConstants.InternalUri, EdmConstants.EdmVersionAnnotation, version); + } + + #region IEdmModel interface's FindDeclaredXxx() methods, here their counterpart methods become FindXxx(). + /// + /// Searches for a type with the given name in this model and all referenced models and returns null if no such type exists. + /// + /// The model to search. + /// The namespace or alias qualified name of the type being found. + /// The requested type, or null if no such type exists. + public static IEdmSchemaType FindType(this IEdmModel model, string qualifiedName) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName"); + + string fullyQualifiedName = model.ReplaceAlias(qualifiedName); + + return FindAcrossModels(model, fullyQualifiedName, findType, RegistrationHelper.CreateAmbiguousTypeBinding); // search built-in EdmCoreModel and CoreVocabularyModel. + } + + /// + /// Searches for bound operations based on the binding type, returns an empty enumerable if no operation exists. + /// + /// The model to search. + /// Type of the binding. + /// A set of operations that share the binding type or empty enumerable if no such operation exists. + public static IEnumerable FindBoundOperations(this IEdmModel model, IEdmType bindingType) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(bindingType, "bindingType"); + return FindAcrossModels(model, bindingType, findBoundOperations, mergeFunctions); // search built-in EdmCoreModel and CoreVocabularyModel. + } + + /// + /// Searches for bound operations based on the qualified name and binding type, returns an empty enumerable if no operation exists. + /// + /// The model to search. + /// The qualified name of the operation. + /// Type of the binding. + /// A set of operations that share the qualified name and binding type or empty enumerable if no such operation exists. + public static IEnumerable FindBoundOperations(this IEdmModel model, string qualifiedName, IEdmType bindingType) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName"); + EdmUtil.CheckArgumentNull(bindingType, "bindingType"); + + // the below is a copy of FindAcrossModels method but Func finder is replaced by FindDeclaredBoundOperations. + IEnumerable candidate = model.FindDeclaredBoundOperations(qualifiedName, bindingType); + + foreach (IEdmModel reference in model.ReferencedModels) + { + IEnumerable fromReference = reference.FindDeclaredBoundOperations(qualifiedName, bindingType); + if (fromReference != null) + { + candidate = candidate == null ? fromReference : mergeFunctions(candidate, fromReference); + } + } + + return candidate; + } + + /// + /// Searches for a term with the given name in this model and all referenced models and returns null if no such term exists. + /// + /// The model to search. + /// The qualified name of the term being found. + /// The requested term, or null if no such term exists. + public static IEdmTerm FindTerm(this IEdmModel model, string qualifiedName) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName"); + + return FindAcrossModels(model, qualifiedName, findTerm, RegistrationHelper.CreateAmbiguousTermBinding); + } + + /// + /// Searches for operations with the given name in this model and all referenced models and returns an empty enumerable if no such operations exist. + /// + /// The model to search. + /// The qualified name of the operations being found. + /// The requested operations. + public static IEnumerable FindOperations(this IEdmModel model, string qualifiedName) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName"); + + return FindAcrossModels(model, qualifiedName, findOperations, mergeFunctions); + } + #endregion + + /// + /// If the container name in the model is the same as the input name. The input name maybe full qualified name. + /// + /// The model to search. + /// Input container name to be searched. The container name may be full qualified with namesapce prefix. + /// True if the model has a container called input name, otherwise false. + public static bool ExistsContainer(this IEdmModel model, string containerName) + { + if (model.EntityContainer == null) + { + return false; + } + + string fullQulifiedName = (model.EntityContainer.Namespace ?? String.Empty) + "." + (containerName ?? String.Empty); + + if (string.Equals(model.EntityContainer.FullName(), fullQulifiedName, StringComparison.Ordinal) + || string.Equals(model.EntityContainer.FullName(), containerName, StringComparison.Ordinal)) + { + return true; + } + + return false; + } + + /// + /// Searches for an entity container with the given name in this model and all referenced models and returns null if no such entity container exists. + /// + /// The model to search. + /// The qualified name of the entity container being found. + /// The requested entity container, or null if no such entity container exists. + public static IEdmEntityContainer FindEntityContainer(this IEdmModel model, string qualifiedName) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName"); + + return FindAcrossModels(model, qualifiedName, findEntityContainer, RegistrationHelper.CreateAmbiguousEntityContainerBinding); + } + + /// + /// Gets an annotatable element's vocabulary annotations defined in a specific model and models referenced by that model. + /// + /// The model to search. + /// Element to check for annotations. + /// Annotations attached to the element (or, if the element is a type, to its base types) by this model or by models referenced by this model. + public static IEnumerable FindVocabularyAnnotationsIncludingInheritedAnnotations(this IEdmModel model, IEdmVocabularyAnnotatable element) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + IEnumerable result = model.FindDeclaredVocabularyAnnotations(element); + + IEdmStructuredType typeElement = element as IEdmStructuredType; + if (typeElement != null) + { + typeElement = typeElement.BaseType; + while (typeElement != null) + { + IEdmVocabularyAnnotatable annotatableElement = typeElement as IEdmVocabularyAnnotatable; + if (annotatableElement != null) + { + result = result.Concat(model.FindDeclaredVocabularyAnnotations(annotatableElement)); + } + + typeElement = typeElement.BaseType; + } + } + + return result; + } + + /// + /// Gets an annotatable element's vocabulary annotations defined in a specific model and models referenced by that model. + /// + /// The model to search. + /// Element to check for annotations. + /// Annotations attached to the element by this model or by models referenced by this model. + public static IEnumerable FindVocabularyAnnotations(this IEdmModel model, IEdmVocabularyAnnotatable element) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + IEnumerable result = model.FindVocabularyAnnotationsIncludingInheritedAnnotations(element); + foreach (IEdmModel referencedModel in model.ReferencedModels) + { + result = result.Concat(referencedModel.FindVocabularyAnnotationsIncludingInheritedAnnotations(element)); + } + + return result; + } + + /// + /// Gets an annotatable element's vocabulary annotations that bind a particular term. + /// + /// Type of the annotation being returned. + /// Model to search. + /// Element to check for annotations. + /// Term to search for. + /// Annotations attached to the element by this model or by models referenced by this model that bind the term. + public static IEnumerable FindVocabularyAnnotations(this IEdmModel model, IEdmVocabularyAnnotatable element, IEdmTerm term) where T : IEdmVocabularyAnnotation + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(term, "term"); + + return FindVocabularyAnnotations(model, element, term, null); + } + + /// + /// Gets an annotatable element's vocabulary annotations that bind a particular term. + /// + /// Type of the annotation being returned. + /// Model to search. + /// Element to check for annotations. + /// Term to search for. + /// Qualifier to apply. + /// Annotations attached to the element by this model or by models referenced by this model that bind the term with the given qualifier. + public static IEnumerable FindVocabularyAnnotations(this IEdmModel model, IEdmVocabularyAnnotatable element, IEdmTerm term, string qualifier) where T : IEdmVocabularyAnnotation + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(term, "term"); + + List result = null; + + foreach (T annotation in model.FindVocabularyAnnotations(element).OfType()) + { + if (annotation.Term == term && (qualifier == null || qualifier == annotation.Qualifier)) + { + if (result == null) + { + result = new List(); + } + + result.Add(annotation); + } + } + + return result ?? Enumerable.Empty(); + } + + /// + /// Gets an annotatable element's vocabulary annotations that bind a particular term. + /// + /// Type of the annotation being returned. + /// Model to search. + /// Element to check for annotations. + /// Name of the term to search for. + /// Annotations attached to the element by this model or by models referenced by this model that bind the term. + public static IEnumerable FindVocabularyAnnotations(this IEdmModel model, IEdmVocabularyAnnotatable element, string termName) where T : IEdmVocabularyAnnotation + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(termName, "termName"); + + return FindVocabularyAnnotations(model, element, termName, null); + } + + /// + /// Gets an annotatable element's vocabulary annotations that bind a particular term. + /// + /// Type of the annotation being returned. + /// Model to search. + /// Element to check for annotations. + /// Name of the term to search for. + /// Qualifier to apply. + /// Annotations attached to the element by this model or by models referenced by this model that bind the term with the given qualifier. + public static IEnumerable FindVocabularyAnnotations(this IEdmModel model, IEdmVocabularyAnnotatable element, string termName, string qualifier) where T : IEdmVocabularyAnnotation + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(termName, "termName"); + + // Look up annotations on the element by name. There's no particular advantage in searching for a term first. + string name; + string namespaceName; + + if (EdmUtil.TryGetNamespaceNameFromQualifiedName(termName, out namespaceName, out name)) + { + foreach (T annotation in model.FindVocabularyAnnotations(element).OfType()) + { + IEdmTerm annotationTerm = annotation.Term; + if (annotationTerm.Namespace == namespaceName && annotationTerm.Name == name && (qualifier == null || qualifier == annotation.Qualifier)) + { + yield return annotation; + } + } + } + } + + /// + /// Gets the of a vocabulary term that has been applied to the type of a value. + /// + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Name of the term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmStructuredValue context, string termName, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "expressionEvaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), termName, null, expressionEvaluator.Evaluate); + } + + /// + /// Gets the of a vocabulary term that has been applied to the type of a value. + /// + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Name of the term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmStructuredValue context, string termName, string qualifier, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "expressionEvaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), termName, qualifier, expressionEvaluator.Evaluate); + } + + /// + /// Gets the of a vocabulary term that has been applied to the type of a value. + /// + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmStructuredValue context, IEdmTerm term, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "expressionEvaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), term, null, expressionEvaluator.Evaluate); + } + + /// + /// Gets the of a vocabulary term that has been applied to the type of a value. + /// + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmStructuredValue context, IEdmTerm term, string qualifier, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "expressionEvaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), term, qualifier, expressionEvaluator.Evaluate); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to the type of a value. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Name of the term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmStructuredValue context, string termName, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), termName, null, evaluator.EvaluateToClrValue); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to the type of a value. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Name of the term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmStructuredValue context, string termName, string qualifier, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), termName, qualifier, evaluator.EvaluateToClrValue); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to the type of a value. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmStructuredValue context, IEdmTerm term, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), term, null, evaluator.EvaluateToClrValue); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to the type of a value. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Value to use as context in evaluation. + /// Term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmStructuredValue context, IEdmTerm term, string qualifier, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(context, "context"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, context, context.Type.AsEntity().EntityDefinition(), term, qualifier, evaluator.EvaluateToClrValue); + } + + /// + /// Gets the of a vocabulary term that has been applied to an element. + /// + /// Model to search for term annotations. + /// Annotated element. + /// Name of the term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, string termName, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "evaluator"); + + return GetTermValue(model, element, termName, null, expressionEvaluator.Evaluate); + } + + /// + /// Gets the of a vocabulary term that has been applied to an element. + /// + /// Model to search for term annotations. + /// Annotated element. + /// Name of the term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, string termName, string qualifier, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "evaluator"); + + return GetTermValue(model, element, termName, qualifier, expressionEvaluator.Evaluate); + } + + /// + /// Gets the of a vocabulary term that has been applied to an element. + /// + /// Model to search for term annotations. + /// Annotated element. + /// Term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, IEdmTerm term, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "evaluator"); + + return GetTermValue(model, element, term, null, expressionEvaluator.Evaluate); + } + + /// + /// Gets the of a vocabulary term that has been applied to an element. + /// + /// Model to search for term annotations. + /// Annotated element. + /// Term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static IEdmValue GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, IEdmTerm term, string qualifier, EdmExpressionEvaluator expressionEvaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(expressionEvaluator, "evaluator"); + + return GetTermValue(model, element, term, qualifier, expressionEvaluator.Evaluate); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to an element. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Annotated element. + /// Name of the term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, string termName, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, element, termName, null, evaluator.EvaluateToClrValue); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to an element. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Annotated element. + /// Name of the term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, string termName, string qualifier, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(termName, "termName"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, element, termName, qualifier, evaluator.EvaluateToClrValue); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to an element. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Annotated element. + /// Term to evaluate. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, IEdmTerm term, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, element, term, null, evaluator.EvaluateToClrValue); + } + + /// + /// Gets the CLR value of a vocabulary term that has been applied to an element. + /// + /// The CLR type of the value to be returned. + /// Model to search for term annotations. + /// Annotated element. + /// Term to evaluate. + /// Qualifier to apply. + /// Evaluator to use to perform expression evaluation. + /// Value of the term evaluated against the supplied value. + public static T GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, IEdmTerm term, string qualifier, EdmToClrEvaluator evaluator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(evaluator, "evaluator"); + + return GetTermValue(model, element, term, qualifier, evaluator.EvaluateToClrValue); + } + + /// + /// Gets an annotation value corresponding to the given namespace and name provided. + /// + /// The model containing the annotation. + /// The annotated element. + /// Namespace of the annotation. + /// Name of the annotation inside the namespace. + /// The requested annotation value, if it exists. Otherwise, null. + public static object GetAnnotationValue(this IEdmModel model, IEdmElement element, string namespaceName, string localName) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + return model.DirectValueAnnotationsManager.GetAnnotationValue(element, namespaceName, localName); + } + + /// + /// Gets an annotation value corresponding to the given namespace and name provided. + /// + /// Type of the annotation being returned. + /// The model containing the annotation. + /// The annotated element. + /// Namespace of the annotation. + /// Name of the annotation inside the namespace. + /// The requested annotation value, if it exists. Otherwise, null. + public static T GetAnnotationValue(this IEdmModel model, IEdmElement element, string namespaceName, string localName) where T : class + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + return AnnotationValue(model.GetAnnotationValue(element, namespaceName, localName)); + } + + /// + /// Gets an annotation value from an annotatable element. + /// + /// Type of the annotation being returned. + /// The model containing the annotation. + /// The annotated element. + /// The requested annotation, if it exists. Otherwise, null. + /// + /// Strongly-typed wrappers for unnamed annotations keyed by CLR type. + /// + public static T GetAnnotationValue(this IEdmModel model, IEdmElement element) where T : class + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + return model.GetAnnotationValue(element, EdmConstants.InternalUri, TypeName.LocalName); + } + + /// + /// Sets an annotation value for an EDM element. If the value is null, no annotation is added and an existing annotation with the same name is removed. + /// + /// The model containing the annotation. + /// The annotated element. + /// Namespace that the annotation belongs to. + /// Name of the annotation within the namespace. + /// Value of the new annotation. + public static void SetAnnotationValue(this IEdmModel model, IEdmElement element, string namespaceName, string localName, object value) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + model.DirectValueAnnotationsManager.SetAnnotationValue(element, namespaceName, localName, value); + } + + /// + /// Gets description for term Org.OData.Core.V1.Description from a target annotatable + /// + /// The model referenced to. + /// The target Annotatable to find annotation + /// Description for term Org.OData.Core.V1.Description + public static string GetDescriptionAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(target, "target"); + + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(target, CoreVocabularyModel.DescriptionTerm).FirstOrDefault(); + if (annotation != null) + { + IEdmStringConstantExpression stringConstant = annotation.Value as IEdmStringConstantExpression; + if (stringConstant != null) + { + return stringConstant.Value; + } + } + + return null; + } + + /// + /// Gets description for term Org.OData.Core.V1.LongDescription from a target annotatable + /// + /// The model referenced to. + /// The target Annotatable to find annotation + /// Description for term Org.OData.Core.V1.LongDescription + public static string GetLongDescriptionAnnotation(this IEdmModel model, IEdmVocabularyAnnotatable target) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(target, "target"); + + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(target, CoreVocabularyModel.LongDescriptionTerm).FirstOrDefault(); + if (annotation != null) + { + IEdmStringConstantExpression stringConstant = annotation.Value as IEdmStringConstantExpression; + if (stringConstant != null) + { + return stringConstant.Value; + } + } + + return null; + } + + /// + /// Gets the collection of qualified type name for term Org.OData.Validation.V1.DerivedTypeConstraint from a navigation source. + /// + /// The model referenced to. + /// The navigation source. + /// Null or a collection string of qualifed type name. + public static IEnumerable GetDerivedTypeConstraints(this IEdmModel model, IEdmNavigationSource navigationSource) + { + if (model == null || navigationSource == null) + { + return null; + } + + IEnumerable derivedTypeConstraints = null; + switch (navigationSource.NavigationSourceKind()) + { + case EdmNavigationSourceKind.EntitySet: + derivedTypeConstraints = model.GetDerivedTypeConstraints((IEdmVocabularyAnnotatable)(IEdmEntitySet)navigationSource); + break; + case EdmNavigationSourceKind.Singleton: + derivedTypeConstraints = model.GetDerivedTypeConstraints((IEdmVocabularyAnnotatable)(IEdmSingleton)navigationSource); + break; + } + + return derivedTypeConstraints; + } + + /// + /// Gets the collection of qualified type name for term Org.OData.Validation.V1.DerivedTypeConstraint from a target annotatable. + /// + /// The model referenced to. + /// The target annotatable to find annotation. + /// Null or a collection string of qualifed type name. + public static IEnumerable GetDerivedTypeConstraints(this IEdmModel model, IEdmVocabularyAnnotatable target) + { + if (model == null || target == null) + { + return null; + } + + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(target, ValidationVocabularyModel.DerivedTypeConstraintTerm).FirstOrDefault(); + if (annotation != null) + { + IEdmCollectionExpression collectionExpression = annotation.Value as IEdmCollectionExpression; + if (collectionExpression != null && collectionExpression.Elements != null) + { + return collectionExpression.Elements.OfType().Select(e => e.Value); + } + } + + return null; + } + + /// + /// Gets all schema elements from the model, and models referenced by it. + /// + /// Model to search for elements + /// Schema elements from the model, and models referenced by it. + public static IEnumerable SchemaElementsAcrossModels(this IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + + IEnumerable result = new IEdmSchemaElement[] { }; + foreach (IEdmModel referencedModel in model.ReferencedModels) + { + result = result.Concat(referencedModel.SchemaElements); + } + + result = result.Concat(model.SchemaElements); + return result; + } + + /// + /// Finds a list of types that derive from the supplied type directly or indirectly, and across models. + /// + /// The model types are being found on. + /// The base type that derived types are being searched for. + /// A list of types that derive from the type. + public static IEnumerable FindAllDerivedTypes(this IEdmModel model, IEdmStructuredType baseType) + { + List result = new List(); + if (baseType is IEdmSchemaElement) + { + model.DerivedFrom(baseType, new HashSetInternal(), result); + } + + return result; + } + + /// + /// Sets an annotation value on an annotatable element. + /// + /// Type of the annotation being set. + /// The model containing the annotation. + /// The annotated element. + /// Value of the new annotation. + public static void SetAnnotationValue(this IEdmModel model, IEdmElement element, T value) where T : class + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + model.SetAnnotationValue(element, EdmConstants.InternalUri, TypeName.LocalName, value); + } + + /// + /// Retrieves a set of annotation values. For each requested value, returns null if no annotation with the given name exists for the given element. + /// + /// The model in which to find the annotations. + /// The set of requested annotations. + /// Returns values that correspond to the provided annotations. A value is null if no annotation with the given name exists for the given element. + public static object[] GetAnnotationValues(this IEdmModel model, IEnumerable annotations) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(annotations, "annotations"); + + return model.DirectValueAnnotationsManager.GetAnnotationValues(annotations); + } + + /// + /// Sets a set of annotation values. If a supplied value is null, no annotation is added and an existing annotation with the same name is removed. + /// + /// The model in which to set the annotations. + /// The annotations to set. + public static void SetAnnotationValues(this IEdmModel model, IEnumerable annotations) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(annotations, "annotations"); + + model.DirectValueAnnotationsManager.SetAnnotationValues(annotations); + } + + /// + /// Gets the direct annotations for an element. + /// + /// The model containing the annotations. + /// The annotated element. + /// The immediate annotations of the element. + public static IEnumerable DirectValueAnnotations(this IEdmModel model, IEdmElement element) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(element, "element"); + + return model.DirectValueAnnotationsManager.GetDirectValueAnnotations(element); + } + + /// + /// Finds the entity set with qualified entity set name (not simple entity set name). + /// + /// The model. + /// Name of the container qualified element, can be an OperationImport or an EntitySet. + /// The Entity Set that was found. + /// True if an entityset was found from the qualified container name, false if none were found. + public static bool TryFindContainerQualifiedEntitySet(this IEdmModel model, string containerQualifiedEntitySetName, out IEdmEntitySet entitySet) + { + entitySet = null; + string containerName = null; + string simpleEntitySetName = null; + + if (containerQualifiedEntitySetName != null && + containerQualifiedEntitySetName.IndexOf(".", StringComparison.Ordinal) > -1 && + EdmUtil.TryParseContainerQualifiedElementName(containerQualifiedEntitySetName, out containerName, out simpleEntitySetName)) + { + if (model.ExistsContainer(containerName)) + { + IEdmEntityContainer container = model.EntityContainer; + if (container != null) + { + entitySet = container.FindEntitySetExtended(simpleEntitySetName); + } + } + } + + return (entitySet != null); + } + + /// + /// Finds the singleton. + /// + /// The model. + /// Name of the container qualified singleton element. + /// The singleton that was found. + /// True if an singleton was found from the qualified container name, false if none were found. + public static bool TryFindContainerQualifiedSingleton(this IEdmModel model, string containerQualifiedSingletonName, out IEdmSingleton singleton) + { + singleton = null; + string containerName = null; + string simpleSingletonName = null; + + if (containerQualifiedSingletonName != null && + containerQualifiedSingletonName.IndexOf(".", StringComparison.Ordinal) > -1 && + EdmUtil.TryParseContainerQualifiedElementName(containerQualifiedSingletonName, out containerName, out simpleSingletonName)) + { + if (model.ExistsContainer(containerName)) + { + singleton = model.EntityContainer.FindSingletonExtended(simpleSingletonName); + + if (singleton != null) + { + return true; + } + } + } + + return false; + } + + /// + /// Tries the find container qualified operation imports. + /// + /// The model. + /// Name of the container qualified operation import. + /// The operation imports. + /// True if OperationImports are found, false if none were found. + public static bool TryFindContainerQualifiedOperationImports(this IEdmModel model, string containerQualifiedOperationImportName, out IEnumerable operationImports) + { + operationImports = null; + string containerName = null; + string simpleOperationName = null; + + if (containerQualifiedOperationImportName.IndexOf(".", StringComparison.Ordinal) > -1 && EdmUtil.TryParseContainerQualifiedElementName(containerQualifiedOperationImportName, out containerName, out simpleOperationName)) + { + if (model.ExistsContainer(containerName)) + { + operationImports = model.EntityContainer.FindOperationImportsExtended(simpleOperationName); + + if (operationImports != null && operationImports.Count() > 0) + { + return true; + } + } + } + + return false; + } + + /// + /// Searches for entity set by the given name that may be container qualified in default container and .Extends containers. + /// + /// The model to search. + /// The name which might be container qualified. If no container name is provided, then default container will be searched. + /// The entity set found or empty if none found. + public static IEdmEntitySet FindDeclaredEntitySet(this IEdmModel model, string qualifiedName) + { + IEdmEntitySet foundEntitySet = null; + if (!model.TryFindContainerQualifiedEntitySet(qualifiedName, out foundEntitySet)) + { + // try searching by entity set name in container and extended containers: + try + { + IEdmEntityContainer container = model.EntityContainer; + if (container != null) + { + return container.FindEntitySetExtended(qualifiedName); + } + } + catch (NotImplementedException) + { + // model.EntityContainer can throw NotImplementedException + return null; + } + } + + return foundEntitySet; + } + + /// + /// Searches for singleton by the given name that may be container qualified in default container and .Extends containers. If no container name is provided, then default container will be searched. + /// + /// The model to search. + /// The name which might be container qualified. If no container name is provided, then default container will be searched. + /// The singleton found or empty if none found. + public static IEdmSingleton FindDeclaredSingleton(this IEdmModel model, string qualifiedName) + { + IEdmSingleton foundSingleton = null; + if (!model.TryFindContainerQualifiedSingleton(qualifiedName, out foundSingleton)) + { + // try searching by singleton name in container and extended containers: + try + { + IEdmEntityContainer container = model.EntityContainer; + if (container != null) + { + return container.FindSingletonExtended(qualifiedName); + } + } + catch (NotImplementedException) + { + // model.EntityContainer can throw NotImplementedException + return null; + } + } + + return foundSingleton; + } + + /// + /// Searches for entity set or singleton by the given name that may be container qualified in default container and .Extends containers. If no container name is provided, then default container will be searched. + /// + /// The model to search. + /// The name which might be container qualified. If no container name is provided, then default container will be searched. + /// The entity set or singleton found or empty if none found. + public static IEdmNavigationSource FindDeclaredNavigationSource(this IEdmModel model, string qualifiedName) + { + IEdmEntitySet entitySet = model.FindDeclaredEntitySet(qualifiedName); + if (entitySet != null) + { + return entitySet; + } + + return model.FindDeclaredSingleton(qualifiedName); + } + + + /// + /// Searches for the operation imports by the specified name in default container and .Extends containers, returns an empty enumerable if no operation import exists. + /// + /// The model to search. + /// The qualified name of the operation import which may or may not include the container name. + /// All operation imports that can be found by the specified name, returns an empty enumerable if no operation import exists. + public static IEnumerable FindDeclaredOperationImports(this IEdmModel model, string qualifiedName) + { + IEnumerable foundOperationImports = null; + if (!model.TryFindContainerQualifiedOperationImports(qualifiedName, out foundOperationImports)) + { + // try searching by operation import name in container and extended containers: + IEdmEntityContainer container = model.EntityContainer; + if (container != null) + { + return container.FindOperationImportsExtended(qualifiedName); + } + } + + return foundOperationImports; + } + + /// + /// Get the primitive value converter for the given type definition in the model. + /// + /// The model involved. + /// The reference to a type definition. + /// The primitive value converter for the type definition. + public static IPrimitiveValueConverter GetPrimitiveValueConverter(this IEdmModel model, IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(model, "mode"); + + // If type definition is not provided, we pass through the primitive value directly. + if (type == null || !type.IsTypeDefinition()) + { + return PassThroughPrimitiveValueConverter.Instance; + } + + return model.GetPrimitiveValueConverter(type.Definition); + } + + /// + /// Set the primitive value converter for the given type definition in the model. + /// + /// The model involved. + /// The reference to a type definition. + /// The primitive value converter for the type definition. + public static void SetPrimitiveValueConverter(this IEdmModel model, IEdmTypeDefinitionReference typeDefinition, IPrimitiveValueConverter converter) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(typeDefinition, "typeDefinition"); + EdmUtil.CheckArgumentNull(converter, "converter"); + + model.SetPrimitiveValueConverter(typeDefinition.Definition, converter); + } + + #endregion + + #region EdmModel + + /// + /// Creates and adds a complex type to the model. + /// + /// The EdmModel. + /// The namespace this type belongs to. + /// The name of this type within its namespace. + /// The complex type created. + public static EdmComplexType AddComplexType(this EdmModel model, string namespaceName, string name) + { + return model.AddComplexType(namespaceName, name, null, false); + } + + /// + /// Creates and adds a complex type to the model. + /// + /// The EdmModel. + /// The namespace this type belongs to. + /// The name of this type within its namespace. + /// The base type of this complex type. + /// The complex type created. + public static EdmComplexType AddComplexType(this EdmModel model, string namespaceName, string name, IEdmComplexType baseType) + { + return model.AddComplexType(namespaceName, name, baseType, false, false); + } + + /// + /// Creates and adds a complex type to the model. + /// + /// The EdmModel. + /// The namespace this type belongs to. + /// The name of this type within its namespace. + /// The base type of this complex type. + /// Denotes whether this complex type is abstract. + /// The complex type created. + public static EdmComplexType AddComplexType(this EdmModel model, string namespaceName, string name, IEdmComplexType baseType, bool isAbstract) + { + return model.AddComplexType(namespaceName, name, baseType, isAbstract, false); + } + + /// + /// Creates and adds a complex type to the model. + /// + /// The EdmModel. + /// The namespace this type belongs to. + /// The name of this type within its namespace. + /// The base type of this complex type. + /// Denotes whether this complex type is abstract. + /// Denotes if the type is open. + /// The complex type created. + public static EdmComplexType AddComplexType(this EdmModel model, string namespaceName, string name, IEdmComplexType baseType, bool isAbstract, bool isOpen) + { + var type = new EdmComplexType(namespaceName, name, baseType, isAbstract, isOpen); + model.AddElement(type); + return type; + } + + /// + /// Creates and adds an entity type to the model. + /// + /// The EdmModel. + /// Namespace the entity belongs to. + /// Name of the entity. + /// The entity type created. + public static EdmEntityType AddEntityType(this EdmModel model, string namespaceName, string name) + { + return model.AddEntityType(namespaceName, name, null, false, false); + } + + /// + /// Creates and adds an entity type to the model. + /// + /// The EdmModel. + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + /// The entity type created. + public static EdmEntityType AddEntityType(this EdmModel model, string namespaceName, string name, IEdmEntityType baseType) + { + return model.AddEntityType(namespaceName, name, baseType, false, false); + } + + /// + /// Creates and adds an entity type to the model. + /// + /// The EdmModel. + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + /// Denotes an entity that cannot be instantiated. + /// Denotes if the type is open. + /// The entity type created. + public static EdmEntityType AddEntityType(this EdmModel model, string namespaceName, string name, IEdmEntityType baseType, bool isAbstract, bool isOpen) + { + return model.AddEntityType(namespaceName, name, baseType, isAbstract, isOpen, false); + } + + /// + /// Creates and adds an entity type to the model. + /// + /// The EdmModel. + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + /// Denotes an entity that cannot be instantiated. + /// Denotes if the type is open. + /// Denotes if the type is a media type. + /// The entity type created. + public static EdmEntityType AddEntityType(this EdmModel model, string namespaceName, string name, IEdmEntityType baseType, bool isAbstract, bool isOpen, bool hasStream) + { + var type = new EdmEntityType(namespaceName, name, baseType, isAbstract, isOpen, hasStream); + model.AddElement(type); + return type; + } + + /// + /// Creates and adds an entity container to the model. + /// + /// The EdmModel. + /// Namespace of the entity container. + /// Name of the entity container. + /// The entity container created. + public static EdmEntityContainer AddEntityContainer(this EdmModel model, string namespaceName, string name) + { + var container = new EdmEntityContainer(namespaceName, name); + model.AddElement(container); + return container; + } + + /// + /// Set annotation Org.OData.Core.V1.OptimisticConcurrency to EntitySet + /// + /// The model to add annotation + /// The target entitySet to set the inline annotation + /// The PropertyPath for annotation + public static void SetOptimisticConcurrencyAnnotation(this EdmModel model, IEdmEntitySet target, IEnumerable properties) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(target, "target"); + EdmUtil.CheckArgumentNull(properties, "properties"); + + IEdmCollectionExpression collectionExpression = new EdmCollectionExpression(properties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray()); + IEdmTerm term = CoreVocabularyModel.ConcurrencyTerm; + + Debug.Assert(term != null, "term!=null"); + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(target, term, collectionExpression); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + + /// + /// Set Org.OData.Core.V1.Description to target. + /// + /// The model referenced to. + /// The target Annotatable to add annotation. + /// Decription to be added. + public static void SetDescriptionAnnotation(this EdmModel model, IEdmVocabularyAnnotatable target, string description) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(target, "target"); + EdmUtil.CheckArgumentNull(description, "description"); + + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(target, CoreVocabularyModel.DescriptionTerm, new EdmStringConstant(description)); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + + /// + /// Set Org.OData.Core.V1.LongDescription to target. + /// + /// The model referenced to. + /// The target Annotatable to add annotation. + /// Decription to be added. + public static void SetLongDescriptionAnnotation(this EdmModel model, IEdmVocabularyAnnotatable target, string description) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(target, "target"); + EdmUtil.CheckArgumentNull(description, "description"); + + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(target, CoreVocabularyModel.LongDescriptionTerm, new EdmStringConstant(description)); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + + /// + /// Set Org.OData.Capabilities.V1.ChangeTracking to target. + /// + /// The model referenced to. + /// The target entity container to set the inline annotation. + /// This entity set supports the odata.track-changes preference. + public static void SetChangeTrackingAnnotation(this EdmModel model, IEdmEntityContainer target, bool isSupported) + { + model.SetChangeTrackingAnnotationImplementation(target, isSupported, null, null); + } + + /// + /// Set Org.OData.Capabilities.V1.ChangeTracking to target. + /// + /// The model referenced to. + /// The target entity set to set the inline annotation. + /// This entity set supports the odata.track-changes preference. + /// Change tracking supports filters on these properties. + /// Change tracking supports these properties expanded. + public static void SetChangeTrackingAnnotation(this EdmModel model, IEdmEntitySet target, bool isSupported, IEnumerable filterableProperties, IEnumerable expandableProperties) + { + model.SetChangeTrackingAnnotationImplementation(target, isSupported, filterableProperties, expandableProperties); + } + + /// + /// Get type reference to the default UInt16 type definition. + /// The default underlying type is . + /// If the user has already defined his own UInt16, this method will not define anything and simply returns the type reference. + /// + /// The model involved + /// The name of the namespace where the type definition resides. + /// Indicate if the type definition reference is nullable. + /// The nullable type reference to UInt16 type definition. + public static IEdmTypeDefinitionReference GetUInt16(this EdmModel model, string namespaceName, bool isNullable) + { + return model.GetUIntImplementation( + namespaceName, + PrimitiveValueConverterConstants.UInt16TypeName, + PrimitiveValueConverterConstants.DefaultUInt16UnderlyingType, + isNullable); + } + + /// + /// Get type reference to the default UInt32 type definition. + /// The default underlying type is . + /// If the user has already defined his own UInt32, this method will not define anything and simply returns the type reference. + /// + /// The model involved + /// The name of the namespace where the type definition resides. + /// Indicate if the type definition reference is nullable. + /// The nullable type reference to UInt32 type definition. + public static IEdmTypeDefinitionReference GetUInt32(this EdmModel model, string namespaceName, bool isNullable) + { + return model.GetUIntImplementation( + namespaceName, + PrimitiveValueConverterConstants.UInt32TypeName, + PrimitiveValueConverterConstants.DefaultUInt32UnderlyingType, + isNullable); + } + + /// + /// Get type reference to the default UInt64 type definition. + /// The default underlying type is . + /// If the user has already defined his own UInt64, this method will not define anything and simply returns the type reference. + /// + /// The model involved + /// The name of the namespace where the type definition resides. + /// Indicate if the type definition reference is nullable. + /// The nullable type reference to UInt64 type definition. + public static IEdmTypeDefinitionReference GetUInt64(this EdmModel model, string namespaceName, bool isNullable) + { + return model.GetUIntImplementation( + namespaceName, + PrimitiveValueConverterConstants.UInt64TypeName, + PrimitiveValueConverterConstants.DefaultUInt64UnderlyingType, + isNullable); + } + + #endregion + + #region IEdmElement + + /// + /// Gets the location of this element. + /// + /// Reference to the calling object. + /// The location of the element. + public static EdmLocation Location(this IEdmElement item) + { + EdmUtil.CheckArgumentNull(item, "item"); + IEdmLocatable locatable = item as IEdmLocatable; + return locatable != null && locatable.Location != null ? locatable.Location : new ObjectLocation(item); + } + + #endregion + + #region IEdmVocabularyAnnotatable + + /// + /// Gets an annotatable element's vocabulary annotations as seen from a particular model. + /// + /// Reference to the calling object. + /// Model to check for annotations. + /// Annotations attached to the element by the model or by models referenced by the model. + public static IEnumerable VocabularyAnnotations(this IEdmVocabularyAnnotatable element, IEdmModel model) + { + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(model, "model"); + return model.FindVocabularyAnnotations(element); + } + + #endregion + + #region IEdmSchemaElement + + /// + /// Gets the full name of the element. + /// + /// Reference to the calling object. + /// The full name of the element. + public static string FullName(this IEdmSchemaElement element) + { + EdmUtil.CheckArgumentNull(element, "element"); + if (element.Name == null) + { + return string.Empty; + } + + if (element.Namespace == null) + { + return element.Name; + } + + IEdmFullNamedElement fullNamedElement = element as IEdmFullNamedElement; + if (fullNamedElement != null) + { + return fullNamedElement.FullName; + } + + return element.Namespace + "." + element.Name; + } + + /// + /// Gets the Short Qualified name of the element. + /// + /// Reference to the calling object. + /// The short qualified name of the element. + public static string ShortQualifiedName(this IEdmSchemaElement element) + { + EdmUtil.CheckArgumentNull(element, "element"); + if (element.Namespace != null && element.Namespace.Equals("Edm")) + { + return (element.Name ?? String.Empty); + } + + return FullName(element); + } + + #endregion + + #region IEdmEntityContainer + + /// + /// Returns entity sets belonging to an IEdmEntityContainer. + /// + /// Reference to the calling object. + /// Entity sets belonging to an IEdmEntityContainer. + public static IEnumerable EntitySets(this IEdmEntityContainer container) + { + EdmUtil.CheckArgumentNull(container, "container"); + return container.AllElements().OfType(); + } + + /// + /// Returns singletons belonging to an IEdmEntityContainer. + /// + /// Reference to the calling object. + /// Singletons belonging to an IEdmEntityContainer. + public static IEnumerable Singletons(this IEdmEntityContainer container) + { + EdmUtil.CheckArgumentNull(container, "container"); + return container.AllElements().OfType(); + } + + /// + /// Returns operation imports belonging to an IEdmEntityContainer. + /// + /// Reference to the calling object. + /// Operation imports belonging to an IEdmEntityContainer. + public static IEnumerable OperationImports(this IEdmEntityContainer container) + { + EdmUtil.CheckArgumentNull(container, "container"); + return container.AllElements().OfType(); + } + + #endregion + + #region IEdmTypeReference + /// + /// Gets the type kind of the type references definition. + /// + /// Reference to the calling object. + /// The type kind of the reference. + public static EdmTypeKind TypeKind(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmType typeDefinition = type.Definition; + return typeDefinition != null ? typeDefinition.TypeKind : EdmTypeKind.None; + } + + /// + /// Gets the full name of the definition referred to by the type reference. + /// + /// Reference to the calling object. + /// The full name of this references definition. + public static string FullName(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Definition.FullTypeName(); + } + + /// + /// Gets the short qualified name of the definition referred to by the type reference. + /// + /// Reference to the calling object. + /// The short qualified name of this references definition. + public static string ShortQualifiedName(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + var namedDefinition = type.Definition as IEdmSchemaElement; + return namedDefinition != null ? namedDefinition.ShortQualifiedName() : null; + } + #endregion + + #region IEdmType + + /// + /// Gets the full name of the definition referred to by the type reference. + /// + /// Reference to the calling object. + /// The full name of this references definition. + public static string FullTypeName(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + + var primitiveType = type as EdmCoreModelPrimitiveType; + if (primitiveType != null) + { + return primitiveType.FullName; + } + + var namedDefinition = type as IEdmSchemaElement; + var collectionType = type as IEdmCollectionType; + if (collectionType == null) + { + return namedDefinition != null ? namedDefinition.FullName() : null; + } + + // Handle collection case. + namedDefinition = collectionType.ElementType.Definition as IEdmSchemaElement; + + return namedDefinition != null ? string.Format(CultureInfo.InvariantCulture, CollectionTypeFormat, namedDefinition.FullName()) : null; + } + + /// + /// Gets the element type of a collection definition or itself of a non-collection definition referred to by the type reference. + /// + /// Reference to the calling object. + /// The element type of this references definition. + public static IEdmType AsElementType(this IEdmType type) + { + IEdmCollectionType collectionType = type as IEdmCollectionType; + return (collectionType != null) ? collectionType.ElementType.Definition : type; + } + + #endregion + + #region IEdmPrimitiveTypeReference + /// + /// Gets the definition of this primitive type reference. + /// + /// Reference to the calling object. + /// Definition of this primitive type reference. + public static IEdmPrimitiveType PrimitiveDefinition(this IEdmPrimitiveTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmPrimitiveType)type.Definition; + } + + /// + /// Gets the primitive kind of the definition referred to by this type reference. + /// + /// Reference to the calling object. + /// Primitive kind of the definition of this reference. + public static EdmPrimitiveTypeKind PrimitiveKind(this IEdmPrimitiveTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmPrimitiveType primitive = type.PrimitiveDefinition(); + return primitive != null ? primitive.PrimitiveKind : EdmPrimitiveTypeKind.None; + } + #endregion + + #region IEdmStructuredTypeDefinition + /// + /// Gets all properties of the structured type definition and its base types. + /// + /// Reference to the calling object. + /// Properties of this type. + public static IEnumerable Properties(this IEdmStructuredType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + if (type.BaseType != null) + { + foreach (IEdmProperty baseProperty in type.BaseType.Properties()) + { + yield return baseProperty; + } + } + + if (type.DeclaredProperties != null) + { + foreach (IEdmProperty declaredProperty in type.DeclaredProperties) + { + yield return declaredProperty; + } + } + } + + /// + /// Gets all structural properties declared in the IEdmStructuredTypeDefinition. + /// + /// Reference to the calling object. + /// All structural properties declared in the IEdmStructuredTypeDefinition. + public static IEnumerable DeclaredStructuralProperties(this IEdmStructuredType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.DeclaredProperties.OfType(); + } + + /// + /// Gets the structural properties declared in this type definition and all base types. + /// + /// Reference to the calling object. + /// The structural properties declared in this type definition and all base types. + public static IEnumerable StructuralProperties(this IEdmStructuredType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Properties().OfType(); + } + #endregion + + #region IEdmStructuredTypeReference + /// + /// Gets the definition of this structured type reference. + /// + /// Reference to the calling object. + /// The definition of this structured type reference. + public static IEdmStructuredType StructuredDefinition(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmStructuredType)type.Definition; + } + + /// + /// Returns true if the definition of this reference is abstract. + /// + /// Reference to the calling object. + /// If the definition of this reference is abstract. + public static bool IsAbstract(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.StructuredDefinition().IsAbstract; + } + + /// + /// Returns true if the definition of this reference is open. + /// + /// Reference to the calling object. + /// If the definition of this reference is open. + public static bool IsOpen(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.StructuredDefinition().IsOpen; + } + + /// + /// Returns true if the definition of this reference is open. + /// + /// Reference to the calling object. + /// If the definition of this reference is open. + public static bool IsOpen(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + + IEdmStructuredType structuredType = type as IEdmStructuredType; + if (structuredType != null) + { + return structuredType.IsOpen; + } + + // If its a collection, return whether its element type is open. + // This is because when processing a navigation property, the target type + // may be a collection type even though a key expression has been applied. + var collectionType = type as IEdmCollectionType; + if (collectionType == null) + { + return false; + } + + return collectionType.ElementType.Definition.IsOpen(); + } + + /// + /// Returns the base type of the definition of this reference. + /// + /// Reference to the calling object. + /// The base type of the definition of this reference. + public static IEdmStructuredType BaseType(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.StructuredDefinition().BaseType; + } + + /// + /// Gets all structural properties declared in the definition of this reference. + /// + /// Reference to the calling object. + /// All structural properties declared in the definition of this reference. + public static IEnumerable DeclaredStructuralProperties(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.StructuredDefinition().DeclaredStructuralProperties(); + } + + /// + /// Gets all structural properties declared in the definition of this reference and all its base types. + /// + /// Reference to the calling object. + /// All structural properties declared in the definition of this reference and all its base types. + public static IEnumerable StructuralProperties(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.StructuredDefinition().StructuralProperties(); + } + + /// + /// Finds a property from the definition of this reference. + /// + /// Reference to the calling object. + /// Name of the property to find. + /// The requested property if it exists. Otherwise, null. + public static IEdmProperty FindProperty(this IEdmStructuredTypeReference type, string name) + { + EdmUtil.CheckArgumentNull(type, "type"); + EdmUtil.CheckArgumentNull(name, "name"); + return type.StructuredDefinition().FindProperty(name); + } + + /// + /// Gets the navigation properties declared in the definition of this reference and its base types. + /// + /// Reference to the calling object. + /// The navigation properties declared in the definition of this reference and its base types. + public static IEnumerable NavigationProperties(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.StructuredDefinition().NavigationProperties(); + } + + /// + /// Gets the navigation properties declared in the definition of this reference. + /// + /// Reference to the calling object. + /// The navigation properties declared in the definition of this reference. + public static IEnumerable DeclaredNavigationProperties(this IEdmStructuredTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.StructuredDefinition().DeclaredNavigationProperties(); + } + + /// + /// Finds a navigation property declared in the definition of this reference by name. + /// + /// Reference to the calling object. + /// Name of the navigation property to find. + /// The requested navigation property if it exists. Otherwise, null. + public static IEdmNavigationProperty FindNavigationProperty(this IEdmStructuredTypeReference type, string name) + { + EdmUtil.CheckArgumentNull(type, "type"); + EdmUtil.CheckArgumentNull(name, "name"); + return type.StructuredDefinition().FindProperty(name) as IEdmNavigationProperty; + } + + #endregion + + #region IEdmEntityTypeDefinition + /// + /// Gets the base type of this entity type definition. + /// + /// Reference to the calling object. + /// The base type of this entity type definition. + public static IEdmEntityType BaseEntityType(this IEdmEntityType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.BaseType as IEdmEntityType; + } + + /// + /// Gets the base type of this structured type definition. + /// + /// Reference to the calling object. + /// The base type of this structured type definition. + public static IEdmStructuredType BaseType(this IEdmStructuredType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.BaseType as IEdmStructuredType; + } + + /// + /// Gets the navigation properties declared in this structured type definition. + /// + /// Reference to the calling object. + /// The navigation properties declared in this structured type definition. + public static IEnumerable DeclaredNavigationProperties(this IEdmStructuredType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.DeclaredProperties.OfType(); + } + + /// + /// Get the navigation properties declared in this structured type and all base types. + /// + /// Reference to the calling object. + /// The navigation properties declared in this structured type and all base types. + public static IEnumerable NavigationProperties(this IEdmStructuredType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.Properties().OfType(); + } + + /// + /// Gets the declared key of the most defined entity with a declared key present. + /// + /// Reference to the calling object. + /// Key of this type. + public static IEnumerable Key(this IEdmEntityType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + IEdmEntityType checkingType = type; + while (checkingType != null) + { + if (checkingType.DeclaredKey != null) + { + return checkingType.DeclaredKey; + } + + checkingType = checkingType.BaseEntityType(); + } + + return Enumerable.Empty(); + } + + /// + /// Determines whether the specified property is a key for its contained type. + /// + /// The property that may be a key. + /// True, if the property is a key, False if the property is not a key. + public static bool IsKey(this IEdmProperty property) + { + EdmUtil.CheckArgumentNull(property, "property"); + IEdmEntityType entityType = property.DeclaringType as IEdmEntityType; + + if (entityType != null) + { + foreach (IEdmProperty key in entityType.Key()) + { + if (key == property) + { + return true; + } + } + } + + return false; + } + + /// + /// Gets the declared alternate keys of the most defined entity with a declared key present. + /// + /// The model to be used. + /// Reference to the calling object. + /// Alternate Keys of this type. + public static IEnumerable> GetAlternateKeysAnnotation(this IEdmModel model, IEdmEntityType type) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(type, "type"); + + IEdmEntityType checkingType = type; + while (checkingType != null) + { + IEnumerable> declaredAlternateKeys = GetDeclaredAlternateKeysForType(checkingType, model); + if (declaredAlternateKeys != null) + { + return declaredAlternateKeys; + } + + checkingType = checkingType.BaseEntityType(); + } + + return Enumerable.Empty>(); + } + + /// + /// Adds the alternate keys to this entity type. + /// + /// The model to be used. + /// Reference to the calling object. + /// Dictionary of alias and structural properties for the alternate key. + public static void AddAlternateKeyAnnotation(this EdmModel model, IEdmEntityType type, IDictionary alternateKey) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(type, "type"); + EdmUtil.CheckArgumentNull(alternateKey, "alternateKey"); + + EdmCollectionExpression annotationValue = null; + var ann = model.FindVocabularyAnnotations(type, AlternateKeysVocabularyModel.AlternateKeysTerm).FirstOrDefault(); + if (ann != null) + { + annotationValue = ann.Value as EdmCollectionExpression; + } + + var alternateKeysCollection = annotationValue != null ? new List(annotationValue.Elements) : new List(); + + List propertyRefs = new List(); + + foreach (KeyValuePair kvp in alternateKey) + { + IEdmRecordExpression propertyRef = new EdmRecordExpression( + new EdmComplexTypeReference(AlternateKeysVocabularyModel.PropertyRefType, false), + new EdmPropertyConstructor(AlternateKeysVocabularyConstants.PropertyRefTypeAliasPropertyName, new EdmStringConstant(kvp.Key)), + new EdmPropertyConstructor(AlternateKeysVocabularyConstants.PropertyRefTypeNamePropertyName, new EdmPropertyPathExpression(kvp.Value.Name))); + propertyRefs.Add(propertyRef); + } + + EdmRecordExpression alternateKeyRecord = new EdmRecordExpression( + new EdmComplexTypeReference(AlternateKeysVocabularyModel.AlternateKeyType, false), + new EdmPropertyConstructor(AlternateKeysVocabularyConstants.AlternateKeyTypeKeyPropertyName, new EdmCollectionExpression(propertyRefs))); + + alternateKeysCollection.Add(alternateKeyRecord); + + var annotation = new EdmVocabularyAnnotation( + type, + AlternateKeysVocabularyModel.AlternateKeysTerm, + new EdmCollectionExpression(alternateKeysCollection)); + + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + + /// + /// Checks whether the given entity type has the as one of the key properties. + /// + /// Given entity type. + /// Property to be searched for. + /// true if the type or base types has given property declared as key. false otherwise. + public static bool HasDeclaredKeyProperty(this IEdmEntityType entityType, IEdmProperty property) + { + EdmUtil.CheckArgumentNull(entityType, "entityType"); + EdmUtil.CheckArgumentNull(property, "property"); + + while (entityType != null) + { + if (entityType.DeclaredKey != null && entityType.DeclaredKey.Any(k => k == property)) + { + return true; + } + + entityType = entityType.BaseEntityType(); + } + + return false; + } + + #endregion + + #region IEdmEntityTypeReference + /// + /// Gets the definition of this entity reference. + /// + /// Reference to the calling object. + /// The definition of this entity reference. + public static IEdmEntityType EntityDefinition(this IEdmEntityTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmEntityType)type.Definition; + } + + /// + /// Gets the base type of the definition of this reference. + /// + /// Reference to the calling object. + /// The base type of the definition of this reference. + public static IEdmEntityType BaseEntityType(this IEdmEntityTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.EntityDefinition().BaseEntityType(); + } + + /// + /// Gets the entity key of the definition of this reference. + /// + /// Reference to the calling object. + /// The entity key of the definition of this reference. + public static IEnumerable Key(this IEdmEntityTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.EntityDefinition().Key(); + } + #endregion + + #region IEdmComplexTypeDefinition + /// + /// Gets the base type of this references definition. + /// + /// Reference to the calling object. + /// The base type of this references definition. + public static IEdmComplexType BaseComplexType(this IEdmComplexType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.BaseType as IEdmComplexType; + } + #endregion + + #region IEdmComplexTypeReference + /// + /// Gets the definition of this reference typed as an IEdmComplexTypeDefinition. + /// + /// Reference to the calling object. + /// The definition of this reference typed as an IEdmComplexTypeDefinition. + public static IEdmComplexType ComplexDefinition(this IEdmComplexTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmComplexType)type.Definition; + } + + /// + /// Gets the base type of this reference. + /// + /// Reference to the calling object. + /// The base type of this reference. + public static IEdmComplexType BaseComplexType(this IEdmComplexTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.ComplexDefinition().BaseComplexType(); + } + #endregion + + #region IEdmEntityReferenceTypeReference + /// + /// Gets the definition of this entity reference type reference. + /// + /// Reference to the calling object. + /// The definition of this entity reference type reference. + public static IEdmEntityReferenceType EntityReferenceDefinition(this IEdmEntityReferenceTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmEntityReferenceType)type.Definition; + } + + /// + /// Gets the entity type referred to by the definition of this entity reference type reference. + /// + /// Reference to the calling object. + /// The entity type referred to by the definition of this entity reference type reference. + public static IEdmEntityType EntityType(this IEdmEntityReferenceTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.EntityReferenceDefinition().EntityType; + } + #endregion + + #region IEdmCollectionTypeReference + /// + /// Gets the definition of this collection reference. + /// + /// Reference to the calling object. + /// The definition of this collection reference. + public static IEdmCollectionType CollectionDefinition(this IEdmCollectionTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmCollectionType)type.Definition; + } + + /// + /// Gets the element type of the definition of this collection reference. + /// + /// Reference to the calling object. + /// The element type of the definition of this collection reference. + public static IEdmTypeReference ElementType(this IEdmCollectionTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return type.CollectionDefinition().ElementType; + } + + #endregion + + #region IEdmEnumTypeReference + + /// + /// Gets the definition of this enumeration reference. + /// + /// Reference to the calling object. + /// The definition of this enumeration reference. + public static IEdmEnumType EnumDefinition(this IEdmEnumTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmEnumType)type.Definition; + } + + #endregion + + #region IEdmTypeDefinitionReference + + /// + /// Gets the definition of this type definition reference. + /// + /// Reference to the calling object. + /// The definition of this type definition reference. + public static IEdmTypeDefinition TypeDefinition(this IEdmTypeDefinitionReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return (IEdmTypeDefinition)type.Definition; + } + + #endregion + + #region IEdmNavigationProperty + + /// + /// Gets the multiplicity of the target of this navigation. + /// + /// Reference to the calling object. + /// The multiplicity of the target end of the relationship. + public static EdmMultiplicity TargetMultiplicity(this IEdmNavigationProperty property) + { + EdmUtil.CheckArgumentNull(property, "property"); + + IEdmTypeReference type = property.Type; + if (type.IsCollection()) + { + return EdmMultiplicity.Many; + } + + return type.IsNullable ? EdmMultiplicity.ZeroOrOne : EdmMultiplicity.One; + } + + /// + /// Gets the entity type targeted by this navigation property. + /// + /// Reference to the calling object. + /// The entity type targeted by this navigation property. + public static IEdmEntityType ToEntityType(this IEdmNavigationProperty property) + { + return property.Type.Definition.AsElementType() as IEdmEntityType; + } + + /// + /// Gets the structured type targeted by this structural property type reference. + /// + /// Reference to the calling object. + /// The structured type targeted by this structural property. + public static IEdmStructuredType ToStructuredType(this IEdmTypeReference propertyTypeReference) + { + IEdmType target = propertyTypeReference.Definition; + if (target.TypeKind == EdmTypeKind.Collection) + { + target = ((IEdmCollectionType)target).ElementType.Definition; + } + + return target as IEdmStructuredType; + } + + /// + /// Gets the entity type declaring this navigation property. + /// + /// Reference to the calling object. + /// The entity type that declares this navigation property. + public static IEdmEntityType DeclaringEntityType(this IEdmNavigationProperty property) + { + return (IEdmEntityType)property.DeclaringType; + } + + /// + /// Gets whether this navigation property originates at the principal end of an association. + /// + /// The navigation property. + /// Whether this navigation property originates at the principal end of an association. + public static bool IsPrincipal(this IEdmNavigationProperty navigationProperty) + { + return navigationProperty.ReferentialConstraint == null && navigationProperty.Partner != null && navigationProperty.Partner.ReferentialConstraint != null; + } + + /// + /// Gets the dependent properties of this navigation property, returning null if this is the principal entity or if there is no referential constraint. + /// + /// The navigation property. + /// The dependent properties of this navigation property, returning null if this is the principal entity or if there is no referential constraint. + public static IEnumerable DependentProperties(this IEdmNavigationProperty navigationProperty) + { + return navigationProperty.ReferentialConstraint == null ? null : navigationProperty.ReferentialConstraint.PropertyPairs.Select(p => p.DependentProperty); + } + + /// + /// Gets the principal properties of this navigation property, returning null if this is the principal entity or if there is no referential constraint. + /// + /// The navigation property. + /// The principal properties of this navigation property, returning null if this is the principal entity or if there is no referential constraint. + public static IEnumerable PrincipalProperties(this IEdmNavigationProperty navigationProperty) + { + return navigationProperty.ReferentialConstraint == null ? null : navigationProperty.ReferentialConstraint.PropertyPairs.Select(p => p.PrincipalProperty); + } + + #endregion + + #region IEdmVocabularyAnnotation + /// + /// Gets the term of this annotation. + /// + /// Reference to the calling object. + /// The term of this annotation. + public static IEdmTerm Term(this IEdmVocabularyAnnotation annotation) + { + EdmUtil.CheckArgumentNull(annotation, "annotation"); + return annotation.Term; + } + #endregion + + #region IEdmOperationImport + /// + /// Tries to get the relative entity set path. + /// + /// The operation to resolve the entitySet path. + /// The model. + /// The parameter. + /// The relative navigations and its path. + /// Last type of the entity. + /// The errors. + /// True if a Entity set path is found, false otherwise. + public static bool TryGetRelativeEntitySetPath(this IEdmOperation operation, IEdmModel model, out IEdmOperationParameter parameter, out Dictionary relativeNavigations, out IEdmEntityType lastEntityType, out IEnumerable errors) + { + errors = Enumerable.Empty(); + parameter = null; + relativeNavigations = null; + lastEntityType = null; + + Debug.Assert(operation != null, "expected non null operation"); + + // If a value does not exist just return as there is nothing to validate. + if (operation.EntitySetPath == null) + { + return false; + } + + Collection foundErrors = new Collection(); + errors = foundErrors; + if (!operation.IsBound) + { + foundErrors.Add( + new EdmError( + operation.Location(), + EdmErrorCode.OperationCannotHaveEntitySetPathWithUnBoundOperation, + Strings.EdmModel_Validator_Semantic_OperationCannotHaveEntitySetPathWithUnBoundOperation(operation.Name))); + } + + return TryGetRelativeEntitySetPath(operation, foundErrors, operation.EntitySetPath, model, operation.Parameters, out parameter, out relativeNavigations, out lastEntityType); + } + + + /// + /// Determines whether [is action import] [the specified operation import]. + /// + /// The operation import. + /// + /// true if [is action import] [the specified operation import]; otherwise, false. + /// + public static bool IsActionImport(this IEdmOperationImport operationImport) + { + return operationImport.ContainerElementKind == EdmContainerElementKind.ActionImport; + } + + /// + /// Determines whether [is function import] [the specified operation import]. + /// + /// The operation import. + /// + /// true if [is function import] [the specified operation import]; otherwise, false. + /// + public static bool IsFunctionImport(this IEdmOperationImport operationImport) + { + return operationImport.ContainerElementKind == EdmContainerElementKind.FunctionImport; + } + + /// + /// Analyzes .EntitySet expression and returns a static reference if available. + /// + /// The operation import containing the entity set expression. + /// The model containing the operation import. + /// The static entity set of the operation import. + /// True if the entity set expression of the contains a static reference to an , otherwise false. + /// TODO: Support resolving target path to a contained entity set. + public static bool TryGetStaticEntitySet(this IEdmOperationImport operationImport, IEdmModel model, out IEdmEntitySetBase entitySet) + { + var pathExpression = operationImport.EntitySet as IEdmPathExpression; + if (pathExpression != null) + { + return pathExpression.TryGetStaticEntitySet(model, out entitySet); + } + + entitySet = null; + return false; + } + + /// + /// Analyzes .EntitySet expression and returns a relative path to an if available. + /// The path starts with the and may have optional sequence of and type casts segments. + /// + /// The operation import containing the entity set expression. + /// The model containing the operation import. + /// The operation import parameter from which the relative entity set path starts. + /// The optional sequence of navigation properties and their path + /// The errors that were found when attempting to get the relative path. + /// True if the entity set expression of the contains a relative path an , otherwise false. + public static bool TryGetRelativeEntitySetPath(this IEdmOperationImport operationImport, IEdmModel model, out IEdmOperationParameter parameter, out Dictionary relativeNavigations, out IEnumerable edmErrors) + { + EdmUtil.CheckArgumentNull(operationImport, "operationImport"); + EdmUtil.CheckArgumentNull(model, "model"); + + parameter = null; + relativeNavigations = null; + edmErrors = new ReadOnlyCollection(new List()); + + IEdmPathExpression pathExpression = operationImport.EntitySet as IEdmPathExpression; + if (pathExpression != null) + { + IEdmEntityType entityType = null; + Collection foundErrors = new Collection(); + bool result = TryGetRelativeEntitySetPath(operationImport, foundErrors, pathExpression, model, operationImport.Operation.Parameters, out parameter, out relativeNavigations, out entityType); + edmErrors = new ReadOnlyCollection(foundErrors); + + return result; + } + + return false; + } + + #endregion + + #region IEdmOperation + + /// + /// Determines whether the specified operation is action. + /// + /// The operation. + /// + /// true if the specified operation is action; otherwise, false. + /// + public static bool IsAction(this IEdmOperation operation) + { + return operation.SchemaElementKind == EdmSchemaElementKind.Action; + } + + /// + /// Determines whether the specified operation is function. + /// + /// The operation. + /// + /// true if the specified operation is function; otherwise, false. + /// + public static bool IsFunction(this IEdmOperation operation) + { + return operation.SchemaElementKind == EdmSchemaElementKind.Function; + } + + /// + /// Gets the from the specified operation. + /// + /// The operation. + /// The instance of or null if the operation has no return type. + public static IEdmOperationReturn GetReturn(this IEdmOperation operation) + { + EdmOperation edmOperation = operation as EdmOperation; + if (edmOperation != null) + { + return edmOperation.Return; + } + + CsdlSemanticsOperation csdlOperation = operation as CsdlSemanticsOperation; + if (csdlOperation != null) + { + return csdlOperation.Return; + } + + if (operation == null || operation.ReturnType == null) + { + return null; + } + + return new EdmOperationReturn(operation, operation.ReturnType); + } + + /// + /// Checks whether all operations have the same return type + /// + /// the list to check + /// Ensures that the Where filter clause applies the Full name, + /// The operation name to filter by. + /// true if the list of operation imports all have the same return type + public static IEnumerable FilterByName(this IEnumerable operations, bool forceFullyQualifiedNameFilter, string operationName) + { + EdmUtil.CheckArgumentNull(operations, "operations"); + EdmUtil.CheckArgumentNull(operationName, "operationName"); + + if (forceFullyQualifiedNameFilter || operationName.IndexOf(".", StringComparison.Ordinal) > -1) + { + return operations.Where(o => o.FullName() == operationName); + } + else + { + return operations.Where(o => o.Name == operationName); + } + } + + /// + /// Determines whether the bound operation's binding type is equivalent to the specified binding type. + /// + /// The operation. + /// Type of the binding. + /// + /// true if [is operation binding type equivalent to] [the specified operation]; otherwise, false. + /// + public static bool HasEquivalentBindingType(this IEdmOperation operation, IEdmType bindingType) + { + EdmUtil.CheckArgumentNull(operation, "operation"); + EdmUtil.CheckArgumentNull(bindingType, "bindingType"); + + if (!operation.IsBound) + { + return false; + } + + IEdmOperationParameter parameter = operation.Parameters.FirstOrDefault(); + if (parameter == null) + { + return false; + } + + IEdmType parameterType = parameter.Type.Definition; + if (parameterType.TypeKind != bindingType.TypeKind) + { + return false; + } + + if (parameterType.TypeKind == EdmTypeKind.Collection) + { + // covariance applies here, so IEnumerable is applicable to IEnumerable where B:A + IEdmCollectionType parameterCollectionType = (IEdmCollectionType)parameterType; + IEdmCollectionType bindingCollectionType = (IEdmCollectionType)bindingType; + + return bindingCollectionType.ElementType.Definition.IsOrInheritsFrom(parameterCollectionType.ElementType.Definition); + } + else + { + return bindingType.IsOrInheritsFrom(parameterType); + } + } + + #endregion + + #region IEdmRecordExpression + + /// + /// Finds a property of a record expression. + /// + /// The record expression. + /// Name of the property to find. + /// The property, if found, otherwise null. + public static IEdmPropertyConstructor FindProperty(this IEdmRecordExpression expression, string name) + { + foreach (IEdmPropertyConstructor propertyConstructor in expression.Properties) + { + if (propertyConstructor.Name == name) + { + return propertyConstructor; + } + } + + return null; + } + + #endregion + + #region IEdmNavigationSource + + /// + /// Return the navigation kind of the navigation source. + /// + /// The navigation source. + /// The kind of the navigation source. + public static EdmNavigationSourceKind NavigationSourceKind(this IEdmNavigationSource navigationSource) + { + if (navigationSource is IEdmEntitySet) + { + return EdmNavigationSourceKind.EntitySet; + } + + if (navigationSource is IEdmSingleton) + { + return EdmNavigationSourceKind.Singleton; + } + + if (navigationSource is IEdmContainedEntitySet) + { + return EdmNavigationSourceKind.ContainedEntitySet; + } + + if (navigationSource is IEdmUnknownEntitySet) + { + return EdmNavigationSourceKind.UnknownEntitySet; + } + + return EdmNavigationSourceKind.None; + } + + /// + /// Returns the fully qualified name of a navigation source. + /// + /// The navigation source to get the full name for. + /// The full qualified name of the navigation source. + public static string FullNavigationSourceName(this IEdmNavigationSource navigationSource) + { + EdmUtil.CheckArgumentNull(navigationSource, "navigationSource"); + + return string.Join(".", navigationSource.Path.PathSegments.ToArray()); + } + + /// + /// Return the entity type of the navigation source. + /// + /// The navigation source. + /// The entity type of the navigation source. + public static IEdmEntityType EntityType(this IEdmNavigationSource navigationSource) + { + var entitySetBase = navigationSource as IEdmEntitySetBase; + if (entitySetBase != null) + { + IEdmCollectionType collectionType = entitySetBase.Type as IEdmCollectionType; + + if (collectionType != null) + { + return collectionType.ElementType.Definition as IEdmEntityType; + } + + var unknownEntitySet = entitySetBase as IEdmUnknownEntitySet; + if (unknownEntitySet != null) + { + // Handle missing navigation target for nullable + // singleton navigation property. + return unknownEntitySet.Type as IEdmEntityType; + } + + return null; + } + + var singleton = navigationSource as IEdmSingleton; + if (singleton != null) + { + return singleton.Type as IEdmEntityType; + } + + return null; + } + + #endregion + + #region IEdmReferences + + /// + /// Sets edmx:Reference information (IEdmReference) to the model. + /// + /// The IEdmModel to set edmx:Reference information. + /// The edmx:Reference information to be set. + public static void SetEdmReferences(this IEdmModel model, IEnumerable edmReferences) + { + EdmUtil.CheckArgumentNull(model, "model"); + model.SetAnnotationValue(model, EdmConstants.InternalUri, CsdlConstants.ReferencesAnnotation, edmReferences); + } + + /// + /// Gets edmx:Reference information (IEdmReference) from the model. + /// + /// The IEdmModel to get edmx:Reference information. + /// The edmx:Reference information. + public static IEnumerable GetEdmReferences(this IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + return (IEnumerable)model.GetAnnotationValue(model, EdmConstants.InternalUri, CsdlConstants.ReferencesAnnotation); + } + + #endregion + + /// + /// Gets the partner path of a navigation property. + /// + /// The navigation property. + /// Path to the partner navigation property from the related entity type. + public static IEdmPathExpression GetPartnerPath(this IEdmNavigationProperty navigationProperty) + { + var edmNavigationProperty = navigationProperty as EdmNavigationProperty; + if (edmNavigationProperty != null) + { + return edmNavigationProperty.PartnerPath; + } + + var csdlSemanticsNavigationProperty = navigationProperty as CsdlSemanticsNavigationProperty; + if (csdlSemanticsNavigationProperty != null) + { + return ((CsdlNavigationProperty)csdlSemanticsNavigationProperty.Element).PartnerPath; + } + + // Default behavior where partner path corresponds to the name of the partner nav. property. In other words, + // the partner must be on an entity type. Will remove this limitation once we are OK to make breaking changes + // on IEdmNavigationProperty. + return navigationProperty.Partner == null ? null : new EdmPathExpression(navigationProperty.Partner.Name); + } + + /// + /// Replace a possibly alias-qualified name with the full namespace qualified name. + /// + /// The model containing the element. + /// The alias- or namespace- qualified name of the element. + /// The namespace qualified name of the element. + internal static string ReplaceAlias(this IEdmModel model, string name) + { + VersioningDictionary mappings = model.GetNamespaceAliases(); + VersioningList list = model.GetUsedNamespacesHavingAlias(); + int idx = name.IndexOf('.'); + + if (list != null && mappings != null && idx > 0) + { + var typeAlias = name.Substring(0, idx); + var ns = list.FirstOrDefault(n => + { + string alias; + return mappings.TryGetValue(n, out alias) && alias == typeAlias; + }); + + return (ns != null) ? string.Format(CultureInfo.InvariantCulture, "{0}{1}", ns, name.Substring(idx)) : name; + } + + return name; + } + + #region methods for finding elements in CsdlSemanticsModel + + internal static IEnumerable FindOperationsInModelTree(this CsdlSemanticsModel model, string name) + { + return model.FindInModelTree(findOperations, name, mergeFunctions); + } + + /// + /// Find types in CsdlSemanticsModel tree. + /// + /// The CsdlSemanticsModel. + /// The name by which to search. + /// The found emd type or null. + internal static IEdmSchemaType FindTypeInModelTree(this CsdlSemanticsModel model, string name) + { + return model.FindInModelTree(findType, name, RegistrationHelper.CreateAmbiguousTypeBinding); + } + + /// + /// Searches for a type with the given name in the model and its main/sibling/referenced models, returns null if no such type exists. + /// + /// the type of value to find. + /// The model to search for type. + /// The func for each IEdmModel node to find element by name. + /// The qualified name of the type being found. + /// The func to combine results ifwhen more than one is found. + /// when searching, will ignore built-in types in EdmCoreModel and CoreVocabularyModel. + /// The requested type, or null if no such type exists. + internal static T FindInModelTree(this CsdlSemanticsModel model, Func finderFunc, string qualifiedName, Func ambiguousCreator) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(finderFunc, "finderFunc"); + EdmUtil.CheckArgumentNull(qualifiedName, "qualifiedName"); + EdmUtil.CheckArgumentNull(ambiguousCreator, "ambiguousCreator"); + + // find type in current model only + T result = finderFunc(model, qualifiedName); + T candidate; + + // now find type in main model and current model's sibling models. + if (model.MainModel != null) + { + // main model: + if ((candidate = finderFunc(model.MainModel, qualifiedName)) != null) + { + result = (result == null) ? candidate : ambiguousCreator(result, candidate); + } + + // current model's sibling models : + foreach (var tmp in model.MainModel.ReferencedModels) + { + // doesn't search the current model again + if ((tmp != EdmCoreModel.Instance) && (tmp != CoreVocabularyModel.Instance) + && tmp != model) + { + if ((candidate = finderFunc(tmp, qualifiedName)) != null) + { + result = (result == null) ? candidate : ambiguousCreator(result, candidate); + } + } + } + } + + // then find type in referenced models + foreach (var tmp in model.ReferencedModels) + { + candidate = finderFunc(tmp, qualifiedName); + if (candidate != null) + { + result = (result == null) ? candidate : ambiguousCreator(result, candidate); + } + } + + return result; + } + #endregion + + #region UrlEscape + /// + /// Determines whether the specified function is UrlEscape function or not. + /// + /// The Edm model. + /// The specified function + /// true if the specified operation is UrlEscape function; otherwise, false. + internal static bool IsUrlEscapeFunction(this IEdmModel model, IEdmFunction function) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(function, "function"); + + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(function, CommunityVocabularyModel.UrlEscapeFunctionTerm).FirstOrDefault(); + if (annotation != null) + { + if (annotation.Value == null) + { + // If the annotation is applied but a value is not specified then the value is assumed to be true. + return true; + } + + IEdmBooleanConstantExpression tagConstant = annotation.Value as IEdmBooleanConstantExpression; + if (tagConstant != null) + { + return tagConstant.Value; + } + } + + return false; + } + + /// + /// Set annotation Org.OData.Community.V1.UrlEscapeFunction to . + /// + /// The model to add annotation + /// The target function to set the inline annotation + internal static void SetUrlEscapeFunction(this EdmModel model, IEdmFunction function) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(function, "function"); + + IEdmBooleanConstantExpression booleanConstant = new EdmBooleanConstant(true); + IEdmTerm term = CommunityVocabularyModel.UrlEscapeFunctionTerm; + + Debug.Assert(term != null, "term!=null"); + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(function, term, booleanConstant); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + #endregion + + internal static bool TryGetRelativeEntitySetPath(IEdmElement element, Collection foundErrors, IEdmPathExpression pathExpression, IEdmModel model, IEnumerable parameters, out IEdmOperationParameter parameter, out Dictionary relativeNavigations, out IEdmEntityType lastEntityType) + { + parameter = null; + relativeNavigations = null; + lastEntityType = null; + + var pathItems = pathExpression.PathSegments.ToList(); + if (pathItems.Count < 1) + { + foundErrors.Add(new EdmError(element.Location(), EdmErrorCode.OperationWithInvalidEntitySetPathMissingCompletePath, Strings.EdmModel_Validator_Semantic_InvalidEntitySetPathMissingBindingParameterName(CsdlConstants.Attribute_EntitySetPath))); + return false; + } + + // If there is no parameter then this will fail in BoundOperationMustHaveParameters rule so skip validating this here. + parameter = parameters.FirstOrDefault(); + if (parameter == null) + { + return false; + } + + bool foundRelativePath = true; + + string bindingParameterName = pathItems.First(); + if (parameter.Name != bindingParameterName) + { + foundErrors.Add( + new EdmError( + element.Location(), + EdmErrorCode.InvalidPathFirstPathParameterNotMatchingFirstParameterName, + Strings.EdmModel_Validator_Semantic_InvalidEntitySetPathWithFirstPathParameterNotMatchingFirstParameterName(CsdlConstants.Attribute_EntitySetPath, EdmModelCsdlSchemaWriter.PathAsXml(pathExpression.PathSegments), bindingParameterName, parameter.Name))); + + foundRelativePath = false; + } + + lastEntityType = parameter.Type.Definition as IEdmEntityType; + if (lastEntityType == null) + { + var collectionReference = parameter.Type as IEdmCollectionTypeReference; + if (collectionReference != null && collectionReference.ElementType().IsEntity()) + { + lastEntityType = collectionReference.ElementType().Definition as IEdmEntityType; + } + else + { + foundErrors.Add( + new EdmError( + element.Location(), + EdmErrorCode.InvalidPathWithNonEntityBindingParameter, + Strings.EdmModel_Validator_Semantic_InvalidEntitySetPathWithNonEntityBindingParameter(CsdlConstants.Attribute_EntitySetPath, EdmModelCsdlSchemaWriter.PathAsXml(pathExpression.PathSegments), bindingParameterName))); + + return false; + } + } + + Dictionary navigationProperties = new Dictionary(); + List paths = new List(); + + // Now check that the next paths are valid parameters. + foreach (string pathSegment in pathItems.Skip(1)) + { + paths.Add(pathSegment); + + if (EdmUtil.IsQualifiedName(pathSegment)) + { + IEdmSchemaType foundType = model.FindDeclaredType(pathSegment); + if (foundType == null) + { + foundErrors.Add( + new EdmError( + element.Location(), + EdmErrorCode.InvalidPathUnknownTypeCastSegment, + Strings.EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownTypeCastSegment(CsdlConstants.Attribute_EntitySetPath, EdmModelCsdlSchemaWriter.PathAsXml(pathExpression.PathSegments), pathSegment))); + + foundRelativePath = false; + break; + } + + IEdmEntityType foundEntityTypeCast = foundType as IEdmEntityType; + + if (foundEntityTypeCast == null) + { + foundErrors.Add( + new EdmError( + element.Location(), + EdmErrorCode.InvalidPathTypeCastSegmentMustBeEntityType, + Strings.EdmModel_Validator_Semantic_InvalidEntitySetPathTypeCastSegmentMustBeEntityType(CsdlConstants.Attribute_EntitySetPath, EdmModelCsdlSchemaWriter.PathAsXml(pathExpression.PathSegments), foundType.FullName()))); + + foundRelativePath = false; + break; + } + + if (!foundEntityTypeCast.IsOrInheritsFrom(lastEntityType)) + { + foundErrors.Add( + new EdmError( + element.Location(), + EdmErrorCode.InvalidPathInvalidTypeCastSegment, + Strings.EdmModel_Validator_Semantic_InvalidEntitySetPathInvalidTypeCastSegment(CsdlConstants.Attribute_EntitySetPath, EdmModelCsdlSchemaWriter.PathAsXml(pathExpression.PathSegments), lastEntityType.FullName(), foundEntityTypeCast.FullName()))); + + foundRelativePath = false; + break; + } + + lastEntityType = foundEntityTypeCast; + } + else + { + IEdmNavigationProperty navigationProperty = lastEntityType.FindProperty(pathSegment) as IEdmNavigationProperty; + if (navigationProperty == null) + { + foundErrors.Add( + new EdmError( + element.Location(), + EdmErrorCode.InvalidPathUnknownNavigationProperty, + Strings.EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownNavigationProperty(CsdlConstants.Attribute_EntitySetPath, EdmModelCsdlSchemaWriter.PathAsXml(pathExpression.PathSegments), pathSegment))); + + foundRelativePath = false; + break; + } + + navigationProperties[navigationProperty] = new EdmPathExpression(paths); + + // In 7.4.1, FindNavigationTarget expected a binding path that included the path + // to the contained entity set. In 7.4.2 FindNavigationTarget was fixed to work off + // of the path from the contained entity set, but retained the old behavior as well + // for backward compatibility. In the next breaking change we should remove that + // behavior in FindNavigationTarget and remove this special handling of containsTarget + // by always clearing the path. + if (!navigationProperty.ContainsTarget) + { + paths.Clear(); + } + + lastEntityType = navigationProperty.ToEntityType(); + } + } + + relativeNavigations = navigationProperties; + return foundRelativePath; + } + + /// + /// This method is only used for the operation import entity set path parsing. + /// + /// The type of the segment. + /// Non-null entity type that may be bad. + internal static IEdmEntityType GetPathSegmentEntityType(IEdmTypeReference segmentType) + { + return (segmentType.IsCollection() ? segmentType.AsCollection().ElementType() : segmentType).AsEntity().EntityDefinition(); + } + + internal static IEnumerable AllElements(this IEdmEntityContainer container, int depth = ContainerExtendsMaxDepth) + { + if (depth <= 0) + { + throw new InvalidOperationException(Edm.Strings.Bad_CyclicEntityContainer(container.FullName())); + } + + CsdlSemanticsEntityContainer semanticsEntityContainer = container as CsdlSemanticsEntityContainer; + if (semanticsEntityContainer == null || semanticsEntityContainer.Extends == null) + { + return container.Elements; + } + + return container.Elements.Concat(semanticsEntityContainer.Extends.AllElements(depth - 1)); + } + + /// + /// Searches for entity set by the given name that may be container qualified in default container and .Extends containers. + /// + /// The container to search. + /// The name which might be container qualified. If no container name is provided, then default container will be searched. + /// The entity set found or empty if none found. + internal static IEdmEntitySet FindEntitySetExtended(this IEdmEntityContainer container, string qualifiedName) + { + return FindInContainerAndExtendsRecursively(container, qualifiedName, (c, n) => c.FindEntitySet(n), ContainerExtendsMaxDepth); + } + + /// + /// Searches for an entity set or contained navigation property according to the specified path that may be container qualified in default container and .Extends containers. + /// + /// The container to search. + /// The name which might be container qualified. If no container name is provided, then default container will be searched. + /// The entity set found or empty if none found. + internal static IEdmNavigationSource FindNavigationSourceExtended(this IEdmEntityContainer container, string path) + { + return FindInContainerAndExtendsRecursively(container, path, (c, n) => c.FindNavigationSource(n), ContainerExtendsMaxDepth); + } + + /// + /// Searches for an entity set or contained navigation property according to the specified path that may be container qualified in default container and .Extends containers. + /// + /// The container to search. + /// The path which might be container qualified. If no container name is provided, then default container will be searched. + /// The navigation source found or empty if none found. + internal static IEdmNavigationSource FindNavigationSource(this IEdmEntityContainer container, string path) + { + string[] pathSegments = path.Split('.').Last().Split('/'); + + // Starting segment must be a singleton or entity set + IEdmNavigationSource navigationSource = container.FindEntitySet(pathSegments[0]); + + if (navigationSource == null) + { + navigationSource = container.FindSingleton(pathSegments[0]); + } + + // Subsequent segments may be single-valued complex or containment nav props + List subPathSegments = new List(); + for (int i = 1; i < pathSegments.Length && navigationSource != null; i++) + { + subPathSegments.Add(pathSegments[i]); + IEdmNavigationProperty navProp = navigationSource.EntityType().FindProperty(pathSegments[i]) as IEdmNavigationProperty; + if (navProp != null) + { + navigationSource = navigationSource.FindNavigationTarget(navProp, new EdmPathExpression(subPathSegments)); + subPathSegments.Clear(); + } + } + + return navigationSource; + } + + /// + /// Searches for singleton by the given name that may be container qualified in default container and .Extends containers. If no container name is provided, then default container will be searched. + /// + /// The container to search. + /// The name which might be container qualified. If no container name is provided, then default container will be searched. + /// The singleton found or empty if none found. + internal static IEdmSingleton FindSingletonExtended(this IEdmEntityContainer container, string qualifiedName) + { + return FindInContainerAndExtendsRecursively(container, qualifiedName, (c, n) => c.FindSingleton(n), ContainerExtendsMaxDepth); + } + + /// + /// Searches for the operation imports by the specified name in default container and .Extends containers, returns an empty enumerable if no operation import exists. + /// + /// The container to search. + /// The qualified name of the operation import which may or may not include the container name. + /// All operation imports that can be found by the specified name, returns an empty enumerable if no operation import exists. + internal static IEnumerable FindOperationImportsExtended(this IEdmEntityContainer container, string qualifiedName) + { + return FindInContainerAndExtendsRecursively(container, qualifiedName, (c, n) => c.FindOperationImports(n), ContainerExtendsMaxDepth); + } + + /// + /// Get the primitive value converter for the given type definition in the model. + /// + /// The model involved. + /// The type definition. + /// The primitive value converter for the type definition. + internal static IPrimitiveValueConverter GetPrimitiveValueConverter(this IEdmModel model, IEdmType typeDefinition) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(typeDefinition != null, "typeDefinition must be provided"); + + // If the model does not have primitive value converter map yet, use the pass-through implementation. + var converter = model.GetAnnotationValue(typeDefinition, EdmConstants.InternalUri, CsdlConstants.PrimitiveValueConverterMapAnnotation); + if (converter == null) + { + return PassThroughPrimitiveValueConverter.Instance; + } + + return converter; + } + + /// + /// Set the primitive value converter for the given type definition in the model. + /// + /// The model involved. + /// The type definition. + /// The primitive value converter for the type definition. + internal static void SetPrimitiveValueConverter(this IEdmModel model, IEdmType typeDefinition, IPrimitiveValueConverter converter) + { + Debug.Assert(model != null, "model != null"); + Debug.Assert(typeDefinition != null, "typeDefinition must be provided"); + Debug.Assert(converter != null, "converter != null"); + + model.SetAnnotationValue(typeDefinition, EdmConstants.InternalUri, CsdlConstants.PrimitiveValueConverterMapAnnotation, converter); + } + + internal static bool TryGetStaticEntitySet(this IEdmPathExpression pathExpression, IEdmModel model, out IEdmEntitySetBase entitySet) + { + var segmentIterator = pathExpression.PathSegments.GetEnumerator(); + if (!segmentIterator.MoveNext()) + { + entitySet = null; + return false; + } + + IEdmEntityContainer container; + var segment = segmentIterator.Current; + if (segment.Contains(".")) + { + // The first segment is the qualified name of an entity container. + container = model.FindEntityContainer(segment); + + if (segmentIterator.MoveNext()) + { + segment = segmentIterator.Current; + } + else + { + // Path that only contains an entity container is invalid. + entitySet = null; + return false; + } + } + else + { + // No entity container specified. Use the default one from model. + container = model.EntityContainer; + } + + if (container == null) + { + entitySet = null; + return false; + } + + // The next segment must be entity set. + var resolvedEntitySet = container.FindEntitySet(segment); + + // If there is any segment left, the path must represent a contained entity set. + entitySet = segmentIterator.MoveNext() ? null : resolvedEntitySet; + return entitySet != null; + } + + internal static bool HasAny(this IEnumerable enumerable) where T : class + { + IList list = enumerable as IList; + if (list != null) + { + return list.Count > 0; + } + + T[] array = enumerable as T[]; + if (array != null) + { + return array.Length > 0; + } + + return enumerable.FirstOrDefault() != null; + } + + /// + /// Gets the declared alternate keys of the most defined entity with a declared key present. + /// + /// Reference to the calling object. + /// The model to be used. + /// Alternate Keys of this type. + private static IEnumerable> GetDeclaredAlternateKeysForType(IEdmEntityType type, IEdmModel model) + { + IEdmVocabularyAnnotation annotationValue = model.FindVocabularyAnnotations(type, AlternateKeysVocabularyModel.AlternateKeysTerm).FirstOrDefault(); + + if (annotationValue != null) + { + List> declaredAlternateKeys = new List>(); + + IEdmCollectionExpression keys = annotationValue.Value as IEdmCollectionExpression; + Debug.Assert(keys != null, "expected IEdmCollectionExpression for alternate key annotation value"); + + foreach (IEdmRecordExpression key in keys.Elements.OfType()) + { + var edmPropertyConstructor = key.Properties.FirstOrDefault(e => e.Name == AlternateKeysVocabularyConstants.AlternateKeyTypeKeyPropertyName); + if (edmPropertyConstructor != null) + { + IEdmCollectionExpression collectionExpression = edmPropertyConstructor.Value as IEdmCollectionExpression; + Debug.Assert(collectionExpression != null, "expected IEdmCollectionExpression type for Key Property"); + + IDictionary alternateKey = new Dictionary(); + foreach (IEdmRecordExpression propertyRef in collectionExpression.Elements.OfType()) + { + var aliasProp = propertyRef.Properties.FirstOrDefault(e => e.Name == AlternateKeysVocabularyConstants.PropertyRefTypeAliasPropertyName); + Debug.Assert(aliasProp != null, "expected non null Alias Property"); + string alias = ((IEdmStringConstantExpression)aliasProp.Value).Value; + + var nameProp = propertyRef.Properties.FirstOrDefault(e => e.Name == AlternateKeysVocabularyConstants.PropertyRefTypeNamePropertyName); + Debug.Assert(nameProp != null, "expected non null Name Property"); + string propertyName = ((IEdmPathExpression)nameProp.Value).PathSegments.FirstOrDefault(); + + alternateKey[alias] = type.FindProperty(propertyName); + } + + if (alternateKey.Any()) + { + declaredAlternateKeys.Add(alternateKey); + } + } + } + + return declaredAlternateKeys; + } + + return null; + } + + private static T FindAcrossModels(this IEdmModel model, TInput qualifiedName, Func finder, Func ambiguousCreator) + { + T candidate = finder(model, qualifiedName); + + foreach (IEdmModel reference in model.ReferencedModels) + { + T fromReference = finder(reference, qualifiedName); + if (fromReference != null) + { + candidate = candidate == null ? fromReference : ambiguousCreator(candidate, fromReference); + } + } + + return candidate; + } + + private static T GetTermValue(this IEdmModel model, IEdmStructuredValue context, IEdmEntityType contextType, IEdmTerm term, string qualifier, Func evaluator) + { + IEnumerable annotations = model.FindVocabularyAnnotations(contextType, term, qualifier); + + if (annotations.Count() != 1) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_NoValueAnnotationOnType(contextType.ToTraceString(), term.ToTraceString())); + } + + return evaluator(annotations.Single().Value, context, term.Type); + } + + private static T GetTermValue(this IEdmModel model, IEdmStructuredValue context, IEdmEntityType contextType, string termName, string qualifier, Func evaluator) + { + IEnumerable annotations = model.FindVocabularyAnnotations(contextType, termName, qualifier); + + if (annotations.Count() != 1) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_NoValueAnnotationOnType(contextType.ToTraceString(), termName)); + } + + IEdmVocabularyAnnotation valueAnnotation = annotations.Single(); + return evaluator(valueAnnotation.Value, context, valueAnnotation.Term().Type); + } + + private static T GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, IEdmTerm term, string qualifier, Func evaluator) + { + IEnumerable annotations = model.FindVocabularyAnnotations(element, term, qualifier); + + if (annotations.Count() != 1) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_NoValueAnnotationOnElement(term.ToTraceString())); + } + + return evaluator(annotations.Single().Value, null, term.Type); + } + + private static T GetTermValue(this IEdmModel model, IEdmVocabularyAnnotatable element, string termName, string qualifier, Func evaluator) + { + IEnumerable annotations = model.FindVocabularyAnnotations(element, termName, qualifier); + + if (annotations.Count() != 1) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_NoValueAnnotationOnElement(termName)); + } + + IEdmVocabularyAnnotation valueAnnotation = annotations.Single(); + return evaluator(valueAnnotation.Value, null, valueAnnotation.Term().Type); + } + + /// + /// Search entity set or singleton or operation import in container and its extended containers. + /// + /// The IEdmEntityContainerElement derived type. + /// The IEdmEntityContainer object, can be CsdlSemanticsEntityContainer. + /// A simple (not fully qualified) entity set name, singleton name, operation import name or path. + /// The func to do the search within container. + /// The recursive deepth of .Extends containers to search. + /// The found entity set or singleton or operation import. + private static T FindInContainerAndExtendsRecursively(IEdmEntityContainer container, string simpleName, Func finderFunc, int depth) + { + Debug.Assert(finderFunc != null, "finderFunc!=null"); + EdmUtil.CheckArgumentNull(container, "container"); + if (depth <= 0) + { + // TODO: p2 add a new string resource for the error message + throw new InvalidOperationException(Edm.Strings.Bad_CyclicEntityContainer(container.FullName())); + } + + T ret = finderFunc(container, simpleName); + IEnumerable operations = ret as IEnumerable; + if (ret == null || operations != null && !operations.HasAny()) + { + // for CsdlSemanticsEntityContainer, try searching .Extends container : + // (after IEdmModel has public Extends property, don't need to check CsdlSemanticsEntityContainer) + CsdlSemanticsEntityContainer tmp = container as CsdlSemanticsEntityContainer; + if (tmp != null && tmp.Extends != null) + { + return FindInContainerAndExtendsRecursively(tmp.Extends, simpleName, finderFunc, --depth); + } + } + + return ret; + } + + private static T AnnotationValue(object annotation) where T : class + { + if (annotation != null) + { + T specificAnnotation = annotation as T; + if (specificAnnotation != null) + { + return specificAnnotation; + } + + IEdmValue valueAnnotation = annotation as IEdmValue; + if (valueAnnotation != null) + { + // [EdmLib] AnnotationValue extension method should use the Clr converter to map annotation value to T. + } + + throw new InvalidOperationException(Edm.Strings.Annotations_TypeMismatch(annotation.GetType().Name, typeof(T).Name)); + } + + return null; + } + + private static void DerivedFrom(this IEdmModel model, IEdmStructuredType baseType, HashSetInternal visited, List derivedTypes) + { + if (visited.Add(baseType)) + { + IEnumerable candidates = model.FindDirectlyDerivedTypes(baseType); + if (candidates != null && candidates.HasAny()) + { + foreach (IEdmStructuredType derivedType in candidates) + { + derivedTypes.Add(derivedType); + model.DerivedFrom(derivedType, visited, derivedTypes); + } + } + + foreach (IEdmModel referenced in model.ReferencedModels) + { + candidates = referenced.FindDirectlyDerivedTypes(baseType); + if (candidates != null && candidates.HasAny()) + { + foreach (IEdmStructuredType derivedType in candidates) + { + derivedTypes.Add(derivedType); + model.DerivedFrom(derivedType, visited, derivedTypes); + } + } + } + } + } + + private static void SetChangeTrackingAnnotationImplementation(this EdmModel model, IEdmVocabularyAnnotatable target, bool isSupported, IEnumerable filterableProperties, IEnumerable expandableProperties) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(target, "target"); + + if (filterableProperties == null) + { + filterableProperties = EmptyStructuralProperties; + } + + if (expandableProperties == null) + { + expandableProperties = EmptyNavigationProperties; + } + + IList properties = new List + { + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.ChangeTrackingSupported, new EdmBooleanConstant(isSupported)), + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.ChangeTrackingFilterableProperties, new EdmCollectionExpression(filterableProperties.Select(p => new EdmPropertyPathExpression(p.Name)).ToArray())), + new EdmPropertyConstructor(CapabilitiesVocabularyConstants.ChangeTrackingExpandableProperties, new EdmCollectionExpression(expandableProperties.Select(p => new EdmNavigationPropertyPathExpression(p.Name)).ToArray())) + }; + + IEdmRecordExpression record = new EdmRecordExpression(properties); + IEdmTerm term = CapabilitiesVocabularyModel.ChangeTrackingTerm; + + Debug.Assert(term != null, "term!=null"); + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(target, term, record); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); + model.SetVocabularyAnnotation(annotation); + } + + private static IEdmTypeDefinitionReference GetUIntImplementation(this EdmModel model, string namespaceName, string name, string underlyingType, bool isNullable) + { + EdmUtil.CheckArgumentNull(model, "model"); + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + + Debug.Assert(!string.IsNullOrEmpty(name), "name must be provided"); + + string qualifiedName = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, name); + + // If the user has already defined his own UInt TypeDefinition, we don't define ours anymore. + var type = model.FindDeclaredType(qualifiedName) as IEdmTypeDefinition; + if (type == null) + { + type = new EdmTypeDefinition(namespaceName, name, EdmCoreModel.Instance.GetPrimitiveTypeKind(underlyingType)); + + model.AddElement(type); + + model.SetPrimitiveValueConverter(type, DefaultPrimitiveValueConverter.Instance); + } + + var typeReference = new EdmTypeDefinitionReference(type, isNullable); + + return typeReference; + } + + internal static class TypeName + { + // Use the name of the type as its local name for annotations. + // Filter out special characters to produce a valid name: + // '.' Appears in qualified names. + // '`', '[', ']', ',' Appear in generic instantiations. + // '+' Appears in names of local classes. + public static readonly string LocalName = typeof(T).ToString().Replace("_", "_____").Replace('.', '_').Replace("[", "").Replace("]", "").Replace(",", "__").Replace("`", "___").Replace("+", "____"); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ToTraceStringExtensionMethods.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ToTraceStringExtensionMethods.cs new file mode 100644 index 0000000..c703a4f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ExtensionMethods/ToTraceStringExtensionMethods.cs @@ -0,0 +1,194 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Text; + +namespace Microsoft.OData.Edm +{ + /// + /// Contains ToTraceString() extension methods. + /// + public static class ToTraceStringExtensionMethods + { + /// + /// Returns the text representation of the current object. + /// + /// Reference to the calling object. + /// The text representation of the current object. + public static string ToTraceString(this IEdmSchemaType schemaType) + { + return ((IEdmSchemaElement)schemaType).ToTraceString(); + } + + /// + /// Returns the text representation of the current object. + /// + /// Reference to the calling object. + /// The text representation of the current object. + public static string ToTraceString(this IEdmSchemaElement schemaElement) + { + return schemaElement.FullName(); + } + + /// + /// Returns the text representation of the current object. + /// + /// Reference to the calling object. + /// The text representation of the current object. + public static string ToTraceString(this IEdmType type) + { + EdmUtil.CheckArgumentNull(type, "type"); + switch (type.TypeKind) + { + case EdmTypeKind.Collection: + return ((IEdmCollectionType)type).ToTraceString(); + case EdmTypeKind.EntityReference: + return ((IEdmEntityReferenceType)type).ToTraceString(); + default: + var schemaType = type as IEdmSchemaType; + return schemaType != null ? schemaType.ToTraceString() : EdmConstants.Value_UnknownType; + } + } + + /// + /// Returns the text representation of the current object. + /// + /// Reference to the calling object. + /// The text representation of the current object. + public static string ToTraceString(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + StringBuilder sb = new StringBuilder(); + sb.Append('['); + if (type.Definition != null) + { + sb.Append(type.Definition.ToTraceString()); + sb.AppendKeyValue(EdmConstants.FacetName_Nullable, type.IsNullable.ToString()); + if (type.IsPrimitive()) + { + sb.AppendFacets(type.AsPrimitive()); + } + } + + sb.Append(']'); + return sb.ToString(); + } + + /// + /// Returns the text representation of the current object. + /// + /// Reference to the calling object. + /// The text representation of the current object. + public static string ToTraceString(this IEdmProperty property) + { + EdmUtil.CheckArgumentNull(property, "property"); + return (property.Name != null ? property.Name : "") + ":" + (property.Type != null ? property.Type.ToTraceString() : ""); + } + + private static string ToTraceString(this IEdmEntityReferenceType type) + { + return EdmTypeKind.EntityReference.ToString() + '(' + (type.EntityType != null ? type.EntityType.ToTraceString() : "") + ')'; + } + + private static string ToTraceString(this IEdmCollectionType type) + { + return EdmTypeKind.Collection.ToString() + '(' + (type.ElementType != null ? type.ElementType.ToTraceString() : "") + ')'; + } + + private static void AppendFacets(this StringBuilder sb, IEdmPrimitiveTypeReference type) + { + switch (type.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Binary: + sb.AppendBinaryFacets(type.AsBinary()); + break; + case EdmPrimitiveTypeKind.Decimal: + sb.AppendDecimalFacets(type.AsDecimal()); + break; + case EdmPrimitiveTypeKind.String: + sb.AppendStringFacets(type.AsString()); + break; + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.DateTimeOffset: + sb.AppendTemporalFacets(type.AsTemporal()); + break; + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + sb.AppendSpatialFacets(type.AsSpatial()); + break; + } + } + + private static void AppendBinaryFacets(this StringBuilder sb, IEdmBinaryTypeReference type) + { + if (type.IsUnbounded || type.MaxLength != null) + { + sb.AppendKeyValue(EdmConstants.FacetName_MaxLength, (type.IsUnbounded) ? EdmConstants.Value_Max : type.MaxLength.ToString()); + } + } + + private static void AppendStringFacets(this StringBuilder sb, IEdmStringTypeReference type) + { + if (type.IsUnbounded == true || type.MaxLength != null) + { + sb.AppendKeyValue(EdmConstants.FacetName_MaxLength, (type.IsUnbounded) ? EdmConstants.Value_Max : type.MaxLength.ToString()); + } + + if (type.IsUnicode != null) + { + sb.AppendKeyValue(EdmConstants.FacetName_Unicode, type.IsUnicode.ToString()); + } + } + + private static void AppendTemporalFacets(this StringBuilder sb, IEdmTemporalTypeReference type) + { + if (type.Precision != null) + { + sb.AppendKeyValue(EdmConstants.FacetName_Precision, type.Precision.ToString()); + } + } + + private static void AppendDecimalFacets(this StringBuilder sb, IEdmDecimalTypeReference type) + { + if (type.Precision != null) + { + sb.AppendKeyValue(EdmConstants.FacetName_Precision, type.Precision.ToString()); + } + + if (type.Scale != null) + { + sb.AppendKeyValue(EdmConstants.FacetName_Scale, type.Scale.ToString()); + } + } + + private static void AppendSpatialFacets(this StringBuilder sb, IEdmSpatialTypeReference type) + { + sb.AppendKeyValue(EdmConstants.FacetName_Srid, type.SpatialReferenceIdentifier != null ? type.SpatialReferenceIdentifier.ToString() : EdmConstants.Value_SridVariable); + } + + private static void AppendKeyValue(this StringBuilder sb, string key, string value) + { + sb.Append(' '); + sb.Append(key); + sb.Append('='); + sb.Append(value); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/GlobalSuppressions.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/GlobalSuppressions.cs new file mode 100644 index 0000000..ca42392 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/GlobalSuppressions.cs @@ -0,0 +1,281 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click +// "In Project Suppression File". +// You do not need to add suppressions to this file manually. + +#region Permanent Exclusions + +#region Excluding autogenerated code + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Edm.Error.#NotSupported()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Edm.Error.#NotImplemented()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Edm.Error.#ArgumentOutOfRange(System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Edm.Error.#ArgumentNull(System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Edm.EntityRes.#Resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Edm.EntityRes.#GetString(System.String,System.Boolean&)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.OData.Edm.EntityRes.#GetObject(System.String)")] + +#endregion + +#region Architectural Complaints +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ToTraceStringExtensionMethods.#ToTraceString(Microsoft.OData.Edm.IEdmSchemaType)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.EdmTypeSemantics.#InheritsFrom(Microsoft.OData.Edm.IEdmStructuredType,Microsoft.OData.Edm.IEdmStructuredType)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.EdmElementComparer.#IsEquivalentTo(Microsoft.OData.Edm.IEdmAssociation,Microsoft.OData.Edm.IEdmAssociation)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#DeclaringEntityType(Microsoft.OData.Edm.IEdmNavigationProperty)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#ToEntityType(Microsoft.OData.Edm.IEdmNavigationProperty)")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.OData.Edm.IEdmCollectionTypeReference")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.OData.Edm.IEdmEntityReferenceTypeReference")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.OData.Edm.IEdmPrimitiveTypeReference")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.OData.Edm.IEdmStructuredTypeReference")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.OData.Edm.Vocabularies.IEdmVocabularyAnnotatable")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.IEdmBinaryValue.#Value")] + +#endregion + +#region Naming Complaints + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Scope = "member", Target = "Microsoft.OData.Edm.EdmConstants.#EdmVersion1_1", Justification = "Underscore is used instead of . in the version name. Can't replace it with anything better.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Scope = "member", Target = "Microsoft.OData.Edm.EdmConstants.#EdmVersion1_2", Justification = "Underscore is used instead of . in the version name. Can't replace it with anything better.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Scope = "member", Target = "Microsoft.OData.Edm.EdmConstants.#EdmVersion2_2", Justification = "Underscore is used instead of . in the version name. Can't replace it with anything better.")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Scope = "member", Target = "Microsoft.OData.Edm.IEdmOperationImport.#IsComposable")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "IsComposable", Scope = "resource", Target = "Microsoft.OData.Edm.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "composable", Scope = "resource", Target = "Microsoft.OData.Edm.resources")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiPoint", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeometryMultiPoint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiLine", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeographyMultiLineString")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiPoint", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeographyMultiPoint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiLine", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeometryMultiLineString")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "To", Scope = "member", Target = "Microsoft.OData.Edm.IEdmNavigationProperty.#To")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Property", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.IEdmPropertyValue.#Property")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Scope = "member", Target = "Microsoft.OData.Edm.IEdmSchemaElement.#Namespace")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Property", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.IEdmStructuredValue.#FindPropertyValue(Microsoft.OData.Edm.IEdmProperty)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Property", Scope = "member", Target = "Microsoft.OData.Edm.EdmStructuredType.#RemoveProperty(Microsoft.OData.Edm.IEdmProperty)")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "1#", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmToClrConverter.RegisterConvertedObject(Microsoft.OData.Edm.Vocabularies.IEdmStructuredValue,System.Object):System.Void")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ANon", Scope = "member", Target = "Microsoft.OData.Edm.Validation.EdmErrorCode.#NullCannotBeAssertedToBeANonNullableType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "BeSide", Scope = "member", Target = "Microsoft.OData.Edm.Validation.EdmErrorCode.#ComposableOperationImportCannotBeSideEffecting")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "BeSide", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationImportComposableOperationImportCannotBeSideEffecting")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Srid", Scope = "member", Target = "Microsoft.OData.Edm.Validation.EdmErrorCode.#InvalidSrid")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.Validation.ValidationRuleSet")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmDirectValueAnnotation.#.ctor(System.String,System.String,System.Object)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmDirectValueAnnotationBinding.#.ctor(Microsoft.OData.Edm.IEdmElement,System.String,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmDirectValueAnnotationBinding.#.ctor(Microsoft.OData.Edm.IEdmElement,System.String,System.String,System.Object)")] + +#endregion + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Scope = "member", Target = "Microsoft.OData.Edm.EdmNamedElement.#elementName", Justification = "Making this a property would result in substantially worse code")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields", Scope = "member", Target = "Microsoft.OData.Edm.EdmStructuredType.#BaseStructuredType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2211:NonConstantFieldsShouldNotBeVisible", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmNullExpression.#Instance")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.EdmEntityType.#AddNavigation(System.String,Microsoft.OData.Edm.EdmEntityType,Microsoft.OData.Edm.EdmMultiplicity)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.EdmEntityType.#AddNavigation(System.String,Microsoft.OData.Edm.EdmEntityType,Microsoft.OData.Edm.EdmMultiplicity,Microsoft.OData.Edm.EdmMultiplicity)")] + +#endregion + +#region Temporary Exclusions + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.IEdmImmediateValueAnnotation.#NamespaceUri")] + +#endregion +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.OData.Edm.IEdmElement")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.IEdmDirectValueAnnotation.#NamespaceUri")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.IEdmDirectValueAnnotationBinding.#NamespaceUri")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningList`1")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningList`1+ArrayVersioningList")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningList`1+EmptyVersioningList")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningList`1+LinkedVersioningList")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningDictionary`2")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningDictionary`2+EmptyVersioningDictionary")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningDictionary`2+HashTreeDictionary")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningDictionary`2+OneKeyDictionary")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningDictionary`2+TreeDictionary")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.OData.Edm.VersioningDictionary`2+TwoKeyDictionary")] + +#region ValidationRules +// Suppress CA2104 messages since validationRule object is immutable. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#DirectValueAnnotationHasXmlSerializableName")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#BinaryTypeReferenceBinaryUnboundedNotValidForMaxLength")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OpenComplexTypeCannotHaveClosedDerivedComplexType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#DecimalTypeReferencePrecisionOutOfRange")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityContainerDuplicateEntityContainerMemberName")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityReferenceTypeInaccessibleEntityType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntitySetCanOnlyBeContainedByASingleNavigationProperty")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationMappingMustBeBidirectional")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyMappingsMustBeUnique")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationSourceTypeHasNoKeys")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeEntityKeyMustBeScalar")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeInvalidKeyKeyDefinedInBaseClass")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeKeyMissingOnEntityType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EnumMemberValueMustHaveSameTypeAsUnderlyingType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EnumTypeEnumMemberNameAlreadyDefined")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#FunctionApplicationExpressionParametersMatchAppliedFunction")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#BoundOperationMustHaveParameters")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OptionalParametersMustComeAfterRequiredParameters")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationImportEntitySetExpressionIsInvalid")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationUnsupportedReturnType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#IfExpressionAssertCorrectTestType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#ImmediateValueAnnotationElementAnnotationIsValid")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#ModelDuplicateSchemaElementName")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NamedElementNameIsNotAllowed")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NamedElementNameMustNotBeEmptyOrWhiteSpace")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyCorrectType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyDependentPropertiesMustBelongToDependentEntity")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyEndWithManyMultiplicityCannotHaveOperationsSpecified")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyInvalidOperationMultipleEndsInAssociatedNavigationProperties")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyPrincipalEndMultiplicity")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyWithRecursiveContainmentTargetMustBeOptional")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#PrimitiveValueValidForType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#PropertyValueBindingValueIsCorrectType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#SchemaElementMustNotHaveKindOfNone")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#SchemaElementNamespaceIsTooLong")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#SchemaElementSystemNamespaceEncountered")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StringTypeReferenceStringUnboundedNotValidForMaxLength")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StructuredTypeBaseTypeMustBeSameKindAsDerivedKind")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StructuredTypeInvalidMemberNameMatchesTypeName")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StructuredTypePropertyNameAlreadyDefined")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#AnnotationInaccessibleTerm")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#TypeReferenceInaccessibleSchemaType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#VocabularyAnnotatableNoDuplicateAnnotations")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#VocabularyAnnotationInaccessibleTarget")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#TypeMustNotHaveKindOfNone")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#TemporalTypeReferencePrecisionOutOfRange")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StructuredTypePropertiesDeclaringTypeMustBeCorrect")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StructuredTypeInaccessibleBaseType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StructuralPropertyInvalidPropertyType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StringTypeReferenceStringMaxLengthNegative")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#SchemaElementNamespaceMustNotBeEmptyOrWhiteSpace")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#SchemaElementNamespaceIsNotAllowed")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#RecordExpressionPropertiesMatchType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#PropertyMustNotHaveKindOfNone")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#PrimitiveTypeMustNotHaveKindOfNone")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyTypeMismatchRelationshipConstraint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyEntityMustNotIndirectlyContainItself")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyDuplicateDependentProperty")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyDependentEndMultiplicity")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NamedElementNameIsTooLong")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#ModelDuplicateEntityContainerName")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#ImmediateValueAnnotationElementAnnotationHasNameAndNamespace")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationImportEntityTypeDoesNotMatchEntitySet")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationImportCannotImportBoundOperation")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationParameterNameAlreadyDefinedDuplicate")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EnumMustHaveIntegerUnderlyingType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeKeyPropertyMustBelongToEntity")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeInvalidKeyNullablePart")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeDuplicatePropertyNameSpecifiedInEntityKey")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyMappingMustPointToValidTargetForProperty")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationSourceInaccessibleEntityType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityContainerElementMustNotHaveKindOfNone")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#ElementDirectValueAnnotationFullNameMustBeUnique")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#DecimalTypeReferenceScaleOutOfRange")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#FunctionMustHaveReturnType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#FunctionWithUrlEscapeFunctionMustBeBound")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#FunctionWithUrlEscapeFunctionMustHaveOneStringParameter")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#CollectionExpressionAllElementsCorrectType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#BinaryTypeReferenceBinaryMaxLengthNegative")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationEntitySetPathMustBeValid")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#UnBoundFunctionOverloadsMustHaveIdenticalReturnTypes")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#ModelBoundFunctionOverloadsMustHaveSameReturnType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationReturnTypeEntityTypeMustBeValid")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntitySetTypeMustBeCollectionOfEntityType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#SingletonTypeMustBeEntityType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyPartnerPathShouldBeResolvable")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyBindingPathMustBeResolvable")] +#endregion + +// EdmCoreModel instance is immutable. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.EdmCoreModel.#Instance")] + +// By design and already public APIs thus cannot be changed. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Date", Scope = "type", Target = "Microsoft.OData.Edm.Date")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Function", Scope = "member", Target = "Microsoft.OData.Edm.EdmEntityContainer.#AddFunctionImport(Microsoft.OData.Edm.IEdmFunction)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Function", Scope = "member", Target = "Microsoft.OData.Edm.EdmEntityContainer.#AddFunctionImport(System.String,Microsoft.OData.Edm.IEdmFunction)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Function", Scope = "member", Target = "Microsoft.OData.Edm.EdmEntityContainer.#AddFunctionImport(System.String,Microsoft.OData.Edm.IEdmFunction,Microsoft.OData.Edm.IEdmExpression)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Function", Scope = "member", Target = "Microsoft.OData.Edm.EdmEntityContainer.#AddFunctionImport(System.String,Microsoft.OData.Edm.IEdmFunction,Microsoft.OData.Edm.IEdmExpression,System.Boolean)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Function", Scope = "member", Target = "Microsoft.OData.Edm.IEdmFunctionImport.#Function")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Alias", Scope = "member", Target = "Microsoft.OData.Edm.IEdmInclude.#Alias")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Namespace", Scope = "member", Target = "Microsoft.OData.Edm.IEdmInclude.#Namespace")] + +// By design and already public APIs thus cannot be changed. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Scope = "member", Target = "Microsoft.OData.Edm.EdmEnumType.#.ctor(System.String,System.String,Microsoft.OData.Edm.IEdmPrimitiveType,System.Boolean)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Scope = "member", Target = "Microsoft.OData.Edm.EdmEnumType.#.ctor(System.String,System.String,System.Boolean)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Scope = "member", Target = "Microsoft.OData.Edm.EdmEnumType.#.ctor(System.String,System.String,Microsoft.OData.Edm.EdmPrimitiveTypeKind,System.Boolean)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags", Scope = "member", Target = "Microsoft.OData.Edm.IEdmEnumType.#IsFlags")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Scope = "member", Target = "Microsoft.OData.Edm.TimeOfDay.#op_Implicit(System.TimeSpan):Microsoft.OData.Edm.TimeOfDay")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmValue.#Microsoft.OData.Edm.Vocabularies.IEdmDelayedValue.Value")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsEnumMemberExpression.#bindingContext")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsFunctionImport.#csdlSchema")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.EnumHelper.#ToStringLiteral(Microsoft.OData.Edm.IEdmEnumTypeReference,System.Int64)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#SetChangeTrackingAnnotation(Microsoft.OData.Edm.EdmModel,Microsoft.OData.Edm.IEdmEntityContainer,System.Boolean)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#SetChangeTrackingAnnotation(Microsoft.OData.Edm.EdmModel,Microsoft.OData.Edm.IEdmEntitySet,System.Boolean,System.Collections.Generic.IEnumerable`1,System.Collections.Generic.IEnumerable`1)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#IsActionImport(Microsoft.OData.Edm.IEdmOperationImport)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#IsFunctionImport(Microsoft.OData.Edm.IEdmOperationImport)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#IsAction(Microsoft.OData.Edm.IEdmOperation)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.OData.Edm.ExtensionMethods.#IsFunction(Microsoft.OData.Edm.IEdmOperation)")] + +// Already public APIs thus cannot be changed. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.TryCreateObjectInstance.#Invoke(Microsoft.OData.Edm.Vocabularies.IEdmStructuredValue,System.Type,Microsoft.OData.Edm.Vocabularies.EdmToClrConverter,System.Object&,System.Boolean&)")] + +// By design. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Scope = "member", Target = "Microsoft.OData.Edm.EdmNavigationSource.#FindNavigationTarget(Microsoft.OData.Edm.IEdmNavigationProperty)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.V1.CapabilitiesVocabularyModel.#.cctor()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyModel.#.cctor()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.Community.V1.AlternateKeysVocabularyModel.#.cctor()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.Community.V1.AuthorizationVocabularyModel.#.cctor()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.V1.ValidationVocabularyModel.#.cctor()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.OData.Edm.EdmCoreModel.#FindOperationImportsByNameNonBindingParameterType(System.String,System.Collections.Generic.IEnumerable`1)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.OData.Edm.Csdl.Parsing.Common.EdmXmlDocumentParser`1.#GetOptionalAttribute(Microsoft.OData.Edm.Csdl.Parsing.Common.XmlElementInfo,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsVocabularyAnnotation.#FindParameterizedOperationImport(System.String,System.Func`2>,System.Func`2,Microsoft.OData.Edm.IEdmOperationImport>)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsModel.#WrapExpression(Microsoft.OData.Edm.Csdl.Parsing.Ast.CsdlExpressionBase,Microsoft.OData.Edm.IEdmEntityType,Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsSchema)", Justification = "Castings are done in different cases of the switch statement.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmExpressionEvaluator.#MatchesType(Microsoft.OData.Edm.IEdmTypeReference,Microsoft.OData.Edm.Vocabularies.IEdmValue,System.Boolean)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.Edm.Csdl.Serialization.EdmModelCsdlSchemaWriter.#WriteInlineExpression(Microsoft.OData.Edm.IEdmExpression)", Justification = "Castings are done in different cases of the switch statement.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.Edm.EdmModelVisitor.#VisitExpression(Microsoft.OData.Edm.IEdmExpression)", Justification = "Castings are done in different cases of the switch statement.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmExpressionEvaluator.#Eval(Microsoft.OData.Edm.IEdmExpression,Microsoft.OData.Edm.Vocabularies.IEdmStructuredValue)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Scope = "member", Target = "Microsoft.OData.Edm.Vocabularies.EdmExpressionEvaluator.#Eval(Microsoft.OData.Edm.IEdmExpression,Microsoft.OData.Edm.Vocabularies.IEdmStructuredValue)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1714:FlagsEnumsShouldHavePluralNames", Scope = "type", Target = "Microsoft.OData.Edm.EdmTypeKind")] + +// Unrecognized word: Multi +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeographyMultiPoint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeometryMultiLineString")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeographyMultiPolygon")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeometryMultiPolygon")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeometryMultiPoint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.OData.Edm.EdmPrimitiveTypeKind.#GeographyMultiLineString")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Edm.EdmCoreModel.#GetUntypedType()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.OData.Edm.EdmCoreModel.#GetUntyped()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#TypeDefinitionUnderlyingTypeCannotBeEdmPrimitiveType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#StructuredTypeBaseTypeCannotBeAbstractType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#PropertyTypeCannotBeCollectionOfAbstractType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#OperationReturnTypeCannotBeCollectionOfAbstractType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeKeyTypeCannotBeEdmPrimitiveType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntitySetTypeCannotBeEdmEntityType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#SingletonTypeCannotBeEdmEntityType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EnumUnderlyingTypeCannotBeEdmPrimitiveType")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationSourceDeclaringTypeCannotHavePathTypeProperty")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#NavigationPropertyTypeCannotHavePathTypeProperty")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.OData.Edm.Validation.ValidationRules.#EntityTypeBoundEscapeFunctionMustBeUnique")] + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/HashSetInternal.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/HashSetInternal.cs new file mode 100644 index 0000000..5f3e158 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/HashSetInternal.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + internal class HashSetInternal : IEnumerable + { + private readonly Dictionary wrappedDictionary; + + public HashSetInternal() + { + this.wrappedDictionary = new Dictionary(); + } + + public bool Add(T thingToAdd) + { + if (this.wrappedDictionary.ContainsKey(thingToAdd)) + { + return false; + } + + this.wrappedDictionary[thingToAdd] = null; + return true; + } + + public bool Contains(T item) + { + return this.wrappedDictionary.ContainsKey(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return (IEnumerator)this.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return this.wrappedDictionary.Keys.GetEnumerator(); + } + + public void Remove(T item) + { + this.wrappedDictionary.Remove(item); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependencyTrigger.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependencyTrigger.cs new file mode 100644 index 0000000..ac0558d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependencyTrigger.cs @@ -0,0 +1,16 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Interface describing anything that can be depended upon in tracking semantic changes in an EDM model. + /// + internal interface IDependencyTrigger + { + HashSetInternal Dependents { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependent.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependent.cs new file mode 100644 index 0000000..17e4671 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IDependent.cs @@ -0,0 +1,16 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Interface describing anything that can be dependent on a dependency trigger in tracking semantic changes in an EDM model. + /// + internal interface IDependent : IFlushCaches + { + HashSetInternal DependsOn { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IFlushCaches.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IFlushCaches.cs new file mode 100644 index 0000000..726da2f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/IFlushCaches.cs @@ -0,0 +1,16 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Interface describing anything that can have cached data that might need flushing. + /// + internal interface IFlushCaches + { + void FlushCaches(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Memoizer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Memoizer.cs new file mode 100644 index 0000000..5edb7a5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Memoizer.cs @@ -0,0 +1,167 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace Microsoft.OData.Edm +{ + /// + /// Remembers the result of evaluating an expensive function so that subsequent + /// evaluations are faster. Thread-safe. + /// + /// Type of the argument to the function. + /// Type of the function result. + internal sealed class Memoizer + { + private readonly Func function; + private readonly Dictionary resultCache; + private readonly ReaderWriterLockSlim slimLock; + + /// + /// Constructs the memoizer. + /// + /// Required. Function whose values are being cached. + /// Optional. Comparer used to determine if two functions arguments are the same. + internal Memoizer(Func function, IEqualityComparer argComparer) + { + Debug.Assert(function != null, "function != null"); + + this.function = function; + this.resultCache = new Dictionary(argComparer); + + this.slimLock = new ReaderWriterLockSlim(); + } + + /// + /// Evaluates the wrapped function for the given argument. If the function has already + /// been evaluated for the given argument, returns cached value. Otherwise, the value + /// is computed and returned. + /// + /// Function argument. + /// Function result. + internal TResult Evaluate(TArg arg) + { + Result result; + bool hasResult; + + // check to see if a result has already been computed + this.slimLock.EnterReadLock(); + + try + { + hasResult = this.resultCache.TryGetValue(arg, out result); + } + finally + { + this.slimLock.ExitReadLock(); + } + + if (!hasResult) + { + // compute the new value + this.slimLock.EnterWriteLock(); + + try + { + // see if the value has been computed in the interim + if (!this.resultCache.TryGetValue(arg, out result)) + { + result = new Result(() => this.function(arg)); + this.resultCache.Add(arg, result); + } + } + finally + { + this.slimLock.ExitWriteLock(); + } + } + + // note: you need to release the global cache lock before (potentially) acquiring + // a result lock in result.GetValue() + return result.GetValue(); + } + + /// + /// Encapsulates a 'deferred' result. The result is constructed with a delegate (must not + /// be null) and when the user requests a value the delegate is invoked and stored. + /// + private class Result + { + private TResult value; + private Func createValueDelegate; + + internal Result(Func createValueDelegate) + { + Debug.Assert(null != createValueDelegate, "delegate must be given"); + this.createValueDelegate = createValueDelegate; + } + + internal TResult GetValue() + { + if (null == this.createValueDelegate) + { + // if the delegate has been cleared, it means we have already computed the value + return this.value; + } + + // lock the entry while computing the value so that two threads + // don't simultaneously do the work + lock (this) + { + if (null == this.createValueDelegate) + { + // between our initial check and our acquisition of the lock, some other + // thread may have computed the value + return this.value; + } + + this.value = this.createValueDelegate(); + + // ensure createValueDelegate (and its closure) is garbage collected, and set to null + // to indicate that the value has been computed + this.createValueDelegate = null; + return this.value; + } + } + } + +#if !ORCAS + /// Read-writer lock, implemented over a Monitor. + private sealed class ReaderWriterLockSlim + { + /// Single object on which to lock. + private object readerWriterLock = new object(); + + /// Enters a reader lock. Writers will also be blocked. + internal void EnterReadLock() + { + Monitor.Enter(this.readerWriterLock); + } + + /// Enters a writer lock. Readers will also be blocked. + internal void EnterWriteLock() + { + Monitor.Enter(this.readerWriterLock); + } + + /// Exits a reader lock. + internal void ExitReadLock() + { + Monitor.Exit(this.readerWriterLock); + } + + /// Exits a writer lock. + internal void ExitWriteLock() + { + Monitor.Exit(this.readerWriterLock); + } + } +#endif + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.NetStandard.VS2017.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.NetStandard.VS2017.csproj new file mode 100644 index 0000000..aa71388 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.NetStandard.VS2017.csproj @@ -0,0 +1,145 @@ + + + + Microsoft.OData.Edm + Microsoft.OData.Edm + netstandard1.1 + 1.6.0 + + false + ..\..\..\tools\StrongNamePublicKeys\35MSSharedLib1024.snk + true + true + false + True + True + $(AssemblyName).xml + $(DefineConstants);PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE;SUPPRESS_SECURITY_RULES + true + true + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + $(MSBuildThisFileDirectory)Microsoft.OData.Edm.StyleCop + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft.OData.Edm + true + true + internal + true + Microsoft.OData.Edm.EntityRes + false + true + + + + + + true + + + false + + + false + + + + + + + TextTemplatingFileGenerator + Microsoft.OData.Edm.cs + + + True + True + Microsoft.OData.Edm.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Edm.cs + + + True + True + Parameterized.Microsoft.OData.Edm.tt + true + + + + + + CapabilitiesVocabularies.xml + Designer + + + CoreVocabularies.xml + Designer + + + AlternateKeysVocabularies.xml + Designer + + + CommunityVocabularies.xml + Designer + + + AuthorizationVocabularies.xml + Designer + + + ValidationVocabularies.xml + Designer + + + + + + + false + + + false + + + $(SuiteBinPath) + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.StyleCop b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.StyleCop new file mode 100644 index 0000000..8c049ff --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.StyleCop @@ -0,0 +1,312 @@ + + + NoMerge + + + + + False + + TemporaryGeneratedFile_.*\.cs$ + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + True + True + + + + + + + False + + + + + False + + + + + + id + in + is + if + on + to + + + + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + + + + False + + + + + + + + + + + + + False + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + False + + + + + + + + + False + + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs new file mode 100644 index 0000000..fd984d3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs @@ -0,0 +1,433 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm { + using System; + using System.Globalization; + using System.Reflection; + using System.Resources; +#if !PORTABLELIB + using System.Security.Permissions; +#endif + using System.Text; + using System.Threading; + + using System.ComponentModel; +#if !PORTABLELIB + [AttributeUsage(AttributeTargets.All)] + internal sealed class EntityResDescriptionAttribute : DescriptionAttribute { + + private bool replaced = false; + + /// + /// Constructs a new sys description. + /// + /// + /// description text. + /// + public EntityResDescriptionAttribute(string description) : base(description) { + } + + /// + /// Retrieves the description text. + /// + /// + /// description + /// + public override string Description { + get { + if (!replaced) { + replaced = true; + DescriptionValue = EntityRes.GetString(base.Description); + } + return base.Description; + } + } + } + + [AttributeUsage(AttributeTargets.All)] + internal sealed class EntityResCategoryAttribute : CategoryAttribute { + + public EntityResCategoryAttribute(string category) : base(category) { + } + + protected override string GetLocalizedString(string value) { + return EntityRes.GetString(value); + } + } +#endif + + /// + /// AutoGenerated resource class. Usage: + /// + /// string s = EntityRes.GetString(EntityRes.MyIdenfitier); + /// + internal sealed class EntityRes { + internal const string EdmPrimitive_UnexpectedKind = "EdmPrimitive_UnexpectedKind"; + internal const string EdmPath_UnexpectedKind = "EdmPath_UnexpectedKind"; + internal const string Annotations_TypeMismatch = "Annotations_TypeMismatch"; + internal const string Constructable_VocabularyAnnotationMustHaveTarget = "Constructable_VocabularyAnnotationMustHaveTarget"; + internal const string Constructable_EntityTypeOrCollectionOfEntityTypeExpected = "Constructable_EntityTypeOrCollectionOfEntityTypeExpected"; + internal const string Constructable_TargetMustBeStock = "Constructable_TargetMustBeStock"; + internal const string TypeSemantics_CouldNotConvertTypeReference = "TypeSemantics_CouldNotConvertTypeReference"; + internal const string EdmModel_CannotUseElementWithTypeNone = "EdmModel_CannotUseElementWithTypeNone"; + internal const string EdmModel_CannotAddMoreThanOneEntityContainerToOneEdmModel = "EdmModel_CannotAddMoreThanOneEntityContainerToOneEdmModel"; + internal const string EdmEntityContainer_CannotUseElementWithTypeNone = "EdmEntityContainer_CannotUseElementWithTypeNone"; + internal const string ValueWriter_NonSerializableValue = "ValueWriter_NonSerializableValue"; + internal const string ValueHasAlreadyBeenSet = "ValueHasAlreadyBeenSet"; + internal const string PathSegmentMustNotContainSlash = "PathSegmentMustNotContainSlash"; + internal const string Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType = "Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType"; + internal const string EdmType_UnexpectedEdmType = "EdmType_UnexpectedEdmType"; + internal const string NavigationPropertyBinding_PathIsNotValid = "NavigationPropertyBinding_PathIsNotValid"; + internal const string Edm_Evaluator_NoTermTypeAnnotationOnType = "Edm_Evaluator_NoTermTypeAnnotationOnType"; + internal const string Edm_Evaluator_NoValueAnnotationOnType = "Edm_Evaluator_NoValueAnnotationOnType"; + internal const string Edm_Evaluator_NoValueAnnotationOnElement = "Edm_Evaluator_NoValueAnnotationOnElement"; + internal const string Edm_Evaluator_UnrecognizedExpressionKind = "Edm_Evaluator_UnrecognizedExpressionKind"; + internal const string Edm_Evaluator_UnboundFunction = "Edm_Evaluator_UnboundFunction"; + internal const string Edm_Evaluator_UnboundPath = "Edm_Evaluator_UnboundPath"; + internal const string Edm_Evaluator_NoContextPath = "Edm_Evaluator_NoContextPath"; + internal const string Edm_Evaluator_FailedTypeAssertion = "Edm_Evaluator_FailedTypeAssertion"; + internal const string Edm_Evaluator_TypeCastNeedsEdmModel = "Edm_Evaluator_TypeCastNeedsEdmModel"; + internal const string EdmModel_Validator_Semantic_SystemNamespaceEncountered = "EdmModel_Validator_Semantic_SystemNamespaceEncountered"; + internal const string EdmModel_Validator_Semantic_NavigationSourceTypeHasNoKeys = "EdmModel_Validator_Semantic_NavigationSourceTypeHasNoKeys"; + internal const string EdmModel_Validator_Semantic_DuplicateEndName = "EdmModel_Validator_Semantic_DuplicateEndName"; + internal const string EdmModel_Validator_Semantic_DuplicatePropertyNameSpecifiedInEntityKey = "EdmModel_Validator_Semantic_DuplicatePropertyNameSpecifiedInEntityKey"; + internal const string EdmModel_Validator_Semantic_InvalidComplexTypeAbstract = "EdmModel_Validator_Semantic_InvalidComplexTypeAbstract"; + internal const string EdmModel_Validator_Semantic_InvalidComplexTypePolymorphic = "EdmModel_Validator_Semantic_InvalidComplexTypePolymorphic"; + internal const string EdmModel_Validator_Semantic_InvalidKeyNullablePart = "EdmModel_Validator_Semantic_InvalidKeyNullablePart"; + internal const string EdmModel_Validator_Semantic_EntityKeyMustBeScalar = "EdmModel_Validator_Semantic_EntityKeyMustBeScalar"; + internal const string EdmModel_Validator_Semantic_EntityComposableBoundEscapeFunctionMustBeLessOne = "EdmModel_Validator_Semantic_EntityComposableBoundEscapeFunctionMustBeLessOne"; + internal const string EdmModel_Validator_Semantic_EntityNoncomposableBoundEscapeFunctionMustBeLessOne = "EdmModel_Validator_Semantic_EntityNoncomposableBoundEscapeFunctionMustBeLessOne"; + internal const string EdmModel_Validator_Semantic_InvalidKeyKeyDefinedInBaseClass = "EdmModel_Validator_Semantic_InvalidKeyKeyDefinedInBaseClass"; + internal const string EdmModel_Validator_Semantic_KeyMissingOnEntityType = "EdmModel_Validator_Semantic_KeyMissingOnEntityType"; + internal const string EdmModel_Validator_Semantic_BadNavigationPropertyUndefinedRole = "EdmModel_Validator_Semantic_BadNavigationPropertyUndefinedRole"; + internal const string EdmModel_Validator_Semantic_BadNavigationPropertyRolesCannotBeTheSame = "EdmModel_Validator_Semantic_BadNavigationPropertyRolesCannotBeTheSame"; + internal const string EdmModel_Validator_Semantic_BadNavigationPropertyCouldNotDetermineType = "EdmModel_Validator_Semantic_BadNavigationPropertyCouldNotDetermineType"; + internal const string EdmModel_Validator_Semantic_InvalidOperationMultipleEndsInAssociation = "EdmModel_Validator_Semantic_InvalidOperationMultipleEndsInAssociation"; + internal const string EdmModel_Validator_Semantic_EndWithManyMultiplicityCannotHaveOperationsSpecified = "EdmModel_Validator_Semantic_EndWithManyMultiplicityCannotHaveOperationsSpecified"; + internal const string EdmModel_Validator_Semantic_EndNameAlreadyDefinedDuplicate = "EdmModel_Validator_Semantic_EndNameAlreadyDefinedDuplicate"; + internal const string EdmModel_Validator_Semantic_SameRoleReferredInReferentialConstraint = "EdmModel_Validator_Semantic_SameRoleReferredInReferentialConstraint"; + internal const string EdmModel_Validator_Semantic_NavigationPropertyPrincipalEndMultiplicityUpperBoundMustBeOne = "EdmModel_Validator_Semantic_NavigationPropertyPrincipalEndMultiplicityUpperBoundMustBeOne"; + internal const string EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNonnullable = "EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNonnullable"; + internal const string EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNullable = "EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNullable"; + internal const string EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeZeroOneOrOne = "EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeZeroOneOrOne"; + internal const string EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeMany = "EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeMany"; + internal const string EdmModel_Validator_Semantic_MismatchNumberOfPropertiesinRelationshipConstraint = "EdmModel_Validator_Semantic_MismatchNumberOfPropertiesinRelationshipConstraint"; + internal const string EdmModel_Validator_Semantic_TypeMismatchRelationshipConstraint = "EdmModel_Validator_Semantic_TypeMismatchRelationshipConstraint"; + internal const string EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintDependentEnd = "EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintDependentEnd"; + internal const string EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintPrimaryEnd = "EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintPrimaryEnd"; + internal const string EdmModel_Validator_Semantic_InvalidPropertyType = "EdmModel_Validator_Semantic_InvalidPropertyType"; + internal const string EdmModel_Validator_Semantic_BoundOperationMustHaveParameters = "EdmModel_Validator_Semantic_BoundOperationMustHaveParameters"; + internal const string EdmModel_Validator_Semantic_RequiredParametersMustPrecedeOptional = "EdmModel_Validator_Semantic_RequiredParametersMustPrecedeOptional"; + internal const string EdmModel_Validator_Semantic_OperationWithUnsupportedReturnType = "EdmModel_Validator_Semantic_OperationWithUnsupportedReturnType"; + internal const string EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet = "EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet"; + internal const string EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet2 = "EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet2"; + internal const string EdmModel_Validator_Semantic_OperationImportEntitySetExpressionKindIsInvalid = "EdmModel_Validator_Semantic_OperationImportEntitySetExpressionKindIsInvalid"; + internal const string EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid = "EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid"; + internal const string EdmModel_Validator_Semantic_OperationImportSpecifiesEntitySetButNotEntityType = "EdmModel_Validator_Semantic_OperationImportSpecifiesEntitySetButNotEntityType"; + internal const string EdmModel_Validator_Semantic_OperationImportCannotImportBoundOperation = "EdmModel_Validator_Semantic_OperationImportCannotImportBoundOperation"; + internal const string EdmModel_Validator_Semantic_FunctionImportWithParameterShouldNotBeIncludedInServiceDocument = "EdmModel_Validator_Semantic_FunctionImportWithParameterShouldNotBeIncludedInServiceDocument"; + internal const string EdmModel_Validator_Semantic_FunctionMustHaveReturnType = "EdmModel_Validator_Semantic_FunctionMustHaveReturnType"; + internal const string EdmModel_Validator_Semantic_UrlEscapeFunctionMustBoundFunction = "EdmModel_Validator_Semantic_UrlEscapeFunctionMustBoundFunction"; + internal const string EdmModel_Validator_Semantic_UrlEscapeFunctionMustHaveOneStringParameter = "EdmModel_Validator_Semantic_UrlEscapeFunctionMustHaveOneStringParameter"; + internal const string EdmModel_Validator_Semantic_ParameterNameAlreadyDefinedDuplicate = "EdmModel_Validator_Semantic_ParameterNameAlreadyDefinedDuplicate"; + internal const string EdmModel_Validator_Semantic_DuplicateEntityContainerMemberName = "EdmModel_Validator_Semantic_DuplicateEntityContainerMemberName"; + internal const string EdmModel_Validator_Semantic_UnboundFunctionOverloadHasIncorrectReturnType = "EdmModel_Validator_Semantic_UnboundFunctionOverloadHasIncorrectReturnType"; + internal const string EdmModel_Validator_Semantic_OperationCannotHaveEntitySetPathWithUnBoundOperation = "EdmModel_Validator_Semantic_OperationCannotHaveEntitySetPathWithUnBoundOperation"; + internal const string EdmModel_Validator_Semantic_InvalidEntitySetPathMissingBindingParameterName = "EdmModel_Validator_Semantic_InvalidEntitySetPathMissingBindingParameterName"; + internal const string EdmModel_Validator_Semantic_InvalidEntitySetPathWithFirstPathParameterNotMatchingFirstParameterName = "EdmModel_Validator_Semantic_InvalidEntitySetPathWithFirstPathParameterNotMatchingFirstParameterName"; + internal const string EdmModel_Validator_Semantic_InvalidEntitySetPathTypeCastSegmentMustBeEntityType = "EdmModel_Validator_Semantic_InvalidEntitySetPathTypeCastSegmentMustBeEntityType"; + internal const string EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownNavigationProperty = "EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownNavigationProperty"; + internal const string EdmModel_Validator_Semantic_InvalidEntitySetPathInvalidTypeCastSegment = "EdmModel_Validator_Semantic_InvalidEntitySetPathInvalidTypeCastSegment"; + internal const string EdmModel_Validator_Semantic_InvalidEntitySetPathWithNonEntityBindingParameter = "EdmModel_Validator_Semantic_InvalidEntitySetPathWithNonEntityBindingParameter"; + internal const string EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownTypeCastSegment = "EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownTypeCastSegment"; + internal const string EdmModel_Validator_Semantic_OperationWithEntitySetPathReturnTypeInvalid = "EdmModel_Validator_Semantic_OperationWithEntitySetPathReturnTypeInvalid"; + internal const string EdmModel_Validator_Semantic_OperationWithEntitySetPathAndReturnTypeTypeNotAssignable = "EdmModel_Validator_Semantic_OperationWithEntitySetPathAndReturnTypeTypeNotAssignable"; + internal const string EdmModel_Validator_Semantic_OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType = "EdmModel_Validator_Semantic_OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType"; + internal const string EdmModel_Validator_Semantic_SchemaElementNameAlreadyDefined = "EdmModel_Validator_Semantic_SchemaElementNameAlreadyDefined"; + internal const string EdmModel_Validator_Semantic_InvalidMemberNameMatchesTypeName = "EdmModel_Validator_Semantic_InvalidMemberNameMatchesTypeName"; + internal const string EdmModel_Validator_Semantic_PropertyNameAlreadyDefined = "EdmModel_Validator_Semantic_PropertyNameAlreadyDefined"; + internal const string EdmModel_Validator_Semantic_BaseTypeMustHaveSameTypeKind = "EdmModel_Validator_Semantic_BaseTypeMustHaveSameTypeKind"; + internal const string EdmModel_Validator_Semantic_BaseTypeOfOpenTypeMustBeOpen = "EdmModel_Validator_Semantic_BaseTypeOfOpenTypeMustBeOpen"; + internal const string EdmModel_Validator_Semantic_KeyPropertyMustBelongToEntity = "EdmModel_Validator_Semantic_KeyPropertyMustBelongToEntity"; + internal const string EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsTypeOfKey = "EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsTypeOfKey"; + internal const string EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsUnderlyingType = "EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsUnderlyingType"; + internal const string EdmModel_Validator_Semantic_DependentPropertiesMustBelongToDependentEntity = "EdmModel_Validator_Semantic_DependentPropertiesMustBelongToDependentEntity"; + internal const string EdmModel_Validator_Semantic_DeclaringTypeMustBeCorrect = "EdmModel_Validator_Semantic_DeclaringTypeMustBeCorrect"; + internal const string EdmModel_Validator_Semantic_InaccessibleType = "EdmModel_Validator_Semantic_InaccessibleType"; + internal const string EdmModel_Validator_Semantic_AmbiguousType = "EdmModel_Validator_Semantic_AmbiguousType"; + internal const string EdmModel_Validator_Semantic_InvalidNavigationPropertyType = "EdmModel_Validator_Semantic_InvalidNavigationPropertyType"; + internal const string EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional = "EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional"; + internal const string EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne = "EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne"; + internal const string EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne = "EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne"; + internal const string EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties = "EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties"; + internal const string EdmModel_Validator_Semantic_DuplicateDependentProperty = "EdmModel_Validator_Semantic_DuplicateDependentProperty"; + internal const string EdmModel_Validator_Semantic_ScaleOutOfRange = "EdmModel_Validator_Semantic_ScaleOutOfRange"; + internal const string EdmModel_Validator_Semantic_PrecisionOutOfRange = "EdmModel_Validator_Semantic_PrecisionOutOfRange"; + internal const string EdmModel_Validator_Semantic_StringMaxLengthOutOfRange = "EdmModel_Validator_Semantic_StringMaxLengthOutOfRange"; + internal const string EdmModel_Validator_Semantic_MaxLengthOutOfRange = "EdmModel_Validator_Semantic_MaxLengthOutOfRange"; + internal const string EdmModel_Validator_Semantic_EnumMemberValueOutOfRange = "EdmModel_Validator_Semantic_EnumMemberValueOutOfRange"; + internal const string EdmModel_Validator_Semantic_EnumMemberNameAlreadyDefined = "EdmModel_Validator_Semantic_EnumMemberNameAlreadyDefined"; + internal const string EdmModel_Validator_Semantic_OpenTypesSupportedForEntityTypesOnly = "EdmModel_Validator_Semantic_OpenTypesSupportedForEntityTypesOnly"; + internal const string EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull = "EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull"; + internal const string EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm = "EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm"; + internal const string EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml = "EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml"; + internal const string EdmModel_Validator_Semantic_InvalidElementAnnotationNotIEdmStringValue = "EdmModel_Validator_Semantic_InvalidElementAnnotationNotIEdmStringValue"; + internal const string EdmModel_Validator_Semantic_InvalidElementAnnotationNullNamespaceOrName = "EdmModel_Validator_Semantic_InvalidElementAnnotationNullNamespaceOrName"; + internal const string EdmModel_Validator_Semantic_CannotAssertNullableTypeAsNonNullableType = "EdmModel_Validator_Semantic_CannotAssertNullableTypeAsNonNullableType"; + internal const string EdmModel_Validator_Semantic_ExpressionPrimitiveKindCannotPromoteToAssertedType = "EdmModel_Validator_Semantic_ExpressionPrimitiveKindCannotPromoteToAssertedType"; + internal const string EdmModel_Validator_Semantic_NullCannotBeAssertedToBeANonNullableType = "EdmModel_Validator_Semantic_NullCannotBeAssertedToBeANonNullableType"; + internal const string EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType = "EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType"; + internal const string EdmModel_Validator_Semantic_CollectionExpressionNotValidForNonCollectionType = "EdmModel_Validator_Semantic_CollectionExpressionNotValidForNonCollectionType"; + internal const string EdmModel_Validator_Semantic_PrimitiveConstantExpressionNotValidForNonPrimitiveType = "EdmModel_Validator_Semantic_PrimitiveConstantExpressionNotValidForNonPrimitiveType"; + internal const string EdmModel_Validator_Semantic_RecordExpressionNotValidForNonStructuredType = "EdmModel_Validator_Semantic_RecordExpressionNotValidForNonStructuredType"; + internal const string EdmModel_Validator_Semantic_RecordExpressionMissingProperty = "EdmModel_Validator_Semantic_RecordExpressionMissingProperty"; + internal const string EdmModel_Validator_Semantic_RecordExpressionHasExtraProperties = "EdmModel_Validator_Semantic_RecordExpressionHasExtraProperties"; + internal const string EdmModel_Validator_Semantic_DuplicateAnnotation = "EdmModel_Validator_Semantic_DuplicateAnnotation"; + internal const string EdmModel_Validator_Semantic_IncorrectNumberOfArguments = "EdmModel_Validator_Semantic_IncorrectNumberOfArguments"; + internal const string EdmModel_Validator_Semantic_DuplicateEntityContainerName = "EdmModel_Validator_Semantic_DuplicateEntityContainerName"; + internal const string EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType = "EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType"; + internal const string EdmModel_Validator_Semantic_ExpressionEnumKindNotValidForAssertedType = "EdmModel_Validator_Semantic_ExpressionEnumKindNotValidForAssertedType"; + internal const string EdmModel_Validator_Semantic_IntegerConstantValueOutOfRange = "EdmModel_Validator_Semantic_IntegerConstantValueOutOfRange"; + internal const string EdmModel_Validator_Semantic_StringConstantLengthOutOfRange = "EdmModel_Validator_Semantic_StringConstantLengthOutOfRange"; + internal const string EdmModel_Validator_Semantic_BinaryConstantLengthOutOfRange = "EdmModel_Validator_Semantic_BinaryConstantLengthOutOfRange"; + internal const string EdmModel_Validator_Semantic_TypeMustNotHaveKindOfNone = "EdmModel_Validator_Semantic_TypeMustNotHaveKindOfNone"; + internal const string EdmModel_Validator_Semantic_SchemaElementMustNotHaveKindOfNone = "EdmModel_Validator_Semantic_SchemaElementMustNotHaveKindOfNone"; + internal const string EdmModel_Validator_Semantic_PropertyMustNotHaveKindOfNone = "EdmModel_Validator_Semantic_PropertyMustNotHaveKindOfNone"; + internal const string EdmModel_Validator_Semantic_PrimitiveTypeMustNotHaveKindOfNone = "EdmModel_Validator_Semantic_PrimitiveTypeMustNotHaveKindOfNone"; + internal const string EdmModel_Validator_Semantic_EntityContainerElementMustNotHaveKindOfNone = "EdmModel_Validator_Semantic_EntityContainerElementMustNotHaveKindOfNone"; + internal const string EdmModel_Validator_Semantic_DuplicateNavigationPropertyMapping = "EdmModel_Validator_Semantic_DuplicateNavigationPropertyMapping"; + internal const string EdmModel_Validator_Semantic_NavigationMappingMustBeBidirectional = "EdmModel_Validator_Semantic_NavigationMappingMustBeBidirectional"; + internal const string EdmModel_Validator_Semantic_EntitySetCanOnlyBeContainedByASingleNavigationProperty = "EdmModel_Validator_Semantic_EntitySetCanOnlyBeContainedByASingleNavigationProperty"; + internal const string EdmModel_Validator_Semantic_TypeAnnotationMissingRequiredProperty = "EdmModel_Validator_Semantic_TypeAnnotationMissingRequiredProperty"; + internal const string EdmModel_Validator_Semantic_TypeAnnotationHasExtraProperties = "EdmModel_Validator_Semantic_TypeAnnotationHasExtraProperties"; + internal const string EdmModel_Validator_Semantic_EnumMustHaveIntegralUnderlyingType = "EdmModel_Validator_Semantic_EnumMustHaveIntegralUnderlyingType"; + internal const string EdmModel_Validator_Semantic_InaccessibleTerm = "EdmModel_Validator_Semantic_InaccessibleTerm"; + internal const string EdmModel_Validator_Semantic_InaccessibleTarget = "EdmModel_Validator_Semantic_InaccessibleTarget"; + internal const string EdmModel_Validator_Semantic_VocabularyAnnotationApplyToNotAllowedAnnotatable = "EdmModel_Validator_Semantic_VocabularyAnnotationApplyToNotAllowedAnnotatable"; + internal const string EdmModel_Validator_Semantic_ElementDirectValueAnnotationFullNameMustBeUnique = "EdmModel_Validator_Semantic_ElementDirectValueAnnotationFullNameMustBeUnique"; + internal const string EdmModel_Validator_Semantic_NoEntitySetsFoundForType = "EdmModel_Validator_Semantic_NoEntitySetsFoundForType"; + internal const string EdmModel_Validator_Semantic_CannotInferEntitySetWithMultipleSetsPerType = "EdmModel_Validator_Semantic_CannotInferEntitySetWithMultipleSetsPerType"; + internal const string EdmModel_Validator_Semantic_EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet = "EdmModel_Validator_Semantic_EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet"; + internal const string EdmModel_Validator_Semantic_NavigationPropertyEntityMustNotIndirectlyContainItself = "EdmModel_Validator_Semantic_NavigationPropertyEntityMustNotIndirectlyContainItself"; + internal const string EdmModel_Validator_Semantic_PathIsNotValidForTheGivenContext = "EdmModel_Validator_Semantic_PathIsNotValidForTheGivenContext"; + internal const string EdmModel_Validator_Semantic_NavigationPropertyMappingMustPointToValidTargetForProperty = "EdmModel_Validator_Semantic_NavigationPropertyMappingMustPointToValidTargetForProperty"; + internal const string EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterNames = "EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterNames"; + internal const string EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterTypes = "EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterTypes"; + internal const string EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterNames = "EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterNames"; + internal const string EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterTypes = "EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterTypes"; + internal const string EdmModel_Validator_Semantic_ModelDuplicateBoundActions = "EdmModel_Validator_Semantic_ModelDuplicateBoundActions"; + internal const string EdmModel_Validator_Semantic_ModelDuplicateUnBoundActions = "EdmModel_Validator_Semantic_ModelDuplicateUnBoundActions"; + internal const string EdmModel_Validator_Semantic_BoundFunctionOverloadsMustHaveSameReturnType = "EdmModel_Validator_Semantic_BoundFunctionOverloadsMustHaveSameReturnType"; + internal const string EdmModel_Validator_Semantic_EntitySetTypeMustBeCollectionOfEntityType = "EdmModel_Validator_Semantic_EntitySetTypeMustBeCollectionOfEntityType"; + internal const string EdmModel_Validator_Semantic_SingletonTypeMustBeEntityType = "EdmModel_Validator_Semantic_SingletonTypeMustBeEntityType"; + internal const string EdmModel_Validator_Semantic_NavigationPropertyOfCollectionTypeMustNotTargetToSingleton = "EdmModel_Validator_Semantic_NavigationPropertyOfCollectionTypeMustNotTargetToSingleton"; + internal const string EdmModel_Validator_Semantic_StructuredTypeBaseTypeCannotBeAbstractType = "EdmModel_Validator_Semantic_StructuredTypeBaseTypeCannotBeAbstractType"; + internal const string EdmModel_Validator_Semantic_PropertyTypeCannotBeCollectionOfAbstractType = "EdmModel_Validator_Semantic_PropertyTypeCannotBeCollectionOfAbstractType"; + internal const string EdmModel_Validator_Semantic_OperationReturnTypeCannotBeCollectionOfAbstractType = "EdmModel_Validator_Semantic_OperationReturnTypeCannotBeCollectionOfAbstractType"; + internal const string EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfSingleton = "EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfSingleton"; + internal const string EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfEntitySet = "EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfEntitySet"; + internal const string EdmModel_Validator_Semantic_DeclaringTypeOfNavigationSourceCannotHavePathProperty = "EdmModel_Validator_Semantic_DeclaringTypeOfNavigationSourceCannotHavePathProperty"; + internal const string EdmModel_Validator_Semantic_TypeOfNavigationPropertyCannotHavePathProperty = "EdmModel_Validator_Semantic_TypeOfNavigationPropertyCannotHavePathProperty"; + internal const string EdmModel_Validator_Syntactic_MissingName = "EdmModel_Validator_Syntactic_MissingName"; + internal const string EdmModel_Validator_Syntactic_EdmModel_NameIsTooLong = "EdmModel_Validator_Syntactic_EdmModel_NameIsTooLong"; + internal const string EdmModel_Validator_Syntactic_EdmModel_NameIsNotAllowed = "EdmModel_Validator_Syntactic_EdmModel_NameIsNotAllowed"; + internal const string EdmModel_Validator_Syntactic_MissingNamespaceName = "EdmModel_Validator_Syntactic_MissingNamespaceName"; + internal const string EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsTooLong = "EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsTooLong"; + internal const string EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsNotAllowed = "EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsNotAllowed"; + internal const string EdmModel_Validator_Syntactic_PropertyMustNotBeNull = "EdmModel_Validator_Syntactic_PropertyMustNotBeNull"; + internal const string EdmModel_Validator_Syntactic_EnumPropertyValueOutOfRange = "EdmModel_Validator_Syntactic_EnumPropertyValueOutOfRange"; + internal const string EdmModel_Validator_Syntactic_InterfaceKindValueMismatch = "EdmModel_Validator_Syntactic_InterfaceKindValueMismatch"; + internal const string EdmModel_Validator_Syntactic_TypeRefInterfaceTypeKindValueMismatch = "EdmModel_Validator_Syntactic_TypeRefInterfaceTypeKindValueMismatch"; + internal const string EdmModel_Validator_Syntactic_InterfaceKindValueUnexpected = "EdmModel_Validator_Syntactic_InterfaceKindValueUnexpected"; + internal const string EdmModel_Validator_Syntactic_EnumerableMustNotHaveNullElements = "EdmModel_Validator_Syntactic_EnumerableMustNotHaveNullElements"; + internal const string EdmModel_Validator_Syntactic_NavigationPartnerInvalid = "EdmModel_Validator_Syntactic_NavigationPartnerInvalid"; + internal const string EdmModel_Validator_Syntactic_InterfaceCriticalCycleInTypeHierarchy = "EdmModel_Validator_Syntactic_InterfaceCriticalCycleInTypeHierarchy"; + internal const string Serializer_SingleFileExpected = "Serializer_SingleFileExpected"; + internal const string Serializer_UnknownEdmVersion = "Serializer_UnknownEdmVersion"; + internal const string Serializer_UnknownEdmxVersion = "Serializer_UnknownEdmxVersion"; + internal const string Serializer_NonInlineOperationImportReturnType = "Serializer_NonInlineOperationImportReturnType"; + internal const string Serializer_ReferencedTypeMustHaveValidName = "Serializer_ReferencedTypeMustHaveValidName"; + internal const string Serializer_OutOfLineAnnotationTargetMustHaveValidName = "Serializer_OutOfLineAnnotationTargetMustHaveValidName"; + internal const string Serializer_NoSchemasProduced = "Serializer_NoSchemasProduced"; + internal const string XmlParser_EmptyFile = "XmlParser_EmptyFile"; + internal const string XmlParser_EmptySchemaTextReader = "XmlParser_EmptySchemaTextReader"; + internal const string XmlParser_MissingAttribute = "XmlParser_MissingAttribute"; + internal const string XmlParser_TextNotAllowed = "XmlParser_TextNotAllowed"; + internal const string XmlParser_UnexpectedAttribute = "XmlParser_UnexpectedAttribute"; + internal const string XmlParser_UnexpectedElement = "XmlParser_UnexpectedElement"; + internal const string XmlParser_UnusedElement = "XmlParser_UnusedElement"; + internal const string XmlParser_UnexpectedNodeType = "XmlParser_UnexpectedNodeType"; + internal const string XmlParser_UnexpectedRootElement = "XmlParser_UnexpectedRootElement"; + internal const string XmlParser_UnexpectedRootElementWrongNamespace = "XmlParser_UnexpectedRootElementWrongNamespace"; + internal const string XmlParser_UnexpectedRootElementNoNamespace = "XmlParser_UnexpectedRootElementNoNamespace"; + internal const string CsdlParser_InvalidEntitySetPathWithUnboundAction = "CsdlParser_InvalidEntitySetPathWithUnboundAction"; + internal const string CsdlParser_InvalidAlias = "CsdlParser_InvalidAlias"; + internal const string CsdlParser_InvalidDeleteAction = "CsdlParser_InvalidDeleteAction"; + internal const string CsdlParser_MissingTypeAttributeOrElement = "CsdlParser_MissingTypeAttributeOrElement"; + internal const string CsdlParser_InvalidEndRoleInRelationshipConstraint = "CsdlParser_InvalidEndRoleInRelationshipConstraint"; + internal const string CsdlParser_InvalidMultiplicity = "CsdlParser_InvalidMultiplicity"; + internal const string CsdlParser_ReferentialConstraintRequiresOneDependent = "CsdlParser_ReferentialConstraintRequiresOneDependent"; + internal const string CsdlParser_ReferentialConstraintRequiresOnePrincipal = "CsdlParser_ReferentialConstraintRequiresOnePrincipal"; + internal const string CsdlParser_InvalidIfExpressionIncorrectNumberOfOperands = "CsdlParser_InvalidIfExpressionIncorrectNumberOfOperands"; + internal const string CsdlParser_InvalidIsTypeExpressionIncorrectNumberOfOperands = "CsdlParser_InvalidIsTypeExpressionIncorrectNumberOfOperands"; + internal const string CsdlParser_InvalidCastExpressionIncorrectNumberOfOperands = "CsdlParser_InvalidCastExpressionIncorrectNumberOfOperands"; + internal const string CsdlParser_InvalidLabeledElementExpressionIncorrectNumberOfOperands = "CsdlParser_InvalidLabeledElementExpressionIncorrectNumberOfOperands"; + internal const string CsdlParser_InvalidTypeName = "CsdlParser_InvalidTypeName"; + internal const string CsdlParser_InvalidQualifiedName = "CsdlParser_InvalidQualifiedName"; + internal const string CsdlParser_NoReadersProvided = "CsdlParser_NoReadersProvided"; + internal const string CsdlParser_NullXmlReader = "CsdlParser_NullXmlReader"; + internal const string CsdlParser_InvalidEntitySetPath = "CsdlParser_InvalidEntitySetPath"; + internal const string CsdlParser_InvalidEnumMemberPath = "CsdlParser_InvalidEnumMemberPath"; + internal const string CsdlParser_CannotSpecifyNullableAttributeForNavigationPropertyWithCollectionType = "CsdlParser_CannotSpecifyNullableAttributeForNavigationPropertyWithCollectionType"; + internal const string CsdlParser_MetadataDocumentCannotHaveMoreThanOneEntityContainer = "CsdlParser_MetadataDocumentCannotHaveMoreThanOneEntityContainer"; + internal const string CsdlSemantics_ReferentialConstraintMismatch = "CsdlSemantics_ReferentialConstraintMismatch"; + internal const string CsdlSemantics_EnumMemberMustHaveValue = "CsdlSemantics_EnumMemberMustHaveValue"; + internal const string CsdlSemantics_ImpossibleAnnotationsTarget = "CsdlSemantics_ImpossibleAnnotationsTarget"; + internal const string CsdlSemantics_DuplicateAlias = "CsdlSemantics_DuplicateAlias"; + internal const string EdmxParser_EdmxVersionMismatch = "EdmxParser_EdmxVersionMismatch"; + internal const string EdmxParser_BodyElement = "EdmxParser_BodyElement"; + internal const string EdmxParser_InvalidReferenceIncorrectNumberOfIncludes = "EdmxParser_InvalidReferenceIncorrectNumberOfIncludes"; + internal const string EdmxParser_UnresolvedReferenceUriInEdmxReference = "EdmxParser_UnresolvedReferenceUriInEdmxReference"; + internal const string EdmParseException_ErrorsEncounteredInEdmx = "EdmParseException_ErrorsEncounteredInEdmx"; + internal const string ValueParser_InvalidBoolean = "ValueParser_InvalidBoolean"; + internal const string ValueParser_InvalidInteger = "ValueParser_InvalidInteger"; + internal const string ValueParser_InvalidLong = "ValueParser_InvalidLong"; + internal const string ValueParser_InvalidFloatingPoint = "ValueParser_InvalidFloatingPoint"; + internal const string ValueParser_InvalidMaxLength = "ValueParser_InvalidMaxLength"; + internal const string ValueParser_InvalidSrid = "ValueParser_InvalidSrid"; + internal const string ValueParser_InvalidScale = "ValueParser_InvalidScale"; + internal const string ValueParser_InvalidGuid = "ValueParser_InvalidGuid"; + internal const string ValueParser_InvalidDecimal = "ValueParser_InvalidDecimal"; + internal const string ValueParser_InvalidDateTimeOffset = "ValueParser_InvalidDateTimeOffset"; + internal const string ValueParser_InvalidDateTime = "ValueParser_InvalidDateTime"; + internal const string ValueParser_InvalidDate = "ValueParser_InvalidDate"; + internal const string ValueParser_InvalidDuration = "ValueParser_InvalidDuration"; + internal const string ValueParser_InvalidBinary = "ValueParser_InvalidBinary"; + internal const string ValueParser_InvalidTimeOfDay = "ValueParser_InvalidTimeOfDay"; + internal const string UnknownEnumVal_Multiplicity = "UnknownEnumVal_Multiplicity"; + internal const string UnknownEnumVal_SchemaElementKind = "UnknownEnumVal_SchemaElementKind"; + internal const string UnknownEnumVal_TypeKind = "UnknownEnumVal_TypeKind"; + internal const string UnknownEnumVal_PrimitiveKind = "UnknownEnumVal_PrimitiveKind"; + internal const string UnknownEnumVal_ContainerElementKind = "UnknownEnumVal_ContainerElementKind"; + internal const string UnknownEnumVal_CsdlTarget = "UnknownEnumVal_CsdlTarget"; + internal const string UnknownEnumVal_PropertyKind = "UnknownEnumVal_PropertyKind"; + internal const string UnknownEnumVal_ExpressionKind = "UnknownEnumVal_ExpressionKind"; + internal const string Bad_AmbiguousElementBinding = "Bad_AmbiguousElementBinding"; + internal const string Bad_UnresolvedType = "Bad_UnresolvedType"; + internal const string Bad_UnresolvedComplexType = "Bad_UnresolvedComplexType"; + internal const string Bad_UnresolvedEntityType = "Bad_UnresolvedEntityType"; + internal const string Bad_UnresolvedPrimitiveType = "Bad_UnresolvedPrimitiveType"; + internal const string Bad_UnresolvedOperation = "Bad_UnresolvedOperation"; + internal const string Bad_AmbiguousOperation = "Bad_AmbiguousOperation"; + internal const string Bad_OperationParametersDontMatch = "Bad_OperationParametersDontMatch"; + internal const string Bad_UnresolvedEntitySet = "Bad_UnresolvedEntitySet"; + internal const string Bad_UnresolvedEntityContainer = "Bad_UnresolvedEntityContainer"; + internal const string Bad_UnresolvedEnumType = "Bad_UnresolvedEnumType"; + internal const string Bad_UnresolvedEnumMember = "Bad_UnresolvedEnumMember"; + internal const string Bad_UnresolvedProperty = "Bad_UnresolvedProperty"; + internal const string Bad_UnresolvedParameter = "Bad_UnresolvedParameter"; + internal const string Bad_UnresolvedReturn = "Bad_UnresolvedReturn"; + internal const string Bad_UnresolvedLabeledElement = "Bad_UnresolvedLabeledElement"; + internal const string Bad_CyclicEntity = "Bad_CyclicEntity"; + internal const string Bad_CyclicComplex = "Bad_CyclicComplex"; + internal const string Bad_CyclicEntityContainer = "Bad_CyclicEntityContainer"; + internal const string Bad_UnresolvedNavigationPropertyPath = "Bad_UnresolvedNavigationPropertyPath"; + internal const string RuleSet_DuplicateRulesExistInRuleSet = "RuleSet_DuplicateRulesExistInRuleSet"; + internal const string EdmToClr_UnsupportedType = "EdmToClr_UnsupportedType"; + internal const string EdmToClr_StructuredValueMappedToNonClass = "EdmToClr_StructuredValueMappedToNonClass"; + internal const string EdmToClr_IEnumerableOfTPropertyAlreadyHasValue = "EdmToClr_IEnumerableOfTPropertyAlreadyHasValue"; + internal const string EdmToClr_StructuredPropertyDuplicateValue = "EdmToClr_StructuredPropertyDuplicateValue"; + internal const string EdmToClr_CannotConvertEdmValueToClrType = "EdmToClr_CannotConvertEdmValueToClrType"; + internal const string EdmToClr_CannotConvertEdmCollectionValueToClrType = "EdmToClr_CannotConvertEdmCollectionValueToClrType"; + internal const string EdmToClr_TryCreateObjectInstanceReturnedWrongObject = "EdmToClr_TryCreateObjectInstanceReturnedWrongObject"; + internal const string EdmUtil_NullValueForMimeTypeAnnotation = "EdmUtil_NullValueForMimeTypeAnnotation"; + internal const string EdmUtil_InvalidAnnotationValue = "EdmUtil_InvalidAnnotationValue"; + internal const string PlatformHelper_DateTimeOffsetMustContainTimeZone = "PlatformHelper_DateTimeOffsetMustContainTimeZone"; + internal const string Date_InvalidAddedOrSubtractedResults = "Date_InvalidAddedOrSubtractedResults"; + internal const string Date_InvalidDateParameters = "Date_InvalidDateParameters"; + internal const string Date_InvalidParsingString = "Date_InvalidParsingString"; + internal const string Date_InvalidCompareToTarget = "Date_InvalidCompareToTarget"; + internal const string TimeOfDay_InvalidTimeOfDayParameters = "TimeOfDay_InvalidTimeOfDayParameters"; + internal const string TimeOfDay_TicksOutOfRange = "TimeOfDay_TicksOutOfRange"; + internal const string TimeOfDay_ConvertErrorFromTimeSpan = "TimeOfDay_ConvertErrorFromTimeSpan"; + internal const string TimeOfDay_InvalidParsingString = "TimeOfDay_InvalidParsingString"; + internal const string TimeOfDay_InvalidCompareToTarget = "TimeOfDay_InvalidCompareToTarget"; + + static EntityRes loader = null; + ResourceManager resources; + + internal EntityRes() { +#if !PORTABLELIB + resources = new System.Resources.ResourceManager("Microsoft.OData.Edm", this.GetType().Assembly); +#else + resources = new System.Resources.ResourceManager("Microsoft.OData.Edm", this.GetType().GetTypeInfo().Assembly); +#endif + } + + private static EntityRes GetLoader() { + if (loader == null) { + EntityRes sr = new EntityRes(); + Interlocked.CompareExchange(ref loader, sr, null); + } + return loader; + } + + private static CultureInfo Culture { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + public static ResourceManager Resources { + get { + return GetLoader().resources; + } + } + + public static string GetString(string name, params object[] args) { + EntityRes sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, EntityRes.Culture); + + if (args != null && args.Length > 0) { + for (int i = 0; i < args.Length; i ++) { + String value = args[i] as String; + if (value != null && value.Length > 1024) { + args[i] = value.Substring(0, 1024 - 3) + "..."; + } + } + return String.Format(CultureInfo.CurrentCulture, res, args); + } + else { + return res; + } + } + + public static string GetString(string name) { + EntityRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetString(name, EntityRes.Culture); + } + + public static string GetString(string name, out bool usedFallback) { + // always false for this version of gensr + usedFallback = false; + return GetString(name); + } +#if !PORTABLELIB + public static object GetObject(string name) { + EntityRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetObject(name, EntityRes.Culture); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj new file mode 100644 index 0000000..86f1da8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj @@ -0,0 +1,607 @@ + + + + v4.5 + .NETPortable + Profile111 + true + Microsoft.OData.Edm + Library + true + false + true + Microsoft.OData.Edm + {7D921888-FE03-4C3F-80FE-2F624505461C} + $(AssemblyName).xml + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(DefineConstants);PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + true + true + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + $(MSBuildThisFileDirectory)Microsoft.OData.Edm.StyleCop + + + + + + Microsoft.OData.Edm + true + true + internal + true + Microsoft.OData.Edm.EntityRes + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + + + CapabilitiesVocabularies.xml + Designer + + + AuthorizationVocabularies.xml + Designer + + + CoreVocabularies.xml + Designer + + + AlternateKeysVocabularies.xml + Designer + + + CommunityVocabularies.xml + Designer + + + ValidationVocabularies.xml + Designer + + + + + TextTemplatingFileGenerator + Microsoft.OData.Edm.cs + + + True + True + Microsoft.OData.Edm.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.OData.Edm.cs + + + True + True + Parameterized.Microsoft.OData.Edm.tt + true + + + + + + false + + + false + + + $(SuiteBinPath) + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.tt new file mode 100644 index 0000000..4f25d99 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.tt @@ -0,0 +1,22 @@ +<#@ include file="..\..\tools\StringResourceGenerator\ResourceClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData.Edm"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Edm"; + + // The name of the generated resource class. + public const string ResourceClassName = "EntityRes"; + + // Indicates whether to skip generation of string resource attributes. + public const bool SkipSRAttributes = false; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.OData.Edm.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt new file mode 100644 index 0000000..3f144ba --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt @@ -0,0 +1,326 @@ +; NOTE: don't use \", use ' instead +; NOTE: don't use #, use ; instead for comments +; NOTE: leave the [strings] alone + +; EDM Model messages +EdmPrimitive_UnexpectedKind=Unexpected primitive type kind. +EdmPath_UnexpectedKind=Unexpected path type kind. +Annotations_TypeMismatch=Annotation of type '{0}' cannot be interpreted as '{1}'. +Constructable_VocabularyAnnotationMustHaveTarget=The annotation must have non-null target. +Constructable_EntityTypeOrCollectionOfEntityTypeExpected=An entity type or a collection of an entity type is expected. +Constructable_TargetMustBeStock=Navigation target entity type must be '{0}'. +TypeSemantics_CouldNotConvertTypeReference=The type '{0}' could not be converted to be a '{1}' type. +EdmModel_CannotUseElementWithTypeNone=An element with type 'None' cannot be used in a model. +EdmModel_CannotAddMoreThanOneEntityContainerToOneEdmModel=Cannot add more than one entity container to an edm model. +EdmEntityContainer_CannotUseElementWithTypeNone=An element with type 'None' cannot be used in an entity container. +ValueWriter_NonSerializableValue=The value writer cannot write a value of kind '{0}'. +ValueHasAlreadyBeenSet=Value has already been set. +PathSegmentMustNotContainSlash=Path segments must not contain '/' character. +Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType=The number of dependent properties must match the number of key properties on the principal entity type. '{0}' principal properties were provided, but {1} dependent properties were provided. +EdmType_UnexpectedEdmType=Unexpected Edm type. +NavigationPropertyBinding_PathIsNotValid=The navigation property binding path is not valid. + +; Evaluation messages +Edm_Evaluator_NoTermTypeAnnotationOnType=Type '{0}' must have a single type annotation with term type '{1}'. +Edm_Evaluator_NoValueAnnotationOnType=Type '{0}' must have a single annotation with term '{1}'. +Edm_Evaluator_NoValueAnnotationOnElement=Element must have a single annotation with term '{0}'. +Edm_Evaluator_UnrecognizedExpressionKind=Expression with kind '{0}' cannot be evaluated. +Edm_Evaluator_UnboundFunction=Function '{0}' is not present in the execution environment. +Edm_Evaluator_UnboundPath=Path segment '{0}' has no binding in the execution environment. +Edm_Evaluator_NoContextPath=A containing object cannot be null when getting value of an annotation with Path in the execution environment. +Edm_Evaluator_FailedTypeAssertion=Value fails to match type '{0}'. +Edm_Evaluator_TypeCastNeedsEdmModel=An edm model must be provided for type cast. + +;Error message for Semantic validation rules +EdmModel_Validator_Semantic_SystemNamespaceEncountered=The namespace '{0}' is a system namespace and cannot be used by non-system types. Please choose a different namespace. +EdmModel_Validator_Semantic_NavigationSourceTypeHasNoKeys=The entity set or singleton '{0}' is based on type '{1}' that has no keys defined. +EdmModel_Validator_Semantic_DuplicateEndName=An end with the name '{0}' is already defined. +EdmModel_Validator_Semantic_DuplicatePropertyNameSpecifiedInEntityKey=The key specified in entity type '{0}' is not valid. Property '{1}' is referenced more than once in the key element. +EdmModel_Validator_Semantic_InvalidComplexTypeAbstract=The complex type '{0}' is marked as abstract. Abstract complex types are only supported in version 1.1 EDM models. +EdmModel_Validator_Semantic_InvalidComplexTypePolymorphic=The complex type '{0}' has a base type specified. Complex type inheritance is only supported in version 1.1 EDM models. +EdmModel_Validator_Semantic_InvalidKeyNullablePart=The key part '{0}' for type '{1}' is not valid. All parts of the key must be non nullable. +EdmModel_Validator_Semantic_EntityKeyMustBeScalar=The property '{0}' in entity type '{1}' is not valid. All properties that are part of the entity key must be of primitive type. +EdmModel_Validator_Semantic_EntityComposableBoundEscapeFunctionMustBeLessOne=The entity type '{0}' has more than one composable escape functions '{1}' defined. +EdmModel_Validator_Semantic_EntityNoncomposableBoundEscapeFunctionMustBeLessOne=The entity type '{0}' has more than one non-composable escape functions '{1}' defined. +EdmModel_Validator_Semantic_InvalidKeyKeyDefinedInBaseClass=The key usage is not valid. '{0}' cannot define keys because one of its base classes '{1}' defines keys. +EdmModel_Validator_Semantic_KeyMissingOnEntityType=The entity type '{0}' has no key defined. Define the key for this entity type. +EdmModel_Validator_Semantic_BadNavigationPropertyUndefinedRole=The navigation property '{0}' is not valid. The role '{1}' is not defined in relationship '{2}'. +EdmModel_Validator_Semantic_BadNavigationPropertyRolesCannotBeTheSame=The navigation property '{0}'is not valid. The from role and to role are the same. +EdmModel_Validator_Semantic_BadNavigationPropertyCouldNotDetermineType=The navigation property type could not be determined from the role '{0}'. +EdmModel_Validator_Semantic_InvalidOperationMultipleEndsInAssociation=An on delete action can only be specified on one end of an association. +EdmModel_Validator_Semantic_EndWithManyMultiplicityCannotHaveOperationsSpecified=The navigation property '{0}' cannot have 'OnDelete' specified since its multiplicity is '*'. +EdmModel_Validator_Semantic_EndNameAlreadyDefinedDuplicate=Each name and plural name in a relationship must be unique. '{0}' is already defined. +EdmModel_Validator_Semantic_SameRoleReferredInReferentialConstraint=In relationship '{0}', the principal and dependent role of the referential constraint refers to the same role in the relationship type. +EdmModel_Validator_Semantic_NavigationPropertyPrincipalEndMultiplicityUpperBoundMustBeOne=The principal navigation property '{0}' has an invalid multiplicity. Valid values for the multiplicity of a principal end are '0..1' or '1'. +EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNonnullable=Because all dependent properties of the navigation '{0}' are non-nullable, the multiplicity of the principal end must be '1'. +EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNullable=Because all dependent properties of the navigation '{0}' are nullable, the multiplicity of the principal end must be '0..1'. +EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeZeroOneOrOne=The multiplicity of the dependent end '{0}' is not valid. Because the dependent properties represent the dependent end key, the multiplicity of the dependent end must be '0..1' or '1'. +EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeMany=The multiplicity of the dependent end '{0}' is not valid. Because the dependent properties don't represent the dependent end key, the the multiplicity of the dependent end must be '*'. +EdmModel_Validator_Semantic_MismatchNumberOfPropertiesinRelationshipConstraint=The number of properties in the dependent and principal role in a relationship constraint must be exactly identical. +EdmModel_Validator_Semantic_TypeMismatchRelationshipConstraint=The types of all properties in the dependent role of a referential constraint must be the same as the corresponding property types in the principal role. The type of property '{0}' on entity '{1}' does not match the type of property '{2}' on entity '{3}' in the referential constraint '{4}'. +EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintDependentEnd=There is no property with name '{0}' defined in the type referred to by role '{1}'. +EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintPrimaryEnd=The principal end properties in the referential constraint of the association '{0}' do not match the key of the type referred to by role '{1}'. +EdmModel_Validator_Semantic_InvalidPropertyType=A property cannot be of type '{0}'. The property type must be a complex, a primitive, an enum, a type definition or an untyped type, or a collection of complex, primitive, enum types, or type definition. +EdmModel_Validator_Semantic_BoundOperationMustHaveParameters=The Bound operation '{0}' must have at least one parameter. +EdmModel_Validator_Semantic_RequiredParametersMustPrecedeOptional=Required Parameter '{0}' must not follow an optional parameter. +EdmModel_Validator_Semantic_OperationWithUnsupportedReturnType=The return type is not valid in operation '{0}'. The operation has an unsupported type. +EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet=The operation import '{0}' returns entities of type '{1}' that cannot exist in the entity set '{2}' specified for the operation import. +EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet2=The operation import '{0}' returns entities of type '{1}' that cannot be returned by the entity set path specified for the operation import. +EdmModel_Validator_Semantic_OperationImportEntitySetExpressionKindIsInvalid=The operation import '{0}' specifies an entity set of kind '{1}' which is not supported in this context. Operation import entity set expression can be either an entity set reference or a path starting with a operation import parameter and traversing navigation properties. +EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid=The operation import '{0}' specifies an entity set expression which is not valid. Operation import entity set expression can be either an entity set reference or a path starting with a operation import parameter and traversing navigation properties. +EdmModel_Validator_Semantic_OperationImportSpecifiesEntitySetButNotEntityType=The operation import '{0}' specifies an entity set but does not return entities. +EdmModel_Validator_Semantic_OperationImportCannotImportBoundOperation=The operation import '{0}' imports operation '{1}' that is bound. Only an unbound operation can be imported using an operation import. +EdmModel_Validator_Semantic_FunctionImportWithParameterShouldNotBeIncludedInServiceDocument=The function import '{0}' should not be included in service document because it has parameter. +EdmModel_Validator_Semantic_FunctionMustHaveReturnType=The function '{0}' must specify a return type. +EdmModel_Validator_Semantic_UrlEscapeFunctionMustBoundFunction=The UrlEscape function '{0}' must be a bound function. +EdmModel_Validator_Semantic_UrlEscapeFunctionMustHaveOneStringParameter=The UrlEscape function '{0}' must have and only have one 'Edm.String' parameter. +EdmModel_Validator_Semantic_ParameterNameAlreadyDefinedDuplicate=Each parameter name in a operation must be unique. The parameter name '{0}' is already defined. +EdmModel_Validator_Semantic_DuplicateEntityContainerMemberName=Each member name in an EntityContainer must be unique. A member with name '{0}' is already defined. +EdmModel_Validator_Semantic_UnboundFunctionOverloadHasIncorrectReturnType=The function '{0}' has a different return type than other function overloads with the same name. Functions with the same name must have the same return type. +EdmModel_Validator_Semantic_OperationCannotHaveEntitySetPathWithUnBoundOperation=The unbound operation '{0}' has an entity set path defined. Entity set path can only be defined on bound operations. +EdmModel_Validator_Semantic_InvalidEntitySetPathMissingBindingParameterName=The attribute '{0}' has an invalid value. The path doesn't contain the binding parameter name. +EdmModel_Validator_Semantic_InvalidEntitySetPathWithFirstPathParameterNotMatchingFirstParameterName=The attribute '{0}' is invalid. The first item of the path '{2}' is '{3}' which does not match the first parameter name {3}. The first segment of the entity set path is required to be the name of the first parameter. +EdmModel_Validator_Semantic_InvalidEntitySetPathTypeCastSegmentMustBeEntityType=The attribute '{0}' has an invalid value. The path '{1}' has a type cast segment '{2}' that is not an entity type. +EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownNavigationProperty=The attribute '{0}' has an invalid value. The path '{1}' has a navigation property segment '{2}' that is unknown. +EdmModel_Validator_Semantic_InvalidEntitySetPathInvalidTypeCastSegment=The attribute '{0}' has an invalid value. The path '{1}' has a type cast segment that doesn't derive from the entity type '{2}' for type segment '{3}'. +EdmModel_Validator_Semantic_InvalidEntitySetPathWithNonEntityBindingParameter=The attribute '{0}' has an invalid value. The path '{1}' has a binding parameter that references a type '{2}' that is not an entity type. +EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownTypeCastSegment=The attribute '{0}' has an invalid value. The path '{1}' has a type cast segment '{2}' that cannot be found in the model. +EdmModel_Validator_Semantic_OperationWithEntitySetPathReturnTypeInvalid=The operation '{0}' has an entity set path and with an invalid return type. The return type is required to be an entity type or a collection of entity type. +EdmModel_Validator_Semantic_OperationWithEntitySetPathAndReturnTypeTypeNotAssignable=The operation '{0}' entity set path determined entity type '{1}' is not assignable to the return type '{2}'. +EdmModel_Validator_Semantic_OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType=The operation '{0}' entity set path was determined to be a reference property but the return type is a collection. +EdmModel_Validator_Semantic_SchemaElementNameAlreadyDefined=An element with the name '{0}' is already defined. +EdmModel_Validator_Semantic_InvalidMemberNameMatchesTypeName=The member name '{0}' cannot be used in a type with the same name. Member names cannot be the same as their enclosing type. +EdmModel_Validator_Semantic_PropertyNameAlreadyDefined=Each property name in a type must be unique. Property name '{0}' is already defined. +EdmModel_Validator_Semantic_BaseTypeMustHaveSameTypeKind=The base type kind of a structured type must be the same as its derived type. +EdmModel_Validator_Semantic_BaseTypeOfOpenTypeMustBeOpen=The base type of open type '{0}' is not open type. +EdmModel_Validator_Semantic_KeyPropertyMustBelongToEntity=The key property '{0}' must belong to the entity '{1}'. +EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsTypeOfKey=The 'Edm.PrimitiveType' cannot be used as the type of a key property '{0}' of an entity type '{1}'. +EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsUnderlyingType=The 'Edm.PrimitiveType' cannot be used as the underlying type of '{0}' type '{1}'. +EdmModel_Validator_Semantic_DependentPropertiesMustBelongToDependentEntity=The dependent property '{0}' must belong to the dependent entity '{1}'. +EdmModel_Validator_Semantic_DeclaringTypeMustBeCorrect=The property '{0}' cannot belong to a type other than its declaring type. +EdmModel_Validator_Semantic_InaccessibleType=The named type '{0}' could not be found from the model being validated. +EdmModel_Validator_Semantic_AmbiguousType=The named type '{0}' is ambiguous from the model being validated. +EdmModel_Validator_Semantic_InvalidNavigationPropertyType=The type of the navigation property '{0}' is invalid. The navigation target type must be an entity type or a collection of entity type. The navigation target entity type must match the declaring type of the partner property. +EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional=The target multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is the same or inherits from the target entity type, then the property represents a recursive containment and it must have an optional target represented by a collection or a nullable entity type. +EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne=The source multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is the same or inherits from the target entity type, then the property represents a recursive containment and the multiplicity of the navigation source must be zero or one. +EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne=The source multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is not the same as the target entity type, then the property represents a non-recursive containment and the multiplicity of the navigation source must be exactly one. +EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties=The complex type '{0}' is invalid. A complex type must contain at least one property. +EdmModel_Validator_Semantic_DuplicateDependentProperty=The dependent property '{0}' of navigation property '{1}' is a duplicate. +EdmModel_Validator_Semantic_ScaleOutOfRange=The scale value can range from 0 through the specified precision value. +EdmModel_Validator_Semantic_PrecisionOutOfRange=Precision cannot be negative. +EdmModel_Validator_Semantic_StringMaxLengthOutOfRange=The max length facet specifies the maximum length of an instance of the string type. For unicode equal to 'true', the max length can range from 1 to 2^30, or if 'false', 1 to 2^31. +EdmModel_Validator_Semantic_MaxLengthOutOfRange=Max length can range from 1 to 2^31. +EdmModel_Validator_Semantic_EnumMemberValueOutOfRange=The value of enum member '{0}' exceeds the range of its underlying type. +EdmModel_Validator_Semantic_EnumMemberNameAlreadyDefined=Each member name of an enum type must be unique. Enum member name '{0}' is already defined. +EdmModel_Validator_Semantic_OpenTypesSupportedForEntityTypesOnly=Only entity types can be open types. +EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull=The string reference is invalid because if 'IsUnbounded' is true 'MaxLength' must be null. +EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm=The declared name and namespace of the annotation must match the name and namespace of its xml value. +EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml=The value of an annotation marked to be serialized as an xml element must have a well-formed xml value. +EdmModel_Validator_Semantic_InvalidElementAnnotationNotIEdmStringValue=The value of an annotation marked to be serialized as an xml element must be IEdmStringValue. +EdmModel_Validator_Semantic_InvalidElementAnnotationNullNamespaceOrName=The value of an annotation marked to be serialized as an xml element must be a string representing an xml element with non-empty name and namespace. +EdmModel_Validator_Semantic_CannotAssertNullableTypeAsNonNullableType=Cannot assert the nullable type '{0}' as a non-nullable type. +EdmModel_Validator_Semantic_ExpressionPrimitiveKindCannotPromoteToAssertedType=Cannot promote the primitive type '{0}' to the specified primitive type '{1}'. +EdmModel_Validator_Semantic_NullCannotBeAssertedToBeANonNullableType=Null value cannot have a non-nullable type. +EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType=The type of the expression is incompatible with the asserted type. +EdmModel_Validator_Semantic_CollectionExpressionNotValidForNonCollectionType=A collection expression is incompatible with a non-collection type. +EdmModel_Validator_Semantic_PrimitiveConstantExpressionNotValidForNonPrimitiveType=A primitive expression is incompatible with a non-primitive type. +EdmModel_Validator_Semantic_RecordExpressionNotValidForNonStructuredType=A record expression is incompatible with a non-structured type. +EdmModel_Validator_Semantic_RecordExpressionMissingProperty=The record expression does not have a constructor for a property named '{0}'. +EdmModel_Validator_Semantic_RecordExpressionHasExtraProperties=The type of the record expression is not open and does not contain a property named '{0}'. +EdmModel_Validator_Semantic_DuplicateAnnotation=The annotated element '{0}' has multiple annotations with the term '{1}' and the qualifier '{2}'. +EdmModel_Validator_Semantic_IncorrectNumberOfArguments=The function application provides '{0}' arguments, but the function '{1}' expects '{2}' arguments. +EdmModel_Validator_Semantic_DuplicateEntityContainerName=Each entity container name in a function must be unique. The name '{0}' is already defined. +EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType=The primitive expression is not compatible with the asserted type. +EdmModel_Validator_Semantic_ExpressionEnumKindNotValidForAssertedType=The enum expression is not compatible with the asserted type. +EdmModel_Validator_Semantic_IntegerConstantValueOutOfRange=The value of the integer constant is out of range for the asserted type. +EdmModel_Validator_Semantic_StringConstantLengthOutOfRange=The value of the string constant is '{0}' characters long, but the max length of its type is '{1}'. +EdmModel_Validator_Semantic_BinaryConstantLengthOutOfRange=The value of the binary constant is '{0}' characters long, but the max length of its type is '{1}'. +EdmModel_Validator_Semantic_TypeMustNotHaveKindOfNone=A type without other errors must not have kind of none. +EdmModel_Validator_Semantic_SchemaElementMustNotHaveKindOfNone=A schema element without other errors must not have kind of none. The kind of schema element '{0}' is none. +EdmModel_Validator_Semantic_PropertyMustNotHaveKindOfNone=A property without other errors must not have kind of none. The kind of property '{0}' is none. +EdmModel_Validator_Semantic_PrimitiveTypeMustNotHaveKindOfNone=A primitive type without other errors must not have kind of none. The kind of primitive type '{0}' is none. +EdmModel_Validator_Semantic_EntityContainerElementMustNotHaveKindOfNone=An entity container element without other errors must not have kind of none. The kind of entity container element '{0}' is none. +EdmModel_Validator_Semantic_DuplicateNavigationPropertyMapping=The entity set '{0}' should have only a single mapping for the property '{1}'. +EdmModel_Validator_Semantic_NavigationMappingMustBeBidirectional=The binding of the entity set or singleton '{0}' on navigation property '{1}' is invalid, the binding of bidirectional navigation property must be bidirectional if specified. +EdmModel_Validator_Semantic_EntitySetCanOnlyBeContainedByASingleNavigationProperty=The entity set '{0}' is invalid because it is contained by more than one navigation property. +EdmModel_Validator_Semantic_TypeAnnotationMissingRequiredProperty=The type annotation is missing a binding for the property '{0}'. +EdmModel_Validator_Semantic_TypeAnnotationHasExtraProperties=They type of the type annotation is not open, and does not contain a property named '{0}'. +EdmModel_Validator_Semantic_EnumMustHaveIntegralUnderlyingType=The underlying type of '{0}' is not valid. The underlying type of an enum type must be an integral type. +EdmModel_Validator_Semantic_InaccessibleTerm=The term '{0}' could not be found from the model being validated. +EdmModel_Validator_Semantic_InaccessibleTarget=The target '{0}' could not be found from the model being validated. +EdmModel_Validator_Semantic_VocabularyAnnotationApplyToNotAllowedAnnotatable=The target '{0}' of the annotation is not allowed in the AppliesTo '{1}' of the term '{2}'.". +EdmModel_Validator_Semantic_ElementDirectValueAnnotationFullNameMustBeUnique=An element already has a direct annotation with the namespace '{0}' and name '{1}'. +EdmModel_Validator_Semantic_NoEntitySetsFoundForType=The association set '{0}' cannot assume an entity set for the role '{2}' because there are no entity sets for the role type '{1}'. +EdmModel_Validator_Semantic_CannotInferEntitySetWithMultipleSetsPerType=The association set '{0}' must specify an entity set for the role '{2}' because there are multiple entity sets for the role type '{1}'. +EdmModel_Validator_Semantic_EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet=Because the navigation property '{0}' is recursive, the mapping from the entity set '{1}' must point back to itself. +EdmModel_Validator_Semantic_NavigationPropertyEntityMustNotIndirectlyContainItself=The navigation property '{0}' is invalid because it indirectly contains itself. +EdmModel_Validator_Semantic_PathIsNotValidForTheGivenContext=The path cannot be resolved in the given context. The segment '{0}' failed to resolve. +EdmModel_Validator_Semantic_NavigationPropertyMappingMustPointToValidTargetForProperty=The entity set or singleton '{1}' is not a valid destination for the navigation property '{0}' because it cannot hold an element of the target entity type. +EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterNames=The bound function '{0}' is a duplicate of other bound functions. For bound functions the combination of the namespace, name, binding parameter type and unordered set of parameter names uniquely identifies a bound function. +EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterTypes=The bound function '{0}' is a duplicate of other bound functions. For bound functions the combination of the namespace, name, binding parameter type and ordered set of parameter types uniquely identifies a bound function. +EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterNames=The unbound function '{0}' is a duplicate of other unbound functions. For unbound functions the combination of the namespace, name and unordered set of parameter names uniquely identifies an unbound function. +EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterTypes=The unbound function '{0}' is a duplicate of other unbound functions. For unbound functions the combination of the namespace, name and ordered set of parameter types uniquely identifies an unbound function. +EdmModel_Validator_Semantic_ModelDuplicateBoundActions=The bound action '{0}' is a duplicate of other bound actions. For bound actions the combination of the namespace, name, and binding parameter type uniquely identifies an bound action. +EdmModel_Validator_Semantic_ModelDuplicateUnBoundActions=The unbound action '{0}' is a duplicate of other unbound actions. For unbound actions the combination of the namespace, and name uniquely identifies an unbound action. +EdmModel_Validator_Semantic_BoundFunctionOverloadsMustHaveSameReturnType=The bound function overload '{0}' does not have the same return type as other function overloads. Expected type '{1}'. +EdmModel_Validator_Semantic_EntitySetTypeMustBeCollectionOfEntityType=The type '{0}' of the entity set '{1}' is not valid, it must be collection of entity type. +EdmModel_Validator_Semantic_SingletonTypeMustBeEntityType=The type '{0}' of the singleton '{1}' is not valid, it must be entity type. +EdmModel_Validator_Semantic_NavigationPropertyOfCollectionTypeMustNotTargetToSingleton=The navigation property mapping '{0}' is invalid because its type is collection but target to a singleton '{1}'. +EdmModel_Validator_Semantic_StructuredTypeBaseTypeCannotBeAbstractType=The type '{0}' cannot be the base type of an '{1}' type '{2}'. +EdmModel_Validator_Semantic_PropertyTypeCannotBeCollectionOfAbstractType=The type '{0}' cannot be used as the type of a property '{1}'. +EdmModel_Validator_Semantic_OperationReturnTypeCannotBeCollectionOfAbstractType=The type '{0}' cannot be used as the return type of a function '{1}'. +EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfSingleton=The type 'Edm.EntityType' cannot be used as the type of a singleton '{0}' in an entity container. +EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfEntitySet=The type 'Edm.EntityType' cannot be used as the type of an entity set '{0}' in an entity container. +EdmModel_Validator_Semantic_DeclaringTypeOfNavigationSourceCannotHavePathProperty=The declaring type '{0}' of {1} '{2}' cannot include path type property. +EdmModel_Validator_Semantic_TypeOfNavigationPropertyCannotHavePathProperty=The type '{0}' of navigation property '{1}' on declaring type '{2}' cannot include path type property. + +;Error message for Syntactic validation rules +EdmModel_Validator_Syntactic_MissingName=The name is missing or not valid. +EdmModel_Validator_Syntactic_EdmModel_NameIsTooLong=The specified name must not be longer than 480 characters: '{0}'. +EdmModel_Validator_Syntactic_EdmModel_NameIsNotAllowed=The specified name is not allowed: '{0}'. +EdmModel_Validator_Syntactic_MissingNamespaceName=The namespace name is missing or not valid. +EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsTooLong=The specified name must not be longer than 480 characters: '{0}'. +EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsNotAllowed=The specified namespace name is not allowed: '{0}'. +EdmModel_Validator_Syntactic_PropertyMustNotBeNull=The value of the property '{0}.{1}' must not be null. +EdmModel_Validator_Syntactic_EnumPropertyValueOutOfRange=The property '{0}.{1}' of type '{2}' has value '{3}' that is not a valid enum member. +EdmModel_Validator_Syntactic_InterfaceKindValueMismatch=An object with the value '{0}' of the '{1}.{2}' property must implement '{3}' interface. +EdmModel_Validator_Syntactic_TypeRefInterfaceTypeKindValueMismatch=An object implementing '{0}' interface has type definition of kind '{1}'. The type reference interface must match to the kind of the definition. +EdmModel_Validator_Syntactic_InterfaceKindValueUnexpected=The value '{0}' of the property '{1}.{2}' is not semantically valid. A semantically valid model must not contain elements of kind '{0}'. +EdmModel_Validator_Syntactic_EnumerableMustNotHaveNullElements=The value of the enumeration the property '{0}.{1}' contains a null element. Enumeration properties must not contain null elements. +EdmModel_Validator_Syntactic_NavigationPartnerInvalid=The partner of the navigation property '{0}' must not be the same property, and must point back to the navigation property. +EdmModel_Validator_Syntactic_InterfaceCriticalCycleInTypeHierarchy=The chain of base types of type '{0}' is cyclic. + +;Error message for Serializer +Serializer_SingleFileExpected=Single file provided but model cannot be serialized into single file. +Serializer_UnknownEdmVersion=Unknown Edm version. +Serializer_UnknownEdmxVersion=Unknown Edmx version. +Serializer_NonInlineOperationImportReturnType=The operation import '{0}' could not be serialized because its return type cannot be represented inline. +Serializer_ReferencedTypeMustHaveValidName=A referenced type can not be serialized with an invalid name. The name '{0}' is invalid. +Serializer_OutOfLineAnnotationTargetMustHaveValidName=The annotation can not be serialized with an invalid target name. The name '{0}' is invalid. +Serializer_NoSchemasProduced=No CSDL is written because no schema elements could be produced. This is likely because the model is empty. + +XmlParser_EmptyFile={0} does not contain a schema definition, or the XmlReader provided started at the end of the file. +XmlParser_EmptySchemaTextReader=The source XmlReader does not contain a schema definition or started at the end of the file. +XmlParser_MissingAttribute=Required schema attribute '{0}' is not present on element '{1}'. +XmlParser_TextNotAllowed=The current schema element does not support text '{0}'. +XmlParser_UnexpectedAttribute=The attribute '{0}' was not expected in the given context. +XmlParser_UnexpectedElement=The schema element '{0}' was not expected in the given context. +XmlParser_UnusedElement=Unused schema element: '{0}'. +XmlParser_UnexpectedNodeType=Unexpected XML node type: {0}. +XmlParser_UnexpectedRootElement=The element '{0}' was unexpected for the root element. The root element should be {1}. +XmlParser_UnexpectedRootElementWrongNamespace=The namespace '{0}' is invalid. The root element is expected to belong to one of the following namespaces: '{1}'. +XmlParser_UnexpectedRootElementNoNamespace=The root element has no namespace. The root element is expected to belong to one of the following namespaces: '{0}'. + +; CSDL Parser +CsdlParser_InvalidEntitySetPathWithUnboundAction=The {0} '{1}' is invalid. The entitySetPath value is not allowed when IsBound attribute is false. +CsdlParser_InvalidAlias=The alias '{0}' is not a valid simple name. +CsdlParser_InvalidDeleteAction=The delete action '{0}' is not valid. Action must be: 'None', 'Cascade', or 'Restrict'. +CsdlParser_MissingTypeAttributeOrElement=An XML attribute or sub-element representing an EDM type is missing. +CsdlParser_InvalidEndRoleInRelationshipConstraint=There is no Role with name '{0}' defined in relationship '{1}'. +CsdlParser_InvalidMultiplicity=The multiplicity '{0}' is not valid. Multiplicity must be: '*', '0..1', or '1'. +CsdlParser_ReferentialConstraintRequiresOneDependent=Referential constraints requires one dependent role. Multiple dependent roles were specified for this referential constraint. +CsdlParser_ReferentialConstraintRequiresOnePrincipal=Referential constraints requires one principal role. Multiple principal roles were specified for this referential constraint. +CsdlParser_InvalidIfExpressionIncorrectNumberOfOperands=If expression must contain 3 operands, the first being a boolean test, the second being being evaluated if the first is true, and the third being evaluated if the first is false. +CsdlParser_InvalidIsTypeExpressionIncorrectNumberOfOperands=The IsType expression must contain 1 operand. +CsdlParser_InvalidCastExpressionIncorrectNumberOfOperands=The Cast expression must contain 1 operand. +CsdlParser_InvalidLabeledElementExpressionIncorrectNumberOfOperands=The LabeledElement expression must contain 1 operand. +CsdlParser_InvalidTypeName=The type name '{0}' is invalid. The type name must be that of a primitive type, a fully qualified name or an inline 'Collection' or 'Ref' type. +CsdlParser_InvalidQualifiedName=The qualified name '{0}' is invalid. A qualified name must have a valid namespace or alias, and a valid name. +CsdlParser_NoReadersProvided=A model could not be produced because no XML readers were provided. +CsdlParser_NullXmlReader=A model could not be produced because one of the XML readers was null. +CsdlParser_InvalidEntitySetPath='{0}' is not a valid entity set path. +CsdlParser_InvalidEnumMemberPath='{0}' is not a valid enum member path. +CsdlParser_CannotSpecifyNullableAttributeForNavigationPropertyWithCollectionType=The 'Nullable' attribute cannot be specified for a navigation property with collection type. +CsdlParser_MetadataDocumentCannotHaveMoreThanOneEntityContainer=Metadata document cannot have more than one entity container. +CsdlSemantics_ReferentialConstraintMismatch= There was a mismatch in the principal and dependent ends of the referential constraint. +CsdlSemantics_EnumMemberMustHaveValue=The enumeration member must have a value. +CsdlSemantics_ImpossibleAnnotationsTarget=The annotation target '{0}' could not be resolved because it cannot refer to an annotatable element. +CsdlSemantics_DuplicateAlias=The schema '{0}' contains the alias '{1}' more than once. + +;EdmxParser +EdmxParser_EdmxVersionMismatch=The EDMX version specified in the 'Version' attribute does not match the version corresponding to the namespace of the 'Edmx' element. +EdmxParser_BodyElement=Unexpected {0} element while parsing Edmx. Edmx is expected to have at most one of 'Runtime' or 'DataServices' elements. +EdmxParser_InvalidReferenceIncorrectNumberOfIncludes=edmx:Reference must contain at least one edmx:Includes or edmx:IncludeAnnotations. +EdmxParser_UnresolvedReferenceUriInEdmxReference=Unresolved Uri found in edmx:Reference, getReferencedModelReaderFunc should not return null when the URI is not a well-known schema. + +;EdxmParseException +EdmParseException_ErrorsEncounteredInEdmx=Encountered the following errors when parsing the EDMX document: \r\n{0} + +; Error message for the value parser +ValueParser_InvalidBoolean=The value '{0}' is not a valid boolean. The value must be 'true' or 'false'. +ValueParser_InvalidInteger=The value '{0}' is not a valid integer. The value must be a valid 32 bit integer. +ValueParser_InvalidLong=The value '{0}' is not a valid integer. The value must be a valid 64 bit integer. +ValueParser_InvalidFloatingPoint=The value '{0}' is not a valid floating point value. +ValueParser_InvalidMaxLength=The value '{0}' is not a valid integer. The value must be a valid 32 bit integer or 'Max'. +ValueParser_InvalidSrid=The value '{0}' is not a valid SRID. The value must either be a 32 bit integer or 'Variable'. +ValueParser_InvalidScale=The value '{0}' is not a valid scale. The value must either be a 32 bit integer or 'Variable'. +ValueParser_InvalidGuid=The value '{0}' is not a valid Guid. +ValueParser_InvalidDecimal=The value '{0}' is not a valid decimal. +ValueParser_InvalidDateTimeOffset=The value '{0}' is not a valid date time offset value. +ValueParser_InvalidDateTime=The value '{0}' is not a valid date time value. +ValueParser_InvalidDate=The value '{0}' is not a valid date value. +ValueParser_InvalidDuration=The value '{0}' is not a valid duration value. +ValueParser_InvalidBinary=The value '{0}' is not a valid binary value. The value must be a hexadecimal string and must not be prefixed by '0x'. +ValueParser_InvalidTimeOfDay=The value '{0}' is not a valid TimeOfDay value. + +;Unknown enumerated type value errors +UnknownEnumVal_Multiplicity=Invalid multiplicity: '{0}' +UnknownEnumVal_SchemaElementKind=Invalid schema element kind: '{0}' +UnknownEnumVal_TypeKind=Invalid type kind: '{0}' +UnknownEnumVal_PrimitiveKind=Invalid primitive kind: '{0}' +UnknownEnumVal_ContainerElementKind=Invalid container element kind: '{0}' +UnknownEnumVal_CsdlTarget=Invalid CSDL target: '{0}' +UnknownEnumVal_PropertyKind=Invalid property kind: '{0}' +UnknownEnumVal_ExpressionKind=Invalid expression kind: '{0}' + +; Error message for 'Bad' types +Bad_AmbiguousElementBinding=The name '{0}' is ambiguous. +Bad_UnresolvedType=The type '{0}' could not be found. +Bad_UnresolvedComplexType=The complex type '{0}' could not be found. +Bad_UnresolvedEntityType=The entity type '{0}' could not be found. +Bad_UnresolvedPrimitiveType=The primitive type '{0}' could not be found. +Bad_UnresolvedOperation=The operation '{0}' could not be found. +Bad_AmbiguousOperation=The operation '{0}' could not be resolved because more than one operation could be used for this application. +Bad_OperationParametersDontMatch=The operation '{0}' could not be resolved because none of the operations with that name take the correct set of parameters. +Bad_UnresolvedEntitySet=The entity set '{0}' could not be found. +Bad_UnresolvedEntityContainer=The entity container '{0}' could not be found. +Bad_UnresolvedEnumType=The enum type '{0}' could not be found. +Bad_UnresolvedEnumMember=The enum member '{0}' could not be found. +Bad_UnresolvedProperty=The property '{0}' could not be found. +Bad_UnresolvedParameter=The parameter '{0}' could not be found. +Bad_UnresolvedReturn=The return of operation '{0}' could not be found. +Bad_UnresolvedLabeledElement=The labeled element '{0}' could not be found. +Bad_CyclicEntity=The entity '{0}' is invalid because its base type is cyclic. +Bad_CyclicComplex=The complex type '{0}' is invalid because its base type is cyclic. +Bad_CyclicEntityContainer=The entity container '{0}' is invalid because its extends hierarchy is cyclic. +Bad_UnresolvedNavigationPropertyPath=A navigation property could not be found for the path '{0}' starting from the type '{1}'. + +; Error messages for validation rulesets +RuleSet_DuplicateRulesExistInRuleSet=The same rule cannot be in the same rule set twice. + +; Error messages for EDM to CLR conversion +EdmToClr_UnsupportedType=Conversion of EDM values to a CLR type with type {0} is not supported. +EdmToClr_StructuredValueMappedToNonClass=Conversion of an EDM structured value is supported only to a CLR class. +EdmToClr_IEnumerableOfTPropertyAlreadyHasValue=Cannot initialize a property '{0}' on an object of type '{1}'. The property already has a value. +EdmToClr_StructuredPropertyDuplicateValue=An EDM structured value contains multiple values for the property '{0}'. Conversion of an EDM structured value with duplicate property values is not supported. +EdmToClr_CannotConvertEdmValueToClrType=Conversion of an EDM value of the type '{0}' to the CLR type '{1}' is not supported. +EdmToClr_CannotConvertEdmCollectionValueToClrType=Conversion of an edm collection value to the CLR type '{0}' is not supported. EDM collection values can be converted to System.Collections.Generic.IEnumerable, System.Collections.Generic.IList or System.Collections.Generic.ICollection. +EdmToClr_TryCreateObjectInstanceReturnedWrongObject=The type '{0}' of the object returned by the TryCreateObjectInstance delegate is not assignable to the expected type '{1}'. + +;Error message for EdmUtil class +EdmUtil_NullValueForMimeTypeAnnotation=The MIME type annotation must not have a null value. +EdmUtil_InvalidAnnotationValue=An annotation of type string was expected for the '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' annotation, but an annotation of type '{1}' was found. + +; Note: The below list of error messages are common to the ODataLib, EdmLib, Spatial, Server and Client projects. +PlatformHelper_DateTimeOffsetMustContainTimeZone=The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information. + +;Error message for Build-in type +Date_InvalidAddedOrSubtractedResults=The added or subtracted value results in an un-representable Date. +Date_InvalidDateParameters=The Year '{0}', Month '{1}' and Day '{2}' parameters describe an un-representable Date. +Date_InvalidParsingString=String '{0}' was not recognized as a valid Date. +Date_InvalidCompareToTarget=Target object '{0}' is not an instance with type of Date. +TimeOfDay_InvalidTimeOfDayParameters=The Hour '{0}', Minute '{1}', Second '{2}' and Millisecond '{3}' parameters describe an un-representable TimeOfDay. +TimeOfDay_TicksOutOfRange=The ticks value '{0}' is out of representable TimeOfDay range. +TimeOfDay_ConvertErrorFromTimeSpan=The TimeSpan value '{0}' is out of representable TimeOfDay range. +TimeOfDay_InvalidParsingString=String '{0}' was not recognized as a valid TimeOfDay. +TimeOfDay_InvalidCompareToTarget=Target object '{0}' is not an instance with type of TimeOfDay. \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs new file mode 100644 index 0000000..fff1661 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs @@ -0,0 +1,2207 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm { + using System; + using System.Resources; + + /// + /// Strongly-typed and parameterized string resources. + /// + internal static class Strings { + /// + /// A string like "Unexpected primitive type kind." + /// + internal static string EdmPrimitive_UnexpectedKind { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmPrimitive_UnexpectedKind); + } + } + + /// + /// A string like "Unexpected path type kind." + /// + internal static string EdmPath_UnexpectedKind { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmPath_UnexpectedKind); + } + } + + /// + /// A string like "Annotation of type '{0}' cannot be interpreted as '{1}'." + /// + internal static string Annotations_TypeMismatch(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Annotations_TypeMismatch, p0, p1); + } + + /// + /// A string like "The annotation must have non-null target." + /// + internal static string Constructable_VocabularyAnnotationMustHaveTarget { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Constructable_VocabularyAnnotationMustHaveTarget); + } + } + + /// + /// A string like "An entity type or a collection of an entity type is expected." + /// + internal static string Constructable_EntityTypeOrCollectionOfEntityTypeExpected { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Constructable_EntityTypeOrCollectionOfEntityTypeExpected); + } + } + + /// + /// A string like "Navigation target entity type must be '{0}'." + /// + internal static string Constructable_TargetMustBeStock(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Constructable_TargetMustBeStock, p0); + } + + /// + /// A string like "The type '{0}' could not be converted to be a '{1}' type." + /// + internal static string TypeSemantics_CouldNotConvertTypeReference(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.TypeSemantics_CouldNotConvertTypeReference, p0, p1); + } + + /// + /// A string like "An element with type 'None' cannot be used in a model." + /// + internal static string EdmModel_CannotUseElementWithTypeNone { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_CannotUseElementWithTypeNone); + } + } + + /// + /// A string like "Cannot add more than one entity container to an edm model." + /// + internal static string EdmModel_CannotAddMoreThanOneEntityContainerToOneEdmModel { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_CannotAddMoreThanOneEntityContainerToOneEdmModel); + } + } + + /// + /// A string like "An element with type 'None' cannot be used in an entity container." + /// + internal static string EdmEntityContainer_CannotUseElementWithTypeNone { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmEntityContainer_CannotUseElementWithTypeNone); + } + } + + /// + /// A string like "The value writer cannot write a value of kind '{0}'." + /// + internal static string ValueWriter_NonSerializableValue(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueWriter_NonSerializableValue, p0); + } + + /// + /// A string like "Value has already been set." + /// + internal static string ValueHasAlreadyBeenSet { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueHasAlreadyBeenSet); + } + } + + /// + /// A string like "Path segments must not contain '/' character." + /// + internal static string PathSegmentMustNotContainSlash { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.PathSegmentMustNotContainSlash); + } + } + + /// + /// A string like "The number of dependent properties must match the number of key properties on the principal entity type. '{0}' principal properties were provided, but {1} dependent properties were provided." + /// + internal static string Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType, p0, p1); + } + + /// + /// A string like "Unexpected Edm type." + /// + internal static string EdmType_UnexpectedEdmType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmType_UnexpectedEdmType); + } + } + + /// + /// A string like "The navigation property binding path is not valid." + /// + internal static string NavigationPropertyBinding_PathIsNotValid { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.NavigationPropertyBinding_PathIsNotValid); + } + } + + /// + /// A string like "Type '{0}' must have a single type annotation with term type '{1}'." + /// + internal static string Edm_Evaluator_NoTermTypeAnnotationOnType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_NoTermTypeAnnotationOnType, p0, p1); + } + + /// + /// A string like "Type '{0}' must have a single annotation with term '{1}'." + /// + internal static string Edm_Evaluator_NoValueAnnotationOnType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_NoValueAnnotationOnType, p0, p1); + } + + /// + /// A string like "Element must have a single annotation with term '{0}'." + /// + internal static string Edm_Evaluator_NoValueAnnotationOnElement(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_NoValueAnnotationOnElement, p0); + } + + /// + /// A string like "Expression with kind '{0}' cannot be evaluated." + /// + internal static string Edm_Evaluator_UnrecognizedExpressionKind(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_UnrecognizedExpressionKind, p0); + } + + /// + /// A string like "Function '{0}' is not present in the execution environment." + /// + internal static string Edm_Evaluator_UnboundFunction(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_UnboundFunction, p0); + } + + /// + /// A string like "Path segment '{0}' has no binding in the execution environment." + /// + internal static string Edm_Evaluator_UnboundPath(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_UnboundPath, p0); + } + + /// + /// A string like "A containing object cannot be null when getting value of an annotation with Path in the execution environment." + /// + internal static string Edm_Evaluator_NoContextPath { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_NoContextPath); + } + } + + /// + /// A string like "Value fails to match type '{0}'." + /// + internal static string Edm_Evaluator_FailedTypeAssertion(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_FailedTypeAssertion, p0); + } + + /// + /// A string like "An edm model must be provided for type cast." + /// + internal static string Edm_Evaluator_TypeCastNeedsEdmModel { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Edm_Evaluator_TypeCastNeedsEdmModel); + } + } + + /// + /// A string like "The namespace '{0}' is a system namespace and cannot be used by non-system types. Please choose a different namespace." + /// + internal static string EdmModel_Validator_Semantic_SystemNamespaceEncountered(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_SystemNamespaceEncountered, p0); + } + + /// + /// A string like "The entity set or singleton '{0}' is based on type '{1}' that has no keys defined." + /// + internal static string EdmModel_Validator_Semantic_NavigationSourceTypeHasNoKeys(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationSourceTypeHasNoKeys, p0, p1); + } + + /// + /// A string like "An end with the name '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_DuplicateEndName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DuplicateEndName, p0); + } + + /// + /// A string like "The key specified in entity type '{0}' is not valid. Property '{1}' is referenced more than once in the key element." + /// + internal static string EdmModel_Validator_Semantic_DuplicatePropertyNameSpecifiedInEntityKey(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DuplicatePropertyNameSpecifiedInEntityKey, p0, p1); + } + + /// + /// A string like "The complex type '{0}' is marked as abstract. Abstract complex types are only supported in version 1.1 EDM models." + /// + internal static string EdmModel_Validator_Semantic_InvalidComplexTypeAbstract(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidComplexTypeAbstract, p0); + } + + /// + /// A string like "The complex type '{0}' has a base type specified. Complex type inheritance is only supported in version 1.1 EDM models." + /// + internal static string EdmModel_Validator_Semantic_InvalidComplexTypePolymorphic(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidComplexTypePolymorphic, p0); + } + + /// + /// A string like "The key part '{0}' for type '{1}' is not valid. All parts of the key must be non nullable." + /// + internal static string EdmModel_Validator_Semantic_InvalidKeyNullablePart(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidKeyNullablePart, p0, p1); + } + + /// + /// A string like "The property '{0}' in entity type '{1}' is not valid. All properties that are part of the entity key must be of primitive type." + /// + internal static string EdmModel_Validator_Semantic_EntityKeyMustBeScalar(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EntityKeyMustBeScalar, p0, p1); + } + + /// + /// A string like "The entity type '{0}' has more than one composable escape functions '{1}' defined." + /// + internal static string EdmModel_Validator_Semantic_EntityComposableBoundEscapeFunctionMustBeLessOne(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EntityComposableBoundEscapeFunctionMustBeLessOne, p0, p1); + } + + /// + /// A string like "The entity type '{0}' has more than one non-composable escape functions '{1}' defined." + /// + internal static string EdmModel_Validator_Semantic_EntityNoncomposableBoundEscapeFunctionMustBeLessOne(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EntityNoncomposableBoundEscapeFunctionMustBeLessOne, p0, p1); + } + + /// + /// A string like "The key usage is not valid. '{0}' cannot define keys because one of its base classes '{1}' defines keys." + /// + internal static string EdmModel_Validator_Semantic_InvalidKeyKeyDefinedInBaseClass(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidKeyKeyDefinedInBaseClass, p0, p1); + } + + /// + /// A string like "The entity type '{0}' has no key defined. Define the key for this entity type." + /// + internal static string EdmModel_Validator_Semantic_KeyMissingOnEntityType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_KeyMissingOnEntityType, p0); + } + + /// + /// A string like "The navigation property '{0}' is not valid. The role '{1}' is not defined in relationship '{2}'." + /// + internal static string EdmModel_Validator_Semantic_BadNavigationPropertyUndefinedRole(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BadNavigationPropertyUndefinedRole, p0, p1, p2); + } + + /// + /// A string like "The navigation property '{0}'is not valid. The from role and to role are the same." + /// + internal static string EdmModel_Validator_Semantic_BadNavigationPropertyRolesCannotBeTheSame(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BadNavigationPropertyRolesCannotBeTheSame, p0); + } + + /// + /// A string like "The navigation property type could not be determined from the role '{0}'." + /// + internal static string EdmModel_Validator_Semantic_BadNavigationPropertyCouldNotDetermineType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BadNavigationPropertyCouldNotDetermineType, p0); + } + + /// + /// A string like "An on delete action can only be specified on one end of an association." + /// + internal static string EdmModel_Validator_Semantic_InvalidOperationMultipleEndsInAssociation { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidOperationMultipleEndsInAssociation); + } + } + + /// + /// A string like "The navigation property '{0}' cannot have 'OnDelete' specified since its multiplicity is '*'." + /// + internal static string EdmModel_Validator_Semantic_EndWithManyMultiplicityCannotHaveOperationsSpecified(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EndWithManyMultiplicityCannotHaveOperationsSpecified, p0); + } + + /// + /// A string like "Each name and plural name in a relationship must be unique. '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_EndNameAlreadyDefinedDuplicate(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EndNameAlreadyDefinedDuplicate, p0); + } + + /// + /// A string like "In relationship '{0}', the principal and dependent role of the referential constraint refers to the same role in the relationship type." + /// + internal static string EdmModel_Validator_Semantic_SameRoleReferredInReferentialConstraint(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_SameRoleReferredInReferentialConstraint, p0); + } + + /// + /// A string like "The principal navigation property '{0}' has an invalid multiplicity. Valid values for the multiplicity of a principal end are '0..1' or '1'." + /// + internal static string EdmModel_Validator_Semantic_NavigationPropertyPrincipalEndMultiplicityUpperBoundMustBeOne(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationPropertyPrincipalEndMultiplicityUpperBoundMustBeOne, p0); + } + + /// + /// A string like "Because all dependent properties of the navigation '{0}' are non-nullable, the multiplicity of the principal end must be '1'." + /// + internal static string EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNonnullable(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNonnullable, p0); + } + + /// + /// A string like "Because all dependent properties of the navigation '{0}' are nullable, the multiplicity of the principal end must be '0..1'." + /// + internal static string EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNullable(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNullable, p0); + } + + /// + /// A string like "The multiplicity of the dependent end '{0}' is not valid. Because the dependent properties represent the dependent end key, the multiplicity of the dependent end must be '0..1' or '1'." + /// + internal static string EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeZeroOneOrOne(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeZeroOneOrOne, p0); + } + + /// + /// A string like "The multiplicity of the dependent end '{0}' is not valid. Because the dependent properties don't represent the dependent end key, the the multiplicity of the dependent end must be '*'." + /// + internal static string EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeMany(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeMany, p0); + } + + /// + /// A string like "The number of properties in the dependent and principal role in a relationship constraint must be exactly identical." + /// + internal static string EdmModel_Validator_Semantic_MismatchNumberOfPropertiesinRelationshipConstraint { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_MismatchNumberOfPropertiesinRelationshipConstraint); + } + } + + /// + /// A string like "The types of all properties in the dependent role of a referential constraint must be the same as the corresponding property types in the principal role. The type of property '{0}' on entity '{1}' does not match the type of property '{2}' on entity '{3}' in the referential constraint '{4}'." + /// + internal static string EdmModel_Validator_Semantic_TypeMismatchRelationshipConstraint(object p0, object p1, object p2, object p3, object p4) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_TypeMismatchRelationshipConstraint, p0, p1, p2, p3, p4); + } + + /// + /// A string like "There is no property with name '{0}' defined in the type referred to by role '{1}'." + /// + internal static string EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintDependentEnd(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintDependentEnd, p0, p1); + } + + /// + /// A string like "The principal end properties in the referential constraint of the association '{0}' do not match the key of the type referred to by role '{1}'." + /// + internal static string EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintPrimaryEnd(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidPropertyInRelationshipConstraintPrimaryEnd, p0, p1); + } + + /// + /// A string like "A property cannot be of type '{0}'. The property type must be a complex, a primitive, an enum, a type definition or an untyped type, or a collection of complex, primitive, enum types, or type definition." + /// + internal static string EdmModel_Validator_Semantic_InvalidPropertyType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidPropertyType, p0); + } + + /// + /// A string like "The Bound operation '{0}' must have at least one parameter." + /// + internal static string EdmModel_Validator_Semantic_BoundOperationMustHaveParameters(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BoundOperationMustHaveParameters, p0); + } + + /// + /// A string like "Required Parameter '{0}' must not follow an optional parameter." + /// + internal static string EdmModel_Validator_Semantic_RequiredParametersMustPrecedeOptional(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_RequiredParametersMustPrecedeOptional, p0); + } + + /// + /// A string like "The return type is not valid in operation '{0}'. The operation has an unsupported type." + /// + internal static string EdmModel_Validator_Semantic_OperationWithUnsupportedReturnType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationWithUnsupportedReturnType, p0); + } + + /// + /// A string like "The operation import '{0}' returns entities of type '{1}' that cannot exist in the entity set '{2}' specified for the operation import." + /// + internal static string EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet, p0, p1, p2); + } + + /// + /// A string like "The operation import '{0}' returns entities of type '{1}' that cannot be returned by the entity set path specified for the operation import." + /// + internal static string EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet2(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet2, p0, p1); + } + + /// + /// A string like "The operation import '{0}' specifies an entity set of kind '{1}' which is not supported in this context. Operation import entity set expression can be either an entity set reference or a path starting with a operation import parameter and traversing navigation properties." + /// + internal static string EdmModel_Validator_Semantic_OperationImportEntitySetExpressionKindIsInvalid(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationImportEntitySetExpressionKindIsInvalid, p0, p1); + } + + /// + /// A string like "The operation import '{0}' specifies an entity set expression which is not valid. Operation import entity set expression can be either an entity set reference or a path starting with a operation import parameter and traversing navigation properties." + /// + internal static string EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid, p0); + } + + /// + /// A string like "The operation import '{0}' specifies an entity set but does not return entities." + /// + internal static string EdmModel_Validator_Semantic_OperationImportSpecifiesEntitySetButNotEntityType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationImportSpecifiesEntitySetButNotEntityType, p0); + } + + /// + /// A string like "The operation import '{0}' imports operation '{1}' that is bound. Only an unbound operation can be imported using an operation import." + /// + internal static string EdmModel_Validator_Semantic_OperationImportCannotImportBoundOperation(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationImportCannotImportBoundOperation, p0, p1); + } + + /// + /// A string like "The function import '{0}' should not be included in service document because it has parameter." + /// + internal static string EdmModel_Validator_Semantic_FunctionImportWithParameterShouldNotBeIncludedInServiceDocument(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_FunctionImportWithParameterShouldNotBeIncludedInServiceDocument, p0); + } + + /// + /// A string like "The function '{0}' must specify a return type." + /// + internal static string EdmModel_Validator_Semantic_FunctionMustHaveReturnType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_FunctionMustHaveReturnType, p0); + } + + /// + /// A string like "The UrlEscape function '{0}' must be a bound function." + /// + internal static string EdmModel_Validator_Semantic_UrlEscapeFunctionMustBoundFunction(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_UrlEscapeFunctionMustBoundFunction, p0); + } + + /// + /// A string like "The UrlEscape function '{0}' must have and only have one 'Edm.String' parameter." + /// + internal static string EdmModel_Validator_Semantic_UrlEscapeFunctionMustHaveOneStringParameter(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_UrlEscapeFunctionMustHaveOneStringParameter, p0); + } + + /// + /// A string like "Each parameter name in a operation must be unique. The parameter name '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_ParameterNameAlreadyDefinedDuplicate(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ParameterNameAlreadyDefinedDuplicate, p0); + } + + /// + /// A string like "Each member name in an EntityContainer must be unique. A member with name '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_DuplicateEntityContainerMemberName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DuplicateEntityContainerMemberName, p0); + } + + /// + /// A string like "The function '{0}' has a different return type than other function overloads with the same name. Functions with the same name must have the same return type." + /// + internal static string EdmModel_Validator_Semantic_UnboundFunctionOverloadHasIncorrectReturnType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_UnboundFunctionOverloadHasIncorrectReturnType, p0); + } + + /// + /// A string like "The unbound operation '{0}' has an entity set path defined. Entity set path can only be defined on bound operations." + /// + internal static string EdmModel_Validator_Semantic_OperationCannotHaveEntitySetPathWithUnBoundOperation(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationCannotHaveEntitySetPathWithUnBoundOperation, p0); + } + + /// + /// A string like "The attribute '{0}' has an invalid value. The path doesn't contain the binding parameter name." + /// + internal static string EdmModel_Validator_Semantic_InvalidEntitySetPathMissingBindingParameterName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidEntitySetPathMissingBindingParameterName, p0); + } + + /// + /// A string like "The attribute '{0}' is invalid. The first item of the path '{2}' is '{3}' which does not match the first parameter name {3}. The first segment of the entity set path is required to be the name of the first parameter." + /// + internal static string EdmModel_Validator_Semantic_InvalidEntitySetPathWithFirstPathParameterNotMatchingFirstParameterName(object p0, object p1, object p2, object p3) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidEntitySetPathWithFirstPathParameterNotMatchingFirstParameterName, p0, p1, p2, p3); + } + + /// + /// A string like "The attribute '{0}' has an invalid value. The path '{1}' has a type cast segment '{2}' that is not an entity type." + /// + internal static string EdmModel_Validator_Semantic_InvalidEntitySetPathTypeCastSegmentMustBeEntityType(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidEntitySetPathTypeCastSegmentMustBeEntityType, p0, p1, p2); + } + + /// + /// A string like "The attribute '{0}' has an invalid value. The path '{1}' has a navigation property segment '{2}' that is unknown." + /// + internal static string EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownNavigationProperty(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownNavigationProperty, p0, p1, p2); + } + + /// + /// A string like "The attribute '{0}' has an invalid value. The path '{1}' has a type cast segment that doesn't derive from the entity type '{2}' for type segment '{3}'." + /// + internal static string EdmModel_Validator_Semantic_InvalidEntitySetPathInvalidTypeCastSegment(object p0, object p1, object p2, object p3) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidEntitySetPathInvalidTypeCastSegment, p0, p1, p2, p3); + } + + /// + /// A string like "The attribute '{0}' has an invalid value. The path '{1}' has a binding parameter that references a type '{2}' that is not an entity type." + /// + internal static string EdmModel_Validator_Semantic_InvalidEntitySetPathWithNonEntityBindingParameter(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidEntitySetPathWithNonEntityBindingParameter, p0, p1, p2); + } + + /// + /// A string like "The attribute '{0}' has an invalid value. The path '{1}' has a type cast segment '{2}' that cannot be found in the model." + /// + internal static string EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownTypeCastSegment(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidEntitySetPathUnknownTypeCastSegment, p0, p1, p2); + } + + /// + /// A string like "The operation '{0}' has an entity set path and with an invalid return type. The return type is required to be an entity type or a collection of entity type." + /// + internal static string EdmModel_Validator_Semantic_OperationWithEntitySetPathReturnTypeInvalid(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationWithEntitySetPathReturnTypeInvalid, p0); + } + + /// + /// A string like "The operation '{0}' entity set path determined entity type '{1}' is not assignable to the return type '{2}'." + /// + internal static string EdmModel_Validator_Semantic_OperationWithEntitySetPathAndReturnTypeTypeNotAssignable(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationWithEntitySetPathAndReturnTypeTypeNotAssignable, p0, p1, p2); + } + + /// + /// A string like "The operation '{0}' entity set path was determined to be a reference property but the return type is a collection." + /// + internal static string EdmModel_Validator_Semantic_OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType, p0); + } + + /// + /// A string like "An element with the name '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_SchemaElementNameAlreadyDefined(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_SchemaElementNameAlreadyDefined, p0); + } + + /// + /// A string like "The member name '{0}' cannot be used in a type with the same name. Member names cannot be the same as their enclosing type." + /// + internal static string EdmModel_Validator_Semantic_InvalidMemberNameMatchesTypeName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidMemberNameMatchesTypeName, p0); + } + + /// + /// A string like "Each property name in a type must be unique. Property name '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_PropertyNameAlreadyDefined(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_PropertyNameAlreadyDefined, p0); + } + + /// + /// A string like "The base type kind of a structured type must be the same as its derived type." + /// + internal static string EdmModel_Validator_Semantic_BaseTypeMustHaveSameTypeKind { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BaseTypeMustHaveSameTypeKind); + } + } + + /// + /// A string like "The base type of open type '{0}' is not open type." + /// + internal static string EdmModel_Validator_Semantic_BaseTypeOfOpenTypeMustBeOpen(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BaseTypeOfOpenTypeMustBeOpen, p0); + } + + /// + /// A string like "The key property '{0}' must belong to the entity '{1}'." + /// + internal static string EdmModel_Validator_Semantic_KeyPropertyMustBelongToEntity(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_KeyPropertyMustBelongToEntity, p0, p1); + } + + /// + /// A string like "The 'Edm.PrimitiveType' cannot be used as the type of a key property '{0}' of an entity type '{1}'." + /// + internal static string EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsTypeOfKey(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsTypeOfKey, p0, p1); + } + + /// + /// A string like "The 'Edm.PrimitiveType' cannot be used as the underlying type of '{0}' type '{1}'." + /// + internal static string EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsUnderlyingType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsUnderlyingType, p0, p1); + } + + /// + /// A string like "The dependent property '{0}' must belong to the dependent entity '{1}'." + /// + internal static string EdmModel_Validator_Semantic_DependentPropertiesMustBelongToDependentEntity(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DependentPropertiesMustBelongToDependentEntity, p0, p1); + } + + /// + /// A string like "The property '{0}' cannot belong to a type other than its declaring type." + /// + internal static string EdmModel_Validator_Semantic_DeclaringTypeMustBeCorrect(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DeclaringTypeMustBeCorrect, p0); + } + + /// + /// A string like "The named type '{0}' could not be found from the model being validated." + /// + internal static string EdmModel_Validator_Semantic_InaccessibleType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InaccessibleType, p0); + } + + /// + /// A string like "The named type '{0}' is ambiguous from the model being validated." + /// + internal static string EdmModel_Validator_Semantic_AmbiguousType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_AmbiguousType, p0); + } + + /// + /// A string like "The type of the navigation property '{0}' is invalid. The navigation target type must be an entity type or a collection of entity type. The navigation target entity type must match the declaring type of the partner property." + /// + internal static string EdmModel_Validator_Semantic_InvalidNavigationPropertyType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidNavigationPropertyType, p0); + } + + /// + /// A string like "The target multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is the same or inherits from the target entity type, then the property represents a recursive containment and it must have an optional target represented by a collection or a nullable entity type." + /// + internal static string EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional, p0); + } + + /// + /// A string like "The source multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is the same or inherits from the target entity type, then the property represents a recursive containment and the multiplicity of the navigation source must be zero or one." + /// + internal static string EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne, p0); + } + + /// + /// A string like "The source multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is not the same as the target entity type, then the property represents a non-recursive containment and the multiplicity of the navigation source must be exactly one." + /// + internal static string EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne, p0); + } + + /// + /// A string like "The complex type '{0}' is invalid. A complex type must contain at least one property." + /// + internal static string EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties, p0); + } + + /// + /// A string like "The dependent property '{0}' of navigation property '{1}' is a duplicate." + /// + internal static string EdmModel_Validator_Semantic_DuplicateDependentProperty(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DuplicateDependentProperty, p0, p1); + } + + /// + /// A string like "The scale value can range from 0 through the specified precision value." + /// + internal static string EdmModel_Validator_Semantic_ScaleOutOfRange { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ScaleOutOfRange); + } + } + + /// + /// A string like "Precision cannot be negative." + /// + internal static string EdmModel_Validator_Semantic_PrecisionOutOfRange { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_PrecisionOutOfRange); + } + } + + /// + /// A string like "The max length facet specifies the maximum length of an instance of the string type. For unicode equal to 'true', the max length can range from 1 to 2^30, or if 'false', 1 to 2^31." + /// + internal static string EdmModel_Validator_Semantic_StringMaxLengthOutOfRange { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_StringMaxLengthOutOfRange); + } + } + + /// + /// A string like "Max length can range from 1 to 2^31." + /// + internal static string EdmModel_Validator_Semantic_MaxLengthOutOfRange { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_MaxLengthOutOfRange); + } + } + + /// + /// A string like "The value of enum member '{0}' exceeds the range of its underlying type." + /// + internal static string EdmModel_Validator_Semantic_EnumMemberValueOutOfRange(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EnumMemberValueOutOfRange, p0); + } + + /// + /// A string like "Each member name of an enum type must be unique. Enum member name '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_EnumMemberNameAlreadyDefined(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EnumMemberNameAlreadyDefined, p0); + } + + /// + /// A string like "Only entity types can be open types." + /// + internal static string EdmModel_Validator_Semantic_OpenTypesSupportedForEntityTypesOnly { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OpenTypesSupportedForEntityTypesOnly); + } + } + + /// + /// A string like "The string reference is invalid because if 'IsUnbounded' is true 'MaxLength' must be null." + /// + internal static string EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull); + } + } + + /// + /// A string like "The declared name and namespace of the annotation must match the name and namespace of its xml value." + /// + internal static string EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm); + } + } + + /// + /// A string like "The value of an annotation marked to be serialized as an xml element must have a well-formed xml value." + /// + internal static string EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml); + } + } + + /// + /// A string like "The value of an annotation marked to be serialized as an xml element must be IEdmStringValue." + /// + internal static string EdmModel_Validator_Semantic_InvalidElementAnnotationNotIEdmStringValue { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidElementAnnotationNotIEdmStringValue); + } + } + + /// + /// A string like "The value of an annotation marked to be serialized as an xml element must be a string representing an xml element with non-empty name and namespace." + /// + internal static string EdmModel_Validator_Semantic_InvalidElementAnnotationNullNamespaceOrName { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InvalidElementAnnotationNullNamespaceOrName); + } + } + + /// + /// A string like "Cannot assert the nullable type '{0}' as a non-nullable type." + /// + internal static string EdmModel_Validator_Semantic_CannotAssertNullableTypeAsNonNullableType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_CannotAssertNullableTypeAsNonNullableType, p0); + } + + /// + /// A string like "Cannot promote the primitive type '{0}' to the specified primitive type '{1}'." + /// + internal static string EdmModel_Validator_Semantic_ExpressionPrimitiveKindCannotPromoteToAssertedType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ExpressionPrimitiveKindCannotPromoteToAssertedType, p0, p1); + } + + /// + /// A string like "Null value cannot have a non-nullable type." + /// + internal static string EdmModel_Validator_Semantic_NullCannotBeAssertedToBeANonNullableType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NullCannotBeAssertedToBeANonNullableType); + } + } + + /// + /// A string like "The type of the expression is incompatible with the asserted type." + /// + internal static string EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType); + } + } + + /// + /// A string like "A collection expression is incompatible with a non-collection type." + /// + internal static string EdmModel_Validator_Semantic_CollectionExpressionNotValidForNonCollectionType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_CollectionExpressionNotValidForNonCollectionType); + } + } + + /// + /// A string like "A primitive expression is incompatible with a non-primitive type." + /// + internal static string EdmModel_Validator_Semantic_PrimitiveConstantExpressionNotValidForNonPrimitiveType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_PrimitiveConstantExpressionNotValidForNonPrimitiveType); + } + } + + /// + /// A string like "A record expression is incompatible with a non-structured type." + /// + internal static string EdmModel_Validator_Semantic_RecordExpressionNotValidForNonStructuredType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_RecordExpressionNotValidForNonStructuredType); + } + } + + /// + /// A string like "The record expression does not have a constructor for a property named '{0}'." + /// + internal static string EdmModel_Validator_Semantic_RecordExpressionMissingProperty(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_RecordExpressionMissingProperty, p0); + } + + /// + /// A string like "The type of the record expression is not open and does not contain a property named '{0}'." + /// + internal static string EdmModel_Validator_Semantic_RecordExpressionHasExtraProperties(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_RecordExpressionHasExtraProperties, p0); + } + + /// + /// A string like "The annotated element '{0}' has multiple annotations with the term '{1}' and the qualifier '{2}'." + /// + internal static string EdmModel_Validator_Semantic_DuplicateAnnotation(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DuplicateAnnotation, p0, p1, p2); + } + + /// + /// A string like "The function application provides '{0}' arguments, but the function '{1}' expects '{2}' arguments." + /// + internal static string EdmModel_Validator_Semantic_IncorrectNumberOfArguments(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_IncorrectNumberOfArguments, p0, p1, p2); + } + + /// + /// A string like "Each entity container name in a function must be unique. The name '{0}' is already defined." + /// + internal static string EdmModel_Validator_Semantic_DuplicateEntityContainerName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DuplicateEntityContainerName, p0); + } + + /// + /// A string like "The primitive expression is not compatible with the asserted type." + /// + internal static string EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType); + } + } + + /// + /// A string like "The enum expression is not compatible with the asserted type." + /// + internal static string EdmModel_Validator_Semantic_ExpressionEnumKindNotValidForAssertedType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ExpressionEnumKindNotValidForAssertedType); + } + } + + /// + /// A string like "The value of the integer constant is out of range for the asserted type." + /// + internal static string EdmModel_Validator_Semantic_IntegerConstantValueOutOfRange { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_IntegerConstantValueOutOfRange); + } + } + + /// + /// A string like "The value of the string constant is '{0}' characters long, but the max length of its type is '{1}'." + /// + internal static string EdmModel_Validator_Semantic_StringConstantLengthOutOfRange(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_StringConstantLengthOutOfRange, p0, p1); + } + + /// + /// A string like "The value of the binary constant is '{0}' characters long, but the max length of its type is '{1}'." + /// + internal static string EdmModel_Validator_Semantic_BinaryConstantLengthOutOfRange(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BinaryConstantLengthOutOfRange, p0, p1); + } + + /// + /// A string like "A type without other errors must not have kind of none." + /// + internal static string EdmModel_Validator_Semantic_TypeMustNotHaveKindOfNone { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_TypeMustNotHaveKindOfNone); + } + } + + /// + /// A string like "A schema element without other errors must not have kind of none. The kind of schema element '{0}' is none." + /// + internal static string EdmModel_Validator_Semantic_SchemaElementMustNotHaveKindOfNone(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_SchemaElementMustNotHaveKindOfNone, p0); + } + + /// + /// A string like "A property without other errors must not have kind of none. The kind of property '{0}' is none." + /// + internal static string EdmModel_Validator_Semantic_PropertyMustNotHaveKindOfNone(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_PropertyMustNotHaveKindOfNone, p0); + } + + /// + /// A string like "A primitive type without other errors must not have kind of none. The kind of primitive type '{0}' is none." + /// + internal static string EdmModel_Validator_Semantic_PrimitiveTypeMustNotHaveKindOfNone(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_PrimitiveTypeMustNotHaveKindOfNone, p0); + } + + /// + /// A string like "An entity container element without other errors must not have kind of none. The kind of entity container element '{0}' is none." + /// + internal static string EdmModel_Validator_Semantic_EntityContainerElementMustNotHaveKindOfNone(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EntityContainerElementMustNotHaveKindOfNone, p0); + } + + /// + /// A string like "The entity set '{0}' should have only a single mapping for the property '{1}'." + /// + internal static string EdmModel_Validator_Semantic_DuplicateNavigationPropertyMapping(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DuplicateNavigationPropertyMapping, p0, p1); + } + + /// + /// A string like "The binding of the entity set or singleton '{0}' on navigation property '{1}' is invalid, the binding of bidirectional navigation property must be bidirectional if specified." + /// + internal static string EdmModel_Validator_Semantic_NavigationMappingMustBeBidirectional(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationMappingMustBeBidirectional, p0, p1); + } + + /// + /// A string like "The entity set '{0}' is invalid because it is contained by more than one navigation property." + /// + internal static string EdmModel_Validator_Semantic_EntitySetCanOnlyBeContainedByASingleNavigationProperty(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EntitySetCanOnlyBeContainedByASingleNavigationProperty, p0); + } + + /// + /// A string like "The type annotation is missing a binding for the property '{0}'." + /// + internal static string EdmModel_Validator_Semantic_TypeAnnotationMissingRequiredProperty(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_TypeAnnotationMissingRequiredProperty, p0); + } + + /// + /// A string like "They type of the type annotation is not open, and does not contain a property named '{0}'." + /// + internal static string EdmModel_Validator_Semantic_TypeAnnotationHasExtraProperties(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_TypeAnnotationHasExtraProperties, p0); + } + + /// + /// A string like "The underlying type of '{0}' is not valid. The underlying type of an enum type must be an integral type." + /// + internal static string EdmModel_Validator_Semantic_EnumMustHaveIntegralUnderlyingType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EnumMustHaveIntegralUnderlyingType, p0); + } + + /// + /// A string like "The term '{0}' could not be found from the model being validated." + /// + internal static string EdmModel_Validator_Semantic_InaccessibleTerm(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InaccessibleTerm, p0); + } + + /// + /// A string like "The target '{0}' could not be found from the model being validated." + /// + internal static string EdmModel_Validator_Semantic_InaccessibleTarget(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_InaccessibleTarget, p0); + } + + /// + /// A string like "The target '{0}' of the annotation is not allowed in the AppliesTo '{1}' of the term '{2}'."." + /// + internal static string EdmModel_Validator_Semantic_VocabularyAnnotationApplyToNotAllowedAnnotatable(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_VocabularyAnnotationApplyToNotAllowedAnnotatable, p0, p1, p2); + } + + /// + /// A string like "An element already has a direct annotation with the namespace '{0}' and name '{1}'." + /// + internal static string EdmModel_Validator_Semantic_ElementDirectValueAnnotationFullNameMustBeUnique(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ElementDirectValueAnnotationFullNameMustBeUnique, p0, p1); + } + + /// + /// A string like "The association set '{0}' cannot assume an entity set for the role '{2}' because there are no entity sets for the role type '{1}'." + /// + internal static string EdmModel_Validator_Semantic_NoEntitySetsFoundForType(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NoEntitySetsFoundForType, p0, p1, p2); + } + + /// + /// A string like "The association set '{0}' must specify an entity set for the role '{2}' because there are multiple entity sets for the role type '{1}'." + /// + internal static string EdmModel_Validator_Semantic_CannotInferEntitySetWithMultipleSetsPerType(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_CannotInferEntitySetWithMultipleSetsPerType, p0, p1, p2); + } + + /// + /// A string like "Because the navigation property '{0}' is recursive, the mapping from the entity set '{1}' must point back to itself." + /// + internal static string EdmModel_Validator_Semantic_EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet, p0, p1); + } + + /// + /// A string like "The navigation property '{0}' is invalid because it indirectly contains itself." + /// + internal static string EdmModel_Validator_Semantic_NavigationPropertyEntityMustNotIndirectlyContainItself(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationPropertyEntityMustNotIndirectlyContainItself, p0); + } + + /// + /// A string like "The path cannot be resolved in the given context. The segment '{0}' failed to resolve." + /// + internal static string EdmModel_Validator_Semantic_PathIsNotValidForTheGivenContext(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_PathIsNotValidForTheGivenContext, p0); + } + + /// + /// A string like "The entity set or singleton '{1}' is not a valid destination for the navigation property '{0}' because it cannot hold an element of the target entity type." + /// + internal static string EdmModel_Validator_Semantic_NavigationPropertyMappingMustPointToValidTargetForProperty(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationPropertyMappingMustPointToValidTargetForProperty, p0, p1); + } + + /// + /// A string like "The bound function '{0}' is a duplicate of other bound functions. For bound functions the combination of the namespace, name, binding parameter type and unordered set of parameter names uniquely identifies a bound function." + /// + internal static string EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterNames(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterNames, p0); + } + + /// + /// A string like "The bound function '{0}' is a duplicate of other bound functions. For bound functions the combination of the namespace, name, binding parameter type and ordered set of parameter types uniquely identifies a bound function." + /// + internal static string EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterTypes(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterTypes, p0); + } + + /// + /// A string like "The unbound function '{0}' is a duplicate of other unbound functions. For unbound functions the combination of the namespace, name and unordered set of parameter names uniquely identifies an unbound function." + /// + internal static string EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterNames(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterNames, p0); + } + + /// + /// A string like "The unbound function '{0}' is a duplicate of other unbound functions. For unbound functions the combination of the namespace, name and ordered set of parameter types uniquely identifies an unbound function." + /// + internal static string EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterTypes(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterTypes, p0); + } + + /// + /// A string like "The bound action '{0}' is a duplicate of other bound actions. For bound actions the combination of the namespace, name, and binding parameter type uniquely identifies an bound action." + /// + internal static string EdmModel_Validator_Semantic_ModelDuplicateBoundActions(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ModelDuplicateBoundActions, p0); + } + + /// + /// A string like "The unbound action '{0}' is a duplicate of other unbound actions. For unbound actions the combination of the namespace, and name uniquely identifies an unbound action." + /// + internal static string EdmModel_Validator_Semantic_ModelDuplicateUnBoundActions(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_ModelDuplicateUnBoundActions, p0); + } + + /// + /// A string like "The bound function overload '{0}' does not have the same return type as other function overloads. Expected type '{1}'." + /// + internal static string EdmModel_Validator_Semantic_BoundFunctionOverloadsMustHaveSameReturnType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_BoundFunctionOverloadsMustHaveSameReturnType, p0, p1); + } + + /// + /// A string like "The type '{0}' of the entity set '{1}' is not valid, it must be collection of entity type." + /// + internal static string EdmModel_Validator_Semantic_EntitySetTypeMustBeCollectionOfEntityType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EntitySetTypeMustBeCollectionOfEntityType, p0, p1); + } + + /// + /// A string like "The type '{0}' of the singleton '{1}' is not valid, it must be entity type." + /// + internal static string EdmModel_Validator_Semantic_SingletonTypeMustBeEntityType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_SingletonTypeMustBeEntityType, p0, p1); + } + + /// + /// A string like "The navigation property mapping '{0}' is invalid because its type is collection but target to a singleton '{1}'." + /// + internal static string EdmModel_Validator_Semantic_NavigationPropertyOfCollectionTypeMustNotTargetToSingleton(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_NavigationPropertyOfCollectionTypeMustNotTargetToSingleton, p0, p1); + } + + /// + /// A string like "The type '{0}' cannot be the base type of an '{1}' type '{2}'." + /// + internal static string EdmModel_Validator_Semantic_StructuredTypeBaseTypeCannotBeAbstractType(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_StructuredTypeBaseTypeCannotBeAbstractType, p0, p1, p2); + } + + /// + /// A string like "The type '{0}' cannot be used as the type of a property '{1}'." + /// + internal static string EdmModel_Validator_Semantic_PropertyTypeCannotBeCollectionOfAbstractType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_PropertyTypeCannotBeCollectionOfAbstractType, p0, p1); + } + + /// + /// A string like "The type '{0}' cannot be used as the return type of a function '{1}'." + /// + internal static string EdmModel_Validator_Semantic_OperationReturnTypeCannotBeCollectionOfAbstractType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_OperationReturnTypeCannotBeCollectionOfAbstractType, p0, p1); + } + + /// + /// A string like "The type 'Edm.EntityType' cannot be used as the type of a singleton '{0}' in an entity container." + /// + internal static string EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfSingleton(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfSingleton, p0); + } + + /// + /// A string like "The type 'Edm.EntityType' cannot be used as the type of an entity set '{0}' in an entity container." + /// + internal static string EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfEntitySet(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfEntitySet, p0); + } + + /// + /// A string like "The declaring type '{0}' of {1} '{2}' cannot include path type property." + /// + internal static string EdmModel_Validator_Semantic_DeclaringTypeOfNavigationSourceCannotHavePathProperty(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_DeclaringTypeOfNavigationSourceCannotHavePathProperty, p0, p1, p2); + } + + /// + /// A string like "The type '{0}' of navigation property '{1}' on declaring type '{2}' cannot include path type property." + /// + internal static string EdmModel_Validator_Semantic_TypeOfNavigationPropertyCannotHavePathProperty(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Semantic_TypeOfNavigationPropertyCannotHavePathProperty, p0, p1, p2); + } + + /// + /// A string like "The name is missing or not valid." + /// + internal static string EdmModel_Validator_Syntactic_MissingName { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_MissingName); + } + } + + /// + /// A string like "The specified name must not be longer than 480 characters: '{0}'." + /// + internal static string EdmModel_Validator_Syntactic_EdmModel_NameIsTooLong(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_EdmModel_NameIsTooLong, p0); + } + + /// + /// A string like "The specified name is not allowed: '{0}'." + /// + internal static string EdmModel_Validator_Syntactic_EdmModel_NameIsNotAllowed(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_EdmModel_NameIsNotAllowed, p0); + } + + /// + /// A string like "The namespace name is missing or not valid." + /// + internal static string EdmModel_Validator_Syntactic_MissingNamespaceName { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_MissingNamespaceName); + } + } + + /// + /// A string like "The specified name must not be longer than 480 characters: '{0}'." + /// + internal static string EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsTooLong(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsTooLong, p0); + } + + /// + /// A string like "The specified namespace name is not allowed: '{0}'." + /// + internal static string EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsNotAllowed(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsNotAllowed, p0); + } + + /// + /// A string like "The value of the property '{0}.{1}' must not be null." + /// + internal static string EdmModel_Validator_Syntactic_PropertyMustNotBeNull(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_PropertyMustNotBeNull, p0, p1); + } + + /// + /// A string like "The property '{0}.{1}' of type '{2}' has value '{3}' that is not a valid enum member." + /// + internal static string EdmModel_Validator_Syntactic_EnumPropertyValueOutOfRange(object p0, object p1, object p2, object p3) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_EnumPropertyValueOutOfRange, p0, p1, p2, p3); + } + + /// + /// A string like "An object with the value '{0}' of the '{1}.{2}' property must implement '{3}' interface." + /// + internal static string EdmModel_Validator_Syntactic_InterfaceKindValueMismatch(object p0, object p1, object p2, object p3) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_InterfaceKindValueMismatch, p0, p1, p2, p3); + } + + /// + /// A string like "An object implementing '{0}' interface has type definition of kind '{1}'. The type reference interface must match to the kind of the definition." + /// + internal static string EdmModel_Validator_Syntactic_TypeRefInterfaceTypeKindValueMismatch(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_TypeRefInterfaceTypeKindValueMismatch, p0, p1); + } + + /// + /// A string like "The value '{0}' of the property '{1}.{2}' is not semantically valid. A semantically valid model must not contain elements of kind '{0}'." + /// + internal static string EdmModel_Validator_Syntactic_InterfaceKindValueUnexpected(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_InterfaceKindValueUnexpected, p0, p1, p2); + } + + /// + /// A string like "The value of the enumeration the property '{0}.{1}' contains a null element. Enumeration properties must not contain null elements." + /// + internal static string EdmModel_Validator_Syntactic_EnumerableMustNotHaveNullElements(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_EnumerableMustNotHaveNullElements, p0, p1); + } + + /// + /// A string like "The partner of the navigation property '{0}' must not be the same property, and must point back to the navigation property." + /// + internal static string EdmModel_Validator_Syntactic_NavigationPartnerInvalid(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_NavigationPartnerInvalid, p0); + } + + /// + /// A string like "The chain of base types of type '{0}' is cyclic." + /// + internal static string EdmModel_Validator_Syntactic_InterfaceCriticalCycleInTypeHierarchy(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmModel_Validator_Syntactic_InterfaceCriticalCycleInTypeHierarchy, p0); + } + + /// + /// A string like "Single file provided but model cannot be serialized into single file." + /// + internal static string Serializer_SingleFileExpected { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Serializer_SingleFileExpected); + } + } + + /// + /// A string like "Unknown Edm version." + /// + internal static string Serializer_UnknownEdmVersion { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Serializer_UnknownEdmVersion); + } + } + + /// + /// A string like "Unknown Edmx version." + /// + internal static string Serializer_UnknownEdmxVersion { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Serializer_UnknownEdmxVersion); + } + } + + /// + /// A string like "The operation import '{0}' could not be serialized because its return type cannot be represented inline." + /// + internal static string Serializer_NonInlineOperationImportReturnType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Serializer_NonInlineOperationImportReturnType, p0); + } + + /// + /// A string like "A referenced type can not be serialized with an invalid name. The name '{0}' is invalid." + /// + internal static string Serializer_ReferencedTypeMustHaveValidName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Serializer_ReferencedTypeMustHaveValidName, p0); + } + + /// + /// A string like "The annotation can not be serialized with an invalid target name. The name '{0}' is invalid." + /// + internal static string Serializer_OutOfLineAnnotationTargetMustHaveValidName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Serializer_OutOfLineAnnotationTargetMustHaveValidName, p0); + } + + /// + /// A string like "No CSDL is written because no schema elements could be produced. This is likely because the model is empty." + /// + internal static string Serializer_NoSchemasProduced { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Serializer_NoSchemasProduced); + } + } + + /// + /// A string like "{0} does not contain a schema definition, or the XmlReader provided started at the end of the file." + /// + internal static string XmlParser_EmptyFile(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_EmptyFile, p0); + } + + /// + /// A string like "The source XmlReader does not contain a schema definition or started at the end of the file." + /// + internal static string XmlParser_EmptySchemaTextReader { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_EmptySchemaTextReader); + } + } + + /// + /// A string like "Required schema attribute '{0}' is not present on element '{1}'." + /// + internal static string XmlParser_MissingAttribute(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_MissingAttribute, p0, p1); + } + + /// + /// A string like "The current schema element does not support text '{0}'." + /// + internal static string XmlParser_TextNotAllowed(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_TextNotAllowed, p0); + } + + /// + /// A string like "The attribute '{0}' was not expected in the given context." + /// + internal static string XmlParser_UnexpectedAttribute(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_UnexpectedAttribute, p0); + } + + /// + /// A string like "The schema element '{0}' was not expected in the given context." + /// + internal static string XmlParser_UnexpectedElement(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_UnexpectedElement, p0); + } + + /// + /// A string like "Unused schema element: '{0}'." + /// + internal static string XmlParser_UnusedElement(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_UnusedElement, p0); + } + + /// + /// A string like "Unexpected XML node type: {0}." + /// + internal static string XmlParser_UnexpectedNodeType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_UnexpectedNodeType, p0); + } + + /// + /// A string like "The element '{0}' was unexpected for the root element. The root element should be {1}." + /// + internal static string XmlParser_UnexpectedRootElement(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_UnexpectedRootElement, p0, p1); + } + + /// + /// A string like "The namespace '{0}' is invalid. The root element is expected to belong to one of the following namespaces: '{1}'." + /// + internal static string XmlParser_UnexpectedRootElementWrongNamespace(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_UnexpectedRootElementWrongNamespace, p0, p1); + } + + /// + /// A string like "The root element has no namespace. The root element is expected to belong to one of the following namespaces: '{0}'." + /// + internal static string XmlParser_UnexpectedRootElementNoNamespace(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.XmlParser_UnexpectedRootElementNoNamespace, p0); + } + + /// + /// A string like "The {0} '{1}' is invalid. The entitySetPath value is not allowed when IsBound attribute is false." + /// + internal static string CsdlParser_InvalidEntitySetPathWithUnboundAction(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidEntitySetPathWithUnboundAction, p0, p1); + } + + /// + /// A string like "The alias '{0}' is not a valid simple name." + /// + internal static string CsdlParser_InvalidAlias(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidAlias, p0); + } + + /// + /// A string like "The delete action '{0}' is not valid. Action must be: 'None', 'Cascade', or 'Restrict'." + /// + internal static string CsdlParser_InvalidDeleteAction(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidDeleteAction, p0); + } + + /// + /// A string like "An XML attribute or sub-element representing an EDM type is missing." + /// + internal static string CsdlParser_MissingTypeAttributeOrElement { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_MissingTypeAttributeOrElement); + } + } + + /// + /// A string like "There is no Role with name '{0}' defined in relationship '{1}'." + /// + internal static string CsdlParser_InvalidEndRoleInRelationshipConstraint(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidEndRoleInRelationshipConstraint, p0, p1); + } + + /// + /// A string like "The multiplicity '{0}' is not valid. Multiplicity must be: '*', '0..1', or '1'." + /// + internal static string CsdlParser_InvalidMultiplicity(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidMultiplicity, p0); + } + + /// + /// A string like "Referential constraints requires one dependent role. Multiple dependent roles were specified for this referential constraint." + /// + internal static string CsdlParser_ReferentialConstraintRequiresOneDependent { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_ReferentialConstraintRequiresOneDependent); + } + } + + /// + /// A string like "Referential constraints requires one principal role. Multiple principal roles were specified for this referential constraint." + /// + internal static string CsdlParser_ReferentialConstraintRequiresOnePrincipal { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_ReferentialConstraintRequiresOnePrincipal); + } + } + + /// + /// A string like "If expression must contain 3 operands, the first being a boolean test, the second being being evaluated if the first is true, and the third being evaluated if the first is false." + /// + internal static string CsdlParser_InvalidIfExpressionIncorrectNumberOfOperands { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidIfExpressionIncorrectNumberOfOperands); + } + } + + /// + /// A string like "The IsType expression must contain 1 operand." + /// + internal static string CsdlParser_InvalidIsTypeExpressionIncorrectNumberOfOperands { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidIsTypeExpressionIncorrectNumberOfOperands); + } + } + + /// + /// A string like "The Cast expression must contain 1 operand." + /// + internal static string CsdlParser_InvalidCastExpressionIncorrectNumberOfOperands { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidCastExpressionIncorrectNumberOfOperands); + } + } + + /// + /// A string like "The LabeledElement expression must contain 1 operand." + /// + internal static string CsdlParser_InvalidLabeledElementExpressionIncorrectNumberOfOperands { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidLabeledElementExpressionIncorrectNumberOfOperands); + } + } + + /// + /// A string like "The type name '{0}' is invalid. The type name must be that of a primitive type, a fully qualified name or an inline 'Collection' or 'Ref' type." + /// + internal static string CsdlParser_InvalidTypeName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidTypeName, p0); + } + + /// + /// A string like "The qualified name '{0}' is invalid. A qualified name must have a valid namespace or alias, and a valid name." + /// + internal static string CsdlParser_InvalidQualifiedName(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidQualifiedName, p0); + } + + /// + /// A string like "A model could not be produced because no XML readers were provided." + /// + internal static string CsdlParser_NoReadersProvided { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_NoReadersProvided); + } + } + + /// + /// A string like "A model could not be produced because one of the XML readers was null." + /// + internal static string CsdlParser_NullXmlReader { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_NullXmlReader); + } + } + + /// + /// A string like "'{0}' is not a valid entity set path." + /// + internal static string CsdlParser_InvalidEntitySetPath(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidEntitySetPath, p0); + } + + /// + /// A string like "'{0}' is not a valid enum member path." + /// + internal static string CsdlParser_InvalidEnumMemberPath(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_InvalidEnumMemberPath, p0); + } + + /// + /// A string like "The 'Nullable' attribute cannot be specified for a navigation property with collection type." + /// + internal static string CsdlParser_CannotSpecifyNullableAttributeForNavigationPropertyWithCollectionType { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_CannotSpecifyNullableAttributeForNavigationPropertyWithCollectionType); + } + } + + /// + /// A string like "Metadata document cannot have more than one entity container." + /// + internal static string CsdlParser_MetadataDocumentCannotHaveMoreThanOneEntityContainer { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlParser_MetadataDocumentCannotHaveMoreThanOneEntityContainer); + } + } + + /// + /// A string like " There was a mismatch in the principal and dependent ends of the referential constraint." + /// + internal static string CsdlSemantics_ReferentialConstraintMismatch { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlSemantics_ReferentialConstraintMismatch); + } + } + + /// + /// A string like "The enumeration member must have a value." + /// + internal static string CsdlSemantics_EnumMemberMustHaveValue { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlSemantics_EnumMemberMustHaveValue); + } + } + + /// + /// A string like "The annotation target '{0}' could not be resolved because it cannot refer to an annotatable element." + /// + internal static string CsdlSemantics_ImpossibleAnnotationsTarget(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlSemantics_ImpossibleAnnotationsTarget, p0); + } + + /// + /// A string like "The schema '{0}' contains the alias '{1}' more than once." + /// + internal static string CsdlSemantics_DuplicateAlias(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.CsdlSemantics_DuplicateAlias, p0, p1); + } + + /// + /// A string like "The EDMX version specified in the 'Version' attribute does not match the version corresponding to the namespace of the 'Edmx' element." + /// + internal static string EdmxParser_EdmxVersionMismatch { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmxParser_EdmxVersionMismatch); + } + } + + /// + /// A string like "Unexpected {0} element while parsing Edmx. Edmx is expected to have at most one of 'Runtime' or 'DataServices' elements." + /// + internal static string EdmxParser_BodyElement(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmxParser_BodyElement, p0); + } + + /// + /// A string like "edmx:Reference must contain at least one edmx:Includes or edmx:IncludeAnnotations." + /// + internal static string EdmxParser_InvalidReferenceIncorrectNumberOfIncludes { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmxParser_InvalidReferenceIncorrectNumberOfIncludes); + } + } + + /// + /// A string like "Unresolved Uri found in edmx:Reference, getReferencedModelReaderFunc should not return null when the URI is not a well-known schema." + /// + internal static string EdmxParser_UnresolvedReferenceUriInEdmxReference { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmxParser_UnresolvedReferenceUriInEdmxReference); + } + } + + /// + /// A string like "Encountered the following errors when parsing the EDMX document: \r\n{0}" + /// + internal static string EdmParseException_ErrorsEncounteredInEdmx(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmParseException_ErrorsEncounteredInEdmx, p0); + } + + /// + /// A string like "The value '{0}' is not a valid boolean. The value must be 'true' or 'false'." + /// + internal static string ValueParser_InvalidBoolean(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidBoolean, p0); + } + + /// + /// A string like "The value '{0}' is not a valid integer. The value must be a valid 32 bit integer." + /// + internal static string ValueParser_InvalidInteger(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidInteger, p0); + } + + /// + /// A string like "The value '{0}' is not a valid integer. The value must be a valid 64 bit integer." + /// + internal static string ValueParser_InvalidLong(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidLong, p0); + } + + /// + /// A string like "The value '{0}' is not a valid floating point value." + /// + internal static string ValueParser_InvalidFloatingPoint(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidFloatingPoint, p0); + } + + /// + /// A string like "The value '{0}' is not a valid integer. The value must be a valid 32 bit integer or 'Max'." + /// + internal static string ValueParser_InvalidMaxLength(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidMaxLength, p0); + } + + /// + /// A string like "The value '{0}' is not a valid SRID. The value must either be a 32 bit integer or 'Variable'." + /// + internal static string ValueParser_InvalidSrid(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidSrid, p0); + } + + /// + /// A string like "The value '{0}' is not a valid scale. The value must either be a 32 bit integer or 'Variable'." + /// + internal static string ValueParser_InvalidScale(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidScale, p0); + } + + /// + /// A string like "The value '{0}' is not a valid Guid." + /// + internal static string ValueParser_InvalidGuid(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidGuid, p0); + } + + /// + /// A string like "The value '{0}' is not a valid decimal." + /// + internal static string ValueParser_InvalidDecimal(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidDecimal, p0); + } + + /// + /// A string like "The value '{0}' is not a valid date time offset value." + /// + internal static string ValueParser_InvalidDateTimeOffset(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidDateTimeOffset, p0); + } + + /// + /// A string like "The value '{0}' is not a valid date time value." + /// + internal static string ValueParser_InvalidDateTime(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidDateTime, p0); + } + + /// + /// A string like "The value '{0}' is not a valid date value." + /// + internal static string ValueParser_InvalidDate(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidDate, p0); + } + + /// + /// A string like "The value '{0}' is not a valid duration value." + /// + internal static string ValueParser_InvalidDuration(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidDuration, p0); + } + + /// + /// A string like "The value '{0}' is not a valid binary value. The value must be a hexadecimal string and must not be prefixed by '0x'." + /// + internal static string ValueParser_InvalidBinary(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidBinary, p0); + } + + /// + /// A string like "The value '{0}' is not a valid TimeOfDay value." + /// + internal static string ValueParser_InvalidTimeOfDay(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.ValueParser_InvalidTimeOfDay, p0); + } + + /// + /// A string like "Invalid multiplicity: '{0}'" + /// + internal static string UnknownEnumVal_Multiplicity(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_Multiplicity, p0); + } + + /// + /// A string like "Invalid schema element kind: '{0}'" + /// + internal static string UnknownEnumVal_SchemaElementKind(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_SchemaElementKind, p0); + } + + /// + /// A string like "Invalid type kind: '{0}'" + /// + internal static string UnknownEnumVal_TypeKind(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_TypeKind, p0); + } + + /// + /// A string like "Invalid primitive kind: '{0}'" + /// + internal static string UnknownEnumVal_PrimitiveKind(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_PrimitiveKind, p0); + } + + /// + /// A string like "Invalid container element kind: '{0}'" + /// + internal static string UnknownEnumVal_ContainerElementKind(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_ContainerElementKind, p0); + } + + /// + /// A string like "Invalid CSDL target: '{0}'" + /// + internal static string UnknownEnumVal_CsdlTarget(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_CsdlTarget, p0); + } + + /// + /// A string like "Invalid property kind: '{0}'" + /// + internal static string UnknownEnumVal_PropertyKind(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_PropertyKind, p0); + } + + /// + /// A string like "Invalid expression kind: '{0}'" + /// + internal static string UnknownEnumVal_ExpressionKind(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.UnknownEnumVal_ExpressionKind, p0); + } + + /// + /// A string like "The name '{0}' is ambiguous." + /// + internal static string Bad_AmbiguousElementBinding(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_AmbiguousElementBinding, p0); + } + + /// + /// A string like "The type '{0}' could not be found." + /// + internal static string Bad_UnresolvedType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedType, p0); + } + + /// + /// A string like "The complex type '{0}' could not be found." + /// + internal static string Bad_UnresolvedComplexType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedComplexType, p0); + } + + /// + /// A string like "The entity type '{0}' could not be found." + /// + internal static string Bad_UnresolvedEntityType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedEntityType, p0); + } + + /// + /// A string like "The primitive type '{0}' could not be found." + /// + internal static string Bad_UnresolvedPrimitiveType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedPrimitiveType, p0); + } + + /// + /// A string like "The operation '{0}' could not be found." + /// + internal static string Bad_UnresolvedOperation(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedOperation, p0); + } + + /// + /// A string like "The operation '{0}' could not be resolved because more than one operation could be used for this application." + /// + internal static string Bad_AmbiguousOperation(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_AmbiguousOperation, p0); + } + + /// + /// A string like "The operation '{0}' could not be resolved because none of the operations with that name take the correct set of parameters." + /// + internal static string Bad_OperationParametersDontMatch(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_OperationParametersDontMatch, p0); + } + + /// + /// A string like "The entity set '{0}' could not be found." + /// + internal static string Bad_UnresolvedEntitySet(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedEntitySet, p0); + } + + /// + /// A string like "The entity container '{0}' could not be found." + /// + internal static string Bad_UnresolvedEntityContainer(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedEntityContainer, p0); + } + + /// + /// A string like "The enum type '{0}' could not be found." + /// + internal static string Bad_UnresolvedEnumType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedEnumType, p0); + } + + /// + /// A string like "The enum member '{0}' could not be found." + /// + internal static string Bad_UnresolvedEnumMember(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedEnumMember, p0); + } + + /// + /// A string like "The property '{0}' could not be found." + /// + internal static string Bad_UnresolvedProperty(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedProperty, p0); + } + + /// + /// A string like "The parameter '{0}' could not be found." + /// + internal static string Bad_UnresolvedParameter(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedParameter, p0); + } + + /// + /// A string like "The return of operation '{0}' could not be found." + /// + internal static string Bad_UnresolvedReturn(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedReturn, p0); + } + + /// + /// A string like "The labeled element '{0}' could not be found." + /// + internal static string Bad_UnresolvedLabeledElement(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedLabeledElement, p0); + } + + /// + /// A string like "The entity '{0}' is invalid because its base type is cyclic." + /// + internal static string Bad_CyclicEntity(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_CyclicEntity, p0); + } + + /// + /// A string like "The complex type '{0}' is invalid because its base type is cyclic." + /// + internal static string Bad_CyclicComplex(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_CyclicComplex, p0); + } + + /// + /// A string like "The entity container '{0}' is invalid because its extends hierarchy is cyclic." + /// + internal static string Bad_CyclicEntityContainer(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_CyclicEntityContainer, p0); + } + + /// + /// A string like "A navigation property could not be found for the path '{0}' starting from the type '{1}'." + /// + internal static string Bad_UnresolvedNavigationPropertyPath(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Bad_UnresolvedNavigationPropertyPath, p0, p1); + } + + /// + /// A string like "The same rule cannot be in the same rule set twice." + /// + internal static string RuleSet_DuplicateRulesExistInRuleSet { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.RuleSet_DuplicateRulesExistInRuleSet); + } + } + + /// + /// A string like "Conversion of EDM values to a CLR type with type {0} is not supported." + /// + internal static string EdmToClr_UnsupportedType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmToClr_UnsupportedType, p0); + } + + /// + /// A string like "Conversion of an EDM structured value is supported only to a CLR class." + /// + internal static string EdmToClr_StructuredValueMappedToNonClass { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmToClr_StructuredValueMappedToNonClass); + } + } + + /// + /// A string like "Cannot initialize a property '{0}' on an object of type '{1}'. The property already has a value." + /// + internal static string EdmToClr_IEnumerableOfTPropertyAlreadyHasValue(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmToClr_IEnumerableOfTPropertyAlreadyHasValue, p0, p1); + } + + /// + /// A string like "An EDM structured value contains multiple values for the property '{0}'. Conversion of an EDM structured value with duplicate property values is not supported." + /// + internal static string EdmToClr_StructuredPropertyDuplicateValue(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmToClr_StructuredPropertyDuplicateValue, p0); + } + + /// + /// A string like "Conversion of an EDM value of the type '{0}' to the CLR type '{1}' is not supported." + /// + internal static string EdmToClr_CannotConvertEdmValueToClrType(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmToClr_CannotConvertEdmValueToClrType, p0, p1); + } + + /// + /// A string like "Conversion of an edm collection value to the CLR type '{0}' is not supported. EDM collection values can be converted to System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IList<T> or System.Collections.Generic.ICollection<T>." + /// + internal static string EdmToClr_CannotConvertEdmCollectionValueToClrType(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmToClr_CannotConvertEdmCollectionValueToClrType, p0); + } + + /// + /// A string like "The type '{0}' of the object returned by the TryCreateObjectInstance delegate is not assignable to the expected type '{1}'." + /// + internal static string EdmToClr_TryCreateObjectInstanceReturnedWrongObject(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmToClr_TryCreateObjectInstanceReturnedWrongObject, p0, p1); + } + + /// + /// A string like "The MIME type annotation must not have a null value." + /// + internal static string EdmUtil_NullValueForMimeTypeAnnotation { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmUtil_NullValueForMimeTypeAnnotation); + } + } + + /// + /// A string like "An annotation of type string was expected for the '{{http://docs.oasis-open.org/odata/ns/metadata}}:{0}' annotation, but an annotation of type '{1}' was found." + /// + internal static string EdmUtil_InvalidAnnotationValue(object p0, object p1) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.EdmUtil_InvalidAnnotationValue, p0, p1); + } + + /// + /// A string like "The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information." + /// + internal static string PlatformHelper_DateTimeOffsetMustContainTimeZone(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.PlatformHelper_DateTimeOffsetMustContainTimeZone, p0); + } + + /// + /// A string like "The added or subtracted value results in an un-representable Date." + /// + internal static string Date_InvalidAddedOrSubtractedResults { + get { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Date_InvalidAddedOrSubtractedResults); + } + } + + /// + /// A string like "The Year '{0}', Month '{1}' and Day '{2}' parameters describe an un-representable Date." + /// + internal static string Date_InvalidDateParameters(object p0, object p1, object p2) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Date_InvalidDateParameters, p0, p1, p2); + } + + /// + /// A string like "String '{0}' was not recognized as a valid Date." + /// + internal static string Date_InvalidParsingString(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Date_InvalidParsingString, p0); + } + + /// + /// A string like "Target object '{0}' is not an instance with type of Date." + /// + internal static string Date_InvalidCompareToTarget(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.Date_InvalidCompareToTarget, p0); + } + + /// + /// A string like "The Hour '{0}', Minute '{1}', Second '{2}' and Millisecond '{3}' parameters describe an un-representable TimeOfDay." + /// + internal static string TimeOfDay_InvalidTimeOfDayParameters(object p0, object p1, object p2, object p3) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.TimeOfDay_InvalidTimeOfDayParameters, p0, p1, p2, p3); + } + + /// + /// A string like "The ticks value '{0}' is out of representable TimeOfDay range." + /// + internal static string TimeOfDay_TicksOutOfRange(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.TimeOfDay_TicksOutOfRange, p0); + } + + /// + /// A string like "The TimeSpan value '{0}' is out of representable TimeOfDay range." + /// + internal static string TimeOfDay_ConvertErrorFromTimeSpan(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.TimeOfDay_ConvertErrorFromTimeSpan, p0); + } + + /// + /// A string like "String '{0}' was not recognized as a valid TimeOfDay." + /// + internal static string TimeOfDay_InvalidParsingString(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.TimeOfDay_InvalidParsingString, p0); + } + + /// + /// A string like "Target object '{0}' is not an instance with type of TimeOfDay." + /// + internal static string TimeOfDay_InvalidCompareToTarget(object p0) { + return Microsoft.OData.Edm.EntityRes.GetString(Microsoft.OData.Edm.EntityRes.TimeOfDay_InvalidCompareToTarget, p0); + } + + } + + /// + /// Strongly-typed and parameterized exception factory. + /// + internal static partial class Error { + + /// + /// The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument. + /// + internal static Exception ArgumentNull(string paramName) { + return new ArgumentNullException(paramName); + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method. + /// + internal static Exception ArgumentOutOfRange(string paramName) { + return new ArgumentOutOfRangeException(paramName); + } + + /// + /// The exception that is thrown when the author has not yet implemented the logic at this point in the program. This can act as an exception based TODO tag. + /// + internal static Exception NotImplemented() { + return new NotImplementedException(); + } + + /// + /// The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality. + /// + internal static Exception NotSupported() { + return new NotSupportedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.tt b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.tt new file mode 100644 index 0000000..3a348af --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.tt @@ -0,0 +1,19 @@ +<#@ include file="..\..\tools\StringResourceGenerator\StringsClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.OData.Edm"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.OData.Edm"; + + // The name of the generated resource class. + public const string ResourceClassName = "EntityRes"; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.OData.Edm.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/DefaultPrimitiveValueConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/DefaultPrimitiveValueConverter.cs new file mode 100644 index 0000000..347141b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/DefaultPrimitiveValueConverter.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System; + using System.Globalization; + + /// + /// The default implementation of primitive value converter for unsigned ints, which: + /// converts UInt16 to Int32, + /// converts UInt32 to Int64, + /// converts UInt64 to Decimal. + /// + internal class DefaultPrimitiveValueConverter : IPrimitiveValueConverter + { + internal static readonly IPrimitiveValueConverter Instance = new DefaultPrimitiveValueConverter(); + + private DefaultPrimitiveValueConverter() + { + } + + public object ConvertToUnderlyingType(object value) + { + if (value is UInt16) + { + return Convert.ToInt32(value, CultureInfo.InvariantCulture); + } + + if (value is UInt32) + { + return Convert.ToInt64(value, CultureInfo.InvariantCulture); + } + + if (value is UInt64) + { + return Convert.ToDecimal(value, CultureInfo.InvariantCulture); + } + + return value; + } + + public object ConvertFromUnderlyingType(object value) + { + if (value is Int32) + { + return Convert.ToUInt16(value, CultureInfo.InvariantCulture); + } + + if (value is Int64) + { + return Convert.ToUInt32(value, CultureInfo.InvariantCulture); + } + + if (value is Decimal) + { + return Convert.ToUInt64(value, CultureInfo.InvariantCulture); + } + + return value; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/IPrimitiveValueConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/IPrimitiveValueConverter.cs new file mode 100644 index 0000000..f1ec5a8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/IPrimitiveValueConverter.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Class for defining a primitive value conversion for a type definition. + /// Suppose a type definition defines a primitive type X (underlying type) as a new type Y, + /// and the type Y has a logically corresponding CLR type Z, + /// the ConvertToUnderlyingType method converts value from Z to X + /// and the ConvertFromUnderlyingType method converts value from X to Z. + /// + public interface IPrimitiveValueConverter + { + /// + /// Converts the given primitive value from the CLR type to the underlying type defined in a type definition. + /// + /// The given CLR value. + /// The converted value of the underlying type. + object ConvertToUnderlyingType(object value); + + /// + /// Converts the given primitive value from the underlying type to the CLR type defined in a type definition. + /// + /// The given value of the CLR type. + /// The converted CLR value. + object ConvertFromUnderlyingType(object value); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PassThroughPrimitiveValueConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PassThroughPrimitiveValueConverter.cs new file mode 100644 index 0000000..df61d7e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PassThroughPrimitiveValueConverter.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// An implementation of primitive value converter that directly pass through the primitive value. + /// + internal class PassThroughPrimitiveValueConverter : IPrimitiveValueConverter + { + internal static readonly IPrimitiveValueConverter Instance = new PassThroughPrimitiveValueConverter(); + + private PassThroughPrimitiveValueConverter() + { + } + + public object ConvertToUnderlyingType(object value) + { + return value; + } + + public object ConvertFromUnderlyingType(object value) + { + return value; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PrimitiveValueConverterConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PrimitiveValueConverterConstants.cs new file mode 100644 index 0000000..4dd101a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/PrimitiveValueConverters/PrimitiveValueConverterConstants.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The constants for primitive value converters. + /// + internal static class PrimitiveValueConverterConstants + { + /// + /// The name of the type definition for UInt16. + /// + internal const string UInt16TypeName = "UInt16"; + + /// + /// The name of the type definition for UInt32. + /// + internal const string UInt32TypeName = "UInt32"; + + /// + /// The name of the type definition for UInt64. + /// + internal const string UInt64TypeName = "UInt64"; + + /// + /// The default underlying type of the type definition for UInt16. + /// + internal const string DefaultUInt16UnderlyingType = "Edm.Int32"; + + /// + /// The default underlying type of the type definition for UInt32. + /// + internal const string DefaultUInt32UnderlyingType = "Edm.Int64"; + + /// + /// The default underlying type of the type definition for UInt64. + /// + internal const string DefaultUInt64UnderlyingType = "Edm.Decimal"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/RegistrationHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/RegistrationHelper.cs new file mode 100644 index 0000000..fc668d2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/RegistrationHelper.cs @@ -0,0 +1,178 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + internal static class RegistrationHelper + { + internal static void RegisterSchemaElement(IEdmSchemaElement element, Dictionary schemaTypeDictionary, Dictionary valueTermDictionary, Dictionary> functionGroupDictionary, Dictionary containerDictionary) + { + string qualifiedName = element.FullName(); + switch (element.SchemaElementKind) + { + case EdmSchemaElementKind.Action: + case EdmSchemaElementKind.Function: + AddOperation((IEdmOperation)element, qualifiedName, functionGroupDictionary); + break; + case EdmSchemaElementKind.TypeDefinition: + AddElement((IEdmSchemaType)element, qualifiedName, schemaTypeDictionary, CreateAmbiguousTypeBinding); + break; + case EdmSchemaElementKind.Term: + AddElement((IEdmTerm)element, qualifiedName, valueTermDictionary, CreateAmbiguousTermBinding); + break; + case EdmSchemaElementKind.EntityContainer: + // Only one entity container can be added. + if (containerDictionary.Count > 0) + { + throw new InvalidOperationException(Edm.Strings.EdmModel_CannotAddMoreThanOneEntityContainerToOneEdmModel); + } + + IEdmEntityContainer container = (IEdmEntityContainer)element; + AddElement(container, qualifiedName, containerDictionary, CreateAmbiguousEntityContainerBinding); + AddElement(container, element.Name, containerDictionary, CreateAmbiguousEntityContainerBinding); + + break; + case EdmSchemaElementKind.None: + throw new InvalidOperationException(Edm.Strings.EdmModel_CannotUseElementWithTypeNone); + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_SchemaElementKind(element.SchemaElementKind)); + } + } + + internal static void RegisterProperty(IEdmProperty element, string name, Dictionary dictionary) + { + AddElement(element, name, dictionary, CreateAmbiguousPropertyBinding); + } + + internal static void AddElement(T element, string name, Dictionary elementDictionary, Func ambiguityCreator) where T : class, IEdmElement + { + T preexisting; + if (elementDictionary.TryGetValue(name, out preexisting)) + { + elementDictionary[name] = ambiguityCreator(preexisting, element); + } + else + { + elementDictionary[name] = element; + } + } + + internal static void AddOperation(IEdmOperation operation, string name, Dictionary> operationListDictionary) + { + IList operationGroup = null; + if (!operationListDictionary.TryGetValue(name, out operationGroup)) + { + operationGroup = new List(); + operationListDictionary.Add(name, operationGroup); + } + + operationGroup.Add(operation); + } + + internal static void AddOperationImport(IEdmOperationImport operationImport, string name, Dictionary operationListDictionary) + { + object preexisting = null; + if (operationListDictionary.TryGetValue(name, out preexisting)) + { + List operationList = preexisting as List; + if (operationList == null) + { + IEdmOperationImport existingFunction = (IEdmOperationImport)preexisting; + operationList = new List(); + operationList.Add(existingFunction); + operationListDictionary[name] = operationList; + } + + operationList.Add(operationImport); + } + else + { + operationListDictionary[name] = operationImport; + } + } + + internal static IEdmSchemaType CreateAmbiguousTypeBinding(IEdmSchemaType first, IEdmSchemaType second) + { + if (first == second) + { + return first; + } + + var ambiguous = first as AmbiguousTypeBinding; + if (ambiguous != null) + { + ambiguous.AddBinding(second); + return ambiguous; + } + + return new AmbiguousTypeBinding(first, second); + } + + internal static IEdmTerm CreateAmbiguousTermBinding(IEdmTerm first, IEdmTerm second) + { + var ambiguous = first as AmbiguousTermBinding; + if (ambiguous != null) + { + ambiguous.AddBinding(second); + return ambiguous; + } + + return new AmbiguousTermBinding(first, second); + } + + internal static IEdmEntitySet CreateAmbiguousEntitySetBinding(IEdmEntitySet first, IEdmEntitySet second) + { + var ambiguous = first as AmbiguousEntitySetBinding; + if (ambiguous != null) + { + ambiguous.AddBinding(second); + return ambiguous; + } + + return new AmbiguousEntitySetBinding(first, second); + } + + internal static IEdmSingleton CreateAmbiguousSingletonBinding(IEdmSingleton first, IEdmSingleton second) + { + var ambiguous = first as AmbiguousSingletonBinding; + if (ambiguous != null) + { + ambiguous.AddBinding(second); + return ambiguous; + } + + return new AmbiguousSingletonBinding(first, second); + } + + internal static IEdmEntityContainer CreateAmbiguousEntityContainerBinding(IEdmEntityContainer first, IEdmEntityContainer second) + { + var ambiguous = first as AmbiguousEntityContainerBinding; + if (ambiguous != null) + { + ambiguous.AddBinding(second); + return ambiguous; + } + + return new AmbiguousEntityContainerBinding(first, second); + } + + private static IEdmProperty CreateAmbiguousPropertyBinding(IEdmProperty first, IEdmProperty second) + { + var ambiguous = first as AmbiguousPropertyBinding; + if (ambiguous != null) + { + ambiguous.AddBinding(second); + return ambiguous; + } + + return new AmbiguousPropertyBinding(first.DeclaringType, first, second); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousBinding.cs new file mode 100644 index 0000000..38f2beb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousBinding.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a name binding to more than one item. + /// + /// Type of the ambiguous element. + internal class AmbiguousBinding : BadElement + where TElement : class, IEdmNamedElement + { + private readonly List bindings = new List(); + + public AmbiguousBinding(TElement first, TElement second) + : base(new EdmError[] { new EdmError(null, EdmErrorCode.BadAmbiguousElementBinding, Edm.Strings.Bad_AmbiguousElementBinding(first.Name)) }) + { + this.AddBinding(first); + this.AddBinding(second); + } + + public IEnumerable Bindings + { + get { return this.bindings; } + } + + public string Name + { + get { return this.bindings.First().Name ?? string.Empty; } + } + + public void AddBinding(TElement binding) + { + if (!this.bindings.Contains(binding)) + { + this.bindings.Add(binding); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntityContainerBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntityContainerBinding.cs new file mode 100644 index 0000000..bd74e94 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntityContainerBinding.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm +{ + internal class AmbiguousEntityContainerBinding : AmbiguousBinding, IEdmEntityContainer, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string fullName; + + public AmbiguousEntityContainerBinding(IEdmEntityContainer first, IEdmEntityContainer second) + : base(first, second) + { + // Ambiguous entity containers can be produced by either searching for full name or simple name. + // This results in the reported NamespaceName being ambiguous so the first one is selected arbitrarily. + this.namespaceName = first.Namespace ?? string.Empty; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.Name); + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.EntityContainer; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public IEnumerable Elements + { + get { return Enumerable.Empty(); } + } + + public IEdmEntitySet FindEntitySet(string name) + { + return null; + } + + public IEdmSingleton FindSingleton(string name) + { + return null; + } + + public IEnumerable FindOperationImports(string operationName) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntitySetBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntitySetBinding.cs new file mode 100644 index 0000000..e635772 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousEntitySetBinding.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm +{ + internal class AmbiguousEntitySetBinding : AmbiguousBinding, IEdmEntitySet + { + public AmbiguousEntitySetBinding(IEdmEntitySet first, IEdmEntitySet second) + : base(first, second) + { + } + + public EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.EntitySet; } + } + + public IEdmEntityContainer Container + { + get + { + IEdmEntitySet first = this.Bindings.FirstOrDefault(); + return first != null ? first.Container : null; + } + } + + public IEdmPathExpression Path + { + get { return null; } + } + + public IEdmType Type + { + get { return new EdmCollectionType(new EdmEntityTypeReference(new BadEntityType(String.Empty, this.Errors), false)); } + } + + public bool IncludeInServiceDocument + { + get { return true; } + } + + public IEnumerable NavigationPropertyBindings + { + get { return Enumerable.Empty(); } + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty property) + { + return null; + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + return null; + } + + public IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousLabeledExpressionBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousLabeledExpressionBinding.cs new file mode 100644 index 0000000..0cad7a2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousLabeledExpressionBinding.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a labeled expression binding to more than one item. + /// + internal class AmbiguousLabeledExpressionBinding : AmbiguousBinding, IEdmLabeledExpression + { + private readonly Cache expressionCache = new Cache(); + private static readonly Func ComputeExpressionFunc = (me) => ComputeExpression(); + + public AmbiguousLabeledExpressionBinding(IEdmLabeledExpression first, IEdmLabeledExpression second) + : base(first, second) + { + } + + public IEdmExpression Expression + { + get { return this.expressionCache.GetValue(this, ComputeExpressionFunc, null); } + } + + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Labeled; } + } + + private static IEdmExpression ComputeExpression() + { + return EdmNullExpression.Instance; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationBinding.cs new file mode 100644 index 0000000..c9cbec0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationBinding.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + internal class AmbiguousOperationBinding : AmbiguousBinding, IEdmOperation, IEdmFullNamedElement + { + private readonly string fullName; + private IEdmOperation first; + + public AmbiguousOperationBinding(IEdmOperation first, IEdmOperation second) + : base(first, second) + { + this.first = first; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.Namespace, this.Name); + } + + public IEdmTypeReference ReturnType + { + // Not using the typical behavior for first.ReturnType as returning null is the old behavior. + get { return null; } + } + + public string Namespace + { + get { return this.first.Namespace; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public IEnumerable Parameters + { + get { return this.first.Parameters; } + } + + public bool IsBound + { + get { return this.first.IsBound; } + } + + public IEdmPathExpression EntitySetPath + { + get { return this.first.EntitySetPath; } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return this.first.SchemaElementKind; } + } + + public IEdmOperationParameter FindParameter(string name) + { + return this.first.FindParameter(name); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationImportBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationImportBinding.cs new file mode 100644 index 0000000..b3d5f4e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousOperationImportBinding.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Class that represents an unresolved operation import binding to two or more operation imports. + /// + internal class AmbiguousOperationImportBinding : AmbiguousBinding, IEdmOperationImport + { + private readonly IEdmOperationImport first; + + public AmbiguousOperationImportBinding(IEdmOperationImport first, IEdmOperationImport second) + : base(first, second) + { + this.first = first; + } + + public IEdmOperation Operation + { + get { return this.first.Operation; } + } + + public IEdmEntityContainer Container + { + get { return first.Container; } + } + + public EdmContainerElementKind ContainerElementKind + { + get { return first.ContainerElementKind; } + } + + public IEdmExpression EntitySet + { + get { return null; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousPropertyBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousPropertyBinding.cs new file mode 100644 index 0000000..d657899 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousPropertyBinding.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm +{ + internal class AmbiguousPropertyBinding : AmbiguousBinding, IEdmProperty + { + private readonly IEdmStructuredType declaringType; + + // Type cache. + private readonly Cache type = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public AmbiguousPropertyBinding(IEdmStructuredType declaringType, IEdmProperty first, IEdmProperty second) + : base(first, second) + { + this.declaringType = declaringType; + } + + /// + /// Gets the kind of this property. + /// + public EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.None; } + } + + public IEdmTypeReference Type + { + get { return this.type.GetValue(this, ComputeTypeFunc, null); } + } + + /// + /// Gets the type that this property belongs to. + /// + public IEdmStructuredType DeclaringType + { + get { return this.declaringType; } + } + + private IEdmTypeReference ComputeType() + { + return new BadTypeReference(new BadType(Errors), true); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousSingletonBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousSingletonBinding.cs new file mode 100644 index 0000000..95de726 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousSingletonBinding.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm +{ + internal class AmbiguousSingletonBinding : AmbiguousBinding, IEdmSingleton + { + public AmbiguousSingletonBinding(IEdmSingleton first, IEdmSingleton second) + : base(first, second) + { + } + + public IEdmType Type + { + get { return new BadEntityType(String.Empty, this.Errors); } + } + + + public EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.Singleton; } + } + + public IEdmEntityContainer Container + { + get + { + IEdmSingleton first = this.Bindings.FirstOrDefault(); + return first != null ? first.Container : null; + } + } + + public IEdmPathExpression Path + { + get { return null; } + } + + public IEnumerable NavigationPropertyBindings + { + get { return Enumerable.Empty(); } + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty property) + { + return null; + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + return null; + } + + public IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTermBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTermBinding.cs new file mode 100644 index 0000000..402f743 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTermBinding.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a name binding to more than one item. + /// + internal class AmbiguousTermBinding : AmbiguousBinding, IEdmTerm, IEdmFullNamedElement + { + private readonly IEdmTerm first; + private readonly string fullName; + + // Type cache. + private readonly Cache type = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + private readonly string appliesTo = null; + private readonly string defaultValue = null; + + public AmbiguousTermBinding(IEdmTerm first, IEdmTerm second) + : base(first, second) + { + this.first = first; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.Namespace, this.Name); + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Term; } + } + + public string Namespace + { + get { return this.first.Namespace ?? string.Empty; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public IEdmTypeReference Type + { + get { return this.type.GetValue(this, ComputeTypeFunc, null); } + } + + public string AppliesTo + { + get { return this.appliesTo; } + } + + public string DefaultValue + { + get { return this.defaultValue; } + } + + private IEdmTypeReference ComputeType() + { + return new BadTypeReference(new BadType(Errors), true); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTypeBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTypeBinding.cs new file mode 100644 index 0000000..8d93d35 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/AmbiguousTypeBinding.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a name binding to more than one item. + /// + internal class AmbiguousTypeBinding : AmbiguousBinding, IEdmSchemaType, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string fullName; + + public AmbiguousTypeBinding(IEdmSchemaType first, IEdmSchemaType second) + : base(first, second) + { + Debug.Assert(first.Namespace == second.Namespace, "Schema elements should only be ambiguous with other elements in the same namespace"); + this.namespaceName = first.Namespace ?? string.Empty; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.Name); + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public EdmTypeKind TypeKind + { + get { return EdmTypeKind.None; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadBinaryTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadBinaryTypeReference.cs new file mode 100644 index 0000000..1cea9ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadBinaryTypeReference.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to a semantically invalid EDM binary type. + /// + internal class BadBinaryTypeReference : EdmBinaryTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadBinaryTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadPrimitiveType(qualifiedName, EdmPrimitiveTypeKind.Binary, errors), isNullable, false, null) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadCollectionType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadCollectionType.cs new file mode 100644 index 0000000..c65acd1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadCollectionType.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM collection type. + /// + internal class BadCollectionType : BadType, IEdmCollectionType + { + private readonly IEdmTypeReference elementType; + + public BadCollectionType(IEnumerable errors) + : base(errors) + { + this.elementType = new BadTypeReference(new BadType(errors), true); + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Collection; } + } + + public IEdmTypeReference ElementType + { + get { return this.elementType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexType.cs new file mode 100644 index 0000000..43120f4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexType.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM complex type definition. + /// + internal class BadComplexType : BadNamedStructuredType, IEdmComplexType + { + public BadComplexType(string qualifiedName, IEnumerable errors) + : base(qualifiedName, errors) + { + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Complex; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexTypeReference.cs new file mode 100644 index 0000000..8d315c3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadComplexTypeReference.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + internal class BadComplexTypeReference : EdmComplexTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadComplexTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadComplexType(qualifiedName, errors), isNullable) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadDecimalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadDecimalTypeReference.cs new file mode 100644 index 0000000..ab17142 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadDecimalTypeReference.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to a semantically invalid EDM decimal type. + /// + internal class BadDecimalTypeReference : EdmDecimalTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadDecimalTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadPrimitiveType(qualifiedName, EdmPrimitiveTypeKind.Decimal, errors), isNullable, null, null) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEdmEnumMemberValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEdmEnumMemberValue.cs new file mode 100644 index 0000000..5b84182 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEdmEnumMemberValue.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + internal class BadEdmEnumMemberValue : BadElement, IEdmEnumMemberValue + { + public BadEdmEnumMemberValue(IEnumerable errors) + : base(errors) + { + } + + public long Value + { + get { return 0; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadElement.cs new file mode 100644 index 0000000..9ad2fa5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadElement.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an invalid EDM element. + /// + internal class BadElement : IEdmElement, IEdmCheckable, IEdmVocabularyAnnotatable + { + private readonly IEnumerable errors; + + public BadElement(IEnumerable errors) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityContainer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityContainer.cs new file mode 100644 index 0000000..48ded4e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityContainer.cs @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM entity container. + /// + internal class BadEntityContainer : BadElement, IEdmEntityContainer, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + public BadEntityContainer(string qualifiedName, IEnumerable errors) + : base(errors) + { + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + } + + public IEnumerable Elements + { + get { return Enumerable.Empty(); } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the kind of this schema element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.EntityContainer; } + } + + public IEdmEntitySet FindEntitySet(string setName) + { + return null; + } + + public IEdmSingleton FindSingleton(string singletonName) + { + return null; + } + + public IEnumerable FindOperationImports(string operationName) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityReferenceType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityReferenceType.cs new file mode 100644 index 0000000..ef289ac --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityReferenceType.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM entity reference type. + /// + internal class BadEntityReferenceType : BadType, IEdmEntityReferenceType + { + private readonly IEdmEntityType entityType; + + public BadEntityReferenceType(IEnumerable errors) + : base(errors) + { + this.entityType = new BadEntityType(String.Empty, this.Errors); + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.EntityReference; } + } + + public IEdmEntityType EntityType + { + get { return this.entityType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntitySet.cs new file mode 100644 index 0000000..67051e9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntitySet.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM entity set. + /// + internal class BadEntitySet : BadElement, IEdmEntitySet + { + private readonly string name; + private readonly IEdmEntityContainer container; + + public BadEntitySet(string name, IEdmEntityContainer container, IEnumerable errors) + : base(errors) + { + this.name = name ?? string.Empty; + this.container = container; + } + + public string Name + { + get { return this.name; } + } + + public EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.EntitySet; } + } + + public IEdmEntityContainer Container + { + get { return this.container; } + } + + public IEnumerable NavigationPropertyBindings + { + get { return Enumerable.Empty(); } + } + + public IEdmPathExpression Path + { + get { return null; } + } + + public IEdmType Type + { + get { return new EdmCollectionType(new EdmEntityTypeReference(new BadEntityType(String.Empty, this.Errors), false)); } + } + + public bool IncludeInServiceDocument + { + get { return true; } + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty property) + { + return null; + } + + public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + return null; + } + + public IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityType.cs new file mode 100644 index 0000000..1accf6e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityType.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM entity type. + /// + internal class BadEntityType : BadNamedStructuredType, IEdmEntityType + { + public BadEntityType(string qualifiedName, IEnumerable errors) + : base(qualifiedName, errors) + { + } + + public IEnumerable DeclaredKey + { + get { return null; } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Entity; } + } + + public bool HasStream + { + get { return false; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityTypeReference.cs new file mode 100644 index 0000000..cdef7dd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEntityTypeReference.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + internal class BadEntityTypeReference : EdmEntityTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadEntityTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadEntityType(qualifiedName, errors), isNullable) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEnumType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEnumType.cs new file mode 100644 index 0000000..c9953b7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadEnumType.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM enumeration type. + /// + internal class BadEnumType : BadType, IEdmEnumType, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + public BadEnumType(string qualifiedName, IEnumerable errors) + : base(errors) + { + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + } + + public IEnumerable Members + { + get { return Enumerable.Empty(); } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Enum; } + } + + public IEdmPrimitiveType UnderlyingType + { + get { return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int32); } + } + + public bool IsFlags + { + get { return false; } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadLabeledExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadLabeledExpression.cs new file mode 100644 index 0000000..a0dd5d4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadLabeledExpression.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM labeled expression. + /// + internal class BadLabeledExpression : BadElement, IEdmLabeledExpression + { + private readonly string name; + + private readonly Cache expressionCache = new Cache(); + private static readonly Func ComputeExpressionFunc = (me) => ComputeExpression(); + + public BadLabeledExpression(string name, IEnumerable errors) + : base(errors) + { + this.name = name ?? string.Empty; + } + + public string Name + { + get { return this.name; } + } + + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Labeled; } + } + + public IEdmExpression Expression + { + get { return this.expressionCache.GetValue(this, ComputeExpressionFunc, null); } + } + + private static IEdmExpression ComputeExpression() + { + return EdmNullExpression.Instance; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNamedStructuredType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNamedStructuredType.cs new file mode 100644 index 0000000..8b5780a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNamedStructuredType.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM named structured type definition. + /// + internal abstract class BadNamedStructuredType : BadStructuredType, IEdmSchemaElement, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + protected BadNamedStructuredType(string qualifiedName, IEnumerable errors) + : base(errors) + { + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + } + + public string Name + { + get { return this.name; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNavigationProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNavigationProperty.cs new file mode 100644 index 0000000..c72e6dd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadNavigationProperty.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Microsoft.OData.Edm.Validation; + + /// + /// Represents a semantically invalid EDM navigation property. + /// + internal class BadNavigationProperty : BadElement, IEdmNavigationProperty + { + private readonly string name; + private readonly IEdmStructuredType declaringType; + + // Type cache. + private readonly Cache type = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public BadNavigationProperty(IEdmStructuredType declaringType, string name, IEnumerable errors) + : base(errors) + { + this.name = name ?? string.Empty; + this.declaringType = declaringType; + } + + public string Name + { + get { return this.name; } + } + + public IEdmStructuredType DeclaringType + { + get { return this.declaringType; } + } + + public IEdmTypeReference Type + { + get { return this.type.GetValue(this, ComputeTypeFunc, null); } + } + + public EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.None; } + } + + public IEdmNavigationProperty Partner + { + get { return null; } + } + + public EdmOnDeleteAction OnDelete + { + get { return EdmOnDeleteAction.None; } + } + + public IEdmReferentialConstraint ReferentialConstraint + { + get { return null; } + } + + public bool ContainsTarget + { + get { return false; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + return error.ErrorCode + ":" + this.ToTraceString(); + } + + private IEdmTypeReference ComputeType() + { + return new BadTypeReference(new BadType(this.Errors), true); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathType.cs new file mode 100644 index 0000000..e3f8eb4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathType.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM path type. + /// + internal class BadPathType : BadType, IEdmPathType, IEdmFullNamedElement + { + public BadPathType(string qualifiedName, IEnumerable errors) + : base(errors) + { + } + + public string Name + { + get + { + throw new NotImplementedException(); + } + } + + public string Namespace + { + get + { + throw new NotImplementedException(); + } + } + + public string FullName + { + get + { + throw new NotImplementedException(); + } + } + + public EdmPathTypeKind PathKind + { + get + { + throw new NotImplementedException(); + } + } + + public EdmSchemaElementKind SchemaElementKind + { + get + { + throw new NotImplementedException(); + } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Path; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathTypeReference.cs new file mode 100644 index 0000000..0a82834 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPathTypeReference.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + internal class BadPathTypeReference : EdmPathTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadPathTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadPathType(qualifiedName, errors), isNullable) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error.ErrorCode + ":"; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveType.cs new file mode 100644 index 0000000..12d1636 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveType.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM primitive type definition. + /// + internal class BadPrimitiveType : BadType, IEdmPrimitiveType, IEdmFullNamedElement + { + private readonly EdmPrimitiveTypeKind primitiveKind; + private readonly string name; + private readonly string namespaceName; + private readonly string fullName; + + public BadPrimitiveType(string qualifiedName, EdmPrimitiveTypeKind primitiveKind, IEnumerable errors) + : base(errors) + { + this.primitiveKind = primitiveKind; + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + } + + public EdmPrimitiveTypeKind PrimitiveKind + { + get { return this.primitiveKind; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Primitive; } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveTypeReference.cs new file mode 100644 index 0000000..7598754 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadPrimitiveTypeReference.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to a semantically invalid EDM primitive type. + /// + internal class BadPrimitiveTypeReference : EdmPrimitiveTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadPrimitiveTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadPrimitiveType(qualifiedName, EdmPrimitiveTypeKind.None, errors), isNullable) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadProperty.cs new file mode 100644 index 0000000..3bed1b6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadProperty.cs @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM property. + /// + internal class BadProperty : BadElement, IEdmStructuralProperty + { + private readonly string name; + private readonly IEdmStructuredType declaringType; + + // Type cache. + private readonly Cache type = new Cache(); + private static readonly Func ComputeTypeFunc = (me) => me.ComputeType(); + + public BadProperty(IEdmStructuredType declaringType, string name, IEnumerable errors) + : base(errors) + { + this.name = name ?? string.Empty; + this.declaringType = declaringType; + } + + public string Name + { + get { return this.name; } + } + + public IEdmStructuredType DeclaringType + { + get { return this.declaringType; } + } + + public IEdmTypeReference Type + { + get { return this.type.GetValue(this, ComputeTypeFunc, null); } + } + + public string DefaultValueString + { + get { return null; } + } + + public EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.None; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + return error.ErrorCode + ":" + this.ToTraceString(); + } + + private IEdmTypeReference ComputeType() + { + return new BadTypeReference(new BadType(Errors), true); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadSpatialTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadSpatialTypeReference.cs new file mode 100644 index 0000000..d0d1bc2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadSpatialTypeReference.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to a semantically invalid EDM spatial type. + /// + internal class BadSpatialTypeReference : EdmSpatialTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadSpatialTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadPrimitiveType(qualifiedName, EdmPrimitiveTypeKind.None, errors), isNullable, null) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStringTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStringTypeReference.cs new file mode 100644 index 0000000..732112e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStringTypeReference.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to a semantically invalid EDM string type. + /// + internal class BadStringTypeReference : EdmStringTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadStringTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadPrimitiveType(qualifiedName, EdmPrimitiveTypeKind.String, errors), isNullable, false, null, false) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStructuredType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStructuredType.cs new file mode 100644 index 0000000..473a61e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadStructuredType.cs @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a semantically invalid EDM structured type definition. + /// + internal abstract class BadStructuredType : BadType, IEdmStructuredType, IEdmCheckable + { + protected BadStructuredType(IEnumerable errors) + : base(errors) + { + } + + public IEdmStructuredType BaseType + { + get { return null; } + } + + public IEnumerable DeclaredProperties + { + get { return Enumerable.Empty(); } + } + + public bool IsAbstract + { + get { return false; } + } + + public bool IsOpen + { + get { return false; } + } + + public IEdmProperty FindProperty(string name) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTemporalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTemporalTypeReference.cs new file mode 100644 index 0000000..b88d5a5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTemporalTypeReference.cs @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to a semantically invalid EDM temporal (Duration, DateTime, DateTimeOffset) type. + /// + internal class BadTemporalTypeReference : EdmTemporalTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadTemporalTypeReference(string qualifiedName, bool isNullable, IEnumerable errors) + : base(new BadPrimitiveType(qualifiedName, EdmPrimitiveTypeKind.None, errors), isNullable, null) + { + this.errors = errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadType.cs new file mode 100644 index 0000000..979ca3e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadType.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + internal class BadType : BadElement, IEdmType + { + public BadType(IEnumerable errors) + : base(errors) + { + } + + public virtual EdmTypeKind TypeKind + { + get { return EdmTypeKind.None; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeDefinition.cs new file mode 100644 index 0000000..67fbdf4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeDefinition.cs @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System.Collections.Generic; + + using Microsoft.OData.Edm.Validation; + + /// + /// Represents a semantically invalid EDM type definition. + /// + internal class BadTypeDefinition : BadType, IEdmTypeDefinition, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + public BadTypeDefinition(string qualifiedName, IEnumerable errors) + : base(errors) + { + qualifiedName = qualifiedName ?? string.Empty; + EdmUtil.TryGetNamespaceNameFromQualifiedName(qualifiedName, out this.namespaceName, out this.name, out this.fullName); + } + + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Enum; } + } + + public IEdmPrimitiveType UnderlyingType + { + get { return EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int32); } + } + + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + public string Namespace + { + get { return this.namespaceName; } + } + + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeReference.cs new file mode 100644 index 0000000..f590349 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/BadTypeReference.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + internal class BadTypeReference : EdmTypeReference, IEdmCheckable + { + private readonly IEnumerable errors; + + public BadTypeReference(BadType definition, bool isNullable) + : base(definition, isNullable) + { + this.errors = definition.Errors; + } + + public IEnumerable Errors + { + get { return this.errors; } + } + + public override string ToString() + { + EdmError error = this.Errors.FirstOrDefault(); + Debug.Assert(error != null, "error != null"); + string prefix = error != null ? error.ErrorCode.ToString() + ":" : ""; + return prefix + this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModel.cs new file mode 100644 index 0000000..6a768a5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModel.cs @@ -0,0 +1,793 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Provides predefined declarations relevant to EDM semantics. + /// + public class EdmCoreModel : EdmElement, IEdmModel, IEdmCoreModelElement + { + /// + /// The default core EDM model. + /// + public static readonly EdmCoreModel Instance = new EdmCoreModel(); + + private readonly IDictionary primitiveTypeKinds = new Dictionary(); + private readonly IDictionary primitiveTypesByKind = new Dictionary(); + + private readonly IDictionary pathTypeKinds = new Dictionary(); + private readonly IDictionary pathTypesByKind = new Dictionary(); + + private readonly IEdmDirectValueAnnotationsManager annotationsManager = new EdmDirectValueAnnotationsManager(); + + private readonly IList coreSchemaElements = new List(); + + private readonly IDictionary coreSchemaTypes = new Dictionary(); + + private readonly EdmCoreModelComplexType complexType = EdmCoreModelComplexType.Instance; + private readonly EdmCoreModelEntityType entityType = EdmCoreModelEntityType.Instance; + private readonly EdmCoreModelUntypedType untypedType = EdmCoreModelUntypedType.Instance; + private readonly EdmCoreModelPrimitiveType primitiveType = new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.PrimitiveType); + + private EdmCoreModel() + { + IList primitiveTypes = new List + { + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Double), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Single), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Int64), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Int32), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Int16), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.SByte), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Byte), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Boolean), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Guid), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Duration), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.TimeOfDay), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Date), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Decimal), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Binary), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.String), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Stream), + + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Geography), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeographyPoint), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeographyLineString), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeographyPolygon), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeographyCollection), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiPolygon), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiLineString), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeographyMultiPoint), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.Geometry), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeometryPoint), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeometryLineString), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeometryPolygon), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeometryCollection), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiPolygon), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiLineString), + new EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind.GeometryMultiPoint), + primitiveType, // Edm.PrimitiveType + }; + + foreach (var primitive in primitiveTypes) + { + primitiveTypeKinds[primitive.Name] = primitive.PrimitiveKind; + primitiveTypeKinds[primitive.Namespace + '.' + primitive.Name] = primitive.PrimitiveKind; + primitiveTypesByKind[primitive.PrimitiveKind] = primitive; + + coreSchemaTypes[primitive.Namespace + '.' + primitive.Name] = primitive; + coreSchemaTypes[primitive.Name] = primitive; + + coreSchemaElements.Add(primitive); + } + + coreSchemaElements.Add(complexType); // Edm.ComplexType + coreSchemaTypes[complexType.Namespace + '.' + complexType.Name] = complexType; + coreSchemaTypes[complexType.Name] = complexType; + + coreSchemaElements.Add(entityType); // Edm.EntityType + coreSchemaTypes[entityType.Namespace + '.' + entityType.Name] = entityType; + coreSchemaTypes[entityType.Name] = entityType; + + coreSchemaElements.Add(untypedType); // Edm.Untyped + coreSchemaTypes[untypedType.Namespace + '.' + untypedType.Name] = untypedType; + coreSchemaTypes[untypedType.Name] = untypedType; + + EdmCoreModelPathType[] pathTypes = + { + new EdmCoreModelPathType(EdmPathTypeKind.AnnotationPath), // Edm.AnotationPath + new EdmCoreModelPathType(EdmPathTypeKind.PropertyPath), // Edm.PropertyPath + new EdmCoreModelPathType(EdmPathTypeKind.NavigationPropertyPath), // Edm.NavigationPropertyPath + }; + + foreach (var pathType in pathTypes) + { + pathTypeKinds[pathType.Name] = pathType.PathKind; + pathTypeKinds[pathType.Namespace + '.' + pathType.Name] = pathType.PathKind; + pathTypesByKind[pathType.PathKind] = pathType; + + coreSchemaTypes[pathType.Namespace + '.' + pathType.Name] = pathType; + coreSchemaTypes[pathType.Name] = pathType; + + coreSchemaElements.Add(pathType); + } + } + + /// + /// Gets the namespace of this core model. + /// + public static string Namespace + { + get { return EdmConstants.EdmNamespace; } + } + + /// + /// Gets the types defined in this core model. + /// + public IEnumerable SchemaElements + { + get { return coreSchemaElements; } + } + + /// + /// Gets the collection of namespaces that schema elements use contained in this model. + /// + public IEnumerable DeclaredNamespaces + { + get { return Enumerable.Empty(); } + } + + /// + /// Gets the vocabulary annotations defined in this model. + /// + public IEnumerable VocabularyAnnotations + { + get { return Enumerable.Empty(); } + } + + /// + /// Gets the collection of models referred to by this model. + /// + public IEnumerable ReferencedModels + { + get { return Enumerable.Empty(); } + } + + /// + /// Gets the model's annotations manager. + /// + public IEdmDirectValueAnnotationsManager DirectValueAnnotationsManager + { + get { return this.annotationsManager; } + } + + /// + /// Gets the only one entity container of the model. + /// + public IEdmEntityContainer EntityContainer + { + get { return null; } + } + + /// + /// Gets a reference to a non-atomic collection type definition. + /// + /// Type of elements in the collection. + /// A new non-atomic collection type reference. + public static IEdmCollectionTypeReference GetCollection(IEdmTypeReference elementType) + { + return new EdmCollectionTypeReference(new EdmCollectionType(elementType)); + } + + /// + /// Searches for a type with the given name in this model only and returns null if no such type exists. + /// + /// The qualified name of the type being found. + /// The requested type, or null if no such type exists. + public IEdmSchemaType FindDeclaredType(string qualifiedName) + { + IEdmSchemaType element; + if (coreSchemaTypes.TryGetValue(qualifiedName, out element)) + { + return element; + } + + return null; + } + + /// + /// Searches for bound operations based on the binding type, returns an empty enumerable if no operation exists. + /// + /// Type of the binding. + /// A set of operations that share the binding type or empty enumerable if no such operation exists. + public IEnumerable FindDeclaredBoundOperations(IEdmType bindingType) + { + return Enumerable.Empty(); + } + + /// + /// Searches for bound operations based on the qualified name and binding type, returns an empty enumerable if no operation exists. + /// + /// The qualified name of the operation. + /// Type of the binding. + /// + /// A set of operations that share the qualified name and binding type or empty enumerable if no such operation exists. + /// + public IEnumerable FindDeclaredBoundOperations(string qualifiedName, IEdmType bindingType) + { + return Enumerable.Empty(); + } + + /// + /// Searches for a term with the given name in this model and returns null if no such term exists. + /// + /// The qualified name of the term being found. + /// The requested term, or null if no such term exists. + public IEdmTerm FindDeclaredTerm(string qualifiedName) + { + return null; + } + + /// + /// Searches for operations with the given name in this model and returns an empty enumerable if no such operation exists. + /// + /// The qualified name of the operation being found. + /// A set operations sharing the specified qualified name, or an empty enumerable if no such operation exists. + public IEnumerable FindDeclaredOperations(string qualifiedName) + { + return Enumerable.Empty(); + } + + /// + /// Searches for any functionImport or actionImport by name and parameter names. + /// + /// The name of the operation imports to find. May be qualified with the namespace. + /// The parameter names of the parameters. + /// The operation imports that matches the search criteria or empty there was no match. + public IEnumerable FindOperationImportsByNameNonBindingParameterType(string operationImportName, IEnumerable parameterNames) + { + return Enumerable.Empty(); + } + + /// + /// Gets primitive type by kind. + /// + /// Kind of the primitive type. + /// Primitive type definition. + public IEdmPrimitiveType GetPrimitiveType(EdmPrimitiveTypeKind kind) + { + return this.GetCoreModelPrimitiveType(kind); + } + + /// + /// Gets Edm.PrimitiveType type. + /// + /// IEdmPrimitiveType type definition. + public IEdmPrimitiveType GetPrimitiveType() + { + return this.primitiveType; + } + + /// + /// Gets Edm.ComplexType type. + /// + /// IEdmComplexType type definition. + public IEdmComplexType GetComplexType() + { + return this.complexType; + } + + /// + /// Gets Edm.EntityType type. + /// + /// IEdmEntityType type definition. + public IEdmEntityType GetEntityType() + { + return this.entityType; + } + + /// + /// Gets Edm.Untyped type. + /// + /// IEdmUntypedType type definition. + public IEdmUntypedType GetUntypedType() + { + return this.untypedType; + } + + /// + /// Gets path type by kind. + /// + /// Kind of the path type. + /// Path type definition. + public IEdmPathType GetPathType(EdmPathTypeKind kind) + { + EdmCoreModelPathType definition; + return pathTypesByKind.TryGetValue(kind, out definition) ? definition : null; + } + + /// + /// Gets the EdmPrimitiveTypeKind by the type name. + /// + /// Name of the type to look up. + /// EdmPrimitiveTypeKind of the type. + public EdmPrimitiveTypeKind GetPrimitiveTypeKind(string typeName) + { + EdmPrimitiveTypeKind kind; + return primitiveTypeKinds.TryGetValue(typeName, out kind) ? kind : EdmPrimitiveTypeKind.None; + } + + /// + /// Gets a reference to a primitive type of the specified kind. + /// + /// Primitive kind of the type reference being created. + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetPrimitive(EdmPrimitiveTypeKind kind, bool isNullable) + { + IEdmPrimitiveType primitiveDefinition = this.GetCoreModelPrimitiveType(kind); + if (primitiveDefinition != null) + { + return primitiveDefinition.GetPrimitiveTypeReference(isNullable); + } + else + { + throw new InvalidOperationException(Edm.Strings.EdmPrimitive_UnexpectedKind); + } + } + + /// + /// Gets the EdmPathTypeKind by the type name. + /// + /// Name of the type to look up. + /// EdmPrimitiveTypeKind of the type. + public EdmPathTypeKind GetPathTypeKind(string typeName) + { + EdmPathTypeKind kind; + return pathTypeKinds.TryGetValue(typeName, out kind) ? kind : EdmPathTypeKind.None; + } + + /// + /// Gets a reference to a path type of the specified kind. + /// + /// Primitive kind of the type reference being created. + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPathTypeReference GetPathType(EdmPathTypeKind kind, bool isNullable) + { + IEdmPathType pathDefinition = this.GetPathType(kind); + if (pathDefinition != null) + { + return new EdmPathTypeReference(pathDefinition, isNullable); + } + else + { + throw new InvalidOperationException(Edm.Strings.EdmPath_UnexpectedKind); + } + } + + /// + /// Gets a reference to the Edm.AnnotationPath type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new Edm.AnnotationPath type reference. + public IEdmPathTypeReference GetAnnotationPath(bool isNullable) + { + return new EdmPathTypeReference(this.GetPathType(EdmPathTypeKind.AnnotationPath), isNullable); + } + + /// + /// Gets a reference to the Edm.PropertyPath type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new Edm.PropertyPath type reference. + public IEdmPathTypeReference GetPropertyPath(bool isNullable) + { + return new EdmPathTypeReference(this.GetPathType(EdmPathTypeKind.PropertyPath), isNullable); + } + + /// + /// Gets a reference to the Edm.NavigationPropertyPath type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new Edm.NavigationPropertyPath type reference. + public IEdmPathTypeReference GetNavigationPropertyPath(bool isNullable) + { + return new EdmPathTypeReference(this.GetPathType(EdmPathTypeKind.NavigationPropertyPath), isNullable); + } + + /// + /// Gets a reference to the Edm.EntityType type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new Edm.EntityType type reference. + public IEdmEntityTypeReference GetEntityType(bool isNullable) + { + return new EdmEntityTypeReference(this.entityType, isNullable); + } + + /// + /// Gets a reference to the Edm.ComplexType type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new Edm.ComplexType type reference. + public IEdmComplexTypeReference GetComplexType(bool isNullable) + { + return new EdmComplexTypeReference(this.complexType, isNullable); + } + + /// + /// Gets a reference to the Edm.PrimitiveType type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new Edm.PrimitiveType type reference. + public IEdmPrimitiveTypeReference GetPrimitiveType(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.primitiveType, isNullable); + } + + /// + /// Gets a reference to the Int16 primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetInt16(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Int16), isNullable); + } + + /// + /// Gets a reference to the Int32 primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetInt32(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Int32), isNullable); + } + + /// + /// Gets a reference to the Int64 primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetInt64(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Int64), isNullable); + } + + /// + /// Gets a reference to the Boolean primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetBoolean(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Boolean), isNullable); + } + + /// + /// Gets a reference to the Byte primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetByte(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Byte), isNullable); + } + + /// + /// Gets a reference to the SByte primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetSByte(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.SByte), isNullable); + } + + /// + /// Gets a reference to the Guid primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference. + public IEdmPrimitiveTypeReference GetGuid(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Guid), isNullable); + } + + /// + /// Get a reference to the Date primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new primitive type reference + public IEdmPrimitiveTypeReference GetDate(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Date), isNullable); + } + + /// + /// Gets a reference to a datetime with offset primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new datetime with offset type reference. + public IEdmTemporalTypeReference GetDateTimeOffset(bool isNullable) + { + return new EdmTemporalTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset), isNullable); + } + + /// + /// Gets a reference to a duration primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new duration type reference. + public IEdmTemporalTypeReference GetDuration(bool isNullable) + { + return new EdmTemporalTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Duration), isNullable); + } + + /// + /// Gets a reference to a TimeOfDay primitive type definition + /// + /// Flag specifying if the referenced type should be nullable. + /// A new TimeOfDay type reference. + public IEdmTemporalTypeReference GetTimeOfDay(bool isNullable) + { + return new EdmTemporalTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.TimeOfDay), isNullable); + } + + /// + /// Gets a reference to a decimal primitive type definition. + /// + /// Precision of values of this type. + /// Scale of values of this type. + /// Flag specifying if the referenced type should be nullable. + /// A new decimal type reference. + public IEdmDecimalTypeReference GetDecimal(int? precision, int? scale, bool isNullable) + { + if (precision.HasValue || scale.HasValue) + { + // Facet values may render this reference as semantically invalid, so can't return an IEdmValidCoreModelElement. + return new EdmDecimalTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Decimal), isNullable, precision, scale); + } + else + { + return new EdmDecimalTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Decimal), isNullable); + } + } + + /// + /// Gets a reference to a decimal primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new decimal type reference. + public IEdmDecimalTypeReference GetDecimal(bool isNullable) + { + return new EdmDecimalTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Decimal), isNullable); + } + + /// + /// Gets a reference to a single primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new decimal type reference. + public IEdmPrimitiveTypeReference GetSingle(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Single), isNullable); + } + + /// + /// Gets a reference to a double primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new decimal type reference. + public IEdmPrimitiveTypeReference GetDouble(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Double), isNullable); + } + + /// + /// Gets a reference to a stream primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new stream type reference. + public IEdmPrimitiveTypeReference GetStream(bool isNullable) + { + return new EdmPrimitiveTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Stream), isNullable); + } + + /// + /// Gets a reference to a temporal primitive type definition. + /// + /// Primitive kind of the type reference being created. + /// Precision of values of this type. + /// Flag specifying if the referenced type should be nullable. + /// A new temporal type reference. + public IEdmTemporalTypeReference GetTemporal(EdmPrimitiveTypeKind kind, int? precision, bool isNullable) + { + switch (kind) + { + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + return new EdmTemporalTypeReference(this.GetCoreModelPrimitiveType(kind), isNullable, precision); + default: + throw new InvalidOperationException(Edm.Strings.EdmPrimitive_UnexpectedKind); + } + } + + /// + /// Gets a reference to a temporal primitive type definition. + /// + /// Primitive kind of the type reference being created. + /// Flag specifying if the referenced type should be nullable. + /// A new temporal type reference. + public IEdmTemporalTypeReference GetTemporal(EdmPrimitiveTypeKind kind, bool isNullable) + { + switch (kind) + { + case EdmPrimitiveTypeKind.DateTimeOffset: + case EdmPrimitiveTypeKind.Duration: + case EdmPrimitiveTypeKind.TimeOfDay: + return new EdmTemporalTypeReference(this.GetCoreModelPrimitiveType(kind), isNullable); + default: + throw new InvalidOperationException(Edm.Strings.EdmPrimitive_UnexpectedKind); + } + } + + /// + /// Gets a reference to a binary primitive type definition. + /// + /// Flag specifying if max length is unbounded. + /// Maximum length of the type. + /// Flag specifying if the referenced type should be nullable. + /// A new binary type reference. + public IEdmBinaryTypeReference GetBinary(bool isUnbounded, int? maxLength, bool isNullable) + { + return new EdmBinaryTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Binary), isNullable, isUnbounded, maxLength); + } + + /// + /// Gets a reference to a binary primitive type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new binary type reference. + public IEdmBinaryTypeReference GetBinary(bool isNullable) + { + return new EdmBinaryTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.Binary), isNullable); + } + + /// + /// Gets a reference to a spatial primitive type definition. + /// + /// Primitive kind of the type reference being created. + /// Spatial Reference Identifier for the spatial type being created. + /// Flag specifying if the referenced type should be nullable. + /// A new spatial type reference. + public IEdmSpatialTypeReference GetSpatial(EdmPrimitiveTypeKind kind, int? spatialReferenceIdentifier, bool isNullable) + { + switch (kind) + { + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return new EdmSpatialTypeReference(this.GetCoreModelPrimitiveType(kind), isNullable, spatialReferenceIdentifier); + default: + throw new InvalidOperationException(Edm.Strings.EdmPrimitive_UnexpectedKind); + } + } + + /// + /// Gets a reference to a spatial primitive type definition. + /// + /// Primitive kind of the type reference being created. + /// Flag specifying if the referenced type should be nullable. + /// A new spatial type reference. + public IEdmSpatialTypeReference GetSpatial(EdmPrimitiveTypeKind kind, bool isNullable) + { + switch (kind) + { + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + case EdmPrimitiveTypeKind.Geometry: + case EdmPrimitiveTypeKind.GeometryPoint: + case EdmPrimitiveTypeKind.GeometryLineString: + case EdmPrimitiveTypeKind.GeometryPolygon: + case EdmPrimitiveTypeKind.GeometryCollection: + case EdmPrimitiveTypeKind.GeometryMultiPolygon: + case EdmPrimitiveTypeKind.GeometryMultiLineString: + case EdmPrimitiveTypeKind.GeometryMultiPoint: + return new EdmSpatialTypeReference(this.GetCoreModelPrimitiveType(kind), isNullable); + default: + throw new InvalidOperationException(Edm.Strings.EdmPrimitive_UnexpectedKind); + } + } + + /// + /// Gets a reference to a string primitive type definition. + /// + /// Flag specifying if max length is the maximum allowable value. + /// Maximum length of the type. + /// Flag specifying if the type should support unicode encoding. + /// Flag specifying if the referenced type should be nullable. + /// A new string type reference. + public IEdmStringTypeReference GetString(bool isUnbounded, int? maxLength, bool? isUnicode, bool isNullable) + { + return new EdmStringTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.String), isNullable, isUnbounded, maxLength, isUnicode); + } + + /// + /// Gets a reference to a binary string type definition. + /// + /// Flag specifying if the referenced type should be nullable. + /// A new string type reference. + public IEdmStringTypeReference GetString(bool isNullable) + { + return new EdmStringTypeReference(this.GetCoreModelPrimitiveType(EdmPrimitiveTypeKind.String), isNullable); + } + + /// + /// Gets a reference to a Edm.Untyped type definition. + /// + /// A new Edm.Untyped type reference. + public IEdmUntypedTypeReference GetUntyped() + { + return new EdmUntypedTypeReference(this.GetUntypedType()); + } + + /// + /// Searches for vocabulary annotations specified by this model or a referenced model for a given element. + /// + /// The annotated element. + /// The vocabulary annotations for the element. + public IEnumerable FindDeclaredVocabularyAnnotations(IEdmVocabularyAnnotatable element) + { + return Enumerable.Empty(); + } + + /// + /// Finds a list of types that derive from the supplied type. + /// + /// The base type that derived types are being searched for. + /// A list of types that derive from the type. + public IEnumerable FindDirectlyDerivedTypes(IEdmStructuredType baseType) + { + return Enumerable.Empty(); + } + + private EdmCoreModelPrimitiveType GetCoreModelPrimitiveType(EdmPrimitiveTypeKind kind) + { + EdmCoreModelPrimitiveType definition; + return primitiveTypesByKind.TryGetValue(kind, out definition) ? definition : null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelComplexType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelComplexType.cs new file mode 100644 index 0000000..c95fd56 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelComplexType.cs @@ -0,0 +1,123 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; + +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.OData.Edm +{ + /// + /// The built-in Edm.ComplexType abstract type in the core model. + /// + internal sealed class EdmCoreModelComplexType : EdmType, IEdmComplexType, IEdmCoreModelElement, IEdmFullNamedElement + { + /// + /// The core Edm.ComplexType singleton. + /// + public static readonly EdmCoreModelComplexType Instance = new EdmCoreModelComplexType(); + + /// + /// Gets the base type of this type. + /// The Edm.ComplexType is always without base type. + /// + public IEdmStructuredType BaseType = null; + + /// + /// private constructor. + /// + private EdmCoreModelComplexType() + { + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Complex; } + } + + /// + /// Gets the schema element kind of this type. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the name of this type. + /// + public string Name + { + get { return CsdlConstants.TypeName_Complex_Short; } + } + + /// + /// Gets the namespace of this type. + /// + public string Namespace + { + get { return EdmConstants.EdmNamespace; } + } + + /// + /// Gets the full name of this type. + /// + public string FullName + { + get { return CsdlConstants.TypeName_Complex; } + } + + /// + /// Gets a value indicating whether this type is abstract. + /// The Edm.ComplexType is always an abstract type. + /// + public bool IsAbstract + { + get { return true; } + } + + /// + /// Gets a value indicating whether this type is open. + /// The Edm.ComplexType is always non-open type. + /// + public bool IsOpen + { + get { return false; } + } + + /// + /// Gets the properties declared immediately within this type. + /// The Edm.ComplexType is always without any declared properties. + /// + public IEnumerable DeclaredProperties + { + get { return Enumerable.Empty(); } + } + + /// + /// Gets the base structured type of this type. + /// The Edm.ComplexType is always without base type. + /// + IEdmStructuredType IEdmStructuredType.BaseType + { + get { return null; } + } + + /// + /// Searches for a structural or navigation property with the given name in this type. + /// + /// The name of the property being found. + /// The Edm.ComplexType is always without any declared properties. + public IEdmProperty FindProperty(string name) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelEntityType.cs new file mode 100644 index 0000000..a20b0c3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelEntityType.cs @@ -0,0 +1,135 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; + +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.OData.Edm +{ + /// + /// The built-in Edm.EntityType abstract type in the core model. + /// + internal sealed class EdmCoreModelEntityType : EdmType, IEdmEntityType, IEdmCoreModelElement, IEdmFullNamedElement + { + /// + /// The core Edm.EntityType singleton. + /// + public static readonly EdmCoreModelEntityType Instance = new EdmCoreModelEntityType(); + + /// + /// Private constructor. + /// + private EdmCoreModelEntityType() + { + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Entity; } + } + + /// + /// Gets the schema element kind of this type. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the name of this type. + /// + public string Name + { + get { return CsdlConstants.TypeName_Entity_Short; } + } + + /// + /// Gets the namespace of this type. + /// + public string Namespace + { + get { return EdmConstants.EdmNamespace; } + } + + /// + /// Gets the full name of this type. + /// + public string FullName + { + get { return CsdlConstants.TypeName_Entity; } + } + + /// + /// Gets the value indicating whether or not this type is a media entity. + /// The Edm.EntityType has not stream. + /// + public bool HasStream + { + get { return false; } + } + + /// + /// Gets a value indicating whether this type is abstract. + /// The Edm.EntityType is always an abstract type. + /// + public bool IsAbstract + { + get { return true; } + } + + /// + /// Gets a value indicating whether this type is open. + /// The Edm.EntityType is always non-open type. + /// + public bool IsOpen + { + get { return false; } + } + + /// + /// Gets the base type of this type. + /// The Edm.EntityType is always without base type. + /// + public IEdmStructuredType BaseType + { + get { return null; } + } + + /// + /// Gets the properties declared immediately within this type. + /// The Edm.EntityType is always without any declared properties. + /// + public IEnumerable DeclaredProperties + { + get { return Enumerable.Empty(); } + } + + /// + /// Gets the structural properties of the entity type that make up the entity key. + /// The Edm.EntityType is always without any declrared keys. + /// + public IEnumerable DeclaredKey + { + get { return Enumerable.Empty(); } + } + + /// + /// Searches for a structural or navigation property with the given name in this type. + /// + /// The name of the property being found. + /// The Edm.EntityType is always without any declared properties. + public IEdmProperty FindProperty(string name) + { + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPathType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPathType.cs new file mode 100644 index 0000000..890aba9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPathType.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The built-in Edm.AnnotationPath, Edm.PropertyPath, Edm.NavigationPropertyPath abstract type in the core model. + /// + internal sealed class EdmCoreModelPathType : EdmType, IEdmPathType, IEdmCoreModelElement, IEdmFullNamedElement + { + /// + /// Initializes a new instance of the class. + /// + /// The path kind. + public EdmCoreModelPathType(EdmPathTypeKind pathKind) + { + Name = pathKind.ToString(); + PathKind = pathKind; + } + + /// + /// Gets the path kind of this type. + /// + public EdmPathTypeKind PathKind { get; } + + /// + /// Gets the full name of this type. + /// + public string FullName + { + get { return Namespace + "." + Name; } + } + + /// + /// Gets the Edm type kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Path; } + } + + /// + /// Gets the schema element kind of this type. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the namespace of this type. + /// + public string Namespace + { + get { return EdmConstants.EdmNamespace; } + } + + /// + /// Gets the name of this type. + /// + public string Name { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPrimitiveType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPrimitiveType.cs new file mode 100644 index 0000000..c88fd4b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelPrimitiveType.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The built-in Edm.PrimitiveType and other concrete primitive types in the core model. + /// + internal sealed class EdmCoreModelPrimitiveType : EdmType, IEdmPrimitiveType, IEdmCoreModelElement, IEdmFullNamedElement + { + /// + /// Initializes a new instance of the class. + /// + /// The primitive type kind. + public EdmCoreModelPrimitiveType(EdmPrimitiveTypeKind primitiveKind) + { + Name = primitiveKind.ToString(); + PrimitiveKind = primitiveKind; + FullName = this.Namespace + "." + this.Name; + } + + /// + /// Gets the name of this type. + /// + public string Name { get; } + + /// + /// Getst the namespace of this type. + /// + public string Namespace + { + get { return EdmConstants.EdmNamespace; } + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Primitive; } + } + + /// + /// Gets the primitive kind of this type. + /// + public EdmPrimitiveTypeKind PrimitiveKind { get; } + + /// + /// Gets the schema element kind of this type. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the full name of this type. + /// + public string FullName { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelUntypedType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelUntypedType.cs new file mode 100644 index 0000000..6c7836c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/EdmCoreModelUntypedType.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.OData.Edm +{ + /// + /// The built-in Edm.Untyped type in the core model. + /// + internal sealed class EdmCoreModelUntypedType : EdmType, IEdmUntypedType, IEdmCoreModelElement, IEdmFullNamedElement + { + /// + /// The core Edm.Untyped singleton. + /// + public static readonly EdmCoreModelUntypedType Instance = new EdmCoreModelUntypedType(); + + /// + /// Private constructor. + /// + private EdmCoreModelUntypedType() + { + } + + /// + /// Gets the Edm type kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Untyped; } + } + + /// + /// Gets the scheme element type kind of this type. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the name of this type. + /// + public string Name + { + get { return CsdlConstants.TypeName_Untyped_Short; } + } + + /// + /// Gets the namespace of this type. + /// + public string Namespace + { + get { return EdmConstants.EdmNamespace; } + } + + /// + /// Gets the full name of this type. + /// + public string FullName + { + get { return CsdlConstants.TypeName_Untyped; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/IEdmCoreModelElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/IEdmCoreModelElement.cs new file mode 100644 index 0000000..f1b27ac --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CoreModel/IEdmCoreModelElement.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// This is a marker interface for core model elements that do not require validation. + /// + internal interface IEdmCoreModelElement + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicComplexType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicComplexType.cs new file mode 100644 index 0000000..2267d08 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicComplexType.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM complex type that cannot be determined due to a cyclic reference. + /// + internal class CyclicComplexType : BadComplexType + { + public CyclicComplexType(string qualifiedName, EdmLocation location) + : base(qualifiedName, new EdmError[] { new EdmError(location, EdmErrorCode.BadCyclicComplex, Edm.Strings.Bad_CyclicComplex(qualifiedName)) }) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityContainer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityContainer.cs new file mode 100644 index 0000000..9819571 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityContainer.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM entity container that cannot be determined due to a cyclic reference. + /// + internal class CyclicEntityContainer : BadEntityContainer + { + public CyclicEntityContainer(string name, EdmLocation location) + : base(name, new EdmError[] { new EdmError(location, EdmErrorCode.BadCyclicEntityContainer, Edm.Strings.Bad_CyclicEntityContainer(name)) }) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityType.cs new file mode 100644 index 0000000..a328aae --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/CyclicEntityType.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM entity type that cannot be determined due to a cyclic reference. + /// + internal class CyclicEntityType : BadEntityType + { + public CyclicEntityType(string qualifiedName, EdmLocation location) + : base(qualifiedName, new EdmError[] { new EdmError(location, EdmErrorCode.BadCyclicEntity, Edm.Strings.Bad_CyclicEntity(qualifiedName)) }) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Date.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Date.cs new file mode 100644 index 0000000..91ec260 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Date.cs @@ -0,0 +1,372 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Globalization; + +namespace Microsoft.OData.Edm +{ + /// + /// Date type for Edm.Date + /// + public struct Date : IComparable, IComparable, IEquatable + { + /// + /// Min value of + /// + public static readonly Date MinValue = new Date(1, 1, 1); + + /// + /// Max value of + /// + public static readonly Date MaxValue = new Date(9999, 12, 31); + + /// + /// Internal using System.DateTime + /// + private DateTime dateTime; + + /// + /// Consturctor of + /// + /// Year value of date + /// Month value of date + /// Day value of date + public Date(int year, int month, int day) + : this() + { + try + { + this.dateTime = new DateTime(year, month, day); + } + catch (System.ArgumentOutOfRangeException) + { + throw new FormatException(Strings.Date_InvalidDateParameters(year, month, day)); + } + } + + /// + /// Gets a object that is set to current date on this computer, expressed as the local time. + /// + public static Date Now + { + get { return DateTime.Now; } + } + + /// + /// Gets the year component of the date represented by this instance. + /// + public int Year + { + get + { + return dateTime.Year; + } + } + + /// + /// Gets the month component of the date represented by this instance. + /// + public int Month + { + get + { + return dateTime.Month; + } + } + + /// + /// Gets the day of the month represented by this instance. + /// + public int Day + { + get + { + return dateTime.Day; + } + } + + /// + /// Determines whether two specified instances of are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if firstOperand and secondOperand represent the same date; otherwise, false. + public static bool operator ==(Date firstOperand, Date secondOperand) + { + return firstOperand.dateTime == secondOperand.dateTime; + } + + /// + /// Determines whether two specified instances of are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if firstOperand and secondOperand do not represent the same date; otherwise, false. + public static bool operator !=(Date firstOperand, Date secondOperand) + { + return firstOperand.dateTime != secondOperand.dateTime; + } + + /// + /// Determines whether one specified is less than + /// another specified + /// + /// The first object to compare. + /// The second object to compare. + /// true if firstOperand is less than secondOperand; otherwise, false. + public static bool operator <(Date firstOperand, Date secondOperand) + { + return firstOperand.dateTime < secondOperand.dateTime; + } + + /// + /// Determines whether one specified is less equal to + /// another specified + /// + /// The first object to compare. + /// The second object to compare. + /// true if firstOperand is less equal to secondOperand; otherwise, false. + public static bool operator <=(Date firstOperand, Date secondOperand) + { + return firstOperand.dateTime <= secondOperand.dateTime; + } + + /// + /// Determines whether one specified is greater than + /// another specified + /// + /// The first object to compare. + /// The second object to compare. + /// true if firstOperand is greater than secondOperand; otherwise, false. + public static bool operator >(Date firstOperand, Date secondOperand) + { + return firstOperand.dateTime > secondOperand.dateTime; + } + + /// + /// Determines whether one specified is greater equal to + /// another specified + /// + /// The first object to compare. + /// The second object to compare. + /// true if firstOperand is greater equal to secondOperand; otherwise, false. + public static bool operator >=(Date firstOperand, Date secondOperand) + { + return firstOperand.dateTime >= secondOperand.dateTime; + } + + /// + /// Returns a new Date that adds the specified number of years to the value of this instance. + /// + /// A number of years. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of years represented by value + public Date AddYears(int value) + { + try + { + return this.dateTime.AddYears(value); + } + catch (System.ArgumentOutOfRangeException) + { + throw new ArgumentOutOfRangeException("value", Strings.Date_InvalidAddedOrSubtractedResults); + } + } + + /// + /// Returns a new Date that adds the specified number of months to the value of this instance. + /// + /// A number of months. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of months represented by value + public Date AddMonths(int value) + { + try + { + return this.dateTime.AddMonths(value); + } + catch (System.ArgumentOutOfRangeException) + { + throw new ArgumentOutOfRangeException("value", Strings.Date_InvalidAddedOrSubtractedResults); + } + } + + /// + /// Returns a new Date that adds the specified number of days to the value of this instance. + /// + /// A number of days. The value parameter can be negative or positive. + /// An object whose value is the sum of the date and time represented by this instance and the number of days represented by value + public Date AddDays(int value) + { + try + { + return this.dateTime.AddDays(value); + } + catch (System.ArgumentOutOfRangeException) + { + throw new ArgumentOutOfRangeException("value", Strings.Date_InvalidAddedOrSubtractedResults); + } + } + + /// + /// Convert Date to Clr DateTime + /// + /// Date Value + /// DateTime Value which represent the Date + public static implicit operator DateTime(Date operand) + { + return operand.dateTime; + } + + /// + /// Convert Clr DateTime to Date + /// + /// DateTime Value + /// Date Value from DateTime + public static implicit operator Date(DateTime operand) + { + return new Date(operand.Year, operand.Month, operand.Day); + } + + /// + /// Convert Date to String + /// + /// string value of Date + public override string ToString() + { + return this.dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + } + + /// + /// Compares the value of this instance to a object value + /// and returns an integer that indicates whether this instance is earlier than, + /// the same as, or later than the object if it is a Date. + /// + /// The object to compare to the current instance + /// Value Description Less than zero This instance is earlier than value. + /// Zero This instance is the same as value. + /// Greater than zero This instance is later than value. + public int CompareTo(object obj) + { + if (obj is Date) + { + Date date2 = (Date)obj; + return this.CompareTo(date2); + } + else + { + throw new ArgumentException(Strings.Date_InvalidCompareToTarget(obj)); + } + } + + /// + /// Compares the value of this instance to a specified Date value + /// and returns an integer that indicates whether this instance is earlier than, + /// the same as, or later than the specified Date value. + /// + /// The object to compare to the current instance + /// Value Description Less than zero This instance is earlier than value. + /// Zero This instance is the same as value. + /// Greater than zero This instance is later than value. + public int CompareTo(Date other) + { + return this.dateTime.CompareTo(other.dateTime); + } + + /// + /// Compares the value of this instance to a specified Date value + /// and returns an bool that indicates whether this instance is same as the specified Date value. + /// + /// The object to compare to the current instance + /// True for equal, false for non-equal. + public bool Equals(Date other) + { + return this.dateTime.Equals(other.dateTime); + } + + /// + /// Compares the value of this instance to a specified object value + /// and returns an bool that indicates whether the specified object is + /// and this instance is same as the specified value. + /// + /// The object to compare to the current instance + /// True for equal, false for non-equal. + public override bool Equals(object obj) + { + if (obj == null || !(obj is Date)) + { + return false; + } + + return this.dateTime.Equals(((Date)obj).dateTime); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return this.dateTime.GetHashCode(); + } + + /// + /// Converts a specified string representation of a date to with CurrentCulture format. + /// + /// A string that represent a date to convert. + /// The instance represented by text + public static Date Parse(string text) + { + return Parse(text, CultureInfo.CurrentCulture); + } + + /// + /// Converts a specified string representation of a date to . + /// + /// A string that represent a date to convert. + /// An object that supplies culture-specific formatting information about text. + /// The instance represented by text + public static Date Parse(string text, IFormatProvider provider) + { + Date result; + if (Date.TryParse(text, provider, out result)) + { + return result; + } + else + { + throw new FormatException(Strings.Date_InvalidParsingString(text)); + } + } + + /// + /// Try converts a specified string representation of a date to with CurrentCulture format. + /// + /// A string that represent a date to convert. + /// A object equivalent to the date input, if the conversion succeeded + /// or , if the conversion failed. + /// True if the input parameter is successfully converted; otherwise, false. + public static bool TryParse(string text, out Date result) + { + return TryParse(text, CultureInfo.CurrentCulture, out result); + } + + /// + /// Try converts a specified string representation of a date to + /// + /// A string that represent a date to convert. + /// An object that supplies culture-specific formatting information about text. + /// A object equivalent to the date input, if the conversion succeeded + /// or , if the conversion failed. + /// True if the input parameter is successfully converted; otherwise, false. + public static bool TryParse(string text, IFormatProvider provider, out Date result) + { + DateTime date; + var b = DateTime.TryParse(text, provider, DateTimeStyles.None, out date); + result = date; + return b; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmAction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmAction.cs new file mode 100644 index 0000000..c9f5081 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmAction.cs @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM action. + /// + public class EdmAction : EdmOperation, IEdmAction + { + /// + /// Initializes a new instance of the class. + /// + /// Name of the namespace. + /// The name. + /// Type of the return. + /// if set to true [is bound]. + /// The entity set path expression. + public EdmAction(string namespaceName, string name, IEdmTypeReference returnType, bool isBound, IEdmPathExpression entitySetPathExpression) + : base(namespaceName, name, returnType, isBound, entitySetPathExpression) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the namespace. + /// The name. + /// Type of the return. + public EdmAction(string namespaceName, string name, IEdmTypeReference returnType) + : base(namespaceName, name, returnType) + { + } + + /// + /// Gets the element kind of this schema element kind which is an Action. + /// + public override EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Action; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmActionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmActionImport.cs new file mode 100644 index 0000000..2bd2243 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmActionImport.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM action import. + /// + public class EdmActionImport : EdmOperationImport, IEdmActionImport + { + private const string ActionArgumentNullParameterName = "action"; + + /// + /// Initializes a new instance of the class. + /// + /// The container. + /// The name. + /// The action. + public EdmActionImport(IEdmEntityContainer container, string name, IEdmAction action) + : this(container, name, action, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The container. + /// The name. + /// The action. + /// The entity set expression. + public EdmActionImport(IEdmEntityContainer container, string name, IEdmAction action, IEdmExpression entitySetExpression) + : base(container, action, name, entitySetExpression) + { + EdmUtil.CheckArgumentNull(action, "action"); + + this.Action = action; + } + + /// + /// Gets the action type of the import. + /// + public IEdmAction Action { get; private set; } + + /// + /// Gets the kind of this actionimport, which is always ActionImport. + /// + public override EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.ActionImport; } + } + + /// + /// Indicates the name of the constructor argument that is passed to EdmOperationImport. + /// + /// Returns the name of the operation from this import. + protected override string OperationArgumentNullParameterName() + { + return ActionArgumentNullParameterName; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmBinaryTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmBinaryTypeReference.cs new file mode 100644 index 0000000..cab2a87 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmBinaryTypeReference.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM binary type. + /// + public class EdmBinaryTypeReference : EdmPrimitiveTypeReference, IEdmBinaryTypeReference + { + private readonly bool isUnbounded; + private readonly int? maxLength; + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + public EdmBinaryTypeReference(IEdmPrimitiveType definition, bool isNullable) + : this(definition, isNullable, false, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + /// Denotes whether the max length is the maximum allowed value. + /// Maximum length of a value of this type. + public EdmBinaryTypeReference(IEdmPrimitiveType definition, bool isNullable, bool isUnbounded, int? maxLength) + : base(definition, isNullable) + { + if (isUnbounded && maxLength != null) + { + throw new InvalidOperationException(Edm.Strings.EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull); + } + + this.isUnbounded = isUnbounded; + this.maxLength = maxLength; + } + + /// + /// Gets a value indicating whether this type specifies the maximum allowed length. + /// + public bool IsUnbounded + { + get { return this.isUnbounded; } + } + + /// + /// Gets the maximum length of this type. + /// + public int? MaxLength + { + get { return this.maxLength; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionType.cs new file mode 100644 index 0000000..75e76c1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionType.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM collection type. + /// + public class EdmCollectionType : EdmType, IEdmCollectionType + { + private readonly IEdmTypeReference elementType; + + /// + /// Initializes a new instance of the class. + /// + /// The type of the elements in this collection. + public EdmCollectionType(IEdmTypeReference elementType) + { + EdmUtil.CheckArgumentNull(elementType, "elementType"); + this.elementType = elementType; + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Collection; } + } + + /// + /// Gets the element type of this collection type. + /// + public IEdmTypeReference ElementType + { + get { return this.elementType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionTypeReference.cs new file mode 100644 index 0000000..25cbce7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmCollectionTypeReference.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM collection type. + /// + public class EdmCollectionTypeReference : EdmTypeReference, IEdmCollectionTypeReference + { + /// + /// Initializes a new instance of the class. + /// + /// The type definition this reference refers to. + public EdmCollectionTypeReference(IEdmCollectionType collectionType) + : base(collectionType, GetIsNullable(collectionType)) + { + } + + private static bool GetIsNullable(IEdmCollectionType collectionType) + { + // check if the member type is entity, if yes, pass in default value (true), + // as per spec: A navigation property whose Type attribute specifies a collection MUST NOT + // specify a value for the Nullable attribute as the collection always exists, it may just be empty. + // else replace with the nullable in member type reference + IEdmTypeReference elementType = collectionType.ElementType; + if (elementType == null) + { + return true; + } + + IEdmEntityTypeReference entityReference = elementType as IEdmEntityTypeReference; + return entityReference != null || collectionType.ElementType.IsNullable; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexType.cs new file mode 100644 index 0000000..0fdf5ac --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexType.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM complex type. + /// + public class EdmComplexType : EdmStructuredType, IEdmComplexType, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + /// + /// Initializes a new instance of the class. + /// + /// The namespace this type belongs to. + /// The name of this type within its namespace. + public EdmComplexType(string namespaceName, string name) + : this(namespaceName, name, null, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The namespace this type belongs to. + /// The name of this type within its namespace. + /// The base type of this complex type. + public EdmComplexType(string namespaceName, string name, IEdmComplexType baseType) + : this(namespaceName, name, baseType, false, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The namespace this type belongs to. + /// The name of this type within its namespace. + /// The base type of this complex type. + /// Denotes whether this complex type is abstract. + public EdmComplexType(string namespaceName, string name, IEdmComplexType baseType, bool isAbstract) + : this(namespaceName, name, baseType, isAbstract, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The namespace this type belongs to. + /// The name of this type within its namespace. + /// The base type of this complex type. + /// Denotes whether this complex type is abstract. + /// Denotes if the type is open. + public EdmComplexType(string namespaceName, string name, IEdmComplexType baseType, bool isAbstract, bool isOpen) + : base(isAbstract, isOpen, baseType) + { + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.namespaceName = namespaceName; + this.name = name; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.Name); + } + + /// + /// Gets the schema element kind of this element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the namespace of this element. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the name of this element. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Complex; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexTypeReference.cs new file mode 100644 index 0000000..d7c9e7d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmComplexTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM complex type. + /// + public class EdmComplexTypeReference : EdmTypeReference, IEdmComplexTypeReference + { + /// + /// Initializes a new instance of the class. + /// + /// The type definition this reference refers to. + /// Denotes whether the type can be nullable. + public EdmComplexTypeReference(IEdmComplexType complexType, bool isNullable) + : base(complexType, isNullable) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmConstants.cs new file mode 100644 index 0000000..350830d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmConstants.cs @@ -0,0 +1,92 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm +{ + /// + /// Contains constant values that apply to the EDM model, regardless of source (for CSDL/EDMX specific constants see ). + /// + public static class EdmConstants + { + /// + /// Version 4.0 of EDM, defined in CSDL namespace "http://docs.oasis-open.org/odata/ns/edmx". + /// + public static readonly Version EdmVersion4 = new Version(4, 0); + + /// + /// Version 4.01 of EDM, defined in CSDL namespace "http://docs.oasis-open.org/odata/ns/edmx". + /// + public static readonly Version EdmVersion401 = new Version(4, 1); + + /// + /// The current latest version of EDM. + /// + public static readonly Version EdmVersionLatest = EdmVersion401; + + /// + /// The default version of EDM to use if none is specified. + /// + public static Version EdmVersionDefault = EdmVersion4; + + internal const string EdmVersion401String = "4.01"; + + internal const string EdmNamespace = "Edm"; + internal const string TransientNamespace = "Transient"; + + internal const string XmlPrefix = "xml"; + internal const string XmlNamespacePrefix = "xmlns"; + + /// + /// The URI of annotations that are internal and will not be serialized. + /// + internal const string InternalUri = "http://schemas.microsoft.com/ado/2011/04/edm/internal"; + + /// + /// The local name of the annotation that stores EDM version of a model. + /// + internal const string EdmVersionAnnotation = "EdmVersion"; + + internal const string FacetName_Nullable = "Nullable"; + internal const string FacetName_Precision = "Precision"; + internal const string FacetName_Scale = "Scale"; + internal const string FacetName_MaxLength = "MaxLength"; + internal const string FacetName_Unicode = "Unicode"; + internal const string FacetName_Collation = "Collation"; + internal const string FacetName_Srid = "SRID"; + + internal const string Value_UnknownType = "UnknownType"; + internal const string Value_UnnamedType = "UnnamedType"; + internal const string Value_Max = "max"; + internal const string Value_SridVariable = "Variable"; + internal const string Value_ScaleVariable = "Variable"; + + internal const string Type_Collection = "Collection"; + internal const string Type_Complex = "Complex"; + internal const string Type_Entity = "Entity"; + internal const string Type_EntityReference = "EntityReference"; + internal const string Type_Enum = "Enum"; + internal const string Type_TypeDefinition = "TypeDefinition"; + internal const string Type_Path = "Path"; + + internal const string Type_Primitive = "Primitive"; + internal const string Type_Binary = "Binary"; + internal const string Type_Decimal = "Decimal"; + internal const string Type_String = "String"; + internal const string Type_Stream = "Stream"; + internal const string Type_Spatial = "Spatial"; + internal const string Type_Temporal = "Temporal"; + + internal const string Type_Structured = "Structured"; + + internal const int Max_Precision = Int32.MaxValue; + internal const int Min_Precision = 0; + + /// The attribute name used on service operations and primitive properties to indicate their MIME type. + internal const string MimeTypeAttributeName = "MimeType"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmContainedEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmContainedEntitySet.cs new file mode 100644 index 0000000..ca96d03 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmContainedEntitySet.cs @@ -0,0 +1,186 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents an EDM contained entity set. + /// + internal class EdmContainedEntitySet : EdmEntitySetBase, IEdmContainedEntitySet + { + private readonly IEdmPathExpression navigationPath; + private readonly IEdmNavigationSource parentNavigationSource; + private readonly IEdmNavigationProperty navigationProperty; + private IEdmPathExpression path; + private string fullPath; + + /// + /// Initializes a new instance of the class. + /// + /// The that container element belongs to + /// An containing the navigation property definition of the contained element + public EdmContainedEntitySet(IEdmNavigationSource parentNavigationSource, IEdmNavigationProperty navigationProperty) + : this(parentNavigationSource, navigationProperty, new EdmPathExpression(navigationProperty.Name)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that container element belongs to + /// An containing the navigation property definition of the contained element + /// The path from the parentNavigationSource to the related resource, concluding with the navigation property name. May traverse complex types and cast segments + public EdmContainedEntitySet(IEdmNavigationSource parentNavigationSource, IEdmNavigationProperty navigationProperty, IEdmPathExpression navigationPath) + : base(navigationProperty.Name, navigationProperty.ToEntityType()) + { + EdmUtil.CheckArgumentNull(parentNavigationSource, "parentNavigationSource"); + EdmUtil.CheckArgumentNull(navigationProperty, "navigationProperty"); + + this.parentNavigationSource = parentNavigationSource; + this.navigationProperty = navigationProperty; + this.navigationPath = navigationPath; + } + + /// + /// Gets the path that a navigation property targets. This property is not thread safe. + /// + public override IEdmPathExpression Path + { + get { return this.path ?? (this.path = this.ComputePath()); } + } + + /// The parent navigation source of this contained entity set. + /// The parent navigation source of this contained entity set. + public IEdmNavigationSource ParentNavigationSource + { + get { return this.parentNavigationSource; } + } + + /// The navigation property of this contained entity set. + /// The navigation property of this contained entity set. + public IEdmNavigationProperty NavigationProperty + { + get { return this.navigationProperty; } + } + + internal IEdmPathExpression NavigationPath + { + get + { + return this.navigationPath; + } + } + + private string FullNavigationPath + { + get + { + if (this.fullPath == null) + { + List fullPath = new List(); + EdmContainedEntitySet currentSource = this; + while (currentSource != null) + { + fullPath.AddRange(currentSource.NavigationPath.PathSegments); + currentSource = currentSource.ParentNavigationSource as EdmContainedEntitySet; + } + + fullPath.Reverse(); + this.fullPath = new EdmPathExpression(fullPath).Path; + } + + return this.fullPath; + } + } + + /// + /// Finds the bindings of the navigation property. + /// + /// The navigation property. + /// The list of bindings for current navigation property. + public override IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + IEnumerable bindings = base.FindNavigationPropertyBindings(navigationProperty); + IEdmNavigationSource parent; + IEdmContainedEntitySet contained = this; + IEnumerable parentBindings; + + while (contained != null) + { + parent = contained.ParentNavigationSource; + parentBindings = parent.FindNavigationPropertyBindings(navigationProperty); + if (parentBindings != null) + { + bindings = bindings == null ? parentBindings : bindings.Concat(parentBindings); + } + + contained = parent as IEdmContainedEntitySet; + } + + return bindings; + } + + /// + /// Finds the entity set that a navigation property targets. + /// + /// The navigation property. + /// The entity set that the navigation property targets + public override IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty) + { + return this.FindNavigationTarget(navigationProperty, new EdmPathExpression(navigationProperty.Name)); + } + + /// + /// Finds the entity set that a navigation property targets. + /// + /// The navigation property. + /// The binding path of the navigation property + /// The entity set that the navigation property targets + public override IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + // 7.4.1 expected the path to be prefixed with the path to the contained navigation source. + // For backward compatibility, if the binding path received starts with the path to this contained resource, + // we trim it off and then treat the remainder as the path to the target. This logic should be removed in + // the next breaking change as it could be ambiguous in the case that the prefix of the path to the contained + // source matches a valid path to the target of the contained source. + if (bindingPath != null) + { + if (bindingPath.Path.Length > this.FullNavigationPath.Length && bindingPath.Path.StartsWith(this.FullNavigationPath, System.StringComparison.Ordinal)) + { + bindingPath = new EdmPathExpression(bindingPath.Path.Substring(this.FullNavigationPath.Length + 1)); + } + } + + IEdmNavigationSource navigationTarget = base.FindNavigationTarget(navigationProperty, bindingPath); + + if (navigationTarget is IEdmUnknownEntitySet) + { + IEnumerable segments = (bindingPath == null || string.IsNullOrEmpty(bindingPath.Path)) ? new string[] { navigationProperty.Name } : bindingPath.PathSegments; + bindingPath = new EdmPathExpression(this.NavigationPath.PathSegments.Concat(segments)); + + return this.parentNavigationSource.FindNavigationTarget(navigationProperty, bindingPath); + } + + return navigationTarget; + } + + private IEdmPathExpression ComputePath() + { + IEdmType targetType = this.navigationProperty.DeclaringType.AsElementType(); + List newPath = new List(this.parentNavigationSource.Path.PathSegments); + if (!(targetType is IEdmComplexType) && !this.parentNavigationSource.Type.AsElementType().IsOrInheritsFrom(targetType)) + { + newPath.Add(targetType.FullTypeName()); + } + + newPath.AddRange(this.NavigationPath.PathSegments); + return new EdmPathExpression(newPath.ToArray()); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmDecimalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmDecimalTypeReference.cs new file mode 100644 index 0000000..d906d63 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmDecimalTypeReference.cs @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using Microsoft.OData.Edm.Csdl; + + /// + /// Represents a reference to an EDM decimal type. + /// + public class EdmDecimalTypeReference : EdmPrimitiveTypeReference, IEdmDecimalTypeReference + { + private readonly int? precision; + private readonly int? scale; + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + public EdmDecimalTypeReference(IEdmPrimitiveType definition, bool isNullable) + : this(definition, isNullable, null, CsdlConstants.Default_Scale) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + /// Precision of values with this type. + /// Scale of values with this type. + public EdmDecimalTypeReference(IEdmPrimitiveType definition, bool isNullable, int? precision, int? scale) + : base(definition, isNullable) + { + this.precision = precision; + this.scale = scale; + } + + /// + /// Gets the precision of this type. + /// + public int? Precision + { + get { return this.precision; } + } + + /// + /// Gets the scale of this type. + /// + public int? Scale + { + get { return this.scale; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmElement.cs new file mode 100644 index 0000000..f0a339c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmElement.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Common base class for all EDM elements. + /// + public abstract class EdmElement : IEdmElement + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityContainer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityContainer.cs new file mode 100644 index 0000000..cb7b819 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityContainer.cs @@ -0,0 +1,295 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM entity container. + /// + public class EdmEntityContainer : EdmElement, IEdmEntityContainer, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + private readonly List containerElements = new List(); + private readonly Dictionary entitySetDictionary = new Dictionary(); + private readonly Dictionary singletonDictionary = new Dictionary(); + private readonly Dictionary operationImportDictionary = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// Namespace of the entity container. + /// Name of the entity container. + public EdmEntityContainer(string namespaceName, string name) + { + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.namespaceName = namespaceName; + this.name = name; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.Name); + } + + /// + /// Gets a collection of the elements of this entity container. + /// + public IEnumerable Elements + { + get { return this.containerElements; } + } + + /// + /// Gets the namespace of this entity container. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the name of this entity container. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the kind of this schema element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.EntityContainer; } + } + + /// + /// Adds an entity container element to this entity container. + /// + /// The element to add. + public void AddElement(IEdmEntityContainerElement element) + { + EdmUtil.CheckArgumentNull(element, "element"); + + this.containerElements.Add(element); + + switch (element.ContainerElementKind) + { + case EdmContainerElementKind.EntitySet: + RegistrationHelper.AddElement((IEdmEntitySet)element, element.Name, this.entitySetDictionary, RegistrationHelper.CreateAmbiguousEntitySetBinding); + break; + case EdmContainerElementKind.Singleton: + RegistrationHelper.AddElement((IEdmSingleton)element, element.Name, this.singletonDictionary, RegistrationHelper.CreateAmbiguousSingletonBinding); + break; + case EdmContainerElementKind.ActionImport: + case EdmContainerElementKind.FunctionImport: + RegistrationHelper.AddOperationImport((IEdmOperationImport)element, element.Name, this.operationImportDictionary); + break; + case EdmContainerElementKind.None: + throw new InvalidOperationException(Edm.Strings.EdmEntityContainer_CannotUseElementWithTypeNone); + default: + throw new InvalidOperationException(Edm.Strings.UnknownEnumVal_ContainerElementKind(element.ContainerElementKind)); + } + } + + /// + /// Creates and adds an entity set to this entity container. + /// + /// Name of the entity set. + /// The entity type of the elements in this entity set. + /// Created entity set. + public virtual EdmEntitySet AddEntitySet(string name, IEdmEntityType elementType) + { + EdmEntitySet entitySet = new EdmEntitySet(this, name, elementType); + this.AddElement(entitySet); + return entitySet; + } + + /// + /// Creates and adds an entity set to this entity container. + /// + /// Name of the entity set. + /// The entity type of the elements in this entity set. + /// Indicates whether the entity set is advertised in the service document. + /// Created entity set. + public virtual EdmEntitySet AddEntitySet(string name, IEdmEntityType elementType, bool includeInServiceDocument) + { + EdmEntitySet entitySet = new EdmEntitySet(this, name, elementType, includeInServiceDocument); + this.AddElement(entitySet); + return entitySet; + } + + /// + /// Creates and adds an singleton to this entity container. + /// + /// Name of the singleton. + /// The entity type of this singleton. + /// Created singleton. + public virtual EdmSingleton AddSingleton(string name, IEdmEntityType entityType) + { + EdmSingleton singleton = new EdmSingleton(this, name, entityType); + this.AddElement(singleton); + return singleton; + } + + /// + /// Creates and adds a function import to this entity container. + /// + /// The function of the specified function import. + /// Created function import. + public virtual EdmFunctionImport AddFunctionImport(IEdmFunction function) + { + EdmFunctionImport functionImport = new EdmFunctionImport(this, function.Name, function); + this.AddElement(functionImport); + return functionImport; + } + + /// + /// Creates and adds a function import to this entity container. + /// + /// Name of the function import. + /// The function of the specified function import. + /// Created function import. + public virtual EdmFunctionImport AddFunctionImport(string name, IEdmFunction function) + { + EdmFunctionImport functionImport = new EdmFunctionImport(this, name, function); + this.AddElement(functionImport); + return functionImport; + } + + /// + /// Creates and adds a function import to this entity container. + /// + /// Name of the function import. + /// The function of the specified function import. + /// An entity set containing entities returned by this function import. + /// The expression kind supported is . + /// Created function import. + public virtual EdmFunctionImport AddFunctionImport(string name, IEdmFunction function, IEdmExpression entitySet) + { + EdmFunctionImport functionImport = new EdmFunctionImport(this, name, function, entitySet, false /*includeInServiceDocument*/); + this.AddElement(functionImport); + return functionImport; + } + + /// + /// Creates and adds a function import to this entity container. + /// + /// Name of the function import. + /// The function of the specified function import. + /// An entity set containing entities returned by this function import. + /// The expression kind supported is . + /// A value indicating whether this function import will be in the service document. + /// Created operation import. + public virtual EdmOperationImport AddFunctionImport(string name, IEdmFunction function, IEdmExpression entitySet, bool includeInServiceDocument) + { + EdmOperationImport functionImport = new EdmFunctionImport(this, name, function, entitySet, includeInServiceDocument); + this.AddElement(functionImport); + return functionImport; + } + + /// + /// Creates and adds an action import to this entity container. + /// + /// Name of the action import. + /// Action that the action import is importing to the container. + /// An entity set containing entities returned by this action import. + /// The expression kind supported is . + /// Created action import. + public virtual EdmActionImport AddActionImport(string name, IEdmAction action, IEdmExpression entitySet) + { + EdmActionImport actionImport = new EdmActionImport(this, name, action, entitySet); + this.AddElement(actionImport); + return actionImport; + } + + /// + /// Creates and adds an action import to this entity container. + /// + /// Action that the action import is importing to the container. + /// Created action import. + public virtual EdmActionImport AddActionImport(IEdmAction action) + { + EdmActionImport actionImport = new EdmActionImport(this, action.Name, action, null); + this.AddElement(actionImport); + return actionImport; + } + + /// + /// Creates and adds an action import to this entity container. + /// + /// Name of the action import. + /// Action that the action import is importing to the container. + /// Created action import. + public virtual EdmActionImport AddActionImport(string name, IEdmAction action) + { + EdmActionImport actionImport = new EdmActionImport(this, name, action, null); + this.AddElement(actionImport); + return actionImport; + } + + /// + /// Searches for an entity set with the given name in this entity container and returns null if no such set exists. + /// + /// The name of the element being found. + /// The requested element, or null if the element does not exist. + public virtual IEdmEntitySet FindEntitySet(string setName) + { + if (!string.IsNullOrEmpty(setName)) + { + IEdmEntitySet element; + return this.entitySetDictionary.TryGetValue(setName, out element) ? element : null; + } + + return null; + } + + /// + /// Searches for a singleton with the given name in this entity container and returns null if no such singleton exists. + /// + /// The name of the singleton to search. + /// The requested singleton, or null if the singleton does not exist. + public virtual IEdmSingleton FindSingleton(string singletonName) + { + IEdmSingleton element; + return this.singletonDictionary.TryGetValue(singletonName, out element) ? element : null; + } + + /// + /// Searches for operation imports with the given name in this entity container and returns null if no such operation import exists. + /// + /// The name of the operation to find. + /// A group of the requested operation imports, or an empty enumerable if no such operation import exists. + public IEnumerable FindOperationImports(string operationName) + { + object element; + if (this.operationImportDictionary.TryGetValue(operationName, out element)) + { + List listElement = element as List; + if (listElement != null) + { + return listElement; + } + + return new IEdmOperationImport[] { (IEdmOperationImport)element }; + } + + return Enumerable.Empty(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceType.cs new file mode 100644 index 0000000..ffaa62c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceType.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM entity reference type. + /// + public class EdmEntityReferenceType : EdmType, IEdmEntityReferenceType + { + private readonly IEdmEntityType entityType; + + /// + /// Initializes a new instance of the class. + /// + /// The entity referred to by this entity reference. + public EdmEntityReferenceType(IEdmEntityType entityType) + { + EdmUtil.CheckArgumentNull(entityType, "entityType"); + this.entityType = entityType; + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.EntityReference; } + } + + /// + /// Gets the entity type pointed to by this entity reference. + /// + public IEdmEntityType EntityType + { + get { return this.entityType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceTypeReference.cs new file mode 100644 index 0000000..92ea7d1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityReferenceTypeReference.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM entity reference type. + /// + public class EdmEntityReferenceTypeReference : EdmTypeReference, IEdmEntityReferenceTypeReference + { + /// + /// Initializes a new instance of the class. + /// + /// The definition referred to by this reference. + /// Denotes whether the type can be nullable. + public EdmEntityReferenceTypeReference(IEdmEntityReferenceType entityReferenceType, bool isNullable) + : base(entityReferenceType, isNullable) + { + } + + /// + /// Gets the entity reference definition to which this type refers. + /// + public IEdmEntityReferenceType EntityReferenceDefinition + { + get { return (IEdmEntityReferenceType)Definition; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySet.cs new file mode 100644 index 0000000..9a5271f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySet.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM entity set. + /// + public class EdmEntitySet : EdmEntitySetBase, IEdmEntitySet + { + private readonly IEdmEntityContainer container; + private readonly IEdmCollectionType type; + private IEdmPathExpression path; + private bool includeInServiceDocument; + + /// + /// Initializes a new instance of the class. + /// + /// An containing this entity set. + /// Name of the entity set. + /// The entity type of the elements in this entity set. + public EdmEntitySet(IEdmEntityContainer container, string name, IEdmEntityType elementType) + : this(container, name, elementType, true) + { + EdmUtil.CheckArgumentNull(container, "container"); + + this.container = container; + this.type = new EdmCollectionType(new EdmEntityTypeReference(elementType, false)); + this.path = new EdmPathExpression(this.container.FullName() + "." + this.Name); + } + + /// + /// Initializes a new instance of the class. + /// + /// An containing this entity set. + /// Name of the entity set. + /// The entity type of the elements in this entity set. + /// Indicates whether the entity set is advertised in the service document. + public EdmEntitySet(IEdmEntityContainer container, string name, IEdmEntityType elementType, bool includeInServiceDocument) + : base(name, elementType) + { + this.includeInServiceDocument = includeInServiceDocument; + } + + /// + /// Gets the kind of element of this container element. + /// + public EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.EntitySet; } + } + + /// + /// Gets the container of this entity set. + /// + public IEdmEntityContainer Container + { + get { return this.container; } + } + + /// + /// Gets the type of this entity set. + /// + public override IEdmType Type + { + get { return this.type; } + } + + /// + /// Gets the path that a navigation property targets. + /// + public override IEdmPathExpression Path + { + get { return this.path; } + } + + /// + /// Gets a value indicating whether the entity set is included in the service document. + /// + public bool IncludeInServiceDocument + { + get { return includeInServiceDocument; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySetBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySetBase.cs new file mode 100644 index 0000000..8494927 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntitySetBase.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an abstract EDM entity set base. + /// + public abstract class EdmEntitySetBase : EdmNavigationSource, IEdmEntitySetBase + { + private readonly IEdmCollectionType type; + + /// + /// Initializes a new instance of the class. + /// + /// Name of the entity set base. + /// The entity type of the elements in this entity set base. + protected EdmEntitySetBase(string name, IEdmEntityType elementType) + : base(name) + { + EdmUtil.CheckArgumentNull(elementType, "elementType"); + + this.type = new EdmCollectionType(new EdmEntityTypeReference(elementType, false)); + } + + /// + /// Gets the type of this navigation source. + /// + public override IEdmType Type + { + get { return this.type; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs new file mode 100644 index 0000000..60a720b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs @@ -0,0 +1,269 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM entity type. + /// + public class EdmEntityType : EdmStructuredType, IEdmEntityType, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + private readonly bool hasStream; + private List declaredKey; + + /// + /// Initializes a new instance of the class. + /// + /// Namespace the entity belongs to. + /// Name of the entity. + public EdmEntityType(string namespaceName, string name) + : this(namespaceName, name, null, false, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + public EdmEntityType(string namespaceName, string name, IEdmEntityType baseType) + : this(namespaceName, name, baseType, false, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + /// Denotes an entity that cannot be instantiated. + /// Denotes if the type is open. + public EdmEntityType(string namespaceName, string name, IEdmEntityType baseType, bool isAbstract, bool isOpen) + : this(namespaceName, name, baseType, isAbstract, isOpen, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace the entity belongs to. + /// Name of the entity. + /// The base type of this entity type. + /// Denotes an entity that cannot be instantiated. + /// Denotes if the type is open. + /// Denotes if the type is a media type. + public EdmEntityType(string namespaceName, string name, IEdmEntityType baseType, bool isAbstract, bool isOpen, bool hasStream) + : base(isAbstract, isOpen, baseType) + { + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.namespaceName = namespaceName; + this.name = name; + this.hasStream = hasStream; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.Name); + } + + /// + /// Gets the structural properties of the entity type that make up the entity key. + /// + public virtual IEnumerable DeclaredKey + { + get { return this.declaredKey; } + } + + /// + /// Gets the kind of this schema element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the namespace this schema element belongs to. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the name of this element. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Entity; } + } + + /// + /// Gets the value indicating whether or not this entity is a media type + /// This value inherits from the base type. + /// + public bool HasStream + { + get { return hasStream || (this.BaseType != null && this.BaseEntityType().HasStream); } + } + + /// + /// Adds the to the key of this entity type. + /// + /// The key properties. + public void AddKeys(params IEdmStructuralProperty[] keyProperties) + { + this.AddKeys((IEnumerable)keyProperties); + } + + /// + /// Adds the to the key of this entity type. + /// + /// The key properties. + public void AddKeys(IEnumerable keyProperties) + { + EdmUtil.CheckArgumentNull(keyProperties, "keyProperties"); + + foreach (IEdmStructuralProperty property in keyProperties) + { + if (this.declaredKey == null) + { + this.declaredKey = new List(); + } + + this.declaredKey.Add(property); + } + } + + /// + /// Creates and adds a navigation property to this type and adds its navigation partner to the navigation target type. + /// + /// Information to create the navigation property. + /// Information to create the partner navigation property. + /// Created navigation property. + public EdmNavigationProperty AddBidirectionalNavigation(EdmNavigationPropertyInfo propertyInfo, EdmNavigationPropertyInfo partnerInfo) + { + EdmUtil.CheckArgumentNull(propertyInfo, "propertyInfo"); + EdmUtil.CheckArgumentNull(propertyInfo.Target, "propertyInfo.Target"); + + EdmEntityType targetType = propertyInfo.Target as EdmEntityType; + if (targetType == null) + { + throw new ArgumentException("propertyInfo.Target", Strings.Constructable_TargetMustBeStock(typeof(EdmEntityType).FullName)); + } + + EdmNavigationProperty property = EdmNavigationProperty.CreateNavigationPropertyWithPartner(propertyInfo, this.FixUpDefaultPartnerInfo(propertyInfo, partnerInfo)); + + this.AddProperty(property); + targetType.AddProperty(property.Partner); + return property; + } + + /// + /// Sets partner information of a top-level navigation property. + /// + /// Navigation property of the entity type. + /// Path to the navigation property of the entity type. + /// Partner navigation property. + /// Path to the partner navigation property + /// from the related entity type. + /// + /// If partnerNavigationProperty is declared on an entity type, its partner will be set accordingly; there is no + /// need to call this method twice, once on each side. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] + public void SetNavigationPropertyPartner( + EdmNavigationProperty navigationProperty, + IEdmPathExpression navigationPropertyPath, + EdmNavigationProperty partnerNavigationProperty, + IEdmPathExpression partnerNavigationPropertyPath) + { + Debug.Assert( + this.IsOnSameTypeHierarchyLineWith(navigationProperty.DeclaringType), + "Nav. property is not defined on this or a derived entity type."); + navigationProperty.SetPartner(partnerNavigationProperty, partnerNavigationPropertyPath); + if (partnerNavigationProperty.DeclaringType is IEdmEntityType) + { + partnerNavigationProperty.SetPartner(navigationProperty, navigationPropertyPath); + } + } + + /// + /// The purpose of this method is to make sure that some of the fields are set to valid partner defaults. + /// For example if .Target is null, it will be set to this entity type. If .TargetMultiplicity + /// is unknown, it will be set to 0..1, etc. + /// Whenever this method applies new values to , it will return a copy of it (thus won't modify the original). + /// If is null, a new info object will be produced. + /// + /// Primary navigation property info. + /// Partner navigation property info. May be null. + /// Partner info. + private EdmNavigationPropertyInfo FixUpDefaultPartnerInfo(EdmNavigationPropertyInfo propertyInfo, EdmNavigationPropertyInfo partnerInfo) + { + EdmNavigationPropertyInfo partnerInfoOverride = null; + + if (partnerInfo == null) + { + partnerInfo = partnerInfoOverride = new EdmNavigationPropertyInfo(); + } + + if (partnerInfo.Name == null) + { + if (partnerInfoOverride == null) + { + partnerInfoOverride = partnerInfo.Clone(); + } + + partnerInfoOverride.Name = (propertyInfo.Name ?? String.Empty) + "Partner"; + } + + if (partnerInfo.Target == null) + { + if (partnerInfoOverride == null) + { + partnerInfoOverride = partnerInfo.Clone(); + } + + partnerInfoOverride.Target = this; + } + + if (partnerInfo.TargetMultiplicity == EdmMultiplicity.Unknown) + { + if (partnerInfoOverride == null) + { + partnerInfoOverride = partnerInfo.Clone(); + } + + partnerInfoOverride.TargetMultiplicity = EdmMultiplicity.ZeroOrOne; + } + + return partnerInfoOverride ?? partnerInfo; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityTypeReference.cs new file mode 100644 index 0000000..2b1b3a6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEntityTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM entity type. + /// + public class EdmEntityTypeReference : EdmTypeReference, IEdmEntityTypeReference + { + /// + /// Initializes a new instance of the class. + /// + /// The definition refered to by this reference. + /// Denotes whether the type can be nullable. + public EdmEntityTypeReference(IEdmEntityType entityType, bool isNullable) + : base(entityType, isNullable) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMember.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMember.cs new file mode 100644 index 0000000..329bca6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMember.cs @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a member of an EDM enumeration type. + /// + public class EdmEnumMember : EdmNamedElement, IEdmEnumMember + { + private readonly IEdmEnumType declaringType; + private IEdmEnumMemberValue value; + + /// + /// Initializes a new instance of the class. + /// + /// The type that declares this member. + /// Name of this enumeration member. + /// Value of this enumeration member. + public EdmEnumMember(IEdmEnumType declaringType, string name, IEdmEnumMemberValue value) + : base(name) + { + EdmUtil.CheckArgumentNull(declaringType, "declaringType"); + EdmUtil.CheckArgumentNull(value, "value"); + + this.declaringType = declaringType; + this.value = value; + } + + /// + /// Gets the type that this member belongs to. + /// + public IEdmEnumType DeclaringType + { + get { return this.declaringType; } + } + + /// + /// Gets the value of this enumeration type member. + /// + public IEdmEnumMemberValue Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMemberValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMemberValue.cs new file mode 100644 index 0000000..adcbe51 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumMemberValue.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The edm enum member value. + /// + public class EdmEnumMemberValue : IEdmEnumMemberValue + { + private readonly long value; + + /// + /// Initializes a new instance of the class. + /// + /// The value of enum member + public EdmEnumMemberValue(long value) + { + this.value = value; + } + + /// + /// Gets the value of enum member + /// + public long Value + { + get + { + return this.value; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumType.cs new file mode 100644 index 0000000..65c53d5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumType.cs @@ -0,0 +1,162 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents the definition of an Edm enumeration type. + /// + public class EdmEnumType : EdmType, IEdmEnumType, IEdmFullNamedElement + { + private readonly IEdmPrimitiveType underlyingType; + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + private readonly bool isFlags; + private readonly List members = new List(); + + /// + /// Initializes a new instance of the class with underlying type. + /// + /// Namespace this enumeration type belongs to. + /// Name of this enumeration type. + public EdmEnumType(string namespaceName, string name) + : this(namespaceName, name, false) + { + } + + /// + /// Initializes a new instance of the class with underlying type. + /// + /// Namespace this enumeration type belongs to. + /// Name of this enumeration type. + /// A value indicating whether the enumeration type can be treated as a bit field. + public EdmEnumType(string namespaceName, string name, bool isFlags) + : this(namespaceName, name, EdmPrimitiveTypeKind.Int32, isFlags) + { + } + + /// + /// Initializes a new instance of the class with underlying type. + /// + /// Namespace this enumeration type belongs to. + /// Name of this enumeration type. + /// The underlying type of this enumeration type. + /// A value indicating whether the enumeration type can be treated as a bit field. + public EdmEnumType(string namespaceName, string name, EdmPrimitiveTypeKind underlyingType, bool isFlags) + : this(namespaceName, name, EdmCoreModel.Instance.GetPrimitiveType(underlyingType), isFlags) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace this enumeration type belongs to. + /// Name of this enumeration type. + /// The underlying type of this enumeration type. + /// A value indicating whether the enumeration type can be treated as a bit field. + public EdmEnumType(string namespaceName, string name, IEdmPrimitiveType underlyingType, bool isFlags) + { + EdmUtil.CheckArgumentNull(underlyingType, "underlyingType"); + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.underlyingType = underlyingType; + this.name = name; + this.namespaceName = namespaceName; + this.isFlags = isFlags; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.name); + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.Enum; } + } + + /// + /// Gets the kind of this schema element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the namespace this schema element belongs to. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the name of this enumeration type. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the underlying type of this enumeration type. + /// + public IEdmPrimitiveType UnderlyingType + { + get { return this.underlyingType; } + } + + /// + /// Gets the members of this enumeration type. + /// + public virtual IEnumerable Members + { + get { return this.members; } + } + + /// + /// Gets a value indicating whether the enumeration type can be treated as a bit field. + /// + public bool IsFlags + { + get { return this.isFlags; } + } + + /// + /// Adds a new member to this enum type. + /// + /// The member to add. + public void AddMember(IEdmEnumMember member) + { + this.members.Add(member); + } + + /// + /// Creates and adds a new member to this enum type. + /// + /// Name of the member. + /// Value of the member. + /// Created member. + public EdmEnumMember AddMember(string name, IEdmEnumMemberValue value) + { + EdmEnumMember member = new EdmEnumMember(this, name, value); + this.AddMember(member); + return member; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumTypeReference.cs new file mode 100644 index 0000000..baee971 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmEnumTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM enumeration type. + /// + public class EdmEnumTypeReference : EdmTypeReference, IEdmEnumTypeReference + { + /// + /// Initializes a new instance of the class. + /// + /// The definition refered to by this reference. + /// Denotes whether the type can be nullable. + public EdmEnumTypeReference(IEdmEnumType enumType, bool isNullable) + : base(enumType, isNullable) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunction.cs new file mode 100644 index 0000000..f24ba1e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunction.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM function. + /// + public class EdmFunction : EdmOperation, IEdmFunction + { + /// + /// Initializes a new instance of the class. + /// + /// Name of the namespace. + /// The name. + /// Type of the return. + /// if set to true [is bound]. + /// The entity set path expression. + /// A value indicating if the function is composable or not. + public EdmFunction(string namespaceName, string name, IEdmTypeReference returnType, bool isBound, IEdmPathExpression entitySetPathExpression, bool isComposable) + : base(namespaceName, name, returnType, isBound, entitySetPathExpression) + { + EdmUtil.CheckArgumentNull(returnType, "returnType"); + this.IsComposable = isComposable; + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace of the function. + /// Name of the function. + /// Return type of the function. + public EdmFunction(string namespaceName, string name, IEdmTypeReference returnType) + : this(namespaceName, name, returnType, false /*isBound*/, null, false /*isComposable*/) + { + } + + /// + /// Gets the element kind of this operation, which is always Operation. + /// virtual will be removed in the near future, stop gap to enable testing for now. + /// + public override EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Function; } + } + + /// + /// Gets a value indicating whether this instance is composable. + /// + public bool IsComposable { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunctionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunctionImport.cs new file mode 100644 index 0000000..300b1e5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmFunctionImport.cs @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM function import. + /// + public class EdmFunctionImport : EdmOperationImport, IEdmFunctionImport + { + private const string FunctionArgumentNullParameterName = "function"; + + /// + /// Initializes a new instance of the class. + /// + /// The container. + /// The name. + /// The function. + public EdmFunctionImport(IEdmEntityContainer container, string name, IEdmFunction function) + : this(container, name, function, null, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The container. + /// The name. + /// The function. + /// The entity set expression. + /// The value indicates if the function is to be include in the service document or not. + public EdmFunctionImport(IEdmEntityContainer container, string name, IEdmFunction function, IEdmExpression entitySetExpression, bool includeInServiceDocument) + : base(container, function, name, entitySetExpression) + { + EdmUtil.CheckArgumentNull(function, "function"); + + this.Function = function; + this.IncludeInServiceDocument = includeInServiceDocument; + } + + /// + /// Gets the function that defines the function import. + /// + public IEdmFunction Function { get; private set; } + + /// + /// Gets the kind of this operation, which is always FunctionImport. + /// + public override EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.FunctionImport; } + } + + /// + /// Gets a value indicating whether [include in service document]. + /// + public bool IncludeInServiceDocument { get; private set; } + + /// + /// Operations the name of the argument null parameter. + /// + /// Returns the name of the operation from this import. + protected override string OperationArgumentNullParameterName() + { + return FunctionArgumentNullParameterName; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmInclude.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmInclude.cs new file mode 100644 index 0000000..66d1c23 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmInclude.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The include information for referenced model. + /// + public class EdmInclude : IEdmInclude + { + private readonly string alias; + private readonly string namespaceIncluded; + + /// + /// constructor. + /// + /// The alias. + /// The namespace. + public EdmInclude(string alias, string namespaceIncluded) + { + this.alias = alias; + this.namespaceIncluded = namespaceIncluded; + } + + /// + /// Gets the alias. + /// + public string Alias + { + get + { + return this.alias; + } + } + + /// + /// Gets the namespace to include. + /// + public string Namespace + { + get + { + return this.namespaceIncluded; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmIncludeAnnotations.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmIncludeAnnotations.cs new file mode 100644 index 0000000..e5b2149 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmIncludeAnnotations.cs @@ -0,0 +1,64 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The includeAnnotation information for referenced model. + /// + public class EdmIncludeAnnotations : IEdmIncludeAnnotations + { + private readonly string termNamespace; + private readonly string qualifier; + private readonly string targetNamespace; + + /// + /// Constructor + /// + /// The term namespace. + /// The qualifier. + /// The target namespace. + public EdmIncludeAnnotations(string termNamespace, string qualifier, string targetNamespace) + { + this.termNamespace = termNamespace; + this.qualifier = qualifier; + this.targetNamespace = targetNamespace; + } + + /// + /// Get the term namespace. + /// + public string TermNamespace + { + get + { + return this.termNamespace; + } + } + + /// + /// Gets the qualifier. + /// + public string Qualifier + { + get + { + return this.qualifier; + } + } + + /// + /// Gets the target namespace. + /// + public string TargetNamespace + { + get + { + return this.targetNamespace; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModel.cs new file mode 100644 index 0000000..2ba9e4c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModel.cs @@ -0,0 +1,190 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM model. + /// + public class EdmModel : EdmModelBase + { + private readonly List elements = new List(); + private readonly Dictionary> vocabularyAnnotationsDictionary = new Dictionary>(); + private readonly Dictionary> derivedTypeMappings = new Dictionary>(); + private readonly HashSet declaredNamespaces = new HashSet(); + + /// + /// Initializes a new instance of the class. + /// + public EdmModel() + : this(true /*includeDefaultVocabularies*/) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// a boolean value indicating whether to embed the built-in vocabulary models. + public EdmModel(bool includeDefaultVocabularies) + : base(Enumerable.Empty(), new EdmDirectValueAnnotationsManager(), includeDefaultVocabularies) + { + } + + /// + /// Gets the collection of schema elements that are contained in this model and referenced models. + /// + public override IEnumerable SchemaElements + { + // TODO: REF plus referneced elements + get { return this.elements; } + } + + /// + /// Gets the collection of namespaces that schema elements use contained in this model. + /// + public override IEnumerable DeclaredNamespaces + { + get { return this.declaredNamespaces; } + } + + /// + /// Gets the collection of vocabulary annotations that are contained in this model. + /// + public override IEnumerable VocabularyAnnotations + { + get { return this.vocabularyAnnotationsDictionary.SelectMany(kvp => kvp.Value); } + } + + /// + /// Adds a model reference to this model. + /// + /// The model to be referenced. + public new void AddReferencedModel(IEdmModel model) + { + base.AddReferencedModel(model); + } + + /// + /// Adds a schema element to this model. + /// + /// Element to be added. + public void AddElement(IEdmSchemaElement element) + { + if (!this.declaredNamespaces.Contains(element.Namespace)) + { + this.declaredNamespaces.Add(element.Namespace); + } + + EdmUtil.CheckArgumentNull(element, "element"); + this.elements.Add(element); + IEdmStructuredType structuredType = element as IEdmStructuredType; + if (structuredType != null && structuredType.BaseType != null) + { + List derivedTypes; + if (!this.derivedTypeMappings.TryGetValue(structuredType.BaseType, out derivedTypes)) + { + derivedTypes = new List(); + this.derivedTypeMappings[structuredType.BaseType] = derivedTypes; + } + + derivedTypes.Add(structuredType); + } + + this.RegisterElement(element); + } + + /// + /// Adds a collection of schema elements to this model. + /// + /// Elements to be added. + public void AddElements(IEnumerable newElements) + { + EdmUtil.CheckArgumentNull(newElements, "newElements"); + foreach (IEdmSchemaElement element in newElements) + { + this.AddElement(element); + } + } + + /// + /// Adds a vocabulary annotation to this model. + /// + /// The annotation to be added. + public void AddVocabularyAnnotation(IEdmVocabularyAnnotation annotation) + { + EdmUtil.CheckArgumentNull(annotation, "annotation"); + if (annotation.Target == null) + { + throw new InvalidOperationException(Strings.Constructable_VocabularyAnnotationMustHaveTarget); + } + + List elementAnnotations; + if (!this.vocabularyAnnotationsDictionary.TryGetValue(annotation.Target, out elementAnnotations)) + { + elementAnnotations = new List(); + this.vocabularyAnnotationsDictionary.Add(annotation.Target, elementAnnotations); + } + + elementAnnotations.Add(annotation); + } + + /// + /// Searches for vocabulary annotations specified by this model. + /// + /// The annotated element. + /// The vocabulary annotations for the element. + public override IEnumerable FindDeclaredVocabularyAnnotations(IEdmVocabularyAnnotatable element) + { + List elementAnnotations; + return this.vocabularyAnnotationsDictionary.TryGetValue(element, out elementAnnotations) ? elementAnnotations : Enumerable.Empty(); + } + + /// + /// Finds a list of types that derive directly from the supplied type. + /// + /// The base type that derived types are being searched for. + /// A list of types from this model that derive directly from the given type. + public override IEnumerable FindDirectlyDerivedTypes(IEdmStructuredType baseType) + { + List types; + if (this.derivedTypeMappings.TryGetValue(baseType, out types)) + { + return types; + } + + return Enumerable.Empty(); + } + + /// + /// Set a vocabulary annotation to this model. + /// + /// The annotation to be set. + public void SetVocabularyAnnotation(IEdmVocabularyAnnotation annotation) + { + EdmUtil.CheckArgumentNull(annotation, "annotation"); + if (annotation.Target == null) + { + throw new InvalidOperationException(Strings.Constructable_VocabularyAnnotationMustHaveTarget); + } + + List elementAnnotations; + if (!this.vocabularyAnnotationsDictionary.TryGetValue(annotation.Target, out elementAnnotations)) + { + elementAnnotations = new List(); + this.vocabularyAnnotationsDictionary.Add(annotation.Target, elementAnnotations); + } + + elementAnnotations.RemoveAll(p => p.Term.FullName() == annotation.Term.FullName()); + elementAnnotations.Add(annotation); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModelBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModelBase.cs new file mode 100644 index 0000000..ace1f7e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmModelBase.cs @@ -0,0 +1,234 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM model. + /// + public abstract class EdmModelBase : EdmElement, IEdmModel + { + private readonly List referencedEdmModels; + private readonly IEdmDirectValueAnnotationsManager annotationsManager; + private readonly Dictionary containersDictionary = new Dictionary(); + private readonly Dictionary schemaTypeDictionary = new Dictionary(); + private readonly Dictionary termDictionary = new Dictionary(); + private readonly Dictionary> functionDictionary = new Dictionary>(); + + /// + /// Initializes a new instance of the class. + /// + /// Models to which this model refers. + /// Annotations manager for the model to use. + /// Only either mainModel and referencedModels should have value. + protected EdmModelBase(IEnumerable referencedModels, IEdmDirectValueAnnotationsManager annotationsManager) + : this(referencedModels, annotationsManager, true /*includeDefaultVocabularies*/) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Models to which this model refers. + /// Annotations manager for the model to use. + /// a boolean value indicating whether to embed the built-in vocabulary models. + /// Only either mainModel and referencedModels should have value. + protected EdmModelBase(IEnumerable referencedModels, IEdmDirectValueAnnotationsManager annotationsManager, bool includeDefaultVocabularies) + { + EdmUtil.CheckArgumentNull(referencedModels, "referencedModels"); + EdmUtil.CheckArgumentNull(annotationsManager, "annotationsManager"); + + this.referencedEdmModels = new List(referencedModels); + + // EdmCoreModel is always embeded. + this.referencedEdmModels.Insert(0, EdmCoreModel.Instance); + + if (includeDefaultVocabularies) + { + this.referencedEdmModels.AddRange(VocabularyModelProvider.VocabularyModels); + } + + this.annotationsManager = annotationsManager; + } + + /// + /// Gets the collection of schema elements that are contained in this model and referenced models. + /// + public abstract IEnumerable SchemaElements + { + get; + } + + /// + /// Gets the collection of namespaces that schema elements use contained in this model. + /// + public abstract IEnumerable DeclaredNamespaces + { + get; + } + + /// + /// Gets the collection of vocabulary annotations that are contained in this model. + /// + public virtual IEnumerable VocabularyAnnotations + { + get { return Enumerable.Empty(); } + } + + /// + /// Gets the collection of models referred to by this model. + /// + public IEnumerable ReferencedModels + { + get { return this.referencedEdmModels; } + } + + /// + /// Gets the model's annotations manager. + /// + public IEdmDirectValueAnnotationsManager DirectValueAnnotationsManager + { + get { return this.annotationsManager; } + } + + /// + /// Gets the only one entity container of the model. + /// + public IEdmEntityContainer EntityContainer + { + get { return this.containersDictionary.Values.FirstOrDefault(); } + } + + /// + /// Searches for a type with the given name in this model only and returns null if no such type exists. + /// + /// The qualified name of the type being found. + /// The requested type, or null if no such type exists. + public IEdmSchemaType FindDeclaredType(string qualifiedName) + { + IEdmSchemaType result; + this.schemaTypeDictionary.TryGetValue(qualifiedName, out result); + return result; + } + + /// + /// Searches for a term with the given name in this model and returns null if no such term exists. + /// + /// The qualified name of the term being found. + /// The requested term, or null if no such term exists. + public IEdmTerm FindDeclaredTerm(string qualifiedName) + { + IEdmTerm result; + this.termDictionary.TryGetValue(qualifiedName, out result); + return result; + } + + /// + /// Searches for a operation with the given name in this model and returns null if no such operation exists. + /// + /// The qualified name of the operation being found. + /// A group of operations sharing the specified qualified name, or an empty enumerable if no such operation exists. + public IEnumerable FindDeclaredOperations(string qualifiedName) + { + IList elements; + if (this.functionDictionary.TryGetValue(qualifiedName, out elements)) + { + return elements; + } + + return Enumerable.Empty(); + } + + /// + /// Searches for bound operations based on the binding type, returns an empty enumerable if no operation exists. + /// + /// Type of the binding. + /// A set of operations that share the binding type or empty enumerable if no such operation exists. + public virtual IEnumerable FindDeclaredBoundOperations(IEdmType bindingType) + { + foreach (IEnumerable operations in this.functionDictionary.Values.Distinct()) + { + foreach (IEdmOperation operation in operations.Where(o => o.HasEquivalentBindingType(bindingType))) + { + yield return operation; + } + } + } + + /// + /// Searches for bound operations based on the qualified name and binding type, returns an empty enumerable if no operation exists. + /// + /// The qualified name of the operation. + /// Type of the binding. + /// + /// A set of operations that share the name and binding type or empty enumerable if no such operation exists. + /// + public virtual IEnumerable FindDeclaredBoundOperations(string qualifiedName, IEdmType bindingType) + { + IEnumerable enumerable = this.FindDeclaredOperations(qualifiedName); + IList operations = enumerable as IList; + if (operations != null) + { + IList matchedOperation = new List(); + for (int i = 0; i < operations.Count; i++) + { + if (operations[i].HasEquivalentBindingType(bindingType)) + { + matchedOperation.Add(operations[i]); + } + } + + return matchedOperation; + } + else + { + return enumerable.Where(o => o.HasEquivalentBindingType(bindingType)); + } + } + + /// + /// Searches for vocabulary annotations specified by this model or a referenced model for a given element. + /// + /// The annotated element. + /// The vocabulary annotations for the element. + public virtual IEnumerable FindDeclaredVocabularyAnnotations(IEdmVocabularyAnnotatable element) + { + return Enumerable.Empty(); + } + + /// + /// Finds a list of types that derive directly from the supplied type. + /// + /// The base type that derived types are being searched for. + /// A list of types that derive directly from the base type. + public abstract IEnumerable FindDirectlyDerivedTypes(IEdmStructuredType baseType); + + /// + /// Adds a schema element to this model. + /// + /// The element to register. + protected void RegisterElement(IEdmSchemaElement element) + { + EdmUtil.CheckArgumentNull(element, "element"); + RegistrationHelper.RegisterSchemaElement(element, this.schemaTypeDictionary, this.termDictionary, this.functionDictionary, this.containersDictionary); + } + + /// + /// Adds a model reference to this model. + /// + /// The model to reference. + protected void AddReferencedModel(IEdmModel model) + { + EdmUtil.CheckArgumentNull(model, "model"); + this.referencedEdmModels.Add(model); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNamedElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNamedElement.cs new file mode 100644 index 0000000..5dfcd9b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNamedElement.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System.Diagnostics; + + /// + /// Common base class for all named EDM elements. + /// + [DebuggerDisplay("Name:{Name}")] + public abstract class EdmNamedElement : EdmElement, IEdmNamedElement + { + private readonly string name; + + /// + /// Initializes a new instance of the class. + /// + /// Name of the element. + protected EdmNamedElement(string name) + { + EdmUtil.CheckArgumentNull(name, "name"); + + this.name = name; + } + + /// + /// Gets the name of this element. + /// + public string Name + { + get { return this.name; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs new file mode 100644 index 0000000..86a7577 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationProperty.cs @@ -0,0 +1,271 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM navigation property. + /// + public sealed class EdmNavigationProperty : EdmProperty, IEdmNavigationProperty + { + private readonly IEdmReferentialConstraint referentialConstraint; + private readonly bool containsTarget; + private readonly EdmOnDeleteAction onDelete; + private IEdmNavigationProperty partner; + + private EdmNavigationProperty( + IEdmStructuredType declaringType, + string name, + IEdmTypeReference type, + IEnumerable dependentProperties, + IEnumerable principalProperties, + bool containsTarget, + EdmOnDeleteAction onDelete) + : base(declaringType, name, type) + { + this.containsTarget = containsTarget; + this.onDelete = onDelete; + + if (dependentProperties != null) + { + this.referentialConstraint = EdmReferentialConstraint.Create(dependentProperties, principalProperties); + } + } + + /// + /// Gets the kind of this property. + /// + public override EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.Navigation; } + } + + /// + /// Gets a value indicating whether the navigation target is contained inside the navigation source. + /// + public bool ContainsTarget + { + get { return this.containsTarget; } + } + + /// + /// Gets the referential constraint for this navigation, returning null if this is the principal end or if there is no referential constraint. + /// + public IEdmReferentialConstraint ReferentialConstraint + { + get { return this.referentialConstraint; } + } + + /// + /// Gets the action to take when an instance of the declaring type is deleted. + /// + public EdmOnDeleteAction OnDelete + { + get { return this.onDelete; } + } + + /// + /// Gets the partner of this navigation property. + /// + public IEdmNavigationProperty Partner + { + get { return this.partner; } + } + + /// + /// Gets the path to the partner in the related entity type. + /// + /// + /// Is null if the containing type is a complex type. + /// + internal IEdmPathExpression PartnerPath { get; private set; } + + /// + /// Creates a navigation property from the given information. + /// + /// The type that declares this property. + /// Information to create the navigation property. + /// Created navigation property. + public static EdmNavigationProperty CreateNavigationProperty(IEdmStructuredType declaringType, EdmNavigationPropertyInfo propertyInfo) + { + EdmUtil.CheckArgumentNull(propertyInfo, "propertyInfo"); + EdmUtil.CheckArgumentNull(propertyInfo.Name, "propertyInfo.Name"); + EdmUtil.CheckArgumentNull(propertyInfo.Target, "propertyInfo.Target"); + + return new EdmNavigationProperty( + declaringType, + propertyInfo.Name, + CreateNavigationPropertyType(propertyInfo.Target, propertyInfo.TargetMultiplicity, "propertyInfo.TargetMultiplicity"), + propertyInfo.DependentProperties, + propertyInfo.PrincipalProperties, + propertyInfo.ContainsTarget, + propertyInfo.OnDelete); + } + + /// + /// Creates two navigation properties representing an association between two entity types. + /// + /// Information to create the navigation property. + /// Information to create the partner navigation property. + /// Created navigation property. + public static EdmNavigationProperty CreateNavigationPropertyWithPartner(EdmNavigationPropertyInfo propertyInfo, EdmNavigationPropertyInfo partnerInfo) + { + EdmUtil.CheckArgumentNull(propertyInfo, "propertyInfo"); + EdmUtil.CheckArgumentNull(propertyInfo.Name, "propertyInfo.Name"); + EdmUtil.CheckArgumentNull(propertyInfo.Target, "propertyInfo.Target"); + EdmUtil.CheckArgumentNull(partnerInfo, "partnerInfo"); + EdmUtil.CheckArgumentNull(partnerInfo.Name, "partnerInfo.Name"); + EdmUtil.CheckArgumentNull(partnerInfo.Target, "partnerInfo.Target"); + + EdmNavigationProperty end1 = new EdmNavigationProperty( + partnerInfo.Target, + propertyInfo.Name, + CreateNavigationPropertyType(propertyInfo.Target, propertyInfo.TargetMultiplicity, "propertyInfo.TargetMultiplicity"), + propertyInfo.DependentProperties, + propertyInfo.PrincipalProperties, + propertyInfo.ContainsTarget, + propertyInfo.OnDelete); + + EdmNavigationProperty end2 = new EdmNavigationProperty( + propertyInfo.Target, + partnerInfo.Name, + CreateNavigationPropertyType(partnerInfo.Target, partnerInfo.TargetMultiplicity, "partnerInfo.TargetMultiplicity"), + partnerInfo.DependentProperties, + partnerInfo.PrincipalProperties, + partnerInfo.ContainsTarget, + partnerInfo.OnDelete); + + end1.SetPartner(end2, new EdmPathExpression(end2.Name)); + end2.SetPartner(end1, new EdmPathExpression(end1.Name)); + return end1; + } + + /// + /// Creates two navigation properties representing an association between two entity types. + /// + /// Navigation property name. + /// Type of the navigation property. + /// Dependent properties of the navigation source. + /// Principal properties of the navigation source. + /// A value indicating whether the navigation source logically contains the navigation target. + /// Action to take upon deletion of an instance of the navigation source. + /// Navigation partner property name. + /// Type of the navigation partner property. + /// Dependent properties of the navigation target. + /// Principal properties of the navigation target. + /// A value indicating whether the navigation target logically contains the navigation source. + /// Action to take upon deletion of an instance of the navigation target. + /// Navigation property. + public static EdmNavigationProperty CreateNavigationPropertyWithPartner( + string propertyName, + IEdmTypeReference propertyType, + IEnumerable dependentProperties, + IEnumerable principalProperties, + bool containsTarget, + EdmOnDeleteAction onDelete, + string partnerPropertyName, + IEdmTypeReference partnerPropertyType, + IEnumerable partnerDependentProperties, + IEnumerable partnerPrincipalProperties, + bool partnerContainsTarget, + EdmOnDeleteAction partnerOnDelete) + { + EdmUtil.CheckArgumentNull(propertyName, "propertyName"); + EdmUtil.CheckArgumentNull(propertyType, "propertyType"); + EdmUtil.CheckArgumentNull(partnerPropertyName, "partnerPropertyName"); + EdmUtil.CheckArgumentNull(partnerPropertyType, "partnerPropertyType"); + + IEdmEntityType declaringType = GetEntityType(partnerPropertyType); + if (declaringType == null) + { + throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeExpected, "partnerPropertyType"); + } + + IEdmEntityType partnerDeclaringType = GetEntityType(propertyType); + if (partnerDeclaringType == null) + { + throw new ArgumentException(Strings.Constructable_EntityTypeOrCollectionOfEntityTypeExpected, "propertyType"); + } + + EdmNavigationProperty end1 = new EdmNavigationProperty( + declaringType, + propertyName, + propertyType, + dependentProperties, + principalProperties, + containsTarget, + onDelete); + + EdmNavigationProperty end2 = new EdmNavigationProperty( + partnerDeclaringType, + partnerPropertyName, + partnerPropertyType, + partnerDependentProperties, + partnerPrincipalProperties, + partnerContainsTarget, + partnerOnDelete); + + end1.SetPartner(end2, new EdmPathExpression(end2.Name)); + end2.SetPartner(end1, new EdmPathExpression(end1.Name)); + return end1; + } + + /// + /// Sets the partner information. + /// + /// The partner navigation property. + /// Path to the partner navigation property in the related entity type. + internal void SetPartner(IEdmNavigationProperty navigationProperty, IEdmPathExpression navigationPropertyPath) + { + Debug.Assert( + DeclaringType is IEdmEntityType, + "Partner info cannot be set for nav. property on a non-entity type."); + partner = navigationProperty; + PartnerPath = navigationPropertyPath; + } + + private static IEdmEntityType GetEntityType(IEdmTypeReference type) + { + IEdmEntityType entityType = null; + if (type.IsEntity()) + { + entityType = (IEdmEntityType)type.Definition; + } + else if (type.IsCollection()) + { + type = ((IEdmCollectionType)type.Definition).ElementType; + if (type.IsEntity()) + { + entityType = (IEdmEntityType)type.Definition; + } + } + + return entityType; + } + + private static IEdmTypeReference CreateNavigationPropertyType(IEdmEntityType entityType, EdmMultiplicity multiplicity, string multiplicityParameterName) + { + switch (multiplicity) + { + case EdmMultiplicity.ZeroOrOne: + return new EdmEntityTypeReference(entityType, true); + + case EdmMultiplicity.One: + return new EdmEntityTypeReference(entityType, false); + + case EdmMultiplicity.Many: + return EdmCoreModel.GetCollection(new EdmEntityTypeReference(entityType, false)); + + default: + throw new ArgumentOutOfRangeException(multiplicityParameterName, Strings.UnknownEnumVal_Multiplicity(multiplicity)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyBinding.cs new file mode 100644 index 0000000..793754f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyBinding.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a mapping from an EDM navigation property to a navigation source. + /// + public class EdmNavigationPropertyBinding : IEdmNavigationPropertyBinding + { + private IEdmNavigationProperty navigationProperty; + private IEdmNavigationSource target; + private IEdmPathExpression path; + + /// + /// Creates a new navigation property binding. + /// + /// The navigation property. + /// The navigation source that the navigation property targets. + public EdmNavigationPropertyBinding(IEdmNavigationProperty navigationProperty, IEdmNavigationSource target) + { + this.navigationProperty = navigationProperty; + this.target = target; + this.path = new EdmPathExpression(navigationProperty == null ? string.Empty : navigationProperty.Name); + } + + /// + /// Creates a new navigation property binding. + /// + /// The navigation property. + /// The navigation source that the navigation property targets. + /// The path of current binding + public EdmNavigationPropertyBinding(IEdmNavigationProperty navigationProperty, IEdmNavigationSource target, IEdmPathExpression bindingPath) + { + this.navigationProperty = navigationProperty; + this.target = target; + this.path = bindingPath; + } + + /// + /// Gets the navigation property. + /// + public IEdmNavigationProperty NavigationProperty + { + get { return this.navigationProperty; } + } + + /// + /// Gets the target navigation source. + /// + public IEdmNavigationSource Target + { + get { return this.target; } + } + + /// + /// The path of current binding. + /// + public IEdmPathExpression Path + { + get { return this.path; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyInfo.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyInfo.cs new file mode 100644 index 0000000..a2136e4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationPropertyInfo.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM navigation property info used during construction of navigation properties. + /// + public sealed class EdmNavigationPropertyInfo + { + /// + /// Gets or sets the name of this navigation property. + /// + public string Name { get; set; } + + /// + /// Gets or sets the entity type that this navigation property leads to. + /// + public IEdmEntityType Target { get; set; } + + /// + /// Gets or sets multiplicity of the navigation target. + /// + public EdmMultiplicity TargetMultiplicity { get; set; } + + /// + /// Gets or sets the dependent properties of the association this navigation property expresses. + /// + public IEnumerable DependentProperties { get; set; } + + /// + /// Gets or sets the principal properties of the association this navigation property expresses. + /// + public IEnumerable PrincipalProperties { get; set; } + + /// + /// Gets or sets a value indicating whether the navigation target is contained inside the navigation source. + /// + public bool ContainsTarget { get; set; } + + /// + /// Gets or sets the action to take when an instance of the declaring type is deleted. + /// + public EdmOnDeleteAction OnDelete { get; set; } + + /// + /// Clones this object. + /// + /// A copy of this object. + public EdmNavigationPropertyInfo Clone() + { + return new EdmNavigationPropertyInfo() + { + Name = this.Name, + Target = this.Target, + TargetMultiplicity = this.TargetMultiplicity, + DependentProperties = this.DependentProperties, + PrincipalProperties = this.PrincipalProperties, + ContainsTarget = this.ContainsTarget, + OnDelete = this.OnDelete + }; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationSource.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationSource.cs new file mode 100644 index 0000000..c5f4667 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmNavigationSource.cs @@ -0,0 +1,264 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +#if PORTABLELIB +using System.Collections.Concurrent; +#endif +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an abstract EDM navigation source. + /// + public abstract class EdmNavigationSource : EdmNamedElement, IEdmNavigationSource + { +#if PORTABLELIB + private readonly ConcurrentDictionary> navigationPropertyMappings = new ConcurrentDictionary>(); + + private readonly ConcurrentDictionary unknownNavigationPropertyCache = new ConcurrentDictionary(); +#else + private readonly Dictionary> navigationPropertyMappings = new Dictionary>(); + + private readonly Dictionary unknownNavigationPropertyCache = new Dictionary(); +#endif + private readonly Cache> navigationTargetsCache = new Cache>(); + private static readonly Func> ComputeNavigationTargetsFunc = (me) => me.ComputeNavigationTargets(); + + /// + /// Initializes a new instance of the class. + /// + /// Name of the navigation source. + protected EdmNavigationSource(string name) + : base(name) + { + } + + /// + /// Gets the navigation targets of this navigation source. + /// + public IEnumerable NavigationPropertyBindings + { + get { return this.navigationTargetsCache.GetValue(this, ComputeNavigationTargetsFunc, null); } + } + + /// + /// Gets the type of this navigation source. + /// + public abstract IEdmType Type { get; } + + /// + /// Gets the path that a navigation property targets. + /// + public abstract IEdmPathExpression Path { get; } + + /// + /// Adds a navigation target, specifying the destination entity set of a navigation property of an entity in this navigation source. + /// + /// The navigation property the target is being set for. + /// The destination navigation source of the specified navigation property. + public void AddNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmNavigationSource target) + { + EdmUtil.CheckArgumentNull(navigationProperty, "navigationProperty"); + EdmUtil.CheckArgumentNull(target, "navigation target"); + + // It doesn't make sense to bind a containment navigation property. This should really error, + // but doing so would be a breaking change, so just ignore the binding. + if (navigationProperty.ContainsTarget) + { + return; + } + + string path = navigationProperty.Name; + + if (!this.Type.AsElementType().IsOrInheritsFrom(navigationProperty.DeclaringType)) + { + path = navigationProperty.DeclaringType.FullTypeName() + '/' + path; + } + + AddNavigationPropertyBinding(navigationProperty, target, new EdmPathExpression(path)); + this.navigationTargetsCache.Clear(null); + } + + /// + /// Adds a navigation target, specifying the destination entity set of a navigation property of an entity in this navigation source. + /// + /// The navigation property the target is being set for. + /// The destination navigation source of the specified navigation property. + /// Sets the path that the navigation property targets. + public void AddNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmNavigationSource target, IEdmPathExpression bindingPath) + { + EdmUtil.CheckArgumentNull(navigationProperty, "navigationProperty"); + EdmUtil.CheckArgumentNull(target, "navigation target"); + EdmUtil.CheckArgumentNull(bindingPath, "binding path"); + + // It doesn't make sense to bind a containment navigation property. This should really error, + // but doing so would be a breaking change, so just ignore the binding. + if (navigationProperty.ContainsTarget) + { + return; + } + + if (navigationProperty.Name != bindingPath.PathSegments.Last()) + { + throw new ArgumentException(Strings.NavigationPropertyBinding_PathIsNotValid); + } + + AddNavigationPropertyBinding(navigationProperty, target, bindingPath); + this.navigationTargetsCache.Clear(null); + } + + /// + /// Finds the bindings of the navigation property. + /// + /// The navigation property. + /// The list of bindings for current navigation property. + public virtual IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty) + { + EdmUtil.CheckArgumentNull(navigationProperty, "navigationProperty"); + +#if PORTABLELIB + ConcurrentDictionary result; +#else + IDictionary result; +#endif + + result = EdmUtil.DictionarySafeGet(this.navigationPropertyMappings, navigationProperty); + if (result != null) + { + return result.Select(item => item.Value); + } + + return null; + } + + /// + /// Finds the navigation source that a navigation property targets. + /// + /// The navigation property. + /// The navigation source that the navigation proportion targets, or null if no such navigation source exists. + public virtual IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty) + { + EdmUtil.CheckArgumentNull(navigationProperty, "property"); + + bool isDerived = !(navigationProperty.DeclaringType.AsElementType() is IEdmComplexType) && !this.Type.AsElementType().IsOrInheritsFrom(navigationProperty.DeclaringType); + + IEdmPathExpression bindingPath = isDerived + ? new EdmPathExpression(navigationProperty.DeclaringType.FullTypeName(), navigationProperty.Name) + : new EdmPathExpression(navigationProperty.Name); + + return FindNavigationTarget(navigationProperty, bindingPath); + } + + /// + /// Finds the navigation source that a navigation property targets. + /// + /// The navigation property. + /// The binding path of the navigation property. + /// The navigation source that the navigation property targets + public virtual IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) + { + EdmUtil.CheckArgumentNull(navigationProperty, "navigationProperty"); + + bindingPath = bindingPath ?? new EdmPathExpression(navigationProperty.Name); + +#if PORTABLELIB + ConcurrentDictionary result; +#else + IDictionary result; +#endif + + result = EdmUtil.DictionarySafeGet(this.navigationPropertyMappings, navigationProperty); + if (result != null) + { + IEdmNavigationPropertyBinding binding = EdmUtil.DictionarySafeGet(result, bindingPath.Path); + { + if (binding != null) + { + return binding.Target; + } + } + } + + if (navigationProperty.ContainsTarget) + { + return AddNavigationPropertyBinding( + navigationProperty, + new EdmContainedEntitySet(this, navigationProperty, bindingPath), + bindingPath) + .Target; + } + + return EdmUtil.DictionaryGetOrUpdate( + this.unknownNavigationPropertyCache, + navigationProperty, + navProperty => new EdmUnknownEntitySet(this, navProperty)); + } + + private IEdmNavigationPropertyBinding AddNavigationPropertyBinding(IEdmNavigationProperty navigationProperty, IEdmNavigationSource target, IEdmPathExpression bindingPath) + { +#if PORTABLELIB + ConcurrentDictionary mapping = EdmUtil.DictionaryGetOrUpdate( + this.navigationPropertyMappings, + navigationProperty, + navProperty => new ConcurrentDictionary()); +#else + IDictionary mapping= EdmUtil.DictionaryGetOrUpdate( + this.navigationPropertyMappings, + navigationProperty, + navProperty => new Dictionary()); +#endif + + IEdmNavigationPropertyBinding containedBinding = EdmUtil.DictionaryGetOrUpdate( + mapping, + bindingPath.Path, + path => new EdmNavigationPropertyBinding(navigationProperty, target, new EdmPathExpression(path))); + + return containedBinding; + } + +#if PORTABLELIB + private IEnumerable ComputeNavigationTargets() + { + List result = new List(); + foreach (KeyValuePair> mapping in this.navigationPropertyMappings) + { + if (!mapping.Key.ContainsTarget) + { + foreach (KeyValuePair kv in mapping.Value) + { + result.Add(kv.Value); + } + } + } + + return result.OrderBy(x => x?.Path?.Path); + } +#else + private IEnumerable ComputeNavigationTargets() + { + List result = new List(); + lock (this.navigationPropertyMappings) + { + foreach (KeyValuePair> mapping in this.navigationPropertyMappings) + { + if (!mapping.Key.ContainsTarget) + { + foreach (KeyValuePair kv in mapping.Value) + { + result.Add(kv.Value); + } + } + } + } + + return result; + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperation.cs new file mode 100644 index 0000000..e06f21b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperation.cs @@ -0,0 +1,173 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System.Collections.Generic; + + /// + /// Represents an EDM operation. + /// + public abstract class EdmOperation : EdmNamedElement, IEdmOperation, IEdmFullNamedElement + { + private readonly string fullName; + private readonly List parameters = new List(); + + /// + /// Initializes a new instance of the class. + /// + /// Name of the namespace. + /// The name. + /// Type of the return. + /// if set to true [is bound]. + /// The entity set path expression. + protected EdmOperation(string namespaceName, string name, IEdmTypeReference returnType, bool isBound, IEdmPathExpression entitySetPathExpression) + : base(name) + { + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + + this.Return = returnType == null ? null : new EdmOperationReturn(this, returnType); + this.Namespace = namespaceName; + this.IsBound = isBound; + this.EntitySetPath = entitySetPathExpression; + this.fullName = EdmUtil.GetFullNameForSchemaElement(namespaceName, this.Name); + } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the namespace. + /// The name. + /// Type of the return. + protected EdmOperation(string namespaceName, string name, IEdmTypeReference returnType) + : this(namespaceName, name, returnType, false, null) + { + } + + /// + /// Gets a value indicating whether this instance is bound. + /// + /// + /// true if this instance is bound; otherwise, false. + /// + public bool IsBound { get; private set; } + + /// + /// Gets the entity set path expression. + /// + /// + /// The entity set path expression. + /// + public IEdmPathExpression EntitySetPath { get; private set; } + + /// + /// Gets the element kind of this operation, which is always Operation. + /// virtual will be removed in the near future, stop gap to enable testing for now. + /// + public abstract EdmSchemaElementKind SchemaElementKind { get; } + + /// + /// Gets the namespace of this operation. + /// + public string Namespace { get; private set; } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the return type of this operation. + /// + public IEdmTypeReference ReturnType + { + get { return Return == null ? null : Return.Type; } + } + + /// + /// Gets the parameters of this operation. + /// + public IEnumerable Parameters + { + get { return this.parameters; } + } + + /// + /// Gets the return of this operation. + /// + internal IEdmOperationReturn Return { get; private set; } + + /// + /// Searches for a parameter with the given name in this operation and returns null if no such parameter exists. + /// + /// The name of the parameter to be found. + /// The requested parameter, or null if no such parameter exists. + public IEdmOperationParameter FindParameter(string name) + { + foreach (IEdmOperationParameter parameter in this.Parameters) + { + if (parameter.Name == name) + { + return parameter; + } + } + + return null; + } + + /// + /// Creates and adds a parameter to this operation (as the last parameter). + /// + /// The name of the parameter being added. + /// The type of the parameter being added. + /// Created parameter. + public EdmOperationParameter AddParameter(string name, IEdmTypeReference type) + { + EdmOperationParameter parameter = new EdmOperationParameter(this, name, type); + this.parameters.Add(parameter); + return parameter; + } + + /// + /// Creates and adds an optional parameter to this operation (as the last parameter). + /// + /// The name of the parameter being added. + /// The type of the parameter being added. + /// Created parameter. + public EdmOptionalParameter AddOptionalParameter(string name, IEdmTypeReference type) + { + return AddOptionalParameter(name, type, null); + } + + /// + /// Creates and adds an optional parameter to this operation (as the last parameter). + /// + /// The name of the parameter being added. + /// The type of the parameter being added. + /// The default value for the parameter being added. + /// Created parameter. + public EdmOptionalParameter AddOptionalParameter(string name, IEdmTypeReference type, string defaultValue) + { + EdmOptionalParameter parameter = new EdmOptionalParameter(this, name, type, defaultValue); + this.parameters.Add(parameter); + return parameter; + } + + /// + /// Adds a parameter to this operation (as the last parameter). + /// + /// The parameter being added. + public void AddParameter(IEdmOperationParameter parameter) + { + EdmUtil.CheckArgumentNull(parameter, "parameter"); + + this.parameters.Add(parameter); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationImport.cs new file mode 100644 index 0000000..5548d39 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationImport.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using Microsoft.OData.Edm.Vocabularies; + + /// + /// Represents an EDM operation import. + /// + public abstract class EdmOperationImport : EdmNamedElement, IEdmOperationImport + { + /// + /// Initializes a new instance of class. + /// + /// An containing this operation import. + /// The operation of the import. + /// Name of the operation import. + /// An entity set containing entities returned by this operation import. + /// The expression kind supported is . + protected EdmOperationImport( + IEdmEntityContainer container, + IEdmOperation operation, + string name, + IEdmExpression entitySet) + : base(name) + { + EdmUtil.CheckArgumentNull(container, "container"); + EdmUtil.CheckArgumentNull(operation, this.OperationArgumentNullParameterName()); + + this.Container = container; + this.Operation = operation; + this.EntitySet = entitySet; + } + + /// + /// Gets the operation. + /// + public IEdmOperation Operation { get; private set; } + + /// + /// Gets the entity set containing entities returned by this operation import. + /// + public IEdmExpression EntitySet { get; private set; } + + /// + /// Gets the kind of this operation, which is always FunctionImport. + /// + public abstract EdmContainerElementKind ContainerElementKind { get; } + + /// + /// Gets the container of this operation. + /// + public IEdmEntityContainer Container { get; private set; } + + /// + /// Operations the name of the argument null parameter. + /// + /// A string that is the name of the operation parameter in the derived operation class. + protected abstract string OperationArgumentNullParameterName(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationParameter.cs new file mode 100644 index 0000000..b4481dd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationParameter.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM operation parameter. + /// + public class EdmOperationParameter : EdmNamedElement, IEdmOperationParameter + { + /// + /// Initializes a new instance of the class. + /// + /// Declaring operation of the parameter. + /// Name of the parameter. + /// Type of the parameter. + public EdmOperationParameter(IEdmOperation declaringOperation, string name, IEdmTypeReference type) + : base(name) + { + EdmUtil.CheckArgumentNull(declaringOperation, "declaringFunction"); + EdmUtil.CheckArgumentNull(name, "name"); + EdmUtil.CheckArgumentNull(type, "type"); + + this.Type = type; + this.DeclaringOperation = declaringOperation; + } + + /// + /// Gets the type of this parameter. + /// + public IEdmTypeReference Type { get; private set; } + + /// + /// Gets the operation that declared this parameter. + /// + public IEdmOperation DeclaringOperation { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationReturn.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationReturn.cs new file mode 100644 index 0000000..a5f3b39 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOperationReturn.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM operation return. + /// Be noted: it is marked as internal class because it's not allowed to create an instance from end user. + /// + internal class EdmOperationReturn : EdmElement, IEdmOperationReturn + { + /// + /// Initializes a new instance of the class. + /// + /// Declaring operation of the return. + /// The return type of the return. + public EdmOperationReturn(IEdmOperation declaringOperation, IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(declaringOperation, "declaringOperation"); + EdmUtil.CheckArgumentNull(type, "type"); + + this.Type = type; + this.DeclaringOperation = declaringOperation; + } + + /// + /// Gets the return type of this return. + /// + public IEdmTypeReference Type { get; private set; } + + /// + /// Gets the operation that declared this return. + /// + public IEdmOperation DeclaringOperation { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOptionalParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOptionalParameter.cs new file mode 100644 index 0000000..9db0113 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmOptionalParameter.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM operation parameter. + /// + public class EdmOptionalParameter : EdmOperationParameter, IEdmOptionalParameter + { + /// + /// Initializes a new instance of the class. + /// + /// Declaring function of the parameter. + /// Name of the parameter. + /// Type of the parameter. + public EdmOptionalParameter(IEdmOperation declaringOperation, string name, IEdmTypeReference type) + : this(declaringOperation, name, type, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Declaring function of the parameter. + /// Name of the parameter. + /// Type of the parameter. + /// The default value of the optional parameter. + public EdmOptionalParameter(IEdmOperation declaringOperation, string name, IEdmTypeReference type, string defaultValue) + : base(declaringOperation, name, type) + { + this.DefaultValueString = defaultValue; + } + + /// + /// Gets the type of this parameter. + /// + public string DefaultValueString { get; private set; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathExpression.cs new file mode 100644 index 0000000..3aacea2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathExpression.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM path expression. + /// + public class EdmPathExpression : EdmElement, IEdmPathExpression + { + private IEnumerable pathSegments; + private string path; + + /// + /// Initializes a new instance of the class. + /// + /// Path string containing segments separated by '/'. For example: "A.B/C/D.E/Func1(NS.T,NS.T2)/P1". + public EdmPathExpression(string path) + { + EdmUtil.CheckArgumentNull(path, "path"); + this.path = path; + } + + /// + /// Initializes a new instance of the class. + /// + /// Path segments. + public EdmPathExpression(params string[] pathSegments) + : this((IEnumerable)pathSegments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Path segments. + public EdmPathExpression(IEnumerable pathSegments) + { + EdmUtil.CheckArgumentNull(pathSegments, "pathSegments"); + + if (pathSegments.Any(segment => segment.Contains("/"))) + { + throw new ArgumentException(Strings.PathSegmentMustNotContainSlash); + } + + this.pathSegments = pathSegments.ToList(); + } + + /// + /// Gets the path segments as a decomposed qualified name. "A.B/C/D.E/Func1(NS.T,NS.T2)/P1" is { "A.B", "C", "D.E", "Func1(NS.T,NS.T2)", "P1" }. + /// + public IEnumerable PathSegments + { + get { return this.pathSegments ?? (this.pathSegments = this.path.Split('/')); } + } + + /// + /// Gets the path string, like "A.B/C/D.E". + /// + public string Path + { + get { return this.path ?? (this.path = string.Join("/", this.pathSegments.ToArray())); } + } + + /// + /// Gets the kind of this expression. + /// + public virtual EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Path; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathTypeReference.cs new file mode 100644 index 0000000..ca8afd3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPathTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM Path type. + /// + public class EdmPathTypeReference : EdmTypeReference, IEdmPathTypeReference + { + /// + /// Constructor + /// + /// IEdmPathType definition. + /// nullable or not. + public EdmPathTypeReference(IEdmPathType definition, bool isNullable) + : base(definition, isNullable) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPrimitiveTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPrimitiveTypeReference.cs new file mode 100644 index 0000000..4a76bed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmPrimitiveTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM primitive type. + /// + public class EdmPrimitiveTypeReference : EdmTypeReference, IEdmPrimitiveTypeReference + { + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + public EdmPrimitiveTypeReference(IEdmPrimitiveType definition, bool isNullable) + : base(definition, isNullable) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmProperty.cs new file mode 100644 index 0000000..b5fdccd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmProperty.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM property. + /// + public abstract class EdmProperty : EdmNamedElement, IEdmProperty + { + private readonly IEdmStructuredType declaringType; + private readonly IEdmTypeReference type; + + /// + /// Initializes a new instance of the class. + /// + /// The type that declares this property. + /// Name of the property. + /// Type of the property. + protected EdmProperty(IEdmStructuredType declaringType, string name, IEdmTypeReference type) + : base(name) + { + EdmUtil.CheckArgumentNull(declaringType, "declaringType"); + EdmUtil.CheckArgumentNull(type, "type"); + + this.declaringType = declaringType; + this.type = type; + } + + /// + /// Gets the type of this property. + /// + public IEdmTypeReference Type + { + get { return this.type; } + } + + /// + /// Gets the type that this property belongs to. + /// + public IEdmStructuredType DeclaringType + { + get { return this.declaringType; } + } + + /// + /// Gets the kind of this property. + /// + public abstract EdmPropertyKind PropertyKind + { + get; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReference.cs new file mode 100644 index 0000000..65773f6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReference.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents edmx:reference element in CSDL doc. + /// + public class EdmReference : IEdmReference + { + private Uri uri; + private List includes = new List(); + private List includeAnnotations = new List(); + + /// + /// Constructor. + /// + /// The Uri to load referenced model. + public EdmReference(Uri uri) + { + this.uri = uri; + } + + /// + /// Gets the Uri to load referenced model. + /// + public Uri Uri + { + get + { + return this.uri; + } + } + + /// + /// Gets the Includes for referenced model. + /// + public IEnumerable Includes + { + get + { + return this.includes; + } + } + + /// + /// Gets the IncludeAnnotations for referenced model. + /// + public IEnumerable IncludeAnnotations + { + get + { + return this.includeAnnotations; + } + } + + /// + /// Add include information. + /// + /// The IEdmInclude. + public void AddInclude(IEdmInclude edmInclude) + { + this.includes.Add(edmInclude); + } + + /// + /// Add IncludeAnnotations information. + /// + /// The IEdmIncludeAnnotations. + public void AddIncludeAnnotations(IEdmIncludeAnnotations edmIncludeAnnotations) + { + this.includeAnnotations.Add(edmIncludeAnnotations); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReferentialConstraint.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReferentialConstraint.cs new file mode 100644 index 0000000..965ee81 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmReferentialConstraint.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents an EDM referential constraint on a navigation property. + /// + public class EdmReferentialConstraint : IEdmReferentialConstraint + { + private readonly IEnumerable propertyPairs; + + /// + /// Initializes a new instance of . + /// + /// The set of property pairs from the referential constraint. + public EdmReferentialConstraint(IEnumerable propertyPairs) + { + EdmUtil.CheckArgumentNull(propertyPairs, "propertyPairs"); + this.propertyPairs = propertyPairs.ToList(); + } + + /// + /// Gets the set of property pairs from the referential constraint. No particular ordering should be assumed. + /// + public IEnumerable PropertyPairs + { + get { return this.propertyPairs; } + } + + /// + /// Creates a new using the provided and to form the pairs. + /// + /// The dependent properties that participate in the referential constraint. Assumed to be in the correct order relative to the principal entity's properties. + /// The principal properties that participate in the referential constraint. Assumed to be in the correct order relative to the dependent entity's properties. + /// The newly created referential constraint. + /// Thrown if the number of dependent properties given does not match the number of key properties in the principal entity type. + public static EdmReferentialConstraint Create(IEnumerable dependentProperties, IEnumerable principalProperties) + { + EdmUtil.CheckArgumentNull(dependentProperties, "dependentProperties"); + EdmUtil.CheckArgumentNull(principalProperties, "principalProperties"); + + var dependentPropertyList = new List(dependentProperties); + var principalPropertyList = new List(principalProperties); + if (dependentPropertyList.Count != principalPropertyList.Count) + { + throw new ArgumentException(Strings.Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType(principalPropertyList.Count, dependentPropertyList.Count)); + } + + return new EdmReferentialConstraint(dependentPropertyList.Select((d, i) => new EdmReferentialConstraintPropertyPair(d, principalPropertyList[i]))); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSingleton.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSingleton.cs new file mode 100644 index 0000000..946c60a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSingleton.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM singleton. + /// + public class EdmSingleton : EdmNavigationSource, IEdmSingleton + { + private readonly IEdmEntityContainer container; + private readonly IEdmEntityType entityType; + private IEdmPathExpression path; + + /// + /// Initializes a new instance of the class. + /// + /// An containing this entity set. + /// Name of the singleton. + /// The entity type of the element of this singleton. + public EdmSingleton(IEdmEntityContainer container, string name, IEdmEntityType entityType) + : base(name) + { + EdmUtil.CheckArgumentNull(container, "container"); + EdmUtil.CheckArgumentNull(entityType, "entityType"); + + this.container = container; + this.entityType = entityType; + this.path = new EdmPathExpression(name); + } + + /// + /// Gets the kind of element of this container element. + /// + public EdmContainerElementKind ContainerElementKind + { + get { return EdmContainerElementKind.Singleton; } + } + + /// + /// Gets the container of this singleton. + /// + public IEdmEntityContainer Container + { + get { return this.container; } + } + + /// + /// Gets the type of this navigation source. + /// + public override IEdmType Type + { + get { return this.entityType; } + } + + /// + /// Gets the path that a navigation property targets. + /// + public override IEdmPathExpression Path + { + get { return this.path; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSpatialTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSpatialTypeReference.cs new file mode 100644 index 0000000..4889977 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmSpatialTypeReference.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM spatial type. + /// + public class EdmSpatialTypeReference : EdmPrimitiveTypeReference, IEdmSpatialTypeReference + { + private readonly int? spatialReferenceIdentifier; + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + public EdmSpatialTypeReference(IEdmPrimitiveType definition, bool isNullable) + : this(definition, isNullable, null) + { + EdmUtil.CheckArgumentNull(definition, "definition"); + switch (definition.PrimitiveKind) + { + case EdmPrimitiveTypeKind.Geography: + case EdmPrimitiveTypeKind.GeographyPoint: + case EdmPrimitiveTypeKind.GeographyLineString: + case EdmPrimitiveTypeKind.GeographyPolygon: + case EdmPrimitiveTypeKind.GeographyCollection: + case EdmPrimitiveTypeKind.GeographyMultiPolygon: + case EdmPrimitiveTypeKind.GeographyMultiLineString: + case EdmPrimitiveTypeKind.GeographyMultiPoint: + this.spatialReferenceIdentifier = CsdlConstants.Default_SpatialGeographySrid; + break; + + // In the case of a BadSpatialTypeReference, the PrimitiveTypeKind is none, and we will treat that the same as Geometry. + default: + this.spatialReferenceIdentifier = CsdlConstants.Default_SpatialGeometrySrid; + break; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + /// Spatial Reference Identifier for the spatial type being created. + public EdmSpatialTypeReference(IEdmPrimitiveType definition, bool isNullable, int? spatialReferenceIdentifier) + : base(definition, isNullable) + { + this.spatialReferenceIdentifier = spatialReferenceIdentifier; + } + + /// + /// Gets the precision of this temporal type. + /// + public int? SpatialReferenceIdentifier + { + get { return this.spatialReferenceIdentifier; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStringTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStringTypeReference.cs new file mode 100644 index 0000000..253a303 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStringTypeReference.cs @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM string type. + /// + public class EdmStringTypeReference : EdmPrimitiveTypeReference, IEdmStringTypeReference + { + private readonly bool isUnbounded; + private readonly int? maxLength; + private readonly bool? isUnicode; + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + public EdmStringTypeReference(IEdmPrimitiveType definition, bool isNullable) + : this(definition, isNullable, false, null, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + /// Denotes whether the max length is the maximum allowed value. + /// Maximum length of a value of this type. + /// Denotes if string is encoded using Unicode. + public EdmStringTypeReference(IEdmPrimitiveType definition, bool isNullable, bool isUnbounded, int? maxLength, bool? isUnicode) + : base(definition, isNullable) + { + if (isUnbounded && maxLength != null) + { + throw new InvalidOperationException(Edm.Strings.EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull); + } + + this.isUnbounded = isUnbounded; + this.maxLength = maxLength; + this.isUnicode = isUnicode; + } + + /// + /// Gets a value indicating whether this string type specifies the maximum allowed length. + /// + public bool IsUnbounded + { + get { return this.isUnbounded; } + } + + /// + /// Gets the maximum length of this string type. + /// + public int? MaxLength + { + get { return this.maxLength; } + } + + /// + /// Gets a value indicating whether this string type supports unicode encoding. + /// + public bool? IsUnicode + { + get { return this.isUnicode; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuralProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuralProperty.cs new file mode 100644 index 0000000..d832838 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuralProperty.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM structural (i.e. non-navigation) property. + /// + public class EdmStructuralProperty : EdmProperty, IEdmStructuralProperty + { + private readonly string defaultValueString; + + /// + /// Initializes a new instance of the class. + /// + /// The type that declares this property. + /// Name of the property. + /// The type of the property. + public EdmStructuralProperty(IEdmStructuredType declaringType, string name, IEdmTypeReference type) + : this(declaringType, name, type, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type that declares this property. + /// Name of the property. + /// The type of the property. + /// The default value of this property. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Justification = "defaultValue might be confused for an IEdmValue.")] + public EdmStructuralProperty(IEdmStructuredType declaringType, string name, IEdmTypeReference type, string defaultValueString) + : base(declaringType, name, type) + { + this.defaultValueString = defaultValueString; + } + + /// + /// Gets the default value of this property. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", Justification = "defaultValue might be confused for an IEdmValue.")] + public string DefaultValueString + { + get { return this.defaultValueString; } + } + + /// + /// Gets the kind of this property. + /// + public override EdmPropertyKind PropertyKind + { + get { return EdmPropertyKind.Structural; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuredType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuredType.cs new file mode 100644 index 0000000..8d104fc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmStructuredType.cs @@ -0,0 +1,192 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Common base class for definitions of EDM structured types. + /// + public abstract class EdmStructuredType : EdmType, IEdmStructuredType + { + private readonly IEdmStructuredType baseStructuredType; + private readonly List declaredProperties = new List(); + private readonly bool isAbstract; + private readonly bool isOpen; + + // PropertiesDictionary cache. + private readonly Cache> propertiesDictionary = new Cache>(); + private static readonly Func> ComputePropertiesDictionaryFunc = (me) => me.ComputePropertiesDictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// Denotes a structured type that cannot be instantiated. + /// Denotes if the type is open. + /// Base type of the type + protected EdmStructuredType(bool isAbstract, bool isOpen, IEdmStructuredType baseStructuredType) + { + this.isAbstract = isAbstract; + this.isOpen = isOpen; + this.baseStructuredType = baseStructuredType; + } + + /// + /// Gets a value indicating whether this type is abstract. + /// + public bool IsAbstract + { + get { return this.isAbstract; } + } + + /// + /// Gets a value indicating whether this type is open. + /// + public bool IsOpen + { + get { return this.isOpen; } + } + + /// + /// Gets the properties declared immediately within this type. + /// + public virtual IEnumerable DeclaredProperties + { + get { return this.declaredProperties; } + } + + /// + /// Gets the base type of this type. + /// + public IEdmStructuredType BaseType + { + get { return this.baseStructuredType; } + } + + /// + /// Gets a dictionary of the properties in this type definition for faster lookup. + /// + protected IDictionary PropertiesDictionary + { + get { return this.propertiesDictionary.GetValue(this, ComputePropertiesDictionaryFunc, null); } + } + + /// + /// Adds the to this type. + /// of the must be this type. + /// + /// The property being added. + public void AddProperty(IEdmProperty property) + { + EdmUtil.CheckArgumentNull(property, "property"); + + if (!Object.ReferenceEquals(this, property.DeclaringType)) + { + throw new InvalidOperationException(Edm.Strings.EdmModel_Validator_Semantic_DeclaringTypeMustBeCorrect(property.Name)); + } + + this.declaredProperties.Add(property); + this.propertiesDictionary.Clear(null); + } + + /// + /// Creates and adds a nullable structural property to this type. + /// + /// Name of the property. + /// Type of the property. + /// Created structural property. + public EdmStructuralProperty AddStructuralProperty(string name, EdmPrimitiveTypeKind type) + { + EdmStructuralProperty property = new EdmStructuralProperty(this, name, EdmCoreModel.Instance.GetPrimitive(type, true)); + this.AddProperty(property); + return property; + } + + /// + /// Creates and adds a nullable structural property to this type. + /// + /// Name of the property. + /// Type of the property. + /// Flag specifying if the property is nullable. + /// Created structural property. + public EdmStructuralProperty AddStructuralProperty(string name, EdmPrimitiveTypeKind type, bool isNullable) + { + EdmStructuralProperty property = new EdmStructuralProperty(this, name, EdmCoreModel.Instance.GetPrimitive(type, isNullable)); + this.AddProperty(property); + return property; + } + + /// + /// Creates and adds a structural property to this type. + /// + /// Name of the property. + /// Type of the property. + /// Created structural property. + public EdmStructuralProperty AddStructuralProperty(string name, IEdmTypeReference type) + { + EdmStructuralProperty property = new EdmStructuralProperty(this, name, type); + this.AddProperty(property); + return property; + } + + /// + /// Creates and adds a structural property to this type. + /// + /// Name of the property. + /// Type of the property. + /// The default value of this property. + /// Created structural property. + public EdmStructuralProperty AddStructuralProperty(string name, IEdmTypeReference type, string defaultValue) + { + EdmStructuralProperty property = new EdmStructuralProperty(this, name, type, defaultValue); + this.AddProperty(property); + return property; + } + + /// + /// Creates and adds a unidirectional navigation property to this type. + /// + /// Information to create the navigation property. + /// Created navigation property. + public EdmNavigationProperty AddUnidirectionalNavigation(EdmNavigationPropertyInfo propertyInfo) + { + EdmUtil.CheckArgumentNull(propertyInfo, "propertyInfo"); + + EdmNavigationProperty property = EdmNavigationProperty.CreateNavigationProperty(this, propertyInfo); + + this.AddProperty(property); + return property; + } + + /// + /// Searches for a structural or navigation property with the given name in this type and all base types and returns null if no such property exists. + /// + /// The name of the property being found. + /// The requested property, or null if no such property exists. + public IEdmProperty FindProperty(string name) + { + IEdmProperty property; + return this.PropertiesDictionary.TryGetValue(name, out property) ? property : null; + } + + /// + /// Computes the the cached dictionary of properties for this type definition. + /// + /// Dictionary of properties keyed by their name. + private IDictionary ComputePropertiesDictionary() + { + Dictionary properties = new Dictionary(); + foreach (IEdmProperty property in this.Properties()) + { + RegistrationHelper.RegisterProperty(property, property.Name, properties); + } + + return properties; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTemporalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTemporalTypeReference.cs new file mode 100644 index 0000000..d96612a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTemporalTypeReference.cs @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM temporal (Duration, DateTime, DateTimeOffset) type. + /// + public class EdmTemporalTypeReference : EdmPrimitiveTypeReference, IEdmTemporalTypeReference + { + private readonly int? precision; + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + public EdmTemporalTypeReference(IEdmPrimitiveType definition, bool isNullable) + : this(definition, isNullable, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type this reference refers to. + /// Denotes whether the type can be nullable. + /// Precision of values with this type. + public EdmTemporalTypeReference(IEdmPrimitiveType definition, bool isNullable, int? precision) + : base(definition, isNullable) + { + this.precision = precision; + } + + /// + /// Gets the precision of this temporal type. + /// + public int? Precision + { + get { return this.precision; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmType.cs new file mode 100644 index 0000000..41cd061 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmType.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents the definition of an EDM type. + /// + public abstract class EdmType : EdmElement, IEdmType + { + /// + /// Gets the kind of this type. + /// + public abstract EdmTypeKind TypeKind + { + get; + } + + /// + /// Returns the text representation of the current object. + /// + /// The text representation of the current object. + public override string ToString() + { + return this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinition.cs new file mode 100644 index 0000000..e2790a2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinition.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents the definition of an Edm type definition. + /// + public class EdmTypeDefinition : EdmType, IEdmTypeDefinition, IEdmFullNamedElement + { + private readonly IEdmPrimitiveType underlyingType; + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + /// + /// Initializes a new instance of the class with underlying type. + /// + /// Namespace this type definition belongs to. + /// Name of this type definition. + /// The underlying type of this type definition. + public EdmTypeDefinition(string namespaceName, string name, EdmPrimitiveTypeKind underlyingType) + : this(namespaceName, name, EdmCoreModel.Instance.GetPrimitiveType(underlyingType)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace this type definition belongs to. + /// Name of this type definition. + /// The underlying type of this type definition. + public EdmTypeDefinition(string namespaceName, string name, IEdmPrimitiveType underlyingType) + { + EdmUtil.CheckArgumentNull(underlyingType, "underlyingType"); + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.underlyingType = underlyingType; + this.name = name; + this.namespaceName = namespaceName; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.name); + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get { return EdmTypeKind.TypeDefinition; } + } + + /// + /// Gets the kind of this schema element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.TypeDefinition; } + } + + /// + /// Gets the namespace this schema element belongs to. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the name of this type definition. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the underlying type of this type definition. + /// + public IEdmPrimitiveType UnderlyingType + { + get { return this.underlyingType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinitionReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinitionReference.cs new file mode 100644 index 0000000..a99b2de --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeDefinitionReference.cs @@ -0,0 +1,149 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM type definition. + /// + public class EdmTypeDefinitionReference : EdmTypeReference, IEdmTypeDefinitionReference + { + /// + /// Initializes a new instance of the class. + /// + /// The definition refered to by this reference. + /// Denotes whether the type can be nullable. + public EdmTypeDefinitionReference(IEdmTypeDefinition typeDefinition, bool isNullable) + : base(typeDefinition, isNullable) + { + this.IsUnbounded = false; + this.MaxLength = null; + this.IsUnicode = ComputeDefaultIsUnicode(typeDefinition); + this.Precision = ComputeDefaultPrecision(typeDefinition); + this.Scale = ComputeDefaultScale(typeDefinition); + this.SpatialReferenceIdentifier = ComputeSrid(typeDefinition); + } + + /// + /// Initializes a new instance of the class. + /// + /// The definition refered to by this reference. + /// Denotes whether the type can be nullable. + /// Denotes whether the length is unbounded. + /// Maximum length of a value of this type. + /// Denotes if string is encoded using Unicode. + /// Precision of values with this type. + /// Scale of values with this type. + /// Spatial Reference Identifier for the spatial type being created. + public EdmTypeDefinitionReference( + IEdmTypeDefinition typeDefinition, + bool isNullable, + bool isUnbounded, + int? maxLength, + bool? isUnicode, + int? precision, + int? scale, + int? spatialReferenceIdentifier) + : base(typeDefinition, isNullable) + { + this.IsUnbounded = isUnbounded; + this.MaxLength = maxLength; + this.IsUnicode = isUnicode; + this.Precision = precision; + this.Scale = scale; + this.SpatialReferenceIdentifier = spatialReferenceIdentifier; + } + + /// + /// Gets a value indicating whether the length of the underlying type + /// is unbounded where the maximum length depends on the underlying + /// type itself and is invalid. + /// This facet ONLY applies when the underlying type is Edm.Binary, + /// Edm.Stream or Edm.String. + /// + public bool IsUnbounded { get; private set; } + + /// + /// Gets the maximum length of the underlying type. This value is only + /// effective when is false. + /// This facet ONLY applies when the underlying type is Edm.Binary, + /// Edm.Stream or Edm.String. + /// + public int? MaxLength { get; private set; } + + /// + /// Gets a value indicating whether the underlying type supports unicode encoding. + /// This facet ONLY applies when the underlying type is Edm.String. + /// + public bool? IsUnicode { get; private set; } + + /// + /// Gets the precision of the underlying type. + /// This facet ONLY applies when the underlying type is Edm.DateTimeOffset, + /// Edm.Decimal, Edm.Duration or Edm.TimeOfDay. + /// + public int? Precision { get; private set; } + + /// + /// Gets the scale of the underlying type. + /// This facet ONLY applies when the underlying type is Edm.Decimal. + /// + public int? Scale { get; private set; } + + /// + /// Gets the Spatial Reference Identifier of the underlying type. + /// This facet ONLY applies when the underlying type is a spatial type. + /// + public int? SpatialReferenceIdentifier { get; private set; } + + private static bool? ComputeDefaultIsUnicode(IEdmTypeDefinition typeDefinition) + { + if (typeDefinition.UnderlyingType.IsString()) + { + return true; + } + + return null; + } + + private static int? ComputeDefaultPrecision(IEdmTypeDefinition typeDefinition) + { + if (typeDefinition.UnderlyingType.IsTemporal()) + { + return CsdlConstants.Default_TemporalPrecision; + } + + return null; + } + + private static int? ComputeDefaultScale(IEdmTypeDefinition typeDefinition) + { + if (typeDefinition.UnderlyingType.IsDecimal()) + { + return CsdlConstants.Default_Scale; + } + + return null; + } + + private static int? ComputeSrid(IEdmTypeDefinition typeDefinition) + { + if (typeDefinition.UnderlyingType.IsGeography()) + { + return CsdlConstants.Default_SpatialGeographySrid; + } + + if (typeDefinition.UnderlyingType.IsGeometry()) + { + return CsdlConstants.Default_SpatialGeometrySrid; + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeReference.cs new file mode 100644 index 0000000..1876477 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmTypeReference.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM type. + /// + public abstract class EdmTypeReference : EdmElement, IEdmTypeReference + { + private readonly IEdmType definition; + private readonly bool isNullable; + + /// + /// Initializes a new instance of the class. + /// + /// Type that describes this value. + /// Denotes whether the type can be nullable. + protected EdmTypeReference(IEdmType definition, bool isNullable) + { + EdmUtil.CheckArgumentNull(definition, "definition"); + + this.definition = definition; + this.isNullable = isNullable; + } + + /// + /// Gets a value indicating whether this type is nullable. + /// + public bool IsNullable + { + get { return this.isNullable; } + } + + /// + /// Gets the definition to which this type refers. + /// + public IEdmType Definition + { + get { return this.definition; } + } + + /// + /// Returns the text representation of the current object. + /// + /// The text representation of the current object. + public override string ToString() + { + return this.ToTraceString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUnknownEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUnknownEntitySet.cs new file mode 100644 index 0000000..6b5eef0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUnknownEntitySet.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System.Collections.Generic; + + /// + /// Represents an EDM unknown entity set + /// + internal class EdmUnknownEntitySet : EdmEntitySetBase, IEdmUnknownEntitySet + { + private readonly IEdmNavigationProperty navigationProperty; + private readonly IEdmNavigationSource parentNavigationSource; + private IEdmPathExpression path; + + /// + /// Initializes a new instance of the class. + /// + /// The that container element belongs to + /// An containing the navagation property definition of the contained element + public EdmUnknownEntitySet(IEdmNavigationSource parentNavigationSource, IEdmNavigationProperty navigationProperty) + : base(navigationProperty.Name, navigationProperty.ToEntityType()) + { + EdmUtil.CheckArgumentNull(parentNavigationSource, "parentNavigationSource"); + EdmUtil.CheckArgumentNull(navigationProperty, "navigationProperty"); + + this.parentNavigationSource = parentNavigationSource; + this.navigationProperty = navigationProperty; + } + + /// + /// Gets the path that a navigation property targets. This property is not thread safe. + /// + public override IEdmPathExpression Path + { + get { return this.path ?? (this.path = ComputePath()); } + } + + /// + /// Gets the type of this navigation source. + /// + public override IEdmType Type + { + get { return this.navigationProperty.Type.Definition; } + } + + /// + /// Finds the entity set that a navigation property targets. + /// + /// The navigation property. + /// The entity set that the navigation propertion targets, or null if no such entity set exists. + /// TODO: change null logic to using UnknownEntitySet + public override IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty property) + { + return null; + } + + private IEdmPathExpression ComputePath() + { + List newPath = new List(this.parentNavigationSource.Path.PathSegments); + newPath.Add(this.navigationProperty.Name); + return new EdmPathExpression(newPath.ToArray()); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs new file mode 100644 index 0000000..b10e2e2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredType.cs @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.OData.Edm +{ + /// + /// Common base class for definitions of EDM structured types. + /// + public sealed class EdmUntypedStructuredType : EdmStructuredType, IEdmStructuredType, IEdmSchemaElement, IEdmSchemaType, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string name; + private readonly string fullName; + + /// + /// Initializes a new instance of the class. + /// + /// The namespace this type belongs to. + /// The name of this type within its namespace. + public EdmUntypedStructuredType(string namespaceName, string name) + : base(/*isAbstract*/true, /*isOpen*/true, /*baseType*/ null) + { + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.namespaceName = namespaceName; + this.name = name; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.name); + } + + /// + /// Initializes a new instance of the class. + /// + public EdmUntypedStructuredType() + : this(EdmConstants.EdmNamespace, CsdlConstants.TypeName_Untyped_Short) + { + } + + /// + /// Gets the namespace of this element. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the name of this element. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the kind of this type. + /// + public override EdmTypeKind TypeKind + { + get + { + return EdmTypeKind.Untyped; + } + } + + /// + /// Gets the kind of this schema element. + /// + public EdmSchemaElementKind SchemaElementKind + { + get + { + return EdmSchemaElementKind.TypeDefinition; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs new file mode 100644 index 0000000..078f44c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedStructuredTypeReference.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM Untyped type. + /// + public class EdmUntypedStructuredTypeReference : EdmTypeReference, IEdmUntypedTypeReference, IEdmStructuredTypeReference + { + /// + /// Constructor + /// + /// IEdmStructuredType definition. + public EdmUntypedStructuredTypeReference(IEdmStructuredType definition) + : base(definition, true) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedTypeReference.cs new file mode 100644 index 0000000..9a9fec6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/EdmUntypedTypeReference.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM Untyped type. + /// + public class EdmUntypedTypeReference : EdmTypeReference, IEdmUntypedTypeReference + { + /// + /// Constructor + /// + /// IEdmUntypedType definition. + public EdmUntypedTypeReference(IEdmUntypedType definition) + : base(definition, true) + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmNavigationTargetMapping.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmNavigationTargetMapping.cs new file mode 100644 index 0000000..2449fff --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmNavigationTargetMapping.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a mapping from an EDM navigation property to an entity set. + /// + public interface IEdmNavigationTargetMapping + { + /// + /// Gets the navigation property. + /// + IEdmNavigationProperty NavigationProperty { get; } + + /// + /// Gets the target entity set. + /// + IEdmEntitySet TargetEntitySet { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmRowType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmRowType.cs new file mode 100644 index 0000000..bd749a6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/IEdmRowType.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM row type. + /// + public interface IEdmRowType : IEdmStructuredType + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmAction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmAction.cs new file mode 100644 index 0000000..0faf073 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmAction.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM action. + /// + public interface IEdmAction : IEdmOperation + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmActionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmActionImport.cs new file mode 100644 index 0000000..2cd3cf9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmActionImport.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM action import. + /// + public interface IEdmActionImport : IEdmOperationImport + { + /// + /// Gets the action type of the import. + /// + IEdmAction Action { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmBinaryTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmBinaryTypeReference.cs new file mode 100644 index 0000000..c7af63b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmBinaryTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM binary type. + /// + public interface IEdmBinaryTypeReference : IEdmPrimitiveTypeReference + { + /// + /// Gets a value indicating whether this type specifies the maximum allowed length. + /// + bool IsUnbounded { get; } + + /// + /// Gets the maximum length of this type. + /// + int? MaxLength { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCheckable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCheckable.cs new file mode 100644 index 0000000..b5ba1f4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCheckable.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm +{ + /// + /// Defines an Edm component who is invalid or whose validity is unknown at construction + /// + public interface IEdmCheckable + { + /// + /// Gets an error if one exists with the current object. + /// + IEnumerable Errors { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionType.cs new file mode 100644 index 0000000..748d98d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionType.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM collection type. + /// + public interface IEdmCollectionType : IEdmType + { + /// + /// Gets the element type of this collection. + /// + IEdmTypeReference ElementType { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionTypeReference.cs new file mode 100644 index 0000000..23f4371 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmCollectionTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to EDM Collection types. + /// + public interface IEdmCollectionTypeReference : IEdmTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexType.cs new file mode 100644 index 0000000..204335a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexType.cs @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM complex type. + /// + public interface IEdmComplexType : IEdmStructuredType, IEdmSchemaType + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexTypeReference.cs new file mode 100644 index 0000000..41ae230 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmComplexTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to EDM complex types. + /// + public interface IEdmComplexTypeReference : IEdmStructuredTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmContainedEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmContainedEntitySet.cs new file mode 100644 index 0000000..a22256a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmContainedEntitySet.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM contained entity set. + /// + public interface IEdmContainedEntitySet : IEdmEntitySetBase + { + /// The parent navigation source of this contained entity set. + IEdmNavigationSource ParentNavigationSource { get; } + + /// The navigation property of this contained entity set. + IEdmNavigationProperty NavigationProperty { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmDecimalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmDecimalTypeReference.cs new file mode 100644 index 0000000..fbe7fcc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmDecimalTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM decimal type. + /// + public interface IEdmDecimalTypeReference : IEdmPrimitiveTypeReference + { + /// + /// Gets the precision of this type. + /// + int? Precision { get; } + + /// + /// Gets the scale of this type. + /// + int? Scale { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmElement.cs new file mode 100644 index 0000000..f7480e5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmElement.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Common base interface for all EDM elements. + /// + public interface IEdmElement + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainer.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainer.cs new file mode 100644 index 0000000..3c4d517 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainer.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM entity container. + /// + public interface IEdmEntityContainer : IEdmSchemaElement, IEdmVocabularyAnnotatable + { + /// + /// Gets a collection of the elements of this entity container. + /// + IEnumerable Elements { get; } + + /// + /// Searches for an entity set with the given name in this entity container and returns null if no such set exists. + /// + /// The name of the element being found. + /// The requested element, or null if the element does not exist. + IEdmEntitySet FindEntitySet(string setName); + + /// + /// Searches for a singleton with the given name in this entity container and returns null if no such singleton exists. + /// + /// The name of the singleton to search. + /// The requested singleton, or null if the singleton does not exist. + IEdmSingleton FindSingleton(string singletonName); + + /// + /// Searches for operation imports with the given name in this entity container and returns null if no such operation import exists. + /// + /// The name of the operations to find. + /// A group of the requested operation imports, or an empty enumerable if no such operation import exists. + IEnumerable FindOperationImports(string operationName); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainerElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainerElement.cs new file mode 100644 index 0000000..9f4cf8e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityContainerElement.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Defines EDM container element types. + /// + public enum EdmContainerElementKind + { + /// + /// Represents an element where the container kind is unknown or in error. + /// + None = 0, + + /// + /// Represents an element implementing . + /// + EntitySet, + + /// + /// Represents an element implementing . + /// + ActionImport, + + /// + /// Represents an element implementing . + /// + FunctionImport, + + /// + /// Represents an element implementing . + /// + Singleton + } + + /// + /// Represents the common elements of all EDM entity container elements. + /// + public interface IEdmEntityContainerElement : IEdmNamedElement, IEdmVocabularyAnnotatable + { + /// + /// Gets the kind of element of this container element. + /// + EdmContainerElementKind ContainerElementKind { get; } + + /// + /// Gets the container that contains this element. + /// + IEdmEntityContainer Container { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceType.cs new file mode 100644 index 0000000..95a05ec --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceType.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM entity reference type. + /// + public interface IEdmEntityReferenceType : IEdmType + { + /// + /// Gets the entity type pointed to by this entity reference. + /// + IEdmEntityType EntityType { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceTypeReference.cs new file mode 100644 index 0000000..61b59a6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityReferenceTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to entity reference types. + /// + public interface IEdmEntityReferenceTypeReference : IEdmTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySet.cs new file mode 100644 index 0000000..473286d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySet.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM entity set. + /// + public interface IEdmEntitySet : IEdmEntitySetBase, IEdmEntityContainerElement + { + /// + /// Gets a value indicating whether the entity set is included in the service document. + /// + bool IncludeInServiceDocument { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySetBase.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySetBase.cs new file mode 100644 index 0000000..1a32978 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntitySetBase.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM base entity set. + /// + public interface IEdmEntitySetBase : IEdmNavigationSource + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs new file mode 100644 index 0000000..5c508ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM entity type. + /// + public interface IEdmEntityType : IEdmStructuredType, IEdmSchemaType + { + /// + /// Gets the structural properties of the entity type that make up the entity key. + /// + IEnumerable DeclaredKey { get; } + + /// + /// Gets the value indicating whether or not this type is a media entity. + /// + bool HasStream { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityTypeReference.cs new file mode 100644 index 0000000..4403c3b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to entity types. + /// + public interface IEdmEntityTypeReference : IEdmStructuredTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMember.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMember.cs new file mode 100644 index 0000000..a57687d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMember.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM enumeration type member. + /// + public interface IEdmEnumMember : IEdmNamedElement, IEdmVocabularyAnnotatable + { + /// + /// Gets the value of this enumeration type member. + /// + IEdmEnumMemberValue Value { get; } + + /// + /// Gets the type that this member belongs to. + /// + IEdmEnumType DeclaringType { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMemberValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMemberValue.cs new file mode 100644 index 0000000..23e51c2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumMemberValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The EdmEnumMemberValue interface. + /// + public interface IEdmEnumMemberValue : IEdmElement + { + /// + /// The value of enum member + /// + long Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumType.cs new file mode 100644 index 0000000..f13c04c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumType.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM enumeration type. + /// + public interface IEdmEnumType : IEdmSchemaType + { + /// + /// Gets the underlying type of this enumeration type. + /// + IEdmPrimitiveType UnderlyingType { get; } + + /// + /// Gets the members of this enumeration type. + /// + IEnumerable Members { get; } + + /// + /// Gets a value indicating whether the enumeration type can be treated as a bit field. + /// + bool IsFlags { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumTypeReference.cs new file mode 100644 index 0000000..d792253 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEnumTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to EDM enumeration types. + /// + public interface IEdmEnumTypeReference : IEdmTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmExpression.cs new file mode 100644 index 0000000..5c8b9b0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmExpression.cs @@ -0,0 +1,157 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Defines EDM expression kinds. + /// + public enum EdmExpressionKind + { + /// + /// Represents an expression with unknown or error kind. + /// + None = 0, + + /// + /// Represents an expression implementing . + /// + BinaryConstant, + + /// + /// Represents an expression implementing . + /// + BooleanConstant, + + /// + /// Represents an expression implementing . + /// + DateTimeOffsetConstant, + + /// + /// Represents an expression implementing . + /// + DecimalConstant, + + /// + /// Represents an expression implementing . + /// + FloatingConstant, + + /// + /// Represents an expression implementing . + /// + GuidConstant, + + /// + /// Represents an expression implementing . + /// + IntegerConstant, + + /// + /// Represents an expression implementing . + /// + StringConstant, + + /// + /// Represents an expression implementing . + /// + DurationConstant, + + /// + /// Represents an expression implementing . + /// + Null, + + /// + /// Represents an expression implementing . + /// + Record, + + /// + /// Represents an expression implementing . + /// + Collection, + + /// + /// Represents an expression implementing . + /// + Path, + + /// + /// Represents an expression implementing . + /// + If, + + /// + /// Represents an expression implementing . + /// + Cast, + + /// + /// Represents an expression implementing . + /// + IsType, + + /// + /// Represents an expression implementing . + /// + FunctionApplication, + + /// + /// Represents an expression implementing . + /// + LabeledExpressionReference, + + /// + /// Represents an expression implementing + /// + Labeled, + + /// + /// Represents an expression implementing . + /// + PropertyPath, + + /// + /// Represents an expression implementing . + /// + NavigationPropertyPath, + + /// + /// Represents an expression implementing . + /// + DateConstant, + + /// + /// Represents an expression implementing . + /// + TimeOfDayConstant, + + /// + /// Represents an expression implementing . + /// + EnumMember, + + /// + /// Represents an expression implementing . + /// + AnnotationPath + } + + /// + /// Represents an EDM expression. + /// + public interface IEdmExpression : IEdmElement + { + /// + /// Gets the kind of this expression. + /// + EdmExpressionKind ExpressionKind { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFullNamedElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFullNamedElement.cs new file mode 100644 index 0000000..f823141 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFullNamedElement.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Common base interface for all named EDM elements. + /// + public interface IEdmFullNamedElement : IEdmNamedElement + { + /// + /// Gets the full name of this element. + /// + string FullName { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunction.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunction.cs new file mode 100644 index 0000000..89e8512 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunction.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM function. + /// + public interface IEdmFunction : IEdmOperation + { + /// + /// Gets a value indicating whether this instance is composable. + /// + bool IsComposable { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunctionImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunctionImport.cs new file mode 100644 index 0000000..65d64a1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmFunctionImport.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM function import. + /// + public interface IEdmFunctionImport : IEdmOperationImport + { + /// + /// Gets a value indicating whether [include in service document]. + /// + bool IncludeInServiceDocument { get; } + + /// + /// Gets the function that defines the function import. + /// + IEdmFunction Function { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmInclude.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmInclude.cs new file mode 100644 index 0000000..818d2b8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmInclude.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The interface of include information for referenced model. + /// + public interface IEdmInclude + { + /// + /// Gets the alias. + /// + string Alias { get; } + + /// + /// Gets the namespace to include. + /// + string Namespace { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmIncludeAnnotations.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmIncludeAnnotations.cs new file mode 100644 index 0000000..4ec1d93 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmIncludeAnnotations.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// The interface of includeAnnotation information for referenced model. + /// + public interface IEdmIncludeAnnotations + { + /// + /// Gets the term namespace to include. + /// + string TermNamespace { get; } + + /// + /// Gets the Qualifier. + /// + string Qualifier { get; } + + /// + /// Getst the target namespace. + /// + string TargetNamespace { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmLocatable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmLocatable.cs new file mode 100644 index 0000000..0b03087 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmLocatable.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Interface for all EDM elements that can be located. + /// + public interface IEdmLocatable + { + /// + /// Gets the location of this element. + /// + EdmLocation Location { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmModel.cs new file mode 100644 index 0000000..eb9cad5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmModel.cs @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Semantic representation of an EDM model. + /// + /// + /// This interface, and all interfaces reachable from it, preserve certain invariants: + /// -- The backing implementation of an element can be loaded or created on demand. + /// -- No direct element mutation occurs through the interfaces. + /// Only the MainModel and ReferencedModels properties are for referneced models scenario. all other properties and methods only focus on this model, not main/sibling/referenced models . + /// + public interface IEdmModel : IEdmElement + { + /// + /// Gets the collection of schema elements that are contained in this model. + /// + IEnumerable SchemaElements { get; } + + /// + /// Gets the collection of vocabulary annotations that are contained in this model. + /// + IEnumerable VocabularyAnnotations { get; } + + /// + /// Gets the collection of models referred to by this model (mainly by the this.References). + /// + IEnumerable ReferencedModels { get; } + + /// + /// Gets the collection of namespaces that schema elements use contained in this model. + /// + IEnumerable DeclaredNamespaces { get; } + + /// + /// Gets the model's annotations manager. + /// + IEdmDirectValueAnnotationsManager DirectValueAnnotationsManager { get; } + + /// + /// Gets the only one entity container of the model. + /// + IEdmEntityContainer EntityContainer { get; } + + /// + /// Searches for a type with the given name in this model only and returns null if no such type exists. + /// + /// The qualified name of the type being found. + /// The requested type, or null if no such type exists. + IEdmSchemaType FindDeclaredType(string qualifiedName); + + /// + /// Searches for bound operations based on the binding type, returns an empty enumerable if no operation exists. + /// + /// Type of the binding. + /// A set of operations that share the binding type or empty enumerable if no such operation exists. + IEnumerable FindDeclaredBoundOperations(IEdmType bindingType); + + /// + /// Searches for bound operations based on the qualified name and binding type, returns an empty enumerable if no operation exists. + /// + /// The qualified name of the operation. + /// Type of the binding. + /// A set of operations that share the qualified name and binding type or empty enumerable if no such operation exists. + IEnumerable FindDeclaredBoundOperations(string qualifiedName, IEdmType bindingType); + + /// + /// Searches for operations with the given name in this model and returns an empty enumerable if no such operation exists. + /// + /// The qualified name of the operation being found. + /// A set of operations sharing the specified qualified name, or an empty enumerable if no such operation exists. + IEnumerable FindDeclaredOperations(string qualifiedName); + + /// + /// Searches for a term with the given name in this model and returns null if no such term exists. + /// + /// The qualified name of the term being found. + /// The requested term, or null if no such term exists. + IEdmTerm FindDeclaredTerm(string qualifiedName); + + /// + /// Searches for vocabulary annotations specified by this model. + /// + /// The annotated element. + /// The vocabulary annotations for the element. + IEnumerable FindDeclaredVocabularyAnnotations(IEdmVocabularyAnnotatable element); + + /// + /// Finds a list of types that derive directly from the supplied type. + /// + /// The base type that derived types are being searched for. + /// A list of types from this model that derive directly from the given type. + IEnumerable FindDirectlyDerivedTypes(IEdmStructuredType baseType); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNamedElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNamedElement.cs new file mode 100644 index 0000000..5d49c0f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNamedElement.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Common base interface for all named EDM elements. + /// + public interface IEdmNamedElement : IEdmElement + { + /// + /// Gets the name of this element. + /// + string Name { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationProperty.cs new file mode 100644 index 0000000..fbc1b1e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationProperty.cs @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Enumerates the multiplicities of EDM navigation properties. + /// + public enum EdmMultiplicity + { + /// + /// The Multiplicity of the association end is unknown. + /// + Unknown = 0, + + /// + /// The Multiplicity of the association end is zero or one. + /// + ZeroOrOne, + + /// + /// The Multiplicity of the association end is one. + /// + One, + + /// + /// The Multiplicity of the association end is many. + /// + Many + } + + /// + /// Enumerates the actions EDM can apply on deletes. + /// + public enum EdmOnDeleteAction + { + /// + /// Take no action on delete. + /// + None = 0, + + /// + /// On delete also delete items on the other end of the association. + /// + Cascade + } + + /// + /// Represents an EDM navigation property. + /// + public interface IEdmNavigationProperty : IEdmProperty + { + /// + /// Gets the partner of this navigation property. + /// + IEdmNavigationProperty Partner { get; } + + /// + /// Gets the action to execute on the deletion of this end of a bidirectional association. + /// + EdmOnDeleteAction OnDelete { get; } + + /// + /// Gets a value indicating whether the navigation target is contained inside the navigation source. + /// + bool ContainsTarget { get; } + + /// + /// Gets the referential constraint for this navigation, returning null if this is the principal end or if there is no referential constraint. + /// + IEdmReferentialConstraint ReferentialConstraint { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationPropertyBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationPropertyBinding.cs new file mode 100644 index 0000000..517e9a0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationPropertyBinding.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a binding from an EDM navigation property to a navigation source. + /// + public interface IEdmNavigationPropertyBinding + { + /// + /// Gets the navigation property. + /// + IEdmNavigationProperty NavigationProperty { get; } + + /// + /// Gets the target navigation source. + /// + IEdmNavigationSource Target { get; } + + /// + /// Get the path that a navigation property targets. + /// + IEdmPathExpression Path { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationSource.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationSource.cs new file mode 100644 index 0000000..0816e78 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmNavigationSource.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Defines the kind of navigation source + /// + public enum EdmNavigationSourceKind + { + /// + /// Represents a value with an unknown or error kind. + /// + None = 0, + + /// + /// Represents EntitySet + /// + EntitySet, + + /// + /// Represents Singleton + /// + Singleton, + + /// + /// Represents ContainedEntitySet + /// + ContainedEntitySet, + + /// + /// Represents UnknownEntitySet + /// + UnknownEntitySet, + } + + /// + /// Represents an EDM navigation source. + /// + public interface IEdmNavigationSource : IEdmNamedElement + { + /// + /// Gets the navigation property bindings of this navigation source. + /// + IEnumerable NavigationPropertyBindings { get; } + + /// + /// Gets the path of this navigation source. + /// + IEdmPathExpression Path { get; } + + /// + /// Gets the type of this navigation source. + /// + IEdmType Type { get; } + + /// + /// Finds the navigation source that a navigation property targets. + /// + /// The navigation property. + /// The navigation source that the navigation property targets + IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty); + + /// + /// Finds the navigation source that a navigation property targets. + /// + /// The navigation property. + /// The binding path of the navigation property. + /// The navigation source that the navigation property targets + IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath); + + /// + /// Finds the bindings of the navigation property. + /// + /// The navigation property. + /// The list of bindings for current navigation property. + IEnumerable FindNavigationPropertyBindings(IEdmNavigationProperty navigationProperty); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperation.cs new file mode 100644 index 0000000..c622fb4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperation.cs @@ -0,0 +1,49 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System.Collections.Generic; + + /// + /// Represents an EDM operation. + /// + public interface IEdmOperation : IEdmSchemaElement + { + /// + /// Gets the return type of this operation. + /// + IEdmTypeReference ReturnType { get; } + + /// + /// Gets the collection of parameters for this operation. + /// + IEnumerable Parameters { get; } + + /// + /// Gets a value indicating whether this instance is bound. + /// + /// + /// true if this instance is bound; otherwise, false. + /// + bool IsBound { get; } + + /// + /// Gets the entity set path expression. + /// + /// + /// The entity set path expression. + /// + IEdmPathExpression EntitySetPath { get; } + + /// + /// Searches for a parameter with the given name, and returns null if no such parameter exists. + /// + /// The name of the parameter being found. + /// The requested parameter or null if no such parameter exists. + IEdmOperationParameter FindParameter(string name); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationImport.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationImport.cs new file mode 100644 index 0000000..676516f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationImport.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM operation import. + /// + public interface IEdmOperationImport : IEdmEntityContainerElement + { + /// + /// Gets the operation. + /// + IEdmOperation Operation { get; } + + /// + /// Gets the entity set containing entities returned by this operation import. + /// + IEdmExpression EntitySet { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationParameter.cs new file mode 100644 index 0000000..6d2189f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationParameter.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a parameter of an EDM operation. + /// + public interface IEdmOperationParameter : IEdmNamedElement, IEdmVocabularyAnnotatable + { + /// + /// Gets the type of this operation parameter. + /// + IEdmTypeReference Type { get; } + + /// + /// Gets the operation that declared this parameter. + /// + IEdmOperation DeclaringOperation { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationReturn.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationReturn.cs new file mode 100644 index 0000000..286c380 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOperationReturn.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a return of an EDM operation. + /// + public interface IEdmOperationReturn : IEdmElement, IEdmVocabularyAnnotatable + { + /// + /// Gets the type of this operation return. + /// + IEdmTypeReference Type { get; } + + /// + /// Gets the operation that declared this return. + /// + IEdmOperation DeclaringOperation { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOptionalParameter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOptionalParameter.cs new file mode 100644 index 0000000..4182583 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmOptionalParameter.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an optional parameter of an EDM operation. + /// + public interface IEdmOptionalParameter : IEdmOperationParameter + { + /// + /// Gets the default value of this property. + /// + string DefaultValueString { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathExpression.cs new file mode 100644 index 0000000..8cf021c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathExpression.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM path expression. + /// + public interface IEdmPathExpression : IEdmExpression + { + /// + /// Gets the path segments as a decomposed qualified name. "A.B/C/D.E" is { "A.B", "C", "D.E" }. + /// + IEnumerable PathSegments { get; } + + /// + /// Gets the path string, like "A.B/C/D.E". + /// + string Path { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathType.cs new file mode 100644 index 0000000..ddb414c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathType.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Enumerates the kinds of Edm Path type. + /// + public enum EdmPathTypeKind + { + /// + /// Represents a path type of unknown kind. + /// + None, + + /// + /// Represents a Edm.AnnotationPath type. + /// + AnnotationPath, + + /// + /// Represents a Edm.PropertyPath type. + /// + PropertyPath, + + /// + /// Represents a Edm.NavigationPropertyPath type. + /// + NavigationPropertyPath + } + + /// + /// Represents a definition of a Path type. + /// + public interface IEdmPathType : IEdmSchemaType + { + /// + /// Gets the path kind of this type. + /// + EdmPathTypeKind PathKind { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathTypeReference.cs new file mode 100644 index 0000000..341f092 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPathTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to Edm.*Path type. + /// + public interface IEdmPathTypeReference : IEdmTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveType.cs new file mode 100644 index 0000000..4a0bece --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveType.cs @@ -0,0 +1,200 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Enumerates the kinds of Edm Primitives. + /// + public enum EdmPrimitiveTypeKind + { + /// + /// Represents a primitive type of unknown kind. + /// + None = 0, + + /// + /// Represents a Binary type. + /// + Binary, + + /// + /// Represents a Boolean type. + /// + Boolean, + + /// + /// Represents a Byte type. + /// + Byte, + + /// + /// Represents a DateTimeOffset type. + /// + DateTimeOffset, + + /// + /// Represents a Decimal type. + /// + Decimal, + + /// + /// Represents a Double type. + /// + Double, + + /// + /// Represents a Guid type. + /// + Guid, + + /// + /// Represents a Int16 type. + /// + Int16, + + /// + /// Represents a Int32 type. + /// + Int32, + + /// + /// Represents a Int64 type. + /// + Int64, + + /// + /// Represents a SByte type. + /// + SByte, + + /// + /// Represents a Single type. + /// + Single, + + /// + /// Represents a String type. + /// + String, + + /// + /// Represents a Stream type. + /// + Stream, + + /// + /// Represents a Duration type. + /// + Duration, + + /// + /// Represents an arbitrary Geography type. + /// + Geography, + + /// + /// Represents a geography Point type. + /// + GeographyPoint, + + /// + /// Represents a geography LineString type. + /// + GeographyLineString, + + /// + /// Represents a geography Polygon type. + /// + GeographyPolygon, + + /// + /// Represents a geography GeographyCollection type. + /// + GeographyCollection, + + /// + /// Represents a geography MultiPolygon type. + /// + GeographyMultiPolygon, + + /// + /// Represents a geography MultiLineString type. + /// + GeographyMultiLineString, + + /// + /// Represents a geography MultiPoint type. + /// + GeographyMultiPoint, + + /// + /// Represents an arbitrary Geometry type. + /// + Geometry, + + /// + /// Represents a geometry Point type. + /// + GeometryPoint, + + /// + /// Represents a geometry LineString type. + /// + GeometryLineString, + + /// + /// Represents a geometry Polygon type. + /// + GeometryPolygon, + + /// + /// Represents a geometry GeometryCollection type. + /// + GeometryCollection, + + /// + /// Represents a geometry MultiPolygon type. + /// + GeometryMultiPolygon, + + /// + /// Represents a geometry MultiLineString type. + /// + GeometryMultiLineString, + + /// + /// Represents a geometry MultiPoint type. + /// + GeometryMultiPoint, + + /// + /// Represents a Date type + /// + Date, + + /// + /// Represents a TimeOfDay type + /// + TimeOfDay, + + /// + /// Represents a Primitive type + /// + PrimitiveType, + } + + /// + /// Represents a definition of an EDM primitive type. + /// + public interface IEdmPrimitiveType : IEdmSchemaType + { + /// + /// Gets the primitive kind of this type. + /// + EdmPrimitiveTypeKind PrimitiveKind { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveTypeReference.cs new file mode 100644 index 0000000..a3d5662 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPrimitiveTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to primitive types. + /// + public interface IEdmPrimitiveTypeReference : IEdmTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmProperty.cs new file mode 100644 index 0000000..d363289 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmProperty.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Defines EDM property types. + /// + public enum EdmPropertyKind + { + /// + /// Represents a property with an unknown or error kind. + /// + None = 0, + + /// + /// Represents a property implementing . + /// + Structural, + + /// + /// Represents a property implementing . + /// + Navigation, + } + + /// + /// Represents an EDM property. + /// + public interface IEdmProperty : IEdmNamedElement, IEdmVocabularyAnnotatable + { + /// + /// Gets the kind of this property. + /// + EdmPropertyKind PropertyKind { get; } + + /// + /// Gets the type of this property. + /// + IEdmTypeReference Type { get; } + + /// + /// Gets the type that this property belongs to. + /// + IEdmStructuredType DeclaringType { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReference.cs new file mode 100644 index 0000000..a80ba5b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReference.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an edmx:reference element. + /// + public interface IEdmReference : IEdmElement + { + /// + /// Uri + /// + Uri Uri { get; } + + /// + /// The Includes information. + /// + IEnumerable Includes { get; } + + /// + /// The IncludeAnnotations information. + /// + IEnumerable IncludeAnnotations { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReferentialConstraint.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReferentialConstraint.cs new file mode 100644 index 0000000..643cc2d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmReferentialConstraint.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + using System.Collections.Generic; + + /// + /// Represents an EDM referential constraint on a navigation property. + /// + public interface IEdmReferentialConstraint + { + /// + /// Gets the set of property pairs from the referential constraint. No particular ordering should be assumed. + /// + IEnumerable PropertyPairs { get; } + } + + /// + /// Represents a pair of properties as part of a referential constraint. + /// + public class EdmReferentialConstraintPropertyPair + { + /// + /// Initializes a new instance of . + /// + /// The local or dependent property in the referential constraint property pair. A null value is not allowed. + /// The foreign or principal property in the referential constraint property pair. A null value is not allowed. + /// Thrown if either of the properties given are null. + public EdmReferentialConstraintPropertyPair(IEdmStructuralProperty dependentProperty, IEdmStructuralProperty principalProperty) + { + EdmUtil.CheckArgumentNull(dependentProperty, "dependentProperty"); + EdmUtil.CheckArgumentNull(principalProperty, "principalProperty"); + + this.DependentProperty = dependentProperty; + this.PrincipalProperty = principalProperty; + } + + /// + /// The local or dependent property in the referential constraint property pair. Will never be null. + /// + public IEdmStructuralProperty DependentProperty { get; private set; } + + /// + /// The foreign or principal property in the referential constraint property pair. Will never be null. + /// + public IEdmStructuralProperty PrincipalProperty { get; private set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaElement.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaElement.cs new file mode 100644 index 0000000..71743e8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaElement.cs @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Defines EDM schema element types. + /// + public enum EdmSchemaElementKind + { + /// + /// Represents a schema element with unknown or error kind. + /// + None = 0, + + /// + /// Represents a schema element implementing . + /// + TypeDefinition, + + /// + /// Represents a schema element implementing . + /// + Term, + + /// + /// Represents a schema element implementing . + /// + Action, + + /// + /// Represents a schema element implementing + /// + EntityContainer, + + /// + /// Represents a schema element implementing . + /// + Function, + } + + /// + /// Common base interface for all named children of EDM schema. + /// + public interface IEdmSchemaElement : IEdmNamedElement, IEdmVocabularyAnnotatable + { + /// + /// Gets the kind of this schema element. + /// + EdmSchemaElementKind SchemaElementKind { get; } + + /// + /// Gets the namespace this schema element belongs to. + /// + string Namespace { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaType.cs new file mode 100644 index 0000000..f9a5cae --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSchemaType.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM type defined in an EDM schema. + /// + public interface IEdmSchemaType : IEdmSchemaElement, IEdmType + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSingleton.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSingleton.cs new file mode 100644 index 0000000..5661d90 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSingleton.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM singleton. + /// + public interface IEdmSingleton : IEdmEntityContainerElement, IEdmNavigationSource + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSpatialTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSpatialTypeReference.cs new file mode 100644 index 0000000..59a938b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmSpatialTypeReference.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM spatial type. + /// + public interface IEdmSpatialTypeReference : IEdmPrimitiveTypeReference + { + /// + /// Gets the Spatial Reference Identifier of this spatial type. + /// + int? SpatialReferenceIdentifier { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStringTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStringTypeReference.cs new file mode 100644 index 0000000..0a31f8c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStringTypeReference.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM string type. + /// + public interface IEdmStringTypeReference : IEdmPrimitiveTypeReference + { + /// + /// Gets a value indicating whether this string type specifies the maximum allowed length. + /// + bool IsUnbounded { get; } + + /// + /// Gets the maximum length of this string type. + /// + int? MaxLength { get; } + + /// + /// Gets a value indicating whether this string type supports unicode encoding. + /// + bool? IsUnicode { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuralProperty.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuralProperty.cs new file mode 100644 index 0000000..1bf24c1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuralProperty.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM structural (i.e. non-navigation) property. + /// + public interface IEdmStructuralProperty : IEdmProperty + { + /// + /// Gets the default value of this property. + /// + string DefaultValueString { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredType.cs new file mode 100644 index 0000000..d0d3a65 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredType.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Common base interface for definitions of EDM structured types. + /// + public interface IEdmStructuredType : IEdmType + { + /// + /// Gets a value indicating whether this type is abstract. + /// + bool IsAbstract { get; } + + /// + /// Gets a value indicating whether this type is open. + /// + bool IsOpen { get; } + + /// + /// Gets the base type of this type. + /// + IEdmStructuredType BaseType { get; } + + /// + /// Gets the properties declared immediately within this type. + /// + IEnumerable DeclaredProperties { get; } + + /// + /// Searches for a structural or navigation property with the given name in this type and all base types and returns null if no such property exists. + /// + /// The name of the property being found. + /// The requested property, or null if no such property exists. + IEdmProperty FindProperty(string name); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredTypeReference.cs new file mode 100644 index 0000000..0ab5c49 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmStructuredTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to EDM structured types. + /// + public interface IEdmStructuredTypeReference : IEdmTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTemporalTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTemporalTypeReference.cs new file mode 100644 index 0000000..b0d7774 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTemporalTypeReference.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a reference to an EDM temporal (Duration, DateTime, DateTimeOffset) type. + /// + public interface IEdmTemporalTypeReference : IEdmPrimitiveTypeReference + { + /// + /// Gets the precision of this temporal type. + /// + int? Precision { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmType.cs new file mode 100644 index 0000000..37a8230 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmType.cs @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Defines EDM metatypes. + /// + public enum EdmTypeKind + { + /// + /// Represents a type with an unknown or error kind. + /// + None, + + /// + /// Represents a type implementing . + /// + Primitive, + + /// + /// Represents a type implementing . + /// + Entity, + + /// + /// Represents a type implementing . + /// + Complex, + + /// + /// Represents a type implementing . + /// + Collection, + + /// + /// Represents a type implementing . + /// + EntityReference, + + /// + /// Represents a type implementing . + /// + Enum, + + /// + /// Represents a type implementing . + /// + TypeDefinition, + + /// + /// Represents a type implementing . + /// + Untyped, + + /// + /// Represetns a type implementing . + /// + Path, + } + + /// + /// Represents the definition of an EDM type. + /// + public interface IEdmType : IEdmElement + { + /// + /// Gets the kind of this type. + /// + EdmTypeKind TypeKind { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinition.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinition.cs new file mode 100644 index 0000000..14bd50b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinition.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM type definition. + /// + public interface IEdmTypeDefinition : IEdmSchemaType + { + /// + /// Gets the underlying type of this type definition. + /// + IEdmPrimitiveType UnderlyingType { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinitionReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinitionReference.cs new file mode 100644 index 0000000..d3189bc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeDefinitionReference.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to EDM type definitions. + /// + public interface IEdmTypeDefinitionReference : IEdmTypeReference + { + /// + /// Gets a value indicating whether the length of the underlying type + /// is unbounded where the maximum length depends on the underlying + /// type itself and is invalid. + /// This facet ONLY applies when the underlying type is Edm.Binary, + /// Edm.Stream or Edm.String. + /// + bool IsUnbounded { get; } + + /// + /// Gets the maximum length of the underlying type. This value is only + /// effective when is false. + /// This facet ONLY applies when the underlying type is Edm.Binary, + /// Edm.Stream or Edm.String. + /// + int? MaxLength { get; } + + /// + /// Gets a value indicating whether the underlying type supports unicode encoding. + /// This facet ONLY applies when the underlying type is Edm.String. + /// + bool? IsUnicode { get; } + + /// + /// Gets the precision of the underlying type. + /// This facet ONLY applies when the underlying type is Edm.DateTimeOffset, + /// Edm.Decimal, Edm.Duration or Edm.TimeOfDay. + /// + int? Precision { get; } + + /// + /// Gets the scale of the underlying type. + /// This facet ONLY applies when the underlying type is Edm.Decimal. + /// + int? Scale { get; } + + /// + /// Gets the Spatial Reference Identifier of the underlying type. + /// This facet ONLY applies when the underlying type is a spatial type. + /// + int? SpatialReferenceIdentifier { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeReference.cs new file mode 100644 index 0000000..0f7fcba --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTypeReference.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a references to a type. + /// + public interface IEdmTypeReference : IEdmElement + { + /// + /// Gets a value indicating whether this type is nullable. + /// + bool IsNullable { get; } + + /// + /// Gets the definition to which this type refers. + /// + IEdmType Definition { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUnknownEntitySet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUnknownEntitySet.cs new file mode 100644 index 0000000..cfee2f6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUnknownEntitySet.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents an EDM unknown entity set. + /// Unknown entity set can appear in following scenarios: + /// 1. The target of navigation property is contained in other entity. + /// 2. The target of navigation property comes from more than one entity set. + /// 3. Other scenarios that the entity set is unknown. + /// + public interface IEdmUnknownEntitySet : IEdmEntitySetBase + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedType.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedType.cs new file mode 100644 index 0000000..8c7d717 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedType.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM Untyped type. + /// + public interface IEdmUntypedType : IEdmSchemaType + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedTypeReference.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedTypeReference.cs new file mode 100644 index 0000000..f4e94ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmUntypedTypeReference.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents references to Edm.Untyped type. + /// + public interface IEdmUntypedTypeReference : IEdmTypeReference + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/TimeOfDay.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/TimeOfDay.cs new file mode 100644 index 0000000..ddefa11 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Schema/TimeOfDay.cs @@ -0,0 +1,410 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Globalization; + +namespace Microsoft.OData.Edm +{ + /// + /// TimeOfDay type for Edm.TimeOfDay + /// + public struct TimeOfDay : IComparable, IComparable, IEquatable + { + /// + /// Max value of ticks + /// + public const long MaxTickValue = 863999999999; + + /// + /// Min value of ticks + /// + public const long MinTickValue = 0; + + /// + /// Represents the number of ticks in 1 hour. This field is constant. + /// + public const long TicksPerHour = 36000000000; + + /// + /// Represents the number of ticks in 1 minute. This field is constant. + /// + public const long TicksPerMinute = 600000000; + + /// + /// Represents the number of ticks in 1 second. + /// + public const long TicksPerSecond = 10000000; + + /// + /// Min value of + /// + public static readonly TimeOfDay MinValue = new TimeOfDay(MinTickValue); + + /// + /// Max value of + /// + public static readonly TimeOfDay MaxValue = new TimeOfDay(MaxTickValue); + + /// + /// Internal using System.TimeSpan + /// + private TimeSpan timeSpan; + + /// + /// Consturctor of + /// + /// Hour value of TimeOfDay + /// Minute value of TimeOfDay + /// Second value of TimeOfDay + /// Millisecond value of TimeOfDay, whose precision will be millisecond (3 digits). + public TimeOfDay(int hour, int minute, int second, int millisecond) + : this() + { + if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 || millisecond < 0 || millisecond > 999) + { + throw new FormatException(Strings.TimeOfDay_InvalidTimeOfDayParameters(hour, minute, second, millisecond)); + } + else + { + timeSpan = new TimeSpan(0, hour, minute, second, millisecond); + } + } + + /// + /// Consturctor of + /// + /// ticks value of TimeOfDay + public TimeOfDay(long ticks) + : this() + { + if (ticks < MinTickValue || ticks > MaxTickValue) + { + throw new FormatException(Strings.TimeOfDay_TicksOutOfRange(ticks)); + } + else + { + timeSpan = new TimeSpan(ticks); + } + } + + /// + /// Gets a object that is set to current time on this computer, expressed as the local time. + /// + public static TimeOfDay Now + { + get { return DateTime.Now.TimeOfDay; } + } + + /// + /// Gets the hour component of the TimeOfDay represented by this instance. + /// + public int Hours + { + get + { + return timeSpan.Hours; + } + } + + /// + /// Gets the minute component of the TimeOfDay represented by this instance. + /// + public int Minutes + { + get + { + return timeSpan.Minutes; + } + } + + /// + /// Gets the second component of the TimeOfDay represented by this instance. + /// + public int Seconds + { + get + { + return timeSpan.Seconds; + } + } + + /// + /// Gets the millisecond component of the TimeOfDay represented by this instance. + /// + public long Milliseconds + { + get + { + return timeSpan.Milliseconds; + } + } + + /// + /// Gets the number of ticks that represent the value of the current TimeOfDay structure + /// + public long Ticks + { + get + { + return timeSpan.Ticks; + } + } + + /// + /// Indicates whether two instances are not equal. + /// + /// The first time interval to compare. + /// The second time interval to compare. + /// true if the values of firstOperand and secondOperand are not equal; otherwise, false. + public static bool operator !=(TimeOfDay firstOperand, TimeOfDay secondOperand) + { + return firstOperand.timeSpan != secondOperand.timeSpan; + } + + /// + /// Indicates whether two instances are equal. + /// + /// The first time interval to compare. + /// The second time interval to compare. + /// true if the values of firstOperand and secondOperand are equal; otherwise, false. + public static bool operator ==(TimeOfDay firstOperand, TimeOfDay secondOperand) + { + return firstOperand.timeSpan == secondOperand.timeSpan; + } + + /// + /// Indicates whether a specified is greater equal to + /// another specified . + /// + /// The first time interval to compare. + /// The second time interval to compare. + /// true if the value of firstOperand is greater equal to the value of secondOperand; otherwise, false. + public static bool operator >=(TimeOfDay firstOperand, TimeOfDay secondOperand) + { + return firstOperand.timeSpan >= secondOperand.timeSpan; + } + + /// + /// Indicates whether a specified is greater than + /// another specified . + /// + /// The first time interval to compare. + /// The second time interval to compare. + /// true if the value of firstOperand is greater than the value of secondOperand; otherwise, false. + public static bool operator >(TimeOfDay firstOperand, TimeOfDay secondOperand) + { + return firstOperand.timeSpan > secondOperand.timeSpan; + } + + /// + /// Indicates whether a specified is less equal to + /// another specified . + /// + /// The first time interval to compare. + /// The second time interval to compare. + /// true if the value of firstOperand is less equal to the value of secondOperand; otherwise, false. + public static bool operator <=(TimeOfDay firstOperand, TimeOfDay secondOperand) + { + return firstOperand.timeSpan <= secondOperand.timeSpan; + } + + /// + /// Indicates whether a specified is less than + /// another specified . + /// + /// The first time interval to compare. + /// The second time interval to compare. + /// true if the value of firstOperand is less than the value of secondOperand; otherwise, false. + public static bool operator <(TimeOfDay firstOperand, TimeOfDay secondOperand) + { + return firstOperand.timeSpan < secondOperand.timeSpan; + } + + /// + /// Convert TimeOfDay to .Net Clr TimeSpan + /// + /// TimeOfDay Value + /// TimeSpan Value which represent the TimeOfDay + public static implicit operator TimeSpan(TimeOfDay time) + { + return time.timeSpan; + } + + /// + /// Convert .Net Clr TimeSpan to TimeOfDay + /// + /// TimeSpan Value + /// TimeOfDay Value from TimeSpan + public static implicit operator TimeOfDay(TimeSpan timeSpan) + { + if (timeSpan.Days != 0 || timeSpan.Hours < 0 + || timeSpan.Minutes < 0 || timeSpan.Milliseconds < 0 + || timeSpan.Ticks < MinTickValue || timeSpan.Ticks > MaxTickValue) + { + throw new FormatException(Strings.TimeOfDay_ConvertErrorFromTimeSpan(timeSpan)); + } + else + { + return new TimeOfDay(timeSpan.Ticks); + } + } + + /// + /// Compares the value of this instance to a specified TimeOfDay value + /// and returns an bool that indicates whether this instance is same as the specified TimeOfDay value. + /// + /// The object to compare to the current instance + /// True for equal, false for non-equal. + public bool Equals(TimeOfDay other) + { + return timeSpan.Equals(other.timeSpan); + } + + /// + /// Compares the value of this instance to a specified object value + /// and returns an bool that indicates whether the specified object is + /// and this instance is same as the specified value. + /// + /// The object to compare to the current instance + /// True for equal, false for non-equal. + public override bool Equals(object obj) + { + if (obj == null || !(obj is TimeOfDay)) + { + return false; + } + + return this.timeSpan.Equals(((TimeOfDay)obj).timeSpan); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return timeSpan.GetHashCode(); + } + + /// + /// Convert TimeOfDay to String. The precision will 100ns (7 digits). + /// + /// string value of timeofday + public override string ToString() + { +#if ORCAS + return this.timeSpan.ToString(); +#else + return this.timeSpan.ToString(@"hh\:mm\:ss\.fffffff", CultureInfo.InvariantCulture); +#endif + } + + /// + /// Compares the value of this instance to a object value + /// and returns an integer that indicates whether this instance is earlier than, + /// the same as, or later than the object if it is a TimeOfDay. + /// + /// The object to compare to the current instance + /// Value Description Less than zero This instance is earlier than value. + /// Zero This instance is the same as value. + /// Greater than zero This instance is later than value. + public int CompareTo(object obj) + { + if (obj is TimeOfDay) + { + TimeOfDay time2 = (TimeOfDay)obj; + return this.CompareTo(time2); + } + else + { + throw new ArgumentException(Strings.TimeOfDay_InvalidCompareToTarget(obj)); + } + } + + /// + /// Compares the value of this instance to a specified TimeOfDay value + /// and returns an integer that indicates whether this instance is earlier than, + /// the same as, or later than the specified TimeOfDay value. + /// + /// The object to compare to the current instance + /// Value Description Less than zero This instance is earlier than value. + /// Zero This instance is the same as value. + /// Greater than zero This instance is later than value. + public int CompareTo(TimeOfDay other) + { + return this.timeSpan.CompareTo(other.timeSpan); + } + + /// + /// Converts a specified string representation of a date to with CurrentCulture format. + /// + /// A string that represent a timeofday to convert. + /// The instance represented by text + public static TimeOfDay Parse(string text) + { + return Parse(text, CultureInfo.CurrentCulture); + } + + /// + /// Converts a specified string representation of a date to . + /// + /// A string that represent a timeofday to convert. + /// An object that supplies culture-specific formatting information about text. + /// The instance represented by text + public static TimeOfDay Parse(string text, IFormatProvider provider) + { + TimeOfDay result; + if (TimeOfDay.TryParse(text, provider, out result)) + { + return result; + } + else + { + throw new FormatException(Strings.TimeOfDay_InvalidParsingString(text)); + } + } + + /// + /// Try converts a specified string representation of a timeofday to with CurrentCulture. + /// + /// A string that represent a timeofday to convert. + /// A object equivalent to the date input, if the conversion succeeded + /// or , if the conversion failed. + /// True if the input parameter is successfully converted; otherwise, false. + public static bool TryParse(string text, out TimeOfDay result) + { + return TryParse(text, CultureInfo.CurrentCulture, out result); + } + + /// + /// Try converts a specified string representation of a timeofday to . + /// + /// A string that represent a timeofday to convert. + /// >An object that supplies culture-specific formatting information about text. + /// A object equivalent to the date input, if the conversion succeeded + /// or , if the conversion failed. + /// True if the input parameter is successfully converted; otherwise, false. + public static bool TryParse(string text, IFormatProvider provider, out TimeOfDay result) + { + TimeSpan time; + bool b; +#if ORCAS + b = TimeSpan.TryParse(text, out time); +#else + b = TimeSpan.TryParse(text, provider, out time); +#endif + if (b && time.Ticks >= MinTickValue && time.Ticks <= MaxTickValue) + { + result = new TimeOfDay(time.Ticks); + return true; + } + + result = TimeOfDay.MinValue; + return false; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ShippingAssemblyAttributes.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ShippingAssemblyAttributes.cs new file mode 100644 index 0000000..28bfcc1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/ShippingAssemblyAttributes.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +// PublicKey=MicrosoftPublicKeyFull +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Tests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Edm.Tests" + AssemblyRef.TestPublicKey)] +#pragma warning disable 436 +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("EdmLibTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("EdmLibTests.SL" + AssemblyRef.TestPublicKey)] +#pragma warning restore 436 + +// .NET Core +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.TestCommon.NetCore" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Edm.Tests.NetCore" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Core.Tests.NetCore" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Spatial.Tests.NetCore" + AssemblyRef.TestPublicKey)] \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/TupleInternal.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/TupleInternal.cs new file mode 100644 index 0000000..da1932e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/TupleInternal.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + internal static class TupleInternal + { + public static TupleInternal Create(T1 item1, T2 item2) + { + return new TupleInternal(item1, item2); + } + } + + internal class TupleInternal + { + public TupleInternal(T1 item1, T2 item2) + { + this.Item1 = item1; + this.Item2 = item2; + } + + public T1 Item1 + { + get; private set; + } + + public T2 Item2 + { + get; private set; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/DuplicateOperationValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/DuplicateOperationValidator.cs new file mode 100644 index 0000000..f5ead80 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/DuplicateOperationValidator.cs @@ -0,0 +1,204 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Validation +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text; + + internal class DuplicateOperationValidator + { + private readonly HashSetInternal functionsParameterNameHash = new HashSetInternal(); + private readonly HashSetInternal functionsParameterTypeHash = new HashSetInternal(); + private readonly HashSetInternal actionsNameHash = new HashSetInternal(); + + private readonly ValidationContext context; + + internal DuplicateOperationValidator(ValidationContext context) + { + this.context = context; + } + + public static bool IsDuplicateOperation(IEdmOperation operation, IEnumerable candidateDuplicateOperations) + { + var validator = new DuplicateOperationValidator(null); + foreach (var candidateOperation in candidateDuplicateOperations) + { + validator.ValidateNotDuplicate(candidateOperation, true /*skipError*/); + } + + return validator.ValidateNotDuplicate(operation, true /*skipError*/); + } + + public bool ValidateNotDuplicate(IEdmOperation operation, bool skipError) + { + bool duplicate = false; + string fullName = operation.FullName(); + var function = operation as IEdmFunction; + if (function != null) + { + var uniqueFunctionParameterName = BuildInternalUniqueParameterNameFunctionString(function); + if (functionsParameterNameHash.Contains(uniqueFunctionParameterName)) + { + duplicate = true; + if (!skipError) + { + this.context.AddError( + function.Location(), + EdmErrorCode.DuplicateFunctions, + function.IsBound ? Strings.EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterNames(fullName) : Strings.EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterNames(fullName)); + } + } + else + { + functionsParameterNameHash.Add(uniqueFunctionParameterName); + } + + var uniqueFunctionParameterType = BuildInternalUniqueParameterTypeFunctionString(function); + if (functionsParameterTypeHash.Contains(uniqueFunctionParameterType)) + { + duplicate = true; + if (!skipError) + { + this.context.AddError( + function.Location(), + EdmErrorCode.DuplicateFunctions, + function.IsBound ? Strings.EdmModel_Validator_Semantic_ModelDuplicateBoundFunctionParameterTypes(fullName) : Strings.EdmModel_Validator_Semantic_ModelDuplicateUnBoundFunctionsParameterTypes(fullName)); + } + } + else + { + functionsParameterTypeHash.Add(uniqueFunctionParameterType); + } + } + else + { + var action = operation as IEdmAction; + Debug.Assert(action != null, "action should not be null"); + var uniqueActionName = BuildInternalUniqueActionString(action); + if (actionsNameHash.Contains(uniqueActionName)) + { + duplicate = true; + if (!skipError) + { + this.context.AddError( + action.Location(), + EdmErrorCode.DuplicateActions, + action.IsBound ? Strings.EdmModel_Validator_Semantic_ModelDuplicateBoundActions(fullName) : Strings.EdmModel_Validator_Semantic_ModelDuplicateUnBoundActions(fullName)); + } + } + else + { + actionsNameHash.Add(uniqueActionName); + } + } + + return duplicate; + } + + /// + /// Creates a unique function name based on the type. Used to find duplicates of functions. + /// - The combination of function name, binding parameter type, and unordered set of non-binding parameter names MUST be unique within a namespace. + /// - An unbound function MAY have the same name as a bound function. (Note this is why IsBound is added into the string) + /// + /// function to create the hash for. + /// A unique string that identifies a function. + private static string BuildInternalUniqueParameterNameFunctionString(IEdmFunction function) + { + StringBuilder builder = new StringBuilder(); + builder.Append(function.IsBound); + builder.Append("-"); + builder.Append(function.Namespace); + builder.Append("-"); + builder.Append(function.Name); + builder.Append("-"); + if (!function.Parameters.Any()) + { + return builder.ToString(); + } + + if (function.IsBound) + { + IEdmOperationParameter bindingParameter = function.Parameters.FirstOrDefault(); + builder.Append(bindingParameter.Type.FullName()); + builder.Append("-"); + foreach (IEdmOperationParameter parameter in function.Parameters.Skip(1).OrderBy(p => p.Name)) + { + builder.Append(parameter.Name); + builder.Append("-"); + } + } + else + { + foreach (IEdmOperationParameter parameter in function.Parameters.OrderBy(p => p.Name)) + { + builder.Append(parameter.Name); + builder.Append("-"); + } + } + + return builder.ToString(); + } + + /// + /// Creates a unique function name based on the type. Used to validate duplicates of functions. Rules this is validating + /// - The combination of function name, binding parameter type, and ordered set of parameter types MUST be unique within a namespace. + /// - An unbound function MAY have the same name as a bound function. (Note this is why IsBound is added into the string) + /// + /// function to use to create the hash. + /// A unique string that identifies a function. + private static string BuildInternalUniqueParameterTypeFunctionString(IEdmFunction function) + { + StringBuilder builder = new StringBuilder(); + builder.Append(function.IsBound); + builder.Append("-"); + builder.Append(function.Namespace); + builder.Append("-"); + builder.Append(function.Name); + builder.Append("-"); + + foreach (IEdmOperationParameter parameter in function.Parameters) + { + builder.Append(parameter.Type.FullName()); + builder.Append("-"); + } + + return builder.ToString(); + } + + /// + /// Creates a unique function name based on the type. Used to find duplicates of functions. + /// - Bound actions support overloading (multiple actions having the same name within the same namespace) by binding parameter type. The combination of action name and the binding parameter type MUST be unique within a namespace. + /// - Unbound actions do not support overloads. The names of all unbound actions MUST be unique within a namespace. An unbound action MAY have the same name as a bound action. + /// + /// action to build the hash from. + /// A unique string that identifies a function. + private static string BuildInternalUniqueActionString(IEdmAction action) + { + StringBuilder builder = new StringBuilder(); + builder.Append(action.IsBound); + builder.Append("-"); + builder.Append(action.Namespace); + builder.Append("-"); + builder.Append(action.Name); + builder.Append("-"); + if (!action.Parameters.Any()) + { + return builder.ToString(); + } + + if (action.IsBound) + { + IEdmOperationParameter bindingParameter = action.Parameters.FirstOrDefault(); + builder.Append(bindingParameter.Type.FullName()); + } + + return builder.ToString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmError.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmError.cs new file mode 100644 index 0000000..011bcbb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmError.cs @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Globalization; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// Represents a reportable error in EDM + /// + public class EdmError + { + /// + /// Initializes a new instance of the EdmError class. + /// + /// The location where the error occurred. + /// An integer code representing the error. + /// A human readable message describing the error. + public EdmError(EdmLocation errorLocation, EdmErrorCode errorCode, string errorMessage) + { + this.ErrorLocation = errorLocation; + this.ErrorCode = errorCode; + this.ErrorMessage = errorMessage; + } + + /// + /// Gets the location of the error in the file in which it occurred. + /// + public EdmLocation ErrorLocation { get; private set; } + + /// + /// Gets an integer code representing the error. + /// + public EdmErrorCode ErrorCode { get; private set; } + + /// + /// Gets a human readable string describing the error. + /// + public string ErrorMessage { get; private set; } + + /// + /// Gets a string representation of the error. + /// + /// A string representation of the error. + public override string ToString() + { + if (this.ErrorLocation != null && !(this.ErrorLocation is ObjectLocation)) + { + return Convert.ToString(this.ErrorCode, CultureInfo.InvariantCulture) + " : " + this.ErrorMessage + " : " + this.ErrorLocation.ToString(); + } + + return Convert.ToString(this.ErrorCode, CultureInfo.InvariantCulture) + " : " + this.ErrorMessage; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs new file mode 100644 index 0000000..7cf2284 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs @@ -0,0 +1,1391 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// EdmLib validation error codes + /// + public enum EdmErrorCode + { + /// + /// Invalid error code + /// + InvalidErrorCodeValue = 0, + + // IOException = 4, + + /// + /// An exception was thrown by the underlying xml reader. + /// + XmlError = 5, + + // TooManyErrors = 6, + // MalformedXml = 7, + + /// + /// Encountered an XML node that was never used + /// + UnexpectedXmlNodeType = 8, + + /// + /// Encountered an XML attribute that was never used + /// + UnexpectedXmlAttribute = 9, + + /// + /// Encountered an XML element that was never used + /// + UnexpectedXmlElement = 10, + + /// + /// Text was found in a location it was not allowed in + /// + TextNotAllowed = 11, + + /// + /// An empty file was provided to the parser + /// + EmptyFile = 12, + + // XsdError = 13, + // InvalidAlias = 14, + + /// + /// An XML element was missing a required attribute + /// + MissingAttribute = 15, + + // IntegerExpected = 16, + + /// + /// Invalid Name + /// + InvalidName = 17, + + /// + /// An XML attribute or element representing EDM type is missing. + /// + MissingType = 18, + + /// + /// Element name is already defined in this context. + /// + AlreadyDefined = 19, + + // ElementNotInSchema = 20, + // unused 21, + // InvalidBaseType = 22, + // NoConcreteDescendants = 23, + // 24, + + /// + /// The specified version number is not valid. + /// + InvalidVersionNumber = 25, + + // InvalidSize = 26, + + /// + /// Malformed boolean value. + /// + InvalidBoolean = 27, + + // unused 28, + // BadType = 29, + // unused 30, + // unused 31, + // InvalidVersioningClass = 32, + // InvalidVersionIntroduced = 33, + // BadNamespace = 34, + // unused 35, + // unused 36, + // unused 37, + // UnresolvedReferenceSchema = 38, + // unused 39, + // NotInNamespace = 40, + // NotUnnestedType = 41, + + /// + /// The property contains an error. + /// + BadProperty = 42, + + // UndefinedProperty = 43, + + /// + /// The type of this property is invalid for the given context. + /// + InvalidPropertyType = 44, + + // InvalidAsNestedType = 45, + // InvalidChangeUnit = 46, + // UnauthorizedAccessException = 47, + // unused 48, + // unused 49, + // unused 50, + + /// + /// Precision out of range + /// + PrecisionOutOfRange = 51, + + /// + /// Scale out of range + /// + ScaleOutOfRange = 52, + + // DefaultNotAllowed = 53, + // InvalidDefault = 54, + //// One of the required facets is missing + // RequiredFacetMissing = 55, + // BadImageFormatException = 56, + // MissingSchemaXml = 57, + // BadPrecisionAndScale = 58, + // InvalidChangeUnitUsage = 59, + + /// + /// Name is too long. + /// + NameTooLong = 60, + + // CircularlyDefinedType = 61, + + /// + /// The provided association is invalid + /// + InvalidAssociation = 62, + + //// + //// The facet isn't allow by the property type. + //// + // FacetNotAllowedByType = 63, + //// + //// This facet value is constant and is specified in the schema + //// + // ConstantFacetSpecifiedInSchema = 64, + // unused 65, + // unused 66, + // unused 67, + // unused 68, + // unused 69, + // unused 70, + // unused 71, + // unused 72, + // unused 73, + + /// + /// Navigation property contains errors. + /// + BadNavigationProperty = 74, + + /// + /// Entity key is invalid. + /// + InvalidKey = 75, + + /// + /// The value of the property must not be null. + /// + InterfaceCriticalPropertyValueMustNotBeNull = 76, + + /// + /// An object with an interface kind property does not implement the interface corresponding to the value of that property. + /// For example this error will be reported for an object that implements interface with kind property reporting .Entity, + /// but does not implement interface. + /// + InterfaceCriticalKindValueMismatch = 77, + + /// + /// The value of an interface kind property is not semantically valid. A semantically valid model must not contain elements of kind 'None'. + /// + InterfaceCriticalKindValueUnexpected = 78, + + /// + /// An enumeration property must not contain null elements. + /// + InterfaceCriticalEnumerableMustNotHaveNullElements = 79, + + /// + /// The value of the enum type property is out of range. + /// + InterfaceCriticalEnumPropertyValueOutOfRange = 80, + + /// + /// If property P1 is a navigation property and P2 is its parnter, then partner property of P2 must be P1. + /// + InterfaceCriticalNavigationPartnerInvalid = 81, + + /// + /// A chain of base types is cyclic. + /// + InterfaceCriticalCycleInTypeHierarchy = 82, + + // reserved for critical structural errors 83, + // reserved for critical structural errors 84, + // reserved for critical structural errors 85, + // reserved for critical structural errors 86, + // reserved for critical structural errors 87, + // reserved for critical structural errors 88, + // reserved for critical structural errors 89, + // reserved for critical structural errors 90, + // reserved for critical structural errors 91, + + /// + /// Multiplicity value was malformed + /// + InvalidMultiplicity = 92, + + // unused = 93 + // unused = 94 + // unused = 95 + + /// + /// The value for the Action attribute is invalid or not allowed in the current context + /// + InvalidAction = 96, + + /// + /// An error occured processing the OnDelete element + /// + InvalidOnDelete = 97, + + /// + /// No complex type with that name exists. + /// + BadUnresolvedComplexType = 98, + + //// Ends were given for the Property element of a EntityContainer that is not a RelationshipSet + // InvalidContainerTypeForEnd = 99, + + /// + /// The extent name used in the EntittyContainerType End does not match the name of any of the EntityContainerProperties in the containing EntityContainer + /// + InvalidEndEntitySet = 100, + + //// An end element was not given, and cannot be inferred because too many EntityContainerEntitySet elements that are good possibilities. + // AmbiguousEntityContainerEnd = 101, + //// An end element was not given, and cannot be infered because there is no EntityContainerEntitySets that are the correct type to be used as an EntitySet. + // MissingExtentEntityContainerEnd = 102, + + /// + /// Operation import specifies an entity set expression which is not supported in this context. Operation import entity set expression can be either an entity set reference or a path starting with a operation import parameter and traversing navigation properties. + /// + OperationImportEntitySetExpressionIsInvalid = 103, + + //// unused 104, + //// unused 105, + //// Not a valid parameter direction for the parameter in a function + // BadParameterDirection = 106, + //// Unable to infer an optional schema part, to resolve this, be more explicit + // FailedInference = 107, + //// unused = 108, + + /// + /// The target entity set must be able to hold an entity that is valid for the navigation property of a mapping. + /// + NavigationPropertyMappingMustPointToValidTargetForProperty = 109, + + /// + /// Invalid role value in the relationship constraint + /// + InvalidRoleInRelationshipConstraint = 110, + + /// + /// Invalid Property in relationship constraint + /// + InvalidPropertyInRelationshipConstraint = 111, + + /// + /// Type mismatch between ToProperty and FromProperty in the relationship constraint + /// + TypeMismatchRelationshipConstraint = 112, + + /// + /// Invalid multiplicty of the principal end of a navigation. + /// + InvalidMultiplicityOfPrincipalEnd = 113, + + /// + /// The number of properties in the FromProperty and ToProperty in the relationship constraint must be identical + /// + MismatchNumberOfPropertiesInRelationshipConstraint = 114, + + //// No Properties defined in either FromProperty or ToProperty in the relationship constraint + // MissingPropertyInRelationshipConstraint = 115, + + /// + /// Invalid multiplicty of the dependent end of a navigation. + /// + InvalidMultiplicityOfDependentEnd = 116, + + /// + /// Open types are supported only in version 1.2 and after version 2.0. Only entity types can be open. + /// + OpenTypeNotSupported = 117, + + /// + /// Same role referred in the ToRole and FromRole of a referential constraint + /// + SameRoleReferredInReferentialConstraint = 119, + + //// Invalid value for attribute ParameterTypeSemantics + // InvalidValueForParameterTypeSemantics = 120, + //// Invalid type used for a Relationship End Type + // InvalidRelationshipEndType = 121, + //// Invalid PrimitiveTypeKind + // InvalidPrimitiveTypeKind = 122, + // unused 123, + //// Invalid TypeConversion DestinationType + // InvalidTypeConversionDestinationType = 124, + //// Expected a integer value between 0 - 255 + // ByteValueExpected = 125, + //// Invalid Type specified in function + // FunctionWithNonScalarTypeNotSupported = 126, + //// Precision must not be greater than 28 + // PrecisionMoreThanAllowedMax = 127, + + /// + /// Properties that are part of entity key must be of scalar type + /// + EntityKeyMustBeScalar = 128, + + /// + /// Binary type properties which are part of entity key are currently supported before V2.0 + /// + EntityKeyMustNotBeBinary = 129, + + //// The primitive type kind does not have a prefered mapping + // NoPreferredMappingForPrimitiveTypeKind = 130, + //// More than one PreferredMapping for a PrimitiveTypeKind + // TooManyPreferredMappingsForPrimitiveTypeKind = 131, + + /// + /// End with * multiplicity cannot have operations specified + /// + EndWithManyMultiplicityCannotHaveOperationsSpecified = 132, + + /// + /// Navigation source type has no keys + /// + NavigationSourceTypeHasNoKeys = 133, + + //// Invalid Number Of Parameters For Aggregate Function + // InvalidNumberOfParametersForAggregateFunction = 134, + //// Invalid Parameter Type For Aggregate Function + // InvalidParameterTypeForAggregateFunction = 135, + //// Composable functions must declare a return type. + // ComposableFunctionWithoutReturnType = 136, + //// Non-composable functions must not declare a return type. + // NonComposableFunctionWithReturnType = 137, + //// Non-composable functions do not permit the aggregate, niladic, or built-in attributes. + // NonComposableFunctionAttributesNotValid = 138, + //// Composable functions can not include command text attribute. + // ComposableFunctionWithCommandText = 139, + //// Functions should not declare both a store name and command text (only one or the other + //// can be used). + // FunctionDeclaresCommandTextAndStoreFunctionName = 140, + //// System Namespace + // SystemNamespace = 141, + //// Empty DefiningQuery text + // EmptyDefiningQuery = 142, + //// Schema, Table and DefiningQuery are all specified, and are mutualy exlusive + // TableAndSchemaAreMutuallyExclusiveWithDefiningQuery = 143, + + /// + /// Conurency can't change for any sub types of an EntitySet type. + /// + ConcurrencyRedefinedOnSubtypeOfEntitySetType = 145, + + /// + /// In version 1.0 operation import can have no return type or return a collection of scalars or a collection of entities. + /// In all other versions operation import can have no return type or return a scalar, a complex type, an entity type or a collection of those. + /// + OperationImportUnsupportedReturnType = 146, + + /// + /// Operation import specifies entity type return but no entity set. + /// + OperationImportReturnsEntitiesButDoesNotSpecifyEntitySet = 148, + + /// + /// Operation import specifies entity type that does not derive from element type of entity set. + /// + OperationImportEntityTypeDoesNotMatchEntitySet = 149, + + /// + /// Operation import specifies a binding to an entity set but does not return entities. + /// + OperationImportSpecifiesEntitySetButDoesNotReturnEntityType = 150, + + /// + /// Operation import cannot import a bound function. + /// + OperationImportCannotImportBoundOperation = 151, + + /// + /// A function must have return type. + /// + FunctionMustHaveReturnType = 152, + + /// + /// Same Entity Set Taking part in the same role of the relationship set in two different relationship sets + /// + SimilarRelationshipEnd = 153, + + /// + /// Entity key refers to the same property twice + /// + DuplicatePropertySpecifiedInEntityKey = 154, + + /// + /// A UrlEscape function must have be bound function. + /// + UrlEscapeFunctionMustBeBoundFunction = 155, + + /// + /// A UrlEscape function must have and only one 'Edm.String' parameter. + /// + UrlEscapeFunctionMustHaveOnlyOneEdmStringParameter = 156, + + //// Function declares a ReturnType attribute and element + // AmbiguousFunctionReturnType = 156, + + /// + /// Nullable complex Type not supported in version 1.0 and 2.0. + /// + NullableComplexTypeProperty = 157, + + //// Only Complex Collections supported in Edm V1.1 + // NonComplexCollections = 158, + + /// + /// No Key defined on Entity Type + /// + KeyMissingOnEntityType = 159, + + //// Invalid namespace specified in using element + // InvalidNamespaceInUsing = 160, + + /// + /// Need not specify system namespace in using + /// + SystemNamespaceEncountered = 161, + + //// Cannot use a reserved/system namespace as alias + // CannotUseSystemNamespaceAsAlias = 162, + + /// + /// Invalid qualification specified for type + /// + InvalidNamespaceName = 163, + + //// Invalid Entity Container Name in extends attribute + // InvalidEntityContainerNameInExtends = 164, + //// Invalid CollectionKind value in property CollectionKind attribute + // InvalidCollectionKind = 165, + //// Must specify namespace or alias of the schema in which this type is defined + // InvalidNamespaceOrAliasSpecified = 166, + //// Entity Container cannot extend itself + // EntityContainerCannotExtendItself = 167, + //// Failed to retrieve provider manifest + // FailedToRetrieveProviderManifest = 168, + //// Mismatched Provider Manifest token values in SSDL artifacts + // ProviderManifestTokenMismatch = 169, + //// Missing Provider Manifest token value in SSDL artifact(s) + // ProviderManifestTokenNotFound = 170, + //// Empty CommandText element + // EmptyCommandText = 171, + //// Inconsistent Provider values in SSDL artifacts + // InconsistentProvider = 172, + //// Inconsistent Provider Manifest token values in SSDL artifacts + // InconsistentProviderManifestToken = 173, + //// Duplicated Function overloads + // DuplicatedFunctionoverloads = 174, + //// Invalid Provider + // InvalidProvider = 175, + //// Function With Non Edm Type Not Supported + // FunctionWithNonEdmTypeNotSupported = 176, + //// Complex Type As Return Type And Defined Entity Set + // ComplexTypeAsReturnTypeAndDefinedEntitySet = 177, + //// Complex Type As Return Type And Defined Entity Set + // ComplexTypeAsReturnTypeAndNestedComplexProperty = 178, + //// unused 179, + //// unused 180, + //// unused 181, + //// In model functions facet attribute is allowed only on ScalarTypes + // FacetOnNonScalarType = 182, + //// Captures several conditions where facets are placed on element where it should not exist. + // IncorrectlyPlacedFacet = 183, + //// Return type has not been declared + // ReturnTypeNotDeclared = 184, + // TypeNotDeclared = 185, + // RowTypeWithoutProperty = 186, + // ReturnTypeDeclaredAsAttributeAndElement = 187, + // TypeDeclaredAsAttributeAndElement = 188, + // ReferenceToNonEntityType = 189, + //// Invalid value in the EnumTypeOption + // InvalidValueInEnumOption = 190, + // IncompatibleSchemaVersion = 191, + //// The structural annotation cannot use codegen namespaces + // NoCodeGenNamespaceInStructuralAnnotation = 192, + //// Function and type cannot have the same fully qualified name + // AmbiguousFunctionAndType = 193, + //// Cannot load different version of schema in the same ItemCollection + // CannotLoadDifferentVersionOfSchemaInTheSameItemCollection = 194, + //// Expected bool value + // BoolValueExpected = 195, + //// End without Multiplicity specified + // EndWithoutMultiplicity = 196, + //// In SSDL, if composable function returns a collection of rows (TVF), all row properties must be of scalar types. + // TVFReturnTypeRowHasNonScalarProperty = 197, + //// The name of NamedEdmItem must not be empty or white space only + // EdmModel_NameMustNotBeEmptyOrWhiteSpace = 198, + //// EdmTypeReference is empty + //// Unused 199, + // EdmAssociationType_AssociationEndMustNotBeNull = 200, + // EdmAssociationConstraint_DependentEndMustNotBeNull = 201, + // EdmAssociationConstraint_DependentPropertiesMustNotBeEmpty = 202, + // EdmNavigationProperty_AssociationMustNotBeNull = 203, + // EdmNavigationProperty_ResultEndMustNotBeNull = 204, + // EdmAssociationEnd_EntityTypeMustNotBeNull = 205, + + /// + /// The enumeration member must have a value. + /// + EnumMemberMustHaveValue = 206, + + // EdmFunctionImport_ReturnTypeMustBeCollectionType = 210, + // EdmModel_NameIsNotAllowed = 211, + // EdmTypeReferenceNotValid = 212, + // EdmFunctionNotExistsInV1 = 213, + // EdmFunctionNotExistsInV1_1 = 214, + // Serializer_OneNamespaceAndOneContainer = 215, + // EdmModel_Validator_Semantic_InvalidEdmTypeReference = 216, + // EdmModel_Validator_TypeNameAlreadyDefinedDuplicate = 217, + + /// + /// The entity container name has already been assigned to a different entity container. + /// + DuplicateEntityContainerMemberName = 218, + + /// + /// An unbound function overload has a different return type. + /// + UnboundFunctionOverloadHasIncorrectReturnType = 219, + + // EdmFunction_UnsupportedParameterType = 219, + + /// + /// Complex types were not allowed to be abstract here. + /// + InvalidAbstractComplexType = 220, + + /// + /// Complex types cannot have base types in this version. + /// + InvalidPolymorphicComplexType = 221, + + /// + /// A navigation property without direct containment cannot contain its declaring entity indirectly. + /// + NavigationPropertyEntityMustNotIndirectlyContainItself = 222, + + /// + /// If a navigation property mapping is of a recursive navigation property, the mapping must point back to the same entity set. + /// + EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet = 223, + + /// + /// Name collision makes this name ambiguous. + /// + BadAmbiguousElementBinding = 224, + + /// + /// Could not find a type with this name. + /// + BadUnresolvedType = 225, + + /// + /// Could not find a primitive type with this name. + /// + BadUnresolvedPrimitiveType = 226, + + /// + /// This complex type is part of a cycle. + /// + BadCyclicComplex = 227, + + /// + /// This Entity Container is bad because some part of its extends hierarchy is part of a cycle. + /// + BadCyclicEntityContainer = 228, + + /// + /// This entity type is part of a cycle. + /// + BadCyclicEntity = 229, + + /// + /// Could not convert type reference to the requested type. + /// + TypeSemanticsCouldNotConvertTypeReference = 230, + + /// + /// This entity set became invalid because the entity that it was of the type of was removed from the model. + /// + ConstructibleEntitySetTypeInvalidFromEntityTypeRemoval = 231, + + /// + /// Could not find an EntityContainer with that name. + /// + BadUnresolvedEntityContainer = 232, + + /// + /// Could not find an EntitySet with that name. + /// + BadUnresolvedEntitySet = 233, + + /// + /// Could not find a property with that name + /// + BadUnresolvedProperty = 234, + + /// + /// Could not find an association end with that name + /// + BadNonComputableAssociationEnd = 235, + + /// + /// Type of the navigation property was invalid because the association of the navigation property was invalid. + /// + NavigationPropertyTypeInvalidBecauseOfBadAssociation = 236, + + /// + /// The base type of an entity must also be an entity. + /// + EntityMustHaveEntityBaseType = 237, + + /// + /// The base type of a complex type must also be complex. + /// + ComplexTypeMustHaveComplexBaseType = 238, + + /// + /// Could not find a operation with this name. + /// + BadUnresolvedOperation = 239, + + /// + /// Every property in an entity key must be a property of the entity. + /// + KeyPropertyMustBelongToEntity = 242, + + /// + /// The principal end of a referential constraint must be one of the ends of the association that defined the referential constraint. + /// + ReferentialConstraintPrincipalEndMustBelongToAssociation = 243, + + /// + /// Dependent properties of a referential constraint must belong to the dependent entity set. + /// + DependentPropertiesMustBelongToDependentEntity = 244, + + /// + /// If a structured type declares a property, that properties declaring type must be the declaring structured type. + /// + DeclaringTypeMustBeCorrect = 245, + + /// + /// Navigation property has a type that is not an entity or collection of entities. + /// + InvalidNavigationPropertyType = 258, + + /// + /// Edm.PrimitiveType cannot be used as the type of a key property of an entity type. + /// + KeyPropertyTypeCannotBeEdmPrimitiveType = 259, + + // unused 260 + + /// + /// Underlying type of the enumeration type is bad because the enumeration type is bad. + /// + UnderlyingTypeIsBadBecauseEnumTypeIsBad = 261, + + /// + /// Complex types must contain at least one property. + /// + ComplexTypeMustHaveProperties = 264, + + /// + /// Unsupported operation import parameter type. + /// + OperationImportParameterIncorrectType = 265, + + /// + /// A referential constraint cannot have multiple dependent properties with the same name. + /// + DuplicateDependentProperty = 267, + + /// + /// Bindable operation must have at least one parameter. + /// + BoundOperationMustHaveParameters = 268, + + /// + /// Operation with an EntitySetPath must be on a bound operation. + /// + OperationCannotHaveEntitySetPathWithUnBoundOperation = 269, + + /// + /// Operation with an EntitySetPath must have the first path item be the same name as the binding parameter. + /// + InvalidPathFirstPathParameterNotMatchingFirstParameterName = 271, + + /// + /// Operation with an EntitySetPath references a binding parameter that is not an entity type. + /// + InvalidPathWithNonEntityBindingParameter = 246, + + /// + /// Operation with an EntitySetPath segment is invalid as it has less than two items in the path. + /// + OperationWithInvalidEntitySetPathMissingCompletePath = 248, + + /// + /// Operation with an EntitySetPath segment has an unknown type cast segment. + /// + InvalidPathUnknownTypeCastSegment = 249, + + /// + /// Operation with an EntitySetPath segment has an invalid type cast segment. + /// + InvalidPathInvalidTypeCastSegment = 250, + + /// + /// Operation with an EntitySetPath segment has an invalid type cast segment, it must be an EntityType. + /// + InvalidPathTypeCastSegmentMustBeEntityType = 251, + + /// + /// Operation with an EntitySetPath segment has an unknown navigation property. + /// + InvalidPathUnknownNavigationProperty = 252, + + /// + /// Operation with an EntitySetPath has a return type that is not assignable to the resulting determined type from the entity set path. + /// + OperationWithEntitySetPathAndReturnTypeTypeNotAssignable = 253, + + /// + /// Operation entity set path resolves to a collection entity type when an entity type is expected + /// + OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType = 254, + + /// + /// Operation entity set path resolves to an entity type when a collection of entity type is expected. + /// + OperationWithEntitySetPathResolvesToEntityTypeMismatchesCollectionEntityTypeReturnType = 255, + + /// + /// Operation with an EntitySetPath has an invalid return type. The return type must be an entity type or collection of entity type. + /// + OperationWithEntitySetPathReturnTypeInvalid = 256, + + /// + /// Collection(Edm.PrimitiveType) and Collection(Edm.ComplexType) cannot be used as the return type of a function. + /// + OperationWithCollectionOfAbstractReturnTypeInvalid = 257, + + /// + /// Max length is out of range. + /// + MaxLengthOutOfRange = 272, + + /// + /// Binding context for Path expression does not supply an entity type + /// + PathExpressionHasNoEntityContext = 274, + + /// + /// Invalid value for SRID + /// + InvalidSrid = 275, + + /// + /// Invalid value for max length + /// + InvalidMaxLength = 276, + + /// + /// Invalid value for long + /// + InvalidLong = 277, + + /// + /// Invalid value for integer + /// + InvalidInteger = 278, + + /// + /// Invalid association set + /// + InvalidAssociationSet = 279, + + /// + /// Invalid parameter mode + /// + InvalidParameterMode = 280, + + /// + /// No entity type with that name exists. + /// + BadUnresolvedEntityType = 281, + + /// + /// Value is invalid + /// + InvalidValue = 282, + + /// + /// Binary value is invalid. + /// + InvalidBinary = 283, + + /// + /// Floating point value is invalid. + /// + InvalidFloatingPoint = 284, + + /// + /// DateTime value is invalid. + /// + InvalidDateTime = 285, + + /// + /// DateTimeOffset value is invalid. + /// + InvalidDateTimeOffset = 286, + + /// + /// Decimal value is invalid. + /// + InvalidDecimal = 287, + + /// + /// Guid value is invalid. + /// + InvalidGuid = 288, + + /// + /// The type kind None is not semantically valid. A semantically valid model must not contain elements of type kind None. + /// + InvalidTypeKindNone = 289, + + /// + /// The if expression is invalid because it does not have 3 elements. + /// + InvalidIfExpressionIncorrectNumberOfOperands = 290, + + /// + /// The enum member value is out of range of its underlying type. + /// + EnumMemberValueOutOfRange = 292, + + /// + /// The IsType expression is invalid because it does not have 1 element. + /// + InvalidIsTypeExpressionIncorrectNumberOfOperands = 293, + + /// + /// The type name is not fully qualified and not a primitive. + /// + InvalidTypeName = 294, + + /// + /// The term name is not fully qualified. + /// + InvalidQualifiedName = 295, + + /// + /// No model was parsed because no XmlReaders were provided. + /// + NoReadersProvided = 296, + + /// + /// Model could not be parsed because one of the XmlReaders was null. + /// + NullXmlReader = 297, + + /// + /// IsUnbounded cannot be true if MaxLength is non-null. + /// + IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull = 298, + + /// + /// ImmediateValueAnnotation is invalid as an element annotation. + /// + InvalidElementAnnotation = 299, + + /// + /// The LabeledElement expression is invalid because it does not have 1 element. + /// + InvalidLabeledElementExpressionIncorrectNumberOfOperands = 300, + + /// + /// Could not find a LabeledElement with that name + /// + BadUnresolvedLabeledElement = 301, + + /// + /// Could not find a enum member with that name + /// + BadUnresolvedEnumMember = 302, + + /// + /// The Cast expression is invalid because it does not have 1 element. + /// + InvalidCastExpressionIncorrectNumberOfOperands = 303, + + /// + /// Could not find a Parameter with that name + /// + BadUnresolvedParameter = 304, + + /// + /// A navigation property with = true must point to an optional target. + /// + NavigationPropertyWithRecursiveContainmentTargetMustBeOptional = 305, + + /// + /// If a navigation property has = true and the target entity type is the same as + /// the declaring type of the property, then the multiplicity of the source of navigation is Zero-Or-One. + /// + NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne = 306, + + /// + /// If a navigation property has = true and the target entity type is defferent than + /// the declaring type of the property, then the multiplicity of the source of navigation is One. + /// + NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne = 307, + + /// + /// The annotation target path cannot possibly refer to an annotable element. + /// + ImpossibleAnnotationsTarget = 309, + + /// + /// A nullable type is not valid if a non-nullable type is required. + /// + CannotAssertNullableTypeAsNonNullableType = 310, + + /// + /// The expression is a primitive constant, and cannot be valid for an non-primitive type. + /// + CannotAssertPrimitiveExpressionAsNonPrimitiveType = 311, + + /// + /// The primitive type is not valid for the requested type. + /// + ExpressionPrimitiveKindNotValidForAssertedType = 312, + + /// + /// Null is not valid in a non nullable expression. + /// + NullCannotBeAssertedToBeANonNullableType = 313, + + /// + /// The expression is not valid for the asserted type. + /// + ExpressionNotValidForTheAssertedType = 314, + + /// + /// A collection expression is not valid for a non-collection type. + /// + CollectionExpressionNotValidForNonCollectionType = 315, + + /// + /// A record expression is not valid for a non-structured type. + /// + RecordExpressionNotValidForNonStructuredType = 316, + + /// + /// The record expression does not have all of the properties required for the specified type. + /// + RecordExpressionMissingRequiredProperty = 317, + + /// + /// The record expression's type is not open, but the record expression has extra properties. + /// + RecordExpressionHasExtraProperties = 318, + + /// + /// Target has multiple annotations with the same term and same qualifier. + /// + DuplicateAnnotation = 319, + + /// + /// Function application has wrong number of arguments for the function being applied. + /// + IncorrectNumberOfArguments = 320, + + /// + /// Is it invalid to have duplicate alias in a single schema model. + /// + DuplicateAlias = 321, + + /// + /// A model cannot be serialized to CSDL if it has references to types without fully qualified names. + /// + ReferencedTypeMustHaveValidName = 322, + + /// + /// The model could not be serialized because multiple schemas were produced and only a single output stream was found. + /// + SingleFileExpected = 323, + + /// + /// The Edmx version is not valid. + /// + UnknownEdmxVersion = 324, + + /// + /// The EdmVersion is not valid. + /// + UnknownEdmVersion = 325, + + /// + /// Nothing was written because no schemas were produced. + /// + NoSchemasProduced = 326, + + /// + /// Model has multiple entity containers with the same name. + /// + DuplicateEntityContainerName = 327, + + /// + /// The container name of a container element must be the full name of the container entity container. + /// + ContainerElementContainerNameIncorrect = 328, + + /// + /// A primitive constant expression is not valid for a non-primitive type. + /// + PrimitiveConstantExpressionNotValidForNonPrimitiveType = 329, + + /// + /// The value of the integer constant is out of range for the asserted type. + /// + IntegerConstantValueOutOfRange = 330, + + /// + /// The length of the string constant is too large for the asserted type. + /// + StringConstantLengthOutOfRange = 331, + + /// + /// The length of the binary constant is too large for the asserted type. + /// + BinaryConstantLengthOutOfRange = 332, + + /// + /// None is not a valid mode for a operation import parameter. + /// + InvalidOperationImportParameterMode = 333, + + /// + /// A type without other errors must not have kind of none. + /// + TypeMustNotHaveKindOfNone = 334, + + /// + /// A primitive type without other errors must not have kind of none. + /// + PrimitiveTypeMustNotHaveKindOfNone = 335, + + /// + /// A property without other errors must not have kind of none. + /// + PropertyMustNotHaveKindOfNone = 336, + + /// + /// A property type cannot be collection of Edm.Primitive or Edm.ComplexType. + /// + PropertyTypeCannotBeCollectionOfAbstractType = 337, + + /// + /// A schema element without other errors must not have kind of none. + /// + SchemaElementMustNotHaveKindOfNone = 338, + + /// + /// An entity container element without other errors must not have kind of none. + /// + EntityContainerElementMustNotHaveKindOfNone = 339, + + /// + /// A binary value must have content. + /// + BinaryValueCannotHaveEmptyValue = 340, + + /// + /// There can only be a single navigation property mapping with containment that targets a particular entity set. + /// + EntitySetCanOnlyBeContainedByASingleNavigationProperty = 341, + + /// + /// The navigation properties partner does not point back to the correct type. + /// + InconsistentNavigationPropertyPartner = 342, + + /// + /// An entity set can only have one navigation property with containment. + /// + EntitySetCanOnlyHaveSingleNavigationPropertyWithContainment = 343, + + /// + /// If a navigation property is traversed from an entity set/singleton, and then it's partner is traversed from the target of the first mapping, the destination should be the originating entity set/singleton. + /// + NavigationMappingMustBeBidirectional = 344, + + /// + /// There can only be a single mapping from a given EntitySet with a particular navigation property. + /// + DuplicateNavigationPropertyMapping = 345, + + /// + /// An entity set must have a mapping for all of the navigation properties in its element type. + /// + AllNavigationPropertiesMustBeMapped = 346, + + /// + /// Type annotation does not have a property binding for all required properties. + /// + TypeAnnotationMissingRequiredProperty = 347, + + /// + /// Type annotation has a property binding for a non-existant property and its type is not open. + /// + TypeAnnotationHasExtraProperties = 348, + + /// + /// Duration value is invalid. + /// + InvalidDuration = 349, + + /// + /// The primitive type is invalid. + /// + InvalidPrimitiveValue = 350, + + /// + /// An Enum type must have an underlying type of integer. + /// + EnumMustHaveIntegerUnderlyingType = 351, + + /// + /// Could not find a term with this name. + /// + BadUnresolvedTerm = 352, + + /// + /// The principal properties of a referential constraint must match the key of the referential constraint. + /// + BadPrincipalPropertiesInReferentialConstraint = 353, + + /// + /// A direct annotation with the same name and namespace already exists. + /// + DuplicateDirectValueAnnotationFullName = 354, + + /// + /// Cannot infer an entity set because no set exists of the given type. + /// + NoEntitySetsFoundForType = 355, + + /// + /// Cannot infer an entity set because more than one set exists of the given type. + /// + CannotInferEntitySetWithMultipleSetsPerType = 356, + + /// + /// Invalid entity set path. + /// + InvalidEntitySetPath = 357, + + /// + /// Invalid enum member path. + /// + InvalidEnumMemberPath = 358, + + /// + /// An annotation qualifier must be a simple name. + /// + QualifierMustBeSimpleName = 359, + + /// + /// Enum type could not be resolved. + /// + BadUnresolvedEnumType = 360, + + /// + /// Could not find a target with this name. + /// + BadUnresolvedTarget = 361, + + /// + /// Path cannot be resolved in the given context. + /// + PathIsNotValidForTheGivenContext = 362, + + /// + /// Could not find a navigation property with this name. + /// + BadUnresolvedNavigationPropertyPath = 363, + + /// + /// The 'Nullable' attribute cannot be specified for a navigation property with collection type. + /// + NavigationPropertyWithCollectionTypeCannotHaveNullableAttribute = 364, + + /// + /// Metadata document cannot have more than one entity container. + /// + MetadataDocumentCannotHaveMoreThanOneEntityContainer = 365, + + /// + /// Model has multiple functions that are the same definitions. + /// + DuplicateFunctions = 366, + + /// + /// Model has multiple functions that are the same definitions. + /// + DuplicateActions = 367, + + /// + /// Bound Function overloads must have the same return type. + /// + BoundFunctionOverloadsMustHaveSameReturnType = 368, + + /// + /// The type of singleton must be entity type. + /// + SingletonTypeMustBeEntityType = 369, + + /// + /// The type of entity set must be collection of entity type. + /// + EntitySetTypeMustBeCollectionOfEntityType = 370, + + /// + /// The binding on navigation property of collection type must not target to singleton. + /// + NavigationPropertyOfCollectionTypeMustNotTargetToSingleton = 371, + + /// + /// Reference must contatin at least one Include or IncludeAnnotations + /// + ReferenceElementMustContainAtLeastOneIncludeOrIncludeAnnotationsElement = 372, + + /// + /// Function import must not have parameters if included in service document. + /// + FunctionImportWithParameterShouldNotBeIncludedInServiceDocument = 373, + + /// + /// Unresolved Uri found in edmx:Reference, getReferencedModelReaderFunc should not return null when the URI is not a well-known schema. + /// + UnresolvedReferenceUriInEdmxReference = 374, + + /// + /// Date value is invalid. + /// + InvalidDate = 375, + + /// + /// TimeOfDay value is invalid. + /// + InvalidTimeOfDay = 376, + + /// + /// Navigation property partner path cannot be resolved. + /// + UnresolvedNavigationPropertyPartnerPath = 377, + + /// + /// Navigation property binding path cannot be resolved. + /// + UnresolvedNavigationPropertyBindingPath = 378, + + /// + /// A required parameter followed an optional parameter. + /// + RequiredParametersMustPrecedeOptional = 379, + + /// + /// The enum type is not valid for the requested type. + /// + ExpressionEnumKindNotValidForAssertedType = 380, + + /// + /// The underlying type of a type definition type cannot be Edm.PrimitiveType. + /// + TypeDefinitionUnderlyingTypeCannotBeEdmPrimitiveType = 381, + + /// + /// The base type of an entity type cannot be Edm.EntityType. + /// + EntityTypeBaseTypeCannotBeEdmEntityType = 382, + + /// + /// The base type of a complex type cannot be Edm.ComplexType. + /// + ComplexTypeBaseTypeCannotBeEdmComplexType = 383, + + /// + /// Edm.EntityType cannot be used as the type of a singleton in an entity container. + /// + EntityTypeOfSingletonCannotBeEdmEntityType = 384, + + /// + /// Edm.EntityType cannot be used as the type of an entity set in an entity container. + /// + EntityTypeOfEntitySetCannotBeEdmEntityType = 385, + + /// + /// The dclaring type of navigation source cannot have path type property. + /// + DeclaringTypeOfNavigationSourceCannotHavePathProperty = 386, + + /// + /// The type of navigation property cannot have path type property. + /// + TypeOfNavigationPropertyCannotHavePathProperty = 387, + + /// + /// Could not find a return on the annotated operation. + /// + BadUnresolvedReturn = 388, + + /// + /// The composable escape bound function should not declare more than one. + /// + EntityComposableBoundEscapeFunctionMustBeLessOne = 389, + + /// + /// The non-composable escape bound function should not declare more than one. + /// + EntityNoncomposableBoundEscapeFunctionMustBeLessOne = 390, + + /// + /// The vocabulary annotation applies to not allowed annotatable element. + /// + AnnotationApplyToNotAllowedAnnotatable = 400, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmValidator.cs new file mode 100644 index 0000000..c4a3594 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/EdmValidator.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// Collection of validation methods. + /// + public static class EdmValidator + { + /// + /// Validate the and all of its properties using the current version of the model. + /// If the model has no version, is used. + /// + /// The root of the model to be validated. + /// Errors encountered while validating the model. + /// True if model is valid, otherwise false. + public static bool Validate(this IEdmModel root, out IEnumerable errors) + { + return Validate(root, root.GetEdmVersion() ?? EdmConstants.EdmVersionDefault, out errors); + } + + /// + /// Validate the and all of its properties given certain version. + /// + /// The root of the model to be validated. + /// Version of Edm to validate against. + /// Errors encountered while validating the model. + /// True if model is valid, otherwise false. + public static bool Validate(this IEdmModel root, Version version, out IEnumerable errors) + { + return Validate(root, ValidationRuleSet.GetEdmModelRuleSet(version), out errors); + } + + /// + /// Validate the and all of its properties given certain version. + /// + /// The root of the model to be validated. + /// Custom rule set to validate against. + /// Errors encountered while validating the model. + /// True if model is valid, otherwise false. + public static bool Validate(this IEdmModel root, ValidationRuleSet ruleSet, out IEnumerable errors) + { + EdmUtil.CheckArgumentNull(root, "root"); + EdmUtil.CheckArgumentNull(ruleSet, "ruleSet"); + + errors = InterfaceValidator.ValidateModelStructureAndSemantics(root, ruleSet); + return errors.FirstOrDefault() == null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ExpressionTypeChecker.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ExpressionTypeChecker.cs new file mode 100644 index 0000000..fe1aa42 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ExpressionTypeChecker.cs @@ -0,0 +1,633 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// Collection of extension methods to assert that an expression is of the required type. + /// + public static class ExpressionTypeChecker + { + private static readonly bool[,] promotionMap = InitializePromotionMap(); + + /// + /// Determines if the type of an expression is compatible with the provided type + /// + /// The expression to assert the type of. + /// The type to assert the expression as. + /// Errors produced if the expression does not match the specified type. + /// A value indicating whether the expression is valid for the given type or not. + /// If the expression has an associated type, this function will check that it matches the expected type and stop looking further. + /// If an expression claims a type, it must be validated that the type is valid for the expression. If the expression does not claim a type + /// this method will attempt to check the validity of the expression itself with the asserted type. + public static bool TryCast(this IEdmExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + return TryCast(expression, type, null, false, out discoveredErrors); + } + + /// + /// Determines if the type of an expression is compatible with the provided type + /// + /// The expression to assert the type of. + /// The type to assert the expression as. + /// The context paths are to be evaluated in. + /// A value indicating whether the expression must match the asserted type exactly, or simply be compatible. + /// Errors produced if the expression does not match the specified type. + /// A value indicating whether the expression is valid for the given type or not. + /// If the expression has an associated type, this function will check that it matches the expected type and stop looking further. + /// If an expression claims a type, it must be validated that the type is valid for the expression. If the expression does not claim a type + /// this method will attempt to check the validity of the expression itself with the asserted type. + public static bool TryCast(this IEdmExpression expression, IEdmTypeReference type, IEdmType context, bool matchExactly, out IEnumerable discoveredErrors) + { + EdmUtil.CheckArgumentNull(expression, "expression"); + type = type.AsActualTypeReference(); + + // If we don't have a type to assert this passes vacuously. + if (type == null || type.TypeKind() == EdmTypeKind.None) + { + discoveredErrors = Enumerable.Empty(); + return true; + } + + switch (expression.ExpressionKind) + { + case EdmExpressionKind.IntegerConstant: + case EdmExpressionKind.StringConstant: + case EdmExpressionKind.BinaryConstant: + case EdmExpressionKind.BooleanConstant: + case EdmExpressionKind.DateTimeOffsetConstant: + case EdmExpressionKind.DecimalConstant: + case EdmExpressionKind.FloatingConstant: + case EdmExpressionKind.GuidConstant: + case EdmExpressionKind.DurationConstant: + case EdmExpressionKind.DateConstant: + case EdmExpressionKind.TimeOfDayConstant: + IEdmPrimitiveValue primitiveValue = (IEdmPrimitiveValue)expression; + if (primitiveValue.Type != null) + { + return TestTypeReferenceMatch(primitiveValue.Type, type, expression.Location(), matchExactly, out discoveredErrors); + } + + return TryCastPrimitiveAsType(primitiveValue, type, out discoveredErrors); + case EdmExpressionKind.Null: + return TryCastNullAsType((IEdmNullExpression)expression, type, out discoveredErrors); + case EdmExpressionKind.Path: + case EdmExpressionKind.PropertyPath: + case EdmExpressionKind.NavigationPropertyPath: + return TryCastPathAsType((IEdmPathExpression)expression, type, context, matchExactly, out discoveredErrors); + case EdmExpressionKind.FunctionApplication: + IEdmApplyExpression applyExpression = (IEdmApplyExpression)expression; + if (applyExpression.AppliedFunction != null) + { + IEdmOperation operation = applyExpression.AppliedFunction as IEdmOperation; + if (operation != null) + { + return TestTypeReferenceMatch(operation.ReturnType, type, expression.Location(), matchExactly, out discoveredErrors); + } + } + + // If we don't have the applied function we just assume that it will work. + discoveredErrors = Enumerable.Empty(); + return true; + case EdmExpressionKind.If: + return TryCastIfAsType((IEdmIfExpression)expression, type, context, matchExactly, out discoveredErrors); + case EdmExpressionKind.IsType: + return TestTypeReferenceMatch(EdmCoreModel.Instance.GetBoolean(false), type, expression.Location(), matchExactly, out discoveredErrors); + case EdmExpressionKind.Record: + IEdmRecordExpression recordExpression = (IEdmRecordExpression)expression; + if (recordExpression.DeclaredType != null) + { + return TestTypeReferenceMatch(recordExpression.DeclaredType, type, expression.Location(), matchExactly, out discoveredErrors); + } + + return TryCastRecordAsType(recordExpression, type, context, matchExactly, out discoveredErrors); + case EdmExpressionKind.Collection: + IEdmCollectionExpression collectionExpression = (IEdmCollectionExpression)expression; + if (collectionExpression.DeclaredType != null) + { + return TestTypeReferenceMatch(collectionExpression.DeclaredType, type, expression.Location(), matchExactly, out discoveredErrors); + } + + return TryCastCollectionAsType(collectionExpression, type, context, matchExactly, out discoveredErrors); + case EdmExpressionKind.Labeled: + return TryCast(((IEdmLabeledExpression)expression).Expression, type, context, matchExactly, out discoveredErrors); + case EdmExpressionKind.Cast: + return TestTypeReferenceMatch(((IEdmCastExpression)expression).Type, type, expression.Location(), matchExactly, out discoveredErrors); + case EdmExpressionKind.LabeledExpressionReference: + return TryCast(((IEdmLabeledExpressionReferenceExpression)expression).ReferencedLabeledExpression, type, out discoveredErrors); + case EdmExpressionKind.EnumMember: + return TryCastEnumConstantAsType((IEdmEnumMemberExpression)expression, type, matchExactly, out discoveredErrors); + default: + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionNotValidForTheAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType) }; + return false; + } + } + + internal static bool TryCastPrimitiveAsType(this IEdmPrimitiveValue expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsPrimitive()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.PrimitiveConstantExpressionNotValidForNonPrimitiveType, Edm.Strings.EdmModel_Validator_Semantic_PrimitiveConstantExpressionNotValidForNonPrimitiveType) }; + return false; + } + + switch (expression.ValueKind) + { + case EdmValueKind.Binary: + return TryCastBinaryConstantAsType((IEdmBinaryConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.Boolean: + return TryCastBooleanConstantAsType((IEdmBooleanConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.DateTimeOffset: + return TryCastDateTimeOffsetConstantAsType((IEdmDateTimeOffsetConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.Decimal: + return TryCastDecimalConstantAsType((IEdmDecimalConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.Floating: + return TryCastFloatingConstantAsType((IEdmFloatingConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.Guid: + return TryCastGuidConstantAsType((IEdmGuidConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.Integer: + return TryCastIntegerConstantAsType((IEdmIntegerConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.String: + return TryCastStringConstantAsType((IEdmStringConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.Duration: + return TryCastDurationConstantAsType((IEdmDurationConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.Date: + return TryCastDateConstantAsType((IEdmDateConstantExpression)expression, type, out discoveredErrors); + case EdmValueKind.TimeOfDay: + return TryCastTimeOfDayConstantAsType((IEdmTimeOfDayConstantExpression)expression, type, out discoveredErrors); + default: + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + } + + internal static bool TryCastNullAsType(this IEdmNullExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsNullable) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.NullCannotBeAssertedToBeANonNullableType, Edm.Strings.EdmModel_Validator_Semantic_NullCannotBeAssertedToBeANonNullableType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + internal static bool TryCastPathAsType(this IEdmPathExpression expression, IEdmTypeReference type, IEdmType context, bool matchExactly, out IEnumerable discoveredErrors) + { + IEdmStructuredType structuredContext = context as IEdmStructuredType; + if (structuredContext != null) + { + IEdmType result = context; + + // [EdmLib] Need to handle paths that bind to things other than properties. + foreach (string segment in expression.PathSegments) + { + IEdmStructuredType structuredResult = result as IEdmStructuredType; + if (structuredResult == null) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.PathIsNotValidForTheGivenContext, Edm.Strings.EdmModel_Validator_Semantic_PathIsNotValidForTheGivenContext(segment)) }; + return false; + } + + IEdmProperty resultProperty = structuredResult.FindProperty(segment); + result = (resultProperty != null) ? resultProperty.Type.Definition : null; + + // If the path is not resolved, it could refer to an open type, and we cannot make an assertion about its type. + if (result == null) + { + discoveredErrors = Enumerable.Empty(); + return true; + } + } + + return TestTypeMatch(result, type.Definition, expression.Location(), matchExactly, out discoveredErrors); + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + internal static bool TryCastIfAsType(this IEdmIfExpression expression, IEdmTypeReference type, IEdmType context, bool matchExactly, out IEnumerable discoveredErrors) + { + IEnumerable ifTrueErrors; + IEnumerable ifFalseErrors; + bool success = TryCast(expression.TrueExpression, type, context, matchExactly, out ifTrueErrors); + success = TryCast(expression.FalseExpression, type, context, matchExactly, out ifFalseErrors) && success; + if (!success) + { + List errorList = new List(ifTrueErrors); + errorList.AddRange(ifFalseErrors); + discoveredErrors = errorList; + } + else + { + discoveredErrors = Enumerable.Empty(); + } + + return success; + } + + internal static bool TryCastRecordAsType(this IEdmRecordExpression expression, IEdmTypeReference type, IEdmType context, bool matchExactly, out IEnumerable discoveredErrors) + { + EdmUtil.CheckArgumentNull(expression, "expression"); + EdmUtil.CheckArgumentNull(type, "type"); + + if (!type.IsStructured()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.RecordExpressionNotValidForNonStructuredType, Edm.Strings.EdmModel_Validator_Semantic_RecordExpressionNotValidForNonStructuredType) }; + return false; + } + + HashSetInternal foundProperties = new HashSetInternal(); + List errors = new List(); + + IEdmStructuredTypeReference structuredType = type.AsStructured(); + foreach (IEdmProperty typeProperty in structuredType.StructuredDefinition().Properties()) + { + IEdmPropertyConstructor expressionProperty = expression.Properties.FirstOrDefault(p => p.Name == typeProperty.Name); + if (expressionProperty == null) + { + errors.Add(new EdmError(expression.Location(), EdmErrorCode.RecordExpressionMissingRequiredProperty, Edm.Strings.EdmModel_Validator_Semantic_RecordExpressionMissingProperty(typeProperty.Name))); + } + else + { + IEnumerable recursiveErrors; + if (!expressionProperty.Value.TryCast(typeProperty.Type, context, matchExactly, out recursiveErrors)) + { + foreach (EdmError error in recursiveErrors) + { + errors.Add(error); + } + } + + foundProperties.Add(typeProperty.Name); + } + } + + if (!structuredType.IsOpen()) + { + foreach (IEdmPropertyConstructor property in expression.Properties) + { + if (!foundProperties.Contains(property.Name)) + { + errors.Add(new EdmError(expression.Location(), EdmErrorCode.RecordExpressionHasExtraProperties, Edm.Strings.EdmModel_Validator_Semantic_RecordExpressionHasExtraProperties(property.Name))); + } + } + } + + if (errors.FirstOrDefault() != null) + { + discoveredErrors = errors; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + internal static bool TryCastCollectionAsType(this IEdmCollectionExpression expression, IEdmTypeReference type, IEdmType context, bool matchExactly, out IEnumerable discoveredErrors) + { + if (!type.IsCollection()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.CollectionExpressionNotValidForNonCollectionType, Edm.Strings.EdmModel_Validator_Semantic_CollectionExpressionNotValidForNonCollectionType) }; + return false; + } + + IEdmTypeReference collectionElementType = type.AsCollection().ElementType(); + bool success = true; + List errors = new List(); + IEnumerable recursiveErrors; + foreach (IEdmExpression element in expression.Elements) + { + success = TryCast(element, collectionElementType, context, matchExactly, out recursiveErrors) && success; + errors.AddRange(recursiveErrors); + } + + discoveredErrors = errors; + return success; + } + + private static bool TryCastGuidConstantAsType(IEdmGuidConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsGuid()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastFloatingConstantAsType(IEdmFloatingConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsFloating()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastDecimalConstantAsType(IEdmDecimalConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsDecimal()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastDateTimeOffsetConstantAsType(IEdmDateTimeOffsetConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsDateTimeOffset()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastDurationConstantAsType(IEdmDurationConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsDuration()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastDateConstantAsType(IEdmDateConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsDate()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastTimeOfDayConstantAsType(IEdmTimeOfDayConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsTimeOfDay()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastBooleanConstantAsType(IEdmBooleanConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsBoolean()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastStringConstantAsType(IEdmStringConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsString()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + IEdmStringTypeReference stringType = type.AsString(); + if (stringType.MaxLength.HasValue && expression.Value.Length > stringType.MaxLength.Value) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.StringConstantLengthOutOfRange, Edm.Strings.EdmModel_Validator_Semantic_StringConstantLengthOutOfRange(expression.Value.Length, stringType.MaxLength.Value)) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastIntegerConstantAsType(IEdmIntegerConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsIntegral()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + switch (type.PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Int64: + return TryCastIntegerConstantInRange(expression, Int64.MinValue, Int64.MaxValue, out discoveredErrors); + case EdmPrimitiveTypeKind.Int32: + return TryCastIntegerConstantInRange(expression, Int32.MinValue, Int32.MaxValue, out discoveredErrors); + case EdmPrimitiveTypeKind.Int16: + return TryCastIntegerConstantInRange(expression, Int16.MinValue, Int16.MaxValue, out discoveredErrors); + case EdmPrimitiveTypeKind.Byte: + return TryCastIntegerConstantInRange(expression, Byte.MinValue, Byte.MaxValue, out discoveredErrors); + case EdmPrimitiveTypeKind.SByte: + return TryCastIntegerConstantInRange(expression, SByte.MinValue, SByte.MaxValue, out discoveredErrors); + default: + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + } + + private static bool TryCastIntegerConstantInRange(IEdmIntegerConstantExpression expression, long min, long max, out IEnumerable discoveredErrors) + { + if (expression.Value < min || expression.Value > max) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.IntegerConstantValueOutOfRange, Edm.Strings.EdmModel_Validator_Semantic_IntegerConstantValueOutOfRange) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastBinaryConstantAsType(IEdmBinaryConstantExpression expression, IEdmTypeReference type, out IEnumerable discoveredErrors) + { + if (!type.IsBinary()) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindNotValidForAssertedType) }; + return false; + } + + IEdmBinaryTypeReference binaryType = type.AsBinary(); + if (binaryType.MaxLength.HasValue && expression.Value.Length > binaryType.MaxLength.Value) + { + discoveredErrors = new EdmError[] { new EdmError(expression.Location(), EdmErrorCode.BinaryConstantLengthOutOfRange, Edm.Strings.EdmModel_Validator_Semantic_BinaryConstantLengthOutOfRange(expression.Value.Length, binaryType.MaxLength.Value)) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TryCastEnumConstantAsType(IEdmEnumMemberExpression expression, IEdmTypeReference type, bool matchExactly, out IEnumerable discoveredErrors) + { + if (!type.IsEnum()) + { + discoveredErrors = new EdmError[] + { + new EdmError( + expression.Location(), + EdmErrorCode.ExpressionEnumKindNotValidForAssertedType, + Edm.Strings.EdmModel_Validator_Semantic_ExpressionEnumKindNotValidForAssertedType) + }; + return false; + } + + foreach (var member in expression.EnumMembers) + { + if (!TestTypeMatch(member.DeclaringType, type.Definition, expression.Location(), matchExactly, out discoveredErrors)) + { + return false; + } + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TestTypeReferenceMatch(this IEdmTypeReference expressionType, IEdmTypeReference assertedType, EdmLocation location, bool matchExactly, out IEnumerable discoveredErrors) + { + if (!TestNullabilityMatch(expressionType, assertedType, location, out discoveredErrors)) + { + return false; + } + + // A bad type reference matches anything (so as to avoid generating spurious errors). + if (expressionType.IsBad()) + { + discoveredErrors = Enumerable.Empty(); + return true; + } + + return TestTypeMatch(expressionType.Definition, assertedType.Definition, location, matchExactly, out discoveredErrors); + } + + private static bool TestTypeMatch(this IEdmType expressionType, IEdmType assertedType, EdmLocation location, bool matchExactly, out IEnumerable discoveredErrors) + { + if (matchExactly) + { + if (!expressionType.IsEquivalentTo(assertedType)) + { + discoveredErrors = new EdmError[] { new EdmError(location, EdmErrorCode.ExpressionNotValidForTheAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType) }; + return false; + } + } + else + { + // A bad type matches anything (so as to avoid generating spurious errors). + if (expressionType.TypeKind == EdmTypeKind.None || expressionType.IsBad()) + { + discoveredErrors = Enumerable.Empty(); + return true; + } + + if (expressionType.TypeKind == EdmTypeKind.Primitive && assertedType.TypeKind == EdmTypeKind.Primitive) + { + IEdmPrimitiveType primitiveExpressionType = expressionType as IEdmPrimitiveType; + IEdmPrimitiveType primitiveAssertedType = assertedType as IEdmPrimitiveType; + if (!primitiveExpressionType.PrimitiveKind.PromotesTo(primitiveAssertedType.PrimitiveKind)) + { + discoveredErrors = new EdmError[] { new EdmError(location, EdmErrorCode.ExpressionPrimitiveKindNotValidForAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionPrimitiveKindCannotPromoteToAssertedType(expressionType.ToTraceString(), assertedType.ToTraceString())) }; + return false; + } + } + else + { + if (!expressionType.IsOrInheritsFrom(assertedType)) + { + discoveredErrors = new EdmError[] { new EdmError(location, EdmErrorCode.ExpressionNotValidForTheAssertedType, Edm.Strings.EdmModel_Validator_Semantic_ExpressionNotValidForTheAssertedType) }; + return false; + } + } + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool TestNullabilityMatch(this IEdmTypeReference expressionType, IEdmTypeReference assertedType, EdmLocation location, out IEnumerable discoveredErrors) + { + if (!assertedType.IsNullable && expressionType.IsNullable) + { + discoveredErrors = new EdmError[] { new EdmError(location, EdmErrorCode.CannotAssertNullableTypeAsNonNullableType, Edm.Strings.EdmModel_Validator_Semantic_CannotAssertNullableTypeAsNonNullableType(expressionType.FullName())) }; + return false; + } + + discoveredErrors = Enumerable.Empty(); + return true; + } + + private static bool PromotesTo(this EdmPrimitiveTypeKind startingKind, EdmPrimitiveTypeKind target) + { + return startingKind == target || promotionMap[(int)startingKind, (int)target]; + } + + private static bool[,] InitializePromotionMap() + { + int typeKindCount = typeof(EdmPrimitiveTypeKind).GetFields().Where(f => f.IsLiteral).Count(); + bool[,] promotionMap = new bool[typeKindCount, typeKindCount]; + + promotionMap[(int)EdmPrimitiveTypeKind.Byte, (int)EdmPrimitiveTypeKind.Int16] = true; + promotionMap[(int)EdmPrimitiveTypeKind.Byte, (int)EdmPrimitiveTypeKind.Int32] = true; + promotionMap[(int)EdmPrimitiveTypeKind.Byte, (int)EdmPrimitiveTypeKind.Int64] = true; + + promotionMap[(int)EdmPrimitiveTypeKind.SByte, (int)EdmPrimitiveTypeKind.Int16] = true; + promotionMap[(int)EdmPrimitiveTypeKind.SByte, (int)EdmPrimitiveTypeKind.Int32] = true; + promotionMap[(int)EdmPrimitiveTypeKind.SByte, (int)EdmPrimitiveTypeKind.Int64] = true; + + promotionMap[(int)EdmPrimitiveTypeKind.Int16, (int)EdmPrimitiveTypeKind.Int32] = true; + promotionMap[(int)EdmPrimitiveTypeKind.Int16, (int)EdmPrimitiveTypeKind.Int64] = true; + + promotionMap[(int)EdmPrimitiveTypeKind.Int32, (int)EdmPrimitiveTypeKind.Int64] = true; + + promotionMap[(int)EdmPrimitiveTypeKind.Single, (int)EdmPrimitiveTypeKind.Double] = true; + + promotionMap[(int)EdmPrimitiveTypeKind.GeographyCollection, (int)EdmPrimitiveTypeKind.Geography] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeographyLineString, (int)EdmPrimitiveTypeKind.Geography] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeographyMultiLineString, (int)EdmPrimitiveTypeKind.Geography] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeographyMultiPoint, (int)EdmPrimitiveTypeKind.Geography] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeographyMultiPolygon, (int)EdmPrimitiveTypeKind.Geography] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeographyPoint, (int)EdmPrimitiveTypeKind.Geography] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeographyPolygon, (int)EdmPrimitiveTypeKind.Geography] = true; + + promotionMap[(int)EdmPrimitiveTypeKind.GeometryCollection, (int)EdmPrimitiveTypeKind.Geometry] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeometryLineString, (int)EdmPrimitiveTypeKind.Geometry] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeometryMultiLineString, (int)EdmPrimitiveTypeKind.Geometry] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeometryMultiPoint, (int)EdmPrimitiveTypeKind.Geometry] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeometryMultiPolygon, (int)EdmPrimitiveTypeKind.Geometry] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeometryPoint, (int)EdmPrimitiveTypeKind.Geometry] = true; + promotionMap[(int)EdmPrimitiveTypeKind.GeometryPolygon, (int)EdmPrimitiveTypeKind.Geometry] = true; + + return promotionMap; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/InterfaceValidator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/InterfaceValidator.cs new file mode 100644 index 0000000..ae3f60b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/InterfaceValidator.cs @@ -0,0 +1,1889 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Validation +{ + internal class InterfaceValidator + { + private static readonly Dictionary InterfaceVisitors = CreateInterfaceVisitorsMap(); + + /// + /// This is a thread-safe cache of object type to interface visitors which is shared between all instances of the validator. + /// + private static readonly Memoizer> ConcreteTypeInterfaceVisitors = new Memoizer>(ComputeInterfaceVisitorsForObject, null); + private readonly HashSetInternal visited = new HashSetInternal(); + private readonly HashSetInternal visitedBad = new HashSetInternal(); + private readonly HashSetInternal danglingReferences = new HashSetInternal(); + private readonly HashSetInternal skipVisitation; + private readonly bool validateDirectValueAnnotations; + private readonly IEdmModel model; + + private InterfaceValidator(HashSetInternal skipVisitation, IEdmModel model, bool validateDirectValueAnnotations) + { + this.skipVisitation = skipVisitation; + this.model = model; + this.validateDirectValueAnnotations = validateDirectValueAnnotations; + } + + public static IEnumerable ValidateModelStructureAndSemantics(IEdmModel model, ValidationRuleSet semanticRuleSet) + { + InterfaceValidator modelValidator = new InterfaceValidator(null, model, true); + + // Perform structural validation of the root object. + List errors = new List(modelValidator.ValidateStructure(model)); + + // Then check references for structural integrity using separate validator (in order to avoid adding referenced objects to the this.visited). + InterfaceValidator referencesValidator = new InterfaceValidator(modelValidator.visited, model, false); + IEnumerable referencesToStructurallyValidate = modelValidator.danglingReferences; + while (referencesToStructurallyValidate.FirstOrDefault() != null) + { + foreach (object reference in referencesToStructurallyValidate) + { + errors.AddRange(referencesValidator.ValidateStructure(reference)); + } + + referencesToStructurallyValidate = referencesValidator.danglingReferences.ToArray(); + } + + // If there are any critical structural errors detected, then it is not safe to traverse the root object, so return the errors without further processing. + if (errors.Any(ValidationHelper.IsInterfaceCritical)) + { + return errors; + } + + // If the root object is structurally sound, apply validation rules to the visited objects that are not known to be bad. + ValidationContext semanticValidationContext = new ValidationContext( + model, (item) => modelValidator.visitedBad.Contains(item) || referencesValidator.visitedBad.Contains(item)); + Dictionary> concreteTypeSemanticInterfaceVisitors = new Dictionary>(); + foreach (object item in modelValidator.visited) + { + if (!modelValidator.visitedBad.Contains(item)) + { + foreach (ValidationRule rule in GetSemanticInterfaceVisitorsForObject(item.GetType(), semanticRuleSet, concreteTypeSemanticInterfaceVisitors)) + { + rule.Evaluate(semanticValidationContext, item); + } + } + } + + errors.AddRange(semanticValidationContext.Errors); + return errors; + } + + public static IEnumerable GetStructuralErrors(IEdmElement item) + { + IEdmModel model = item as IEdmModel; + InterfaceValidator structuralValidator = new InterfaceValidator(null, model, model != null); + return structuralValidator.ValidateStructure(item); + } + + #region Private implementation + + private static Dictionary CreateInterfaceVisitorsMap() + { + Dictionary map = new Dictionary(); + + var nestedTypes = typeof(InterfaceValidator).GetNonPublicNestedTypes(); + + foreach (Type nestedType in nestedTypes) + { + if (nestedType.IsClass()) + { + Type baseType = nestedType.GetBaseType(); + if (baseType.IsGenericType() && baseType.GetBaseType() == typeof(VisitorBase)) + { + map.Add(baseType.GetGenericArguments()[0], (VisitorBase)Activator.CreateInstance(nestedType)); + } + } + } + + return map; + } + + private static IEnumerable ComputeInterfaceVisitorsForObject(Type objectType) + { + List visitors = new List(); + foreach (Type type in objectType.GetInterfaces()) + { + VisitorBase visitor; + if (InterfaceVisitors.TryGetValue(type, out visitor)) + { + visitors.Add(visitor); + } + } + + return visitors; + } + + private static EdmError CreatePropertyMustNotBeNullError(T item, string propertyName) + { + return new EdmError( + GetLocation(item), + EdmErrorCode.InterfaceCriticalPropertyValueMustNotBeNull, + Strings.EdmModel_Validator_Syntactic_PropertyMustNotBeNull(typeof(T).Name, propertyName)); + } + + private static EdmError CreateEnumPropertyOutOfRangeError(T item, E enumValue, string propertyName) + { + return new EdmError( + GetLocation(item), + EdmErrorCode.InterfaceCriticalEnumPropertyValueOutOfRange, + Strings.EdmModel_Validator_Syntactic_EnumPropertyValueOutOfRange(typeof(T).Name, propertyName, typeof(E).Name, enumValue)); + } + + private static EdmError CheckForInterfaceKindValueMismatchError(T item, K kind, string propertyName) + { + // If object implements an expected interface, return no error. + if (item is I) + { + return null; + } + + return new EdmError( + GetLocation(item), + EdmErrorCode.InterfaceCriticalKindValueMismatch, + Strings.EdmModel_Validator_Syntactic_InterfaceKindValueMismatch(kind, typeof(T).Name, propertyName, typeof(I).Name)); + } + + private static EdmError CreateInterfaceKindValueUnexpectedError(T item, K kind, string propertyName) + { + return new EdmError( + GetLocation(item), + EdmErrorCode.InterfaceCriticalKindValueUnexpected, + Strings.EdmModel_Validator_Syntactic_InterfaceKindValueUnexpected(kind, typeof(T).Name, propertyName)); + } + + private static EdmError CreateTypeRefInterfaceTypeKindValueMismatchError(T item) where T : IEdmTypeReference + { + Debug.Assert(item.Definition != null, "item.Definition != null"); + return new EdmError( + GetLocation(item), + EdmErrorCode.InterfaceCriticalKindValueMismatch, + Strings.EdmModel_Validator_Syntactic_TypeRefInterfaceTypeKindValueMismatch(typeof(T).Name, item.Definition.TypeKind)); + } + + private static EdmError CreatePrimitiveTypeRefInterfaceTypeKindValueMismatchError(T item) where T : IEdmPrimitiveTypeReference + { + Debug.Assert(item.Definition is IEdmPrimitiveType, "item.Definition is IEdmPrimitiveType"); + return new EdmError( + GetLocation(item), + EdmErrorCode.InterfaceCriticalKindValueMismatch, + Strings.EdmModel_Validator_Syntactic_TypeRefInterfaceTypeKindValueMismatch(typeof(T).Name, ((IEdmPrimitiveType)item.Definition).PrimitiveKind)); + } + + private static void ProcessEnumerable(T item, IEnumerable enumerable, string propertyName, IList targetList, ref List errors) + { + if (enumerable == null) + { + CollectErrors(CreatePropertyMustNotBeNullError(item, propertyName), ref errors); + } + else + { + foreach (E enumMember in enumerable) + { + if (enumMember != null) + { + targetList.Add(enumMember); + } + else + { + CollectErrors( + new EdmError(GetLocation(item), EdmErrorCode.InterfaceCriticalEnumerableMustNotHaveNullElements, Strings.EdmModel_Validator_Syntactic_EnumerableMustNotHaveNullElements(typeof(T).Name, propertyName)), + ref errors); + break; + } + } + } + } + + private static void CollectErrors(EdmError newError, ref List errors) + { + if (newError != null) + { + if (errors == null) + { + errors = new List(); + } + + errors.Add(newError); + } + } + + private static bool IsCheckableBad(object element) + { + IEdmCheckable checkable = element as IEdmCheckable; + return checkable != null && checkable.Errors != null && checkable.Errors.Count() > 0; + } + + private static EdmLocation GetLocation(object item) + { + IEdmLocatable edmLocatable = item as IEdmLocatable; + return edmLocatable != null && edmLocatable.Location != null ? edmLocatable.Location : new ObjectLocation(item); + } + + private static IEnumerable GetSemanticInterfaceVisitorsForObject(Type objectType, ValidationRuleSet ruleSet, Dictionary> concreteTypeSemanticInterfaceVisitors) + { + List visitors; + if (!concreteTypeSemanticInterfaceVisitors.TryGetValue(objectType, out visitors)) + { + visitors = new List(); + foreach (Type type in objectType.GetInterfaces()) + { + visitors.AddRange(ruleSet.GetRules(type)); + } + + concreteTypeSemanticInterfaceVisitors.Add(objectType, visitors); + } + + return visitors; + } + + private IEnumerable ValidateStructure(object item) + { + if (item is IEdmCoreModelElement || this.visited.Contains(item) || (this.skipVisitation != null && this.skipVisitation.Contains(item))) + { + // If we already visited this object, then errors (if any) have already been reported. + return Enumerable.Empty(); + } + + this.visited.Add(item); + if (this.danglingReferences.Contains(item)) + { + // If this edm element is visited, then it is no longer a dangling reference. + this.danglingReferences.Remove(item); + } + + //// First pass: collect immediate errors for each interface and collect followup objects for the second pass. + + List immediateErrors = null; + List followup = new List(); + List references = new List(); + IEnumerable visitors; + visitors = ConcreteTypeInterfaceVisitors.Evaluate(item.GetType()); + foreach (VisitorBase visitor in visitors) + { + IEnumerable errors = visitor.Visit(item, followup, references); + + // For performance reasons some visitors may return null errors enumerator. + if (errors != null) + { + foreach (EdmError error in errors) + { + if (immediateErrors == null) + { + immediateErrors = new List(); + } + + immediateErrors.Add(error); + } + } + } + + // End of the first pass: if there are immediate errors, return them without doing the second pass. + if (immediateErrors != null) + { + this.visitedBad.Add(item); + return immediateErrors; + } + + //// Second pass: collect errors from followup objects. + + List followupErrors = new List(); + + // An element's direct annotations are available only through a model, + // and so are not found in a normal traversal. + if (this.validateDirectValueAnnotations) + { + IEdmElement element = item as IEdmElement; + if (element != null) + { + foreach (IEdmDirectValueAnnotation annotation in this.model.DirectValueAnnotations(element)) + { + followupErrors.AddRange(this.ValidateStructure(annotation)); + } + } + } + + // Out-of-line annotations are found through the model visitor, but inline annotations + // are not found through normal traversal, so add in-line annotations here. + IEdmVocabularyAnnotatable annotatable = item as IEdmVocabularyAnnotatable; + if (this.model != null && annotatable != null) + { + foreach (IEdmVocabularyAnnotation annotation in annotatable.VocabularyAnnotations(this.model)) + { + // Inline annotations have a null target. + if (annotation.Target == null) + { + // Target must be set for validation, so create a new EdmVocabularyAnnotation with the target set + followupErrors.AddRange(this.ValidateStructure(new EdmVocabularyAnnotation(annotatable, annotation.Term, annotation.Qualifier, annotation.Value))); + } + } + } + + foreach (object followupItem in followup) + { + followupErrors.AddRange(this.ValidateStructure(followupItem)); + } + + foreach (object referencedItem in references) + { + this.CollectReference(referencedItem); + } + + return followupErrors; + } + + private void CollectReference(object reference) + { + if (!(reference is IEdmCoreModelElement) && + !this.visited.Contains(reference) && + (this.skipVisitation == null || !this.skipVisitation.Contains(reference))) + { + this.danglingReferences.Add(reference); + } + } + + #endregion + + #region Interface validators + + /* + * The general shape of a validation visitor is + * IEnumerable Visit(IEdmXYZInterface item, List followup, List references) + * Each visitor may return a null or empty collection of errors. + * Note that if a visitor returns errors, followup and references will be ignored. + */ + + private abstract class VisitorBase + { + public abstract IEnumerable Visit(object item, List followup, List references); + } + + private abstract class VisitorOfT : VisitorBase + { + public override IEnumerable Visit(object item, List followup, List references) + { + return this.VisitT((T)item, followup, references); + } + + protected abstract IEnumerable VisitT(T item, List followup, List references); + } + + #region Core interfaces + + private sealed class VisitorOfIEdmCheckable : VisitorOfT + { + protected override IEnumerable VisitT(IEdmCheckable checkable, List followup, List references) + { + List checkableErrors = new List(); + List errors = null; + ProcessEnumerable(checkable, checkable.Errors, "Errors", checkableErrors, ref errors); + return errors ?? checkableErrors; + } + } + + private sealed class VisitorOfIEdmElement : VisitorOfT + { + protected override IEnumerable VisitT(IEdmElement element, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmFullNamedElement : VisitorOfT + { + protected override IEnumerable VisitT(IEdmFullNamedElement element, List followup, List references) + { + return element.Name != null ? null : new EdmError[] { CreatePropertyMustNotBeNullError(element, "Name") }; + } + } + + private sealed class VisitorOfIEdmNamedElement : VisitorOfT + { + protected override IEnumerable VisitT(IEdmNamedElement element, List followup, List references) + { + return element.Name != null ? null : new EdmError[] { CreatePropertyMustNotBeNullError(element, "Name") }; + } + } + + private sealed class VisitorOfIEdmSchemaElement : VisitorOfT + { + protected override IEnumerable VisitT(IEdmSchemaElement element, List followup, List references) + { + List errors = new List(); + + switch (element.SchemaElementKind) + { + case EdmSchemaElementKind.TypeDefinition: + CollectErrors(CheckForInterfaceKindValueMismatchError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + break; + + case EdmSchemaElementKind.Action: + CollectErrors(CheckForInterfaceKindValueMismatchError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + CollectErrors(CheckForInterfaceKindValueMismatchError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + break; + case EdmSchemaElementKind.Function: + CollectErrors(CheckForInterfaceKindValueMismatchError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + CollectErrors(CheckForInterfaceKindValueMismatchError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + break; + + case EdmSchemaElementKind.Term: + CollectErrors(CheckForInterfaceKindValueMismatchError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + break; + + case EdmSchemaElementKind.EntityContainer: + CollectErrors(CheckForInterfaceKindValueMismatchError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + break; + + case EdmSchemaElementKind.None: + break; + + default: + CollectErrors(CreateEnumPropertyOutOfRangeError(element, element.SchemaElementKind, "SchemaElementKind"), ref errors); + break; + } + + if (element.Namespace == null) + { + CollectErrors(CreatePropertyMustNotBeNullError(element, "Namespace"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmModel : VisitorOfT + { + protected override IEnumerable VisitT(IEdmModel model, List followup, List references) + { + List errors = null; + ProcessEnumerable(model, model.SchemaElements, "SchemaElements", followup, ref errors); + ProcessEnumerable(model, model.VocabularyAnnotations, "VocabularyAnnotations", followup, ref errors); + return errors; + } + } + + private sealed class VisitorOfIEdmEntityContainer : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEntityContainer container, List followup, List references) + { + List errors = null; + ProcessEnumerable(container, container.Elements, "Elements", followup, ref errors); + return errors; + } + } + + private sealed class VisitorOfIEdmEntityContainerElement : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEntityContainerElement element, List followup, List references) + { + EdmError termKindError = null; + switch (element.ContainerElementKind) + { + case EdmContainerElementKind.EntitySet: + termKindError = CheckForInterfaceKindValueMismatchError(element, element.ContainerElementKind, "ContainerElementKind"); + break; + + case EdmContainerElementKind.Singleton: + termKindError = CheckForInterfaceKindValueMismatchError(element, element.ContainerElementKind, "ContainerElementKind"); + break; + + case EdmContainerElementKind.ActionImport: + case EdmContainerElementKind.FunctionImport: + termKindError = CheckForInterfaceKindValueMismatchError(element, element.ContainerElementKind, "ContainerElementKind"); + break; + + case EdmContainerElementKind.None: + break; + + default: + termKindError = CreateEnumPropertyOutOfRangeError(element, element.ContainerElementKind, "ContainerElementKind"); + break; + } + + return termKindError != null ? new EdmError[] { termKindError } : null; + } + } + + private sealed class VisitorOfIEdmContainedEntitySet : VisitorOfT + { + protected override IEnumerable VisitT(IEdmContainedEntitySet item, List followup, List references) + { + List errors = null; + + if (item.ParentNavigationSource == null) + { + CollectErrors(CreatePropertyMustNotBeNullError(item, "ParentNavigationSource"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmNavigationSource : VisitorOfT + { + protected override IEnumerable VisitT(IEdmNavigationSource set, List followup, List references) + { + List errors = null; + + // Navigation targets are not EDM elements, so we expand and process them here instead of adding them as followups. + List navPropBindings = new List(); + ProcessEnumerable(set, set.NavigationPropertyBindings, "NavigationPropertyBindings", navPropBindings, ref errors); + foreach (IEdmNavigationPropertyBinding navPropBinding in navPropBindings) + { + if (navPropBinding.NavigationProperty != null) + { + references.Add(navPropBinding.NavigationProperty); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(navPropBinding, "NavigationProperty"), ref errors); + } + + if (navPropBinding.Target != null) + { + references.Add(navPropBinding.Target); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(navPropBinding, "Target"), ref errors); + } + } + + return errors; + } + } + + private sealed class VisitorOfIEdmEntitySetBase : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEntitySetBase set, List followup, List references) + { + List errors = null; + + if (set.Type != null) + { + references.Add(set.Type); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(set, "Type"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmSingleton : VisitorOfT + { + protected override IEnumerable VisitT(IEdmSingleton singleton, List followup, List references) + { + List errors = null; + + if (singleton.Type != null) + { + references.Add(singleton.Type); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(singleton, "Type"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmTypeReference type, List followup, List references) + { + if (type.Definition != null) + { + // Transient types, such as collection and entity refs are considered to be owned by the type reference, so they go as followups. + // Schema types are owned by their model, so they go as references. + if (type.Definition is IEdmSchemaType) + { + references.Add(type.Definition); + } + else + { + followup.Add(type.Definition); + } + + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(type, "Definition") }; + } + } + } + + private sealed class VisitorOfIEdmType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmType type, List followup, List references) + { + EdmError typeKindError = null; + switch (type.TypeKind) + { + case EdmTypeKind.Primitive: + typeKindError = CheckForInterfaceKindValueMismatchError(type, type.TypeKind, "TypeKind"); + break; + + case EdmTypeKind.Entity: + typeKindError = CheckForInterfaceKindValueMismatchError(type, type.TypeKind, "TypeKind"); + break; + + case EdmTypeKind.Complex: + typeKindError = CheckForInterfaceKindValueMismatchError(type, type.TypeKind, "TypeKind"); + break; + + case EdmTypeKind.Collection: + typeKindError = CheckForInterfaceKindValueMismatchError(type, type.TypeKind, "TypeKind"); + break; + + case EdmTypeKind.EntityReference: + typeKindError = CheckForInterfaceKindValueMismatchError(type, type.TypeKind, "TypeKind"); + break; + + case EdmTypeKind.Enum: + typeKindError = CheckForInterfaceKindValueMismatchError(type, type.TypeKind, "TypeKind"); + break; + + case EdmTypeKind.TypeDefinition: + typeKindError = CheckForInterfaceKindValueMismatchError(type, type.TypeKind, "TypeKind"); + break; + + case EdmTypeKind.None: + break; + + default: + typeKindError = CreateInterfaceKindValueUnexpectedError(type, type.TypeKind, "TypeKind"); + break; + } + + return typeKindError != null ? new EdmError[] { typeKindError } : null; + } + } + + private sealed class VisitorOfIEdmPrimitiveType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPrimitiveType type, List followup, List references) + { + // Trying to reduce amount of noise in errors - if this type is bad, then most likely it will have an unacceptable kind, no need to report it. + if (!IsCheckableBad(type) && (type.PrimitiveKind < EdmPrimitiveTypeKind.None || type.PrimitiveKind > EdmPrimitiveTypeKind.GeometryMultiPoint)) + { + return new EdmError[] { CreateInterfaceKindValueUnexpectedError(type, type.PrimitiveKind, "PrimitiveKind") }; + } + else + { + return null; + } + } + } + + private sealed class VisitorOfIEdmStructuredType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmStructuredType type, List followup, List references) + { + List errors = null; + ProcessEnumerable(type, type.DeclaredProperties, "DeclaredProperties", followup, ref errors); + + if (type.BaseType != null) + { + HashSetInternal visitiedTypes = new HashSetInternal(); + visitiedTypes.Add(type); + for (IEdmStructuredType currentBaseType = currentBaseType = type.BaseType; currentBaseType != null; currentBaseType = currentBaseType.BaseType) + { + if (visitiedTypes.Contains(currentBaseType)) + { + IEdmSchemaType schemaType = type as IEdmSchemaType; + string typeName = schemaType != null ? schemaType.FullName() : typeof(Type).Name; + CollectErrors(new EdmError(GetLocation(type), EdmErrorCode.InterfaceCriticalCycleInTypeHierarchy, Strings.EdmModel_Validator_Syntactic_InterfaceCriticalCycleInTypeHierarchy(typeName)), ref errors); + break; + } + } + + references.Add(type.BaseType); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmEntityType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEntityType type, List followup, List references) + { + List errors = null; + if (type.DeclaredKey != null) + { + ProcessEnumerable(type, type.DeclaredKey, "DeclaredKey", references, ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmEntityReferenceType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEntityReferenceType type, List followup, List references) + { + if (type.EntityType != null) + { + references.Add(type.EntityType); + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(type, "EntityType") }; + } + } + } + + private sealed class VisitorOfIEdmUntypedType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmUntypedType type, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmPathType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPathType type, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmEnumType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEnumType type, List followup, List references) + { + List errors = null; + + ProcessEnumerable(type, type.Members, "Members", followup, ref errors); + + if (type.UnderlyingType != null) + { + references.Add(type.UnderlyingType); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(type, "UnderlyingType"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmTypeDefinition : VisitorOfT + { + protected override IEnumerable VisitT(IEdmTypeDefinition type, List followup, List references) + { + List errors = null; + + if (type.UnderlyingType != null) + { + references.Add(type.UnderlyingType); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(type, "UnderlyingType"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmTerm : VisitorOfT + { + protected override IEnumerable VisitT(IEdmTerm term, List followup, List references) + { + if (term.Type != null) + { + // Term owns its element type reference, so it goes as a followup. + followup.Add(term.Type); + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(term, "Type") }; + } + } + } + + private sealed class VisitorOfIEdmCollectionType : VisitorOfT + { + protected override IEnumerable VisitT(IEdmCollectionType type, List followup, List references) + { + if (type.ElementType != null) + { + // Collection owns its element type reference, so it goes as a followup. + followup.Add(type.ElementType); + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(type, "ElementType") }; + } + } + } + + private sealed class VisitorOfIEdmProperty : VisitorOfT + { + protected override IEnumerable VisitT(IEdmProperty property, List followup, List references) + { + List errors = null; + + switch (property.PropertyKind) + { + case EdmPropertyKind.Structural: + CollectErrors(CheckForInterfaceKindValueMismatchError(property, property.PropertyKind, "PropertyKind"), ref errors); + break; + + case EdmPropertyKind.Navigation: + CollectErrors(CheckForInterfaceKindValueMismatchError(property, property.PropertyKind, "PropertyKind"), ref errors); + break; + + case EdmPropertyKind.None: + break; + + default: + CollectErrors(CreateInterfaceKindValueUnexpectedError(property, property.PropertyKind, "PropertyKind"), ref errors); + break; + } + + if (property.Type != null) + { + // Property owns its type reference, so it goes as a followup. + followup.Add(property.Type); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(property, "Type"), ref errors); + } + + if (property.DeclaringType != null) + { + references.Add(property.DeclaringType); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(property, "DeclaringType"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmStructuralProperty : VisitorOfT + { + protected override IEnumerable VisitT(IEdmStructuralProperty property, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmNavigationProperty : VisitorOfT + { + protected override IEnumerable VisitT(IEdmNavigationProperty property, List followup, List references) + { + List errors = null; + + followup.Add(property.Type); + + if (property.Partner != null) + { + followup.Add(property.Partner); + + if (!(property.Partner is BadNavigationProperty)) + { + // Validates that the partner of the partner navigation property + // 1) is null if the partner navigation property is on a complex type, or + // 2) leads back to the same property if the partner navigation property is on an entity type, + // and also validates that the partner property is not referencing the property itself (except for the case that the Type of the navigation property is the same as + // its declaring property, ex: a Person entity has Navigation property Friend which has the target as Person and Partner as Friend) + if ((property.Partner.Partner != null && property.Partner.Partner != property) + || (property.Partner == property && (ValidationHelper.ComputeNavigationPropertyTarget(property) != property.DeclaringType))) + { + CollectErrors(new EdmError(GetLocation(property), EdmErrorCode.InterfaceCriticalNavigationPartnerInvalid, Strings.EdmModel_Validator_Syntactic_NavigationPartnerInvalid(property.Name)), ref errors); + } + } + } + + if (property.ReferentialConstraint != null) + { + followup.Add(property.ReferentialConstraint); + } + + if (property.OnDelete < EdmOnDeleteAction.None || property.OnDelete > EdmOnDeleteAction.Cascade) + { + CollectErrors(CreateEnumPropertyOutOfRangeError(property, property.OnDelete, "OnDelete"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmReferentialConstraint : VisitorOfT + { + protected override IEnumerable VisitT(IEdmReferentialConstraint member, List followup, List references) + { + List errors = null; + + if (member.PropertyPairs == null) + { + CollectErrors(CreatePropertyMustNotBeNullError(member, "PropertyPairs"), ref errors); + } + else + { + foreach (EdmReferentialConstraintPropertyPair pair in member.PropertyPairs) + { + if (pair == null) + { + CollectErrors(new EdmError(GetLocation(member), EdmErrorCode.InterfaceCriticalEnumerableMustNotHaveNullElements, Strings.EdmModel_Validator_Syntactic_EnumerableMustNotHaveNullElements(typeof(IEdmReferentialConstraint).Name, "PropertyPairs")), ref errors); + break; + } + + followup.Add(pair.PrincipalProperty); + followup.Add(pair.DependentProperty); + } + } + + return errors; + } + } + + private sealed class VisitorOfIEdmEnumMember : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEnumMember member, List followup, List references) + { + List errors = null; + + if (member.DeclaringType != null) + { + references.Add(member.DeclaringType); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(member, "DeclaringType"), ref errors); + } + + if (member.Value != null) + { + followup.Add(member.Value); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(member, "Value"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmOperation : VisitorOfT + { + protected override IEnumerable VisitT(IEdmOperation operation, List followup, List references) + { + List errors = null; + + ProcessEnumerable(operation, operation.Parameters, "Parameters", followup, ref errors); + + // Return type is optional for Action but not for Function + // So, from the point of view of this interface, derived validation will ensure return type null is valid or not. + if (operation.ReturnType != null) + { + // Function owns its return type reference, so it goes as a followup. + followup.Add(operation.ReturnType); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmAction : VisitorOfT + { + protected override IEnumerable VisitT(IEdmAction operation, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmFunction : VisitorOfT + { + protected override IEnumerable VisitT(IEdmFunction operation, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmOperationImport : VisitorOfT + { + protected override IEnumerable VisitT(IEdmOperationImport functionImport, List followup, List references) + { + if (functionImport.EntitySet != null) + { + followup.Add(functionImport.EntitySet); + } + + followup.Add(functionImport.Operation); + + return null; + } + } + + private sealed class VisitorOfIEdmActionImport : VisitorOfT + { + protected override IEnumerable VisitT(IEdmActionImport actionImport, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmFunctionImport : VisitorOfT + { + protected override IEnumerable VisitT(IEdmFunctionImport functionImport, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmOperationParameter : VisitorOfT + { + protected override IEnumerable VisitT(IEdmOperationParameter parameter, List followup, List references) + { + List errors = null; + + if (parameter.Type != null) + { + // Parameter owns its type reference, so it goes as a followup. + followup.Add(parameter.Type); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(parameter, "Type"), ref errors); + } + + if (parameter.DeclaringOperation != null) + { + references.Add(parameter.DeclaringOperation); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(parameter, "DeclaringFunction"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmOptionalParameter : VisitorOfT + { + protected override IEnumerable VisitT(IEdmOptionalParameter parameter, List followup, List references) + { + return null; + } + } + + private sealed class VisitorOfIEdmOperationReturn : VisitorOfT + { + protected override IEnumerable VisitT(IEdmOperationReturn operationReturn, List followup, List references) + { + List errors = null; + + if (operationReturn.Type != null) + { + followup.Add(operationReturn.Type); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(operationReturn, "Type"), ref errors); + } + + if (operationReturn.DeclaringOperation != null) + { + references.Add(operationReturn.DeclaringOperation); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(operationReturn, "DeclaringOperation"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmCollectionTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmCollectionTypeReference typeRef, List followup, List references) + { + return typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.Collection ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmEntityReferenceTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEntityReferenceTypeReference typeRef, List followup, List references) + { + return typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.EntityReference ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmStructuredTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmStructuredTypeReference typeRef, List followup, List references) + { + return typeRef.Definition != null && !typeRef.Definition.TypeKind.IsStructured() ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmEntityTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEntityTypeReference typeRef, List followup, List references) + { + return typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.Entity ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmComplexTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmComplexTypeReference typeRef, List followup, List references) + { + return typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.Complex ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmUntypedTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmUntypedTypeReference typeRef, List followup, List references) + { + return (typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.Untyped) + ? + new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } + : + null; + } + } + + private sealed class VisitorOfIEdmPathTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPathTypeReference typeRef, List followup, List references) + { + return (typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.Path) + ? + new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } + : + null; + } + } + + private sealed class VisitorOfIEdmEnumTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEnumTypeReference typeRef, List followup, List references) + { + return typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.Enum ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmTypeDefinitionReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmTypeDefinitionReference typeRef, List followup, List references) + { + return typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.TypeDefinition ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmPrimitiveTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPrimitiveTypeReference typeRef, List followup, List references) + { + return typeRef.Definition != null && typeRef.Definition.TypeKind != EdmTypeKind.Primitive ? new EdmError[] { CreateTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmBinaryTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmBinaryTypeReference typeRef, List followup, List references) + { + IEdmPrimitiveType primitive = typeRef.Definition as IEdmPrimitiveType; + return primitive != null && primitive.PrimitiveKind != EdmPrimitiveTypeKind.Binary ? new EdmError[] { CreatePrimitiveTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmDecimalTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmDecimalTypeReference typeRef, List followup, List references) + { + IEdmPrimitiveType primitive = typeRef.Definition as IEdmPrimitiveType; + return primitive != null && primitive.PrimitiveKind != EdmPrimitiveTypeKind.Decimal ? new EdmError[] { CreatePrimitiveTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmStringTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmStringTypeReference typeRef, List followup, List references) + { + IEdmPrimitiveType primitive = typeRef.Definition as IEdmPrimitiveType; + return primitive != null && primitive.PrimitiveKind != EdmPrimitiveTypeKind.String ? new EdmError[] { CreatePrimitiveTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmTemporalTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmTemporalTypeReference typeRef, List followup, List references) + { + IEdmPrimitiveType primitive = typeRef.Definition as IEdmPrimitiveType; + return primitive != null && !primitive.PrimitiveKind.IsTemporal() ? new EdmError[] { CreatePrimitiveTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmSpatialTypeReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmSpatialTypeReference typeRef, List followup, List references) + { + IEdmPrimitiveType primitive = typeRef.Definition as IEdmPrimitiveType; + return primitive != null && !primitive.PrimitiveKind.IsSpatial() ? new EdmError[] { CreatePrimitiveTypeRefInterfaceTypeKindValueMismatchError(typeRef) } : null; + } + } + + private sealed class VisitorOfIEdmReference : VisitorOfT + { + protected override IEnumerable VisitT(IEdmReference edmReference, List followup, List references) + { + return !edmReference.Includes.Any() && edmReference.IncludeAnnotations.Any() ? new EdmError[] { CreatePropertyMustNotBeNullError(edmReference, "Includes/IncludeAnnotations") } : null; + } + } + + private sealed class VisitorOfIEdmInclude : VisitorOfT + { + protected override IEnumerable VisitT(IEdmInclude edmInclude, List followup, List references) + { + return string.IsNullOrEmpty(edmInclude.Namespace) ? new EdmError[] { CreatePropertyMustNotBeNullError(edmInclude, "Namespace") } : null; + } + } + + private sealed class VisitorOfIEdmIncludeAnnotations : VisitorOfT + { + protected override IEnumerable VisitT(IEdmIncludeAnnotations edmIncludeAnnotations, List followup, List references) + { + return string.IsNullOrEmpty(edmIncludeAnnotations.TermNamespace) ? new EdmError[] { CreatePropertyMustNotBeNullError(edmIncludeAnnotations, "TermNamespace") } : null; + } + } + #endregion + + #region Expressions + + private sealed class VisitorOfIEdmExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmExpression expression, List followup, List references) + { + // Trying to reduce amount of noise in errors - if this expression is bad, then most likely it will have an unacceptable kind, no need to report it. + EdmError expressionKindError = null; + if (!IsCheckableBad(expression)) + { + switch (expression.ExpressionKind) + { + case EdmExpressionKind.IntegerConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.StringConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.BinaryConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.BooleanConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.DateConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.DateTimeOffsetConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.DurationConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.DecimalConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.FloatingConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.GuidConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.TimeOfDayConstant: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.Null: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.Record: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.Collection: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.Path: + case EdmExpressionKind.PropertyPath: + case EdmExpressionKind.NavigationPropertyPath: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.EnumMember: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.If: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.Cast: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.IsType: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.FunctionApplication: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.Labeled: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + case EdmExpressionKind.LabeledExpressionReference: + expressionKindError = CheckForInterfaceKindValueMismatchError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + + default: + expressionKindError = CreateInterfaceKindValueUnexpectedError(expression, expression.ExpressionKind, "ExpressionKind"); + break; + } + } + + return expressionKindError != null ? new EdmError[] { expressionKindError } : null; + } + } + + private sealed class VisitorOfIEdmRecordExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmRecordExpression expression, List followup, List references) + { + List errors = null; + + ProcessEnumerable(expression, expression.Properties, "Properties", followup, ref errors); + + if (expression.DeclaredType != null) + { + // Record constructor owns its type reference, so it goes as a followup. + followup.Add(expression.DeclaredType); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmPropertyConstructor : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPropertyConstructor expression, List followup, List references) + { + List errors = null; + + if (expression.Name == null) + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "Name"), ref errors); + } + + if (expression.Value != null) + { + followup.Add(expression.Value); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "Value"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmCollectionExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmCollectionExpression expression, List followup, List references) + { + List errors = null; + + ProcessEnumerable(expression, expression.Elements, "Elements", followup, ref errors); + + if (expression.DeclaredType != null) + { + // Collection constructor owns its type reference, so it goes as a followup. + followup.Add(expression.DeclaredType); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmLabeledElement : VisitorOfT + { + protected override IEnumerable VisitT(IEdmLabeledExpression expression, List followup, List references) + { + if (expression.Expression != null) + { + followup.Add(expression.Expression); + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(expression, "Expression") }; + } + } + } + + private sealed class VisitorOfIEdmPathExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPathExpression expression, List followup, List references) + { + List errors = null; + + List segments = new List(); + ProcessEnumerable(expression, expression.PathSegments, "Path", segments, ref errors); + + return errors; + } + } + + private sealed class VistorOfIEdmEnumMemberExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEnumMemberExpression expression, List followup, List references) + { + List errors = null; + + ProcessEnumerable(expression, expression.EnumMembers, "EnumMembers", followup, ref errors); + + return errors; + } + } + + private sealed class VistorOfIEdmIfExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmIfExpression expression, List followup, List references) + { + List errors = null; + + if (expression.TestExpression != null) + { + followup.Add(expression.TestExpression); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "TestExpression"), ref errors); + } + + if (expression.TrueExpression != null) + { + followup.Add(expression.TrueExpression); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "TrueExpression"), ref errors); + } + + if (expression.FalseExpression != null) + { + followup.Add(expression.FalseExpression); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "FalseExpression"), ref errors); + } + + return errors; + } + } + + private sealed class VistorOfIEdmCastExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmCastExpression expression, List followup, List references) + { + List errors = null; + + if (expression.Operand != null) + { + followup.Add(expression.Operand); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "Operand"), ref errors); + } + + if (expression.Type != null) + { + // Assert owns its type reference, so it goes as a followup. + followup.Add(expression.Type); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "Type"), ref errors); + } + + return errors; + } + } + + private sealed class VistorOfIEdmIsTypeExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmIsTypeExpression expression, List followup, List references) + { + List errors = null; + + if (expression.Operand != null) + { + followup.Add(expression.Operand); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "Operand"), ref errors); + } + + if (expression.Type != null) + { + // Assert owns its type reference, so it goes as a followup. + followup.Add(expression.Type); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "Type"), ref errors); + } + + return errors; + } + } + + private sealed class VistorOfIEdmFunctionApplicationExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmApplyExpression expression, List followup, List references) + { + List errors = null; + + if (expression.AppliedFunction != null) + { + followup.Add(expression.AppliedFunction); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(expression, "AppliedFunction"), ref errors); + } + + ProcessEnumerable(expression, expression.Arguments, "Arguments", followup, ref errors); + + return errors; + } + } + + private sealed class VistorOfIEdmLabeledElementReferenceExpression : VisitorOfT + { + protected override IEnumerable VisitT(IEdmLabeledExpressionReferenceExpression expression, List followup, List references) + { + if (expression.ReferencedLabeledExpression != null) + { + references.Add(expression.ReferencedLabeledExpression); + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(expression, "ReferencedLabeledExpression") }; + } + } + } + + #endregion + + #region Values + + private sealed class VisitorOfIEdmValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmValue value, List followup, List references) + { + List errors = null; + if (value.Type != null) + { + // Value owns its type reference, so it goes as a followup. + followup.Add(value.Type); + } + + switch (value.ValueKind) + { + case EdmValueKind.Binary: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Boolean: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Collection: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.DateTimeOffset: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Decimal: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Enum: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Floating: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Guid: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Integer: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Null: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.String: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Structured: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Duration: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.Date: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.TimeOfDay: + CollectErrors(CheckForInterfaceKindValueMismatchError(value, value.ValueKind, "ValueKind"), ref errors); + break; + + case EdmValueKind.None: + break; + + default: + CollectErrors(CreateInterfaceKindValueUnexpectedError(value, value.ValueKind, "ValueKind"), ref errors); + break; + } + + return errors; + } + } + + private sealed class VisitorOfIEdmDelayedValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmDelayedValue value, List followup, List references) + { + if (value.Value != null) + { + followup.Add(value.Value); + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(value, "Value") }; + } + } + } + + private sealed class VisitorOfIEdmPropertyValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPropertyValue value, List followup, List references) + { + return value.Name == null ? new EdmError[] { CreatePropertyMustNotBeNullError(value, "Name") } : null; + } + } + + private sealed class VisitorOfIEdmEnumValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmEnumValue value, List followup, List references) + { + if (value.Value != null) + { + followup.Add(value.Value); + return null; + } + else + { + return new EdmError[] { CreatePropertyMustNotBeNullError(value, "Value") }; + } + } + } + + private sealed class VisitorOfIEdmCollectionValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmCollectionValue value, List followup, List references) + { + List errors = null; + ProcessEnumerable(value, value.Elements, "Elements", followup, ref errors); + return errors; + } + } + + private sealed class VisitorOfIEdmStructuredValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmStructuredValue value, List followup, List references) + { + List errors = null; + ProcessEnumerable(value, value.PropertyValues, "PropertyValues", followup, ref errors); + return errors; + } + } + + private sealed class VisitorOfIEdmBinaryValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmBinaryValue value, List followup, List references) + { + return value.Value == null ? new EdmError[] { CreatePropertyMustNotBeNullError(value, "Value") } : null; + } + } + + private sealed class VisitorOfIEdmStringValue : VisitorOfT + { + protected override IEnumerable VisitT(IEdmStringValue value, List followup, List references) + { + return value.Value == null ? new EdmError[] { CreatePropertyMustNotBeNullError(value, "Value") } : null; + } + } + + #endregion + + #region Annotations + + private sealed class VisitorOfIEdmVocabularyAnnotation : VisitorOfT + { + protected override IEnumerable VisitT(IEdmVocabularyAnnotation annotation, List followup, List references) + { + List errors = null; + + if (annotation.Term != null) + { + references.Add(annotation.Term); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(annotation, "Term"), ref errors); + } + + if (annotation.Target != null) + { + references.Add(annotation.Target); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(annotation, "Target"), ref errors); + } + + if (annotation.Value != null) + { + followup.Add(annotation.Value); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(annotation, "Value"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmPropertyValueBinding : VisitorOfT + { + protected override IEnumerable VisitT(IEdmPropertyValueBinding binding, List followup, List references) + { + List errors = null; + + if (binding.Value != null) + { + followup.Add(binding.Value); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(binding, "Value"), ref errors); + } + + if (binding.BoundProperty != null) + { + references.Add(binding.BoundProperty); + } + else + { + CollectErrors(CreatePropertyMustNotBeNullError(binding, "BoundProperty"), ref errors); + } + + return errors; + } + } + + private sealed class VisitorOfIEdmDirectValueAnnotation : VisitorOfT + { + protected override IEnumerable VisitT(IEdmDirectValueAnnotation annotation, List followup, List references) + { + List errors = null; + + if (annotation.NamespaceUri == null) + { + CollectErrors(CreatePropertyMustNotBeNullError(annotation, "NamespaceUri"), ref errors); + } + + if (annotation.Value == null) + { + CollectErrors(CreatePropertyMustNotBeNullError(annotation, "Value"), ref errors); + } + + return errors; + } + } + + #endregion + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ObjectLocation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ObjectLocation.cs new file mode 100644 index 0000000..6799c5e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ObjectLocation.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// Defines an object as a location of itself. + /// + public class ObjectLocation : EdmLocation + { + internal ObjectLocation(object obj) + { + this.Object = obj; + } + + /// + /// Gets the object. + /// + public object Object { get; private set; } + + /// + /// Gets a string representation of the location. + /// + /// A string representation of the location. + public override string ToString() + { + return "(" + this.Object.ToString() + ")"; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationContext.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationContext.cs new file mode 100644 index 0000000..cba976e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationContext.cs @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// Context that records errors reported by validation rules. + /// + public sealed class ValidationContext + { + private readonly List errors = new List(); + private readonly IEdmModel model; + private readonly Func isBad; + + internal ValidationContext(IEdmModel model, Func isBad) + { + this.model = model; + this.isBad = isBad; + } + + /// + /// Gets the model being validated. + /// + public IEdmModel Model + { + get { return this.model; } + } + + internal IEnumerable Errors + { + get { return this.errors; } + } + + /// + /// Method returns true if the is known to have structural errors associated with it. + /// + /// The element to test. + /// True if the has structural errors associated with it. + public bool IsBad(IEdmElement element) + { + return this.isBad(element); + } + + /// + /// Register an error with the validation context. + /// + /// Location of the error. + /// Value representing the error. + /// Message text discribing the error. + public void AddError(EdmLocation location, EdmErrorCode errorCode, string errorMessage) + { + this.AddError(new EdmError(location, errorCode, errorMessage)); + } + + /// + /// Register an error with the validation context. + /// + /// Error to register. + public void AddError(EdmError error) + { + this.errors.Add(error); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationExtensionMethods.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationExtensionMethods.cs new file mode 100644 index 0000000..421f2a7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationExtensionMethods.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// Contains IsBad() and Errors() extension methods. + /// + public static class ValidationExtensionMethods + { + /// + /// Returns true if this element contains errors returned by the method. + /// + /// Reference to the calling object. + /// This element is an invalid element. + public static bool IsBad(this IEdmElement element) + { + EdmUtil.CheckArgumentNull(element, "element"); + return element.Errors().FirstOrDefault() != null; + } + + /// + /// Gets the errors, if any, that belong to this element or elements that this element contains. For example errors for a structural type include the errors of the type itself and errors of its declared properties. + /// The method does not analyze elements referenced by this element. For example errors of a property do not include errors from its type. + /// + /// Reference to the calling object. + /// Any errors that belong to this element or elements that element contains. + public static IEnumerable Errors(this IEdmElement element) + { + EdmUtil.CheckArgumentNull(element, "element"); + return InterfaceValidator.GetStructuralErrors(element); + } + + /// + /// Gets the errors, if any, that belong to this type reference or its definition. + /// + /// The type reference. + /// Any errors that belong to this type reference or its definition. + public static IEnumerable TypeErrors(this IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(type, "type"); + return InterfaceValidator.GetStructuralErrors(type).Concat(InterfaceValidator.GetStructuralErrors(type.Definition)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationHelper.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationHelper.cs new file mode 100644 index 0000000..c403b8a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationHelper.cs @@ -0,0 +1,225 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Validation +{ + internal static class ValidationHelper + { + internal static bool IsEdmSystemNamespace(string namespaceName) + { + return (namespaceName == EdmConstants.TransientNamespace || + namespaceName == EdmConstants.EdmNamespace); + } + + internal static bool AddMemberNameToHashSet(IEdmNamedElement item, HashSetInternal memberNameList, ValidationContext context, EdmErrorCode errorCode, string errorString, bool suppressError) + { + IEdmSchemaElement schemaElement = item as IEdmSchemaElement; + string name = (schemaElement != null) ? schemaElement.FullName() : item.Name; + if (!memberNameList.Add(name)) + { + if (!suppressError) + { + context.AddError(item.Location(), errorCode, errorString); + } + + return false; + } + + return true; + } + + internal static bool AllPropertiesAreNullable(IEnumerable properties) + { + return properties.Where(p => !p.Type.IsNullable).Count() == 0; + } + + internal static bool HasNullableProperty(IEnumerable properties) + { + return properties.Where(p => p.Type.IsNullable).Count() > 0; + } + + internal static bool PropertySetIsSubset(IEnumerable set, IEnumerable subset) + { + return subset.Except(set).Count() <= 0; + } + + internal static bool PropertySetsAreEquivalent(IEnumerable set1, IEnumerable set2) + { + if (set1.Count() != set2.Count()) + { + return false; + } + + IEnumerator set2Enum = set2.GetEnumerator(); + foreach (IEdmStructuralProperty prop1 in set1) + { + set2Enum.MoveNext(); + if (prop1 != set2Enum.Current) + { + return false; + } + } + + return true; + } + + internal static bool ValidateValueCanBeWrittenAsXmlElementAnnotation(IEdmValue value, string annotationNamespace, string annotationName, out EdmError error) + { + IEdmStringValue edmStringValue = value as IEdmStringValue; + if (edmStringValue == null) + { + error = new EdmError(value.Location(), EdmErrorCode.InvalidElementAnnotation, Edm.Strings.EdmModel_Validator_Semantic_InvalidElementAnnotationNotIEdmStringValue); + return false; + } + + string rawString = edmStringValue.Value; + + XmlReader reader = XmlReader.Create(new StringReader(rawString)); + + try + { + // Skip to root element. + if (reader.NodeType != XmlNodeType.Element) + { + while (reader.Read() && reader.NodeType != XmlNodeType.Element) + { + } + } + + // The annotation must be an element. + if (reader.EOF) + { + error = new EdmError(value.Location(), EdmErrorCode.InvalidElementAnnotation, Edm.Strings.EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml); + return false; + } + + // The root element must corespond to the term of the annotation + string elementNamespace = reader.NamespaceURI; + string elementName = reader.LocalName; + + if (EdmUtil.IsNullOrWhiteSpaceInternal(elementNamespace) || EdmUtil.IsNullOrWhiteSpaceInternal(elementName)) + { + error = new EdmError(value.Location(), EdmErrorCode.InvalidElementAnnotation, Edm.Strings.EdmModel_Validator_Semantic_InvalidElementAnnotationNullNamespaceOrName); + return false; + } + + if (!((annotationNamespace == null || elementNamespace == annotationNamespace) && (annotationName == null || elementName == annotationName))) + { + error = new EdmError(value.Location(), EdmErrorCode.InvalidElementAnnotation, Edm.Strings.EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm); + return false; + } + + // Parse the entire fragment to determine if the XML is valid + while (reader.Read()) + { + } + + error = null; + return true; + } + catch (XmlException) + { + error = new EdmError(value.Location(), EdmErrorCode.InvalidElementAnnotation, Edm.Strings.EdmModel_Validator_Semantic_InvalidElementAnnotationValueInvalidXml); + return false; + } + } + + internal static bool IsInterfaceCritical(EdmError error) + { + return error.ErrorCode >= EdmErrorCode.InterfaceCriticalPropertyValueMustNotBeNull && error.ErrorCode <= EdmErrorCode.InterfaceCriticalCycleInTypeHierarchy; + } + + internal static bool ItemExistsInReferencedModel(this IEdmModel model, string fullName, bool checkEntityContainer) + { + foreach (IEdmModel referenced in model.ReferencedModels) + { + if (referenced.FindDeclaredType(fullName) != null || + referenced.FindDeclaredTerm(fullName) != null || + (checkEntityContainer && referenced.ExistsContainer(fullName)) || + (referenced.FindDeclaredOperations(fullName) ?? Enumerable.Empty()).FirstOrDefault() != null) + { + return true; + } + } + + return false; + } + + // Take operation name to avoid recomputing it + internal static bool OperationOrNameExistsInReferencedModel(this IEdmModel model, IEdmOperation operation, string operationFullName) + { + foreach (IEdmModel referenced in model.ReferencedModels) + { + if (referenced.FindDeclaredType(operationFullName) != null || + referenced.ExistsContainer(operationFullName) || + referenced.FindDeclaredTerm(operationFullName) != null) + { + return true; + } + else + { + IEnumerable operationList = referenced.FindDeclaredOperations(operationFullName) ?? Enumerable.Empty(); + if (DuplicateOperationValidator.IsDuplicateOperation(operation, operationList)) + { + return true; + } + } + } + + return false; + } + + internal static bool TypeIndirectlyContainsTarget(IEdmEntityType source, IEdmEntityType target, HashSetInternal visited, IEdmModel context) + { + if (visited.Add(source)) + { + if (source.IsOrInheritsFrom(target)) + { + return true; + } + + foreach (IEdmNavigationProperty navProp in source.NavigationProperties()) + { + if (navProp.ContainsTarget && TypeIndirectlyContainsTarget(navProp.ToEntityType(), target, visited, context)) + { + return true; + } + } + + foreach (IEdmStructuredType derived in context.FindAllDerivedTypes(source)) + { + IEdmEntityType derivedEntity = derived as IEdmEntityType; + if (derivedEntity != null && TypeIndirectlyContainsTarget(derivedEntity, target, visited, context)) + { + return true; + } + } + } + + return false; + } + + internal static IEdmEntityType ComputeNavigationPropertyTarget(IEdmNavigationProperty property) + { + Debug.Assert(property != null, "Navigation property can't be null"); + IEdmType target = property.Type.Definition; + if (target.TypeKind == EdmTypeKind.Collection) + { + target = ((IEdmCollectionType)target).ElementType.Definition; + } + + return (IEdmEntityType)target; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRule.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRule.cs new file mode 100644 index 0000000..4862f93 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRule.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// A semantic validation rule. + /// + public abstract class ValidationRule + { + internal abstract Type ValidatedType { get; } + + internal abstract void Evaluate(ValidationContext context, object item); + } + + /// + /// A validation rule that is valid for a specific type. + /// + /// Type that the rule is valid for. + public sealed class ValidationRule : ValidationRule + where TItem : IEdmElement + { + private readonly Action validate; + + /// + /// Initializes a new instance of the ValidationRule class. + /// + /// Action to perform the validation. + public ValidationRule(Action validate) + { + this.validate = validate; + } + + internal override Type ValidatedType + { + get { return typeof(TItem); } + } + + internal override void Evaluate(ValidationContext context, object item) + { + Debug.Assert(item is TItem, "item should be " + typeof(TItem)); + TItem typedItem = (TItem)item; + this.validate(context, typedItem); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRuleSet.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRuleSet.cs new file mode 100644 index 0000000..1f4d941 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRuleSet.cs @@ -0,0 +1,222 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// A set of rules to run during validation. + /// + public sealed class ValidationRuleSet : IEnumerable + { + private readonly Dictionary> rules; + + private static readonly ValidationRuleSet BaseRuleSet = + new ValidationRuleSet(new ValidationRule[] + { + ValidationRules.EntityTypeKeyPropertyMustBelongToEntity, + ValidationRules.StructuredTypePropertiesDeclaringTypeMustBeCorrect, + ValidationRules.NamedElementNameMustNotBeEmptyOrWhiteSpace, + ValidationRules.NamedElementNameIsTooLong, + ValidationRules.NamedElementNameIsNotAllowed, + ValidationRules.SchemaElementNamespaceIsNotAllowed, + ValidationRules.SchemaElementNamespaceIsTooLong, + ValidationRules.SchemaElementNamespaceMustNotBeEmptyOrWhiteSpace, + ValidationRules.SchemaElementSystemNamespaceEncountered, + ValidationRules.EntityContainerDuplicateEntityContainerMemberName, + ValidationRules.EntityTypeDuplicatePropertyNameSpecifiedInEntityKey, + ValidationRules.EntityTypeInvalidKeyNullablePart, + ValidationRules.EntityTypeEntityKeyMustBeScalar, + ValidationRules.EntityTypeInvalidKeyKeyDefinedInBaseClass, + ValidationRules.EntityTypeBoundEscapeFunctionMustBeUnique, +//// ValidationRules.EntityTypeKeyMissingOnEntityType, + ValidationRules.StructuredTypeInvalidMemberNameMatchesTypeName, + ValidationRules.StructuredTypePropertyNameAlreadyDefined, + ValidationRules.StructuralPropertyInvalidPropertyType, + ValidationRules.OperationParameterNameAlreadyDefinedDuplicate, + ValidationRules.OperationImportEntityTypeDoesNotMatchEntitySet, + ValidationRules.OperationImportCannotImportBoundOperation, + ValidationRules.StructuredTypeBaseTypeMustBeSameKindAsDerivedKind, + ValidationRules.NavigationPropertyWithRecursiveContainmentTargetMustBeOptional, + ValidationRules.NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne, + ValidationRules.NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne, + ValidationRules.EntitySetTypeMustBeCollectionOfEntityType, + ValidationRules.NavigationSourceInaccessibleEntityType, + ValidationRules.StructuredTypeInaccessibleBaseType, + ValidationRules.EntityReferenceTypeInaccessibleEntityType, + ValidationRules.TypeReferenceInaccessibleSchemaType, + ValidationRules.NavigationSourceTypeHasNoKeys, + ValidationRules.DecimalTypeReferenceScaleOutOfRange, + ValidationRules.BinaryTypeReferenceBinaryMaxLengthNegative, + ValidationRules.StringTypeReferenceStringMaxLengthNegative, + ValidationRules.EnumMemberValueMustHaveSameTypeAsUnderlyingType, + ValidationRules.EnumTypeEnumMemberNameAlreadyDefined, + ValidationRules.BoundOperationMustHaveParameters, + ValidationRules.OptionalParametersMustComeAfterRequiredParameters, + ValidationRules.OperationEntitySetPathMustBeValid, + ValidationRules.OperationReturnTypeEntityTypeMustBeValid, + ValidationRules.OperationImportEntitySetExpressionIsInvalid, + ValidationRules.FunctionImportWithParameterShouldNotBeIncludedInServiceDocument, + ValidationRules.BinaryTypeReferenceBinaryUnboundedNotValidForMaxLength, + ValidationRules.StringTypeReferenceStringUnboundedNotValidForMaxLength, + ValidationRules.ImmediateValueAnnotationElementAnnotationIsValid, + ValidationRules.VocabularyAnnotationAssertCorrectExpressionType, + ValidationRules.IfExpressionAssertCorrectTestType, + ValidationRules.CollectionExpressionAllElementsCorrectType, + ValidationRules.RecordExpressionPropertiesMatchType, + ValidationRules.NavigationPropertyDependentPropertiesMustBelongToDependentEntity, + ValidationRules.NavigationPropertyInvalidOperationMultipleEndsInAssociatedNavigationProperties, + ValidationRules.NavigationPropertyEndWithManyMultiplicityCannotHaveOperationsSpecified, + ValidationRules.NavigationPropertyPartnerPathShouldBeResolvable, + ValidationRules.NavigationPropertyTypeMismatchRelationshipConstraint, + ValidationRules.NavigationPropertyDuplicateDependentProperty, + ValidationRules.NavigationPropertyPrincipalEndMultiplicity, + ValidationRules.NavigationPropertyDependentEndMultiplicity, + ValidationRules.NavigationPropertyCorrectType, + ValidationRules.NavigationPropertyBindingPathMustBeResolvable, + ValidationRules.ImmediateValueAnnotationElementAnnotationHasNameAndNamespace, + ValidationRules.OpenComplexTypeCannotHaveClosedDerivedComplexType, + ValidationRules.FunctionApplicationExpressionParametersMatchAppliedFunction, + ValidationRules.VocabularyAnnotatableNoDuplicateAnnotations, + ValidationRules.TemporalTypeReferencePrecisionOutOfRange, + ValidationRules.DecimalTypeReferencePrecisionOutOfRange, + ValidationRules.ModelDuplicateEntityContainerName, + ValidationRules.ModelBoundFunctionOverloadsMustHaveSameReturnType, + ValidationRules.UnBoundFunctionOverloadsMustHaveIdenticalReturnTypes, + ValidationRules.TypeMustNotHaveKindOfNone, + ValidationRules.PrimitiveTypeMustNotHaveKindOfNone, + ValidationRules.PropertyMustNotHaveKindOfNone, + ValidationRules.SchemaElementMustNotHaveKindOfNone, + ValidationRules.EntityContainerElementMustNotHaveKindOfNone, + ValidationRules.PrimitiveValueValidForType, + ValidationRules.EntitySetCanOnlyBeContainedByASingleNavigationProperty, + ValidationRules.NavigationMappingMustBeBidirectional, + ValidationRules.SingletonTypeMustBeEntityType, + ValidationRules.NavigationPropertyMappingsMustBeUnique, + ValidationRules.PropertyValueBindingValueIsCorrectType, + ValidationRules.EnumMustHaveIntegerUnderlyingType, + ValidationRules.AnnotationInaccessibleTerm, + ValidationRules.ElementDirectValueAnnotationFullNameMustBeUnique, + ValidationRules.VocabularyAnnotationInaccessibleTarget, + ValidationRules.EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet, + ValidationRules.NavigationPropertyMappingMustPointToValidTargetForProperty, + ValidationRules.DirectValueAnnotationHasXmlSerializableName, + ValidationRules.FunctionMustHaveReturnType, + ValidationRules.FunctionWithUrlEscapeFunctionMustBeBound, + ValidationRules.FunctionWithUrlEscapeFunctionMustHaveOneStringParameter, + ValidationRules.EntitySetTypeCannotBeEdmEntityType, + ValidationRules.SingletonTypeCannotBeEdmEntityType, + ValidationRules.OperationReturnTypeCannotBeCollectionOfAbstractType, + ValidationRules.PropertyTypeCannotBeCollectionOfAbstractType, + ValidationRules.EntityTypeKeyTypeCannotBeEdmPrimitiveType, + ValidationRules.TypeDefinitionUnderlyingTypeCannotBeEdmPrimitiveType, + ValidationRules.EnumUnderlyingTypeCannotBeEdmPrimitiveType, + ValidationRules.StructuredTypeBaseTypeCannotBeAbstractType, + ValidationRules.NavigationSourceDeclaringTypeCannotHavePathTypeProperty, + ValidationRules.NavigationPropertyTypeCannotHavePathTypeProperty + }); + + private static readonly ValidationRuleSet V4RuleSet = + new ValidationRuleSet( + BaseRuleSet, + new ValidationRule[] + { + ValidationRules.OperationUnsupportedReturnType, + ValidationRules.ModelDuplicateSchemaElementName, + }); + + /// + /// Initializes a new instance of the ValidationRuleSet class. + /// + /// Ruleset whose rules should be contained in this set. + /// Additional rules to add to the set. + public ValidationRuleSet(IEnumerable baseSet, IEnumerable newRules) + : this(EdmUtil.CheckArgumentNull(baseSet, "baseSet").Concat(EdmUtil.CheckArgumentNull(newRules, "newRules"))) + { + } + + /// + /// Initializes a new instance of the ValidationRuleSet class. + /// + /// Rules to be contained in this ruleset. + public ValidationRuleSet(IEnumerable rules) + { + EdmUtil.CheckArgumentNull(rules, "rules"); + this.rules = new Dictionary>(); + foreach (ValidationRule rule in rules) + { + this.AddRule(rule); + } + } + + /// + /// Gets the default validation ruleset for the given version. + /// + /// The EDM version being validated. + /// The set of rules to validate that the model conforms to the given version. + public static ValidationRuleSet GetEdmModelRuleSet(Version version) + { + if (version == EdmConstants.EdmVersion4 || version == EdmConstants.EdmVersion401) + { + return V4RuleSet; + } + else + { + throw new InvalidOperationException(Edm.Strings.Serializer_UnknownEdmVersion); + } + } + + /// + /// Gets all of the rules in this ruleset. + /// + /// All of the rules in this ruleset. + public IEnumerator GetEnumerator() + { + foreach (List ruleList in this.rules.Values) + { + foreach (ValidationRule rule in ruleList) + { + yield return rule; + } + } + } + + /// + /// Gets all of the rules in this ruleset. + /// + /// All of the rules in this ruleset. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + internal IEnumerable GetRules(Type t) + { + List foundRules; + return this.rules.TryGetValue(t, out foundRules) ? foundRules : Enumerable.Empty(); + } + + private void AddRule(ValidationRule rule) + { + List typeRules; + if (!this.rules.TryGetValue(rule.ValidatedType, out typeRules)) + { + typeRules = new List(); + this.rules[rule.ValidatedType] = typeRules; + } + + if (typeRules.Contains(rule)) + { + throw new InvalidOperationException(Edm.Strings.RuleSet_DuplicateRulesExistInRuleSet); + } + + typeRules.Add(rule); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRules.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRules.cs new file mode 100644 index 0000000..f4d80aa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Validation/ValidationRules.cs @@ -0,0 +1,2871 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Csdl.CsdlSemantics; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm.Validation +{ + /// + /// Built in Edm validation rules. + /// + public static class ValidationRules + { + #region IEdmElement + + /// + /// Validates that no direct annotations share the same name and namespace. + /// + public static readonly ValidationRule ElementDirectValueAnnotationFullNameMustBeUnique = + new ValidationRule( + (context, item) => + { + HashSetInternal annotationNameSet = new HashSetInternal(); + foreach (IEdmDirectValueAnnotation annotation in context.Model.DirectValueAnnotationsManager.GetDirectValueAnnotations(item)) + { + if (!annotationNameSet.Add(annotation.NamespaceUri + ":" + annotation.Name)) + { + context.AddError( + annotation.Location(), + EdmErrorCode.DuplicateDirectValueAnnotationFullName, + Strings.EdmModel_Validator_Semantic_ElementDirectValueAnnotationFullNameMustBeUnique(annotation.NamespaceUri, annotation.Name)); + } + } + }); + + #endregion + + #region IEdmNamedElement + + /// + /// Validates that a name is not empty or whitespace. + /// + public static readonly ValidationRule NamedElementNameMustNotBeEmptyOrWhiteSpace = + new ValidationRule( + (context, item) => + { + if (EdmUtil.IsNullOrWhiteSpaceInternal(item.Name) || item.Name.Length == 0) + { + context.AddError( + item.Location(), + EdmErrorCode.InvalidName, + Strings.EdmModel_Validator_Syntactic_MissingName); + } + }); + + /// + /// Validates that an element name is not too long according to the CSDL spec. + /// + public static readonly ValidationRule NamedElementNameIsTooLong = + new ValidationRule( + (context, item) => + { + if (!EdmUtil.IsNullOrWhiteSpaceInternal(item.Name) && item.Name.Length > CsdlConstants.Max_NameLength) + { + context.AddError( + item.Location(), + EdmErrorCode.NameTooLong, + Strings.EdmModel_Validator_Syntactic_EdmModel_NameIsTooLong(item.Name)); + } + }); + + /// + /// Validates that an element name matches the allowed pattern of names according to the CSDL spec. + /// + public static readonly ValidationRule NamedElementNameIsNotAllowed = + new ValidationRule( + (context, item) => + { + // Don't run this rule for IEdmDirectValueAnnotation, since these custom annotations may have names which are invalid C# identifiers, but work in XML. + // We validate the name of IEdmDirectionValueAnnotation in a separate validation rule, DirectValueAnnotationHasXmlSerializableName. + if (item is IEdmDirectValueAnnotation) + { + return; + } + + if (!EdmUtil.IsNullOrWhiteSpaceInternal(item.Name) && item.Name.Length <= CsdlConstants.Max_NameLength && item.Name.Length > 0) + { + if (!EdmUtil.IsValidUndottedName(item.Name)) + { + context.AddError( + item.Location(), + EdmErrorCode.InvalidName, + Strings.EdmModel_Validator_Syntactic_EdmModel_NameIsNotAllowed(item.Name)); + } + } + }); + + #endregion + + #region IEdmSchemaElement + + /// + /// Validates that an element namespace is not empty or whitespace. + /// + public static readonly ValidationRule SchemaElementNamespaceMustNotBeEmptyOrWhiteSpace = + new ValidationRule( + (context, item) => + { + if (EdmUtil.IsNullOrWhiteSpaceInternal(item.Namespace) || item.Namespace.Length == 0) + { + context.AddError( + item.Location(), + EdmErrorCode.InvalidNamespaceName, + Strings.EdmModel_Validator_Syntactic_MissingNamespaceName); + } + }); + + /// + /// Validates that an element namespace is not too long according to the CSDL spec. + /// + public static readonly ValidationRule SchemaElementNamespaceIsTooLong = + new ValidationRule( + (context, item) => + { + if (item.Namespace.Length > CsdlConstants.Max_NamespaceLength) + { + context.AddError( + item.Location(), + EdmErrorCode.InvalidNamespaceName, + Strings.EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsTooLong(item.Namespace)); + } + }); + + /// + /// Validates that an element namespace matches the allowed pattern of namespaces according to the CSDL spec. + /// + public static readonly ValidationRule SchemaElementNamespaceIsNotAllowed = + new ValidationRule( + (context, item) => + { + // max length is hard coded in the xsd + if (item.Namespace.Length <= CsdlConstants.Max_NamespaceLength && item.Namespace.Length > 0 && !EdmUtil.IsNullOrWhiteSpaceInternal(item.Namespace)) + { + if (!EdmUtil.IsValidDottedName(item.Namespace)) + { + context.AddError( + item.Location(), + EdmErrorCode.InvalidNamespaceName, + Strings.EdmModel_Validator_Syntactic_EdmModel_NamespaceNameIsNotAllowed(item.Namespace)); + } + } + }); + + /// + /// Validates that an element namespace is not a reserved system namespace. + /// + public static readonly ValidationRule SchemaElementSystemNamespaceEncountered = + new ValidationRule( + (context, element) => + { + if (ValidationHelper.IsEdmSystemNamespace(element.Namespace)) + { + context.AddError( + element.Location(), + EdmErrorCode.SystemNamespaceEncountered, + Strings.EdmModel_Validator_Semantic_SystemNamespaceEncountered(element.Namespace)); + } + }); + + /// + /// A schema element without other errors must not have kind of none. + /// + public static readonly ValidationRule SchemaElementMustNotHaveKindOfNone = + new ValidationRule( + (context, element) => + { + if (element.SchemaElementKind == EdmSchemaElementKind.None && !context.IsBad(element)) + { + context.AddError( + element.Location(), + EdmErrorCode.SchemaElementMustNotHaveKindOfNone, + Strings.EdmModel_Validator_Semantic_SchemaElementMustNotHaveKindOfNone(element.FullName())); + } + }); + + #endregion + + #region IEdmEntityContainerElement + + /// + /// An entity container element without other errors must not have kind of none. + /// + public static readonly ValidationRule EntityContainerElementMustNotHaveKindOfNone = + new ValidationRule( + (context, element) => + { + if (element.ContainerElementKind == EdmContainerElementKind.None && !context.IsBad(element)) + { + context.AddError( + element.Location(), + EdmErrorCode.EntityContainerElementMustNotHaveKindOfNone, + Strings.EdmModel_Validator_Semantic_EntityContainerElementMustNotHaveKindOfNone(element.Container.FullName() + '/' + element.Name)); + } + }); + + #endregion + + #region IEdmEntityContainer + + /// + /// Validates that there are no duplicate names in an entity container. + /// + public static readonly ValidationRule EntityContainerDuplicateEntityContainerMemberName = + new ValidationRule( + (context, entityContainer) => + { + HashSetInternal nonOperationList = new HashSetInternal(); + HashSetInternal operationImportOperationList = new HashSetInternal(); + HashSetInternal operationImportList = new HashSetInternal(); + foreach (var item in entityContainer.Elements) + { + bool duplicate = false; + + var operationImport = item as IEdmOperationImport; + if (operationImport != null) + { + if (!operationImportList.Contains(operationImport.Name)) + { + operationImportList.Add(operationImport.Name); + } + + // OperationImports of the same name can exist as long as they reference different operations. + string operationImportUniqueString = operationImport.Name + "_" + operationImport.Operation.GetHashCode(); + if (operationImportOperationList.Contains(operationImportUniqueString)) + { + duplicate = true; + } + else + { + operationImportOperationList.Add(operationImportUniqueString); + } + + if (nonOperationList.Contains(operationImport.Name)) + { + duplicate = true; + } + } + else + { + if (nonOperationList.Contains(item.Name)) + { + duplicate = true; + } + else + { + nonOperationList.Add(item.Name); + } + + if (operationImportList.Contains(item.Name)) + { + duplicate = true; + } + } + + if (duplicate) + { + context.AddError( + item.Location(), + EdmErrorCode.DuplicateEntityContainerMemberName, + Strings.EdmModel_Validator_Semantic_DuplicateEntityContainerMemberName(item.Name)); + } + } + }); + + #endregion + + #region IEdmNavigationSource + + /// + /// Validates that there is no entity set or singleton whose entity type has no key. + /// + public static readonly ValidationRule NavigationSourceTypeHasNoKeys = + new ValidationRule( + (context, navigationSource) => + { + // Entity types used in Singletons don't require keys + if (navigationSource.NavigationSourceKind() == EdmNavigationSourceKind.Singleton + || navigationSource.NavigationSourceKind() == EdmNavigationSourceKind.None) + { + return; + } + + IEdmEntityType entityType = navigationSource.EntityType(); + + if (entityType == null) + { + return; + } + + if ((navigationSource.EntityType().Key() == null || !navigationSource.EntityType().Key().Any()) && !context.IsBad(navigationSource.EntityType())) + { + string errorMessage = Strings.EdmModel_Validator_Semantic_NavigationSourceTypeHasNoKeys( + navigationSource.Name, + navigationSource.EntityType().Name); + + context.AddError( + navigationSource.Location(), + EdmErrorCode.NavigationSourceTypeHasNoKeys, + errorMessage); + } + }); + + /// + /// Validates that there is no entity set or singleton whose entity type has not property defined with Path type. + /// + public static readonly ValidationRule NavigationSourceDeclaringTypeCannotHavePathTypeProperty = + new ValidationRule( + (context, navigationSource) => + { + IEdmEntityType entityType = navigationSource.EntityType(); + + if (entityType == null) + { + return; + } + + IList visited = new List(); + if (HasPathTypeProperty(entityType, visited)) + { + string name = navigationSource is IEdmSingleton ? "singleton" : "entity set"; + + string errorMessage = Strings + .EdmModel_Validator_Semantic_DeclaringTypeOfNavigationSourceCannotHavePathProperty(entityType.FullName(), name, navigationSource.Name); + + context.AddError( + navigationSource.Location(), + EdmErrorCode.DeclaringTypeOfNavigationSourceCannotHavePathProperty, + errorMessage); + } + }); + + /// + /// Validates that the entity type of an entity set or singleton can be found from the model being validated. + /// + public static readonly ValidationRule NavigationSourceInaccessibleEntityType = + new ValidationRule( + (context, navigationSource) => + { + IEdmEntityType entityType = navigationSource.EntityType(); + if (entityType != null && !context.IsBad(entityType)) + { + CheckForUnreacheableTypeError(context, entityType, navigationSource.Location()); + } + }); + + /// + /// Validates that no navigation property is mapped multiple times for a single path. + /// + public static readonly ValidationRule NavigationPropertyMappingsMustBeUnique = + new ValidationRule( + (context, navigationSource) => + { + var set = new HashSetInternal>(); + foreach (var mapping in navigationSource.NavigationPropertyBindings) + { + if (!set.Add(new KeyValuePair(mapping.NavigationProperty, mapping.Path.Path))) + { + // TODO: Update error message in V7.1 #644 + context.AddError( + navigationSource.Location(), + EdmErrorCode.DuplicateNavigationPropertyMapping, + Strings.EdmModel_Validator_Semantic_DuplicateNavigationPropertyMapping(navigationSource.Name, mapping.NavigationProperty.Name)); + } + } + }); + + /// + /// Validates that the target of a navigation property mapping is valid for the target type of the property. + /// + public static readonly ValidationRule NavigationPropertyMappingMustPointToValidTargetForProperty = + new ValidationRule( + (context, navigationSource) => + { + foreach (IEdmNavigationPropertyBinding mapping in navigationSource.NavigationPropertyBindings) + { + if (mapping.NavigationProperty.IsBad() || mapping.Target.IsBad()) + { + continue; + } + + if (!(mapping.Target.EntityType().IsOrInheritsFrom(mapping.NavigationProperty.ToEntityType()) || mapping.NavigationProperty.ToEntityType().IsOrInheritsFrom(mapping.Target.EntityType())) && !context.IsBad(mapping.Target)) + { + context.AddError( + navigationSource.Location(), + EdmErrorCode.NavigationPropertyMappingMustPointToValidTargetForProperty, + Strings.EdmModel_Validator_Semantic_NavigationPropertyMappingMustPointToValidTargetForProperty(mapping.NavigationProperty.Name, mapping.Target.Name)); + } + + if (mapping.Target is IEdmSingleton && mapping.NavigationProperty.Type.Definition.TypeKind == EdmTypeKind.Collection) + { + context.AddError( + navigationSource.Location(), + EdmErrorCode.NavigationPropertyOfCollectionTypeMustNotTargetToSingleton, + Strings.EdmModel_Validator_Semantic_NavigationPropertyOfCollectionTypeMustNotTargetToSingleton(mapping.NavigationProperty.Name, mapping.Target.Name)); + } + } + }); + + /// + /// Validates that the binding path of navigation property must be resolved to a valid path, that is: + /// Each segments in path must be defined, and inner path segments can only be complex or containment, and last path segment must be the navigation property name. + /// + public static readonly ValidationRule NavigationPropertyBindingPathMustBeResolvable = + new ValidationRule( + (context, navigationSource) => + { + foreach (IEdmNavigationPropertyBinding mapping in navigationSource.NavigationPropertyBindings) + { + if (mapping.NavigationProperty.IsBad() || mapping.Target.IsBad()) + { + continue; + } + + if (!TryResolveNavigationPropertyBindingPath(context.Model, navigationSource, mapping)) + { + // TODO: Update error message in V7.1 #644 + context.AddError( + navigationSource.Location(), + EdmErrorCode.UnresolvedNavigationPropertyBindingPath, + string.Format( + CultureInfo.CurrentCulture, + "The binding path {0} for navigation property {1} under navigation source {2} is not valid.", + mapping.Path.Path, + mapping.NavigationProperty.Name, + navigationSource.Name)); + } + } + }); + + #endregion + + #region IEdmEntitySet + + /// + /// Validates that an entity set can only have a single navigation property targeting it that has Contains set to true. + /// + public static readonly ValidationRule EntitySetCanOnlyBeContainedByASingleNavigationProperty = + new ValidationRule( + (context, set) => + { + bool containmentFound = false; + foreach (IEdmNavigationSource navigationSource in set.Container.Elements.OfType()) + { + foreach (IEdmNavigationPropertyBinding binding in navigationSource.NavigationPropertyBindings) + { + IEdmNavigationProperty property = binding.NavigationProperty; + + if (binding.Target == set && property.ContainsTarget) + { + if (containmentFound) + { + context.AddError( + set.Location(), + EdmErrorCode.EntitySetCanOnlyBeContainedByASingleNavigationProperty, + Strings.EdmModel_Validator_Semantic_EntitySetCanOnlyBeContainedByASingleNavigationProperty(set.Container.FullName() + "." + set.Name)); + } + + containmentFound = true; + } + } + } + }); + + /// + /// Validates that if a navigation property is traversed to another entity set/singleton, and then the navigation properties partner is traversed, the destination will be the source entity set/singleton. + /// + public static readonly ValidationRule NavigationMappingMustBeBidirectional = + new ValidationRule( + (context, navigationSource) => + { + foreach (IEdmNavigationPropertyBinding binding in navigationSource.NavigationPropertyBindings) + { + IEdmNavigationProperty property = binding.NavigationProperty; + if (property.Partner == null || property.IsBad()) + { + continue; + } + + IEdmNavigationSource opposingNavigationSource = binding.Target.FindNavigationTarget(property.Partner, new EdmPathExpression(property.Partner.Name)); + + if (opposingNavigationSource == null || opposingNavigationSource is IEdmUnknownEntitySet || opposingNavigationSource is IEdmContainedEntitySet) + { + continue; + } + + if (opposingNavigationSource != navigationSource && property.Partner.DeclaringEntityType().FindProperty(property.Partner.Name) == property.Partner) + { + context.AddError( + navigationSource.Location(), + EdmErrorCode.NavigationMappingMustBeBidirectional, + Strings.EdmModel_Validator_Semantic_NavigationMappingMustBeBidirectional(navigationSource.Name, property.Name)); + } + } + }); + + /// + /// Validates that if a navigation property mapping is of recursive containment, the mapping points back to the source entity set. + /// + public static readonly ValidationRule EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet = + new ValidationRule( + (context, set) => + { + foreach (IEdmNavigationPropertyBinding mapping in set.NavigationPropertyBindings) + { + if (mapping.NavigationProperty.ContainsTarget && + mapping.NavigationProperty.DeclaringType.IsOrInheritsFrom(mapping.NavigationProperty.ToEntityType()) && + mapping.Target != set) + { + context.AddError( + set.Location(), + EdmErrorCode.EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet, + Strings.EdmModel_Validator_Semantic_EntitySetRecursiveNavigationPropertyMappingsMustPointBackToSourceEntitySet(mapping.NavigationProperty, set.Name)); + } + } + }); + + /// + /// Validates that the type of entity set is collection of entity type. + /// + public static readonly ValidationRule EntitySetTypeMustBeCollectionOfEntityType = + new ValidationRule( + (context, entitySet) => + { + bool isCollectionOfEntityType = false; + IEdmCollectionType collectionType = entitySet.Type as IEdmCollectionType; + + if (collectionType != null) + { + isCollectionOfEntityType = collectionType.ElementType != null && collectionType.ElementType.Definition is IEdmEntityType; + } + + if (!isCollectionOfEntityType) + { + string errorMessage = Strings.EdmModel_Validator_Semantic_EntitySetTypeMustBeCollectionOfEntityType(entitySet.Type.FullTypeName(), entitySet.Name); + + context.AddError( + entitySet.Location(), + EdmErrorCode.EntitySetTypeMustBeCollectionOfEntityType, + errorMessage); + } + }); + + /// + /// Validates that the type of an entity set cannot be Edm.EntityType. + /// + public static readonly ValidationRule EntitySetTypeCannotBeEdmEntityType = + new ValidationRule( + (context, entitySet) => + { + if (entitySet.Type.AsElementType() == EdmCoreModelEntityType.Instance) + { + context.AddError( + entitySet.Location(), + EdmErrorCode.EntityTypeOfEntitySetCannotBeEdmEntityType, + Strings.EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfEntitySet(entitySet.Name)); + } + }); + #endregion + + #region IEdmSingelton + + /// + /// Validates that the type of singleton is entity type. + /// + public static readonly ValidationRule SingletonTypeMustBeEntityType = + new ValidationRule( + (context, singleton) => + { + if (!(singleton.Type is IEdmEntityType)) + { + string errorMessage = + Strings.EdmModel_Validator_Semantic_SingletonTypeMustBeEntityType( + singleton.Type.FullTypeName(), singleton.Name); + + context.AddError( + singleton.Location(), + EdmErrorCode.SingletonTypeMustBeEntityType, + errorMessage); + } + }); + + /// + /// Validates that the type of singleton cannot be Edm.EntityType. + /// + public static readonly ValidationRule SingletonTypeCannotBeEdmEntityType = + new ValidationRule( + (context, singleton) => + { + if (singleton.Type == EdmCoreModelEntityType.Instance) + { + context.AddError( + singleton.Location(), + EdmErrorCode.EntityTypeOfSingletonCannotBeEdmEntityType, + Strings.EdmModel_Validator_Semantic_EdmEntityTypeCannotBeTypeOfSingleton(singleton.Name)); + } + }); + #endregion + + #region IEdmStructuredType + + /// + /// Validates that a type does not have a property with the same name as that type. + /// + public static readonly ValidationRule StructuredTypeInvalidMemberNameMatchesTypeName = + new ValidationRule( + (context, structuredType) => + { + IEdmSchemaType schemaType = structuredType as IEdmSchemaType; + if (schemaType != null) + { + var properties = structuredType.Properties().ToList(); + if (properties.Count > 0) + { + foreach (var property in properties) + { + if (property != null) + { + if (property.Name.EqualsOrdinal(schemaType.Name)) + { + context.AddError( + property.Location(), + EdmErrorCode.BadProperty, + Strings.EdmModel_Validator_Semantic_InvalidMemberNameMatchesTypeName(property.Name)); + } + } + } + } + } + }); + + /// + /// Validates that there are not duplicate properties in a type. + /// + public static readonly ValidationRule StructuredTypePropertyNameAlreadyDefined = + new ValidationRule( + (context, structuredType) => + { + HashSetInternal propertyNames = new HashSetInternal(); + foreach (var property in structuredType.Properties()) + { + // We only want to report the properties that are declared in this type. Otherwise properties will get reported multiple times due to inheritance. + if (property != null) + { + ValidationHelper.AddMemberNameToHashSet( + property, + propertyNames, + context, + EdmErrorCode.AlreadyDefined, + Strings.EdmModel_Validator_Semantic_PropertyNameAlreadyDefined(property.Name), + /*supressError*/ !structuredType.DeclaredProperties.Contains(property)); + } + } + }); + + /// + /// Validates that the base type of a complex type is complex, and the base type of an entity type is an entity. + /// + public static readonly ValidationRule StructuredTypeBaseTypeMustBeSameKindAsDerivedKind = + new ValidationRule( + (context, structuredType) => + { + // We can either have 2 rules (entity and complex) or have one rule. I'm choosing the latter. + if (structuredType is IEdmSchemaType) + { + if (structuredType.BaseType != null && structuredType.BaseType.TypeKind != structuredType.TypeKind) + { + context.AddError( + structuredType.Location(), + (structuredType.TypeKind == EdmTypeKind.Entity) ? EdmErrorCode.EntityMustHaveEntityBaseType : EdmErrorCode.ComplexTypeMustHaveComplexBaseType, + Strings.EdmModel_Validator_Semantic_BaseTypeMustHaveSameTypeKind); + } + } + }); + + /// + /// Validates that the base type of a structured type cannot be Edm.EntityType or Edm.ComplexType. + /// + public static readonly ValidationRule StructuredTypeBaseTypeCannotBeAbstractType = + new ValidationRule( + (context, structuredType) => + { + if (structuredType.BaseType != null && + (structuredType.BaseType == EdmCoreModelComplexType.Instance || structuredType.BaseType == EdmCoreModelEntityType.Instance) && + !context.IsBad(structuredType.BaseType)) + { + string typeKind = structuredType.TypeKind == EdmTypeKind.Entity ? "entity" : "complex"; + context.AddError( + structuredType.Location(), + (structuredType.TypeKind == EdmTypeKind.Entity) + ? EdmErrorCode.EntityTypeBaseTypeCannotBeEdmEntityType + : EdmErrorCode.ComplexTypeBaseTypeCannotBeEdmComplexType, + Strings.EdmModel_Validator_Semantic_StructuredTypeBaseTypeCannotBeAbstractType( + structuredType.BaseType.FullTypeName(), typeKind, structuredType.FullTypeName())); + } + }); + + /// + /// Validates that the base type of a structured type can be found from the model being validated. + /// + public static readonly ValidationRule StructuredTypeInaccessibleBaseType = + new ValidationRule( + (context, structuredType) => + { + IEdmSchemaType schemaBaseType = structuredType.BaseType as IEdmSchemaType; + if (schemaBaseType != null && !context.IsBad(schemaBaseType)) + { + CheckForUnreacheableTypeError(context, schemaBaseType, structuredType.Location()); + } + }); + + /// + /// Validates that the declaring type of a property contains that property. + /// + public static readonly ValidationRule StructuredTypePropertiesDeclaringTypeMustBeCorrect = + new ValidationRule( + (context, structuredType) => + { + foreach (var property in structuredType.DeclaredProperties) + { + if (property != null) + { + if (!property.DeclaringType.Equals(structuredType)) + { + context.AddError( + property.Location(), + EdmErrorCode.DeclaringTypeMustBeCorrect, + Strings.EdmModel_Validator_Semantic_DeclaringTypeMustBeCorrect(property.Name)); + } + } + } + }); + + #endregion + + #region IEdmEnumType + + /// + /// Validates that there are not duplicate enum members in an enum. + /// + public static readonly ValidationRule EnumTypeEnumMemberNameAlreadyDefined = + new ValidationRule( + (context, enumType) => + { + HashSetInternal memberNames = new HashSetInternal(); + foreach (var member in enumType.Members) + { + // We only want to report the properties that are declared in this type. Otherwise properties will get reported multiple times due to inheritance. + if (member != null) + { + ValidationHelper.AddMemberNameToHashSet( + member, + memberNames, + context, + EdmErrorCode.AlreadyDefined, + Strings.EdmModel_Validator_Semantic_EnumMemberNameAlreadyDefined(member.Name), + /*supressError*/ false); + } + } + }); + + /// + /// Raises an error if the underlying type of an enum type is not an integer type. + /// + public static readonly ValidationRule EnumMustHaveIntegerUnderlyingType = + new ValidationRule( + (context, enumType) => + { + if (!enumType.UnderlyingType.PrimitiveKind.IsIntegral() && !context.IsBad(enumType.UnderlyingType)) + { + context.AddError( + enumType.Location(), + EdmErrorCode.EnumMustHaveIntegerUnderlyingType, + Strings.EdmModel_Validator_Semantic_EnumMustHaveIntegralUnderlyingType(enumType.FullName())); + } + }); + + /// + /// Validates that the underlying type of a type definition cannot be Edm.PrimitiveType. + /// + public static readonly ValidationRule EnumUnderlyingTypeCannotBeEdmPrimitiveType = + new ValidationRule( + (context, enumType) => + { + if (enumType.UnderlyingType.PrimitiveKind == EdmPrimitiveTypeKind.PrimitiveType && !context.IsBad(enumType.UnderlyingType)) + { + context.AddError( + enumType.Location(), + EdmErrorCode.TypeDefinitionUnderlyingTypeCannotBeEdmPrimitiveType, + Strings.EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsUnderlyingType("enumeration", enumType.FullName())); + } + }); + #endregion + + #region IEdmEnumMember + + /// + /// Raises an error if the type of an enum member doesn't match the underlying type of the enum it belongs to. + /// + public static readonly ValidationRule EnumMemberValueMustHaveSameTypeAsUnderlyingType = + new ValidationRule( + (context, enumMember) => + { + IEnumerable discoveredErrors; + if (!context.IsBad(enumMember.DeclaringType) && + !context.IsBad(enumMember.DeclaringType.UnderlyingType)) + { + IEdmPrimitiveValue enumValue = new EdmIntegerConstant(enumMember.Value.Value); + + if (!enumValue.TryCastPrimitiveAsType(enumMember.DeclaringType.UnderlyingType.GetPrimitiveTypeReference(false), out discoveredErrors)) + { + context.AddError( + enumMember.Location(), + EdmErrorCode.EnumMemberValueOutOfRange, + Strings.EdmModel_Validator_Semantic_EnumMemberValueOutOfRange(enumMember.Name)); + } + } + }); + + #endregion + + #region IEdmTypeDefintion + + /// + /// Validates that the underlying type of a type definition cannot be Edm.PrimitiveType. + /// + public static readonly ValidationRule TypeDefinitionUnderlyingTypeCannotBeEdmPrimitiveType = + new ValidationRule( + (context, typeDefinition) => + { + if (typeDefinition.UnderlyingType == EdmCoreModel.Instance.GetPrimitiveType() && !context.IsBad(typeDefinition.UnderlyingType)) + { + context.AddError( + typeDefinition.Location(), + EdmErrorCode.TypeDefinitionUnderlyingTypeCannotBeEdmPrimitiveType, + Strings.EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsUnderlyingType("type definition", typeDefinition.FullName())); + } + }); + #endregion + + #region IEdmEntityType + + /// + /// Validates that there are not duplicate properties in an entity key. + /// + public static readonly ValidationRule EntityTypeDuplicatePropertyNameSpecifiedInEntityKey = + new ValidationRule( + (context, entityType) => + { + if (entityType.DeclaredKey != null) + { + HashSetInternal keyPropertyNameList = new HashSetInternal(); + foreach (var item in entityType.DeclaredKey) + { + ValidationHelper.AddMemberNameToHashSet( + item, + keyPropertyNameList, + context, + EdmErrorCode.DuplicatePropertySpecifiedInEntityKey, + Strings.EdmModel_Validator_Semantic_DuplicatePropertyNameSpecifiedInEntityKey(entityType.Name, item.Name), + /*supressError*/ false); + } + } + }); + + /// + /// Validates that no part of an entity key is nullable. + /// + public static readonly ValidationRule EntityTypeInvalidKeyNullablePart = + new ValidationRule( + (context, entityType) => + { + if (entityType.Key() != null) + { + foreach (var key in entityType.Key()) + { + if (key.Type.IsPrimitive()) + { + if (key.Type.IsNullable) + { + context.AddError( + key.Location(), + EdmErrorCode.InvalidKey, + Strings.EdmModel_Validator_Semantic_InvalidKeyNullablePart(key.Name, entityType.Name)); + } + } + } + } + }); + + /// + /// Validates that all parts of an entity key are scalar. + /// + public static readonly ValidationRule EntityTypeEntityKeyMustBeScalar = + new ValidationRule( + (context, entityType) => + { + if (entityType.Key() != null) + { + foreach (IEdmStructuralProperty key in entityType.Key()) + { + if (!key.Type.IsPrimitive() && !key.Type.IsEnum() && !context.IsBad(key)) + { + context.AddError( + key.Location(), + EdmErrorCode.EntityKeyMustBeScalar, + Strings.EdmModel_Validator_Semantic_EntityKeyMustBeScalar(key.Name, entityType.Name)); + } + } + } + }); + + /// + /// Validates that a key is not defined if there is already a key in the base type. + /// + public static readonly ValidationRule EntityTypeInvalidKeyKeyDefinedInBaseClass = + new ValidationRule( + (context, entityType) => + { + if (entityType.BaseType != null && + entityType.DeclaredKey != null && + entityType.BaseType.TypeKind == EdmTypeKind.Entity && + entityType.BaseEntityType().DeclaredKey != null) + { + context.AddError( + entityType.Location(), + EdmErrorCode.InvalidKey, + Strings.EdmModel_Validator_Semantic_InvalidKeyKeyDefinedInBaseClass(entityType.Name, entityType.BaseEntityType().Name)); + } + }); + + /// + /// Validates that the entity type has a key. + /// + public static readonly ValidationRule EntityTypeKeyMissingOnEntityType = + new ValidationRule( + (context, entityType) => + { + // Abstract entity type can have no key. + var keys = entityType.Key(); + if ((keys == null || !keys.Any()) && entityType.BaseType == null && !entityType.IsAbstract) + { + context.AddError( + entityType.Location(), + EdmErrorCode.KeyMissingOnEntityType, + Strings.EdmModel_Validator_Semantic_KeyMissingOnEntityType(entityType.Name)); + } + }); + + /// + /// Validates that all properties in the key of an entity blong to that entity. + /// + public static readonly ValidationRule EntityTypeKeyPropertyMustBelongToEntity = + new ValidationRule( + (context, entityType) => + { + if (entityType.DeclaredKey != null) + { + foreach (IEdmStructuralProperty key in entityType.DeclaredKey) + { + // Key must be one of the declared properties. + if (key.DeclaringType != entityType && !context.IsBad(key)) + { + context.AddError( + entityType.Location(), + EdmErrorCode.KeyPropertyMustBelongToEntity, + Strings.EdmModel_Validator_Semantic_KeyPropertyMustBelongToEntity(key.Name, entityType.Name)); + } + } + } + }); + + /// + /// Validates that Edm.PrimitiveType cannot be used as the type of a key property of an entity type. + /// + public static readonly ValidationRule EntityTypeKeyTypeCannotBeEdmPrimitiveType = + new ValidationRule( + (context, entityType) => + { + if (entityType.DeclaredKey != null) + { + foreach (IEdmStructuralProperty key in entityType.DeclaredKey) + { + if (key.Type.Definition == EdmCoreModel.Instance.GetPrimitiveType()) + { + context.AddError( + entityType.Location(), + EdmErrorCode.KeyPropertyTypeCannotBeEdmPrimitiveType, + Strings.EdmModel_Validator_Semantic_EdmPrimitiveTypeCannotBeUsedAsTypeOfKey( + key.Name, entityType.FullName())); + } + } + } + }); + + /// + /// Validates that the escape functions are unique. + /// + public static readonly ValidationRule EntityTypeBoundEscapeFunctionMustBeUnique = + new ValidationRule( + (context, entityType) => + { + IList composableEscapeFunctions = new List(); + IList nonComposableEscapeFunctions = new List(); + foreach (var function in context.Model.FindBoundOperations(entityType).Where(o => o.IsFunction()).OfType()) + { + if (!context.Model.IsUrlEscapeFunction(function)) + { + continue; + } + + if (function.IsComposable) + { + composableEscapeFunctions.Add(function); + } + else + { + nonComposableEscapeFunctions.Add(function); + } + } + + if (composableEscapeFunctions.Count() > 1) + { + string escapeFunctionString = String.Join(",", composableEscapeFunctions.Select(c => c.Name).ToArray()); + context.AddError( + entityType.Location(), + EdmErrorCode.EntityComposableBoundEscapeFunctionMustBeLessOne, + Strings.EdmModel_Validator_Semantic_EntityComposableBoundEscapeFunctionMustBeLessOne(entityType.FullName(), escapeFunctionString)); + } + + if (nonComposableEscapeFunctions.Count() > 1) + { + string escapeFunctionString = String.Join(",", nonComposableEscapeFunctions.Select(c => c.Name).ToArray()); + context.AddError( + entityType.Location(), + EdmErrorCode.EntityNoncomposableBoundEscapeFunctionMustBeLessOne, + Strings.EdmModel_Validator_Semantic_EntityNoncomposableBoundEscapeFunctionMustBeLessOne(entityType.FullName(), escapeFunctionString)); + } + }); + #endregion + + #region IEdmEntityReferenceType + + /// + /// Validates that the entity type wrapped in this entity reference can be found through the model being validated. + /// + public static readonly ValidationRule EntityReferenceTypeInaccessibleEntityType = + new ValidationRule( + (context, entityReferenceType) => + { + if (!context.IsBad(entityReferenceType.EntityType)) + { + CheckForUnreacheableTypeError(context, entityReferenceType.EntityType, entityReferenceType.Location()); + } + }); + + #endregion + + #region IEdmType + + /// + /// A type without other errors must not have kind of none. + /// + public static readonly ValidationRule TypeMustNotHaveKindOfNone = + new ValidationRule( + (context, type) => + { + if (type.TypeKind == EdmTypeKind.None && !context.IsBad(type)) + { + context.AddError( + type.Location(), + EdmErrorCode.TypeMustNotHaveKindOfNone, + Strings.EdmModel_Validator_Semantic_TypeMustNotHaveKindOfNone); + } + }); + #endregion + + #region IEdmPrimitiveType + + /// + /// A primtive type without other errors must not have kind of none. + /// + public static readonly ValidationRule PrimitiveTypeMustNotHaveKindOfNone = + new ValidationRule( + (context, type) => + { + if (type.PrimitiveKind == EdmPrimitiveTypeKind.None && !context.IsBad(type)) + { + context.AddError( + type.Location(), + EdmErrorCode.PrimitiveTypeMustNotHaveKindOfNone, + Strings.EdmModel_Validator_Semantic_PrimitiveTypeMustNotHaveKindOfNone(type.FullName())); + } + }); + + #endregion + + #region IEdmComplexType + + /// + /// Validates that a open complex type can not have closed derived complex type. + /// + public static readonly ValidationRule OpenComplexTypeCannotHaveClosedDerivedComplexType = + new ValidationRule( + (context, complexType) => + { + if (complexType.BaseType != null && complexType.BaseType.IsOpen && !complexType.IsOpen) + { + context.AddError( + complexType.Location(), + EdmErrorCode.InvalidAbstractComplexType, + Strings.EdmModel_Validator_Semantic_BaseTypeOfOpenTypeMustBeOpen(complexType.FullName())); + } + }); + + #endregion + + #region IEdmStructuralProperty + + /// + /// Validates that the property is of an allowed type. + /// + public static readonly ValidationRule StructuralPropertyInvalidPropertyType = + new ValidationRule( + (context, property) => + { + IEdmType validatedType; + if (property.Type.IsCollection()) + { + validatedType = property.Type.AsCollection().ElementType().Definition; + } + else + { + validatedType = property.Type.Definition; + } + + if (validatedType.TypeKind != EdmTypeKind.Primitive && validatedType.TypeKind != EdmTypeKind.Enum + && validatedType.TypeKind != EdmTypeKind.Untyped && validatedType.TypeKind != EdmTypeKind.Complex + && validatedType.TypeKind != EdmTypeKind.Path + && validatedType.TypeKind != EdmTypeKind.TypeDefinition + && !context.IsBad(validatedType)) + { + context.AddError(property.Location(), EdmErrorCode.InvalidPropertyType, Strings.EdmModel_Validator_Semantic_InvalidPropertyType(property.Type.TypeKind().ToString())); + } + }); + + #endregion + + #region IEdmNavigationProperty + + /// + /// Validates that only one end of an association has an OnDelete operation. + /// + public static readonly ValidationRule NavigationPropertyInvalidOperationMultipleEndsInAssociatedNavigationProperties = + new ValidationRule( + (context, navigationProperty) => + { + if (navigationProperty.OnDelete != EdmOnDeleteAction.None && navigationProperty.Partner != null && navigationProperty.Partner.OnDelete != EdmOnDeleteAction.None) + { + context.AddError( + navigationProperty.Location(), + EdmErrorCode.InvalidAction, + Strings.EdmModel_Validator_Semantic_InvalidOperationMultipleEndsInAssociation); + } + }); + + /// + /// Validates that the type of a navigation property corresponds to the other end of the association and the multiplicity of the other end. + /// + public static readonly ValidationRule NavigationPropertyCorrectType = + new ValidationRule( + (context, property) => + { + // Validates that target must be an entity type. + if (property.ToEntityType() == null) + { + context.AddError( + property.Location(), + EdmErrorCode.InvalidNavigationPropertyType, + Strings.EdmModel_Validator_Semantic_InvalidNavigationPropertyType(property.Name)); + return; + } + + if (property.Partner == null + || property.Partner is BadNavigationProperty + || property.Partner.DeclaringType is IEdmComplexType) + { + return; + } + + if (property.ToEntityType() != property.Partner.DeclaringEntityType()) + { + context.AddError( + property.Location(), + EdmErrorCode.InvalidNavigationPropertyType, + Strings.EdmModel_Validator_Semantic_InvalidNavigationPropertyType(property.Name)); + } + }); + + /// + /// Validates that the dependent properties of a navigation property contain no duplicates. + /// + public static readonly ValidationRule NavigationPropertyDuplicateDependentProperty = + new ValidationRule( + (context, navigationProperty) => + { + IEnumerable dependentProperties = navigationProperty.DependentProperties(); + if (dependentProperties != null) + { + HashSetInternal propertyNames = new HashSetInternal(); + foreach (var property in navigationProperty.DependentProperties()) + { + if (property != null) + { + ValidationHelper.AddMemberNameToHashSet( + property, + propertyNames, + context, + EdmErrorCode.DuplicateDependentProperty, + Strings.EdmModel_Validator_Semantic_DuplicateDependentProperty(property.Name, navigationProperty.Name), + /*supressError*/ false); + } + } + } + }); + + /// + /// Validates multiplicity of the principal end: + /// 0..1 - if some dependent properties are nullable, + /// 1 - if some dependent properties are not nullable. + /// * - not allowed. + /// + public static readonly ValidationRule NavigationPropertyPrincipalEndMultiplicity = + new ValidationRule( + (context, navigationProperty) => + { + /* + Dependent properties | | | | + nullable? | All | Mixed | None | + ------------------------------------------------- + 0..1 | + | + | error | + ------------------------------------------------- + 1 | error | + | + | + ------------------------------------------------- + * | error | error | error | + ------------------------------------------------- + */ + + IEnumerable dependentProperties = navigationProperty.DependentProperties(); + if (dependentProperties != null) + { + if (ValidationHelper.AllPropertiesAreNullable(dependentProperties)) + { + if (navigationProperty.TargetMultiplicity() != EdmMultiplicity.ZeroOrOne) + { + context.AddError( + navigationProperty.Location(), + EdmErrorCode.InvalidMultiplicityOfPrincipalEnd, + Strings.EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNullable(navigationProperty.Name)); + } + } + else if (!ValidationHelper.HasNullableProperty(dependentProperties)) + { + if (navigationProperty.TargetMultiplicity() != EdmMultiplicity.One) + { + context.AddError( + navigationProperty.Location(), + EdmErrorCode.InvalidMultiplicityOfPrincipalEnd, + Strings.EdmModel_Validator_Semantic_InvalidMultiplicityOfPrincipalEndDependentPropertiesAllNonnullable(navigationProperty.Name)); + } + } + else + { + if (navigationProperty.TargetMultiplicity() != EdmMultiplicity.One && + navigationProperty.TargetMultiplicity() != EdmMultiplicity.ZeroOrOne) + { + context.AddError( + navigationProperty.Location(), + EdmErrorCode.InvalidMultiplicityOfPrincipalEnd, + Strings.EdmModel_Validator_Semantic_NavigationPropertyPrincipalEndMultiplicityUpperBoundMustBeOne(navigationProperty.Name)); + } + } + } + }); + + /// + /// Validates that if the dependent properties are equivalent to the key of the dependent entity, the multiplicity of the dependent entity cannot be 1 + /// Validates multiplicity of the dependent entity according to the following rules: + /// 0..1, 1 - if dependent properties represent the dependent entity key. + /// * - if dependent properties don't represent the dependent entity key. + /// + public static readonly ValidationRule NavigationPropertyDependentEndMultiplicity = + new ValidationRule( + (context, navigationProperty) => + { + // without a partner, the multiplicity of the source end cannot be determined. + if (navigationProperty.Partner == null) + { + return; + } + + IEnumerable dependentProperties = navigationProperty.DependentProperties(); + if (dependentProperties != null) + { + if (ValidationHelper.PropertySetsAreEquivalent(navigationProperty.DeclaringEntityType().Key(), dependentProperties)) + { + if (navigationProperty.Type.IsCollection()) + { + context.AddError( + navigationProperty.Location(), + EdmErrorCode.InvalidMultiplicityOfDependentEnd, + Strings.EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeZeroOneOrOne(navigationProperty.Name)); + } + } + else if (!navigationProperty.Partner.Type.IsCollection()) + { + context.AddError( + navigationProperty.Location(), + EdmErrorCode.InvalidMultiplicityOfDependentEnd, + Strings.EdmModel_Validator_Semantic_InvalidMultiplicityOfDependentEndMustBeMany(navigationProperty.Name)); + } + } + }); + + /// + /// Validates that all dependent properties of a navigation property belong to the dependent entity type. + /// + public static readonly ValidationRule NavigationPropertyDependentPropertiesMustBelongToDependentEntity = + new ValidationRule( + (context, navigationProperty) => + { + IEnumerable dependentProperties = navigationProperty.DependentProperties(); + if (dependentProperties != null) + { + IEdmEntityType dependentEntity = navigationProperty.DeclaringEntityType(); + foreach (IEdmStructuralProperty dependantProperty in dependentProperties) + { + if (!context.IsBad(dependantProperty) && !dependantProperty.IsBad()) + { + var property = dependentEntity.FindProperty(dependantProperty.Name); + + // If we can't find the property by name, or we find a good property but it's not our dependent property + if (property != dependantProperty) + { + context.AddError( + navigationProperty.Location(), + EdmErrorCode.DependentPropertiesMustBelongToDependentEntity, + Strings.EdmModel_Validator_Semantic_DependentPropertiesMustBelongToDependentEntity(dependantProperty.Name, dependentEntity.Name)); + } + } + } + } + }); + + /// + /// Validates that the navigation property does not have both a multiplicity of many and an OnDelete operation. + /// + public static readonly ValidationRule NavigationPropertyEndWithManyMultiplicityCannotHaveOperationsSpecified = + new ValidationRule( + (context, end) => + { + // If an end has a multiplicity of many, it cannot have any operation behaviour + if ( + end.Partner != null && + end.Partner.Type.IsCollection() && + end.OnDelete != EdmOnDeleteAction.None) + { + string errorMessage = Strings.EdmModel_Validator_Semantic_EndWithManyMultiplicityCannotHaveOperationsSpecified(end.Name); + + context.AddError( + end.Location(), + EdmErrorCode.EndWithManyMultiplicityCannotHaveOperationsSpecified, + errorMessage); + } + }); + + /// + /// Validates that the navigation property partner path, if exists, should be resolvable to a navigation property. + /// + public static readonly ValidationRule NavigationPropertyPartnerPathShouldBeResolvable = + new ValidationRule( + (context, property) => + { + var path = property.GetPartnerPath(); + if (path != null + && property.Type.Definition.AsElementType() is IEdmEntityType + && CsdlSemanticsNavigationProperty.ResolvePartnerPath( + (IEdmEntityType)property.Type.Definition.AsElementType(), path, context.Model) + == null) + { + context.AddError( + property.Location(), + EdmErrorCode.UnresolvedNavigationPropertyPartnerPath, + string.Format( + CultureInfo.CurrentCulture, + "Cannot resolve partner path for navigation property '{0}'.", + property.Name)); + } + }); + + /// + /// Validates that if a navigation property has = true and the target entity type is the same as + /// the declaring type of the property, then the multiplicity of the target of navigation is 0..1 or Many. + /// This depends on there being a targetting cycle. Because of the rule , we know that either this is always true, or there will be an error + /// + public static readonly ValidationRule NavigationPropertyWithRecursiveContainmentTargetMustBeOptional = + new ValidationRule( + (context, property) => + { + if (property.ContainsTarget && + property.DeclaringType.IsOrInheritsFrom(property.ToEntityType()) && + !(property.Type.IsCollection() || property.Type.IsNullable)) + { + context.AddError( + property.Location(), + EdmErrorCode.NavigationPropertyWithRecursiveContainmentTargetMustBeOptional, + Strings.EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional(property.Name)); + } + }); + + /// + /// Validates that if a navigation property has = true and the target entity type is the same as + /// the declaring type of the property, then the multiplicity of the source of navigation is Zero-Or-One. + /// This depends on there being a targetting cycle. Because of the rule , we know that either this is always true, or there will be an error + /// + public static readonly ValidationRule NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne = + new ValidationRule( + (context, property) => + { + if (property.Partner != null && + property.ContainsTarget && + property.DeclaringType.IsOrInheritsFrom(property.ToEntityType()) && + (property.Partner.Type.IsCollection() || !property.Partner.Type.IsNullable)) + { + context.AddError( + property.Location(), + EdmErrorCode.NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne, + Strings.EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne(property.Name)); + } + }); + + /// + /// Validates that if a navigation property has = true and the target entity type is defferent than + /// the declaring type of the property, then the multiplicity of the source of navigation is One. + /// + public static readonly ValidationRule NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne = + new ValidationRule( + (context, property) => + { + if (property.Partner != null && + property.ContainsTarget && + !property.DeclaringType.IsOrInheritsFrom(property.ToEntityType()) && + (property.Partner.Type.IsCollection() || property.Partner.Type.IsNullable)) + { + context.AddError( + property.Location(), + EdmErrorCode.NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne, + Strings.EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne(property.Name)); + } + }); + + /// + /// Validates that if an entity does not directly contain itself, it cannot contain itself through a containment loop. + /// + public static readonly ValidationRule NavigationPropertyEntityMustNotIndirectlyContainItself = + new ValidationRule( + (context, property) => + { + if (property.ContainsTarget && + !property.DeclaringType.IsOrInheritsFrom(property.ToEntityType())) + { + if (ValidationHelper.TypeIndirectlyContainsTarget(property.ToEntityType(), property.DeclaringEntityType(), new HashSetInternal(), context.Model)) + { + context.AddError( + property.Location(), + EdmErrorCode.NavigationPropertyEntityMustNotIndirectlyContainItself, + Strings.EdmModel_Validator_Semantic_NavigationPropertyEntityMustNotIndirectlyContainItself(property.Name)); + } + } + }); + + /// + /// Validates that the type of the navigation property cannot have path type property defined. + /// + public static readonly ValidationRule NavigationPropertyTypeCannotHavePathTypeProperty = + new ValidationRule( + (context, property) => + { + IEdmTypeReference propertyType = property.Type; + if (propertyType.IsCollection()) + { + propertyType = propertyType.AsCollection().ElementType(); + } + + IEdmStructuredType structuredType = propertyType.ToStructuredType(); + if (structuredType == null) + { + return; + } + + IList visited = new List(); + if (HasPathTypeProperty(structuredType, visited)) + { + string errorMessage = Strings + .EdmModel_Validator_Semantic_TypeOfNavigationPropertyCannotHavePathProperty(property.Type.FullName(), property.Name, property.DeclaringType.FullTypeName()); + + context.AddError( + property.Location(), + EdmErrorCode.TypeOfNavigationPropertyCannotHavePathProperty, + errorMessage); + } + }); + + /// + /// Validates that each pair of properties between the dependent properties and the principal properties are of the same type. + /// + public static readonly ValidationRule NavigationPropertyTypeMismatchRelationshipConstraint = + new ValidationRule( + (context, navigationProperty) => + { + IEnumerable dependentProperties = navigationProperty.DependentProperties(); + if (dependentProperties != null) + { + int dependentPropertiesCount = dependentProperties.Count(); + IEdmEntityType principalEntityType = navigationProperty.ToEntityType(); + IEnumerable principalProperties = navigationProperty.PrincipalProperties(); + if (dependentPropertiesCount == principalProperties.Count()) + { + for (int i = 0; i < dependentPropertiesCount; i++) + { + var dependentType = dependentProperties.ElementAtOrDefault(i).Type.Definition; + var principalType = principalProperties.ElementAtOrDefault(i).Type.Definition; + if (!(dependentType is BadType) && !(principalType is BadType) && !dependentType.IsEquivalentTo(principalType)) + { + string errorMessage = Strings.EdmModel_Validator_Semantic_TypeMismatchRelationshipConstraint(navigationProperty.DependentProperties().ToList()[i].Name, navigationProperty.DeclaringEntityType().FullName(), principalProperties.ToList()[i].Name, principalEntityType.Name, "Fred"); + + context.AddError(navigationProperty.Location(), EdmErrorCode.TypeMismatchRelationshipConstraint, errorMessage); + } + } + } + } + }); + + #endregion + + #region IEdmProperty + + /// + /// A property without other errors must not have kind of none. + /// + public static readonly ValidationRule PropertyMustNotHaveKindOfNone = + new ValidationRule( + (context, property) => + { + if (property.PropertyKind == EdmPropertyKind.None && !context.IsBad(property)) + { + context.AddError( + property.Location(), + EdmErrorCode.PropertyMustNotHaveKindOfNone, + Strings.EdmModel_Validator_Semantic_PropertyMustNotHaveKindOfNone(property.Name)); + } + }); + + /// + /// Collection(Edm.PrimitiveType) and Collection(Edm.ComplexType) cannot be used as the type of a property. + /// + public static readonly ValidationRule PropertyTypeCannotBeCollectionOfAbstractType = + new ValidationRule( + (context, property) => + { + if (property.Type.IsCollection()) + { + IEdmTypeReference elementType = property.Type.AsCollection().ElementType(); + if (elementType.Definition == EdmCoreModelComplexType.Instance || + elementType.Definition == EdmCoreModel.Instance.GetPrimitiveType()) + { + context.AddError( + property.Location(), + EdmErrorCode.PropertyTypeCannotBeCollectionOfAbstractType, + Strings.EdmModel_Validator_Semantic_PropertyTypeCannotBeCollectionOfAbstractType( + property.Type.FullName(), property.Name)); + } + } + }); + + #endregion + + #region IEdmOperationImport + + /// + /// Validates that if an operation import cannot import an operation that is bound. + /// + public static readonly ValidationRule OperationImportCannotImportBoundOperation = + new ValidationRule( + (context, operationImport) => + { + if (operationImport.Operation.IsBound) + { + context.AddError( + operationImport.Location(), + EdmErrorCode.OperationImportCannotImportBoundOperation, + Strings.EdmModel_Validator_Semantic_OperationImportCannotImportBoundOperation(operationImport.Name, operationImport.Operation.Name)); + } + }); + + /// + /// Validates that the entity set of a operation import is defined using a path or an entity set reference expression. + /// + public static readonly ValidationRule OperationImportEntitySetExpressionIsInvalid = + new ValidationRule( + (context, operationImport) => + { + if (operationImport.EntitySet != null) + { + if (operationImport.EntitySet.ExpressionKind != EdmExpressionKind.Path) + { + context.AddError( + operationImport.Location(), + EdmErrorCode.OperationImportEntitySetExpressionIsInvalid, + Strings.EdmModel_Validator_Semantic_OperationImportEntitySetExpressionKindIsInvalid(operationImport.Name, operationImport.EntitySet.ExpressionKind)); + } + else + { + IEdmEntitySetBase entitySet; + + if (!operationImport.TryGetStaticEntitySet(context.Model, out entitySet)) + { + context.AddError( + operationImport.Location(), + EdmErrorCode.OperationImportEntitySetExpressionIsInvalid, + Strings.EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid(operationImport.Name)); + } + else + { + // Checking isbad so that two errors are not returned when a csdl document is parsed, one of it being unresolved another + // for the error below. + if (!context.IsBad(entitySet)) + { + IEdmEntitySet foundEntitySet = operationImport.Container.FindEntitySetExtended(entitySet.Name); + if (foundEntitySet == null) + { + context.AddError( + operationImport.Location(), + EdmErrorCode.OperationImportEntitySetExpressionIsInvalid, + Strings.EdmModel_Validator_Semantic_OperationImportEntitySetExpressionIsInvalid(operationImport.Name)); + } + } + } + } + } + }); + + /// + /// Validates that the return type of a operation import must match the type of the entity set of the function. + /// + public static readonly ValidationRule OperationImportEntityTypeDoesNotMatchEntitySet = + new ValidationRule( + (context, operationImport) => + { + if (operationImport.EntitySet != null && operationImport.Operation.ReturnType != null) + { + IEdmTypeReference elementType = operationImport.Operation.ReturnType.IsCollection() ? operationImport.Operation.ReturnType.AsCollection().ElementType() : operationImport.Operation.ReturnType; + if (elementType.IsEntity()) + { + IEdmEntityType returnedEntityType = elementType.AsEntity().EntityDefinition(); + + IEdmEntitySetBase entitySet; + IEdmOperationParameter parameter; + Dictionary path; + IEnumerable errors; + if (operationImport.TryGetStaticEntitySet(context.Model, out entitySet)) + { + IEdmEntityType entitySetElementType = entitySet.EntityType(); + if (!returnedEntityType.IsOrInheritsFrom(entitySetElementType) && !context.IsBad(returnedEntityType) && !context.IsBad(entitySet) && !context.IsBad(entitySetElementType)) + { + string errorMessage = Strings.EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet( + operationImport.Name, + returnedEntityType.FullName(), + entitySet.Name); + + context.AddError( + operationImport.Location(), + EdmErrorCode.OperationImportEntityTypeDoesNotMatchEntitySet, + errorMessage); + } + } + else if (operationImport.TryGetRelativeEntitySetPath(context.Model, out parameter, out path, out errors)) + { + List pathList = path.Select(s => s.Key).ToList(); + IEdmTypeReference relativePathType = pathList.Count == 0 ? parameter.Type : path.Last().Key.Type; + IEdmTypeReference relativePathElementType = relativePathType.IsCollection() ? relativePathType.AsCollection().ElementType() : relativePathType; + if (!returnedEntityType.IsOrInheritsFrom(relativePathElementType.Definition) && !context.IsBad(returnedEntityType) && !context.IsBad(relativePathElementType.Definition)) + { + context.AddError( + operationImport.Location(), + EdmErrorCode.OperationImportEntityTypeDoesNotMatchEntitySet, + Strings.EdmModel_Validator_Semantic_OperationImportEntityTypeDoesNotMatchEntitySet2(operationImport.Name, elementType.FullName())); + } + } + + // The case when all try gets fail is caught by the FunctionImportEntitySetExpressionIsInvalid rule. + } + else if (!context.IsBad(elementType.Definition)) + { + context.AddError( + operationImport.Location(), + EdmErrorCode.OperationImportSpecifiesEntitySetButDoesNotReturnEntityType, + Strings.EdmModel_Validator_Semantic_OperationImportSpecifiesEntitySetButNotEntityType(operationImport.Name)); + } + } + }); + + #endregion + + #region IEdmFunctionImport + + /// + /// Validates that the function import included in service document must not have parameters. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly ValidationRule FunctionImportWithParameterShouldNotBeIncludedInServiceDocument = + new ValidationRule( + (context, functionImport) => + { + if (functionImport.IncludeInServiceDocument && functionImport.Function.Parameters.Any()) + { + context.AddError( + functionImport.Location(), + EdmErrorCode.FunctionImportWithParameterShouldNotBeIncludedInServiceDocument, + Strings.EdmModel_Validator_Semantic_FunctionImportWithParameterShouldNotBeIncludedInServiceDocument(functionImport.Name)); + } + }); + + #endregion + + #region IEdmFunction + + /// + /// Validates that if a function, it must have a return type. + /// + public static readonly ValidationRule FunctionMustHaveReturnType = + new ValidationRule( + (context, function) => + { + if (function.ReturnType == null) + { + context.AddError( + function.Location(), + EdmErrorCode.FunctionMustHaveReturnType, + Strings.EdmModel_Validator_Semantic_FunctionMustHaveReturnType(function.Name)); + } + }); + + /// + /// Validates that if a bound function has Org.OData.Community.V1.UrlEscapeFunction annotation, it must be bound function. + /// + public static readonly ValidationRule FunctionWithUrlEscapeFunctionMustBeBound = + new ValidationRule( + (context, function) => + { + if (!context.Model.IsUrlEscapeFunction(function)) + { + return; + } + + // The UrlEscape function should be bound function. + if (!function.IsBound) + { + context.AddError( + function.Location(), + EdmErrorCode.UrlEscapeFunctionMustBeBoundFunction, + Strings.EdmModel_Validator_Semantic_UrlEscapeFunctionMustBoundFunction(function.Name)); + } + }); + + /// + /// Validates that if a bound function has Org.OData.Community.V1.UrlEscapeFunction annotation, it must have only one string parameter. + /// + public static readonly ValidationRule FunctionWithUrlEscapeFunctionMustHaveOneStringParameter = + new ValidationRule( + (context, function) => + { + if (!context.Model.IsUrlEscapeFunction(function)) + { + return; + } + + // The UrlEscape function should have and only have two parameters, the non-binding parameter type should be "Edm.String" + if (function.Parameters == null || + function.Parameters.Count() != 2 || + !function.Parameters.ElementAt(1).Type.IsString()) + { + context.AddError( + function.Location(), + EdmErrorCode.UrlEscapeFunctionMustHaveOnlyOneEdmStringParameter, + Strings.EdmModel_Validator_Semantic_UrlEscapeFunctionMustHaveOneStringParameter(function.Name)); + } + }); + #endregion + + #region IEdmOperation + + /// + /// Validates that a operation import has an allowed return type. + /// + public static readonly ValidationRule OperationUnsupportedReturnType = + new ValidationRule( + (context, operation) => + { + if (operation.ReturnType != null) + { + IEdmTypeReference elementType = operation.ReturnType.IsCollection() ? operation.ReturnType.AsCollection().ElementType() : operation.ReturnType; + var isUnresolvedElement = elementType.Definition is IUnresolvedElement; + if (!isUnresolvedElement && context.IsBad(elementType.Definition)) + { + context.AddError( + operation.Location(), + EdmErrorCode.OperationImportUnsupportedReturnType, + Strings.EdmModel_Validator_Semantic_OperationWithUnsupportedReturnType(operation.Name)); + } + } + }); + + /// + /// Validates that a operation does not have multiple parameters with the same name. + /// + public static readonly ValidationRule OperationParameterNameAlreadyDefinedDuplicate = + new ValidationRule( + (context, operation) => + { + HashSetInternal parameterList = new HashSetInternal(); + if (operation.Parameters != null) + { + foreach (var parameter in operation.Parameters) + { + ValidationHelper.AddMemberNameToHashSet( + parameter, + parameterList, + context, + EdmErrorCode.AlreadyDefined, + Strings.EdmModel_Validator_Semantic_ParameterNameAlreadyDefinedDuplicate(parameter.Name), + /*supressError*/ false); + } + } + }); + + /// + /// Validates that if an operation is bindable, it must have non-optional parameters. + /// + public static readonly ValidationRule BoundOperationMustHaveParameters = + new ValidationRule( + (context, operation) => + { + if (operation.IsBound && !operation.Parameters.Any(p => !(p is IEdmOptionalParameter))) + { + context.AddError( + operation.Location(), + EdmErrorCode.BoundOperationMustHaveParameters, + Strings.EdmModel_Validator_Semantic_BoundOperationMustHaveParameters(operation.Name)); + } + }); + + /// + /// Validates optional parameters must come before required parameters. + /// + public static readonly ValidationRule OptionalParametersMustComeAfterRequiredParameters = + new ValidationRule( + (context, operation) => + { + bool foundOptional = false; + foreach (IEdmOperationParameter parameter in operation.Parameters) + { + if (parameter is IEdmOptionalParameter) + { + foundOptional = true; + } + else if (foundOptional) + { + context.AddError( + operation.Location(), + EdmErrorCode.RequiredParametersMustPrecedeOptional, + Strings.EdmModel_Validator_Semantic_RequiredParametersMustPrecedeOptional(parameter.Name)); + } + } + }); + + /// + /// Validates that if a operationImport is bindable, it must have parameters. + /// + public static readonly ValidationRule OperationEntitySetPathMustBeValid = + new ValidationRule((context, operation) => + { + IEdmOperationParameter bindingParameter = null; + Dictionary navProps = null; + IEdmEntityType lastEntityType = null; + IEnumerable errors = null; + + operation.TryGetRelativeEntitySetPath(context.Model, out bindingParameter, out navProps, out lastEntityType, out errors); + + foreach (var error in errors) + { + context.AddError(error); + } + }); + + /// + /// Validates that the return type is consistent with the entityset path if it exists. + /// + public static readonly ValidationRule OperationReturnTypeEntityTypeMustBeValid = + new ValidationRule((context, operation) => + { + IEdmOperationParameter bindingParameter = null; + Dictionary navProps = null; + IEdmEntityType lastEntityType = null; + IEnumerable errors = null; + + // If there is no relative entity set path to reolve then there is nothing to compare against. + if (!operation.TryGetRelativeEntitySetPath(context.Model, out bindingParameter, out navProps, out lastEntityType, out errors)) + { + return; + } + + if (operation.ReturnType != null) + { + IEdmEntityType elementType = operation.ReturnType.Definition as IEdmEntityType; + IEdmCollectionType returnCollectionType = operation.ReturnType.Definition as IEdmCollectionType; + if (elementType == null && returnCollectionType != null) + { + elementType = returnCollectionType.ElementType.Definition as IEdmEntityType; + } + + bool isEntity = operation.ReturnType.IsEntity(); + if (returnCollectionType != null) + { + isEntity = returnCollectionType.ElementType.IsEntity(); + } + + if (!isEntity || context.IsBad(elementType)) + { + context.AddError(operation.Location(), EdmErrorCode.OperationWithEntitySetPathReturnTypeInvalid, Strings.EdmModel_Validator_Semantic_OperationWithEntitySetPathReturnTypeInvalid(operation.Name)); + } + + IEdmNavigationProperty navProp = null; + if (navProps != null) + { + navProp = navProps.LastOrDefault().Key; + } + + if (navProp != null && navProp.TargetMultiplicity() != EdmMultiplicity.Many) + { + if (returnCollectionType != null) + { + context.AddError(operation.Location(), EdmErrorCode.OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType, Strings.EdmModel_Validator_Semantic_OperationWithEntitySetPathResolvesToCollectionEntityTypeMismatchesEntityTypeReturnType(operation.Name)); + } + } + + if (lastEntityType != null && elementType != null && !elementType.IsOrInheritsFrom(lastEntityType)) + { + context.AddError(operation.Location(), EdmErrorCode.OperationWithEntitySetPathAndReturnTypeTypeNotAssignable, Strings.EdmModel_Validator_Semantic_OperationWithEntitySetPathAndReturnTypeTypeNotAssignable(operation.Name, elementType.FullName(), lastEntityType.FullName())); + } + } + }); + + + /// + /// Validates that the return type cannot be Collection(Edm.PrimitiveType) or Collection(Edm.ComplexType). + /// + public static readonly ValidationRule OperationReturnTypeCannotBeCollectionOfAbstractType = + new ValidationRule((context, operation) => + { + if (operation.ReturnType != null && operation.ReturnType.IsCollection()) + { + IEdmTypeReference elementType = operation.ReturnType.AsCollection().ElementType(); + if (elementType.Definition == EdmCoreModelComplexType.Instance || + elementType.Definition == EdmCoreModel.Instance.GetPrimitiveType()) + { + context.AddError( + operation.Location(), + EdmErrorCode.OperationWithCollectionOfAbstractReturnTypeInvalid, + Strings.EdmModel_Validator_Semantic_OperationReturnTypeCannotBeCollectionOfAbstractType(operation.ReturnType.FullName(), operation.FullName())); + } + } + }); + + #endregion + + #region IEdmTypeReference + + /// + /// Validates that a type reference refers to a type that can be found through the model being validated. + /// + public static readonly ValidationRule TypeReferenceInaccessibleSchemaType = + new ValidationRule( + (context, typeReference) => + { + IEdmSchemaType schemaType = typeReference.Definition as IEdmSchemaType; + if (schemaType != null && !context.IsBad(schemaType)) + { + CheckForUnreacheableTypeError(context, schemaType, typeReference.Location()); + } + }); + + #endregion + + #region IEdmDecimalTypeReference + + /// + /// Validates that the scale is between 0 and the precision of the decimal type. + /// + public static readonly ValidationRule DecimalTypeReferenceScaleOutOfRange = + new ValidationRule( + (context, type) => + { + if (type.Scale > type.Precision || type.Scale < 0) + { + context.AddError( + type.Location(), + EdmErrorCode.ScaleOutOfRange, + Strings.EdmModel_Validator_Semantic_ScaleOutOfRange); + } + }); + + /// + /// Validates that the precision is between 0 and the max precision of the decimal type. + /// + public static readonly ValidationRule DecimalTypeReferencePrecisionOutOfRange = + new ValidationRule( + (context, type) => + { + if (type.Precision > EdmConstants.Max_Precision || type.Precision < EdmConstants.Min_Precision) + { + context.AddError( + type.Location(), + EdmErrorCode.PrecisionOutOfRange, + Strings.EdmModel_Validator_Semantic_PrecisionOutOfRange); + } + }); + + #endregion + + #region IEdmStringTypeReference + + /// + /// Validates that the max length of a string is not negative. + /// + public static readonly ValidationRule StringTypeReferenceStringMaxLengthNegative = + new ValidationRule( + (context, type) => + { + if (type.MaxLength < 0) + { + context.AddError( + type.Location(), + EdmErrorCode.MaxLengthOutOfRange, + Strings.EdmModel_Validator_Semantic_StringMaxLengthOutOfRange); + } + }); + + /// + /// Validates that IsUnbounded cannot be true if MaxLength is non-null. + /// + public static readonly ValidationRule StringTypeReferenceStringUnboundedNotValidForMaxLength = + new ValidationRule( + (context, type) => + { + if (type.MaxLength != null && type.IsUnbounded) + { + context.AddError( + type.Location(), + EdmErrorCode.IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull, + Strings.EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull); + } + }); + + #endregion + + #region IEdmBinaryTypeReference + + /// + /// Validates that the max length of a binary type is not negative. + /// + public static readonly ValidationRule BinaryTypeReferenceBinaryMaxLengthNegative = + new ValidationRule( + (context, type) => + { + if (type.MaxLength < 0) + { + context.AddError( + type.Location(), + EdmErrorCode.MaxLengthOutOfRange, + Strings.EdmModel_Validator_Semantic_MaxLengthOutOfRange); + } + }); + + /// + /// Validates that isUnbounded cannot be true if MaxLength is non-null. + /// + public static readonly ValidationRule BinaryTypeReferenceBinaryUnboundedNotValidForMaxLength = + new ValidationRule( + (context, type) => + { + if (type.MaxLength != null && type.IsUnbounded) + { + context.AddError( + type.Location(), + EdmErrorCode.IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull, + Strings.EdmModel_Validator_Semantic_IsUnboundedCannotBeTrueWhileMaxLengthIsNotNull); + } + }); + + #endregion + + #region IEdmTemporalTypeReference + + /// + /// Validates that the precision is between 0 and the max precision of the temporal type. + /// + public static readonly ValidationRule TemporalTypeReferencePrecisionOutOfRange = + new ValidationRule( + (context, type) => + { + if (type.Precision > EdmConstants.Max_Precision || type.Precision < EdmConstants.Min_Precision) + { + context.AddError( + type.Location(), + EdmErrorCode.PrecisionOutOfRange, + Strings.EdmModel_Validator_Semantic_PrecisionOutOfRange); + } + }); + + #endregion + + #region IEdmModel + /// + /// Validates every schema element in the current model is unique across all referenced models. + /// + public static readonly ValidationRule ModelDuplicateSchemaElementName = + new ValidationRule( + (context, model) => + { + HashSetInternal nonFunctionNameList = new HashSetInternal(); + DuplicateOperationValidator duplicateOperationValidator = new DuplicateOperationValidator(context); + HashSetInternal operationList = new HashSetInternal(); + + foreach (var item in model.SchemaElements) + { + bool duplicate = false; + string fullName = item.FullName(); + IEdmOperation operation = item as IEdmOperation; + if (operation != null) + { + if (!operationList.Contains(operation.FullName())) + { + operationList.Add(operation.FullName()); + } + + // If a non-function already exists with the same name, stop processing as a function, as it is irrelevant it will always be an error. + if (nonFunctionNameList.Contains(fullName)) + { + duplicate = true; + } + + duplicateOperationValidator.ValidateNotDuplicate(operation, false /*skipError*/); + + if (!duplicate) + { + duplicate = model.OperationOrNameExistsInReferencedModel(operation, fullName); + } + } + else + { + if (!nonFunctionNameList.Add(fullName)) + { + duplicate = true; + } + else + { + if (operationList.Contains(fullName)) + { + duplicate = true; + } + else + { + duplicate = model.ItemExistsInReferencedModel(fullName, true); + } + } + } + + if (duplicate) + { + context.AddError( + item.Location(), + EdmErrorCode.AlreadyDefined, + Strings.EdmModel_Validator_Semantic_SchemaElementNameAlreadyDefined(fullName)); + } + } + }); + + /// + /// Validates that there are not duplicate properties in an entity key. + /// + public static readonly ValidationRule ModelDuplicateEntityContainerName = + new ValidationRule( + (context, model) => + { + HashSetInternal entityContainerNameList = new HashSetInternal(); + var container = model.EntityContainer; + if (container != null) + { + ValidationHelper.AddMemberNameToHashSet( + container, + entityContainerNameList, + context, + EdmErrorCode.DuplicateEntityContainerName, + Strings.EdmModel_Validator_Semantic_DuplicateEntityContainerName(container.Name), + /*supressError*/ false); + } + }); + + /// + /// Validates all function overloads with the same name have the same returntype. + /// + public static readonly ValidationRule ModelBoundFunctionOverloadsMustHaveSameReturnType = + new ValidationRule( + (context, model) => + { + foreach (var functionNameGroup in model.SchemaElements.OfType().Where(f => f.IsBound).GroupBy(f2 => f2.FullName())) + { + Dictionary bindingTypeReturnTypeLookup = new Dictionary(new EdmTypeReferenceComparer()); + foreach (var function in functionNameGroup) + { + // Skip invalid functions that have no parameters or null ReturnType. This is validated elsewhere. + if (!function.Parameters.Any() || function.ReturnType == null) + { + continue; + } + + var bindingParameter = function.Parameters.First(); + if (!bindingTypeReturnTypeLookup.ContainsKey(bindingParameter.Type)) + { + bindingTypeReturnTypeLookup.Add(bindingParameter.Type, function.ReturnType); + } + else + { + IEdmTypeReference expectedReturnType = bindingTypeReturnTypeLookup[bindingParameter.Type]; + if (!function.ReturnType.IsEquivalentTo(expectedReturnType)) + { + context.AddError( + function.Location(), + EdmErrorCode.BoundFunctionOverloadsMustHaveSameReturnType, + Strings.EdmModel_Validator_Semantic_BoundFunctionOverloadsMustHaveSameReturnType(function.Name, expectedReturnType.FullName())); + } + } + } + } + }); + + /// + /// Validates that all function overloads have the same return types. + /// + public static readonly ValidationRule UnBoundFunctionOverloadsMustHaveIdenticalReturnTypes = + new ValidationRule((context, model) => + { + Dictionary functionNameToReturnTypeLookup = new Dictionary(); + foreach (var function in model.SchemaElements.OfType().Where(f => !f.IsBound)) + { + if (!functionNameToReturnTypeLookup.ContainsKey(function.Name)) + { + functionNameToReturnTypeLookup.Add(function.Name, function.ReturnType); + } + else + { + if (!function.ReturnType.IsEquivalentTo(functionNameToReturnTypeLookup[function.Name])) + { + context.AddError( + function.Location(), + EdmErrorCode.UnboundFunctionOverloadHasIncorrectReturnType, + Strings.EdmModel_Validator_Semantic_UnboundFunctionOverloadHasIncorrectReturnType(function.Name)); + } + } + } + }); + + #endregion + + #region IEdmImmediateValueAnnotation + + /// + /// Validates that an immediate annotation has a name and a namespace. + /// + public static readonly ValidationRule ImmediateValueAnnotationElementAnnotationIsValid = + new ValidationRule( + (context, annotation) => + { + IEdmStringValue stringValue = annotation.Value as IEdmStringValue; + if (stringValue != null) + { + if (stringValue.IsSerializedAsElement(context.Model)) + { + if (EdmUtil.IsNullOrWhiteSpaceInternal(annotation.NamespaceUri) || EdmUtil.IsNullOrWhiteSpaceInternal(annotation.Name)) + { + context.AddError( + annotation.Location(), + EdmErrorCode.InvalidElementAnnotation, + Strings.EdmModel_Validator_Semantic_InvalidElementAnnotationMismatchedTerm); + } + } + } + }); + + /// + /// Validates that an immediate annotation that is flagged to be serialized as an element can be serialized safely. + /// + public static readonly ValidationRule ImmediateValueAnnotationElementAnnotationHasNameAndNamespace = + new ValidationRule( + (context, annotation) => + { + IEdmStringValue stringValue = annotation.Value as IEdmStringValue; + if (stringValue != null) + { + if (stringValue.IsSerializedAsElement(context.Model)) + { + EdmError error; + if (!ValidationHelper.ValidateValueCanBeWrittenAsXmlElementAnnotation(stringValue, annotation.NamespaceUri, annotation.Name, out error)) + { + context.AddError(error); + } + } + } + }); + + /// + /// Validates that the name of a direct annotation can safely be serialized as XML. + /// + public static readonly ValidationRule DirectValueAnnotationHasXmlSerializableName = + new ValidationRule( + (context, annotation) => + { + string name = annotation.Name; + + // We check for null, whitespace, and length in separate IEdmNamedElement validation rules. + if (!EdmUtil.IsNullOrWhiteSpaceInternal(name) && name.Length <= CsdlConstants.Max_NameLength && name.Length > 0) + { + try + { + // Note: this check can be done without the try/catch block, but we need XmlConvert.IsStartNCNameChar and XmlConvert.IsNCNameChar, which are not available in 3.5. + XmlConvert.VerifyNCName(annotation.Name); + } + catch (XmlException) + { + IEdmValue value = annotation.Value as IEdmValue; + EdmLocation errorLocation = value == null ? null : value.Location(); + context.AddError(new EdmError(errorLocation, EdmErrorCode.InvalidName, Edm.Strings.EdmModel_Validator_Syntactic_EdmModel_NameIsNotAllowed(annotation.Name))); + } + } + }); + + #endregion + + #region IEdmVocabularyAnnotation + + /// + /// Validates that a vocabulary annotations target can be found through the model containing the annotation. + /// + public static readonly ValidationRule VocabularyAnnotationInaccessibleTarget = + new ValidationRule( + (context, annotation) => + { + var target = annotation.Target; + bool foundTarget = false; + + IEdmEntityContainer entityContainer = target as IEdmEntityContainer; + if (entityContainer != null) + { + foundTarget = (context.Model.FindEntityContainer(entityContainer.FullName()) as IEdmEntityContainer != null); + } + else + { + IEdmEntitySet entitySet = target as IEdmEntitySet; + if (entitySet != null) + { + IEdmEntityContainer container = entitySet.Container as IEdmEntityContainer; + if (container != null) + { + foundTarget = (container.FindEntitySetExtended(entitySet.Name) != null); + } + } + else + { + IEdmSchemaType schemaType = target as IEdmSchemaType; + if (schemaType != null) + { + foundTarget = (context.Model.FindType(schemaType.FullName()) as IEdmSchemaType != null); + } + else + { + IEdmTerm term = target as IEdmTerm; + if (term != null) + { + foundTarget = (context.Model.FindTerm(term.FullName()) != null); + } + else + { + IEdmOperation operation = target as IEdmOperation; + if (operation != null) + { + foundTarget = context.Model.FindOperations(operation.FullName()).Any(); + } + else + { + IEdmOperationImport operationImport = target as IEdmOperationImport; + if (operationImport != null) + { + foundTarget = operationImport.Container.FindOperationImportsExtended(operationImport.Name).Any(); + } + else + { + IEdmProperty typeProperty = target as IEdmProperty; + if (typeProperty != null) + { + string declaringTypeFullName = EdmUtil.FullyQualifiedName(typeProperty.DeclaringType as IEdmSchemaType); + IEdmStructuredType modelType = context.Model.FindType(declaringTypeFullName) as IEdmStructuredType; + if (modelType != null) + { + // If we can find a structured type with this name in the model check if it has a property with this name + foundTarget = (modelType.FindProperty(typeProperty.Name) != null); + } + } + else + { + IEdmOperationParameter operationParameter = target as IEdmOperationParameter; + if (operationParameter != null) + { + IEdmOperation declaringOperation = operationParameter.DeclaringOperation as IEdmOperation; + if (declaringOperation != null) + { + // For all functions with this name declared in the model check if it has a parameter with this name + foreach (var func in context.Model.FindOperations(declaringOperation.FullName())) + { + if (func.FindParameter(operationParameter.Name) != null) + { + foundTarget = true; + break; + } + } + } + else + { + IEdmOperationImport declaringFunctionImport = operationParameter.DeclaringOperation as IEdmOperationImport; + if (declaringFunctionImport != null) + { + var container = declaringFunctionImport.Container as IEdmEntityContainer; + foreach (var currentFunction in container.FindOperationImportsExtended(declaringFunctionImport.Name)) + { + if (currentFunction.Operation.FindParameter(operationParameter.Name) != null) + { + foundTarget = true; + break; + } + } + } + } + } + else + { + // Only validate annotations targeting elements that can be found via the model API. + // E.g. annotations targeting annotations will not be valid without this branch. + foundTarget = true; + } + } + } + } + } + } + } + } + + if (!foundTarget) + { + context.AddError( + annotation.Location(), + EdmErrorCode.BadUnresolvedTarget, + Strings.EdmModel_Validator_Semantic_InaccessibleTarget(EdmUtil.FullyQualifiedName(target))); + } + }); + + /// + /// Validates that if a vocabulary annotation declares a type, the expression for that annotation has the correct type. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly ValidationRule VocabularyAnnotationAssertCorrectExpressionType = + new ValidationRule( + (context, annotation) => + { + IEnumerable errors; + if (!annotation.Value.TryCast(annotation.Term.Type, out errors)) + { + foreach (EdmError error in errors) + { + if (error.ErrorCode != EdmErrorCode.RecordExpressionMissingRequiredProperty) + { + context.AddError(error); + } + } + } + }); + + /// + /// Validates that a vocabulary annotations term can be found through the model containing the annotation. + /// + public static readonly ValidationRule AnnotationInaccessibleTerm = + new ValidationRule( + (context, annotation) => + { + // An unbound term is not treated as a semantic error, and looking up its name would fail. + IEdmTerm term = annotation.Term; + if (!(term is Microsoft.OData.Edm.Csdl.CsdlSemantics.IUnresolvedElement) && context.Model.FindTerm(term.FullName()) == null) + { + context.AddError( + annotation.Location(), + EdmErrorCode.BadUnresolvedTerm, + Strings.EdmModel_Validator_Semantic_InaccessibleTerm(annotation.Term.FullName())); + } + }); + + /// + /// Validates that a vocabulary annotations target can be allowed in the AppliesTo of the term. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] + public static readonly ValidationRule VocabularyAnnotationTargetAllowedApplyToElement = + new ValidationRule( + (context, annotation) => + { + IEdmTerm term = annotation.Term; + if (term.AppliesTo == null) + { + return; + } + + var hash = new HashSet(term.AppliesTo.Split(' ').Select(e => e.Trim())); + string symbolicString = annotation.Target.GetSymbolicString(); + if (hash.Contains(symbolicString)) + { + return; + } + + context.AddError( + annotation.Location(), + EdmErrorCode.AnnotationApplyToNotAllowedAnnotatable, + Strings.EdmModel_Validator_Semantic_VocabularyAnnotationApplyToNotAllowedAnnotatable(EdmUtil.FullyQualifiedName(annotation.Target), term.AppliesTo, term.FullName())); + }); + #endregion + + #region IEdmPropertyValueBinding + + /// + /// Validates that the value of a property value binding is the correct type. + /// + public static readonly ValidationRule PropertyValueBindingValueIsCorrectType = + new ValidationRule( + (context, binding) => + { + IEnumerable errors; + if (!binding.Value.TryCast(binding.BoundProperty.Type, out errors) && + !context.IsBad(binding) && + !context.IsBad(binding.BoundProperty)) + { + foreach (EdmError error in errors) + { + context.AddError(error); + } + } + }); + + #endregion + + #region IEdmIfExpression + + /// + /// Validates that an if expression has a boolean condition. + /// + public static readonly ValidationRule IfExpressionAssertCorrectTestType = + new ValidationRule( + (context, expression) => + { + IEnumerable errors; + if (!expression.TestExpression.TryCast(EdmCoreModel.Instance.GetBoolean(false), out errors)) + { + foreach (EdmError error in errors) + { + context.AddError(error); + } + } + }); + + #endregion + + #region IEdmCollectionExpression + + /// + /// Validates that all properties of a collection expression are of the correct type. + /// + public static readonly ValidationRule CollectionExpressionAllElementsCorrectType = + new ValidationRule( + (context, expression) => + { + if (expression.DeclaredType != null && !context.IsBad(expression) && !context.IsBad(expression.DeclaredType)) + { + IEnumerable discoveredErrors; + ExpressionTypeChecker.TryCastCollectionAsType(expression, expression.DeclaredType, null, false, out discoveredErrors); + foreach (EdmError error in discoveredErrors) + { + context.AddError(error); + } + } + }); + + #endregion + + #region IEdmRecordExpression + + /// + /// Validates that if a value record expression declares a type, the property types are correct. + /// + public static readonly ValidationRule RecordExpressionPropertiesMatchType = + new ValidationRule( + (context, expression) => + { + if (expression.DeclaredType != null && !context.IsBad(expression) && !context.IsBad(expression.DeclaredType)) + { + IEnumerable discoveredErrors; + ExpressionTypeChecker.TryCastRecordAsType(expression, expression.DeclaredType, null, false, out discoveredErrors); + foreach (EdmError error in discoveredErrors) + { + context.AddError(error); + } + } + }); + + #endregion + + #region IEdmApplyExpression + + /// + /// Validates the types of a function application are correct. + /// + public static readonly ValidationRule FunctionApplicationExpressionParametersMatchAppliedFunction = + new ValidationRule( + (context, expression) => + { + IEdmFunction appliedFunction = expression.AppliedFunction; + if (appliedFunction != null && !context.IsBad(appliedFunction)) + { + if (appliedFunction.Parameters.Count() != expression.Arguments.Count()) + { + context.AddError(new EdmError( + expression.Location(), + EdmErrorCode.IncorrectNumberOfArguments, + Edm.Strings.EdmModel_Validator_Semantic_IncorrectNumberOfArguments(expression.Arguments.Count(), appliedFunction.FullName(), appliedFunction.Parameters.Count()))); + } + + IEnumerator parameterExpressionEnumerator = expression.Arguments.GetEnumerator(); + foreach (IEdmOperationParameter parameter in appliedFunction.Parameters) + { + parameterExpressionEnumerator.MoveNext(); + IEnumerable recursiveErrors; + if (!parameterExpressionEnumerator.Current.TryCast(parameter.Type, out recursiveErrors)) + { + foreach (EdmError error in recursiveErrors) + { + context.AddError(error); + } + } + } + } + }); + + #endregion + + #region IEdmVocabularyAnnotatable + + /// + /// Validates that there are no annotations that share the same term and qualifier. + /// + public static readonly ValidationRule VocabularyAnnotatableNoDuplicateAnnotations = + new ValidationRule( + (context, annotatable) => + { + HashSetInternal annotationSet = new HashSetInternal(); + foreach (IEdmVocabularyAnnotation annotation in context.Model.FindDeclaredVocabularyAnnotations(annotatable)) + { + if (!annotationSet.Add(annotation.Term.FullName() + ":" + annotation.Qualifier)) + { + context.AddError(new EdmError( + annotation.Location(), + EdmErrorCode.DuplicateAnnotation, + Edm.Strings.EdmModel_Validator_Semantic_DuplicateAnnotation(EdmUtil.FullyQualifiedName(annotatable), annotation.Term.FullName(), annotation.Qualifier))); + } + } + }); + + #endregion + + #region IEdmPrimitiveValue + + /// + /// Validates that if a primitive value declares a type, the value is acceptable for the type. + /// + public static readonly ValidationRule PrimitiveValueValidForType = + new ValidationRule( + (context, value) => + { + if (value.Type != null && !context.IsBad(value) && !context.IsBad(value.Type)) + { + IEnumerable discoveredErrors; + ExpressionTypeChecker.TryCastPrimitiveAsType(value, value.Type, out discoveredErrors); + foreach (EdmError error in discoveredErrors) + { + context.AddError(error); + } + } + }); + + #endregion + + private static void CheckForUnreacheableTypeError(ValidationContext context, IEdmSchemaType type, EdmLocation location) + { + IEdmType foundType = context.Model.FindType(type.FullName()); + if (foundType is AmbiguousTypeBinding) + { + context.AddError( + location, + EdmErrorCode.BadAmbiguousElementBinding, + Strings.EdmModel_Validator_Semantic_AmbiguousType(type.FullName())); + } + else if (!foundType.IsEquivalentTo(type)) + { + context.AddError( + location, + EdmErrorCode.BadUnresolvedType, + Strings.EdmModel_Validator_Semantic_InaccessibleType(type.FullName())); + } + } + + private static bool TryResolveNavigationPropertyBindingPath(IEdmModel model, IEdmNavigationSource navigationSource, IEdmNavigationPropertyBinding binding) + { + var pathSegments = binding.Path.PathSegments.ToArray(); + var definingType = navigationSource.EntityType() as IEdmStructuredType; + for (int index = 0; index < pathSegments.Length - 1; index++) + { + string segment = pathSegments[index]; + if (segment.IndexOf('.') < 0) + { + var property = definingType.FindProperty(segment); + if (property == null) + { + return false; + } + + var navProperty = property as IEdmNavigationProperty; + if (navProperty != null && !navProperty.ContainsTarget) + { + return false; + } + + definingType = property.Type.Definition.AsElementType() as IEdmStructuredType; + if (definingType == null) + { + return false; + } + } + else + { + var derivedType = model.FindType(segment) as IEdmStructuredType; + if (derivedType == null || !derivedType.IsOrInheritsFrom(definingType)) + { + return false; + } + + definingType = derivedType; + } + } + + var navigationProperty = definingType.FindProperty(pathSegments.Last()) as IEdmNavigationProperty; + return navigationProperty != null; + } + + private static bool HasPathTypeProperty(IEdmStructuredType structuredType, IList visited) + { + if (structuredType == null || visited == null || visited.Any(c => c == structuredType)) + { + return false; + } + + visited.Add(structuredType); + + IEdmStructuredType baseType = structuredType.BaseType; + if (baseType != null) + { + if (HasPathTypeProperty(baseType, visited)) + { + return true; + } + } + + foreach (var property in structuredType.DeclaredProperties) + { + IEdmTypeReference propertyType = property.Type; + if (propertyType.IsCollection()) + { + propertyType = propertyType.AsCollection().ElementType(); + } + + if (propertyType.IsStructured()) + { + if (HasPathTypeProperty(propertyType.AsStructured().StructuredDefinition(), visited)) + { + return true; + } + } + else if (propertyType.IsPath()) + { + return true; + } + } + + return false; + } + + + internal class EdmTypeReferenceComparer : IEqualityComparer + { + public bool Equals(IEdmTypeReference x, IEdmTypeReference y) + { + return x.IsEquivalentTo(y); + } + + public int GetHashCode(IEdmTypeReference obj) + { + string fullName = obj.FullName(); + + // Currently when operations have Row types this occurs, simply returning 0 instead to force equals comparision. + // Code may be able to be removed when RowType is removed. + if (fullName == null) + { + return 0; + } + + return fullName.GetHashCode(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningDictionary.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningDictionary.cs new file mode 100644 index 0000000..2307e7d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningDictionary.cs @@ -0,0 +1,333 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Provides a dictionary that is thread safe by virtue of being immutable. + /// Any update returns a new dictionary (which, for efficiency, may share some of the state of the old one). + /// + /// Key type of the dictionary. + /// Value type of the dictionary. + internal abstract class VersioningDictionary + { + protected readonly Func CompareFunction; + + protected VersioningDictionary(Func compareFunction) + { + this.CompareFunction = compareFunction; + } + + public static VersioningDictionary Create(Func compareFunction) + { + return new EmptyVersioningDictionary(compareFunction); + } + + public abstract VersioningDictionary Set(TKey keyToSet, TValue newValue); + + public abstract VersioningDictionary Remove(TKey keyToRemove); + + public TValue Get(TKey key) + { + TValue value; + if (this.TryGetValue(key, out value)) + { + return value; + } + + throw new KeyNotFoundException(key.ToString()); + } + + public abstract bool TryGetValue(TKey key, out TValue value); + + internal sealed class EmptyVersioningDictionary : VersioningDictionary + { + public EmptyVersioningDictionary(Func compareFunction) + : base(compareFunction) + { + } + + public override VersioningDictionary Set(TKey keyToSet, TValue newValue) + { + return new OneKeyDictionary(this.CompareFunction, keyToSet, newValue); + } + + public override VersioningDictionary Remove(TKey keyToRemove) + { + throw new KeyNotFoundException(keyToRemove.ToString()); + } + + public override bool TryGetValue(TKey key, out TValue value) + { + value = default(TValue); + return false; + } + } + + internal sealed class OneKeyDictionary : VersioningDictionary + { + private readonly TKey key; + private readonly TValue value; + + public OneKeyDictionary(Func compareFunction, TKey key, TValue value) + : base(compareFunction) + { + this.key = key; + this.value = value; + } + + public override VersioningDictionary Set(TKey keyToSet, TValue newValue) + { + if (this.CompareFunction(keyToSet, this.key) == 0) + { + // Replacing the single key produces a new dictionary. + return new OneKeyDictionary(this.CompareFunction, keyToSet, newValue); + } + + return new TwoKeyDictionary(this.CompareFunction, this.key, this.value, keyToSet, newValue); + } + + public override VersioningDictionary Remove(TKey keyToRemove) + { + if (this.CompareFunction(keyToRemove, this.key) == 0) + { + return new EmptyVersioningDictionary(this.CompareFunction); + } + + throw new KeyNotFoundException(keyToRemove.ToString()); + } + + public override bool TryGetValue(TKey key, out TValue value) + { + if (this.CompareFunction(key, this.key) == 0) + { + value = this.value; + return true; + } + + value = default(TValue); + return false; + } + } + + internal sealed class TwoKeyDictionary : VersioningDictionary + { + private readonly TKey firstKey; + private readonly TValue firstValue; + private readonly TKey secondKey; + private readonly TValue secondValue; + + public TwoKeyDictionary(Func compareFunction, TKey firstKey, TValue firstValue, TKey secondKey, TValue secondValue) + : base(compareFunction) + { + this.firstKey = firstKey; + this.firstValue = firstValue; + this.secondKey = secondKey; + this.secondValue = secondValue; + } + + public override VersioningDictionary Set(TKey keyToSet, TValue newValue) + { + if (this.CompareFunction(keyToSet, this.firstKey) == 0) + { + return new TwoKeyDictionary(this.CompareFunction, keyToSet, newValue, this.secondKey, this.secondValue); + } + + if (this.CompareFunction(keyToSet, this.secondKey) == 0) + { + return new TwoKeyDictionary(this.CompareFunction, this.firstKey, this.firstValue, keyToSet, newValue); + } + + return new TreeDictionary(this.CompareFunction, this.firstKey, this.firstValue, this.secondKey, this.secondValue, keyToSet, newValue); + } + + public override VersioningDictionary Remove(TKey keyToRemove) + { + if (this.CompareFunction(keyToRemove, this.firstKey) == 0) + { + return new OneKeyDictionary(this.CompareFunction, this.secondKey, this.secondValue); + } + + if (this.CompareFunction(keyToRemove, this.secondKey) == 0) + { + return new OneKeyDictionary(this.CompareFunction, this.firstKey, this.firstValue); + } + + throw new KeyNotFoundException(keyToRemove.ToString()); + } + + public override bool TryGetValue(TKey key, out TValue value) + { + if (this.CompareFunction(key, this.firstKey) == 0) + { + value = this.firstValue; + return true; + } + + if (this.CompareFunction(key, this.secondKey) == 0) + { + value = this.secondValue; + return true; + } + + value = default(TValue); + return false; + } + } + + internal sealed class TreeDictionary : VersioningDictionary + { + private const int MaxTreeHeight = 10; + private readonly VersioningTree tree; + + public TreeDictionary(Func compareFunction, TKey firstKey, TValue firstValue, TKey secondKey, TValue secondValue, TKey thirdKey, TValue thirdValue) + : base(compareFunction) + { + this.tree = new VersioningTree(firstKey, firstValue, null, null).SetKeyValue(secondKey, secondValue, this.CompareFunction).SetKeyValue(thirdKey, thirdValue, this.CompareFunction); + } + + public TreeDictionary(Func compareFunction, VersioningTree tree) + : base(compareFunction) + { + this.tree = tree; + } + + public override VersioningDictionary Set(TKey keyToSet, TValue newValue) + { + if (this.tree.Height > MaxTreeHeight) + { + return new HashTreeDictionary(this.CompareFunction, this.tree, keyToSet, newValue); + } + + return new TreeDictionary(this.CompareFunction, this.tree.SetKeyValue(keyToSet, newValue, this.CompareFunction)); + } + + public override VersioningDictionary Remove(TKey keyToRemove) + { + return new TreeDictionary(this.CompareFunction, this.tree.Remove(keyToRemove, this.CompareFunction)); + } + + public override bool TryGetValue(TKey key, out TValue value) + { + if (this.tree == null) + { + value = default(TValue); + return false; + } + + return this.tree.TryGetValue(key, this.CompareFunction, out value); + } + } + + internal sealed class HashTreeDictionary : VersioningDictionary + { + private const int HashSize = 17; + private readonly VersioningTree[] treeBuckets; + + public HashTreeDictionary(Func compareFunction, VersioningTree tree, TKey key, TValue value) + : base(compareFunction) + { + this.treeBuckets = new VersioningTree[HashSize]; + this.SetKeyValues(tree); + this.SetKeyValue(key, value); + } + + public HashTreeDictionary(Func compareFunction, VersioningTree[] trees, TKey key, TValue value) + : base(compareFunction) + { + this.treeBuckets = (VersioningTree[])trees.Clone(); + this.SetKeyValue(key, value); + } + + public HashTreeDictionary(Func compareFunction, VersioningTree[] trees, TKey key) + : base(compareFunction) + { + this.treeBuckets = (VersioningTree[])trees.Clone(); + this.RemoveKey(key); + } + + public override VersioningDictionary Set(TKey keyToSet, TValue newValue) + { + return new HashTreeDictionary(this.CompareFunction, this.treeBuckets, keyToSet, newValue); + } + + public override VersioningDictionary Remove(TKey keyToRemove) + { + return new HashTreeDictionary(this.CompareFunction, this.treeBuckets, keyToRemove); + } + + public override bool TryGetValue(TKey key, out TValue value) + { + VersioningTree tree = this.treeBuckets[GetBucket(key)]; + + if (tree == null) + { + value = default(TValue); + return false; + } + else + { + return tree.TryGetValue(key, this.CompareFunction, out value); + } + } + + private void SetKeyValue(TKey keyToSet, TValue newValue) + { + int hashBucket = GetBucket(keyToSet); + + if (this.treeBuckets[hashBucket] == null) + { + this.treeBuckets[hashBucket] = new VersioningTree(keyToSet, newValue, null, null); + } + else + { + this.treeBuckets[hashBucket] = this.treeBuckets[hashBucket].SetKeyValue(keyToSet, newValue, this.CompareFunction); + } + } + + private void SetKeyValues(VersioningTree tree) + { + if (tree == null) + { + return; + } + + this.SetKeyValue(tree.Key, tree.Value); + + this.SetKeyValues(tree.LeftChild); + this.SetKeyValues(tree.RightChild); + } + + private void RemoveKey(TKey keyToRemove) + { + int hashBucket = GetBucket(keyToRemove); + + if (this.treeBuckets[hashBucket] == null) + { + throw new KeyNotFoundException(keyToRemove.ToString()); + } + else + { + this.treeBuckets[hashBucket] = this.treeBuckets[hashBucket].Remove(keyToRemove, this.CompareFunction); + } + } + + private static int GetBucket(TKey key) + { + int hash = key.GetHashCode(); + if (hash < 0) + { + hash = -hash; + } + + return hash % HashSize; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningList.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningList.cs new file mode 100644 index 0000000..07fdf0c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningList.cs @@ -0,0 +1,366 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Provides a list that is thread safe by virtue of being immutable. + /// "Mutating" operations return a new list (which, for efficiency, may share some of the state of the old one). + /// + /// Element type of the list. + internal abstract class VersioningList : IEnumerable + { + public abstract int Count { get; } + + public TElement this[int index] + { + get + { + if (((uint)index) >= this.Count) + { + throw new IndexOutOfRangeException(); + } + + return this.IndexedElement(index); + } + } + + public static VersioningList Create() + { + return new EmptyVersioningList(); + } + + public abstract VersioningList Add(TElement value); + + public VersioningList RemoveAt(int index) + { + if (((uint)index) >= this.Count) + { + throw new IndexOutOfRangeException(); + } + + return this.RemoveIndexedElement(index); + } + + public abstract IEnumerator GetEnumerator(); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + protected abstract TElement IndexedElement(int index); + + protected abstract VersioningList RemoveIndexedElement(int index); + + internal sealed class EmptyVersioningList : VersioningList + { + public override int Count + { + get { return 0; } + } + + public override VersioningList Add(TElement value) + { + return new LinkedVersioningList(this, value); + } + + public override IEnumerator GetEnumerator() + { + return new EmptyListEnumerator(); + } + + protected override TElement IndexedElement(int index) + { + throw new IndexOutOfRangeException(); + } + + protected override VersioningList RemoveIndexedElement(int index) + { + throw new IndexOutOfRangeException(); + } + } + + internal sealed class EmptyListEnumerator : IEnumerator + { + public TElement Current + { + get { return default(TElement); } + } + + object System.Collections.IEnumerator.Current + { + get { return this.Current; } + } + + public void Dispose() + { + } + + public bool MoveNext() + { + return false; + } + + public void Reset() + { + } + } + + internal sealed class LinkedVersioningList : VersioningList + { + private readonly VersioningList preceding; + private readonly TElement last; + + public LinkedVersioningList(VersioningList preceding, TElement last) + { + this.preceding = preceding; + this.last = last; + } + + public override int Count + { + get { return this.preceding.Count + 1; } + } + + public VersioningList Preceding + { + get { return this.preceding; } + } + + public TElement Last + { + get { return this.last; } + } + + private int Depth + { + get + { + int depth = 0; + LinkedVersioningList layer = this; + while (layer != null) + { + depth++; + layer = layer.Preceding as LinkedVersioningList; + } + + return depth; + } + } + + public override VersioningList Add(TElement value) + { + if (this.Depth < 5) + { + return new LinkedVersioningList(this, value); + } + + return new ArrayVersioningList(this, value); + } + + public override IEnumerator GetEnumerator() + { + return new LinkedListEnumerator(this); + } + + protected override TElement IndexedElement(int index) + { + if (index == this.Count - 1) + { + return this.last; + } + + return this.preceding.IndexedElement(index); + } + + protected override VersioningList RemoveIndexedElement(int index) + { + if (index == this.Count - 1) + { + return this.preceding; + } + + return new LinkedVersioningList(this.preceding.RemoveIndexedElement(index), this.last); + } + } + + internal sealed class LinkedListEnumerator : IEnumerator + { + private readonly LinkedVersioningList list; + private IEnumerator preceding; + private bool complete; + + public LinkedListEnumerator(LinkedVersioningList list) + { + this.list = list; + this.preceding = list.Preceding.GetEnumerator(); + } + + public TElement Current + { + get + { + if (this.complete) + { + return this.list.Last; + } + + return this.preceding.Current; + } + } + + object System.Collections.IEnumerator.Current + { + get { return this.Current; } + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (this.complete) + { + return false; + } + + if (!this.preceding.MoveNext()) + { + this.complete = true; + } + + return true; + } + + public void Reset() + { + this.preceding.Reset(); + this.complete = false; + } + } + + internal sealed class ArrayVersioningList : VersioningList + { + private readonly TElement[] elements; + + public ArrayVersioningList(VersioningList preceding, TElement last) + { + this.elements = new TElement[preceding.Count + 1]; + int index = 0; + foreach (TElement element in preceding) + { + this.elements[index++] = element; + } + + this.elements[index] = last; + } + + private ArrayVersioningList(TElement[] elements) + { + this.elements = elements; + } + + public override int Count + { + get { return this.elements.Length; } + } + + public TElement ElementAt(int index) + { + return this.elements[index]; + } + + public override VersioningList Add(TElement value) + { + return new LinkedVersioningList(this, value); + } + + public override IEnumerator GetEnumerator() + { + return new ArrayListEnumerator(this); + } + + protected override TElement IndexedElement(int index) + { + return this.elements[index]; + } + + protected override VersioningList RemoveIndexedElement(int index) + { + if (this.elements.Length == 1) + { + return new EmptyVersioningList(); + } + + int newIndex = 0; + TElement[] newElements = new TElement[this.elements.Length - 1]; + for (int oldIndex = 0; oldIndex < this.elements.Length; oldIndex++) + { + if (oldIndex != index) + { + newElements[newIndex++] = this.elements[oldIndex]; + } + } + + return new ArrayVersioningList(newElements); + } + } + + internal sealed class ArrayListEnumerator : IEnumerator + { + private readonly ArrayVersioningList array; + private int index; + + public ArrayListEnumerator(ArrayVersioningList array) + { + this.array = array; + } + + public TElement Current + { + get + { + if (this.index <= this.array.Count) + { + return this.array.ElementAt(this.index - 1); + } + + return default(TElement); + } + } + + object System.Collections.IEnumerator.Current + { + get { return this.Current; } + } + + public void Dispose() + { + } + + public bool MoveNext() + { + int count = this.array.Count; + if (this.index <= count) + { + this.index++; + } + + return this.index <= count; + } + + public void Reset() + { + this.index = 0; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningTree.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningTree.cs new file mode 100644 index 0000000..e13209f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/VersioningTree.cs @@ -0,0 +1,241 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm +{ + /// + /// Provides an approximately-balanced binary search tree that is thread safe by virtue of being immutable. + /// Updates return a new tree (which, for efficiency, may share some state with the old one). + /// + /// Key type of the tree. + /// Value type of the tree. + internal class VersioningTree + { + public readonly TKey Key; + public readonly TValue Value; + public readonly int Height; + public readonly VersioningTree LeftChild; + public readonly VersioningTree RightChild; + + /// + /// Initializes a new instance of VersioningTree. + /// + /// The key of the tree node. + /// The value of the tree node. + /// A tree with all keys less than the key of the tree node. May be null. + /// A tree with all keys greater than the key of the tree node. May be null. + public VersioningTree(TKey key, TValue value, VersioningTree leftChild, VersioningTree rightChild) + { + this.Key = key; + this.Value = value; + this.Height = Max(GetHeight(leftChild), GetHeight(rightChild)) + 1; + this.LeftChild = leftChild; + this.RightChild = rightChild; + } + + public TValue GetValue(TKey key, Func compareFunction) + { + TValue value; + if (this.TryGetValue(key, compareFunction, out value)) + { + return value; + } + + throw new KeyNotFoundException(key.ToString()); + } + + public bool TryGetValue(TKey key, Func compareFunction, out TValue value) + { + VersioningTree tree = this; + while (tree != null) + { + int comparison = compareFunction(key, tree.Key); + if (comparison == 0) + { + value = tree.Value; + return true; + } + + tree = comparison < 0 ? tree = tree.LeftChild : tree.RightChild; + } + + value = default(TValue); + return false; + } + + public VersioningTree SetKeyValue(TKey key, TValue value, Func compareFunction) + { + VersioningTree leftChild = this.LeftChild; + VersioningTree rightChild = this.RightChild; + + int comparison = compareFunction(key, this.Key); + if (comparison < 0) + { + // Insert to the left. + if (GetHeight(leftChild) > GetHeight(rightChild)) + { + // The left subtree is taller than the right, so rotate to the right. + int leftChildComparison = compareFunction(key, leftChild.Key); + + VersioningTree newLeft = + leftChildComparison < 0 ? SetKeyValue(leftChild.LeftChild, key, value, compareFunction) : leftChild.LeftChild; + VersioningTree newRight = + new VersioningTree( + this.Key, + this.Value, + leftChildComparison > 0 ? SetKeyValue(leftChild.RightChild, key, value, compareFunction) : leftChild.RightChild, + rightChild); + return + new VersioningTree( + leftChildComparison == 0 ? key : leftChild.Key, + leftChildComparison == 0 ? value : leftChild.Value, + newLeft, + newRight); + } + else + { + return + new VersioningTree( + this.Key, + this.Value, + SetKeyValue(leftChild, key, value, compareFunction), + rightChild); + } + } + else if (comparison == 0) + { + // Insert here. + return new VersioningTree(key, value, leftChild, rightChild); + } + else + { + // Insert to the right. + if (GetHeight(leftChild) < GetHeight(rightChild)) + { + // The right subtree is taller than the left, so rotate to the left. + int rightChildComparison = compareFunction(key, rightChild.Key); + + VersioningTree newLeft = + new VersioningTree( + this.Key, + this.Value, + leftChild, + rightChildComparison < 0 ? SetKeyValue(rightChild.LeftChild, key, value, compareFunction) : rightChild.LeftChild); + VersioningTree newRight = + rightChildComparison > 0 ? SetKeyValue(rightChild.RightChild, key, value, compareFunction) : rightChild.RightChild; + return + new VersioningTree( + rightChildComparison == 0 ? key : rightChild.Key, + rightChildComparison == 0 ? value : rightChild.Value, + newLeft, + newRight); + } + else + { + return + new VersioningTree( + this.Key, + this.Value, + leftChild, + SetKeyValue(rightChild, key, value, compareFunction)); + } + } + } + + public VersioningTree Remove(TKey key, Func compareFunction) + { + int comparision = compareFunction(key, this.Key); + if (comparision < 0) + { + // Delete to the left. + if (this.LeftChild == null) + { + throw new KeyNotFoundException(key.ToString()); + } + + return new VersioningTree(this.Key, this.Value, this.LeftChild.Remove(key, compareFunction), this.RightChild); + } + else if (comparision == 0) + { + // Delete here. + if (this.LeftChild == null) + { + return this.RightChild; + } + + if (this.RightChild == null) + { + return this.LeftChild; + } + + if (this.LeftChild.Height < this.RightChild.Height) + { + // Attach the right child as the rightmost child of the left child. This can unbalance the tree, but + // maintaining balance can cost a great deal of storage allocation. + return this.LeftChild.MakeRightmost(this.RightChild); + } + else + { + // Attach the left child as the leftmost child of the right child. + return this.RightChild.MakeLeftmost(this.LeftChild); + } + } + else + { + // Delete to the right. + if (this.RightChild == null) + { + throw new KeyNotFoundException(key.ToString()); + } + + return new VersioningTree(this.Key, this.Value, this.LeftChild, this.RightChild.Remove(key, compareFunction)); + } + } + + private static VersioningTree SetKeyValue(VersioningTree me, TKey key, TValue value, Func compareFunction) + { + if (me == null) + { + return new VersioningTree(key, value, null, null); + } + + return me.SetKeyValue(key, value, compareFunction); + } + + private static int GetHeight(VersioningTree tree) + { + return tree == null ? 0 : tree.Height; + } + + private static int Max(int x, int y) + { + return x > y ? x : y; + } + + private VersioningTree MakeLeftmost(VersioningTree leftmost) + { + if (this.LeftChild == null) + { + return new VersioningTree(this.Key, this.Value, leftmost, this.RightChild); + } + + return new VersioningTree(this.Key, this.Value, this.LeftChild.MakeLeftmost(leftmost), this.RightChild); + } + + private VersioningTree MakeRightmost(VersioningTree rightmost) + { + if (this.RightChild == null) + { + return new VersioningTree(this.Key, this.Value, this.LeftChild, rightmost); + } + + return new VersioningTree(this.Key, this.Value, this.LeftChild, this.RightChild.MakeRightmost(rightmost)); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularies.xml b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularies.xml new file mode 100644 index 0000000..137b3e3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularies.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyConstants.cs new file mode 100644 index 0000000..a1c28e5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyConstants.cs @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies.Community.V1 +{ + /// + /// Constant values for Alternate Keys Vocabularies + /// + public static class AlternateKeysVocabularyConstants + { + /// OData.Community.Keys.V1.AlternateKeys + public const string AlternateKeys = "OData.Community.Keys.V1.AlternateKeys"; + + /// OData.Community.Keys.V1.AlternateKey.Key + internal const string AlternateKeyTypeKeyPropertyName = "Key"; + + /// OData.Community.Keys.V1.PropertyRef.Name + internal const string PropertyRefTypeNamePropertyName = "Name"; + + /// OData.Community.Keys.V1.PropertyRef.Alias + internal const string PropertyRefTypeAliasPropertyName = "Alias"; + + /// OData.Community.Keys.V1.AlternateKey + internal const string AlternateKeyType = "OData.Community.Keys.V1.AlternateKey"; + + /// OData.Community.Keys.V1.PropertyRef + internal const string PropertyRefType = "OData.Community.Keys.V1.PropertyRef"; + + /// OData.Community.Keys.V1 file suffix + internal const string VocabularyUrlSuffix = "/OData.Community.Keys.V1.xml"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyModel.cs new file mode 100644 index 0000000..452b737 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AlternateKeysVocabularyModel.cs @@ -0,0 +1,39 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Vocabularies.Community.V1 +{ + /// + /// Representing Alternate Keys Vocabulary Model. + /// + public static class AlternateKeysVocabularyModel + { + /// + /// The EDM model with Alternate Keys vocabularies. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmModel is immutable")] + public static readonly IEdmModel Instance = VocabularyModelProvider.AlternateKeyModel; + + /// + /// The Alternate Keys term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm AlternateKeysTerm = VocabularyModelProvider.AlternateKeyModel.FindDeclaredTerm(AlternateKeysVocabularyConstants.AlternateKeys); + + /// + /// The AlternateKey ComplexType. + /// + internal static readonly IEdmComplexType AlternateKeyType = VocabularyModelProvider.AlternateKeyModel.FindDeclaredType(AlternateKeysVocabularyConstants.AlternateKeyType) as IEdmComplexType; + + /// + /// The PropertyRef ComplexType. + /// + internal static readonly IEdmComplexType PropertyRefType = VocabularyModelProvider.AlternateKeyModel.FindDeclaredType(AlternateKeysVocabularyConstants.PropertyRefType) as IEdmComplexType; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotation.cs new file mode 100644 index 0000000..4809135 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotation.cs @@ -0,0 +1,57 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM annotation with an immediate native value. + /// + public class EdmDirectValueAnnotation : EdmNamedElement, IEdmDirectValueAnnotation + { + private readonly object value; + private readonly string namespaceUri; + + /// + /// Initializes a new instance of the class. + /// + /// Namespace URI of the annotation. + /// Name of the annotation within the namespace. + /// Value of the annotation + public EdmDirectValueAnnotation(string namespaceUri, string name, object value) + : this(namespaceUri, name) + { + EdmUtil.CheckArgumentNull(value, "value"); + this.value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace URI of the annotation. + /// Name of the annotation within the namespace. + internal EdmDirectValueAnnotation(string namespaceUri, string name) + : base(name) + { + EdmUtil.CheckArgumentNull(namespaceUri, "namespaceUri"); + this.namespaceUri = namespaceUri; + } + + /// + /// The namespace Uri of the annotation. + /// + public string NamespaceUri + { + get { return this.namespaceUri; } + } + + /// + /// Gets the value of this annotation. + /// + public object Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationBinding.cs new file mode 100644 index 0000000..1865c11 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationBinding.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents the combination of an EDM annotation with an immediate value and the element to which it is attached. + /// + public class EdmDirectValueAnnotationBinding : IEdmDirectValueAnnotationBinding + { + private readonly IEdmElement element; + private readonly string namespaceUri; + private readonly string name; + private readonly object value; + + /// + /// Initializes a new instance of the class. + /// + /// Element to which the annotation is attached. + /// Namespace URI of the annotation. + /// Name of the annotation within the namespace. + /// Value of the annotation + public EdmDirectValueAnnotationBinding(IEdmElement element, string namespaceUri, string name, object value) + { + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(namespaceUri, "namespaceUri"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.element = element; + this.namespaceUri = namespaceUri; + this.name = name; + this.value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// Element to which the annotation is attached. + /// Namespace URI of the annotation. + /// Name of the annotation within the namespace. + public EdmDirectValueAnnotationBinding(IEdmElement element, string namespaceUri, string name) + { + EdmUtil.CheckArgumentNull(element, "element"); + EdmUtil.CheckArgumentNull(namespaceUri, "namespaceUri"); + EdmUtil.CheckArgumentNull(name, "name"); + + this.element = element; + this.namespaceUri = namespaceUri; + this.name = name; + } + + /// + /// Gets the element to which the annotation is attached. + /// + public IEdmElement Element + { + get { return this.element; } + } + + /// + /// Gets the namespace Uri of the annotation. + /// + public string NamespaceUri + { + get { return this.namespaceUri; } + } + + /// + /// Gets the local name of the annotation. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the value of this annotation. + /// + public object Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationsManager.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationsManager.cs new file mode 100644 index 0000000..a8e22ce --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmDirectValueAnnotationsManager.cs @@ -0,0 +1,456 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Direct-value annotations manager provides services for setting and getting transient annotations on elements. + /// + /// + /// An object representing transient annotations is in one of these states: + /// 1) Null, if the element has no transient annotations. + /// 2) An EdmVocabularyAnnotation, if the element has exactly one annotation. + /// 3) A list of EdmVocabularyAnnotation, if the element has more than one annotation. + /// If the speed of annotation lookup for elements with many annotations becomes a concern, another option + /// including a dictionary is possible. + /// + public class EdmDirectValueAnnotationsManager : IEdmDirectValueAnnotationsManager + { + /// + /// Keeps track of transient annotations on elements. + /// + private VersioningDictionary annotationsDictionary; + + /// + /// Used for locking during updates to the annotations dictionary; + /// + private object annotationsDictionaryLock = new object(); + + /// + /// Elements for which normal comparison failed to produce a valid result, arbitrarily ordered to enable stable comparisons. + /// + private VersioningList unsortedElements = VersioningList.Create(); + + /// + /// Used for locking during updates to the unsorted elements list. + /// + private object unsortedElementsLock = new object(); + + /// + /// Initializes a new instance of the class. + /// + public EdmDirectValueAnnotationsManager() + { + this.annotationsDictionary = VersioningDictionary.Create(this.CompareElements); + } + + /// + /// Gets annotations associated with an element. + /// + /// The annotated element. + /// The immediate annotations for the element. + public IEnumerable GetDirectValueAnnotations(IEdmElement element) + { + // Fetch the annotations dictionary once and only once, because this.annotationsDictionary might get updated by another thread. + VersioningDictionary annotationsDictionary = this.annotationsDictionary; + + IEnumerable immutableAnnotations = this.GetAttachedAnnotations(element); + object transientAnnotations = GetTransientAnnotations(element, annotationsDictionary); + + if (immutableAnnotations != null) + { + foreach (IEdmDirectValueAnnotation existingAnnotation in immutableAnnotations) + { + if (!IsDead(existingAnnotation.NamespaceUri, existingAnnotation.Name, transientAnnotations)) + { + yield return existingAnnotation; + } + } + } + + foreach (IEdmDirectValueAnnotation existingAnnotation in TransientAnnotations(transientAnnotations)) + { + yield return existingAnnotation; + } + } + + /// + /// Sets an annotation value for an EDM element. If the value is null, no annotation is added and an existing annotation with the same name is removed. + /// + /// The annotated element. + /// Namespace that the annotation belongs to. + /// Name of the annotation within the namespace. + /// New annotation to set. + public void SetAnnotationValue(IEdmElement element, string namespaceName, string localName, object value) + { + lock (this.annotationsDictionaryLock) + { + // Use a local variable to store any interim changes to the annotations dictionary, and perform one atomic update + // to the field. Otherwise, other threads could see a dictionary in an interim state. + VersioningDictionary annotationsDictionary = this.annotationsDictionary; + this.SetAnnotationValue(element, namespaceName, localName, value, ref annotationsDictionary); + + this.annotationsDictionary = annotationsDictionary; + } + } + + /// + /// Sets a set of annotation values. If a supplied value is null, no annotation is added and an existing annotation with the same name is removed. + /// + /// The annotations to set + public void SetAnnotationValues(IEnumerable annotations) + { + lock (this.annotationsDictionaryLock) + { + // Use a local variable to store any interim changes to the annotations dictionary, and perform one atomic update + // to the field. Otherwise, other threads could see a dictionary in an interim state. + VersioningDictionary annotationsDictionary = this.annotationsDictionary; + + foreach (IEdmDirectValueAnnotationBinding annotation in annotations) + { + this.SetAnnotationValue(annotation.Element, annotation.NamespaceUri, annotation.Name, annotation.Value, ref annotationsDictionary); + } + + this.annotationsDictionary = annotationsDictionary; + } + } + + /// + /// Retrieves an annotation value for an EDM element. Returns null if no annotation with the given name exists for the given element. + /// + /// The annotated element. + /// Namespace that the annotation belongs to. + /// Local name of the annotation. + /// Returns the annotation that corresponds to the provided name. Returns null if no annotation with the given name exists for the given element. + public object GetAnnotationValue(IEdmElement element, string namespaceName, string localName) + { + // Fetch the annotations dictionary once and only once, because this.annotationsDictionary might get updated by another thread. + VersioningDictionary annotationsDictionary = this.annotationsDictionary; + + return this.GetAnnotationValue(element, namespaceName, localName, annotationsDictionary); + } + + /// + /// Retrieves a set of annotation values. For each requested value, returns null if no annotation with the given name exists for the given element. + /// + /// The set of requested annotations + /// Returns values that correspond to the provided annotations. A value is null if no annotation with the given name exists for the given element. + public object[] GetAnnotationValues(IEnumerable annotations) + { + // Fetch the annotations dictionary once and only once, because this.annotationsDictionary might get updated by another thread. + VersioningDictionary annotationsDictionary = this.annotationsDictionary; + + object[] values = new object[annotations.Count()]; + + int index = 0; + foreach (IEdmDirectValueAnnotationBinding annotation in annotations) + { + values[index++] = this.GetAnnotationValue(annotation.Element, annotation.NamespaceUri, annotation.Name, annotationsDictionary); + } + + return values; + } + + /// + /// Retrieves the annotations that are directly attached to an element. + /// + /// The element in question. + /// The annotations that are directly attached to an element (outside the control of the manager). + protected virtual IEnumerable GetAttachedAnnotations(IEdmElement element) + { + return null; + } + + private static void SetAnnotation(IEnumerable immutableAnnotations, ref object transientAnnotations, string namespaceName, string localName, object value) + { + bool needTombstone = false; + if (immutableAnnotations != null) + { + if (immutableAnnotations.Any(existingAnnotation => existingAnnotation.NamespaceUri == namespaceName && existingAnnotation.Name == localName)) + { + needTombstone = true; + } + } + + if (value == null) + { + // "Removing" an immutable annotation leaves behind a transient annotation with a null value + // as a tombstone to hide the immutable annotation. The normal logic below makes this happen. + // Removing a transient annotation actually takes the annotation away. + if (!needTombstone) + { + RemoveTransientAnnotation(ref transientAnnotations, namespaceName, localName); + return; + } + } + + IEdmDirectValueAnnotation newAnnotation = value != null ? + new EdmDirectValueAnnotation(namespaceName, localName, value) : + new EdmDirectValueAnnotation(namespaceName, localName); + + if (transientAnnotations == null) + { + transientAnnotations = newAnnotation; + return; + } + + IEdmDirectValueAnnotation singleAnnotation = transientAnnotations as IEdmDirectValueAnnotation; + if (singleAnnotation != null) + { + if (singleAnnotation.NamespaceUri == namespaceName && singleAnnotation.Name == localName) + { + transientAnnotations = newAnnotation; + } + else + { + transientAnnotations = VersioningList.Create().Add(singleAnnotation).Add(newAnnotation); + } + + return; + } + + VersioningList annotationsList = (VersioningList)transientAnnotations; + for (int index = 0; index < annotationsList.Count; index++) + { + IEdmDirectValueAnnotation existingAnnotation = annotationsList[index]; + if (existingAnnotation.NamespaceUri == namespaceName && existingAnnotation.Name == localName) + { + annotationsList = annotationsList.RemoveAt(index); + break; + } + } + + transientAnnotations = annotationsList.Add(newAnnotation); + } + + private static IEdmDirectValueAnnotation FindTransientAnnotation(object transientAnnotations, string namespaceName, string localName) + { + if (transientAnnotations != null) + { + IEdmDirectValueAnnotation singleAnnotation = transientAnnotations as IEdmDirectValueAnnotation; + if (singleAnnotation != null) + { + if (singleAnnotation.NamespaceUri == namespaceName && singleAnnotation.Name == localName) + { + return singleAnnotation; + } + } + else + { + VersioningList annotationsList = (VersioningList)transientAnnotations; + return annotationsList.FirstOrDefault( + existingAnnotation => existingAnnotation.NamespaceUri == namespaceName && existingAnnotation.Name == localName); + } + } + + return null; + } + + private static void RemoveTransientAnnotation(ref object transientAnnotations, string namespaceName, string localName) + { + if (transientAnnotations != null) + { + IEdmDirectValueAnnotation singleAnnotation = transientAnnotations as IEdmDirectValueAnnotation; + if (singleAnnotation != null) + { + if (singleAnnotation.NamespaceUri == namespaceName && singleAnnotation.Name == localName) + { + transientAnnotations = null; + return; + } + } + else + { + VersioningList annotationsList = (VersioningList)transientAnnotations; + for (int index = 0; index < annotationsList.Count; index++) + { + IEdmDirectValueAnnotation existingAnnotation = annotationsList[index]; + if (existingAnnotation.NamespaceUri == namespaceName && existingAnnotation.Name == localName) + { + annotationsList = annotationsList.RemoveAt(index); + if (annotationsList.Count == 1) + { + transientAnnotations = annotationsList.Single(); + } + else + { + transientAnnotations = annotationsList; + } + + return; + } + } + } + } + } + + private static IEnumerable TransientAnnotations(object transientAnnotations) + { + if (transientAnnotations == null) + { + yield break; + } + + IEdmDirectValueAnnotation singleAnnotation = transientAnnotations as IEdmDirectValueAnnotation; + if (singleAnnotation != null) + { + if (singleAnnotation.Value != null) + { + yield return singleAnnotation; + } + + yield break; + } + + VersioningList annotationsList = (VersioningList)transientAnnotations; + foreach (IEdmDirectValueAnnotation existingAnnotation in annotationsList) + { + if (existingAnnotation.Value != null) + { + yield return existingAnnotation; + } + } + } + + private static bool IsDead(string namespaceName, string localName, object transientAnnotations) + { + return FindTransientAnnotation(transientAnnotations, namespaceName, localName) != null; + } + + /// + /// Retrieves the transient annotations for an EDM element. + /// + /// The annotated element. + /// The dictionary for looking up the element's annotations. + /// The transient annotations for the element, in a form managed by the annotations manager. + /// This method is static to guarantee that the annotations dictionary is not fetched more than once per lookup operation. + private static object GetTransientAnnotations(IEdmElement element, VersioningDictionary annotationsDictionary) + { + object transientAnnotations; + annotationsDictionary.TryGetValue(element, out transientAnnotations); + return transientAnnotations; + } + + private void SetAnnotationValue(IEdmElement element, string namespaceName, string localName, object value, ref VersioningDictionary annotationsDictionary) + { + object transientAnnotations = GetTransientAnnotations(element, annotationsDictionary); + object transientAnnotationsBeforeSet = transientAnnotations; + SetAnnotation(this.GetAttachedAnnotations(element), ref transientAnnotations, namespaceName, localName, value); + + // There is at least one case (removing an annotation that was not present to begin with) where the transient annotations are not changed, + // so test to see if updating the dictionary is necessary. + if (transientAnnotations != transientAnnotationsBeforeSet) + { + annotationsDictionary = annotationsDictionary.Set(element, transientAnnotations); + } + } + + private object GetAnnotationValue(IEdmElement element, string namespaceName, string localName, VersioningDictionary annotationsDictionary) + { + IEdmDirectValueAnnotation annotation = FindTransientAnnotation(GetTransientAnnotations(element, annotationsDictionary), namespaceName, localName); + if (annotation != null) + { + return annotation.Value; + } + + IEnumerable immutableAnnotations = this.GetAttachedAnnotations(element); + if (immutableAnnotations != null) + { + foreach (IEdmDirectValueAnnotation existingAnnotation in immutableAnnotations) + { + if (existingAnnotation.NamespaceUri == namespaceName && existingAnnotation.Name == localName) + { + // No need to check that the immutable annotation isn't Dead, because if it were + // the tombstone would have been found in the transient annotations. + return existingAnnotation.Value; + } + } + } + + return null; + } + + private int CompareElements(IEdmElement left, IEdmElement right) + { + if (left == right) + { + return 0; + } + + /* Left and right are distinct. */ + + int leftHash = left.GetHashCode(); + int rightHash = right.GetHashCode(); + + if (leftHash < rightHash) + { + return -1; + } + + if (leftHash > rightHash) + { + return 1; + } + + /* Left and right are distinct and have identical hash codes. */ + + IEdmNamedElement leftNamed = left as IEdmNamedElement; + IEdmNamedElement rightNamed = right as IEdmNamedElement; + + if (leftNamed == null) + { + if (rightNamed != null) + { + return -1; + } + } + else if (rightNamed == null) + { + return 1; + } + else + { + /* Left and right are both named. */ + + int nameComparison = string.Compare(leftNamed.Name, rightNamed.Name, StringComparison.Ordinal); + + if (nameComparison != 0) + { + return nameComparison; + } + } + + /* Left and right are distinct, have identical hash codes, and have identical names. */ + + /* The first element to occur in the unsorted list is the greatest. */ + + while (true) + { + foreach (IEdmElement element in this.unsortedElements) + { + if (element == left) + { + return 1; + } + + if (element == right) + { + return -1; + } + } + + lock (this.unsortedElementsLock) + { + this.unsortedElements = this.unsortedElements.Add(left); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmPropertyValueBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmPropertyValueBinding.cs new file mode 100644 index 0000000..132fbfb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmPropertyValueBinding.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents a property binding specified as part of an EDM type annotation. + /// + public class EdmPropertyValueBinding : EdmElement, IEdmPropertyValueBinding + { + private readonly IEdmProperty boundProperty; + private readonly IEdmExpression value; + + /// + /// Initializes a new instance of the class. + /// + /// Property that is given a value by the annotation. + /// Expression producing the value of the annotation. + public EdmPropertyValueBinding(IEdmProperty boundProperty, IEdmExpression value) + { + EdmUtil.CheckArgumentNull(boundProperty, "boundProperty"); + EdmUtil.CheckArgumentNull(value, "value"); + + this.boundProperty = boundProperty; + this.value = value; + } + + /// + /// Gets the property that is given a value by the annotation. + /// + public IEdmProperty BoundProperty + { + get { return this.boundProperty; } + } + + /// + /// Gets the expression producing the value of the annotation. + /// + public IEdmExpression Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTerm.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTerm.cs new file mode 100644 index 0000000..bf1a410 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTerm.cs @@ -0,0 +1,137 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM term. + /// + public class EdmTerm : EdmNamedElement, IEdmTerm, IEdmFullNamedElement + { + private readonly string namespaceName; + private readonly string fullName; + private readonly IEdmTypeReference type; + private readonly string appliesTo; + private readonly string defaultValue; + + /// + /// Initializes a new instance of class. + /// The new term will be of the nullable primitive . + /// + /// Namespace of the term. + /// Name of the term. + /// Type of the term. + public EdmTerm(string namespaceName, string name, EdmPrimitiveTypeKind type) + : this(namespaceName, name, type, null) + { + } + + /// + /// Initializes a new instance of class. + /// The new term will be of the nullable primitive . + /// + /// Namespace of the term. + /// Name of the term. + /// Type of the term. + /// AppliesTo of the term. + public EdmTerm(string namespaceName, string name, EdmPrimitiveTypeKind type, string appliesTo) + : this(namespaceName, name, EdmCoreModel.Instance.GetPrimitive(type, true), appliesTo) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace of the term. + /// Name of the term. + /// Type of the term. + public EdmTerm(string namespaceName, string name, IEdmTypeReference type) + : this(namespaceName, name, type, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace of the term. + /// Name of the term. + /// Type of the term. + /// AppliesTo of the term. + public EdmTerm(string namespaceName, string name, IEdmTypeReference type, string appliesTo) + : this(namespaceName, name, type, appliesTo, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Namespace of the term. + /// Name of the term. + /// Type of the term. + /// AppliesTo of the term. + /// DefaultValue of the term. + public EdmTerm(string namespaceName, string name, IEdmTypeReference type, string appliesTo, string defaultValue) + : base(name) + { + EdmUtil.CheckArgumentNull(namespaceName, "namespaceName"); + EdmUtil.CheckArgumentNull(type, "type"); + + this.namespaceName = namespaceName; + this.type = type; + this.appliesTo = appliesTo; + this.defaultValue = defaultValue; + this.fullName = EdmUtil.GetFullNameForSchemaElement(this.namespaceName, this.Name); + } + + /// + /// Gets the namespace of this term. + /// + public string Namespace + { + get { return this.namespaceName; } + } + + /// + /// Gets the full name of this schema element. + /// + public string FullName + { + get { return this.fullName; } + } + + /// + /// Gets the type of this term. + /// + public IEdmTypeReference Type + { + get { return this.type; } + } + + /// + /// Gets the AppliesTo of this term. + /// + public string AppliesTo + { + get { return this.appliesTo; } + } + + /// + /// Gets the DefaultValue of this term. + /// + public string DefaultValue + { + get { return this.defaultValue; } + } + + /// + /// Gets the schema element kind of this term. + /// + public EdmSchemaElementKind SchemaElementKind + { + get { return EdmSchemaElementKind.Term; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTypedDirectValueAnnotationBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTypedDirectValueAnnotationBinding.cs new file mode 100644 index 0000000..5681353 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmTypedDirectValueAnnotationBinding.cs @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents the combination of an EDM annotation with an immediate value and the element to which it is attached. + /// + /// Type of the annotation value. + public class EdmTypedDirectValueAnnotationBinding : EdmNamedElement, IEdmDirectValueAnnotationBinding + { + private readonly IEdmElement element; + private readonly T value; + + /// + /// Initializes a new instance of the class. + /// + /// Element to which the annotation is attached. + /// Value of the annotation + public EdmTypedDirectValueAnnotationBinding(IEdmElement element, T value) + : base(ExtensionMethods.TypeName.LocalName) + { + this.element = element; + this.value = value; + } + + /// + /// Gets the element to which the annotation is attached. + /// + public IEdmElement Element + { + get { return this.element; } + } + + /// + /// Gets the namespace Uri of the annotation. + /// + public string NamespaceUri + { + get { return EdmConstants.InternalUri; } + } + + /// + /// Gets the value of this annotation. + /// + public object Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmVocabularyAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmVocabularyAnnotation.cs new file mode 100644 index 0000000..dd871b8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/EdmVocabularyAnnotation.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM annotation with an immediate value. + /// + public class EdmVocabularyAnnotation : EdmElement, IEdmVocabularyAnnotation + { + private readonly IEdmVocabularyAnnotatable target; + private readonly IEdmTerm term; + private readonly string qualifier; + private readonly IEdmExpression value; + + /// + /// Initializes a new instance of the class. + /// + /// Element the annotation applies to. + /// Term bound by the annotation. + /// Expression producing the value of the annotation. + public EdmVocabularyAnnotation(IEdmVocabularyAnnotatable target, IEdmTerm term, IEdmExpression value) + : this(target, term, null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Element the annotation applies to. + /// Term bound by the annotation. + /// Qualifier used to discriminate between multiple bindings of the same property or type. + /// Expression producing the value of the annotation. + public EdmVocabularyAnnotation(IEdmVocabularyAnnotatable target, IEdmTerm term, string qualifier, IEdmExpression value) + { + EdmUtil.CheckArgumentNull(target, "target"); + EdmUtil.CheckArgumentNull(term, "term"); + EdmUtil.CheckArgumentNull(value, "value"); + + this.target = target; + this.term = term; + this.qualifier = qualifier; + this.value = value; + } + + /// + /// Gets the element the annotation applies to. + /// + public IEdmVocabularyAnnotatable Target + { + get { return this.target; } + } + + /// + /// Gets the term bound by the annotation. + /// + public IEdmTerm Term + { + get { return this.term; } + } + + /// + /// Gets the qualifier used to discriminate between multiple bindings of the same property or type. + /// + public string Qualifier + { + get { return this.qualifier; } + } + + /// + /// Gets the expression producing the value of the annotation. + /// + public IEdmExpression Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotation.cs new file mode 100644 index 0000000..1d937b0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotation.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM annotation with an immediate value. + /// + public interface IEdmDirectValueAnnotation : IEdmNamedElement + { + /// + /// Gets the namespace Uri of the annotation. + /// + string NamespaceUri { get; } + + /// + /// Gets the value of this annotation. + /// + object Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationBinding.cs new file mode 100644 index 0000000..cb60a47 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationBinding.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents the combination of an EDM annotation with an immediate value and the element to which it is attached. + /// + public interface IEdmDirectValueAnnotationBinding + { + /// + /// Gets the element to which the annotation is attached + /// + IEdmElement Element { get; } + + /// + /// Gets the namespace URI of the annotation. + /// + string NamespaceUri { get; } + + /// + /// Gets the local name of this annotation. + /// + string Name { get; } + + /// + /// Gets the value of this annotation. + /// + object Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationsManager.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationsManager.cs new file mode 100644 index 0000000..3e3b8bd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmDirectValueAnnotationsManager.cs @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Manages getting and setting direct annotations on EDM elements. + /// + public interface IEdmDirectValueAnnotationsManager + { + /// + /// Gets annotations associated with an element. + /// + /// The annotated element. + /// The direct annotations for the element. + IEnumerable GetDirectValueAnnotations(IEdmElement element); + + /// + /// Sets an annotation value for an EDM element. If the value is null, no annotation is added and an existing annotation with the same name is removed. + /// + /// The annotated element. + /// Namespace that the annotation belongs to. + /// Name of the annotation within the namespace. + /// The value of the annotation. + void SetAnnotationValue(IEdmElement element, string namespaceName, string localName, object value); + + /// + /// Sets a set of annotation values. If a supplied value is null, no annotation is added and an existing annotation with the same name is removed. + /// + /// The annotations to set + void SetAnnotationValues(IEnumerable annotations); + + /// + /// Retrieves an annotation value for an EDM element. Returns null if no annotation with the given name exists for the given element. + /// + /// The annotated element. + /// Namespace that the annotation belongs to. + /// Local name of the annotation. + /// Returns the annotation value that corresponds to the provided name. Returns null if no annotation with the given name exists for the given element. + object GetAnnotationValue(IEdmElement element, string namespaceName, string localName); + + /// + /// Retrieves a set of annotation values. For each requested value, returns null if no annotation with the given name exists for the given element. + /// + /// The set of requested annotations + /// Returns values that correspond to the provided annotations. A value is null if no annotation with the given name exists for the given element. + object[] GetAnnotationValues(IEnumerable annotations); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmPropertyValueBinding.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmPropertyValueBinding.cs new file mode 100644 index 0000000..f774d59 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmPropertyValueBinding.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents a property binding specified as part of an EDM type annotation. + /// + public interface IEdmPropertyValueBinding : IEdmElement + { + /// + /// Gets the property that is given a value by the annotation. + /// + IEdmProperty BoundProperty { get; } + + /// + /// Gets the expression producing the value of the annotation. + /// + IEdmExpression Value { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmTerm.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmTerm.cs new file mode 100644 index 0000000..c511180 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmTerm.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM term. + /// + public interface IEdmTerm : IEdmSchemaElement + { + /// + /// Gets the type of this term. + /// + IEdmTypeReference Type { get; } + + /// + /// Gets the AppliesTo of this term. + /// + string AppliesTo { get; } + + /// + /// Gets the DefaultValue of this term. + /// + string DefaultValue { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotatable.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotatable.cs new file mode 100644 index 0000000..4f5154e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotatable.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an element that can be targeted by Vocabulary Annotations + /// + public interface IEdmVocabularyAnnotatable : IEdmElement + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotation.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotation.cs new file mode 100644 index 0000000..41a7bab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Annotations/IEdmVocabularyAnnotation.cs @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM vocabulary annotation. + /// + public interface IEdmVocabularyAnnotation : IEdmElement + { + /// + /// Gets the qualifier used to discriminate between multiple bindings of the same property or type. + /// + string Qualifier { get; } + + /// + /// Gets the term bound by the annotation. + /// + IEdmTerm Term { get; } + + /// + /// Gets the element the annotation applies to. + /// + IEdmVocabularyAnnotatable Target { get; } + + /// + /// Gets the expression producing the value of the annotation. + /// + IEdmExpression Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularies.xml b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularies.xml new file mode 100644 index 0000000..1492b61 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularies.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularyModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularyModel.cs new file mode 100644 index 0000000..70faf9f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/AuthorizationVocabularyModel.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies.V1 +{ + /// + /// Representing Org.OData.Authorization.V1 Vocabulary Model. + /// + internal class AuthorizationVocabularyModel + { + /// + /// The EDM model with authorization vocabularies. + /// + public static readonly IEdmModel Instance = VocabularyModelProvider.AuthorizationModel; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularies.xml b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularies.xml new file mode 100644 index 0000000..04334f4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularies.xml @@ -0,0 +1,926 @@ + + + + + + + + + + + + + + + + + Terms describing capabilities of a service + + + + + + + + + + + + + + + + + + + +There are some capabilities which are strongly recommended for services to support even +though they are optional. Support for $top and $skip is a good example as +supporting these query options helps with performance of a service and are essential. Such +capabilities are assumed to be default capabilities of an OData service even in +the case that a capabilities annotation doesn’t exist. Capabilities annotations are +mainly expected to be used to explicitly specify that a service doesn’t support such +capabilities. Capabilities annotations can as well be used to declaratively +specify the support of such capabilities. + +On the other hand, there are some capabilities that a service may choose to support or +not support and in varying degrees. $filter and $orderby are such good examples. +This vocabulary aims to define terms to specify support or no support for such +capabilities. + +A service is assumed to support by default the following capabilities even though an +annotation doesn’t exist: +- Countability ($count) +- Client pageability ($top, $skip) +- Expandability ($expand) +- Indexability by key +- Batch support ($batch) +- Navigability of navigation properties + +A service is expected to support the following capabilities. If not supported, the +service is expected to call out the restrictions using annotations: +- Filterability ($filter) +- Sortability ($orderby) +- Queryability of top level entity sets +- Query functions + +A client cannot assume that a service supports certain capabilities. A client can try, but +it needs to be prepared to handle an error in case the following capabilities are not +supported: +- Insertability +- Updatability +- Deletability + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Possible string value identifiers when specifying properties are '*', _PropertyName_, '-'_PropertyName_. Where, '*' denotes all properties are accessible,'-'_PropertyName_ excludes that specific property and _PropertyName_ explicitly provides access to the specific property. The absence of 'RestrictedProperties' denotes all properties are accessible using that scope. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyConstants.cs new file mode 100644 index 0000000..821c025 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyConstants.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies.V1 +{ + /// + /// Constant values for Capabilities Vocabulary + /// + public static class CapabilitiesVocabularyConstants + { + /// Org.OData.Capabilities.V1.ChangeTracking + public const string ChangeTracking = "Org.OData.Capabilities.V1.ChangeTracking"; + + /// Property Supported of Org.OData.Capabilities.V1.ChangeTracking + public const string ChangeTrackingSupported = "Supported"; + + /// Property FilterableProperties of Org.OData.Capabilities.V1.ChangeTracking + public const string ChangeTrackingFilterableProperties = "FilterableProperties"; + + /// Property ExpandableProperties of Org.OData.Capabilities.V1.ChangeTracking + public const string ChangeTrackingExpandableProperties = "ExpandableProperties"; + + /// Org.OData.Capabilities.V1.xml file suffix + internal const string VocabularyUrlSuffix = "/Org.OData.Capabilities.V1.xml"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyModel.cs new file mode 100644 index 0000000..35f5114 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CapabilitiesVocabularyModel.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies.V1 +{ + /// + /// Representing Capabilities Vocabulary Model. + /// + internal class CapabilitiesVocabularyModel + { + /// + /// The EDM model with capabilities vocabularies. + /// + public static readonly IEdmModel Instance = VocabularyModelProvider.CapabilitesModel; + + /// + /// The change tracking term. + /// + public static readonly IEdmTerm ChangeTrackingTerm = VocabularyModelProvider.CapabilitesModel.FindDeclaredTerm(CapabilitiesVocabularyConstants.ChangeTracking); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularies.xml b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularies.xml new file mode 100644 index 0000000..18a1be4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularies.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyConstants.cs new file mode 100644 index 0000000..03c5142 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyConstants.cs @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies.Community.V1 +{ + /// + /// Constant values for Community Vocabularies + /// + internal static class CommunityVocabularyConstants + { + /// OData.Community.V1.UrlEscapeFunction + internal const string UrlEscapeFunction = "Org.OData.Community.V1.UrlEscapeFunction"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyModel.cs new file mode 100644 index 0000000..3110024 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CommunityVocabularyModel.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; +using Microsoft.OData.Edm.Vocabularies.V1; + +namespace Microsoft.OData.Edm.Vocabularies.Community.V1 +{ + /// + /// Representing Community Vocabulary Model. + /// + public static class CommunityVocabularyModel + { + /// + /// The EDM model with Community vocabularies. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmModel is immutable")] + public static readonly IEdmModel Instance = VocabularyModelProvider.CommunityModel; + + /// + /// The Url escape function term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm UrlEscapeFunctionTerm = VocabularyModelProvider.CommunityModel.FindDeclaredTerm(CommunityVocabularyConstants.UrlEscapeFunction); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularies.xml b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularies.xml new file mode 100644 index 0000000..d06d0dc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularies.xml @@ -0,0 +1,388 @@ + + + + Core terms needed to write vocabularies + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Property|Type +:-------|:--- +Any simple identifier | Any type listed in `Validation.OpenPropertyTypeConstraint`, or any type if there is no constraint + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyConstants.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyConstants.cs new file mode 100644 index 0000000..8453496 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyConstants.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies.V1 +{ + /// + /// Constant values for Core Vocabulary + /// + public static class CoreVocabularyConstants + { + /// Org.OData.Core.V1.Description + public const string Description = "Org.OData.Core.V1.Description"; + + /// Org.OData.Core.V1.LongDescription + public const string LongDescription = "Org.OData.Core.V1.LongDescription"; + + /// Org.OData.Core.V1.IsLanguageDependent + public const string IsLanguageDependent = "Org.OData.Core.V1.IsLanguageDependent"; + + /// Org.OData.Core.V1.RequiresType + public const string RequiresType = "Org.OData.Core.V1.RequiresType"; + + /// Org.OData.Core.V1.ResourcePath + public const string ResourcePath = "Org.OData.Core.V1.ResourcePath"; + + /// Org.OData.Core.V1.DereferenceableIDs + public const string DereferenceableIDs = "Org.OData.Core.V1.DereferenceableIDs"; + + /// Org.OData.Core.V1.ConventionalIDs + public const string ConventionalIDs = "Org.OData.Core.V1.ConventionalIDs"; + + /// Org.OData.Core.V1.Permissions + public const string Permissions = "Org.OData.Core.V1.Permissions"; + + /// Org.OData.Core.V1.Immutable + public const string Immutable = "Org.OData.Core.V1.Immutable"; + + /// Org.OData.Core.V1.Computed + public const string Computed = "Org.OData.Core.V1.Computed"; + + /// Org.OData.Core.V1.IsURL + public const string IsURL = "Org.OData.Core.V1.IsURL"; + + /// Org.OData.Core.V1.AcceptableMediaTypes + public const string AcceptableMediaTypes = "Org.OData.Core.V1.AcceptableMediaTypes"; + + /// Org.OData.Core.V1.MediaType + public const string MediaType = "Org.OData.Core.V1.MediaType"; + + /// Org.OData.Core.V1.IsMediaType + public const string IsMediaType = "Org.OData.Core.V1.IsMediaType"; + + /// Org.OData.Core.V1.OptimisticConcurrency + public const string OptimisticConcurrency = "Org.OData.Core.V1.OptimisticConcurrency"; + + /// Org.OData.Core.V1.OptionalParameter + public const string OptionalParameter = "Org.OData.Core.V1.OptionalParameter"; + + /// Org.OData.Core.V1.xml file suffix + internal const string VocabularyUrlSuffix = "/Org.OData.Core.V1.xml"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyModel.cs new file mode 100644 index 0000000..f3f84fb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyModel.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.OData.Edm.Vocabularies.V1 +{ + /// + /// Representing Core Vocabulary Model. + /// + public static class CoreVocabularyModel + { + /// + /// The EDM model with core vocabularies. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmModel is immutable")] + public static readonly IEdmModel Instance = VocabularyModelProvider.CoreModel; + + /// + /// The concurrency term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm ConcurrencyTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.OptimisticConcurrency); + + /// + /// The description term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm DescriptionTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.Description); + + /// + /// The description term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm LongDescriptionTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.LongDescription); + + /// + /// The IsLanguageDependent term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm IsLanguageDependentTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.IsLanguageDependent); + + /// + /// The RequiresType term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm RequiresTypeTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.RequiresType); + + /// + /// The ResourcePath term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm ResourcePathTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.ResourcePath); + + /// + /// The DereferenceableIDs term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm DereferenceableIDsTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.DereferenceableIDs); + + /// + /// The ConventionalIDs term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm ConventionalIDsTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.ConventionalIDs); + + /// + /// The Immutable term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm ImmutableTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.Immutable); + + /// + /// The Computed term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm ComputedTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.Computed); + + /// + /// The Optional Parameter term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm OptionalParameterTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.OptionalParameter); + + /// + /// The IsURL term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm IsURLTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.IsURL); + + /// + /// The AcceptableMediaTypes term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm AcceptableMediaTypesTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.AcceptableMediaTypes); + + /// + /// The MediaType term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm MediaTypeTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.MediaType); + + /// + /// The IsMediaType term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm IsMediaTypeTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.IsMediaType); + + /// + /// The Permissions Term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm PermissionsTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.Permissions); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmExpressionEvaluator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmExpressionEvaluator.cs new file mode 100644 index 0000000..9a66699 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmExpressionEvaluator.cs @@ -0,0 +1,922 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.OData.Edm.Csdl; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Expression evaluator. + /// + public class EdmExpressionEvaluator + { + private readonly IDictionary> builtInFunctions; + private readonly Dictionary labeledValues = new Dictionary(); + private readonly Func lastChanceOperationApplier; +#if !ORCAS + private readonly Func getAnnotationExpressionForType; + private readonly Func getAnnotationExpressionForProperty; + + private readonly IEdmModel edmModel; +#endif + private Func resolveTypeFromName = (typeName, edmModel) => FindEdmType(typeName, edmModel); + + /// + /// Initializes a new instance of the EdmExpressionEvaluator class. + /// + /// Builtin functions dictionary to the evaluators for the functions. + public EdmExpressionEvaluator(IDictionary> builtInFunctions) + { + this.builtInFunctions = builtInFunctions; + } + + /// + /// Initializes a new instance of the EdmExpressionEvaluator class. + /// + /// Builtin functions dictionary to the evaluators for the functions. + /// Function to call to evaluate an application of a function with no static binding. + public EdmExpressionEvaluator(IDictionary> builtInFunctions, Func lastChanceOperationApplier) + : this(builtInFunctions) + { + this.lastChanceOperationApplier = lastChanceOperationApplier; + } + +#if !ORCAS + /// + /// Initializes a new instance of the EdmExpressionEvaluator class. + /// + /// Builtin functions dictionary to the evaluators for the functions. + /// Function to call to evaluate an application of a function with no static binding. + /// Function to get the of an annotation of an . + /// Function to get the of an annotation of a property or navigation property in . + /// The edm model. + public EdmExpressionEvaluator( + IDictionary> builtInFunctions, + Func lastChanceOperationApplier, + Func getAnnotationExpressionForType, + Func getAnnotationExpressionForProperty, + IEdmModel edmModel) + : this(builtInFunctions, lastChanceOperationApplier) + { + this.getAnnotationExpressionForType = getAnnotationExpressionForType; + this.getAnnotationExpressionForProperty = getAnnotationExpressionForProperty; + this.edmModel = edmModel; + } +#endif + + /// + /// Function used to get edm type based on and the type name. + /// + protected Func ResolveTypeFromName + { + get { return this.resolveTypeFromName; } + set { this.resolveTypeFromName = value; } + } + + /// + /// Evaluates an expression with no value context. + /// + /// Expression to evaluate. The expression must not contain paths, because no context for evaluating a path is supplied. + /// The value that results from evaluating the expression in the context of the supplied value. + public IEdmValue Evaluate(IEdmExpression expression) + { + EdmUtil.CheckArgumentNull(expression, "expression"); + + return this.Eval(expression, null); + } + + /// + /// Evaluates an expression in the context of a value. + /// + /// Expression to evaluate. + /// Value to use as context in evaluating the expression. Cannot be null if the expression contains paths. + /// The value that results from evaluating the expression in the context of the supplied value. + public IEdmValue Evaluate(IEdmExpression expression, IEdmStructuredValue context) + { + EdmUtil.CheckArgumentNull(expression, "expression"); + + return this.Eval(expression, context); + } + + /// + /// Evaluates an expression in the context of a value and a target type. + /// + /// Expression to evaluate. + /// Value to use as context in evaluating the expression. Cannot be null if the expression contains paths. + /// Type to which the result value is expected to conform. + /// The value that results from evaluating the expression in the context of the supplied value, asserted to be of the target type. + public IEdmValue Evaluate(IEdmExpression expression, IEdmStructuredValue context, IEdmTypeReference targetType) + { + EdmUtil.CheckArgumentNull(expression, "expression"); + EdmUtil.CheckArgumentNull(targetType, "targetType"); + + return Cast(targetType, this.Eval(expression, context)); + } + + /// + /// Get the of a specified edm type name from an . + /// + /// The specified edm type name. + /// The edm model. + /// The requested type, or null if no such type exists. + protected static IEdmType FindEdmType(string edmTypeName, IEdmModel edmModel) + { + return edmModel.FindDeclaredType(edmTypeName); + } + + private static bool InRange(Int64 value, Int64 min, Int64 max) + { + return value >= min && value <= max; + } + + private static bool FitsInSingle(double value) + { + return value >= Single.MinValue && value <= Single.MaxValue; + } + + private static bool MatchesType(IEdmTypeReference targetType, IEdmValue operand) + { + return MatchesType(targetType, operand, true); + } + + private static bool MatchesType(IEdmTypeReference targetType, IEdmValue operand, bool testPropertyTypes) + { + IEdmTypeReference operandType = operand.Type; + EdmValueKind operandKind = operand.ValueKind; + + if (operandType != null && operandKind != EdmValueKind.Null && operandType.Definition.IsOrInheritsFrom(targetType.Definition)) + { + return true; + } + + switch (operandKind) + { + case EdmValueKind.Binary: + if (targetType.IsBinary()) + { + IEdmBinaryTypeReference targetBinary = targetType.AsBinary(); + return targetBinary.IsUnbounded || !targetBinary.MaxLength.HasValue || targetBinary.MaxLength.Value >= ((IEdmBinaryValue)operand).Value.Length; + } + + break; + case EdmValueKind.Boolean: + return targetType.IsBoolean(); + case EdmValueKind.Date: + return targetType.IsDate(); + case EdmValueKind.DateTimeOffset: + return targetType.IsDateTimeOffset(); + case EdmValueKind.Decimal: + return targetType.IsDecimal(); + case EdmValueKind.Guid: + return targetType.IsGuid(); + case EdmValueKind.Null: + return targetType.IsNullable; + case EdmValueKind.Duration: + return targetType.IsDuration(); + case EdmValueKind.String: + if (targetType.IsString()) + { + IEdmStringTypeReference targetString = targetType.AsString(); + return targetString.IsUnbounded || !targetString.MaxLength.HasValue || targetString.MaxLength.Value >= ((IEdmStringValue)operand).Value.Length; + } + + break; + case EdmValueKind.TimeOfDay: + return targetType.IsTimeOfDay(); + case EdmValueKind.Floating: + return targetType.IsDouble() || (targetType.IsSingle() && FitsInSingle(((IEdmFloatingValue)operand).Value)); + case EdmValueKind.Integer: + if (targetType.TypeKind() == EdmTypeKind.Primitive) + { + switch (targetType.AsPrimitive().PrimitiveKind()) + { + case EdmPrimitiveTypeKind.Int16: + return InRange(((IEdmIntegerValue)operand).Value, Int16.MinValue, Int16.MaxValue); + case EdmPrimitiveTypeKind.Int32: + return InRange(((IEdmIntegerValue)operand).Value, Int32.MinValue, Int32.MaxValue); + case EdmPrimitiveTypeKind.SByte: + return InRange(((IEdmIntegerValue)operand).Value, SByte.MinValue, SByte.MaxValue); + case EdmPrimitiveTypeKind.Byte: + return InRange(((IEdmIntegerValue)operand).Value, Byte.MinValue, Byte.MaxValue); + case EdmPrimitiveTypeKind.Int64: + case EdmPrimitiveTypeKind.Single: + case EdmPrimitiveTypeKind.Double: + return true; + } + } + + break; + case EdmValueKind.Collection: + if (targetType.IsCollection()) + { + IEdmTypeReference targetElementType = targetType.AsCollection().ElementType(); + + // This enumerates the entire collection, which is unfortunate. + foreach (IEdmDelayedValue elementValue in ((IEdmCollectionValue)operand).Elements) + { + if (!MatchesType(targetElementType, elementValue.Value)) + { + return false; + } + } + + return true; + } + + break; + case EdmValueKind.Enum: + return ((IEdmEnumValue)operand).Type.Definition.IsEquivalentTo(targetType.Definition); + case EdmValueKind.Structured: + if (targetType.IsStructured()) + { + return AssertOrMatchStructuredType(targetType.AsStructured(), (IEdmStructuredValue)operand, testPropertyTypes, null); + } + + break; + } + + return false; + } + + private static IEdmValue Cast(IEdmTypeReference targetType, IEdmValue operand) + { + IEdmTypeReference operandType = operand.Type; + EdmValueKind operandKind = operand.ValueKind; + + if ((operandType != null && operandKind != EdmValueKind.Null && operandType.Definition.IsOrInheritsFrom(targetType.Definition)) || targetType.TypeKind() == EdmTypeKind.None) + { + return operand; + } + + bool matches = true; + + switch (operandKind) + { + case EdmValueKind.Collection: + if (targetType.IsCollection()) + { + // Avoid enumerating the collection at this point. + return new CastCollectionValue(targetType.AsCollection(), (IEdmCollectionValue)operand); + } + else + { + matches = false; + } + + break; + + case EdmValueKind.Structured: + if (targetType.IsStructured()) + { + IEdmStructuredTypeReference structuredTargetType = targetType.AsStructured(); + List newProperties = new List(); + matches = AssertOrMatchStructuredType(structuredTargetType, (IEdmStructuredValue)operand, true, newProperties); + if (matches) + { + return new EdmStructuredValue(structuredTargetType, newProperties); + } + } + else + { + matches = false; + } + + break; + default: + matches = MatchesType(targetType, operand); + break; + } + + if (!matches) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_FailedTypeAssertion(targetType.ToTraceString())); + } + + return operand; + } + + private static bool AssertOrMatchStructuredType(IEdmStructuredTypeReference structuredTargetType, IEdmStructuredValue structuredValue, bool testPropertyTypes, List newProperties) + { + // If the value has a nominal type, the target type must be derived from the nominal type for a type match to be possible. + IEdmTypeReference operandType = structuredValue.Type; + if (operandType != null && !structuredTargetType.StructuredDefinition().InheritsFrom(operandType.AsStructured().StructuredDefinition())) + { + return false; + } + + HashSetInternal visitedProperties = new HashSetInternal(); + + foreach (IEdmProperty property in structuredTargetType.StructuralProperties()) + { + IEdmPropertyValue propertyValue = structuredValue.FindPropertyValue(property.Name); + if (propertyValue == null) + { + return false; + } + + visitedProperties.Add(propertyValue); + if (testPropertyTypes) + { + if (newProperties != null) + { + newProperties.Add(new EdmPropertyValue(propertyValue.Name, Cast(property.Type, propertyValue.Value))); + } + else if (!MatchesType(property.Type, propertyValue.Value)) + { + return false; + } + } + } + + if (structuredTargetType.IsEntity()) + { + foreach (IEdmNavigationProperty property in structuredTargetType.AsEntity().NavigationProperties()) + { + IEdmPropertyValue propertyValue = structuredValue.FindPropertyValue(property.Name); + if (propertyValue == null) + { + return false; + } + + // Make a superficial test of the navigation property value--check that it has a valid set of properties, + // but don't test their types. + if (testPropertyTypes && !MatchesType(property.Type, propertyValue.Value, false)) + { + return false; + } + + visitedProperties.Add(propertyValue); + if (newProperties != null) + { + newProperties.Add(propertyValue); + } + } + } + + //// Allow property values not mentioned in the target type, whether or not the target type is open. + + if (newProperties != null) + { + foreach (IEdmPropertyValue propertyValue in structuredValue.PropertyValues) + { + if (!visitedProperties.Contains(propertyValue)) + { + newProperties.Add(propertyValue); + } + } + } + + return true; + } + + private IEdmValue Eval(IEdmExpression expression, IEdmStructuredValue context) + { + switch (expression.ExpressionKind) + { + case EdmExpressionKind.IntegerConstant: + return (IEdmIntegerConstantExpression)expression; + case EdmExpressionKind.StringConstant: + return (IEdmStringConstantExpression)expression; + case EdmExpressionKind.BinaryConstant: + return (IEdmBinaryConstantExpression)expression; + case EdmExpressionKind.BooleanConstant: + return (IEdmBooleanConstantExpression)expression; + case EdmExpressionKind.DateTimeOffsetConstant: + return (IEdmDateTimeOffsetConstantExpression)expression; + case EdmExpressionKind.DecimalConstant: + return (IEdmDecimalConstantExpression)expression; + case EdmExpressionKind.FloatingConstant: + return (IEdmFloatingConstantExpression)expression; + case EdmExpressionKind.GuidConstant: + return (IEdmGuidConstantExpression)expression; + case EdmExpressionKind.DurationConstant: + return (IEdmDurationConstantExpression)expression; + case EdmExpressionKind.DateConstant: + return (IEdmDateConstantExpression)expression; + case EdmExpressionKind.TimeOfDayConstant: + return (IEdmTimeOfDayConstantExpression)expression; + case EdmExpressionKind.Null: + return (IEdmNullExpression)expression; + case EdmExpressionKind.Path: + { + if (context == null) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_NoContextPath); + } + + IEdmPathExpression pathExpression = (IEdmPathExpression)expression; + IEdmValue result = context; +#if ORCAS + // [EdmLib] Need to handle paths that bind to things other than properties. + foreach (string hop in pathExpression.PathSegments) + { + result = FindProperty(hop, result); + + if (result == null) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_UnboundPath(hop)); + } + } +#else + // Only Support Annotation in EntityType or ComplexType or Property or NavigationProperty. + // Empty Path is not supported. + foreach (string hop in pathExpression.PathSegments) + { + if (hop.Contains("@")) + { + var currentPathSegementInfos = hop.Split('@'); + var propertyName = currentPathSegementInfos[0]; + var termInfo = currentPathSegementInfos[1]; + IEdmExpression termCastExpression = null; + + if (!string.IsNullOrWhiteSpace(termInfo)) + { + var termInfos = termInfo.Split('#'); + if (termInfos.Length <= 2) + { + string termName = termInfos[0]; + string qualifier = termInfos.Length == 2 ? termInfos[1] : null; + + if (string.IsNullOrWhiteSpace(propertyName) && this.getAnnotationExpressionForType != null) + { + termCastExpression = this.getAnnotationExpressionForType(this.edmModel, context.Type.Definition, termName, qualifier); + } + else if (!string.IsNullOrWhiteSpace(propertyName) && this.getAnnotationExpressionForProperty != null) + { + termCastExpression = this.getAnnotationExpressionForProperty(this.edmModel, context.Type.Definition, propertyName, termName, qualifier); + } + } + } + + if (termCastExpression == null) + { + result = null; + break; + } + + result = this.Eval(termCastExpression, context); + } + else if (hop == "$count") + { + var edmCollectionValue = result as IEdmCollectionValue; + if (edmCollectionValue != null) + { + result = new EdmIntegerConstant(edmCollectionValue.Elements.Count()); + } + else + { + result = null; + break; + } + } + else if (hop.Contains(".")) + { + if (this.edmModel == null) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_TypeCastNeedsEdmModel); + } + + IEdmType typeSegmentClientType = this.resolveTypeFromName(hop, this.edmModel); + if (typeSegmentClientType == null) + { + result = null; + break; + } + + IEdmTypeReference operandType = result.Type; + EdmValueKind operandKind = result.ValueKind; + + if (operandKind == EdmValueKind.Collection) + { + List elementValues = new List(); + var collection = result as IEdmCollectionValue; + foreach (IEdmDelayedValue element in collection.Elements) + { + if (element.Value.Type.Definition.IsOrInheritsFrom(typeSegmentClientType)) + { + elementValues.Add(element); + } + } + + result = new EdmCollectionValue( + new EdmCollectionTypeReference(new EdmCollectionType(typeSegmentClientType.GetTypeReference(false))), + elementValues); + } + else if (operandKind != EdmValueKind.Structured + || (operandKind == EdmValueKind.Structured + && !operandType.Definition.IsOrInheritsFrom(typeSegmentClientType))) + { + result = null; + break; + } + } + else + { + result = FindProperty(hop, result); + if (result == null) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_UnboundPath(hop)); + } + } + } +#endif + return result; + } + + case EdmExpressionKind.PropertyPath: + case EdmExpressionKind.NavigationPropertyPath: + { + EdmUtil.CheckArgumentNull(context, "context"); + + IEdmPathExpression pathExpression = (IEdmPathExpression)expression; + IEdmValue result = context; + + // [EdmLib] Need to handle paths that bind to things other than properties. + foreach (string hop in pathExpression.PathSegments) + { + result = FindProperty(hop, result); + + if (result == null) + { + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_UnboundPath(hop)); + } + } + + return result; + } + + case EdmExpressionKind.FunctionApplication: + { + IEdmApplyExpression apply = (IEdmApplyExpression)expression; + IEdmFunction target = apply.AppliedFunction; + if (target != null) + { + IList argumentExpressions = apply.Arguments.ToList(); + IEdmValue[] arguments = new IEdmValue[argumentExpressions.Count()]; + + { + int argumentIndex = 0; + foreach (IEdmExpression argument in argumentExpressions) + { + arguments[argumentIndex++] = this.Eval(argument, context); + } + } + + //// Static validation will have checked that the number and types of arguments are correct, + //// so those checks are not performed dynamically. + + Func operationEvaluator; + if (this.builtInFunctions.TryGetValue(target, out operationEvaluator)) + { + return operationEvaluator(arguments); + } + + if (this.lastChanceOperationApplier != null) + { + return this.lastChanceOperationApplier(target.FullName(), arguments); + } + } + + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_UnboundFunction(target != null ? target.ToTraceString() : string.Empty)); + } + + case EdmExpressionKind.If: + { + IEdmIfExpression ifExpression = (IEdmIfExpression)expression; + + if (((IEdmBooleanValue)this.Eval(ifExpression.TestExpression, context)).Value) + { + return this.Eval(ifExpression.TrueExpression, context); + } + + return this.Eval(ifExpression.FalseExpression, context); + } + + case EdmExpressionKind.IsType: + { + IEdmIsTypeExpression isType = (IEdmIsTypeExpression)expression; + + IEdmValue operand = this.Eval(isType.Operand, context); + IEdmTypeReference targetType = isType.Type; + + return new EdmBooleanConstant(MatchesType(targetType, operand)); + } + + case EdmExpressionKind.Cast: + { + IEdmCastExpression castType = (IEdmCastExpression)expression; + + IEdmValue operand = this.Eval(castType.Operand, context); + IEdmTypeReference targetType = castType.Type; + + return Cast(targetType, operand); + } + + case EdmExpressionKind.Record: + { + IEdmRecordExpression record = (IEdmRecordExpression)expression; + DelayedExpressionContext recordContext = new DelayedExpressionContext(this, context); + + List propertyValues = new List(); + + //// Static validation will have checked that the set of supplied properties are appropriate + //// for the supplied type and have no duplicates, so those checks are not performed dynamically. + + foreach (IEdmPropertyConstructor propertyConstructor in record.Properties) + { + propertyValues.Add(new DelayedRecordProperty(recordContext, propertyConstructor)); + } + + EdmStructuredValue result = new EdmStructuredValue(record.DeclaredType != null ? record.DeclaredType.AsStructured() : null, propertyValues); + return result; + } + + case EdmExpressionKind.Collection: + { + IEdmCollectionExpression collection = (IEdmCollectionExpression)expression; + DelayedExpressionContext collectionContext = new DelayedExpressionContext(this, context); + List elementValues = new List(); + + //// Static validation will have checked that the result types of the element expressions are + //// appropriate and so these checks are not performed dynamically. + + foreach (IEdmExpression element in collection.Elements) + { + elementValues.Add(this.MapLabeledExpressionToDelayedValue(element, collectionContext, context)); + } + + EdmCollectionValue result = new EdmCollectionValue(collection.DeclaredType != null ? collection.DeclaredType.AsCollection() : null, elementValues); + return result; + } + + case EdmExpressionKind.LabeledExpressionReference: + { + return this.MapLabeledExpressionToDelayedValue(((IEdmLabeledExpressionReferenceExpression)expression).ReferencedLabeledExpression, null, context).Value; + } + + case EdmExpressionKind.Labeled: + return this.MapLabeledExpressionToDelayedValue(expression, new DelayedExpressionContext(this, context), context).Value; + + case EdmExpressionKind.EnumMember: + IEdmEnumMemberExpression enumMemberExpression = (IEdmEnumMemberExpression)expression; + var enumMembers = enumMemberExpression.EnumMembers.ToList(); + IEdmEnumType enumType = enumMembers.First().DeclaringType; + IEdmEnumTypeReference enumTypeReference = new EdmEnumTypeReference(enumType, false); + if (enumMembers.Count() == 1) + { + return new EdmEnumValue(enumTypeReference, enumMemberExpression.EnumMembers.Single()); + } + else + { + if (!enumType.IsFlags || !EdmEnumValueParser.IsEnumIntegerType(enumType)) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Type {0} cannot be assigned with multi-values.", enumType.FullName())); + } + + long result = 0; + foreach (var enumMember in enumMembers) + { + long value = enumMember.Value.Value; + result |= value; + } + + return new EdmEnumValue(enumTypeReference, new EdmEnumMemberValue(result)); + } + + default: + throw new InvalidOperationException(Edm.Strings.Edm_Evaluator_UnrecognizedExpressionKind(((int)expression.ExpressionKind).ToString(System.Globalization.CultureInfo.InvariantCulture))); + } + } + + private IEdmDelayedValue MapLabeledExpressionToDelayedValue(IEdmExpression expression, DelayedExpressionContext delayedContext, IEdmStructuredValue context) + { + //// Labeled expressions map to delayed values either at the point of definition or at a point of reference + //// (evaluation of a LabeledExpressionReference that refers to the expression). All of these must map to + //// a single delayed value (so that the value itself is evaluated only once). + + IEdmLabeledExpression labeledExpression = expression as IEdmLabeledExpression; + if (labeledExpression == null) + { + //// If an expression has no label, there can be no references to it and so only the point of definition needs a mapping to a delayed value, + //// and so there is no need to cache the delayed value. The point of definition always supplies a context. + + System.Diagnostics.Debug.Assert(delayedContext != null, "Labeled element definition failed to supply an evaluation context."); + return new DelayedCollectionElement(delayedContext, expression); + } + + DelayedValue expressionValue; + if (this.labeledValues.TryGetValue(labeledExpression, out expressionValue)) + { + return expressionValue; + } + + expressionValue = new DelayedCollectionElement(delayedContext ?? new DelayedExpressionContext(this, context), labeledExpression.Expression); + this.labeledValues[labeledExpression] = expressionValue; + return expressionValue; + } + + private static IEdmValue FindProperty(string name, IEdmValue context) + { + IEdmValue result = null; + + IEdmStructuredValue structuredContext = context as IEdmStructuredValue; + if (structuredContext != null) + { + IEdmPropertyValue propertyValue = structuredContext.FindPropertyValue(name); + if (propertyValue != null) + { + result = propertyValue.Value; + } + } + + return result; + } + + private class DelayedExpressionContext + { + private readonly EdmExpressionEvaluator expressionEvaluator; + private readonly IEdmStructuredValue context; + + public DelayedExpressionContext(EdmExpressionEvaluator expressionEvaluator, IEdmStructuredValue context) + { + this.expressionEvaluator = expressionEvaluator; + this.context = context; + } + + public IEdmValue Eval(IEdmExpression expression) + { + return this.expressionEvaluator.Eval(expression, this.context); + } + } + + private abstract class DelayedValue : IEdmDelayedValue + { + private readonly DelayedExpressionContext context; + private IEdmValue value; + + public DelayedValue(DelayedExpressionContext context) + { + this.context = context; + } + + public abstract IEdmExpression Expression { get; } + + public IEdmValue Value + { + get + { + if (this.value == null) + { + this.value = this.context.Eval(this.Expression); + } + + return this.value; + } + } + } + + private class DelayedRecordProperty : DelayedValue, IEdmPropertyValue + { + private readonly IEdmPropertyConstructor constructor; + + public DelayedRecordProperty(DelayedExpressionContext context, IEdmPropertyConstructor constructor) + : base(context) + { + this.constructor = constructor; + } + + public string Name + { + get { return this.constructor.Name; } + } + + public override IEdmExpression Expression + { + get { return this.constructor.Value; } + } + } + + private class DelayedCollectionElement : DelayedValue + { + private readonly IEdmExpression expression; + + public DelayedCollectionElement(DelayedExpressionContext context, IEdmExpression expression) + : base(context) + { + this.expression = expression; + } + + public override IEdmExpression Expression + { + get { return this.expression; } + } + } + + private class CastCollectionValue : EdmElement, IEdmCollectionValue, IEnumerable + { + private readonly IEdmCollectionTypeReference targetCollectionType; + private readonly IEdmCollectionValue collectionValue; + + public CastCollectionValue(IEdmCollectionTypeReference targetCollectionType, IEdmCollectionValue collectionValue) + { + this.targetCollectionType = targetCollectionType; + this.collectionValue = collectionValue; + } + + IEnumerable IEdmCollectionValue.Elements + { + get { return this; } + } + + IEdmTypeReference IEdmValue.Type + { + get { return this.targetCollectionType; } + } + + EdmValueKind IEdmValue.ValueKind + { + get { return EdmValueKind.Collection; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new CastCollectionValueEnumerator(this); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return new CastCollectionValueEnumerator(this); + } + + private class CastCollectionValueEnumerator : IEnumerator + { + private readonly CastCollectionValue value; + private readonly IEnumerator enumerator; + + public CastCollectionValueEnumerator(CastCollectionValue value) + { + this.value = value; + this.enumerator = value.collectionValue.Elements.GetEnumerator(); + } + + public IEdmDelayedValue Current + { + get { return new DelayedCast(this.value.targetCollectionType.ElementType(), this.enumerator.Current); } + } + + object System.Collections.IEnumerator.Current + { + get { return this.Current; } + } + + bool System.Collections.IEnumerator.MoveNext() + { + return this.enumerator.MoveNext(); + } + + void System.Collections.IEnumerator.Reset() + { + this.enumerator.Reset(); + } + + void IDisposable.Dispose() + { + this.enumerator.Dispose(); + } + + private class DelayedCast : IEdmDelayedValue + { + private readonly IEdmDelayedValue delayedValue; + private readonly IEdmTypeReference targetType; + private IEdmValue value; + + public DelayedCast(IEdmTypeReference targetType, IEdmDelayedValue value) + { + this.delayedValue = value; + this.targetType = targetType; + } + + public IEdmValue Value + { + get + { + if (this.value == null) + { + this.value = EdmExpressionEvaluator.Cast(this.targetType, this.delayedValue.Value); + } + + return this.value; + } + } + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrConverter.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrConverter.cs new file mode 100644 index 0000000..cde9fc7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrConverter.cs @@ -0,0 +1,982 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// to CLR value converter. + /// + public class EdmToClrConverter + { + private static readonly Type TypeICollectionOfT = typeof(ICollection<>); + private static readonly Type TypeIListOfT = typeof(IList<>); + private static readonly Type TypeListOfT = typeof(List<>); + private static readonly Type TypeIEnumerableOfT = typeof(IEnumerable<>); + private static readonly Type TypeNullableOfT = typeof(Nullable<>); + private static readonly MethodInfo CastToClrTypeMethodInfo = typeof(CastHelper).GetMethod("CastToClrType"); + private static readonly MethodInfo EnumerableToListOfTMethodInfo = typeof(CastHelper).GetMethod("EnumerableToListOfT"); + + private readonly Dictionary convertedObjects = new Dictionary(); + private readonly Dictionary enumerableConverters = new Dictionary(); + private readonly Dictionary enumTypeConverters = new Dictionary(); + + private readonly TryCreateObjectInstance tryCreateObjectInstanceDelegate; + private readonly TryGetClrPropertyInfo tryGetClrPropertyInfoDelegate; + + /// + /// Initializes a new instance of the class. + /// + public EdmToClrConverter() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The delegate customizing conversion of structured values. + public EdmToClrConverter(TryCreateObjectInstance tryCreateObjectInstanceDelegate) + { + EdmUtil.CheckArgumentNull(tryCreateObjectInstanceDelegate, "tryCreateObjectInstanceDelegate"); + + this.tryCreateObjectInstanceDelegate = tryCreateObjectInstanceDelegate; + } + + /// + /// Initializes a new instance of the class. + /// + /// The delegate customizing conversion of structured values. + /// The delegate customizing the behavior to get client CLR property info + /// The delegate customizing the behavior to get client CLR type name + public EdmToClrConverter( + TryCreateObjectInstance tryCreateObjectInstanceDelegate, + TryGetClrPropertyInfo tryGetClrPropertyInfoDelegate, + TryGetClrTypeName tryGetClrTypeNameDelegate) + { + this.tryCreateObjectInstanceDelegate = tryCreateObjectInstanceDelegate; + this.tryGetClrPropertyInfoDelegate = tryGetClrPropertyInfoDelegate; + this.TryGetClrTypeNameDelegate = tryGetClrTypeNameDelegate; + } + + /// + /// The delegate to get the CLR type name. + /// + internal TryGetClrTypeName TryGetClrTypeNameDelegate { get; private set; } + + /// + /// Converts to a CLR value of the specified type. + /// Supported values for are: + /// CLR primitive types such as and , + /// CLR enum types, + /// , + /// , + /// , + /// CLR classes with default constructors and public properties with setters and collection properties of the following shapes: + /// EnumerableProperty { get; set; }, + /// CollectionProperty { get; set; }, + /// ListProperty { get; set; }, + /// CollectionProperty { get { return this.nonNullCollection; } }, + /// ListProperty { get { return this.nonNullList; } }. + /// + /// The CLR type. + /// The EDM value to be converted. + /// A CLR value converted from . + /// This method performs boxing and unboxing for value types. Use value-type specific methods such as to avoid boxing and unboxing. + public T AsClrValue(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + // convertEnumValues: false -- no need to produce an object of the enum type because + // the produced underlying value will get converted to the enum type by (T)this.AsClrValue. + bool convertEnumValues = false; + return (T)this.AsClrValue(edmValue, typeof(T), convertEnumValues); + } + + /// + /// Converts to a CLR value of the specified type. + /// Supported values for are: + /// CLR primitive types such as and , + /// CLR enum types, + /// , + /// , + /// , + /// CLR classes with default constructors and public properties with setters and collection properties of the following shapes: + /// EnumerableProperty { get; set; }, + /// CollectionProperty { get; set; }, + /// ListProperty { get; set; }, + /// CollectionProperty { get { return this.nonNullCollection; } }, + /// ListProperty { get { return this.nonNullList; } }. + /// + /// The EDM value to be converted. + /// The CLR type. + /// A CLR value converted from . + /// This method performs boxing and unboxing for value types. Use value-type specific methods such as to avoid boxing and unboxing. + public object AsClrValue(IEdmValue edmValue, Type clrType) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + EdmUtil.CheckArgumentNull(clrType, "clrType"); + + // convertEnumValues: true -- must produce an object of the requested enum type because there is nothing else + // down the line that can convert an underlying value to an enum type. + bool convertEnumValues = true; + return this.AsClrValue(edmValue, clrType, convertEnumValues); + } + + /// + /// Registers the corresponding to the . + /// All subsequent conversions from this performed by this instance of will return the specified + /// . Registration is required to support graph consistency and loops during conversion process. + /// This method should be called inside the delegate if the delegate is calling back into + /// in order to populate properties of the . + /// + /// The EDM value. + /// The CLR object. + public void RegisterConvertedObject(IEdmStructuredValue edmValue, object clrObject) + { + this.convertedObjects.Add(edmValue, clrObject); + } + + #region Static converters + + /// + /// Converts to a CLR byte array value. + /// + /// The EDM value to be converted. + /// Converted byte array. + /// Exception is thrown if is not . + internal static byte[] AsClrByteArray(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + if (edmValue is IEdmNullValue) + { + return null; + } + + return ((IEdmBinaryValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted string. + /// Exception is thrown if is not . + internal static string AsClrString(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + if (edmValue is IEdmNullValue) + { + return null; + } + + return ((IEdmStringValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted boolean. + /// Exception is thrown if is not . + internal static Boolean AsClrBoolean(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmBooleanValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted integer. + /// Exception is thrown if is not . + internal static Int64 AsClrInt64(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmIntegerValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted char. + /// Exception is thrown if is not . + /// Exception is thrown if cannot be converted to . + internal static Char AsClrChar(IEdmValue edmValue) + { + checked + { + return (Char)AsClrInt64(edmValue); + } + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted byte. + /// Exception is thrown if is not . + /// Exception is thrown if cannot be converted to . + internal static Byte AsClrByte(IEdmValue edmValue) + { + checked + { + return (Byte)AsClrInt64(edmValue); + } + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted integer. + /// Exception is thrown if is not . + /// Exception is thrown if cannot be converted to . + internal static Int16 AsClrInt16(IEdmValue edmValue) + { + checked + { + return (Int16)AsClrInt64(edmValue); + } + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted integer. + /// Exception is thrown if is not . + /// Exception is thrown if cannot be converted to . + internal static Int32 AsClrInt32(IEdmValue edmValue) + { + checked + { + return (Int32)AsClrInt64(edmValue); + } + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted double. + /// Exception is thrown if is not . + internal static Double AsClrDouble(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmFloatingValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted single. + /// Exception is thrown if is not . + internal static Single AsClrSingle(IEdmValue edmValue) + { + return (Single)AsClrDouble(edmValue); + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted TimeOfDay. + /// Exception is thrown if is not . + internal static TimeOfDay AsClrTimeOfDay(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmTimeOfDayValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted date. + /// Exception is thrown if is not . + internal static Date AsClrDate(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmDateValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted decimal. + /// Exception is thrown if is not . + internal static decimal AsClrDecimal(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmDecimalValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted Duration. + /// Exception is thrown if is not . + internal static TimeSpan AsClrDuration(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmDurationValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted Guid. + /// Exception is thrown if is not . + internal static Guid AsClrGuid(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmGuidValue)edmValue).Value; + } + + /// + /// Converts to a value. + /// + /// The EDM value to be converted. + /// Converted DateTimeOffset. + /// Exception is thrown if is not . + internal static DateTimeOffset AsClrDateTimeOffset(IEdmValue edmValue) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + + return ((IEdmDateTimeOffsetValue)edmValue).Value; + } + + #endregion + + #region Private implementation + + private static bool TryConvertAsNonGuidPrimitiveType(Type clrType, IEdmValue edmValue, out object clrValue) + { + if (clrType == typeof(Boolean)) + { + clrValue = AsClrBoolean(edmValue); + return true; + } + + if (clrType == typeof(Char)) + { + clrValue = AsClrChar(edmValue); + return true; + } + + if (clrType == typeof(SByte)) + { + checked + { + clrValue = (SByte)AsClrInt64(edmValue); + return true; + } + } + + if (clrType == typeof(Byte)) + { + clrValue = AsClrByte(edmValue); + return true; + } + + if (clrType == typeof(Int16)) + { + clrValue = AsClrInt16(edmValue); + return true; + } + + if (clrType == typeof(UInt16)) + { + checked + { + clrValue = (UInt16)AsClrInt64(edmValue); + return true; + } + } + + if (clrType == typeof(Int32)) + { + clrValue = AsClrInt32(edmValue); + return true; + } + + if (clrType == typeof(UInt32)) + { + checked + { + clrValue = (UInt32)AsClrInt64(edmValue); + return true; + } + } + + if (clrType == typeof(Int64)) + { + clrValue = AsClrInt64(edmValue); + return true; + } + + if (clrType == typeof(UInt64)) + { + checked + { + clrValue = (UInt64)AsClrInt64(edmValue); + return true; + } + } + + if (clrType == typeof(Single)) + { + clrValue = AsClrSingle(edmValue); + return true; + } + + if (clrType == typeof(Double)) + { + clrValue = AsClrDouble(edmValue); + return true; + } + + if (clrType == typeof(Decimal)) + { + clrValue = AsClrDecimal(edmValue); + return true; + } + + if (clrType == typeof(String)) + { + clrValue = AsClrString(edmValue); + return true; + } + + clrValue = null; + return false; + } + + private static MethodInfo FindICollectionOfElementTypeAddMethod(Type collectionType, Type elementType) + { + Type collectionOfElementType = typeof(ICollection<>).MakeGenericType(elementType); + return collectionOfElementType.GetMethod("Add"); + } + + /// + /// Searches the for a property with the . + /// Handles the case of multiple properties with the same name (declared via C# "new") by choosing the one on the deepest derived type. + /// + /// The clr object type. + /// The property name. + /// The property or null. + private PropertyInfo FindProperty(Type clrObjectType, string propertyName) + { + if (this.tryGetClrPropertyInfoDelegate != null) + { + PropertyInfo propertyInfo = null; + if (this.tryGetClrPropertyInfoDelegate(clrObjectType, propertyName, out propertyInfo)) + { + return propertyInfo; + } + else + { + return null; + } + } + else + { + List properties = clrObjectType.GetProperties().Where(p => p.Name == propertyName).ToList(); + switch (properties.Count) + { + case 0: + return null; + + case 1: + return properties[0]; + + default: + PropertyInfo property = properties[0]; + for (int i = 1; i < properties.Count; ++i) + { + PropertyInfo candidate = properties[i]; + if (property.DeclaringType.IsAssignableFrom(candidate.DeclaringType)) + { + property = candidate; + } + } + + return property; + } + } + } + + /// + /// Used for error messages only. + /// + /// The EDM value. + /// The EDM value interface name. + private static string GetEdmValueInterfaceName(IEdmValue edmValue) + { + Debug.Assert(edmValue != null, "edmValue != null"); + + // We want search to be stable regardless of the order of elements coming from GetInterfaces() method, + // so we sort first, then find the deepest derived interface descending from IEdmValue. + Type interfaceType = typeof(IEdmValue); + foreach (Type candidate in edmValue.GetType().GetInterfaces().OrderBy(i => i.FullName)) + { + if (interfaceType.IsAssignableFrom(candidate) && interfaceType != candidate) + { + interfaceType = candidate; + } + } + + return interfaceType.Name; + } + + private static bool IsBuiltInOrEnumType(Type type) + { + return type.IsPrimitive() || type == typeof(string) || type == typeof(decimal) || type.IsEnum(); + } + + private object AsClrValue(IEdmValue edmValue, Type clrType, bool convertEnumValues) + { + if (!IsBuiltInOrEnumType(clrType)) + { + // First look for nullable primitives, then DateTime, DateTimeOffset and byte[] which don't have dedicated TypeCode, so they are processed here. + if (clrType.IsGenericType() && clrType.GetGenericTypeDefinition() == TypeNullableOfT) + { + if (edmValue is IEdmNullValue) + { + return null; + } + + return this.AsClrValue(edmValue, clrType.GetGenericArguments().Single()); + } + else if (clrType == typeof(Guid)) + { + return AsClrGuid(edmValue); + } + else if (clrType == typeof(Date)) + { + return AsClrDate(edmValue); + } + else if (clrType == typeof(DateTimeOffset)) + { + return AsClrDateTimeOffset(edmValue); + } + else if (clrType == typeof(TimeOfDay)) + { + return AsClrTimeOfDay(edmValue); + } + else if (clrType == typeof(TimeSpan)) + { + return AsClrDuration(edmValue); + } + else if (clrType == typeof(byte[])) + { + return AsClrByteArray(edmValue); + } + else if (clrType.IsGenericType() && clrType.IsInterface() && + (clrType.GetGenericTypeDefinition() == TypeICollectionOfT || + clrType.GetGenericTypeDefinition() == TypeIListOfT || + clrType.GetGenericTypeDefinition() == TypeIEnumerableOfT)) + { + // We are asked to produce an IEnumerable, perform an equivalent of this.AsIEnumerable(edmValue, typeof(T)).Cast().ToList() + return this.AsListOfT(edmValue, clrType); + } + else + { + return this.AsClrObject(edmValue, clrType); + } + } + else + { + // A CLR enum type will report some primitive type code, so we get here. + // If this is the case and the value is of an edm enumeration type, we want to unbox the primitive type value, + // otherwise assume the edm value is primitive and let it fail inside the converter if it's not. + bool isEnum = clrType.IsEnum(); + if (isEnum) + { + Type underlyingType = Enum.GetUnderlyingType(clrType); + IEdmEnumValue edmEnumValue = edmValue as IEdmEnumValue; + + object clrValue = null; + if (edmEnumValue != null) + { + EdmEnumMemberValue memberValue = edmEnumValue.Value as EdmEnumMemberValue; + if (memberValue != null + && !TryConvertEnumType(underlyingType, memberValue.Value, out clrValue)) + { + throw new InvalidCastException(Strings.EdmToClr_UnsupportedType(underlyingType)); + } + } + else if (!TryConvertAsNonGuidPrimitiveType(underlyingType, edmValue, out clrValue)) + { + throw new InvalidCastException(Strings.EdmToClr_UnsupportedType(underlyingType)); + } + + // In case of enums, because the converter returns a primitive type value we want to convert it to the CLR enum type. + return convertEnumValues ? this.GetEnumValue(clrValue, clrType) : clrValue; + } + + object nonEnumclrValue = null; + if (!TryConvertAsNonGuidPrimitiveType(clrType, edmValue, out nonEnumclrValue)) + { + throw new InvalidCastException(Strings.EdmToClr_UnsupportedType(clrType)); + } + + return nonEnumclrValue; + } + } + + private static bool TryConvertEnumType(Type type, long enumValue, out object clrValue) + { + if (type == typeof(SByte)) + { + clrValue = (SByte)(enumValue); + return true; + } + + if (type == typeof(Byte)) + { + clrValue = (Byte)(enumValue); + return true; + } + + if (type == typeof(Int16)) + { + clrValue = (Int16)(enumValue); + return true; + } + + if (type == typeof(UInt16)) + { + clrValue = (UInt16)(enumValue); + return true; + } + + if (type == typeof(Int32)) + { + clrValue = (Int32)(enumValue); + return true; + } + + if (type == typeof(UInt32)) + { + clrValue = (UInt32)(enumValue); + return true; + } + + if (type == typeof(Int64)) + { + clrValue = (Int64)(enumValue); + return true; + } + + clrValue = null; + return false; + } + + private object AsListOfT(IEdmValue edmValue, Type clrType) + { + Debug.Assert(clrType.IsGenericType(), "clrType.IsGenericType"); + + if (edmValue is IEdmNullValue) + { + return null; + } + + Type elementType = clrType.GetGenericArguments().Single(); + + MethodInfo enumerableConverter; + if (!this.enumerableConverters.TryGetValue(elementType, out enumerableConverter)) + { + enumerableConverter = EnumerableToListOfTMethodInfo.MakeGenericMethod(elementType); + this.enumerableConverters.Add(elementType, enumerableConverter); + } + + try + { + return enumerableConverter.Invoke(null, new object[] { this.AsIEnumerable(edmValue, elementType) }); + } + catch (TargetInvocationException targetInvocationException) + { + // Unwrap the target invocation exception that masks an interesting invalid cast exception. + if (targetInvocationException.InnerException != null && targetInvocationException.InnerException is InvalidCastException) + { + throw targetInvocationException.InnerException; + } + else + { + throw; + } + } + } + + private object GetEnumValue(object clrValue, Type clrType) + { + Debug.Assert(clrType.IsEnum(), "clrType.IsEnum"); + + MethodInfo enumTypeConverter; + if (!this.enumTypeConverters.TryGetValue(clrType, out enumTypeConverter)) + { + enumTypeConverter = CastToClrTypeMethodInfo.MakeGenericMethod(clrType); + this.enumTypeConverters.Add(clrType, enumTypeConverter); + } + + try + { + return enumTypeConverter.Invoke(null, new object[] { clrValue }); + } + catch (TargetInvocationException targetInvocationException) + { + // Unwrap the target invocation exception that masks an interesting invalid cast exception. + if (targetInvocationException.InnerException != null && targetInvocationException.InnerException is InvalidCastException) + { + throw targetInvocationException.InnerException; + } + else + { + throw; + } + } + } + + private object AsClrObject(IEdmValue edmValue, Type clrObjectType) + { + EdmUtil.CheckArgumentNull(edmValue, "edmValue"); + EdmUtil.CheckArgumentNull(clrObjectType, "clrObjectType"); + + if (edmValue is IEdmNullValue) + { + return null; + } + + IEdmStructuredValue edmStructuredValue = edmValue as IEdmStructuredValue; + if (edmStructuredValue == null) + { + if (edmValue is IEdmCollectionValue) + { + throw new InvalidCastException(Strings.EdmToClr_CannotConvertEdmCollectionValueToClrType(clrObjectType.FullName)); + } + else + { + throw new InvalidCastException(Strings.EdmToClr_CannotConvertEdmValueToClrType(GetEdmValueInterfaceName(edmValue), clrObjectType.FullName)); + } + } + + object clrObject; + + if (this.convertedObjects.TryGetValue(edmStructuredValue, out clrObject)) + { + return clrObject; + } + + // By convention we only support mapping structured values to a CLR class. + if (!clrObjectType.IsClass()) + { + throw new InvalidCastException(Strings.EdmToClr_StructuredValueMappedToNonClass); + } + + // Try user-defined logic before the default logic. + bool clrObjectInitialized; + if (this.tryCreateObjectInstanceDelegate != null && this.tryCreateObjectInstanceDelegate(edmStructuredValue, clrObjectType, this, out clrObject, out clrObjectInitialized)) + { + // The user-defined logic might have produced null, which is Ok, but we need to null the type in to keep them in sync. + if (clrObject != null) + { + Type newClrObjectType = clrObject.GetType(); + if (!clrObjectType.IsAssignableFrom(newClrObjectType)) + { + throw new InvalidCastException(Strings.EdmToClr_TryCreateObjectInstanceReturnedWrongObject(newClrObjectType.FullName, clrObjectType.FullName)); + } + + clrObjectType = newClrObjectType; + } + } + else + { + // Default instance creation logic: use Activator to create the new CLR object from the type. + clrObject = Activator.CreateInstance(clrObjectType); + clrObjectInitialized = false; + } + + // Cache the object before populating its properties as their values might refer to the object. + this.convertedObjects[edmStructuredValue] = clrObject; + + if (!clrObjectInitialized && clrObject != null) + { + this.PopulateObjectProperties(edmStructuredValue, clrObject, clrObjectType); + } + + return clrObject; + } + + private void PopulateObjectProperties(IEdmStructuredValue edmValue, object clrObject, Type clrObjectType) + { + // Populate properties of the CLR object. + // All CLR object properties that have no edm counterparts will remain intact. + // By convention we only support converting from a structured value. + HashSetInternal populatedProperties = new HashSetInternal(); + foreach (IEdmPropertyValue propertyValue in edmValue.PropertyValues) + { + PropertyInfo clrProperty = this.FindProperty(clrObjectType, propertyValue.Name); + + // By convention we ignore an ems property if it has no counterpart on the CLR side. + if (clrProperty != null) + { + if (populatedProperties.Contains(propertyValue.Name)) + { + throw new InvalidCastException(Strings.EdmToClr_StructuredPropertyDuplicateValue(propertyValue.Name)); + } + + if (!this.TrySetCollectionProperty(clrProperty, clrObject, propertyValue)) + { + object convertedClrPropertyValue = this.AsClrValue(propertyValue.Value, clrProperty.PropertyType); + clrProperty.SetValue(clrObject, convertedClrPropertyValue, null); + } + + populatedProperties.Add(propertyValue.Name); + } + } + } + + private bool TrySetCollectionProperty(PropertyInfo clrProperty, object clrObject, IEdmPropertyValue propertyValue) + { + if (propertyValue.Value is IEdmNullValue) + { + return false; + } + + Type clrPropertyType = clrProperty.PropertyType; + + // Process the following cases: + // class C1 + // { + // IEnumerable EnumerableProperty { get; set; } + // + // ICollection CollectionProperty { get; set; } + // + // IList ListProperty { get; set; } + // + // ICollection CollectionProperty { get { return this.nonNullCollection; } } + // + // IList ListProperty { get { return this.nonNullList; } } + // } + if (clrPropertyType.IsGenericType()) + { + Type genericTypeDefinition = clrPropertyType.GetGenericTypeDefinition(); + bool genericTypeDefinitionIsIEnumerableOfT = genericTypeDefinition == TypeIEnumerableOfT; + IEnumerable clrPropertyTypeInterfaces = clrPropertyType.GetInterfaces(); + if (genericTypeDefinitionIsIEnumerableOfT || clrPropertyTypeInterfaces.Any(t => t.GetGenericTypeDefinition() == TypeIEnumerableOfT)) + { + object clrPropertyValue = clrProperty.GetValue(clrObject, null); + + // If property already has a value, we are trying to reuse it and add elements into it (except the case of IEnumerable), + // otherwise we create List, add elements to it and then assign it to the property. + Type elementType = clrPropertyType.GetGenericArguments().Single(); + Type clrPropertyValueType; + if (clrPropertyValue == null) + { + // If collection property has no value, create an instance of List and + // assign it to the property. + clrPropertyValueType = TypeListOfT.MakeGenericType(elementType); + clrPropertyValue = Activator.CreateInstance(clrPropertyValueType); + clrProperty.SetValue(clrObject, clrPropertyValue, null); + } + else + { + if (genericTypeDefinitionIsIEnumerableOfT) + { + // Cannot add elements to an existing value of type IEnumerable. + throw new InvalidCastException(Strings.EdmToClr_IEnumerableOfTPropertyAlreadyHasValue(clrProperty.Name, clrProperty.DeclaringType.FullName)); + } + + clrPropertyValueType = clrPropertyValue.GetType(); + } + + // Find the ICollection.Add(elementType) method. Note that IList implements + MethodInfo clrPropertyValueTypeAddMethod = FindICollectionOfElementTypeAddMethod(clrPropertyValueType, elementType); + + // Convert the collection elements and add them to the CLR collection. + foreach (object convertedElementValue in this.AsIEnumerable(propertyValue.Value, elementType)) + { + try + { + clrPropertyValueTypeAddMethod.Invoke(clrPropertyValue, new object[] { convertedElementValue }); + } + catch (TargetInvocationException targetInvokationException) + { + // Unwrap the target invokation exception that masks an interesting invalid cast exception. + if (targetInvokationException.InnerException != null && targetInvokationException.InnerException is InvalidCastException) + { + throw targetInvokationException.InnerException; + } + else + { + throw; + } + } + } + + return true; + } + } + + return false; + } + + private IEnumerable AsIEnumerable(IEdmValue edmValue, Type elementType) + { + // By convention we only support converting from a collection value. + foreach (IEdmDelayedValue element in ((IEdmCollectionValue)edmValue).Elements) + { + if (element.Value != null || elementType.IsGenericType() && elementType.GetGenericTypeDefinition() == TypeNullableOfT) + { + yield return this.AsClrValue(element.Value, elementType); + } + } + } + + /// + /// The class contains method that are called thru reflection to produce values of correct CLR types. + /// For example if one has an int value and a clr type represnting an enum : int, there is no other way to convert the int + /// to the enum type object. + /// + private static class CastHelper + { + public static T CastToClrType(object obj) + { + return (T)obj; + } + + public static List EnumerableToListOfT(IEnumerable enumerable) + { + return enumerable.Cast().ToList(); + } + } + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrEvaluator.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrEvaluator.cs new file mode 100644 index 0000000..dbd9d48 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/EdmToClrEvaluator.cs @@ -0,0 +1,132 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Expression evaluator capable of producing CLR values. + /// + public class EdmToClrEvaluator : EdmExpressionEvaluator + { + private EdmToClrConverter edmToClrConverter = new EdmToClrConverter(); + + /// + /// Initializes a new instance of the class. + /// + /// Builtin functions dictionary to the evaluators for the functions. + public EdmToClrEvaluator(IDictionary> builtInFunctions) + : base(builtInFunctions) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Builtin functions dictionary to the evaluators for the functions. + /// Function to call to evaluate an application of a function with no static binding. + public EdmToClrEvaluator(IDictionary> builtInFunctions, Func lastChanceOperationApplier) + : base(builtInFunctions, lastChanceOperationApplier) + { + } + +#if !ORCAS + /// + /// Initializes a new instance of the class. + /// + /// Builtin functions dictionary to the evaluators for the functions. + /// Function to call to evaluate an application of a function with no static binding. + /// Function to get the of an annotation of an . + /// Function to get the of an annotation of a property or navigation property in . + /// The edm model. + public EdmToClrEvaluator( + IDictionary> builtInFunctions, + Func lastChanceOperationApplier, + Func getAnnotationExpressionForType, + Func getAnnotationExpressionForProperty, + IEdmModel edmModel) + : base(builtInFunctions, lastChanceOperationApplier, getAnnotationExpressionForType, getAnnotationExpressionForProperty, edmModel) + { + this.ResolveTypeFromName = this.ResolveEdmTypeFromName; + } +#endif + + /// + /// Gets or sets an instance of that is used to produce CLR values during evaluation. + /// + public EdmToClrConverter EdmToClrConverter + { + get + { + return this.edmToClrConverter; + } + + set + { + EdmUtil.CheckArgumentNull(value, "value"); + this.edmToClrConverter = value; + } + } + + /// + /// Evaluates an expression with no value context. + /// + /// The CLR type of the value to be returned. + /// Expression to evaluate. The expression must not contain paths, because no context for evaluating a path is supplied. + /// The value that results from evaluating the expression in the context of the supplied value. + public T EvaluateToClrValue(IEdmExpression expression) + { + IEdmValue edmValue = this.Evaluate(expression); + return this.edmToClrConverter.AsClrValue(edmValue); + } + + /// + /// Evaluates an expression in the context of a value. + /// + /// The CLR type of the value to be returned. + /// Expression to evaluate. + /// Value to use as context in evaluating the expression. + /// The value that results from evaluating the expression in the context of the supplied value. + public T EvaluateToClrValue(IEdmExpression expression, IEdmStructuredValue context) + { + IEdmValue edmValue = this.Evaluate(expression, context); + return this.edmToClrConverter.AsClrValue(edmValue); + } + + /// + /// Evaluates an expression in the context of a value and a target type. + /// + /// The CLR type of the value to be returned. + /// Expression to evaluate. + /// Value to use as context in evaluating the expression. + /// Type to which the result value is expected to conform. + /// The value that results from evaluating the expression in the context of the supplied value, asserted to be of the targetType. + public T EvaluateToClrValue(IEdmExpression expression, IEdmStructuredValue context, IEdmTypeReference targetType) + { + IEdmValue edmValue = this.Evaluate(expression, context, targetType); + return this.edmToClrConverter.AsClrValue(edmValue); + } + + /// + /// Find the Edm type from an by edm type name. + /// + /// The edm type name. + /// The edm model. + /// If the exists, return it. Or return false. + internal IEdmType ResolveEdmTypeFromName(string edmTypeName, IEdmModel edmModel) + { + string typeName = null; + if (this.edmToClrConverter.TryGetClrTypeNameDelegate(edmModel, edmTypeName, out typeName)) + { + return FindEdmType(typeName, edmModel); + } + + return null; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmApplyExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmApplyExpression.cs new file mode 100644 index 0000000..aad7163 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmApplyExpression.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM operation application expression. + /// + public class EdmApplyExpression : EdmElement, IEdmApplyExpression + { + private readonly IEdmFunction appliedFunction; + private readonly IEnumerable arguments; + + /// + /// Initializes a new instance of the class. + /// + /// Function to apply. + /// Application arguments. Value may be null, in which case it is treated as an empty enumerable. + public EdmApplyExpression(IEdmFunction appliedFunction, params IEdmExpression[] arguments) + : this(appliedFunction, (IEnumerable)arguments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Function to apply. + /// Application arguments. Value may be null, in which case it is treated as an empty enumerable. + public EdmApplyExpression(IEdmFunction appliedFunction, IEnumerable arguments) + { + EdmUtil.CheckArgumentNull(appliedFunction, "appliedFunction"); + EdmUtil.CheckArgumentNull(arguments, "arguments"); + + this.appliedFunction = appliedFunction; + this.arguments = arguments; + } + + /// + /// Gets the applied function. + /// + public IEdmFunction AppliedFunction + { + get { return this.appliedFunction; } + } + + /// + /// Gets the arguments to the operation. + /// + public IEnumerable Arguments + { + get { return this.arguments; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.FunctionApplication; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCastExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCastExpression.cs new file mode 100644 index 0000000..370f9ce --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCastExpression.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM type assertion expression. + /// + public class EdmCastExpression : EdmElement, IEdmCastExpression + { + private readonly IEdmExpression operand; + private readonly IEdmTypeReference type; + + /// + /// Initializes a new instance of the class. + /// + /// Expression for which the type is casted. + /// Type to cast. + public EdmCastExpression(IEdmExpression operand, IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(operand, "operand"); + EdmUtil.CheckArgumentNull(type, "type"); + + this.operand = operand; + this.type = type; + } + + /// + /// Gets the expression for which the type is asserted. + /// + public IEdmExpression Operand + { + get { return this.operand; } + } + + /// + /// Gets the asserted type. + /// + public IEdmTypeReference Type + { + get { return this.type; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Cast; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCollectionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCollectionExpression.cs new file mode 100644 index 0000000..66ab529 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmCollectionExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM multi-value construction expression. + /// + public class EdmCollectionExpression : EdmElement, IEdmCollectionExpression + { + private readonly IEdmTypeReference declaredType; + private readonly IEnumerable elements; + + /// + /// Initializes a new instance of the class. + /// + /// The constructed element values. + public EdmCollectionExpression(params IEdmExpression[] elements) + : this((IEnumerable)elements) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Declared type of the collection. + /// The constructed element values. + public EdmCollectionExpression(IEdmTypeReference declaredType, params IEdmExpression[] elements) + : this(declaredType, (IEnumerable)elements) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The constructed element values. + public EdmCollectionExpression(IEnumerable elements) + : this(null, elements) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Declared type of the collection. + /// The constructed element values. + public EdmCollectionExpression(IEdmTypeReference declaredType, IEnumerable elements) + { + EdmUtil.CheckArgumentNull(elements, "elements"); + + this.declaredType = declaredType; + this.elements = elements; + } + + /// + /// Gets the declared type of the collection. + /// + public IEdmTypeReference DeclaredType + { + get { return this.declaredType; } + } + + /// + /// Gets the constructed element values. + /// + public IEnumerable Elements + { + get { return this.elements; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Collection; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmEnumMemberExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmEnumMemberExpression.cs new file mode 100644 index 0000000..a057f12 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmEnumMemberExpression.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM enumeration member reference expression. + /// + public class EdmEnumMemberExpression : EdmElement, IEdmEnumMemberExpression + { + private readonly List enumMembers; + + /// + /// Initializes a new instance of the class. + /// + /// Referenced enum member. + public EdmEnumMemberExpression(params IEdmEnumMember[] enumMembers) + { + EdmUtil.CheckArgumentNull(enumMembers, "referencedEnumMember"); + Debug.Assert(enumMembers.Any(), "enumMembers is empty."); + this.enumMembers = enumMembers.ToList(); + } + + /// + /// Gets the referenced enum member. + /// + public IEnumerable EnumMembers + { + get { return this.enumMembers; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.EnumMember; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIfExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIfExpression.cs new file mode 100644 index 0000000..915ef48 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIfExpression.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM if expression. + /// + public class EdmIfExpression : EdmElement, IEdmIfExpression + { + private readonly IEdmExpression testExpression; + private readonly IEdmExpression trueExpression; + private readonly IEdmExpression falseExpression; + + /// + /// Initializes a new instance of the class. + /// + /// Test expression + /// Expression to evaluate if evaluates to true. + /// Expression to evaluate if evaluates to false. + public EdmIfExpression(IEdmExpression testExpression, IEdmExpression trueExpression, IEdmExpression falseExpression) + { + EdmUtil.CheckArgumentNull(testExpression, "testExpression"); + EdmUtil.CheckArgumentNull(trueExpression, "trueExpression"); + EdmUtil.CheckArgumentNull(falseExpression, "falseExpression"); + + this.testExpression = testExpression; + this.trueExpression = trueExpression; + this.falseExpression = falseExpression; + } + + /// + /// Gets the test expression. + /// + public IEdmExpression TestExpression + { + get { return this.testExpression; } + } + + /// + /// Gets the expression to evaluate if evaluates to true. + /// + public IEdmExpression TrueExpression + { + get { return this.trueExpression; } + } + + /// + /// Gets the expression to evaluate if evaluates to false. + /// + public IEdmExpression FalseExpression + { + get { return this.falseExpression; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.If; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIsTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIsTypeExpression.cs new file mode 100644 index 0000000..0ea7e7a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmIsTypeExpression.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM type test expression. + /// + public class EdmIsTypeExpression : EdmElement, IEdmIsTypeExpression + { + private readonly IEdmExpression operand; + private readonly IEdmTypeReference type; + + /// + /// Initializes a new instance of the class. + /// + /// Expression whose type is to be tested. + /// Type to test. + public EdmIsTypeExpression(IEdmExpression operand, IEdmTypeReference type) + { + EdmUtil.CheckArgumentNull(operand, "operand"); + EdmUtil.CheckArgumentNull(type, "type"); + + this.operand = operand; + this.type = type; + } + + /// + /// Gets the expression whose type is to be tested. + /// + public IEdmExpression Operand + { + get { return this.operand; } + } + + /// + /// Gets the type to be tested against. + /// + public IEdmTypeReference Type + { + get { return this.type; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.IsType; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpression.cs new file mode 100644 index 0000000..668d5e9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpression.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM labeled expression. + /// + public class EdmLabeledExpression : EdmElement, IEdmLabeledExpression + { + private readonly string name; + private readonly IEdmExpression expression; + + /// + /// Initializes a new instance of the class. + /// + /// Label of the expression. + /// Underlying expression. + public EdmLabeledExpression(string name, IEdmExpression expression) + { + EdmUtil.CheckArgumentNull(name, "name"); + EdmUtil.CheckArgumentNull(expression, "expression"); + + this.name = name; + this.expression = expression; + } + + /// + /// Gets the label. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the underlying expression. + /// + public IEdmExpression Expression + { + get { return this.expression; } + } + + /// + /// Gets the expression kind. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Labeled; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpressionReferenceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpressionReferenceExpression.cs new file mode 100644 index 0000000..667aafa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmLabeledExpressionReferenceExpression.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM labeled expression reference expression. + /// + public class EdmLabeledExpressionReferenceExpression : EdmElement, IEdmLabeledExpressionReferenceExpression + { + private IEdmLabeledExpression referencedLabeledExpression; + + /// + /// Initializes a new instance of class with non-initialized property. + /// + public EdmLabeledExpressionReferenceExpression() + { + } + + /// + /// Initializes a new instance of the class. + /// This constructor will not allow changing property after the EdmLabeledExpressionReferenceExpression instance has been constructed. + /// + /// Referenced labeled element. + public EdmLabeledExpressionReferenceExpression(IEdmLabeledExpression referencedLabeledExpression) + { + EdmUtil.CheckArgumentNull(referencedLabeledExpression, "referencedLabeledExpression"); + this.referencedLabeledExpression = referencedLabeledExpression; + } + + /// + /// Gets or sets the referenced labeled element. + /// The referenced labeled element can be initialized only once either using the constructor or by assigning value directly to this property. + /// + public IEdmLabeledExpression ReferencedLabeledExpression + { + get + { + return this.referencedLabeledExpression; + } + + set + { + EdmUtil.CheckArgumentNull(value, "value"); + + if (this.referencedLabeledExpression != null) + { + throw new InvalidOperationException(Strings.ValueHasAlreadyBeenSet); + } + + this.referencedLabeledExpression = value; + } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.LabeledExpressionReference; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNavigationPropertyPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNavigationPropertyPathExpression.cs new file mode 100644 index 0000000..c1ea5f0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNavigationPropertyPathExpression.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM navigation property path expression. + /// + public class EdmNavigationPropertyPathExpression : EdmPathExpression + { + /// + /// Initializes a new instance of the class. + /// + /// Path string containing segments separated by '/'. For example: "A.B/C/D.E/Func1(NS.T,NS.T2)/P1". + public EdmNavigationPropertyPathExpression(string path) + : base(path) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Path segments. + public EdmNavigationPropertyPathExpression(params string[] pathSegments) + : base(pathSegments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Path segments. + public EdmNavigationPropertyPathExpression(IEnumerable pathSegments) + : base(pathSegments) + { + } + + /// + /// Gets the kind of this expression. + /// + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.NavigationPropertyPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNullExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNullExpression.cs new file mode 100644 index 0000000..afc88f0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmNullExpression.cs @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM null. + /// + public class EdmNullExpression : EdmValue, IEdmNullExpression + { + /// + /// Singleton instance. + /// + public static EdmNullExpression Instance = new EdmNullExpression(); + + /// + /// Initializes a new instance of the class. + /// + private EdmNullExpression() + : base(null) + { + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Null; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Null; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyConstructor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyConstructor.cs new file mode 100644 index 0000000..0559a23 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyConstructor.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM property constructor specified as part of a EDM record construction expression. + /// + public class EdmPropertyConstructor : EdmElement, IEdmPropertyConstructor + { + private readonly string name; + private readonly IEdmExpression value; + + /// + /// Initializes a new instance of the class. + /// + /// Property name. + /// Property value. + public EdmPropertyConstructor(string name, IEdmExpression value) + { + EdmUtil.CheckArgumentNull(name, "name"); + EdmUtil.CheckArgumentNull(value, "value"); + + this.name = name; + this.value = value; + } + + /// + /// Gets the name of the property. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the expression for the value of the property. + /// + public IEdmExpression Value + { + get { return this.value; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyPathExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyPathExpression.cs new file mode 100644 index 0000000..d6ee638 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmPropertyPathExpression.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM property path expression. + /// + public class EdmPropertyPathExpression : EdmPathExpression + { + /// + /// Initializes a new instance of the class. + /// + /// Path string containing segments seperated by '/'. For example: "A.B/C/D.E/Func1(NS.T,NS.T2)/P1". + public EdmPropertyPathExpression(string path) + : base(path) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Path segments. + public EdmPropertyPathExpression(params string[] pathSegments) + : base(pathSegments) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Path segments. + public EdmPropertyPathExpression(IEnumerable pathSegments) + : base(pathSegments) + { + } + + /// + /// Gets the kind of this expression. + /// + public override EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.PropertyPath; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmRecordExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmRecordExpression.cs new file mode 100644 index 0000000..ceb66ef --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/EdmRecordExpression.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM record construction expression. + /// + public class EdmRecordExpression : EdmElement, IEdmRecordExpression + { + private readonly IEdmStructuredTypeReference declaredType; + private readonly IEnumerable properties; + + /// + /// Initializes a new instance of the class. + /// + /// Property constructors. + public EdmRecordExpression(params IEdmPropertyConstructor[] properties) + : this((IEnumerable)properties) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Declared type of the record. + /// Property constructors. + public EdmRecordExpression(IEdmStructuredTypeReference declaredType, params IEdmPropertyConstructor[] properties) + : this(declaredType, (IEnumerable)properties) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Property constructors. + public EdmRecordExpression(IEnumerable properties) + : this(null, properties) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Optional declared type of the record. + /// Property constructors. + public EdmRecordExpression(IEdmStructuredTypeReference declaredType, IEnumerable properties) + { + EdmUtil.CheckArgumentNull(properties, "properties"); + + this.declaredType = declaredType; + this.properties = properties; + } + + /// + /// Gets the declared type of the record, or null if there is no declared type. + /// + public IEdmStructuredTypeReference DeclaredType + { + get { return this.declaredType; } + } + + /// + /// Gets the constructed property values. + /// + public IEnumerable Properties + { + get { return this.properties; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.Record; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmApplyExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmApplyExpression.cs new file mode 100644 index 0000000..aec64e4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmApplyExpression.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM function application expression. + /// + public interface IEdmApplyExpression : IEdmExpression + { + /// + /// Gets the applied function. + /// + IEdmFunction AppliedFunction { get; } + + /// + /// Gets the arguments to the function. + /// + IEnumerable Arguments { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBinaryConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBinaryConstantExpression.cs new file mode 100644 index 0000000..deb4ab0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBinaryConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM binary constant expression. + /// + public interface IEdmBinaryConstantExpression : IEdmExpression, IEdmBinaryValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBooleanConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBooleanConstantExpression.cs new file mode 100644 index 0000000..fb68c0b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmBooleanConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM boolean constant expression. + /// + public interface IEdmBooleanConstantExpression : IEdmExpression, IEdmBooleanValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCastExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCastExpression.cs new file mode 100644 index 0000000..c7255b2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCastExpression.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM type assertion expression. + /// + public interface IEdmCastExpression : IEdmExpression + { + /// + /// Gets the expression for which the type is asserted. + /// + IEdmExpression Operand { get; } + + /// + /// Gets the asserted type. + /// + IEdmTypeReference Type { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCollectionExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCollectionExpression.cs new file mode 100644 index 0000000..d44b412 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmCollectionExpression.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM multi-value construction expression. + /// + public interface IEdmCollectionExpression : IEdmExpression + { + /// + /// Gets the declared type of the collection, or null if there is no declared type. + /// + IEdmTypeReference DeclaredType { get; } + + /// + /// Gets the constructed element values. + /// + IEnumerable Elements { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateConstantExpression.cs new file mode 100644 index 0000000..ebbe952 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM date constant expression. + /// + public interface IEdmDateConstantExpression : IEdmExpression, IEdmDateValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateTimeOffsetConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateTimeOffsetConstantExpression.cs new file mode 100644 index 0000000..4f673c6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDateTimeOffsetConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM datetime with offset constant expression. + /// + public interface IEdmDateTimeOffsetConstantExpression : IEdmExpression, IEdmDateTimeOffsetValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDecimalConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDecimalConstantExpression.cs new file mode 100644 index 0000000..2647ee7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDecimalConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM decimal constant expression. + /// + public interface IEdmDecimalConstantExpression : IEdmExpression, IEdmDecimalValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDurationConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDurationConstantExpression.cs new file mode 100644 index 0000000..06cafc2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmDurationConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM duration constant expression. + /// + public interface IEdmDurationConstantExpression : IEdmExpression, IEdmDurationValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmEnumMemberExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmEnumMemberExpression.cs new file mode 100644 index 0000000..343f23c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmEnumMemberExpression.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM enumeration member reference expression. + /// + public interface IEdmEnumMemberExpression : IEdmExpression + { + /// + /// Gets the referenced enum member. + /// + IEnumerable EnumMembers { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmFloatingConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmFloatingConstantExpression.cs new file mode 100644 index 0000000..709e40d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmFloatingConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM floating constant expression. + /// + public interface IEdmFloatingConstantExpression : IEdmExpression, IEdmFloatingValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmGuidConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmGuidConstantExpression.cs new file mode 100644 index 0000000..f6f6013 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmGuidConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM guid constant expression. + /// + public interface IEdmGuidConstantExpression : IEdmExpression, IEdmGuidValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIfExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIfExpression.cs new file mode 100644 index 0000000..8197dc3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIfExpression.cs @@ -0,0 +1,29 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM if expression. + /// + public interface IEdmIfExpression : IEdmExpression + { + /// + /// Gets the test expression. + /// + IEdmExpression TestExpression { get; } + + /// + /// Gets the expression to evaluate if evaluates to true. + /// + IEdmExpression TrueExpression { get; } + + /// + /// Gets the expression to evaluate if evaluates to false. + /// + IEdmExpression FalseExpression { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIntegerConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIntegerConstantExpression.cs new file mode 100644 index 0000000..0e11c4d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIntegerConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM integer constant expression. + /// + public interface IEdmIntegerConstantExpression : IEdmExpression, IEdmIntegerValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIsTypeExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIsTypeExpression.cs new file mode 100644 index 0000000..0b7b661 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmIsTypeExpression.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM type test expression. + /// + public interface IEdmIsTypeExpression : IEdmExpression + { + /// + /// Gets the expression whose type is to be tested. + /// + IEdmExpression Operand { get; } + + /// + /// Gets the type to be tested against. + /// + IEdmTypeReference Type { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpression.cs new file mode 100644 index 0000000..79be879 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpression.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM labeled expression element. + /// + public interface IEdmLabeledExpression : IEdmNamedElement, IEdmExpression + { + /// + /// Gets the underlying expression. + /// + IEdmExpression Expression { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpressionReferenceExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpressionReferenceExpression.cs new file mode 100644 index 0000000..bfceae7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmLabeledExpressionReferenceExpression.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents a reference to an EDM labeled expression. + /// + public interface IEdmLabeledExpressionReferenceExpression : IEdmExpression + { + /// + /// Gets the referenced expression. + /// + IEdmLabeledExpression ReferencedLabeledExpression { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmNullExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmNullExpression.cs new file mode 100644 index 0000000..be1e176 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmNullExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM null expression. + /// + public interface IEdmNullExpression : IEdmExpression, IEdmNullValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmPropertyConstructor.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmPropertyConstructor.cs new file mode 100644 index 0000000..a102795 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmPropertyConstructor.cs @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM property constructor specified as part of a EDM construction record expression. + /// + public interface IEdmPropertyConstructor : IEdmElement + { + /// + /// Gets the name of the property. + /// + string Name { get; } + + /// + /// Gets the expression for the value of the property. + /// + IEdmExpression Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmRecordExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmRecordExpression.cs new file mode 100644 index 0000000..7008154 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmRecordExpression.cs @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM record construction expression. + /// + public interface IEdmRecordExpression : IEdmExpression + { + /// + /// Gets the declared type of the record, or null if there is no declared type. + /// + IEdmStructuredTypeReference DeclaredType { get; } + + /// + /// Gets the constructed property values. + /// + IEnumerable Properties { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmStringConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmStringConstantExpression.cs new file mode 100644 index 0000000..1e739a4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmStringConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM string constant expression. + /// + public interface IEdmStringConstantExpression : IEdmExpression, IEdmStringValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmTimeOfDayConstantExpression.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmTimeOfDayConstantExpression.cs new file mode 100644 index 0000000..08c7b31 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Expressions/IEdmTimeOfDayConstantExpression.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM TimeOfDay constant expression. + /// + public interface IEdmTimeOfDayConstantExpression : IEdmExpression, IEdmTimeOfDayValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/TryCreateObjectInstance.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/TryCreateObjectInstance.cs new file mode 100644 index 0000000..aad747a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/TryCreateObjectInstance.cs @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents a delegate for creating an instance of CLR type based on and . + /// The delegate can be used to create CLR instances of polymorphic types. + /// + /// The for which the needs to be created. + /// The expected CLR type of the object instance. In case of polymorphic properties and collections this may be a base type. + /// The converter instance calling this delegate. + /// The output parameter returning a CLR object instance created for the . + /// The output parameter returning true if all properties of the created are initialized. + /// False if properties of the created instance should be initialized using the default logic. + /// True if the delegate produced a desired . + /// If delegate returns false, the default logic will be applied to create and populate a CLR object instance. + public delegate bool TryCreateObjectInstance( + IEdmStructuredValue edmValue, + Type clrType, + EdmToClrConverter converter, + out object objectInstance, + out bool objectInstanceInitialized); + + /// + /// Represents a delegate to get property info of a CLR type based on the property name and . + /// + /// The CLR type which contains the property info. + /// The property name which might be server side name or client side name. + /// The output parameter returning a PropertyInfo. + /// True if the delegate find the property, else false. + public delegate bool TryGetClrPropertyInfo( + Type clrType, + string edmName, + out PropertyInfo propertyInfo); + + /// + /// Represents a delegate to get CLR type name based on the edm type name and . + /// + /// The used to find the type name. + /// The edm type name. + /// The output parameter returning a CLR type name. + /// True if the delegate find the type name, else false. + public delegate bool TryGetClrTypeName( + IEdmModel edmModel, + string edmTypeName, + out string clrTypeName); +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularies.xml b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularies.xml new file mode 100644 index 0000000..ddbde19 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularies.xml @@ -0,0 +1,122 @@ + + + + Terms describing validation rules + + + + + + + + + + + + + + + + + + + + + + + + + + + + Validation.Exclusive + + + + + + + + + Validation.Exclusive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Example: entity type `Customer` has navigation properties `AllOrders`, `OpenOrders`, and `ClosedOrders`. +The term allows to express that items of `OpenOrders` and `ClosedOrders` are also items of the `AllOrders` navigation property, +even though they are defined in an `Orders` entity set. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularyModel.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularyModel.cs new file mode 100644 index 0000000..d1c67f7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/ValidationVocabularyModel.cs @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.OData.Edm.Vocabularies.V1 +{ + /// + /// Representing Validation Vocabulary Model. + /// + public static class ValidationVocabularyModel + { + /// + /// The namespace of the validation vocabulary model. + /// + public static readonly string Namespace = "Org.OData.Validation.V1"; + + /// + /// The EDM model with validation vocabularies. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmModel is immutable")] + public static readonly IEdmModel Instance = VocabularyModelProvider.ValidationModel; + + /// + /// The DerivedTypeConstraint term. + /// + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")] + public static readonly IEdmTerm DerivedTypeConstraintTerm = VocabularyModelProvider.ValidationModel.FindDeclaredTerm(Namespace + ".DerivedTypeConstraint"); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBinaryConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBinaryConstant.cs new file mode 100644 index 0000000..f8b2760 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBinaryConstant.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM binary constant. + /// + public class EdmBinaryConstant : EdmValue, IEdmBinaryConstantExpression + { + private readonly byte[] value; + + /// + /// Initializes a new instance of the class. + /// + /// Integer value represented by this value. + public EdmBinaryConstant(byte[] value) + : this(null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the integer. + /// Integer value represented by this value. + public EdmBinaryConstant(IEdmBinaryTypeReference type, byte[] value) + : base(type) + { + EdmUtil.CheckArgumentNull(value, "value"); + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public byte[] Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.BinaryConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Binary; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBooleanConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBooleanConstant.cs new file mode 100644 index 0000000..f3c094b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmBooleanConstant.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM boolean constant. + /// + public class EdmBooleanConstant : EdmValue, IEdmBooleanConstantExpression + { + private readonly bool value; + + /// + /// Initializes a new instance of the class. + /// + /// Boolean value represented by this value. + public EdmBooleanConstant(bool value) + : this(null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the boolean. + /// Boolean value represented by this value. + public EdmBooleanConstant(IEdmPrimitiveTypeReference type, bool value) + : base(type) + { + this.value = value; + } + + /// + /// Gets a value indicating whether the value of this boolean value is true or false. + /// + public bool Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.BooleanConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Boolean; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmCollectionValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmCollectionValue.cs new file mode 100644 index 0000000..a75f827 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmCollectionValue.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM collection value. + /// + public class EdmCollectionValue : EdmValue, IEdmCollectionValue + { + private readonly IEnumerable elements; + + /// + /// Initializes a new instance of the class. + /// + /// A reference to a collection type that describes this collection value + /// The collection of values stored in this collection value + public EdmCollectionValue(IEdmCollectionTypeReference type, IEnumerable elements) + : base(type) + { + this.elements = elements; + } + + /// + /// Gets the values stored in this collection. + /// + public IEnumerable Elements + { + get { return this.elements; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Collection; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateConstant.cs new file mode 100644 index 0000000..91bc53e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateConstant.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM date constant. + /// + public class EdmDateConstant : EdmValue, IEdmDateConstantExpression + { + private readonly Date value; + + /// + /// Initializes a new instance of the class. + /// + /// Date value represented by this value. + public EdmDateConstant(Date value) + : this(null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the Date. + /// Date value represented by this value. + public EdmDateConstant(IEdmPrimitiveTypeReference type, Date value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the content of this value. + /// + public Date Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DateConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Date; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateTimeOffsetConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateTimeOffsetConstant.cs new file mode 100644 index 0000000..b23a549 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDateTimeOffsetConstant.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM datetime with offset constant. + /// + public class EdmDateTimeOffsetConstant : EdmValue, IEdmDateTimeOffsetConstantExpression + { + private readonly DateTimeOffset value; + + /// + /// Initializes a new instance of the class. + /// + /// DateTimeOffset value represented by this value. + public EdmDateTimeOffsetConstant(DateTimeOffset value) + : this(null, value) + { + this.value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the DateTimeOffset. + /// DateTimeOffset value represented by this value. + public EdmDateTimeOffsetConstant(IEdmTemporalTypeReference type, DateTimeOffset value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public DateTimeOffset Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DateTimeOffsetConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.DateTimeOffset; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDecimalConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDecimalConstant.cs new file mode 100644 index 0000000..61572d7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDecimalConstant.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM decimal constant. + /// + public class EdmDecimalConstant : EdmValue, IEdmDecimalConstantExpression + { + private readonly decimal value; + + /// + /// Initializes a new instance of the class. + /// + /// Decimal value represented by this value. + public EdmDecimalConstant(decimal value) + : this(null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the decimal. + /// Decimal value represented by this value. + public EdmDecimalConstant(IEdmDecimalTypeReference type, decimal value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public decimal Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DecimalConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Decimal; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDurationConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDurationConstant.cs new file mode 100644 index 0000000..223b290 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmDurationConstant.cs @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM duration constant. + /// + public class EdmDurationConstant : EdmValue, IEdmDurationConstantExpression + { + private readonly TimeSpan value; + + /// + /// Initializes a new instance of the class. + /// + /// Duration value represented by this value. + public EdmDurationConstant(TimeSpan value) + : this(null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the Duration. + /// Duration value represented by this value. + public EdmDurationConstant(IEdmTemporalTypeReference type, TimeSpan value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public TimeSpan Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.DurationConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Duration; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmEnumValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmEnumValue.cs new file mode 100644 index 0000000..b1ba7d1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmEnumValue.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM enumeration type value. + /// + public class EdmEnumValue : EdmValue, IEdmEnumValue + { + private readonly IEdmEnumMemberValue value; + + /// + /// Initializes a new instance of the class. + /// + /// A reference to the enumeration type that describes this value. + /// The enumeration type value. + public EdmEnumValue(IEdmEnumTypeReference type, IEdmEnumMember member) + : this(type, member.Value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A reference to the enumeration type that describes this value. + /// The underlying type value. + public EdmEnumValue(IEdmEnumTypeReference type, IEdmEnumMemberValue value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the underlying type value of the enumeration type. + /// + public IEdmEnumMemberValue Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Enum; } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmFloatingConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmFloatingConstant.cs new file mode 100644 index 0000000..54cf1a0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmFloatingConstant.cs @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM floating point constant. + /// + public class EdmFloatingConstant : EdmValue, IEdmFloatingConstantExpression + { + private readonly double value; + + /// + /// Initializes a new instance of the class. + /// + /// Floating point value represented by this value. + public EdmFloatingConstant(double value) + : this(null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the floating point. + /// Floating point value represented by this value. + public EdmFloatingConstant(IEdmPrimitiveTypeReference type, double value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public double Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.FloatingConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Floating; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmGuidConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmGuidConstant.cs new file mode 100644 index 0000000..1f186f3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmGuidConstant.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM guid constant. + /// + public class EdmGuidConstant : EdmValue, IEdmGuidConstantExpression + { + private readonly Guid value; + + /// + /// Initializes a new instance of the class. + /// + /// Integer value represented by this value. + public EdmGuidConstant(Guid value) + : this(null, value) + { + this.value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the integer. + /// Integer value represented by this value. + public EdmGuidConstant(IEdmPrimitiveTypeReference type, Guid value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public Guid Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.GuidConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Guid; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmIntegerConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmIntegerConstant.cs new file mode 100644 index 0000000..9f78296 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmIntegerConstant.cs @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM integer constant. + /// + public class EdmIntegerConstant : EdmValue, IEdmIntegerConstantExpression + { + private readonly Int64 value; + + /// + /// Initializes a new instance of the class. + /// + /// Integer value represented by this value. + public EdmIntegerConstant(Int64 value) + : this(null, value) + { + this.value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the integer. + /// Integer value represented by this value. + public EdmIntegerConstant(IEdmPrimitiveTypeReference type, Int64 value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public Int64 Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.IntegerConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Integer; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmPropertyValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmPropertyValue.cs new file mode 100644 index 0000000..d78a74e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmPropertyValue.cs @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents a value of an EDM property. + /// + public class EdmPropertyValue : IEdmPropertyValue + { + private readonly string name; + private IEdmValue value; + + /// + /// Initializes a new instance of the class with non-initialized property. + /// This constructor allows setting property once after has been constructed. + /// + /// Name of the property for which this provides a value. + public EdmPropertyValue(string name) + { + EdmUtil.CheckArgumentNull(name, "name"); + this.name = name; + } + + /// + /// Initializes a new instance of the class. + /// This constructor will not allow changing property after the EdmPropertyValue instance has been constructed. + /// + /// Name of the property for which this provides a value. + /// Value of the property. + public EdmPropertyValue(string name, IEdmValue value) + { + EdmUtil.CheckArgumentNull(name, "name"); + EdmUtil.CheckArgumentNull(value, "value"); + + this.name = name; + this.value = value; + } + + /// + /// Gets the name of the property for which this provides a value. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets or sets the value of the property. + /// The value can be initialized only once either using the constructor or by assigning value directly to this property. + /// + public IEdmValue Value + { + get + { + return this.value; + } + + set + { + EdmUtil.CheckArgumentNull(value, "value"); + + if (this.value != null) + { + throw new InvalidOperationException(Strings.ValueHasAlreadyBeenSet); + } + + this.value = value; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStringConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStringConstant.cs new file mode 100644 index 0000000..78bd679 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStringConstant.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM string constant. + /// + public class EdmStringConstant : EdmValue, IEdmStringConstantExpression + { + private readonly string value; + + /// + /// Initializes a new instance of the class. + /// + /// String value represented by this value. + public EdmStringConstant(string value) + : this(null, value) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the string. + /// String value represented by this value. + public EdmStringConstant(IEdmStringTypeReference type, string value) + : base(type) + { + EdmUtil.CheckArgumentNull(value, "value"); + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public string Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.StringConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.String; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStructuredValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStructuredValue.cs new file mode 100644 index 0000000..d8eb49e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmStructuredValue.cs @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM structured value. + /// + public class EdmStructuredValue : EdmValue, IEdmStructuredValue + { + private readonly IEnumerable propertyValues; + + private readonly Cache> propertiesDictionaryCache; + private static readonly Func> ComputePropertiesDictionaryFunc = (me) => me.ComputePropertiesDictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// Type that describes this value. + /// Child values of this value. + public EdmStructuredValue(IEdmStructuredTypeReference type, IEnumerable propertyValues) + : base(type) + { + EdmUtil.CheckArgumentNull(propertyValues, "propertyValues"); + + this.propertyValues = propertyValues; + if (propertyValues != null) + { + // If there are enough property values, make FindPropertyValue use a dictionary. + int propertyCount = propertyValues.Count(); + if (propertyCount > 5) + { + this.propertiesDictionaryCache = new Cache>(); + } + } + } + + /// + /// Gets the property values of this structured value. + /// + public IEnumerable PropertyValues + { + get { return this.propertyValues; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.Structured; } + } + + private Dictionary PropertiesDictionary + { + get { return this.propertiesDictionaryCache == null ? null : this.propertiesDictionaryCache.GetValue(this, ComputePropertiesDictionaryFunc, null); } + } + + /// + /// Retrieves the value corresponding to the given property name. Returns null if no such value exists. + /// + /// The property that describes the value being found. + /// The requested value, or null if no such value exists. + public IEdmPropertyValue FindPropertyValue(string propertyName) + { + // If there is a dictionary, use it. + Dictionary propertiesDictionary = this.PropertiesDictionary; + if (propertiesDictionary != null) + { + IEdmPropertyValue propertyValue; + propertiesDictionary.TryGetValue(propertyName, out propertyValue); + return propertyValue; + } + + // If there is no dictionary, go through the property values and take the first match. + foreach (IEdmPropertyValue propertyValue in this.propertyValues) + { + if (propertyValue.Name == propertyName) + { + return propertyValue; + } + } + + return null; + } + + private Dictionary ComputePropertiesDictionary() + { + Dictionary propertiesDictionary = new Dictionary(); + + foreach (IEdmPropertyValue propertyValue in this.propertyValues) + { + propertiesDictionary[propertyValue.Name] = propertyValue; + } + + return propertiesDictionary; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmTimeOfDayConstant.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmTimeOfDayConstant.cs new file mode 100644 index 0000000..20ee55d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmTimeOfDayConstant.cs @@ -0,0 +1,61 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM TimeOfDay constant. + /// + public class EdmTimeOfDayConstant : EdmValue, IEdmTimeOfDayConstantExpression + { + private readonly TimeOfDay value; + + /// + /// Initializes a new instance of the class. + /// + /// TimeOfDay value represented by this value. + public EdmTimeOfDayConstant(TimeOfDay value) + : this(null, value) + { + this.value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the TimeOfDay. + /// TimeOfDay value represented by this value. + public EdmTimeOfDayConstant(IEdmTemporalTypeReference type, TimeOfDay value) + : base(type) + { + this.value = value; + } + + /// + /// Gets the definition of this value. + /// + public TimeOfDay Value + { + get { return this.value; } + } + + /// + /// Gets the kind of this expression. + /// + public EdmExpressionKind ExpressionKind + { + get { return EdmExpressionKind.TimeOfDayConstant; } + } + + /// + /// Gets the kind of this value. + /// + public override EdmValueKind ValueKind + { + get { return EdmValueKind.TimeOfDay; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmValue.cs new file mode 100644 index 0000000..fd27654 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/EdmValue.cs @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM value. + /// + public abstract class EdmValue : IEdmValue, IEdmDelayedValue + { + private readonly IEdmTypeReference type; + + /// + /// Initializes a new instance of the EdmValue class. + /// + /// Type of the value. + protected EdmValue(IEdmTypeReference type) + { + this.type = type; + } + + /// + /// Gets the type of this value. + /// + public IEdmTypeReference Type + { + get { return this.type; } + } + + /// + /// Gets the kind of this value. + /// + public abstract EdmValueKind ValueKind + { + get; + } + + IEdmValue IEdmDelayedValue.Value + { + get { return this; } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBinaryValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBinaryValue.cs new file mode 100644 index 0000000..4c1e92f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBinaryValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM binary value. + /// + public interface IEdmBinaryValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this binary value. + /// + byte[] Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBooleanValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBooleanValue.cs new file mode 100644 index 0000000..f119491 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmBooleanValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM boolean value. + /// + public interface IEdmBooleanValue : IEdmPrimitiveValue + { + /// + /// Gets a value indicating whether the value of this boolean value is true or false. + /// + bool Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmCollectionValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmCollectionValue.cs new file mode 100644 index 0000000..28ea234 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmCollectionValue.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM collection value. + /// + public interface IEdmCollectionValue : IEdmValue + { + /// + /// Gets the values stored in this collection. + /// + IEnumerable Elements { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateTimeOffsetValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateTimeOffsetValue.cs new file mode 100644 index 0000000..8ee0d5a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateTimeOffsetValue.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM datetime with offset value. + /// + public interface IEdmDateTimeOffsetValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this value. + /// + DateTimeOffset Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateValue.cs new file mode 100644 index 0000000..ed56e67 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDateValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM date. + /// + public interface IEdmDateValue : IEdmPrimitiveValue + { + /// + /// Gets the value. + /// + Date Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDecimalValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDecimalValue.cs new file mode 100644 index 0000000..e860a5e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDecimalValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM decimal value. + /// + public interface IEdmDecimalValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this decimal value. + /// + decimal Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDelayedValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDelayedValue.cs new file mode 100644 index 0000000..0d7519d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDelayedValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents a lazily computed value. + /// + public interface IEdmDelayedValue + { + /// + /// Gets the data stored in this value. + /// + IEdmValue Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDurationValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDurationValue.cs new file mode 100644 index 0000000..8380a72 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmDurationValue.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM duration value. + /// + public interface IEdmDurationValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this duration value. + /// + TimeSpan Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmEnumValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmEnumValue.cs new file mode 100644 index 0000000..6c40649 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmEnumValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM enumeration type value. + /// + public interface IEdmEnumValue : IEdmPrimitiveValue + { + /// + /// Gets the underlying type value of the enumeration type. + /// + IEdmEnumMemberValue Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmFloatingValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmFloatingValue.cs new file mode 100644 index 0000000..3460039 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmFloatingValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM floating point value. + /// + public interface IEdmFloatingValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this floating value. + /// + double Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmGuidValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmGuidValue.cs new file mode 100644 index 0000000..5e437a1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmGuidValue.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM integer value. + /// + public interface IEdmGuidValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this guid value. + /// + Guid Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmIntegerValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmIntegerValue.cs new file mode 100644 index 0000000..16e2e36 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmIntegerValue.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM integer value. + /// + public interface IEdmIntegerValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this integer value. + /// + Int64 Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmNullValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmNullValue.cs new file mode 100644 index 0000000..2eaf988 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmNullValue.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM null value. + /// + public interface IEdmNullValue : IEdmValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPrimitiveValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPrimitiveValue.cs new file mode 100644 index 0000000..21c22df --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPrimitiveValue.cs @@ -0,0 +1,15 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM primitive value. + /// + public interface IEdmPrimitiveValue : IEdmValue + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPropertyValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPropertyValue.cs new file mode 100644 index 0000000..240b7ec --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmPropertyValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents a value of an EDM property. + /// + public interface IEdmPropertyValue : IEdmDelayedValue + { + /// + /// Gets the name of the property this value is associated with. + /// + string Name { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStringValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStringValue.cs new file mode 100644 index 0000000..cbea5e6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStringValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM string value. + /// + public interface IEdmStringValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this string value. + /// + string Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStructuredValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStructuredValue.cs new file mode 100644 index 0000000..22df436 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmStructuredValue.cs @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM structured value. + /// + public interface IEdmStructuredValue : IEdmValue + { + /// + /// Gets the property values of this structured value. + /// + IEnumerable PropertyValues { get; } + + /// + /// Finds the value corresponding to the provided property name. + /// + /// Property to find the value of. + /// The found property, or null if no property was found. + IEdmPropertyValue FindPropertyValue(string propertyName); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmTimeOfDayValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmTimeOfDayValue.cs new file mode 100644 index 0000000..c13649f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmTimeOfDayValue.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Represents an EDM TimeOfDay value. + /// + public interface IEdmTimeOfDayValue : IEdmPrimitiveValue + { + /// + /// Gets the definition of this value. + /// + TimeOfDay Value { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmValue.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmValue.cs new file mode 100644 index 0000000..437468c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/Values/IEdmValue.cs @@ -0,0 +1,110 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm.Vocabularies +{ + /// + /// Defines Edm values + /// + public enum EdmValueKind + { + /// + /// Represents a value with an unknown or error kind. + /// + None = 0, + + /// + /// Represents a value implementing . + /// + Binary, + + /// + /// Represents a value implementing . + /// + Boolean, + + /// + /// Represents a value implementing . + /// + Collection, + + /// + /// Represents a value implementing . + /// + DateTimeOffset, + + /// + /// Represents a value implementing . + /// + Decimal, + + /// + /// Represents a value implementing . + /// + Enum, + + /// + /// Represents a value implementing . + /// + Floating, + + /// + /// Represents a value implementing . + /// + Guid, + + /// + /// Represents a value implementing . + /// + Integer, + + /// + /// Represents a value implementing . + /// + Null, + + /// + /// Represents a value implementing . + /// + String, + + /// + /// Represents a value implementing . + /// + Structured, + + /// + /// Represents a value implementing . + /// + Duration, + + /// + /// Represents a value implementing . + /// + Date, + + /// + /// Represents a value implementing . + /// + TimeOfDay, + } + + /// + /// Represents an EDM value. + /// + public interface IEdmValue : IEdmElement + { + /// + /// Gets the type of this value. + /// + IEdmTypeReference Type { get; } + + /// + /// Gets the kind of this value. + /// + EdmValueKind ValueKind { get; } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/VocabularyModelProvider.cs b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/VocabularyModelProvider.cs new file mode 100644 index 0000000..1b7152a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.OData.Edm/Vocabularies/VocabularyModelProvider.cs @@ -0,0 +1,120 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Vocabularies.V1 +{ + internal static class VocabularyModelProvider + { + /// + /// Core Vocabulary Model. + /// + public static readonly IEdmModel CoreModel; + + /// + /// Capabilities Vocabulary Model. + /// + public static readonly IEdmModel CapabilitesModel; + + /// + /// Alternate Vocabulary Model. + /// + public static readonly IEdmModel AlternateKeyModel; + + /// + /// Community Vocabulary Model. + /// + public static readonly IEdmModel CommunityModel; + + /// + /// Validation Vocabulary Model. + /// + public static readonly IEdmModel ValidationModel; + + /// + /// Authorization Vocabulary Model. + /// + public static readonly IEdmModel AuthorizationModel; + + /// + /// All Vocabulary Models. + /// + public static readonly IEnumerable VocabularyModels; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static VocabularyModelProvider() + { + Assembly assembly = typeof(VocabularyModelProvider).GetAssembly(); + + string[] allResources = assembly.GetManifestResourceNames(); + + // core + string coreVocabularies = allResources.FirstOrDefault(x => x.Contains("CoreVocabularies.xml")); + Debug.Assert(coreVocabularies != null, "CoreVocabularies.xml: not found."); + CoreModel = LoadSchemaEdmModel(assembly, coreVocabularies, new IEdmModel[] { }); + + // Authorization + string authorizationVocabularies = allResources.FirstOrDefault(x => x.Contains("AuthorizationVocabularies.xml")); + Debug.Assert(authorizationVocabularies != null, "AuthorizationVocabularies.xml: not found."); + AuthorizationModel = LoadCsdlEdmModel(assembly, authorizationVocabularies, new[] { CoreModel }); // authorization relies on core + + // Validation + string validationVocabularies = allResources.Where(x => x.Contains("ValidationVocabularies.xml")).FirstOrDefault(); + Debug.Assert(validationVocabularies != null, "ValidationVocabularies.xml: not found."); + ValidationModel = LoadSchemaEdmModel(assembly, validationVocabularies, new[] { CoreModel }); // validation relies on core + + // capabilites + string capabilitiesVocabularies = allResources.FirstOrDefault(x => x.Contains("CapabilitiesVocabularies.xml")); + Debug.Assert(capabilitiesVocabularies != null, "CapabilitiesVocabularies.xml: not found."); + CapabilitesModel = LoadCsdlEdmModel(assembly, capabilitiesVocabularies, new[] { CoreModel, AuthorizationModel, ValidationModel }); // capabilities relies on core & validation + + // alternateKey + string alternateKeysVocabularies = allResources.Where(x => x.Contains("AlternateKeysVocabularies.xml")).FirstOrDefault(); + Debug.Assert(alternateKeysVocabularies != null, "AlternateKeysVocabularies.xml: not found."); + AlternateKeyModel = LoadSchemaEdmModel(assembly, alternateKeysVocabularies, new[] { CoreModel }); // alternate relies on core + + // Community + string communityVocabularies = allResources.Where(x => x.Contains("CommunityVocabularies.xml")).FirstOrDefault(); + Debug.Assert(communityVocabularies != null, "CommunityVocabularies.xml: not found."); + CommunityModel = LoadCsdlEdmModel(assembly, communityVocabularies, new[] { CoreModel }); // community relies on core + + VocabularyModels = new List + { + CoreModel, CapabilitesModel, AlternateKeyModel, CommunityModel, ValidationModel, AuthorizationModel + }; + } + + private static IEdmModel LoadCsdlEdmModel(Assembly assembly, string vocabularyName, IEnumerable referenceModels) + { + using (Stream stream = assembly.GetManifestResourceStream(vocabularyName)) + { + IEdmModel model; + IEnumerable errors; + CsdlReader.TryParse(XmlReader.Create(stream), referenceModels, false, out model, out errors); + return model; + } + } + + private static IEdmModel LoadSchemaEdmModel(Assembly assembly, string vocabularyName, IEnumerable referenceModels) + { + using (Stream stream = assembly.GetManifestResourceStream(vocabularyName)) + { + IEdmModel model; + IEnumerable errors; + SchemaReader.TryParse(new[] { XmlReader.Create(stream) }, referenceModels, false, out model, out errors); + return model; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/ActionOnDispose.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/ActionOnDispose.cs new file mode 100644 index 0000000..bef1fd2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/ActionOnDispose.cs @@ -0,0 +1,41 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// This class is responsible for executing an action the first time dispose is called on it. + /// + internal class ActionOnDispose : IDisposable + { + /// The action to be executed on dispose + private Action action; + + /// + /// Constructs an instance of the ActonOnDispose object + /// + /// the action to be execute on dispose + public ActionOnDispose(Action action) + { + Util.CheckArgumentNull(action, "action"); + this.action = action; + } + + /// + /// The dipose method of the IDisposable insterface + /// + public void Dispose() + { + if (this.action != null) + { + action(); + action = null; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.Net35/Microsoft.Spatial.NetFX35.csproj b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.Net35/Microsoft.Spatial.NetFX35.csproj new file mode 100644 index 0000000..c85fa4d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.Net35/Microsoft.Spatial.NetFX35.csproj @@ -0,0 +1,401 @@ + + + + true + v3.5 + Microsoft.Spatial.NetFX35.V7 + Library + $(DefineConstants);SPATIAL + $(AssemblyName).xml + Microsoft.Spatial + {1104C2E4-076F-47AA-8121-03015AE70630} + false + ..\ + + + + + $(EnlistmentRoot)\src\Microsoft.Spatial\Microsoft.Spatial.csproj + SpatialCrossTargettingSourcePath + + + + + + + + + + + + + Microsoft.Spatial.txt + Microsoft.Spatial + true + true + internal + true + Microsoft.Spatial.TextRes + + + + + + + Microsoft\Spatial\PlatformHelper.cs + + + Microsoft\Spatial\WrappedGeoJsonWriter.cs + + + Microsoft\Spatial\GeoJsonObjectFormatterImplementation.cs + + + Microsoft\Spatial\GeoJsonObjectWriter.cs + + + Microsoft\Spatial\GeoJsonWriterBase.cs + + + Microsoft\Spatial\IGeoJsonWriter.cs + + + Microsoft\Spatial\TypeWashedToGeographyLatLongPipeline.cs + + + Microsoft\Spatial\TypeWashedToGeographyLongLatPipeline.cs + + + Microsoft\Spatial\GeoJsonObjectReader.cs + + + Microsoft\Spatial\CompositeKey.cs + + + Microsoft\Spatial\FormatterExtensions.cs + + + Microsoft\Spatial\GeographyPipeline.cs + + + Microsoft\Spatial\GeographyFactory.cs + + + Microsoft\Spatial\GeographyFactoryOfT.cs + + + Microsoft\Spatial\GeographyFullGlobe.cs + + + Microsoft\Spatial\GeographyCurve.cs + + + Microsoft\Spatial\GeographyLineString.cs + + + Microsoft\Spatial\GeographyMultiCurve.cs + + + Microsoft\Spatial\GeographyMultiLineString.cs + + + Microsoft\Spatial\GeographyMultiPoint.cs + + + Microsoft\Spatial\GeographyMultiPolygon.cs + + + Microsoft\Spatial\GeographyMultiSurface.cs + + + Microsoft\Spatial\GeographyPoint.cs + + + Microsoft\Spatial\GeographyPolygon.cs + + + Microsoft\Spatial\Geography.cs + + + Microsoft\Spatial\GeographyCollection.cs + + + Microsoft\Spatial\GeographySurface.cs + + + Microsoft\Spatial\GeometryPipeline.cs + + + Microsoft\Spatial\GeometryCollection.cs + + + Microsoft\Spatial\GeometryCurve.cs + + + Microsoft\Spatial\GeometryFactory.cs + + + Microsoft\Spatial\GeometryFactoryOfT.cs + + + Microsoft\Spatial\GeometryLineString.cs + + + Microsoft\Spatial\GeometryMultiCurve.cs + + + Microsoft\Spatial\GeometryMultiLineString.cs + + + Microsoft\Spatial\GeometryMultiPoint.cs + + + Microsoft\Spatial\GeometryMultiPolygon.cs + + + Microsoft\Spatial\GeometryMultiSurface.cs + + + Microsoft\Spatial\GeometryPoint.cs + + + Microsoft\Spatial\GeometryPolygon.cs + + + Microsoft\Spatial\GeometrySurface.cs + + + Microsoft\Spatial\Geometry.cs + + + Microsoft\Spatial\IGeographyProvider.cs + + + Microsoft\Spatial\IGeometryProvider.cs + + + Microsoft\Spatial\IShapeProvider.cs + + + Microsoft\Spatial\ISpatial.cs + + + Microsoft\Spatial\GeoJsonObjectFormatter.cs + + + Microsoft\Spatial\ParseErrorException.cs + + + Microsoft\Spatial\SpatialBuilder.cs + + + Microsoft\Spatial\SpatialFactory.cs + + + Microsoft\Spatial\SpatialFormatter.cs + + + Microsoft\Spatial\SpatialReader.cs + + + Microsoft\Spatial\SpatialType.cs + + + Microsoft\Spatial\SpatialTypeExtensions.cs + + + GlobalSuppressions.cs + + + Microsoft\Spatial\CoordinateSystem.cs + + + Microsoft\Spatial\ActionOnDispose.cs + + + Microsoft\Spatial\GmlFormatter.cs + + + Microsoft\Spatial\WellKnownTextSqlFormatter.cs + + + Microsoft\Spatial\GeographyPosition.cs + + + Microsoft\Spatial\GeometryPosition.cs + + + Microsoft\Spatial\SpatialValidator.cs + + + Microsoft\Spatial\SpatialPipeline.cs + + + Microsoft\Spatial\GeographyOperationsExtensions.cs + + + Microsoft\Spatial\GeometryOperationsExtensions.cs + + + Microsoft\Spatial\SpatialImplementation.cs + + + Microsoft\Spatial\SpatialOperations.cs + + + Microsoft\Spatial\OrcasExtensions.cs + + + Microsoft\Spatial\DrawBoth.cs + + + Microsoft\Spatial\WellKnownTextSqlFormatterImplementation.cs + + + Microsoft\Spatial\GmlWriter.cs + + + Microsoft\Spatial\GmlFormatterImplementation.cs + + + Microsoft\Spatial\WellKnownTextLexer.cs + + + Microsoft\Spatial\WellKnownTextTokenType.cs + + + Microsoft\Spatial\DataServicesSpatialImplementation.cs + + + Microsoft\Spatial\GeographyBuilderImplementation.cs + + + Microsoft\Spatial\GeometryBuilderImplementation.cs + + + Microsoft\Spatial\GeographyLineStringImplementation.cs + + + Microsoft\Spatial\GeographyMultiLineStringImplementation.cs + + + Microsoft\Spatial\GeographyMultiPointImplementation.cs + + + Microsoft\Spatial\GeographyMultiPolygonImplementation.cs + + + Microsoft\Spatial\GeographyPointImplementation.cs + + + Microsoft\Spatial\GeographyPolygonImplementation.cs + + + Microsoft\Spatial\GeographyCollectionImplementation.cs + + + Microsoft\Spatial\GeographyHelperMethods.cs + + + Microsoft\Spatial\GeographyFullGlobeImplementation.cs + + + Microsoft\Spatial\GeometryLineStringImplementation.cs + + + Microsoft\Spatial\GeometryMultiLineStringImplementation.cs + + + Microsoft\Spatial\GeometryMultiPointImplementation.cs + + + Microsoft\Spatial\GeometryMultiPolygonImplementation.cs + + + Microsoft\Spatial\GeometryPointImplementation.cs + + + Microsoft\Spatial\GeometryPolygonImplementation.cs + + + Microsoft\Spatial\GeometryCollectionImplementation.cs + + + Microsoft\Spatial\DebugUtils.cs + + + Microsoft\Spatial\GeometryHelperMethods.cs + + + Microsoft\Spatial\GmlReader.cs + + + Microsoft\Spatial\GmlConstants.cs + + + Microsoft\Spatial\WellKnownTextConstants.cs + + + Microsoft\Spatial\SpatialTreeBuilder.cs + + + Microsoft\Spatial\TypeWashedPipeline.cs + + + Microsoft\Spatial\TypeWashedToGeometryPipeline.cs + + + Microsoft\Spatial\SpatialValidatorImplementation.cs + + + Microsoft\Spatial\ExtensionMethods.cs + + + Microsoft\Spatial\LexerToken.cs + + + Microsoft\Spatial\TextLexerBase.cs + + + Microsoft\Spatial\WellKnownTextSqlReader.cs + + + Microsoft\Spatial\WellKnownTextSqlWriter.cs + + + Microsoft\Spatial\GeoJsonMember.cs + + + Microsoft\Spatial\GeoJsonConstants.cs + + + Microsoft\Spatial\ForwardingSegment.cs + + + Microsoft\Spatial\Util.cs + + + Microsoft\Spatial\XmlConstants.cs + + + + + + + + + true + + + true + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/Microsoft.Spatial.NetStandard.csproj b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/Microsoft.Spatial.NetStandard.csproj new file mode 100644 index 0000000..effdb34 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/Microsoft.Spatial.NetStandard.csproj @@ -0,0 +1,185 @@ + + + + Microsoft.Spatial + Library + v5.0 + .NETPortable + + + true + true + false + Microsoft.Spatial + {48142D27-C862-4F4F-A781-1A9B72F6CDFA} + $(AssemblyName).xml + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + 14.0 + 14.0 + $(DefineConstants);SPATIAL;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + true + + + + + + + Microsoft.Spatial + true + true + internal + true + Microsoft.Spatial.TextRes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + + + TextTemplatingFileGenerator + Microsoft.Spatial.cs + + + True + True + Microsoft.Spatial.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.Spatial.cs + + + True + True + Parameterized.Microsoft.Spatial.tt + true + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/project.json b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/project.json new file mode 100644 index 0000000..4443cc9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NetStandard/project.json @@ -0,0 +1,9 @@ +{ + "supports": {}, + "dependencies": { + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Net35.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Net35.Release.nuspec new file mode 100644 index 0000000..0f1ca4c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Net35.Release.nuspec @@ -0,0 +1,26 @@ + + + + Microsoft.Spatial + Microsoft.Spatial.Net35 + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionNuGetSemantic$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Contains classes and methods that facilitate geography and geometry spatial operations. + Contains classes and methods that facilitate geography and geometry spatial operations. Targets .NET Portable Lib with support for .NET 3.5. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Nightly.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Nightly.Release.nuspec new file mode 100644 index 0000000..aa5a427 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Nightly.Release.nuspec @@ -0,0 +1,29 @@ + + + + Microsoft.Spatial + Microsoft.Spatial + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionFullSemantic$-Nightly$NightlyBuildVersion$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Contains classes and methods that facilitate geography and geometry spatial operations. + Contains classes and methods that facilitate geography and geometry spatial operations. Targets .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Release.nuspec b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Release.nuspec new file mode 100644 index 0000000..5f3df76 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.NuGet/Microsoft.Spatial.Release.nuspec @@ -0,0 +1,30 @@ + + + + Microsoft.Spatial + Microsoft.Spatial + wcf data services odata odatalib edmlib spatial ado.net ef entity framework open protocol wcfds wcfdataservices dataservices + $VersionNuGetSemantic$ + Microsoft + http://go.microsoft.com/fwlink/?linkid=833178 + http://odata.github.io/ + http://static.tumblr.com/hgchgxz/9ualgdf98/icon.png + true + Contains classes and methods that facilitate geography and geometry spatial operations. + Contains classes and methods that facilitate geography and geometry spatial operations. Targets .NET Platform Standard 1.1. +OData .NET library is open source at http://github.com/OData/odata.net. Documentation for the library can be found at https://odata.github.io/odata.net. + http://odata.github.io/odata.net/v7/#ODL-$VersionFullSemantic$ + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.props b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.props new file mode 100644 index 0000000..fb15e51 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Build.props @@ -0,0 +1,11 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + $(MSBuildThisFileDirectory)Spatial.StyleCop + + + + + diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/CompositeKey.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/CompositeKey.cs new file mode 100644 index 0000000..f638e99 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/CompositeKey.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// A key consisting of multiple fields + /// + /// The type of the first field. + /// The type of the second field. + internal class CompositeKey : IEquatable> + { + /// + /// The first field + /// + private readonly T1 first; + + /// + /// The second field + /// + private readonly T2 second; + + /// + /// Initializes a new instance of the class. + /// + /// The first. + /// The second. + public CompositeKey(T1 first, T2 second) + { + this.first = first; + this.second = second; + } + + /// + /// Implements the operator ==. + /// + /// The left. + /// The right. + /// + /// The result of the operator. + /// + public static bool operator ==(CompositeKey left, CompositeKey right) + { + return Equals(left, right); + } + + /// + /// Implements the operator !=. + /// + /// The left. + /// The right. + /// + /// The result of the operator. + /// + public static bool operator !=(CompositeKey left, CompositeKey right) + { + return !Equals(left, right); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(CompositeKey other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Equals(other.first, this.first) && Equals(other.second, this.second); + } + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + /// The parameter is null. + public override bool Equals(object obj) + { + return Equals(obj as CompositeKey); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + unchecked + { + return (this.first.GetHashCode() * 397) ^ this.second.GetHashCode(); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/CoordinateSystem.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/CoordinateSystem.cs new file mode 100644 index 0000000..fd9700f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/CoordinateSystem.cs @@ -0,0 +1,220 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + + /// + /// Coordinate System Reference + /// + public class CoordinateSystem + { + /// + /// Default Geometry Reference + /// + public static readonly CoordinateSystem DefaultGeometry = new CoordinateSystem(0, "Unitless Plane", Topology.Geometry); + + /// + /// Default Geography Reference (SRID 4326, WGS84) + /// + public static readonly CoordinateSystem DefaultGeography = new CoordinateSystem(4326, "WGS84", Topology.Geography); + + /// + /// List of registered references + /// + private static readonly Dictionary, CoordinateSystem> References; + + /// + /// A lock object for the References static dict + /// + private static readonly object referencesLock = new object(); + + /// + /// The shape of the space that this coordinate system measures. + /// + private Topology topology; + + /// Initializes a static instance of the class. + [SuppressMessage("Microsoft.Performance", "CA1810", Justification = "Static Constructor required")] + static CoordinateSystem() + { + References = new Dictionary, CoordinateSystem>(EqualityComparer>.Default); + AddRef(DefaultGeometry); + AddRef(DefaultGeography); + } + + /// Initializes a new instance of the class. + /// The coordinate system ID, according to the EPSG + /// The Name of the system + /// The topology of this coordinate system + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "epsg", Justification = "This is not hungarian notation, but the widley accepted abreviation")] + internal CoordinateSystem(int epsgId, string name, Topology topology) + { + this.topology = topology; + this.EpsgId = epsgId; + this.Name = name; + } + + /// + /// The shapes of the spaces measured by coordinate systems. + /// + internal enum Topology + { + /// + /// Ellipsoidal coordinates + /// + Geography = 0, + + /// + /// Planar coordinates + /// + Geometry + } + + /// Gets the coordinate system ID according to the EPSG, or NULL if this is not an EPSG coordinate system. + /// The coordinate system ID according to the EPSG. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Epsg", Justification = "This is not hungarian notation, but the widley accepted abreviation")] + public int? EpsgId { get; internal set; } + + /// Gets the coordinate system Id, no matter what scheme is used. + /// The coordinate system Id. + public string Id + { + get { return EpsgId.Value.ToString(CultureInfo.InvariantCulture); } + } + + /// Gets the Name of the Reference. + /// The Name of the Reference. + public string Name { get; internal set; } + + /// Gets or creates a Geography coordinate system with the ID, or the default if null is given. + /// The coordinate system. + /// The coordinate system id, according to the EPSG. Null indicates the default should be returned. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "epsg", Justification = "This is not hungarian notation, but the widley accepted abreviation")] + public static CoordinateSystem Geography(int? epsgId) + { + return epsgId.HasValue ? GetOrCreate(epsgId.Value, Topology.Geography) : DefaultGeography; + } + + /// Gets or creates a Geometry coordinate system with the ID, or the default if null is given. + /// The coordinate system. + /// The coordinate system id, according to the EPSG. Null indicates the default should be returned. + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "epsg", Justification = "This is not hungarian notation, but the widley accepted abreviation")] + public static CoordinateSystem Geometry(int? epsgId) + { + return epsgId.HasValue ? GetOrCreate(epsgId.Value, Topology.Geometry) : DefaultGeometry; + } + + /// Displays the coordinate system for debugging. + /// The coordinate system, for debugging. + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}CoordinateSystem(EpsgId={1})", this.topology, this.EpsgId); + } + + /// Displays a string that can be used with extended WKT. + /// String representation in the form of SRID=#; + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wkt", Justification = "This is not hungarian notation, but the widley accepted abreviation")] + public string ToWktId() + { + return WellKnownTextConstants.WktSrid + WellKnownTextConstants.WktEquals + this.EpsgId + WellKnownTextConstants.WktSemiColon; + } + + /// Indicates the Equals overload. + /// True if equal. + /// The other CoordinateSystem. + public override bool Equals(object obj) + { + return this.Equals(obj as CoordinateSystem); + } + + /// Indicates the Equals overload. + /// True if equal. + /// The other CoordinateSystem. + public bool Equals(CoordinateSystem other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Equals(other.topology, this.topology) && other.EpsgId.Equals(this.EpsgId); + } + + /// Returns a hash code for this instance. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + public override int GetHashCode() + { + unchecked + { + return (this.topology.GetHashCode() * 397) ^ (this.EpsgId.HasValue ? this.EpsgId.Value : 0); + } + } + + /// + /// For tests only. Identifies whether the coordinate system is of the designated topology. + /// + /// The expected topology. + /// True if this coordinate system is of the expected topology. + internal bool TopologyIs(Topology expected) + { + return this.topology == expected; + } + + /// + /// Get or create a CoordinateSystem with ID + /// + /// The SRID + /// The topology. + /// + /// A CoordinateSystem object + /// + private static CoordinateSystem GetOrCreate(int epsgId, Topology topology) + { + CoordinateSystem r; + lock (referencesLock) + { + if (References.TryGetValue(KeyFor(epsgId, topology), out r)) + { + return r; + } + + r = new CoordinateSystem(epsgId, "ID " + epsgId, topology); + AddRef(r); + } + + return r; + } + + /// + /// Remember this coordinate system in the references dictionary. + /// + /// The coords. + private static void AddRef(CoordinateSystem coords) + { + References.Add(KeyFor(coords.EpsgId.Value, coords.topology), coords); + } + + /// + /// Gets the key for a coordinate system + /// + /// ID + /// topology + /// The key to use with the references dict. + private static CompositeKey KeyFor(int epsgId, Topology topology) + { + return new CompositeKey(epsgId, topology); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/DataServicesSpatialImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/DataServicesSpatialImplementation.cs new file mode 100644 index 0000000..c6728d4 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/DataServicesSpatialImplementation.cs @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Class responsible for knowing how to create the Geography and Geometry builders for + /// the data services implemenation of Spatial types + /// + internal class DataServicesSpatialImplementation : SpatialImplementation + { + /// + /// Property used to register Spatial operations implementation. + /// + public override SpatialOperations Operations + { + get; + set; + } + + /// + /// Creates a SpatialBuilder for this implemenation + /// + /// + /// The SpatialBuilder created. + /// + public override SpatialBuilder CreateBuilder() + { + var geography = new GeographyBuilderImplementation(this); + var geometry = new GeometryBuilderImplementation(this); + var input = new ForwardingSegment(geography, geometry); + return new SpatialBuilder(input, input, geography, geometry); + } + + /// + /// Creates a GmlFormatter for this implementation + /// + /// The GmlFormatter created. + public override GmlFormatter CreateGmlFormatter() + { + return new GmlFormatterImplementation(this); + } + + /// + /// Creates a GeoJsonObjectFormatter for this implementation + /// + /// The GeoJsonObjectFormatter created. + public override GeoJsonObjectFormatter CreateGeoJsonObjectFormatter() + { + return new GeoJsonObjectFormatterImplementation(this); + } + + /// + /// Creates a WellKnownTextSqlFormatter for this implementation + /// + /// The WellKnownTextSqlFormatter created. + public override WellKnownTextSqlFormatter CreateWellKnownTextSqlFormatter() + { + return new WellKnownTextSqlFormatterImplementation(this); + } + + /// + /// Creates a WellKnownTextSqlFormatter for this implementation + /// + /// Controls the writing and reading of the Z and M dimension + /// + /// The WellKnownTextSqlFormatter created. + /// + public override WellKnownTextSqlFormatter CreateWellKnownTextSqlFormatter(bool allowOnlyTwoDimensions) + { + return new WellKnownTextSqlFormatterImplementation(this, allowOnlyTwoDimensions); + } + + /// + /// Creates a SpatialValidator for this implementation + /// + /// The SpatialValidator created. + public override SpatialPipeline CreateValidator() + { + return new ForwardingSegment(new SpatialValidatorImplementation()); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/DebugUtils.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/DebugUtils.cs new file mode 100644 index 0000000..e697295 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/DebugUtils.cs @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Dummy class for code that is shared with ODataLib. + /// The ODataLib version of this class has an implementation, but this version is just provided + /// so that we don't have to conditionally compile all references to it in the shared code. + /// Since it is debug-only anyway, there is no harm in leaving this no-op version so that the shared code is cleaner. + /// + internal static class DebugUtils + { + /// + /// Dummy method to allow shared code to compile. + /// + [System.Diagnostics.Conditional("DEBUG")] + internal static void CheckNoExternalCallers() + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/DrawBoth.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/DrawBoth.cs new file mode 100644 index 0000000..2b142e8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/DrawBoth.cs @@ -0,0 +1,307 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Base class to create a unified set of handlers for Geometry and Geography + /// + internal abstract class DrawBoth + { + /// + /// Gets the draw geography. + /// + public virtual GeographyPipeline GeographyPipeline + { + get { return new DrawGeographyInput(this); } + } + + /// + /// Gets the draw geometry. + /// + public virtual GeometryPipeline GeometryPipeline + { + get { return new DrawGeometryInput(this); } + } + + /// + /// Performs an implicit conversion from to . + /// + /// The instance to convert. + /// + /// The result of the conversion. + /// + public static implicit operator SpatialPipeline(DrawBoth both) + { + if (both != null) + { + return new SpatialPipeline(both.GeographyPipeline, both.GeometryPipeline); + } + + return null; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// The position to be passed down the pipeline + protected virtual GeographyPosition OnLineTo(GeographyPosition position) + { + return position; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// The position to be passed down the pipeline + protected virtual GeometryPosition OnLineTo(GeometryPosition position) + { + return position; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected virtual GeographyPosition OnBeginFigure(GeographyPosition position) + { + return position; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected virtual GeometryPosition OnBeginFigure(GeometryPosition position) + { + return position; + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// The type to be passed down the pipeline + protected virtual SpatialType OnBeginGeography(SpatialType type) + { + return type; + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// The type to be passed down the pipeline + protected virtual SpatialType OnBeginGeometry(SpatialType type) + { + return type; + } + + /// + /// Ends the current figure + /// + protected virtual void OnEndFigure() + { + } + + /// + /// Ends the current spatial object + /// + protected virtual void OnEndGeography() + { + } + + /// + /// Ends the current spatial object + /// + protected virtual void OnEndGeometry() + { + } + + /// + /// Setup the pipeline for reuse + /// + protected virtual void OnReset() + { + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + /// the coordinate system to be passed down the pipeline + protected virtual CoordinateSystem OnSetCoordinateSystem(CoordinateSystem coordinateSystem) + { + return coordinateSystem; + } + + /// + /// This class is responsible for taking the calls to DrawGeography and delegating them to the unified + /// handlers + /// + private class DrawGeographyInput : GeographyPipeline + { + /// + /// the DrawBoth instance that should be delegated to + /// + private readonly DrawBoth both; + + /// + /// Initializes a new instance of the class. + /// + /// The both. + public DrawGeographyInput(DrawBoth both) + { + this.both = both; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + public override void LineTo(GeographyPosition position) + { + both.OnLineTo(position); + } + + /// + /// Begin drawing a figure + /// + /// Next position + public override void BeginFigure(GeographyPosition position) + { + both.OnBeginFigure(position); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + public override void BeginGeography(SpatialType type) + { + both.OnBeginGeography(type); + } + + /// + /// Ends the current figure + /// + public override void EndFigure() + { + both.OnEndFigure(); + } + + /// + /// Ends the current spatial object + /// + public override void EndGeography() + { + both.OnEndGeography(); + } + + /// + /// Setup the pipeline for reuse + /// + public override void Reset() + { + both.OnReset(); + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + both.OnSetCoordinateSystem(coordinateSystem); + } + } + + /// + /// This class is responsible for taking the calls to DrawGeometry and delegating them to the unified + /// handlers + /// + private class DrawGeometryInput : GeometryPipeline + { + /// + /// the DrawBoth instance that should be delegated to + /// + private readonly DrawBoth both; + + /// + /// Initializes a new instance of the class. + /// + /// The both. + public DrawGeometryInput(DrawBoth both) + { + this.both = both; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + public override void LineTo(GeometryPosition position) + { + both.OnLineTo(position); + } + + /// + /// Begin drawing a figure + /// + /// Next position + public override void BeginFigure(GeometryPosition position) + { + both.OnBeginFigure(position); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + public override void BeginGeometry(SpatialType type) + { + both.OnBeginGeometry(type); + } + + /// + /// Ends the current figure + /// + public override void EndFigure() + { + both.OnEndFigure(); + } + + /// + /// Ends the current spatial object + /// + public override void EndGeometry() + { + both.OnEndGeometry(); + } + + /// + /// Setup the pipeline for reuse + /// + public override void Reset() + { + both.OnReset(); + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + both.OnSetCoordinateSystem(coordinateSystem); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/ExtensionMethods.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/ExtensionMethods.cs new file mode 100644 index 0000000..63ab5ad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/ExtensionMethods.cs @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Globalization; + using System.IO; + + /// + /// Extension methods for TextWriter + /// + internal static class ExtensionMethods + { + /// + /// Write a double to a TextWriter ensuring that the value will be roundtrippable thorugh double.parse + /// + /// the writer + /// the double value to be written + public static void WriteRoundtrippable(this TextWriter writer, double d) + { + writer.Write(d.ToString("R", CultureInfo.InvariantCulture)); + } + + /// + /// If the arg is non-null, evaluate the op. Otherwise, propogate the null. + /// + /// The type of the arg. + /// The type of the result. + /// The arg. + /// The op. + /// op(arg) if arg is non-null; null if arg is null. + internal static TResult? IfValidReturningNullable(this TArg arg, Func op) + where TArg : class + where TResult : struct + { + return arg == null ? (TResult?)null : op(arg); + } + + /// + /// If the arg is non-null, evaluate the op. Otherwise, propogate the null. + /// + /// The type of the arg. + /// The type of the result. + /// The arg. + /// The op. + /// op(arg) if arg is non-null; null if arg is null. + internal static TResult IfValid(this TArg arg, Func op) + where TArg : class + where TResult : class + { + return arg == null ? default(TResult) : op(arg); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/FormatterExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/FormatterExtensions.cs new file mode 100644 index 0000000..23e6123 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/FormatterExtensions.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + + /// Represents the extensions to formatters. + public static class FormatterExtensions + { + /// Writes the specified formatter. + /// A string value of the formatted object. + /// The formatter. + /// The spatial object. + public static String Write(this SpatialFormatter formatter, ISpatial spatial) + { + Util.CheckArgumentNull(formatter, "formatter"); + + StringBuilder builder = new StringBuilder(); + using (TextWriter writer = new StringWriter(builder, CultureInfo.InvariantCulture)) + { + formatter.Write(spatial, writer); + } + + return builder.ToString(); + } + + /// Writes the specified formatter. + /// A string value of the formatted object. + /// The formatter. + /// The spatial object. + public static String Write(this SpatialFormatter formatter, ISpatial spatial) + { + Util.CheckArgumentNull(formatter, "formatter"); + + StringBuilder builder = new StringBuilder(); + XmlWriterSettings settings = new XmlWriterSettings() { OmitXmlDeclaration = true }; + using (XmlWriter writer = XmlWriter.Create(builder, settings)) + { + formatter.Write(spatial, writer); + } + + return builder.ToString(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/ForwardingSegment.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/ForwardingSegment.cs new file mode 100644 index 0000000..84e5d0f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/ForwardingSegment.cs @@ -0,0 +1,564 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + + /// + /// This is a forwarding transform pipe segment + /// + internal class ForwardingSegment : SpatialPipeline + { + /// + /// The singleton NoOp implementation of the DrawGeography + /// + internal static readonly SpatialPipeline SpatialPipelineNoOp = new SpatialPipeline(new NoOpGeographyPipeline(), new NoOpGeometryPipeline()); + + /// + /// The current drawspatial that will be called and whose results will be forwarded to the + /// next segment + /// + private readonly SpatialPipeline current; + + /// + /// The SpatialPipeline to forward the calls to + /// + private SpatialPipeline next = SpatialPipelineNoOp; + + /// + /// the cached GeographyForwarder for this instance + /// + private GeographyForwarder geographyForwarder; + + /// + /// the cached GeometryForwarder for this instance + /// + private GeometryForwarder geometryForwarder; + + /// + /// Constructs a new SpatialPipeline segment + /// + /// The DrawSpatial to draw to before calling next. + public ForwardingSegment(SpatialPipeline current) + { + Debug.Assert(current.GeographyPipeline == null || !(current.GeographyPipeline is GeographyForwarder), "the current should not already be wrapped."); + Debug.Assert(current.GeometryPipeline == null || !(current.GeometryPipeline is GeometryForwarder), "the current should not already be wrapped."); + this.current = current; + } + + /// + /// Initializes a new instance of the class. + /// + /// The current geography. + /// The current geometry. + public ForwardingSegment(GeographyPipeline currentGeography, GeometryPipeline currentGeometry) + : this(new SpatialPipeline(currentGeography, currentGeometry)) + { + } + + /// + /// Gets the geography. + /// + public override GeographyPipeline GeographyPipeline + { + get { return this.geographyForwarder ?? (this.geographyForwarder = new GeographyForwarder(this)); } + } + + /// + /// Gets the geometry. + /// + public override GeometryPipeline GeometryPipeline + { + get { return this.geometryForwarder ?? (this.geometryForwarder = new GeometryForwarder(this)); } + } + + /// + /// The next geography sink in the pipeline + /// + public GeographyPipeline NextDrawGeography + { + get { return this.next; } + } + + /// + /// The next geometry sink in the pipeline + /// + public GeometryPipeline NextDrawGeometry + { + get { return this.next; } + } + + /// + /// Add the next pipeline + /// + /// the next pipleine + /// The last pipesegment in the chain, usually the one just created + public override SpatialPipeline ChainTo(SpatialPipeline destination) + { + Util.CheckArgumentNull(destination, "destination"); + +#if DEBUG + // must be null, a user implemented pipeline, lastly if it is ours, it better be wrapped in a forwarder + string[] spatialNamespaces = new string[] { "Microsoft.Spatial", "Microsoft.Spatial" }; + Debug.Assert(destination.GeographyPipeline == null || !spatialNamespaces.Any(s => s == destination.GeographyPipeline.GetType().Namespace) || destination.GeographyPipeline is GeographyForwarder, "the destination must be wrapped with the same type of the exception handling/Reset won't work correctly."); + Debug.Assert(destination.GeometryPipeline == null || !spatialNamespaces.Any(s => s == destination.GeometryPipeline.GetType().Namespace) || destination.GeometryPipeline is GeometryForwarder, "the destination must be wrapped with the same type of the exception handling/Reset won't work correctly."); +#endif + this.next = destination; + destination.StartingLink = this.StartingLink; + return destination; + } + + /// + /// Run one action on a pipeline + /// + /// what to do at this stage of the pipeline + /// The handler reset. + /// what the rest of the pipeline should do + /// The delegation reset. + private static void DoAction(Action handler, Action handlerReset, Action delegation, Action delegationReset) + { + try + { + handler(); + } + catch (Exception e) + { + if (Util.IsCatchableExceptionType(e)) + { + handlerReset(); + delegationReset(); + } + + throw; + } + + try + { + delegation(); + } + catch (Exception e) + { + if (Util.IsCatchableExceptionType(e)) + { + handlerReset(); + } + + throw; + } + } + + /// + /// Run one action on a pipeline + /// + /// The type taken and returned by the transform style methods. + /// what to do at this stage of the pipeline + /// The handler reset. + /// what the rest of the pipeline should do + /// The delegation reset. + /// The argument to pass to both this node and the rest of the pipeline + private static void DoAction(Action handler, Action handlerReset, Action delegation, Action delegationReset, T argument) + { + try + { + handler(argument); + } + catch (Exception e) + { + if (Util.IsCatchableExceptionType(e)) + { + handlerReset(); + delegationReset(); + } + + throw; + } + + try + { + delegation(argument); + } + catch (Exception e) + { + if (Util.IsCatchableExceptionType(e)) + { + handlerReset(); + } + + throw; + } + } + + /// + /// The forwarding implementation of DrawGeography + /// + internal class GeographyForwarder : GeographyPipeline + { + /// + /// The ForwardingSegment instance that this pipe is + /// associated with + /// + private readonly ForwardingSegment segment; + + /// + /// Initializes a new instance of the class. + /// + /// The segment. + public GeographyForwarder(ForwardingSegment segment) + { + this.segment = segment; + } + + /// + /// Gets the current DrawGeography from the associated ForwardingSegment instance + /// + private GeographyPipeline Current + { + get { return this.segment.current; } + } + + /// + /// Gets the next GeographyPipeline from the associated ForwardingSegment instance + /// + private GeographyPipeline Next + { + get { return this.segment.next; } + } + + /// + /// Set the system reference to be used by this run of the pipeline + /// + /// the coordinate reference system + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + DoAction(val => Current.SetCoordinateSystem(val), val => Next.SetCoordinateSystem(val), coordinateSystem); + } + + /// + /// start processing Geography data + /// + /// the sort of Geography data being processed + public override void BeginGeography(SpatialType type) + { + DoAction(val => Current.BeginGeography(val), val => Next.BeginGeography(val), type); + } + + /// + /// finish processing Geography data + /// + public override void EndGeography() + { + DoAction(Current.EndGeography, Next.EndGeography); + } + + /// + /// Begin drawing a Geography figure + /// + /// Next position + public override void BeginFigure(GeographyPosition position) + { + Util.CheckArgumentNull(position, "position"); + DoAction( + val => Current.BeginFigure(val), + val => Next.BeginFigure(val), + position); + } + + /// + /// Finish drawing a Geography figure + /// + public override void EndFigure() + { + DoAction(Current.EndFigure, Next.EndFigure); + } + + /// + /// Continue drawing a Geography figure + /// + /// Next position + public override void LineTo(GeographyPosition position) + { + Util.CheckArgumentNull(position, "position"); + DoAction( + val => Current.LineTo(val), + val => Next.LineTo(val), + position); + } + + /// + /// Reset the piprline + /// + public override void Reset() + { + DoAction(Current.Reset, Next.Reset); + } + + /// + /// Run one action on a Geography pipeline + /// + /// The type taken and returned by the transform style methods. + /// what to do at this stage of the pipeline + /// what the rest of the pipeline should do + /// The argument to pass to both this node and the rest of the pipeline + private void DoAction(Action handler, Action delegation, T argument) + { + ForwardingSegment.DoAction(handler, Current.Reset, delegation, Next.Reset, argument); + } + + /// + /// Run one action on a Geography pipeline + /// + /// what to do at this stage of the pipeline + /// what the rest of the pipeline should do + private void DoAction(Action handler, Action delegation) + { + ForwardingSegment.DoAction(handler, Current.Reset, delegation, Next.Reset); + } + } + + /// + /// The forwarding implementation of DrawGeography + /// + internal class GeometryForwarder : GeometryPipeline + { + /// + /// The ForwardingSegment instance that this pipe is + /// associated with + /// + private readonly ForwardingSegment segment; + + /// + /// Initializes a new instance of the class. + /// + /// The segment. + public GeometryForwarder(ForwardingSegment segment) + { + this.segment = segment; + } + + /// + /// Gets the current DrawGeometry from the associated ForwardingSegment instance + /// + private GeometryPipeline Current + { + get { return this.segment.current; } + } + + /// + /// Gets the next GeometryPipeline from the associated ForwardingSegment instance + /// + private GeometryPipeline Next + { + get { return this.segment.next; } + } + + /// + /// Set the system reference to be used by this run of the pipeline + /// + /// the coordinate reference system + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + DoAction(val => Current.SetCoordinateSystem(val), val => Next.SetCoordinateSystem(val), coordinateSystem); + } + + /// + /// start processing Geometry data + /// + /// the sort of Geometry data being processed + public override void BeginGeometry(SpatialType type) + { + DoAction(val => Current.BeginGeometry(val), val => Next.BeginGeometry(val), type); + } + + /// + /// finish processing Geometry data + /// + public override void EndGeometry() + { + DoAction(Current.EndGeometry, Next.EndGeometry); + } + + /// + /// Begin drawing a Geometry figure + /// + /// Next position + public override void BeginFigure(GeometryPosition position) + { + Util.CheckArgumentNull(position, "position"); + DoAction(val => Current.BeginFigure(val), val => Next.BeginFigure(val), position); + } + + /// + /// Finish drawing a Geometry figure + /// + public override void EndFigure() + { + DoAction(Current.EndFigure, Next.EndFigure); + } + + /// + /// Continue drawing a Geometry figure + /// + /// Next position + public override void LineTo(GeometryPosition position) + { + Util.CheckArgumentNull(position, "position"); + DoAction(val => Current.LineTo(val), val => Next.LineTo(val), position); + } + + /// + /// Reset the piprline + /// + public override void Reset() + { + DoAction(Current.Reset, Next.Reset); + } + + /// + /// Run one action on a Geography pipeline + /// + /// The type taken and returned by the transform style methods. + /// what to do at this stage of the pipeline + /// what the rest of the pipeline should do + /// The argument to pass to both this node and the rest of the pipeline + private void DoAction(Action handler, Action delegation, T argument) + { + ForwardingSegment.DoAction(handler, Current.Reset, delegation, Next.Reset, argument); + } + + /// + /// Run one action on a Geography pipeline + /// + /// what to do at this stage of the pipeline + /// what the rest of the pipeline should do + private void DoAction(Action handler, Action delegation) + { + ForwardingSegment.DoAction(handler, Current.Reset, delegation, Next.Reset); + } + } + + /// + /// A noop implementation of DrawGeography + /// + private class NoOpGeographyPipeline : GeographyPipeline + { + /// + /// Draw a point in the specified coordinate + /// + /// Next position + public override void LineTo(GeographyPosition position) + { + } + + /// + /// Begin drawing a figure + /// + /// Next position + public override void BeginFigure(GeographyPosition position) + { + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + public override void BeginGeography(SpatialType type) + { + } + + /// + /// Ends the current figure + /// + public override void EndFigure() + { + } + + /// + /// Ends the current spatial object + /// + public override void EndGeography() + { + } + + /// + /// Setup the pipeline for reuse + /// + public override void Reset() + { + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + } + } + + /// + /// a noop implementation of DrawGeometry + /// + private class NoOpGeometryPipeline : GeometryPipeline + { + /// + /// Draw a point in the specified coordinate + /// + /// Next position + public override void LineTo(GeometryPosition position) + { + } + + /// + /// Begin drawing a figure + /// + /// Next position + public override void BeginFigure(GeometryPosition position) + { + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + public override void BeginGeometry(SpatialType type) + { + } + + /// + /// Ends the current figure + /// + public override void EndFigure() + { + } + + /// + /// Ends the current spatial object + /// + public override void EndGeometry() + { + } + + /// + /// Setup the pipeline for reuse + /// + public override void Reset() + { + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonConstants.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonConstants.cs new file mode 100644 index 0000000..440e39d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonConstants.cs @@ -0,0 +1,90 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Constants for the GeoJSON format + /// See http://geojson.org/geojson-spec.html for full details on GeoJson format. + /// + internal static class GeoJsonConstants + { + /// + /// Name of the type member that identifies the spatial type. + /// + internal const string TypeMemberName = "type"; + + /// + /// Value of the type member for Point values. + /// + internal const string TypeMemberValuePoint = "Point"; + + /// + /// Value of the type member for LineString values. + /// + internal const string TypeMemberValueLineString = "LineString"; + + /// + /// Value of the type member for Polygon values. + /// + internal const string TypeMemberValuePolygon = "Polygon"; + + /// + /// Value of the type member for MultiPoint values. + /// + internal const string TypeMemberValueMultiPoint = "MultiPoint"; + + /// + /// Value of the type member for MultiLineString values. + /// + internal const string TypeMemberValueMultiLineString = "MultiLineString"; + + /// + /// Value of the type member for MultiPolygon values. + /// + internal const string TypeMemberValueMultiPolygon = "MultiPolygon"; + + /// + /// Value of the type member for GeometryCollection values. + /// + internal const string TypeMemberValueGeometryCollection = "GeometryCollection"; + + /// + /// Name of the coordinates member that contains the spatial data. + /// + internal const string CoordinatesMemberName = "coordinates"; + + /// + /// Name of the geometries member that contains the spatial data. + /// + internal const string GeometriesMemberName = "geometries"; + + /// + /// Name of the crs member that contains the coordinate reference system details. + /// + internal const string CrsMemberName = "crs"; + + /// + /// Value of the type member inside of the crs object. + /// + internal const string CrsTypeMemberValue = "name"; + + /// + /// Name of the name member inside of the properties member in the crs object. + /// + internal const string CrsNameMemberName = "name"; + + /// + /// Name of the properties member inside of the crs object. + /// + internal const string CrsPropertiesMemberName = "properties"; + + /// + /// Prefix to use when specifying the coordinate reference system inside the crs object. + /// + internal const string CrsValuePrefix = "EPSG"; + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonMember.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonMember.cs new file mode 100644 index 0000000..bc8667a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonMember.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Defines the members that may be found in a GeoJSON object. + /// + internal enum GeoJsonMember + { + /// + /// "type" member in a GeoJSON object. + /// + Type, + + /// + /// "coordinates" member in GeoJSON object. + /// + Coordinates, + + /// + /// "geometries" member in GeoJSON object. + /// + Geometries, + + /// + /// "crs" member in GeoJSON object. + /// + Crs, + + /// + /// 'properties' member in GeoJSON object + /// + Properties, + + /// + /// 'name' member in GeoJSON object + /// + Name, + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatter.cs new file mode 100644 index 0000000..7bfb7e1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatter.cs @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + + /// Represents a formatter for Json object. + public abstract class GeoJsonObjectFormatter + { + /// Creates the implementation of the formatter. + /// The created implementation. + public static GeoJsonObjectFormatter Create() + { + return SpatialImplementation.CurrentImplementation.CreateGeoJsonObjectFormatter(); + } + + /// Reads from the source. + /// The object that was read. + /// The source json object. + /// The spatial type to read. + public abstract T Read(IDictionary source) where T : class, ISpatial; + + /// Converts spatial value to a Json object. + /// The json object. + /// The spatial value. + public abstract IDictionary Write(ISpatial value); + + /// Creates the writerStream. + /// The writerStream that was created. + /// The actual stream to write Json. + public abstract SpatialPipeline CreateWriter(IGeoJsonWriter writer); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatterImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatterImplementation.cs new file mode 100644 index 0000000..c83cdb5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectFormatterImplementation.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.Generic; + + /// + /// Formatter for Json Object + /// + internal class GeoJsonObjectFormatterImplementation : GeoJsonObjectFormatter + { + /// + /// The implementation that created this instance. + /// + private readonly SpatialImplementation creator; + + /// + /// Spatial builder + /// + private SpatialBuilder builder; + + /// + /// The parse pipeline + /// + private SpatialPipeline parsePipeline; + + /// + /// Constructor + /// + /// The SpatialImplementation that created this instance + public GeoJsonObjectFormatterImplementation(SpatialImplementation creator) + { + this.creator = creator; + } + + /// + /// Read from the source + /// + /// The spatial type to read + /// The source json object + /// The read instance + public override T Read(IDictionary source) + { + this.EnsureParsePipeline(); + if (typeof(Geometry).IsAssignableFrom(typeof(T))) + { + new GeoJsonObjectReader(this.builder).ReadGeometry(source); + return this.builder.ConstructedGeometry as T; + } + + new GeoJsonObjectReader(this.builder).ReadGeography(source); + return this.builder.ConstructedGeography as T; + } + + /// + /// Convert spatial value to a Json Object + /// + /// The spatial value + /// The json object + public override IDictionary Write(ISpatial value) + { + var writer = new GeoJsonObjectWriter(); + value.SendTo(new ForwardingSegment(writer)); + return writer.JsonObject; + } + + /// Creates the writerStream. + /// The writerStream that was created. + /// The actual stream to write Json. + public override SpatialPipeline CreateWriter(IGeoJsonWriter writer) + { + return new ForwardingSegment(new WrappedGeoJsonWriter(writer)); + } + + /// + /// Initialize the pipeline + /// + private void EnsureParsePipeline() + { + if (this.parsePipeline == null) + { + this.builder = this.creator.CreateBuilder(); + this.parsePipeline = this.creator.CreateValidator().ChainTo(this.builder); + } + else + { + this.parsePipeline.GeographyPipeline.Reset(); + this.parsePipeline.GeometryPipeline.Reset(); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectReader.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectReader.cs new file mode 100644 index 0000000..5b485ed --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectReader.cs @@ -0,0 +1,560 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + + /// + /// The spatial reader that can read from a pre parsed GeoJson payload + /// + internal class GeoJsonObjectReader : SpatialReader> + { + /// + /// Initializes a new instance of the class. + /// + /// The pipeline. + internal GeoJsonObjectReader(SpatialPipeline destination) + : base(destination) + { + } + + /// + /// Parses some serialized format that represents a geography value, passing the result down the pipeline. + /// + /// The jsonObject to read from. + protected override void ReadGeographyImplementation(IDictionary input) + { + TypeWashedPipeline pipeline = new TypeWashedToGeographyLongLatPipeline(Destination); + new SendToTypeWashedPipeline(pipeline).SendToPipeline(input, true); + } + + /// + /// Parses some serialized format that represents a geometry value, passing the result down the pipeline. + /// + /// The jsonObject to read from. + protected override void ReadGeometryImplementation(IDictionary input) + { + TypeWashedPipeline pipeline = new TypeWashedToGeometryPipeline(Destination); + new SendToTypeWashedPipeline(pipeline).SendToPipeline(input, true); + } + + /// + /// A common way to call Geography and Geometry pipeline apis from the structured Json + /// + private class SendToTypeWashedPipeline + { + /// + /// Pipeline to use for the output of the translation of the GeoJSON object into pipeline method calls. + /// + private TypeWashedPipeline pipeline; + + /// + /// Initializes a new instance of the class. + /// + /// Spatial pipeline that will receive the pipeline method calls. + internal SendToTypeWashedPipeline(TypeWashedPipeline pipeline) + { + this.pipeline = pipeline; + } + + /// + /// Translates a dictionary of parsed GeoJSON members and values into method calls on the spatial pipeline. + /// + /// Dictionary containing GeoJSON members and values. + /// Coordinate System must be set for this pipeline + internal void SendToPipeline(IDictionary members, bool requireSetCoordinates) + { + SpatialType spatialType = GetSpatialType(members); + + int? epsgId; + if (!TryGetCoordinateSystemId(members, out epsgId)) + { + epsgId = null; + } + + if (requireSetCoordinates || epsgId != null) + { + // When to set coordinate system: + // 1. On outer spatial types, coordinate system must be set (requireSetCoordinates = true) + // if the other spatial type does not declare a CRS, set to null. + // 2. On inner spatial types, set coordinate system ONLY if the CRS is declared on the type + // If the CRS is different from the other one, validator will throw. However, it's still valid GeoJSON. + this.pipeline.SetCoordinateSystem(epsgId); + } + + String contentMemberName; + + if (spatialType == SpatialType.Collection) + { + contentMemberName = GeoJsonConstants.GeometriesMemberName; + } + else + { + contentMemberName = GeoJsonConstants.CoordinatesMemberName; + } + + IEnumerable memberObject = GetMemberValueAsJsonArray(members, contentMemberName); + SendShape(spatialType, memberObject); + } + + /// + /// Iterates over an object array, verifies that each element in the array is another array, and calls a delgate on the contained array. + /// + /// Array to iterate over. + /// Delegate to invoke for each element once it has been validated to be an array. + private static void SendArrayOfArray(IEnumerable array, Action send) + { + foreach (object element in array) + { + IEnumerable arrayElement = ValueAsJsonArray(element); + send(arrayElement); + } + } + + /// + /// Convert an object to a nullable double value. + /// + /// Object to convert. + /// If the specified element was null, returns null, otherwise returns the converted double value. + private static double? ValueAsNullableDouble(object value) + { + return value == null ? null : (double?)ValueAsDouble(value); + } + + /// + /// Convert an object to a non-null double value. + /// + /// Object to convert. + /// Converted double value. + private static double ValueAsDouble(object value) + { + if (value == null) + { + throw new FormatException(Strings.GeoJsonReader_InvalidNullElement); + } + + // At this point we are expecting them to only be numeric, so verify that. + if (value is String || value is IDictionary || value is IEnumerable || value is bool) + { + throw new FormatException(Strings.GeoJsonReader_ExpectedNumeric); + } + + // value is already a numeric value at this point, so can safely convert it using InvariantCulture. + return Convert.ToDouble(value, CultureInfo.InvariantCulture); + } + + /// + /// Values as json array. + /// + /// The value. + /// The value cast as a json array. + private static IEnumerable ValueAsJsonArray(object value) + { + if (value == null) + { + return null; + } + + // need to be sure it isn't a string because string is also enumerable); + if (value is string) + { + throw new FormatException(Strings.GeoJsonReader_ExpectedArray); + } + + if (value is IDictionary || value is IDictionary) + { + // These are typically signatures of json objects though they can be looked at as IEnumerable, + // but that would be a mistake, becuase we wouldn't know how to interpret the KeyValuePair + throw new FormatException(Strings.GeoJsonReader_ExpectedArray); + } + + IEnumerable array = value as IEnumerable; + if (array != null) + { + return array; + } + + throw new FormatException(Strings.GeoJsonReader_ExpectedArray); + } + + /// + /// Values as json object. + /// + /// The value. + /// The value cast as IDictionary<string, object> + private static IDictionary ValueAsJsonObject(object value) + { + if (value == null) + { + return null; + } + + IDictionary castValue = value as IDictionary; + if (castValue != null) + { + return castValue; + } + + throw new FormatException(Strings.JsonReaderExtensions_CannotReadValueAsJsonObject(value)); + } + + /// + /// Values as string. + /// + /// Name of the property. + /// The value. + /// The value cast as a string. + private static string ValueAsString(string propertyName, object value) + { + if (value == null) + { + return null; + } + + string castValue = value as string; + if (castValue != null) + { + return castValue; + } + + throw new FormatException(Strings.JsonReaderExtensions_CannotReadPropertyValueAsString(value, propertyName)); + } + + /// + /// Get the type member value from the specified GeoJSON member dictionary. + /// + /// Dictionary containing the GeoJSON members and their values. + /// SpatialType for the GeoJSON object. + private static SpatialType GetSpatialType(IDictionary geoJsonObject) + { + object typeName; + if (geoJsonObject.TryGetValue(GeoJsonConstants.TypeMemberName, out typeName)) + { + return ReadTypeName(ValueAsString(GeoJsonConstants.TypeMemberName, typeName)); + } + else + { + throw new FormatException(Strings.GeoJsonReader_MissingRequiredMember(GeoJsonConstants.TypeMemberName)); + } + } + + /// + /// Tries to get a coordinate system id from the geo json object's 'crs' property + /// + /// The geo json object. + /// The coordinate system id. + /// True if the object had a coordinate system + private static bool TryGetCoordinateSystemId(IDictionary geoJsonObject, out int? epsgId) + { + object crsValue; + if (!geoJsonObject.TryGetValue(GeoJsonConstants.CrsMemberName, out crsValue)) + { + epsgId = null; + return false; + } + + IDictionary crsMembers = ValueAsJsonObject(crsValue); + epsgId = GetCoordinateSystemIdFromCrs(crsMembers); + return true; + } + + /// + /// Gets the coordinate system ID from a representation of the CRS object + /// + /// The parsed representation of the CRS object. + /// The coordinate system ID + private static int GetCoordinateSystemIdFromCrs(IDictionary crsJsonObject) + { + // get the value of the 'type' property + object typeValue; + if (!crsJsonObject.TryGetValue(GeoJsonConstants.TypeMemberName, out typeValue)) + { + throw new FormatException(Strings.GeoJsonReader_MissingRequiredMember(GeoJsonConstants.TypeMemberName)); + } + + // we previously validated that the value was a string, so this cast is safe + var typeString = ValueAsString(GeoJsonConstants.TypeMemberName, typeValue); + + // validate the type is supported + if (!string.Equals(typeString, GeoJsonConstants.CrsTypeMemberValue, StringComparison.Ordinal)) + { + throw new FormatException(Strings.GeoJsonReader_InvalidCrsType(typeString)); + } + + // get the value of the 'properties' property + object propertiesValue; + if (!crsJsonObject.TryGetValue(GeoJsonConstants.CrsPropertiesMemberName, out propertiesValue)) + { + throw new FormatException(Strings.GeoJsonReader_MissingRequiredMember(GeoJsonConstants.CrsPropertiesMemberName)); + } + + var properties = ValueAsJsonObject(propertiesValue); + + // get the value of the 'name' property + object nameValue; + if (!properties.TryGetValue(GeoJsonConstants.CrsNameMemberName, out nameValue)) + { + throw new FormatException(Strings.GeoJsonReader_MissingRequiredMember(GeoJsonConstants.CrsNameMemberName)); + } + + // we previously validated that the value was a string + var nameString = ValueAsString(GeoJsonConstants.CrsNameMemberName, nameValue); + + // the value of 'name' must be of the form 'EPSG:1234' where 1234 is a valid integer + var offset = GeoJsonConstants.CrsValuePrefix.Length; + int epsgId; + if (nameString == null + || !nameString.StartsWith(GeoJsonConstants.CrsValuePrefix, StringComparison.Ordinal) + || nameString.Length == offset // just EPSG, no : or value + || nameString[offset] != ':' + || !int.TryParse(nameString.Substring(offset + 1), out epsgId)) + { + throw new FormatException(Strings.GeoJsonReader_InvalidCrsName(nameString)); + } + + return epsgId; + } + + /// + /// Get the designated member value from the specified GeoJSON member dictionary. + /// + /// Dictionary containing the GeoJSON members and their values. + /// The member's tag name + /// Member value for the GeoJSON object. + private static IEnumerable GetMemberValueAsJsonArray(IDictionary geoJsonObject, String memberName) + { + object coordinates; + if (geoJsonObject.TryGetValue(memberName, out coordinates)) + { + return ValueAsJsonArray(coordinates); + } + else + { + throw new FormatException(Strings.GeoJsonReader_MissingRequiredMember(memberName)); + } + } + + /// + /// This method assumes a non forward only enumerable + /// + /// The enumerable to check + /// true if there is at least one element + private static bool EnumerableAny(IEnumerable enumerable) + { + IEnumerator enumerator = enumerable.GetEnumerator(); + return enumerator.MoveNext(); + } + + /// + /// Reads GeoJson 'type' value and maps it a valid SpatialType. + /// + /// The GeoJson standard type name + /// SpatialType corresponding to the GeoJson type name. + private static SpatialType ReadTypeName(string typeName) + { + switch (typeName) + { + case GeoJsonConstants.TypeMemberValuePoint: + return SpatialType.Point; + case GeoJsonConstants.TypeMemberValueLineString: + return SpatialType.LineString; + case GeoJsonConstants.TypeMemberValuePolygon: + return SpatialType.Polygon; + case GeoJsonConstants.TypeMemberValueMultiPoint: + return SpatialType.MultiPoint; + case GeoJsonConstants.TypeMemberValueMultiLineString: + return SpatialType.MultiLineString; + case GeoJsonConstants.TypeMemberValueMultiPolygon: + return SpatialType.MultiPolygon; + case GeoJsonConstants.TypeMemberValueGeometryCollection: + return SpatialType.Collection; + default: + throw new FormatException(Strings.GeoJsonReader_InvalidTypeName(typeName)); + } + } + + /// + /// Sends a shape to the spatial pipeline. + /// + /// SpatialType of the shape. + /// Content member for the shape + private void SendShape(SpatialType spatialType, IEnumerable contentMembers) + { + this.pipeline.BeginGeo(spatialType); + SendCoordinates(spatialType, contentMembers); + this.pipeline.EndGeo(); + } + + /// + /// Translates the coordinates member value into method calls on the spatial pipeline. + /// + /// SpatialType of the GeoJSON object. + /// Coordinates value of the GeoJSON object, or inner geometries for collection + private void SendCoordinates(SpatialType spatialType, IEnumerable contentMembers) + { + if (EnumerableAny(contentMembers)) + { + // non-empty shape + switch (spatialType) + { + case SpatialType.Point: + SendPoint(contentMembers); + break; + case SpatialType.LineString: + SendLineString(contentMembers); + break; + case SpatialType.Polygon: + SendPolygon(contentMembers); + break; + case SpatialType.MultiPoint: + SendMultiShape(SpatialType.Point, contentMembers); + break; + case SpatialType.MultiLineString: + SendMultiShape(SpatialType.LineString, contentMembers); + break; + case SpatialType.MultiPolygon: + SendMultiShape(SpatialType.Polygon, contentMembers); + break; + case SpatialType.Collection: + foreach (IDictionary collectionMember in contentMembers) + { + SendToPipeline(collectionMember, false); + } + + break; + default: + Debug.Assert(false, "SendCoordinates has not been implemented for SpatialType " + spatialType); + break; + } + } + } + + /// + /// Translates the coordinates member value of a Point object into method calls on the spatial pipeline. + /// + /// Parsed coordinates array. + private void SendPoint(IEnumerable coordinates) + { + SendPosition(coordinates, true); + this.pipeline.EndFigure(); + } + + /// + /// Translates the coordinates member value of a LineString object into method calls on the spatial pipeline. + /// + /// Parsed coordinates array. + private void SendLineString(IEnumerable coordinates) + { + SendPositionArray(coordinates); + } + + /// + /// Translates the coordinates member value of a Polygon object into method calls on the spatial pipeline. + /// + /// Parsed coordinates array. + private void SendPolygon(IEnumerable coordinates) + { + SendArrayOfArray(coordinates, (positionArray) => this.SendPositionArray(positionArray)); + } + + /// + /// Translates the coordinates member value of a MultiPoint, MultiLineString, or MultiPolygon object into method calls on the spatial pipeline. + /// + /// Type of the shape contained in the Multi shape. + /// Parsed coordinates array. + private void SendMultiShape(SpatialType containedSpatialType, IEnumerable coordinates) + { + Debug.Assert( + containedSpatialType == SpatialType.Point || + containedSpatialType == SpatialType.LineString || + containedSpatialType == SpatialType.Polygon, + "SendMultiShape only expects to write Point, LineString, or Polygon contained shapes."); + + SendArrayOfArray(coordinates, (containedShapeCoordinates) => this.SendShape(containedSpatialType, containedShapeCoordinates)); + } + + /// + /// Translates an array of positions into method calls on the spatial pipeline. + /// + /// List containing the positions. + private void SendPositionArray(IEnumerable positionArray) + { + bool first = true; + + SendArrayOfArray( + positionArray, + (array) => + { + SendPosition(array, first); + if (first) + { + first = false; + } + }); + + this.pipeline.EndFigure(); + } + + /// + /// Translates an individual position into a method call on the spatial pipeline. + /// + /// List containing elements of the position. + /// True if the position is the first one being written to a figure, otherwise false. + private void SendPosition(IEnumerable positionElements, bool first) + { + int count = 0; + double x = 0.0; + double y = 0.0; + double? z = null; + double? m = null; + + foreach (object element in positionElements) + { + count++; + + // If values can be read as integer, JsonReader will return them as that type, so we need to explicitly convert here. + switch (count) + { + case 1: + x = ValueAsDouble(element); + break; + case 2: + y = ValueAsDouble(element); + break; + case 3: + z = ValueAsNullableDouble(element); + break; + case 4: + m = ValueAsNullableDouble(element); + break; + default: + // will be an error below during range checking + break; + } + } + + if (count < 2 || count > 4) + { + throw new FormatException(Strings.GeoJsonReader_InvalidPosition); + } + + if (first) + { + this.pipeline.BeginFigure(x, y, z, m); + } + else + { + this.pipeline.LineTo(x, y, z, m); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectWriter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectWriter.cs new file mode 100644 index 0000000..fcc4b89 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonObjectWriter.cs @@ -0,0 +1,189 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Collections; + + /// + /// Convert Spatial objects into json writer + /// + internal sealed class GeoJsonObjectWriter : GeoJsonWriterBase + { + /// + /// Stack of json objects + /// + private readonly Stack containers = new Stack(); + + /// + /// Buffered key of the current name-value pair + /// + private string currentPropertyName; + + /// + /// Stores the last object fully serialized + /// + private object lastCompletedObject; + + /// + /// Get the top level json object + /// + internal IDictionary JsonObject + { + get + { + return lastCompletedObject as IDictionary; + } + } + + /// + /// Test if the current container is an array + /// + private bool IsArray + { + get { return this.containers.Peek() is IList; } + } + + /// + /// Start a new json object scope + /// + protected override void StartObjectScope() + { + object jsonObject = new Dictionary(StringComparer.Ordinal); + + if (this.containers.Count > 0) + { + this.AddToScope(jsonObject); + } + + // switch into the new container + this.containers.Push(jsonObject); + } + + /// + /// Start a new json array scope + /// + protected override void StartArrayScope() + { + Debug.Assert(this.containers.Count > 0, "Array scope cannot be a top level GeoJson object"); + object jsonObject = new List(); + this.AddToScope(jsonObject); + this.containers.Push(jsonObject); + } + + /// + /// Add a property name to the current json object + /// + /// The name to add + protected override void AddPropertyName(String name) + { + Debug.Assert(this.currentPropertyName == null, "A property name has already been set and not been used"); + this.currentPropertyName = name; + } + + /// + /// Add a value to the current json scope + /// + /// The value to add + protected override void AddValue(String value) + { + this.AddToScope(value); + } + + /// + /// Add a value to the current json scope + /// + /// The value to add + protected override void AddValue(double value) + { + this.AddToScope(value); + } + + /// + /// End the current json array scope + /// + protected override void EndArrayScope() + { + this.containers.Pop(); + } + + /// + /// End the current json object scope + /// + protected override void EndObjectScope() + { + object o = this.containers.Pop(); + + if (this.containers.Count == 0) + { + this.lastCompletedObject = o; + } + } + + /// + /// Add an json object to the current scope + /// + /// The json object + private void AddToScope(object jsonObject) + { + Debug.Assert(this.containers.Count > 0, "No scope has been created"); + + if (this.IsArray) + { + Debug.Assert(this.GetAndClearCurrentPropertyName() == null, "Array member must not have names"); + this.AsList().Add(jsonObject); + } + else + { + string name = this.GetAndClearCurrentPropertyName(); + Debug.Assert(name != null, "Dictionary members must have a name"); + this.AsDictionary().Add(name, jsonObject); + } + } + + /// + /// Return the current property name, and clear the buffer + /// + /// The current property name + /// + /// When inserting to a dictionary, the name-value pair comes across multiple pipeline calls + /// Therefore we need to buffer the name part and wait for the value part. + /// You can get into an incorrect state (caught by asserts) if you add a property name without + /// using it immediately next. + /// + private String GetAndClearCurrentPropertyName() + { + String name = this.currentPropertyName; + this.currentPropertyName = null; + return name; + } + + /// + /// Access the current container as a List + /// + /// The current container as list + private IList AsList() + { + var ret = this.containers.Peek() as IList; + Debug.Assert(ret != null, "Calling AsList() on a scope that is not a list"); + return ret; + } + + /// + /// Access the current container as a Dictionary + /// + /// The current container as dictionary + private IDictionary AsDictionary() + { + var ret = this.containers.Peek() as IDictionary; + Debug.Assert(ret != null, "Calling AsDictionary() on a scope that is not a dictionary"); + return ret; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonWriterBase.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonWriterBase.cs new file mode 100644 index 0000000..7629964 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeoJsonWriterBase.cs @@ -0,0 +1,472 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + + /// + /// Base Writer for GeoJson + /// + internal abstract class GeoJsonWriterBase : DrawBoth + { + /// + /// Stack to track the current type being written. + /// + private readonly Stack stack; + + /// + /// CoordinateSystem for the types being written. + /// + private CoordinateSystem currentCoordinateSystem; + + /// + /// Figure added in current shape + /// + private bool figureDrawn; + + /// + /// Creates a new instance of the GeoJsonWriter. + /// + public GeoJsonWriterBase() + { + this.stack = new Stack(); + } + + /// + /// True if the shape should write start and end object scope, otherwise false. + /// + private bool ShapeHasObjectScope + { + get + { + return this.IsTopLevel || this.stack.Peek() == SpatialType.Collection; + } + } + + /// + /// True if the shape is not a child of another shape. + /// + private bool IsTopLevel + { + get + { + return this.stack.Count == 0; + } + } + + /// + /// True if the shape should write start and end object scope, otherwise false. + /// + private bool FigureHasArrayScope + { + get + { + return this.stack.Peek() != SpatialType.Point; + } + } + + #region DrawBoth + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// + /// The position to be passed down the pipeline + /// + protected override GeographyPosition OnLineTo(GeographyPosition position) + { + // GeoJSON specification is that longitude is first + WriteControlPoint(position.Longitude, position.Latitude, position.Z, position.M); + return position; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// + /// The position to be passed down the pipeline + /// + protected override GeometryPosition OnLineTo(GeometryPosition position) + { + this.WriteControlPoint(position.X, position.Y, position.Z, position.M); + return position; + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// + /// The type to be passed down the pipeline + /// + protected override SpatialType OnBeginGeography(SpatialType type) + { + BeginShape(type, CoordinateSystem.DefaultGeography); + return type; + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// + /// The type to be passed down the pipeline + /// + protected override SpatialType OnBeginGeometry(SpatialType type) + { + BeginShape(type, CoordinateSystem.DefaultGeometry); + return type; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeographyPosition OnBeginFigure(GeographyPosition position) + { + BeginFigure(); + WriteControlPoint(position.Longitude, position.Latitude, position.Z, position.M); + return position; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeometryPosition OnBeginFigure(GeometryPosition position) + { + BeginFigure(); + this.WriteControlPoint(position.X, position.Y, position.Z, position.M); + return position; + } + + /// + /// Ends the current figure + /// + protected override void OnEndFigure() + { + EndFigure(); + } + + /// + /// Ends the current spatial object + /// + protected override void OnEndGeography() + { + EndShape(); + } + + /// + /// Ends the current spatial object + /// + protected override void OnEndGeometry() + { + EndShape(); + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + /// + /// the coordinate system to be passed down the pipeline + /// + protected override CoordinateSystem OnSetCoordinateSystem(CoordinateSystem coordinateSystem) + { + SetCoordinateSystem(coordinateSystem); + return coordinateSystem; + } + + /// + /// Setup the pipeline for reuse + /// + protected override void OnReset() + { + Reset(); + } + + #endregion + + #region Abstract Defs + + /// + /// Add a property name to the current json object + /// + /// The name to add + protected abstract void AddPropertyName(String name); + + /// + /// Add a value to the current json scope + /// + /// The value to add + protected abstract void AddValue(String value); + + /// + /// Add a value to the current json scope + /// + /// The value to add + protected abstract void AddValue(double value); + + /// + /// Start a new json object scope + /// + protected abstract void StartObjectScope(); + + /// + /// Start a new json array scope + /// + protected abstract void StartArrayScope(); + + /// + /// End the current json object scope + /// + protected abstract void EndObjectScope(); + + /// + /// End the current json array scope + /// + protected abstract void EndArrayScope(); + + #endregion + + /// + /// Setup the pipeline for reuse + /// + protected virtual void Reset() + { + this.stack.Clear(); + this.currentCoordinateSystem = default(CoordinateSystem); + } + + #region Private Methods + + /// + /// Gets the GeoJson type name to use when writing the specified type. + /// + /// SpatialType being written. + /// GeoJson type name corresponding to the specified . + private static string GetSpatialTypeName(SpatialType type) + { + switch (type) + { + case SpatialType.Point: + return GeoJsonConstants.TypeMemberValuePoint; + case SpatialType.LineString: + return GeoJsonConstants.TypeMemberValueLineString; + case SpatialType.Polygon: + return GeoJsonConstants.TypeMemberValuePolygon; + case SpatialType.MultiPoint: + return GeoJsonConstants.TypeMemberValueMultiPoint; + case SpatialType.MultiLineString: + return GeoJsonConstants.TypeMemberValueMultiLineString; + case SpatialType.MultiPolygon: + return GeoJsonConstants.TypeMemberValueMultiPolygon; + case SpatialType.Collection: + return GeoJsonConstants.TypeMemberValueGeometryCollection; + default: + Debug.Assert(false, "SpatialType " + type + " is not currently supported in GeoJsonWriter."); + throw new NotImplementedException(); + } + } + + /// + /// Gets the name of the GeoJson member to use when writing the body of the spatial object. + /// + /// SpatialType being written. + /// Name of the GeoJson member to use when writing the body of the spatial object. + private static string GetDataName(SpatialType type) + { + switch (type) + { + case SpatialType.Point: + case SpatialType.LineString: + case SpatialType.Polygon: + case SpatialType.MultiPoint: + case SpatialType.MultiLineString: + case SpatialType.MultiPolygon: + return GeoJsonConstants.CoordinatesMemberName; + case SpatialType.Collection: + return GeoJsonConstants.GeometriesMemberName; + default: + Debug.Assert(false, "SpatialType " + type + " is not currently supported in GeoJsonWriter."); + throw new NotImplementedException(); + } + } + + /// + /// Whether or not the specified type wraps its data in an outer array. + /// + /// SpatialType being written. + /// True if the type uses an outer array, otherwise false. + private static bool TypeHasArrayScope(SpatialType type) + { + return type != SpatialType.Point && type != SpatialType.LineString; + } + + /// + /// Sets the CoordinateSystem for Geography and Geometry shapes. + /// + /// CoordinateSystem value to set. + private void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + this.currentCoordinateSystem = coordinateSystem; + } + + /// + /// Start writing a Geography or Geometry shape. + /// + /// SpatialType to use when writing the shape. + /// Default CoordinateSystem to use if SetCoordinateSystem is never called on this shape. + private void BeginShape(SpatialType type, CoordinateSystem defaultCoordinateSystem) + { + if (this.currentCoordinateSystem == null) + { + this.currentCoordinateSystem = defaultCoordinateSystem; + } + + if (this.ShapeHasObjectScope) + { + this.WriteShapeHeader(type); + } + + if (TypeHasArrayScope(type)) + { + this.StartArrayScope(); + } + + this.stack.Push(type); + this.figureDrawn = false; + } + + /// + /// Write the type header information for a shape. + /// + /// SpatialType being written. + private void WriteShapeHeader(SpatialType type) + { + this.StartObjectScope(); + this.AddPropertyName(GeoJsonConstants.TypeMemberName); + this.AddValue(GetSpatialTypeName(type)); + this.AddPropertyName(GetDataName(type)); + } + + /// + /// Start writing a figure in a Geography or Geometry shape. + /// + private void BeginFigure() + { + if (this.FigureHasArrayScope) + { + this.StartArrayScope(); + } + + this.figureDrawn = true; + } + + /// + /// Write a position in a Geography or Geometry figure. + /// + /// First (X/Longitude) Coordinate + /// Second (Y/Latitude) Coordinate + /// Z Coordinate + /// M Coordinate + private void WriteControlPoint(double first, double second, double? z, double? m) + { + this.StartArrayScope(); + this.AddValue(first); + this.AddValue(second); + + if (z.HasValue) + { + this.AddValue(z.Value); + + if (m.HasValue) + { + this.AddValue(m.Value); + } + } + else if (m.HasValue) + { + this.AddValue(null); + this.AddValue(m.Value); + } + + this.EndArrayScope(); + } + + /// + /// Ends a Geography or Geometry figure. + /// + private void EndFigure() + { + if (this.FigureHasArrayScope) + { + this.EndArrayScope(); + } + } + + /// + /// Ends a Geography or Geometry shape. + /// + private void EndShape() + { + SpatialType type = this.stack.Pop(); + + if (TypeHasArrayScope(type)) + { + this.EndArrayScope(); + } + else if (!this.figureDrawn) + { + // type hasn't write out at least one set of [] yet + this.StartArrayScope(); + this.EndArrayScope(); + } + + if (this.IsTopLevel) + { + this.WriteCrs(); + } + + if (this.ShapeHasObjectScope) + { + this.EndObjectScope(); + } + } + + /// + /// Writes the coordinate reference system footer for the GeoJson object. + /// + private void WriteCrs() + { + // "crs": { + // "type": "name", + // "properties": { + // "name": "EPSG:4326" + // } + // } + this.AddPropertyName(GeoJsonConstants.CrsMemberName); + this.StartObjectScope(); + this.AddPropertyName(GeoJsonConstants.TypeMemberName); + this.AddValue(GeoJsonConstants.CrsTypeMemberValue); + this.AddPropertyName(GeoJsonConstants.CrsPropertiesMemberName); + this.StartObjectScope(); + this.AddPropertyName(GeoJsonConstants.CrsTypeMemberValue); + this.AddValue(String.Concat(GeoJsonConstants.CrsValuePrefix, ':', this.currentCoordinateSystem.Id)); + this.EndObjectScope(); + this.EndObjectScope(); + } + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Geography.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/Geography.cs new file mode 100644 index 0000000..9c556fc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Geography.cs @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.Generic; + using System.Linq; + + /// Represents a base class of geography shapes. + public abstract class Geography : ISpatial + { + /// + /// The implementation that created this instance + /// + private SpatialImplementation creator; + + /// + /// The CoordinateSystem of this geography + /// + private CoordinateSystem coordinateSystem; + + /// Initializes a new instance of the class. + /// The coordinate system of this geography. + /// The implementation that created this instance. + protected Geography(CoordinateSystem coordinateSystem, SpatialImplementation creator) + { + Util.CheckArgumentNull(coordinateSystem, "coordinateSystem"); + Util.CheckArgumentNull(creator, "creator"); + this.coordinateSystem = coordinateSystem; + this.creator = creator; + } + + /// Gets the coordinate system of the geography. + /// The coordinate system of the geography. + public CoordinateSystem CoordinateSystem + { + get + { + return this.coordinateSystem; + } + + internal set + { + this.coordinateSystem = value; + } + } + + /// Gets a value that indicates whether the geography is empty. + /// true if the geography is empty; otherwise, false. + public abstract bool IsEmpty { get; } + + /// + /// Gets the implementation that created this instance. + /// + internal SpatialImplementation Creator + { + get { return this.creator; } + + set { this.creator = value; } + } + + /// Sends the current spatial object to the given pipeline. + /// The spatial pipeline. + public virtual void SendTo(GeographyPipeline chain) + { + Util.CheckArgumentNull(chain, "chain"); + chain.SetCoordinateSystem(this.coordinateSystem); + } + + /// + /// Computes the hashcode for the given CoordinateSystem and the fields + /// + /// Spatial type instances or doubles for base types (Geography/Geometry types). + /// CoordinateSystem instance. + /// Spatial type instances or doubles for base types (Geography/Geometry types). + /// hashcode for the CoordinateSystem instance and Spatial type instances. + internal static int ComputeHashCodeFor(CoordinateSystem coords, IEnumerable fields) + { + unchecked + { + return fields.Aggregate(coords.GetHashCode(), (current, field) => (current * 397) ^ field.GetHashCode()); + } + } + + /// + /// Check for basic equality due to emptyness, nullness, referential equality and difference in coordinate system + /// + /// The other geography + /// Boolean value indicating equality, or null to indicate inconclusion + internal bool? BaseEquals(Geography other) + { + if (other == null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (!this.coordinateSystem.Equals(other.coordinateSystem)) + { + return false; + } + + if (this.IsEmpty || other.IsEmpty) + { + return this.IsEmpty && other.IsEmpty; + } + + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyBuilderImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyBuilderImplementation.cs new file mode 100644 index 0000000..c220577 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyBuilderImplementation.cs @@ -0,0 +1,195 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + /// + /// Builder for Geography types + /// + internal class GeographyBuilderImplementation : GeographyPipeline, IGeographyProvider + { + /// + /// The tree builder + /// + private readonly SpatialTreeBuilder builder; + + /// + /// Constructor + /// + /// The implementation that created this instance. + public GeographyBuilderImplementation(SpatialImplementation creator) + { + this.builder = new GeographyTreeBuilder(creator); + } + + /// + /// Fires when the provider constructs a geography object. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Not following the event-handler pattern")] + public event Action ProduceGeography + { + add { this.builder.ProduceInstance += value; } + remove { this.builder.ProduceInstance -= value; } + } + + /// + /// Constructed Geography + /// + public Geography ConstructedGeography + { + get { return this.builder.ConstructedInstance; } + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "ForwardingSegment does the validation")] + public override void LineTo(GeographyPosition position) + { + Debug.Assert(position != null, "ForwardingSegment should have validated nullness"); + this.builder.LineTo(position.Latitude, position.Longitude, position.Z, position.M); + } + + /// + /// Begin drawing a figure + /// + /// Next position + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "ForwardingSegment does the validation")] + public override void BeginFigure(GeographyPosition position) + { + Debug.Assert(position != null, "ForwardingSegment should have validated nullness"); + this.builder.BeginFigure(position.Latitude, position.Longitude, position.Z, position.M); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + public override void BeginGeography(SpatialType type) + { + this.builder.BeginGeo(type); + } + + /// + /// Ends the current figure + /// + public override void EndFigure() + { + this.builder.EndFigure(); + } + + /// + /// Ends the current spatial object + /// + public override void EndGeography() + { + this.builder.EndGeo(); + } + + /// + /// Setup the pipeline for reuse + /// + public override void Reset() + { + this.builder.Reset(); + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + Util.CheckArgumentNull(coordinateSystem, "coordinateSystem"); + this.builder.SetCoordinateSystem(coordinateSystem.EpsgId); + } + + /// + /// Geography Tree Builder + /// + private class GeographyTreeBuilder : SpatialTreeBuilder + { + /// + /// The implementation that created this instance. + /// + private readonly SpatialImplementation creator; + + /// + /// CoordinateSystem for the building geography + /// + private CoordinateSystem currentCoordinateSystem; + + /// + /// Initializes a new instance of the class. + /// + /// The implementation that created this instance. + public GeographyTreeBuilder(SpatialImplementation creator) + { + Util.CheckArgumentNull(creator, "creator"); + this.creator = creator; + } + + /// + /// Set the coordinate system based on the given ID + /// + /// The coordinate system ID to set. Null indicates the default should be used + internal override void SetCoordinateSystem(int? epsgId) + { + this.currentCoordinateSystem = CoordinateSystem.Geography(epsgId); + } + + /// + /// Create a new instance of Point + /// + /// Whether the point is empty + /// X + /// Y + /// Z + /// M + /// A new instance of point + protected override Geography CreatePoint(bool isEmpty, double x, double y, double? z, double? m) + { + return isEmpty ? new GeographyPointImplementation(this.currentCoordinateSystem, this.creator) : new GeographyPointImplementation(this.currentCoordinateSystem, this.creator, x, y, z, m); + } + + /// + /// Create a new instance of T + /// + /// The spatial type to create + /// The arguments + /// A new instance of T + protected override Geography CreateShapeInstance(SpatialType type, IEnumerable spatialData) + { + switch (type) + { + case SpatialType.LineString: + return new GeographyLineStringImplementation(this.currentCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.Polygon: + return new GeographyPolygonImplementation(this.currentCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.MultiPoint: + return new GeographyMultiPointImplementation(this.currentCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.MultiLineString: + return new GeographyMultiLineStringImplementation(this.currentCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.MultiPolygon: + return new GeographyMultiPolygonImplementation(this.currentCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.Collection: + return new GeographyCollectionImplementation(this.currentCoordinateSystem, this.creator, spatialData.ToArray()); + case SpatialType.FullGlobe: + return new GeographyFullGlobeImplementation(this.currentCoordinateSystem, this.creator); + default: + Debug.Assert(false, "Point type should not call CreateShapeInstance, use CreatePoint instead."); + return null; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollection.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollection.cs new file mode 100644 index 0000000..698ce32 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollection.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the collection of geographies. + public abstract class GeographyCollection : Geography + { + /// Initializes a new instance of the class. + /// The coordinate system of this geography collection. + /// The implementation that created this instance. + protected GeographyCollection(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the collection of geographies. + /// The collection of geographies. + public abstract ReadOnlyCollection Geographies { get; } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeographyCollection other) + { + return this.BaseEquals(other) ?? this.Geographies.SequenceEqual(other.Geographies); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyCollection); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Geographies); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollectionImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollectionImplementation.cs new file mode 100644 index 0000000..db5c8bc --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCollectionImplementation.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geography Collection + /// + internal class GeographyCollectionImplementation : GeographyCollection + { + /// + /// Collection of geography instances + /// + private Geography[] geographyArray; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Collection of geography instances + internal GeographyCollectionImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params Geography[] geography) + : base(coordinateSystem, creator) + { + this.geographyArray = geography ?? new Geography[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Collection of geography instances + internal GeographyCollectionImplementation(SpatialImplementation creator, params Geography[] geography) + : this(CoordinateSystem.DefaultGeography, creator, geography) + { + } + + /// + /// Is Geography Collection Empty + /// + public override bool IsEmpty + { + get + { + return this.geographyArray.Length == 0; + } + } + + /// + /// Geographies + /// + public override ReadOnlyCollection Geographies + { + get { return new ReadOnlyCollection(this.geographyArray); } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.Collection); + for (int i = 0; i < this.geographyArray.Length; ++i) + { + this.geographyArray[i].SendTo(pipeline); + } + + pipeline.EndGeography(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCurve.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCurve.cs new file mode 100644 index 0000000..0490818 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyCurve.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the curve of geography. + public abstract class GeographyCurve : Geography + { + /// Initializes a new instance of the class. + /// The coordinate system of this geography curve. + /// The implementation that created this instance. + protected GeographyCurve(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactory.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactory.cs new file mode 100644 index 0000000..0594334 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactory.cs @@ -0,0 +1,275 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Geography Factory + /// + public static class GeographyFactory + { + #region Point and MultiPoint + + /// + /// Create a Geography Point + /// + /// The CoordinateSystem + /// The latitude value + /// The longitude value + /// The Z value + /// The M value + /// A Geography Point Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeographyFactory Point(CoordinateSystem coordinateSystem, double latitude, double longitude, double? z, double? m) + { + return new GeographyFactory(coordinateSystem).Point(latitude, longitude, z, m); + } + + /// + /// Create a Geography Point + /// + /// The latitude value + /// The longitude value + /// The Z value + /// The M value + /// A Geography Point Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeographyFactory Point(double latitude, double longitude, double? z, double? m) + { + return Point(CoordinateSystem.DefaultGeography, latitude, longitude, z, m); + } + + /// + /// Create a Geography Point + /// + /// The CoordinateSystem + /// The latitude value + /// The longitude value + /// A Geography Point Factory + public static GeographyFactory Point(CoordinateSystem coordinateSystem, double latitude, double longitude) + { + return Point(coordinateSystem, latitude, longitude, null, null); + } + + /// + /// Create a Geography Point + /// + /// The latitude value + /// The longitude value + /// A Geography Point Factory + public static GeographyFactory Point(double latitude, double longitude) + { + return Point(CoordinateSystem.DefaultGeography, latitude, longitude, null, null); + } + + /// + /// Create a factory with an empty Geography Point + /// + /// The CoordinateSystem + /// A Geography Point Factory + public static GeographyFactory Point(CoordinateSystem coordinateSystem) + { + return new GeographyFactory(coordinateSystem).Point(); + } + + /// + /// Create a factory with an empty Geography Point + /// + /// A Geography Point Factory + public static GeographyFactory Point() + { + return Point(CoordinateSystem.DefaultGeography); + } + + /// + /// Create a Geography MultiPoint + /// + /// The CoordinateSystem + /// A Geography MultiPoint Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeographyFactory MultiPoint(CoordinateSystem coordinateSystem) + { + return new GeographyFactory(coordinateSystem).MultiPoint(); + } + + /// + /// Create a Geography MultiPoint + /// + /// A Geography MultiPoint Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeographyFactory MultiPoint() + { + return MultiPoint(CoordinateSystem.DefaultGeography); + } + + #endregion + + #region LineString and MultiLineString + + /// + /// Create a Geography LineString with a starting position + /// + /// The CoordinateSystem + /// The latitude value + /// The longitude value + /// The Z value + /// The M value + /// A Geography LineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeographyFactory LineString(CoordinateSystem coordinateSystem, double latitude, double longitude, double? z, double? m) + { + return new GeographyFactory(coordinateSystem).LineString(latitude, longitude, z, m); + } + + /// + /// Create a Geography LineString with a starting position + /// + /// The latitude value + /// The longitude value + /// The Z value + /// The M value + /// A Geography LineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeographyFactory LineString(double latitude, double longitude, double? z, double? m) + { + return LineString(CoordinateSystem.DefaultGeography, latitude, longitude, z, m); + } + + /// + /// Create a Geography LineString with a starting position + /// + /// The CoordinateSystem + /// The latitude value + /// The longitude value + /// A Geography LineString Factory + public static GeographyFactory LineString(CoordinateSystem coordinateSystem, double latitude, double longitude) + { + return LineString(coordinateSystem, latitude, longitude, null, null); + } + + /// + /// Create a Geography LineString with a starting position + /// + /// The latitude value + /// The longitude value + /// A Geography LineString Factory + public static GeographyFactory LineString(double latitude, double longitude) + { + return LineString(CoordinateSystem.DefaultGeography, latitude, longitude, null, null); + } + + /// + /// Create an empty Geography LineString + /// + /// The CoordinateSystem + /// A Geography LineString Factory + public static GeographyFactory LineString(CoordinateSystem coordinateSystem) + { + return new GeographyFactory(coordinateSystem).LineString(); + } + + /// + /// Create an empty Geography LineString + /// + /// A Geography LineString Factory + public static GeographyFactory LineString() + { + return LineString(CoordinateSystem.DefaultGeography); + } + + /// + /// Create a Geography MultiLineString + /// + /// The CoordinateSystem + /// A Geography MultiLineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeographyFactory MultiLineString(CoordinateSystem coordinateSystem) + { + return new GeographyFactory(coordinateSystem).MultiLineString(); + } + + /// + /// Create a Geography MultiLineString + /// + /// A Geography MultiLineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeographyFactory MultiLineString() + { + return MultiLineString(CoordinateSystem.DefaultGeography); + } + + #endregion + + #region Polygon + + /// + /// Create a Geography Polygon + /// + /// The CoordinateSystem + /// A Geography Polygon Factory + public static GeographyFactory Polygon(CoordinateSystem coordinateSystem) + { + return new GeographyFactory(coordinateSystem).Polygon(); + } + + /// + /// Create a Geography Polygon + /// + /// A Geography Polygon Factory + public static GeographyFactory Polygon() + { + return Polygon(CoordinateSystem.DefaultGeography); + } + + /// + /// Create a Geography MultiPolygon + /// + /// The CoordinateSystem + /// A Geography MultiPolygon Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeographyFactory MultiPolygon(CoordinateSystem coordinateSystem) + { + return new GeographyFactory(coordinateSystem).MultiPolygon(); + } + + /// + /// Create a Geography MultiPolygon + /// + /// A Geography MultiPolygon Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeographyFactory MultiPolygon() + { + return MultiPolygon(CoordinateSystem.DefaultGeography); + } + + #endregion + + #region Collection + + /// + /// Create a Geography Collection + /// + /// The CoordinateSystem + /// A Geography Collection Factory + public static GeographyFactory Collection(CoordinateSystem coordinateSystem) + { + return new GeographyFactory(coordinateSystem).Collection(); + } + + /// + /// Create a Geography Collection + /// + /// A Geography Collection Factory + public static GeographyFactory Collection() + { + return Collection(CoordinateSystem.DefaultGeography); + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactoryOfT.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactoryOfT.cs new file mode 100644 index 0000000..b66880e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFactoryOfT.cs @@ -0,0 +1,310 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Geograph Spatial Factory + /// + /// The target type + public class GeographyFactory : SpatialFactory where T : Geography + { + /// + /// The provider of the built type + /// + private IGeographyProvider provider; + + /// + /// The chain to build through + /// + private GeographyPipeline buildChain; + + /// + /// Initializes a new instance of the GeographyFactory class + /// + /// The coordinate system + internal GeographyFactory(CoordinateSystem coordinateSystem) + { + var builder = SpatialBuilder.Create(); + this.provider = builder; + this.buildChain = SpatialValidator.Create().ChainTo(builder).StartingLink; + this.buildChain.SetCoordinateSystem(coordinateSystem); + } + + /// + /// Using implicit cast to trigger the Finalize call + /// + /// The factory + /// The built instance of the target type + [SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")] + public static implicit operator T(GeographyFactory factory) + { + if (factory == null) + { + throw new ArgumentNullException("factory"); + } + + return factory.Build(); + } + + #region Public Construction Calls + + /// + /// Start a new Point + /// + /// The latitude value + /// The longitude value + /// The Z value + /// The M Value + /// The current instance of GeographyFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeographyFactory Point(double latitude, double longitude, double? z, double? m) + { + this.BeginGeo(SpatialType.Point); + this.LineTo(latitude, longitude, z, m); + return this; + } + + /// + /// Start a new Point + /// + /// The latitude value + /// The longitude value + /// The current instance of GeographyFactory + public GeographyFactory Point(double latitude, double longitude) + { + return this.Point(latitude, longitude, null, null); + } + + /// + /// Start a new empty Point + /// + /// The current instance of GeographyFactory + public GeographyFactory Point() + { + this.BeginGeo(SpatialType.Point); + return this; + } + + /// + /// Start a new LineString + /// + /// The latitude value + /// The longitude value + /// The Z value + /// The M Value + /// The current instance of GeographyFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeographyFactory LineString(double latitude, double longitude, double? z, double? m) + { + this.BeginGeo(SpatialType.LineString); + this.LineTo(latitude, longitude, z, m); + return this; + } + + /// + /// Start a new LineString + /// + /// The latitude value + /// The longitude value + /// The current instance of GeographyFactory + public GeographyFactory LineString(double latitude, double longitude) + { + return this.LineString(latitude, longitude, null, null); + } + + /// + /// Start a new empty LineString + /// + /// The current instance of GeographyFactory + public GeographyFactory LineString() + { + this.BeginGeo(SpatialType.LineString); + return this; + } + + /// + /// Start a new Polygon + /// + /// The current instance of GeographyFactory + public GeographyFactory Polygon() + { + this.BeginGeo(SpatialType.Polygon); + return this; + } + + /// + /// Start a new MultiPoint + /// + /// The current instance of GeographyFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public GeographyFactory MultiPoint() + { + this.BeginGeo(SpatialType.MultiPoint); + return this; + } + + /// + /// Start a new MultiLineString + /// + /// The current instance of GeographyFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public GeographyFactory MultiLineString() + { + this.BeginGeo(SpatialType.MultiLineString); + return this; + } + + /// + /// Start a new MultiPolygon + /// + /// The current instance of GeographyFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public GeographyFactory MultiPolygon() + { + this.BeginGeo(SpatialType.MultiPolygon); + return this; + } + + /// + /// Start a new Collection + /// + /// The current instance of GeographyFactory + public GeographyFactory Collection() + { + this.BeginGeo(SpatialType.Collection); + return this; + } + + /// + /// Start a new Polygon Ring + /// + /// The latitude value + /// The longitude value + /// The Z value + /// The M Value + /// The current instance of GeographyFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeographyFactory Ring(double latitude, double longitude, double? z, double? m) + { + this.StartRing(latitude, longitude, z, m); + return this; + } + + /// + /// Start a new Polygon Ring + /// + /// The latitude value + /// The longitude value + /// The current instance of GeographyFactory + public GeographyFactory Ring(double latitude, double longitude) + { + return this.Ring(latitude, longitude, null, null); + } + + /// + /// Add a new point in the current line figure + /// + /// The latitude value + /// The longitude value + /// The Z value + /// The M Value + /// The current instance of GeographyFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeographyFactory LineTo(double latitude, double longitude, double? z, double? m) + { + this.AddPos(latitude, longitude, z, m); + return this; + } + + /// + /// Add a new point in the current line figure + /// + /// The latitude value + /// The longitude value + /// The current instance of GeographyFactory + public GeographyFactory LineTo(double latitude, double longitude) + { + return this.LineTo(latitude, longitude, null, null); + } + + #endregion + + /// + /// Finish the current geography + /// + /// The constructed instance + public T Build() + { + this.Finish(); + return (T)this.provider.ConstructedGeography; + } + + #region GeoDataPipeline overrides + + /// + /// Begin a new geography + /// + /// The spatial type + protected override void BeginGeo(SpatialType type) + { + base.BeginGeo(type); + this.buildChain.BeginGeography(type); + } + + /// + /// Begin drawing a figure + /// TODO: longitude and latitude should be swapped !!! per ABNF. + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + protected override void BeginFigure(double latitude, double longitude, double? z, double? m) + { + base.BeginFigure(latitude, longitude, z, m); + this.buildChain.BeginFigure(new GeographyPosition(latitude, longitude, z, m)); + } + + /// + /// Draw a point in the specified coordinate + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + protected override void AddLine(double latitude, double longitude, double? z, double? m) + { + base.AddLine(latitude, longitude, z, m); + this.buildChain.LineTo(new GeographyPosition(latitude, longitude, z, m)); + } + + /// + /// Ends the figure set on the current node + /// + protected override void EndFigure() + { + base.EndFigure(); + this.buildChain.EndFigure(); + } + + /// + /// Ends the current spatial object + /// + protected override void EndGeo() + { + base.EndGeo(); + this.buildChain.EndGeography(); + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobe.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobe.cs new file mode 100644 index 0000000..e17d375 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobe.cs @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the full globe of geography. + public abstract class GeographyFullGlobe : GeographySurface + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyFullGlobe(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011", Justification = "Method should not be declared on base")] + public bool Equals(GeographyFullGlobe other) + { + return this.BaseEquals(other) ?? true; + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyFullGlobe); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, new[] { 0 }); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobeImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobeImplementation.cs new file mode 100644 index 0000000..5ab3ea7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyFullGlobeImplementation.cs @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Implementation of FullGlobe + /// + internal class GeographyFullGlobeImplementation : GeographyFullGlobe + { + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + internal GeographyFullGlobeImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + internal GeographyFullGlobeImplementation(SpatialImplementation creator) + : this(CoordinateSystem.DefaultGeography, creator) + { + } + + /// + /// Is FullGlobe empty + /// + public override bool IsEmpty + { + get { return false; } + } + + /// + /// Sends the spatial geography object to the given sink + /// + /// The spatial pipeline + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.FullGlobe); + pipeline.EndGeography(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyHelperMethods.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyHelperMethods.cs new file mode 100644 index 0000000..84e4678 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyHelperMethods.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Helper methods for the geography type. + /// + internal static class GeographyHelperMethods + { + /// + /// Sends the current spatial object to the given pipeline with a figure that represents this LineString + /// + /// GeographyLineString instance to serialize. + /// The pipeline to populate to + internal static void SendFigure(this GeographyLineString lineString, GeographyPipeline pipeline) + { + ReadOnlyCollection points = lineString.Points; + for (int i = 0; i < points.Count; ++i) + { + if (i == 0) + { + pipeline.BeginFigure(new GeographyPosition(points[i].Latitude, points[i].Longitude, points[i].Z, points[i].M)); + } + else + { + pipeline.LineTo(new GeographyPosition(points[i].Latitude, points[i].Longitude, points[i].Z, points[i].M)); + } + } + + if (points.Count > 0) + { + pipeline.EndFigure(); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineString.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineString.cs new file mode 100644 index 0000000..df8beff --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineString.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents a geography line string consist of an array of geo points. + public abstract class GeographyLineString : GeographyCurve + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyLineString(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the point list. + /// The point list. + public abstract ReadOnlyCollection Points { get; } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeographyLineString other) + { + return this.BaseEquals(other) ?? this.Points.SequenceEqual(other.Points); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyLineString); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Points); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineStringImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineStringImplementation.cs new file mode 100644 index 0000000..eeaa93e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyLineStringImplementation.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// A Geography linestring consist of an array of GeoPoints + /// + internal class GeographyLineStringImplementation : GeographyLineString + { + /// + /// Points array + /// + private GeographyPoint[] points; + + /// + /// Constructor + /// + /// CoordinateSystem + /// The implementation that created this instance. + /// The point list + internal GeographyLineStringImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeographyPoint[] points) + : base(coordinateSystem, creator) + { + this.points = points ?? new GeographyPoint[0]; + } + + /// + /// Is LineString Empty + /// + public override bool IsEmpty + { + get + { + return this.points.Length == 0; + } + } + + /// + /// Point list + /// + public override ReadOnlyCollection Points + { + get + { + return new ReadOnlyCollection(this.points); + } + } + + /// + /// Sends the current spatial object to the given sink + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.LineString); + this.SendFigure(pipeline); + pipeline.EndGeography(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiCurve.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiCurve.cs new file mode 100644 index 0000000..69b8a32 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiCurve.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the multi-curve of geography. + public abstract class GeographyMultiCurve : GeographyCollection + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyMultiCurve(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineString.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineString.cs new file mode 100644 index 0000000..bf65d68 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineString.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the multi-line string of geography. + public abstract class GeographyMultiLineString : GeographyMultiCurve + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyMultiLineString(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the line strings. + /// A collection of line strings. + public abstract ReadOnlyCollection LineStrings { get; } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeographyMultiLineString other) + { + return this.BaseEquals(other) ?? this.LineStrings.SequenceEqual(other.LineStrings); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyMultiLineString); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.LineStrings); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineStringImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineStringImplementation.cs new file mode 100644 index 0000000..438e4d8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiLineStringImplementation.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geography Multi-LineString + /// + internal class GeographyMultiLineStringImplementation : GeographyMultiLineString + { + /// + /// Line Strings + /// + private GeographyLineString[] lineStrings; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Line Strings + internal GeographyMultiLineStringImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeographyLineString[] lineStrings) + : base(coordinateSystem, creator) + { + this.lineStrings = lineStrings ?? new GeographyLineString[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Line Strings + internal GeographyMultiLineStringImplementation(SpatialImplementation creator, params GeographyLineString[] lineStrings) + : this(CoordinateSystem.DefaultGeography, creator, lineStrings) + { + } + + /// + /// Is MultiLineString Empty + /// + public override bool IsEmpty + { + get + { + return this.lineStrings.Length == 0; + } + } + + /// + /// Geographies + /// + public override ReadOnlyCollection Geographies + { + get { return new ReadOnlyCollection(this.lineStrings); } + } + + /// + /// Line Strings + /// + public override ReadOnlyCollection LineStrings + { + get { return new ReadOnlyCollection(this.lineStrings); } + } + + /// + /// Sends the current spatial object to the given sink + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.MultiLineString); + + for (int i = 0; i < this.lineStrings.Length; ++i) + { + this.lineStrings[i].SendTo(pipeline); + } + + pipeline.EndGeography(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPoint.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPoint.cs new file mode 100644 index 0000000..2832cb1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPoint.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the multi-point of geography. + public abstract class GeographyMultiPoint : GeographyCollection + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyMultiPoint(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets a collection of points. + /// A collection of points. + public abstract ReadOnlyCollection Points { get; } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeographyMultiPoint other) + { + return this.BaseEquals(other) ?? this.Points.SequenceEqual(other.Points); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyMultiPoint); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Points); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPointImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPointImplementation.cs new file mode 100644 index 0000000..e3efd3f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPointImplementation.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geography Multi-Point + /// + internal class GeographyMultiPointImplementation : GeographyMultiPoint + { + /// + /// Points + /// + private GeographyPoint[] points; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Points + internal GeographyMultiPointImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeographyPoint[] points) + : base(coordinateSystem, creator) + { + this.points = points ?? new GeographyPoint[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Points + internal GeographyMultiPointImplementation(SpatialImplementation creator, params GeographyPoint[] points) + : this(CoordinateSystem.DefaultGeography, creator, points) + { + } + + /// + /// Is MultiPoint Empty + /// + public override bool IsEmpty + { + get + { + return this.points.Length == 0; + } + } + + /// + /// Geography + /// + public override ReadOnlyCollection Geographies + { + get { return new ReadOnlyCollection(this.points); } + } + + /// + /// Points + /// + public override ReadOnlyCollection Points + { + get { return new ReadOnlyCollection(this.points); } + } + + /// + /// Sends the current spatial object to the given sink + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.MultiPoint); + + for (int i = 0; i < this.points.Length; ++i) + { + this.points[i].SendTo(pipeline); + } + + pipeline.EndGeography(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygon.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygon.cs new file mode 100644 index 0000000..857e97e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygon.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the multi-polygon of geography. + public abstract class GeographyMultiPolygon : GeographyMultiSurface + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyMultiPolygon(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets a collection of polygons. + /// A collection of polygons. + public abstract ReadOnlyCollection Polygons { get; } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeographyMultiPolygon other) + { + return this.BaseEquals(other) ?? this.Polygons.SequenceEqual(other.Polygons); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyMultiPolygon); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Polygons); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygonImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygonImplementation.cs new file mode 100644 index 0000000..0a4d2e3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiPolygonImplementation.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geography Multi-Polygon + /// + internal class GeographyMultiPolygonImplementation : GeographyMultiPolygon + { + /// + /// Polygons + /// + private GeographyPolygon[] polygons; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Polygons + internal GeographyMultiPolygonImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeographyPolygon[] polygons) + : base(coordinateSystem, creator) + { + this.polygons = polygons; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Polygons + internal GeographyMultiPolygonImplementation(SpatialImplementation creator, params GeographyPolygon[] polygons) + : this(CoordinateSystem.DefaultGeography, creator, polygons) + { + } + + /// + /// Is MultiPolygon Empty + /// + public override bool IsEmpty + { + get + { + return this.polygons.Length == 0; + } + } + + /// + /// Geographies + /// + public override ReadOnlyCollection Geographies + { + get { return new ReadOnlyCollection(this.polygons); } + } + + /// + /// Polygons + /// + public override ReadOnlyCollection Polygons + { + get { return new ReadOnlyCollection(this.polygons); } + } + + /// + /// Sends the current spatial object to the given sink + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.MultiPolygon); + for (int i = 0; i < this.polygons.Length; ++i) + { + this.polygons[i].SendTo(pipeline); + } + + pipeline.EndGeography(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiSurface.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiSurface.cs new file mode 100644 index 0000000..46cda59 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyMultiSurface.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the multi-surface of geography. + public abstract class GeographyMultiSurface : GeographyCollection + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyMultiSurface(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyOperationsExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyOperationsExtensions.cs new file mode 100644 index 0000000..b4448fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyOperationsExtensions.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Linq; + + /// + /// Extension methods for the Geography operations + /// + public static class GeographyOperationsExtensions + { + /// Determines the distance of the geography. + /// The operation result. + /// The first operand. + /// The second operand. + public static double? Distance(this Geography operand1, Geography operand2) + { + return OperationsFor(operand1, operand2).IfValidReturningNullable(ops => ops.Distance(operand1, operand2)); + } + + /// Determines the Length of the geography LineString. + /// The operation result. + /// The LineString operand. + public static double? Length(this Geography operand) + { + return OperationsFor(operand).IfValidReturningNullable(ops => ops.Length(operand)); + } + + /// Determines if geography point and polygon will intersect. + /// The operation result. + /// The first operand. + /// The second operand. + public static bool? Intersects(this Geography operand1, Geography operand2) + { + return OperationsFor(operand1, operand2).IfValidReturningNullable(ops => ops.Intersects(operand1, operand2)); + } + + /// + /// Finds the ops instance registered for the operands. + /// + /// The operands. + /// The ops value, or null if any operand is null + private static SpatialOperations OperationsFor(params Geography[] operands) + { + if (operands.Any(operand => operand == null)) + { + return null; + } + + return operands[0].Creator.VerifyAndGetNonNullOperations(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPipeline.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPipeline.cs new file mode 100644 index 0000000..554d111 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPipeline.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the pipeline of geography. + public abstract class GeographyPipeline + { + /// Begins drawing a spatial object. + /// The spatial type of the object. + public abstract void BeginGeography(SpatialType type); + + /// Begins drawing a figure. + /// The position of the figure. + public abstract void BeginFigure(GeographyPosition position); + + /// Draws a point in the specified coordinate. + /// The position of the line. + public abstract void LineTo(GeographyPosition position); + + /// Ends the current figure. + public abstract void EndFigure(); + + /// Ends the current spatial object. + public abstract void EndGeography(); + + /// Sets the coordinate system. + /// The coordinate system to set. + public abstract void SetCoordinateSystem(CoordinateSystem coordinateSystem); + + /// Resets the pipeline. + public abstract void Reset(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPoint.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPoint.cs new file mode 100644 index 0000000..f29020f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPoint.cs @@ -0,0 +1,116 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents a geography point. + public abstract class GeographyPoint : Geography + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyPoint(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the latitude. + /// The latitude. + public abstract double Latitude { get; } + + /// Gets the longitude. + /// The longitude. + public abstract double Longitude { get; } + + /// Gets the nullable Z. + /// The nullable Z. + /// Z is the altitude portion of position. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z is meaningful")] + public abstract double? Z { get; } + + /// Gets the nullable M. + /// The nullable M. + /// M is the arbitrary measure associated with a position. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "m is meaningful")] + public abstract double? M { get; } + + /// Creates a geography point using the specified latitude and longitude. + /// The geography point that was created. + /// The latitude. + /// The longitude. + public static GeographyPoint Create(double latitude, double longitude) + { + return Create(CoordinateSystem.DefaultGeography, latitude, longitude, null, null); + } + + /// Creates a geography point using the specified latitude, longitude and dimension. + /// The geography point that was created. + /// The latitude. + /// The longitude. + /// The z dimension. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z is meaningful")] + public static GeographyPoint Create(double latitude, double longitude, double? z) + { + return Create(CoordinateSystem.DefaultGeography, latitude, longitude, z, null); + } + + /// Creates a geography point using the specified latitude, longitude and dimensions. + /// The geography point that was created. + /// The latitude. + /// The longitude. + /// The z dimension. + /// The m dimension. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + public static GeographyPoint Create(double latitude, double longitude, double? z, double? m) + { + return Create(CoordinateSystem.DefaultGeography, latitude, longitude, z, m); + } + + /// Creates a geography point using the specified coordinate system, latitude, longitude and dimensions. + /// The geography point that was created. + /// The coordinate system to use. + /// The latitude. + /// The longitude. + /// The z dimension. + /// The m dimension. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + public static GeographyPoint Create(CoordinateSystem coordinateSystem, double latitude, double longitude, double? z, double? m) + { + var builder = SpatialBuilder.Create(); + var pipeline = builder.GeographyPipeline; + pipeline.SetCoordinateSystem(coordinateSystem); + pipeline.BeginGeography(SpatialType.Point); + pipeline.BeginFigure(new GeographyPosition(latitude, longitude, z, m)); + pipeline.EndFigure(); + pipeline.EndGeography(); + return (GeographyPoint)builder.ConstructedGeography; + } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeographyPoint other) + { + return this.BaseEquals(other) ?? this.Latitude == other.Latitude && this.Longitude == other.Longitude && this.Z == other.Z && this.M == other.M; + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyPoint); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Geography.ComputeHashCodeFor(this.CoordinateSystem, new[] { this.IsEmpty ? 0 : this.Latitude, this.IsEmpty ? 0 : this.Longitude, this.Z ?? 0, this.M ?? 0 }); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPointImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPointImplementation.cs new file mode 100644 index 0000000..2e8bbb8 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPointImplementation.cs @@ -0,0 +1,160 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// This class is an implementation of Geography point. + /// + internal class GeographyPointImplementation : GeographyPoint + { + /// + /// Latitude + /// + private double latitude; + + /// + /// Longitude + /// + private double longitude; + + /// + /// Z + /// + private double? z; + + /// + /// M + /// + private double? m; + + #region Internal Constructors + + /// + /// Point constructor + /// + /// CoordinateSystem + /// The implementation that created this instance. + /// latitude + /// longitude + /// Z + /// M + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "zvalue and mvalue are spelled correctly")] + internal GeographyPointImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, double latitude, double longitude, double? zvalue, double? mvalue) + : base(coordinateSystem, creator) + { + if (double.IsNaN(latitude) || double.IsInfinity(latitude)) + { + throw new ArgumentException(Strings.InvalidPointCoordinate(latitude, "latitude")); + } + + if (double.IsNaN(longitude) || double.IsInfinity(longitude)) + { + throw new ArgumentException(Strings.InvalidPointCoordinate(longitude, "longitude")); + } + + this.latitude = latitude; + this.longitude = longitude; + this.z = zvalue; + this.m = mvalue; + } + + /// + /// Create a empty point + /// + /// CoordinateSystem + /// The implementation that created this instance. + internal GeographyPointImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + this.latitude = double.NaN; + this.longitude = double.NaN; + } + + #endregion + + /// + /// Latitude + /// + public override double Latitude + { + get + { + if (this.IsEmpty) + { + throw new NotSupportedException(Strings.Point_AccessCoordinateWhenEmpty); + } + + return this.latitude; + } + } + + /// + /// Longitude + /// + public override double Longitude + { + get + { + if (this.IsEmpty) + { + throw new NotSupportedException(Strings.Point_AccessCoordinateWhenEmpty); + } + + return this.longitude; + } + } + + /// + /// Is Point Empty + /// + public override bool IsEmpty + { + get + { + return double.IsNaN(this.latitude); + } + } + + /// + /// Nullable Z + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + public override double? Z + { + get { return this.z; } + } + + /// + /// Nullable M + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + public override double? M + { + get { return this.m; } + } + + /// + /// Sends the current spatial object to the given sink + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.Point); + if (!this.IsEmpty) + { + pipeline.BeginFigure(new GeographyPosition(this.latitude, this.longitude, this.z, this.m)); + pipeline.EndFigure(); + } + + pipeline.EndGeography(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygon.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygon.cs new file mode 100644 index 0000000..53845cf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygon.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the geography polygon. + public abstract class GeographyPolygon : GeographySurface + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographyPolygon(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets a collection of rings. + /// A collection of rings. + public abstract ReadOnlyCollection Rings { get; } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeographyPolygon other) + { + return this.BaseEquals(other) ?? this.Rings.SequenceEqual(other.Rings); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeographyPolygon); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Rings); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygonImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygonImplementation.cs new file mode 100644 index 0000000..fd737c3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPolygonImplementation.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geography polygon + /// + internal class GeographyPolygonImplementation : GeographyPolygon + { + /// + /// Rings + /// + private GeographyLineString[] rings; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// The rings of this polygon + internal GeographyPolygonImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeographyLineString[] rings) + : base(coordinateSystem, creator) + { + this.rings = rings ?? new GeographyLineString[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// The rings of this polygon + internal GeographyPolygonImplementation(SpatialImplementation creator, params GeographyLineString[] rings) + : this(CoordinateSystem.DefaultGeography, creator, rings) + { + } + + /// + /// Is Polygon Empty + /// + public override bool IsEmpty + { + get + { + return this.rings.Length == 0; + } + } + + /// + /// Set of rings + /// + public override ReadOnlyCollection Rings + { + get { return new ReadOnlyCollection(this.rings); } + } + + /// + /// Sends the current spatial object to the given sink + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeographyPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeography(SpatialType.Polygon); + + for (int i = 0; i < this.rings.Length; ++i) + { + this.rings[i].SendFigure(pipeline); + } + + pipeline.EndGeography(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPosition.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPosition.cs new file mode 100644 index 0000000..297c78e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographyPosition.cs @@ -0,0 +1,159 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Represents one position in the Geographyal coordinate system + /// + //// DEVNOTE(pqian): + //// Initially we had this as a value-type (struct) since it's rarely used outside of call parameters + //// However, since we need to remove the parameterless constructor, we now define it as a refererence type. + public class GeographyPosition : IEquatable + { + /// lattitude portion of position + private readonly double latitude; + + /// longitude portion of position + private readonly double longitude; + + /// arbitrary measure associated with a position + private readonly double? m; + + /// altitude portion of position + private readonly double? z; + + /// Creates a new instance of the class from components. + /// The latitude portion of a position. + /// The longitude portion of a position. + /// The altitude portion of a position. + /// The arbitrary measure associated with a position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public GeographyPosition(double latitude, double longitude, double? z, double? m) + { + this.latitude = latitude; + this.longitude = longitude; + this.z = z; + this.m = m; + } + + /// Creates a new instance of the class from components. + /// The latitude portion of a position. + /// The longitude portion of a position. + public GeographyPosition(double latitude, double longitude) + : this(latitude, longitude, null, null) + { + } + + /// Gets the latitude portion of a position. + /// The latitude portion of a position. + public double Latitude + { + get { return latitude; } + } + + /// Gets the longitude portion of a position. + /// The longitude portion of a position. + public double Longitude + { + get { return longitude; } + } + + /// Gets the arbitrary measure associated with a position. + /// The arbitrary measure associated with a position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public double? M + { + get { return m; } + } + + /// Gets the altitude portion of a position. + /// The altitude portion of a position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public double? Z + { + get { return z; } + } + + /// Performs equality comparison. + /// true if each pair of coordinates is equal; otherwise, false. + /// The first position. + /// The second position. + public static bool operator ==(GeographyPosition left, GeographyPosition right) + { + if (ReferenceEquals(left, null)) + { + return ReferenceEquals(right, null); + } + else if (ReferenceEquals(right, null)) + { + return false; + } + + return left.Equals(right); + } + + /// Performs inequality comparison. + /// true if left is not equal to right; otherwise, false. + /// The first position. + /// The other position. + public static bool operator !=(GeographyPosition left, GeographyPosition right) + { + return !(left == right); + } + + /// Performs equality comparison on an object. + /// true if each pair of coordinates is equal; otherwise, false. + /// The object for comparison. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (obj.GetType() != typeof(GeographyPosition)) + { + return false; + } + + return Equals((GeographyPosition)obj); + } + + /// Performs equality comparison on a spatial geographic position. + /// true if each pair of coordinates is equal; otherwise, false. + /// The other position. + public bool Equals(GeographyPosition other) + { + return other != null && other.latitude.Equals(latitude) && other.longitude.Equals(longitude) && other.z.Equals(z) && + other.m.Equals(m); + } + + /// Computes a hash code. + /// A hash code. + public override int GetHashCode() + { + unchecked + { + int result = latitude.GetHashCode(); + result = (result * 397) ^ longitude.GetHashCode(); + result = (result * 397) ^ (z.HasValue ? z.Value.GetHashCode() : 0); + result = (result * 397) ^ (m.HasValue ? m.Value.GetHashCode() : 0); + return result; + } + } + + /// Formats this instance to a readable string. + /// The string representation of this instance. + public override string ToString() + { + return String.Format(System.Globalization.CultureInfo.InvariantCulture, "GeographyPosition(latitude:{0}, longitude:{1}, z:{2}, m:{3})", this.latitude, this.longitude, this.z.HasValue ? this.z.ToString() : "null", this.m.HasValue ? this.m.ToString() : "null"); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographySurface.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographySurface.cs new file mode 100644 index 0000000..8f464eb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeographySurface.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the geography surface. + public abstract class GeographySurface : Geography + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeographySurface(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Geometry.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/Geometry.cs new file mode 100644 index 0000000..83f07bf --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Geometry.cs @@ -0,0 +1,100 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the base class of geography shapes. + public abstract class Geometry : ISpatial + { + /// + /// The implementation that created this instance. + /// + private SpatialImplementation creator; + + /// + /// The CoordinateSystem of this geometry + /// + private CoordinateSystem coordinateSystem; + + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected Geometry(CoordinateSystem coordinateSystem, SpatialImplementation creator) + { + Util.CheckArgumentNull(coordinateSystem, "coordinateSystem"); + Util.CheckArgumentNull(creator, "creator"); + this.coordinateSystem = coordinateSystem; + this.creator = creator; + } + + /// Gets the SRID of this instance of geometry. + /// The SRID of this instance of geometry. + public CoordinateSystem CoordinateSystem + { + get + { + return this.coordinateSystem; + } + + internal set + { + this.coordinateSystem = value; + } + } + + /// Gets a value that indicates whether geometry is empty. + /// true if the geometry is empty; otherwise, false. + public abstract bool IsEmpty { get; } + + /// + /// Gets the implementation that created this instance. + /// + internal SpatialImplementation Creator + { + get { return this.creator; } + + set { this.creator = value; } + } + + /// Sends the current spatial object to the given pipeline. + /// The spatial pipeline. + public virtual void SendTo(GeometryPipeline chain) + { + Util.CheckArgumentNull(chain, "chain"); + chain.SetCoordinateSystem(this.coordinateSystem); + } + + /// + /// Check for basic equality due to emptyness, nullness, referential equality and difference in coordinate system + /// + /// The other geography + /// Boolean value indicating equality, or null to indicate inconclusion + internal bool? BaseEquals(Geometry other) + { + if (other == null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (!this.coordinateSystem.Equals(other.coordinateSystem)) + { + return false; + } + + if (this.IsEmpty || other.IsEmpty) + { + return this.IsEmpty && other.IsEmpty; + } + + return null; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryBuilderImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryBuilderImplementation.cs new file mode 100644 index 0000000..ea88141 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryBuilderImplementation.cs @@ -0,0 +1,193 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + /// + /// Builder for Geometry types + /// + internal class GeometryBuilderImplementation : GeometryPipeline, IGeometryProvider + { + /// + /// The tree builder + /// + private readonly SpatialTreeBuilder builder; + + /// + /// Constructor + /// + /// The implementation that created this instance. + public GeometryBuilderImplementation(SpatialImplementation creator) + { + this.builder = new GeometryTreeBuilder(creator); + } + + /// + /// Fires when the provider constructs a geometry object. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Not following the event-handler pattern")] + public event Action ProduceGeometry + { + add { this.builder.ProduceInstance += value; } + remove { this.builder.ProduceInstance -= value; } + } + + /// + /// Constructed Geography + /// + public Geometry ConstructedGeometry + { + get { return this.builder.ConstructedInstance; } + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "ForwardingSegment does the validation")] + public override void LineTo(GeometryPosition position) + { + Debug.Assert(position != null, "ForwardingSegment should have validated nullness"); + this.builder.LineTo(position.X, position.Y, position.Z, position.M); + } + + /// + /// Begin drawing a figure + /// + /// Next position + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "ForwardingSegment does the validation")] + public override void BeginFigure(GeometryPosition position) + { + Debug.Assert(position != null, "ForwardingSegment should have validated nullness"); + this.builder.BeginFigure(position.X, position.Y, position.Z, position.M); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + public override void BeginGeometry(SpatialType type) + { + this.builder.BeginGeo(type); + } + + /// + /// Ends the current figure + /// + public override void EndFigure() + { + this.builder.EndFigure(); + } + + /// + /// Ends the current spatial object + /// + public override void EndGeometry() + { + this.builder.EndGeo(); + } + + /// + /// Setup the pipeline for reuse + /// + public override void Reset() + { + this.builder.Reset(); + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + public override void SetCoordinateSystem(CoordinateSystem coordinateSystem) + { + Util.CheckArgumentNull(coordinateSystem, "coordinateSystem"); + this.builder.SetCoordinateSystem(coordinateSystem.EpsgId); + } + + /// + /// Geography Tree Builder + /// + private class GeometryTreeBuilder : SpatialTreeBuilder + { + /// + /// The implementation that created this instance. + /// + private readonly SpatialImplementation creator; + + /// + /// CoordinateSystem for the building geography + /// + private CoordinateSystem buildCoordinateSystem; + + /// + /// Initializes a new instance of the class. + /// + /// The implementation that created this instance. + public GeometryTreeBuilder(SpatialImplementation creator) + { + Util.CheckArgumentNull(creator, "creator"); + this.creator = creator; + } + + /// + /// Set the coordinate system based on the given ID + /// + /// The coordinate system ID to set. Null indicates the default should be used + internal override void SetCoordinateSystem(int? epsgId) + { + this.buildCoordinateSystem = CoordinateSystem.Geometry(epsgId); + } + + /// + /// Create a new instance of Point + /// + /// Whether the point is empty + /// X + /// Y + /// Z + /// M + /// A new instance of point + protected override Geometry CreatePoint(bool isEmpty, double x, double y, double? z, double? m) + { + return isEmpty ? new GeometryPointImplementation(this.buildCoordinateSystem, this.creator) : new GeometryPointImplementation(this.buildCoordinateSystem, this.creator, x, y, z, m); + } + + /// + /// Create a new instance of T + /// + /// The spatial type to create + /// The arguments + /// A new instance of T + protected override Geometry CreateShapeInstance(SpatialType type, IEnumerable spatialData) + { + switch (type) + { + case SpatialType.LineString: + return new GeometryLineStringImplementation(this.buildCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.Polygon: + return new GeometryPolygonImplementation(this.buildCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.MultiPoint: + return new GeometryMultiPointImplementation(this.buildCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.MultiLineString: + return new GeometryMultiLineStringImplementation(this.buildCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.MultiPolygon: + return new GeometryMultiPolygonImplementation(this.buildCoordinateSystem, this.creator, spatialData.Cast().ToArray()); + case SpatialType.Collection: + return new GeometryCollectionImplementation(this.buildCoordinateSystem, this.creator, spatialData.ToArray()); + default: + Debug.Assert(false, "Point type should not call CreateShapeInstance, use CreatePoint instead."); + return null; + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollection.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollection.cs new file mode 100644 index 0000000..8e2d8c9 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollection.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the geometry collection. + public abstract class GeometryCollection : Geometry + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeometryCollection(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the geometry instances in this collection. + /// A collection of geometries. + public abstract ReadOnlyCollection Geometries { get; } + + /// Determines whether this instance and another specified geometry instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geometry to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeometryCollection other) + { + return this.BaseEquals(other) ?? this.Geometries.SequenceEqual(other.Geometries); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeometryCollection); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Geometries); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollectionImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollectionImplementation.cs new file mode 100644 index 0000000..e72c549 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCollectionImplementation.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geometry Collection + /// + internal class GeometryCollectionImplementation : GeometryCollection + { + /// + /// Collection of Geometry instances + /// + private Geometry[] geometryArray; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Collection of Geometry instances + internal GeometryCollectionImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params Geometry[] geometry) + : base(coordinateSystem, creator) + { + this.geometryArray = geometry ?? new Geometry[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Collection of Geometry instances + internal GeometryCollectionImplementation(SpatialImplementation creator, params Geometry[] geometry) + : this(CoordinateSystem.DefaultGeometry, creator, geometry) + { + } + + /// + /// Is Geometry Collection Empty + /// + public override bool IsEmpty + { + get + { + return this.geometryArray.Length == 0; + } + } + + /// + /// Geographies + /// + public override ReadOnlyCollection Geometries + { + get { return new ReadOnlyCollection(this.geometryArray); } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeometryPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeometry(SpatialType.Collection); + for (int i = 0; i < this.geometryArray.Length; ++i) + { + this.geometryArray[i].SendTo(pipeline); + } + + pipeline.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCurve.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCurve.cs new file mode 100644 index 0000000..03dfc2e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryCurve.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the geometry curve. + public abstract class GeometryCurve : Geometry + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeometryCurve(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactory.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactory.cs new file mode 100644 index 0000000..4b363cb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactory.cs @@ -0,0 +1,279 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Geometry Factory + /// + public static class GeometryFactory + { + #region Point and MultiPoint + + /// + /// Create a Geometry Point + /// + /// The CoordinateSystem + /// The X value + /// The Y value + /// The Z value + /// The M value + /// A Geometry Point Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory Point(CoordinateSystem coordinateSystem, double x, double y, double? z, double? m) + { + return new GeometryFactory(coordinateSystem).Point(x, y, z, m); + } + + /// + /// Create a Geometry Point + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + /// A Geometry Point Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory Point(double x, double y, double? z, double? m) + { + return Point(CoordinateSystem.DefaultGeometry, x, y, z, m); + } + + /// + /// Create a Geometry Point + /// + /// The CoordinateSystem + /// The X value + /// The Y value + /// A Geometry Point Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory Point(CoordinateSystem coordinateSystem, double x, double y) + { + return Point(coordinateSystem, x, y, null, null); + } + + /// + /// Create a Geometry Point + /// + /// The X value + /// The Y value + /// A Geometry Point Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory Point(double x, double y) + { + return Point(CoordinateSystem.DefaultGeometry, x, y, null, null); + } + + /// + /// Create a factory with an empty Geometry Point + /// + /// The CoordinateSystem + /// A Geometry Point Factory + public static GeometryFactory Point(CoordinateSystem coordinateSystem) + { + return new GeometryFactory(coordinateSystem).Point(); + } + + /// + /// Create a factory with an empty Geometry Point + /// + /// A Geometry Point Factory + public static GeometryFactory Point() + { + return Point(CoordinateSystem.DefaultGeometry); + } + + /// + /// Create a Geometry MultiPoint + /// + /// The CoordinateSystem + /// A Geometry MultiPoint Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeometryFactory MultiPoint(CoordinateSystem coordinateSystem) + { + return new GeometryFactory(coordinateSystem).MultiPoint(); + } + + /// + /// Create a Geometry MultiPoint + /// + /// A Geometry MultiPoint Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeometryFactory MultiPoint() + { + return MultiPoint(CoordinateSystem.DefaultGeometry); + } + + #endregion + + #region LineString and MultiLineString + + /// + /// Create a Geometry LineString with a starting position + /// + /// The CoordinateSystem + /// The X value + /// The Y value + /// The Z value + /// The M value + /// A Geometry LineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory LineString(CoordinateSystem coordinateSystem, double x, double y, double? z, double? m) + { + return new GeometryFactory(coordinateSystem).LineString(x, y, z, m); + } + + /// + /// Create a Geometry LineString with a starting position + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + /// A Geometry LineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory LineString(double x, double y, double? z, double? m) + { + return LineString(CoordinateSystem.DefaultGeometry, x, y, z, m); + } + + /// + /// Create a Geometry LineString with a starting position + /// + /// The coordinate system + /// The X value + /// The Y value + /// A Geometry LineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory LineString(CoordinateSystem coordinateSystem, double x, double y) + { + return LineString(coordinateSystem, x, y, null, null); + } + + /// + /// Create a Geometry LineString with a starting position + /// + /// The X value + /// The Y value + /// A Geometry LineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryFactory LineString(double x, double y) + { + return LineString(CoordinateSystem.DefaultGeometry, x, y, null, null); + } + + /// + /// Create an empty Geometry LineString + /// + /// The CoordinateSystem + /// A Geometry LineString Factory + public static GeometryFactory LineString(CoordinateSystem coordinateSystem) + { + return new GeometryFactory(coordinateSystem).LineString(); + } + + /// + /// Create an empty Geometry LineString + /// + /// A Geometry LineString Factory + public static GeometryFactory LineString() + { + return LineString(CoordinateSystem.DefaultGeometry); + } + + /// + /// Create a Geometry MultiLineString + /// + /// The CoordinateSystem + /// A Geometry MultiLineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeometryFactory MultiLineString(CoordinateSystem coordinateSystem) + { + return new GeometryFactory(coordinateSystem).MultiLineString(); + } + + /// + /// Create a Geometry MultiLineString + /// + /// A Geometry MultiLineString Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeometryFactory MultiLineString() + { + return MultiLineString(CoordinateSystem.DefaultGeometry); + } + + #endregion + + #region Polygon + + /// + /// Create a Geometry Polygon + /// + /// The CoordinateSystem + /// A Geometry Polygon Factory + public static GeometryFactory Polygon(CoordinateSystem coordinateSystem) + { + return new GeometryFactory(coordinateSystem).Polygon(); + } + + /// + /// Create a Geometry Polygon + /// + /// A Geometry Polygon Factory + public static GeometryFactory Polygon() + { + return Polygon(CoordinateSystem.DefaultGeometry); + } + + /// + /// Create a Geometry MultiPolygon + /// + /// The CoordinateSystem + /// A Geometry MultiPolygon Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeometryFactory MultiPolygon(CoordinateSystem coordinateSystem) + { + return new GeometryFactory(coordinateSystem).MultiPolygon(); + } + + /// + /// Create a Geometry MultiPolygon + /// + /// A Geometry MultiPolygon Factory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public static GeometryFactory MultiPolygon() + { + return MultiPolygon(CoordinateSystem.DefaultGeometry); + } + + #endregion + + #region Collection + + /// + /// Create a Geometry Collection + /// + /// The CoordinateSystem + /// A Geometry Collection Factory + public static GeometryFactory Collection(CoordinateSystem coordinateSystem) + { + return new GeometryFactory(coordinateSystem).Collection(); + } + + /// + /// Create a Geometry Collection + /// + /// A Geometry Collection Factory + public static GeometryFactory Collection() + { + return Collection(CoordinateSystem.DefaultGeometry); + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactoryOfT.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactoryOfT.cs new file mode 100644 index 0000000..227a1fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryFactoryOfT.cs @@ -0,0 +1,310 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Geometry Spatial Factory + /// + /// The target type + public class GeometryFactory : SpatialFactory where T : Geometry + { + /// + /// The provider of the built type + /// + private IGeometryProvider provider; + + /// + /// The chain to build through + /// + private GeometryPipeline buildChain; + + /// + /// Initializes a new instance of the GeometryFactory class + /// + /// The coordinate system + internal GeometryFactory(CoordinateSystem coordinateSystem) + { + var builder = SpatialBuilder.Create(); + this.provider = builder; + this.buildChain = SpatialValidator.Create().ChainTo(builder).StartingLink; + this.buildChain.SetCoordinateSystem(coordinateSystem); + } + + /// + /// Cast a factory to the target type + /// + /// The factory + /// The built instance of the target type + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Operator used to build")] + public static implicit operator T(GeometryFactory factory) + { + if (factory != null) + { + return factory.Build(); + } + + return null; + } + + #region Public Construction Calls + + /// + /// Start a new Point + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory Point(double x, double y, double? z, double? m) + { + this.BeginGeo(SpatialType.Point); + this.LineTo(x, y, z, m); + return this; + } + + /// + /// Start a new Point + /// + /// The X value + /// The Y value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory Point(double x, double y) + { + return this.Point(x, y, null, null); + } + + /// + /// Start a new empty Point + /// + /// The current instance of GeometryFactory + public GeometryFactory Point() + { + this.BeginGeo(SpatialType.Point); + return this; + } + + /// + /// Start a new LineString + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory LineString(double x, double y, double? z, double? m) + { + this.BeginGeo(SpatialType.LineString); + this.LineTo(x, y, z, m); + return this; + } + + /// + /// Start a new LineString + /// + /// The X value + /// The Y value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory LineString(double x, double y) + { + return this.LineString(x, y, null, null); + } + + /// + /// Start a new empty LineString + /// + /// The current instance of GeometryFactory + public GeometryFactory LineString() + { + this.BeginGeo(SpatialType.LineString); + return this; + } + + /// + /// Start a new Polygon + /// + /// The current instance of GeometryFactory + public GeometryFactory Polygon() + { + this.BeginGeo(SpatialType.Polygon); + return this; + } + + /// + /// Start a new MultiPoint + /// + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public GeometryFactory MultiPoint() + { + this.BeginGeo(SpatialType.MultiPoint); + return this; + } + + /// + /// Start a new MultiLineString + /// + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public GeometryFactory MultiLineString() + { + this.BeginGeo(SpatialType.MultiLineString); + return this; + } + + /// + /// Start a new MultiPolygon + /// + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Multi is meaningful")] + public GeometryFactory MultiPolygon() + { + this.BeginGeo(SpatialType.MultiPolygon); + return this; + } + + /// + /// Start a new Collection + /// + /// The current instance of GeometryFactory + public GeometryFactory Collection() + { + this.BeginGeo(SpatialType.Collection); + return this; + } + + /// + /// Start a new Polygon Ring + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory Ring(double x, double y, double? z, double? m) + { + this.StartRing(x, y, z, m); + return this; + } + + /// + /// Start a new Polygon Ring + /// + /// The X value + /// The Y value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory Ring(double x, double y) + { + return this.Ring(x, y, null, null); + } + + /// + /// Add a new point in the current line figure + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory LineTo(double x, double y, double? z, double? m) + { + this.AddPos(x, y, z, m); + return this; + } + + /// + /// Add a new point in the current line figure + /// + /// The X value + /// The Y value + /// The current instance of GeometryFactory + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public GeometryFactory LineTo(double x, double y) + { + return this.LineTo(x, y, null, null); + } + + #endregion + + /// + /// Finish the current Geometry + /// + /// The constructed instance + public T Build() + { + this.Finish(); + return (T)this.provider.ConstructedGeometry; + } + + #region GeoDataPipeline overrides + + /// + /// Begin a new geometry + /// + /// The spatial type + protected override void BeginGeo(SpatialType type) + { + base.BeginGeo(type); + this.buildChain.BeginGeometry(type); + } + + /// + /// Begin drawing a figure + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + protected override void BeginFigure(double x, double y, double? z, double? m) + { + base.BeginFigure(x, y, z, m); + this.buildChain.BeginFigure(new GeometryPosition(x, y, z, m)); + } + + /// + /// Draw a point in the specified coordinate + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + protected override void AddLine(double x, double y, double? z, double? m) + { + base.AddLine(x, y, z, m); + this.buildChain.LineTo(new GeometryPosition(x, y, z, m)); + } + + /// + /// Ends the figure set on the current node + /// + protected override void EndFigure() + { + base.EndFigure(); + this.buildChain.EndFigure(); + } + + /// + /// Ends the current spatial object + /// + protected override void EndGeo() + { + base.EndGeo(); + this.buildChain.EndGeometry(); + } + + #endregion + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryHelperMethods.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryHelperMethods.cs new file mode 100644 index 0000000..6792345 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryHelperMethods.cs @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Helper methods for Geometry types + /// + internal static class GeometryHelperMethods + { + /// + /// Sends the current spatial object to the given pipeline with a figure that represents this LineString + /// + /// GeometryLineString instance for which the figure needs to be drawn. + /// The pipeline to populate to + internal static void SendFigure(this GeometryLineString GeometryLineString, GeometryPipeline pipeline) + { + Util.CheckArgumentNull(GeometryLineString, "GeometryLineString"); + for (int i = 0; i < GeometryLineString.Points.Count; ++i) + { + GeometryPoint currentPoint = GeometryLineString.Points[i]; + var position = new GeometryPosition(currentPoint.X, currentPoint.Y, currentPoint.Z, currentPoint.M); + if (i == 0) + { + pipeline.BeginFigure(position); + } + else + { + pipeline.LineTo(position); + } + } + + if (GeometryLineString.Points.Count > 0) + { + pipeline.EndFigure(); + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineString.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineString.cs new file mode 100644 index 0000000..2c568d7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineString.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the geometry line string. + public abstract class GeometryLineString : GeometryCurve + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeometryLineString(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the point list. + /// The point list. + public abstract ReadOnlyCollection Points { get; } + + /// Determines whether this instance and another specified geometry instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geometry to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeometryLineString other) + { + return this.BaseEquals(other) ?? this.Points.SequenceEqual(other.Points); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeometryLineString); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Points); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineStringImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineStringImplementation.cs new file mode 100644 index 0000000..bddc616 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryLineStringImplementation.cs @@ -0,0 +1,78 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geometry Line String + /// + internal class GeometryLineStringImplementation : GeometryLineString + { + /// + /// Points array + /// + private GeometryPoint[] points; + + /// + /// Constructor + /// + /// CoordinateSystem + /// The implementation that created this instance. + /// The point list + internal GeometryLineStringImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeometryPoint[] points) + : base(coordinateSystem, creator) + { + this.points = points ?? new GeometryPoint[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// The point list + internal GeometryLineStringImplementation(SpatialImplementation creator, params GeometryPoint[] points) + : this(CoordinateSystem.DefaultGeometry, creator, points) + { + } + + /// + /// Is LineString Empty + /// + public override bool IsEmpty + { + get + { + return this.points.Length == 0; + } + } + + /// + /// Point list + /// + public override ReadOnlyCollection Points + { + get + { + return new ReadOnlyCollection(this.points); + } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeometryPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeometry(SpatialType.LineString); + this.SendFigure(pipeline); + pipeline.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiCurve.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiCurve.cs new file mode 100644 index 0000000..eda8546 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiCurve.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the geometry multi-curve. + public abstract class GeometryMultiCurve : GeometryCollection + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeometryMultiCurve(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineString.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineString.cs new file mode 100644 index 0000000..8793d71 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineString.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the geometry multi-line string. + public abstract class GeometryMultiLineString : GeometryMultiCurve + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeometryMultiLineString(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets a collection of line strings. + /// A collection of line strings. + public abstract ReadOnlyCollection LineStrings { get; } + + /// Determines whether this instance and another specified geometry instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geometry to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeometryMultiLineString other) + { + return this.BaseEquals(other) ?? this.LineStrings.SequenceEqual(other.LineStrings); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeometryMultiLineString); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.LineStrings); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineStringImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineStringImplementation.cs new file mode 100644 index 0000000..0d2fa6d --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiLineStringImplementation.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geometry Multi-LineString + /// + internal class GeometryMultiLineStringImplementation : GeometryMultiLineString + { + /// + /// Line Strings + /// + private GeometryLineString[] lineStrings; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Line Strings + internal GeometryMultiLineStringImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeometryLineString[] lineStrings) + : base(coordinateSystem, creator) + { + this.lineStrings = lineStrings ?? new GeometryLineString[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Line Strings + internal GeometryMultiLineStringImplementation(SpatialImplementation creator, params GeometryLineString[] lineStrings) + : this(CoordinateSystem.DefaultGeometry, creator, lineStrings) + { + } + + /// + /// Is MultiLineString Empty + /// + public override bool IsEmpty + { + get + { + return this.lineStrings.Length == 0; + } + } + + /// + /// Geometry + /// + public override ReadOnlyCollection Geometries + { + get { return new ReadOnlyCollection(this.lineStrings); } + } + + /// + /// Line Strings + /// + public override ReadOnlyCollection LineStrings + { + get { return new ReadOnlyCollection(this.lineStrings); } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeometryPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeometry(SpatialType.MultiLineString); + + for (int i = 0; i < this.lineStrings.Length; ++i) + { + this.lineStrings[i].SendTo(pipeline); + } + + pipeline.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPoint.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPoint.cs new file mode 100644 index 0000000..c3dafe6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPoint.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the geometry multi-point. + public abstract class GeometryMultiPoint : GeometryCollection + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeometryMultiPoint(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets a collection of points. + /// A collection of points. + public abstract ReadOnlyCollection Points { get; } + + /// Determines whether this instance and another specified geometry instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geometry to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeometryMultiPoint other) + { + return this.BaseEquals(other) ?? this.Points.SequenceEqual(other.Points); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeometryMultiPoint); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Points); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPointImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPointImplementation.cs new file mode 100644 index 0000000..ad43224 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPointImplementation.cs @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geometry Multi-Point + /// + internal class GeometryMultiPointImplementation : GeometryMultiPoint + { + /// + /// Points + /// + private GeometryPoint[] points; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Points + internal GeometryMultiPointImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeometryPoint[] points) + : base(coordinateSystem, creator) + { + this.points = points ?? new GeometryPoint[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Points + internal GeometryMultiPointImplementation(SpatialImplementation creator, params GeometryPoint[] points) + : this(CoordinateSystem.DefaultGeometry, creator, points) + { + } + + /// + /// Is MultiPoint Empty + /// + public override bool IsEmpty + { + get + { + return this.points.Length == 0; + } + } + + /// + /// Geometry + /// + public override ReadOnlyCollection Geometries + { + get { return new ReadOnlyCollection(this.points); } + } + + /// + /// Points + /// + public override ReadOnlyCollection Points + { + get { return new ReadOnlyCollection(this.points); } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeometryPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeometry(SpatialType.MultiPoint); + + for (int i = 0; i < this.points.Length; ++i) + { + this.points[i].SendTo(pipeline); + } + + pipeline.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygon.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygon.cs new file mode 100644 index 0000000..1545fc2 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygon.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the geometry multi-polygon. + public abstract class GeometryMultiPolygon : GeometryMultiSurface + { + /// Initializes a new instance of the class. + /// The coordinate system of this instance. + /// The implementation that created this instance. + protected GeometryMultiPolygon(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets a collection of polygons. + /// A collection of polygons. + public abstract ReadOnlyCollection Polygons { get; } + + /// Determines whether this instance and another specified geometry instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geometry to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeometryMultiPolygon other) + { + return this.BaseEquals(other) ?? this.Polygons.SequenceEqual(other.Polygons); + } + + /// Determines whether this instance and the specified object have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The object to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeometryMultiPolygon); + } + + /// Gets the hash code. + /// The hash code. + public override int GetHashCode() + { + return Microsoft.Spatial.Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Polygons); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygonImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygonImplementation.cs new file mode 100644 index 0000000..cc8eb71 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiPolygonImplementation.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geometry Multi-Polygon + /// + internal class GeometryMultiPolygonImplementation : GeometryMultiPolygon + { + /// + /// Polygons + /// + private GeometryPolygon[] polygons; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// Polygons + internal GeometryMultiPolygonImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeometryPolygon[] polygons) + : base(coordinateSystem, creator) + { + this.polygons = polygons; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// Polygons + internal GeometryMultiPolygonImplementation(SpatialImplementation creator, params GeometryPolygon[] polygons) + : this(CoordinateSystem.DefaultGeometry, creator, polygons) + { + } + + /// + /// Is MultiPolygon Empty + /// + public override bool IsEmpty + { + get + { + return this.polygons.Length == 0; + } + } + + /// + /// Geometry + /// + public override ReadOnlyCollection Geometries + { + get { return new ReadOnlyCollection(this.polygons); } + } + + /// + /// Polygons + /// + public override ReadOnlyCollection Polygons + { + get { return new ReadOnlyCollection(this.polygons); } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeometryPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeometry(SpatialType.MultiPolygon); + for (int i = 0; i < this.polygons.Length; ++i) + { + this.polygons[i].SendTo(pipeline); + } + + pipeline.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiSurface.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiSurface.cs new file mode 100644 index 0000000..ef08430 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryMultiSurface.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the geometry multi-surface. + public abstract class GeometryMultiSurface : GeometryCollection + { + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + internal GeometryMultiSurface(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryOperationsExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryOperationsExtensions.cs new file mode 100644 index 0000000..d53a25a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryOperationsExtensions.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Linq; + + /// + /// Extension methods for the Geography operations + /// + public static class GeometryOperationsExtensions + { + /// Determines the distance of the geometry. + /// The operation result. + /// The first operand. + /// The second operand. + public static double? Distance(this Geometry operand1, Geometry operand2) + { + return OperationsFor(operand1, operand2).IfValidReturningNullable(ops => ops.Distance(operand1, operand2)); + } + + /// Determines the Length of the geometry LineString. + /// The operation result. + /// The LineString operand. + public static double? Length(this Geometry operand) + { + return OperationsFor(operand).IfValidReturningNullable(ops => ops.Length(operand)); + } + + /// Determines if geometry point and polygon will intersect. + /// The operation result. + /// The first operand, point. + /// The second operand, polygon. + public static bool? Intersects(this Geometry operand1, Geometry operand2) + { + return OperationsFor(operand1, operand2).IfValidReturningNullable(ops => ops.Intersects(operand1, operand2)); + } + + /// + /// Finds the ops instance registered for the operands. + /// + /// The operands. + /// The ops value, or null if any operand is null + private static SpatialOperations OperationsFor(params Geometry[] operands) + { + if (operands.Any(operand => operand == null)) + { + return null; + } + + return operands[0].Creator.VerifyAndGetNonNullOperations(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPipeline.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPipeline.cs new file mode 100644 index 0000000..d528f30 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPipeline.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the pipeline of geometry. + public abstract class GeometryPipeline + { + /// Begins drawing a spatial object. + /// The spatial type of the object. + public abstract void BeginGeometry(SpatialType type); + + /// Begins drawing a figure. + /// The position of the figure. + public abstract void BeginFigure(GeometryPosition position); + + /// Draws a point in the specified coordinate. + /// The position of the line. + public abstract void LineTo(GeometryPosition position); + + /// Ends the current figure. + public abstract void EndFigure(); + + /// Ends the current spatial object. + public abstract void EndGeometry(); + + /// Sets the coordinate system. + /// The coordinate system to set. + public abstract void SetCoordinateSystem(CoordinateSystem coordinateSystem); + + /// Resets the pipeline. + public abstract void Reset(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPoint.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPoint.cs new file mode 100644 index 0000000..0b76fad --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPoint.cs @@ -0,0 +1,119 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the Geometry Point. + public abstract class GeometryPoint : Geometry + { + /// Initializes a new instance of the class. Empty Point constructor. + /// The CoordinateSystem. + /// The implementation that created this instance. + protected GeometryPoint(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the Latitude. + /// The Latitude. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "X is meaningful")] + public abstract double X { get; } + + /// Gets the Longitude. + /// The Longitude. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Y is meaningful")] + public abstract double Y { get; } + + /// Gets the Nullable Z. + /// The Nullable Z. + /// Z is the altitude portion of position. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z is meaningful")] + public abstract double? Z { get; } + + /// Gets the Nullable M. + /// The Nullable M. + /// M is the arbitrary measure associated with a position. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "m is meaningful")] + public abstract double? M { get; } + + /// Creates the specified latitude. + /// The GeographyPoint that was created. + /// The x dimension. + /// The y dimension. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x and y are meaningful")] + public static GeometryPoint Create(double x, double y) + { + return Create(CoordinateSystem.DefaultGeometry, x, y, null, null); + } + + /// Creates the specified latitude. + /// The GeographyPoint that was created. + /// The x dimension. + /// The y dimension. + /// The z dimension. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y and z are meaningful")] + public static GeometryPoint Create(double x, double y, double? z) + { + return Create(CoordinateSystem.DefaultGeometry, x, y, z, null); + } + + /// Creates the specified latitude. + /// The GeographyPoint that was created. + /// The x dimension. + /// The y dimension. + /// The z dimension. + /// The m dimension. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryPoint Create(double x, double y, double? z, double? m) + { + return Create(CoordinateSystem.DefaultGeometry, x, y, z, m); + } + + /// Creates the specified latitude. + /// The GeographyPoint that was created. + /// The coordinate system to use. + /// The x dimension. + /// The y dimension. + /// The z dimension. + /// The m dimension. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + public static GeometryPoint Create(CoordinateSystem coordinateSystem, double x, double y, double? z, double? m) + { + var builder = SpatialBuilder.Create(); + var pipeline = builder.GeometryPipeline; + pipeline.SetCoordinateSystem(coordinateSystem); + pipeline.BeginGeometry(SpatialType.Point); + pipeline.BeginFigure(new GeometryPosition(x, y, z, m)); + pipeline.EndFigure(); + pipeline.EndGeometry(); + return (GeometryPoint)builder.ConstructedGeometry; + } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeometryPoint other) + { + return this.BaseEquals(other) ?? this.X == other.X && this.Y == other.Y && this.Z == other.Z && this.M == other.M; + } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeometryPoint); + } + + /// Gets the Hashcode. + /// The hashcode. + public override int GetHashCode() + { + return Geography.ComputeHashCodeFor(this.CoordinateSystem, new[] { this.IsEmpty ? 0 : this.X, this.IsEmpty ? 0 : this.Y, this.Z ?? 0, this.M ?? 0 }); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPointImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPointImplementation.cs new file mode 100644 index 0000000..6c893d7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPointImplementation.cs @@ -0,0 +1,158 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// Geometry Point + /// + internal class GeometryPointImplementation : GeometryPoint + { + /// + /// Latitude + /// + private double x; + + /// + /// Longitude + /// + private double y; + + /// + /// Z + /// + private double? z; + + /// + /// M + /// + private double? m; + + /// + /// Empty Point constructor + /// + /// CoordinateSystem + /// The implementation that created this instance. + internal GeometryPointImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + this.x = double.NaN; + this.y = double.NaN; + } + + /// + /// Point constructor + /// + /// CoordinateSystem + /// The implementation that created this instance. + /// latitude + /// longitude + /// Z + /// M + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "zvalue and mvalue are spelled correctly")] + internal GeometryPointImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, double x, double y, double? z, double? m) + : base(coordinateSystem, creator) + { + if (double.IsNaN(x) || double.IsInfinity(x)) + { + throw new ArgumentException(Strings.InvalidPointCoordinate(x, "x")); + } + + if (double.IsNaN(y) || double.IsInfinity(y)) + { + throw new ArgumentException(Strings.InvalidPointCoordinate(y, "y")); + } + + this.x = x; + this.y = y; + this.z = z; + this.m = m; + } + + /// + /// Latitude + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "X is meaningful")] + public override double X + { + get + { + if (this.IsEmpty) + { + throw new NotSupportedException(Strings.Point_AccessCoordinateWhenEmpty); + } + + return this.x; + } + } + + /// + /// Longitude + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "Y is meaningful")] + public override double Y + { + get + { + if (this.IsEmpty) + { + throw new NotSupportedException(Strings.Point_AccessCoordinateWhenEmpty); + } + + return this.y; + } + } + + /// + /// Is Point Empty + /// + public override bool IsEmpty + { + get + { + return double.IsNaN(this.x); + } + } + + /// + /// Nullable Z + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + public override double? Z + { + get { return this.z; } + } + + /// + /// Nullable M + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + public override double? M + { + get { return this.m; } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeometryPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeometry(SpatialType.Point); + if (!this.IsEmpty) + { + pipeline.BeginFigure(new GeometryPosition(this.x, this.y, this.z, this.m)); + pipeline.EndFigure(); + } + + pipeline.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygon.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygon.cs new file mode 100644 index 0000000..b0abd34 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygon.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + using System.Linq; + + /// Represents the Geometry polygon. + public abstract class GeometryPolygon : GeometrySurface + { + /// Initializes a new instance of the class. + /// The CoordinateSystem. + /// The implementation that created this instance. + protected GeometryPolygon(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + + /// Gets the set of rings. + public abstract ReadOnlyCollection Rings { get; } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "null is a valid value")] + public bool Equals(GeometryPolygon other) + { + return this.BaseEquals(other) ?? this.Rings.SequenceEqual(other.Rings); + } + + /// Determines whether this instance and another specified geography instance have the same value. + /// true if the value of the value parameter is the same as this instance; otherwise, false. + /// The geography to compare to this instance. + public override bool Equals(object obj) + { + return this.Equals(obj as GeometryPolygon); + } + + /// Indicates the Get Hashcode. + /// The hashcode. + public override int GetHashCode() + { + return Geography.ComputeHashCodeFor(this.CoordinateSystem, this.Rings); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygonImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygonImplementation.cs new file mode 100644 index 0000000..d5aa348 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPolygonImplementation.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.ObjectModel; + + /// + /// Geometry polygon + /// + internal class GeometryPolygonImplementation : GeometryPolygon + { + /// + /// Rings + /// + private GeometryLineString[] rings; + + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + /// The rings of this polygon + internal GeometryPolygonImplementation(CoordinateSystem coordinateSystem, SpatialImplementation creator, params GeometryLineString[] rings) + : base(coordinateSystem, creator) + { + this.rings = rings ?? new GeometryLineString[0]; + } + + /// + /// Constructor + /// + /// The implementation that created this instance. + /// The rings of this polygon + internal GeometryPolygonImplementation(SpatialImplementation creator, params GeometryLineString[] rings) + : this(CoordinateSystem.DefaultGeometry, creator, rings) + { + } + + /// + /// Is Polygon Empty + /// + public override bool IsEmpty + { + get + { + return this.rings.Length == 0; + } + } + + /// + /// Set of rings + /// + public override ReadOnlyCollection Rings + { + get { return new ReadOnlyCollection(this.rings); } + } + + /// + /// Sends the current spatial object to the given pipeline + /// + /// The spatial pipeline + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "base does the validation")] + public override void SendTo(GeometryPipeline pipeline) + { + base.SendTo(pipeline); + pipeline.BeginGeometry(SpatialType.Polygon); + + for (int i = 0; i < this.rings.Length; ++i) + { + this.rings[i].SendFigure(pipeline); + } + + pipeline.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPosition.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPosition.cs new file mode 100644 index 0000000..5eb653e --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometryPosition.cs @@ -0,0 +1,158 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Represents one position in the Geometry coordinate system + /// + public class GeometryPosition : IEquatable + { + /// arbitrary measure associated with a position + private readonly double? m; + + /// x portion of position + private readonly double x; + + /// y portion of position + private readonly double y; + + /// altitude portion of position + private readonly double? z; + + /// Creates a new instance of the from components. + /// The X portion of position. + /// The Y portion of position. + /// The altitude portion of position. + /// The arbitrary measure associated with a position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public GeometryPosition(double x, double y, double? z, double? m) + { + this.x = x; + this.y = y; + this.z = z; + this.m = m; + } + + /// Creates a new instance of the from components. + /// The X portion of position. + /// The Y portion of position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y make sense in context")] + public GeometryPosition(double x, double y) + : this(x, y, null, null) + { + } + + /// Gets the arbitrary measure associated with a position. + /// The arbitrary measure associated with a position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public double? M + { + get { return m; } + } + + /// Gets the X portion of position. + /// The X portion of position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public double X + { + get { return x; } + } + + /// Gets the Y portion of position. + /// The Y portion of position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public double Y + { + get { return y; } + } + + /// Gets the altitude portion of position. + /// The altitude portion of position. + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m make sense in context")] + public double? Z + { + get { return z; } + } + + /// Performs the equality comparison. + /// true if each pair of coordinates is equal; otherwise, false. + /// The first position. + /// The second position. + public static bool operator ==(GeometryPosition left, GeometryPosition right) + { + if (ReferenceEquals(left, null)) + { + return ReferenceEquals(right, null); + } + else if (ReferenceEquals(right, null)) + { + return false; + } + + return left.Equals(right); + } + + /// Performs the inequality comparison. + /// true if left is not equal to right; otherwise, false. + /// The first position. + /// The other position. + public static bool operator !=(GeometryPosition left, GeometryPosition right) + { + return !(left == right); + } + + /// Performs the equality comparison on an object. + /// true if each pair of coordinates is equal; otherwise, false. + /// The object for comparison. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (obj.GetType() != typeof(GeometryPosition)) + { + return false; + } + + return Equals((GeometryPosition)obj); + } + + /// Performs the equality comparison on a spatial geometry position. + /// true if each pair of coordinates is equal; otherwise, false. + /// The other position. + public bool Equals(GeometryPosition other) + { + return other != null && other.x.Equals(x) && other.y.Equals(y) && other.z.Equals(z) && other.m.Equals(m); + } + + /// Computes a hash code. + /// A hash code. + public override int GetHashCode() + { + unchecked + { + int result = x.GetHashCode(); + result = (result * 397) ^ y.GetHashCode(); + result = (result * 397) ^ (z.HasValue ? z.Value.GetHashCode() : 0); + result = (result * 397) ^ (m.HasValue ? m.Value.GetHashCode() : 0); + return result; + } + } + + /// Formats this instance to a readable string. + /// The string representation of this instance. + public override string ToString() + { + return String.Format(System.Globalization.CultureInfo.InvariantCulture, "GeometryPosition({0}, {1}, {2}, {3})", this.x, this.y, this.z.HasValue ? this.z.ToString() : "null", this.m.HasValue ? this.m.ToString() : "null"); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometrySurface.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometrySurface.cs new file mode 100644 index 0000000..540825a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GeometrySurface.cs @@ -0,0 +1,22 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents a geometry surface. + public abstract class GeometrySurface : Geometry + { + /// + /// Constructor + /// + /// The CoordinateSystem + /// The implementation that created this instance. + internal GeometrySurface(CoordinateSystem coordinateSystem, SpatialImplementation creator) + : base(coordinateSystem, creator) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GlobalSuppressions.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GlobalSuppressions.cs new file mode 100644 index 0000000..828dfd6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GlobalSuppressions.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +[module: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Spatial")] + +// Multi* +[module: SuppressMessage("Microsoft.Naming", "CA1702", Scope = "member", Target = "Microsoft.Spatial.SpatialType.MultiLineString")] +[module: SuppressMessage("Microsoft.Naming", "CA1702", Scope = "member", Target = "Microsoft.Spatial.SpatialType.MultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiLineString")] +[module: SuppressMessage("Microsoft.Naming", "CA1702", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiPolygon")] +[module: SuppressMessage("Microsoft.Naming", "CA1702", Scope = "type", Target = "Microsoft.Spatial.GeometryMultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702", Scope = "type", Target = "Microsoft.Spatial.GeometryMultiLineString")] + +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeometryFactory.#MultiPoint(Microsoft.Spatial.CoordinateSystem)", MessageId = "MultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeometryFactory.#MultiLineString(Microsoft.Spatial.CoordinateSystem)", MessageId = "MultiLine")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeometryFactory.#MultiPoint()", MessageId = "MultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeometryFactory.#MultiLineString()", MessageId = "MultiLine")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeometryFactory`1.#MultiPoint()", MessageId = "MultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeometryFactory`1.#MultiLineString()", MessageId = "MultiLine")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory`1.#MultiPoint()", MessageId = "MultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory`1.#MultiLineString()", MessageId = "MultiLine")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory.#MultiPoint(Microsoft.Spatial.CoordinateSystem)", MessageId = "MultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory.#MultiLineString(Microsoft.Spatial.CoordinateSystem)", MessageId = "MultiLine")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory.#MultiPoint()", MessageId = "MultiPoint")] +[module: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory.#MultiLineString()", MessageId = "MultiLine")] + +// *Collection +[module: SuppressMessage("Microsoft.Naming", "CA1711", Scope = "type", Target = "Microsoft.Spatial.GeographyCollection")] +[module: SuppressMessage("Microsoft.Naming", "CA1711", Scope = "type", Target = "Microsoft.Spatial.GeometryCollection")] + +// x -> latitude in parameter +[module: SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory`1.#BeginFigure(System.Double,System.Double,System.Nullable`1,System.Nullable`1)", MessageId = "0#")] +[module: SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory`1.#BeginFigure(System.Double,System.Double,System.Nullable`1,System.Nullable`1)", MessageId = "1#")] +[module: SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory`1.#AddLine(System.Double,System.Double,System.Nullable`1,System.Nullable`1)", MessageId = "0#")] +[module: SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", Scope = "member", Target = "Microsoft.Spatial.GeographyFactory`1.#AddLine(System.Double,System.Double,System.Nullable`1,System.Nullable`1)", MessageId = "1#")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.Spatial.CoordinateSystem.#DefaultGeometry", Justification = "DefaultGeometry object is not mutable externally.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Scope = "member", Target = "Microsoft.Spatial.CoordinateSystem.#DefaultGeography", Justification = "DefaultGeography object is not mutable externally.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiPoint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiSurface")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeometryMultiLineString")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeometryMultiPolygon")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.Spatial.SpatialType.#MultiLineString")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.Spatial.SpatialType.#MultiPolygon")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "member", Target = "Microsoft.Spatial.SpatialType.#MultiPoint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeometryMultiSurface")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeometryMultiPoint")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeometryMultiCurve")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiPolygon")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiLineString")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Scope = "type", Target = "Microsoft.Spatial.GeographyMultiCurve")] diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlConstants.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlConstants.cs new file mode 100644 index 0000000..8e091be --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlConstants.cs @@ -0,0 +1,194 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Gml Constants + /// + internal class GmlConstants + { + /// + /// Gml Namespace + /// + internal const string GmlNamespace = "http://www.opengis.net/gml"; + + /// + /// FullGlobe namespace + /// + internal const string FullGlobeNamespace = "http://schemas.microsoft.com/sqlserver/2011/geography"; + + /// + /// Gml Prefix + /// + internal const string GmlPrefix = "gml"; + + /// + /// System reference attribute name + /// + internal const string SrsName = "srsName"; + + /// + /// gml:id attribute name + /// + internal const string IdName = "id"; + + /// + /// System Reference Prefix + /// + internal const string SrsPrefix = "http://www.opengis.net/def/crs/EPSG/0/"; + + /// + /// Gml representation of a point + /// + internal const string Position = "pos"; + + /// + /// The Gml:name element name + /// + internal const string Name = "name"; + + /// + /// the Gml:Description element name + /// + internal const string Description = "description"; + + /// + /// the metadata property element name + /// + internal const string MetadataProperty = "metaDataProperty"; + + /// + /// Description Reference element name + /// + internal const string DescriptionReference = "descriptionReference"; + + /// + /// identifier element name + /// + internal const string IdentifierElement = "identifier"; + + /// + /// Gml representation of a point + /// + internal const string PointProperty = "pointProperty"; + + /// + /// Gml representation of a point array + /// + internal const string PositionList = "posList"; + + /// + /// Gml Point + /// + internal const string Point = "Point"; + + /// + /// Gml representation of a linestring + /// + internal const string LineString = "LineString"; + + /// + /// Gml Polygon + /// + internal const string Polygon = "Polygon"; + + /// + /// Gml MultiPoint + /// + internal const string MultiPoint = "MultiPoint"; + + /// + /// Gml MultiLineString + /// + internal const string MultiLineString = "MultiCurve"; + + /// + /// Gml MultiPolygon + /// + internal const string MultiPolygon = "MultiSurface"; + + /// + /// Gml Collection + /// + internal const string Collection = "MultiGeometry"; + + /// + /// Gml FullGlobe + /// + internal const string FullGlobe = "FullGlobe"; + + /// + /// Gml Polygon exterior ring + /// + internal const string ExteriorRing = "exterior"; + + /// + /// Gml Polygon interior ring + /// + internal const string InteriorRing = "interior"; + + /// + /// Gml Ring + /// + internal const string LinearRing = "LinearRing"; + + /// + /// Member Tag for MultiPoint + /// + internal const string PointMember = "pointMember"; + + /// + /// Members Tag for MultiPoint + /// + internal const string PointMembers = "pointMembers"; + + /// + /// Member Tag for MultiLineString + /// + internal const string LineStringMember = "curveMember"; + + /// + /// Members Tag for MultiLineString + /// + internal const string LineStringMembers = "curveMembers"; + + /// + /// Member Tag for MultiPolygon + /// + internal const string PolygonMember = "surfaceMember"; + + /// + /// Members Tag for MultiPolygon + /// + internal const string PolygonMembers = "surfaceMembers"; + + /// + /// Member Tag for Collection + /// + internal const string CollectionMember = "geometryMember"; + + /// + /// Members Tag for Collection + /// + internal const string CollectionMembers = "geometryMembers"; + + /// + /// Attribute name for Axis Labels + /// + internal const string AxisLabels = "axisLabels"; + + /// + /// Attribute name for unit of measure labels + /// + internal const string UomLabels = "uomLabels"; + + /// + /// Attribute name for count + /// + internal const string Count = "count"; + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatter.cs new file mode 100644 index 0000000..78eedbe --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatter.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Xml; + + /// + /// The object to move spatial types to and from the GML format + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gml", Justification = "Gml is the accepted name in the industry")] + public abstract class GmlFormatter : SpatialFormatter + { + /// Initializes a new instance of the class. + /// The implementation that created this instance. + protected GmlFormatter(SpatialImplementation creator) : base(creator) + { + } + + /// Creates the implementation of the formatter. + /// The created GmlFormatter implementation. + public static GmlFormatter Create() + { + return SpatialImplementation.CurrentImplementation.CreateGmlFormatter(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatterImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatterImplementation.cs new file mode 100644 index 0000000..3d40c18 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlFormatterImplementation.cs @@ -0,0 +1,56 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Xml; + + /// + /// The object to move spatial types to and from the GML format + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gml", Justification = "Gml is the accepted name in the industry")] + internal class GmlFormatterImplementation : GmlFormatter + { + /// + /// Initializes a new instance of the class. + /// + /// The implementation that created this instance. + internal GmlFormatterImplementation(SpatialImplementation creator) + : base(creator) + { + } + + /// + /// Create the writer + /// + /// The object that should be the target of the ISpatialPipeline writer. + /// A writer that implements ISpatialPipeline. + public override SpatialPipeline CreateWriter(XmlWriter target) + { + return new ForwardingSegment(new GmlWriter(target)); + } + + /// + /// Reads the geography. + /// + /// The reader stream. + /// The pipeline. + protected override void ReadGeography(XmlReader readerStream, SpatialPipeline pipeline) + { + new GmlReader(pipeline).ReadGeography(readerStream); + } + + /// + /// Reads the geometry. + /// + /// The reader stream. + /// The pipeline. + protected override void ReadGeometry(XmlReader readerStream, SpatialPipeline pipeline) + { + new GmlReader(pipeline).ReadGeometry(readerStream); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlReader.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlReader.cs new file mode 100644 index 0000000..53ff6ec --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlReader.cs @@ -0,0 +1,834 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Xml; + + /// + /// Gml Reader + /// + internal class GmlReader : SpatialReader + { + /// + /// Creates a reader that that will send messages to the destination during read. + /// + /// The instance to message to during read. + public GmlReader(SpatialPipeline destination) + : base(destination) + { + } + + /// + /// Parses some serialized format that represents a geography value, passing the result down the pipeline. + /// + /// The XmlReader instance to read from. + protected override void ReadGeographyImplementation(XmlReader input) + { + new Parser(input, new TypeWashedToGeographyLatLongPipeline(Destination)).Read(); + } + + /// + /// Parses some serialized format that represents a geometry value, passing the result down the pipeline. + /// + /// The XmlReader instance to read from. + protected override void ReadGeometryImplementation(XmlReader input) + { + new Parser(input, new TypeWashedToGeometryPipeline(Destination)).Read(); + } + + /// + /// This class parses the xml and calls the pipeline based on what is parsed + /// + private class Parser + { + /// + /// Delimiters used in position arrays. As per Xml spec white space characters is: #x20 | #x9 | #xD | #xA + /// + private static readonly char[] coordinateDelimiter = { ' ', '\t', '\r', '\n' }; + + /// + /// List of known gml elements that can be ignored by the parser + /// + private static readonly Dictionary skippableElements = new Dictionary(StringComparer.Ordinal) + { + { GmlConstants.Name, GmlConstants.Name }, + { GmlConstants.Description, GmlConstants.Description }, + { GmlConstants.MetadataProperty, GmlConstants.MetadataProperty }, + { GmlConstants.DescriptionReference, GmlConstants.DescriptionReference }, + { GmlConstants.IdentifierElement, GmlConstants.IdentifierElement } + }; + + #region Atomized Constants + + /// + /// Atomized gml namespace + /// + private readonly string gmlNamespace; + + /// + /// Atomized Full Globe namespace + /// + private readonly string fullGlobeNamespace; + + #endregion + + /// + /// Output pipeline + /// + private readonly TypeWashedPipeline pipeline; + + /// + /// Input reader + /// + private readonly XmlReader reader; + + /// + /// Number of points in the current figure + /// + private int points; // number of points in current figure + + /// + /// Constructor + /// + /// Input Reader + /// Output pipeline + internal Parser(XmlReader reader, TypeWashedPipeline pipeline) + { + Debug.Assert(reader != null && pipeline != null, "reader or pipeline is null"); + this.reader = reader; + this.pipeline = pipeline; + + var nametable = this.reader.NameTable; + this.gmlNamespace = nametable.Add(GmlConstants.GmlNamespace); + this.fullGlobeNamespace = nametable.Add(GmlConstants.FullGlobeNamespace); + } + + /// + /// Read + /// + public void Read() + { + this.ParseGmlGeometry(true); + } + + /// + /// Parses the top level element in the document + /// + /// Whether coordinte system is expected + private void ParseGmlGeometry(bool readCoordinateSystem) + { + if (!this.reader.IsStartElement()) + { + throw new FormatException(Strings.GmlReader_ExpectReaderAtElement); + } + + // handle SRID here + if (object.ReferenceEquals(this.reader.NamespaceURI, this.gmlNamespace)) + { + this.ReadAttributes(readCoordinateSystem); + + switch (this.reader.LocalName) + { + case GmlConstants.Point: + this.ParseGmlPointShape(); + break; + case GmlConstants.LineString: + this.ParseGmlLineStringShape(); + break; + case GmlConstants.Polygon: + this.ParseGmlPolygonShape(); + break; + case GmlConstants.MultiPoint: + this.ParseGmlMultiPointShape(); + break; + case GmlConstants.MultiLineString: + this.ParseGmlMultiCurveShape(); + break; + case GmlConstants.MultiPolygon: + this.ParseGmlMultiSurfaceShape(); + break; + case GmlConstants.Collection: + this.ParseGmlMultiGeometryShape(); + break; + default: + throw new FormatException(Strings.GmlReader_InvalidSpatialType(this.reader.LocalName)); + } + } + else if (object.ReferenceEquals(this.reader.NamespaceURI, this.fullGlobeNamespace) && this.reader.LocalName.Equals(GmlConstants.FullGlobe)) + { + this.ReadAttributes(readCoordinateSystem); + this.ParseGmlFullGlobeElement(); + } + else + { + throw new FormatException(Strings.GmlReader_ExpectReaderAtElement); + } + } + + /// + /// Set the CoordinateSystem + /// + /// Should we allow CRS attributes + private void ReadAttributes(bool expectSrsName) + { + bool crsSet = false; + + // skip whitespace + this.reader.MoveToContent(); + if (this.reader.MoveToFirstAttribute()) + { + do + { + if (this.reader.NamespaceURI.Equals(XmlConstants.XmlnsNamespace, StringComparison.Ordinal)) + { + // xmlns namespace + continue; + } + + string attributeName = this.reader.LocalName; + switch (attributeName) + { + case GmlConstants.AxisLabels: + case GmlConstants.UomLabels: + case GmlConstants.Count: + case GmlConstants.IdName: + break; + case GmlConstants.SrsName: + if (!expectSrsName) + { + this.reader.MoveToElement(); + throw new FormatException(Strings.GmlReader_InvalidAttribute(attributeName, this.reader.Name)); + } + + // srsName="http://www.opengis.net/def/crs/EPSG/0/SRID" + string attributeValue = this.reader.Value; + if (attributeValue.StartsWith(GmlConstants.SrsPrefix, StringComparison.Ordinal)) + { + int id = XmlConvert.ToInt32(attributeValue.Substring(GmlConstants.SrsPrefix.Length)); + this.pipeline.SetCoordinateSystem(id); + crsSet = true; + } + else + { + throw new FormatException(Strings.GmlReader_InvalidSrsName(GmlConstants.SrsPrefix)); + } + + break; + default: + this.reader.MoveToElement(); + throw new FormatException(Strings.GmlReader_InvalidAttribute(attributeName, this.reader.Name)); + } + } + while (this.reader.MoveToNextAttribute()); + + this.reader.MoveToElement(); + } + + if (expectSrsName && !crsSet) + { + this.pipeline.SetCoordinateSystem(null); + } + } + + /// + /// creates a shape and parses the element. + /// This is used to parse a top level Point element, as opposed to + /// a point which is embeded in a linestring or a polygon. + /// + private void ParseGmlPointShape() + { + this.pipeline.BeginGeo(SpatialType.Point); + this.PrepareFigure(); + this.ParseGmlPointElement(true /* allowEmpty */); + this.EndFigure(); + this.pipeline.EndGeo(); + } + + /// + /// creates a shape and parses the element for top level LineString shapes + /// + private void ParseGmlLineStringShape() + { + this.pipeline.BeginGeo(SpatialType.LineString); + this.PrepareFigure(); + this.ParseGmlLineString(); + this.EndFigure(); + this.pipeline.EndGeo(); + } + + /// + /// Creates a shape and parses the Polygon element. + /// + private void ParseGmlPolygonShape() + { + // GmlPolygonElement := + // + // GmlExteriorLinearRingElement GmlInteriorLinearRingElement* + // + // | + this.pipeline.BeginGeo(SpatialType.Polygon); + + if (this.ReadStartOrEmptyElement(GmlConstants.Polygon)) + { + this.ReadSkippableElements(); + if (!this.IsEndElement(GmlConstants.Polygon)) + { + this.PrepareFigure(); + this.ParseGmlRingElement(GmlConstants.ExteriorRing); + this.EndFigure(); + + this.ReadSkippableElements(); + while (this.IsStartElement(GmlConstants.InteriorRing)) + { + this.PrepareFigure(); + this.ParseGmlRingElement(GmlConstants.InteriorRing); + this.EndFigure(); + this.ReadSkippableElements(); + } + } + + this.ReadSkippableElements(); + this.ReadEndElement(); + } + + this.pipeline.EndGeo(); + } + + /// + /// Creates a shape and parses the MultiPoint element. + /// + private void ParseGmlMultiPointShape() + { + this.pipeline.BeginGeo(SpatialType.MultiPoint); + this.ParseMultiItemElement(GmlConstants.MultiPoint, GmlConstants.PointMember, GmlConstants.PointMembers, this.ParseGmlPointShape); + this.pipeline.EndGeo(); + } + + /// + /// Creates a shape and parses the MultiLineString(Gml MultiCurve) element. + /// + private void ParseGmlMultiCurveShape() + { + this.pipeline.BeginGeo(SpatialType.MultiLineString); + this.ParseMultiItemElement(GmlConstants.MultiLineString, GmlConstants.LineStringMember, GmlConstants.LineStringMembers, this.ParseGmlLineStringShape); + this.pipeline.EndGeo(); + } + + /// + /// Creates a shape and parses the MultiPolygon(Gml MultiSurface) element. + /// + private void ParseGmlMultiSurfaceShape() + { + this.pipeline.BeginGeo(SpatialType.MultiPolygon); + this.ParseMultiItemElement(GmlConstants.MultiPolygon, GmlConstants.PolygonMember, GmlConstants.PolygonMembers, this.ParseGmlPolygonShape); + this.pipeline.EndGeo(); + } + + /// + /// Creates a shape and parses the Collection(Gml MultiGeometry) element. + /// + private void ParseGmlMultiGeometryShape() + { + this.pipeline.BeginGeo(SpatialType.Collection); + this.ParseMultiItemElement(GmlConstants.Collection, GmlConstants.CollectionMember, GmlConstants.CollectionMembers, () => this.ParseGmlGeometry(false)); + this.pipeline.EndGeo(); + } + + /// + /// Creates a shape and parses the FullGlobe element + /// + private void ParseGmlFullGlobeElement() + { + // FullGlobeElement := + // + // + this.pipeline.BeginGeo(SpatialType.FullGlobe); + if (this.ReadStartOrEmptyElement(GmlConstants.FullGlobe)) + { + if (this.IsEndElement(GmlConstants.FullGlobe)) + { + this.ReadEndElement(); + } + } + + this.pipeline.EndGeo(); + } + + /// + /// Parses a simple point. + /// + /// Allow Empty Point + private void ParseGmlPointElement(bool allowEmpty) + { + if (this.ReadStartOrEmptyElement(GmlConstants.Point)) + { + // + // foo + // 1 2 3 4 + // bar + // + this.ReadSkippableElements(); + this.ParseGmlPosElement(allowEmpty); + this.ReadSkippableElements(); + this.ReadEndElement(); + } + } + + /// + /// Parses the GmlLineStringElement. + /// + private void ParseGmlLineString() + { + // Linestrings are not allowed to contain empty points. + // + // GmlLineStringElement := + // + // PosList + // | GmlPosListElement + // + if (this.ReadStartOrEmptyElement(GmlConstants.LineString)) + { + this.ReadSkippableElements(); + if (this.IsPosListStart()) + { + this.ParsePosList(false /* allowEmpty */); + } + else + { + this.ParseGmlPosListElement(true /* allowEmpty */); + } + + this.ReadSkippableElements(); + this.ReadEndElement(); + } + } + + /// + /// Parses the GmlExteriorLinearRingElement + /// + /// The type or ring + private void ParseGmlRingElement(string ringTag) + { + // GmlExteriorLinearRingElement := + // + // GmlLinearRingElement + // + // | + if (this.ReadStartOrEmptyElement(ringTag)) + { + if (!this.IsEndElement(ringTag)) + { + this.ParseGmlLinearRingElement(); + } + + this.ReadEndElement(); + } + } + + /// + /// ParseGmlLinearRingElement parses the GmlLinearRingElement + /// + private void ParseGmlLinearRingElement() + { + // GmlLinearRingElement := + // + // PosList + // | GmlPosListElement + // + // | + if (this.ReadStartOrEmptyElement(GmlConstants.LinearRing)) + { + if (this.IsEndElement(GmlConstants.LinearRing)) + { + // Empty rings are not allowed + throw new FormatException(Strings.GmlReader_EmptyRingsNotAllowed); + } + else + { + if (this.IsPosListStart()) + { + this.ParsePosList(false /* allowEmpty */); + } + else + { + this.ParseGmlPosListElement(false /* allowEmpty */); + } + } + + this.ReadEndElement(); + } + } + + /// + /// Common function for all item collections, since they are all parsed exactly the same way + /// + /// The wrapping header tag + /// The member tag + /// The members tag + /// Parser for individual items + private void ParseMultiItemElement(string header, string member, string members, Action parseItem) + { + // Example: MultiPoint + // GmlMultiPointElement := + // + // GmlPointMemberElement* GmlPointMembersElement? + // + // | + // + // GmlPointMemberElement := + // + // GmlPointElement? + // + // | + // + // GmlPointMembersElement := + // + // GmlPointElement* + // + // | + if (this.ReadStartOrEmptyElement(header)) + { + this.ReadSkippableElements(); + if (!this.IsEndElement(header)) + { + while (this.IsStartElement(member)) + { + if (this.ReadStartOrEmptyElement(member)) + { + if (!this.IsEndElement(member)) + { + parseItem(); + this.ReadEndElement(); // end of member + this.ReadSkippableElements(); + } + } + } + + if (this.IsStartElement(members)) + { + if (this.ReadStartOrEmptyElement(members)) + { + while (reader.IsStartElement()) + { + parseItem(); + } + + this.ReadEndElement(); + } + } + } + + this.ReadSkippableElements(); + this.ReadEndElement(); + } + } + + /// + /// parses a pos element, which eventually is used in most other top level elements. + /// This represents a single point location with either two or zero coordinates. + /// + /// Allow empty pos + private void ParseGmlPosElement(bool allowEmpty) + { + // GmlPosListElement := + // + // {Double}* + // + // | + this.ReadAttributes(false); + if (this.ReadStartOrEmptyElement(GmlConstants.Position)) + { + double[] doubleList = this.ReadContentAsDoubleArray(); + + if (doubleList.Length != 0) + { + if (doubleList.Length < 2) + { + // When parsing a pos, we need at least two coordinates. + throw new FormatException(Strings.GmlReader_PosNeedTwoNumbers); + } + + this.AddPoint(doubleList[0], doubleList[1], doubleList.Length > 2 ? doubleList[2] : default(double?), doubleList.Length > 3 ? doubleList[3] : default(double?)); + } + else if (!allowEmpty) + { + throw new FormatException(Strings.GmlReader_PosNeedTwoNumbers); + } + + this.ReadEndElement(); + } + else if (!allowEmpty) + { + // Read an empty "pos", and allowEmpty is false. + throw new FormatException(Strings.GmlReader_PosNeedTwoNumbers); + } + } + + /// + /// Parses a sequence of 1 or more pos and pointProperty elements + /// + /// Allow Empty Point + private void ParsePosList(bool allowEmpty) + { + // PosList := {GmlPosElement | GmlPointPropertyElement}* + do + { + if (this.IsStartElement(GmlConstants.Position)) + { + this.ParseGmlPosElement(allowEmpty); + } + else + { + this.ParseGmlPointPropertyElement(allowEmpty); + } + } + while (this.IsPosListStart()); + } + + /// + /// Parses a simple pointProperty. + /// + /// Allow empty point + private void ParseGmlPointPropertyElement(bool allowEmpty) + { + // GmlPointPropertyElement := + // + // GmlPointElement + // + if (this.ReadStartOrEmptyElement(GmlConstants.PointProperty)) + { + this.ParseGmlPointElement(allowEmpty); + this.ReadEndElement(); + } + } + + /// + /// parses a GmlPosListElement. + /// + /// Alow empty posList + private void ParseGmlPosListElement(bool allowEmpty) + { + // GmlPosListElement := + // + // {Double}* + // + // | + if (this.ReadStartOrEmptyElement(GmlConstants.PositionList)) + { + if (!this.IsEndElement(GmlConstants.PositionList)) + { + double[] doubleList = this.ReadContentAsDoubleArray(); + if (doubleList.Length != 0) + { + if (doubleList.Length % 2 != 0) + { + // Odd number of doubles + throw new FormatException(Strings.GmlReader_PosListNeedsEvenCount); + } + + // posList supports only 2D points + for (int i = 0; i < doubleList.Length; i += 2) + { + this.AddPoint(doubleList[i], doubleList[i + 1], null, null); + } + } + else + { + // Read a posList with 0 elements. + throw new FormatException(Strings.GmlReader_PosListNeedsEvenCount); + } + } + else if (!allowEmpty) + { + // Read a posList with no contents. + throw new FormatException(Strings.GmlReader_PosListNeedsEvenCount); + } + + this.ReadEndElement(); + } + else if (!allowEmpty) + { + // Read an empty posList. + throw new FormatException(Strings.GmlReader_PosListNeedsEvenCount); + } + } + + /// + /// Reads the current content in the xml element as a double array + /// + /// + /// XmlReader.ReadContentAs(typeof(double[])) basically does this but a lot slower, since it will handle a bunch of + /// different splitters and formats. Here we simply parse it as a string and split in on one separator + /// + /// The double array + private double[] ReadContentAsDoubleArray() + { + String[] splitted = this.reader.ReadContentAsString().Split(coordinateDelimiter, StringSplitOptions.RemoveEmptyEntries); + double[] array = new double[splitted.Length]; + for (int i = 0; i < splitted.Length; ++i) + { + array[i] = XmlConvert.ToDouble(splitted[i]); + } + + return array; + } + + /// + /// Main element reading function. + /// Returns true if it read a non-empty start element of the given name. + /// possibilities: + /// 1- current element is not a start element named "element" - throw + /// 2- current element is named "element" but is an empty element - return false + /// 3- current element is named "element" and is not empty - return true + /// If the funciton returns true, it means that a non-empty element of the given name + /// was read, so the caller takes responsability to read the corresponding end element. + /// + /// The element name + /// Returns true if it read a non-empty start element of the given name. + private bool ReadStartOrEmptyElement(string element) + { + bool isEmptyElement = reader.IsEmptyElement; + + if (element != GmlConstants.FullGlobe) + { + reader.ReadStartElement(element, this.gmlNamespace); + } + else + { + reader.ReadStartElement(element, GmlConstants.FullGlobeNamespace); + } + + return !isEmptyElement; + } + + /// + /// Is Start Element + /// + /// Expected Element Tag + /// True if reader is at the expected element + private bool IsStartElement(string element) + { + return reader.IsStartElement(element, this.gmlNamespace); + } + + /// + /// Is End Element + /// + /// Expected Element Tag + /// True if reader is at the end of the expected element + private bool IsEndElement(string element) + { + this.reader.MoveToContent(); + return reader.NodeType == XmlNodeType.EndElement && this.reader.LocalName.Equals(element, StringComparison.Ordinal); + } + + /// + /// Read End Element + /// + private void ReadEndElement() + { + this.reader.MoveToContent(); + if (this.reader.NodeType != XmlNodeType.EndElement) + { + throw new FormatException(Strings.GmlReader_UnexpectedElement(this.reader.Name)); + } + + this.reader.ReadEndElement(); + } + + /// + /// Call MoveToContent, then skip a known set of irrelevant elements (gml:name, gml:description) + /// + private void ReadSkippableElements() + { + bool shouldSkip = true; + while (shouldSkip) + { + this.reader.MoveToContent(); + if (this.reader.NodeType == XmlNodeType.Element && + Object.ReferenceEquals(this.reader.NamespaceURI, this.gmlNamespace)) + { + // calling LocalName_get() multiple times should be avoided. + String localName = this.reader.LocalName; + shouldSkip = skippableElements.ContainsKey(localName); + } + else + { + shouldSkip = false; + } + + if (shouldSkip) + { + this.reader.Skip(); + } + } + } + + /// + /// Is reader at the start of a pos or pointProperty + /// + /// True if reader is at the expected element + private bool IsPosListStart() + { + return IsStartElement(GmlConstants.Position) || IsStartElement(GmlConstants.PointProperty); + } + + /// + /// Prepare for figure drawing + /// + private void PrepareFigure() + { + points = 0; + } + + /// + /// Draw a point in the current figure + /// + /// X coordinate + /// Y coordinate + /// Z coordinate + /// M coordinate + private void AddPoint(double x, double y, double? z, double? m) + { + if (z.HasValue && double.IsNaN(z.Value)) + { + z = null; + } + + if (m.HasValue && double.IsNaN(m.Value)) + { + m = null; + } + + if (points == 0) + { + pipeline.BeginFigure(x, y, z, m); + } + else + { + pipeline.LineTo(x, y, z, m); + } + + points += 1; + } + + /// + /// End Current Figure + /// + private void EndFigure() + { + if (points > 0) + { + pipeline.EndFigure(); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlWriter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlWriter.cs new file mode 100644 index 0000000..cf00b26 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/GmlWriter.cs @@ -0,0 +1,388 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Xml; + + /// + /// Gml Writer + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gml", Justification = "Gml is the common name for this format")] + internal sealed class GmlWriter : DrawBoth + { + /// + /// The underlying writer + /// + private XmlWriter writer; + + /// + /// Stack of spatial types currently been built + /// + private Stack parentStack; + + /// + /// If an SRID has been written already. + /// + private bool coordinateSystemWritten; + + /// + /// The Coordinate System to write + /// + private CoordinateSystem currentCoordinateSystem; + + /// + /// Figure has been written to the current spatial type + /// + private bool figureWritten; + + /// + /// Whether there are shapes written in the current container + /// + private bool shouldWriteContainerWrapper; + + /// + /// Constructor + /// + /// The Xml Writer to output to + public GmlWriter(XmlWriter writer) + { + this.writer = writer; + this.OnReset(); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// The type to be passed down the pipeline + protected override SpatialType OnBeginGeography(SpatialType type) + { + this.BeginGeo(type); + return type; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeographyPosition OnLineTo(GeographyPosition position) + { + this.WritePoint(position.Latitude, position.Longitude, position.Z, position.M); + return position; + } + + /// + /// Ends the current spatial object + /// + protected override void OnEndGeography() + { + this.EndGeo(); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// The type to be passed down the pipeline + protected override SpatialType OnBeginGeometry(SpatialType type) + { + this.BeginGeo(type); + return type; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeometryPosition OnLineTo(GeometryPosition position) + { + this.WritePoint(position.X, position.Y, position.Z, position.M); + return position; + } + + /// + /// Ends the current spatial object + /// + protected override void OnEndGeometry() + { + this.EndGeo(); + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + /// The coordinateSystem to be passed down the pipeline + protected override CoordinateSystem OnSetCoordinateSystem(CoordinateSystem coordinateSystem) + { + this.currentCoordinateSystem = coordinateSystem; + return coordinateSystem; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeographyPosition OnBeginFigure(GeographyPosition position) + { + this.BeginFigure(position.Latitude, position.Longitude, position.Z, position.M); + return position; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeometryPosition OnBeginFigure(GeometryPosition position) + { + BeginFigure(position.X, position.Y, position.Z, position.M); + return position; + } + + /// + /// Ends the current figure + /// + protected override void OnEndFigure() + { + if (this.parentStack.Peek() == SpatialType.Polygon) + { + this.writer.WriteEndElement(); + this.writer.WriteEndElement(); + } + } + + /// + /// Setup the pipeline for reuse + /// + protected override void OnReset() + { + this.parentStack = new Stack(); + this.coordinateSystemWritten = false; + this.currentCoordinateSystem = null; + this.figureWritten = false; + this.shouldWriteContainerWrapper = false; + } + + /// + /// Begin a figure + /// + /// The first coordinate + /// The second coordinate + /// The optional third coordinate + /// The optional fourth coordinate + private void BeginFigure(double x, double y, double? z, double? m) + { + if (this.parentStack.Peek() == SpatialType.Polygon) + { + // first figure is exterior, all subsequent figures are interior + this.WriteStartElement(this.figureWritten ? GmlConstants.InteriorRing : GmlConstants.ExteriorRing); + this.WriteStartElement(GmlConstants.LinearRing); + } + + this.figureWritten = true; + this.WritePoint(x, y, z, m); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + private void BeginGeo(SpatialType type) + { + if (this.shouldWriteContainerWrapper) + { + Debug.Assert(this.parentStack.Count > 0, "should be inside a container"); + + // when the first shape in the collection is beginning, write the container's wrapper + switch (this.parentStack.Peek()) + { + case SpatialType.MultiPoint: + this.WriteStartElement(GmlConstants.PointMembers); + break; + case SpatialType.MultiLineString: + this.WriteStartElement(GmlConstants.LineStringMembers); + break; + case SpatialType.MultiPolygon: + this.WriteStartElement(GmlConstants.PolygonMembers); + break; + case SpatialType.Collection: + this.WriteStartElement(GmlConstants.CollectionMembers); + break; + default: + Debug.Assert(false, "unknown container type, incorrect flag status"); + break; + } + + this.shouldWriteContainerWrapper = false; + } + + this.figureWritten = false; + this.parentStack.Push(type); + + switch (type) + { + case SpatialType.Point: + this.WriteStartElement(GmlConstants.Point); + break; + case SpatialType.LineString: + this.WriteStartElement(GmlConstants.LineString); + break; + case SpatialType.Polygon: + this.WriteStartElement(GmlConstants.Polygon); + break; + case SpatialType.MultiPoint: + this.shouldWriteContainerWrapper = true; + this.WriteStartElement(GmlConstants.MultiPoint); + break; + case SpatialType.MultiLineString: + this.shouldWriteContainerWrapper = true; + this.WriteStartElement(GmlConstants.MultiLineString); + break; + case SpatialType.MultiPolygon: + this.shouldWriteContainerWrapper = true; + this.WriteStartElement(GmlConstants.MultiPolygon); + break; + case SpatialType.Collection: + this.shouldWriteContainerWrapper = true; + this.WriteStartElement(GmlConstants.Collection); + break; + case SpatialType.FullGlobe: + this.writer.WriteStartElement(GmlConstants.FullGlobe, GmlConstants.FullGlobeNamespace); + break; + default: + throw new NotSupportedException(Strings.Validator_InvalidType(type)); + } + + this.WriteCoordinateSystem(); + } + + /// + /// Write the element with namespaces + /// + /// The element name + private void WriteStartElement(string elementName) + { + this.writer.WriteStartElement(GmlConstants.GmlPrefix, elementName, GmlConstants.GmlNamespace); + } + + /// + /// Write coordinate system + /// + private void WriteCoordinateSystem() + { + Debug.Assert(this.writer.WriteState == WriteState.Element, "Writer at element start"); + + if (!coordinateSystemWritten && this.currentCoordinateSystem != null) + { + this.coordinateSystemWritten = true; + var crsValue = GmlConstants.SrsPrefix + this.currentCoordinateSystem.Id; + this.writer.WriteAttributeString(GmlConstants.GmlPrefix, GmlConstants.SrsName, GmlConstants.GmlNamespace, crsValue); + } + } + + /// + /// Write a Point + /// + /// The first coordinate + /// The second coordinate + /// The optional third coordinate + /// The optional fourth coordinate + private void WritePoint(double x, double y, double? z, double? m) + { + this.WriteStartElement(GmlConstants.Position); + + this.writer.WriteValue(x); + this.writer.WriteValue(" "); + this.writer.WriteValue(y); + + if (z.HasValue) + { + this.writer.WriteValue(" "); + this.writer.WriteValue(z.Value); + + if (m.HasValue) + { + this.writer.WriteValue(" "); + this.writer.WriteValue(m.Value); + } + } + else + { + if (m.HasValue) + { + this.writer.WriteValue(" "); + this.writer.WriteValue(double.NaN); + this.writer.WriteValue(" "); + this.writer.WriteValue(m.Value); + } + } + + this.writer.WriteEndElement(); + } + + /// + /// End Geography/Geometry + /// + private void EndGeo() + { + SpatialType type = this.parentStack.Pop(); + + switch (type) + { + case SpatialType.MultiPoint: + case SpatialType.MultiLineString: + case SpatialType.MultiPolygon: + case SpatialType.Collection: + if (!this.shouldWriteContainerWrapper) + { + // container wrapper has been written, end it + this.writer.WriteEndElement(); + } + + this.writer.WriteEndElement(); + + // On EndGeo we should turn off wrapper, nested empty collection types relies on this call. + this.shouldWriteContainerWrapper = false; + break; + case SpatialType.Point: + if (!figureWritten) + { + // empty point needs an empty pos + this.WriteStartElement(GmlConstants.Position); + this.writer.WriteEndElement(); + } + + this.writer.WriteEndElement(); + break; + case SpatialType.LineString: + if (!figureWritten) + { + // empty linestring needs an empty posList + this.WriteStartElement(GmlConstants.PositionList); + this.writer.WriteEndElement(); + } + + this.writer.WriteEndElement(); + break; + case SpatialType.Polygon: + case SpatialType.FullGlobe: + this.writer.WriteEndElement(); + break; + default: + Debug.Assert(false, "Unknown type in stack, mismatch switch between BeginGeo() and EndGeo()?"); + break; + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeoJsonWriter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeoJsonWriter.cs new file mode 100644 index 0000000..fb9384f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeoJsonWriter.cs @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Represent an actual Json writing stream in Spatial. + /// + public interface IGeoJsonWriter + { + /// + /// Start the object scope. + /// + void StartObjectScope(); + + /// + /// End the current object scope. + /// + void EndObjectScope(); + + /// + /// Start the array scope. + /// + void StartArrayScope(); + + /// + /// End the current array scope. + /// + void EndArrayScope(); + + /// + /// Add a property name to the current json object. + /// + /// The name to add. + void AddPropertyName(string name); + + /// + /// Add a value to the current json scope. + /// + /// The value to add. + void AddValue(double value); + + /// + /// Add a value to the current json scope. + /// + /// The value to add. + void AddValue(string value); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeographyProvider.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeographyProvider.cs new file mode 100644 index 0000000..797b227 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeographyProvider.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// Provides access to the geography objects that this object constructs. + public interface IGeographyProvider + { + /// Fires when the provider constructs a geography object. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Not following the event-handler pattern")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Not following the event-handler pattern")] + event Action ProduceGeography; + + /// Gets the geography object that was constructed most recently. + /// The geography object that was constructed. + Geography ConstructedGeography { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeometryProvider.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeometryProvider.cs new file mode 100644 index 0000000..649ab6b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/IGeometryProvider.cs @@ -0,0 +1,23 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// Provides access to the geometry objects that this object constructs. + public interface IGeometryProvider + { + /// Fires when the provider constructs a geometry object. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Not following the event-handler pattern")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Not following the event-handler pattern")] + event Action ProduceGeometry; + + /// Gets the geometry object that was constructed most recently. + /// The geometry object that was constructed. + Geometry ConstructedGeometry { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/IShapeProvider.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/IShapeProvider.cs new file mode 100644 index 0000000..90078e1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/IShapeProvider.cs @@ -0,0 +1,13 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Provides access to the constructed geography or geometry. + public interface IShapeProvider : IGeographyProvider, IGeometryProvider + { + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/ISpatial.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/ISpatial.cs new file mode 100644 index 0000000..e5dc6cd --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/ISpatial.cs @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Represents the spatial interface. + public interface ISpatial + { + /// Gets the coordinate system. + /// The coordinate system. + CoordinateSystem CoordinateSystem { get; } + + /// Gets a value that indicates whether the spatial type is empty. + /// true if the spatial type is empty; otherwise, false. + bool IsEmpty { get; } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/InternalsVisibleTo.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/InternalsVisibleTo.cs new file mode 100644 index 0000000..4a1a3e3 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/InternalsVisibleTo.cs @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Service.Test.Common" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Service.Test.Client.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstoriaUnitTests.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Spatial.TDDUnitTests" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Spatial.Tests" + AssemblyRef.TestPublicKey)] + +// .NET Core +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.TestCommon.NetCore" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Edm.Tests.NetCore" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.OData.Core.Tests.NetCore" + AssemblyRef.TestPublicKey)] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Spatial.Tests.NetCore" + AssemblyRef.TestPublicKey)] \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/LexerToken.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/LexerToken.cs new file mode 100644 index 0000000..fd5d0cb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/LexerToken.cs @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// Text Lexer Token + /// + internal class LexerToken + { + /// + /// The Token Text + /// + public string Text { get; set; } + + /// + /// Token Type + /// + public int Type { get; set; } + + /// + /// Test whether this token matches the input criterion + /// + /// The target type + /// The target text, or null + /// The StringComparison + /// True if this token matches the input criterion + public bool MatchToken(int targetType, String targetText, StringComparison comparison) + { + return (this.Type == targetType) && (String.IsNullOrEmpty(targetText) || this.Text.Equals(targetText, comparison)); + } + + /// + /// String representation of this token + /// + /// String representation of this token + public override string ToString() + { + return "Type:[" + this.Type + "] Text:[" + this.Text + "]"; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.NetStandard.VS2017.csproj b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.NetStandard.VS2017.csproj new file mode 100644 index 0000000..235b7b0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.NetStandard.VS2017.csproj @@ -0,0 +1,85 @@ + + + + Microsoft.Spatial + Microsoft.Spatial + netstandard1.1 + 1.6.0 + + false + ..\..\..\tools\StrongNamePublicKeys\35MSSharedLib1024.snk + true + false + True + True + $(AssemblyName).xml + $(DefineConstants);SPATIAL;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE;SUPPRESS_SECURITY_RULES + true + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + + + + + Microsoft.Spatial + true + true + internal + true + Microsoft.Spatial.TextRes + + + + + + TextTemplatingFileGenerator + Microsoft.Spatial.cs + + + True + True + Microsoft.Spatial.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.Spatial.cs + + + True + True + Parameterized.Microsoft.Spatial.tt + true + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.cs new file mode 100644 index 0000000..8759a8a --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.cs @@ -0,0 +1,184 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial { + using System; + using System.Globalization; + using System.Reflection; + using System.Resources; +#if !PORTABLELIB + using System.Security.Permissions; +#endif + using System.Text; + using System.Threading; + + using System.ComponentModel; +#if !PORTABLELIB + [AttributeUsage(AttributeTargets.All)] + internal sealed class TextResDescriptionAttribute : DescriptionAttribute { + + private bool replaced = false; + + /// + /// Constructs a new sys description. + /// + /// + /// description text. + /// + public TextResDescriptionAttribute(string description) : base(description) { + } + + /// + /// Retrieves the description text. + /// + /// + /// description + /// + public override string Description { + get { + if (!replaced) { + replaced = true; + DescriptionValue = TextRes.GetString(base.Description); + } + return base.Description; + } + } + } + + [AttributeUsage(AttributeTargets.All)] + internal sealed class TextResCategoryAttribute : CategoryAttribute { + + public TextResCategoryAttribute(string category) : base(category) { + } + + protected override string GetLocalizedString(string value) { + return TextRes.GetString(value); + } + } +#endif + + /// + /// AutoGenerated resource class. Usage: + /// + /// string s = TextRes.GetString(TextRes.MyIdenfitier); + /// + internal sealed class TextRes { + internal const string SpatialImplementation_NoRegisteredOperations = "SpatialImplementation_NoRegisteredOperations"; + internal const string InvalidPointCoordinate = "InvalidPointCoordinate"; + internal const string Point_AccessCoordinateWhenEmpty = "Point_AccessCoordinateWhenEmpty"; + internal const string SpatialBuilder_CannotCreateBeforeDrawn = "SpatialBuilder_CannotCreateBeforeDrawn"; + internal const string GmlReader_UnexpectedElement = "GmlReader_UnexpectedElement"; + internal const string GmlReader_ExpectReaderAtElement = "GmlReader_ExpectReaderAtElement"; + internal const string GmlReader_InvalidSpatialType = "GmlReader_InvalidSpatialType"; + internal const string GmlReader_EmptyRingsNotAllowed = "GmlReader_EmptyRingsNotAllowed"; + internal const string GmlReader_PosNeedTwoNumbers = "GmlReader_PosNeedTwoNumbers"; + internal const string GmlReader_PosListNeedsEvenCount = "GmlReader_PosListNeedsEvenCount"; + internal const string GmlReader_InvalidSrsName = "GmlReader_InvalidSrsName"; + internal const string GmlReader_InvalidAttribute = "GmlReader_InvalidAttribute"; + internal const string WellKnownText_UnexpectedToken = "WellKnownText_UnexpectedToken"; + internal const string WellKnownText_UnexpectedCharacter = "WellKnownText_UnexpectedCharacter"; + internal const string WellKnownText_UnknownTaggedText = "WellKnownText_UnknownTaggedText"; + internal const string WellKnownText_TooManyDimensions = "WellKnownText_TooManyDimensions"; + internal const string Validator_SridMismatch = "Validator_SridMismatch"; + internal const string Validator_InvalidType = "Validator_InvalidType"; + internal const string Validator_FullGlobeInCollection = "Validator_FullGlobeInCollection"; + internal const string Validator_LineStringNeedsTwoPoints = "Validator_LineStringNeedsTwoPoints"; + internal const string Validator_FullGlobeCannotHaveElements = "Validator_FullGlobeCannotHaveElements"; + internal const string Validator_NestingOverflow = "Validator_NestingOverflow"; + internal const string Validator_InvalidPointCoordinate = "Validator_InvalidPointCoordinate"; + internal const string Validator_UnexpectedCall = "Validator_UnexpectedCall"; + internal const string Validator_UnexpectedCall2 = "Validator_UnexpectedCall2"; + internal const string Validator_InvalidPolygonPoints = "Validator_InvalidPolygonPoints"; + internal const string Validator_InvalidLatitudeCoordinate = "Validator_InvalidLatitudeCoordinate"; + internal const string Validator_InvalidLongitudeCoordinate = "Validator_InvalidLongitudeCoordinate"; + internal const string Validator_UnexpectedGeography = "Validator_UnexpectedGeography"; + internal const string Validator_UnexpectedGeometry = "Validator_UnexpectedGeometry"; + internal const string GeoJsonReader_MissingRequiredMember = "GeoJsonReader_MissingRequiredMember"; + internal const string GeoJsonReader_InvalidPosition = "GeoJsonReader_InvalidPosition"; + internal const string GeoJsonReader_InvalidTypeName = "GeoJsonReader_InvalidTypeName"; + internal const string GeoJsonReader_InvalidNullElement = "GeoJsonReader_InvalidNullElement"; + internal const string GeoJsonReader_ExpectedNumeric = "GeoJsonReader_ExpectedNumeric"; + internal const string GeoJsonReader_ExpectedArray = "GeoJsonReader_ExpectedArray"; + internal const string GeoJsonReader_InvalidCrsType = "GeoJsonReader_InvalidCrsType"; + internal const string GeoJsonReader_InvalidCrsName = "GeoJsonReader_InvalidCrsName"; + internal const string JsonReaderExtensions_CannotReadPropertyValueAsString = "JsonReaderExtensions_CannotReadPropertyValueAsString"; + internal const string JsonReaderExtensions_CannotReadValueAsJsonObject = "JsonReaderExtensions_CannotReadValueAsJsonObject"; + internal const string PlatformHelper_DateTimeOffsetMustContainTimeZone = "PlatformHelper_DateTimeOffsetMustContainTimeZone"; + + static TextRes loader = null; + ResourceManager resources; + + internal TextRes() { +#if !PORTABLELIB + resources = new System.Resources.ResourceManager("Microsoft.Spatial", this.GetType().Assembly); +#else + resources = new System.Resources.ResourceManager("Microsoft.Spatial", this.GetType().GetTypeInfo().Assembly); +#endif + } + + private static TextRes GetLoader() { + if (loader == null) { + TextRes sr = new TextRes(); + Interlocked.CompareExchange(ref loader, sr, null); + } + return loader; + } + + private static CultureInfo Culture { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + public static ResourceManager Resources { + get { + return GetLoader().resources; + } + } + + public static string GetString(string name, params object[] args) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, TextRes.Culture); + + if (args != null && args.Length > 0) { + for (int i = 0; i < args.Length; i ++) { + String value = args[i] as String; + if (value != null && value.Length > 1024) { + args[i] = value.Substring(0, 1024 - 3) + "..."; + } + } + return String.Format(CultureInfo.CurrentCulture, res, args); + } + else { + return res; + } + } + + public static string GetString(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetString(name, TextRes.Culture); + } + + public static string GetString(string name, out bool usedFallback) { + // always false for this version of gensr + usedFallback = false; + return GetString(name); + } +#if !PORTABLELIB + public static object GetObject(string name) { + TextRes sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetObject(name, TextRes.Culture); + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.csproj b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.csproj new file mode 100644 index 0000000..497f834 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.csproj @@ -0,0 +1,181 @@ + + + + Microsoft.Spatial + Library + v4.5 + .NETPortable + Profile111 + true + true + false + Microsoft.Spatial + {5D921888-FE03-4C3F-40FE-2F624505461D} + $(AssemblyName).xml + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(DefineConstants);SPATIAL;PORTABLELIB;SUPPRESS_PORTABLELIB_TARGETFRAMEWORK_ATTRIBUTE + true + + + + + + + Microsoft.Spatial + true + true + internal + true + Microsoft.Spatial.TextRes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + + + TextTemplatingFileGenerator + Microsoft.Spatial.cs + + + True + True + Microsoft.Spatial.tt + true + + + TextTemplatingFileGenerator + Parameterized.Microsoft.Spatial.cs + + + True + True + Parameterized.Microsoft.Spatial.tt + true + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.tt b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.tt new file mode 100644 index 0000000..4bf10fa --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.tt @@ -0,0 +1,22 @@ +<#@ include file="..\..\tools\StringResourceGenerator\ResourceClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.Spatial"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.Spatial"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // Indicates whether to skip generation of string resource attributes. + public const bool SkipSRAttributes = false; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.Spatial.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.txt b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.txt new file mode 100644 index 0000000..ec15fab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Microsoft.Spatial.txt @@ -0,0 +1,58 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Note: During the build, this file is merged with the DataWeb\OData\Microsoft.Data.Json.Common.txt file to generate the System.Data.Spatial.txt file. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Error Messages + +SpatialImplementation_NoRegisteredOperations=No operations are registered. Please provide operations using SpatialImplementation.CurrentImplementation.Operations property. + +InvalidPointCoordinate=The value '{0}' is not valid for the coordinate '{1}'. +Point_AccessCoordinateWhenEmpty=Access to the coordinate properties of an empty point is not supported. + +SpatialBuilder_CannotCreateBeforeDrawn=The builder cannot create an instance until all pipeline calls are completed. + +GmlReader_UnexpectedElement=Incorrect GML Format: The XmlReader instance encountered an unexpected element "{0}". +GmlReader_ExpectReaderAtElement=Incorrect GML Format: the XmlReader instance is expected to be at the start of a GML element. +GmlReader_InvalidSpatialType=Incorrect GML Format: unknown spatial type tag "{0}". +GmlReader_EmptyRingsNotAllowed=Incorrect GML Format: a LinearRing element must not be empty. +GmlReader_PosNeedTwoNumbers=Incorrect GML Format: a pos element must contain at least two coordinates. +GmlReader_PosListNeedsEvenCount=Incorrect GML Format: a posList element must contain an even number of coordinates. +GmlReader_InvalidSrsName=Incorrect GML Format: a srsName attribute must begin with the namespace "{0}". +GmlReader_InvalidAttribute=The attribute '{0}' on element '{1}' is not supported. + +WellKnownText_UnexpectedToken=Expecting token type "{0}" with text "{1}" but found "{2}". +WellKnownText_UnexpectedCharacter=Unexpected character '{0}' found in text. +WellKnownText_UnknownTaggedText=Unknown Tagged Text "{0}". +WellKnownText_TooManyDimensions=The WellKnownTextReader is configured to allow only two dimensions, and a third dimension was encountered. + +Validator_SridMismatch=Invalid spatial data: An instance of spatial type can have only one unique CoordinateSystem for all of its coordinates. +Validator_InvalidType=Invalid spatial data: Invalid spatial type "{0}". +Validator_FullGlobeInCollection=Invalid spatial data: the spatial type "FullGlobe" cannot be part of a collection type. +Validator_LineStringNeedsTwoPoints=Invalid spatial data: the spatial type "LineString" must contain at least two points. +Validator_FullGlobeCannotHaveElements=Invalid spatial data: the spatial type "FullGlobe" cannot contain figures. +Validator_NestingOverflow=Invalid spatial data: only {0} levels of nesting are supported in collection types. +Validator_InvalidPointCoordinate=Invalid spatial data: the coordinates ({0} {1} {2} {3}) are not valid. +Validator_UnexpectedCall=Invalid spatial data: expected call to "{0}" but got call to "{1}". +Validator_UnexpectedCall2=Invalid spatial data: expected call to "{0}" or "{1}" but got call to "{2}". +Validator_InvalidPolygonPoints=Invalid spatial data: A polygon ring must contain at least four points, and the last point must be equal to the first point. +Validator_InvalidLatitudeCoordinate=Invalid latitude coordinate {0}. A latitude coordinate must be a value between -90.0 and +90.0 degrees. +Validator_InvalidLongitudeCoordinate=Invalid longitude coordinate {0}. A longitude coordinate must be a value between -15069.0 and +15069.0 degrees +Validator_UnexpectedGeography=A geography operation was called while processing a geometric shape. +Validator_UnexpectedGeometry=A geometry operation was called while processing a geographic shape. + +GeoJsonReader_MissingRequiredMember=Invalid GeoJSON. The '{0}' member is required, but was not found. +GeoJsonReader_InvalidPosition=Invalid GeoJSON. A position must contain at least two and no more than four elements. +GeoJsonReader_InvalidTypeName=Invalid GeoJSON. The value '{0}' is not a valid value for the 'type' member. +GeoJsonReader_InvalidNullElement=Invalid GeoJSON. A null value was found in an array element where nulls are not allowed. +GeoJsonReader_ExpectedNumeric=Invalid GeoJSON. A non-numeric value was found in an array element where a numeric value was expected. +GeoJsonReader_ExpectedArray=Invalid GeoJSON. A primitive value was found in an array element where an array was expected. +GeoJsonReader_InvalidCrsType=Invalid GeoJSON. The value '{0}' is not a recognized CRS type. +GeoJsonReader_InvalidCrsName=Invalid GeoJSON. The value '{0}' is not a recognized CRS name. + +; Note: The below list of error messages are common to both the OData and the Spatial project. + +JsonReaderExtensions_CannotReadPropertyValueAsString=Cannot read the value '{0}' for the property '{1}' as a quoted JSON string value. +JsonReaderExtensions_CannotReadValueAsJsonObject=Cannot read the value '{0}' as a JSON object. + +; Note: The below list of error messages are common to the ODataLib, EdmLib, Spatial, Server and Client projects. +PlatformHelper_DateTimeOffsetMustContainTimeZone=The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information. diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/OrcasExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/OrcasExtensions.cs new file mode 100644 index 0000000..ca73d5c --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/OrcasExtensions.cs @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Text; + + /// + /// This class holds extension methods for objects that have new capabilities + /// in newer versions of .net, and this lets us make the calls look the same and reduces the #if noise + /// + internal static class OrcasExtensions + { + /// + /// StringBuilder didn't have a clear method in Orcas, so we added and extension method to give it one. + /// + /// The StringBuilder instance to clear. + internal static void Clear(this StringBuilder builder) + { + builder.Length = 0; + builder.Capacity = 0; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.cs new file mode 100644 index 0000000..3dcdcc5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.cs @@ -0,0 +1,378 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial { + using System; + using System.Resources; + + /// + /// Strongly-typed and parameterized string resources. + /// + internal static class Strings { + /// + /// A string like "No operations are registered. Please provide operations using SpatialImplementation.CurrentImplementation.Operations property." + /// + internal static string SpatialImplementation_NoRegisteredOperations { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.SpatialImplementation_NoRegisteredOperations); + } + } + + /// + /// A string like "The value '{0}' is not valid for the coordinate '{1}'." + /// + internal static string InvalidPointCoordinate(object p0, object p1) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.InvalidPointCoordinate, p0, p1); + } + + /// + /// A string like "Access to the coordinate properties of an empty point is not supported." + /// + internal static string Point_AccessCoordinateWhenEmpty { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Point_AccessCoordinateWhenEmpty); + } + } + + /// + /// A string like "The builder cannot create an instance until all pipeline calls are completed." + /// + internal static string SpatialBuilder_CannotCreateBeforeDrawn { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.SpatialBuilder_CannotCreateBeforeDrawn); + } + } + + /// + /// A string like "Incorrect GML Format: The XmlReader instance encountered an unexpected element "{0}"." + /// + internal static string GmlReader_UnexpectedElement(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_UnexpectedElement, p0); + } + + /// + /// A string like "Incorrect GML Format: the XmlReader instance is expected to be at the start of a GML element." + /// + internal static string GmlReader_ExpectReaderAtElement { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_ExpectReaderAtElement); + } + } + + /// + /// A string like "Incorrect GML Format: unknown spatial type tag "{0}"." + /// + internal static string GmlReader_InvalidSpatialType(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_InvalidSpatialType, p0); + } + + /// + /// A string like "Incorrect GML Format: a LinearRing element must not be empty." + /// + internal static string GmlReader_EmptyRingsNotAllowed { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_EmptyRingsNotAllowed); + } + } + + /// + /// A string like "Incorrect GML Format: a pos element must contain at least two coordinates." + /// + internal static string GmlReader_PosNeedTwoNumbers { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_PosNeedTwoNumbers); + } + } + + /// + /// A string like "Incorrect GML Format: a posList element must contain an even number of coordinates." + /// + internal static string GmlReader_PosListNeedsEvenCount { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_PosListNeedsEvenCount); + } + } + + /// + /// A string like "Incorrect GML Format: a srsName attribute must begin with the namespace "{0}"." + /// + internal static string GmlReader_InvalidSrsName(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_InvalidSrsName, p0); + } + + /// + /// A string like "The attribute '{0}' on element '{1}' is not supported." + /// + internal static string GmlReader_InvalidAttribute(object p0, object p1) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GmlReader_InvalidAttribute, p0, p1); + } + + /// + /// A string like "Expecting token type "{0}" with text "{1}" but found "{2}"." + /// + internal static string WellKnownText_UnexpectedToken(object p0, object p1, object p2) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.WellKnownText_UnexpectedToken, p0, p1, p2); + } + + /// + /// A string like "Unexpected character '{0}' found in text." + /// + internal static string WellKnownText_UnexpectedCharacter(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.WellKnownText_UnexpectedCharacter, p0); + } + + /// + /// A string like "Unknown Tagged Text "{0}"." + /// + internal static string WellKnownText_UnknownTaggedText(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.WellKnownText_UnknownTaggedText, p0); + } + + /// + /// A string like "The WellKnownTextReader is configured to allow only two dimensions, and a third dimension was encountered." + /// + internal static string WellKnownText_TooManyDimensions { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.WellKnownText_TooManyDimensions); + } + } + + /// + /// A string like "Invalid spatial data: An instance of spatial type can have only one unique CoordinateSystem for all of its coordinates." + /// + internal static string Validator_SridMismatch { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_SridMismatch); + } + } + + /// + /// A string like "Invalid spatial data: Invalid spatial type "{0}"." + /// + internal static string Validator_InvalidType(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_InvalidType, p0); + } + + /// + /// A string like "Invalid spatial data: the spatial type "FullGlobe" cannot be part of a collection type." + /// + internal static string Validator_FullGlobeInCollection { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_FullGlobeInCollection); + } + } + + /// + /// A string like "Invalid spatial data: the spatial type "LineString" must contain at least two points." + /// + internal static string Validator_LineStringNeedsTwoPoints { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_LineStringNeedsTwoPoints); + } + } + + /// + /// A string like "Invalid spatial data: the spatial type "FullGlobe" cannot contain figures." + /// + internal static string Validator_FullGlobeCannotHaveElements { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_FullGlobeCannotHaveElements); + } + } + + /// + /// A string like "Invalid spatial data: only {0} levels of nesting are supported in collection types." + /// + internal static string Validator_NestingOverflow(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_NestingOverflow, p0); + } + + /// + /// A string like "Invalid spatial data: the coordinates ({0} {1} {2} {3}) are not valid." + /// + internal static string Validator_InvalidPointCoordinate(object p0, object p1, object p2, object p3) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_InvalidPointCoordinate, p0, p1, p2, p3); + } + + /// + /// A string like "Invalid spatial data: expected call to "{0}" but got call to "{1}"." + /// + internal static string Validator_UnexpectedCall(object p0, object p1) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_UnexpectedCall, p0, p1); + } + + /// + /// A string like "Invalid spatial data: expected call to "{0}" or "{1}" but got call to "{2}"." + /// + internal static string Validator_UnexpectedCall2(object p0, object p1, object p2) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_UnexpectedCall2, p0, p1, p2); + } + + /// + /// A string like "Invalid spatial data: A polygon ring must contain at least four points, and the last point must be equal to the first point." + /// + internal static string Validator_InvalidPolygonPoints { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_InvalidPolygonPoints); + } + } + + /// + /// A string like "Invalid latitude coordinate {0}. A latitude coordinate must be a value between -90.0 and +90.0 degrees." + /// + internal static string Validator_InvalidLatitudeCoordinate(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_InvalidLatitudeCoordinate, p0); + } + + /// + /// A string like "Invalid longitude coordinate {0}. A longitude coordinate must be a value between -15069.0 and +15069.0 degrees" + /// + internal static string Validator_InvalidLongitudeCoordinate(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_InvalidLongitudeCoordinate, p0); + } + + /// + /// A string like "A geography operation was called while processing a geometric shape." + /// + internal static string Validator_UnexpectedGeography { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_UnexpectedGeography); + } + } + + /// + /// A string like "A geometry operation was called while processing a geographic shape." + /// + internal static string Validator_UnexpectedGeometry { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.Validator_UnexpectedGeometry); + } + } + + /// + /// A string like "Invalid GeoJSON. The '{0}' member is required, but was not found." + /// + internal static string GeoJsonReader_MissingRequiredMember(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_MissingRequiredMember, p0); + } + + /// + /// A string like "Invalid GeoJSON. A position must contain at least two and no more than four elements." + /// + internal static string GeoJsonReader_InvalidPosition { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_InvalidPosition); + } + } + + /// + /// A string like "Invalid GeoJSON. The value '{0}' is not a valid value for the 'type' member." + /// + internal static string GeoJsonReader_InvalidTypeName(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_InvalidTypeName, p0); + } + + /// + /// A string like "Invalid GeoJSON. A null value was found in an array element where nulls are not allowed." + /// + internal static string GeoJsonReader_InvalidNullElement { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_InvalidNullElement); + } + } + + /// + /// A string like "Invalid GeoJSON. A non-numeric value was found in an array element where a numeric value was expected." + /// + internal static string GeoJsonReader_ExpectedNumeric { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_ExpectedNumeric); + } + } + + /// + /// A string like "Invalid GeoJSON. A primitive value was found in an array element where an array was expected." + /// + internal static string GeoJsonReader_ExpectedArray { + get { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_ExpectedArray); + } + } + + /// + /// A string like "Invalid GeoJSON. The value '{0}' is not a recognized CRS type." + /// + internal static string GeoJsonReader_InvalidCrsType(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_InvalidCrsType, p0); + } + + /// + /// A string like "Invalid GeoJSON. The value '{0}' is not a recognized CRS name." + /// + internal static string GeoJsonReader_InvalidCrsName(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.GeoJsonReader_InvalidCrsName, p0); + } + + /// + /// A string like "Cannot read the value '{0}' for the property '{1}' as a quoted JSON string value." + /// + internal static string JsonReaderExtensions_CannotReadPropertyValueAsString(object p0, object p1) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.JsonReaderExtensions_CannotReadPropertyValueAsString, p0, p1); + } + + /// + /// A string like "Cannot read the value '{0}' as a JSON object." + /// + internal static string JsonReaderExtensions_CannotReadValueAsJsonObject(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.JsonReaderExtensions_CannotReadValueAsJsonObject, p0); + } + + /// + /// A string like "The time zone information is missing on the DateTimeOffset value '{0}'. A DateTimeOffset value must contain the time zone information." + /// + internal static string PlatformHelper_DateTimeOffsetMustContainTimeZone(object p0) { + return Microsoft.Spatial.TextRes.GetString(Microsoft.Spatial.TextRes.PlatformHelper_DateTimeOffsetMustContainTimeZone, p0); + } + + } + + /// + /// Strongly-typed and parameterized exception factory. + /// + internal static partial class Error { + + /// + /// The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument. + /// + internal static Exception ArgumentNull(string paramName) { + return new ArgumentNullException(paramName); + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method. + /// + internal static Exception ArgumentOutOfRange(string paramName) { + return new ArgumentOutOfRangeException(paramName); + } + + /// + /// The exception that is thrown when the author has not yet implemented the logic at this point in the program. This can act as an exception based TODO tag. + /// + internal static Exception NotImplemented() { + return new NotImplementedException(); + } + + /// + /// The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality. + /// + internal static Exception NotSupported() { + return new NotSupportedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.tt b/ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.tt new file mode 100644 index 0000000..2eed869 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Parameterized.Microsoft.Spatial.tt @@ -0,0 +1,19 @@ +<#@ include file="..\..\tools\StringResourceGenerator\StringsClassGenerator.ttinclude" #> +<#+ +public static class Configuration +{ + // The namespace where the generated resource classes reside. + public const string ResourceClassNamespace = "Microsoft.Spatial"; + + // The assembly name where the generated resource classes will be linked. + public const string AssemblyName = "Microsoft.Spatial"; + + // The name of the generated resource class. + public const string ResourceClassName = "TextRes"; + + // The list of text files containing all the string resources. + public static readonly string[] TextFiles = { + "Microsoft.Spatial.txt" + }; +} +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/ParseErrorException.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/ParseErrorException.cs new file mode 100644 index 0000000..b98ba46 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/ParseErrorException.cs @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; +#if ORCAS + using System.Runtime.Serialization; +#endif + + /// The exception that is thrown on an unsuccessful parsing of the serialized format. +#if ORCAS + [Serializable] +#endif + public class ParseErrorException : Exception + { + /// Creates a new instance of the class. + public ParseErrorException() + { + } + + /// Creates a new instance of the class from a message and previous exception. + /// The message about the exception. + /// The exception that preceeded this one. + public ParseErrorException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// Creates a new instance of the class from a message. + /// The message about the exception. + public ParseErrorException(string message) + : base(message) + { + } + +#if ORCAS + /// Creates a new instance of the class from a serialized data. + /// The instance that holds the serialized object data about the exception being thrown. + /// The instance that contains contextual information about the source or destination. + protected ParseErrorException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Spatial.StyleCop b/ApiAsAService/odata.net/src/Microsoft.Spatial/Spatial.StyleCop new file mode 100644 index 0000000..bdbc1ca --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Spatial.StyleCop @@ -0,0 +1,231 @@ + + + NoMerge + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + id + in + is + if + + + + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + + + + False + + + + + + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + + + + False + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialBuilder.cs new file mode 100644 index 0000000..188bdf0 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialBuilder.cs @@ -0,0 +1,74 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// Creates a geometry or geography instances from spatial data pipelines. + public class SpatialBuilder : SpatialPipeline, IShapeProvider + { + /// + /// The builder to be delegated to when this class is accessed from the IGeographyPipeline or IGeographyProvider interfaces. + /// + private readonly IGeographyProvider geographyOutput; + + /// + /// The builder to be delegated to when this class is accessed from the IGeometryPipeline or IGeometryProvider interfaces. + /// + private readonly IGeometryProvider geometryOutput; + + /// Initializes a new instance of the class. + /// The geography input. + /// The geometry input. + /// The geography output. + /// The geometry output. + public SpatialBuilder(GeographyPipeline geographyInput, GeometryPipeline geometryInput, IGeographyProvider geographyOutput, IGeometryProvider geometryOutput) + : base(geographyInput, geometryInput) + { + this.geographyOutput = geographyOutput; + this.geometryOutput = geometryOutput; + } + + /// Fires when the provider constructs geography object. + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Not following the event-handler pattern")] + public event Action ProduceGeography + { + add { this.geographyOutput.ProduceGeography += value; } + remove { this.geographyOutput.ProduceGeography -= value; } + } + + /// Fires when the provider constructs geometry object. + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "Not following the event-handler pattern")] + public event Action ProduceGeometry + { + add { this.geometryOutput.ProduceGeometry += value; } + remove { this.geometryOutput.ProduceGeometry -= value; } + } + + /// Gets the geography object that was constructed most recently. + /// The geography object that was constructed. + public Geography ConstructedGeography + { + get { return this.geographyOutput.ConstructedGeography; } + } + + /// Gets the geometry object that was constructed most recently. + /// The geometry object that was constructed. + public Geometry ConstructedGeometry + { + get { return this.geometryOutput.ConstructedGeometry; } + } + + /// Creates an implementation of the builder. + /// The created SpatialBuilder implementation. + public static SpatialBuilder Create() + { + return SpatialImplementation.CurrentImplementation.CreateBuilder(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFactory.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFactory.cs new file mode 100644 index 0000000..556b7c5 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFactory.cs @@ -0,0 +1,244 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// + /// Base Spatial Factory + /// + public abstract class SpatialFactory + { + /// + /// Stack of Containers + /// + private Stack containers; + + /// + /// Whether a figure has been started + /// + private bool figureDrawn; + + /// + /// Inside a Polygon Ring + /// + private bool inRing; + + /// + /// Current polygon ring has been closed + /// + private bool ringClosed; + + /// + /// X coordinate of the current ring's starting position + /// + private double ringStartX; + + /// + /// Y coordinate of the current ring's starting position + /// + private double ringStartY; + + /// + /// Z coordinate of the current ring's starting position + /// + private double? ringStartZ; + + /// + /// M coordinate of the current ring's starting position + /// + private double? ringStartM; + + /// + /// Initializes a new instance of the BaseSpatialFactory class + /// + internal SpatialFactory() + { + this.containers = new Stack(); + } + + /// + /// Gets the current container Definition + /// + private SpatialType CurrentType + { + get + { + if (this.containers.Count == 0) + { + return SpatialType.Unknown; + } + else + { + return this.containers.Peek(); + } + } + } + + /// + /// Begin Geo + /// + /// The spatial type + protected virtual void BeginGeo(SpatialType type) + { + // close on nesting types until we find a container suitable for the current type + while (!this.CanContain(type)) + { + this.EndGeo(); + } + + this.containers.Push(type); + } + + /// + /// Begin drawing a figure + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + protected virtual void BeginFigure(double x, double y, double? z, double? m) + { + Debug.Assert(!this.figureDrawn, "Figure already started"); + this.figureDrawn = true; + } + + /// + /// Draw a point in the specified coordinate + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + protected virtual void AddLine(double x, double y, double? z, double? m) + { + Debug.Assert(this.figureDrawn, "Figure not yet started"); + + if (this.inRing) + { + this.ringClosed = x == this.ringStartX && y == this.ringStartY; + } + } + + /// + /// Ends the figure set on the current node + /// + protected virtual void EndFigure() + { + Debug.Assert(this.figureDrawn, "Figure not yet started"); + if (this.inRing) + { + if (!this.ringClosed) + { + this.AddLine(this.ringStartX, this.ringStartY, this.ringStartZ, this.ringStartM); + } + + this.inRing = false; + this.ringClosed = true; + } + + this.figureDrawn = false; + } + + /// + /// Ends the current spatial object + /// + protected virtual void EndGeo() + { + if (this.figureDrawn) + { + this.EndFigure(); + } + + this.containers.Pop(); + } + + /// + /// Finish the current instance + /// + protected virtual void Finish() + { + while (this.containers.Count > 0) + { + this.EndGeo(); + } + } + + /// + /// Add a new position to the current line figure + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + protected virtual void AddPos(double x, double y, double? z, double? m) + { + if (!this.figureDrawn) + { + this.BeginFigure(x, y, z, m); + } + else + { + this.AddLine(x, y, z, m); + } + } + + /// + /// Start a new polygon ring + /// + /// The X value + /// The Y value + /// The Z value + /// The M value + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z and m are meaningful")] + protected virtual void StartRing(double x, double y, double? z, double? m) + { + if (this.figureDrawn) + { + this.EndFigure(); + } + + this.BeginFigure(x, y, z, m); + + this.ringStartX = x; + this.ringStartY = y; + this.ringStartM = m; + this.ringStartZ = z; + this.inRing = true; + this.ringClosed = false; + } + + /// + /// Can the current container contain the spatial type + /// + /// The spatial type to test + /// A boolean value indicating whether the current container can contain the spatial type + private bool CanContain(SpatialType type) + { + switch (this.CurrentType) + { + case SpatialType.Unknown: + case SpatialType.Collection: + // top level or collection + return true; + case SpatialType.MultiPoint: + return type == SpatialType.Point; + case SpatialType.MultiLineString: + return type == SpatialType.LineString; + case SpatialType.MultiPolygon: + return type == SpatialType.Polygon; + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFormatter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFormatter.cs new file mode 100644 index 0000000..85c04bb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialFormatter.cs @@ -0,0 +1,101 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + /// Represents the base class for all Spatial Formats. + /// The type of reader to be read from. + /// The type of reader to be read from. + public abstract class SpatialFormatter + { + /// + /// The implementation that created this instance. + /// + private readonly SpatialImplementation creator; + + /// Initializes a new instance of the <see cref="T:Microsoft.Spatial.SpatialFormatter`2" /> class. + /// The implementation that created this instance. + protected SpatialFormatter(SpatialImplementation creator) + { + Util.CheckArgumentNull(creator, "creator"); + this.creator = creator; + } + + /// Parses the input, and produces the object. + /// The input. + /// The input to be parsed. + /// The type of object to produce. + public TResult Read(TReaderStream input) where TResult : class, ISpatial + { + var trans = MakeValidatingBuilder(); + IShapeProvider parsePipelineEnd = trans.Value; + + Read(input, trans.Key); + if (typeof(Geometry).IsAssignableFrom(typeof(TResult))) + { + return (TResult)(object)parsePipelineEnd.ConstructedGeometry; + } + else + { + return (TResult)(object)parsePipelineEnd.ConstructedGeography; + } + } + + /// Parses the input, and produces the object. + /// The input to be parsed. + /// The pipeline to call during reading. + /// The type of object to produce. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The type hierarchy is too deep to have a specificly typed Read for each of them.")] + public void Read(TReaderStream input, SpatialPipeline pipeline) where TResult : class, ISpatial + { + if (typeof(Geometry).IsAssignableFrom(typeof(TResult))) + { + ReadGeometry(input, pipeline); + } + else + { + ReadGeography(input, pipeline); + } + } + + /// Creates a valid format from the spatial object. + /// The object that the format is being created for. + /// The stream to write the formatted object to. + public void Write(ISpatial spatial, TWriterStream writerStream) + { + var writer = this.CreateWriter(writerStream); + spatial.SendTo(writer); + } + + /// Creates the writerStream. + /// The writerStream that was created. + /// The stream that should be written to. + public abstract SpatialPipeline CreateWriter(TWriterStream writerStream); + + /// Reads the Geography from the readerStream and call the appropriate pipeline methods. + /// The stream to read from. + /// The pipeline to call based on what is read. + protected abstract void ReadGeography(TReaderStream readerStream, SpatialPipeline pipeline); + + /// Reads the Geometry from the readerStream and call the appropriate pipeline methods. + /// The stream to read from. + /// The pipeline to call based on what is read. + protected abstract void ReadGeometry(TReaderStream readerStream, SpatialPipeline pipeline); + + /// Creates the builder that will be called by the parser to build the new type. + /// The builder that was created. + protected KeyValuePair MakeValidatingBuilder() + { + var builder = this.creator.CreateBuilder(); + var validator = this.creator.CreateValidator(); + validator.ChainTo(builder); + return new KeyValuePair(validator, builder); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialImplementation.cs new file mode 100644 index 0000000..0510b40 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialImplementation.cs @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// Class responsible for knowing how to create the Geography and Geometry builders for + /// a particular implemenation of Spatial types + /// + public abstract class SpatialImplementation + { + /// Default Spatial Implementation. + private static SpatialImplementation spatialImplementation = new DataServicesSpatialImplementation(); + + /// Returns an instance of SpatialImplementation that is currently being used. + public static SpatialImplementation CurrentImplementation + { + get + { + return spatialImplementation; + } + + // Intended for use by tests and DataContractSerialization + internal set + { + spatialImplementation = value; + } + } + + /// Gets or sets the Spatial operations implementation. + public abstract SpatialOperations Operations + { + get; + set; + } + + /// Creates a SpatialBuilder for this implementation. + /// The SpatialBuilder created. + public abstract SpatialBuilder CreateBuilder(); + + /// Creates a Formatter for Json Object. + /// The JsonObjectFormatter created. + public abstract GeoJsonObjectFormatter CreateGeoJsonObjectFormatter(); + + /// Creates a GmlFormatter for this implementation. + /// The GmlFormatter created. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gml", Justification = "Gml is the common name for this format")] + public abstract GmlFormatter CreateGmlFormatter(); + + /// Creates a WellKnownTextSqlFormatter for this implementation. + /// The WellKnownTextSqlFormatter created. + public abstract WellKnownTextSqlFormatter CreateWellKnownTextSqlFormatter(); + + /// Creates a WellKnownTextSqlFormatter for this implementation. + /// The WellKnownTextSqlFormatter created. + /// Controls the writing and reading of the Z and M dimension. + public abstract WellKnownTextSqlFormatter CreateWellKnownTextSqlFormatter(bool allowOnlyTwoDimensions); + + /// Creates a spatial Validator. + /// The SpatialValidator created. + public abstract SpatialPipeline CreateValidator(); + + /// + /// This method throws if the operations instance is null. It returns a non-null operations implementation. + /// + /// a SpatialOperations implementation. + internal SpatialOperations VerifyAndGetNonNullOperations() + { + var operations = this.Operations; + if (operations == null) + { + throw new NotImplementedException(Strings.SpatialImplementation_NoRegisteredOperations); + } + + return operations; + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialOperations.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialOperations.cs new file mode 100644 index 0000000..5130d92 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialOperations.cs @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// Class responsible for knowing how to perform operations for a particular implemenation of Spatial types + /// + public abstract class SpatialOperations + { + /// Indicates the Geometry Distance. + /// The operation result. + /// The Operand 1. + /// The Operand 2. + public virtual double Distance(Geometry operand1, Geometry operand2) + { + throw new NotImplementedException(); + } + + /// Indicates a Geography Distance. + /// The operation result. + /// The Operand 1. + /// The Operand 2. + public virtual double Distance(Geography operand1, Geography operand2) + { + throw new NotImplementedException(); + } + + /// Indicates the Geometry LineString's length. + /// The operation result. + /// The Operand. + public virtual double Length(Geometry operand) + { + throw new NotImplementedException(); + } + + /// Indicates a Geography LineString's length. + /// The operation result. + /// The Operand. + public virtual double Length(Geography operand) + { + throw new NotImplementedException(); + } + + /// Indicates the Geometry Intersects() method. + /// The operation result. + /// The Operand 1, point. + /// The Operand 2, polygon. + public virtual bool Intersects(Geometry operand1, Geometry operand2) + { + throw new NotImplementedException(); + } + + /// Indicates a Geography Intersects() method. + /// The operation result. + /// The Operand 1, point. + /// The Operand 2, polygon. + public virtual bool Intersects(Geography operand1, Geography operand2) + { + throw new NotImplementedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialPipeline.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialPipeline.cs new file mode 100644 index 0000000..77acc07 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialPipeline.cs @@ -0,0 +1,114 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// One link of a geospatial pipeline + /// + public class SpatialPipeline + { + /// + /// the geography side of the pipeline + /// + private GeographyPipeline geographyPipeline; + + /// + /// the geometry side of the pipeline + /// + private GeometryPipeline geometryPipeline; + + /// + /// A reference to the begining link of the chain + /// useful for getting the startingLink when creating the chain fluently + /// e.g. new ForwardingSegment(new Node()).ChainTo(new Node()).ChainTo(new Node).StartingLink + /// + private SpatialPipeline startingLink; + + /// Initializes a new instance of the class. + public SpatialPipeline() + { + this.startingLink = this; + } + + /// Initializes a new instance of the class. + /// The geography chain. + /// The geometry chain. + public SpatialPipeline(GeographyPipeline geographyPipeline, GeometryPipeline geometryPipeline) + { + this.geographyPipeline = geographyPipeline; + this.geometryPipeline = geometryPipeline; + this.startingLink = this; + } + + /// Gets the geography side of the pipeline. + public virtual GeographyPipeline GeographyPipeline + { + get { return geographyPipeline; } + } + + /// Gets the geometry side of the pipeline. + public virtual GeometryPipeline GeometryPipeline + { + get { return geometryPipeline; } + } + + /// Gets or sets the starting link. + /// The starting link. + public SpatialPipeline StartingLink + { + get { return this.startingLink; } + set { this.startingLink = value; } + } + + /// + /// Performs an implicit conversion from to . + /// + /// The spatial chain. + /// + /// The result of the conversion. + /// + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "we have DrawGeometry and DrawGeography properties that can be used instead of the implicit cast")] + public static implicit operator GeographyPipeline(SpatialPipeline spatialPipeline) + { + if (spatialPipeline != null) + { + return spatialPipeline.GeographyPipeline; + } + + return null; + } + + /// + /// Performs an implicit conversion from to . + /// + /// The spatial chain. + /// + /// The result of the conversion. + /// + [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "we have DrawGeometry and DrawGeography properties that can be used instead of the implicit cast")] + public static implicit operator GeometryPipeline(SpatialPipeline spatialPipeline) + { + if (spatialPipeline != null) + { + return spatialPipeline.GeometryPipeline; + } + + return null; + } + + /// Adds the next pipeline. + /// The last pipesegment in the chain, usually the one just created. + /// The next pipeline. + public virtual SpatialPipeline ChainTo(SpatialPipeline destination) + { + throw new NotImplementedException(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialReader.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialReader.cs new file mode 100644 index 0000000..f2ad99b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialReader.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Reader to be used by spatial formats + /// + /// The type of source that the reader operates on. + internal abstract class SpatialReader + { + /// + /// Creates a reader + /// + /// the instance of the pipeline that the reader will message while it is reading. + protected SpatialReader(SpatialPipeline destination) + { + Util.CheckArgumentNull(destination, "destination"); + + this.Destination = destination; + } + + /// + /// The pipeline that is messaged while the reader is reading. + /// + protected SpatialPipeline Destination { get; set; } + + /// + /// Parses some serialized format that represents one or more Geography spatial values, passing the first one down the pipeline. + /// + /// Throws if the input is not valid. In that case, guarantees that it will not pass anything down the pipeline, or will clear the pipeline by passing down a Reset. + /// The input string + public void ReadGeography(TSource input) + { + Util.CheckArgumentNull(input, "input"); + + try + { + this.ReadGeographyImplementation(input); + } + catch (Exception ex) + { + if (Util.IsCatchableExceptionType(ex)) + { + // transform it to a parse exception + throw new ParseErrorException(ex.Message, ex); + } + + throw; + } + } + + /// + /// Parses some serialized format that represents one or more Geometry spatial values, passing the first one down the pipeline. + /// + /// Throws if the input is not valid. In that case, guarantees that it will not pass anything down the pipeline, or will clear the pipeline by passing down a Reset. + /// The input string + public void ReadGeometry(TSource input) + { + Util.CheckArgumentNull(input, "input"); + + try + { + this.ReadGeometryImplementation(input); + } + catch (Exception ex) + { + if (Util.IsCatchableExceptionType(ex)) + { + // transform it to a parse exception + throw new ParseErrorException(ex.Message, ex); + } + + throw; + } + } + + /// + /// Sets the reader and underlying Destination back to a clean + /// starting state after an exception + /// + public virtual void Reset() + { + ((GeographyPipeline)Destination).Reset(); + ((GeometryPipeline)Destination).Reset(); + } + + /// + /// Parses some serialized format that represents one or more Geometry spatial values, passing the first one down the pipeline. + /// + /// Throws if the input is not valid. In that case, guarantees that it will not pass anything down the pipeline, or will clear the pipeline by passing down a Reset. + /// The input string + protected abstract void ReadGeometryImplementation(TSource input); + + /// + /// Parses some serialized format that represents one or more Geography spatial values, passing the first one down the pipeline. + /// + /// Throws if the input is not valid. In that case, guarantees that it will not pass anything down the pipeline, or will clear the pipeline by passing down a Reset. + /// The input string + protected abstract void ReadGeographyImplementation(TSource input); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTreeBuilder.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTreeBuilder.cs new file mode 100644 index 0000000..43e11ab --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTreeBuilder.cs @@ -0,0 +1,273 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + + /// + /// Tree based builder for spatial types + /// + /// Geography or Geometry + internal abstract class SpatialTreeBuilder : TypeWashedPipeline where T : class, ISpatial + { + /// + /// The figure this builder is currently building + /// + private List currentFigure; + + /// + /// Current builder tree root + /// + private SpatialBuilderNode currentNode; + + /// + /// lastConstructed + /// + private SpatialBuilderNode lastConstructedNode; + + /// + /// Fires when the builder creates a top-level spatial object. + /// + public event Action ProduceInstance; + + /// + /// Get the constructed spatial instance + /// + /// The constructed spatial instance + public T ConstructedInstance + { + get + { + if (this.lastConstructedNode == null || this.lastConstructedNode.Instance == null || this.lastConstructedNode.Parent != null) + { + throw new InvalidOperationException(Strings.SpatialBuilder_CannotCreateBeforeDrawn); + } + + return this.lastConstructedNode.Instance; + } + } + + /// + /// Gets a value indicating whether this instance is geography. + /// + /// + /// true if this instance is geography; otherwise, false. + /// + public override bool IsGeography + { + get { return typeof(Geography).IsAssignableFrom(typeof(T)); } + } + + /// + /// Draw a point in the specified coordinate + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + internal override void LineTo(double x, double y, double? z, double? m) + { + this.currentFigure.Add(this.CreatePoint(false, x, y, z, m)); + } + + /// + /// Begin drawing a figure + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + internal override void BeginFigure(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + if (this.currentFigure == null) + { + this.currentFigure = new List(); + } + + this.currentFigure.Add(this.CreatePoint(false, coordinate1, coordinate2, coordinate3, coordinate4)); + } + + /// + /// Begin a new spatial type + /// + /// The spatial type + internal override void BeginGeo(SpatialType type) + { + Debug.Assert(type != SpatialType.Unknown, "cannot build unknown type - should have validated this call"); + + // traverse down the tree); + if (this.currentNode == null) + { + this.currentNode = new SpatialBuilderNode { Type = type }; + + // we are just getting started, clear the last built + this.lastConstructedNode = null; + } + else + { + this.currentNode = this.currentNode.CreateChildren(type); + } + } + + /// + /// Ends the figure set on the current node + /// + internal override void EndFigure() + { + Debug.Assert(this.currentFigure != null, "current figure is null - call AddPointToNode before ending a figure"); + + // Attach the figure to the current node + if (this.currentFigure.Count == 1) + { + // point + SpatialBuilderNode pointNode = this.currentNode.CreateChildren(SpatialType.Point); + pointNode.Instance = this.currentFigure[0]; + } + else + { + SpatialBuilderNode lineStringNode = this.currentNode.CreateChildren(SpatialType.LineString); + lineStringNode.Instance = this.CreateShapeInstance(SpatialType.LineString, this.currentFigure); + } + + // need to clear current figure, since next geography may not contain a figure + this.currentFigure = null; + } + + /// + /// Ends the current spatial object + /// + internal override void EndGeo() + { + // Create the geography instance + switch (this.currentNode.Type) + { + case SpatialType.Point: + this.currentNode.Instance = this.currentNode.Children.Count > 0 ? this.currentNode.Children[0].Instance : this.CreatePoint(true, double.NaN, double.NaN, null, null); + break; + case SpatialType.LineString: + this.currentNode.Instance = this.currentNode.Children.Count > 0 ? this.currentNode.Children[0].Instance : this.CreateShapeInstance(SpatialType.LineString, new T[0]); + break; + case SpatialType.Polygon: + case SpatialType.MultiPoint: + case SpatialType.MultiLineString: + case SpatialType.MultiPolygon: + case SpatialType.Collection: + this.currentNode.Instance = this.CreateShapeInstance(this.currentNode.Type, this.currentNode.Children.Select(node => node.Instance)); + break; + case SpatialType.FullGlobe: + this.currentNode.Instance = this.CreateShapeInstance(SpatialType.FullGlobe, new T[0]); + break; + default: + Debug.Assert(false, "Unknown geography type"); + break; + } + + this.TraverseUpTheTree(); + this.NotifyIfWeJustFinishedBuildingSomething(); + } + + /// + /// Setup the pipeline for reuse + /// + internal override void Reset() + { + this.currentNode = null; + this.currentFigure = null; + } + + /// + /// Create a new instance of Point + /// + /// Whether the point is empty + /// X + /// Y + /// Z + /// M + /// A new instance of point + protected abstract T CreatePoint(bool isEmpty, double x, double y, double? z, double? m); + + /// + /// Create a new instance of T + /// + /// The spatial type to create + /// The arguments + /// A new instance of T + protected abstract T CreateShapeInstance(SpatialType type, IEnumerable spatialData); + + /// + /// Notifies if we just finished building something. + /// + private void NotifyIfWeJustFinishedBuildingSomething() + { + if (this.currentNode == null && this.ProduceInstance != null) + { + this.ProduceInstance(this.lastConstructedNode.Instance); + } + } + + /// + /// Traverses up the tree. + /// + private void TraverseUpTheTree() + { + this.lastConstructedNode = this.currentNode; + this.currentNode = this.currentNode.Parent; + } + + /// + /// A spatial instance node in the builder tree + /// + private class SpatialBuilderNode + { + /// + /// Constructor + /// + public SpatialBuilderNode() + { + this.Children = new List(); + } + + /// + /// Children nodes + /// + public List Children { get; private set; } + + /// + /// Instance + /// + public T Instance { get; set; } + + /// + /// Parent node + /// + public SpatialBuilderNode Parent { get; private set; } + + /// + /// Spatial Type + /// + public SpatialType Type { get; set; } + + /// + /// Create a child node + /// + /// The node type + /// The child node + internal SpatialBuilderNode CreateChildren(SpatialType type) + { + var node = new SpatialBuilderNode { Parent = this, Type = type }; + this.Children.Add(node); + return node; + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialType.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialType.cs new file mode 100644 index 0000000..bc28cb1 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialType.cs @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Defines a list of allowed OpenGisTypes types. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028", Justification = "byte required for packing")] + public enum SpatialType : byte + { + /// + /// Unknown + /// + Unknown = 0, + + /// + /// Point + /// + Point = 1, + + /// + /// Line String + /// + LineString = 2, + + /// + /// Polygon + /// + Polygon = 3, + + /// + /// Multi-Point + /// + MultiPoint = 4, + + /// + /// Multi-Line-String + /// + MultiLineString = 5, + + /// + /// Multi-Polygon + /// + MultiPolygon = 6, + + /// + /// Collection + /// + Collection = 7, + +#if CURVE_SUPPORT + /// + /// Circular-String + /// + CircularString = 8, + + /// + /// Compound Curve + /// + CompoundCurve = 9, + + /// + /// Curve Polygon + /// + CurvePolygon = 10, +#endif + + /// + /// Full Globe + /// + FullGlobe = 11 + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTypeExtensions.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTypeExtensions.cs new file mode 100644 index 0000000..f860a16 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialTypeExtensions.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// Provides a place to add extension methods that work with ISpatial. + public static class SpatialTypeExtensions + { + /// Allows the delegation of the call to the proper type (geography or Geometry). + /// The instance that will have SendTo called. + /// The pipeline that the instance will be sent to. + public static void SendTo(this ISpatial shape, SpatialPipeline destination) + { + if (shape == null) + { + return; + } + + if (shape.GetType().IsSubclassOf(typeof(Geometry))) + { + ((Geometry)shape).SendTo(destination); + } + else + { + ((Geography)shape).SendTo(destination); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidator.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidator.cs new file mode 100644 index 0000000..dd7052b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidator.cs @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Base class for Spatial Type Validator implementations + /// + public static class SpatialValidator + { + /// Creates the currently registered SpatialValidator implementation. + /// The created SpatialValidator. + public static SpatialPipeline Create() + { + return SpatialImplementation.CurrentImplementation.CreateValidator(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidatorImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidatorImplementation.cs new file mode 100644 index 0000000..f870aeb --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/SpatialValidatorImplementation.cs @@ -0,0 +1,1391 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + + /// + /// Semantically validate a GeoData + /// + /// + /// Grammar, states, and actions: + /// := SetSRID { Finish } + /// := (Begin_Point | ... | Begin_FullGlobe (: verify depth = 1 :) ) + /// := [ BeginFigure 1 EndFigure ] 2 End + /// := [ BeginFigure 1 { LineTo } EndFigure (: verify 2+ points :) ] 2 End + /// := { BeginFigure 1 { LineTo } EndFigure (: verify 4+ points and closed :) } End + /// := { { SetSRID } Begin_Point } End + /// := { { SetSRID } Begin_LineString } End + /// := { { SetSRID } Begin_Polygon } End + /// := { { SetSRID } } End + /// := End + /// := [ BeginFigure 1 { AddCircularArc } EndFigure ] 2 End + /// := [ BeginFigure 1 { LineTo | AddCircularArc } EndFigure ] | 2 End + /// := { } EndFigure + /// := AddSegmentLine 0 BeginFigure { LineTo } | AddSegmentArc 0 BeginFigure { AddCircularArc } + /// := AddSegmentLine { LineTo } | AddSegmentArc { AddCircularArc } + /// := { | | EndFigure (: verify closed and three distinct :)} End + /// := BeginFigure 1 { LineTo | AddCircularArc } + /// := StartSimpleRing 0 + /// := { } + /// := AddSegmentLine 0 BeginFigure { LineTo } | AddSegmentArc 0 BeginFigure { AddCircularArc } + /// := AddSegmentLine { LineTo } | AddSegmentArc { AddCircularArc } + /// ]]> + /// + internal class SpatialValidatorImplementation : SpatialPipeline + { + /// + /// Max value for Longitude + /// + /// + /// ~263 radians converted to degrees + /// + internal const double MaxLongitude = 15069; + + /// + /// Max value for latitude + /// + internal const double MaxLatitude = 90; + + /// + /// The DrawBoth derived instance of the geography Validator that is nested in this class + /// + private readonly NestedValidator geographyValidatorInstance = new NestedValidator(); + + /// + /// The DrawBoth derived instance of the geometry Validator that is nested in this class + /// + private readonly NestedValidator geometryValidatorInstance = new NestedValidator(); + + /// + /// Gets the draw geography. + /// + public override GeographyPipeline GeographyPipeline + { + get { return this.geographyValidatorInstance.GeographyPipeline; } + } + + /// + /// Gets the draw geometry. + /// + public override GeometryPipeline GeometryPipeline + { + get { return this.geometryValidatorInstance.GeometryPipeline; } + } + + /// + /// this is the actual validator, and derived from DrawBoth + /// while the real SpatialValidator derives from DrawSpatial. + /// We simple create an instance of this nested class and pass back + /// the DrawGeometry, and DrawGeography when the outter classes DataSpatial + /// properties are accessed. + /// + private class NestedValidator : DrawBoth + { + /// + /// Set coordinate system + /// + private static readonly ValidatorState CoordinateSystem = new SetCoordinateSystemState(); + + /// + /// BeginGeo + /// + private static readonly ValidatorState BeginSpatial = new BeginGeoState(); + + /// + /// Starting a point + /// + private static readonly ValidatorState PointStart = new PointStartState(); + + /// + /// Building a point + /// + private static readonly ValidatorState PointBuilding = new PointBuildingState(); + + /// + /// Ending a point + /// + private static readonly ValidatorState PointEnd = new PointEndState(); + + /// + /// Starting a LineString + /// + private static readonly ValidatorState LineStringStart = new LineStringStartState(); + + /// + /// Building a LineString + /// + private static readonly ValidatorState LineStringBuilding = new LineStringBuildingState(); + + /// + /// Ending a LineString + /// + private static readonly ValidatorState LineStringEnd = new LineStringEndState(); + + /// + /// Starting a Polygon + /// + private static readonly ValidatorState PolygonStart = new PolygonStartState(); + + /// + /// Building a Polygon + /// + private static readonly ValidatorState PolygonBuilding = new PolygonBuildingState(); + + /// + /// Starting a MultiPoint + /// + private static readonly ValidatorState MultiPoint = new MultiPointState(); + + /// + /// Starting a LineString + /// + private static readonly ValidatorState MultiLineString = new MultiLineStringState(); + + /// + /// Starting a MultiPolygon + /// + private static readonly ValidatorState MultiPolygon = new MultiPolygonState(); + + /// + /// Starting a Collection + /// + private static readonly ValidatorState Collection = new CollectionState(); + + /// + /// Starting a FullGlobe + /// + private static readonly ValidatorState FullGlobe = new FullGlobeState(); + + /// + /// Geometry Functional Specification 3.2.3.4 + /// Max Geometry Collection Depth + /// + private const int MaxGeometryCollectionDepth = 28; + + /// + /// States + /// + private readonly Stack stack = new Stack(16); + + /// + /// CoordinateSystem + /// + private CoordinateSystem validationCoordinateSystem; + + /// + /// Number of rings in a polygon + /// + private int ringCount; + + /// + /// First point's X coordinate + /// + private double initialFirstCoordinate; + + /// + /// First point's Y coordinate + /// + private double initialSecondCoordinate; + + /// + /// Last point's X coordinate + /// + private double mostRecentFirstCoordinate; + + /// + /// Last point's Y coordinate + /// + private double mostRecentSecondCoordinate; + + /// + /// we are validating a geography stream + /// + private bool processingGeography; + +#if CURVE_SUPPORT + /// + /// Last point's Z coordinate + /// + private double? mostRecentZValue; +#endif + + /// + /// Number of points in the GeoData + /// + private int pointCount; + + /// + /// Stack depth + /// + private int depth; + + /// + /// Constructs a new SpatialValidatorImplementation segment + /// + public NestedValidator() + { + InitializeObject(); + } + + /// + /// Calls to the pipeline interface Represented as state transition + /// + private enum PipelineCall + { + /// + /// Set CoordinateSystem + /// + SetCoordinateSystem, + + /// + /// BeginGeo() + /// + /// fake transition, just for exception + Begin, + + /// + /// BeginGeo(point) + /// + BeginPoint, + + /// + /// BeginGeo(LineString) + /// + BeginLineString, + + /// + /// BeginGeo(Polygon) + /// + BeginPolygon, + + /// + /// BeginGeo(MultiPoint) + /// + BeginMultiPoint, + + /// + /// BeginGeo(MultiLineString) + /// + BeginMultiLineString, + + /// + /// BeginGeo(MultiPolygon) + /// + BeginMultiPolygon, + + /// + /// BeginGeo(Collection) + /// + BeginCollection, + + /// + /// BeginGeo(FullGlobe) + /// + BeginFullGlobe, + + /// + /// BeginFigure + /// + BeginFigure, + + /// + /// LineTo + /// + LineTo, +#if CURVE_SUPPORT + BeginCircularString, + BeginCompoundCurve, + BeginCurvePolygon, + AddCircularArc, + AddSegmentLine, + AddSegmentArc, + StartSimpleRing, + StartCompoundCurveRing, +#endif + /// + /// EndFigure + /// + EndFigure, + + /// + /// EndGeo + /// + End, + } + + /// + /// Implemented by a subclass to handle the setting of a coordinate system + /// + /// the new coordinate system + /// the coordinate system to be passed down the pipeline + protected override CoordinateSystem OnSetCoordinateSystem(CoordinateSystem coordinateSystem) + { + ValidatorState currentState = this.stack.Peek(); + Execute(PipelineCall.SetCoordinateSystem); + + if (currentState == CoordinateSystem) + { + this.validationCoordinateSystem = coordinateSystem; + } + else if (this.validationCoordinateSystem != coordinateSystem) + { + throw new FormatException(Strings.Validator_SridMismatch); + } + + return coordinateSystem; + } + + /// + /// Implemented by a subclass to handle the start of drawing a Geography figure + /// + /// the shape to draw + /// the SpatialType to be passed down the pipeline + protected override SpatialType OnBeginGeography(SpatialType shape) + { + if (this.depth > 0 && !this.processingGeography) + { + throw new FormatException(Strings.Validator_UnexpectedGeometry); + } + + this.processingGeography = true; + BeginShape(shape); + + return shape; + } + + /// + /// Implemented by a subclass to handle the end of drawing a Geography figure + /// + protected override void OnEndGeography() + { + Execute(PipelineCall.End); + if (!this.processingGeography) + { + throw new FormatException(Strings.Validator_UnexpectedGeometry); + } + + this.depth -= 1; + } + + /// + /// Implemented by a subclass to handle the start of drawing a Geometry figure + /// + /// the shape to draw + /// the SpatialType to be passed down the pipeline + protected override SpatialType OnBeginGeometry(SpatialType shape) + { + if (this.depth > 0 && this.processingGeography) + { + throw new FormatException(Strings.Validator_UnexpectedGeography); + } + + this.processingGeography = false; + BeginShape(shape); + return shape; + } + + /// + /// Implemented by a subclass to handle the end of drawing a Geometry figure + /// + protected override void OnEndGeometry() + { + Execute(PipelineCall.End); + if (this.processingGeography) + { + throw new FormatException(Strings.Validator_UnexpectedGeography); + } + + this.depth -= 1; + } + + /// + /// Implemented by a subclass to handle the start of a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeographyPosition OnBeginFigure(GeographyPosition position) + { + BeginFigure(ValidateGeographyPosition, position.Latitude, position.Longitude, position.Z, position.M); + return position; + } + + /// + /// Implemented by a subclass to handle the start of a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeometryPosition OnBeginFigure(GeometryPosition position) + { + BeginFigure(ValidateGeometryPosition, position.X, position.Y, position.Z, position.M); + return position; + } + + /// + /// Implemented by a subclass to handle the end of a figure + /// + protected override void OnEndFigure() + { + Execute(PipelineCall.EndFigure); + } + + /// + /// Implemented by a subclass to return to its initial state + /// + protected override void OnReset() + { + InitializeObject(); + } + + /// + /// Implemented by a subclass to handle the addition of a waypoint to a Geography figure + /// + /// Next position + /// the GeographyPosition to be passed down the pipeline + protected override GeographyPosition OnLineTo(GeographyPosition position) + { + if (this.processingGeography) + { + ValidateGeographyPosition(position.Latitude, position.Longitude, position.Z, position.M); + } + + AddControlPoint(position.Latitude, position.Longitude); + + if (!this.processingGeography) + { + throw new FormatException(Strings.Validator_UnexpectedGeometry); + } + + return position; + } + + /// + /// Implemented by a subclass to handle the addition of a waypoint to a Geometry figure + /// + /// Next position + /// the GeometryPosition to be passed down the pipeline + protected override GeometryPosition OnLineTo(GeometryPosition position) + { + if (!this.processingGeography) + { + ValidateGeometryPosition(position.X, position.Y, position.Z, position.M); + } + + AddControlPoint(position.X, position.Y); + + if (this.processingGeography) + { + throw new FormatException(Strings.Validator_UnexpectedGeography); + } + + return position; + } + + /// + /// Test whether a double is finite + /// + /// The double value + /// True if the input double is not NaN or INF + private static bool IsFinite(double value) + { + return !double.IsNaN(value) && !double.IsInfinity(value); + } + + /// + /// Test whether a point is in valid format + /// + /// The first coordinate + /// The second coordinate + /// The z coordinate + /// The m coordinate + /// Whether the input coordinate is valid + [SuppressMessage("Microsoft.Naming", "CA1704", Justification = "z and m are meaningful")] + private static bool IsPointValid(double first, double second, double? z, double? m) + { + return IsFinite(first) && IsFinite(second) && (z == null || IsFinite(z.Value)) && + (m == null || IsFinite(m.Value)); + } + + /// + /// Validate one position + /// + /// the first two dimensional co-ordinate + /// the second two dimensional co-ordinate + /// the altitude + /// the measure + private static void ValidateOnePosition(double first, double second, double? z, double? m) + { + if (!IsPointValid(first, second, z, m)) + { + throw new FormatException(Strings.Validator_InvalidPointCoordinate(first, second, z, m)); + } + } + + /// + /// Validate one Geography position + /// + /// the latitude + /// the longitude + /// the altitude + /// the measure + private static void ValidateGeographyPosition(double latitude, double longitude, double? z, double? m) + { + ValidateOnePosition(latitude, longitude, z, m); + if (!IsLatitudeValid(latitude)) + { + throw new FormatException(Strings.Validator_InvalidLatitudeCoordinate(latitude)); + } + + if (!IsLongitudeValid(longitude)) + { + throw new FormatException(Strings.Validator_InvalidLongitudeCoordinate(longitude)); + } + } + + /// + /// Validate one Geography position + /// + /// the x coordinate + /// the y coordinate + /// the altitude + /// the measure + private static void ValidateGeometryPosition(double x, double y, double? z, double? m) + { + ValidateOnePosition(x, y, z, m); + } + + /// + /// Test whether a latitude value is within acceptable range + /// + /// The latitude value + /// True if the latitude value is within range + private static bool IsLatitudeValid(double latitude) + { + return latitude >= -MaxLatitude && latitude <= MaxLatitude; + } + + /// + /// Test whether a longitude value is within acceptable range + /// + /// The longitude value + /// True if the longitude value is within range + private static bool IsLongitudeValid(double longitude) + { + return longitude >= -MaxLongitude && longitude <= MaxLongitude; + } + + /// + /// Validate a Geography polygon + /// + /// The number of points in the ring + /// its first latitude + /// it first longitued + /// its last latitude + /// its last longitude + private static void ValidateGeographyPolygon( + int numOfPoints, + double initialFirstCoordinate, + double initialSecondCoordinate, + double mostRecentFirstCoordinate, + double mostRecentSecondCoordinate) + { + if (numOfPoints < 4 || initialFirstCoordinate != mostRecentFirstCoordinate || + !AreLongitudesEqual(initialSecondCoordinate, mostRecentSecondCoordinate)) + { + throw new FormatException(Strings.Validator_InvalidPolygonPoints); + } + } + + /// + /// Validate a Geometry polygon + /// + /// The number of points in the ring + /// its first x + /// it first y + /// its last x + /// its last y + private static void ValidateGeometryPolygon( + int numOfPoints, + double initialFirstCoordinate, + double initialSecondCoordinate, + double mostRecentFirstCoordinate, + double mostRecentSecondCoordinate) + { + if (numOfPoints < 4 || initialFirstCoordinate != mostRecentFirstCoordinate || + initialSecondCoordinate != mostRecentSecondCoordinate) + { + throw new FormatException(Strings.Validator_InvalidPolygonPoints); + } + } + + /// + /// Test whether two longitude values are equal + /// + /// Left longitude + /// Right longitude + /// True if the two longitudes are equals + private static bool AreLongitudesEqual(double left, double right) + { + return left == right || (left - right) % 360 == 0; + } + + /// + /// Begins the figure. + /// + /// The validate action. + /// The x. + /// The y. + /// The z. + /// The m. + private void BeginFigure(Action validate, double x, double y, double? z, double? m) + { + validate(x, y, z, m); + this.Execute(PipelineCall.BeginFigure); + this.pointCount = 0; + this.TrackPosition(x, y); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + private void BeginShape(SpatialType type) + { + this.depth += 1; + + switch (type) + { + case SpatialType.Point: + Execute(PipelineCall.BeginPoint); + break; + case SpatialType.LineString: + Execute(PipelineCall.BeginLineString); + break; + case SpatialType.Polygon: + Execute(PipelineCall.BeginPolygon); + break; + case SpatialType.MultiPoint: + Execute(PipelineCall.BeginMultiPoint); + break; + case SpatialType.MultiLineString: + Execute(PipelineCall.BeginMultiLineString); + break; + case SpatialType.MultiPolygon: + Execute(PipelineCall.BeginMultiPolygon); + break; + case SpatialType.Collection: + Execute(PipelineCall.BeginCollection); + break; + case SpatialType.FullGlobe: + if (!this.processingGeography) + { + throw new FormatException(Strings.Validator_InvalidType(type)); + } + + Execute(PipelineCall.BeginFullGlobe); + break; +#if CURVE_SUPPORT + case SpatialType.CircularString: + Execute(Transition.Begin_CircularString); + break; + + case SpatialType.CompoundCurve: + Execute(Transition.Begin_CompoundCurve); + break; + + case SpatialType.CurvePolygon: + Execute(Transition.Begin_CurvePolygon); + break; +#endif + default: + throw new FormatException(Strings.Validator_InvalidType(type)); + } + } + + /// + /// Add a control point to the current figure. + /// + /// the first coordinate + /// the second coordinate + private void AddControlPoint(double first, double second) + { + Execute(PipelineCall.LineTo); + + TrackPosition(first, second); + } + + /// + /// Tracks the position. + /// + /// The first. + /// The second. + private void TrackPosition(double first, double second) + { + if (this.pointCount == 0) + { + this.initialFirstCoordinate = first; + this.initialSecondCoordinate = second; + } + + this.mostRecentFirstCoordinate = first; + this.mostRecentSecondCoordinate = second; +#if CURVE_SUPPORT + this.mostRecentZValue = z; +#endif + this.pointCount += 1; + } + + /// + /// Transit into a new state + /// + /// The state to transit into + private void Execute(PipelineCall transition) + { + ValidatorState state = this.stack.Peek(); + state.ValidateTransition(transition, this); + } + + /// + /// initialize the object to a fresh clean smelling state + /// + private void InitializeObject() + { + this.depth = default(int); + this.initialFirstCoordinate = default(double); + this.initialSecondCoordinate = default(double); + this.mostRecentFirstCoordinate = default(double); + this.mostRecentSecondCoordinate = default(double); + this.pointCount = default(int); + this.validationCoordinateSystem = null; + this.ringCount = default(int); + + this.stack.Clear(); + this.stack.Push(CoordinateSystem); + } + + /// + /// Push a new state onto the stack + /// + /// The new state + private void Call(ValidatorState state) + { + if (this.stack.Count > MaxGeometryCollectionDepth) + { + throw new FormatException(Strings.Validator_NestingOverflow(MaxGeometryCollectionDepth)); + } + + this.stack.Push(state); + } + + /// + /// Pop a state from the stack + /// + private void Return() + { + this.stack.Pop(); + Debug.Assert(this.stack.Count > 0, "the stack should always have the SetCoordinateSystemState"); + } + + /// + /// Replace the current state on the stack with the new state + /// + /// The new state + private void Jump(ValidatorState state) + { + this.stack.Pop(); + Debug.Assert(this.stack.Count > 0, "the stack should always have the SetCoordinateSystemState"); + this.stack.Push(state); + } + + #region State Validation + + /// + /// SpatialValidatorImplementation State + /// + private abstract class ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal abstract void ValidateTransition( + PipelineCall transition, + NestedValidator validator); + + /// + /// Throw an incorrect state exception + /// + /// The expected state + /// The actual state + protected static void ThrowExpected(PipelineCall transition, PipelineCall actual) + { + throw new FormatException(Strings.Validator_UnexpectedCall(transition, actual)); + } + + /// + /// Throw an incorrect state exception + /// + /// The expected state1 + /// The expected state2 + /// The actual state + protected static void ThrowExpected( + PipelineCall transition1, + PipelineCall transition2, + PipelineCall actual) + { + throw new FormatException(Strings.Validator_UnexpectedCall2(transition1, transition2, actual)); + } + + /// + /// Throw an incorrect state exception + /// + /// The expected state1 + /// The expected state2 + /// The expected state3 + /// The actual state + protected static void ThrowExpected( + PipelineCall transition1, + PipelineCall transition2, + PipelineCall transition3, + PipelineCall actual) + { + throw new FormatException(Strings.Validator_UnexpectedCall2(transition1 + ", " + transition2, transition3, actual)); + } + } + + /// + /// SetCoordinateSystem State + /// Validator is currently waiting for a SetCoordinateSystemCall + /// + private class SetCoordinateSystemState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition( + PipelineCall transition, + NestedValidator validator) + { + switch (transition) + { + case PipelineCall.SetCoordinateSystem: + validator.Call(BeginSpatial); + return; + default: + ThrowExpected(PipelineCall.SetCoordinateSystem, transition); + return; + } + } + } + + /// + /// Beginning a GeoData + /// Validator is currently waiting for a BeginGeo() call + /// + private class BeginGeoState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition( + PipelineCall transition, + NestedValidator validator) + { + switch (transition) + { + case PipelineCall.BeginPoint: + validator.Jump(PointStart); + return; + case PipelineCall.BeginLineString: + validator.Jump(LineStringStart); + return; + case PipelineCall.BeginPolygon: + validator.Jump(PolygonStart); + return; + case PipelineCall.BeginMultiPoint: + validator.Jump(MultiPoint); + return; + case PipelineCall.BeginMultiLineString: + validator.Jump(MultiLineString); + return; + case PipelineCall.BeginMultiPolygon: + validator.Jump(MultiPolygon); + return; + case PipelineCall.BeginCollection: + validator.Jump(Collection); + return; + case PipelineCall.BeginFullGlobe: + if (validator.depth != 1) + { + throw new FormatException(Strings.Validator_FullGlobeInCollection); + } + + validator.Jump(FullGlobe); + return; +#if CURVE_SUPPORT + case Transition.BeginCircularString: + Jump(CircularString); + return; + case Transition.BeginCompoundCurve: + Jump(CompoundCurve); + return; + case Transition.BeginCurvePolygon: + Jump(CurvePolygon); + return; +#endif + default: + ThrowExpected(PipelineCall.Begin, transition); + return; + } + } + } + + /// + /// Point Start State + /// After BeginGeo(Point), waiting for BeginFigure() or EndGeo() + /// + private class PointStartState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.BeginFigure: + validator.Jump(PointBuilding); + return; + case PipelineCall.End: + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.BeginFigure, PipelineCall.End, transition); + return; + } + } + } + + /// + /// Point Building State + /// After BeginFigure(), waiting for EndFigure() immediately + /// + private class PointBuildingState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.LineTo: + if (validator.pointCount != 0) + { + ThrowExpected(PipelineCall.EndFigure, transition); + } + + return; + + case PipelineCall.EndFigure: + if (validator.pointCount == 0) + { + ThrowExpected(PipelineCall.BeginFigure, transition); + } + + validator.Jump(PointEnd); + return; + + default: + ThrowExpected(PipelineCall.EndFigure, transition); + return; + } + } + } + + /// + /// Point End State + /// After EndFigure() for a point, waiting for EndGeo() + /// + private class PointEndState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.End: + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.End, transition); + return; + } + } + } + + /// + /// LineString Start state + /// After BeginGeo(LineString), waiting for BeginFigure/EndGeo + /// + private class LineStringStartState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.BeginFigure: + validator.Jump(LineStringBuilding); + return; + case PipelineCall.End: + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.BeginFigure, PipelineCall.End, transition); + return; + } + } + } + + /// + /// LineString Building State + /// After BeginFigure() for a line + /// Waiting for LineTo/EndFigure + /// + private class LineStringBuildingState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.LineTo: + return; + case PipelineCall.EndFigure: + if (validator.pointCount < 2) + { + throw new FormatException(Strings.Validator_LineStringNeedsTwoPoints); + } + + validator.Jump(LineStringEnd); + return; + default: + ThrowExpected(PipelineCall.LineTo, PipelineCall.EndFigure, transition); + return; + } + } + } + + /// + /// LineString End State + /// After EndFigure() on Line + /// Waiting for EndGeo + /// + private class LineStringEndState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.End: + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.End, transition); + return; + } + } + } + + /// + /// PolygonStart State + /// After polygon started, waiting for Rings to build + /// + private class PolygonStartState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.BeginFigure: + validator.Jump(PolygonBuilding); + return; + case PipelineCall.End: + validator.ringCount = 0; + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.BeginFigure, PipelineCall.End, transition); + return; + } + } + } + + /// + /// Polygon Building State + /// Drawing rings + /// + private class PolygonBuildingState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.LineTo: + return; + case PipelineCall.EndFigure: + validator.ringCount += 1; + if (validator.processingGeography) + { + ValidateGeographyPolygon( + validator.pointCount, + validator.initialFirstCoordinate, + validator.initialSecondCoordinate, + validator.mostRecentFirstCoordinate, + validator.mostRecentSecondCoordinate); + } + else + { + ValidateGeometryPolygon( + validator.pointCount, + validator.initialFirstCoordinate, + validator.initialSecondCoordinate, + validator.mostRecentFirstCoordinate, + validator.mostRecentSecondCoordinate); + } + + validator.Jump(PolygonStart); + return; + default: + ThrowExpected(PipelineCall.LineTo, PipelineCall.EndFigure, transition); + return; + } + } + } + + /// + /// MultiPoint State + /// Inside a MultiPoint Container + /// + private class MultiPointState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.SetCoordinateSystem: + return; + case PipelineCall.BeginPoint: + validator.Call(PointStart); + return; + case PipelineCall.End: + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.SetCoordinateSystem, PipelineCall.BeginPoint, PipelineCall.End, transition); + return; + } + } + } + + /// + /// MultiLineString State + /// Inside a MultiLineString container + /// + private class MultiLineStringState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.SetCoordinateSystem: + return; + case PipelineCall.BeginLineString: + validator.Call(LineStringStart); + return; + case PipelineCall.End: + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.SetCoordinateSystem, PipelineCall.BeginLineString, PipelineCall.End, transition); + return; + } + } + } + + /// + /// MultiPolygon State + /// Inside a MultiPolygon container + /// + private class MultiPolygonState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.SetCoordinateSystem: + return; + case PipelineCall.BeginPolygon: + validator.Call(PolygonStart); + return; + case PipelineCall.End: + validator.Return(); + return; + default: + ThrowExpected(PipelineCall.SetCoordinateSystem, PipelineCall.BeginPolygon, PipelineCall.End, transition); + return; + } + } + } + + /// + /// Collection State + /// Inside a Collection container + /// + private class CollectionState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.SetCoordinateSystem: + return; + case PipelineCall.BeginPoint: + validator.Call(PointStart); + return; + case PipelineCall.BeginLineString: + validator.Call(LineStringStart); + return; + case PipelineCall.BeginPolygon: + validator.Call(PolygonStart); + return; + case PipelineCall.BeginMultiPoint: + validator.Call(MultiPoint); + return; + case PipelineCall.BeginMultiLineString: + validator.Call(MultiLineString); + return; + case PipelineCall.BeginMultiPolygon: + validator.Call(MultiPolygon); + return; + case PipelineCall.BeginCollection: + validator.Call(Collection); + return; + case PipelineCall.End: + validator.Return(); + return; + case PipelineCall.BeginFullGlobe: + throw new FormatException(Strings.Validator_FullGlobeInCollection); +#if CURVE_SUPPORT + case Transition.Begin_CircularString: + Call(CircularString); + return; + + case Transition.Begin_CompoundCurve: + Call(CompoundCurve); + return; + + case Transition.Begin_CurvePolygon: + Call(CurvePolygon); + return; +#endif + default: + ThrowExpected(PipelineCall.SetCoordinateSystem, PipelineCall.Begin, PipelineCall.End, transition); + return; + } + } + } + + /// + /// FullGlobe state + /// Inside a FullGlobe container + /// + private class FullGlobeState : ValidatorState + { + /// + /// Validate a call to the pipeline interface (a state transition) + /// + /// The transition + /// The validator instance + internal override void ValidateTransition(PipelineCall transition, NestedValidator validator) + { + switch (transition) + { + case PipelineCall.End: + validator.Return(); + return; + default: + throw new FormatException(Strings.Validator_FullGlobeCannotHaveElements); + } + } + } + + #endregion + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/TextLexerBase.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/TextLexerBase.cs new file mode 100644 index 0000000..b08b40f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/TextLexerBase.cs @@ -0,0 +1,174 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Diagnostics; + using System.IO; + using System.Text; + + /// + /// Lexer base + /// + internal abstract class TextLexerBase + { + /// + /// Input text + /// + private TextReader reader; + + /// + /// Current lexer output + /// + private LexerToken currentToken; + + /// + /// Peek lexer output, if this is not null then we have advanced already + /// + private LexerToken peekToken; + + /// + /// Constructor + /// + /// The input text + protected TextLexerBase(TextReader text) + { + this.reader = text; + } + + /// + /// Current token + /// + public LexerToken CurrentToken + { + get { return this.currentToken; } + } + + /// + /// Peek one token ahead of the current position + /// + /// The peeked token + /// True if there is one more token after the current position, otherwise false + public bool Peek(out LexerToken token) + { + // read but don't advance + // implementation: actually advance the lexer + // but make sure Peek/Next call returns the same token + if (this.peekToken != null) + { + // we've already peeked + token = this.peekToken; + return true; + } + + // store current token + LexerToken temp = this.currentToken; + + // advance parser + if (this.Next()) + { + this.peekToken = this.currentToken; + token = this.currentToken; + + // restore current state + this.currentToken = temp; + return true; + } + else + { + this.peekToken = null; + token = null; + + // restore current state + this.currentToken = temp; + return false; + } + } + + /// + /// Move to the next token + /// + /// True if lexer has moved, otherwise false + public bool Next() + { + // DEVNOTE(pqian): + // This function is called a very large number of times during text parsing. + // For example, experiments show that during simple WKT parsing, this function accounts for over 70% + // of the workload. Thus, efforts should be taken to keep this function as light weight as possible. + // Token Accumulation Logic: + // we'll accumulate whenever we are: + // 1a. starting fresh, hence currentType is null. + // 1b. we are inside a token that's building, and the new character is still part of the same token. + // we should break out of the loop if: + // 2a. MatchTokenType tell us to (terminate = true) + // 2b. We are inside a token and we've encountered a new token (currentType and peek type is different) + // It follows that most of the time we will either accumulate the token or break out of the loop + // to return the token, except in one case, where we are starting fresh AND the first character is a terminal + // character (delimeter). In this case we will both accumulate that char and break out of the loop. + if (this.peekToken != null) + { + this.currentToken = this.peekToken; + this.peekToken = null; + return true; + } + + LexerToken originalToken = this.CurrentToken; + int? currentTokenType = null; + int textValue; + StringBuilder nextTokenText = null; + bool isDelimiter = false; + + while (!isDelimiter && (textValue = this.reader.Peek()) >= 0) + { + char currentChar = (char)textValue; + int newTokenType; + isDelimiter = this.MatchTokenType(currentChar, currentTokenType, out newTokenType); + + if (!currentTokenType.HasValue) + { + // fresh token + currentTokenType = newTokenType; + nextTokenText = new StringBuilder(); + nextTokenText.Append(currentChar); + this.reader.Read(); + } + else + { + // existing token + if (currentTokenType == newTokenType) + { + // continuation of the current token + nextTokenText.Append(currentChar); + this.reader.Read(); + } + else + { + // starting a new token + isDelimiter = true; + } + } + } + + // we got here due to end of stream, could still have unprocessed tokens + if (currentTokenType.HasValue) + { + Debug.Assert(nextTokenText != null, "Token text should not be null if current Token type has value"); + this.currentToken = new LexerToken() { Text = nextTokenText.ToString(), Type = currentTokenType.Value }; + } + + return originalToken != this.currentToken; + } + + /// + /// Examine the current character and determine its token type + /// + /// The char that will be read next + /// The currently active token type + /// The matched token type + /// Whether the current character is a delimiter, thereby terminate the current token immediately + protected abstract bool MatchTokenType(char nextChar, int? currentType, out int type); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedPipeline.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedPipeline.cs new file mode 100644 index 0000000..3895439 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedPipeline.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Internal pipeline Inteface that washes the distinction between Geography and Geometry + /// + internal abstract class TypeWashedPipeline + { + /// + /// Gets a value indicating whether this instance is geography. + /// + /// + /// true if this instance is geography; otherwise, false. + /// + public abstract bool IsGeography { get; } + + /// + /// Set the coordinate system based on the given EPSG ID + /// + /// The coordinate system ID to set. Null indicates the default should be used + internal abstract void SetCoordinateSystem(int? epsgId); + + /// + /// Setup the pipeline for reuse + /// + internal abstract void Reset(); + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + internal abstract void BeginGeo(SpatialType type); + + /// + /// Begin drawing a figure + /// + /// X or Latitude Coordinate + /// Y or Longitude Coordinate + /// Z Coordinate + /// M Coordinate + internal abstract void BeginFigure(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4); + + /// + /// Add a control point to the current figure + /// + /// First coordinate + /// Second coordinate + /// Third coordinate + /// Fourth coordinate + internal abstract void LineTo(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4); + + /// + /// Ends the current figure + /// + internal abstract void EndFigure(); + + /// + /// Ends the current spatial object + /// + internal abstract void EndGeo(); + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLatLongPipeline.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLatLongPipeline.cs new file mode 100644 index 0000000..19a5f6b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLatLongPipeline.cs @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Adapter from the type washed API to Geography, where it assumes that coord1 is Latitude. + /// + internal class TypeWashedToGeographyLatLongPipeline : TypeWashedPipeline + { + /// + /// The pipeline to redirect the calls to + /// + private readonly GeographyPipeline output; + + /// + /// Constructor + /// + /// The pipeline to redirect the calls to + public TypeWashedToGeographyLatLongPipeline(SpatialPipeline output) + { + this.output = output; + } + + /// + /// Gets a value indicating whether this instance is geography. + /// + /// + /// true if this instance is geography; otherwise, false. + /// + public override bool IsGeography + { + get { return true; } + } + + /// + /// Set the coordinate system based on the given ID + /// + /// The coordinate system ID to set. Null indicates the default should be used + internal override void SetCoordinateSystem(int? epsgId) + { + var coordinateSystem = CoordinateSystem.Geography(epsgId); + this.output.SetCoordinateSystem(coordinateSystem); + } + + /// + /// Setup the pipeline for reuse + /// + internal override void Reset() + { + output.Reset(); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + internal override void BeginGeo(SpatialType type) + { + this.output.BeginGeography(type); + } + + /// + /// Begin drawing a figure + /// + /// 1st Coordinate + /// 2nd Coordinate + /// 3rd Coordinate + /// 4th Coordinate + internal override void BeginFigure(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + this.output.BeginFigure(new GeographyPosition(coordinate1, coordinate2, coordinate3, coordinate4)); + } + + /// + /// Draw a line to a point in the specified coordinate + /// + /// 1st Coordinate + /// 2nd Coordinate + /// 3rd Coordinate + /// 4th Coordinate + internal override void LineTo(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + this.output.LineTo(new GeographyPosition(coordinate1, coordinate2, coordinate3, coordinate4)); + } + + /// + /// Ends the current figure + /// + internal override void EndFigure() + { + this.output.EndFigure(); + } + + /// + /// Ends the current spatial object + /// + internal override void EndGeo() + { + this.output.EndGeography(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLongLatPipeline.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLongLatPipeline.cs new file mode 100644 index 0000000..3ada415 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeographyLongLatPipeline.cs @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Adapter from the type washed API to Geography, where it assumes that coord1 is Longitude. + /// + internal class TypeWashedToGeographyLongLatPipeline : TypeWashedPipeline + { + /// + /// The pipeline to redirect the calls to + /// + private readonly GeographyPipeline output; + + /// + /// Constructor + /// + /// The pipeline to redirect the calls to + public TypeWashedToGeographyLongLatPipeline(SpatialPipeline output) + { + this.output = output; + } + + /// + /// Gets a value indicating whether this instance is geography. + /// + /// + /// true if this instance is geography; otherwise, false. + /// + public override bool IsGeography + { + get { return true; } + } + + /// + /// Set the coordinate system based on the given ID + /// + /// The coordinate system ID to set. Null indicates the default should be used + internal override void SetCoordinateSystem(int? epsgId) + { + var coordinateSystem = CoordinateSystem.Geography(epsgId); + this.output.SetCoordinateSystem(coordinateSystem); + } + + /// + /// Setup the pipeline for reuse + /// + internal override void Reset() + { + output.Reset(); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + internal override void BeginGeo(SpatialType type) + { + this.output.BeginGeography(type); + } + + /// + /// Begin drawing a figure + /// + /// 1st Coordinate + /// 2nd Coordinate + /// 3rd Coordinate + /// 4th Coordinate + internal override void BeginFigure(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + this.output.BeginFigure(new GeographyPosition(coordinate2, coordinate1, coordinate3, coordinate4)); + } + + /// + /// Draw a line to a point in the specified coordinate + /// + /// 1st Coordinate + /// 2nd Coordinate + /// 3rd Coordinate + /// 4th Coordinate + internal override void LineTo(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + this.output.LineTo(new GeographyPosition(coordinate2, coordinate1, coordinate3, coordinate4)); + } + + /// + /// Ends the current figure + /// + internal override void EndFigure() + { + this.output.EndFigure(); + } + + /// + /// Ends the current spatial object + /// + internal override void EndGeo() + { + this.output.EndGeography(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeometryPipeline.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeometryPipeline.cs new file mode 100644 index 0000000..df75845 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/TypeWashedToGeometryPipeline.cs @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Adapter from the type washed API to Geometry, where it assumes that coord1 is X. + /// + internal class TypeWashedToGeometryPipeline : TypeWashedPipeline + { + /// + /// The pipeline to redirect the calls to + /// + private readonly GeometryPipeline output; + + /// + /// Constructor + /// + /// The pipeline to redirect the calls to + public TypeWashedToGeometryPipeline(SpatialPipeline output) + { + this.output = output; + } + + /// + /// Gets a value indicating whether this instance is geography. + /// + /// + /// true if this instance is geography; otherwise, false. + /// + public override bool IsGeography + { + get { return false; } + } + + /// + /// Set the coordinate system based on the given ID + /// + /// The coordinate system ID to set. Null indicates the default should be used + internal override void SetCoordinateSystem(int? epsgId) + { + var coordinateSystem = CoordinateSystem.Geometry(epsgId); + this.output.SetCoordinateSystem(coordinateSystem); + } + + /// + /// Setup the pipeline for reuse + /// + internal override void Reset() + { + output.Reset(); + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + internal override void BeginGeo(SpatialType type) + { + this.output.BeginGeometry(type); + } + + /// + /// Begin drawing a figure + /// + /// 1st Coordinate + /// 2nd Coordinate + /// 3rd Coordinate + /// 4th Coordinate + internal override void BeginFigure(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + this.output.BeginFigure(new GeometryPosition(coordinate1, coordinate2, coordinate3, coordinate4)); + } + + /// + /// Draw a line to a point in the specified coordinate + /// + /// 1st Coordinate + /// 2nd Coordinate + /// 3rd Coordinate + /// 4th Coordinate + internal override void LineTo(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + this.output.LineTo(new GeometryPosition(coordinate1, coordinate2, coordinate3, coordinate4)); + } + + /// + /// Ends the current figure + /// + internal override void EndFigure() + { + this.output.EndFigure(); + } + + /// + /// Ends the current spatial object + /// + internal override void EndGeo() + { + this.output.EndGeometry(); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/Util.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/Util.cs new file mode 100644 index 0000000..fd83502 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/Util.cs @@ -0,0 +1,79 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// Util class + /// + internal class Util + { +#if !PORTABLELIB + /// StackOverFlow exception type + private static readonly Type StackOverflowType = typeof(System.StackOverflowException); + + /// ThreadAbortException exception type + private static readonly Type ThreadAbortType = typeof(System.Threading.ThreadAbortException); + + /// AccessViolationException exception type + private static readonly Type AccessViolationType = typeof(System.AccessViolationException); +#endif + + /// OutOfMemoryException exception type + private static readonly Type OutOfMemoryType = typeof(System.OutOfMemoryException); + + /// NullReferenceException exception type + private static readonly Type NullReferenceType = typeof(System.NullReferenceException); + + /// SecurityException exception type + private static readonly Type SecurityType = typeof(System.Security.SecurityException); + + /// + /// Check if input is null, throw an ArgumentNullException if it is. + /// + /// The input argument + /// The error to throw + internal static void CheckArgumentNull([ValidatedNotNull] object arg, string errorMessage) + { + if (arg == null) + { + throw new ArgumentNullException(errorMessage); + } + } + + /// + /// Determines if the exception is one of the prohibited types that should not be caught. + /// + /// The exception to be checked against the prohibited list. + /// True if the exception is ok to be caught, false otherwise. + internal static bool IsCatchableExceptionType(Exception e) + { + // a 'catchable' exception is defined by what it is not. + Type type = e.GetType(); + + return ((type != OutOfMemoryType) && +#if !PORTABLELIB + (type != StackOverflowType) && + (type != ThreadAbortType) && + (type != AccessViolationType) && +#endif + (type != NullReferenceType) && + !SecurityType.IsAssignableFrom(type)); + } + + /// + /// A workaround to a problem with FxCop which does not recognize the CheckArgumentNotNull method + /// as the one which validates the argument is not null. + /// + /// This has been suggested as a workaround in msdn forums by the VS team. Note that even though this is production code + /// the attribute has no effect on anything else. + private sealed class ValidatedNotNullAttribute : Attribute + { + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextConstants.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextConstants.cs new file mode 100644 index 0000000..58d09a7 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextConstants.cs @@ -0,0 +1,106 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Well Known Text Constants + /// + internal class WellKnownTextConstants + { + /// + /// SRID + /// + internal const string WktSrid = "SRID"; + + /// + /// POINT + /// + internal const string WktPoint = "POINT"; + + /// + /// LINESTRING + /// + internal const string WktLineString = "LINESTRING"; + + /// + /// POLYGON + /// + internal const string WktPolygon = "POLYGON"; + + /// + /// GEOMETRYCOLLECTION + /// DEVNOTE: Because there is no inherent Geography support in the WKT specification, + /// this constant is used for both GeographyCollection and GeometryCollection + /// + internal const string WktCollection = "GEOMETRYCOLLECTION"; + + /// + /// MULTIPOINT + /// + internal const string WktMultiPoint = "MULTIPOINT"; + + /// + /// MULTILINESTRING + /// + internal const string WktMultiLineString = "MULTILINESTRING"; + + /// + /// MULTIPOLYGON + /// + internal const string WktMultiPolygon = "MULTIPOLYGON"; + + /// + /// FULLGLOBE + /// + internal const string WktFullGlobe = "FULLGLOBE"; + + /// + /// NULL + /// + internal const string WktEmpty = "EMPTY"; + + /// + /// NULL + /// + internal const string WktNull = "NULL"; + + /// + /// Equals Operator '=' + /// + internal const string WktEquals = "="; + + /// + /// Semicolon ';' + /// + internal const string WktSemiColon = ";"; + + /// + /// Delimiter ',' + WktWhiteSpace + /// + internal const string WktDelimiterWithWhiteSpace = ", "; + + /// + /// Open Parenthesis '(' + /// + internal const string WktOpenParen = "("; + + /// + /// Close Parenthesis '); + /// + internal const string WktCloseParen = ")"; + + /// + /// Whitespace ' ' + /// + internal const string WktWhitespace = " "; + + /// + /// Period/Dot '.' + /// + internal const string WktPeriod = "."; + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextLexer.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextLexer.cs new file mode 100644 index 0000000..c4fe998 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextLexer.cs @@ -0,0 +1,100 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.IO; + + /// + /// WellKnownText Lexer + /// + internal class WellKnownTextLexer : TextLexerBase + { + /// + /// Constructor + /// + /// Input text + public WellKnownTextLexer(TextReader text) + : base(text) + { + } + + /// + /// Examine the current character and determine its token type + /// + /// The next char that will be read. + /// The currently active token type + /// The matched token type + /// Whether the current character is a delimiter, thereby terminate the current token immediately + protected override bool MatchTokenType(char nextChar, int? activeTokenType, out int tokenType) + { + switch (nextChar) + { + case '=': + tokenType = (int)WellKnownTextTokenType.Equals; + return true; + case ';': + tokenType = (int)WellKnownTextTokenType.Semicolon; + return true; + case '(': + tokenType = (int)WellKnownTextTokenType.LeftParen; + return true; + case ')': + tokenType = (int)WellKnownTextTokenType.RightParen; + return true; + case '.': + tokenType = (int)WellKnownTextTokenType.Period; + return true; + case ',': + tokenType = (int)WellKnownTextTokenType.Comma; + return true; + case ' ': + case '\t': + case '\r': + case '\n': + tokenType = (int)WellKnownTextTokenType.WhiteSpace; + return false; + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + tokenType = (int)WellKnownTextTokenType.Number; + return false; + + // E is special because of exponents + case 'e': + case 'E': + if (activeTokenType == (int)WellKnownTextTokenType.Number) + { + tokenType = (int)WellKnownTextTokenType.Number; + } + else + { + tokenType = (int)WellKnownTextTokenType.Text; + } + + return false; + default: + if ((nextChar >= 'A' && nextChar <= 'Z') || nextChar >= 'a' && nextChar <= 'z') + { + tokenType = (int)WellKnownTextTokenType.Text; + return false; + } + + throw new FormatException(Strings.WellKnownText_UnexpectedCharacter(nextChar)); + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatter.cs new file mode 100644 index 0000000..ea14d1b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatter.cs @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.IO; + + /// + /// The object to move spatial types to and from the WellKnownTextSql format + /// + public abstract class WellKnownTextSqlFormatter : SpatialFormatter + { + /// Initializes a new instance of the class. + /// The implementation that created this instance. + protected WellKnownTextSqlFormatter(SpatialImplementation creator) : base(creator) + { + } + + /// Creates the implementation of the formatter. + /// Returns the created WellKnownTextSqlFormatter implementation. + public static WellKnownTextSqlFormatter Create() + { + return SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(); + } + + /// Creates the implementation of the formatter and checks whether the specified formatter has Z. + /// The created WellKnownTextSqlFormatter. + /// Restricts the formatter to allow only two dimensions. + public static WellKnownTextSqlFormatter Create(bool allowOnlyTwoDimensions) + { + return SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(allowOnlyTwoDimensions); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatterImplementation.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatterImplementation.cs new file mode 100644 index 0000000..b365822 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlFormatterImplementation.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.IO; + + /// + /// The object to move spatial types to and from the WellKnownTextSql format + /// + internal class WellKnownTextSqlFormatterImplementation : WellKnownTextSqlFormatter + { + /// + /// restricts the writer and reader to allow only two dimensions. + /// + private readonly bool allowOnlyTwoDimensions; + + /// + /// Initializes a new instance of the class. + /// + /// The implementation that created this instance. + internal WellKnownTextSqlFormatterImplementation(SpatialImplementation creator) : base(creator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The implementation that created this instance. + /// restricts the reader to allow only two dimensions. + internal WellKnownTextSqlFormatterImplementation(SpatialImplementation creator, bool allowOnlyTwoDimensions) : base(creator) + { + this.allowOnlyTwoDimensions = allowOnlyTwoDimensions; + } + + /// + /// Create the writer + /// + /// The object that should be the target of the ISpatialPipeline writer. + /// A writer that implements ISpatialPipeline. + public override SpatialPipeline CreateWriter(TextWriter target) + { + return new ForwardingSegment(new WellKnownTextSqlWriter(target, this.allowOnlyTwoDimensions)); + } + + /// + /// Reads the geography. + /// + /// The reader stream. + /// The pipeline. + protected override void ReadGeography(TextReader readerStream, SpatialPipeline pipeline) + { + new WellKnownTextSqlReader(pipeline, allowOnlyTwoDimensions).ReadGeography(readerStream); + } + + /// + /// Reads the geometry. + /// + /// The reader stream. + /// The pipeline. + protected override void ReadGeometry(TextReader readerStream, SpatialPipeline pipeline) + { + new WellKnownTextSqlReader(pipeline, allowOnlyTwoDimensions).ReadGeometry(readerStream); + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlReader.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlReader.cs new file mode 100644 index 0000000..7ced9af --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlReader.cs @@ -0,0 +1,468 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + + /// + /// Reader for Extended Well Known Text, Case sensitive + /// example: + /// SRID=1234;POINT(10.0 20.0 NULL 30.0) + /// + internal class WellKnownTextSqlReader : SpatialReader + { + /// + /// restricts the reader to allow only two dimensions. + /// + private bool allowOnlyTwoDimensions; + + /// + /// Creates a reader that that will send messages to the destination during read. + /// + /// The instance to message to during read. + public WellKnownTextSqlReader(SpatialPipeline destination) + : this(destination, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The destination. + /// if set to true allows only two dimensions. + public WellKnownTextSqlReader(SpatialPipeline destination, bool allowOnlyTwoDimensions) + : base(destination) + { + this.allowOnlyTwoDimensions = allowOnlyTwoDimensions; + } + + /// + /// Parses some serialized format that represents a geography value, passing the result down the pipeline. + /// + /// TextReader instance to read from. + protected override void ReadGeographyImplementation(TextReader input) + { + // Geography in WKT has lat/long reversed, should be y (long), x (lat), z, m + new Parser(input, new TypeWashedToGeographyLongLatPipeline(this.Destination), allowOnlyTwoDimensions).Read(); + } + + /// + /// Parses some serialized format that represents a geometry value, passing the result down the pipeline. + /// + /// TextReader instance to read from. + protected override void ReadGeometryImplementation(TextReader input) + { + new Parser(input, new TypeWashedToGeometryPipeline(this.Destination), allowOnlyTwoDimensions).Read(); + } + + /// + /// This class parses the text and calls the pipeline based on what is parsed + /// + private class Parser + { + /// + /// restricts the parser to allow only two dimensions. + /// + private readonly bool allowOnlyTwoDimensions; + + /// + /// Text lexer + /// + private readonly TextLexerBase lexer; + + /// + /// Output pipeline + /// + private readonly TypeWashedPipeline pipeline; + + /// + /// Creates a parser with the given reader and pipeline + /// + /// The reader that is the source of what is parsed. + /// The pipeline to be called as the parser recognizes tokens. + /// if set to true allows only two dimensions. + public Parser(TextReader reader, TypeWashedPipeline pipeline, bool allowOnlyTwoDimensions) + { + this.lexer = new WellKnownTextLexer(reader); + this.pipeline = pipeline; + this.allowOnlyTwoDimensions = allowOnlyTwoDimensions; + } + + /// + /// Read WellKnownText into an instance of Geography + /// + public void Read() + { + this.ParseSRID(); + this.ParseTaggedText(); + } + + /// + /// Test whether the current token matches the expected token + /// + /// The expected token type + /// The expected token text + /// True if the two tokens match + private bool IsTokenMatch(WellKnownTextTokenType type, String text) + { + return this.lexer.CurrentToken.MatchToken((int)type, text, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Move the lexer to the next non-whitespace token + /// + /// True if the lexer gets a new token + private bool NextToken() + { + while (this.lexer.Next()) + { + if (!this.lexer.CurrentToken.MatchToken((int)WellKnownTextTokenType.WhiteSpace, String.Empty, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + /// + /// Parse Collection Text + /// + private void ParseCollectionText() + { + // ::= | { }* + if (!this.ReadEmptySet()) + { + this.ReadToken(WellKnownTextTokenType.LeftParen, null); + this.ParseTaggedText(); + while (this.ReadOptionalToken(WellKnownTextTokenType.Comma, null)) + { + this.ParseTaggedText(); + } + + this.ReadToken(WellKnownTextTokenType.RightParen, null); + } + } + + /// + /// Parse a LineString text + /// + private void ParseLineStringText() + { + // ::= | { }* + if (!this.ReadEmptySet()) + { + this.ReadToken(WellKnownTextTokenType.LeftParen, null); + this.ParsePoint(true); + while (this.ReadOptionalToken(WellKnownTextTokenType.Comma, null)) + { + this.ParsePoint(false); + } + + this.ReadToken(WellKnownTextTokenType.RightParen, null); + this.pipeline.EndFigure(); + } + } + + /// + /// Parse a Multi* text + /// + /// The inner spatial type + /// The inner reader + private void ParseMultiGeoText(SpatialType innerType, Action innerReader) + { + // ::= | { }* + // ::= | { }* + // ::= | { }* + if (!this.ReadEmptySet()) + { + this.ReadToken(WellKnownTextTokenType.LeftParen, null); + this.pipeline.BeginGeo(innerType); + innerReader(); + this.pipeline.EndGeo(); + + while (this.ReadOptionalToken(WellKnownTextTokenType.Comma, null)) + { + this.pipeline.BeginGeo(innerType); + innerReader(); + this.pipeline.EndGeo(); + } + + this.ReadToken(WellKnownTextTokenType.RightParen, null); + } + } + + /// + /// Parse Point Representation + /// + /// Whether this is the first point in the figure + private void ParsePoint(bool firstFigure) + { + double x = this.ReadDouble(); + double y = this.ReadDouble(); + double? z; + double? m; + + if (this.TryReadOptionalNullableDouble(out z) && allowOnlyTwoDimensions) + { + throw new FormatException(Strings.WellKnownText_TooManyDimensions); + } + + if (this.TryReadOptionalNullableDouble(out m) && allowOnlyTwoDimensions) + { + throw new FormatException(Strings.WellKnownText_TooManyDimensions); + } + + if (firstFigure) + { + this.pipeline.BeginFigure(x, y, z, m); + } + else + { + this.pipeline.LineTo(x, y, z, m); + } + } + + /// + /// Parse a point text + /// + private void ParsePointText() + { + // ::= | + if (!this.ReadEmptySet()) + { + this.ReadToken(WellKnownTextTokenType.LeftParen, null); + this.ParsePoint(true); + this.ReadToken(WellKnownTextTokenType.RightParen, null); + this.pipeline.EndFigure(); + } + } + + /// + /// Parse a Polygon text + /// + private void ParsePolygonText() + { + // ::= | { }* + if (!this.ReadEmptySet()) + { + this.ReadToken(WellKnownTextTokenType.LeftParen, null); + this.ParseLineStringText(); + while (this.ReadOptionalToken(WellKnownTextTokenType.Comma, null)) + { + this.ParseLineStringText(); + } + + this.ReadToken(WellKnownTextTokenType.RightParen, null); + } + } + + /// + /// Parse an instance of SRID + /// + private void ParseSRID() + { + // := SRID ; + if (this.ReadOptionalToken(WellKnownTextTokenType.Text, WellKnownTextConstants.WktSrid)) + { + this.ReadToken(WellKnownTextTokenType.Equals, null); + this.pipeline.SetCoordinateSystem(this.ReadInteger()); + this.ReadToken(WellKnownTextTokenType.Semicolon, null); + } + else + { + this.pipeline.SetCoordinateSystem(null); + } + } + + /// + /// Parse Tagged Text + /// + private void ParseTaggedText() + { + // ::= | + // | + // | + // | + // | + // | + // + // ::= point + // ::= linestring + // ::= polygon + // ::= multipoint + // ::= multilinestring + // ::= multipolygon + // ::= geometrycollection + if (!this.NextToken()) + { + throw new FormatException(Strings.WellKnownText_UnknownTaggedText(String.Empty)); + } + + switch (this.lexer.CurrentToken.Text.ToUpperInvariant()) + { + case WellKnownTextConstants.WktPoint: + this.pipeline.BeginGeo(SpatialType.Point); + this.ParsePointText(); + this.pipeline.EndGeo(); + break; + case WellKnownTextConstants.WktLineString: + this.pipeline.BeginGeo(SpatialType.LineString); + this.ParseLineStringText(); + this.pipeline.EndGeo(); + break; + case WellKnownTextConstants.WktPolygon: + this.pipeline.BeginGeo(SpatialType.Polygon); + this.ParsePolygonText(); + this.pipeline.EndGeo(); + break; + case WellKnownTextConstants.WktMultiPoint: + this.pipeline.BeginGeo(SpatialType.MultiPoint); + this.ParseMultiGeoText(SpatialType.Point, this.ParsePointText); + this.pipeline.EndGeo(); + break; + case WellKnownTextConstants.WktMultiLineString: + this.pipeline.BeginGeo(SpatialType.MultiLineString); + this.ParseMultiGeoText(SpatialType.LineString, this.ParseLineStringText); + this.pipeline.EndGeo(); + break; + case WellKnownTextConstants.WktMultiPolygon: + this.pipeline.BeginGeo(SpatialType.MultiPolygon); + this.ParseMultiGeoText(SpatialType.Polygon, this.ParsePolygonText); + this.pipeline.EndGeo(); + break; + case WellKnownTextConstants.WktCollection: + this.pipeline.BeginGeo(SpatialType.Collection); + this.ParseCollectionText(); + this.pipeline.EndGeo(); + break; + case WellKnownTextConstants.WktFullGlobe: + this.pipeline.BeginGeo(SpatialType.FullGlobe); + this.pipeline.EndGeo(); + break; + default: + throw new FormatException(Strings.WellKnownText_UnknownTaggedText(this.lexer.CurrentToken.Text)); + } + } + + /// + /// Read a double literal + /// + /// The read double + private double ReadDouble() + { + // := {.} + var numberText = new StringBuilder(); + this.ReadToken(WellKnownTextTokenType.Number, null); + numberText.Append(this.lexer.CurrentToken.Text); + if (this.ReadOptionalToken(WellKnownTextTokenType.Period, null)) + { + numberText.Append(WellKnownTextConstants.WktPeriod); + this.ReadToken(WellKnownTextTokenType.Number, null); + numberText.Append(this.lexer.CurrentToken.Text); + } + + return Double.Parse(numberText.ToString(), CultureInfo.InvariantCulture); + } + + /// + /// Check to see if the content is EMPTY + /// + /// True if the content is declared as EMPTY + private bool ReadEmptySet() + { + // ::= EMPTY + return this.ReadOptionalToken(WellKnownTextTokenType.Text, WellKnownTextConstants.WktEmpty); + } + + /// + /// Read an integer literal + /// + /// The read integer + private Int32 ReadInteger() + { + this.ReadToken(WellKnownTextTokenType.Number, null); + return XmlConvert.ToInt32(this.lexer.CurrentToken.Text); + } + + /// + /// Read an optional double literal + /// + /// The value that was read. + /// true if a value was read, otherwise returns false + private bool TryReadOptionalNullableDouble(out double? value) + { + // := {.} + var numberText = new StringBuilder(); + + if (this.ReadOptionalToken(WellKnownTextTokenType.Number, null)) + { + numberText.Append(this.lexer.CurrentToken.Text); + if (this.ReadOptionalToken(WellKnownTextTokenType.Period, null)) + { + numberText.Append(WellKnownTextConstants.WktPeriod); + this.ReadToken(WellKnownTextTokenType.Number, null); + numberText.Append(this.lexer.CurrentToken.Text); + } + + value = Double.Parse(numberText.ToString(), CultureInfo.InvariantCulture); + return true; + } + + value = null; + return this.ReadOptionalToken(WellKnownTextTokenType.Text, WellKnownTextConstants.WktNull); + } + + /// + /// Read an optional token. If the read token matches the expected optional token, then consume it. + /// + /// The expected token type + /// The expected token text, or null + /// True if the optional token matches the next token in stream + private bool ReadOptionalToken(WellKnownTextTokenType expectedTokenType, String expectedTokenText) + { + LexerToken token; + while (this.lexer.Peek(out token)) + { + if (token.MatchToken((int)WellKnownTextTokenType.WhiteSpace, null, StringComparison.OrdinalIgnoreCase)) + { + this.lexer.Next(); + } + else if (token.MatchToken((int)expectedTokenType, expectedTokenText, StringComparison.OrdinalIgnoreCase)) + { + this.lexer.Next(); + return true; + } + else + { + return false; + } + } + + return false; + } + + /// + /// Read and consume a token from the lexer, throw if the read token does not match the expected token + /// + /// The expected token type + /// The expected token text + private void ReadToken(WellKnownTextTokenType type, String text) + { + if (!(this.NextToken() && this.IsTokenMatch(type, text))) + { + throw new FormatException(Strings.WellKnownText_UnexpectedToken(type, text, this.lexer.CurrentToken)); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlWriter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlWriter.cs new file mode 100644 index 0000000..9ea6e05 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextSqlWriter.cs @@ -0,0 +1,417 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + + /// + /// WellKnownText Writer + /// + internal sealed class WellKnownTextSqlWriter : DrawBoth + { + /// + /// restricts the writer to allow only two dimensions. + /// + private bool allowOnlyTwoDimensions; + + /// + /// The underlying writer + /// + private TextWriter writer; + + /// + /// Stack of spatial types currently been built + /// + private Stack parentStack; + + /// + /// Detects if a CoordinateSystem (SRID) has been written already. + /// + private bool coordinateSystemWritten; + + /// + /// Figure has been written to the current spatial type + /// + private bool figureWritten; + + /// + /// A shape has been written in the current nesting level + /// + private bool shapeWritten; + + /// + /// Wells the known text SQL format. -- 2D writer + /// + /// The writer. + public WellKnownTextSqlWriter(TextWriter writer) + : this(writer, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The writer. + /// if set to true allows only two dimensions. + public WellKnownTextSqlWriter(TextWriter writer, bool allowOnlyTwoDimensions) + { + this.allowOnlyTwoDimensions = allowOnlyTwoDimensions; + this.writer = writer; + this.parentStack = new Stack(); + Reset(); + } + + #region DrawBoth + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// + /// The position to be passed down the pipeline + /// + protected override GeographyPosition OnLineTo(GeographyPosition position) + { + this.AddLineTo(position.Longitude, position.Latitude, position.Z, position.M); + return position; + } + + /// + /// Draw a point in the specified coordinate + /// + /// Next position + /// + /// The position to be passed down the pipeline + /// + protected override GeometryPosition OnLineTo(GeometryPosition position) + { + this.AddLineTo(position.X, position.Y, position.Z, position.M); + return position; + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// + /// The type to be passed down the pipeline + /// + protected override SpatialType OnBeginGeography(SpatialType type) + { + BeginGeo(type); + return type; + } + + /// + /// Begin drawing a spatial object + /// + /// The spatial type of the object + /// + /// The type to be passed down the pipeline + /// + protected override SpatialType OnBeginGeometry(SpatialType type) + { + BeginGeo(type); + return type; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeographyPosition OnBeginFigure(GeographyPosition position) + { + WriteFigureScope(position.Longitude, position.Latitude, position.Z, position.M); + return position; + } + + /// + /// Begin drawing a figure + /// + /// Next position + /// The position to be passed down the pipeline + protected override GeometryPosition OnBeginFigure(GeometryPosition position) + { + WriteFigureScope(position.X, position.Y, position.Z, position.M); + return position; + } + + /// + /// Ends the current figure + /// + protected override void OnEndFigure() + { + EndFigure(); + } + + /// + /// Ends the current spatial object + /// + protected override void OnEndGeography() + { + EndGeo(); + } + + /// + /// Ends the current spatial object + /// + protected override void OnEndGeometry() + { + EndGeo(); + } + + /// + /// Set the coordinate system + /// + /// The CoordinateSystem + /// + /// the coordinate system to be passed down the pipeline + /// + protected override CoordinateSystem OnSetCoordinateSystem(CoordinateSystem coordinateSystem) + { + WriteCoordinateSystem(coordinateSystem); + return coordinateSystem; + } + + /// + /// Setup the pipeline for reuse + /// + protected override void OnReset() + { + Reset(); + } + #endregion + + /// + /// Write the coordinate system + /// + /// The CoordinateSystem + private void WriteCoordinateSystem(CoordinateSystem coordinateSystem) + { + if (!this.coordinateSystemWritten) + { + // SRID can only be set once in WKT, but can be set once per BeginGeo in collection types + this.writer.Write(WellKnownTextConstants.WktSrid); + this.writer.Write(WellKnownTextConstants.WktEquals); + this.writer.Write(coordinateSystem.Id); + this.writer.Write(WellKnownTextConstants.WktSemiColon); + + this.coordinateSystemWritten = true; + } + } + + /// + /// Setup the pipeline for reuse + /// + private void Reset() + { + this.figureWritten = default(bool); + this.parentStack.Clear(); + this.shapeWritten = default(bool); + this.coordinateSystemWritten = default(bool); + + // we are unable to reset the text writer, we will just + // noop and start writing fresh + ////this.writer. + } + + /// + /// Start to write a new Geography/Geometry + /// + /// The SpatialType to write + private void BeginGeo(SpatialType type) + { + SpatialType parentType = this.parentStack.Count == 0 ? SpatialType.Unknown : this.parentStack.Peek(); + + if (parentType == SpatialType.MultiPoint || parentType == SpatialType.MultiLineString || parentType == SpatialType.MultiPolygon || parentType == SpatialType.Collection) + { + // container, first element should write out (, subsequent ones write out "," + this.writer.Write(this.shapeWritten ? WellKnownTextConstants.WktDelimiterWithWhiteSpace : WellKnownTextConstants.WktOpenParen); + } + + // Write tagged text + // Only write if toplevel, or the parent is a collection type + if (parentType == SpatialType.Unknown || parentType == SpatialType.Collection) + { + this.WriteTaggedText(type); + } + + this.figureWritten = false; + this.parentStack.Push(type); + } + + /// + /// Adds the control point. + /// + /// The x. + /// The y. + /// The z. + /// The m. + private void AddLineTo(double x, double y, double? z, double? m) + { + this.writer.Write(WellKnownTextConstants.WktDelimiterWithWhiteSpace); + this.WritePoint(x, y, z, m); + } + + /// + /// Ends the figure. + /// + private void EndFigure() + { + this.writer.Write(WellKnownTextConstants.WktCloseParen); + } + + /// + /// write tagged text for type + /// + /// the spatial type + private void WriteTaggedText(SpatialType type) + { + switch (type) + { + case SpatialType.Point: + this.writer.Write(WellKnownTextConstants.WktPoint); + break; + case SpatialType.LineString: + this.writer.Write(WellKnownTextConstants.WktLineString); + break; + case SpatialType.Polygon: + this.writer.Write(WellKnownTextConstants.WktPolygon); + break; + case SpatialType.Collection: + this.shapeWritten = false; + this.writer.Write(WellKnownTextConstants.WktCollection); + break; + case SpatialType.MultiPoint: + this.shapeWritten = false; + this.writer.Write(WellKnownTextConstants.WktMultiPoint); + break; + case SpatialType.MultiLineString: + this.shapeWritten = false; + this.writer.Write(WellKnownTextConstants.WktMultiLineString); + break; + case SpatialType.MultiPolygon: + this.shapeWritten = false; + this.writer.Write(WellKnownTextConstants.WktMultiPolygon); + break; + case SpatialType.FullGlobe: + this.writer.Write(WellKnownTextConstants.WktFullGlobe); + break; + } + + if (type != SpatialType.FullGlobe) + { + this.writer.Write(WellKnownTextConstants.WktWhitespace); + } + } + + /// + /// Start to write a figure + /// + /// The coordinate1. + /// The coordinate2. + /// The coordinate3. + /// The coordinate4. + private void WriteFigureScope(double coordinate1, double coordinate2, double? coordinate3, double? coordinate4) + { + Debug.Assert(this.parentStack.Count > 0, "Should have called BeginGeo"); + + if (this.figureWritten) + { + this.writer.Write(WellKnownTextConstants.WktDelimiterWithWhiteSpace); + } + else + { + // first figure + if (this.parentStack.Peek() == SpatialType.Polygon) + { + // Polygon has additional set of paren to separate out rings + this.writer.Write(WellKnownTextConstants.WktOpenParen); + } + } + + // figure scope + this.writer.Write(WellKnownTextConstants.WktOpenParen); + this.figureWritten = true; + + this.WritePoint(coordinate1, coordinate2, coordinate3, coordinate4); + } + + /// + /// End the current Geography/Geometry + /// + private void EndGeo() + { + switch (this.parentStack.Pop()) + { + case SpatialType.Point: + case SpatialType.LineString: + if (!figureWritten) + { + this.writer.Write(WellKnownTextConstants.WktEmpty); + } + + break; + case SpatialType.Polygon: + this.writer.Write(figureWritten ? WellKnownTextConstants.WktCloseParen : WellKnownTextConstants.WktEmpty); + break; + case SpatialType.Collection: + case SpatialType.MultiPoint: + case SpatialType.MultiLineString: + case SpatialType.MultiPolygon: + this.writer.Write(this.shapeWritten ? WellKnownTextConstants.WktCloseParen : WellKnownTextConstants.WktEmpty); + break; + case SpatialType.FullGlobe: + this.writer.Write(WellKnownTextConstants.WktCloseParen); + break; + } + + this.shapeWritten = true; + this.writer.Flush(); + } + + /// + /// Write out a point + /// + /// The x coordinate + /// The y coordinate + /// The z coordinate + /// The m coordinate + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", Justification = "x, y, z, m are meaningful")] + private void WritePoint(double x, double y, double? z, double? m) + { + this.writer.WriteRoundtrippable(x); + this.writer.Write(WellKnownTextConstants.WktWhitespace); + this.writer.WriteRoundtrippable(y); + + if (!this.allowOnlyTwoDimensions && z.HasValue) + { + this.writer.Write(WellKnownTextConstants.WktWhitespace); + this.writer.WriteRoundtrippable(z.Value); + + if (!this.allowOnlyTwoDimensions && m.HasValue) + { + this.writer.Write(WellKnownTextConstants.WktWhitespace); + this.writer.WriteRoundtrippable(m.Value); + } + } + else + { + if (!this.allowOnlyTwoDimensions && m.HasValue) + { + this.writer.Write(WellKnownTextConstants.WktWhitespace); + this.writer.Write(WellKnownTextConstants.WktNull); + this.writer.Write(WellKnownTextConstants.WktWhitespace); + this.writer.Write(m.Value); + } + } + } + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextTokenType.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextTokenType.cs new file mode 100644 index 0000000..5a7662f --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WellKnownTextTokenType.cs @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// WellKnownText Lexer Token Type + /// + internal enum WellKnownTextTokenType : int + { + /// + /// A-Z only support upper case text. i.e., POINT() instead of Point() or point() + /// + Text = 0, + + /// + /// character '=' + /// + Equals, + + /// + /// characters '0' to '9' + /// + Number, + + /// + /// character ';' + /// + Semicolon, + + /// + /// character '(' + /// + LeftParen, + + /// + /// character ')' + /// + RightParen, + + /// + /// character '.' + /// + Period, + + /// + /// character ',' + /// + Comma, + + /// + /// character ' ', '\t' + /// + WhiteSpace + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/WrappedGeoJsonWriter.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/WrappedGeoJsonWriter.cs new file mode 100644 index 0000000..a68f5d6 --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/WrappedGeoJsonWriter.cs @@ -0,0 +1,97 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + using System; + + /// + /// Writer to convert spatial types to Json that wraps a JsonWriter. + /// + internal sealed class WrappedGeoJsonWriter : GeoJsonWriterBase + { + #region Private Fields + + /// + /// The actual stream to write Json. + /// + private readonly IGeoJsonWriter writer; + + #endregion + + /// + /// Constructor + /// + /// The actual stream to write Json. + public WrappedGeoJsonWriter(IGeoJsonWriter writer) + { + this.writer = writer; + } + + #region Override Methods + + /// + /// Start a new json object scope + /// + protected override void StartObjectScope() + { + writer.StartObjectScope(); + } + + /// + /// Start a new json array scope + /// + protected override void StartArrayScope() + { + writer.StartArrayScope(); + } + + /// + /// Add a property name to the current json object + /// + /// The name to add + protected override void AddPropertyName(String name) + { + writer.AddPropertyName(name); + } + + /// + /// Add a value to the current json scope + /// + /// The value to add + protected override void AddValue(String value) + { + writer.AddValue(value); + } + + /// + /// Add a value to the current json scope + /// + /// The value to add + protected override void AddValue(double value) + { + writer.AddValue(value); + } + + /// + /// End the current json array scope + /// + protected override void EndArrayScope() + { + writer.EndArrayScope(); + } + + /// + /// End the current json object scope + /// + protected override void EndObjectScope() + { + writer.EndObjectScope(); + } + + #endregion + } +} diff --git a/ApiAsAService/odata.net/src/Microsoft.Spatial/XmlConstants.cs b/ApiAsAService/odata.net/src/Microsoft.Spatial/XmlConstants.cs new file mode 100644 index 0000000..119f33b --- /dev/null +++ b/ApiAsAService/odata.net/src/Microsoft.Spatial/XmlConstants.cs @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.Spatial +{ + /// + /// Class that contains all the constants for various schemas. + /// + internal class XmlConstants + { + /// + /// Namespace for xmlns + /// + internal const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/"; + } +} diff --git a/ApiAsAService/odata.net/src/PlatformHelper.cs b/ApiAsAService/odata.net/src/PlatformHelper.cs new file mode 100644 index 0000000..e76558d --- /dev/null +++ b/ApiAsAService/odata.net/src/PlatformHelper.cs @@ -0,0 +1,1134 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Globalization; +using System.Text.RegularExpressions; + +#if !SPATIAL +using Microsoft.OData.Edm; +#endif + +#if ODATA_SERVICE +namespace Microsoft.OData.Service +#else +#if ODATA_CLIENT +namespace Microsoft.OData.Client +#else +#if SPATIAL +namespace Microsoft.Spatial +#else +#if ODATA_CORE +namespace Microsoft.OData +#else +namespace Microsoft.OData.Edm +#endif +#endif +#endif +#endif +{ + using System; + using System.Collections.Generic; +#if PORTABLELIB + using System.Diagnostics; + using System.Linq; +#endif + using System.Reflection; +#if PORTABLELIB +#endif + using System.Xml; +#if !SPATIAL + +#endif + + /// + /// Helper methods that provide a common API surface on all platforms. + /// + internal static class PlatformHelper + { + /// + /// Use this instead of Type.EmptyTypes. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static readonly Type[] EmptyTypes = new Type[0]; + + /// + /// This pattern eliminates all invalid dates, the supported format should be "YYYY-MM-DD" + /// + internal static readonly Regex DateValidator = CreateCompiled(@"^(\d{4})-(0?[1-9]|1[012])-(0?[1-9]|[12]\d|3[0|1])$", RegexOptions.Singleline); + + /// + /// This pattern eliminates all invalid timeOfDay, the supported format should be "hh:mm:ss.fffffff" + /// + internal static readonly Regex TimeOfDayValidator = CreateCompiled(@"^(0?\d|1\d|2[0-3]):(0?\d|[1-5]\d)(:(0?\d|[1-5]\d)(\.\d{1,7})?)?$", RegexOptions.Singleline); + + /// + /// This pattern eliminates whether a text is potentially DateTimeOffset but not others like GUID, digit .etc + /// + internal static readonly Regex PotentialDateTimeOffsetValidator = CreateCompiled(@"^(\d{2,4})-(\d{1,2})-(\d{1,2})(T|(\s+))(\d{1,2}):(\d{1,2})", RegexOptions.Singleline); + +#if PORTABLELIB + /// + /// Replacement for Uri.UriSchemeHttp, which does not exist on. + /// + internal static readonly string UriSchemeHttp = "http"; + + /// + /// Replacement for Uri.UriSchemeHttps, which does not exist on. + /// + internal static readonly string UriSchemeHttps = "https"; +#else + /// + /// Use this instead of Uri.UriSchemeHttp. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp; + + /// + /// Use this instead of Uri.UriSchemeHttps. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps; +#endif + + #region Helper methods for properties + + /// + /// Replacement for Type.Assembly. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static Assembly GetAssembly(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().Assembly; +#else + return type.Assembly; +#endif + } + + /// + /// Replacement for Type.IsValueType. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsValueType(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsValueType; +#else + return type.IsValueType; +#endif + } + + /// + /// Replacement for Type.IsAbstract. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsAbstract(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsAbstract; +#else + return type.IsAbstract; +#endif + } + + /// + /// Replacement for Type.IsGenericType. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsGenericType(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsGenericType; +#else + return type.IsGenericType; +#endif + } + + /// + /// Replacement for Type.IsGenericTypeDefinition. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsGenericTypeDefinition(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsGenericTypeDefinition; +#else + return type.IsGenericTypeDefinition; +#endif + } + + /// + /// Replacement for Type.IsVisible. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsVisible(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsVisible; +#else + return type.IsVisible; +#endif + } + + /// + /// Replacement for Type.IsInterface. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsInterface(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsInterface; +#else + return type.IsInterface; +#endif + } + + /// + /// Replacement for Type.IsClass. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsClass(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsClass; +#else + return type.IsClass; +#endif + } + + /// + /// Replacement for Type.IsEnum. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsEnum(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsEnum; +#else + return type.IsEnum; +#endif + } + + /// + /// Replacement for Type.BaseType. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static Type GetBaseType(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().BaseType; +#else + return type.BaseType; +#endif + } + + /// + /// Replacement for Type.ContainsGenericParameters. + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool ContainsGenericParameters(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().ContainsGenericParameters; +#else + return type.ContainsGenericParameters; +#endif + } + + #endregion + + #region Helper methods for static methods + +#if !SPATIAL + /// + /// Converts a string to a Date. + /// + /// String to be converted. + /// The converted date value + /// if parsing was successful + internal static bool TryConvertStringToDate(string text, out Date date) + { + date = default(Date); + if (text == null || !PlatformHelper.DateValidator.IsMatch(text)) + { + return false; + } + + return Date.TryParse(text, CultureInfo.InvariantCulture, out date); + } + + /// + /// Converts a string to a Date. + /// + /// String to be converted. + /// Date value + internal static Date ConvertStringToDate(string text) + { + Date date; + if (!PlatformHelper.TryConvertStringToDate(text, out date)) + { + throw new FormatException(string.Format(CultureInfo.InvariantCulture, "String '{0}' was not recognized as a valid Edm.Date.", text)); + } + + return date; + } + + /// + /// Converts a string to a TimeOfDay. + /// + /// String to be converted. + /// Time of the day + /// Whether the value is a valid time of day + internal static bool TryConvertStringToTimeOfDay(string text, out TimeOfDay timeOfDay) + { + timeOfDay = default(TimeOfDay); + if (text == null || !PlatformHelper.TimeOfDayValidator.IsMatch(text)) + { + return false; + } + + return TimeOfDay.TryParse(text, CultureInfo.InvariantCulture, out timeOfDay); + } + + /// + /// Converts a string to a TimeOfDay. + /// + /// String to be converted. + /// TimeOfDay value + internal static TimeOfDay ConvertStringToTimeOfDay(string text) + { + TimeOfDay timeOfDay; + if (!PlatformHelper.TryConvertStringToTimeOfDay(text, out timeOfDay)) + { + throw new FormatException(string.Format(CultureInfo.InvariantCulture, "String '{0}' was not recognized as a valid Edm.TimeOfDay.", text)); + } + + return timeOfDay; + } +#endif + + /// + /// Converts a string to a DateTimeOffset. + /// + /// String to be converted. + /// See documentation for method being accessed in the body of the method. + internal static DateTimeOffset ConvertStringToDateTimeOffset(string text) + { + text = AddSecondsPaddingIfMissing(text); + DateTimeOffset dateTimeOffset = XmlConvert.ToDateTimeOffset(text); + + // Validate the time zone after we know that the text is a valid date time offset string. + ValidateTimeZoneInformationInDateTimeOffsetString(text); + + return dateTimeOffset; + } + +#if ODATA_CLIENT + /// + /// Converts a string to a DateTime. This is only built in client library + /// + /// A DateTime value to be converted. + /// See documentation for method being accessed in the body of the method. + internal static DateTime ConvertStringToDateTime(string text) + { + var offset = ConvertStringToDateTimeOffset(text); + return offset.UtcDateTime; + } + + /// + /// Convert the given DateTime instance to corresponding DateTimeOffset instance. + /// The conversion rules for a DateTime kind to DateTimeOffset are: + /// a) Unspecified -> UTC + /// b) Local -> Local + /// c) UTC -> UTC + /// + /// Given DateTime value. + /// DateTimeOffset corresponding to given DateTime value. + internal static DateTimeOffset ConvertDateTimeToDateTimeOffset(DateTime dt) + { + if (dt.Kind == DateTimeKind.Unspecified) + { + return new DateTimeOffset(new DateTime(dt.Ticks, DateTimeKind.Utc)); + } + + return new DateTimeOffset(dt); + } +#endif + + /// + /// Validates that the DateTimeOffset string contains the time zone information. + /// + /// String to be validated. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + private static void ValidateTimeZoneInformationInDateTimeOffsetString(string text) + { + + // The XML DateTime pattern is described here: http://www.w3.org/TR/xmlschema-2/#dateTime + // If timezone is specified, the indicator will always be at the same place from the end of the string, so we can look there for the Z or +/-. + // + // UTC timezone, for example: "2012-12-21T15:01:23.1234567Z" + if (text.Length > 1 && (text[text.Length - 1] == 'Z' || text[text.Length - 1] == 'z')) + { + return; + } + + // Timezone offset from UTC, for example: "2012-12-21T15:01:23.1234567-08:00" or "2012-12-21T15:01:23.1234567+08:00" + const int timeZoneSignOffset = 6; + if (text.Length > timeZoneSignOffset && (text[text.Length - timeZoneSignOffset] == '-' || text[text.Length - timeZoneSignOffset] == '+')) + { + return; + } + + // No timezone specified, for example: "2012-12-21T15:01:23.1234567" + throw new FormatException(Strings.PlatformHelper_DateTimeOffsetMustContainTimeZone(text)); + } + + /// + /// Adds the seconds padding as zeros to the date time string if seconds part is missing. + /// + /// String that needs seconds padding + /// DateTime string after adding seconds padding + internal static string AddSecondsPaddingIfMissing(string text) + { + int indexOfT = text.IndexOf("T", System.StringComparison.Ordinal); + const int ColonBeforeSecondsOffset = 6; + int indexOfColonBeforeSeconds = indexOfT + ColonBeforeSecondsOffset; + + // check if the string is in the format of yyyy-mm-ddThh:mm or in the format of yyyy-mm-ddThh:mm[- or +]hh:mm + if (indexOfT > 0 && + (text.Length == indexOfColonBeforeSeconds || text.Length > indexOfColonBeforeSeconds && text[indexOfColonBeforeSeconds] != ':')) + { + text = text.Insert(indexOfColonBeforeSeconds, ":00"); + } + + return text; + } + + /// + /// Gets the specified type. + /// + /// Name of the type to get. + /// Throws if the type could not be found. + /// Type instance that represents the specified type name. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static Type GetTypeOrThrow(string typeName) + { + return Type.GetType(typeName, true); + } + + #endregion + + #region Methods to replace other changed functionality where the replacement doesn't map exactly to an existing method on other platforms + + /// + /// Gets the Unicode Category of the specified character. + /// + /// Character to get category of. + /// Category of the character. + internal static UnicodeCategory GetUnicodeCategory(Char c) + { + // Portable Library platform doesn't have Char.GetUnicodeCategory, its on CharUnicodeInfo instead. +#if PORTABLELIB + return CharUnicodeInfo.GetUnicodeCategory(c); +#else + return Char.GetUnicodeCategory(c); +#endif + } + + /// + /// Replacement for usage of MemberInfo.MemberType property. + /// + /// MemberInfo on which to access this method. + /// True if the specified member is a property, otherwise false. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsProperty(MemberInfo member) + { +#if PORTABLELIB + return member is PropertyInfo; +#else + return member.MemberType == MemberTypes.Property; +#endif + } + + /// + /// Replacement for usage of Type.IsPrimitive property. + /// + /// Type on which to access this method. + /// True if the specified type is primitive, otherwise false. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsPrimitive(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsPrimitive; +#else + return type.IsPrimitive; +#endif + } + + /// + /// Replacement for usage of Type.IsSealed property. + /// + /// Type on which to access this method. + /// True if the specified type is sealed, otherwise false. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsSealed(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().IsSealed; +#else + return type.IsSealed; +#endif + } + + /// + /// Replacement for usage of MemberInfo.MemberType property. + /// + /// MemberInfo on which to access this method. + /// True if the specified member is a method, otherwise false. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool IsMethod(MemberInfo member) + { +#if PORTABLELIB + return member is MethodInfo; +#else + return member.MemberType == MemberTypes.Method; +#endif + } + + /// + /// Compares two methodInfos and returns true if they represent the same method. + /// Need this for Windows Phone as the method Infos of the same method are not always instance equivalent. + /// + /// MemberInfo to compare. + /// MemberInfo to compare. + /// True if the specified member is a method, otherwise false. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static bool AreMembersEqual(MemberInfo member1, MemberInfo member2) + { +#if PORTABLELIB + return member1 == member2; +#else + return member1.MetadataToken == member2.MetadataToken; +#endif + } + + /// + /// Gets public properties for the specified type. + /// + /// Type on which to call this helper method. + /// True if method should return only instance properties, false if it should return both instance and static properties. + /// Enumerable of public properties for the type. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static IEnumerable GetPublicProperties(this Type type, bool instanceOnly) + { + return GetPublicProperties(type, instanceOnly, false /*declaredOnly*/); + } + + /// + /// Gets public properties for the specified type. + /// + /// Type on which to call this helper method. + /// True if method should return only instance properties, false if it should return both instance and static properties. + /// True if method should return only properties that are declared on the type, false if it should return properties declared on the type as well as those inherited from any base types. + /// Enumerable of public properties for the type. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static IEnumerable GetPublicProperties(this Type type, bool instanceOnly, bool declaredOnly) + { + // PORTABLELIB: The BindingFlags enum and all related reflection method overloads have been removed from PORTABLELIB. Instead of trying to provide + // a general purpose flags enum and methods that can take any combination of the flags, we provide more restrictive methods that + // still allow for the same functionality as needed by the calling code. +#if PORTABLELIB + // TypeInfo.DeclaredProperties and Type.GetRuntimeProperties return both public and private properties, so need to filter out only public ones. + IEnumerable properties = declaredOnly ? type.GetTypeInfo().DeclaredProperties : type.GetRuntimeProperties(); + return properties.Where(p => IsPublic(p) && (!instanceOnly || IsInstance(p))); +#else + BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; + if (!instanceOnly) + { + bindingFlags |= BindingFlags.Static; + } + + if (declaredOnly) + { + bindingFlags |= BindingFlags.DeclaredOnly; + } + + return type.GetProperties(bindingFlags); +#endif + } + + /// + /// Gets non public properties for the specified type. + /// + /// Type on which to call this helper method. + /// True if method should return only instance properties, false if it should return both instance and static properties. + /// True if method should return only properties that are declared on the type, false if it should return properties declared on the type as well as those inherited from any base types. + /// Enumerable of non public properties for the type. + internal static IEnumerable GetNonPublicProperties(this Type type, bool instanceOnly, bool declaredOnly) + { + // PORTABLELIB: The BindingFlags enum and all related reflection method overloads have been removed from PORTABLELIB. Instead of trying to provide + // a general purpose flags enum and methods that can take any combination of the flags, we provide more restrictive methods that + // still allow for the same functionality as needed by the calling code. +#if PORTABLELIB + // TypeInfo.DeclaredProperties and Type.GetRuntimeProperties return both public and private properties, so need to filter out only public ones. + IEnumerable properties = declaredOnly ? type.GetTypeInfo().DeclaredProperties : type.GetRuntimeProperties(); + return properties.Where(p => !IsPublic(p) && (!instanceOnly || IsInstance(p))); +#else + BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance; + if (!instanceOnly) + { + bindingFlags |= BindingFlags.Static; + } + + if (declaredOnly) + { + bindingFlags |= BindingFlags.DeclaredOnly; + } + + return type.GetProperties(bindingFlags); +#endif + } + + /// + /// Gets instance constructors for the specified type. + /// + /// Type on which to call this helper method. + /// True if method should return only public constructors, false if it should return only non-public constructors. + /// Enumerable of instance constructors for the specified type. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static IEnumerable GetInstanceConstructors(this Type type, bool isPublic) + { +#if PORTABLELIB + return type.GetTypeInfo().DeclaredConstructors.Where(c => !c.IsStatic && isPublic == c.IsPublic); +#else + BindingFlags bindingFlags = BindingFlags.Instance; + bindingFlags |= isPublic ? BindingFlags.Public : BindingFlags.NonPublic; + return type.GetConstructors(bindingFlags); +#endif + } + + /// + /// Gets a instance constructor for the type that takes the specified argument types. + /// + /// Type on which to call this helper method. + /// True if method should search only public constructors, false if it should search only non-public constructors. + /// Array of argument types for the constructor. + /// ConstructorInfo for the constructor with the specified characteristics if found, otherwise null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static ConstructorInfo GetInstanceConstructor(this Type type, bool isPublic, Type[] argTypes) + { +#if PORTABLELIB + return GetInstanceConstructors(type, isPublic).SingleOrDefault(c => CheckTypeArgs(c, argTypes)); +#endif +#if !PORTABLELIB + BindingFlags bindingFlags = BindingFlags.Instance; + bindingFlags |= isPublic ? BindingFlags.Public : BindingFlags.NonPublic; + return type.GetConstructor(bindingFlags, null, argTypes, null); +#endif + } + + /// + /// Tries to the get method from the type, returns null if not found. + /// + /// The type. + /// The name. + /// The parameter types. + /// Returns True if found. + internal static bool TryGetMethod(this Type type, string name, Type[] parameterTypes, out MethodInfo foundMethod) + { + foundMethod = null; + try + { + foundMethod = type.GetMethod(name, parameterTypes); + return foundMethod != null; + } + catch (ArgumentNullException) + { + return false; + } + } + + /// + /// Gets all methods on the specified type. + /// + /// Type on which to call this helper method. + /// Enumerable of all methods for the specified type. + internal static IEnumerable GetMethods(this Type type) + { +#if PORTABLELIB + return type.GetRuntimeMethods(); +#else + return type.GetMethods(); +#endif + } + + /// + /// Gets a method on the specified type. + /// + /// Type on which to call this helper method. + /// Name of the method on the type. + /// True if method should search only public methods, false if it should search only non-public methods. + /// True if method should search only static methods, false if it should search only instance methods. + /// MethodInfo for the method with the specified characteristics if found, otherwise null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static MethodInfo GetMethod(this Type type, string name, bool isPublic, bool isStatic) + { + // WIN8: The BindingFlags enum and all related reflection method overloads have been removed from Win8. Instead of trying to provide + // a general purpose flags enum and methods that can take any combination of the flags, we provide more restrictive methods that + // still allow for the same functionality as needed by the calling code. +#if PORTABLELIB + return type.GetRuntimeMethods() + .Where( + m => + m.Name == name && + isPublic == m.IsPublic && + isStatic == m.IsStatic) + .SingleOrDefault(); +#else + BindingFlags bindingFlags = BindingFlags.Default; + bindingFlags |= isPublic ? BindingFlags.Public : BindingFlags.NonPublic; + bindingFlags |= isStatic ? BindingFlags.Static : BindingFlags.Instance; + return type.GetMethod(name, bindingFlags); +#endif + } + + /// + /// Gets a method on the specified type. + /// + /// Type on which to call this helper method. + /// Name of the method on the type. + /// Argument types for the method. + /// True if method should search only public methods, false if it should search only non-public methods. + /// True if method should search only static methods, false if it should search only instance methods. + /// MethodInfo for the method with the specified characteristics if found, otherwise null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static MethodInfo GetMethod(this Type type, string name, Type[] types, bool isPublic, bool isStatic) + { +#if PORTABLELIB + MethodInfo methodInfo = type.GetMethod(name, types); + if (isPublic == methodInfo.IsPublic && isStatic == methodInfo.IsStatic) + { + return methodInfo; + } + + return null; +#else + BindingFlags bindingFlags = BindingFlags.Default; + bindingFlags |= isPublic ? BindingFlags.Public : BindingFlags.NonPublic; + bindingFlags |= isStatic ? BindingFlags.Static : BindingFlags.Instance; + return type.GetMethod(name, bindingFlags, null, types, null); +#endif + } + + /// + /// Gets all public static methods for a type. + /// + /// Type on which to call this helper method. + /// Enumerable of all public static methods for the specified type. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static IEnumerable GetPublicStaticMethods(this Type type) + { +#if PORTABLELIB + return type.GetRuntimeMethods().Where(m => m.IsPublic && m.IsStatic); +#else + return type.GetMethods(BindingFlags.Static | BindingFlags.Public); +#endif + } + + /// + /// Replacement for Type.GetNestedTypes(BindingFlags.NonPublic) + /// + /// Type on which to call this helper method. + /// All types nested in the current type + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is shared among multiple assemblies and this method should be available as a helper in case it is needed in new code.")] + internal static IEnumerable GetNonPublicNestedTypes(this Type type) + { +#if PORTABLELIB + return type.GetTypeInfo().DeclaredNestedTypes.Where(t => !t.IsNestedPublic).Select(t => t.AsType()); +#else + return type.GetNestedTypes(BindingFlags.NonPublic); +#endif + } + #endregion + +#if PORTABLELIB + /// + /// Checks if the specified constructor takes arguments of the specified types. + /// + /// ConstructorInfo on which to call this helper method. + /// Array of type arguments to check against the constructor parameters. + /// True if the constructor takes arguments of the specified types, otherwise false. + private static bool CheckTypeArgs(ConstructorInfo constructorInfo, Type[] types) + { + Debug.Assert(types != null, "Types should not be null, use a different overload of the calling method if you don't care about the parameter types."); + + ParameterInfo[] parameters = constructorInfo.GetParameters(); + if (parameters.Length != types.Length) + { + return false; + } + + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].ParameterType != types[i]) + { + return false; + } + } + + return true; + } + + #region Extension Methods to replace missing functionality (used for PORTABLELIB only, methods with these signatures already exist on other platforms) + /// + /// Replacement for Type.IsAssignableFrom(Type) + /// + /// Type on which to call this helper method. + /// Type to test for assignability. + /// See documentation for method being accessed in the body of the method. + internal static bool IsAssignableFrom(this Type thisType, Type otherType) + { + return thisType.GetTypeInfo().IsAssignableFrom(otherType.GetTypeInfo()); + } + + /// + /// Replacement for Type.IsSubclassOf(Type). + /// + /// Type on which to call this helper method. + /// Type to test if typeType is a subclass. + /// True if thisType is a subclass of otherType, otherwise false. + /// + /// TODO: Add this back to TypeInfo. This method will still be needed since it works on Type, but the + /// implementation should just be able to call the TypeInfo version directly instead of the full implementation here. + /// + internal static bool IsSubclassOf(this Type thisType, Type otherType) + { + // devnotes (sparra): + // (1) This intentionally does not take interfaces into account, because the other platform implementations also do not. + // E.g, a type is never a subclass of an interface, even if the type is an interface itself. + // (2) On other platforms, there is an odd behavior where typeof(SomeInterface).IsSubclassOf(typeof(object)) returns true for the default + // implementation on Type, even though object is not a BaseType of any interface. Since we are not using IsSubclassOf in any way that + // would be affected by this, we do not try to duplicate that behavior with this helper method, and it will return false in that case. + + // If the types are the same one cannot be a subclass of the other. + if (thisType == otherType) + { + return false; + } + + Type type = thisType.GetTypeInfo().BaseType; + while (type != null) + { + if (type == otherType) + { + return true; + } + + type = type.GetTypeInfo().BaseType; + } + + return false; + } + + /// + /// Replacement for GetMethod(string). + /// + /// Type on which to call this helper method. + /// Method to find on the specified type. + /// MethodInfo if one was found for the specified type, otherwise false. + internal static MethodInfo GetMethod(this Type type, string name) + { + return type.GetRuntimeMethods().Where(m => m.IsPublic && m.Name == name).SingleOrDefault(); + } + + /// + /// Replacement for Type.GetMethod(string, Type[]). + /// + /// Type on which to call this helper method. + /// Name of method to find on the specified type. + /// Array of arguments to the method. + /// MethodInfo if one was found for the specified type, otherwise false. + internal static MethodInfo GetMethod(this Type type, string name, Type[] types) + { + // GetRuntimeMethod(string, Type[]) only searched public methods, so it matches Type.GetMethod(string, Type[]) behavior on other platforms. + return type.GetRuntimeMethod(name, types); + } + + /// + /// Gets a MethodInfo from the specified type. Replaces uses of Type.GetMember. + /// + /// Type on which to call this helper method. + /// Name of the method to find. + /// True if the method is public, false otherwise. + /// True if the method is static, false otherwise. + /// Number of generics arguments the method has. + /// MethodInfo for the method that was found. + internal static MethodInfo GetMethodWithGenericArgs(this Type type, string name, bool isPublic, bool isStatic, int genericArgCount) + { + return type.GetRuntimeMethods().Single(m => m.Name == name && m.IsPublic == isPublic && m.IsStatic == isStatic && m.GetGenericArguments().Count() == genericArgCount); + } + + /// + /// Replacement for Type.GetProperty(string, Type). + /// + /// Type on which to call this helper method. + /// Name of public property to find on the specified type. + /// Return type for the property. + /// PropertyInfo if a property was found on the type with the specified name and return type, otherwise null. + internal static PropertyInfo GetProperty(this Type type, string name, Type returnType) + { + // Type.GetRuntimeProperty returns public properties only + PropertyInfo propertyInfo = type.GetRuntimeProperty(name); + if (propertyInfo != null && propertyInfo.PropertyType == returnType) + { + return propertyInfo; + } + + return null; + } + + /// + /// Replacement for Type.GetProperty(string). + /// + /// Type on which to call this helper method. + /// Name of public property to find on the specified type. + /// PropertyInfo if a property was found on the type with the specified name and return type, otherwise null. + internal static PropertyInfo GetProperty(this Type type, string name) + { + // Type.GetRuntimeProperty returns public properties only + return type.GetRuntimeProperty(name); + } + + /// + /// Replacement for PropertyInfo.GetGetMethod(). + /// + /// PropertyInfo on which to call this helper method. + /// MethodInfo for the public get accessor of the specified PropertyInfo, or null if there is no get accessor or it is non-public. + internal static MethodInfo GetGetMethod(this PropertyInfo propertyInfo) + { + MethodInfo getMethod = propertyInfo.GetMethod; + if (getMethod != null && getMethod.IsPublic) + { + return getMethod; + } + + return null; + } + + /// + /// Replacement for PropertyInfo.GetSetMethod(). + /// + /// PropertyInfo on which to call this helper method. + /// MethodInfo for the public set accessor of the specified PropertyInfo, or null if there is no set accessor or it is non-public. + internal static MethodInfo GetSetMethod(this PropertyInfo propertyInfo) + { + MethodInfo setMethod = propertyInfo.SetMethod; + if (setMethod != null && setMethod.IsPublic) + { + return setMethod; + } + + return null; + } + + /// + /// Replacement for MethodInfo.GetBaseDefinition(). + /// + /// MethodInfo on which to call this helper method. + /// See documentation for method being accessed in the body of the method. + internal static MethodInfo GetBaseDefinition(this MethodInfo methodInfo) + { + return methodInfo.GetRuntimeBaseDefinition(); + } + + /// + /// Replacement for Type.GetProperties(). + /// + /// Type on which to call this helper method. + /// Enumerable of all instance and static public properties on the type. + internal static IEnumerable GetProperties(this Type type) + { + return GetPublicProperties(type, false /*instanceOnly*/); + } + + /// + /// Replacement for Type.GetFields(string). + /// + /// Type on which to call this helper method. + /// Enumerable of all public instance fields for the specified type. + internal static IEnumerable GetFields(this Type type) + { + // Need to filter to public only to match Type.GetFields() behavior on other platforms. + return type.GetRuntimeFields() + .Where(m => m.IsPublic); + } + + /// + /// Replacement for Type.GetCustomAttributes(Type, bool). + /// + /// Type on which to call this helper method. + /// Attribute type to find on the specified type. + /// True if the base types should be searched, false otherwise. + /// See documentation for method being accessed in the body of the method. + internal static IEnumerable GetCustomAttributes(this Type type, Type attributeType, bool inherit) + { + return type.GetTypeInfo().GetCustomAttributes(attributeType, inherit); + } + + /// + /// Replacement for Type.GetCustomAttributes(bool). + /// + /// Type on which to call this helper method. + /// True if the base types should be searched, false otherwise. + /// See documentation for method being accessed in the body of the method. + internal static IEnumerable GetCustomAttributes(this Type type, bool inherit) + { + return type.GetTypeInfo().GetCustomAttributes(inherit); + } + + /// + /// Replacement for Type.GetGenericArguments(). + /// + /// Type on which to call this helper method. + /// Array of Type objects that represent the type arguments of a generic type or the type parameters of a generic type definition. + internal static Type[] GetGenericArguments(this Type type) + { + if (type.GetTypeInfo().IsGenericTypeDefinition) + { + return type.GetTypeInfo().GenericTypeParameters; + } + else + { + return type.GenericTypeArguments; + } + } + + /// + /// Replacement for Type.GetInterfaces(). + /// + /// Type on which to call this helper method. + /// See documentation for property being accessed in the body of the method. + internal static IEnumerable GetInterfaces(this Type type) + { + return type.GetTypeInfo().ImplementedInterfaces; + } + + /// + /// Replacement for Type.IsInstanceOfType(object). + /// + /// Type on which to call this helper method. + /// Object to test to see if it's an instance of the specified type. + /// See documentation for method being accessed in the body of the method. + internal static bool IsInstanceOfType(this Type type, object obj) + { + return type.GetTypeInfo().IsAssignableFrom(obj.GetType().GetTypeInfo()); + } + + /// + /// Replacement for Assembly.GetType(string, bool). + /// + /// Assembly on which to call this helper method. + /// Name of the type to get from the assembly. + /// True if an exception should be thrown if the type cannot be found, otherwise false. + /// Type instance if the type could be found in the assembly, otherwise null. + /// + /// TODO: Add a new method called Assembly.GetDefinedType(string) that returns a TypeInfo and will throw like Assembly.GetType(string, true) used to. + /// This helper method will still be needed but should be updated to use the new implementation once it exists. + /// + internal static Type GetType(this Assembly assembly, string typeName, bool throwOnError) + { + Type type = assembly.GetType(typeName); + if (type == null && throwOnError) + { + throw new TypeLoadException(); + } + + return type; + } + + /// + /// Replacement for Assembly.GetTypes(). + /// + /// Assembly on which to call this helper method. + /// Enumerable of the types in the assembly. + internal static IEnumerable GetTypes(this Assembly assembly) + { + return assembly.DefinedTypes.Select(dt => dt.AsType()); + } + + /// + /// Replacement for GetField(string). + /// + /// Type on which to call this helper method. + /// Method to find on the specified type. + /// FieldInfo if one was found for the specified type, otherwise false. + internal static FieldInfo GetField(this Type type, string name) + { + return type.GetFields().SingleOrDefault(field => field.Name == name); + } + + /// + /// Checks if the specified PropertyInfo is an instance property. + /// + /// PropertyInfo on which to call this helper method. + /// True if either the GetMethod or SetMethod for the property is an instance method. + private static bool IsInstance(PropertyInfo propertyInfo) + { + return (propertyInfo.GetMethod != null && !propertyInfo.GetMethod.IsStatic) || (propertyInfo.SetMethod != null && !propertyInfo.SetMethod.IsStatic); + } + + /// + /// Checks if the specified PropertyInfo is a public property. + /// + /// PropertyInfo on which to call this helper method. + /// True if either the GetMethod or SetMethod for the property is public. + private static bool IsPublic(PropertyInfo propertyInfo) + { + return (propertyInfo.GetMethod != null && propertyInfo.GetMethod.IsPublic) || (propertyInfo.SetMethod != null && propertyInfo.SetMethod.IsPublic); + } + #endregion +#endif + + /// + /// Creates a Compiled Regex expression + /// + /// Pattern to match. + /// Options to use. + /// Regex expression to match supplied patter + /// Is marked as compiled option only in platforms otherwise RegexOption.None is used + public static Regex CreateCompiled(string pattern, RegexOptions options) + { +#if ORCAS || PORTABLELIB + options = options | RegexOptions.None; +#else + options = options | RegexOptions.Compiled; +#endif + return new Regex(pattern, options); + } + } +} diff --git a/ApiAsAService/odata.net/tools/Build.props b/ApiAsAService/odata.net/tools/Build.props new file mode 100644 index 0000000..9be93f1 --- /dev/null +++ b/ApiAsAService/odata.net/tools/Build.props @@ -0,0 +1,28 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + true + + + + + + + Test\$(RelativeOutputPath) + false + false + + + + + diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/After.Common.targets b/ApiAsAService/odata.net/tools/CustomMSBuild/After.Common.targets new file mode 100644 index 0000000..fe961cd --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/After.Common.targets @@ -0,0 +1,188 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + GenerateTextStringResources; + GenerateAssemblyAttributeFile; + GenerateVersionConstantsFile; + ExcludeAssemblyFilesFromSourceAnalysis; + $(BuildDependsOn); + + + + + + + + + + + PrepareForBuild; + GenerateStringResources; + + + + + + + + + + + Resx + false + %(Filename) + + + + + + + + + + + + + + + true + + + + + + + + + + + + + UNDEFINED_GenerateAssemblyAttributeTemplate + +using System.Reflection; +using System.Resources; + +[assembly: AssemblyDescription("%AssemblyNameFull%")] +[assembly: AssemblyTitle("%AssemblyNameFull%")] +[assembly: AssemblyDefaultAlias("%AssemblyNameFull%")] +[assembly: AssemblyFileVersion("%VersionFull%")] +[assembly: AssemblyInformationalVersion("%VersionFull%")] +[assembly: AssemblyVersion("%VersionFull%")] +[assembly: SatelliteContractVersion("%VersionFull%")] +[assembly: AssemblyMetadata("Serviceable", "True")] + + + + + $(GenerateAssemblyAttributeTemplateCSharp) + $(VersionOutputPath)\AssemblyMetadataAttribute$(DefaultLanguageSourceExtension) + true + + + + $(IntermediateOutputPath.TrimEnd("\\"))\AssemblyAttributes$(DefaultLanguageSourceExtension) + $(AssemblyName)$(TargetExt) + $([System.String]::Copy('$(GenerateAssemblyAttributeTemplate)').Replace("%25AssemblyNameFull%25",$(AssemblyNameFull)).Replace("%25VersionFull%25",$(VersionFull))) + + + + + + + + + + + + + + + + + + + + + + + + + UNDEFINED_GenerateVersionConstantsTemplate + +internal static class VersionConstants +{ + internal const string ReleaseVersion = "%VersionFullSemantic%"; + internal const string AssemblyVersion = "%VersionFull%"; +} + + +Friend Class VersionConstants + Friend Shared ReleaseVersion As String = "%VersionFullSemantic%" + Friend Shared AssemblyVersion As String = "%VersionFull%" +End Class + + + + + $(GenerateVersionConstantsTemplateCSharp) + + + + $(GenerateVersionConstantsTemplateVB) + + + + $(IntermediateOutputPath.TrimEnd("\\"))\VersionConstants$(DefaultLanguageSourceExtension) + $([System.String]::Copy('$(GenerateVersionConstantsTemplate)').Replace("%25VersionFull%25",$(VersionFull)).Replace("%25VersionFullSemantic%25",$(VersionFullSemantic))) + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/Before.Common.targets b/ApiAsAService/odata.net/tools/CustomMSBuild/Before.Common.targets new file mode 100644 index 0000000..28d90d6 --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/Before.Common.targets @@ -0,0 +1,21 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + $(VersionRunBeforeTargets);CoreCompile + $(EnlistmentRoot)\src\AssemblyInfo + AssemblyInfoCommon$(DefaultLanguageSourceExtension) + + + + + true + $(AssemblyInfoCommonFile) + + + + diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/Build.props b/ApiAsAService/odata.net/tools/CustomMSBuild/Build.props new file mode 100644 index 0000000..f56fa9c --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/Build.props @@ -0,0 +1,141 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + $(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll + $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.root)) + $(EnlistmentRoot.TrimEnd({'\\'})) + $(MSBuildThisFileDirectory.TrimEnd('\')) + $(EnlistmentRoot)\tools\CustomMSBuild + + + + 12.0 + Debug + + AnyCPU + + false + + + + + $(EnlistmentRoot)\obj + $(EnlistmentRoot)\bin\$(Platform)\$(Configuration) + $(OutputRootDir.TrimEnd({'\\'})) + + $([System.IO.Path]::Combine($(RelativeOutputPath), $(MSBuildProjectName))) + + + $([System.IO.Path]::Combine($(OutputRootDir), $(RelativeOutputPath))) + + $(OutDir.TrimEnd("\\"))\ + + + $(OutDir) + + + false + + + + + true + false + + + + bin\ + + + + + + $(IntermediateOutputRootDir) + + $(BaseIntermediateOutputPath)\$(Platform)\$(Configuration)\$(MSBuildProjectFile)\ + + + + + + + $(MSBuildThisFileDirectory)Before.Common.targets + $(MSBuildThisFileDirectory)After.Common.targets + + + false + true + $(MSBuildThisFileDirectory)ODataRuleSets.ruleset + true + + + true + true + + + + + + + + + + + + + + + True + + + + + + + + + + + + + + 10 + 1000 + false + true + false + + + + + false + $(DefineConstants),DEBUG,TRACE,CODE_ANALYSIS + + + + true + pdbonly + $(DefineConstants),TRACE,CODE_ANALYSIS + + + + true + full + prompt + 4 + true + + diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/Codesign.props b/ApiAsAService/odata.net/tools/CustomMSBuild/Codesign.props new file mode 100644 index 0000000..dbda1e5 --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/Codesign.props @@ -0,0 +1,91 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + $(BuildToolsetRoot)\StrongNamePublicKeys + $(EnlistmentRoot)\tools\StrongNamePublicKeys + 35MSSharedLib1024.snk + testkey.snk + None + Delay + + $(OutputPath.TrimEnd("\"))\logs + CodeSigner$(MSBuildProjectName)Log.xml + + + + Test + $(SigningScenarioForRelease) + Test + + + + + + + + $(DefineConstants),DelaySignKeys + + false + + true + false + true + false + + + $(StrongNamePublicKeysPath)\$(StrongNameKeyFile) + $(CustomStrongNamePublicKeysPath)\$(StrongNameKeyFile) + $(StrongNamePublicKeysPath)\$(TestStrongNameKeyFile) + $(CustomStrongNamePublicKeysPath)\$(TestStrongNameKeyFile) + + + + + + + + + $(DefineConstants),TestSignKeys + + false + + false + true + false + $(StrongNamePublicKeysPath)\$(TestStrongNameKeyFile) + $(CustomStrongNamePublicKeysPath)\$(TestStrongNameKeyFile) + + + + + + + + + false + + false + false + + + + + + diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/GetNugetPackageMetadata.proj b/ApiAsAService/odata.net/tools/CustomMSBuild/GetNugetPackageMetadata.proj new file mode 100644 index 0000000..a53614d --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/GetNugetPackageMetadata.proj @@ -0,0 +1,12 @@ + + + + $([System.DateTime]::Now.ToString("yyyyMMddHHmm")) + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/ODataRuleSets.ruleset b/ApiAsAService/odata.net/tools/CustomMSBuild/ODataRuleSets.ruleset new file mode 100644 index 0000000..be84c3a --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/ODataRuleSets.ruleset @@ -0,0 +1,599 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/Portable.targets b/ApiAsAService/odata.net/tools/CustomMSBuild/Portable.targets new file mode 100644 index 0000000..05d1b15 --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/Portable.targets @@ -0,0 +1,18 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + $(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets + + + + + $(PortableClassLibraryFrameworkPathOverride.TrimEnd('\\'))\ + + <_TargetFrameworkDirectories>$(FrameworkPathOverride) + <_FullFrameworkReferenceAssemblyPaths>$(FrameworkPathOverride) + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/TargetFrameworkPath.props b/ApiAsAService/odata.net/tools/CustomMSBuild/TargetFrameworkPath.props new file mode 100644 index 0000000..caa3a12 --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/TargetFrameworkPath.props @@ -0,0 +1,33 @@ + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + + + + + NuGet + + + $(TargetFrameworkIdentifier)\$(TargetFrameworkVersion) + + + Net35 + + + + Powershell + + + Desktop + + + $(TargetFrameworkFolderName)\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion) + + + $(TargetFrameworkFolderName)\$(RelativeOutputPath) + + diff --git a/ApiAsAService/odata.net/tools/CustomMSBuild/Versioning.props b/ApiAsAService/odata.net/tools/CustomMSBuild/Versioning.props new file mode 100644 index 0000000..7c5d51b --- /dev/null +++ b/ApiAsAService/odata.net/tools/CustomMSBuild/Versioning.props @@ -0,0 +1,52 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + 7 + 6 + 1 + beta + + + + + 2017 + $([System.Convert]::ToInt16('$([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd"))')) + $([System.Convert]::ToString($(VersionDateCode))) + + + + + $(VersionMajor).$(VersionMinor).$(VersionBuildNumber) + $(VersionFullSemantic).$(VersionRevision) + + + + + 12 + 0 + 0 + + + + + 2 + 4 + 0 + $(T4VersionMajor).$(T4VersionMinor).$(T4VersionBuildNumber) + $(T4VersionFullSemantic) + + + + + $(VersionFullSemantic) + $(VersionFullSemantic)-$(VersionRelease) + + diff --git a/ApiAsAService/odata.net/tools/ExecutePerf.ps1 b/ApiAsAService/odata.net/tools/ExecutePerf.ps1 new file mode 100644 index 0000000..82fa8dd --- /dev/null +++ b/ApiAsAService/odata.net/tools/ExecutePerf.ps1 @@ -0,0 +1,219 @@ +<# +Name: ExecutePerf.ps1 +Usage: -Workspace [workspace] -TestType Component|Service -Threshold [Percentage] -Judgement [Percentage] -BuildId [Num] -RunnerParams [XunitRunnerParams] +CopyRight: Copyright (C) Microsoft Corporation. All rights reserved. +#> + +Param +( + [String]$Workspace = $(throw "Workspace path is required."), # The performance working space + [String]$TestType = $(throw "TestType is required."), # the performance test type + [Int]$Threshold = $(throw "Threshold is required."), # Percentage, if the percentage ups to this value, it's regression + [Int]$Judgement = $(throw "Judgement is required."), # Percentage, the how many better. + [String]$BuildId = $(throw "BuildId is required."), + [String]$RunnerParams +) + +# In which has the base running bits +$BaseBitsPath = $Workspace + "\BaseBits" + +# In which has the test running bits +$TestBitsPath = $Workspace + "\TestBits" + +# Performance test type +If($TestType -eq "") +{ + $TestType = "Component" +} + +<# +Description: Analysis result between test and base +#> +Function AnalysisTestResult +{ + Param( + [string]$baseResult, + [string]$currResult, + [Int] $judgement, + [Int] $threshold, + [string]$logFile + ) + # reading the test xml results + [Xml]$currXml = Get-Content $currResult + $currRun = $currXml.results.run + $currTests = $currRun.test + + $index = 1 + $a =@() + $exitResult = $True + [Int]$betterNum = 0 + foreach($currTest in $currTests) + { + $fullName = $currTest.name + + $baseTest = FindBaseTest $baseResult $fullName + If ($baseTest -eq $null) + { + throw "Cannot find $fullName in base result." + } + + $info = @{} + $info.no = $index + $info.name = $fullName + + $currMean = $currTest.summary.Duration.mean + $baseMean = $baseTest.summary.Duration.mean + [decimal]$delta = [decimal]$currMean - [decimal]$baseMean + + If ($delta -lt [decimal]0) + { + $betterNum += 1 # Find a better one + $delta*=[decimal](-1.0) + $info.percentage="-" + } + + $itemResult = "Pass" + [decimal]$percentage = ( $delta / [decimal]$baseMean ) * 100; + If ($percentage -gt [decimal]$judgement) + { + $exitResult = $False # It's for global + $itemResult = "Fail" + } + + $info.percentage += [string]([int]$percentage) + "%" + $info.result = $itemResult + $a+=$info + $index+=1 + } + + $a | ConvertTo-json | Out-File $logFile + + If($exitResult -eq $False) + { + # exit 1 # fail + throw "Performance regression found, please see result: $logFile" + } + + # how many better? + $totalCount = $a.Count + $betterPercentage = [Int](([Double]$betterNum / [Double]$totalCount) * 100) + If($betterPercentage -gt $threshold) + { + UpdateBase $TestBitsPath $BaseBitsPath + } + #Write-Host "betterPercentage=" $betterPercentage +} + +Function FindBaseTest +{ + Param( + [string]$resultFile = $(throw "-resultFile is required."), + [string]$testName = $(throw "-testName is required.") + ) + + [Xml]$xml = Get-Content $resultFile + $tests = $xml.results.run.test + + foreach($test in $tests) + { + $fullName = $test.name + + If($fullName -eq $testName) + { + return $test + } + } + + return $null +} + +Function UpdateBase +{ + Param( + [string]$source, + [string]$dest + ) + + If((Test-Path $dest) -eq $True) + { + Remove-Item $dest -Recurse + } + + Copy-Item $source -Destination $dest -Recurse +} + +<# +Description: Run the performance test cases +#> +Function Execute([string]$testFolder, [string]$testType, [string]$runid) +{ + $location = Get-Location + Set-Location ($testFolder + "\bin") + + $testDllName = "Microsoft.OData.Performance." + $testType + ".Tests.dll" + $result = $runid + ".xml" + $analysisResult = $runid + ".analysisResult.xml" + .\xunit.performance.run.exe $testDllName -runner .\xunit.console.exe -runnerargs "-parallel none $RunnerParams" -runid $runid + .\xunit.performance.analysis.exe $result -xml $analysisResult + + Set-Location $location +} + +<# +Description: Get the RunId based on inputs, the output is similar as "Component.20170725.101" +#> +Function GetRunId([string]$testType, [string]$date, [string]$buildId) +{ + If($testType -eq "Component") + { + return "Component." + $date + "." + $buildId + } + + If($testType -eq "Service") + { + return "Service." + $date + "." + $buildId + } + + # failed. + throw "Wrong test type input. It should be '-TestType Component|Service'" +} + +# Step 0. if the base path is not existed, update the base directly +If((Test-Path $BaseBitsPath) -eq $False) +{ + # Update the "BaseBits" with "TestBits" + UpdateBase $TestBitsPath $BaseBitsPath + + #it means we don't do anyting at the first running. + exit 0 +} + +# Step 1. Get the running date & test result name +$RunDate = Get-Date -Format yyyyMMdd +$TestRunId = GetRunId $TestType $RunDate $BuildId + +# Step 2. Run the current tests +Execute $TestBitsPath $TestType $TestRunId +$TestResult = $TestBitsPath + "\bin\" + $TestRunId + ".analysisResult.xml" + +# Step 3. Run the base tests +Remove-Item -Path ($BaseBitsPath + "\bin\") -Include Component.* +Remove-Item -Path ($BaseBitsPath + "\bin\") -Include Service.* +Execute $BaseBitsPath $TestType $TestRunId +$BaseResult = $BaseBitsPath + "\bin\" + $TestRunId + ".analysisResult.xml" + +# Step 4. Analysis the between test results and base results +$LogFolder = $Workspace + "\Logs" +If((Test-Path $LogFolder) -eq $False) +{ + New-Item -Path $Workspace -name Logs -ItemType directory +} + +$LogFileName = $TestRunId + ".log" +$LogFilePath = $LogFolder + "\" + $LogFileName +If((Test-Path $LogFilePath ) -eq $False) +{ + New-Item -Path $LogFolder -name $LogFileName -ItemType File +} + +AnalysisTestResult $BaseResult $TestResult $Threshold $Judgement $LogFilePath diff --git a/ApiAsAService/odata.net/tools/FxCopRules/AtomMaterializerInvokerRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/AtomMaterializerInvokerRule.cs new file mode 100644 index 0000000..ccb0ba2 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/AtomMaterializerInvokerRule.cs @@ -0,0 +1,181 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that all methods used in AtomMaterializerInvoker + /// are available and that there aren't unused methods. + /// + public class AtomMaterializerInvokerRule : BaseDataWebRule + { + private System.Collections.ArrayList methodsUsedByCompiler; + + /// Initializes a new instance. + public AtomMaterializerInvokerRule() + : base("AtomMaterializerInvokerRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks the client module for consistency. + /// Module to check. + /// Problems in the module. + public override ProblemCollection Check(ModuleNode module) + { + base.Check(module); + if (!module.Name.EndsWith("Data.Services.Client", System.StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + TypeNode compiler = null; + TypeNode materializer = null; + TypeNode invoker = null; + foreach (TypeNode node in module.Types) + { + if (node.Name.Name.EndsWith("ODataEntityMaterializerInvoker")) + { + invoker = node; + } + else if (node.Name.Name.EndsWith("ODataEntityMaterializer")) + { + materializer = node; + } + else if (node.Name.Name.EndsWith("ProjectionPlanCompiler")) + { + compiler = node; + } + + if (invoker != null && materializer != null && compiler != null) + { + break; + } + } + + if (invoker == null || materializer == null || compiler == null) + { + Resolution resolution = new Resolution("Unable to find AtomMaterializerInvoker/AtomMaterializer/ProjectionPlanCompiler - check assembly or update rule."); + Problems.Add(new Problem(resolution)); + return Problems; + } + + string[] methodNames = GetMethodNamesFromInvoker(invoker); + string[] usedMethods = GetMethodsUsedByCompiler(compiler); + + // Look for methods that aren't used by compiler. + for (int i = 0; i < methodNames.Length; i++) + { + string methodName = methodNames[i]; + if (methodName == "DirectMaterializePlan" || methodName == "ShallowMaterializePlan") + { + // These are referenced directly so a lambda can be compiled and passed around, + // but are not part of projection plan building. + continue; + } + + bool found = System.Array.BinarySearch(usedMethods, methodName) >= 0; + if (!found) + { + Problems.Add(new Problem(GetResolution(methodName))); + } + } + + // TODO: look for methods that aren't implemented by invoker. + // Harder to do because we gather collection information by just + // picking up string literals, which gets asserts and other + // information. A more careful analysis requred code flow analysis, + // for example the wrappers for DataServiceCollection creation determine + // the method name through some branching statements. + // + // Heuristic: things with no spaces or some other non-identifier characters are method names. + for (int i = 0; i < usedMethods.Length; i++) + { + string usedMethod = usedMethods[i]; + if (usedMethod.Contains(" ") || usedMethod.Contains("|") || usedMethod.Contains("(")) + { + continue; + } + + // There are method name checks in the projection plan compiler used + // to recognize certain patterns. Skip those. + if (usedMethod == "Select" || usedMethod == "Create" || + usedMethod == "CreateTracked" || usedMethod == "ToList" || + usedMethod == "ReferenceEquals") + { + continue; + } + + // There are literals used to create parameter names. + if (usedMethod == "entry" || usedMethod == "subentry" || usedMethod == "type") + { + continue; + } + + bool found = System.Array.BinarySearch(methodNames, usedMethod) >= 0; + if (!found) + { + Resolution resolution = new Resolution("Implement " + usedMethod + " on AtomMaterializerInvoker"); + Problems.Add(new Problem(resolution)); + } + } + + return Problems.Count > 0 ? Problems : null; + } + + private string[] GetMethodsUsedByCompiler(TypeNode compiler) + { + System.Collections.ArrayList result = new System.Collections.ArrayList(); + this.methodsUsedByCompiler = result; + foreach (Member member in compiler.Members) + { + if (member.NodeType == NodeType.Method) + { + this.Visit(member); + } + } + + this.methodsUsedByCompiler = null; + result.Sort(); + return (string[])result.ToArray(typeof(string)); + } + + private static string[] GetMethodNamesFromInvoker(TypeNode invoker) + { + System.Collections.ArrayList result = new System.Collections.ArrayList(invoker.Members.Count); + foreach (Member member in invoker.Members) + { + if (member.NodeType == NodeType.Method) + { + result.Add(member.Name.Name); + } + } + + result.Sort(); + return (string[])result.ToArray(typeof(string)); + } + + public override void VisitLiteral(Literal literal) + { + if (literal.Type != null && literal.Type.FullName == "System.String") + { + this.methodsUsedByCompiler.Add(literal.Value as string); + } + + base.VisitLiteral(literal); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/BaseDataWebRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/BaseDataWebRule.cs new file mode 100644 index 0000000..e92849a --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/BaseDataWebRule.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using System.Diagnostics; + using Microsoft.FxCop.Sdk; + + /// + /// Inherit from this class to implement a rule in the DataWebRules + /// assembly. + /// + public abstract class BaseDataWebRule: BaseIntrospectionRule + { + /// Initializes a new . + /// Name of the rule, typically the class name. + public BaseDataWebRule(string name) + : base(name, "DataWebRules.Rules", typeof(BaseDataWebRule).Assembly) + { + } + + internal InstanceInitializer ConstructAsInstanceInitializer(Construct cons) + { + MemberBinding constructorBinding = cons.Constructor as MemberBinding; + return (constructorBinding != null) ? + constructorBinding.BoundMember as InstanceInitializer : null; + } + + internal void CheckMethod(Method method, Predicate checkHasProblem, params string[] args) + { + if (method != null && checkHasProblem(method)) + { + this.Problems.Add(new Problem(GetResolution(args))); + } + } + + internal bool HasParameterType(Method method, string typeNamePrefix) + { + Debug.Assert(method != null, "method != null"); + Debug.Assert(typeNamePrefix != null, "typeNamePrefix != null"); + + foreach (Parameter parameter in method.Parameters) + { + if (parameter.Type.FullName.StartsWith(typeNamePrefix)) + { + return true; + } + } + + return false; + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/CodeTypeReferenceRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/CodeTypeReferenceRule.cs new file mode 100644 index 0000000..3463dc4 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/CodeTypeReferenceRule.cs @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + public class CodeTypeReferenceRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public CodeTypeReferenceRule() + : base("CodeTypeReferenceRule") + { + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitConstruct(Construct cons) + { + InstanceInitializer init = ConstructAsInstanceInitializer(cons); + + if (init != null) + { + CheckMethod(init, + x => x.DeclaringType.FullName == "System.CodeDom.CodeTypeReference" && + !HasParameterType(x, "System.CodeDom.CodeTypeReferenceOptions"), + methodUnderCheck.FullName); + + CheckMethod(init, + x => x.DeclaringType.FullName == "System.CodeDom.CodeTypeReferenceExpression" && + !HasParameterType(x, "System.CodeDom.CodeTypeReference"), + methodUnderCheck.FullName); + + CheckMethod(init, + x => x.DeclaringType.FullName == "System.CodeDom.CodeParameterDeclaration" && + !HasParameterType(x, "System.CodeDom.CodeTypeReference"), + methodUnderCheck.FullName); + + CheckMethod(init, + x => x.DeclaringType.FullName == "System.CodeDom.CodeMemberField" && + !HasParameterType(x, "System.CodeDom.CodeTypeReference"), + methodUnderCheck.FullName); + + CheckMethod(init, + x => x.DeclaringType.FullName == "System.CodeDom.CodeArrayCreateExpression" && + !HasParameterType(x, "System.CodeDom.CodeTypeReference"), + methodUnderCheck.FullName); + + CheckMethod(init, + x => x.DeclaringType.FullName == "System.CodeDom.CodeCastExpression" && + !HasParameterType(x, "System.CodeDom.CodeTypeReference"), + methodUnderCheck.FullName); + + CheckMethod(init, + x => x.DeclaringType.FullName == "System.CodeDom.CodeObjectCreateExpression" && + !HasParameterType(x, "System.CodeDom.CodeTypeReference"), + methodUnderCheck.FullName); + } + + base.VisitConstruct(cons); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.csproj b/ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.csproj new file mode 100644 index 0000000..dcb2064 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.csproj @@ -0,0 +1,55 @@ + + + + 2.0 + {0854AF44-074A-41B4-909D-2AA4CAE82332} + DataWebRules + DataWebRules + 4 + Library + None + + + + + PayloadMetadataKind.cs + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.ruleset b/ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.ruleset new file mode 100644 index 0000000..fc5ac39 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/DataWebRules.ruleset @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/FxCopRules/DoNotHandleProhibitedExceptionsRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/DoNotHandleProhibitedExceptionsRule.cs new file mode 100644 index 0000000..05c72f2 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/DoNotHandleProhibitedExceptionsRule.cs @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.FxCop.Sdk; +using System.Linq; + +namespace DataWebRules +{ + class DoNotHandleProhibitedExceptionsRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public DoNotHandleProhibitedExceptionsRule() + : base("DoNotHandleProhibitedExceptionsRule") + { + } + + public override Microsoft.FxCop.Sdk.ProblemCollection Check(Microsoft.FxCop.Sdk.Member member) + { + Method method = member as Method; + if (method != null) + { + methodUnderCheck = method; + Visit(method.Body); + } + base.Check(member); + return Problems; + } + + public override void VisitCatch(Microsoft.FxCop.Sdk.CatchNode catchNode) + { + if (catchNode.Type == FrameworkTypes.Exception) + { + string[] doNotHandleMethods = new string[] { + "Microsoft.OData.Service.CommonUtil.IsCatchableExceptionType(System.Exception)", + "Microsoft.OData.Client.CommonUtil.IsCatchableExceptionType(System.Exception)", + "Microsoft.OData.Core.ExceptionUtils.IsCatchableExceptionType(System.Exception)", + "Microsoft.Spatial.Util.IsCatchableExceptionType(System.Exception)", + }; + MethodCallFinder finder = new MethodCallFinder(true, doNotHandleMethods); + finder.Visit(catchNode.Block); + if(!doNotHandleMethods.Any(m => finder.Found(m))) + { + Problems.Add(new Problem(GetResolution(methodUnderCheck.FullName), catchNode)); + } + } + base.VisitCatch(catchNode); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/EntityDescriptorPublicProperties.cs b/ApiAsAService/odata.net/tools/FxCopRules/EntityDescriptorPublicProperties.cs new file mode 100644 index 0000000..358eac3 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/EntityDescriptorPublicProperties.cs @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that no call to EntityDescriptor public properties are made at request building time. Instead they should call the equivalent internal method + /// + public class EntityDescriptorPublicPropertiesRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public EntityDescriptorPublicPropertiesRule() + : base("EntityDescriptorPublicPropertiesRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitMethodCall(MethodCall methodCall) + { + if (methodCall != null) + { + MemberBinding callBinding = methodCall.Callee as MemberBinding; + if (callBinding != null) + { + Method method = callBinding.BoundMember as Method; + if (method != null && + method.DeclaringType.FullName == "Microsoft.OData.Client.EntityDescriptor" && + method.Name.Name != "get_Entity" && + method.Name.Name != "get_StreamDescriptors" && + method.Name.Name != "set_ETag" && + method.IsPublic && + method.NodeType == NodeType.Method) + { + if (methodUnderCheck.DeclaringType.FullName == "Microsoft.OData.Client.BaseSaveResult" || + methodUnderCheck.DeclaringType.FullName == "Microsoft.OData.Client.SaveResult") + { + this.Problems.Add(new Problem(GetResolution(methodUnderCheck.FullName))); + } + } + } + } + + base.VisitMethodCall(methodCall); + } + } +} + diff --git a/ApiAsAService/odata.net/tools/FxCopRules/HashSetCtorRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/HashSetCtorRule.cs new file mode 100644 index 0000000..c0dc158 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/HashSetCtorRule.cs @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that HashSet`1 constructor take an explicit + /// comparer object. + /// + public class HashSetCtorRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public HashSetCtorRule(): base("HashSetCtorRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitConstruct(Construct cons) + { + CheckMethod( + ConstructAsInstanceInitializer(cons), + x => + x.Template != null && + x.Template.FullName == "System.Collections.Generic.HashSet`1.#ctor" && + !HasParameterType(x, "System.Collections.Generic.IEqualityComparer`1"), + methodUnderCheck.FullName); + + CheckMethod( + ConstructAsInstanceInitializer(cons), + x => + x.Template != null && + x.Template.FullName == "System.Collections.Generic.Dictionary`2.#ctor" && + !HasParameterType(x, "System.Collections.Generic.IEqualityComparer`1"), + methodUnderCheck.FullName); + + base.VisitConstruct(cons); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/HttpWebRequestRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/HttpWebRequestRule.cs new file mode 100644 index 0000000..eadcdd1 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/HttpWebRequestRule.cs @@ -0,0 +1,116 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + using System.Collections.Generic; + + /// + /// This rule checks that methods (mentioned above) on HttpWebRequest and HttpWebResponse are called + /// via the WebUtil methods + /// + public class HttpWebRequestRule : BaseDataWebRule + { + private Method methodUnderCheck; + + private static readonly List RequestTypeNames = new List + { + "System.Net.WebRequest", + "System.Net.HttpWebRequest", + "Microsoft.OData.Service.Http.WebRequest", + "Microsoft.OData.Service.Http.HttpWebRequest", + "Microsoft.OData.Service.Http.ClientHttpWebRequest", + "Microsoft.OData.Service.Http.XHRHttpWebRequest" + }; + + private static readonly List RequestMethodNames = new List + { + "GetResponse", + "BeginGetResponse", + "EndGetResponse", + "GetStream", + "BeginGetRequestStream", + "EndGetRequestStream" + }; + + private static readonly List ResponseTypeNames = new List + { + "System.Net.WebResponse", + "System.Net.HttpWebResponse", + "Microsoft.OData.Service.Http.WebResponse", + "Microsoft.OData.Service.Http.HttpWebResponse", + "Microsoft.OData.Service.Http.ClientHttpWebResponse", + "Microsoft.OData.Service.Http.XHRHttpWebResponse" + }; + + private static readonly List ResponseMethodNames = new List + { + "GetResponseStream" + }; + + /// Initializes a new instance. + public HttpWebRequestRule() + : base("HttpWebRequestRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// + /// Verifies that the only access to the specified methods is through the corresponding WebUtil wrapper method. + /// Using MemberBinding visitor instead of MethodCall because it will cover both direct method access (e.g. response.GetResponseStream()) + /// as well as delegate invocation (e.g. SomeMethod.GetResponseStream, where SomeMethod actually is the one to invoke the method). + /// + /// MemberBinding to validate. + public override void VisitMemberBinding(MemberBinding memberBinding) + { + if (memberBinding != null && memberBinding.BoundMember.NodeType == NodeType.Method) + { + Method method = (Method)memberBinding.BoundMember; + if (RequestTypeNames.Contains(method.DeclaringType.FullName) && RequestMethodNames.Contains(method.Name.Name)) + { + if (methodUnderCheck.DeclaringType.FullName != "Microsoft.OData.Client.HttpWebRequestMessage" || methodUnderCheck.Name.Name != method.Name.Name) + { + this.Problems.Add(new Problem(GetResolution(method.Name.Name))); + } + } + else if (ResponseTypeNames.Contains(method.DeclaringType.FullName) && ResponseMethodNames.Contains(method.Name.Name)) + { + if (methodUnderCheck.DeclaringType.FullName != "Microsoft.OData.Client.HttpWebResponseMessage") + { + this.Problems.Add(new Problem(GetResolution(method.Name.Name))); + } + } + } + + base.VisitMemberBinding(memberBinding); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/IDSPEnumerateTypesRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/IDSPEnumerateTypesRule.cs new file mode 100644 index 0000000..fda55d6 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/IDSPEnumerateTypesRule.cs @@ -0,0 +1,105 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using Microsoft.FxCop.Sdk; + using System.Collections.Generic; + + /// + /// This rule checks that only DataServiceWrapper.Types can call IsComplexTypeVisible() and IsEntityTypeVisible(). + /// + public class IDSPEnumerateTypesRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public IDSPEnumerateTypesRule() + : base("IDSPEnumerateTypesRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + MethodCallFinder finder = new MethodCallFinder(MethodsNotAllowedOutsideMetadataPath.ToArray()); + finder.Visit(method); + if (!IsMethodSafeToEnumerateTypes(method.FullName)) + { + foreach (string metadataOnlyMethod in MethodsNotAllowedOutsideMetadataPath) + { + if (finder.Found(metadataOnlyMethod)) + { + this.Problems.Add(new Problem(GetResolution(method.FullName, metadataOnlyMethod))); + } + } + } + + return Problems.Count > 0 ? Problems : null; + } + + /// + /// + /// + private static List MethodsNotAllowedOutsideMetadataPath = new List() + { + "Microsoft.OData.Service.Providers.DataServiceProviderWrapper.get_VisibleTypes", + "Microsoft.OData.Service.Providers.DataServiceProviderWrapper.get_Types", + "Microsoft.OData.Service.Providers.DataServiceProviderWrapper.get_ResourceSets", + "Microsoft.OData.Service.Providers.DataServiceProviderWrapper.get_ServiceOperations", + "Microsoft.OData.Service.Providers.IDataServiceProvider.get_Types", + "Microsoft.OData.Service.Providers.IDataServiceProvider.get_ResourceSets", + "Microsoft.OData.Service.Providers.IDataServiceProvider.get_ServiceOperations", + }; + + /// + /// + /// + private static List MethodsSafeToEnumerateTypes = new List() + { + "Microsoft.OData.Service.RequestDescription.UpdateMetadataVersion(Microsoft.OData.Service.Caching.DataServiceCacheItem,Microsoft.OData.Service.Providers.DataServiceProviderWrapper,Microsoft.OData.Service.Providers.BaseServiceProvider)", + "Microsoft.OData.Service.Providers.ObjectContextServiceProvider.CheckConfigurationConsistency(Microsoft.OData.Service.DataServiceConfiguration)", + "Microsoft.OData.Service.Providers.ObjectContextServiceProvider.GetMetadata(System.Xml.XmlWriter,Microsoft.OData.Service.Providers.DataServiceProviderWrapper,Microsoft.OData.Service.IDataService)", + "Microsoft.OData.Service.Providers.ObjectContextServiceProvider+MetadataManager.#ctor(System.Data.Metadata.Edm.MetadataWorkspace,System.Data.Metadata.Edm.EntityContainer,Microsoft.OData.Service.Providers.DataServiceProviderWrapper,Microsoft.OData.Service.IDataService)", + "Microsoft.OData.Service.Serializers.ServiceDocumentSerializer.WriteServiceDocument(Microsoft.OData.Service.Providers.DataServiceProviderWrapper)", + "Microsoft.OData.Service.Providers.MetadataProviderEdmModel.GroupResourceTypesByNamespace(System.Boolean@,System.Boolean@)", + "Microsoft.OData.Service.Providers.MetadataProviderEdmModel.EnsureStructuredTypes", + "Microsoft.OData.Service.Providers.MetadataProviderEdmModel.PairUpNavigationProperties", + "Microsoft.OData.Service.Providers.MetadataProviderEdmModel.EnsureEntityContainers", + "Microsoft.OData.Service.Providers.DataServiceProviderWrapper.PopulateMetadataCacheItemForBuiltInProvider", + "Microsoft.OData.Service.RequestDescription.UpdateMetadataVersion(Microsoft.OData.Service.Caching.DataServiceCacheItem,Microsoft.OData.Service.Providers.DataServiceProviderWrapper)" + }; + + /// + /// + /// + /// + /// + private bool IsMethodSafeToEnumerateTypes(string methodName) + { + return MethodsSafeToEnumerateTypes.Exists(s => s == methodName); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/MethodCallFinder.cs b/ApiAsAService/odata.net/tools/FxCopRules/MethodCallFinder.cs new file mode 100644 index 0000000..6deda19 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/MethodCallFinder.cs @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using Microsoft.FxCop.Sdk; + using System.Collections.Generic; + internal class MethodCallFinder : BinaryReadOnlyVisitor + { + private readonly Dictionary timesFound = new Dictionary(); + private readonly HashSet alreadyExplored = new HashSet(); + private bool exploreGraph = false; + + internal MethodCallFinder(params string[] fullNames) + : this(false, (IEnumerable)fullNames) + { + } + + internal MethodCallFinder(bool exploreGraph, params string[] fullNames) + : this(exploreGraph, (IEnumerable)fullNames) + { + } + + internal MethodCallFinder(bool exploreGraph, IEnumerable fullNames) + { + this.exploreGraph = exploreGraph; + foreach (string fullName in fullNames) + { + this.timesFound.Add(fullName, 0); + } + } + + internal IDictionary TimesFound { get { return this.timesFound; } } + internal bool Found(string fullName) { return this.timesFound[fullName] > 0; } + + public override void VisitMethodCall(MethodCall call) + { + if (call != null) + { + MemberBinding binding = call.Callee as MemberBinding; + Method calleeMethod = (binding == null) ? null : binding.BoundMember as Method; + if (calleeMethod != null) + { + + string fullName = calleeMethod.FullName; + if (exploreGraph && alreadyExplored.Add(fullName)) + { + // see if the method(s) is is the call graph + Visit(calleeMethod); + } + + int hits; + if (this.timesFound.TryGetValue(fullName, out hits)) + { + this.timesFound[fullName] = hits + 1; + } + } + } + + base.VisitMethodCall(call); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/FxCopRules/MethodCallNotAllowed.cs b/ApiAsAService/odata.net/tools/FxCopRules/MethodCallNotAllowed.cs new file mode 100644 index 0000000..b2de5c3 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/MethodCallNotAllowed.cs @@ -0,0 +1,230 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + #region Namespaces + + using System; + using System.Linq; + using Microsoft.FxCop.Sdk; + + #endregion Namespaces + + /// This rule checks all the rules declared statically. + public class MethodCallNotAllowed : BaseDataWebRule + { + private static readonly MethodCallToDisallow[] Items = new MethodCallToDisallow[] + { + new MethodCallToDisallow + { + Description = "Static Create should be used to ensure no instances with null are created.", + NamespacesToCheck = "*", + DeclaringTypeOfMethodToExclude = "Microsoft.OData.Client.DataServiceQueryContinuation", + NameOfMethodToExclude = ".ctor" + }, + new MethodCallToDisallow + { + Description = "Static Create should be used to ensure no instances with null are created.", + NamespacesToCheck = "*", + DeclaringTypeOfMethodToExclude = "Microsoft.OData.Client.DataServiceQueryContinuation`1", + NameOfMethodToExclude = ".ctor" + }, + new MethodCallToDisallow + { + Description = "ODataLib should not call IEdmModel.FindType() directly because the type resolver is required for the WcfDataServicesClientBehavior.", + NamespacesToCheck = "Microsoft.OData.Core", + DeclaringTypeOfMethodToExclude = "Microsoft.OData.Edm.ExtensionMethods", + NameOfMethodToExclude = "FindType" + }, + new MethodCallToDisallow + { + Description = "ODataLib should not call IEdmModel.FindDeclaredType() directly because the type resolver is required for the WcfDataServicesClientBehavior.", + NamespacesToCheck = "Microsoft.OData.Core", + DeclaringTypeOfMethodToExclude = "Microsoft.OData.Edm.IEdmModel", + NameOfMethodToExclude = "FindDeclaredType" + }, + new MethodCallToDisallow + { + Description = "ODataLib should not call IEdmEntitySet.get_ElementType() directly because the type resolver is required for the WcfDataServicesClientBehavior.", + NamespacesToCheck = "Microsoft.OData.Core", + DeclaringTypeOfMethodToExclude = "Microsoft.OData.Edm.IEdmEntitySet", + NameOfMethodToExclude = "get_ElementType" + }, + new MethodCallToDisallow + { + Description = "ODataLib should not call IEdmOperationParameter.get_Type() directly because the type resolver is required for the WcfDataServicesClientBehavior.", + NamespacesToCheck = "Microsoft.OData.Core", + DeclaringTypeOfMethodToExclude = "Microsoft.OData.Edm.IEdmOperationParameter", + NameOfMethodToExclude = "get_Type" + }, + new MethodCallToDisallow + { + Description = "ODataLib should not call IEdmFunctionBase.get_ReturnType() directly because the type resolver is required for the WcfDataServicesClientBehavior.", + NamespacesToCheck = "Microsoft.OData.Core", + DeclaringTypeOfMethodToExclude = "Microsoft.OData.Edm.IEdmFunctionBase", + NameOfMethodToExclude = "get_ReturnType" + }, + }; + + private Method methodUnderCheck; + + /// Initializes a new instance. + public MethodCallNotAllowed() + : base("MethodCallNotAllowed") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + this.methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitConstruct(Construct cons) + { + if (cons != null) + { + MemberBinding constructorBinding = cons.Constructor as MemberBinding; + if (constructorBinding != null) + { + Method method = constructorBinding.BoundMember as Method; // More likely: InstanceInitializer; + this.CheckMethodUsage(method); + } + } + + base.VisitConstruct(cons); + } + + private void CheckMethodUsage(Method method) + { + foreach (MethodCallToDisallow item in Items) + { + if (item.MatchesMethodToDisallow(this.methodUnderCheck, method)) + { + Problem problem = new Problem(GetResolution( + item.DeclaringTypeOfMethodToExclude, + item.NameOfMethodToExclude, + this.methodUnderCheck.DeclaringType.FullName, + this.methodUnderCheck.Name.Name, item.Description)); + this.Problems.Add(problem); + } + } + } + + /// Visits a method call. + /// Call. + /// Resulting expression to visit. + public override void VisitMethodCall(MethodCall methodCall) + { + if (methodCall != null) + { + MemberBinding callBinding = methodCall.Callee as MemberBinding; + if (callBinding != null) + { + Method method = callBinding.BoundMember as Method; + this.CheckMethodUsage(method); + } + } + + base.VisitMethodCall(methodCall); + } + + private class MethodCallToDisallow + { + /// + /// Namespaces to perform the check. null indicates to check all namespaces. + /// + private string[] namespacesToCheck; + + /// + /// Description of the rule. + /// + internal string Description { get; set; } + + /// + /// Semi-colon delimited namespaces where this rule applies. "*" indicates to check all namespaces. + /// + internal string NamespacesToCheck + { + set + { + if (!string.IsNullOrEmpty(value) && value != "*") + { + this.namespacesToCheck = value.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries); + } + } + } + + /// + /// Declaring type of the method to exclude. If the value is "*", exclude the matching method name declared under all types. + /// + internal string DeclaringTypeOfMethodToExclude { get; set; } + + /// + /// Method name to exclude. If the value is "*", exclude all methods declared under the type DeclaringTypeOfMethodToExclude. + /// + internal string NameOfMethodToExclude { get; set; } + + internal bool MatchesMethodToDisallow(Method callerMethod, Method calleeMethod) + { + if (this.ShouldCheckMethodOnType(callerMethod.DeclaringType.FullName)) + { + return NamesMatchMethod(this.DeclaringTypeOfMethodToExclude, this.NameOfMethodToExclude, calleeMethod); + } + + return false; + } + + private bool ShouldCheckMethodOnType(string callerMethodDeclaringTypeFullName) + { + if (this.namespacesToCheck == null) + { + return true; + } + + return this.namespacesToCheck.Any(namespaceToCheck => callerMethodDeclaringTypeFullName.StartsWith(namespaceToCheck + ".")); + } + + private static bool NamesMatchMethod(string typeName, string methodName, Method method) + { + if (method == null) + { + return false; + } + + if (typeName != "*" && typeName != method.DeclaringType.FullName) + { + return false; + } + + return methodName == "*" || methodName == method.Name.Name; + } + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/ProcessRequestUriRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/ProcessRequestUriRule.cs new file mode 100644 index 0000000..da93c73 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/ProcessRequestUriRule.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using Microsoft.FxCop.Sdk; + using System.Collections.Generic; + + /// + /// This rule checks that every method that calls ProcessRequestUri also + /// checks rights. + /// + public class ProcessRequestUriRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public ProcessRequestUriRule() + : base("ProcessRequestUriRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + const string RequestUriName = "Microsoft.OData.Service.RequestUriProcessor.ProcessRequestUri(System.String,Microsoft.OData.Service.IDataService)"; + const string CheckResourceRightsName = "Microsoft.OData.Service.DataServiceConfiguration.CheckResourceRights(Microsoft.OData.Service.Providers.ResourceContainer,Microsoft.OData.Service.EntitySetRights)"; + const string CheckResourceRightsForReadName = "Microsoft.OData.Service.DataServiceConfiguration.CheckResourceRightsForRead(Microsoft.OData.Service.Providers.ResourceContainer,System.Boolean)"; + MethodCallFinder finder = new MethodCallFinder(RequestUriName, CheckResourceRightsName, CheckResourceRightsForReadName); + finder.Visit(method); + if (finder.Found(RequestUriName) && + (!finder.Found(CheckResourceRightsForReadName) && + !finder.Found(CheckResourceRightsName))) + { + this.Problems.Add(new Problem(GetResolution(method.FullName))); + } + + return Problems.Count > 0 ? Problems : null; + } + } + + +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/Rules.xml b/ApiAsAService/odata.net/tools/FxCopRules/Rules.xml new file mode 100644 index 0000000..649102c --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/Rules.xml @@ -0,0 +1,258 @@ + + + + + AtomMaterializerInvoker should contain exactly the methods used to support projection plans. + + AtomMaterializerInvoker is an intermediate type that can be used to compile lambdas with signatures + that do not contain parameter types referencing non-public types such as AtomEntry. + Its purpose is to support projection plans in Silverlight and other low-trust environments, and + simply forward functionality to AtomMaterializer. + This rule will be violated if the type contains methods that aren't used for project plan compilation. + + Remove the method '{0}' on AtomMaterializerInvoker if it's not needed for projection plan compilation. + Warning + NonBreaking + http://mswikis/dpdev/Astoria/AtomMaterializerInvokerRule.aspx + mruiz + mruiz + + + + Use a constructor for HashSet<T> or Dictionary<TKey,TValue> with an explicit object equality comparer + + A HashSet constructor or a Dictionary constructor that doesn't specify an EqualityComparer picks one based on the type. + This comparer may be more expensive than what is really needed, and it's hard to figure out the exact behavior from the call site code. + Prefer a constructor that takes an explicit EqualityComparer (use EqualityComparer<T>.Default to get the default). + + Change the use of constructor on '{0}' to specify an explicit EqualityComparer constructor. + Warning + NonBreaking + http://mswikis/dpdev/Astoria/HashSetCtorRule.aspx + mruiz + mruiz + + + + Follow + + Provides rules to enforce that certain method calls are not allowed. + + Do not use method '{0}.{1}' from '{2}.{3}'. {4} + Warning + NonBreaking + http://mswikis/dpdev/Astoria/MethodCallNotAllowed.aspx + jli + jli + + + + Check rights immediately after calling ProcessRequestUri. + + ProcessRequestUri does not check rights on the last segment of the + given URI. It is up to the caller to perform this check, based on + what the inteded action is. + + + Add a call to DataServiceConfiguration.CheckResourceRights or + DataServiceConfiguration.CheckResourceRightsForRead to '{0}'. + + Warning + NonBreaking + http://mswikis/dpdev/Astoria/ProcessRequestUriRule.aspx + mruiz + mruiz + + + + Use ThreadStatic fields rather than Thread.GetData and Thread.SetData. + + ThreadStatic fields have better performance than Thread.GetData and Thread.SetData, and equivalent semantics. + + Change the use of Thread.GetData/Thread.SetData on '{0}' to use a ThreadStatic field instead. + Warning + NonBreaking + http://mswikis/dpdev/Astoria/ThreadGetSetDataRule.aspx + mruiz + mruiz + + + + Use a constructor for DataServiceException with an explicit error code. + A DataServiceException should always specify the status code for a response. + Change the use of constructor on '{0}' to specify an explicit status code parameter. + Warning + NonBreaking + http://mswikis/dpdev/Astoria/DataServiceExceptionCtorRule.aspx + mruiz + mruiz + + + + Do not enumerate through all resource types except in the $metadata code path. + Do not enumerate through all resource types except in the $metadata code path. + Method '{0}' calls '{1}' which enumerates through all resource types. You may only call it from inside the $metadata code path. + Warning + NonBreaking + http://mswikis/dpdev/Astoria/IDSPEnumerateTypesRule.aspx + jli + jli + + + + Do not call EntityDescriptor.SelfLink public property. Use EntityDescriptor.GetLink internal method instead. + + Since in V1, we did not support self links, we need to support scenarios where the self link is not specified. Hence we decided + that if the self link is not specified, we will fall back to the edit link. GetLink method encapsulates this logic inside it. + + Method '{0}' makes call to EntityDescriptor.SelfLink property. Use EntityDescriptor.GetLink method instead. + Warning + NonBreaking + http://mswikis/dpdev/Astoria/SelfLinkRule.aspx + pratikp + pratikp + + + Do not call typeof(DataServiceCollection<>). Use WebUtil.IsDataServiceCollectionType(Type t) or WebUtil.GetDataServiceCollectionOfT(params Type[] typeArguments) method instead. + DataServiceCollection<> derives from ObservableCollection<>. ObservableCollection<> lives in WindowsBase.dll assembly which may not exist on some platforms (e.g. IA64). To check if a type is DataServiceCollection<> use WebUtil.IsDataServiceCollectionType(Type t) method. To create a DataServiceCollection<T> object call WebUtil.GetDataServiceCollectionOfT(params Type[] typeArguments) method. + Method '{0}' is using typeof(DataServiceCollection<>) expression. Use WebUtil.IsDataServiceCollectionType(Type t) or WebUtil.GetDataServiceCollectionOfT(params Type[] typeArguments) method instead. + Warning + NonBreaking + http://mswikis/dpdev/Astoria/TypeOfDataServiceCollectionOfTRule.aspx + pawelka + pawelka + + + + Calling ToString() and OriginalString on Uri instance could lead to problems with escape sequences. + + Uri.ToString() will unescape escaped sequences in the uri, while OriginalString may not contain any escaped sequences. + This is potentially dangerous in Identities and payloads. + + + Uri.ToString() should only be used for final display purposes, while OriginalString should only be used in payloads and for verification purposes. + For usage of OriginalString, verify that the source of the Uri instance is fully escaped. + For all other cases when a Uri is needed to be converted to a string, use Microsoft.OData.Client.CommonUtil.UriToString() + + Error + Breaking + http://mswikis/dpdev/Astoria/SystemUriToStringRule.aspx + pqian + pqian + + + + Do not call the following public properties on EntityDescriptor from SaveResult or BaseSaveResult files. + + The issue is that in non-batch SaveChanges scenario, we have transient entity descriptors which contain the latest metadata for an entity + from the response of a previous request within the SaveChanges call. We need to use the latest metadata, and hence we should be calling the + internal methods which check for the presence of transient entity descriptors. + + + Please call the equivalent internal methods instead of public properties. + + Error + Breaking + http://mswikis/dpdev/Astoria/EntityDescriptorPublicPropertiesRule.aspx + pratikp + pratikp + + + + Using CodeTypeReference without CodeTypeReferenceOptions.GlobalReference + + In order to generate correct code for users who uses customized System namespace, the CodeTypeReference should always include a GlobalReference + + + Change the method {0}: for creating CodeTypeReference, use the constructor overload with CodeTypeReferenceOptions instead. For creating CodeTypeReferenceExpression, use constructor overload which takes CodeTypeReference instead. + + Error + Breaking + http://mswikis/dpdev/Astoria/CodeTypeReferenceRule.aspx + pqian + pqian + + + + Do not call certain methods on HttpWebRequest or HttpWebResponse directly. + + With the following HttpWebRequest and HttpWebResponse methods, we need to do either debug validation of the HTTP headers, or call a test hook + that helps with payload and header verification. See comments on the individual methods for more details. + + HttpWebRequest: GetResponse, BeginGetResponse, EndGetResponse, GetRequestStream, BeginGetRequestStream, EndGetRequestStream + HttpWebResponse: GetResponseStream + + + All the methods on HttpWebRequest must be encapsulated within HttpWebRequestMessage. All the methods on HttpWebResponse must be encapsulated within HttpWebResponseMessage. + + Error + Breaking + http://mswikis/dpdev/Astoria/HttpWebRequestRule.aspx + pratikp + pratikp + + + When catching the type Exception be sure to check CommonUtil.IsCatchableExceptionType + + catch(Exception) requires that we call CommonUtil.IsCatchableExceptionType and if it returns false, we need to simply rethrow (throw;) the exception. + Otherwise we end up eating system exceptions like OutOfMemoryException. + + DoNotHandleProhibitedExceptionsRule: Add a call to CommonUtil.IsCatchableExceptionType to check if this is a handlable exception, and rethrow if it is not. + Error + NonBreaking + http://mswikis/dpdev/Astoria/DoNotHandleProhibitedExceptionsRule.aspx + jeffreed + jeffreed + + + + Do not call the SpatialImplementation.Operations public property. + + SpatialImplementation.Operations property does not check for null value. + + + Call SpatialImplementation.VerifyAndGetNonNullOperations which does the null check. + + Error + Breaking + http://mswikis/dpdev/Astoria/SystemSpatialOperationsPropertyRule.aspx + pratikp + pratikp + + + + Calling System.Uri.EscapeDataString can cause problems if the data being escaped contains the single quote literal delimiter character. + + In .NET 4.0 and earlier, Uri.EscapeDataString does not escape single quotes. In .NET 4.5, this has been changed so that single quotes are included + in the list of characters the method escapes. See Dev11:93651 for more details. In order to be compatible with OData servers and clients that + are not expecting this character to be escaped when it is used as a literal delimiter, we need to ensure we do not escape this character. + + + Uri.EscapeDataString should only be used in cases where the string being escaped will never contain single quotes as a literal delimiter. + Where possible, call Uri.EscapeDataString on the data before the delimiters are added, or consider using DataStringEscapeBuilder.EscapeDataString instead. + + Warning + Breaking + http://mswikis/dpdev/Astoria/SystemUriEscapeDataStringRule.aspx + sparra + sparra + + + + Using certain ODataLib object-model properties directly can break support for omitting certain payload annotations via a query option. + + Because certain payload metadata annotations may be computable client side, a user can choose to have them omitted from the response payload + using a query option. This is implemented by ignoring the values set by the serializer. Setting the properties directly will cause them to + be written regardless of what was specified in the request. + Getting a value of these properties is similarly dangerous because the value may be uninitialized due to it being omitted. + + + Do not get or set {1}.{0} directly. Instead, use an implementation of MetadataQueryOptionHandler to set the values on the {1} instance you are modifying. + + Warning + Breaking + http://mswikis/dpdev/Astoria/ShouldNotDireclyAccessPayloadMetadataProperties.aspx + mmeehan + mmeehan + + diff --git a/ApiAsAService/odata.net/tools/FxCopRules/SelfLinkRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/SelfLinkRule.cs new file mode 100644 index 0000000..6a9e8f8 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/SelfLinkRule.cs @@ -0,0 +1,74 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that no one calls EntityDescriptor.SelfLink public property. Instead they should call EntityDescriptor.GetSelfLink method + /// + public class SelfLinkRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public SelfLinkRule() + : base("SelfLinkRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitMethodCall(MethodCall methodCall) + { + if (methodCall != null) + { + MemberBinding callBinding = methodCall.Callee as MemberBinding; + if (callBinding != null) + { + Method method = callBinding.BoundMember as Method; + if (method != null && + method.DeclaringType.FullName == "Microsoft.OData.Client.EntityDescriptor" && + method.Name.Name == "get_SelfLink" && + methodUnderCheck.FullName != "Microsoft.OData.Client.EntityDescriptor.GetLink(System.Boolean)") + { + this.Problems.Add(new Problem(GetResolution(methodUnderCheck.FullName))); + } + } + } + + base.VisitMethodCall(methodCall); + } + } +} + diff --git a/ApiAsAService/odata.net/tools/FxCopRules/ShouldNotDireclyAccessPayloadMetadataProperties.cs b/ApiAsAService/odata.net/tools/FxCopRules/ShouldNotDireclyAccessPayloadMetadataProperties.cs new file mode 100644 index 0000000..4ad6280 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/ShouldNotDireclyAccessPayloadMetadataProperties.cs @@ -0,0 +1,111 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using Microsoft.OData.Service.Serializers; + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that certain ODL object-model properties which are affected + /// by the metadata query-option are not set directly. + /// + public sealed class ShouldNotDireclyAccessPayloadMetadataProperties : BaseDataWebRule + { + /// Initializes a new instance. + public ShouldNotDireclyAccessPayloadMetadataProperties() + : base("ShouldNotDireclyAccessPayloadMetadataProperties") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Visit(member); + return Problems.Count > 0 ? Problems : null; + } + + public override void VisitMethodCall(MethodCall call) + { + if (call.SourceContext.FileName == null + || !call.SourceContext.FileName.Contains(@"System\Data\Services") + || call.SourceContext.FileName.Contains(@"System\Data\Services\Client")) + { + return; + } + + MemberBinding callBinding = call.Callee as MemberBinding; + if (callBinding != null) + { + Method method = (Method)callBinding.BoundMember; + + if (callBinding.TargetObject == null || callBinding.TargetObject.Type == null) + { + return; + } + + string[] disallowedPropertyNames; + string targetTypeName = callBinding.TargetObject.Type.FullName; + switch (targetTypeName) + { + case "Microsoft.OData.Core.ODataEntry": + disallowedPropertyNames = Enum.GetNames(typeof(PayloadMetadataKind.Entry)); + break; + + case "Microsoft.OData.Core.ODataResourceSet": + disallowedPropertyNames = Enum.GetNames(typeof(PayloadMetadataKind.Feed)); + break; + + case "Microsoft.OData.Core.ODataNestedResourceInfo": + disallowedPropertyNames = Enum.GetNames(typeof(PayloadMetadataKind.Navigation)); + break; + + case "Microsoft.OData.Core.ODataAssociationLink": + disallowedPropertyNames = Enum.GetNames(typeof(PayloadMetadataKind.Association)); + break; + + case "Microsoft.OData.Core.ODataStreamReferenceValue": + disallowedPropertyNames = Enum.GetNames(typeof(PayloadMetadataKind.Stream)); + break; + + case "Microsoft.OData.Core.ODataOperation": + case "Microsoft.OData.Core.ODataAction": + case "Microsoft.OData.Core.ODataFunction": + disallowedPropertyNames = Enum.GetNames(typeof(PayloadMetadataKind.Operation)); + break; + + default: + return; + } + + foreach (var propertyName in disallowedPropertyNames) + { + bool isSetter = method.FullName.Contains(".set_" + propertyName); + bool isGetter = method.FullName.Contains(".get_" + propertyName); + if (isSetter || isGetter) + { + Problem problem = new Problem(this.GetResolution(propertyName, targetTypeName)); + this.Problems.Add(problem); + } + } + } + + base.VisitMethodCall(call); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/SystemSpatialOperationsPropertyRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/SystemSpatialOperationsPropertyRule.cs new file mode 100644 index 0000000..7eaaf51 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/SystemSpatialOperationsPropertyRule.cs @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that no call to SpatialImplementation.Operations property is made from the product code. + /// Instead they should call SpatialImplementation.VerifyAndGetNonNullOperations method. + /// + public class SystemSpatialOperationsPropertyRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public SystemSpatialOperationsPropertyRule() + : base("SystemSpatialOperationsPropertyRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitMethodCall(MethodCall methodCall) + { + if (methodCall != null) + { + MemberBinding callBinding = methodCall.Callee as MemberBinding; + if (callBinding != null) + { + Method method = callBinding.BoundMember as Method; + if (method != null && + method.DeclaringType.DeclaringModule.ContainingAssembly.Name == "Microsoft.Spatial" && + method.DeclaringType.FullName == "Microsoft.Spatial.SpatialImplementation" && + method.Name.Name == "get_Operations" && + method.IsPublic && + method.NodeType == NodeType.Method) + { + if (methodUnderCheck.Name.Name != "VerifyAndGetNonNullOperations") + { + this.Problems.Add(new Problem(GetResolution(methodUnderCheck.FullName))); + } + } + } + } + + base.VisitMethodCall(methodCall); + } + } +} + diff --git a/ApiAsAService/odata.net/tools/FxCopRules/SystemUriEscapeDataStringRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/SystemUriEscapeDataStringRule.cs new file mode 100644 index 0000000..6e493a2 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/SystemUriEscapeDataStringRule.cs @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using Microsoft.FxCop.Sdk; + + public class SystemUriEscapeDataStringRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public SystemUriEscapeDataStringRule() + : base("SystemUriEscapeDataStringRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + public override void VisitMethodCall(MethodCall call) + { + MemberBinding callBinding = call.Callee as MemberBinding; + if (callBinding != null) + { + Method method = callBinding.BoundMember as Method; + + if (method.FullName == "System.Uri.EscapeDataString(System.String)") + { + Problem problem = new Problem(GetResolution()); + this.Problems.Add(problem); + } + } + + base.VisitMethodCall(call); + } + + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/SystemUriToStringRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/SystemUriToStringRule.cs new file mode 100644 index 0000000..b81a5a0 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/SystemUriToStringRule.cs @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using Microsoft.FxCop.Sdk; + + public class SystemUriToStringRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public SystemUriToStringRule() + : base("SystemUriToStringRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + public override void VisitMethodCall(MethodCall call) + { + MemberBinding callBinding = call.Callee as MemberBinding; + if (callBinding != null) + { + Method method = callBinding.BoundMember as Method; + + if (callBinding.TargetObject != null && callBinding.TargetObject.Type != null && callBinding.TargetObject.Type.FullName == "System.Uri") + { + if (method.FullName == "System.Object.ToString" || + method.FullName == "System.Uri.get_OriginalString") + { + Problem problem = new Problem(GetResolution()); + this.Problems.Add(problem); + } + } + } + + base.VisitMethodCall(call); + } + + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/ThreadGetSetDataRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/ThreadGetSetDataRule.cs new file mode 100644 index 0000000..79ca7df --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/ThreadGetSetDataRule.cs @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that Thread.GetData and Thread.SetData aren't used. + /// + public class ThreadGetSetDataRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public ThreadGetSetDataRule() : base("ThreadGetSetDataRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitMethodCall(MethodCall methodCall) + { + if (methodCall != null) + { + MemberBinding callBinding = methodCall.Callee as MemberBinding; + if (callBinding != null) + { + Method method = callBinding.BoundMember as Method; + if (method != null && + method.DeclaringType.Namespace.Name == "System.Threading" && + method.DeclaringType.Name.Name == "Thread" && + (method.Name.Name == "GetData" || method.Name.Name == "SetData")) + { + this.Problems.Add(new Problem(GetResolution(methodUnderCheck.FullName))); + } + } + } + + base.VisitMethodCall(methodCall); + } + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/TypeOfDataServiceCollectionOfTRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/TypeOfDataServiceCollectionOfTRule.cs new file mode 100644 index 0000000..cc8071c --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/TypeOfDataServiceCollectionOfTRule.cs @@ -0,0 +1,87 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using Microsoft.FxCop.Sdk; + using System.Diagnostics; + + class TypeOfDataServiceCollectionOfTRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public TypeOfDataServiceCollectionOfTRule() + : base("TypeOfDataServiceCollectionOfTRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a method call. + /// Method call. + /// Resulting expression to visit. + public override void VisitMethodCall(MethodCall methodCall) + { + if (methodCall != null) + { + MemberBinding callBinding = methodCall.Callee as MemberBinding; + if (callBinding != null) + { + /* + typeof(DataServiceCollection<>) is compiled to IL as: + ldtoken [Microsoft.OData.Client]Microsoft.OData.Client.DataServiceCollection`1 + call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + */ + Method method = callBinding.BoundMember as Method; + if (method.Name.Name == "GetTypeFromHandle" && + method.DeclaringType.FullName == "System.Type") + { + Debug.Assert(methodCall.Operands.Count == 1); + Debug.Assert(methodCall.Operands[0].NodeType == NodeType.Ldtoken); + + ClassNode classType = (((Literal)(((UnaryExpression)methodCall.Operands[0]).Operand)).Value) as ClassNode; + if (classType != null && classType.FullName == "Microsoft.OData.Client.DataServiceCollection`1") + { + if (methodUnderCheck.DeclaringType.FullName != "System.Data.Serices.Client.WebUtil" && + methodUnderCheck.Name.Name != "GetDataServiceCollectionOfTType") + { + this.Problems.Add(new Problem(GetResolution(methodUnderCheck.FullName))); + } + } + } + } + } + + base.VisitMethodCall(methodCall); + } + + + } +} diff --git a/ApiAsAService/odata.net/tools/FxCopRules/WebDataServiceExceptionCtorRule.cs b/ApiAsAService/odata.net/tools/FxCopRules/WebDataServiceExceptionCtorRule.cs new file mode 100644 index 0000000..7cca762 --- /dev/null +++ b/ApiAsAService/odata.net/tools/FxCopRules/WebDataServiceExceptionCtorRule.cs @@ -0,0 +1,66 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace DataWebRules +{ + using System; + using Microsoft.FxCop.Sdk; + + /// + /// This rule checks that HashSet`1 constructor take an explicit + /// comparer object. + /// + public sealed class DataServiceExceptionCtorRule : BaseDataWebRule + { + private Method methodUnderCheck; + + /// Initializes a new instance. + public DataServiceExceptionCtorRule() + : base("DataServiceExceptionCtorRule") + { + } + + /// Visibility of targets to which this rule commonly applies. + public override TargetVisibilities TargetVisibility + { + get + { + return TargetVisibilities.All; + } + } + + /// Checks type members. + /// Member being checked. + /// A collection of problems found in or null. + public override ProblemCollection Check(Member member) + { + Method method = member as Method; + if (method == null) + { + return null; + } + + methodUnderCheck = method; + Visit(method); + return Problems.Count > 0 ? Problems : null; + } + + /// Visits a constructor invocation. + /// Construction. + /// Resulting expression to visit. + public override void VisitConstruct(Construct cons) + { + CheckMethod( + ConstructAsInstanceInitializer(cons), + x => + x.DeclaringType.FullName.StartsWith("Microsoft.OData.Service.DataServiceException") && + !HasParameterType(x, "System.Int32"), + methodUnderCheck.FullName); + + base.VisitConstruct(cons); + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/KoKoMo.csproj b/ApiAsAService/odata.net/tools/KoKoMo/KoKoMo.csproj new file mode 100644 index 0000000..e39776e --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/KoKoMo.csproj @@ -0,0 +1,40 @@ + + + Microsoft.Test.KoKoMo + ;1699;1570;1572;1573;1591;3005;3003;3008;3016;3001;3002;3009;0618;0168;0169;1684 + Microsoft.Test.KoKoMo + prompt + 4 + Library + {EB4C9641-0452-4E7F-AA38-3EBD871914A3} + false + kokomo.snk + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ApiAsAService/odata.net/tools/KoKoMo/Model.cs b/ApiAsAService/odata.net/tools/KoKoMo/Model.cs new file mode 100644 index 0000000..0f85d33 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/Model.cs @@ -0,0 +1,566 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //BindingFlags +using System.Collections.Generic; //List + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // Model (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Class)] + public class ModelAttribute : ModelItemAttribute + { + //Data + protected int _maxinstances = Int32.MaxValue; //Default (unlimited) + protected bool _inherit = false; + //Constructor + public ModelAttribute() + { + } + + //Properties + public virtual int MaxInstances + { + get { return _maxinstances; } + set { _maxinstances = value; } + } + + public virtual bool Inherit + { + get { return _inherit; } + set { _inherit = value; } + } + } + + //////////////////////////////////////////////////////////////// + // Delegates + // + //////////////////////////////////////////////////////////////// + public delegate void ModelEventHandler(Object source, T item) where T : ModelItem; + public delegate bool CallBeforeHandler(ModelAction action, ModelParameters parameters); + public delegate void CallAfterHandler(ModelAction action, ModelParameters parameters, object result); + + + //////////////////////////////////////////////////////////////// + // Model + // + //////////////////////////////////////////////////////////////// + public abstract class Model : ModelItem + { + //Data + ModelActions _actions; + ModelVariables _variables; + ModelEngine _engine; + bool _isExecuting = true; + List _callbefore; + Model _parentmodel = null; + int _maxinstances = Int32.MaxValue; //Default (unlimited) + bool _inherit = false; + private static Dictionary CustomAttributes = new Dictionary(); + + //Constructor + public Model() + : base(null, null) + { + //Obtain the attribute. + //Note: We don't want to require a constructor that takes the attribute, since this + //is an inherited class by the user, we'll simply obtain the attribute for default info. + foreach (ModelAttribute attr in this.GetCustomAttributes(typeof(ModelAttribute)) ) + this.SetAttributeValues(attr); + + //By default, constructing the object, it becomes enabled. + //This is so, you can automatically construct (disabled) models, and add them to the system + //However if called in the context of the [attr] caller, it will get reset to the attribute + this.Disabled = false; + } + + public virtual void Init() + { + //Override, if you have code that should be executed first (before any methods are called) + } + + public virtual void Terminate() + { + //TODO: Call from the engine + //Override, if you have code that should be executed before the model is released + } + + public override void SetAttributeValues(ModelItemAttribute attr) + { + base.SetAttributeValues(attr); + if (attr is ModelAttribute) + { + ModelAttribute mattr = attr as ModelAttribute; + _maxinstances = mattr.MaxInstances; + _inherit = mattr.Inherit; + } + } + + public virtual ModelEngine Engine + { + get { return _engine; } + set { _engine = value; } + } + + //Accessors + + /// + /// Indicates if the model is executing IUT code or just exploring the state space. + /// + public virtual bool IsExecuting + { + get { return _isExecuting; } + set { _isExecuting = value; } + } + + public virtual int MaxInstances + { + get { return _maxinstances; } + set { _maxinstances = value; } + } + + public virtual bool Inherit + { + get { return _inherit; } + set { _inherit = value; } + } + + public virtual ModelActions Actions + { + get + { + //Dynamically find all actions (of this model) + //Note: Override if you had a different way to determine actions + if(_actions == null) + { + _actions = new ModelActions(); + + //Looking for *ALL* methods and properties + BindingFlags bindingflags = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static | + BindingFlags.GetProperty | BindingFlags.SetProperty | + BindingFlags.InvokeMethod; + + //Loop over them + foreach(MethodInfo info in this.GetType().GetMethods(bindingflags)) + { + foreach(ModelActionAttribute attr in info.GetCustomAttributes(typeof(ModelActionAttribute), _inherit)) + { + _actions.Add(new ModelAction(this, info, attr)); + } + } + + //Sort + _actions.SortByWeightDesc(); + } + return _actions; + } + } + + public virtual ModelVariables Variables + { + get + { + //Dynamically find all model variables + //Note: Override if you had a different way to determine variables + if(_variables == null) + { + //Find all [ModelAction] attributes + _variables = new ModelVariables(); + + //Looking for *ALL* fields and properties + BindingFlags bindingflags = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static | + BindingFlags.GetField | BindingFlags.GetProperty; + + //Loop over them + foreach(MemberInfo info in this.GetType().GetMembers(bindingflags)) + { + foreach (ModelVariableAttribute attr in info.GetCustomAttributes(typeof(ModelVariableAttribute), _inherit)) + { + _variables.Add(new ModelVariable(this, info, attr)); + } + } + + //Sort + _variables.SortByWeightDesc(); + } + return _variables; + } + } + + public event CallBeforeHandler CallBefore + { + //Note: We want the handler to be able to return a result (ie: continue) so we override this + add + { + if(_callbefore == null) + _callbefore = new List(); + _callbefore.Add(value); + } + remove + { + if(_callbefore != null) + _callbefore.Remove(value); + } + } + + public virtual bool OnCallBefore(ModelAction action, ModelParameters parameters) + { + //Note: Override if you want to do something, or call other methods BEFORE execution of the action + bool result = true; //true, indicates continue to call the action + if(_callbefore != null) + { + foreach(CallBeforeHandler handler in _callbefore) + result &= handler(action, parameters); + } + + return result; + } + + public event CallAfterHandler CallAfter; + public virtual void OnCallAfter(ModelAction action, ModelParameters parameters, object result) + { + //Note: Override if you want to do after + if(CallAfter != null) + CallAfter(action, parameters, result); + } + + public virtual void OnException(ModelAction action, ModelParameters parameters, Exception e) + { + //Since were using reflection, if an error occurs within the method + //make this easier to debug (for the method writer) so they see their exception + //instead of the reflection based one. + while(e.InnerException != null && e is TargetInvocationException) + e = e.InnerException; + + //Find what should have thrown the exception, action or parameters + Type exception = action.Exception; + string exceptionid = action.ExceptionId; + if(exception == null && exceptionid == null) + { + //Otherwise maybe one of the parameters was supposed to throw + //Find the parameter that expects an error + ModelParameters found = (ModelParameters)parameters.FindFlag((int)ModelItemFlags.Throws); + if(found.Count > 0) + { + //Note: We find the 'highest' weighted parameter (ie: order of errors processed) + found.SortByWeightDesc(); + exception = found.First.Exception; + exceptionid = found.First.ExceptionId; + } + } + + //Exception not expected + if(exception == null && exceptionid == null) + throw new ModelException(action, "Threw an exception: " + e.Message, e); + + //Expected: Simple verification, type based + if(exception != null && (exception != e.GetType())) + throw new ModelException(action, "Threw the wrong exception: " + e.Message, e); + + //Expected: Advanced verification, user implemented function + if(exceptionid != null) + this.OnException(action, parameters, e, exceptionid); //Throws if not verified + } + + public virtual void OnException(ModelAction action, ModelParameters parameters, Exception e, string id) + { + //Override this method, and verify the ExceptionId specified in the model + throw new ModelException(this, "ExceptionId was specified and not verified. Override Model.VerifyException, and verify the ExceptionId as was specified in the model", e); + } + + public override void Reload() + { + //Clear, so they will be dynamically setup by reflection again + _actions = null; + _variables = null; + } + + public virtual Model ParentModel + { + get { return _parentmodel; } + set { _parentmodel = value; } + } + + public virtual Models Children + { + get + { + //We have to recompute this everytime since + //new children could have been added. + //Can change this later. + Models found = new Models(this.Engine); + if(this.Engine != null) + { + foreach(Model model in this.Engine.Models) + { + if(model.ParentModel == this) + found.Add(model); + } + } + return found; + } + } + + protected virtual Object[] GetCustomAttributes(Type attributetype) + { + Type key = this.GetType(); + object[] value = null; + + if (!CustomAttributes.TryGetValue(key, out value)) + { + value = this.GetType().GetCustomAttributes(attributetype, _inherit); + CustomAttributes[key] = value; //add or update the value, last update wins + } + return value; + } + } + + + //////////////////////////////////////////////////////////////// + // Models + // + //////////////////////////////////////////////////////////////// + public class Models : ModelItems + { + //Data + protected ModelEngine _engine = null; + + //Constructors + + ///Constructor + ///The engine context for this collection. + public Models(ModelEngine engine) + { + _engine = engine; + } + + ///Type Indexer + public virtual Model this[Type type] + { + get + { + //Find first, fail if not found, although make it easier to debug + Models found = this.FindType(type); + if (found == null || found.Count <= 0) + throw new IndexOutOfRangeException(this.Name + "['" + type + "'] is not found"); + return found.First; + } + } + + ///Find models of type "type" + public virtual Models FindType(Type type) + { + //Find a matching variable, of the specified type + Models found = new Models(_engine); + foreach (Model model in this) + { + if (model.GetType() == type || model.GetType().IsSubclassOf(type)) + found.Add(model); + } + + //Otherwise + return found; + } + + ///Add a Model to collection + public override Model Add(Model model) + { + //Delegate + base.Add(model); + + //Hook up the engine + model.Engine = _engine; + return model; + } + + //Events + public override void OnAdded(Model model) + { + //Delegate + base.OnAdded(model); + + //Notify the parents + if(_engine != null && _engine.Parent != null) + _engine.Parent.Models.OnAdded(model); + } + + public override void OnRemoved(Model model) + { + //Delegate + base.OnRemoved(model); + + //Notify the parents + if (_engine != null && _engine.Parent != null) + _engine.Parent.Models.OnRemoved(model); + } + + ///Given the assembly or the calling object, find models in the assembly and add them to the collection + public virtual void AddFromAssembly(object caller) + { + //Delegate + //Note: Assembly.GetAssembly is not implemented in .Net CF + this.AddFromAssembly(caller.GetType().Assembly); + } + + ///Add the models found in the specified assembly + public virtual void AddFromAssembly(Assembly assembly) + { + //Find all [Model] attributes + foreach (Type type in assembly.GetTypes()) + { + //Models can be inherited from common bases + if (type.IsAbstract) + continue; + + //Loop over all attributes + foreach (ModelAttribute attr in type.GetCustomAttributes(typeof(ModelAttribute), false)) + { + //Ensure the class if of type Model + //Instead of throwing a hard to debug, constructor error + if (!type.IsSubclassOf(typeof(Model))) + throw new ModelException(_engine, "[Model] class: '" + type.Name + "' must inherit from Model"); + + //Ensure the class has a default constructor (required) + ConstructorInfo ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, new Type[0], null); //Note: Types.EmptyTypes is not supported on .Net CF + if (ctor == null) + throw new ModelException(_engine, "[Model] class: '" + type.Name + "' must implement a default constructor"); + + //Construct the model + Model model = (Model)ctor.Invoke(null); + model.SetAttributeValues(attr); + this.Add(model); + } + } + } + + /// + /// Initialize all models in the collection. + /// + public virtual void Init() + { + foreach (Model model in this) + model.Init(); + } + + /// + /// Get or Set the 'IsExecuting' property for all models in the collection. This flag allows the + /// model developer to indicate which portions of the model should be run only during execution. + /// + public virtual bool IsExecuting + { + get + { + if (this.Count == 0) + throw new InvalidOperationException("Unable to read aggregate property 'IsExecuting' because the model collection is empty."); + + bool val = this.First.IsExecuting; + + for (int i = 1; i < this.Count; i++) + { + if (val != this[i].IsExecuting) + throw new ModelException(_engine, "Unable to read aggregate property 'IsExecuting' because not all models have the same value."); + + val = this[i].IsExecuting; + } + + return val; + } + + //note: less error handling is needed here because "set" of a non-nullable type is better defined + //than "get" over an empty or varied set of objects. + set + { + foreach (Model model in this) + model.IsExecuting = value; + } + } + + ///Return a collection of ModelAction for all the actions that are defined on all models in this collection + public virtual ModelActions Actions + { + get + { + //Helper to return the flat list of all actions + ModelActions actions = new ModelActions(); + foreach (Model model in this) + actions.Add(model.Actions); + return actions; + } + } + + ///Return a collection of all variables defined in all the models in this collection + public virtual ModelVariables Variables + { + get + { + //Helper to return the flat list of all variables + ModelVariables variables = new ModelVariables(); + foreach (Model model in this) + variables.Add(model.Variables); + return variables; + } + } + + public virtual int MaxInstances + { + get + { + //Return the smaller of the values + int maxinstances = Int32.MaxValue; + foreach(Model model in this) + maxinstances = Math.Min(maxinstances, model.MaxInstances); + return maxinstances; + } + set + { + foreach(Model model in this) + model.MaxInstances = value; + } + } + + + /// + /// Set the CallBefore event on all models in the engine. + /// + public virtual event CallBeforeHandler CallBefore + { + add + { + foreach (Model model in this) + model.CallBefore += value; + } + remove + { + foreach (Model model in this) + model.CallBefore -= value; + } + } + + /// + /// Set the CallAfter event on all models in the engine. + /// + public virtual event CallAfterHandler CallAfter + { + add + { + foreach (Model model in this) + model.CallAfter += value; + } + remove + { + foreach (Model model in this) + model.CallAfter -= value; + } + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelAction.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelAction.cs new file mode 100644 index 0000000..011b4fe --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelAction.cs @@ -0,0 +1,489 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //MethodInfo +using System.Collections; //Hashtable +using System.Collections.Generic; //List +using System.Security.Permissions; +using System.Security; + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelActionFlags + // + //////////////////////////////////////////////////////////////// + public enum ModelActionFlags //: ModelItemFlags - would be nice to inherit + { + CallFirst = 0x00000010, + CallLast = 0x00000020, + CallBefore = 0x00000040, + CallAfter = 0x00000080, + } + + //////////////////////////////////////////////////////////////// + // ModelAction (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] + public class ModelActionAttribute : ModelItemAttribute + { + //Data + int _callLimit = Int32.MaxValue; //Default (unlimited) + + //Constructor + public ModelActionAttribute() + { + } + + public ModelActionAttribute(int weight) + { + this.Weight = weight; + } + + //Properties + public int CallLimit + { + get { return _callLimit; } + set { _callLimit = value; } + } + + public bool CallOnce + { + //Delegate (helper over CallLimit) + get { return _callLimit == 1; } + set { _callLimit = (value ? 1 : Int32.MaxValue); } + } + + public bool CallFirst + { + get { return base.IsFlag((int)ModelActionFlags.CallFirst); } + set { SetFlag((int)ModelActionFlags.CallFirst, value); } + } + + public bool CallLast + { + get { return base.IsFlag((int)ModelActionFlags.CallLast); } + set + { + SetFlag((int)ModelActionFlags.CallLast, value); + SetFlag((int)ModelItemFlags.Tracked, false); //CallLast actions are not tracked for coverage + } + } + + public bool CallBefore + { + get { return base.IsFlag((int)ModelActionFlags.CallBefore); } + set { SetFlag((int)ModelActionFlags.CallBefore, value); } + } + + public bool CallAfter + { + get { return base.IsFlag((int)ModelActionFlags.CallAfter); } + set { SetFlag((int)ModelActionFlags.CallAfter, value); } + } + } + + //////////////////////////////////////////////////////////////// + // ModelAction + // + //////////////////////////////////////////////////////////////// + public class ModelAction : ModelItem + { + //Data + MethodInfo _method; + ModelRequirements _requirements; + ModelParameters _parameters; + int _callLimit = Int32.MaxValue; //Default (unlimited) + + //Constructor + public ModelAction(Model model, MethodInfo method, ModelActionAttribute attr) + : base(model, attr) + { + _method = method; + if(attr!= null) + _callLimit = attr.CallLimit; + } + + public override string Name + { + get { return _method.Name; } + } + + public override int Weight + { + get { return this.Model.Weight * _weight; } + } + + public override bool Disabled + { + get { return this.Model.Disabled || base.Disabled; } + set { base.Disabled = value; } + } + + public int CallLimit + { + get { return _callLimit; } + set { _callLimit = value; } + } + + public bool CallOnce + { + //Delegate (helper over CallLimit) + get { return _callLimit == 1; } + set { _callLimit = (value ? 1 : Int32.MaxValue); } + } + + public bool CallFirst + { + get { return base.IsFlag((int)ModelActionFlags.CallFirst); } + set { SetFlag((int)ModelActionFlags.CallFirst, value); } + } + + public bool CallLast + { + get { return base.IsFlag((int)ModelActionFlags.CallLast); } + set + { + SetFlag((int)ModelActionFlags.CallLast, value); + SetFlag((int)ModelItemFlags.Tracked, false); //CallLast actions are not tracked for coverage + } + } + + public bool CallBefore + { + get { return base.IsFlag((int)ModelActionFlags.CallBefore); } + set { SetFlag((int)ModelActionFlags.CallBefore, value); } + } + + public bool CallAfter + { + get { return base.IsFlag((int)ModelActionFlags.CallAfter); } + set { SetFlag((int)ModelActionFlags.CallAfter, value); } + } + + [PermissionSet(SecurityAction.Assert, Unrestricted = true)] + [SecuritySafeCritical] + public virtual object Execute(ModelParameters parameters) + { + object output = null; + this.Accessed++; + + try + { + //Arguments + object[] args = parameters.Values.ToArray(); + + //Invoke the method (with arguments) + output = _method.Invoke(this.Model, args); + } + catch(Exception e) + { + this.Model.OnException(this, parameters, e); + return output; + } + + //Should it have thrown, and it didn't? + if(this.Throws) + throw new ModelException(this, "Requirements were not met, and was expected to throw Exception: '" + this.Exception + "'" + " ID: '" + this.ExceptionId + "''"); + return output; + } + + public virtual ModelRequirements Requirements + { + get + { + //Dynamically find all actions (of this model) + //Note: Override if you had a different way to determine actions + if(_requirements == null) + { + //Find all [ModelRequirement] attributes + _requirements = new ModelRequirements(); + foreach(ModelRequirementAttribute attr in _method.GetCustomAttributes(typeof(ModelRequirementAttribute), false/*inhert*/)) + { + if(attr._values.Count == 0) + throw new ModelException(this, "Missing requirement values."); + + //Loop through the values + foreach(ModelValue value in attr._values) + { + //Find the assoicated model variable, by name or type if not specified + ModelVariables found = null; + + //Note: We only look within the current model for the variable + //Looking across other models would be error prone, since we don't necessarily + //know how thie models are related (ie: if a variable on another model + //applies, just becuase it has the same name/type). + + //Find by name + if(attr.Variable != null) + { + found = this.Model.Variables.Find(attr.Variable); + if(found.Count == 0) + { + //Is is a dynamic variable (ie: property/field/method) + ModelRequirement requirement = new ModelRequirement(this, attr, null, value); + found.Add(requirement.Variable); + } + if(found.Count == 0) + throw new ModelException(this, "Unable to find variable: '" + attr.Variable + "'"); + } + else + { + //Find by Type + found = this.Model.Variables.FindType(value.Type); + if(found.Count == 0) + throw new ModelException(this, "Unable to find variable matching type: '" + value.Type.ToString() + "'"); + } + + //Note: Create a seperate ModelRequirement class for each modelvariable + //This keeps it simple, and is only an issue with OR clauses + if(found.Count == 1) + { + ModelVariable variable = found.First; + ModelRequirement requirement = _requirements.Find(variable); + if (requirement != null && (requirement.Throws == attr.Throws)) + { + //Add the value + requirement.Values.Add(value); + requirement.InferDynamicVariables(); //For the new value + } + else + { + //Add the requirement + requirement = new ModelRequirement(this, attr, variable, value); + _requirements.Add(requirement); + } + } + else + { + throw new ModelException(this, "Ambigous variable " + (attr.Variable != null ? ("'" + attr.Variable + "' ") : "") + "multiple ModelVariables exists with type: '" + value.Type + "', you will need to explicitly specify the variable and/or model your refering to in the requirement. The recommended design is to use unique enumerations for model variables instead."); + } + } + } + + //Sort + _requirements.SortByWeightDesc(); + } + return _requirements; + } + } + + public virtual ModelParameters Parameters + { + get + { + //Dynamically find all actions (of this model) + //Note: Override if you had a different way to determine actions + if(_parameters == null) + { + _parameters = new ModelParameters(); + + //Obtain the method parameter info + ParameterInfo[] infos = _method.GetParameters(); + + //Find all [ModelParameter] attributes + foreach(ModelParameterAttribute attr in _method.GetCustomAttributes(typeof(ModelParameterAttribute), false/*inhert*/)) + { + //Find the assoicated ParameterInfo (from the method) + ParameterInfo info = null; + + //Find by index, if specified + if(attr.Position >= 0) + { + if(attr.Position >= infos.Length) + throw new ModelException(this, "Parameter position '" + attr.Position + "' is greater than the number of actual method parameters"); + info = infos[attr.Position]; + } + + //Find by name, if specified + if(attr.Name != null) + { + foreach(ParameterInfo p in infos) + { + if(String.Compare(p.Name, attr.Name, true/*ignore case*/)==0) + { + info = p; + break; + } + } + if(info == null) + throw new ModelException(this, "Unable to find method parameter: " + attr.Name); + } + + //Still not specified + if(info == null) + { + if(infos.Length == 1) //If only one parameter, no need to specify, no ambiguity + info = infos[0]; + else if(infos.Length == 0) //No parameters, but params specified + throw new ModelException(this, "MethodParameter attribute is specified, but this action doesn't take parameters?"); + else //More than one parameter, ambigous + throw new ModelException(this, "When there are numerous method parameters, ModelParameter.Position or .Name is required in order to distinguish."); + } + + //Add the parameter + ModelParameter parameter = new ModelParameter(this, attr, info); + _parameters.Add(parameter); + } + + //Loop over the physical parameters + // 1. Looking for additional attributes + // 2. Ensuring we have a [parameter] specified for every actual parameter + foreach(ParameterInfo info in infos) + { + //Add the parameters attributes, if any are specified on the actual parameter + foreach(ModelParameterAttribute attr in info.GetCustomAttributes(typeof(ModelParameterAttribute), false/*inhert*/)) + _parameters.Add(new ModelParameter(this, attr, info)); + + //Ensure all parameters have attributes + if (_parameters.Find(info.Name).Count <= 0) + throw new ModelException(this, "Unable to find a ModelParameter for parameter: '" + info.Name + "'"); + } + + //Sort + _parameters.SortByWeightDesc(); + } + return _parameters; + } + } + + public virtual MethodInfo Method + { + get { return _method; } + } + + public override void Reload() + { + //Clear, so they will be dynamically setup by reflection again + _requirements = null; + _parameters = null; + } + + public virtual object Clone() + { + ModelAction clone = (ModelAction)this.MemberwiseClone(); + + //Clone the collections, so add/remove is independent + clone._requirements = (ModelRequirements)this.Requirements.Clone(); + clone._parameters = (ModelParameters)this.Parameters.Clone(); + return clone; + } + } + + //////////////////////////////////////////////////////////////// + // ModelActions + // + //////////////////////////////////////////////////////////////// + + ///This class represents a collection of ModelAction + public class ModelActions : ModelItems + { + //Constructors + ///Default Constructor + public ModelActions() + { + } + + ///Constructor with multiple actions + public ModelActions(params ModelAction[] actions) + : base(actions) + { + } + + ///Return a collection of all parameters defined in all the actions in this collection + public virtual ModelParameters Parameters + { + get + { + //Helper to return the flat list of all variables + ModelParameters found = new ModelParameters(); + foreach(ModelAction action in this) + found.Add(action.Parameters); + return found; + } + } + + public new virtual ModelActions Find(params string[] names) + { + return (ModelActions)base.Find(names); + } + + public new virtual ModelActions FindExcept(params string[] names) + { + return (ModelActions)base.FindExcept(names); + } + + ///Find all actions that match the give flag + public virtual ModelActions FindFlag(ModelActionFlags flag) + { + //Delegate + return this.FindFlag(flag, true); + } + + ///Find all actions that dont match the given flag + public virtual ModelActions FindFlag(ModelActionFlags flag, bool include) + { + //Delegate + return (ModelActions)base.FindFlag((int)flag, include); + } + } + + //////////////////////////////////////////////////////////////// + // ModelActionInfo + // + //////////////////////////////////////////////////////////////// + public class ModelActionInfo + { + //Data + ModelAction _action; + ModelParameters _parameters; + object _retval; + bool _created; + + //Constructor + public ModelActionInfo(ModelAction action, ModelParameters parameters, object retval ) + { + _action = action; + _parameters = parameters; + _retval = retval; + } + + //Accessors + public ModelAction Action + { + get {return _action; } + set {_action = value; } + } + + public ModelParameters Parameters + { + get { return _parameters; } + set { _parameters = value; } + } + + public object RetVal + { + get { return _retval; } + set { _retval = value; } + } + + public bool Created + { + get { return _created; } + set { _created = value; } + } + } + + //////////////////////////////////////////////////////////////// + // ModelActionInfos + // + //////////////////////////////////////////////////////////////// + public class ModelActionInfos : List + { + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelEngine.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelEngine.cs new file mode 100644 index 0000000..27f2312 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelEngine.cs @@ -0,0 +1,769 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //Reflection +using System.Collections; //IEnumerable +using System.Collections.Generic; //List +using System.Threading; //Timeout + +//////////////////////////////////////////////////////////////// +// 'Static' State Variable Design +// Maintain limited state variables, not explosive states +// Very little code +// Easy parameter passing (equivilent classes) +// Transitions well to Spec# +// Finite +// Known at compile time +// Able to pre-determine paths (shortest, bee-line, etc) +// Able to run until all values hit +// All transitions easily determined +// +// Example: +// +// [Model] +// class +// { +// public enum Opened +// { +// No, +// Yes, +// } +// +// [ModelVariable] +// Opened _opened = Opened.No; +// +// [ModelAction(Weight = 10) +// [ModelRequirement(Opened.No)] +// void Open() +// { +// //perform the operation +// //verify +// +// //Behavior +// _opened = Opened.Yes; +// } +// } +// +//////////////////////////////////////////////////////////////// + +namespace Microsoft.Test.KoKoMo +{ + + //////////////////////////////////////////////////////////////// + // ModelEngineFlags + // + //////////////////////////////////////////////////////////////// + public enum ModelEngineFlags + { + InvalidActions = 0x00000001, ///Include invalid actions + InvalidParameters = 0x00000002, ///Include invalid parameters + } + + //////////////////////////////////////////////////////////////// + // ModelEngine + // + //////////////////////////////////////////////////////////////// + /// + /// The centerpiece of the engine functionality, this class creates the + /// engine and user can call methods on this class to execute models. + /// + public class ModelEngine : ModelItem, IDisposable + { + //Data + protected ModelEngine _parent = null; + protected Models _models = null; + protected object _caller = null; + protected ModelEngineOptions _options = null; + + //tracks the actions selected by the state machine + //so it can spit it out in case of a exception or incase we want to get + //the trace of the problem and use it in a repro. + protected ModelActionInfos _actionstrace = new ModelActionInfos(); + protected int _actionscalled = 0; + + /// + /// Constructor which just takes the model class. + /// + /// + public ModelEngine(object caller) + : base(null, null) + { + //TODO: I'd like to remove this overload + _caller = caller; + _options = (ModelEngineOptions)ModelEngineOptions.Default.Clone(); + } + + /// + /// Constructor which takes multiple models. + /// + /// object of type Model + public ModelEngine(params Model[] models) + : this(null, models) + { + } + + /// + /// Constructor which takes multiple models. + /// + /// object of type Model + public ModelEngine(ModelEngine parent, params Model[] models) + : base(null, null) + { + _parent = parent; + _caller = parent; + + //Options + if(parent != null) + _options = (ModelEngineOptions)parent.Options.Clone(); //Copy from parent + else + _options = (ModelEngineOptions)ModelEngineOptions.Default.Clone(); //Defaults + + //Use passed in models, instead of finding them + //Saves perf, and the user having to first clear, then add + _models = new Models(this); + _models.Add(models); + } + + //Destructor + void IDisposable.Dispose() + { + foreach(Model model in this.Models) + model.Terminate(); + } + + //Accessors + /// + /// Property that controls whether InvalidActions be called or not. + /// + public virtual bool InvalidActions + { + get { return base.IsFlag((int)ModelEngineFlags.InvalidActions); } + set { SetFlag((int)ModelEngineFlags.InvalidActions, value); } + } + + /// + /// Property that controls whether invalid parameter values be generated or not. + /// + public virtual bool InvalidParameters + { + get { return base.IsFlag((int)ModelEngineFlags.InvalidParameters); } + set { SetFlag((int)ModelEngineFlags.InvalidParameters, value); } + } + + /// + /// List of ActionInfo items of the trace of actions that were executed by the state machine. + /// + public ModelActionInfos ActionsTrace + { + get{ return _actionstrace; } + } + + public ModelEngineOptions Options + { + get { return _options; } + set { _options = value; } + } + + private long DetermineTicks() + { + //Infinite + if ( _options.Timeout == System.Threading.Timeout.Infinite) + return long.MaxValue; + + //Update expected ticks + return TimeSpan.FromSeconds(_options.Timeout ).Ticks; + } + + public virtual ModelEngine Parent + { + get { return _parent; } + } + + /// + /// Returns all the models in the scope for the engine. + /// + public virtual Models Models + { + get + { + if(_models == null) + { + _models = new Models(this); + if (this.Options.LoadAssemblyModels) + _models.AddFromAssembly(_caller); + } + return _models; + } + } + + /// + /// This method forces the engine to reload the models. + /// Useful to reload the weights and other properties. + /// + public override void Reload() + { + //Clear, so they will be dynamically setup by reflection again + _models = null; + } + + //Methods + //FIXME: should this be public? It is difficult to extend the behavior of the engine without + //hosting the extension in the same assembly, unless we do this. + internal ModelActions GetPossibleActions() + { + //Default - exclude: invalid, callbefore, callafter, calllast, which are handled seperatly. + ModelActionFlags exclude = ModelActionFlags.CallBefore | ModelActionFlags.CallAfter | ModelActionFlags.CallLast; + if(!this.InvalidActions) + exclude |= (ModelActionFlags)ModelItemFlags.Invalid; + + //Loop through all models + ModelActions totalactions = new ModelActions(); + foreach(Model model in this.Models) + { + //Ignore disabled models + if(model.Weight == 0 || model.Disabled) + continue; + + totalactions.Add(model.Actions.FindFlag(exclude, false)); + } + + //Delegate + return this.GetPossibleActions(totalactions); + } + + protected ModelActions GetPossibleActions(ModelActions totalactions) + { + //Loop through all actions, specified + ModelActions possibleactions = new ModelActions(); + foreach(ModelAction action in totalactions) + { + //Ignore Disabled actions + if(action.Weight == 0 || action.Disabled) + continue; + + //Ignore CallLimit/CallOnce actions that have already been called (max times) + if(action.Accessed >= action.CallLimit) + continue; + + //Note: CallFirst and CallLast imply CallOnce + if(action.Accessed>0 && (action.CallFirst || action.CallLast)) + continue; + + //Ignore Actions, that return Models when were over the limit of those models + Type returntype = action.Method.ReturnType; + if(!returntype.IsPrimitive && typeof(Model).IsAssignableFrom(returntype) && returntype != typeof(Model)) + { + Models found = (Models)this.Models.FindType(returntype).FindFlag((int)ModelItemFlags.Disabled, false); + if(found.Count >= found.MaxInstances) + continue; + } + + //Determine if Requirements meet + ModelRequirement failedrequirement = null; + bool matched = MeetsRequirements(action.Requirements, out failedrequirement); + if(matched) + { + //Requirements met, action can be called + possibleactions.Add(action); + } + else + { + //Requirements not met, action can't be called + //Unless the user wants this to be called, when the requirements aren't met + if(this.InvalidActions && failedrequirement != null && failedrequirement.Throws) + { + //Note: We clone the action, and set the expected exception, just as if + //it were an invalid action from the start. We also set the weight of the + //invalid action, to the weight of the requirement, so it's not weighted + //the same as the (positive) version that's specified at the action level + ModelAction invalidaction = (ModelAction)action.Clone(); + invalidaction.Exception = failedrequirement.Exception; + invalidaction.ExceptionId = failedrequirement.ExceptionId; + invalidaction.Weight = failedrequirement.Weight; + possibleactions.Add(invalidaction); + } + } + } + + possibleactions.SortByWeightDesc(); //Sort all actions, across models + return possibleactions; + } + + protected bool MeetsRequirements(ModelRequirements requirements) + { + //Delegate + ModelRequirement failedrequirement = null; + return this.MeetsRequirements(requirements, out failedrequirement); + } + + protected bool MeetsRequirements(ModelRequirements requirements, out ModelRequirement failedrequirement) + { + if(requirements != null) + { + //Loop over all requirements + foreach(ModelRequirement requirement in requirements) + { + //Ignore disabled requirements + if(requirement.Weight == 0 || requirement.Disabled) + continue; + + //By default were just looking at this specific variable instance + //However if 'Global' is enabled, consider this variable on any models of this type + List currentValues = new List(); + if(requirement.Global) + { + //Note: We do this check everytime, since models could have been added dynamically + if(requirement.Variable != null) + { + foreach(Model model in this.Models.FindType(requirement.Variable.Model.GetType())) + { + ModelVariables variables = model.Variables.Find(requirement.Variable.Name); + foreach (ModelVariable variable in variables) + { + currentValues.Add(variable.CachedValue); + } + } + } + else + { + currentValues.Add(true); + } + } + else + { + if(requirement.Variable == null) + throw new ModelException(this, "Variable is null?"); + currentValues.Add(requirement.Variable.CachedValue); + } + + //See if this requirement is met + bool matched = false; + foreach (Object value in currentValues) + { + if(requirement.Evaluate(value)) + { + matched = true; + break; + } + } + + //Stop on first mis-match, (ie: at least one requirement didn't match) + if(!matched) + { + //First failed requirement + //Note: This has to be order based, since production checks conditions in-order + //and will fail (and throw) according to the first one not meet + failedrequirement = requirement; + return false; + } + } + } + + failedrequirement = null; + return true; + } + + protected ModelAction DetermineNextAction() + { + return this.GetPossibleActions().Choose(this); + } + + protected ModelParameters DetermineParameters(ModelAction action) + { + try + { + ModelParameters allparameters = action.Parameters; + ModelParameters choosen = new ModelParameters(); + + //Loop through the method parameters + foreach(ParameterInfo info in action.Method.GetParameters()) + { + //Find the all parameters assoicated with this param + ModelParameters parameters = allparameters.Find(info.Name); + //Exclude invalid parameters (if not requested) + if(!this.InvalidParameters) + parameters = (ModelParameters)parameters.FindFlag((int)ModelItemFlags.Throws, false); + if(parameters.Count <= 0) + throw new ModelException(this, "Unable to find a ModelParameter for method parameter: '" + info.Name + "'"); + + //Choose one of the parameters, based upon weight + ModelParameter parameter = parameters.Choose(this); + parameter.Accessed++; + + //Note: We cloning the param, since were choosing only one of the values to use this time. + parameter = (ModelParameter)parameter.Clone(); + + //Choose (or generate) one of the values + ModelValue value = DetermineParameterValue(parameter); + value.Accessed++; + + //Add it to the array + parameter.Value = value; + choosen.Add(parameter); + } + + return choosen; + } + catch(Exception e) + { + //Make this easier to debug + throw new ModelException(this, "DetermineParameters", e); + } + } + + protected ModelValue DetermineParameterValue(ModelParameter parameter) + { + ModelValues values = new ModelValues(); + values.Add(parameter.Values); + + //Parameter values, can be specified in numerous ways: + // 1. Value list - simply choose one of them + // 2. Expression - generate value that meets the criteria (ie: <, >, !=, etc) + // 3. Variable - simply obtain the value by calling a method/field + + //#3. Variable - simply obtain the value by calling a method/field + if(parameter.Variable != null) + { + object current = parameter.Variable.CachedValue; + if(current is IEnumerable && !typeof(IEnumerable).IsAssignableFrom(parameter.Type)) + { + foreach(object v in (IEnumerable)current) + values.Add(new ModelValue(v)); + } + else + { + values.Add(new ModelValue(current)); + } + } + + //First ensure we have a set of values/requirements to choose from + if(values.Count <= 0) + throw new ModelException(parameter, "No values specified to choose from"); + + //Note: Since we allow the operator on the individual values, this is a little more complex. + //Note: We allow multiple operators, not just one, (ie: x > 5, < 10, and != 7). + //This gives you great power and flexibility in expressions, but means we have to work a + //little hard in determing a value that meets the requirements + + //#1. Value list - simply choose one of them + //Note: Bitmask is already exploded into combinations + ModelValues equalvalues = values.FindOperator(ModelValueOperator.Equal); + + //#2. Expression - generate value that meets the criteria (ie: <, >, !=, etc) + //Note: Since we allow the operator on the individual values, this is a little more complex. + //Note: We allow multiple operators, not just one, (ie: x > 5, < 10, and != 7). + //This gives you great power and flexibility in expressions, but means we have to work a + //little hard in determing a value that meets the requirements. + int min = Int32.MinValue; + int max = Int32.MaxValue; + + //Adjust our parameter, simplier to loop over all of them + foreach(ModelValue value in values.FindOperator(ModelValueOperator.Equal, false).FindOperator(ModelValueOperator.NotEqual, false)) + { + //To keep this simple, we just support integers (for now). + if(!(value.Value is int || value.Value is Nullable)) + throw new ModelException(parameter, "Generated value range must be specified in terms of integers, not '" + value.Type + "'"); + + //Simplify our life + int v = (int)value.Value; + + //Adjust Max (if there is one) + switch(value.Operator) + { + case ModelValueOperator.LessThanOrEqual: + if(v < max) + max = v; + break; + + case ModelValueOperator.GreaterThanOrEqual: + if(v > min) + min = v; + break; + + case ModelValueOperator.LessThan: + if(v-1 < max && v > Int32.MinValue/*prevent underflow*/) + max = v-1; + break; + + case ModelValueOperator.GreaterThan: + if(v+1 > min && v < Int32.MaxValue/*prevent overflow*/) + min = v+1; + break; + }; + } + + //Choose a new value, within the specified range + //Note: We retry in case it equals one of the existing invalid values (ie: !=) + while(true) + { + ModelValue choice = null; + if(equalvalues.Count > 0) + { + //Simple: Choose one of the specified values + choice = new ModelValue(equalvalues.Choose(this).Value); + } + else + { + //Otherwise: Choose a value within in the range + //Note: Random.Next = min <= x < max (so we have to add one) + int index = _options.Random.Next(min, max < Int32.MaxValue ? max + 1 : max); //Prevent overflow + choice = new ModelValue(index); + } + + //As soon as we find a value, within the range, (and not in the invalid list), were done + bool valid = true; + foreach(ModelValue invalid in values.FindOperator(ModelValueOperator.NotEqual)) + { + if(invalid.Evaluate(choice.Value, ModelValueOperator.Equal)) + valid = false; + } + + if(valid) + return choice; + } + } + + /// + /// This method does the following. + /// It starts the engine and the stops it if + /// 1. Time out has occured OR + /// 2. Exception has been thrown OR + /// 3. Auto Restart is false and there are no more actions available to be called OR + /// 4. Variable tracking was on and we have covered all values OR + /// 5. State tracking was on and we have covered all the states OR + /// 6. Maximum number of actions that we have specified have been invoked + /// 7. and there + /// + public void Run() + { + //No requirements, which means run until termination + this.RunUntil((ModelRequirements)null); + } + + /// + /// RunUntil(custom delegate) + /// Powerful, where you own the code in the handler, and determine exactly when to stop execution + /// ie: RunUntil(delegate() { return _x > 5; } //Where _x > 5 is any c# code expression + /// + /// + /// + /// + public bool RunUntil(ModelFunction func) + { + //Build up a ModelRequirement around the delegate + ModelRequirements criteria = new ModelRequirements( + new ModelExpression(func, new ModelValue(true)) + ); + + return this.RunUntil(criteria); + } + + /// + /// Internal method to RunUntil( requirements ) are met + /// The public version is the delegate method above, much simplier and more powerful for users. + /// + /// + /// + /// + protected bool RunUntil(ModelRequirements requirements) + { + //Run until the following requirements are met (ie: variable = values) + //Or until the time is elapsed. + long startingticks = DateTime.Now.Ticks; + long remainingticks = DetermineTicks(); + if(this.Options.Tracing) + ModelTrace.WriteLine("Model Seed: " + this.Options.Seed); + + bool meetsrequirements = Execute(requirements, startingticks, remainingticks); + + //CallLast + //Because requirements not met and model needs to shutdown correctly. + if (!meetsrequirements) + { + //CallLast, actions + foreach (Model model in this.Models) + { + if (model.Actions.Accessed > 0) //Only if 'first' was called + { + foreach (ModelAction last in this.GetPossibleActions(model.Actions.FindFlag(ModelActionFlags.CallLast))) + this.ExecuteAction(last); + } + } + } + + return meetsrequirements; + } + + protected virtual bool Execute(ModelRequirements requirements, long startingticks, long remainingticks) + { + bool meetsrequirements = false; + _actionscalled = 0; + + //Continue until no more actions to execute + ModelAction action = this.DetermineNextAction(); + while (action != null) + { + //Model + Model model = action.Model; + + //Init + if (model.Actions.Accessed == 0) + model.Init(); + + //CallFirst, actions + //TODO: What happens if this now meets the requirements? + foreach (ModelAction first in this.GetPossibleActions(model.Actions.FindFlag(ModelActionFlags.CallFirst))) + { + if (first != action) + this.ExecuteAction(first); + } + + //Execute (choose the parameters as well) + //Note: CallFirst might have disabled this model, so we check first + if (model.Enabled) + this.ExecuteAction(action); + + + //Determine if all the requirements were met + if (requirements != null && requirements.Count > 0 + && MeetsRequirements(requirements)) + { + meetsrequirements = true; + break; + } + + //Check action count + if (_actionscalled >= _options.MaxActions) + { + if(this.Options.Tracing) + ModelTrace.WriteLine("MaxActions: '" + _options.MaxActions + "' was reached."); + break; + } + + //Check Timeout + long currentticks = DateTime.Now.Ticks; + if (currentticks - startingticks > remainingticks) + { + if(this.Options.Tracing) + ModelTrace.WriteLine("Timeout: '" + _options.Timeout + "' has elapsed."); + break; + } + + //Determine the next action + action = this.DetermineNextAction(); + } + return meetsrequirements; + } + + /// + /// This takes a set of ModelAction objects and runs them in the order specified. + /// Useful for scenario playback. + /// + /// + public void RunScenario( params ModelAction[] actions ) + { + //Delegate + this.RunScenario(new ModelActions( actions )); + } + + /// + /// Another overload for RunActions which runs the actions specified. + /// + /// + public void RunScenario(ModelActions actions) + { + //Delegate + foreach(ModelAction action in actions ) + this.ExecuteAction(action); + } + + /// + /// If you are also interested in the actual parameters and not just actions, + /// you can use RunScenario which takes the ModelActionInfo object which also + /// contains the parameter values. + /// + /// + public void RunScenario(params ModelActionInfo[] actioninfos) + { + //Delegate + foreach(ModelActionInfo actioninfo in actioninfos ) + this.ExecuteActionInfo(actioninfo); + } + + private void ExecuteAction(ModelAction action) + { + //CallBefore, actions + //TODO: What happens if this now meets the requirements? + foreach(ModelAction before in this.GetPossibleActions(action.Model.Actions.FindFlag(ModelActionFlags.CallBefore))) + this.ExecuteActionInfo(new ModelActionInfo(before, this.DetermineParameters(before), null)); + + //Execute Action + this.ExecuteActionInfo(new ModelActionInfo(action, this.DetermineParameters(action), null)); + + //CallAfter, actions + //TODO: What happens if this now meets the requirements? + foreach(ModelAction after in this.GetPossibleActions(action.Model.Actions.FindFlag(ModelActionFlags.CallAfter))) + this.ExecuteActionInfo(new ModelActionInfo(after, this.DetermineParameters(after), null)); + } + + private void ExecuteActionInfo(ModelActionInfo actioninfo) + { + ModelAction action = actioninfo.Action; + ModelParameters parameters = actioninfo.Parameters; + Model model = action.Model; + + //Pre-Execute, events + //Note: If CallBefore returns false, we simply don't execute the method + if(model.OnCallBefore(action, parameters)) + { + //Adding the selected action (and its param values) to the trace. + if(this.Options.Tracing) + _actionstrace.Add( actioninfo ); + + //Execute the method (delegate) + actioninfo.RetVal = action.Execute(parameters); + _actionscalled++; + + //Add the returned model to the system + Model output = actioninfo.RetVal as Model; + if(output != null) + { + //If it doesn't already exist, and the model type is part of the set + if(this.Models.FindInstance(output) == null) + { + actioninfo.Created = true; + + //Add returned models, if requested + if(this.Options.AddReturnedModels) + { + //Note: We always obey the maxinstance count + Models found = (Models)this.Models.FindType(output.GetType()).FindFlag((int)ModelItemFlags.Disabled, false); + if(found.Count < output.MaxInstances) + { + output.Enabled = true; //Enabled by default + if (output.ParentModel == null) //Hook up the creator, if not already specified + output.ParentModel = action.Model; + this.Models.Add(output); + } + } + } + } + + //Trace + if(this.Options.Tracing) + ModelTrace.WriteLine(ModelTrace.FormatMethod(actioninfo)); + + //Post-Execute, events + model.OnCallAfter(action, parameters, actioninfo.RetVal); + } + + //Reset cached variables + foreach(ModelVariable variable in this.Models.Variables) + variable.CachedValue = null; + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelEngineOptions.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelEngineOptions.cs new file mode 100644 index 0000000..8d62a5b --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelEngineOptions.cs @@ -0,0 +1,229 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Security.Permissions; +using System.Security; + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // WeightScheme + // + //////////////////////////////////////////////////////////////// + /// + /// Global weighing scheme for the engine. Depending on this scheme + /// the engine decides the probabilities. + /// + public enum WeightScheme + { + Custom, ///Default weighting (specified in the model) + Equal, ///Equal weighing + AdaptiveEqual, ///Adjusted to weight uncalled item higher + Geometric ///Each item has twice the chance of the next one. + }; + + //////////////////////////////////////////////////////////////// + // ModelEngine + // + //////////////////////////////////////////////////////////////// + /// + /// This class stores all the options that are set on the Engine. + /// + public class ModelEngineOptions : ICloneable + { + //Data + int _seed = unchecked((int)DateTime.Now.Ticks); + Random _rand = null; + WeightScheme _weightscheme = WeightScheme.Custom; //Default - as defined in the model + long _timeout = 30; //Default, seconds + long _maxactions = long.MaxValue; //Default, no limit + bool _addreturnedmodels = true; + bool _loadAssemblyModels = true; + bool _tracing = true; + static ModelEngineOptions _default = null; + + //Constructor + /// Default constructor + public ModelEngineOptions() + { + _rand = new Random(_seed); + } + + //Accessors + /// + /// Weight Scheme to be used by the Engine. See WeightScheme Enum for various settings. + /// + public virtual WeightScheme WeightScheme + { + //Expose our random generator, in case tests need it, (ie: one seed for complete repro) + get { return _weightscheme; } + set { _weightscheme = value; } + } + + /// + /// Sets/Gets a boolean that indicates whether or not assembly models should be loaded + /// + public virtual bool LoadAssemblyModels + { + get { return _loadAssemblyModels; } + set { _loadAssemblyModels = value; } + } + + /// + /// Sets/Gets the unit value of the duration. Default value = 30 seconds + /// + public long Timeout + { + get{ return _timeout; } + set{ _timeout = value; } + } + + /// + /// Enable/Disable action tracing. Default value = true + /// + public bool Tracing + { + get{ return _tracing; } + set{ _tracing = value; } + } + + /// + /// Sets/Gets the maximum actions that must be executed by the state machine. + /// After the number of actions set is hit, the machine stops and returns to user. + /// Along with Seed, this is a powerful way to reproduce errors and add regressions. + /// + public long MaxActions + { + get{ return _maxactions; } + set{ _maxactions = value; } + } + + /// + /// This property allows you to automatically add returned models from actions + /// + public bool AddReturnedModels + { + get { return _addreturnedmodels; } + set { _addreturnedmodels = value; } + } + + public virtual Random Random + { + //Expose our random generator, in case tests need it, (ie: one seed for complete repro) + get { return _rand; } + set { _rand = value; } + } + + /// + /// The seed that is used to set/get on the random sequence generator. + /// + public virtual int Seed + { + get { return _seed; } + set + { + _seed = value; + _rand = new Random(value); + } + } + + /// + /// Default option settings, loaded once from the commandline + /// + public static ModelEngineOptions Default + { + //Expose our random generator, in case tests need it, (ie: one seed for complete repro) + get + { + if(_default == null) + { + _default = new ModelEngineOptions(); + _default.Load(); + } + return _default; + } + } + + /// + /// Default option settings, loaded once from the commandline + /// + public virtual Object Clone() + { + return this.MemberwiseClone(); + } + + [EnvironmentPermission(SecurityAction.Assert, Unrestricted = true)] + [SecuritySafeCritical] + public void Load() + { +#if (!(SmartPhone || WindowsCE || PocketPC)) //GetCommandLineArgs is not implemented in .Net CF + string[] args = Environment.GetCommandLineArgs(); + int count = 0; + while (count < args.Length) + { + switch (args[count].ToUpper(System.Globalization.CultureInfo.InvariantCulture)) + { + case "/ADDRETURNEDMODELS+": + this.AddReturnedModels = true; + break; + case "/ADDRETURNEDMODELS-": + this.AddReturnedModels = false; + break; + case "/SEED": + if (count + 1 < args.Length) + { + this.Seed = Int32.Parse(args[count + 1]); + count++; + } + else + throw new ModelException(null, "Command line argument Seed needs a parameter"); + break; + case "/MAXACTIONS": + if (count + 1 < args.Length) + { + this.MaxActions = Int32.Parse(args[count + 1]); + count++; + } + else + throw new ModelException(null, "Command line argument MaxAction needs a parameter"); + break; + case "/LOADASSEMBLYMODELS": + if (count + 1 < args.Length) + { + this.LoadAssemblyModels = bool.Parse(args[count + 1]); + count++; + } + else + throw new ModelException(null, "Command line argument LoadAssemblyModels needs a parameter"); + break; + case "/TIMEOUT": + if (count + 1 < args.Length) + { + this.Timeout = Int32.Parse(args[count + 1]); + count++; + } + else + throw new ModelException(null, "Command line argument Timeout needs a parameter"); + break; + case "/WEIGHTING": + if (count + 1 < args.Length) + { + this.WeightScheme = (WeightScheme)Enum.Parse(typeof(WeightScheme), args[count + 1], true /* Ignore case */ ); + count++; + } + else + throw new ModelException(null, "Command line argument Weighting needs a parameter"); + break; + default: + break; + } + count++; + } +#endif //(!(SmartPhone || WindowsCE || PocketPC)) + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelException.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelException.cs new file mode 100644 index 0000000..9793f70 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelException.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //Reflection +using System.Collections.Generic; //List +using System.Threading; //Timeout + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelException + // + //////////////////////////////////////////////////////////////// + /// + /// Any exception thrown from KoKoMo code. + /// + public class ModelException : Exception + { + //Constructors + public ModelException(ModelItem item, string message) + : this(item, message, null, null) + { + //Delegate + } + + public ModelException(ModelItem item, string message, Exception inner) + : this(item, message, inner, null) + { + //Delegate + } + + public ModelException(ModelEngine engine, string message) + : this(null, "Engine: " + message, null, engine.ActionsTrace) + { + //Delegate + } + + public ModelException(ModelEngine engine, string message, Exception inner) + : this(null, "Engine: " + message, inner, engine.ActionsTrace) + { + //Delegate + } + + public ModelException(ModelItem source, string message, Exception inner, List actions) + : base(source != null ? (source.FullName + ": " + message) : message, inner) + { + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelExpression.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelExpression.cs new file mode 100644 index 0000000..2da6fe8 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelExpression.cs @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Text; +using System.Reflection; + +namespace Microsoft.Test.KoKoMo +{ + public delegate object ModelFunction(); + //////////////////////////////////////////////////////////////// + // ModelExpresion + // + //////////////////////////////////////////////////////////////// + public class ModelExpression : ModelRequirement + { + //Data + ModelFunction _func = null; + + //Constructor + public ModelExpression(ModelFunction func, ModelValue value) + : base(null, value) + { + _func = func; + } + + //Overrides + public override bool Evaluate(Object expected) + { + return base.Evaluate(_func()); + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelItem.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelItem.cs new file mode 100644 index 0000000..857c410 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelItem.cs @@ -0,0 +1,538 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; //Hashtable +using System.Collections.Generic; //List + + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelItemFlags + // + //////////////////////////////////////////////////////////////// + /// + /// This Enum stores the values to track if a particular property on the custom attribute was set. + /// It is used for boolean flags such as Invalid, Disabled, Tracked, etc. + /// + public enum ModelItemFlags + { + //High range, as to not conflict with inherited (ModelRangeFlags, or ModelActionFlags, etc) + Invalid = 0x01000000, + Throws = 0x02000000, + Disabled = 0x10000000, + Tracked = 0x20000000 + }; + + //////////////////////////////////////////////////////////////// + // ModelItem (attribute) + // + //////////////////////////////////////////////////////////////// + /// + ///This class is the base class for all custom attributes in KoKoMo + /// + public abstract class ModelItemAttribute : Attribute + { + //Data + protected int _weight = 1; //Weighting + protected String _name = null; //Name + protected String _category = null; //Category + protected Object[] _labels = null; //Labels + protected int _flags = (int)ModelItemFlags.Tracked; + Type _exception; //If the action ALWAYS throws an exception + string _exceptionid; //Advanced verification + + /// + ///Returns the value of the Name property on the model item. + /// + public virtual String Name + { + get { return _name; } + set { _name = value; } + } + + /// + ///Returns the value of the Desc property on the model item. + /// + public virtual String Category + { + get { return _category; } + set { _category = value; } + } + + /// + ///Returns the value of the Weight property on the model item. + /// + public virtual int Weight + { + get { return _weight; } + set { _weight = value; } + } + + /// + ///Returns the value of the Enabled property on the model item. + /// + public virtual bool Enabled + { + get { return !this.Disabled; } + set { this.Disabled = !value; } + } + + /// + ///Returns the value of the Disabled property on the model item. + /// + public virtual bool Disabled + { + get { return IsFlag((int)ModelItemFlags.Disabled); } + set { SetFlag((int)ModelItemFlags.Disabled, value); } + } + + /// + ///Returns the value of the Tracked property on the model item. + ///Default is true. + /// + public virtual bool Tracked + { + get { return IsFlag((int)ModelItemFlags.Tracked); } + set { SetFlag((int)ModelItemFlags.Tracked, value); } + } + + /// + ///Returns the value of the Invalid property on the model item. + /// + public virtual bool Invalid + { + get { return IsFlag((int)ModelItemFlags.Invalid); } + set { SetFlag((int)ModelItemFlags.Invalid, value); } + } + + /// + ///This property returns the exception type specified on the model item. + /// + public virtual Type Exception + { + get { return _exception; } + set + { + _exception = value; + SetFlag((int)ModelItemFlags.Throws, true); + } + } + + /// + ///This property returns the Exception Id specified on the model item. + /// + public virtual string ExceptionId + { + get { return _exceptionid; } + set + { + _exceptionid = value; + SetFlag((int)ModelItemFlags.Throws, true); + } + } + + /// + ///Returns if the model item has a Throws= property set. + /// + public virtual bool Throws + { + get { return IsFlag((int)ModelItemFlags.Throws); } + } + + /// + ///This property returns all the properties set on the custom attribute. + /// + public virtual int Flags + { + get { return _flags; } + set { _flags = value; } + } + + /// + /// This method returns true if the required property is set on the custom attribute. + /// + /// Flag to set specified from the enum + /// true if Flag is set to true + public virtual bool IsFlag(int flag) + { + return (_flags & flag) == flag; + } + + /// + /// This method Sets the flags to track all the properties set on the custom attribute of the item. + /// + /// Flag to set specified from the enum + /// true/false for the flag + public virtual void SetFlag(int flag, bool value) + { + if(value) + _flags |= flag; + else + _flags &= ~flag; + } + + + public virtual object Label + { + get + { + if(_labels != null ) + return _labels[0]; + return null; + } + set + { + if(_labels == null) + _labels = new Object[1]; + _labels[0] = value; + } + } + + public virtual object[] Labels + { + get { return _labels; } + set { _labels = value; } + } + } + + //////////////////////////////////////////////////////////////// + // ModelItem + // + //////////////////////////////////////////////////////////////// + + /// + /// The base class for any item in the KoKoMo framework. + /// This is the base class for any Action, Parameter, Variable, Requirement, Range etc. + /// + public abstract class ModelItem + { + //Data + /// + /// Weight of the item. + /// + protected int _weight = 1; //Default + protected String _name = null; + protected String _fullname = null; + protected String _category = null; + private Model _model = null; + protected object[] _labels = null; + + /// + /// Flags set on the item. + /// + protected int _flags = (int)ModelItemFlags.Tracked; + internal int _accessed = 0; + + /// + /// ID of the item. + /// + private int _id = 0; + static Hashtable _idhash = new Hashtable(100); + + //Exception + Type _exception = null; //Invalid Value - throws exception + string _exceptionid = null; //Invalid Value - throws exception + + /// + /// Constructor to create an item. + /// + /// Custom attribute specified on the item. + public ModelItem(Model model, ModelItemAttribute attr) + { + _model = model; + this.SetAttributeValues(attr); + } + + //Accessors + /// + /// Property that returns the name of this item. + /// + public virtual string Name + { + get + { + if(_name != null) + return _name; + return this.GetType().Name; + } + set { _name = value; } + } + + //Accessors + /// + /// Property that returns the full name of this item. + /// + public virtual string FullName + { + get + { + if(_fullname == null && _model != null) + { + _fullname = _model.Name + "." + this.Name; + return _fullname; + } + + //Otherwise + return this.Name; + } + } + + public virtual Model Model + { + get { return _model; } + } + + //Accessors + /// + /// Property that returns the category of this item. + /// + public virtual string Category + { + get { return _category; } + set { _category = value; } + } + + /// + /// Gets the label declared on the item. + /// + public virtual object Label + { + get + { + if(_labels != null && _labels.Length > 0 ) + return _labels[0]; + return null; + } + } + /// + /// Gets the labels declared on the item. + /// + public virtual object[] Labels + { + get { return _labels; } + } + + /// + /// Returns the ID of the item. + /// + public virtual int Id + { + get + { + if(_id == 0) + _id = NextId(this.GetType()); + return _id; + } + set + { + _id = value; + } + } + + public static int NextId(Type type) + { + //Retrieve it from the hash + object id = _idhash[type]; + if(id == null) + id = 1; + else + id = (int)(id) + 1; + + //Add it to the hash + _idhash[type] = id; + return (int)id; + } + + /// + /// Returns the Weight of the item. + /// + public virtual int Weight + { + get { return _weight; } + set { _weight = value; } + } + + /// + /// Returns if the item is enabled or not. + /// + public virtual bool Enabled + { + get { return !this.Disabled; } + set { this.Disabled = !value; } + } + + /// + /// Returns if the item is disabled or not. + /// + public virtual bool Disabled + { + get { return IsFlag((int)ModelItemFlags.Disabled); } + set { SetFlag((int)ModelItemFlags.Disabled, value); } + } + + /// + /// Returns if the item is tracked or not. + /// + public virtual bool Tracked + { + get { return IsFlag((int)ModelItemFlags.Tracked); } + set { SetFlag((int)ModelItemFlags.Tracked, value); } + } + + /// + /// Returns if the item is invalid or not. + /// + public virtual bool Invalid + { + get { return IsFlag((int)ModelItemFlags.Invalid); } + set { SetFlag((int)ModelItemFlags.Invalid, value); } + } + + /// + /// Returns the type of exception thrown by the item. + /// + public virtual Type Exception + { + get { return _exception; } + set + { + _exception = value; + SetFlag((int)ModelItemFlags.Throws, (value == null && String.IsNullOrEmpty(ExceptionId))? false : true); + } + } + + /// + /// Returns the ExceptionID for this item. + /// + public virtual string ExceptionId + { + get { return _exceptionid; } + set + { + _exceptionid = value; + SetFlag((int)ModelItemFlags.Throws, (String.IsNullOrEmpty(value) && Exception==null) ? false : true); + } + } + + /// + /// Returns if the item is throws an exception or not. + /// + public virtual bool Throws + { + get { return IsFlag((int)ModelItemFlags.Throws); } + } + + /// + /// Virtual method to set the item state depending on the custom attribute definition. + /// + public virtual void SetAttributeValues(ModelItemAttribute attr) + { + if(attr != null) + { + _weight = attr.Weight; + _flags = attr.Flags; + _exception = attr.Exception; + _exceptionid= attr.ExceptionId; + _name = attr.Name; + _category = attr.Category; + _labels = attr.Labels; + } + } + + /// + /// Returns the current set of flags on this Item. + /// + public virtual int Flags + { + get { return _flags; } + set { _flags = value; } + } + + /// + /// This method returns true if the required property is set on the custom attribute. + /// + /// Flag to set specified from the enum + /// true if Flag is set to true + public virtual bool IsFlag(int flag) + { + return (_flags & flag) == flag; + } + + /// + /// This method returns true if the required property is set on the custom attribute. + /// + /// Flag to set specified from the enum + /// false if Flag is set to true + public virtual bool IsFlagExcept(int flag) + { + return (_flags & flag) == 0; + } + + /// + /// This method Sets the flags to track all the properties set on the custom attribute of the item. + /// + /// Flag to set specified from the enum + /// true/false for the flag + public virtual void SetFlag(int flag, bool value) + { + if(value) + _flags |= flag; + else + _flags &= ~flag; + } + + /// + /// Returns the number of times this item was accessed. + /// + public virtual int Accessed + { + get { return _accessed; } + set { _accessed = value; } + } + + /// + /// Method to reload this item and reset its values. + /// + public abstract void Reload(); + + /// + /// Overriden ToString() + /// + /// Item.Name + public override string ToString() + { + return this.Name; + } + + /// + /// Checks if a specified label is defined on the item. + /// + /// Label to check + /// True if label is defined, false otherwise + public virtual int CompareLabel( object label ) + { + if ( this._labels != null ) + { + foreach( object lbl in _labels ) + { + IComparable icomp = lbl as IComparable; + if ( icomp != null ) + { + return icomp.CompareTo(label); + } + else + { + if ( lbl.Equals(label) ) + return 0; + } + } + } + return 1; + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelItems.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelItems.cs new file mode 100644 index 0000000..982a69c --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelItems.cs @@ -0,0 +1,672 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //Assembly +using System.Collections; //IComparer +using System.Collections.Generic; //List + + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelItems + // + //////////////////////////////////////////////////////////////// + /// + /// This class represents a collection of ModelItem objects. + /// This class has methods to manage the collection such as indexers, finders, add/remove, etc. + /// + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + /// + public abstract class ModelItems : IEnumerable where T : ModelItem + { + //Data + protected IComparer _weightcomparer = null; + protected List _list = new List(); //List doesn't allow overriding Add, so we have to contain this + protected List> _onadded = null; + protected List> _onremoved = null; + + //Constructor + /// + /// Default constructor does nothing. + /// + public ModelItems() + { + } + + /// + /// Constructor that adds the items passed in. + /// + /// Comma seperated list of items to add + public ModelItems(params T[] items) + { + this.Add(items); + } + + /// + /// Count of items in the collection. + /// + public virtual int Count + { + get { return _list.Count; } + } + + /// + /// Position Indexer to return the object at position "index" + /// + public virtual T this[int index] + { + get + { + //Find by index + //Fail if not found, although make it easier to debug + if(index < 0 || index > _list.Count) + throw new IndexOutOfRangeException(this.Name + '[' + index + "] is not found"); + return _list[index]; + } + } + + /// + /// Named indexer. + /// + public virtual T this[string name] + { + //Find first, fail if not found, although make it easier to debug + get + { + ModelItems found = this.Find(name); + if(found == null || found.Count <= 0) + throw new IndexOutOfRangeException(this.Name + "['" + name + "'] is not found"); + return found.First; + } + } + + public virtual T First + { + get + { + if(_list.Count > 0) + return _list[0]; + return null; + } + } + + public virtual bool Throws + { + get + { + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + //If any of the items are invalid, break and return true + if(item.Throws) + return true; + } + return false; //Otherwise + } + } + + //Events + public event ModelEventHandler Added + { + //Note: We need access to this list, so we can't use the default implementation + add + { + if(_onadded == null) + _onadded = new List>(); + _onadded.Add(value); + } + remove + { + if (_onadded != null) + _onadded.Remove(value); + } + } + + public event ModelEventHandler Removed + { + //Note: We need access to this list, so we can't use the default implementation + add + { + if(_onremoved == null) + _onremoved = new List>(); + _onremoved.Add(value); + } + remove + { + if(_onremoved != null) + _onremoved.Remove(value); + } + } + + public virtual void OnAdded(T item) + { + if (_onadded != null) + { + foreach (ModelEventHandler handler in _onadded) + handler(this, item); + } + } + + public virtual void OnRemoved(T item) + { + if (_onremoved != null) + { + foreach (ModelEventHandler handler in _onremoved) + handler(this, item); + } + } + + /// + /// Adds an item. + /// + /// + /// + public virtual T Add(T item) + { + _list.Add(item); + + //Events + OnAdded(item); + return item; + } + + /// + /// Adds another ModelItems collection to this collection. + /// + /// + /// + public virtual ModelItems Add(ModelItems items) + { + foreach(T item in items) + this.Add(item); + return items; + } + + /// + /// Adds an array of ModelItems. + /// + /// + /// + public virtual T[] Add(params T[] items) + { + foreach(T item in items) + this.Add(item); + return items; + } + + /// + /// Removes a model item from this collection. + /// + /// + /// + public virtual T Remove(T item) + { + _list.Remove(item); + + //Events + OnRemoved(item); + return item; + } + + /// + /// Removes a collection of model items from this collection. + /// + /// + /// + public virtual ModelItems Remove(ModelItems items) + { + foreach(T item in items) + this.Remove(item); + return items; + } + + public virtual T[] ToArray() + { + return _list.ToArray(); + } + + //Accessors + /// + /// Returns the Type.ToString for this item type. + /// + public virtual string Name + { + get { return this.GetType().Name; } + } + + /// + /// Returns the weight of this collection which is the sum of all weights of all items. + /// Setter sets the specified weight for all the items in the collection like a bulk update. + /// + public virtual int Weight + { + get + { + //Return the weight of all items + int weight = 0; + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + weight += item.Weight; + return weight; + } + + set + { + //Update the weight of all items + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + item.Weight = value; + } + } + + /// + /// Returns true if any of items is enabled. + /// Sets diabled = false for all items in the collection. + /// + public virtual bool Enabled + { + get + { + //Return if any the items are enabled + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + if(item.Enabled) + return true; + } + return false; + } + + set + { + //Update the weight of all items + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + item.Enabled = value; + } + } + + /// + /// Returns the total accessed count for all items in the collection. + /// + public virtual int Accessed + { + get + { + //Return if any the items are accessed + int accessed = 0; + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + accessed += item.Accessed; + return accessed; + } + } + + /// + /// Returns true if for each that is tracked the accessed count is more than 0. + /// + public virtual bool AllCovered + { + get + { + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + if (item.Enabled && item.Tracked && item.Accessed == 0) + return false; + } + return true; + } + } + + /// + /// Sorts the internal array by weights. + /// + public virtual void SortByWeightDesc() + { + if(_weightcomparer == null) + _weightcomparer = new ModelItemWeightComparer(true/*desc*/); + _list.Sort(_weightcomparer); + } + + /// + /// Clears the internal array. + /// + public virtual void Clear() + { + _list.Clear(); + } + + /// + /// Returns the index of a particular item in the collection. + /// + /// + /// + public virtual int IndexOf(T item) + { + return _list.IndexOf(item); + } + + /// + /// Returns an enumerator over the internal array. + /// + /// + public virtual IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } + + /// + /// Returns an enumerator over the internal array. + /// + /// + IEnumerator IEnumerable.GetEnumerator() + { + return _list.GetEnumerator(); + } + + /// + /// Overridden Clone method to copy this item. + /// + /// + public virtual object Clone() + { + ModelItems clone = (ModelItems)this.MemberwiseClone(); + clone._list = new List(); + clone.Add(this); + return clone; + } + + /// + /// Returns a collection of items found for the given names. + /// + /// Names of items. (usually type.ToString) + /// ModelItems collection + public virtual ModelItems Find(params string[] names) + { + //Construct the typed collection (ie: (Models)Find). + //The simplest, without dealing with constructors, is to clone and clear + ModelItems items = (ModelItems)this.Clone(); + items.Clear(); + + //Note: To perserve the order, we need to loop over the names in the outer loop + //instead of the inner, which makes the exclusion (ie: include=false) more difficult + foreach (string name in names) + { + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + //Compare + bool matched = String.Compare(item.Name, name, true/*ignore case*/) == 0; + if (matched) + items.Add(item); + } + } + return items; + } + + /// + /// Returns a collection of items found except those specified. + /// + /// Names to ignore while returning. + /// ModelItems collection + public virtual ModelItems FindExcept(params string[] names) + { + //Construct the typed collection (ie: (Models)Find). + //The simplest, without dealing with constructors, is to clone and clear + ModelItems items = (ModelItems)this.Clone(); + items.Clear(); + + //Find all items that don't match ANY of the names + //Note: This doesn't need to perserve the order, so matching this a seperate function (simplier) + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + bool matched = false; + foreach (string name in names) + { + //Delegate + if (String.Compare(item.Name, name, true/*ignore case*/) == 0) + { + matched = true; + break; + } + } + + if (!matched) + items.Add(item); + } + return items; + } + + /// + /// Returns all the items in the collection which match the flag specified. + /// + /// Flag that is present on the item such as disabled, tracked, throws, etc. + /// Returns a ModelItems collection + public virtual ModelItems FindFlag(int flag) + { + //Delegate + return this.FindFlag(flag, true); + } + + /// + /// Returns all the items in the collection which match the flag specified. + /// + /// boolean to indicate if names are to be ignored or not, false for ignore. + /// Flag that is present on the item such as disabled, tracked, throws, etc. + /// Returns a ModelItems collection + /// + public virtual ModelItems FindFlag(int flag, bool include) + { + //Construct the typed collection (ie: (Models)Find). + //The simplest, without dealing with constructors, is to clone and clear + ModelItems items = (ModelItems)this.Clone(); + items.Clear(); + + //Note: there could be more than one (even by the same name) + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + if (include) + { + if (item.IsFlag(flag)) + items.Add(item); + } + else + { + if (item.IsFlagExcept(flag)) + items.Add(item); + } + } + return items; + } + + /// + /// Returns a ModelItem in the collection for the given ModelItem instance + /// + /// ModelItem instance + /// Returns a ModelItem instance or null if not found + public virtual T FindInstance(T instance) + { + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + if ((object)item == (object)instance) + return item; + } + return null; + } + + /// + /// Returns a collection of ModelItems for the label + /// + /// Label to Find by + /// Returns a ModelItem collection + public virtual ModelItems FindByLabel(object label) + { + //Construct itself without bothering about constructors. + ModelItems items = (ModelItems)this.Clone(); + items.Clear(); + + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + if (item.CompareLabel(label) == 0) + items.Add(item); + + return items; + } + + /// + /// Returns a collection of ModelItems that do not define the given label + /// + /// Label to find by + /// Returns a ModelItem collection + public virtual ModelItems FindByNoLabel(object label) + { + //Construct itself without bothering about constructors. + ModelItems items = (ModelItems)this.Clone(); + items.Clear(); + + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + if (item.CompareLabel(label) != 0) + items.Add(item); + + return items; + } + + public virtual T Choose(ModelEngine engine) + { + return this.Choose(engine.Options.Random, engine.Options.WeightScheme); + } + + public virtual T Choose(Random r, WeightScheme weightscheme) + { + if (this.Count <= 0) + return null; + + switch (weightscheme) + { + case WeightScheme.Equal: + return this[r.Next(this.Count)]; + + case WeightScheme.Geometric: + { + int weight = (int)Math.Pow(2, this.Count) - 1; + int index = r.Next(1, weight + 1); + int level = (int)Math.Round(Math.Log(index + 1) / Math.Log(2)); + level = this.Count - level; + return this[level]; + } + + case WeightScheme.AdaptiveEqual: + case WeightScheme.Custom: + { + int weight = this.Weight; + int index = r.Next(weight); + /// Implementation Note: All internal enumeration should be performed on this instead of _list + /// to ensure that any overriden GetEnumerator() can dictate how enumeration is performed + /// over the list items. + foreach (T item in this) + { + if (index < item.Weight) + { + //Note: Adaptive never turns off the action complete, just reduces frequency + if (weightscheme == WeightScheme.AdaptiveEqual && item.Weight > 1) + item.Weight--; + return item; + } + + index -= item.Weight; + } + return null; + } + + default: + throw new ModelException(null, "Unhandled WeightScheme: " + weightscheme); + }; + } + } + + /////////////////////////////////////////////////////////////////////////// + // ModelItemWeightComparer + // + /////////////////////////////////////////////////////////////////////////// + ///This is a base class used to compare any two model items and used + ///in sorting by weights, since weight is stored on the ModelItem class + public class ModelItemWeightComparer : IComparer where T : ModelItem + { + //Data + bool _desc = true; + + //Constructor + ///Constructor + public ModelItemWeightComparer(bool desc) + { + _desc = desc; + } + + //Methods + ///IComparer Compare method to compare two model items + public virtual int Compare(T x, T y) + { + //Return Values: + // -1 => x < y. + // 0 => x = y. + // +1 => x > y. + + //Note: If we want DESC sort, we'll reverse these definitions. + if(_desc) + { + T temp = x; + x = y; + y = temp; + } + + //Reference comparison (or both null) + if((object)x == (object)y) + return 0; + + //Handle null + if(x == null || y == null) + return y == null ? 1 : -1; + + //Weight + return ((IComparable)x.Weight).CompareTo(y.Weight); + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelParameter.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelParameter.cs new file mode 100644 index 0000000..1f1d307 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelParameter.cs @@ -0,0 +1,179 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //Reflection + + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelParameter (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple=true)] + public class ModelParameterAttribute : ModelRangeAttribute + { + //Data + int _position = -1; //Not specified + + //Constructor + public ModelParameterAttribute() + { + } + + public ModelParameterAttribute(params object[] values) + : base(values) + { + } + + //Accessors + public int Position + { + get { return _position; } + set { _position = value; } + } + } + + //////////////////////////////////////////////////////////////// + // ModelParameter + // + //////////////////////////////////////////////////////////////// + public class ModelParameter : ModelRange + { + //Data + ModelAction _action; + ParameterInfo _paraminfo; + + //Constructor + public ModelParameter(ModelAction action, ModelParameterAttribute attr, ParameterInfo paraminfo) + : base(action != null ? action.Model : null, attr) + { + _action = action; + _paraminfo = paraminfo; + + //Infer values from the type, if not specified + if(attr != null && attr.Type != null) + this.AddValuesFromType(attr, null); + + //BitMask + //TODO: Better place to expand this, (incase values added later). + if(this.BitMask) + this.AddBitCombinations(); + + //Infer dynamic variable(s) + this.InferDynamicVariables(); + } + + public virtual ModelAction Action + { + get { return _action; } + } + + public virtual int Position + { + get { return _paraminfo.Position; } + } + + public override Type Type + { + get { return _paraminfo.ParameterType; } + } + + public override string Name + { + get { return _paraminfo.Name; } + } + + public virtual bool IsOptional + { + get { return Attribute.IsDefined(_paraminfo, typeof(ParamArrayAttribute)); } + } + + public override string FullName + { + get + { + if(_fullname == null && _action != null) + { + _fullname = _action.FullName + "." + this.Name; + return _fullname; + } + + //Otherwise + return this.Name; + } + } + + public virtual bool IsNullable + { + get + { + return this.Type.GetGenericTypeDefinition() == typeof( System.Nullable<> ); + } + } + + public virtual object Clone() + { + ModelParameter clone = (ModelParameter)this.MemberwiseClone(); + + //Clone the collections, so add/remove is independent + clone.Values = (ModelValues)this.Values.Clone(); + return clone; + } + } + + //////////////////////////////////////////////////////////////// + // ModelParameters + // + //////////////////////////////////////////////////////////////// + ///This class represents a collection of model parameters, this is used by the action class to store its parameters + public class ModelParameters : ModelRanges + { + //Constructors + ///Default constructor + public ModelParameters() + { + } + + public new virtual ModelParameters Find(params string[] names) + { + return (ModelParameters)base.Find(names); + } + + public new virtual ModelParameters FindExcept(params string[] names) + { + return (ModelParameters)base.FindExcept(names); + } + + ///Return a collection of all the values of all the parameters in this collection + public virtual ModelValues Values + { + get + { + ModelValues values = new ModelValues(); + foreach (ModelRange range in this) + values.Add(range.Values); + + return values; + } + } + + ///A string concatenated version of the parameters to output as method signature + public override string ToString() + { + string output = null; + foreach (ModelParameter parameter in this) + { + if (output != null) + output += ", "; + output += parameter.Value; + } + + return output; + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelRange.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelRange.cs new file mode 100644 index 0000000..769ae1d --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelRange.cs @@ -0,0 +1,456 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //MemberInfo +using System.Collections; //Hashtable + + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelValueConjunction + // + //////////////////////////////////////////////////////////////// + public enum ModelValueConjunction + { + And, + Or, + } + + //////////////////////////////////////////////////////////////// + // ModelValueFlags + // + //////////////////////////////////////////////////////////////// + public enum ModelValueFlags + { + Bitmask = 0x00000001, + } + + //////////////////////////////////////////////////////////////// + // ModelRange (attribute) + // + //////////////////////////////////////////////////////////////// + public abstract class ModelRangeAttribute : ModelItemAttribute + { + //Data + Type _type = null; + internal ModelValues _values = new ModelValues(); + ModelValueConjunction _conjunction = ModelValueConjunction.And; + string _variable = null; + + protected ModelRangeAttribute() + { + } + + protected ModelRangeAttribute(params object[] values) + { + this.Values = values; + } + + //Accessors + public ModelValueConjunction Conjunction + { + get { return _conjunction; } + set { _conjunction = value; } + } + + public new ModelValueFlags Flags + { + get { return (ModelValueFlags)base.Flags; } + set { base.Flags = (int)value; } + } + + public Type Type + { + get { return _type; } + set { _type = value; } + } + + public object[] Values + { + get { return _values.ToArray(); } + set { _values.Add(value); } + } + + public object Value + { + get + { + if(_values.Count > 0) + return _values.First.Value; + return null; + } + set { _values.Add(new ModelValue(value, ModelValueOperator.Equal)); } + } + + public object Equal + { + get { return this.Value; } + set { this.Value = value; } + } + + public object Not + { + get { return this.Value; } + set { _values.Add(new ModelValue(value, ModelValueOperator.NotEqual)); } + } + + public object GreaterThan + { + get { return this.Value; } + set { _values.Add(new ModelValue(value, ModelValueOperator.GreaterThan)); } + } + + public object GreaterThanOrEqual + { + get { return this.Value; } + set { _values.Add(new ModelValue(value, ModelValueOperator.GreaterThanOrEqual)); } + } + + public object LessThan + { + get { return this.Value; } + set { _values.Add(new ModelValue(value, ModelValueOperator.LessThan)); } + } + + public object LessThanOrEqual + { + get { return this.Value; } + set { _values.Add(new ModelValue(value, ModelValueOperator.LessThanOrEqual)); } + } + + public object Min + { + //Delegate (this is an alias over GreaterThanOrEqual) + get { return this.Value; } + set { _values.Add(new ModelValue(value, ModelValueOperator.GreaterThanOrEqual)); } + } + + public object Max + { + //Delegate (this is an alias over LessThanOrEqual) + get { return this.Value; } + set { _values.Add(new ModelValue(value, ModelValueOperator.LessThanOrEqual)); } + } + + public object[] Any + { + get { return this.Values; } + set + { + _values.Clear(); + _values.Add(value); + _conjunction= ModelValueConjunction.Or; + } + } + + public bool BitMask + { + get { return IsFlag((int)ModelValueFlags.Bitmask); } + set { SetFlag((int)ModelValueFlags.Bitmask, value); } + } + + public string Variable + { + get { return _variable; } + set { _variable = value; } + } + } + + + //////////////////////////////////////////////////////////////// + // ModelRange + // + //////////////////////////////////////////////////////////////// + public abstract class ModelRange : ModelItem + { + //Data + + //Note: We want to allow dynamic (instance) changing of the data, without affecting + //the assoicated attribute. So simply store that state as well + internal ModelValues _values = new ModelValues(); + ModelValueConjunction _conjunction = ModelValueConjunction.And; + protected ModelVariable _variable = null; + + //Constructor + public ModelRange(Model model, ModelRangeAttribute attr) + : base(model, attr) + { + SetAttributeValues(attr); + } + + //Accessors + public virtual void SetAttributeValues(ModelRangeAttribute attr) + { + base.SetAttributeValues(attr); + if(attr != null) + { + _conjunction = attr.Conjunction; + + //Specified variable + if (attr.Variable != null && this.Model != null) + _variable = DetermineVariable(attr.Variable); + + //Copy the value array (so modifications don't muck with the original static model) + _values.Clear(); + _values.Add(attr._values); + } + } + + public virtual void InferDynamicVariables() + { + //Infer dynamic variable(s) + foreach (ModelValue v in _values) + { + //Example: Max="GetFieldCount" + //If the value is specified as a string, and the parameter/requirement isn't a string, + //then we try to assume the input is actually a 'variable' to call, so try and find it. + string variable = v.Value as string; + if (variable != null && this.Type != typeof(String) && this.Model != null) + v.Variable = DetermineVariable(variable); + } + } + + public virtual ModelVariable DetermineVariable(String name) + { + ModelVariable v = null; + + //[ModelVariable] + if(this.Model.Variables.Find(name).Count > 0) + v = this.Model.Variables[name]; + + //If not found, then is could be a dynamic variable (Columns.Count) + Object instance = this.Model; + if(v == null && name != null) + { + //Loop over the parts (ie: Command.Connection.IsOpen) + string[] parts = name.TrimEnd('(', ')').Split('.'); + for(int i = 0; i < parts.Length; i++) + { + String part = parts[i]; + if(String.IsNullOrEmpty(part)) + continue; + + //Reflection + MemberInfo[] members = instance.GetType().GetMember(part, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + if(members == null || members.Length == 0) + throw new ModelException(this, "Unable to find variable: '" + part + "'"); + + MemberInfo member = members[0];//TODO: Abmigious, more than one? + + //Variable + v = new ModelVariable(this.Model, instance, members[0], name, null); + if(i+1 0; + return matched == this.Values.Count; //AND + } + + public override void Reload() + { + } + } + + //////////////////////////////////////////////////////////////// + // ModelRanges + // + //////////////////////////////////////////////////////////////// + ///This class represents a range of items. This forms a base class for ModelVariables, ModelParameters and ModelRequirements. + public abstract class ModelRanges : ModelItems where T : ModelItem + { + //Constructor + ///Default constructor + public ModelRanges() + { + } + + ///Constructor with set of values + public ModelRanges(params T[] values) + : base(values) + { + } + + ///Returns a collection of all items that match the flag specified. + public virtual ModelRanges FindFlag(ModelValueFlags flag) + { + //Delegate + return this.FindFlag(flag, true); + } + + ///Returns a collection of all items except those that match the flag specified. + public virtual ModelRanges FindFlag(ModelValueFlags flag, bool include) + { + //Delegate + return (ModelRanges)base.FindFlag((int)flag, include); + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelRequirement.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelRequirement.cs new file mode 100644 index 0000000..950be13 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelRequirement.cs @@ -0,0 +1,171 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelRequirement (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Method, AllowMultiple=true)] + public class ModelRequirementAttribute : ModelRangeAttribute + { + //Constructor + public ModelRequirementAttribute() + { + } + + public ModelRequirementAttribute(params object[] values) + : base(values) + { + } + } + + //////////////////////////////////////////////////////////////// + // ModelRequirement + // + //////////////////////////////////////////////////////////////// + public class ModelRequirement : ModelRange + { + ModelAction _action; + bool _global = true; //global requirement (ie: any) + + //Constructor + public ModelRequirement(ModelVariable variable, Object value) + : this(variable, new ModelValue(value)) + { + //Delegate + } + + public ModelRequirement(ModelVariable variable, ModelValue value) + : this(null, null, variable, value) + { + //Delegate + } + + public ModelRequirement(ModelAction action, ModelVariable variable, ModelValue value) + : this(action, null, variable, value) + { + //Delegate + } + + public ModelRequirement(ModelAction action, ModelRequirementAttribute attr, ModelVariable variable, ModelValue value) + : base(action != null ? action.Model : null, attr) + { + //Action + _action = action; + + //Variable + if (variable != null) + { + _variable = variable; + if (variable.BitMask) + this.BitMask = true; + } + //if(_variable == null) + // throw new ModelException(this, "An empty variable is not a valid requirement", null); + + //Only override the attr, if values are really specified + if(value != null) + _values = new ModelValues(value); + + //BitMask + //TODO: Better place to expand this, (incase values added later). + if(this.BitMask) + this.AddBitCombinations(); + + //Infer dynamic variable(s) + this.InferDynamicVariables(); + + //Requirements assoicated with actions, are not global. They are tied to that particular + //instance of the model, and it's instance of state variables. However if not assoicated + //with actions, the default behavior is that their global, they apply to all models + //that contain that state variable, unless explicitly indicated otherwise. + _global = (action == null); + } + + public override string Name + { + get + { + if(_name == null) + { + String name = null; + if(_action != null) + name += _action.Name + "."; + if(_variable != null) + name += _variable.Name; + _name = name; + } + return _name; + } + } + + public virtual ModelAction Action + { + get { return _action; } + } + + public override Type Type + { + get + { + if(_variable != null) + return _variable.Type; + return null; + } + } + + public virtual bool Global + { + get { return _global; } + set { _global = value; } + } + } + + //////////////////////////////////////////////////////////////// + // ModelRequirements + // + //////////////////////////////////////////////////////////////// + ///This class represents a collection of requirements, this is used by the ModelAction class. + public class ModelRequirements : ModelRanges + { + //Constructors + ///Default constructor + public ModelRequirements() + { + } + + ///Overload that takes an array of requirements + public ModelRequirements(params ModelRequirement[] requirements) + : base(requirements) + { + } + + ///Add a requirement to this collection from the variable and the value specified + public virtual ModelRequirement Add(ModelVariable variable, object value) + { + return this.Add(new ModelRequirement(variable, new ModelValue(value))); + } + + ///Find a requirement from the variable specified + public virtual ModelRequirement Find(ModelVariable variable) + { + //Find a matching variable, of the specified type + foreach (ModelRequirement requirement in this) + { + if (requirement.Variable == variable) + return requirement; + } + + //Otherwise + return null; + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelTrace.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelTrace.cs new file mode 100644 index 0000000..995216a --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelTrace.cs @@ -0,0 +1,194 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.IO; //TextWriter +using System.Text; //StringBuilder + + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelTraceLevel + // + //////////////////////////////////////////////////////////////// + public enum ModelTraceLevel + { + //TODO: + }; + + //////////////////////////////////////////////////////////////// + // ModelTrace + // + //////////////////////////////////////////////////////////////// + public class ModelTrace + { + //Data + static TextWriter _writer = Console.Out; + + //Accessors + public static bool Enabled + { + //Note: This changes the default for all engines + //You can also change this on a per engine basis: + // ModelEngine.Options.Tracing=true|false + get { return ModelEngineOptions.Default.Tracing; } + set { ModelEngineOptions.Default.Tracing = value; } + } + + public static TextWriter Out + { + get { return _writer; } + set { _writer = value; } + } + + //Methods + public static void Write(object value) + { + if(_writer != null) + _writer.Write(value); + } + + public static void WriteLine() + { + if(_writer != null) + _writer.WriteLine(); + } + + public static void WriteLine(object value) + { + if(_writer != null) + _writer.WriteLine(value); + } + + public static string FormatMethod(ModelActionInfo info) + { + //Format: + // Command cmd = conn.createcommand(); + StringBuilder buffer = new StringBuilder(100); + + //Return + if(info.RetVal != null) + { + Model output = info.RetVal as Model; + if(output != null) + { + //Type + if(info.Created) + { + buffer.Append(output.GetType().Name); + buffer.Append(" "); + } + + //Variable + buffer.Append(output.Name); + buffer.Append(output.Id); + buffer.Append(" = "); + } + } + + //Call + buffer.Append(info.Action.Model.Name); + if(!info.Action.Method.IsStatic) + buffer.Append(info.Action.Model.Id); + buffer.Append("."); + buffer.Append(info.Action.Name); + + //Parameters + buffer.Append("("); + if(info.Parameters != null) + { + int ordinal = 0; + foreach(ModelParameter parameter in info.Parameters) + { + if(ordinal++ > 0) + buffer.Append(", "); + FormatValue(buffer, parameter.Type, parameter.Value.Value); + } + } + buffer.Append(")"); + buffer.Append(";"); + + //Output + return buffer.ToString(); + } + + public static void FormatValue(StringBuilder builder, Type type, object value) + { + String prefix = null; + String suffix = null; + String cast = null; + String format = null; + + //Special types + if(type == typeof(String)) + { + //Strings + prefix = "\""; + suffix = "\""; + } + else if(type.IsEnum) + { + //Enums + prefix = type.Name + "."; + format = value.ToString().Replace(", ", " | " + prefix); + } + else if(type == typeof(bool)) + { + //Bool (lowercase) + format = value.ToString().ToLower(); + } + else if(type == typeof(byte)) + { + cast = "(byte)"; + } + else if(type == typeof(short)) + { + cast = "(short)"; + } + else if(type.IsArray && value != null) + { + //new object[]{...} + builder.Append("new "); + //Note: Don't box integers, primarily since Int32 isn't a portable type (ie: Java) + builder.Append(type == typeof(Int32[]) ? "int[]" : type.Name); + builder.Append("{"); + + //Recurse + int ordinal = 0; + Array items = (Array)value; + foreach(object item in items) + { + if(ordinal++ > 0) + builder.Append(", "); + FormatValue(builder, type.GetElementType(), item); + } + + builder.Append("}"); + return; //Were done + } + + //Default formating + if(format == null && value != null) + format = value.ToString(); + if(format == null) + format = "null"; + + //Prefix + if(prefix != null) + builder.Append(prefix); + + //Value + if(cast != null) + builder.Append(cast); + builder.Append(format); + + //Suffix + if(suffix != null) + builder.Append(suffix); + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelValue.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelValue.cs new file mode 100644 index 0000000..e1ec700 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelValue.cs @@ -0,0 +1,251 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; //Comparer + + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelValueOperator + // + //////////////////////////////////////////////////////////////// + public enum ModelValueOperator + { + Equal, // x == y + NotEqual, // x != y + GreaterThan, // x > y + GreaterThanOrEqual, // x >= y + LessThan, // x < y + LessThanOrEqual, // x <= y + } + + //////////////////////////////////////////////////////////////// + // ModelValue + // + //////////////////////////////////////////////////////////////// + public class ModelValue : ModelItem + { + //Data + + //Note: We want to allow dynamic (instance) changing of the data, without affecting + //the assoicated attribute. So simply store that state as well + Type _type; + object _value = null; + ModelVariable _variable = null; //Dynamic value + ModelValueOperator _operator = ModelValueOperator.Equal; + + //Constructor + public ModelValue(object value) + : this(value, ModelValueOperator.Equal) + { + //Delegate + } + + public ModelValue(object value, ModelValueOperator op) + : this(value != null ? value.GetType() : null, value, op) + { + //Delegate + } + + public ModelValue(Type type, object value) + : this(type, value, ModelValueOperator.Equal) + { + //Delegate + } + + public ModelValue(Type type, object value, ModelValueOperator op) + : base(null, null) + { + _type = type; + _operator = op; + _value = value; + } + + public virtual Type Type + { + get { return _type; } + set { _type = value; } + } + + public virtual bool IsEnum + { + get { return this.Type != null && this.Type.IsEnum; } + } + + public object Value + { + get + { + if(_variable != null) + return _variable.CachedValue; //Dynamic value + return _value; + } + } + + public ModelVariable Variable + { + get { return _variable; } + set { _variable = value; } + } + + public override void Reload() + { + } + + public override string ToString() + { + if(this.Value != null) + return this.Value.ToString(); + return null; + } + + public ModelValueOperator Operator + { + get { return _operator; } + set { _operator = value; } + } + + public virtual bool Evaluate(object expected) + { + //Delegate + return this.Evaluate(expected, _operator); + } + + public virtual bool Evaluate(object expected, ModelValueOperator op) + { + bool matched = false; + int icompare = 0; + object actual= this.Value; + + //Delegate the actual comparison + try + { + if(op == ModelValueOperator.Equal || op == ModelValueOperator.NotEqual) + { + //Note: For == or !=, we don't require IComparer + icompare = object.Equals(expected, actual) ? 0 : 1; + } + else + { + //Note: To use <, >, the value needs to support IComparer + icompare = Comparer.DefaultInvariant.Compare(expected, actual); + } + } + catch(Exception e) + { + //Make this easier to debug + throw new ModelException(this, "Unable to compare: '" + actual + "' to: '" + expected + "'", e); + } + + //Determine if matched (equal, notequal, etc) + switch(op) + { + case ModelValueOperator.Equal: + matched = (icompare == 0); + break; + + case ModelValueOperator.NotEqual: + matched = (icompare != 0); + break; + + case ModelValueOperator.GreaterThan: + matched = (icompare > 0); + break; + + case ModelValueOperator.GreaterThanOrEqual: + matched = (icompare >= 0); + break; + + case ModelValueOperator.LessThan: + matched = (icompare < 0); + break; + + case ModelValueOperator.LessThanOrEqual: + matched = (icompare <= 0); + break; + + default: + throw new ModelException(this, "Unhandled Operator: " + op, null); + }; + + return matched; + } + } + + //////////////////////////////////////////////////////////////// + // ModelValues + // + //////////////////////////////////////////////////////////////// + ///This class represents a collection of model values. Many of our custom attributes represent collection entities. + ///We use this class to store all the possible values of the entity. + public class ModelValues : ModelItems + { + ///Default constructor + public ModelValues() + { + } + + ///Constructor with multiple model values to build the collection + public ModelValues(params ModelValue[] values) + : base(values) + { + } + + ///Add an array of objects to this collection + public virtual void Add(object[] values) + { + if (values == null) + values = new Object[] { null }; + + foreach (object value in values) + this.Add(new ModelValue(value)); + } + + ///Returns an object array of the values stored in the collection + public new virtual object[] ToArray() + { + object[] values = null; + if (this.Count > 0) + { + values = new object[this.Count]; + for (int i = 0; i < this.Count; i++) + values[i] = this[i].Value; + } + return values; + } + + ///Finds a value that matches the instance + public virtual ModelValue FindValue(object instance) + { + foreach (ModelValue val in _list) + { + if (val.Evaluate(instance, ModelValueOperator.Equal)) + return val; + } + return null; + } + + ///Finds all the model values that match the operator specified + public virtual ModelValues FindOperator(ModelValueOperator op) + { + return this.FindOperator(op, true); + } + + ///Finds all the model values except those that match the operator specified + public virtual ModelValues FindOperator(ModelValueOperator op, bool include) + { + ModelValues found = new ModelValues(); + foreach (ModelValue item in _list) + { + if ((item.Operator == op) == include) + found.Add(item); + } + return found; + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/ModelVariable.cs b/ApiAsAService/odata.net/tools/KoKoMo/ModelVariable.cs new file mode 100644 index 0000000..aeea8dc --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/ModelVariable.cs @@ -0,0 +1,183 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //MemberInfo +using System.Security.Permissions; +using System.Security; + +namespace Microsoft.Test.KoKoMo +{ + //////////////////////////////////////////////////////////////// + // ModelVariable (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class ModelVariableAttribute : ModelRangeAttribute + { + } + + //////////////////////////////////////////////////////////////// + // ModelVariable + // + //////////////////////////////////////////////////////////////// + public class ModelVariable : ModelRange + { + //Note: We allow model variables to be simple fields, or properties. Fields for simplicity + //in value comparison, and properties for custom complex expressions (ie: any code). + MemberInfo _info; + Object _instance; + + //Note: Many variables (ie: methods/properties) are expensive, some have side-effects, + //and many are used as requirements on numerous actions. Instead of calling them for each + //and every variable on each an every action/requirement/parameter, we only call them once. + //We cache the value internally, and the engine resets the cache + bool _cached = false; + Object _cachedvalue = null; + + //Constructor + public ModelVariable(Model model, MemberInfo info, ModelVariableAttribute attr) + : this(model, model, info, info.Name, attr) + { + } + + public ModelVariable(Model model, Object instance, MemberInfo info, String name, ModelVariableAttribute attr) + : base(model, attr) + { + _info = info; + _instance = instance; + _name = name; + + if(attr != null) + { + //Infer values from the type, if not specified + if(_values.Count == 0 || attr.Type != null) + this.AddValuesFromType(attr, this.Type); + } + + //BitMask + //TODO: Better place to expand this, (incase values added later). + if(this.BitMask) + this.AddBitCombinations(); + } + + public override string Name + { + get { return _name; } + } + + public override Type Type + { + [PermissionSet(SecurityAction.Assert, Unrestricted = true)] + [SecuritySafeCritical] + get + { + if(_info is FieldInfo) + return ((FieldInfo)_info).FieldType; //Field + if(_info is MethodInfo) + return ((MethodInfo)_info).ReturnType; //Method + return ((PropertyInfo)_info).PropertyType; //Property + } + } + + public virtual object CurrentValue + { + [PermissionSet(SecurityAction.Assert, Unrestricted = true)] + [SecuritySafeCritical] + get + { + //Invoke (field, method, property) + try + { + if(_info is FieldInfo) + return ((FieldInfo)_info).GetValue(_instance); //Field + else if(_info is MethodInfo) + return ((MethodInfo)_info).Invoke(_instance, null); //Method (no parameters) + else + return ((PropertyInfo)_info).GetValue(_instance, null); //Property + } + catch(Exception e) + { + //Make this easier to debug + throw new ModelException(this, null, e); + } + } + } + + public virtual object CachedValue + { + get + { + if(_cached) + return _cachedvalue; + + + _cached = true; + _cachedvalue = this.CurrentValue; + return _cachedvalue; + } + set + { + _cached = false; + _cachedvalue = value; + } + } + + public override void Reload() + { + } + } + + //////////////////////////////////////////////////////////////// + // ModelVariables + // + //////////////////////////////////////////////////////////////// + ///This class represents a collection of model variables + public class ModelVariables : ModelRanges + { + //Constructors + ///Default constructor + public ModelVariables() + { + } + + public new virtual ModelVariables Find(params string[] names) + { + return (ModelVariables)base.Find(names); + } + + public new virtual ModelVariables FindExcept(params string[] names) + { + return (ModelVariables)base.FindExcept(names); + } + + public virtual ModelVariable this[Type type] + { + //Find first, fail if not found, although make it easier to debug + get + { + ModelVariables found = this.FindType(type); + if (found == null || found.Count <= 0) + throw new IndexOutOfRangeException(this.Name + "['" + type + "'] is not found"); + return found.First; + } + } + + ///Finds a model variable in this collection of the specified type + public virtual ModelVariables FindType(Type type) + { + //Find a matching variable, of the specified type + ModelVariables found = new ModelVariables(); + foreach (ModelVariable variable in this) + { + if (variable.Type == type) + found.Add(variable); + } + + return found; + } + } +} diff --git a/ApiAsAService/odata.net/tools/KoKoMo/Properties/assemblyinfo.cs b/ApiAsAService/odata.net/tools/KoKoMo/Properties/assemblyinfo.cs new file mode 100644 index 0000000..476bc75 --- /dev/null +++ b/ApiAsAService/odata.net/tools/KoKoMo/Properties/assemblyinfo.cs @@ -0,0 +1,9 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Reflection; +using System.Security; +[assembly: AllowPartiallyTrustedCallers] \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/KoKoMo/kokomo.snk b/ApiAsAService/odata.net/tools/KoKoMo/kokomo.snk new file mode 100644 index 0000000000000000000000000000000000000000..d340f0e4194edf2f972cfdf583d6895a11693c29 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096Q_~8(d$G5TKCZn1>;u9kr+ zO1eq3@8yqTEPT?l0=JWBX5e+A7LPCfGICS5ECEAr&8Fcke`GSgJf*}UCF7F9c_cL5 z>c%(>INN)DL>_?%DWQF&3I~M^eDGKgSUMzOmQ%MiBX08C-(rvtio^lHOspmw4UWXC z4tjl}J{G&`i!BqT!&Esv%;?Tp%e=~N!~su2a=MKwP1oxs{U1m1*D>{zk2IJK7j|bV z;Mx$KShE_odFS9u1g|5iMPrz43pdXuD+VJOs}%I-OuA_^qi;NY@+tOBFOo@&7$kv! z71fC>is|Z@QPye&A8o)lSUj4KCA;5iZj}FnGpO5KDGf246ER8Oykw{_!MF%uH4J4E50``1*HoEW i_rWFF519o;(EZJ%nYXmS2gqLuzepXy0b92iWJm + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/Interop.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/Interop.cs new file mode 100644 index 0000000..dea50a7 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/Interop.cs @@ -0,0 +1,281 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; //IndexerName +using System.Security; + + +//Note: Currently TLBIMP.EXE has a bug where it doesn't allow the user to specify the +//AllowsPartialTrustedCaller attribute, so we cannot run our tests in anything +//but FullTrust, becuase of the interop. So for now we will write our own interop +//wrapper. Eventually this code could also just be compiled into the ModuleCore.dll, +//but since all the tests currently link with dll already, we'll just built them here... + +namespace Microsoft.Test.ModuleCore +{ +// internal class Interop +// { + //////////////////////////////////////////////////////////////// + // LtmContext + // + // Managed wrapper around Ltm context object + //////////////////////////////////////////////////////////////// + [ComImport(), Guid("DAA3A3F6-B08B-11d1-A971-00C04F94A717"), ClassInterface(ClassInterfaceType.None)] + public class LtmContext + { + } + + //////////////////////////////////////////////////////////////////////// + // TestResult + // + //////////////////////////////////////////////////////////////////////// + public enum TestResult + { + Failed = 0, + Passed, + Skipped, + NonExistent, + Unknown, + Timeout, + Warning, + Exception, + Aborted, + Assert + }; + + //////////////////////////////////////////////////////////////////////// + // TestPropertyFlags + // + //////////////////////////////////////////////////////////////////////// + public enum TestPropertyFlags + { + Read = 0x00000000, + Write = 0x00000001, + Required = 0x00000010, + Inheritance = 0x00000100, + MultipleValues = 0x00001000, + DefaultValue = 0x00002000, + Visible = 0x00010000, + }; + + //////////////////////////////////////////////////////////////////////// + // TestType + // + //////////////////////////////////////////////////////////////////////// + public enum TestType + { + TestSuite = 0, + TestModule = 1, + TestCase = 2, + TestVariation = 3, + }; + + //////////////////////////////////////////////////////////////////////// + // TestFlags + // + //////////////////////////////////////////////////////////////////////// + public enum TestFlags + { + }; + + //////////////////////////////////////////////////////////////////////// + // TestMethod + // + //////////////////////////////////////////////////////////////////////// + public enum TestMethod + { + Init = 0, + Terminate = 1, + Execute = 2, + }; + + //////////////////////////////////////////////////////////////////////// + // TestLogFlags + // + //////////////////////////////////////////////////////////////////////// + public enum TestLogFlags + { + Raw = 0x00000000, //No fixup - Don't use, unless you know the text contains no CR/LF, no Xml reserverd tokens, or no other non-respresentable characters + Text = 0x00000001, //Default - Automatically fixup CR/LF correctly for log files, fixup xml tokens, etc + Xml = 0x00000002, //For Xml - User text is placed into a CDATA section (with no xml fixups) + Ignore = 0x00000004, //Ignore - User text is placed into ignore tags (can combine this with console_xml as well) + Trace = 0x00000010, //Trace - User text is not displayed unless epxlicitly enabled + }; + + //////////////////////////////////////////////////////////////////////// + // ITestItem + // + //////////////////////////////////////////////////////////////////////// + [ComImport, Guid("A40464E3-320C-4a0d-BFE2-112BF04F6202"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurityAttribute()] + public interface ITestItem + { + // Simple Meta-data about the item + int Id { get; } + String Guid { [return: MarshalAs(UnmanagedType.BStr)] get; } + String Name { [return: MarshalAs(UnmanagedType.BStr)] get; } + String Desc { [return: MarshalAs(UnmanagedType.BStr)] get; } + String Owner { [return: MarshalAs(UnmanagedType.BStr)] get; } + String Version { [return: MarshalAs(UnmanagedType.BStr)] get; } + int Priority { get; } + TestType Type { get; } + TestFlags Flags { get; } + + // Extensible Meta-data about the item + ITestProperties Metadata { [return: MarshalAs(UnmanagedType.Interface)] get; } + + // Children (testcases, variations, etc) + ITestItems Children { [return: MarshalAs(UnmanagedType.Interface)] get; } + + // Execution + // Control Flow: Init->Execute->(recurse into children)->Terminate + TestResult Init (); + TestResult Execute (); + TestResult Terminate (); + } + + //////////////////////////////////////////////////////////////////////// + // ITestItems + // + //////////////////////////////////////////////////////////////////////// + [ComImport, Guid("F2D83417-A9FF-4503-B11F-8776FF812AEA"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurityAttribute()] + public interface ITestItems + { + // Enumeration + int Count { get; } + [return: MarshalAs(UnmanagedType.Interface)] + ITestItem GetItem ( int index); + } + + //////////////////////////////////////////////////////////////////////// + // ITestProperty + // + //////////////////////////////////////////////////////////////////////// + [ComImport, Guid("2FD231B5-0333-44f1-909E-483CDE87B196"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurityAttribute()] + public interface ITestProperty + { + // Simple Meta-data about the property +// int Id { get; } +// String Guid { get; } + String Name { [return: MarshalAs(UnmanagedType.BStr)] get; } + String Desc { [return: MarshalAs(UnmanagedType.BStr)] get; } +// string Owner { [return: MarshalAs(UnmanagedType.BStr)] get; } +// int Priority { get; } + TestPropertyFlags Flags { get; set; } + Object Value { get; } + void set_Value(ref object value); + + // Extensible Meta-data about the property + ITestProperties Metadata { [return: MarshalAs(UnmanagedType.Interface)] get; } + + // Children - Heiarchical properties + ITestProperties Children { [return: MarshalAs(UnmanagedType.Interface)] get; } + } + + //////////////////////////////////////////////////////////////////////// + // ITestProperties + // + //////////////////////////////////////////////////////////////////////// + [ComImport, Guid("EECA332B-072A-4018-A7B5-DF83AD9BAB28"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurityAttribute()] + public interface ITestProperties + { + // Enumeration + int Count { get; } + [return: MarshalAs(UnmanagedType.Interface)] + ITestProperty GetItem ( int index); + + //Access methods + [return: MarshalAs(UnmanagedType.Interface )] + ITestProperty Get ([MarshalAs(UnmanagedType.BStr)] string name); + [return: MarshalAs(UnmanagedType.Interface )] + ITestProperty Add ([MarshalAs(UnmanagedType.BStr)] string name); + void Remove ([MarshalAs(UnmanagedType.BStr)] string name); + void Clear (); + } + + //////////////////////////////////////////////////////////////////////// + // ITestLoader + // + //////////////////////////////////////////////////////////////////////// + [ComImport, Guid("54FE009F-5D58-4f90-B238-8961350FD632"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurityAttribute()] + public interface ITestLoader + { + //Simple Metadata +// int Id { get; } + String Guid { get; } + String Name { [return: MarshalAs(UnmanagedType.BStr)] get; } + String Desc { [return: MarshalAs(UnmanagedType.BStr)] get; } + + // Extensible Meta-data about the item + ITestProperties Metadata { [return: MarshalAs(UnmanagedType.Interface)] get; } + + //Execution + void Init (); + [return: MarshalAs(UnmanagedType.Interface )] + ITestItem CreateTest ( [MarshalAs(UnmanagedType.BStr)] string assembly, [MarshalAs(UnmanagedType.BStr)] string test); + void Terminate (); + + //Enumeration + [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BSTR)] + String[] Enumerate ( [MarshalAs(UnmanagedType.BStr)] string assembly); + + //Input (get/set) + ITestProperties Properties { [param: MarshalAs(UnmanagedType.Interface)] set; [return: MarshalAs(UnmanagedType.Interface)] get; } + + //Logging (get/set) + ITestLog Log { [param: MarshalAs(UnmanagedType.Interface)] set; [return: MarshalAs(UnmanagedType.Interface)] get; } + } + + //////////////////////////////////////////////////////////////////////// + // ITestLog + // + //////////////////////////////////////////////////////////////////////// + [ComImport, Guid("11B3FA9D-B07A-44ad-9C9B-81558AA663B8"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressUnmanagedCodeSecurityAttribute()] + public interface ITestLog + { + //Simple Metadata +// int Id { get; } +// String Guid { get; } + String Name { [return: MarshalAs(UnmanagedType.BStr)] get; } + String Desc { [return: MarshalAs(UnmanagedType.BStr)] get; } + + // Extensible Meta-data about the item + ITestProperties Metadata { [return: MarshalAs(UnmanagedType.Interface)] get; } + + //Construction + void Init (); + void Terminate (); + + //Console + void Write( TestLogFlags flags, + [MarshalAs(UnmanagedType.BStr)] string text); + void WriteLine( TestLogFlags flags, + [MarshalAs(UnmanagedType.BStr)] string text); + + //Scoping + void Enter( [MarshalAs(UnmanagedType.Interface)] ITestItem item, TestMethod method ); + void Leave( [MarshalAs(UnmanagedType.Interface)] ITestItem item, TestMethod method, TestResult result ); + + //(Error) Logging routines + void Error ( TestResult result, + TestLogFlags flags, + [MarshalAs(UnmanagedType.BStr)] string actual, + [MarshalAs(UnmanagedType.BStr)] string expected, + [MarshalAs(UnmanagedType.BStr)] string source, + [MarshalAs(UnmanagedType.BStr)] string message, + [MarshalAs(UnmanagedType.BStr)] string stack, + [MarshalAs(UnmanagedType.BStr)] string filename, + int lineno); + } +// } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/ModuleCore.csproj b/ApiAsAService/odata.net/tools/ModuleCore/src/ModuleCore.csproj new file mode 100644 index 0000000..68d6f15 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/ModuleCore.csproj @@ -0,0 +1,43 @@ + + + false + true + Microsoft.Test.ModuleCore + ;1699;1570;1572;1573;1591; + prompt + Library + {5E46C9E2-8B2F-4961-8C26-EFA9DF6CD68D} + false + Microsoft.Test.ModuleCore + false + modulecore.snk + 4 + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/Properties/assemblyinfo.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/Properties/assemblyinfo.cs new file mode 100644 index 0000000..d0e4313 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/Properties/assemblyinfo.cs @@ -0,0 +1,8 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Reflection; +[assembly: AssemblyKeyFile("framework.snk")] diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestAttribute.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestAttribute.cs new file mode 100644 index 0000000..7901cd5 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestAttribute.cs @@ -0,0 +1,726 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // SecurityFlags + // + //////////////////////////////////////////////////////////////// + public enum SecurityFlags + { + None = 0, + FullTrust = 1, + ThreatModel = 2, + }; + + //////////////////////////////////////////////////////////////// + // TestAttribute (attribute) + // + //////////////////////////////////////////////////////////////// + [Serializable()] + public abstract class TestAttribute : Attribute + { + //Data + protected string _name; + protected string _desc; + protected object[] _params; + protected int _id; + protected string _guid; + protected bool _inheritance = true; + protected TestAttribute _parent = null; + protected string _filter; + protected string _version; + + //Allows Inhertiance (ie: object to determine if ever been set) + protected int? _priority; //Allows Inhertiance + protected string _purpose; //Allows Inhertiance + protected bool? _implemented; //Allows Inhertiance + protected string[] _owners; //Allows Inhertiance + protected string[] _areas; //Allows Inhertiance + protected bool? _skipped; //Allows Inhertiance + protected bool? _error; //Allows Inhertiance + protected bool? _manual; //Allows Inhertiance + protected SecurityFlags? _security; //Allows Inhertiance + protected string _filtercriteria;//Allows Inhertiance + protected string[] _languages; //Allows Inhertiance + protected string _xml; //Allows Inhertiance + protected int? _timeout; //Allows Inhertiance + protected int? _threads; //Allows Inhertiance + protected int? _repeat; //Allows Inhertiance + protected bool? _stress; //Allows Inhertiance + + //Constructors + public TestAttribute() + { + } + + public TestAttribute(string desc) + { + Desc = desc; + } + + public TestAttribute(string desc, params Object[] parameters) + { + Desc = desc; + Params = parameters; + } + + //Accessors + public virtual string Name + { + get { return _name; } + set { _name = value; } + } + + public virtual string Desc + { + get { return _desc; } + set { _desc = value; } + } + + public virtual int Id + { + get { return _id; } + set { _id = value; } + } + + public virtual string Guid + { + get { return _guid; } + set { _guid = value; } + } + + public virtual int Priority + { + get + { + if(_priority == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Priority; + + //Default + return 2; + } + return (int)_priority; + } + set { _priority = value; } + } + + [TestProperty(Visible = true)] + public virtual string Owner + { + get + { + if(_owners != null) + return _owners[0]; + return null; + } + set + { + if(_owners == null) + _owners = new string[1]; + _owners[0] = value; + } + } + + [TestProperty(Visible = true, MultipleValues=true)] + public virtual string[] Owners + { + get { return _owners; } + set { _owners = value; } + } + + [TestProperty(Visible = true)] + public virtual string Area + { + get + { + if(_areas != null) + return _areas[0]; + return null; + } + set + { + if(_areas == null) + _areas = new string[1]; + _areas[0] = value; + } + } + + [TestProperty(Visible = true, MultipleValues=true)] + public virtual string[] Areas + { + get { return _areas; } + set { _areas = value; } + } + + public virtual string Version + { + get { return _version; } + set { _version = value; } + } + + [TestProperty(Visible=false)] + public virtual object Param + { + get + { + if(_params != null) + return _params[0]; + return null; + } + set + { + if(_params == null) + _params = new object[1]; + _params[0] = value; + } + } + + [TestProperty(Visible=false, MultipleValues=true)] + public virtual object[] Params + { + get { return _params; } + set { _params = value; } + } + + [TestProperty(Visible=false, DefaultValue=true)] + public virtual bool Inheritance + { + get { return _inheritance; } + set { _inheritance = value; } + } + + public virtual TestAttribute Parent + { + get { return _parent; } + set { _parent = value; } + } + + public virtual string Filter + { + get { return _filter; } + set { _filter = value; } + } + + [TestProperty(Visible=true)] + public virtual string Purpose + { + get + { + if(_purpose == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Purpose; + + //Default + return null; + } + return (string)_purpose; + } + set { _purpose = value; } + } + + [TestProperty(Visible=true, DefaultValue=true)] + public virtual bool Implemented + { + get + { + if(_implemented == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Implemented; + + //Default + return true; + } + return (bool)_implemented; + } + set { _implemented = value; } + } + + [TestProperty(Visible=true, DefaultValue=false)] + public virtual bool Skipped + { + get + { + if(_skipped == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Skipped; + + //Default + return false; + } + return (bool)_skipped; + } + set { _skipped = value; } + } + + [TestProperty(Visible=true, DefaultValue=false)] + public virtual bool Error + { + get + { + if(_error == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Error; + + //Default + return false; + } + return (bool)_error; + } + set { _error = value; } + } + + [TestProperty(Visible=true, DefaultValue=false)] + public virtual bool Manual + { + get + { + if(_manual == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Manual; + + //Default + return false; + } + return (bool)_manual; + } + set { _manual = value; } + } + + [TestProperty(Visible=true, DefaultValue=SecurityFlags.None)] + public virtual SecurityFlags Security + { + get + { + if(_security == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Security; + + //Default + return SecurityFlags.None; + } + return (SecurityFlags)_security; + } + set { _security = value; } + } + + [TestProperty(Visible=false, MultipleValues=true)] + public virtual string FilterCriteria + { + get + { + if(_filtercriteria == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.FilterCriteria; + + //Default + return null; + } + return (string)_filtercriteria; + } + set { _filtercriteria = value; } + } + + [TestProperty(Visible=false)] + public virtual string Language + { + get + { + if(Languages != null) + return Languages[0]; + return null; + } + set + { + if(Languages == null) + Languages = new string[1]; + Languages[0] = value; + } + } + + [TestProperty(Visible=false, MultipleValues=true)] + public virtual string[] Languages + { + get + { + if(_languages == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Languages; + + //Default + return null; + } + return (string[])_languages; + } + set { _languages = value; } + } + + [TestProperty(Visible=false, MultipleValues=true)] + public virtual string Xml + { + get + { + if(_xml == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Xml; + + //Default + return null; + } + return (string)_xml; + } + set { _xml = value; } + } + + [TestProperty(Visible=true, DefaultValue=false)] + public virtual bool Stress + { + get + { + if(_stress == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Stress; + + //Default + return false; + } + return (bool)_stress; + } + set { _stress = value; } + } + + [TestProperty(Visible=true, DefaultValue=0)] + public virtual int Timeout + { + get + { + if(_timeout == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Timeout; + + //Default (infinite) + return 0; + } + return (int)_timeout; + } + set { _timeout = value; } + } + + [TestProperty(Visible=true, DefaultValue=1)] + public virtual int Threads + { + get + { + if(_threads == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Threads; + + //Default (one thread) + return 1; + } + return (int)_threads; + } + set { _threads = value; } + } + + [TestProperty(Visible=true, DefaultValue=0)] + public virtual int Repeat + { + get + { + if(_repeat == null) + { + //Inheritance + if(Inheritance && _parent != null) + return _parent.Repeat; + + //Default (no repeat) + return 0; + } + return (int)_repeat; + } + set { _repeat = value; } + } + } + + + //////////////////////////////////////////////////////////////// + // TestModule (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class TestModuleAttribute : TestAttribute + { + //Data + protected string _created; + protected string _modified; + + //Constructors + public TestModuleAttribute() + : base() + { + } + + public TestModuleAttribute(string desc) + : base(desc) + { + } + + public TestModuleAttribute(string desc, params Object[] parameters) + : base(desc, parameters) + { + } + + [TestProperty(Visible = true)] + public virtual string Created + { + get { return _created; } + set { _created = value; } + } + + [TestProperty(Visible=true)] + public virtual string Modified + { + get { return _modified; } + set { _modified = value; } + } + } + + //////////////////////////////////////////////////////////////// + // TestCase (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [Serializable()] + public class TestCaseAttribute : TestAttribute + { + //Constructors + public TestCaseAttribute() + : base() + { + } + + public TestCaseAttribute(string desc) + : base(desc) + { + } + + public TestCaseAttribute(string desc, params Object[] parameters) + : base(desc, parameters) + { + } + } + + + //////////////////////////////////////////////////////////////// + // Variation (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + [Serializable] + public class VariationAttribute : TestAttribute + { + //Data + + //Constructors + public VariationAttribute() + : base() + { + } + + public VariationAttribute(string desc) + : base(desc) + { + } + + public VariationAttribute(string desc, params Object[] parameters) + : base(desc, parameters) + { + } + } + + //////////////////////////////////////////////////////////////// + // TestPropertyAttribute (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Property)] + public class TestPropertyAttribute : Attribute + { + //Data + protected string _name; + protected string _desc; + protected Guid _guid; + protected int _id; + protected object _defaultvalue; + protected TestPropertyFlags _flags = TestPropertyFlags.Read; + + //Constructors + public TestPropertyAttribute() + { + } + + public virtual string Name + { + get { return _name; } + set { _name = value; } + } + + public virtual string Desc + { + get { return _desc; } + set { _desc = value; } + } + + public virtual Guid Guid + { + get { return _guid; } + set { _guid = value; } + } + + public virtual int Id + { + get { return _id; } + set { _id = value; } + } + + public virtual object DefaultValue + { + get { return _defaultvalue; } + set { _defaultvalue = value;} + } + + public virtual bool Settable + { + get { return this.IsFlag(TestPropertyFlags.Write); } + set { this.SetFlag(TestPropertyFlags.Write, value); } + } + + public virtual bool Required + { + get { return this.IsFlag(TestPropertyFlags.Required); } + set { this.SetFlag(TestPropertyFlags.Required, value); } + } + + public virtual bool Inherit + { + get { return this.IsFlag(TestPropertyFlags.Inheritance); } + set { this.SetFlag(TestPropertyFlags.Inheritance, value); } + } + + public virtual bool Visible + { + get { return this.IsFlag(TestPropertyFlags.Visible); } + set { this.SetFlag(TestPropertyFlags.Visible, value); } + } + + public virtual bool MultipleValues + { + get { return this.IsFlag(TestPropertyFlags.MultipleValues); } + set { this.SetFlag(TestPropertyFlags.MultipleValues, value); } + } +/* + public virtual bool Default + { + get { return this.IsFlag(TestPropertyFlags.DefaultValue); } + set { this.SetFlag(TestPropertyFlags.DefaultValue, value); } + } +*/ + public virtual TestPropertyFlags Flags + { + get { return _flags; } + set { _flags = value; } + } + + //Helpers + protected bool IsFlag(TestPropertyFlags flags) + { + return (_flags & flags) == flags; + } + + protected void SetFlag(TestPropertyFlags flags, bool value) + { + if(value) + _flags |= flags; + else + _flags &= ~flags; + } + } + + //////////////////////////////////////////////////////////////// + // TestInclude (attribute) + // + //////////////////////////////////////////////////////////////// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class TestIncludeAttribute : Attribute + { + //Data + protected string _name; + protected string _file; + protected string _files; + protected string _filter; + + //Constructors + public TestIncludeAttribute() + { + } + + public virtual string Name + { + //Prefix for testcase names + get { return _name; } + set { _name = value; } + } + + public virtual string File + { + get { return _file; } + set { _file = value; } + } + + public virtual string Files + { + //Search Pattern (ie: *.*) + get { return _files; } + set { _files = value; } + } + + public virtual string Filter + { + get { return _filter; } + set { _filter = value; } + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestCase.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestCase.cs new file mode 100644 index 0000000..a001a83 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestCase.cs @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //MethodInfo + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TestCases + // + //////////////////////////////////////////////////////////////// + public abstract class TestCase : TestItem + { + //Data + + //Constructor + public TestCase() + : this(null, null) + { + } + + public TestCase(string name, string desc) + : base(name, desc, TestType.TestCase) + { + } + + //Accessors + protected override TestAttribute CreateAttribute() + { + return new TestCaseAttribute(); + } + + protected virtual TestVariation CreateVariation(TestFunc func) + { + //Override if you have a specific variation class + return new TestVariation(func); + } + + public new virtual TestCaseAttribute Attribute + { + get { return (TestCaseAttribute)base.Attribute; } + set { base.Attribute = value; } + } + + //Helpers + protected override void DetermineChildren() + { + //Delegate (add any nested testcases) + base.DetermineChildren(); + + //Dynamically figure out what functions are variations... + foreach(MethodInfo method in this.GetType().GetMethods()) + { + //Loop through the Attributes for method + foreach(VariationAttribute attr in method.GetCustomAttributes(typeof(VariationAttribute), false/*inhert*/)) + { + //Every method that has a [Variation attribute] = a variation + //Add this variation to our array + TestFunc func = null; + try + { + func = (TestFunc)Delegate.CreateDelegate(typeof(TestFunc), this, method); + } + catch(Exception e) + { + e = new TestFailedException("Variation: '" + method + "' doesn't match expected signature of 'void func()', unable to add that variation.", null, null, e); + HandleException(e); + continue; + } + + TestVariation var = this.CreateVariation(func); + attr.Name = func.Method.Name; //Name is always the function name (invoke) + if(attr.Desc == null) + attr.Desc = attr.Name; + var.Attribute = attr; + AddChild(var); + } + } + + //Sort + //Default sort is based upon IComparable of each item + Children.Sort(); + } + + public TestVariation Variation + { + //Currently executing child + get { return base.CurrentChild as TestVariation; } + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestException.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestException.cs new file mode 100644 index 0000000..11b6648 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestException.cs @@ -0,0 +1,96 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TestSkippedException + // + // Since this is a very common exception and makes it so you + // can catch only the skipped exceptions this is quite useful + // + //////////////////////////////////////////////////////////////// + public class TestSkippedException : TestException + { + //Constructor + public TestSkippedException(string message) + : this(message, false, true, null) + { + } + + public TestSkippedException(string message, object actual, object expected, Exception inner) + : base(TestResult.Skipped, message, actual, expected, inner) + { + } + } + + //////////////////////////////////////////////////////////////// + // TestFailedException + // + //////////////////////////////////////////////////////////////// + public class TestFailedException : TestException + { + //Constructor + public TestFailedException(string message) + : this(message, false, true, null) + { + } + + public TestFailedException(string message, object actual, object expected, Exception inner) + : base(TestResult.Failed, message, actual, expected, inner) + { + } + } + + //////////////////////////////////////////////////////////////// + // TestWarningException + // + //////////////////////////////////////////////////////////////// + public class TestWarningException : TestException + { + //Constructor + public TestWarningException(string message) + : this(message, false, true, null) + { + } + + public TestWarningException(string message, object actual, object expected, Exception inner) + : base(TestResult.Warning, message, actual, expected, inner) + { + } + } + + //////////////////////////////////////////////////////////////// + // TestException + // + // For all other cases, you can just setup the status directly + //////////////////////////////////////////////////////////////// + public class TestException : SystemException + { + //Data + public TestResult Result; + public object Actual; + public object Expected; + + //Constructor + public TestException(TestResult result, string message) + : this(result, message, false, true, null) + { + } + + public TestException(TestResult result, string message, object actual, object expected, Exception inner) + : base(message, inner) + { + //Note: iResult is the variation result (ie: TEST_PASS, TEST_FAIL, etc...) + //Setup the exception + Result = result; + Actual = actual; + Expected = expected; + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestItem.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestItem.cs new file mode 100644 index 0000000..fb0b11c --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestItem.cs @@ -0,0 +1,680 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //BindingFlags +using System.Collections; //IEnumerable +using System.Security.Permissions; +using System.Security; + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TestItem + // + //////////////////////////////////////////////////////////////// + public abstract class TestItem : MarshalByRefObject, ITestItem, IComparable + { + //Data + protected TestItem _parent; + protected TestItems _children; + protected TestAttribute _attribute; + protected TestProperties _metadata; + protected TestType _testtype; + protected TestFlags _testflags; + protected TestItem _currentchild; + + //Constructor + public TestItem(string name, string desc, TestType testtype) + { + if (name != null) + this.Name = name; + if (this.Name == null) + this.Name = this.GetType().Name; + if (desc != null) + this.Desc = desc; + _testtype = testtype; + } + + //Accessors + public virtual TestAttribute Attribute + { + get + { + if (_attribute == null) + _attribute = CreateAttribute(); + return _attribute; + } + set { _attribute = value; } + } + + protected abstract TestAttribute CreateAttribute(); + + //Note: These are just a mere convience to access the attribute values + //for this particular object. Also note that for non-attribute based + //scenarios (dynamic), the attribute class will be created just to hold + //the values + public object Param + { + get { return Attribute.Param; } + set { Attribute.Param = value; } + } + + public object[] Params + { + get { return Attribute.Params; } + set { Attribute.Params = value; } + } + + public int Id + { + get { return Attribute.Id; } + set { Attribute.Id = value; } + } + + public int Order + { + get { return Attribute.Id; } + set { Attribute.Id = value; } + } + + public int Priority + { + get { return Attribute.Priority; } + set { Attribute.Priority = value; } + } + + public string Guid + { + get { return Attribute.Guid; } + set { Attribute.Guid = value; } + } + + public TestType Type + { + get { return _testtype; } + set { _testtype = value; } + } + + public TestFlags Flags + { + get { return _testflags; } + set { _testflags = value; } + } + + public string Name + { + get { return Attribute.Name; } + set { Attribute.Name = value; } + } + + public string Desc + { + get { return Attribute.Desc; } + set { Attribute.Desc = value; } + } + + public string Purpose + { + get { return Attribute.Purpose; } + set { Attribute.Purpose = value; } + } + + public bool Implemented + { + get { return Attribute.Implemented; } + set { Attribute.Implemented = value; } + } + + public string Owner + { + get { return Attribute.Owner; } + set { Attribute.Owner = value; } + } + + public string[] Owners + { + get { return Attribute.Owners; } + set { Attribute.Owners = value; } + } + + public string Version + { + get { return Attribute.Version; } + set { Attribute.Version = value; } + } + + public bool Skipped + { + get { return Attribute.Skipped; } + set { Attribute.Skipped = value; } + } + + public bool Error + { + get { return Attribute.Error; } + set { Attribute.Error = value; } + } + + public bool Manual + { + get { return Attribute.Manual; } + set { Manual = value; } + } + + public SecurityFlags Security + { + get { return Attribute.Security; } + set { Attribute.Security = value; } + } + + public bool Inheritance + { + get { return Attribute.Inheritance; } + set { Attribute.Inheritance = value; } + } + + public string FilterCriteria + { + get { return Attribute.FilterCriteria; } + set { Attribute.FilterCriteria = value; } + } + + public string Language + { + get { return Attribute.Language; } + set { Attribute.Language = value; } + } + + public string[] Languages + { + get { return Attribute.Languages; } + set { Attribute.Languages = value; } + } + + public string Xml + { + get { return Attribute.Xml; } + set { Attribute.Xml = value; } + } + + public bool Stress + { + get { return Attribute.Stress; } + set { Attribute.Stress = value; } + } + + public int Threads + { + get { return Attribute.Threads; } + set { Attribute.Threads = value; } + } + + public int Repeat + { + get { return Attribute.Repeat; } + set { Attribute.Repeat = value; } + } + + public int Timeout + { + get { return Attribute.Timeout; } + set { Attribute.Timeout = value; } + } + + public string Filter + { + get { return Attribute.Filter; } + set { Attribute.Filter = value; } + } + + public TestItem Parent + { + get { return _parent; } + set { _parent = value; } + } + + ITestItems ITestItem.Children + { + get { return this.Children; } + } + + public TestItems Children + { + get + { + //Deferred Creation of the children. + if (_children == null) + _children = new TestItems(); + return _children; + } + set { _children = value; } + } + + public TestItem CurrentChild + { + //Currently executing child + //Note: We do this so that within global functions can know which + //testcase/variation were in, without having to pass this state from execute arround + get { return _currentchild; } + set { _currentchild = value; } + } + + ITestProperties ITestItem.Metadata + { + get { return this.Metadata; } + } + + public TestProperties Metadata + { + get + { + if (_metadata == null) + { + _metadata = new TestProperties(); + + //Note: The default implemention simply exposes the attribute assoicated with + //this test as properties (meta-data). However you could expose any additional + //properties, either attribute, or even alter or override this default altogether. + foreach (PropertyInfo propinfo in this.Attribute.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + //Obtain the name + string name = propinfo.Name; + string desc = name; + object defaultvalue = null; + int id = 1; + TestPropertyFlags flags = TestPropertyFlags.Read; + // Guid guid + + //Obtain meta-data about this property + //This is simply done using an attribute on the attribute property. You could obtain the meta-data + //different and populate the property object yourself, however the default uses attributes for simplicitly + object[] attributes = propinfo.GetCustomAttributes(typeof(TestPropertyAttribute), true/*inhert*/); + + //Note: if the property isn't attributed, it's probably not meant to be included + //as a testproperty, and just a generic helper of some sort + if (attributes == null || attributes.Length < 1) + continue; + + //Loop over attributes + foreach (TestPropertyAttribute attr in attributes) + { + //Name + if (attr.Name != null) + name = attr.Name; + + //Desc + if (attr.Desc != null) + desc = attr.Desc; + + //Id + if (attr.Id >= 0) + id = attr.Id; + + //Guid + //TODO: + // if(attr.Guid) + // guid = attr.Guid; + + //Flags + if (attr.Flags != 0) + flags = attr.Flags; + + //Default + if (attr.DefaultValue != null) + defaultvalue = attr.DefaultValue; + } + + //Obtain the value + object value = null; + try + { + //Note: there could be exceptions thrown accessing the property + value = propinfo.GetValue(this.Attribute, null); + } + catch (Exception e) + { + //Continue processing other properties, even if this one fails for some reason. + TestLog.WriteLineIgnore(e.ToString()); + } + + //If the value is an array, then we override the perfered serialization + bool array = (value != null && value.GetType().HasElementType && value is IEnumerable); + if (array) + flags |= TestPropertyFlags.MultipleValues; + + //If the value is the same as the default value, update the flags + if (value != null && value.Equals(defaultvalue)) + flags |= TestPropertyFlags.DefaultValue; + + //Value (arrays as seperate properties) + TestProperty property = _metadata.Add(name); + property.Value = value; + property.Desc = desc; + property.Flags = flags; + //property.Order = id; + //property.Guid = guid; + } + } + + return _metadata; + } + } + + protected virtual void UpdateAttributes() + { + } + + protected virtual void DetermineChildren() + { + //Dynamically figure out what the contained testcases... + foreach (Type type in TypeEx.GetNestedTypes(this.GetType(), true/*inherit*/)) + { + //Were only interested in objects that inherit from TestCase. + //NOTE: We need to filter other objects, since some internal VB classes throw trying + //to get Attributes + if (!type.IsSubclassOf(typeof(TestCase))) + continue; + + //Every class that has a TestCase attribute, IS a TestCase + foreach (TestCaseAttribute attr in type.GetCustomAttributes(typeof(TestCaseAttribute), false/*inhert*/)) + { + //Create this class (call the constructor with no arguments) + TestCase testcase = (TestCase)Activator.CreateInstance(type); + if (attr.Name == null) + attr.Name = type.Name; + testcase.Attribute = attr; + this.AddChild(testcase); + } + } + + //Sort + //Default sort is based upon IComparable of each item + Children.Sort(); + } + public T GetParamFromParams(int index) + { + if (this.Params == null) + throw new InvalidOperationException("No Params exist to get from"); + if (index >= this.Params.Length) + throw new ArgumentOutOfRangeException("There are " + this.Params.Length + " in the Params collection, the index:" + index.ToString() + " is out of the range"); + object o = this.Params[index]; + if (o.GetType().Equals(typeof(T)) || o.GetType().IsSubclassOf(typeof(T))) + return (T)o; + else + throw new InvalidOperationException("Type:" + o.GetType().FullName + " is incompatible with type: " + typeof(T).FullName); + } + public void AddChild(TestItem child) + { + //Setup the parent + child.Parent = this; + + //Inheritance of attributes + child.Attribute.Parent = this.Attribute; + + //Adjust the Id (if not set) + if (child.Id <= 0) + child.Id = Children.Count + 1; + + //Only add implemented items + //Note: we still increment id counts, to save the 'spot' once they are implemented + if (child.Implemented) + { + //Determine any children of this node (before adding it) + //Note: We don't call 'determinechildren' from within the (testcase) constructor + //since none of the attributes/properties are setup until now. So as soon as we + //setup that information then we call determinechildren which when implemented + //for dynamic tests can now look at those properties (otherwise they wouldn't be setup + //until after the constructor returns). + child.DetermineChildren(); + + //Add it to our list... + Children.Add(child); + } + } + + protected TestItem FindChild(Type type) + { + //Compare + if (type == this.GetType()) + return this; + + //Otherwise recursive + foreach (TestItem item in this.Children) + { + TestItem found = item.FindChild(type); + if (found != null) + return found; + } + + return null; + } + + public virtual void Init() + { + //Note: This version is the only override-able Init, since the other one + //automatically handles the exception you might throw from this function + //Note: If you override this function, (as with most overrides) make sure you call the base. + } + + TestResult ITestItem.Init() + { + //Enter + OnEnter(TestMethod.Init); + + TestResult result = TestResult.Passed; + if (Parent != null) + Parent.CurrentChild = this; + + //Obtain the error object (to prime it) + try + { + if (_testtype == TestType.TestModule) + { + //Clear any previous existing info (in the static class). + //Note: We actually have to clear these "statics" since they are not cleaned up + //until the process exits. Which means that if you run again, in an apartment model + //thread it will try to release these when setting them which is not allowed to + //call a method on an object created in another apartment + if (TestLog.Internal == null) + { + TestLog.Internal = new LtmContext() as ITestLog; + TestInput.Properties = new TestProps(new LtmContext() as ITestProperties); + } + } + } + catch (Exception e) + { + result = HandleException(e); + } + + //NOTE: Since exceptions are a way-of-life in COOL, and the fact that they are just + //caught by the runtime before passed back to LTM, we lose the entire stack and just + //an unknown error code is returned. + + //To help solve this we will wrap the call in a try catch and actually + //log the exception to the LTM output window... + try + { + //Skipped + if (this.Skipped || !this.Implemented) + { + result = TestResult.Skipped; + } + else + { + this.Init(); + } + } + catch (Exception e) + { + result = HandleException(e); + } + + //Leave + OnLeave(TestMethod.Init); + return result; + } + + public virtual void Execute() + { + //Note: This version is the only override-able version, since the other one + //automatically handles the exception you might throw from this function + //Note: If you override this function, (as with most overrides) make sure you call the base. + } + + [EnvironmentPermission(SecurityAction.Assert, Unrestricted = true)] + [SecuritySafeCritical] + TestResult ITestItem.Execute() + { + //Enter + OnEnter(TestMethod.Execute); + TestResult result = TestResult.Passed; + + try + { + //Skipped + if (this.Skipped || !this.Implemented) + result = TestResult.Skipped; + else + this.Execute(); + } + catch (Exception e) + { + result = HandleException(e); + } + + //Leave + OnLeave(TestMethod.Execute); + return result; + } + + [SecurityCritical] + public override object InitializeLifetimeService() + { + System.Runtime.Remoting.Lifetime.ILease lease = (System.Runtime.Remoting.Lifetime.ILease)base.InitializeLifetimeService(); + if (lease.CurrentState == System.Runtime.Remoting.Lifetime.LeaseState.Initial) + { + lease.InitialLeaseTime = TimeSpan.FromMinutes(30); + lease.RenewOnCallTime = TimeSpan.FromMinutes(30); + } + return null; + } + + public virtual void Terminate() + { + //Note: This version is the only override-able Terminate, since the other one + //automatically handles the exception you might throw from this function + //Note: If you override this function, (as with most overrides) make sure you call the base. + } + + TestResult ITestItem.Terminate() + { + //Enter + OnEnter(TestMethod.Terminate); + TestResult result = TestResult.Passed; + + //NOTE: Since exceptions are a way-of-life in C#, and the fact that they are just + //caught by the runtime before passed back to LTM, we lose the entire stack and just + //an unknown error code is returned. + + //To help solve this we will wrap the call in a try catch and actually + //log the exception to the LTM output window... + try + { + //Skipped + if (this.Skipped || !this.Implemented) + result = TestResult.Skipped; + else + this.Terminate(); + + //Before exiting make sure we reset our CurChild to null, to prevent incorrect uses + if (Parent != null) + Parent.CurrentChild = null; + } + catch (Exception e) + { + HandleException(e); + } + + try + { + //Clear any previous existing info (in the static class). + if (_testtype == TestType.TestModule) + { + //Note: We actually have to clear these "statics" since they are not cleaned up + //until the process exits. Which means that if you run again, in an apartment model + //thread it will try to release these when setting them which is not allowed to + //call a method on an object created in another apartment + TestInput.Dispose(); + TestLog.Dispose(); + } + } + catch (Exception e) + { + result = HandleException(e); + } + + //This is also a good point to hint to the GC to free up any unused memory + //at the end of each TestCase and the end of each module. + GC.Collect(); + GC.WaitForPendingFinalizers(); + + //Leave + OnLeave(TestMethod.Terminate); + return result; + } + + protected virtual void OnEnter(TestMethod method) + { + //TODO: Events + } + + protected virtual void OnLeave(TestMethod method) + { + //TODO: Events + } + + protected virtual TestResult HandleException(Exception e) + { + //Note: override this if your product has specilized exceptions (ie: nesting or collections) + //that you need to recurse over of print out differently + return TestLog.HandleException(e); + } + + public virtual int CompareTo(object o) + { + //Default comparison, name based. + return this.Name.CompareTo(((TestItem)o).Name); + } + } + + //////////////////////////////////////////////////////////////// + // TestItems + // + //////////////////////////////////////////////////////////////// + [Serializable] + public class TestItems : ArrayList, ITestItems + { + //Data + + //Constructor + public TestItems() + { + } + + //ITestItems + int ITestItems.Count + { + get { return this.Count; } + } + + ITestItem ITestItems.GetItem(int index) + { + return (ITestItem)this[index]; + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestLoader.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestLoader.cs new file mode 100644 index 0000000..c3b1be8 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestLoader.cs @@ -0,0 +1,147 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; //ArrayList +using System.Reflection; //Assembly + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TestLoader + // + //////////////////////////////////////////////////////////////// + public class TestLoader : ITestLoader + { + //Data + protected String _guid = null; + protected String _name = null; + protected String _desc = null; + protected TestProperties _metadata = null; + + //Constructor + public TestLoader() + { + _name = this.GetType().FullName; + _guid = _name; + } + + // Input (get/set) + public ITestProperties Properties + { + get { return TestInput.Properties.Internal; } + set { TestInput.Properties = new TestProps(value); } + } + + // Logging (get/set) + public ITestLog Log + { + get { return TestLog.Internal; } + set { TestLog.Internal = value; } + } + + //Accessors + public string Guid + { + get { return _guid; } + set { _guid = value; } + } + + public string Name + { + get { return _name; } + set { _name = value; } + } + + public string Desc + { + get { return _desc; } + set { _desc = value; } + } + + public ITestProperties Metadata + { + get { return _metadata; } + } + + //Enumeration + public String[] Enumerate(String assemblyname) + { + ArrayList array = new ArrayList(); + Assembly assembly = Assembly.LoadFrom(assemblyname); + foreach(Type type in assembly.GetTypes()) + { + //Were only looking for classes that inherit from TestModel + if(!type.IsSubclassOf(typeof(TestModule))) + continue; + + //Those classes must have the [TestModule] attribute to be considered + if(type.GetCustomAttributes(typeof(TestModuleAttribute), false/*inhert*/).Length == 0) + continue; + + //Found valid TestModule class + array.Add(type.FullName); + } + + return (String[])array.ToArray(typeof(String)); + } + + // Initialization + public void Init() + { + try + { + ; + } + catch(Exception e) + { + TestLog.HandleException(e); + } + } + + public void Terminate() + { + try + { + //Clear any previous existing info (in the static class). + + //Note: We actually have to clear these "statics" since they are not cleaned up + //until the process exits. Which means that if you run again, in an apartment model + //thread it will try to release these when setting them which is not allowed to + //call a method on an object created in another apartment + TestInput.Dispose(); + TestLog.Dispose(); + } + catch(Exception) + { +// TestLog.HandleException(e); + } + } + + public ITestItem CreateTest(String assembly, String type) + { + try + { +// this.Log = new LtmContext() as ITestLog; +// this.Properties = new LtmContext() as ITestProperties; + + //Create an instance + //Note: Assembly.CreateInstance returns null for type not found + TestModule test = (TestModule)Assembly.LoadFrom(assembly).CreateInstance(type); + if(test == null) + throw new TypeLoadException( + String.Format("Unable to find type: '{0}' in assembly: '{1}'", type, assembly) + ); + return test; + } + catch(Exception e) + { + TestLog.HandleException(e); + throw new TestFailedException(e.Message, null, null, e); + } + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestLog.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestLog.cs new file mode 100644 index 0000000..beaa5a6 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestLog.cs @@ -0,0 +1,531 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Diagnostics; //TraceListener +using System.Reflection; //TargetInvocationException +using System.IO; //TextWriter +using System.Text; //Encoding + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TraceLevel + // + //////////////////////////////////////////////////////////////// + public enum TraceLevel + { + None, + Info, + Default, + Debug, + All, + }; + + //////////////////////////////////////////////////////////////// + // TestLog + // + //////////////////////////////////////////////////////////////// + public class TestLog + { + //Data + static ITestLog _internal; + static TraceLevel _level = TraceLevel.Default; + static TestLogAssertHandler _asserthandler = null; + + //Accessors + static public ITestLog Internal + { + set + { + _internal = value; + DisableAsserts(); + } + get + { +// if(_internal == null) +// _internal = (new LtmContext() as ITestLog); + return _internal; + } + } + + static public TraceLevel Level + { + get { return _level; } + set { _level = value; } + } + + static public TestLogAssertHandler AssertHandler + { + get { return _asserthandler; } + } + + static internal void Dispose() + { + //Reset the info. + _internal = null; + if(_asserthandler != null) + Debug.Listeners.Remove(_asserthandler); + _asserthandler = null; + } + + //Helpers + static public string NewLine + { + get { return "\n"; } + } + + static public bool WillTrace(TraceLevel level) + { + return(_level >= level); + } + + static public void Write(object value) + { + Write(TestLogFlags.Text, StringEx.ToString(value)); + } + + static public void WriteLine(object value) + { + WriteLine(TestLogFlags.Text, StringEx.ToString(value)); + } + + static public void WriteLine() + { + WriteLine(TestLogFlags.Text, null); + } + + static public void Write(string text) + { + Write(TestLogFlags.Text, text); + } + + static public void Write(string text, params object[] args) + { + //Delegate + Write(TestLogFlags.Text, String.Format(text, args)); + } + + static public void WriteLine(string text) + { + WriteLine(TestLogFlags.Text, text); + } + + static public void WriteLine(string text, params object[] args) + { + //Delegate + WriteLine(String.Format(text, args)); + } + + static public void Write(char[] value) + { + WriteLine(TestLogFlags.Text, new string(value)); + } + + static public void WriteLine(char[] value) + { + WriteLine(TestLogFlags.Text, new string(value)); + } + + static public void WriteXml(string text) + { + Write(TestLogFlags.Xml, text); + } + + static public void WriteRaw(string text) + { + Write(TestLogFlags.Raw, text); + } + + static public void WriteIgnore(string text) + { + Write(TestLogFlags.Ignore, text); + } + + static public void WriteLineIgnore(string text) + { + WriteLine(TestLogFlags.Ignore, text); + } + + static public void Write(TestLogFlags flags, string text) + { + if(Internal != null) + Internal.Write(flags, text); + else + Console.Write(text); + } + + static public void WriteLine(TestLogFlags flags, string text) + { + if(Internal != null) + Internal.WriteLine(flags, text); + else + Console.WriteLine(text); + } + + static public void Trace(String value) + { + Trace(TraceLevel.Default, value); + } + + static public void TraceLine(String value) + { + TraceLine(TraceLevel.Default, value); + } + + static public void TraceLine() + { + TraceLine(TraceLevel.Default, null); + } + + static public void Trace(TraceLevel level, String value) + { + if(WillTrace(level)) + Write(TestLogFlags.Trace | TestLogFlags.Ignore, value); + } + + static public void TraceLine(TraceLevel level, String value) + { + if (WillTrace(level)) + Write(TestLogFlags.Trace | TestLogFlags.Ignore, value + TestLog.NewLine); + } + + static public void TraceLine(TraceLevel level) + { + TraceLine(level, null); + } + + static public bool Compare(bool equal, string message) + { + if(equal) + return true; + return Compare(false, true, message); + } + + static public bool Compare(object actual, object expected, string message) + { + if(InternalEquals(actual, expected)) + return true; + + //Compare not only compares but throws - so your test stops processing + //This way processing stops upon the first error, so you don't have to check return + //values or validate values afterwards. If you have other items to do, then use the + //TestLog.Equals instead of TestLog.Compare + throw new TestFailedException(message, actual, expected, null); + + } + + static public bool Equals(object actual, object expected, string message) + { + //Equals is identical to Compare, except that Equals doesn't throw. + //i.e. the test wants to record the failure and continue to do other things + if (InternalEquals(actual, expected)) + return true; + + StackTrace stackTrace = new StackTrace(true); + TestLog.Error(TestResult.Failed, actual, expected, null, message, stackTrace.ToString(), null, 0); + + return false; + } + + static public bool Warning(bool equal, string message) + { + return Warning(equal, true, message, null); + } + + static public bool Warning(object actual, object expected, string message) + { + return Warning(actual, expected, message, null); + } + + static public bool Warning(object actual, object expected, string message, Exception inner) + { + //See if these are equal + bool equal = InternalEquals(actual, expected); + if(equal) + return true; + + try + { + throw new TestWarningException(message, actual, expected, inner); + } + catch(Exception e) + { + //Warning should continue - not halt test progress + TestLog.HandleException(e); + return false; + } + } + + static public bool Skip(string message) + { + //Delegate + return Skip(true, message); + } + + static public bool Skip(bool skip, string message) + { + if(skip) + throw new TestSkippedException(message); + return false; + } + + static internal bool InternalEquals(object actual, object expected) + { + //Handle null comparison + if(actual == null && expected == null) + return true; + else if(actual == null || expected == null) + return false; + else if(Convert.IsDBNull(expected)) + return Convert.IsDBNull(actual); + + //Otherwise + return expected.Equals(actual); + } + + static public void Error(TestResult result, object actual, object expected, string source, string message, string stack, String filename, int lineno) + { + //Log the error + if(Internal != null) + { + //ITestConsole.Log + //TODO: We could pass in ints - if the objects are convertable to int, ie: they may + //actually be comparing two values, not complex objects. + Internal.Error( result, + TestLogFlags.Text, //flags + StringEx.Format(actual), //actual + StringEx.Format(expected), //expected + source, //source + message, //message + stack, //stack + filename, //filename + lineno //line + ); + } + else + { + //We call IError::Compare, which logs the error AND increments the error count... + Console.WriteLine("Message:\t" + message); + Console.WriteLine("Source:\t\t" + source); + Console.WriteLine("Expected:\t" + expected); + Console.WriteLine("Received:\t" + actual); + Console.WriteLine("Details:" + TestLog.NewLine + stack); + Console.WriteLine("File:\t\t" + filename); + Console.WriteLine("Line:\t\t" + lineno); + } + } + + public static TestResult HandleException(Exception e) + { + TestResult result = TestResult.Failed; + Exception inner = e; + + //If reflection was used check for test skipped/passed exception + while(inner is TargetInvocationException) + { + inner = inner.InnerException; + } + + if (!(inner is TestException) || + ((inner as TestException).Result != TestResult.Skipped && + (inner as TestException).Result != TestResult.Passed && + (inner as TestException).Result != TestResult.Warning)) + inner = e; //start over so we do not loose the stack trace + + while(inner != null) + { + //Need to handle Loader exceptions seperatly + if(inner is ReflectionTypeLoadException) + { + //Recurse + foreach(Exception loaderexception in ((ReflectionTypeLoadException)inner).LoaderExceptions) + HandleException(loaderexception); + } + + //Source + String source = inner.Source; + + //Message + String message = inner.Message; + if(inner != e) + message = "Inner Exception -> " + message; + + //Expected / Actual + Object actual = inner.GetType(); + Object expected = null; + String details = inner.StackTrace; + String filename = null; + int line = 0; + if(inner is TestException) + { + TestException testinner = (TestException)inner; + + //Setup more meaningful info + actual = testinner.Actual; + expected = testinner.Expected; + result = testinner.Result; + switch(result) + { + case TestResult.Passed: + case TestResult.Skipped: + WriteLine(message); + return result; //were done + }; + } + + //Log + TestLog.Error(result, actual, expected, source, message, details, filename, line); + + //Next + inner = inner.InnerException; + } + + return result; + } + + private static void DisableAsserts() + { + try + { + //Turn on assert dialog boxes. + if(_asserthandler == null) + { + //Disable the assert pop-up dialogs + foreach(TraceListener listener in Debug.Listeners) + { + if(listener is DefaultTraceListener) + { + //Disable assert dialogs popups + ((DefaultTraceListener)listener).AssertUiEnabled = false; + } + } + + //Add our own handler + // Note: We can't simply set DefaultTeaceListener.AssertUiEnabled = false, since that + // actually disables the assertions, and we no longer see them. We need to count them + // as errors and continue in Ltm + _asserthandler = new TestLogAssertHandler(); + Debug.Listeners.Add(_asserthandler); + } + } + catch (Exception e) + { + //If disabling the asserts fails, simply log it and move on. + //No reason to hold up the test or block error logging for that, and don't count it as an error + TestLog.WriteLine("Unable to disable asserts:" + TestLog.NewLine + e); + } + } + } + + //////////////////////////////////////////////////////////////// + // TestLogWriter + // + //////////////////////////////////////////////////////////////// + public class TestLogWriter : TextWriter + { + //Data + protected TestLogFlags _flags = TestLogFlags.Text; + + //Constructor + public TestLogWriter(TestLogFlags flags) + { + _flags = flags; + } + + //Overrides + public override void Write(char ch) + { + //A subclass must minimally implement the Write(Char) method. + Write(ch.ToString()); + } + + public override void Write(string text) + { + //Delegate + TestLog.Write(_flags, text); + } + + public override void Write(char[] ch) + { + //Note: This is a workaround the TextWriter::Write(char[]) that incorrectly + //writes 1 char at a time, which means \r\n is written sperately and then gets fixed + //up to be two carriage returns! + if (ch != null) + { + StringBuilder builder = new StringBuilder(ch.Length); + builder.Append(ch); + Write(builder.ToString()); + } + } + + public override void WriteLine(string strText) + { + Write(strText + this.NewLine); + } + + public override void WriteLine() + { + //Writes a line terminator to the text stream. + //The default line terminator is a carriage return followed by a line feed ("\r\n"), + //but this value can be changed using the NewLine property. + Write(this.NewLine); + } + + public override Encoding Encoding + { + get { return Encoding.Unicode; } + } + } + + //////////////////////////////////////////////////////////////// + // TestLogAssertHandler + // + //////////////////////////////////////////////////////////////// + public class TestLogAssertHandler : DefaultTraceListener + { + //Data + protected bool _shouldthrow = false; + + //Constructor + public TestLogAssertHandler() + { + } + + //Accessors + public virtual bool ShouldThrow + { + get { return _shouldthrow; } + set { _shouldthrow = value; } + } + + //Overloads + public override void Fail(string message, string details) + { + //Handle the assert, treat it as an error. + Exception e = new TestException(TestResult.Assert, message, details, null, null); + e.Source = "Debug.Assert"; + + //Note: We don't throw the exception (by default), since we want to continue on Asserts + //(as many of them are benign or simply a valid runtime error). + if(this.ShouldThrow) + throw e; + + StackTrace stack = new StackTrace(); + TestLog.Error(TestResult.Assert, details, null, "Debug.Assert", message, stack.ToString(), null, 0); + } + + public override void Write(string strText) + { + //TODO: Product tracing + } + + public override void WriteLine(string strText) + { + //TODO: Product tracing + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestModule.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestModule.cs new file mode 100644 index 0000000..0fb3717 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestModule.cs @@ -0,0 +1,244 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //Assembly +using System.IO; //Directory +using System.Collections; //ArrayList +using System.Security; //AllowPartiallyTrustedCallersAttribute + +[assembly: AllowPartiallyTrustedCallersAttribute] +[assembly: SecurityRules(SecurityRuleSet.Level1, SkipVerificationInFullTrust = true)] +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TestModule + // + //////////////////////////////////////////////////////////////// + public abstract class TestModule : TestItem + { + //Default Loader + public class ModuleLoader : TestLoader + { + } + + //Data + protected TestSpec _testspec; + protected ArrayList _includes = new ArrayList(); + + //Constructors + public TestModule() + : this(null, null) + { + } + + public TestModule(string name, string desc) + : base(name, desc, TestType.TestModule) + { + this.Guid = GetType().ToString(); + + //Population + DetermineChildren(); + DetermineIncludes(); + DetermineFilters(); + } + + //Accessors + public new virtual TestModuleAttribute Attribute + { + get { return (TestModuleAttribute)base.Attribute;} + set { base.Attribute = value; } + } + + public string Created + { + get { return Attribute.Created; } + set { Attribute.Created = value; } + } + + public string Modified + { + get { return Attribute.Modified; } + set { Attribute.Modified = value; } + } + + public TestSpec TestSpec + { + get + { + //Deferred Creation + if(_testspec == null) + _testspec = new TestSpec(this); + return _testspec; + } + } + + public override void Init() + { + //Delegate + base.Init(); + + //Includes (aggregate tests) + foreach(TestModule include in _includes) + include.Init(); + } + + public override void Terminate() + { + //Includes (aggregate tests) + foreach(TestModule include in _includes) + include.Terminate(); + + //Delegate + base.Terminate(); + } + + //Helpers + protected override TestAttribute CreateAttribute() + { + //Obtain attributes + //Note: All the other levels, fill in the attribute (when enumerating), however + //the module doesn't have anyone who fills it in, so just obtain it directly + object[] attributes = GetType().GetCustomAttributes(typeof(TestModuleAttribute), false/*inhert*/); + if(attributes != null && attributes.Length>0) + return (TestModuleAttribute)attributes[0]; + + //Otherwise + return new TestModuleAttribute(); + } + + protected override void DetermineChildren() + { + base.DetermineChildren(); + } + + protected virtual void DetermineIncludes() + { + try + { + //[TestInclude] attributes + foreach(TestIncludeAttribute attr in this.GetType().GetCustomAttributes(typeof(TestIncludeAttribute), false/*inhert*/)) + { + //Files (either specified or search pattern) + string[] filenames = new string[]{attr.File}; + if(attr.Files != null) + filenames = Directory.GetFiles(".", attr.Files); + + //Loop through the files + foreach(string filename in filenames) + { + try + { + //Note: Prevent recursion - (ie: ignore this assembly) + Assembly assembly = Assembly.LoadFrom(filename); + if(assembly == Assembly.GetAssembly(this.GetType())) + continue; + + //Find the TestModule(s) (from the specified assembly) + foreach(Type type in assembly.GetTypes()) + { + //Were only interested in objects that inherit from TestModule + if(type.IsSubclassOf(typeof(TestModule)) ) + { + //Create this module (call the constructor with no arguments) + TestModule testmodule = (TestModule)Activator.CreateInstance(type); + + //Filter the module (as specified) + if(attr.Filter != null) + testmodule.TestSpec.ApplyFilter(testmodule, attr.Filter); + + //Add the children + if(testmodule.Children.Count > 0) + { + //Determine what prefix to use (to distinguish testcases) + string prefix = testmodule.Name; + if(attr.Name != null) + prefix = attr.Name; + + //Add all the testcases + //TODO: What about ModuleInit/Terminate for this group? + _includes.Add(testmodule); + foreach(TestItem testcase in testmodule.Children) + { + if(prefix != null && prefix.Length > 0) + testcase.Name = (prefix + "." + testcase.Name); + Children.Add(testcase); + } + } + } + } + } + catch(Exception e) + { + //We might not be able to load all assemblies in the directory (ie: *.dll) + //so simply move onto the next assembly... + TestLog.WriteLineIgnore(e.ToString()); + } + } + } + } + catch(Exception e) + { + //Make this easier to debug + TestLog.WriteLineIgnore(e.ToString()); + throw e; + } + } + + protected virtual void DetermineFilters() + { + //Override if you don't want filtering, or want to provider your own mechanism + try + { + // /Filter + if(TestInput.Filter != null) + this.TestSpec.ApplyFilter(this, this.FilterScope(TestInput.Filter)); + + // /MaxPriority + string maxprifilter = "//TestVariation[@Priority<='{0}']"; + if(TestInput.MaxPriority != null) + this.TestSpec.ApplyFilter(this, String.Format(maxprifilter, TestInput.MaxPriority)); + + // Module [TestFilter(...)] + if(this.Filter != null) + this.TestSpec.ApplyFilter(this, this.FilterScope(this.Filter)); + + // TestCase [TestFilter(...)] + foreach(TestItem testcase in this.Children) + { + if(testcase.Filter != null) + this.TestSpec.ApplyFilter(testcase, this.FilterScope(testcase.Filter)); + } + } + catch(Exception e) + { + //Make this easier to debug + TestLog.WriteLineIgnore(e.ToString()); + throw e; + } + } + + protected virtual string FilterScope(string xpath) + { + //Basically we want to allow either simply filtering at the variation node (ie: no scope), + //in which case we'll just add the 'assumed' scope, or allow filtering at any level. + //We also want to be consitent with the XmlDriver in which all filters are predicates only. + string varfilter = "//TestVariation[{0}]"; + if(xpath != null) + { + xpath = xpath.Trim(); + if(xpath.Length > 0) + { + //Add the Variation Scope, if no scope was specified + if(xpath[0] != '/') + xpath = String.Format(varfilter, xpath); + } + } + + return xpath; + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestParser.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestParser.cs new file mode 100644 index 0000000..35d1a2c --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestParser.cs @@ -0,0 +1,213 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Text; //StringBuilder +using System.Collections; //HashTable + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // KeywordParser + // + //////////////////////////////////////////////////////////////// + public class KeywordParser + { + //Enum + protected enum PARSE + { + Initial, + Keyword, + Equal, + Value, + SingleBegin, + SingleEnd, + DoubleBegin, + DoubleEnd, + End + }; + + //Accessors + //Note: You can override these if you want to leverage our parser (inherit), and change + //some of the behavior (without reimplementing it). + static Tokens DefaultTokens = new Tokens(); + public class Tokens + { + public string Equal = "="; + public string Seperator = ";"; + public string SingleQuote = "'"; + public string DoubleQuote = "\""; + }; + + + //Methods + static public Hashtable ParseKeywords(string str) + { + return ParseKeywords(str, DefaultTokens); + } + + static public Hashtable ParseKeywords(string str, Tokens tokens) + { + PARSE state = PARSE.Initial; + int index = 0; + int keyStart = 0; + InsensitiveHashtable keywords = new InsensitiveHashtable(5); + string key = null; + + if(str != null) + { + StringBuilder builder = new StringBuilder(str.Length); + for(; index < str.Length; index ++) + { + char ch = str[index]; + switch(state) + { + case PARSE.Initial: + if(Char.IsLetterOrDigit(ch)) + { + keyStart = index; + state = PARSE.Keyword; + } + break; + + case PARSE.Keyword: + if(tokens.Seperator.IndexOf(ch) >= 0) + { + state = PARSE.Initial; + } + else if(tokens.Equal.IndexOf(ch) >= 0) + { + //Note: We have a case-insentive hashtable so we don't have to lowercase + key = str.Substring(keyStart, index - keyStart).Trim(); + state = PARSE.Equal; + } + break; + + case PARSE.Equal: + if(Char.IsWhiteSpace(ch)) + break; + builder.Length = 0; + + //Note: Since we allow you to alter the tokens, these are not + //constant values, so we cannot use a switch statement... + if(tokens.SingleQuote.IndexOf(ch) >= 0) + { + state = PARSE.SingleBegin; + } + else if(tokens.DoubleQuote.IndexOf(ch) >= 0) + { + state = PARSE.DoubleBegin; + } + else if(tokens.Seperator.IndexOf(ch) >= 0) + { + keywords.Update(key, String.Empty); + state = PARSE.Initial; + } + else + { + state = PARSE.Value; + builder.Append(ch); + } + break; + + case PARSE.Value: + if(tokens.Seperator.IndexOf(ch) >= 0) + { + keywords.Update(key, (builder.ToString()).Trim()); + state = PARSE.Initial; + } + else + { + builder.Append(ch); + } + break; + + case PARSE.SingleBegin: + if(tokens.SingleQuote.IndexOf(ch) >= 0) + state = PARSE.SingleEnd; + else + builder.Append(ch); + break; + + case PARSE.DoubleBegin: + if(tokens.DoubleQuote.IndexOf(ch) >= 0) + state = PARSE.DoubleEnd; + else + builder.Append(ch); + break; + + case PARSE.SingleEnd: + if(tokens.SingleQuote.IndexOf(ch) >= 0) + { + state = PARSE.SingleBegin; + builder.Append(ch); + } + else + { + keywords.Update(key, builder.ToString()); + state = PARSE.End; + goto case PARSE.End; + } + break; + + case PARSE.DoubleEnd: + if(tokens.DoubleQuote.IndexOf(ch) >= 0) + { + state = PARSE.DoubleBegin; + builder.Append(ch); + } + else + { + keywords.Update(key, builder.ToString()); + state = PARSE.End; + goto case PARSE.End; + } + break; + + case PARSE.End: + if(tokens.Seperator.IndexOf(ch) >= 0) + { + state = PARSE.Initial; + } + break; + + default: + throw new TestFailedException("Unhandled State: " + StringEx.ToString(state)); + } + } + + switch(state) + { + case PARSE.Initial: + case PARSE.Keyword: + case PARSE.End: + break; + + case PARSE.Equal: + keywords.Update(key, String.Empty); + break; + + case PARSE.Value: + keywords.Update(key, (builder.ToString()).Trim()); + break; + + case PARSE.SingleBegin: + case PARSE.DoubleBegin: + case PARSE.SingleEnd: + case PARSE.DoubleEnd: + keywords.Update(key, builder.ToString()); + break; + + default: + throw new TestFailedException("Unhandled State: " + StringEx.ToString(state)); + }; + } + return keywords; + } + } +} + + diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestProperties.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestProperties.cs new file mode 100644 index 0000000..da12deb --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestProperties.cs @@ -0,0 +1,481 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; //IEnumerator +using System.Collections.Generic; //List +using System.Security.Permissions; +using System.Security; + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TestInput + // + //////////////////////////////////////////////////////////////// + public class TestInput + { + //Data + static TestProps _properties; + static String _initstring; + static String _commandline; + + //Constructor + static public TestProps Properties + { + get + { + //Delegate for now... + if(_properties == null) + TestInput.Properties = new TestProps(new TestProperties()); + return _properties; + } + [EnvironmentPermission(SecurityAction.Assert, Unrestricted = true)] + [SecuritySafeCritical] + set + { + //TODO: This code should eventually be in ltm and then we simply only use it's collection + //_properties = value; + + //For now, we do our own logic + //Clear between each run + _properties = new TestProps(new TestProperties()); + + //InitString keywords + String initstring = value["Alias/InitString"]; + if(initstring != null) + { + _properties["Alias/InitString"] = initstring; + //Keywords + Hashtable keywords = KeywordParser.ParseKeywords(initstring); + foreach(String key in keywords.Keys) + _properties["Alias/InitString/" + key] = keywords[key] as String; + } + + //CommandLine + String commandline = value["CommandLine"] ?? Environment.CommandLine; + if(commandline != null) + { + _properties["CommandLine"] = commandline; + //Options + KeywordParser.Tokens tokens = new KeywordParser.Tokens(); + tokens.Equal = " "; + tokens.Seperator = "/"; + Hashtable options = KeywordParser.ParseKeywords(commandline, tokens); + foreach (String key in options.Keys) + _properties["CommandLine/" + key] = options[key] as String; + } + } + } + + static internal void Dispose() + { + //Reset the info. + //Since this is a static class, (to make it simplier to access from anywhere in your code) + //we need to reset this info everytime a test is run - so if you don't select an alias + //the next time it doesn't use the previous alias setting - ie: ProviderInfo doesn't + //get called when no alias is selected... + _properties = null; + _initstring = null; + _commandline = null; + } + + static public string InitString + { + get + { + //Useful typed getter + if(_initstring == null) + _initstring = TestInput.Properties["Alias/InitString"]; + return _initstring; + } + } + + static public string CommandLine + { + get + { + //Useful typed getter + if (_commandline == null) + _commandline = TestInput.Properties["CommandLine"]; + return _commandline; + } + } + + static public String Filter + { + get + { + if(TestInput.Properties != null) + return TestInput.Properties["CommandLine/Filter"]; + return null; + } + } + + static public String MaxPriority + { + get + { + if(TestInput.Properties != null) + return TestInput.Properties["CommandLine/MaxPriority"]; + return null; + } + } + } + + //////////////////////////////////////////////////////////////// + // TestProps + // + //////////////////////////////////////////////////////////////// + public class TestProps : ITestProperties, IEnumerable, IEnumerator + { + //Data + protected ITestProperties _internal; + protected int _enum = -1; + + //Constructor + public TestProps(ITestProperties properties) + { + _internal = properties; + } + + //Accessors + public virtual ITestProperties Internal + { + get { return _internal; } + } + + public virtual int Count + { + get + { + if(_internal != null) + return _internal.Count; + return 0; + } + } + + public virtual TestProp this[int index] + { + get + { + ITestProperty property = _internal.GetItem(index); + if(property != null) + return new TestProp(property); + return null; + } + } + + public virtual String this[String name] + { + get + { + ITestProperty property = _internal.Get(name); + if(property != null) + return StringEx.ToString(property.Value); + return null; + } + set + { + this.Add(name).Value = value; + } + } + + public virtual TestProp Get(String name) + { + ITestProperty property = _internal.Get(name); + if(property != null) + return new TestProp(property); + return null; + } + + public virtual TestProp Add(String name) + { + return new TestProp(_internal.Add(name)); + } + + public virtual void Remove(String name) + { + _internal.Remove(name); + } + + public virtual IEnumerator GetEnumerator() + { + return this; + } + + public virtual bool MoveNext() + { + if(_enum+1 >= this.Count) + return false; + _enum++; + return true; + } + + public virtual Object Current + { + get { return this[_enum]; } + } + + public virtual void Reset() + { + _enum = -1; + } + + public virtual void Clear() + { + if(_internal != null) + _internal.Clear(); + } + + ITestProperty ITestProperties.Add(String name) + { + return _internal.Add(name); + } + + ITestProperty ITestProperties.Get(String name) + { + return _internal.Get(name); + } + + ITestProperty ITestProperties.GetItem(int index) + { + return _internal.GetItem(index); + } + } + + //////////////////////////////////////////////////////////////// + // TestProp + // + //////////////////////////////////////////////////////////////// + public class TestProp : ITestProperty, IEnumerable + { + //Data + protected ITestProperty _internal; + + //Constructor + public TestProp(ITestProperty property) + { + _internal = property; + } + + //Accessors + public virtual ITestProperty Internal + { + get { return _internal; } + } + + public virtual String Name + { + get { return _internal.Name; } + } + + public virtual String Desc + { + get { return _internal.Desc; } + } + + public virtual TestPropertyFlags Flags + { + get { return _internal.Flags; } + set { _internal.Flags = value; } + } + + public virtual Object Value + { + get { return _internal.Value; } + set { _internal.set_Value(ref value); } + } + + public virtual TestProps Children + { + get { return new TestProps(_internal.Children); } + } + + public virtual IEnumerator GetEnumerator() + { + return this.Children; + } + + void ITestProperty.set_Value(ref object value) + { + _internal.set_Value(ref value); + } + + ITestProperties ITestProperty.Children + { + get { return _internal.Children; } + } + + ITestProperties ITestProperty.Metadata + { + get { return _internal.Metadata; } + } + + } + + //////////////////////////////////////////////////////////////// + // TestProperty + // + //////////////////////////////////////////////////////////////// + [Serializable] + public class TestProperty : ITestProperty + { + //Data + protected String _name = null; + protected String _desc = null; + protected Object _value = null; + protected TestPropertyFlags _flags = 0; + protected TestProperties _metadata = null; + protected TestProperties _children = null; + + //Constructor + public TestProperty(String name, Object value) + { + _name = name; + _value = value; + } + + //Accessors + public string Name + { + get { return _name; } + set { _name = value; } + } + + public string Desc + { + get { return _desc; } + set { _desc = value; } + } + + public TestPropertyFlags Flags + { + get { return _flags; } + set { _flags = value; } + } + + public object Value + { + get { return _value; } + set { _value = value; } + } + + public void set_Value(ref object value) + { + _value = value; + } + + public ITestProperties Metadata + { + get { return _metadata; } + } + + public ITestProperties Children + { + get { return _children; } + } + } + + //////////////////////////////////////////////////////////////// + // TestProperties + // + //////////////////////////////////////////////////////////////// + [Serializable] + public class TestProperties : ITestProperties, IEnumerable + { + //Data + protected List _list = null; + + //Constructor + public TestProperties() + { + _list = new List(); + } + + //Methods + public virtual int Count + { + get { return _list.Count; } + } + + public virtual TestProperty this[int index] + { + get { return _list[index]; } + } + + public virtual Object this[String name] + { + get + { + ITestProperty property = this.Get(name); + if(property != null) + return property.Value; + return null; + } + set { this.Add(name).Value = value; } + } + + public virtual int IndexOf(string name) + { + int count = _list.Count; + for(int i=0; i= 0) + return _list[index]; + return null; + } + + ITestProperty ITestProperties.Add(String name) + { + return (TestProperty)Add(name); + } + + public virtual TestProperty Add(String name) + { + //Exists + int index = this.IndexOf(name); + if(index >= 0) + return _list[index]; + + //Otherwise add + TestProperty property = new TestProperty(name, null); + _list.Add(property); + return property; + } + + public virtual void Remove(String name) + { + int index = this.IndexOf(name); + if(index >= 0) + _list.RemoveAt(index); + } + + public virtual void Clear() + { + _list.Clear(); + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestSpec.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestSpec.cs new file mode 100644 index 0000000..5aa8213 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestSpec.cs @@ -0,0 +1,289 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; //Hashtable +using System.Xml; //XmlDocument + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // TestSpec + // + //////////////////////////////////////////////////////////////// + public class TestSpec + { + //Data + internal TestXmlDocument _xmldoc; + protected TestModule _testmodule; + + //Constructor + public TestSpec(TestModule testmodule) + { + _testmodule = testmodule; + } + + public void ApplyFilter(TestItem testmodule, string xpathquery) + { + try + { + //Delegate to XPath + XmlNodeList xmlnodes = this.XmlDocument.SelectNodes(xpathquery); + if(xmlnodes.Count > 0) + { + //Build a (object indexable) hashtable of all found items + Hashtable found = new Hashtable(); + foreach(XmlNode xmlnode in xmlnodes) + { + TestItem item = FindMatchingNode(xmlnode); + if(item != null) + found[item] = xmlnode; + } + + //If the entire testmodule was selected as part of the filter, were done. + //(as all children are implicitly included if the parent is selected) + if(!found.Contains(testmodule)) + ApplyFilter(testmodule, found); + } + else + { + //No results + testmodule.Children.Clear(); + } + } + catch(Exception e) + { + //Make this easier to debug + throw new TestFailedException( + testmodule.Name + " failed to apply filter: \"" + xpathquery + "\"", + xpathquery, + null, + e + ); + } + } + + public void ApplyFilter(TestItem testItem, Hashtable found) + { + // Remove all test items not found in the list. + for(int i=0; i + <-- Owner type is an enum, "test", "dev" or "pm" --> + + XQuery conformance tests + + + http://webdata/data/mytest/test.xml + http://webdata/data/mytest/test.xsd + + + Tests for FLWR expressions + + Simple 1 FLWR expression + + + NT + English + + + + http://webdata/data/mytest/special_test.xml + + + + http://webdata/data/mytest/test.wsdl + + + + + */ + + //Create the document + _xmldoc = new TestXmlDocument(); + + //Add the module (from the root) + this.AddChild(_xmldoc, _testmodule); + } + + protected void AddChild(XmlNode parent, TestItem node) + { + // + string name = node.Type.ToString(); + + //Create the Element + TestXmlElement element = (TestXmlElement)_xmldoc.CreateElement(name); + parent.AppendChild(element); + + //Add typed properties + //TODO: Should these also be part of the properties collection, or not in the interface + AddProperty(element, "Name", node.Name, 0); + AddProperty(element, "Desc", node.Desc, 0); + AddProperty(element, "Id", node.Order, 0); +// AddProperty(element, "Guid", node.Guid, 0); + AddProperty(element, "Priority",node.Priority, 0); + AddProperty(element, "Owner", node.Owner, 0); + AddProperty(element, "Owners", node.Owners, TestPropertyFlags.MultipleValues); + AddProperty(element, "Version", node.Version, 0); + + //Add extended properties + AddProperties(element, node); + + //Add our own uniqueid (for fast assoication later) + //Note: We place this 'userdata' into our own version of the XmlElement, (derived class) + element.Item = node; + + //Recursure through the children + foreach(TestItem childnode in node.Children) + this.AddChild(element, childnode); + } + + protected void AddProperties(XmlNode element, TestItem node) + { + //Obtain ALL the meta-data from the test. This is stored in the properties + //collection, which is at least all the attribute data, (plus potentially more) + foreach(TestProperty property in node.Metadata) + this.AddProperty(element, property.Name, property.Value, property.Flags); + } + + protected void AddProperty(XmlNode element, string name, object value, TestPropertyFlags flags) + { + //Ignore all the properties that have no name or value (as to not bloat the xml) + if(name == null || value == null) + return; + + //How to serialize (elements or attributes) + if((flags & TestPropertyFlags.MultipleValues) != 0) + { + //Property as Element + AddValue(element, name, value); + } + else + { + //Property as Attribute + XmlAttribute xmlattribute = _xmldoc.CreateAttribute(name); + xmlattribute.Value = StringEx.ToString(value); + element.Attributes.Append( xmlattribute ); + } + } + + protected void AddValue(XmlNode element, string name, object value) + { + //Recurise through the value(s) + if(value != null && value.GetType().HasElementType && value is IEnumerable) + { + //Recurse through the values + foreach(object item in (IEnumerable)value) + AddValue(element, name, item); + } + else + { + //value + XmlElement child = _xmldoc.CreateElement(name); + child.InnerText = StringEx.ToString(value); + element.AppendChild(child); + } + } + } + + //////////////////////////////////////////////////////////////// + // TestXmlDocument + // + //////////////////////////////////////////////////////////////// + internal class TestXmlDocument : XmlDocument + { + public override XmlElement CreateElement(string prefix, string name, string namespaceURI) + { + return new TestXmlElement(prefix, name, namespaceURI, this); + } + } + + //////////////////////////////////////////////////////////////// + // TestElement + // + //////////////////////////////////////////////////////////////// + internal class TestXmlElement : XmlElement + { + //Data + internal TestItem Item; + + //Constructor + public TestXmlElement(string prefix, string name, string namespaceURI, TestXmlDocument xmldoc) + : base(prefix, name, namespaceURI, xmldoc) + { + } + } +} \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/TestThread.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/TestThread.cs new file mode 100644 index 0000000..44369c8 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/TestThread.cs @@ -0,0 +1,262 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; //ArrayList +using System.Threading; //Thread + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // Delegate + // + //////////////////////////////////////////////////////////////// + public delegate void ThreadFunc(object obj); + + + //////////////////////////////////////////////////////////////// + // TestThreads + // + //////////////////////////////////////////////////////////////// + public class TestThreads + { + //Data + protected ArrayList _threads; + + //Constructor + public TestThreads() + { + _threads = new ArrayList(); + } + + //Static + public static int MaxThreads = 20; + + //Accessors + public virtual ArrayList Internal + { + get { return _threads; } + } + + public virtual int Count + { + get { return Internal.Count; } + } + + public virtual TestThread this[int index] + { + get { return (TestThread)(Internal[index]); } + } + + //Helpers + public virtual TestThread Add(TestThread thread) + { + Internal.Add(thread); + return thread; + } + + public virtual void Add(ThreadFunc func, object param) + { + Add(1, func, param); + } + + public virtual void Add(int threads, ThreadFunc func, object param) + { + //Default to one iteration + Add(threads, 1, func, param); + } + + public virtual void Add(int threads, int iterations, ThreadFunc func, object param) + { + for(int i=0; i +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Reflection; //BindingFlags + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // Delegate + // + //////////////////////////////////////////////////////////////// + public delegate void TestFunc(); + + //////////////////////////////////////////////////////////////// + // TestVariation + // + //////////////////////////////////////////////////////////////// + public class TestVariation : TestItem + { + //Data + protected TestFunc _func = null; + + //Constructor + public TestVariation() + : this(null, null) + { + } + + public TestVariation(TestFunc func) + : this(func.Method.Name, func) + { + } + + public TestVariation(string desc, TestFunc func) + : base(null, desc, TestType.TestVariation) + { + _func = func; + } + + // MemberwiseClone Shallow Copy + public TestVariation Clone() + { + return (TestVariation)MemberwiseClone(); + } + + protected override void DetermineChildren() + { + //No children + } + + protected override TestAttribute CreateAttribute() + { + return new VariationAttribute(); + } + + public override void Execute() + { + if(Parent != null) + Parent.CurrentChild = this; + + //Delegate function + //Note: Override, if you have some other approach to executing your code (reflection, xml file, etc) + _func(); + } + + public override int CompareTo(object o) + { + //Default comparison, Id based. + return this.Id.CompareTo(((TestItem)o).Id); + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/Util.cs b/ApiAsAService/odata.net/tools/ModuleCore/src/Util.cs new file mode 100644 index 0000000..9b8a2b6 --- /dev/null +++ b/ApiAsAService/odata.net/tools/ModuleCore/src/Util.cs @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections; //ArrayList +using System.Diagnostics; //Assert + +namespace Microsoft.Test.ModuleCore +{ + //////////////////////////////////////////////////////////////// + // StringEx + // + //////////////////////////////////////////////////////////////// + internal class StringEx + { + static public string ToString(object value) + { + if(value == null) + return null; + + if(value.GetType().IsEnum) + { + //Note: Value is not quite as easy as simply calling ToString(). Enums for example + //will return the type of the enum object and not the string of value... + + // Breaking change 628 (URT >= 2218): Format is being removed. + // e.ToString("G") is the replacement for format. + //return ((Enum)value).Format(); + return ((Enum)value).ToString("G"); + } + + return value.ToString(); + } + + static public string Format(object value) + { + if(value == null) + return "(null)"; + if(value is string) + return "\"" + value + "\""; + + return ToString(value); + } + } + + //////////////////////////////////////////////////////////////// + // TypeEx + // + //////////////////////////////////////////////////////////////// + internal class TypeEx + { + public static ArrayList GetNestedTypes(Type type, bool inherit) + { + //GetNestedTypes: + // "This method returns only the nested types of the current type. + // It does not search the hierarchy of inherited types. + // To find types that are nested in inherited types, you must walk the inheritance hierarchy." + ArrayList types = new ArrayList(); + + //Recurse + if(inherit && type.BaseType != null) + types = GetNestedTypes(type.BaseType, inherit); + + //Nested Types + foreach(Type t in type.GetNestedTypes()) + types.Add(t); + + return types; + } + } + + //////////////////////////////////////////////////////////////// + // InsensitiveHashtable + // + //////////////////////////////////////////////////////////////// + internal class InsensitiveHashtable : Hashtable + { + //Case-insensitive + public InsensitiveHashtable(int capacity) + : base(capacity, StringComparer.InvariantCultureIgnoreCase) + { + } + + //Helpers + public void Update(object key, object value) + { + //If it already exist, update the value + if(Contains(key)) + { + this[key] = value; + return; + } + + //Otheriwse add the value + Add(key, value); + } + + public void Dump() + { + //Loop over all keys + foreach(object key in Keys) + Console.WriteLine(key + "=" + this[key] + ";"); + } + } +} diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/framework.snk b/ApiAsAService/odata.net/tools/ModuleCore/src/framework.snk new file mode 100644 index 0000000000000000000000000000000000000000..110b59c7b0d27388353dcf4116f721595f473e58 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa500968(fU`!uG#RTE`+KN zuKf+^=>2N!kB9pMc5H)8nUWr|JLj6&)!f0|n$k8CAp(#KayILlN=pn$R@96PlTucm;!K;}lU1BV%Wh@=~);)AxZ!P8VeqOH+#FjlK9EuV{ OWf&lBz>_phTGEsG5JRQ_ literal 0 HcmV?d00001 diff --git a/ApiAsAService/odata.net/tools/ModuleCore/src/modulecore.snk b/ApiAsAService/odata.net/tools/ModuleCore/src/modulecore.snk new file mode 100644 index 0000000000000000000000000000000000000000..db3b62919de1b6fc916b4dff0c625d060bb4543f GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096&G*xLc+B0%?`Hq=$Q@l-2>HLhgnl`tP zg-BG{yYrg$IkLh8hBvooWv3m?^*bYg**W&Dr2aZ@;~6jLxDlEVz}D;EF#m)@9DY&Q zxpI+F@$X1>bs#l8>=NXKd*>7a7L<1%KRRfwNl7NyR99b)!=!j&nuc^S@S0TUNP(+g z6gI-Jmd+#42@s&&s5S5t0vCM&4@FLoaHQq1|AijPf+&~G^DXK6Ra1nf7}$~-?!nPM zncr++D^|d&Z{z{5VIW3c(PAIdy6vg<@qsf2-hNaTu`rKvVzwW%MXLSs~Wh zTBPvaU=x=V8SJ}ml)jtl@;G`=GZi{`yHkUq zczjvKgCo8w+U6z{OT}vG4DL#NT2&27SuFcIMLL>i#%UD~jVoQhi)c8`y<4p4&ZnUg z*emBRNNBg%nM<>#)|>({2+w>em<}IjjGrR(u}$wcgEu$|n}~}VMYseIDEO+#O1k-n9jEci2o!>!D*MoX+&a<%|61gk8Rh3|dZaYsonk`qNJec{hi=%}f + +Param +( + [String]$Workspace = $(throw "Workspace path is required."), # The performance working space + [String]$TestType = $(throw "TestType is required."), # the performance test type + [Int]$Threshold = $(throw "Threshold is required."), # Percentage, if the percentage ups to this value, it's regression + [String]$BuildId = $(throw "BuildId is required."), + [String]$RunnerParams +) + +# In which has the base running bits +$BaseBitsPath = $Workspace + "\BaseBits" +If((Test-Path $BaseBitsPath) -eq $False) +{ + throw "~/BaseBits is required." +} + +# In which has the test running bits +$TestBitsPath = $Workspace + "\TestBits" +If((Test-Path $TestBitsPath) -eq $False) +{ + throw "~/TestBits is required." +} + +# In which saves the logs files +$LogPath = $Workspace + "\Logs" +If((Test-Path $LogPath) -eq $False) +{ + New-Item -Path $Workspace -name Logs -ItemType directory +} + +# Performance test type +If($TestType -eq "") +{ + $TestType = "Component" +} + +<# +Description: Analysis result between test and base +#> +Function AnalysisTestResult +{ + Param( + [string]$baseResult, + [string]$currResult, + [Int] $threshold, + [string]$logFile + ) + # reading the test xml results + [Xml]$baseXml = Get-Content $baseResult + [Xml]$currXml = Get-Content $currResult + $currRun = $currXml.results.run + $currTests = $currRun.test + + $index = 1 + $a =@() + $exitResult = $True + foreach($currTest in $currTests) + { + $fullName = $currTest.name + + $baseTest = FindBaseTest $baseXml $fullName + If ($baseTest -eq $null) + { + throw "Cannot find $fullName in base result." + } + + $info = @{} + $info.no = $index + $info.name = $fullName + + $currMean = $currTest.summary.Duration.mean + $baseMean = $baseTest.summary.Duration.mean + [decimal]$delta = [decimal]$currMean - [decimal]$baseMean + + If ($delta -lt [decimal]0) + { + $delta*=[decimal](-1.0) + $info.percentage="-" + } + + $itemResult = "Pass" + [decimal]$percentage = ( $delta / [decimal]$baseMean ) * 100; + If ($percentage -gt [decimal]$threshold) + { + $exitResult = $False # It's for global + $itemResult = "Fail" + } + + $info.percentage += [string]([int]$percentage) + "%" + $info.result = $itemResult + $a+=$info + $index+=1 + } + + $a | ConvertTo-json | Out-File $logFile + + If($exitResult -eq $False) + { + # exit 1 # fail + throw $a | ConvertTo-json + } +} + +<# +Description: search the test in base result. +#> +Function FindBaseTest([Xml]$baseXml, [string]$testName) +{ + $tests = $baseXml.results.run.test + + foreach($test in $tests) + { + $fullName = $test.name + + If($fullName -eq $testName) + { + return $test + } + } + + return $null +} + +<# +Description: Run the performance test cases +#> +Function Execute([string]$testFolder, [string]$testType, [string]$runid) +{ + $location = Get-Location + Set-Location ($testFolder + "\bin") + + $testDllName = "Microsoft.OData.Performance." + $testType + ".Tests.dll" + $result = $runid + ".xml" + $analysisResult = $runid + ".analysisResult.xml" + .\xunit.performance.run.exe $testDllName -runner .\xunit.console.exe -runnerargs "-parallel none $RunnerParams" -runid $runid + .\xunit.performance.analysis.exe $result -xml $analysisResult + + Set-Location $location +} + +<# +Description: Get the RunId based on inputs, the output is similar as "Component.20170725.101" +#> +Function GetRunId([string]$testType, [string]$date, [string]$buildId) +{ + If($testType -eq "Component") + { + return "Component." + $date + "." + $buildId + } + + If($testType -eq "Service") + { + return "Service." + $date + "." + $buildId + } + + # failed. + throw "Wrong test type input. It should be '-TestType Component|Service'" +} + +# Step 1. Get the running date & test result name +$RunDate = Get-Date -Format yyyyMMdd +$TestRunId = GetRunId $TestType $RunDate $BuildId + +# Step 2. Run the current tests +Execute $TestBitsPath $TestType $TestRunId +$TestResult = $TestBitsPath + "\bin\" + $TestRunId + ".analysisResult.xml" + +# Step 3. Run the base tests +Execute $BaseBitsPath $TestType $TestRunId +$BaseResult = $BaseBitsPath + "\bin\" + $TestRunId + ".analysisResult.xml" + +# Step 4. Analysis the between test results and base results +$LogFileName = $TestRunId + ".log" +$LogFilePath = $LogPath + "\" + $LogFileName +If((Test-Path $LogFilePath ) -eq $False) +{ + New-Item -Path $LogPath -name $LogFileName -ItemType File +} + +AnalysisTestResult $BaseResult $TestResult $Threshold $LogFilePath diff --git a/ApiAsAService/odata.net/tools/PoliCheck/RunPoliCheck.ps1 b/ApiAsAService/odata.net/tools/PoliCheck/RunPoliCheck.ps1 new file mode 100644 index 0000000..92bb179 --- /dev/null +++ b/ApiAsAService/odata.net/tools/PoliCheck/RunPoliCheck.ps1 @@ -0,0 +1,34 @@ +param( + [string]$BuildSourceDir, + [string]$folderName, + [string]$branchName, + [string]$resultRoot, + [string]$PoliCheckPath +) + +# +#Example: +# RunPoliCheck.ps1 -BuildSourceDir "C:\BuildAgent\_work\32\s" +# -folderName "src" +# -branchName "odata.net-master" +# -resultRoot "C:\Users\ODatabld\Documents\PoliCheck\LatestRunResult" +# -PoliCheckPath "C:\Program Files (x86)\Microsoft\PoliCheck\" +# + +$targetPath= "${BuildSourceDir}\${folderName}" +Write-Output "targetPath: ${targetPath}" +$result="${resultRoot}\${branchName}\poli_result_${folderName}.xml" + +cd "${PoliCheckPath}" + +.\Policheck.exe /F:$targetPath /T:9 /Sev:"1|2" /PE:2 /O:$result + +$FileContent = Get-Content $result +$PassResult = Select-String -InputObject $FileContent -Pattern "" + +If ($PassResult.Matches.Count -eq 0) { + Write-Error "PoliCheck failed for target ${targetPath}. For details, please check this result file on build machine: ${result}: section ." + exit 1 +} + +Write-Output "PoliCheck pass for target ${targetPath}" \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/Scripts/TestCleanup.cmd b/ApiAsAService/odata.net/tools/Scripts/TestCleanup.cmd new file mode 100644 index 0000000..a441aa2 --- /dev/null +++ b/ApiAsAService/odata.net/tools/Scripts/TestCleanup.cmd @@ -0,0 +1,3 @@ +@echo off +set ENLISTMENT_ROOT=%1 +cscript artdbclean.js //Nologo \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/Scripts/TestStartup.cmd b/ApiAsAService/odata.net/tools/Scripts/TestStartup.cmd new file mode 100644 index 0000000..caf5c16 --- /dev/null +++ b/ApiAsAService/odata.net/tools/Scripts/TestStartup.cmd @@ -0,0 +1,2 @@ +@echo off +set ENLISTMENT_ROOT=%1 diff --git a/ApiAsAService/odata.net/tools/Scripts/artcommon.js b/ApiAsAService/odata.net/tools/Scripts/artcommon.js new file mode 100644 index 0000000..3023760 --- /dev/null +++ b/ApiAsAService/odata.net/tools/Scripts/artcommon.js @@ -0,0 +1,155 @@ +////////////////////////////////////////////////////////////////////////////// +// // +// artcommon.js // +// // +// Provides utility functions used by multiple JScript files. // +// // +////////////////////////////////////////////////////////////////////////////// + +// Deletes the specified folder, forcing read-only files to be deleted as well. +function DeleteFolder(path) +{ + var fso = new ActiveXObject("Scripting.FileSystemObject"); + fso.DeleteFolder(path, true); +} + +// Checks whether the specified file exists. +function FileExists(path) +{ + var fso; + fso = new ActiveXObject("Scripting.FileSystemObject"); + if (fso.FileExists(path)) + { + return true; + } + else + { + return false; + } +} + +function GetEnvironmentVariable(name) +{ + var WshShell = new ActiveXObject("WScript.Shell"); + var result = WshShell.ExpandEnvironmentStrings("%" + name + "%"); + if (result == "%" + name + "%") + { + result = null; + } + return result; +} + +// Returns the extension of the specified path string (including the "."); +// empty if there is no extension. +function PathGetExtension(path) +{ + var l = path.length; + var startIndex = l; + while (--startIndex >= 0) + { + var ch = path.substr(startIndex, 1); + if (ch == ".") + { + if (startIndex != (l - 1)) + { + return path.substr(startIndex, l - startIndex); + } + return ""; + } + else if (ch == "\\" || ch == ":") + { + break; + } + } + return ""; +} + +// Runs the specified function catching exceptions and quits the current script. +function RunAndQuit(f) +{ + try + { + f(); + } + catch(e) + { + WScript.Echo("Error caught while running this function:"); + WScript.Echo(f.toString()); + WScript.Echo("Error details:"); + if (typeof(e) == "object" && e.toString() == "[object Error]") + { + for (var p in e) WScript.Echo(" " + p + ": " + p[e]); + } + else + { + WScript.Echo(e); + } + WScript.Quit(1); + } + WScript.Quit(0); +} + +// Runs a command and waits for it to exit. +// Returns an array with stdout in 0, stderr in 1 and exit code in 2. +function RunConsoleCommand(strCommand) +{ + var WshShell = new ActiveXObject("WScript.Shell"); + var result = new Array(3); + var oExec = WshShell.Exec(strCommand); + + result[0] = oExec.StdOut.ReadAll(); + result[1] = oExec.StdErr.ReadAll(); + result[2] = oExec.ExitCode; + + return result; +} + +// Uses the Speech API to speak to the user. +function Say(text) +{ + var voice = new ActiveXObject("SAPI.SpVoice"); + try { + voice.Speak(text); + } + catch (e) { + // See http://msdn2.microsoft.com/en-us/library/ms717306.aspx for error codes. + // SPERR_DEVICE_BUSY 0x80045006 -2147201018 + if (e.number == -2147201018) { + WScript.Echo("The wave device is busy."); + } + } +} + +// Splits a string into a string array. +function StringSplit(strLine, strSeparator) +{ + var result = new Array(); + var startIndex = 0; + var resultIndex = 0; + while (startIndex < strLine.length) + { + var endIndex = strLine.indexOf(strSeparator, startIndex); + if (endIndex == -1) + { + endIndex = strLine.length; + } + result[resultIndex] = strLine.substring(startIndex, endIndex); + startIndex = endIndex + strSeparator.length; + resultIndex++; + } + return result; +} + + +// +// References: +// +// JScript Language Reference +// http://msdn2.microsoft.com/en-us/library/yek4tbz0 +// +// Windows Script Host Object Model +// http://msdn2.microsoft.com/en-us/library/a74hyyw0 +// +// Script Runtime +// http://msdn2.microsoft.com/en-us/library/hww8txat.aspx +// diff --git a/ApiAsAService/odata.net/tools/Scripts/artdbclean.js b/ApiAsAService/odata.net/tools/Scripts/artdbclean.js new file mode 100644 index 0000000..8c0ed54 --- /dev/null +++ b/ApiAsAService/odata.net/tools/Scripts/artdbclean.js @@ -0,0 +1,101 @@ +// Run with: +// cscript artdbclean.js //Nologo + +////////////////////////////////////////////////////////////////////////////// +// Include artcommon.js +var artCommonPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings("artcommon.js"); +eval(new ActiveXObject("Scripting.FileSystemObject").OpenTextFile(artCommonPath, 1).ReadAll()); + +// Opens a result set over the specified connection. +function OpenResultSet(connection, queryText) +{ + var command = new ActiveXObject("ADODB.Command"); + command.ActiveConnection = connection; + command.CommandText = queryText; + return command.Execute(); +} + +// Opens an ADO SQL Server driver connection to the specified. +function OpenSqlServerConnection(serverName, initialCatalog, useTrustedConnection, + username, password, provider) +{ + var connectionString = "Provider='" + provider + "';Data Source=" + serverName + ";"; + if (initialCatalog != null && initialCatalog != "") + { + connectionString += "Initial Catalog='" + initialCatalog + "';"; + } + if (useTrustedConnection == true) + { + connectionString += "Integrated Security='SSPI';"; + } + else if (username != null && username != "") + { + connectionString += "Username='" + username + "';Password='" + password + "';"; + } + + var result = new ActiveXObject("ADODB.Connection"); + result.Open(connectionString); + return result; +} + +function DropDatabasesForServer(serverName, path, provider) +{ + var connection; + try + { + connection = OpenSqlServerConnection(serverName, null, true, null, null, provider); + } catch (e) { + //WScript.Echo("Continuing after failing to connect to server '" + serverName + "' with error: " + e.message); + return; + } + + var rs = OpenResultSet(connection, "select d.database_id, d.name, mf.physical_name " + + "from sys.master_files as mf inner join sys.databases as d on mf.database_id = d.database_id " + + "where mf.physical_name like '" + path + "%'"); + var databases = new Object(); + while (!rs.EOF) + { + var databaseName = rs.fields.Item(1).Value; + var databaseFile = rs.fields.Item(2).Value; + databases[databaseName] = databaseFile; + rs.MoveNext(); + } + + for (var m in databases) + { + WScript.Echo("Dropping database " + m + " from '" + serverName + "' to unlock file " + databases[m] + "."); + try + { + connection.Execute("DROP DATABASE [" + m + "]"); + } catch(e) { + if (e.message.indexOf("Unable to open the physical file") >= 0) { + //WScript.Echo("Continuing after this error: " + e.message); + //WScript.Echo(e.message); + } else { + throw e; + } + } + } +} + +var enlistmentRoot = GetEnvironmentVariable("ENLISTMENT_ROOT"); +if (enlistmentRoot == null || enlistmentRoot == "") +{ + WScript.Echo("Enlistment_Root isn't defined."); + WScript.Quit(1); +} + +var oledbServerNames = ["(local)", ".\\SQLEXPRESS"]; +for (var i in oledbServerNames) +{ + DropDatabasesForServer(oledbServerNames[i], enlistmentRoot, "sqloledb"); +} + +var sqlncliServerNames = ["(localdb)\\MSSQLLocalDB"]; +for (var i in sqlncliServerNames) +{ + // Use Sql native client for localDB. + DropDatabasesForServer(sqlncliServerNames[i], enlistmentRoot, "sqlncli11"); +} + +WScript.Quit(0); diff --git a/ApiAsAService/odata.net/tools/StringResourceGenerator/ClassGeneratorCommon.ttinclude b/ApiAsAService/odata.net/tools/StringResourceGenerator/ClassGeneratorCommon.ttinclude new file mode 100644 index 0000000..066e6d7 --- /dev/null +++ b/ApiAsAService/odata.net/tools/StringResourceGenerator/ClassGeneratorCommon.ttinclude @@ -0,0 +1,69 @@ +<# +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- +#><#+ + private void GenerateFromAllTextFiles() + { + string templatePath = Path.GetDirectoryName(this.Host.TemplateFile); + + foreach (string textFile in Configuration.TextFiles) + { + string fullname = Path.Combine(templatePath, textFile); + GenerateFromSingleTextFile(fullname); + } + } + + private void GenerateFromSingleTextFile(string fullname) + { + using (var reader = new StreamReader(fullname)) + { + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + GenerateFromSingleLine(line); + } + } + } + + private void GenerateFromSingleLine(string line) + { + if (string.IsNullOrEmpty(line)) + { + // Skip empty line. + return; + } + + // Remove leading and trailing whitespaces, + line = line.Trim(); + + if (line.StartsWith(";")) + { + // Skip comment line. + return; + } + + // Find the first occurrence of '='. + int equalPos = line.IndexOf('='); + if (equalPos < 0) + { + throw new InvalidOperationException("Missing equal operator"); + } + + string id = line.Substring(0, equalPos); + if (string.IsNullOrEmpty(id)) + { + throw new InvalidOperationException("Invalid resource id"); + } + + string content = line.Substring(equalPos + 1); + if (string.IsNullOrEmpty(content)) + { + throw new InvalidOperationException("Invalid resource content"); + } + + GenerateFromSingleResource(id, content); + } +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/StringResourceGenerator/ResourceClassGenerator.ttinclude b/ApiAsAService/odata.net/tools/StringResourceGenerator/ResourceClassGenerator.ttinclude new file mode 100644 index 0000000..7fdbf5f --- /dev/null +++ b/ApiAsAService/odata.net/tools/StringResourceGenerator/ResourceClassGenerator.ttinclude @@ -0,0 +1,219 @@ +<# +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- +#> +<#@ template debug="true" hostspecific="true" language="C#" visibility="internal" #> +<#@ output extension=".cs" #> +<#@ assembly name="System.Core" #> +<#@ Import Namespace="System" #> +<#@ Import Namespace="System.Diagnostics" #> +<#@ Import Namespace="System.IO" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ include file="ClassGeneratorCommon.ttinclude" #> +<# + // ************************************************************************ + // Step 1: Validate the input arguments. + // ************************************************************************ + if (string.IsNullOrEmpty(Configuration.ResourceClassNamespace)) + { + throw new ArgumentException("Invalid ResourceClassNamespace"); + } + + if (string.IsNullOrEmpty(Configuration.AssemblyName)) + { + throw new ArgumentException("Invalid AssemblyName"); + } + + if (string.IsNullOrEmpty(Configuration.ResourceClassName)) + { + throw new ArgumentException("Invalid ResourceClassName"); + } +#> +<# + // ************************************************************************ + // Step 2: Generate document header and common imports. + // ************************************************************************ +#> +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace <#= Configuration.ResourceClassNamespace #> { + using System; + using System.Globalization; + using System.Reflection; + using System.Resources; +#if !PORTABLELIB + using System.Security.Permissions; +#endif + using System.Text; + using System.Threading; + +<# + // ************************************************************************ + // Step 3: Generate string resource attributes if not SkipSRAttributes. + // ************************************************************************ + if (!Configuration.SkipSRAttributes) + { +#> + using System.ComponentModel; +#if !PORTABLELIB + [AttributeUsage(AttributeTargets.All)] + internal sealed class <#= Configuration.ResourceClassName #>DescriptionAttribute : DescriptionAttribute { + + private bool replaced = false; + + /// + /// Constructs a new sys description. + /// + /// + /// description text. + /// + public <#= Configuration.ResourceClassName #>DescriptionAttribute(string description) : base(description) { + } + + /// + /// Retrieves the description text. + /// + /// + /// description + /// + public override string Description { + get { + if (!replaced) { + replaced = true; + DescriptionValue = <#= Configuration.ResourceClassName #>.GetString(base.Description); + } + return base.Description; + } + } + } + + [AttributeUsage(AttributeTargets.All)] + internal sealed class <#= Configuration.ResourceClassName #>CategoryAttribute : CategoryAttribute { + + public <#= Configuration.ResourceClassName #>CategoryAttribute(string category) : base(category) { + } + + protected override string GetLocalizedString(string value) { + return <#= Configuration.ResourceClassName #>.GetString(value); + } + } +#endif + +<# + } +#> +<# + // ************************************************************************ + // Step 4: Generate resource class header. + // ************************************************************************ +#> + /// + /// AutoGenerated resource class. Usage: + /// + /// string s = <#= Configuration.ResourceClassName #>.GetString(<#= Configuration.ResourceClassName #>.MyIdenfitier); + /// + internal sealed class <#= Configuration.ResourceClassName #> { +<# + // ************************************************************************ + // Step 5: Generate a string const for each string resource item. + // ************************************************************************ + GenerateFromAllTextFiles(); +#> +<# + // ************************************************************************ + // Step 6: Generate resource class footer + // ************************************************************************ +#> + + static <#= Configuration.ResourceClassName #> loader = null; + ResourceManager resources; + + internal <#= Configuration.ResourceClassName #>() { +#if !PORTABLELIB + resources = new System.Resources.ResourceManager("<#= Configuration.AssemblyName #>", this.GetType().Assembly); +#else + resources = new System.Resources.ResourceManager("<#= Configuration.AssemblyName #>", this.GetType().GetTypeInfo().Assembly); +#endif + } + + private static <#= Configuration.ResourceClassName #> GetLoader() { + if (loader == null) { + <#= Configuration.ResourceClassName #> sr = new <#= Configuration.ResourceClassName #>(); + Interlocked.CompareExchange(ref loader, sr, null); + } + return loader; + } + + private static CultureInfo Culture { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + public static ResourceManager Resources { + get { + return GetLoader().resources; + } + } + + public static string GetString(string name, params object[] args) { + <#= Configuration.ResourceClassName #> sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, <#= Configuration.ResourceClassName #>.Culture); + + if (args != null && args.Length > 0) { + for (int i = 0; i < args.Length; i ++) { + String value = args[i] as String; + if (value != null && value.Length > 1024) { + args[i] = value.Substring(0, 1024 - 3) + "..."; + } + } + return String.Format(CultureInfo.CurrentCulture, res, args); + } + else { + return res; + } + } + + public static string GetString(string name) { + <#= Configuration.ResourceClassName #> sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetString(name, <#= Configuration.ResourceClassName #>.Culture); + } + + public static string GetString(string name, out bool usedFallback) { + // always false for this version of gensr + usedFallback = false; + return GetString(name); + } +#if !PORTABLELIB + public static object GetObject(string name) { + <#= Configuration.ResourceClassName #> sys = GetLoader(); + if (sys == null) + return null; + return sys.resources.GetObject(name, <#= Configuration.ResourceClassName #>.Culture); + } +#endif + } +}<#+ + // ************************************************************************ + // Generation helper method + // ************************************************************************ + private void GenerateFromSingleResource(string id, string content) + { +#> + internal const string <#= id #> = "<#= id #>"; +<#+ + } +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/StringResourceGenerator/StringsClassGenerator.ttinclude b/ApiAsAService/odata.net/tools/StringResourceGenerator/StringsClassGenerator.ttinclude new file mode 100644 index 0000000..9de450c --- /dev/null +++ b/ApiAsAService/odata.net/tools/StringResourceGenerator/StringsClassGenerator.ttinclude @@ -0,0 +1,228 @@ +<# +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- +#> +<#@ template debug="true" hostspecific="true" language="C#" visibility="internal" #> +<#@ output extension=".cs" #> +<#@ assembly name="System.Core" #> +<#@ Import Namespace="System" #> +<#@ Import Namespace="System.Diagnostics" #> +<#@ Import Namespace="System.IO" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ include file="ClassGeneratorCommon.ttinclude" #> +<# + // ************************************************************************ + // Step 1: Validate the input arguments. + // ************************************************************************ + if (string.IsNullOrEmpty(Configuration.ResourceClassNamespace)) + { + throw new ArgumentException("Invalid ResourceClassNamespace"); + } + + if (string.IsNullOrEmpty(Configuration.AssemblyName)) + { + throw new ArgumentException("Invalid AssemblyName"); + } + + if (string.IsNullOrEmpty(Configuration.ResourceClassName)) + { + throw new ArgumentException("Invalid ResourceClassName"); + } +#> +<# + // ************************************************************************ + // Step 2: Generate document header and common imports. + // ************************************************************************ +#> +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +// GENERATED FILE. DO NOT MODIFY. +// +// +//--------------------------------------------------------------------- + +namespace <#= Configuration.ResourceClassNamespace #> { + using System; + using System.Resources; + +<# + // ************************************************************************ + // Step 3: Generate resource class header. + // ************************************************************************ +#> + /// + /// Strongly-typed and parameterized string resources. + /// + internal static class Strings { +<# + // ************************************************************************ + // Step 4: Generate a string const for each string resource item. + // ************************************************************************ + GenerateFromAllTextFiles(); +#> +<# + // ************************************************************************ + // Step 5: Generate resource class footer + // ************************************************************************ +#> + } + + /// + /// Strongly-typed and parameterized exception factory. + /// + internal static partial class Error { + + /// + /// The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method that does not accept it as a valid argument. + /// + internal static Exception ArgumentNull(string paramName) { + return new ArgumentNullException(paramName); + } + + /// + /// The exception that is thrown when the value of an argument is outside the allowable range of values as defined by the invoked method. + /// + internal static Exception ArgumentOutOfRange(string paramName) { + return new ArgumentOutOfRangeException(paramName); + } + + /// + /// The exception that is thrown when the author has not yet implemented the logic at this point in the program. This can act as an exception based TODO tag. + /// + internal static Exception NotImplemented() { + return new NotImplementedException(); + } + + /// + /// The exception that is thrown when an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality. + /// + internal static Exception NotSupported() { + return new NotSupportedException(); + } + } +}<#+ + // ************************************************************************ + // Generation helper methods + // ************************************************************************ + private static int ComputeArgumentCountInFormatString(string str) + { + bool parsingIndex = false; + int maxCount = 0; + var literal = new StringBuilder(); + foreach (char c in str) + { + if (parsingIndex) + { + if (char.IsDigit(c)) + { + literal.Append(c); + } + else + { + // Support handling the following three cases: + // "{2}" + // "{0,8:c}" + // "{1:p1}" + if (c == '}' || c == ',' || c == ':') + { + int count = int.Parse(literal.ToString()) + 1; + if (count > maxCount) + { + maxCount = count; + } + + literal.Clear(); + } + + parsingIndex = false; + } + } + else + { + parsingIndex = c == '{'; + } + } + + return maxCount; + } + + private string GenerateParameterList(int count) + { + var builder = new StringBuilder(); + + for (int i = 0; i < count; ++i) + { + builder.AppendFormat("object p{0}", i); + + if (i != count - 1) + { + builder.Append(", "); + } + } + + return builder.ToString(); + } + + private string GenerateArgumentList(int count) + { + var builder = new StringBuilder(); + + for (int i = 0; i < count; ++i) + { + builder.AppendFormat("p{0}", i); + + if (i != count - 1) + { + builder.Append(", "); + } + } + + return builder.ToString(); + } + + private void GenerateFromSingleResource(string id, string content) + { + int count = ComputeArgumentCountInFormatString(content); +#> + /// + /// A string like "<#= content.Replace("<", "<").Replace(">", ">") #>" + /// +<#+ + if (count == 0) + { + GenerateAsProperty(id); + } + else + { + GenerateAsMethod(id, count); + } + } + + private void GenerateAsProperty(string id) + { +#> + internal static string <#= id #> { + get { + return <#= Configuration.ResourceClassNamespace #>.<#= Configuration.ResourceClassName #>.GetString(<#= Configuration.ResourceClassNamespace #>.<#= Configuration.ResourceClassName #>.<#= id #>); + } + } + +<#+ + } + + private void GenerateAsMethod(string id, int count) + { +#> + internal static string <#= id #>(<#= GenerateParameterList(count) #>) { + return <#= Configuration.ResourceClassNamespace #>.<#= Configuration.ResourceClassName #>.GetString(<#= Configuration.ResourceClassNamespace #>.<#= Configuration.ResourceClassName #>.<#= id #>, <#= GenerateArgumentList(count) #>); + } + +<#+ + } +#> \ No newline at end of file diff --git a/ApiAsAService/odata.net/tools/StrongNamePublicKeys/35MSSharedLib1024.snk b/ApiAsAService/odata.net/tools/StrongNamePublicKeys/35MSSharedLib1024.snk new file mode 100644 index 0000000000000000000000000000000000000000..695f1b38774e839e5b90059bfb7f32df1dff4223 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa50098C{E+7Ye`kjtcRG*W zi8#m|)B?I?xgZ^2Sw5D;l4TxtPwG;3)3^j?qDHjEteSTF{rM+4WI`v zCD?tsZ^;k+S&r1&HRMb=j738S=;J$tCKNrc$@P|lZXBwC^22y$plQBaj-2N`{$WF!m`d=Iy5D^XA7(JX$^N zNdw7I=5&5x0+y8nHV=1!zl-n{1&+9zb|HC;%!R7T18?!X6;``{V%`;ygoKD{sJpHq zzQrk2>;{dMRsht(Ly4;iEadqJN&apfFy${$A)bR=Ct=fPS@mr)O7eb*7lD}z%6=Fj^+u~?#} z$um_fVzN~MztqpkPS&jI$D=Qc;#XT3#0h=v=mb6k#-b{u`(y*IQVZ2?9;;$i%4)c( zc2-@jjHXq43Y@?W%KSV7YAT%Gf<=LQw_NO$yq iFl(s~jC^_M`a}OK1)w}{J1-GeKXw#H9zQDJcVZdJ +Param +( + [String]$Workspace = $(throw "Workspace path is required."), + [String]$CompType = $(throw "TestType is required."), # the performance test type + [String]$BuildId = $(throw "BuildId is required."), + [String]$RunnerParams +) + +# In which has the base running bits +$BaseBitsPath = Join-Path $Workspace "BaseBits" +If((Test-Path $BaseBitsPath) -eq $False) +{ + throw "~/BaseBits is required." +} + +# In which has the test running bits +$TestBitsPath = Join-Path $Workspace "TestBits" +If((Test-Path $TestBitsPath) -eq $False) +{ + throw "~/TestBits is required." +} + +<# +Description: Run the performance test cases +#> +Function Execute([string]$testFolder, [string]$testType, [string]$runid) +{ + $location = Get-Location + Set-Location (Join-Path $testFolder "bin") + + $testDllName = "Microsoft.OData.Performance." + $testType + ".Tests.dll" + $result = $runid + ".xml" + $analysisResult = $runid + ".analysisResult.xml" + .\xunit.performance.run.exe $testDllName -runner .\xunit.console.exe -runnerargs "-parallel none $RunnerParams" -runid $runid + .\xunit.performance.analysis.exe $result -xml $analysisResult + + Set-Location $location +} + +<# +Description: Get the RunId based on inputs, the output is similar as "Component.20170725.101" +#> +Function GetRunId([string]$testType, [string]$date, [string]$buildId) +{ + If($testType -eq "Component") + { + return "Component." + $date + "." + $buildId + } + + If($testType -eq "Service") + { + return "Service." + $date + "." + $buildId + } + + # failed. + throw "Wrong test type input. It should be 'Component|Service'" +} + +# Step 1. Get the running date & test result name +$RunDate = Get-Date -Format yyyyMMdd +$TestRunId = GetRunId $CompType $RunDate $BuildId + +# Step 2. Run the current tests +Execute $TestBitsPath $CompType $TestRunId +$TempPath = Join-Path $TestBitsPath "bin" +$TestResult = Join-Path $TempPath ($TestRunId + ".analysisResult.xml") +Move-Item -Path $TestResult -Destination (Join-Path $TestBitsPath "test.xml") -Force + +# Step 3. Run the base tests +Execute $BaseBitsPath $CompType $TestRunId +$TempPath = Join-Path $BaseBitsPath "bin" +$BaseResult = Join-Path $TempPath ($TestRunId + ".analysisResult.xml") +Move-Item -Path $BaseResult -Destination (Join-Path $BaseBitsPath "base.xml") -Force From d85f87e31aa6fdf14f0a50fa17732eee863bdecb Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Mon, 22 Jul 2019 12:16:56 -0700 Subject: [PATCH 02/21] Update packages --- .../GlobalSuppressions.cs | 4 +- .../Microsoft.AspNet.OData.csproj | 39 ++++++++++++------- .../Microsoft.AspNet.OData/packages.config | 3 ++ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs index 6cee9b4..b546149 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs @@ -34,4 +34,6 @@ [assembly: SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.StartsWith(System.String)", Scope = "member", Target = "Microsoft.AspNet.OData.Query.DefaultSkipTokenHandler.#PopulatePropertyValuePairs(System.String,Microsoft.AspNet.OData.ODataQueryContext)")] [assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Skiptoken", Scope = "resource", Target = "Microsoft.AspNet.OData.Properties.SRResources.resources")] [assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "skiptoken", Scope = "resource", Target = "Microsoft.AspNet.OData.Properties.SRResources.resources")][assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Subquery", Scope = "member", Target = "Microsoft.AspNet.OData.Query.ODataQuerySettings.#EnableCorrelatedSubqueryBuffering")] -[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Subquery", Scope = "member", Target = "Microsoft.AspNet.OData.EnableQueryAttribute.#EnableCorrelatedSubqueryBuffering")] \ No newline at end of file +[assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Subquery", Scope = "member", Target = "Microsoft.AspNet.OData.EnableQueryAttribute.#EnableCorrelatedSubqueryBuffering")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder.#ProjectCollection(System.Linq.Expressions.Expression,System.Type,Microsoft.OData.UriParser.SelectExpandClause,Microsoft.OData.Edm.IEdmEntityType,Microsoft.OData.Edm.IEdmNavigationSource,Microsoft.OData.UriParser.ExpandedReferenceSelectItem,System.Nullable`1)")] + diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj index 2767a17..55b0299 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj @@ -18,40 +18,53 @@ - ..\..\sln\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - True + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - ..\..\sln\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll True - ..\..\sln\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + ..\..\..\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - ..\..\sln\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + ..\..\..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - ..\..\sln\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll + ..\..\..\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - ..\..\sln\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll True - - - False - ..\..\sln\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll + + + ..\..\..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.dll + True + True + + + ..\..\..\packages\System.Net.Http.Formatting.Extension.5.2.3.0\lib\System.Net.Http.Extensions.dll + + + ..\..\..\packages\System.Net.Http.Formatting.Extension.5.2.3.0\lib\System.Net.Http.Formatting.dll + + + ..\..\..\packages\System.Net.Http.Formatting.Extension.5.2.3.0\lib\System.Net.Http.Primitives.dll + + + ..\..\..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.WebRequest.dll + True + True - False - ..\..\sln\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config index ac131a1..2753bfa 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config @@ -8,4 +8,7 @@ + + + \ No newline at end of file From 275724a95882788774ea6313ed41540f175bf5a8 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Mon, 22 Jul 2019 12:34:01 -0700 Subject: [PATCH 03/21] Added sample csdl files --- ApiAsAService/NW_Simple.xml | 143 ++++++++++++++++++++++++++++++ ApiAsAService/Trippin.xml | 167 ++++++++++++++++++++++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 ApiAsAService/NW_Simple.xml create mode 100644 ApiAsAService/Trippin.xml diff --git a/ApiAsAService/NW_Simple.xml b/ApiAsAService/NW_Simple.xml new file mode 100644 index 0000000..a9e9939 --- /dev/null +++ b/ApiAsAService/NW_Simple.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/Trippin.xml b/ApiAsAService/Trippin.xml new file mode 100644 index 0000000..dec04f0 --- /dev/null +++ b/ApiAsAService/Trippin.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name + + + + + + + + + + + + + \ No newline at end of file From 17378870b8f088235e2161ea31f106d4873245e6 Mon Sep 17 00:00:00 2001 From: KanishManuja-MS <41647051+KanishManuja-MS@users.noreply.github.com> Date: Mon, 22 Jul 2019 16:16:49 -0700 Subject: [PATCH 04/21] commit GetTrippin api to get the schema from cosmos db --- .../ApiAsAService/ApiAsAService.csproj | 2 + .../DataSource/DocumentDBRepository.cs | 120 ++++++++++++++++++ ApiAsAService/ApiAsAService/Item.cs | 11 ++ ApiAsAService/ApiAsAService/Program.cs | 16 +++ 4 files changed, 149 insertions(+) create mode 100644 ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs create mode 100644 ApiAsAService/ApiAsAService/Item.cs diff --git a/ApiAsAService/ApiAsAService/ApiAsAService.csproj b/ApiAsAService/ApiAsAService/ApiAsAService.csproj index 9da73c0..53fe110 100644 --- a/ApiAsAService/ApiAsAService/ApiAsAService.csproj +++ b/ApiAsAService/ApiAsAService/ApiAsAService.csproj @@ -93,6 +93,7 @@ + @@ -101,6 +102,7 @@ + diff --git a/ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs b/ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs new file mode 100644 index 0000000..62c94b6 --- /dev/null +++ b/ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs @@ -0,0 +1,120 @@ +namespace todo +{ + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + using DynamicEdmModelCreation; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Client; + using Microsoft.Azure.Documents.Linq; + + public static class DocumentDBRepository where T : class + { + private static readonly string DatabaseId = "schemadb"; + private static readonly string CollectionId = "schemas"; + private static DocumentClient client; + + public static async Task GetSchema(string id) + { + try + { + Document document = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri("schemadb", "schemas", "trippinschema")); + return (T)(dynamic)document; + } + catch (DocumentClientException e) + { + if (e.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return null; + } + else + { + throw; + } + } + } + + public static async Task> GetItemsAsync(Expression> predicate) + { + IDocumentQuery query = client.CreateDocumentQuery( + UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), + new FeedOptions { MaxItemCount = -1 }) + .Where(predicate) + .AsDocumentQuery(); + + List results = new List(); + while (query.HasMoreResults) + { + results.AddRange(await query.ExecuteNextAsync()); + } + + return results; + } + + public static async Task CreateItemAsync(T item) + { + return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), item); + } + + public static async Task UpdateItemAsync(string id, T item) + { + return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id), item); + } + + public static async Task DeleteItemAsync(string id) + { + await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id)); + } + + public static void Initialize() + { + client = new DocumentClient(new Uri("https://schema-store-sqlapi.documents.azure.com:443/"), "b1aMOAo2Z9Fre9ip8hxpquEVScQKfrlDPlM9DzjqPFyDENuk3GJ2UIaoWptY4CL5ddbgXTlc9zRPRpYVNcbSkQ=="); + CreateDatabaseIfNotExistsAsync().Wait(); + CreateCollectionIfNotExistsAsync().Wait(); + } + + private static async Task CreateDatabaseIfNotExistsAsync() + { + try + { + await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId)); + } + catch (DocumentClientException e) + { + if (e.StatusCode == System.Net.HttpStatusCode.NotFound) + { + await client.CreateDatabaseAsync(new Database { Id = DatabaseId }); + } + else + { + throw; + } + } + } + + private static async Task CreateCollectionIfNotExistsAsync() + { + try + { + await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId)); + } + catch (DocumentClientException e) + { + if (e.StatusCode == System.Net.HttpStatusCode.NotFound) + { + await client.CreateDocumentCollectionAsync( + UriFactory.CreateDatabaseUri(DatabaseId), + new DocumentCollection { Id = CollectionId }, + new RequestOptions { OfferThroughput = 1000 }); + } + else + { + throw; + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/Item.cs b/ApiAsAService/ApiAsAService/Item.cs new file mode 100644 index 0000000..83b414d --- /dev/null +++ b/ApiAsAService/ApiAsAService/Item.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace DynamicEdmModelCreation +{ + public class Item + { + public string id { get; set; } + public string csdl { get; set; } + } +} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/Program.cs b/ApiAsAService/ApiAsAService/Program.cs index 35633fa..d057951 100644 --- a/ApiAsAService/ApiAsAService/Program.cs +++ b/ApiAsAService/ApiAsAService/Program.cs @@ -5,8 +5,12 @@ using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.Documents.Linq; using Microsoft.Owin.Hosting; using Owin; +using todo; namespace DynamicEdmModelCreation { @@ -16,6 +20,8 @@ class Program public static void Main(string[] args) { + GetTrippin().Wait(); + using (WebApp.Start(serviceUrl, Configuration)) { Console.WriteLine("Server listening at {0}", serviceUrl); @@ -27,6 +33,16 @@ public static void Main(string[] args) } } + public static async Task GetTrippin() + { + DocumentDBRepository.Initialize(); + var item = await DocumentDBRepository.GetSchema(("trippinschema")); + Console.Write(item.csdl); + Console.WriteLine("\nDone fetching the schema"); + Console.ReadKey(); + return; + } + private static async Task QueryTheService() { await SendQuery("/odata/mydatasource/", "Query service document."); From 7053d98e1ed58e16fe1ccea4639084bdef9ad843 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Mon, 22 Jul 2019 16:53:20 -0700 Subject: [PATCH 05/21] Update references --- .../Extensions/HttpConfigurationExtensions.cs | 1 - .../Microsoft.Restier.AspNet.csproj | 2 + .../Microsoft.Restier.Core.csproj | 1 + .../Microsoft.Restier.EntityFramework.csproj | 1 + .../Web.config | 4 + ApiAsAService/RESTierAsAService.sln | 9 +++ .../GlobalSuppressions.cs | 3 + .../Microsoft.AspNet.OData.csproj | 77 ++++++++++++++----- .../src/Microsoft.AspNet.OData/app.config | 8 +- .../Microsoft.AspNet.OData/packages.config | 18 +++-- 10 files changed, 94 insertions(+), 30 deletions(-) diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs index 54c06f6..b6b6c04 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNet.OData.Batch; using Microsoft.AspNet.OData.Extensions; diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj index d8890b1..efe949d 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj @@ -22,6 +22,8 @@ + + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj index 5c7e332..bb2661b 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj @@ -42,6 +42,7 @@ + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj index 5f1022e..35eb3ed 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj @@ -21,6 +21,7 @@ + diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config index a63ac4b..71bf8a1 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Samples.Northwind.AspNet/Web.config @@ -49,6 +49,10 @@ + + + + diff --git a/ApiAsAService/RESTierAsAService.sln b/ApiAsAService/RESTierAsAService.sln index ffa986e..56d69d5 100644 --- a/ApiAsAService/RESTierAsAService.sln +++ b/ApiAsAService/RESTierAsAService.sln @@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI", "WebAPI", "{B43F20 EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.Sample.Trippin", "Trippin\Trippin\Microsoft.OData.Service.Sample.Trippin.csproj", "{B379640E-9064-438D-8DA5-6F7B394C2C46}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 @@ -82,6 +84,12 @@ Global {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -97,6 +105,7 @@ Global {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2} = {DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F} {A6F9775D-F7E2-424E-8363-79644A73038F} = {B43F200F-B847-48BE-9362-36D94C48521D} {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {B43F200F-B847-48BE-9362-36D94C48521D} + {B379640E-9064-438D-8DA5-6F7B394C2C46} = {DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5A37189C-A5E1-4871-AF65-8EBF2DA60FE3} diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs index b546149..3d1d4cd 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/GlobalSuppressions.cs @@ -36,4 +36,7 @@ [assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "skiptoken", Scope = "resource", Target = "Microsoft.AspNet.OData.Properties.SRResources.resources")][assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Subquery", Scope = "member", Target = "Microsoft.AspNet.OData.Query.ODataQuerySettings.#EnableCorrelatedSubqueryBuffering")] [assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Subquery", Scope = "member", Target = "Microsoft.AspNet.OData.EnableQueryAttribute.#EnableCorrelatedSubqueryBuffering")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder.#ProjectCollection(System.Linq.Expressions.Expression,System.Type,Microsoft.OData.UriParser.SelectExpandClause,Microsoft.OData.Edm.IEdmEntityType,Microsoft.OData.Edm.IEdmNavigationSource,Microsoft.OData.UriParser.ExpandedReferenceSelectItem,System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.AspNet.OData.Query.DefaultSkipTokenHandler.#ApplyToCore(System.Linq.IQueryable,Microsoft.AspNet.OData.Query.ODataQuerySettings,System.Collections.Generic.IList`1,Microsoft.AspNet.OData.ODataQueryContext,System.String)")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.AspNet.OData.ODataQueryParameterBindingAttribute+ODataQueryParameterBinding.#ExecuteBindingAsync(System.Web.Http.Metadata.ModelMetadataProvider,System.Web.Http.Controllers.HttpActionContext,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Text.StringBuilder.AppendFormat(System.String,System.Object)", Scope = "member", Target = "Microsoft.AspNet.OData.GetNextPageHelper.#GetNextPageLink(System.Uri,System.Collections.Generic.IEnumerable`1>,System.Int32,System.Object,System.Func`2)")] diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj index 55b0299..ee3e684 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj @@ -12,17 +12,17 @@ true ..\Strict.ruleset true - v4.5 + v4.6.2 $(DefineConstants);ASPNETODATA;ASPNETWEBAPI;NETFX;NETFX45 + - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.2.2.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll - - ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - True + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll ..\..\..\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll @@ -33,38 +33,62 @@ ..\..\..\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - - ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - True + + ..\..\..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + - - ..\..\..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.dll + + ..\..\..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll True True ..\..\..\packages\System.Net.Http.Formatting.Extension.5.2.3.0\lib\System.Net.Http.Extensions.dll - - ..\..\..\packages\System.Net.Http.Formatting.Extension.5.2.3.0\lib\System.Net.Http.Formatting.dll + + ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll ..\..\..\packages\System.Net.Http.Formatting.Extension.5.2.3.0\lib\System.Net.Http.Primitives.dll - - ..\..\..\packages\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.WebRequest.dll + + ..\..\..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll True True - - False - ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll + + ..\..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + True + True + + + ..\..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + True + + + ..\..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + True + + + ..\..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + True + + + ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll @@ -73,6 +97,18 @@ Properties\CommonAssemblyInfo.cs + + Properties\CommonWebApiResources1.Designer.cs + True + True + CommonWebApiResources.resx + + + Properties\SRResources1.Designer.cs + True + True + SRResources.resx + @@ -169,6 +205,7 @@ Properties\CommonWebApiResources.resx ResXFileCodeGenerator Designer + CommonWebApiResources1.Designer.cs Properties\SRResources.resx @@ -194,7 +231,9 @@ - + + Designer + diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config index dde2c3c..835ae48 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config @@ -1,11 +1,11 @@ - + - - + + - \ No newline at end of file + diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config index 2753bfa..c317403 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/packages.config @@ -1,14 +1,20 @@  - - - - + + + + - - + + + + + + + + \ No newline at end of file From 833cfe628f7d4dae29036a0dfd9562ad67dd7c2e Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Tue, 23 Jul 2019 01:28:04 -0700 Subject: [PATCH 06/21] Add support for dynamic model building --- .../Extensions/ServiceCollectionExtensions.cs | 3 +- ApiAsAService/RESTierAsAService.sln | 10 +- .../Trippin/Trippin/Api/DynamicApi.cs | 219 +++++ .../Trippin/Trippin/Api/TrippinApi.cs | 196 ----- .../Trippin/Trippin/App_Start/WebApiConfig.cs | 14 +- .../Trippin/Controllers/TrippinController.cs | 796 +++++++++--------- ApiAsAService/Trippin/Trippin/Global.asax | 2 +- ApiAsAService/Trippin/Trippin/Global.asax.cs | 2 +- ApiAsAService/Trippin/Trippin/Helpers.cs | 2 +- ...rosoft.OData.Service.ApiAsAService.csproj} | 6 +- .../Trippin/Trippin/Models/Airline.cs | 2 +- .../Trippin/Trippin/Models/Airport.cs | 2 +- .../Models/CustomizedPayloadValueConverter.cs | 2 +- .../Trippin/Trippin/Models/Employee.cs | 2 +- ApiAsAService/Trippin/Trippin/Models/Event.cs | 2 +- .../Trippin/Trippin/Models/Feature.cs | 2 +- .../Trippin/Trippin/Models/Flight.cs | 2 +- .../Trippin/Trippin/Models/Location.cs | 2 +- .../Trippin/Trippin/Models/Manager.cs | 2 +- ApiAsAService/Trippin/Trippin/Models/Order.cs | 2 +- .../Trippin/Trippin/Models/Person.cs | 2 +- .../Trippin/Trippin/Models/SpecialOrder.cs | 2 +- ApiAsAService/Trippin/Trippin/Models/Trip.cs | 2 +- .../Trippin/Trippin/Models/TrippinModel.cs | 2 +- .../Trippin/Properties/AssemblyInfo.cs | 4 +- .../Submit/CustomizedSubmitProcessor.cs | 2 +- 26 files changed, 651 insertions(+), 633 deletions(-) create mode 100644 ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Api/TrippinApi.cs rename ApiAsAService/Trippin/Trippin/{Microsoft.OData.Service.Sample.Trippin.csproj => Microsoft.OData.Service.ApiAsAService.csproj} (98%) diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs index 32b39fb..8f44709 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs @@ -216,7 +216,8 @@ public static IServiceCollection AddCoreServices(this IServiceCollection service services.AddScoped(apiType, apiType) .AddScoped(typeof(ApiBase), apiType); - services.TryAddSingleton(); + //services.TryAddSingleton(); + services.TryAddScoped(); return services.AddService() .AddScoped(); diff --git a/ApiAsAService/RESTierAsAService.sln b/ApiAsAService/RESTierAsAService.sln index 56d69d5..af3ab97 100644 --- a/ApiAsAService/RESTierAsAService.sln +++ b/ApiAsAService/RESTierAsAService.sln @@ -26,8 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Restier.Samples.Northwind.AspNet", "RESTier\src\Microsoft.Restier.Samples.Northwind.AspNet\Microsoft.Restier.Samples.Northwind.AspNet.csproj", "{3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RESTier", "RESTier", "{76B4E51F-233E-4DD3-AABF-A6F47788040D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "WebAPI\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" @@ -36,7 +34,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI", "WebAPI", "{B43F20 EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.Sample.Trippin", "Trippin\Trippin\Microsoft.OData.Service.Sample.Trippin.csproj", "{B379640E-9064-438D-8DA5-6F7B394C2C46}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.ApiAsAService", "Trippin\Trippin\Microsoft.OData.Service.ApiAsAService.csproj", "{B379640E-9064-438D-8DA5-6F7B394C2C46}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -73,11 +71,6 @@ Global {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E373B2A-2ED2-4566-A275-6BE81CFFE00B}.Release|Any CPU.Build.0 = Release|Any CPU - {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2}.Release|Any CPU.Build.0 = Release|Any CPU {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -102,7 +95,6 @@ Global {97E94F97-E73B-4074-8587-AE1B91B4D61E} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} {8ECF4E97-1816-44AD-AD63-6ACF287ED520} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} {0E373B2A-2ED2-4566-A275-6BE81CFFE00B} = {37B52FD3-E72B-406F-8C5A-F146256D7743} - {3EAB0AED-2BE2-4120-B26E-3401B86C4DC2} = {DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F} {A6F9775D-F7E2-424E-8363-79644A73038F} = {B43F200F-B847-48BE-9362-36D94C48521D} {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {B43F200F-B847-48BE-9362-36D94C48521D} {B379640E-9064-438D-8DA5-6F7B394C2C46} = {DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F} diff --git a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs new file mode 100644 index 0000000..d695997 --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs @@ -0,0 +1,219 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.OData.Query; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Service.ApiAsAService.Models; +using Microsoft.OData.Service.ApiAsAService.Submit; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Submit; +using Microsoft.Restier.EntityFramework; +using Microsoft.Restier.AspNet.Model; + +namespace Microsoft.OData.Service.ApiAsAService.Api +{ + public class DynamicApi : EntityFrameworkApi + { + public TrippinModel ModelContext { get { return DbContext; } } + + //[Resource] + //public Person Me + //{ + // get + // { + // return DbContext.People + // .Include("Friends") + // .Include("Trips") + // .Single(p => p.PersonId == 1); + // } + //} + + //private IQueryable PeopleWithFriends + //{ + // get { return ModelContext.People.Include("Friends"); } + //} + + ///// + ///// Implements an action import. + ///// + //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", HasSideEffects = true)] + //public void ResetDataSource() + //{ + // TrippinModel.ResetDataSource(); + //} + + ///// + ///// Action import - clean up all the expired trips. + ///// + //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", HasSideEffects = true)] + //public void CleanUpExpiredTrips() + //{ + // // DO NOT ACTUALLY REMOVE THE TRIPS. + //} + + ///// + ///// Bound action - set the end-up time of a trip. + ///// + ///// The trip to update. + ///// The trip updated. + //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", IsBound = true, HasSideEffects = true)] + //public Trip EndTrip(Trip trip) + //{ + // // DO NOT ACTUALLY UPDATE THE TRIP. + // return trip; + //} + + ///// + ///// Bound function - gets the number of friends of a person. + ///// + ///// The key of the binding person. + ///// The number of friends of the person. + //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", IsBound = true)] + //public int GetNumberOfFriends(Person person) + //{ + // if (person == null) + // { + // return 0; + // } + + // var personWithFriends = PeopleWithFriends.Single(p => p.PersonId == person.PersonId); + // return personWithFriends.Friends == null ? 0 : personWithFriends.Friends.Count; + //} + + ///// + ///// Function import - gets the person with most friends. + ///// + ///// The person with most friends. + //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", EntitySet = "People")] + //public Person GetPersonWithMostFriends() + //{ + // Person result = null; + + // foreach (var person in PeopleWithFriends) + // { + // if (person.Friends == null) + // { + // continue; + // } + + // if (result == null) + // { + // result = person; + // } + + // if (person.Friends.Count > result.Friends.Count) + // { + // result = person; + // } + // } + + // return result; + //} + + ///// + ///// Function import - gets people with at least n friends. + ///// + ///// The minimum number of friends. + ///// People with at least n friends. + //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", EntitySet = "People")] + //public IEnumerable GetPeopleWithFriendsAtLeast(int n) + //{ + // foreach (var person in PeopleWithFriends) + // { + // if (person.Friends == null) + // { + // continue; + // } + + // if (person.Friends.Count >= n) + // { + // yield return person; + // } + // } + //} + + //protected bool CanDeleteTrips() + //{ + // return false; + //} + + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + { + // Add customized OData validation settings + Func validationSettingFactory = (sp) => new ODataValidationSettings + { + MaxAnyAllExpressionDepth =3, + MaxExpansionDepth = 3 + }; + + IServiceCollection serviceCollection = EntityFrameworkApi.ConfigureApi(apiType, services) + .AddSingleton() + .AddSingleton(validationSettingFactory) + .AddService() + .AddService(); + + return serviceCollection; + } + + + private class DynamicModelBuilder : IModelBuilder + { + public IModelBuilder InnerHandler { get; set; } + + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + { + EdmModel model = new EdmModel(); + EdmEntityContainer container = new EdmEntityContainer("Microsoft.OData.Service.ApiAsAService.Models", "container"); + model.AddElement(container); + + EdmEntityType person = new EdmEntityType("Microsoft.OData.Service.ApiAsAService.Models", "Person"); + person.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); + EdmStructuralProperty key = person.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32); + person.AddKeys(key); + model.AddElement(person); + EdmEntitySet people = container.AddEntitySet("People", person); + + EdmEntityType detailInfo = new EdmEntityType("Microsoft.OData.Service.ApiAsAService.Models", "DetailInfo"); + detailInfo.AddKeys(detailInfo.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32)); + detailInfo.AddStructuralProperty("Title", EdmPrimitiveTypeKind.String); + model.AddElement(detailInfo); + EdmEntitySet detailInfos = container.AddEntitySet("DetailInfos", person); + + EdmNavigationProperty detailInfoNavProp = person.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo + { + Name = "DetailInfo", + TargetMultiplicity = EdmMultiplicity.One, + Target = detailInfo + }); + people.AddNavigationTarget(detailInfoNavProp, detailInfos); + + //var model = await InnerHandler.GetModelAsync(context, cancellationToken); + + //// Set computed annotation + //var tripType = (EdmEntityType)model.SchemaElements.Single(e => e.Name == "Trip"); + //var trackGuidProperty = tripType.DeclaredProperties.Single(prop => prop.Name == "TrackGuid"); + //var timeStampValueProp= model.EntityContainer.FindEntitySet("Airlines").EntityType().FindProperty("TimeStampValue"); + //var term = new EdmTerm("Org.OData.Core.V1", "Computed", EdmPrimitiveTypeKind.Boolean); + //var anno1 = new EdmVocabularyAnnotation(trackGuidProperty, term, new EdmBooleanConstant(true)); + //var anno2 = new EdmVocabularyAnnotation(timeStampValueProp, term, new EdmBooleanConstant(true)); + //((EdmModel)model).SetVocabularyAnnotation(anno1); + //((EdmModel)model).SetVocabularyAnnotation(anno2); + + return model; + } + } + + public DynamicApi(IServiceProvider serviceProvider) : base(serviceProvider) + { + } + } + } \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Api/TrippinApi.cs b/ApiAsAService/Trippin/Trippin/Api/TrippinApi.cs deleted file mode 100644 index 3f0f140..0000000 --- a/ApiAsAService/Trippin/Trippin/Api/TrippinApi.cs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNet.OData.Query; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData.Edm; -using Microsoft.OData.Edm.Vocabularies; -using Microsoft.OData.Service.Sample.Trippin.Models; -using Microsoft.OData.Service.Sample.Trippin.Submit; -using Microsoft.Restier.Core; -using Microsoft.Restier.Core.Model; -using Microsoft.Restier.Core.Submit; -using Microsoft.Restier.EntityFramework; -using Microsoft.Restier.AspNet.Model; - -namespace Microsoft.OData.Service.Sample.Trippin.Api -{ - public class TrippinApi : EntityFrameworkApi - { - public TrippinModel ModelContext { get { return DbContext; } } - - [Resource] - public Person Me - { - get - { - return DbContext.People - .Include("Friends") - .Include("Trips") - .Single(p => p.PersonId == 1); - } - } - - private IQueryable PeopleWithFriends - { - get { return ModelContext.People.Include("Friends"); } - } - - /// - /// Implements an action import. - /// - [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", HasSideEffects = true)] - public void ResetDataSource() - { - TrippinModel.ResetDataSource(); - } - - /// - /// Action import - clean up all the expired trips. - /// - [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", HasSideEffects = true)] - public void CleanUpExpiredTrips() - { - // DO NOT ACTUALLY REMOVE THE TRIPS. - } - - /// - /// Bound action - set the end-up time of a trip. - /// - /// The trip to update. - /// The trip updated. - [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", IsBound = true, HasSideEffects = true)] - public Trip EndTrip(Trip trip) - { - // DO NOT ACTUALLY UPDATE THE TRIP. - return trip; - } - - /// - /// Bound function - gets the number of friends of a person. - /// - /// The key of the binding person. - /// The number of friends of the person. - [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", IsBound = true)] - public int GetNumberOfFriends(Person person) - { - if (person == null) - { - return 0; - } - - var personWithFriends = PeopleWithFriends.Single(p => p.PersonId == person.PersonId); - return personWithFriends.Friends == null ? 0 : personWithFriends.Friends.Count; - } - - /// - /// Function import - gets the person with most friends. - /// - /// The person with most friends. - [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", EntitySet = "People")] - public Person GetPersonWithMostFriends() - { - Person result = null; - - foreach (var person in PeopleWithFriends) - { - if (person.Friends == null) - { - continue; - } - - if (result == null) - { - result = person; - } - - if (person.Friends.Count > result.Friends.Count) - { - result = person; - } - } - - return result; - } - - /// - /// Function import - gets people with at least n friends. - /// - /// The minimum number of friends. - /// People with at least n friends. - [Operation(Namespace = "Microsoft.OData.Service.Sample.Trippin.Models", EntitySet = "People")] - public IEnumerable GetPeopleWithFriendsAtLeast(int n) - { - foreach (var person in PeopleWithFriends) - { - if (person.Friends == null) - { - continue; - } - - if (person.Friends.Count >= n) - { - yield return person; - } - } - } - - protected bool CanDeleteTrips() - { - return false; - } - - protected static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) - { - // Add customized OData validation settings - Func validationSettingFactory = (sp) => new ODataValidationSettings - { - MaxAnyAllExpressionDepth =3, - MaxExpansionDepth = 3 - }; - - services.MakeTransient(); - - IServiceCollection serviceCollection = EntityFrameworkApi.ConfigureApi(apiType, services) - .AddSingleton() - .AddSingleton(validationSettingFactory) - .AddService() - .AddService(); - - serviceCollection.MakeTransient(); - return serviceCollection; - } - - - private class TrippinModelExtender : IModelBuilder - { - public IModelBuilder InnerHandler { get; set; } - - public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) - { - var model = await InnerHandler.GetModelAsync(context, cancellationToken); - - // Set computed annotation - var tripType = (EdmEntityType)model.SchemaElements.Single(e => e.Name == "Trip"); - var trackGuidProperty = tripType.DeclaredProperties.Single(prop => prop.Name == "TrackGuid"); - var timeStampValueProp= model.EntityContainer.FindEntitySet("Airlines").EntityType().FindProperty("TimeStampValue"); - var term = new EdmTerm("Org.OData.Core.V1", "Computed", EdmPrimitiveTypeKind.Boolean); - var anno1 = new EdmVocabularyAnnotation(trackGuidProperty, term, new EdmBooleanConstant(true)); - var anno2 = new EdmVocabularyAnnotation(timeStampValueProp, term, new EdmBooleanConstant(true)); - ((EdmModel)model).SetVocabularyAnnotation(anno1); - ((EdmModel)model).SetVocabularyAnnotation(anno2); - - return model; - } - } - - public TrippinApi(IServiceProvider serviceProvider) : base(serviceProvider) - { - } - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs index a25604b..b028403 100644 --- a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs +++ b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs @@ -3,26 +3,28 @@ using System.Web.Http; using Microsoft.AspNet.OData.Extensions; -using Microsoft.OData.Service.Sample.Trippin.Api; +using Microsoft.OData.Service.ApiAsAService.Api; using Microsoft.Restier.AspNet.Batch; -namespace Microsoft.OData.Service.Sample.Trippin +namespace Microsoft.OData.Service.ApiAsAService { public static class WebApiConfig { public static void Register(HttpConfiguration config) { - RegisterTrippin(config, GlobalConfiguration.DefaultServer); + RegisterService(config, GlobalConfiguration.DefaultServer); } - public static async void RegisterTrippin( + public static async void RegisterService( HttpConfiguration config, HttpServer server) { // enable query options for all properties config.Filter().Expand().Select().OrderBy().MaxTop(null).Count(); - await config.MapRestierRoute( - "TrippinApi", "", + await config.MapRestierRoute( + "ApiAsAService", "", new RestierBatchHandler(server)); } + + } } diff --git a/ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs b/ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs index 2a14254..0dd2f14 100644 --- a/ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs +++ b/ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs @@ -11,408 +11,408 @@ using Microsoft.AspNet.OData.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; -using Microsoft.OData.Service.Sample.Trippin.Api; -using Microsoft.OData.Service.Sample.Trippin.Models; +using Microsoft.OData.Service.ApiAsAService.Api; +using Microsoft.OData.Service.ApiAsAService.Models; using Microsoft.Restier.Core; -namespace Microsoft.OData.Service.Sample.Trippin.Controllers +namespace Microsoft.OData.Service.ApiAsAService.Controllers { public class TrippinController : ODataController { - private TrippinModel DbContext - { - get - { - var api = (TrippinApi)this.Request.GetRequestContainer().GetService(); - return api.ModelContext; - } - } - - private bool PeopleExists(int key) - { - return DbContext.People.Any(p => p.PersonId == key); - } - - private string GetServiceRootUri() - { - var routeName = Request.ODataProperties().RouteName; - ODataRoute odataRoute = Configuration.Routes[routeName] as ODataRoute; - var prefixName = odataRoute.RoutePrefix; - var requestUri = Request.RequestUri.ToString(); - var serviceRootUri = requestUri.Substring(0, requestUri.IndexOf(prefixName, StringComparison.InvariantCultureIgnoreCase) + prefixName.Length); - return serviceRootUri; - } - - [HttpPut] - [ODataRoute("People({key})/LastName")] - public IHttpActionResult UpdatePersonLastName([FromODataUri]int key, [FromBody]string name) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.LastName = name; - - try - { - DbContext.SaveChanges(); - } - catch (Exception e) - { - if (!PeopleExists(key)) - { - return NotFound(); - } - else - { - throw e; - } - } - return Ok(name); - } - - [HttpPut] - [ODataRoute("People({key})/BirthDate")] - public IHttpActionResult UpdatePersonBirthDate([FromODataUri]int key, [FromBody]string birthDate) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.BirthDate = Date.Parse(birthDate); - - try - { - DbContext.SaveChanges(); - } - catch (Exception e) - { - if (!PeopleExists(key)) - { - return NotFound(); - } - else - { - throw e; - } - } - return Ok(birthDate); - } - - [HttpPut] - [ODataRoute("People({key})/BirthDate2")] - public IHttpActionResult UpdatePersonBirthDate2([FromODataUri]int key, [FromBody]string birthDate) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.BirthDate2 = Date.Parse(birthDate); - - try - { - DbContext.SaveChanges(); - } - catch (Exception e) - { - if (!PeopleExists(key)) - { - return NotFound(); - } - else - { - throw e; - } - } - return Ok(birthDate); - } - - [HttpPut] - [ODataRoute("People({key})/BirthTime")] - public IHttpActionResult UpdatePersonBirthTime([FromODataUri]int key, [FromBody]string birthTime) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.BirthTime = TimeOfDay.Parse(birthTime); - - try - { - DbContext.SaveChanges(); - } - catch (Exception e) - { - if (!PeopleExists(key)) - { - return NotFound(); - } - else - { - throw e; - } - } - return Ok(birthTime); - } - - [HttpPut] - [ODataRoute("People({key})/BirthTime2")] - public IHttpActionResult UpdatePersonBirthTime2([FromODataUri]int key, [FromBody]string birthTime) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.BirthTime2 = TimeOfDay.Parse(birthTime); - - try - { - DbContext.SaveChanges(); - } - catch (Exception e) - { - if (!PeopleExists(key)) - { - return NotFound(); - } - else - { - throw e; - } - } - return Ok(birthTime); - } - - [HttpPut] - [ODataRoute("People({key})/BirthDateTime")] - public IHttpActionResult UpdatePersonBirthDateTime([FromODataUri]int key, [FromBody]string birthDateTime) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.BirthDateTime = DateTimeOffset.Parse(birthDateTime).DateTime; - - try - { - DbContext.SaveChanges(); - } - catch (Exception e) - { - if (!PeopleExists(key)) - { - return NotFound(); - } - else - { - throw e; - } - } - return Ok(birthDateTime); - } - - [HttpPut] - [ODataRoute("People({key})/BirthDateTime2")] - public IHttpActionResult UpdatePersonBirthDateTime2([FromODataUri]int key, [FromBody]string birthDateTime) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.BirthDateTime2 = DateTimeOffset.Parse(birthDateTime).DateTime; - - try - { - DbContext.SaveChanges(); - } - catch (Exception e) - { - if (!PeopleExists(key)) - { - return NotFound(); - } - else - { - throw e; - } - } - return Ok(birthDateTime); - } - - [HttpGet] - [ODataRoute("People({key})/Trips/$ref")] - public IHttpActionResult GetRefToTripsFromPeople([FromODataUri]int key) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - var trips = DbContext.Trips.Where(t => t.PersonId == key); - var serviceRootUri = GetServiceRootUri(); - IList uris = new List(); - foreach (var trip in trips) - { - uris.Add(new Uri(string.Format("{0}/Trips({1})", serviceRootUri, trip.TripId))); - } - return Ok(uris); - } - - [HttpGet] - [ODataRoute("People({key})/Trips({key2})/$ref")] - public IHttpActionResult GetRefToTripsFromPeople([FromODataUri]int key, [FromODataUri]int key2) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - var trips = DbContext.Trips.Where(t => t.PersonId == key); - var serviceRootUri = GetServiceRootUri(); - - if (trips.All(t => t.TripId != key2)) - { - return NotFound(); - } - - return Ok(new Uri(string.Format("{0}/Trips({1})", serviceRootUri, key2))); - } - - [HttpPost] - [ODataRoute("People({key})/Trips")] - public IHttpActionResult PostToTripsFromPeople([FromODataUri]int key, Trip trip) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - if (entity.PersonId != key) - { - return BadRequest(); - } - DbContext.Trips.Add(trip); - DbContext.SaveChanges(); - return Created(trip); - } - - [HttpPost] - [ODataRoute("People({key})/Trips/$ref")] - public IHttpActionResult CreateRefForTripsToPeople([FromODataUri]int key, [FromBody] Uri link) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - var relatedKey = Helpers.GetKeyFromUri(Request, link); - var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == relatedKey); - if (trip == null) - { - return NotFound(); - } - - trip.PersonId = key; - DbContext.SaveChanges(); - - return StatusCode(HttpStatusCode.NoContent); - } - - [HttpDelete] - [ODataRoute("People({key})/Trips({relatedKey})/$ref")] - public IHttpActionResult DeleteRefToTripsFromPeople([FromODataUri]int key, [FromODataUri]int relatedKey) - { - var entity = DbContext.People.Find(key); - if (entity == null) - { - return NotFound(); - } - - var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == relatedKey && t.PersonId == key); - if (trip == null) - { - return NotFound(); - } - - trip.PersonId = null; - DbContext.SaveChanges(); - - return StatusCode(HttpStatusCode.NoContent); - } - - [HttpGet] - [ODataRoute("Flights({key})/Airline/$ref")] - public IHttpActionResult GetRefToAirlineFromFlight([FromODataUri]int key) - { - var entity = DbContext.Flights.Find(key); - if (entity == null) - { - return NotFound(); - } - - if (entity.AirlineId == null) - { - return NotFound(); - } - - var serviceRootUri = GetServiceRootUri(); - var uri = new Uri(string.Format("{0}/Airlines('{1}')", serviceRootUri, entity.AirlineId)); - return Ok(uri); - } - - [HttpPut] - [ODataRoute("Flights({key})/Airline/$ref")] - public IHttpActionResult UpdateRefToAirLineFromFlight([FromODataUri] int key, [FromBody] Uri link) - { - var entity = DbContext.Flights.Find(key); - if (entity == null) - { - return NotFound(); - } - - var relatedKey = Helpers.GetKeyFromUri(Request, link); - var aireLine = DbContext.Airlines - .SingleOrDefault(t => t.AirlineCode.Equals(relatedKey, StringComparison.OrdinalIgnoreCase)); - if (aireLine == null) - { - return NotFound(); - } - - entity.Airline = aireLine; - DbContext.SaveChanges(); - - return StatusCode(HttpStatusCode.NoContent); - } - - [HttpDelete] - [ODataRoute("Flights({key})/Airline/$ref")] - public IHttpActionResult DeleteRefToAirLineFromFlight([FromODataUri] int key) - { - var entity = DbContext.Flights.Find(key); - if (entity == null) - { - return NotFound(); - } - - entity.AirlineId = null; - DbContext.SaveChanges(); - - return StatusCode(HttpStatusCode.NoContent); - } + //private TrippinModel DbContext + //{ + // get + // { + // var api = (DynamicApi)this.Request.GetRequestContainer().GetService(); + // return api.ModelContext; + // } + //} + + //private bool PeopleExists(int key) + //{ + // return DbContext.People.Any(p => p.PersonId == key); + //} + + //private string GetServiceRootUri() + //{ + // var routeName = Request.ODataProperties().RouteName; + // ODataRoute odataRoute = Configuration.Routes[routeName] as ODataRoute; + // var prefixName = odataRoute.RoutePrefix; + // var requestUri = Request.RequestUri.ToString(); + // var serviceRootUri = requestUri.Substring(0, requestUri.IndexOf(prefixName, StringComparison.InvariantCultureIgnoreCase) + prefixName.Length); + // return serviceRootUri; + //} + + //[HttpPut] + //[ODataRoute("People({key})/LastName")] + //public IHttpActionResult UpdatePersonLastName([FromODataUri]int key, [FromBody]string name) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.LastName = name; + + // try + // { + // DbContext.SaveChanges(); + // } + // catch (Exception e) + // { + // if (!PeopleExists(key)) + // { + // return NotFound(); + // } + // else + // { + // throw e; + // } + // } + // return Ok(name); + //} + + //[HttpPut] + //[ODataRoute("People({key})/BirthDate")] + //public IHttpActionResult UpdatePersonBirthDate([FromODataUri]int key, [FromBody]string birthDate) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.BirthDate = Date.Parse(birthDate); + + // try + // { + // DbContext.SaveChanges(); + // } + // catch (Exception e) + // { + // if (!PeopleExists(key)) + // { + // return NotFound(); + // } + // else + // { + // throw e; + // } + // } + // return Ok(birthDate); + //} + + //[HttpPut] + //[ODataRoute("People({key})/BirthDate2")] + //public IHttpActionResult UpdatePersonBirthDate2([FromODataUri]int key, [FromBody]string birthDate) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.BirthDate2 = Date.Parse(birthDate); + + // try + // { + // DbContext.SaveChanges(); + // } + // catch (Exception e) + // { + // if (!PeopleExists(key)) + // { + // return NotFound(); + // } + // else + // { + // throw e; + // } + // } + // return Ok(birthDate); + //} + + //[HttpPut] + //[ODataRoute("People({key})/BirthTime")] + //public IHttpActionResult UpdatePersonBirthTime([FromODataUri]int key, [FromBody]string birthTime) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.BirthTime = TimeOfDay.Parse(birthTime); + + // try + // { + // DbContext.SaveChanges(); + // } + // catch (Exception e) + // { + // if (!PeopleExists(key)) + // { + // return NotFound(); + // } + // else + // { + // throw e; + // } + // } + // return Ok(birthTime); + //} + + //[HttpPut] + //[ODataRoute("People({key})/BirthTime2")] + //public IHttpActionResult UpdatePersonBirthTime2([FromODataUri]int key, [FromBody]string birthTime) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.BirthTime2 = TimeOfDay.Parse(birthTime); + + // try + // { + // DbContext.SaveChanges(); + // } + // catch (Exception e) + // { + // if (!PeopleExists(key)) + // { + // return NotFound(); + // } + // else + // { + // throw e; + // } + // } + // return Ok(birthTime); + //} + + //[HttpPut] + //[ODataRoute("People({key})/BirthDateTime")] + //public IHttpActionResult UpdatePersonBirthDateTime([FromODataUri]int key, [FromBody]string birthDateTime) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.BirthDateTime = DateTimeOffset.Parse(birthDateTime).DateTime; + + // try + // { + // DbContext.SaveChanges(); + // } + // catch (Exception e) + // { + // if (!PeopleExists(key)) + // { + // return NotFound(); + // } + // else + // { + // throw e; + // } + // } + // return Ok(birthDateTime); + //} + + //[HttpPut] + //[ODataRoute("People({key})/BirthDateTime2")] + //public IHttpActionResult UpdatePersonBirthDateTime2([FromODataUri]int key, [FromBody]string birthDateTime) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.BirthDateTime2 = DateTimeOffset.Parse(birthDateTime).DateTime; + + // try + // { + // DbContext.SaveChanges(); + // } + // catch (Exception e) + // { + // if (!PeopleExists(key)) + // { + // return NotFound(); + // } + // else + // { + // throw e; + // } + // } + // return Ok(birthDateTime); + //} + + //[HttpGet] + //[ODataRoute("People({key})/Trips/$ref")] + //public IHttpActionResult GetRefToTripsFromPeople([FromODataUri]int key) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + // var trips = DbContext.Trips.Where(t => t.PersonId == key); + // var serviceRootUri = GetServiceRootUri(); + // IList uris = new List(); + // foreach (var trip in trips) + // { + // uris.Add(new Uri(string.Format("{0}/Trips({1})", serviceRootUri, trip.TripId))); + // } + // return Ok(uris); + //} + + //[HttpGet] + //[ODataRoute("People({key})/Trips({key2})/$ref")] + //public IHttpActionResult GetRefToTripsFromPeople([FromODataUri]int key, [FromODataUri]int key2) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + // var trips = DbContext.Trips.Where(t => t.PersonId == key); + // var serviceRootUri = GetServiceRootUri(); + + // if (trips.All(t => t.TripId != key2)) + // { + // return NotFound(); + // } + + // return Ok(new Uri(string.Format("{0}/Trips({1})", serviceRootUri, key2))); + //} + + //[HttpPost] + //[ODataRoute("People({key})/Trips")] + //public IHttpActionResult PostToTripsFromPeople([FromODataUri]int key, Trip trip) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + // if (entity.PersonId != key) + // { + // return BadRequest(); + // } + // DbContext.Trips.Add(trip); + // DbContext.SaveChanges(); + // return Created(trip); + //} + + //[HttpPost] + //[ODataRoute("People({key})/Trips/$ref")] + //public IHttpActionResult CreateRefForTripsToPeople([FromODataUri]int key, [FromBody] Uri link) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // var relatedKey = Helpers.GetKeyFromUri(Request, link); + // var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == relatedKey); + // if (trip == null) + // { + // return NotFound(); + // } + + // trip.PersonId = key; + // DbContext.SaveChanges(); + + // return StatusCode(HttpStatusCode.NoContent); + //} + + //[HttpDelete] + //[ODataRoute("People({key})/Trips({relatedKey})/$ref")] + //public IHttpActionResult DeleteRefToTripsFromPeople([FromODataUri]int key, [FromODataUri]int relatedKey) + //{ + // var entity = DbContext.People.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == relatedKey && t.PersonId == key); + // if (trip == null) + // { + // return NotFound(); + // } + + // trip.PersonId = null; + // DbContext.SaveChanges(); + + // return StatusCode(HttpStatusCode.NoContent); + //} + + //[HttpGet] + //[ODataRoute("Flights({key})/Airline/$ref")] + //public IHttpActionResult GetRefToAirlineFromFlight([FromODataUri]int key) + //{ + // var entity = DbContext.Flights.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // if (entity.AirlineId == null) + // { + // return NotFound(); + // } + + // var serviceRootUri = GetServiceRootUri(); + // var uri = new Uri(string.Format("{0}/Airlines('{1}')", serviceRootUri, entity.AirlineId)); + // return Ok(uri); + //} + + //[HttpPut] + //[ODataRoute("Flights({key})/Airline/$ref")] + //public IHttpActionResult UpdateRefToAirLineFromFlight([FromODataUri] int key, [FromBody] Uri link) + //{ + // var entity = DbContext.Flights.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // var relatedKey = Helpers.GetKeyFromUri(Request, link); + // var aireLine = DbContext.Airlines + // .SingleOrDefault(t => t.AirlineCode.Equals(relatedKey, StringComparison.OrdinalIgnoreCase)); + // if (aireLine == null) + // { + // return NotFound(); + // } + + // entity.Airline = aireLine; + // DbContext.SaveChanges(); + + // return StatusCode(HttpStatusCode.NoContent); + //} + + //[HttpDelete] + //[ODataRoute("Flights({key})/Airline/$ref")] + //public IHttpActionResult DeleteRefToAirLineFromFlight([FromODataUri] int key) + //{ + // var entity = DbContext.Flights.Find(key); + // if (entity == null) + // { + // return NotFound(); + // } + + // entity.AirlineId = null; + // DbContext.SaveChanges(); + + // return StatusCode(HttpStatusCode.NoContent); + //} } } \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Global.asax b/ApiAsAService/Trippin/Trippin/Global.asax index ee77783..ed6ba9a 100644 --- a/ApiAsAService/Trippin/Trippin/Global.asax +++ b/ApiAsAService/Trippin/Trippin/Global.asax @@ -1 +1 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.OData.Service.Sample.Trippin.WebApiApplication" Language="C#" %> +<%@ Application Codebehind="Global.asax.cs" Inherits="Microsoft.OData.Service.ApiAsAService.WebApiApplication" Language="C#" %> diff --git a/ApiAsAService/Trippin/Trippin/Global.asax.cs b/ApiAsAService/Trippin/Trippin/Global.asax.cs index 61954c1..9694e90 100644 --- a/ApiAsAService/Trippin/Trippin/Global.asax.cs +++ b/ApiAsAService/Trippin/Trippin/Global.asax.cs @@ -4,7 +4,7 @@ using System.Web; using System.Web.Http; -namespace Microsoft.OData.Service.Sample.Trippin +namespace Microsoft.OData.Service.ApiAsAService { public class WebApiApplication : HttpApplication { diff --git a/ApiAsAService/Trippin/Trippin/Helpers.cs b/ApiAsAService/Trippin/Trippin/Helpers.cs index c7def16..4df71b6 100644 --- a/ApiAsAService/Trippin/Trippin/Helpers.cs +++ b/ApiAsAService/Trippin/Trippin/Helpers.cs @@ -10,7 +10,7 @@ using Microsoft.AspNet.OData.Routing; using Microsoft.OData.UriParser; -namespace Microsoft.OData.Service.Sample.Trippin +namespace Microsoft.OData.Service.ApiAsAService { public static class Helpers { diff --git a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.Sample.Trippin.csproj b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj similarity index 98% rename from ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.Sample.Trippin.csproj rename to ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj index 99cf008..64ad98a 100644 --- a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.Sample.Trippin.csproj +++ b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj @@ -11,8 +11,8 @@ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} Library Properties - Microsoft.OData.Service.Sample.Trippin - Microsoft.OData.Service.Sample.Trippin + Microsoft.OData.Service.ApiAsAService + ApiAsAService v4.7.2 true @@ -51,7 +51,7 @@ - + Global.asax diff --git a/ApiAsAService/Trippin/Trippin/Models/Airline.cs b/ApiAsAService/Trippin/Trippin/Models/Airline.cs index 4d717e5..cdd5937 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Airline.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Airline.cs @@ -4,7 +4,7 @@ using System; using System.ComponentModel.DataAnnotations; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Airline { diff --git a/ApiAsAService/Trippin/Trippin/Models/Airport.cs b/ApiAsAService/Trippin/Trippin/Models/Airport.cs index b759797..85fe3ca 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Airport.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Airport.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Airport { diff --git a/ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs b/ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs index 8c0e6da..4a3a9f0 100644 --- a/ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs +++ b/ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs @@ -3,7 +3,7 @@ using Microsoft.OData.Edm; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { /// /// The customized payload value converter for test only diff --git a/ApiAsAService/Trippin/Trippin/Models/Employee.cs b/ApiAsAService/Trippin/Trippin/Models/Employee.cs index 92bf530..ec7fed7 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Employee.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Employee.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Employee : Person { diff --git a/ApiAsAService/Trippin/Trippin/Models/Event.cs b/ApiAsAService/Trippin/Trippin/Models/Event.cs index 1a953b4..a3dacf9 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Event.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Event.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Event { diff --git a/ApiAsAService/Trippin/Trippin/Models/Feature.cs b/ApiAsAService/Trippin/Trippin/Models/Feature.cs index 7a39c71..17a4ecd 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Feature.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Feature.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public enum Feature { diff --git a/ApiAsAService/Trippin/Trippin/Models/Flight.cs b/ApiAsAService/Trippin/Trippin/Models/Flight.cs index ac183bd..2d460cb 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Flight.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Flight.cs @@ -4,7 +4,7 @@ using System; using System.ComponentModel.DataAnnotations.Schema; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Flight { diff --git a/ApiAsAService/Trippin/Trippin/Models/Location.cs b/ApiAsAService/Trippin/Trippin/Models/Location.cs index 25746a1..5454e49 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Location.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Location.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Location { diff --git a/ApiAsAService/Trippin/Trippin/Models/Manager.cs b/ApiAsAService/Trippin/Trippin/Models/Manager.cs index 17f0a5f..8f40ab8 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Manager.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Manager.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Manager : Person { diff --git a/ApiAsAService/Trippin/Trippin/Models/Order.cs b/ApiAsAService/Trippin/Trippin/Models/Order.cs index 5f4ae0d..11acda0 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Order.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Order.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Order { diff --git a/ApiAsAService/Trippin/Trippin/Models/Person.cs b/ApiAsAService/Trippin/Trippin/Models/Person.cs index 4d3df95..6cab67b 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Person.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Person.cs @@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class Person { diff --git a/ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs b/ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs index 99f9300..8467832 100644 --- a/ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs +++ b/ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class SpecialOrder : Order { diff --git a/ApiAsAService/Trippin/Trippin/Models/Trip.cs b/ApiAsAService/Trippin/Trippin/Models/Trip.cs index 72c1b96..6e5d3a4 100644 --- a/ApiAsAService/Trippin/Trippin/Models/Trip.cs +++ b/ApiAsAService/Trippin/Trippin/Models/Trip.cs @@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { [Table("TripsTable")] public class Trip diff --git a/ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs b/ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs index f70e921..5d5a948 100644 --- a/ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs +++ b/ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs @@ -6,7 +6,7 @@ using System.Collections.ObjectModel; using System.Data.Entity; -namespace Microsoft.OData.Service.Sample.Trippin.Models +namespace Microsoft.OData.Service.ApiAsAService.Models { public class TrippinModel : DbContext { diff --git a/ApiAsAService/Trippin/Trippin/Properties/AssemblyInfo.cs b/ApiAsAService/Trippin/Trippin/Properties/AssemblyInfo.cs index 02e4e0e..4f254b5 100644 --- a/ApiAsAService/Trippin/Trippin/Properties/AssemblyInfo.cs +++ b/ApiAsAService/Trippin/Trippin/Properties/AssemblyInfo.cs @@ -8,11 +8,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Microsoft.OData.Service.Sample.Trippin")] +[assembly: AssemblyTitle("Microsoft.OData.Service.ApiAsAService")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Microsoft.OData.Service.Sample.Trippin")] +[assembly: AssemblyProduct("Microsoft.OData.Service.ApiAsAService")] [assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/ApiAsAService/Trippin/Trippin/Submit/CustomizedSubmitProcessor.cs b/ApiAsAService/Trippin/Trippin/Submit/CustomizedSubmitProcessor.cs index f57c102..32bfad4 100644 --- a/ApiAsAService/Trippin/Trippin/Submit/CustomizedSubmitProcessor.cs +++ b/ApiAsAService/Trippin/Trippin/Submit/CustomizedSubmitProcessor.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.Restier.Core.Submit; -namespace Microsoft.OData.Service.Sample.Trippin.Submit +namespace Microsoft.OData.Service.ApiAsAService.Submit { public class CustomizedSubmitProcessor : IChangeSetItemFilter { From 39c8589e6398567a0690734eb08626ae5678a250 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Tue, 23 Jul 2019 10:04:34 -0700 Subject: [PATCH 07/21] Dynamically load schema --- .../Trippin/Trippin/Api/DynamicApi.cs | 61 ++++++------------- .../Trippin/Trippin/App_Start/WebApiConfig.cs | 7 ++- ...crosoft.OData.Service.ApiAsAService.csproj | 8 ++- 3 files changed, 29 insertions(+), 47 deletions(-) diff --git a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs index d695997..3862fae 100644 --- a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs +++ b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs @@ -17,12 +17,15 @@ using Microsoft.Restier.Core.Submit; using Microsoft.Restier.EntityFramework; using Microsoft.Restier.AspNet.Model; +using Microsoft.OData.Edm.Csdl; +using System.Xml; +using Microsoft.OData.Edm.Validation; namespace Microsoft.OData.Service.ApiAsAService.Api { - public class DynamicApi : EntityFrameworkApi + public class DynamicApi : EntityFrameworkApi where T : System.Data.Entity.DbContext { - public TrippinModel ModelContext { get { return DbContext; } } + public T ModelContext { get { return DbContext; } } //[Resource] //public Person Me @@ -154,7 +157,7 @@ public class DynamicApi : EntityFrameworkApi MaxExpansionDepth = 3 }; - IServiceCollection serviceCollection = EntityFrameworkApi.ConfigureApi(apiType, services) + IServiceCollection serviceCollection = EntityFrameworkApi.ConfigureApi(apiType, services) .AddSingleton() .AddSingleton(validationSettingFactory) .AddService() @@ -170,45 +173,19 @@ private class DynamicModelBuilder : IModelBuilder public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) { - EdmModel model = new EdmModel(); - EdmEntityContainer container = new EdmEntityContainer("Microsoft.OData.Service.ApiAsAService.Models", "container"); - model.AddElement(container); - - EdmEntityType person = new EdmEntityType("Microsoft.OData.Service.ApiAsAService.Models", "Person"); - person.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); - EdmStructuralProperty key = person.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32); - person.AddKeys(key); - model.AddElement(person); - EdmEntitySet people = container.AddEntitySet("People", person); - - EdmEntityType detailInfo = new EdmEntityType("Microsoft.OData.Service.ApiAsAService.Models", "DetailInfo"); - detailInfo.AddKeys(detailInfo.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32)); - detailInfo.AddStructuralProperty("Title", EdmPrimitiveTypeKind.String); - model.AddElement(detailInfo); - EdmEntitySet detailInfos = container.AddEntitySet("DetailInfos", person); - - EdmNavigationProperty detailInfoNavProp = person.AddUnidirectionalNavigation( - new EdmNavigationPropertyInfo - { - Name = "DetailInfo", - TargetMultiplicity = EdmMultiplicity.One, - Target = detailInfo - }); - people.AddNavigationTarget(detailInfoNavProp, detailInfos); - - //var model = await InnerHandler.GetModelAsync(context, cancellationToken); - - //// Set computed annotation - //var tripType = (EdmEntityType)model.SchemaElements.Single(e => e.Name == "Trip"); - //var trackGuidProperty = tripType.DeclaredProperties.Single(prop => prop.Name == "TrackGuid"); - //var timeStampValueProp= model.EntityContainer.FindEntitySet("Airlines").EntityType().FindProperty("TimeStampValue"); - //var term = new EdmTerm("Org.OData.Core.V1", "Computed", EdmPrimitiveTypeKind.Boolean); - //var anno1 = new EdmVocabularyAnnotation(trackGuidProperty, term, new EdmBooleanConstant(true)); - //var anno2 = new EdmVocabularyAnnotation(timeStampValueProp, term, new EdmBooleanConstant(true)); - //((EdmModel)model).SetVocabularyAnnotation(anno1); - //((EdmModel)model).SetVocabularyAnnotation(anno2); - - return model; + //return await InnerHandler.GetModelAsync(context, cancellationToken); + IEdmModel model; + IEnumerable errors; + var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); + var file = System.IO.Path.Combine(appData, "Trippin.xml"); + + XmlReader xmlReader = XmlReader.Create(file); + if (CsdlReader.TryParse(xmlReader, out model, out errors)) + { + return model; + } + + throw new Exception("Couldn't parse xml"); } } diff --git a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs index b028403..ffa43c0 100644 --- a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs +++ b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs @@ -20,11 +20,14 @@ public static async void RegisterService( { // enable query options for all properties config.Filter().Expand().Select().OrderBy().MaxTop(null).Count(); - await config.MapRestierRoute( + await config.MapRestierRoute>( "ApiAsAService", "", new RestierBatchHandler(server)); } - + private static void MapRestierRoute(System.Type modelType) + { + + } } } diff --git a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj index 64ad98a..f6ebc3a 100644 --- a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj +++ b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj @@ -44,6 +44,9 @@ + + Always + Designer @@ -82,9 +85,7 @@ Web.config - - - + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll @@ -159,6 +160,7 @@ ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll True + From f2c13ffd3afdfbcd030cae5546f07bac70656945 Mon Sep 17 00:00:00 2001 From: riach Date: Tue, 23 Jul 2019 15:26:15 -0700 Subject: [PATCH 08/21] WIP --- ApiAsAService/ApiAsAService.sln | 79 +++++ .../ApiAsAService/ApiAsAService.csproj | 20 ++ ApiAsAService/ClassLibrary1/Class1.cs | 13 + .../ClassLibrary1/ClassLibrary1.csproj | 49 +++ .../ClassLibrary1/Properties/AssemblyInfo.cs | 36 ++ .../EdmHelperTest/EdmHelperTest.csproj | 31 ++ .../EdmHelperTest/NWSimple_LoadTest.cs | 16 + ApiAsAService/EdmHelperTest/NW_Simple.xml | 143 ++++++++ ApiAsAService/EdmHelperTest/Trippin.xml | 167 +++++++++ .../EdmHelperTest/Trippin_LoadTest.cs | 15 + ApiAsAService/EdmLoadTester/App.config | 4 + .../EdmLoadTester/EdmLoadTester.csproj | 55 +++ ApiAsAService/EdmLoadTester/Program.cs | 28 ++ .../EdmLoadTester/Properties/AssemblyInfo.cs | 36 ++ ApiAsAService/EdmLoader/EdmLoader/App.config | 17 + .../EdmLoader/EdmLoader/EdmLoader.cs | 314 +++++++++++++++++ .../EdmLoader/EdmLoader/EdmLoader.csproj | 88 +++++ .../EdmLoader/Properties/AssemblyInfo.cs | 36 ++ .../EdmLoader/PropertyBuilderHelper.cs | 73 ++++ .../EdmLoader/EdmLoader/TypeBuilderHelpers.cs | 76 +++++ .../EdmLoader/EdmLoader/packages.config | 6 + .../EdmLoaderTest/EdmLoaderTest.csproj | 67 ++++ .../EdmLoaderTest/EdmModelLoaderTest.cs | 16 + ApiAsAService/EdmLoaderTest/NW_Simple.xml | 143 ++++++++ .../EdmLoaderTest/Properties/AssemblyInfo.cs | 20 ++ ApiAsAService/EdmLoaderTest/Trippin.xml | 167 +++++++++ ApiAsAService/EdmLoaderTest/packages.config | 5 + ApiAsAService/ReflectionTester/App.config | 20 ++ ApiAsAService/ReflectionTester/Program.cs | 317 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++ .../ReflectionTester/PropertyBuilderHelper.cs | 67 ++++ .../ReflectionTester/ReflectionTester.csproj | 66 ++++ .../ReflectionTester/TypeBuilderHelpers.cs | 79 +++++ .../ReflectionTester/packages.config | 6 + 34 files changed, 2311 insertions(+) create mode 100644 ApiAsAService/ClassLibrary1/Class1.cs create mode 100644 ApiAsAService/ClassLibrary1/ClassLibrary1.csproj create mode 100644 ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/EdmHelperTest/EdmHelperTest.csproj create mode 100644 ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs create mode 100644 ApiAsAService/EdmHelperTest/NW_Simple.xml create mode 100644 ApiAsAService/EdmHelperTest/Trippin.xml create mode 100644 ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs create mode 100644 ApiAsAService/EdmLoadTester/App.config create mode 100644 ApiAsAService/EdmLoadTester/EdmLoadTester.csproj create mode 100644 ApiAsAService/EdmLoadTester/Program.cs create mode 100644 ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/EdmLoader/EdmLoader/App.config create mode 100644 ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs create mode 100644 ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj create mode 100644 ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs create mode 100644 ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs create mode 100644 ApiAsAService/EdmLoader/EdmLoader/packages.config create mode 100644 ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj create mode 100644 ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs create mode 100644 ApiAsAService/EdmLoaderTest/NW_Simple.xml create mode 100644 ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/EdmLoaderTest/Trippin.xml create mode 100644 ApiAsAService/EdmLoaderTest/packages.config create mode 100644 ApiAsAService/ReflectionTester/App.config create mode 100644 ApiAsAService/ReflectionTester/Program.cs create mode 100644 ApiAsAService/ReflectionTester/Properties/AssemblyInfo.cs create mode 100644 ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs create mode 100644 ApiAsAService/ReflectionTester/ReflectionTester.csproj create mode 100644 ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs create mode 100644 ApiAsAService/ReflectionTester/packages.config diff --git a/ApiAsAService/ApiAsAService.sln b/ApiAsAService/ApiAsAService.sln index 043c8e4..23d66b3 100644 --- a/ApiAsAService/ApiAsAService.sln +++ b/ApiAsAService/ApiAsAService.sln @@ -5,19 +5,98 @@ VisualStudioVersion = 15.0.27703.2026 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAService\ApiAsAService.csproj", "{A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLoader", "EdmLoader\EdmLoader\EdmLoader.csproj", "{C9516DB1-9D15-4E31-9FC5-795656878426}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmHelperTest", "EdmHelperTest\EdmHelperTest.csproj", "{DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLoadTester", "EdmLoadTester\EdmLoadTester.csproj", "{3CF839FF-83B2-4162-B860-68F57B7B0190}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{E51693D7-8503-419A-A7B5-8EAD987E54FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReflectionTester", "ReflectionTester\ReflectionTester.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeAnalysis|Any CPU = CodeAnalysis|Any CPU + CodeAnalysis|x64 = CodeAnalysis|x64 Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.ActiveCfg = Release|x64 + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.Build.0 = Release|x64 {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.ActiveCfg = Debug|x64 + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.Build.0 = Debug|x64 {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.ActiveCfg = Release|x64 + {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.Build.0 = Release|x64 + {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|x64.ActiveCfg = Release|x64 + {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|x64.Build.0 = Release|x64 + {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|x64.ActiveCfg = Debug|x64 + {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|x64.Build.0 = Debug|x64 + {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|Any CPU.Build.0 = Release|Any CPU + {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|x64.ActiveCfg = Release|x64 + {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|x64.Build.0 = Release|x64 + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|x64.ActiveCfg = Release|x64 + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|x64.Build.0 = Release|x64 + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|x64.ActiveCfg = Debug|x64 + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|x64.Build.0 = Debug|x64 + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|Any CPU.Build.0 = Release|Any CPU + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|x64.ActiveCfg = Release|x64 + {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|x64.Build.0 = Release|x64 + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.Build.0 = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.ActiveCfg = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.Build.0 = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.Build.0 = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.Build.0 = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|x64.Build.0 = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|x64.ActiveCfg = Debug|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|x64.Build.0 = Debug|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|Any CPU.Build.0 = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|x64.ActiveCfg = Release|Any CPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|x64.Build.0 = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|x64.Build.0 = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|x64.ActiveCfg = Debug|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|x64.Build.0 = Debug|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.Build.0 = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|x64.ActiveCfg = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ApiAsAService/ApiAsAService/ApiAsAService.csproj b/ApiAsAService/ApiAsAService/ApiAsAService.csproj index 9da73c0..5d482fc 100644 --- a/ApiAsAService/ApiAsAService/ApiAsAService.csproj +++ b/ApiAsAService/ApiAsAService/ApiAsAService.csproj @@ -33,6 +33,26 @@ prompt 4 + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + ..\packages\Microsoft.AspNet.OData.7.1.0\lib\net45\Microsoft.AspNet.OData.dll diff --git a/ApiAsAService/ClassLibrary1/Class1.cs b/ApiAsAService/ClassLibrary1/Class1.cs new file mode 100644 index 0000000..744d100 --- /dev/null +++ b/ApiAsAService/ClassLibrary1/Class1.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Foonamespace +{ + public class Class1 + { + public string Foo { get; set; } + } +} diff --git a/ApiAsAService/ClassLibrary1/ClassLibrary1.csproj b/ApiAsAService/ClassLibrary1/ClassLibrary1.csproj new file mode 100644 index 0000000..df3e8c9 --- /dev/null +++ b/ApiAsAService/ClassLibrary1/ClassLibrary1.csproj @@ -0,0 +1,49 @@ + + + + + Debug + AnyCPU + {E51693D7-8503-419A-A7B5-8EAD987E54FC} + Library + Properties + ClassLibrary1 + ClassLibrary1 + v4.6.1 + 512 + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs b/ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8eadb47 --- /dev/null +++ b/ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ClassLibrary1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ClassLibrary1")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e51693d7-8503-419a-a7b5-8ead987e54fc")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmHelperTest/EdmHelperTest.csproj b/ApiAsAService/EdmHelperTest/EdmHelperTest.csproj new file mode 100644 index 0000000..86092d4 --- /dev/null +++ b/ApiAsAService/EdmHelperTest/EdmHelperTest.csproj @@ -0,0 +1,31 @@ + + + + netcoreapp2.1 + + false + + AnyCPU;x64 + + + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs b/ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs new file mode 100644 index 0000000..6583d9d --- /dev/null +++ b/ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs @@ -0,0 +1,16 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EdmHelperTest +{ + [TestClass] + public class NWSimple_LoadTest + { + [TestMethod] + public void TestEdmLoader() + { + var model = EdmLoader.EdmLoader.ReadModel("NW_Simple.xml"); + Assert.IsNotNull(model); + EdmLoader.EdmLoader.GetComplexTypes(model, "NorthWind_Simple"); + } + } +} diff --git a/ApiAsAService/EdmHelperTest/NW_Simple.xml b/ApiAsAService/EdmHelperTest/NW_Simple.xml new file mode 100644 index 0000000..a9e9939 --- /dev/null +++ b/ApiAsAService/EdmHelperTest/NW_Simple.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmHelperTest/Trippin.xml b/ApiAsAService/EdmHelperTest/Trippin.xml new file mode 100644 index 0000000..dec04f0 --- /dev/null +++ b/ApiAsAService/EdmHelperTest/Trippin.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs b/ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs new file mode 100644 index 0000000..eadcea6 --- /dev/null +++ b/ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs @@ -0,0 +1,15 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EdmHelperTest +{ + [TestClass] + public class Trippin_LoadTest + { + [TestMethod] + public void TestEdmLoader() + { + var model = EdmLoader.EdmLoader.ReadModel("Trippin.xml"); + Assert.IsNotNull(model); + } + } +} diff --git a/ApiAsAService/EdmLoadTester/App.config b/ApiAsAService/EdmLoadTester/App.config new file mode 100644 index 0000000..6916e39 --- /dev/null +++ b/ApiAsAService/EdmLoadTester/App.config @@ -0,0 +1,4 @@ + + + + diff --git a/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj b/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj new file mode 100644 index 0000000..bdaff01 --- /dev/null +++ b/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {3CF839FF-83B2-4162-B860-68F57B7B0190} + Exe + EdmLoadTester + EdmLoadTester + v4.6.1 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\ReflectionTester\bin\Debug\HelloWorld.exe + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoadTester/Program.cs b/ApiAsAService/EdmLoadTester/Program.cs new file mode 100644 index 0000000..1e2ead7 --- /dev/null +++ b/ApiAsAService/EdmLoadTester/Program.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdmLoadTester +{ + using ODataDemo; + + class Program + { + static void Main(string[] args) + { + try + { + var add = new Address(){City = "Redmond"}; + var entities = new Entities(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + } + } +} diff --git a/ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs b/ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a567485 --- /dev/null +++ b/ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EdmLoadTester")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EdmLoadTester")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3cf839ff-83b2-4162-b860-68f57b7b0190")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmLoader/EdmLoader/App.config b/ApiAsAService/EdmLoader/EdmLoader/App.config new file mode 100644 index 0000000..7e1d79c --- /dev/null +++ b/ApiAsAService/EdmLoader/EdmLoader/App.config @@ -0,0 +1,17 @@ + + + + +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs b/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs new file mode 100644 index 0000000..3434940 --- /dev/null +++ b/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdmLoader +{ + using System.Data.Entity; + using System.IO; + using System.Reflection; + using System.Reflection.Emit; + using System.Text.RegularExpressions; + using System.Xml; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Csdl; + using Microsoft.OData.Edm.Validation; + + + + public class TypeBuilderInfo + { + public bool IsDerived { get; set; } + public TypeBuilder Builder { get; set; } + } + public class EdmLoader + { + static Dictionary _typeBuildersDict = new Dictionary(); + static Regex collectionRegex = new Regex(@"Collection\((.+)\)", RegexOptions.Compiled); + public static void Main(string[] args) + { + var model = ReadModel(@"C:\repos\fun\lab\ApiAsAService\EdmHelperTest\NW_Simple.xml"); + GetComplexTypes(model, "NW_Simple"); + + } + + public static IEdmModel ReadModel(string fileName) + { + var edmxReaderSettings = new global::Microsoft.OData.Edm.Csdl.CsdlReaderSettings() + { + IgnoreUnexpectedAttributesAndElements = true + }; + var references = global::System.Linq.Enumerable.Empty(); + + using (var reader = XmlReader.Create(fileName)) + { + IEdmModel model; + IEnumerable errors; + if (CsdlReader.TryParse(reader, references, edmxReaderSettings, out model, out errors)) + { + return model; + } + return null; + } + } + + public static void GetComplexTypes(IEdmModel model, string assemblyName = "NorthWind_Simple") + { + var assemblyFullName = assemblyName + ".DataModels.dll"; + var assembly = new AssemblyName(assemblyName); + var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assembly,AssemblyBuilderAccess.RunAndSave ); + var moduleBuilder = assemblyBuilder.DefineDynamicModule("Modules" + ".mod", assemblyFullName + , false); + IList baseProps = new List(); + foreach (var modelSchemaElement in model.SchemaElements) + { + var one = model.FindDeclaredType(modelSchemaElement.FullName()); + + if (one is IEdmStructuredType) + { + if (one is IEdmEntityType) + { + + var two = model.FindDirectlyDerivedTypes((IEdmStructuredType)one); + if (two.Any()) + { + //baseProps.Add(one.FullName()); + //Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); + + } + } + else + { + baseProps.Add(one.FullName()); + Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); + + } + + } + + + + } + + //foreach (var modelSchemaElement in model.SchemaElements) + //{ + // if (!baseProps.Contains(modelSchemaElement.FullName())) + // { + // var one = model.FindDeclaredType(modelSchemaElement.FullName()); + // if (one != null) + // { + // Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); + // } + // } + //} + + //foreach (var modelSchemaElement in model.SchemaElements) + //{ + + // var one = model.FindDeclaredType(modelSchemaElement.FullName()); + // if (one != null) + // { + // Compile((IEdmStructuredType)one, moduleBuilder, one.FullName(), true); + // } + + //} + + foreach (var typeBuilder in _typeBuildersDict) + { + //if (typeBuilder.Value.IsDerived) + //{ + + // var previouslyBuiltType = _typeBuildersDict[(typeBuilder.Value.Builder.BaseType.FullName)]; + // typeBuilder.Value.Builder.CreatePassThroughConstructors(previouslyBuiltType.Builder); + //} + typeBuilder.Value.Builder.CreateType(); + } + + ////generate the entities type + //var entitiesBuilder = moduleBuilder.DefineType("Entities", TypeAttributes.Class | TypeAttributes.Public, typeof(DbContext)); + //var dbContextType = typeof(DbContext); + //entitiesBuilder.CreatePassThroughConstructors(dbContextType); + + + //foreach (var typeBuilderInfo in _typeBuildersDict) + //{ + // Type listOf = typeof(DbSet<>); + // Type selfContained = listOf.MakeGenericType(typeBuilderInfo.Value.Builder); + // PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Key.Split('.')[1], selfContained); + //} + //entitiesBuilder.CreateType(); + + assemblyBuilder.Save(assemblyFullName); + } + + + + internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilder, string moduleName, bool navPass = false) + { + TypeBuilder typeBuilder=null; + if (type.BaseType != null && !navPass) + { + var previouslyBuiltType = _typeBuildersDict[(type.BaseType.FullTypeName())]; + + typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public, previouslyBuiltType.Builder); + _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = true }); + + + } + + if (!navPass) + { + if (typeBuilder==null) + { + typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public); + _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = false }); + } + + foreach (var property in type.DeclaredProperties) + { + if (property.PropertyKind != EdmPropertyKind.Navigation) + { + GenerateProperty(property, typeBuilder, moduleBuilder); + + } + } + + } + else + { + typeBuilder = _typeBuildersDict[moduleName].Builder; + foreach (var property in type.DeclaredProperties) + { + if (property.PropertyKind == EdmPropertyKind.Navigation) + GenerateProperty(property, typeBuilder, moduleBuilder); + } + } + + + + + + + + } + + internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder) + { + //var propertyName = property.Name; + + //var emdPropType = property.Type.PrimitiveKind(); + //var propertyType = GetPrimitiveClrType(emdPropType, false); + //if (propertyType == null) + //{ + // if (property.Type.FullName().ToLower().Contains("geography")) + // { + // return; + // } + + // if (property.PropertyKind == EdmPropertyKind.Navigation) + // { + // if (property.Type.FullName().StartsWith("Collection")) + // { + // var typeName = collectionRegex.Match(property.Type.FullName()).Groups[1].Value; + // Type listOf = typeof(List<>); + // Type selfContained = listOf.MakeGenericType(_typeBuildersDict[typeName].Builder); + // propertyType = selfContained; + // } + // else + // { + // var navProptype = _typeBuildersDict[property.Type.FullName()]; + // propertyType = navProptype.Builder; + // } + + + // } + // else + // { + // var previouslyBuiltType = moduleBuilder.GetType(property.Type.FullName()); + // propertyType = previouslyBuiltType; + // } + + + //} + //PropertyBuilderHelper.BuildProperty(typeBuilder, propertyName, propertyType); + // var field = typeBuilder.DefineField(propertyName, propertyType, FieldAttributes.Private); + // var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); + // + // // Generate getter method + // + // var getter = typeBuilder.DefineMethod("Get", MethodAttributes.Public, propertyType, Type.EmptyTypes); + // + // var il = getter.GetILGenerator(); + // + // il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack + // il.Emit(OpCodes.Ldfld, field); // Load the field "_Name" + // il.Emit(OpCodes.Ret); // Return + // + // propertyBuilder.SetGetMethod(getter); + // + // // Generate setter method + // + // var setter = typeBuilder.DefineMethod("Set", MethodAttributes.Public, null, new[] { propertyType }); + // + // il = setter.GetILGenerator(); + // + // il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack + // il.Emit(OpCodes.Ldarg_1); // Push "value" on the stack + // il.Emit(OpCodes.Stfld, field); // Set the field "_Name" to "value" + // il.Emit(OpCodes.Ret); // Return + // + // propertyBuilder.SetSetMethod(setter); + } + + /// + /// Get Clr type + /// + /// Edm Primitive Type Kind + /// Nullable value + /// CLR type + private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNullable) + { + switch (typeKind) + { + case EdmPrimitiveTypeKind.Binary: + return typeof(byte[]); + case EdmPrimitiveTypeKind.Boolean: + return isNullable ? typeof(Boolean?) : typeof(Boolean); + case EdmPrimitiveTypeKind.Byte: + return isNullable ? typeof(Byte?) : typeof(Byte); + case EdmPrimitiveTypeKind.Date: + return isNullable ? typeof(Date?) : typeof(Date); + case EdmPrimitiveTypeKind.DateTimeOffset: + return isNullable ? typeof(DateTimeOffset?) : typeof(DateTimeOffset); + case EdmPrimitiveTypeKind.Decimal: + return isNullable ? typeof(Decimal?) : typeof(Decimal); + case EdmPrimitiveTypeKind.Double: + return isNullable ? typeof(Double?) : typeof(Double); + case EdmPrimitiveTypeKind.Guid: + return isNullable ? typeof(Guid?) : typeof(Guid); + case EdmPrimitiveTypeKind.Int16: + return isNullable ? typeof(Int16?) : typeof(Int16); + case EdmPrimitiveTypeKind.Int32: + return isNullable ? typeof(Int32?) : typeof(Int32); + case EdmPrimitiveTypeKind.Int64: + return isNullable ? typeof(Int64?) : typeof(Int64); + case EdmPrimitiveTypeKind.SByte: + return isNullable ? typeof(SByte?) : typeof(SByte); + case EdmPrimitiveTypeKind.Single: + return isNullable ? typeof(Single?) : typeof(Single); + case EdmPrimitiveTypeKind.Stream: + return typeof(Stream); + case EdmPrimitiveTypeKind.String: + return typeof(String); + case EdmPrimitiveTypeKind.Duration: + return isNullable ? typeof(TimeSpan?) : typeof(TimeSpan); + case EdmPrimitiveTypeKind.TimeOfDay: + return isNullable ? typeof(TimeOfDay?) : typeof(TimeOfDay); + default: + return null; + } + } + } +} diff --git a/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj b/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj new file mode 100644 index 0000000..b275018 --- /dev/null +++ b/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj @@ -0,0 +1,88 @@ + + + + + Debug + AnyCPU + {C9516DB1-9D15-4E31-9FC5-795656878426} + Exe + EdmLoader + EdmLoader + v4.6.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + + + ..\..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs b/ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1227ef0 --- /dev/null +++ b/ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EdmLoader")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EdmLoader")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c9516db1-9d15-4e31-9fc5-795656878426")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs b/ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs new file mode 100644 index 0000000..76f22a1 --- /dev/null +++ b/ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdmLoader +{ + using System.Reflection; + using System.Reflection.Emit; + using System.Threading; + + public class PropertyBuilderHelper + { + public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Type fieldType) + { + + FieldBuilder fieldBldr = typeBuilder.DefineField(fieldName, + fieldType, + FieldAttributes.Private); + + // The last argument of DefineProperty is null, because the + // property has no parameters. (If you don't specify null, you must + // specify an array of Type objects. For a parameterless property, + // use an array with no elements: new Type[] {}) + PropertyBuilder propBuilder = typeBuilder.DefineProperty(fieldName, + PropertyAttributes.HasDefault, + fieldType, + null); + + // The property set and property get methods require a special + // set of attributes. + MethodAttributes getSetAttr = + MethodAttributes.Public | MethodAttributes.SpecialName | + MethodAttributes.HideBySig; + + // Define the "get" accessor method for CustomerName. + MethodBuilder propMethodBldr = + typeBuilder.DefineMethod($"get_{fieldName}", + getSetAttr, + fieldType, + Type.EmptyTypes); + + ILGenerator custNameGetIL = propMethodBldr.GetILGenerator(); + + custNameGetIL.Emit(OpCodes.Ldarg_0); + custNameGetIL.Emit(OpCodes.Ldfld, fieldBldr); + custNameGetIL.Emit(OpCodes.Ret); + + // Define the "set" accessor method for CustomerName. + MethodBuilder propSetMethodBldr = + typeBuilder.DefineMethod($"set_{fieldName}", + getSetAttr, + null, + new Type[] { fieldType }); + + ILGenerator custNameSetIL = propSetMethodBldr.GetILGenerator(); + + custNameSetIL.Emit(OpCodes.Ldarg_0); + custNameSetIL.Emit(OpCodes.Ldarg_1); + custNameSetIL.Emit(OpCodes.Stfld, fieldBldr); + custNameSetIL.Emit(OpCodes.Ret); + + // Last, we must map the two methods created above to our PropertyBuilder to + // their corresponding behaviors, "get" and "set" respectively. + propBuilder.SetGetMethod(propMethodBldr); + propBuilder.SetSetMethod(propSetMethodBldr); + + + + } + } +} diff --git a/ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs b/ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs new file mode 100644 index 0000000..03fc133 --- /dev/null +++ b/ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +public static class TypeBuilderHelper +{ + /// Creates one constructor for each public constructor in the base class. Each constructor simply + /// forwards its arguments to the base constructor, and matches the base constructor's signature. + /// Supports optional values, and custom attributes on constructors and parameters. + /// Does not support n-ary (variadic) constructors + public static void CreatePassThroughConstructors(this TypeBuilder builder, Type baseType) + { + //foreach (var constructor in baseType.GetConstructors()) + //{ + // var parameters = constructor.GetParameters(); + // if (parameters.Length > 0 && parameters.Last().IsDefined(typeof(ParamArrayAttribute), false)) + // { + // //throw new InvalidOperationException("Variadic constructors are not supported"); + // continue; + // } + + // var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); + // var requiredCustomModifiers = parameters.Select(p => p.GetRequiredCustomModifiers()).ToArray(); + // var optionalCustomModifiers = parameters.Select(p => p.GetOptionalCustomModifiers()).ToArray(); + + // var ctor = builder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers); + // for (var i = 0; i < parameters.Length; ++i) + // { + // var parameter = parameters[i]; + // var parameterBuilder = ctor.DefineParameter(i + 1, parameter.Attributes, parameter.Name); + // if (((int)parameter.Attributes & (int)ParameterAttributes.HasDefault) != 0) + // { + // parameterBuilder.SetConstant(parameter.RawDefaultValue); + // } + + // foreach (var attribute in BuildCustomAttributes(parameter.GetCustomAttributesData())) + // { + // parameterBuilder.SetCustomAttribute(attribute); + // } + // } + + // foreach (var attribute in BuildCustomAttributes(constructor.GetCustomAttributesData())) + // { + // ctor.SetCustomAttribute(attribute); + // } + + // var emitter = ctor.GetILGenerator(); + // emitter.Emit(OpCodes.Nop); + + // // Load `this` and call base constructor with arguments + // emitter.Emit(OpCodes.Ldarg_0); + // for (var i = 1; i <= parameters.Length; ++i) + // { + // emitter.Emit(OpCodes.Ldarg, i); + // } + // emitter.Emit(OpCodes.Call, constructor); + + // emitter.Emit(OpCodes.Ret); + //} + } + + + private static CustomAttributeBuilder[] BuildCustomAttributes(IEnumerable customAttributes) + { + return customAttributes.Select(attribute => { + var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray(); + var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); + var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray(); + var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); + var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray(); + return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues); + }).ToArray(); + } +} \ No newline at end of file diff --git a/ApiAsAService/EdmLoader/EdmLoader/packages.config b/ApiAsAService/EdmLoader/EdmLoader/packages.config new file mode 100644 index 0000000..5816bdb --- /dev/null +++ b/ApiAsAService/EdmLoader/EdmLoader/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj b/ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj new file mode 100644 index 0000000..7fea77b --- /dev/null +++ b/ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj @@ -0,0 +1,67 @@ + + + + + + Debug + AnyCPU + {5DDF63C2-4186-4705-9193-EBA92623A37B} + Library + Properties + EdmLoaderTest + EdmLoaderTest + v4.7 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + Always + + + + + + + Always + + + + + + {c9516db1-9d15-4e31-9fc5-795656878426} + EdmLoader + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs b/ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs new file mode 100644 index 0000000..2fbffc4 --- /dev/null +++ b/ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EdmLoaderTest +{ + [TestClass] + public class EdmModelLoaderTest + { + [TestMethod] + public void TestEdmLoader() + { + var model = EdmLoader.EdmLoader.ReadModel("NW_Simple.xml"); + Assert.IsNotNull(model); + } + } +} diff --git a/ApiAsAService/EdmLoaderTest/NW_Simple.xml b/ApiAsAService/EdmLoaderTest/NW_Simple.xml new file mode 100644 index 0000000..a9e9939 --- /dev/null +++ b/ApiAsAService/EdmLoaderTest/NW_Simple.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs b/ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0fe6592 --- /dev/null +++ b/ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("EdmLoaderTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EdmLoaderTest")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("5ddf63c2-4186-4705-9193-eba92623a37b")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmLoaderTest/Trippin.xml b/ApiAsAService/EdmLoaderTest/Trippin.xml new file mode 100644 index 0000000..dec04f0 --- /dev/null +++ b/ApiAsAService/EdmLoaderTest/Trippin.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/packages.config b/ApiAsAService/EdmLoaderTest/packages.config new file mode 100644 index 0000000..4a68cce --- /dev/null +++ b/ApiAsAService/EdmLoaderTest/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ApiAsAService/ReflectionTester/App.config b/ApiAsAService/ReflectionTester/App.config new file mode 100644 index 0000000..12cde46 --- /dev/null +++ b/ApiAsAService/ReflectionTester/App.config @@ -0,0 +1,20 @@ + + + + +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/ReflectionTester/Program.cs b/ApiAsAService/ReflectionTester/Program.cs new file mode 100644 index 0000000..6e37880 --- /dev/null +++ b/ApiAsAService/ReflectionTester/Program.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReflectionTester +{ + using System.Data.Entity; + using System.IO; + using System.Reflection; + using System.Reflection.Emit; + using System.Text.RegularExpressions; + using System.Threading; + using System.Xml; + using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Csdl; + using Microsoft.OData.Edm.Validation; + + class Program + { + static Dictionary _typeBuildersDict = new Dictionary(); + static Regex collectionRegex = new Regex(@"Collection\((.+)\)", RegexOptions.Compiled); + + public class TypeBuilderInfo + { + public bool IsDerived { get; set; } + public TypeBuilder Builder { get; set; } + } + + public static IEdmModel ReadModel(string fileName) + { + var edmxReaderSettings = new global::Microsoft.OData.Edm.Csdl.CsdlReaderSettings() + { + IgnoreUnexpectedAttributesAndElements = true + }; + var references = global::System.Linq.Enumerable.Empty(); + + using (var reader = XmlReader.Create(fileName)) + { + IEdmModel model; + IEnumerable errors; + if (CsdlReader.TryParse(reader, references, edmxReaderSettings, out model, out errors)) + { + return model; + } + return null; + } + } + + public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) + { + + IList baseProps = new List(); + foreach (var modelSchemaElement in model.SchemaElements) + { + var one = model.FindDeclaredType(modelSchemaElement.FullName()); + + if (one is IEdmStructuredType) + { + if (one is IEdmEntityType) + { + + var two = model.FindDirectlyDerivedTypes((IEdmStructuredType)one); + if (two.Any()) + { + baseProps.Add(one.FullName()); + Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); + } + } + else + { + baseProps.Add(one.FullName()); + Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); + + } + + } + } + + foreach (var modelSchemaElement in model.SchemaElements) + { + if (!baseProps.Contains(modelSchemaElement.FullName())) + { + var one = model.FindDeclaredType(modelSchemaElement.FullName()); + if (one != null) + { + Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); + } + } + } + + foreach (var modelSchemaElement in model.SchemaElements) + { + var one = model.FindDeclaredType(modelSchemaElement.FullName()); + if (one != null) + { + Compile((IEdmStructuredType)one, moduleBuilder, one.FullName(), true); + } + + } + + foreach (var typeBuilder in _typeBuildersDict) + { + if (typeBuilder.Value.IsDerived) + { + + var previouslyBuiltType = _typeBuildersDict[(typeBuilder.Value.Builder.BaseType.FullName)]; + typeBuilder.Value.Builder.CreatePassThroughConstructors(previouslyBuiltType.Builder); + } + typeBuilder.Value.Builder.CreateType(); + } + + //generate the entities type + var entitiesBuilder = moduleBuilder.DefineType("Entities", TypeAttributes.Class | TypeAttributes.Public, typeof(DbContext)); + var dbContextType = typeof(DbContext); + entitiesBuilder.CreatePassThroughConstructors(dbContextType); + + + foreach (var typeBuilderInfo in _typeBuildersDict) + { + Type listOf = typeof(DbSet<>); + Type selfContained = listOf.MakeGenericType(typeBuilderInfo.Value.Builder); + PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Key.Split('.')[1], selfContained); + } + entitiesBuilder.CreateType(); + + + } + + + + internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilder, string moduleName, bool navPass = false) + { + TypeBuilder typeBuilder = null; + if (type.BaseType != null && !navPass) + { + var previouslyBuiltType = _typeBuildersDict[(type.BaseType.FullTypeName())]; + + typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public, previouslyBuiltType.Builder); + _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = true }); + + + } + + if (!navPass) + { + if (typeBuilder == null) + { + typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public); + _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = false }); + } + + foreach (var property in type.DeclaredProperties) + { + if (property.PropertyKind != EdmPropertyKind.Navigation) + { + GenerateProperty(property, typeBuilder, moduleBuilder); + + } + } + + } + else + { + typeBuilder = _typeBuildersDict[moduleName].Builder; + foreach (var property in type.DeclaredProperties) + { + if (property.PropertyKind == EdmPropertyKind.Navigation) + GenerateProperty(property, typeBuilder, moduleBuilder); + } + } + + + + + + + + } + + internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder) + { + var propertyName = property.Name; + + var emdPropType = property.Type.PrimitiveKind(); + var propertyType = GetPrimitiveClrType(emdPropType, false); + if (propertyType == null) + { + if (property.Type.FullName().ToLower().Contains("geography")) + { + return; + } + + if (property.PropertyKind == EdmPropertyKind.Navigation) + { + if (property.Type.FullName().StartsWith("Collection")) + { + var typeName = collectionRegex.Match(property.Type.FullName()).Groups[1].Value; + Type listOf = typeof(List<>); + Type selfContained = listOf.MakeGenericType(_typeBuildersDict[typeName].Builder); + propertyType = selfContained; + } + else + { + var navProptype = _typeBuildersDict[property.Type.FullName()]; + propertyType = navProptype.Builder; + } + + + } + else + { + var previouslyBuiltType = moduleBuilder.GetType(property.Type.FullName()); + propertyType = previouslyBuiltType; + } + + + } + PropertyBuilderHelper.BuildProperty(typeBuilder, propertyName, propertyType); + //var field = typeBuilder.DefineField(propertyName, propertyType, FieldAttributes.Private); + //var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); + + //// Generate getter method + + //var getter = typeBuilder.DefineMethod("Get", MethodAttributes.Public, propertyType, Type.EmptyTypes); + + //var il = getter.GetILGenerator(); + + //il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack + //il.Emit(OpCodes.Ldfld, field); // Load the field "_Name" + //il.Emit(OpCodes.Ret); // Return + + //propertyBuilder.SetGetMethod(getter); + + //// Generate setter method + + //var setter = typeBuilder.DefineMethod("Set", MethodAttributes.Public, null, new[] { propertyType }); + + //il = setter.GetILGenerator(); + + //il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack + //il.Emit(OpCodes.Ldarg_1); // Push "value" on the stack + //il.Emit(OpCodes.Stfld, field); // Set the field "_Name" to "value" + //il.Emit(OpCodes.Ret); // Return + + //propertyBuilder.SetSetMethod(setter); + } + + /// + /// Get Clr type + /// + /// Edm Primitive Type Kind + /// Nullable value + /// CLR type + private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNullable) + { + switch (typeKind) + { + case EdmPrimitiveTypeKind.Binary: + return typeof(byte[]); + case EdmPrimitiveTypeKind.Boolean: + return isNullable ? typeof(Boolean?) : typeof(Boolean); + case EdmPrimitiveTypeKind.Byte: + return isNullable ? typeof(Byte?) : typeof(Byte); + case EdmPrimitiveTypeKind.Date: + return isNullable ? typeof(Date?) : typeof(Date); + case EdmPrimitiveTypeKind.DateTimeOffset: + return isNullable ? typeof(DateTimeOffset?) : typeof(DateTimeOffset); + case EdmPrimitiveTypeKind.Decimal: + return isNullable ? typeof(Decimal?) : typeof(Decimal); + case EdmPrimitiveTypeKind.Double: + return isNullable ? typeof(Double?) : typeof(Double); + case EdmPrimitiveTypeKind.Guid: + return isNullable ? typeof(Guid?) : typeof(Guid); + case EdmPrimitiveTypeKind.Int16: + return isNullable ? typeof(Int16?) : typeof(Int16); + case EdmPrimitiveTypeKind.Int32: + return isNullable ? typeof(Int32?) : typeof(Int32); + case EdmPrimitiveTypeKind.Int64: + return isNullable ? typeof(Int64?) : typeof(Int64); + case EdmPrimitiveTypeKind.SByte: + return isNullable ? typeof(SByte?) : typeof(SByte); + case EdmPrimitiveTypeKind.Single: + return isNullable ? typeof(Single?) : typeof(Single); + case EdmPrimitiveTypeKind.Stream: + return typeof(Stream); + case EdmPrimitiveTypeKind.String: + return typeof(String); + case EdmPrimitiveTypeKind.Duration: + return isNullable ? typeof(TimeSpan?) : typeof(TimeSpan); + case EdmPrimitiveTypeKind.TimeOfDay: + return isNullable ? typeof(TimeOfDay?) : typeof(TimeOfDay); + default: + return null; + } + } + + static void Main(string[] args) + { + var model = ReadModel(@"C:\repos\fun\lab\ApiAsAService\EdmHelperTest\NW_Simple.xml"); + + + // create a dynamic assembly and module + AssemblyName assemblyName = new AssemblyName(); + assemblyName.Name = "HelloWorld"; + AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); + ModuleBuilder module; + module = assemblyBuilder.DefineDynamicModule("HelloWorld.exe"); + BuildModules(model, module); + + assemblyBuilder.Save("HelloWorld.exe"); + } + + + } +} diff --git a/ApiAsAService/ReflectionTester/Properties/AssemblyInfo.cs b/ApiAsAService/ReflectionTester/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..44c9ec2 --- /dev/null +++ b/ApiAsAService/ReflectionTester/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ReflectionTester")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ReflectionTester")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3678c588-f1dd-4ef6-879a-3d1d8114a880")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs b/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs new file mode 100644 index 0000000..ac0e515 --- /dev/null +++ b/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs @@ -0,0 +1,67 @@ +namespace ReflectionTester +{ + using System; + using System.Reflection; + using System.Reflection.Emit; + + public class PropertyBuilderHelper + { + public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Type fieldType) + { + + FieldBuilder fieldBldr = typeBuilder.DefineField(fieldName, + fieldType, + FieldAttributes.Private); + + // The last argument of DefineProperty is null, because the + // property has no parameters. (If you don't specify null, you must + // specify an array of Type objects. For a parameterless property, + // use an array with no elements: new Type[] {}) + PropertyBuilder propBuilder = typeBuilder.DefineProperty(fieldName, + PropertyAttributes.HasDefault, + fieldType, + null); + + // The property set and property get methods require a special + // set of attributes. + MethodAttributes getSetAttr = + MethodAttributes.Public | MethodAttributes.SpecialName | + MethodAttributes.HideBySig; + + // Define the "get" accessor method for CustomerName. + MethodBuilder propMethodBldr = + typeBuilder.DefineMethod($"get_{fieldName}", + getSetAttr, + fieldType, + Type.EmptyTypes); + + ILGenerator custNameGetIL = propMethodBldr.GetILGenerator(); + + custNameGetIL.Emit(OpCodes.Ldarg_0); + custNameGetIL.Emit(OpCodes.Ldfld, fieldBldr); + custNameGetIL.Emit(OpCodes.Ret); + + // Define the "set" accessor method for CustomerName. + MethodBuilder propSetMethodBldr = + typeBuilder.DefineMethod($"set_{fieldName}", + getSetAttr, + null, + new Type[] { fieldType }); + + ILGenerator custNameSetIL = propSetMethodBldr.GetILGenerator(); + + custNameSetIL.Emit(OpCodes.Ldarg_0); + custNameSetIL.Emit(OpCodes.Ldarg_1); + custNameSetIL.Emit(OpCodes.Stfld, fieldBldr); + custNameSetIL.Emit(OpCodes.Ret); + + // Last, we must map the two methods created above to our PropertyBuilder to + // their corresponding behaviors, "get" and "set" respectively. + propBuilder.SetGetMethod(propMethodBldr); + propBuilder.SetSetMethod(propSetMethodBldr); + + + + } + } +} diff --git a/ApiAsAService/ReflectionTester/ReflectionTester.csproj b/ApiAsAService/ReflectionTester/ReflectionTester.csproj new file mode 100644 index 0000000..e8ba828 --- /dev/null +++ b/ApiAsAService/ReflectionTester/ReflectionTester.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880} + Exe + ReflectionTester + ReflectionTester + v4.7 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs b/ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs new file mode 100644 index 0000000..6d55105 --- /dev/null +++ b/ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace ReflectionTester +{ + public static class TypeBuilderHelper + { + /// Creates one constructor for each public constructor in the base class. Each constructor simply + /// forwards its arguments to the base constructor, and matches the base constructor's signature. + /// Supports optional values, and custom attributes on constructors and parameters. + /// Does not support n-ary (variadic) constructors + public static void CreatePassThroughConstructors(this TypeBuilder builder, Type baseType) + { + //foreach (var constructor in baseType.GetConstructors()) + //{ + // var parameters = constructor.GetParameters(); + // if (parameters.Length > 0 && parameters.Last().IsDefined(typeof(ParamArrayAttribute), false)) + // { + // //throw new InvalidOperationException("Variadic constructors are not supported"); + // continue; + // } + + // var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); + // var requiredCustomModifiers = parameters.Select(p => p.GetRequiredCustomModifiers()).ToArray(); + // var optionalCustomModifiers = parameters.Select(p => p.GetOptionalCustomModifiers()).ToArray(); + + // var ctor = builder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers); + // for (var i = 0; i < parameters.Length; ++i) + // { + // var parameter = parameters[i]; + // var parameterBuilder = ctor.DefineParameter(i + 1, parameter.Attributes, parameter.Name); + // if (((int)parameter.Attributes & (int)ParameterAttributes.HasDefault) != 0) + // { + // parameterBuilder.SetConstant(parameter.RawDefaultValue); + // } + + // foreach (var attribute in BuildCustomAttributes(parameter.GetCustomAttributesData())) + // { + // parameterBuilder.SetCustomAttribute(attribute); + // } + // } + + // foreach (var attribute in BuildCustomAttributes(constructor.GetCustomAttributesData())) + // { + // ctor.SetCustomAttribute(attribute); + // } + + // var emitter = ctor.GetILGenerator(); + // emitter.Emit(OpCodes.Nop); + + // // Load `this` and call base constructor with arguments + // emitter.Emit(OpCodes.Ldarg_0); + // for (var i = 1; i <= parameters.Length; ++i) + // { + // emitter.Emit(OpCodes.Ldarg, i); + // } + // emitter.Emit(OpCodes.Call, constructor); + + // emitter.Emit(OpCodes.Ret); + //} + } + + + private static CustomAttributeBuilder[] BuildCustomAttributes(IEnumerable customAttributes) + { + return customAttributes.Select(attribute => { + var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray(); + var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); + var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray(); + var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); + var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray(); + return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues); + }).ToArray(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/ReflectionTester/packages.config b/ApiAsAService/ReflectionTester/packages.config new file mode 100644 index 0000000..b50c150 --- /dev/null +++ b/ApiAsAService/ReflectionTester/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 1d1da7fc6ea9060eee58ec0db860e0dba4192d2a Mon Sep 17 00:00:00 2001 From: riach Date: Tue, 23 Jul 2019 16:48:47 -0700 Subject: [PATCH 09/21] WIP --- ApiAsAService/EdmLoadTester/App.config | 19 +++- .../EdmLoadTester/EdmLoadTester.csproj | 12 ++- ApiAsAService/EdmLoadTester/Program.cs | 21 +++- ApiAsAService/EdmLoadTester/packages.config | 4 + ApiAsAService/ReflectionTester/Program.cs | 95 ++++++++----------- .../ReflectionTester/PropertyBuilderHelper.cs | 1 - 6 files changed, 89 insertions(+), 63 deletions(-) create mode 100644 ApiAsAService/EdmLoadTester/packages.config diff --git a/ApiAsAService/EdmLoadTester/App.config b/ApiAsAService/EdmLoadTester/App.config index 6916e39..7e1d79c 100644 --- a/ApiAsAService/EdmLoadTester/App.config +++ b/ApiAsAService/EdmLoadTester/App.config @@ -1,4 +1,17 @@ - + - - + + +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj b/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj index bdaff01..6131123 100644 --- a/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj +++ b/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj @@ -33,10 +33,17 @@ 4 - - ..\ReflectionTester\bin\Debug\HelloWorld.exe + + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + + + ..\ReflectionTester\bin\Debug\NW_Simple.dll + @@ -50,6 +57,7 @@ + \ No newline at end of file diff --git a/ApiAsAService/EdmLoadTester/Program.cs b/ApiAsAService/EdmLoadTester/Program.cs index 1e2ead7..3ff12a6 100644 --- a/ApiAsAService/EdmLoadTester/Program.cs +++ b/ApiAsAService/EdmLoadTester/Program.cs @@ -14,15 +14,30 @@ static void Main(string[] args) { try { - var add = new Address(){City = "Redmond"}; - var entities = new Entities(); + using (var context = new Entities()) + { + var address = context.Supplier.Create(); + address.Concurrency = 2; + } + + using (var context = new Entities()) + { + var address = context.Supplier.ToList(); + if (address.Count != 1) + { + throw new Exception("Error"); + } + + var isTrue = address.FirstOrDefault().Concurrency.Equals(2); + } + } catch (Exception e) { Console.WriteLine(e); throw; } - + } } } diff --git a/ApiAsAService/EdmLoadTester/packages.config b/ApiAsAService/EdmLoadTester/packages.config new file mode 100644 index 0000000..373cff5 --- /dev/null +++ b/ApiAsAService/EdmLoadTester/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ApiAsAService/ReflectionTester/Program.cs b/ApiAsAService/ReflectionTester/Program.cs index 6e37880..44b154b 100644 --- a/ApiAsAService/ReflectionTester/Program.cs +++ b/ApiAsAService/ReflectionTester/Program.cs @@ -7,6 +7,7 @@ namespace ReflectionTester { using System.Data.Entity; + using System.Data.Entity.Infrastructure; using System.IO; using System.Reflection; using System.Reflection.Emit; @@ -25,6 +26,7 @@ class Program public class TypeBuilderInfo { public bool IsDerived { get; set; } + public bool IsStructured { get; set; } public TypeBuilder Builder { get; set; } } @@ -54,25 +56,25 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) IList baseProps = new List(); foreach (var modelSchemaElement in model.SchemaElements) { - var one = model.FindDeclaredType(modelSchemaElement.FullName()); + var declaredType = model.FindDeclaredType(modelSchemaElement.FullName()); - if (one is IEdmStructuredType) + if (declaredType is IEdmStructuredType) { - if (one is IEdmEntityType) + if (declaredType is IEdmEntityType) { - var two = model.FindDirectlyDerivedTypes((IEdmStructuredType)one); - if (two.Any()) + var derivedTypes = model.FindDirectlyDerivedTypes((IEdmStructuredType)declaredType); + if (derivedTypes.Any()) { - baseProps.Add(one.FullName()); - Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); + baseProps.Add(declaredType.FullName()); + Compile((IEdmStructuredType)declaredType, moduleBuilder, declaredType.FullName()); } } else { - baseProps.Add(one.FullName()); - Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); - + baseProps.Add(declaredType.FullName()); + Compile((IEdmStructuredType)declaredType, moduleBuilder, declaredType.FullName()); + _typeBuildersDict[declaredType.FullName()].IsStructured = true; } } @@ -119,10 +121,25 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) foreach (var typeBuilderInfo in _typeBuildersDict) { - Type listOf = typeof(DbSet<>); - Type selfContained = listOf.MakeGenericType(typeBuilderInfo.Value.Builder); - PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Key.Split('.')[1], selfContained); + if (!typeBuilderInfo.Value.IsStructured) + { + Type listOf = typeof(DbSet<>); + Type selfContained = listOf.MakeGenericType(typeBuilderInfo.Value.Builder); + PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Key.Split('.')[1], selfContained); + } + } + // create the Main(string[] args) method + MethodBuilder methodbuilder = entitiesBuilder.DefineMethod("OnModelCreating", MethodAttributes.Public + | MethodAttributes.HideBySig + | MethodAttributes.CheckAccessOnOverride + | MethodAttributes.Virtual, + typeof(void), new Type[] { typeof(DbModelBuilder) }); + + // generate the IL for the Main method + ILGenerator ilGenerator = methodbuilder.GetILGenerator(); + ilGenerator.ThrowException(typeof(UnintentionalCodeFirstException)); + ilGenerator.Emit(OpCodes.Ret); entitiesBuilder.CreateType(); @@ -171,12 +188,6 @@ internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilde } } - - - - - - } internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder) @@ -211,40 +222,18 @@ internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBui } else { + var previouslyBuiltType = moduleBuilder.GetType(property.Type.FullName()); propertyType = previouslyBuiltType; } - - + } + if (property.Type.IsNullable && Nullable.GetUnderlyingType(propertyType)!=null) + { + Type nullableOf = typeof(Nullable<>); + Type selfContained = nullableOf.MakeGenericType(propertyType); + propertyType = selfContained; } PropertyBuilderHelper.BuildProperty(typeBuilder, propertyName, propertyType); - //var field = typeBuilder.DefineField(propertyName, propertyType, FieldAttributes.Private); - //var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); - - //// Generate getter method - - //var getter = typeBuilder.DefineMethod("Get", MethodAttributes.Public, propertyType, Type.EmptyTypes); - - //var il = getter.GetILGenerator(); - - //il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack - //il.Emit(OpCodes.Ldfld, field); // Load the field "_Name" - //il.Emit(OpCodes.Ret); // Return - - //propertyBuilder.SetGetMethod(getter); - - //// Generate setter method - - //var setter = typeBuilder.DefineMethod("Set", MethodAttributes.Public, null, new[] { propertyType }); - - //il = setter.GetILGenerator(); - - //il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack - //il.Emit(OpCodes.Ldarg_1); // Push "value" on the stack - //il.Emit(OpCodes.Stfld, field); // Set the field "_Name" to "value" - //il.Emit(OpCodes.Ret); // Return - - //propertyBuilder.SetSetMethod(setter); } /// @@ -299,19 +288,17 @@ private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNu static void Main(string[] args) { var model = ReadModel(@"C:\repos\fun\lab\ApiAsAService\EdmHelperTest\NW_Simple.xml"); - + var name = "NW_Simple"; // create a dynamic assembly and module AssemblyName assemblyName = new AssemblyName(); - assemblyName.Name = "HelloWorld"; + assemblyName.Name = name; AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); ModuleBuilder module; - module = assemblyBuilder.DefineDynamicModule("HelloWorld.exe"); + module = assemblyBuilder.DefineDynamicModule($"{assemblyName.Name}.dll"); BuildModules(model, module); - assemblyBuilder.Save("HelloWorld.exe"); + assemblyBuilder.Save($"{assemblyName.Name}.dll"); } - - } } diff --git a/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs b/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs index ac0e515..dd5c03d 100644 --- a/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs +++ b/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs @@ -8,7 +8,6 @@ public class PropertyBuilderHelper { public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Type fieldType) { - FieldBuilder fieldBldr = typeBuilder.DefineField(fieldName, fieldType, FieldAttributes.Private); From 2aab065479647dd64600abfbbd98be1dc65941e6 Mon Sep 17 00:00:00 2001 From: riach Date: Tue, 23 Jul 2019 16:51:12 -0700 Subject: [PATCH 10/21] wip --- ApiAsAService/ApiAsAService.sln | 42 --- ApiAsAService/ClassLibrary1/Class1.cs | 13 - .../ClassLibrary1/ClassLibrary1.csproj | 49 --- .../ClassLibrary1/Properties/AssemblyInfo.cs | 36 -- .../EdmHelperTest/EdmHelperTest.csproj | 31 -- .../EdmHelperTest/NWSimple_LoadTest.cs | 16 - ApiAsAService/EdmHelperTest/NW_Simple.xml | 143 -------- ApiAsAService/EdmHelperTest/Trippin.xml | 167 ---------- .../EdmHelperTest/Trippin_LoadTest.cs | 15 - ApiAsAService/EdmLoader/EdmLoader/App.config | 17 - .../EdmLoader/EdmLoader/EdmLoader.cs | 314 ------------------ .../EdmLoader/EdmLoader/EdmLoader.csproj | 88 ----- .../EdmLoader/Properties/AssemblyInfo.cs | 36 -- .../EdmLoader/PropertyBuilderHelper.cs | 73 ---- .../EdmLoader/EdmLoader/TypeBuilderHelpers.cs | 76 ----- .../EdmLoader/EdmLoader/packages.config | 6 - .../EdmLoaderTest/EdmLoaderTest.csproj | 67 ---- .../EdmLoaderTest/EdmModelLoaderTest.cs | 16 - ApiAsAService/EdmLoaderTest/NW_Simple.xml | 143 -------- .../EdmLoaderTest/Properties/AssemblyInfo.cs | 20 -- ApiAsAService/EdmLoaderTest/Trippin.xml | 167 ---------- ApiAsAService/EdmLoaderTest/packages.config | 5 - 22 files changed, 1540 deletions(-) delete mode 100644 ApiAsAService/ClassLibrary1/Class1.cs delete mode 100644 ApiAsAService/ClassLibrary1/ClassLibrary1.csproj delete mode 100644 ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs delete mode 100644 ApiAsAService/EdmHelperTest/EdmHelperTest.csproj delete mode 100644 ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs delete mode 100644 ApiAsAService/EdmHelperTest/NW_Simple.xml delete mode 100644 ApiAsAService/EdmHelperTest/Trippin.xml delete mode 100644 ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs delete mode 100644 ApiAsAService/EdmLoader/EdmLoader/App.config delete mode 100644 ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs delete mode 100644 ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj delete mode 100644 ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs delete mode 100644 ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs delete mode 100644 ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs delete mode 100644 ApiAsAService/EdmLoader/EdmLoader/packages.config delete mode 100644 ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj delete mode 100644 ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs delete mode 100644 ApiAsAService/EdmLoaderTest/NW_Simple.xml delete mode 100644 ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs delete mode 100644 ApiAsAService/EdmLoaderTest/Trippin.xml delete mode 100644 ApiAsAService/EdmLoaderTest/packages.config diff --git a/ApiAsAService/ApiAsAService.sln b/ApiAsAService/ApiAsAService.sln index 23d66b3..0a646a4 100644 --- a/ApiAsAService/ApiAsAService.sln +++ b/ApiAsAService/ApiAsAService.sln @@ -5,14 +5,8 @@ VisualStudioVersion = 15.0.27703.2026 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAService\ApiAsAService.csproj", "{A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLoader", "EdmLoader\EdmLoader\EdmLoader.csproj", "{C9516DB1-9D15-4E31-9FC5-795656878426}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmHelperTest", "EdmHelperTest\EdmHelperTest.csproj", "{DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLoadTester", "EdmLoadTester\EdmLoadTester.csproj", "{3CF839FF-83B2-4162-B860-68F57B7B0190}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{E51693D7-8503-419A-A7B5-8EAD987E54FC}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReflectionTester", "ReflectionTester\ReflectionTester.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" EndProject Global @@ -37,30 +31,6 @@ Global {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.Build.0 = Release|Any CPU {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.ActiveCfg = Release|x64 {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.Build.0 = Release|x64 - {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|x64.ActiveCfg = Release|x64 - {C9516DB1-9D15-4E31-9FC5-795656878426}.CodeAnalysis|x64.Build.0 = Release|x64 - {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|x64.ActiveCfg = Debug|x64 - {C9516DB1-9D15-4E31-9FC5-795656878426}.Debug|x64.Build.0 = Debug|x64 - {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|Any CPU.Build.0 = Release|Any CPU - {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|x64.ActiveCfg = Release|x64 - {C9516DB1-9D15-4E31-9FC5-795656878426}.Release|x64.Build.0 = Release|x64 - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|x64.ActiveCfg = Release|x64 - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.CodeAnalysis|x64.Build.0 = Release|x64 - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|x64.ActiveCfg = Debug|x64 - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Debug|x64.Build.0 = Debug|x64 - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|Any CPU.Build.0 = Release|Any CPU - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|x64.ActiveCfg = Release|x64 - {DFC7A8A2-9075-414B-BDAC-67CA0A1F5104}.Release|x64.Build.0 = Release|x64 {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU @@ -73,18 +43,6 @@ Global {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.Build.0 = Release|Any CPU {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.ActiveCfg = Release|Any CPU {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.Build.0 = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.CodeAnalysis|x64.Build.0 = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|x64.ActiveCfg = Debug|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Debug|x64.Build.0 = Debug|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|Any CPU.Build.0 = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|x64.ActiveCfg = Release|Any CPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC}.Release|x64.Build.0 = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU diff --git a/ApiAsAService/ClassLibrary1/Class1.cs b/ApiAsAService/ClassLibrary1/Class1.cs deleted file mode 100644 index 744d100..0000000 --- a/ApiAsAService/ClassLibrary1/Class1.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Foonamespace -{ - public class Class1 - { - public string Foo { get; set; } - } -} diff --git a/ApiAsAService/ClassLibrary1/ClassLibrary1.csproj b/ApiAsAService/ClassLibrary1/ClassLibrary1.csproj deleted file mode 100644 index df3e8c9..0000000 --- a/ApiAsAService/ClassLibrary1/ClassLibrary1.csproj +++ /dev/null @@ -1,49 +0,0 @@ - - - - - Debug - AnyCPU - {E51693D7-8503-419A-A7B5-8EAD987E54FC} - Library - Properties - ClassLibrary1 - ClassLibrary1 - v4.6.1 - 512 - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs b/ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs deleted file mode 100644 index 8eadb47..0000000 --- a/ApiAsAService/ClassLibrary1/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ClassLibrary1")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ClassLibrary1")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("e51693d7-8503-419a-a7b5-8ead987e54fc")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmHelperTest/EdmHelperTest.csproj b/ApiAsAService/EdmHelperTest/EdmHelperTest.csproj deleted file mode 100644 index 86092d4..0000000 --- a/ApiAsAService/EdmHelperTest/EdmHelperTest.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - netcoreapp2.1 - - false - - AnyCPU;x64 - - - - - - - - - - - - - - - - Always - - - Always - - - - diff --git a/ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs b/ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs deleted file mode 100644 index 6583d9d..0000000 --- a/ApiAsAService/EdmHelperTest/NWSimple_LoadTest.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace EdmHelperTest -{ - [TestClass] - public class NWSimple_LoadTest - { - [TestMethod] - public void TestEdmLoader() - { - var model = EdmLoader.EdmLoader.ReadModel("NW_Simple.xml"); - Assert.IsNotNull(model); - EdmLoader.EdmLoader.GetComplexTypes(model, "NorthWind_Simple"); - } - } -} diff --git a/ApiAsAService/EdmHelperTest/NW_Simple.xml b/ApiAsAService/EdmHelperTest/NW_Simple.xml deleted file mode 100644 index a9e9939..0000000 --- a/ApiAsAService/EdmHelperTest/NW_Simple.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmHelperTest/Trippin.xml b/ApiAsAService/EdmHelperTest/Trippin.xml deleted file mode 100644 index dec04f0..0000000 --- a/ApiAsAService/EdmHelperTest/Trippin.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs b/ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs deleted file mode 100644 index eadcea6..0000000 --- a/ApiAsAService/EdmHelperTest/Trippin_LoadTest.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace EdmHelperTest -{ - [TestClass] - public class Trippin_LoadTest - { - [TestMethod] - public void TestEdmLoader() - { - var model = EdmLoader.EdmLoader.ReadModel("Trippin.xml"); - Assert.IsNotNull(model); - } - } -} diff --git a/ApiAsAService/EdmLoader/EdmLoader/App.config b/ApiAsAService/EdmLoader/EdmLoader/App.config deleted file mode 100644 index 7e1d79c..0000000 --- a/ApiAsAService/EdmLoader/EdmLoader/App.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - -
    - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs b/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs deleted file mode 100644 index 3434940..0000000 --- a/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdmLoader -{ - using System.Data.Entity; - using System.IO; - using System.Reflection; - using System.Reflection.Emit; - using System.Text.RegularExpressions; - using System.Xml; - using Microsoft.OData.Edm; - using Microsoft.OData.Edm.Csdl; - using Microsoft.OData.Edm.Validation; - - - - public class TypeBuilderInfo - { - public bool IsDerived { get; set; } - public TypeBuilder Builder { get; set; } - } - public class EdmLoader - { - static Dictionary _typeBuildersDict = new Dictionary(); - static Regex collectionRegex = new Regex(@"Collection\((.+)\)", RegexOptions.Compiled); - public static void Main(string[] args) - { - var model = ReadModel(@"C:\repos\fun\lab\ApiAsAService\EdmHelperTest\NW_Simple.xml"); - GetComplexTypes(model, "NW_Simple"); - - } - - public static IEdmModel ReadModel(string fileName) - { - var edmxReaderSettings = new global::Microsoft.OData.Edm.Csdl.CsdlReaderSettings() - { - IgnoreUnexpectedAttributesAndElements = true - }; - var references = global::System.Linq.Enumerable.Empty(); - - using (var reader = XmlReader.Create(fileName)) - { - IEdmModel model; - IEnumerable errors; - if (CsdlReader.TryParse(reader, references, edmxReaderSettings, out model, out errors)) - { - return model; - } - return null; - } - } - - public static void GetComplexTypes(IEdmModel model, string assemblyName = "NorthWind_Simple") - { - var assemblyFullName = assemblyName + ".DataModels.dll"; - var assembly = new AssemblyName(assemblyName); - var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assembly,AssemblyBuilderAccess.RunAndSave ); - var moduleBuilder = assemblyBuilder.DefineDynamicModule("Modules" + ".mod", assemblyFullName - , false); - IList baseProps = new List(); - foreach (var modelSchemaElement in model.SchemaElements) - { - var one = model.FindDeclaredType(modelSchemaElement.FullName()); - - if (one is IEdmStructuredType) - { - if (one is IEdmEntityType) - { - - var two = model.FindDirectlyDerivedTypes((IEdmStructuredType)one); - if (two.Any()) - { - //baseProps.Add(one.FullName()); - //Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); - - } - } - else - { - baseProps.Add(one.FullName()); - Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); - - } - - } - - - - } - - //foreach (var modelSchemaElement in model.SchemaElements) - //{ - // if (!baseProps.Contains(modelSchemaElement.FullName())) - // { - // var one = model.FindDeclaredType(modelSchemaElement.FullName()); - // if (one != null) - // { - // Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); - // } - // } - //} - - //foreach (var modelSchemaElement in model.SchemaElements) - //{ - - // var one = model.FindDeclaredType(modelSchemaElement.FullName()); - // if (one != null) - // { - // Compile((IEdmStructuredType)one, moduleBuilder, one.FullName(), true); - // } - - //} - - foreach (var typeBuilder in _typeBuildersDict) - { - //if (typeBuilder.Value.IsDerived) - //{ - - // var previouslyBuiltType = _typeBuildersDict[(typeBuilder.Value.Builder.BaseType.FullName)]; - // typeBuilder.Value.Builder.CreatePassThroughConstructors(previouslyBuiltType.Builder); - //} - typeBuilder.Value.Builder.CreateType(); - } - - ////generate the entities type - //var entitiesBuilder = moduleBuilder.DefineType("Entities", TypeAttributes.Class | TypeAttributes.Public, typeof(DbContext)); - //var dbContextType = typeof(DbContext); - //entitiesBuilder.CreatePassThroughConstructors(dbContextType); - - - //foreach (var typeBuilderInfo in _typeBuildersDict) - //{ - // Type listOf = typeof(DbSet<>); - // Type selfContained = listOf.MakeGenericType(typeBuilderInfo.Value.Builder); - // PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Key.Split('.')[1], selfContained); - //} - //entitiesBuilder.CreateType(); - - assemblyBuilder.Save(assemblyFullName); - } - - - - internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilder, string moduleName, bool navPass = false) - { - TypeBuilder typeBuilder=null; - if (type.BaseType != null && !navPass) - { - var previouslyBuiltType = _typeBuildersDict[(type.BaseType.FullTypeName())]; - - typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public, previouslyBuiltType.Builder); - _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = true }); - - - } - - if (!navPass) - { - if (typeBuilder==null) - { - typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public); - _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = false }); - } - - foreach (var property in type.DeclaredProperties) - { - if (property.PropertyKind != EdmPropertyKind.Navigation) - { - GenerateProperty(property, typeBuilder, moduleBuilder); - - } - } - - } - else - { - typeBuilder = _typeBuildersDict[moduleName].Builder; - foreach (var property in type.DeclaredProperties) - { - if (property.PropertyKind == EdmPropertyKind.Navigation) - GenerateProperty(property, typeBuilder, moduleBuilder); - } - } - - - - - - - - } - - internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder) - { - //var propertyName = property.Name; - - //var emdPropType = property.Type.PrimitiveKind(); - //var propertyType = GetPrimitiveClrType(emdPropType, false); - //if (propertyType == null) - //{ - // if (property.Type.FullName().ToLower().Contains("geography")) - // { - // return; - // } - - // if (property.PropertyKind == EdmPropertyKind.Navigation) - // { - // if (property.Type.FullName().StartsWith("Collection")) - // { - // var typeName = collectionRegex.Match(property.Type.FullName()).Groups[1].Value; - // Type listOf = typeof(List<>); - // Type selfContained = listOf.MakeGenericType(_typeBuildersDict[typeName].Builder); - // propertyType = selfContained; - // } - // else - // { - // var navProptype = _typeBuildersDict[property.Type.FullName()]; - // propertyType = navProptype.Builder; - // } - - - // } - // else - // { - // var previouslyBuiltType = moduleBuilder.GetType(property.Type.FullName()); - // propertyType = previouslyBuiltType; - // } - - - //} - //PropertyBuilderHelper.BuildProperty(typeBuilder, propertyName, propertyType); - // var field = typeBuilder.DefineField(propertyName, propertyType, FieldAttributes.Private); - // var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); - // - // // Generate getter method - // - // var getter = typeBuilder.DefineMethod("Get", MethodAttributes.Public, propertyType, Type.EmptyTypes); - // - // var il = getter.GetILGenerator(); - // - // il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack - // il.Emit(OpCodes.Ldfld, field); // Load the field "_Name" - // il.Emit(OpCodes.Ret); // Return - // - // propertyBuilder.SetGetMethod(getter); - // - // // Generate setter method - // - // var setter = typeBuilder.DefineMethod("Set", MethodAttributes.Public, null, new[] { propertyType }); - // - // il = setter.GetILGenerator(); - // - // il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack - // il.Emit(OpCodes.Ldarg_1); // Push "value" on the stack - // il.Emit(OpCodes.Stfld, field); // Set the field "_Name" to "value" - // il.Emit(OpCodes.Ret); // Return - // - // propertyBuilder.SetSetMethod(setter); - } - - /// - /// Get Clr type - /// - /// Edm Primitive Type Kind - /// Nullable value - /// CLR type - private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNullable) - { - switch (typeKind) - { - case EdmPrimitiveTypeKind.Binary: - return typeof(byte[]); - case EdmPrimitiveTypeKind.Boolean: - return isNullable ? typeof(Boolean?) : typeof(Boolean); - case EdmPrimitiveTypeKind.Byte: - return isNullable ? typeof(Byte?) : typeof(Byte); - case EdmPrimitiveTypeKind.Date: - return isNullable ? typeof(Date?) : typeof(Date); - case EdmPrimitiveTypeKind.DateTimeOffset: - return isNullable ? typeof(DateTimeOffset?) : typeof(DateTimeOffset); - case EdmPrimitiveTypeKind.Decimal: - return isNullable ? typeof(Decimal?) : typeof(Decimal); - case EdmPrimitiveTypeKind.Double: - return isNullable ? typeof(Double?) : typeof(Double); - case EdmPrimitiveTypeKind.Guid: - return isNullable ? typeof(Guid?) : typeof(Guid); - case EdmPrimitiveTypeKind.Int16: - return isNullable ? typeof(Int16?) : typeof(Int16); - case EdmPrimitiveTypeKind.Int32: - return isNullable ? typeof(Int32?) : typeof(Int32); - case EdmPrimitiveTypeKind.Int64: - return isNullable ? typeof(Int64?) : typeof(Int64); - case EdmPrimitiveTypeKind.SByte: - return isNullable ? typeof(SByte?) : typeof(SByte); - case EdmPrimitiveTypeKind.Single: - return isNullable ? typeof(Single?) : typeof(Single); - case EdmPrimitiveTypeKind.Stream: - return typeof(Stream); - case EdmPrimitiveTypeKind.String: - return typeof(String); - case EdmPrimitiveTypeKind.Duration: - return isNullable ? typeof(TimeSpan?) : typeof(TimeSpan); - case EdmPrimitiveTypeKind.TimeOfDay: - return isNullable ? typeof(TimeOfDay?) : typeof(TimeOfDay); - default: - return null; - } - } - } -} diff --git a/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj b/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj deleted file mode 100644 index b275018..0000000 --- a/ApiAsAService/EdmLoader/EdmLoader/EdmLoader.csproj +++ /dev/null @@ -1,88 +0,0 @@ - - - - - Debug - AnyCPU - {C9516DB1-9D15-4E31-9FC5-795656878426} - Exe - EdmLoader - EdmLoader - v4.6.1 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - - ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll - - - ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll - - - ..\..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs b/ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs deleted file mode 100644 index 1227ef0..0000000 --- a/ApiAsAService/EdmLoader/EdmLoader/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("EdmLoader")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("EdmLoader")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c9516db1-9d15-4e31-9fc5-795656878426")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs b/ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs deleted file mode 100644 index 76f22a1..0000000 --- a/ApiAsAService/EdmLoader/EdmLoader/PropertyBuilderHelper.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdmLoader -{ - using System.Reflection; - using System.Reflection.Emit; - using System.Threading; - - public class PropertyBuilderHelper - { - public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Type fieldType) - { - - FieldBuilder fieldBldr = typeBuilder.DefineField(fieldName, - fieldType, - FieldAttributes.Private); - - // The last argument of DefineProperty is null, because the - // property has no parameters. (If you don't specify null, you must - // specify an array of Type objects. For a parameterless property, - // use an array with no elements: new Type[] {}) - PropertyBuilder propBuilder = typeBuilder.DefineProperty(fieldName, - PropertyAttributes.HasDefault, - fieldType, - null); - - // The property set and property get methods require a special - // set of attributes. - MethodAttributes getSetAttr = - MethodAttributes.Public | MethodAttributes.SpecialName | - MethodAttributes.HideBySig; - - // Define the "get" accessor method for CustomerName. - MethodBuilder propMethodBldr = - typeBuilder.DefineMethod($"get_{fieldName}", - getSetAttr, - fieldType, - Type.EmptyTypes); - - ILGenerator custNameGetIL = propMethodBldr.GetILGenerator(); - - custNameGetIL.Emit(OpCodes.Ldarg_0); - custNameGetIL.Emit(OpCodes.Ldfld, fieldBldr); - custNameGetIL.Emit(OpCodes.Ret); - - // Define the "set" accessor method for CustomerName. - MethodBuilder propSetMethodBldr = - typeBuilder.DefineMethod($"set_{fieldName}", - getSetAttr, - null, - new Type[] { fieldType }); - - ILGenerator custNameSetIL = propSetMethodBldr.GetILGenerator(); - - custNameSetIL.Emit(OpCodes.Ldarg_0); - custNameSetIL.Emit(OpCodes.Ldarg_1); - custNameSetIL.Emit(OpCodes.Stfld, fieldBldr); - custNameSetIL.Emit(OpCodes.Ret); - - // Last, we must map the two methods created above to our PropertyBuilder to - // their corresponding behaviors, "get" and "set" respectively. - propBuilder.SetGetMethod(propMethodBldr); - propBuilder.SetSetMethod(propSetMethodBldr); - - - - } - } -} diff --git a/ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs b/ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs deleted file mode 100644 index 03fc133..0000000 --- a/ApiAsAService/EdmLoader/EdmLoader/TypeBuilderHelpers.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; - -public static class TypeBuilderHelper -{ - /// Creates one constructor for each public constructor in the base class. Each constructor simply - /// forwards its arguments to the base constructor, and matches the base constructor's signature. - /// Supports optional values, and custom attributes on constructors and parameters. - /// Does not support n-ary (variadic) constructors - public static void CreatePassThroughConstructors(this TypeBuilder builder, Type baseType) - { - //foreach (var constructor in baseType.GetConstructors()) - //{ - // var parameters = constructor.GetParameters(); - // if (parameters.Length > 0 && parameters.Last().IsDefined(typeof(ParamArrayAttribute), false)) - // { - // //throw new InvalidOperationException("Variadic constructors are not supported"); - // continue; - // } - - // var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); - // var requiredCustomModifiers = parameters.Select(p => p.GetRequiredCustomModifiers()).ToArray(); - // var optionalCustomModifiers = parameters.Select(p => p.GetOptionalCustomModifiers()).ToArray(); - - // var ctor = builder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers); - // for (var i = 0; i < parameters.Length; ++i) - // { - // var parameter = parameters[i]; - // var parameterBuilder = ctor.DefineParameter(i + 1, parameter.Attributes, parameter.Name); - // if (((int)parameter.Attributes & (int)ParameterAttributes.HasDefault) != 0) - // { - // parameterBuilder.SetConstant(parameter.RawDefaultValue); - // } - - // foreach (var attribute in BuildCustomAttributes(parameter.GetCustomAttributesData())) - // { - // parameterBuilder.SetCustomAttribute(attribute); - // } - // } - - // foreach (var attribute in BuildCustomAttributes(constructor.GetCustomAttributesData())) - // { - // ctor.SetCustomAttribute(attribute); - // } - - // var emitter = ctor.GetILGenerator(); - // emitter.Emit(OpCodes.Nop); - - // // Load `this` and call base constructor with arguments - // emitter.Emit(OpCodes.Ldarg_0); - // for (var i = 1; i <= parameters.Length; ++i) - // { - // emitter.Emit(OpCodes.Ldarg, i); - // } - // emitter.Emit(OpCodes.Call, constructor); - - // emitter.Emit(OpCodes.Ret); - //} - } - - - private static CustomAttributeBuilder[] BuildCustomAttributes(IEnumerable customAttributes) - { - return customAttributes.Select(attribute => { - var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray(); - var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); - var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray(); - var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); - var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray(); - return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues); - }).ToArray(); - } -} \ No newline at end of file diff --git a/ApiAsAService/EdmLoader/EdmLoader/packages.config b/ApiAsAService/EdmLoader/EdmLoader/packages.config deleted file mode 100644 index 5816bdb..0000000 --- a/ApiAsAService/EdmLoader/EdmLoader/packages.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj b/ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj deleted file mode 100644 index 7fea77b..0000000 --- a/ApiAsAService/EdmLoaderTest/EdmLoaderTest.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - Debug - AnyCPU - {5DDF63C2-4186-4705-9193-EBA92623A37B} - Library - Properties - EdmLoaderTest - EdmLoaderTest - v4.7 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - Always - - - - - - - Always - - - - - - {c9516db1-9d15-4e31-9fc5-795656878426} - EdmLoader - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs b/ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs deleted file mode 100644 index 2fbffc4..0000000 --- a/ApiAsAService/EdmLoaderTest/EdmModelLoaderTest.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace EdmLoaderTest -{ - [TestClass] - public class EdmModelLoaderTest - { - [TestMethod] - public void TestEdmLoader() - { - var model = EdmLoader.EdmLoader.ReadModel("NW_Simple.xml"); - Assert.IsNotNull(model); - } - } -} diff --git a/ApiAsAService/EdmLoaderTest/NW_Simple.xml b/ApiAsAService/EdmLoaderTest/NW_Simple.xml deleted file mode 100644 index a9e9939..0000000 --- a/ApiAsAService/EdmLoaderTest/NW_Simple.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs b/ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 0fe6592..0000000 --- a/ApiAsAService/EdmLoaderTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("EdmLoaderTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("EdmLoaderTest")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("5ddf63c2-4186-4705-9193-eba92623a37b")] - -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmLoaderTest/Trippin.xml b/ApiAsAService/EdmLoaderTest/Trippin.xml deleted file mode 100644 index dec04f0..0000000 --- a/ApiAsAService/EdmLoaderTest/Trippin.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Name - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoaderTest/packages.config b/ApiAsAService/EdmLoaderTest/packages.config deleted file mode 100644 index 4a68cce..0000000 --- a/ApiAsAService/EdmLoaderTest/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file From 054eb79fc0ef09ba109492306231157da4c244bd Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Tue, 23 Jul 2019 17:30:11 -0700 Subject: [PATCH 11/21] Starting to implement getting datasource from url. --- .../Extensions/HttpConfigurationExtensions.cs | 2 +- .../RestierController.cs | 10 +- .../Routing/RestierRoutingConvention.cs | 2 +- .../Trippin/Trippin/Api/DynamicApi.cs | 135 +----- .../Trippin/Trippin/App_Data/Trippin.xml | 159 +++++++ .../Trippin/Trippin/App_Start/WebApiConfig.cs | 27 +- .../Controllers/DynamicApiController.cs | 41 ++ .../Trippin/Controllers/TrippinController.cs | 418 ------------------ .../Trippin/Trippin/DynamicHelper.cs | 52 +++ .../Trippin/Trippin/DynamicODataRoute.cs | 82 ++++ .../Trippin/Trippin/DynamicRouteConstraint.cs | 205 +++++++++ ...crosoft.OData.Service.ApiAsAService.csproj | 5 +- 12 files changed, 585 insertions(+), 553 deletions(-) create mode 100644 ApiAsAService/Trippin/Trippin/App_Data/Trippin.xml create mode 100644 ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs create mode 100644 ApiAsAService/Trippin/Trippin/DynamicHelper.cs create mode 100644 ApiAsAService/Trippin/Trippin/DynamicODataRoute.cs create mode 100644 ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs index b6b6c04..9555e07 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Extensions/HttpConfigurationExtensions.cs @@ -75,7 +75,7 @@ void configureAction(IContainerBuilder builder) => builder /// The instance. /// The name of the route. /// The routing conventions created. - private static IList CreateRestierRoutingConventions( + internal static IList CreateRestierRoutingConventions( this HttpConfiguration config, string routeName) { var conventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, config); diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs index 40a7161..b78cd7a 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/RestierController.cs @@ -62,12 +62,20 @@ private ApiBase Api } } + /// + /// new method for derived class to set the API + /// + protected void SetApi(ApiBase api) + { + this.api = api; + } + /// /// Handles a GET request to query entities. /// /// The cancellation token. /// The task object that contains the response message. - public async Task Get(CancellationToken cancellationToken) + protected async Task GetResponse(CancellationToken cancellationToken) { var path = GetPath(); var lastSegment = path.Segments.LastOrDefault(); diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs index e272f99..05c892e 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs @@ -19,7 +19,7 @@ namespace Microsoft.Restier.AspNet ///
    internal class RestierRoutingConvention : IODataRoutingConvention { - private const string RestierControllerName = "Restier"; + private const string RestierControllerName = "DynamicApi"; private const string MethodNameOfGet = "Get"; private const string MethodNameOfPost = "Post"; private const string MethodNameOfPut = "Put"; diff --git a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs index 3862fae..343db32 100644 --- a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs +++ b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs @@ -20,6 +20,13 @@ using Microsoft.OData.Edm.Csdl; using System.Xml; using Microsoft.OData.Edm.Validation; +using Microsoft.AspNet.OData.Routing; +using System.Web.Http; +using Microsoft.Restier.AspNet.Batch; +using Microsoft.Restier.AspNet; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Routing.Conventions; namespace Microsoft.OData.Service.ApiAsAService.Api { @@ -27,133 +34,12 @@ public class DynamicApi : EntityFrameworkApi where T : System.Data.Entity. { public T ModelContext { get { return DbContext; } } - //[Resource] - //public Person Me - //{ - // get - // { - // return DbContext.People - // .Include("Friends") - // .Include("Trips") - // .Single(p => p.PersonId == 1); - // } - //} - - //private IQueryable PeopleWithFriends - //{ - // get { return ModelContext.People.Include("Friends"); } - //} - - ///// - ///// Implements an action import. - ///// - //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", HasSideEffects = true)] - //public void ResetDataSource() - //{ - // TrippinModel.ResetDataSource(); - //} - - ///// - ///// Action import - clean up all the expired trips. - ///// - //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", HasSideEffects = true)] - //public void CleanUpExpiredTrips() - //{ - // // DO NOT ACTUALLY REMOVE THE TRIPS. - //} - - ///// - ///// Bound action - set the end-up time of a trip. - ///// - ///// The trip to update. - ///// The trip updated. - //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", IsBound = true, HasSideEffects = true)] - //public Trip EndTrip(Trip trip) - //{ - // // DO NOT ACTUALLY UPDATE THE TRIP. - // return trip; - //} - - ///// - ///// Bound function - gets the number of friends of a person. - ///// - ///// The key of the binding person. - ///// The number of friends of the person. - //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", IsBound = true)] - //public int GetNumberOfFriends(Person person) - //{ - // if (person == null) - // { - // return 0; - // } - - // var personWithFriends = PeopleWithFriends.Single(p => p.PersonId == person.PersonId); - // return personWithFriends.Friends == null ? 0 : personWithFriends.Friends.Count; - //} - - ///// - ///// Function import - gets the person with most friends. - ///// - ///// The person with most friends. - //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", EntitySet = "People")] - //public Person GetPersonWithMostFriends() - //{ - // Person result = null; - - // foreach (var person in PeopleWithFriends) - // { - // if (person.Friends == null) - // { - // continue; - // } - - // if (result == null) - // { - // result = person; - // } - - // if (person.Friends.Count > result.Friends.Count) - // { - // result = person; - // } - // } - - // return result; - //} - - ///// - ///// Function import - gets people with at least n friends. - ///// - ///// The minimum number of friends. - ///// People with at least n friends. - //[Operation(Namespace = "Microsoft.OData.Service.ApiAsAService.Models", EntitySet = "People")] - //public IEnumerable GetPeopleWithFriendsAtLeast(int n) - //{ - // foreach (var person in PeopleWithFriends) - // { - // if (person.Friends == null) - // { - // continue; - // } - - // if (person.Friends.Count >= n) - // { - // yield return person; - // } - // } - //} - - //protected bool CanDeleteTrips() - //{ - // return false; - //} - - public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) { // Add customized OData validation settings Func validationSettingFactory = (sp) => new ODataValidationSettings { - MaxAnyAllExpressionDepth =3, + MaxAnyAllExpressionDepth = 3, MaxExpansionDepth = 3 }; @@ -173,7 +59,6 @@ private class DynamicModelBuilder : IModelBuilder public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) { - //return await InnerHandler.GetModelAsync(context, cancellationToken); IEdmModel model; IEnumerable errors; var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); @@ -193,4 +78,4 @@ public DynamicApi(IServiceProvider serviceProvider) : base(serviceProvider) { } } - } \ No newline at end of file +} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/App_Data/Trippin.xml b/ApiAsAService/Trippin/Trippin/App_Data/Trippin.xml new file mode 100644 index 0000000..3ad3328 --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/App_Data/Trippin.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TimeStampValue + + + + + + + + + + + + + + + + + + + + + + + + LastUpdated + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs index ffa43c0..32a5581 100644 --- a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs +++ b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs @@ -3,6 +3,7 @@ using System.Web.Http; using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; using Microsoft.OData.Service.ApiAsAService.Api; using Microsoft.Restier.AspNet.Batch; @@ -20,14 +21,28 @@ public static async void RegisterService( { // enable query options for all properties config.Filter().Expand().Select().OrderBy().MaxTop(null).Count(); - await config.MapRestierRoute>( - "ApiAsAService", "", - new RestierBatchHandler(server)); - } + //await config.MapRestierRoute>( + // "ApiAsAService", "", + // new RestierBatchHandler(server)); - private static void MapRestierRoute(System.Type modelType) - { + HttpConfiguration dummyConfig = new HttpConfiguration(); + var services = dummyConfig.Services; + + ODataRoute route = await DynamicHelper.MapDynamicRoute( + typeof(Models.TrippinModel), + dummyConfig, + "RestierRoute", + "", + null); + + foreach(var service in dummyConfig.Services.GetServices(typeof(object))) + { + config.Services.Add(service.GetType(), service); + } + DynamicODataRoute odataRoute = new DynamicODataRoute(route.RoutePrefix, new DynamicRouteConstraint("RestierRoute")); + // config.Routes.Remove("RestierRoute"); + config.Routes.Add("DynamicRoute", odataRoute); } } } diff --git a/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs b/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs new file mode 100644 index 0000000..fba967e --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Edm; +using Microsoft.OData.Service.ApiAsAService.Api; +using Microsoft.OData.Service.ApiAsAService.Models; +using Microsoft.Restier.AspNet; +using Microsoft.Restier.Core; + +namespace Microsoft.OData.Service.ApiAsAService.Controllers +{ + public class DynamicApiController : RestierController + { + public async Task Get(CancellationToken cancellationToken) + { + // Get the data source from the request + ODataPath path = Request.ODataProperties().Path; + string dataSource = path.Segments[0].Identifier; + + //IEdmCollectionType collectionType = (IEdmCollectionType)path.EdmType; + //IEdmEntityTypeReference entityType = collectionType.ElementType.AsEntity(); + + Type dynamicType = dataSource=="Trippin" ? typeof(Models.TrippinModel) : typeof(Models.TrippinModel); + base.SetApi(DynamicHelper.CreateDynamicApi(dynamicType, Request.GetRequestContainer())); + + return await base.GetResponse(cancellationToken); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs b/ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs deleted file mode 100644 index 0dd2f14..0000000 --- a/ApiAsAService/Trippin/Trippin/Controllers/TrippinController.cs +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Web.Http; -using Microsoft.AspNet.OData; -using Microsoft.AspNet.OData.Extensions; -using Microsoft.AspNet.OData.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData.Edm; -using Microsoft.OData.Service.ApiAsAService.Api; -using Microsoft.OData.Service.ApiAsAService.Models; -using Microsoft.Restier.Core; - -namespace Microsoft.OData.Service.ApiAsAService.Controllers -{ - public class TrippinController : ODataController - { - //private TrippinModel DbContext - //{ - // get - // { - // var api = (DynamicApi)this.Request.GetRequestContainer().GetService(); - // return api.ModelContext; - // } - //} - - //private bool PeopleExists(int key) - //{ - // return DbContext.People.Any(p => p.PersonId == key); - //} - - //private string GetServiceRootUri() - //{ - // var routeName = Request.ODataProperties().RouteName; - // ODataRoute odataRoute = Configuration.Routes[routeName] as ODataRoute; - // var prefixName = odataRoute.RoutePrefix; - // var requestUri = Request.RequestUri.ToString(); - // var serviceRootUri = requestUri.Substring(0, requestUri.IndexOf(prefixName, StringComparison.InvariantCultureIgnoreCase) + prefixName.Length); - // return serviceRootUri; - //} - - //[HttpPut] - //[ODataRoute("People({key})/LastName")] - //public IHttpActionResult UpdatePersonLastName([FromODataUri]int key, [FromBody]string name) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.LastName = name; - - // try - // { - // DbContext.SaveChanges(); - // } - // catch (Exception e) - // { - // if (!PeopleExists(key)) - // { - // return NotFound(); - // } - // else - // { - // throw e; - // } - // } - // return Ok(name); - //} - - //[HttpPut] - //[ODataRoute("People({key})/BirthDate")] - //public IHttpActionResult UpdatePersonBirthDate([FromODataUri]int key, [FromBody]string birthDate) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.BirthDate = Date.Parse(birthDate); - - // try - // { - // DbContext.SaveChanges(); - // } - // catch (Exception e) - // { - // if (!PeopleExists(key)) - // { - // return NotFound(); - // } - // else - // { - // throw e; - // } - // } - // return Ok(birthDate); - //} - - //[HttpPut] - //[ODataRoute("People({key})/BirthDate2")] - //public IHttpActionResult UpdatePersonBirthDate2([FromODataUri]int key, [FromBody]string birthDate) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.BirthDate2 = Date.Parse(birthDate); - - // try - // { - // DbContext.SaveChanges(); - // } - // catch (Exception e) - // { - // if (!PeopleExists(key)) - // { - // return NotFound(); - // } - // else - // { - // throw e; - // } - // } - // return Ok(birthDate); - //} - - //[HttpPut] - //[ODataRoute("People({key})/BirthTime")] - //public IHttpActionResult UpdatePersonBirthTime([FromODataUri]int key, [FromBody]string birthTime) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.BirthTime = TimeOfDay.Parse(birthTime); - - // try - // { - // DbContext.SaveChanges(); - // } - // catch (Exception e) - // { - // if (!PeopleExists(key)) - // { - // return NotFound(); - // } - // else - // { - // throw e; - // } - // } - // return Ok(birthTime); - //} - - //[HttpPut] - //[ODataRoute("People({key})/BirthTime2")] - //public IHttpActionResult UpdatePersonBirthTime2([FromODataUri]int key, [FromBody]string birthTime) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.BirthTime2 = TimeOfDay.Parse(birthTime); - - // try - // { - // DbContext.SaveChanges(); - // } - // catch (Exception e) - // { - // if (!PeopleExists(key)) - // { - // return NotFound(); - // } - // else - // { - // throw e; - // } - // } - // return Ok(birthTime); - //} - - //[HttpPut] - //[ODataRoute("People({key})/BirthDateTime")] - //public IHttpActionResult UpdatePersonBirthDateTime([FromODataUri]int key, [FromBody]string birthDateTime) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.BirthDateTime = DateTimeOffset.Parse(birthDateTime).DateTime; - - // try - // { - // DbContext.SaveChanges(); - // } - // catch (Exception e) - // { - // if (!PeopleExists(key)) - // { - // return NotFound(); - // } - // else - // { - // throw e; - // } - // } - // return Ok(birthDateTime); - //} - - //[HttpPut] - //[ODataRoute("People({key})/BirthDateTime2")] - //public IHttpActionResult UpdatePersonBirthDateTime2([FromODataUri]int key, [FromBody]string birthDateTime) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.BirthDateTime2 = DateTimeOffset.Parse(birthDateTime).DateTime; - - // try - // { - // DbContext.SaveChanges(); - // } - // catch (Exception e) - // { - // if (!PeopleExists(key)) - // { - // return NotFound(); - // } - // else - // { - // throw e; - // } - // } - // return Ok(birthDateTime); - //} - - //[HttpGet] - //[ODataRoute("People({key})/Trips/$ref")] - //public IHttpActionResult GetRefToTripsFromPeople([FromODataUri]int key) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - // var trips = DbContext.Trips.Where(t => t.PersonId == key); - // var serviceRootUri = GetServiceRootUri(); - // IList uris = new List(); - // foreach (var trip in trips) - // { - // uris.Add(new Uri(string.Format("{0}/Trips({1})", serviceRootUri, trip.TripId))); - // } - // return Ok(uris); - //} - - //[HttpGet] - //[ODataRoute("People({key})/Trips({key2})/$ref")] - //public IHttpActionResult GetRefToTripsFromPeople([FromODataUri]int key, [FromODataUri]int key2) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - // var trips = DbContext.Trips.Where(t => t.PersonId == key); - // var serviceRootUri = GetServiceRootUri(); - - // if (trips.All(t => t.TripId != key2)) - // { - // return NotFound(); - // } - - // return Ok(new Uri(string.Format("{0}/Trips({1})", serviceRootUri, key2))); - //} - - //[HttpPost] - //[ODataRoute("People({key})/Trips")] - //public IHttpActionResult PostToTripsFromPeople([FromODataUri]int key, Trip trip) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - // if (entity.PersonId != key) - // { - // return BadRequest(); - // } - // DbContext.Trips.Add(trip); - // DbContext.SaveChanges(); - // return Created(trip); - //} - - //[HttpPost] - //[ODataRoute("People({key})/Trips/$ref")] - //public IHttpActionResult CreateRefForTripsToPeople([FromODataUri]int key, [FromBody] Uri link) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // var relatedKey = Helpers.GetKeyFromUri(Request, link); - // var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == relatedKey); - // if (trip == null) - // { - // return NotFound(); - // } - - // trip.PersonId = key; - // DbContext.SaveChanges(); - - // return StatusCode(HttpStatusCode.NoContent); - //} - - //[HttpDelete] - //[ODataRoute("People({key})/Trips({relatedKey})/$ref")] - //public IHttpActionResult DeleteRefToTripsFromPeople([FromODataUri]int key, [FromODataUri]int relatedKey) - //{ - // var entity = DbContext.People.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // var trip = DbContext.Trips.SingleOrDefault(t => t.TripId == relatedKey && t.PersonId == key); - // if (trip == null) - // { - // return NotFound(); - // } - - // trip.PersonId = null; - // DbContext.SaveChanges(); - - // return StatusCode(HttpStatusCode.NoContent); - //} - - //[HttpGet] - //[ODataRoute("Flights({key})/Airline/$ref")] - //public IHttpActionResult GetRefToAirlineFromFlight([FromODataUri]int key) - //{ - // var entity = DbContext.Flights.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // if (entity.AirlineId == null) - // { - // return NotFound(); - // } - - // var serviceRootUri = GetServiceRootUri(); - // var uri = new Uri(string.Format("{0}/Airlines('{1}')", serviceRootUri, entity.AirlineId)); - // return Ok(uri); - //} - - //[HttpPut] - //[ODataRoute("Flights({key})/Airline/$ref")] - //public IHttpActionResult UpdateRefToAirLineFromFlight([FromODataUri] int key, [FromBody] Uri link) - //{ - // var entity = DbContext.Flights.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // var relatedKey = Helpers.GetKeyFromUri(Request, link); - // var aireLine = DbContext.Airlines - // .SingleOrDefault(t => t.AirlineCode.Equals(relatedKey, StringComparison.OrdinalIgnoreCase)); - // if (aireLine == null) - // { - // return NotFound(); - // } - - // entity.Airline = aireLine; - // DbContext.SaveChanges(); - - // return StatusCode(HttpStatusCode.NoContent); - //} - - //[HttpDelete] - //[ODataRoute("Flights({key})/Airline/$ref")] - //public IHttpActionResult DeleteRefToAirLineFromFlight([FromODataUri] int key) - //{ - // var entity = DbContext.Flights.Find(key); - // if (entity == null) - // { - // return NotFound(); - // } - - // entity.AirlineId = null; - // DbContext.SaveChanges(); - - // return StatusCode(HttpStatusCode.NoContent); - //} - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/DynamicHelper.cs b/ApiAsAService/Trippin/Trippin/DynamicHelper.cs new file mode 100644 index 0000000..950a08b --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/DynamicHelper.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNet.OData.Routing; +using Microsoft.OData.Service.ApiAsAService.Api; +using Microsoft.Restier.AspNet.Batch; +using Microsoft.Restier.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Web.Http; + +namespace Microsoft.OData.Service.ApiAsAService +{ + public static class DynamicHelper + { + private const string MapRestierRouteMethod = "MapRestierRoute"; + private const string RestierAssembly = "Microsoft.Restier.AspNet"; + private const string httpConfigurationExtensionsType = "System.Web.Http.HttpConfigurationExtensions"; + private const string ApiAsAServiceAssembly = "Microsoft.OData.Service.ApiAsAService"; + private const string DynamicApiType = "Microsoft.OData.Service.ApiAsAService.Api.DynamicApi`1"; + private static Type dynamicApiType = Assembly.GetExecutingAssembly().GetType(DynamicApiType); + + public static Task MapDynamicRoute(Type tApi, HttpConfiguration config, string routeName, + string routePrefix, RestierBatchHandler batchHandler) + { + Type restierType = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == RestierAssembly).GetType(httpConfigurationExtensionsType); + Type dynamicType = dynamicApiType.MakeGenericType(tApi); + MethodInfo method = restierType.GetMethod( + MapRestierRouteMethod, + BindingFlags.Static | BindingFlags.Public, + null, + new[] { typeof(HttpConfiguration), typeof(string), typeof(string), typeof(RestierBatchHandler) }, + null); + return (Task)method.MakeGenericMethod(dynamicType).Invoke(null, new object[] { config, routeName, routePrefix, batchHandler }); + } + + public static ApiBase CreateDynamicApi(Type tApi, IServiceProvider serviceProvider) + { + return (ApiBase)dynamicApiType.MakeGenericType(tApi).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); + } + + //private static Type GetDynamicApiType() + //{ + // if (dynamicApiType == null) + // { + // dynamicApiType = Assembly.GetExecutingAssembly().GetType(DynamicApiType); + // } + + // return dynamicApiType; + //} + } +} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/DynamicODataRoute.cs b/ApiAsAService/Trippin/Trippin/DynamicODataRoute.cs new file mode 100644 index 0000000..f5877d3 --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/DynamicODataRoute.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Routing; + +namespace Microsoft.OData.Service.ApiAsAService +{ + public class DynamicODataRoute : ODataRoute + { + private static readonly string _escapedHashMark = Uri.HexEscape('#'); + private static readonly string _escapedQuestionMark = Uri.HexEscape('?'); + + private bool _canGenerateDirectLink; + + public DynamicODataRoute(string routePrefix, ODataPathRouteConstraint pathConstraint) + : base(routePrefix, pathConstraint) + { + _canGenerateDirectLink = routePrefix != null && RoutePrefix.IndexOf('{') == -1; + } + + public override IHttpVirtualPathData GetVirtualPath( + HttpRequestMessage request, + IDictionary values) + { + if (values == null || !values.Keys.Contains(HttpRoute.HttpRouteKey, StringComparer.OrdinalIgnoreCase)) + { + return null; + } + + object odataPathValue; + if (!values.TryGetValue(ODataRouteConstants.ODataPath, out odataPathValue)) + { + return null; + } + + string odataPath = odataPathValue as string; + if (odataPath != null) + { + return GenerateLinkDirectly(request, odataPath) ?? base.GetVirtualPath(request, values); + } + + return null; + } + + internal HttpVirtualPathData GenerateLinkDirectly(HttpRequestMessage request, string odataPath) + { + HttpConfiguration configuration = request.GetConfiguration(); + if (configuration == null || !_canGenerateDirectLink) + { + return null; + } + + string dataSource = request.Properties[DynamicRouteConstraint.DataSourceNameProperty] as string; + string link = CombinePathSegments(RoutePrefix, dataSource); + link = CombinePathSegments(link, odataPath); + link = UriEncode(link); + + return new HttpVirtualPathData(this, link); + } + + private static string CombinePathSegments(string routePrefix, string odataPath) + { + return string.IsNullOrEmpty(routePrefix) + ? odataPath + : (string.IsNullOrEmpty(odataPath) ? routePrefix : routePrefix + '/' + odataPath); + } + + private static string UriEncode(string str) + { + string escape = Uri.EscapeUriString(str); + escape = escape.Replace("#", _escapedHashMark); + escape = escape.Replace("?", _escapedQuestionMark); + return escape; + } + } +} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs new file mode 100644 index 0000000..023c0a3 --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Web.Http.Routing; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData; + +namespace Microsoft.OData.Service.ApiAsAService +{ + public class DynamicRouteConstraint : ODataPathRouteConstraint + { + // "%2F" + private static readonly string escapedSlash = Uri.HexEscape('/'); + internal const string DataSourceNameProperty = "DynamicService_DataSourceName"; + + public DynamicRouteConstraint(string routeName) + : base(routeName) + { + } + + public override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (routeDirection == HttpRouteDirection.UriResolution) + { + if (values.TryGetValue(ODataRouteConstants.ODataPath, out object oDataPathValue)) + { + string oDataPathString = oDataPathValue as string; + ODataPath path; + + try + { + IServiceProvider requestContainer = request.CreateRequestContainer(this.RouteName); + //IHttpRequestMessageProvider httpRequestMessageProvider = requestContainer.GetRequiredService(); + //httpRequestMessageProvider.Request = request; + + string[] segments = oDataPathString.Split('/'); + string dataSource = segments[0]; + request.Properties[DataSourceNameProperty] = dataSource; + + // Service root is the current RequestUri, less the query string and the ODataPath (always the + // last portion of the absolute path). ODL expects an escaped service root and other service + // root calculations are calculated using AbsoluteUri (also escaped). But routing exclusively + // uses unescaped strings, determined using + // address.GetComponents(UriComponents.Path, UriFormat.Unescaped) + // + // For example if the AbsoluteUri is + // , the + // oDataPathString will contain "FunctionCall(p0='Chinese西雅图Chars')". + // + // Due to this decoding and the possibility of unecessarily-escaped characters, there's no + // reliable way to determine the original string from which oDataPathString was derived. + // Therefore a straightforward string comparison won't always work. See RemoveODataPath() for + // details of chosen approach. + string requestLeftPart = request.RequestUri.GetLeftPart(UriPartial.Path); + string serviceRoot = requestLeftPart; + if (!string.IsNullOrEmpty(oDataPathString)) + { + serviceRoot = RemoveODataPath(serviceRoot, oDataPathString); + } + + // As mentioned above, we also need escaped ODataPath. + // The requestLeftPart and request.RequestUri.Query are both escaped. + // The ODataPath for service documents is empty. + string oDataPathAndQuery = requestLeftPart.Substring(serviceRoot.Length); + if (!string.IsNullOrEmpty(request.RequestUri.Query)) + { + // Ensure path handler receives the query string as well as the path. + oDataPathAndQuery += request.RequestUri.Query; + } + + // Leave an escaped '/' out of the service route because DefaultODataPathHandler will add a + // literal '/' to the end of this string if not already present. That would double the slash + // in response links and potentially lead to later 404s. + if (serviceRoot.EndsWith(escapedSlash, StringComparison.OrdinalIgnoreCase)) + { + serviceRoot = serviceRoot.Substring(0, serviceRoot.Length - 3); + } + + IODataPathHandler pathHandler = requestContainer.GetRequiredService(); + oDataPathAndQuery = oDataPathAndQuery.Substring(oDataPathAndQuery.IndexOf('/') + 1); + path = pathHandler.Parse(serviceRoot, oDataPathAndQuery, requestContainer); + } + catch (ODataException) + { + path = null; + } + + if (path != null) + { + // Set all the properties we need for routing, querying, formatting + request.ODataProperties().Path = path; + request.ODataProperties().RouteName = this.RouteName; + + if (!values.ContainsKey(ODataRouteConstants.Controller)) + { + // Select controller name using the routing conventions + string controllerName = this.SelectControllerName(path, request); + if (controllerName != null) + { + values[ODataRouteConstants.Controller] = controllerName; + } + } + + return true; + } + } + + // The request doesn't match this route so dipose the request container. + request.DeleteRequestContainer(true); + return false; + } + else + { + // This constraint only applies to URI resolution + return true; + } + } + + // Find the substring of the given URI string before the given ODataPath. Tests rely on the following: + // 1. ODataPath comes at the end of the processed Path + // 2. Virtual path root, if any, comes at the beginning of the Path and a '/' separates it from the rest + // 3. OData prefix, if any, comes between the virtual path root and the ODataPath and '/' characters separate + // it from the rest + // 4. Even in the case of Unicode character corrections, the only differences between the escaped Path and the + // unescaped string used for routing are %-escape sequences which may be present in the Path + // + // Therefore, look for the '/' character at which to lop off the ODataPath. Can't just unescape the given + // uriString because subsequent comparisons would only help to check wehther a match is _possible_, not where + // to do the lopping. + private static string RemoveODataPath(string uriString, string oDataPathString) + { + // Potential index of oDataPathString within uriString. + int endIndex = uriString.Length - oDataPathString.Length - 1; + if (endIndex <= 0) + { + // Bizarre: oDataPathString is longer than uriString. Likely the values collection passed to Match() + // is corrupt. + throw new InvalidOperationException($"Request Uri Is Too Short For ODataPath. the Uri is {uriString}, and the OData path is {oDataPathString}."); + } + + string startString = uriString.Substring(0, endIndex + 1); // Potential return value. + string endString = uriString.Substring(endIndex + 1); // Potential oDataPathString match. + if (string.Equals(endString, oDataPathString, StringComparison.Ordinal)) + { + // Simple case, no escaping in the ODataPathString portion of the Path. In this case, don't do extra + // work to look for trailing '/' in startString. + return startString; + } + + while (true) + { + // Escaped '/' is a derivative case but certainly possible. + int slashIndex = startString.LastIndexOf('/', endIndex - 1); + int escapedSlashIndex = + startString.LastIndexOf(escapedSlash, endIndex - 1, StringComparison.OrdinalIgnoreCase); + if (slashIndex > escapedSlashIndex) + { + endIndex = slashIndex; + } + else if (escapedSlashIndex >= 0) + { + // Include the escaped '/' (three characters) in the potential return value. + endIndex = escapedSlashIndex + 2; + } + else + { + // Failure, unable to find the expected '/' or escaped '/' separator. + throw new InvalidOperationException($"The OData path is not found. The Uri is {uriString}, and the OData path is {oDataPathString}."); + } + + startString = uriString.Substring(0, endIndex + 1); + endString = uriString.Substring(endIndex + 1); + + // Compare unescaped strings to avoid both arbitrary escaping and use of lowercase 'a' through 'f' in + // %-escape sequences. + endString = Uri.UnescapeDataString(endString); + if (string.Equals(endString, oDataPathString, StringComparison.Ordinal)) + { + return startString; + } + + if (endIndex == 0) + { + // Failure, could not match oDataPathString after an initial '/' or escaped '/'. + throw new InvalidOperationException($"The OData path is not found. The Uri is {uriString}, and the OData path is {oDataPathString}."); + } + } + } + } +} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj index f6ebc3a..1714967 100644 --- a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj +++ b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj @@ -53,8 +53,11 @@ - + + + + Global.asax From 4ba80465166e1ef907082d9ee6d57cf7db9ab4c0 Mon Sep 17 00:00:00 2001 From: riach Date: Wed, 24 Jul 2019 10:51:56 -0700 Subject: [PATCH 12/21] Edm generator tool --- ApiAsAService/ApiAsAService.sln | 16 +--- ApiAsAService/EdmLoadTester/App.config | 17 ---- .../EdmLoadTester/EdmLoadTester.csproj | 63 --------------- ApiAsAService/EdmLoadTester/Program.cs | 43 ---------- .../EdmLoadTester/Properties/AssemblyInfo.cs | 36 --------- ApiAsAService/EdmLoadTester/packages.config | 4 - .../App.config | 0 .../EdmObjectsGenerator.csproj} | 4 +- .../Program.cs | 14 ++-- .../Properties/AssemblyInfo.cs | 0 .../PropertyBuilderHelper.cs | 13 +-- .../EdmObjectsGenerator/TypeBuilderHelpers.cs | 79 +++++++++++++++++++ .../packages.config | 0 .../ReflectionTester/TypeBuilderHelpers.cs | 79 ------------------- 14 files changed, 92 insertions(+), 276 deletions(-) delete mode 100644 ApiAsAService/EdmLoadTester/App.config delete mode 100644 ApiAsAService/EdmLoadTester/EdmLoadTester.csproj delete mode 100644 ApiAsAService/EdmLoadTester/Program.cs delete mode 100644 ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs delete mode 100644 ApiAsAService/EdmLoadTester/packages.config rename ApiAsAService/{ReflectionTester => EdmObjectsGenerator}/App.config (100%) rename ApiAsAService/{ReflectionTester/ReflectionTester.csproj => EdmObjectsGenerator/EdmObjectsGenerator.csproj} (96%) rename ApiAsAService/{ReflectionTester => EdmObjectsGenerator}/Program.cs (98%) rename ApiAsAService/{ReflectionTester => EdmObjectsGenerator}/Properties/AssemblyInfo.cs (100%) rename ApiAsAService/{ReflectionTester => EdmObjectsGenerator}/PropertyBuilderHelper.cs (79%) create mode 100644 ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs rename ApiAsAService/{ReflectionTester => EdmObjectsGenerator}/packages.config (100%) delete mode 100644 ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs diff --git a/ApiAsAService/ApiAsAService.sln b/ApiAsAService/ApiAsAService.sln index 0a646a4..8e5105e 100644 --- a/ApiAsAService/ApiAsAService.sln +++ b/ApiAsAService/ApiAsAService.sln @@ -5,9 +5,7 @@ VisualStudioVersion = 15.0.27703.2026 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAService\ApiAsAService.csproj", "{A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLoadTester", "EdmLoadTester\EdmLoadTester.csproj", "{3CF839FF-83B2-4162-B860-68F57B7B0190}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReflectionTester", "ReflectionTester\ReflectionTester.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmObjectsGenerator", "EdmObjectsGenerator\EdmObjectsGenerator.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -31,18 +29,6 @@ Global {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.Build.0 = Release|Any CPU {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.ActiveCfg = Release|x64 {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.Build.0 = Release|x64 - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.Build.0 = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.ActiveCfg = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.Build.0 = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.Build.0 = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.Build.0 = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU diff --git a/ApiAsAService/EdmLoadTester/App.config b/ApiAsAService/EdmLoadTester/App.config deleted file mode 100644 index 7e1d79c..0000000 --- a/ApiAsAService/EdmLoadTester/App.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - -
    - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj b/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj deleted file mode 100644 index 6131123..0000000 --- a/ApiAsAService/EdmLoadTester/EdmLoadTester.csproj +++ /dev/null @@ -1,63 +0,0 @@ - - - - - Debug - AnyCPU - {3CF839FF-83B2-4162-B860-68F57B7B0190} - Exe - EdmLoadTester - EdmLoadTester - v4.6.1 - 512 - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - - - ..\ReflectionTester\bin\Debug\NW_Simple.dll - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmLoadTester/Program.cs b/ApiAsAService/EdmLoadTester/Program.cs deleted file mode 100644 index 3ff12a6..0000000 --- a/ApiAsAService/EdmLoadTester/Program.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdmLoadTester -{ - using ODataDemo; - - class Program - { - static void Main(string[] args) - { - try - { - using (var context = new Entities()) - { - var address = context.Supplier.Create(); - address.Concurrency = 2; - } - - using (var context = new Entities()) - { - var address = context.Supplier.ToList(); - if (address.Count != 1) - { - throw new Exception("Error"); - } - - var isTrue = address.FirstOrDefault().Concurrency.Equals(2); - } - - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - - } - } -} diff --git a/ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs b/ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs deleted file mode 100644 index a567485..0000000 --- a/ApiAsAService/EdmLoadTester/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("EdmLoadTester")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("EdmLoadTester")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("3cf839ff-83b2-4162-b860-68f57b7b0190")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/EdmLoadTester/packages.config b/ApiAsAService/EdmLoadTester/packages.config deleted file mode 100644 index 373cff5..0000000 --- a/ApiAsAService/EdmLoadTester/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/ApiAsAService/ReflectionTester/App.config b/ApiAsAService/EdmObjectsGenerator/App.config similarity index 100% rename from ApiAsAService/ReflectionTester/App.config rename to ApiAsAService/EdmObjectsGenerator/App.config diff --git a/ApiAsAService/ReflectionTester/ReflectionTester.csproj b/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj similarity index 96% rename from ApiAsAService/ReflectionTester/ReflectionTester.csproj rename to ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj index e8ba828..7fb79ba 100644 --- a/ApiAsAService/ReflectionTester/ReflectionTester.csproj +++ b/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj @@ -6,8 +6,8 @@ AnyCPU {3678C588-F1DD-4EF6-879A-3D1D8114A880} Exe - ReflectionTester - ReflectionTester + EdmObjectsGenerator + EdmObjectsGenerator v4.7 512 true diff --git a/ApiAsAService/ReflectionTester/Program.cs b/ApiAsAService/EdmObjectsGenerator/Program.cs similarity index 98% rename from ApiAsAService/ReflectionTester/Program.cs rename to ApiAsAService/EdmObjectsGenerator/Program.cs index 44b154b..36e36f1 100644 --- a/ApiAsAService/ReflectionTester/Program.cs +++ b/ApiAsAService/EdmObjectsGenerator/Program.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ReflectionTester +namespace EdmObjectsGenerator { + using System; + using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.IO; + using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text.RegularExpressions; @@ -287,7 +284,8 @@ private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNu static void Main(string[] args) { - var model = ReadModel(@"C:\repos\fun\lab\ApiAsAService\EdmHelperTest\NW_Simple.xml"); + //TODO: grab edm path and assembly name from cmdline args + var model = ReadModel(@"<>"); var name = "NW_Simple"; // create a dynamic assembly and module diff --git a/ApiAsAService/ReflectionTester/Properties/AssemblyInfo.cs b/ApiAsAService/EdmObjectsGenerator/Properties/AssemblyInfo.cs similarity index 100% rename from ApiAsAService/ReflectionTester/Properties/AssemblyInfo.cs rename to ApiAsAService/EdmObjectsGenerator/Properties/AssemblyInfo.cs diff --git a/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs b/ApiAsAService/EdmObjectsGenerator/PropertyBuilderHelper.cs similarity index 79% rename from ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs rename to ApiAsAService/EdmObjectsGenerator/PropertyBuilderHelper.cs index dd5c03d..64542ff 100644 --- a/ApiAsAService/ReflectionTester/PropertyBuilderHelper.cs +++ b/ApiAsAService/EdmObjectsGenerator/PropertyBuilderHelper.cs @@ -1,4 +1,4 @@ -namespace ReflectionTester +namespace EdmObjectsGenerator { using System; using System.Reflection; @@ -12,17 +12,13 @@ public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Typ fieldType, FieldAttributes.Private); - // The last argument of DefineProperty is null, because the - // property has no parameters. (If you don't specify null, you must - // specify an array of Type objects. For a parameterless property, - // use an array with no elements: new Type[] {}) + PropertyBuilder propBuilder = typeBuilder.DefineProperty(fieldName, PropertyAttributes.HasDefault, fieldType, null); - // The property set and property get methods require a special - // set of attributes. + MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; @@ -54,8 +50,7 @@ public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Typ custNameSetIL.Emit(OpCodes.Stfld, fieldBldr); custNameSetIL.Emit(OpCodes.Ret); - // Last, we must map the two methods created above to our PropertyBuilder to - // their corresponding behaviors, "get" and "set" respectively. + propBuilder.SetGetMethod(propMethodBldr); propBuilder.SetSetMethod(propSetMethodBldr); diff --git a/ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs b/ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs new file mode 100644 index 0000000..00cc360 --- /dev/null +++ b/ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs @@ -0,0 +1,79 @@ +namespace EdmObjectsGenerator +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Reflection.Emit; + + public static class TypeBuilderHelper + { + /// Creates one constructor for each public constructor in the base class. Each constructor simply + /// forwards its arguments to the base constructor, and matches the base constructor's signature. + /// Supports optional values, and custom attributes on constructors and parameters. + /// Does not support n-ary (variadic) constructors + public static void CreatePassThroughConstructors(this TypeBuilder builder, Type baseType) + { + foreach (var constructor in baseType.GetConstructors()) + { + var parameters = constructor.GetParameters(); + if (parameters.Length > 0 && parameters.Last().IsDefined(typeof(ParamArrayAttribute), false)) + { + //throw new InvalidOperationException("Variadic constructors are not supported"); + continue; + } + + var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); + var requiredCustomModifiers = parameters.Select(p => p.GetRequiredCustomModifiers()).ToArray(); + var optionalCustomModifiers = parameters.Select(p => p.GetOptionalCustomModifiers()).ToArray(); + + var ctor = builder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers); + for (var i = 0; i < parameters.Length; ++i) + { + var parameter = parameters[i]; + var parameterBuilder = ctor.DefineParameter(i + 1, parameter.Attributes, parameter.Name); + if (((int)parameter.Attributes & (int)ParameterAttributes.HasDefault) != 0) + { + parameterBuilder.SetConstant(parameter.RawDefaultValue); + } + + foreach (var attribute in BuildCustomAttributes(parameter.GetCustomAttributesData())) + { + parameterBuilder.SetCustomAttribute(attribute); + } + } + + foreach (var attribute in BuildCustomAttributes(constructor.GetCustomAttributesData())) + { + ctor.SetCustomAttribute(attribute); + } + + var emitter = ctor.GetILGenerator(); + emitter.Emit(OpCodes.Nop); + + // Load `this` and call base constructor with arguments + emitter.Emit(OpCodes.Ldarg_0); + for (var i = 1; i <= parameters.Length; ++i) + { + emitter.Emit(OpCodes.Ldarg, i); + } + emitter.Emit(OpCodes.Call, constructor); + + emitter.Emit(OpCodes.Ret); + } + } + + + private static CustomAttributeBuilder[] BuildCustomAttributes(IEnumerable customAttributes) + { + return customAttributes.Select(attribute => { + var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray(); + var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); + var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray(); + var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); + var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray(); + return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues); + }).ToArray(); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/ReflectionTester/packages.config b/ApiAsAService/EdmObjectsGenerator/packages.config similarity index 100% rename from ApiAsAService/ReflectionTester/packages.config rename to ApiAsAService/EdmObjectsGenerator/packages.config diff --git a/ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs b/ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs deleted file mode 100644 index 6d55105..0000000 --- a/ApiAsAService/ReflectionTester/TypeBuilderHelpers.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; - -namespace ReflectionTester -{ - public static class TypeBuilderHelper - { - /// Creates one constructor for each public constructor in the base class. Each constructor simply - /// forwards its arguments to the base constructor, and matches the base constructor's signature. - /// Supports optional values, and custom attributes on constructors and parameters. - /// Does not support n-ary (variadic) constructors - public static void CreatePassThroughConstructors(this TypeBuilder builder, Type baseType) - { - //foreach (var constructor in baseType.GetConstructors()) - //{ - // var parameters = constructor.GetParameters(); - // if (parameters.Length > 0 && parameters.Last().IsDefined(typeof(ParamArrayAttribute), false)) - // { - // //throw new InvalidOperationException("Variadic constructors are not supported"); - // continue; - // } - - // var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); - // var requiredCustomModifiers = parameters.Select(p => p.GetRequiredCustomModifiers()).ToArray(); - // var optionalCustomModifiers = parameters.Select(p => p.GetOptionalCustomModifiers()).ToArray(); - - // var ctor = builder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, parameterTypes, requiredCustomModifiers, optionalCustomModifiers); - // for (var i = 0; i < parameters.Length; ++i) - // { - // var parameter = parameters[i]; - // var parameterBuilder = ctor.DefineParameter(i + 1, parameter.Attributes, parameter.Name); - // if (((int)parameter.Attributes & (int)ParameterAttributes.HasDefault) != 0) - // { - // parameterBuilder.SetConstant(parameter.RawDefaultValue); - // } - - // foreach (var attribute in BuildCustomAttributes(parameter.GetCustomAttributesData())) - // { - // parameterBuilder.SetCustomAttribute(attribute); - // } - // } - - // foreach (var attribute in BuildCustomAttributes(constructor.GetCustomAttributesData())) - // { - // ctor.SetCustomAttribute(attribute); - // } - - // var emitter = ctor.GetILGenerator(); - // emitter.Emit(OpCodes.Nop); - - // // Load `this` and call base constructor with arguments - // emitter.Emit(OpCodes.Ldarg_0); - // for (var i = 1; i <= parameters.Length; ++i) - // { - // emitter.Emit(OpCodes.Ldarg, i); - // } - // emitter.Emit(OpCodes.Call, constructor); - - // emitter.Emit(OpCodes.Ret); - //} - } - - - private static CustomAttributeBuilder[] BuildCustomAttributes(IEnumerable customAttributes) - { - return customAttributes.Select(attribute => { - var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray(); - var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); - var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray(); - var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType().ToArray(); - var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray(); - return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues); - }).ToArray(); - } - } -} \ No newline at end of file From 49cf29ef2a61b61bef4bc0819c8c353d6304818e Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Wed, 24 Jul 2019 11:21:31 -0700 Subject: [PATCH 13/21] Dynamic Routing Works --- .../Model/ModelMapper.cs | 2 +- .../Model/RestierModelExtender.cs | 15 +- .../Routing/RestierRoutingConvention.cs | 16 ++ .../Extensions/ApiBaseExtensions.cs | 1 + .../RestierContainerBuilder.cs | 4 +- .../Model/ModelMapper.cs | 2 +- .../Model/ModelProducer.cs | 7 +- .../Query/QueryExecutor.cs | 7 +- .../Query/QueryExpressionProcessor.cs | 8 +- .../Query/QueryExpressionSourcer.cs | 4 +- .../Submit/SubmitExecutor.cs | 4 +- .../Trippin/Trippin/Api/DynamicApi.cs | 55 ++-- .../Trippin/Trippin/App_Start/WebApiConfig.cs | 28 +- .../Controllers/DynamicApiController.cs | 29 +- .../Trippin/Trippin/DynamicHelper.cs | 250 ++++++++++++++++-- .../Trippin/Trippin/DynamicRouteConstraint.cs | 47 +++- ...crosoft.OData.Service.ApiAsAService.csproj | 6 +- .../Extensions/HttpConfigurationExtensions.cs | 9 +- 18 files changed, 385 insertions(+), 109 deletions(-) diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs index 8753de9..e5d8c65 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/ModelMapper.cs @@ -47,7 +47,7 @@ public bool TryGetRelevantType( if (element != null) { IEdmType entityType = null; - var entitySet = element as EdmEntitySet; + var entitySet = element as IEdmEntitySet; if (entitySet != null) { var entitySetType = entitySet.Type as EdmCollectionType; diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs index 34e2911..d8a0dbf 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Model/RestierModelExtender.cs @@ -45,12 +45,15 @@ public static void ApplyTo( // The model builder must maintain a singleton life time, for holding states and being injected into // some other services. - services.AddSingleton(new RestierModelExtender(targetType)); - - services.AddService(); - services.AddService(); - services.AddService(); - services.AddService(); + services.AddScoped(sp=>new RestierModelExtender(sp.GetRequiredService().GetApiBase().GetType())); + + services.AddScoped(); +// // services.AddScoped(); + // services.AddScoped(); +// services.AddScoped(); + //services.AddScoped(sp => new ModelMapper(new RestierModelExtender(sp.GetRequiredService().GetApiBase().GetType()))); + //services.AddScoped(sp => new QueryExpressionExpander(new RestierModelExtender(sp.GetRequiredService().GetApiBase().GetType()))); + //services.AddScoped(sp => new QueryExpressionSourcer(new RestierModelExtender(sp.GetRequiredService().GetApiBase().GetType())); } private static bool IsEntitySetProperty(PropertyInfo property) diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs index 05c892e..f2c3187 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Routing/RestierRoutingConvention.cs @@ -208,4 +208,20 @@ private static bool IsAction(ODataPathSegment lastSegment) return false; } } + + /// + /// Class to create an instance of a RestierRoutingConvention + /// + public static class RestierRoutingConventionFactory + { + /// + /// Method to create an instance of a RestierRoutingConvention + /// + /// + public static IODataRoutingConvention Create() + { + return new RestierRoutingConvention(); + } + + } } diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs index a783be1..c88fb88 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs @@ -467,6 +467,7 @@ private static Type EnsureElementType(this ApiBase api, string namespaceName, st Type elementType = null; var mapper = api.GetApiService(); + if (mapper != null) { var modelContext = new ModelContext(api.ServiceProvider); diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs index f9faf7c..5cbb419 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/RestierContainerBuilder.cs @@ -18,7 +18,7 @@ namespace Microsoft.Restier.Core public class RestierContainerBuilder : IContainerBuilder { private readonly IServiceCollection services = new ServiceCollection(); - private readonly Type apiType; + private Type apiType; /// /// Initializes a new instance of the class. @@ -110,7 +110,7 @@ IEdmModel modelFactory(IServiceProvider sp) apiType, services }); - services.AddSingleton(modelFactory); + services.AddScoped(modelFactory); return this; } diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs index f67b863..f9d7bb4 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelMapper.cs @@ -14,7 +14,7 @@ namespace Microsoft.Restier.EntityFramework /// /// Represents a model mapper based on a DbContext. /// - internal class ModelMapper : IModelMapper + public class ModelMapper : IModelMapper { private readonly Type dbContextType; diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs index ea0664f..cbfdbea 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Model/ModelProducer.cs @@ -26,8 +26,11 @@ namespace Microsoft.Restier.EntityFramework /// Represents a model producer that uses the /// metadata workspace accessible from a DbContext. /// - internal class ModelProducer : IModelBuilder + public class ModelProducer : IModelBuilder { + /// + /// InnerModelBuilder + /// public IModelBuilder InnerModelBuilder { get; set; } /// @@ -106,7 +109,9 @@ public Task GetModelAsync(ModelContext context, CancellationToken can var clrType = itemCollection.GetClrType(objectSpaceType); // As entity set name and type map +#pragma warning disable CA1062 // Validate arguments of public methods context.ResourceSetTypeMap.Add(efEntitySet.Name, clrType); +#pragma warning restore CA1062 // Validate arguments of public methods ICollection keyProperties = new List(); foreach (var property in efEntityType.KeyProperties) diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs index b66bb2b..7037cdf 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExecutor.cs @@ -25,8 +25,11 @@ namespace Microsoft.Restier.EntityFramework /// This class only executes queries against EF provider, it'll /// delegate other queries to inner IQueryExecutor. /// - internal class QueryExecutor : IQueryExecutor + public class QueryExecutor : IQueryExecutor { + /// + /// Inner Executor + /// public IQueryExecutor Inner { get; set; } /// @@ -56,7 +59,9 @@ public async Task ExecuteQueryAsync( #if EF7 if (query.Provider is IAsyncQueryProvider) #else +#pragma warning disable CA1062 // Validate arguments of public methods if (query.Provider is IDbAsyncQueryProvider) +#pragma warning restore CA1062 // Validate arguments of public methods #endif { return new QueryResult(await query.ToArrayAsync(cancellationToken).ConfigureAwait(false)); diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs index 6c4fff2..f358037 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionProcessor.cs @@ -12,9 +12,11 @@ namespace Microsoft.Restier.EntityFramework /// /// A query expression filter to handle EF related logic. /// - internal class QueryExpressionProcessor : IQueryExpressionProcessor + public class QueryExpressionProcessor : IQueryExpressionProcessor { - // It will be ConventionBasedEntitySetProcessor + /// + /// Inner QueryExpressionProcessor It will be ConventionBasedEntitySetProcessor + /// public IQueryExpressionProcessor Inner { get; set; } /// @@ -25,7 +27,9 @@ public Expression Process(QueryExpressionContext context) if (Inner != null) { var innerFilteredExpression = Inner.Process(context); +#pragma warning disable CA1062 // Validate arguments of public methods if (innerFilteredExpression != null && innerFilteredExpression != context.VisitedNode) +#pragma warning restore CA1062 // Validate arguments of public methods { return innerFilteredExpression; } diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs index 31b9f0a..1a07ddd 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Query/QueryExpressionSourcer.cs @@ -18,7 +18,7 @@ namespace Microsoft.Restier.EntityFramework /// /// Represents a query expression sourcer that uses a DbContext. /// - internal class QueryExpressionSourcer : IQueryExpressionSourcer + public class QueryExpressionSourcer : IQueryExpressionSourcer { /// /// Sources an expression. @@ -36,7 +36,9 @@ public Expression ReplaceQueryableSource(QueryExpressionContext context, bool em { Ensure.NotNull(context, nameof(context)); +#pragma warning disable CA1062 // Validate arguments of public methods if (context.ModelReference.EntitySet == null) +#pragma warning restore CA1062 // Validate arguments of public methods { // EF provider can only source *ResourceSet*. return null; diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs index b7f13e0..d2bce88 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Submit/SubmitExecutor.cs @@ -17,7 +17,7 @@ namespace Microsoft.Restier.EntityFramework /// /// To execute submission of changes to database. /// - internal class SubmitExecutor : ISubmitExecutor + public class SubmitExecutor : ISubmitExecutor { /// /// Asynchronously executes the submission. @@ -29,7 +29,9 @@ public async Task ExecuteSubmitAsync(SubmitContext context, Cancel { var dbContext = context.GetApiService(); await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); +#pragma warning disable CA1062 // Validate arguments of public methods return new SubmitResult(context.ChangeSet); +#pragma warning restore CA1062 // Validate arguments of public methods } } } diff --git a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs index 343db32..e5624bd 100644 --- a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs +++ b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs @@ -16,25 +16,16 @@ using Microsoft.Restier.Core.Model; using Microsoft.Restier.Core.Submit; using Microsoft.Restier.EntityFramework; -using Microsoft.Restier.AspNet.Model; using Microsoft.OData.Edm.Csdl; using System.Xml; using Microsoft.OData.Edm.Validation; -using Microsoft.AspNet.OData.Routing; -using System.Web.Http; -using Microsoft.Restier.AspNet.Batch; -using Microsoft.Restier.AspNet; -using Microsoft.AspNet.OData.Extensions; -using Microsoft.AspNet.OData.Batch; -using Microsoft.AspNet.OData.Routing.Conventions; + namespace Microsoft.OData.Service.ApiAsAService.Api { - public class DynamicApi : EntityFrameworkApi where T : System.Data.Entity.DbContext + public class DynamicApi : ApiBase { - public T ModelContext { get { return DbContext; } } - - public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) + public static new IServiceCollection ConfigureApi(Type apiType, IServiceCollection services) { // Add customized OData validation settings Func validationSettingFactory = (sp) => new ODataValidationSettings @@ -43,39 +34,43 @@ public class DynamicApi : EntityFrameworkApi where T : System.Data.Entity. MaxExpansionDepth = 3 }; - IServiceCollection serviceCollection = EntityFrameworkApi.ConfigureApi(apiType, services) + IServiceCollection serviceCollection = ApiBase.ConfigureApi(apiType, services) .AddSingleton() .AddSingleton(validationSettingFactory) .AddService() - .AddService(); + .AddSingleton() + .AddScoped(sp => ((DynamicModelBuilder)sp.GetRequiredService()).GetModel()); return serviceCollection; } + public DynamicApi(IServiceProvider serviceProvider) : base(serviceProvider) { } + } + + public class DynamicModelBuilder : IModelBuilder + { + public IModelBuilder InnerHandler { get; set; } - private class DynamicModelBuilder : IModelBuilder + public string DataSourceName { get; set; } + public IEdmModel GetModel() { - public IModelBuilder InnerHandler { get; set; } + IEdmModel model; + IEnumerable errors; + var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); + var file = System.IO.Path.Combine(appData, DataSourceName + ".xml"); - public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) + XmlReader xmlReader = XmlReader.Create(file); + if (CsdlReader.TryParse(xmlReader, out model, out errors)) { - IEdmModel model; - IEnumerable errors; - var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); - var file = System.IO.Path.Combine(appData, "Trippin.xml"); - - XmlReader xmlReader = XmlReader.Create(file); - if (CsdlReader.TryParse(xmlReader, out model, out errors)) - { - return model; - } - - throw new Exception("Couldn't parse xml"); + return model; } + + throw new Exception("Couldn't parse xml"); } - public DynamicApi(IServiceProvider serviceProvider) : base(serviceProvider) + public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) { + return GetModel(); } } } \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs index 32a5581..4b82f04 100644 --- a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs +++ b/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs @@ -16,6 +16,7 @@ public static void Register(HttpConfiguration config) RegisterService(config, GlobalConfiguration.DefaultServer); } + private const string RouteName = "DynamicRoute"; public static async void RegisterService( HttpConfiguration config, HttpServer server) { @@ -25,24 +26,17 @@ public static async void RegisterService( // "ApiAsAService", "", // new RestierBatchHandler(server)); - HttpConfiguration dummyConfig = new HttpConfiguration(); - var services = dummyConfig.Services; + //ODataRoute route = await DynamicHelper.MapDynamicRoute( + // typeof(Models.TrippinModel), + // config, + // RouteName, + // "", + // null); - ODataRoute route = await DynamicHelper.MapDynamicRoute( - typeof(Models.TrippinModel), - dummyConfig, - "RestierRoute", - "", - null); - - foreach(var service in dummyConfig.Services.GetServices(typeof(object))) - { - config.Services.Add(service.GetType(), service); - } - - DynamicODataRoute odataRoute = new DynamicODataRoute(route.RoutePrefix, new DynamicRouteConstraint("RestierRoute")); - // config.Routes.Remove("RestierRoute"); - config.Routes.Add("DynamicRoute", odataRoute); + //config.Routes.Add(RouteName, route); + ODataRoute route = await DynamicHelper.MapRestierRoute(config, RouteName, null); + DynamicODataRoute odataRoute = new DynamicODataRoute(route.RoutePrefix, new DynamicRouteConstraint(RouteName)); + config.Routes.Add(RouteName, odataRoute); } } } diff --git a/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs b/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs index fba967e..b934829 100644 --- a/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs +++ b/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs @@ -18,6 +18,7 @@ using Microsoft.OData.Service.ApiAsAService.Models; using Microsoft.Restier.AspNet; using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; namespace Microsoft.OData.Service.ApiAsAService.Controllers { @@ -25,15 +26,27 @@ public class DynamicApiController : RestierController { public async Task Get(CancellationToken cancellationToken) { - // Get the data source from the request - ODataPath path = Request.ODataProperties().Path; - string dataSource = path.Segments[0].Identifier; - - //IEdmCollectionType collectionType = (IEdmCollectionType)path.EdmType; - //IEdmEntityTypeReference entityType = collectionType.ElementType.AsEntity(); - Type dynamicType = dataSource=="Trippin" ? typeof(Models.TrippinModel) : typeof(Models.TrippinModel); - base.SetApi(DynamicHelper.CreateDynamicApi(dynamicType, Request.GetRequestContainer())); + IServiceProvider serviceProvider = Request.GetRequestContainer(); + //DynamicModelBuilder modelBuilder = serviceProvider.GetRequiredService() as DynamicModelBuilder; + //string datasource = modelBuilder.DataSourceName; + + //Type dynamicType; + //switch (datasource) + //{ + // case "Trippin": + // dynamicType = typeof(Models.TrippinModel); + // break; + // case "NWind": + // dynamicType = typeof(ODataDemo.NWModel); + // break; + // default: + // return Request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", datasource)); + //} + + ApiFactory factory = Request.GetRequestContainer().GetRequiredService(); +// factory.ModelType = dynamicType; + base.SetApi(factory.GetApiBase()); return await base.GetResponse(cancellationToken); } diff --git a/ApiAsAService/Trippin/Trippin/DynamicHelper.cs b/ApiAsAService/Trippin/Trippin/DynamicHelper.cs index 950a08b..1d4601c 100644 --- a/ApiAsAService/Trippin/Trippin/DynamicHelper.cs +++ b/ApiAsAService/Trippin/Trippin/DynamicHelper.cs @@ -1,10 +1,21 @@ -using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Batch; +using Microsoft.AspNet.OData.Extensions; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNet.OData.Routing.Conventions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Service.ApiAsAService.Api; +using Microsoft.Restier.AspNet; using Microsoft.Restier.AspNet.Batch; using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; +using Microsoft.Restier.EntityFramework; using System; using System.Collections.Generic; +using System.Data.Entity; using System.Linq; +using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using System.Web.Http; @@ -14,39 +25,228 @@ namespace Microsoft.OData.Service.ApiAsAService public static class DynamicHelper { private const string MapRestierRouteMethod = "MapRestierRoute"; - private const string RestierAssembly = "Microsoft.Restier.AspNet"; +// private const string RestierAssembly = "Microsoft.Restier.AspNet"; private const string httpConfigurationExtensionsType = "System.Web.Http.HttpConfigurationExtensions"; - private const string ApiAsAServiceAssembly = "Microsoft.OData.Service.ApiAsAService"; +// private const string ApiAsAServiceAssembly = "Microsoft.OData.Service.ApiAsAService"; private const string DynamicApiType = "Microsoft.OData.Service.ApiAsAService.Api.DynamicApi`1"; + private const string DynamicHelperType = "Microsoft.OData.Service.ApiAsAService.DynamicHelper"; private static Type dynamicApiType = Assembly.GetExecutingAssembly().GetType(DynamicApiType); - - public static Task MapDynamicRoute(Type tApi, HttpConfiguration config, string routeName, - string routePrefix, RestierBatchHandler batchHandler) + private static Type dynamicHelperType = Assembly.GetExecutingAssembly().GetType(DynamicHelperType); + + //public static Task MapDynamicRoute(Type tApi, HttpConfiguration config, string routeName, + // string routePrefix, RestierBatchHandler batchHandler) + //{ + //Type restierType = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == RestierAssembly).GetType(httpConfigurationExtensionsType); + //Type dynamicType = dynamicApiType.MakeGenericType(tApi); + // MethodInfo method = restierType.GetMethod( + // MapRestierRouteMethod, + // BindingFlags.Static | BindingFlags.Public, + // null, + // new[] { typeof(HttpConfiguration), typeof(string), typeof(string), typeof(RestierBatchHandler) }, + // null); + // return (Task)method.MakeGenericMethod(dynamicType).Invoke(null, new object[] { config, routeName, routePrefix, batchHandler }); + //} + + //public static Task MapDynamicRoute(Type tApi, HttpConfiguration config, string routeName, + // string routePrefix, RestierBatchHandler batchHandler) + //{ + // Type dynamicType = dynamicApiType.MakeGenericType(tApi); + // MethodInfo method = dynamicHelperType.GetMethod( + // MapRestierRouteMethod, + // BindingFlags.Static | BindingFlags.Public, + // null, + // new[] { typeof(HttpConfiguration), typeof(string), typeof(string), typeof(RestierBatchHandler) }, + // null); + // return (Task)method.MakeGenericMethod(dynamicType).Invoke(null, new object[] { config, routeName, routePrefix, batchHandler }); + //} + + //public static ApiBase CreateDynamicApi(Type tApi, IServiceProvider serviceProvider) + //{ + // return (ApiBase)dynamicApiType.MakeGenericType(tApi).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); + //} + + public static ApiBase CreateEntifyFrameworkApi(Type tApi, IServiceProvider serviceProvider) { - Type restierType = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == RestierAssembly).GetType(httpConfigurationExtensionsType); - Type dynamicType = dynamicApiType.MakeGenericType(tApi); - MethodInfo method = restierType.GetMethod( - MapRestierRouteMethod, - BindingFlags.Static | BindingFlags.Public, - null, - new[] { typeof(HttpConfiguration), typeof(string), typeof(string), typeof(RestierBatchHandler) }, - null); - return (Task)method.MakeGenericMethod(dynamicType).Invoke(null, new object[] { config, routeName, routePrefix, batchHandler }); + return (ApiBase)typeof(EntityFrameworkApi<>).MakeGenericType(tApi).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); } - public static ApiBase CreateDynamicApi(Type tApi, IServiceProvider serviceProvider) + /// + /// Maps the API routes to the RestierController. + /// + /// The user API. + /// The instance. + /// The name of the route. + /// The prefix of the route. + /// The handler for batch requests. + /// The task object containing the resulted instance. + public static Task MapRestierRoute( + HttpConfiguration config, + string routeName, + string routePrefix, + RestierBatchHandler batchHandler = null) + where TApi : ApiBase { - return (ApiBase)dynamicApiType.MakeGenericType(tApi).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); + // This will be added a service to callback stored in ApiConfiguration + // Callback is called by ApiBase.AddApiServices method to add real services. + ApiBase.AddPublisherServices(typeof(TApi), services => + { + services.AddScoped(sp=>new ApiFactory(sp)); + MapEfServices(services); + services.AddODataServices(); + }); + + IContainerBuilder func() => new RestierContainerBuilder(typeof(TApi)); + config.UseCustomContainerBuilder(func); + + var conventions = CreateRestierRoutingConventions(config, routeName); + if (batchHandler != null) + { + batchHandler.ODataRouteName = routeName; + } + + void configureAction(IContainerBuilder builder) => builder + .AddService>(ServiceLifetime.Singleton, sp => conventions) + .AddService(ServiceLifetime.Singleton, sp => batchHandler); + + var route = GenerateODataServiceRoute(config, routeName, routePrefix, configureAction); + return Task.FromResult(route); } - //private static Type GetDynamicApiType() - //{ - // if (dynamicApiType == null) - // { - // dynamicApiType = Assembly.GetExecutingAssembly().GetType(DynamicApiType); - // } + /// + /// Creates the default routing conventions. + /// + /// The instance. + /// The name of the route. + /// The routing conventions created. + internal static IList CreateRestierRoutingConventions( + HttpConfiguration config, string routeName) + { + var conventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(routeName, config); + var index = 0; + for (; index < conventions.Count; index++) + { + if (conventions[index] is AttributeRoutingConvention attributeRouting) + { + break; + } + } - // return dynamicApiType; - //} + conventions.Insert(index + 1, RestierRoutingConventionFactory.Create()); + return conventions; + } + + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The configuring action to add the services to the root container. + /// The added . + public static ODataRoute GenerateODataServiceRoute(HttpConfiguration configuration, string routeName, + string routePrefix, Action configureAction) + { + if (configuration == null) + { + throw new ArgumentNullException("configuration"); + } + + if (routeName == null) + { + throw new ArgumentNullException("routeName"); + } + + // 1) Build and configure the root container. + IServiceProvider rootContainer = configuration.CreateODataRootContainer(routeName, configureAction); + + // 2) Resolve the path handler and set URI resolver to it. + IODataPathHandler pathHandler = rootContainer.GetRequiredService(); + + // if settings is not on local, use the global configuration settings. + if (pathHandler != null && pathHandler.UrlKeyDelimiter == null) + { + ODataUrlKeyDelimiter urlKeyDelimiter = configuration.GetUrlKeyDelimiter(); + pathHandler.UrlKeyDelimiter = urlKeyDelimiter; + } + + // 3) Resolve some required services and create the route constraint. + ODataPathRouteConstraint routeConstraint = new ODataPathRouteConstraint(routeName); + + // Attribute routing must initialized before configuration.EnsureInitialized is called. + rootContainer.GetServices(); + + // 4) Resolve HTTP handler, create the OData route and register it. + ODataRoute route; + HttpRouteCollection routes = configuration.Routes; + routePrefix = RemoveTrailingSlash(routePrefix); + HttpMessageHandler messageHandler = rootContainer.GetService(); + if (messageHandler != null) + { + route = new ODataRoute( + routePrefix, + routeConstraint, + defaults: null, + constraints: null, + dataTokens: null, + handler: messageHandler); + } + else + { + ODataBatchHandler batchHandler = rootContainer.GetService(); + if (batchHandler != null) + { + batchHandler.ODataRouteName = routeName; + string batchTemplate = String.IsNullOrEmpty(routePrefix) ? ODataRouteConstants.Batch + : routePrefix + '/' + ODataRouteConstants.Batch; + routes.MapHttpBatchRoute(routeName + "Batch", batchTemplate, batchHandler); + } + + route = new ODataRoute(routePrefix, routeConstraint); + } + + return route; + } + + private static string RemoveTrailingSlash(string routePrefix) + { + if (!String.IsNullOrEmpty(routePrefix)) + { + int prefixLastIndex = routePrefix.Length - 1; + if (routePrefix[prefixLastIndex] == '/') + { + // Remove the last trailing slash if it has one. + routePrefix = routePrefix.Substring(0, routePrefix.Length - 1); + } + } + return routePrefix; + } + + private static IServiceCollection MapEfServices(IServiceCollection services) + { + services.AddScoped(sp => + { + var modelType = sp.GetRequiredService().ModelType; + DbContext dbContext = Activator.CreateInstance(modelType) as DbContext; +#if EF7 + dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; +#else + dbContext.Configuration.ProxyCreationEnabled = false; +#endif + return dbContext; + }); + + return services + .AddService() + .AddService((sp, next) => new ModelMapper(sp.GetRequiredService().ModelType)) + .MakeScoped() + .AddService() + .AddService() + .AddService() + .AddService() + .AddService() + ; + + } } + } \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs index 023c0a3..7d80044 100644 --- a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs +++ b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs @@ -3,12 +3,17 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Web.Http.Routing; using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNet.OData.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.OData; +using Microsoft.OData.Service.ApiAsAService.Api; +using Microsoft.Restier.AspNet; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; namespace Microsoft.OData.Service.ApiAsAService { @@ -37,21 +42,42 @@ public override bool Match(HttpRequestMessage request, IHttpRoute route, string if (routeDirection == HttpRouteDirection.UriResolution) { - if (values.TryGetValue(ODataRouteConstants.ODataPath, out object oDataPathValue)) + string oDataPathString; + if (values.TryGetValue(ODataRouteConstants.ODataPath, out object oDataPathValue) && !String.IsNullOrEmpty(oDataPathString = oDataPathValue as string)) { - string oDataPathString = oDataPathValue as string; ODataPath path; try { - IServiceProvider requestContainer = request.CreateRequestContainer(this.RouteName); + string[] segments = oDataPathString.Split('/'); + string dataSourceName = segments[0]; + + IServiceProvider serviceProvider = request.CreateRequestContainer(this.RouteName); + //IHttpRequestMessageProvider httpRequestMessageProvider = requestContainer.GetRequiredService(); //httpRequestMessageProvider.Request = request; - - string[] segments = oDataPathString.Split('/'); - string dataSource = segments[0]; - request.Properties[DataSourceNameProperty] = dataSource; + Type dynamicType; + switch (dataSourceName) + { + case "Trippin": + dynamicType = typeof(Models.TrippinModel); + break; + case "NWind": + dynamicType = typeof(ODataDemo.NWModel); + break; + default: + throw new Exception("Service not found"); //return request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", dataSourceName)); + } + + ApiFactory factory = serviceProvider.GetRequiredService(); + factory.ModelType = dynamicType; + DynamicModelBuilder modelBuilder = serviceProvider.GetRequiredService() as DynamicModelBuilder; + if (modelBuilder != null) + { + modelBuilder.DataSourceName = segments[0]; + } + // Service root is the current RequestUri, less the query string and the ODataPath (always the // last portion of the absolute path). ODL expects an escaped service root and other service // root calculations are calculated using AbsoluteUri (also escaped). But routing exclusively @@ -91,9 +117,10 @@ public override bool Match(HttpRequestMessage request, IHttpRoute route, string serviceRoot = serviceRoot.Substring(0, serviceRoot.Length - 3); } - IODataPathHandler pathHandler = requestContainer.GetRequiredService(); - oDataPathAndQuery = oDataPathAndQuery.Substring(oDataPathAndQuery.IndexOf('/') + 1); - path = pathHandler.Parse(serviceRoot, oDataPathAndQuery, requestContainer); + IODataPathHandler pathHandler = serviceProvider.GetRequiredService(); + int slashIndex = oDataPathAndQuery.IndexOf('/'); + oDataPathAndQuery = slashIndex < 0 ? "" : oDataPathAndQuery.Substring(slashIndex + 1); + path = pathHandler.Parse(serviceRoot, oDataPathAndQuery, serviceProvider); } catch (ODataException) { diff --git a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj index 1714967..6016cb0 100644 --- a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj +++ b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj @@ -43,6 +43,9 @@ 4 + + Always + Always @@ -75,6 +78,7 @@ + @@ -198,7 +202,7 @@ True 51480 / - http://localhost:18384/ + http://localhost:18384 False False diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs index c5eb03a..a7be001 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Extensions/HttpConfigurationExtensions.cs @@ -451,7 +451,12 @@ internal static bool HasEnabledNullDynamicProperty(this HttpConfiguration config return false; } - internal static ODataUrlKeyDelimiter GetUrlKeyDelimiter(this HttpConfiguration configuration) + /// + /// Gets the UrlKeyDelimiter for the specified configuration + /// + /// + /// + public static ODataUrlKeyDelimiter GetUrlKeyDelimiter(this HttpConfiguration configuration) { if (configuration == null) { @@ -748,7 +753,7 @@ private static string RemoveTrailingSlash(string routePrefix) /// The route name. /// The configuring action to add the services to the root container. /// The per-route container from the configuration - internal static IServiceProvider CreateODataRootContainer(this HttpConfiguration configuration, + public static IServiceProvider CreateODataRootContainer(this HttpConfiguration configuration, string routeName, Action configureAction) { IPerRouteContainer perRouteContainer = configuration.GetPerRouteContainer(); From 868e2a4cd240e57511ea297f577b1d9327933e0d Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Wed, 24 Jul 2019 12:05:55 -0700 Subject: [PATCH 14/21] Merging DbContext generation --- ApiAsAService/EdmObjectsGenerator/App.config | 12 +- .../EdmObjectsGenerator.csproj | 8 +- ApiAsAService/EdmObjectsGenerator/Program.cs | 23 ++- .../Microsoft.Restier.AspNet/ApiFactory.cs | 63 ++++++ ApiAsAService/RESTierAsAService.sln | 8 + .../Trippin/Trippin/Api/DynamicApi.cs | 1 + .../Trippin/Trippin/App_Data/NWind.xml | 129 +++++++++++++ .../Trippin/Trippin/DynamicRouteConstraint.cs | 28 +-- ...crosoft.OData.Service.ApiAsAService.csproj | 4 + .../Trippin/Trippin/Models/NWModel.cs | 180 ++++++++++++++++++ 10 files changed, 433 insertions(+), 23 deletions(-) create mode 100644 ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs create mode 100644 ApiAsAService/Trippin/Trippin/App_Data/NWind.xml create mode 100644 ApiAsAService/Trippin/Trippin/Models/NWModel.cs diff --git a/ApiAsAService/EdmObjectsGenerator/App.config b/ApiAsAService/EdmObjectsGenerator/App.config index 12cde46..eb90e3f 100644 --- a/ApiAsAService/EdmObjectsGenerator/App.config +++ b/ApiAsAService/EdmObjectsGenerator/App.config @@ -1,20 +1,20 @@ - + -
    +
    - + - + - + - \ No newline at end of file + diff --git a/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj b/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj index 7fb79ba..ff3a4e2 100644 --- a/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj +++ b/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj @@ -5,13 +5,14 @@ Debug AnyCPU {3678C588-F1DD-4EF6-879A-3D1D8114A880} - Exe + Library EdmObjectsGenerator EdmObjectsGenerator - v4.7 + v4.6.1 512 true true + AnyCPU @@ -32,6 +33,9 @@ prompt 4 + + + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll diff --git a/ApiAsAService/EdmObjectsGenerator/Program.cs b/ApiAsAService/EdmObjectsGenerator/Program.cs index 36e36f1..57c3fea 100644 --- a/ApiAsAService/EdmObjectsGenerator/Program.cs +++ b/ApiAsAService/EdmObjectsGenerator/Program.cs @@ -15,7 +15,7 @@ using Microsoft.OData.Edm.Csdl; using Microsoft.OData.Edm.Validation; - class Program + public class Program { static Dictionary _typeBuildersDict = new Dictionary(); static Regex collectionRegex = new Regex(@"Collection\((.+)\)", RegexOptions.Compiled); @@ -282,10 +282,27 @@ private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNu } } - static void Main(string[] args) + public static Type GenerateDbContext(string csdlFile) + { + var model = ReadModel(csdlFile); + var name = csdlFile.Split('\\').Last(); + + // create a dynamic assembly and module + AssemblyName assemblyName = new AssemblyName(); + assemblyName.Name = name; + AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + ModuleBuilder module; + module = assemblyBuilder.DefineDynamicModule($"{assemblyName.Name}.dll"); + BuildModules(model, module); + Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name); + Type entitiesType = assembly.GetTypes().FirstOrDefault(t => t.Name == "Entities"); + return entitiesType; + } + + public static void Main(string[] args) { //TODO: grab edm path and assembly name from cmdline args - var model = ReadModel(@"<>"); + var model = ReadModel(@"C:\Repos\ApiAsAService\ApiAsAService\Trippin\Trippin\App_Data\NWind.xml"); var name = "NW_Simple"; // create a dynamic assembly and module diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs new file mode 100644 index 0000000..7618baf --- /dev/null +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNet.OData.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core; +using Microsoft.Restier.Core.Model; +using Microsoft.Restier.EntityFramework; +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web; + +namespace Microsoft.Restier.AspNet +{ + /// + /// API Factory + /// + public class ApiFactory + { + private IServiceProvider serviceProvider; + private ApiBase apiBase; + + /// + /// Factory for creating an APIBase + /// + /// + public ApiFactory(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + /// + /// ModelType + /// + public Type ModelType {get; set;} + + /// + /// Get ApiBase + /// + /// + public ApiBase GetApiBase() + { + if (apiBase == null) + { + if (ModelType == null) + { + ModelType = typeof(DbContext); + return generateApiBase(ModelType, serviceProvider); + } + + apiBase = generateApiBase(ModelType, serviceProvider); + } + + return apiBase; + } + + private static ApiBase generateApiBase(Type modelType, IServiceProvider serviceProvider) + { + return (ApiBase)typeof(EntityFrameworkApi<>).MakeGenericType(modelType).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); + } + } +} \ No newline at end of file diff --git a/ApiAsAService/RESTierAsAService.sln b/ApiAsAService/RESTierAsAService.sln index af3ab97..34ee83c 100644 --- a/ApiAsAService/RESTierAsAService.sln +++ b/ApiAsAService/RESTierAsAService.sln @@ -36,6 +36,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shar EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.ApiAsAService", "Trippin\Trippin\Microsoft.OData.Service.ApiAsAService.csproj", "{B379640E-9064-438D-8DA5-6F7B394C2C46}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmObjectsGenerator", "EdmObjectsGenerator\EdmObjectsGenerator.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 @@ -83,6 +85,12 @@ Global {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.Build.0 = Debug|Any CPU {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.ActiveCfg = Release|Any CPU {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.Build.0 = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs index e5624bd..84c7a08 100644 --- a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs +++ b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs @@ -72,5 +72,6 @@ public async Task GetModelAsync(ModelContext context, CancellationTok { return GetModel(); } + } } \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml b/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml new file mode 100644 index 0000000..3aa4ca2 --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs index 7d80044..fcfcb66 100644 --- a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs +++ b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs @@ -56,18 +56,22 @@ public override bool Match(HttpRequestMessage request, IHttpRoute route, string //IHttpRequestMessageProvider httpRequestMessageProvider = requestContainer.GetRequiredService(); //httpRequestMessageProvider.Request = request; - Type dynamicType; - switch (dataSourceName) - { - case "Trippin": - dynamicType = typeof(Models.TrippinModel); - break; - case "NWind": - dynamicType = typeof(ODataDemo.NWModel); - break; - default: - throw new Exception("Service not found"); //return request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", dataSourceName)); - } + //Type dynamicType; + //switch (dataSourceName) + //{ + // case "Trippin": + // dynamicType = typeof(Models.TrippinModel); + // break; + // case "NWind": + // dynamicType = typeof(ODataDemo.NWModel); + // break; + // default: + // throw new Exception("Service not found"); //return request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", dataSourceName)); + //} + var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); + var file = System.IO.Path.Combine(appData, dataSourceName + ".xml"); + + Type dynamicType = EdmObjectsGenerator.Program.GenerateDbContext(file); ApiFactory factory = serviceProvider.GetRequiredService(); factory.ModelType = dynamicType; diff --git a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj index 6016cb0..7182e4f 100644 --- a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj +++ b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj @@ -170,6 +170,10 @@ + + {3678c588-f1dd-4ef6-879a-3d1d8114a880} + EdmObjectsGenerator + {8ecf4e97-1816-44ad-ad63-6acf287ed520} Microsoft.Restier.AspNet diff --git a/ApiAsAService/Trippin/Trippin/Models/NWModel.cs b/ApiAsAService/Trippin/Trippin/Models/NWModel.cs new file mode 100644 index 0000000..ea5a691 --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/Models/NWModel.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; +using System.Data.Entity; + +namespace ODataDemo +{ + public class NWModel : DbContext + { + static NWModel() + { + Database.SetInitializer(new NwDatabaseInitializer()); + } + + public NWModel() + : base("name=NWModel") + { + } + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } + + public DbSet Products { get; set; } + public DbSet ProductDetails { get; set; } + public DbSet Categories { get; set; } + public DbSet Suppliers { get; set; } + public DbSet Persons { get; set; } + + private static NWModel instance; + public static NWModel Instance + { + get + { + if (instance == null) + { + ResetDataSource(); + } + return instance; + } + } + + public static void ResetDataSource() + { + instance = new NWModel(); + + #region Products + + var products = new List + { + new Product + { + ProductID=1, + Name = "widget", + }, + + new Product + { + ProductID=2, + Name = "Gadget", + }, + }; + + instance.Products.AddRange(products); + + #endregion + + + instance.Persons.AddRange(new List + { + new Person + { + ID = 1, + Name = "Willie", + }, + new Person + { + ID = 2, + Name = "Vincent", + }, + }); + + instance.SaveChanges(); + } + } + + class NwDatabaseInitializer : DropCreateDatabaseAlways + { + protected override void Seed(NWModel context) + { + NWModel.ResetDataSource(); + } + } + + public class Product + { + [Key] + public Int32 ProductID; + public string Name; + public string Description; + public DateTimeOffset ReleaseDate; + public DateTimeOffset DiscontinuedDate; + public Int16 Price; + public virtual ICollection Categories { get; set; } + public virtual Supplier Supplier { get; set; } + public virtual ProductDetail ProductDetail { get; set; } + } + + public class ProductDetail + { + [Key] + public Int32 ProductID; + public string Details; + public virtual Product Product { get; set; } + } + + public class Category + { + [Key] + public Int32 ID; + public string Name; + public virtual ICollection Products {get; set;} + } + + public class Supplier + { + [Key] + public Int32 Id; + public string Name; + public Address Address; + public Int32 Concurrency; + public virtual ICollection Products { get; set; } + } + + public class Address + { + public string Street; + public string City; + public string State; + public string ZipCode; + public string Country; + } + + public class Person + { + [Key] + public Int32 ID; + public string Name; + public virtual PersonDetail PersonDetail { get; set; } + } + + public class Customer : Person + { + public Decimal TotalExpense; + } + + public class Employee : Person + { + [Key] + public Int64 EmployeeId; + public DateTimeOffset HireDate; + public Single Salary; + } + + public class PersonDetail + { + [Key] + public Int32 PersonId; + public int Age; + public bool Gender; + public string Phone; + public Address Address; + public virtual Person Person { get; set; } + } +} From 6998cafecce97d10f510bb893c61d6e04998a51d Mon Sep 17 00:00:00 2001 From: riach Date: Wed, 24 Jul 2019 20:58:24 -0700 Subject: [PATCH 15/21] fix for trippin --- ApiAsAService/ApiAsAService.sln | 14 ++ ApiAsAService/EdmObjectsGenerator/Program.cs | 191 ++++++++++++++----- 2 files changed, 152 insertions(+), 53 deletions(-) diff --git a/ApiAsAService/ApiAsAService.sln b/ApiAsAService/ApiAsAService.sln index 8e5105e..0623e75 100644 --- a/ApiAsAService/ApiAsAService.sln +++ b/ApiAsAService/ApiAsAService.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAServ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmObjectsGenerator", "EdmObjectsGenerator\EdmObjectsGenerator.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLoadTester", "..\..\EdmLoadTester\EdmLoadTester.csproj", "{3CF839FF-83B2-4162-B860-68F57B7B0190}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeAnalysis|Any CPU = CodeAnalysis|Any CPU @@ -41,6 +43,18 @@ Global {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.Build.0 = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|x64.ActiveCfg = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|x64.Build.0 = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.Build.0 = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.ActiveCfg = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.Build.0 = Debug|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.Build.0 = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.ActiveCfg = Release|Any CPU + {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ApiAsAService/EdmObjectsGenerator/Program.cs b/ApiAsAService/EdmObjectsGenerator/Program.cs index 57c3fea..eea8991 100644 --- a/ApiAsAService/EdmObjectsGenerator/Program.cs +++ b/ApiAsAService/EdmObjectsGenerator/Program.cs @@ -19,12 +19,12 @@ public class Program { static Dictionary _typeBuildersDict = new Dictionary(); static Regex collectionRegex = new Regex(@"Collection\((.+)\)", RegexOptions.Compiled); - + static Queue _builderQueue = new Queue(); public class TypeBuilderInfo { public bool IsDerived { get; set; } public bool IsStructured { get; set; } - public TypeBuilder Builder { get; set; } + public TypeInfo Builder { get; set; } } public static IEdmModel ReadModel(string fileName) @@ -49,48 +49,48 @@ public static IEdmModel ReadModel(string fileName) public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) { - - IList baseProps = new List(); + //first create the basic types for the enums foreach (var modelSchemaElement in model.SchemaElements) { var declaredType = model.FindDeclaredType(modelSchemaElement.FullName()); - - if (declaredType is IEdmStructuredType) + if (declaredType == null) continue; + if (declaredType is IEdmEnumType) { - if (declaredType is IEdmEntityType) - { - - var derivedTypes = model.FindDirectlyDerivedTypes((IEdmStructuredType)declaredType); - if (derivedTypes.Any()) - { - baseProps.Add(declaredType.FullName()); - Compile((IEdmStructuredType)declaredType, moduleBuilder, declaredType.FullName()); - } - } - else - { - baseProps.Add(declaredType.FullName()); - Compile((IEdmStructuredType)declaredType, moduleBuilder, declaredType.FullName()); - _typeBuildersDict[declaredType.FullName()].IsStructured = true; - } + CreateType((IEdmEnumType)declaredType, moduleBuilder, declaredType.FullName()); + } + + } + //next create the basic types for the types + foreach (var modelSchemaElement in model.SchemaElements) + { + var declaredType = model.FindDeclaredType(modelSchemaElement.FullName()); + if (declaredType == null) continue; + if (!(declaredType is IEdmEnumType)) + { + CreateType((IEdmStructuredType)declaredType, moduleBuilder, declaredType.FullName()); } } + //go through and add all elements and their properties but not nav properties foreach (var modelSchemaElement in model.SchemaElements) { - if (!baseProps.Contains(modelSchemaElement.FullName())) + + var one = model.FindDeclaredType(modelSchemaElement.FullName()); + if (one != null && !(modelSchemaElement is IEdmEnumType)) { - var one = model.FindDeclaredType(modelSchemaElement.FullName()); - if (one != null) - { - Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); - } + Compile((IEdmStructuredType)one, moduleBuilder, one.FullName()); } + } + //finally add the nav properties foreach (var modelSchemaElement in model.SchemaElements) { + if ((modelSchemaElement is IEdmEnumType)) + { + continue; + } var one = model.FindDeclaredType(modelSchemaElement.FullName()); if (one != null) { @@ -98,17 +98,22 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) } } - - foreach (var typeBuilder in _typeBuildersDict) + //now go through the queue and create the types in dependency order + while (_builderQueue.Count != 0) { - if (typeBuilder.Value.IsDerived) + var typeBuilder = _builderQueue.Dequeue(); + if (typeBuilder.Builder is TypeBuilder) + { + ((TypeBuilder)typeBuilder.Builder).CreateType(); + + } + if (typeBuilder.Builder is EnumBuilder) { + ((EnumBuilder)typeBuilder.Builder).CreateType(); - var previouslyBuiltType = _typeBuildersDict[(typeBuilder.Value.Builder.BaseType.FullName)]; - typeBuilder.Value.Builder.CreatePassThroughConstructors(previouslyBuiltType.Builder); } - typeBuilder.Value.Builder.CreateType(); } + //generate the entities type var entitiesBuilder = moduleBuilder.DefineType("Entities", TypeAttributes.Class | TypeAttributes.Public, typeof(DbContext)); @@ -122,10 +127,11 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) { Type listOf = typeof(DbSet<>); Type selfContained = listOf.MakeGenericType(typeBuilderInfo.Value.Builder); - PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Key.Split('.')[1], selfContained); + PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Value.Builder.Name, selfContained); } - } + + // create the Main(string[] args) method MethodBuilder methodbuilder = entitiesBuilder.DefineMethod("OnModelCreating", MethodAttributes.Public | MethodAttributes.HideBySig @@ -142,6 +148,40 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) } + internal static TypeBuilder CreateType(IEdmStructuredType targetType, ModuleBuilder moduleBuilder, string moduleName) + { + if (_typeBuildersDict.ContainsKey(moduleName)) + { + return (TypeBuilder)_typeBuildersDict[moduleName].Builder; + } + if (targetType.BaseType != null) + { + TypeBuilder previouslyBuiltType = null; + if (!_typeBuildersDict.ContainsKey(moduleName)) + { + previouslyBuiltType = CreateType(targetType.BaseType, moduleBuilder, targetType.BaseType.FullTypeName()); + + } + + var typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public, previouslyBuiltType); + var typeBuilderInfo = new TypeBuilderInfo() {Builder = typeBuilder, IsDerived = true}; + _typeBuildersDict.Add(moduleName, typeBuilderInfo); + _builderQueue.Enqueue(typeBuilderInfo); + return typeBuilder; + + } + else + { + var typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public); + var builderInfo = new TypeBuilderInfo() {Builder = typeBuilder, IsDerived = false}; + _typeBuildersDict.Add(moduleName, builderInfo); + _builderQueue.Enqueue(builderInfo); + + return typeBuilder; + } + + } + internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilder, string moduleName, bool navPass = false) @@ -149,11 +189,7 @@ internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilde TypeBuilder typeBuilder = null; if (type.BaseType != null && !navPass) { - var previouslyBuiltType = _typeBuildersDict[(type.BaseType.FullTypeName())]; - - typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public, previouslyBuiltType.Builder); - _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = true }); - + typeBuilder = CreateType(type, moduleBuilder, moduleName); } @@ -161,8 +197,7 @@ internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilde { if (typeBuilder == null) { - typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public); - _typeBuildersDict.Add(moduleName, new TypeBuilderInfo() { Builder = typeBuilder, IsDerived = false }); + typeBuilder = CreateType(type, moduleBuilder, moduleName); } foreach (var property in type.DeclaredProperties) @@ -177,7 +212,7 @@ internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilde } else { - typeBuilder = _typeBuildersDict[moduleName].Builder; + typeBuilder = (TypeBuilder)_typeBuildersDict[moduleName].Builder; foreach (var property in type.DeclaredProperties) { if (property.PropertyKind == EdmPropertyKind.Navigation) @@ -187,6 +222,32 @@ internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilde } + internal static EnumBuilder CreateType(IEdmEnumType targetType, ModuleBuilder moduleBuilder, string moduleName) + { + if (_typeBuildersDict.ContainsKey(moduleName)) + { + return (EnumBuilder)_typeBuildersDict[moduleName].Builder; + } + + EnumBuilder typeBuilder = moduleBuilder.DefineEnum(moduleName, TypeAttributes.Public, typeof(int)); + var builderInfo = new TypeBuilderInfo() {Builder = typeBuilder, IsDerived = false}; + _typeBuildersDict.Add(moduleName, builderInfo); + _builderQueue.Enqueue(builderInfo); + return typeBuilder; + + + } + + internal static void Compile(IEdmEnumType type, ModuleBuilder moduleBuilder, string moduleName) + { + var typeBuilder = CreateType(type, moduleBuilder, moduleName); + foreach (var enumMember in type.Members) + { + GenerateEnum(enumMember, typeBuilder, moduleBuilder); + + } + } + internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder) { var propertyName = property.Name; @@ -206,7 +267,9 @@ internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBui { var typeName = collectionRegex.Match(property.Type.FullName()).Groups[1].Value; Type listOf = typeof(List<>); - Type selfContained = listOf.MakeGenericType(_typeBuildersDict[typeName].Builder); + var baseType = _typeBuildersDict.ContainsKey(typeName) ? _typeBuildersDict[typeName].Builder : typeof(string); + + var selfContained = listOf.MakeGenericType(baseType); propertyType = selfContained; } else @@ -215,16 +278,30 @@ internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBui propertyType = navProptype.Builder; } - } else { - - var previouslyBuiltType = moduleBuilder.GetType(property.Type.FullName()); - propertyType = previouslyBuiltType; + if (property.Type.FullName().StartsWith("Collection")) + { + var typeName = collectionRegex.Match(property.Type.FullName()).Groups[1].Value; + Type listOf = typeof(List<>); + var baseType = _typeBuildersDict.ContainsKey(typeName) ? _typeBuildersDict[typeName].Builder : typeof(string); + var selfContained = listOf.MakeGenericType(baseType); + + + + propertyType = selfContained; + } + else + { + var previouslyBuiltType = _typeBuildersDict[property.Type.FullName()]; + + propertyType = previouslyBuiltType.Builder; + } + } } - if (property.Type.IsNullable && Nullable.GetUnderlyingType(propertyType)!=null) + if (property.Type.IsNullable && Nullable.GetUnderlyingType(propertyType) != null) { Type nullableOf = typeof(Nullable<>); Type selfContained = nullableOf.MakeGenericType(propertyType); @@ -233,6 +310,14 @@ internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBui PropertyBuilderHelper.BuildProperty(typeBuilder, propertyName, propertyType); } + internal static void GenerateEnum(IEdmEnumMember member, EnumBuilder enumBuilder, ModuleBuilder moduleBuilder) + { + var memberName = member.Name; + + var memberValue = Convert.ToInt32(member.Value.Value); + enumBuilder.DefineLiteral(memberName, memberValue); + } + /// /// Get Clr type /// @@ -299,11 +384,11 @@ public static Type GenerateDbContext(string csdlFile) return entitiesType; } - public static void Main(string[] args) + static void Main(string[] args) { //TODO: grab edm path and assembly name from cmdline args - var model = ReadModel(@"C:\Repos\ApiAsAService\ApiAsAService\Trippin\Trippin\App_Data\NWind.xml"); - var name = "NW_Simple"; + var model = ReadModel("<>"); + var name = "name"; // create a dynamic assembly and module AssemblyName assemblyName = new AssemblyName(); From fd7e0878c2ba544b34804f170c4ceae3107777fb Mon Sep 17 00:00:00 2001 From: riach Date: Wed, 24 Jul 2019 21:10:09 -0700 Subject: [PATCH 16/21] fix enum generation --- ApiAsAService/EdmObjectsGenerator/Program.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ApiAsAService/EdmObjectsGenerator/Program.cs b/ApiAsAService/EdmObjectsGenerator/Program.cs index eea8991..b60f15f 100644 --- a/ApiAsAService/EdmObjectsGenerator/Program.cs +++ b/ApiAsAService/EdmObjectsGenerator/Program.cs @@ -70,6 +70,11 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder) { CreateType((IEdmStructuredType)declaredType, moduleBuilder, declaredType.FullName()); } + else + { + Compile((IEdmEnumType)declaredType, moduleBuilder, declaredType.FullName()); + + } } //go through and add all elements and their properties but not nav properties @@ -387,8 +392,8 @@ public static Type GenerateDbContext(string csdlFile) static void Main(string[] args) { //TODO: grab edm path and assembly name from cmdline args - var model = ReadModel("<>"); - var name = "name"; + var model = ReadModel(@"C:\repos\fun\lab\ApiAsAService\Trippin.xml"); + var name = "Trippin2"; // create a dynamic assembly and module AssemblyName assemblyName = new AssemblyName(); From 8c7736800bdd11efab050ed38c138221593dd734 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Thu, 25 Jul 2019 08:16:33 -0700 Subject: [PATCH 17/21] Exploration: Move type generation to a separate domain. --- .../{Program.cs => DbContextGenerator.cs} | 62 +++++++++++-------- .../EdmObjectsGenerator.csproj | 2 +- .../Trippin/Trippin/DynamicHelper.cs | 27 ++++++-- .../Trippin/Trippin/DynamicRouteConstraint.cs | 5 +- 4 files changed, 60 insertions(+), 36 deletions(-) rename ApiAsAService/EdmObjectsGenerator/{Program.cs => DbContextGenerator.cs} (87%) diff --git a/ApiAsAService/EdmObjectsGenerator/Program.cs b/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs similarity index 87% rename from ApiAsAService/EdmObjectsGenerator/Program.cs rename to ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs index 57c3fea..2a81ada 100644 --- a/ApiAsAService/EdmObjectsGenerator/Program.cs +++ b/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs @@ -15,12 +15,13 @@ using Microsoft.OData.Edm.Csdl; using Microsoft.OData.Edm.Validation; - public class Program + [Serializable] + public class DbContextGenerator { static Dictionary _typeBuildersDict = new Dictionary(); static Regex collectionRegex = new Regex(@"Collection\((.+)\)", RegexOptions.Compiled); - public class TypeBuilderInfo + public class TypeBuilderInfo : MarshalByRefObject { public bool IsDerived { get; set; } public bool IsStructured { get; set; } @@ -282,38 +283,45 @@ private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNu } } - public static Type GenerateDbContext(string csdlFile) - { - var model = ReadModel(csdlFile); - var name = csdlFile.Split('\\').Last(); + public string csdlFileName { get; set; } - // create a dynamic assembly and module - AssemblyName assemblyName = new AssemblyName(); - assemblyName.Name = name; - AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); - ModuleBuilder module; - module = assemblyBuilder.DefineDynamicModule($"{assemblyName.Name}.dll"); - BuildModules(model, module); - Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name); - Type entitiesType = assembly.GetTypes().FirstOrDefault(t => t.Name == "Entities"); - return entitiesType; - } + public Type DbContextType { get; set; } - public static void Main(string[] args) + public void GenerateDbContext() { - //TODO: grab edm path and assembly name from cmdline args - var model = ReadModel(@"C:\Repos\ApiAsAService\ApiAsAService\Trippin\Trippin\App_Data\NWind.xml"); - var name = "NW_Simple"; + string fileName = this.csdlFileName; + IEdmModel model = ReadModel(fileName); - // create a dynamic assembly and module + AppDomain appDomain = AppDomain.CurrentDomain; + string name = fileName.Split('\\').Last().Replace(".xml", ""); AssemblyName assemblyName = new AssemblyName(); - assemblyName.Name = name; - AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); - ModuleBuilder module; - module = assemblyBuilder.DefineDynamicModule($"{assemblyName.Name}.dll"); + assemblyName.Name = name + "_Assembly"; + + AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); + ModuleBuilder module = assemblyBuilder.DefineDynamicModule($"{assemblyName.Name}"); BuildModules(model, module); - assemblyBuilder.Save($"{assemblyName.Name}.dll"); + //assemblyBuilder.Save($"{assemblyName.Name}.dll"); + Assembly assembly = appDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == assemblyName.Name); + this.DbContextType = assembly.GetTypes().FirstOrDefault(t => t.Name == "Entities"); + AppDomain domain = appDomain.GetData("domain") as AppDomain; + domain.SetData("contextType", this.DbContextType); + //AppDomain.CurrentDomain.SetData("contextType", this.DbContextType); + DbContextGenerator program = new DbContextGenerator(); + //Result.DbContextType = this.DbContextType; + //domain.DoCallBack(new CrossAppDomainDelegate(program.ReturnDbContext)); } + + //public void ReturnDbContext() + //{ + // Type response = this.DbContextType; + // Result.DbContextType = response; + //} } + + //[Serializable] + //public static class Result + //{ + // public static Type DbContextType { get; set; } + //} } diff --git a/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj b/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj index ff3a4e2..bc7d3da 100644 --- a/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj +++ b/ApiAsAService/EdmObjectsGenerator/EdmObjectsGenerator.csproj @@ -57,7 +57,7 @@ - + diff --git a/ApiAsAService/Trippin/Trippin/DynamicHelper.cs b/ApiAsAService/Trippin/Trippin/DynamicHelper.cs index 1d4601c..31df8ae 100644 --- a/ApiAsAService/Trippin/Trippin/DynamicHelper.cs +++ b/ApiAsAService/Trippin/Trippin/DynamicHelper.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNet.OData.Batch; +using EdmObjectsGenerator; +using Microsoft.AspNet.OData.Batch; using Microsoft.AspNet.OData.Extensions; using Microsoft.AspNet.OData.Routing; using Microsoft.AspNet.OData.Routing.Conventions; @@ -25,9 +26,9 @@ namespace Microsoft.OData.Service.ApiAsAService public static class DynamicHelper { private const string MapRestierRouteMethod = "MapRestierRoute"; -// private const string RestierAssembly = "Microsoft.Restier.AspNet"; + // private const string RestierAssembly = "Microsoft.Restier.AspNet"; private const string httpConfigurationExtensionsType = "System.Web.Http.HttpConfigurationExtensions"; -// private const string ApiAsAServiceAssembly = "Microsoft.OData.Service.ApiAsAService"; + // private const string ApiAsAServiceAssembly = "Microsoft.OData.Service.ApiAsAService"; private const string DynamicApiType = "Microsoft.OData.Service.ApiAsAService.Api.DynamicApi`1"; private const string DynamicHelperType = "Microsoft.OData.Service.ApiAsAService.DynamicHelper"; private static Type dynamicApiType = Assembly.GetExecutingAssembly().GetType(DynamicApiType); @@ -90,7 +91,7 @@ public static Task MapRestierRoute( // Callback is called by ApiBase.AddApiServices method to add real services. ApiBase.AddPublisherServices(typeof(TApi), services => { - services.AddScoped(sp=>new ApiFactory(sp)); + services.AddScoped(sp => new ApiFactory(sp)); MapEfServices(services); services.AddODataServices(); }); @@ -245,8 +246,24 @@ private static IServiceCollection MapEfServices(IServiceCollection services) .AddService() .AddService() ; + } + public static Type GetDynamicDbContext(string csdlFile) + { + string name = csdlFile.Split('\\').Last().Replace(".xml", ""); + AppDomain currentDomain = AppDomain.CurrentDomain; + var newDomain = AppDomain.CreateDomain(name, null, AppDomain.CurrentDomain.SetupInformation); + newDomain.SetData("domain", currentDomain); + var program = new DbContextGenerator(); + program.csdlFileName = csdlFile; + newDomain.DoCallBack(new CrossAppDomainDelegate(program.GenerateDbContext)); + // string assemblies = String.Concat(newDomain.GetAssemblies().Select(a=>a.GetName().Name)); + // Assembly assembly = newDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == assemblyName.Name); + //Type result = program.DbContextType; + //return Result.DbContextType; + //return program.DbContextType; + object resultType = AppDomain.CurrentDomain.GetData("contextType"); + return resultType as Type; } } - } \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs index fcfcb66..5afef4b 100644 --- a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs +++ b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs @@ -54,8 +54,6 @@ public override bool Match(HttpRequestMessage request, IHttpRoute route, string IServiceProvider serviceProvider = request.CreateRequestContainer(this.RouteName); - //IHttpRequestMessageProvider httpRequestMessageProvider = requestContainer.GetRequiredService(); - //httpRequestMessageProvider.Request = request; //Type dynamicType; //switch (dataSourceName) //{ @@ -68,10 +66,11 @@ public override bool Match(HttpRequestMessage request, IHttpRoute route, string // default: // throw new Exception("Service not found"); //return request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", dataSourceName)); //} + var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); var file = System.IO.Path.Combine(appData, dataSourceName + ".xml"); - Type dynamicType = EdmObjectsGenerator.Program.GenerateDbContext(file); + Type dynamicType = DynamicHelper.GetDynamicDbContext(file); ApiFactory factory = serviceProvider.GetRequiredService(); factory.ModelType = dynamicType; From 7788fedef6a3d6425ab73d993d5f94eadc435328 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Sun, 26 Jul 2020 19:30:36 -0700 Subject: [PATCH 18/21] Load model dynamically Fix codegen -Build entitysets using EdmContainer -Create constructor for DbContextGenerator -Add [Key] and [Order] attributes as appropriate Start to clean up code, remove unused code, archive old projects Code now works end-to-end for Trippin. Loads metadata for NWind and NW_Simple, but need to work through some generation issues. --- ApiAsAService/ApiAsAService.sln | 65 ------ .../ApiAsAService/ApiAsAService.csproj | 142 ------------ ApiAsAService/ApiAsAService/App.config | 34 --- .../ApiAsAService/App_Start/WebApiConfig.cs | 18 -- ApiAsAService/ApiAsAService/Constants.cs | 16 -- .../Controllers/HandleAllController.cs | 152 ------------- .../CustomODataPathRouteConstraint.cs | 205 ------------------ .../ApiAsAService/CustomODataRoute.cs | 82 ------- .../DataSource/AnotherDataSource.cs | 86 -------- .../DataSource/DataSourceProvider.cs | 57 ----- .../DataSource/DocumentDBRepository.cs | 120 ---------- .../ApiAsAService/DataSource/IDataSource.cs | 19 -- .../ApiAsAService/DataSource/MyDataSource.cs | 81 ------- .../ApiAsAService/DynamicModelHelper.cs | 52 ----- .../HttpRequestMessageProvider.cs | 13 -- .../IHttpRequestMessageProvider.cs | 13 -- ApiAsAService/ApiAsAService/Item.cs | 11 - .../MatchAllRoutingConvention.cs | 44 ---- ApiAsAService/ApiAsAService/Program.cs | 86 -------- .../ApiAsAService/Properties/AssemblyInfo.cs | 36 --- ApiAsAService/ApiAsAService/packages.config | 31 --- .../EdmObjectsGenerator/DbContextGenerator.cs | 46 ++-- .../PropertyBuilderHelper.cs | 27 ++- .../EdmObjectsGenerator/TypeBuilderHelpers.cs | 17 ++ ApiAsAService/NW_Simple.xml | 143 ------------ .../Microsoft.Restier.AspNet.csproj | 2 +- .../Microsoft.Restier.Core.csproj | 2 +- .../Microsoft.Restier.EntityFramework.csproj | 4 +- ApiAsAService/RESTierAsAService.sln | 9 - ApiAsAService/ReadMe.md | 2 +- .../Trippin/Trippin/Api/DynamicApi.cs | 5 +- .../Trippin/Trippin/App_Data/NWind.xml | 4 - .../Trippin/Trippin/App_Data/Northwind.mdf | Bin 0 -> 8388608 bytes .../Controllers/DynamicApiController.cs | 19 +- .../CustomizedPayloadValueConverter.cs | 32 +++ .../Trippin/Trippin/DynamicRouteConstraint.cs | 30 +-- ...crosoft.OData.Service.ApiAsAService.csproj | 19 +- ApiAsAService/Trippin/Trippin/Web.config | 7 +- .../Microsoft.AspNet.OData.csproj | 2 +- .../src/Microsoft.AspNet.OData/app.config | 2 +- .../tools/WebStack.versions.settings.targets | 14 +- ApiAsAService/WebAPIAsAService.sln | 178 --------------- 42 files changed, 142 insertions(+), 1785 deletions(-) delete mode 100644 ApiAsAService/ApiAsAService.sln delete mode 100644 ApiAsAService/ApiAsAService/ApiAsAService.csproj delete mode 100644 ApiAsAService/ApiAsAService/App.config delete mode 100644 ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs delete mode 100644 ApiAsAService/ApiAsAService/Constants.cs delete mode 100644 ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs delete mode 100644 ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs delete mode 100644 ApiAsAService/ApiAsAService/CustomODataRoute.cs delete mode 100644 ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs delete mode 100644 ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs delete mode 100644 ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs delete mode 100644 ApiAsAService/ApiAsAService/DataSource/IDataSource.cs delete mode 100644 ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs delete mode 100644 ApiAsAService/ApiAsAService/DynamicModelHelper.cs delete mode 100644 ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs delete mode 100644 ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs delete mode 100644 ApiAsAService/ApiAsAService/Item.cs delete mode 100644 ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs delete mode 100644 ApiAsAService/ApiAsAService/Program.cs delete mode 100644 ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs delete mode 100644 ApiAsAService/ApiAsAService/packages.config delete mode 100644 ApiAsAService/NW_Simple.xml create mode 100644 ApiAsAService/Trippin/Trippin/App_Data/Northwind.mdf create mode 100644 ApiAsAService/Trippin/Trippin/CustomizedPayloadValueConverter.cs delete mode 100644 ApiAsAService/WebAPIAsAService.sln diff --git a/ApiAsAService/ApiAsAService.sln b/ApiAsAService/ApiAsAService.sln deleted file mode 100644 index 0623e75..0000000 --- a/ApiAsAService/ApiAsAService.sln +++ /dev/null @@ -1,65 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2026 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAService\ApiAsAService.csproj", "{A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmObjectsGenerator", "EdmObjectsGenerator\EdmObjectsGenerator.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmLoadTester", "..\..\EdmLoadTester\EdmLoadTester.csproj", "{3CF839FF-83B2-4162-B860-68F57B7B0190}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - CodeAnalysis|Any CPU = CodeAnalysis|Any CPU - CodeAnalysis|x64 = CodeAnalysis|x64 - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.ActiveCfg = Release|x64 - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.Build.0 = Release|x64 - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.ActiveCfg = Debug|x64 - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.Build.0 = Debug|x64 - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.ActiveCfg = Release|x64 - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.Build.0 = Release|x64 - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|x64.Build.0 = Release|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|x64.ActiveCfg = Debug|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|x64.Build.0 = Debug|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.Build.0 = Release|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|x64.ActiveCfg = Release|Any CPU - {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|x64.Build.0 = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.CodeAnalysis|x64.Build.0 = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.ActiveCfg = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Debug|x64.Build.0 = Debug|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|Any CPU.Build.0 = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.ActiveCfg = Release|Any CPU - {3CF839FF-83B2-4162-B860-68F57B7B0190}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {04797995-11AD-4320-89A0-C353AD9C9B4A} - EndGlobalSection -EndGlobal diff --git a/ApiAsAService/ApiAsAService/ApiAsAService.csproj b/ApiAsAService/ApiAsAService/ApiAsAService.csproj deleted file mode 100644 index 2638836..0000000 --- a/ApiAsAService/ApiAsAService/ApiAsAService.csproj +++ /dev/null @@ -1,142 +0,0 @@ - - - - - Debug - AnyCPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925} - Exe - ApiAsAService - ApiAsAService - v4.5 - 512 - true - - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - - ..\packages\Microsoft.AspNet.OData.7.1.0\lib\net45\Microsoft.AspNet.OData.dll - - - ..\packages\Microsoft.Azure.DocumentDB.2.5.1\lib\net45\Microsoft.Azure.Documents.Client.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - - - ..\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - - - ..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll - - - ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll - - - ..\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/App.config b/ApiAsAService/ApiAsAService/App.config deleted file mode 100644 index fc74af6..0000000 --- a/ApiAsAService/ApiAsAService/App.config +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs b/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs deleted file mode 100644 index 02c49e2..0000000 --- a/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Web.Http; -using Microsoft.AspNet.OData.Extensions; - -namespace DynamicEdmModelCreation -{ - public static class WebApiConfig - { - public static void Register(HttpConfiguration config) - { - config.CustomMapODataServiceRoute("odata", "odata"); - - config.AddODataQueryFilter(); - } - } -} diff --git a/ApiAsAService/ApiAsAService/Constants.cs b/ApiAsAService/ApiAsAService/Constants.cs deleted file mode 100644 index 775f8fa..0000000 --- a/ApiAsAService/ApiAsAService/Constants.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -namespace DynamicEdmModelCreation -{ - public static class Constants - { - public const string CustomODataPath = "CustomODataPath"; - - public const string ODataDataSource = "ODataDataSource"; - - public const string MyDataSource = "mydatasource"; - - public const string AnotherDataSource = "anotherdatasource"; - } -} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs b/ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs deleted file mode 100644 index 6e2d68d..0000000 --- a/ApiAsAService/ApiAsAService/Controllers/HandleAllController.cs +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Linq; -using System.Web.Http; -using DynamicEdmModelCreation.DataSource; -using Microsoft.AspNet.OData; -using Microsoft.AspNet.OData.Extensions; -using Microsoft.OData.Edm; -using Microsoft.OData.UriParser; -using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; - -namespace DynamicEdmModelCreation.Controllers -{ - public class HandleAllController : ODataController - { - private class Customer - { - public int Id; - public string FirstName; - public string LastName; - public int Age; - } - - private Customer[] population = new Customer[] - { - new Customer - { - Id=0, - FirstName="Don", - LastName="Juan", - Age=10, - }, - new Customer - { - Id=1, - FirstName="Sally", - LastName="Spade", - Age=12, - }, - new Customer - { - Id=3, - FirstName="Pat", - LastName="Patrick", - Age=9, - }, - }; - - // Get entityset - [EnableQuery(AllowedQueryOptions =Microsoft.AspNet.OData.Query.AllowedQueryOptions.All)] - public EdmEntityObjectCollection Get() - { - // Get entity set's EDM type: A collection type. - ODataPath path = Request.ODataProperties().Path; - IEdmCollectionType collectionType = (IEdmCollectionType)path.EdmType; - IEdmEntityTypeReference entityType = collectionType.ElementType.AsEntity(); - - // Create an untyped collection with the EDM collection type. - EdmEntityObjectCollection collection = - new EdmEntityObjectCollection(new EdmCollectionTypeReference(collectionType)); - - // Add untyped objects to collection. - DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], entityType, collection); - - return collection; - } - - // Get entityset(key) - public object Get(string key) - { - //return population.Where(c => c.Id.ToString() == key); - - // Get entity type from path. - ODataPath path = Request.ODataProperties().Path; - IEdmEntityType entityType = (IEdmEntityType)path.EdmType; - - // Create an untyped entity object with the entity type. - EdmEntityObject entity = new EdmEntityObject(entityType); - - DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], key, entity); - - return entity; - } - - public IHttpActionResult GetName(string key) - { - // Get entity type from path. - ODataPath path = Request.ODataProperties().Path; - - if (path.PathTemplate != "~/entityset/key/property") - { - return BadRequest("Not the correct property access request!"); - } - - PropertySegment property = path.Segments.Last() as PropertySegment; - IEdmEntityType entityType = property.Property.DeclaringType as IEdmEntityType; - - // Create an untyped entity object with the entity type. - EdmEntityObject entity = new EdmEntityObject(entityType); - - DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], key, entity); - - object value = DataSourceProvider.GetProperty((string)Request.Properties[Constants.ODataDataSource], "Name", entity); - - if (value == null) - { - return NotFound(); - } - - string strValue = value as string; - return Ok(strValue); - } - - public IHttpActionResult GetNavigation(string key, string navigation) - { - ODataPath path = Request.ODataProperties().Path; - - if (path.PathTemplate != "~/entityset/key/navigation") - { - return BadRequest("Not the correct navigation property access request!"); - } - - NavigationPropertySegment property = path.Segments.Last() as NavigationPropertySegment; - if (property == null) - { - return BadRequest("Not the correct navigation property access request!"); - } - - IEdmEntityType entityType = property.NavigationProperty.DeclaringType as IEdmEntityType; - - EdmEntityObject entity = new EdmEntityObject(entityType); - - DataSourceProvider.Get((string)Request.Properties[Constants.ODataDataSource], key, entity); - - object value = DataSourceProvider.GetProperty((string)Request.Properties[Constants.ODataDataSource], navigation, entity); - - if (value == null) - { - return NotFound(); - } - - IEdmEntityObject nav = value as IEdmEntityObject; - if (nav == null) - { - return NotFound(); - } - - return Ok(nav); - } - } -} diff --git a/ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs b/ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs deleted file mode 100644 index 0798d40..0000000 --- a/ApiAsAService/ApiAsAService/CustomODataPathRouteConstraint.cs +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Web.Http.Routing; -using Microsoft.AspNet.OData.Extensions; -using Microsoft.AspNet.OData.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData; - -namespace DynamicEdmModelCreation -{ - public class CustomODataPathRouteConstraint : ODataPathRouteConstraint - { - // "%2F" - private static readonly string escapedSlash = Uri.HexEscape('/'); - - public CustomODataPathRouteConstraint(string routeName) - : base(routeName) - { - } - - public override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection) - { - if (request == null) - { - throw new ArgumentNullException(nameof(request)); - } - - if (values == null) - { - throw new ArgumentNullException(nameof(values)); - } - - if (routeDirection == HttpRouteDirection.UriResolution) - { - if (values.TryGetValue(ODataRouteConstants.ODataPath, out object oDataPathValue)) - { - string oDataPathString = oDataPathValue as string; - ODataPath path; - - try - { - IServiceProvider requestContainer = request.CreateRequestContainer(this.RouteName); - IHttpRequestMessageProvider httpRequestMessageProvider = requestContainer.GetRequiredService(); - httpRequestMessageProvider.Request = request; - - string[] segments = oDataPathString.Split('/'); - string dataSource = segments[0]; - request.Properties[Constants.ODataDataSource] = dataSource; - request.Properties[Constants.CustomODataPath] = string.Join("/", segments, 1, segments.Length - 1); - oDataPathString = (string)request.Properties[Constants.CustomODataPath]; - - // Service root is the current RequestUri, less the query string and the ODataPath (always the - // last portion of the absolute path). ODL expects an escaped service root and other service - // root calculations are calculated using AbsoluteUri (also escaped). But routing exclusively - // uses unescaped strings, determined using - // address.GetComponents(UriComponents.Path, UriFormat.Unescaped) - // - // For example if the AbsoluteUri is - // , the - // oDataPathString will contain "FunctionCall(p0='Chinese西雅图Chars')". - // - // Due to this decoding and the possibility of unecessarily-escaped characters, there's no - // reliable way to determine the original string from which oDataPathString was derived. - // Therefore a straightforward string comparison won't always work. See RemoveODataPath() for - // details of chosen approach. - string requestLeftPart = request.RequestUri.GetLeftPart(UriPartial.Path); - string serviceRoot = requestLeftPart; - if (!string.IsNullOrEmpty(oDataPathString)) - { - serviceRoot = RemoveODataPath(serviceRoot, oDataPathString); - } - - // As mentioned above, we also need escaped ODataPath. - // The requestLeftPart and request.RequestUri.Query are both escaped. - // The ODataPath for service documents is empty. - string oDataPathAndQuery = requestLeftPart.Substring(serviceRoot.Length); - if (!string.IsNullOrEmpty(request.RequestUri.Query)) - { - // Ensure path handler receives the query string as well as the path. - oDataPathAndQuery += request.RequestUri.Query; - } - - // Leave an escaped '/' out of the service route because DefaultODataPathHandler will add a - // literal '/' to the end of this string if not already present. That would double the slash - // in response links and potentially lead to later 404s. - if (serviceRoot.EndsWith(escapedSlash, StringComparison.OrdinalIgnoreCase)) - { - serviceRoot = serviceRoot.Substring(0, serviceRoot.Length - 3); - } - - IODataPathHandler pathHandler = requestContainer.GetRequiredService(); - path = pathHandler.Parse(serviceRoot, oDataPathAndQuery, requestContainer); - } - catch (ODataException) - { - path = null; - } - - if (path != null) - { - // Set all the properties we need for routing, querying, formatting - request.ODataProperties().Path = path; - request.ODataProperties().RouteName = this.RouteName; - - if (!values.ContainsKey(ODataRouteConstants.Controller)) - { - // Select controller name using the routing conventions - string controllerName = this.SelectControllerName(path, request); - if (controllerName != null) - { - values[ODataRouteConstants.Controller] = controllerName; - } - } - - return true; - } - } - - // The request doesn't match this route so dipose the request container. - request.DeleteRequestContainer(true); - return false; - } - else - { - // This constraint only applies to URI resolution - return true; - } - } - - // Find the substring of the given URI string before the given ODataPath. Tests rely on the following: - // 1. ODataPath comes at the end of the processed Path - // 2. Virtual path root, if any, comes at the beginning of the Path and a '/' separates it from the rest - // 3. OData prefix, if any, comes between the virtual path root and the ODataPath and '/' characters separate - // it from the rest - // 4. Even in the case of Unicode character corrections, the only differences between the escaped Path and the - // unescaped string used for routing are %-escape sequences which may be present in the Path - // - // Therefore, look for the '/' character at which to lop off the ODataPath. Can't just unescape the given - // uriString because subsequent comparisons would only help to check wehther a match is _possible_, not where - // to do the lopping. - private static string RemoveODataPath(string uriString, string oDataPathString) - { - // Potential index of oDataPathString within uriString. - int endIndex = uriString.Length - oDataPathString.Length - 1; - if (endIndex <= 0) - { - // Bizarre: oDataPathString is longer than uriString. Likely the values collection passed to Match() - // is corrupt. - throw new InvalidOperationException($"Request Uri Is Too Short For ODataPath. the Uri is {uriString}, and the OData path is {oDataPathString}."); - } - - string startString = uriString.Substring(0, endIndex + 1); // Potential return value. - string endString = uriString.Substring(endIndex + 1); // Potential oDataPathString match. - if (string.Equals(endString, oDataPathString, StringComparison.Ordinal)) - { - // Simple case, no escaping in the ODataPathString portion of the Path. In this case, don't do extra - // work to look for trailing '/' in startString. - return startString; - } - - while (true) - { - // Escaped '/' is a derivative case but certainly possible. - int slashIndex = startString.LastIndexOf('/', endIndex - 1); - int escapedSlashIndex = - startString.LastIndexOf(escapedSlash, endIndex - 1, StringComparison.OrdinalIgnoreCase); - if (slashIndex > escapedSlashIndex) - { - endIndex = slashIndex; - } - else if (escapedSlashIndex >= 0) - { - // Include the escaped '/' (three characters) in the potential return value. - endIndex = escapedSlashIndex + 2; - } - else - { - // Failure, unable to find the expected '/' or escaped '/' separator. - throw new InvalidOperationException($"The OData path is not found. The Uri is {uriString}, and the OData path is {oDataPathString}."); - } - - startString = uriString.Substring(0, endIndex + 1); - endString = uriString.Substring(endIndex + 1); - - // Compare unescaped strings to avoid both arbitrary escaping and use of lowercase 'a' through 'f' in - // %-escape sequences. - endString = Uri.UnescapeDataString(endString); - if (string.Equals(endString, oDataPathString, StringComparison.Ordinal)) - { - return startString; - } - - if (endIndex == 0) - { - // Failure, could not match oDataPathString after an initial '/' or escaped '/'. - throw new InvalidOperationException($"The OData path is not found. The Uri is {uriString}, and the OData path is {oDataPathString}."); - } - } - } - } -} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/CustomODataRoute.cs b/ApiAsAService/ApiAsAService/CustomODataRoute.cs deleted file mode 100644 index d7665ce..0000000 --- a/ApiAsAService/ApiAsAService/CustomODataRoute.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Web.Http; -using System.Web.Http.Routing; -using Microsoft.AspNet.OData.Routing; - -namespace DynamicEdmModelCreation -{ - public class CustomODataRoute : ODataRoute - { - private static readonly string _escapedHashMark = Uri.HexEscape('#'); - private static readonly string _escapedQuestionMark = Uri.HexEscape('?'); - - private bool _canGenerateDirectLink; - - public CustomODataRoute(string routePrefix, ODataPathRouteConstraint pathConstraint) - : base(routePrefix, pathConstraint) - { - _canGenerateDirectLink = routePrefix != null && RoutePrefix.IndexOf('{') == -1; - } - - public override IHttpVirtualPathData GetVirtualPath( - HttpRequestMessage request, - IDictionary values) - { - if (values == null || !values.Keys.Contains(HttpRoute.HttpRouteKey, StringComparer.OrdinalIgnoreCase)) - { - return null; - } - - object odataPathValue; - if (!values.TryGetValue(ODataRouteConstants.ODataPath, out odataPathValue)) - { - return null; - } - - string odataPath = odataPathValue as string; - if (odataPath != null) - { - return GenerateLinkDirectly(request, odataPath) ?? base.GetVirtualPath(request, values); - } - - return null; - } - - internal HttpVirtualPathData GenerateLinkDirectly(HttpRequestMessage request, string odataPath) - { - HttpConfiguration configuration = request.GetConfiguration(); - if (configuration == null || !_canGenerateDirectLink) - { - return null; - } - - string dataSource = request.Properties[Constants.ODataDataSource] as string; - string link = CombinePathSegments(RoutePrefix, dataSource); - link = CombinePathSegments(link, odataPath); - link = UriEncode(link); - - return new HttpVirtualPathData(this, link); - } - - private static string CombinePathSegments(string routePrefix, string odataPath) - { - return string.IsNullOrEmpty(routePrefix) - ? odataPath - : (string.IsNullOrEmpty(odataPath) ? routePrefix : routePrefix + '/' + odataPath); - } - - private static string UriEncode(string str) - { - string escape = Uri.EscapeUriString(str); - escape = escape.Replace("#", _escapedHashMark); - escape = escape.Replace("?", _escapedQuestionMark); - return escape; - } - } -} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs b/ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs deleted file mode 100644 index 92cd5ed..0000000 --- a/ApiAsAService/ApiAsAService/DataSource/AnotherDataSource.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.AspNet.OData; -using Microsoft.OData.Edm; - -namespace DynamicEdmModelCreation.DataSource -{ - internal class AnotherDataSource : IDataSource - { - private IEdmEntityType _school; - - public void GetModel(EdmModel model, EdmEntityContainer container) - { - EdmEntityType student = new EdmEntityType("ns", "Student"); - student.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); - EdmStructuralProperty key = student.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32); - student.AddKeys(key); - model.AddElement(student); - EdmEntitySet students = container.AddEntitySet("Students", student); - - EdmEntityType school = new EdmEntityType("ns", "School"); - school.AddKeys(school.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32)); - school.AddStructuralProperty("CreatedDay", EdmPrimitiveTypeKind.DateTimeOffset); - model.AddElement(school); - EdmEntitySet schools = container.AddEntitySet("Schools", student); - - EdmNavigationProperty schoolNavProp = student.AddUnidirectionalNavigation( - new EdmNavigationPropertyInfo - { - Name = "School", - TargetMultiplicity = EdmMultiplicity.One, - Target = school - }); - students.AddNavigationTarget(schoolNavProp, schools); - - _school = school; - } - - public void Get(IEdmEntityTypeReference entityType, EdmEntityObjectCollection collection) - { - EdmEntityObject entity = new EdmEntityObject(entityType); - entity.TrySetPropertyValue("Name", "Foo"); - entity.TrySetPropertyValue("ID", 100); - entity.TrySetPropertyValue("School", Createchool(99, new DateTimeOffset(2016, 1, 19, 1, 2, 3, TimeSpan.Zero), entity.ActualEdmType)); - collection.Add(entity); - - entity = new EdmEntityObject(entityType); - entity.TrySetPropertyValue("Name", "Bar"); - entity.TrySetPropertyValue("ID", 101); - entity.TrySetPropertyValue("School", Createchool(99, new DateTimeOffset(1978, 11, 15, 1, 2, 3, TimeSpan.Zero), entity.ActualEdmType)); - - collection.Add(entity); - } - - public void Get(string key, EdmEntityObject entity) - { - entity.TrySetPropertyValue("Name", "Foo"); - entity.TrySetPropertyValue("ID", int.Parse(key)); - entity.TrySetPropertyValue("School", Createchool(99, new DateTimeOffset(2016, 1, 19, 1, 2, 3, TimeSpan.Zero), entity.ActualEdmType)); - } - - public object GetProperty(string property, EdmEntityObject entity) - { - object value; - entity.TryGetPropertyValue(property, out value); - return value; - } - - private IEdmEntityObject Createchool(int id, DateTimeOffset dto, IEdmStructuredType edmType) - { - IEdmNavigationProperty navigationProperty = edmType.DeclaredProperties.OfType().FirstOrDefault(e => e.Name == "School"); - if (navigationProperty == null) - { - return null; - } - - EdmEntityObject entity = new EdmEntityObject(navigationProperty.ToEntityType()); - entity.TrySetPropertyValue("ID", id); - entity.TrySetPropertyValue("CreatedDay", dto); - return entity; - } - } -} diff --git a/ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs b/ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs deleted file mode 100644 index d6a5cf7..0000000 --- a/ApiAsAService/ApiAsAService/DataSource/DataSourceProvider.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNet.OData; -using Microsoft.OData.Edm; - -namespace DynamicEdmModelCreation.DataSource -{ - internal class DataSourceProvider - { - public static IEdmModel GetEdmModel(string dataSourceName) - { - EdmModel model = new EdmModel(); - EdmEntityContainer container = new EdmEntityContainer("ns", "container"); - model.AddElement(container); - - GetDataSource(dataSourceName).GetModel(model, container); - - return model; - } - - public static void Get( - string dataSourceName, - IEdmEntityTypeReference entityType, - EdmEntityObjectCollection collection) - { - GetDataSource(dataSourceName).Get(entityType, collection); - } - - public static void Get(string dataSourceName, string key, EdmEntityObject entity) - { - GetDataSource(dataSourceName).Get(key, entity); - } - - public static object GetProperty(string dataSourceName, string property, EdmEntityObject entity) - { - return GetDataSource(dataSourceName).GetProperty(property, entity); - } - - private static IDataSource GetDataSource(string dataSourceName) - { - dataSourceName = dataSourceName == null ? string.Empty : dataSourceName.ToLowerInvariant(); - - switch (dataSourceName) - { - case Constants.MyDataSource: - return new MyDataSource(); - case Constants.AnotherDataSource: - return new AnotherDataSource(); - default: - throw new InvalidOperationException( - string.Format("Data source: {0} is not registered.", dataSourceName)); - } - } - } -} diff --git a/ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs b/ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs deleted file mode 100644 index 62c94b6..0000000 --- a/ApiAsAService/ApiAsAService/DataSource/DocumentDBRepository.cs +++ /dev/null @@ -1,120 +0,0 @@ -namespace todo -{ - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Linq; - using System.Linq.Expressions; - using System.Threading.Tasks; - using DynamicEdmModelCreation; - using Microsoft.Azure.Documents; - using Microsoft.Azure.Documents.Client; - using Microsoft.Azure.Documents.Linq; - - public static class DocumentDBRepository where T : class - { - private static readonly string DatabaseId = "schemadb"; - private static readonly string CollectionId = "schemas"; - private static DocumentClient client; - - public static async Task GetSchema(string id) - { - try - { - Document document = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri("schemadb", "schemas", "trippinschema")); - return (T)(dynamic)document; - } - catch (DocumentClientException e) - { - if (e.StatusCode == System.Net.HttpStatusCode.NotFound) - { - return null; - } - else - { - throw; - } - } - } - - public static async Task> GetItemsAsync(Expression> predicate) - { - IDocumentQuery query = client.CreateDocumentQuery( - UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), - new FeedOptions { MaxItemCount = -1 }) - .Where(predicate) - .AsDocumentQuery(); - - List results = new List(); - while (query.HasMoreResults) - { - results.AddRange(await query.ExecuteNextAsync()); - } - - return results; - } - - public static async Task CreateItemAsync(T item) - { - return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), item); - } - - public static async Task UpdateItemAsync(string id, T item) - { - return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id), item); - } - - public static async Task DeleteItemAsync(string id) - { - await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id)); - } - - public static void Initialize() - { - client = new DocumentClient(new Uri("https://schema-store-sqlapi.documents.azure.com:443/"), "b1aMOAo2Z9Fre9ip8hxpquEVScQKfrlDPlM9DzjqPFyDENuk3GJ2UIaoWptY4CL5ddbgXTlc9zRPRpYVNcbSkQ=="); - CreateDatabaseIfNotExistsAsync().Wait(); - CreateCollectionIfNotExistsAsync().Wait(); - } - - private static async Task CreateDatabaseIfNotExistsAsync() - { - try - { - await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId)); - } - catch (DocumentClientException e) - { - if (e.StatusCode == System.Net.HttpStatusCode.NotFound) - { - await client.CreateDatabaseAsync(new Database { Id = DatabaseId }); - } - else - { - throw; - } - } - } - - private static async Task CreateCollectionIfNotExistsAsync() - { - try - { - await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId)); - } - catch (DocumentClientException e) - { - if (e.StatusCode == System.Net.HttpStatusCode.NotFound) - { - await client.CreateDocumentCollectionAsync( - UriFactory.CreateDatabaseUri(DatabaseId), - new DocumentCollection { Id = CollectionId }, - new RequestOptions { OfferThroughput = 1000 }); - } - else - { - throw; - } - } - } - } -} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/DataSource/IDataSource.cs b/ApiAsAService/ApiAsAService/DataSource/IDataSource.cs deleted file mode 100644 index 585dad0..0000000 --- a/ApiAsAService/ApiAsAService/DataSource/IDataSource.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Microsoft.AspNet.OData; -using Microsoft.OData.Edm; - -namespace DynamicEdmModelCreation.DataSource -{ - internal interface IDataSource - { - void GetModel(EdmModel model, EdmEntityContainer container); - - void Get(IEdmEntityTypeReference entityType, EdmEntityObjectCollection collection); - - void Get(string key, EdmEntityObject entity); - - object GetProperty(string property, EdmEntityObject entity); - } -} diff --git a/ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs b/ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs deleted file mode 100644 index a22e67c..0000000 --- a/ApiAsAService/ApiAsAService/DataSource/MyDataSource.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Linq; -using Microsoft.AspNet.OData; -using Microsoft.OData.Edm; - -namespace DynamicEdmModelCreation.DataSource -{ - internal class MyDataSource : IDataSource - { - public void GetModel(EdmModel model, EdmEntityContainer container) - { - EdmEntityType product = new EdmEntityType("ns", "Product"); - product.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); - EdmStructuralProperty key = product.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32); - product.AddKeys(key); - model.AddElement(product); - EdmEntitySet products = container.AddEntitySet("Products", product); - - EdmEntityType detailInfo = new EdmEntityType("ns", "DetailInfo"); - detailInfo.AddKeys(detailInfo.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32)); - detailInfo.AddStructuralProperty("Title", EdmPrimitiveTypeKind.String); - model.AddElement(detailInfo); - EdmEntitySet detailInfos = container.AddEntitySet("DetailInfos", product); - - EdmNavigationProperty detailInfoNavProp = product.AddUnidirectionalNavigation( - new EdmNavigationPropertyInfo - { - Name = "DetailInfo", - TargetMultiplicity = EdmMultiplicity.One, - Target = detailInfo - }); - products.AddNavigationTarget(detailInfoNavProp, detailInfos); - } - - public void Get(IEdmEntityTypeReference entityType, EdmEntityObjectCollection collection) - { - EdmEntityObject entity = new EdmEntityObject(entityType); - entity.TrySetPropertyValue("Name", "abc"); - entity.TrySetPropertyValue("ID", 1); - entity.TrySetPropertyValue("DetailInfo", CreateDetailInfo(88, "abc_detailinfo", entity.ActualEdmType)); - - collection.Add(entity); - entity = new EdmEntityObject(entityType); - entity.TrySetPropertyValue("Name", "def"); - entity.TrySetPropertyValue("ID", 2); - entity.TrySetPropertyValue("DetailInfo", CreateDetailInfo(99, "def_detailinfo", entity.ActualEdmType)); - - collection.Add(entity); - } - - public void Get(string key, EdmEntityObject entity) - { - entity.TrySetPropertyValue("Name", "abc"); - entity.TrySetPropertyValue("ID", int.Parse(key)); - entity.TrySetPropertyValue("DetailInfo", CreateDetailInfo(88, "abc_detailinfo", entity.ActualEdmType)); - } - - public object GetProperty(string property, EdmEntityObject entity) - { - object value; - entity.TryGetPropertyValue(property, out value); - return value; - } - - private IEdmEntityObject CreateDetailInfo(int id, string title, IEdmStructuredType edmType) - { - IEdmNavigationProperty navigationProperty = edmType.DeclaredProperties.OfType().FirstOrDefault(e => e.Name == "DetailInfo"); - if (navigationProperty == null) - { - return null; - } - - EdmEntityObject entity = new EdmEntityObject(navigationProperty.ToEntityType()); - entity.TrySetPropertyValue("ID", id); - entity.TrySetPropertyValue("Title", title); - return entity; - } - } -} diff --git a/ApiAsAService/ApiAsAService/DynamicModelHelper.cs b/ApiAsAService/ApiAsAService/DynamicModelHelper.cs deleted file mode 100644 index 50c8a29..0000000 --- a/ApiAsAService/ApiAsAService/DynamicModelHelper.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; -using DynamicEdmModelCreation.DataSource; -using Microsoft.AspNet.OData.Extensions; -using Microsoft.AspNet.OData.Routing; -using Microsoft.AspNet.OData.Routing.Conventions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData; -using Microsoft.OData.Edm; -using ServiceLifetime = Microsoft.OData.ServiceLifetime; - -namespace DynamicEdmModelCreation -{ - public static class DynamicModelHelper - { - public static ODataRoute CustomMapODataServiceRoute(this HttpConfiguration configuration, string routeName, string routePrefix) - { - ODataRoute route = configuration.MapODataServiceRoute(routeName, routePrefix, builder => - { - // Get the model from the datasource of the current request: model-per-pequest. - builder.AddService(ServiceLifetime.Scoped, sp => - { - IHttpRequestMessageProvider requestMessageProvider = sp.GetRequiredService(); - string dataSource = requestMessageProvider.Request.Properties[Constants.ODataDataSource] as string; - IEdmModel model = DataSourceProvider.GetEdmModel(dataSource); - return model; - }); - - // Create a request provider for every request. This is a workaround for the missing HttpContext of a self-hosted webapi. - builder.AddService(ServiceLifetime.Scoped, sp => new HttpRequestMessageProvider()); - - // The routing conventions are registered as singleton. - builder.AddService(ServiceLifetime.Singleton, sp => - { - IList routingConventions = ODataRoutingConventions.CreateDefault(); - routingConventions.Insert(0, new MatchAllRoutingConvention()); - return routingConventions.ToList().AsEnumerable(); - }); - }); - - CustomODataRoute odataRoute = new CustomODataRoute(route.RoutePrefix, new CustomODataPathRouteConstraint(routeName)); - configuration.Routes.Remove(routeName); - configuration.Routes.Add(routeName, odataRoute); - - return odataRoute; - } - } -} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs b/ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs deleted file mode 100644 index 9fe6151..0000000 --- a/ApiAsAService/ApiAsAService/HttpRequestMessageProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Net.Http; - -namespace DynamicEdmModelCreation -{ - public class HttpRequestMessageProvider : IHttpRequestMessageProvider - { - /// - public HttpRequestMessage Request { get; set; } - } -} diff --git a/ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs b/ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs deleted file mode 100644 index 409e940..0000000 --- a/ApiAsAService/ApiAsAService/IHttpRequestMessageProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Net.Http; - -namespace DynamicEdmModelCreation -{ - // based on github https://github.com/OData/ODataSamples/pull/67 - public interface IHttpRequestMessageProvider - { - HttpRequestMessage Request { get; set; } - } -} diff --git a/ApiAsAService/ApiAsAService/Item.cs b/ApiAsAService/ApiAsAService/Item.cs deleted file mode 100644 index 83b414d..0000000 --- a/ApiAsAService/ApiAsAService/Item.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -namespace DynamicEdmModelCreation -{ - public class Item - { - public string id { get; set; } - public string csdl { get; set; } - } -} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs b/ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs deleted file mode 100644 index ed83735..0000000 --- a/ApiAsAService/ApiAsAService/MatchAllRoutingConvention.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Linq; -using System.Net.Http; -using System.Web.Http.Controllers; -using Microsoft.AspNet.OData.Routing; -using Microsoft.AspNet.OData.Routing.Conventions; -using Microsoft.OData.UriParser; -using ODataPath = Microsoft.AspNet.OData.Routing.ODataPath; - -namespace DynamicEdmModelCreation -{ - public class MatchAllRoutingConvention : IODataRoutingConvention - { - public string SelectAction( - ODataPath odataPath, - HttpControllerContext controllerContext, - ILookup actionMap) - { - if (odataPath.PathTemplate == "~/entityset/key/navigation") - { - if (controllerContext.Request.Method == HttpMethod.Get) - { - NavigationPropertySegment navigationPathSegment = (NavigationPropertySegment)odataPath.Segments.Last(); - - controllerContext.RouteData.Values["navigation"] = navigationPathSegment.NavigationProperty.Name; - - KeySegment keyValueSegment = (KeySegment)odataPath.Segments[1]; - controllerContext.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Keys.First().Value; - - return "GetNavigation"; - } - } - - return null; - } - - public string SelectController(ODataPath odataPath, HttpRequestMessage request) - { - return (odataPath.Segments.FirstOrDefault() is EntitySetSegment) ? "HandleAll" : null; - } - } -} \ No newline at end of file diff --git a/ApiAsAService/ApiAsAService/Program.cs b/ApiAsAService/ApiAsAService/Program.cs deleted file mode 100644 index d057951..0000000 --- a/ApiAsAService/ApiAsAService/Program.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Net.Http; -using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Microsoft.Azure.Documents.Linq; -using Microsoft.Owin.Hosting; -using Owin; -using todo; - -namespace DynamicEdmModelCreation -{ - class Program - { - private static readonly string serviceUrl = "http://localhost:54321"; - - public static void Main(string[] args) - { - GetTrippin().Wait(); - - using (WebApp.Start(serviceUrl, Configuration)) - { - Console.WriteLine("Server listening at {0}", serviceUrl); - - QueryTheService().Wait(); - - Console.WriteLine("Press Any Key to Exit ..."); - Console.ReadKey(); - } - } - - public static async Task GetTrippin() - { - DocumentDBRepository.Initialize(); - var item = await DocumentDBRepository.GetSchema(("trippinschema")); - Console.Write(item.csdl); - Console.WriteLine("\nDone fetching the schema"); - Console.ReadKey(); - return; - } - - private static async Task QueryTheService() - { - await SendQuery("/odata/mydatasource/", "Query service document."); - await SendQuery("/odata/mydatasource/$metadata", "Query $metadata."); - await SendQuery("/odata/mydatasource/Products", "Query the Products entity set."); - await SendQuery("/odata/mydatasource/Products(1)", "Query a Product entry."); - - await SendQuery("/odata/anotherdatasource/", "Query service document."); - await SendQuery("/odata/anotherdatasource/$metadata", "Query $metadata."); - await SendQuery("/odata/anotherdatasource/Students", "Query the Students entity set."); - await SendQuery("/odata/anotherdatasource/Students(100)", "Query a Student entry."); - - await SendQuery("/odata/mydatasource/Products(1)/Name", "Query the name of Products(1)."); - await SendQuery("/odata/anotherdatasource/Students(100)/Name", "Query the name of Students(100)."); - - await SendQuery("/odata/mydatasource/Products(1)/DetailInfo", "Query the navigation property 'DetailInfo' of Products(1)."); - await SendQuery("/odata/anotherdatasource/Students(100)/School", "Query the navigation proeprty 'School' of Students(100)."); - } - - private static async Task SendQuery(string query, string queryDescription) - { - Console.WriteLine("Sending request to: {0}. Executing {1}...", query, queryDescription); - - HttpClient client = new HttpClient(); - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, serviceUrl + query); - HttpResponseMessage response = await client.SendAsync(request); - - Console.WriteLine("\r\nResult:"); - Console.WriteLine(response.StatusCode); - Console.WriteLine(await response.Content.ReadAsStringAsync()); - Console.WriteLine(); - } - - private static void Configuration(IAppBuilder builder) - { - HttpConfiguration configuration = new HttpConfiguration(); - WebApiConfig.Register(configuration); - builder.UseWebApi(configuration); - } - } -} diff --git a/ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs b/ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs deleted file mode 100644 index b123ad8..0000000 --- a/ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DynamicEdmModelCreation")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DynamicEdmModelCreation")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a3d7d54b-67d2-44cc-bfcd-c7ce09b58925")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ApiAsAService/ApiAsAService/packages.config b/ApiAsAService/ApiAsAService/packages.config deleted file mode 100644 index cf83a5d..0000000 --- a/ApiAsAService/ApiAsAService/packages.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs b/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs index 595fca8..8fa4101 100644 --- a/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs +++ b/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs @@ -125,30 +125,29 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder, st //generate the DbContext type var entitiesBuilder = moduleBuilder.DefineType(dbContextName, TypeAttributes.Class | TypeAttributes.Public, typeof(DbContext)); var dbContextType = typeof(DbContext); - entitiesBuilder.CreatePassThroughConstructors(dbContextType); + entitiesBuilder.CreateDefaultConstructor(dbContextType, "name=" + dbContextName); - - foreach (var typeBuilderInfo in _typeBuildersDict) + foreach (var entitySet in model.EntityContainer.EntitySets()) { - if (!typeBuilderInfo.Value.IsStructured) + TypeBuilderInfo entityType = _typeBuildersDict.FirstOrDefault(t => t.Key == entitySet.EntityType().FullName()).Value; + if (entityType != null) { Type listOf = typeof(DbSet<>); - Type selfContained = listOf.MakeGenericType(typeBuilderInfo.Value.Builder); - PropertyBuilderHelper.BuildProperty(entitiesBuilder, typeBuilderInfo.Value.Builder.Name, selfContained); + Type selfContained = listOf.MakeGenericType(entityType.Builder); + PropertyBuilderHelper.BuildProperty(entitiesBuilder, entitySet.Name, selfContained); } } - - // create the Main(string[] args) method + // create the OnModelCreating method MethodBuilder methodbuilder = entitiesBuilder.DefineMethod("OnModelCreating", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.CheckAccessOnOverride | MethodAttributes.Virtual, typeof(void), new Type[] { typeof(DbModelBuilder) }); - // generate the IL for the Main method + // generate the IL for the OnModelCreating method ILGenerator ilGenerator = methodbuilder.GetILGenerator(); - ilGenerator.ThrowException(typeof(UnintentionalCodeFirstException)); +//todo: insert code ilGenerator.Emit(OpCodes.Ret); entitiesBuilder.CreateType(); } @@ -165,7 +164,6 @@ internal static TypeBuilder CreateType(IEdmStructuredType targetType, ModuleBuil if (!_typeBuildersDict.ContainsKey(moduleName)) { previouslyBuiltType = CreateType(targetType.BaseType, moduleBuilder, targetType.BaseType.FullTypeName()); - } var typeBuilder = moduleBuilder.DefineType(moduleName, TypeAttributes.Class | TypeAttributes.Public, previouslyBuiltType); @@ -189,10 +187,22 @@ internal static TypeBuilder CreateType(IEdmStructuredType targetType, ModuleBuil internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilder, string moduleName, bool navPass = false) { TypeBuilder typeBuilder = null; + + int iKey = 0; + int keyCount = 0; + IEdmEntityType entityType = type as IEdmEntityType; + if (entityType != null) + { + IEnumerable keyProperties = entityType.DeclaredKey; + if (keyProperties != null) + { + keyCount = keyProperties.Count(); + } + } + if (type.BaseType != null && !navPass) { typeBuilder = CreateType(type, moduleBuilder, moduleName); - } if (!navPass) @@ -206,7 +216,7 @@ internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilde { if (property.PropertyKind != EdmPropertyKind.Navigation) { - GenerateProperty(property, typeBuilder, moduleBuilder); + GenerateProperty(property, typeBuilder, moduleBuilder, property.IsKey() ? keyCount == 1 ? -1 : iKey++ : (int?)null); } } } @@ -244,10 +254,9 @@ internal static void Compile(IEdmEnumType type, ModuleBuilder moduleBuilder, str } } - internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder) + internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder, int? keyIndex = null) { var propertyName = property.Name; - var emdPropType = property.Type.PrimitiveKind(); var propertyType = GetPrimitiveClrType(emdPropType, false); if (propertyType == null) @@ -293,13 +302,14 @@ internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBui } } - if (property.Type.IsNullable && Nullable.GetUnderlyingType(propertyType) != null) + if (property.Type.IsNullable && propertyType.IsValueType) { Type nullableOf = typeof(Nullable<>); Type selfContained = nullableOf.MakeGenericType(propertyType); propertyType = selfContained; } - PropertyBuilderHelper.BuildProperty(typeBuilder, propertyName, propertyType); + + PropertyBuilderHelper.BuildProperty(typeBuilder, propertyName, propertyType, keyIndex); } internal static void GenerateEnum(IEdmEnumMember member, EnumBuilder enumBuilder, ModuleBuilder moduleBuilder) @@ -411,7 +421,7 @@ public class Program static void Main(string[] args) { //TODO: grab edm path and assembly name from cmdline args - string csdlFile = @"C:\repos\fun\lab\ApiAsAService\Trippin.xml"; + string csdlFile = @"Trippin.xml"; DbContextGenerator generator = new DbContextGenerator(); generator.GenerateDbContext(csdlFile); diff --git a/ApiAsAService/EdmObjectsGenerator/PropertyBuilderHelper.cs b/ApiAsAService/EdmObjectsGenerator/PropertyBuilderHelper.cs index 64542ff..f8e197a 100644 --- a/ApiAsAService/EdmObjectsGenerator/PropertyBuilderHelper.cs +++ b/ApiAsAService/EdmObjectsGenerator/PropertyBuilderHelper.cs @@ -1,12 +1,16 @@ namespace EdmObjectsGenerator { using System; + using System.Linq; using System.Reflection; using System.Reflection.Emit; public class PropertyBuilderHelper { - public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Type fieldType) + // null keyIndex means the property is not a key. + // keyIndex of -1 means the key is the only key property. + // keyIndex > -1 means the key is part of a multi-part key, as indicated by the index + public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Type fieldType, int? keyIndex = null) { FieldBuilder fieldBldr = typeBuilder.DefineField(fieldName, fieldType, @@ -17,8 +21,23 @@ public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Typ PropertyAttributes.HasDefault, fieldType, null); + if (keyIndex != null) + { + // Set the [Key] attribute + ConstructorInfo constructor = typeof(System.ComponentModel.DataAnnotations.KeyAttribute).GetConstructor(Type.EmptyTypes); + CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(constructor, new object[] { }); + propBuilder.SetCustomAttribute(attributeBuilder); + if (keyIndex > -1) + { + // Set the [Column(Order={keyIndex})] attribute + Type columnAttribute = typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute); + constructor = columnAttribute.GetConstructor(Type.EmptyTypes); + PropertyInfo[] propertyInfos = columnAttribute.GetProperties().Where(p => p.Name == "Order").ToArray(); + attributeBuilder = new CustomAttributeBuilder(constructor, new object[] { }, propertyInfos, new object[] { keyIndex }); + propBuilder.SetCustomAttribute(attributeBuilder); + } + } - MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; @@ -49,13 +68,9 @@ public static void BuildProperty(TypeBuilder typeBuilder, string fieldName, Typ custNameSetIL.Emit(OpCodes.Ldarg_1); custNameSetIL.Emit(OpCodes.Stfld, fieldBldr); custNameSetIL.Emit(OpCodes.Ret); - propBuilder.SetGetMethod(propMethodBldr); propBuilder.SetSetMethod(propSetMethodBldr); - - - } } } diff --git a/ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs b/ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs index 00cc360..b29f20e 100644 --- a/ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs +++ b/ApiAsAService/EdmObjectsGenerator/TypeBuilderHelpers.cs @@ -8,6 +8,7 @@ public static class TypeBuilderHelper { + /* /// Creates one constructor for each public constructor in the base class. Each constructor simply /// forwards its arguments to the base constructor, and matches the base constructor's signature. /// Supports optional values, and custom attributes on constructors and parameters. @@ -62,7 +63,23 @@ public static void CreatePassThroughConstructors(this TypeBuilder builder, Type emitter.Emit(OpCodes.Ret); } } + */ + public static void CreateDefaultConstructor(this TypeBuilder builder, Type baseType, string paramValue) + { + var constructor = baseType.GetConstructor(new Type[] { typeof(string) }); + var ctor = builder.DefineConstructor(MethodAttributes.Public, constructor.CallingConvention, new Type[] { }); + + var emitter = ctor.GetILGenerator(); + emitter.Emit(OpCodes.Nop); + + // Load `this` and call base constructor with arguments + emitter.Emit(OpCodes.Ldarg_0); + emitter.Emit(OpCodes.Ldstr, paramValue); + emitter.Emit(OpCodes.Call, constructor); + + emitter.Emit(OpCodes.Ret); + } private static CustomAttributeBuilder[] BuildCustomAttributes(IEnumerable customAttributes) { diff --git a/ApiAsAService/NW_Simple.xml b/ApiAsAService/NW_Simple.xml deleted file mode 100644 index a9e9939..0000000 --- a/ApiAsAService/NW_Simple.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj index efe949d..22080c8 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/Microsoft.Restier.AspNet.csproj @@ -10,7 +10,7 @@ $(PackageTags);webapi;batch - net462;net472 + net472 diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj index bb2661b..7c4b843 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj @@ -10,7 +10,7 @@ $(PackageTags) - netstandard2.0;net462 + net472 diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj index 35eb3ed..458909a 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.EntityFramework/Microsoft.Restier.EntityFramework.csproj @@ -10,11 +10,11 @@ $(PackageTags)entityframework;entityframework6 - net462;net472 + net472 - net462;net472 + net472 true diff --git a/ApiAsAService/RESTierAsAService.sln b/ApiAsAService/RESTierAsAService.sln index 34ee83c..00e1eeb 100644 --- a/ApiAsAService/RESTierAsAService.sln +++ b/ApiAsAService/RESTierAsAService.sln @@ -11,8 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{9D3D8728-C31 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.Core", "RESTier\src\Microsoft.Restier.Core\Microsoft.Restier.Core.csproj", "{300B769A-3513-49D0-A035-7DB965C8D2A4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.AspNetCore", "RESTier\src\Microsoft.Restier.AspNetCore\Microsoft.Restier.AspNetCore.csproj", "{97E94F97-E73B-4074-8587-AE1B91B4D61E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.AspNet", "RESTier\src\Microsoft.Restier.AspNet\Microsoft.Restier.AspNet.csproj", "{8ECF4E97-1816-44AD-AD63-6ACF287ED520}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Restier.EntityFramework", "RESTier\src\Microsoft.Restier.EntityFramework\Microsoft.Restier.EntityFramework.csproj", "{0E373B2A-2ED2-4566-A275-6BE81CFFE00B}" @@ -55,12 +53,6 @@ Global {300B769A-3513-49D0-A035-7DB965C8D2A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {300B769A-3513-49D0-A035-7DB965C8D2A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {300B769A-3513-49D0-A035-7DB965C8D2A4}.Release|Any CPU.Build.0 = Release|Any CPU - {97E94F97-E73B-4074-8587-AE1B91B4D61E}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {97E94F97-E73B-4074-8587-AE1B91B4D61E}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97E94F97-E73B-4074-8587-AE1B91B4D61E}.Release|Any CPU.Build.0 = Release|Any CPU {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU {8ECF4E97-1816-44AD-AD63-6ACF287ED520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -100,7 +92,6 @@ Global {37B52FD3-E72B-406F-8C5A-F146256D7743} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} = {76B4E51F-233E-4DD3-AABF-A6F47788040D} {300B769A-3513-49D0-A035-7DB965C8D2A4} = {D8A3183C-1E9C-4D6C-AC72-4EF938EC9895} - {97E94F97-E73B-4074-8587-AE1B91B4D61E} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} {8ECF4E97-1816-44AD-AD63-6ACF287ED520} = {9D3D8728-C31B-4D5E-B471-79A9DBBA0E58} {0E373B2A-2ED2-4566-A275-6BE81CFFE00B} = {37B52FD3-E72B-406F-8C5A-F146256D7743} {A6F9775D-F7E2-424E-8363-79644A73038F} = {B43F200F-B847-48BE-9362-36D94C48521D} diff --git a/ApiAsAService/ReadMe.md b/ApiAsAService/ReadMe.md index 58b3cc4..19e37f2 100644 --- a/ApiAsAService/ReadMe.md +++ b/ApiAsAService/ReadMe.md @@ -1 +1 @@ -# DynamicEdmModelCreation ----------------------- This sample shows how to dynamically create an EDM model, and bind it into Web API OData pipeline. ## Background By default, Web API OData supports a static EDM model per OData route. In WebApiConfig.Register static method, before calling config.Routes.MapODataServiceRoute, an EDM model is created, and then assigned as a parameter to the MapODataServiceRoute method. ```C# public static class WebApiConfig { public static void Register(HttpConfiguration config) { var builder = new ODataConventionModelBuilder(); builder.EntitySet("Customers"); config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel()); } } ``` However, there are some other scenarios which require runtime EDM model binding, e.g.: 1. Multi-tenant OData service: One-model per tenant 2. Hundreds of models, want to delay load as many as possible in WebApiConfig 3. Per request model This sample describes how to create a per-request EDM model, which can be used as a foundation for scenario 1 and 2: In request Uri, there is a path segment between route prefix and entity set, e.g. `mydatasource` in http://servername/odata/mydatasource/Products. Based on datasource, the service could build EDM model on the fly, and then use an untyped controller to handle the request. The steps are: 1. Create a customized `ODataPathRouteConstraint`, which allows to set EdmModel property, before Match is called. 2. Create a customized `ODataRoute` to override GetVirtualPath logic, and generate OData links correctly. 3. Create a customized `MapODataServiceRoute` that takes a Func instead of an IEdmModel. --- ## .NET Classic It's .NET Framework Console application hosting a Web API service depending on `Microsoft.AspNet.OData` nuget package. When it runs, it will output the following result: ```json Server listening at http://localhost:54321 Sending request to: /odata/mydatasource/. Executing Query service document.... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata","value":[{"name":"Products","kind":"EntitySet","url":"Products"},{"name":"DetailInfos","kind":"EntitySet","url":"DetailInfos"}]} Sending request to: /odata/mydatasource/$metadata. Executing Query $metadata.... Result: OK Sending request to: /odata/mydatasource/Products. Executing Query the Products entity set.... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#Products","value":[{"Name":"abc","ID":1},{"Name":"def","ID":2}]} Sending request to: /odata/mydatasource/Products(1). Executing Query a Product entry.... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#Products/$entity","Name":"abc","ID":1} Sending request to: /odata/anotherdatasource/. Executing Query service document.... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata","value":[{"name":"Students","kind":"EntitySet","url":"Students"},{"name":"Schools","kind":"EntitySet","url":"Schools"}]} Sending request to: /odata/anotherdatasource/$metadata. Executing Query $metadata.... Result: OK Sending request to: /odata/anotherdatasource/Students. Executing Query the Students entity set.... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Students","value":[{"Name":"Foo","ID":100},{"Name":"Bar","ID":101}]} Sending request to: /odata/anotherdatasource/Students(100). Executing Query a Student entry.... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Students/$entity","Name":"Foo","ID":100} Sending request to: /odata/mydatasource/Products(1)/Name. Executing Query the name of Products(1).... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#Products(1)/Name","value":"abc"} Sending request to: /odata/anotherdatasource/Students(100)/Name. Executing Query the name of Students(100).... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Students(100)/Name","value":"Foo"} Sending request to: /odata/mydatasource/Products(1)/DetailInfo. Executing Query the navigation property 'DetailInfo' of Products(1).... Result: OK {"@odata.context":"http://localhost:54321/odata/mydatasource/$metadata#DetailInfos/ns.DetailInfo/$entity","ID":88,"Title":"abc_detailinfo"} Sending request to: /odata/anotherdatasource/Students(100)/School. Executing Query the navigation proeprty 'School' of Students(100).... Result: OK {"@odata.context":"http://localhost:54321/odata/anotherdatasource/$metadata#Schools/ns.School/$entity","ID":99,"CreatedDay":"2016-01-19T01:02:03Z"} Press Any Key to Exit ... ``` \ No newline at end of file +# API As A Service ----------------------- This service, when run, loads an .xml schema based on the path and dynamically generates types necessary to expose an OData service over the schema using RESTier and WebAPI. ## Samples http://localhost:18384/Trippin - Trippin service document http://localhost:18384/Trippin/$metadata - Trippin metadata document http://localhost:18384/Trippin/Airlines - Trippin airlines http://localhost:18384/NWind - NWind service document http://localhost:18384/NWind/$metadata - NWind metadata document http://localhost:18384/Trippin/Airlines - Trippin airlines \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs index 84c7a08..5365636 100644 --- a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs +++ b/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Vocabularies; -using Microsoft.OData.Service.ApiAsAService.Models; using Microsoft.OData.Service.ApiAsAService.Submit; using Microsoft.Restier.Core; using Microsoft.Restier.Core.Model; @@ -35,7 +34,7 @@ public class DynamicApi : ApiBase }; IServiceCollection serviceCollection = ApiBase.ConfigureApi(apiType, services) - .AddSingleton() +// .AddSingleton() .AddSingleton(validationSettingFactory) .AddService() .AddSingleton() @@ -70,7 +69,7 @@ public IEdmModel GetModel() public async Task GetModelAsync(ModelContext context, CancellationToken cancellationToken) { - return GetModel(); + return await Task.FromResult(GetModel()); } } diff --git a/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml b/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml index 3aa4ca2..00ad62b 100644 --- a/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml +++ b/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml @@ -79,7 +79,6 @@ - @@ -99,9 +98,6 @@ - - - diff --git a/ApiAsAService/Trippin/Trippin/App_Data/Northwind.mdf b/ApiAsAService/Trippin/Trippin/App_Data/Northwind.mdf new file mode 100644 index 0000000000000000000000000000000000000000..4cb12e745f5592e92fd14f02f24cec0721cb7239 GIT binary patch literal 8388608 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
    aN@QE=($q=*$;MK|sAIg9(F~K(Yu&7>uXF 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%0A>g0`JRG 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𝔬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∨G=Mfzee+xgKqUmVZi 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
  • `_b(Cv$tg5 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 `(`z`cP}z*_=u1w0CPci_E%#{!Q7 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 z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILK;VD1L&pLD0001k{H=FRq7X7*z<>b* q1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1|A4 Get(CancellationToken cancellationToken) { IServiceProvider serviceProvider = Request.GetRequestContainer(); - //DynamicModelBuilder modelBuilder = serviceProvider.GetRequiredService() as DynamicModelBuilder; - //string datasource = modelBuilder.DataSourceName; - - //Type dynamicType; - //switch (datasource) - //{ - // case "Trippin": - // dynamicType = typeof(Models.TrippinModel); - // break; - // case "NWind": - // dynamicType = typeof(ODataDemo.NWModel); - // break; - // default: - // return Request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", datasource)); - //} - ApiFactory factory = Request.GetRequestContainer().GetRequiredService(); -// factory.ModelType = dynamicType; + //factory.ModelType = dynamicType; base.SetApi(factory.GetApiBase()); return await base.GetResponse(cancellationToken); diff --git a/ApiAsAService/Trippin/Trippin/CustomizedPayloadValueConverter.cs b/ApiAsAService/Trippin/Trippin/CustomizedPayloadValueConverter.cs new file mode 100644 index 0000000..2cb04c1 --- /dev/null +++ b/ApiAsAService/Trippin/Trippin/CustomizedPayloadValueConverter.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.OData.Edm; + +namespace Microsoft.OData.Service.ApiAsAService +{ + ///

    + /// The customized payload value converter for test only + /// + public class CustomizedPayloadValueConverter : ODataPayloadValueConverter + { + public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference) + { + if (edmTypeReference != null) + { + if (value is string) + { + var stringValue = (string) value; + + // Make People(1)/FirstName converted + if (stringValue == "Russell") + { + return stringValue + "Converter"; + } + } + } + + return base.ConvertToPayloadValue(value, edmTypeReference); + } + } +} diff --git a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs index 5afef4b..7542f98 100644 --- a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs +++ b/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs @@ -53,25 +53,25 @@ public override bool Match(HttpRequestMessage request, IHttpRoute route, string string dataSourceName = segments[0]; IServiceProvider serviceProvider = request.CreateRequestContainer(this.RouteName); - - //Type dynamicType; - //switch (dataSourceName) - //{ - // case "Trippin": - // dynamicType = typeof(Models.TrippinModel); - // break; - // case "NWind": - // dynamicType = typeof(ODataDemo.NWModel); - // break; - // default: - // throw new Exception("Service not found"); //return request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", dataSourceName)); - //} - +#if false + Type dynamicType; + switch (dataSourceName) + { + case "Trippin": + dynamicType = typeof(Models.TrippinModel); + break; + case "NWind": + dynamicType = typeof(ODataDemo.NWModel); + break; + default: + throw new Exception("Service not found"); //return request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", dataSourceName)); + } +#else var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); var file = System.IO.Path.Combine(appData, dataSourceName + ".xml"); Type dynamicType = DynamicHelper.GetDynamicDbContext(file); - +#endif ApiFactory factory = serviceProvider.GetRequiredService(); factory.ModelType = dynamicType; diff --git a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj index 7182e4f..6a20943 100644 --- a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj +++ b/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj @@ -46,9 +46,13 @@ Always + + Always + Always + Designer Designer @@ -65,21 +69,6 @@ Global.asax - - - - - - - - - - - - - - - diff --git a/ApiAsAService/Trippin/Trippin/Web.config b/ApiAsAService/Trippin/Trippin/Web.config index a374ec4..59683e0 100644 --- a/ApiAsAService/Trippin/Trippin/Web.config +++ b/ApiAsAService/Trippin/Trippin/Web.config @@ -67,13 +67,12 @@ - + + + - - - diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj index ee3e684..ddee195 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/Microsoft.AspNet.OData.csproj @@ -12,7 +12,7 @@ true ..\Strict.ruleset true - v4.6.2 + v4.7.2 $(DefineConstants);ASPNETODATA;ASPNETWEBAPI;NETFX;NETFX45 diff --git a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config index 835ae48..3b11353 100644 --- a/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config +++ b/ApiAsAService/WebAPI/src/Microsoft.AspNet.OData/app.config @@ -8,4 +8,4 @@ - + diff --git a/ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets b/ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets index bb713b6..b25fdd3 100644 --- a/ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets +++ b/ApiAsAService/WebAPI/tools/WebStack.versions.settings.targets @@ -22,10 +22,20 @@ Revision number is a date code. Note that this only work for 6 years before the year part (year minus 2017) overflows the Int16. The system convert below will throw errors when this happens. --> + + + + + 2020 + 1 + 1 @@ -111,7 +121,7 @@ - + $([System.Convert]::ToInt16('$(VersionMajor)')) $([System.Convert]::ToInt16('$(VersionMinor)')) diff --git a/ApiAsAService/WebAPIAsAService.sln b/ApiAsAService/WebAPIAsAService.sln deleted file mode 100644 index ea4a200..0000000 --- a/ApiAsAService/WebAPIAsAService.sln +++ /dev/null @@ -1,178 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI", "WebAPI", "{A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "WebAPI\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DDADB8FF-887D-48BE-8510-B463C3C4CAAB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsAService", "ApiAsAService\ApiAsAService.csproj", "{A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OData.Net", "OData.Net", "{E81D641A-FF79-40B6-A482-F73457938A23}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Core", "odata.net\src\Microsoft.OData.Core\Microsoft.OData.Core.csproj", "{989A83CC-B864-4A75-8BF3-5EDA99203A86}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Edm", "odata.net\src\Microsoft.OData.Edm\Microsoft.OData.Edm.csproj", "{7D921888-FE03-4C3F-80FE-2F624505461C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Spatial", "odata.net\src\Microsoft.Spatial\Microsoft.Spatial.csproj", "{5D921888-FE03-4C3F-40FE-2F624505461D}" -EndProject -Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 - WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{b6b951b6-c3f0-4b8e-8955-e039145e7dec}*SharedItemsImports = 13 - EndGlobalSection - GlobalSection(SolutionConfigurationPlatforms) = preSolution - CodeAnalysis|Any CPU = CodeAnalysis|Any CPU - CodeAnalysis|x64 = CodeAnalysis|x64 - CodeAnalysis|x86 = CodeAnalysis|x86 - Cover|Any CPU = Cover|Any CPU - Cover|x64 = Cover|x64 - Cover|x86 = Cover|x86 - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|Any CPU.Build.0 = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x64.ActiveCfg = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x64.Build.0 = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.ActiveCfg = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.CodeAnalysis|x86.Build.0 = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|Any CPU.ActiveCfg = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|Any CPU.Build.0 = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x64.ActiveCfg = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x64.Build.0 = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x86.ActiveCfg = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Cover|x86.Build.0 = CodeAnalysis|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x64.ActiveCfg = Debug|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x64.Build.0 = Debug|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.ActiveCfg = Debug|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|x86.Build.0 = Debug|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x64.ActiveCfg = Release|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x64.Build.0 = Release|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.ActiveCfg = Release|Any CPU - {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|x86.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x64.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x86.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.CodeAnalysis|x86.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|Any CPU.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|Any CPU.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x64.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x64.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x86.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Cover|x86.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.ActiveCfg = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x64.Build.0 = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x86.ActiveCfg = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Debug|x86.Build.0 = Debug|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|Any CPU.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x64.Build.0 = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x86.ActiveCfg = Release|Any CPU - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925}.Release|x86.Build.0 = Release|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|Any CPU.ActiveCfg = Cover|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|Any CPU.Build.0 = Cover|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x64.ActiveCfg = Cover|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x64.Build.0 = Cover|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x86.ActiveCfg = Cover|x86 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.CodeAnalysis|x86.Build.0 = Cover|x86 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.ActiveCfg = Cover|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|Any CPU.Build.0 = Cover|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.ActiveCfg = Cover|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x64.Build.0 = Cover|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.ActiveCfg = Cover|x86 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Cover|x86.Build.0 = Cover|x86 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|Any CPU.Build.0 = Debug|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.ActiveCfg = Debug|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x64.Build.0 = Debug|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.ActiveCfg = Debug|x86 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Debug|x86.Build.0 = Debug|x86 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.ActiveCfg = Release|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|Any CPU.Build.0 = Release|Any CPU - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.ActiveCfg = Release|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x64.Build.0 = Release|x64 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.ActiveCfg = Release|x86 - {989A83CC-B864-4A75-8BF3-5EDA99203A86}.Release|x86.Build.0 = Release|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|Any CPU.ActiveCfg = Cover|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|Any CPU.Build.0 = Cover|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x64.ActiveCfg = Cover|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x64.Build.0 = Cover|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x86.ActiveCfg = Cover|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.CodeAnalysis|x86.Build.0 = Cover|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.ActiveCfg = Cover|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|Any CPU.Build.0 = Cover|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.ActiveCfg = Cover|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x64.Build.0 = Cover|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.ActiveCfg = Cover|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Cover|x86.Build.0 = Cover|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.ActiveCfg = Debug|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x64.Build.0 = Debug|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.ActiveCfg = Debug|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Debug|x86.Build.0 = Debug|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|Any CPU.Build.0 = Release|Any CPU - {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.ActiveCfg = Release|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x64.Build.0 = Release|x64 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.ActiveCfg = Release|x86 - {7D921888-FE03-4C3F-80FE-2F624505461C}.Release|x86.Build.0 = Release|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|Any CPU.ActiveCfg = Cover|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|Any CPU.Build.0 = Cover|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x64.ActiveCfg = Cover|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x64.Build.0 = Cover|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x86.ActiveCfg = Cover|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.CodeAnalysis|x86.Build.0 = Cover|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.ActiveCfg = Cover|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|Any CPU.Build.0 = Cover|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.ActiveCfg = Cover|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x64.Build.0 = Cover|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.ActiveCfg = Cover|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Cover|x86.Build.0 = Cover|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.ActiveCfg = Debug|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x64.Build.0 = Debug|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.ActiveCfg = Debug|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Debug|x86.Build.0 = Debug|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|Any CPU.Build.0 = Release|Any CPU - {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.ActiveCfg = Release|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x64.Build.0 = Release|x64 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.ActiveCfg = Release|x86 - {5D921888-FE03-4C3F-40FE-2F624505461D}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} = {DDADB8FF-887D-48BE-8510-B463C3C4CAAB} - {A6F9775D-F7E2-424E-8363-79644A73038F} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} - {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {A9836F9E-6DB3-4D9F-ADCA-CF42D8C8BA93} - {A3D7D54B-67D2-44CC-BFCD-C7CE09B58925} = {DDADB8FF-887D-48BE-8510-B463C3C4CAAB} - {E81D641A-FF79-40B6-A482-F73457938A23} = {DDADB8FF-887D-48BE-8510-B463C3C4CAAB} - {989A83CC-B864-4A75-8BF3-5EDA99203A86} = {E81D641A-FF79-40B6-A482-F73457938A23} - {7D921888-FE03-4C3F-80FE-2F624505461C} = {E81D641A-FF79-40B6-A482-F73457938A23} - {5D921888-FE03-4C3F-40FE-2F624505461D} = {E81D641A-FF79-40B6-A482-F73457938A23} - EndGlobalSection -EndGlobal From 0ae9e9ade429c608cd50e91dbbf4b60d8e1cdc0f Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Sun, 26 Jul 2020 21:27:15 -0700 Subject: [PATCH 19/21] More code clean-up --- ...ESTierAsAService.sln => ApiAsAService.sln} | 20 +- .../Api/DynamicApi.cs | 0 .../ApiAsAService/App_Data/NW_Simple.xml | 143 ++++ .../App_Data/NWind.xml | 0 .../App_Data/Northwind.mdf | Bin 8388608 -> 8388608 bytes .../ApiAsAService/App_Data/Trippin.mdf | Bin 0 -> 5570560 bytes .../App_Data/Trippin.xml | 0 .../App_Start/WebApiConfig.cs | 0 .../Controllers/DynamicApiController.cs | 0 .../CustomizedPayloadValueConverter.cs | 0 .../DynamicHelper.cs | 0 .../DynamicODataRoute.cs | 0 .../DynamicRouteConstraint.cs | 0 .../Trippin => ApiAsAService}/Global.asax | 0 .../Trippin => ApiAsAService}/Global.asax.cs | 0 .../Trippin => ApiAsAService}/Helpers.cs | 0 ...crosoft.OData.Service.ApiAsAService.csproj | 46 +- .../Properties/AssemblyInfo.cs | 0 .../Submit/CustomizedSubmitProcessor.cs | 0 .../Web.Debug.config | 0 .../Web.Release.config | 0 .../Trippin => ApiAsAService}/Web.config | 0 .../Trippin => ApiAsAService}/packages.config | 0 ApiAsAService/Trippin/ReadMe.txt | 32 - ApiAsAService/Trippin/Trippin.sln | 22 - .../Trippin/Trippin/Models/Airline.cs | 19 - .../Trippin/Trippin/Models/Airport.cs | 17 - .../Models/CustomizedPayloadValueConverter.cs | 32 - .../Trippin/Trippin/Models/Employee.cs | 14 - ApiAsAService/Trippin/Trippin/Models/Event.cs | 12 - .../Trippin/Trippin/Models/Feature.cs | 13 - .../Trippin/Trippin/Models/Flight.cs | 40 -- .../Trippin/Trippin/Models/Location.cs | 10 - .../Trippin/Trippin/Models/Manager.cs | 14 - .../Trippin/Trippin/Models/NWModel.cs | 180 ----- ApiAsAService/Trippin/Trippin/Models/Order.cs | 23 - .../Trippin/Trippin/Models/Person.cs | 59 -- .../Trippin/Trippin/Models/SpecialOrder.cs | 10 - ApiAsAService/Trippin/Trippin/Models/Trip.cs | 44 -- .../Trippin/Trippin/Models/TrippinModel.cs | 621 ------------------ 40 files changed, 177 insertions(+), 1194 deletions(-) rename ApiAsAService/{RESTierAsAService.sln => ApiAsAService.sln} (95%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Api/DynamicApi.cs (100%) create mode 100644 ApiAsAService/ApiAsAService/App_Data/NW_Simple.xml rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/App_Data/NWind.xml (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/App_Data/Northwind.mdf (99%) create mode 100644 ApiAsAService/ApiAsAService/App_Data/Trippin.mdf rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/App_Data/Trippin.xml (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/App_Start/WebApiConfig.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Controllers/DynamicApiController.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/CustomizedPayloadValueConverter.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/DynamicHelper.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/DynamicODataRoute.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/DynamicRouteConstraint.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Global.asax (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Global.asax.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Helpers.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Microsoft.OData.Service.ApiAsAService.csproj (78%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Properties/AssemblyInfo.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Submit/CustomizedSubmitProcessor.cs (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Web.Debug.config (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Web.Release.config (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/Web.config (100%) rename ApiAsAService/{Trippin/Trippin => ApiAsAService}/packages.config (100%) delete mode 100644 ApiAsAService/Trippin/ReadMe.txt delete mode 100644 ApiAsAService/Trippin/Trippin.sln delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Airline.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Airport.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Employee.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Event.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Feature.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Flight.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Location.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Manager.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/NWModel.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Order.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Person.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/Trip.cs delete mode 100644 ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs diff --git a/ApiAsAService/RESTierAsAService.sln b/ApiAsAService/ApiAsAService.sln similarity index 95% rename from ApiAsAService/RESTierAsAService.sln rename to ApiAsAService/ApiAsAService.sln index 00e1eeb..4b4777b 100644 --- a/ApiAsAService/RESTierAsAService.sln +++ b/ApiAsAService/ApiAsAService.sln @@ -32,10 +32,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI", "WebAPI", "{B43F20 EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.AspNet.OData.Shared", "WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.shproj", "{B6B951B6-C3F0-4B8E-8955-E039145E7DEC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.ApiAsAService", "Trippin\Trippin\Microsoft.OData.Service.ApiAsAService.csproj", "{B379640E-9064-438D-8DA5-6F7B394C2C46}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdmObjectsGenerator", "EdmObjectsGenerator\EdmObjectsGenerator.csproj", "{3678C588-F1DD-4EF6-879A-3D1D8114A880}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiAsAService", "ApiAsAService", "{BB79862F-A592-4A07-997A-36B372686928}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.ApiAsAService", "ApiAsAService\Microsoft.OData.Service.ApiAsAService.csproj", "{B379640E-9064-438D-8DA5-6F7B394C2C46}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution WebAPI\src\Microsoft.AspNet.OData.Shared\Microsoft.AspNet.OData.Shared.projitems*{a6f9775d-f7e2-424e-8363-79644a73038f}*SharedItemsImports = 4 @@ -71,18 +73,18 @@ Global {A6F9775D-F7E2-424E-8363-79644A73038F}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6F9775D-F7E2-424E-8363-79644A73038F}.Release|Any CPU.Build.0 = Release|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.Build.0 = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Debug|Any CPU.Build.0 = Debug|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.ActiveCfg = Release|Any CPU {3678C588-F1DD-4EF6-879A-3D1D8114A880}.Release|Any CPU.Build.0 = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -96,7 +98,7 @@ Global {0E373B2A-2ED2-4566-A275-6BE81CFFE00B} = {37B52FD3-E72B-406F-8C5A-F146256D7743} {A6F9775D-F7E2-424E-8363-79644A73038F} = {B43F200F-B847-48BE-9362-36D94C48521D} {B6B951B6-C3F0-4B8E-8955-E039145E7DEC} = {B43F200F-B847-48BE-9362-36D94C48521D} - {B379640E-9064-438D-8DA5-6F7B394C2C46} = {DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F} + {B379640E-9064-438D-8DA5-6F7B394C2C46} = {BB79862F-A592-4A07-997A-36B372686928} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5A37189C-A5E1-4871-AF65-8EBF2DA60FE3} diff --git a/ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs b/ApiAsAService/ApiAsAService/Api/DynamicApi.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/Api/DynamicApi.cs rename to ApiAsAService/ApiAsAService/Api/DynamicApi.cs diff --git a/ApiAsAService/ApiAsAService/App_Data/NW_Simple.xml b/ApiAsAService/ApiAsAService/App_Data/NW_Simple.xml new file mode 100644 index 0000000..62c348b --- /dev/null +++ b/ApiAsAService/ApiAsAService/App_Data/NW_Simple.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/App_Data/NWind.xml b/ApiAsAService/ApiAsAService/App_Data/NWind.xml similarity index 100% rename from ApiAsAService/Trippin/Trippin/App_Data/NWind.xml rename to ApiAsAService/ApiAsAService/App_Data/NWind.xml diff --git a/ApiAsAService/Trippin/Trippin/App_Data/Northwind.mdf b/ApiAsAService/ApiAsAService/App_Data/Northwind.mdf similarity index 99% rename from ApiAsAService/Trippin/Trippin/App_Data/Northwind.mdf rename to ApiAsAService/ApiAsAService/App_Data/Northwind.mdf index 4cb12e745f5592e92fd14f02f24cec0721cb7239..3f87daca266fcc2eab9a29e60ca355ed0552590a 100644 GIT binary patch delta 1259 zcmZ{iU2GIp7=~x2v)fZx%dW$=3;kicqU@FxN-1rzNM95MD@fG^6^qUyRwzG3g(9Vv z)sVMF0<NBntBg*pZh8O z%jp7V^qA+{V&k^&-SA*okA~)Jnif(RihLXHTsElYi8vd>`g&cqCTh$oTLtyIEKPHB zv*Vg}NnWken=)>lKjUiJ>Eq(!sgdNWBfUS2mF&Czj_dzl%t=J_;ai#IG%Bq~k#EP> zocPY&mScyFq2>vtFdtX%lb;8^$||u6Rp_dHcFJ(x*&m`@-w)&8(owCh@F269%a`$06S9J5J{$amq%9L-D>2wvC>n4Sz3WpuP$Lzl2 z^d0t^3Xj68D0B{c#nBlPYl}|$>2I5j%ekG3p+b2eCPETX5s|~=R!G_-p6POQj_}Gv zQut+$Erc9vX6CJ!^`Q$&BJ=tK9dtfdVG7uAs^$MBZW|Yc# z{USGOPfR7Bag%=>w^YtSVaj7MQK<)Ia<5^@nmt}2N3sLs?hHXW&>%`>OSOpP`v=^9 zNT%az+#a23%3bY7Zf#ep#aLb&oh#aM{k}w8D2fzQ6jK#G#XX8*#WaOq5pWXmdx!p- zy`+3_=ErIiy28CrZr!|!-*XqZF}ZtX?g*{cpnu1;wPiBdw*0fdFWvaYx&5nic9GSk zI~NvOhOVEK-`80i?iAOpUR2&(V3p|2GEi&H`cJw* zcj_Cgc8~dUsJ@i2i0MR-C?V8mh?qglB+7_#qJpR-W)Weciii;R5wnT=i3f-|M3jgT z)x=yPPCQ7=Bjyt|!~&w0SV+_ni->xnfoLQa6Auwh#1i6RVkyx~fLKO6LOe<=Cmtgn zCsq(Ei6@8_;z?o^v6^^_SVOcDPZQ4&&k_k@EwPSxj(DDUfmlzxNVE|fh?j_$37bd~ z8;MtlO~k82JMkK^nb<;f5L=0D#CBo_(MjwiUMJol-XwMryNNDh5AhbUm*^(;5k17) KMDM5AfA(J(mVfmC delta 1112 zcmaKqTWAzl7{_NOvzwD-qtS7@s~h7cP0ek)Mq_SX|K3uo(WLEaHT5!H8a43}HD01o z7m4QCy`ocJl6u&?`^_^|nIs_{Ua4-bX)@?cccv>z3C|e8sG0?xYu?Yi9 z&Ls9ccro~YIVV=4kG@TI^F)>Ypva6HE&Y$&?j1X9j4T~j>N9yYe^=9UJEho$FIUfX z4s;lI{Nw7a3F@Ygc|@0S z-)_@6o@H)KMpw#$8cWD%y(OGNkJ*vR_g(Uu3Xj68$aXGy#ntKK?X*vL&!3|kUZ)Qy zMzUqB(;Jj17bUu1-u>GO$?!i`fgCFn-W<0(7r%VkFG8}b)0;}jhvh;j{jXO_|L<2% zTF+LhOGr+s5RH0BCiWWv866PQlH1ANieyzXI(AW*@|y}#q+4=Fk73E`6J8;IO9lRQ z%Pl!rF9LE+rRprbzTmF?G7(X6do0hC-R(xYqbHT(WkwsT5?j*izF0&kaum6WJcUn@ zub8BmtSC@Sabl6FBd<#57uE%?s1vwX$-QarFY+p$r!QgKwYm4xpU!Ia%j$X7&$749 zy6p@tu-ZLN>)t#&%RJ?OXc6j}M)-*jhyW2JrV}%W5K&0XB#MYxM3^WhN{A1MQsN_G zHc>{D6BR@yQAI?EkBKNTho~mz67z@}Vm?tz)DiVW1JOtxmYkmH3SKocMx>5gUk&#FxY-Vl&Z3d_`;_wi4Tj z?SxJ2Aa)YFh~323L_4vE*h}mqI*3kUKXHIKNE{-%h{MDY;waHg93zesCy0|o4{?e( OP4p6Hh_m0~-0*w)O+irr diff --git a/ApiAsAService/ApiAsAService/App_Data/Trippin.mdf b/ApiAsAService/ApiAsAService/App_Data/Trippin.mdf new file mode 100644 index 0000000000000000000000000000000000000000..48a77b4b50ac274cc901eaa86b92e05a5453cb13 GIT binary patch literal 5570560 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

    F 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

    - /// The customized payload value converter for test only - /// - public class CustomizedPayloadValueConverter : ODataPayloadValueConverter - { - public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference) - { - if (edmTypeReference != null) - { - if (value is string) - { - var stringValue = (string) value; - - // Make People(1)/FirstName converted - if (stringValue == "Russell") - { - return stringValue + "Converter"; - } - } - } - - return base.ConvertToPayloadValue(value, edmTypeReference); - } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/Employee.cs b/ApiAsAService/Trippin/Trippin/Models/Employee.cs deleted file mode 100644 index ec7fed7..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Employee.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Employee : Person - { - public virtual ICollection Peers { get; set; } - - public long Cost { get; set; } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/Event.cs b/ApiAsAService/Trippin/Trippin/Models/Event.cs deleted file mode 100644 index a3dacf9..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Event.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Event - { - public int Id { get; set; } - public Location OccursAt { get; set; } - public string Description { get; set; } - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Models/Feature.cs b/ApiAsAService/Trippin/Trippin/Models/Feature.cs deleted file mode 100644 index 17a4ecd..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Feature.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public enum Feature - { - Feature1, - Feature2, - Feature3, - Feature4 - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Models/Flight.cs b/ApiAsAService/Trippin/Trippin/Models/Flight.cs deleted file mode 100644 index 2d460cb..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Flight.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Flight - { - public int FlightId { get; set; } - - public string ConfirmationCode { get; set; } - - public DateTimeOffset StartsAt { get; set; } - - public DateTimeOffset EndsAt { get; set; } - - public TimeSpan Duration { get; set; } - - public string SeatNumber { get; set; } - - public string FlightNumber { get; set; } - - public string FromId { get; set; } - - public string ToId { get; set; } - - public string AirlineId { get; set; } - - [ForeignKey("FromId")] - public virtual Airport From { get; set; } - - [ForeignKey("ToId")] - public virtual Airport To { get; set; } - - [ForeignKey("AirlineId")] - public virtual Airline Airline { get; set; } - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Models/Location.cs b/ApiAsAService/Trippin/Trippin/Models/Location.cs deleted file mode 100644 index 5454e49..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Location.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Location - { - public string Address { get; set; } - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Models/Manager.cs b/ApiAsAService/Trippin/Trippin/Models/Manager.cs deleted file mode 100644 index 8f40ab8..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Manager.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Manager : Person - { - public virtual ICollection DirectReports { get; set; } - - public long Budget { get; set; } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/NWModel.cs b/ApiAsAService/Trippin/Trippin/Models/NWModel.cs deleted file mode 100644 index ea5a691..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/NWModel.cs +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel.DataAnnotations; -using System.Data.Entity; - -namespace ODataDemo -{ - public class NWModel : DbContext - { - static NWModel() - { - Database.SetInitializer(new NwDatabaseInitializer()); - } - - public NWModel() - : base("name=NWModel") - { - } - - protected override void OnModelCreating(DbModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - } - - public DbSet Products { get; set; } - public DbSet ProductDetails { get; set; } - public DbSet Categories { get; set; } - public DbSet Suppliers { get; set; } - public DbSet Persons { get; set; } - - private static NWModel instance; - public static NWModel Instance - { - get - { - if (instance == null) - { - ResetDataSource(); - } - return instance; - } - } - - public static void ResetDataSource() - { - instance = new NWModel(); - - #region Products - - var products = new List - { - new Product - { - ProductID=1, - Name = "widget", - }, - - new Product - { - ProductID=2, - Name = "Gadget", - }, - }; - - instance.Products.AddRange(products); - - #endregion - - - instance.Persons.AddRange(new List - { - new Person - { - ID = 1, - Name = "Willie", - }, - new Person - { - ID = 2, - Name = "Vincent", - }, - }); - - instance.SaveChanges(); - } - } - - class NwDatabaseInitializer : DropCreateDatabaseAlways - { - protected override void Seed(NWModel context) - { - NWModel.ResetDataSource(); - } - } - - public class Product - { - [Key] - public Int32 ProductID; - public string Name; - public string Description; - public DateTimeOffset ReleaseDate; - public DateTimeOffset DiscontinuedDate; - public Int16 Price; - public virtual ICollection Categories { get; set; } - public virtual Supplier Supplier { get; set; } - public virtual ProductDetail ProductDetail { get; set; } - } - - public class ProductDetail - { - [Key] - public Int32 ProductID; - public string Details; - public virtual Product Product { get; set; } - } - - public class Category - { - [Key] - public Int32 ID; - public string Name; - public virtual ICollection Products {get; set;} - } - - public class Supplier - { - [Key] - public Int32 Id; - public string Name; - public Address Address; - public Int32 Concurrency; - public virtual ICollection Products { get; set; } - } - - public class Address - { - public string Street; - public string City; - public string State; - public string ZipCode; - public string Country; - } - - public class Person - { - [Key] - public Int32 ID; - public string Name; - public virtual PersonDetail PersonDetail { get; set; } - } - - public class Customer : Person - { - public Decimal TotalExpense; - } - - public class Employee : Person - { - [Key] - public Int64 EmployeeId; - public DateTimeOffset HireDate; - public Single Salary; - } - - public class PersonDetail - { - [Key] - public Int32 PersonId; - public int Age; - public bool Gender; - public string Phone; - public Address Address; - public virtual Person Person { get; set; } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/Order.cs b/ApiAsAService/Trippin/Trippin/Models/Order.cs deleted file mode 100644 index 11acda0..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Order.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Order - { - [Key] - [Column(Order = 0)] - public int PersonId { get; set; } - - [Key] - [Column(Order = 1)] - public int OrderId { get; set; } - - public double Price { get; set; } - - public string Description { get; set; } - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Models/Person.cs b/ApiAsAService/Trippin/Trippin/Models/Person.cs deleted file mode 100644 index 6cab67b..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Person.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Person - { - public virtual Person BestFriend { get; set; } - - public virtual ICollection Friends { get; set; } - - public virtual ICollection Trips { get; set; } - - public long PersonId { get; set; } - - public string UserName { get; set; } - - [Required] - public string FirstName { get; set; } - - [MaxLength(26), MinLength(1)] - public string LastName { get; set; } - - public long? Age { get; set; } - - public long Concurrency { get; set; } - - [Column(TypeName = "Date")] - public DateTime BirthDate { get; set; } - - [Column(TypeName = "Date")] - public DateTime? BirthDate2 { get; set; } - - [Column(TypeName = "Time")] - public TimeSpan BirthTime { get; set; } - - [Column(TypeName = "Time")] - public TimeSpan? BirthTime2 { get; set; } - - // Notes: - // 1) System.DateTime is mapped to Edm.DateTimeOffset by default; - // 2) The range of SqlDateTime is limited (1753-01-01 ~ 9999-12-31); - // so use SqlDateTime2 for wider range (0001-01-01 ~ 9999-12-31). - [Column(TypeName = "DateTime2")] - public DateTime BirthDateTime { get; set; } - - [Column(TypeName = "DateTime2")] - public DateTime? BirthDateTime2 { get; set; } - - public Feature FavoriteFeature { get; set; } - - public Feature? FavoriteFeature2 { get; set; } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs b/ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs deleted file mode 100644 index 8467832..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/SpecialOrder.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class SpecialOrder : Order - { - public string Tag { get; set; } - } -} \ No newline at end of file diff --git a/ApiAsAService/Trippin/Trippin/Models/Trip.cs b/ApiAsAService/Trippin/Trippin/Models/Trip.cs deleted file mode 100644 index 6e5d3a4..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Trip.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - [Table("TripsTable")] - public class Trip - { - public int TripId { get; set; } - - [DatabaseGenerated(DatabaseGeneratedOption.Computed)] - public Guid? TrackGuid { get; set; } - - public long? PersonId { get; set; } - - public Guid ShareId { get; set; } - - public string Name { get; set; } - - [Column("BudgetCol", Order = 1)] - public float Budget { get; set; } - - public string Description { get; set; } - - public DateTimeOffset StartsAt { get; set; } - - public DateTimeOffset EndsAt { get; set; } - - [NotMapped] - public TimeSpan TotalTimespan { get { return this.EndsAt - this.StartsAt; } } - - public virtual ICollection Flights { get; set; } - - [ConcurrencyCheck] - public DateTimeOffset LastUpdated { get; set; } - - public virtual ICollection Events { get; set; } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs b/ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs deleted file mode 100644 index 5d5a948..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/TrippinModel.cs +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Data.Entity; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class TrippinModel : DbContext - { - static TrippinModel() - { - Database.SetInitializer(new TrippinDatabaseInitializer()); - } - - public TrippinModel() - : base("name=TrippinModel") - { - } - - protected override void OnModelCreating(DbModelBuilder modelBuilder) - { - modelBuilder.Entity().HasMany(s => s.Flights).WithMany().Map(c => - { - c.MapLeftKey("TripId"); - c.MapRightKey("FlightId"); - c.ToTable("TripAndFlights"); - }); - - modelBuilder.Entity().HasMany(p => p.Friends).WithMany().Map( - c => - { - c.MapLeftKey("Friend1Id"); - c.MapRightKey("Friend2Id"); - c.ToTable("PersonFriends"); - } - ); - - base.OnModelCreating(modelBuilder); - } - - public DbSet People { get; set; } - public DbSet Orders { get; set; } - public DbSet Airports { get; set; } - public DbSet Airlines { get; set; } - public DbSet Flights { get; set; } - public DbSet Trips { get; set; } - public DbSet Events { get; set; } - - private static TrippinModel instance; - public static TrippinModel Instance - { - get - { - if (instance == null) - { - ResetDataSource(); - } - return instance; - } - } - - public static void ResetDataSource() - { - instance = new TrippinModel(); - - // As per kb321843, manually handle dropping Friends constraint before cleaning up People table. - instance.Database.ExecuteSqlCommand("DELETE FROM PersonFriends"); - - // Discard all local changes, and reload data from DB, them remove all - foreach (var x in instance.People) - { - // Discard local changes for the person.. - instance.Entry(x).State = EntityState.Detached; - } - - instance.People.RemoveRange(instance.People); - instance.Orders.RemoveRange(instance.Orders); - instance.Flights.RemoveRange(instance.Flights); - instance.Airlines.RemoveRange(instance.Airlines); - instance.Airports.RemoveRange(instance.Airports); - instance.Trips.RemoveRange(instance.Trips); - instance.Events.RemoveRange(instance.Events); - - // This is to set the People Id from 0 - instance.Database.ExecuteSqlCommand("DBCC CHECKIDENT ('People', RESEED, 0)"); - instance.Database.ExecuteSqlCommand("DBCC CHECKIDENT ('Flights', RESEED, 0)"); - instance.Database.ExecuteSqlCommand("DBCC CHECKIDENT ('TripsTable', RESEED, 0)"); - - instance.SaveChanges(); - - #region Airports - - var airports = new List - { - new Airport - { - Name = "San Francisco International Airport", - IataCode = "SFO", - IcaoCode = "KSFO" - }, - new Airport - { - Name = "Los Angeles International Airport", - IataCode = "LAX", - IcaoCode = "KLAX" - }, - new Airport - { - Name = "Shanghai Hongqiao International Airport", - IataCode = "SHA", - IcaoCode = "ZSSS" - }, - new Airport - { - Name = "Beijing Capital International Airport", - IataCode = "PEK", - IcaoCode = "ZBAA" - }, - new Airport - { - Name = "John F. Kennedy International Airport", - IataCode = "JFK", - IcaoCode = "KJFK" - }, - new Airport - { - Name = "Rome Ciampino Airport", - IataCode = "CIA", - IcaoCode = "LIRA" - }, - new Airport - { - Name = "Toronto Pearson International Airport", - IataCode = "YYZ", - IcaoCode = "CYYZ" - }, - new Airport - { - Name = "Sydney Airport", - IataCode = "SYD", - IcaoCode = "YSSY" - }, - new Airport - { - Name = "Istanbul Ataturk Airport", - IataCode = "IST", - IcaoCode = "LTBA" - }, - new Airport - { - Name = "Singapore Changi Airport", - IataCode = "SIN", - IcaoCode = "WSSS" - }, - new Airport - { - Name = "Abu Dhabi International Airport", - IataCode = "AUH", - IcaoCode = "OMAA" - }, - new Airport - { - Name = "Guangzhou Baiyun International Airport", - IataCode = "CAN", - IcaoCode = "ZGGG" - }, - new Airport - { - Name = "O'Hare International Airport", - IataCode = "ORD", - IcaoCode = "KORD" - }, - new Airport - { - Name = "Hartsfield-Jackson Atlanta International Airport", - IataCode = "ATL", - IcaoCode = "KATL" - }, - new Airport - { - Name = "Seattle-Tacoma International Airport", - IataCode = "SEA", - IcaoCode = "KSEA" - } - }; - instance.Airports.AddRange(airports); - - #endregion - - #region Airlines - - var airlines = new List - { - new Airline - { - Name = "American Airlines", - AirlineCode = "AA" - }, - - new Airline - { - Name = "Shanghai Airline", - AirlineCode = "FM" - }, - - new Airline - { - Name = "China Eastern Airlines", - AirlineCode = "MU" - }, - - new Airline - { - Name = "Air France", - AirlineCode = "AF" - }, - - new Airline - { - Name = "Alitalia", - AirlineCode = "AZ" - }, - - new Airline - { - Name = "Air Canada", - AirlineCode = "AC" - }, - - new Airline - { - Name = "Austrian Airlines", - AirlineCode = "OS" - }, - - new Airline - { - Name = "Turkish Airlines", - AirlineCode = "TK" - }, - - new Airline - { - Name = "Japan Airlines", - AirlineCode = "JL" - }, - - new Airline - { - Name = "Singapore Airlines", - AirlineCode = "SQ" - }, - - new Airline - { - Name = "Korean Air", - AirlineCode = "KE" - }, - - new Airline - { - Name = "China Southern", - AirlineCode = "CZ" - }, - - new Airline - { - Name = "AirAsia", - AirlineCode = "AK" - }, - - new Airline - { - Name = "Hong Kong Airlines", - AirlineCode = "HX" - }, - - new Airline - { - Name = "Emirates", - AirlineCode = "EK" - } - }; - instance.Airlines.AddRange(airlines); - - #endregion - - #region People - - #region Friends russellwhyte & scottketchum & ronaldmundy - var person1 = new Person - { - PersonId = 1, - FirstName = "Russell", - LastName = "Whyte", - UserName = "russellwhyte", - BirthDate = new DateTime(1980, 10, 15), - BirthTime = new TimeSpan(2, 3, 4), - BirthDateTime = new DateTime(1980, 10, 15, 2, 3, 4), - FavoriteFeature = Feature.Feature1, - }; - - var person2 = new Person - { - PersonId = 2, - FirstName = "Scott", - LastName = "Ketchum", - UserName = "scottketchum", - BirthDate = new DateTime(1983, 11, 12), - BirthTime = new TimeSpan(1, 2, 3), - BirthDateTime = new DateTime(1983, 11, 12, 1, 2, 3), - FavoriteFeature = Feature.Feature2, - Friends = new Collection { person1 } - }; - - var person3 = new Person - { - PersonId = 3, - FirstName = "Ronald", - LastName = "Mundy", - UserName = "ronaldmundy", - BirthDate = new DateTime(1984, 12, 11), - BirthTime = new TimeSpan(0, 1, 2), - BirthDateTime = new DateTime(1984, 12, 11, 0, 1, 2), - FavoriteFeature = Feature.Feature3, - Friends = new Collection { person1, person2 } - }; - - var person4 = new Person - { - PersonId = 4, - FirstName = "Javier", - UserName = "javieralfred", - BirthDate = new DateTime(1985, 1, 10), - BirthTime = new TimeSpan(23, 59, 1), - BirthDateTime = new DateTime(1985, 1, 10, 23, 59, 1), - FavoriteFeature = Feature.Feature4, - BirthDate2 = new DateTime(1985, 1, 10), - BirthTime2 = new TimeSpan(23, 59, 1), - BirthDateTime2 = new DateTime(1985, 1, 10, 23, 59, 1), - FavoriteFeature2 = Feature.Feature4, - }; - - person1.Friends = new Collection { person2 }; - #endregion - - instance.People.AddRange(new List - { - person1, - person2, - person3, - person4, - - new Person - { - PersonId = 5, - FirstName = "Willie", - LastName = "Ashmore", - UserName = "willieashmore", - BirthDate = new DateTime(1986, 2, 9), - BirthTime = new TimeSpan(22, 58, 2), - BirthDateTime = new DateTime(1986, 2, 9, 22, 58, 2), - FavoriteFeature = Feature.Feature1, - BestFriend = person4, - Friends = new Collection() - }, - new Person - { - PersonId = 6, - FirstName = "Vincent", - LastName = "Calabrese", - UserName = "vincentcalabrese", - BirthDate = new DateTime(1987, 3, 8), - BirthTime = new TimeSpan(21, 57, 3), - BirthDateTime = new DateTime(1987, 3, 8, 21, 57, 3), - FavoriteFeature = Feature.Feature2, - BestFriend = person4, - Friends = new Collection() - }, - new Person - { - PersonId = 7, - FirstName = "Clyde", - LastName = "Guess", - UserName = "clydeguess", - BirthDate = new DateTime(1988, 4, 7), - BirthTime = new TimeSpan(20, 56, 4), - BirthDateTime = new DateTime(1988, 4, 7, 20, 56, 4), - FavoriteFeature = Feature.Feature3, - }, - new Person - { - PersonId = 8, - FirstName = "Keith", - LastName = "Pinckney", - UserName = "keithpinckney", - BirthDate = new DateTime(1989, 5, 6), - BirthTime = new TimeSpan(19, 55, 5), - BirthDateTime = new DateTime(1989, 5, 6, 19, 55, 5), - FavoriteFeature = Feature.Feature4, - }, - new Person - { - PersonId = 9, - FirstName = "Marshall", - LastName = "Garay", - UserName = "marshallgaray", - BirthDate = new DateTime(1990, 6, 5), - BirthTime = new TimeSpan(18, 54, 6), - BirthDateTime = new DateTime(1990, 6, 5, 18, 54, 6), - FavoriteFeature = Feature.Feature1, - }, - new Person - { - PersonId = 10, - FirstName = "Ryan", - LastName = "Theriault", - UserName = "ryantheriault", - BirthDate = new DateTime(1991, 7, 4), - BirthTime = new TimeSpan(17, 53, 7), - BirthDateTime = new DateTime(1991, 7, 4, 17, 53, 7), - FavoriteFeature = Feature.Feature2, - }, - new Person - { - PersonId = 11, - FirstName = "Elaine", - LastName = "Stewart", - UserName = "elainestewart", - BirthDate = new DateTime(1992, 8, 3), - BirthTime = new TimeSpan(16, 52, 8), - BirthDateTime = new DateTime(1992, 8, 3, 16, 52, 8), - FavoriteFeature = Feature.Feature3, - }, - new Person - { - PersonId = 12, - FirstName = "Sallie", - LastName = "Sampson", - UserName = "salliesampson", - BirthDate = new DateTime(1993, 9, 2), - BirthTime = new TimeSpan(15, 51, 9), - BirthDateTime = new DateTime(1993, 9, 2, 15, 51, 9), - FavoriteFeature = Feature.Feature4, - }, - new Person - { - PersonId = 13, - FirstName = "Joni", - LastName = "Rosales", - UserName = "jonirosales", - BirthDate = new DateTime(1994, 10, 1), - BirthTime = new TimeSpan(14, 50, 10), - BirthDateTime = new DateTime(1994, 10, 1, 14, 50, 10), - FavoriteFeature = Feature.Feature1, - } - }); - - #endregion - - #region Orders - - var orders = new List - { - new Order - { - PersonId = 1, - OrderId = 1, - Description = "Person 1 Order 1", - Price = 200 - }, - new Order - { - PersonId = 1, - OrderId = 2, - Description = "Person 1 Order 2", - Price = 400 - }, - new Order - { - PersonId = 2, - OrderId = 1, - Description = "Person 2 Order 1", - Price = 600 - }, - new Order - { - PersonId = 2, - OrderId = 2, - Description = "Person 2 Order 2", - Price = 800 - }, - }; - instance.Orders.AddRange(orders); - - #endregion - - #region Flights - - var flights = new List - { - new Flight - { - ConfirmationCode = "JH58493", - FlightNumber = "AA26", - StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 6, 15, 0)), - EndsAt = new DateTimeOffset(new DateTime(2014, 1, 1, 11, 35, 0)), - AirlineId = airlines[0].AirlineCode, - FromId = airports[12].IcaoCode, - ToId = airports[4].IcaoCode - }, - new Flight - { - ConfirmationCode = "JH38143", - FlightNumber = "AA4035", - StartsAt = new DateTimeOffset(new DateTime(2014, 1, 4, 17, 55, 0)), - EndsAt = new DateTimeOffset(new DateTime(2014, 1, 4, 20, 45, 0)), - AirlineId = airlines[0].AirlineCode, - FromId = airports[4].IcaoCode, - ToId = airports[12].IcaoCode - }, - new Flight - { - ConfirmationCode = "JH58494", - FlightNumber = "FM1930", - StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 8, 0, 0)), - EndsAt = new DateTimeOffset(new DateTime(2014, 2, 1, 9, 20, 0)), - AirlineId = airlines[1].AirlineCode, - SeatNumber = "B11", - FromId = airports[2].IcaoCode, - ToId = airports[3].IcaoCode - }, - new Flight - { - ConfirmationCode = "JH58495", - FlightNumber = "MU1930", - StartsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 15, 00, 0)), - EndsAt = new DateTimeOffset(new DateTime(2014, 2, 10, 16, 30, 0)), - AirlineId = airlines[2].AirlineCode, - SeatNumber = "A32", - FromId = airports[3].IcaoCode, - ToId = airports[2].IcaoCode - }, - }; - flights.ForEach(f => f.Duration = f.EndsAt - f.StartsAt); - instance.Flights.AddRange(flights); - - #endregion - - #region Trips - - var trips = new List - { - new Trip - { - PersonId = 1, - Name = "Trip in Beijing", - Budget = 2000.0f, - ShareId = new Guid("f94e9116-8bdd-4dac-ab61-08438d0d9a71"), - Description = "Trip from Shanghai to Beijing", - StartsAt = new DateTimeOffset(new DateTime(2014, 2, 1)), - EndsAt = new DateTimeOffset(new DateTime(2014, 2, 4)), - Flights = new List(){flights[0], flights[1]}, - LastUpdated = DateTime.UtcNow, - }, - new Trip - { - PersonId = 1, - ShareId = new Guid("9d9b2fa0-efbf-490e-a5e3-bac8f7d47354"), - Name = "Trip in US", - Budget = 3000.0f, - Description = "Trip from San Francisco to New York City. It is a 4 days' trip.", - StartsAt = new DateTimeOffset(new DateTime(2014, 1, 1)), - EndsAt = new DateTimeOffset(new DateTime(2014, 1, 4)), - LastUpdated = DateTime.UtcNow, - }, - new Trip - { - PersonId = 2, - ShareId = new Guid("ccbda221-8a58-4ab8-b232-e47e3391bfc2"), - Name = "Honeymoon", - Budget = 2650.0f, - Description = "Happy honeymoon trip", - StartsAt = new DateTime(2014, 2, 1), - EndsAt = new DateTime(2014, 2, 4), - LastUpdated = DateTime.UtcNow, - } - }; - instance.Trips.AddRange(trips); - - #endregion - - #region Events - - instance.Events.AddRange(new[] - { - new Event - { - OccursAt = new Location - { - Address = "Address1" - } - }, - }); - #endregion - - instance.SaveChanges(); - } - } - - class TrippinDatabaseInitializer : DropCreateDatabaseAlways - { - protected override void Seed(TrippinModel context) - { - TrippinModel.ResetDataSource(); - } - } -} From 719416ef652e85ca8ea50ae4442e2a4ebf4f7eb0 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Mon, 27 Jul 2020 22:35:41 -0700 Subject: [PATCH 20/21] Minor cleanup --- ApiAsAService/ApiAsAService.sln | 2 - .../App_Data/{NW_Simple.xml => NWSimple.xml} | 56 +++++++++---------- .../ApiAsAService/App_Data/NWind.xml | 46 +++++++-------- .../ApiAsAService/App_Data/Trippin.xml | 52 ++++++++--------- .../ApiAsAService/App_Start/WebApiConfig.cs | 10 ---- .../Controllers/DynamicApiController.cs | 11 ---- ApiAsAService/ApiAsAService/DynamicHelper.cs | 47 ++-------------- .../ApiAsAService/DynamicRouteConstraint.cs | 18 +----- ...crosoft.OData.Service.ApiAsAService.csproj | 2 +- ApiAsAService/ApiAsAService/Web.config | 2 +- .../EdmObjectsGenerator/DbContextGenerator.cs | 52 ++++++++--------- .../Microsoft.Restier.AspNet/ApiFactory.cs | 10 +--- 12 files changed, 110 insertions(+), 198 deletions(-) rename ApiAsAService/ApiAsAService/App_Data/{NW_Simple.xml => NWSimple.xml} (71%) diff --git a/ApiAsAService/ApiAsAService.sln b/ApiAsAService/ApiAsAService.sln index 4b4777b..3ee16ae 100644 --- a/ApiAsAService/ApiAsAService.sln +++ b/ApiAsAService/ApiAsAService.sln @@ -22,8 +22,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{DB42E0B8-C0C7-4DE4-9437-2B2A229B5F8F}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RESTier", "RESTier", "{76B4E51F-233E-4DD3-AABF-A6F47788040D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData", "WebAPI\src\Microsoft.AspNet.OData\Microsoft.AspNet.OData.csproj", "{A6F9775D-F7E2-424E-8363-79644A73038F}" diff --git a/ApiAsAService/ApiAsAService/App_Data/NW_Simple.xml b/ApiAsAService/ApiAsAService/App_Data/NWSimple.xml similarity index 71% rename from ApiAsAService/ApiAsAService/App_Data/NW_Simple.xml rename to ApiAsAService/ApiAsAService/App_Data/NWSimple.xml index 62c348b..4ec736e 100644 --- a/ApiAsAService/ApiAsAService/App_Data/NW_Simple.xml +++ b/ApiAsAService/ApiAsAService/App_Data/NWSimple.xml @@ -1,7 +1,7 @@  - + @@ -13,12 +13,12 @@ - - - + + + - - + + @@ -26,7 +26,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -42,10 +42,10 @@ - + - + @@ -60,12 +60,12 @@ - + - + - + @@ -78,9 +78,9 @@ - + - + @@ -89,44 +89,44 @@ - + - - + + - + - + - + - + - + - + - + - + - + - + diff --git a/ApiAsAService/ApiAsAService/App_Data/NWind.xml b/ApiAsAService/ApiAsAService/App_Data/NWind.xml index 00ad62b..9723190 100644 --- a/ApiAsAService/ApiAsAService/App_Data/NWind.xml +++ b/ApiAsAService/ApiAsAService/App_Data/NWind.xml @@ -1,7 +1,7 @@  - + @@ -13,9 +13,9 @@ - - - + + + @@ -23,7 +23,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -39,9 +39,9 @@ - + - + @@ -56,12 +56,12 @@ - + - + - + @@ -74,41 +74,41 @@ - - + + - + - + - + - + - + - + - + - + - + - + diff --git a/ApiAsAService/ApiAsAService/App_Data/Trippin.xml b/ApiAsAService/ApiAsAService/App_Data/Trippin.xml index 3ad3328..f8a86e0 100644 --- a/ApiAsAService/ApiAsAService/App_Data/Trippin.xml +++ b/ApiAsAService/ApiAsAService/App_Data/Trippin.xml @@ -1,7 +1,7 @@  - + @@ -23,7 +23,7 @@ - + @@ -40,13 +40,13 @@ - + - + - + @@ -76,13 +76,13 @@ - - - + + + - - + + @@ -98,22 +98,22 @@ - - + + - + - + - + - + - + @@ -122,29 +122,29 @@ - + TimeStampValue - - - + + + - - + + - - + + - + diff --git a/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs b/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs index 4b82f04..f9fecec 100644 --- a/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs +++ b/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs @@ -22,16 +22,6 @@ public static async void RegisterService( { // enable query options for all properties config.Filter().Expand().Select().OrderBy().MaxTop(null).Count(); - //await config.MapRestierRoute>( - // "ApiAsAService", "", - // new RestierBatchHandler(server)); - - //ODataRoute route = await DynamicHelper.MapDynamicRoute( - // typeof(Models.TrippinModel), - // config, - // RouteName, - // "", - // null); //config.Routes.Add(RouteName, route); ODataRoute route = await DynamicHelper.MapRestierRoute(config, RouteName, null); diff --git a/ApiAsAService/ApiAsAService/Controllers/DynamicApiController.cs b/ApiAsAService/ApiAsAService/Controllers/DynamicApiController.cs index fdded4a..10b7c80 100644 --- a/ApiAsAService/ApiAsAService/Controllers/DynamicApiController.cs +++ b/ApiAsAService/ApiAsAService/Controllers/DynamicApiController.cs @@ -2,22 +2,12 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.AspNet.OData; using Microsoft.AspNet.OData.Extensions; -using Microsoft.AspNet.OData.Routing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData.Edm; -using Microsoft.OData.Service.ApiAsAService.Api; using Microsoft.Restier.AspNet; -using Microsoft.Restier.Core; -using Microsoft.Restier.Core.Model; namespace Microsoft.OData.Service.ApiAsAService.Controllers { @@ -28,7 +18,6 @@ public async Task Get(CancellationToken cancellationToken) IServiceProvider serviceProvider = Request.GetRequestContainer(); ApiFactory factory = Request.GetRequestContainer().GetRequiredService(); - //factory.ModelType = dynamicType; base.SetApi(factory.GetApiBase()); return await base.GetResponse(cancellationToken); diff --git a/ApiAsAService/ApiAsAService/DynamicHelper.cs b/ApiAsAService/ApiAsAService/DynamicHelper.cs index 1a2cdbb..3ff8d61 100644 --- a/ApiAsAService/ApiAsAService/DynamicHelper.cs +++ b/ApiAsAService/ApiAsAService/DynamicHelper.cs @@ -4,7 +4,6 @@ using Microsoft.AspNet.OData.Routing; using Microsoft.AspNet.OData.Routing.Conventions; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OData.Service.ApiAsAService.Api; using Microsoft.Restier.AspNet; using Microsoft.Restier.AspNet.Batch; using Microsoft.Restier.Core; @@ -25,52 +24,18 @@ namespace Microsoft.OData.Service.ApiAsAService { public static class DynamicHelper { - private const string MapRestierRouteMethod = "MapRestierRoute"; - // private const string RestierAssembly = "Microsoft.Restier.AspNet"; - private const string httpConfigurationExtensionsType = "System.Web.Http.HttpConfigurationExtensions"; - // private const string ApiAsAServiceAssembly = "Microsoft.OData.Service.ApiAsAService"; + //private const string MapRestierRouteMethod = "MapRestierRoute"; + //private const string httpConfigurationExtensionsType = "System.Web.Http.HttpConfigurationExtensions"; private const string DynamicApiType = "Microsoft.OData.Service.ApiAsAService.Api.DynamicApi`1"; private const string DynamicHelperType = "Microsoft.OData.Service.ApiAsAService.DynamicHelper"; - private static Type dynamicApiType = Assembly.GetExecutingAssembly().GetType(DynamicApiType); - private static Type dynamicHelperType = Assembly.GetExecutingAssembly().GetType(DynamicHelperType); + //private static Type dynamicApiType = Assembly.GetExecutingAssembly().GetType(DynamicApiType); + //private static Type dynamicHelperType = Assembly.GetExecutingAssembly().GetType(DynamicHelperType); - //public static Task MapDynamicRoute(Type tApi, HttpConfiguration config, string routeName, - // string routePrefix, RestierBatchHandler batchHandler) + //public static ApiBase CreateEntifyFrameworkApi(Type tApi, IServiceProvider serviceProvider) //{ - //Type restierType = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == RestierAssembly).GetType(httpConfigurationExtensionsType); - //Type dynamicType = dynamicApiType.MakeGenericType(tApi); - // MethodInfo method = restierType.GetMethod( - // MapRestierRouteMethod, - // BindingFlags.Static | BindingFlags.Public, - // null, - // new[] { typeof(HttpConfiguration), typeof(string), typeof(string), typeof(RestierBatchHandler) }, - // null); - // return (Task)method.MakeGenericMethod(dynamicType).Invoke(null, new object[] { config, routeName, routePrefix, batchHandler }); + // return (ApiBase)typeof(EntityFrameworkApi<>).MakeGenericType(tApi).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); //} - //public static Task MapDynamicRoute(Type tApi, HttpConfiguration config, string routeName, - // string routePrefix, RestierBatchHandler batchHandler) - //{ - // Type dynamicType = dynamicApiType.MakeGenericType(tApi); - // MethodInfo method = dynamicHelperType.GetMethod( - // MapRestierRouteMethod, - // BindingFlags.Static | BindingFlags.Public, - // null, - // new[] { typeof(HttpConfiguration), typeof(string), typeof(string), typeof(RestierBatchHandler) }, - // null); - // return (Task)method.MakeGenericMethod(dynamicType).Invoke(null, new object[] { config, routeName, routePrefix, batchHandler }); - //} - - //public static ApiBase CreateDynamicApi(Type tApi, IServiceProvider serviceProvider) - //{ - // return (ApiBase)dynamicApiType.MakeGenericType(tApi).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); - //} - - public static ApiBase CreateEntifyFrameworkApi(Type tApi, IServiceProvider serviceProvider) - { - return (ApiBase)typeof(EntityFrameworkApi<>).MakeGenericType(tApi).GetConstructor(new Type[] { typeof(IServiceProvider) }).Invoke(new object[] { serviceProvider }); - } - /// /// Maps the API routes to the RestierController. /// diff --git a/ApiAsAService/ApiAsAService/DynamicRouteConstraint.cs b/ApiAsAService/ApiAsAService/DynamicRouteConstraint.cs index 7542f98..08de5d6 100644 --- a/ApiAsAService/ApiAsAService/DynamicRouteConstraint.cs +++ b/ApiAsAService/ApiAsAService/DynamicRouteConstraint.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Net; using System.Net.Http; using System.Web.Http.Routing; using Microsoft.AspNet.OData.Extensions; @@ -12,7 +11,6 @@ using Microsoft.OData; using Microsoft.OData.Service.ApiAsAService.Api; using Microsoft.Restier.AspNet; -using Microsoft.Restier.Core; using Microsoft.Restier.Core.Model; namespace Microsoft.OData.Service.ApiAsAService @@ -53,25 +51,11 @@ public override bool Match(HttpRequestMessage request, IHttpRoute route, string string dataSourceName = segments[0]; IServiceProvider serviceProvider = request.CreateRequestContainer(this.RouteName); -#if false - Type dynamicType; - switch (dataSourceName) - { - case "Trippin": - dynamicType = typeof(Models.TrippinModel); - break; - case "NWind": - dynamicType = typeof(ODataDemo.NWModel); - break; - default: - throw new Exception("Service not found"); //return request.CreateErrorResponse(HttpStatusCode.NotFound, String.Format("Service {0} not found.", dataSourceName)); - } -#else var appData = System.Web.HttpContext.Current.Server.MapPath("~/App_Data"); var file = System.IO.Path.Combine(appData, dataSourceName + ".xml"); Type dynamicType = DynamicHelper.GetDynamicDbContext(file); -#endif + ApiFactory factory = serviceProvider.GetRequiredService(); factory.ModelType = dynamicType; diff --git a/ApiAsAService/ApiAsAService/Microsoft.OData.Service.ApiAsAService.csproj b/ApiAsAService/ApiAsAService/Microsoft.OData.Service.ApiAsAService.csproj index 05fd787..3a78084 100644 --- a/ApiAsAService/ApiAsAService/Microsoft.OData.Service.ApiAsAService.csproj +++ b/ApiAsAService/ApiAsAService/Microsoft.OData.Service.ApiAsAService.csproj @@ -46,7 +46,7 @@ Always - + Always diff --git a/ApiAsAService/ApiAsAService/Web.config b/ApiAsAService/ApiAsAService/Web.config index 59683e0..4b37290 100644 --- a/ApiAsAService/ApiAsAService/Web.config +++ b/ApiAsAService/ApiAsAService/Web.config @@ -69,7 +69,7 @@ - + diff --git a/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs b/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs index 8fa4101..0f93168 100644 --- a/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs +++ b/ApiAsAService/EdmObjectsGenerator/DbContextGenerator.cs @@ -3,13 +3,11 @@ using System; using System.Collections.Generic; using System.Data.Entity; - using System.Data.Entity.Infrastructure; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text.RegularExpressions; - using System.Threading; using System.Xml; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; @@ -18,9 +16,9 @@ [Serializable] public class DbContextGenerator { - static Dictionary _typeBuildersDict = new Dictionary(); + Dictionary _typeBuildersDict = new Dictionary(); static Regex collectionRegex = new Regex(@"Collection\((.+)\)", RegexOptions.Compiled); - static Queue _builderQueue = new Queue(); + Queue _builderQueue = new Queue(); public class TypeBuilderInfo : MarshalByRefObject { public bool IsDerived { get; set; } @@ -48,7 +46,7 @@ public static IEdmModel ReadModel(string fileName) } } - public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder, string dbContextName) + public void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder, string dbContextName) { //first create the basic types for the enums foreach (var modelSchemaElement in model.SchemaElements) @@ -123,7 +121,7 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder, st //generate the DbContext type - var entitiesBuilder = moduleBuilder.DefineType(dbContextName, TypeAttributes.Class | TypeAttributes.Public, typeof(DbContext)); + var entitiesBuilder = moduleBuilder.DefineType(dbContextName + "Context", TypeAttributes.Class | TypeAttributes.Public, typeof(DbContext)); var dbContextType = typeof(DbContext); entitiesBuilder.CreateDefaultConstructor(dbContextType, "name=" + dbContextName); @@ -152,7 +150,7 @@ public static void BuildModules(IEdmModel model, ModuleBuilder moduleBuilder, st entitiesBuilder.CreateType(); } - internal static TypeBuilder CreateType(IEdmStructuredType targetType, ModuleBuilder moduleBuilder, string moduleName) + internal TypeBuilder CreateType(IEdmStructuredType targetType, ModuleBuilder moduleBuilder, string moduleName) { if (_typeBuildersDict.ContainsKey(moduleName)) { @@ -184,7 +182,7 @@ internal static TypeBuilder CreateType(IEdmStructuredType targetType, ModuleBuil } } - internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilder, string moduleName, bool navPass = false) + internal void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilder, string moduleName, bool navPass = false) { TypeBuilder typeBuilder = null; @@ -231,7 +229,7 @@ internal static void Compile(IEdmStructuredType type, ModuleBuilder moduleBuilde } } - internal static EnumBuilder CreateType(IEdmEnumType targetType, ModuleBuilder moduleBuilder, string moduleName) + internal EnumBuilder CreateType(IEdmEnumType targetType, ModuleBuilder moduleBuilder, string moduleName) { if (_typeBuildersDict.ContainsKey(moduleName)) { @@ -245,7 +243,7 @@ internal static EnumBuilder CreateType(IEdmEnumType targetType, ModuleBuilder mo return typeBuilder; } - internal static void Compile(IEdmEnumType type, ModuleBuilder moduleBuilder, string moduleName) + internal void Compile(IEdmEnumType type, ModuleBuilder moduleBuilder, string moduleName) { var typeBuilder = CreateType(type, moduleBuilder, moduleName); foreach (var enumMember in type.Members) @@ -254,7 +252,7 @@ internal static void Compile(IEdmEnumType type, ModuleBuilder moduleBuilder, str } } - internal static void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder, int? keyIndex = null) + internal void GenerateProperty(IEdmProperty property, TypeBuilder typeBuilder, ModuleBuilder moduleBuilder, int? keyIndex = null) { var propertyName = property.Name; var emdPropType = property.Type.PrimitiveKind(); @@ -373,7 +371,7 @@ private static Type GetPrimitiveClrType(EdmPrimitiveTypeKind typeKind, bool isNu // could also use Domain.SetData()/Domain.GetData() on the new domain public string csdlFileName { get; set; } - public Type GenerateDbContext(string csdlFileName) + public Type GenerateDbContext(string csdlFileName, bool save=false) { // Get name for assembly string dbContextName = csdlFileName.Split('\\').Last().Replace(".xml", ""); @@ -390,15 +388,24 @@ public Type GenerateDbContext(string csdlFileName) // Build Assembly AssemblyName assembly_Name = new AssemblyName(assemblyName); - AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assembly_Name, AssemblyBuilderAccess.RunAndSave); + AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assembly_Name, AssemblyBuilderAccess.RunAndCollect); ModuleBuilder module = assemblyBuilder.DefineDynamicModule($"{assembly_Name.Name}"); - BuildModules(model, module, dbContextName); - //assemblyBuilder.Save($"{assembly_Name.Name}.dll"); + DbContextGenerator generator = new DbContextGenerator(); + generator.BuildModules(model, module, dbContextName); + + // save for debugging purposes + if (save) + { + assemblyBuilder.Save($"{assembly_Name.Name}.dll"); + } + assembly = appDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == assemblyName); } + // Return generated DbContext - return assembly.GetTypes().FirstOrDefault(t => t.Name == dbContextName); + Type dbContext = assembly.GetTypes().FirstOrDefault(t => t.Name == dbContextName + "Context"); + return dbContext; } public void GenerateDbContextInANewAppDomain() @@ -414,17 +421,4 @@ public void GenerateDbContextInANewAppDomain() domain.SetData("contextType", dbContextType); } } - - public class Program - { - // support cmd-line for testing - static void Main(string[] args) - { - //TODO: grab edm path and assembly name from cmdline args - string csdlFile = @"Trippin.xml"; - - DbContextGenerator generator = new DbContextGenerator(); - generator.GenerateDbContext(csdlFile); - } - } } diff --git a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs index 7618baf..4d09bc1 100644 --- a/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs +++ b/ApiAsAService/RESTier/src/Microsoft.Restier.AspNet/ApiFactory.cs @@ -1,15 +1,7 @@ -using Microsoft.AspNet.OData.Extensions; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Restier.Core; -using Microsoft.Restier.Core.Model; +using Microsoft.Restier.Core; using Microsoft.Restier.EntityFramework; using System; -using System.Collections.Generic; using System.Data.Entity; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Web; namespace Microsoft.Restier.AspNet { From d280004ad0781447bfe46296d50761a5431629e9 Mon Sep 17 00:00:00 2001 From: mikepizzo Date: Mon, 11 Sep 2023 10:54:01 -0700 Subject: [PATCH 21/21] Update databases --- .../ApiAsAService/App_Data/Northwind.mdf | Bin 8388608 -> 8388608 bytes .../ApiAsAService/App_Data/Trippin.mdf | Bin 5570560 -> 5570560 bytes ApiAsAService/ApiAsAService/Web.config | 10 +++++----- ApiAsAService/ReadMe.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ApiAsAService/ApiAsAService/App_Data/Northwind.mdf b/ApiAsAService/ApiAsAService/App_Data/Northwind.mdf index 3f87daca266fcc2eab9a29e60ca355ed0552590a..b215b281a20488f75e21ad6b5f93d6e428f7475b 100644 GIT binary patch delta 8125 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 delta 5459 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 Y{R^rZH4#;VdIB{G^(475a - - - + + + - + \ No newline at end of file diff --git a/ApiAsAService/ReadMe.md b/ApiAsAService/ReadMe.md index 19e37f2..26d73f9 100644 --- a/ApiAsAService/ReadMe.md +++ b/ApiAsAService/ReadMe.md @@ -1 +1 @@ -# API As A Service ----------------------- This service, when run, loads an .xml schema based on the path and dynamically generates types necessary to expose an OData service over the schema using RESTier and WebAPI. ## Samples http://localhost:18384/Trippin - Trippin service document http://localhost:18384/Trippin/$metadata - Trippin metadata document http://localhost:18384/Trippin/Airlines - Trippin airlines http://localhost:18384/NWind - NWind service document http://localhost:18384/NWind/$metadata - NWind metadata document http://localhost:18384/Trippin/Airlines - Trippin airlines \ No newline at end of file +# API As A Service ----------------------- This service, when run, loads an .xml schema based on the path and dynamically generates types necessary to expose an OData service over the schema using RESTier and WebAPI. ## Samples http://localhost:18384/Trippin - Trippin service document http://localhost:18384/Trippin/$metadata - Trippin metadata document http://localhost:18384/Trippin/Airlines - Trippin airlines http://localhost:18384/NWind - NWind service document http://localhost:18384/NWind/$metadata - NWind metadata document http://localhost:18384/NWind/Products - NWind Products \ No newline at end of file

    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 WE$~R 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 zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM Y7%*VKfB^#r3>YwAz<>b*1`PZL0tAlYrvLx| literal 0 HcmV?d00001 diff --git a/ApiAsAService/Trippin/Trippin/App_Data/Trippin.xml b/ApiAsAService/ApiAsAService/App_Data/Trippin.xml similarity index 100% rename from ApiAsAService/Trippin/Trippin/App_Data/Trippin.xml rename to ApiAsAService/ApiAsAService/App_Data/Trippin.xml diff --git a/ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs b/ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/App_Start/WebApiConfig.cs rename to ApiAsAService/ApiAsAService/App_Start/WebApiConfig.cs diff --git a/ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs b/ApiAsAService/ApiAsAService/Controllers/DynamicApiController.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/Controllers/DynamicApiController.cs rename to ApiAsAService/ApiAsAService/Controllers/DynamicApiController.cs diff --git a/ApiAsAService/Trippin/Trippin/CustomizedPayloadValueConverter.cs b/ApiAsAService/ApiAsAService/CustomizedPayloadValueConverter.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/CustomizedPayloadValueConverter.cs rename to ApiAsAService/ApiAsAService/CustomizedPayloadValueConverter.cs diff --git a/ApiAsAService/Trippin/Trippin/DynamicHelper.cs b/ApiAsAService/ApiAsAService/DynamicHelper.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/DynamicHelper.cs rename to ApiAsAService/ApiAsAService/DynamicHelper.cs diff --git a/ApiAsAService/Trippin/Trippin/DynamicODataRoute.cs b/ApiAsAService/ApiAsAService/DynamicODataRoute.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/DynamicODataRoute.cs rename to ApiAsAService/ApiAsAService/DynamicODataRoute.cs diff --git a/ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs b/ApiAsAService/ApiAsAService/DynamicRouteConstraint.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/DynamicRouteConstraint.cs rename to ApiAsAService/ApiAsAService/DynamicRouteConstraint.cs diff --git a/ApiAsAService/Trippin/Trippin/Global.asax b/ApiAsAService/ApiAsAService/Global.asax similarity index 100% rename from ApiAsAService/Trippin/Trippin/Global.asax rename to ApiAsAService/ApiAsAService/Global.asax diff --git a/ApiAsAService/Trippin/Trippin/Global.asax.cs b/ApiAsAService/ApiAsAService/Global.asax.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/Global.asax.cs rename to ApiAsAService/ApiAsAService/Global.asax.cs diff --git a/ApiAsAService/Trippin/Trippin/Helpers.cs b/ApiAsAService/ApiAsAService/Helpers.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/Helpers.cs rename to ApiAsAService/ApiAsAService/Helpers.cs diff --git a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj b/ApiAsAService/ApiAsAService/Microsoft.OData.Service.ApiAsAService.csproj similarity index 78% rename from ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj rename to ApiAsAService/ApiAsAService/Microsoft.OData.Service.ApiAsAService.csproj index 6a20943..05fd787 100644 --- a/ApiAsAService/Trippin/Trippin/Microsoft.OData.Service.ApiAsAService.csproj +++ b/ApiAsAService/ApiAsAService/Microsoft.OData.Service.ApiAsAService.csproj @@ -84,65 +84,65 @@ - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll True - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll True - ..\..\packages\Microsoft.Extensions.DependencyInjection.2.2.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll + ..\packages\Microsoft.Extensions.DependencyInjection.2.2.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll - ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - ..\..\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + ..\packages\Microsoft.OData.Core.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll - ..\..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll + ..\packages\Microsoft.OData.Edm.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll - ..\..\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll + ..\packages\Microsoft.Spatial.7.6.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll - ..\..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - ..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll True - ..\..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll + ..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll True - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - ..\..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll True True - ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll True - ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll True - ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll True - ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll True @@ -150,32 +150,32 @@ - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll True - + {3678c588-f1dd-4ef6-879a-3d1d8114a880} EdmObjectsGenerator - + {8ecf4e97-1816-44ad-ad63-6acf287ed520} Microsoft.Restier.AspNet - + {300b769a-3513-49d0-a035-7db965c8d2a4} Microsoft.Restier.Core - + {0e373b2a-2ed2-4566-a275-6be81cffe00b} Microsoft.Restier.EntityFramework - + {a6f9775d-f7e2-424e-8363-79644a73038f} Microsoft.AspNet.OData diff --git a/ApiAsAService/Trippin/Trippin/Properties/AssemblyInfo.cs b/ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/Properties/AssemblyInfo.cs rename to ApiAsAService/ApiAsAService/Properties/AssemblyInfo.cs diff --git a/ApiAsAService/Trippin/Trippin/Submit/CustomizedSubmitProcessor.cs b/ApiAsAService/ApiAsAService/Submit/CustomizedSubmitProcessor.cs similarity index 100% rename from ApiAsAService/Trippin/Trippin/Submit/CustomizedSubmitProcessor.cs rename to ApiAsAService/ApiAsAService/Submit/CustomizedSubmitProcessor.cs diff --git a/ApiAsAService/Trippin/Trippin/Web.Debug.config b/ApiAsAService/ApiAsAService/Web.Debug.config similarity index 100% rename from ApiAsAService/Trippin/Trippin/Web.Debug.config rename to ApiAsAService/ApiAsAService/Web.Debug.config diff --git a/ApiAsAService/Trippin/Trippin/Web.Release.config b/ApiAsAService/ApiAsAService/Web.Release.config similarity index 100% rename from ApiAsAService/Trippin/Trippin/Web.Release.config rename to ApiAsAService/ApiAsAService/Web.Release.config diff --git a/ApiAsAService/Trippin/Trippin/Web.config b/ApiAsAService/ApiAsAService/Web.config similarity index 100% rename from ApiAsAService/Trippin/Trippin/Web.config rename to ApiAsAService/ApiAsAService/Web.config diff --git a/ApiAsAService/Trippin/Trippin/packages.config b/ApiAsAService/ApiAsAService/packages.config similarity index 100% rename from ApiAsAService/Trippin/Trippin/packages.config rename to ApiAsAService/ApiAsAService/packages.config diff --git a/ApiAsAService/Trippin/ReadMe.txt b/ApiAsAService/Trippin/ReadMe.txt deleted file mode 100644 index d6e7d1e..0000000 --- a/ApiAsAService/Trippin/ReadMe.txt +++ /dev/null @@ -1,32 +0,0 @@ -ODataServiceSample ------------------- - -This sample illustrates how to create an OData service with RESTier. -It requires to be run with VS 2015 as it uses LocalDB, it will set up DB automatically, user just need to import the solution into VS 2015, build and run. In addition the OData service exposes a $metadata document which allows the data to the consumed by OData Client for .NET clients and other clients that accept the $metadata format. - -The data model contains several entity sets and operations. -User can practice with the sample like, (root URL is http://localhost:18384/api/trippin/) - GET /People - GET /People(key) - GET /People?$filter=..&$orderby=..&$top=..&$skip=.. - PATCH /People(key) - POST /People - GET /People(key)/Microsoft.OData.Service.Sample.Trippin.Models.GetNumberOfFriends - POST /Trips({key})/Microsoft.OData.Service.Sample.Trippin.Models.EndTrip - GET /GetPersonWithMostFriends - POST /ResetDataSource - -Furthermore, the service exposes a Service Document (aka. $metadata document) that -lists all the top-level entities so clients can discover them. This enables OData clients -to discover and consume OData Services exposed. - -For a detailed description of RESTier, refer to document http://odata.github.io/RESTier/. - -For the source code of RESTier, refer to https://github.com/OData/RESTier/. - -Any questions, ask on [Stack Overflow](http://stackoverflow.com/questions/ask?tags=odata). - -Any issues or feature requests, report on [Github issues](https://github.com/OData/RESTier/issues). - -Contribution is also highly welcomed. Please refer to the [CONTRIBUTING.md](https://github.com/OData/RESTier/blob/master/.github/CONTRIBUTING.md) for more details. - diff --git a/ApiAsAService/Trippin/Trippin.sln b/ApiAsAService/Trippin/Trippin.sln deleted file mode 100644 index e3c7673..0000000 --- a/ApiAsAService/Trippin/Trippin.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OData.Service.Sample.Trippin", "Trippin\Microsoft.OData.Service.Sample.Trippin.csproj", "{B379640E-9064-438D-8DA5-6F7B394C2C46}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B379640E-9064-438D-8DA5-6F7B394C2C46}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/ApiAsAService/Trippin/Trippin/Models/Airline.cs b/ApiAsAService/Trippin/Trippin/Models/Airline.cs deleted file mode 100644 index cdd5937..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Airline.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using System.ComponentModel.DataAnnotations; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Airline - { - [Key] - public string AirlineCode { get; set; } - - public string Name { get; set; } - - [Timestamp] - public Byte[] TimeStampValue { get; set; } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/Airport.cs b/ApiAsAService/Trippin/Trippin/Models/Airport.cs deleted file mode 100644 index 85fe3ca..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/Airport.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System.ComponentModel.DataAnnotations; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - public class Airport - { - public string Name { get; set; } - - [Key] - public string IcaoCode { get; set; } - - public string IataCode { get; set; } - } -} diff --git a/ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs b/ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs deleted file mode 100644 index 4a3a9f0..0000000 --- a/ApiAsAService/Trippin/Trippin/Models/CustomizedPayloadValueConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Microsoft.OData.Edm; - -namespace Microsoft.OData.Service.ApiAsAService.Models -{ - ///

  • 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

  • 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

    !qc{WwrioZ(*avXhIustffhVR9r2$ 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%0A7Z{hTpEGPZ(XlgCLgdy=lEAcCBpL5!kR%q) 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